Skip to content

Commit 25168b7

Browse files
SS-JIAfacebook-github-bot
authored andcommitted
Enable tensor aliases with texture storage (#5347)
Summary: Pull Request resolved: #5347 ## Context Allow aliasing for `VulkanImage` objects, and by extension texture backed Tensors. Essentially just a re-application/extension of what was done in #4769. bypass-github-export-checks ghstack-source-id: 242452078 exported-using-ghexport Reviewed By: jorgep31415 Differential Revision: D62606929 fbshipit-source-id: 446b07d3176d99679750674e6ae82e29e88bc27d
1 parent 31e652d commit 25168b7

File tree

6 files changed

+85
-31
lines changed

6 files changed

+85
-31
lines changed

backends/vulkan/runtime/api/containers/Tensor.cpp

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -682,13 +682,9 @@ vTensorStorage::vTensorStorage(
682682
image_extents_(other.image_extents_),
683683
buffer_length_{other.buffer_length_},
684684
buffer_offset_{buffer_offset},
685-
image_(),
685+
image_(other.image_),
686686
buffer_(other.buffer_, buffer_offset),
687-
last_access_{other.last_access_} {
688-
if (other.storage_type_ != utils::kBuffer) {
689-
VK_THROW("Tensors with texture storage cannot be copied!");
690-
}
691-
}
687+
last_access_{other.last_access_} {}
692688

693689
vTensorStorage::~vTensorStorage() {
694690
flush();
@@ -758,14 +754,10 @@ void vTensorStorage::transition(
758754
}
759755

760756
bool vTensorStorage::is_copy_of(const vTensorStorage& other) const {
761-
if (storage_type_ != other.storage_type_) {
762-
return false;
763-
}
764-
// Copies are only enabled for buffer storage at the moment
765-
if (storage_type_ != utils::kBuffer) {
766-
return false;
757+
if (storage_type_ == utils::kBuffer) {
758+
return buffer_.is_copy_of(other.buffer_);
767759
}
768-
return buffer_.is_copy_of(other.buffer_);
760+
return image_.is_copy_of(other.image_);
769761
}
770762

771763
void vTensorStorage::discard_and_reallocate(

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ struct Allocation final {
8080
}
8181

8282
friend class VulkanBuffer;
83+
friend class VulkanImage;
8384
};
8485

8586
} // namespace vkapi

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ VulkanBuffer::VulkanBuffer(
8383
: buffer_properties_(other.buffer_properties_),
8484
allocator_(other.allocator_),
8585
memory_(other.memory_),
86-
owns_memory_(other.owns_memory_),
86+
owns_memory_(false),
8787
is_copy_(true),
8888
handle_(other.handle_) {
8989
// TODO: set the offset and range appropriately

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

Lines changed: 22 additions & 0 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+
is_copy_(false),
101102
handles_{
102103
VK_NULL_HANDLE,
103104
VK_NULL_HANDLE,
@@ -120,6 +121,7 @@ VulkanImage::VulkanImage(
120121
allocator_(vma_allocator),
121122
memory_{},
122123
owns_memory_{allocate_memory},
124+
is_copy_(false),
123125
handles_{
124126
VK_NULL_HANDLE,
125127
VK_NULL_HANDLE,
@@ -175,13 +177,25 @@ VulkanImage::VulkanImage(
175177
}
176178
}
177179

180+
VulkanImage::VulkanImage(const VulkanImage& other) noexcept
181+
: image_properties_(other.image_properties_),
182+
view_properties_(other.view_properties_),
183+
sampler_properties_(other.sampler_properties_),
184+
allocator_(other.allocator_),
185+
memory_(other.memory_),
186+
owns_memory_{false},
187+
is_copy_(true),
188+
handles_(other.handles_),
189+
layout_(other.layout_) {}
190+
178191
VulkanImage::VulkanImage(VulkanImage&& other) noexcept
179192
: image_properties_(other.image_properties_),
180193
view_properties_(other.view_properties_),
181194
sampler_properties_(other.sampler_properties_),
182195
allocator_(other.allocator_),
183196
memory_(std::move(other.memory_)),
184197
owns_memory_(other.owns_memory_),
198+
is_copy_(other.is_copy_),
185199
handles_(other.handles_),
186200
layout_(other.layout_) {
187201
other.handles_.image = VK_NULL_HANDLE;
@@ -201,6 +215,7 @@ VulkanImage& VulkanImage::operator=(VulkanImage&& other) noexcept {
201215
allocator_ = other.allocator_;
202216
memory_ = std::move(other.memory_);
203217
owns_memory_ = other.owns_memory_;
218+
is_copy_ = other.is_copy_;
204219
handles_ = other.handles_;
205220
layout_ = other.layout_;
206221

@@ -212,6 +227,13 @@ VulkanImage& VulkanImage::operator=(VulkanImage&& other) noexcept {
212227
}
213228

214229
VulkanImage::~VulkanImage() {
230+
// Do not destroy any resources if this class instance is a copy of another
231+
// class instance, since this means that this class instance does not have
232+
// ownership of the underlying resource.
233+
if (is_copy_) {
234+
return;
235+
}
236+
215237
if (VK_NULL_HANDLE != handles_.image_view) {
216238
vkDestroyImageView(this->device(), handles_.image_view, nullptr);
217239
}

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
#include <unordered_map>
2323

2424
namespace vkcompute {
25+
26+
// Forward declare vTensor classes such that they can be set as friend classes
27+
namespace api {
28+
class vTensorStorage;
29+
} // namespace api
30+
2531
namespace vkapi {
2632

2733
class ImageSampler final {
@@ -96,7 +102,23 @@ class VulkanImage final {
96102
VkSampler,
97103
const bool allocate_memory = true);
98104

99-
VulkanImage(const VulkanImage&) = delete;
105+
protected:
106+
/*
107+
* The Copy constructor allows for creation of a class instance that are
108+
* "aliases" of another class instance. The resulting class instance will not
109+
* have ownership of the underlying VkImage.
110+
*
111+
* This behaviour is analogous to creating a copy of a pointer, thus it is
112+
* unsafe, as the original class instance may be destroyed before the copy.
113+
* These constructors are therefore marked protected so that they may be used
114+
* only in situations where the lifetime of the original class instance is
115+
* guaranteed to exceed, or at least be the same as, the lifetime of the
116+
* copied class instance.
117+
*/
118+
VulkanImage(const VulkanImage& other) noexcept;
119+
120+
public:
121+
// To discourage creating copies, the assignment operator is still deleted.
100122
VulkanImage& operator=(const VulkanImage&) = delete;
101123

102124
VulkanImage(VulkanImage&&) noexcept;
@@ -123,6 +145,9 @@ class VulkanImage final {
123145
Allocation memory_;
124146
// Indicates whether the underlying memory is owned by this resource
125147
bool owns_memory_;
148+
// Indicates whether this VulkanImage was copied from another VulkanImage,
149+
// thus it does not have ownership of the underlying VKBuffer
150+
bool is_copy_;
126151
Handles handles_;
127152
// Layout
128153
VkImageLayout layout_;
@@ -193,10 +218,18 @@ class VulkanImage final {
193218
return owns_memory_;
194219
}
195220

221+
inline bool is_copy() const {
222+
return is_copy_;
223+
}
224+
196225
inline operator bool() const {
197226
return (handles_.image != VK_NULL_HANDLE);
198227
}
199228

229+
inline bool is_copy_of(const VulkanImage& other) const {
230+
return (handles_.image == other.handles_.image) && is_copy_;
231+
}
232+
200233
inline void bind_allocation(const Allocation& memory) {
201234
VK_CHECK_COND(!memory_, "Cannot bind an already bound allocation!");
202235
VK_CHECK(vmaBindImageMemory(allocator_, memory.allocation, handles_.image));
@@ -207,6 +240,8 @@ class VulkanImage final {
207240
}
208241

209242
VkMemoryRequirements get_memory_requirements() const;
243+
244+
friend class api::vTensorStorage;
210245
};
211246

212247
struct ImageMemoryBarrier final {

backends/vulkan/test/vulkan_compute_api_test.cpp

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -604,26 +604,30 @@ TEST_F(VulkanComputeAPITest, texture_add_sanity_check) {
604604
}
605605
}
606606

607-
TEST_F(VulkanComputeAPITest, tensor_copy_test) {
608-
std::vector<int64_t> sizes = {9, 9};
609-
std::vector<int64_t> strides =
610-
get_reference_strides(sizes, utils::kWidthPacked);
611-
std::vector<int64_t> dim_order = {0, 1};
607+
TEST_F(VulkanComputeAPITest, tensor_alias_test) {
608+
for (utils::StorageType storage_type : {utils::kTexture3D, utils::kBuffer}) {
609+
std::vector<int64_t> sizes = {9, 9};
612610

613-
vTensor original = CREATE_FLOAT_BUFFER(sizes, /*allocate_memory=*/true);
614-
vTensor copy = vTensor(original, sizes, dim_order);
615-
EXPECT_TRUE(get_vma_allocation_count() == 1);
616-
EXPECT_TRUE(copy.is_view_of(original));
611+
const size_t alloc_count_before = get_vma_allocation_count();
617612

618-
// Fill original tensor with some data
619-
fill_vtensor(original, 2.5f, true);
613+
vTensor original = vTensor(context(), sizes, vkapi::kFloat, storage_type);
620614

621-
std::vector<float> data_out(copy.staging_buffer_numel());
622-
// Extract the copy tensor; should contain the data of the original tensor
623-
extract_vtensor(copy, data_out);
615+
vTensor copy = vTensor(original);
624616

625-
for (size_t i = 0; i < data_out.size(); ++i) {
626-
CHECK_VALUE(data_out, i, 2.5f + i);
617+
// Two tensors but only one additional allocation.
618+
EXPECT_TRUE(get_vma_allocation_count() == alloc_count_before + 1);
619+
EXPECT_TRUE(copy.is_view_of(original));
620+
621+
// Fill original tensor with some data
622+
fill_vtensor(original, 2.5f, true);
623+
624+
std::vector<float> data_out(copy.staging_buffer_numel());
625+
// Extract the copy tensor; should contain the data of the original tensor
626+
extract_vtensor(copy, data_out);
627+
628+
for (size_t i = 0; i < original.numel(); ++i) {
629+
CHECK_VALUE(data_out, i, 2.5f + i);
630+
}
627631
}
628632
}
629633

0 commit comments

Comments
 (0)