ASUS Ascent GX10 (NVIDIA GB10) setup guide

Setting up a single ASUS Ascent GX10 (DGX Spark) as an inference server: essentials, recommended settings, troubleshooting, and recipes to try

Related: How to set a clock cap on the GB10 → (Korean) / Why vLLM pegs four CPU cores at 100% on the DGX Spark → / Running DeepSeek V4 Flash on two DGX Sparks → (Korean) / Connecting four DGX Sparks through a switch → (Korean)

한국어 원문 → / 日本語 →

Updated 2026-08-28. Reflects what we confirmed while setting up a fourth machine: a corrupt netplan file in the factory image (section 3), a firmware capsule failure when several apply at once, and the retry (1.3), a fix to the purge protection gate (1.1), dgx-dashboard surviving server mode (1.1), when to turn Wi-Fi off (1.4), and an updated per-rail ceiling for the 200G link (section 3).

While bringing in our third ASUS Ascent GX10 (NVIDIA GB10, part of the DGX Spark family), we walked through the procedure of turning a factory-state machine into an inference server once more, from the beginning. This article is about the ASUS Ascent GX10. The NVIDIA DGX Spark and GB10 machines from other vendors are probably similar, but firmware, base image, and package composition can differ by product, so check on your own hardware before following along. This time we applied in one pass everything we had worked out piece by piece over several weeks on the first two machines, and we also record the problems we hit along the way. The article splits the procedure into four parts: what you must do, what is worth doing, the problems we ran into, and recipes you can run right away on a single node once setup is done. Settings specific to our home network (VLANs, addresses, switches) are left out.

Caution. This article describes system changes that are hard to undo or can cut off remote access: firmware upgrades, driver replacement, removal of desktop packages, firewall configuration. Before running any command, check what it changes on your own hardware and version, and proceed at your own risk. The numbers are measurements from our machines, not guarantees.

Contents
  1. Getting started: follow the on-screen setup, then switch to SSH
  2. Essentials: server mode, driver 595, PD firmware, security defaults
  3. Recommended settings: GPU clock cap, CPU performance-core cap, vLLM spin patch, Tailscale
  4. Troubleshooting: problems we hit, misinformation, 200G ConnectX-7
  5. Recipes to run right away on one node: ollama Qwen3.8-27B, DeepSeek V4 Flash EXL3
  6. One-screen checklist
  7. Appendix: why it shows 121GiB, not 128GiB

0. Getting started: follow the on-screen setup, then switch to SSH

The first-boot setup screens (account, keyboard, network) can be completed as guided. There is nothing special to choose. After that it is easier to put the monitor and keyboard away and work over SSH. Everything else in this article was done over SSH. We recommend giving a coding agent (for example Claude Code) SSH access and working through it together. Most of the steps below are repeated checks and comparisons, which suits an agent well.

# On the DGX Spark (once, from a local terminal)
ip -br a                       # check the wired IP
systemctl status ssh           # check the SSH server is running (if missing: sudo apt install openssh-server)

# On your workstation
ssh-copy-id user@<spark-ip>     # copy the public key. No password needed from here on
ssh user@<spark-ip>

This is what the factory state looked like. Driver and firmware versions can differ from machine to machine, so check first. The fourth machine came with the timezone already set to Asia/Seoul and a newer OTA metapackage, so it differed from the table in small ways.

ItemFactory state (delivered 2026-08)Target in this article
OS / kernelUbuntu 24.04.4 / 6.17.0-1031-nvidiaunchanged
Default targetgraphical (GNOME, gdm3, 13 snaps, setup wizard app)multi-user
GPU driver580.173.02595.84
USB-C PD firmware0x10x516
ClocksGPU up to 3003MHz, CPU performance cores 3.9GHz (no limit)(recommended) GPU 2000MHz, performance cores 2.8GHz
SSH / firewallpassword login allowed / firewall offkey only / default deny
Security updatesno automation, 15 pendingapplied automatically, no automatic reboot

0.1 Initial state check: are all ports and devices up?

Before starting the setup, and once more after finishing, compare the device list the OS sees against the expected values. Run the lines one at a time or bundle them into a script.

