AI & Compute

Understanding RAM, GTT, and GPU Memory on AMD Strix Halo

Why tools report different memory sizes on Strix Halo — unified memory architecture, GTT aperture vs usable RAM, what the kernel, ROCm, Vulkan and llama.cpp each report, with measured numbers.

On Strix Halo the CPU and iGPU share one physical memory pool (unified memory). That is great for fitting big models — and confusing, because every tool in the stack reports a different size for “GPU memory”. This page maps each number to what it actually is, using real captures from the tested machine: a Corsair AI Workstation 300 with Ryzen AI Max+ 395, Radeon 8060S and 128 GiB of memory.

The unified-memory architecture, as observed

Ryzen AI Max+ has no discrete GPU memory. The Radeon 8060S reads and writes the same LPDDR5 channels as the CPU (vendor specs market this as LPDDR5X; dmidecode on this machine reports Type LPDDR5), through a GTT (Graphics Translation Table) that maps an aperture into system RAM. From this machine’s capture:

  • Memory is one pool: free -h reports 124 GiB total (the modules are 8 × 16 GB — 16 GiB each, which dmidecode prints as “16 GB”; the sysfs GTT total of exactly 137,438,953,472 bytes = 128 GiB confirms the binary reading). Type is reported LPDDR5, 8000 MT/s configured, 8532 MT/s measured.
  • Inference competes with the OS for the same memory controller — one pool, shared bandwidth. There is no “GPU memory” to be starved separately.
  • The BIOS “dedicated” iGPU memory setting on this machine is 1 GiB — the minimum the Corsair AI Workstation 300’s BIOS offers. It maps 1:1 to what dmesg reports: the VRAM: 1024M visible aperture is that dedicated slice.
  • The large GTT is a separate mechanism from that split: it is sized by the kernel command line (amdgpu.gttsize=131072 / ttm.pages_limit, see above), not by BIOS. Minimal dedicated slice + a full-pool GTT is what makes the whole box behave as one unified pool for GPU workloads.

What the kernel says at boot

Trimmed from dmesg on the tested machine — this is the ground truth for the aperture layout:

[    4.023700] amdgpu 0000:c5:00.0: amdgpu: VRAM: 1024M 0x0000008000000000 - 0x000000803FFFFFFF (1024M used)
[    4.023804] amdgpu 0000:c5:00.0: amdgpu: amdgpu: 1024M of VRAM memory ready
[    4.023805] amdgpu 0000:c5:00.0: amdgpu: [drm] Configuring gttsize via module parameter is deprecated, please use ttm.pages_limit
[    4.023806] amdgpu 0000:c5:00.0: amdgpu: amdgpu: 131072M of GTT memory ready.

Two things to notice:

  • The “VRAM” aperture is only 1 GiB. On a discrete GPU that line would be the card’s onboard memory; here it is a small visible window. The number that matters is the next one: 131072 MiB (128 GiB) of GTT memory ready — the full physical pool, addressable by the GPU.
  • The kernel deprecates amdgpu.gttsize. This machine’s command line passes both amdgpu.gttsize=131072 and ttm.pages_limit=33554432 (× 4 KiB pages = the same 128 GiB), plus ttm.page_pool_size=33554432 and iommu=pt. Newer setups should rely on ttm.pages_limit; the value is already in /proc/cmdline if you want to audit it.

What each tool reports (measured)

Tool / sourceShows (measured on this machine)What it actually is
free -h124 GiB totalphysical pool available to everything
dmesg (amdgpu)1024 MiB “VRAM”, 131072 MiB GTTvisible aperture / full GTT, set at boot
/sys/class/drm/card0/device/mem_info_*gtt_total = 137,438,953,472 B (128 GiB), gtt_used ≈ 34 GiB*; vram_total = 1 GiB, vram_used ≈ 155 MiB*kernel’s live accounting of both apertures
ROCm/HIP — llama-cli --list-devicesROCm0: AMD Radeon 8060S Graphics (131072 MiB, 83680 MiB free)the GTT aperture as seen by the runtime; “free” varies with what is loaded
rocminfo (7.14)gfx1151 agent listed, but no Memory (MB) line in this captureversion quirk — use --list-devices for the number
vulkaninfo memoryHeaps (RADV)two heaps: 43.00 GiB + 86.00 GiB, with live budget (94.91 GiB total at capture time) and usage fieldsthe driver’s view of the same pool, split into two heaps and reported dynamically (below)
vulkaninfo memoryHeaps (llvmpipe)one heap, size = budget = 124.45 GiB, usage 45.73 GiB at capture timethe whole physical pool as seen by the software rasterizer

