| // Copyright 2022 The IREE Authors |
| // |
| // Licensed under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| #include "iree/tooling/numpy_io.h" |
| |
| #include "iree/async/util/proactor_pool.h" |
| #include "iree/base/threading/numa.h" |
| #include "iree/io/memory_stream.h" |
| #include "iree/io/vec_stream.h" |
| #include "iree/testing/gtest.h" |
| #include "iree/testing/status_matchers.h" |
| #include "iree/tooling/device_util.h" |
| #include "iree/tooling/testdata/npy/npy_files.h" |
| |
| namespace iree { |
| namespace { |
| |
| using iree::testing::status::IsOk; |
| using iree::testing::status::StatusIs; |
| using ::testing::ElementsAreArray; |
| |
| using StreamPtr = |
| std::unique_ptr<iree_io_stream_t, void (*)(iree_io_stream_t*)>; |
| |
| class NumpyIOTest : public ::testing::Test { |
| protected: |
| virtual void SetUp() { |
| iree_async_proactor_pool_t* proactor_pool = nullptr; |
| IREE_ASSERT_OK(iree_async_proactor_pool_create( |
| iree_numa_node_count(), /*node_ids=*/nullptr, |
| iree_async_proactor_pool_options_default(), iree_allocator_system(), |
| &proactor_pool)); |
| iree_hal_device_create_params_t create_params = |
| iree_hal_device_create_params_default(); |
| create_params.proactor_pool = proactor_pool; |
| iree_status_t status = iree_hal_create_device( |
| iree_hal_available_driver_registry(), IREE_SV("local-sync"), |
| &create_params, iree_allocator_system(), &device_); |
| iree_async_proactor_pool_release(proactor_pool); |
| if (iree_status_is_not_found(status)) { |
| fprintf(stderr, "Skipping test as 'local-sync' driver was not found:\n"); |
| iree_status_fprint(stderr, status); |
| iree_status_free(status); |
| GTEST_SKIP(); |
| } |
| device_allocator_ = iree_hal_device_allocator(device_); |
| } |
| |
| virtual void TearDown() { iree_hal_device_release(device_); } |
| |
| StreamPtr OpenInputFile(const char* name) { |
| const struct iree_file_toc_t* file_toc = iree_numpy_npy_files_create(); |
| for (size_t i = 0; i < iree_numpy_npy_files_size(); ++i) { |
| if (strcmp(file_toc[i].name, name) != 0) continue; |
| iree_io_stream_t* stream = NULL; |
| IREE_CHECK_OK(iree_io_memory_stream_wrap( |
| IREE_IO_STREAM_MODE_READABLE | IREE_IO_STREAM_MODE_SEEKABLE, |
| iree_make_byte_span((void*)file_toc[i].data, file_toc[i].size), |
| iree_io_stream_release_callback_null(), iree_allocator_system(), |
| &stream)); |
| return StreamPtr(stream, iree_io_stream_release); |
| } |
| return StreamPtr{nullptr, iree_io_stream_release}; |
| } |
| |
| StreamPtr OpenOutputFile(const char* name) { |
| iree_io_stream_t* stream = NULL; |
| IREE_CHECK_OK(iree_io_vec_stream_create( |
| IREE_IO_STREAM_MODE_READABLE | IREE_IO_STREAM_MODE_WRITABLE | |
| IREE_IO_STREAM_MODE_SEEKABLE, |
| // /*block_size=*/32 * 1024, |
| /*block_size=*/64, iree_allocator_system(), &stream)); |
| return StreamPtr(stream, iree_io_stream_release); |
| } |
| |
| iree_hal_device_t* device_ = nullptr; |
| iree_hal_allocator_t* device_allocator_ = nullptr; |
| }; |
| |
| template <typename T> |
| static void AssertBufferViewContents(iree_hal_buffer_view_t* buffer_view, |
| std::vector<iree_hal_dim_t> shape, |
| iree_hal_element_type_t element_type, |
| iree_hal_encoding_type_t encoding_type, |
| std::vector<T> expected_contents) { |
| ASSERT_EQ(iree_hal_buffer_view_shape_rank(buffer_view), shape.size()); |
| for (size_t i = 0; i < shape.size(); ++i) { |
| ASSERT_EQ(iree_hal_buffer_view_shape_dim(buffer_view, i), shape[i]); |
| } |
| ASSERT_EQ(iree_hal_buffer_view_element_type(buffer_view), element_type); |
| ASSERT_EQ(iree_hal_buffer_view_encoding_type(buffer_view), encoding_type); |
| |
| iree_hal_buffer_t* buffer = iree_hal_buffer_view_buffer(buffer_view); |
| ASSERT_EQ(iree_hal_buffer_byte_length(buffer), |
| expected_contents.size() * sizeof(T)); |
| |
| std::vector<T> actual_contents; |
| actual_contents.resize(expected_contents.size()); |
| IREE_ASSERT_OK(iree_hal_buffer_map_read(buffer, 0, actual_contents.data(), |
| actual_contents.size() * sizeof(T))); |
| |
| ASSERT_THAT(actual_contents, ElementsAreArray(expected_contents)); |
| } |
| |
| template <typename T> |
| static void LoadArrayAndAssertContents(iree_io_stream_t* stream, |
| iree_hal_device_t* device, |
| iree_hal_allocator_t* device_allocator, |
| std::vector<iree_hal_dim_t> shape, |
| iree_hal_element_type_t element_type, |
| iree_hal_encoding_type_t encoding_type, |
| std::vector<T> contents) { |
| iree_hal_buffer_params_t buffer_params = {}; |
| buffer_params.usage = IREE_HAL_BUFFER_USAGE_TRANSFER; |
| buffer_params.access = IREE_HAL_MEMORY_ACCESS_READ; |
| buffer_params.type = IREE_HAL_MEMORY_TYPE_HOST_LOCAL; |
| iree_hal_buffer_view_t* buffer_view = NULL; |
| IREE_ASSERT_OK(iree_numpy_npy_load_ndarray( |
| stream, IREE_NUMPY_NPY_LOAD_OPTION_DEFAULT, buffer_params, device, |
| device_allocator, &buffer_view)); |
| AssertBufferViewContents<T>(buffer_view, shape, element_type, encoding_type, |
| contents); |
| iree_hal_buffer_view_release(buffer_view); |
| } |
| |
| // Tests that an empty file returns EOF. |
| TEST_F(NumpyIOTest, LoadEmptyFile) { |
| auto stream = OpenInputFile("empty.npy"); |
| |
| // Should start at EOF - the file is empty. |
| ASSERT_TRUE(iree_io_stream_is_eos(stream.get())); |
| |
| // Try (and fail) to parse something from the empty file. |
| iree_hal_buffer_params_t buffer_params = {}; |
| buffer_params.usage = IREE_HAL_BUFFER_USAGE_TRANSFER; |
| buffer_params.access = IREE_HAL_MEMORY_ACCESS_READ; |
| buffer_params.type = IREE_HAL_MEMORY_TYPE_HOST_LOCAL; |
| iree_hal_buffer_view_t* buffer_view = NULL; |
| EXPECT_THAT(Status(iree_numpy_npy_load_ndarray( |
| stream.get(), IREE_NUMPY_NPY_LOAD_OPTION_DEFAULT, |
| buffer_params, device_, device_allocator_, &buffer_view)), |
| StatusIs(StatusCode::kOutOfRange)); |
| |
| // Should still be at EOF. |
| ASSERT_TRUE(iree_io_stream_is_eos(stream.get())); |
| } |
| |
| // Tests loading a single array from a file. |
| TEST_F(NumpyIOTest, LoadSingleArray) { |
| auto stream = OpenInputFile("single.npy"); |
| |
| // np.array([1.1, 2.2, 3.3], dtype=np.float32) |
| LoadArrayAndAssertContents<float>(stream.get(), device_, device_allocator_, |
| {3}, IREE_HAL_ELEMENT_TYPE_FLOAT_32, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1.1f, 2.2f, 3.3f}); |
| |
| // Should have hit EOF. |
| ASSERT_TRUE(iree_io_stream_is_eos(stream.get())); |
| } |
| |
| // Tests loading multiple arrays from a concatenated file. |
| TEST_F(NumpyIOTest, LoadMultipleArrays) { |
| auto stream = OpenInputFile("multiple.npy"); |
| |
| // np.array([1.1, 2.2, 3.3], dtype=np.float32) |
| LoadArrayAndAssertContents<float>(stream.get(), device_, device_allocator_, |
| {3}, IREE_HAL_ELEMENT_TYPE_FLOAT_32, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1.1f, 2.2f, 3.3f}); |
| |
| // np.array([[0, 1], [2, 3]], dtype=np.int32) |
| LoadArrayAndAssertContents<int32_t>(stream.get(), device_, device_allocator_, |
| {2, 2}, IREE_HAL_ELEMENT_TYPE_SINT_32, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {0, 1, 2, 3}); |
| |
| // np.array(42, dtype=np.int32) |
| LoadArrayAndAssertContents<int32_t>(stream.get(), device_, device_allocator_, |
| {}, IREE_HAL_ELEMENT_TYPE_SINT_32, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {42}); |
| |
| // Should have hit EOF. |
| ASSERT_TRUE(iree_io_stream_is_eos(stream.get())); |
| } |
| |
| // Tests loading arrays with various shapes. |
| TEST_F(NumpyIOTest, ArrayShapes) { |
| auto stream = OpenInputFile("array_shapes.npy"); |
| |
| // np.array(1, dtype=np.int8) |
| LoadArrayAndAssertContents<int8_t>(stream.get(), device_, device_allocator_, |
| {}, IREE_HAL_ELEMENT_TYPE_SINT_8, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1}); |
| |
| // np.array([], dtype=np.int8) |
| LoadArrayAndAssertContents<int8_t>( |
| stream.get(), device_, device_allocator_, {0}, |
| IREE_HAL_ELEMENT_TYPE_SINT_8, IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, {}); |
| |
| // np.array([1], dtype=np.int8) |
| LoadArrayAndAssertContents<int8_t>(stream.get(), device_, device_allocator_, |
| {1}, IREE_HAL_ELEMENT_TYPE_SINT_8, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1}); |
| |
| // np.array([[1], [2]], dtype=np.int8) |
| LoadArrayAndAssertContents<int8_t>(stream.get(), device_, device_allocator_, |
| {2, 1}, IREE_HAL_ELEMENT_TYPE_SINT_8, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1, 2}); |
| |
| // np.array([[0], [1], [2], [3], [4], [5], [6], [7]], dtype=np.int8) |
| LoadArrayAndAssertContents<int8_t>(stream.get(), device_, device_allocator_, |
| {8, 1}, IREE_HAL_ELEMENT_TYPE_SINT_8, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {0, 1, 2, 3, 4, 5, 6, 7}); |
| |
| // np.array([[1, 2], [3, 4]], dtype=np.int8) |
| LoadArrayAndAssertContents<int8_t>(stream.get(), device_, device_allocator_, |
| {2, 2}, IREE_HAL_ELEMENT_TYPE_SINT_8, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1, 2, 3, 4}); |
| |
| // np.array([[[1], [2]], [[3], [4]]], dtype=np.int8) |
| LoadArrayAndAssertContents<int8_t>(stream.get(), device_, device_allocator_, |
| {2, 2, 1}, IREE_HAL_ELEMENT_TYPE_SINT_8, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1, 2, 3, 4}); |
| |
| // Should have hit EOF. |
| ASSERT_TRUE(iree_io_stream_is_eos(stream.get())); |
| } |
| |
| // Tests loading arrays with various element types. |
| TEST_F(NumpyIOTest, ArrayTypes) { |
| auto stream = OpenInputFile("array_types.npy"); |
| |
| // np.array([True, False], dtype=np.bool_) |
| LoadArrayAndAssertContents<int8_t>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_BOOL_8, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1, 0}); |
| |
| // np.array([-1, 1], dtype=np.int8) |
| LoadArrayAndAssertContents<int8_t>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_SINT_8, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {-1, 1}); |
| |
| // np.array([-20000, 20000], dtype=np.int16) |
| LoadArrayAndAssertContents<int16_t>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_SINT_16, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {-20000, 20000}); |
| |
| // np.array([-2000000, 2000000], dtype=np.int32) |
| LoadArrayAndAssertContents<int32_t>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_SINT_32, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {-2000000, 2000000}); |
| |
| // np.array([-20000000000, 20000000000], dtype=np.int64) |
| LoadArrayAndAssertContents<int64_t>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_SINT_64, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {-20000000000, 20000000000}); |
| |
| // np.array([1, 255], dtype=np.uint8) |
| LoadArrayAndAssertContents<uint8_t>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_UINT_8, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1, 255}); |
| |
| // np.array([1, 65535], dtype=np.uint16) |
| LoadArrayAndAssertContents<uint16_t>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_UINT_16, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1, 65535}); |
| |
| // np.array([1, 4294967295], dtype=np.uint32) |
| LoadArrayAndAssertContents<uint32_t>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_UINT_32, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1, 4294967295u}); |
| |
| // np.array([1, 18446744073709551615], dtype=np.uint64) |
| LoadArrayAndAssertContents<uint64_t>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_UINT_64, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1, 18446744073709551615ull}); |
| |
| // np.array([-1.1, 1.1], dtype=np.float16) |
| LoadArrayAndAssertContents<uint16_t>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_FLOAT_16, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {0xBC66, 0x3C66}); |
| |
| // np.array([-1.1, 1.1], dtype=np.float32) |
| LoadArrayAndAssertContents<float>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_FLOAT_32, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {-1.1f, 1.1f}); |
| |
| // np.array([-1.1, 1.1], dtype=np.float64) |
| LoadArrayAndAssertContents<double>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_FLOAT_64, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {-1.1, 1.1}); |
| |
| // np.array([1 + 5j, 2 + 6j], dtype=np.complex64) |
| LoadArrayAndAssertContents<float>(stream.get(), device_, device_allocator_, |
| {2}, IREE_HAL_ELEMENT_TYPE_COMPLEX_FLOAT_64, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, |
| {1.0f, 5.0f, 2.0f, 6.0f}); |
| |
| // np.array([-1.1, 1.1], dtype=np.float64) |
| LoadArrayAndAssertContents<double>( |
| stream.get(), device_, device_allocator_, {2}, |
| IREE_HAL_ELEMENT_TYPE_COMPLEX_FLOAT_128, |
| IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR, {1.0, 5.0, 2.0, 6.0}); |
| |
| // Should have hit EOF. |
| ASSERT_TRUE(iree_io_stream_is_eos(stream.get())); |
| } |
| |
| static void RoundTripArrays(iree_io_stream_t* source_stream, |
| iree_io_stream_t* target_stream, |
| iree_hal_device_t* device, |
| iree_hal_allocator_t* device_allocator) { |
| while (!iree_io_stream_is_eos(source_stream)) { |
| iree_hal_buffer_params_t buffer_params = {}; |
| buffer_params.usage = IREE_HAL_BUFFER_USAGE_TRANSFER; |
| buffer_params.access = IREE_HAL_MEMORY_ACCESS_READ; |
| buffer_params.type = IREE_HAL_MEMORY_TYPE_HOST_LOCAL; |
| iree_hal_buffer_view_t* buffer_view = NULL; |
| IREE_ASSERT_OK(iree_numpy_npy_load_ndarray( |
| source_stream, IREE_NUMPY_NPY_LOAD_OPTION_DEFAULT, buffer_params, |
| device, device_allocator, &buffer_view)); |
| IREE_ASSERT_OK(iree_numpy_npy_save_ndarray( |
| target_stream, IREE_NUMPY_NPY_SAVE_OPTION_DEFAULT, buffer_view, |
| iree_hal_allocator_host_allocator(device_allocator))); |
| iree_hal_buffer_view_release(buffer_view); |
| } |
| } |
| |
| static void CompareStreams(iree_io_stream_t* source_stream, |
| iree_io_stream_t* target_stream) { |
| iree_io_stream_pos_t source_size = iree_io_stream_length(source_stream); |
| iree_io_stream_pos_t target_size = iree_io_stream_length(target_stream); |
| ASSERT_EQ(source_size, target_size) << "streams should have the same length"; |
| |
| IREE_ASSERT_OK( |
| iree_io_stream_seek(source_stream, IREE_IO_STREAM_SEEK_SET, 0)); |
| IREE_ASSERT_OK( |
| iree_io_stream_seek(target_stream, IREE_IO_STREAM_SEEK_SET, 0)); |
| |
| std::vector<uint8_t> source_data; |
| source_data.resize(source_size); |
| std::vector<uint8_t> target_data; |
| target_data.resize(target_size); |
| IREE_ASSERT_OK(iree_io_stream_read(source_stream, source_data.size(), |
| source_data.data(), NULL)); |
| IREE_ASSERT_OK(iree_io_stream_read(target_stream, target_data.size(), |
| target_data.data(), NULL)); |
| ASSERT_THAT(target_data, ElementsAreArray(source_data)); |
| |
| ASSERT_EQ(iree_io_stream_is_eos(source_stream), |
| iree_io_stream_is_eos(target_stream)) |
| << "streams should have the same length"; |
| } |
| |
| // Tests round-tripping a single array. |
| TEST_F(NumpyIOTest, RoundTripSingleArray) { |
| auto source_stream = OpenInputFile("single.npy"); |
| auto target_stream = OpenOutputFile("single_out.npy"); |
| RoundTripArrays(source_stream.get(), target_stream.get(), device_, |
| device_allocator_); |
| CompareStreams(source_stream.get(), target_stream.get()); |
| } |
| |
| // Tests round-tripping multiple array. |
| TEST_F(NumpyIOTest, RoundTripMultipleArrays) { |
| auto source_stream = OpenInputFile("multiple.npy"); |
| auto target_stream = OpenOutputFile("multiple_out.npy"); |
| RoundTripArrays(source_stream.get(), target_stream.get(), device_, |
| device_allocator_); |
| CompareStreams(source_stream.get(), target_stream.get()); |
| } |
| |
| // Tests round-tripping arrays with various shapes. |
| TEST_F(NumpyIOTest, RoundTripArrayShapes) { |
| auto source_stream = OpenInputFile("array_shapes.npy"); |
| auto target_stream = OpenOutputFile("array_shapes_out.npy"); |
| RoundTripArrays(source_stream.get(), target_stream.get(), device_, |
| device_allocator_); |
| CompareStreams(source_stream.get(), target_stream.get()); |
| } |
| |
| // Tests round-tripping arrays with various types. |
| TEST_F(NumpyIOTest, RoundTripArrayTypes) { |
| auto source_stream = OpenInputFile("array_types.npy"); |
| auto target_stream = OpenOutputFile("array_types_out.npy"); |
| RoundTripArrays(source_stream.get(), target_stream.get(), device_, |
| device_allocator_); |
| CompareStreams(source_stream.get(), target_stream.get()); |
| } |
| |
| } // namespace |
| } // namespace iree |