lspci | grep -E 'ConnectX|NVIDIA|Non-Volatile|Realtek|MEDIATEK'   # PCI devices
ip -br link                                                        # network interfaces
ethtool enP7s7 | grep -E 'Speed|Link detected'                     # 10G RJ45
for d in /sys/class/net/en*np*; do echo $(basename $d) $(cat $d/operstate) $(cat $d/speed); done   # 200G
ls /sys/bus/usb/devices | grep -c '^usb'; lsusb                    # number of USB root hubs, and devices
lsblk -d -o NAME,SIZE,MODEL | grep nvme                            # NVMe
nproc; free -g | grep Mem; nvidia-smi -L                           # CPU, memory, GPU
nvidia-smi --query-gpu=driver_version,clocks.sm --format=csv       # driver, clock
for z in /sys/class/thermal/thermal_zone*; do printf '%s=%s°C ' $(cat $z/device/path | sed 's/.*\.//') $(( $(cat $z/temp)/1000 )); done; echo   # 7 thermal zones
fwupdmgr get-devices | grep 'Current version'                      # EC, SoC FW, PD, NVMe firmware
sudo dmesg --level=err,crit | grep -v -i tpm | tail                # kernel errors
systemctl list-units --state=failed                                # failed units
ItemExpected on the GX10 (identical on all four machines)
PCI devices1 NVIDIA VGA (GB10), 1 NVMe (Phison), 1 Realtek 8127 10G, 1 MediaTek 7925 Wi-Fi, 4 ConnectX-7 (only while a cable is plugged in)
Network interfacesenP7s7 (10G RJ45) UP 10000Mb/s, wlP9s9 (Wi-Fi), and four 200G interfaces: enp1s0f0np0, enp1s0f1np1, enP2p1s0f0np0, enP2p1s0f1np1 (two physical ports, each exposed as two PCIe functions). A port with a cable shows up 200000
USB12 root hubs (6 USB 2.0 + 6 USB 3.0), one built-in Wi-Fi/BT combo device
Storage1 NVMe (1TB or 4TB depending on the shipping configuration)
CPU / memory / GPU20 cores / MemTotal 121GiB / GPU 0: NVIDIA GB10
Thermal zones7: TSOC, TS0E, TS0P, TS1E, TS1P, TGPU, TUNC. Idle 42-50°C
FirmwareEC, SoC FW (UEFI), USB-C PD, and NVMe versions are listed. If PD is 0x1, go to 1.3
Kernel errors / failed unitsnvidia-fs: warning: error retrieving numa node is harmless. Otherwise 0 errors, 0 failed units

Check physically too. A device showing up in the OS and a port actually working are two different things. The two 200G QSFP ports in particular do not even appear as PCI devices without a cable, so plug a DAC cable in and confirm that both cages link up (operstate up, speed 200000). Plug a device into each USB-C port and check it appears in lsusb, check the RJ45 link LED and 10000Mb/s in ethtool, and try HDMI with a monitor once. Best done before bolting the machine into its final spot, while it can still be exchanged. We brought in the third machine without cables in the 200G ports and spent a while chasing a phantom "it doesn't show up on PCI" problem (section 3).

1. Essentials

1.1 Switching to server mode while keeping the platform packages

The factory image is a GNOME desktop. If the machine is going to be an inference server, it is better to strip the desktop off. The GB10's CPU and GPU share a single 128GiB memory pool, and a running desktop takes several GiB out of that pool. The README of the single-node recipe lists available memory at launch puts available memory at launch at about 114.5GiB on a host with the desktop still running; this node, switched to server mode, had 117.4GiB at launch of the same recipe and 119.0GiB idle. This was not measured before and after on the same machine, so it is not a strict comparison, but a 3-4.5GiB difference goes straight into KV cache headroom when loading a large model.

Figure 1. Available memory (out of MemTotal 121.6GiB). Desktop host (recipe README, at launch) ~114.5GiB; this node in server mode 117.4GiB at launch, 119.0GiB idle.

Why a 128GiB machine reports MemTotal 121GiB, and where the rest goes, is covered in the appendix.

The problem is not the command that removes the desktop but the apt autoremove --purge that follows it. When setting up the second machine, that command took out 14 NVIDIA platform packages in one go. These packages are held only as dependencies of a metapackage called dgx-spark-ota-update-meta, so the moment the metapackage leaves together with snapd, they become "packages nobody needs". The two snippets that supply boot parameters (nvidia-spark-initcall-bl, nvidia-spark-grub-pci) went with them, and we recovered with a reinstall, update-grub, and a reboot.

This time we blocked it in three steps. Mark the platform packages as manually installed before removing anything; run the removal as a simulation first and stop if the removal list contains driver, CUDA, or platform packages; and compare the boot parameters afterwards.

# Commands in sections 1 and 2 assume a root shell (sudo -i)

# 1) Protect: mark the metapackage's dependencies (except snap-related) + installed NVIDIA/DGX/docker/CUDA packages as manually installed
apt-cache depends dgx-spark-ota-update-meta | awk '/Depends:/{print $2}' | grep -v snaps | xargs apt-mark manual
apt-mark manual $(dpkg -l | awk '/^ii/{print $2}' | grep -E '^(nvidia-|dgx-|docker|containerd|cuda|lldpd|mlnx)')
# (if you have a reference machine, compare against its `apt-mark showmanual` list and add what is missing)

# 2) Switch + simulation gate (stops if protected packages appear in the removal list)
systemctl set-default multi-user.target
CAND="ubuntu-desktop ubuntu-desktop-minimal gdm3 gnome-shell xorg xwayland gnome-control-center \
      libreoffice-core network-manager-gnome snapd nvidia-desktop-default-snaps nvidia-system-station \
      dgxstation-desktop dgx-oobe-desktop dgx-spark-ota-update-meta ubuntu-server-minimal"
PURGE=""; for p in $CAND; do dpkg -s "$p" >/dev/null 2>&1 && PURGE="$PURGE $p"; done   # installed ones only
apt-get -s purge $PURGE | awk '/^Remv/{print $2}' | grep -E '^(nvidia-driver|linux-modules-nvidia|cuda|docker|nvidia-spark|network-manager$|netplan|openssh-server$|ufw$)' \
  && { echo "protected package in removal list, aborting"; exit 1; }
