Skip to content

Commit cb8b453

Browse files
Merge branch 'fix/metal-offset-views' into fix/metal-nonpacked-views
# Conflicts: # backends/apple/metal/runtime/shims/memory.cpp # backends/apple/metal/runtime/test/test_memory.cpp
2 parents dcd8bb5 + 093b315 commit cb8b453

2 files changed

Lines changed: 74 additions & 5 deletions

File tree

‎backends/apple/metal/runtime/shims/memory.cpp‎

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ std::unordered_map<void*, int32_t> not_own_handles;
6363
// one only drops the handle.
6464
std::unordered_set<Tensor*> retired_handles;
6565

66+
// The address an empty view stands for, where that is not the address it is
67+
// bound at (aoti_torch__reinterpret_tensor binds empty views at their
68+
// parent's start). Views of it are offset from here, so they are checked
69+
// against the memory they really reach.
70+
std::unordered_map<Tensor*, void*> empty_view_address;
71+
6672
// Size of each CPU allocation the runtime owns, so that views of it can be
6773
// bound into one Metal buffer over all of it (metal_register_cpu_view).
6874
std::unordered_map<void*, size_t> cpu_allocation_bytes;
@@ -762,6 +768,7 @@ AOTITorchError aoti_torch_delete_tensor_object(AOTITensorHandle tensor) {
762768

763769
if (retired_handles.erase(tensor) != 0) {
764770
// Its memory is gone, and its address may be another allocation's now.
771+
empty_view_address.erase(tensor);
765772
tensors.erase(it);
766773
return Error::Ok;
767774
}
@@ -770,6 +777,7 @@ AOTITorchError aoti_torch_delete_tensor_object(AOTITensorHandle tensor) {
770777
void* data_ptr = tensor_ptr->mutable_data_ptr();
771778
if (data_ptr == nullptr) {
772779
// A tensor with no memory (make_memoryless_tensor).
780+
empty_view_address.erase(tensor);
773781
tensors.erase(it);
774782
return Error::Ok;
775783
}
@@ -801,6 +809,7 @@ AOTITorchError aoti_torch_delete_tensor_object(AOTITensorHandle tensor) {
801809
memory_to_n_tensor.erase(data_ptr);
802810
}
803811
metal_forget_strided_view(tensor);
812+
empty_view_address.erase(tensor);
804813
tensors.erase(it);
805814
ET_LOG(
806815
Debug,
@@ -810,6 +819,7 @@ AOTITorchError aoti_torch_delete_tensor_object(AOTITensorHandle tensor) {
810819
ET_CHECK_OK_OR_RETURN_ERROR(release_memory(data_ptr));
811820

812821
metal_forget_strided_view(tensor);
822+
empty_view_address.erase(tensor);
813823
tensors.erase(it);
814824
ET_LOG(Debug, "aoti_torch_delete_tensor_object: successful");
815825
return Error::Ok;
@@ -1127,11 +1137,17 @@ AOTITorchError aoti_torch__reinterpret_tensor(
11271137
InvalidArgument,
11281138
"aoti_torch__reinterpret_tensor: sizes and strides must fit a tensor");
11291139

1130-
// Handle storage offset by adjusting the data pointer
1140+
// Handle storage offset by adjusting the data pointer. An empty `self` is
1141+
// offset from the address it stands for, not the one it is bound at.
1142+
void* origin = data_ptr;
1143+
auto empty_origin = empty_view_address.find(self);
1144+
if (empty_origin != empty_view_address.end()) {
1145+
origin = empty_origin->second;
1146+
}
11311147
size_t element_size = dtype_to_element_size(dtype);
11321148
void* adjusted_data = nullptr;
11331149
ET_CHECK_OR_RETURN_ERROR(
1134-
offset_pointer(data_ptr, storage_offset, element_size, &adjusted_data),
1150+
offset_pointer(origin, storage_offset, element_size, &adjusted_data),
11351151
InvalidArgument,
11361152
"aoti_torch__reinterpret_tensor: storage_offset overflows");
11371153

@@ -1145,16 +1161,19 @@ AOTITorchError aoti_torch__reinterpret_tensor(
11451161
"aoti_torch__reinterpret_tensor: sizes must not be negative");
11461162

11471163
// An empty view reads nothing. Its offset can put it at the very end of its
1148-
// buffer, which may be where the next allocation starts; point it at the
1149-
// start of its parent instead, so that it is not taken for a view there.
1164+
// buffer, which may be where the next allocation starts; bind it at the
1165+
// start of its parent instead, so that it is not taken for a view there,
1166+
// and remember the address it stands for (empty_view_address).
11501167
int64_t view_numel = 1;
11511168
for (auto size : sizes) {
11521169
ET_CHECK_OR_RETURN_ERROR(
11531170
!__builtin_mul_overflow(view_numel, int64_t{size}, &view_numel),
11541171
InvalidArgument,
11551172
"aoti_torch__reinterpret_tensor: the number of elements overflows");
11561173
}
1157-
if (view_numel == 0) {
1174+
const bool empty_view = view_numel == 0;
1175+
void* empty_address = adjusted_data;
1176+
if (empty_view) {
11581177
adjusted_data = data_ptr;
11591178
}
11601179

@@ -1366,6 +1385,9 @@ AOTITorchError aoti_torch__reinterpret_tensor(
13661385
ET_DCHECK_MSG(recorded, "a packable strided view was not recorded");
13671386
(void)recorded;
13681387
}
1388+
if (empty_view && empty_address != adjusted_data) {
1389+
empty_view_address[tensor.get()] = empty_address;
1390+
}
13691391
if (registered) {
13701392
view_handles.insert(tensor.get());
13711393
}
@@ -1463,6 +1485,11 @@ AOTITorchError aoti_torch_new_tensor_handle(
14631485
*new_handle = tensor.get();
14641486

14651487
metal_share_strided_view(orig_handle, tensor.get());
1488+
// A copy of an empty view stands for the same address.
1489+
auto empty_origin = empty_view_address.find(orig_handle);
1490+
if (empty_origin != empty_view_address.end()) {
1491+
empty_view_address[tensor.get()] = empty_origin->second;
1492+
}
14661493

14671494
// If the original is a view into a Metal buffer, the new handle is one too,
14681495
// and deleting either must leave the view registered for the other one.
@@ -1498,6 +1525,7 @@ void retire_handles_within(void* ptr, size_t nbytes) {
14981525
// regions do not overlap Metal buffers.
14991526
view_owner.erase(entry.first);
15001527
metal_forget_strided_view(entry.first);
1528+
empty_view_address.erase(entry.first);
15011529
retired_handles.insert(entry.first);
15021530
}
15031531
}
@@ -1545,6 +1573,7 @@ void cleanup_memory() {
15451573
view_owner.clear();
15461574
view_handles.clear();
15471575
retired_handles.clear();
1576+
empty_view_address.clear();
15481577
cpu_allocation_bytes.clear();
15491578

15501579
// Clean up Metal resources

‎backends/apple/metal/runtime/test/test_memory.cpp‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,46 @@ TEST_F(MetalGraphViewTest, FreeingABufferForgetsTheStridedViewsInIt) {
19911991
ASSERT_EQ(aoti_torch_delete_tensor_object(weight), Error::Ok);
19921992
}
19931993

1994+
// An empty view is bound at its parent's start but stands for its own offset:
1995+
// views of it are offset from there, so one past the end of the allocation is
1996+
// refused, and one inside it lands where it belongs, also through a copy.
1997+
TEST_F(MetalGraphViewTest, ViewsOfAnEmptyViewKeepItsOffset) {
1998+
const int64_t base_size = 8;
1999+
AOTITensorHandle base = nullptr;
2000+
ASSERT_EQ(
2001+
aoti_torch_empty_strided(
2002+
1, &base_size, &kStride, kFloat32, kDeviceMps, 0, &base),
2003+
Error::Ok);
2004+
auto* memory = static_cast<float*>(base->mutable_data_ptr());
2005+
const int64_t zero = 0;
2006+
const int64_t four = 4;
2007+
2008+
AOTITensorHandle at_end = nullptr;
2009+
ASSERT_EQ(
2010+
aoti_torch__reinterpret_tensor(
2011+
base, 1, &zero, &kStride, /*storage_offset=*/8, &at_end),
2012+
Error::Ok);
2013+
AOTITensorHandle past_end = nullptr;
2014+
EXPECT_NE(
2015+
aoti_torch__reinterpret_tensor(
2016+
at_end, 1, &four, &kStride, /*storage_offset=*/0, &past_end),
2017+
Error::Ok);
2018+
2019+
AOTITensorHandle middle = nullptr;
2020+
ASSERT_EQ(
2021+
aoti_torch__reinterpret_tensor(
2022+
base, 1, &zero, &kStride, /*storage_offset=*/4, &middle),
2023+
Error::Ok);
2024+
AOTITensorHandle copy = nullptr;
2025+
ASSERT_EQ(aoti_torch_new_tensor_handle(middle, &copy), Error::Ok);
2026+
AOTITensorHandle back_half = nullptr;
2027+
ASSERT_EQ(
2028+
aoti_torch__reinterpret_tensor(
2029+
copy, 1, &four, &kStride, /*storage_offset=*/0, &back_half),
2030+
Error::Ok);
2031+
EXPECT_EQ(back_half->const_data_ptr(), memory + 4);
2032+
}
2033+
19942034
// Memory a tensor owns goes with the tensor, not with aoti_torch_mps_free.
19952035
TEST_F(MetalGraphViewTest, FreeingATensorsMemoryIsRefused) {
19962036
const int64_t size = 4;

0 commit comments

Comments
 (0)