|
7 | 7 | #define __XNNPACK_TEST_BUFFER_H_
|
8 | 8 |
|
9 | 9 | #include <algorithm>
|
| 10 | +#include <cassert> |
10 | 11 | #include <cstddef>
|
11 | 12 | #include <cstdint>
|
12 | 13 | #include <cstdlib>
|
13 | 14 | #include <initializer_list>
|
14 | 15 | #include <limits>
|
| 16 | +#include <memory> |
15 | 17 | #include <type_traits>
|
| 18 | +#include <vector> |
16 | 19 |
|
17 | 20 | #include "xnnpack.h"
|
18 | 21 | #include "xnnpack/common.h"
|
@@ -46,6 +49,10 @@ class NumericLimits<quantized<T>> {
|
46 | 49 | }
|
47 | 50 | };
|
48 | 51 |
|
| 52 | +struct PaddingBytes { |
| 53 | + size_t value; |
| 54 | +}; |
| 55 | + |
49 | 56 | // This is a container similar to std::vector, but it leaves the memory
|
50 | 57 | // uninitialized, supports alignment.
|
51 | 58 | // TODO: It would be good if this also managed padding in a way that allowed
|
@@ -99,8 +106,10 @@ class Buffer {
|
99 | 106 | using const_iterator = const T*;
|
100 | 107 |
|
101 | 108 | Buffer() : data_(nullptr), size_(0) {}
|
102 |
| - explicit Buffer(size_t size) |
103 |
| - : data_(reinterpret_cast<T*>(allocate(size * sizeof(T)))), size_(size) {} |
| 109 | + explicit Buffer(size_t size, PaddingBytes extra_bytes = {0}) |
| 110 | + : data_(reinterpret_cast<T*>( |
| 111 | + allocate(size * sizeof(T) + extra_bytes.value))), |
| 112 | + size_(size) {} |
104 | 113 | Buffer(size_t size, T value) : Buffer(size) {
|
105 | 114 | std::fill(begin(), end(), value);
|
106 | 115 | }
|
@@ -165,6 +174,205 @@ void fill_uniform_random_bits(T* data, size_t size, Rng& rng) {
|
165 | 174 | }
|
166 | 175 | }
|
167 | 176 |
|
| 177 | +// Returns {x[i] for i in perm} |
| 178 | +template <typename T> |
| 179 | +std::vector<T> permute(const std::vector<size_t>& perm, |
| 180 | + const std::vector<T>& x) { |
| 181 | + std::vector<T> result(perm.size()); |
| 182 | + for (size_t i = 0; i < perm.size(); ++i) { |
| 183 | + result[i] = x[perm[i]]; |
| 184 | + } |
| 185 | + return result; |
| 186 | +} |
| 187 | + |
| 188 | +// This stores a multi-dimensional array in a Buffer<T, Alignment> object |
| 189 | +// (above). The sizes of dimensions are `extent`s, the distance between elements |
| 190 | +// in a dimension in memory are `stride`s. |
| 191 | +// This buffer holds an std::shared_ptr to the underlying Buffer<T, Alignment> |
| 192 | +// objects, i.e. copies are shallow. |
| 193 | +template <typename T, size_t Alignment = alignof(T)> |
| 194 | +class Tensor { |
| 195 | + public: |
| 196 | + using value_type = T; |
| 197 | + using iterator = typename xnnpack::Buffer<T>::iterator; |
| 198 | + using const_iterator = typename xnnpack::Buffer<T>::const_iterator; |
| 199 | + |
| 200 | + using index_type = std::vector<size_t>; |
| 201 | + |
| 202 | + Tensor() = default; |
| 203 | + Tensor(const Tensor& other) = default; |
| 204 | + Tensor(Tensor&& other) = default; |
| 205 | + // Constructs an array with strides in descending order, with no |
| 206 | + // padding/alignment between dimensions. |
| 207 | + explicit Tensor(index_type extents, PaddingBytes extra_bytes = {0}) |
| 208 | + : extents_(std::move(extents)), strides_(extents_.size()) { |
| 209 | + size_t stride = 1; |
| 210 | + for (size_t i = rank(); i > 0; --i) { |
| 211 | + strides_[i - 1] = stride; |
| 212 | + stride *= extents_[i - 1]; |
| 213 | + } |
| 214 | + data_ = std::make_shared<Buffer<T, Alignment>>(stride, extra_bytes); |
| 215 | + begin_ = data_->begin(); |
| 216 | + end_ = data_->end(); |
| 217 | + } |
| 218 | + Tensor& operator=(const Tensor& other) = default; |
| 219 | + Tensor& operator=(Tensor&& other) = default; |
| 220 | + |
| 221 | + // Returns true if every stride is the product of the following extents, i.e. |
| 222 | + // the buffer can be interpreted as a flat array without considering the |
| 223 | + // strides. |
| 224 | + bool is_contiguous() const { |
| 225 | + size_t stride = 1; |
| 226 | + for (size_t i = rank(); i > 0; --i) { |
| 227 | + if (strides_[i - 1] != stride) { |
| 228 | + return false; |
| 229 | + } |
| 230 | + stride *= extents_[i - 1]; |
| 231 | + } |
| 232 | + return true; |
| 233 | + } |
| 234 | + |
| 235 | + const index_type& extents() const { return extents_; } |
| 236 | + const index_type& strides() const { return strides_; } |
| 237 | + size_t extent(size_t dim) const { return extents_[dim]; } |
| 238 | + size_t stride(size_t dim) const { return strides_[dim]; } |
| 239 | + |
| 240 | + size_t rank() const { return extents_.size(); } |
| 241 | + bool empty() const { return begin_ >= end_; } |
| 242 | + |
| 243 | + T* base() { return begin_; } |
| 244 | + const T* base() const { return begin_; } |
| 245 | + |
| 246 | + // Form a reference to an element at a particular index. |
| 247 | + T& operator()(const index_type& indices) { |
| 248 | + return *(begin_ + flat_offset(indices)); |
| 249 | + } |
| 250 | + const T& operator()(const index_type& indices) const { |
| 251 | + return *(begin_ + flat_offset(indices)); |
| 252 | + } |
| 253 | + |
| 254 | + template <typename... Args> |
| 255 | + T& operator()(Args... args) { |
| 256 | + return operator()(index_type{args...}); |
| 257 | + } |
| 258 | + template <typename... Args> |
| 259 | + const T& operator()(Args... args) const { |
| 260 | + return operator()(index_type{args...}); |
| 261 | + } |
| 262 | + |
| 263 | + // The following functions can only be used if `is_contiguous` is true. |
| 264 | + T* data() { |
| 265 | + assert(is_contiguous()); |
| 266 | + return begin_; |
| 267 | + } |
| 268 | + const T* data() const { |
| 269 | + assert(is_contiguous()); |
| 270 | + return begin_; |
| 271 | + } |
| 272 | + size_t size() const { |
| 273 | + assert(is_contiguous()); |
| 274 | + return data_->size(); |
| 275 | + } |
| 276 | + T* begin() { return data(); } |
| 277 | + T* end() { return end_; } |
| 278 | + const T* begin() const { return data(); } |
| 279 | + const T* end() const { return end_; } |
| 280 | + const T* cbegin() const { return data(); } |
| 281 | + const T* cend() const { return end_; } |
| 282 | + T& operator[](size_t index) { return data()[index]; } |
| 283 | + const T& operator[](size_t index) const { return data()[index]; } |
| 284 | + |
| 285 | + // This does not actually transpose any data in memory, it just changes the |
| 286 | + // strides. To implement the transpose in memory, use this, followed by |
| 287 | + // `make_copy` below. |
| 288 | + Tensor<T, Alignment> transpose(std::vector<size_t> perm) const { |
| 289 | + Tensor<T, Alignment> result(*this); |
| 290 | + result.extents_ = permute(perm, extents_); |
| 291 | + result.strides_ = permute(perm, strides_); |
| 292 | + return result; |
| 293 | + } |
| 294 | + |
| 295 | + // This uses the same rules for indexing as numpy, i.e. negative numbers are |
| 296 | + // offset are added to the extents. |
| 297 | + Tensor<T, Alignment> slice(std::vector<int64_t> begins, |
| 298 | + std::vector<int64_t> ends) const { |
| 299 | + assert(rank() == begins.size()); |
| 300 | + assert(rank() == ends.size()); |
| 301 | + |
| 302 | + Tensor<T, Alignment> result(*this); |
| 303 | + std::vector<size_t> offsets(rank()); |
| 304 | + for (size_t i = 0; i < rank(); ++i) { |
| 305 | + offsets[i] = begins[i] < 0 ? extents_[i] + begins[i] : begins[i]; |
| 306 | + result.extents_[i] = |
| 307 | + (ends[i] <= 0 ? extents_[i] + ends[i] : ends[i]) - offsets[i]; |
| 308 | + } |
| 309 | + |
| 310 | + result.begin_ = begin_ + flat_offset(offsets); |
| 311 | + result.end_ = result.begin_ + result.flat_offset(result.extents_); |
| 312 | + |
| 313 | + return result; |
| 314 | + } |
| 315 | + |
| 316 | + // Make a copy of the buffer. The result will be contiguous, i.e. the strides |
| 317 | + // of this buffer are lost when copying. |
| 318 | + Tensor<T, Alignment> deep_copy(PaddingBytes extra_bytes = {0}) const { |
| 319 | + Tensor<T, Alignment> result(extents_, extra_bytes); |
| 320 | + copy_impl(rank(), extents_.data(), strides_.data(), base(), |
| 321 | + result.strides_.data(), result.base()); |
| 322 | + return result; |
| 323 | + } |
| 324 | + |
| 325 | + private: |
| 326 | + static void copy_impl(size_t rank, const size_t* extents, |
| 327 | + const size_t* src_strides, const T* src, |
| 328 | + const size_t* dst_strides, T* dst) { |
| 329 | + if (rank == 0) { |
| 330 | + *dst = *src; |
| 331 | + return; |
| 332 | + } else { |
| 333 | + --rank; |
| 334 | + size_t extent = *extents++; |
| 335 | + size_t src_stride = *src_strides++; |
| 336 | + size_t dst_stride = *dst_strides++; |
| 337 | + if (rank == 0 && src_stride == 1 && dst_stride == 1) { |
| 338 | + std::copy_n(src, extent, dst); |
| 339 | + } else { |
| 340 | + for (size_t i = 0; i < extent; ++i) { |
| 341 | + copy_impl(rank, extents, src_strides, src, dst_strides, dst); |
| 342 | + src += src_stride; |
| 343 | + dst += dst_stride; |
| 344 | + } |
| 345 | + } |
| 346 | + } |
| 347 | + } |
| 348 | + |
| 349 | + // Compute the offset of an index from the pointer to element 0. |
| 350 | + size_t flat_offset(const index_type& indices) const { |
| 351 | + assert(indices.size() == rank()); |
| 352 | + size_t result = 0; |
| 353 | + for (size_t i = 0; i < rank(); ++i) { |
| 354 | + result += strides_[i] * indices[i]; |
| 355 | + } |
| 356 | + return result; |
| 357 | + } |
| 358 | + |
| 359 | + index_type extents_; |
| 360 | + index_type strides_; |
| 361 | + std::shared_ptr<xnnpack::Buffer<T, Alignment>> data_; |
| 362 | + T* begin_ = nullptr; |
| 363 | + T* end_ = nullptr; |
| 364 | +}; |
| 365 | + |
| 366 | +template <typename Rng> |
| 367 | +std::vector<size_t> random_shape(Rng& rng, size_t rank, size_t min_dim = 1, |
| 368 | + size_t max_dim = 9) { |
| 369 | + std::vector<size_t> shape(rank); |
| 370 | + for (size_t i = 0; i < rank; ++i) { |
| 371 | + shape[i] = (rng() % (max_dim - min_dim + 1)) + min_dim; |
| 372 | + } |
| 373 | + return shape; |
| 374 | +} |
| 375 | + |
168 | 376 | }; // namespace xnnpack
|
169 | 377 |
|
170 | 378 | #endif // __XNNPACK_TEST_BUFFER_H_
|
0 commit comments