apt-get purge -y $PURGE
apt-get -s autoremove --purge | awk '/^Remv/{print $2}' | grep -E '^(nvidia-|dgx-|lldpd|docker|cuda|mlnx)' \
  && { echo "protected package in removal list, aborting"; exit 1; }
apt-get autoremove --purge -y
rm -rf /snap /var/snap /var/lib/snapd /home/*/snap

# 3) Compare (once more after the reboot)
update-grub
grep -o 'initcall_blacklist=[^ ]*\|pci=pcie_bus_safe\|iommu.passthrough=[0-9]' /boot/grub/grub.cfg | sort -u

The gate must judge on the package name field alone, not the whole Remv line. The line carries a version string (Remv network-manager [1.46.0-1]), so a pattern anchored at the end of a name silently fails to match, and dropping the anchors produces false positives on network-manager-gnome, which is a legitimate removal target. On the fourth machine we found the whole-line gate was effectively inert, and fixed it as above.

We do not restore the OTA metapackage. Restoring it brings snapd back through nvidia-desktop-default-snaps and breaks server mode. What the metapackage does is "pull in the packages NVIDIA adds in the next release", so every so often compare its Depends list with what is installed and install only the missing ones individually. Individual platform packages update normally through apt upgrade.

One more thing. Even after the switch to server mode, dgx-dashboard and dgx-dashboard-admin stay behind. They are the desktop web management UI (port 11000) and its root D-Bus backend; a server managed over SSH has no use for them, and the admin process's resident memory grows to hundreds of MB over time (270-360MB measured on our nodes). Turn them off with systemctl disable --now dgx-dashboard dgx-dashboard-admin.

1.2 Driver 580 → 595

The reason to switch is a defect, not speed. On 580.173.02, a GPU error (Xid 13) reproduced on the path that offloads KV cache to disk, and it disappeared with the same code on 595.84. We put new machines on 595 from the start as well.

apt-get -s install nvidia-driver-595-open linux-modules-nvidia-595-open-nvidia-hwe-24.04 | grep -E '^(Inst|Remv)'
# Check that every removal (Remv) line is a 580-series package. If not, stop
apt-get -s install nvidia-driver-595-open linux-modules-nvidia-595-open-nvidia-hwe-24.04 \
  | grep ^Remv | grep -v 580 && { echo "removal other than 580, aborting"; exit 1; }
apt-get install -y nvidia-driver-595-open linux-modules-nvidia-595-open-nvidia-hwe-24.04

linux-modules-nvidia-595-open-nvidia-hwe-24.04 pulls in the prebuilt module package matching the running kernel (here …-6.17.0-1031-nvidia). No DKMS build is needed, and machines with different kernels each get the right one. Right after installation, until reboot, nvidia-smi reports "Driver/library version mismatch"; this is normal. To roll back, check the version with apt-cache policy nvidia-driver-580-open and then apt-get install nvidia-driver-580-open=<version> linux-modules-nvidia-580-open-nvidia-hwe-24.04.

1.3 USB-C PD firmware

There is a phenomenon reported across the GB10 family (examples: a GX10 user report, a PGX user report; summary in the appendix of the clock cap article (Korean)). Under full load the clock sits at 400-950MHz and performance drops to a third, yet every throttle flag reads Not Active. The understood cause is the USB-C PD controller firmware negotiating power incorrectly and the SoC locks its own clock down; a reboot does not clear it, and you have to unplug the power cable and drain the machine. The prevention is to update the PD firmware to the latest version. This machine's PD firmware was 0x1, and LVFS had 0x516 (urgency High) (source: fwupdmgr get-updates output, LVFS firmware ID com.asus.gx10dgx.usbpd.firmware, LVFS page).

fwupdmgr refresh --force
fwupdmgr get-updates                  # "GX10 USB-C PD FW Controller Update" 0x516
fwupdmgr update -y --no-reboot-check  # the capsule is staged in /boot/efi/EFI/ubuntu/fw/
# Reboot (about 2 minutes with a single capsule) → check 0x00000516 in fwupdmgr get-devices

We handled the driver replacement, the server mode switch, and the firmware update into a single reboot. The UEFI ("GX10 SoC FW") and EC firmware also arrive through the same channel.

When several capsules are pending, watch out for two things. On the fourth machine, the EC, SoC FW, and PD capsules were bundled into one reboot; it took about 9 minutes to get SSH back, and the PD update alone failed (expected 0x516 and got 0x1). Running fwupdmgr refresh --force and then update again re-staged the failed capsule, and the reboot that applied only the PD capsule succeeded. Judge the results with fwupdmgr get-devices --json: in the text output every device is named "UEFI Device Firmware", which makes them hard to tell apart, while the JSON UpdateState field (2=success, 3=failed, 4=needs reboot) is unambiguous.

1.4 Security and update defaults

ItemSettingNotes
sshdPasswordAuthentication no, KbdInteractiveAuthentication no, PermitRootLogin prohibit-passwordAfter confirming the key is in ~/.ssh/authorized_keys, sshd -t && systemctl reload ssh
ufwincoming deny / outgoing allow / routed deny. SSH only from the management network; service ports one line at a time as neededIf you are working over SSH, add ufw allow from <mgmt-net/24> to any port 22 proto tcp first, then ufw enable, and confirm a new connection from a second terminal while keeping the existing session open. Add inference API ports (8000, 8888, and so on) explicitly when opening them to the LAN
unattended-upgradesautomatic package lists and upgrades, Automatic-Reboot "false", Remove-Unused-Kernel-Packages "true"A reboot in the middle of inference is unacceptable, so automatic reboot is off
Timezone / Wi-Fitimedatectl set-timezone / nmcli radio wifi offFactory state is UTC (varies by machine). Keep Wi-Fi alive as a second path until the desktop purge, the reboot, and the wired verification are all done, then turn it off last
persistenced / journaldenabled / persistentfactory defaults, unchanged

2. Recommended settings

Everything from here on is optional; the machine works without it. But if you are going to run a GB10 for a long time without a separate cooling fan, power and heat deserve a thought at least once, and the numbers below are material for that decision.

2.1 GPU clock cap: power −40-50%, LLM decode −5%, video generation +7-17%

The GB10 has no wattage power limit (nvidia-smi -pl). The only control is the clock cap (-lgc). The two charts below are the result of sweeping from 1400MHz to unlimited in 200MHz steps. Decode is memory-bandwidth bound, so lowering the clock barely reduces token speed, while power and temperature climb steeply with the clock.

Figure 2. LLM decode (DeepSeek V4 Flash Q2, llama.cpp, median of 3 runs of 256 tokens), tok/s (solid) and power under load (dashed) per cap. 2000MHz gives 16.7 tok/s at 22W, unlimited 17.5 tok/s at 45W. At 2600 and above the measured SM clock stays at ~2495MHz, so the results are the same.

Figure 3. Video generation (MiniMax-H3, 20 steps), total time (solid) and power under load (dashed) per cap. 2000MHz: 103s, 50W, 66°C. 2200: 97s, 65W, 73°C. 2400 and above: about 96s, 85W, 80°C or more. For work that is mostly GPU compute, the time gain ends at 2200-2400.

CapUseLLM decodeVideo generation
2200Speed first17.1 tok/s, 28W, 57°C97s, 65W, 73°C
2000Balanced (recommended value)16.7 tok/s, 22W, 55°C103s, 50W, 66°C
1800Midsummer, poor ventilation16.2 tok/s, 19W, 53°C107s, 41W, 61°C
1400-1600Long runs, low heat15.0-15.7 tok/s, 15-17W, 49-51°C113-125s, 28-33W
# /etc/systemd/system/gpu-clock-cap.service
# After writing: systemctl daemon-reload && systemctl enable --now gpu-clock-cap   (to turn off: systemctl stop)
[Unit]
Description=GPU clock cap 300-2000MHz
After=multi-user.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/nvidia-smi -lgc 300,2000
ExecStop=/usr/bin/nvidia-smi -rgc
[Install]
WantedBy=multi-user.target

Setting 2000 actually gives about 1989MHz, snapped to an internal step. One thing to remember: this cap is a benchmark condition. When recording performance numbers, record the clock condition with them, and check the actual clock under load with nvidia-smi --query-gpu=clocks.sm. All the numbers in the recipes below were taken with this cap in place.

2.2 CPU performance-core cap: why limit the CPU?

The CPU cap exists for a different reason than the GPU cap. The starting point was "why is the CPU hot during LLM inference?" When we ran decode with vLLM (TP=2) across two connected GB10s, the GPU sat around 70°C while three or four performance cores were pinned at 100% and pushed the SoC temperature to 96°C. The cause was vLLM's default of spinning while its inter-process message queue waits for a new message (2.3 below). That fix lives on the software side, and the problem can come back when the engine or image changes. So we also put a performance-core clock cap on the host as a safety net. A single-node TP=1 setup has none of the spinning in 2.3, so this cap is insurance for when another engine or configuration overdrives the CPU, and it is optional.

The GB10's 20 cores are 10 performance cores (up to 3.9GHz) and 10 efficiency cores (2.8GHz). Looking at the temperature change per zone when the performance cores are capped at 2.8GHz, the same as the efficiency cores, the drop shrinks with distance from the performance cores, which shows they are the heat source.

Figure 4. Temperature change under load (°C) with the performance cores capped 3.9 → 2.8GHz. Performance cores −14, uncore/memory −3.7, GPU −1.7, NVMe 0. SoC overall −9. Single-stream speed was unchanged; 8 concurrent streams −5%.

# /etc/systemd/system/cpu-clock-cap.service: cap only the performance cores (max 3.9GHz) at 2.8GHz
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/bash -c 'for p in /sys/devices/system/cpu/cpufreq/policy*; do \
  m=$(cat $p/cpuinfo_max_freq); \
  if [ "$m" -gt 2808000 ]; then echo 2808000 > $p/scaling_max_freq; fi; done'
ExecStop=/usr/bin/bash -c 'for p in /sys/devices/system/cpu/cpufreq/policy*; do \
  cat $p/cpuinfo_max_freq > $p/scaling_max_freq; done'
[Install]
WantedBy=multi-user.target

/sys/devices/system/cpu/cpufreq/boost defaults to 0 on this platform, so there is nothing to touch there. In short, the GPU cap is a ceiling on power and heat, and the CPU cap is a safety net for when software starts overdriving the CPU. For both, know not just the number but the reason it is there, so you can lift it at the right time later.

2.3 vLLM spin patch: when connecting two or more nodes

Scope first: this spinning happens only in vLLM's multiprocess executor (mp executor). It does not apply when running TP=1 on one node (the worker lives in the same process, so there is no queue at all). This is about connecting two or more nodes, or explicitly passing --distributed-executor-backend=mp.

vLLM's shm_broadcast.py has a default that spins for 1 second (busy_loop_s=1) while waiting on the message queue before it goes to sleep. The interval between decode steps is far shorter than 1 second, so in practice it is always spinning. On an ordinary server with dozens of cores, wasting 3-4 cores goes unnoticed, but on the GB10, where CPU and GPU sit in one package, that heat becomes SoC temperature directly. We captured stacks with py-spy and changed the default to 2ms; the combined CPU of the vLLM processes dropped from 333% to 89% and the SoC temperature fell 11°C. The investigation is in the original article, and the controlled reproduction in the follow-up experiment.

Figure 5. Spin default (1 second) vs the 2ms patch. Left: combined CPU usage of the vLLM processes (%). Right: SoC temperature under load (°C, node without an external fan). In the reproduction, the default reached 85°C after 90 seconds of load, 90°C at 2 minutes 18 seconds, and a peak of 95°C; the patch peaked at 85°C.

# Inside the container/virtualenv, before starting vLLM (adjust the path to your install location)
sed -i 's/busy_loop_s: float = [0-9.]*/busy_loop_s: float = 0.002/' \
  $(python -c 'import vllm,os;print(os.path.dirname(vllm.__file__))')/distributed/device_communicators/shm_broadcast.py
