vulkan: route download through staging when blob memory is not HOST_CACHED (13x, fixes #6857)#6864
Conversation
…ACHED
VkCompute::record_download() skips the staging buffer whenever the blob
allocator is mappable. On an integrated GPU the blob allocator prefers
DEVICE_LOCAL|HOST_VISIBLE "unified" memory, which is mappable but
WRITE-COMBINED rather than HOST_CACHED, so the result is memcpy'd out of
uncached memory.
Uploads are unaffected -- writing to write-combined memory is fast. Only
the device-to-host read collapses, giving a large upload/download
asymmetry on the very same buffers.
Measured on a Radeon 8060S (Strix Halo, RADV GFX1151, 256 GB/s unified
LPDDR5X), 128 MB transfer, median of 3 runs:
host memcpy (reference) 23.0 GB/s
ncnn upload 10.0 GB/s
ncnn download before 0.32 GB/s
ncnn download after 4.19 GB/s 13.1x
The staging allocator is the one that asks for HOST_CACHED, and it was
bypassed in exactly the case where it is needed.
Add VulkanDevice::is_cached() mirroring is_coherent(), track it as a
`cached` flag on VkAllocator alongside `mappable` / `coherent`, and
extend the record_download() test to require both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 98b0f74b0d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // gpu cast to fp32 on the fly (integrated gpu) | ||
| Option opt_staging = opt; | ||
| if (!opt_staging.blob_vkallocator->mappable) | ||
| if (!opt_staging.blob_vkallocator->mappable || !opt_staging.blob_vkallocator->cached) |
There was a problem hiding this comment.
Track cached state separately for buffer memory
When a VkBlobAllocator serves both VkMat and VkImageMat and their selected memory types differ, fastMalloc(VkImageMemory) overwrites the same allocator-wide cached flag previously set by fastMalloc(VkBufferMemory). This condition therefore tests the most recently initialized memory kind rather than the buffer memory used for the download; for example, a cached image type can cause an uncached buffer download to bypass staging and retain the slow CPU read this change is intended to prevent. Base this decision on buffer_memory_type_index or maintain separate buffer/image cached flags.
Useful? React with 👍 / 👎.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #6864 +/- ##
========================================
Coverage 95.10% 95.11%
========================================
Files 947 947
Lines 417208 417309 +101
========================================
+ Hits 396796 396922 +126
+ Misses 20412 20387 -25 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Fixes #6857. Opening as requested in #6857 (comment) — using the
is_cached()onVkAllocatorapproach you suggested.Problem
VkCompute::record_download()skips the staging buffer whenever the blob allocator is mappable:On an integrated GPU the blob allocator prefers
DEVICE_LOCAL | HOST_VISIBLEunified memory. That memory is mappable — but it is write-combined, notHOST_CACHED. So the result getsmemcpy'd out of uncached memory.Uploads are unaffected (writing to write-combined memory is fast). Only the device-to-host read collapses, which is why the same buffers show a large upload/download asymmetry.
The staging allocator is precisely the one that requests
HOST_CACHED, and it was bypassed in exactly the case where it is needed.Change
28 lines across 5 files:
VulkanDevice::is_cached()— mirrorsis_coherent()VkAllocator::cached— tracked alongsidemappable/coherent, set at every site those two are setrecord_download()— requires mappable and cached to take the direct pathNo behaviour change on discrete GPUs (blob memory is not mappable, so the staging path was already taken) or on hardware whose mappable blob memory is
HOST_CACHED(condition still passes).Measurements
Radeon 8060S (Strix Halo, RADV GFX1151, 256 GB/s unified LPDDR5X), 128 MB transfer.
A/B built from the same tree, the only difference being the
record_download()condition — 3 runs each:0.32 → 4.19 GB/s, 13.1x. For reference on the same machine: plain host
memcpy23.0 GB/s, ncnn upload 10.0 GB/s. The 31x upload/download asymmetry reported in the issue becomes ~2.4x.Tests
ctest(178 tests,NCNN_VULKAN=ON), run on two clean worktrees — pristinea4d2ea1anda4d2ea1+ this commit:a4d2ea1Identical failure sets. The single failure is
test_binaryop_3→test_binaryop_atan2_special op_type=10, byte-identical output on both builds — a pre-existing atan2 special-value mismatch on RADV, unrelated to this change.One question
VkCompute::record_clone(const VkMat& src, Mat& dst, const Option& opt)(command.cpp:649) has the same shape of test:and then defers a
TYPE_post_downloadmemcpy out of the mapped buffer — so on write-combined memory it should hit the identical uncached-read cost. I have not touched it here, since you asked specifically about therecord_download()path and I have no benchmark isolatingrecord_clone. Happy to add it to this PR or send it separately, whichever you prefer.🤖 Generated with Claude Code