Enhance performance for GpuToCpu for intel integrated GPU - #311
Conversation
I noticed serious slowdowns in copying from Intel integrated GPU -> CPU on systems that have both a intel CPU and GPU.
|
Odd, you wouldn't expect a driver to even hand out write combined memory when allocating read back memory. Might be worth also filing an ANV bug around this since it makes sense to investigate this together with them. This also changes the contract we have a bit; which is that before folks didn't have to flush memory ranges at all since all memory we handed out was coherent. |
|
Can you ask fable to double check this, and to add asserts on the WC flag on your machine? If it triggers I would strongly suggest taking this upstream as we won't be the only consumer suffering from this performance problem. Edit: let me read the claude report for a sec. |
|
@manon-traverse @MarijnS95 I think we should discuss what to do here: the bug report seems valid and correct (somehow we're handing out WC memory for readback). However: switching this has some implications, primarily around the contract we have for "we never need to explicitly flush" and potentially also around our persistently mapped buffers. The way I see it there are 3 or 4 options.
|
|
Filed also an issue on ANV to see if we can get to the bottom of this a bit more https://gitlab.freedesktop.org/mesa/mesa/-/work_items/16136 |
This comment was marked as outdated.
This comment was marked as outdated.
|
Pretty easy to throw AI at problems when you have a 270k processor.... Does this patch make sense to you? Detailscommit 8f4c9439463efb62529a3e17bf1a0f10d8adfba3 (HEAD)
Author: Mark Harfouche <mark.harfouche@gmail.com>
Date: Thu Aug 20 08:00:41 2026 -0400
anv/i915: expose a cached+coherent memory type where the PAT supports it
anv_i915_physical_device_init_memory_types() picks its integrated-GPU
memory types based on devinfo->has_llc. Everything from Xe-HP on sets
has_llc = false (XEHP_FEATURES), so every integrated part since Meteor
Lake falls into the branch written for Atom parts and gets exactly two
host-visible types: coherent-but-write-combining, and cached-but-not-
coherent. No type advertises HOST_COHERENT | HOST_CACHED together.
Applications that ask for cached readback memory the usual way -- prefer
HOST_VISIBLE | HOST_COHERENT | HOST_CACHED, fall back to HOST_VISIBLE |
HOST_COHERENT -- therefore never match the preferred set and land on the
write-combining type, where CPU reads are roughly two orders of magnitude
slower. Measured on Arrow Lake-S (ARL, i915), 32 MiB GPU->CPU readback,
median of 25, including any invalidate the type requires:
type 0 HOST_VISIBLE | HOST_COHERENT 186.70 ms 0.18 GB/s
type 1 HOST_VISIBLE | HOST_CACHED 1.77 ms 18.98 GB/s
type 2 HOST_VISIBLE | HOST_COHERENT | HOST_CACHED 1.37 ms 24.42 GB/s (new)
The new type is not just 135x the write-combining one, it also beats the
cached-but-incoherent type, because it needs no invalidate at all.
This is not a missing hardware capability. These platforms have a PAT
entry that is write-back on the CPU side and coherent on the GPU side
(MTL/ARL: PAT 3, Xe2: PAT 1), anv_device_get_pat_entry() already maps
ANV_BO_ALLOC_HOST_CACHED_COHERENT onto pat.cached_coherent, and the i915
kmd backend already programs it via I915_GEM_CREATE_EXT_SET_PAT. The
combination was simply never advertised, so it was unreachable. The xe
kmd backend already exposes this type in the equivalent branch.
Gate on the platform actually having that PAT entry rather than on
has_llc, so the real Atom parts -- which have no set_pat uapi -- keep
today's two types.
Verified on ARL/i915 with no vkFlushMappedMemoryRanges or
vkInvalidateMappedMemoryRanges issued for the new type, in both
directions, with the CPU's cache lines for the destination deliberately
dirtied before the GPU overwrote them: 20 x 8 MiB round trips through
device-local memory compared byte for byte, plus 25 x 32 MiB GPU->CPU
readbacks, no stale bytes.
mark@antpitta $ git diff
diff --git a/src/intel/vulkan/i915/anv_device.c b/src/intel/vulkan/i915/anv_device.c
index ecbe7663..93c35b73 100644
--- a/src/intel/vulkan/i915/anv_device.c
+++ b/src/intel/vulkan/i915/anv_device.c
@@ -221,6 +221,32 @@ anv_i915_physical_device_init_memory_types(struct anv_physical_device *device)
VK_MEMORY_PROPERTY_HOST_CACHED_BIT,
.heapIndex = 0,
};
+
+ /* Platforms from Xe-HP on report has_llc == false, so every integrated
+ * part since Meteor Lake lands here even though it is not an Atom and
+ * does have a PAT entry that is write-back on the CPU side and coherent
+ * on the GPU side (MTL/ARL: PAT 3, Xe2: PAT 1).
+ *
+ * anv_device_get_pat_entry() already maps ANV_BO_ALLOC_HOST_CACHED_COHERENT
+ * onto that entry, and i915_gem_create_uncached()/_ext() already program
+ * it, but no memory type advertised both bits, so the combination was
+ * unreachable. Applications asking for cached readback memory had to
+ * settle for the write-combining type, where CPU reads are roughly two
+ * orders of magnitude slower.
+ *
+ * Expose the cached+coherent type where the PAT can back it. Platforms
+ * without the set_pat uapi (the actual Atoms) keep the two types above.
+ */
+ if (device->info.has_set_pat_uapi &&
+ device->info.pat.cached_coherent.mmap == INTEL_DEVICE_INFO_MMAP_MODE_WB) {
+ device->memory.types[device->memory.type_count++] = (struct anv_memory_type) {
+ .propertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
+ VK_MEMORY_PROPERTY_HOST_CACHED_BIT,
+ .heapIndex = 0,
+ };
+ }
}
return VK_SUCCESS;
|
|
Note This comment was written by Claude, an AI agent, acting on @hmaarrfk's behalf and posted from his account. He asked it to reproduce the numbers in this PR, dig into @Jasper-Bekkers' questions, and report back. Everything below is collapsed so you can skip it — the short version is: the root cause is in ANV, not in gpu-allocator, and Mark is happy to drop this PR's patch in favour of a Mesa fix. All claims below were measured on his machine; the caveats are listed at the end. Mark reviewed the main analysis before it was posted; the addendum at the bottom was added afterwards at his request and he has not read it yet. Treat all of it as machine-generated and check anything load-bearing. Full analysis: ANV never advertises COHERENT|CACHED on modern Intel iGPUs — 26-line Mesa patch, measurements, and answers to the questions raised aboveTest system: Arrow Lake-S ( 1. The assert you asked for fires@Jasper-Bekkers asked for an assert on the WC flag. Unmodified gpu-allocator 0.28.0, So yes — readback memory is genuinely not One correction to the PR text: the "type 1/4" framing is misleading. Types 3/4 are ANV's 2. gpu-allocator did not regress — wgpu changed allocatorsThis is worth correcting because it changes who owns the bug. The selection logic in What changed is in
// Prefer cached memory for downloads.
let host_cached = flags.contains(Flags::HOST_CACHED) ^ usage.contains(UsageFlags::DOWNLOAD);
// Prefer coherent for both uploads and downloads.
let host_coherent = flags.contains(Flags::HOST_COHERENT) ^ usage.intersects(UPLOAD | DOWNLOAD);
device_local as u32 * 8 + host_visible as u32 * 4 + host_cached as u32 * 2 + host_coherent as u32
Relevant to the contract discussion: this means wgpu was already being handed non-coherent memory on this hardware before v28. The "callers never need to flush" invariant was not actually holding there either; it was just invisible because nothing checked. 3. It is not about dual-GPU systems@hmaarrfk speculated above that dual-GPU was the trigger and offered to physically remove a card. That is not necessary — there is no NVIDIA Vulkan ICD installed on this machine at all ( 4. Root cause: ANV has the capability and never advertises it
/* The spec requires that we expose a host-visible, coherent memory
* type, but Atom GPUs don't share LLC. ...
*/That Atom branch is now the only branch modern integrated hardware can reach, and the The hardware supports the combination, and so does the rest of ANV:
Only the enumeration was missing. The capability was implemented and unreachable. 5. The fix, and what it measures26 lines, gated on the platform genuinely having the PAT entry so real Atom parts are untouched ( if (device->info.has_set_pat_uapi &&
device->info.pat.cached_coherent.mmap == INTEL_DEVICE_INFO_MMAP_MODE_WB) {
device->memory.types[device->memory.type_count++] = (struct anv_memory_type) {
.propertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT,
.heapIndex = 0,
};
}32 MiB GPU→CPU readback, median of 25, including any invalidate the type requires:
The new type is 135× the write-combining type, and still ~29% faster than the cached-but-incoherent type, because it needs no invalidate at all. Coherency was tested adversarially rather than assumed. For any type claiming 6. What this means for this PRWith the Mesa fix, gpu-allocator needs no change at all:
Row 2 is the one that matters. The existing preferred mask matches the new type on the first try, so:
That is @Jasper-Bekkers' option 1, and it turned out to be a small driver patch rather than a negotiation. This PR's patch (option 2) does work, and is a safe no-op once a proper type exists — row 4 shows the new fallback simply never fires, because the preferred pass succeeds first. But it is no longer needed, and @hmaarrfk is happy to close this PR in favour of the Mesa fix. If you would still like a defensive change in gpu-allocator for drivers that genuinely cannot offer both bits, the honest version is option 3 (explicit opt-in to incoherent memory), not a silent fallback — but there is no longer a known driver that needs it. 7. Caveats
|
| platform | pat.cached_coherent |
has_llc |
|---|---|---|
| TGL / ADL / RPL | PAT 0 → WB, 2WAY | true |
| MTL / ARL | PAT 3 → WB, 1WAY | false |
| Xe2 (LNL / BMG) | PAT 1 → WB, 1WAY | false |
The type this patch exposes is backed by a 1-way coherent entry, not the 2-way entry the older LLC parts use. That is not a shortfall — the kernel uAPI sets 1-way as exactly the required threshold:
For coherency the
@pat_indexneeds to be at least 1way coherent whendrm_xe_gem_create.cpu_cachingisDRM_XE_GEM_CPU_CACHING_WB. The KMD will extract the coherency mode from the@pat_indexand reject if there is a mismatch.
and it requires 1WAY-or-2WAY for userptr and imported dma-buf. So the MTL/ARL entry sits at the sanctioned bar and the kernel enforces it, which matches the empirical result reported above (CPU cache lines deliberately dirtied, GPU overwrite, no invalidate, zero stale bytes over ~800 MiB).
Honest gap: none of the sources reachable from this machine spell out what "1-way" means directionally — which agent snoops which. That is in Intel's BSpec 45101 / 71582, which is not publicly accessible, and gitlab.freedesktop.org is behind Anubis so work item 16136 could not be read either. The kernel treating 1-way as sufficient, plus the adversarial test passing, is strong evidence — but it is inference, not a quote from the spec. If anyone at Intel can confirm the directional semantics on the Mesa side, that would close it properly.
The framing that matters
Two properties that were equivalent through Raptor Lake decoupled at Meteor Lake:
- (a) the GPU shares the CPU's last-level cache → now false, correctly
- (b) memory can be simultaneously CPU-cached and GPU-coherent → still true, via PAT
ANV uses (a) as a proxy for (b). The patch stops proxying and tests (b) directly — hence the gate on has_set_pat_uapi && pat.cached_coherent.mmap == INTEL_DEVICE_INFO_MMAP_MODE_WB rather than on has_llc.
<details><summary>Claude's draft</summary> This is better handled upstream, so drop the vendored gpu-allocator source and its patch and build straight against the crates.io release again. xref Traverse-Research/gpu-allocator#311 Build number bumped to 2. Resume this Claude session: ``` cd /home/mark/git/feedstock/wgpu-native-feedstock claude --resume 480d6866-d840-428f-859a-cae899ee4b99 ``` </details>
|
I think it makes some sense - but do engage with the mesa developers directly and also dig into why they seemingly disabled it over bandwidth concerns. Your benchmark seems to indicate that it's actually /faster/ so it would be interesting to see what measurements the mesa developments did and how they differ from yours. |
I noticed serious slowdowns in copying from Intel integrated GPU -> CPU on systems that have both a intel CPU and GPU.
I'm using this
python -> wgpu-native -> wgpu-rs -> gpu-allocator
they seem to have moved to your allocator, and I am now seeing major slowdowns in copying data from the GPU to the CPU:
This little patch seems to resolve my problems.
An other solution was to use Non-temporal copy, but that is really niche and seems like a workaround.
I'm very rusty on memory profiling, so I had to entrust my claude..... but I do think there is a small reproducer in here (you will be better at reading this than me).
I do read enough code that we are inteligently trying to select memory of the right type. so I trust that this PR will be useful.
claude teaching me things every day, read at your own risk
Ready-to-paste GitHub issue
Repository: https://github.com/gfx-rs/wgpu
Title:
Vulkan/ANV: MAP_READ buffers land on write-combined memory since v28 (gpu-allocator switch) - mapped readback ~87x slower on Intel iGPUsSummary
Since wgpu v28.0.0, reading back a mapped
MAP_READbuffer on Intel integratedGPUs (Mesa ANV) is about 87x slower than it was on v27 - 0.29 GB/s instead of
25 GB/s, i.e. slower than the PCIe readback it is supposed to avoid.
The
MAP_READallocation now lands on a write-combined (HOST_COHERENT,uncached) memory type instead of the
HOST_CACHEDtype that ANV also exposes.On write-combined memory CPU reads are uncached, so a plain
memcpyout of themapped range runs at ~0.28 GB/s.
This is a regression from the Vulkan backend's switch from
gpu-alloctogpu-allocator(#8158). Discrete NVIDIA cards are unaffected, because they expose amemory type that is simultaneously
HOST_VISIBLE | HOST_COHERENT | HOST_CACHED, sogpu-allocator's preferred flag mask matches on the first try.Bisected to a version boundary: v27 fast, v28.0.0 slow, v29.0.4 slow - same
machine, same driver, same source file.
Environment
Intel(R) Graphics (ARL),IntegratedGpuIntel open-source Mesa driver, Mesa 26.0.3-1ubuntu1 (ANV)The same regression has been observed on nine machines: every Intel Vulkan adapter
on wgpu >= 28 is slow, every NVIDIA adapter is fast, and Intel adapters on wgpu 27
are fast.
Measurements
8 MiB buffer, median of 9
copy_from_slicecalls out of the mapped range, withthe destination pre-warmed so first-touch page faults are not timed. The same-sized
host-to-host
memcpyin the second column is the RAM baseline measured in the sameprocess, so the ratio is self-normalising.
Three consecutive runs of each version varied by under 1%.
Root cause
ANV on parts without a CPU-shared LLC exposes no memory type that is both
HOST_COHERENTandHOST_CACHED:Measuring a CPU
memcpyout of each host-visible type directly, with plainashand no wgpu involved, shows exactly what landing on the wrong one costs
(8 MiB, median of 9):
The ~90x gap in the wgpu numbers is exactly the gap between memory type 0 and
memory type 1. As a control,
llvmpipeon the same machine exposes a singleHOST_VISIBLE | HOST_COHERENT | HOST_CACHEDtype, the preferred mask matches, andthere is no regression.
Why v28 changed behaviour
wgpu-hal29.0.4src/vulkan/device.rs:897maps a read-mappable buffer togpu_allocator::MemoryLocation::GpuToCpu:gpu-allocator0.28.0src/vulkan/mod.rs:810then tries two hard flag masks,and both of them require
HOST_COHERENT:There is no third attempt, so
HOST_CACHED-but-not-coherent memory is unreachablefor a
MAP_READbuffer.gpu-alloc, used up to v27, treated these as a priority ordering rather than arequirement.
wgpu-hal27.0.1src/vulkan/device.rs:1113setgpu_alloc::UsageFlags::DOWNLOAD("Allocator will strongly prefer host-cachedmemory"), and
gpu-alloc0.6.2src/usage.rs:164sorts candidates bya preference, not a filter - so
HOST_CACHEDwon and coherency was allowed to lose.Suggested fix
For
GpuToCpu, preferHOST_VISIBLE | HOST_CACHEDoverHOST_VISIBLE | HOST_COHERENTwhen the fully-preferred mask does not match. Readingback is the entire point of the allocation, and cached-but-non-coherent serves that
far better than coherent-but-uncached.
This looks safe on the wgpu side: the Vulkan backend already supports non-coherent
mappings.
wgpu-hal29.0.4src/vulkan/device.rs:993recordsand
flush_mapped_ranges/invalidate_mapped_rangesalready issuevkFlushMappedMemoryRanges/vkInvalidateMappedMemoryRanges. So the only thingbetween ANV and the fast memory type is
gpu-allocator's hardHOST_COHERENTrequirement in the fallback mask.
Two places it could be fixed:
gpu-allocator: add a third attempt (HOST_VISIBLE | HOST_CACHED) forGpuToCpu, or make the selection a scored preference rather than a subset test.wgpu-hal: bypassMemoryLocationforMAP_READbuffers and select thememory type explicitly, picking a
HOST_CACHEDtype when no coherent+cached typeexists.
Reproducer
Two files, no shaders, no windowing.
cargo run --releasefinishes in well under aminute.
Cargo.toml:build.rs(present only so the output states which wgpu it was built against):src/main.rs:Set
wgpu = "27"inCargo.tomlfor the good version. wgpu 27 and 28 tookInstanceDescriptorby reference and still derivedDefault, so those two buildsneed one cosmetic hunk, unrelated to the regression:
Output
wgpu 29.0.4,
cargo run --release --features alloc-report:wgpu 27.0.1, same machine, same driver:
The process exits non-zero when the mapped read is more than 5x slower than RAM.
Device::generate_allocator_report, added by the same PR, confirms the buffer is asingle ~8 MiB suballocation, but
AllocatorReportcarries noVkMemoryTypeindex,so it cannot show which memory type was chosen - which is why the
ashdump aboveis included. Exposing the memory type index on
AllocationReportwould make thisclass of problem diagnosable from wgpu alone.
Memory-type dumper
The
ash-only helper that produced the two dumps above (memtypes/,ash = "0.38"):xref: #260