grep -n 'busy_loop_s' $(python -c 'import vllm,os;print(os.path.dirname(vllm.__file__))')/distributed/device_communicators/shm_broadcast.py
# The changed line should show up. If the pattern is missing, sed silently does nothing.
# If your build exposes it as an environment variable (VLLM_MQ_SPIN_SECONDS and the like), use that instead.

One honest caveat. In a later re-verification, single-stream decode was about 2-6% slower with the patch: 38.7 tok/s vs 40.9 default (within run-to-run variance, but the direction was consistent; no difference at 4 concurrent streams, and the CPU reduction reproduced as before). So we treat this patch as "a switch to flip when heat is the problem". No external cooling and it is summer? On. Single-stream speed matters most? Off. The CPU cap in 2.2 stays in place regardless of that choice.

2.4 Tailscale: SSH from outside the house too

Once an inference server is set up there is little reason to sit at it, and most access is SSH from somewhere else. Putting Tailscale on it instead of router port forwarding lets you connect by the same name from anywhere. The later parts of this article were also done from outside the house over the tailnet.

curl -fsSL https://tailscale.com/install.sh | sh
tailscale up                      # log in at the printed URL (or create an auth key in the admin console and pass it with --authkey)
tailscale ip -4                   # this machine's tailnet address
ufw allow in on tailscale0        # if the firewall is on, allow the tailnet interface as a whole