*used/budget/usage figures are point-in-time (captures ran around benchmark sessions); expect them to move with system load.

Vulkan’s view: two heaps and a live budget

Where the kernel, sysfs and ROCm all report one static number (128 GiB GTT), RADV reports two heaps plus dynamic accounting fields:

memoryHeaps: count = 2
	memoryHeaps[0]:
		size   = 46170898432 (0xac0000000) (43.00 GiB)
		budget = 33967968256 (0x7e8a61000) (31.64 GiB)
		usage  = 0 (0x00000000) (0.00 B)
		flags:
			None
	memoryHeaps[1]:
		size   = 92341796864 (0x1580000000) (86.00 GiB)
		budget = 67935936512 (0xfd14c2000) (63.27 GiB)
		usage  = 0 (0x00000000) (0.00 B)
		flags: count = 1
			MEMORY_HEAP_DEVICE_LOCAL_BIT
  • size is the heap’s total; budget is what the driver/OS currently says is available to the application; usage is what this device has taken. Re-run vulkaninfo under load and the budget numbers move — unlike the kernel/sysfs values, these are not static hardware facts.
  • The two heap sizes (43 + 86 = 129 GiB) reconcile exactly with **128 GiB GTT
    • the 1 GiB visible VRAM aperture** from dmesg — same memory, re-sliced by the driver.
  • Heap 0 carries no flags; heap 1 carries MEMORY_HEAP_DEVICE_LOCAL_BIT — i.e. the larger 86 GiB heap is the one RADV marks as “device local” (the smaller 43 GiB heap is shared). On a unified-memory APU both are still system RAM; the bit only steers allocation preference.
  • llvmpipe, for comparison, exposes the whole pool as a single 124.45 GiB heap and reports live system usage in it — a reminder that “usage” on an iGPU is just system RAM usage wearing a GPU costume.
  • Running vulkaninfo over SSH (no display) also prints loader noise that the block above omits: on this machine an ERROR about a non-functional NVIDIA ICD (libGLX_nvidia.so.0, from a stray libnvidia-gl-595 package — there is no NVIDIA GPU in the box), a skipped libvulkan_dzn.so, and 'DISPLAY' not set... skipping surface info. None of it affects RADV’s output.

Reading the full table: kernel boot message, sysfs and the ROCm runtime all agree on 128 GiB static, Vulkan re-slices it into dynamic heaps that sum to the same memory, and the kernel’s small 1 GiB “VRAM” window never shows up in any user-facing tool. No single number is “the GPU memory” — but on this machine every one resolves to the same physical pool.

Practical allocation limits

  • Reserve headroom for OS + desktop + services; a 128 GiB box is not a 128 GiB model box. On this machine, a 29.1 GB model fully offloaded still left ~49 GiB free per free -h.
  • Weights + KV cache + activations must fit inside the remainder with margin: model file size + ctx × per-token-KV + peak activation < usable RAM.
  • Watch the system, not the GPU: OOM on Strix Halo is a system-level event — the desktop freezes before anything prints an error.
  • Swap is your canary: any swap activity during generation means you are over the practical limit, regardless of what the runtime reported as free. This machine carries an 8 GiB swap file, which will absorb small overruns at a huge speed cost rather than hard-failing.

Examples from measured runs

  • Qwen3.8-27B Q8_0 (29.1 GB file), fully offloaded (-ngl 999, ctx from the benchmark matrix): ran without issue on ROCm and Vulkan — full numbers on the ROCm vs Vulkan page. Observed amdgpu GTT usage after model load: 34,724 MiB / 131,054 on ROCm, 35,544 MiB / 131,054 on Vulkan — a few GiB above the weight file (context + activation working set).
  • Qwen3.6-35B-A3B-MTP Q8_0 (37.8 GB file), fully offloaded, server idle (observed amdgpu GTT usage after model load): 37,615 MiB / 131,054 MiB on ROCm, 37,382 MiB / 131,054 MiB on Vulkan — same weights in the same pool, no material difference between backends; ~90 GiB of headroom left over.
  • Over-limit behavior: not measured on this machine. The largest tested load used ~37.5 GiB of a ~128 GiB pool and no memory pressure was observed in normal use, so no deliberately over-sized run was done. If you do hit the limit, expect a system-level OOM — the desktop freezes before anything prints an error, and this machine’s 8 GiB swap file absorbs small overruns at a huge speed cost rather than hard-failing.

Sources and upstream documentation