1 /* Copyright 2015 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 #ifndef TENSORFLOW_CORE_FRAMEWORK_TENSOR_H_
17 #define TENSORFLOW_CORE_FRAMEWORK_TENSOR_H_
18
19 #include <cstdint>
20 #include <type_traits>
21 #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
22 #include "tensorflow/core/framework/allocator.h"
23 #include "tensorflow/core/framework/tensor_shape.h"
24 #include "tensorflow/core/framework/tensor_types.h"
25 #include "tensorflow/core/framework/types.h"
26 #include "tensorflow/core/framework/types.pb.h"
27 #include "tensorflow/core/lib/core/refcount.h"
28 #include "tensorflow/core/lib/core/status.h"
29 #include "tensorflow/core/lib/core/stringpiece.h"
30 #include "tensorflow/core/lib/gtl/inlined_vector.h"
31 #include "tensorflow/core/platform/logging.h"
32 #include "tensorflow/core/platform/macros.h"
33 #include "tensorflow/core/platform/mem.h"
34 #include "tensorflow/core/platform/types.h"
35
36 namespace tensorflow {
37
38 // Forward declarations. In particular, we forward declare protos so that their
39 // symbols can be removed from .so exports.
40 class AllocationDescription;
41 class Allocator;
42 class OpKernelContext;
43 class Tensor;
44 class TensorBuffer;
45 class TensorCApi;
46 class TensorDescription;
47 class TensorProto;
48 class Var;
49
50 namespace batch_util {
51 Status CopyElementToSlice(Tensor element, Tensor* parent, int64 index);
52 Status MaybeMoveSliceToElement(Tensor* parent, Tensor* element, int64 index);
53 } // namespace batch_util
54
55 /// @ingroup core
56 /// Represents an n-dimensional array of values.
57 class Tensor {
58 public:
59 /// \brief Creates a 1-dimensional, 0-element float tensor.
60 ///
61 /// The returned Tensor is not a scalar (shape {}), but is instead
62 /// an empty one-dimensional Tensor (shape {0}, NumElements() ==
63 /// 0). Since it has no elements, it does not need to be assigned a
64 /// value and is initialized by default (IsInitialized() is
65 /// true). If this is undesirable, consider creating a one-element
66 /// scalar which does require initialization:
67 ///
68 /// ```c++
69 ///
70 /// Tensor(DT_FLOAT, TensorShape({}))
71 ///
72 /// ```
73 Tensor();
74
75 /// \brief Creates a Tensor of the given `type` and `shape`. If
76 /// LogMemory::IsEnabled() the allocation is logged as coming from
77 /// an unknown kernel and step. Calling the Tensor constructor
78 /// directly from within an Op is deprecated: use the
79 /// OpKernelConstruction/OpKernelContext allocate_* methods to
80 /// allocate a new tensor, which record the kernel and step.
81 ///
82 /// The underlying buffer is allocated using a `CPUAllocator`.
83 Tensor(DataType type, const TensorShape& shape);
84
85 /// \brief Creates a tensor with the input `type` and `shape`, using
86 /// the allocator `a` to allocate the underlying buffer. If
87 /// LogMemory::IsEnabled() the allocation is logged as coming from
88 /// an unknown kernel and step. Calling the Tensor constructor
89 /// directly from within an Op is deprecated: use the
90 /// OpKernelConstruction/OpKernelContext allocate_* methods to
91 /// allocate a new tensor, which record the kernel and step.
92 ///
93 /// `a` must outlive the lifetime of this Tensor.
94 Tensor(Allocator* a, DataType type, const TensorShape& shape);
95
96 /// \brief Creates a tensor with the input `type` and `shape`, using
97 /// the allocator `a` and the specified "allocation_attr" to
98 /// allocate the underlying buffer. If the kernel and step are known
99 /// allocation_attr.allocation_will_be_logged should be set to true
100 /// and LogMemory::RecordTensorAllocation should be called after the
101 /// tensor is constructed. Calling the Tensor constructor directly
102 /// from within an Op is deprecated: use the
103 /// OpKernelConstruction/OpKernelContext allocate_* methods to
104 /// allocate a new tensor, which record the kernel and step.
105 ///
106 /// `a` must outlive the lifetime of this Tensor.
107 Tensor(Allocator* a, DataType type, const TensorShape& shape,
108 const AllocationAttributes& allocation_attr);
109
110 /// \brief Creates an empty Tensor of the given data type.
111 ///
112 /// Like Tensor(), returns a 1-dimensional, 0-element Tensor with
113 /// IsInitialized() returning True. See the Tensor() documentation
114 /// for details.
115 explicit Tensor(DataType type);
116
117 private:
118 // A tag type for selecting the `Tensor` constructor overload that creates a
119 // scalar tensor in host memory.
120 struct host_scalar_tag {};
121
122 class HostScalarTensorBufferBase;
123 template <typename T>
124 struct ValueAndTensorBuffer;
125
126 // Creates a tensor with the given scalar `value` in CPU memory.
127 template <typename T>
128 Tensor(T value, host_scalar_tag tag);
129
130 public:
131 // A series of specialized constructors for scalar tensors in host memory.
132 //
133 // NOTE: The `Variant` host-scalar constructor is not defined, because Variant
134 // is implicitly constructible from many different types, and this causes
135 // ambiguities with some compilers.
Tensor(float scalar_value)136 explicit Tensor(float scalar_value)
137 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(double scalar_value)138 explicit Tensor(double scalar_value)
139 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(int32 scalar_value)140 explicit Tensor(int32 scalar_value)
141 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(uint32 scalar_value)142 explicit Tensor(uint32 scalar_value)
143 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(uint16 scalar_value)144 explicit Tensor(uint16 scalar_value)
145 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(uint8 scalar_value)146 explicit Tensor(uint8 scalar_value)
147 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(int16 scalar_value)148 explicit Tensor(int16 scalar_value)
149 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(int8 scalar_value)150 explicit Tensor(int8 scalar_value)
151 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(string scalar_value)152 explicit Tensor(string scalar_value)
153 : Tensor(std::move(scalar_value), host_scalar_tag{}) {}
Tensor(complex64 scalar_value)154 explicit Tensor(complex64 scalar_value)
155 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(complex128 scalar_value)156 explicit Tensor(complex128 scalar_value)
157 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(int64 scalar_value)158 explicit Tensor(int64 scalar_value)
159 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(uint64 scalar_value)160 explicit Tensor(uint64 scalar_value)
161 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(bool scalar_value)162 explicit Tensor(bool scalar_value)
163 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(qint8 scalar_value)164 explicit Tensor(qint8 scalar_value)
165 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(quint8 scalar_value)166 explicit Tensor(quint8 scalar_value)
167 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(qint16 scalar_value)168 explicit Tensor(qint16 scalar_value)
169 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(quint16 scalar_value)170 explicit Tensor(quint16 scalar_value)
171 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(qint32 scalar_value)172 explicit Tensor(qint32 scalar_value)
173 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(bfloat16 scalar_value)174 explicit Tensor(bfloat16 scalar_value)
175 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(Eigen::half scalar_value)176 explicit Tensor(Eigen::half scalar_value)
177 : Tensor(scalar_value, host_scalar_tag{}) {}
Tensor(ResourceHandle scalar_value)178 explicit Tensor(ResourceHandle scalar_value)
179 : Tensor(std::move(scalar_value), host_scalar_tag{}) {}
180
181 // NOTE: The `const char*` host-scalar constructor is provided as a
182 // convenience because otherwise passing a string literal would surprisingly
183 // construct a DT_BOOL tensor.
Tensor(const char * scalar_value)184 explicit Tensor(const char* scalar_value)
185 : Tensor(string(scalar_value), host_scalar_tag{}) {}
186
187 /// Copy constructor.
188 Tensor(const Tensor& other);
189
190 /// \brief Move constructor. After this call, <other> is safely destructible
191 /// and can be assigned to, but other calls on it (e.g. shape manipulation)
192 /// are not valid.
193 Tensor(Tensor&& other);
194
195 ~Tensor();
196
197 /// Returns the data type.
dtype()198 DataType dtype() const { return shape_.data_type(); }
199
200 /// Returns the shape of the tensor.
shape()201 const TensorShape& shape() const { return shape_; }
202
203 /// \brief Convenience accessor for the tensor shape.
204 ///
205 /// For all shape accessors, see comments for relevant methods of
206 /// `TensorShape` in `tensor_shape.h`.
dims()207 int dims() const { return shape().dims(); }
208
209 /// Convenience accessor for the tensor shape.
dim_size(int d)210 int64 dim_size(int d) const { return shape().dim_size(d); }
211
212 /// Convenience accessor for the tensor shape.
NumElements()213 int64 NumElements() const { return shape().num_elements(); }
214
IsSameSize(const Tensor & b)215 bool IsSameSize(const Tensor& b) const {
216 return shape().IsSameSize(b.shape());
217 }
218
219 // True iff the two tensors use the same underlying refcounted storage
220 bool SharesBufferWith(const Tensor& b) const;
221
222 /// \brief If necessary, has this Tensor been initialized?
223 ///
224 /// Zero-element Tensors are always considered initialized, even if they
225 /// have never been assigned to and do not have any memory allocated.
226 bool IsInitialized() const;
227
228 /// Returns the estimated memory usage of this tensor.
229 size_t TotalBytes() const;
230
231 // Returns the size of allocated memory for this tensor.
232 size_t AllocatedBytes() const;
233
234 /// Returns true iff this tensor is aligned.
IsAligned()235 bool IsAligned() const {
236 #if EIGEN_MAX_ALIGN_BYTES == 0
237 return true;
238 #else
239 void* ptr = base<void>();
240 return reinterpret_cast<intptr_t>(ptr) % EIGEN_MAX_ALIGN_BYTES == 0;
241 #endif
242 }
243
244 /// Assign operator. This tensor shares other's underlying storage.
245 Tensor& operator=(const Tensor& other) {
246 CopyFromInternal(other, other.shape());
247 return *this;
248 }
249
250 /// Move operator. See move constructor for details.
251 Tensor& operator=(Tensor&& other);
252
253 /// \brief Copy the other tensor into this tensor and reshape it.
254 ///
255 /// This tensor shares other's underlying storage. Returns `true`
256 /// iff `other.shape()` has the same number of elements of the given
257 /// `shape`.
CopyFrom(const Tensor & other,const TensorShape & shape)258 bool CopyFrom(const Tensor& other,
259 const TensorShape& shape) TF_MUST_USE_RESULT {
260 if (other.NumElements() != shape.num_elements()) return false;
261 CopyFromInternal(other, shape);
262 return true;
263 }
264
265 /// \brief Slice this tensor along the 1st dimension.
266
267 /// I.e., the returned tensor satisfies
268 /// returned[i, ...] == this[dim0_start + i, ...].
269 /// The returned tensor shares the underlying tensor buffer with this
270 /// tensor.
271 ///
272 /// NOTE: The returned tensor may not satisfy the same alignment
273 /// requirement as this tensor depending on the shape. The caller
274 /// must check the returned tensor's alignment before calling certain
275 /// methods that have alignment requirement (e.g., `flat()`, `tensor()`).
276 ///
277 /// NOTE: When fed with an N-dimensional tensor, this method returns a tensor
278 /// also with N dimensions. If you want to select a sub tensor, see SubSlice.
279 ///
280 /// REQUIRES: `dims()` >= 1
281 /// REQUIRES: `0 <= dim0_start <= dim0_limit <= dim_size(0)`
282 Tensor Slice(int64 dim0_start, int64 dim0_limit) const;
283
284 /// \brief Select a subslice from this tensor along the 1st dimension.
285 ///
286 /// When fed with an N-dimensional tensor, this method returns a tensor with
287 /// N-1 dimensions, where the returned tensor is a subslice of the input
288 /// tensor along the first dimension. The N-1 dimensions of the returned
289 /// tensor are the last N-1 dimensions of the input tensor.
290 ///
291 /// NOTE: The returned tensor may not satisfy the same alignment
292 /// requirement as this tensor depending on the shape. The caller
293 /// must check the returned tensor's alignment before calling certain
294 /// methods that have alignment requirement (e.g., `flat()`, `tensor()`).
295 ///
296 /// REQUIRES: `dims()` >= 1
297 /// REQUIRES: `0 <= dim0_start < dim_size(0)`
298 Tensor SubSlice(int64 index) const;
299
300 /// \brief Parse `other` and construct the tensor.
301
302 /// Returns `true` iff the parsing succeeds. If the parsing fails,
303 /// the state of `*this` is unchanged.
304 bool FromProto(const TensorProto& other) TF_MUST_USE_RESULT;
305 bool FromProto(Allocator* a, const TensorProto& other) TF_MUST_USE_RESULT;
306
307 /// \brief Fills in `proto` with `*this` tensor's content.
308 ///
309 /// `AsProtoField()` fills in the repeated field for `proto.dtype()`, while
310 /// `AsProtoTensorContent()` encodes the content in `proto.tensor_content()`
311 /// in a compact form.
312 void AsProtoField(TensorProto* proto) const;
313 void AsProtoTensorContent(TensorProto* proto) const;
314
315 /// \brief Return the tensor data as an `Eigen::Tensor` with the type and
316 /// sizes of this `Tensor`.
317 ///
318 /// Use these methods when you know the data type and the number of
319 /// dimensions of the Tensor and you want an `Eigen::Tensor`
320 /// automatically sized to the `Tensor` sizes. The implementation check
321 /// fails if either type or sizes mismatch.
322 ///
323 /// Example:
324 ///
325 /// ```c++
326 ///
327 /// typedef float T;
328 /// Tensor my_mat(...built with Shape{rows: 3, cols: 5}...);
329 /// auto mat = my_mat.matrix<T>(); // 2D Eigen::Tensor, 3 x 5.
330 /// auto mat = my_mat.tensor<T, 2>(); // 2D Eigen::Tensor, 3 x 5.
331 /// auto vec = my_mat.vec<T>(); // CHECK fails as my_mat is 2D.
332 /// auto vec = my_mat.tensor<T, 3>(); // CHECK fails as my_mat is 2D.
333 /// auto mat = my_mat.matrix<int32>();// CHECK fails as type mismatch.
334 ///
335 /// ```
336 template <typename T>
vec()337 typename TTypes<T>::Vec vec() {
338 return tensor<T, 1>();
339 }
340
341 template <typename T>
matrix()342 typename TTypes<T>::Matrix matrix() {
343 return tensor<T, 2>();
344 }
345
346 template <typename T, size_t NDIMS>
347 typename TTypes<T, NDIMS>::Tensor tensor();
348
349 /// \brief Return the tensor data to an `Eigen::Tensor` with the
350 /// same size but a bitwise cast to the specified dtype `T`.
351 ///
352 /// Using a bitcast is useful for move and copy operations.
353 /// NOTE: this is the same as `tensor()` except a bitcast is allowed.
354 template <typename T, size_t NDIMS>
355 typename TTypes<T, NDIMS>::Tensor bit_casted_tensor();
356
357 /// \brief Return the tensor data to an `Eigen::Tensor` with the
358 /// last dimension elements converted into single elements of a larger type.
359 ///
360 /// For example, this is useful for kernels that can treat NCHW_VECT_C int8
361 /// tensors as NCHW int32 tensors. The sizeof(T) should equal the size of
362 /// the original element type * num elements in the original last dimension.
363 /// NDIMS should be 1 less than the original number of dimensions.
364 template <typename T, size_t NDIMS>
365 typename TTypes<T, NDIMS>::Tensor reinterpret_last_dimension();
366
367 /// \brief Return the tensor data as an `Eigen::Tensor` of the data type and a
368 /// specified shape.
369 ///
370 /// These methods allow you to access the data with the dimensions
371 /// and sizes of your choice. You do not need to know the number of
372 /// dimensions of the Tensor to call them. However, they `CHECK` that
373 /// the type matches and the dimensions requested creates an
374 /// `Eigen::Tensor` with the same number of elements as the tensor.
375 ///
376 /// Example:
377 ///
378 /// ```c++
379 ///
380 /// typedef float T;
381 /// Tensor my_ten(...built with Shape{planes: 4, rows: 3, cols: 5}...);
382 /// // 1D Eigen::Tensor, size 60:
383 /// auto flat = my_ten.flat<T>();
384 /// // 2D Eigen::Tensor 12 x 5:
385 /// auto inner = my_ten.flat_inner_dims<T>();
386 /// // 2D Eigen::Tensor 4 x 15:
387 /// auto outer = my_ten.shaped<T, 2>({4, 15});
388 /// // CHECK fails, bad num elements:
389 /// auto outer = my_ten.shaped<T, 2>({4, 8});
390 /// // 3D Eigen::Tensor 6 x 5 x 2:
391 /// auto weird = my_ten.shaped<T, 3>({6, 5, 2});
392 /// // CHECK fails, type mismatch:
393 /// auto bad = my_ten.flat<int32>();
394 ///
395 /// ```
396 template <typename T>
flat()397 typename TTypes<T>::Flat flat() {
398 return shaped<T, 1>({NumElements()});
399 }
400
401 template <typename T>
unaligned_flat()402 typename TTypes<T>::UnalignedFlat unaligned_flat() {
403 return unaligned_shaped<T, 1>({NumElements()});
404 }
405
406 /// Returns the data as an Eigen::Tensor with NDIMS dimensions, collapsing all
407 /// Tensor dimensions but the last NDIMS-1 into the first dimension of the
408 /// result. If NDIMS > dims() then leading dimensions of size 1 will be
409 /// added to make the output rank NDIMS.
410 template <typename T, size_t NDIMS = 2>
411 typename TTypes<T, NDIMS>::Tensor flat_inner_dims();
412
413 /// Returns the data as an Eigen::Tensor with NDIMS dimensions, collapsing all
414 /// Tensor dimensions but the first NDIMS-1 into the last dimension of the
415 /// result. If NDIMS > dims() then trailing dimensions of size 1 will be
416 /// added to make the output rank NDIMS.
417 template <typename T, size_t NDIMS = 2>
418 typename TTypes<T, NDIMS>::Tensor flat_outer_dims();
419
420 /// Returns the data as an Eigen::Tensor with NDIMS dimensions, collapsing the
421 /// first 'begin' Tensor dimensions into the first dimension of the result and
422 /// the Tensor dimensions of the last dims() - 'begin' - NDIMS into the last
423 /// dimension of the result. If 'begin' < 0 then the |'begin'| leading
424 /// dimensions of size 1 will be added. If 'begin' + NDIMS > dims() then
425 /// 'begin' + NDIMS - dims() trailing dimensions of size 1 will be added.
426 template <typename T, size_t NDIMS = 3>
427 typename TTypes<T, NDIMS>::Tensor flat_inner_outer_dims(int64 begin);
428
429 template <typename T, size_t NDIMS>
430 typename TTypes<T, NDIMS>::Tensor shaped(gtl::ArraySlice<int64> new_sizes);
431
432 /// \brief Return the tensor data to an `Eigen::Tensor` with the new
433 /// shape specified in `new_sizes` and cast to a new dtype `T`.
434 ///
435 /// Using a bitcast is useful for move and copy operations.
436 /// The allowed bitcast is the only difference from `shaped()`.
437 template <typename T, size_t NDIMS>
438 typename TTypes<T, NDIMS>::Tensor bit_casted_shaped(
439 gtl::ArraySlice<int64> new_sizes);
440
441 template <typename T, size_t NDIMS>
442 typename TTypes<T, NDIMS>::UnalignedTensor unaligned_shaped(
443 gtl::ArraySlice<int64> new_sizes);
444
445 /// \brief Return the Tensor data as a `TensorMap` of fixed size 1:
446 /// `TensorMap<TensorFixedSize<T, 1>>`.
447
448 /// Using `scalar()` allows the compiler to perform optimizations as
449 /// the size of the tensor is known at compile time.
450 template <typename T>
451 typename TTypes<T>::Scalar scalar();
452
453 /// Const versions of all the methods above.
454 template <typename T>
vec()455 typename TTypes<T>::ConstVec vec() const {
456 return tensor<T, 1>();
457 }
458
459 template <typename T>
matrix()460 typename TTypes<T>::ConstMatrix matrix() const {
461 return tensor<T, 2>();
462 }
463
464 template <typename T, size_t NDIMS>
465 typename TTypes<T, NDIMS>::ConstTensor tensor() const;
466
467 /// \brief Return the tensor data to an `Eigen::Tensor` with the
468 /// same size but a bitwise cast to the specified dtype `T`.
469 ///
470 /// Using a bitcast is useful for move and copy operations.
471 /// NOTE: this is the same as `tensor()` except a bitcast is allowed.
472 template <typename T, size_t NDIMS>
473 typename TTypes<T, NDIMS>::ConstTensor bit_casted_tensor() const;
474
475 /// \brief Return the tensor data to an `Eigen::Tensor` with the
476 /// last dimension elements converted into single elements of a larger type.
477 ///
478 /// For example, this is useful for kernels that can treat NCHW_VECT_C int8
479 /// tensors as NCHW int32 tensors. The sizeof(T) should equal the size of
480 /// the original element type * num elements in the original last dimension.
481 /// NDIMS should be 1 less than the original number of dimensions.
482 template <typename T, size_t NDIMS>
483 typename TTypes<T, NDIMS>::ConstTensor reinterpret_last_dimension() const;
484
485 template <typename T>
flat()486 typename TTypes<T>::ConstFlat flat() const {
487 return shaped<T, 1>({NumElements()});
488 }
489
490 template <typename T>
unaligned_flat()491 typename TTypes<T>::UnalignedConstFlat unaligned_flat() const {
492 return unaligned_shaped<T, 1>({NumElements()});
493 }
494
495 template <typename T, size_t NDIMS>
496 typename TTypes<T, NDIMS>::ConstTensor shaped(
497 gtl::ArraySlice<int64> new_sizes) const;
498
499 /// \brief Return the tensor data to an `Eigen::Tensor` with the new
500 /// shape specified in `new_sizes` and cast to a new dtype `T`.
501 ///
502 /// Using a bitcast is useful for move and copy operations.
503 /// The allowed bitcast is the only difference from `shaped()`.
504 template <typename T, size_t NDIMS>
505 typename TTypes<T, NDIMS>::ConstTensor bit_casted_shaped(
506 gtl::ArraySlice<int64> new_sizes) const;
507
508 template <typename T, size_t NDIMS>
509 typename TTypes<T, NDIMS>::UnalignedConstTensor unaligned_shaped(
510 gtl::ArraySlice<int64> new_sizes) const;
511
512 template <typename T>
513 typename TTypes<T>::ConstScalar scalar() const;
514
515 template <typename T, size_t NDIMS = 2>
516 typename TTypes<T, NDIMS>::ConstTensor flat_inner_dims() const;
517
518 template <typename T, size_t NDIMS = 2>
519 typename TTypes<T, NDIMS>::ConstTensor flat_outer_dims() const;
520
521 template <typename T, size_t NDIMS = 3>
522 typename TTypes<T, NDIMS>::ConstTensor flat_inner_outer_dims(
523 int64 begin) const;
524
525 /// Render the first `max_entries` values in `*this` into a string.
526 string SummarizeValue(int64 max_entries, bool print_v2 = false) const;
527
528 /// A human-readable summary of the tensor suitable for debugging.
529 // `num_values` is the number of actual data values in the tensor
530 // included in the message. If the tensor might be resident in
531 // GPU/TPU memory use DeviceSafeDebugString instead.
532 string DebugString(int num_values) const;
DebugString()533 string DebugString() const { return DebugString(3); }
534
535 // Variant of DebugString() that should be used for possibly non-CPU tensors.
536 // If the tensor is not resident on CPU, we can't read its values as
537 // DebugString() does.
538 string DeviceSafeDebugString() const;
539
540 /// Fill in the `TensorDescription` proto with metadata about the
541 /// tensor that is useful for monitoring and debugging.
542 void FillDescription(TensorDescription* description) const;
543
544 /// \brief Returns a `StringPiece` mapping the current tensor's buffer.
545 ///
546 /// The returned `StringPiece` may point to memory location on devices
547 /// that the CPU cannot address directly.
548 ///
549 /// NOTE: The underlying tensor buffer is refcounted, so the lifetime
550 /// of the contents mapped by the `StringPiece` matches the lifetime of
551 /// the buffer; callers should arrange to make sure the buffer does
552 /// not get destroyed while the `StringPiece` is still used.
553 ///
554 /// REQUIRES: `DataTypeCanUseMemcpy(dtype())`.
555 StringPiece tensor_data() const;
556
557 /// Copy the other tensor into this tensor, reshape it and reinterpret the
558 /// buffer's datatype. If Status::OK() is returned, the two tensors now share
559 /// the same underlying storage.
560 ///
561 /// This call requires that the `other` tensor and the given type and shape
562 /// are "compatible" (i.e. they occupy the same number of bytes).
563 ///
564 /// Specifically:
565 ///
566 /// shape.num_elements() * DataTypeSize(type)
567 ///
568 /// must equal
569 ///
570 /// other.num_elements() * DataTypeSize(other.dtype())
571 ///
572 /// In addition, this function requires:
573 /// * DataTypeSize(other.dtype()) != 0
574 /// * DataTypeSize(type) != 0
575 ///
576 /// If any of the requirements are not met, errors::InvalidArgument is
577 /// returned.
578 Status BitcastFrom(const Tensor& other, DataType dtype,
579 const TensorShape& shape);
580
581 /// Like BitcastFrom, but CHECK fails if any preconditions are not met.
582 ///
583 /// Deprecated. Use BitcastFrom instead and check the returned Status.
UnsafeCopyFromInternal(const Tensor & other,DataType dtype,const TensorShape & shape)584 void UnsafeCopyFromInternal(const Tensor& other, DataType dtype,
585 const TensorShape& shape) {
586 TF_CHECK_OK(BitcastFrom(other, dtype, shape));
587 }
588
589 private:
590 // Returns true if the refcount on buf_ and any possible underlying root
591 // buffer is one.
592 bool RefCountIsOne() const;
593 void CheckType(DataType expected_dtype) const;
594 void CheckTypeAndIsAligned(DataType expected_dtype) const;
595 void CheckIsAlignedAndSingleElement() const;
set_dtype(DataType t)596 void set_dtype(DataType t) { shape_.set_data_type(t); }
597
598 // TensorShape's InlineVector.
599 static gtl::InlinedVector<int64, 4> ComputeFlatInnerDims(
600 gtl::ArraySlice<int64> orig, int64 num_out_dims);
601 static gtl::InlinedVector<int64, 4> ComputeFlatOuterDims(
602 gtl::ArraySlice<int64> orig, int64 num_out_dims);
603
604 TensorShape shape_;
605 TensorBuffer* buf_;
606
607 friend class DMAHelper;
608 friend class TensorCApi;
609 friend class TensorReference; // For access to buf_
610 friend class VariableOp; // For access to set_shape
611 friend class AutoReloadVariableOp; // For access to set_shape
612 friend class TensorTestHelper; // For access to set_shape
613 friend class CastOpBase; // For access to set_dtype;
614 friend class OpKernelContext; // For access to RefCountIsOne().
615 friend class ScopedAllocator; // For access to buf_.
616 friend class XlaTensor; // For access to RefCountIsOne().
617 friend class XlaTensorBuffer; // For access to the private constructor taking
618 // the buffer
619 friend class Var;
620 template <typename Device, typename T>
621 friend class AssignVariableOp; // For access to RefCountIsOne().
622 template <typename Device, typename T>
623 friend Status PrepareToUpdateVariable(
624 OpKernelContext* ctx, Tensor* tensor,
625 bool copy_on_read_mode); // For access to RefCountIsOne().
626 template <typename Device, typename T>
627 friend Status EnsureSparseVariableAccess(
628 OpKernelContext* ctx, Var* var); // For access to RefCountIsOne().
629 friend Status batch_util::CopyElementToSlice(
630 Tensor element, Tensor* parent,
631 int64 index); // For access to RefCountIsOne().
632 friend Status batch_util::MaybeMoveSliceToElement(
633 Tensor* parent, Tensor* element,
634 int64 index); // For access to RefCountIsOne().
635
636 friend class NumpyTensorBuffer; // For access to the private constructor
637 // taking the buffer.
638
639 // Creates a tensor with the input datatype, shape and buf.
640 //
641 // Acquires a ref on buf that belongs to this Tensor.
642 Tensor(DataType type, const TensorShape& shape, TensorBuffer* buf);
643
644 bool CanUseDMA() const;
645
646 // Only needed by variable op to set the shape of an uninitialized
647 // Tensor.
648 // TODO: Remove this when we have a better story for detecting
649 // uninitialized tensors.
set_shape(const TensorShape & shape)650 void set_shape(const TensorShape& shape) {
651 DataType dt = dtype();
652 shape_ = shape;
653 set_dtype(dt);
654 }
655
656 void CopyFromInternal(const Tensor& other, const TensorShape& shape);
657
658 template <typename T>
659 T* base() const;
660
661 template <size_t NDIMS>
662 void FillDimsAndValidateCompatibleShape(
663 gtl::ArraySlice<int64> new_sizes,
664 Eigen::array<Eigen::DenseIndex, NDIMS>* dims) const;
665
666 template <typename T, size_t NDIMS>
667 void FillDimsAndValidateCompatibleShape(
668 gtl::ArraySlice<int64> new_sizes,
669 Eigen::array<Eigen::DenseIndex, NDIMS>* dims) const;
670 };
671
672 // Implementation details
673
674 // START_SKIP_DOXYGEN
675
676 // Interface to access the raw ref-counted data buffer.
677 class TensorBuffer : public core::RefCounted {
678 public:
TensorBuffer(void * data_ptr)679 explicit TensorBuffer(void* data_ptr) : data_(data_ptr) {}
~TensorBuffer()680 ~TensorBuffer() override {}
681
682 // data() points to a memory region of size() bytes.
683 //
684 // NOTE(mrry): The `data()` method is not virtual for performance reasons.
685 // It can be called multiple times when the contents of a `Tensor` are
686 // accessed, and so making it non-virtual allows the body to be inlined.
data()687 void* data() const { return data_; }
688 virtual size_t size() const = 0;
689
690 // If this TensorBuffer is sub-buffer of another TensorBuffer,
691 // returns that TensorBuffer. Otherwise, returns this.
692 virtual TensorBuffer* root_buffer() = 0;
693
694 // Fill metadata about the allocation into the proto.
695 virtual void FillAllocationDescription(
696 AllocationDescription* proto) const = 0;
697
698 template <typename T>
base()699 T* base() const {
700 return reinterpret_cast<T*>(data());
701 }
702
703 // Whether this TensorBuffer owns the underlying memory.
OwnsMemory()704 virtual bool OwnsMemory() const { return true; }
705
706 private:
707 void* const data_;
708 };
709
710 template <typename T>
base()711 T* Tensor::base() const {
712 return buf_ == nullptr ? nullptr : buf_->base<T>();
713 }
714
715 template <typename T, size_t NDIMS>
tensor()716 typename TTypes<T, NDIMS>::Tensor Tensor::tensor() {
717 CheckTypeAndIsAligned(DataTypeToEnum<T>::v());
718 return typename TTypes<T, NDIMS>::Tensor(base<T>(),
719 shape().AsEigenDSizes<NDIMS>());
720 }
721
722 template <typename T, size_t NDIMS>
tensor()723 typename TTypes<T, NDIMS>::ConstTensor Tensor::tensor() const {
724 CheckTypeAndIsAligned(DataTypeToEnum<T>::v());
725 return typename TTypes<T, NDIMS>::ConstTensor(base<const T>(),
726 shape().AsEigenDSizes<NDIMS>());
727 }
728
729 template <typename T, size_t NDIMS>
bit_casted_tensor()730 typename TTypes<T, NDIMS>::Tensor Tensor::bit_casted_tensor() {
731 CHECK(IsAligned());
732 return typename TTypes<T, NDIMS>::Tensor(base<T>(),
733 shape().AsEigenDSizes<NDIMS>());
734 }
735
736 template <typename T, size_t NDIMS>
bit_casted_tensor()737 typename TTypes<T, NDIMS>::ConstTensor Tensor::bit_casted_tensor() const {
738 CHECK(IsAligned());
739 return typename TTypes<T, NDIMS>::ConstTensor(base<const T>(),
740 shape().AsEigenDSizes<NDIMS>());
741 }
742
743 template <typename T, size_t NDIMS>
reinterpret_last_dimension()744 typename TTypes<T, NDIMS>::Tensor Tensor::reinterpret_last_dimension() {
745 if (NDIMS == dims()) {
746 return tensor<T, NDIMS>();
747 }
748 CHECK(IsAligned());
749 CHECK_EQ(NDIMS, dims() - 1);
750 CHECK_EQ(sizeof(T), shape_.dim_sizes()[NDIMS] * DataTypeSize(dtype()));
751 Eigen::array<Eigen::DenseIndex, NDIMS> dims;
752 for (int d = 0; d < NDIMS; ++d) {
753 dims[d] = shape_.dim_sizes()[d];
754 }
755 return typename TTypes<T, NDIMS>::Tensor(base<T>(), dims);
756 }
757
758 template <typename T, size_t NDIMS>
reinterpret_last_dimension()759 typename TTypes<T, NDIMS>::ConstTensor Tensor::reinterpret_last_dimension()
760 const {
761 if (NDIMS == dims()) {
762 return tensor<T, NDIMS>();
763 }
764 CHECK(IsAligned());
765 CHECK_EQ(NDIMS, dims() - 1);
766 CHECK_EQ(sizeof(T), shape_.dim_sizes()[NDIMS] * DataTypeSize(dtype()));
767 Eigen::array<Eigen::DenseIndex, NDIMS> dims;
768 for (int d = 0; d < NDIMS; ++d) {
769 dims[d] = shape_.dim_sizes()[d];
770 }
771 return typename TTypes<T, NDIMS>::ConstTensor(base<const T>(), dims);
772 }
773
774 template <size_t NDIMS>
FillDimsAndValidateCompatibleShape(gtl::ArraySlice<int64> new_sizes,Eigen::array<Eigen::DenseIndex,NDIMS> * dims)775 void Tensor::FillDimsAndValidateCompatibleShape(
776 gtl::ArraySlice<int64> new_sizes,
777 Eigen::array<Eigen::DenseIndex, NDIMS>* dims) const {
778 CHECK_EQ(NDIMS, new_sizes.size());
779 int64 new_num_elements = 1;
780 for (size_t d = 0; d < NDIMS; d++) {
781 new_num_elements *= new_sizes[d];
782 (*dims)[d] = new_sizes[d];
783 }
784 CHECK_EQ(new_num_elements, NumElements());
785 }
786
787 template <typename T, size_t NDIMS>
FillDimsAndValidateCompatibleShape(gtl::ArraySlice<int64> new_sizes,Eigen::array<Eigen::DenseIndex,NDIMS> * dims)788 void Tensor::FillDimsAndValidateCompatibleShape(
789 gtl::ArraySlice<int64> new_sizes,
790 Eigen::array<Eigen::DenseIndex, NDIMS>* dims) const {
791 CHECK_EQ(NDIMS, new_sizes.size());
792 int64 new_num_elements = 1;
793 for (size_t d = 0; d < NDIMS; d++) {
794 new_num_elements *= new_sizes[d];
795 (*dims)[d] = new_sizes[d];
796 }
797 const int element_size = DataTypeSize(BaseType(dtype()));
798 if (element_size > 0) {
799 CHECK_EQ(new_num_elements * sizeof(T), NumElements() * element_size);
800 } else {
801 // DataTypeSize() returns 0 for some data types. In this case, assume that T
802 // has the same size as the buffer type.
803 // NOTE: If we can be sure that DataTypeSize() does not return 0 for all POD
804 // types, then we should check DataTypeToEnum<T>::v() == dtype(). Or simply
805 // check if `element_size > 0` to err when bit cast is attempted on Tensor
806 // of unknown data type size.
807 CHECK_EQ(new_num_elements, NumElements());
808 }
809 }
810
811 template <typename T, size_t NDIMS>
shaped(gtl::ArraySlice<int64> new_sizes)812 typename TTypes<T, NDIMS>::Tensor Tensor::shaped(
813 gtl::ArraySlice<int64> new_sizes) {
814 CheckTypeAndIsAligned(DataTypeToEnum<T>::v());
815 Eigen::array<Eigen::DenseIndex, NDIMS> dims;
816 FillDimsAndValidateCompatibleShape(new_sizes, &dims);
817 return typename TTypes<T, NDIMS>::Tensor(base<T>(), dims);
818 }
819
820 template <typename T, size_t NDIMS>
bit_casted_shaped(gtl::ArraySlice<int64> new_sizes)821 typename TTypes<T, NDIMS>::Tensor Tensor::bit_casted_shaped(
822 gtl::ArraySlice<int64> new_sizes) {
823 CHECK(IsAligned());
824 Eigen::array<Eigen::DenseIndex, NDIMS> dims;
825 FillDimsAndValidateCompatibleShape<T>(new_sizes, &dims);
826 return typename TTypes<T, NDIMS>::Tensor(base<T>(), dims);
827 }
828
829 template <typename T, size_t NDIMS>
unaligned_shaped(gtl::ArraySlice<int64> new_sizes)830 typename TTypes<T, NDIMS>::UnalignedTensor Tensor::unaligned_shaped(
831 gtl::ArraySlice<int64> new_sizes) {
832 CheckType(DataTypeToEnum<T>::v());
833 Eigen::array<Eigen::DenseIndex, NDIMS> dims;
834 FillDimsAndValidateCompatibleShape(new_sizes, &dims);
835 return typename TTypes<T, NDIMS>::UnalignedTensor(base<T>(), dims);
836 }
837
838 template <typename T, size_t NDIMS>
shaped(gtl::ArraySlice<int64> new_sizes)839 typename TTypes<T, NDIMS>::ConstTensor Tensor::shaped(
840 gtl::ArraySlice<int64> new_sizes) const {
841 CheckType(DataTypeToEnum<T>::v());
842 CHECK(IsAligned());
843 Eigen::array<Eigen::DenseIndex, NDIMS> dims;
844 FillDimsAndValidateCompatibleShape(new_sizes, &dims);
845 return typename TTypes<T, NDIMS>::ConstTensor(base<T>(), dims);
846 }
847
848 template <typename T, size_t NDIMS>
bit_casted_shaped(gtl::ArraySlice<int64> new_sizes)849 typename TTypes<T, NDIMS>::ConstTensor Tensor::bit_casted_shaped(
850 gtl::ArraySlice<int64> new_sizes) const {
851 CHECK(IsAligned());
852 Eigen::array<Eigen::DenseIndex, NDIMS> dims;
853 FillDimsAndValidateCompatibleShape<T>(new_sizes, &dims);
854 return typename TTypes<T, NDIMS>::ConstTensor(base<T>(), dims);
855 }
856
857 template <typename T, size_t NDIMS>
unaligned_shaped(gtl::ArraySlice<int64> new_sizes)858 typename TTypes<T, NDIMS>::UnalignedConstTensor Tensor::unaligned_shaped(
859 gtl::ArraySlice<int64> new_sizes) const {
860 CheckType(DataTypeToEnum<T>::v());
861 Eigen::array<Eigen::DenseIndex, NDIMS> dims;
862 FillDimsAndValidateCompatibleShape(new_sizes, &dims);
863 return typename TTypes<T, NDIMS>::UnalignedConstTensor(base<T>(), dims);
864 }
865
866 template <typename T>
scalar()867 typename TTypes<T>::Scalar Tensor::scalar() {
868 CheckIsAlignedAndSingleElement();
869 return typename TTypes<T>::Scalar(base<T>());
870 }
871
872 template <typename T>
scalar()873 typename TTypes<T>::ConstScalar Tensor::scalar() const {
874 CheckIsAlignedAndSingleElement();
875 return typename TTypes<T>::ConstScalar(base<T>());
876 }
877
878 template <typename T, size_t NDIMS>
flat_inner_dims()879 typename TTypes<T, NDIMS>::Tensor Tensor::flat_inner_dims() {
880 return shaped<T, NDIMS>(ComputeFlatInnerDims(shape_.dim_sizes(), NDIMS));
881 }
882
883 template <typename T, size_t NDIMS>
flat_outer_dims()884 typename TTypes<T, NDIMS>::Tensor Tensor::flat_outer_dims() {
885 return shaped<T, NDIMS>(ComputeFlatOuterDims(shape_.dim_sizes(), NDIMS));
886 }
887
888 template <typename T, size_t NDIMS>
flat_inner_outer_dims(int64 begin)889 typename TTypes<T, NDIMS>::Tensor Tensor::flat_inner_outer_dims(int64 begin) {
890 gtl::InlinedVector<int64, 4> flat_outer =
891 ComputeFlatOuterDims(shape_.dim_sizes(), begin + NDIMS);
892 return shaped<T, NDIMS>(ComputeFlatInnerDims(flat_outer, NDIMS));
893 }
894
895 template <typename T, size_t NDIMS>
flat_inner_dims()896 typename TTypes<T, NDIMS>::ConstTensor Tensor::flat_inner_dims() const {
897 return shaped<T, NDIMS>(ComputeFlatInnerDims(shape_.dim_sizes(), NDIMS));
898 }
899
900 template <typename T, size_t NDIMS>
flat_outer_dims()901 typename TTypes<T, NDIMS>::ConstTensor Tensor::flat_outer_dims() const {
902 return shaped<T, NDIMS>(ComputeFlatOuterDims(shape_.dim_sizes(), NDIMS));
903 }
904
905 template <typename T, size_t NDIMS>
flat_inner_outer_dims(int64 begin)906 typename TTypes<T, NDIMS>::ConstTensor Tensor::flat_inner_outer_dims(
907 int64 begin) const {
908 gtl::InlinedVector<int64, 4> flat_outer =
909 ComputeFlatOuterDims(shape_.dim_sizes(), begin + NDIMS);
910 return shaped<T, NDIMS>(ComputeFlatInnerDims(flat_outer, NDIMS));
911 }
912
Tensor(const Tensor & other)913 inline Tensor::Tensor(const Tensor& other)
914 : shape_(other.shape()), buf_(other.buf_) {
915 if (buf_) buf_->Ref();
916 }
917
Tensor(Tensor && other)918 inline Tensor::Tensor(Tensor&& other)
919 : shape_(std::move(other.shape())), buf_(other.buf_) {
920 other.buf_ = nullptr;
921 }
922
923 class Tensor::HostScalarTensorBufferBase : public TensorBuffer {
924 public:
925 using TensorBuffer::TensorBuffer;
926 void FillAllocationDescription(AllocationDescription* proto) const final;
927 };
928
929 // A packed representation for a single scalar value of type `T`, and a
930 // `TensorBuffer` implementation that describes (and manages the lifetime of)
931 // that value.
932 template <typename T>
933 struct Tensor::ValueAndTensorBuffer {
934 class HostScalarTensorBuffer : public Tensor::HostScalarTensorBufferBase {
935 public:
HostScalarTensorBufferValueAndTensorBuffer936 HostScalarTensorBuffer(void* data) : HostScalarTensorBufferBase(data) {}
sizeValueAndTensorBuffer937 size_t size() const final { return sizeof(T); }
root_bufferValueAndTensorBuffer938 TensorBuffer* root_buffer() final { return this; }
939
940 // Override `operator delete` so that calling `delete this` in
941 // `core::Refcounted::Unref()` for an object of this type will free
942 // the enclosing `ValueAndTensorBuffer` for the tensor buffer.
943 //
944 // NOTE(mrry): The definition of this method must be outside the class
945 // definition in order to satisfy some compilers.
946 static void operator delete(void* ptr);
947
deleteValueAndTensorBuffer948 static void operator delete(void*, void*) {
949 // Some compilers require an overridden class-specific deallocation
950 // function, which will be called if placement `new` throws an
951 // exception.
952 }
953
954 private:
~HostScalarTensorBufferValueAndTensorBuffer955 ~HostScalarTensorBuffer() override { static_cast<T*>(data())->~T(); }
956 };
957
958 T value;
959 HostScalarTensorBuffer tensor_buffer;
960 };
961
962 /* static */
963 template <typename T>
delete(void * ptr)964 void Tensor::ValueAndTensorBuffer<T>::HostScalarTensorBuffer::operator delete(
965 void* ptr) {
966 // Use a dummy object to compute to offset of
967 // `ValueAndTensorBuffer::tensor_buffer`, because `offsetof()` is not
968 // necessarily defined on this non-POD type (until C++17).
969 //
970 // NOTE(mrry): Using `sizeof(Tensor::ValueAndTensorBuffer<T>)` here requires
971 // us to define this method outside the class definition, so that it is not
972 // considered an incomplete type.
973 typename std::aligned_storage<sizeof(Tensor::ValueAndTensorBuffer<T>),
974 alignof(Tensor::ValueAndTensorBuffer<T>)>::type
975 dummy_storage_;
976 Tensor::ValueAndTensorBuffer<T>* dummy_object =
977 reinterpret_cast<Tensor::ValueAndTensorBuffer<T>*>(&dummy_storage_);
978 intptr_t offset = reinterpret_cast<intptr_t>(&dummy_object->tensor_buffer) -
979 reinterpret_cast<intptr_t>(dummy_object);
980
981 port::AlignedFree(static_cast<char*>(ptr) - offset);
982 }
983
984 template <typename T>
Tensor(T value,host_scalar_tag tag)985 Tensor::Tensor(T value, host_scalar_tag tag) {
986 auto* value_and_buf = static_cast<Tensor::ValueAndTensorBuffer<T>*>(
987 port::AlignedMalloc(sizeof(typename Tensor::ValueAndTensorBuffer<T>),
988 EIGEN_MAX_ALIGN_BYTES));
989 new (&value_and_buf->value) T(std::move(value));
990 new (&value_and_buf->tensor_buffer)
991 typename Tensor::ValueAndTensorBuffer<T>::HostScalarTensorBuffer(
992 value_and_buf);
993 buf_ = &value_and_buf->tensor_buffer;
994 set_dtype(DataTypeToEnum<T>::value);
995 }
996
997 inline Tensor& Tensor::operator=(Tensor&& other) {
998 // Avoid self-assignment, since we might destroy our underlying buffer.
999 if (&other != this) {
1000 shape_ = std::move(other.shape_);
1001 if (buf_) buf_->Unref();
1002 buf_ = other.buf_;
1003 other.buf_ = nullptr;
1004 }
1005 return *this;
1006 }
1007
1008 // END_SKIP_DOXYGEN
1009
1010 } // namespace tensorflow
1011
1012 #endif // TENSORFLOW_CORE_FRAMEWORK_TENSOR_H_
1013