With MagicDNS on, ssh <hostname> is all it takes. The inference API can be opened over the same path, so for services used outside the LAN it is simpler to open them only over the tailnet than to add LAN rules to ufw.

3. Troubleshooting: problems we hit and misinformation

SymptomCause / fix
docker.service fails after reboot: error creating buildkit instance: invalid databaseThe buildkit local DB shipped in the factory image (created 2025-09) was corrupt. The two machines we brought in most recently reproduced it identically, so it looks like a defect common to the image. It is only build cache, so systemctl stop docker.socket docker; mv /var/lib/docker/buildkit /var/lib/docker/buildkit.bak; systemctl start docker. Unrelated to images or containers. Then register the nvidia runtime with nvidia-ctk runtime configure --runtime=docker.
nvidia-smi right after the driver install: Driver/library version mismatchThe old kernel module is still loaded while only the libraries are new. Resolved by a reboot. Do not run GPU jobs before rebooting.
On 595, nvidia-smi --query-gpu=clocks.applications.graphics and display_mode return strings"Requested functionality has been deprecated". Health-check scripts that do numeric comparisons will break. Read clocks.sm or handle the string as an exception.
GPU clock stays at 400-950MHz under load, power ~10W, all throttle flags Not ActivePD firmware low-clock lock (1.3), reports 1, 2). Neither a reboot nor nvidia-smi -rgc clears it: shut down → unplug the power cable → press the power button to drain → reconnect after 1 minute. Distinguish it from an intentional cap (about 1989MHz).
Boot parameters disappeared after apt autoremovePlatform packages that were dependencies of the metapackage were removed with it (1.1). After a large purge, compare /proc/cmdline and /etc/default/grub.d/ with a reference machine.
netplan generate fails with Invalid YAML: aliases are not supported and no netplan configuration takes effectThe factory image can carry a corrupt netplan file whose entire content is null bytes (our fourth machine: 715 bytes, all 0x00). One broken file makes netplan give up on everything, so the symptom shows up far from the cause. Sweep /etc/netplan/* with file, move the broken file away, and run netplan generate again.
A static IP reverts to DHCP after a rebootOn this image netplan is the source of truth for the network. On some machines the NM profiles exist only under /run (volatile), so settings made with nmcli con mod can vanish on reboot. Put persistent configuration in /etc/netplan/. An empty /etc/NetworkManager/system-connections/ is not a fault; it is the netplan renderer working as designed.
200G ConnectX-7 missing from lspciThe NIC is not exposed unless a cable is plugged in (managed by the hotplug package). Not a driver problem.
Files left in /tmp vanished after a rebootUbuntu clears /tmp at boot. Move staging files to home before rebooting.
Waited for a remote script to finish with pgrep -f scriptname and it stayed "running" foreverThe waiting side's ssh command line contains the same string, so it matches itself. Use a bracket pattern like pgrep -f "[d]ownload.sh". pkill -f kills itself for the same reason.
When a model server takes more than 90% of memory, a userspace OOM daemon may kill it firstCheck whether a daemon like earlyoom is active (systemctl is-active earlyoom). Some recipes that intentionally fill memory with a large model tell you to turn earlyoom off.
Misinformation: "disabling Display in UEFI frees memory"

In the single-node recipe community, advice is circulating that "the desktop/display takes 7GiB of UMA, so disable the Display item in UEFI" (the 7GiB figure comes from the MiaAI recipe README). On inspection, the GX10 firmware (GX10DGX.0105) does have items like Display, iGPU, and iGPU Memory Carveout, but they are all hidden (SuppressIf) and do not appear in the setup screen, and they are not in NVIDIA's official UEFI guide either. What the firmware actually takes is fixed at about 3.9GiB (including the 3.0GiB carveout), as shown in the appendix, and the 7GiB in question is userspace GPU allocations by GNOME/Xorg and the like, so it comes back when you switch to server mode. There is nothing to do in the firmware, and editing the hidden variables directly is unverified, so we do not recommend it.

200G ConnectX-7: not visible, or linked but not working, or slow

The 200G ports used to join two or more nodes confused us at four stages. The symptoms and fixes for each stage are listed in order. All of it actually happened on our machines.

  1. No cable, no device. Not a single ConnectX-7 line in lspci. The driver (mlx5_core) is loaded. This is normal behavior managed by the hotplug package, and the device appears once a cable is plugged in.
  2. With a cable in, one port shows up as two interfaces. Two differently named interfaces such as enp1s0f0np0 and enP2p1s0f0np0 are the same physical port (a socket-direct configuration exposed on two PCIe roots. NVIDIA's documentation also says each QSFP port appears as two Linux interfaces: ConnectX-7 Networking; the structure is explained by ServeTheHome). cat /sys/class/net/*/phys_switch_id shows the same value for all four interfaces, and only phys_port_name differs (p0 or p1). Do not bond them; give the two interfaces IPs in different subnets (NVIDIA's Connect Two Sparks playbook does the same). That lets NCCL split traffic across both paths so the bandwidth adds up. At this stage the link and RoCE state are ACTIVE, and it only looks like "it won't come up" only because there is no IP.
    nmcli con add type ethernet con-name ic200g   ifname enp1s0f0np0   ipv4.method manual ipv4.addresses <rail-A>/24 ipv4.never-default yes 802-3-ethernet.mtu 9000
    nmcli con add type ethernet con-name ic200g-b ifname enP2p1s0f0np0 ipv4.method manual ipv4.addresses <rail-B>/24 ipv4.never-default yes 802-3-ethernet.mtu 9000
    # The peer machine uses the same two subnets. In the firewall, allow all four port names (so it survives moving the cable to the other cage)
    for i in enp1s0f0np0 enp1s0f1np1 enP2p1s0f0np0 enP2p1s0f1np1; do ufw allow in on $i; done
  3. Every indicator is normal but throughput is 1/8. Link 200000Mb/s, RoCE 4X×50G, PCIe Gen5 x4, zero error counters, jumbo frames pass, yet both ib_write_bw and iperf3 stall around 13Gb/s (normal is 98-110Gb/s per path, about 196Gb/s combined; the per-path ceiling is the PCIe Gen5 x4 link, not the cable, and in a later switch-based setup ib_send_bw read a uniform 109Gb/s per path. NVIDIA's playbook benchmarking guide expects about 197Gb/s from ib_write_bw, and the same 13Gb/s ceiling is reported on the NVIDIA forums). If two processes on the same device split it 6.9+6.9, you are hitting a device ceiling, and if two interfaces within one machine sending to each other also give 13, the cable and the peer machine are ruled out. A reboot does not fix it. Shut down both machines, unplug the power cables, press the power button to drain, and reconnect after 1 minute; that cleared it (13.3 → 108Gb/s, 196Gb/s over both paths). It held through several normal reboots afterwards. This looks like the same family of power-side issue as the PD firmware low-clock lock.
  4. Moving the cable to the other cage breaks the configuration. The NetworkManager profile has the port MAC baked in, so activation fails with permanent MAC address doesn't match. Clear the MAC lock with nmcli con mod ic200g 802-3-ethernet.mac-address "" connection.interface-name <new interface> and bind by name again. Firewall rules are also by port name, so open all four as above.

Two things to remember when diagnosing. ufw allows ICMP by default, so you can get a state where ping works but TCP does not (ib_write_bw says "Couldn't connect" while ping is fine). And on the GB10 there are reports of ib_write_bw reading low while NCCL is fine (NVIDIA forums), so make the final call with NCCL (nccl-tests). GPU_DIRECT_RDMA_SUPPORTED=False in the NCCL log is normal on the GB10.

All of the above is about connecting two machines directly with a cable. With three or more nodes there are only two ports per machine, so a full mesh of direct links is impossible and a switch becomes necessary. That setup is covered separately in connecting four DGX Sparks through a switch (Korean).

4. Recipes to run right away on one node: only what we verified ourselves

Two recipes you can follow verbatim on a single node once setup is done. Both were measured with the GPU cap above (1989MHz) in place.

4.1 ollama + Qwen3.8-27B (GGUF Q4_K_M): first token within 10 minutes

The fastest functional check. Install ollama with the official script, download Qwen3.8-27B-Q4_K_M.gguf (19GB) from ggml-org/Qwen3.8-27B-GGUF on Hugging Face, and register it with a Modelfile. One trap: in this environment ollama's default context came out as 262144 (check with ollama ps), so even a small model can take tens of GB of KV cache. Set num_ctx explicitly in the Modelfile.

curl -fsSL https://ollama.com/install.sh | sh
pip install -U huggingface_hub          # hf CLI
hf download ggml-org/Qwen3.8-27B-GGUF Qwen3.8-27B-Q4_K_M.gguf --local-dir ~/models/qwen3.8-27b
cat > Modelfile <<EOF
FROM /home/$USER/models/qwen3.8-27b/Qwen3.8-27B-Q4_K_M.gguf
PARAMETER num_ctx 32768
EOF
ollama create qwen3.8-27b-q4km -f Modelfile     # about 1 minute (blob copy)
ollama run qwen3.8-27b-q4km --verbose "Introduce yourself in one sentence"
MeasurementValueNotes
Load100% GPU, 20GBollama ps. First load 43 seconds (cold page cache), resident afterwards
Decode11.5-11.8 tok/sFor a 27B dense Q4 at 19GB, the ceiling from the GB10's memory bandwidth (273GB/s) is about 14 tok/s, so this is in the normal range
Prompt evaluation290 tok/sThe first request took 36 seconds as warm-up, 0.3 seconds from the second on
GPU during generation95%, 1989MHz, 25W, 57°CGPU cap applied

4.2 DeepSeek V4 Flash on one node: the MiaAI recipe (EXL3 3.0bpw, REAP K216)

The original FP8 (166.9GB) does not fit on one node. MiaAI-Lab/DeepSeek-v4-Flash-One-DGX-Spark serves 0xSero/deepseek-v4-flash-0731-spark with SparkInfer kernels and DSpark speculative decoding. This checkpoint is a 106.9GB build that keeps 216 of the 256 experts (REAP) and is quantized to EXL3 3.0bpw. So be aware that this is a pruned, 3bpw variant, not "the original DeepSeek V4 Flash".

# Prerequisites: docker + compose v2, nvidia runtime registered (nvidia-ctk runtime configure --runtime=docker), user in the docker group
git clone https://github.com/MiaAI-Lab/DeepSeek-v4-Flash-One-DGX-Spark mia-one && cd mia-one
./download.sh            # image pull → 107GB from HF → TP4→TP1 coalesce → checksum. ~200GB of disk, 30 minutes to several hours depending on your connection
systemctl stop earlyoom ollama   # required by the recipe (the server intentionally takes 94% of memory) and also stops the ollama instance from 4.1. Confirm available ≥114.3GiB in free -h
./start.sh --no-wait     # defaults: MAX_MODEL_LEN 384000, MAX_NUM_SEQS 1, KV_RECORD=stock432, DSpark speculative decoding
curl -s -o /dev/null -w '%{http_code}\n' localhost:8888/health   # 200 means ready. Took 552 seconds on this machine

This is where server mode paid off. Available memory at launch was 117.4GiB (the recipe README's host: 114.5), so the KV pool came out at 510,778 tokens. That is 16% more than the README's best of 439,622.

Figure 6. Prefill speed by prompt length (tok/s, no prefix cache). ~1,000 tok/s from 32k to 100k, 843 tok/s at 330k. Needle-in-a-haystack tests at the same lengths (finding a hidden passphrase) were all exactly correct at 64k, 200k, and 330k, with 0 preemptions.

MeasurementValueREADME claim
Launch (/health 200)552s-
Decode, single stream (thinking off)36-37 tok/s (512 tok ×3), 38-44 tok/s (acceptance-test code generation)44-47 (330k context, structured)
Prefill 32k / 100k1,091 / 1,024 tok/s~1,024
Needle 64k / 200k / 330kall correct320k/370k correct
Recipe's own acceptance test (acceptance_c1.py)all passgate ≥35 tok/s

Only decode came in at 80-95% of the README, but we had the GPU cap (1989MHz) on and the conditions differed from the README's (330k context, structured output). We suspect EXL3 trellis decode is compute-heavy enough to be sensitive to the clock, but we did not run an A/B with the cap lifted. The first structured-output (JSON schema) request took 28 seconds for 23 tokens, which is a one-time grammar compilation cost; it is normal from the second request on. If you have two or more nodes, there is a separate article (Korean) on running the original FP8 with TP=2.

5. One-screen checklist

  1. Initial setup as guided on screen → ip -br a, systemctl status sshssh-copy-id from the workstation
  2. Initial state check (0.1): PCI, network, USB, NVMe, thermal zones, firmware, kernel errors. Physically verify the 200G ports with cables plugged in
  3. timedatectl set-timezone, apt-get update
  4. apt-mark manual on the platform packages (meta Depends + installed nvidia-spark-* and the like)
  5. Base packages: ufw, unattended-upgrades, build-essential, python3-dev, jq, htop, and so on
  6. unattended-upgrades configuration (automatic reboot off), sshd key only, ufw default deny, docker group
  7. (Recommended) enable the two clock cap units (GPU 300,2000 / CPU performance cores 2.8GHz), Tailscale
  8. Driver 595 (after simulation), multi-user, purge/autoremove (gated), grub comparison, PD FW staged, security updates
  9. Move temporary files to home and reboot → verify: driver version, clocks, /proc/cmdline, PD firmware 0x516, 0 failed units, docker OK
  10. Confirm operation with one recipe (4.1 is the fastest)

Appendix. Why it shows 121GiB instead of 128GiB (units explained, measured)

All memory numbers in this article are binary GiB (230 bytes). That is the unit free -g and /proc/meminfo use, and the product's "128GB" is really 128GiB (137.4GB decimal). The memory address range DMI reports, 0x80000000-0x207FFFFFFF, is exactly 237 bytes = 128GiB, and it is whittled down from there in the order below (all values read on this machine).

StageSizeWhat
Physical LPDDR5X128.00GiBSingle pool shared by CPU and GPU (LPDDR5 8533MT/s, dmidecode)
Not in the memory map−0.37GiBDRAM that is simply absent from the memory map UEFI hands to the kernel (memblock 127.63GiB). Presumably used by firmware; its purpose is not documented
In the map but reserved−3.55GiBThe display/iGPU reservation lives here. NVIDIA's release notes (July 2026) say the "Display Reserved Memory" can be set in the BIOS to 2GB (default) or 4GB (release notes, forum announcement). On our ASUS firmware the two large regions are 2616MiB + 460MiB = 3076MiB, the boot framebuffer (BOOTFB) sits inside them, and that matches the hidden "iGPU Memory Carveout" value 24 from section 3 (times 128MiB = 3072MiB). The unit of that field is not documented, though, and its minimum is also 24, so it cannot be reduced. The remaining ~0.55GiB is UEFI runtime, ACPI, TPM and similar. System RAM left: 124.08GiB
Kernel's own fixed allocations−2.45GiBpage structs for 32,526,926 4KiB pages (64B each = 1.94GiB) + kernel image, initramfs, percpu, and so on, 0.5GiB. After this: MemTotal 121.63GiB, the 121 in free -g. CMA 128MiB is included in here
Idle system usage−2.6GiBServices, slab (including the GPU driver's UVM), excluding page cache. Server mode idle MemAvailable 119.0GiB. With a desktop running, a few more GiB go here

So "only 121GB of 128GB shows up" is normal, not a fault, and the number to plan a model against is MemAvailable rather than MemTotal. If you are loading a model where 100MiB matters, there is really one lever left: a 64KiB-page kernel (NVIDIA ships it as linux-image-nvidia-64k-hwe-24.04) shrinks the 1.94GiB of page structs to about one sixteenth and recovers roughly 1.8GiB; the Grace performance tuning guide also recommends 64K pages for large-memory workloads. But the DGX OS kernel that ships on Spark is the non-64K -nvidia kernel, and the 595 driver has a known defect (open-gpu-kernel-modules #1269) on paths that pin 4GiB or more in one mapping (such as disk KV offloading), so validate with your workload first and keep a 4K boot entry. The display reservation cannot be lowered even where the BIOS exposes it (the default, 2GB or the value 24 on our firmware, is the minimum), CMA (128MiB) is already used as movable pages so shrinking it gains nothing (see cma= and crashkernel= in the kernel parameters documentation), and crashkernel is already 0.

Recorded 2026-08-21 to 2026-08-22. Hardware: ASUS Ascent GX10 (NVIDIA GB10, 128GB). Other GB10 products may differ in firmware and image. Firmware GX10DGX.0105, Ubuntu 24.04.4, kernel 6.17.0-1031-nvidia, driver 595.84. The clock cap sweep and spin experiment numbers were measured on the two existing machines of the same model (see the linked articles). Environment-dependent items such as network configuration, addresses, and hostnames are omitted.