From c9a7a002b97348669d82b6efc99acae5cf62391c Mon Sep 17 00:00:00 2001 From: Stephen Jia Date: Tue, 14 Jul 2026 14:08:46 -0700 Subject: [PATCH] [ET-VK][fix] Widen WAR barrier to a full memory dependency for reused tensors `vTensorStorage::transition()` emitted the write-after-read (WAR) barrier with a read-only source access mask, which Vulkan treats as an execution-only dependency (no memory dependency). Some Adreno drivers drop this narrow `COMPUTE -> COMPUTE` dependency, letting the current write race the previous read. This surfaced as deterministic output corruption at the tile seams of the tiled im2col+GEMM conv2d path (the reused im2col scratch `TmpTensor`): the next tile's im2col overwrote the trailing rows of the scratch while the current tile's GEMM was still reading them. The Vulkan op impl was unchanged and passed previously only because other leased CI devices happened to serialize the tiles; a device/driver whose scheduling did not exposed the latent hazard. When the previous access was a genuine read-only (`prev_read && !prev_written && cur_written`), widen the barrier to a full `ALL_COMMANDS` execution + memory dependency so the driver reliably honors it. Scoped to the rare WAR transition, so the common first-write and RAW/WAW paths are untouched; widening a barrier is always correctness-safe. Fixes WAR hazards generically for all storage types, not just this conv. Differential Revision: [D111994459](https://our.internmc.facebook.com/intern/diff/D111994459/) ghstack-source-id: 402958244 Pull-Request: https://github.com/pytorch/executorch/pull/20915 --- backends/vulkan/runtime/api/containers/Tensor.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/backends/vulkan/runtime/api/containers/Tensor.cpp b/backends/vulkan/runtime/api/containers/Tensor.cpp index 15e2660078a..f63a9fc789b 100644 --- a/backends/vulkan/runtime/api/containers/Tensor.cpp +++ b/backends/vulkan/runtime/api/containers/Tensor.cpp @@ -810,11 +810,22 @@ void vTensorStorage::transition( dst_stage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; } + VkAccessFlags dst_access = vkapi::vk_access(cur_stage, cur_access); + + // WAR hazard: the read-only source access mask yields an execution-only + // dependency that some drivers may drop, so widen it to a full memory + // barrier to keep the write from racing the prior read. + const bool prev_read = (prev_access & vkapi::MemoryAccessType::READ) != 0; + if (prev_read && !prev_written && cur_written) { + src_stage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + dst_stage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + src_access = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + dst_access = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + } + pipeline_barrier.stage.src |= src_stage; pipeline_barrier.stage.dst |= dst_stage; - VkAccessFlags dst_access = vkapi::vk_access(cur_stage, cur_access); - if (image_) { pipeline_barrier.images.emplace_back( src_access, dst_access, cur_layout, new_layout, image_);