@@ -63,6 +63,12 @@ std::unordered_map<void*, int32_t> not_own_handles;
6363// one only drops the handle.
6464std::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).
6874std::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
0 commit comments