@@ -30,9 +30,14 @@ cost twice over.
3030it relocates the ceiling rather than removing
3131it: packets now arrive faster than a single guest receive queue can drain, and
3232the drops reappear at a higher rate. More queue pairs is what removes them,
33- which is why both are defaults: vhost everywhere, and a queue count that follows
34- the VM's vCPU count. The knob you are more likely to reach for is the other
35- direction — see [ Choosing a queue count] ( #choosing-a-queue-count ) .
33+ which is why enabling vhost also enables a queue count that follows the VM's
34+ vCPU count — the two travel together.
35+
36+ vhost is ** off by default** and enabled per node (or per VM). Two things make
37+ it an opt-in rather than a default: turning it on changes the virtio-net device
38+ of every bridge/macvtap VM on its next boot, and it requires ` /dev/vhost-net `
39+ to be accessible to the account QEMU runs under, which the VMM cannot verify on
40+ the operator's behalf — see [ Enabling vhost on a node] ( #enabling-vhost-on-a-node ) .
3641
3742## Configuration
3843
@@ -47,22 +52,24 @@ bridge = "dstack-br0"
4752vhost = true
4853```
4954
50- Queue pairs are not a node setting. They default to the VM's vCPU count, capped
51- at 16, because the useful number follows the VM rather than the host — the guest
52- driver uses at most one queue pair per vCPU. A deployment overrides that per VM,
53- up to ` max_net_queues ` .
55+ Queue pairs are not a node setting. With vhost on they default to the VM's vCPU
56+ count, capped at 16, because the useful number follows the VM rather than the
57+ host — the guest driver uses at most one queue pair per vCPU. A deployment
58+ overrides that per VM, up to ` max_net_queues ` .
5459
5560Raising ` max_net_queues ` above 16 widens what a deployment may ask for without
5661moving the default's cap, so a larger VM never silently acquires a worse
5762default. Lowering it below 16 does lower the default too, because a node that
5863refuses a request for four queue pairs should not hand out sixteen by itself.
5964The hard ceiling from any source is 64.
6065
61- Turning vhost off also turns the multiqueue default off. Without vhost the QEMU
62- main loop drains every queue on one thread, so extra queues buy little while
63- still costing a netd interface, more MSI-X vectors, and a changed guest device.
64- An explicit queue count is still honoured without vhost, since that combination
65- is a deliberate request rather than a default.
66+ Without vhost the default is a single queue pair. The QEMU main loop drains
67+ every queue on one thread, so extra queues buy little while still costing a
68+ netd interface, more MSI-X vectors, and a changed guest device. An explicit
69+ queue count is still honoured without vhost, since that combination is a
70+ deliberate request rather than a default. The two defaults travelling together
71+ also means a node that never sets ` vhost ` keeps building the device its VMs
72+ have always had.
6673
6774A VM overrides either value at deploy time, and ` UpdateVm ` changes them
6875afterwards — the new values apply from the VM's next boot:
@@ -128,6 +135,31 @@ vhost state are not part of it, so retuning a NIC does not change app identity
128135or require an on-chain update. Adding or removing a NIC does: the NIC count
129136changes the guest's ACPI tables and therefore RTMR0.
130137
138+ ## Enabling vhost on a node
139+
140+ Setting ` vhost = true ` in ` [cvm.networking] ` is a node-wide behaviour change:
141+ every bridge or macvtap VM that has not pinned its own data plane gets a
142+ different virtio-net device on its next boot — ` vhost=on ` , ` mq=on ` with
143+ vCPU-scaled queue pairs, and the matching MSI-X vector count. The device is not
144+ measured, so attestation and app identity are unaffected. Before flipping it:
145+
146+ 1 . ** Verify ` /dev/vhost-net ` is accessible to the account QEMU runs under.**
147+ It is ` root:kvm 0660 ` on Debian-family hosts, where adding the account to
148+ the ` kvm ` group suffices, and ` root:root 0600 ` on several others. If the
149+ account lacks access, QEMU exits at launch and every affected VM stops
150+ restarting. The VMM warns at startup when its own access fails, but it
151+ cannot refuse on that basis — QEMU need not share its credentials.
152+
153+ 2 . ** Restart ` netd ` before or together with the VMM.** Multiqueue bridge NICs
154+ are prepared by ` netd ` , and the VMM checks that ` netd ` echoes the queue
155+ count it built. An older ` netd ` fails that check; the launch is rolled back
156+ and fails with the reason in the VMM log, but the VM does not start until
157+ ` netd ` is upgraded.
158+
159+ 3 . ** Roll back by setting ` vhost = false ` .** The node value reaches every VM
160+ that did not pin ` vhost ` explicitly, from its next boot; a VM that pinned
161+ ` vhost = true ` keeps it until updated.
162+
131163## What each mode supports
132164
133165| Mode | netdev | vhost | queues > 1 |
@@ -144,8 +176,8 @@ vhost switches bridge mode to a `tap` netdev driven by the same setuid
144176no compiled-in default path for the ` tap ` netdev, so the VMM probes the known
145177distribution locations; set ` cvm.qemu_bridge_helper ` if yours is elsewhere. If
146178no helper is found the NIC falls back to the non-vhost ` bridge ` netdev with a
147- warning, because vhost is a default and a default must not stop a node from
148- booting VMs .
179+ warning, because a node-wide setting must not stop a node from booting VMs
180+ that never asked for it .
149181
150182The helper returns exactly one descriptor, which is why more than one queue
151183pair in bridge mode is created by ` netd ` instead: it adds a persistent
@@ -204,6 +236,21 @@ A VM with fewer vCPUs than queues leaves the extra pairs idle — `ethtool -l
204236eth0` reports the smaller number. An explicit over-provision is not rejected at
205237deployment, because ` vmm-cli.py resize ` can raise the vCPU count later.
206238
239+ Queue pairs also cost guest memory, and the default follows vCPUs while memory
240+ does not: each RX ring keeps 256 page-sized buffers posted, so 16 queue pairs
241+ pin roughly 16 MB of pages plus per-queue NAPI and socket state. On a 1 GB
242+ guest with 16 queue pairs under sustained load, RX refill failed with atomic
243+ order-0 page-allocation errors (` try_fill_recv ` /` skb_page_frag_refill ` in the
244+ guest log); the same shape with 2 GB ran clean. A many-vCPU VM given very
245+ little memory should also be given fewer queue pairs — as a working floor,
246+ keep at least ~ 128 MB of guest RAM per queue pair beyond the application's own
247+ needs. The TDX bounce-buffer pool is not the constraint: the guest kernel
248+ sizes swiotlb at 6% of RAM clamped to [ 64 MB, 1 GB] , while peak demand is
249+ bounded by ring size at about 2 MB per queue pair — a deliberately undersized
250+ 32 MB pool sustained full multiqueue line rate with zero `swiotlb buffer is
251+ full` events, so adding a ` swiotlb=` boot parameter would only take memory
252+ away from the allocator that actually runs out.
253+
207254` vectors ` is derived, never configured: ` 2N + 2 ` , one vector per queue
208255direction plus config and control. One queue pair emits no ` mq=on ` or
209256` vectors= ` at all, leaving the guest device line byte for byte identical to the
@@ -223,11 +270,29 @@ interface to describe, so it reports what the next launch would build instead --
223270the same calculation, against the node configuration and manifest as they stand
224271now, rather than the ones a finished boot ran under.
225272
226- If that account lacks access, QEMU exits at startup and the VM never boots. The
227- VMM does not pre-check this: QEMU need not share the VMM's credentials, so
228- refusing a launch on the VMM's own access would block deployments the host can
229- run. It only warns when the device node is missing outright, which is a fact
230- about the host rather than about either account.
273+ If that account lacks access, QEMU exits at startup and the VM never boots —
274+ there is no fallback to the userspace backend at this point, on any QEMU
275+ version (verified on 8.2.2 and 10.2). What the per-VM launcher log shows
276+ depends on the version: QEMU 8.2 prints `warning: tap: open vhost char device
277+ failed: Permission denied` (once per queue) and then dies on ` net/net.c:1185:
278+ net_client_init1: Assertion 'nc' failed` — an upstream bug
279+ ([ qemu #1486 ] ( https://gitlab.com/qemu-project/qemu/-/issues/1486 ) ); later
280+ versions exit cleanly with ` Could not open '/dev/vhost-net' ` . Grep for either.
281+ The VMM does not refuse a launch over this: QEMU need not share the VMM's
282+ credentials, so a refusal based on the VMM's own access would block deployments
283+ the host can run. It warns instead — when the device node is missing outright,
284+ and when the VMM's own open is denied, since QEMU usually does share its
285+ account.
286+
287+ QEMU does have a * runtime* fallback, at a different failure point: once the
288+ netdev initialized with vhost, a later ` vhost_net_start() ` failure at guest
289+ driver activation logs `unable to start vhost net: <errno >: falling back on
290+ userspace virtio` and keeps the NIC working on the userspace data path. That
291+ path is reachable only after ` /dev/vhost-net ` was opened successfully at
292+ launch, so an access problem never lands there. If it does fire, it is the one
293+ case where ` GetInfo ` can overstate the data plane — the interface reports the
294+ vhost state the launch settled while the packets take the userspace path — and
295+ that QEMU log line is the indicator.
231296
232297vhost-net works normally in a TDX guest: the virtio rings and buffers live in
233298shared, unencrypted memory precisely so a host-side backend can reach them.
0 commit comments