[{"content":" 🇺🇸 Author(s) or some of authors are not native speaker(s) of English. Text may contain bad spelling, incorrect expressions, verbal turns, sentence constructions, etc. 📜 Changelog 01 Nov. 2026: Add: More benchmarks 2026 October 28 Oct. 2026 Update: Additional information about VM freezes on shutdown/reboot and 100% CPU Add: Problem X in Windows because of Y 15 Oct. 2026 Update: Additional information about Peripherals \u0026ndash; Audio 2026 July-August 21 Aug. 2026 Add: Passing additional devices to the VM 20 Jul. 2026 Update: Additional information about TPM Add: How to update QEMU/KVM udev symlinks (merging devices into one) 📝 Disclaimers / Information 🧠 About AI.\nYes, I used AI, and no, AI did not write this text.\nThis text was written entirely by me, from start to finish.\nAI was used to search for and verify information, as well as to write scripts. But with one key distinction \u0026ndash; I understand what I\u0026rsquo;m doing (well, most), rather than mindlessly copying and pasting.\nThis is NOT compilation of information, it\u0026rsquo;s exactly what I did myself.\n👀 This is a pretty complex topic, and making it work for everyone\u0026hellip; I don\u0026rsquo;t know how.\nWhat you\u0026rsquo;re about to read is one particular path, one, but I\u0026rsquo;ve walked through dozens of them, tried A LOT of stuff. So you\u0026rsquo;ll need at least some basic knowledge.\nThis is a long text, full of commands and scripts. I did all of this while writing and describing it at the same time, and it took more than one day.\nIf you find a mistake here, or something doesn\u0026rsquo;t work for you even though it DEFINITELY should \u0026ndash; contact me.\nIf you run into issues with the XML, here\u0026rsquo;s my full XML file for reference.\nDifferent hardware (not covered here), a different distro, a different environment/WM, something else entirely different \u0026mdash; that\u0026rsquo;s not a reason to text me. Although\u0026hellip; I might take a look.\nForeword If you ended up here, then:\nYou were sent here (I don\u0026rsquo;t envy you) You know what this is about But if by chance it\u0026rsquo;s neither, for those who don\u0026rsquo;t know what VFIO is, let me explain.\nVFIO (Virtual Function I/O) \u0026mdash; is a framework/Linux kernel driver that provides safe direct access to hardware from user space. Put simply, it\u0026rsquo;s a way to hand a PC component directly into a virtual machine, that is, PCI-passthrough. Well, no need to explain what PCI is, right?\nAnd what is this about? Say you have Windows, and on Windows you, for example, play games. Windows uses your hardware, let it be a graphics card. And it uses it well, because it works with the graphics card directly. But what if you don\u0026rsquo;t want to use Windows, and want Linux, and for some reason, you don\u0026rsquo;t want to run games on Linux or you want to run games on Windows; or you need Adobe, CAD, etc.\nThe obvious solution is dual-boot! Damn it to hell. It\u0026rsquo;s not cool, it\u0026rsquo;s not interesting, it\u0026rsquo;s slow.\nAnother obvious solution \u0026ndash; virtualization. Install a Windows virtual machine inside Linux. But then there\u0026rsquo;s a problem, virtualization:\nHOST (Linux) [hardware] ↓ hypervisor/paravirtualization (VirtIO) ↓ GUEST (Windows) [virt. hardware] It\u0026rsquo;s such a f*cking layer of fat between the hardware and the OS (a thinner one with VirtIO), I/O (Input/Output) is in the shit, we\u0026rsquo;re in the shit, everything lags. Not good.\nThis is why VFIO exists, but it has mutated from some kind of technology into what\u0026rsquo;s by now a bro term category/topic, which can cover not only VFIO itself by definition, but something bigger, which is what this will be about. So it comes out like this:\nHOST (Linux) [hardware (was here)] ↓ GUEST (Windows) [hardware (now here)] Cool, right?\nIn this text I\u0026rsquo;ll show how I set up KVM on Linux openSUSE Tumbleweed from start to finish + extra features (Quality-of-Life). Of the main things, it\u0026rsquo;s of course passthrough and/or efficient use of:\nSSD GPU CPU RAM Right from the start, keep in mind, I have an iGPU and a dGPU, and this text was written specifically for that scenario. If you don\u0026rsquo;t have two GPUs, iGPU + dGPU or dGPU + dGPU, this text isn\u0026rsquo;t for you. And in that case it\u0026rsquo;s worth reading it only maybe for general knowledge and the scripts.\nAlso, it\u0026rsquo;s important to mention, if you\u0026rsquo;re reading this with a clear intention and desire to do it, you\u0026rsquo;re probably mentally ready for it, and you have the hardware. Weak hardware will obviously not be great, but technically speaking, in this particular case, what matters isn\u0026rsquo;t raw power but quantity \u0026ndash; lots of RAM, cores and ideally a separate disk.\nNOT having a second GPU is not covered in this text, that\u0026rsquo;s a completely different approach, because with ONE GPU you \u0026ldquo;use\u0026rdquo; EITHER the host OR the guest, while this text is built around using both, at the same time.\n🚧 This text describes AMD CPU + NVIDIA GPU, if you have an Intel processor \u0026ndash; there won\u0026rsquo;t be any problems, the differences are minor and I\u0026rsquo;ve pointed them out. But if you have an AMD GPU, you\u0026rsquo;ll have to think, think some more, and google, because I don\u0026rsquo;t have an AMD GPU to check what and how, to say exactly how it should be done. So. I chose openSUSE Tumbleweed + KDE Plasma. Why? We need the newest releases of everything \u0026ndash; a rolling-release scheme, in order to pull off this trick with a VM running at 1:1 performance compared to bare metal. Debian and other LTS distros don\u0026rsquo;t really fit the bill.\nThe obvious option is Arch. A developer pushed a software update \u0026ndash; you installed it, just what you need. But because of this Arch often breaks, and you have to keep an eye on the news feed of updates to check \u0026ldquo;is my system going to break after this update, and what do I need to do manually\u0026rdquo;, not cool.\nThere\u0026rsquo;s Void, which is like Arch, except updates are automatically tested, so the chance that the system will go down after an update is much lower. But Void uses runit instead of systemd, which for some people is a plus, but systemd is mega-popular, everything is built for it, do you need it, considering how complex the topic is? And also, I heard it\u0026rsquo;s mostly old stuff/meager repositories there, everything new you have to build by hand.\nAnd the third option \u0026mdash; openSUSE Tumbleweed, which is like Void, but with systemd and the newest software, plus a built-in YaST GUI application through which you can configure the system and set up a KVM literally in one click.\nAs for KDE Plasma, that\u0026rsquo;s because it\u0026rsquo;s the most feature-packed and simple. For the time being + tests \u0026ndash; just the thing. At the time of writing this text, only in KDE Plasma does 4K HDR 320 Hz work properly, or something like that, maybe it matters to someone.\nAnd absolutely everything will be described, said, and shown in the context of Tumbleweed and KDE Plasma. So if you have a different distro/DE/WM and you have no clue about Linux whatsoever and have never f*cked around with this miracle \u0026ndash; don\u0026rsquo;t do this, at least not on your working OS.\nBut if you do know what you\u0026rsquo;re doing, and you\u0026rsquo;re NOT on KDE Plasma OR you\u0026rsquo;re exclusively interested in cold dGPU passthrough \u0026ndash; read this.\nIntroduction About monitors Think about how you\u0026rsquo;re going to look at Windows. There\u0026rsquo;s Looking Glass, of course, and I\u0026rsquo;ll even show how to set it up, but for games, especially action games, it\u0026rsquo;s terrible + it adds dGPU overhead. In my case, 2 monitors (both FULLHD):\nHost:\nPrimary \u0026ndash; HDMI 1.4 Secondary \u0026ndash; DP Guest:\nPrimary \u0026ndash; DP Secondary \u0026ndash; HDMI 1.4 So it\u0026rsquo;s a crossed connection, and that\u0026rsquo;s because of a lack of inputs, both on the monitors and on the motherboard \u0026ndash; if it were up to me, I\u0026rsquo;d plug everything into DP.\nI mentioned the HDMI version for a reason, mine is 1.4, and that\u0026rsquo;s a max of 120 Hz (albeit via software), while my primary monitor is 144 Hz.\nLogic #1, as simple and logical as it gets: make the host\u0026rsquo;s primary monitor DP, in work I\u0026rsquo;ll see the full 144 Hz, but I highly doubt games will actually give me 144 FPS, so HDMI at 120 Hz for the guest.\nThat\u0026rsquo;s exactly what I did at first, forgetting that NVIDIA doesn\u0026rsquo;t work with FreeSync, G-Sync, and other Sync tech over HDMI \u0026lt;2.1, and even then it\u0026rsquo;s janky, as far as I know.\nSo, Logic #2: the other way around \u0026ndash; the host isn\u0026rsquo;t as smooth, yes, but the difference between 120 vs 144 isn\u0026rsquo;t that noticeable, unlike say 60 vs 75, and meanwhile all the Sync stuff works with NVIDIA over DP.\nIn the BIOS, aka UEFI Firmware, you obviously need to enable hardware virtualization for your CPU and enable IOMMU. How to do this \u0026ndash; google it for your specific processor and motherboard manufacturer, everyone\u0026rsquo;s setup is different here.\nAlso enable IOMMU in the OS (this does not replace the BIOS settings):\nYaST \u0026ndash;\u0026gt; System \u0026ndash;\u0026gt; Boot Loader, add to the end: amd_iommu=force_isolation iommu=pt. If you have Intel, then instead of amd_iommu=force_isolation \u0026ndash;\u0026gt; intel_iommu=on.\nReboot.\nCheck the parameters and whether IOMMU is enabled:\nsudo sh -c \u0026#39;dmesg | grep -i iommu \u0026amp;\u0026amp; echo \u0026amp;\u0026amp; dmesg | grep -E \u0026#34;Intel-IOMMU|AMD-Vi\u0026#34;\u0026#39; You can check whether you won the lottery like this:\nsh -c \u0026#39;for g in $(find /sys/kernel/iommu_groups/* -maxdepth 0 -type d | sort -V); do echo \u0026#34;IOMMU Group ${g##*/}:\u0026#34;; for d in $(ls -1 $g/devices); do lspci -nns $d; done; echo; done\u0026#39; If every piece of hardware, or at least the pieces you need for passthrough, are in their own separate IOMMU group \u0026ndash; you got lucky. Namely, that\u0026rsquo;s the SSD you need and the GPU you need, plus anything else you want, optionally. If not, you\u0026rsquo;ll have to apply the ACS patch (I\u0026rsquo;ll describe this once I have enough information).\nAs for the SSD, it might happen that, say, you have 3 SSD slots, but 2 of them are in one group and 1 is in a separate one, you already know what to do here, right?\nVirtualization YaST \u0026ndash;\u0026gt; Virtualization \u0026ndash;\u0026gt; Install Hypervisor and Tools - KVM server and KVM tools.\nIf YaST offers to create a bridge during installation, agree to it, if there\u0026rsquo;s an error \u0026ndash; it doesn\u0026rsquo;t matter.\nAfter installation, check:\nsudo systemctl status virtqemud And also add yourself to the groups:\nsudo usermod -aG libvirt,kvm $(whoami) This is needed so that later you can passthrough PCI devices, pin CPU cores, set up hugepages, and other stuff.\nReboot, and you can check that you\u0026rsquo;re in the group like this:\ngroups \u0026amp;\u0026amp; grep -E \u0026#39;libvirt|kvm\u0026#39; /etc/group And also, for W11:\nls /usr/share/qemu/ovmf* There should be /usr/share/qemu/ovmf-x86_64-smm-ms-code.bin, that\u0026rsquo;s SMM + Secure Boot.\nCreating the virtual machine Open Virtual Machine Manager and create a new VM, Local install media (ISO), yes, you need to have a Windows image. RAM/CPU whatever, doesn\u0026rsquo;t matter right now. Storage \u0026ndash; this part matters, uncheck Enable storage for this VM. And it\u0026rsquo;s IMPORTANT to check Customize configuration before install.\nIf \u0026ldquo;Virtual Network is not active\u0026rdquo; \u0026ndash; sudo virsh net-autostart default and click \u0026ldquo;Yes\u0026rdquo;.\nOverview:\nChipset: Q35 Firmware: /usr/share/qemu/ovmf-x86_64-smm-ms-code.bin CPU:\nCopy host CPU configuration (host-passthrough) Add Hardware \u0026ndash;\u0026gt; TPM. Let\u0026rsquo;s slow down here for a second. You\u0026rsquo;ve got two options, either passthrough, or not. Are there any downsides or upsides to passing through the TPM? Well, in the day-to-day life of an average person \u0026ndash; not really.\nPassthrough is basically synonymous with exclusive access, and there\u0026rsquo;s only one TPM. Because of this, errors can start popping up, for example, you start the VM, everything works, you shut down the VM, start it again, and now it\u0026rsquo;s a coin flip:\nIt\u0026rsquo;ll start '/dev/fdset/0' is not a TPM device \u0026ndash; can be fixed with a host reboot, not cool You can check whether the TPM is being used by the host like this:\nsudo journalctl -k -b 0 | grep -i \u0026#34;ima\\|tpm\u0026#34; If you see some activity (more than a couple of lines after the OS boots, but some usage), then it\u0026rsquo;s being used by the host.\nIf you\u0026rsquo;re willing to take the risk, or the TPM isn\u0026rsquo;t being used by the host, then why not:\nType: Passthrough Model: CRB (2.0 only) / TIS Version: CRB - 2.0 / TIS - 2.0/1.2 If not, it\u0026rsquo;s better to emulate it:\nType: Emulated Model: if TPM 2.0 support is available - CRB, otherwise - TIS Version: CRB - 2.0 / TIS - 2.0/1.2 If you have two TPMs in the list, delete the one you didn\u0026rsquo;t create.\nDisk passthrough There are two ways to passthrough a disk. The universal one, and, if you got lucky with IOMMU groups \u0026ndash; VFIO.\nCheck the IOMMU groups:\nsh -c \u0026#39;for g in $(find /sys/kernel/iommu_groups/* -maxdepth 0 -type d | sort -V); do echo \u0026#34;IOMMU Group ${g##*/}:\u0026#34;; for d in $(ls -1 $g/devices); do lspci -nns $d; done; echo; done\u0026#39; If you see something like this:\n1 2 3 IOMMU Group 24: 04:08.0 PCI bridge [0604]: Advanced Micro Devices, Inc. [AMD] 600 Series Chipset PCIe Switch Downstream Port [1022:43f5] (rev 01) 0d:00.0 Non-Volatile memory controller [0108]: Kingston Technology Company, Inc. KC3000/FURY Renegade NVMe SSD [E18] [2646:5013] (rev 01) Or something like this:\n1 2 IOMMU Group 27: 10:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd NVMe SSD Controller SM981/PM981/PM983 [144d:a808] And that\u0026rsquo;s it, if there\u0026rsquo;s NOTHING else in that group, you got very lucky, you can passthrough the disk in the most direct way.\nIn the first case, the disk\u0026rsquo;s bridge is in the group together with the disk itself, that\u0026rsquo;s totally fine to passthrough, but if, say, there\u0026rsquo;s a couple of USB devices, another disk, a network card, or something else in there, then no, don\u0026rsquo;t do that.\nThe universal method is via the VirtIO driver, it\u0026rsquo;s not bad, but the latency is higher, you\u0026rsquo;ll lose about 3-5% of linear speed (writing/moving files), and a bit more loss on random write/read (video editing, rendering, gaming, etc.) compared to bare metal.\nYou can also try one method first and then the other, without recreating the VM or reinstalling Windows, both will work.\nLet\u0026rsquo;s do it:\nlsblk -o NAME,SIZE,TYPE,MOUNTPOINT,MODEL Output:\n1 2 3 4 5 NAME SIZE TYPE MOUNTPOINT MODEL nvme1n1 1.9T disk KINGSTON SKC3000D2048G ├─nvme1n1p1 16M part └─nvme1n1p2 1.9T part ... Check that this is the disk you need and that it has no mount points.\nNext:\nls -la /dev/disk/by-id/ | grep nvme1n1 We need something like nvme-KINGSTON_SKC3000D2048G_xxxx, without _x or -x. Write this down.\nThis is how we find the disk\u0026rsquo;s serial number, needed to bind the disk by serial (VirtIO) rather than by name. In Linux, whichever disk gets initialized first becomes that one. This means that what you specify in the VM settings can change after the host reboots, it won\u0026rsquo;t change if you use the serial number.\nVirtIO (universal) Download the virtio-win driver. It\u0026rsquo;s needed so Windows can see your disk.\nGo to Add Hardware \u0026ndash;\u0026gt; Storage:\nSelect or create custom storage Device type: Disk device Bus type: VirtIO Manage \u0026ndash;\u0026gt; /dev/disk/by-id/nvme-KINGSTON_SKC3000D2048G_xxxx (see above) Advanced options: Cache mode: none Discard mode: unmap Add Hardware \u0026ndash;\u0026gt; Storage again:\nDevice type: CDROM virtio-win.iso VFIO If you haven\u0026rsquo;t installed Windows yet, skip this step; if you have already installed it but want VFIO, boot into Windows, open the terminal as admin:\nbcdedit /set {current} safeboot minimal Shut down the VM and delete the current VirtIO disk.\nAdd Hardware \u0026ndash;\u0026gt; PCI Host Device, here you need to find the SSD you need, yes, it\u0026rsquo;ll be a bit of a hassle, but once you find it, click on it and Finish. From now on it will show up not as a disk, but as a \u0026ldquo;PCI ID\u0026rdquo; in the left panel, don\u0026rsquo;t lose track of it.\n🚧 WARNING! This is NOT the disk\u0026rsquo;s ID, NOT its serial number, it\u0026rsquo;s the PCI slot ID.\nIf you move the disk to a different slot, you\u0026rsquo;ll need to add it again.\nBut more IMPORTANTLY, if you put a different disk in its place, it will be automatically passed through (if you haven\u0026rsquo;t done isolation \u0026ndash; then it won\u0026rsquo;t be automatic, only when the VM starts). So if you swap it, say, with the host\u0026rsquo;s system disk, that\u0026rsquo;s going to be sad.\nBefore doing anything like that, remove the disk from the VM, then add it again!\nGo to Boot Options and select that same \u0026ldquo;PCI ID\u0026rdquo; there. That\u0026rsquo;s it. Simple, right?\nWindows should boot into safe mode, It\u0026rsquo;s a good idea to boot into safe mode when the system disk changes, the disk is the same, but for Windows it used to be Red Hat and now it isn\u0026rsquo;t Red Hat anymore \u0026ndash; that counts as a disk change. exit safe mode:\nbcdedit /deletevalue {current} safeboot Now in Task Manager, under Performance, you\u0026rsquo;ll see your actual disk instead of some Red Hat thing.\nSince we didn\u0026rsquo;t do isolation, the passthrough is dynamic: when the VM is off, the host sees the disk, when it\u0026rsquo;s on, the host doesn\u0026rsquo;t see the disk. There\u0026rsquo;s not much point in doing isolation, it doesn\u0026rsquo;t add anything. But if the system suddenly decides to do something with the disk right at the moment the VM starts, worst case you get a kernel panic :D\nBut that\u0026rsquo;s not a big deal, nothing will break. The most important thing is that the disk isn\u0026rsquo;t mounted while the VM is starting up, otherwise the filesystem could get destroyed :)\nOnce the disk is ALREADY in the VM, the host won\u0026rsquo;t do anything to it (unless you go around flipping the VM on/off like a light switch). If you still want to isolate it, see About VFIO Passthrough.\nHere\u0026rsquo;s a small speed comparison:\nBare metal: 6500/6300 VirtIO: 6300/6100 VFIO: 6420/6330 Installing Windows If you made a VirtIO disk, then at the Windows installation stage, specifically at the disk selection step, horror will befall you, but don\u0026rsquo;t worry, click Load Driver \u0026ndash;\u0026gt; \u0026ldquo;virtio-win\\viostor\\w11\\amd64\u0026rdquo; and install that. Well, and then as usual.\n📝 During installation, if it asks you to press something in white text on black again \u0026ndash; don\u0026rsquo;t press anything, or you\u0026rsquo;ll end up back in the Windows installer. After installing the OS, you can remove the VirtIO \u0026amp; Windows ISO from the devices, you don\u0026rsquo;t need them anymore.\nAnd a small spoiler, even though it\u0026rsquo;s off-topic, still check Problem X in Windows because of Y!\nPeripherals \u0026ndash; Keyboard and Mouse Sooner or later, this will have to be solved, and properly \u0026ndash; evdev passthrough.\nFind the devices you need (you need mouse and kbd):\nls /dev/input/by-id/ | grep event In my case that\u0026rsquo;s usb-COMPANY_USB_Device-if01-event-mouse and usb-I-CHIP_MMD_KM40_Receiver_1234-event-kbd.\nYou can check that it\u0026rsquo;s the right device like this:\nsudo evtest /dev/input/by-id/usb-COMPANY_USB_Device-if01-event-mouse And the same for the keyboard. Press some buttons/keys/move the mouse around, if you see a ton of log output, that\u0026rsquo;s it, that\u0026rsquo;s the one.\nAdd the devices to cgroup_device_acl (you\u0026rsquo;ll have to search for it and edit it, or just add your own \u0026ndash; it\u0026rsquo;s usually all commented out by default):\nsudo nano /etc/libvirt/qemu.conf 1 2 3 4 5 6 7 cgroup_device_acl = [ \u0026#34;/dev/null\u0026#34;, \u0026#34;/dev/full\u0026#34;, \u0026#34;/dev/zero\u0026#34;, \u0026#34;/dev/random\u0026#34;, \u0026#34;/dev/urandom\u0026#34;, \u0026#34;/dev/ptmx\u0026#34;, \u0026#34;/dev/kvm\u0026#34;, \u0026#34;/dev/rtc\u0026#34;, \u0026#34;/dev/hpet\u0026#34;, \u0026#34;/dev/input/by-id/usb-COMPANY_USB_Device-if01-event-mouse\u0026#34;, \u0026#34;/dev/input/by-id/usb-I-CHIP_MMD_KM40_Receiver_1234-event-kbd\u0026#34; ] sudo sh -c \u0026#39;systemctl restart virtqemud \u0026amp;\u0026amp; systemctl status virtqemud\u0026#39; In Virtual Machine Manager \u0026ndash;\u0026gt; Edit \u0026ndash;\u0026gt; Preferences: Enable XML editing.\nOpen the VM settings, go to Overview \u0026ndash;\u0026gt; XML, at the end look for \u0026lt;/devices\u0026gt; and inside that tag add:\n1 2 3 4 5 6 \u0026lt;input type=\u0026#39;evdev\u0026#39;\u0026gt; \u0026lt;source dev=\u0026#39;/dev/input/by-id/usb-COMPANY_USB_Device-if01-event-mouse\u0026#39;/\u0026gt; \u0026lt;/input\u0026gt; \u0026lt;input type=\u0026#39;evdev\u0026#39;\u0026gt; \u0026lt;source dev=\u0026#39;/dev/input/by-id/usb-I-CHIP_MMD_KM40_Receiver_1234-event-kbd\u0026#39; grab=\u0026#39;all\u0026#39; repeat=\u0026#39;on\u0026#39;/\u0026gt; \u0026lt;/input\u0026gt; Apply, the text will \u0026ldquo;disappear\u0026rdquo;, but it hasn\u0026rsquo;t actually disappeared, it just moved up higher. When the VM starts, input from the host will immediately disappear and go into the VM. To switch back, use LCtrl + RCtrl.\n📝 Media keys won\u0026rsquo;t work in the guest, to make them work you\u0026rsquo;d need to add them too, for example, mine are if01, but I don\u0026rsquo;t recommend doing that, because later on, in the audio setup section, we\u0026rsquo;ll make it nice and clean anyway.\nAlso note that a dongle and a USB cable are different things, if you only add one of them, the other one won\u0026rsquo;t work (depends on the device\u0026rsquo;s behavior).\nUsing a keyboard as an example, say you only added the dongle:\nOnly the dongle is enabled \u0026ndash; works Both the dongle and the cable are enabled, but the keyboard specifically switches to working over the cable \u0026ndash; doesn\u0026rsquo;t work Both the dongle and the cable are enabled, but the keyboard is just charging via the cable while actually working through the dongle \u0026ndash; works If you decide to add both, it won\u0026rsquo;t work \u0026ndash; if one of them is disabled, the VM will refuse to start. This is solved via udev symlinks, more on that below.\nudev symlinks (merging devices into one) I\u0026rsquo;ll show this using a keyboard as an example. Connect the keyboard through every method you want to merge, or if using one method causes the other to \u0026ldquo;drop off\u0026rdquo; and vice versa, just do what\u0026rsquo;s described below one at a time.\nCheck the list of devices again:\nls /dev/input/by-id/ | grep event 📝 If you\u0026rsquo;re not sure about the device, check it:\nsudo evtest /dev/input/by-id/usb-SmartCloud_MMD_KM40-event-kbd For me, pressing regular keys doesn\u0026rsquo;t write anything to the log, while:\nsudo evtest /dev/input/by-id/usb-SmartCloud_MMD_KM40-if02-event-kbd Does write to the log. So the device we need is usb-SmartCloud_MMD_KM40-if02-event-kbd.\nIn my case, I end up with:\nusb-I-CHIP_MMD_KM40_Receiver_1234-event-kbd \u0026ndash; the dongle usb-SmartCloud_MMD_KM40-if02-event-kbd \u0026ndash; the cable Next, we do this with them:\nDEV=/dev/input/by-id/usb-I-CHIP_MMD_KM40_Receiver_1234-event-kbd \u0026amp;\u0026amp; echo \u0026#34;- Properties:\u0026#34; \u0026amp;\u0026amp; udevadm info -q property -n \u0026#34;$DEV\u0026#34; | grep -E \u0026#34;ID_VENDOR_ID|ID_MODEL_ID|ID_SERIAL|ID_USB_INTERFACE_NUM|ID_INPUT\u0026#34; \u0026amp;\u0026amp; echo \u0026#34;- Name:\u0026#34; \u0026amp;\u0026amp; udevadm info -a -n \u0026#34;$DEV\u0026#34; | grep -m1 \u0026#39;ATTRS{name}\u0026#39; DEV=/dev/input/by-id/usb-SmartCloud_MMD_KM40-if02-event-kbd \u0026amp;\u0026amp; echo \u0026#34;- Properties:\u0026#34; \u0026amp;\u0026amp; udevadm info -q property -n \u0026#34;$DEV\u0026#34; | grep -E \u0026#34;ID_VENDOR_ID|ID_MODEL_ID|ID_SERIAL|ID_USB_INTERFACE_NUM|ID_INPUT\u0026#34; \u0026amp;\u0026amp; echo \u0026#34;- Name:\u0026#34; \u0026amp;\u0026amp; udevadm info -a -n \u0026#34;$DEV\u0026#34; | grep -m1 \u0026#39;ATTRS{name}\u0026#39; Substitute your own values into DEV, of course.\nWhat we\u0026rsquo;re interested in:\nID_VENDOR_ID ID_MODEL_ID ID_USB_INTERFACE_NUM ATTRS{name} \u0026ndash; needed to avoid collisions Next:\nsudo nano /etc/udev/rules.d/99-vm-keyboard.rules 1 2 3 4 5 # /dev/input/by-id/usb-I-CHIP_MMD_KM40_Receiver_1234-event-kbd SUBSYSTEM==\u0026#34;input\u0026#34;, KERNEL==\u0026#34;event*\u0026#34;, ENV{ID_VENDOR_ID}==\u0026#34;\u0026lt;ID_VENDOR_ID\u0026gt;\u0026#34;, ENV{ID_MODEL_ID}==\u0026#34;\u0026lt;ID_MODEL_ID\u0026gt;\u0026#34;, ENV{ID_USB_INTERFACE_NUM}==\u0026#34;\u0026lt;ID_USB_INTERFACE_NUM\u0026gt;\u0026#34;, ATTRS{name}==\u0026#34;\u0026lt;NAME\u0026gt;\u0026#34;, OPTIONS+=\u0026#34;link_priority=-10\u0026#34;, SYMLINK+=\u0026#34;input/by-id/vm-keyboard-event-kbd\u0026#34; # /dev/input/by-id/usb-SmartCloud_MMD_KM40-if02-event-kbd SUBSYSTEM==\u0026#34;input\u0026#34;, KERNEL==\u0026#34;event*\u0026#34;, ENV{ID_VENDOR_ID}==\u0026#34;\u0026lt;ID_VENDOR_ID\u0026gt;\u0026#34;, ENV{ID_MODEL_ID}==\u0026#34;\u0026lt;ID_MODEL_ID\u0026gt;\u0026#34;, ENV{ID_USB_INTERFACE_NUM}==\u0026#34;\u0026lt;ID_USB_INTERFACE_NUM\u0026gt;\u0026#34;, ATTRS{name}==\u0026#34;\u0026lt;NAME\u0026gt;\u0026#34;, OPTIONS+=\u0026#34;link_priority=10\u0026#34;, SYMLINK+=\u0026#34;input/by-id/vm-keyboard-event-kbd\u0026#34; Replace the values in \u0026lt;...\u0026gt; with your own. I\u0026rsquo;d also suggest adding similar comments, so you don\u0026rsquo;t get confused about what\u0026rsquo;s what.\n📝 Pay attention to link_priority, that\u0026rsquo;s the priority. When I plug my keyboard in to charge it, I obviously don\u0026rsquo;t unplug the dongle from USB.\nBut my keyboard isn\u0026rsquo;t dumb and starts working over the cable instead of the dongle, but the dongle is still present in the system regardless.\nAs a result, udev ends up using the dongle, which\u0026hellip; doesn\u0026rsquo;t work, because the cable is plugged in. That\u0026rsquo;s why the dongle has a lower priority, and the cable has a higher one.\nApply the rules:\nsudo sh -c \u0026#39;udevadm control --reload-rules \u0026amp;\u0026amp; udevadm trigger\u0026#39; Check:\nls -l /dev/input/by-id/vm-keyboard-event-kbd And also:\nsudo evtest /dev/input/by-id/vm-keyboard-event-kbd And press some keys, try just the dongle, just the cable, and both, everything should work.\nNext, everywhere, add/replace the keyboard with /dev/input/by-id/vm-keyboard-event-kbd, as shown at the start of the section.\n🚧 Unfortunately, at the time of writing this text, it doesn\u0026rsquo;t work on the fly. Meaning, if after starting the VM you \u0026ldquo;switch\u0026rdquo; devices, plug in or unplug the cable, QEMU will lose the device and you\u0026rsquo;ll need to reboot the VM. This isn\u0026rsquo;t a bug, it\u0026rsquo;s just how the system behaves. Pinning CPU cores \u0026amp; HugePages The most interesting and simplest part. But first, a preamble. With Intel\u0026rsquo;s f*ckton-of-cores chips, it\u0026rsquo;s not quite a f*ckton of cores. There are P-cores \u0026ndash; performance cores, and E-cores \u0026ndash; energy-efficient cores, performance ones are powerful, energy-efficient ones are kind of \u0026ldquo;on life support\u0026rdquo;. Take the legendary i5-14600K, for example, it has 14 cores, right? But\u0026hellip;\n6 P-cores and 8 E-cores, meaning 6 genuinely powerful cores, and 8 so-so cores. But (once again), when overclocked, the energy-efficient cores boost from 2.6 GHz to 4.0 GHz, not a huge amount, but no longer on life support. The performance cores go from 3.5 GHz to 5.3 GHz. Regardless of whether you\u0026rsquo;re overclocking or not, throw the powerful cores + their threads into the VM, and leave the host all the energy-efficient ones. That\u0026rsquo;s it.\nWith AMD it\u0026rsquo;s simpler, they don\u0026rsquo;t have the concept of energy-efficient cores. You won\u0026rsquo;t have to detect P and E cores, you\u0026rsquo;ll only need to match cores to threads, and with AMD, especially with Zen 4 (AM5 socket), figuring out which thread belongs to which core is a piece of cake. But (once again), there are exceptions, with the f*ckton-of-cores AMD chips, 12-16 cores like the R 9, the cores are split across two chiplets, and, well\u0026hellip; it\u0026rsquo;s better not to throw a bunch of cores from one chiplet plus a bunch from the other into the VM, better to use only one chiplet.\nlscpu -e=CORE,CPU,CLUSTER,SOCKET,MINMHZ,MAXMHZ,MHZ CORE \u0026ndash; core CPU \u0026ndash; thread CLUSTER \u0026ndash; chiplet I don\u0026rsquo;t have an R 9, so I can\u0026rsquo;t show and describe exactly how to handle that, I just have a Ryzen on AM5:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 CORE CPU CLUSTER SOCKET MINMHZ MAXMHZ MHZ 0 0 - 0 422.3340 5392.8721 3021.3081 1 1 - 0 422.3340 5392.8721 4339.9531 2 2 - 0 422.3340 5392.8721 3021.3081 3 3 - 0 422.3340 5392.8721 4341.2192 4 4 - 0 422.3340 5392.8721 3470.1899 5 5 - 0 422.3340 5392.8721 3021.3081 6 6 - 0 422.3340 5392.8721 4333.1089 7 7 - 0 422.3340 5392.8721 3021.3081 0 8 - 0 422.3340 5392.8721 3021.3081 1 9 - 0 422.3340 5392.8721 4339.2979 2 10 - 0 422.3340 5392.8721 3021.3081 3 11 - 0 422.3340 5392.8721 3021.3081 4 12 - 0 422.3340 5392.8721 3021.3081 5 13 - 0 422.3340 5392.8721 3021.3081 6 14 - 0 422.3340 5392.8721 3021.3081 7 15 - 0 422.3340 5392.8721 4001.2991 From 0 to 7, I have 8 cores, but as usual with this kind of thing \u0026ndash; the counting starts not at 1, but at zero, meaning 0-7 = 1-8 cores = 8 cores.\nYou need to find matches, for example CORE 0, we see that in the right column, opposite 0, there\u0026rsquo;s 0 and 8, these are twins, meaning it\u0026rsquo;s one single core with two threads (because of SMT/HT). Next, the second core (marked as 1) has 1 and 9. So I found 2 cores and their 2 threads, meaning 4 threads total, which I\u0026rsquo;ll give to the host:\n0,8 1,9 The rest (12 of them) will go to the VM:\n2,10 3,11 4,12 5,13 6,14 7,15 See? With AMD with a single chiplet, it\u0026rsquo;s all pretty simple!\nGod bless the QEMU dispatcher.\nWe create the dispatcher, a really handy and universal thing, it can also come in useful later for hooks of other VMs:\nsudo nano /etc/libvirt/hooks/qemu dispatcher.sh Loading file... vfio/qemu-hooks/dispatcher.sh Create the hook directories:\nVM=\u0026#34;win11\u0026#34;; sudo mkdir -p /etc/libvirt/hooks/qemu.d/\u0026#34;$VM\u0026#34;/prepare/begin/ /etc/libvirt/hooks/qemu.d/\u0026#34;$VM\u0026#34;/release/end/ Where win11 is the name of your virtual machine, you can find it out with sudo virsh list --all.\nThe hook for when the VM starts:\nsudo nano /etc/libvirt/hooks/qemu.d/win11/prepare/begin/50-cpu-pinning-and-hugepages.sh 50-cpu-pinning-and-hugepages.sh Loading file... vfio/qemu-hooks/start/50-cpu-pinning-and-hugepages.sh The hook for when the VM shuts down:\nsudo nano /etc/libvirt/hooks/qemu.d/win11/release/end/50-cpu-pinning-and-hugepages.sh 50-cpu-pinning-and-hugepages.sh Loading file... vfio/qemu-hooks/stop/50-cpu-pinning-and-hugepages.sh Don\u0026rsquo;t forget to change the core threads in the scripts and set up hugepages to however much you need. There will also be additional information about hugepages further on, along with a table of examples.\nGrant execution permissions:\nsudo chmod +x /etc/libvirt/hooks/qemu And for the hooks:\nVM=\u0026#34;win11\u0026#34;; sudo find /etc/libvirt/hooks/qemu.d/\u0026#34;$VM\u0026#34; -type f -name \u0026#39;*.sh\u0026#39; \\( -path \u0026#39;*/prepare/begin/*\u0026#39; -o -path \u0026#39;*/prepare/end/*\u0026#39; -o -path \u0026#39;*/release/begin/*\u0026#39; -o -path \u0026#39;*/release/end/*\u0026#39; \\) -exec chmod +x {} + Restart the service to refresh the hooks:\nsudo sh -c \u0026#39;systemctl restart virtqemud \u0026amp;\u0026amp; systemctl status virtqemud\u0026#39; In my case that\u0026rsquo;s 6 cores with SMT, meaning 12 threads. In the VM settings \u0026ndash;\u0026gt; CPUs, set Cores to the number of cores (6 in my case), and set Threads to the number of threads per core (2 in my case), then apply. Sockets is the number of processors, set it to 1.\nIn the VM settings, Overview - XML, look for \u0026lt;vcpu placement=\u0026quot;static\u0026quot;\u0026gt;X\u0026lt;/vcpu\u0026gt;, where X is some total number of threads. Enter the value you need:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 \u0026lt;vcpu placement=\u0026#34;static\u0026#34;\u0026gt;12\u0026lt;/vcpu\u0026gt; \u0026lt;cputune\u0026gt; \u0026lt;vcpupin vcpu=\u0026#34;0\u0026#34; cpuset=\u0026#34;2\u0026#34;/\u0026gt; \u0026lt;vcpupin vcpu=\u0026#34;1\u0026#34; cpuset=\u0026#34;10\u0026#34;/\u0026gt; \u0026lt;vcpupin vcpu=\u0026#34;2\u0026#34; cpuset=\u0026#34;3\u0026#34;/\u0026gt; \u0026lt;vcpupin vcpu=\u0026#34;3\u0026#34; cpuset=\u0026#34;11\u0026#34;/\u0026gt; \u0026lt;vcpupin vcpu=\u0026#34;4\u0026#34; cpuset=\u0026#34;4\u0026#34;/\u0026gt; \u0026lt;vcpupin vcpu=\u0026#34;5\u0026#34; cpuset=\u0026#34;12\u0026#34;/\u0026gt; \u0026lt;vcpupin vcpu=\u0026#34;6\u0026#34; cpuset=\u0026#34;5\u0026#34;/\u0026gt; \u0026lt;vcpupin vcpu=\u0026#34;7\u0026#34; cpuset=\u0026#34;13\u0026#34;/\u0026gt; \u0026lt;vcpupin vcpu=\u0026#34;8\u0026#34; cpuset=\u0026#34;6\u0026#34;/\u0026gt; \u0026lt;vcpupin vcpu=\u0026#34;9\u0026#34; cpuset=\u0026#34;14\u0026#34;/\u0026gt; \u0026lt;vcpupin vcpu=\u0026#34;10\u0026#34; cpuset=\u0026#34;7\u0026#34;/\u0026gt; \u0026lt;vcpupin vcpu=\u0026#34;11\u0026#34; cpuset=\u0026#34;15\u0026#34;/\u0026gt; \u0026lt;emulatorpin cpuset=\u0026#34;0-1,8-9\u0026#34;/\u0026gt; \u0026lt;/cputune\u0026gt; Here cpuset is the core of your processor, and vcpu is the thread inside the VM, 12 virtual threads means 0 to 11, on the right I\u0026rsquo;m assigning them the REAL threads from the host. You need to assign a virtual pair to a real pair (see above).\nemulatorpin tells the host to use these cores for \u0026ldquo;keeping\u0026rdquo; KVM alive, meaning in my case it lists the cores I left for the host \u0026ndash; the first 2 cores (4 threads), so that the VM doesn\u0026rsquo;t reach into the cores allocated to the guest, well, or at least it shouldn\u0026rsquo;t.\nIf you passed through the disk via VirtIO, and not VFIO, then after \u0026lt;emulatorpin/\u0026gt; add:\n1 \u0026lt;iothreadpin iothread=\u0026#34;1\u0026#34; cpuset=\u0026#34;0-1,8-9\u0026#34;/\u0026gt; HugePages need to be added into the XML right after the \u0026lt;currentMemory\u0026gt; tag:\n1 2 3 4 5 \u0026lt;memoryBacking\u0026gt; \u0026lt;hugepages\u0026gt; \u0026lt;page size=\u0026#34;2\u0026#34; unit=\u0026#34;M\u0026#34;/\u0026gt; \u0026lt;/hugepages\u0026gt; \u0026lt;/memoryBacking\u0026gt; And also change:\n1 2 \u0026lt;memory unit=\u0026#34;KiB\u0026#34;\u0026gt;X\u0026lt;/memory\u0026gt; \u0026lt;currentMemory unit=\u0026#34;KiB\u0026#34;\u0026gt;X\u0026lt;/currentMemory\u0026gt; Where X is \u0026ndash; GB * 1048576, for example 42 GB * 1048576 = 44040192 KiB. You need to specify this very precisely.\nExamples:\nGB TARGET (hook) KiB (XML) 8 4096 8388608 16 8192 16777216 24 12288 25165824 32 16384 33554432 42 21504 44040192 48 24576 50331648 Regarding memoryBacking (size=\u0026quot;2\u0026quot;) and hugepages-2048kB, the page size matters, the absolute best option is 1024 and hugepages-1048576kB \u0026mdash; 1gb, but it\u0026rsquo;s like disk fragmentation. It\u0026rsquo;ll (probably very) likely be problematic for the host to find the amount of memory it needs in 1 contiguous gigabyte chunks, because RAM isn\u0026rsquo;t linear. But the latency will get even lower. 2mb is a compromise. Important: you can\u0026rsquo;t go lower, you can\u0026rsquo;t go higher, only 4kb (the system default), 2mb, and 1024mb (1gb) work, and that\u0026rsquo;s it.\nYou can also check that hugepages have been allocated (after starting the VM) like this:\ncat /proc/meminfo | grep Huge dGPU passthrough There are two options. The blunt one, but 100% reliable, requires a reboot to \u0026ldquo;switch modes\u0026rdquo;. And the elegant one, but it might not work. The elegant option is hot-switching: start the VM \u0026ndash; the dGPU flies off into the VM, shut down the VM \u0026ndash; the dGPU comes back to the host, f*cking insane. But it\u0026rsquo;s complicated. It might work right now, but stop working later. If the elegant method stops working for me, I\u0026rsquo;ll definitely update the text, but if it stops for you\u0026hellip; well, that\u0026rsquo;s kind of on you, I\u0026rsquo;m not you, I don\u0026rsquo;t know what you\u0026rsquo;ve got.\nRight at the start, FIRST THING! Enable the ssh server, in case something goes wrong, the image disappears, or something else \u0026ndash; you could log in from your phone and fix the problem:\nsudo sh -c \u0026#39;systemctl enable --now sshd \u0026amp;\u0026amp; firewall-cmd --permanent --add-service=ssh \u0026amp;\u0026amp; firewall-cmd --reload \u0026amp;\u0026amp; systemctl status sshd\u0026#39; Once you\u0026rsquo;re done with everything, you can (and it\u0026rsquo;s better, if you don\u0026rsquo;t need it) disable the ssh server:\nsudo sh -c \u0026#39;systemctl disable --now sshd \u0026amp;\u0026amp; firewall-cmd --permanent --remove-service=ssh \u0026amp;\u0026amp; firewall-cmd --reload \u0026amp;\u0026amp; systemctl status sshd\u0026#39; Log out and on the login screen select Wayland.\nNow let\u0026rsquo;s make Wayland work with the iGPU, otherwise the display might disappear after passing through the dGPU.\n📝 The steps below need to be done if you haven\u0026rsquo;t done them yet, and your display has already disappeared; so let\u0026rsquo;s do it ahead of time, consider it a \u0026ldquo;preventive fix\u0026rdquo;. Find the iGPU\u0026rsquo;s PCI address in decimal (hex to decimal):\nlspci -D | grep -E \u0026#34;VGA|3D|Display\u0026#34; | grep -v \u0026#34;NVIDIA\u0026#34; | awk -F\u0026#39;[:. ]\u0026#39; \u0026#39;{printf \u0026#34;\\n%s:%s:%s.%s (hex) =\u0026gt; PCI:%d:%d:%d (decimal)\\n\\n\u0026#34;, $1, $2, $3, $4, strtonum(\u0026#34;0x\u0026#34;$2), strtonum(\u0026#34;0x\u0026#34;$3), strtonum(\u0026#34;0x\u0026#34;$4)}\u0026#39; Output:\n0000:11:00.0 (hex) =\u0026gt; PCI:17:0:0 (decimal) For AMD:\n1 2 3 4 5 6 7 sudo tee /etc/X11/xorg.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; Section \u0026#34;Device\u0026#34; Identifier \u0026#34;iGPU\u0026#34; Driver \u0026#34;amdgpu\u0026#34; BusID \u0026#34;PCI:17:0:0\u0026#34; EndSection EOF sudo zypper install xf86-video-amdgpu For Intel:\n1 2 3 4 5 6 7 sudo tee /etc/X11/xorg.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; Section \u0026#34;Device\u0026#34; Identifier \u0026#34;iGPU\u0026#34; Driver \u0026#34;modesetting\u0026#34; # or intel BusID \u0026#34;PCI:17:0:0\u0026#34; EndSection EOF If Driver \u0026ldquo;modesetting\u0026rdquo; didn\u0026rsquo;t work, and you\u0026rsquo;re using \u0026ldquo;intel\u0026rdquo;, then:\nsudo zypper install xf86-video-intel Keep in mind that \u0026ldquo;xf86-video-intel\u0026rdquo; isn\u0026rsquo;t updated anymore and doesn\u0026rsquo;t work that great on Intel \u0026gt;=11th gen, and in any case \u0026ndash; it\u0026rsquo;s preferable to use Driver \u0026ldquo;modesetting\u0026rdquo; for Intel.\nIf you forgot the PCI address:\nlspci | grep -E \u0026#34;VGA|3D|Display\u0026#34; Output:\n01:00.0 VGA compatible controller: NVIDIA Corporation AD104 [GeForce RTX 4070] (rev a1) 11:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Raphael (rev c5) This time we just need 11:00.0. Next, let\u0026rsquo;s find the DRM device by PCI address:\nls -la /dev/dri/by-path/ | grep \u0026#34;11:00.0\u0026#34; Output:\nlrwxrwxrwx. 1 root root 8 Jun 15 13:11 pci-0000:11:00.0-card -\u0026gt; ../card0 lrwxrwxrwx. 1 root root 13 Jun 15 13:11 pci-0000:11:00.0-render -\u0026gt; ../renderD128 Pass the device to KWin \u0026ndash; pci-0000:11:00.0-card :\nmkdir -p ~/.config/systemd/user/plasma-kwin_wayland.service.d 1 2 3 4 tee ~/.config/systemd/user/plasma-kwin_wayland.service.d/override.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [Service] ExecStartPre=/bin/sh -c \u0026#39;systemctl --user set-environment KWIN_DRM_DEVICES=$(readlink -f /dev/dri/by-path/pci-0000:11:00.0-card)\u0026#39; EOF sudo nano /etc/environment 1 KWIN_DRM_DEVICES=\u0026#34;/dev/dri/by-path/pci-0000\\:11\\:00.0-card\u0026#34; Note the \\, that\u0026rsquo;s escaping the :, it has to be like that.\nsystemctl --user daemon-reload If you\u0026rsquo;re planning to do hot passthrough \u0026ndash; I recommend reading about cold passthrough first, the knowledge will help you.\nNVIDIA (Cold passthrough; VFIO from boot) sudo nano /etc/modprobe.d/vfio.conf There:\ninstall vfio-pci /sbin/vfio-pci-override.sh Next:\nlspci -nnk | grep -A3 -i nvidia 1 2 3 4 5 6 7 8 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation AD104 [GeForce RTX 4070] [10de:2786] (rev a1) Subsystem: Micro-Star International Co., Ltd. [MSI] Device [1462:5137] Kernel driver in use: nvidia Kernel modules: nouveau, nvidia_drm, nvidia 01:00.1 Audio device [0403]: NVIDIA Corporation AD104 High Definition Audio Controller [10de:22bc] (rev a1) Subsystem: Micro-Star International Co., Ltd. [MSI] Device [1462:5137] Kernel driver in use: snd_hda_intel Kernel modules: snd_hda_intel Write down 01:00.0 and 01:00.1.\nsudo nano /sbin/vfio-pci-override.sh 1 2 3 4 5 6 7 8 9 #!/bin/sh DEVS=\u0026#34;0000:01:00.0 0000:01:00.1\u0026#34; for DEV in $DEVS; do echo \u0026#34;vfio-pci\u0026#34; \u0026gt; /sys/bus/pci/devices/$DEV/driver_override done modprobe -i vfio-pci To what you wrote down earlier, add 0000: \u0026ndash; 0000:\u0026lt;your_value\u0026gt; (like in the script). Make the script executable:\nsudo chmod +x /sbin/vfio-pci-override.sh Next:\nsudo nano /etc/dracut.conf.d/vfio.conf 1 2 force_drivers+=\u0026#34; vfio vfio_pci vfio_iommu_type1 \u0026#34; install_items+=\u0026#34; /sbin/vfio-pci-override.sh \u0026#34; Rebuild initramfs:\nsudo sdbootutil --no-reuse-initrd add-all-kernels YaST \u0026ndash;\u0026gt; System \u0026ndash;\u0026gt; Boot Loader \u0026ndash;\u0026gt; Kernel Parameters, at the end:\n1 rd.driver.pre=vfio-pci Reboot. Check:\nlspci -nnk | grep -A3 -i nvidia It should say Kernel driver in use: vfio-pci:\n1 2 3 4 5 6 7 8 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation AD104 [GeForce RTX 4070] [10de:2786] (rev a1) Subsystem: Micro-Star International Co., Ltd. [MSI] Device [1462:5137] Kernel driver in use: vfio-pci Kernel modules: nouveau, nvidia_drm, nvidia 01:00.1 Audio device [0403]: NVIDIA Corporation AD104 High Definition Audio Controller [10de:22bc] (rev a1) Subsystem: Micro-Star International Co., Ltd. [MSI] Device [1462:5137] Kernel driver in use: vfio-pci Kernel modules: snd_hda_intel In the VM settings we add a new PCI Host Device (dGPU and its Audio) (like with the VFIO disk), and in the same place remove Display Spice and then Video \u0026lt;X\u0026gt; (for example Video Virtio or Video QXL).\nOtherwise Windows will detect \u0026ldquo;that\u0026rdquo; display and treat it as a second/third\u0026hellip; display, you get the idea, it\u0026rsquo;ll just get in the way basically.\nNVIDIA (Hot passthrough) I did this with the proprietary NVIDIA driver, for the non-proprietary driver you\u0026rsquo;ll need to tweak the scripts a bit. As far as I\u0026rsquo;m concerned, an NVIDIA GPU without the proprietary drivers is useless. So that\u0026rsquo;s just how it is.\nLet\u0026rsquo;s make KWin work with the iGPU, so that later it lets go of the dGPU.\nmkdir -p ~/.config/environment.d Specify the iGPU\u0026rsquo;s PCI address (lspci | grep -E \u0026quot;VGA|3D|Display\u0026quot;):\n1 2 3 4 tee ~/.config/environment.d/kwin-gpu.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; KWIN_DRM_DEVICES=\u0026#34;/dev/dri/by-path/pci-0000:11:00.0-card\u0026#34; __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json EOF systemctl --user daemon-reload Reboot.\nYou can check with this command using the iGPU\u0026rsquo;s PCI address:\nsudo lsof /dev/dri/by-path/pci-0000:11:00.0-card | grep kwin There should be something there. And with the dGPU\u0026rsquo;s PCI address:\nsudo lsof /dev/dri/by-path/pci-0000:01:00.0-card | grep kwin This should be empty. WARNINGs don\u0026rsquo;t count.\nAlso, an extra check if you have NVIDIA:\nsudo lsof /dev/nvidia* /dev/dri/by-path/pci-0000:01:00.0* This one should really be empty. But if there\u0026rsquo;s something there, and it\u0026rsquo;s not something system-related like KWin, that\u0026rsquo;s not scary, a hook or your own hands can just kill the process and that\u0026rsquo;s it. But if it\u0026rsquo;s KWin, that won\u0026rsquo;t fly.\nWe already set up the hook structure and dispatcher above in Pinning CPU cores \u0026amp; HugePages, if you skipped that, go do it and come back here.\nDon\u0026rsquo;t forget to change the PCI addresses in the scripts to your own.\nsudo nano /etc/libvirt/hooks/qemu.d/win11/prepare/begin/10-dgpu-nvidia-detach.sh detach.sh Loading file... vfio/qemu-hooks/dgpu/nvidia/detach.sh sudo nano /etc/libvirt/hooks/qemu.d/win11/release/end/90-dgpu-nvidia-attach.sh attach.sh Loading file... vfio/qemu-hooks/dgpu/nvidia/attach.sh Make the hooks executable:\nVM=\u0026#34;win11\u0026#34;; sudo find /etc/libvirt/hooks/qemu.d/\u0026#34;$VM\u0026#34; -type f -name \u0026#39;*.sh\u0026#39; \\( -path \u0026#39;*/prepare/begin/*\u0026#39; -o -path \u0026#39;*/prepare/end/*\u0026#39; -o -path \u0026#39;*/release/begin/*\u0026#39; -o -path \u0026#39;*/release/end/*\u0026#39; \\) -exec chmod +x {} + Where win11 is the name of your VM.\n📝 If you\u0026rsquo;re NOT on KDE Plasma, but KDE Plasma is still present on the system \u0026ndash; comment out the lines with (KDE), in case you don\u0026rsquo;t have KDE at all, nothing will happen. If KDE is present but you\u0026rsquo;re not using KDE, then something will happen, something you probably don\u0026rsquo;t need. Restart the service to refresh the hooks:\nsudo sh -c \u0026#39;systemctl restart virtqemud \u0026amp;\u0026amp; systemctl status virtqemud\u0026#39; There\u0026rsquo;s a non-obvious little problem here. If the power goes out or you decide to punch the shutdown button on your PC while the VM is running, the shutdown script won\u0026rsquo;t execute. And starting the VM again won\u0026rsquo;t succeed, because there\u0026rsquo;ll be nothing to unload. In that case you\u0026rsquo;ll need to run the \u0026ldquo;on shutdown\u0026rdquo; script manually, nothing special about that.\nThe last step \u0026ndash; look at the very end of the chapter above (about adding the PCI dGPU and removing the other devices).\nPeripherals \u0026ndash; Audio Everything works, but there\u0026rsquo;s no sound, let\u0026rsquo;s fix that. This will also work for a microphone.\nWe\u0026rsquo;ll use \u0026ldquo;PulseAudio (on PipeWire 1.6.7)\u0026rdquo; as an example \u0026ndash; meaning it\u0026rsquo;s actually PipeWire, with PulseAudio there for compatibility. You can find out what you have with pactl info.\nsudo zypper in -y qemu-audio-pa qemu-audio-pipewire You can check with:\nqemu-system-x86_64 -audiodev help pa and pipewire should show up.\nIf everything\u0026rsquo;s fine, restart the service:\nsudo sh -c \u0026#39;systemctl restart virtqemud \u0026amp;\u0026amp; systemctl status virtqemud\u0026#39; Create the configuration:\nmkdir -p ~/.config/pipewire/pipewire-pulse.conf.d 1 2 3 4 5 6 7 8 cat \u0026gt; ~/.config/pipewire/pipewire-pulse.conf.d/10-tcp.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; pulse.properties = { server.address = [ \u0026#34;unix:native\u0026#34; \u0026#34;tcp:4713\u0026#34; ] } EOF Restart the pipewire service:\nsystemctl --user restart pipewire-pulse Check:\nss -tlnp | grep 4713 \u0026amp;\u0026amp; pactl --server=127.0.0.1:4713 info It should look like this:\n1 2 3 LISTEN 0 32 0.0.0.0:4713 0.0.0.0:* users:((\u0026#34;pipewire-pulse\u0026#34;,pid=2735,fd=17)) LISTEN 0 32 [::]:4713 [::]:* users:((\u0026#34;pipewire-pulse\u0026#34;,pid=2735,fd=18)) Server String: 127.0.0.1:4713 In the VM settings, remove the existing \u0026ldquo;Sound X\u0026rdquo; and create a new one - ICH9. Then go to Overview and there, in the XML inside \u0026lt;devices\u0026gt;:\n1 \u0026lt;audio id=\u0026#34;1\u0026#34; type=\u0026#34;pulseaudio\u0026#34; serverName=\u0026#34;127.0.0.1:4713\u0026#34;/\u0026gt; And add \u0026lt;audio id=\u0026quot;1\u0026quot;/\u0026gt; inside the \u0026lt;sound\u0026gt; tag.\nBefore starting the VM, you can check with this command:\nsudo virsh dumpxml win11 | grep -A3 audio This is audio mixing, adjust the sound inside the VM to your liking, as well as in the host\u0026rsquo;s mixer. And if you remember, I recommended not passing through the media keys on the keyboard, thanks to that, if you match the guest\u0026rsquo;s volume to the host\u0026rsquo;s, so they\u0026rsquo;re roughly the same, then while you\u0026rsquo;re in the guest with input capture and pressing media keys \u0026ndash; you\u0026rsquo;ll be changing both the guest and the host at once, just like on \u0026ldquo;one single system\u0026rdquo;.\n💡 Matching the volume levels is pretty simple, 100% in the guest, and the same in the host\u0026rsquo;s mixer, and then you adjust the host\u0026rsquo;s overall volume as usual. 🚧 When starting the VM right after the system boots, you might get an error:\npulseaudio: Wrong context state Reason: Connection refused That\u0026rsquo;s okay. It happens because the service hasn\u0026rsquo;t started yet and, accordingly, the socket isn\u0026rsquo;t working.\nIt\u0026rsquo;ll either start on the second try, or just play some sound before starting the VM. Alternative:\nsystemctl --user enable pipewire-pulse.service Hiding and speeding up the VM This is basic hiding, full KVM hiding is an entire science of its own and of course involves patching QEMU and the Linux kernel, which is a lot harder and won\u0026rsquo;t be covered here, but maybe someday I\u0026rsquo;ll write about it. Speeding up refers to the so-called \u0026ldquo;Hyper-V Enlightenments\u0026rdquo; (which is closely tied to basic, but modern, hiding), so that certain things don\u0026rsquo;t trigger a heavy VM Exit into the host, and instructions instead go through as if for Hyper-V (Windows native virtualization environment).\nYeah, it might seem like a bit of a contradiction, hiding the fact of virtualization, while at the same time flat out telling the system that it\u0026rsquo;s a VM, sounds like a weird solution, but it\u0026rsquo;s basically fine.\nEverything is done in the XML, I think by now you\u0026rsquo;ve already visited it plenty of times.\nIn \u0026lt;cpu\u0026gt; you should already have something like this:\n1 2 3 \u0026lt;cpu mode=\u0026#34;host-passthrough\u0026#34; check=\u0026#34;none\u0026#34; migratable=\u0026#34;on\u0026#34;\u0026gt; \u0026lt;topology sockets=\u0026#34;1\u0026#34; dies=\u0026#34;1\u0026#34; cores=\u0026#34;6\u0026#34; threads=\u0026#34;2\u0026#34;/\u0026gt; \u0026lt;/cpu\u0026gt; Inside \u0026lt;cpu\u0026gt; you need to add:\n1 2 \u0026lt;cache mode=\u0026#34;passthrough\u0026#34;/\u0026gt; \u0026lt;feature policy=\u0026#34;require\u0026#34; name=\u0026#34;invtsc\u0026#34;/\u0026gt; And also replace migratable=\u0026quot;on\u0026quot; with migratable=\u0026quot;off\u0026quot;.\nIf you have AMD (with SMT, and I hope you do have SMT), also add \u0026lt;feature policy=\u0026quot;require\u0026quot; name=\u0026quot;topoext\u0026quot;/\u0026gt;.\nThe \u0026lt;features\u0026gt; block should look like this:\nfeatures.xml xml Loading file... vfio/qemu-xml/features.xml And the \u0026lt;clock\u0026gt; block should look like this:\nclock.xml xml Loading file... vfio/qemu-xml/clock.xml Find the \u0026ldquo;uuid\u0026rdquo; tag right at the top and copy the value, also run:\nsudo dmidecode -s baseboard-serial-number And copy that value too, even if it says \u0026ldquo;Default string\u0026rdquo; :D\nInsert into the XML after \u0026lt;/cputune\u0026gt;:\nsysinfo.xml xml Loading file... vfio/qemu-xml/sysinfo.xml Don\u0026rsquo;t forget to change the serial number and UUID.\nFind \u0026lt;os\u0026gt; and add inside it:\n1 \u0026lt;smbios mode=\u0026#34;sysinfo\u0026#34;/\u0026gt; Also look for \u0026lt;interface type=\u0026quot;network\u0026quot;\u0026gt;, there change the MAC address to 00:15:5D:xx:xx:xx, where the X\u0026rsquo;s are any value, but not 00 and FF. Afterward, in the guest, you can check with ipconfig /all. 00:15:5D is the Hyper-V MAC prefix, while 52:54:00 is QEMU\u0026rsquo;s.\nAnd remove this from \u0026lt;devices\u0026gt;:\n1 2 3 \u0026lt;input type=\u0026#34;tablet\u0026#34; bus=\u0026#34;usb\u0026#34;\u0026gt; \u0026lt;address type=\u0026#34;usb\u0026#34; bus=\u0026#34;0\u0026#34; port=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;/input\u0026gt; Also, right at the very end, find memballoon, and change it to this (turn it off):\n1 2 3 4 5 \u0026lt;memballoon model=\u0026#34;virtio\u0026#34;\u0026gt; \u0026lt;address type=\u0026#34;pci\u0026#34; domain=\u0026#34;0x0000\u0026#34; bus=\u0026#34;0x05\u0026#34; slot=\u0026#34;0x00\u0026#34; function=\u0026#34;0x0\u0026#34;/\u0026gt; \u0026lt;/memballoon\u0026gt; ↓ \u0026lt;memballoon model=\u0026#34;none\u0026#34;/\u0026gt; memballoon basically isn\u0026rsquo;t used, but it exists and makes calls, why should it make calls and potentially cause freezes if it doesn\u0026rsquo;t do anything, right? Also, in Windows, if you\u0026rsquo;ve noticed the \u0026ldquo;blnsvr.exe\u0026rdquo; process in Task Manager, you can disable the BalloonService in services, otherwise it\u0026rsquo;ll just keep running vain too.\nIn Task Manager it\u0026rsquo;ll still say \u0026ldquo;Virtual Machine: Yes\u0026rdquo;, that won\u0026rsquo;t go away, the goal is to make it modern and not lose performance, meaning it\u0026rsquo;s not about hiding-hiding the fact of virtualization, but making it look like \u0026ldquo;legitimate\u0026rdquo; virtualization. In the terminal \u0026ndash; systeminfo, if at the very bottom it says \u0026ldquo;A hypervisor has been detected. Features required for Hyper-V will not be displayed.\u0026rdquo; \u0026ndash; that means everything\u0026rsquo;s working.\nThis status shows up if you enable Core Isolation or WSL in the system, meaning these are completely legitimate features that trigger this status too. And the performance is better, because Windows isn\u0026rsquo;t emulating calls, but using the fast \u0026ldquo;native\u0026rdquo; Hyper-V ones.\nThanks to these tweaks, my average FPS in the Cyberpunk 2077 benchmark went from 140 \u0026ndash;\u0026gt; 162, while on bare metal it\u0026rsquo;s 163. That\u0026rsquo;s within the margin of error. Surprising, isn\u0026rsquo;t it?\nNetwork setup Just a small tweak to lock an IP address to the VM (so it doesn\u0026rsquo;t change).\nIn the VM settings \u0026ndash;\u0026gt; NIC, and there copy the MAC address.\nNext, in the main window with the list of VMs \u0026ndash;\u0026gt; click on the VM \u0026ndash;\u0026gt; Edit \u0026ndash;\u0026gt; Connection Details \u0026ndash;\u0026gt; Virtual Networks \u0026ndash;\u0026gt; XML, add to the dhcp block:\n1 \u0026lt;host mac=\u0026#34;MAC\u0026#34; ip=\u0026#39;192.168.122.5\u0026#39;/\u0026gt; Apply, and in that same window click the Stop Network button, then Start Network.\nYou can check by typing ipconfig in a Windows terminal.\nGuest additions In Windows, you can choose whether or not to install the \u0026ldquo;Guest Additions\u0026rdquo;, you can do it right away, or skip it if you don\u0026rsquo;t need them.\nThis section is mainly here so I can refer to it when you need to install them, so if you ever need them, you\u0026rsquo;ll know about it.\nGo to https://www.spice-space.org/download.html and in the \u0026ldquo;Windows binaries\u0026rdquo; section download and install \u0026ldquo;spice-guest-tools\u0026rdquo;.\nAlso go to https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/, download and install \u0026ldquo;virtio-win-guest-tools.exe\u0026rdquo;.\nReboot Windows, of course.\nShared clipboard and shared folders (Samba) The most convenient convenience there is. In short, here\u0026rsquo;s what we\u0026rsquo;re going to do:\nA shared clipboard including text and images (only if MIME type is image \u0026ndash; screenshots). A file share (Samba). A script for conveniently \u0026ldquo;copying\u0026rdquo; files to the share without wasting TBW (well, just to be safe). Clipboard A brief preamble. I tried the native way, tried a lot of things. Specifically through semi-spice, like spice but not spice, without graphics. But Wayland doesn\u0026rsquo;t let you read the clipboard unless the window is in focus, meaning:\ncopy \u0026ndash;\u0026gt; click on the spice window \u0026ndash;\u0026gt; paste. Inconvenient, but of course there\u0026rsquo;s a workaround with GDK_BACKEND=x11.\nBut that\u0026rsquo;s not the only landmine, text/plain works both ways, but without Russian text (Cyrillic). text/plain\\;charset=utf=8 only works from guest to host, from host to guest see the workflow above. And of course there\u0026rsquo;s the constantly breaking desktop environment (a completely non-functional taskbar and \u0026ldquo;start\u0026rdquo; menu) because of GDK_BACKEND=x11. And a couple dozen more issues, and so\u0026hellip;\nClipCascade ClipCascade is a piece of software for (drumroll) sharing your clipboard over the network, and that\u0026rsquo;s it, that\u0026rsquo;s all it does. Exactly what\u0026rsquo;s needed. With this approach there\u0026rsquo;ll be a small delay in transferring clipboard data between systems, but by the time you switch input to paste, it\u0026rsquo;ll already have transferred. Without Docker it\u0026rsquo;ll be rough.\nsudo zypper in docker-compose docker-compose-switch wl-clipboard sudo systemctl enable --now docker sudo usermod -G docker -a $USER newgrp docker sudo systemctl restart docker mkdir -p ClipCascade \u0026amp;\u0026amp; cd ClipCascade \u0026amp;\u0026amp; nano docker-compose.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 services: clipcascade: image: sathvikrao/clipcascade:latest container_name: clipcascade # user: \u0026#34;1000:1000\u0026#34; ports: - \u0026#34;127.0.0.1:8080:8080\u0026#34; - \u0026#34;192.168.122.1:8080:8080\u0026#34; # restart: always # Note, this is disabled since we\u0026#39;ll create a unit later volumes: - ./cc_users:/database:z environment: - CC_MAX_MESSAGE_SIZE_IN_MiB=10 - CC_P2P_ENABLED=false # - CC_SIGNUP_ENABLED=true mkdir cc_users docker compose up -d \u0026amp;\u0026amp; docker compose logs -f Now for the client, download the archive from the GitHub repo and unpack it wherever\u0026rsquo;s convenient.\npython3 -m venv venv source venv/bin/activate pip install -r requirements.txt python main.py --gui false --xmode false --xmode false \u0026ndash; that\u0026rsquo;s for Wayland.\nIt\u0026rsquo;ll ask you to log in, login:password \u0026ndash; admin:admin123, for the rest just hit Enter (or read the text below first).\nOn Windows, download the .exe (portable) or .msi (installer) from https://github.com/Sathvik-Rao/ClipCascade/releases, if it\u0026rsquo;s the .exe then create a folder and put the .exe there. Authorization uses the same credentials, but Server URL: http://192.168.122.1:8080.\n📝 You don\u0026rsquo;t have to change these awesome default authorization credentials in this specific case (ONLY IN THIS CASE), because we\u0026rsquo;re routing this traffic over the host\u0026rsquo;s own network, nothing leaves your PC.\nAccordingly, you can also skip encryption, it\u0026rsquo;ll work faster and more reliably.\nAlso, in the additional settings, you can make it so the password is saved to a file, so you won\u0026rsquo;t have to log in every time after restarting the docker container (which means after every host reboot).\nIf everything works, great. We just need to automate it, ClipCascade creates a DATA file, everything\u0026rsquo;s already in there, we just need to run the applications in the background.\nFirst, Docker, technically everything\u0026rsquo;s already there, but Docker starts before libvirt, and as a result \u0026ndash; it doesn\u0026rsquo;t start properly, because there\u0026rsquo;s no network yet. There are two options, a global one for ALL containers, or a one-off unit, I\u0026rsquo;d suggest the unit:\nsudo nano /etc/systemd/system/clipcascade-server-docker.service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [Unit] Description=ClipCascade Docker Compose After=docker.service virtnetworkd.service Requires=docker.service virtnetworkd.service [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=/home/\u0026lt;USERNAME\u0026gt;/Software/InDocker/ClipCascade ExecStart=/usr/bin/docker compose up -d ExecStop=/usr/bin/docker compose down [Install] WantedBy=multi-user.target Don\u0026rsquo;t forget to set your own paths.\nsudo systemctl daemon-reload sudo sh -c \u0026#39;systemctl enable --now clipcascade-server-docker.service \u0026amp;\u0026amp; systemctl status clipcascade-server-docker.service\u0026#39; Next, the ClipCascade client daemon:\nnano ~/.config/systemd/user/clipcascade.service 1 2 3 4 5 6 7 8 9 10 11 12 13 [Unit] Description=ClipCascade Client After=graphical-session.target [Service] Type=simple WorkingDirectory=%h/Software/ClipCascade/ ExecStart=/usr/bin/bash -c \u0026#34;tail -f /dev/null | %h/Software/ClipCascade/venv/bin/python3 -u main.py --gui false --xmode false\u0026#34; Restart=always RestartSec=3 [Install] WantedBy=graphical-session.target %h/ = /home/username/ (only works in user services).\nsystemctl --user daemon-reload systemctl --user enable --now clipcascade.service \u0026amp;\u0026amp; systemctl --user status clipcascade.service You can check the logs like this:\njournalctl --user -u clipcascade.service -f Now for Windows.\nIf it\u0026rsquo;s the portable version:\nJust make a shortcut with ALT and drop it into WIN + R \u0026ndash;\u0026gt; shell:startup.\nIf it\u0026rsquo;s the installer:\nDon\u0026rsquo;t do anything.\nCommon step:\nIn Settings, search for Firewall \u0026ndash;\u0026gt; Windows Defender Firewall \u0026ndash;\u0026gt; Allow an app or feature\u0026hellip; \u0026ndash;\u0026gt; add ClipCascade.exe there. Just in case.\nWIN + R \u0026ndash;\u0026gt; intl.cpl \u0026ndash;\u0026gt; Administrative \u0026ndash;\u0026gt; Change system locale \u0026ndash; Use Unicode UTF-8\u0026hellip;, and reboot, otherwise without this you might not be able to copy and paste non-English text.\nAlternative automation:\nIf you\u0026rsquo;re not happy with ClipCascade running constantly in the background on the host, since after all it\u0026rsquo;s Java, which eats on average 400-900mb of RAM just because it\u0026rsquo;s Java, you can shove the start and stop into hooks instead.\nNext, you need to switch Docker to the same backend that libvirt uses on your system, in the case of openSUSE that\u0026rsquo;s nftables.\n🚧 Below, wherever you see virbr0 \u0026ndash; that\u0026rsquo;s your KVM bridge, by default (network \u0026ldquo;default\u0026rdquo;) it\u0026rsquo;s exactly \u0026ldquo;virbr0\u0026rdquo;. If you start Docker before VM/at OS boot, and then the VM itself, everything will be fine. But if you start the VM first, and then Docker, KVM networking breaks (1, 2). This is because of a conflict with other \u0026ldquo;owners\u0026rdquo; of the network rules.\nWhat\u0026rsquo;s described below is an experimental feature in the newer Docker (\u0026gt;=29) (at the time of writing this text), and not all Docker features work with it, if you have a home PC and not a server that needs a 100% SLA, and you\u0026rsquo;re not using Swarm \u0026ndash; then why not.\nChange the backend:\nsudo nano /etc/docker/daemon.json Add \u0026quot;firewall-backend\u0026quot;: \u0026quot;nftables\u0026quot; there.\nEnable ip forwarding manually, because normally Docker does this itself, but in my case with nftables it doesn\u0026rsquo;t, so:\necho \u0026#34;net.ipv4.ip_forward = 1\u0026#34; | sudo tee /etc/sysctl.d/99-ip-forward.conf \u0026amp;\u0026amp; sudo sysctl --system sudo systemctl restart docker \u0026amp;\u0026amp; docker ps \u0026amp;\u0026amp; systemctl --user restart clipcascade.service \u0026amp;\u0026amp; systemctl --user status clipcascade.service Check that it\u0026rsquo;s changed:\nsudo nft list tables There should be ip docker-bridges/ip6 docker-bridges in there.\nNext, disable the service/client and their autostart (if you have enabled it before):\nsudo systemctl disable --now clipcascade-server-docker.service \u0026amp;\u0026amp; systemctl --user disable --now clipcascade.service And create the hooks:\nsudo nano /etc/libvirt/hooks/qemu.d/win11/prepare/begin/95-clipcascade.sh 95-clipcascade.sh Loading file... vfio/qemu-hooks/start/95-clipcascade.sh sudo nano /etc/libvirt/hooks/qemu.d/win11/release/end/95-clipcascade.sh 95-clipcascade.sh Loading file... vfio/qemu-hooks/stop/95-clipcascade.sh Make the hooks executable:\nVM=\u0026#34;win11\u0026#34;; sudo find /etc/libvirt/hooks/qemu.d/\u0026#34;$VM\u0026#34; -type f -name \u0026#39;*.sh\u0026#39; \\( -path \u0026#39;*/prepare/begin/*\u0026#39; -o -path \u0026#39;*/prepare/end/*\u0026#39; -o -path \u0026#39;*/release/begin/*\u0026#39; -o -path \u0026#39;*/release/end/*\u0026#39; \\) -exec chmod +x {} + Where win11 is the name of your VM.\nReboot the host, just to make absolutely sure everything\u0026rsquo;s checked, start the VM, check the clipboard, then shut down the VM.\ndocker ps \u0026amp;\u0026amp; echo \u0026amp;\u0026amp; systemctl --user status clipcascade.service Alternatives If for some reason you don\u0026rsquo;t like ClipCascade, you can try UniClipboard or even KDE Connect, but the image clipboard might not work there, only text (as for UniClipboard \u0026ndash; not sure, as for KDE Connect \u0026ndash; probably won\u0026rsquo;t work).\nShared folders Samba, of course, how else?\nCreate, or mentally pick, a \u0026ldquo;shared\u0026rdquo; folder, in my case ~/Public/KVM. The folder should have 755 permissions a.k.a. drwxr-xr-x, which is exactly the permissions you get by default when creating folders as your own user (not root).\nOpen YaST \u0026ndash;\u0026gt; Network Services \u0026ndash;\u0026gt; Samba Server. Yeah, you could\u0026rsquo;ve done it by hand, but YaST does it all for you, so why not.\nLeave WORKGROUP as WORKGROUP, it doesn\u0026rsquo;t matter.\nStart-Up:\nAfter writing configuration: Start After reboot: Start on boot Shares:\nAdd\u0026hellip; \u0026ndash;\u0026gt; Add your folder Share Name: Something related to the theme (mine has the same name as the folder), you\u0026rsquo;ll need this later Share Description: If needed Share Type: Directory, not Printer Share Path: pick the folder you created earlier Read-Only: ❌ Inherit ACLs: ❌ Expose Snapshots: ❌ Utilize Btrfs Features: ❌ (this is about snapshots, apparently) Allow Users to Share Their Directories: ❌ Don\u0026rsquo;t touch Identity, Trusted Domain, and LDAP Settings. Click OK.\nNow let\u0026rsquo;s tweak the config a bit:\nsudo nano /etc/samba/smb.conf In [global]:\nunix extensions = no And in the same place, change smb3 directory leases = Yes to No.\nThen find [kvm] (your Share Name) and add to that block:\n1 2 3 4 5 6 force user = username force group = username create mask = 0664 directory mask = 0775 follow symlinks = yes wide links = yes Where username is your username. This is so Windows creates files with the correct permissions and you can work with them freely on the host.\ntestparm, if everything\u0026rsquo;s ok, then everything\u0026rsquo;s ok.\nRestart the service:\nsudo sh -c \u0026#39;systemctl restart smb nmb \u0026amp;\u0026amp; systemctl status smb nmb\u0026#39; Set a password for the samba user:\nsudo smbpasswd -a username Where username is once again your username.\nThis is needed because otherwise Windows will drive you f*cking crazy; if you feel like digging through the registry and group policies, you can skip setting a password, but then you\u0026rsquo;ll also need to enable Guest mode for your folder in samba (you can do this in YaST).\nCheck:\nsmbclient -L 127.0.0.1 -N \u0026amp;\u0026amp; smbclient -L 192.168.122.1 -N You should see something like:\n1 2 3 4 5 6 7 8 9 Sharename Type Comment --------- ---- ------- profiles Disk Network Profiles Service users Disk All users print$ Disk Printer Drivers KVM Disk For KVM IPC$ IPC IPC Service (Samba 4.24.3-git.475.629de6765b9SUSE-oS16.9-x86_64) nobody Disk Home Directories SMB1 disabled -- no workgroup available For both commands. And a final sanity check:\nsmbclient //192.168.122.1/KVM -N Where KVM is your Share Name.\nFirewall rules, just to be safe:\nsudo sh -c \u0026#39;firewall-cmd --zone=libvirt --add-service=samba --permanent \u0026amp;\u0026amp; firewall-cmd --reload \u0026amp;\u0026amp; firewall-cmd --zone=libvirt --list-services\u0026#39; And if you run:\nsudo ss -tlnp | grep 445 You\u0026rsquo;ll see that samba is listening on all addresses, meaning it\u0026rsquo;s accessible to other PCs on the local network, or on the internet (if you have a public IP and haven\u0026rsquo;t closed the ports on your router). If you don\u0026rsquo;t need samba for other use cases, it\u0026rsquo;s better to make it listen only on localhost and the KVM network respectively:\nsudo nano /etc/samba/smb.conf In [global]:\n1 2 interfaces = lo virbr0 bind interfaces only = yes lo \u0026ndash; localhost virbr0 \u0026ndash; the default (literally \u0026ldquo;default\u0026rdquo;) KVM network testparm sudo sh -c \u0026#39;systemctl restart smb nmb \u0026amp;\u0026amp; systemctl status smb \u0026amp;\u0026amp; systemctl status nmb \u0026amp;\u0026amp; sudo ss -tlnp | grep 445\u0026#39; If you did interfaces = lo virbr0 When setting up the shared clipboard, we did this if it needed to start together with the system.\nYou need to tweak the samba services a bit:\nsudo systemctl edit smb.service sudo systemctl edit nmb.service Add to both services:\n1 2 3 4 5 6 7 8 9 10 ... ### Anything between here and the comment below will become the contents of the drop-in file [Unit] After=virtnetworkd.service Requires=virtnetworkd.service ### Edits below this comment will be discarded ... This is needed so samba starts strictly after the KVM network, otherwise it\u0026rsquo;ll fail because the network won\u0026rsquo;t exist yet.\nFor reference, here\u0026rsquo;s the catch: if you only do this for the clipboard service and start it together with the system, it\u0026rsquo;ll trigger the KVM network to start, which means samba will work fine with interfaces = lo virbr0, but if you don\u0026rsquo;t start the clipboard service together with the system, samba won\u0026rsquo;t work without these edits. Either way, it\u0026rsquo;s better to add this regardless if you\u0026rsquo;re using interfaces = lo virbr0, no matter how you start the clipboard service, it\u0026rsquo;s simply more reliable.\nIn Windows, right-click This PC \u0026ndash;\u0026gt; Map network drive \u0026ndash;\u0026gt; Folder \u0026ndash; \\\\192.168.122.1\\KVM. Connect using different credentials, enter your username and password there, and check the box for Remember my credentials. And don\u0026rsquo;t forget the \u0026ldquo;Reconnect at sign-in\u0026rdquo; checkbox, so it connects automatically.\nAnd check that everything works, create some files, and check the permissions on the host with ls -l.\nA script for symlinks (if needed).\nrl.sh Loading file... vfio/scripts/host/rl.sh nano ~/Scripts/rl.sh chmod +x ~/Scripts/rl.sh ln -s ~/Scripts/rl.sh ~/.local/bin/rl At the start of the script, specify the paths to the folders/symlinks on the share.\nTo use it, just type rl, that\u0026rsquo;s the help.\nrl file \u0026ndash; create a symlink to a file rl dir/ \u0026ndash; create a symlink to a directory rl file1 file2 dir1 dir2 \u0026ndash; create 4 symlinks. The script detects the disk on its own, if you\u0026rsquo;re creating a symlink to a file/directory located on \u0026ldquo;Disk 1\u0026rdquo;, it\u0026rsquo;ll put the symlink into \u0026ldquo;Disk 1\u0026rdquo; on the share, same goes for \u0026ldquo;Disk 2\u0026rdquo; and so on.\nRegarding symlinks to directories, Windows deletes folders recursively, so when deleting directory symlinks in Windows, Windows goes inside (since it doesn\u0026rsquo;t know it\u0026rsquo;s a symlink) and deletes the contents. Windows can\u0026rsquo;t quite pull off that horror show and the folder doesn\u0026rsquo;t get deleted. Long story short, folder symlinks can only be deleted from the host.\nIf you\u0026rsquo;re sharing a second disk (better not do this for the system disk!), and it just doesn\u0026rsquo;t work (DENIED), and you have SELinux (at the time of writing this text, openSUSE ships with SELinux by default), don\u0026rsquo;t forget:\nsudo semanage fcontext -a -t samba_share_t \u0026#34;/mnt/second(/.*)?\u0026#34; sudo restorecon -R -v /mnt/second You can check like this:\nsudo semanage fcontext -l | grep second Where second is your mount point. It should output samba_share_t.\nAdditional stuff Hardware features By default, hardware features are disabled for legal reasons. This doesn\u0026rsquo;t really relate to virtualization topics specifically, it\u0026rsquo;s more about OS configuration. But it might happen that the VM can\u0026rsquo;t use these features if they\u0026rsquo;re not available on the host, which is why I added this, and that\u0026rsquo;s because host-passthrough is what gives the VM access to the processor\u0026rsquo;s hardware features.\nYou can check for hardware features:\ngrep -E \u0026#34;aes|sha\u0026#34; /proc/cpuinfo You\u0026rsquo;ll get a wall of text, look for the presence of aes and sha. And also:\nsudo zypper in libva-utils vainfo If H.264 and H.265 are missing from the list, they\u0026rsquo;re disabled and you should enable them.\nsudo zypper in opi opi codecs And agree to everything along the way. If everything stalls partway through and you see a 403 error, dust off your VPN, friends, and try again (whatever\u0026rsquo;s already installed won\u0026rsquo;t get installed again).\nFor future reference, when updating the system with zypper dup, if you get errors with packages, since openSUSE might have updated them while the packman side hasn\u0026rsquo;t yet, choose the option along the lines of \u0026ldquo;keep old versions\u0026rdquo; and just try again in a couple of days, otherwise you\u0026rsquo;ll lose the codecs.\nCheck again:\nvainfo And take a look at the new codecs.\nYou can check this in Windows too.\nFor the CPU:\nYou can check via CPU-Z, specifically \u0026ldquo;Instructions\u0026rdquo;.\nFor the dGPU (NVIDIA):\nnvidia-smi It should print a table. In Task Manager, under Performance \u0026ndash;\u0026gt; GPU, there should be Video Encode and Decode.\nLooking Glass (an alternative to switching on the monitor) LookingGlass \u0026ndash; an alternative to physically switching the video output on the monitor, it adds more latency and puts more load on the system, but it\u0026rsquo;s convenient for work or light games like Kenshi or Sims. Well, more precisely, nothing stops you from using LG for anything you want, but the native image straight from the GPU will always be better.\nThe obvious upside is that it\u0026rsquo;s a window that exists on the host, meaning you\u0026rsquo;ll still have access to the host\u0026rsquo;s monitor(s), and at the same time you\u0026rsquo;ll be able to freely resize, rotate, move, and \u0026ldquo;minimize Windows\u0026rdquo;, convenient.\nHaven\u0026rsquo;t we all dreamed of \u0026ldquo;minimizing\u0026rdquo; Windows at some point?\nLooking Glass can also pass through keyboard, mouse, clipboard, and audio, which is pretty convenient. But that works via spice, which is pretty inconvenient, well, on Wayland. So, given that I already showed how to set all of that up separately \u0026ndash; we\u0026rsquo;ll set up LG just to look at Windows, nothing more.\nFor the guest you need the virtio drivers, see here.\nYou need to install some packages, but since one of the packages is a kmp, it\u0026rsquo;s ideally best to first update the whole system with sudo zypper dup and reboot the PC. Otherwise it might update the Linux kernel as a dependency without updating everything else, which isn\u0026rsquo;t great.\nPackages:\nsudo zypper install looking-glass-client looking-glass-kvmfr-kmp-default Looking Glass can\u0026rsquo;t handle more than 1 monitor, and Looking Glass will capture whichever one is the PRIMARY one in the guest. Meaning, the size needs to be calculated based on the monitor that\u0026rsquo;s going to be captured.\nThe math is as follows: $$ ((width \\times height) \\times (pixel\\_size \\times 2) \\div 1024 \\div 1024) + 10 $$\nThat\u0026rsquo;s the frame size in MiB + a 10 MiB overhead for service data, and this needs to be rounded up to the nearest power of two.\nFor SDR (32-bit) pixel_size = 4, for HDR (64-bit) it\u0026rsquo;s = 8.\nFor example, FULLHD SDR:\n$$ ((1920 \\times 1080) \\times (4 \\times 2) \\div 1024 \\div 1024) + 10 = 25.8 \\approx 32 \\ MiB $$\nStep by step $\\ 1920 \\times 1080 = 2073600$ $\\ 2073600 \\times 8 = 16588800$ $\\ 16588800 \\div 1024 = 16200$ $\\ 16200 \\div 1024 = 15.8203125$ $\\ 15.8203125 + 10 = 25.8203125$ $\\ 25.8203125 \\to 2^{\\lceil \\log_2 25.8203125 \\rceil} = 32$ Examples:\nResolution Frame + 10 MiB Rounded to power of 2 1920×1080 15.8 MiB 25.8 MiB 32 MiB 2560×1440 28.1 MiB 38.1 MiB 64 MiB 3440×1440 (ultrawide) 37.8 MiB 47.8 MiB 64 MiB 3840×2160 (4K) 63.3 MiB 73.3 MiB 128 MiB If you\u0026rsquo;re not a minimalist, you don\u0026rsquo;t have a super-duper 32k TV, and you don\u0026rsquo;t want to bother with the math \u0026ndash; just set it to 256 MiB.\nConfiguring the module:\necho \u0026#34;options kvmfr static_size_mb=32\u0026#34; | sudo tee /etc/modprobe.d/kvmfr.conf Where 32 is that same size in MiB.\necho kvmfr | sudo tee /etc/modules-load.d/kvmfr.conf sudo modprobe kvmfr Check:\nls -la /dev/kvmfr0 udev (access permissions):\nsudo nano /etc/udev/rules.d/71-kvmfr.rules SUBSYSTEM==\u0026#34;kvmfr\u0026#34;, GROUP=\u0026#34;kvm\u0026#34;, MODE=\u0026#34;0660\u0026#34; sudo sh -c \u0026#39;udevadm control --reload-rules \u0026amp;\u0026amp; udevadm trigger\u0026#39; In /etc/libvirt/qemu.conf add \u0026quot;/dev/kvmfr0\u0026quot; in cgroup_device_acl.\nRestart the service:\nsudo sh -c \u0026#39;systemctl restart virtqemud \u0026amp;\u0026amp; systemctl status virtqemud\u0026#39; Next:\nsudo EDITOR=nano virsh edit \u0026lt;your_vm_name\u0026gt; There, right at the start:\n1 2 3 \u0026lt;domain type=\u0026#39;kvm\u0026#39;\u0026gt; 🡓 \u0026lt;domain type=\u0026#39;kvm\u0026#39; xmlns:qemu=\u0026#39;http://libvirt.org/schemas/domain/qemu/1.0\u0026#39;\u0026gt; And at the very end, after the closing \u0026lt;/devices\u0026gt;, but before the closing \u0026lt;/domain\u0026gt;, add:\n1 2 3 4 5 6 \u0026lt;qemu:commandline\u0026gt; \u0026lt;qemu:arg value=\u0026#34;-device\u0026#34;/\u0026gt; \u0026lt;qemu:arg value=\u0026#39;{\u0026#34;driver\u0026#34;:\u0026#34;ivshmem-plain\u0026#34;,\u0026#34;id\u0026#34;:\u0026#34;shmem0\u0026#34;,\u0026#34;memdev\u0026#34;:\u0026#34;looking-glass\u0026#34;}\u0026#39;/\u0026gt; \u0026lt;qemu:arg value=\u0026#34;-object\u0026#34;/\u0026gt; \u0026lt;qemu:arg value=\u0026#39;{\u0026#34;qom-type\u0026#34;:\u0026#34;memory-backend-file\u0026#34;,\u0026#34;id\u0026#34;:\u0026#34;looking-glass\u0026#34;,\u0026#34;mem-path\u0026#34;:\u0026#34;/dev/kvmfr0\u0026#34;,\u0026#34;size\u0026#34;:33554432,\u0026#34;share\u0026#34;:true}\u0026#39;/\u0026gt; \u0026lt;/qemu:commandline\u0026gt; 33554432 \u0026ndash; that\u0026rsquo;s 32 MiB, but in bytes, $32 \\times 1024 \\times 1024$.\nCheck:\nsudo virsh dumpxml \u0026lt;your_vm_name\u0026gt; | grep -A6 \u0026#34;qemu:commandline\u0026#34; 📝 Let me explain \u0026ldquo;why is that\u0026rdquo;. Virtual Machine Manager (GUI) simply won\u0026rsquo;t let you edit/add these parameters, they just get deleted when saving.\nBut, if you add them via virsh edit, and afterward change some other XML through Virtual Machine Manager \u0026ndash; it won\u0026rsquo;t get deleted anymore.\nNow the config:\nmkdir -p ~/.config/looking-glass \u0026amp;\u0026amp; nano ~/.config/looking-glass/client.ini 1 2 3 4 5 [app] shmFile=/dev/kvmfr0 [spice] enable=no I also recommend:\n1 2 3 [input] hideCursor=no escapeKey=KEY_ESC The most important one is escapeKey, the cursor setting is optional. Do you have a Scroll Lock key? Have you ever even heard of it? Well, in LG that key is the modifier by default, the setting above changes it, hold it down and you\u0026rsquo;ll see a menu + you can also check here.\nIf you launch LG right now, you\u0026rsquo;ll get an error saying the Host application (on the guest) isn\u0026rsquo;t running, if that happens, that\u0026rsquo;s good, it means it\u0026rsquo;s working correctly so far.\nOn Windows, download the Windows Host Binary here https://looking-glass.io/downloads and install it.\nPermission denied If, when starting the VM, you get ...can't open backing store /dev/kvmfr0 for guest RAM: Permission denied, that\u0026rsquo;s probably SELinux, since it\u0026rsquo;s enabled by default on openSUSE.\nI don\u0026rsquo;t recommend disabling it, but just to be sure:\nsudo getenforce Or check the parameter globally with grep '^SELINUX=' /etc/selinux/config.\nIf it says Enforcing \u0026ndash; that means it\u0026rsquo;s enabled. Run:\nsudo setenforce 0 This switches it to permissive, try starting the VM again. If it worked, that was because of SELinux.\nIf that\u0026rsquo;s the case: first, switch it back:\nsudo setenforce 1 Next:\nsudo sh -c \u0026#39;semanage fcontext -a -t svirt_image_t \u0026#39;/dev/kvmfr0\u0026#39; \u0026amp;\u0026amp; restorecon -v /dev/kvmfr0\u0026#39; Check:\nls -Z /dev/kvmfr0 It should say svirt_image_t.\nAnd also:\nsudo nano /etc/udev/rules.d/71-kvmfr.rules Add to the end there:\n...MODE=\u0026#34;0660\u0026#34;, RUN+=\u0026#34;/usr/sbin/restorecon %E{DEVNAME}\u0026#34; sudo sh -c \u0026#39;udevadm control --reload-rules \u0026amp;\u0026amp; sudo rmmod kvmfr \u0026amp;\u0026amp; sudo modprobe kvmfr \u0026amp;\u0026amp; ls -Z /dev/kvmfr0\u0026#39; 📝 Right off the bat with Looking Glass you\u0026rsquo;ll probably get a blurry mess, to avoid that the LG window needs to be opened fullscreen, and I mean truly fully full screen, the way games usually open, for example.\nOr, if you want to experience some unreal WM zen, like on Niri, for example, run niri msg windows, find the size of the LG window there, and using NVIDIA, AMD, or Intel, create a custom monitor resolution. Whew! How stylish is that :D\nVFIO switching script (Cold passthrough) Put the script and configs somewhere, for example:\nmkdir -p /home/$USER/Scripts/vfiopt Create the script and configs with the content you need (see dGPU Passthrough):\ntouch /home/$USER/Scripts/vfiopt/vfiopt.sh /home/$USER/Scripts/vfiopt/modprobe-vfio.conf /home/$USER/Scripts/vfiopt/dracut-vfio.conf /home/$USER/Scripts/vfiopt/vfiopt.conf (echo; head -n -0 /etc/{modprobe,dracut.conf}.d/vfio.conf; echo) Also, in nano /home/$USER/Scripts/vfiopt/vfiopt.conf, specify the PCI address of the dGPU and its audio device:\n1 2 # PCI dGPU \u0026amp; Audio (0000:XX:XX.X) DGPU_DEVS=\u0026#34;0000:01:00.0 0000:01:00.1\u0026#34; The script itself:\nsudo nano /home/$USER/Scripts/vfiopt/vfiopt.sh vfiopt.sh Loading file... vfio/scripts/host/vfiopt.sh sudo chmod 755 /home/$USER/Scripts/vfiopt/vfiopt.sh Create a symlink:\nsudo ln -s /home/$USER/Scripts/vfiopt/vfiopt.sh /usr/local/bin/vfiopt Restart the terminal. And you can run the script from anywhere:\nvfiopt About VFIO passthrough As you might have noticed in /sbin/vfio-pci-override.sh:\n1 DEVS=\u0026#34;0000:01:00.0 0000:01:00.1\u0026#34; There\u0026rsquo;s this line, and you can add not just the GPU to it, but anything at all, like:\n1 DEVS=\u0026#34;0000:01:00.0 0000:01:00.1 0000:X 0000:X 0000:X\u0026#34; And all of that will get passed through via vfio.\nPassing GPU temperatures from guest to host For the guest you need the virtio drivers, see here.\nYou might not need this at all, but for those who do, I\u0026rsquo;ll show it, also keep in mind that the main use case for passing through temperatures is needed if you have hot passthrough, if you have cold passthrough, then the python script I\u0026rsquo;m providing can be greatly simplified.\nPersonally, the built-in «Smart Fan» in the BIOS isn\u0026rsquo;t enough for me, which is why I prefer to control the CPU and case fans based on the CPU and GPU temperature by \u0026ldquo;MAX\u0026rdquo;. That is, if the CPU is heating up, that\u0026rsquo;s one curve, if it\u0026rsquo;s the GPU card, that\u0026rsquo;s a different curve.\nAccordingly, when the GPU is passed through to the guest, its temperatures aren\u0026rsquo;t available on the host, and the guest doesn\u0026rsquo;t have access to the case fans. Also, further on, I\u0026rsquo;ll show my example of how to do this with CoolerControl.\n🚧 The text below is EXCLUSIVELY for NVIDIA GPUs.\nProprietary NVIDIA drivers are required both on the host and the guest, since we\u0026rsquo;re going to use nvidia-smi \u0026ndash;\u0026gt; NVML.\nnvidia-smi is the official utility that wraps NVML, which is part of the proprietary NVIDIA driver, and we\u0026rsquo;re going to use NVML directly.\nSo, let\u0026rsquo;s begin.\nIn the VM settings, Add hardware \u0026ndash;\u0026gt; Channel:\nName: org.local.gpu-temp Device Type: UNIX socket (unix) Auto socket: ✅ Run VM. On the host:\nsudo virsh dumpxml win11 | grep -B2 -A2 \u0026#34;org.local.gpu-temp\u0026#34; Where win11 is the name of your VM. Output:\n1 2 3 4 5 \u0026lt;channel type=\u0026#39;unix\u0026#39;\u0026gt; \u0026lt;source mode=\u0026#39;bind\u0026#39; path=\u0026#39;/run/libvirt/qemu/channel/2-win11/org.local.gpu-temp\u0026#39;/\u0026gt; \u0026lt;target type=\u0026#39;virtio\u0026#39; name=\u0026#39;org.local.gpu-temp\u0026#39; state=\u0026#39;disconnected\u0026#39;/\u0026gt; \u0026lt;alias name=\u0026#39;channel0\u0026#39;/\u0026gt; \u0026lt;address type=\u0026#39;virtio-serial\u0026#39; controller=\u0026#39;0\u0026#39; bus=\u0026#39;0\u0026#39; port=\u0026#39;1\u0026#39;/\u0026gt; Here we need the path \u0026ndash; /run/libvirt/qemu/channel/2-win11/org.local.gpu-temp \u0026ndash; remember this path, you might need it in the Python script. VM can now be shut down for further testing.\nAdd yourself to the group sudo usermod -aG qemu $USER (just in case) and reboot.\nCreate the python script:\nsudo mkdir -p /usr/local/sbin/gpu-temp-bridge sudo nano /usr/local/sbin/gpu-temp-bridge/gpu-temp-bridge.py gpu-temp-bridge.py Loading file... vfio/scripts/host/gpu-temp-bridge.py Don\u0026rsquo;t forget to change the VM name, the socket path (if it\u0026rsquo;s different), and the dGPU\u0026rsquo;s PCI address to your own.\n📝 In the future (probably in the very distant future), the library file \u0026ldquo;libnvidia-ml.so\u0026rdquo; (name) might get a major version bump .so-1 \u0026ndash;\u0026gt; .so-2, keep that in mind. The python script grabs the temperature from the host, but if the sensor is unavailable, it grabs the temperature from the guest\u0026rsquo;s socket instead. If for some reason nothing is available, it falls back to the temperature set in FALLBACK_TEMP, This isn\u0026rsquo;t a huge deal \u0026ndash; 50 is neither too much nor too little, but of course it\u0026rsquo;s better to set your own individual value \u0026ndash; your GPU\u0026rsquo;s idle temperature. but that might seem unsafe. And it is, sort of, but not really.\n📝 About it being unsafe: it\u0026rsquo;s unsafe for the GPU only if you want to control the GPU\u0026rsquo;s own fans. But if you want to control the GPU\u0026rsquo;s fans, then you don\u0026rsquo;t need to pass the temperature through to the host, the host already has full access to the dGPU whenever the VM isn\u0026rsquo;t running.\nExactly the same way the guest has full access to the dGPU whenever the VM is running. Meaning, the GPU\u0026rsquo;s fans should be controlled from whichever OS is currently using the card. All of this is purely so you can control any other fans based on the GPU temperature.\nIn other words, both the host and the guest can control the GPU\u0026rsquo;s own fans while they\u0026rsquo;re using the GPU, but only the host can control all the other fans.\nA quick check, just run the script:\npython3 /usr/local/sbin/gpu-temp-bridge/gpu-temp-bridge.py And in second terminal:\nwatch -n 1 cat /dev/shm/gpu-temp You should see a temperature. Put some load on the GPU somehow, for example:\nsudo zypper in glmark2 That\u0026rsquo;s a super lightweight benchmark, then run it through the dGPU:\n__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia glmark2 Does the temperature change? If yes, great.\nNow let\u0026rsquo;s start the VM.\n💡 When starting the VM, there\u0026rsquo;ll be an error/the script will crash (a hook will kill it), that\u0026rsquo;s normal, just run it again. Later, in automation, we\u0026rsquo;ll make it all nice \u0026rsquo;n\u0026rsquo; cool. In Windows, open powershell as admin:\n1 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser And confirm (Y). This is so that scripts can be run as admin.\nNow create the script \u0026ldquo;gpu-temp-bridge.ps1\u0026rdquo;:\ngpu-temp-bridge.ps1 powershell Loading file... vfio/scripts/guest/windows/gpu-temp-bridge.ps1 Run it, and from the guest, put some load on the GPU, if the numbers start moving on the host, everything\u0026rsquo;s working, the temperature is being passed through.\nIt\u0026rsquo;s better to put gpu-temp-bridge.ps1 somewhere rather than just leaving it on the \u0026ldquo;desktop\u0026rdquo;, since later we\u0026rsquo;ll create an autostart task.\nShut down the VM and let\u0026rsquo;s automate this, we\u0026rsquo;ll make a daemon and autostart.\nCreate the daemon:\nsudo nano /etc/systemd/system/gpu-temp-bridge.service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [Unit] Description=GPU temperature bridge (host NVML / guest virtio-serial -\u0026gt; /dev/shm) After=virtqemud.service [Service] Type=simple ExecStart=/usr/bin/python3 /usr/local/sbin/gpu-temp-bridge/gpu-temp-bridge.py Restart=on-failure RestartSec=5 User=root [Install] WantedBy=multi-user.target sudo rm -rf /dev/shm/gpu-temp sudo systemctl daemon-reload sudo sh -c \u0026#39;systemctl enable --now gpu-temp-bridge.service \u0026amp;\u0026amp; systemctl status gpu-temp-bridge.service\u0026#39; Check the logs:\nsudo journalctl -u gpu-temp-bridge.service -f Now let\u0026rsquo;s create hooks, before and after the dGPU gets ripped out:\nsudo nano /etc/libvirt/hooks/qemu.d/win11/prepare/begin/09-dgpu-bridge-stop.sh 09-dgpu-bridge-stop.sh Loading file... vfio/qemu-hooks/start/09-dgpu-bridge-stop.sh sudo nano /etc/libvirt/hooks/qemu.d/win11/prepare/begin/11-dgpu-bridge-start.sh 11-dgpu-bridge-start.sh Loading file... vfio/qemu-hooks/start/11-dgpu-bridge-start.sh And just in case (to be extra sure):\nsudo nano /etc/libvirt/hooks/qemu.d/win11/release/end/11-dgpu-bridge-restart.sh 91-dgpu-bridge-restart.sh Loading file... vfio/qemu-hooks/stop/91-dgpu-bridge-restart.sh Check it, start the VM, then let\u0026rsquo;s automate the powershell side.\nCreate a temporary .ps1 script (it\u0026rsquo;ll create the task):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $action = New-ScheduledTaskAction -Execute \u0026#34;powershell.exe\u0026#34; ` -Argument \u0026#34;-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File C:\\Scripts\\gpu-temp-bridge.ps1\u0026#34; $trigger = New-ScheduledTaskTrigger -AtStartup $principal = New-ScheduledTaskPrincipal -UserId \u0026#34;SYSTEM\u0026#34; -LogonType ServiceAccount -RunLevel Highest $settings = New-ScheduledTaskSettingsSet ` -AllowStartIfOnBatteries ` -DontStopIfGoingOnBatteries ` -StartWhenAvailable ` -RestartCount 999 ` -RestartInterval (New-TimeSpan -Minutes 1) ` -ExecutionTimeLimit ([TimeSpan]::Zero) Register-ScheduledTask -TaskName \u0026#34;GpuTempBridge\u0026#34; -Action $action -Trigger $trigger ` -Principal $principal -Settings $settings -Force C:\\Scripts\\gpu-temp-bridge.ps1 \u0026ndash; don\u0026rsquo;t forget to set your own path to the main script.\nRun powershell as admin and start this script, then turn everything off, turn everything back on, if it all works without you lifting a finger, congratulations, you absolute nerd.\nSo, CoolerControl. If you click \u0026ldquo;+\u0026rdquo; and then \u0026ldquo;Custom Sensor\u0026rdquo; in the window that opens, you can select Sensor Type: File, and just point it to the path /dev/shm/gpu-temp. And you also need to disable the regular dGPU sensors there too.\nBut CoolerControl is still a f*ckton stubborn and will still try to use the dGPU anyway, which will result in it tragically dying at the hands of a hook. You could, of course, shove its daemon into a hook too, but that would no longer be an elegant solution, it\u0026rsquo;d be a workaround, yeah, that\u0026rsquo;s different.\nsudo systemctl edit coolercontrold Add there:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ### Editing /etc/systemd/system/coolercontrold.service.d/override.conf ### Anything between here and the comment below will become the contents of the drop-in file [Service] Environment=\u0026#34;CUDA_VISIBLE_DEVICES=\u0026#34; DevicePolicy=closed DeviceDeny=/dev/nvidiactl DeviceDeny=/dev/nvidia0 DeviceDeny=/dev/nvidia-uvm DeviceDeny=/dev/nvidia-modeset ### Edits below this comment will be discarded ### /usr/lib/systemd/system/coolercontrold.service # [Unit] # Description=CoolerControl Daemon ... Next:\nsudo sh -c \u0026#39;sudo systemctl daemon-reload \u0026amp;\u0026amp; systemctl restart coolercontrold.service\u0026#39; Also, in CoolerControl, you need to disable the dGPU sensors and run the GUI itself on the iGPU (read below \u0026ndash; FATAL: Module X is in use).\nNext, you need to make the fans show up, if they\u0026rsquo;re not there.\nsudo sensors-detect Just hit Enter for everything except the last one, \u0026ldquo;Lastly, we can probe the I2C/SMBus adapters for connected hardware\u0026hellip;\u0026rdquo;.\nAnd if at the end you got:\n1 2 3 4 5 Sorry, no sensors were detected. Either your system has no sensors, or they are not supported, or they are connected to an I2C or SMBus adapter that is not supported. If you find out what chips are on your board, check https://hwmon.wiki.kernel.org/device_support_status for driver status. That\u0026rsquo;s all thanks to Gigabyte. Since I happen to have a Gigabyte board, I\u0026rsquo;ll show how to fix that. If you don\u0026rsquo;t have Gigabyte \u0026ndash; check this.\ngit clone https://github.com/frankcrawford/it87 \u0026amp;\u0026amp; cd it87/ sudo zypper in dkms sudo ./dkms-install.sh After that, either everything\u0026rsquo;s fine, or:\n1 2 3 4 5 ... Running depmod... done. modprobe: ERROR: could not insert \u0026#39;it87\u0026#39;: Key was rejected by service make: *** [Makefile:110: dkms] Error 1 Finished running dkms install steps. That\u0026rsquo;s because of Secure Boot:\nsudo mokutil --import /var/lib/dkms/mok.pub Come up with a simple password, and reboot. During boot there\u0026rsquo;ll be a MOK screen, where you need to enroll the key and enter the password you came up with. Same thing you had with the NVIDIA drivers, and probably the root password, if you have Secure Boot.\nCheck whether the drivers loaded:\nlsmod | grep it87 If not, do everything described below:\nsudo modprobe it87 If that didn\u0026rsquo;t work, then:\nsudo modprobe it87 ignore_resource_conflict=1 And add the module to autoload:\necho \u0026#34;it87\u0026#34; | sudo tee /etc/modules-load.d/it87.conf And if the previous step didn\u0026rsquo;t work without ignore_resource_conflict=1:\necho \u0026#34;options it87 ignore_resource_conflict=1\u0026#34; | sudo tee /etc/modprobe.d/it87.conf Check:\nsensors Do you see this?\n1 2 3 4 5 6 7 8 9 10 11 fan1: 858 RPM (min = 0 RPM) fan2: 764 RPM (min = 0 RPM) fan3: 732 RPM (min = 0 RPM) fan4: 0 RPM (min = 0 RPM) fan5: 855 RPM (min = 0 RPM) pwm1: 25% (freq = 23437 Hz) pwm2: 20% (freq = 23437 Hz) pwm3: 24% (freq = 23437 Hz) pwm4: 25% (freq = 23437 Hz) pwm5: 25% (freq = 23437 Hz) ... Then everything\u0026rsquo;s great.\nAnd make sure to read the readme so you know how to update it, that\u0026rsquo;s important.\nIf the fans aren\u0026rsquo;t controllable manually or via CoolerControl and the like, you need to go into the BIOS, into Smart Fan, and \u0026ldquo;reset\u0026rdquo; the curves there, specifically make it into ONE single point, because Smart Fan sort of overrides this. Meaning, make something like this:\n1 2 3 4 5 6 7 PWM(%) 25 25 25 25 25 25 100 Temp(C) 0 80 80 80 80 80 80 Where 25 is the minimum speed you want (be reasonable, don\u0026rsquo;t set it to 0) in case CoolerControl falls over, so there\u0026rsquo;s no overheating. Where 100 is just 100. Where the first Temp is always 0, and the rest are the max possible, in my case that\u0026rsquo;s 80. For example, if I set it to 90, and reboot into the BIOS again, it\u0026rsquo;ll reset back to 80.\n🚧 After updating the OS (mainly the kernel), the coolers may disappear or may run in \u0026ldquo;read-only\u0026rdquo; mode, this can be fixed by reinstalling (in my case) it87:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 $ sudo dkms status | grep it87 it87/v1.0-232-g20f2f2f.20260416, 7.1.2-1-default, x86_64: installed (Original modules exist) $ sudo dkms remove it87/v1.0-232-g20f2f2f.20260416 --all Module it87/v1.0-232-g20f2f2f.20260416 for kernel 7.1.2-1-default (x86_64): Before uninstall, this module version was ACTIVE on this kernel. Deleting /lib/modules/7.1.2-1-default/updates/it87.ko.zst Restoring archived original module /lib/modules/7.1.2-1-default/kernel/drivers/hwmon/it87.ko.zst Running depmod... done. $ sudo modprobe -r it87 $ sudo ./dkms-install.sh About to run dkms install steps... Creating symlink /var/lib/dkms/it87/v1.0-232-g20f2f2f.20260416/source -\u0026gt; /usr/src/it87-v1.0-232-g20f2f2f.20260416 Sign command: /lib/modules/7.1.3-1-default/build/scripts/sign-file Signing key: /var/lib/dkms/mok.key Public certificate (MOK): /var/lib/dkms/mok.pub Building module(s)... done. Signing module /var/lib/dkms/it87/v1.0-232-g20f2f2f.20260416/build/it87.ko Found pre-existing /lib/modules/7.1.3-1-default/kernel/drivers/hwmon/it87.ko.zst, archiving for uninstallation Installing /lib/modules/7.1.3-1-default/updates/it87.ko.zst Running depmod... done. Finished running dkms install steps. $ sudo systemctl restart coolercontrold.service $ sudo mokutil --import /var/lib/dkms/mok.pub # check just in case Passing additional devices to the VM Two main ways:\npass through the device itself and only it pass through an entire USB \u0026ldquo;port\u0026rdquo; (controller), meaning this is VFIO The first is as simple as it gets, universal, and will work in any case (the only question is how well).\nBut the second only works with great IOMMU groups, and there\u0026rsquo;s a catch \u0026ndash; USB ports are usually on the same controller, the chance of being able to pass through just 1 port, rather than the whole bunch, is extremely small, I\u0026rsquo;d even say impossible, because a port doesn\u0026rsquo;t get passed through \u0026ndash; a controller does. For example, a separate PCI USB card won\u0026rsquo;t have this catch.\nThe obvious upside of this kind of passthrough is performance, it\u0026rsquo;s like passing through the dGPU, the same thing, you give it entirely to the guest. With the first method it still goes through the host, which causes latency, and can cause some serious \u0026ldquo;quirks\u0026rdquo; with USB flash drives/disks, webcams, and so on. With input devices \u0026ndash; gamepads, mice, keyboards, the \u0026ldquo;quirks\u0026rdquo; aren\u0026rsquo;t noticeable, although they exist, unless you\u0026rsquo;re a cyber-athlete.\nI\u0026rsquo;ll be showing this using a Chinese GameSir gamepad connected via a dongle as an example.\nPassing through a specific device This one\u0026rsquo;s very simple. Connect the device. In my case it\u0026rsquo;s detected as a DualShock 4 (the Chinese love emulating DualShock/XBOX gamepads for compatibility).\nNext, in the VM settings, Add Hardware \u0026ndash;\u0026gt; USB Host Device, and pick your device, that\u0026rsquo;s it. Done.\nBut, I also recommend changing the Startup Policy to Optional, this is so that if the device isn\u0026rsquo;t physically connected, the VM starts rather than throwing an error.\nAnswering the obvious questions:\nDo I need to connect the device BEFORE starting the VM or can I do it after? BEFORE, otherwise it won\u0026rsquo;t get passed through, but, see the next question, it doesn\u0026rsquo;t really matter.\nWhat if the device disconnects for some reason while the VM is running, will I need to restart the VM? Yes, but actually not necessarily, you can just remove the device and add it again while the VM is running.\nPassing through a USB port (controller; VFIO) Native to the max. And of course you need to have clean IOMMU groups, so that the PCI device being passed through is in its own separate group, that was already covered in the guide.\nConnect the device, then:\nlsusb In my case Bus 001 Device 008: ID 054c:09cc Sony Corp. DualShock 4 [CUH-ZCT2x] (why DualShock when it\u0026rsquo;s a GameSir \u0026ndash; see above).\nWhat we see is \u0026ldquo;Bus 001\u0026rdquo; and \u0026ldquo;Dev (Device) 008\u0026rdquo;, then:\nlsusb -t Look at \u0026ldquo;Bus 001\u0026rdquo;, in my case that\u0026rsquo;s the entire f*cking rear panel of the motherboard. So for me this isn\u0026rsquo;t an option.\nBut\u0026hellip; I have a type-c on the front panel and on the rear panel of the motherboard, and those are typically found on separate controllers, because they require huge speeds. Plug something in there, and check:\n1 2 3 4 5 6 7 8 lsusb Bus 003 Device 015: ID 214b:7000 Huasheng Electronics 4-port hub [Maxxter ACT-HUB2-4P, HS8836, iSoul ultra-slim] Bus 003 Device 018: ID 054c:09cc Sony Corp. DualShock 4 [CUH-ZCT2x] lsusb -t /: Bus 003.Port 001: Dev 001, Class=root_hub, Driver=xhci_hcd/2p, 480M |__ Port 001: Dev 015, If 0, Class=Hub, Driver=hub/4p, 480M |__ Port 004: Dev 018, If 0, Class=Human Interface Device, Driver=usbhid, 12M Huasheng is a USB hub, and the gamepad\u0026rsquo;s dongle is plugged into it.\nAnd that\u0026rsquo;s it, there\u0026rsquo;s nothing else under Bus 003, and it\u0026rsquo;s the exact same situation with the type-c on the rear panel. And this is something you can work with, the rest is just a matter of technique, adapters, hubs, and all that, like I\u0026rsquo;ve already done :D\nLet\u0026rsquo;s find out the PCI address:\nreadlink -f /sys/bus/usb/devices/usb3 Bus 003 = usb3 accordingly. You\u0026rsquo;ll get something like:\n1 /sys/devices/pci0000:00/0000:00:08.1/0000:11:00.3/usb3 We need 0000:11:00.3.\nNext, if you\u0026rsquo;re using Virtual Machine Manager, and not launching the VM directly through QEMU from a terminal (and this guide hasn\u0026rsquo;t said anything about that, so I\u0026rsquo;m assuming you\u0026rsquo;re launching it through Virtual Machine Manager), libvirt will do all the magic under the hood, since USB drivers aren\u0026rsquo;t as pushy as GPU drivers, it\u0026rsquo;ll unbind the device from the host on its own when the VM starts.\nThat is, Add Hardware \u0026ndash;\u0026gt; PCI Host Device \u0026ndash;\u0026gt; that very PCI device, just like we added the dGPU\u0026rsquo;s PCI device. Done.\nTa-da \u0026mdash; the connected devices work natively and completely bypass the host system when the VM is on, which is also safe (for any kind of checks).\nProblems FATAL: Module X is in use sudo lsof /dev/nvidia* /dev/dri/by-path/pci-0000:01:00.0* Here, of course, use your own dGPU PCI address \u0026ndash; lspci | grep -E \u0026quot;VGA|3D|Display\u0026quot;.\nCheck what\u0026rsquo;s using the card, for example in my case, this is what often shows up:\n1 2 3 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME code 5485 user 28u CHR 195,255 0t0 1348 /dev/nvidiactl code 5485 user 29u CHR 226,129 0t0 1325 /dev/dri/by-path/../renderD129 code is VSCode, which decided it just had to use the dGPU, you just need to close/kill the process and that\u0026rsquo;s it. If it\u0026rsquo;s something system-related, check the KDE Plasma section below.\nWhat if you don\u0026rsquo;t want the application to get killed, you can just launch it on the iGPU right away. But that won\u0026rsquo;t work for VS Code, since what you launch there is a wrapper, and launching it with the right parameters won\u0026rsquo;t make VS Code itself run on the iGPU, and modifying the wrapper would just get reset by a VS Code update, so in the case of VS Code it\u0026rsquo;s simpler to just disable hardware acceleration.\nFor other applications, in the .desktop file:\n1 Exec=env __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/radeon_icd.x86_64.json /path/to/bin For a terminal, simply:\n1 __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/radeon_icd.x86_64.json /path/to/bin Note that __EGL_VENDOR_LIBRARY_FILENAMES and VK_ICD_FILENAMES differ depending on the vendor, Intel/AMD/NVIDIA/something else? Some Chinese GPU brand?\nScreen flickers and the VM won\u0026rsquo;t start after an OS update (KDE Plasma) Check the dGPU\u0026rsquo;s PCI address:\nsudo lsof /dev/nvidia* /dev/dri/by-path/pci-0000:01:00.0* Here, of course, use your own dGPU PCI address \u0026ndash; lspci | grep -E \u0026quot;VGA|3D|Display\u0026quot;.\nThere\u0026rsquo;ll almost certainly be a bunch of stuff there, a bunch of things using your dGPU that shouldn\u0026rsquo;t be, and it\u0026rsquo;s probably something related to the desktop environment, likely KWin. And it\u0026rsquo;s not exactly using your card (cardX) itself, only EGL/OpenGL rendering, even though it technically isn\u0026rsquo;t using the card itself (it\u0026rsquo;s not outputting the image to the screen through it), which is why it seems like everything\u0026rsquo;s supposedly fine.\nAs a result, the hook kills KWin to detach the card, and along with it, kills itself (KVM). Because of this tragic death, KWin gets revived in an emergency, which is why the screen flickers.\nCongratulations, you updated yourself right into this mess :D\nMeet KWin. It\u0026rsquo;ll probably break from time to time because of updates that change its behavior (its GPU initialization logic), and the fix (possibly) will be up to you to find, by the way, while writing this text (before its first publication), I updated the system and ran into this for the first time, the fix was (as already described in the dGPU passthrough chapter) __EGL_VENDOR_LIBRARY_FILENAMES.\nWelcome to the «VFIO» club buddy! How do you make sure it doesn\u0026rsquo;t break? You can\u0026rsquo;t, really, you can just monitor and keep an eye on the changelog. Or a more radical approach \u0026ndash; don\u0026rsquo;t use KWin, meaning don\u0026rsquo;t use KDE Plasma. Or just don\u0026rsquo;t do hot passthrough, only cold passthrough, but if you specifically want hot passthrough for some reason, then there\u0026rsquo;s no point in cold passthrough either.\nYeah, I get that there\u0026rsquo;s an obvious contradiction here, I said we\u0026rsquo;d use this, and now I\u0026rsquo;m saying the opposite. But for a first attempt, given that this text is aimed at an audience that \u0026ldquo;kind of knows what they\u0026rsquo;re doing, but not really, but does have some experience\u0026rdquo;, my opinion is that it really is better to use something with a rich GUI, for clarity\u0026rsquo;s sake, so to speak.\nMost of what\u0026rsquo;s described here will work just fine without KDE Plasma, you just ignore everything that mentions KWin (possibly X11 too, depending on your setup) and KDE Plasma respectively, but you\u0026rsquo;ll have to find an alternative for your DE/WM \u0026ndash; \u0026ldquo;transplant\u0026rdquo; everything onto the iGPU, that\u0026rsquo;s pretty much all you need to do, and it\u0026rsquo;s not out of the question that \u0026ldquo;over there\u0026rdquo; this might even be simpler in that regard (for example, on Niri \u0026ndash; it\u0026rsquo;s very simple).\nAs for me, even if I end up using a different DE/WM, I\u0026rsquo;ll try to stay on top of things and apply fixes. But I\u0026rsquo;ll add them seamlessly, meaning as if that\u0026rsquo;s how it was supposed to be from the start, but I\u0026rsquo;ll keep an update log.\nVM freezes on shutdown/reboot and 100% CPU This is an NVIDIA/AMD bug related to FLR. There\u0026rsquo;s no fix for NVIDIA. And I don\u0026rsquo;t have AMD, but for AMD, this might help.\nJust do a \u0026ldquo;double\u0026rdquo; shutdown through Virtual Machine Manager. As long as it\u0026rsquo;s not force off, but an actual shutdown, it\u0026rsquo;s safe. Either shut down from within Windows + Shutdown from Virtual Machine Manager, or do shutdown twice from Virtual Machine Manager.\nOr some automation like this:\n1 2 3 4 5 6 7 8 9 10 11 #!/bin/bash if [[ $EUID -ne 0 ]]; then exec sudo \u0026#34;$0\u0026#34; \u0026#34;$@\u0026#34; fi VM=\u0026#34;$1\u0026#34; virsh shutdown \u0026#34;$VM\u0026#34; sleep 16 virsh shutdown \u0026#34;$VM\u0026#34; Yeah, obviously, this is a workaround.\nAlso, the culprit might be some random piece of software with a kernel driver. It\u0026rsquo;s easy to check, shut down Windows from within Windows while looking at Windows through the monitor directly, not through LG, if there\u0026rsquo;s a BSOD with an error like \u0026ldquo;VIDEO_DXGKRNL_FATAL_ERROR\u0026rdquo; \u0026ndash; then that\u0026rsquo;s your case. Keep in mind: LG doesn\u0026rsquo;t show the BSOD.\nFor example, in my case it\u0026rsquo;s MSI Afterburner. Even if close it before shutting down, its driver stays loaded. After I removed it from autostart and started the VM, it started shutting down perfectly fine.\n2 ways to fix this:\nDon\u0026rsquo;t use MSI Afterburner at all Unload the driver / Don\u0026rsquo;t load the driver To not load the driver: General \u0026ndash;\u0026gt; Enable low-level IO driver - off. Only takes effect after a VM reboot.\nWhat are the downsides of disabling this driver? Mostly sensors. Undervolting/overclocking works through the NVIDIA driver (it\u0026rsquo;s just a convenient interface, nothing more), so all of that will work. And for sensors there\u0026rsquo;s HWiNFO, GPU-Z, Aida64, whatever you like.\nProblem X in Windows because of Y This text covered a VirtIO/VFIO disk, in both cases Windows is physically installed on the disk, with no virtual disks. If something doesn\u0026rsquo;t update, something breaks badly, or some other world-scale sh*tshow happens \u0026ndash; you can always just \u0026ldquo;dual-boot\u0026rdquo; into Windows.\nTwo things, KEEP AN EYE on BitLocker. I don\u0026rsquo;t know why, but after one such boot into Windows, this dumb thing decided to turn on disk encryption all by itself after months of working in a VM, and it\u0026rsquo;s lucky I noticed.\nIf encryption kicks in, you won\u0026rsquo;t have the key, nor any sane way to unlock it if you didn\u0026rsquo;t sign into your Microsoft account before. And with a TPM mismatch, which there will be if you emulated it or some other f*cked-up Windows-style bullsh*t happens \u0026ndash; the disk gets locked, and demands a password that you physically can\u0026rsquo;t know.\nYou can check the BitLocker status like this (powershell as admin):\nmanage-bde -status You can turn it off like this:\nmanage-bde -off After turning it off, keep running the first command to check from time to time and wait until it says Fully Decrypted (the percentage will be going down), and only then reboot into Linux (if you were on bare metal Windows).\nAnd also, TURN OFF Fast Startup and hibernation, if they\u0026rsquo;re on, and you\u0026rsquo;ll be jumping between KVM and dual-boot \u0026ndash; the filesystem will go «croak». To disable them, in a terminal as admin:\npowercfg /h off Check whether it\u0026rsquo;s enabled:\npowercfg /a Also, my advice, disable Windows updates, and only enable them when you need them. You can disable them any way you like, the main thing is that it\u0026rsquo;s rock solid and not permanent (with the ability to turn them back on).\nHow to update QEMU/KVM For all of these steps, the VM should, of course, be turned off.\nBesides the obvious zypper dup \u0026ndash; updating the whole system, there\u0026rsquo;s this in the XML:\n1 2 3 \u0026lt;os firmware=\u0026#34;efi\u0026#34;\u0026gt; \u0026lt;type arch=\u0026#34;x86_64\u0026#34; machine=\u0026#34;pc-q35-10.2\u0026#34;\u0026gt;hvm\u0026lt;/type\u0026gt; ... pc-q35-10.2 \u0026lt;\u0026ndash; this needs to be updated too. After updating QEMU, run:\nqemu-system-x86_64 -machine help | grep q35 This will show all the versions. Take the newest one and manually change it in the XML.\nBut first I recommend making a backup. Backup of the XML:\nsudo virsh dumpxml win11 \u0026gt; ~/win11.xml Backup of the NVRAM file (if \u0026lt;os firmware=\u0026quot;efi\u0026quot;\u0026gt;):\nsudo virsh dumpxml win11 | grep nvram In the output:\n1 \u0026lt;nvram template=\u0026#39;/usr/share/qemu/ovmf-x86_64-smm-ms-vars.bin\u0026#39; templateFormat=\u0026#39;raw\u0026#39; format=\u0026#39;raw\u0026#39;\u0026gt;/var/lib/libvirt/qemu/nvram/win11_VARS.fd\u0026lt;/nvram\u0026gt; We need /var/lib/libvirt/qemu/nvram/win11_VARS.fd:\nsudo cp /var/lib/libvirt/qemu/nvram/win11_VARS.fd ~/ Restore the backup:\nsudo virsh define ~/win11.xml Check:\nsudo virsh dumpxml win11 | diff - ~/win11.xml And the NVRAM:\nsudo cp ~/win11_VARS.fd /var/lib/libvirt/qemu/nvram/win11_VARS.fd Conclusion \u0026amp; Benchmarks I\u0026rsquo;m f*cking exhausted. I\u0026rsquo;m too lazy to download benchmarks and run tests, so here are some screenshots from the Cyberpunk 2077 and Forza Horizon 6 benchmarks, I don\u0026rsquo;t have any other games with a built-in benchmark, and I already ported these two anyway.\nCP2077 \u0026amp; Forza Horizon 6; Bare 158 / 111, KVM 160 / 111 So, 4 months later 01.11.2026 of using a Linux host and Windows in KVM. A pile of host updates, a bit fewer guest ones, all kinds of software and games. Everything works f*cking great, I recommend it.\nI made new and more comprehensive benchmarks, this time on a lived-in Windows install, not a clean one, and with a pile of software, in the same Cyberpunk 2077, as well as in Black Myth: Wukong Benchmark Tool, Bright Memory: Infinite Ray Tracing Benchmark and 3DMark (Time Spy, Steel Nomad, CPU Profile).\nA quick explanation:\nCyberpunk 2077, Black Myth: Wukong \u0026ndash; generic benchmark, with and without RTX. Bright Memory: Infinite \u0026ndash; RTX benchmark. Time Spy \u0026ndash; GPU + CPU benchmark Steel Nomad \u0026ndash; non-RTX, but a heavy GPU-only benchmark. CPU Profile \u0026ndash; CPU-only, multithreading benchmark. As in the previous two tests (CP2077 \u0026amp; Forza Horizon), even though I didn\u0026rsquo;t mention it, there were 3 runs of each benchmark, and what you see are the average values.\nThe benchmarks on the Windows VM were run without LG streaming, directly to the monitor.\nAlso, I kindly remind you that on bare metal there are 8 cores 16 threads, while in the VM it\u0026rsquo;s 6 cores 12 threads ($\\text{loss} \\ 25\\% \\to \\ \\approx 23\\%:\\ \\downarrow N \\Rightarrow \\downarrow \\text{TDP}$), this is important information for understanding the difference in the Time Spy and CPU Profile benchmarks, and also a bit in the Cyberpunk 2077 tests (because it\u0026rsquo;s CPU-bound).\n$\\Delta = \\left|\\frac{\\text{Bare Metal} - \\text{KVM}}{\\text{Bare Metal}}\\right| \\times 100$\n3DMark KVM Bare Metal $|\\Delta|$ Time Spy Extreme 7 418 7 842 5.41% GPU 8 224 8 192 0.39% CPU 4 771 6 317 24.47% Steel Nomad (DX12) 3 865 3 853 0.31% CPU Profile 6 550 8 439 22.38% 16 threads 6 375 8 422 24.31% 8 threads 5 540 6 706 17.39% 4 threads 3 919 3 931 0.31% 2 threads 2 009 2 043 1.66% 1 thread 1 017 1 023 0.59% Recognize the loss numbers in the CPU tests? That\u0026rsquo;s 1:1. The losses are controlled and come from having fewer cores, not simply because \u0026ldquo;it\u0026rsquo;s virtualization\u0026rdquo;.\nGames CP2077 CP2077 (RTX+DLSS+FG) Wukong (Very High) Wukong (Cinematic+RTX) Bright Memory (NO DLSS) KVM 125 157 56 32 62 95 139 46 26 \u0026mdash; 163 175 65 40 \u0026mdash; Bare Metal 128 162 56 33 62 105 144 45 26 \u0026mdash; 165 181 65 40 \u0026mdash; The images are big, so open them in the browser/download them and zoom in.\nCP2077 / Black Myth: Wukong / Bright Memory: Infinite 3DMark: Time Spy / Steel Nomad / CPU Profile References My notes/tutorial to achieve KVM with passthrough on OpenSUSE and Ryzen/Threadripper system \u0026ndash; Level1Techs Fedora 33: Ultimiate VFIO Guide for 2020/2021 [WIP] \u0026ndash; Level1Techs openSUSE - Docker \u0026ndash; openSUSE libvirt/QEMU Installation \u0026ndash; Looking Glass Docs Tons and tons of random internet and AI ","permalink":"https://hv8.pages.dev/blog/en/windows-but-on-linux-hardware-passthrough/","summary":"Why choose between Windows and Linux? When you can have both.","title":"Windows, but... on Linux: Hardware Passthrough"},{"content":" 🇺🇸 Author(s) or some of authors are not native speaker(s) of English. Text may contain bad spelling, incorrect expressions, verbal turns, sentence constructions, etc. 📝 This is a brief action note without explanations. If you don\u0026rsquo;t understand what it\u0026rsquo;s about, it\u0026rsquo;s better to learn a little theory first. Not how it works, but what it is. If the VPS is new and there is no Docker:\napt update \u0026amp;\u0026amp; apt upgrade -y apt install -y curl wget sudo git curl -fsSL https://get.docker.com -o get-docker.sh \u0026amp;\u0026amp; sudo sh ./get-docker.sh 🚧 If you have a firewall, allow ports 80/tcp and 443/tcp for SSL. Installation via Script This is an automatic script for installing and configuring the panel domain, as well as other domains (if needed) and SSL for them. But the NGINX config will be fully configured only for the panel.\ngit clone https://github.com/nozsh/home-server-vps-ztnet-init ztnet \u0026amp;\u0026amp; cd ztnet \u0026amp;\u0026amp; bash init/init.sh Next, create a network in the admin panel. Connect to the network on the home server following the instructions. And on the VPS run:\nNET_ID=\u0026#34;XYZ\u0026#34;; docker exec ztnet sh -c \u0026#34;cd /var/lib/zerotier-one \u0026amp;\u0026amp; mkdir networks.d \u0026amp;\u0026amp; cd networks.d \u0026amp;\u0026amp; touch $NET_ID.conf\u0026#34; Where XYZ is the network ID.\nIf you added other domains besides the panel, you need to edit the NGINX configs \u0026ndash; change proxy_pass. You can skip adding additional domains right away. Later just use the script. Where \u0026lt;ZeroTier_IP\u0026gt; is the home server IP in the ZeroTier network, APP_PORT is the port on which the service runs on the home server.\ndocker compose down \u0026amp;\u0026amp; docker compose up -d \u0026amp;\u0026amp; docker compose logs -f And if the home server loses connect:\nsudo systemctl restart zerotier-one Manually mkdir ztnet \u0026amp;\u0026amp; cd ztnet \u0026amp;\u0026amp; wget -O docker-compose.yml https://raw.githubusercontent.com/sinamics/ztnet/main/docker-compose.yml nano docker-compose.yml Change everything as you need according to the documentation.\nReverse proxy (NGINX) nano docker-compose.yml It should be like this:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 services: zerotier: ports: - \u0026#34;9993:9993/udp\u0026#34; - \u0026#34;80:80\u0026#34; - \u0026#34;443:443\u0026#34; nginx: image: nginx:alpine container_name: nginx restart: unless-stopped volumes: - ./nginx/conf.d:/etc/nginx/conf.d - /etc/letsencrypt:/etc/letsencrypt:ro network_mode: \u0026#34;service:zerotier\u0026#34; depends_on: - zerotier Example of complete docker-compose.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 services: postgres: image: postgres:15.2-alpine container_name: postgres restart: unless-stopped environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: database_password POSTGRES_DB: ztnet volumes: - postgres-data:/var/lib/postgresql/data networks: - app-network zerotier: image: zyclonite/zerotier:1.14.2 hostname: zerotier container_name: zerotier restart: unless-stopped volumes: - zerotier:/var/lib/zerotier-one cap_add: - NET_ADMIN - SYS_ADMIN devices: - /dev/net/tun:/dev/net/tun networks: - app-network ports: - \u0026#34;9993:9993/udp\u0026#34; - \u0026#34;80:80\u0026#34; - \u0026#34;443:443\u0026#34; environment: - ZT_OVERRIDE_LOCAL_CONF=true - ZT_ALLOW_MANAGEMENT_FROM=172.31.255.0/29 ztnet: image: sinamics/ztnet:latest container_name: ztnet working_dir: /app volumes: - zerotier:/var/lib/zerotier-one restart: unless-stopped ports: - 3000:3000 environment: POSTGRES_HOST: postgres POSTGRES_PORT: 5432 POSTGRES_USER: postgres POSTGRES_PASSWORD: database_password POSTGRES_DB: ztnet NEXTAUTH_URL: \u0026#34;https://ztnet-panel.domain.org\u0026#34; NEXTAUTH_SECRET: \u0026#34;something_random\u0026#34; NEXTAUTH_URL_INTERNAL: \u0026#34;http://ztnet:3000\u0026#34; networks: - app-network links: - postgres depends_on: - postgres - zerotier nginx: image: nginx:alpine container_name: nginx restart: unless-stopped volumes: - ./nginx/conf.d:/etc/nginx/conf.d - /etc/letsencrypt:/etc/letsencrypt:ro network_mode: \u0026#34;service:zerotier\u0026#34; depends_on: - zerotier volumes: zerotier: postgres-data: networks: app-network: driver: bridge ipam: driver: default config: - subnet: 172.31.255.0/29 NGINX configs mkdir -p nginx/conf.d nano nginx/conf.d/panel-ztnet.domain.org.conf Config for panel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server { listen 80; server_name ztnet-panel.domain.org; location /.well-known/acme-challenge/ { root /var/www/certbot; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; server_name ztnet-panel.domain.org; ssl_certificate /etc/letsencrypt/live/ztnet-panel.domain.org/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/ztnet-panel.domain.org/privkey.pem; ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; location / { proxy_pass http://ztnet:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } nano nginx/conf.d/your-app.domain.org.conf Config for some services 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server { listen 80; server_name your-app.domain.org; location /.well-known/acme-challenge/ { root /var/www/certbot; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; server_name your-app.domain.org; ssl_certificate /etc/letsencrypt/live/your-app.domain.org/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-app.domain.org/privkey.pem; ssl_session_cache shared:le_nginx_SSL:10m; ssl_session_timeout 1440m; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; location / { proxy_pass http://\u0026lt;ZeroTier_IP\u0026gt;:APP_PORT; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } Change where highlighted. Where \u0026lt;ZeroTier_IP\u0026gt; is the home server IP in the ZeroTier network, APP_PORT is the port on which the service runs on the home server.\nSSL SSL can be obtained through the Docker too, but it will be easier through the host.\napt install certbot certbot certonly --standalone --non-interactive --agree-tos --email cert@domain.org -d ztnet-panel.domain.org ls /etc/letsencrypt/live/ 💡 SSL cannot be obtained/renewed if the zerotier container is running, due to port conflict. Start docker compose up -d \u0026amp;\u0026amp; docker compose logs -f In the admin panel, create a network. Connect to the network on the home server following the instructions. Next, you need to add the VPS running ZTNet to the network with the home server:\ndocker exec -it ztnet sh cd /var/lib/zerotier-one mkdir networks.d cd networks.d touch \u0026lt;network\u0026gt;.conf Where \u0026lt;network\u0026gt; is the network ID.\nexit docker compose down \u0026amp;\u0026amp; docker compose up -d \u0026amp;\u0026amp; docker compose logs -f And if the home server loses connect:\nsudo systemctl restart zerotier-one Add/Configure Services (NGINX) To add a new service, copy the existing NGINX configuration, and change the domain and proxy_pass. And request an SSL certificate.\ndocker compose down \u0026amp;\u0026amp; docker compose up -d \u0026amp;\u0026amp; docker compose logs -f Or if you used the automatic configuration script (or just download this script separately), from the directory where docker-compose.yml is located, run:\nbash init/add_new.sh The script will stop the Docker containers, create the NGINX config, request SSL and start the containers. If there were no errors \u0026ndash; no manual actions are needed.\nUseful commands Docker You can check that the connection reaches your home server through the NGINX container with the command:\ndocker exec nginx wget -O- http://\u0026lt;ZeroTier_IP\u0026gt;:APP_PORT --timeout=5 If something didn\u0026rsquo;t start, and/or doesn\u0026rsquo;t work properly after:\ndocker compose restart \u0026amp;\u0026amp; docker compose logs -f Run:\ndocker compose down \u0026amp;\u0026amp; docker compose up -d \u0026amp;\u0026amp; docker compose logs -f ZeroTier sudo zerotier-cli join \u0026lt;network\u0026gt; sudo zerotier-cli leave \u0026lt;network\u0026gt; sudo zerotier-cli listnetworks Scripts VPS \u0026ldquo;certbot_renew.sh\u0026rdquo;:\n1 2 3 4 5 6 7 8 9 10 #!/bin/bash cd /root/ztnet docker compose down certbot renew --quiet #certbot renew bash start_nologs.sh #bash restart_nologs.sh In the same directory where docker-compose.yml is located:\n\u0026ldquo;start.sh\u0026rdquo;:\n1 2 3 #!/bin/bash docker compose up -d \u0026amp;\u0026amp; docker compose logs -f \u0026ldquo;start_nologs.sh\u0026rdquo;:\n1 2 3 #!/bin/bash docker compose up -d \u0026ldquo;restart.sh\u0026rdquo;:\n1 2 3 4 #!/bin/bash docker compose down docker compose up -d \u0026amp;\u0026amp; docker compose logs -f \u0026ldquo;restart_nologs.sh\u0026rdquo;:\n1 2 3 4 #!/bin/bash docker compose down docker compose up -d Cron sudo crontab -e VPS:\n0 4 1 * * /bin/bash /root/certbot_renew.sh Home Server:\n2 4 1 * * systemctl restart zerotier-one ","permalink":"https://hv8.pages.dev/blog/en/tunnel-between-home-server-and-vps-via-zerotier/","summary":"Another battle with NAT!","title":"Tunnel between home server and VPS via ZeroTier"},{"content":" 🇺🇸 Author(s) or some of authors are not native speaker(s) of English. Text may contain bad spelling, incorrect expressions, verbal turns, sentence constructions, etc. 🚧 Before you start do something, read the chapter \u0026ldquo;How to get it all back?\u0026rdquo;! 📝 This note is written assuming that you have already enabled virtualization in BIOS/UEFI and configured a virtual machine in VirtualBox. 💡 If you have blocked updates, for example with software like “Windows Update Blocker” (WUP), then you need to unblock updates, otherwise something may go wrong. If you see this:\nIt should be something like this:\nLook at native API and VT-x/AMD-V is key\nSo your virtual machine is running slow because it\u0026rsquo;s not using hardware virtualization of your CPU.\nSlow running virtual machine on VirtualBox, this is a Windows virtualization issue and specifically due to Device Guard and Credential Guard on Windows 11.\nIn the case of Windows 11, even with virtualization disabled everywhere else, it\u0026rsquo;s not disabled unless Device Guard and Credential Guard are disabled, and this is unfortunately about security.\nWindows kind of \u0026ldquo;takes over\u0026rdquo; virtualization, so other software can\u0026rsquo;t use it.\nWindows 10 \u0026amp; 11 You need to disable anything related to virtualization, WIN + R \u0026ndash;\u0026gt; \u0026ldquo;optionalfeatures\u0026rdquo;:\nHyper-V Virtual Machine Platform Windows Hypervision Platform WSL and others, if any 📝 Note that what is described in the “Windows 11” chapter may be applicable and necessary for Windows 10 as well, but only partially. If you have Windows 10, apply and reboot, everything should work. If you have Windows 11, click reboot later, then continue.\nWindows 11 Settings \u0026ndash;\u0026gt; Privacy \u0026amp; security \u0026ndash;\u0026gt; Windows Security \u0026ndash;\u0026gt; Device security \u0026ndash;\u0026gt; Core Isolation, disable:\nMemory Integrity Kernel-mode Hardware-enforced Stack Protection Screenshots Next, download this utility from the official website. Open PowerShell as administrator:\n1 cd \u0026lt;path_to_utility\u0026gt; 1 ./DG_Readiness_Tool_vX.X.ps1 -Disable Note the X.X, the version you downloaded should be there.\nReboot and follow the disabled instructions (it\u0026rsquo;s simple).\nHow to get it all back? As above, but in reverse.\nIn the case of only Windows 11.\n1 ./DG_Readiness_Tool_vX.X.ps1 -Enable And reboot.\n🚧 Before you do the things described below, read in full! WIN + R \u0026ndash;\u0026gt; \u0026ldquo;msinfo32\u0026rdquo;, if \u0026ldquo;Secure Boot State\u0026rdquo; - \u0026ldquo;on\u0026rdquo; then scroll down to check. If \u0026ldquo;off\u0026rdquo;, then move on.\nBoot into BIOS/UEFI:\nDisable Secure Boot if it says \u0026ldquo;Enabled\u0026rdquo; If it says \u0026ldquo;Standard\u0026rdquo; change to \u0026ldquo;Custom\u0026rdquo; Change \u0026ldquo;Custom\u0026rdquo; to \u0026ldquo;Standard\u0026rdquo; accepting Factory Defaults Enable Secure Boot Note that before you enable Secure Boot, you should disable CSM. Given the fact that you\u0026rsquo;re reading this because you want to enable all back, and it won\u0026rsquo;t work without Secure Boot, you probably already have CSM disabled, but check anyway.\nAlso, if you\u0026rsquo;re on Windows 11 Pro, you can mess around with \u0026ldquo;gpedit.msc\u0026rdquo; if something didn\u0026rsquo;t work: Computer Configuration \u0026ndash;\u0026gt; Administrative Templates \u0026ndash;\u0026gt; System \u0026ndash;\u0026gt; Device Guard \u0026ndash;\u0026gt; Turn On Virtualization Based Security.\nCheck How to check that everything is working. In \u0026ldquo;msinfo32\u0026rdquo; it will be “Secure Boot State” = on, \u0026ldquo;Virtualization-based security\u0026rdquo; (VBS) = Running.\n1 ./DG_Readiness_Tool_vX.X.ps1 -Ready It\u0026rsquo;ll show something like:\n1 2 3 4 Credential-Guard is not running. HVCI is enabled and running. Config-CI is enabled and running. (Enforced mode) Not all services are running. Not:\n1 2 3 4 Credential-Guard is not running. HVCI is not running. Config-CI is enabled and running. (Enforced mode) Not all services are running. \u0026ldquo;Credential-Guard is not running.\u0026rdquo; depends on hardware and support and all that. If it doesn\u0026rsquo;t work for you, it\u0026rsquo;s no big deal. It\u0026rsquo;s a cool thing, but alas, it\u0026rsquo;s designed for enterprise.\nYou can also check it with the command:\n1 Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\\Microsoft\\Windows\\DeviceGuard | Select-Object SecurityServicesRunning More logs (by AI) There\u0026rsquo;s an explanation here by AI, just for general knowledge I thought I\u0026rsquo;d add it.\nMore info:\n1 Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\\Microsoft\\Windows\\DeviceGuard | Format-List * AvailableSecurityProperties - list of features that the hardware supports 1 - BaseVirtualizationSupport (CPU supports virtualization) 2 - SecureBoot (UEFI supports Secure Boot) 3 - DMAProtection (chipset supports IOMMU) 4 - SecureMemoryOverwrite \u0026hellip; RequiredSecurityProperties - mandatory VBS features 1 - virtualization 2 - Secure Boot 3 - DMA Protection SecurityServicesConfigured - configured VBS services (configured doesn\u0026rsquo;t mean running) 1 - Credential Guard 2 - HVCI 3 - Secure Launch 5 - Kernel Stack Protection SecurityServicesRunning - VBS services that are running 1 - Credential Guard 2 - HVCI (Hypervisor-enforced Code Integrity); Memory Integrity 3 - System Guard Secure Launch; Firmware Protection 4 - not used 5 - Kernel-mode Hardware-enforced Stack Protection (KernelShadowStacks) VirtualizationBasedSecurityStatus - VBS status 0 - VBS is disabled 1 - VBS is enabled but not running 2 - VBS is running and working And a command for beautiful output (by AI):\n1 2 3 4 5 6 7 8 9 10 11 $dg = Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\\Microsoft\\Windows\\DeviceGuard $props = $dg.RequiredSecurityProperties Write-Host \u0026#34;`n=== Security ===\u0026#34; -ForegroundColor Cyan Write-Host \u0026#34;VBS Running: \u0026#34; -NoNewline; if($dg.VirtualizationBasedSecurityStatus -eq 2){\u0026#34;✅\u0026#34;}else{\u0026#34;❌\u0026#34;} Write-Host \u0026#34;Secure Boot: \u0026#34; -NoNewline; if($props -contains 2){\u0026#34;✅\u0026#34;}else{\u0026#34;❌\u0026#34;} Write-Host \u0026#34;DMA Protection: \u0026#34; -NoNewline; if($props -contains 3){\u0026#34;✅\u0026#34;}else{\u0026#34;❌\u0026#34;} Write-Host \u0026#34;Memory Integrity (HVCI): \u0026#34; -NoNewline; if($dg.SecurityServicesRunning -contains 2){\u0026#34;✅\u0026#34;}else{\u0026#34;❌\u0026#34;} Write-Host \u0026#34;Kernel Stack Protection: \u0026#34; -NoNewline; if($dg.SecurityServicesRunning -contains 5){\u0026#34;✅\u0026#34;}else{\u0026#34;❌\u0026#34;} Write-Host \u0026#34;Firmware Protection: \u0026#34; -NoNewline; if($dg.SecurityServicesRunning -contains 3){\u0026#34;✅\u0026#34;}else{\u0026#34;❌\u0026#34;} Write-Host \u0026#34;Credential Guard: \u0026#34; -NoNewline; if($dg.SecurityServicesRunning -contains 1){\u0026#34;✅\u0026#34;}else{\u0026#34;❌\u0026#34;} What if? (Windows 11) What if you disabled everything, used your VM, enabled it all back, but then decided to use the VM again, and repeating this cycle, but:\nYou cannot change the settings. In this case, just skip the step with disabling the settings in Core Isolation and move on.\nReferences How to Disable Hyper-V in Windows 11 24H2 \u0026ndash; Broadcom VM using native API instead of AMD-V \u0026ndash; VirtualBox Forum Win11 build 22581 broke VB by enabling Credential Guard \u0026ndash; VirtualBox Forum How to Remove Green Turtle icon in virtual box Windows 11 24h2 \u0026ndash; YouTube I always get the turtle \u0026ndash; Reddit Secure boot enabled but not active for windows 11 \u0026ndash; Reddit ","permalink":"https://hv8.pages.dev/blog/en/how-to-fix-slow-virtualbox-run-windows-10-11/","summary":"How do you get that damn turtle out of here?","title":"Fix VirtualBox slow running on Windows 10\u002611"},{"content":" 🇺🇸 Author(s) or some of authors are not native speaker(s) of English. Text may contain bad spelling, incorrect expressions, verbal turns, sentence constructions, etc. 🔥 In this article, you\u0026rsquo;ll learn how to set up a local text generation system on your computer \u0026ndash; with minimal technical knowledge required. Introduction: About KoboldCPP \u0026amp; SillyTavern, LM Studio KoboldCPP is a user-friendly tool for running text generation models in GGML and GGUF formats, built on top of llama.cpp.\nllama.cpp is written purely in C/C++ and has no external dependencies, which allows it to run with high performance.\nKoboldCPP is especially useful for running local LLMs on systems with limited VRAM, as it can utilize the CPU instead of relying solely on the GPU.\nWhile KoboldCPP may be slower than other backends that load models exclusively into VRAM, it makes it possible to run large LLMs on less powerful machines.\nKoboldCPP includes its own interface (UI), but it\u0026rsquo;s not as flexible, feature-rich, or (in the author\u0026rsquo;s opinion) as comfortable to use as SillyTavern.\nSillyTavern is a UI designed for interacting with text generation models via API (KoboldAI/CPP, OpenAI, OpenRouter, and others).\nIt integrates with in-chat image generation tools like WebUI from A1111 and ComfyUI, and also supports TTS. It can work either as a standalone system (non-AI) or generate voice using AI through APIs.\nSillyTavern features a highly customizable interface, includes powerful tools like lorebooks (lorebook and world info), supports automatic message translation via Google, DeepL, and other services, and offers a wide range of community-created extensions and add-ons.\nKoboldCPP + SillyTavern is a versatile, flexible setup that\u0026rsquo;s suitable for just about anything \u0026ndash; including immersive RP.\nLM Studio is an incredibly simple and user-friendly tool \u0026ndash; install it, launch it, and you\u0026rsquo;re good to go.\nIt comes with built-in support for models with \u0026ldquo;reasoning\u0026rdquo; capabilities, no setup required.\nLM Studio is great for testing models and handling both technical and general tasks like coding, answering questions, writing, rewriting, and more.\nAs a backend, LM Studio is less flexible than KoboldCPP. And as a frontend, it\u0026rsquo;s not as customizable as SillyTavern \u0026ndash; but it\u0026rsquo;s minimalist and pleasant to use.\n💡 This article covers the setup process for both Windows and Linux (Debian/Arch). Installing Git (Bash), NodeJS and Python NodeJS 📝 NodeJS is required to run SillyTavern. If you don\u0026rsquo;t plan to use SillyTavern, you won\u0026rsquo;t need NodeJS. Windows For Windows, download the installer for your system architecture.\nI recommend using the LTS version.\nRun the installer, click \u0026ldquo;Next\u0026rdquo; through the steps, uncheck the box at the end that says \u0026ldquo;Automatically install the necessary tools\u0026hellip;\u0026rdquo;, and complete the installation.\nLinux On Linux, you can install NodeJS using Volta or any other method you prefer.\n📝 The version of Node available in package managers (like apt, pacman, etc.) may be outdated. Installing Volta:\n1 curl https://get.volta.sh | bash Installing NodeJS and NPM:\n1 volta install node@x Where \u0026ldquo;x\u0026rdquo; is the major version of Node you want to install, which you can check on the NodeJS homepage.\nFor example, Node.js LTS v22.14.0:\n1 volta install node@22 Git 📝 Git is needed to clone the SillyTavern repository and for easy updates, but you can also download the repository manually. Windows For Windows, you need to download Git, either the \u0026ldquo;Standalone\u0026rdquo; installer or the \u0026ldquo;Portable\u0026rdquo; version. I recommend the portable version, but you\u0026rsquo;ll have to update your environment variables manually \u0026ndash; more on that later.\nLinux If you\u0026rsquo;re a Linux user, Git is probably already installed. You can check by running git -v. If it\u0026rsquo;s not installed:\nDebian:\n1 2 apt update apt install git Arch:\n1 2 pacman -Syu pacman -S git Python 📝 To run the \u0026ldquo;unpacked\u0026rdquo; version of KoboldCPP, Python is required.\nI recommend installing Python and using the \u0026ldquo;unpacked\u0026rdquo; KoboldCPP \u0026ndash; this will improve performance.\nMore details on this will come later.\n📝 At the time of writing this article, I\u0026rsquo;m using Python 3.11.9. 💡 If you\u0026rsquo;re using the latest Python version and something isn\u0026rsquo;t working, try downgrading the minor version \u0026ndash; x.x.x. Windows For Windows, you need to download and install Python.\nDownload the «Windows Installer» for the Python version you want from the «Stable Releases» column. If you don\u0026rsquo;t need a specific version, just get the latest one.\nDuring the installation, click «Customize installation» and check the boxes for «pip», «tcl/tk», and «Add Python to environment variables»:\nYou can choose the installation location as you prefer.\nLinux Just like with Git, Python is usually already installed on Linux distros, but it might be an older version. You can check by running:\n1 2 3 4 5 python -V # OR python2 -V # OR python3 -V Python 2 is an old version. If you don\u0026rsquo;t have Python installed, or the version is outdated, then:\nDebian:\n1 2 3 apt update apt install python3 apt install python3-pip # Might need to Arch:\n1 2 3 pacman -Syu pacman -S python pacman -S python-pip # Might need to Setting up environment variables After installing Git, NodeJS, and Python, you may need to add them to your environment variables. This lets you run them without specifying the full path to their binaries. Programs will also be able to use simple commands like python, node, and git.\nUsually, the Python and NodeJS installers update the environment variables automatically \u0026ndash; unless you disabled that option. The same goes for Git if you installed it via the installer instead of using the portable version.\nWindows 💡 If you installed Python and NodeJS using their installers as described earlier, but downloaded Git as a portable version, you only need to add Git to your environment variables. Win + R systempropertiesadvanced Environment Variables User variables Path New Git: path\\to\\git\\bin\\ and path\\to\\git\\cmd\\ NodeJS: path\\to\\nodejs\\ Python: path\\to\\python\\ and path\\to\\python\\Scripts\\ One line \u0026ndash; one path!\nLinux If you installed Git and Python through a package manager (apt, pacman, etc.) or they were already on your system, and NodeJS was installed via the official site (including through Volta), everything should be fine.\nBut if that\u0026rsquo;s not the case:\nOpen .bashrc in the nano editor:\n1 nano ~/.bashrc At the end of the file add:\n1 export PATH=$PATH:/path/to Update .bashrc:\n1 source ~/.bashrc Usually the paths are:\nGit: /usr/bin/git NodeJS: /usr/bin/node Python: /usr/bin/python or /usr/bin/python3 But they might be different.\nYou can try running this command to find out where the program is located:\n1 which \u0026lt;...\u0026gt; Example of .bashrc:\n1 2 3 4 5 ... export PATH=$PATH:/usr/bin/git export PATH=$PATH:/usr/bin/node export PATH=$PATH:/usr/bin/python3 Check Type the following in the terminal:\n1 2 3 4 5 6 7 8 9 10 11 $ git -v git version 2.48.1.windows.1 $ node -v v22.14.0 $ npm -v 10.9.2 $ python -V Python 3.11.9 If you get the version number instead of an error like this:\n1 2 \u0026#39;x\u0026#39; is not recognized as an internal or external command, operable program or batch file. 1 -bash: x: command not found That means everything is working.\nInstalling and Running KoboldCPP + SillyTavern \u0026amp; LM Studio To start, I recommend thinking about your directories where to put what and set up something like this:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 | - AI \u0026lt;== Root | | - - Backend \u0026lt;== Backend | - - - LM Studio \u0026lt;== Installation of LM Studio | - - - KoboldCPP \u0026lt;== KoboldCPP dir | - - - - Loose \u0026lt;== Unpacked KoboldCPP | - - - - Settings \u0026lt;== KoboldCPP configs (.kcpps) | - - - - .exe \u0026lt;== KoboldCPP binary file | | - - UI \u0026lt;== Frontend | - - - SillyTavern \u0026lt;== SillyTavern dir | | - - Models \u0026lt;== Models directory | - - - Text \u0026lt;== Text models KoboldCPP Go to the releases page on the GitHub KoboldCPP repository.\nDownload the binary for your system, choosing based on the release notes.\nRun the binary file:\nYou can see the logs here KoboldCPP - GUI The startup isn\u0026rsquo;t fast because these are packed Python files (code), the Python interpreter, and other files bundled into one.\nYou can also interact with the binary through the terminal, for example:\n1 koboldcpp_cu12.exe --help To unpack KoboldCPP, go to «Extra» =\u0026gt; «Unpack KoboldCpp To Folder» and choose an empty directory to extract to.\nNow you can run KoboldCPP much faster using Python:\n1 python koboldcpp.py --help However, in this case the GUI won\u0026rsquo;t work, GUI only works when running the binary file.\nIf you don\u0026rsquo;t want to deal with the terminal and typing lots of flags, there\u0026rsquo;s an option to configure everything through the GUI.\nStart the binary file and set up everything via the GUI, then save the config (click the «Save» button).\nAfter that, you can use this command:\n1 python koboldcpp.py --config \u0026#34;/path/to/myconfig.kcpps\u0026#34; This will launch KoboldCPP using the previously created config.\nIf you need to edit the config, you can do it the same way through the GUI (by loading it with the «Load» button, making changes, and then saving), or simply open the config file in a text editor to swap models or change the context size.\nSillyTavern Download the source code manually from the GitHub repository or use git:\n1 git clone https://github.com/SillyTavern/SillyTavern -b release This command clones the repository into a folder named \u0026ldquo;SillyTavern\u0026rdquo; in the same directory where the command was run.\nRunning on Windows:\nDouble-click «Start.bat» or run in the terminal:\n1 Start.bat Running on Linux:\nMake the file executable:\n1 chmod +x start.sh Run:\n1 ./start.sh Base settings Open the «API Connections» tab (at the top):\nAPI: Text Completion API Type: KoboldCpp API URL: KoboldCPP API URL You can find the «API URL» in the terminal after starting KoboldCPP, but usually it\u0026rsquo;s :5001 if that port was free.\nI highly recommend enabling «Derive context size from backend» so that the «Context (tokens)» value is automatically fetched from the backend \u0026ndash; meaning from the KoboldCPP setting where you set the context size.\n«Auto-connect to Last Server» is also a useful option. It lets SillyTavern connect automatically to the last backend server on startup, so you don\u0026rsquo;t have to tap «Connect» manually every time.\nSillyTavern - tab with backend API settings Tap «Connect», and if you see 🟢 and the model name, that means everything is working.\nLM Studio Go to the LM Studio website, select your OS and architecture, then download and install it.\n📝 I recommend disabling «Enable Local LLM Service» if you don\u0026rsquo;t want the service to run when LM Studio is closed. Now, go to the settings:\nLM Studio - App settings «User Interface Complexity Level» - set to «Power User» «Show side button labels» - enable (for clarity) «Model loading guardrails» - your choice, I personally use «Relaxed» «Use LM Studio\u0026rsquo;s Hugging Face Proxy» - helps with accessing HF through LM Studio if a direct connection doesn\u0026rsquo;t work Other settings are up to you Tap «Discover» in the sidebar:\nLM Studio - Overview of models for download 💡 When searching for models, don\u0026rsquo;t forget to check the «GGUF» box. Go to «Runtime»:\nLM Studio - Overview of Runtime components Update «CPU llama.cpp», and the second CUDA package for Nvidia GPUs. For AMD GPUs, use Vulkan or ROCm.\nOn the «My Models» page (in the sidebar), you can view and adjust default settings for each model.\nLM Studio - Managing downloaded models If you download models manually instead of through LM Studio, they should be placed like this:\n1 2 3 4 | - Text \u0026lt;== «Models Directory» in LM Studio | - - dir1 \u0026lt;== Some dir (author) | - - - dir2 \u0026lt;== Some dir (model name) | - - - - .gguf \u0026lt;== Model dir1 and dir2 don\u0026rsquo;t have to be named after the author or model, the main thing is to keep this hierarchy, otherwise LM Studio won\u0026rsquo;t recognize them.\nModels load and unload from the top:\nLM Studio - GUI Tips for KoboldCPP \u0026amp; SillyTavern KoboldCPP 📝 These tips are for KoboldCPP \u0026amp; llama.cpp, behavior may vary in other software. CuBLAS usually runs faster than CLBlast for Nvidia GPUs.\nFor AMD GPUs, you should use Vulkan, or even better, ROCm.\nUsing «MMQ», «MMAP», «MLOCK», «ContextShift», «FastForwarding», and «FlashAttention» might help speed up generation \u0026ndash; this is best determined through experimentation.\nExplanation: MMQ, MMAP, MLOCK, ContextShift, FastForwarding, FlashAttention MMQ (Quantized Matrix Multiplication)\nMMQ mode uses quantized matrix multiplications for prompt processing instead of the standard cuBLAS operations. This saves VRAM and can boost performance for some quantized formats (like Q4_0), though the effect might be less noticeable for other formats.\nImagine you have two ways to do matrix calculations when generating a new token in a language model. One uses the standard cuBLAS algorithm, which already supports low precision, and the other is MMQ mode, optimized specifically for quantized data. When MMQ is enabled, the system picks the optimized algorithm for prompt processing, allowing faster request handling and reduced VRAM usage if the model is quantized at the right level. This doesn\u0026rsquo;t mean cuBLAS stops being used\u0026ndash;MMQ operates within the cuBLAS framework but changes how data is processed for certain quantized formats.\nMMAP (Memory Mapping)\nMMAP is a way of handling model weights and layers where they\u0026rsquo;re loaded into RAM only partially. The system loads needed parts on demand.\nWhen running an LLM, MMAP lets the model appear as if it\u0026rsquo;s fully loaded into RAM, but in reality, only the blocks required for the current request are read.\nMLOCK (Memory Lock)\nMLOCK forces the operating system to \u0026ldquo;lock\u0026rdquo; the loaded model data in RAM, preventing it from being swapped out to disk. This avoids delays caused by accessing slower virtual memory.\nFor LLMs, it\u0026rsquo;s important that key model weights stay in fast memory (VRAM or RAM). MLOCK pins data in RAM if part of the model is loaded there, preventing swapping and reducing latency.\nSwapping is moving processes or parts of them from RAM to disk (swap file).\nContextShift\nContextShift allows efficient handling of large context windows. Instead of recalculating the entire context, the system \u0026ldquo;shifts\u0026rdquo; the KV cache\u0026ndash;old data is discarded, and new data is moved to the start of the new window.\nFor example, if a chatbot is in a long conversation, when generating a new response, the model doesn\u0026rsquo;t start processing the entire context from scratch. Instead of recomputing everything, it keeps intermediate computed representations (hidden states) of the processed messages. When generating the new reply, the model uses these saved states to quickly incorporate the latest messages. This is like incremental generation in transformers, where the model continues calculations based on what\u0026rsquo;s already been processed.\nThis approach speeds up processing significantly, since the model focuses only on the new information.\nFastForwarding (KV-caching)\nFastForwarding lets the model skip reprocessing tokens for which hidden states (KV cache) have already been computed, generating new tokens only for changes in the sequence. This feature is especially useful when using «ContextShift».\nThink of incremental generation in transformers: when generating a new token, the model doesn\u0026rsquo;t fully recalculate all previous tokens but uses the saved KV cache for the already processed parts. FastForwarding in KoboldCPP works similarly \u0026ndash; if the model continues generating text in a long conversation or document, it “fast-forwards” through the already computed sections and updates only the latest, changed parts of the sequence, which significantly speeds up processing.\nFor example, if there\u0026rsquo;s a history of 1000 tokens, FastForwarding creates a KV cache of those 1000 tokens in one pass, and on the next generation, it uses this cache instead of recalculating everything from scratch.\nFlashAttention\nFlashAttention uses more efficient data handling and GPU cache usage, which speeds up computations and reduces VRAM consumption.\nIn transformer models, attention calculations usually require a quadratic amount of operations as sequence length grows. FlashAttention optimizes this process, enabling faster processing of long sequences, which is especially important for high-load AI server applications.\nContext also requires VRAM\u0026ndash;and quite a bit of it\u0026ndash;so you shouldn\u0026rsquo;t load all your VRAM just with model layers if you\u0026rsquo;re using a large context of 8-12K+ tokens.\nTry to fit as many layers as possible into VRAM while leaving enough space for the context. If even one layer doesn\u0026rsquo;t fit fully in VRAM, it\u0026rsquo;s better to leave more than one layer unloaded\u0026ndash;like 2-3 layers.\nIf your VRAM isn\u0026rsquo;t enough to load most layers, switch to lighter quantization formats. If Q6 doesn\u0026rsquo;t fit, use Q5. If Q5 doesn\u0026rsquo;t fit, go for Q4.\nThis will reduce generation quality, but it will work. If accuracy and quality are critical, running locally might not be the best option if your hardware is insufficient.\nAs a last resort, if you have a lot of RAM, you can significantly sacrifice speed by loading more layers into RAM than VRAM.\nCheck the generation speed with «BLAS Batch Size» set to 512, then set it to 2048 and check again. If 2048 is slower than 512, maybe 1024 will work better.\nIdeally, the bigger the «BLAS Batch Size», the better, but too large a value can actually slow down generation speed.\nRegarding «Threads», the value \u0026ldquo;-1\u0026rdquo; means \u0026ldquo;auto.\u0026rdquo;\nIf you don\u0026rsquo;t manually set a value for each «Threads» parameter, it will inherit the value from the main «Threads» setting.\nFor example: you have 6 cores, 12 threads, if you set «Threads» to 12 but leave «BLAS threads» unset, then «BLAS threads» will also use 12 threads. In total, that would be 24 threads used, while you only have 12 available\u0026ndash;and the OS also needs resources.\nUsually, KoboldCPP sets the optimal number of threads automatically, but it\u0026rsquo;s worth checking which values are assigned by default to ensure everything is okay. You can see this next to the «Threads» parameter (after the model starts) via the GUI or in the terminal logs.\nIf your system runs slowly or unstably, try lowering the number of threads manually.\nMake sure KoboldCPP does not use more threads than you physically have, and that the system keeps some free resources (2-4+ threads) for itself.\nTo change the model, you need to restart KoboldCPP.\nSillyTavern I couldn\u0026rsquo;t find a complete guide on YouTube that explains all the SillyTavern settings in one place.\nSo, if you decide to stick around, you\u0026rsquo;ll have to experiment and do a lot of googling\u0026hellip; And if you\u0026rsquo;re a total beginner, you\u0026rsquo;ll be googling even more.\nHere are your two new best friends \u0026ndash; SillyTavern Documentation \u0026amp; r/SillyTavernAI, where you might find the info you need.\nModels are not created in the GGUF format, GGUF is a format for storing the model\u0026rsquo;s quantizations. So, wherever you download a GGUF model (often from HF), there should be a link to the \u0026ldquo;original\u0026rdquo; or instructions provided.\nOn the original page, you can usually find not only the model description but also settings, and in some cases, even presets for ST1.\nFor example, the model I\u0026rsquo;m using at the time of writing this article is «NemoMix Unleashed 12B» (GGUF).\nEverything is described there, with settings and ST presets provided.\nActually, having the perfect settings isn\u0026rsquo;t mandatory. But the right settings for the «Context Template» (very important), «Instruct Template» (if available), «System Prompt» (less important), and sampler settings for fine-tuning output can VERY significantly improve generation quality.\nConclusion KoboldCPP combined with SillyTavern is a powerful tool for enthusiasts who value flexibility, customization, and detailed tweaking. It\u0026rsquo;s the perfect choice for experiments and RP. However, because of the many parameters and fine settings, it can take some time to get used to.\nLM Studio, offers simplicity and ease of use. It\u0026rsquo;s a great option for those who want to start using local models right away with minimal setup. If your goal is to generate text, code, or answers without needing fine-tuning, LM Studio is the optimal choice.\nRead too:\nLocal AI image generation. Stability Matrix An easy way to run local AI installation to generate images. 𝙓 ST - short for SillyTavern.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hv8.pages.dev/blog/en/local-ai-text-generation-koboldcpp-sillytavern-lm-studio/","summary":"How to run local AI text generation installation on your PC.","title":"Local AI text generation. KoboldCPP \u0026 SillyTavern. LM Studio"},{"content":" 🇺🇸 Author(s) or some of authors are not native speaker(s) of English. Text may contain bad spelling, incorrect expressions, verbal turns, sentence constructions, etc. 🚀 In this article, you\u0026rsquo;ll know how to easily set up a local installation for generating images on your computer \u0026ndash; without any technical knowledge. ✨ Introduction: About Stability Matrix We\u0026rsquo;ll be using Stability Matrix, a cross-platform package manager for Stable Diffusion that works on Windows, Linux, and macOS. Additionally, Stability Matrix supports installing various panels for managing image generation, including those that work with Flux models.\nStability Matrix handles everything for you, so you don\u0026rsquo;t need to install multiple dependencies separately and manually. This is nice because all necessary dependencies are kept in one place and won\u0026rsquo;t interfere with your operating system.\nFor example, if you already have a specific version of Python installed, Stability Matrix will install the required version anyway and use only that one without modifying yours. If you don\u0026rsquo;t have Python installed, it won\u0026rsquo;t appear in your system after installing a panel via Stability Matrix \u0026ndash; the version installed by Stability Matrix will be used exclusively within it.\n🖼️ Pictures Images generated using flux1-dev-bnb-nf4-v2 model.\n⚙️ Installing Stability Matrix Go to GitHub page of Stability Matrix repository and download it from Releases page:\nOr from the buttons in the README:\nYou can install Stability Matrix or run it without installation (portable). I recommend portable. To do this, simply create a folder for Stability Matrix and run the executable file from that folder.\nOf course, agree the license agreement \u0026ndash; without even reading it:\nInstalling Stability Matrix Next, choose the installation path or use Portable Mode:\nAt the end of installation, Stability Matrix will suggest you install panel right away.\nIf you\u0026rsquo;re not sure what you need, click Skip first-time setup:\n🚀 Installing and Launching first panel Panels can be installed from the Packages tab:\nClick Add Package:\nHere, you\u0026rsquo;ll find a list of panels available for installation. Each panel specifies its compatibility: CPU, NVIDIA, DirectML, AMD (Linux), macOS, etc.\nIf this is your first time, I recommend \u0026ldquo;Stable Diffusion WebUI\u0026rdquo; by AUTOMATIC1111 or \u0026ldquo;Stable Diffusion WebUI Forge\u0026rdquo; by lllyasviel.\nBoth panels, \u0026ldquo;Stable Diffusion WebUI\u0026rdquo; and \u0026ldquo;Stable Diffusion WebUI Forge\u0026rdquo; are very similar.\nHowever, Forge version offers more features and supports not only SD (Stable Diffusion) models, also Flux.\nStarting Flux models locally You do not have CLIP state dict! 𝙓 When you\u0026rsquo;ve chose the panel you need, click Install:\nDownload MP4 Your browser does not support video. Installation may take a lot of time, depending of the panel.\nWhen installation is complete, click 🚀 Launch. The panel startup may also take some time.\nIf everything runs good and there are no critical errors, output should look something like:\n1 2 3 ... local URL: http://127.0.0.1:7860 ... If this URL does not open automatically in your browser, click manually or copy and paste into your browser.\nWebUI by AUTOMATIC1111 You can learn how to use WebUI by AUTOMATIC1111 in the article about. Stable Diffusion (Text in Russian)\nOr watch video on YouTube:\n💡 WebUI and WebUI Forge very similar. Know one means you\u0026rsquo;ll understand the other as well.\nExcept for some features which are only support in Forge.\nLow VRAM Warning 1 2 3 4 5 6 [Low VRAM Warning] You just set Forge to use 100% GPU memory (**** MB) to load model weights. [Low VRAM Warning] This means you will have 0% GPU memory (0.00 MB) to do matrix computation. Computations may fallback to CPU or go Out of Memory. [Low VRAM Warning] In many cases, image generation will be 10x slower. [Low VRAM Warning] To solve the problem, you can set the \u0026#39;GPU Weights\u0026#39; (on the top of page) to a lower value. [Low VRAM Warning] If you cannot find \u0026#39;GPU Weights\u0026#39;, you can click the \u0026#39;all\u0026#39; option in the \u0026#39;UI\u0026#39; area on the left-top corner of the webpage. [Low VRAM Warning] Make sure that you know what you are testing. In some cases, you may get Low VRAM Warning.\nThis warning indicates that you have allocated too much video memory to the panel, which may cause the generation speed degrade over time.\nSolution: Reduce allocated memory by 500-1500 MB. For example, if you have 12 GB of VRAM, allocate 11 GB for panel.\nHow to do this depends of the panel, but here\u0026rsquo;s how it\u0026rsquo;s make in WebUI Forge:\n🔍 Stability Matrix Overview How to install models Models, LoRAs, encoders, VAEs, and everything else for all panels are located in /Data/Models. The main directories are:\n/StableDiffusion/ - Models (any, not only SD) /Lora/ - LoRAs /CLIP/ - Encoders /VAE/ - VAEs Packages The Packages tab, as you\u0026rsquo;ve probably guessed, here only for managing packages (panels). This includes installation, updates, removal, launching, etc.\nThe Inference sub-tab (inside Add Package) is used for installing panels primarily designed for running models.\nThe Training sub-tab (inside Add Package) is for installing panels designed for training models.\nInference Inference is a redesigned interface for Stable Diffusion, built into Stability Matrix.\nFrom the Stability Matrix Repository From the Stability Matrix repository:\nPowerful auto-completion and syntax highlighting using a formal language grammar Workspaces open in tabs that save and load from .smproj project files Customizable dockable and float panels Generated images contain Inference Project, ComfyUI Nodes, and A1111-compatible metadata Drag and drop gallery images or files to load states Some might find it useful, but self, I prefer using the stock interface of the installed panel.\nCheckpoint Manager If Packages is a package (panels) manager, then Checkpoint Manager is a manager for all your installed models, LoRAs, encoders, etc.\nModel Browser In Model Browser, you can browse models and download them from Hugging Face.\nAdditionally, you can link your Civitai account (Settings - Accounts) to browse and download models, LoRAs, and other resources from Civitai from Stability Matrix.\nOutput Browser In Output Browser, you can view all generated images.\nWorkflows Workflow ready-made processes and scenarios to simplify making of some tasks.\n📦 Where to find models Probably the best website for finding models is Civitai.\nCivitai not only offers a big pick of models but also allows creators to publish images. Users can also upload images generated with these models, making it easy to see what you can got.\nYou can also find models on Hugging Face.\nAnd of course, you can find models in the internet.\n🚧 Be careful running ckpt models, especially from untrusted sources!\nRunning ckpt models is risky as running an executable file.\nIt\u0026rsquo;s safer to use the safetensors instead.\nYou can read more about safetensors here.\n","permalink":"https://hv8.pages.dev/blog/en/local-ai-image-generation-stability-matrix/","summary":"An easy way to run local AI installation to generate images.","title":"Local AI image generation. Stability Matrix"},{"content":" 🇺🇸 Author(s) or some of authors are not native speaker(s) of English. Text may contain bad spelling, incorrect expressions, verbal turns, sentence constructions, etc. Most likely, you\u0026rsquo;re getting noise or just a blank instead of a beautiful image when trying to generate with the Flux model in Stable Diffusion web UI by AUTOMATIC1111.\nOr maybe you\u0026rsquo;re get error \u0026ldquo;You do not have CLIP state dict!\u0026rdquo; when using the Flux model on another panel.\nAnd that\u0026rsquo;s probably why you\u0026rsquo;re reading this.\n🚧 This is note about how to run the Flux model.\nNot tutorial on setting up local installation for image generation. Local AI image generation. Stability Matrix An easy way to run local AI installation to generate images. 𝙓 If you\u0026rsquo;re getting noise or blank image, that means your panel doesn\u0026rsquo;t support Flux models.\nAs of the time of writing, Flux works in WebUI Forge and ComfyUI, both of which are listed in the Stability Matrix. Also, for now, Flux models work best with NVIDIA GPUs.\n📝 The optimized version of the original Flux model, available here, can run out of the box on WebUI Forge.\nBecause, all the necessary encoders and VAE are already built-in.\nModels You can download original models from here or here.\nGGUF versions are available here or here.\nWhen running Flux models (or any other models based on Flux), you might run into this error:\n1 You do not have CLIP state dict! This happens because the text encoder is missing. Someone refer to it as CLIP, though technically they\u0026rsquo;re slightly different things.\nTo make Flux model running, you\u0026rsquo;ll need to download the encoders and VAE.\nDownload encoders, clip-l* and t5* from here.\n📝 Whether you should use fp8 or fp16 depends on your hardware.\nIf you have low VRAM (4-8GB) and limited RAM (8-16GB), use fp8.\nIf you have mid-range VRAM (11-12GB) and a lot of RAM (32GB+), you can use fp16.\nIf you have plenty of everything, especially VRAM (20GB+), just go with fp16.\nNow, download the VAE files: vae/diffusion_pytorch_model.safetensors and ae.safetensors from here or here.\nPlace VAE into models\\VAE, and encoders into models\\text_encoder or models\\CLIP (if you\u0026rsquo;re using Stability Matrix).\nNow all that\u0026rsquo;s left is to load the VAE and encoders from the menu.\nYou can load them in any order.\nFor more details, check out this discussion from the WebUI Forge developer.\n","permalink":"https://hv8.pages.dev/blog/en/start-flux-model-local/","summary":"You do not have CLIP state dict!","title":"Starting Flux models locally"}]