1 /* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 #include "tensorflow/lite/delegates/gpu/metal/metal_spatial_tensor.h"
17
18 #include <cstring>
19 #include <memory>
20 #include <utility>
21 #include <vector>
22
23 #include "tensorflow/lite/delegates/gpu/common/task/buffer_desc.h"
24
25 namespace tflite {
26 namespace gpu {
27 namespace metal {
28 namespace {
29
CreateTextureBuffer(id<MTLBuffer> buffer,uint64_t buffer_offset,const TensorDescriptor & descriptor,id<MTLTexture> * texture)30 absl::Status CreateTextureBuffer(id<MTLBuffer> buffer, uint64_t buffer_offset,
31 const TensorDescriptor& descriptor,
32 id<MTLTexture>* texture) {
33 std::vector<uint64_t> storage_dims = descriptor.GetStorageDims();
34 if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, *)) {
35 const size_t data_size = storage_dims[0] * descriptor.GetElementSize() *
36 SizeOf(descriptor.GetDataType());
37 MTLTextureDescriptor* texture_desc = [[MTLTextureDescriptor alloc] init];
38 texture_desc.width = storage_dims[0];
39 texture_desc.pixelFormat =
40 DataTypeToRGBAPixelFormat(descriptor.GetDataType(), false);
41 texture_desc.textureType = MTLTextureTypeTextureBuffer;
42 texture_desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
43 texture_desc.storageMode = buffer.storageMode;
44 *texture = [buffer newTextureWithDescriptor:texture_desc
45 offset:buffer_offset
46 bytesPerRow:data_size];
47 if (!*texture) {
48 return absl::UnknownError("Failed to allocate id<MTLTexture>");
49 }
50 } else {
51 return absl::UnknownError(
52 "TensorStorageType::IMAGE_BUFFER available only in iOS 12/tvOS "
53 "12/macOS 10.14 and higher.");
54 }
55 return absl::OkStatus();
56 }
57
AllocateTensorMemory(id<MTLDevice> device,const TensorDescriptor & descriptor,id<MTLBuffer> * buffer,id<MTLTexture> * texture)58 absl::Status AllocateTensorMemory(id<MTLDevice> device,
59 const TensorDescriptor& descriptor,
60 id<MTLBuffer>* buffer,
61 id<MTLTexture>* texture) {
62 std::vector<uint64_t> storage_dims = descriptor.GetStorageDims();
63 const void* data_ptr =
64 descriptor.GetData().empty() ? nullptr : descriptor.GetData().data();
65 switch (descriptor.GetStorageType()) {
66 case TensorStorageType::BUFFER:
67 case TensorStorageType::IMAGE_BUFFER: {
68 const size_t data_size = storage_dims[0] * descriptor.GetElementSize() *
69 SizeOf(descriptor.GetDataType());
70 if (data_ptr) {
71 *buffer = [device newBufferWithBytes:data_ptr
72 length:data_size
73 options:MTLResourceStorageModeShared];
74 } else {
75 *buffer = [device newBufferWithLength:data_size
76 options:MTLResourceStorageModeShared];
77 }
78 if (!*buffer) {
79 return absl::UnknownError("Failed to allocate id<MTLBuffer>");
80 }
81 if (descriptor.GetStorageType() == TensorStorageType::IMAGE_BUFFER) {
82 RETURN_IF_ERROR(CreateTextureBuffer(*buffer, 0, descriptor, texture));
83 }
84 return absl::OkStatus();
85 }
86 case TensorStorageType::TEXTURE_2D: {
87 MTLTextureDescriptor* texture_desc = [MTLTextureDescriptor
88 texture2DDescriptorWithPixelFormat:DataTypeToRGBAPixelFormat(
89 descriptor.GetDataType(),
90 false)
91 width:storage_dims[0]
92 height:storage_dims[1]
93 mipmapped:NO];
94 texture_desc.textureType = MTLTextureType2D;
95 texture_desc.usage =
96 MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
97 texture_desc.storageMode = MTLStorageModePrivate;
98
99 *texture = [device newTextureWithDescriptor:texture_desc];
100 if (!*texture) {
101 return absl::UnknownError("Failed to allocate id<MTLTexture>");
102 }
103 if (data_ptr) {
104 WriteDataToTexture2D(*texture, device, data_ptr);
105 }
106 return absl::OkStatus();
107 }
108 case TensorStorageType::TEXTURE_3D: {
109 MTLTextureDescriptor* texture_desc = [[MTLTextureDescriptor alloc] init];
110 texture_desc.width = storage_dims[0];
111 texture_desc.height = storage_dims[1];
112 texture_desc.depth = storage_dims[2];
113 texture_desc.pixelFormat =
114 DataTypeToRGBAPixelFormat(descriptor.GetDataType(), false);
115 texture_desc.textureType = MTLTextureType3D;
116 texture_desc.usage =
117 MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
118 texture_desc.storageMode = MTLStorageModePrivate;
119
120 *texture = [device newTextureWithDescriptor:texture_desc];
121 if (!*texture) {
122 return absl::UnknownError("Failed to allocate id<MTLTexture>");
123 }
124 if (data_ptr) {
125 WriteDataToTexture3D(*texture, device, data_ptr);
126 }
127 return absl::OkStatus();
128 }
129 case TensorStorageType::TEXTURE_ARRAY: {
130 MTLTextureDescriptor* texture_desc = [[MTLTextureDescriptor alloc] init];
131 texture_desc.width = storage_dims[0];
132 texture_desc.height = storage_dims[1];
133 texture_desc.arrayLength = storage_dims[2];
134 texture_desc.pixelFormat =
135 DataTypeToRGBAPixelFormat(descriptor.GetDataType(), false);
136 texture_desc.textureType = MTLTextureType2DArray;
137 texture_desc.usage =
138 MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
139 texture_desc.storageMode = MTLStorageModePrivate;
140
141 *texture = [device newTextureWithDescriptor:texture_desc];
142 if (!*texture) {
143 return absl::UnknownError("Failed to allocate id<MTLTexture>");
144 }
145 if (data_ptr) {
146 WriteDataToTexture2DArray(*texture, device, data_ptr);
147 }
148 return absl::OkStatus();
149 }
150 case TensorStorageType::SINGLE_TEXTURE_2D:
151 default:
152 return absl::InternalError("Unsupported tensor storage type");
153 }
154 }
155 } // namespace
156
MetalSpatialTensor(id<MTLBuffer> buffer,id<MTLTexture> texture,bool memory_owner,bool texture_mem_owner,const TensorDescriptor & descriptor)157 MetalSpatialTensor::MetalSpatialTensor(id<MTLBuffer> buffer,
158 id<MTLTexture> texture,
159 bool memory_owner,
160 bool texture_mem_owner,
161 const TensorDescriptor& descriptor)
162 : memory_(buffer),
163 texture_mem_(texture),
164 memory_owner_(memory_owner),
165 texture_mem_owner_(texture_mem_owner),
166 descriptor_(descriptor) {}
167
MetalSpatialTensor(MetalSpatialTensor && tensor)168 MetalSpatialTensor::MetalSpatialTensor(MetalSpatialTensor&& tensor)
169 : memory_(tensor.memory_),
170 texture_mem_(tensor.texture_mem_),
171 memory_owner_(tensor.memory_owner_),
172 texture_mem_owner_(tensor.texture_mem_owner_),
173 descriptor_(std::move(tensor.descriptor_)),
174 aligned_texture_width_(tensor.aligned_texture_width_),
175 buffer_offset_(tensor.buffer_offset_) {
176 tensor.memory_ = nullptr;
177 }
178
operator =(MetalSpatialTensor && tensor)179 MetalSpatialTensor& MetalSpatialTensor::operator=(MetalSpatialTensor&& tensor) {
180 if (this != &tensor) {
181 Release();
182 std::swap(memory_, tensor.memory_);
183 std::swap(texture_mem_, tensor.texture_mem_);
184 std::swap(memory_owner_, tensor.memory_owner_);
185 std::swap(texture_mem_owner_, tensor.texture_mem_owner_);
186 descriptor_ = std::move(tensor.descriptor_);
187 std::swap(aligned_texture_width_, tensor.aligned_texture_width_);
188 std::swap(buffer_offset_, tensor.buffer_offset_);
189 }
190 return *this;
191 }
192
Release()193 void MetalSpatialTensor::Release() {
194 if (memory_owner_ && memory_) {
195 memory_ = nullptr;
196 }
197 if (texture_mem_owner_ && texture_mem_) {
198 texture_mem_ = nullptr;
199 }
200 }
201
GetGPUResources(const GPUObjectDescriptor * obj_ptr,GPUResourcesWithValue * resources) const202 absl::Status MetalSpatialTensor::GetGPUResources(
203 const GPUObjectDescriptor* obj_ptr,
204 GPUResourcesWithValue* resources) const {
205 const auto* buffer_desc = dynamic_cast<const BufferDescriptor*>(obj_ptr);
206 if (buffer_desc) {
207 if (descriptor_.GetStorageType() != TensorStorageType::BUFFER) {
208 return absl::InvalidArgumentError(
209 "Tensor can be used with BufferDescriptor only wtih "
210 "TensorStorageType::BUFFER.");
211 }
212 resources->buffers.push_back({"buffer", {memory_, buffer_offset_}});
213 return absl::OkStatus();
214 }
215 const auto* tensor_desc = dynamic_cast<const TensorDescriptor*>(obj_ptr);
216 if (!tensor_desc) {
217 return absl::InvalidArgumentError("Expected TensorDescriptor on input.");
218 }
219 tensor_desc->GetGpuResources(descriptor_.GetBHWDCShape(),
220 &resources->generic);
221
222 if (descriptor_.GetStorageType() == TensorStorageType::BUFFER) {
223 resources->buffers.push_back({"buffer", {memory_, buffer_offset_}});
224 } else if (descriptor_.GetStorageType() == TensorStorageType::TEXTURE_2D) {
225 if (obj_ptr->GetAccess() == AccessType::WRITE &&
226 tensor_desc->GetUseBufferForWriteOnlyTexture2d()) {
227 resources->AddInt("aligned_texture_width", aligned_texture_width_);
228 resources->buffers.push_back({"buffer", {memory_, buffer_offset_}});
229 } else {
230 resources->images2d.push_back({"image2d", texture_mem_});
231 }
232 } else if (descriptor_.GetStorageType() == TensorStorageType::TEXTURE_3D) {
233 resources->images3d.push_back({"image3d", texture_mem_});
234 } else if (descriptor_.GetStorageType() == TensorStorageType::TEXTURE_ARRAY) {
235 resources->image2d_arrays.push_back({"image2d_array", texture_mem_});
236 } else if (descriptor_.GetStorageType() == TensorStorageType::IMAGE_BUFFER) {
237 if (obj_ptr->GetAccess() == AccessType::WRITE &&
238 tensor_desc->GetUseBufferForWriteOnlyImageBuffer()) {
239 resources->buffers.push_back({"buffer", {memory_, buffer_offset_}});
240 } else {
241 resources->image_buffers.push_back({"image_buffer", texture_mem_});
242 }
243 }
244
245 return absl::OkStatus();
246 }
247
CreateFromDescriptor(const TensorDescriptor & desc,id<MTLDevice> device)248 absl::Status MetalSpatialTensor::CreateFromDescriptor(
249 const TensorDescriptor& desc, id<MTLDevice> device) {
250 desc.CopyWithoutData(&descriptor_);
251 memory_owner_ = true;
252 id<MTLBuffer> buffer;
253 id<MTLTexture> texture;
254 RETURN_IF_ERROR(AllocateTensorMemory(device, desc, &buffer, &texture));
255 memory_ = buffer;
256 texture_mem_ = texture;
257 return absl::OkStatus();
258 }
259
UploadDescriptorData(const TensorDescriptor & desc,id<MTLDevice> device)260 absl::Status MetalSpatialTensor::UploadDescriptorData(
261 const TensorDescriptor& desc, id<MTLDevice> device) {
262 return WriteData(device, desc.GetData().data());
263 }
264
ToDescriptor(TensorDescriptor * desc,id<MTLDevice> device) const265 absl::Status MetalSpatialTensor::ToDescriptor(TensorDescriptor* desc,
266 id<MTLDevice> device) const {
267 *desc = descriptor_;
268 std::vector<uint8_t> data(GetMemorySizeInBytes());
269 RETURN_IF_ERROR(ReadData(device, data.data()));
270 desc->SetData(std::move(data));
271 return absl::OkStatus();
272 }
273
WriteData(id<MTLDevice> device,const void * ptr)274 absl::Status MetalSpatialTensor::WriteData(id<MTLDevice> device,
275 const void* ptr) {
276 switch (descriptor_.GetStorageType()) {
277 case TensorStorageType::BUFFER:
278 case TensorStorageType::IMAGE_BUFFER:
279 std::memcpy(
280 reinterpret_cast<uint8_t*>([memory_ contents]) + buffer_offset_, ptr,
281 GetMemorySizeInBytes());
282 break;
283 case TensorStorageType::TEXTURE_2D:
284 WriteDataToTexture2D(texture_mem_, device, ptr);
285 break;
286 case TensorStorageType::TEXTURE_3D:
287 WriteDataToTexture3D(texture_mem_, device, ptr);
288 break;
289 case TensorStorageType::TEXTURE_ARRAY:
290 WriteDataToTexture2DArray(texture_mem_, device, ptr);
291 break;
292 case TensorStorageType::SINGLE_TEXTURE_2D:
293 default:
294 return absl::InternalError("Unsupported tensor storage type");
295 }
296 return absl::OkStatus();
297 }
298
ReadData(id<MTLDevice> device,void * ptr) const299 absl::Status MetalSpatialTensor::ReadData(id<MTLDevice> device,
300 void* ptr) const {
301 switch (descriptor_.GetStorageType()) {
302 case TensorStorageType::BUFFER:
303 case TensorStorageType::IMAGE_BUFFER:
304 std::memcpy(
305 ptr, reinterpret_cast<uint8_t*>([memory_ contents]) + buffer_offset_,
306 GetMemorySizeInBytes());
307 break;
308 case TensorStorageType::TEXTURE_2D:
309 ReadDataFromTexture2D(texture_mem_, device, ptr);
310 break;
311 case TensorStorageType::TEXTURE_3D:
312 ReadDataFromTexture3D(texture_mem_, device, ptr);
313 break;
314 case TensorStorageType::TEXTURE_ARRAY:
315 ReadDataFromTexture2DArray(texture_mem_, device, ptr);
316 break;
317 case TensorStorageType::SINGLE_TEXTURE_2D:
318 default:
319 return absl::InternalError("Unsupported tensor storage type");
320 }
321 return absl::OkStatus();
322 }
323
SetBufferHandle(id<MTLBuffer> buffer)324 absl::Status MetalSpatialTensor::SetBufferHandle(id<MTLBuffer> buffer) {
325 if (memory_owner_) {
326 return absl::InvalidArgumentError(
327 "SetBufferHandle can be used only with shared "
328 "Tensors(CreateSharedBufferTensor).");
329 }
330 if (memory_ == buffer) {
331 return absl::OkStatus();
332 }
333 memory_ = buffer;
334 if (descriptor_.GetStorageType() == TensorStorageType::IMAGE_BUFFER) {
335 id<MTLTexture> texture_buffer = nullptr;
336 RETURN_IF_ERROR(
337 CreateTextureBuffer(memory_, 0, descriptor_, &texture_buffer));
338 texture_mem_ = texture_buffer;
339 }
340 return absl::OkStatus();
341 }
342
GetBufferHandle() const343 id<MTLBuffer> MetalSpatialTensor::GetBufferHandle() const { return memory_; }
344
CreateTensor(id<MTLDevice> device,const TensorDescriptor & descriptor,MetalSpatialTensor * result)345 absl::Status CreateTensor(id<MTLDevice> device,
346 const TensorDescriptor& descriptor,
347 MetalSpatialTensor* result) {
348 id<MTLBuffer> buffer;
349 id<MTLTexture> texture;
350 RETURN_IF_ERROR(AllocateTensorMemory(device, descriptor, &buffer, &texture));
351 *result = MetalSpatialTensor(buffer, texture, true, true, descriptor);
352 return absl::OkStatus();
353 }
354
CreateTensorSharedBuffer(id<MTLBuffer> buffer,const TensorDescriptor & descriptor,MetalSpatialTensor * result,uint64_t buffer_offset)355 absl::Status CreateTensorSharedBuffer(id<MTLBuffer> buffer,
356 const TensorDescriptor& descriptor,
357 MetalSpatialTensor* result,
358 uint64_t buffer_offset) {
359 id<MTLTexture> texture_buffer = nullptr;
360 if (buffer &&
361 descriptor.GetStorageType() == TensorStorageType::IMAGE_BUFFER) {
362 RETURN_IF_ERROR(CreateTextureBuffer(buffer, buffer_offset, descriptor,
363 &texture_buffer));
364 }
365 *result = MetalSpatialTensor(buffer, texture_buffer, false, true, descriptor);
366 result->buffer_offset_ = buffer_offset;
367 return absl::OkStatus();
368 }
369
CreateTensorSharedImage2DBuffer(id<MTLBuffer> buffer,const TensorDescriptor & descriptor,int row_bytes_alignment,MetalSpatialTensor * result,uint64_t buffer_offset)370 absl::Status CreateTensorSharedImage2DBuffer(id<MTLBuffer> buffer,
371 const TensorDescriptor& descriptor,
372 int row_bytes_alignment,
373 MetalSpatialTensor* result,
374 uint64_t buffer_offset) {
375 std::vector<uint64_t> storage_dims = descriptor.GetStorageDims();
376 const int width = storage_dims[0];
377 const int height = storage_dims[1];
378 const int channels = descriptor.GetElementSize();
379 MTLTextureDescriptor* texture_desc = [[MTLTextureDescriptor alloc] init];
380 texture_desc.width = width;
381 texture_desc.height = height;
382 texture_desc.depth = 1;
383 texture_desc.textureType = MTLTextureType2D;
384 texture_desc.arrayLength = 1;
385 texture_desc.mipmapLevelCount = 1;
386 texture_desc.sampleCount = 1;
387 texture_desc.pixelFormat =
388 DataTypeToRGBAPixelFormat(descriptor.GetDataType(), false);
389 texture_desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
390 texture_desc.storageMode = buffer.storageMode;
391 const size_t pixel_size = channels * SizeOf(descriptor.GetDataType());
392 const size_t bytes_per_row = width * pixel_size;
393 const size_t bytes_per_row_aligned =
394 AlignByN(bytes_per_row, row_bytes_alignment);
395 id<MTLTexture> texture_buffer =
396 [buffer newTextureWithDescriptor:texture_desc
397 offset:buffer_offset
398 bytesPerRow:bytes_per_row_aligned];
399 if (!texture_buffer) {
400 return absl::UnknownError("Failed to allocate id<MTLTexture>.");
401 }
402 if (bytes_per_row_aligned % pixel_size != 0) {
403 return absl::UnknownError("Alignment mismatch.");
404 }
405 *result = MetalSpatialTensor(buffer, texture_buffer, false, true, descriptor);
406 result->aligned_texture_width_ = bytes_per_row_aligned / pixel_size;
407 result->buffer_offset_ = buffer_offset;
408 return absl::OkStatus();
409 }
410
GetFastestStorageType(const GpuInfo & gpu_info)411 TensorStorageType GetFastestStorageType(const GpuInfo& gpu_info) {
412 const bool a7_or_a8 =
413 gpu_info.IsApple() && (gpu_info.apple_info.IsA7GenerationGpu() ||
414 gpu_info.apple_info.IsA8GenerationGpu());
415 if (a7_or_a8) {
416 return TensorStorageType::TEXTURE_2D;
417 } else {
418 return TensorStorageType::BUFFER;
419 }
420 }
421
422 } // namespace metal
423 } // namespace gpu
424 } // namespace tflite
425