Skip to content

Commit 7559ddd

Browse files
SS-JIAfacebook-github-bot
authored andcommitted
Fix tensor views with tensors that use deferred memory allocation (pytorch#5831)
Summary: Pull Request resolved: pytorch#5831 ## Context Fix one (hopefully) final kink with tensor views. ### The issue When creating a tensor view of a tensor that uses deferred memory allocation, at the time the view is created, it will copy the handle of the original tensor's `VkImage`. However, since the `VkImage` is not yet bounded to memory, it will have have a `VkImageView` associated with it, and the tensor view will copy the `VkImageView` handle and `VK_NULL_HANDLE`. Later, the original tensor is expected to be bound to an allocation, at which point it will create an image view and bind the `VkImage` to memory. However, this does not update its views, which will still have a null `VkImageView`. If the tensor view is also bound to the allocation, then the same `VkImage` will be bound to the same memory multiple times which is against the Vulkan spec. ### The fix Allow binding of `VulkanImage` and `VulkanBuffer` copies to only call the binding function if it is not a copy; assume that the original tensor is responsible for binding to memory. In `ComputeGraph`, when adding a tensor view, add the created `Value` as a user of any `SharedObject` of which the original tensor is also a user. ghstack-source-id: 246029590 exported-using-ghexport Reviewed By: jorgep31415 Differential Revision: D63790718 fbshipit-source-id: 1a93851ce77c3c685986dbc8a6300d9905ff60d2
1 parent e2e2129 commit 7559ddd

File tree

7 files changed

+43
-11
lines changed

7 files changed

+43
-11
lines changed

backends/vulkan/runtime/graph/ComputeGraph.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,11 @@ ValueRef ComputeGraph::add_tensor_view(const ValueRef vref) {
300300
const vTensorPtr t = get_tensor(vref);
301301
ValueRef idx(static_cast<int>(values_.size()));
302302
values_.emplace_back(api::vTensor(*t));
303+
for (SharedObject& sobj : shared_objects_) {
304+
if (sobj.has_user(vref)) {
305+
sobj.add_user(this, idx);
306+
}
307+
}
303308
return idx;
304309
}
305310

@@ -311,6 +316,11 @@ ValueRef ComputeGraph::add_tensor_view(
311316
const vTensorPtr t = get_tensor(vref);
312317
ValueRef idx(static_cast<int>(values_.size()));
313318
values_.emplace_back(api::vTensor(*t, sizes, strides, offset_numel));
319+
for (SharedObject& sobj : shared_objects_) {
320+
if (sobj.has_user(vref)) {
321+
sobj.add_user(this, idx);
322+
}
323+
}
314324
return idx;
315325
}
316326

backends/vulkan/runtime/graph/containers/SharedObject.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212

1313
namespace vkcompute {
1414

15+
bool SharedObject::has_user(const ValueRef idx) const {
16+
return std::find(users.begin(), users.end(), idx) != users.end();
17+
}
18+
1519
void SharedObject::add_user(ComputeGraph* const graph, const ValueRef idx) {
1620
vTensorPtr t = graph->get_tensor(idx);
1721

backends/vulkan/runtime/graph/containers/SharedObject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ struct SharedObject {
3131
std::vector<ValueRef> users;
3232
vkapi::Allocation allocation;
3333

34+
bool has_user(const ValueRef idx) const;
3435
void add_user(ComputeGraph* const graph, const ValueRef idx);
3536
void allocate(ComputeGraph* const graph);
3637
void bind_users(ComputeGraph* const graph);

backends/vulkan/runtime/vk_api/memory/Buffer.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ class VulkanBuffer final {
161161

162162
inline void bind_allocation(const Allocation& memory) {
163163
VK_CHECK_COND(!memory_, "Cannot bind an already bound allocation!");
164-
VK_CHECK(vmaBindBufferMemory(allocator_, memory.allocation, handle_));
164+
if (!is_copy_) {
165+
VK_CHECK(vmaBindBufferMemory(allocator_, memory.allocation, handle_));
166+
}
165167
memory_.allocation = memory.allocation;
166168
}
167169

backends/vulkan/runtime/vk_api/memory/Image.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ VulkanImage::VulkanImage()
9898
allocator_(VK_NULL_HANDLE),
9999
memory_{},
100100
owns_memory_(false),
101+
owns_view_(false),
101102
is_copy_(false),
102103
handles_{
103104
VK_NULL_HANDLE,
@@ -121,6 +122,7 @@ VulkanImage::VulkanImage(
121122
allocator_(vma_allocator),
122123
memory_{},
123124
owns_memory_{allocate_memory},
125+
owns_view_(false),
124126
is_copy_(false),
125127
handles_{
126128
VK_NULL_HANDLE,
@@ -168,6 +170,7 @@ VulkanImage::VulkanImage(
168170
&(memory_.allocation),
169171
nullptr));
170172
// Only create the image view if the image has been bound to memory
173+
owns_view_ = true;
171174
create_image_view();
172175
} else {
173176
VK_CHECK(vkCreateImage(
@@ -182,6 +185,7 @@ VulkanImage::VulkanImage(const VulkanImage& other) noexcept
182185
allocator_(other.allocator_),
183186
memory_(other.memory_),
184187
owns_memory_{false},
188+
owns_view_{false},
185189
is_copy_(true),
186190
handles_(other.handles_),
187191
layout_(other.layout_) {}
@@ -193,6 +197,7 @@ VulkanImage::VulkanImage(VulkanImage&& other) noexcept
193197
allocator_(other.allocator_),
194198
memory_(std::move(other.memory_)),
195199
owns_memory_(other.owns_memory_),
200+
owns_view_(other.owns_view_),
196201
is_copy_(other.is_copy_),
197202
handles_(other.handles_),
198203
layout_(other.layout_) {
@@ -225,17 +230,17 @@ VulkanImage& VulkanImage::operator=(VulkanImage&& other) noexcept {
225230
}
226231

227232
VulkanImage::~VulkanImage() {
233+
if (owns_view_ && handles_.image_view != VK_NULL_HANDLE) {
234+
vkDestroyImageView(this->device(), handles_.image_view, nullptr);
235+
}
236+
228237
// Do not destroy any resources if this class instance is a copy of another
229238
// class instance, since this means that this class instance does not have
230239
// ownership of the underlying resource.
231240
if (is_copy_) {
232241
return;
233242
}
234243

235-
if (handles_.image_view != VK_NULL_HANDLE) {
236-
vkDestroyImageView(this->device(), handles_.image_view, nullptr);
237-
}
238-
239244
if (handles_.image != VK_NULL_HANDLE) {
240245
if (owns_memory_) {
241246
vmaDestroyImage(allocator_, handles_.image, memory_.allocation);

backends/vulkan/runtime/vk_api/memory/Image.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ class VulkanImage final {
145145
Allocation memory_;
146146
// Indicates whether the underlying memory is owned by this resource
147147
bool owns_memory_;
148+
// In some cases, a VulkanImage may be a copy of another VulkanImage but still
149+
// own a unique view of the VkImage.
150+
bool owns_view_;
148151
// Indicates whether this VulkanImage was copied from another VulkanImage,
149152
// thus it does not have ownership of the underlying VKBuffer
150153
bool is_copy_;
@@ -228,10 +231,17 @@ class VulkanImage final {
228231

229232
inline void bind_allocation(const Allocation& memory) {
230233
VK_CHECK_COND(!memory_, "Cannot bind an already bound allocation!");
231-
VK_CHECK(vmaBindImageMemory(allocator_, memory.allocation, handles_.image));
234+
// To prevent multiple instances of binding the same VkImage to a memory
235+
// block, do not actually bind memory if this VulkanImage is a copy. Assume
236+
// that the original VulkanImage is responsible for binding the image.
237+
if (!is_copy_) {
238+
VK_CHECK(
239+
vmaBindImageMemory(allocator_, memory.allocation, handles_.image));
240+
}
232241
memory_.allocation = memory.allocation;
233242

234243
// Only create the image view if the image has been bound to memory
244+
owns_view_ = true;
235245
create_image_view();
236246
}
237247

backends/vulkan/test/vulkan_compute_api_test.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3146,12 +3146,12 @@ void test_transpose_view_mm(
31463146
mat2_t_small_size = {B, N - 1, K - 3};
31473147
}
31483148

3149-
// Build graph
3149+
// Build graph; use shared objects to test views of shared objects
31503150

31513151
IOValueRef mat1 =
3152-
graph.add_input_tensor(mat1_size, vkapi::kFloat, utils::kWidthPacked);
3153-
IOValueRef mat2_transpose =
3154-
graph.add_input_tensor(mat2_t_size, vkapi::kFloat, utils::kWidthPacked);
3152+
graph.add_input_tensor(mat1_size, vkapi::kFloat, utils::kWidthPacked, 0);
3153+
IOValueRef mat2_transpose = graph.add_input_tensor(
3154+
mat2_t_size, vkapi::kFloat, utils::kWidthPacked, 1);
31553155

31563156
ValueRef mat2 = graph.add_tensor_view(mat2_transpose.value);
31573157

@@ -3167,7 +3167,7 @@ void test_transpose_view_mm(
31673167
}
31683168

31693169
IOValueRef out;
3170-
out.value = graph.add_tensor(out_size, vkapi::kFloat, utils::kWidthPacked);
3170+
out.value = graph.add_tensor(out_size, vkapi::kFloat, utils::kWidthPacked, 2);
31713171

31723172
VK_GET_OP_FN("aten.transpose.int")
31733173
(graph, {mat2_transpose.value, dim0, dim1, mat2});

0 commit comments

Comments
 (0)