From 99c9b3aa88df32ba3cc1f126f2d64eb37d1e01c7 Mon Sep 17 00:00:00 2001 From: Brian Liu Date: Fri, 13 Dec 2024 20:54:52 +0000 Subject: [PATCH 01/87] #13127: Make TensorLayout::compute_physical_shard_shape public - Add support for ShardMode::PHYSICAL - Remove input logical_shard_shape arg - Refactor tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp to use tensor spec * Remove get_physical_shape and use implementation in tensor spec * Switch shard alignment to PageConfig or optional physical shard shape * Update RM examples since there is no longer a page alignment for width * Add example to test alignment to nearest page (and not full shard) for shards in last row/col --- .../tensor/test_sharding_with_alignment.cpp | 406 +++++++++++------- ttnn/cpp/ttnn/tensor/layout/tensor_layout.cpp | 70 +-- ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp | 7 +- 3 files changed, 307 insertions(+), 176 deletions(-) diff --git a/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp b/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp index 520d851700e2..466d602d9908 100644 --- a/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp +++ b/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp @@ -7,7 +7,7 @@ #include "tt_metal/common/logger.hpp" #include "tt_metal/common/work_split.hpp" #include "ttnn/async_runtime.hpp" -#include "ttnn/tensor/layout/tensor_layout.hpp" +#include "ttnn/tensor/tensor_spec.hpp" #include "ttnn/tensor/tensor.hpp" #include "ttnn/tensor/types.hpp" #include "ttnn_test_fixtures.hpp" @@ -81,33 +81,6 @@ void pretty_print_data_as_shards( std::cout << std::endl; } -Size get_physical_shape( - const ttnn::SimpleShape& shape, const Size& logical_shard_shape, const Size& physical_shard_shape) { - const int rank = static_cast(shape.rank()); - - size_t width = 1; - size_t height = 1; - - // Iterate dims in reverse order - // Even tensor of rank 0 or 1 - for (int i = -1; i >= -rank; --i) { - auto& dim = i == -1 ? width : height; - dim *= shape[i]; - } - - auto get_physical_size = [](auto original_size, auto logical_shard_size, auto physical_shard_size) { - auto num_shards = tt::div_up(original_size, logical_shard_size); - - return physical_shard_size * num_shards; - }; - - auto physical_height = get_physical_size(height, logical_shard_shape.height(), physical_shard_shape.height()); - auto physical_width = get_physical_size(width, logical_shard_shape.width(), physical_shard_shape.width()); - Size size{physical_height, physical_width}; - - return size; -} - using LogicalPhysicalIdxPairs = std::vector>; using LogicalPhysicalMapping = std::pair; std::vector compute_logical_to_physical_shards_mapping( @@ -145,21 +118,22 @@ std::vector compute_logical_to_physical_shards_mapping( }; std::vector convert_fp32_logical_data_to_physical_data( - const std::vector& data, - const ttnn::SimpleShape& shape, - const Size& logical_shard_shape, - const Size& physical_shard_shape) { + const std::vector& logical_data, const TensorSpec& tensor_spec) { + const auto& logical_shape = tensor_spec.logical_shape(); TT_FATAL( - data.size() == shape.volume(), - "Data size {} should be same as volume indicated by shape {}", - data.size(), - shape); - auto physical_size = get_physical_shape(shape, logical_shard_shape, physical_shard_shape); + logical_data.size() == logical_shape.volume(), + "Logical data size {} should be same as volume indicated by logical shape {}", + logical_data.size(), + logical_shape); - std::vector physical_data(physical_size.height() * physical_size.width(), 0); + const auto& logical_shard_shape = tensor_spec.tensor_layout().get_logical_shard_shape(); + const auto& physical_shard_shape = tensor_spec.tensor_layout().get_physical_shard_shape(); + const auto& physical_shape = tensor_spec.physical_shape(); - auto logical_2D_shape = flatten_to_2D(shape); - size_t physical_stride = physical_size.width(); + std::vector physical_data(physical_shape.height() * physical_shape.width(), 0); + + auto logical_2D_shape = flatten_to_2D(logical_shape); + size_t physical_stride = physical_shape.width(); const auto logical_physical_mapping = compute_logical_to_physical_shards_mapping( logical_2D_shape, logical_shard_shape, physical_shard_shape, physical_stride); @@ -170,36 +144,37 @@ std::vector convert_fp32_logical_data_to_physical_data( auto physical_idx_start = idx_pair[1]; for (size_t col = 0; col < cols; col++) { - physical_data[physical_idx_start + col] = data[logical_idx_start + col]; + physical_data[physical_idx_start + col] = logical_data[logical_idx_start + col]; } } } TT_FATAL( - physical_data.size() == physical_size.height() * physical_size.width(), - "Physical data size {} should be same as calculated physical size {}", + physical_data.size() == physical_shape.height() * physical_shape.width(), + "Physical data size {} should be same as volume indicated by physical shape {}", physical_data.size(), - physical_size); + physical_shape); return physical_data; }; std::vector convert_fp32_physical_data_to_logical_data( - const std::vector& physical_data, - const ttnn::SimpleShape& shape, - const Size& logical_shard_shape, - const Size& physical_shard_shape) { - auto physical_size = get_physical_shape(shape, logical_shard_shape, physical_shard_shape); + const std::vector& physical_data, const TensorSpec& tensor_spec) { + auto physical_shape = tensor_spec.physical_shape(); TT_FATAL( - physical_data.size() == physical_size.height() * physical_size.width(), - "Physical data size {} should be same as calculated physical size {}", + physical_data.size() == physical_shape.height() * physical_shape.width(), + "Physical data size {} should be same as volume indicated by physical shape {}", physical_data.size(), - physical_size); + physical_shape); - auto logical_2D_shape = flatten_to_2D(shape); - std::vector data(logical_2D_shape.height() * logical_2D_shape.width(), 0); + const auto& logical_shape = tensor_spec.logical_shape(); + const auto& logical_shard_shape = tensor_spec.tensor_layout().get_logical_shard_shape(); + const auto& physical_shard_shape = tensor_spec.tensor_layout().get_physical_shard_shape(); - size_t physical_stride = physical_size.width(); + auto logical_2D_shape = flatten_to_2D(logical_shape); + std::vector logical_data(logical_2D_shape.height() * logical_2D_shape.width(), 0); + + size_t physical_stride = physical_shape.width(); const auto logical_physical_mapping = compute_logical_to_physical_shards_mapping( logical_2D_shape, logical_shard_shape, physical_shard_shape, physical_stride); @@ -210,18 +185,18 @@ std::vector convert_fp32_physical_data_to_logical_data( auto physical_idx_start = idx_pair[1]; for (size_t col = 0; col < cols; col++) { - data[logical_idx_start + col] = physical_data[physical_idx_start + col]; + logical_data[logical_idx_start + col] = physical_data[physical_idx_start + col]; } } } TT_FATAL( - data.size() == shape.volume(), - "Data size {} should be same as volume indicated by shape {}", - data.size(), - shape); + logical_data.size() == logical_shape.volume(), + "Logical data size {} should be same as volume indicated by logical shape {}", + logical_data.size(), + logical_shape); - return data; + return logical_data; }; } // namespace @@ -229,14 +204,15 @@ std::vector convert_fp32_physical_data_to_logical_data( namespace { struct ShardWithAlignmentInputs { SimpleShape shape; - Size shard_shape; - Alignment shard_alignment; - std::vector data; + Size logical_shard_shape; + std::optional physical_shard_shape; + PageConfig page_config; + std::vector logical_data; }; struct ShardWithAlignmentExpected { Size physical_shard_shape; - Size physical_size; + Size physical_shape; std::vector physical_data; }; @@ -252,23 +228,41 @@ class ShardWithAlignmentTests : public ::testing::TestWithParam TensorLayout::compute_shard_spec_buffer(const ttn switch (shard_spec.mode) { case ShardMode::PHYSICAL: break; case ShardMode::LOGICAL: { - const auto& physical_shard_shape = compute_physical_shard_shape(shard_spec.shape); + const auto& physical_shard_shape = get_physical_shard_shape(); shard_spec.shape = physical_shard_shape; break; } @@ -190,29 +190,48 @@ size_t TensorLayout::compute_page_size_bytes(const Size& page_size) const { return page_config_.get_page_size_bytes(page_size, dtype_); } -Size TensorLayout::compute_physical_shard_shape(const Size& logical_shard_shape) const { +Size TensorLayout::get_logical_shard_shape() const { TT_FATAL( - memory_config_.shard_spec.has_value() and memory_config_.shard_spec.value().mode == ShardMode::LOGICAL, - "TensorLayout must be logically sharded for compute_physical_shard_shape!"); + memory_config_.shard_spec.has_value(), "Shard spec must have value for TensorLayout::get_logical_shard_shape!"); + + // Shape in shard spec will always represent logical shard shape in either mode + return Size(memory_config_.shard_spec.value().shape); +} + +Size TensorLayout::get_physical_shard_shape() const { + TT_FATAL( + memory_config_.shard_spec.has_value(), + "Shard spec must have value for TensorLayout::get_physical_shard_shape!"); const auto& shard_spec = memory_config_.shard_spec.value(); - // TODO: If physical_shard_shape is provided, alignment_ == physical_shard_shape is guaranteed (should we store - // physical_shard_shape instead?) - if (shard_spec.physical_shard_shape.has_value()) { - const auto& physical_shard_shape = shard_spec.physical_shard_shape.value(); + + auto compute_physical_shard_shape_for_logical_mode = [&]() -> Size { + // TODO: If physical_shard_shape is provided, alignment_ == physical_shard_shape is guaranteed (should we store + // physical_shard_shape instead?) + if (shard_spec.physical_shard_shape.has_value()) { + const auto& physical_shard_shape = shard_spec.physical_shard_shape.value(); + TT_FATAL( + physical_shard_shape[0] == alignment_[-2] and physical_shard_shape[1] == alignment_[-1], + "Alignment {} must be same as physical shard shape {} provided in shard spec!", + alignment_, + physical_shard_shape); + return physical_shard_shape; + } + + const auto& logical_shard_shape = Size(shard_spec.shape); + // TODO: Alignment is guaranteed to be rank 2 or less if tensor is sharded (remove validate?) + const int alignment_rank = static_cast(alignment_.size()); TT_FATAL( - physical_shard_shape[0] == alignment_[-2] and physical_shard_shape[1] == alignment_[-1], - "Alignment {} must be same as physical shard shape {} provided in shard spec!", - alignment_, - physical_shard_shape); - return physical_shard_shape; - } + alignment_rank <= 2, "Alignment {} must be rank 2 or less to compute physical shard shape", alignment_); + auto physical_shard_height = CMAKE_UNIQUE_NAMESPACE::round_up(logical_shard_shape.height(), alignment_[-2]); + auto physical_shard_width = CMAKE_UNIQUE_NAMESPACE::round_up(logical_shard_shape.width(), alignment_[-1]); + return Size{physical_shard_height, physical_shard_width}; + }; - // TODO: Alignment is guaranteed to be rank 2 or less if tensor is sharded (remove validate?) - const int alignment_rank = static_cast(alignment_.size()); - TT_FATAL(alignment_rank <= 2, "Alignment {} must be rank 2 or less to compute physical shard shape", alignment_); - auto physical_shard_height = CMAKE_UNIQUE_NAMESPACE::round_up(logical_shard_shape.height(), alignment_[-2]); - auto physical_shard_width = CMAKE_UNIQUE_NAMESPACE::round_up(logical_shard_shape.width(), alignment_[-1]); - return Size{physical_shard_height, physical_shard_width}; + switch (shard_spec.mode) { + case ShardMode::PHYSICAL: return shard_spec.shape; break; + case ShardMode::LOGICAL: return compute_physical_shard_shape_for_logical_mode(); break; + default: TT_THROW("Unsupported shard mode {} in get_physical_shard_shape!", shard_spec.mode); + } } Size TensorLayout::compute_physical_shape(const ttnn::SimpleShape& shape) const { @@ -230,8 +249,8 @@ Size TensorLayout::compute_physical_shape(const ttnn::SimpleShape& shape) const dim *= shape[i]; } - const auto& logical_shard_shape = Size(memory_config_.shard_spec.value().shape); - const auto& physical_shard_shape = compute_physical_shard_shape(logical_shard_shape); + const auto& logical_shard_shape = get_logical_shard_shape(); + const auto& physical_shard_shape = get_physical_shard_shape(); auto get_physical_size = [](auto original_size, auto logical_shard_size, auto physical_shard_size, auto alignment) -> uint32_t { @@ -284,12 +303,7 @@ Size TensorLayout::compute_physical_shape(const ttnn::SimpleShape& shape) const Size TensorLayout::compute_page_shape(const Size& physical_size) const { std::optional physical_shard_shape = std::nullopt; if (memory_config_.shard_spec.has_value()) { - const auto& shard_spec = memory_config_.shard_spec.value(); - switch (shard_spec.mode) { - case ShardMode::PHYSICAL: physical_shard_shape = shard_spec.shape; break; - case ShardMode::LOGICAL: physical_shard_shape = compute_physical_shard_shape(shard_spec.shape); break; - default: TT_THROW("Unsupported shard mode {} in compute_shard_spec_buffer!", shard_spec.mode); - } + physical_shard_shape = get_physical_shard_shape(); } return page_config_.get_page_shape(physical_size, dtype_, memory_config_, physical_shard_shape); diff --git a/ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp b/ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp index b2adf7b0b154..2e9b24cb03a2 100644 --- a/ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp +++ b/ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp @@ -55,6 +55,12 @@ class TensorLayout { // H is all dimensions except W multiplied and aligned to tile and shard height Size compute_physical_shape(const ttnn::SimpleShape& shape) const; + // Returns logical shard shape from shard spec shape + Size get_logical_shard_shape() const; + + // Returns physical shard shape based on ShardMode, shard shape, and alignment + Size get_physical_shard_shape() const; + TensorLayout with_memory_config(MemoryConfig memory_config) const { TensorLayout result = *this; result.memory_config_ = std::move(memory_config); @@ -79,7 +85,6 @@ class TensorLayout { Size compute_page_shape(const Size& physical_size) const; size_t compute_page_size_bytes(const Size& page_size) const; - Size compute_physical_shard_shape(const Size& logical_shard_shape) const; DataType dtype_ = DataType::BFLOAT16; PageConfig page_config_; From e3dbd25ee2b3ef3bf25d92697ac6ab6a5edb8cf4 Mon Sep 17 00:00:00 2001 From: Nour Ardo Date: Mon, 16 Dec 2024 11:38:31 -0500 Subject: [PATCH 02/87] Link Tensor.reshape to ttnn.reshape (#15669) ### Ticket #13745 ### Problem description Linking Tensor.reshape to ttnn.reshape. Adding Tensor.reshape as an experimental operation under tonne ### Checklist - [x] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12202038977 - [x] Blackhole Post commit (if applicable) https://github.com/tenstorrent/tt-metal/actions/runs/12204887897 - [ ] Model regression CI testing passes (if applicable) - [x] Device performance regression CI testing passes (if applicable) https://github.com/tenstorrent/tt-metal/actions/runs/12204897132 - [ ] New/Existing tests provide coverage for changes --- ...test_tilize_zero_padding_channels_last.cpp | 7 +- .../tensors/test_async_tensor_apis.cpp | 12 +- tests/tt_eager/tensors/test_copy_and_move.cpp | 7 +- tests/ttnn/unit_tests/test_reshape.py | 2 +- ttnn/CMakeLists.txt | 2 + ttnn/cpp/pybind11/pytensor.cpp | 11 +- .../operations/data_movement/fold/fold.cpp | 7 +- .../reshape_on_device/device/reshape_op.cpp | 5 +- .../reshape_on_device/reshape.cpp | 3 +- .../data_movement/reshape_view/reshape.cpp | 9 +- .../experimental/experimental_pybind.cpp | 6 + .../experimental/reshape/reshape.cpp | 161 ++++++++++++++++++ .../experimental/reshape/reshape.hpp | 26 +++ .../experimental/reshape/reshape_pybind.cpp | 79 +++++++++ .../experimental/reshape/reshape_pybind.hpp | 13 ++ .../device/moreh_getitem_rm_factory.cpp | 3 +- .../pool/global_avg_pool/global_avg_pool.cpp | 3 +- .../split_query_key_value_and_split_heads.cpp | 40 +++-- ttnn/cpp/ttnn/tensor/tensor.cpp | 5 - ttnn/cpp/ttnn/tensor/tensor.hpp | 2 - ttnn/cpp/ttnn/tensor/tensor_ops.cpp | 92 ---------- ttnn/cpp/ttnn/tensor/tensor_ops.hpp | 3 - 22 files changed, 355 insertions(+), 143 deletions(-) create mode 100644 ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp create mode 100644 ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp create mode 100644 ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.cpp create mode 100644 ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.hpp diff --git a/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp b/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp index e565c4d80269..3445d424d538 100644 --- a/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp +++ b/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp @@ -13,6 +13,8 @@ #include "ttnn/tensor/tensor.hpp" #include "ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp" #include "tt_metal/host_api.hpp" +#include "ttnn/operations/numpy/functions.hpp" +#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" using namespace tt; using namespace tt_metal; @@ -37,9 +39,8 @@ int main(int argc, char** argv) { //////////////////////////////////////////////////////////////////////////// ttnn::SimpleShape shape{1, 32, 61, 32}; // Allocates a DRAM buffer on device populated with values specified by initialize - Tensor a = ttnn::arange(/*start=*/0, /*stop=*/shape.volume(), /*step=*/1, DataType::BFLOAT16) - .reshape(shape) - .to(device); + Tensor a = + ttnn::experimental::view(ttnn::arange(/*start=*/0, /*stop=*/shape.volume(), /*step=*/1, DataType::BFLOAT16), shape).to(device); Tensor b = ttnn::tilize_with_zero_padding(a); Tensor c = b.cpu(); //////////////////////////////////////////////////////////////////////////// diff --git a/tests/tt_eager/tensors/test_async_tensor_apis.cpp b/tests/tt_eager/tensors/test_async_tensor_apis.cpp index 3ef44800178e..2791cbc7d537 100644 --- a/tests/tt_eager/tensors/test_async_tensor_apis.cpp +++ b/tests/tt_eager/tensors/test_async_tensor_apis.cpp @@ -18,7 +18,12 @@ #include "ttnn/operations/eltwise/binary/binary.hpp" #include "ttnn/operations/eltwise/unary/unary.hpp" -namespace tt::tt_metal { +#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" + +using namespace tt; +using namespace tt_metal; +using namespace constants; + namespace { using ::tt::constants::TILE_HEIGHT; @@ -58,7 +63,7 @@ TEST_F(DispatchFixture, TestTensorOwnershipSanity) { }, host_tensor.get_storage()); // Send tensor to device, read it back and copy it to empty tensor initialized by main thread - Tensor reshaped_tensor = host_tensor.reshape(ttnn::SimpleShape{1, 1, 32, 128}); + Tensor reshaped_tensor = ttnn::experimental::view(host_tensor, ttnn::SimpleShape{1, 1, 32, 128}); auto device_tensor = reshaped_tensor.to(Layout::TILE).to(device); auto thread_local_tensor = device_tensor.cpu().to(Layout::ROW_MAJOR); readback_tensor.set_storage(thread_local_tensor.get_storage()); @@ -285,7 +290,8 @@ TEST_F(DispatchFixture, TestTensorAsyncDataMovement) { }, host_tensor.get_storage()); - Tensor reshaped_tensor = host_tensor.reshape(ttnn::SimpleShape{1, 1, 32, tensor_stop / 32}); + Tensor reshaped_tensor = + ttnn::experimental::view(host_tensor, ttnn::SimpleShape{1, 1, 32, tensor_stop / 32}); auto device_tensor = reshaped_tensor.to(Layout::TILE).to(device); auto thread_local_tensor = device_tensor.cpu().to(Layout::ROW_MAJOR); log_info(LogTest, "Worker populating empty host readback_tensor"); diff --git a/tests/tt_eager/tensors/test_copy_and_move.cpp b/tests/tt_eager/tensors/test_copy_and_move.cpp index 5fb62254db1a..b8eabd74003a 100644 --- a/tests/tt_eager/tensors/test_copy_and_move.cpp +++ b/tests/tt_eager/tensors/test_copy_and_move.cpp @@ -11,6 +11,7 @@ #include "ttnn/tensor/tensor_impl.hpp" #include "tt_metal/host_api.hpp" #include "ttnn/operations/functions.hpp" +#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" using namespace tt; using namespace tt_metal; @@ -37,10 +38,8 @@ bool test_tensor_copy_semantics(Device* device) { pass &= dev_a_data == dev_a_copy_data; // host tensor updated with host tensor copy assignment - Tensor host_c = ttnn::arange(/*start=*/0, /*stop=*/tt_metal::compute_volume(single_tile_shape), /*step=*/1) - .reshape(single_tile_shape) - .to(Layout::TILE); - Tensor host_c_copy = ttnn::random::random(single_tile_shape).to(Layout::TILE); + Tensor host_c = ttnn::experimental::view(ttnn::arange(/*start=*/0, /*stop=*/tt_metal::compute_volume(single_tile_shape), /*step=*/1), single_tile_shape).to(Layout::TILE); + Tensor host_c_copy = ttnn::numpy::random::random(single_tile_shape).to(Layout::TILE); host_c_copy = host_c; auto host_c_data = owned_buffer::get_as(host_c); auto host_c_copy_data = owned_buffer::get_as(host_c_copy); diff --git a/tests/ttnn/unit_tests/test_reshape.py b/tests/ttnn/unit_tests/test_reshape.py index 823085fe6564..2d323473fe33 100644 --- a/tests/ttnn/unit_tests/test_reshape.py +++ b/tests/ttnn/unit_tests/test_reshape.py @@ -36,7 +36,7 @@ def test_reshape_sharded_rm(device, n, c, h, w): torch_input_tensor, layout=ttnn.ROW_MAJOR_LAYOUT, device=device, memory_config=sharded_mem_config ) - tt_output_tensor = tt_input_tensor.reshape(n, c, h * 2, w // 2) + tt_output_tensor = ttnn.experimental.view(tt_input_tensor, n, c, h * 2, w // 2) sharded_mem_config = ttnn.create_sharded_memory_config( tt_output_tensor.shape, diff --git a/ttnn/CMakeLists.txt b/ttnn/CMakeLists.txt index 0143fd2d24c1..5b20f6eba57a 100644 --- a/ttnn/CMakeLists.txt +++ b/ttnn/CMakeLists.txt @@ -563,6 +563,8 @@ set(ALL_TTNN_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/data_movement/expand/expand_pybind.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/data_movement/expand/device/expand_rm_program_factory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/data_movement/expand/device/expand_device_operation.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/experimental/reshape/reshape.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/experimental/reshape/reshape_pybind.cpp ) #Split src and python bindings diff --git a/ttnn/cpp/pybind11/pytensor.cpp b/ttnn/cpp/pybind11/pytensor.cpp index 68a507a2c4a9..3071f382e380 100644 --- a/ttnn/cpp/pybind11/pytensor.cpp +++ b/ttnn/cpp/pybind11/pytensor.cpp @@ -21,6 +21,10 @@ #include "ttnn/tensor/tensor_impl.hpp" #include "ttnn/tensor/tensor_ops.hpp" +#include "ttnn/common/constants.hpp" +#include "ttnn/operations/core/core.hpp" + + using namespace tt::tt_metal; namespace py = pybind11; @@ -1683,10 +1687,11 @@ void pytensor_module(py::module& m_tensor) { dtype = tt_tensor.get_dtype() )doc") + .def( "reshape", [](Tensor& self, int N, int C, int H, int W) { - return self.reshape(infer_dims_for_reshape(self, ttnn::SmallVector{N, C, H, W})); + return ttnn::reshape(self, infer_dims_for_reshape(self, ttnn::SmallVector{N, C, H, W})); }, R"doc( Reshapes TT tensor @@ -1697,7 +1702,7 @@ void pytensor_module(py::module& m_tensor) { )doc") .def( "reshape", - [](Tensor& self, const ttnn::Shape& shape) -> Tensor { return self.reshape(shape); }, + [](Tensor& self, const ttnn::Shape& shape) -> Tensor { return ttnn::reshape(self, shape); }, R"doc( Reshapes TT tensor @@ -1708,7 +1713,7 @@ void pytensor_module(py::module& m_tensor) { .def( "reshape", [](Tensor& self, const ttnn::SmallVector& shape) -> Tensor { - return self.reshape(infer_dims_for_reshape(self, shape)); + return ttnn::reshape(self, infer_dims_for_reshape(self, shape)); }, R"doc( Reshapes TT tensor diff --git a/ttnn/cpp/ttnn/operations/data_movement/fold/fold.cpp b/ttnn/cpp/ttnn/operations/data_movement/fold/fold.cpp index 754ea6e24b12..0a8a5253366c 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/fold/fold.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/fold/fold.cpp @@ -12,6 +12,7 @@ #include "ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.hpp" #include "ttnn/cpp/ttnn/operations/data_movement/pad/pad.hpp" #include "tt_metal/common/constants.hpp" +#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" #include "fold.hpp" @@ -221,7 +222,8 @@ std::vector fold_with_transpose_sharded_( // reshape n = tt_output_tensor.shape()[0], w = tt_output_tensor.shape()[1], c = tt_output_tensor.shape()[2], h = tt_output_tensor.shape()[3]; - tt_output_tensor = tt_output_tensor.reshape(ttnn::SimpleShape{n, (w / stride_w), (c * stride_w), h}); + tt_output_tensor = + ttnn::experimental::view(tt_output_tensor, ttnn::SimpleShape{n, (w / stride_w), (c * stride_w), h}); tt::log_debug("reshape_hc_output: {}", tt_output_tensor.shape()); @@ -234,7 +236,8 @@ std::vector fold_with_transpose_sharded_( // reshape n = tt_output_tensor.shape()[0], w = tt_output_tensor.shape()[1], h = tt_output_tensor.shape()[2], c = tt_output_tensor.shape()[3]; - tt_output_tensor = tt_output_tensor.reshape(ttnn::SimpleShape{n, w, (h / stride_h), (c * stride_h)}); + tt_output_tensor = + ttnn::experimental::view(tt_output_tensor, ttnn::SimpleShape{n, w, (h / stride_h), (c * stride_h)}); tt::log_debug("reshape_hw_output: {}", tt_output_tensor.shape()); diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_op.cpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_op.cpp index 1da82c867772..6ed1e13dc3bf 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_op.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_op.cpp @@ -26,10 +26,11 @@ void ReshapeDeviceOperation::validate(const std::vector& input_tensors) TT_FATAL( input_tensor_a.memory_config().memory_layout == TensorMemoryLayout::INTERLEAVED, - "Reshape does not currently support sharding"); + "Use ttnn::experimental::view for reshaping sharded inputs"); TT_FATAL( this->output_mem_config.memory_layout == TensorMemoryLayout::INTERLEAVED, - "Reshape does not currently support sharding"); + "Reshape does not currently support sharding. Use ttnn::experimental::view for reshaping sharded " + "inputs"); if (input_tensor_a.get_layout() == Layout::TILE) { TT_FATAL(input_tensor_a.volume() % TILE_HW == 0, "Error"); diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.cpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.cpp index 0910eb284cfa..445b111c3d7b 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.cpp @@ -10,6 +10,7 @@ #include "ttnn/operations/experimental/auto_format/auto_format.hpp" #include "ttnn/tensor/tensor_utils.hpp" #include "device/reshape_op.hpp" +#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" namespace ttnn::operations::data_movement { @@ -57,7 +58,7 @@ ttnn::Tensor ReshapeOperation::invoke( padded_output_shape[3] == input_tensor.get_padded_shape()[3])) { // Don't need to do a check here to see the H and W both divisible by 32 // since handled within the tensor reshape method - return input_tensor.reshape(output_shape); + return ttnn::experimental::view(input_tensor, output_shape); } if (input_tensor.get_padded_shape() == padded_output_shape) { return ttnn::operations::experimental::auto_format::AutoFormat::move_tensor_to_mem_config( diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp index 924da1f446b8..b3a747200cca 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp @@ -22,6 +22,7 @@ #include "ttnn/operations/data_movement/sharded/interleaved_to_sharded/interleaved_to_sharded.hpp" #include "ttnn/operations/data_movement/untilize_with_unpadding/untilize_with_unpadding.hpp" #include "ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp" +#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" namespace ttnn::operations::data_movement { @@ -273,10 +274,10 @@ ttnn::Tensor PerformView(const ttnn::Tensor& tensor, const ttnn::Shape& shape, c (shape[-1]%tile_first_dim!=0 || shape.rank()==1 || shape[-2]%tile_second_dim!=0 )) { //Correct the output shape to add padding metadata before reshape (view) - return tensor.reshape(tiling_reshape_corrector(shape, tile_first_dim, tile_second_dim)); + return ttnn::experimental::view(tensor, tiling_reshape_corrector(shape, tile_first_dim, tile_second_dim)); } //Perform a reshape (view) - return tensor.reshape(shape); + return ttnn::experimental::view(tensor, shape); } ttnn::Shape shape_corrector(const ttnn::Tensor& tensor, const ttnn::Shape& shape) { @@ -360,7 +361,7 @@ ttnn::Tensor ReshapeViewOperation::invoke( tensor_shape_second_last_dim % tile_first_dim == 0)); // There is no padding on the second last dimension if (!(ttnn::has_storage_type_of(tensor, ttnn::StorageType::DEVICE))) { // This case has been allowed in the past though it means introducing padding values to the data - return tensor.reshape(shape); + return ttnn::experimental::view(tensor, shape); } if (this_is_view) { @@ -377,7 +378,7 @@ ttnn::Tensor ReshapeViewOperation::invoke( if (tile_tensor_view_reshape_possible) { // This case has been allowed in the past though it means introducing padding values to the data - return tensor.reshape(shape); + return ttnn::experimental::view(tensor, shape); } //This is a completely incorrect test but it is due to issue 15558 TT_FATAL(false, "Attempting to reshape between two shapes with different volumes"); diff --git a/ttnn/cpp/ttnn/operations/experimental/experimental_pybind.cpp b/ttnn/cpp/ttnn/operations/experimental/experimental_pybind.cpp index d6f9431947f3..599e8eaf785d 100644 --- a/ttnn/cpp/ttnn/operations/experimental/experimental_pybind.cpp +++ b/ttnn/cpp/ttnn/operations/experimental/experimental_pybind.cpp @@ -36,6 +36,9 @@ #include "ttnn/operations/experimental/ccl/all_gather_matmul/all_gather_matmul_pybind.hpp" #include "ttnn/operations/experimental/ccl/all_reduce/all_reduce_pybind.hpp" #include "ttnn/operations/experimental/plusone/plusone_pybind.hpp" + +#include "ttnn/operations/experimental/reshape/reshape_pybind.hpp" + namespace ttnn::operations::experimental { void py_module(py::module& module) { @@ -76,10 +79,13 @@ void py_module(py::module& module) { plusone::detail::bind_experimental_plusone_operation(module); + reshape::detail::py_bind_view(module); + // CCL ops auto m_experimental_ccl = module.def_submodule("ccl", "experiemental collective communication operations"); ccl::py_bind_all_gather_matmul(m_experimental_ccl); ccl::py_bind_all_reduce(m_experimental_ccl); + } } // namespace ttnn::operations::experimental diff --git a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp new file mode 100644 index 000000000000..a62303dfa0c9 --- /dev/null +++ b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp @@ -0,0 +1,161 @@ + +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "ttnn/common/constants.hpp" +#include "ttnn/run_operation.hpp" +#include "reshape.hpp" +#include "tt_metal/common/constants.hpp" +#include +#include +#include "ttnn/operations/experimental/auto_format/auto_format.hpp" +#include "ttnn/tensor/tensor_utils.hpp" +#include "ttnn/operations/data_movement/data_transfer/data_transfer.hpp" +#include "ttnn/operations/data_movement/slice/slice.hpp" +#include "ttnn/operations/core/core.hpp" + + +#include "ttnn/tensor/tensor.hpp" + +#include +#include + +#include "common/bfloat16.hpp" +#include "ttnn/tensor/tensor_impl.hpp" +#include "ttnn/tensor/tensor_impl_wrapper.hpp" +#include "ttnn/tensor/tensor_utils.hpp" +#include "ttnn/tensor/types.hpp" +#include "tt_metal/common/constants.hpp" +#include "tt_metal/common/math.hpp" +#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tt_metal/graph/graph_tracking.hpp" +#include "ttnn/distributed/api.hpp" +#include "ttnn/distributed/types.hpp" +#include "ttnn/core.hpp" + +namespace ttnn::operations::experimental::reshape { + +ttnn::Tensor MultiDeviceHostStorageVisit( + auto&& storage, const ttnn::Tensor& input_tensor, const ttnn::Shape& new_shape) { + using T = std::decay_t; + auto updated_storage = std::get(input_tensor.get_storage()); + for (int i = 0; i < updated_storage.shapes.size(); i++) { + updated_storage.shapes[i] = new_shape; + } + if (input_tensor.get_layout() == Layout::ROW_MAJOR) { + return Tensor(updated_storage, new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), std::nullopt); + } else { + const auto tile = input_tensor.get_tensor_spec().tile(); + return Tensor(updated_storage, new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), tile); + } +} + +ttnn::Tensor MultiDeviceStorageVisit(auto&& storage, const ttnn::Tensor& input_tensor, const ttnn::Shape& new_shape) { + using T = std::decay_t; + MultiDeviceStorage updated_storage = std::get(input_tensor.get_storage()); + std::unordered_map new_shapes; + + for (auto device_id : updated_storage.ordered_device_ids) { + new_shapes.insert({device_id, new_shape}); + } + updated_storage.shapes = new_shapes; + if (input_tensor.get_layout() == Layout::ROW_MAJOR) { + return Tensor(updated_storage, new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), std::nullopt); + } else { + const auto tile = input_tensor.get_tensor_spec().tile(); + return Tensor(updated_storage, new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), tile); + } +} + +ttnn::Tensor DeviceStorageVisit(auto&& storage, const ttnn::Tensor& input_tensor, const ttnn::Shape& new_shape) { + using T = std::decay_t; + if (input_tensor.get_layout() == Layout::ROW_MAJOR) { + if (input_tensor.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED) { + DeviceStorage device_storage = std::get(input_tensor.get_storage()); + DeviceBuffer device_buffer = device_storage.get_buffer(); + device_buffer->set_page_size(new_shape[-1] * input_tensor.element_size()); + device_storage.insert_buffer(device_buffer); + return Tensor(device_storage, new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), std::nullopt); + } else { + DeviceStorage device_storage = std::get(input_tensor.get_storage()); + DeviceBuffer device_buffer = device_storage.get_buffer(); + ShardSpecBuffer shard_spec_buffer = device_buffer->shard_spec(); + + auto shard_spec = shard_spec_buffer.tensor_shard_spec; + auto shard_shape = shard_spec.shape; + + uint32_t mul_div = + new_shape[-1] > shard_shape[1] ? (new_shape[-1] / shard_shape[1]) : (shard_shape[1] / new_shape[-1]); + shard_spec.shape[0] = new_shape[-1] > shard_shape[1] ? shard_shape[0] / mul_div : shard_shape[0] * mul_div; + shard_spec.shape[1] = new_shape[-1]; + + shard_spec_buffer.page_shape = {1, new_shape[-1]}; + shard_spec_buffer.tensor2d_shape = {shard_spec.shape[0], 1}; + shard_spec_buffer.set_shard_spec(shard_spec); + + device_buffer->set_shard_spec(shard_spec_buffer); + device_storage.insert_buffer(device_buffer); + + return Tensor(device_storage, new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), std::nullopt); + } + } else { + const auto tile = input_tensor.get_tensor_spec().tile(); + return Tensor(input_tensor.get_storage(), new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), tile); + } +} + +ttnn::Tensor tensor_reshape(const ttnn::Tensor& input_tensor, const ttnn::Shape& new_shape) { + ZoneScoped; + GraphTracker::instance().track_function_start("ttnn::experimental::view", input_tensor, new_shape); + const auto& new_padded_shape = new_shape.padded_shape(); + + TT_ASSERT( + input_tensor.volume() == new_padded_shape.volume(), + "{} != {}", + input_tensor.volume(), + new_padded_shape.volume()); + if (input_tensor.get_layout() == Layout::TILE) { + const auto tile = input_tensor.get_tensor_spec().tile(); + TT_ASSERT( + new_padded_shape[-2] % tile.get_tile_shape()[0] == 0 && + new_padded_shape[-1] % tile.get_tile_shape()[1] == 0 && + "Expected a multiple of 32 for H, W (or -1 evaluating to such) in ttnn::experimental::view()!"); + } + auto output = std::visit( + [&input_tensor, &new_shape](auto&& storage) -> Tensor { + using T = std::decay_t; + const auto& tensor = input_tensor; + if constexpr (std::is_same_v) { + return MultiDeviceHostStorageVisit(storage, input_tensor, new_shape); + } + if constexpr (std::is_same_v) { + return MultiDeviceStorageVisit(storage, input_tensor, new_shape); + } + if constexpr (std::is_same_v) { + return DeviceStorageVisit(storage, input_tensor, new_shape); + } else { + if (input_tensor.get_layout() == Layout::ROW_MAJOR) { + return Tensor( + tensor.get_storage(), new_shape, tensor.get_dtype(), tensor.get_layout(), std::nullopt); + } else { + const auto tile = input_tensor.get_tensor_spec().tile(); + return Tensor(tensor.get_storage(), new_shape, tensor.get_dtype(), tensor.get_layout(), tile); + } + } + }, + input_tensor.get_storage()); + output = tt::tt_metal::set_tensor_id(output); + GraphTracker::instance().track_function_end(output); + return output; +} + +ttnn::Tensor ViewOperation::invoke(const ttnn::Tensor& tensor, const ttnn::SimpleShape& shape) { + return tensor_reshape(tensor, ttnn::Shape(shape.view())); +} + +ttnn::Tensor ViewOperation::invoke(const ttnn::Tensor& tensor, const ttnn::Shape& shape) { + return tensor_reshape(tensor, shape); +} + +} // namespace ttnn::operations::experimental::reshape diff --git a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp new file mode 100644 index 000000000000..1bc54d12d1b7 --- /dev/null +++ b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ttnn/run_operation.hpp" +#include "ttnn/decorators.hpp" +#include + +namespace ttnn { +namespace operations::experimental::reshape { + +struct ViewOperation { + static ttnn::Tensor invoke(const ttnn::Tensor& input_tensor, const ttnn::Shape& shape); + static ttnn::Tensor invoke(const ttnn::Tensor& input_tensor, const ttnn::SimpleShape& shape); +}; + +} // namespace operations::experimental::reshape + +namespace experimental { +constexpr auto view = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::view", + ttnn::operations::experimental::reshape::ViewOperation>(); +} // namespace experimental +} // namespace ttnn diff --git a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.cpp b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.cpp new file mode 100644 index 000000000000..4ec1a2a14f9c --- /dev/null +++ b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "reshape_pybind.hpp" +#include "reshape.hpp" + +#include +#include + +#include "ttnn/cpp/pybind11/decorators.hpp" + +#include "ttnn/types.hpp" + +#include "ttnn/tensor/tensor.hpp" +#include "ttnn/tensor/tensor_impl.hpp" + + +namespace ttnn::operations::experimental::reshape::detail { +namespace py = pybind11; + +void py_bind_view(py::module& module) { + auto doc = R"doc( + + Note: + - It is recommended to use ttnn.reshape if you are not sure which operation to use + - If this is the functionality required for your application, it will be called by ttnn.reshape + - The following conditions must be met for the function not to corrupt your data: + * the last dimension must not change + * In Layout::TILE the second last two dimensions must not change OR there is no padding on the second last dimension + + + Args: + * input_tensor: Input Tensor. + * new_shape: New shape of tensor. + + Returns: + ttnn.Tensor: the output tensor with the new shape. + + Example: + + >>> tensor = ttnn.from_torch(torch.tensor((2, 1, 4), dtype=torch.bfloat16), device=device) + >>> output = ttnn.experimental.view(tensor, (2, 1, 1, 4)) + + )doc"; + bind_registered_operation( + module, + ttnn::experimental::view, + doc, + ttnn::pybind_overload_t{ + [](const decltype(ttnn::experimental::view)& self, ttnn::Tensor& input_tensor, int N, int C, int H, int W) { + return self(input_tensor, infer_dims_for_reshape(input_tensor, ttnn::SmallVector{N, C, H, W})); + }, + py::arg("input_tensor"), + py::arg("N"), + py::arg("C"), + py::arg("H"), + py::arg("W"), + }, + + ttnn::pybind_overload_t{ + [](const decltype(ttnn::experimental::view)& self, ttnn::Tensor& input_tensor, const ttnn::Shape& shape) { + return self(input_tensor, shape); + }, + py::arg("input_tensor"), + py::arg("shape"), + }, + ttnn::pybind_overload_t{ + [](const decltype(ttnn::experimental::view)& self, + ttnn::Tensor& input_tensor, + const ttnn::SmallVector& shape) { + return self(input_tensor, infer_dims_for_reshape(input_tensor, shape)); + }, + py::arg("input_tensor"), + py::arg("shape"), + }); +} + +} // namespace ttnn::operations::experimental::reshape::detail diff --git a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.hpp b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.hpp new file mode 100644 index 000000000000..b2f9d94d5b70 --- /dev/null +++ b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.hpp @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "pybind11/pybind_fwd.hpp" + +namespace ttnn::operations::experimental::reshape::detail { + +void py_bind_view(pybind11::module& module); + +} // namespace ttnn::operations::experimental diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_rm_factory.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_rm_factory.cpp index 8a37c4b6f7dd..8a81c5b302e2 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_rm_factory.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_rm_factory.cpp @@ -4,6 +4,7 @@ #include "moreh_getitem_device_operation.hpp" #include "ttnn/operations/moreh/moreh_helper_functions.hpp" +#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" namespace { namespace CMAKE_UNIQUE_NAMESPACE { @@ -59,7 +60,7 @@ MorehGetItemOperation::MorehGetItemRmFactory::cached_program_t MorehGetItemOpera uint32_t index_end_dim = index_dims.back(); Tensor input_5d = input; - input_5d = input_5d.reshape(input_5d_shape); + input_5d = ttnn::experimental::view(input_5d, input_5d_shape); auto input_5d_shape_without_padding = input_5d_shape.value.without_padding(); diff --git a/ttnn/cpp/ttnn/operations/pool/global_avg_pool/global_avg_pool.cpp b/ttnn/cpp/ttnn/operations/pool/global_avg_pool/global_avg_pool.cpp index 557559206121..98637a7cb069 100644 --- a/ttnn/cpp/ttnn/operations/pool/global_avg_pool/global_avg_pool.cpp +++ b/ttnn/cpp/ttnn/operations/pool/global_avg_pool/global_avg_pool.cpp @@ -4,6 +4,7 @@ #include "ttnn/operations/pool/global_avg_pool/global_avg_pool.hpp" #include "ttnn/operations/reduction/generic/generic_reductions.hpp" +#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" namespace tt { namespace tt_metal { @@ -40,7 +41,7 @@ Tensor global_avg_pool2d( input_padding.pad_value()); auto output_shape = tt::tt_metal::LegacyShape({in_shape[0], 1, in_shape[1] * in_shape[2], in_shape[3]}, output_padding); - output = output.reshape(output_shape); + output = ttnn::experimental::view(output, output_shape); output = pool_2d(output, memory_config, output_dtype); return output; diff --git a/ttnn/cpp/ttnn/operations/transformer/split_query_key_value_and_split_heads/split_query_key_value_and_split_heads.cpp b/ttnn/cpp/ttnn/operations/transformer/split_query_key_value_and_split_heads/split_query_key_value_and_split_heads.cpp index 4ac733211eb0..819ca1654336 100644 --- a/ttnn/cpp/ttnn/operations/transformer/split_query_key_value_and_split_heads/split_query_key_value_and_split_heads.cpp +++ b/ttnn/cpp/ttnn/operations/transformer/split_query_key_value_and_split_heads/split_query_key_value_and_split_heads.cpp @@ -9,6 +9,7 @@ #include "ttnn/cpp/ttnn/operations/experimental/transformer/nlp_create_qkv_heads/nlp_create_qkv_heads.hpp" #include "ttnn/cpp/ttnn/operations/experimental/transformer/nlp_create_qkv_heads_falcon7b/nlp_create_qkv_heads_falcon7b.hpp" #include "ttnn/cpp/ttnn/operations/experimental/transformer/create_qkv_heads/create_qkv_heads.hpp" +#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" namespace ttnn::operations::transformer { @@ -104,11 +105,13 @@ std::tuple SplitQueryKeyValueAndSplitHeadsOperation::inv head_size, padded_head_size); - const auto input_4d = input_tensor.reshape(ttnn::SimpleShape{ - input_shape.with_tile_padding()[0], - 1, - input_shape.with_tile_padding()[1], - input_shape.with_tile_padding()[2]}); + const auto input_4d = ttnn::experimental::view( + input_tensor, + ttnn::SimpleShape{ + input_shape.with_tile_padding()[0], + 1, + input_shape.with_tile_padding()[1], + input_shape.with_tile_padding()[2]}); auto outputs = ttnn::experimental::nlp_create_qkv_heads_falcon7b( input_4d, memory_config.value_or(input_tensor.memory_config())); return detail::reshape_outputs_of_split_query_key_value_and_split_heads( @@ -168,11 +171,13 @@ std::tuple SplitQueryKeyValueAndSplitHeadsOperation::inv "Invalid operation: KV tensor should not be provided when the input tensor is sharded. Please ensure that " "the KV tensor is only used in non-sharded configurations."); - const auto input_tensor_4d = input_tensor.reshape(ttnn::SimpleShape{ - input_shape.with_tile_padding()[0], - 1, - input_shape.with_tile_padding()[1], - input_shape.with_tile_padding()[2]}); + const auto input_tensor_4d = ttnn::experimental::view( + input_tensor, + ttnn::SimpleShape{ + input_shape.with_tile_padding()[0], + 1, + input_shape.with_tile_padding()[1], + input_shape.with_tile_padding()[2]}); return detail::reshape_outputs_of_split_query_key_value_and_split_heads( ttnn::experimental::create_qkv_heads( input_tensor_4d, @@ -184,15 +189,18 @@ std::tuple SplitQueryKeyValueAndSplitHeadsOperation::inv sequence_size_padded, transpose_key); } else { - const auto input_tensor_4d = input_tensor.reshape(ttnn::SimpleShape{ - input_shape.with_tile_padding()[0], - 1, - input_shape.with_tile_padding()[1], - input_shape.with_tile_padding()[2]}); + const auto input_tensor_4d = ttnn::experimental::view( + input_tensor, + ttnn::SimpleShape{ + input_shape.with_tile_padding()[0], + 1, + input_shape.with_tile_padding()[1], + input_shape.with_tile_padding()[2]}); std::optional input_tensor_kv_4d = std::nullopt; if (input_tensor_kv.has_value()) { auto padded_input_shape_kv = input_tensor_kv.value().get_shape().with_tile_padding(); - input_tensor_kv_4d = input_tensor_kv.value().reshape( + input_tensor_kv_4d = ttnn::experimental::view( + input_tensor_kv.value(), ttnn::SimpleShape{padded_input_shape_kv[0], 1, padded_input_shape_kv[1], padded_input_shape_kv[2]}); } const auto outputs = ttnn::experimental::nlp_create_qkv_heads( diff --git a/ttnn/cpp/ttnn/tensor/tensor.cpp b/ttnn/cpp/ttnn/tensor/tensor.cpp index f4304d33c6a5..eab2d044d5bb 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor.cpp @@ -630,11 +630,6 @@ const bool Tensor::is_sharded() const { uint32_t Tensor::element_size() const { return tensor_impl::element_size_bytes(this->get_dtype()); } -Tensor Tensor::reshape(const ttnn::SimpleShape& new_shape) const { - return tensor_ops::tensor_reshape(*this, new_shape); -} - -Tensor Tensor::reshape(const ttnn::Shape& new_shape) const { return tensor_ops::tensor_reshape(*this, new_shape); } bool Tensor::is_allocated() const { ZoneScoped; diff --git a/ttnn/cpp/ttnn/tensor/tensor.hpp b/ttnn/cpp/ttnn/tensor/tensor.hpp index b8b7a993b8a6..08b7a653389c 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor.hpp @@ -188,8 +188,6 @@ struct Tensor { // ====================================================================================== // Low Level APIs // ====================================================================================== - Tensor reshape(const ttnn::SimpleShape& new_shape) const; - Tensor reshape(const ttnn::Shape& new_shape) const; // ====================================================================================== // Getters diff --git a/ttnn/cpp/ttnn/tensor/tensor_ops.cpp b/ttnn/cpp/ttnn/tensor/tensor_ops.cpp index 44ee8fd7a6c7..d7f03b7a0725 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_ops.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor_ops.cpp @@ -373,97 +373,5 @@ Tensor tensor_unpad_from_tile(const Tensor& input_tensor, const ttnn::SimpleShap return output; } -Tensor tensor_reshape(const Tensor& input_tensor, const ttnn::Shape& new_shape) { - ZoneScoped; - GraphTracker::instance().track_function_start("Tensor::reshape", input_tensor, new_shape); - const auto& new_padded_shape = new_shape.padded_shape(); - const auto tile = input_tensor.get_tensor_spec().tile(); - TT_ASSERT( - input_tensor.volume() == new_padded_shape.volume(), - "{} != {}", - input_tensor.volume(), - new_padded_shape.volume()); - if (input_tensor.get_layout() == Layout::TILE) { - TT_ASSERT( - new_padded_shape[-2] % tile.get_tile_shape()[0] == 0 && - new_padded_shape[-1] % tile.get_tile_shape()[1] == 0 && - "Expected a multiple of 32 for H, W (or -1 evaluating to such) in Tensor::reshape()!"); - } - auto output = std::visit( - [&input_tensor, &new_shape, &tile](auto&& storage) -> Tensor { - using T = std::decay_t; - const auto& tensor = input_tensor; - if constexpr (std::is_same_v) { - auto updated_storage = std::get(tensor.get_storage()); - for (int i = 0; i < updated_storage.shapes.size(); i++) { - updated_storage.shapes[i] = new_shape; - } - return Tensor(updated_storage, new_shape, tensor.get_dtype(), tensor.get_layout(), tile); - } - if constexpr (std::is_same_v) { - MultiDeviceStorage updated_storage = std::get(tensor.get_storage()); - std::unordered_map new_shapes; - - for (auto device_id : updated_storage.ordered_device_ids) { - new_shapes.insert({device_id, new_shape}); - } - updated_storage.shapes = new_shapes; - return Tensor(updated_storage, new_shape, tensor.get_dtype(), tensor.get_layout(), tile); - } - if constexpr (std::is_same_v) { - if (input_tensor.get_layout() == Layout::ROW_MAJOR) { - if (tensor.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED) { - DeviceStorage device_storage = std::get(tensor.get_storage()); - DeviceBuffer device_buffer = device_storage.get_buffer(); - const auto& tensor_spec = tensor.tensor_spec(); - auto page_size_bytes = tensor_spec.compute_page_size_bytes(); - device_buffer->set_page_size(page_size_bytes); - device_storage.insert_buffer(device_buffer); - return Tensor(device_storage, new_shape, tensor.get_dtype(), tensor.get_layout(), tile); - } else { - DeviceStorage device_storage = std::get(tensor.get_storage()); - DeviceBuffer device_buffer = device_storage.get_buffer(); - ShardSpecBuffer shard_spec_buffer = device_buffer->shard_spec(); - - auto shard_spec = shard_spec_buffer.tensor_shard_spec; - auto shard_shape = shard_spec.shape; - - uint32_t mul_div; - if (new_shape[-1] == 0 || shard_shape[1] == 0) { - mul_div = 0; - } else { - mul_div = new_shape[-1] > shard_shape[1] ? - (new_shape[-1] / shard_shape[1]) : - (shard_shape[1] / new_shape[-1]); - } - - shard_spec.shape[0] = new_shape[-1] > shard_shape[1] ? shard_shape[0] / mul_div : shard_shape[0] * mul_div; - shard_spec.shape[1] = new_shape[-1]; - - shard_spec_buffer.page_shape = {1, new_shape[-1]}; - shard_spec_buffer.tensor2d_shape = {shard_spec.shape[0], 1}; - shard_spec_buffer.set_shard_spec(shard_spec); - - device_buffer->set_shard_spec(shard_spec_buffer); - device_storage.insert_buffer(device_buffer); - - return Tensor(device_storage, new_shape, tensor.get_dtype(), tensor.get_layout(), tile); - } - } else { - return Tensor(tensor.get_storage(), new_shape, tensor.get_dtype(), tensor.get_layout(), tile); - } - } else { - return Tensor(tensor.get_storage(), new_shape, tensor.get_dtype(), tensor.get_layout(), tile); - } - }, - input_tensor.get_storage()); - output = tt::tt_metal::set_tensor_id(output); - GraphTracker::instance().track_function_end(output); - return output; -} - -Tensor tensor_reshape(const Tensor& input_tensor, const ttnn::SimpleShape& new_shape) { - return tensor_reshape(input_tensor, ttnn::Shape(new_shape.view())); -} } // namespace tt::tt_metal::tensor_ops diff --git a/ttnn/cpp/ttnn/tensor/tensor_ops.hpp b/ttnn/cpp/ttnn/tensor/tensor_ops.hpp index b8edff425f8b..bba2ce486195 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_ops.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor_ops.hpp @@ -60,7 +60,4 @@ Tensor tensor_pad_to_tile(const Tensor& input_tensor, float pad_value); Tensor tensor_unpad_from_tile(const Tensor& input_tensor, const ttnn::SimpleShape& output_tensor_shape); -Tensor tensor_reshape(const Tensor& input_tensor, const ttnn::SimpleShape& new_shape); -Tensor tensor_reshape(const Tensor& input_tensor, const ttnn::Shape& new_shape); - } // namespace tt::tt_metal::tensor_ops From 05886fd32101a8afb5a6817645f1ec30c9ee4785 Mon Sep 17 00:00:00 2001 From: Oleg Milyutin Date: Mon, 16 Dec 2024 12:51:07 -0500 Subject: [PATCH 03/87] #0: Fix merge conflicts originating from #15289 (#16062) Main got broken due to conflicting https://github.com/tenstorrent/tt-metal/pull/15289 and https://github.com/tenstorrent/tt-metal/pull/15669 + https://github.com/tenstorrent/tt-metal/pull/15671. This PR fixes the issue. Tested by compiling and running the affected tests locally. --- .../test_tilize_zero_padding_channels_last.cpp | 2 +- .../tensors/test_async_tensor_apis.cpp | 5 +---- tests/tt_eager/tensors/test_copy_and_move.cpp | 2 +- .../data_movement/reshape_view/reshape.cpp | 18 +++++++++--------- .../experimental/reshape/reshape.cpp | 2 +- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp b/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp index 3445d424d538..05655d8cbc86 100644 --- a/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp +++ b/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp @@ -13,7 +13,7 @@ #include "ttnn/tensor/tensor.hpp" #include "ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp" #include "tt_metal/host_api.hpp" -#include "ttnn/operations/numpy/functions.hpp" +#include "ttnn/operations/functions.hpp" #include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" using namespace tt; diff --git a/tests/tt_eager/tensors/test_async_tensor_apis.cpp b/tests/tt_eager/tensors/test_async_tensor_apis.cpp index 2791cbc7d537..dd4c40c6a71b 100644 --- a/tests/tt_eager/tensors/test_async_tensor_apis.cpp +++ b/tests/tt_eager/tensors/test_async_tensor_apis.cpp @@ -20,10 +20,7 @@ #include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" -using namespace tt; -using namespace tt_metal; -using namespace constants; - +namespace tt::tt_metal { namespace { using ::tt::constants::TILE_HEIGHT; diff --git a/tests/tt_eager/tensors/test_copy_and_move.cpp b/tests/tt_eager/tensors/test_copy_and_move.cpp index b8eabd74003a..a194a808678e 100644 --- a/tests/tt_eager/tensors/test_copy_and_move.cpp +++ b/tests/tt_eager/tensors/test_copy_and_move.cpp @@ -39,7 +39,7 @@ bool test_tensor_copy_semantics(Device* device) { // host tensor updated with host tensor copy assignment Tensor host_c = ttnn::experimental::view(ttnn::arange(/*start=*/0, /*stop=*/tt_metal::compute_volume(single_tile_shape), /*step=*/1), single_tile_shape).to(Layout::TILE); - Tensor host_c_copy = ttnn::numpy::random::random(single_tile_shape).to(Layout::TILE); + Tensor host_c_copy = ttnn::random::random(single_tile_shape).to(Layout::TILE); host_c_copy = host_c; auto host_c_data = owned_buffer::get_as(host_c); auto host_c_copy_data = owned_buffer::get_as(host_c_copy); diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp index b3a747200cca..b5b9e2d80787 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp @@ -271,12 +271,11 @@ ttnn::Tensor PerformView(const ttnn::Tensor& tensor, const ttnn::Shape& shape, c return tensor; } if (tensor.get_layout() == ttnn::TILE_LAYOUT && - (shape[-1]%tile_first_dim!=0 || shape.rank()==1 || shape[-2]%tile_second_dim!=0 )) - { - //Correct the output shape to add padding metadata before reshape (view) + (shape[-1] % tile_first_dim != 0 || shape.rank() == 1 || shape[-2] % tile_second_dim != 0)) { + // Correct the output shape to add padding metadata before reshape (view) return ttnn::experimental::view(tensor, tiling_reshape_corrector(shape, tile_first_dim, tile_second_dim)); } - //Perform a reshape (view) + // Perform a reshape (view) return ttnn::experimental::view(tensor, shape); } @@ -344,11 +343,12 @@ ttnn::Tensor ReshapeViewOperation::invoke( // Just edit shape if shape has a 0 dimension if (tensor.get_logical_volume() == 0) { - TT_FATAL(shape.logical_shape().volume() == 0 , "Tensor volume is 0, but shape's volume is not"); - TT_FATAL((tensor.storage_type() != StorageType::MULTI_DEVICE && - tensor.storage_type() != StorageType::MULTI_DEVICE_HOST), - "Reshaping a multi-device tensor with 0 volume is not supported"); - return tensor.reshape(shape); + TT_FATAL(shape.logical_shape().volume() == 0, "Tensor volume is 0, but shape's volume is not"); + TT_FATAL( + (tensor.storage_type() != StorageType::MULTI_DEVICE && + tensor.storage_type() != StorageType::MULTI_DEVICE_HOST), + "Reshaping a multi-device tensor with 0 volume is not supported"); + return ttnn::experimental::view(tensor, shape); } TT_FATAL(shape.logical_shape().volume() != 0, "Tensor volume is not 0, but shape volume is 0"); diff --git a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp index a62303dfa0c9..8419dc81730f 100644 --- a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp +++ b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp @@ -8,7 +8,7 @@ #include "reshape.hpp" #include "tt_metal/common/constants.hpp" #include -#include +#include #include "ttnn/operations/experimental/auto_format/auto_format.hpp" #include "ttnn/tensor/tensor_utils.hpp" #include "ttnn/operations/data_movement/data_transfer/data_transfer.hpp" From 8d01f5d95ffee868b54fb1396bc33860f58e507e Mon Sep 17 00:00:00 2001 From: Colman Glagovich <114512306+cglagovichTT@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:02:59 -0500 Subject: [PATCH 04/87] Integrate chunked prefill into t3k Llama3-70B (#15921) --- .../tests/test_chunked_generation.py | 351 ++++++++++++++++++ .../llama2_70b/tests/test_llama_attention.py | 168 ++++++--- .../tests/test_llama_device_perf.py | 1 + .../llama2_70b/tests/test_llama_model.py | 175 +++++++-- .../tests/test_llama_model_t3000.py | 1 + .../t3000/llama2_70b/tests/test_llama_perf.py | 4 +- .../tests/test_llama_perf_decode.py | 6 +- .../tests/test_llama_stress_test.py | 2 +- .../t3000/llama2_70b/tt/generator_vllm.py | 13 - .../tt/llama_attention_optimized.py | 92 ++++- .../llama2_70b/tt/llama_decoder_optimized.py | 17 +- .../t3000/llama2_70b/tt/llama_generation.py | 177 +++++++-- .../llama2_70b/tt/llama_model_optimized.py | 77 ++-- .../demos/t3000/llama2_70b/tt/model_config.py | 3 +- tests/scripts/t3000/run_t3000_demo_tests.sh | 3 + 15 files changed, 926 insertions(+), 164 deletions(-) create mode 100644 models/demos/t3000/llama2_70b/tests/test_chunked_generation.py diff --git a/models/demos/t3000/llama2_70b/tests/test_chunked_generation.py b/models/demos/t3000/llama2_70b/tests/test_chunked_generation.py new file mode 100644 index 000000000000..22ba67ece5de --- /dev/null +++ b/models/demos/t3000/llama2_70b/tests/test_chunked_generation.py @@ -0,0 +1,351 @@ +# SPDX-FileCopyrightText: © 2023 Tenstorrent Inc. +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from loguru import logger +import torch +import ttnn +from ttnn import ReplicateTensorToMesh + +from models.demos.t3000.llama2_70b.reference.llama.llama import Llama +from models.demos.t3000.llama2_70b.tt.llama_generation import ( + TtLlamaModelForGeneration, + get_block_size, + num_blocks_in_seq, +) +from models.demos.t3000.llama2_70b.tt.llama_common import ( + setup_llama_env, + check_mesh_device, + comp_pcc, + load_llama_state_dict, +) +from models.demos.t3000.llama2_70b.demo.demo_continuous_batching_paged_attention import ( + PagedAttentionConfig, + ModelArgs, + TTArgs, +) + + +def run_chunked_prefill_single_user(model_args, tt_args, chunk_size): + # Set up paged attention config + paged_attention_config = PagedAttentionConfig() + bsz = model_args.max_batch_size + + # Create static page table (same as in demo) + permutation = torch.randperm(paged_attention_config.max_num_blocks) + reverse_permutation = torch.argsort(permutation) + static_page_table = reverse_permutation.reshape(bsz, paged_attention_config.max_num_blocks // bsz) + + # Build reference generator + ref_generator = Llama.build( + ckpt_dir=model_args.ckpt_dir, + tokenizer_path=model_args.tokenizer_path, + max_seq_len=model_args.max_seq_len, + max_batch_size=model_args.max_batch_size, + skip_model_load=model_args.skip_model_load, + n_layers=model_args.num_layers, + ) + + # Load state dict for TT model + state_dict = load_llama_state_dict(model_args.ckpt_dir, n_layers=model_args.num_layers) + + # Build TT generator with paged attention + tt_model = TtLlamaModelForGeneration( + configuration=ref_generator.model.params, + state_dict=state_dict, + model_args=model_args, + tt_args=tt_args, + paged_attention_config=paged_attention_config, + ) + + # For testing, override the max prefill length + tt_model.model_config["MAX_PREFILL_SEQ_LEN"] = chunk_size + + # Extract the model's KV cache such that we can pass it in to the forward function + kv_cache = [l.attention.layer_past for l in tt_model.tt_model.layers] + + # Create random input + seq_len = model_args.max_seq_len + input_tokens = torch.randint(0, 32000, (1, seq_len), dtype=torch.long) + + # Slice out relevant part of page table + block_size = get_block_size(kv_cache) + num_blocks = num_blocks_in_seq(seq_len, block_size) + static_page_table = static_page_table[:, :num_blocks] + + # Run both models + with torch.no_grad(): + tt_logits = tt_model.prefill_forward_single_user( + input_tokens, + start_pos=0, + user_id=0, + page_table=static_page_table, + kv_cache=kv_cache, + ) + ref_logits = ref_generator.model.forward(input_tokens, start_pos=0) + + # Compare outputs + does_pass, pcc = comp_pcc(ref_logits, tt_logits, pcc=0.99) + logger.info(f"PCC between reference and TT model logits: {pcc}") + assert does_pass, f"Logits PCC {pcc} below threshold of 0.99" + + ref_kv_cache = [[l.attention.cache_k, l.attention.cache_v] for l in ref_generator.model.layers] + # Compare KV caches + for layer_idx in range(len(kv_cache)): + tt_cache = kv_cache[layer_idx] + ref_cache = ref_kv_cache[layer_idx] + + # Unshuffle paged cache and review it as unpaged cache (similar to paged_update_cache test) + tt_got_back_shuffled = [ + ttnn.to_torch(kv, mesh_composer=ttnn.ConcatMeshToTensor(tt_args.mesh_device, dim=1)) for kv in tt_cache + ] + tt_got_back_unshuffled = [shuffled[reverse_permutation] for shuffled in tt_got_back_shuffled] + + # Reshape to match reference cache dimensions + max_num_blocks = tt_got_back_shuffled[0].shape[0] + block_size = tt_got_back_shuffled[0].shape[2] + num_heads = tt_got_back_shuffled[0].shape[1] + head_dim = tt_got_back_shuffled[0].shape[3] + tt_got_back = [ + unshuffled.reshape(1, max_num_blocks, num_heads, block_size, head_dim) + .transpose(1, 2) + .reshape(1, num_heads, -1, head_dim) + for unshuffled in tt_got_back_unshuffled + ] + + for i in range(len(tt_got_back)): + ref_cache_slice = ref_cache[i][:1, :seq_len, :, :].permute(0, 2, 1, 3) + # Compare caches + does_pass_cache, pcc_cache = comp_pcc(ref_cache_slice, tt_got_back[i][:, :, :seq_len, :]) + logger.info(f"PCC between reference and TT model KV cache at layer {layer_idx}: {pcc_cache}") + assert does_pass_cache, f"KV cache PCC {pcc_cache} below threshold at layer {layer_idx}" + + return does_pass, pcc + + +@torch.no_grad() +@pytest.mark.timeout(240000) +@pytest.mark.parametrize( + "llama_version", + ["llama3"], +) +@pytest.mark.parametrize( + "num_layers", + [ + 1, + ], + ids=[ + "1L", + ], +) +@pytest.mark.parametrize( + "max_batch_size, max_context_len, chunk_size", + [(1, 128 * 1024, 32 * 1024), (16, 8 * 1024, 2 * 1024), (32, 2 * 1024, 1 * 1024)], + ids=["1BSZ", "16BSZ", "32BSZ"], +) +def test_chunked_prefill_single_user( + t3k_mesh_device, llama_version, num_layers, max_batch_size, max_context_len, chunk_size +): + """ + This test ensures that chunked prefill, when used by calling `prefill_forward_single_user`, + matches the reference implementation. + """ + if max_context_len == 128 * 1024: + pytest.skip("Skipping test for max_context_len = 128*1024 since reference runs OOM") + # Set up environment + model_config, ckpt_dir, tokenizer_path, cache_path = setup_llama_env( + llama_version=llama_version, + ) + + # Check device compatibility + check_mesh_device(t3k_mesh_device, model_config) + t3k_mesh_device.enable_async(True) + + # Create args + model_args = ModelArgs( + implementation="tt", + llama_version=llama_version, + ckpt_dir=ckpt_dir, + tokenizer_path=tokenizer_path, + max_batch_size=max_batch_size, + num_layers=num_layers, + max_seq_len=max_context_len, + max_kv_context_len=max_context_len, + ) + + tt_args = TTArgs( + mesh_device=t3k_mesh_device, + n_devices=8, + cache_path=cache_path, + ) + + # Run test + does_pass, pcc = run_chunked_prefill_single_user(model_args, tt_args, chunk_size) + assert does_pass, f"Test failed with PCC {pcc}" + + +def run_batch_prefill_test(model_args, tt_args, chunk_size, batch): + # Set up paged attention config + paged_attention_config = PagedAttentionConfig() + # Create static page table (same as in demo) + permutation = torch.randperm(paged_attention_config.max_num_blocks) + reverse_permutation = torch.argsort(permutation) + static_page_table = reverse_permutation.reshape( + model_args.max_batch_size, paged_attention_config.max_num_blocks // model_args.max_batch_size + ) + # Build reference generator + ref_generator = Llama.build( + ckpt_dir=model_args.ckpt_dir, + tokenizer_path=model_args.tokenizer_path, + max_seq_len=model_args.max_seq_len, + max_batch_size=model_args.max_batch_size, + skip_model_load=model_args.skip_model_load, + n_layers=model_args.num_layers, + ) + + # Load state dict for TT model + state_dict = load_llama_state_dict(model_args.ckpt_dir, n_layers=model_args.num_layers) + + # Build TT generator with paged attention + tt_model = TtLlamaModelForGeneration( + configuration=ref_generator.model.params, + state_dict=state_dict, + model_args=model_args, + tt_args=tt_args, + paged_attention_config=paged_attention_config, + ) + + # For testing, override the max prefill length + tt_model.model_config["MAX_PREFILL_SEQ_LEN"] = chunk_size + + # Extract the model's KV cache such that we can pass it in to the forward function + kv_cache = [l.attention.layer_past for l in tt_model.tt_model.layers] + + # Create random input with varying sequence lengths + max_seq_len = model_args.max_seq_len + prompt_lens = torch.randint( + chunk_size, max_seq_len + 1, (batch,) + ) # Random lengths between chunk_size and max_seq_len + input_tokens = torch.randint(0, 32000, (batch, max_seq_len), dtype=torch.long) + logger.info(f"Prompt lengths: {prompt_lens}") + batch_page_table = static_page_table[:batch] + # Run both models + with torch.no_grad(): + tt_logits = tt_model.prefill_forward( + input_tokens, + start_pos=0, + page_table=batch_page_table, + kv_cache=kv_cache, + prompt_lens=prompt_lens, + ) + logger.info(f"TT logits shape: {tt_logits.shape}") + + # Run reference model + batch_logits = ref_generator.model.forward(input_tokens, start_pos=0) + ref_logits = batch_logits[torch.arange(batch), prompt_lens - 1, :].unsqueeze(1) # Only keep last token's logits + ref_kv_cache = [[l.attention.cache_k, l.attention.cache_v] for l in ref_generator.model.layers] + + # Compare outputs + does_pass, pcc = comp_pcc(ref_logits, tt_logits, pcc=0.99) + logger.info(f"PCC between reference and TT model: {pcc}") + assert does_pass, f"PCC {pcc} below threshold of 0.99" + + # Compare KV caches + for layer_idx in range(len(kv_cache)): + tt_cache = kv_cache[layer_idx] + ref_cache = ref_kv_cache[layer_idx] + + # Unshuffle paged cache and review it as unpaged cache (similar to paged_update_cache test) + tt_got_back_shuffled = [ + ttnn.to_torch(kv, mesh_composer=ttnn.ConcatMeshToTensor(tt_args.mesh_device, dim=1)) for kv in tt_cache + ] + tt_got_back_unshuffled = [shuffled[reverse_permutation] for shuffled in tt_got_back_shuffled] + + # Reshape to match reference cache dimensions + max_num_blocks = tt_got_back_shuffled[0].shape[0] + block_size = tt_got_back_shuffled[0].shape[2] + num_heads = tt_got_back_shuffled[0].shape[1] + head_dim = tt_got_back_shuffled[0].shape[3] + tt_got_back = [ + unshuffled.reshape( + model_args.max_batch_size, max_num_blocks // model_args.max_batch_size, num_heads, block_size, head_dim + ) + .transpose(1, 2) + .reshape(model_args.max_batch_size, num_heads, -1, head_dim) + for unshuffled in tt_got_back_unshuffled + ] + + for b in range(batch): + valid_seq_len = prompt_lens[b] + logger.info(f"valid seq len: {valid_seq_len}") + for i in range(len(tt_got_back)): + logger.info(f"layer {i}, batch {b}") + logger.info(f"ref cache shape: {ref_cache[i].shape}") + logger.info(f"tt cache shape: {tt_got_back[i].shape}") + ref_cache_slice = ref_cache[i][b : b + 1, :valid_seq_len, :, :].permute(0, 2, 1, 3) + tt_cache_slice = tt_got_back[i][b : b + 1, :, :valid_seq_len, :] + # Compare caches + does_pass_cache, pcc_cache = comp_pcc(ref_cache_slice, tt_cache_slice) + logger.info(f"PCC between reference and TT model KV cache at layer {layer_idx}: {pcc_cache}") + assert does_pass_cache, f"KV cache PCC {pcc_cache} below threshold at layer {layer_idx}" + + return does_pass, pcc + + +@torch.no_grad() +@pytest.mark.timeout(240000) +@pytest.mark.parametrize( + "llama_version", + ["llama3"], +) +@pytest.mark.parametrize( + "num_layers", + [ + 1, + ], + ids=[ + "1L", + ], +) +@pytest.mark.parametrize( + "max_batch_size, max_context_len, chunk_size, batch", + [(1, 128 * 1024, 32 * 1024, 1), (16, 8 * 1024, 2 * 1024, 4), (32, 2 * 1024, 1 * 1024, 4)], + ids=["1BSZ", "16BSZ", "32BSZ"], +) +def test_batch_prefill(t3k_mesh_device, llama_version, num_layers, max_batch_size, max_context_len, chunk_size, batch): + """ + This test ensures that batch prefill matches the reference implementation + when processing multiple sequences of different lengths. + """ + if max_context_len == 128 * 1024: + pytest.skip("Skipping test for max_context_len = 128*1024 since reference runs OOM") + # Set up environment + model_config, ckpt_dir, tokenizer_path, cache_path = setup_llama_env( + llama_version=llama_version, + ) + + # Check device compatibility + check_mesh_device(t3k_mesh_device, model_config) + t3k_mesh_device.enable_async(True) + + # Create args + model_args = ModelArgs( + implementation="tt", + llama_version=llama_version, + ckpt_dir=ckpt_dir, + tokenizer_path=tokenizer_path, + max_batch_size=max_batch_size, + num_layers=num_layers, + max_seq_len=max_context_len, + max_kv_context_len=max_context_len, + ) + + tt_args = TTArgs( + mesh_device=t3k_mesh_device, + n_devices=8, + cache_path=cache_path, + ) + + # Run test + does_pass, pcc = run_batch_prefill_test(model_args, tt_args, chunk_size, batch) + assert does_pass, f"Test failed with PCC {pcc}" diff --git a/models/demos/t3000/llama2_70b/tests/test_llama_attention.py b/models/demos/t3000/llama2_70b/tests/test_llama_attention.py index 884f1ea4bb1a..72bd9b7091f6 100644 --- a/models/demos/t3000/llama2_70b/tests/test_llama_attention.py +++ b/models/demos/t3000/llama2_70b/tests/test_llama_attention.py @@ -146,7 +146,7 @@ def tt_llama_attention_prepare_inputs( cos_gathered, dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, - cache_file_name=cache_name(f"cos_gathered_prefill_{seq_len}"), + cache_file_name=cache_name(f"cos_gathered_prefill_{start_pos}_to_{start_pos + seq_len}"), memory_config=ttnn.DRAM_MEMORY_CONFIG, device=llama_attention_model.mesh_device, mesh_mapper=ReplicateTensorToMesh(llama_attention_model.mesh_device), @@ -155,7 +155,7 @@ def tt_llama_attention_prepare_inputs( sin_gathered, dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, - cache_file_name=cache_name(f"sin_gathered_prefill_{seq_len}"), + cache_file_name=cache_name(f"sin_gathered_prefill_{start_pos}_to_{start_pos + seq_len}"), memory_config=ttnn.DRAM_MEMORY_CONFIG, device=llama_attention_model.mesh_device, mesh_mapper=ReplicateTensorToMesh(llama_attention_model.mesh_device), @@ -219,6 +219,8 @@ def run_test_LlamaAttention_inference( tokenizer_path, cache_path, paged_attention, + is_chunked_prefill=False, + chunk_size=None, ): # Prepare paths and devices skip_model_load = should_skip_model_load() @@ -315,15 +317,9 @@ def run_test_LlamaAttention_inference( start_pos = generation_start_pos + i # PyTorch output -------------------------------------------------------------------- - if mode == "prefill": - attention_input, start_pos, freqs_cis, attn_mask = pytorch_LlamaAttention_model.prepare_inputs_prefill( - pt_inp_normed, start_pos - ) - else: - attention_input, start_pos, freqs_cis, attn_mask = pytorch_LlamaAttention_model.prepare_inputs( - pt_inp_normed, start_pos - ) - + attention_input, start_pos, freqs_cis, attn_mask = pytorch_LlamaAttention_model.prepare_inputs_prefill( + pt_inp_normed, start_pos + ) pytorch_out = pytorch_LlamaAttention_model( attention_input, start_pos, @@ -331,36 +327,108 @@ def run_test_LlamaAttention_inference( attn_mask, ) - # TT hardware execution ------------------------------------------------------------- - attention_input, start_pos, rot_mat, cache_idxs = tt_llama_attention_prepare_inputs( - tt_LlamaAttention_model, - tt_input, - start_pos, - mode, - configuration.rope_theta, - rope_setup=rope_setup if mode == "decode" else None, - use_scaled_rope=configuration.use_scaled_rope, - ) + if is_chunked_prefill: + assert mode == "prefill", "Chunked prefill should only be run in prefill mode" + assert start_pos == 0, "Start pos should be 0 for chunked prefill" + assert batch == 1, "Batch should be 1 for chunked prefill" + + """ + In chunked prefill mode, we need to split the prefill input into chunks. + Each chunk will be processed sequentially. Each chunk must be given the appropriate + sin/cos values. Also, each chunk must be given a partial page table for paged_fill_cache + so that paged_fill_cache fills the current chunk properly. + Be vary careful that we don't pick up cached sin/cos values since they will be incorrect. + """ + for chunk_start in range(0, seq_len, chunk_size): + chunk_end = chunk_start + chunk_size + assert chunk_end <= seq_len, "Chunk end should be less than seq_len" + chunk_page_table = page_table[ + :, + chunk_start // paged_attention_config.block_size : chunk_end // paged_attention_config.block_size, + ] + chunk_page_table_tt = ttnn.as_tensor( + chunk_page_table, + dtype=ttnn.int32, + layout=ttnn.ROW_MAJOR_LAYOUT, + device=t3k_mesh_device, + memory_config=ttnn.DRAM_MEMORY_CONFIG, + mesh_mapper=ReplicateTensorToMesh(t3k_mesh_device), + ) + # SDPA requires that the page table batch dim matches the input batch dim, which must be 1 in prefill + prefill_page_table = page_table[0:1, :] + prefill_page_table_tt = ttnn.as_tensor( + prefill_page_table, + dtype=ttnn.int32, + layout=ttnn.ROW_MAJOR_LAYOUT, + device=t3k_mesh_device, + memory_config=ttnn.DRAM_MEMORY_CONFIG, + mesh_mapper=ReplicateTensorToMesh(t3k_mesh_device), + ) - tt_out = tt_LlamaAttention_model( - attention_input, - rot_mat, - start_pos, - cache_idxs=cache_idxs if mode == "decode" else None, - page_table=page_table_tt, - mode=mode, - ) + chunk_tt_input = tt_input[:, chunk_start:chunk_end] + # TT hardware execution ------------------------------------------------------------- + attention_input, _, rot_mat, cache_idxs = tt_llama_attention_prepare_inputs( + tt_LlamaAttention_model, + chunk_tt_input, + chunk_start, + mode, + configuration.rope_theta, + rope_setup=None, + use_scaled_rope=configuration.use_scaled_rope, + ) + tt_chunk_out = tt_LlamaAttention_model( + attention_input, + rot_mat, + None, + cache_idxs=None, + page_table=prefill_page_table_tt, + mode=mode, + chunk_page_table=chunk_page_table_tt, + chunk_start_idx=chunk_start, + ) + + tt_chunk_out = ttnn.from_device(tt_chunk_out) + tt_chunk_out = ttnn.to_torch(tt_chunk_out, mesh_composer=ConcatMeshToTensor(t3k_mesh_device, dim=3)) + tt_chunk_out = tt_chunk_out.permute(2, 1, 0, 3).squeeze(1) # [batch, seq_len, hidden_dim] - tt_out = ttnn.from_device(tt_out) - tt_out = ttnn.to_torch(tt_out, mesh_composer=ConcatMeshToTensor(t3k_mesh_device, dim=3)) - tt_out = tt_out.permute(2, 1, 0, 3).squeeze(1) # [batch, seq_len, hidden_dim] - if mode == "decode": - tt_out = tt_out[:batch] + # check outputs ---------------------------------------------------------------------- + pytorch_chunk_out = pytorch_out[:, chunk_start:chunk_end] + does_pass, output_pcc = comp_pcc(pytorch_chunk_out, tt_chunk_out, pcc) + logger.info(f"Chunk {chunk_start} output: {output_pcc}") + all_pccs.append(extract_pcc_from_log(output_pcc)) + + else: + # TT hardware execution ------------------------------------------------------------- + attention_input, start_pos, rot_mat, cache_idxs = tt_llama_attention_prepare_inputs( + tt_LlamaAttention_model, + tt_input, + start_pos, + mode, + configuration.rope_theta, + rope_setup=rope_setup if mode == "decode" else None, + use_scaled_rope=configuration.use_scaled_rope, + ) + + tt_out = tt_LlamaAttention_model( + attention_input, + rot_mat, + start_pos, + cache_idxs=cache_idxs, + page_table=page_table_tt, + mode=mode, + ) - # check outputs ---------------------------------------------------------------------- - does_pass, output_pcc = comp_pcc(pytorch_out, tt_out, pcc) - logger.info(f"Output: {output_pcc}") - all_pccs.append(extract_pcc_from_log(output_pcc)) + tt_out = ttnn.from_device(tt_out) + tt_out = ttnn.to_torch(tt_out, mesh_composer=ConcatMeshToTensor(t3k_mesh_device, dim=3)) + tt_out = tt_out.permute(2, 1, 0, 3).squeeze(1) # [batch, seq_len, hidden_dim] + + if mode == "decode": + tt_out = tt_out[:batch] + + # check outputs ---------------------------------------------------------------------- + does_pass, output_pcc = comp_pcc(pytorch_out, tt_out, pcc) + logger.info(f"Output: {output_pcc}") + all_pccs.append(extract_pcc_from_log(output_pcc)) if does_pass: logger.info(f"[start_pos={start_pos}] {llama_version} Attention output Passed!") @@ -387,7 +455,6 @@ def run_test_LlamaAttention_inference( # concat the pasts by heads tt_layer_present_all = [ttnn.from_device(lp) for lp in tt_LlamaAttention_model.layer_past] if paged_attention: - tt_layer_present_all = [ttnn.from_device(lp) for lp in tt_LlamaAttention_model.layer_past] tt_layer_present_all = [ ( ttnn.to_torch(lp, mesh_composer=ConcatMeshToTensor(t3k_mesh_device, dim=1))[reverse_permutation] @@ -451,9 +518,13 @@ def run_test_LlamaAttention_inference( ), ) @pytest.mark.parametrize( - "paged_attention", - (True, False), - ids=("paged_attention", "non_paged_attention"), + "paged_attention, is_chunked_prefill, chunk_size", + ( + (True, True, 128), + (True, False, None), + (False, False, None), + ), + ids=("chunked_paged_attention", "standard_paged_attention", "non_paged_attention"), ) def test_LlamaAttention_inference( batch, @@ -464,6 +535,8 @@ def test_LlamaAttention_inference( max_context_len, llama_version, paged_attention, + is_chunked_prefill, + chunk_size, use_program_cache, ): if seq_len == 1 and batch != max_batch_size: @@ -475,6 +548,15 @@ def test_LlamaAttention_inference( if llama_version == "llama2" and seq_len > 2048: pytest.skip(f"Llama2 with seq_len={seq_len} is not supported (max 2048)") + if is_chunked_prefill and seq_len == 1: + pytest.skip("Chunked prefill is not valid for decode mode tests") + + if is_chunked_prefill: + assert paged_attention, "Chunked prefill is only valid for paged attention" + assert chunk_size is not None, "Chunk size must be provided for chunked prefill" + assert chunk_size > 0, "Chunk size must be greater than 0" + assert seq_len % chunk_size == 0, "Sequence length must be divisible by chunk size" + model_config, ckpt_dir, tokenizer_path, cache_path = setup_llama_env( llama_version=llama_version, max_batch_size=max_batch_size, @@ -494,4 +576,6 @@ def test_LlamaAttention_inference( tokenizer_path, cache_path, paged_attention, + is_chunked_prefill, + chunk_size, ) diff --git a/models/demos/t3000/llama2_70b/tests/test_llama_device_perf.py b/models/demos/t3000/llama2_70b/tests/test_llama_device_perf.py index e53711014339..a2eee1391703 100644 --- a/models/demos/t3000/llama2_70b/tests/test_llama_device_perf.py +++ b/models/demos/t3000/llama2_70b/tests/test_llama_device_perf.py @@ -76,6 +76,7 @@ def test_run_device_perf_llama( t3k_mesh_device, batch, seq_len, + max_batch_size, max_context_len, N_LAYERS_TO_PCC[n_layers], model_config, diff --git a/models/demos/t3000/llama2_70b/tests/test_llama_model.py b/models/demos/t3000/llama2_70b/tests/test_llama_model.py index 14c4a78db8a3..ef41fbe6d892 100644 --- a/models/demos/t3000/llama2_70b/tests/test_llama_model.py +++ b/models/demos/t3000/llama2_70b/tests/test_llama_model.py @@ -6,7 +6,7 @@ from loguru import logger import torch import ttnn -from ttnn import ConcatMeshToTensor +from ttnn import ConcatMeshToTensor, ReplicateTensorToMesh import os import scipy @@ -31,6 +31,7 @@ check_kv_cache, ) +from models.demos.t3000.llama2_70b.tests.test_llama_attention import PagedAttentionConfig DEVICE_PERF_START_SIGNPOST = "START_PERF_RUN" DEVICE_PERF_END_SIGNPOST = "END_PERF_RUN" @@ -64,6 +65,7 @@ def run_test_LlamaModel_inference( t3k_mesh_device, batch, seq_len, + max_batch_size, max_context_len, pcc, model_config, @@ -75,6 +77,8 @@ def run_test_LlamaModel_inference( prompt_file=None, generation_start_pos=0, device_perf=False, # set to True when measuring device perf + paged_attention=False, + chunk_size=None, ): if device_perf: # Enable tracy signpost support in device perf runs only from tracy import signpost @@ -108,6 +112,20 @@ def run_test_LlamaModel_inference( # PyTorch model -------------------------------------------------------------------- pytorch_model = PytorchLlamaModel(hugging_face_reference_model) # TT model ------------------------------------------------------------------------- + + page_table = None + paged_attention_config = None + if paged_attention: + paged_attention_config = PagedAttentionConfig() + + # Implied shuffling of blocks + permutation = torch.randperm(paged_attention_config.max_num_blocks) + # Page table which maps virtua blocks to physical + reverse_permutation = torch.argsort(permutation) + page_table = reverse_permutation.reshape( + max_batch_size, paged_attention_config.max_num_blocks // max_batch_size + ) + tt_model = TtLlamaModel_optimized( t3k_mesh_device, state_dict, @@ -116,6 +134,7 @@ def run_test_LlamaModel_inference( model_config, configuration, cache_path=cache_path, + paged_attention_config=paged_attention_config, ) mode = "prefill" if seq_len > 1 else "decode" @@ -155,36 +174,96 @@ def run_test_LlamaModel_inference( pt_inp_ids, start_pos, ) + if mode == "decode": + pytorch_out = pytorch_out.squeeze().reshape(batch, -1) # [batch, hidden_dim] + else: + pytorch_out = pytorch_out.squeeze().reshape(seq_len, -1) # [seq, hidden_dim] if device_perf: signpost(DEVICE_PERF_START_SIGNPOST) # start for device perf measurement # TT hardware execution ------------------------------------------------------------- - tt_inp_emb, start_pos, rot_mat, cache_idxs, _ = tt_model.prepare_device_inputs(tt_inp_ids, start_pos, mode=mode) + if chunk_size is not None: + tt_out = [] + assert mode == "prefill" + for chunk_start in range(0, seq_len, chunk_size): + logger.info(f"Chunk start: {chunk_start}") + chunk_end = chunk_start + chunk_size + assert chunk_end <= seq_len, "Chunk end should be less than seq_len" + chunk_page_table = page_table[ + :, + chunk_start // paged_attention_config.block_size : chunk_end // paged_attention_config.block_size, + ] + # SDPA requires that the page table batch dim matches the input batch dim, which must be 1 in prefill + prefill_page_table = page_table[0:1, :] + + chunk_tt_input = tt_inp_ids[:, chunk_start:chunk_end] + # TT hardware execution ------------------------------------------------------------- + ( + tt_inp_emb, + start_pos, + rot_mat, + rot_idxs_tt, + cache_idxs, + page_table_tt, + chunk_page_table_tt, + ) = tt_model.prepare_inputs( + chunk_tt_input, + chunk_start, + mode=mode, + page_table=prefill_page_table, + chunk_page_table=chunk_page_table, + ) + + tt_chunk_out = tt_model( + tt_inp_emb, + rot_mat, + start_pos, + cache_idxs=cache_idxs, + mode=mode, + page_table=page_table_tt, + chunk_page_table=chunk_page_table_tt, + chunk_start_idx=chunk_start, + ) + + tt_chunk_out = ttnn.from_device(tt_chunk_out) + tt_chunk_out = ttnn.to_torch(tt_chunk_out, mesh_composer=ConcatMeshToTensor(t3k_mesh_device, dim=3)) + tt_chunk_out = tt_chunk_out[..., : configuration.vocab_size] + tt_chunk_out = tt_chunk_out.permute(2, 1, 0, 3).squeeze() # [batch, seq_len, hidden_dim] + + tt_out.append(tt_chunk_out) + + tt_out = torch.cat(tt_out, dim=0).float() - tt_out = tt_model( - tt_inp_emb, - rot_mat, - start_pos, - cache_idxs=cache_idxs, - mode=mode, - ) - del tt_inp_emb, rot_mat + else: + if mode == "decode": + tt_inp_emb, start_pos, rot_mat, cache_idxs, *_ = tt_model.prepare_device_inputs_decode( + tt_inp_ids, start_pos, mode=mode + ) + else: + tt_inp_emb, start_pos, rot_mat, cache_idxs, *_ = tt_model.prepare_inputs( + tt_inp_ids, start_pos, mode=mode + ) + + tt_out = tt_model( + tt_inp_emb, + rot_mat, + start_pos, + cache_idxs=cache_idxs, + mode=mode, + ) + del tt_inp_emb, rot_mat - tt_out = ttnn.from_device(tt_out) - tt_out = ttnn.to_torch(tt_out, mesh_composer=ConcatMeshToTensor(t3k_mesh_device, dim=3)) + tt_out = ttnn.from_device(tt_out) + tt_out = ttnn.to_torch(tt_out, mesh_composer=ConcatMeshToTensor(t3k_mesh_device, dim=3)) - if device_perf: - signpost(DEVICE_PERF_END_SIGNPOST) # end for device perf measurement + if device_perf: + signpost(DEVICE_PERF_END_SIGNPOST) # end for device perf measurement - tt_out = tt_out[..., : configuration.vocab_size] - tt_out = tt_out.permute(2, 1, 0, 3).squeeze() # [batch, hidden_dim] - if mode == "decode": - tt_out = tt_out[:batch] - tt_out = tt_out.float() - if mode == "decode": - pytorch_out = pytorch_out.squeeze().reshape(batch, -1) # [batch, hidden_dim] - else: - pytorch_out = pytorch_out.squeeze().reshape(seq_len, -1) # [seq, hidden_dim] + tt_out = tt_out[..., : configuration.vocab_size] + tt_out = tt_out.permute(2, 1, 0, 3).squeeze() # [batch, hidden_dim] + if mode == "decode": + tt_out = tt_out[:batch] + tt_out = tt_out.float() # check outputs ---------------------------------------------------------------------- does_pass, output_pcc = comp_pcc(pytorch_out, tt_out, pcc) @@ -219,6 +298,29 @@ def run_test_LlamaModel_inference( logger.info(f"Average Top-5 over {len(all_top5)} tokens: {sum(all_top5) / len(all_top5)}") # Check kv cache # PyTorch output -------------------------------------------------------------------- + tt_layer_present_all = [ttnn.from_device(lp) for lp in tt_model.layers[0].attention.layer_past] + if paged_attention: + tt_layer_present_all = [ + ( + ttnn.to_torch(lp, mesh_composer=ConcatMeshToTensor(t3k_mesh_device, dim=1))[reverse_permutation] + .reshape( + max_batch_size, + paged_attention_config.max_num_blocks // max_batch_size, + configuration.n_kv_heads, + paged_attention_config.block_size, + tt_model.head_dim, + ) + .transpose(1, 2) + .reshape(max_batch_size, configuration.n_kv_heads, -1, tt_model.head_dim)[:batch, ...] + ) + for lp in tt_layer_present_all + ] + else: + tt_layer_present_all = [ + ttnn.to_torch(lp, mesh_composer=ConcatMeshToTensor(t3k_mesh_device, dim=1))[:batch, ...] + for lp in tt_layer_present_all + ] + pytorch_layer_present = [ pytorch_model.model.layers[0] .attention.cache_k.clone() @@ -228,12 +330,6 @@ def run_test_LlamaModel_inference( .permute(0, 2, 1, 3)[:batch, ...], # [batch, n_kv_heads, seq, head_dim] ] - tt_layer_present_all = [ttnn.from_device(lp) for lp in tt_model.layers[0].attention.layer_past] - tt_layer_present_all = [ - ttnn.to_torch(lp, mesh_composer=ConcatMeshToTensor(t3k_mesh_device, dim=1))[:batch, ...] - for lp in tt_layer_present_all - ] - cache_test_pass = check_kv_cache( pytorch_layer_present, tt_layer_present_all, @@ -298,6 +394,14 @@ def run_test_LlamaModel_inference( ("models/demos/t3000/llama2_70b/demo/data/a_tale_of_two_cities.txt", None), ids=("prompt_input", "rand_input"), ) +@pytest.mark.parametrize( + "paged_attention, chunk_size", + ( + (True, 128), + (False, None), + ), + ids=("chunked_paged_attention", "unpaged_attention"), +) def test_LlamaModel_inference( batch, seq_len, @@ -308,8 +412,18 @@ def test_LlamaModel_inference( max_context_len, llama_version, prompt_file, + paged_attention, + chunk_size, use_program_cache, ): + if chunk_size is not None and seq_len == 1: + pytest.skip("Chunked prefill is not valid for decode mode tests") + + if chunk_size is not None: + assert paged_attention, "Chunked prefill is only valid for paged attention" + assert chunk_size > 0, "Chunk size must be greater than 0" + assert seq_len % chunk_size == 0, "Sequence length must be divisible by chunk size" + is_decode = seq_len == 1 if is_decode and batch != max_batch_size: pytest.skip(f"Input batch size should match max_batch_size") @@ -332,6 +446,7 @@ def test_LlamaModel_inference( t3k_mesh_device, batch, seq_len, + max_batch_size, max_context_len, pcc, model_config, @@ -341,4 +456,6 @@ def test_LlamaModel_inference( tokenizer_path, cache_path, prompt_file=prompt_file, + paged_attention=paged_attention, + chunk_size=chunk_size, ) diff --git a/models/demos/t3000/llama2_70b/tests/test_llama_model_t3000.py b/models/demos/t3000/llama2_70b/tests/test_llama_model_t3000.py index 6e2b753af733..852fbcbf1736 100644 --- a/models/demos/t3000/llama2_70b/tests/test_llama_model_t3000.py +++ b/models/demos/t3000/llama2_70b/tests/test_llama_model_t3000.py @@ -71,6 +71,7 @@ def test_LlamaModel_inference( t3k_mesh_device, batch, seq_len, + max_batch_size, max_context_len, N_LAYERS_TO_PCC[n_layers], model_config, diff --git a/models/demos/t3000/llama2_70b/tests/test_llama_perf.py b/models/demos/t3000/llama2_70b/tests/test_llama_perf.py index 29ca2e75ff8a..42f62e650a79 100644 --- a/models/demos/t3000/llama2_70b/tests/test_llama_perf.py +++ b/models/demos/t3000/llama2_70b/tests/test_llama_perf.py @@ -154,7 +154,7 @@ def run_test_LlamaModel_end_to_end( if user_id == 0 or user_id == 25: profiler.start(f"processing_of_prefill_input_{user_id}") - tt_inp_emb, start_pos, rot_mat, _, _ = tt_model.prepare_device_inputs( + tt_inp_emb, start_pos, rot_mat, *_ = tt_model.prepare_inputs( prefill_ids[user_id : user_id + 1], start_pos=0, mode="prefill" ) if user_id == 0 or user_id == 25: @@ -204,7 +204,7 @@ def run_test_LlamaModel_end_to_end( if cur_pos == 0 or cur_pos == 35: # Skip the first few iterations to warm up profiler.start(f"processing_of_decode_input_{cur_pos}") - tt_inp_emb, start_pos, rot_mat, cache_idxs, _ = tt_model.prepare_device_inputs( + tt_inp_emb, start_pos, rot_mat, cache_idxs, _ = tt_model.prepare_device_inputs_decode( decode_ids, start_pos, mode="decode" ) diff --git a/models/demos/t3000/llama2_70b/tests/test_llama_perf_decode.py b/models/demos/t3000/llama2_70b/tests/test_llama_perf_decode.py index fd885c6f0e00..97a9e256ae25 100644 --- a/models/demos/t3000/llama2_70b/tests/test_llama_perf_decode.py +++ b/models/demos/t3000/llama2_70b/tests/test_llama_perf_decode.py @@ -129,7 +129,7 @@ def run_test_LlamaModel_end_to_end( ##### Prepare Inputs ##### prev_pos = total_len - 1 - tt_inp_emb, prev_pos, rot_mat, cache_idxs, _ = tt_model.prepare_device_inputs(tokens, prev_pos) + tt_inp_emb, prev_pos, rot_mat, cache_idxs, _ = tt_model.prepare_device_inputs_decode(tokens, prev_pos) ##### Compile Model ##### logger.info("Compiling model") @@ -320,7 +320,9 @@ def run_test_LlamaModel_end_to_end_hybrid_data_tensor_parallel( ##### Prepare Inputs ##### prev_pos = total_len - 1 - tt_inp_emb, prev_pos, rot_mat, cache_idxs, _ = tt_model.prepare_device_inputs(tokens, prev_pos, mode="decode") + tt_inp_emb, prev_pos, rot_mat, cache_idxs, _ = tt_model.prepare_device_inputs_decode( + tokens, prev_pos, mode="decode" + ) ##### Compile Model ##### logger.info("Compiling model") diff --git a/models/demos/t3000/llama2_70b/tests/test_llama_stress_test.py b/models/demos/t3000/llama2_70b/tests/test_llama_stress_test.py index bbfd1e68bd65..5840cba8dbfb 100644 --- a/models/demos/t3000/llama2_70b/tests/test_llama_stress_test.py +++ b/models/demos/t3000/llama2_70b/tests/test_llama_stress_test.py @@ -98,7 +98,7 @@ def run_test_LlamaModel_stress_test( start_pos = 0 prev_pos = start_pos for cur_pos in tqdm(range(start_pos + 1, total_len), desc="Decode to 2k Progress", leave=False, colour="green"): - tt_inp_emb, prev_pos, rot_mat, cache_idxs, _ = tt_model.prepare_device_inputs( + tt_inp_emb, prev_pos, rot_mat, cache_idxs, _ = tt_model.prepare_device_inputs_decode( tokens[:, prev_pos:cur_pos], prev_pos ) diff --git a/models/demos/t3000/llama2_70b/tt/generator_vllm.py b/models/demos/t3000/llama2_70b/tt/generator_vllm.py index 9538ace45b14..86dbb12f25b5 100644 --- a/models/demos/t3000/llama2_70b/tt/generator_vllm.py +++ b/models/demos/t3000/llama2_70b/tt/generator_vllm.py @@ -15,20 +15,7 @@ ) from models.demos.t3000.llama2_70b.reference.llama.llama.model import ModelArgs as ReferenceModelArgs -from vllm.inputs import INPUT_REGISTRY, DecoderOnlyInputs, EncoderDecoderInputs, InputContext - -def input_processor_for_llama70b(ctx: InputContext, inputs: Union[DecoderOnlyInputs, EncoderDecoderInputs]): - prompt_len = len(inputs.get("prompt_token_ids")) - if prompt_len > 32768: - raise ValueError( - f"TT LLama70b does not yet support prompts longer than 32768 tokens (received prompt with {prompt_len} tokens)" - ) - - return inputs - - -@INPUT_REGISTRY.register_input_processor(input_processor_for_llama70b) class TtLlamaForCausalLM(TtLlamaModelForGeneration): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/models/demos/t3000/llama2_70b/tt/llama_attention_optimized.py b/models/demos/t3000/llama2_70b/tt/llama_attention_optimized.py index 5fce23560c11..0779d352c796 100644 --- a/models/demos/t3000/llama2_70b/tt/llama_attention_optimized.py +++ b/models/demos/t3000/llama2_70b/tt/llama_attention_optimized.py @@ -206,6 +206,8 @@ def __call__( page_table=None, kv_cache=None, mode="decode", + chunk_page_table=None, + chunk_start_idx=None, ): # Decode should have input tensor of shape (seqlen=1, 1, batch, hidden_size) if mode == "decode": @@ -219,7 +221,15 @@ def __call__( ) # Prefill should have input tensor of shape (1, batch=1, seqlen, hidden_size) elif mode == "prefill": - return self.prefill_forward(xs, rot_mats, user_id, page_table=page_table, kv_cache=kv_cache) + return self.prefill_forward( + xs, + rot_mats, + user_id, + page_table=page_table, + kv_cache=kv_cache, + chunk_page_table=chunk_page_table, + chunk_start_idx=chunk_start_idx, + ) else: raise ValueError(f"Unknown llm_mode: {mode}") @@ -350,10 +360,26 @@ def attn_selfout( return tt_matmul_out_tensor - def prefill_forward(self, xs, rot_mats, user_id: int = 0, page_table=None, kv_cache=None): + def prefill_forward( + self, + xs, + rot_mats, + user_id: int = 0, + page_table=None, + kv_cache=None, + chunk_page_table=None, + chunk_start_idx=None, + ): query_layer, key_layer, value_layer = self.prefill_attn_qkv(xs, rot_mats) attn_outputs = self.prefill_attn_mqa( - query_layer, key_layer, value_layer, user_id, page_table=page_table, kv_cache=kv_cache + query_layer, + key_layer, + value_layer, + user_id, + page_table=page_table, + kv_cache=kv_cache, + chunk_page_table=chunk_page_table, + chunk_start_idx=chunk_start_idx, ) return self.prefill_attn_selfout(attn_outputs) @@ -429,7 +455,17 @@ def prefill_attn_qkv( return query_layer_ret, key_layer_ret, value_layer - def prefill_attn_mqa(self, query_layer, key_layer, value_layer, user_id: int = 0, page_table=None, kv_cache=None): + def prefill_attn_mqa( + self, + query_layer, + key_layer, + value_layer, + user_id: int = 0, + page_table=None, + kv_cache=None, + chunk_page_table=None, + chunk_start_idx=None, + ): if kv_cache: keys = kv_cache[self.layer_num][0] values = kv_cache[self.layer_num][1] @@ -438,11 +474,21 @@ def prefill_attn_mqa(self, query_layer, key_layer, value_layer, user_id: int = 0 values = self.layer_past[1] if page_table: + # In the case that the tokens have been padded along the seq len dimension, we need to fill the cache with the unpadded k/v values. + # Assume that the page table does not have padding, so we can use it to get the unpadded page len. + block_size = keys.shape[2] + # If chunked prefill, use chunk_page_table if given, otherwise use page_table. + fill_page_table = chunk_page_table if chunk_page_table is not None else page_table + page_len = fill_page_table.shape[1] * block_size + + k_fill_sliced = key_layer[:, :, :page_len, :] if page_len < key_layer.shape[2] else key_layer + v_fill_sliced = value_layer[:, :, :page_len, :] if page_len < value_layer.shape[2] else value_layer + ttnn.experimental.paged_fill_cache( - keys, ttnn.experimental.typecast(key_layer, self.kv_dtype), page_table, batch_idx=user_id + keys, ttnn.experimental.typecast(k_fill_sliced, self.kv_dtype), fill_page_table, batch_idx=user_id ) ttnn.experimental.paged_fill_cache( - values, ttnn.experimental.typecast(value_layer, self.kv_dtype), page_table, batch_idx=user_id + values, ttnn.experimental.typecast(v_fill_sliced, self.kv_dtype), fill_page_table, batch_idx=user_id ) else: ttnn.fill_cache(keys, ttnn.experimental.typecast(key_layer, self.kv_dtype), user_id) @@ -450,21 +496,37 @@ def prefill_attn_mqa(self, query_layer, key_layer, value_layer, user_id: int = 0 seq_len = query_layer.shape[2] q_chunk_size = 128 if seq_len % 128 == 0 else 32 - k_chunk_size = q_chunk_size + k_chunk_size = 512 if seq_len % 512 == 0 else 128 if seq_len % 128 == 0 else 32 pc_sdpa = ttnn.SDPAProgramConfig( compute_with_storage_grid_size=[8, 7], q_chunk_size=q_chunk_size, k_chunk_size=k_chunk_size, + exp_approx_mode=False, ) - attn_output = ttnn.transformer.scaled_dot_product_attention( - query_layer, - key_layer, - value_layer, - is_causal=True, - scale=self.scale, - program_config=pc_sdpa, - ) + if chunk_start_idx is not None: + """ + Chunked prefill mode uses the chunk_start_idx to offset Q into the filled paged K and V cache. + """ + attn_output = ttnn.transformer.chunked_scaled_dot_product_attention( + query_layer, + keys, + values, + page_table, + chunk_start_idx, + program_config=pc_sdpa, + compute_kernel_config=self.model_config["SDPA_COMPUTE_KERNEL_CONFIG"], + ) + else: + attn_output = ttnn.transformer.scaled_dot_product_attention( + query_layer, + key_layer, + value_layer, + is_causal=True, + scale=self.scale, + program_config=pc_sdpa, + compute_kernel_config=self.model_config["SDPA_COMPUTE_KERNEL_CONFIG"], + ) # deallocate keys and values query_layer.deallocate(True) diff --git a/models/demos/t3000/llama2_70b/tt/llama_decoder_optimized.py b/models/demos/t3000/llama2_70b/tt/llama_decoder_optimized.py index 84c6fdb06fba..a1bd6b1565e7 100644 --- a/models/demos/t3000/llama2_70b/tt/llama_decoder_optimized.py +++ b/models/demos/t3000/llama2_70b/tt/llama_decoder_optimized.py @@ -154,9 +154,20 @@ def __call__( page_table=None, kv_cache=None, mode="decode", + chunk_page_table=None, + chunk_start_idx=None, ) -> ttnn.Tensor: if mode == "prefill": - return self.prefill_forward(xs, rot_mats, start_pos, user_id, page_table=page_table, kv_cache=kv_cache) + return self.prefill_forward( + xs, + rot_mats, + start_pos, + user_id, + page_table=page_table, + kv_cache=kv_cache, + chunk_page_table=chunk_page_table, + chunk_start_idx=chunk_start_idx, + ) elif mode == "decode": return self.decode_forward(xs, rot_mats, start_pos, cache_idxs, page_table=page_table, kv_cache=kv_cache) else: @@ -276,6 +287,8 @@ def prefill_forward( user_id: int = 0, page_table=None, kv_cache=None, + chunk_page_table=None, + chunk_start_idx=None, ) -> List[ttnn.Tensor]: attn_norm_interleaved = self.tt_distributed_rmsnorm(xs, self.norm_eps, self.attn_norm_sharded) attn_norm_interleaved = ttnn.all_gather( @@ -294,6 +307,8 @@ def prefill_forward( page_table=page_table, kv_cache=kv_cache, mode="prefill", + chunk_page_table=chunk_page_table, + chunk_start_idx=chunk_start_idx, ) attn_norm_interleaved.deallocate(True) diff --git a/models/demos/t3000/llama2_70b/tt/llama_generation.py b/models/demos/t3000/llama2_70b/tt/llama_generation.py index c74423249678..f0de257f7302 100644 --- a/models/demos/t3000/llama2_70b/tt/llama_generation.py +++ b/models/demos/t3000/llama2_70b/tt/llama_generation.py @@ -74,7 +74,7 @@ def capture_trace(self, tokens: torch.Tensor, start_pos: int, page_table=None, k tt_page_table, tt_inp, rot_idxs_tt, - ) = self.tt_model.prepare_device_inputs( + ) = self.tt_model.prepare_device_inputs_decode( tokens, start_pos, mode="decode", @@ -173,7 +173,7 @@ def decode_forward(self, tokens: torch.Tensor, start_pos: int, page_table=None, batch = tokens.shape[0] # Get inputs on device - tt_inp_emb, start_pos, rot_mat, cache_idxs_tt, tt_page_table = self.tt_model.prepare_device_inputs( + tt_inp_emb, start_pos, rot_mat, cache_idxs_tt, tt_page_table = self.tt_model.prepare_device_inputs_decode( tokens, start_pos, mode="decode", page_table=page_table ) @@ -205,29 +205,116 @@ def prefill_forward_single_user( assert batch == 1 assert start_pos == 0, "start_pos must be 0 for prefill_forward_single_user" - tt_inp_emb, start_pos, rot_mat, _, tt_page_table = self.tt_model.prepare_device_inputs( - tokens, start_pos=start_pos, valid_seq_len=seq_len, mode="prefill", page_table=page_table - ) + use_chunked_prefill = seq_len > self.tt_model.model_config["MAX_PREFILL_SEQ_LEN"] + if use_chunked_prefill: + """ + Chunked prefill requires paged attention. There are some strange constraints which we must meet: + - page_table, which is used in SDPA, must match batch size of inputs, which is 1. This is because SDPA + checks that page table batch dim matches input batch dim. Therefore we must slice the page table for the current user. + - page_table must also have enough entries in each chunk, so it will be padded with zeros if necessary. + - chunked_page_table is the slice of the page table for the current chunk. This is used by paged_fill_cache + to keep it otherwise unaware that it is operating on a chunk. + - due to the above point, we must always set user_id to 0 for chunked prefill. + """ + assert page_table is not None, "page_table must be provided for chunked prefill" + assert kv_cache is not None, "kv_cache must be provided for chunked prefill" + chunk_size = get_max_prefill_chunk_size(seq_len, self.tt_model.model_config["MAX_PREFILL_SEQ_LEN"]) + block_size = get_block_size(kv_cache) + last_token_idx_in_chunk = last_token_idx % chunk_size if last_token_idx is not None else None + # Calculate which chunk contains the last_token_idx + last_chunk_start = (last_token_idx // chunk_size) * chunk_size if last_token_idx is not None else None + page_table_user = page_table[user_id : user_id + 1, :] + # Pad page table to match number of blocks in seq_len + num_padding_blocks = num_blocks_in_seq(seq_len, block_size) - page_table_user.shape[1] + page_table_user_padded = torch.cat( + [page_table_user, torch.zeros(1, num_padding_blocks, dtype=torch.int32)], dim=-1 + ) + CHUNK_USER_ID = 0 + + logits_list = [] + for chunk_start in range(0, seq_len, chunk_size): + chunk_end = chunk_start + chunk_size + assert ( + chunk_end <= seq_len + ), f"Chunk end should be less than seq_len, got chunk_end={chunk_end} and seq_len={seq_len}" + chunk_tokens = tokens[:, chunk_start:chunk_end] + chunk_page_table = page_table_user[:, chunk_start // block_size : chunk_end // block_size] + + ( + tt_inp_emb, + start_pos, + rot_mat, + _rot_idxs_tt, + _cache_idxs_tt, + page_table_tt, + chunk_page_table_tt, + ) = self.tt_model.prepare_inputs( + chunk_tokens, + start_pos=chunk_start, + mode="prefill", + page_table=page_table_user_padded, + chunk_page_table=chunk_page_table, + ) + tt_logits = self.tt_model( + tt_inp_emb, + rot_mat, + start_pos, + user_id=CHUNK_USER_ID, + last_token_idx=last_token_idx_in_chunk, + page_table=page_table_tt, + kv_cache=kv_cache, + mode="prefill", + chunk_page_table=chunk_page_table_tt, + chunk_start_idx=chunk_start, + ) - tt_logits = self.tt_model( - tt_inp_emb, - rot_mat, - start_pos, - user_id=user_id, - last_token_idx=last_token_idx, - page_table=tt_page_table, - kv_cache=kv_cache, - mode="prefill", - ) + logits = self._process_logits(tt_logits) + logits = logits.squeeze(1) + ttnn.deallocate(tt_logits) - del tt_inp_emb - del rot_mat - del tt_page_table + if last_token_idx is not None: + # If this was the chunk containing last_token_idx, we're done + if chunk_start == last_chunk_start: + return logits + else: + logits_list.append(logits) - logits = self._process_logits(tt_logits) - logits = logits.squeeze(1) - del tt_logits - return logits + # Concatenate all logits + logits = torch.cat(logits_list, dim=-2) + return logits + + else: + ( + tt_inp_emb, + start_pos, + rot_mat, + _rot_idxs_tt, + _cache_idxs_tt, + tt_page_table, + _chunk_page_table, + ) = self.tt_model.prepare_inputs( + tokens, start_pos=start_pos, valid_seq_len=seq_len, mode="prefill", page_table=page_table + ) + + tt_logits = self.tt_model( + tt_inp_emb, + rot_mat, + start_pos, + user_id=user_id, + last_token_idx=last_token_idx, + page_table=tt_page_table, + kv_cache=kv_cache, + mode="prefill", + ) + + del tt_inp_emb + del rot_mat + del tt_page_table + + logits = self._process_logits(tt_logits) + logits = logits.squeeze(1) + del tt_logits + return logits def prefill_forward(self, tokens: torch.Tensor, start_pos: int, page_table=None, kv_cache=None, prompt_lens=None): batch, batch_seq_len = tokens.shape @@ -248,13 +335,7 @@ def prefill_forward(self, tokens: torch.Tensor, start_pos: int, page_table=None, [tokens[user_id : user_id + 1, :seq_len], torch.zeros(1, prefill_seq_len - seq_len).long()], dim=-1 ) if page_table is not None: - block_size = get_block_size(kv_cache) - num_padding_blocks = num_blocks_in_seq(prefill_seq_len, block_size) - num_blocks_in_seq( - seq_len, block_size - ) - page_table_user = torch.cat( - [page_table, torch.zeros(batch, num_padding_blocks, dtype=torch.int32)], dim=-1 - ) + page_table_user = _get_prefill_user_page_table(page_table, kv_cache, seq_len) logger.info(f"Filling kv cache for user {user_id + 1}") @@ -281,6 +362,13 @@ def _process_logits(self, tt_logits): return logits[..., : self.params.vocab_size].float() +def _get_prefill_user_page_table(page_table, kv_cache, prefill_len): + # Ensure page_table is not padded with extra blocks for paged_fill_cache to work properly + block_size = get_block_size(kv_cache) + num_blocks = num_blocks_in_seq(prefill_len, block_size) + return page_table[:, :num_blocks] + + def get_padded_prefill_len(seq_len): """ If seq_len is less than 32, pad to 32 @@ -305,3 +393,34 @@ def num_blocks_in_seq(seq_len, block_size): def nearest_pow_2(x): return 2 ** math.ceil(math.log2(x)) + + +def get_max_prefill_chunk_size(seq_len, max_prefill_seq_len): + """ + Determine the largest multiple of 1024 that divides `seq_len` and is less than or equal to `max_prefill_seq_len`. + + **Assumptions**: + - `seq_len` is a multiple of 1024. + - `max_prefill_seq_len` is a multiple of 1024. + """ + + if not isinstance(seq_len, int) or not isinstance(max_prefill_seq_len, int): + raise TypeError("Both seq_len and max_prefill_seq_len must be integers.") + if seq_len <= 0 or max_prefill_seq_len <= 0: + raise ValueError("Both seq_len and max_prefill_seq_len must be positive integers.") + + if seq_len % 1024 != 0: + raise ValueError("seq_len must be a multiple of 1024.") + if max_prefill_seq_len % 1024 != 0: + raise ValueError("max_prefill_seq_len must be a multiple of 1024.") + + # Calculate the maximum possible chunk size + # It cannot exceed either max_prefill_seq_len or seq_len + max_possible_chunk = min(max_prefill_seq_len, seq_len) + + # Iterate from the largest possible multiple of 1024 down to 1024 + for chunk_size in range(max_possible_chunk, 0, -1024): + if seq_len % chunk_size == 0: + return chunk_size + + raise ValueError("No valid chunk size found") diff --git a/models/demos/t3000/llama2_70b/tt/llama_model_optimized.py b/models/demos/t3000/llama2_70b/tt/llama_model_optimized.py index c821a40173fd..32bce8227ecc 100644 --- a/models/demos/t3000/llama2_70b/tt/llama_model_optimized.py +++ b/models/demos/t3000/llama2_70b/tt/llama_model_optimized.py @@ -176,12 +176,9 @@ def validate_input_shape(self, inp_ids, mode): seq_len <= self.model_config["MAX_CONTEXT_LEN"] ), f"Sequence length {seq_len} exceeds MAX_CONTEXT_LEN {self.model_config['MAX_CONTEXT_LEN']}" - if mode == "prefill": - assert ( - seq_len <= self.model_config["MAX_PREFILL_SEQ_LEN"] - ), f"Prefill only supports seq_len <= {self.model_config['MAX_PREFILL_SEQ_LEN']}" - - def prepare_inputs(self, inp_ids, start_pos, valid_seq_len=None, mode="decode", page_table=None): + def prepare_inputs( + self, inp_ids, start_pos, valid_seq_len=None, mode="decode", page_table=None, chunk_page_table=None + ): """ Prepare inputs for decode mode. Assume that current token is at start_pos, and KV cache has valid data up to start_pos. @@ -235,7 +232,7 @@ def prepare_inputs(self, inp_ids, start_pos, valid_seq_len=None, mode="decode", cos_gathered, dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, - cache_file_name=cache_name(f"cos_gathered_prefill_{seq_len}"), + cache_file_name=cache_name(f"cos_gathered_prefill_{start_pos}_{start_pos+seq_len}"), memory_config=ttnn.DRAM_MEMORY_CONFIG, device=self.mesh_device, mesh_mapper=ReplicateTensorToMesh(self.mesh_device), @@ -244,7 +241,7 @@ def prepare_inputs(self, inp_ids, start_pos, valid_seq_len=None, mode="decode", sin_gathered, dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, - cache_file_name=cache_name(f"sin_gathered_prefill_{seq_len}"), + cache_file_name=cache_name(f"sin_gathered_prefill_{start_pos}_{start_pos+seq_len}"), memory_config=ttnn.DRAM_MEMORY_CONFIG, device=self.mesh_device, mesh_mapper=ReplicateTensorToMesh(self.mesh_device), @@ -266,6 +263,17 @@ def prepare_inputs(self, inp_ids, start_pos, valid_seq_len=None, mode="decode", layout=ttnn.ROW_MAJOR_LAYOUT, mesh_mapper=ReplicateTensorToMesh(self.mesh_device), ) + if chunk_page_table is not None: + chunk_page_table = ttnn.as_tensor( + chunk_page_table, + device=self.mesh_device, + memory_config=ttnn.DRAM_MEMORY_CONFIG, + dtype=ttnn.int32, + layout=ttnn.ROW_MAJOR_LAYOUT, + mesh_mapper=ReplicateTensorToMesh(self.mesh_device), + ) + + return (xs, start_pos, rot_mats, rot_idxs_tt, cache_idxs_tt, page_table, chunk_page_table) elif mode == "decode": assert seq_len == 1, "Decode mode only supports seq_len=1" @@ -297,10 +305,9 @@ def prepare_inputs(self, inp_ids, start_pos, valid_seq_len=None, mode="decode", layout=ttnn.ROW_MAJOR_LAYOUT, mesh_mapper=ReplicateTensorToMesh(self.mesh_device), ) + return (xs, start_pos, rot_mats, rot_idxs_tt, cache_idxs_tt, page_table) - return (xs, start_pos, rot_mats, rot_idxs_tt, cache_idxs_tt, page_table) - - def prepare_device_inputs( + def prepare_device_inputs_decode( self, tokens: torch.Tensor, start_pos: int, @@ -310,29 +317,27 @@ def prepare_device_inputs( return_tokens=False, # if true, return tokens for decode mode return_rot_idxs=False, # if true, return rot_idxs for decode mode ): + assert mode == "decode" tt_inp, start_pos, rot_mat, rot_idxs_tt, cache_idxs_tt, tt_page_table = self.prepare_inputs( tokens, start_pos, valid_seq_len=valid_seq_len, mode=mode, page_table=page_table ) - if mode == "decode": - tt_inp = ttnn.to_device(tt_inp, self.mesh_device, memory_config=ttnn.DRAM_MEMORY_CONFIG) - tt_inp_emb = self.tt_embd(tt_inp) - tt_inp_emb = ttnn.interleaved_to_sharded(tt_inp_emb, self.model_config["WORD_EMBEDDING_OUTPUT_MEMCFG"]) - cache_idxs_tt = ttnn.to_device(cache_idxs_tt, self.mesh_device, memory_config=ttnn.DRAM_MEMORY_CONFIG) - rot_mat, rot_idxs_tt = self.rope_setup_decode.get_rot_mats( - rot_idxs_tt, return_rot_idxs=True - ) # Sends rot_idxs to device internally - if tt_page_table is not None: - tt_page_table = ttnn.to_device(tt_page_table, self.mesh_device, memory_config=ttnn.DRAM_MEMORY_CONFIG) - else: - tt_inp_emb = tt_inp + tt_inp = ttnn.to_device(tt_inp, self.mesh_device, memory_config=ttnn.DRAM_MEMORY_CONFIG) + tt_inp_emb = self.tt_embd(tt_inp) + tt_inp_emb = ttnn.interleaved_to_sharded(tt_inp_emb, self.model_config["WORD_EMBEDDING_OUTPUT_MEMCFG"]) + cache_idxs_tt = ttnn.to_device(cache_idxs_tt, self.mesh_device, memory_config=ttnn.DRAM_MEMORY_CONFIG) + rot_mat, rot_idxs_tt = self.rope_setup_decode.get_rot_mats( + rot_idxs_tt, return_rot_idxs=True + ) # Sends rot_idxs to device internally + if tt_page_table is not None: + tt_page_table = ttnn.to_device(tt_page_table, self.mesh_device, memory_config=ttnn.DRAM_MEMORY_CONFIG) return_out = [tt_inp_emb, start_pos, rot_mat, cache_idxs_tt, tt_page_table] - if mode == "decode": - if return_tokens: - return_out.append(tt_inp) - if return_rot_idxs: - return_out.append(rot_idxs_tt) + + if return_tokens: + return_out.append(tt_inp) + if return_rot_idxs: + return_out.append(rot_idxs_tt) return tuple(return_out) def __call__( @@ -346,6 +351,8 @@ def __call__( page_table=None, kv_cache=None, mode="decode", + chunk_page_table=None, + chunk_start_idx=None, ) -> ttnn.Tensor: if self.vllm: assert page_table is not None @@ -359,6 +366,8 @@ def __call__( last_token_idx=last_token_idx, page_table=page_table, kv_cache=kv_cache, + chunk_page_table=chunk_page_table, + chunk_start_idx=chunk_start_idx, ) elif mode == "decode": return self.decode_forward(xs, rot_mats, start_pos, cache_idxs, page_table=page_table, kv_cache=kv_cache) @@ -450,11 +459,21 @@ def prefill_forward( last_token_idx=None, page_table=None, kv_cache=None, + chunk_page_table=None, + chunk_start_idx=None, ) -> ttnn.Tensor: ### Run all layers for layer in self.layers: xs = layer( - xs, rot_mats, start_pos, user_id, page_table=page_table, kv_cache=kv_cache, mode="prefill" + xs, + rot_mats, + start_pos, + user_id, + page_table=page_table, + kv_cache=kv_cache, + mode="prefill", + chunk_page_table=chunk_page_table, + chunk_start_idx=chunk_start_idx, ) # xs is sharded # Distributed rmsnorm diff --git a/models/demos/t3000/llama2_70b/tt/model_config.py b/models/demos/t3000/llama2_70b/tt/model_config.py index 65396108b431..68b39aedd5e3 100644 --- a/models/demos/t3000/llama2_70b/tt/model_config.py +++ b/models/demos/t3000/llama2_70b/tt/model_config.py @@ -388,7 +388,8 @@ def get_model_config(llama_version="llama3", max_batch_size=32, max_context_len= model_config["SDPA_DECODE_PROGRAM_CONFIG"] = ttnn.SDPAProgramConfig( compute_with_storage_grid_size=[8, 4], # Can be increased, but could result in di/dt? q_chunk_size=0, # unused - k_chunk_size=0, # unused + k_chunk_size=256, # unused + exp_approx_mode=False, ) model_config["SDPA_OUTPUT_MEMCFG"] = ttnn.MemoryConfig( diff --git a/tests/scripts/t3000/run_t3000_demo_tests.sh b/tests/scripts/t3000/run_t3000_demo_tests.sh index 627769a5a9eb..729b050b8a36 100755 --- a/tests/scripts/t3000/run_t3000_demo_tests.sh +++ b/tests/scripts/t3000/run_t3000_demo_tests.sh @@ -35,6 +35,9 @@ run_t3000_llama3_70b_tests() { # Output verification demo for old llama3-70b codebase, to be removed once old codebase is deleted env WH_ARCH_YAML=wormhole_b0_80_arch_eth_dispatch.yaml pytest models/demos/t3000/llama3_70b/demo/demo.py::test_LlamaModel_demo[wormhole_b0-True-device_params0-short_context-check_enabled-greedy-tt-70b-T3000-80L-decode_only-trace_mode_off-text_completion-llama3] --timeout=900 ; fail+=$? + # Test chunked prefill for llama2-70b + env WH_ARCH_YAML=wormhole_b0_80_arch_eth_dispatch.yaml pytest models/demos/t3000/llama2_70b/tests/test_chunked_generation.py::test_batch_prefill --timeout=900 ; fail+=$? + # Record the end time end_time=$(date +%s) duration=$((end_time - start_time)) From 923b2bb8eaea66d2a9e5f1b964b30a4995730a62 Mon Sep 17 00:00:00 2001 From: Andrew Fuller Date: Mon, 16 Dec 2024 14:06:36 -0500 Subject: [PATCH 05/87] Bump MagicEnum to v0.9.7 (#16065) ### Ticket Closes #16036 ### Problem description Consumers cannot opt to use an external MagicEnum as the #include statements are incompatible. ### What's changed Bump to MagicEnum v0.9.7 which unifies the way to consume it across source and post-install. --- dependencies/CMakeLists.txt | 2 +- tests/tt_eager/ops/test_bcast_op.cpp | 2 +- tests/tt_eager/ops/test_sfpu.cpp | 2 +- tt_metal/common/tt_backend_api_types.cpp | 2 +- tt_metal/impl/allocator/allocator.cpp | 2 +- tt_metal/impl/dispatch/command_queue_interface.hpp | 2 +- tt_metal/impl/dispatch/data_collection.cpp | 2 +- tt_metal/programming_examples/eltwise_binary/eltwise_binary.cpp | 2 +- tt_metal/programming_examples/matmul_common/bmm_op.hpp | 2 +- tt_metal/tools/profiler/op_profiler.hpp | 2 +- tt_metal/tt_stl/reflection.hpp | 2 +- ttnn/cpp/pybind11/export_enum.hpp | 2 +- ttnn/cpp/ttnn/core.hpp | 2 +- ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.hpp | 2 +- .../data_movement/sharded/reshard/device/reshard_op.cpp | 2 +- .../operations/eltwise/binary/device/binary_composite_op.cpp | 2 +- .../operations/eltwise/binary/device/binary_composite_op.hpp | 2 +- .../eltwise/binary/device/binary_device_operation.hpp | 2 +- .../ttnn/operations/eltwise/binary_backward/binary_backward.cpp | 2 +- .../eltwise/complex_binary/device/complex_binary_op.hpp | 2 +- .../eltwise/complex_unary/device/complex_unary_op.hpp | 2 +- .../complex_unary_backward/device/complex_unary_backward_op.hpp | 2 +- .../ttnn/operations/eltwise/ternary/ternary_composite_op.hpp | 2 +- .../operations/eltwise/ternary_backward/ternary_backward.cpp | 2 +- .../ttnn/operations/eltwise/unary/device/unary_composite_op.cpp | 2 +- .../ttnn/operations/eltwise/unary/device/unary_composite_op.hpp | 2 +- .../operations/eltwise/unary/device/unary_device_operation.cpp | 2 +- .../ttnn/operations/eltwise/unary_backward/unary_backward.cpp | 2 +- ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp | 2 +- .../device/layernorm_post_all_gather_op.cpp | 2 +- .../device/layernorm_pre_all_gather_op.cpp | 2 +- 31 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index c63dcf9a7a89..eb0bf54d180f 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -72,7 +72,7 @@ endif() # magic_enum : https://github.com/Neargye/magic_enum ############################################################################################################################ -CPMAddPackage(NAME magic_enum GITHUB_REPOSITORY Neargye/magic_enum GIT_TAG v0.9.6) +CPMAddPackage(NAME magic_enum GITHUB_REPOSITORY Neargye/magic_enum GIT_TAG v0.9.7) ############################################################################################################################ # fmt : https://github.com/fmtlib/fmt diff --git a/tests/tt_eager/ops/test_bcast_op.cpp b/tests/tt_eager/ops/test_bcast_op.cpp index 38675a937239..f563c01cfb26 100644 --- a/tests/tt_eager/ops/test_bcast_op.cpp +++ b/tests/tt_eager/ops/test_bcast_op.cpp @@ -7,7 +7,7 @@ #include "ttnn/tensor/tensor.hpp" #include "ttnn/operations/data_movement/bcast/bcast.hpp" #include "common/constants.hpp" -#include +#include #include using namespace tt; diff --git a/tests/tt_eager/ops/test_sfpu.cpp b/tests/tt_eager/ops/test_sfpu.cpp index 1e9df15d4bee..8274a3ab7a65 100644 --- a/tests/tt_eager/ops/test_sfpu.cpp +++ b/tests/tt_eager/ops/test_sfpu.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include "tt_metal/host_api.hpp" #include "tt_metal/detail/tt_metal.hpp" diff --git a/tt_metal/common/tt_backend_api_types.cpp b/tt_metal/common/tt_backend_api_types.cpp index 58a082b5f725..31d0c1db8ac7 100644 --- a/tt_metal/common/tt_backend_api_types.cpp +++ b/tt_metal/common/tt_backend_api_types.cpp @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "tt_backend_api_types.hpp" -#include +#include std::string tt::get_string(tt::ARCH arch) { switch (arch) { diff --git a/tt_metal/impl/allocator/allocator.cpp b/tt_metal/impl/allocator/allocator.cpp index ad25e1f3a6f2..af3b4bb49fac 100644 --- a/tt_metal/impl/allocator/allocator.cpp +++ b/tt_metal/impl/allocator/allocator.cpp @@ -4,7 +4,7 @@ #include "tt_metal/impl/allocator/allocator.hpp" -#include +#include #include "tt_metal/common/math.hpp" #include "tt_metal/detail/util.hpp" #include "tt_metal/impl/allocator/algorithms/free_list.hpp" diff --git a/tt_metal/impl/dispatch/command_queue_interface.hpp b/tt_metal/impl/dispatch/command_queue_interface.hpp index 8204be59bb8f..2ea6d2e76e06 100644 --- a/tt_metal/impl/dispatch/command_queue_interface.hpp +++ b/tt_metal/impl/dispatch/command_queue_interface.hpp @@ -4,7 +4,7 @@ #pragma once #include -#include +#include #include #include "tt_metal/common/base.hpp" diff --git a/tt_metal/impl/dispatch/data_collection.cpp b/tt_metal/impl/dispatch/data_collection.cpp index 3b04ce19a878..e41a449f08a8 100644 --- a/tt_metal/impl/dispatch/data_collection.cpp +++ b/tt_metal/impl/dispatch/data_collection.cpp @@ -7,7 +7,7 @@ #include "tt_metal/impl/kernels/kernel.hpp" #include "tt_metal/common/core_coord.hpp" -#include +#include using namespace tt; using namespace tt::tt_metal; diff --git a/tt_metal/programming_examples/eltwise_binary/eltwise_binary.cpp b/tt_metal/programming_examples/eltwise_binary/eltwise_binary.cpp index 98154a7c16ac..de01317173ae 100644 --- a/tt_metal/programming_examples/eltwise_binary/eltwise_binary.cpp +++ b/tt_metal/programming_examples/eltwise_binary/eltwise_binary.cpp @@ -11,7 +11,7 @@ #include "common/bfloat16.hpp" -#include +#include using namespace tt; using namespace tt::tt_metal; diff --git a/tt_metal/programming_examples/matmul_common/bmm_op.hpp b/tt_metal/programming_examples/matmul_common/bmm_op.hpp index b229618d1725..c1d118215ec2 100644 --- a/tt_metal/programming_examples/matmul_common/bmm_op.hpp +++ b/tt_metal/programming_examples/matmul_common/bmm_op.hpp @@ -15,7 +15,7 @@ #include "tt_metal/common/bfloat16.hpp" #include "umd/device/tt_xy_pair.h" -#include +#include #include "tt_metal/common/work_split.hpp" diff --git a/tt_metal/tools/profiler/op_profiler.hpp b/tt_metal/tools/profiler/op_profiler.hpp index 0e1d0d9ef15b..376f5b341505 100644 --- a/tt_metal/tools/profiler/op_profiler.hpp +++ b/tt_metal/tools/profiler/op_profiler.hpp @@ -11,7 +11,7 @@ #include "ttnn/tensor/tensor.hpp" #include -#include +#include #include "tools/profiler/profiler.hpp" #include "tt_metal/impl/kernels/kernel.hpp" #include "ttnn/operation.hpp" diff --git a/tt_metal/tt_stl/reflection.hpp b/tt_metal/tt_stl/reflection.hpp index 33dd853cc384..29cd686f013e 100644 --- a/tt_metal/tt_stl/reflection.hpp +++ b/tt_metal/tt_stl/reflection.hpp @@ -24,7 +24,7 @@ #include "concepts.hpp" #include -#include +#include #include "type_name.hpp" #include "tt_metal/common/logger.hpp" diff --git a/ttnn/cpp/pybind11/export_enum.hpp b/ttnn/cpp/pybind11/export_enum.hpp index c0561411b89f..a07620dfb87b 100644 --- a/ttnn/cpp/pybind11/export_enum.hpp +++ b/ttnn/cpp/pybind11/export_enum.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include namespace py = pybind11; diff --git a/ttnn/cpp/ttnn/core.hpp b/ttnn/cpp/ttnn/core.hpp index db32d95c1b0b..c0fd10852b5f 100644 --- a/ttnn/cpp/ttnn/core.hpp +++ b/ttnn/cpp/ttnn/core.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include "ttnn/tensor/tensor.hpp" #include "ttnn/tensor/tensor_impl.hpp" // TTNN_TENSOR_PRINT_PROFILE #include "ttnn/tensor/types.hpp" diff --git a/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.hpp b/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.hpp index 2ab6380247fb..b45d1bf15b54 100644 --- a/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.hpp +++ b/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.hpp @@ -8,7 +8,7 @@ #include #include "ttnn/tensor/tensor.hpp" -#include +#include #include "ttnn/tensor/host_buffer/functions.hpp" #include "ttnn/tensor/tensor_utils.hpp" #include "ttnn/operations/core/compute_kernel/compute_kernel_config.hpp" diff --git a/ttnn/cpp/ttnn/operations/data_movement/sharded/reshard/device/reshard_op.cpp b/ttnn/cpp/ttnn/operations/data_movement/sharded/reshard/device/reshard_op.cpp index 6ec78ed579b3..44dabab2e629 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/sharded/reshard/device/reshard_op.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/sharded/reshard/device/reshard_op.cpp @@ -4,7 +4,7 @@ #include "reshard_op.hpp" -#include +#include #include "reshard_program_factory.hpp" #include "tt_metal/common/constants.hpp" diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.cpp index 6f24991c759b..a54d641d6e6c 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.cpp @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "binary_composite_op.hpp" -#include +#include #include #include "ttnn/operations/eltwise/binary/binary.hpp" #include "ttnn/operations/eltwise/unary/unary.hpp" diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.hpp index 1da4530b62fc..dea52bf595aa 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.hpp @@ -8,7 +8,7 @@ #include #include #include "ttnn/tensor/tensor.hpp" -#include +#include #include "ttnn/operations/core/core.hpp" namespace ttnn::operations::binary { diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.hpp index 11a77a206e9f..048d086e4efb 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.hpp @@ -5,7 +5,7 @@ #pragma once #include -#include +#include #include #include diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_backward/binary_backward.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_backward/binary_backward.cpp index 0d647f99577a..ea3b863e8305 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_backward/binary_backward.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_backward/binary_backward.cpp @@ -26,7 +26,7 @@ #include "ttnn/operations/creation.hpp" #include "ttnn/common/constants.hpp" #include "ttnn/operations/eltwise/binary_backward/binary_backward.hpp" -#include +#include #include namespace ttnn::operations::binary_backward { diff --git a/ttnn/cpp/ttnn/operations/eltwise/complex_binary/device/complex_binary_op.hpp b/ttnn/cpp/ttnn/operations/eltwise/complex_binary/device/complex_binary_op.hpp index 42660946c11e..27748350623a 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/complex_binary/device/complex_binary_op.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/complex_binary/device/complex_binary_op.hpp @@ -7,7 +7,7 @@ #include #include #include "ttnn/tensor/tensor.hpp" -#include +#include #include "ttnn/operations/eltwise/complex/complex.hpp" namespace ttnn::operations::complex_binary { diff --git a/ttnn/cpp/ttnn/operations/eltwise/complex_unary/device/complex_unary_op.hpp b/ttnn/cpp/ttnn/operations/eltwise/complex_unary/device/complex_unary_op.hpp index b64e059000cb..8e043674ed29 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/complex_unary/device/complex_unary_op.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/complex_unary/device/complex_unary_op.hpp @@ -7,7 +7,7 @@ #include #include #include "ttnn/tensor/tensor.hpp" -#include +#include #include "ttnn/operations/eltwise/complex/complex.hpp" namespace ttnn::operations::complex_unary { diff --git a/ttnn/cpp/ttnn/operations/eltwise/complex_unary_backward/device/complex_unary_backward_op.hpp b/ttnn/cpp/ttnn/operations/eltwise/complex_unary_backward/device/complex_unary_backward_op.hpp index e3de7966efea..5e9ac298bc75 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/complex_unary_backward/device/complex_unary_backward_op.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/complex_unary_backward/device/complex_unary_backward_op.hpp @@ -7,7 +7,7 @@ #include #include #include "ttnn/tensor/tensor.hpp" -#include +#include #include "ttnn/operations/eltwise/complex/complex.hpp" namespace ttnn::operations::complex_unary_backward { diff --git a/ttnn/cpp/ttnn/operations/eltwise/ternary/ternary_composite_op.hpp b/ttnn/cpp/ttnn/operations/eltwise/ternary/ternary_composite_op.hpp index 99e4a79a5264..1b5f342ddbfe 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/ternary/ternary_composite_op.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/ternary/ternary_composite_op.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include "ttnn/tensor/tensor.hpp" #include "ttnn/operations/core/core.hpp" #include "ttnn/run_operation.hpp" diff --git a/ttnn/cpp/ttnn/operations/eltwise/ternary_backward/ternary_backward.cpp b/ttnn/cpp/ttnn/operations/eltwise/ternary_backward/ternary_backward.cpp index 5e261d58cce8..c792f0f988da 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/ternary_backward/ternary_backward.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/ternary_backward/ternary_backward.cpp @@ -12,7 +12,7 @@ #include "tt_metal/host_api.hpp" #include "tt_metal/tools/profiler/op_profiler.hpp" #include "ttnn/operations/eltwise/ternary_backward/ternary_backward.hpp" -#include +#include namespace ttnn::operations::ternary_backward { diff --git a/ttnn/cpp/ttnn/operations/eltwise/unary/device/unary_composite_op.cpp b/ttnn/cpp/ttnn/operations/eltwise/unary/device/unary_composite_op.cpp index 04baa0f9599a..b967bf65a059 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/unary/device/unary_composite_op.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/unary/device/unary_composite_op.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include "tt_metal/common/bfloat16.hpp" #include "ttnn/operations/data_movement/reshape_on_device/reshape.hpp" diff --git a/ttnn/cpp/ttnn/operations/eltwise/unary/device/unary_composite_op.hpp b/ttnn/cpp/ttnn/operations/eltwise/unary/device/unary_composite_op.hpp index 584ec6fadb57..b35a963d5d50 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/unary/device/unary_composite_op.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/unary/device/unary_composite_op.hpp @@ -6,7 +6,7 @@ #include #include #include "ttnn/tensor/tensor.hpp" -#include +#include #include "ttnn/cpp/ttnn/operations/eltwise/ternary/where.hpp" #include "ttnn/operations/eltwise/unary/unary.hpp" #include "ttnn/operations/eltwise/binary/binary.hpp" diff --git a/ttnn/cpp/ttnn/operations/eltwise/unary/device/unary_device_operation.cpp b/ttnn/cpp/ttnn/operations/eltwise/unary/device/unary_device_operation.cpp index 56ba7717bcf5..5b7a9efc1e11 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/unary/device/unary_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/unary/device/unary_device_operation.cpp @@ -4,7 +4,7 @@ #include "unary_device_operation.hpp" -#include +#include #include "tt_metal/common/constants.hpp" #include "tt_metal/host_api.hpp" #include "tt_metal/tools/profiler/op_profiler.hpp" diff --git a/ttnn/cpp/ttnn/operations/eltwise/unary_backward/unary_backward.cpp b/ttnn/cpp/ttnn/operations/eltwise/unary_backward/unary_backward.cpp index 423a0a6775ca..6a0aaed9c3f1 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/unary_backward/unary_backward.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/unary_backward/unary_backward.cpp @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -#include +#include #include #include "ttnn/operations/data_movement/bcast/bcast.hpp" #include "tt_metal/common/constants.hpp" diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp index 184ec4fd0970..2b3cbb83cba5 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp @@ -4,7 +4,7 @@ #include "moreh_helper_functions.hpp" -#include +#include #include #include "common/constants.hpp" diff --git a/ttnn/cpp/ttnn/operations/normalization/layernorm_distributed/device/layernorm_post_all_gather_op.cpp b/ttnn/cpp/ttnn/operations/normalization/layernorm_distributed/device/layernorm_post_all_gather_op.cpp index dec7a4005e41..7628a438671b 100644 --- a/ttnn/cpp/ttnn/operations/normalization/layernorm_distributed/device/layernorm_post_all_gather_op.cpp +++ b/ttnn/cpp/ttnn/operations/normalization/layernorm_distributed/device/layernorm_post_all_gather_op.cpp @@ -11,7 +11,7 @@ #include "tt_metal/common/constants.hpp" #include "tt_metal/detail/util.hpp" -#include +#include #include diff --git a/ttnn/cpp/ttnn/operations/normalization/layernorm_distributed/device/layernorm_pre_all_gather_op.cpp b/ttnn/cpp/ttnn/operations/normalization/layernorm_distributed/device/layernorm_pre_all_gather_op.cpp index dceb72e35e22..c0c1f08fc12f 100644 --- a/ttnn/cpp/ttnn/operations/normalization/layernorm_distributed/device/layernorm_pre_all_gather_op.cpp +++ b/ttnn/cpp/ttnn/operations/normalization/layernorm_distributed/device/layernorm_pre_all_gather_op.cpp @@ -11,7 +11,7 @@ #include "tt_metal/common/constants.hpp" #include "tt_metal/detail/util.hpp" -#include +#include #include From e6a4fff8444763960c738340b1bfd2e62f5818b9 Mon Sep 17 00:00:00 2001 From: Austin Ho Date: Mon, 16 Dec 2024 15:33:57 +0000 Subject: [PATCH 06/87] #15944: Fix pybind of create_sub_device_manager_with_fabric to call the correct function. Add binding for single device api as well --- tests/ttnn/unit_tests/test_sub_device.py | 19 +++++--- ttnn/cpp/pybind11/device.cpp | 48 +++++++++++++++---- .../ttnn/distributed/distributed_pybind.cpp | 2 +- 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/tests/ttnn/unit_tests/test_sub_device.py b/tests/ttnn/unit_tests/test_sub_device.py index 2eb998b720ad..25abe9acffd2 100644 --- a/tests/ttnn/unit_tests/test_sub_device.py +++ b/tests/ttnn/unit_tests/test_sub_device.py @@ -29,8 +29,10 @@ def run_sub_devices(device, create_fabric_sub_device=False): sub_devices_1 = [sub_device_1, sub_device_2] sub_devices_2 = [sub_device_2] if create_fabric_sub_device: - sub_device_manager1 = device.create_sub_device_manager_with_fabric(sub_devices_1, 3200) - sub_device_manager2 = device.create_sub_device_manager_with_fabric(sub_devices_2, 3200) + sub_device_manager1, fabric_sub_device_id1 = device.create_sub_device_manager_with_fabric(sub_devices_1, 3200) + sub_device_manager2, fabric_sub_device_id2 = device.create_sub_device_manager_with_fabric(sub_devices_2, 3200) + assert fabric_sub_device_id1 == ttnn.SubDeviceId(len(sub_devices_1)) + assert fabric_sub_device_id2 == ttnn.SubDeviceId(len(sub_devices_2)) else: sub_device_manager1 = device.create_sub_device_manager(sub_devices_1, 3200) sub_device_manager2 = device.create_sub_device_manager(sub_devices_2, 3200) @@ -75,7 +77,8 @@ def run_sub_devices_program(device, create_fabric_sub_device=False): sub_device_2 = ttnn.SubDevice([tensix_cores1]) sub_devices = [sub_device_1, sub_device_2] if create_fabric_sub_device: - sub_device_manager = device.create_sub_device_manager_with_fabric(sub_devices, 3200) + sub_device_manager, fabric_sub_device_id = device.create_sub_device_manager_with_fabric(sub_devices, 3200) + assert fabric_sub_device_id == ttnn.SubDeviceId(len(sub_devices)) else: sub_device_manager = device.create_sub_device_manager(sub_devices, 3200) device.load_sub_device_manager(sub_device_manager) @@ -135,8 +138,9 @@ def run_sub_devices_program(device, create_fabric_sub_device=False): @pytest.mark.parametrize("enable_async_mode", (False, True), indirect=True) -def test_sub_devices(device, enable_async_mode): - run_sub_devices(device) +@pytest.mark.parametrize("create_fabric_sub_device", (False, True)) +def test_sub_devices(device, create_fabric_sub_device, enable_async_mode): + run_sub_devices(device, create_fabric_sub_device) @pytest.mark.parametrize("enable_async_mode", (False, True), indirect=True) @@ -146,8 +150,9 @@ def test_sub_devices_mesh(mesh_device, create_fabric_sub_device, enable_async_mo @pytest.mark.parametrize("enable_async_mode", (False, True), indirect=True) -def test_sub_device_program(device, enable_async_mode): - run_sub_devices_program(device) +@pytest.mark.parametrize("create_fabric_sub_device", (False, True)) +def test_sub_device_program(device, create_fabric_sub_device, enable_async_mode): + run_sub_devices_program(device, create_fabric_sub_device) @pytest.mark.parametrize("enable_async_mode", (False, True), indirect=True) diff --git a/ttnn/cpp/pybind11/device.cpp b/ttnn/cpp/pybind11/device.cpp index 1196c1f08e01..cb46a4510e85 100644 --- a/ttnn/cpp/pybind11/device.cpp +++ b/ttnn/cpp/pybind11/device.cpp @@ -4,6 +4,7 @@ #include "device.hpp" +#include #include #include @@ -117,12 +118,15 @@ void device_module(py::module& m_device) { )doc"); auto pySubDeviceId = static_cast>(m_device.attr("SubDeviceId")); - pySubDeviceId.def( - py::init(), - py::arg("id"), - R"doc( + pySubDeviceId + .def( + py::init(), + py::arg("id"), + R"doc( Creates a SubDeviceId object with the given ID. - )doc"); + )doc") + .def(py::self == py::self) + .def(py::self != py::self); auto pyDevice = static_cast>>(m_device.attr("Device")); pyDevice @@ -168,7 +172,7 @@ void device_module(py::module& m_device) { [device, sub_devices, local_l1_size, &sub_device_manager_id] { sub_device_manager_id = device->create_sub_device_manager(sub_devices, local_l1_size); }, - true); + /*blocking=*/true); return sub_device_manager_id; }, py::arg("sub_devices"), @@ -183,13 +187,37 @@ void device_module(py::module& m_device) { Returns: SubDeviceManagerId: The ID of the created sub-device manager. )doc") + .def( + "create_sub_device_manager_with_fabric", + [](Device* device, const std::vector& sub_devices, DeviceAddr local_l1_size) { + std::tuple manager_and_sub_device_ids; + device->push_work( + [device, sub_devices, local_l1_size, &manager_and_sub_device_ids] { + manager_and_sub_device_ids = + device->create_sub_device_manager_with_fabric(sub_devices, local_l1_size); + }, + /*blocking=*/true); + return manager_and_sub_device_ids; + }, + py::arg("sub_devices"), + py::arg("local_l1_size"), + R"doc( + Creates a sub-device manager for the given device. This will automatically create a sub-device of ethernet cores for use with fabric. + Note that this is a temporary API until migration to actual fabric is complete. + + Args: + sub_devices (List[ttnn.SubDevice]): The sub-devices to include in the sub-device manager. No ethernet cores should be included in this list. + local_l1_size (int): The size of the local allocators of each sub-device. The global allocator will be shrunk by this amount. + + Returns: + SubDeviceManagerId: The ID of the created sub-device manager. + SubDeviceId: The ID of the sub-device that will be used for fabric. + )doc") .def( "load_sub_device_manager", [](Device* device, SubDeviceManagerId sub_device_manager_id) { - device->push_work([device, sub_device_manager_id] { - device->push_work( - [device, sub_device_manager_id] { device->load_sub_device_manager(sub_device_manager_id); }); - }); + device->push_work( + [device, sub_device_manager_id] { device->load_sub_device_manager(sub_device_manager_id); }); }, py::arg("sub_device_manager_id"), R"doc( diff --git a/ttnn/cpp/ttnn/distributed/distributed_pybind.cpp b/ttnn/cpp/ttnn/distributed/distributed_pybind.cpp index 71114e39ee59..1e1ac97f507d 100644 --- a/ttnn/cpp/ttnn/distributed/distributed_pybind.cpp +++ b/ttnn/cpp/ttnn/distributed/distributed_pybind.cpp @@ -199,7 +199,7 @@ void py_module(py::module& module) { .def( "create_sub_device_manager_with_fabric", [](MeshDevice& self, const std::vector& sub_devices, DeviceAddr local_l1_size) { - return self.create_sub_device_manager(sub_devices, local_l1_size); + return self.create_sub_device_manager_with_fabric(sub_devices, local_l1_size); }, py::arg("sub_devices"), py::arg("local_l1_size"), From 33c402e2957b2d1d90e194eb4e61c448d6db1577 Mon Sep 17 00:00:00 2001 From: Roman Furko Date: Mon, 16 Dec 2024 13:28:56 -0800 Subject: [PATCH 07/87] [tt-train] Add option to disable wandb in examples (#16069) ### Problem description NanoGPT example crashes if `wandb` credentials are missing. ### What's changed Add option to disable wandb when running from command line. Use `-w 0` arguments. Added another option how to disable `wandb`. ### Checklist - [x] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12360978772 - [x] New/Existing tests provide coverage for changes --- tt-train/README.md | 3 + tt-train/sources/examples/nano_gpt/main.cpp | 70 ++++++++++++--------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/tt-train/README.md b/tt-train/README.md index 5082ae686d71..62bcde843bf3 100644 --- a/tt-train/README.md +++ b/tt-train/README.md @@ -70,6 +70,9 @@ TT_METAL_LOGGER_LEVEL=FATAL ./build/sources/examples/nano_gpt/nano_gpt --model_p ### CI only tests If CI fails, but local tests pass as expected, please consider changing ENABLE_CI_ONLY_TT_TRAIN_TESTS definition in tests/CMakeLists.txt +### wandb support +If you don't have an account to wandb (or don't want to use it), use `-w 0` argument or run `wandb offline` beforehand (creates `wandb/settings` file) + # Contributing * Create a new branch. * Make your changes and commit them. diff --git a/tt-train/sources/examples/nano_gpt/main.cpp b/tt-train/sources/examples/nano_gpt/main.cpp index 89fb363187f7..541cb32c3de0 100644 --- a/tt-train/sources/examples/nano_gpt/main.cpp +++ b/tt-train/sources/examples/nano_gpt/main.cpp @@ -178,12 +178,6 @@ const std::unordered_map< schedulers = {{"identity", create_idendity_scheduler}, {"warmup_linear", create_warmup_with_linear_scheduler}}; int main(int argc, char **argv) { - auto result = signal(SIGINT, signal_handler); - if (result == SIG_ERR) { - std::cerr << "Failed to set signal handler\n"; - return -1; - } - auto start_timer = std::chrono::high_resolution_clock::now(); CLI::App app{"NanoGPT Example"}; argv = app.ensure_utf8(argv); @@ -191,35 +185,48 @@ int main(int argc, char **argv) { std::string config_name = std::string(CONFIGS_FOLDER) + "/training_shakespear_nanogpt.yaml"; bool is_eval = false; bool add_time_to_name = true; + bool enable_wandb = true; app.add_option("-c,--config", config_name, "Yaml Config name")->default_val(config_name); app.add_option("-e,--eval", is_eval, "Is evaluation")->default_val(is_eval); app.add_option("-t,--add_time_to_name", add_time_to_name, "Add time to run name")->default_val(add_time_to_name); + app.add_option("-w,--wandb", enable_wandb, "Enable wandb logging")->default_val(enable_wandb); CLI11_PARSE(app, argc, argv); + if (enable_wandb) { + auto result = signal(SIGINT, signal_handler); + if (result == SIG_ERR) { + std::cerr << "Failed to set signal handler\n"; + return -1; + } + } + auto yaml_config = YAML::LoadFile(config_name); TrainingConfig config = parse_config(yaml_config); - wandbcpp::init({.project = config.project_name, .name = generate_run_name(config, add_time_to_name)}); - wandbcpp::update_config({ - {"model", "transformer"}, - {"num_heads", static_cast(config.transformer_config.num_heads)}, - {"embedding_dim", static_cast(config.transformer_config.embedding_dim)}, - {"num_blocks", static_cast(config.transformer_config.num_blocks)}, - {"dropout_prob", config.transformer_config.dropout_prob}, - {"learning_rate", config.learning_rate}, - {"weight_decay", config.weight_decay}, - {"batch_size", static_cast(config.batch_size)}, - {"sequence_length", static_cast(config.transformer_config.max_sequence_length)}, - {"max_steps", static_cast(config.max_steps)}, - {"seed", static_cast(config.seed)}, - {"tokenizer_type", config.tokenizer_type}, - {"use_kahan_summation", config.use_kahan_summation}, - {"gradient_accumulation_steps", static_cast(config.gradient_accumulation_steps)}, - {"positional_embedding_type", - config.transformer_config.positional_embedding_type == ttml::models::gpt2::PositionalEmbeddingType::Trainable - ? "trainable" - : "fixed"}, - {"scheduler_type", config.scheduler_type}, - }); + if (enable_wandb) { + wandbcpp::init({.project = config.project_name, .name = generate_run_name(config, add_time_to_name)}); + wandbcpp::update_config({ + {"model", "transformer"}, + {"num_heads", static_cast(config.transformer_config.num_heads)}, + {"embedding_dim", static_cast(config.transformer_config.embedding_dim)}, + {"num_blocks", static_cast(config.transformer_config.num_blocks)}, + {"dropout_prob", config.transformer_config.dropout_prob}, + {"learning_rate", config.learning_rate}, + {"weight_decay", config.weight_decay}, + {"batch_size", static_cast(config.batch_size)}, + {"sequence_length", static_cast(config.transformer_config.max_sequence_length)}, + {"max_steps", static_cast(config.max_steps)}, + {"seed", static_cast(config.seed)}, + {"tokenizer_type", config.tokenizer_type}, + {"use_kahan_summation", config.use_kahan_summation}, + {"gradient_accumulation_steps", static_cast(config.gradient_accumulation_steps)}, + {"positional_embedding_type", + config.transformer_config.positional_embedding_type == + ttml::models::gpt2::PositionalEmbeddingType::Trainable + ? "trainable" + : "fixed"}, + {"scheduler_type", config.scheduler_type}, + }); + } // set seed ttml::autograd::ctx().set_seed(config.seed); @@ -375,7 +382,7 @@ int main(int argc, char **argv) { fmt::print("Step: {}, Loss: {}\n", global_step, gradient_accumulator_helper.average_loss()); loss_meter.update(gradient_accumulator_helper.average_loss()); - if (global_step % 10 == 0) { + if (enable_wandb && global_step % 10 == 0) { wandbcpp::log( {{"Step", (int)global_step}, {"Samples", (int)get_samples_count(global_step)}, @@ -416,6 +423,9 @@ int main(int argc, char **argv) { config.max_steps, (double)duration / 1000000., device->num_program_cache_entries()); - wandbcpp::finish(); + + if (enable_wandb) { + wandbcpp::finish(); + } return 0; } From a275ddd205ca449994aea8d5671f28189ab64faf Mon Sep 17 00:00:00 2001 From: Salar Hosseini <159165450+skhorasganiTT@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:47:09 -0500 Subject: [PATCH 08/87] [skip ci] Update perf and latest features for llm models (Dec 16) (#16060) --- README.md | 10 +++++----- models/MODEL_UPDATES.md | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d06621a08fa1..306acfcb9b95 100644 --- a/README.md +++ b/README.md @@ -28,17 +28,17 @@ | [Falcon 7B](./models/demos/wormhole/falcon7b) | 32 | [n150](https://tenstorrent.com/hardware/wormhole) | 71 | 17.6 | 26 | 563.2 | [v0.53.0-rc44](https://github.com/tenstorrent/tt-metal/tree/v0.53.0-rc44) | | | [Mistral 7B](./models/demos/wormhole/mistral7b) | 32 | [n150](https://tenstorrent.com/hardware/wormhole) | | 9.9 | 25 | 316.8 | [v0.51.0-rc28](https://github.com/tenstorrent/tt-metal/tree/v0.51.0-rc28) | | | [Mamba 2.8B](./models/demos/wormhole/mamba) | 32 | [n150](https://tenstorrent.com/hardware/wormhole) | 48 | 12.3 | 41 | 393.6 | [v0.51.0-rc26](https://github.com/tenstorrent/tt-metal/tree/v0.51.0-rc26) | | -| [Llama 3.1 8B](./models/demos/llama3) | 1 | [n150](https://tenstorrent.com/hardware/wormhole) | 202 | 28.6 | 23 | 28.6 | [v0.53.1-rc7](https://github.com/tenstorrent/tt-metal/tree/v0.53.1-rc7) | | -| [Llama 3.2 1B](./models/demos/llama3) | 1 | [n150](https://tenstorrent.com/hardware/wormhole) | 71 | 90.8 | 160 | 90.8 | [v0.53.1-rc7](https://github.com/tenstorrent/tt-metal/tree/v0.53.1-rc7) | | -| [Llama 3.2 3B](./models/demos/llama3) | 1 | [n150](https://tenstorrent.com/hardware/wormhole) | 112 | 49.1 | 60 | 49.1 | [v0.53.1-rc7](https://github.com/tenstorrent/tt-metal/tree/v0.53.1-rc7) | | +| [Llama 3.1 8B](./models/demos/llama3) | 32 | [n150](https://tenstorrent.com/hardware/wormhole) | 151 | 22.8 | 23 | 729.6 | [v0.53.1-rc23](https://github.com/tenstorrent/tt-metal/tree/v0.53.1-rc23) | | +| [Llama 3.2 1B](./models/demos/llama3) | 32 | [n150](https://tenstorrent.com/hardware/wormhole) | 56 | 55.1 | 160 | 1763.2 | [v0.53.1-rc23](https://github.com/tenstorrent/tt-metal/tree/v0.53.1-rc23) | | +| [Llama 3.2 3B](./models/demos/llama3) | 32 | [n150](https://tenstorrent.com/hardware/wormhole) | 96 | 35.0 | 60 | 1120.0 | [v0.53.1-rc23](https://github.com/tenstorrent/tt-metal/tree/v0.53.1-rc23) | | | [Falcon 7B (DP=8)](./models/demos/t3000/falcon7b) | 256 | [QuietBox](https://tenstorrent.com/hardware/tt-quietbox) | 97 | 14.6 | 26 | 3737.6 | [v0.53.0-rc44](https://github.com/tenstorrent/tt-metal/tree/v0.53.0-rc44) | | | [Llama 3.1 70B (TP=8)](./models/demos/t3000/llama3_70b) | 32 | [QuietBox](https://tenstorrent.com/hardware/tt-quietbox) | 190 | 15.1 | 20 | 483.2 | [v0.53.0-rc36](https://github.com/tenstorrent/tt-metal/tree/v0.53.0-rc36) | [384f179](https://github.com/tenstorrent/vllm/tree/384f1790c3be16e1d1b10de07252be2e66d00935) | -| [Falcon 40B (TP=8)](./models/demos/t3000/falcon40b) | 32 | [QuietBox](https://tenstorrent.com/hardware/tt-quietbox) | | 5.3 | 36 | 169.6 | [v0.53.1-rc7](https://github.com/tenstorrent/tt-metal/tree/v0.53.1-rc7) | | +| [Falcon 40B (TP=8)](./models/demos/t3000/falcon40b) | 32 | [QuietBox](https://tenstorrent.com/hardware/tt-quietbox) | | 5.3 | 36 | 169.6 | [v0.53.1-rc23](https://github.com/tenstorrent/tt-metal/tree/v0.53.1-rc23) | | | [Mixtral 8x7B (TP=8)](./models/demos/t3000/mixtral8x7b) | 32 | [QuietBox](https://tenstorrent.com/hardware/tt-quietbox) | 230 | 14.6 | 33 | 467.2 | [v0.53.0-rc44](https://github.com/tenstorrent/tt-metal/tree/v0.53.0-rc44) | | | [Falcon 7B (DP=32)](./models/demos/tg/falcon7b) | 1024 | [Galaxy](https://tenstorrent.com/hardware/galaxy) | 242 | 4.4 | 26 | 4505.6 | [v0.53.0-rc33](https://github.com/tenstorrent/tt-metal/tree/v0.53.0-rc33) | | | [Llama 3.1 70B (DP=4, TP=8)](./models/demos/t3000/llama3_70b) | 128 | [Galaxy](https://tenstorrent.com/hardware/galaxy) | 190 | 14.3 | 20 | 1835.5 | [v0.52.0-rc31](https://github.com/tenstorrent/tt-metal/tree/v0.52.0-rc31) | | -> **Last Update:** December 7, 2024 +> **Last Update:** December 16, 2024 > > **Notes:** > diff --git a/models/MODEL_UPDATES.md b/models/MODEL_UPDATES.md index 5b6396d3d710..1f7e03273731 100644 --- a/models/MODEL_UPDATES.md +++ b/models/MODEL_UPDATES.md @@ -4,6 +4,12 @@ > > Please refer to the front-page [README](../README.md) for the latest verified release for each model. +## December 16, 2024 + +### [Llama 3.1/3.2](demos/llama3) +- Added support for batch size 32 and the maximum context length (131072 tokens). +- Added full hardware compatibilty for the 1B/3B/8B/11B/70B models (all models are now compatible with N150, N300, QuietBox, Galaxy except for 70B which is only supported on QuietBox and Galaxy due to its large size). + ## December 2, 2024 ### [Llama 3.1/3.2](demos/llama3) From d8579965e2b96102162b0f99d45d346c6b930780 Mon Sep 17 00:00:00 2001 From: Dimitri Gnidash <119051828+dimitri-tenstorrent@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:55:32 -0500 Subject: [PATCH 09/87] #16070: Use the same Docker image as built (#16071) ### Ticket #16070 ### Problem description For Blackhole Post Commit - We build and tag Docker image with 20.04 https://github.com/tenstorrent/tt-metal/actions/runs/12359826668/job/34493376435 ``` Run docker push ghcr.io/tenstorrent/tt-metal/tt-metalium/ubuntu-20.04-amd64:dev-abhullar-l1-data-cache The push refers to repository [ghcr.io/tenstorrent/tt-metal/tt-metalium/ubuntu-20.04-amd64] ``` - And later request the image that is 22.04 ``` Run docker pull ghcr.io/tenstorrent/tt-metal/tt-metalium/ubuntu-22.04-amd64:dev-abhullar-l1-data-cache ``` ### What's changed Describe the approach used to solve the problem. Summarize the changes made and its impact. ### Checklist - [ ] Post commit CI passes - [ ] Blackhole Post commit (if applicable) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [ ] New/Existing tests provide coverage for changes --- .github/workflows/blackhole-post-commit.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/blackhole-post-commit.yaml b/.github/workflows/blackhole-post-commit.yaml index 237654297f90..72ba467fa924 100644 --- a/.github/workflows/blackhole-post-commit.yaml +++ b/.github/workflows/blackhole-post-commit.yaml @@ -24,6 +24,8 @@ jobs: build-docker-image: uses: ./.github/workflows/build-docker-artifact.yaml secrets: inherit + with: + os: "ubuntu-22.04-amd64" build-artifact: needs: build-docker-image uses: ./.github/workflows/build-artifact.yaml From bf9f3cddab35050535ea5bb4bff0c2993ab3ef2c Mon Sep 17 00:00:00 2001 From: Roman Furko Date: Mon, 16 Dec 2024 14:18:28 -0800 Subject: [PATCH 10/87] [tt-train] Bump magic_enum from 0.9.6 to 0.9.7 (#16074) ### Problem description TT-train main branch is again not building... ### What's changed Bump `magic_enum` from v0.9.6 to v0.9.7 ### Checklist - [x] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12362131879 - [x] New/Existing tests provide coverage for changes --- tt-train/cmake/dependencies.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tt-train/cmake/dependencies.cmake b/tt-train/cmake/dependencies.cmake index 5359cc73c21a..4e284f34a981 100644 --- a/tt-train/cmake/dependencies.cmake +++ b/tt-train/cmake/dependencies.cmake @@ -52,7 +52,7 @@ CPMAddPackage(NAME fmt GITHUB_REPOSITORY fmtlib/fmt GIT_TAG 11.0.1) # magic_enum : https://github.com/Neargye/magic_enum ############################################################################################################################ -CPMAddPackage(NAME magic_enum GITHUB_REPOSITORY Neargye/magic_enum GIT_TAG v0.9.6) +CPMAddPackage(NAME magic_enum GITHUB_REPOSITORY Neargye/magic_enum GIT_TAG v0.9.7) ############################################################################################################################ # nlohmann/json : https://github.com/nlohmann/json From ba516bca679d7ec6f7b7db6afbe12e097fa436a4 Mon Sep 17 00:00:00 2001 From: Abhinav Sarje Date: Mon, 16 Dec 2024 15:23:26 -0800 Subject: [PATCH 11/87] Update ttcnn.md --- tech_reports/CNNs/ttcnn.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tech_reports/CNNs/ttcnn.md b/tech_reports/CNNs/ttcnn.md index ab45dcfbddfc..1659c4d66da3 100644 --- a/tech_reports/CNNs/ttcnn.md +++ b/tech_reports/CNNs/ttcnn.md @@ -48,7 +48,7 @@ to extract meaningful features and patterns. ### `conv2d` -Applies a 2D convolution over `input_tensor`, a 4D tensor with dimensions ordered as `(batch_size, input_height, input_width, in_channels)` using provided weights, with dimensions `(out_channels, in_channels, kernel_height, kernel_width)`, and optional `bias`, with dimensions `(1, 1, 1, out_channels)`, and generates `output_tensor` with dimensions ordered as `(batch_size, output_height, output_width, out_channels)`. +Applies a 2D convolution over `input_tensor`, a 4D tensor with dimensions ordered as `(batch_size, input_height, input_width, in_channels)` using provided weights, with dimensions `(out_channels, in_channels, kernel_height, kernel_width)`, and optional `bias`, with dimensions `(1, 1, 1, out_channels)`, and generates `output_tensor` with first three dimensions flattened and ordered as `(1, 1, batch_size * output_height * output_width, out_channels)`. A simple `reshape` can be used to obtain the unflattened tensor. #### Python API @@ -178,7 +178,7 @@ Once the inputs are prepared, we can call the `conv2d` operation as shown in the weights_dtype=ttnn.bfloat16 ) - ttnn_output_tensor_on_device = ttnn.conv2d( + [ttnn_output_tensor_on_device, [out_height, out_width]] = ttnn.conv2d( input_tensor=ttnn_input_tensor, weight_tensor=ttnn_weight_tensor, in_channels=in_channels, @@ -193,10 +193,13 @@ Once the inputs are prepared, we can call the `conv2d` operation as shown in the input_height=input_height, input_width=input_width, conv_config=conv_config, + return_output_dim=True, ) ``` -To get higher performance it is advisable to use the following optional arguments: +Note that the `conv2d` supports controling the return of output dimensions, through argument `return_output_dim`, and for processed weights and bias tensors, through argument `return_weights_and_bias`. + +To achieve higher performance it is advisable to use the following optional arguments: * `deallocate_activation = True` If the input tensor is no longer needed after the conv2d operation, this option will free up the input buffer and have more memory available for the operation. * `reallocate_halo_output = True` The `conv2d` operation executes a _haloing_ step before computing the convolutions to optimize memory accesses. This option will reallocate the output of this step in order to reduce memory fragmentation to avoid memory fitting issues. From 25c17eb5de8bb6a2c002714d6238d7d8acd211ba Mon Sep 17 00:00:00 2001 From: Yan Zaretskiy Date: Tue, 10 Dec 2024 22:30:28 +0000 Subject: [PATCH 12/87] #13643: Extend binary-ng math support to match all primitive binary ops --- .../operations/eltwise/test_binary_bcast.py | 81 ++++++- ttnn/CMakeLists.txt | 1 + .../eltwise/binary_ng/binary_ng.cpp | 17 ++ .../eltwise/binary_ng/binary_ng.hpp | 68 ++++++ .../eltwise/binary_ng/binary_ng_pybind.cpp | 35 ++- .../eltwise/binary_ng/binary_ng_pybind.hpp | 4 +- .../device/binary_ng_device_operation.cpp | 24 +- .../device/binary_ng_program_factory.cpp | 146 +++---------- .../binary_ng/device/binary_ng_utils.cpp | 206 ++++++++++++++++++ .../binary_ng/device/binary_ng_utils.hpp | 72 ++++++ .../device/kernels/compute/eltwise_binary.cpp | 80 +++++-- .../compute/eltwise_binary_no_bcast.cpp | 64 ++++-- .../kernels/compute/eltwise_binary_scalar.cpp | 60 +++-- .../kernels/compute/eltwise_defines.hpp | 26 +++ .../device/kernels/compute/eltwise_utils.hpp | 41 ++++ .../operations/eltwise/binary_ng/types.hpp | 5 +- ttnn/ttnn/operations/binary_ng.py | 30 +++ 17 files changed, 766 insertions(+), 194 deletions(-) create mode 100644 ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.cpp create mode 100644 ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.hpp create mode 100644 ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_defines.hpp create mode 100644 ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_utils.hpp create mode 100644 ttnn/ttnn/operations/binary_ng.py diff --git a/tests/ttnn/unit_tests/operations/eltwise/test_binary_bcast.py b/tests/ttnn/unit_tests/operations/eltwise/test_binary_bcast.py index 7f6f592bf6df..34774b95cc44 100644 --- a/tests/ttnn/unit_tests/operations/eltwise/test_binary_bcast.py +++ b/tests/ttnn/unit_tests/operations/eltwise/test_binary_bcast.py @@ -5,8 +5,10 @@ import torch import pytest import ttnn -import random -from tests.ttnn.unit_tests.operations.eltwise.backward.utility_funcs import data_gen_with_range, compare_pcc + +from tests.ttnn.unit_tests.operations.eltwise.backward.utility_funcs import ( + compare_pcc, +) @pytest.mark.parametrize( @@ -17,7 +19,30 @@ (torch.Size([5, 1, 1, 64]), torch.Size([1, 3, 128, 1])), ), ) -def test_binary_scalar_ops(input_shapes, device): +@pytest.mark.parametrize( + "ttnn_fn", + [ + ttnn.experimental.gte, + ttnn.experimental.gt, + ttnn.experimental.lte, + ttnn.experimental.lt, + ttnn.experimental.eq, + ttnn.experimental.ne, + ttnn.experimental.logical_and, + ttnn.experimental.logical_or, + ttnn.experimental.logical_xor, + ttnn.experimental.ldexp, + ttnn.experimental.logaddexp, + ttnn.experimental.logaddexp2, + ttnn.experimental.squared_difference, + ttnn.experimental.add, + ttnn.experimental.sub, + ttnn.experimental.mul, + ttnn.experimental.div, + ttnn.experimental.bias_gelu, + ], +) +def test_binary_scalar_ops(input_shapes, ttnn_fn, device): a_shape, b_shape = input_shapes a_pt = torch.rand(a_shape).bfloat16() b_pt = torch.rand(b_shape).bfloat16() @@ -25,13 +50,59 @@ def test_binary_scalar_ops(input_shapes, device): a_tt = ttnn.from_torch(a_pt, device=device, layout=ttnn.TILE_LAYOUT, memory_config=ttnn.DRAM_MEMORY_CONFIG) b_tt = ttnn.from_torch(b_pt, device=device, layout=ttnn.TILE_LAYOUT, memory_config=ttnn.DRAM_MEMORY_CONFIG) cq_id = 0 - out_tt = ttnn.experimental.add(a_tt, b_tt, queue_id=cq_id) - out_pt = a_pt + b_pt + out_tt = ttnn_fn(a_tt, b_tt, queue_id=cq_id) + golden_fn = ttnn.get_golden_function(ttnn_fn) + out_pt = golden_fn(a_pt, b_pt) comp_pass = compare_pcc([out_tt], [out_pt]) assert comp_pass +@pytest.mark.parametrize( + "input_shapes", + ( + (torch.Size([1, 1, 31, 32]), torch.Size([5, 3, 32, 32])), + (torch.Size([5, 2, 64, 1]), torch.Size([1, 3, 1, 128])), + (torch.Size([5, 1, 1, 64]), torch.Size([2, 3, 128, 1])), + ), +) +@pytest.mark.parametrize( + "ttnn_fn", + [ + ttnn.experimental.gte, + ttnn.experimental.gt, + ttnn.experimental.lte, + ttnn.experimental.lt, + ttnn.experimental.eq, + ttnn.experimental.ne, + ttnn.experimental.logical_and, + ttnn.experimental.logical_or, + ttnn.experimental.logical_xor, + ttnn.experimental.ldexp, + ttnn.experimental.logaddexp, + ttnn.experimental.logaddexp2, + ttnn.experimental.squared_difference, + ttnn.experimental.add, + ttnn.experimental.sub, + ttnn.experimental.mul, + ttnn.experimental.div, + ttnn.experimental.bias_gelu, + ], +) +def test_binary_scalar_ops_invalid_bcast(input_shapes, ttnn_fn, device): + a_shape, b_shape = input_shapes + a_pt = torch.rand(a_shape).bfloat16() + b_pt = torch.rand(b_shape).bfloat16() + + a_tt = ttnn.from_torch(a_pt, device=device, layout=ttnn.TILE_LAYOUT, memory_config=ttnn.DRAM_MEMORY_CONFIG) + b_tt = ttnn.from_torch(b_pt, device=device, layout=ttnn.TILE_LAYOUT, memory_config=ttnn.DRAM_MEMORY_CONFIG) + + with pytest.raises(RuntimeError) as e: + cq_id = 0 + _ = ttnn_fn(a_tt, b_tt, queue_id=cq_id) + assert "Broadcasting rule violation" in str(e.value) + + @pytest.mark.parametrize( "shapes", [ diff --git a/ttnn/CMakeLists.txt b/ttnn/CMakeLists.txt index 5b20f6eba57a..5ee740e2a261 100644 --- a/ttnn/CMakeLists.txt +++ b/ttnn/CMakeLists.txt @@ -121,6 +121,7 @@ set(ALL_TTNN_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_device_operation.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_program_factory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary/binary.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary/common/binary_op_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.cpp diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.cpp index 7e476e293b2e..1439186fbd4a 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.cpp @@ -52,5 +52,22 @@ Tensor BinaryNg::invoke( } template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; +template struct BinaryNg; } // namespace ttnn::operations::binary_ng diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.hpp index ac1faf4a4e7e..d41261f60b94 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.hpp @@ -49,4 +49,72 @@ namespace ttnn::experimental { constexpr auto add = ttnn::register_operation_with_auto_launch_op< "ttnn::experimental::add", ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto sub = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::sub", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto mul = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::mul", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto div = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::div", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto eq = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::eq", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto ne = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::ne", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto gt = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::gt", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto gte = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::gte", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto lt = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::lt", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto lte = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::lte", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto squared_difference = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::squared_difference", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto bias_gelu = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::bias_gelu", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto logical_and = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::logical_and", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto logical_or = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::logical_or", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto logical_xor = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::logical_xor", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto ldexp = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::ldexp", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto logaddexp = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::logaddexp", + ttnn::operations::binary_ng::BinaryNg>(); + +constexpr auto logaddexp2 = ttnn::register_operation_with_auto_launch_op< + "ttnn::experimental::logaddexp2", + ttnn::operations::binary_ng::BinaryNg>(); } diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.cpp index ab6c28e4421d..da264795941e 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.cpp @@ -9,17 +9,16 @@ namespace ttnn::operations::binary_ng { namespace detail { -void bind_binary_ng_operation(py::module& module) { - using OperationType = decltype(ttnn::experimental::add); - +template +void bind_binary_ng_operation(py::module& module, T op, const std::string& docstring) { bind_registered_operation( module, - ttnn::experimental::add, - "Binary Add Ng Operation", + op, + docstring, // tensor and scalar ttnn::pybind_overload_t{ - [](const OperationType& self, + [](const T& self, const ttnn::Tensor& input_tensor_a, const float scalar, const std::optional& dtype, @@ -38,7 +37,7 @@ void bind_binary_ng_operation(py::module& module) { // tensor and tensor ttnn::pybind_overload_t{ - [](const OperationType& self, + [](const T& self, const ttnn::Tensor& input_tensor_a, const ttnn::Tensor& input_tensor_b, const std::optional& dtype, @@ -57,5 +56,25 @@ void bind_binary_ng_operation(py::module& module) { } } // namespace detail -void py_module(py::module& module) { detail::bind_binary_ng_operation(module); } +void py_module(py::module& module) { + detail::bind_binary_ng_operation(module, ttnn::experimental::add, "Binary Add Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::sub, "Binary Sub Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::mul, "Binary Mul Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::div, "Binary Div Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::gt, "Binary Greater Than Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::lt, "Binary Less Than Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::lte, "Binary Less Than or Equal To Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::gte, "Binary Greater Than or Equal To Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::eq, "Binary Equal Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::ne, "Binary Not Equal Operation"); + detail::bind_binary_ng_operation( + module, ttnn::experimental::squared_difference, "Binary Squared Difference Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::bias_gelu, "Binary Bias GELU Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::logical_and, "Binary Logical And Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::logical_or, "Binary Logical Or Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::logical_xor, "Binary Logical Xor Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::ldexp, "Binary Ldexp Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::logaddexp, "Binary Logaddexp Operation"); + detail::bind_binary_ng_operation(module, ttnn::experimental::logaddexp2, "Binary Logaddexp2 Operation"); +} } // namespace ttnn::operations::eltwise::binary_ng diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.hpp index 3a417d010cb3..b8cc769c7800 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.hpp @@ -5,12 +5,14 @@ #pragma once #include "pybind11/pybind_fwd.hpp" +#include namespace py = pybind11; namespace ttnn::operations::binary_ng { namespace detail { -void bind_binary_ng_operation(py::module& module); +template +void bind_binary_ng_operation(py::module& module, T op, const std::string& docstring); } void py_module(py::module& module); diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_device_operation.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_device_operation.cpp index 0279ff3734ee..b55c8a8cccd8 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_device_operation.cpp @@ -90,18 +90,18 @@ void BinaryNgDeviceOperation::validate_on_program_cache_hit( const auto input_shape_b = tensor_args.input_tensor_b.has_value() ? tensor_args.input_tensor_b->get_logical_shape() : ttnn::Shape{1, 1}; - constexpr int max_rank = 4; - if (input_shape_a.rank() > 0 && input_shape_b.rank() > 0) { - for (int i = 1; i <= max_rank; i++) { - auto a_dim = i <= input_shape_a.rank() ? input_shape_a[-i] : 1; - auto b_dim = i <= input_shape_b.rank() ? input_shape_b[-i] : 1; - TT_FATAL( - a_dim == b_dim || a_dim == 1 || b_dim == 1, - "Broadcasting rule violation for rank {}, dim a: {}, dim b: {}", - i, - a_dim, - b_dim); - } + const int rank_a = input_shape_a.rank(); + const int rank_b = input_shape_b.rank(); + const int larger_rank = std::max(rank_a, rank_b); + for (int i = -1; i >= -larger_rank; --i) { + auto a_dim = (i >= -rank_a) ? input_shape_a[i] : 1; + auto b_dim = (i >= -rank_b) ? input_shape_b[i] : 1; + TT_FATAL( + a_dim == b_dim || a_dim == 1 || b_dim == 1, + "Broadcasting rule violation for rank {}, dim a: {}, dim b: {}", + i, + a_dim, + b_dim); } } diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_program_factory.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_program_factory.cpp index bce5cfdc8242..d1125e8238f4 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_program_factory.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_program_factory.cpp @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -#include "binary_ng_device_operation.hpp" +#include "binary_ng_utils.hpp" #include "tt_metal/common/work_split.hpp" #include "ttnn/operations/cb_utils.hpp" @@ -17,124 +17,6 @@ std::tuple extract_shape_dims(const Tens return {shape[-4], shape[-3], shape[-2] / tile.get_height(), shape[-1] / tile.get_width()}; } -enum class KernelName { - ReaderNoBcast, - ReaderRowBcast, - ReaderColBcast, - ReaderScalarBcast, - WriterNoBcast, - WriterRowBcast, - WriterColBcast, - WriterScalarBcast, - WriterScalar, - ComputeNoBcast, - ComputeBcast, - ComputeScalar -}; - -struct BinaryNgKernelConfig { - BinaryNgKernelConfig(SubtileBroadcastType subtile_broadcast_type) { - switch (subtile_broadcast_type) { - case SubtileBroadcastType::NONE: - reader_kernel = KernelName::ReaderNoBcast; - compute_kernel = KernelName::ComputeNoBcast; - writer_kernel = KernelName::WriterNoBcast; - bcast_input = std::nullopt; - break; - - case SubtileBroadcastType::SCALAR_A: - reader_kernel = KernelName::ReaderScalarBcast; - compute_kernel = KernelName::ComputeBcast; - writer_kernel = KernelName::WriterNoBcast; - bcast_input = 0; - break; - - case SubtileBroadcastType::SCALAR_B: - reader_kernel = KernelName::ReaderNoBcast; - compute_kernel = KernelName::ComputeBcast; - writer_kernel = KernelName::WriterScalarBcast; - bcast_input = 1; - break; - - case SubtileBroadcastType::ROW_A: - reader_kernel = KernelName::ReaderRowBcast; - compute_kernel = KernelName::ComputeNoBcast; - writer_kernel = KernelName::WriterNoBcast; - bcast_input = std::nullopt; - break; - - case SubtileBroadcastType::ROW_B: - reader_kernel = KernelName::ReaderNoBcast; - compute_kernel = KernelName::ComputeNoBcast; - writer_kernel = KernelName::WriterRowBcast; - bcast_input = std::nullopt; - break; - - case SubtileBroadcastType::COL_A: - reader_kernel = KernelName::ReaderColBcast; - compute_kernel = KernelName::ComputeBcast; - writer_kernel = KernelName::WriterNoBcast; - bcast_input = 0; - break; - - case SubtileBroadcastType::COL_B: - reader_kernel = KernelName::ReaderNoBcast; - compute_kernel = KernelName::ComputeBcast; - writer_kernel = KernelName::WriterColBcast; - bcast_input = 1; - break; - - case SubtileBroadcastType::ROW_A_COL_B: - reader_kernel = KernelName::ReaderRowBcast; - compute_kernel = KernelName::ComputeBcast; - writer_kernel = KernelName::WriterColBcast; - bcast_input = 1; - break; - - case SubtileBroadcastType::ROW_B_COL_A: - reader_kernel = KernelName::ReaderColBcast; - compute_kernel = KernelName::ComputeBcast; - writer_kernel = KernelName::WriterRowBcast; - bcast_input = 0; - break; - } - } - - std::string bcast_input_str() const { - if (bcast_input.has_value()) { - return std::to_string(*bcast_input); - } - return ""; - } - - KernelName reader_kernel; - KernelName compute_kernel; - KernelName writer_kernel; - std::optional bcast_input; -}; - -std::string get_kernel_file_path(KernelName kernel_name) { - constexpr std::string_view root = "ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels"; - constexpr std::string_view dataflow = "{}/dataflow/{}"; - constexpr std::string_view compute = "{}/compute/{}"; - - switch (kernel_name) { - case KernelName::ReaderNoBcast: return fmt::format(dataflow, root, "reader_interleaved_no_bcast.cpp"); - case KernelName::ReaderRowBcast: return fmt::format(dataflow, root, "reader_interleaved_row_bcast.cpp"); - case KernelName::ReaderColBcast: return fmt::format(dataflow, root, "reader_interleaved_col_bcast.cpp"); - case KernelName::ReaderScalarBcast: return fmt::format(dataflow, root, "reader_interleaved_scalar_bcast.cpp"); - case KernelName::WriterNoBcast: return fmt::format(dataflow, root, "writer_interleaved_no_bcast.cpp"); - case KernelName::WriterRowBcast: return fmt::format(dataflow, root, "writer_interleaved_row_bcast.cpp"); - case KernelName::WriterColBcast: return fmt::format(dataflow, root, "writer_interleaved_col_bcast.cpp"); - case KernelName::WriterScalarBcast: return fmt::format(dataflow, root, "writer_interleaved_scalar_bcast.cpp"); - case KernelName::WriterScalar: return fmt::format(dataflow, root, "writer_interleaved_scalar.cpp"); - case KernelName::ComputeNoBcast: return fmt::format(compute, root, "eltwise_binary_no_bcast.cpp"); - case KernelName::ComputeBcast: return fmt::format(compute, root, "eltwise_binary.cpp"); - case KernelName::ComputeScalar: return fmt::format(compute, root, "eltwise_binary_scalar.cpp"); - default: __builtin_unreachable(); // GCC 12 doesn't compile even though we exhaustively match - } -} - std::tuple calculate_compute_kernel_args( SubtileBroadcastType broadcast_type, uint32_t start_tile_id, uint32_t HtWt, uint32_t Wt) { uint32_t start_t = start_tile_id % HtWt; @@ -282,6 +164,8 @@ BinaryNgDeviceOperation::ProgramFactory::cached_program_t BinaryNgDeviceOperatio auto b_data_format = b.has_value() ? datatype_to_dataformat_converter(b->get_dtype()) : DataFormat::Float16_b; auto c_data_format = datatype_to_dataformat_converter(c.get_dtype()); + tt::DataFormat b_intermediate_format = b_data_format; + uint32_t a_single_tile_size = tt_metal::detail::TileSize(a_data_format); uint32_t b_single_tile_size = tt_metal::detail::TileSize(b_data_format); uint32_t c_single_tile_size = tt_metal::detail::TileSize(c_data_format); @@ -299,10 +183,23 @@ BinaryNgDeviceOperation::ProgramFactory::cached_program_t BinaryNgDeviceOperatio Buffer* b_buffer = nullptr; Buffer* c_buffer = c.buffer(); + auto op_type = operation_attributes.binary_op_type; + auto compute_kernel_defines = OpConfig(op_type).as_defines(); + bool op_has_exp = + op_type == BinaryOpType::LOGADDEXP || op_type == BinaryOpType::LDEXP || op_type == BinaryOpType::LOGADDEXP2; + // How many tiles to store per input CB (double buffer) constexpr uint32_t num_tiles_per_cb = 2; auto [a_cb, a_cb_handle] = create_cb(tt::CBIndex::c_0, program, all_device_cores, a_single_tile_size, num_tiles_per_cb, a_data_format); + + if (compute_kernel_defines.find("PREPROCESS_A_INIT") != compute_kernel_defines.end()) { + auto a_intermediate_format = op_has_exp ? tt::DataFormat::Float16_b : a_data_format; + uint32_t a_intermediate_single_tile_size = tt_metal::detail::TileSize(a_intermediate_format); + auto [a_cb_interim, a_cb_interim_handle] = create_cb( + tt::CBIndex::c_3, program, all_device_cores, a_intermediate_single_tile_size, 1, a_intermediate_format); + } + auto [c_cb, c_cb_handle] = create_cb(tt::CBIndex::c_2, program, all_device_cores, c_single_tile_size, num_tiles_per_cb, c_data_format); @@ -311,6 +208,13 @@ BinaryNgDeviceOperation::ProgramFactory::cached_program_t BinaryNgDeviceOperatio auto [b_cb, b_cb_handle] = create_cb(tt::CBIndex::c_1, program, all_device_cores, b_single_tile_size, b_num_tiles_per_cb, b_data_format); + if (compute_kernel_defines.find("PREPROCESS_B_INIT") != compute_kernel_defines.end()) { + auto b_intermediate_format = op_has_exp ? tt::DataFormat::Float16_b : b_data_format; + uint32_t b_intermediate_single_tile_size = tt_metal::detail::TileSize(b_intermediate_format); + auto [b_cb_interim, b_cb_interim_handle] = create_cb( + tt::CBIndex::c_4, program, all_device_cores, b_intermediate_single_tile_size, 1, b_intermediate_format); + } + auto a_is_dram = static_cast(a_buffer->buffer_type() == tt_metal::BufferType::DRAM); bool b_is_dram = false; auto c_is_dram = static_cast(c_buffer->buffer_type() == tt_metal::BufferType::DRAM); @@ -347,12 +251,12 @@ BinaryNgDeviceOperation::ProgramFactory::cached_program_t BinaryNgDeviceOperatio // Compute kernel needs to know which op it's going to perform // This has to be passed as a compile-time argument // For now we're just going to do addition + compute_kernel_defines["BCAST_INPUT"] = kernel_config.bcast_input_str(); auto compute_kernel_id = tt_metal::CreateKernel( program, get_kernel_file_path(compute_kernel), all_device_cores, - tt_metal::ComputeConfig{ - .fp32_dest_acc_en = fp32_dest_acc_en, .defines = {{"BCAST_INPUT", kernel_config.bcast_input_str()}}}); + tt_metal::ComputeConfig{.fp32_dest_acc_en = fp32_dest_acc_en, .defines = compute_kernel_defines}); auto set_runtime_args = [](Program& program, KernelHandle kernel_id, CoreCoord core, auto&& args) { tt_metal::SetRuntimeArgs(program, kernel_id, core, args); diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.cpp new file mode 100644 index 000000000000..3671cd9d10f8 --- /dev/null +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.cpp @@ -0,0 +1,206 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "binary_ng_utils.hpp" + +#include +#include +#include + +template <> +struct fmt::formatter : fmt::formatter { + auto format(ttnn::operations::binary_ng::Lowercase const& value, fmt::format_context& ctx) const { + auto out = ctx.out(); + for (char c : value.view) { + *out++ = std::tolower(static_cast(c)); + } + return out; + } +}; + +namespace ttnn::operations::binary_ng { + +BinaryNgKernelConfig::BinaryNgKernelConfig(SubtileBroadcastType subtile_broadcast_type) { + switch (subtile_broadcast_type) { + case SubtileBroadcastType::NONE: + reader_kernel = KernelName::ReaderNoBcast; + compute_kernel = KernelName::ComputeNoBcast; + writer_kernel = KernelName::WriterNoBcast; + bcast_input = std::nullopt; + break; + + case SubtileBroadcastType::SCALAR_A: + reader_kernel = KernelName::ReaderScalarBcast; + compute_kernel = KernelName::ComputeBcast; + writer_kernel = KernelName::WriterNoBcast; + bcast_input = 0; + break; + + case SubtileBroadcastType::SCALAR_B: + reader_kernel = KernelName::ReaderNoBcast; + compute_kernel = KernelName::ComputeBcast; + writer_kernel = KernelName::WriterScalarBcast; + bcast_input = 1; + break; + + case SubtileBroadcastType::ROW_A: + reader_kernel = KernelName::ReaderRowBcast; + compute_kernel = KernelName::ComputeNoBcast; + writer_kernel = KernelName::WriterNoBcast; + bcast_input = std::nullopt; + break; + + case SubtileBroadcastType::ROW_B: + reader_kernel = KernelName::ReaderNoBcast; + compute_kernel = KernelName::ComputeNoBcast; + writer_kernel = KernelName::WriterRowBcast; + bcast_input = std::nullopt; + break; + + case SubtileBroadcastType::COL_A: + reader_kernel = KernelName::ReaderColBcast; + compute_kernel = KernelName::ComputeBcast; + writer_kernel = KernelName::WriterNoBcast; + bcast_input = 0; + break; + + case SubtileBroadcastType::COL_B: + reader_kernel = KernelName::ReaderNoBcast; + compute_kernel = KernelName::ComputeBcast; + writer_kernel = KernelName::WriterColBcast; + bcast_input = 1; + break; + + case SubtileBroadcastType::ROW_A_COL_B: + reader_kernel = KernelName::ReaderRowBcast; + compute_kernel = KernelName::ComputeBcast; + writer_kernel = KernelName::WriterColBcast; + bcast_input = 1; + break; + + case SubtileBroadcastType::ROW_B_COL_A: + reader_kernel = KernelName::ReaderColBcast; + compute_kernel = KernelName::ComputeBcast; + writer_kernel = KernelName::WriterRowBcast; + bcast_input = 0; + break; + } +} + +std::string BinaryNgKernelConfig::bcast_input_str() const { + if (bcast_input.has_value()) { + return std::to_string(*bcast_input); + } + return ""; +} + +std::string get_kernel_file_path(KernelName kernel_name) { + constexpr std::string_view root = "ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels"; + constexpr std::string_view dataflow = "{}/dataflow/{}"; + constexpr std::string_view compute = "{}/compute/{}"; + + switch (kernel_name) { + case KernelName::ReaderNoBcast: return fmt::format(dataflow, root, "reader_interleaved_no_bcast.cpp"); + case KernelName::ReaderRowBcast: return fmt::format(dataflow, root, "reader_interleaved_row_bcast.cpp"); + case KernelName::ReaderColBcast: return fmt::format(dataflow, root, "reader_interleaved_col_bcast.cpp"); + case KernelName::ReaderScalarBcast: return fmt::format(dataflow, root, "reader_interleaved_scalar_bcast.cpp"); + case KernelName::WriterNoBcast: return fmt::format(dataflow, root, "writer_interleaved_no_bcast.cpp"); + case KernelName::WriterRowBcast: return fmt::format(dataflow, root, "writer_interleaved_row_bcast.cpp"); + case KernelName::WriterColBcast: return fmt::format(dataflow, root, "writer_interleaved_col_bcast.cpp"); + case KernelName::WriterScalarBcast: return fmt::format(dataflow, root, "writer_interleaved_scalar_bcast.cpp"); + case KernelName::WriterScalar: return fmt::format(dataflow, root, "writer_interleaved_scalar.cpp"); + case KernelName::ComputeNoBcast: return fmt::format(compute, root, "eltwise_binary_no_bcast.cpp"); + case KernelName::ComputeBcast: return fmt::format(compute, root, "eltwise_binary.cpp"); + case KernelName::ComputeScalar: return fmt::format(compute, root, "eltwise_binary_scalar.cpp"); + default: __builtin_unreachable(); // GCC 12 doesn't compile even though we exhaustively match + } +} + +constexpr OpConfig::SfpuConfig NezConfig("nez_tile_init", "nez_tile(i)"); +constexpr OpConfig::SfpuConfig GtzConfig("gtz_tile_init", "gtz_tile(i)"); + +OpConfig::OpConfig(BinaryOpType binary_op_type) { + fpu_binary_op = FpuBinaryOp::SUB; + switch (binary_op_type) { + case BinaryOpType::ADD: fpu_binary_op = FpuBinaryOp::ADD; break; + case BinaryOpType::SUB: break; + case BinaryOpType::MUL: fpu_binary_op = FpuBinaryOp::MUL; break; + case BinaryOpType::DIV: + preprocess_b = SfpuConfig("recip_tile_init", "recip_tile(i)", "compute_kernel_api/eltwise_unary/recip.h"); + fpu_binary_op = FpuBinaryOp::MUL; + break; + case BinaryOpType::GT: postprocess = GtzConfig; break; + case BinaryOpType::LT: postprocess = SfpuConfig("ltz_tile_init", "ltz_tile(i)"); break; + case BinaryOpType::GTE: postprocess = SfpuConfig("gez_tile_init", "gez_tile(i)"); break; + case BinaryOpType::LTE: postprocess = SfpuConfig("lez_tile_init", "lez_tile(i)"); break; + case BinaryOpType::EQ: postprocess = SfpuConfig("eqz_tile_init", "eqz_tile(i)"); break; + case BinaryOpType::NE: postprocess = NezConfig; break; + case BinaryOpType::SQUARED_DIFFERENCE: postprocess = SfpuConfig("square_tile_init", "square_tile(i)"); break; + case BinaryOpType::BIAS_GELU: + fpu_binary_op = FpuBinaryOp::ADD; + preprocess_a = + SfpuConfig("gelu_tile_init", "gelu_tile(i)", "compute_kernel_api/eltwise_unary/gelu.h"); + break; + case BinaryOpType::LOGICAL_AND: + fpu_binary_op = FpuBinaryOp::MUL; + postprocess = NezConfig; + break; + case BinaryOpType::LOGICAL_OR: + fpu_binary_op = FpuBinaryOp::ADD; + preprocess_a = NezConfig; + preprocess_b = NezConfig; + postprocess = GtzConfig; + break; + case BinaryOpType::LOGICAL_XOR: + preprocess_a = NezConfig; + preprocess_b = NezConfig; + postprocess = NezConfig; + break; + case BinaryOpType::LDEXP: + fpu_binary_op = FpuBinaryOp::MUL; + preprocess_b = SfpuConfig("exp2_tile_init", "exp2_tile(i)"); + break; + case BinaryOpType::LOGADDEXP: + fpu_binary_op = FpuBinaryOp::ADD; + preprocess_a = + SfpuConfig("exp_tile_init", "exp_tile(i)", "compute_kernel_api/eltwise_unary/exp.h"); + preprocess_b = preprocess_a; + postprocess = SfpuConfig("log_tile_init", "log_tile(i)"); + break; + case BinaryOpType::LOGADDEXP2: + fpu_binary_op = FpuBinaryOp::ADD; + preprocess_a = SfpuConfig("exp2_tile_init", "exp2_tile(i)"); + preprocess_b = preprocess_a; + postprocess = SfpuConfig("log_with_base_tile_init", "log_with_base_tile(i, 0x3dc5u);"); + break; + default: __builtin_unreachable(); + } +} + +std::map OpConfig::SfpuConfig::as_defines(std::string_view prefix) const { + if (init.empty()) { + return {}; + } + + std::map defines; + defines[fmt::format("{}_INIT", prefix)] = init; + defines[fmt::format("{}_APPLY(i)", prefix)] = apply; + defines[fmt::format("{}_INCLUDE", prefix)] = include; + return defines; +} + +std::map OpConfig::as_defines() const { + std::map defines; + defines.merge(preprocess_a.as_defines("PREPROCESS_A")); + defines.merge(preprocess_b.as_defines("PREPROCESS_B")); + defines.merge(postprocess.as_defines("POSTPROCESS")); + + auto binary_op_str = magic_enum::enum_name(fpu_binary_op); + defines["BINARY_OP"] = fmt::format("{}_tiles", Lowercase{binary_op_str}); + defines["BINARY_OP_TYPE"] = fmt::format("EltwiseBinaryType::ELW{}", binary_op_str); + + return defines; +} + +} // namespace ttnn::operations::binary_ng diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.hpp new file mode 100644 index 000000000000..cc8a242fc0c4 --- /dev/null +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.hpp @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "binary_ng_device_operation.hpp" +#include "ttnn/operations/eltwise/binary_ng/types.hpp" + +#include +#include + +namespace ttnn::operations::binary_ng { + +enum class KernelName { + ReaderNoBcast, + ReaderRowBcast, + ReaderColBcast, + ReaderScalarBcast, + WriterNoBcast, + WriterRowBcast, + WriterColBcast, + WriterScalarBcast, + WriterScalar, + ComputeNoBcast, + ComputeBcast, + ComputeScalar +}; + +struct BinaryNgKernelConfig { + BinaryNgKernelConfig(SubtileBroadcastType subtile_broadcast_type); + + std::string bcast_input_str() const; + + KernelName reader_kernel; + KernelName compute_kernel; + KernelName writer_kernel; + std::optional bcast_input; +}; + +std::string get_kernel_file_path(KernelName kernel_name); + +struct OpConfig { + struct SfpuConfig { + SfpuConfig() = default; + constexpr SfpuConfig( + std::string_view init, std::string_view apply, std::string_view include = "compute_kernel_api.h") : + init{init}, apply{apply}, include{include} {} + std::string_view init{}; + std::string_view apply{}; + std::string_view include{}; + + std::map as_defines(std::string_view prefix) const; + }; + + enum class FpuBinaryOp { ADD, SUB, MUL }; + + OpConfig(BinaryOpType binary_op_type); + + std::map as_defines() const; + + SfpuConfig preprocess_a{}; + SfpuConfig preprocess_b{}; + SfpuConfig postprocess{}; + FpuBinaryOp fpu_binary_op; +}; + +struct Lowercase { + std::string_view view; +}; + +} // namespace ttnn::operations::binary_ng diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary.cpp index dff89bfd6134..ca31d410f390 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary.cpp @@ -2,24 +2,72 @@ // // SPDX-License-Identifier: Apache-2.0 +#include #include "compute_kernel_api/eltwise_binary.h" -#include +#include "eltwise_defines.hpp" +#include "eltwise_utils.hpp" + +#ifdef PREPROCESS_A_INCLUDE +#include QUOTE(PREPROCESS_A_INCLUDE) +#endif +#ifdef PREPROCESS_B_INCLUDE +#include QUOTE(PREPROCESS_B_INCLUDE) +#endif + +#ifdef POSTPROCESS_INCLUDE +#include QUOTE(POSTPROCESS_INCLUDE) +#endif namespace NAMESPACE { -ALWI void process_tile(uint32_t cb_bcast, uint32_t cb_other, uint32_t cb_out, uint32_t freq, uint32_t tile_start) { +ALWI void process_tile( + tt::CBIndex cb_pre_lhs, + tt::CBIndex cb_post_lhs, + tt::CBIndex cb_pre_rhs, + tt::CBIndex cb_post_rhs, + tt::CBIndex cb_out, + uint32_t freq, + uint32_t tile_start) { + using namespace ckernel; constexpr uint32_t onetile = 1; +#if BCAST_INPUT + auto cb_bcast = cb_post_rhs; + auto cb_other = cb_post_lhs; +#else + auto cb_bcast = cb_post_lhs; + auto cb_other = cb_post_rhs; +#endif + +#if PREPROCESS_A && (BCAST_INPUT == 0) + PREPROCESS(PREPROCESS_A_INIT, PREPROCESS_A_APPLY, cb_pre_lhs, cb_post_lhs, cb_out, onetile); +#elif PREPROCESS_B && (BCAST_INPUT == 1) + PREPROCESS(PREPROCESS_B_INIT, PREPROCESS_B_APPLY, cb_pre_rhs, cb_post_rhs, cb_out, onetile); +#endif + cb_wait_front(cb_bcast, onetile); for (uint32_t j = tile_start; j < freq; ++j) { +#if PREPROCESS_A && (BCAST_INPUT == 1) + PREPROCESS(PREPROCESS_A_INIT, PREPROCESS_A_APPLY, cb_pre_lhs, cb_post_lhs, cb_out, onetile); +#elif PREPROCESS_B && (BCAST_INPUT == 0) + PREPROCESS(PREPROCESS_B_INIT, PREPROCESS_B_APPLY, cb_pre_rhs, cb_post_rhs, cb_out, onetile); +#endif cb_wait_front(cb_other, onetile); + cb_reserve_back(cb_out, onetile); +#if PREPROCESS_A || PREPROCESS_B + binary_op_specific_init(); +#endif tile_regs_acquire(); - add_tiles(cb_bcast, cb_other, 0, 0, 0); + BINARY_OP(cb_post_lhs, cb_post_rhs, 0, 0, 0); +#if POSTPROCESS + POSTPROCESS_INIT(); + POSTPROCESS_APPLY(0); +#endif tile_regs_commit(); tile_regs_wait(); @@ -41,30 +89,28 @@ void MAIN { return; } - constexpr auto cb_in0 = tt::CBIndex::c_0; - constexpr auto cb_in1 = tt::CBIndex::c_1; - constexpr auto cb_out0 = tt::CBIndex::c_2; + constexpr auto cb_pre_lhs = tt::CBIndex::c_0; + constexpr auto cb_pre_rhs = tt::CBIndex::c_1; + constexpr auto cb_out = tt::CBIndex::c_2; -#if BCAST_INPUT - auto cb_bcast = cb_in1; - auto cb_other = cb_in0; -#else - auto cb_bcast = cb_in0; - auto cb_other = cb_in1; -#endif + constexpr auto cb_post_lhs = PREPROCESS_A ? tt::CBIndex::c_3 : cb_pre_lhs; + constexpr auto cb_post_rhs = PREPROCESS_B ? tt::CBIndex::c_4 : cb_pre_rhs; - binary_op_init_common(cb_bcast, cb_other, cb_out0); - add_tiles_init(); + binary_op_init_common(cb_post_lhs, cb_post_rhs, cb_out); + +#if not(PREPROCESS_A || PREPROCESS_B) + binary_op_specific_init(); +#endif uint32_t complete_iterations = (num_tiles + tile_start) / tile_freq; uint32_t remaining_iterations = (num_tiles + tile_start) % tile_freq; for (uint32_t i = 0; i < complete_iterations; ++i, tile_start = 0) { - process_tile(cb_bcast, cb_other, cb_out0, tile_freq, tile_start); + process_tile(cb_pre_lhs, cb_post_lhs, cb_pre_rhs, cb_post_rhs, cb_out, tile_freq, tile_start); } if (remaining_iterations > 0) { - process_tile(cb_bcast, cb_other, cb_out0, remaining_iterations, tile_start); + process_tile(cb_pre_lhs, cb_post_lhs, cb_pre_rhs, cb_post_rhs, cb_out, remaining_iterations, tile_start); } } } // namespace NAMESPACE diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_no_bcast.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_no_bcast.cpp index 1e3703fa4b80..e1d3fb08997f 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_no_bcast.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_no_bcast.cpp @@ -6,35 +6,71 @@ #include "compute_kernel_api/eltwise_binary.h" #include "dprint.h" +#include "eltwise_defines.hpp" +#include "eltwise_utils.hpp" + +#ifdef PREPROCESS_A_INCLUDE +#include QUOTE(PREPROCESS_A_INCLUDE) +#endif + +#ifdef PREPROCESS_B_INCLUDE +#include QUOTE(PREPROCESS_B_INCLUDE) +#endif + +#ifdef POSTPROCESS_INCLUDE +#include QUOTE(POSTPROCESS_INCLUDE) +#endif + namespace NAMESPACE { void MAIN { uint32_t num_tiles = get_arg_val(0); - constexpr auto cb_in0 = tt::CBIndex::c_0; - constexpr auto cb_in1 = tt::CBIndex::c_1; - constexpr auto cb_out0 = tt::CBIndex::c_2; + constexpr auto cb_pre_lhs = tt::CBIndex::c_0; + constexpr auto cb_pre_rhs = tt::CBIndex::c_1; + constexpr auto cb_out = tt::CBIndex::c_2; - binary_op_init_common(cb_in0, cb_in1, cb_out0); - add_tiles_init(); + constexpr auto cb_post_lhs = PREPROCESS_A ? tt::CBIndex::c_3 : cb_pre_lhs; + constexpr auto cb_post_rhs = PREPROCESS_B ? tt::CBIndex::c_4 : cb_pre_rhs; + + binary_op_init_common(cb_post_lhs, cb_post_rhs, cb_out); + +#if not(PREPROCESS_A || PREPROCESS_B) + binary_op_specific_init(); +#endif constexpr uint32_t onetile = 1; - for(uint32_t tile_id = 0; tile_id < num_tiles; ++tile_id) { - cb_wait_front(cb_in0, onetile); - cb_wait_front(cb_in1, onetile); - cb_reserve_back(cb_out0, onetile); + for (uint32_t tile_id = 0; tile_id < num_tiles; ++tile_id) { +#if PREPROCESS_A + PREPROCESS(PREPROCESS_A_INIT, PREPROCESS_A_APPLY, cb_pre_lhs, cb_post_lhs, cb_out, onetile); +#endif + cb_wait_front(cb_post_lhs, onetile); + +#if PREPROCESS_B + PREPROCESS(PREPROCESS_B_INIT, PREPROCESS_B_APPLY, cb_pre_rhs, cb_post_rhs, cb_out, onetile); +#endif + cb_wait_front(cb_post_rhs, onetile); + + cb_reserve_back(cb_out, onetile); +#if PREPROCESS_A || PREPROCESS_B + binary_op_specific_init(); +#endif tile_regs_acquire(); - add_tiles(cb_in0, cb_in1, 0, 0, 0); + BINARY_OP(cb_post_lhs, cb_post_rhs, 0, 0, 0); +#if POSTPROCESS + POSTPROCESS_INIT(); + POSTPROCESS_APPLY(0); +#endif tile_regs_commit(); tile_regs_wait(); - pack_tile(0, cb_out0); + pack_tile(0, cb_out); tile_regs_release(); - cb_push_back(cb_out0, onetile); - cb_pop_front(cb_in0, onetile); - cb_pop_front(cb_in1, onetile); + cb_push_back(cb_out, onetile); + cb_pop_front(cb_post_lhs, onetile); + cb_pop_front(cb_post_rhs, onetile); } } } // namespace NAMESPACE diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_scalar.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_scalar.cpp index 2b377fb52cab..92e2b95be2ed 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_scalar.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_scalar.cpp @@ -4,37 +4,71 @@ #include #include "compute_kernel_api/eltwise_binary.h" -#include "dprint.h" + +#include "eltwise_defines.hpp" +#include "eltwise_utils.hpp" + +#ifdef PREPROCESS_A_INCLUDE +#include QUOTE(PREPROCESS_A_INCLUDE) +#endif + +#ifdef PREPROCESS_B_INCLUDE +#include QUOTE(PREPROCESS_B_INCLUDE) +#endif + +#ifdef POSTPROCESS_INCLUDE +#include QUOTE(POSTPROCESS_INCLUDE) +#endif namespace NAMESPACE { void MAIN { uint32_t num_tiles = get_arg_val(0); - constexpr auto cb_in0 = tt::CBIndex::c_0; - constexpr auto cb_in1 = tt::CBIndex::c_1; - constexpr auto cb_out0 = tt::CBIndex::c_2; + constexpr auto cb_pre_lhs = tt::CBIndex::c_0; + constexpr auto cb_pre_rhs = tt::CBIndex::c_1; + constexpr auto cb_out = tt::CBIndex::c_2; - binary_op_init_common(cb_in0, cb_in1, cb_out0); - add_tiles_init(); + constexpr auto cb_post_lhs = PREPROCESS_A ? tt::CBIndex::c_3 : cb_pre_lhs; + constexpr auto cb_post_rhs = PREPROCESS_B ? tt::CBIndex::c_4 : cb_pre_rhs; + + binary_op_init_common(cb_post_lhs, cb_post_rhs, cb_out); + +#if not(PREPROCESS_A || PREPROCESS_B) + binary_op_specific_init(); +#endif constexpr uint32_t onetile = 1; - cb_wait_front(cb_in1, onetile); +#if PREPROCESS_B + PREPROCESS(PREPROCESS_B_INIT, PREPROCESS_B_APPLY, cb_pre_rhs, cb_post_rhs, cb_out, onetile); +#endif + cb_wait_front(cb_post_rhs, onetile); for(uint32_t tile_id = 0; tile_id < num_tiles; ++tile_id) { - cb_wait_front(cb_in0, onetile); - cb_reserve_back(cb_out0, onetile); +#if PREPROCESS_A + PREPROCESS(PREPROCESS_A_INIT, PREPROCESS_A_APPLY, cb_pre_lhs, cb_post_lhs, cb_out, onetile); +#endif + cb_wait_front(cb_post_lhs, onetile); + + cb_reserve_back(cb_out, onetile); +#if PREPROCESS_A || PREPROCESS_B + binary_op_specific_init(); +#endif tile_regs_acquire(); - add_tiles(cb_in0, cb_in1, 0, 0, 0); + BINARY_OP(cb_post_lhs, cb_post_rhs, 0, 0, 0); +#if POSTPROCESS + POSTPROCESS_INIT(); + POSTPROCESS_APPLY(0); +#endif tile_regs_commit(); tile_regs_wait(); - pack_tile(0, cb_out0); + pack_tile(0, cb_out); tile_regs_release(); - cb_pop_front(cb_in0, onetile); - cb_push_back(cb_out0, onetile); + cb_pop_front(cb_post_lhs, onetile); + cb_push_back(cb_out, onetile); } } } // namespace NAMESPACE diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_defines.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_defines.hpp new file mode 100644 index 000000000000..5238d3b8db6a --- /dev/null +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_defines.hpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#define DO_QUOTE(x) #x +#define QUOTE(x) DO_QUOTE(x) + +#if defined(PREPROCESS_A_INIT) +#define PREPROCESS_A 1 +#else +#define PREPROCESS_A 0 +#endif + +#if defined(PREPROCESS_B_INIT) +#define PREPROCESS_B 1 +#else +#define PREPROCESS_B 0 +#endif + +#if defined(POSTPROCESS_INIT) +#define POSTPROCESS 1 +#else +#define POSTPROCESS 0 +#endif diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_utils.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_utils.hpp new file mode 100644 index 000000000000..63f350c47c1e --- /dev/null +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_utils.hpp @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "compute_kernel_api/common.h" +#include "compute_kernel_api/tile_move_copy.h" + +#define PREPROCESS(init, apply, cb_pre, cb_post, cb_out, per_core_block_size) \ + do { \ + using namespace ckernel; \ + \ + copy_tile_to_dst_init_short(); \ + \ + reconfig_data_format_srca(/*old*/ cb_post, /*new*/ cb_pre); \ + pack_reconfig_data_format(/*old*/ cb_out, /*new*/ cb_post); \ + \ + cb_wait_front(cb_pre, per_core_block_size); \ + cb_reserve_back(cb_post, per_core_block_size); \ + \ + tile_regs_acquire(); \ + init(); \ + for (uint32_t i = 0; i < per_core_block_size; ++i) { \ + copy_tile(cb_pre, i, i); \ + apply(i); \ + } \ + tile_regs_commit(); \ + \ + tile_regs_wait(); \ + for (uint32_t i = 0; i < per_core_block_size; ++i) { \ + pack_tile(i, cb_post); /* DST[0]->cb */ \ + } \ + tile_regs_release(); \ + \ + cb_pop_front(cb_pre, per_core_block_size); \ + cb_push_back(cb_post, per_core_block_size); \ + \ + reconfig_data_format_srca(/*old*/ cb_pre, /*new*/ cb_post); \ + pack_reconfig_data_format(/*old*/ cb_post, /*new*/ cb_out); \ + } while (0) diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/types.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/types.hpp index ccb5695b3ac5..06c53bfe7e6d 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/types.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/types.hpp @@ -10,6 +10,7 @@ enum class BinaryOpType { ADD, SUB, MUL, + DIV, GT, LT, LTE, @@ -18,13 +19,11 @@ enum class BinaryOpType { NE, SQUARED_DIFFERENCE, BIAS_GELU, - LOGADDEXP, LOGICAL_AND, LOGICAL_OR, LOGICAL_XOR, LDEXP, + LOGADDEXP, LOGADDEXP2, - DIV_FAST }; - } diff --git a/ttnn/ttnn/operations/binary_ng.py b/ttnn/ttnn/operations/binary_ng.py new file mode 100644 index 000000000000..d012271bda5e --- /dev/null +++ b/ttnn/ttnn/operations/binary_ng.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +import ttnn +import torch + + +ttnn.attach_golden_function(ttnn.experimental.add, golden_function=lambda a, b: a + b) +ttnn.attach_golden_function(ttnn.experimental.sub, golden_function=lambda a, b: a - b) +ttnn.attach_golden_function(ttnn.experimental.mul, golden_function=lambda a, b: a * b) +ttnn.attach_golden_function(ttnn.experimental.div, golden_function=lambda a, b: torch.divide(a, b)) +ttnn.attach_golden_function(ttnn.experimental.eq, golden_function=lambda a, b: torch.eq(a, b)) +ttnn.attach_golden_function(ttnn.experimental.ne, golden_function=lambda a, b: torch.ne(a, b)) +ttnn.attach_golden_function(ttnn.experimental.gt, golden_function=lambda a, b: torch.gt(a, b)) +ttnn.attach_golden_function(ttnn.experimental.lt, golden_function=lambda a, b: torch.lt(a, b)) +ttnn.attach_golden_function(ttnn.experimental.gte, golden_function=lambda a, b: torch.ge(a, b)) +ttnn.attach_golden_function(ttnn.experimental.lte, golden_function=lambda a, b: torch.le(a, b)) +ttnn.attach_golden_function(ttnn.experimental.ldexp, golden_function=lambda a, b: torch.ldexp(a, b)) +ttnn.attach_golden_function(ttnn.experimental.logaddexp, golden_function=lambda a, b: torch.logaddexp(a, b)) +ttnn.attach_golden_function(ttnn.experimental.logaddexp2, golden_function=lambda a, b: torch.logaddexp2(a, b)) +ttnn.attach_golden_function(ttnn.experimental.logical_and, golden_function=lambda a, b: torch.logical_and(a, b)) +ttnn.attach_golden_function(ttnn.experimental.logical_or, golden_function=lambda a, b: torch.logical_or(a, b)) +ttnn.attach_golden_function(ttnn.experimental.logical_xor, golden_function=lambda a, b: torch.logical_xor(a, b)) +ttnn.attach_golden_function( + ttnn.experimental.squared_difference, golden_function=lambda a, b: torch.square(torch.sub(a, b)) +) +ttnn.attach_golden_function( + ttnn.experimental.bias_gelu, golden_function=lambda a, b: torch.nn.functional.gelu(torch.add(a, b)) +) From 5e054594efca1f0b9fed1521b9525fd4443f8470 Mon Sep 17 00:00:00 2001 From: Borys Bradel <164946524+bbradelTT@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:18:36 -0500 Subject: [PATCH 13/87] #14530: remove up front padding from generic reduce (#16053) ### Ticket Link to Github Issue https://github.com/tenstorrent/tt-metal/issues/14530 ### Problem description - padding before running the op changed the logical shape ### What's changed - remove the padding before running the op ### Checklist - [x] Post commit CI passes between https://github.com/tenstorrent/tt-metal/actions/runs/12355026926 and https://github.com/tenstorrent/tt-metal/actions/runs/12359171831 all tests passed in at least one run - [x] Blackhole Post commit (if applicable) https://github.com/tenstorrent/tt-metal/actions/runs/12355031824 - [x] Model regression CI testing passes (if applicable) https://github.com/tenstorrent/tt-metal/actions/runs/12355039357 - [x] Device performance regression CI testing passes (if applicable) https://github.com/tenstorrent/tt-metal/actions/runs/12355035328 - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [x] New/Existing tests provide coverage for changes --- tests/ttnn/unit_tests/operations/test_sum.py | 19 +++++++++++++++ .../reduction/generic/generic_reductions.cpp | 24 +++---------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/tests/ttnn/unit_tests/operations/test_sum.py b/tests/ttnn/unit_tests/operations/test_sum.py index ccbf7c7859d9..35e46e13aafe 100644 --- a/tests/ttnn/unit_tests/operations/test_sum.py +++ b/tests/ttnn/unit_tests/operations/test_sum.py @@ -50,3 +50,22 @@ def test_sum_global(device, batch_size, h, w): output_tensor = output_tensor[0, 0, 0] assert_with_pcc(torch_output_tensor, output_tensor) + + +@pytest.mark.parametrize("n", [1, 9]) +@pytest.mark.parametrize("c", [1, 9]) +@pytest.mark.parametrize("h", [9, 37]) +@pytest.mark.parametrize("w", [9, 63]) +@pytest.mark.parametrize("dim", [None, 0, 1, 2, 3]) +def test_sum_4d(device, n, c, h, w, dim): + torch.manual_seed(0) + + keepdim = True + torch_input_tensor = torch.rand((n, c, h, w), dtype=torch.float32) + torch_output_tensor = torch.sum(torch_input_tensor, dim=dim, keepdim=keepdim) + + input_tensor = ttnn.from_torch(torch_input_tensor, layout=ttnn.TILE_LAYOUT, device=device) + + output_tensor = ttnn.sum(input_tensor, dim=dim, keepdim=keepdim) + output_tensor = ttnn.to_torch(output_tensor) + assert_with_pcc(torch_output_tensor, output_tensor, pcc=0.99) diff --git a/ttnn/cpp/ttnn/operations/reduction/generic/generic_reductions.cpp b/ttnn/cpp/ttnn/operations/reduction/generic/generic_reductions.cpp index 6b5e63c94334..ca77f783e11e 100644 --- a/ttnn/cpp/ttnn/operations/reduction/generic/generic_reductions.cpp +++ b/ttnn/cpp/ttnn/operations/reduction/generic/generic_reductions.cpp @@ -8,6 +8,7 @@ #include "ttnn/operations/eltwise/binary/binary_composite.hpp" #include "ttnn/operations/reduction/generic/device/reduce_op.hpp" #include "ttnn/operations/core/core.hpp" + namespace ttnn { namespace operations::reduction { @@ -59,37 +60,18 @@ static Tensor reduce_impl( if (dim.size() == 1 && rank == 4) { if (dim[0] == rank - 3) { - // Pad before running the op to only pay cost of formatting once - auto input_tensor_pad_shape = AutoFormat::pad_to_tile_shape(input_tensor_arg.get_legacy_shape(), true); auto out_shape = input_tensor_arg.get_legacy_shape(); out_shape[1] = 1; - auto formatted_input_tensor = input_tensor_arg; - float pad_value = (reduce_type == ReduceType::Max) ? -std::numeric_limits::infinity() - : (reduce_type == ReduceType::Min) ? std::numeric_limits::infinity() - : 0; - - if (!AutoFormat::check_input_tensor_format(input_tensor_arg, input_tensor_pad_shape)) { - formatted_input_tensor = AutoFormat::format_input_tensor( - input_tensor_arg, input_tensor_arg.device(), input_tensor_pad_shape, pad_value, Layout::TILE); - } - Tensor output = ttnn::transpose(formatted_input_tensor, 1, -2, memory_config); + Tensor output = ttnn::transpose(input_tensor_arg, 1, -2, memory_config); output = reduce_impl(output, 2, keepdim, memory_config, compute_kernel_config, scalar, false); output = ttnn::transpose(output, 1, -2, memory_config); return AutoFormat::format_output_tensor(output, out_shape, input_tensor_arg.device(), Layout::TILE); } else if (dim[0] == 0) { - // Pad before running the op to only pay cost of formatting once - auto input_tensor_pad_shape = - AutoFormat::pad_to_tile_shape(input_tensor_arg.get_legacy_shape(), false, true); auto out_shape = input_tensor_arg.get_legacy_shape(); out_shape[0] = 1; - auto formatted_input_tensor = input_tensor_arg; - if (!AutoFormat::check_input_tensor_format(input_tensor_arg, input_tensor_pad_shape)) { - formatted_input_tensor = AutoFormat::format_input_tensor( - input_tensor_arg, input_tensor_arg.device(), input_tensor_pad_shape, 0.0, Layout::TILE); - } - Tensor output = ttnn::transpose(formatted_input_tensor, 0, -2, memory_config); + Tensor output = ttnn::transpose(input_tensor_arg, 0, -2, memory_config); output = reduce_impl(output, 2, keepdim, memory_config, compute_kernel_config, scalar, false); output = ttnn::transpose(output, 0, -2, memory_config); return AutoFormat::format_output_tensor(output, out_shape, input_tensor_arg.device(), Layout::TILE); From 668c05c974ef39d045cc413152287106dae6907b Mon Sep 17 00:00:00 2001 From: Michael Chiou <156848643+ttmchiou@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:25:32 -0800 Subject: [PATCH 14/87] Revert "#0: Fix merge conflicts originating from #15289 (#16062)" This reverts commit 05886fd32101a8afb5a6817645f1ec30c9ee4785. --- .../test_tilize_zero_padding_channels_last.cpp | 2 +- .../tensors/test_async_tensor_apis.cpp | 5 ++++- tests/tt_eager/tensors/test_copy_and_move.cpp | 2 +- .../data_movement/reshape_view/reshape.cpp | 18 +++++++++--------- .../experimental/reshape/reshape.cpp | 2 +- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp b/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp index 05655d8cbc86..3445d424d538 100644 --- a/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp +++ b/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp @@ -13,7 +13,7 @@ #include "ttnn/tensor/tensor.hpp" #include "ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp" #include "tt_metal/host_api.hpp" -#include "ttnn/operations/functions.hpp" +#include "ttnn/operations/numpy/functions.hpp" #include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" using namespace tt; diff --git a/tests/tt_eager/tensors/test_async_tensor_apis.cpp b/tests/tt_eager/tensors/test_async_tensor_apis.cpp index dd4c40c6a71b..2791cbc7d537 100644 --- a/tests/tt_eager/tensors/test_async_tensor_apis.cpp +++ b/tests/tt_eager/tensors/test_async_tensor_apis.cpp @@ -20,7 +20,10 @@ #include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" -namespace tt::tt_metal { +using namespace tt; +using namespace tt_metal; +using namespace constants; + namespace { using ::tt::constants::TILE_HEIGHT; diff --git a/tests/tt_eager/tensors/test_copy_and_move.cpp b/tests/tt_eager/tensors/test_copy_and_move.cpp index a194a808678e..b8eabd74003a 100644 --- a/tests/tt_eager/tensors/test_copy_and_move.cpp +++ b/tests/tt_eager/tensors/test_copy_and_move.cpp @@ -39,7 +39,7 @@ bool test_tensor_copy_semantics(Device* device) { // host tensor updated with host tensor copy assignment Tensor host_c = ttnn::experimental::view(ttnn::arange(/*start=*/0, /*stop=*/tt_metal::compute_volume(single_tile_shape), /*step=*/1), single_tile_shape).to(Layout::TILE); - Tensor host_c_copy = ttnn::random::random(single_tile_shape).to(Layout::TILE); + Tensor host_c_copy = ttnn::numpy::random::random(single_tile_shape).to(Layout::TILE); host_c_copy = host_c; auto host_c_data = owned_buffer::get_as(host_c); auto host_c_copy_data = owned_buffer::get_as(host_c_copy); diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp index b5b9e2d80787..b3a747200cca 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp @@ -271,11 +271,12 @@ ttnn::Tensor PerformView(const ttnn::Tensor& tensor, const ttnn::Shape& shape, c return tensor; } if (tensor.get_layout() == ttnn::TILE_LAYOUT && - (shape[-1] % tile_first_dim != 0 || shape.rank() == 1 || shape[-2] % tile_second_dim != 0)) { - // Correct the output shape to add padding metadata before reshape (view) + (shape[-1]%tile_first_dim!=0 || shape.rank()==1 || shape[-2]%tile_second_dim!=0 )) + { + //Correct the output shape to add padding metadata before reshape (view) return ttnn::experimental::view(tensor, tiling_reshape_corrector(shape, tile_first_dim, tile_second_dim)); } - // Perform a reshape (view) + //Perform a reshape (view) return ttnn::experimental::view(tensor, shape); } @@ -343,12 +344,11 @@ ttnn::Tensor ReshapeViewOperation::invoke( // Just edit shape if shape has a 0 dimension if (tensor.get_logical_volume() == 0) { - TT_FATAL(shape.logical_shape().volume() == 0, "Tensor volume is 0, but shape's volume is not"); - TT_FATAL( - (tensor.storage_type() != StorageType::MULTI_DEVICE && - tensor.storage_type() != StorageType::MULTI_DEVICE_HOST), - "Reshaping a multi-device tensor with 0 volume is not supported"); - return ttnn::experimental::view(tensor, shape); + TT_FATAL(shape.logical_shape().volume() == 0 , "Tensor volume is 0, but shape's volume is not"); + TT_FATAL((tensor.storage_type() != StorageType::MULTI_DEVICE && + tensor.storage_type() != StorageType::MULTI_DEVICE_HOST), + "Reshaping a multi-device tensor with 0 volume is not supported"); + return tensor.reshape(shape); } TT_FATAL(shape.logical_shape().volume() != 0, "Tensor volume is not 0, but shape volume is 0"); diff --git a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp index 8419dc81730f..a62303dfa0c9 100644 --- a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp +++ b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp @@ -8,7 +8,7 @@ #include "reshape.hpp" #include "tt_metal/common/constants.hpp" #include -#include +#include #include "ttnn/operations/experimental/auto_format/auto_format.hpp" #include "ttnn/tensor/tensor_utils.hpp" #include "ttnn/operations/data_movement/data_transfer/data_transfer.hpp" From 10d11b60700a6707457c0e5c64a428684cfce145 Mon Sep 17 00:00:00 2001 From: Michael Chiou <156848643+ttmchiou@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:26:21 -0800 Subject: [PATCH 15/87] Revert "Link Tensor.reshape to ttnn.reshape (#15669)" This reverts commit e3dbd25ee2b3ef3bf25d92697ac6ab6a5edb8cf4. --- ...test_tilize_zero_padding_channels_last.cpp | 7 +- .../tensors/test_async_tensor_apis.cpp | 12 +- tests/tt_eager/tensors/test_copy_and_move.cpp | 7 +- tests/ttnn/unit_tests/test_reshape.py | 2 +- ttnn/CMakeLists.txt | 2 - ttnn/cpp/pybind11/pytensor.cpp | 11 +- .../operations/data_movement/fold/fold.cpp | 7 +- .../reshape_on_device/device/reshape_op.cpp | 5 +- .../reshape_on_device/reshape.cpp | 3 +- .../data_movement/reshape_view/reshape.cpp | 9 +- .../experimental/experimental_pybind.cpp | 6 - .../experimental/reshape/reshape.cpp | 161 ------------------ .../experimental/reshape/reshape.hpp | 26 --- .../experimental/reshape/reshape_pybind.cpp | 79 --------- .../experimental/reshape/reshape_pybind.hpp | 13 -- .../device/moreh_getitem_rm_factory.cpp | 3 +- .../pool/global_avg_pool/global_avg_pool.cpp | 3 +- .../split_query_key_value_and_split_heads.cpp | 40 ++--- ttnn/cpp/ttnn/tensor/tensor.cpp | 5 + ttnn/cpp/ttnn/tensor/tensor.hpp | 2 + ttnn/cpp/ttnn/tensor/tensor_ops.cpp | 92 ++++++++++ ttnn/cpp/ttnn/tensor/tensor_ops.hpp | 3 + 22 files changed, 143 insertions(+), 355 deletions(-) delete mode 100644 ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp delete mode 100644 ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp delete mode 100644 ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.cpp delete mode 100644 ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.hpp diff --git a/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp b/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp index 3445d424d538..e565c4d80269 100644 --- a/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp +++ b/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp @@ -13,8 +13,6 @@ #include "ttnn/tensor/tensor.hpp" #include "ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp" #include "tt_metal/host_api.hpp" -#include "ttnn/operations/numpy/functions.hpp" -#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" using namespace tt; using namespace tt_metal; @@ -39,8 +37,9 @@ int main(int argc, char** argv) { //////////////////////////////////////////////////////////////////////////// ttnn::SimpleShape shape{1, 32, 61, 32}; // Allocates a DRAM buffer on device populated with values specified by initialize - Tensor a = - ttnn::experimental::view(ttnn::arange(/*start=*/0, /*stop=*/shape.volume(), /*step=*/1, DataType::BFLOAT16), shape).to(device); + Tensor a = ttnn::arange(/*start=*/0, /*stop=*/shape.volume(), /*step=*/1, DataType::BFLOAT16) + .reshape(shape) + .to(device); Tensor b = ttnn::tilize_with_zero_padding(a); Tensor c = b.cpu(); //////////////////////////////////////////////////////////////////////////// diff --git a/tests/tt_eager/tensors/test_async_tensor_apis.cpp b/tests/tt_eager/tensors/test_async_tensor_apis.cpp index 2791cbc7d537..3ef44800178e 100644 --- a/tests/tt_eager/tensors/test_async_tensor_apis.cpp +++ b/tests/tt_eager/tensors/test_async_tensor_apis.cpp @@ -18,12 +18,7 @@ #include "ttnn/operations/eltwise/binary/binary.hpp" #include "ttnn/operations/eltwise/unary/unary.hpp" -#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" - -using namespace tt; -using namespace tt_metal; -using namespace constants; - +namespace tt::tt_metal { namespace { using ::tt::constants::TILE_HEIGHT; @@ -63,7 +58,7 @@ TEST_F(DispatchFixture, TestTensorOwnershipSanity) { }, host_tensor.get_storage()); // Send tensor to device, read it back and copy it to empty tensor initialized by main thread - Tensor reshaped_tensor = ttnn::experimental::view(host_tensor, ttnn::SimpleShape{1, 1, 32, 128}); + Tensor reshaped_tensor = host_tensor.reshape(ttnn::SimpleShape{1, 1, 32, 128}); auto device_tensor = reshaped_tensor.to(Layout::TILE).to(device); auto thread_local_tensor = device_tensor.cpu().to(Layout::ROW_MAJOR); readback_tensor.set_storage(thread_local_tensor.get_storage()); @@ -290,8 +285,7 @@ TEST_F(DispatchFixture, TestTensorAsyncDataMovement) { }, host_tensor.get_storage()); - Tensor reshaped_tensor = - ttnn::experimental::view(host_tensor, ttnn::SimpleShape{1, 1, 32, tensor_stop / 32}); + Tensor reshaped_tensor = host_tensor.reshape(ttnn::SimpleShape{1, 1, 32, tensor_stop / 32}); auto device_tensor = reshaped_tensor.to(Layout::TILE).to(device); auto thread_local_tensor = device_tensor.cpu().to(Layout::ROW_MAJOR); log_info(LogTest, "Worker populating empty host readback_tensor"); diff --git a/tests/tt_eager/tensors/test_copy_and_move.cpp b/tests/tt_eager/tensors/test_copy_and_move.cpp index b8eabd74003a..5fb62254db1a 100644 --- a/tests/tt_eager/tensors/test_copy_and_move.cpp +++ b/tests/tt_eager/tensors/test_copy_and_move.cpp @@ -11,7 +11,6 @@ #include "ttnn/tensor/tensor_impl.hpp" #include "tt_metal/host_api.hpp" #include "ttnn/operations/functions.hpp" -#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" using namespace tt; using namespace tt_metal; @@ -38,8 +37,10 @@ bool test_tensor_copy_semantics(Device* device) { pass &= dev_a_data == dev_a_copy_data; // host tensor updated with host tensor copy assignment - Tensor host_c = ttnn::experimental::view(ttnn::arange(/*start=*/0, /*stop=*/tt_metal::compute_volume(single_tile_shape), /*step=*/1), single_tile_shape).to(Layout::TILE); - Tensor host_c_copy = ttnn::numpy::random::random(single_tile_shape).to(Layout::TILE); + Tensor host_c = ttnn::arange(/*start=*/0, /*stop=*/tt_metal::compute_volume(single_tile_shape), /*step=*/1) + .reshape(single_tile_shape) + .to(Layout::TILE); + Tensor host_c_copy = ttnn::random::random(single_tile_shape).to(Layout::TILE); host_c_copy = host_c; auto host_c_data = owned_buffer::get_as(host_c); auto host_c_copy_data = owned_buffer::get_as(host_c_copy); diff --git a/tests/ttnn/unit_tests/test_reshape.py b/tests/ttnn/unit_tests/test_reshape.py index 2d323473fe33..823085fe6564 100644 --- a/tests/ttnn/unit_tests/test_reshape.py +++ b/tests/ttnn/unit_tests/test_reshape.py @@ -36,7 +36,7 @@ def test_reshape_sharded_rm(device, n, c, h, w): torch_input_tensor, layout=ttnn.ROW_MAJOR_LAYOUT, device=device, memory_config=sharded_mem_config ) - tt_output_tensor = ttnn.experimental.view(tt_input_tensor, n, c, h * 2, w // 2) + tt_output_tensor = tt_input_tensor.reshape(n, c, h * 2, w // 2) sharded_mem_config = ttnn.create_sharded_memory_config( tt_output_tensor.shape, diff --git a/ttnn/CMakeLists.txt b/ttnn/CMakeLists.txt index 5ee740e2a261..c5dcfab9443f 100644 --- a/ttnn/CMakeLists.txt +++ b/ttnn/CMakeLists.txt @@ -564,8 +564,6 @@ set(ALL_TTNN_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/data_movement/expand/expand_pybind.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/data_movement/expand/device/expand_rm_program_factory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/data_movement/expand/device/expand_device_operation.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/experimental/reshape/reshape.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/experimental/reshape/reshape_pybind.cpp ) #Split src and python bindings diff --git a/ttnn/cpp/pybind11/pytensor.cpp b/ttnn/cpp/pybind11/pytensor.cpp index 3071f382e380..68a507a2c4a9 100644 --- a/ttnn/cpp/pybind11/pytensor.cpp +++ b/ttnn/cpp/pybind11/pytensor.cpp @@ -21,10 +21,6 @@ #include "ttnn/tensor/tensor_impl.hpp" #include "ttnn/tensor/tensor_ops.hpp" -#include "ttnn/common/constants.hpp" -#include "ttnn/operations/core/core.hpp" - - using namespace tt::tt_metal; namespace py = pybind11; @@ -1687,11 +1683,10 @@ void pytensor_module(py::module& m_tensor) { dtype = tt_tensor.get_dtype() )doc") - .def( "reshape", [](Tensor& self, int N, int C, int H, int W) { - return ttnn::reshape(self, infer_dims_for_reshape(self, ttnn::SmallVector{N, C, H, W})); + return self.reshape(infer_dims_for_reshape(self, ttnn::SmallVector{N, C, H, W})); }, R"doc( Reshapes TT tensor @@ -1702,7 +1697,7 @@ void pytensor_module(py::module& m_tensor) { )doc") .def( "reshape", - [](Tensor& self, const ttnn::Shape& shape) -> Tensor { return ttnn::reshape(self, shape); }, + [](Tensor& self, const ttnn::Shape& shape) -> Tensor { return self.reshape(shape); }, R"doc( Reshapes TT tensor @@ -1713,7 +1708,7 @@ void pytensor_module(py::module& m_tensor) { .def( "reshape", [](Tensor& self, const ttnn::SmallVector& shape) -> Tensor { - return ttnn::reshape(self, infer_dims_for_reshape(self, shape)); + return self.reshape(infer_dims_for_reshape(self, shape)); }, R"doc( Reshapes TT tensor diff --git a/ttnn/cpp/ttnn/operations/data_movement/fold/fold.cpp b/ttnn/cpp/ttnn/operations/data_movement/fold/fold.cpp index 0a8a5253366c..754ea6e24b12 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/fold/fold.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/fold/fold.cpp @@ -12,7 +12,6 @@ #include "ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.hpp" #include "ttnn/cpp/ttnn/operations/data_movement/pad/pad.hpp" #include "tt_metal/common/constants.hpp" -#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" #include "fold.hpp" @@ -222,8 +221,7 @@ std::vector fold_with_transpose_sharded_( // reshape n = tt_output_tensor.shape()[0], w = tt_output_tensor.shape()[1], c = tt_output_tensor.shape()[2], h = tt_output_tensor.shape()[3]; - tt_output_tensor = - ttnn::experimental::view(tt_output_tensor, ttnn::SimpleShape{n, (w / stride_w), (c * stride_w), h}); + tt_output_tensor = tt_output_tensor.reshape(ttnn::SimpleShape{n, (w / stride_w), (c * stride_w), h}); tt::log_debug("reshape_hc_output: {}", tt_output_tensor.shape()); @@ -236,8 +234,7 @@ std::vector fold_with_transpose_sharded_( // reshape n = tt_output_tensor.shape()[0], w = tt_output_tensor.shape()[1], h = tt_output_tensor.shape()[2], c = tt_output_tensor.shape()[3]; - tt_output_tensor = - ttnn::experimental::view(tt_output_tensor, ttnn::SimpleShape{n, w, (h / stride_h), (c * stride_h)}); + tt_output_tensor = tt_output_tensor.reshape(ttnn::SimpleShape{n, w, (h / stride_h), (c * stride_h)}); tt::log_debug("reshape_hw_output: {}", tt_output_tensor.shape()); diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_op.cpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_op.cpp index 6ed1e13dc3bf..1da82c867772 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_op.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_op.cpp @@ -26,11 +26,10 @@ void ReshapeDeviceOperation::validate(const std::vector& input_tensors) TT_FATAL( input_tensor_a.memory_config().memory_layout == TensorMemoryLayout::INTERLEAVED, - "Use ttnn::experimental::view for reshaping sharded inputs"); + "Reshape does not currently support sharding"); TT_FATAL( this->output_mem_config.memory_layout == TensorMemoryLayout::INTERLEAVED, - "Reshape does not currently support sharding. Use ttnn::experimental::view for reshaping sharded " - "inputs"); + "Reshape does not currently support sharding"); if (input_tensor_a.get_layout() == Layout::TILE) { TT_FATAL(input_tensor_a.volume() % TILE_HW == 0, "Error"); diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.cpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.cpp index 445b111c3d7b..0910eb284cfa 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.cpp @@ -10,7 +10,6 @@ #include "ttnn/operations/experimental/auto_format/auto_format.hpp" #include "ttnn/tensor/tensor_utils.hpp" #include "device/reshape_op.hpp" -#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" namespace ttnn::operations::data_movement { @@ -58,7 +57,7 @@ ttnn::Tensor ReshapeOperation::invoke( padded_output_shape[3] == input_tensor.get_padded_shape()[3])) { // Don't need to do a check here to see the H and W both divisible by 32 // since handled within the tensor reshape method - return ttnn::experimental::view(input_tensor, output_shape); + return input_tensor.reshape(output_shape); } if (input_tensor.get_padded_shape() == padded_output_shape) { return ttnn::operations::experimental::auto_format::AutoFormat::move_tensor_to_mem_config( diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp index b3a747200cca..924da1f446b8 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp @@ -22,7 +22,6 @@ #include "ttnn/operations/data_movement/sharded/interleaved_to_sharded/interleaved_to_sharded.hpp" #include "ttnn/operations/data_movement/untilize_with_unpadding/untilize_with_unpadding.hpp" #include "ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp" -#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" namespace ttnn::operations::data_movement { @@ -274,10 +273,10 @@ ttnn::Tensor PerformView(const ttnn::Tensor& tensor, const ttnn::Shape& shape, c (shape[-1]%tile_first_dim!=0 || shape.rank()==1 || shape[-2]%tile_second_dim!=0 )) { //Correct the output shape to add padding metadata before reshape (view) - return ttnn::experimental::view(tensor, tiling_reshape_corrector(shape, tile_first_dim, tile_second_dim)); + return tensor.reshape(tiling_reshape_corrector(shape, tile_first_dim, tile_second_dim)); } //Perform a reshape (view) - return ttnn::experimental::view(tensor, shape); + return tensor.reshape(shape); } ttnn::Shape shape_corrector(const ttnn::Tensor& tensor, const ttnn::Shape& shape) { @@ -361,7 +360,7 @@ ttnn::Tensor ReshapeViewOperation::invoke( tensor_shape_second_last_dim % tile_first_dim == 0)); // There is no padding on the second last dimension if (!(ttnn::has_storage_type_of(tensor, ttnn::StorageType::DEVICE))) { // This case has been allowed in the past though it means introducing padding values to the data - return ttnn::experimental::view(tensor, shape); + return tensor.reshape(shape); } if (this_is_view) { @@ -378,7 +377,7 @@ ttnn::Tensor ReshapeViewOperation::invoke( if (tile_tensor_view_reshape_possible) { // This case has been allowed in the past though it means introducing padding values to the data - return ttnn::experimental::view(tensor, shape); + return tensor.reshape(shape); } //This is a completely incorrect test but it is due to issue 15558 TT_FATAL(false, "Attempting to reshape between two shapes with different volumes"); diff --git a/ttnn/cpp/ttnn/operations/experimental/experimental_pybind.cpp b/ttnn/cpp/ttnn/operations/experimental/experimental_pybind.cpp index 599e8eaf785d..d6f9431947f3 100644 --- a/ttnn/cpp/ttnn/operations/experimental/experimental_pybind.cpp +++ b/ttnn/cpp/ttnn/operations/experimental/experimental_pybind.cpp @@ -36,9 +36,6 @@ #include "ttnn/operations/experimental/ccl/all_gather_matmul/all_gather_matmul_pybind.hpp" #include "ttnn/operations/experimental/ccl/all_reduce/all_reduce_pybind.hpp" #include "ttnn/operations/experimental/plusone/plusone_pybind.hpp" - -#include "ttnn/operations/experimental/reshape/reshape_pybind.hpp" - namespace ttnn::operations::experimental { void py_module(py::module& module) { @@ -79,13 +76,10 @@ void py_module(py::module& module) { plusone::detail::bind_experimental_plusone_operation(module); - reshape::detail::py_bind_view(module); - // CCL ops auto m_experimental_ccl = module.def_submodule("ccl", "experiemental collective communication operations"); ccl::py_bind_all_gather_matmul(m_experimental_ccl); ccl::py_bind_all_reduce(m_experimental_ccl); - } } // namespace ttnn::operations::experimental diff --git a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp deleted file mode 100644 index a62303dfa0c9..000000000000 --- a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.cpp +++ /dev/null @@ -1,161 +0,0 @@ - -// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. -// -// SPDX-License-Identifier: Apache-2.0 - -#include "ttnn/common/constants.hpp" -#include "ttnn/run_operation.hpp" -#include "reshape.hpp" -#include "tt_metal/common/constants.hpp" -#include -#include -#include "ttnn/operations/experimental/auto_format/auto_format.hpp" -#include "ttnn/tensor/tensor_utils.hpp" -#include "ttnn/operations/data_movement/data_transfer/data_transfer.hpp" -#include "ttnn/operations/data_movement/slice/slice.hpp" -#include "ttnn/operations/core/core.hpp" - - -#include "ttnn/tensor/tensor.hpp" - -#include -#include - -#include "common/bfloat16.hpp" -#include "ttnn/tensor/tensor_impl.hpp" -#include "ttnn/tensor/tensor_impl_wrapper.hpp" -#include "ttnn/tensor/tensor_utils.hpp" -#include "ttnn/tensor/types.hpp" -#include "tt_metal/common/constants.hpp" -#include "tt_metal/common/math.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" -#include "tt_metal/graph/graph_tracking.hpp" -#include "ttnn/distributed/api.hpp" -#include "ttnn/distributed/types.hpp" -#include "ttnn/core.hpp" - -namespace ttnn::operations::experimental::reshape { - -ttnn::Tensor MultiDeviceHostStorageVisit( - auto&& storage, const ttnn::Tensor& input_tensor, const ttnn::Shape& new_shape) { - using T = std::decay_t; - auto updated_storage = std::get(input_tensor.get_storage()); - for (int i = 0; i < updated_storage.shapes.size(); i++) { - updated_storage.shapes[i] = new_shape; - } - if (input_tensor.get_layout() == Layout::ROW_MAJOR) { - return Tensor(updated_storage, new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), std::nullopt); - } else { - const auto tile = input_tensor.get_tensor_spec().tile(); - return Tensor(updated_storage, new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), tile); - } -} - -ttnn::Tensor MultiDeviceStorageVisit(auto&& storage, const ttnn::Tensor& input_tensor, const ttnn::Shape& new_shape) { - using T = std::decay_t; - MultiDeviceStorage updated_storage = std::get(input_tensor.get_storage()); - std::unordered_map new_shapes; - - for (auto device_id : updated_storage.ordered_device_ids) { - new_shapes.insert({device_id, new_shape}); - } - updated_storage.shapes = new_shapes; - if (input_tensor.get_layout() == Layout::ROW_MAJOR) { - return Tensor(updated_storage, new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), std::nullopt); - } else { - const auto tile = input_tensor.get_tensor_spec().tile(); - return Tensor(updated_storage, new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), tile); - } -} - -ttnn::Tensor DeviceStorageVisit(auto&& storage, const ttnn::Tensor& input_tensor, const ttnn::Shape& new_shape) { - using T = std::decay_t; - if (input_tensor.get_layout() == Layout::ROW_MAJOR) { - if (input_tensor.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED) { - DeviceStorage device_storage = std::get(input_tensor.get_storage()); - DeviceBuffer device_buffer = device_storage.get_buffer(); - device_buffer->set_page_size(new_shape[-1] * input_tensor.element_size()); - device_storage.insert_buffer(device_buffer); - return Tensor(device_storage, new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), std::nullopt); - } else { - DeviceStorage device_storage = std::get(input_tensor.get_storage()); - DeviceBuffer device_buffer = device_storage.get_buffer(); - ShardSpecBuffer shard_spec_buffer = device_buffer->shard_spec(); - - auto shard_spec = shard_spec_buffer.tensor_shard_spec; - auto shard_shape = shard_spec.shape; - - uint32_t mul_div = - new_shape[-1] > shard_shape[1] ? (new_shape[-1] / shard_shape[1]) : (shard_shape[1] / new_shape[-1]); - shard_spec.shape[0] = new_shape[-1] > shard_shape[1] ? shard_shape[0] / mul_div : shard_shape[0] * mul_div; - shard_spec.shape[1] = new_shape[-1]; - - shard_spec_buffer.page_shape = {1, new_shape[-1]}; - shard_spec_buffer.tensor2d_shape = {shard_spec.shape[0], 1}; - shard_spec_buffer.set_shard_spec(shard_spec); - - device_buffer->set_shard_spec(shard_spec_buffer); - device_storage.insert_buffer(device_buffer); - - return Tensor(device_storage, new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), std::nullopt); - } - } else { - const auto tile = input_tensor.get_tensor_spec().tile(); - return Tensor(input_tensor.get_storage(), new_shape, input_tensor.get_dtype(), input_tensor.get_layout(), tile); - } -} - -ttnn::Tensor tensor_reshape(const ttnn::Tensor& input_tensor, const ttnn::Shape& new_shape) { - ZoneScoped; - GraphTracker::instance().track_function_start("ttnn::experimental::view", input_tensor, new_shape); - const auto& new_padded_shape = new_shape.padded_shape(); - - TT_ASSERT( - input_tensor.volume() == new_padded_shape.volume(), - "{} != {}", - input_tensor.volume(), - new_padded_shape.volume()); - if (input_tensor.get_layout() == Layout::TILE) { - const auto tile = input_tensor.get_tensor_spec().tile(); - TT_ASSERT( - new_padded_shape[-2] % tile.get_tile_shape()[0] == 0 && - new_padded_shape[-1] % tile.get_tile_shape()[1] == 0 && - "Expected a multiple of 32 for H, W (or -1 evaluating to such) in ttnn::experimental::view()!"); - } - auto output = std::visit( - [&input_tensor, &new_shape](auto&& storage) -> Tensor { - using T = std::decay_t; - const auto& tensor = input_tensor; - if constexpr (std::is_same_v) { - return MultiDeviceHostStorageVisit(storage, input_tensor, new_shape); - } - if constexpr (std::is_same_v) { - return MultiDeviceStorageVisit(storage, input_tensor, new_shape); - } - if constexpr (std::is_same_v) { - return DeviceStorageVisit(storage, input_tensor, new_shape); - } else { - if (input_tensor.get_layout() == Layout::ROW_MAJOR) { - return Tensor( - tensor.get_storage(), new_shape, tensor.get_dtype(), tensor.get_layout(), std::nullopt); - } else { - const auto tile = input_tensor.get_tensor_spec().tile(); - return Tensor(tensor.get_storage(), new_shape, tensor.get_dtype(), tensor.get_layout(), tile); - } - } - }, - input_tensor.get_storage()); - output = tt::tt_metal::set_tensor_id(output); - GraphTracker::instance().track_function_end(output); - return output; -} - -ttnn::Tensor ViewOperation::invoke(const ttnn::Tensor& tensor, const ttnn::SimpleShape& shape) { - return tensor_reshape(tensor, ttnn::Shape(shape.view())); -} - -ttnn::Tensor ViewOperation::invoke(const ttnn::Tensor& tensor, const ttnn::Shape& shape) { - return tensor_reshape(tensor, shape); -} - -} // namespace ttnn::operations::experimental::reshape diff --git a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp deleted file mode 100644 index 1bc54d12d1b7..000000000000 --- a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. -// -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "ttnn/run_operation.hpp" -#include "ttnn/decorators.hpp" -#include - -namespace ttnn { -namespace operations::experimental::reshape { - -struct ViewOperation { - static ttnn::Tensor invoke(const ttnn::Tensor& input_tensor, const ttnn::Shape& shape); - static ttnn::Tensor invoke(const ttnn::Tensor& input_tensor, const ttnn::SimpleShape& shape); -}; - -} // namespace operations::experimental::reshape - -namespace experimental { -constexpr auto view = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::view", - ttnn::operations::experimental::reshape::ViewOperation>(); -} // namespace experimental -} // namespace ttnn diff --git a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.cpp b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.cpp deleted file mode 100644 index 4ec1a2a14f9c..000000000000 --- a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. -// -// SPDX-License-Identifier: Apache-2.0 - -#include "reshape_pybind.hpp" -#include "reshape.hpp" - -#include -#include - -#include "ttnn/cpp/pybind11/decorators.hpp" - -#include "ttnn/types.hpp" - -#include "ttnn/tensor/tensor.hpp" -#include "ttnn/tensor/tensor_impl.hpp" - - -namespace ttnn::operations::experimental::reshape::detail { -namespace py = pybind11; - -void py_bind_view(py::module& module) { - auto doc = R"doc( - - Note: - - It is recommended to use ttnn.reshape if you are not sure which operation to use - - If this is the functionality required for your application, it will be called by ttnn.reshape - - The following conditions must be met for the function not to corrupt your data: - * the last dimension must not change - * In Layout::TILE the second last two dimensions must not change OR there is no padding on the second last dimension - - - Args: - * input_tensor: Input Tensor. - * new_shape: New shape of tensor. - - Returns: - ttnn.Tensor: the output tensor with the new shape. - - Example: - - >>> tensor = ttnn.from_torch(torch.tensor((2, 1, 4), dtype=torch.bfloat16), device=device) - >>> output = ttnn.experimental.view(tensor, (2, 1, 1, 4)) - - )doc"; - bind_registered_operation( - module, - ttnn::experimental::view, - doc, - ttnn::pybind_overload_t{ - [](const decltype(ttnn::experimental::view)& self, ttnn::Tensor& input_tensor, int N, int C, int H, int W) { - return self(input_tensor, infer_dims_for_reshape(input_tensor, ttnn::SmallVector{N, C, H, W})); - }, - py::arg("input_tensor"), - py::arg("N"), - py::arg("C"), - py::arg("H"), - py::arg("W"), - }, - - ttnn::pybind_overload_t{ - [](const decltype(ttnn::experimental::view)& self, ttnn::Tensor& input_tensor, const ttnn::Shape& shape) { - return self(input_tensor, shape); - }, - py::arg("input_tensor"), - py::arg("shape"), - }, - ttnn::pybind_overload_t{ - [](const decltype(ttnn::experimental::view)& self, - ttnn::Tensor& input_tensor, - const ttnn::SmallVector& shape) { - return self(input_tensor, infer_dims_for_reshape(input_tensor, shape)); - }, - py::arg("input_tensor"), - py::arg("shape"), - }); -} - -} // namespace ttnn::operations::experimental::reshape::detail diff --git a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.hpp b/ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.hpp deleted file mode 100644 index b2f9d94d5b70..000000000000 --- a/ttnn/cpp/ttnn/operations/experimental/reshape/reshape_pybind.hpp +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. -// -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "pybind11/pybind_fwd.hpp" - -namespace ttnn::operations::experimental::reshape::detail { - -void py_bind_view(pybind11::module& module); - -} // namespace ttnn::operations::experimental diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_rm_factory.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_rm_factory.cpp index 8a81c5b302e2..8a37c4b6f7dd 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_rm_factory.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_rm_factory.cpp @@ -4,7 +4,6 @@ #include "moreh_getitem_device_operation.hpp" #include "ttnn/operations/moreh/moreh_helper_functions.hpp" -#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" namespace { namespace CMAKE_UNIQUE_NAMESPACE { @@ -60,7 +59,7 @@ MorehGetItemOperation::MorehGetItemRmFactory::cached_program_t MorehGetItemOpera uint32_t index_end_dim = index_dims.back(); Tensor input_5d = input; - input_5d = ttnn::experimental::view(input_5d, input_5d_shape); + input_5d = input_5d.reshape(input_5d_shape); auto input_5d_shape_without_padding = input_5d_shape.value.without_padding(); diff --git a/ttnn/cpp/ttnn/operations/pool/global_avg_pool/global_avg_pool.cpp b/ttnn/cpp/ttnn/operations/pool/global_avg_pool/global_avg_pool.cpp index 98637a7cb069..557559206121 100644 --- a/ttnn/cpp/ttnn/operations/pool/global_avg_pool/global_avg_pool.cpp +++ b/ttnn/cpp/ttnn/operations/pool/global_avg_pool/global_avg_pool.cpp @@ -4,7 +4,6 @@ #include "ttnn/operations/pool/global_avg_pool/global_avg_pool.hpp" #include "ttnn/operations/reduction/generic/generic_reductions.hpp" -#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" namespace tt { namespace tt_metal { @@ -41,7 +40,7 @@ Tensor global_avg_pool2d( input_padding.pad_value()); auto output_shape = tt::tt_metal::LegacyShape({in_shape[0], 1, in_shape[1] * in_shape[2], in_shape[3]}, output_padding); - output = ttnn::experimental::view(output, output_shape); + output = output.reshape(output_shape); output = pool_2d(output, memory_config, output_dtype); return output; diff --git a/ttnn/cpp/ttnn/operations/transformer/split_query_key_value_and_split_heads/split_query_key_value_and_split_heads.cpp b/ttnn/cpp/ttnn/operations/transformer/split_query_key_value_and_split_heads/split_query_key_value_and_split_heads.cpp index 819ca1654336..4ac733211eb0 100644 --- a/ttnn/cpp/ttnn/operations/transformer/split_query_key_value_and_split_heads/split_query_key_value_and_split_heads.cpp +++ b/ttnn/cpp/ttnn/operations/transformer/split_query_key_value_and_split_heads/split_query_key_value_and_split_heads.cpp @@ -9,7 +9,6 @@ #include "ttnn/cpp/ttnn/operations/experimental/transformer/nlp_create_qkv_heads/nlp_create_qkv_heads.hpp" #include "ttnn/cpp/ttnn/operations/experimental/transformer/nlp_create_qkv_heads_falcon7b/nlp_create_qkv_heads_falcon7b.hpp" #include "ttnn/cpp/ttnn/operations/experimental/transformer/create_qkv_heads/create_qkv_heads.hpp" -#include "ttnn/cpp/ttnn/operations/experimental/reshape/reshape.hpp" namespace ttnn::operations::transformer { @@ -105,13 +104,11 @@ std::tuple SplitQueryKeyValueAndSplitHeadsOperation::inv head_size, padded_head_size); - const auto input_4d = ttnn::experimental::view( - input_tensor, - ttnn::SimpleShape{ - input_shape.with_tile_padding()[0], - 1, - input_shape.with_tile_padding()[1], - input_shape.with_tile_padding()[2]}); + const auto input_4d = input_tensor.reshape(ttnn::SimpleShape{ + input_shape.with_tile_padding()[0], + 1, + input_shape.with_tile_padding()[1], + input_shape.with_tile_padding()[2]}); auto outputs = ttnn::experimental::nlp_create_qkv_heads_falcon7b( input_4d, memory_config.value_or(input_tensor.memory_config())); return detail::reshape_outputs_of_split_query_key_value_and_split_heads( @@ -171,13 +168,11 @@ std::tuple SplitQueryKeyValueAndSplitHeadsOperation::inv "Invalid operation: KV tensor should not be provided when the input tensor is sharded. Please ensure that " "the KV tensor is only used in non-sharded configurations."); - const auto input_tensor_4d = ttnn::experimental::view( - input_tensor, - ttnn::SimpleShape{ - input_shape.with_tile_padding()[0], - 1, - input_shape.with_tile_padding()[1], - input_shape.with_tile_padding()[2]}); + const auto input_tensor_4d = input_tensor.reshape(ttnn::SimpleShape{ + input_shape.with_tile_padding()[0], + 1, + input_shape.with_tile_padding()[1], + input_shape.with_tile_padding()[2]}); return detail::reshape_outputs_of_split_query_key_value_and_split_heads( ttnn::experimental::create_qkv_heads( input_tensor_4d, @@ -189,18 +184,15 @@ std::tuple SplitQueryKeyValueAndSplitHeadsOperation::inv sequence_size_padded, transpose_key); } else { - const auto input_tensor_4d = ttnn::experimental::view( - input_tensor, - ttnn::SimpleShape{ - input_shape.with_tile_padding()[0], - 1, - input_shape.with_tile_padding()[1], - input_shape.with_tile_padding()[2]}); + const auto input_tensor_4d = input_tensor.reshape(ttnn::SimpleShape{ + input_shape.with_tile_padding()[0], + 1, + input_shape.with_tile_padding()[1], + input_shape.with_tile_padding()[2]}); std::optional input_tensor_kv_4d = std::nullopt; if (input_tensor_kv.has_value()) { auto padded_input_shape_kv = input_tensor_kv.value().get_shape().with_tile_padding(); - input_tensor_kv_4d = ttnn::experimental::view( - input_tensor_kv.value(), + input_tensor_kv_4d = input_tensor_kv.value().reshape( ttnn::SimpleShape{padded_input_shape_kv[0], 1, padded_input_shape_kv[1], padded_input_shape_kv[2]}); } const auto outputs = ttnn::experimental::nlp_create_qkv_heads( diff --git a/ttnn/cpp/ttnn/tensor/tensor.cpp b/ttnn/cpp/ttnn/tensor/tensor.cpp index eab2d044d5bb..f4304d33c6a5 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor.cpp @@ -630,6 +630,11 @@ const bool Tensor::is_sharded() const { uint32_t Tensor::element_size() const { return tensor_impl::element_size_bytes(this->get_dtype()); } +Tensor Tensor::reshape(const ttnn::SimpleShape& new_shape) const { + return tensor_ops::tensor_reshape(*this, new_shape); +} + +Tensor Tensor::reshape(const ttnn::Shape& new_shape) const { return tensor_ops::tensor_reshape(*this, new_shape); } bool Tensor::is_allocated() const { ZoneScoped; diff --git a/ttnn/cpp/ttnn/tensor/tensor.hpp b/ttnn/cpp/ttnn/tensor/tensor.hpp index 08b7a653389c..b8b7a993b8a6 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor.hpp @@ -188,6 +188,8 @@ struct Tensor { // ====================================================================================== // Low Level APIs // ====================================================================================== + Tensor reshape(const ttnn::SimpleShape& new_shape) const; + Tensor reshape(const ttnn::Shape& new_shape) const; // ====================================================================================== // Getters diff --git a/ttnn/cpp/ttnn/tensor/tensor_ops.cpp b/ttnn/cpp/ttnn/tensor/tensor_ops.cpp index d7f03b7a0725..44ee8fd7a6c7 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_ops.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor_ops.cpp @@ -373,5 +373,97 @@ Tensor tensor_unpad_from_tile(const Tensor& input_tensor, const ttnn::SimpleShap return output; } +Tensor tensor_reshape(const Tensor& input_tensor, const ttnn::Shape& new_shape) { + ZoneScoped; + GraphTracker::instance().track_function_start("Tensor::reshape", input_tensor, new_shape); + const auto& new_padded_shape = new_shape.padded_shape(); + const auto tile = input_tensor.get_tensor_spec().tile(); + TT_ASSERT( + input_tensor.volume() == new_padded_shape.volume(), + "{} != {}", + input_tensor.volume(), + new_padded_shape.volume()); + if (input_tensor.get_layout() == Layout::TILE) { + TT_ASSERT( + new_padded_shape[-2] % tile.get_tile_shape()[0] == 0 && + new_padded_shape[-1] % tile.get_tile_shape()[1] == 0 && + "Expected a multiple of 32 for H, W (or -1 evaluating to such) in Tensor::reshape()!"); + } + auto output = std::visit( + [&input_tensor, &new_shape, &tile](auto&& storage) -> Tensor { + using T = std::decay_t; + const auto& tensor = input_tensor; + if constexpr (std::is_same_v) { + auto updated_storage = std::get(tensor.get_storage()); + for (int i = 0; i < updated_storage.shapes.size(); i++) { + updated_storage.shapes[i] = new_shape; + } + return Tensor(updated_storage, new_shape, tensor.get_dtype(), tensor.get_layout(), tile); + } + if constexpr (std::is_same_v) { + MultiDeviceStorage updated_storage = std::get(tensor.get_storage()); + std::unordered_map new_shapes; + + for (auto device_id : updated_storage.ordered_device_ids) { + new_shapes.insert({device_id, new_shape}); + } + updated_storage.shapes = new_shapes; + return Tensor(updated_storage, new_shape, tensor.get_dtype(), tensor.get_layout(), tile); + } + if constexpr (std::is_same_v) { + if (input_tensor.get_layout() == Layout::ROW_MAJOR) { + if (tensor.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED) { + DeviceStorage device_storage = std::get(tensor.get_storage()); + DeviceBuffer device_buffer = device_storage.get_buffer(); + const auto& tensor_spec = tensor.tensor_spec(); + auto page_size_bytes = tensor_spec.compute_page_size_bytes(); + device_buffer->set_page_size(page_size_bytes); + device_storage.insert_buffer(device_buffer); + return Tensor(device_storage, new_shape, tensor.get_dtype(), tensor.get_layout(), tile); + } else { + DeviceStorage device_storage = std::get(tensor.get_storage()); + DeviceBuffer device_buffer = device_storage.get_buffer(); + ShardSpecBuffer shard_spec_buffer = device_buffer->shard_spec(); + + auto shard_spec = shard_spec_buffer.tensor_shard_spec; + auto shard_shape = shard_spec.shape; + + uint32_t mul_div; + if (new_shape[-1] == 0 || shard_shape[1] == 0) { + mul_div = 0; + } else { + mul_div = new_shape[-1] > shard_shape[1] ? + (new_shape[-1] / shard_shape[1]) : + (shard_shape[1] / new_shape[-1]); + } + + shard_spec.shape[0] = new_shape[-1] > shard_shape[1] ? shard_shape[0] / mul_div : shard_shape[0] * mul_div; + shard_spec.shape[1] = new_shape[-1]; + + shard_spec_buffer.page_shape = {1, new_shape[-1]}; + shard_spec_buffer.tensor2d_shape = {shard_spec.shape[0], 1}; + shard_spec_buffer.set_shard_spec(shard_spec); + + device_buffer->set_shard_spec(shard_spec_buffer); + device_storage.insert_buffer(device_buffer); + + return Tensor(device_storage, new_shape, tensor.get_dtype(), tensor.get_layout(), tile); + } + } else { + return Tensor(tensor.get_storage(), new_shape, tensor.get_dtype(), tensor.get_layout(), tile); + } + } else { + return Tensor(tensor.get_storage(), new_shape, tensor.get_dtype(), tensor.get_layout(), tile); + } + }, + input_tensor.get_storage()); + output = tt::tt_metal::set_tensor_id(output); + GraphTracker::instance().track_function_end(output); + return output; +} + +Tensor tensor_reshape(const Tensor& input_tensor, const ttnn::SimpleShape& new_shape) { + return tensor_reshape(input_tensor, ttnn::Shape(new_shape.view())); +} } // namespace tt::tt_metal::tensor_ops diff --git a/ttnn/cpp/ttnn/tensor/tensor_ops.hpp b/ttnn/cpp/ttnn/tensor/tensor_ops.hpp index bba2ce486195..b8edff425f8b 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_ops.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor_ops.hpp @@ -60,4 +60,7 @@ Tensor tensor_pad_to_tile(const Tensor& input_tensor, float pad_value); Tensor tensor_unpad_from_tile(const Tensor& input_tensor, const ttnn::SimpleShape& output_tensor_shape); +Tensor tensor_reshape(const Tensor& input_tensor, const ttnn::SimpleShape& new_shape); +Tensor tensor_reshape(const Tensor& input_tensor, const ttnn::Shape& new_shape); + } // namespace tt::tt_metal::tensor_ops From 60f2d28a86bf058a7234e370a3fa772e66bc94a7 Mon Sep 17 00:00:00 2001 From: Oleg Milyutin Date: Mon, 16 Dec 2024 21:43:15 -0500 Subject: [PATCH 16/87] #15061: Implement multi-device tensor distribution APIs in terms of C++ ttnn tensors (#15886) ### Ticket #15755 ### Problem description Multi-device tensor distribution currently works through `distributed.py`, which relies on PyTorch libraries to perform sharding / concatenation. ### What's changed * Add xtensor to ttnn. * Lower facilities from tt-train down to ttnn. In particular: `chunk`, `concatenate` functions along with some conversion utils, and the relevant tests. * Add `distributed_tensor.hpp` header with the multi-device distribution APIs. **In follow up PRs:** * Support bf4 / bf8 and other formats in `from_vector` / `to_vector` and other overloads. * Support outputting a tilized tensor. * Migrate functionality from `pytensor.cpp` to using the new APIs. ### Checklist - [x] [Post commit CI passes](https://github.com/tenstorrent/tt-metal/actions/runs/12333746639/job/34427015707) (failure in clang-tidy in unreleated tt-train directory) - [X] [code analysis run](https://github.com/tenstorrent/tt-metal/actions/runs/12360844971) - [x] [T3K unit + frequent + model reg tests](https://github.com/tenstorrent/tt-metal/actions/runs/12360656141) - same breakage on main. - [X] New/Existing tests provide coverage for changes --- dependencies/CMakeLists.txt | 14 ++ tests/ttnn/unit_tests/gtests/CMakeLists.txt | 4 + .../gtests/tensor/test_distributed_tensor.cpp | 186 +++++++++++++++++ .../gtests/tensor/test_partition.cpp | 120 +++++++++++ .../gtests/tensor/test_vector_conversion.cpp | 119 +++++++++++ .../gtests/tensor/test_xtensor_conversion.cpp | 86 ++++++++ tt-train/sources/examples/sample_app/main.cpp | 14 +- .../sources/ttml/core/distributed_mapping.hpp | 52 +---- tt-train/sources/ttml/core/mesh_device.cpp | 4 +- .../sources/ttml/core/tt_tensor_utils.cpp | 79 +------ .../sources/ttml/core/tt_tensor_utils.hpp | 14 +- .../sources/ttml/core/ttnn_all_includes.hpp | 3 + tt-train/sources/ttml/core/xtensor_utils.cpp | 65 ------ tt-train/sources/ttml/core/xtensor_utils.hpp | 37 +--- tt-train/tests/3rd_party/xtensor_test.cpp | 70 +------ tt-train/tests/core/distributed_test.cpp | 103 +--------- ttnn/CMakeLists.txt | 5 + ttnn/cpp/ttnn/distributed/api.cpp | 5 +- ttnn/cpp/ttnn/distributed/api.hpp | 9 +- .../ttnn/distributed/distributed_tensor.cpp | 194 ++++++++++++++++++ .../ttnn/distributed/distributed_tensor.hpp | 58 ++++++ .../distributed/distributed_tensor_config.cpp | 3 +- .../distributed/distributed_tensor_config.hpp | 2 - ttnn/cpp/ttnn/tensor/CMakeLists.txt | 1 + ttnn/cpp/ttnn/tensor/tensor.cpp | 134 +++++++++++- ttnn/cpp/ttnn/tensor/tensor.hpp | 38 ++++ ttnn/cpp/ttnn/tensor/tensor_ops.cpp | 1 + .../ttnn/tensor/xtensor/conversion_utils.hpp | 66 ++++++ ttnn/cpp/ttnn/tensor/xtensor/partition.cpp | 174 ++++++++++++++++ ttnn/cpp/ttnn/tensor/xtensor/partition.hpp | 27 +++ .../tensor/xtensor}/xtensor_all_includes.hpp | 0 31 files changed, 1265 insertions(+), 422 deletions(-) create mode 100644 tests/ttnn/unit_tests/gtests/tensor/test_distributed_tensor.cpp create mode 100644 tests/ttnn/unit_tests/gtests/tensor/test_partition.cpp create mode 100644 tests/ttnn/unit_tests/gtests/tensor/test_vector_conversion.cpp create mode 100644 tests/ttnn/unit_tests/gtests/tensor/test_xtensor_conversion.cpp delete mode 100644 tt-train/sources/ttml/core/xtensor_utils.cpp create mode 100644 ttnn/cpp/ttnn/distributed/distributed_tensor.cpp create mode 100644 ttnn/cpp/ttnn/distributed/distributed_tensor.hpp create mode 100644 ttnn/cpp/ttnn/tensor/xtensor/conversion_utils.hpp create mode 100644 ttnn/cpp/ttnn/tensor/xtensor/partition.cpp create mode 100644 ttnn/cpp/ttnn/tensor/xtensor/partition.hpp rename {tt-train/sources/ttml/core => ttnn/cpp/ttnn/tensor/xtensor}/xtensor_all_includes.hpp (100%) diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index eb0bf54d180f..3064d846fe59 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -97,3 +97,17 @@ CPMAddPackage(NAME pybind11 GITHUB_REPOSITORY pybind/pybind11 GIT_TAG b8f28551cc ############################################################################################################################ CPMAddPackage(NAME json GITHUB_REPOSITORY nlohmann/json GIT_TAG v3.9.1) + +############################################################################################################################ +# xtensor : https://github.com/xtensor-stack/xtensor +############################################################################################################################ + +CPMAddPackage(NAME xtl GITHUB_REPOSITORY xtensor-stack/xtl GIT_TAG 0.7.7 OPTIONS "XTL_ENABLE_TESTS OFF") +CPMAddPackage(NAME xtensor GITHUB_REPOSITORY xtensor-stack/xtensor GIT_TAG 0.25.0 OPTIONS "XTENSOR_ENABLE_TESTS OFF") +CPMAddPackage( + NAME xtensor-blas + GITHUB_REPOSITORY xtensor-stack/xtensor-blas + GIT_TAG 0.21.0 + OPTIONS + "XTENSOR_ENABLE_TESTS OFF" +) diff --git a/tests/ttnn/unit_tests/gtests/CMakeLists.txt b/tests/ttnn/unit_tests/gtests/CMakeLists.txt index 7961ac445b22..573e382348b6 100644 --- a/tests/ttnn/unit_tests/gtests/CMakeLists.txt +++ b/tests/ttnn/unit_tests/gtests/CMakeLists.txt @@ -25,8 +25,12 @@ set(TTNN_TENSOR_UNIT_TESTS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/tensor/test_tensor_layout.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tensor/test_create_tensor_multi_device.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tensor/test_create_tensor_with_layout.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tensor/test_distributed_tensor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tensor/test_partition.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tensor/test_shape_base.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tensor/test_sharding_with_alignment.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tensor/test_vector_conversion.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tensor/test_xtensor_conversion.cpp ) add_executable(unit_tests_ttnn ${TTNN_UNIT_TESTS_SRC}) diff --git a/tests/ttnn/unit_tests/gtests/tensor/test_distributed_tensor.cpp b/tests/ttnn/unit_tests/gtests/tensor/test_distributed_tensor.cpp new file mode 100644 index 000000000000..a09069347515 --- /dev/null +++ b/tests/ttnn/unit_tests/gtests/tensor/test_distributed_tensor.cpp @@ -0,0 +1,186 @@ +// SPDX-FileCopyrightText: (c) 2024 Tenstorrent AI ULC +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "ttnn/distributed/api.hpp" +#include "ttnn/operations/functions.hpp" +#include "ttnn_test_fixtures.hpp" +#include +#include + +namespace ttnn::distributed::test { + +using ::testing::ElementsAre; + +using TensorDistributionTest = T3kMultiDeviceFixture; + +TensorSpec get_tensor_spec(const ttnn::SimpleShape& shape, DataType dtype) { + return TensorSpec(shape, TensorLayout(dtype, Layout::ROW_MAJOR, MemoryConfig{})); +} + +TEST_F(TensorDistributionTest, Replication) { + Tensor input_tensor = Tensor::from_vector( + std::vector{42.F, 13.F, -99.F}, get_tensor_spec(ttnn::SimpleShape{1, 1, 1, 3}, DataType::FLOAT32)); + + auto mapper = replicate_tensor_to_mesh_mapper(*mesh_device_); + Tensor replicated_tensor = distribute_tensor(input_tensor, *mesh_device_, *mapper); + + std::vector device_tensors = get_device_tensors(replicated_tensor); + EXPECT_EQ(device_tensors.size(), mesh_device_->num_devices()); + for (const auto& device_tensor : device_tensors) { + EXPECT_THAT(device_tensor.to_vector(), ElementsAre(42.F, 13.F, -99.F)); + } +} + +TEST_F(TensorDistributionTest, Shard1DInvalidDim) { + const int num_devices = mesh_device_->num_devices(); + Tensor input_tensor = Tensor::from_vector( + std::vector(num_devices, 0), + get_tensor_spec(ttnn::SimpleShape{1, 1, 1, num_devices}, DataType::FLOAT32)); + + EXPECT_ANY_THROW({ + auto mapper = shard_tensor_to_mesh_mapper(*mesh_device_, -1); + Tensor sharded_tensor = distribute_tensor(input_tensor, *mesh_device_, *mapper); + }); + + EXPECT_ANY_THROW({ + auto mapper = shard_tensor_to_mesh_mapper(*mesh_device_, 4); + Tensor sharded_tensor = distribute_tensor(input_tensor, *mesh_device_, *mapper); + }); +} + +TEST_F(TensorDistributionTest, Shard1DTooFewShards) { + const int num_devices = mesh_device_->num_devices(); + ASSERT_LT(3, num_devices); + Tensor input_tensor = Tensor::from_vector( + std::vector{42.F, 13.F, -99.F}, get_tensor_spec(ttnn::SimpleShape{1, 1, 1, 3}, DataType::FLOAT32)); + + EXPECT_ANY_THROW({ + auto mapper = shard_tensor_to_mesh_mapper(*mesh_device_, 3); + Tensor sharded_tensor = distribute_tensor(input_tensor, *mesh_device_, *mapper); + }); +} + +TEST_F(TensorDistributionTest, Shard1D) { + const int num_devices = mesh_device_->num_devices(); + std::vector test_data; + for (int i = 0; i < num_devices; i++) { + test_data.insert(test_data.end(), {i * 1.F, i * 2.F, i * 3.F}); + } + Tensor input_tensor = + Tensor::from_vector(test_data, get_tensor_spec(ttnn::SimpleShape{1, num_devices, 3, 1}, DataType::FLOAT32)); + + auto mapper = shard_tensor_to_mesh_mapper(*mesh_device_, 1); + Tensor sharded_tensor = distribute_tensor(input_tensor, *mesh_device_, *mapper); + + std::vector device_tensors = get_device_tensors(sharded_tensor); + EXPECT_EQ(device_tensors.size(), mesh_device_->num_devices()); + for (int i = 0; i < device_tensors.size(); i++) { + EXPECT_THAT(device_tensors[i].to_vector(), ElementsAre(i * 1.F, i * 2.F, i * 3.F)); + } + + auto composer = concat_mesh_to_tensor_composer(/*dim=*/0); + Tensor concatenated_tensor = aggregate_tensor(sharded_tensor, *composer); + + Tensor expected_tensor = + Tensor::from_vector(test_data, get_tensor_spec(ttnn::SimpleShape{num_devices, 1, 3, 1}, DataType::FLOAT32)); + EXPECT_TRUE(ttnn::allclose(concatenated_tensor, expected_tensor)); +} + +TEST_F(TensorDistributionTest, Shard2DInvalidMeshShape) { + const auto [num_rows, num_cols] = mesh_device_->shape(); + ASSERT_EQ(num_rows, 2); + ASSERT_EQ(num_cols, 4); + + EXPECT_ANY_THROW( + shard_tensor_to_2d_mesh_mapper(*mesh_device_, MeshShape{3, 1}, Shard2dConfig{.row_dim = 1, .col_dim = 2})); + + EXPECT_ANY_THROW( + shard_tensor_to_2d_mesh_mapper(*mesh_device_, MeshShape{2, 5}, Shard2dConfig{.row_dim = 1, .col_dim = 2})); +} + +TEST_F(TensorDistributionTest, Shard2DInvalidShardConfig) { + EXPECT_ANY_THROW(shard_tensor_to_2d_mesh_mapper(*mesh_device_, MeshShape{2, 4}, Shard2dConfig{})); +} + +TEST_F(TensorDistributionTest, Concat2DInvalidConfig) { + EXPECT_ANY_THROW(concat_2d_mesh_to_tensor_composer(*mesh_device_, Concat2dConfig{.row_dim = 2, .col_dim = 2})); +} + +TEST_F(TensorDistributionTest, Shard2DReplicateDim) { + const auto [num_rows, num_cols] = mesh_device_->shape(); + ASSERT_EQ(num_rows, 2); + ASSERT_EQ(num_cols, 4); + const int num_devices = num_rows * num_cols; + + std::vector test_data = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}; + Tensor input_tensor = + Tensor::from_vector(test_data, get_tensor_spec(ttnn::SimpleShape{1, num_rows, num_cols, 1}, DataType::FLOAT32)); + input_tensor.print(); + + auto mapper = shard_tensor_to_2d_mesh_mapper( + *mesh_device_, + MeshShape{num_rows, num_cols}, + Shard2dConfig{ + .row_dim = 1, + }); + Tensor sharded_tensor = distribute_tensor(input_tensor, *mesh_device_, *mapper); + sharded_tensor.print(); + + std::vector device_tensors = get_device_tensors(sharded_tensor); + EXPECT_EQ(device_tensors.size(), mesh_device_->num_devices()); + + int i = 0; + for (; i < 4; i++) { + EXPECT_THAT(device_tensors[i].to_vector(), ElementsAre(0.0, 1.0, 2.0, 3.0)); + } + for (; i < device_tensors.size(); i++) { + EXPECT_THAT(device_tensors[i].to_vector(), ElementsAre(4.0, 5.0, 6.0, 7.0)); + } +} + +TEST_F(TensorDistributionTest, Shard2D) { + const auto [num_rows, num_cols] = mesh_device_->shape(); + ASSERT_EQ(num_rows, 2); + ASSERT_EQ(num_cols, 4); + const int num_devices = num_rows * num_cols; + + std::vector test_data; + for (int i = 0; i < num_devices; i++) { + test_data.insert(test_data.end(), {i * 1.F, i * 2.F, i * 3.F}); + } + Tensor input_tensor = + Tensor::from_vector(test_data, get_tensor_spec(ttnn::SimpleShape{1, num_rows, num_cols, 3}, DataType::FLOAT32)); + + auto mapper = shard_tensor_to_2d_mesh_mapper( + *mesh_device_, + MeshShape{num_rows, num_cols}, + Shard2dConfig{ + .row_dim = 1, + .col_dim = 2, + }); + Tensor sharded_tensor = distribute_tensor(input_tensor, *mesh_device_, *mapper); + + std::vector device_tensors = get_device_tensors(sharded_tensor); + EXPECT_EQ(device_tensors.size(), mesh_device_->num_devices()); + for (int i = 0; i < device_tensors.size(); i++) { + EXPECT_THAT(device_tensors[i].to_vector(), ElementsAre(i * 1.F, i * 2.F, i * 3.F)); + } + + auto composer = concat_2d_mesh_to_tensor_composer( + *mesh_device_, + Concat2dConfig{ + .row_dim = 0, + .col_dim = 2, + }); + Tensor concatenated_tensor = aggregate_tensor(sharded_tensor, *composer); + + Tensor expected_tensor = + Tensor::from_vector(test_data, get_tensor_spec(ttnn::SimpleShape{num_rows, 1, num_cols, 3}, DataType::FLOAT32)); + EXPECT_TRUE(ttnn::allclose(concatenated_tensor, expected_tensor)); +} + +} // namespace ttnn::distributed::test diff --git a/tests/ttnn/unit_tests/gtests/tensor/test_partition.cpp b/tests/ttnn/unit_tests/gtests/tensor/test_partition.cpp new file mode 100644 index 000000000000..4b0062581c0b --- /dev/null +++ b/tests/ttnn/unit_tests/gtests/tensor/test_partition.cpp @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: (c) 2024 Tenstorrent AI ULC +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "ttnn/tensor/tensor.hpp" +#include "ttnn/tensor/xtensor/conversion_utils.hpp" +#include "ttnn/tensor/xtensor/partition.hpp" +#include "ttnn/tensor/xtensor/xtensor_all_includes.hpp" + +namespace ttnn { +namespace { + +using ::testing::SizeIs; +using ::tt::tt_metal::Tensor; +using ::ttnn::experimental::xtensor::chunk; +using ::ttnn::experimental::xtensor::concat; + +TEST(PartitionTest, ChunkBasicNonDivisible3) { + // Create a 1D tensor: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + xt::xarray tensor = xt::arange(10); + + // Chunk into 3 parts along dimension 0 + auto chunks = chunk(tensor, 3, 0); + + ASSERT_THAT(chunks, SizeIs(3)); + EXPECT_EQ(chunks[0].shape()[0], 4u); // first chunk size 4 + EXPECT_EQ(chunks[1].shape()[0], 4u); // next chunk size 4 + EXPECT_EQ(chunks[2].shape()[0], 2u); // last chunk size 2 +} + +TEST(PartitionTest, ChunkBasicLessChunksThanProvided) { + // Create a 1D tensor: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12] + xt::xarray tensor = xt::arange(13); + + // Chunk into 6 parts along dimension 0 + auto chunks = chunk(tensor, 6, 0); + + ASSERT_THAT(chunks, SizeIs(5)); + EXPECT_EQ(chunks[0].shape()[0], 3u); // first chunk size 3 + EXPECT_EQ(chunks[1].shape()[0], 3u); // next chunk size 3 + EXPECT_EQ(chunks[2].shape()[0], 3u); // next chunk size 3 + EXPECT_EQ(chunks[3].shape()[0], 3u); // next chunk size 3 + EXPECT_EQ(chunks[4].shape()[0], 1u); // last chunk size 1 +} + +TEST(PartitionTest, DefaultAxis) { + xt::xarray a = {{1.0, 2.0}, {3.0, 4.0}}; + xt::xarray b = {{5.0, 6.0}, {7.0, 8.0}}; + std::vector> input = {a, b}; + + xt::xarray result = concat(input); // axis=0 by default + xt::xarray expected = {{1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0}, {7.0, 8.0}}; + + xt::allclose(result, expected); +} + +TEST(PartitionTest, AxisOne) { + xt::xarray x = {{1, 2, 3}, {4, 5, 6}}; + xt::xarray y = {{7, 8}, {9, 10}}; + std::vector> input = {x, y}; + + xt::xarray result = concat(input, 1); + xt::xarray expected = {{1, 2, 3, 7, 8}, {4, 5, 6, 9, 10}}; + + xt::allclose(result, expected); +} + +TEST(PartitionTest, MultipleArraysAxis0) { + xt::xarray a = {1.0f, 2.0f}; + xt::xarray b = {3.0f, 4.0f}; + xt::xarray c = {5.0f, 6.0f}; + std::vector> input = {a, b, c}; + + xt::xarray result = concat(input, 0); + xt::xarray expected = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + + xt::allclose(result, expected); +} + +TEST(PartitionTest, EmptyArray) { + xt::xarray a = {{1, 2}, {3, 4}}; + xt::xarray b; // Empty + std::vector> input = {a, b}; + + EXPECT_ANY_THROW({ xt::xarray result = concat(input, 0); }); +} + +TEST(PartitionTest, HigherDimensions) { + xt::xarray arr1 = xt::arange(1, 9); // 1 to 8 + arr1.reshape({2, 2, 2}); + xt::xarray arr2 = xt::arange(9, 17); // 9 to 16 + arr2.reshape({2, 2, 2}); + + std::vector> input = {arr1, arr2}; + xt::xarray result = concat(input, 0); + + // Expected: shape (4,2,2) with arr1 stacked over arr2 along axis 0 + xt::xarray expected = xt::concatenate(xt::xtuple(arr1, arr2), 0); + + xt::allclose(result, expected); +} + +TEST(PartitionTest, HigherAxis) { + xt::xarray arr1 = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}; + xt::xarray arr2 = {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}; + // Both have shape (2,2,2) + + std::vector> input = {arr1, arr2}; + xt::xarray result = concat(input, 2); + // Expected shape: (2,2,4) + xt::xarray expected = {{{1, 2, 9, 10}, {3, 4, 11, 12}}, {{5, 6, 13, 14}, {7, 8, 15, 16}}}; + + xt::allclose(result, expected); +} + +} // namespace +} // namespace ttnn diff --git a/tests/ttnn/unit_tests/gtests/tensor/test_vector_conversion.cpp b/tests/ttnn/unit_tests/gtests/tensor/test_vector_conversion.cpp new file mode 100644 index 000000000000..8dc25e1abf44 --- /dev/null +++ b/tests/ttnn/unit_tests/gtests/tensor/test_vector_conversion.cpp @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: (c) 2024 Tenstorrent AI ULC +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +#include "ttnn/tensor/tensor.hpp" +#include "ttnn/tensor/xtensor/conversion_utils.hpp" +#include "ttnn/tensor/xtensor/xtensor_all_includes.hpp" + +namespace ttnn { +namespace { + +using ::testing::Eq; +using ::testing::Pointwise; + +const std::vector& get_shapes_for_test() { + static auto* shapes = new std::vector{ + ttnn::SimpleShape{1}, + ttnn::SimpleShape{1, 1, 1, 1}, + ttnn::SimpleShape{1, 1, 1, 10}, + ttnn::SimpleShape{1, 32, 32, 16}, + ttnn::SimpleShape{1, 40, 3, 128}, + ttnn::SimpleShape{2, 2}, + ttnn::SimpleShape{1, 1, 1, 1, 10}, + }; + return *shapes; +} + +TensorSpec get_tensor_spec(const ttnn::SimpleShape& shape, DataType dtype, Layout layout = Layout::ROW_MAJOR) { + return TensorSpec(shape, TensorLayout(dtype, layout, MemoryConfig{})); +} + +template +std::vector arange(int64_t start, int64_t end, int64_t step) { + std::vector result; + for (int el : xt::arange(start, end, step)) { + if constexpr (std::is_same_v) { + result.push_back(T(static_cast(el))); + } else { + result.push_back(static_cast(el)); + } + } + return result; +} + +template +class VectorConversionTest : public ::testing::Test {}; + +using TestTypes = ::testing::Types; +TYPED_TEST_SUITE(VectorConversionTest, TestTypes); + +TYPED_TEST(VectorConversionTest, Roundtrip) { + for (const auto& shape : get_shapes_for_test()) { + auto input = arange(0, static_cast(shape.volume()), 1); + auto output = Tensor::from_vector(input, get_tensor_spec(shape, convert_to_data_type())) + .template to_vector(); + EXPECT_THAT(output, Pointwise(Eq(), input)) << "for shape: " << shape; + } +} + +TYPED_TEST(VectorConversionTest, InvalidSize) { + ttnn::SimpleShape shape{32, 32}; + auto input = arange(0, 42, 1); + + ASSERT_NE(input.size(), shape.volume()); + EXPECT_ANY_THROW(Tensor::from_vector(input, get_tensor_spec(shape, convert_to_data_type()))); +} + +TYPED_TEST(VectorConversionTest, RoundtripTilezedLayout) { + ttnn::SimpleShape shape{128, 128}; + + auto input = arange(0, shape.volume(), 1); + // TODO: Support this. + EXPECT_ANY_THROW( + Tensor::from_vector(input, get_tensor_spec(shape, convert_to_data_type(), Layout::TILE))); + + auto output = Tensor::from_vector(input, get_tensor_spec(shape, convert_to_data_type())) + .to(Layout::TILE) + .template to_vector(); + EXPECT_THAT(output, Pointwise(Eq(), input)); +} + +TYPED_TEST(VectorConversionTest, InvalidDtype) { + ttnn::SimpleShape shape{32, 32}; + auto input = arange(0, 42, 1); + + ASSERT_NE(input.size(), shape.volume()); + EXPECT_ANY_THROW(Tensor::from_vector( + input, + get_tensor_spec( + shape, + // Use INT32 for verification, except for when the actual type is int32_t. + (std::is_same_v ? DataType::FLOAT32 : DataType::INT32)))); +} + +TEST(FloatVectorConversionTest, RoundtripBfloat16Representation) { + for (const auto& shape : get_shapes_for_test()) { + auto input_bf16 = arange(0, static_cast(shape.volume()), 1); + std::vector input_ft; + input_ft.reserve(input_bf16.size()); + std::transform(input_bf16.begin(), input_bf16.end(), std::back_inserter(input_ft), [](bfloat16 bf) { + return bf.to_float(); + }); + + auto output_bf16 = + Tensor::from_vector(input_ft, get_tensor_spec(shape, DataType::BFLOAT16)).to_vector(); + EXPECT_THAT(output_bf16, Pointwise(Eq(), input_bf16)) << "for shape: " << shape; + + auto output_ft = Tensor::from_vector(input_bf16, get_tensor_spec(shape, DataType::BFLOAT16)).to_vector(); + EXPECT_THAT(output_ft, Pointwise(Eq(), input_ft)) << "for shape: " << shape; + } +} + +} // namespace +} // namespace ttnn diff --git a/tests/ttnn/unit_tests/gtests/tensor/test_xtensor_conversion.cpp b/tests/ttnn/unit_tests/gtests/tensor/test_xtensor_conversion.cpp new file mode 100644 index 000000000000..d84eda7093b1 --- /dev/null +++ b/tests/ttnn/unit_tests/gtests/tensor/test_xtensor_conversion.cpp @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: (c) 2024 Tenstorrent AI ULC +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "ttnn/tensor/tensor.hpp" +#include "ttnn/tensor/types.hpp" +#include "ttnn/tensor/xtensor/conversion_utils.hpp" +#include "ttnn/tensor/xtensor/xtensor_all_includes.hpp" + +namespace ttnn { +namespace { + +using ::testing::ElementsAre; +using ::testing::Eq; +using ::ttnn::experimental::xtensor::from_xtensor; +using ::ttnn::experimental::xtensor::get_shape_from_xarray; +using ::ttnn::experimental::xtensor::span_to_xtensor_view; +using ::ttnn::experimental::xtensor::to_xtensor; +using ::ttnn::experimental::xtensor::xtensor_to_span; + +TensorSpec get_tensor_spec(const ttnn::SimpleShape& shape) { + return TensorSpec(shape, TensorLayout(DataType::FLOAT32, Layout::ROW_MAJOR, MemoryConfig{})); +} + +TEST(XtensorConversionTest, SpanToXtensor) { + std::vector data = {1, 2, 3, 4, 5, 6}; + tt::stl::Span data_span(data.data(), data.size()); + ttnn::SimpleShape shape({2, 3}); + + auto result = span_to_xtensor_view(data_span, shape); + + // Check shape + EXPECT_THAT(result.shape(), ElementsAre(2, 3)); + + // Check data + int expected_val = 1; + for (size_t i = 0; i < result.shape()[0]; ++i) { + for (size_t j = 0; j < result.shape()[1]; ++j) { + EXPECT_EQ(result(i, j), expected_val++); + } + } +} + +TEST(XtensorConversionTest, XtensorToSpan) { + xt::xarray arr = {{1.0f, 2.0f}, {3.0f, 4.0f}}; + EXPECT_THAT(xtensor_to_span(arr), ElementsAre(1.0f, 2.0f, 3.0f, 4.0f)); +} + +TEST(XtensorConversionTest, GetShape) { + EXPECT_THAT( + get_shape_from_xarray(xt::xarray::from_shape({2, 3, 4, 5, 6})), Eq(ttnn::SimpleShape{2, 3, 4, 5, 6})); + EXPECT_THAT(get_shape_from_xarray(xt::xarray::from_shape({7})), Eq(ttnn::SimpleShape{7})); +} + +TEST(XtensorConversionTest, FromXtensorInvalidShape) { + xt::xarray arr = {{1.0f, 2.0f}, {3.0f, 4.0f}}; + EXPECT_ANY_THROW(from_xtensor(arr, get_tensor_spec(ttnn::SimpleShape{3, 3}))); +} + +TEST(XtensorConversionTest, Roundtrip) { + const std::vector shapes{ + ttnn::SimpleShape{1}, + ttnn::SimpleShape{1, 1, 1, 1}, + ttnn::SimpleShape{1, 1, 1, 10}, + ttnn::SimpleShape{1, 32, 32, 16}, + ttnn::SimpleShape{1, 40, 3, 128}, + ttnn::SimpleShape{2, 2}, + ttnn::SimpleShape{1, 1, 1, 1, 10}, + }; + + for (const auto& shape : shapes) { + const auto tensor_spec = get_tensor_spec(shape); + xt::xarray input = xt::arange(shape.volume()); + xt::dynamic_shape new_shape(shape.cbegin(), shape.cend()); + input.reshape(new_shape); + + auto output = to_xtensor(from_xtensor(input, tensor_spec)); + EXPECT_TRUE(xt::allclose(input, output)); + } +} + +} // namespace +} // namespace ttnn diff --git a/tt-train/sources/examples/sample_app/main.cpp b/tt-train/sources/examples/sample_app/main.cpp index 36231a6fc60c..8998e49bb732 100644 --- a/tt-train/sources/examples/sample_app/main.cpp +++ b/tt-train/sources/examples/sample_app/main.cpp @@ -24,12 +24,14 @@ void print_tensor(const tt::tt_metal::Tensor& tensor) { tt::tt_metal::memcpy(device->command_queue(), data.data(), tensor); // print the data - for (size_t i = 0; i < shape[0]; i++) { - for (size_t j = 0; j < shape[1]; j++) { - for (size_t k = 0; k < shape[2]; k++) { - for (size_t l = 0; l < shape[3]; l++) { - std::cout << data[i * shape[1] * shape[2] * shape[3] + j * shape[2] * shape[3] + k * shape[3] + l] - .to_float() + for (size_t dim0 = 0; dim0 < shape[0]; dim0++) { + for (size_t dim1 = 0; dim1 < shape[1]; dim1++) { + for (size_t dim2 = 0; dim2 < shape[2]; dim2++) { + for (size_t dim3 = 0; dim3 < shape[3]; dim3++) { + std::cout << data + [dim0 * shape[1] * shape[2] * shape[3] + dim1 * shape[2] * shape[3] + + dim2 * shape[3] + dim3] + .to_float() << " "; } std::cout << std::endl; diff --git a/tt-train/sources/ttml/core/distributed_mapping.hpp b/tt-train/sources/ttml/core/distributed_mapping.hpp index d40644486da7..1ba3a9e5c02b 100644 --- a/tt-train/sources/ttml/core/distributed_mapping.hpp +++ b/tt-train/sources/ttml/core/distributed_mapping.hpp @@ -4,56 +4,16 @@ #pragma once #include -#include #include #include "core/xtensor_utils.hpp" +#include "ttnn/tensor/xtensor/partition.hpp" namespace ttml::core { + template std::vector> chunk(const xt::xarray& tensor, int num_chunks, int dim) { - if (num_chunks <= 0) { - throw std::invalid_argument("num_chunks must be > 0"); - } - if (dim < 0 || static_cast(dim) >= tensor.dimension()) { - throw std::invalid_argument("invalid dimension index"); - } - - int size_along_dim = static_cast(tensor.shape()[dim]); - if (num_chunks > size_along_dim) { - throw std::invalid_argument("num_chunks cannot exceed the size of the tensor along the given dimension."); - } - - if (num_chunks == 1) { - return {tensor}; - } - - int chunk_size = (size_along_dim + num_chunks - 1) / num_chunks; - int remaining_size = size_along_dim; - - std::vector> chunks; - chunks.reserve(static_cast(num_chunks)); - - int start = 0; - int end = 0; - for (int i = 0; i < num_chunks && end < size_along_dim; ++i) { - int current_chunk_size = std::min(chunk_size, remaining_size); - remaining_size -= current_chunk_size; - end = start + current_chunk_size; - - // Build indices for slicing - xt::xstrided_slice_vector indices(tensor.dimension(), xt::all()); - indices[dim] = xt::range(start, end); - - auto chunk_view = xt::strided_view(tensor, indices); - - // Construct xarray from the view - // This forces a copy of that slice into a new xarray - chunks.push_back(xt::xarray(chunk_view)); - start = end; - } - - return chunks; + return ttnn::experimental::xtensor::chunk(tensor, num_chunks, dim); } template @@ -212,11 +172,11 @@ class ConcatMesh2dToTensor : public MeshToXTensor, T> { auto row_end = row_start + cols; std::vector> row_tensors(row_start, row_end); - auto concatenated_row = core::concatenate(row_tensors, col_dim); + auto concatenated_row = core::concat(row_tensors, col_dim); row_concatenated.push_back(std::move(concatenated_row)); } - auto result = core::concatenate(row_concatenated, row_dim); + auto result = core::concat(row_concatenated, row_dim); return {result}; } @@ -256,7 +216,7 @@ class ConcatMeshToXTensor : public MeshToXTensor, T> { } std::vector> compose_impl(const std::vector>& tensors) const { - return {core::concatenate(tensors, m_concat_dim)}; + return {core::concat(tensors, m_concat_dim)}; } private: diff --git a/tt-train/sources/ttml/core/mesh_device.cpp b/tt-train/sources/ttml/core/mesh_device.cpp index 33f3d0265561..b6fff694bf09 100644 --- a/tt-train/sources/ttml/core/mesh_device.cpp +++ b/tt-train/sources/ttml/core/mesh_device.cpp @@ -7,7 +7,7 @@ namespace ttml::core { MeshDevice::MeshDevice(tt::tt_metal::distributed::MeshShape shape) : - m_mesh_device(ttnn::distributed::api::open_mesh_device( + m_mesh_device(ttnn::distributed::open_mesh_device( shape, DEFAULT_L1_SMALL_SIZE, DEFAULT_TRACE_REGION_SIZE, @@ -24,7 +24,7 @@ MeshDevice::MeshDevice(tt::tt_metal::distributed::MeshShape shape) : MeshDevice::~MeshDevice() { assert(m_mesh_device); - ttnn::distributed::api::close_mesh_device(m_mesh_device); + ttnn::distributed::close_mesh_device(m_mesh_device); } } // namespace ttml::core diff --git a/tt-train/sources/ttml/core/tt_tensor_utils.cpp b/tt-train/sources/ttml/core/tt_tensor_utils.cpp index 00e2c0761c76..8ca99db84531 100644 --- a/tt-train/sources/ttml/core/tt_tensor_utils.cpp +++ b/tt-train/sources/ttml/core/tt_tensor_utils.cpp @@ -32,7 +32,7 @@ T get_median(std::vector& vec) { template void print_tensor_stats_(const tt::tt_metal::Tensor& tensor, const std::string& name) { auto tensor_shape = tensor.get_shape(); - auto tensor_vec = ttml::core::to_vector(tensor); + auto tensor_vec = tensor.to_vector(); auto median = get_median(tensor_vec); auto mean = std::accumulate(tensor_vec.begin(), tensor_vec.end(), 0.F) / static_cast(tensor_vec.size()); @@ -90,50 +90,6 @@ tt::tt_metal::Tensor ttml_create_owned_tensor( return {std::move(storage), shape, data_type, layout}; } -// TODO: optimize precomputing multipliers -template -std::vector untile_tensor_to_vec(const tt::tt_metal::Tensor& cpu_tensor) { - auto tiled_buffer = tt::tt_metal::host_buffer::get_as(cpu_tensor); - auto untiled_shape = cpu_tensor.get_logical_shape(); - auto tiled_shape = cpu_tensor.get_padded_shape(); - - // Calculate total size of the untiled tensor - size_t total_size = untiled_shape.volume(); - - std::vector untiled_data(total_size); - - auto compute_flat_index = [](const std::vector& indices, ttnn::SimpleShape& shape) -> uint32_t { - uint32_t flat_index = 0; - uint32_t multiplier = 1; - for (int i = (int)indices.size() - 1; i >= 0; --i) { - flat_index += indices[i] * multiplier; - multiplier *= shape[i]; - } - return flat_index; - }; - - std::vector indices(tiled_shape.rank(), 0); - - for (size_t idx = 0; idx < total_size; ++idx) { - uint32_t untiled_index = compute_flat_index(indices, untiled_shape); - uint32_t tiled_index = compute_flat_index(indices, tiled_shape); - if constexpr (std::is_same_v) { - untiled_data[untiled_index] = tiled_buffer[tiled_index].to_float(); - } else { - untiled_data[untiled_index] = tiled_buffer[tiled_index]; - } - - for (int dim = (int)tiled_shape.rank() - 1; dim >= 0; --dim) { - if (++indices[dim] < untiled_shape[dim]) { - break; - } - indices[dim] = 0; - } - } - - return untiled_data; -} - } // namespace namespace ttml::core { @@ -196,12 +152,12 @@ template if (buffers[i].shape() != first_shape) { throw std::runtime_error(fmt::format( "Cannot create a host buffer from xtensors with different shapes: {} vs {}!", - get_shape_4d(buffers[0]), - get_shape_4d(buffers[i]))); + ttnn::experimental::xtensor::get_shape_from_xarray(buffers[0]), + ttnn::experimental::xtensor::get_shape_from_xarray(buffers[i]))); } } for (const auto& buffer : buffers) { - auto shape = create_shape(get_shape_4d(buffer)); + auto shape = ttnn::experimental::xtensor::get_shape_from_xarray(buffer); if constexpr (std::is_same_v) { auto owned_buffer = @@ -271,17 +227,6 @@ tt::tt_metal::Tensor from_vector( return ttnn::typecast(tensor, DataType::FLOAT32); } -template <> -std::vector to_vector(const tt::tt_metal::Tensor& tensor) { - auto cpu_tensor = tensor.cpu(); - cpu_tensor = cpu_tensor.to(Layout::ROW_MAJOR); - if (cpu_tensor.get_dtype() == DataType::BFLOAT16) { - return untile_tensor_to_vec(cpu_tensor); - } - assert(cpu_tensor.get_dtype() == DataType::FLOAT32); - return untile_tensor_to_vec(cpu_tensor); -} - /* From vector uint32 doesn't support tilize_with_zero_padding on device */ @@ -342,22 +287,6 @@ tt::tt_metal::Tensor from_vector( return output; } -template <> -std::vector to_vector(const tt::tt_metal::Tensor& tensor) { - auto cpu_tensor = tensor.cpu(); - cpu_tensor = cpu_tensor.to(Layout::ROW_MAJOR); - - return untile_tensor_to_vec(cpu_tensor); -} - -template <> -std::vector to_vector(const tt::tt_metal::Tensor& tensor) { - auto cpu_tensor = tensor.cpu(); - cpu_tensor = cpu_tensor.to(Layout::ROW_MAJOR); - - return untile_tensor_to_vec(cpu_tensor); -} - bool is_tensor_initialized(const tt::tt_metal::Tensor& tensor) { return tensor.tensor_attributes != nullptr; } diff --git a/tt-train/sources/ttml/core/tt_tensor_utils.hpp b/tt-train/sources/ttml/core/tt_tensor_utils.hpp index 448b77163842..89ab8e01b5b0 100644 --- a/tt-train/sources/ttml/core/tt_tensor_utils.hpp +++ b/tt-train/sources/ttml/core/tt_tensor_utils.hpp @@ -38,7 +38,9 @@ template const std::vector>& buffers, const std::unordered_map& config); template -[[nodiscard]] std::vector to_vector(const tt::tt_metal::Tensor& tensor); +[[nodiscard]] std::vector to_vector(const tt::tt_metal::Tensor& tensor) { + return tensor.to_vector(); +} [[nodiscard]] bool is_tensor_initialized(const tt::tt_metal::Tensor& tensor); @@ -47,24 +49,24 @@ template template [[nodiscard]] tt::tt_metal::Tensor from_xtensor( const xt::xarray& buffer, ttnn::distributed::MeshDevice* device, Layout layout = Layout::TILE) { - auto shape = create_shape(get_shape_4d(buffer)); + auto shape = ttnn::experimental::xtensor::get_shape_from_xarray(buffer); auto buffer_view = xtensor_to_span(buffer); return from_vector(std::vector(buffer_view.begin(), buffer_view.end()), shape, device, layout); } template [[nodiscard]] xt::xarray to_xtensor(const tt::tt_metal::Tensor& tensor) { - auto vec = to_vector(tensor); - auto shape = tensor.get_shape().logical_shape(); + auto vec = tensor.to_vector(); + const auto& shape = tensor.get_shape().logical_shape(); std::vector shape_vec(shape.cbegin(), shape.cend()); - return xt::adapt(to_vector(tensor), shape_vec); + return xt::adapt(std::move(vec), shape_vec); } template auto to_xtensor(const tt::tt_metal::Tensor& tensor, const MeshToXTensorVariant& composer) { auto cpu_tensor = tensor.cpu(); cpu_tensor = cpu_tensor.to(Layout::ROW_MAJOR); - auto cpu_tensors = ttnn::distributed::api::get_device_tensors(cpu_tensor); + auto cpu_tensors = ttnn::distributed::get_device_tensors(cpu_tensor); std::vector> res; res.reserve(cpu_tensors.size()); for (const auto& shard : cpu_tensors) { diff --git a/tt-train/sources/ttml/core/ttnn_all_includes.hpp b/tt-train/sources/ttml/core/ttnn_all_includes.hpp index d41cf6eea2fe..f7f5b8cb282e 100644 --- a/tt-train/sources/ttml/core/ttnn_all_includes.hpp +++ b/tt-train/sources/ttml/core/ttnn_all_includes.hpp @@ -59,6 +59,9 @@ #include // NOLINT #include // NOLINT #include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT #include // NOLINT #pragma GCC diagnostic pop diff --git a/tt-train/sources/ttml/core/xtensor_utils.cpp b/tt-train/sources/ttml/core/xtensor_utils.cpp deleted file mode 100644 index 96c0d0a7c1ff..000000000000 --- a/tt-train/sources/ttml/core/xtensor_utils.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-FileCopyrightText: (c) 2024 Tenstorrent AI ULC -// -// SPDX-License-Identifier: Apache-2.0 - -#include "xtensor_utils.hpp" - -namespace ttml::core { -namespace detail { -template -auto vector_to_tuple_helper(const std::vector& v, std::index_sequence) { - return std::make_tuple(v[Indices]...); -} - -template -auto vector_to_tuple(const std::vector& buffer) { - assert(buffer.size() >= N); - return vector_to_tuple_helper(buffer, std::make_index_sequence()); -} - -template -xt::xarray concat_helper(const std::vector>& v, size_t axis = 0) { - constexpr int FIXED_N = N < 2 ? 2 : N; - if (N < 2) { - throw std::runtime_error("Tuple size in concatenate must be greater than 1"); - } - auto tuple = detail::vector_to_tuple(v); - return xt::concatenate(std::move(tuple), axis); -} - -template -consteval auto create_array_impl(std::index_sequence) { - return std::array (*)(const std::vector>& v, size_t axis), sizeof...(I)>{ - concat_helper...}; -} - -template -consteval auto create_array() { - return create_array_impl(std::make_index_sequence()); -} - -} // namespace detail - -template -xt::xarray concatenate(const std::vector>& v, size_t axis) { - constexpr size_t MAX_TUPLE_SIZE = 64; - - if (v.empty()) { - return {}; - } - if (v.size() == 1) { - return v.front(); - } - if (v.size() > MAX_TUPLE_SIZE) { - throw std::runtime_error( - fmt::format("Number of tensors to concatenate exceeds the maximum supported size {}", MAX_TUPLE_SIZE)); - } - constexpr auto table = detail::create_array(); - return (*table[v.size()])(v, axis); -} - -template xt::xarray concatenate(const std::vector>& v, size_t axis); -template xt::xarray concatenate(const std::vector>& v, size_t axis); -template xt::xarray concatenate(const std::vector>& v, size_t axis); -template xt::xarray concatenate(const std::vector>& v, size_t axis); -} // namespace ttml::core diff --git a/tt-train/sources/ttml/core/xtensor_utils.hpp b/tt-train/sources/ttml/core/xtensor_utils.hpp index 046ba2fe4027..074cc4a58519 100644 --- a/tt-train/sources/ttml/core/xtensor_utils.hpp +++ b/tt-train/sources/ttml/core/xtensor_utils.hpp @@ -5,9 +5,10 @@ #pragma once #include -#include #include #include +#include +#include // TODO: decide if we want to use xarray everwhere or xtensor is ok /* @@ -20,40 +21,16 @@ set to N at compile time. xtensor_fixed : tensor whose shape namespace ttml::core { template xt::xarray span_to_xtensor_view(std::span vec, const ttnn::SimpleShape& shape) { - std::vector shape_vec(shape.cbegin(), shape.cend()); - return xt::adapt(vec.data(), vec.size(), xt::no_ownership(), shape_vec); + return ttnn::experimental::xtensor::span_to_xtensor_view(vec, shape); } template auto xtensor_to_span(const xt::xarray& xtensor) { - auto adaptor = xt::adapt(xtensor.data(), xtensor.size(), xt::no_ownership()); - return std::span(adaptor.data(), adaptor.size()); -} - -// TODO: decide if we want to keep this function with E or use the xtensor type directly -template -std::array get_shape_4d(const E& expr) { - const int max_dims = 4; - // TODO: Ensure that E is an xtensor expression - - // Retrieve the shape of the tensor - auto& expr_shape = expr.shape(); - std::array shape4d = {1, 1, 1, 1}; - - size_t dims = expr_shape.size(); - - if (dims > max_dims) { - throw std::runtime_error(fmt::format("Number of dimensions {} greater than max_shape {}", dims, max_dims)); - } - - // Copy the dimensions into the shape array - for (size_t i = 0; i < dims; ++i) { - shape4d[i + max_dims - dims] = static_cast(expr_shape[i]); - } - - return shape4d; + return ttnn::experimental::xtensor::xtensor_to_span(xtensor); } template -xt::xarray concatenate(const std::vector>& v, size_t axis = 0); +xt::xarray concat(const std::vector>& v, size_t axis = 0) { + return ttnn::experimental::xtensor::concat(v, axis); +} } // namespace ttml::core diff --git a/tt-train/tests/3rd_party/xtensor_test.cpp b/tt-train/tests/3rd_party/xtensor_test.cpp index 03f4faaf1e0f..0a7349b3a56b 100644 --- a/tt-train/tests/3rd_party/xtensor_test.cpp +++ b/tt-train/tests/3rd_party/xtensor_test.cpp @@ -4,7 +4,7 @@ #include -#include +#include #include "core/xtensor_utils.hpp" @@ -27,71 +27,3 @@ TEST(XTensorTest, BasicOperations) { // Verify the result EXPECT_TRUE(xt::allclose(arr2, expected)); } - -TEST(XTensorTest, SpanToXtensor) { - std::vector data = {1, 2, 3, 4, 5, 6}; - std::span data_span(data.data(), data.size()); - ttnn::SimpleShape shape({2, 3}); - - auto result = ttml::core::span_to_xtensor_view(data_span, shape); - - // Check shape - EXPECT_EQ(result.shape().size(), 2); - EXPECT_EQ(result.shape()[0], 2); - EXPECT_EQ(result.shape()[1], 3); - - // Check data - int expected_val = 1; - for (size_t i = 0; i < result.shape()[0]; ++i) { - for (size_t j = 0; j < result.shape()[1]; ++j) { - EXPECT_EQ(result(i, j), expected_val++); - } - } -} - -// Test xtensor_to_span -TEST(XTensorTest, XtensorToSpan) { - xt::xarray arr = {{1.0f, 2.0f}, {3.0f, 4.0f}}; - auto span_result = ttml::core::xtensor_to_span(arr); - - EXPECT_EQ(span_result.size(), arr.size()); - - // Check data - size_t index = 0; - for (float val : arr) { - EXPECT_FLOAT_EQ(span_result[index++], val); - } -} - -// Test get_shape_4d -TEST(XTensorTest, GetShape4D) { - // Test a 4D shape - xt::xarray arr_4d = xt::xarray::from_shape({2, 3, 4, 5}); - auto shape4d = ttml::core::get_shape_4d(arr_4d); - EXPECT_EQ(shape4d[0], 2); - EXPECT_EQ(shape4d[1], 3); - EXPECT_EQ(shape4d[2], 4); - EXPECT_EQ(shape4d[3], 5); - - // Test a 2D shape, should zero-pad to the left (or right) as per logic - xt::xarray arr_2d = xt::xarray::from_shape({10, 20}); - auto shape2d = ttml::core::get_shape_4d(arr_2d); - // dims=2, so shape4d = {1, 1, 10, 20} - EXPECT_EQ(shape2d[0], 1); - EXPECT_EQ(shape2d[1], 1); - EXPECT_EQ(shape2d[2], 10); - EXPECT_EQ(shape2d[3], 20); - - // Test a 1D shape - xt::xarray arr_1d = xt::xarray::from_shape({7}); - auto shape1d = ttml::core::get_shape_4d(arr_1d); - // dims=1, so shape4d = {1, 1, 1, 7} - EXPECT_EQ(shape1d[0], 1); - EXPECT_EQ(shape1d[1], 1); - EXPECT_EQ(shape1d[2], 1); - EXPECT_EQ(shape1d[3], 7); - - // Test throwing an exception for >4D - xt::xarray arr_5d = xt::xarray::from_shape({2, 2, 2, 2, 2}); - EXPECT_THROW(ttml::core::get_shape_4d(arr_5d), std::runtime_error); -} diff --git a/tt-train/tests/core/distributed_test.cpp b/tt-train/tests/core/distributed_test.cpp index 4d9bc0e8ae6a..0617c317ef34 100644 --- a/tt-train/tests/core/distributed_test.cpp +++ b/tt-train/tests/core/distributed_test.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include "core/distributed_mapping.hpp" @@ -22,35 +22,6 @@ class MeshOpsTest : public ::testing::Test { using TestTypes = ::testing::Types; TYPED_TEST_SUITE(MeshOpsTest, TestTypes); -TYPED_TEST(MeshOpsTest, ChunkBasicNonDivisible3) { - // Create a 1D tensor: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - // Using TypeParam ensures we test both uint32_t and float. - xt::xarray tensor = xt::arange(10); - - // Chunk into 3 parts along dimension 0 - auto chunks = ttml::core::chunk(tensor, 3, 0); - - ASSERT_THAT(chunks, SizeIs(3)); - EXPECT_EQ(chunks[0].shape()[0], 4u); // first chunk size 4 - EXPECT_EQ(chunks[1].shape()[0], 4u); // next chunk size 4 - EXPECT_EQ(chunks[2].shape()[0], 2u); // last chunk size 2 -} - -TYPED_TEST(MeshOpsTest, ChunkBasicLessChunksThanProvided) { - // Create a 1D tensor: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12] - xt::xarray tensor = xt::arange(13); - - // Chunk into 6 parts along dimension 0 - auto chunks = ttml::core::chunk(tensor, 6, 0); - - ASSERT_THAT(chunks, SizeIs(5)); - EXPECT_EQ(chunks[0].shape()[0], 3u); // first chunk size 3 - EXPECT_EQ(chunks[1].shape()[0], 3u); // next chunk size 3 - EXPECT_EQ(chunks[2].shape()[0], 3u); // next chunk size 3 - EXPECT_EQ(chunks[3].shape()[0], 3u); // next chunk size 3 - EXPECT_EQ(chunks[4].shape()[0], 1u); // last chunk size 1 -} - TYPED_TEST(MeshOpsTest, ShardXTensorToMeshBasicShard) { tt::tt_metal::distributed::MeshShape mesh_shape = {1, 4}; @@ -162,76 +133,6 @@ TYPED_TEST(MeshOpsTest, VectorMeshToXTensorVectorReturn) { } } -TEST(ConcatenateTest, DefaultAxis) { - xt::xarray a = {{1.0, 2.0}, {3.0, 4.0}}; - xt::xarray b = {{5.0, 6.0}, {7.0, 8.0}}; - std::vector> input = {a, b}; - - xt::xarray result = ttml::core::concatenate(input); // axis=0 by default - xt::xarray expected = {{1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0}, {7.0, 8.0}}; - - xt::allclose(result, expected); -} - -TEST(ConcatenateTest, AxisOne) { - xt::xarray x = {{1, 2, 3}, {4, 5, 6}}; - xt::xarray y = {{7, 8}, {9, 10}}; - std::vector> input = {x, y}; - - xt::xarray result = ttml::core::concatenate(input, 1); - xt::xarray expected = {{1, 2, 3, 7, 8}, {4, 5, 6, 9, 10}}; - - xt::allclose(result, expected); -} - -TEST(ConcatenateTest, MultipleArraysAxis0) { - xt::xarray a = {1.0f, 2.0f}; - xt::xarray b = {3.0f, 4.0f}; - xt::xarray c = {5.0f, 6.0f}; - std::vector> input = {a, b, c}; - - xt::xarray result = ttml::core::concatenate(input, 0); - xt::xarray expected = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - - xt::allclose(result, expected); -} - -TEST(ConcatenateTest, EmptyArray) { - xt::xarray a = {{1, 2}, {3, 4}}; - xt::xarray b; // Empty - std::vector> input = {a, b}; - - EXPECT_ANY_THROW({ xt::xarray result = ttml::core::concatenate(input, 0); }); -} - -TEST(ConcatenateTest, HigherDimensions) { - xt::xarray arr1 = xt::arange(1, 9); // 1 to 8 - arr1.reshape({2, 2, 2}); - xt::xarray arr2 = xt::arange(9, 17); // 9 to 16 - arr2.reshape({2, 2, 2}); - - std::vector> input = {arr1, arr2}; - xt::xarray result = ttml::core::concatenate(input, 0); - - // Expected: shape (4,2,2) with arr1 stacked over arr2 along axis 0 - xt::xarray expected = xt::concatenate(xt::xtuple(arr1, arr2), 0); - - xt::allclose(result, expected); -} - -TEST(ConcatenateTest, HigherAxis) { - xt::xarray arr1 = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}; - xt::xarray arr2 = {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}; - // Both have shape (2,2,2) - - std::vector> input = {arr1, arr2}; - xt::xarray result = ttml::core::concatenate(input, 2); - // Expected shape: (2,2,4) - xt::xarray expected = {{{1, 2, 9, 10}, {3, 4, 11, 12}}, {{5, 6, 13, 14}, {7, 8, 15, 16}}}; - - xt::allclose(result, expected); -} - TYPED_TEST(MeshOpsTest, ConcatenateSameParametersAsCompose) { tt::tt_metal::distributed::MeshShape mesh_shape = {1, 3}; @@ -242,7 +143,7 @@ TYPED_TEST(MeshOpsTest, ConcatenateSameParametersAsCompose) { std::vector> shards = {s1, s2, s3}; ttml::core::ConcatMeshToXTensor composer(mesh_shape, 0); - auto composed = ttml::core::concatenate(shards); + auto composed = ttml::core::concat(shards); xt::xarray expected = { TypeParam(0), TypeParam(1), TypeParam(2), TypeParam(3), TypeParam(4), TypeParam(5)}; diff --git a/ttnn/CMakeLists.txt b/ttnn/CMakeLists.txt index c5dcfab9443f..a3bcf32d425d 100644 --- a/ttnn/CMakeLists.txt +++ b/ttnn/CMakeLists.txt @@ -10,6 +10,8 @@ set(ALL_TTNN_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/distributed/api.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/distributed/distributed_tensor_config.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/distributed/distributed_pybind.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/distributed/distributed_tensor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/distributed/distributed_tensor_config.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/graph/graph_processor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/graph/graph_trace_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/graph/graph_pybind.cpp @@ -613,6 +615,9 @@ set(TTNN_PUBLIC_LINK_LIBRARIES metal_common_libs Metalium::Metal Boost::container + xtensor + xtensor-blas + xtl ) set(TTNN_PUBLIC_LINK_DIRS "") diff --git a/ttnn/cpp/ttnn/distributed/api.cpp b/ttnn/cpp/ttnn/distributed/api.cpp index 14aa7085ff54..fee7fa1566c5 100644 --- a/ttnn/cpp/ttnn/distributed/api.cpp +++ b/ttnn/cpp/ttnn/distributed/api.cpp @@ -11,10 +11,11 @@ #include "ttnn/tensor/tensor_utils.hpp" #include "ttnn/distributed/distributed_tensor_config.hpp" #include "tt_metal/distributed/mesh_device.hpp" +#include "ttnn/distributed/distributed_tensor_config.hpp" using namespace tt::tt_metal; -namespace ttnn::distributed::api { +namespace ttnn::distributed { std::shared_ptr open_mesh_device( const MeshShape& mesh_shape, @@ -299,4 +300,4 @@ Tensor create_multi_device_tensor( } } -} // namespace ttnn::distributed::api +} // namespace ttnn::distributed diff --git a/ttnn/cpp/ttnn/distributed/api.hpp b/ttnn/cpp/ttnn/distributed/api.hpp index 23a914a02c92..7ff9435c7e32 100644 --- a/ttnn/cpp/ttnn/distributed/api.hpp +++ b/ttnn/cpp/ttnn/distributed/api.hpp @@ -9,8 +9,9 @@ #include "ttnn/tensor/tensor.hpp" #include "ttnn/distributed/distributed_tensor_config.hpp" #include "ttnn/distributed/types.hpp" +#include "ttnn/distributed/distributed_tensor_config.hpp" -namespace ttnn::distributed::api { +namespace ttnn::distributed { std::shared_ptr open_mesh_device( const MeshShape& mesh_shape, @@ -55,10 +56,4 @@ Tensor create_multi_device_tensor( tt::tt_metal::StorageType storage_type, const tt::tt_metal::DistributedTensorConfig& strategy); -} // namespace ttnn::distributed::api - -namespace ttnn::distributed { - -using namespace api; - } // namespace ttnn::distributed diff --git a/ttnn/cpp/ttnn/distributed/distributed_tensor.cpp b/ttnn/cpp/ttnn/distributed/distributed_tensor.cpp new file mode 100644 index 000000000000..4908413132f4 --- /dev/null +++ b/ttnn/cpp/ttnn/distributed/distributed_tensor.cpp @@ -0,0 +1,194 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "ttnn/distributed/api.hpp" +#include "ttnn/distributed/distributed_tensor.hpp" +#include "common/assert.hpp" +#include "ttnn/distributed/distributed_tensor_config.hpp" +#include "ttnn/distributed/types.hpp" +#include "ttnn/tensor/xtensor/partition.hpp" + +namespace ttnn::distributed { +namespace { + +class ReplicateTensorToMesh : public TensorToMesh { +public: + ReplicateTensorToMesh(size_t num_devices) : num_devices_(num_devices) {} + + std::vector map(const Tensor& tensor) override { + std::vector tensors; + tensors.reserve(num_devices_); + std::fill_n(std::back_inserter(tensors), num_devices_, tensor); + return tensors; + } + + DistributedTensorConfig config() const override { return DistributedTensorConfig{ReplicateTensor{num_devices_}}; } + +private: + size_t num_devices_ = 0; +}; + +class ShardTensorToMesh : public TensorToMesh { +public: + ShardTensorToMesh(size_t num_devices, int dim) : num_devices_(num_devices), shard_dim_(dim) {} + + std::vector map(const Tensor& tensor) override { + return experimental::xtensor::chunk(tensor, num_devices_, shard_dim_); + } + + DistributedTensorConfig config() const override { return DistributedTensorConfig{ShardTensor{shard_dim_}}; } + +private: + size_t num_devices_ = 0; + int shard_dim_ = -1; +}; + +class ShardTensorTo2dMesh : public TensorToMesh { +public: + ShardTensorTo2dMesh(const MeshShape& mesh_shape, const Shard2dConfig& config) : + mesh_shape_(mesh_shape), config_(config) {} + + std::vector map(const Tensor& tensor) override { + const auto [rows, cols] = mesh_shape_; + const auto [row_dim, col_dim] = config_; + + std::vector row_tensors; + + // Shard along rows + if (!row_dim.has_value()) { + row_tensors.reserve(rows); + for (int i = 0; i < rows; ++i) { + row_tensors.push_back(tensor); + } + } else { + row_tensors = experimental::xtensor::chunk(tensor, rows, *row_dim); + } + + std::vector tensor_shards; + tensor_shards.reserve(rows * cols); + // Shard along columns + if (!col_dim.has_value()) { + for (const auto& t : row_tensors) { + for (int i = 0; i < cols; ++i) { + tensor_shards.push_back(t); + } + } + } else { + for (const auto& t : row_tensors) { + auto col_chunks = experimental::xtensor::chunk(t, cols, *col_dim); + tensor_shards.insert(tensor_shards.end(), col_chunks.begin(), col_chunks.end()); + } + } + + TT_FATAL( + static_cast(tensor_shards.size()) == rows * cols, + "ShardTensorTo2dMesh: Sharding failed. Number of shards should match the product of the mesh " + "dimensions. Size: {}, rows: {}, cols: {}", + tensor_shards.size(), + rows, + cols); + + return tensor_shards; + } + + DistributedTensorConfig config() const override { + return DistributedTensorConfig{ShardTensor2D{ShardMesh{mesh_shape_.num_rows, mesh_shape_.num_cols}}}; + } + +private: + MeshShape mesh_shape_; + Shard2dConfig config_; +}; + +class ConcatMeshToTensor : public MeshToTensor { +public: + ConcatMeshToTensor(int dim) : concat_dim_(dim) {} + + Tensor compose(const std::vector& tensors) override { + return experimental::xtensor::concat(tensors, concat_dim_); + } + +private: + int concat_dim_ = -1; +}; + +class Concat2dMeshToTensor : public MeshToTensor { +public: + Concat2dMeshToTensor(MeshDevice& mesh_device, const Concat2dConfig& config) : + mesh_shape_(mesh_device.shape()), config_(config) {} + + Tensor compose(const std::vector& tensors) override { + const auto [rows, cols] = mesh_shape_; + const auto [row_dim, col_dim] = config_; + + std::vector row_concatenated; + row_concatenated.reserve(rows); + for (int i = 0; i < rows; ++i) { + auto row_start = tensors.begin() + i * cols; + auto row_end = row_start + cols; + std::vector row_tensors(row_start, row_end); + row_concatenated.push_back(experimental::xtensor::concat(row_tensors, col_dim)); + } + + return experimental::xtensor::concat(row_concatenated, row_dim); + } + +private: + MeshShape mesh_shape_; + Concat2dConfig config_; +}; + +} // namespace + +std::unique_ptr replicate_tensor_to_mesh_mapper(MeshDevice& mesh_device) { + return std::make_unique(mesh_device.num_devices()); +} + +std::unique_ptr shard_tensor_to_mesh_mapper(MeshDevice& mesh_device, int dim) { + return std::make_unique(mesh_device.num_devices(), dim); +} + +std::unique_ptr shard_tensor_to_2d_mesh_mapper( + MeshDevice& mesh_device, const MeshShape& mesh_shape, const Shard2dConfig& config) { + TT_FATAL( + config.row_dim.has_value() || config.col_dim.has_value(), + "Sharding a tensor to 2D mesh requires at least one dimension to shard"); + TT_FATAL( + mesh_shape.num_rows <= mesh_device.shape().num_rows && // + mesh_shape.num_cols <= mesh_device.shape().num_cols, + "Device mesh shape does not match the provided mesh shape."); + return std::make_unique(mesh_shape, config); +} + +std::unique_ptr concat_mesh_to_tensor_composer(int dim) { + return std::make_unique(dim); +} + +std::unique_ptr concat_2d_mesh_to_tensor_composer(MeshDevice& mesh_device, const Concat2dConfig& config) { + TT_FATAL( + config.row_dim != config.col_dim, + "Dimensions in 'dims' must be different; got row_dim: {}, col_dim: {}", + config.row_dim, + config.col_dim); + return std::make_unique(mesh_device, config); +} + +Tensor distribute_tensor(const Tensor& tensor, MeshDevice& mesh_device, TensorToMesh& mapper) { + TT_FATAL( + tensor.storage_type() != StorageType::MULTI_DEVICE && tensor.storage_type() != StorageType::MULTI_DEVICE_HOST, + "TensorToMesh does not support multi-device or multi-device host tensors; got storage type: {}", + tensor.storage_type()); + std::vector tensors = mapper.map(tensor); + Tensor output = aggregate_as_tensor(tensors, mapper.config()); + return output.to(&mesh_device); +} + +Tensor aggregate_tensor(const Tensor& tensor, MeshToTensor& composer) { + return is_multi_device_tensor(tensor) ? composer.compose(get_tensors_from_multi_device_storage(tensor)) + : composer.compose({tensor}); +} + +} // namespace ttnn::distributed diff --git a/ttnn/cpp/ttnn/distributed/distributed_tensor.hpp b/ttnn/cpp/ttnn/distributed/distributed_tensor.hpp new file mode 100644 index 000000000000..7aaee73caa4c --- /dev/null +++ b/ttnn/cpp/ttnn/distributed/distributed_tensor.hpp @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ttnn/tensor/tensor.hpp" +#include "ttnn/distributed/types.hpp" + +namespace ttnn::distributed { + +// Mapper interface that distributes a host tensor onto a multi-device configuration. +class TensorToMesh { +public: + virtual ~TensorToMesh() = default; + virtual std::vector map(const Tensor& tensor) = 0; + virtual DistributedTensorConfig config() const = 0; +}; + +// Composer interface that aggregates a multi-device tensor into a host tensor. +class MeshToTensor { +public: + virtual ~MeshToTensor() = default; + virtual Tensor compose(const std::vector& tensors) = 0; +}; + +// Creates a mapper that replicates a tensor across all devices. +std::unique_ptr replicate_tensor_to_mesh_mapper(MeshDevice& mesh_device); + +// Creates a mapper that shards a tensor along a single dimension. +std::unique_ptr shard_tensor_to_mesh_mapper(MeshDevice& mesh_device, int dim); + +// Creates a mapper that shards a tensor along two dimensions, which will be intepreted as rows and columns. +// If either dimension is not specified, the tensor is replicated along that dimension. +struct Shard2dConfig { + std::optional row_dim; + std::optional col_dim; +}; +std::unique_ptr shard_tensor_to_2d_mesh_mapper( + MeshDevice& mesh_device, const MeshShape& mesh_shape, const Shard2dConfig& config); + +// Creates a composer that concatenates a tensor across a single dimension. +std::unique_ptr concat_mesh_to_tensor_composer(int dim); + +// Creates a composer that concatenates a tensor across two dimensions. +struct Concat2dConfig { + int row_dim = -1; + int col_dim = -1; +}; +std::unique_ptr concat_2d_mesh_to_tensor_composer(MeshDevice& mesh_device, const Concat2dConfig& config); + +// Distributes a host tensor onto multi-device configuration according to the `mapper`. +Tensor distribute_tensor(const Tensor& tensor, MeshDevice& mesh_device, TensorToMesh& mapper); + +// Aggregates a multi-device tensor into a host tensor according to the `composer`. +Tensor aggregate_tensor(const Tensor& tensor, MeshToTensor& composer); + +} // namespace ttnn::distributed diff --git a/ttnn/cpp/ttnn/distributed/distributed_tensor_config.cpp b/ttnn/cpp/ttnn/distributed/distributed_tensor_config.cpp index 9ae22852fd55..6e69a86b8bea 100644 --- a/ttnn/cpp/ttnn/distributed/distributed_tensor_config.cpp +++ b/ttnn/cpp/ttnn/distributed/distributed_tensor_config.cpp @@ -51,7 +51,8 @@ bool operator==(const AllGatherTensor&, const AllGatherTensor&) { } bool operator==(const ShardTensor& lhs, const ShardTensor& rhs) { return lhs.shard_dimension == rhs.shard_dimension; } bool operator==(const ShardTensor2D& lhs, const ShardTensor2D& rhs) { - return lhs.shard_mesh.x == rhs.shard_mesh.x && lhs.shard_mesh.y == rhs.shard_mesh.y; + return lhs.shard_mesh.x == rhs.shard_mesh.x && // + lhs.shard_mesh.y == rhs.shard_mesh.y; } } // namespace tt::tt_metal diff --git a/ttnn/cpp/ttnn/distributed/distributed_tensor_config.hpp b/ttnn/cpp/ttnn/distributed/distributed_tensor_config.hpp index 5f67262028ed..6d7c11099d0e 100644 --- a/ttnn/cpp/ttnn/distributed/distributed_tensor_config.hpp +++ b/ttnn/cpp/ttnn/distributed/distributed_tensor_config.hpp @@ -4,7 +4,6 @@ #pragma once -#include #include #include @@ -26,7 +25,6 @@ struct ShardMesh { std::uint16_t y = 0; std::uint16_t x = 0; }; - struct ShardTensor2D { ShardMesh shard_mesh; // logic 2D grid that defines the mapping of shards to devices ShardTensor2D(ShardMesh mesh) : shard_mesh(std::move(mesh)) {} diff --git a/ttnn/cpp/ttnn/tensor/CMakeLists.txt b/ttnn/cpp/ttnn/tensor/CMakeLists.txt index ef152a27f8af..583b3ca9f439 100644 --- a/ttnn/cpp/ttnn/tensor/CMakeLists.txt +++ b/ttnn/cpp/ttnn/tensor/CMakeLists.txt @@ -11,6 +11,7 @@ set(TENSOR_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/layout/page_config.cpp ${CMAKE_CURRENT_SOURCE_DIR}/layout/size.cpp ${CMAKE_CURRENT_SOURCE_DIR}/layout/tensor_layout.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/xtensor/partition.cpp CACHE INTERNAL "Tensor sources to reuse in ttnn build" ) diff --git a/ttnn/cpp/ttnn/tensor/tensor.cpp b/ttnn/cpp/ttnn/tensor/tensor.cpp index f4304d33c6a5..689cc127d34f 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor.cpp @@ -30,12 +30,9 @@ using namespace tt::constants; -namespace tt { - -namespace tt_metal { - +namespace tt::tt_metal { namespace { -namespace CMAKE_UNIQUE_NAMESPACE { + MemoryConfig extract_memory_config(const Storage& storage) { return std::visit( [](const auto& storage) -> MemoryConfig { @@ -50,7 +47,60 @@ MemoryConfig extract_memory_config(const Storage& storage) { }, storage); } -} // namespace CMAKE_UNIQUE_NAMESPACE + +template +Tensor create_owned_tensor_from_span(tt::stl::Span data, const TensorSpec& spec) { + // TODO: support tilized layouts. + TT_FATAL(spec.layout() == Layout::ROW_MAJOR, "Unsupported layout: {}", spec.layout()); + auto buffer = tt::tt_metal::owned_buffer::create(std::vector(data.begin(), data.end())); + auto storage = OwnedStorage{std::move(buffer)}; + return Tensor{std::move(storage), spec}; +} + +// TODO: optimize precomputing multipliers +template +std::vector untile_tensor_to_vec(const Tensor& cpu_tensor) { + auto tiled_buffer = tt::tt_metal::host_buffer::get_as(cpu_tensor); + auto untiled_shape = cpu_tensor.get_logical_shape(); + auto tiled_shape = cpu_tensor.get_padded_shape(); + + // Calculate total size of the untiled tensor + size_t total_size = untiled_shape.volume(); + + std::vector untiled_data(total_size); + + auto compute_flat_index = [](const std::vector& indices, ttnn::SimpleShape& shape) -> uint32_t { + uint32_t flat_index = 0; + uint32_t multiplier = 1; + for (int i = (int)indices.size() - 1; i >= 0; --i) { + flat_index += indices[i] * multiplier; + multiplier *= shape[i]; + } + return flat_index; + }; + + std::vector indices(tiled_shape.rank(), 0); + + for (size_t idx = 0; idx < total_size; ++idx) { + uint32_t untiled_index = compute_flat_index(indices, untiled_shape); + uint32_t tiled_index = compute_flat_index(indices, tiled_shape); + if constexpr (std::is_same_v) { + untiled_data[untiled_index] = tiled_buffer[tiled_index].to_float(); + } else { + untiled_data[untiled_index] = tiled_buffer[tiled_index]; + } + + for (int dim = (int)tiled_shape.rank() - 1; dim >= 0; --dim) { + if (++indices[dim] < untiled_shape[dim]) { + break; + } + indices[dim] = 0; + } + } + + return untiled_data; +} + } // namespace Tensor::TensorAttributes::TensorAttributes() : @@ -111,7 +161,7 @@ Tensor::Tensor( tile->get_tile_shape()); } } - auto memory_config = CMAKE_UNIQUE_NAMESPACE::extract_memory_config(storage); + auto memory_config = extract_memory_config(storage); init( std::move(storage), TensorSpec( @@ -559,6 +609,72 @@ const Storage& Tensor::get_storage() const { return this->tensor_attributes->storage; } +template <> +Tensor Tensor::from_span(tt::stl::Span buffer, const TensorSpec& spec) { + size_t volume = spec.logical_shape().volume(); + TT_FATAL( + buffer.size() == volume, "Current buffer size is {} different from shape volume {}", buffer.size(), volume); + if (spec.data_type() == DataType::FLOAT32) { + return create_owned_tensor_from_span(buffer, spec); + } else if (spec.data_type() == DataType::BFLOAT16) { + std::vector bfloat16_data; + bfloat16_data.reserve(buffer.size()); + std::transform(std::begin(buffer), std::end(buffer), std::back_inserter(bfloat16_data), [](float value) { + return bfloat16(value); + }); + return create_owned_tensor_from_span( + tt::stl::Span(bfloat16_data.data(), bfloat16_data.size()), spec); + } else { + // TODO: support bf8 and bf4 + TT_THROW("Unsupported data type for from_span: {}", spec.data_type()); + } +} + +template +Tensor Tensor::from_span(tt::stl::Span buffer, const TensorSpec& spec) { + size_t volume = spec.logical_shape().volume(); + TT_FATAL( + buffer.size() == volume, "Current buffer size is {} different from shape volume {}", buffer.size(), volume); + TT_FATAL( + spec.data_type() == convert_to_data_type(), + "Unsupported data type for from_span: got {}, expected: {}", + spec.data_type(), + convert_to_data_type()); + return create_owned_tensor_from_span(buffer, spec); +} + +template <> +std::vector Tensor::to_vector() const { + auto cpu_tensor = this->cpu().to(Layout::ROW_MAJOR); + if (cpu_tensor.get_dtype() == DataType::BFLOAT16) { + return untile_tensor_to_vec(cpu_tensor); + } else if (cpu_tensor.get_dtype() == DataType::FLOAT32) { + return untile_tensor_to_vec(cpu_tensor); + } else { + // TODO: support bf4, bf8. + TT_THROW("Cannot convert tensor to vector for data type: {}", cpu_tensor.get_dtype()); + } +} + +template +std::vector Tensor::to_vector() const { + auto cpu_tensor = this->cpu().to(Layout::ROW_MAJOR); + TT_FATAL( + cpu_tensor.get_dtype() == convert_to_data_type(), + "Unsupported data type for to_vector: got {}, expected: {}", + cpu_tensor.get_dtype(), + convert_to_data_type()); + return untile_tensor_to_vec(cpu_tensor); +} + +// Instantiate explicitly for the supported types. +template Tensor Tensor::from_span(tt::stl::Span buffer, const TensorSpec& spec); +template Tensor Tensor::from_span(tt::stl::Span buffer, const TensorSpec& spec); +template Tensor Tensor::from_span(tt::stl::Span buffer, const TensorSpec& spec); +template std::vector Tensor::to_vector() const; +template std::vector Tensor::to_vector() const; +template std::vector Tensor::to_vector() const; + Tensor Tensor::to(Device* target_device, const MemoryConfig& mem_config,uint8_t cq_id, const std::vector& sub_device_ids) const { return tensor_ops::tensor_to(*this, target_device, mem_config, cq_id, sub_device_ids); @@ -964,6 +1080,4 @@ bool validate_worker_modes(const std::vector& workers) { return worker_modes_match; } -} // namespace tt_metal - -} // namespace tt +} // namespace tt::tt_metal diff --git a/ttnn/cpp/ttnn/tensor/tensor.hpp b/ttnn/cpp/ttnn/tensor/tensor.hpp index b8b7a993b8a6..30e18978b8e5 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor.hpp @@ -139,6 +139,44 @@ struct Tensor { std::vector get_workers(bool blocking = false) const; + // Converts a buffer of elements of type `T` to a `Tensor`. + // Elements in the buffer are assumed to be stored in row-major order. The size of the buffer and the type of the + // elements have to match `spec`. + // + // The data in the buffer is copied into a tensor with an owned storage. + // + // IMPORTANT: this function supports a limited subset of types (float32, bfloat16, uint32_t, int32_t), + // and only row-major layout. + // + // TODO: + // 1. add support for returning a tensor with a borrowed storage based off the buffer. + // 2. add support for sharding. + // 3. add support for block float formats. + // 4. add support for tilized layouts. + // 5. add support for on-device tensor creation. + template + static Tensor from_span(tt::stl::Span buffer, const TensorSpec& spec); + + // Same as `from_span`, but takes a vector instead. + template + static Tensor from_vector(const std::vector& buffer, const TensorSpec& spec) { + return from_span(tt::stl::Span(buffer.data(), buffer.size()), spec); + } + + // Converts a `Tensor` to a `std::vector`. + // Elements in the vector will be stored in row-major order. The type of the requested vector has to match that of + // the `Tensor`. + // + // If the tensor resides on a device, it will be brough back to host. + // + // IMPORTANT: this function supports a limited subset of types (float32, bfloat16, uint32_t, int32_t). + // + // TODO: + // 1. add support for sharding. + // 2. add support for block float formats. + template + std::vector to_vector() const; + Tensor to( Device* target_device, const MemoryConfig& mem_config = {.memory_layout = tt::tt_metal::TensorMemoryLayout::INTERLEAVED}, diff --git a/ttnn/cpp/ttnn/tensor/tensor_ops.cpp b/ttnn/cpp/ttnn/tensor/tensor_ops.cpp index 44ee8fd7a6c7..96b53b879018 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_ops.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor_ops.cpp @@ -215,6 +215,7 @@ Tensor tensor_to(const Tensor& input_tensor, Layout target_layout, distributed:: host_storage != nullptr) { distributed_config = host_storage->strategy; } + Tensor tensor_modified_layout = Tensor(workers.size(), distributed_config); for (int worker_index = 0; worker_index < workers.size(); ++worker_index) { auto& worker = workers[worker_index]; diff --git a/ttnn/cpp/ttnn/tensor/xtensor/conversion_utils.hpp b/ttnn/cpp/ttnn/tensor/xtensor/conversion_utils.hpp new file mode 100644 index 000000000000..40705ef1d740 --- /dev/null +++ b/ttnn/cpp/ttnn/tensor/xtensor/conversion_utils.hpp @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ttnn/tensor/shape/small_vector.hpp" +#include "ttnn/tensor/tensor.hpp" +#include + +namespace ttnn::experimental::xtensor { + +// Returns the shape of the xtensor as `ttnn::SimpleShape`. +template +ttnn::SimpleShape get_shape_from_xarray(const E& xarr) { + ttnn::SmallVector shape_dims; + for (size_t i = 0; i < xarr.shape().size(); ++i) { + shape_dims.push_back(xarr.shape()[i]); + } + return ttnn::SimpleShape(shape_dims); +} + +// Converts a span to an xtensor view. +// IMPORTANT: the lifetime of the returned xtensor view is tied to the lifetime of the underlying buffer. +template +xt::xarray span_to_xtensor_view(tt::stl::Span buffer, const ttnn::SimpleShape& shape) { + std::vector shape_vec(shape.cbegin(), shape.cend()); + return xt::adapt(buffer.data(), buffer.size(), xt::no_ownership(), shape_vec); +} + +// Converts a span to an xtensor view. +// IMPORTANT: the lifetime of the returned xtensor view is tied to the lifetime of the underlying buffer. +template +xt::xarray span_to_xtensor_view(std::span buffer, const ttnn::SimpleShape& shape) { + std::vector shape_vec(shape.cbegin(), shape.cend()); + return xt::adapt(buffer.data(), buffer.size(), xt::no_ownership(), shape_vec); +} + +// Converts an xtensor to a span. +// IMPORTANT: the lifetime of the returned span is tied to the lifetime of the underlying xtensor. +template +auto xtensor_to_span(const xt::xarray& xtensor) { + auto adaptor = xt::adapt(xtensor.data(), xtensor.size(), xt::no_ownership()); + return tt::stl::Span(adaptor.data(), adaptor.size()); +} + +// Converts an xtensor to a Tensor. +// IMPORTANT: this copies the data into the returned Tensor, which can be an expensive operation. +template +tt::tt_metal::Tensor from_xtensor(const xt::xarray& buffer, const TensorSpec& spec) { + auto shape = get_shape_from_xarray(buffer); + TT_FATAL(shape == spec.logical_shape(), "xtensor has a different shape than the supplied TensorSpec"); + auto buffer_view = xtensor_to_span(buffer); + return tt::tt_metal::Tensor::from_span(buffer_view, spec); +} + +// Converts a Tensor to an xtensor. +// IMPORTANT: this copies the data into the returned Tensor, which can be an expensive operation. +template +xt::xarray to_xtensor(const tt::tt_metal::Tensor& tensor) { + auto vec = tensor.to_vector(); + auto shape = tensor.get_shape().logical_shape(); + return xt::xarray(span_to_xtensor_view(tt::stl::Span(vec.data(), vec.size()), shape)); +} + +} // namespace ttnn::experimental::xtensor diff --git a/ttnn/cpp/ttnn/tensor/xtensor/partition.cpp b/ttnn/cpp/ttnn/tensor/xtensor/partition.cpp new file mode 100644 index 000000000000..e01a6838bd8c --- /dev/null +++ b/ttnn/cpp/ttnn/tensor/xtensor/partition.cpp @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "ttnn/tensor/layout/tensor_layout.hpp" +#include "ttnn/tensor/tensor.hpp" +#include "ttnn/tensor/types.hpp" +#include "ttnn/tensor/xtensor/conversion_utils.hpp" + +namespace ttnn::experimental::xtensor { +namespace { + +using ::tt::tt_metal::Tensor; + +template +auto vector_to_tuple_helper(const std::vector& v, std::index_sequence) { + return std::make_tuple(v[Indices]...); +} + +template +auto vector_to_tuple(const std::vector& buffer) { + TT_FATAL(buffer.size() >= N, "Buffer size {} is less than N {}", buffer.size(), N); + return vector_to_tuple_helper(buffer, std::make_index_sequence()); +} + +template +xt::xarray concat_helper(const std::vector>& v, size_t axis = 0) { + constexpr int FIXED_N = N < 2 ? 2 : N; + TT_FATAL(N > 1, "Tuple size in concatenate must be greater than 1; got N: {}", N); + auto tuple = vector_to_tuple(v); + return xt::concatenate(std::move(tuple), axis); +} + +template +consteval auto create_array_impl(std::index_sequence) { + return std::array (*)(const std::vector>& v, size_t axis), sizeof...(I)>{ + concat_helper...}; +} + +template +consteval auto create_array() { + return create_array_impl(std::make_index_sequence()); +} + +} // namespace + +template +std::vector> chunk(const xt::xarray& xtensor, int num_chunks, int dim) { + TT_FATAL(num_chunks > 0, "num_chunks must be > 0; got num_chunks: {}", num_chunks); + TT_FATAL( + dim >= 0 && dim < xtensor.dimension(), + "invalid dimension index; got dim: {}, tensor dimension: {}", + dim, + xtensor.dimension()); + + const int size_along_dim = static_cast(xtensor.shape()[dim]); + TT_FATAL( + num_chunks <= size_along_dim, + "num_chunks cannot exceed the size of the tensor along the given dimension; got num_chunks: {}, " + "size_along_dim: {}", + num_chunks, + size_along_dim); + + if (num_chunks == 1) { + return {xtensor}; + } + + const int chunk_size = (size_along_dim + num_chunks - 1) / num_chunks; + int remaining_size = size_along_dim; + + std::vector> chunks; + chunks.reserve(static_cast(num_chunks)); + int start = 0; + int end = 0; + for (int i = 0; i < num_chunks && end < size_along_dim; ++i) { + int current_chunk_size = std::min(chunk_size, remaining_size); + remaining_size -= current_chunk_size; + end = start + current_chunk_size; + + // Build indices for slicing + xt::xstrided_slice_vector indices(xtensor.dimension(), xt::all()); + indices[dim] = xt::range(start, end); + + auto chunk_view = xt::strided_view(xtensor, indices); + + // TODO: optimize away this copy. + // Construct xarray from the view + // This forces a copy of that slice into a new xarray + chunks.push_back(xt::xarray(chunk_view)); + start = end; + } + + return chunks; +} + +template +xt::xarray concat(const std::vector>& v, int dim) { + constexpr size_t MAX_TUPLE_SIZE = 64; + + if (v.empty()) { + return {}; + } else if (v.size() == 1) { + return v.front(); + } else if (v.size() > MAX_TUPLE_SIZE) { + TT_THROW( + "Number of tensors to concatenate exceeds the maximum supported size {}; got size: {}", + MAX_TUPLE_SIZE, + v.size()); + } else { + constexpr auto table = create_array(); + return (*table[v.size()])(v, dim); + } +} + +template xt::xarray concat(const std::vector>& v, int dim); +template xt::xarray concat(const std::vector>& v, int dim); +template xt::xarray concat(const std::vector>& v, int dim); +template xt::xarray concat(const std::vector>& v, int dim); + +// Adaptor APIs from xtensor to ttnn::Tensor. +namespace adaptor { +namespace { + +template +Tensor concat_impl(const std::vector& tensors, const TensorLayout& layout, int dim) { + std::vector> xtensors; + for (const auto& tensor : tensors) { + xtensors.push_back(to_xtensor(tensor)); + } + xt::xarray result = concat(xtensors, dim); + return from_xtensor(result, TensorSpec(get_shape_from_xarray(result), layout)); +} + +template +std::vector chunk_impl(const Tensor& tensor, const TensorLayout& layout, int num_chunks, int dim) { + xt::xarray xtensor = to_xtensor(tensor); + auto xtensor_chunks = chunk(xtensor, num_chunks, dim); + + std::vector tensors; + tensors.reserve(xtensor_chunks.size()); + for (const auto& c : xtensor_chunks) { + TensorSpec chunk_spec(get_shape_from_xarray(c), layout); + tensors.push_back(from_xtensor(c, chunk_spec)); + } + return tensors; +} + +} // namespace +} // namespace adaptor + +std::vector chunk(const Tensor& tensor, int num_chunks, int dim) { + const auto& reference_layout = tensor.tensor_spec().tensor_layout(); + switch (reference_layout.get_data_type()) { + case DataType::BFLOAT16: return adaptor::chunk_impl(tensor, reference_layout, num_chunks, dim); + case DataType::FLOAT32: return adaptor::chunk_impl(tensor, reference_layout, num_chunks, dim); + case DataType::INT32: return adaptor::chunk_impl(tensor, reference_layout, num_chunks, dim); + case DataType::UINT32: return adaptor::chunk_impl(tensor, reference_layout, num_chunks, dim); + default: TT_THROW("Unsupported data type: {}", reference_layout.get_data_type()); + } +} + +Tensor concat(const std::vector& tensors, int dim) { + TT_FATAL(tensors.size() > 0, "Cannot concatenate an empty list of tensors"); + const auto& reference_layout = tensors.front().tensor_spec().tensor_layout(); + switch (reference_layout.get_data_type()) { + case DataType::BFLOAT16: return adaptor::concat_impl(tensors, reference_layout, dim); + case DataType::FLOAT32: return adaptor::concat_impl(tensors, reference_layout, dim); + case DataType::INT32: return adaptor::concat_impl(tensors, reference_layout, dim); + case DataType::UINT32: return adaptor::concat_impl(tensors, reference_layout, dim); + default: TT_THROW("Unsupported data type: {}", reference_layout.get_data_type()); + } +} + +} // namespace ttnn::experimental::xtensor diff --git a/ttnn/cpp/ttnn/tensor/xtensor/partition.hpp b/ttnn/cpp/ttnn/tensor/xtensor/partition.hpp new file mode 100644 index 000000000000..59a888f446fe --- /dev/null +++ b/ttnn/cpp/ttnn/tensor/xtensor/partition.hpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ttnn/tensor/tensor.hpp" +#include "ttnn/tensor/xtensor/xtensor_all_includes.hpp" + +namespace ttnn::experimental::xtensor { + +// IMPORTANT: `chunk` and `concatenate` are not yet part of the public ttnn API. Internally, they rely on the `xtensor` +// library for efficient host-side operations. + +// Splits a tensor into chunks along the specified dimension. +std::vector chunk(const tt::tt_metal::Tensor& tensor, int num_chunks, int dim = 0); + +template +std::vector> chunk(const xt::xarray& tensor, int num_chunks, int dim = 0); + +// Concatenates a list of tensors along the specified dimension. +tt::tt_metal::Tensor concat(const std::vector& tensors, int dim = 0); + +template +xt::xarray concat(const std::vector>& v, int dim = 0); + +} // namespace ttnn::experimental::xtensor diff --git a/tt-train/sources/ttml/core/xtensor_all_includes.hpp b/ttnn/cpp/ttnn/tensor/xtensor/xtensor_all_includes.hpp similarity index 100% rename from tt-train/sources/ttml/core/xtensor_all_includes.hpp rename to ttnn/cpp/ttnn/tensor/xtensor/xtensor_all_includes.hpp From bbce2c3359e128273a38e2afdcf5e346a0941858 Mon Sep 17 00:00:00 2001 From: Artem Yerofieiev <169092593+ayerofieiev-tt@users.noreply.github.com> Date: Mon, 16 Dec 2024 20:08:17 -0800 Subject: [PATCH 17/87] #0: Allow ttnn.pad to pad Tensor to an odd width in row major (#16079) ### Ticket https://github.com/tenstorrent/tt-metal/issues/15603 ### Problem description ttnn.pad call throws an error when user attempts to pad a tensor to an odd number of elements in width dimension ### What's changed Remove the check, it is outdated since we resolved the underlying constraint ### Checklist - [x] [Post commit CI](https://github.com/tenstorrent/tt-metal/actions/runs/12363708529) --- tests/ttnn/unit_tests/operations/test_pad.py | 6 +++++- .../cpp/ttnn/operations/data_movement/pad/device/pad_op.cpp | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/ttnn/unit_tests/operations/test_pad.py b/tests/ttnn/unit_tests/operations/test_pad.py index fa1373d59fea..0dc9aa18ef1e 100644 --- a/tests/ttnn/unit_tests/operations/test_pad.py +++ b/tests/ttnn/unit_tests/operations/test_pad.py @@ -18,7 +18,11 @@ @pytest.mark.parametrize("w", [224]) @pytest.mark.parametrize( "padding,torch_padding", - [(((0, 1), (3, 25), (32, 32)), (32, 32, 3, 25, 0, 1)), (((0, 1), (3, 25), (4, 6)), (4, 6, 3, 25, 0, 1))], + [ + (((0, 1), (3, 25), (32, 32)), (32, 32, 3, 25, 0, 1)), + (((0, 1), (3, 25), (4, 6)), (4, 6, 3, 25, 0, 1)), + (((0, 1), (3, 25), (4, 7)), (4, 7, 3, 25, 0, 1)), # Odd padding widths (5 and 7) + ], ) @pytest.mark.parametrize("value", [0]) def test_pad_rm(device, n, c, h, w, padding, torch_padding, value): diff --git a/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_op.cpp b/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_op.cpp index 0dbbf5fc4ae6..ef91a8bd3774 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_op.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_op.cpp @@ -45,7 +45,6 @@ void Pad::validate_with_output_tensors( input_tensor.get_dtype() == DataType::FLOAT32 || input_tensor.get_dtype() == DataType::BFLOAT16, "Cannot pad tilized tensor with specified format"); } else if (input_tensor.get_layout() == Layout::ROW_MAJOR) { - TT_FATAL(this->output_tensor_shape[3] % 2 == 0, "RM padding requires output X dim to be a multiple of 2"); TT_FATAL( input_tensor.get_dtype() == DataType::FLOAT32 || input_tensor.get_dtype() == DataType::BFLOAT16, "Cannot pad RM tensor with specified format"); From 56836273314e149f230155f8e7e1f9d42dcecb64 Mon Sep 17 00:00:00 2001 From: Nemanja Grujic <109360083+nemanjagrujic@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:04:34 +0100 Subject: [PATCH 18/87] #15565 Add unit test to show sharding ttnn.from_torch problems (#15827) #15565 ### Ticket [Link to Github Issue](https://github.com/tenstorrent/tt-metal/issues/15565) ### Problem description Issues with sharding giving low PCC in some cases need unit test which show it. ### What's changed Added better unit test to showcase sharding low PCC problems ### Checklist - [x] Post commit CI passes --- .../wormhole/test_eltwise_block_shard_spec.py | 276 ++++++++++++++++++ .../tensor/test_sharding_with_alignment.cpp | 45 +++ 2 files changed, 321 insertions(+) create mode 100644 tests/ttnn/python_api_testing/non_working_unit_tests/wormhole/test_eltwise_block_shard_spec.py diff --git a/tests/ttnn/python_api_testing/non_working_unit_tests/wormhole/test_eltwise_block_shard_spec.py b/tests/ttnn/python_api_testing/non_working_unit_tests/wormhole/test_eltwise_block_shard_spec.py new file mode 100644 index 000000000000..88f9a193b179 --- /dev/null +++ b/tests/ttnn/python_api_testing/non_working_unit_tests/wormhole/test_eltwise_block_shard_spec.py @@ -0,0 +1,276 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from loguru import logger +import random +import pytest +import torch +import ttnn + +from tests.ttnn.utils_for_testing import assert_with_pcc, check_with_pcc +from tests.ttnn.python_api_testing.sweep_tests import ttnn_ops +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_rand_inf + +Y, X = (8, 8) + + +def run_tests( + input_shape, + dtype, + dlayout, + tensor_memory_layout, + byffer_type, + shard_grid, + shard_shape, + shard_orientation, + halo, + torch_op, + ttnn_op, + gen_infs, + device, +): + random.seed(0) + data_seed = random.randint(0, 20000000) + torch.manual_seed(data_seed) + + if gen_infs: + torch_input_tensor_a = gen_rand_inf(input_shape, low=-100, high=100) + else: + torch_input_tensor_a = torch.Tensor(size=input_shape).uniform_(-50, 50).to(torch.bfloat16) + + torch_output_tensor = torch_input_tensor_a + + shard_spec = ttnn.ShardSpec(shard_grid, shard_shape, shard_orientation, halo) + sharded_config = ttnn.MemoryConfig(tensor_memory_layout, byffer_type, shard_spec) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=dtype, + layout=dlayout, + device=device, + memory_config=sharded_config, + ) + + output_tensor = input_tensor_a + output_tensor = ttnn.to_torch(output_tensor) + + [passed, message] = check_with_pcc(torch_output_tensor, output_tensor, 0.999) + assert passed, f"PCC={message}" + + +test_sweep_args = [ + ( + (256, 2, 5, 1536), # Tensor shape + ttnn.bfloat16, # Tensor dtype + ttnn.TILE_LAYOUT, # Tensor layout + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [320, 192], # shard shape + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (256, 2, 5, 1536), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [320, 192], + ttnn.ShardOrientation.ROW_MAJOR, + False, # halo + ), + ( + (256, 2, 5, 1536), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [320, 192], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 256, 2, 2304), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [64, 288], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 256, 2, 2304), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [64, 288], + ttnn.ShardOrientation.ROW_MAJOR, + False, # halo + ), + ( + (1, 256, 2, 2304), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [64, 288], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (32, 4, 8, 768), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [128, 96], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (32, 4, 8, 768), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [128, 96], + ttnn.ShardOrientation.ROW_MAJOR, + False, # halo + ), + ( + (32, 4, 8, 768), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [128, 96], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 25, 160, 32), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [32, 160], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 25, 160, 32), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [32, 160], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 2, 1248, 32), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [32, 1248], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 2, 1248, 32), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [32, 1248], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 2, 1472, 32), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [32, 1472], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 2, 1472, 32), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [32, 1472], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (2, 1, 224, 128), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [128, 224], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), +] + + +def nop(x, memory_config=None): + return x + + +@pytest.mark.parametrize( + "input_shape, dtype, dlayout, tensor_memory_layout, byffer_type, shard_grid, shard_shape, shard_orientation, halo", + (test_sweep_args), +) +def test_eltwise_nop( + input_shape, + dtype, + dlayout, + tensor_memory_layout, + byffer_type, + shard_grid, + shard_shape, + shard_orientation, + halo, + device, +): + run_tests( + input_shape, + dtype, + dlayout, + tensor_memory_layout, + byffer_type, + shard_grid, + shard_shape, + shard_orientation, + halo, + nop, + nop, + False, + device, + ) diff --git a/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp b/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp index 466d602d9908..14a421a30be4 100644 --- a/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp +++ b/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp @@ -1049,6 +1049,51 @@ INSTANTIATE_TEST_SUITE_P( CreateShardedTensorWithAlignmentExpected{ .physical_shape = Size{28, 9} } + }, + //////////////////////////////////////////////////////////////////// + // EXAMPLE 4: Some of block sharding failurs + //////////////////////////////////////////////////////////////////// + CreateShardedTensorWithAlignmentParams{ + CreateShardedTensorWithAlignmentInputs{ + .shape = SimpleShape{32, 4, 8, 768}, + .data_type = DataType::BFLOAT16, + .page_config = PageConfig(Layout::TILE), + .memory_config = + MemoryConfig{ + .memory_layout = TensorMemoryLayout::BLOCK_SHARDED, + .buffer_type = BufferType::L1, + .shard_spec = ShardSpec{ + num_cores_to_corerangeset(64, CoreCoord{8, 8}, /*row_wise=*/true), // tt::div_up(32 * 4 * 8, 128) * tt::div_up(768, 96) + {128, 96}, + ShardOrientation::ROW_MAJOR, + false, + ShardMode::PHYSICAL} + } + }, + CreateShardedTensorWithAlignmentExpected{ + .physical_size = Size{1024, 768} + } + }, + CreateShardedTensorWithAlignmentParams{ + CreateShardedTensorWithAlignmentInputs{ + .shape = SimpleShape{32, 4, 8, 768}, + .data_type = DataType::BFLOAT16, + .page_config = PageConfig(Layout::TILE), + .memory_config = + MemoryConfig{ + .memory_layout = TensorMemoryLayout::BLOCK_SHARDED, + .buffer_type = BufferType::L1, + .shard_spec = ShardSpec{ + num_cores_to_corerangeset(64, CoreCoord{8, 8}, /*row_wise=*/true), // tt::div_up(32 * 4 * 8, 128) * tt::div_up(768, 96) + {128, 96}, + ShardOrientation::COL_MAJOR, + false, + ShardMode::PHYSICAL} + } + }, + CreateShardedTensorWithAlignmentExpected{ + .physical_size = Size{1024, 768} + } } ) // Values // clang-format on From 738f9f533e89de38c994b1d07f9461e5a73360b1 Mon Sep 17 00:00:00 2001 From: Shwetank Singh <156869929+shwetankTT@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:15:29 +0530 Subject: [PATCH 19/87] #14977: conv config to use higher cores. (#15962) ### Ticket #14977 ### Problem description The convolution in these layers were using 8 cores only. Changed the config to row major so that they can have use more cores. This also improves performance as some of the tensor layout conversion are not required anymore. if the output is in bfloat16 and output_layout is ROW_MAJOR it can take partial tile as shard shape and will use all 64 cores. --- models/demos/segformer/tt/common.py | 3 +++ .../tt/ttnn_segformer_efficient_selfattention.py | 14 +++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/models/demos/segformer/tt/common.py b/models/demos/segformer/tt/common.py index d777116d2321..f2278ad58bfb 100644 --- a/models/demos/segformer/tt/common.py +++ b/models/demos/segformer/tt/common.py @@ -19,6 +19,7 @@ def __init__( activation="", groups=1, dtype=ttnn.bfloat8_b, + output_layout=ttnn.TILE_LAYOUT, ) -> None: self.weights = parameters["weight"] self.bias = parameters["bias"] @@ -35,6 +36,7 @@ def __init__( self.shard_layout = ( ttnn.TensorMemoryLayout.HEIGHT_SHARDED if height_sharding else ttnn.TensorMemoryLayout.BLOCK_SHARDED ) + self.output_layout = output_layout def __call__(self, device, input_tensor): conv_config = ttnn.Conv2dConfig( @@ -49,6 +51,7 @@ def __call__(self, device, input_tensor): reallocate_halo_output=True, enable_act_double_buffer=True, enable_split_reader=False, + output_layout=self.output_layout, ) compute_config = ttnn.init_device_compute_kernel_config( device.arch(), diff --git a/models/demos/segformer/tt/ttnn_segformer_efficient_selfattention.py b/models/demos/segformer/tt/ttnn_segformer_efficient_selfattention.py index b2d9e2f8fb2a..02797dfb9ad8 100644 --- a/models/demos/segformer/tt/ttnn_segformer_efficient_selfattention.py +++ b/models/demos/segformer/tt/ttnn_segformer_efficient_selfattention.py @@ -83,7 +83,7 @@ def __call__( strategy=mm_a_x_strategy, orientation=ttnn.ShardOrientation.ROW_MAJOR, ), - dtype=ttnn.bfloat8_b, + dtype=ttnn.bfloat16, ) query = ttnn.linear( hidden_states, @@ -91,7 +91,7 @@ def __call__( bias=parameters.query.bias, memory_config=mm_a_x_memory_config, core_grid=ttnn.CoreGrid(y=mm_a_y, x=mm_a_x), - dtype=ttnn.bfloat8_b, + dtype=ttnn.bfloat16, ) query = ttnn.to_memory_config(query, ttnn.L1_MEMORY_CONFIG, dtype=ttnn.bfloat8_b) @@ -110,15 +110,19 @@ def __call__( batch_size, __, seq_len, num_channels = hidden_states.shape # Need for RM input to reshape, then back to TILE after that - hidden_states = ttnn.to_memory_config(hidden_states, ttnn.L1_MEMORY_CONFIG, dtype=ttnn.bfloat8_b) + hidden_states = ttnn.to_memory_config(hidden_states, ttnn.L1_MEMORY_CONFIG, dtype=ttnn.bfloat16) hidden_states = ttnn.to_layout(hidden_states, layout=ttnn.ROW_MAJOR_LAYOUT) hidden_states = ttnn.reshape(hidden_states, (batch_size, height, width, num_channels)) - hidden_states = ttnn.to_layout(hidden_states, ttnn.TILE_LAYOUT) - hidden_states = ttnn.to_memory_config(hidden_states, ttnn.L1_MEMORY_CONFIG, dtype=ttnn.bfloat8_b) + if hidden_states.shape[3] == 160: + # conv config update + self.sr.output_layout = ttnn.TILE_LAYOUT + hidden_states = ttnn.to_layout(hidden_states, ttnn.TILE_LAYOUT) + hidden_states = ttnn.to_memory_config(hidden_states, ttnn.L1_MEMORY_CONFIG, dtype=ttnn.bfloat8_b) hidden_states, __, __ = self.sr(device, hidden_states) hidden_states = ttnn.to_memory_config(hidden_states, memory_config=ttnn.L1_MEMORY_CONFIG) + hidden_states = ttnn.to_layout(hidden_states, ttnn.TILE_LAYOUT) hidden_states = ttnn.layer_norm( hidden_states, weight=parameters.layer_norm.weight, From 8caba97148d832e1368c67c1a9633abd71e5490c Mon Sep 17 00:00:00 2001 From: Nemanja Grujic <109360083+nemanjagrujic@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:35:35 +0100 Subject: [PATCH 20/87] Revert "#15565 Add unit test to show sharding ttnn.from_torch problems (#15827)" This reverts commit 56836273314e149f230155f8e7e1f9d42dcecb64. --- .../wormhole/test_eltwise_block_shard_spec.py | 276 ------------------ .../tensor/test_sharding_with_alignment.cpp | 45 --- 2 files changed, 321 deletions(-) delete mode 100644 tests/ttnn/python_api_testing/non_working_unit_tests/wormhole/test_eltwise_block_shard_spec.py diff --git a/tests/ttnn/python_api_testing/non_working_unit_tests/wormhole/test_eltwise_block_shard_spec.py b/tests/ttnn/python_api_testing/non_working_unit_tests/wormhole/test_eltwise_block_shard_spec.py deleted file mode 100644 index 88f9a193b179..000000000000 --- a/tests/ttnn/python_api_testing/non_working_unit_tests/wormhole/test_eltwise_block_shard_spec.py +++ /dev/null @@ -1,276 +0,0 @@ -# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. - -# SPDX-License-Identifier: Apache-2.0 - -from loguru import logger -import random -import pytest -import torch -import ttnn - -from tests.ttnn.utils_for_testing import assert_with_pcc, check_with_pcc -from tests.ttnn.python_api_testing.sweep_tests import ttnn_ops -from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_rand_inf - -Y, X = (8, 8) - - -def run_tests( - input_shape, - dtype, - dlayout, - tensor_memory_layout, - byffer_type, - shard_grid, - shard_shape, - shard_orientation, - halo, - torch_op, - ttnn_op, - gen_infs, - device, -): - random.seed(0) - data_seed = random.randint(0, 20000000) - torch.manual_seed(data_seed) - - if gen_infs: - torch_input_tensor_a = gen_rand_inf(input_shape, low=-100, high=100) - else: - torch_input_tensor_a = torch.Tensor(size=input_shape).uniform_(-50, 50).to(torch.bfloat16) - - torch_output_tensor = torch_input_tensor_a - - shard_spec = ttnn.ShardSpec(shard_grid, shard_shape, shard_orientation, halo) - sharded_config = ttnn.MemoryConfig(tensor_memory_layout, byffer_type, shard_spec) - - input_tensor_a = ttnn.from_torch( - torch_input_tensor_a, - dtype=dtype, - layout=dlayout, - device=device, - memory_config=sharded_config, - ) - - output_tensor = input_tensor_a - output_tensor = ttnn.to_torch(output_tensor) - - [passed, message] = check_with_pcc(torch_output_tensor, output_tensor, 0.999) - assert passed, f"PCC={message}" - - -test_sweep_args = [ - ( - (256, 2, 5, 1536), # Tensor shape - ttnn.bfloat16, # Tensor dtype - ttnn.TILE_LAYOUT, # Tensor layout - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [320, 192], # shard shape - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), - ( - (256, 2, 5, 1536), - ttnn.bfloat16, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [320, 192], - ttnn.ShardOrientation.ROW_MAJOR, - False, # halo - ), - ( - (256, 2, 5, 1536), - ttnn.bfloat8_b, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [320, 192], - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), - ( - (1, 256, 2, 2304), - ttnn.bfloat16, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [64, 288], - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), - ( - (1, 256, 2, 2304), - ttnn.bfloat16, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [64, 288], - ttnn.ShardOrientation.ROW_MAJOR, - False, # halo - ), - ( - (1, 256, 2, 2304), - ttnn.bfloat8_b, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [64, 288], - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), - ( - (32, 4, 8, 768), - ttnn.bfloat16, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [128, 96], - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), - ( - (32, 4, 8, 768), - ttnn.bfloat16, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [128, 96], - ttnn.ShardOrientation.ROW_MAJOR, - False, # halo - ), - ( - (32, 4, 8, 768), - ttnn.bfloat8_b, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [128, 96], - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), - ( - (1, 25, 160, 32), - ttnn.bfloat16, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [32, 160], - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), - ( - (1, 25, 160, 32), - ttnn.bfloat8_b, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [32, 160], - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), - ( - (1, 2, 1248, 32), - ttnn.bfloat16, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [32, 1248], - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), - ( - (1, 2, 1248, 32), - ttnn.bfloat8_b, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [32, 1248], - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), - ( - (1, 2, 1472, 32), - ttnn.bfloat16, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [32, 1472], - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), - ( - (1, 2, 1472, 32), - ttnn.bfloat8_b, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [32, 1472], - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), - ( - (2, 1, 224, 128), - ttnn.bfloat8_b, - ttnn.TILE_LAYOUT, - ttnn.TensorMemoryLayout.BLOCK_SHARDED, - ttnn.BufferType.L1, - ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid - [128, 224], - ttnn.ShardOrientation.COL_MAJOR, - False, # halo - ), -] - - -def nop(x, memory_config=None): - return x - - -@pytest.mark.parametrize( - "input_shape, dtype, dlayout, tensor_memory_layout, byffer_type, shard_grid, shard_shape, shard_orientation, halo", - (test_sweep_args), -) -def test_eltwise_nop( - input_shape, - dtype, - dlayout, - tensor_memory_layout, - byffer_type, - shard_grid, - shard_shape, - shard_orientation, - halo, - device, -): - run_tests( - input_shape, - dtype, - dlayout, - tensor_memory_layout, - byffer_type, - shard_grid, - shard_shape, - shard_orientation, - halo, - nop, - nop, - False, - device, - ) diff --git a/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp b/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp index 14a421a30be4..466d602d9908 100644 --- a/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp +++ b/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp @@ -1049,51 +1049,6 @@ INSTANTIATE_TEST_SUITE_P( CreateShardedTensorWithAlignmentExpected{ .physical_shape = Size{28, 9} } - }, - //////////////////////////////////////////////////////////////////// - // EXAMPLE 4: Some of block sharding failurs - //////////////////////////////////////////////////////////////////// - CreateShardedTensorWithAlignmentParams{ - CreateShardedTensorWithAlignmentInputs{ - .shape = SimpleShape{32, 4, 8, 768}, - .data_type = DataType::BFLOAT16, - .page_config = PageConfig(Layout::TILE), - .memory_config = - MemoryConfig{ - .memory_layout = TensorMemoryLayout::BLOCK_SHARDED, - .buffer_type = BufferType::L1, - .shard_spec = ShardSpec{ - num_cores_to_corerangeset(64, CoreCoord{8, 8}, /*row_wise=*/true), // tt::div_up(32 * 4 * 8, 128) * tt::div_up(768, 96) - {128, 96}, - ShardOrientation::ROW_MAJOR, - false, - ShardMode::PHYSICAL} - } - }, - CreateShardedTensorWithAlignmentExpected{ - .physical_size = Size{1024, 768} - } - }, - CreateShardedTensorWithAlignmentParams{ - CreateShardedTensorWithAlignmentInputs{ - .shape = SimpleShape{32, 4, 8, 768}, - .data_type = DataType::BFLOAT16, - .page_config = PageConfig(Layout::TILE), - .memory_config = - MemoryConfig{ - .memory_layout = TensorMemoryLayout::BLOCK_SHARDED, - .buffer_type = BufferType::L1, - .shard_spec = ShardSpec{ - num_cores_to_corerangeset(64, CoreCoord{8, 8}, /*row_wise=*/true), // tt::div_up(32 * 4 * 8, 128) * tt::div_up(768, 96) - {128, 96}, - ShardOrientation::COL_MAJOR, - false, - ShardMode::PHYSICAL} - } - }, - CreateShardedTensorWithAlignmentExpected{ - .physical_size = Size{1024, 768} - } } ) // Values // clang-format on From 5381e4370ab2b67e8dd8a1fa3f938b222930a258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bojan=20Ro=C5=A1ko?= <156314064+broskoTT@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:08:13 +0100 Subject: [PATCH 21/87] [UMD] Removed set_*_params calls and constants (#15908) ### Ticket Related to https://github.com/tenstorrent/tt-metal/issues/13948 ### Problem description Related to UMD change: https://github.com/tenstorrent/tt-umd/pull/395 These parameters are defined by hardware. The only parameters to be defined by the client are membars. This change adds set_barrier_address_params accordingly. ### What's changed Removed set_device_dram_address_params and set_device_l1_address_params, and related constants. Replace with call to set_barrier_address_params. ### Checklist - [ ] All post-commit tests : https://github.com/tenstorrent/tt-metal/actions/runs/12361649190 - main generally unhealthy, I haven't seen new fails related to this change. - [x] Blackhole post-commit tests : https://github.com/tenstorrent/tt-metal/actions/runs/12361650912 - [ ] (Single-card) Model perf tests : https://github.com/tenstorrent/tt-metal/actions/runs/12361652594 - N300 job failing on main - [x] (Single-card) Device perf regressions : https://github.com/tenstorrent/tt-metal/actions/runs/12361654455 - [x] (T3K) T3000 unit tests : https://github.com/tenstorrent/tt-metal/actions/runs/12361656223 - [ ] (T3K) T3000 demo tests : https://github.com/tenstorrent/tt-metal/actions/runs/12361657485 - falcon job failing on main - [ ] (TG) TG unit tests : https://github.com/tenstorrent/tt-metal/actions/runs/12361658889 - unit test failing on main - [ ] (TG) TG demo tests : https://github.com/tenstorrent/tt-metal/actions/runs/12361660547 - failing on main - [ ] (TGG) TGG unit tests : https://github.com/tenstorrent/tt-metal/actions/runs/12361662343 - failing on main for a long time due to hugepages - [x] (TGG) TGG demo tests : https://github.com/tenstorrent/tt-metal/actions/runs/12361664160 --- tt_metal/llrt/blackhole/bh_hal_active_eth.cpp | 3 --- tt_metal/llrt/hal.hpp | 3 +-- tt_metal/llrt/tt_cluster.cpp | 12 ++++++------ tt_metal/llrt/tt_cluster.hpp | 2 -- tt_metal/llrt/wormhole/wh_hal_active_eth.cpp | 3 --- tt_metal/third_party/umd | 2 +- 6 files changed, 8 insertions(+), 17 deletions(-) diff --git a/tt_metal/llrt/blackhole/bh_hal_active_eth.cpp b/tt_metal/llrt/blackhole/bh_hal_active_eth.cpp index f6620851b279..bf86f5966287 100644 --- a/tt_metal/llrt/blackhole/bh_hal_active_eth.cpp +++ b/tt_metal/llrt/blackhole/bh_hal_active_eth.cpp @@ -44,8 +44,6 @@ HalCoreInfoType create_active_eth_mem_map() { mem_map_bases[static_cast(HalL1MemAddrType::GO_MSG)] = GET_ETH_MAILBOX_ADDRESS_HOST(go_message); mem_map_bases[static_cast(HalL1MemAddrType::LAUNCH_MSG_BUFFER_RD_PTR)] = GET_ETH_MAILBOX_ADDRESS_HOST(launch_msg_rd_ptr); - mem_map_bases[static_cast(HalL1MemAddrType::FW_VERSION_ADDR)] = - eth_l1_mem::address_map::FW_VERSION_ADDR; mem_map_bases[static_cast(HalL1MemAddrType::BANK_TO_NOC_SCRATCH)] = eth_l1_mem::address_map::ERISC_MEM_BANK_TO_NOC_SCRATCH; @@ -66,7 +64,6 @@ HalCoreInfoType create_active_eth_mem_map() { eth_l1_mem::address_map::MAX_SIZE - eth_l1_mem::address_map::ERISC_L1_UNRESERVED_BASE; mem_map_sizes[static_cast(HalL1MemAddrType::GO_MSG)] = sizeof(go_msg_t); mem_map_sizes[static_cast(HalL1MemAddrType::LAUNCH_MSG_BUFFER_RD_PTR)] = sizeof(std::uint32_t); - mem_map_sizes[static_cast(HalL1MemAddrType::FW_VERSION_ADDR)] = sizeof(std::uint32_t); mem_map_sizes[static_cast(HalL1MemAddrType::BANK_TO_NOC_SCRATCH)] = eth_l1_mem::address_map::ERISC_MEM_BANK_TO_NOC_SIZE; diff --git a/tt_metal/llrt/hal.hpp b/tt_metal/llrt/hal.hpp index 9344b6bd4ac5..bc23f59e2705 100644 --- a/tt_metal/llrt/hal.hpp +++ b/tt_metal/llrt/hal.hpp @@ -49,10 +49,9 @@ enum class HalL1MemAddrType : uint8_t { CORE_INFO, GO_MSG, LAUNCH_MSG_BUFFER_RD_PTR, - FW_VERSION_ADDR, // Really only applicable to active eth core right now LOCAL, BANK_TO_NOC_SCRATCH, - COUNT // Keep this last so it always indicates number of enum options + COUNT // Keep this last so it always indicates number of enum options }; enum class HalDramMemAddrType : uint8_t { DRAM_BARRIER = 0, COUNT = 1 }; diff --git a/tt_metal/llrt/tt_cluster.cpp b/tt_metal/llrt/tt_cluster.cpp index bf056fd446a6..12f144dd7cea 100644 --- a/tt_metal/llrt/tt_cluster.cpp +++ b/tt_metal/llrt/tt_cluster.cpp @@ -252,15 +252,15 @@ void Cluster::open_driver(const bool &skip_driver_allocs) { } else if (this->target_type_ == TargetDevice::Simulator) { device_driver = std::make_unique(sdesc_path); } - std::uint32_t dram_barrier_base = tt_metal::hal.get_dev_addr(tt_metal::HalDramMemAddrType::DRAM_BARRIER); - device_driver->set_device_dram_address_params(tt_device_dram_address_params{dram_barrier_base}); - l1_address_params.tensix_l1_barrier_base = tt_metal::hal.get_dev_addr(tt_metal::HalProgrammableCoreType::TENSIX, tt_metal::HalL1MemAddrType::BARRIER); + barrier_address_params barrier_params; + barrier_params.tensix_l1_barrier_base = tt_metal::hal.get_dev_addr(tt_metal::HalProgrammableCoreType::TENSIX, tt_metal::HalL1MemAddrType::BARRIER); + barrier_params.dram_barrier_base = tt_metal::hal.get_dev_addr(tt_metal::HalDramMemAddrType::DRAM_BARRIER); + if (tt_metal::hal.get_arch() != tt::ARCH::GRAYSKULL) { - l1_address_params.eth_l1_barrier_base = tt_metal::hal.get_dev_addr(tt_metal::HalProgrammableCoreType::ACTIVE_ETH, tt_metal::HalL1MemAddrType::BARRIER); - l1_address_params.fw_version_addr = tt_metal::hal.get_dev_addr(tt_metal::HalProgrammableCoreType::ACTIVE_ETH, tt_metal::HalL1MemAddrType::FW_VERSION_ADDR); + barrier_params.eth_l1_barrier_base = tt_metal::hal.get_dev_addr(tt_metal::HalProgrammableCoreType::ACTIVE_ETH, tt_metal::HalL1MemAddrType::BARRIER); } - device_driver->set_device_l1_address_params(l1_address_params); + device_driver->set_barrier_address_params(barrier_params); this->get_metal_desc_from_tt_desc( device_driver->get_virtual_soc_descriptors(), device_driver->get_harvesting_masks_for_soc_descriptors()); diff --git a/tt_metal/llrt/tt_cluster.hpp b/tt_metal/llrt/tt_cluster.hpp index 3b59f4cc7d06..629f23ddd23d 100644 --- a/tt_metal/llrt/tt_cluster.hpp +++ b/tt_metal/llrt/tt_cluster.hpp @@ -297,8 +297,6 @@ class Cluster { // Mapping of each devices' ethernet routing mode std::unordered_map> device_eth_routing_info_; - tt_device_l1_address_params l1_address_params; - std::unordered_map>> ethernet_sockets_; }; diff --git a/tt_metal/llrt/wormhole/wh_hal_active_eth.cpp b/tt_metal/llrt/wormhole/wh_hal_active_eth.cpp index c0af4cc0bd72..118140f01c16 100644 --- a/tt_metal/llrt/wormhole/wh_hal_active_eth.cpp +++ b/tt_metal/llrt/wormhole/wh_hal_active_eth.cpp @@ -41,8 +41,6 @@ HalCoreInfoType create_active_eth_mem_map() { mem_map_bases[static_cast(HalL1MemAddrType::GO_MSG)] = GET_ETH_MAILBOX_ADDRESS_HOST(go_message); mem_map_bases[static_cast(HalL1MemAddrType::LAUNCH_MSG_BUFFER_RD_PTR)] = GET_ETH_MAILBOX_ADDRESS_HOST(launch_msg_rd_ptr); - mem_map_bases[static_cast(HalL1MemAddrType::FW_VERSION_ADDR)] = - eth_l1_mem::address_map::FW_VERSION_ADDR; mem_map_bases[static_cast(HalL1MemAddrType::BANK_TO_NOC_SCRATCH)] = eth_l1_mem::address_map::ERISC_MEM_BANK_TO_NOC_SCRATCH; @@ -63,7 +61,6 @@ HalCoreInfoType create_active_eth_mem_map() { eth_l1_mem::address_map::MAX_SIZE - eth_l1_mem::address_map::ERISC_L1_UNRESERVED_BASE; mem_map_sizes[static_cast(HalL1MemAddrType::GO_MSG)] = sizeof(go_msg_t); mem_map_sizes[static_cast(HalL1MemAddrType::LAUNCH_MSG_BUFFER_RD_PTR)] = sizeof(uint32_t); - mem_map_sizes[static_cast(HalL1MemAddrType::FW_VERSION_ADDR)] = sizeof(std::uint32_t); mem_map_sizes[static_cast(HalL1MemAddrType::BANK_TO_NOC_SCRATCH)] = eth_l1_mem::address_map::ERISC_MEM_BANK_TO_NOC_SIZE; std::vector> processor_classes(NumEthDispatchClasses); diff --git a/tt_metal/third_party/umd b/tt_metal/third_party/umd index 7da601738873..cfadca1bbbab 160000 --- a/tt_metal/third_party/umd +++ b/tt_metal/third_party/umd @@ -1 +1 @@ -Subproject commit 7da601738873f11c0fb946e590e957f5b6c8a8a9 +Subproject commit cfadca1bbbabf2d9cf3ab038322d7416d8059ff3 From 3c8facf59046ee0aef04dd6c47728e8e2bb8451e Mon Sep 17 00:00:00 2001 From: Raymond Kim Date: Tue, 17 Dec 2024 09:06:03 -0500 Subject: [PATCH 22/87] #0: [skip ci] Bump yolov4 perf threshold for stability --- models/demos/yolov4/tests/test_perf_yolo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/demos/yolov4/tests/test_perf_yolo.py b/models/demos/yolov4/tests/test_perf_yolo.py index 1a212ff25382..6b7799af08af 100644 --- a/models/demos/yolov4/tests/test_perf_yolo.py +++ b/models/demos/yolov4/tests/test_perf_yolo.py @@ -30,7 +30,7 @@ def get_expected_compile_time_sec(): def get_expected_inference_time_sec(): - return 0.21 + return 0.23 @pytest.mark.models_performance_bare_metal From 52f05f3fff71179020482d81f854148a40f8f1b9 Mon Sep 17 00:00:00 2001 From: Raymond Kim Date: Tue, 17 Dec 2024 14:31:32 +0000 Subject: [PATCH 23/87] #0: Revert "#13643: Extend binary-ng math support to match all primitive binary ops" This reverts commit 25c17eb5de8bb6a2c002714d6238d7d8acd211ba to fix post-commit. --- .../operations/eltwise/test_binary_bcast.py | 81 +------ ttnn/CMakeLists.txt | 1 - .../eltwise/binary_ng/binary_ng.cpp | 17 -- .../eltwise/binary_ng/binary_ng.hpp | 68 ------ .../eltwise/binary_ng/binary_ng_pybind.cpp | 35 +-- .../eltwise/binary_ng/binary_ng_pybind.hpp | 4 +- .../device/binary_ng_device_operation.cpp | 24 +- .../device/binary_ng_program_factory.cpp | 146 ++++++++++--- .../binary_ng/device/binary_ng_utils.cpp | 206 ------------------ .../binary_ng/device/binary_ng_utils.hpp | 72 ------ .../device/kernels/compute/eltwise_binary.cpp | 80 ++----- .../compute/eltwise_binary_no_bcast.cpp | 64 ++---- .../kernels/compute/eltwise_binary_scalar.cpp | 60 ++--- .../kernels/compute/eltwise_defines.hpp | 26 --- .../device/kernels/compute/eltwise_utils.hpp | 41 ---- .../operations/eltwise/binary_ng/types.hpp | 5 +- ttnn/ttnn/operations/binary_ng.py | 30 --- 17 files changed, 194 insertions(+), 766 deletions(-) delete mode 100644 ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.cpp delete mode 100644 ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.hpp delete mode 100644 ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_defines.hpp delete mode 100644 ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_utils.hpp delete mode 100644 ttnn/ttnn/operations/binary_ng.py diff --git a/tests/ttnn/unit_tests/operations/eltwise/test_binary_bcast.py b/tests/ttnn/unit_tests/operations/eltwise/test_binary_bcast.py index 34774b95cc44..7f6f592bf6df 100644 --- a/tests/ttnn/unit_tests/operations/eltwise/test_binary_bcast.py +++ b/tests/ttnn/unit_tests/operations/eltwise/test_binary_bcast.py @@ -5,10 +5,8 @@ import torch import pytest import ttnn - -from tests.ttnn.unit_tests.operations.eltwise.backward.utility_funcs import ( - compare_pcc, -) +import random +from tests.ttnn.unit_tests.operations.eltwise.backward.utility_funcs import data_gen_with_range, compare_pcc @pytest.mark.parametrize( @@ -19,30 +17,7 @@ (torch.Size([5, 1, 1, 64]), torch.Size([1, 3, 128, 1])), ), ) -@pytest.mark.parametrize( - "ttnn_fn", - [ - ttnn.experimental.gte, - ttnn.experimental.gt, - ttnn.experimental.lte, - ttnn.experimental.lt, - ttnn.experimental.eq, - ttnn.experimental.ne, - ttnn.experimental.logical_and, - ttnn.experimental.logical_or, - ttnn.experimental.logical_xor, - ttnn.experimental.ldexp, - ttnn.experimental.logaddexp, - ttnn.experimental.logaddexp2, - ttnn.experimental.squared_difference, - ttnn.experimental.add, - ttnn.experimental.sub, - ttnn.experimental.mul, - ttnn.experimental.div, - ttnn.experimental.bias_gelu, - ], -) -def test_binary_scalar_ops(input_shapes, ttnn_fn, device): +def test_binary_scalar_ops(input_shapes, device): a_shape, b_shape = input_shapes a_pt = torch.rand(a_shape).bfloat16() b_pt = torch.rand(b_shape).bfloat16() @@ -50,59 +25,13 @@ def test_binary_scalar_ops(input_shapes, ttnn_fn, device): a_tt = ttnn.from_torch(a_pt, device=device, layout=ttnn.TILE_LAYOUT, memory_config=ttnn.DRAM_MEMORY_CONFIG) b_tt = ttnn.from_torch(b_pt, device=device, layout=ttnn.TILE_LAYOUT, memory_config=ttnn.DRAM_MEMORY_CONFIG) cq_id = 0 - out_tt = ttnn_fn(a_tt, b_tt, queue_id=cq_id) - golden_fn = ttnn.get_golden_function(ttnn_fn) - out_pt = golden_fn(a_pt, b_pt) + out_tt = ttnn.experimental.add(a_tt, b_tt, queue_id=cq_id) + out_pt = a_pt + b_pt comp_pass = compare_pcc([out_tt], [out_pt]) assert comp_pass -@pytest.mark.parametrize( - "input_shapes", - ( - (torch.Size([1, 1, 31, 32]), torch.Size([5, 3, 32, 32])), - (torch.Size([5, 2, 64, 1]), torch.Size([1, 3, 1, 128])), - (torch.Size([5, 1, 1, 64]), torch.Size([2, 3, 128, 1])), - ), -) -@pytest.mark.parametrize( - "ttnn_fn", - [ - ttnn.experimental.gte, - ttnn.experimental.gt, - ttnn.experimental.lte, - ttnn.experimental.lt, - ttnn.experimental.eq, - ttnn.experimental.ne, - ttnn.experimental.logical_and, - ttnn.experimental.logical_or, - ttnn.experimental.logical_xor, - ttnn.experimental.ldexp, - ttnn.experimental.logaddexp, - ttnn.experimental.logaddexp2, - ttnn.experimental.squared_difference, - ttnn.experimental.add, - ttnn.experimental.sub, - ttnn.experimental.mul, - ttnn.experimental.div, - ttnn.experimental.bias_gelu, - ], -) -def test_binary_scalar_ops_invalid_bcast(input_shapes, ttnn_fn, device): - a_shape, b_shape = input_shapes - a_pt = torch.rand(a_shape).bfloat16() - b_pt = torch.rand(b_shape).bfloat16() - - a_tt = ttnn.from_torch(a_pt, device=device, layout=ttnn.TILE_LAYOUT, memory_config=ttnn.DRAM_MEMORY_CONFIG) - b_tt = ttnn.from_torch(b_pt, device=device, layout=ttnn.TILE_LAYOUT, memory_config=ttnn.DRAM_MEMORY_CONFIG) - - with pytest.raises(RuntimeError) as e: - cq_id = 0 - _ = ttnn_fn(a_tt, b_tt, queue_id=cq_id) - assert "Broadcasting rule violation" in str(e.value) - - @pytest.mark.parametrize( "shapes", [ diff --git a/ttnn/CMakeLists.txt b/ttnn/CMakeLists.txt index a3bcf32d425d..cf777f35fd72 100644 --- a/ttnn/CMakeLists.txt +++ b/ttnn/CMakeLists.txt @@ -123,7 +123,6 @@ set(ALL_TTNN_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_device_operation.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_program_factory.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary/binary.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary/common/binary_op_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.cpp diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.cpp index 1439186fbd4a..7e476e293b2e 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.cpp @@ -52,22 +52,5 @@ Tensor BinaryNg::invoke( } template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; -template struct BinaryNg; } // namespace ttnn::operations::binary_ng diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.hpp index d41261f60b94..ac1faf4a4e7e 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng.hpp @@ -49,72 +49,4 @@ namespace ttnn::experimental { constexpr auto add = ttnn::register_operation_with_auto_launch_op< "ttnn::experimental::add", ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto sub = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::sub", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto mul = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::mul", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto div = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::div", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto eq = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::eq", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto ne = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::ne", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto gt = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::gt", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto gte = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::gte", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto lt = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::lt", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto lte = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::lte", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto squared_difference = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::squared_difference", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto bias_gelu = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::bias_gelu", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto logical_and = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::logical_and", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto logical_or = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::logical_or", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto logical_xor = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::logical_xor", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto ldexp = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::ldexp", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto logaddexp = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::logaddexp", - ttnn::operations::binary_ng::BinaryNg>(); - -constexpr auto logaddexp2 = ttnn::register_operation_with_auto_launch_op< - "ttnn::experimental::logaddexp2", - ttnn::operations::binary_ng::BinaryNg>(); } diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.cpp index da264795941e..ab6c28e4421d 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.cpp @@ -9,16 +9,17 @@ namespace ttnn::operations::binary_ng { namespace detail { -template -void bind_binary_ng_operation(py::module& module, T op, const std::string& docstring) { +void bind_binary_ng_operation(py::module& module) { + using OperationType = decltype(ttnn::experimental::add); + bind_registered_operation( module, - op, - docstring, + ttnn::experimental::add, + "Binary Add Ng Operation", // tensor and scalar ttnn::pybind_overload_t{ - [](const T& self, + [](const OperationType& self, const ttnn::Tensor& input_tensor_a, const float scalar, const std::optional& dtype, @@ -37,7 +38,7 @@ void bind_binary_ng_operation(py::module& module, T op, const std::string& docst // tensor and tensor ttnn::pybind_overload_t{ - [](const T& self, + [](const OperationType& self, const ttnn::Tensor& input_tensor_a, const ttnn::Tensor& input_tensor_b, const std::optional& dtype, @@ -56,25 +57,5 @@ void bind_binary_ng_operation(py::module& module, T op, const std::string& docst } } // namespace detail -void py_module(py::module& module) { - detail::bind_binary_ng_operation(module, ttnn::experimental::add, "Binary Add Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::sub, "Binary Sub Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::mul, "Binary Mul Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::div, "Binary Div Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::gt, "Binary Greater Than Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::lt, "Binary Less Than Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::lte, "Binary Less Than or Equal To Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::gte, "Binary Greater Than or Equal To Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::eq, "Binary Equal Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::ne, "Binary Not Equal Operation"); - detail::bind_binary_ng_operation( - module, ttnn::experimental::squared_difference, "Binary Squared Difference Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::bias_gelu, "Binary Bias GELU Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::logical_and, "Binary Logical And Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::logical_or, "Binary Logical Or Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::logical_xor, "Binary Logical Xor Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::ldexp, "Binary Ldexp Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::logaddexp, "Binary Logaddexp Operation"); - detail::bind_binary_ng_operation(module, ttnn::experimental::logaddexp2, "Binary Logaddexp2 Operation"); -} +void py_module(py::module& module) { detail::bind_binary_ng_operation(module); } } // namespace ttnn::operations::eltwise::binary_ng diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.hpp index b8cc769c7800..3a417d010cb3 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/binary_ng_pybind.hpp @@ -5,14 +5,12 @@ #pragma once #include "pybind11/pybind_fwd.hpp" -#include namespace py = pybind11; namespace ttnn::operations::binary_ng { namespace detail { -template -void bind_binary_ng_operation(py::module& module, T op, const std::string& docstring); +void bind_binary_ng_operation(py::module& module); } void py_module(py::module& module); diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_device_operation.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_device_operation.cpp index b55c8a8cccd8..0279ff3734ee 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_device_operation.cpp @@ -90,18 +90,18 @@ void BinaryNgDeviceOperation::validate_on_program_cache_hit( const auto input_shape_b = tensor_args.input_tensor_b.has_value() ? tensor_args.input_tensor_b->get_logical_shape() : ttnn::Shape{1, 1}; - const int rank_a = input_shape_a.rank(); - const int rank_b = input_shape_b.rank(); - const int larger_rank = std::max(rank_a, rank_b); - for (int i = -1; i >= -larger_rank; --i) { - auto a_dim = (i >= -rank_a) ? input_shape_a[i] : 1; - auto b_dim = (i >= -rank_b) ? input_shape_b[i] : 1; - TT_FATAL( - a_dim == b_dim || a_dim == 1 || b_dim == 1, - "Broadcasting rule violation for rank {}, dim a: {}, dim b: {}", - i, - a_dim, - b_dim); + constexpr int max_rank = 4; + if (input_shape_a.rank() > 0 && input_shape_b.rank() > 0) { + for (int i = 1; i <= max_rank; i++) { + auto a_dim = i <= input_shape_a.rank() ? input_shape_a[-i] : 1; + auto b_dim = i <= input_shape_b.rank() ? input_shape_b[-i] : 1; + TT_FATAL( + a_dim == b_dim || a_dim == 1 || b_dim == 1, + "Broadcasting rule violation for rank {}, dim a: {}, dim b: {}", + i, + a_dim, + b_dim); + } } } diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_program_factory.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_program_factory.cpp index d1125e8238f4..bce5cfdc8242 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_program_factory.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_program_factory.cpp @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -#include "binary_ng_utils.hpp" +#include "binary_ng_device_operation.hpp" #include "tt_metal/common/work_split.hpp" #include "ttnn/operations/cb_utils.hpp" @@ -17,6 +17,124 @@ std::tuple extract_shape_dims(const Tens return {shape[-4], shape[-3], shape[-2] / tile.get_height(), shape[-1] / tile.get_width()}; } +enum class KernelName { + ReaderNoBcast, + ReaderRowBcast, + ReaderColBcast, + ReaderScalarBcast, + WriterNoBcast, + WriterRowBcast, + WriterColBcast, + WriterScalarBcast, + WriterScalar, + ComputeNoBcast, + ComputeBcast, + ComputeScalar +}; + +struct BinaryNgKernelConfig { + BinaryNgKernelConfig(SubtileBroadcastType subtile_broadcast_type) { + switch (subtile_broadcast_type) { + case SubtileBroadcastType::NONE: + reader_kernel = KernelName::ReaderNoBcast; + compute_kernel = KernelName::ComputeNoBcast; + writer_kernel = KernelName::WriterNoBcast; + bcast_input = std::nullopt; + break; + + case SubtileBroadcastType::SCALAR_A: + reader_kernel = KernelName::ReaderScalarBcast; + compute_kernel = KernelName::ComputeBcast; + writer_kernel = KernelName::WriterNoBcast; + bcast_input = 0; + break; + + case SubtileBroadcastType::SCALAR_B: + reader_kernel = KernelName::ReaderNoBcast; + compute_kernel = KernelName::ComputeBcast; + writer_kernel = KernelName::WriterScalarBcast; + bcast_input = 1; + break; + + case SubtileBroadcastType::ROW_A: + reader_kernel = KernelName::ReaderRowBcast; + compute_kernel = KernelName::ComputeNoBcast; + writer_kernel = KernelName::WriterNoBcast; + bcast_input = std::nullopt; + break; + + case SubtileBroadcastType::ROW_B: + reader_kernel = KernelName::ReaderNoBcast; + compute_kernel = KernelName::ComputeNoBcast; + writer_kernel = KernelName::WriterRowBcast; + bcast_input = std::nullopt; + break; + + case SubtileBroadcastType::COL_A: + reader_kernel = KernelName::ReaderColBcast; + compute_kernel = KernelName::ComputeBcast; + writer_kernel = KernelName::WriterNoBcast; + bcast_input = 0; + break; + + case SubtileBroadcastType::COL_B: + reader_kernel = KernelName::ReaderNoBcast; + compute_kernel = KernelName::ComputeBcast; + writer_kernel = KernelName::WriterColBcast; + bcast_input = 1; + break; + + case SubtileBroadcastType::ROW_A_COL_B: + reader_kernel = KernelName::ReaderRowBcast; + compute_kernel = KernelName::ComputeBcast; + writer_kernel = KernelName::WriterColBcast; + bcast_input = 1; + break; + + case SubtileBroadcastType::ROW_B_COL_A: + reader_kernel = KernelName::ReaderColBcast; + compute_kernel = KernelName::ComputeBcast; + writer_kernel = KernelName::WriterRowBcast; + bcast_input = 0; + break; + } + } + + std::string bcast_input_str() const { + if (bcast_input.has_value()) { + return std::to_string(*bcast_input); + } + return ""; + } + + KernelName reader_kernel; + KernelName compute_kernel; + KernelName writer_kernel; + std::optional bcast_input; +}; + +std::string get_kernel_file_path(KernelName kernel_name) { + constexpr std::string_view root = "ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels"; + constexpr std::string_view dataflow = "{}/dataflow/{}"; + constexpr std::string_view compute = "{}/compute/{}"; + + switch (kernel_name) { + case KernelName::ReaderNoBcast: return fmt::format(dataflow, root, "reader_interleaved_no_bcast.cpp"); + case KernelName::ReaderRowBcast: return fmt::format(dataflow, root, "reader_interleaved_row_bcast.cpp"); + case KernelName::ReaderColBcast: return fmt::format(dataflow, root, "reader_interleaved_col_bcast.cpp"); + case KernelName::ReaderScalarBcast: return fmt::format(dataflow, root, "reader_interleaved_scalar_bcast.cpp"); + case KernelName::WriterNoBcast: return fmt::format(dataflow, root, "writer_interleaved_no_bcast.cpp"); + case KernelName::WriterRowBcast: return fmt::format(dataflow, root, "writer_interleaved_row_bcast.cpp"); + case KernelName::WriterColBcast: return fmt::format(dataflow, root, "writer_interleaved_col_bcast.cpp"); + case KernelName::WriterScalarBcast: return fmt::format(dataflow, root, "writer_interleaved_scalar_bcast.cpp"); + case KernelName::WriterScalar: return fmt::format(dataflow, root, "writer_interleaved_scalar.cpp"); + case KernelName::ComputeNoBcast: return fmt::format(compute, root, "eltwise_binary_no_bcast.cpp"); + case KernelName::ComputeBcast: return fmt::format(compute, root, "eltwise_binary.cpp"); + case KernelName::ComputeScalar: return fmt::format(compute, root, "eltwise_binary_scalar.cpp"); + default: __builtin_unreachable(); // GCC 12 doesn't compile even though we exhaustively match + } +} + std::tuple calculate_compute_kernel_args( SubtileBroadcastType broadcast_type, uint32_t start_tile_id, uint32_t HtWt, uint32_t Wt) { uint32_t start_t = start_tile_id % HtWt; @@ -164,8 +282,6 @@ BinaryNgDeviceOperation::ProgramFactory::cached_program_t BinaryNgDeviceOperatio auto b_data_format = b.has_value() ? datatype_to_dataformat_converter(b->get_dtype()) : DataFormat::Float16_b; auto c_data_format = datatype_to_dataformat_converter(c.get_dtype()); - tt::DataFormat b_intermediate_format = b_data_format; - uint32_t a_single_tile_size = tt_metal::detail::TileSize(a_data_format); uint32_t b_single_tile_size = tt_metal::detail::TileSize(b_data_format); uint32_t c_single_tile_size = tt_metal::detail::TileSize(c_data_format); @@ -183,23 +299,10 @@ BinaryNgDeviceOperation::ProgramFactory::cached_program_t BinaryNgDeviceOperatio Buffer* b_buffer = nullptr; Buffer* c_buffer = c.buffer(); - auto op_type = operation_attributes.binary_op_type; - auto compute_kernel_defines = OpConfig(op_type).as_defines(); - bool op_has_exp = - op_type == BinaryOpType::LOGADDEXP || op_type == BinaryOpType::LDEXP || op_type == BinaryOpType::LOGADDEXP2; - // How many tiles to store per input CB (double buffer) constexpr uint32_t num_tiles_per_cb = 2; auto [a_cb, a_cb_handle] = create_cb(tt::CBIndex::c_0, program, all_device_cores, a_single_tile_size, num_tiles_per_cb, a_data_format); - - if (compute_kernel_defines.find("PREPROCESS_A_INIT") != compute_kernel_defines.end()) { - auto a_intermediate_format = op_has_exp ? tt::DataFormat::Float16_b : a_data_format; - uint32_t a_intermediate_single_tile_size = tt_metal::detail::TileSize(a_intermediate_format); - auto [a_cb_interim, a_cb_interim_handle] = create_cb( - tt::CBIndex::c_3, program, all_device_cores, a_intermediate_single_tile_size, 1, a_intermediate_format); - } - auto [c_cb, c_cb_handle] = create_cb(tt::CBIndex::c_2, program, all_device_cores, c_single_tile_size, num_tiles_per_cb, c_data_format); @@ -208,13 +311,6 @@ BinaryNgDeviceOperation::ProgramFactory::cached_program_t BinaryNgDeviceOperatio auto [b_cb, b_cb_handle] = create_cb(tt::CBIndex::c_1, program, all_device_cores, b_single_tile_size, b_num_tiles_per_cb, b_data_format); - if (compute_kernel_defines.find("PREPROCESS_B_INIT") != compute_kernel_defines.end()) { - auto b_intermediate_format = op_has_exp ? tt::DataFormat::Float16_b : b_data_format; - uint32_t b_intermediate_single_tile_size = tt_metal::detail::TileSize(b_intermediate_format); - auto [b_cb_interim, b_cb_interim_handle] = create_cb( - tt::CBIndex::c_4, program, all_device_cores, b_intermediate_single_tile_size, 1, b_intermediate_format); - } - auto a_is_dram = static_cast(a_buffer->buffer_type() == tt_metal::BufferType::DRAM); bool b_is_dram = false; auto c_is_dram = static_cast(c_buffer->buffer_type() == tt_metal::BufferType::DRAM); @@ -251,12 +347,12 @@ BinaryNgDeviceOperation::ProgramFactory::cached_program_t BinaryNgDeviceOperatio // Compute kernel needs to know which op it's going to perform // This has to be passed as a compile-time argument // For now we're just going to do addition - compute_kernel_defines["BCAST_INPUT"] = kernel_config.bcast_input_str(); auto compute_kernel_id = tt_metal::CreateKernel( program, get_kernel_file_path(compute_kernel), all_device_cores, - tt_metal::ComputeConfig{.fp32_dest_acc_en = fp32_dest_acc_en, .defines = compute_kernel_defines}); + tt_metal::ComputeConfig{ + .fp32_dest_acc_en = fp32_dest_acc_en, .defines = {{"BCAST_INPUT", kernel_config.bcast_input_str()}}}); auto set_runtime_args = [](Program& program, KernelHandle kernel_id, CoreCoord core, auto&& args) { tt_metal::SetRuntimeArgs(program, kernel_id, core, args); diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.cpp deleted file mode 100644 index 3671cd9d10f8..000000000000 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.cpp +++ /dev/null @@ -1,206 +0,0 @@ -// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. -// -// SPDX-License-Identifier: Apache-2.0 - -#include "binary_ng_utils.hpp" - -#include -#include -#include - -template <> -struct fmt::formatter : fmt::formatter { - auto format(ttnn::operations::binary_ng::Lowercase const& value, fmt::format_context& ctx) const { - auto out = ctx.out(); - for (char c : value.view) { - *out++ = std::tolower(static_cast(c)); - } - return out; - } -}; - -namespace ttnn::operations::binary_ng { - -BinaryNgKernelConfig::BinaryNgKernelConfig(SubtileBroadcastType subtile_broadcast_type) { - switch (subtile_broadcast_type) { - case SubtileBroadcastType::NONE: - reader_kernel = KernelName::ReaderNoBcast; - compute_kernel = KernelName::ComputeNoBcast; - writer_kernel = KernelName::WriterNoBcast; - bcast_input = std::nullopt; - break; - - case SubtileBroadcastType::SCALAR_A: - reader_kernel = KernelName::ReaderScalarBcast; - compute_kernel = KernelName::ComputeBcast; - writer_kernel = KernelName::WriterNoBcast; - bcast_input = 0; - break; - - case SubtileBroadcastType::SCALAR_B: - reader_kernel = KernelName::ReaderNoBcast; - compute_kernel = KernelName::ComputeBcast; - writer_kernel = KernelName::WriterScalarBcast; - bcast_input = 1; - break; - - case SubtileBroadcastType::ROW_A: - reader_kernel = KernelName::ReaderRowBcast; - compute_kernel = KernelName::ComputeNoBcast; - writer_kernel = KernelName::WriterNoBcast; - bcast_input = std::nullopt; - break; - - case SubtileBroadcastType::ROW_B: - reader_kernel = KernelName::ReaderNoBcast; - compute_kernel = KernelName::ComputeNoBcast; - writer_kernel = KernelName::WriterRowBcast; - bcast_input = std::nullopt; - break; - - case SubtileBroadcastType::COL_A: - reader_kernel = KernelName::ReaderColBcast; - compute_kernel = KernelName::ComputeBcast; - writer_kernel = KernelName::WriterNoBcast; - bcast_input = 0; - break; - - case SubtileBroadcastType::COL_B: - reader_kernel = KernelName::ReaderNoBcast; - compute_kernel = KernelName::ComputeBcast; - writer_kernel = KernelName::WriterColBcast; - bcast_input = 1; - break; - - case SubtileBroadcastType::ROW_A_COL_B: - reader_kernel = KernelName::ReaderRowBcast; - compute_kernel = KernelName::ComputeBcast; - writer_kernel = KernelName::WriterColBcast; - bcast_input = 1; - break; - - case SubtileBroadcastType::ROW_B_COL_A: - reader_kernel = KernelName::ReaderColBcast; - compute_kernel = KernelName::ComputeBcast; - writer_kernel = KernelName::WriterRowBcast; - bcast_input = 0; - break; - } -} - -std::string BinaryNgKernelConfig::bcast_input_str() const { - if (bcast_input.has_value()) { - return std::to_string(*bcast_input); - } - return ""; -} - -std::string get_kernel_file_path(KernelName kernel_name) { - constexpr std::string_view root = "ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels"; - constexpr std::string_view dataflow = "{}/dataflow/{}"; - constexpr std::string_view compute = "{}/compute/{}"; - - switch (kernel_name) { - case KernelName::ReaderNoBcast: return fmt::format(dataflow, root, "reader_interleaved_no_bcast.cpp"); - case KernelName::ReaderRowBcast: return fmt::format(dataflow, root, "reader_interleaved_row_bcast.cpp"); - case KernelName::ReaderColBcast: return fmt::format(dataflow, root, "reader_interleaved_col_bcast.cpp"); - case KernelName::ReaderScalarBcast: return fmt::format(dataflow, root, "reader_interleaved_scalar_bcast.cpp"); - case KernelName::WriterNoBcast: return fmt::format(dataflow, root, "writer_interleaved_no_bcast.cpp"); - case KernelName::WriterRowBcast: return fmt::format(dataflow, root, "writer_interleaved_row_bcast.cpp"); - case KernelName::WriterColBcast: return fmt::format(dataflow, root, "writer_interleaved_col_bcast.cpp"); - case KernelName::WriterScalarBcast: return fmt::format(dataflow, root, "writer_interleaved_scalar_bcast.cpp"); - case KernelName::WriterScalar: return fmt::format(dataflow, root, "writer_interleaved_scalar.cpp"); - case KernelName::ComputeNoBcast: return fmt::format(compute, root, "eltwise_binary_no_bcast.cpp"); - case KernelName::ComputeBcast: return fmt::format(compute, root, "eltwise_binary.cpp"); - case KernelName::ComputeScalar: return fmt::format(compute, root, "eltwise_binary_scalar.cpp"); - default: __builtin_unreachable(); // GCC 12 doesn't compile even though we exhaustively match - } -} - -constexpr OpConfig::SfpuConfig NezConfig("nez_tile_init", "nez_tile(i)"); -constexpr OpConfig::SfpuConfig GtzConfig("gtz_tile_init", "gtz_tile(i)"); - -OpConfig::OpConfig(BinaryOpType binary_op_type) { - fpu_binary_op = FpuBinaryOp::SUB; - switch (binary_op_type) { - case BinaryOpType::ADD: fpu_binary_op = FpuBinaryOp::ADD; break; - case BinaryOpType::SUB: break; - case BinaryOpType::MUL: fpu_binary_op = FpuBinaryOp::MUL; break; - case BinaryOpType::DIV: - preprocess_b = SfpuConfig("recip_tile_init", "recip_tile(i)", "compute_kernel_api/eltwise_unary/recip.h"); - fpu_binary_op = FpuBinaryOp::MUL; - break; - case BinaryOpType::GT: postprocess = GtzConfig; break; - case BinaryOpType::LT: postprocess = SfpuConfig("ltz_tile_init", "ltz_tile(i)"); break; - case BinaryOpType::GTE: postprocess = SfpuConfig("gez_tile_init", "gez_tile(i)"); break; - case BinaryOpType::LTE: postprocess = SfpuConfig("lez_tile_init", "lez_tile(i)"); break; - case BinaryOpType::EQ: postprocess = SfpuConfig("eqz_tile_init", "eqz_tile(i)"); break; - case BinaryOpType::NE: postprocess = NezConfig; break; - case BinaryOpType::SQUARED_DIFFERENCE: postprocess = SfpuConfig("square_tile_init", "square_tile(i)"); break; - case BinaryOpType::BIAS_GELU: - fpu_binary_op = FpuBinaryOp::ADD; - preprocess_a = - SfpuConfig("gelu_tile_init", "gelu_tile(i)", "compute_kernel_api/eltwise_unary/gelu.h"); - break; - case BinaryOpType::LOGICAL_AND: - fpu_binary_op = FpuBinaryOp::MUL; - postprocess = NezConfig; - break; - case BinaryOpType::LOGICAL_OR: - fpu_binary_op = FpuBinaryOp::ADD; - preprocess_a = NezConfig; - preprocess_b = NezConfig; - postprocess = GtzConfig; - break; - case BinaryOpType::LOGICAL_XOR: - preprocess_a = NezConfig; - preprocess_b = NezConfig; - postprocess = NezConfig; - break; - case BinaryOpType::LDEXP: - fpu_binary_op = FpuBinaryOp::MUL; - preprocess_b = SfpuConfig("exp2_tile_init", "exp2_tile(i)"); - break; - case BinaryOpType::LOGADDEXP: - fpu_binary_op = FpuBinaryOp::ADD; - preprocess_a = - SfpuConfig("exp_tile_init", "exp_tile(i)", "compute_kernel_api/eltwise_unary/exp.h"); - preprocess_b = preprocess_a; - postprocess = SfpuConfig("log_tile_init", "log_tile(i)"); - break; - case BinaryOpType::LOGADDEXP2: - fpu_binary_op = FpuBinaryOp::ADD; - preprocess_a = SfpuConfig("exp2_tile_init", "exp2_tile(i)"); - preprocess_b = preprocess_a; - postprocess = SfpuConfig("log_with_base_tile_init", "log_with_base_tile(i, 0x3dc5u);"); - break; - default: __builtin_unreachable(); - } -} - -std::map OpConfig::SfpuConfig::as_defines(std::string_view prefix) const { - if (init.empty()) { - return {}; - } - - std::map defines; - defines[fmt::format("{}_INIT", prefix)] = init; - defines[fmt::format("{}_APPLY(i)", prefix)] = apply; - defines[fmt::format("{}_INCLUDE", prefix)] = include; - return defines; -} - -std::map OpConfig::as_defines() const { - std::map defines; - defines.merge(preprocess_a.as_defines("PREPROCESS_A")); - defines.merge(preprocess_b.as_defines("PREPROCESS_B")); - defines.merge(postprocess.as_defines("POSTPROCESS")); - - auto binary_op_str = magic_enum::enum_name(fpu_binary_op); - defines["BINARY_OP"] = fmt::format("{}_tiles", Lowercase{binary_op_str}); - defines["BINARY_OP_TYPE"] = fmt::format("EltwiseBinaryType::ELW{}", binary_op_str); - - return defines; -} - -} // namespace ttnn::operations::binary_ng diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.hpp deleted file mode 100644 index cc8a242fc0c4..000000000000 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/binary_ng_utils.hpp +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. -// -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "binary_ng_device_operation.hpp" -#include "ttnn/operations/eltwise/binary_ng/types.hpp" - -#include -#include - -namespace ttnn::operations::binary_ng { - -enum class KernelName { - ReaderNoBcast, - ReaderRowBcast, - ReaderColBcast, - ReaderScalarBcast, - WriterNoBcast, - WriterRowBcast, - WriterColBcast, - WriterScalarBcast, - WriterScalar, - ComputeNoBcast, - ComputeBcast, - ComputeScalar -}; - -struct BinaryNgKernelConfig { - BinaryNgKernelConfig(SubtileBroadcastType subtile_broadcast_type); - - std::string bcast_input_str() const; - - KernelName reader_kernel; - KernelName compute_kernel; - KernelName writer_kernel; - std::optional bcast_input; -}; - -std::string get_kernel_file_path(KernelName kernel_name); - -struct OpConfig { - struct SfpuConfig { - SfpuConfig() = default; - constexpr SfpuConfig( - std::string_view init, std::string_view apply, std::string_view include = "compute_kernel_api.h") : - init{init}, apply{apply}, include{include} {} - std::string_view init{}; - std::string_view apply{}; - std::string_view include{}; - - std::map as_defines(std::string_view prefix) const; - }; - - enum class FpuBinaryOp { ADD, SUB, MUL }; - - OpConfig(BinaryOpType binary_op_type); - - std::map as_defines() const; - - SfpuConfig preprocess_a{}; - SfpuConfig preprocess_b{}; - SfpuConfig postprocess{}; - FpuBinaryOp fpu_binary_op; -}; - -struct Lowercase { - std::string_view view; -}; - -} // namespace ttnn::operations::binary_ng diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary.cpp index ca31d410f390..dff89bfd6134 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary.cpp @@ -2,72 +2,24 @@ // // SPDX-License-Identifier: Apache-2.0 -#include #include "compute_kernel_api/eltwise_binary.h" -#include "eltwise_defines.hpp" -#include "eltwise_utils.hpp" - -#ifdef PREPROCESS_A_INCLUDE -#include QUOTE(PREPROCESS_A_INCLUDE) -#endif +#include -#ifdef PREPROCESS_B_INCLUDE -#include QUOTE(PREPROCESS_B_INCLUDE) -#endif - -#ifdef POSTPROCESS_INCLUDE -#include QUOTE(POSTPROCESS_INCLUDE) -#endif namespace NAMESPACE { -ALWI void process_tile( - tt::CBIndex cb_pre_lhs, - tt::CBIndex cb_post_lhs, - tt::CBIndex cb_pre_rhs, - tt::CBIndex cb_post_rhs, - tt::CBIndex cb_out, - uint32_t freq, - uint32_t tile_start) { - using namespace ckernel; +ALWI void process_tile(uint32_t cb_bcast, uint32_t cb_other, uint32_t cb_out, uint32_t freq, uint32_t tile_start) { constexpr uint32_t onetile = 1; -#if BCAST_INPUT - auto cb_bcast = cb_post_rhs; - auto cb_other = cb_post_lhs; -#else - auto cb_bcast = cb_post_lhs; - auto cb_other = cb_post_rhs; -#endif - -#if PREPROCESS_A && (BCAST_INPUT == 0) - PREPROCESS(PREPROCESS_A_INIT, PREPROCESS_A_APPLY, cb_pre_lhs, cb_post_lhs, cb_out, onetile); -#elif PREPROCESS_B && (BCAST_INPUT == 1) - PREPROCESS(PREPROCESS_B_INIT, PREPROCESS_B_APPLY, cb_pre_rhs, cb_post_rhs, cb_out, onetile); -#endif - cb_wait_front(cb_bcast, onetile); for (uint32_t j = tile_start; j < freq; ++j) { -#if PREPROCESS_A && (BCAST_INPUT == 1) - PREPROCESS(PREPROCESS_A_INIT, PREPROCESS_A_APPLY, cb_pre_lhs, cb_post_lhs, cb_out, onetile); -#elif PREPROCESS_B && (BCAST_INPUT == 0) - PREPROCESS(PREPROCESS_B_INIT, PREPROCESS_B_APPLY, cb_pre_rhs, cb_post_rhs, cb_out, onetile); -#endif cb_wait_front(cb_other, onetile); - cb_reserve_back(cb_out, onetile); -#if PREPROCESS_A || PREPROCESS_B - binary_op_specific_init(); -#endif tile_regs_acquire(); - BINARY_OP(cb_post_lhs, cb_post_rhs, 0, 0, 0); -#if POSTPROCESS - POSTPROCESS_INIT(); - POSTPROCESS_APPLY(0); -#endif + add_tiles(cb_bcast, cb_other, 0, 0, 0); tile_regs_commit(); tile_regs_wait(); @@ -89,28 +41,30 @@ void MAIN { return; } - constexpr auto cb_pre_lhs = tt::CBIndex::c_0; - constexpr auto cb_pre_rhs = tt::CBIndex::c_1; - constexpr auto cb_out = tt::CBIndex::c_2; + constexpr auto cb_in0 = tt::CBIndex::c_0; + constexpr auto cb_in1 = tt::CBIndex::c_1; + constexpr auto cb_out0 = tt::CBIndex::c_2; - constexpr auto cb_post_lhs = PREPROCESS_A ? tt::CBIndex::c_3 : cb_pre_lhs; - constexpr auto cb_post_rhs = PREPROCESS_B ? tt::CBIndex::c_4 : cb_pre_rhs; - - binary_op_init_common(cb_post_lhs, cb_post_rhs, cb_out); - -#if not(PREPROCESS_A || PREPROCESS_B) - binary_op_specific_init(); +#if BCAST_INPUT + auto cb_bcast = cb_in1; + auto cb_other = cb_in0; +#else + auto cb_bcast = cb_in0; + auto cb_other = cb_in1; #endif + binary_op_init_common(cb_bcast, cb_other, cb_out0); + add_tiles_init(); + uint32_t complete_iterations = (num_tiles + tile_start) / tile_freq; uint32_t remaining_iterations = (num_tiles + tile_start) % tile_freq; for (uint32_t i = 0; i < complete_iterations; ++i, tile_start = 0) { - process_tile(cb_pre_lhs, cb_post_lhs, cb_pre_rhs, cb_post_rhs, cb_out, tile_freq, tile_start); + process_tile(cb_bcast, cb_other, cb_out0, tile_freq, tile_start); } if (remaining_iterations > 0) { - process_tile(cb_pre_lhs, cb_post_lhs, cb_pre_rhs, cb_post_rhs, cb_out, remaining_iterations, tile_start); + process_tile(cb_bcast, cb_other, cb_out0, remaining_iterations, tile_start); } } } // namespace NAMESPACE diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_no_bcast.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_no_bcast.cpp index e1d3fb08997f..1e3703fa4b80 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_no_bcast.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_no_bcast.cpp @@ -6,71 +6,35 @@ #include "compute_kernel_api/eltwise_binary.h" #include "dprint.h" -#include "eltwise_defines.hpp" -#include "eltwise_utils.hpp" - -#ifdef PREPROCESS_A_INCLUDE -#include QUOTE(PREPROCESS_A_INCLUDE) -#endif - -#ifdef PREPROCESS_B_INCLUDE -#include QUOTE(PREPROCESS_B_INCLUDE) -#endif - -#ifdef POSTPROCESS_INCLUDE -#include QUOTE(POSTPROCESS_INCLUDE) -#endif - namespace NAMESPACE { void MAIN { uint32_t num_tiles = get_arg_val(0); - constexpr auto cb_pre_lhs = tt::CBIndex::c_0; - constexpr auto cb_pre_rhs = tt::CBIndex::c_1; - constexpr auto cb_out = tt::CBIndex::c_2; + constexpr auto cb_in0 = tt::CBIndex::c_0; + constexpr auto cb_in1 = tt::CBIndex::c_1; + constexpr auto cb_out0 = tt::CBIndex::c_2; - constexpr auto cb_post_lhs = PREPROCESS_A ? tt::CBIndex::c_3 : cb_pre_lhs; - constexpr auto cb_post_rhs = PREPROCESS_B ? tt::CBIndex::c_4 : cb_pre_rhs; - - binary_op_init_common(cb_post_lhs, cb_post_rhs, cb_out); - -#if not(PREPROCESS_A || PREPROCESS_B) - binary_op_specific_init(); -#endif + binary_op_init_common(cb_in0, cb_in1, cb_out0); + add_tiles_init(); constexpr uint32_t onetile = 1; - for (uint32_t tile_id = 0; tile_id < num_tiles; ++tile_id) { -#if PREPROCESS_A - PREPROCESS(PREPROCESS_A_INIT, PREPROCESS_A_APPLY, cb_pre_lhs, cb_post_lhs, cb_out, onetile); -#endif - cb_wait_front(cb_post_lhs, onetile); - -#if PREPROCESS_B - PREPROCESS(PREPROCESS_B_INIT, PREPROCESS_B_APPLY, cb_pre_rhs, cb_post_rhs, cb_out, onetile); -#endif - cb_wait_front(cb_post_rhs, onetile); - - cb_reserve_back(cb_out, onetile); + for(uint32_t tile_id = 0; tile_id < num_tiles; ++tile_id) { + cb_wait_front(cb_in0, onetile); + cb_wait_front(cb_in1, onetile); + cb_reserve_back(cb_out0, onetile); -#if PREPROCESS_A || PREPROCESS_B - binary_op_specific_init(); -#endif tile_regs_acquire(); - BINARY_OP(cb_post_lhs, cb_post_rhs, 0, 0, 0); -#if POSTPROCESS - POSTPROCESS_INIT(); - POSTPROCESS_APPLY(0); -#endif + add_tiles(cb_in0, cb_in1, 0, 0, 0); tile_regs_commit(); tile_regs_wait(); - pack_tile(0, cb_out); + pack_tile(0, cb_out0); tile_regs_release(); - cb_push_back(cb_out, onetile); - cb_pop_front(cb_post_lhs, onetile); - cb_pop_front(cb_post_rhs, onetile); + cb_push_back(cb_out0, onetile); + cb_pop_front(cb_in0, onetile); + cb_pop_front(cb_in1, onetile); } } } // namespace NAMESPACE diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_scalar.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_scalar.cpp index 92e2b95be2ed..2b377fb52cab 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_scalar.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_binary_scalar.cpp @@ -4,71 +4,37 @@ #include #include "compute_kernel_api/eltwise_binary.h" - -#include "eltwise_defines.hpp" -#include "eltwise_utils.hpp" - -#ifdef PREPROCESS_A_INCLUDE -#include QUOTE(PREPROCESS_A_INCLUDE) -#endif - -#ifdef PREPROCESS_B_INCLUDE -#include QUOTE(PREPROCESS_B_INCLUDE) -#endif - -#ifdef POSTPROCESS_INCLUDE -#include QUOTE(POSTPROCESS_INCLUDE) -#endif +#include "dprint.h" namespace NAMESPACE { void MAIN { uint32_t num_tiles = get_arg_val(0); - constexpr auto cb_pre_lhs = tt::CBIndex::c_0; - constexpr auto cb_pre_rhs = tt::CBIndex::c_1; - constexpr auto cb_out = tt::CBIndex::c_2; + constexpr auto cb_in0 = tt::CBIndex::c_0; + constexpr auto cb_in1 = tt::CBIndex::c_1; + constexpr auto cb_out0 = tt::CBIndex::c_2; - constexpr auto cb_post_lhs = PREPROCESS_A ? tt::CBIndex::c_3 : cb_pre_lhs; - constexpr auto cb_post_rhs = PREPROCESS_B ? tt::CBIndex::c_4 : cb_pre_rhs; - - binary_op_init_common(cb_post_lhs, cb_post_rhs, cb_out); - -#if not(PREPROCESS_A || PREPROCESS_B) - binary_op_specific_init(); -#endif + binary_op_init_common(cb_in0, cb_in1, cb_out0); + add_tiles_init(); constexpr uint32_t onetile = 1; -#if PREPROCESS_B - PREPROCESS(PREPROCESS_B_INIT, PREPROCESS_B_APPLY, cb_pre_rhs, cb_post_rhs, cb_out, onetile); -#endif - cb_wait_front(cb_post_rhs, onetile); + cb_wait_front(cb_in1, onetile); for(uint32_t tile_id = 0; tile_id < num_tiles; ++tile_id) { -#if PREPROCESS_A - PREPROCESS(PREPROCESS_A_INIT, PREPROCESS_A_APPLY, cb_pre_lhs, cb_post_lhs, cb_out, onetile); -#endif - cb_wait_front(cb_post_lhs, onetile); - - cb_reserve_back(cb_out, onetile); + cb_wait_front(cb_in0, onetile); + cb_reserve_back(cb_out0, onetile); -#if PREPROCESS_A || PREPROCESS_B - binary_op_specific_init(); -#endif tile_regs_acquire(); - BINARY_OP(cb_post_lhs, cb_post_rhs, 0, 0, 0); -#if POSTPROCESS - POSTPROCESS_INIT(); - POSTPROCESS_APPLY(0); -#endif + add_tiles(cb_in0, cb_in1, 0, 0, 0); tile_regs_commit(); tile_regs_wait(); - pack_tile(0, cb_out); + pack_tile(0, cb_out0); tile_regs_release(); - cb_pop_front(cb_post_lhs, onetile); - cb_push_back(cb_out, onetile); + cb_pop_front(cb_in0, onetile); + cb_push_back(cb_out0, onetile); } } } // namespace NAMESPACE diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_defines.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_defines.hpp deleted file mode 100644 index 5238d3b8db6a..000000000000 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_defines.hpp +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. -// -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#define DO_QUOTE(x) #x -#define QUOTE(x) DO_QUOTE(x) - -#if defined(PREPROCESS_A_INIT) -#define PREPROCESS_A 1 -#else -#define PREPROCESS_A 0 -#endif - -#if defined(PREPROCESS_B_INIT) -#define PREPROCESS_B 1 -#else -#define PREPROCESS_B 0 -#endif - -#if defined(POSTPROCESS_INIT) -#define POSTPROCESS 1 -#else -#define POSTPROCESS 0 -#endif diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_utils.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_utils.hpp deleted file mode 100644 index 63f350c47c1e..000000000000 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/device/kernels/compute/eltwise_utils.hpp +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. -// -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "compute_kernel_api/common.h" -#include "compute_kernel_api/tile_move_copy.h" - -#define PREPROCESS(init, apply, cb_pre, cb_post, cb_out, per_core_block_size) \ - do { \ - using namespace ckernel; \ - \ - copy_tile_to_dst_init_short(); \ - \ - reconfig_data_format_srca(/*old*/ cb_post, /*new*/ cb_pre); \ - pack_reconfig_data_format(/*old*/ cb_out, /*new*/ cb_post); \ - \ - cb_wait_front(cb_pre, per_core_block_size); \ - cb_reserve_back(cb_post, per_core_block_size); \ - \ - tile_regs_acquire(); \ - init(); \ - for (uint32_t i = 0; i < per_core_block_size; ++i) { \ - copy_tile(cb_pre, i, i); \ - apply(i); \ - } \ - tile_regs_commit(); \ - \ - tile_regs_wait(); \ - for (uint32_t i = 0; i < per_core_block_size; ++i) { \ - pack_tile(i, cb_post); /* DST[0]->cb */ \ - } \ - tile_regs_release(); \ - \ - cb_pop_front(cb_pre, per_core_block_size); \ - cb_push_back(cb_post, per_core_block_size); \ - \ - reconfig_data_format_srca(/*old*/ cb_pre, /*new*/ cb_post); \ - pack_reconfig_data_format(/*old*/ cb_post, /*new*/ cb_out); \ - } while (0) diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/types.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/types.hpp index 06c53bfe7e6d..ccb5695b3ac5 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary_ng/types.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary_ng/types.hpp @@ -10,7 +10,6 @@ enum class BinaryOpType { ADD, SUB, MUL, - DIV, GT, LT, LTE, @@ -19,11 +18,13 @@ enum class BinaryOpType { NE, SQUARED_DIFFERENCE, BIAS_GELU, + LOGADDEXP, LOGICAL_AND, LOGICAL_OR, LOGICAL_XOR, LDEXP, - LOGADDEXP, LOGADDEXP2, + DIV_FAST }; + } diff --git a/ttnn/ttnn/operations/binary_ng.py b/ttnn/ttnn/operations/binary_ng.py deleted file mode 100644 index d012271bda5e..000000000000 --- a/ttnn/ttnn/operations/binary_ng.py +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. - -# SPDX-License-Identifier: Apache-2.0 - -import ttnn -import torch - - -ttnn.attach_golden_function(ttnn.experimental.add, golden_function=lambda a, b: a + b) -ttnn.attach_golden_function(ttnn.experimental.sub, golden_function=lambda a, b: a - b) -ttnn.attach_golden_function(ttnn.experimental.mul, golden_function=lambda a, b: a * b) -ttnn.attach_golden_function(ttnn.experimental.div, golden_function=lambda a, b: torch.divide(a, b)) -ttnn.attach_golden_function(ttnn.experimental.eq, golden_function=lambda a, b: torch.eq(a, b)) -ttnn.attach_golden_function(ttnn.experimental.ne, golden_function=lambda a, b: torch.ne(a, b)) -ttnn.attach_golden_function(ttnn.experimental.gt, golden_function=lambda a, b: torch.gt(a, b)) -ttnn.attach_golden_function(ttnn.experimental.lt, golden_function=lambda a, b: torch.lt(a, b)) -ttnn.attach_golden_function(ttnn.experimental.gte, golden_function=lambda a, b: torch.ge(a, b)) -ttnn.attach_golden_function(ttnn.experimental.lte, golden_function=lambda a, b: torch.le(a, b)) -ttnn.attach_golden_function(ttnn.experimental.ldexp, golden_function=lambda a, b: torch.ldexp(a, b)) -ttnn.attach_golden_function(ttnn.experimental.logaddexp, golden_function=lambda a, b: torch.logaddexp(a, b)) -ttnn.attach_golden_function(ttnn.experimental.logaddexp2, golden_function=lambda a, b: torch.logaddexp2(a, b)) -ttnn.attach_golden_function(ttnn.experimental.logical_and, golden_function=lambda a, b: torch.logical_and(a, b)) -ttnn.attach_golden_function(ttnn.experimental.logical_or, golden_function=lambda a, b: torch.logical_or(a, b)) -ttnn.attach_golden_function(ttnn.experimental.logical_xor, golden_function=lambda a, b: torch.logical_xor(a, b)) -ttnn.attach_golden_function( - ttnn.experimental.squared_difference, golden_function=lambda a, b: torch.square(torch.sub(a, b)) -) -ttnn.attach_golden_function( - ttnn.experimental.bias_gelu, golden_function=lambda a, b: torch.nn.functional.gelu(torch.add(a, b)) -) From 55bb502f905c63ec5e7b7883838a2fe03f004eaa Mon Sep 17 00:00:00 2001 From: Oleg Milyutin Date: Tue, 17 Dec 2024 10:43:36 -0500 Subject: [PATCH 24/87] #0: Remove some dead code (#16084) 1. `Tensor::deepcopy` that isn't actually doing a deep copy and isn't used anywhere. 2. `cpu_sharded` / `host_sharded` that doesn't work due to deprecation of slow dispatch, and that was previously used for debugging. ### Checklist - [x] [Post commit CI passes](https://github.com/tenstorrent/tt-metal/actions/runs/12365560530) (failure unrelated) --- ttnn/cpp/pybind11/pytensor.cpp | 7 ---- ttnn/cpp/ttnn/tensor/tensor.cpp | 16 +-------- ttnn/cpp/ttnn/tensor/tensor.hpp | 4 --- ttnn/cpp/ttnn/tensor/tensor_impl.cpp | 37 -------------------- ttnn/cpp/ttnn/tensor/tensor_impl.hpp | 3 -- ttnn/cpp/ttnn/tensor/tensor_impl_wrapper.hpp | 1 - ttnn/cpp/ttnn/tensor/tensor_ops.cpp | 9 ----- ttnn/cpp/ttnn/tensor/tensor_ops.hpp | 2 -- 8 files changed, 1 insertion(+), 78 deletions(-) diff --git a/ttnn/cpp/pybind11/pytensor.cpp b/ttnn/cpp/pybind11/pytensor.cpp index 68a507a2c4a9..cd381eabab5b 100644 --- a/ttnn/cpp/pybind11/pytensor.cpp +++ b/ttnn/cpp/pybind11/pytensor.cpp @@ -1161,13 +1161,6 @@ void pytensor_module(py::module& m_tensor) { tt_tensor = tt_tensor.cpu() )doc") - .def("cpu_sharded", &Tensor::cpu_sharded, R"doc( - Move TT Tensor from TT accelerator device to host device in sharded orientation. - - .. code-block:: python - - tt_tensor = tt_tensor.cpu_sharded() - )doc") .def( "to", py::overload_cast(&Tensor::to, py::const_), diff --git a/ttnn/cpp/ttnn/tensor/tensor.cpp b/ttnn/cpp/ttnn/tensor/tensor.cpp index 689cc127d34f..1057b1834966 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor.cpp @@ -482,21 +482,9 @@ void Tensor::perform_cleanup_for_async_mode() { } } -void Tensor::deepcopy(const Tensor& other) { - ZoneScoped; - // Wait until the tensor being copied is populated - other.wait_for_tensor_data_populated(); - // Populate tensor metadata - this->set_storage(other.get_storage()); - this->set_tensor_spec(other.get_tensor_spec()); - // Set metadata populated flag for getters - this->tensor_attributes->num_workers_completed++; -} - void Tensor::populate_buffers_and_metadata(const Tensor& other) { ZoneScoped; - // Similar to deepcopy, but to be applied on a tensor that has an empty storage - // container initialized. Require tensor storage to be correctly initialized. + // Applied on a tensor that has an empty storage container initialized. this->set_tensor_spec(other.get_tensor_spec()); // Populate storage container with buffers + shapes std::visit( @@ -698,8 +686,6 @@ Tensor Tensor::cpu(bool blocking, uint8_t cq_id, const std::vector& return tensor_ops::tensor_cpu(*this, blocking, cq_id, sub_device_ids); } -Tensor Tensor::cpu_sharded() const { return tensor_ops::tensor_cpu_sharded(*this); } - Tensor Tensor::extract_shard(const CoreCoord& core) const { ZoneScoped; const auto& buffer_page_mapping = *this->buffer()->get_buffer_page_mapping(); diff --git a/ttnn/cpp/ttnn/tensor/tensor.hpp b/ttnn/cpp/ttnn/tensor/tensor.hpp index 30e18978b8e5..6827c4213206 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor.hpp @@ -131,8 +131,6 @@ struct Tensor { void perform_cleanup_for_async_mode(); - void deepcopy(const Tensor& other); - void populate_buffers_and_metadata(const Tensor& other); void deallocate(bool force = false); @@ -209,8 +207,6 @@ struct Tensor { uint8_t cq_id = ttnn::DefaultQueueId, const std::vector& sub_device_ids = {}) const; - Tensor cpu_sharded() const; - Tensor unpad(const ttnn::SimpleShape& output_tensor_start, const ttnn::SimpleShape& output_tensor_end) const; Tensor pad_to_tile(float pad_value) const; diff --git a/ttnn/cpp/ttnn/tensor/tensor_impl.cpp b/ttnn/cpp/ttnn/tensor/tensor_impl.cpp index dc7545ac0e5b..3f731c97c654 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_impl.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor_impl.cpp @@ -633,43 +633,6 @@ Tensor to_host( return to_host(tensor, blocking, cq_id, sub_device_ids); } -// ====================================================================================== -// .to_host_sharded() -// ====================================================================================== - -template -Tensor to_host_sharded(const Tensor& tensor) { - TT_ASSERT(tensor.is_allocated(), "Buffer must be allocated on device!"); - auto device_buffer = tensor.buffer(); - auto device = tensor.device(); - TT_ASSERT(device != nullptr && "Need device to be set copy data from device to host!"); - std::vector data_vec; - const char* TT_METAL_SLOW_DISPATCH_MODE = std::getenv("TT_METAL_SLOW_DISPATCH_MODE"); - if (TT_METAL_SLOW_DISPATCH_MODE == nullptr) { - TT_THROW("FAST_DISPATCH is not supported for to_host_sharded!"); - } - ::detail::ReadFromBuffer(*device_buffer, data_vec, true); - auto output_buffer = owned_buffer::create(std::move(data_vec)); - return Tensor(OwnedStorage{output_buffer}, tensor.get_tensor_spec()); -} - -template Tensor to_host_sharded(const Tensor& tensor); -template Tensor to_host_sharded(const Tensor& tensor); -template Tensor to_host_sharded(const Tensor& tensor); -template Tensor to_host_sharded(const Tensor& tensor); -template Tensor to_host_sharded(const Tensor& tensor); -template Tensor to_host_sharded(const Tensor& tensor); - -template <> -Tensor to_host_sharded(const Tensor& tensor) { - return to_host_sharded(tensor); -} - -template <> -Tensor to_host_sharded(const Tensor& tensor) { - return to_host_sharded(tensor); -} - // ====================================================================================== // .to_device() details // ====================================================================================== diff --git a/ttnn/cpp/ttnn/tensor/tensor_impl.hpp b/ttnn/cpp/ttnn/tensor/tensor_impl.hpp index 87c34bdb1998..0ceb2b9c1d1a 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_impl.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor_impl.hpp @@ -191,9 +191,6 @@ Tensor to_host( uint8_t cq_id = ttnn::DefaultQueueId, tt::stl::Span sub_device_ids = {}); -template -Tensor to_host_sharded(const Tensor& tensor); - template Tensor to_device( const Tensor& tensor, diff --git a/ttnn/cpp/ttnn/tensor/tensor_impl_wrapper.hpp b/ttnn/cpp/ttnn/tensor/tensor_impl_wrapper.hpp index 6ab8a8dae755..9cf4c810591c 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_impl_wrapper.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor_impl_wrapper.hpp @@ -39,7 +39,6 @@ inline size_t packed_buffer_size_bytes_wrapper(DataType dtype, size_t volume_unp WRAP_FUNCTION(to_host) WRAP_FUNCTION(extract_shard) -WRAP_FUNCTION(to_host_sharded) WRAP_FUNCTION(to_device) WRAP_FUNCTION(to_layout) WRAP_FUNCTION(pad) diff --git a/ttnn/cpp/ttnn/tensor/tensor_ops.cpp b/ttnn/cpp/ttnn/tensor/tensor_ops.cpp index 96b53b879018..c2df9f3e4307 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_ops.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor_ops.cpp @@ -160,15 +160,6 @@ Tensor tensor_cpu( return host_tensor; } -Tensor tensor_cpu_sharded(const Tensor& input_tensor) { - ZoneScoped; - GraphTracker::instance().track_function_start("Tensor::cpu_sharded", input_tensor); - auto output = tensor_impl::to_host_sharded_wrapper(input_tensor); - output = tt::tt_metal::set_tensor_id(output); - GraphTracker::instance().track_function_end(output); - return output; -} - Tensor tensor_to(const Tensor& input_tensor, Layout target_layout, Device* worker) { ZoneScoped; GraphTracker::instance().track_function_start("Tensor::to", input_tensor, target_layout, worker); diff --git a/ttnn/cpp/ttnn/tensor/tensor_ops.hpp b/ttnn/cpp/ttnn/tensor/tensor_ops.hpp index b8edff425f8b..b65af33cb42a 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_ops.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor_ops.hpp @@ -41,8 +41,6 @@ Tensor tensor_to(const Tensor& input_tensor, Layout target_layout, distributed:: Tensor tensor_cpu( const Tensor& input_tensor, bool blocking, uint8_t cq_id, const std::vector& sub_device_ids); -Tensor tensor_cpu_sharded(const Tensor& input_tensor); - void tensor_print(const Tensor& input_tensor); Tensor tensor_pad( From 6799677cefab9013538b6a6291f47278672748d6 Mon Sep 17 00:00:00 2001 From: Samarth Agarwal Date: Tue, 17 Dec 2024 11:14:48 -0500 Subject: [PATCH 25/87] [skip ci] Updated installation script to not prompt for user input (#16101) --- install_dependencies.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/install_dependencies.sh b/install_dependencies.sh index fec8cd1c99f0..e8698fb73d16 100755 --- a/install_dependencies.sh +++ b/install_dependencies.sh @@ -22,7 +22,6 @@ FLAVOR=`grep '^ID=' /etc/os-release | awk -F= '{print $2}' | tr -d '"'` VERSION=`grep '^VERSION_ID=' /etc/os-release | awk -F= '{print $2}' | tr -d '"'` MAJOR=${VERSION%.*} ARCH=`uname -m` -DEBIAN_FRONTEND="noninteractive" usage() { @@ -122,7 +121,7 @@ install() prep_ubuntu echo "Installing packages..." - apt-get install -y --no-install-recommends "${UB_LIST[@]}" + DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends "${UB_LIST[@]}" fi } From c45a5b788b3b47711e918dd67dccdfb716c88c40 Mon Sep 17 00:00:00 2001 From: Andrew Fuller Date: Tue, 17 Dec 2024 12:23:48 -0500 Subject: [PATCH 26/87] Python -> Python3 (#16063) ### Ticket #14393 ### Problem description The scripts do not execute inside a clean Docker container because they call `python` instead of `python3`. ### What's changed python -> python3 ### Checklist - [x] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12360690672 - [x] Device perf regressions passes https://github.com/tenstorrent/tt-metal/actions/runs/12358294738 --- .github/workflows/perf-device-models-impl.yaml | 17 ++++++++++------- .github/workflows/perf-device-models.yaml | 1 + .../stable_diffusion/demo/web_demo/web_demo.py | 2 +- tests/scripts/run_build_docs.sh | 2 +- tests/scripts/run_ttnn_sweeps.sh | 2 +- tests/scripts/set_up_end_to_end_tests_env.sh | 6 +++--- tt_metal/tools/profiler/process_model_log.py | 2 +- tt_metal/tools/profiler/profile_this.py | 2 +- ttnn/tracy/__main__.py | 4 ++-- 9 files changed, 21 insertions(+), 17 deletions(-) diff --git a/.github/workflows/perf-device-models-impl.yaml b/.github/workflows/perf-device-models-impl.yaml index ee56a304c2f0..dd312999b868 100644 --- a/.github/workflows/perf-device-models-impl.yaml +++ b/.github/workflows/perf-device-models-impl.yaml @@ -2,6 +2,11 @@ name: "[internal] Single-card Device perf regressions impl" on: workflow_call: + inputs: + os: + required: false + type: string + default: "ubuntu-20.04" jobs: device-perf: @@ -22,22 +27,20 @@ jobs: LD_LIBRARY_PATH: ${{ github.workspace }}/build/lib runs-on: ${{ matrix.test-info.runs-on }} steps: - - uses: tenstorrent/tt-metal/.github/actions/checkout-with-submodule-lfs@main - name: Ensure weka mount is active run: | sudo systemctl restart mnt-MLPerf.mount sudo /etc/rc.local ls -al /mnt/MLPerf/bit_error_tests + - uses: tenstorrent/tt-metal/.github/actions/checkout-with-submodule-lfs@main - name: Set up dynamic env vars for build run: | echo "TT_METAL_HOME=$(pwd)" >> $GITHUB_ENV - - uses: actions/download-artifact@v4 + - uses: ./.github/actions/prepare-metal-run with: - name: TTMetal_build_${{ matrix.test-info.arch }}_profiler - - name: Extract files - run: tar -xvf ttm_${{ matrix.test-info.arch }}.tar - - uses: ./.github/actions/install-python-deps - - name: Run device performance regressions + arch: ${{ matrix.test-info.arch }} + is_profiler: 'true' + - name: ${{ matrix.test-group.name }} tests timeout-minutes: ${{ matrix.test-info.timeout }} run: | source python_env/bin/activate diff --git a/.github/workflows/perf-device-models.yaml b/.github/workflows/perf-device-models.yaml index 67ef9232f81f..54f13d240dc2 100644 --- a/.github/workflows/perf-device-models.yaml +++ b/.github/workflows/perf-device-models.yaml @@ -11,6 +11,7 @@ jobs: uses: ./.github/workflows/build-artifact.yaml with: tracy: true + os: "ubuntu-20.04-amd64" secrets: inherit device-perf: needs: build-artifact-profiler diff --git a/models/demos/wormhole/stable_diffusion/demo/web_demo/web_demo.py b/models/demos/wormhole/stable_diffusion/demo/web_demo/web_demo.py index 2ec33d03bd36..e4b2e6abf878 100644 --- a/models/demos/wormhole/stable_diffusion/demo/web_demo/web_demo.py +++ b/models/demos/wormhole/stable_diffusion/demo/web_demo/web_demo.py @@ -9,7 +9,7 @@ # Two scripts to run script1 = "pytest models/demos/wormhole/stable_diffusion/demo/web_demo/sdserver.py" -script2 = "python models/demos/wormhole/stable_diffusion/demo/web_demo/flaskserver.py" +script2 = "python3 models/demos/wormhole/stable_diffusion/demo/web_demo/flaskserver.py" script3 = "streamlit run models/demos/wormhole/stable_diffusion/demo/web_demo/streamlit_app.py" # Start both scripts using subprocess diff --git a/tests/scripts/run_build_docs.sh b/tests/scripts/run_build_docs.sh index 66dc0d36cebd..867095da3bea 100755 --- a/tests/scripts/run_build_docs.sh +++ b/tests/scripts/run_build_docs.sh @@ -9,6 +9,6 @@ fi echo "Checking docs build..." cd $TT_METAL_HOME/docs -python -m pip install -r requirements-docs.txt +python3 -m pip install -r requirements-docs.txt make clean make html diff --git a/tests/scripts/run_ttnn_sweeps.sh b/tests/scripts/run_ttnn_sweeps.sh index f5ecfe0a5b49..5581636ae388 100755 --- a/tests/scripts/run_ttnn_sweeps.sh +++ b/tests/scripts/run_ttnn_sweeps.sh @@ -15,7 +15,7 @@ run_ttnn_sweeps() { export PYTHONPATH=$TT_METAL_HOME source python_env/bin/activate - python tests/ttnn/sweep_tests/run_sweeps.py + python3 tests/ttnn/sweep_tests/run_sweeps.py } run_ttnn_sweeps diff --git a/tests/scripts/set_up_end_to_end_tests_env.sh b/tests/scripts/set_up_end_to_end_tests_env.sh index 3c57afe72b16..08a65a43fe30 100755 --- a/tests/scripts/set_up_end_to_end_tests_env.sh +++ b/tests/scripts/set_up_end_to_end_tests_env.sh @@ -16,10 +16,10 @@ set_up_end_to_end_tests_env() { source env/bin/activate - python -m pip config set global.extra-index-url https://download.pytorch.org/whl/cpu + python3 -m pip config set global.extra-index-url https://download.pytorch.org/whl/cpu - python -m pip install -r requirements.txt - python -m pip install ../../ttnn-*.whl + python3 -m pip install -r requirements.txt + python3 -m pip install ../../ttnn-*.whl cd ../../ rm -rf tt_metal tt_eager ttnn models diff --git a/tt_metal/tools/profiler/process_model_log.py b/tt_metal/tools/profiler/process_model_log.py index 2097fade1f6a..9c5d47d02e44 100755 --- a/tt_metal/tools/profiler/process_model_log.py +++ b/tt_metal/tools/profiler/process_model_log.py @@ -46,7 +46,7 @@ def post_process_ops_log(output_logs_subdir, columns, sum_vals=True, op_name="", def run_device_profiler(command, output_logs_subdir): output_profiler_dir = get_profiler_folder(output_logs_subdir) - profiler_cmd = f"python -m tracy -p -r -o {output_profiler_dir} -t 5000 -m {command}" + profiler_cmd = f"python3 -m tracy -p -r -o {output_profiler_dir} -t 5000 -m {command}" subprocess.run([profiler_cmd], shell=True, check=True) diff --git a/tt_metal/tools/profiler/profile_this.py b/tt_metal/tools/profiler/profile_this.py index f8848c9a2bec..4323380a1eba 100755 --- a/tt_metal/tools/profiler/profile_this.py +++ b/tt_metal/tools/profiler/profile_this.py @@ -26,7 +26,7 @@ def profile_command(test_command, output_folder, name_append): options += f"-o {output_folder}" if name_append: options += f" -n {name_append}" - opProfilerTestCommand = f"python -m tracy -v -r -p {options} -m {test_command}" + opProfilerTestCommand = f"python3 -m tracy -v -r -p {options} -m {test_command}" subprocess.run([opProfilerTestCommand], shell=True, check=False, env=currentEnvs) diff --git a/ttnn/tracy/__main__.py b/ttnn/tracy/__main__.py index 10e2a03bb3cd..39b605f206e1 100644 --- a/ttnn/tracy/__main__.py +++ b/ttnn/tracy/__main__.py @@ -10,7 +10,7 @@ def main(): from optparse import OptionParser - usage = "python -m tracy [-m module | scriptfile] [arg] ..." + usage = "python3 -m tracy [-m module | scriptfile] [arg] ..." parser = OptionParser(usage=usage) parser.allow_interspersed_args = False parser.add_option("-m", dest="module", action="store_true", help="Profile a library module.", default=False) @@ -146,7 +146,7 @@ def main(): originalArgs.remove("-r") osCmd = " ".join(originalArgs[1:]) - testCommand = f"python -m tracy {osCmd}" + testCommand = f"python3 -m tracy {osCmd}" envVars = dict(os.environ) # No Dispatch cores for op_report From b80a975f1d480c1deb5cf2e520c9797c058c0903 Mon Sep 17 00:00:00 2001 From: Saad Jameel <163029024+sjameelTT@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:21:38 -0500 Subject: [PATCH 27/87] Add transpose WH sharded, generalize row major permute when N > 4, and do a minor refactor of ttnn::permute (#15881) ### Ticket #14790 add transpose wh sharded implementation when shard shape < height dimension #15165 add N-d permute with width dimension #15589 correct permute dimensionality when less than 4D #15750 remove the composite flag from permute #12550 re-enable some permute tests for blackhole #12349 re-enable working transpose tests for blackhole #16066 disable test uniform as it's stochastic ### Problem description This PR addresses several permute and transpose problems all at once - Transpose WH sharded does not currently work when the shard shape is less than the height - Permute on greater than 4 dimensions does not work when moving width around (for both tiled and RM) - The Permute kernel when width doesn't change is single core - Permute has an unclean API in which we have a composite flag that is not generically applicable - Permute on less than 4 dimensions gets an incorrect output shape in cases where it's a no-op - Permute tests are disabled for BH due to LLK issues - Transpose tests are disabled for BH due to LLK issues ### What's changed - Add transpose WH sharded implementation for when shard shape is less than the height dim (outputs a block sharded output) - Add an N-d permute kernel that works generically on any row major input. We have to call a global init each loop of the compute kernel as transpose sets some registers that aren't cleared (there's no transpose_uninit). This results in bad pcc when there's more than one loop. For GS/BH, even the global init doesn't solve the problem so the test is disabled. For Tiled, we need 5D untilize/tilize. This increases sweeps coverage for permute from **50%** to **86%** - For the optimized case where Permute's width dimension is not shuffled, make the kernel multicore - Remove composite flag that is default set to to make permute non-generic. This has caused forge models to have bad pcc as they were not aware of this optional argument. - Refactor ttnn::permute to add nop checks and correct shape calculations - Re-enable permute and transpose tests for blackhole When replacing variants of transpose with this RM permute kernel, a lot of tests on BH/GS failed, so I will do that in a follow-up to address. The LLK issues are causing pains there. If we get N-d untilize/tilize support and once the LLK issues are fixed, permute should have the ability to be generic. The remaining issues for the pytorch 2.0 sweeps after the untilize/tilize fix are the CB overflow on transpose wh, which should be fixed out of the box when we replace the kernel that is used (which I am not doing in this PR since it doesn't work for GS/BH atm). ### Checklist - [x] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12367177499/job/34547311782 (failing test is failing on main) - [x] Blackhole Post commit (if applicable) https://github.com/tenstorrent/tt-metal/actions/runs/12367175575 - [x] Model regression CI testing passes (if applicable) https://github.com/tenstorrent/tt-metal/actions/runs/12357119737 - [x] Device performance regression CI testing passes (if applicable) https://github.com/tenstorrent/tt-metal/actions/runs/12357115316 - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [ ] New/Existing tests provide coverage for changes --- .../pytests/tt_dnn/test_permute.py | 1 - .../unit_testing/misc/test_transpose.py | 84 ++++- .../unit_tests/operations/test_permute.py | 101 +++++- .../unit_tests/operations/test_uniform.py | 1 + .../data_movement/common/kernels/common.hpp | 39 +++ .../transpose_xw_rm_single_tile_size.cpp | 65 ++++ ...permute_interleaved_rm_blocked_generic.cpp | 124 ++++++++ ..._permute_interleaved_rm_row_invariant.cpp} | 7 +- ...permute_interleaved_rm_blocked_generic.cpp | 167 ++++++++++ ..._permute_interleaved_rm_row_invariant.cpp} | 13 +- .../device/permute_device_operation.cpp | 13 +- .../device/permute_device_operation.hpp | 28 +- .../device/permute_program_factory.cpp | 288 ++++++++++++++++-- .../data_movement/permute/permute.cpp | 105 ++----- .../data_movement/permute/permute.hpp | 1 - .../data_movement/permute/permute_pybind.cpp | 2 +- .../transpose/device/transpose_op.cpp | 88 ++++-- .../device/transpose_program_factory.cpp | 75 +++-- .../device/attn_matmul_device_operation.cpp | 4 +- ttnn/cpp/ttnn/tensor/shape/shape.cpp | 12 + ttnn/cpp/ttnn/tensor/shape/shape.hpp | 2 + 21 files changed, 1041 insertions(+), 179 deletions(-) create mode 100644 ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/compute/transpose_xw_rm_single_tile_size.cpp create mode 100644 ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/reader_permute_interleaved_rm_blocked_generic.cpp rename ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/{reader_permute_interleaved_rm.cpp => reader_permute_interleaved_rm_row_invariant.cpp} (78%) create mode 100644 ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/writer_permute_interleaved_rm_blocked_generic.cpp rename ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/{writer_permute_interleaved_rm.cpp => writer_permute_interleaved_rm_row_invariant.cpp} (80%) diff --git a/tests/tt_eager/python_api_testing/sweep_tests/pytests/tt_dnn/test_permute.py b/tests/tt_eager/python_api_testing/sweep_tests/pytests/tt_dnn/test_permute.py index d9ab2571a58a..98699e2d4f20 100644 --- a/tests/tt_eager/python_api_testing/sweep_tests/pytests/tt_dnn/test_permute.py +++ b/tests/tt_eager/python_api_testing/sweep_tests/pytests/tt_dnn/test_permute.py @@ -20,7 +20,6 @@ ] -@skip_for_blackhole("Mismatching on BH, see #12349") @pytest.mark.parametrize("input_shapes, permute_args", params) def test_run_permute_test(input_shapes, permute_args, device, function_level_defaults): datagen_func = [ diff --git a/tests/tt_eager/python_api_testing/unit_testing/misc/test_transpose.py b/tests/tt_eager/python_api_testing/unit_testing/misc/test_transpose.py index 489b25ba5e9c..a8f8385c059b 100644 --- a/tests/tt_eager/python_api_testing/unit_testing/misc/test_transpose.py +++ b/tests/tt_eager/python_api_testing/unit_testing/misc/test_transpose.py @@ -299,7 +299,6 @@ def test_transpose_wh_sharded_program_cache(dtype, device, use_program_cache): ) -@skip_for_blackhole("Mismatching on BH, see #12349") @skip_for_grayskull("Grayskull has pcc issue when transpose used untilize") @pytest.mark.parametrize("n", [1]) @pytest.mark.parametrize("c", [1]) @@ -333,7 +332,6 @@ def test_tranpose_hw_rm_with_padding(device, n, c, h, w): assert_with_pcc(torch_output_tensor, activation_pyt_padded_out, 0.9999) -@skip_for_blackhole("Mismatching on BH, see #12349") @skip_for_grayskull("Grayskull has pcc issue when transpose used untilize") @pytest.mark.parametrize("n", [16]) @pytest.mark.parametrize("c", [128]) @@ -369,13 +367,10 @@ def run_tranpose_hw_rm_program_cache(device, n, c, h, w, use_program_cache): memory_config=ttnn.L1_MEMORY_CONFIG, ) activation_pyt_padded = ttnn.transpose(activation_pyt_padded, 2, 3, memory_config=ttnn.L1_MEMORY_CONFIG) - activation_pyt_padded_out = ttnn.to_memory_config(activation_pyt_padded, ttnn.L1_MEMORY_CONFIG) - activation_pyt_padded_out = ttnn.from_device(activation_pyt_padded_out) - activation_pyt_padded_out = ttnn.to_torch(activation_pyt_padded_out) + activation_pyt_padded_out = ttnn.to_torch(activation_pyt_padded) assert_with_pcc(torch_output_tensor, activation_pyt_padded_out, 0.9999) -@skip_for_blackhole("Mismatching on BH, see #12349") @skip_for_grayskull("Grayskull has pcc issue when transpose used untilize") @pytest.mark.parametrize("n", [16]) @pytest.mark.parametrize("c", [128]) @@ -402,7 +397,7 @@ def test_tranpose_hw_rm_with_program_cache(device, n, c, h, w, use_program_cache @pytest.mark.parametrize("c", [224]) @pytest.mark.parametrize("h", [16]) @pytest.mark.parametrize("w", [112]) -def test_tranpose_hw_sharded_rm(device, n, c, h, w): +def test_transpose_hw_sharded_rm(device, n, c, h, w): torch.manual_seed(2005) torch_input_tensor = torch.rand((n, c, h, w), dtype=torch.bfloat16) torch_output_tensor = torch_input_tensor.transpose(2, 3) @@ -469,7 +464,6 @@ def run_tranpose_hw_sharded_rm_with_program_cache(device, n, c, h, w): assert_with_pcc(torch_output_tensor, tt_output_tensor, 0.9999) -@skip_for_blackhole("Mismatching on BH, see #12349") @pytest.mark.parametrize("n", [16]) @pytest.mark.parametrize("c", [128]) @pytest.mark.parametrize("h", [128]) @@ -581,7 +575,6 @@ def run_tranpose_hc_sharded(device, n, c, h, w, grid_size): assert_with_pcc(torch_output_tensor, tt_output_tensor, 0.9999) -@skip_for_blackhole("Mismatching on BH, see #12349") @pytest.mark.parametrize( "n, c, h, w, grid_size", [ @@ -1011,3 +1004,76 @@ def test_transpose_forge_hc(device, b, h, w, dim0, dim1): output_tensor = ttnn.to_torch(output_tensor) assert_with_pcc(torch_output_tensor, output_tensor) + + +@pytest.mark.parametrize("n", [1]) +@pytest.mark.parametrize("c", [1]) +@pytest.mark.parametrize("h", [256]) +@pytest.mark.parametrize("w", [32]) +def test_tranpose_hw_sharded_tiled_8_cores(device, n, c, h, w): + torch.manual_seed(2005) + torch_input_tensor = torch.rand((n, c, h, w), dtype=torch.bfloat16) + torch_output_tensor = torch_input_tensor.transpose(2, 3) + tt_input_tensor = ttnn.from_torch( + torch_input_tensor, + dtype=ttnn.DataType.BFLOAT16, + layout=ttnn.TILE_LAYOUT, + device=device, + memory_config=ttnn.L1_MEMORY_CONFIG, + ) + + sharded_mem_config = ttnn.create_sharded_memory_config( + (32, 32), + core_grid=ttnn.CoreRangeSet( + { + ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(0, 6)), + ttnn.CoreRange(ttnn.CoreCoord(1, 0), ttnn.CoreCoord(1, 0)), + } + ), + strategy=ttnn.ShardStrategy.HEIGHT, + orientation=ttnn.ShardOrientation.COL_MAJOR, + use_height_and_width_as_shard_shape=True, + ) + tt_input_tensor = ttnn.to_memory_config(tt_input_tensor, sharded_mem_config) + + tt_output_tensor = ttnn.transpose(tt_input_tensor, 2, 3, memory_config=sharded_mem_config) + tt_output_tensor = ttnn.to_torch(tt_output_tensor) + + assert_with_pcc(torch_output_tensor, tt_output_tensor, 0.9999) + + +@pytest.mark.parametrize("n", [1]) +@pytest.mark.parametrize("c", [1]) +@pytest.mark.parametrize("h", [224]) +@pytest.mark.parametrize("w", [32]) +def test_tranpose_hw_sharded_tiled_n_cores(device, n, c, h, w): + torch.manual_seed(2005) + torch_input_tensor = torch.rand((n, c, h, w), dtype=torch.bfloat16) + torch_output_tensor = torch_input_tensor.transpose(2, 3) + tt_input_tensor = ttnn.from_torch( + torch_input_tensor, + dtype=ttnn.DataType.BFLOAT16, + layout=ttnn.TILE_LAYOUT, + device=device, + memory_config=ttnn.L1_MEMORY_CONFIG, + ) + + sharded_mem_config = ttnn.create_sharded_memory_config( + (32, 32), + core_grid=ttnn.CoreRangeSet( + { + ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(0, h // 32 - 1)), + } + ), + strategy=ttnn.ShardStrategy.HEIGHT, + orientation=ttnn.ShardOrientation.COL_MAJOR, + use_height_and_width_as_shard_shape=True, + ) + tt_input_tensor = ttnn.to_memory_config(tt_input_tensor, sharded_mem_config) + + tt_output_tensor = ttnn.transpose(tt_input_tensor, 2, 3, memory_config=sharded_mem_config) + tt_output_tensor = ttnn.to_memory_config(tt_output_tensor, ttnn.L1_MEMORY_CONFIG) + tt_output_tensor = ttnn.from_device(tt_output_tensor) + tt_output_tensor = ttnn.to_torch(tt_output_tensor) + + assert_with_pcc(torch_output_tensor, tt_output_tensor, 0.9999) diff --git a/tests/ttnn/unit_tests/operations/test_permute.py b/tests/ttnn/unit_tests/operations/test_permute.py index 40a57515f562..cc09f7d7d5ea 100644 --- a/tests/ttnn/unit_tests/operations/test_permute.py +++ b/tests/ttnn/unit_tests/operations/test_permute.py @@ -7,9 +7,10 @@ import torch import ttnn +import itertools from tests.ttnn.utils_for_testing import assert_with_pcc -from models.utility_functions import is_blackhole +from models.utility_functions import is_blackhole, is_grayskull, skip_for_grayskull, skip_for_blackhole @pytest.mark.parametrize("h", [32]) @@ -171,3 +172,101 @@ def test_permute_pad_value(device, pad_value): assert ttnn.to_torch(a) == float("-inf") tt_output = ttnn.to_torch(tt_output) assert_with_pcc(torch_output, tt_output, 0.9999) + + +def generate_permutations(N): + """ + Generator function that yields all permutations of tuples with values 0 to N-1. + + :param N: The number defining the range of values (0 to N-1). + :yield: Tuples representing each permutation. + """ + for perm in itertools.permutations(range(N)): + yield perm + + +@skip_for_blackhole("tilize_block gives bad pcc after second iteration") +@skip_for_grayskull("tilize_block gives bad pcc after second iteration") +@pytest.mark.parametrize("shape", [(7, 7, 7, 7, 7)]) +@pytest.mark.parametrize("perm", generate_permutations(5)) +@pytest.mark.parametrize("memory_config", [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG]) +@pytest.mark.parametrize("dtype", [ttnn.bfloat16, ttnn.float32]) +def test_permute_5d_width(shape, perm, memory_config, dtype, device): + torch.manual_seed(2005) + input_a = torch.randn(shape) + torch_output = torch.permute(input_a, perm) + + tt_input = ttnn.from_torch( + input_a, device=device, layout=ttnn.ROW_MAJOR_LAYOUT, dtype=dtype, memory_config=memory_config + ) + + tt_output = ttnn.permute(tt_input, perm) + tt_output = ttnn.to_torch(tt_output) + assert_with_pcc(torch_output, tt_output, 0.9999) + + +@skip_for_blackhole("tilize_block gives bad pcc after second iteration") +@skip_for_grayskull("tilize_block gives bad pcc after second iteration") +@pytest.mark.parametrize("shape", [(3, 65, 3, 3, 65), (1, 6, 256, 20, 50), (6, 20, 50, 1, 256)]) +@pytest.mark.parametrize("perm", [(4, 0, 3, 2, 1), (1, 3, 4, 0, 2), (3, 0, 4, 1, 2)]) +@pytest.mark.parametrize("memory_config", [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG]) +@pytest.mark.parametrize("dtype", [ttnn.bfloat16, ttnn.float32]) +def test_permute_5d_blocked(shape, perm, memory_config, dtype, device): + torch.manual_seed(520) + input_a = torch.randn(shape) + + torch_output = torch.permute(input_a, perm) + + tt_input = ttnn.from_torch( + input_a, device=device, layout=ttnn.ROW_MAJOR_LAYOUT, dtype=dtype, memory_config=memory_config + ) + + tt_output = ttnn.permute(tt_input, perm) + tt_output = ttnn.to_torch(tt_output) + + assert_with_pcc(torch_output, tt_output, 0.9999) + + +@skip_for_blackhole("tilize_block gives bad pcc after second iteration") +@skip_for_grayskull("tilize_block gives bad pcc after second iteration") +def test_permute_nd(device): + torch_tensor = torch.rand((1, 3, 16, 16, 16, 16), dtype=torch.bfloat16) + input_tensor = ttnn.from_torch(torch_tensor, layout=ttnn.ROW_MAJOR_LAYOUT, device=device) + output_tensor = ttnn.permute(input_tensor, (0, 2, 4, 3, 5, 1)) + output_tensor = ttnn.to_torch(output_tensor) + torch_output = torch.permute(torch_tensor, (0, 2, 4, 3, 5, 1)) + assert_with_pcc(torch_output, output_tensor, 0.9999) + + +def test_permute_squeeze(device): + ones = ttnn.ones((1, 1, 3)) + tensor = ttnn.to_device(ones, device) + out = ttnn.permute(tensor, (0, 1, 2)) + assert_with_pcc(ttnn.to_torch(out), ttnn.to_torch(ones), 0.9999) + + +@pytest.mark.parametrize("shape", [(1, 49, 768)]) +@pytest.mark.parametrize("perm", generate_permutations(3)) +@pytest.mark.parametrize("layout", [ttnn.TILE_LAYOUT]) +@pytest.mark.parametrize("memory_config", [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG]) +@pytest.mark.parametrize("dtype", [ttnn.bfloat16, ttnn.float32]) +def test_permute_3D(shape, perm, layout, memory_config, dtype, device): + if is_grayskull() and dtype == ttnn.float32: + pytest.skip("Grayskull doesn't support float32") + torch_tensor = torch.rand(shape, dtype=torch.bfloat16) + input_tensor = ttnn.from_torch(torch_tensor, layout=layout, device=device, dtype=dtype, memory_config=memory_config) + output_tensor = ttnn.permute(input_tensor, perm) + output_tensor = ttnn.to_torch(output_tensor) + torch_output = torch.permute(torch_tensor, perm) + assert torch_output.shape == output_tensor.shape + assert_with_pcc(torch_output, output_tensor, 0.9999) + + +def test_nil_volume_permute(device): + torch_tensor = torch.rand([1, 0, 30, 32], dtype=torch.bfloat16) + input_tensor = ttnn.from_torch(torch_tensor, layout=ttnn.TILE_LAYOUT, device=device) + output_tensor = ttnn.permute(input_tensor, (0, 1, 3, 2)) + output_tensor = ttnn.to_torch(output_tensor) + torch_output = torch.permute(torch_tensor, (0, 1, 3, 2)) + assert torch_output.shape == output_tensor.shape + assert_with_pcc(torch_output, output_tensor, 0.9999) diff --git a/tests/ttnn/unit_tests/operations/test_uniform.py b/tests/ttnn/unit_tests/operations/test_uniform.py index 9c3f05a6a6a8..abdfd9aaa31a 100644 --- a/tests/ttnn/unit_tests/operations/test_uniform.py +++ b/tests/ttnn/unit_tests/operations/test_uniform.py @@ -94,6 +94,7 @@ def run_uniform(shape, rand_range, dtype, device, compute_kernel_options=None, m ) +@pytest.mark.skip("#16066: Undefined behaviour. It will fail on some runs and pass on others since it's stochastic.") @skip_for_grayskull("Requires wormhole_b0 to run") @pytest.mark.parametrize( "shape", diff --git a/ttnn/cpp/ttnn/operations/data_movement/common/kernels/common.hpp b/ttnn/cpp/ttnn/operations/data_movement/common/kernels/common.hpp index 27c68f53b18f..34cf4e3eb3b9 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/common/kernels/common.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/common/kernels/common.hpp @@ -137,4 +137,43 @@ template FORCE_INLINE constexpr uint32_t round_up() { return b * div_up(); } + +// Function template to swap two elements in a uint32_t array +template +FORCE_INLINE void swap_elements(uint32_t (&array)[N], size_t i, size_t j) { + // Perform the swap + uint32_t temp = array[i]; + array[i] = array[j]; + array[j] = temp; +} + +// 2D Transpose function for debug use in reader/writer kernels +FORCE_INLINE void transpose_2d( + uint32_t input_l1_addr, + uint32_t output_l1_addr, + uint32_t X, + uint32_t W, + uint32_t element_size, + uint32_t input_page_size, + uint32_t output_page_size) { + volatile tt_l1_ptr uint8_t* input_ptr = reinterpret_cast(input_l1_addr); + volatile tt_l1_ptr uint8_t* output_ptr = reinterpret_cast(output_l1_addr); + // transpose from XW, where X is outer and W inner, to WX, where W is outer and X is inner + // each element is element_size bytes + // each row is W elements, and each row is separated by input_page_size bytes + // each output row is X elements, and each row is separated by output_page_size bytes + + for (uint32_t x = 0; x < X; ++x) { + for (uint32_t w = 0; w < W; ++w) { + // Compute the input and output addresses + uint32_t input_addr = x * input_page_size + w * element_size; + uint32_t output_addr = w * output_page_size + x * element_size; + // Copy the element - do we have memcpy? use this for now + for (uint32_t i = 0; i < element_size; ++i) { + output_ptr[output_addr + i] = input_ptr[input_addr + i]; + } + } + } +} + } // namespace tt::data_movement::common diff --git a/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/compute/transpose_xw_rm_single_tile_size.cpp b/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/compute/transpose_xw_rm_single_tile_size.cpp new file mode 100644 index 000000000000..411510700649 --- /dev/null +++ b/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/compute/transpose_xw_rm_single_tile_size.cpp @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "compute_kernel_api/eltwise_unary/eltwise_unary.h" +#include "compute_kernel_api/transpose_wh.h" +#include "compute_kernel_api/tilize.h" +#include "compute_kernel_api/untilize.h" +#include "compute_kernel_api/pack_untilize.h" + +namespace NAMESPACE { +void MAIN { + constexpr uint32_t x_block_size = get_compile_time_arg_val(0); + constexpr uint32_t w_block_size = get_compile_time_arg_val(1); + + uint32_t num_blocks = get_arg_val(0); + + constexpr auto cb_in = tt::CBIndex::c_0; + constexpr auto cb_tilize = tt::CBIndex::c_1; + constexpr auto cb_out = tt::CBIndex::c_2; + + unary_op_init_common(cb_in, cb_out); + + for (uint32_t n = 0; n < num_blocks; n++) { + // tilize input via unpack and then pack + tilize_init_short(cb_in, 1); + + cb_wait_front(cb_in, x_block_size); + cb_reserve_back(cb_tilize, 1); + + tilize_block(cb_in, 1, cb_tilize); // tilize and pack into cb_tilize + + // tile slice according to unpacker is garbage after tilize_block in the second iteration, missing an uninit? + cb_push_back(cb_tilize, 1); + cb_pop_front(cb_in, x_block_size); + + tilize_uninit(cb_in); + + // transpose input + cb_wait_front(cb_tilize, 1); + transpose_wh_init_short(cb_tilize); + pack_untilize_dst_init_short<1>(cb_out); + + tile_regs_acquire(); + transpose_wh_tile(cb_tilize, 0, 0); // transpose call + tile_regs_commit(); + + // pack and untilize + cb_reserve_back(cb_out, w_block_size); + + tile_regs_wait(); + pack_untilize_dst<1>(cb_out); // pack call + tile_regs_release(); + + cb_push_back(cb_out, w_block_size); + + cb_wait_front(cb_out, w_block_size); + pack_untilize_uninit(cb_out); + + cb_pop_front(cb_tilize, 1); + } +} +} // namespace NAMESPACE diff --git a/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/reader_permute_interleaved_rm_blocked_generic.cpp b/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/reader_permute_interleaved_rm_blocked_generic.cpp new file mode 100644 index 000000000000..f63aaab6d09f --- /dev/null +++ b/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/reader_permute_interleaved_rm_blocked_generic.cpp @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include "dataflow_api.h" + +void kernel_main() { + constexpr bool src0_is_dram = (bool)get_compile_time_arg_val(0); + constexpr uint32_t N = get_compile_time_arg_val(1); + constexpr uint32_t input_cb_page_size = get_compile_time_arg_val(2); + constexpr uint32_t num_rows = get_compile_time_arg_val(3); + constexpr uint32_t x_dim = get_compile_time_arg_val(4); + constexpr uint32_t num_blocks_total = get_compile_time_arg_val(5); + constexpr uint32_t x_blocks = get_compile_time_arg_val(6); + constexpr uint32_t w_blocks = get_compile_time_arg_val(7); + constexpr uint32_t x_block_size = get_compile_time_arg_val(8); + constexpr uint32_t w_block_size = get_compile_time_arg_val(9); + constexpr uint32_t element_size = get_compile_time_arg_val(10); + constexpr uint32_t input_tensor_page_size = get_compile_time_arg_val(11); + + // Precomputed constants: size of a 32 element block along the W dimension (measured in bytes) + constexpr uint32_t w_block_size_bytes = w_block_size * element_size; + + const uint32_t src_addr = get_arg_val(0); + uint32_t start_block = get_arg_val(1); + uint32_t end_block = get_arg_val(2); + + // Input shape and strides (excluding W dimension and measured in rows, not bytes) + // start at runtime arg 3 since address/start_block/end_block make up the first 3 args + uint32_t input_shape[N], src_strides[N]; + for (uint32_t i = 3; i < N + 3; i++) { + input_shape[i - 3] = get_arg_val(i); + src_strides[i - 3] = get_arg_val(i + N); + } + + /** + * We have a multidimensional tensor: + * - num_blocks_total = (rows * x_blocks * w_blocks) where rows = num_rows / X + * Here, 'rows' represent the combination of all rows before and after X dimension. + * So: rows * X * W_dimension = total number of elements (conceptually). + * + * For each 'block': + * - Compute which w_block and x_block this corresponds to. + * - Then compute which row set (xw_block) we are in. + */ + + // x_dim is the dimension along which we are reading the tensor, as it's the new W dimension in the output tensor + uint32_t X = input_shape[x_dim]; + uint32_t X_stride = src_strides[x_dim]; + + const InterleavedAddrGen s0 = {.bank_base_address = src_addr, .page_size = input_tensor_page_size}; + + uint32_t idxs[N]; + idxs[N - 1] = 0; + uint32_t non_x_rows = num_rows / X; + + for (uint32_t block = start_block; block < end_block; ++block) { + // Decompose block into w_block, x_block, and xw_block indices + uint32_t rem = block; + const uint32_t w_block = rem % w_blocks; // Which W block are we in? + rem /= w_blocks; + + const uint32_t x_block = rem % x_blocks; // Which X block? + rem /= x_blocks; + + uint32_t xw_block = rem % (non_x_rows); // Which row set (beyond X dimension)? + uint32_t remainder = xw_block; + + // Compute X block boundaries + uint32_t x_start = x_block * x_block_size; + uint32_t x_end = min(x_start + x_block_size, X); + + // Compute W block boundaries + uint32_t w_start = w_block * w_block_size; + uint32_t w_end = min(w_start + w_block_size, input_shape[N - 1]); + uint32_t w_offset = w_start * element_size; + + uint32_t w_read_size_bytes = (w_end - w_start) * element_size; + + // Map linear index i to multidimensional indices idxs[] + // We skip x_dim when doing this mapping and set it separately later + for (int32_t d = N - 2; d >= 0; --d) { // Exclude W dimension + if (d == (int32_t)x_dim) { + idxs[d] = 0; // Initialize x_dim to zero (will be set in inner loop) + continue; // Skip x_dim during mapping + } + idxs[d] = remainder % input_shape[d]; + remainder /= input_shape[d]; + } + idxs[N - 1] = 0; // Initialize W dimension index to zero if not already set + + // Precompute the base address offset (excluding x_dim) + uint64_t base_addr_offset = 0; + for (uint32_t d = 0; d < N; ++d) { + if (d != x_dim) { + base_addr_offset += idxs[d] * src_strides[d]; + } + } + + // Reserve space in the circular buffer for the X-block length + cb_reserve_back(tt::CBIndex::c_0, x_block_size); + uint32_t src_buffer_l1_addr = get_write_ptr(tt::CBIndex::c_0); + + // We read in 'x_block_len' chunks along the X dimension + uint32_t page_offset = 0; + // Read along the X dimension + for (uint32_t x = x_start; x < x_end; ++x) { + // Compute the address offset for this index + uint64_t addr_offset = base_addr_offset + x * X_stride; + uint64_t src_noc_addr = get_noc_addr(addr_offset, s0, w_offset); + + // Perform async read of the current line (w_block_len elements) into L1 + noc_async_read(src_noc_addr, src_buffer_l1_addr + page_offset, w_read_size_bytes); + + // Advance output pointer by one page size for next row + page_offset += input_cb_page_size; + } + // Wait for all async reads to complete before proceeding + noc_async_read_barrier(); + // Push the filled block into the circular buffer + cb_push_back(tt::CBIndex::c_0, x_block_size); + } +} diff --git a/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/reader_permute_interleaved_rm.cpp b/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/reader_permute_interleaved_rm_row_invariant.cpp similarity index 78% rename from ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/reader_permute_interleaved_rm.cpp rename to ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/reader_permute_interleaved_rm_row_invariant.cpp index 73241cb9703e..93a42f813251 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/reader_permute_interleaved_rm.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/reader_permute_interleaved_rm_row_invariant.cpp @@ -12,16 +12,17 @@ void kernel_main() { constexpr uint32_t num_rows = get_compile_time_arg_val(3); const uint32_t src_addr = get_arg_val(0); + const uint32_t start_row = get_arg_val(1); + const uint32_t end_row = get_arg_val(2); const InterleavedAddrGen s0 = {.bank_base_address = src_addr, .page_size = page_size}; uint32_t curr_addr = src_addr; - for (uint32_t i = 0; i < num_rows; ++i) { + for (uint32_t row = start_row; row < end_row; ++row) { cb_reserve_back(tt::CBIndex::c_0, 1); uint32_t src_buffer_l1_addr = get_write_ptr(tt::CBIndex::c_0); - noc_async_read_page(i, s0, src_buffer_l1_addr); + noc_async_read_page(row, s0, src_buffer_l1_addr); noc_async_read_barrier(); - volatile tt_l1_ptr uint16_t* out_stick = reinterpret_cast(src_buffer_l1_addr); cb_push_back(tt::CBIndex::c_0, 1); } } diff --git a/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/writer_permute_interleaved_rm_blocked_generic.cpp b/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/writer_permute_interleaved_rm_blocked_generic.cpp new file mode 100644 index 000000000000..5af2edb379f1 --- /dev/null +++ b/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/writer_permute_interleaved_rm_blocked_generic.cpp @@ -0,0 +1,167 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include "dataflow_api.h" +#include "ttnn/cpp/ttnn/operations/data_movement/common/kernels/common.hpp" + +void kernel_main() { + // Compile-time constants + constexpr bool dst_is_dram = (bool)get_compile_time_arg_val(0); + constexpr uint32_t N = get_compile_time_arg_val(1); + constexpr uint32_t output_cb_page_size = get_compile_time_arg_val(2); + constexpr uint32_t num_rows = get_compile_time_arg_val(3); + + constexpr uint32_t X = get_compile_time_arg_val(4); + constexpr uint32_t X_stride = get_compile_time_arg_val(5); + constexpr uint32_t x_dim = get_compile_time_arg_val(6); + + constexpr uint32_t W_stride = get_compile_time_arg_val(7); + constexpr uint32_t input_cb_page_size = get_compile_time_arg_val(8); + constexpr uint32_t element_size = get_compile_time_arg_val(9); + + constexpr uint32_t num_blocks_total = get_compile_time_arg_val(10); + constexpr uint32_t x_blocks = get_compile_time_arg_val(11); + constexpr uint32_t w_blocks = get_compile_time_arg_val(12); + constexpr uint32_t x_block_size = get_compile_time_arg_val(13); + constexpr uint32_t w_block_size = get_compile_time_arg_val(14); + constexpr uint32_t W = get_compile_time_arg_val(15); + constexpr uint32_t output_tensor_page_size = get_compile_time_arg_val(16); + + constexpr uint32_t cb_id_in = tt::CBIndex::c_2; + + // Precompute bytes-per-block along X + constexpr uint32_t x_block_size_bytes = x_block_size * element_size; + + // W dimension is always the last dimension + constexpr uint32_t w_dim = N - 1; + + // Calculate how many "non_x_rows" we have (these are the combinations of all dimensions except X) + constexpr uint32_t non_x_rows = num_rows / X; + + // Destination base address + const uint32_t dst_addr = get_arg_val(0); + const uint32_t start_block = get_arg_val(1); + const uint32_t end_block = get_arg_val(2); + + // Interleaved address configuration for the destination + const InterleavedAddrGen s0 = {.bank_base_address = dst_addr, .page_size = output_tensor_page_size}; + + // Input shape, permutation, and destination strides + // start at runtime arg 3 since address/start_block/end_block make up the first 3 args + uint32_t input_shape[N], perm[N], dest_strides[N]; + for (uint32_t i = 3; i < N + 3; i++) { + input_shape[i - 3] = get_arg_val(i); + perm[i - 3] = get_arg_val(i + N); + dest_strides[i - 3] = get_arg_val(i + 2 * N); + } + + // The source data was transposed between W and X by the previous kernel. + // Adjust input_shape and perm to reflect that swap. + tt::data_movement::common::swap_elements(input_shape, x_dim, w_dim); + for (uint32_t i = 0; i < N; i++) { + if (perm[i] == x_dim) { + perm[i] = w_dim; + } else if (perm[i] == w_dim) { + perm[i] = x_dim; + } + } + + // Find where the original X dimension ended up in the permuted output + uint32_t x_dim_in_dest = N; // Will hold the position of x_dim in the permuted array + for (uint32_t i = 0; i < N; ++i) { + if (perm[i] == x_dim) { + x_dim_in_dest = i; + break; + } + } + + uint32_t src_multi_idx[N] = {0}; + uint32_t dest_multi_idx[N] = {0}; + + // Process each block of data from start_block to end_block + for (uint32_t block = start_block; block < end_block; ++block) { + // Decompose linear block index into w_block, x_block, and xw_block + uint32_t rem = block; + + // w_block: portion of the W dimension handled by this block + const uint32_t w_block = rem % w_blocks; + rem /= w_blocks; + + // x_block: portion of the X dimension handled by this block + const uint32_t x_block = rem % x_blocks; + rem /= x_blocks; + + // xw_block: which "non-X row set" we are in + const uint32_t xw_block = rem % non_x_rows; + + // Compute start/end boundaries for the current X and W blocks + const uint32_t x_start = x_block * x_block_size; + const uint32_t x_end = min(x_start + x_block_size, X); + + const uint32_t w_start = w_block * w_block_size; + const uint32_t w_end = min(w_start + w_block_size, W); + + // Compute the read size for the X dimension + const uint32_t x_read_size_bytes = (x_end - x_start) * element_size; + const uint32_t x_offset = x_start * element_size; + + // Decode xw_block into multi-dimensional indices excluding the W dimension and X dimension + uint32_t remainder = xw_block; + for (int32_t d = N - 2; d >= 0; --d) { + if (d == (int32_t)x_dim) { + // Skip the original X dimension index during this mapping + continue; + } + src_multi_idx[d] = remainder % input_shape[d]; + remainder /= input_shape[d]; + } + + // Compute dest_multi_idx (excluding W dimension), and a base linear index + // for all dimensions except W and X. We'll add W and X offsets later. + uint32_t dest_linear_idx_base = 0; + for (uint32_t i = 0; i < N; ++i) { + uint32_t src_idx = perm[i]; + if (src_idx != x_dim) { + dest_multi_idx[i] = src_multi_idx[src_idx]; + // Accumulate partial index product for all dimensions except W + if (i < w_dim) { + dest_linear_idx_base += dest_multi_idx[i] * dest_strides[i]; + } + } + } + + // Wait for the transposed block data to be ready in the input CB + cb_wait_front(cb_id_in, w_block_size); + uint32_t transposed_buffer_read_addr = get_read_ptr(cb_id_in); + + // Iterate over the W dimension elements + for (uint32_t w = w_start; w < w_end; ++w) { + // Update indices for the current W + src_multi_idx[x_dim] = w; + dest_multi_idx[x_dim_in_dest] = w; + + // Compute final linear index for the current W + uint32_t dest_linear_idx = dest_linear_idx_base; + if (x_dim_in_dest < w_dim) { + dest_linear_idx += dest_multi_idx[x_dim_in_dest] * dest_strides[x_dim_in_dest]; + } + + // Compute the NoC address for the output + uint64_t dst_noc_addr = get_noc_addr(dest_linear_idx, s0, x_offset); + + // Compute the L1 address from which to write (offset by W-block pages) + uint32_t l1_addr = transposed_buffer_read_addr + (w - w_start) * output_cb_page_size; + + // Perform an asynchronous write of the X-block to the destination + noc_async_write(l1_addr, dst_noc_addr, x_read_size_bytes); + } + + // Wait until all writes are completed before proceeding to the next block + noc_async_write_barrier(); + + // Pop the block from the input circular buffer, as we're done writing it + cb_pop_front(cb_id_in, w_block_size); + } +} diff --git a/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/writer_permute_interleaved_rm.cpp b/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/writer_permute_interleaved_rm_row_invariant.cpp similarity index 80% rename from ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/writer_permute_interleaved_rm.cpp rename to ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/writer_permute_interleaved_rm_row_invariant.cpp index 34be75dfdf45..a06e5d568921 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/writer_permute_interleaved_rm.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/writer_permute_interleaved_rm_row_invariant.cpp @@ -12,19 +12,22 @@ void kernel_main() { constexpr uint32_t num_rows = get_compile_time_arg_val(3); const uint32_t dst_addr = get_arg_val(0); + const uint32_t start_row = get_arg_val(1); + const uint32_t end_row = get_arg_val(2); const InterleavedAddrGen s0 = {.bank_base_address = dst_addr, .page_size = page_size}; + // start at runtime arg 3 since address/start_block/end_block make up the first 3 args uint32_t input_shape[N], perm[N], dest_strides[N]; - for (uint32_t i = 1; i <= N; i++) { - input_shape[i - 1] = get_arg_val(i); - perm[i - 1] = get_arg_val(i + N); - dest_strides[i - 1] = get_arg_val(i + 2 * N); + for (uint32_t i = 3; i < N + 3; i++) { + input_shape[i - 3] = get_arg_val(i); + perm[i - 3] = get_arg_val(i + N); + dest_strides[i - 3] = get_arg_val(i + 2 * N); } uint32_t src_buffer_l1_addr = get_write_ptr(tt::CBIndex::c_0); uint32_t curr_addr = dst_addr; - for (uint32_t row = 0; row < num_rows; ++row) { + for (uint32_t row = start_row; row < end_row; ++row) { // Compute multi-dimensional index for the source row uint32_t src_multi_idx[N]; size_t remaining = row; diff --git a/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.cpp b/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.cpp index bbe319681bba..8bc4bece3b01 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.cpp @@ -12,7 +12,12 @@ namespace ttnn::operations::data_movement { PermuteDeviceOperation::program_factory_t PermuteDeviceOperation::select_program_factory( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - return SingleCore{}; + // If the last dimension is not permuted, we can use the row-invariant kernel + if (operation_attributes.dims.back() == tensor_args.input_tensor.get_logical_shape().rank() - 1) { + return MultiCoreRowInvariant{}; + } + // Otherwise, we need to use the blocked generic, row moving kernel + return MultiCoreBlockedGeneric{}; } void PermuteDeviceOperation::validate_on_program_cache_miss( @@ -20,10 +25,6 @@ void PermuteDeviceOperation::validate_on_program_cache_miss( TT_FATAL( attributes.dims.size() == tensor_args.input_tensor.get_logical_shape().rank(), "Permute dimensions must match input tensor rank"); - TT_FATAL( - attributes.dims.back() == tensor_args.input_tensor.get_logical_shape().rank() - 1, - "Last dimension of permute must be the last dimension of the input tensor as page-breaking is not supported at " - "the moment"); TT_FATAL(tensor_args.input_tensor.is_sharded() == false, "Permute operation does not support sharded input tensor"); TT_FATAL( tensor_args.input_tensor.get_layout() == Layout::ROW_MAJOR, "Permute operation only supports row-major layout"); @@ -34,7 +35,7 @@ void PermuteDeviceOperation::validate_on_program_cache_hit( PermuteDeviceOperation::shape_return_value_t PermuteDeviceOperation::compute_output_shapes( const operation_attributes_t& attributes, const tensor_args_t& tensor_args) { - SmallVector shape, padded_shape; + SmallVector shape; auto input_shape = tensor_args.input_tensor.get_logical_shape(); shape.reserve(input_shape.rank()); for (auto dim : attributes.dims) { diff --git a/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.hpp b/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.hpp index 2f9481feb8c1..05e251e8ca89 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.hpp @@ -30,11 +30,12 @@ struct PermuteDeviceOperation { using tensor_return_value_t = Tensor; - struct SingleCore { + struct MultiCoreRowInvariant { // Shared variables are the variables that are shared between the create and override_runtime_arguments methods struct shared_variables_t { KernelHandle unary_reader_kernel_id; KernelHandle unary_writer_kernel_id; + CoreRangeSet core_range; }; using cached_program_t = ttnn::device_operation::CachedProgram; @@ -49,7 +50,30 @@ struct PermuteDeviceOperation { const tensor_args_t& tensor_args, tensor_return_value_t& tensor_return_value); }; - using program_factory_t = std::variant; + + struct MultiCoreBlockedGeneric { + // Shared variables are the variables that are shared between the create and override_runtime_arguments methods + struct shared_variables_t { + KernelHandle unary_reader_kernel_id; + KernelHandle unary_writer_kernel_id; + KernelHandle compute_kernel_id; + CoreRangeSet core_range; + }; + using cached_program_t = ttnn::device_operation::CachedProgram; + + static cached_program_t create( + const operation_attributes_t& operation_attributes, + const tensor_args_t& tensor_args, + tensor_return_value_t& tensor_return_value); + + static void override_runtime_arguments( + cached_program_t& cached_program, + const operation_attributes_t& operation_attributes, + const tensor_args_t& tensor_args, + tensor_return_value_t& tensor_return_value); + }; + + using program_factory_t = std::variant; // Mandatory methods diff --git a/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_program_factory.cpp b/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_program_factory.cpp index 29f6065cb5bd..56bfe893f5d9 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_program_factory.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_program_factory.cpp @@ -4,18 +4,20 @@ #include "ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.hpp" #include "tt_metal/common/work_split.hpp" +#include "noc/noc_parameters.h" // DRAM_ALIGNMENT namespace ttnn::operations::data_movement { namespace detail { uint32_t num_pages(const ttnn::Tensor& input_tensor) { - const auto& padded_shape = input_tensor.get_logical_shape(); - return padded_shape.volume() / padded_shape[-1]; + const auto& shape = input_tensor.get_logical_shape(); + return shape.volume() / shape[-1]; } uint32_t page_size(const ttnn::Tensor& input_tensor) { - const auto& padded_shape = input_tensor.get_logical_shape(); // in anticipation of RM padding - return padded_shape[-1] * input_tensor.element_size(); + auto BUFFER_ALIGNMENT = input_tensor.buffer()->buffer_type() == tt::tt_metal::BufferType::DRAM ? DRAM_ALIGNMENT : L1_ALIGNMENT; + const auto& shape = input_tensor.get_logical_shape(); // in anticipation of RM padding + return tt::round_up(shape[-1] * input_tensor.element_size(), BUFFER_ALIGNMENT); } std::vector get_row_strides(const ttnn::SimpleShape& shape) { @@ -27,9 +29,10 @@ std::vector get_row_strides(const ttnn::SimpleShape& shape) { } return strides; } + } // namespace detail -PermuteDeviceOperation::SingleCore::cached_program_t PermuteDeviceOperation::SingleCore::create( +PermuteDeviceOperation::MultiCoreRowInvariant::cached_program_t PermuteDeviceOperation::MultiCoreRowInvariant::create( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args, tensor_return_value_t& tensor_return_value) { @@ -55,56 +58,80 @@ PermuteDeviceOperation::SingleCore::cached_program_t PermuteDeviceOperation::Sin tt::tt_metal::Device* device = input_tensor.device(); uint32_t src0_cb_index = tt::CBIndex::c_0; - uint32_t num_input_pages_to_read = 1; + uint32_t num_input_pages_to_read = 2; + + uint32_t num_rows = input_tensor.volume() / input_tensor.get_logical_shape()[-1]; + + auto compute_with_storage_grid_size = input_tensor.device()->compute_with_storage_grid_size(); + auto [num_cores, all_cores, core_group_1, core_group_2, num_tiles_per_core_group_1, num_tiles_per_core_group_2] = + tt::tt_metal::split_work_to_cores(compute_with_storage_grid_size, num_rows); - CoreRange core({0, 0}, {0, 0}); tt::tt_metal::CircularBufferConfig cb_src0_config = tt::tt_metal::CircularBufferConfig( num_input_pages_to_read * input_rm_page_size, {{src0_cb_index, cb_data_format}}) .set_page_size(src0_cb_index, input_rm_page_size); - auto cb_src0 = tt::tt_metal::CreateCircularBuffer(program, core, cb_src0_config); + auto cb_src0 = tt::tt_metal::CreateCircularBuffer(program, all_cores, cb_src0_config); uint32_t N = operation_attributes.dims.size(); - uint32_t num_rows = input_tensor.volume() / input_tensor.get_logical_shape()[-1]; bool src_is_dram = src_buffer->buffer_type() == tt::tt_metal::BufferType::DRAM ? 1 : 0; std::vector reader_compile_time_args = {(uint32_t)src_is_dram, N, input_rm_page_size, num_rows}; tt::tt_metal::KernelHandle unary_reader_kernel_id = tt::tt_metal::CreateKernel( program, - "ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/reader_permute_interleaved_rm.cpp", - core, + "ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/" + "reader_permute_interleaved_rm_row_invariant.cpp", + all_cores, tt::tt_metal::ReaderDataMovementConfig(reader_compile_time_args)); bool dst_is_dram = dst_buffer->buffer_type() == tt::tt_metal::BufferType::DRAM ? 1 : 0; std::vector writer_compile_time_args = {(std::uint32_t)dst_is_dram, N, output_rm_page_size, num_rows}; tt::tt_metal::KernelHandle unary_writer_kernel_id = tt::tt_metal::CreateKernel( program, - "ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/writer_permute_interleaved_rm.cpp", - core, + "ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/" + "writer_permute_interleaved_rm_row_invariant.cpp", + all_cores, tt::tt_metal::WriterDataMovementConfig(writer_compile_time_args)); - std::vector reader_runtime_args = {src_buffer->address()}; - - tt::tt_metal::SetRuntimeArgs(program, unary_reader_kernel_id, core, reader_runtime_args); + std::vector reader_runtime_args = {src_buffer->address(), 0, 0}; auto input_shape_view = input_tensor.get_logical_shape().view(); auto output_strides = detail::get_row_strides(output_tensor.get_logical_shape()); // in anticipation of RM padding - std::vector writer_runtime_args = {dst_buffer->address()}; + std::vector writer_runtime_args = {dst_buffer->address(), 0, 0}; writer_runtime_args.insert(writer_runtime_args.end(), input_shape_view.begin(), input_shape_view.end()); writer_runtime_args.insert( writer_runtime_args.end(), operation_attributes.dims.begin(), operation_attributes.dims.end()); writer_runtime_args.insert(writer_runtime_args.end(), output_strides.begin(), output_strides.end()); - tt::tt_metal::SetRuntimeArgs(program, unary_writer_kernel_id, core, writer_runtime_args); + auto cores = corerange_to_cores(all_cores, std::nullopt); + uint32_t start_row = 0; + uint32_t num_rows_per_core = 0; + for (const auto& core : cores) { + if (core_group_1.contains(core)) { + num_rows_per_core = num_tiles_per_core_group_1; + } else if (core_group_2.contains(core)) { + num_rows_per_core = num_tiles_per_core_group_2; + } else { + // no-op + num_rows_per_core = 0; + } + uint32_t end_row = start_row + num_rows_per_core; + reader_runtime_args[1] = start_row; + reader_runtime_args[2] = end_row; + writer_runtime_args[1] = start_row; + writer_runtime_args[2] = end_row; + tt::tt_metal::SetRuntimeArgs(program, unary_reader_kernel_id, core, reader_runtime_args); + tt::tt_metal::SetRuntimeArgs(program, unary_writer_kernel_id, core, writer_runtime_args); + start_row = end_row; + } return { std::move(program), {.unary_reader_kernel_id = unary_reader_kernel_id, .unary_writer_kernel_id = unary_writer_kernel_id}}; } -void PermuteDeviceOperation::SingleCore::override_runtime_arguments( +void PermuteDeviceOperation::MultiCoreRowInvariant::override_runtime_arguments( cached_program_t& cached_program, const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args, @@ -118,15 +145,228 @@ void PermuteDeviceOperation::SingleCore::override_runtime_arguments( auto src_buffer = input_tensor.buffer(); auto dst_buffer = output_tensor.buffer(); + auto& all_cores = cached_program.shared_variables.core_range; - { - auto& runtime_args = tt::tt_metal::GetRuntimeArgs(program, unary_reader_kernel_id, CoreCoord{0, 0}); + auto cores = corerange_to_cores(all_cores, std::nullopt); + for (const auto& core : cores) { + auto& runtime_args = tt::tt_metal::GetRuntimeArgs(program, unary_reader_kernel_id, core); runtime_args[0] = src_buffer->address(); + auto& runtime_args_writer = tt::tt_metal::GetRuntimeArgs(program, unary_writer_kernel_id, core); + runtime_args_writer[0] = dst_buffer->address(); } +} + +PermuteDeviceOperation::MultiCoreBlockedGeneric::cached_program_t +PermuteDeviceOperation::MultiCoreBlockedGeneric::create( + const operation_attributes_t& operation_attributes, + const tensor_args_t& tensor_args, + tensor_return_value_t& tensor_return_value) { + using namespace tt; + using namespace tt::tt_metal; + + const auto& input_tensor = tensor_args.input_tensor; + auto& output_tensor = tensor_return_value; + + auto src_buffer = input_tensor.buffer(); + auto dst_buffer = output_tensor.buffer(); + + tt::tt_metal::Program program{}; + + tt::DataFormat cb_data_format = tt::tt_metal::datatype_to_dataformat_converter(input_tensor.get_dtype()); + uint32_t w_block_size = constants::TILE_WIDTH; + uint32_t input_cb_page_size = w_block_size * input_tensor.element_size(); + + tt::DataFormat cb_data_format_output = tt::tt_metal::datatype_to_dataformat_converter(output_tensor.get_dtype()); + uint32_t x_block_size = constants::TILE_HEIGHT; + uint32_t output_cb_page_size = x_block_size * input_tensor.element_size(); + + tt::tt_metal::Device* device = input_tensor.device(); + + uint32_t src0_cb_index = tt::CBIndex::c_0; + uint32_t src1_cb_index = tt::CBIndex::c_2; + uint32_t src2_cb_index = tt::CBIndex::c_1; + uint32_t num_input_pages_to_read = 2; + + // we are focused on reading one row at a time, in a pattern that allows us to write an entire output row at a time + // if W is being swapped with another dim X (e.g. H), then we need to read X rows at a time (X is the new row + // dimension) CB is thus X pages in size (X*W*element_size) we read in X input rows of size W, and write out W + // output rows of size X find the new row dimension (X) + + uint32_t x_dim = operation_attributes.dims.back(); + uint32_t X = input_tensor.get_logical_shape()[x_dim]; + // stride from one row to the next for each dim in the input tensor + auto input_strides = detail::get_row_strides(input_tensor.get_logical_shape()); + uint32_t X_stride = input_strides[x_dim]; + + auto output_strides = detail::get_row_strides(output_tensor.get_logical_shape()); + // after we transpose X and W, we need to stride from one row to the next for each dim in the output tensor + uint32_t W = input_tensor.get_logical_shape()[-1]; + uint32_t W_stride = output_strides[x_dim]; + + uint32_t N = operation_attributes.dims.size(); + uint32_t num_rows = input_tensor.volume() / input_tensor.get_logical_shape()[-1]; + + // treat the input tensor as 3D with rows * x_blocks * w_blocks + uint32_t x_blocks = tt::div_up(X, x_block_size); + uint32_t w_blocks = tt::div_up(W, w_block_size); + uint32_t num_blocks_total = (num_rows / X) * x_blocks * w_blocks; + + auto compute_with_storage_grid_size = input_tensor.device()->compute_with_storage_grid_size(); + auto [num_cores, all_cores, core_group_1, core_group_2, num_tiles_per_core_group_1, num_tiles_per_core_group_2] = + tt::tt_metal::split_work_to_cores(compute_with_storage_grid_size, num_blocks_total); + + tt::tt_metal::CircularBufferConfig cb_src0_config = + tt::tt_metal::CircularBufferConfig( + num_input_pages_to_read * input_cb_page_size * x_block_size, {{src0_cb_index, cb_data_format}}) + .set_page_size(src0_cb_index, input_cb_page_size); + auto cb_src0 = tt::tt_metal::CreateCircularBuffer(program, all_cores, cb_src0_config); + + tt::tt_metal::CircularBufferConfig cb_src1_config = + tt::tt_metal::CircularBufferConfig( + num_input_pages_to_read * output_cb_page_size * w_block_size, {{src1_cb_index, cb_data_format}}) + .set_page_size(src1_cb_index, output_cb_page_size); + auto cb_src1 = tt::tt_metal::CreateCircularBuffer(program, all_cores, cb_src1_config); + + tt::tt_metal::CircularBufferConfig cb_src2_config = + tt::tt_metal::CircularBufferConfig( + num_input_pages_to_read * x_block_size * w_block_size * input_tensor.element_size(), + {{src2_cb_index, cb_data_format}}) + .set_page_size(src2_cb_index, x_block_size * w_block_size * input_tensor.element_size()); + auto cb_src2 = tt::tt_metal::CreateCircularBuffer(program, all_cores, cb_src2_config); + + bool src_is_dram = src_buffer->buffer_type() == tt::tt_metal::BufferType::DRAM ? 1 : 0; + std::vector reader_compile_time_args = { + (uint32_t)src_is_dram, + N, + input_cb_page_size, + num_rows, + x_dim, + num_blocks_total, + x_blocks, + w_blocks, + x_block_size, + w_block_size, + input_tensor.element_size(), + input_tensor.get_logical_shape()[-1] * input_tensor.element_size()}; - { - auto& runtime_args = tt::tt_metal::GetRuntimeArgs(program, unary_writer_kernel_id, CoreCoord{0, 0}); - runtime_args[0] = dst_buffer->address(); + tt::tt_metal::KernelHandle unary_reader_kernel_id = tt::tt_metal::CreateKernel( + program, + "ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/" + "reader_permute_interleaved_rm_blocked_generic.cpp", + all_cores, + tt::tt_metal::ReaderDataMovementConfig(reader_compile_time_args)); + + bool dst_is_dram = dst_buffer->buffer_type() == tt::tt_metal::BufferType::DRAM ? 1 : 0; + std::vector writer_compile_time_args = { + (std::uint32_t)dst_is_dram, + N, + output_cb_page_size, + num_rows, + + X, + X_stride, + x_dim, + + W_stride, + input_cb_page_size, + input_tensor.element_size(), + + num_blocks_total, + x_blocks, + w_blocks, + x_block_size, + w_block_size, + + W, + output_tensor.get_logical_shape()[-1] * output_tensor.element_size()}; + tt::tt_metal::KernelHandle unary_writer_kernel_id = tt::tt_metal::CreateKernel( + program, + "ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/dataflow/" + "writer_permute_interleaved_rm_blocked_generic.cpp", + all_cores, + tt::tt_metal::WriterDataMovementConfig(writer_compile_time_args)); + + std::vector compute_kernel_args = {x_block_size, w_block_size}; + bool fp32_dest_acc_en = cb_data_format_output == tt::DataFormat::Float32; + auto compute_kernel_id = tt::tt_metal::CreateKernel( + program, + "ttnn/cpp/ttnn/operations/data_movement/permute/device/kernels/compute/transpose_xw_rm_single_tile_size.cpp", + all_cores, + tt::tt_metal::ComputeConfig{ + .fp32_dest_acc_en = fp32_dest_acc_en, + .compile_args = compute_kernel_args, + }); + + auto input_shape_view = input_tensor.get_logical_shape().view(); + + std::vector reader_runtime_args = {src_buffer->address(), 0, 0}; + reader_runtime_args.insert(reader_runtime_args.end(), input_shape_view.begin(), input_shape_view.end()); + reader_runtime_args.insert(reader_runtime_args.end(), input_strides.begin(), input_strides.end()); + + std::vector writer_runtime_args = {dst_buffer->address(), 0, 0}; + + writer_runtime_args.insert(writer_runtime_args.end(), input_shape_view.begin(), input_shape_view.end()); + writer_runtime_args.insert( + writer_runtime_args.end(), operation_attributes.dims.begin(), operation_attributes.dims.end()); + writer_runtime_args.insert(writer_runtime_args.end(), output_strides.begin(), output_strides.end()); + auto cores = corerange_to_cores(all_cores, std::nullopt); + + std::vector compute_runtime_args = {dst_buffer->address(), 0, 0}; + + uint32_t start_block = 0; + uint32_t num_blocks_per_core = 0; + for (const auto& core : cores) { + if (core_group_1.contains(core)) { + num_blocks_per_core = num_tiles_per_core_group_1; + } else if (core_group_2.contains(core)) { + num_blocks_per_core = num_tiles_per_core_group_2; + } else { + // no-op + num_blocks_per_core = 0; + } + compute_runtime_args[0] = num_blocks_per_core; + uint32_t end_block = start_block + num_blocks_per_core; + reader_runtime_args[1] = start_block; + reader_runtime_args[2] = end_block; + writer_runtime_args[1] = start_block; + writer_runtime_args[2] = end_block; + tt::tt_metal::SetRuntimeArgs(program, unary_reader_kernel_id, core, reader_runtime_args); + tt::tt_metal::SetRuntimeArgs(program, unary_writer_kernel_id, core, writer_runtime_args); + tt::tt_metal::SetRuntimeArgs(program, compute_kernel_id, core, compute_runtime_args); + start_block = end_block; + } + + return { + std::move(program), + {.unary_reader_kernel_id = unary_reader_kernel_id, + .unary_writer_kernel_id = unary_writer_kernel_id, + .compute_kernel_id = compute_kernel_id, + .core_range = all_cores}}; +} + +void PermuteDeviceOperation::MultiCoreBlockedGeneric::override_runtime_arguments( + cached_program_t& cached_program, + const operation_attributes_t& operation_attributes, + const tensor_args_t& tensor_args, + tensor_return_value_t& tensor_return_value) { + auto& program = cached_program.program; + auto& unary_reader_kernel_id = cached_program.shared_variables.unary_reader_kernel_id; + auto& unary_writer_kernel_id = cached_program.shared_variables.unary_writer_kernel_id; + auto& compute_kernel_id = cached_program.shared_variables.compute_kernel_id; + + const auto& input_tensor = tensor_args.input_tensor; + auto& output_tensor = tensor_return_value; + + auto src_buffer = input_tensor.buffer(); + auto dst_buffer = output_tensor.buffer(); + auto& all_cores = cached_program.shared_variables.core_range; + + auto cores = corerange_to_cores(all_cores, std::nullopt); + for (const auto& core : cores) { + auto& runtime_args = tt::tt_metal::GetRuntimeArgs(program, unary_reader_kernel_id, core); + runtime_args[0] = src_buffer->address(); + auto& runtime_args_writer = tt::tt_metal::GetRuntimeArgs(program, unary_writer_kernel_id, core); + runtime_args_writer[0] = dst_buffer->address(); } } diff --git a/ttnn/cpp/ttnn/operations/data_movement/permute/permute.cpp b/ttnn/cpp/ttnn/operations/data_movement/permute/permute.cpp index 288f5b5a1012..00d622a15fb1 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/permute/permute.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/permute/permute.cpp @@ -25,30 +25,15 @@ inline bool is_on_device(const Tensor& t) { ttnn::has_storage_type_of(t, ttnn::StorageType::MULTI_DEVICE); } -inline bool has_tile_padding(const Tensor& t) { - if (t.get_logical_shape().rank() > 1) { - auto the_shape = t.get_logical_shape(); - auto the_shape_with_padding = t.get_padded_shape(); - return the_shape[-1] != the_shape_with_padding[-1] or the_shape[-2] != the_shape_with_padding[-2]; - } - return false; -} - ttnn::Tensor permute_impl( const ttnn::Tensor& a, - const SmallVector& dims, + const ttnn::SmallVector& dims, const MemoryConfig& output_mem_config, const std::optional& pad_value) { using ttnn::operations::experimental::auto_format::AutoFormat; - Device* device; // Get the device - if (a.storage_type() != StorageType::DEVICE) { - device = AutoFormat::GetDefaultDevice(); - TT_ASSERT(device != nullptr, "Requires setting default device if no inputs to op are on device"); - } else { - device = a.device(); - } + Device* device = a.device(); if (a.get_shape().rank() > 4) { auto input = a.get_layout() == Layout::TILE @@ -57,16 +42,14 @@ ttnn::Tensor permute_impl( TT_FATAL( !(pad_value.has_value() && pad_value.value() != 0.0f), "Non-zero padding is not supported for permute on tensors with rank > 4."); - input = ttnn::prim::permute(input, dims, output_mem_config, std::nullopt); + SmallVector permute_dims(dims.begin(), dims.end()); + input = ttnn::prim::permute(input, permute_dims, output_mem_config, std::nullopt); return ttnn::to_layout(input, a.get_layout(), std::nullopt, std::nullopt, (Device*)nullptr); } TT_FATAL(dims.size() == 4, "Only 4D tensor are supported for permute."); uint32_t N = dims[0], C = dims[1], H = dims[2], W = dims[3]; - // Convert tensor back to original - auto input_shape = a.get_logical_shape(); - auto formatted_input_tensor = a; // WH and CN should be supported without typecast bool wh = N == 0 && C == 1 && H == 3 && W == 2; @@ -142,13 +125,14 @@ ttnn::Tensor permute_impl( } else { TT_ASSERT(false, "Illegal permute args"); } + // Convert tensor back to original dtype if typecast was performed output = typecast ? ttnn::typecast(output, DataType::BFLOAT8_B) : output; return output; } ttnn::Tensor permute_launch( const ttnn::Tensor& a, - tt::stl::Span dims, + const ttnn::SmallVector& dims, const MemoryConfig& output_mem_config, const std::optional& pad_value) { std::vector output_tensors = {ttnn::Tensor(operation::get_workers_for_op_output({a}))}; @@ -159,31 +143,21 @@ ttnn::Tensor permute_launch( const std::vector>& optional_output_tensors) mutable -> std::vector { auto& a = input_tensors.at(0); - SmallVector normalized_dims(dims.size()); - std::transform(dims.begin(), dims.end(), normalized_dims.begin(), [a](std::int64_t idx) { - return a.get_legacy_shape().get_normalized_index(idx); - }); - SmallVector seq_dims(dims.size()); - std::iota(seq_dims.begin(), seq_dims.end(), 0); - if (normalized_dims == seq_dims) { - return {ttnn::operations::experimental::auto_format::AutoFormat::move_tensor_to_mem_config( - a, output_mem_config)}; - } - return {permute_impl(a, normalized_dims, output_mem_config, pad_value)}; + return {permute_impl(a, dims, output_mem_config, pad_value)}; }, {a}, output_tensors); return output_tensors.at(0); } -Tensor composite_invoke( - const ttnn::Tensor& input_tensor, - tt::stl::Span dims, - const std::optional& memory_config, - const std::optional& pad_value) { - auto output_tensor = - permute_launch(input_tensor, dims, memory_config.value_or(input_tensor.memory_config()), pad_value); - return output_tensor; +bool is_permute_nop(const ttnn::Tensor& a, tt::stl::Span dims) { + if (a.get_shape().rank() <= 1) { + return true; + } + auto normalized_dims = ttnn::SmallVector(dims.begin(), dims.end()); + ttnn::SmallVector seq_dims(dims.size()); + std::iota(seq_dims.begin(), seq_dims.end(), 0); + return normalized_dims == seq_dims; } } // namespace detail @@ -193,23 +167,24 @@ ttnn::Tensor ExecutePermute::invoke( const ttnn::Tensor& input_tensor, tt::stl::Span dims, const std::optional& memory_config, - bool composite, const std::optional& pad_value) { - if (composite) { - return detail::composite_invoke(input_tensor, dims, memory_config, pad_value); - } - - const bool initial_input_tensor_on_device = detail::is_on_device(input_tensor); - const auto input_layout = input_tensor.get_layout(); const auto input_rank = input_tensor.get_logical_shape().rank(); - TT_FATAL( input_rank == dims.size(), "The number of dimensions in the tensor input does not match the length of the desired ordering"); + TT_FATAL(detail::is_on_device(input_tensor), "Tensor must already be on device"); + + SmallVector normalized_dims(dims.size()); + std::transform(dims.begin(), dims.end(), normalized_dims.begin(), [input_tensor](std::int64_t idx) { + return input_tensor.get_logical_shape().get_normalized_index(idx); + }); + if (detail::is_permute_nop(input_tensor, normalized_dims)) { + return ttnn::to_memory_config(input_tensor, memory_config.value_or(input_tensor.memory_config())); + } - auto adjust_order = [](tt::stl::Span dims) { - ttnn::SmallVector new_order; - TT_FATAL(dims.size() <= 4, "Error"); + auto adjust_order = [](tt::stl::Span dims) { + ttnn::SmallVector new_order; + TT_FATAL(dims.size() <= 4, "Minimum rank of tensor required is 4"); int additional_ranks = 4 - dims.size(); for (int i = 0; i < additional_ranks; i++) { new_order.push_back(i); @@ -220,33 +195,15 @@ ttnn::Tensor ExecutePermute::invoke( return new_order; }; auto itensor = (input_tensor.get_logical_shape().rank() < 4) ? ttnn::unsqueeze_to_4D(input_tensor) : input_tensor; - auto iorder = - dims.size() < 4 ? adjust_order(dims) : dims; // internals of permute_impl already adjust negative indices + auto iorder = normalized_dims.size() < 4 ? adjust_order(normalized_dims) : normalized_dims; - TT_FATAL(detail::is_on_device(itensor), "Error"); + const auto input_layout = input_tensor.get_layout(); auto output_tensor = detail::permute_launch(itensor, iorder, memory_config.value_or(input_tensor.memory_config()), pad_value); output_tensor = ttnn::to_layout(output_tensor, input_layout, std::nullopt, std::nullopt, (Device*)nullptr); if (input_rank < 4) { - const auto shape = output_tensor.get_shape(); - const auto full_shape = output_tensor.get_shape().with_tile_padding(); - SmallVector shape_vec{}; - SmallVector full_shape_vec{}; - int i = 0; - while (i < 3 and shape[i] == 1) { - i++; - } - for (; i < shape.rank(); i++) { - shape_vec.push_back(shape[i]); - full_shape_vec.push_back(full_shape[i]); - } - output_tensor = ttnn::reshape(output_tensor, ttnn::Shape(shape_vec, full_shape_vec)); - } - - if (initial_input_tensor_on_device and not detail::is_on_device(output_tensor)) { - output_tensor = - ttnn::to_device(output_tensor, input_tensor.device(), memory_config.value_or(input_tensor.memory_config())); + output_tensor = ttnn::squeeze_from_4D(output_tensor, input_rank); } return output_tensor; @@ -257,7 +214,7 @@ ttnn::Tensor ExecutePermute::invoke( tt::stl::Span dims, const std::optional& memory_config, const std::optional& pad_value) { - return invoke(DefaultQueueId, input_tensor, dims, memory_config, true, pad_value); + return invoke(DefaultQueueId, input_tensor, dims, memory_config, pad_value); } ttnn::Tensor ExecutePermute::invoke( diff --git a/ttnn/cpp/ttnn/operations/data_movement/permute/permute.hpp b/ttnn/cpp/ttnn/operations/data_movement/permute/permute.hpp index 7f9301b696b4..2f13b9c2845b 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/permute/permute.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/permute/permute.hpp @@ -15,7 +15,6 @@ struct ExecutePermute { const ttnn::Tensor& input_tensor, tt::stl::Span dims, const std::optional& memory_config, - bool composite = true, const std::optional& pad_value = 0.0f); static ttnn::Tensor invoke( diff --git a/ttnn/cpp/ttnn/operations/data_movement/permute/permute_pybind.cpp b/ttnn/cpp/ttnn/operations/data_movement/permute/permute_pybind.cpp index 2fbb5c0bcd00..be6adbf880b5 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/permute/permute_pybind.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/permute/permute_pybind.cpp @@ -44,7 +44,7 @@ void bind_permute(py::module& module) { const std::optional& memory_config, uint8_t queue_id, const std::optional& pad_value) { - return self(queue_id, input_tensor, dims, memory_config, false, pad_value); + return self(queue_id, input_tensor, dims, memory_config, pad_value); }, py::arg("input_tensor").noconvert(), py::arg("dims"), diff --git a/ttnn/cpp/ttnn/operations/data_movement/transpose/device/transpose_op.cpp b/ttnn/cpp/ttnn/operations/data_movement/transpose/device/transpose_op.cpp index 1776e28adc8c..c560cd17ebea 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/transpose/device/transpose_op.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/transpose/device/transpose_op.cpp @@ -23,14 +23,29 @@ void Transpose::validate(const std::vector& input_tensors) const { TT_FATAL(input_tensor.buffer() != nullptr, "Operands to transpose need to be allocated in buffers on device!"); TT_FATAL( !(this->dim != TransposeOpDim::HC && this->pad_value.has_value() && this->pad_value != 0.0f), - "Non-zero padding is not supported for any transpose other than HC."); + "Non-zero padding {} is not supported for any transpose other than HC.", + this->pad_value.value()); + TT_FATAL( + this->dim == TransposeOpDim::HC || this->dim == TransposeOpDim::WH || this->dim == TransposeOpDim::CN, + "Transpose HC, WH, CN are the only supported transpose operations. Transpose {} is not supported.", + (int)this->dim); const auto shape = input_tensor.get_padded_shape(); bool row_major = input_tensor.get_layout() == Layout::ROW_MAJOR; uint32_t W = shape[3], H = shape[2], C = shape[1], N = shape[0]; uint32_t HW = H * W; if (not row_major) { - TT_FATAL(W % TILE_WIDTH == 0 && H % TILE_HEIGHT == 0, "Error"); - TT_FATAL(input_tensor.volume() % TILE_HW == 0, "Error"); + TT_FATAL( + W % TILE_WIDTH == 0 && H % TILE_HEIGHT == 0, + "Tiled tensor H {} W {} must be a multiple of TILE HEIGHT {} and TILE WIDTH", + H, + W, + TILE_HEIGHT, + TILE_WIDTH); + TT_FATAL( + input_tensor.volume() % TILE_HW == 0, + "Tiled tensor volume {} must be a multiple of TILE HEIGHT * TILE WIDTH", + input_tensor.volume(), + TILE_HW); } uint32_t ROW_MAJOR_STICK_WIDTH = 16; if (this->dim == TransposeOpDim::WH) { @@ -38,27 +53,43 @@ void Transpose::validate(const std::vector& input_tensors) const { TT_FATAL( (W * input_tensor.element_size()) % ROW_MAJOR_STICK_WIDTH == 0 && (H * input_tensor.element_size()) % ROW_MAJOR_STICK_WIDTH == 0, - "Error"); + "Row major tensor W {} H {} must be a multiple of ROW_MAJOR_STICK_WIDTH for transpose wh", + W, + H, + ROW_MAJOR_STICK_WIDTH); } if (input_tensor.is_sharded()) { - TT_FATAL(input_tensor.memory_config().memory_layout != TensorMemoryLayout::WIDTH_SHARDED, "Error"); + TT_FATAL( + input_tensor.memory_config().memory_layout != TensorMemoryLayout::WIDTH_SHARDED, + "Only height and block sharding is supported for transpose wh"); const auto shard_spec = input_tensor.shard_spec().value(); - TT_FATAL(shard_spec.shape[1] == W, "Error"); - TT_FATAL(shard_spec.shape[0] % H == 0, "Error"); - TT_FATAL(this->output_mem_config.is_sharded(), "Error"); - TT_FATAL(this->output_mem_config.memory_layout != TensorMemoryLayout::WIDTH_SHARDED, "Error"); + TT_FATAL( + (shard_spec.shape[0] % H == 0) || (H % shard_spec.shape[0] == 0), + "Only a multiple of H {} or a factor of H is allows for the shard height {} for transpose WH", + H, + shard_spec.shape[0]); + TT_FATAL(shard_spec.shape[1] == W, "Only height sharding is supported"); + TT_FATAL(this->output_mem_config.is_sharded(), "Output must be sharded for transpose WH"); + TT_FATAL( + this->output_mem_config.memory_layout != TensorMemoryLayout::WIDTH_SHARDED, + "Only height and block sharding is supported for transpose wh"); } else { - TT_FATAL(!this->output_mem_config.is_sharded(), "Error"); + TT_FATAL(!this->output_mem_config.is_sharded(), "Interleaved input tensors cannot output sharded outputs"); } } else { if (input_tensor.is_sharded()) { - TT_FATAL(input_tensor.memory_config().memory_layout == TensorMemoryLayout::HEIGHT_SHARDED, "Error"); + TT_FATAL( + input_tensor.memory_config().memory_layout == TensorMemoryLayout::HEIGHT_SHARDED, + "Only height sharding is supported for transpose hc"); const auto shard_spec = input_tensor.shard_spec().value(); - TT_FATAL(shard_spec.shape[1] == W, "Error"); - TT_FATAL(this->output_mem_config.is_sharded(), "Error"); - TT_FATAL(this->output_mem_config.memory_layout == TensorMemoryLayout::HEIGHT_SHARDED, "Error"); + TT_FATAL(shard_spec.shape[1] == W, "Block/Width sharding is not supported"); + TT_FATAL( + this->output_mem_config.is_sharded(), "Sharded input can only output sharded tensors for transpose hc"); + TT_FATAL( + this->output_mem_config.memory_layout == TensorMemoryLayout::HEIGHT_SHARDED, + "Only height sharding is supported for the ouput of sharded transpose hc"); } else { - TT_FATAL(!this->output_mem_config.is_sharded(), "Error"); + TT_FATAL(!this->output_mem_config.is_sharded(), "Interleaved inputs cannot output sharded outputs"); } } if (this->dim == TransposeOpDim::HC) { @@ -78,19 +109,8 @@ void Transpose::validate(const std::vector& input_tensors) const { "HC transpose does not support sharded+tilized inputs"); TT_FATAL( !(input_tensor.is_sharded() && pad_value.has_value() && pad_value.value() != 0.0f), - "Sharded HC transpose does not support non-zero padding"); - } else if (this->dim == TransposeOpDim::CW) { - TT_FATAL(C % TILE_WIDTH == 0, "Error"); - TT_FATAL( - input_tensor.get_dtype() == DataType::BFLOAT16 || input_tensor.get_dtype() == DataType::FLOAT32, "Error"); - } else if (this->dim == TransposeOpDim::NH) { - TT_FATAL(N % TILE_HEIGHT == 0, "Error"); - TT_FATAL( - input_tensor.get_dtype() == DataType::BFLOAT16 || input_tensor.get_dtype() == DataType::FLOAT32, "Error"); - } else if (this->dim == TransposeOpDim::NW) { - TT_FATAL(N % TILE_WIDTH == 0, "Error"); - TT_FATAL( - input_tensor.get_dtype() == DataType::BFLOAT16 || input_tensor.get_dtype() == DataType::FLOAT32, "Error"); + "Sharded HC transpose does not support non-zero padding {}", + pad_value.value()); } } @@ -147,9 +167,15 @@ std::vector Transpose::compute_output_specs(const std::vector< if (this->dim == TransposeOpDim::WH) { const auto& input_padded_shape = input_tensor.get_padded_shape(); ShardSpec shard_spec = input_tensor.shard_spec().value(); - shard_spec.shape[0] = shard_spec.shape[0] / input_padded_shape[-2] * input_padded_shape[-1]; - shard_spec.shape[1] = input_padded_shape[-2]; - output_mem_config.shard_spec = shard_spec; + if (shard_spec.shape[0] >= input_padded_shape[-2]) { + shard_spec.shape[0] = shard_spec.shape[0] / input_padded_shape[-2] * input_padded_shape[-1]; + shard_spec.shape[1] = input_padded_shape[-2]; + output_mem_config.shard_spec = shard_spec; + } else { + std::swap(shard_spec.shape[0], shard_spec.shape[1]); + output_mem_config.shard_spec = shard_spec; + output_mem_config.memory_layout = TensorMemoryLayout::BLOCK_SHARDED; + } } else if (this->dim == TransposeOpDim::HC) { output_mem_config.shard_spec = input_tensor.shard_spec().value(); } else { diff --git a/ttnn/cpp/ttnn/operations/data_movement/transpose/device/transpose_program_factory.cpp b/ttnn/cpp/ttnn/operations/data_movement/transpose/device/transpose_program_factory.cpp index 2dbdcf7dc8a2..02bc49406d84 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/transpose/device/transpose_program_factory.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/transpose/device/transpose_program_factory.cpp @@ -1776,8 +1776,9 @@ operation::ProgramWithCallbacks transpose_wh_multi_core_sharded(const Tensor& a, uint32_t dst_single_tile_size = tt::tt_metal::detail::TileSize(dst_cb_data_format); tt::tt_metal::Buffer* src0_buffer = a.buffer(); - - int32_t num_tiles = a.volume() / TILE_HW; + const auto tile = a.get_tensor_spec().tile(); + const uint32_t tile_hw = tile.get_tile_hw(); + int32_t num_tiles = a.volume() / tile_hw; tt::tt_metal::Device* device = a.device(); @@ -1793,7 +1794,7 @@ operation::ProgramWithCallbacks transpose_wh_multi_core_sharded(const Tensor& a, auto& all_cores = shard_spec.grid; uint32_t num_cores = all_cores.num_cores(); - uint32_t num_tiles_per_shard = shard_spec.numel() / TILE_HW; + uint32_t num_tiles_per_shard = shard_spec.numel() / tile_hw; tt::tt_metal::LegacyShape output_shape = output.get_legacy_shape(); @@ -1848,11 +1849,22 @@ operation::ProgramWithCallbacks transpose_wh_multi_core_sharded(const Tensor& a, total_cores, tt::tt_metal::ComputeConfig{.fp32_dest_acc_en = fp32_dest_acc_en, .compile_args = compute_compile_time_args}); - uint32_t Wt = shard_spec.shape[1] / TILE_WIDTH; - uint32_t Ht = a.get_legacy_shape()[-2] / TILE_HEIGHT; - uint32_t HtWt = Ht * Wt; - uint32_t N = shard_spec.shape[0] / a.get_legacy_shape()[-2]; - uint32_t NHtWt = N * HtWt; + auto padded_shape = a.get_padded_shape(); + auto shard_shape = shard_spec.shape; + + uint32_t H = padded_shape[2], W = padded_shape[3]; + uint32_t Hs = shard_shape[0], Ws = shard_shape[1]; + + uint32_t Hts = Hs / tile.tile_shape[0]; + uint32_t Wts = Ws / tile.tile_shape[1]; + + uint32_t Ht = H / tile.tile_shape[0]; + uint32_t Ht_per_shard = std::min(Ht, Hts); + + uint32_t num_hw_blocks_per_shard = Hts > Ht ? Hts / Ht : 1; + + uint32_t HtWt_tile_size = Ht_per_shard * Wts; + uint32_t num_blocks = num_hw_blocks_per_shard * HtWt_tile_size; auto bbox = all_cores.bounding_box(); std::vector cores = @@ -1862,13 +1874,17 @@ operation::ProgramWithCallbacks transpose_wh_multi_core_sharded(const Tensor& a, std::vector> unary_compute_args = {cores.size(), std::vector(5)}; std::vector> unary_writer_args = {cores.size(), std::vector(1)}; std::fill( - unary_reader_args.begin(), unary_reader_args.begin() + all_cores.num_cores(), std::vector{NHtWt}); + unary_reader_args.begin(), + unary_reader_args.begin() + all_cores.num_cores(), + std::vector{num_blocks}); std::fill( unary_compute_args.begin(), unary_compute_args.begin() + all_cores.num_cores(), - std::vector{NHtWt, HtWt, N, Ht, Wt}); + std::vector{num_blocks, HtWt_tile_size, num_hw_blocks_per_shard, Ht_per_shard, Wts}); std::fill( - unary_writer_args.begin(), unary_writer_args.begin() + all_cores.num_cores(), std::vector{NHtWt}); + unary_writer_args.begin(), + unary_writer_args.begin() + all_cores.num_cores(), + std::vector{num_blocks}); tt::tt_metal::SetRuntimeArgs(program, reader_kernel_id, cores, unary_reader_args); tt::tt_metal::SetRuntimeArgs(program, compute_kernel_id, cores, unary_compute_args); @@ -1899,7 +1915,11 @@ operation::ProgramWithCallbacks transpose_wh_multi_core_sharded(const Tensor& a, auto shard_spec = src_tensor.shard_spec().value(); - uint32_t num_tiles_per_shard = shard_spec.numel() / TILE_HW; + const auto tile = src_tensor.get_tensor_spec().tile(); + const uint32_t tile_hw = tile.get_tile_hw(); + int32_t num_tiles = src_tensor.volume() / tile_hw; + + uint32_t num_tiles_per_shard = shard_spec.numel() / tile_hw; if (src0_sharded) { UpdateDynamicCircularBufferAddressAndTotalSize( @@ -1911,11 +1931,22 @@ operation::ProgramWithCallbacks transpose_wh_multi_core_sharded(const Tensor& a, program, cb_output, *dst_buffer, num_tiles_per_shard * dst_single_tile_size); } - uint32_t Wt = shard_spec.shape[1] / TILE_WIDTH; - uint32_t Ht = src_tensor.get_legacy_shape()[-2] / TILE_HEIGHT; - uint32_t HtWt = Ht * Wt; - uint32_t N = shard_spec.shape[0] / src_tensor.get_legacy_shape()[-2]; - uint32_t NHtWt = N * HtWt; + auto padded_shape = src_tensor.get_padded_shape(); + auto shard_shape = shard_spec.shape; + + uint32_t H = padded_shape[2], W = padded_shape[3]; + uint32_t Hs = shard_shape[0], Ws = shard_shape[1]; + + uint32_t Hts = Hs / tile.tile_shape[0]; + uint32_t Wts = Ws / tile.tile_shape[1]; + + uint32_t Ht = H / tile.tile_shape[0]; + uint32_t Ht_per_shard = std::min(Ht, Hts); + + uint32_t num_hw_blocks_per_shard = Hts > Ht ? Hts / Ht : 1; + + uint32_t HtWt_tile_size = Ht_per_shard * Wts; + uint32_t num_blocks = num_hw_blocks_per_shard * HtWt_tile_size; const auto& all_cores = shard_spec.grid; bool row_major = shard_spec.orientation == ShardOrientation::ROW_MAJOR; @@ -1927,13 +1958,17 @@ operation::ProgramWithCallbacks transpose_wh_multi_core_sharded(const Tensor& a, std::vector> unary_compute_args = {cores.size(), std::vector(5)}; std::vector> unary_writer_args = {cores.size(), std::vector(1)}; std::fill( - unary_reader_args.begin(), unary_reader_args.begin() + all_cores.num_cores(), std::vector{NHtWt}); + unary_reader_args.begin(), + unary_reader_args.begin() + all_cores.num_cores(), + std::vector{num_blocks}); std::fill( unary_compute_args.begin(), unary_compute_args.begin() + all_cores.num_cores(), - std::vector{NHtWt, HtWt, N, Ht, Wt}); + std::vector{num_blocks, HtWt_tile_size, num_hw_blocks_per_shard, Ht_per_shard, Wts}); std::fill( - unary_writer_args.begin(), unary_writer_args.begin() + all_cores.num_cores(), std::vector{NHtWt}); + unary_writer_args.begin(), + unary_writer_args.begin() + all_cores.num_cores(), + std::vector{num_blocks}); tt::tt_metal::SetRuntimeArgs(program, reader_kernel_id, cores, unary_reader_args); tt::tt_metal::SetRuntimeArgs(program, compute_kernel_id, cores, unary_compute_args); diff --git a/ttnn/cpp/ttnn/operations/experimental/matmul/attn_matmul/device/attn_matmul_device_operation.cpp b/ttnn/cpp/ttnn/operations/experimental/matmul/attn_matmul/device/attn_matmul_device_operation.cpp index 26de812b441b..092481bdaee6 100644 --- a/ttnn/cpp/ttnn/operations/experimental/matmul/attn_matmul/device/attn_matmul_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/experimental/matmul/attn_matmul/device/attn_matmul_device_operation.cpp @@ -60,7 +60,9 @@ void AttnMatmulDeviceOperation::validate(const std::vector& input_tensor } else { TT_FATAL( ashape[3] == bshape[2], - "Dimension K (A.shape[3] and B.shape[2]) must match for A and B in attn_matmul op"); // A.K == B.K + "Dimension K (A.shape[3]and B.shape[2]) must match for A shape: {} and B shape: {} in attn_matmul op", + ashape, + bshape); // A.K == B.K } } diff --git a/ttnn/cpp/ttnn/tensor/shape/shape.cpp b/ttnn/cpp/ttnn/tensor/shape/shape.cpp index 7dee54285266..d4a54500c466 100644 --- a/ttnn/cpp/ttnn/tensor/shape/shape.cpp +++ b/ttnn/cpp/ttnn/tensor/shape/shape.cpp @@ -7,6 +7,7 @@ #include #include #include "ttnn/tensor/shape/small_vector.hpp" +#include "tt_metal/common/assert.hpp" namespace tt::tt_metal { @@ -20,6 +21,17 @@ uint64_t SimpleShape::volume() const { return std::accumulate(cbegin(), cend(), uint64_t{1}, std::multiplies()); } +const uint32_t SimpleShape::get_normalized_index(std::int64_t index) const { + std::int64_t rank = static_cast(this->rank()); + std::uint64_t normalized_index = index >= 0 ? index : rank + index; + TT_FATAL( + normalized_index >= 0 and normalized_index < rank, + "Index is out of bounds for the rank, should be between 0 and {} however is {}", + rank - 1, + normalized_index); + return normalized_index; +} + std::ostream& operator<<(std::ostream& os, const tt::tt_metal::SimpleShape& shape) { os << "SimpleShape(["; for (size_t i = 0; i < shape.rank(); ++i) { diff --git a/ttnn/cpp/ttnn/tensor/shape/shape.hpp b/ttnn/cpp/ttnn/tensor/shape/shape.hpp index b16615789275..f6f78d35fd5f 100644 --- a/ttnn/cpp/ttnn/tensor/shape/shape.hpp +++ b/ttnn/cpp/ttnn/tensor/shape/shape.hpp @@ -30,6 +30,8 @@ class SimpleShape final : protected ShapeBase { [[nodiscard]] size_t rank() const; [[nodiscard]] uint64_t volume() const; + const uint32_t get_normalized_index(std::int64_t index) const; + // Needed for reflect / fmt static constexpr auto attribute_names = std::forward_as_tuple("value"); auto attribute_values() const { return std::forward_as_tuple(this->value_); } From 9208f770ce104c2ade686fa41773bde5df9d4bac Mon Sep 17 00:00:00 2001 From: Nour Ardo Date: Tue, 17 Dec 2024 14:59:23 -0500 Subject: [PATCH 28/87] Adding ND support for tilize/untilize with padding (#15933) ### Ticket https://github.com/tenstorrent/tt-metal/issues/15935 ### Problem description Supporting ND tensors for tilize_with_val_padding and untilize_with_unpadding operations ### What's changed Describe the approach used to solve the problem. Summarize the changes made and its impact. ### Checklist - [x] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12280234070 - [x] Blackhole Post commit (if applicable) https://github.com/tenstorrent/tt-metal/actions/runs/12280247340 - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [ ] New/Existing tests provide coverage for changes --- tests/ttnn/unit_tests/test_to_layout.py | 24 +++++ .../core/work_split/work_split_tilize.hpp | 6 +- .../tilize_with_val_padding.cpp | 101 +++++++++++++++--- .../untilize_with_unpadding.cpp | 81 +++++++++++--- 4 files changed, 181 insertions(+), 31 deletions(-) diff --git a/tests/ttnn/unit_tests/test_to_layout.py b/tests/ttnn/unit_tests/test_to_layout.py index b84a8f4c5fce..d074b41b570e 100644 --- a/tests/ttnn/unit_tests/test_to_layout.py +++ b/tests/ttnn/unit_tests/test_to_layout.py @@ -140,3 +140,27 @@ def test_to_layout_device(device, h, w, input_layout, output_layout): torch_brought_back = ttnn.to_torch(new_layout_tensor) assert_with_pcc(torch_input_tensor, torch_brought_back) + + +@pytest.mark.parametrize("shape", [[1, 50, 1, 3, 768], [1, 1370, 1, 3, 1280]]) +@pytest.mark.parametrize("input_layout", [ttnn.TILE_LAYOUT, ttnn.ROW_MAJOR_LAYOUT]) +@pytest.mark.parametrize("output_layout", [ttnn.TILE_LAYOUT, ttnn.ROW_MAJOR_LAYOUT]) +def test_to_layout_5D(shape, input_layout, output_layout, device): + torch.manual_seed(2005) + input_a = torch.randn(shape, dtype=torch.bfloat16) + input_tensor = ttnn.from_torch(input_a, device=device, layout=input_layout, dtype=ttnn.bfloat16) + output_tensor = ttnn.to_layout(input_tensor, output_layout) + output_tensor = ttnn.to_torch(output_tensor) + assert_with_pcc(input_a, output_tensor) + + +@pytest.mark.parametrize("shape", [[1, 1, 58, 1, 37, 256], [1, 1, 64, 1, 90, 1280]]) +@pytest.mark.parametrize("input_layout", [ttnn.TILE_LAYOUT, ttnn.ROW_MAJOR_LAYOUT]) +@pytest.mark.parametrize("output_layout", [ttnn.TILE_LAYOUT, ttnn.ROW_MAJOR_LAYOUT]) +def test_to_layout_6D(shape, input_layout, output_layout, device): + torch.manual_seed(2005) + input_a = torch.randn(shape, dtype=torch.bfloat16) + input_tensor = ttnn.from_torch(input_a, device=device, layout=input_layout, dtype=ttnn.bfloat16) + output_tensor = ttnn.to_layout(input_tensor, output_layout) + output_tensor = ttnn.to_torch(output_tensor) + assert_with_pcc(input_a, output_tensor) diff --git a/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp b/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp index e44919022f1f..cd783f161b57 100644 --- a/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp +++ b/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp @@ -184,11 +184,7 @@ inline std::vector> distribute_work( bool has_cliff, uint32_t nblocks_per_core_cliff) { TT_FATAL( - logical_shape.rank() >= 2 && logical_shape.rank() <= 4, - "Only 2D, 3D, and 4D tensors are supported. Shape: {}", - "Error", - logical_shape, - padding); + logical_shape.rank() >= 2, "Logical shape rank needs to be >=2. Shape: {}", "Error", logical_shape, padding); auto input_w = logical_shape.rank() >= 4 ? logical_shape[-4] : 1; auto input_z = logical_shape.rank() >= 3 ? logical_shape[-3] : 1; diff --git a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.cpp b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.cpp index 4186adc4d966..0e6e10445225 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.cpp @@ -7,11 +7,81 @@ #include "device/tilize_with_val_padding_op.hpp" #include "ttnn/common/constants.hpp" #include "ttnn/run_operation.hpp" +#include "ttnn/operations/data_movement/common/common.hpp" +#include "ttnn/operations/data_movement/reshape_view/reshape.hpp" using namespace tt::tt_metal; namespace ttnn::operations::data_movement { +using OwnedTilizeValArgs = std::tuple; +using BaseTilizeValType = std::function; + +using MassagedTilizeVal = MassagedOperation; +using MassagedTilizeValParams = MassagedOperationParams; + +ttnn::Shape update_original_shape(ttnn::Shape& original, uint32_t tile_height, uint32_t tile_width) { + std::vector update_original(original.rank()); + uint32_t indx1 = original.rank() - 1; + uint32_t indx2 = original.rank() - 2; + if (original[indx2] % tile_height != 0) { + update_original[indx2] = (original[indx2] / tile_height + 1) * tile_height; + for (int i = 0; i < original.rank(); i++) { + if (i != indx2) { + update_original[i] = original[i]; + } + } + return tt::tt_metal::LegacyShape(update_original); + } + + else if (original[indx1] % tile_width != 0) { + update_original[indx1] = (original[indx1] / tile_width + 1) * tile_width; + for (int i = 0; i < original.rank(); i++) { + if (i != indx1) { + update_original[i] = original[i]; + } + } + return tt::tt_metal::LegacyShape(update_original); + } + return original; +} + +MassagedTilizeVal build_ndiml_tilize_val(BaseTilizeValType base_tilize) { + auto original_shape = std::make_shared(ttnn::Shape{}); + return MassagedTilizeVal(MassagedTilizeValParams{ + .predicate = [](const ttnn::Tensor& input_tensor) -> bool { return input_tensor.get_shape().rank() > 4; }, + .pre_transform = [=](const ttnn::Tensor& input_tensor) -> OwnedTilizeValArgs { + *original_shape = input_tensor.get_shape(); + ttnn::Tensor squeezed_tensor = squeeze_to_le_4D(input_tensor); + return std::make_tuple(squeezed_tensor); + }, + .post_transform = [=](const ttnn::Tensor& output) -> ttnn::Tensor { + const auto tile = output.get_tensor_spec().tile(); + uint32_t tile_height = tile.get_height(); + uint32_t tile_width = tile.get_width(); + auto unsqueezed_tensor = + ttnn::reshape(output, update_original_shape(*original_shape, tile_height, tile_width)); + return unsqueezed_tensor; + }, + .operation = std::move(base_tilize)}); +} + +tt::tt_metal::LegacyShape squeeze_output_shape(tt::tt_metal::LegacyShape output_shape) { + if (output_shape.rank() > 4) { + std::array output_shape_4d; + output_shape_4d[0] = 1; + int extra_rank = output_shape.rank() - 4; + for (int i = extra_rank; i >= 0; i--) { + output_shape_4d[0] *= output_shape[i]; + } + output_shape_4d[1] = output_shape[1 + extra_rank]; + output_shape_4d[2] = output_shape[2 + extra_rank]; + output_shape_4d[3] = output_shape[3 + extra_rank]; + return tt::tt_metal::LegacyShape(output_shape_4d); + } + return output_shape; +} + ttnn::Tensor ExecuteTilizeWithValPadding::invoke( uint8_t queue_id, const ttnn::Tensor& input_tensor, @@ -20,18 +90,21 @@ ttnn::Tensor ExecuteTilizeWithValPadding::invoke( const std::optional& memory_config, std::optional output_dtype, bool use_multicore) { - return operation::run( - TilizeWithValPadding{ - output_tensor_shape, - pad_value, - memory_config.value_or(input_tensor.memory_config()), - output_dtype.value_or(input_tensor.get_dtype()), - use_multicore}, - {input_tensor}, - {}, - {}, - queue_id) - .at(0); + auto base_tilize = [=](const ttnn::Tensor& input_tensor) { + return operation::run( + TilizeWithValPadding{ + squeeze_output_shape(output_tensor_shape), + pad_value, + memory_config.value_or(input_tensor.memory_config()), + output_dtype.value_or(input_tensor.get_dtype()), + use_multicore}, + {input_tensor}, + {}, + {}, + queue_id)[0]; + }; + + return build_ndiml_tilize_val(base_tilize)(input_tensor); } ttnn::Tensor ExecuteTilizeWithValPadding::invoke( @@ -54,8 +127,8 @@ ttnn::Tensor ExecuteTilizeWithZeroPadding::invoke( using namespace tt::constants; auto shape = input_tensor.get_legacy_shape(); - shape[2] = tt::round_up(shape[2], TILE_HEIGHT); - shape[3] = tt::round_up(shape[3], TILE_WIDTH); + shape[2] = tt::round_up(shape[2], tt::constants::TILE_HEIGHT); + shape[3] = tt::round_up(shape[3], tt::constants::TILE_WIDTH); PadValue pad_value; if (input_tensor.get_dtype() == DataType::BFLOAT16 or input_tensor.get_dtype() == DataType::FLOAT32) { diff --git a/ttnn/cpp/ttnn/operations/data_movement/untilize_with_unpadding/untilize_with_unpadding.cpp b/ttnn/cpp/ttnn/operations/data_movement/untilize_with_unpadding/untilize_with_unpadding.cpp index b2feccd12c9d..0215c6869a59 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/untilize_with_unpadding/untilize_with_unpadding.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/untilize_with_unpadding/untilize_with_unpadding.cpp @@ -8,10 +8,53 @@ #include "ttnn/common/constants.hpp" #include "ttnn/run_operation.hpp" +#include "ttnn/operations/data_movement/common/common.hpp" +#include "ttnn/operations/data_movement/reshape_view/reshape.hpp" + using namespace tt::tt_metal; +LegacyShape squeeze_output_shape(tt::tt_metal::LegacyShape output_shape) { + if (output_shape.rank() > 4) { + std::vector output_shape_4d(output_shape.rank()); + output_shape_4d[0] = 1; + int extra_rank = output_shape.rank() - 4; + for (int i = extra_rank; i >= 0; i--) { + output_shape_4d[0] *= (output_shape[i] + 1); + } + output_shape_4d[0]--; + output_shape_4d[1] = output_shape[1 + extra_rank]; + output_shape_4d[2] = output_shape[2 + extra_rank]; + output_shape_4d[3] = output_shape[3 + extra_rank]; + return tt::tt_metal::LegacyShape(output_shape_4d); + } + return output_shape; +} + namespace ttnn::operations::data_movement { +using OwnedUntilizeValArgs = std::tuple; +using BaseUntilizeValType = std::function; + +using MassagedUntilizeVal = MassagedOperation; +using MassagedUntilizeValParams = MassagedOperationParams; + +MassagedUntilizeVal build_ndiml_untilize_val(BaseUntilizeValType base_untilize) { + auto original_shape = std::make_shared(ttnn::Shape{}); + + return MassagedUntilizeVal(MassagedUntilizeValParams{ + .predicate = [](const ttnn::Tensor& input_tensor) -> bool { return input_tensor.get_shape().rank() > 4; }, + .pre_transform = [=](const ttnn::Tensor& input_tensor) -> OwnedUntilizeValArgs { + *original_shape = input_tensor.get_shape(); + ttnn::Tensor squeezed_tensor = squeeze_to_le_4D(input_tensor); + return std::make_tuple(squeezed_tensor); + }, + .post_transform = [=](const ttnn::Tensor& output) -> ttnn::Tensor { + auto unsqueezed_tensor = ttnn::reshape(output, *original_shape); + return unsqueezed_tensor; + }, + .operation = std::move(base_untilize)}); +} + ttnn::Tensor ExecuteUntilizeWithUnpadding::invoke( uint8_t queue_id, const ttnn::Tensor& input_tensor, @@ -22,18 +65,32 @@ ttnn::Tensor ExecuteUntilizeWithUnpadding::invoke( // MT: Currently only uint32 is moved to DST directly, fp32 is converted to fp16b bool fp32_dest_acc_en = input_tensor.get_dtype() == DataType::UINT32; - return operation::run( - UntilizeWithUnpadding{ - output_tensor_end, - memory_config.value_or(input_tensor.memory_config()), - use_multicore, - use_pack_untilize, - fp32_dest_acc_en}, - {input_tensor}, - {}, - {}, - queue_id) - .at(0); + std::vector output_end_vector; + tt::tt_metal::LegacyShape output_end = tt::tt_metal::LegacyShape{}; + if (input_tensor.get_shape().rank() > 4) { + for (auto index = 0; index < input_tensor.get_shape().rank(); ++index) { + output_end_vector.push_back(input_tensor.get_shape()[index] - 1); + } + output_end = squeeze_output_shape(LegacyShape(output_end_vector)); + } else { + output_end = output_tensor_end; + } + + auto base_untilize = [=](const ttnn::Tensor& input_tensor) { + return operation::run( + UntilizeWithUnpadding{ + output_end, + memory_config.value_or(input_tensor.memory_config()), + use_multicore, + use_pack_untilize, + fp32_dest_acc_en}, + {input_tensor}, + {}, + {}, + queue_id)[0]; + }; + + return build_ndiml_untilize_val(base_untilize)(input_tensor); } ttnn::Tensor ExecuteUntilizeWithUnpadding::invoke( From 05e6c61d58e763024d423bc0c2e8b27c66a593b5 Mon Sep 17 00:00:00 2001 From: Salar Hosseini <159165450+skhorasganiTT@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:05:47 -0500 Subject: [PATCH 29/87] [Llama3.2-11b vLLM Integration] Add support for paged cross attention, fixes for continuous batching, simplified decode forward call (#16076) --- .../demos/llama3/demo/simple_vision_demo.py | 24 +-- models/demos/llama3/tt/generator.py | 194 +++++++++++++++--- models/demos/llama3/tt/generator_vllm.py | 47 ++++- models/demos/llama3/tt/llama_attention.py | 7 +- models/demos/llama3/tt/llama_model.py | 5 +- .../tt/multimodal/llama_cross_attention.py | 89 +++++--- .../llama_cross_attention_transformer_text.py | 12 +- .../llama3/tt/multimodal/llama_cross_block.py | 2 + .../tt/multimodal/llama_vision_model.py | 113 +++++++++- 9 files changed, 400 insertions(+), 93 deletions(-) diff --git a/models/demos/llama3/demo/simple_vision_demo.py b/models/demos/llama3/demo/simple_vision_demo.py index cda3c2ed9577..1d5654efba29 100644 --- a/models/demos/llama3/demo/simple_vision_demo.py +++ b/models/demos/llama3/demo/simple_vision_demo.py @@ -189,22 +189,14 @@ def test_llama_multimodal_demo_text( position_id = prefill_lens + gen_idx next_token_tensor = next_tokens.reshape(max_batch_size, 1) - if enable_trace: - logits = generator.easy_trace( - position_id, - next_token_tensor, - batch_xattn_masks, - batch_text_masks, - xattn_caches, - ) - else: - logits = generator.decode_forward( - position_id, - next_token_tensor, - batch_xattn_masks, - batch_text_masks, - xattn_caches, - ) + logits = generator.decode_forward( + position_id, + next_token_tensor, + batch_xattn_masks, + batch_text_masks, + xattn_caches, + enable_trace=enable_trace, + ) next_tokens, next_texts = sampler(logits) # Update next token diff --git a/models/demos/llama3/tt/generator.py b/models/demos/llama3/tt/generator.py index c42450e48d3f..89fde136d6df 100644 --- a/models/demos/llama3/tt/generator.py +++ b/models/demos/llama3/tt/generator.py @@ -167,7 +167,7 @@ def decode_forward_trace_text( return logits - def prefill_forward_single_user( + def _prefill_forward_single_user( self, vision_images, vision_mask, @@ -178,6 +178,7 @@ def prefill_forward_single_user( prefill_len, page_table=None, kv_cache=None, + cross_page_table=None, ): """ Performs vision encode step then text prefill. @@ -194,6 +195,10 @@ def prefill_forward_single_user( if page_table is not None: page_table = self._get_prefill_user_page_table(page_table, kv_cache, prefill_len) + if cross_page_table is not None: + num_vision_tokens = vision_tokens.shape[2] + cross_page_table = self._get_prefill_user_page_table(cross_page_table, kv_cache, num_vision_tokens) + ( tt_h, tt_xattn_mask, @@ -201,12 +206,14 @@ def prefill_forward_single_user( tt_full_text_mask_expand_11SD, rot_mats, tt_page_table, + tt_cross_page_table, ) = self.model.prepare_inputs_prefill( tokens, cross_attention_masks, full_text_row_masked_out_mask, prefill_len=prefill_len, page_table=page_table, + cross_page_table=cross_page_table, ) tt_logits = self.model.ttnn_prefill_forward( @@ -221,9 +228,11 @@ def prefill_forward_single_user( page_table=tt_page_table, kv_cache=kv_cache, get_last_token=(last_token_idx // 32) * 32, + cross_page_table=tt_cross_page_table, ) del tt_page_table + del tt_cross_page_table logits = self.model.process_output_prefill(tt_logits, B, last_token_idx=(last_token_idx % 32)) @@ -239,9 +248,10 @@ def prefill_forward( prompt_lens, page_table=None, kv_cache=None, + cross_page_table=None, ): """ - Batched version of prefill_forward_single_user for vision model. + Batched version of _prefill_forward_single_user for vision model. """ batch, batch_seq_len = tokens.shape output_logits = torch.zeros(batch, 1, self.model_args.vocab_size) @@ -256,7 +266,7 @@ def prefill_forward( cross_attention_masks, full_text_row_masked_out_mask, logits, - ) = self.prefill_forward_single_user( + ) = self._prefill_forward_single_user( vision_images=vision_images[user_id], vision_mask=vision_masks[user_id], tokens=tokens[user_id : user_id + 1, :seq_len], # Keep batch dimension @@ -266,6 +276,7 @@ def prefill_forward( prefill_len=seq_len, page_table=page_table, kv_cache=kv_cache, + cross_page_table=cross_page_table, ) output_logits[user_id] = logits output_xattn_masks.append(cross_attention_masks) @@ -281,14 +292,51 @@ def decode_forward( tokens, cross_attention_masks, full_text_row_masked_out_mask, - xattn_caches, + xattn_caches=None, + page_table=None, + kv_cache=None, + cross_page_table=None, + enable_trace=True, + read_from_device=True, + ): + decode_kwargs = { + "position_id": start_pos, + "tokens": tokens, + "cross_attention_masks": cross_attention_masks, + "full_text_row_masked_out_mask": full_text_row_masked_out_mask, + "xattn_caches": xattn_caches, + "page_table": page_table, + "kv_cache": kv_cache, + "cross_page_table": cross_page_table, + } + if enable_trace: + tt_logits = self._easy_trace(**decode_kwargs) + else: + tt_logits = self._decode_forward_no_trace(**decode_kwargs) + + if read_from_device: + return self.read_decode_output(tt_logits, tokens.shape[0]) + else: + return tt_logits + + def read_decode_output(self, tt_logits, unpadded_batch): + logits = self.model.process_output_decode(tt_logits, B=unpadded_batch, S=1) + return logits + + def _decode_forward_no_trace( + self, + position_id, + tokens, + cross_attention_masks, + full_text_row_masked_out_mask, + xattn_caches=None, page_table=None, kv_cache=None, - prompt_lens=None, + cross_page_table=None, ): """ Performs text decode step. - Returns logits + Returns tt_logits on device """ # forward_decode should be traced callable @@ -303,8 +351,14 @@ def decode_forward( tt_position_id, tt_rot_mats, tt_page_table, + tt_cross_page_table, ) = self.model.prepare_inputs_decode( - tokens, cross_attention_masks, full_text_row_masked_out_mask, position_id=start_pos, page_table=page_table + tokens, + cross_attention_masks, + full_text_row_masked_out_mask, + position_id=position_id, + page_table=page_table, + cross_page_table=cross_page_table, ) tt_logits = self.model.ttnn_decode_forward( @@ -316,18 +370,21 @@ def decode_forward( tt_rot_mats, page_table=tt_page_table, kv_cache=kv_cache, + cross_page_table=tt_cross_page_table, ) - logits = self.model.process_output_decode(tt_logits, B, S) - return logits + return tt_logits - def capture_trace( + def _capture_trace( self, position_id, tokens, cross_attention_masks, full_text_row_masked_out_mask, xattn_caches, + page_table=None, + kv_cache=None, + cross_page_table=None, ): """ Captures a trace for the decode_forward method. @@ -339,8 +396,14 @@ def capture_trace( tt_position_id, tt_rot_mats, tt_page_table, + tt_cross_page_table, ) = self.model.prepare_inputs_decode( - tokens, cross_attention_masks, full_text_row_masked_out_mask, position_id=position_id + tokens, + cross_attention_masks, + full_text_row_masked_out_mask, + position_id=position_id, + page_table=page_table, + cross_page_table=cross_page_table, ) # Compile run @@ -351,7 +414,11 @@ def capture_trace( xattn_caches, tt_position_id, tt_rot_mats, + page_table=tt_page_table, + kv_cache=kv_cache, + cross_page_table=tt_cross_page_table, ) + logger.info("Done Compiling Model") # Get inputs ready for trace run ( @@ -360,9 +427,15 @@ def capture_trace( tt_full_text_mask_expand_1NSH, tt_position_id, tt_rope_id, - _, + tt_page_table, + tt_cross_page_table, ) = self.model.prepare_decode_inputs_host( - tokens, cross_attention_masks, full_text_row_masked_out_mask, position_id + tokens, + cross_attention_masks, + full_text_row_masked_out_mask, + position_id, + page_table=page_table, + cross_page_table=cross_page_table, ) ( @@ -371,8 +444,18 @@ def capture_trace( tt_full_text_mask_expand_1NSH, tt_position_id, tt_rope_id, + tt_page_table, + tt_cross_page_table, ) = copy_host_to_device( - (tt_h, tt_xattn_mask, tt_full_text_mask_expand_1NSH, tt_position_id, tt_rope_id), + ( + tt_h, + tt_xattn_mask, + tt_full_text_mask_expand_1NSH, + tt_position_id, + tt_rope_id, + tt_page_table, + tt_cross_page_table, + ), mesh_device=self.mesh_device, ) @@ -400,19 +483,34 @@ def capture_trace( xattn_caches, tt_position_id, tt_rot_mats, + page_table=tt_page_table, + kv_cache=kv_cache, + cross_page_table=tt_cross_page_table, ) ttnn.end_trace_capture(self.mesh_device, trace_id, cq_id=0) + logger.info("Done Capturing Decode Trace") - return trace_id, tt_logits_rm, tt_h, tt_xattn_mask, tt_full_text_mask_expand_1NSH, tt_position_id, tt_rope_id + return ( + trace_id, + tt_logits_rm, + tt_h, + tt_xattn_mask, + tt_full_text_mask_expand_1NSH, + tt_position_id, + tt_rope_id, + tt_page_table, + tt_cross_page_table, + ) - def decode_forward_trace( + def _decode_forward_trace( self, position_id, tokens, cross_attention_masks, full_text_row_masked_out_mask, - xattn_caches, # TODO: unused since captured in trace? + page_table, + cross_page_table, trace_id, trace_logits_rm, trace_h, @@ -420,43 +518,64 @@ def decode_forward_trace( trace_full_text_mask_expand_1NSH, trace_position_id, trace_rope_id, + trace_page_table, + trace_cross_page_table, ): + """ + Executes the trace for the decode_forward method but does not read back outputs. + """ ( tt_h, tt_xattn_mask, tt_full_text_mask_expand_1NSH, tt_position_id, tt_rope_id, - _, + tt_page_table, + tt_cross_page_table, ) = self.model.prepare_decode_inputs_host( - tokens, cross_attention_masks, full_text_row_masked_out_mask, position_id=position_id + tokens, + cross_attention_masks, + full_text_row_masked_out_mask, + position_id=position_id, + page_table=page_table, + cross_page_table=cross_page_table, ) copy_host_to_device( - host_tensors=(tt_h, tt_xattn_mask, tt_full_text_mask_expand_1NSH, tt_position_id, tt_rope_id), + host_tensors=( + tt_h, + tt_xattn_mask, + tt_full_text_mask_expand_1NSH, + tt_position_id, + tt_rope_id, + tt_page_table, + tt_cross_page_table, + ), device_tensors=( trace_h, trace_xattn_mask, trace_full_text_mask_expand_1NSH, trace_position_id, trace_rope_id, + trace_page_table, + trace_cross_page_table, ), ) ttnn.execute_trace(self.mesh_device, trace_id, cq_id=0, blocking=False) - B, S = tokens.shape - logits = self.model.process_output_decode(trace_logits_rm, B=B, S=S) - - return logits + return trace_logits_rm - def easy_trace( + def _easy_trace( self, position_id, tokens, cross_attention_masks, full_text_row_masked_out_mask, - xattn_caches, + xattn_caches=None, + page_table=None, + kv_cache=None, + cross_page_table=None, ): """ Tracing is easy! Just call this method and we'll handle tracing for you. @@ -470,12 +589,17 @@ def easy_trace( tt_full_text_mask_expand_1NSH, tt_position_id, tt_rope_id, - ) = self.capture_trace( + tt_page_table, + tt_cross_page_table, + ) = self._capture_trace( position_id, tokens, cross_attention_masks, full_text_row_masked_out_mask, xattn_caches, + page_table=page_table, + kv_cache=kv_cache, + cross_page_table=cross_page_table, ) self.trace_id = trace_id self.trace_inputs = { @@ -484,17 +608,20 @@ def easy_trace( "tt_full_text_mask_expand_1NSH": tt_full_text_mask_expand_1NSH, "tt_position_id": tt_position_id, "tt_rope_id": tt_rope_id, + "tt_page_table": tt_page_table, + "tt_cross_page_table": tt_cross_page_table, } self.trace_outputs = { "tt_logits_rm": tt_logits_rm, } - return self.decode_forward_trace( + trace_logits_rm = self._decode_forward_trace( position_id, tokens, cross_attention_masks, full_text_row_masked_out_mask, - xattn_caches, + page_table, + cross_page_table, self.trace_id, self.trace_outputs["tt_logits_rm"], self.trace_inputs["tt_h"], @@ -502,8 +629,12 @@ def easy_trace( self.trace_inputs["tt_full_text_mask_expand_1NSH"], self.trace_inputs["tt_position_id"], self.trace_inputs["tt_rope_id"], + self.trace_inputs["tt_page_table"], + self.trace_inputs["tt_cross_page_table"], ) + return trace_logits_rm + def generate( self, model_input, @@ -526,7 +657,7 @@ def generate( cross_attention_masks, full_text_row_masked_out_mask, logits, - ) = self.prefill_forward_single_user( + ) = self._prefill_forward_single_user( vision_images, vision_mask, prompt_tokens_tensor, @@ -536,7 +667,7 @@ def generate( prefill_len=prefill_len, ) - logits = logits.view(1, 1, self.model_args.max_vocab_size) + logits = logits.view(1, 1, self.model_args.vocab_size) def sample(logits): if temperature > 0: @@ -564,6 +695,7 @@ def sample(logits): [cross_attention_masks], [full_text_row_masked_out_mask], xattn_caches, + enable_trace=False, ) next_token, text = sample(logits) diff --git a/models/demos/llama3/tt/generator_vllm.py b/models/demos/llama3/tt/generator_vllm.py index f962b2801b1a..7989aba9547b 100644 --- a/models/demos/llama3/tt/generator_vllm.py +++ b/models/demos/llama3/tt/generator_vllm.py @@ -9,14 +9,17 @@ from models.demos.llama3.tt.generator import LlamaGenerator from models.demos.llama3.demo.simple_vision_demo import create_multimodal_model +from models.utility_functions import nearest_32 from vllm.inputs import INPUT_REGISTRY, DecoderOnlyInputs, EncoderDecoderInputs, InputContext +from vllm.model_executor.models.interfaces import SupportsMultiModal +from vllm.model_executor.models.mllama import MLLAMA_IMAGE_TOKEN_ID, MLLAMA_IMAGE_TOKEN def input_processor_for_mllama(ctx: InputContext, inputs: Union[DecoderOnlyInputs, EncoderDecoderInputs]): """ Based on vllm.model_executor.models.mllama.py::input_processor_for_mllama(). - Note that vLLM's input_processor_for_mllama performs additional processing to handle chunking which we do not yet support. + Note that vLLM's input_processor_for_mllama performs additional processing to compute num_tiles while here it is fixed. """ # Move encoder_prompt to prompt. If the user does not explicitly provide separate @@ -27,11 +30,32 @@ def input_processor_for_mllama(ctx: InputContext, inputs: Union[DecoderOnlyInput inputs["prompt"] = inputs["encoder_prompt"] inputs["prompt_token_ids"] = inputs["encoder_prompt_token_ids"] + multi_modal_data = inputs.get("encoder_multi_modal_data") + if multi_modal_data is None or "image" not in multi_modal_data or multi_modal_data["image"] is None: + # text-only + inputs["encoder_prompt"] = "" + inputs["encoder_prompt_token_ids"] = [] + inputs["encoder_multi_modal_data"] = {} + return inputs + + # Set encoder prompt length based on the number of vision tokens so block manager allocates enough blocks (cross block tables). + hf_config = ctx.model_config.hf_config + assert hf_config.vision_config.image_size % 14 == 0, "chunk size should be multiple of 14" + token_per_chunk = nearest_32( + (hf_config.vision_config.image_size // 14) ** 2 + 1 + ) # Note: we use nearest 32 while vLLM does not by default + num_vision_tokens = ( + hf_config.vision_config.max_num_tiles * token_per_chunk + ) # Note: we use max_num_tiles while vLLM uses num_tiles by default + inputs["encoder_prompt"] = MLLAMA_IMAGE_TOKEN * num_vision_tokens + inputs["encoder_prompt_token_ids"] = [MLLAMA_IMAGE_TOKEN_ID] * num_vision_tokens + return inputs +# @MULTIMODAL_REGISTRY.register_image_input_mapper() # TODO: Add once model can accept inputs from multi_modal_input_mapper (raw pixel values) @INPUT_REGISTRY.register_input_processor(input_processor_for_mllama) -class TtMllamaForConditionalGeneration(LlamaGenerator): +class TtMllamaForConditionalGeneration(LlamaGenerator, SupportsMultiModal): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -52,11 +76,10 @@ def prefill_forward( self, tokens: torch.Tensor, images: List[PIL.Image.Image], - xattn_caches, - start_pos, - page_table: torch.Tensor = None, - kv_cache=None, - prompt_lens=None, + page_table: torch.Tensor, + kv_cache, + prompt_lens, + cross_page_table: torch.Tensor, ): """ Replaces prefill_forward from LlamaGenerator with a version that supports mask creation. @@ -73,5 +96,13 @@ def prefill_forward( total_lens.append(prompt_lens[user_id] + self.max_gen_len) return super().prefill_forward( - vision_images, vision_masks, tokens, xattn_caches, total_lens, prompt_lens, page_table, kv_cache + vision_images, + vision_masks, + tokens, + None, + total_lens, + prompt_lens, + page_table=page_table, + kv_cache=kv_cache, + cross_page_table=cross_page_table, ) diff --git a/models/demos/llama3/tt/llama_attention.py b/models/demos/llama3/tt/llama_attention.py index cba5b063237b..a9405a437391 100644 --- a/models/demos/llama3/tt/llama_attention.py +++ b/models/demos/llama3/tt/llama_attention.py @@ -92,7 +92,6 @@ def __init__( self.ccl_topology = configuration.ccl_topology() self.is_multichip = configuration.is_multichip - self.layer_num = layer_num layer_name = configuration.get_state_dict_prefix(self.__class__.__name__, layer_num) if configuration.dummy_weights or (weight_cache_path is None): cache_name = lambda _: None @@ -317,8 +316,8 @@ def forward_decode( # KV update ### if kv_cache: - keys = kv_cache[self.layer_num][0] - values = kv_cache[self.layer_num][1] + keys = kv_cache[0] + values = kv_cache[1] else: keys = self.layer_past[0] values = self.layer_past[1] @@ -536,7 +535,7 @@ def forward_prefill(self, x_11SH, rot_mats, user_id: int = 0, page_table=None, k # Fill KV-Cache if kv_cache: - keys_BKSD, values_BKSD = kv_cache[self.layer_num][0], kv_cache[self.layer_num][1] + keys_BKSD, values_BKSD = kv_cache[0], kv_cache[1] else: keys_BKSD, values_BKSD = self.layer_past[0], self.layer_past[1] diff --git a/models/demos/llama3/tt/llama_model.py b/models/demos/llama3/tt/llama_model.py index bc6197379767..f86a0058e6e1 100644 --- a/models/demos/llama3/tt/llama_model.py +++ b/models/demos/llama3/tt/llama_model.py @@ -172,7 +172,10 @@ def prepare_decode_inputs_host(self, tokens, current_pos, page_table=None): mesh_mapper=mesh_mapper, ) - rope_idxs = self.rope_setup.get_rot_idxs(current_pos, on_host=True) + rot_current_pos = torch.maximum( + current_pos, torch.tensor(0, dtype=torch.int64) + ) # Ensure position indices are non-negative + rope_idxs = self.rope_setup.get_rot_idxs(rot_current_pos, on_host=True) current_pos_tt = ttnn.from_torch( current_pos, device=None, diff --git a/models/demos/llama3/tt/multimodal/llama_cross_attention.py b/models/demos/llama3/tt/multimodal/llama_cross_attention.py index 5aa338a012b9..57bfedecffa3 100644 --- a/models/demos/llama3/tt/multimodal/llama_cross_attention.py +++ b/models/demos/llama3/tt/multimodal/llama_cross_attention.py @@ -131,7 +131,7 @@ def __init__( eps=self.norm_eps, ) - def compute_xattn_kv_cache(self, xattn_tokens, user_id, xattn_cache): + def compute_xattn_kv_cache(self, xattn_tokens, user_id, xattn_cache, cross_page_table=None): """ Uses xattn_tokens to compute K, V. Should be run inside of forward_prefill. Updates xattn_cache with K, V (TODO: support page table for KV cache) @@ -188,17 +188,28 @@ def compute_xattn_kv_cache(self, xattn_tokens, user_id, xattn_cache): xk = self.k_norm(xk, mode="decode") k_cache, v_cache = xattn_cache - # Work around fill_cache memory constraint by making these sharded - k_fill = ttnn.interleaved_to_sharded(xk, self.model_config["XATTN_KV_PREFILL_MEM_CFG"](seqlen_y)) - v_fill = ttnn.interleaved_to_sharded(xv, self.model_config["XATTN_KV_PREFILL_MEM_CFG"](seqlen_y)) - ttnn.fill_cache(k_cache, k_fill, user_id) - ttnn.fill_cache(v_cache, v_fill, user_id) + if cross_page_table: + ttnn.experimental.paged_fill_cache( + k_cache, ttnn.experimental.typecast(xk, k_cache.dtype), cross_page_table, batch_idx=user_id + ) + ttnn.experimental.paged_fill_cache( + v_cache, ttnn.experimental.typecast(xv, v_cache.dtype), cross_page_table, batch_idx=user_id + ) + else: + # Work around fill_cache memory constraint by making these sharded + k_fill = ttnn.interleaved_to_sharded(xk, self.model_config["XATTN_KV_PREFILL_MEM_CFG"](seqlen_y)) + v_fill = ttnn.interleaved_to_sharded(xv, self.model_config["XATTN_KV_PREFILL_MEM_CFG"](seqlen_y)) + + ttnn.fill_cache(k_cache, k_fill, user_id) + ttnn.fill_cache(v_cache, v_fill, user_id) return xk, xv - def forward_decode(self, x_11SH, xattn_mask, full_text_row_masked_out_mask_1NSH, xattn_cache): - batch = xattn_cache[0].shape[0] + def forward_decode( + self, x_11SH, xattn_mask, full_text_row_masked_out_mask_1NSH, xattn_cache, cross_page_table=None + ): + batch = xattn_mask.shape[1] x_11SH = ttnn.sharded_to_interleaved(x_11SH, ttnn.L1_MEMORY_CONFIG) # TODO support sharded input @@ -232,17 +243,29 @@ def forward_decode(self, x_11SH, xattn_mask, full_text_row_masked_out_mask_1NSH, ) # TODO: Can I get rid of the KV repeat_interleave? - - output = ttnn.transformer.scaled_dot_product_attention_decode( - xq, - xk, - xv, - is_causal=False, - attn_mask=xattn_mask, - scale=self.scale, - program_config=program_config, - compute_kernel_config=self.compute_kernel_config_sdpa, - ) + if cross_page_table: + output = ttnn.transformer.paged_scaled_dot_product_attention_decode( + xq, + xk, + xv, + is_causal=False, + attn_mask=xattn_mask, + page_table_tensor=cross_page_table, + scale=self.scale, + program_config=program_config, + compute_kernel_config=self.compute_kernel_config_sdpa, + ) + else: + output = ttnn.transformer.scaled_dot_product_attention_decode( + xq, + xk, + xv, + is_causal=False, + attn_mask=xattn_mask, + scale=self.scale, + program_config=program_config, + compute_kernel_config=self.compute_kernel_config_sdpa, + ) # WARNING: this broadcast is also broken, must broadcast on host output = ttnn.mul(output, full_text_row_masked_out_mask_1NSH) @@ -275,14 +298,23 @@ def forward_decode(self, x_11SH, xattn_mask, full_text_row_masked_out_mask_1NSH, return ttnn.to_memory_config(output, self.model_config["DECODE_RESIDUAL_MEMCFG"]) def forward_prefill( - self, x_11SH, xattn_mask, full_text_row_masked_out_mask_1NSH, xattn_cache, user_id, vision_tokens + self, + x_11SH, + xattn_mask, + full_text_row_masked_out_mask_1NSH, + xattn_cache, + user_id, + vision_tokens, + cross_page_table=None, ): seq_len = x_11SH.shape[-2] # B, S, D assert seq_len % 32 == 0 and seq_len > 0, "Seqlen must be divisible by 32" # Compute cross attention cache. Return contiguous caches - k_cache_user, v_cache_user = self.compute_xattn_kv_cache(vision_tokens, user_id, xattn_cache) + k_cache_user, v_cache_user = self.compute_xattn_kv_cache( + vision_tokens, user_id, xattn_cache, cross_page_table=cross_page_table + ) cache_seq_len = k_cache_user.shape[-2] if seq_len > 1024: @@ -357,7 +389,15 @@ def forward_prefill( return output def forward( - self, x_11SH, xattn_mask, full_text_row_masked_out_mask_1NSH, xattn_cache, mode, user_id=0, vision_tokens=None + self, + x_11SH, + xattn_mask, + full_text_row_masked_out_mask_1NSH, + xattn_cache, + mode, + user_id=0, + vision_tokens=None, + cross_page_table=None, ): if mode == "prefill": return self.forward_prefill( @@ -367,6 +407,9 @@ def forward( xattn_cache, user_id=user_id, vision_tokens=vision_tokens, + cross_page_table=cross_page_table, ) else: - return self.forward_decode(x_11SH, xattn_mask, full_text_row_masked_out_mask_1NSH, xattn_cache) + return self.forward_decode( + x_11SH, xattn_mask, full_text_row_masked_out_mask_1NSH, xattn_cache, cross_page_table=cross_page_table + ) diff --git a/models/demos/llama3/tt/multimodal/llama_cross_attention_transformer_text.py b/models/demos/llama3/tt/multimodal/llama_cross_attention_transformer_text.py index 1c05ec06e1cf..162f6dc6da75 100644 --- a/models/demos/llama3/tt/multimodal/llama_cross_attention_transformer_text.py +++ b/models/demos/llama3/tt/multimodal/llama_cross_attention_transformer_text.py @@ -276,10 +276,12 @@ def forward( mode="decode", page_table=None, kv_cache=None, + cross_page_table=None, text_only_inference=False, vision_tokens=None, get_last_token=-1, ): + total_layer_idx = 0 # Used to track the total layer index for accessing the paged kv cache for idx, ( layer, xattn_layer, @@ -289,13 +291,18 @@ def forward( h = xattn_layer( h, xattn_mask=xattn_mask, - xattn_cache=xattn_caches[xattn_layer_idx], + xattn_cache=xattn_caches[xattn_layer_idx] + if cross_page_table is None + else kv_cache[total_layer_idx], full_text_row_masked_out_mask_1NSH=full_text_row_masked_out_mask_1NSH, full_text_row_masked_out_mask_11SD=full_text_row_masked_out_mask_11SD, mode=mode, user_id=user_id, vision_tokens=vision_tokens, + cross_page_table=cross_page_table, ) + if idx in self.fusion_schedule: + total_layer_idx += 1 h = layer( h, current_pos, @@ -303,8 +310,9 @@ def forward( user_id=user_id, mode=mode, page_table=page_table, - kv_cache=kv_cache, + kv_cache=kv_cache[total_layer_idx] if kv_cache is not None else None, ) + total_layer_idx += 1 if get_last_token != -1: h = ttnn.slice(h, (0, 0, get_last_token, 0), (1, 1, get_last_token + 32, h.shape[-1])) diff --git a/models/demos/llama3/tt/multimodal/llama_cross_block.py b/models/demos/llama3/tt/multimodal/llama_cross_block.py index 9d8c3760af04..90c693b95674 100644 --- a/models/demos/llama3/tt/multimodal/llama_cross_block.py +++ b/models/demos/llama3/tt/multimodal/llama_cross_block.py @@ -125,6 +125,7 @@ def forward( mode, user_id=0, vision_tokens=None, + cross_page_table=None, ): skip_mem_cfg = self.model_config["DECODE_RESIDUAL_MEMCFG"] if mode == "decode" else ttnn.DRAM_MEMORY_CONFIG assert ( @@ -139,6 +140,7 @@ def forward( mode=mode, user_id=user_id, vision_tokens=vision_tokens, + cross_page_table=cross_page_table, ) # FIXME: DRAM workaround for No circular buffer with id error attn_out = ttnn.to_memory_config(attn_out, memory_config=ttnn.DRAM_MEMORY_CONFIG) diff --git a/models/demos/llama3/tt/multimodal/llama_vision_model.py b/models/demos/llama3/tt/multimodal/llama_vision_model.py index ef400f99275b..e879f2d23427 100644 --- a/models/demos/llama3/tt/multimodal/llama_vision_model.py +++ b/models/demos/llama3/tt/multimodal/llama_vision_model.py @@ -282,7 +282,13 @@ def prepare_inputs_common(self, position_ids, tokens): return h def prepare_inputs_prefill( - self, tokens, cross_attention_masks, full_text_row_masked_out_mask, prefill_len, page_table=None + self, + tokens, + cross_attention_masks, + full_text_row_masked_out_mask, + prefill_len, + page_table=None, + cross_page_table=None, ): B = tokens.shape[0] assert B == 1, f"Only batch 1 is supported, got {B}" @@ -361,6 +367,17 @@ def prepare_inputs_prefill( mesh_mapper=ttnn.ReplicateTensorToMesh(self.mesh_device), ) + if isinstance(cross_page_table, torch.Tensor): + # Support vLLM tensor cross_page_table input + cross_page_table = ttnn.as_tensor( + cross_page_table, + device=self.mesh_device, + memory_config=ttnn.DRAM_MEMORY_CONFIG, + dtype=ttnn.int32, + layout=ttnn.ROW_MAJOR_LAYOUT, + mesh_mapper=ttnn.ReplicateTensorToMesh(self.mesh_device), + ) + return ( tt_h, tt_xattn_mask, @@ -368,10 +385,17 @@ def prepare_inputs_prefill( tt_full_text_mask_expand_11SD, rot_mats, page_table, + cross_page_table, ) def prepare_inputs_decode( - self, tokens, cross_attention_masks, full_text_row_masked_out_mask, position_id, page_table=None + self, + tokens, + cross_attention_masks, + full_text_row_masked_out_mask, + position_id, + page_table=None, + cross_page_table=None, ): ( tt_h, @@ -380,8 +404,14 @@ def prepare_inputs_decode( tt_position_id, tt_rope_id, tt_page_table, + tt_cross_page_table, ) = self.prepare_decode_inputs_host( - tokens, cross_attention_masks, full_text_row_masked_out_mask, position_id, page_table=page_table + tokens, + cross_attention_masks, + full_text_row_masked_out_mask, + position_id, + page_table=page_table, + cross_page_table=cross_page_table, ) ( @@ -391,8 +421,17 @@ def prepare_inputs_decode( tt_position_id, tt_rope_id, tt_page_table, + tt_cross_page_table, ) = copy_host_to_device( - (tt_h, tt_xattn_mask, tt_full_text_mask_expand_1NSH, tt_position_id, tt_rope_id, tt_page_table), + ( + tt_h, + tt_xattn_mask, + tt_full_text_mask_expand_1NSH, + tt_position_id, + tt_rope_id, + tt_page_table, + tt_cross_page_table, + ), mesh_device=self.mesh_device, ) @@ -411,15 +450,26 @@ def prepare_inputs_decode( tt_position_id, tt_rot_mats, tt_page_table, + tt_cross_page_table, ) def prepare_decode_inputs_host( - self, tokens, cross_attention_masks, full_text_row_masked_out_mask, position_id, page_table=None + self, + tokens, + cross_attention_masks, + full_text_row_masked_out_mask, + position_id, + page_table=None, + cross_page_table=None, ): B = tokens.shape[0] assert ( B == self.configuration.max_batch_size ), f"Batch size must match max batch size. Got {B}, expected {self.configuration.max_batch_size}" + unpadded_batch_size = len(cross_attention_masks) + assert unpadded_batch_size == len( + full_text_row_masked_out_mask + ), f"cross_attention_masks batch dim ({unpadded_batch_size}) does not match full_text_row_masked_out_mask batch dim ({len(full_text_row_masked_out_mask)})" h = self.prepare_inputs_common(position_id, tokens) tt_h = self.configuration.prepare_residual_tensor_decode( h, @@ -435,8 +485,20 @@ def prepare_decode_inputs_host( mesh_mapper=ttnn.ReplicateTensorToMesh(self.mesh_device), ) - tt_rope_id = self.text_model.rope_setup.get_rot_idxs(position_id, on_host=True) - xattn_mask = torch.cat([cross_attention_masks[i][:, :, position_id[i]] for i in range(B)], dim=1).unsqueeze(0) + rot_position_id = torch.maximum( + position_id, torch.tensor(0, dtype=torch.int64) + ) # Ensure position indices are non-negative + tt_rope_id = self.text_model.rope_setup.get_rot_idxs(rot_position_id, on_host=True) + + xattn_mask = torch.cat( + [cross_attention_masks[i][:, :, position_id[i]] for i in range(unpadded_batch_size)], dim=1 + ).unsqueeze(0) + # Pad xattn_mask along batch if tokens have been padded + if B > unpadded_batch_size: + xattn_mask = torch.cat( + [xattn_mask, torch.zeros(1, 1, B - unpadded_batch_size, xattn_mask.shape[-1])], dim=2 + ) + xattn_mask_expand = xattn_mask.expand(-1, self.configuration.n_heads // self.configuration.num_devices, -1, -1) xattn_mask_expand = xattn_mask_expand.transpose(1, 2).contiguous() @@ -449,8 +511,13 @@ def prepare_decode_inputs_host( ) full_text_mask = torch.cat( - [full_text_row_masked_out_mask[i][:, :, position_id[i]] for i in range(B)], dim=1 + [full_text_row_masked_out_mask[i][:, :, position_id[i]] for i in range(unpadded_batch_size)], dim=1 ).unsqueeze(0) + # Pad full_text_mask along batch if tokens have been padded + if B > unpadded_batch_size: + full_text_mask = torch.cat( + [full_text_mask, torch.zeros(1, 1, B - unpadded_batch_size, full_text_mask.shape[-1])], dim=2 + ) full_text_mask_expand_1NSH = full_text_mask.expand( -1, self.configuration.n_heads // self.configuration.num_devices, -1, self.configuration.head_dim ) @@ -472,6 +539,15 @@ def prepare_decode_inputs_host( mesh_mapper=ttnn.ReplicateTensorToMesh(self.mesh_device), ) + if isinstance(cross_page_table, torch.Tensor): + # Support vLLM tensor cross_page_table input + cross_page_table = ttnn.as_tensor( + cross_page_table, + dtype=ttnn.int32, + layout=ttnn.ROW_MAJOR_LAYOUT, + mesh_mapper=ttnn.ReplicateTensorToMesh(self.mesh_device), + ) + return ( tt_h, tt_xattn_mask, @@ -479,6 +555,7 @@ def prepare_decode_inputs_host( tt_position_id, tt_rope_id, page_table, + cross_page_table, ) def transform_decode_inputs_device(self, tt_h, tt_rope_id, tt_xattn_mask, tt_full_text_mask_expand_1NSH, B): @@ -550,6 +627,7 @@ def forward( vision_tokens=None, page_table=None, kv_cache=None, + cross_page_table=None, ) -> torch.Tensor: """ This method takes torch tensors in, returns torch tensors. @@ -570,12 +648,14 @@ def forward( tt_position_id, rot_mats, tt_page_table, + tt_cross_page_table, ) = prepare_fn( tokens, cross_attention_masks, full_text_row_masked_out_mask, pos_arg, page_table=page_table, + cross_page_table=cross_page_table, ) logits = self.text_model.forward( @@ -592,6 +672,7 @@ def forward( kv_cache=kv_cache, text_only_inference=text_only_inference, vision_tokens=vision_tokens, + cross_page_table=tt_cross_page_table, ) tt_out = ttnn.to_layout(logits, ttnn.ROW_MAJOR_LAYOUT) @@ -611,10 +692,17 @@ def ttnn_prefill_forward( page_table=None, kv_cache=None, get_last_token=-1, + cross_page_table=None, ): """ This method runs prefill forward. It takes ttnn tensors in, returns ttnn tensors. """ + + if cross_page_table is not None: + assert ( + xattn_caches is None and kv_cache is not None + ), "no separate xattn_caches should be allocated when using cross_page_table with paged kv cache" + logits = self.text_model.forward( h, xattn_mask=xattn_mask, @@ -627,6 +715,7 @@ def ttnn_prefill_forward( mode="prefill", page_table=page_table, kv_cache=kv_cache, + cross_page_table=cross_page_table, vision_tokens=vision_tokens, get_last_token=get_last_token, ) @@ -643,10 +732,17 @@ def ttnn_decode_forward( rot_mats, page_table=None, kv_cache=None, + cross_page_table=None, ): """ This method runs decode forward. It takes ttnn tensors in, returns ttnn tensors. """ + + if cross_page_table is not None: + assert ( + xattn_caches is None and kv_cache is not None + ), "no separate xattn_caches should be allocated when using cross_page_table with paged kv cache" + logits = self.text_model.forward( h, xattn_mask=xattn_mask, @@ -658,6 +754,7 @@ def ttnn_decode_forward( mode="decode", page_table=page_table, kv_cache=kv_cache, + cross_page_table=cross_page_table, ) tt_out = ttnn.to_layout(logits, ttnn.ROW_MAJOR_LAYOUT) return tt_out From 1164a9359e41bca0bf498955f9cabf4db626e808 Mon Sep 17 00:00:00 2001 From: Armin Ale Date: Tue, 17 Dec 2024 16:33:14 -0500 Subject: [PATCH 30/87] #0: Enable Local Sweeps and Use a Faster Interprocess Queue (#16098) ### Problem description - Running large numbers of sweeps is impractical with ES backend, especially if the sweeps are not reused/rerun - Interprocess communication in the runner is slow and a bottleneck for running sweeps ### What's changed - Changed interprocess queues to [faster-fifo](https://github.com/alex-petrenko/faster-fifo) for better throughput/more efficient locking - Modified the parameter generator to optionally dump vectors to disk as JSON instead of pushing to ES - Modified the runner to optionally read vectors from a JSON file on disk instead of reading from ES ### Checklist - [X] Post commit CI passes --- .../actions/install-python-deps/action.yml | 1 + tests/sweep_framework/requirements-sweeps.txt | 1 + .../sweeps_parameter_generator.py | 57 ++++++++++-- tests/sweep_framework/sweeps_runner.py | 86 ++++++++++++++++++- 4 files changed, 136 insertions(+), 9 deletions(-) diff --git a/.github/actions/install-python-deps/action.yml b/.github/actions/install-python-deps/action.yml index e4301d309f8c..f3ec10ee96d7 100644 --- a/.github/actions/install-python-deps/action.yml +++ b/.github/actions/install-python-deps/action.yml @@ -18,6 +18,7 @@ runs: cache-dependency-path: | tt_metal/python_env/requirements-dev.txt docs/requirements-docs.txt + tests/sweep_framework/requirements-sweeps.txt pyproject.toml create_venv.sh install-cmd: ./create_venv.sh diff --git a/tests/sweep_framework/requirements-sweeps.txt b/tests/sweep_framework/requirements-sweeps.txt index b475c2f89cee..8e2aba486ece 100644 --- a/tests/sweep_framework/requirements-sweeps.txt +++ b/tests/sweep_framework/requirements-sweeps.txt @@ -1,3 +1,4 @@ elasticsearch termcolor beautifultable +faster-fifo diff --git a/tests/sweep_framework/sweeps_parameter_generator.py b/tests/sweep_framework/sweeps_parameter_generator.py index 57cfdd8a77b3..67d2f16ec136 100644 --- a/tests/sweep_framework/sweeps_parameter_generator.py +++ b/tests/sweep_framework/sweeps_parameter_generator.py @@ -9,12 +9,11 @@ import datetime import os import hashlib +import json from framework.permutations import * from framework.serialize import serialize -from elasticsearch import Elasticsearch, NotFoundError from framework.statuses import VectorValidity, VectorStatus -from framework.elastic_config import * from framework.sweeps_logger import sweeps_logger as logger SWEEPS_DIR = pathlib.Path(__file__).parent @@ -37,7 +36,10 @@ def generate_vectors(module_name): v["sweep_name"] = module_name invalidate_vectors(test_module, suite_vectors) - export_suite_vectors(module_name, suite, suite_vectors) + if DUMP_FILE: + export_suite_vectors_json(module_name, suite, suite_vectors) + else: + export_suite_vectors(module_name, suite, suite_vectors) # Perform any post-gen validation to the resulting vectors. @@ -51,6 +53,37 @@ def invalidate_vectors(test_module, vectors) -> None: vector["invalid_reason"] = reason +def export_suite_vectors_json(module_name, suite_name, vectors): + EXPORT_DIR_PATH = SWEEPS_DIR / "vectors_export" + EXPORT_PATH = EXPORT_DIR_PATH / str(module_name + ".json") + if not EXPORT_DIR_PATH.exists(): + EXPORT_DIR_PATH.mkdir() + + current_time = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + serialized_vectors = dict() + warnings = [] + for i in range(len(vectors)): + vector = dict() + for elem in vectors[i].keys(): + vector[elem] = serialize(vectors[i][elem], warnings) + input_hash = hashlib.sha224(str(vector).encode("utf-8")).hexdigest() + vector["timestamp"] = current_time + vector["input_hash"] = input_hash + vector["tag"] = SWEEPS_TAG + serialized_vectors[input_hash] = vector + + if EXPORT_PATH.exists(): + with open(EXPORT_PATH, "r") as file: + data = json.load(file) + with open(EXPORT_PATH, "w") as file: + data[suite_name] = serialized_vectors + json.dump(data, file, indent=2) + else: + with open(EXPORT_PATH, "w") as file: + json.dump({suite_name: serialized_vectors}, file, indent=2) + logger.info(f"SWEEPS: Generated {len(vectors)} test vectors for suite {suite_name}.") + + # Output the individual test vectors. def export_suite_vectors(module_name, suite_name, vectors): # Perhaps we export with some sort of readable id, which can be passed to a runner to run specific sets of input vectors. (export seed as well for reproducability) @@ -178,11 +211,25 @@ def clean_module(module_name): help="Custom tag for the vectors you are generating. This is to keep copies seperate from other people's test vectors. By default, this will be your username. You are able to specify a tag when running tests using the runner.", ) parser.add_argument("--explicit", required=False, action="store_true") + parser.add_argument( + "--dump-file", + required=False, + action="store_true", + help="If set, dumps the results to disk in JSON instead of using ES", + ) args = parser.parse_args(sys.argv[1:]) - global ELASTIC_CONNECTION_STRING - ELASTIC_CONNECTION_STRING = get_elastic_url(args.elastic) + global DUMP_FILE + if not args.dump_file: + from elasticsearch import Elasticsearch, NotFoundError + from framework.elastic_config import * + + global ELASTIC_CONNECTION_STRING + ELASTIC_CONNECTION_STRING = get_elastic_url(args.elastic) + DUMP_FILE = False + else: + DUMP_FILE = True global SWEEPS_TAG SWEEPS_TAG = args.tag diff --git a/tests/sweep_framework/sweeps_runner.py b/tests/sweep_framework/sweeps_runner.py index d06298662537..6fcf93734c45 100644 --- a/tests/sweep_framework/sweeps_runner.py +++ b/tests/sweep_framework/sweeps_runner.py @@ -8,10 +8,12 @@ import importlib import datetime import os +import json import enlighten from tt_metal.tools.profiler.process_ops_logs import get_device_data_generate_report from tt_metal.tools.profiler.common import PROFILER_LOGS_DIR -from multiprocessing import Process, Queue +from multiprocessing import Process +from faster_fifo import Queue from queue import Empty import subprocess from framework.statuses import TestStatus, VectorValidity, VectorStatus @@ -235,6 +237,65 @@ def get_suite_vectors(client, vector_index, suite): return header_info, test_vectors +def export_test_results_json(header_info, results): + if len(results) == 0: + return + module_name = header_info[0]["sweep_name"] + EXPORT_DIR_PATH = pathlib.Path(__file__).parent / "results_export" + EXPORT_PATH = EXPORT_DIR_PATH / str(module_name + ".json") + + if not EXPORT_DIR_PATH.exists(): + EXPORT_DIR_PATH.mkdir() + + curr_git_hash = git_hash() + for result in results: + result["git_hash"] = curr_git_hash + + new_data = [] + + for i in range(len(results)): + result = header_info[i] + for elem in results[i].keys(): + if elem == "device_perf": + result[elem] = results[i][elem] + continue + result[elem] = serialize(results[i][elem]) + new_data.append(result) + + if EXPORT_PATH.exists(): + with open(EXPORT_PATH, "r") as file: + old_data = json.load(file) + new_data = old_data + new_data + with open(EXPORT_PATH, "w") as file: + json.dump(new_data, file, indent=2) + else: + with open(EXPORT_PATH, "w") as file: + json.dump(new_data, file, indent=2) + + +def run_sweeps_json(module_name, suite_name): + pbar_manager = enlighten.get_manager() + with open(READ_FILE, "r") as file: + print(READ_FILE) + data = json.load(file) + for suite in data: + if suite_name and suite_name != suite: + continue # user only wants to run a specific suite + + for input_hash in data[suite]: + data[suite][input_hash]["vector_id"] = input_hash + vectors = [data[suite][input_hash] for input_hash in data[suite]] + module_name = vectors[0]["sweep_name"] + test_module = importlib.import_module("sweeps." + module_name) + header_info, test_vectors = sanitize_inputs(vectors) + logger.info(f"Executing tests for module {module_name}, suite {suite}") + results = execute_suite(test_module, test_vectors, pbar_manager, suite) + logger.info(f"Completed tests for module {module_name}, suite {suite}.") + logger.info(f"Tests Executed - {len(results)}") + logger.info("Dumping results to JSON file.") + export_test_results_json(header_info, results) + + def run_sweeps(module_name, suite_name, vector_id): client = Elasticsearch(ELASTIC_CONNECTION_STRING, basic_auth=(ELASTIC_USERNAME, ELASTIC_PASSWORD)) pbar_manager = enlighten.get_manager() @@ -441,6 +502,9 @@ def disable_profiler(): default=os.getenv("USER"), help="Custom tag for the vectors you are running. This is to keep copies seperate from other people's test vectors. By default, this will be your username. You are able to specify a tag when generating tests using the generator.", ) + parser.add_argument( + "--read-file", required=False, help="Read and execute test vectors from a specified file path instead of ES." + ) args = parser.parse_args(sys.argv[1:]) if not args.module_name and args.vector_id: @@ -448,8 +512,19 @@ def disable_profiler(): logger.error("Module name is required if vector id is specified.") exit(1) - global ELASTIC_CONNECTION_STRING - ELASTIC_CONNECTION_STRING = get_elastic_url(args.elastic) + global READ_FILE + if not args.read_file: + from elasticsearch import Elasticsearch, NotFoundError + from framework.elastic_config import * + + global ELASTIC_CONNECTION_STRING + ELASTIC_CONNECTION_STRING = get_elastic_url(args.elastic) + READ_FILE = None + else: + if not args.module_name: + logger.error("You must specify a module with a local file.") + exit(1) + READ_FILE = args.read_file global MEASURE_PERF MEASURE_PERF = args.perf @@ -476,7 +551,10 @@ def disable_profiler(): from framework.device_fixtures import default_device from framework.sweeps_logger import sweeps_logger as logger - run_sweeps(args.module_name, args.suite_name, args.vector_id) + if READ_FILE: + run_sweeps_json(args.module_name, args.suite_name) + else: + run_sweeps(args.module_name, args.suite_name, args.vector_id) if args.watcher: disable_watcher() From 0fdac2b7b678279f235fbdf24aa218f4d8094c22 Mon Sep 17 00:00:00 2001 From: Raymond Kim Date: Tue, 17 Dec 2024 16:47:13 -0500 Subject: [PATCH 31/87] #0: [skip ci] (MINOR) Update to v0.54.0 --- infra/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/VERSION b/infra/VERSION index 8f986634c760..80451e53e54c 100644 --- a/infra/VERSION +++ b/infra/VERSION @@ -2,4 +2,4 @@ # This is solely for helping with version management via our release system # as of this writing # change -v0.53.0 +v0.54.0 From 9db0306a9c06637accd922492869f4fc773515ce Mon Sep 17 00:00:00 2001 From: Joseph Chu Date: Fri, 13 Dec 2024 22:36:59 +0000 Subject: [PATCH 32/87] #15601: Implement support for MeshDevice::reshape(..) --- .../test_ethernet_hop_latencies_no_edm.cpp | 54 ++-- tests/ttnn/distributed/CMakeLists.txt | 6 +- .../distributed/test_distributed_reshape.cpp | 280 ++++++++++++++++++ tt_metal/distributed/mesh_device.cpp | 235 +++++++++++---- tt_metal/distributed/mesh_device.hpp | 31 +- tt_metal/distributed/mesh_device_view.cpp | 22 +- tt_metal/distributed/mesh_device_view.hpp | 15 +- ttnn/cpp/ttnn/distributed/api.cpp | 4 +- .../ttnn/distributed/distributed_pybind.cpp | 7 +- .../ccl/all_gather/device/all_gather_op.cpp | 6 +- .../device/reduce_scatter_op.cpp | 6 +- 11 files changed, 540 insertions(+), 126 deletions(-) create mode 100644 tests/ttnn/distributed/test_distributed_reshape.cpp diff --git a/tests/tt_metal/tt_metal/perf_microbenchmark/ethernet/test_ethernet_hop_latencies_no_edm.cpp b/tests/tt_metal/tt_metal/perf_microbenchmark/ethernet/test_ethernet_hop_latencies_no_edm.cpp index f9adf4f13fbe..dc922d70ecb2 100644 --- a/tests/tt_metal/tt_metal/perf_microbenchmark/ethernet/test_ethernet_hop_latencies_no_edm.cpp +++ b/tests/tt_metal/tt_metal/perf_microbenchmark/ethernet/test_ethernet_hop_latencies_no_edm.cpp @@ -449,48 +449,48 @@ int main(int argc, char** argv) { T3000TestDevice test_fixture; auto view = test_fixture.mesh_device_->get_view(); - auto get_device_list = [](const std::shared_ptr& view, std::size_t n_hops) { + auto get_device_list = [](const MeshDeviceView& view, std::size_t n_hops) { switch (n_hops) { case 2: return std::vector{ - view->get_device(0, 0), - view->get_device(0, 1), + view.get_device(0, 0), + view.get_device(0, 1), }; case 4: return std::vector{ - view->get_device(1, 1), - view->get_device(0, 1), - view->get_device(0, 2), - view->get_device(1, 2), + view.get_device(1, 1), + view.get_device(0, 1), + view.get_device(0, 2), + view.get_device(1, 2), }; case 8: return std::vector{ - view->get_device(1, 1), - view->get_device(1, 0), - view->get_device(0, 0), - view->get_device(0, 1), - view->get_device(0, 2), - view->get_device(0, 3), - view->get_device(1, 3), - view->get_device(1, 2), + view.get_device(1, 1), + view.get_device(1, 0), + view.get_device(0, 0), + view.get_device(0, 1), + view.get_device(0, 2), + view.get_device(0, 3), + view.get_device(1, 3), + view.get_device(1, 2), }; case 12: // Does an extra loop through the inner ring return std::vector{ - view->get_device(1, 1), - view->get_device(1, 0), - view->get_device(0, 0), - view->get_device(0, 1), - view->get_device(0, 2), - view->get_device(1, 2), - view->get_device(1, 1), - view->get_device(0, 1), - view->get_device(0, 2), - view->get_device(0, 3), - view->get_device(1, 3), - view->get_device(1, 2), + view.get_device(1, 1), + view.get_device(1, 0), + view.get_device(0, 0), + view.get_device(0, 1), + view.get_device(0, 2), + view.get_device(1, 2), + view.get_device(1, 1), + view.get_device(0, 1), + view.get_device(0, 2), + view.get_device(0, 3), + view.get_device(1, 3), + view.get_device(1, 2), }; default: TT_THROW("Unsupported hop_count"); return std::vector{}; diff --git a/tests/ttnn/distributed/CMakeLists.txt b/tests/ttnn/distributed/CMakeLists.txt index f41d726988af..5823925eec31 100644 --- a/tests/ttnn/distributed/CMakeLists.txt +++ b/tests/ttnn/distributed/CMakeLists.txt @@ -1,11 +1,13 @@ add_executable( test_distributed test_distributed.cpp - test_distributed_atexit.cpp + test_distributed_reshape.cpp ) +add_executable(test_distributed_atexit test_distributed_atexit.cpp) # Set up properties for the target setup_ttnn_test_target(test_distributed) - +setup_ttnn_test_target(test_distributed_atexit) # Add test to CTest add_test(NAME test_distributed COMMAND test_distributed) +add_test(NAME test_distributed_atexit COMMAND test_distributed_atexit) diff --git a/tests/ttnn/distributed/test_distributed_reshape.cpp b/tests/ttnn/distributed/test_distributed_reshape.cpp new file mode 100644 index 000000000000..0c5ddb11f0b8 --- /dev/null +++ b/tests/ttnn/distributed/test_distributed_reshape.cpp @@ -0,0 +1,280 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include +#include +#include +#include +#include "tests/tt_metal/test_utils/env_vars.hpp" + +namespace ttnn::distributed::test { + +// Helper function to check test environment +void check_test_environment() { + auto slow_dispatch = getenv("TT_METAL_SLOW_DISPATCH_MODE"); + const auto arch = tt::get_arch_from_string(tt::test_utils::get_umd_arch_name()); + const size_t num_devices = tt::tt_metal::GetNumAvailableDevices(); + if (slow_dispatch) { + GTEST_SKIP() << "Skipping Multi-Device test suite, since it can only be run in Fast Dispatch Mode."; + } + if (num_devices < 8 or arch != tt::ARCH::WORMHOLE_B0) { + GTEST_SKIP() << "Skipping T3K Multi-Device test suite on non T3K machine."; + } +} + +std::vector get_physical_device_ids(const MeshDevice& mesh) { + std::vector device_ids; + for (auto* device : mesh.get_devices(ttnn::distributed::MeshType::RowMajor)) { + device_ids.push_back(device->id()); + } + return device_ids; +} + +static constexpr std::array kMeshShapes{ + {{1, 1}, {1, 2}, {1, 3}, {1, 4}, {1, 5}, {1, 6}, {1, 7}, {1, 8}, {2, 1}, {2, 2}, {2, 3}, {2, 4}, + {3, 1}, {3, 2}, {4, 1}, {4, 2}, {8, 1}, {7, 1}, {6, 1}, {5, 1}, {4, 1}, {3, 1}, {2, 1}, {1, 1}}}; + +class MeshConfigurationTest : public ::testing::TestWithParam { +protected: + void SetUp() override { check_test_environment(); } +}; + +TEST_P(MeshConfigurationTest, TestMeshConfigurations) { + const auto& shape = GetParam(); + auto mesh = ttnn::distributed::open_mesh_device( + {shape.num_rows, shape.num_cols}, + DEFAULT_L1_SMALL_SIZE, + DEFAULT_TRACE_REGION_SIZE, + 1, + tt::tt_metal::DispatchCoreType::WORKER); + EXPECT_EQ(mesh->num_rows(), shape.num_rows); + EXPECT_EQ(mesh->num_cols(), shape.num_cols); + ttnn::distributed::close_mesh_device(mesh); +} + +// Test all possible mesh configurations on T3000 +INSTANTIATE_TEST_SUITE_P(MeshShapes, MeshConfigurationTest, ::testing::ValuesIn(kMeshShapes)); + +class MeshReshapeTest : public ::testing::TestWithParam> { +protected: + void SetUp() override { check_test_environment(); } +}; + +TEST_P(MeshReshapeTest, TestReshapeBetweenConfigurations) { + const auto& [old_shape, new_shape] = GetParam(); + + if ((old_shape.num_rows * old_shape.num_cols) != (new_shape.num_rows * new_shape.num_cols)) { + GTEST_SKIP() << "Device counts don't match; we test this in InvalidReshapeDimensions"; + } + if (old_shape.num_rows == 1 or old_shape.num_cols == 1) { + GTEST_SKIP() << "Old shape is 1xN or Nx1; we test this in From1x4To2x2Invalid"; + } + + auto mesh = ttnn::distributed::open_mesh_device( + {old_shape.num_rows, old_shape.num_cols}, + DEFAULT_L1_SMALL_SIZE, + DEFAULT_TRACE_REGION_SIZE, + 1, + tt::tt_metal::DispatchCoreType::WORKER); + + EXPECT_EQ(mesh->num_rows(), old_shape.num_rows); + EXPECT_EQ(mesh->num_cols(), old_shape.num_cols); + + auto original_order = get_physical_device_ids(*mesh); + + // Attempt reshape + mesh->reshape({new_shape.num_rows, new_shape.num_cols}); + + // Verify new shape + EXPECT_EQ(mesh->num_rows(), new_shape.num_rows); + EXPECT_EQ(mesh->num_cols(), new_shape.num_cols); + + // Verify device ordering is preserved + EXPECT_EQ(get_physical_device_ids(*mesh), original_order); +} + +// Generate all possible combinations of shapes from kMeshShapes +INSTANTIATE_TEST_SUITE_P( + ReshapeConfigurations, + MeshReshapeTest, + ::testing::Combine(::testing::ValuesIn(kMeshShapes), ::testing::ValuesIn(kMeshShapes))); + +// Base class for non-parameterized tests +class T3000ReshapeTest : public ::testing::Test { +protected: + void SetUp() override { check_test_environment(); } +}; + +TEST_F(T3000ReshapeTest, InvalidReshapeDimensions) { + auto mesh = ttnn::distributed::open_mesh_device( + {1, 8}, DEFAULT_L1_SMALL_SIZE, DEFAULT_TRACE_REGION_SIZE, 1, tt::tt_metal::DispatchCoreType::WORKER); + + // Test reshaping to dimensions that don't match total device count + EXPECT_THROW(mesh->reshape({3, 3}), std::runtime_error); // 9 devices != 8 + EXPECT_THROW(mesh->reshape({1, 9}), std::runtime_error); // 9 devices != 8 + + // Verify original shape is preserved after failed reshapes + EXPECT_EQ(mesh->num_rows(), 1); + EXPECT_EQ(mesh->num_cols(), 8); +} + +TEST_F(T3000ReshapeTest, From1x8To2x4) { + auto mesh = ttnn::distributed::open_mesh_device( + {1, 8}, DEFAULT_L1_SMALL_SIZE, DEFAULT_TRACE_REGION_SIZE, 1, tt::tt_metal::DispatchCoreType::WORKER); + + EXPECT_EQ(mesh->num_rows(), 1); + EXPECT_EQ(mesh->num_cols(), 8); + auto original_order = get_physical_device_ids(*mesh); + + mesh->reshape({2, 4}); + EXPECT_EQ(mesh->num_rows(), 2); + EXPECT_EQ(mesh->num_cols(), 4); + auto new_order = get_physical_device_ids(*mesh); + EXPECT_EQ(original_order, new_order); +} + +TEST_F(T3000ReshapeTest, OnRingTopology) { + auto mesh = ttnn::distributed::open_mesh_device( + {1, 8}, + DEFAULT_L1_SMALL_SIZE, + DEFAULT_TRACE_REGION_SIZE, + 1, + tt::tt_metal::DispatchCoreType::WORKER, + ttnn::distributed::MeshType::Ring); + + EXPECT_EQ(mesh->num_rows(), 1); + EXPECT_EQ(mesh->num_cols(), 8); + auto original_order = get_physical_device_ids(*mesh); + + mesh->reshape({2, 4}); + + EXPECT_EQ(mesh->num_rows(), 2); + EXPECT_EQ(mesh->num_cols(), 4); + auto new_order = get_physical_device_ids(*mesh); + EXPECT_EQ(original_order, new_order); +} + +TEST_F(T3000ReshapeTest, InvalidTotalDeviceCount) { + auto mesh = ttnn::distributed::open_mesh_device( + {1, 8}, DEFAULT_L1_SMALL_SIZE, DEFAULT_TRACE_REGION_SIZE, 1, tt::tt_metal::DispatchCoreType::WORKER); + + // Test reshaping to dimensions that don't match total device count + EXPECT_THROW(mesh->reshape({3, 3}), std::runtime_error); // 9 devices != 8 + EXPECT_THROW(mesh->reshape({1, 9}), std::runtime_error); // 9 devices != 8 + + // Verify original shape is preserved after failed reshapes + EXPECT_EQ(mesh->num_rows(), 1); + EXPECT_EQ(mesh->num_cols(), 8); +} + +TEST_F(T3000ReshapeTest, MultipleReshapes) { + auto mesh = ttnn::distributed::open_mesh_device( + {1, 8}, DEFAULT_L1_SMALL_SIZE, DEFAULT_TRACE_REGION_SIZE, 1, tt::tt_metal::DispatchCoreType::WORKER); + + auto original_order = get_physical_device_ids(*mesh); + + // Test multiple reshapes + mesh->reshape({2, 4}); // 1x8 -> 2x4 + auto order1 = get_physical_device_ids(*mesh); + EXPECT_EQ(order1, original_order); + + mesh->reshape({4, 2}); // 2x4 -> 4x2 + auto order2 = get_physical_device_ids(*mesh); + EXPECT_EQ(order2, original_order); + + mesh->reshape({1, 8}); // 4x2 -> 1x8 (back to original) + auto final_order = get_physical_device_ids(*mesh); + EXPECT_EQ(final_order, original_order); +} + +TEST_F(T3000ReshapeTest, RingPreservation) { + auto mesh = ttnn::distributed::open_mesh_device( + {1, 8}, DEFAULT_L1_SMALL_SIZE, DEFAULT_TRACE_REGION_SIZE, 1, tt::tt_metal::DispatchCoreType::WORKER); + + // Store original device positions + std::vector original_layout; + for (size_t i = 0; i < mesh->num_rows(); ++i) { + for (size_t j = 0; j < mesh->num_cols(); ++j) { + original_layout.push_back(mesh->get_device(i, j)->id()); + } + } + + mesh->reshape({2, 4}); + + // Verify devices are still connected in a Ring topology + std::vector new_layout; + for (size_t i = 0; i < mesh->num_rows(); ++i) { + for (size_t j = 0; j < mesh->num_cols(); ++j) { + new_layout.push_back(mesh->get_device(i, j)->id()); + } + } + EXPECT_EQ(new_layout, original_layout); +} + +TEST_F(T3000ReshapeTest, From1x4To2x2Invalid) { + auto mesh = ttnn::distributed::open_mesh_device( + {1, 4}, DEFAULT_L1_SMALL_SIZE, DEFAULT_TRACE_REGION_SIZE, 1, tt::tt_metal::DispatchCoreType::WORKER); + + // This is an invalid reshape because the 1x4 mesh does not fully cover the 2x2 mesh + EXPECT_THROW(mesh->reshape({2, 2}), std::runtime_error); +} + +TEST_F(T3000ReshapeTest, From1x4To2x2Valid) { + auto& system_mesh = tt::tt_metal::distributed::SystemMesh::instance(); + + // Fetch the device ids for a physically connected 2x2 mesh. + auto physical_device_ids = system_mesh.get_mapped_physical_device_ids( + MeshDeviceConfig(MeshShape{2, 2}, ttnn::distributed::MeshType::Line)); + + // Supply the physical device ids to the mesh constructor that we know we know is 2x2 physically connected. + // We will create a 1x4 mesh and then reshape it to 2x2. + auto mesh = ttnn::distributed::open_mesh_device( + {1, 4}, + DEFAULT_L1_SMALL_SIZE, + DEFAULT_TRACE_REGION_SIZE, + 1, + tt::tt_metal::DispatchCoreType::WORKER, + ttnn::distributed::MeshType::Line, + MeshOffset{0, 0}, + physical_device_ids); + + mesh->reshape({2, 2}); + EXPECT_EQ(mesh->num_rows(), 2); + EXPECT_EQ(mesh->num_cols(), 2); + auto new_layout = get_physical_device_ids(*mesh); + for (auto physical_device_id : physical_device_ids) { + EXPECT_TRUE(std::find(new_layout.begin(), new_layout.end(), physical_device_id) != new_layout.end()); + } +} + +TEST_F(T3000ReshapeTest, From2x2To1x4) { + auto mesh = ttnn::distributed::open_mesh_device( + {2, 2}, DEFAULT_L1_SMALL_SIZE, DEFAULT_TRACE_REGION_SIZE, 1, tt::tt_metal::DispatchCoreType::WORKER); + + std::vector original_layout; + for (size_t i = 0; i < mesh->num_rows(); ++i) { + for (size_t j = 0; j < mesh->num_cols(); ++j) { + auto id = mesh->get_device(i, j)->id(); + original_layout.push_back(id); + } + } + + mesh->reshape({1, 4}); + EXPECT_EQ(mesh->num_rows(), 1); + EXPECT_EQ(mesh->num_cols(), 4); + + std::vector new_layout; + for (size_t i = 0; i < mesh->num_rows(); ++i) { + for (size_t j = 0; j < mesh->num_cols(); ++j) { + auto id = mesh->get_device(i, j)->id(); + new_layout.push_back(id); + } + } + + EXPECT_EQ(new_layout, original_layout); +} + +} // namespace ttnn::distributed::test diff --git a/tt_metal/distributed/mesh_device.cpp b/tt_metal/distributed/mesh_device.cpp index fb709c39901e..073c92b18314 100644 --- a/tt_metal/distributed/mesh_device.cpp +++ b/tt_metal/distributed/mesh_device.cpp @@ -26,7 +26,8 @@ static std::string get_config_path(const std::string& filename) { return root_path + "/tt_metal/distributed/mesh_configurations/" + filename; } -static std::unordered_map load_translation_map(const std::string& filename, const std::string& key) { +static std::unordered_map load_translation_map( + const std::string& filename, const std::string& key) { std::ifstream file(filename); if (!file.is_open()) { throw std::runtime_error("Unable to open file: " + filename); @@ -72,6 +73,7 @@ class SystemMesh::Impl { MeshShape logical_mesh_shape_; std::unordered_map logical_to_physical_coordinates_; + std::unordered_map logical_to_device_id_; std::unordered_map physical_coordinate_to_device_id_; std::unordered_map physical_device_id_to_coordinate_; @@ -91,6 +93,8 @@ class SystemMesh::Impl { static MeshShape get_system_mesh_shape(size_t system_num_devices); static std::unordered_map get_system_mesh_translation_map( size_t system_num_devices); + + chip_id_t get_physical_device_id(size_t logical_row_idx, size_t logical_col_idx) const; }; // Implementation of private static methods @@ -152,6 +156,9 @@ void SystemMesh::Impl::initialize() { auto num_devices = physical_coordinate_to_device_id_.size(); logical_mesh_shape_ = get_system_mesh_shape(num_devices); logical_to_physical_coordinates_ = get_system_mesh_translation_map(num_devices); + for (const auto& [logical_coordinate, physical_coordinate] : logical_to_physical_coordinates_) { + logical_to_device_id_.emplace(logical_coordinate, physical_coordinate_to_device_id_.at(physical_coordinate)); + } } const MeshShape& SystemMesh::Impl::get_shape() const { return logical_mesh_shape_; } @@ -160,50 +167,125 @@ size_t SystemMesh::Impl::get_num_devices() const { return num_rows * num_cols; } +chip_id_t SystemMesh::Impl::get_physical_device_id(size_t logical_row_idx, size_t logical_col_idx) const { + TT_FATAL( + logical_row_idx < logical_mesh_shape_.num_rows, + "Row index out of bounds: {} >= {}", + logical_row_idx, + logical_mesh_shape_.num_rows); + TT_FATAL( + logical_col_idx < logical_mesh_shape_.num_cols, + "Column index out of bounds: {} >= {}", + logical_col_idx, + logical_mesh_shape_.num_cols); + auto logical_coordinate = Coordinate{logical_row_idx, logical_col_idx}; + return logical_to_device_id_.at(logical_coordinate); +} + std::vector SystemMesh::Impl::get_mapped_physical_device_ids(const MeshDeviceConfig& config) const { std::vector physical_device_ids; auto [system_mesh_rows, system_mesh_cols] = this->get_shape(); - auto [requested_rows, requested_cols] = config.mesh_shape; + auto [requested_num_rows, requested_num_cols] = config.mesh_shape; auto [row_offset, col_offset] = config.offset; - if (requested_rows == 1) { + // First check if total size fits + TT_FATAL( + requested_num_rows * requested_num_cols <= system_mesh_rows * system_mesh_cols, + "Requested submesh is too big: {}x{}", + requested_num_rows, + requested_num_cols); + + bool is_single_row_or_column = requested_num_rows == 1 or requested_num_cols == 1; + if (is_single_row_or_column) { TT_FATAL(row_offset == 0 and col_offset == 0, "Row and column offsets unsupported for single row mesh"); + auto line_length = requested_num_rows * requested_num_cols; auto line_coords = MeshDeviceView::get_line_coordinates( - requested_cols, Coordinate{row_offset, col_offset}, system_mesh_rows, system_mesh_cols); + line_length, Coordinate{row_offset, col_offset}, system_mesh_rows, system_mesh_cols); for (const auto& logical_coordinate : line_coords) { - auto physical_coordinate = logical_to_physical_coordinates_.at(logical_coordinate); - auto physical_device_id = physical_coordinate_to_device_id_.at(physical_coordinate); + auto physical_device_id = logical_to_device_id_.at(logical_coordinate); physical_device_ids.push_back(physical_device_id); log_debug( LogMetal, - "Logical coordinate: {}, Physical coordinate: {}, Physical device ID: {}", + "Logical coordinate: {}, Physical device ID: {}", logical_coordinate, - physical_coordinate, physical_device_id); } + return physical_device_ids; + } + bool requires_rotation = requested_num_rows > system_mesh_rows || requested_num_cols > system_mesh_cols; + + + if (requires_rotation) { + bool can_rotate = requested_num_rows <= system_mesh_cols && requested_num_cols <= system_mesh_rows; + if (can_rotate) { + // Rotate requested shape; row_offset and col_offset refer to original orientation + std::swap(requested_num_rows, requested_num_cols); + } else { + TT_THROW("User has requested a submesh that is too big and is not rotatable: {}x{} and SystemMesh is {}x{}.", + requested_num_rows, requested_num_cols, + system_mesh_rows, system_mesh_cols); + } } else { - for (int row = 0; row < requested_rows; row++) { - for (int col = 0; col < requested_cols; col++) { - auto logical_device_id = (row + row_offset) * system_mesh_cols + (col + col_offset); - auto logical_coordinate = - Coordinate{logical_device_id / system_mesh_cols, logical_device_id % system_mesh_cols}; - auto physical_coordinate = logical_to_physical_coordinates_.at(logical_coordinate); - auto physical_device_id = physical_coordinate_to_device_id_.at(physical_coordinate); - physical_device_ids.push_back(physical_device_id); - - log_debug( - LogMetal, - "Logical device ID: {}, Logical coordinate: {}, Physical coordinate: {}, Physical device ID: {}", - logical_device_id, - logical_coordinate, - physical_coordinate, - physical_device_id); + // If no rotation, check dimensions directly + TT_FATAL( + requested_num_rows <= system_mesh_rows && requested_num_cols <= system_mesh_cols, + "Requested submesh is too big: {}x{} and SystemMesh is {}x{}", + requested_num_rows, requested_num_cols, + system_mesh_rows, system_mesh_cols); + } + + size_t original_rows = system_mesh_rows; + size_t original_cols = system_mesh_cols; + + // Check that offsets fit in the original mesh + TT_FATAL( + row_offset + requested_num_rows <= original_rows, + "Row offset + requested rows exceeds mesh size: {} + {} > {}", + row_offset, requested_num_rows, original_rows); + TT_FATAL( + col_offset + requested_num_cols <= original_cols, + "Column offset + requested columns exceeds mesh size: {} + {} > {}", + col_offset, requested_num_cols, original_cols); + + // Map each submesh coordinate to the original logical coordinates + for (size_t row = 0; row < requested_num_rows; row++) { + for (size_t col = 0; col < requested_num_cols; col++) { + Coordinate logical_coordinate; + if (requires_rotation) { + // After swapping requested_num_rows and requested_num_cols, + // (row, col) now iterate over the rotated shape. + size_t old_row = row_offset + row; // top row + size_t old_col = col_offset + col; // increasing columns horizontally + logical_coordinate = Coordinate{old_row, old_col}; + } else { + logical_coordinate = Coordinate{row + row_offset, col + col_offset}; } + + TT_FATAL( + logical_coordinate.row < logical_mesh_shape_.num_rows, + "Row coordinate out of bounds: {} >= {}", + logical_coordinate.row, + logical_mesh_shape_.num_rows); + TT_FATAL( + logical_coordinate.col < logical_mesh_shape_.num_cols, + "Column coordinate out of bounds: {} >= {}", + logical_coordinate.col, + logical_mesh_shape_.num_cols); + + auto physical_device_id = logical_to_device_id_.at(logical_coordinate); + physical_device_ids.push_back(physical_device_id); + + log_debug( + LogMetal, + "Logical coordinate: {}, Physical device ID: {}", + logical_coordinate, + physical_device_id); } } return physical_device_ids; } + void SystemMesh::Impl::register_mesh_device( const std::shared_ptr& mesh_device, const std::vector& devices) { std::vector physical_device_ids; @@ -226,14 +308,9 @@ std::vector SystemMesh::Impl::request_available_devices(const MeshDev requested_num_cols, row_offset, col_offset); - TT_FATAL(requested_num_rows <= max_num_rows, "Requested too many rows: {} > {}", requested_num_rows, max_num_rows); - TT_FATAL( - requested_num_rows * requested_num_cols <= max_num_rows * max_num_cols, - "Requested submesh is too big: {}x{}", - requested_num_rows, - requested_num_cols); - return config.physical_device_ids.empty() ? this->get_mapped_physical_device_ids(config) : config.physical_device_ids; + return config.physical_device_ids.empty() ? this->get_mapped_physical_device_ids(config) + : config.physical_device_ids; } SystemMesh::SystemMesh() : pimpl_(std::make_unique()) {} @@ -247,6 +324,10 @@ SystemMesh& SystemMesh::instance() { return instance; } +chip_id_t SystemMesh::get_physical_device_id(size_t logical_row_idx, size_t logical_col_idx) const { + return pimpl_->get_physical_device_id(logical_row_idx, logical_col_idx); +} + const MeshShape& SystemMesh::get_shape() const { return pimpl_->get_shape(); } size_t SystemMesh::get_num_devices() const { return pimpl_->get_num_devices(); } @@ -269,9 +350,7 @@ static MeshDeviceID generate_unique_mesh_id() { return next_id++; } -Device* MeshDevice::reference_device() const { - return this->devices.at(0); -} +Device* MeshDevice::reference_device() const { return this->devices.at(0); } MeshDevice::MeshDevice(const MeshShape& mesh_device_shape, MeshType type, std::weak_ptr parent_mesh) : mesh_device_shape(mesh_device_shape), @@ -319,8 +398,8 @@ std::shared_ptr MeshDevice::create_submesh( auto submesh = std::make_shared(submesh_shape, type, shared_from_this()); auto start_coordinate = Coordinate{offset.row, offset.col}; auto end_coordinate = Coordinate{offset.row + submesh_shape.num_rows - 1, offset.col + submesh_shape.num_cols - 1}; - submesh->primary_view = std::make_shared(*this, start_coordinate, end_coordinate); - submesh->devices = submesh->primary_view->get_devices(); + submesh->view = std::make_unique(*this, start_coordinate, end_coordinate); + submesh->devices = submesh->view->get_devices(); SystemMesh::instance().register_mesh_device(submesh, submesh->devices); this->submeshes.push_back(submesh); log_trace( @@ -353,15 +432,6 @@ void MeshDevice::initialize( size_t num_command_queues, const DispatchCoreConfig& dispatch_core_config, const MeshDeviceConfig& config) { - auto [num_rows, num_cols] = this->shape(); - auto num_requested_devices = num_rows * num_cols; - auto num_available_devices = tt::tt_metal::GetNumAvailableDevices(); - TT_FATAL( - num_requested_devices <= num_available_devices, - "User has requested more devices than available: {} requested, {} available", - num_requested_devices, - num_available_devices); - auto& system_mesh = SystemMesh::instance(); auto physical_device_ids = system_mesh.request_available_devices(config); @@ -371,7 +441,7 @@ void MeshDevice::initialize( for (auto physical_device_id : physical_device_ids) { this->devices.push_back(this->opened_devices.at(physical_device_id)); } - this->primary_view = std::make_shared(*this); + this->view = std::make_unique(*this); system_mesh.register_mesh_device(shared_from_this(), this->devices); } @@ -391,8 +461,11 @@ Device* MeshDevice::get_device(chip_id_t physical_device_id) const { TT_THROW("Physical Device ID: {} not found in assigned devices", physical_device_id); } -std::vector MeshDevice::get_devices() const { return this->primary_view->get_devices(this->type); } +std::vector MeshDevice::get_devices(const std::optional& requested_type) const { + return this->view->get_devices(requested_type.value_or(this->type)); +} +// TODO: Remove this function once we have a proper view interface Device* MeshDevice::get_device(size_t row_idx, size_t col_idx) const { return this->get_device_index(row_idx * num_cols() + col_idx); } @@ -407,7 +480,9 @@ const DeviceIds MeshDevice::get_device_ids() const { size_t MeshDevice::num_devices() const { return this->devices.size(); } -CoreCoord MeshDevice::compute_with_storage_grid_size() const { return this->reference_device()->compute_with_storage_grid_size(); } +CoreCoord MeshDevice::compute_with_storage_grid_size() const { + return this->reference_device()->compute_with_storage_grid_size(); +} CoreCoord MeshDevice::dram_grid_size() const { return this->reference_device()->dram_grid_size(); } @@ -419,6 +494,41 @@ size_t MeshDevice::num_cols() const { return this->mesh_device_shape.num_cols; } MeshShape MeshDevice::shape() const { return this->mesh_device_shape; } +void MeshDevice::reshape(const MeshShape& new_shape) { + TT_FATAL( + new_shape.num_rows * new_shape.num_cols == this->num_devices(), + "New shape must have the same number of devices as current shape"); + + std::unordered_map physical_device_id_to_linearized_index; + for (size_t i = 0; i < this->num_devices(); i++) { + physical_device_id_to_linearized_index[this->devices[i]->id()] = i; + } + + // From an MxN mesh, we can always reduce rank to a 1xM*N Line mesh. + // However, going from a Line mesh to an MxN mesh is not always possible. + if (new_shape.num_rows != 1 and new_shape.num_cols != 1) { + auto new_physical_device_ids = + SystemMesh::instance().request_available_devices(MeshDeviceConfig{new_shape}); + + for (size_t i = 0; i < new_physical_device_ids.size(); i++) { + if (physical_device_id_to_linearized_index.find(new_physical_device_ids[i]) == physical_device_id_to_linearized_index.end()) { + TT_THROW( + "User has requested a reshape of the MeshDevice to shape: {}x{}, but it is not possible to form a " + "physically connected mesh of {}x{} grid with the opened devices from the original shape: {}x{}.", + new_shape.num_rows, + new_shape.num_cols, + new_shape.num_rows, + new_shape.num_cols, + this->num_rows(), + this->num_cols()); + } + } + } + + this->mesh_device_shape = new_shape; + this->view = std::make_unique(*this); +} + void MeshDevice::close_devices() { for (const auto& submesh : this->submeshes) { submesh->close_devices(); @@ -430,16 +540,17 @@ void MeshDevice::close_devices() { this->submeshes.clear(); this->parent_mesh.reset(); this->devices.clear(); - this->primary_view.reset(); + this->view.reset(); } std::string MeshDevice::to_string() const { return fmt::format("MeshDevice({}x{} grid, {} devices)", this->num_rows(), this->num_cols(), this->num_devices()); } -std::shared_ptr MeshDevice::get_view() const { return this->primary_view; } - -std::shared_ptr MeshDevice::get_view() { return this->primary_view; } +const MeshDeviceView& MeshDevice::get_view() const { + TT_FATAL(view, "MeshDeviceView is not initialized"); + return *view; +} MeshDeviceID MeshDevice::get_mesh_id() const { return this->mesh_id; } @@ -475,7 +586,8 @@ size_t MeshDevice::num_program_cache_entries() const { return total_entries; } -MeshSubDeviceManagerId MeshDevice::create_sub_device_manager(tt::stl::Span sub_devices, DeviceAddr local_l1_size) { +MeshSubDeviceManagerId MeshDevice::create_sub_device_manager( + tt::stl::Span sub_devices, DeviceAddr local_l1_size) { MeshSubDeviceManagerId mesh_sub_device_manager_id(*this); for (uint32_t i = 0; i < this->num_devices(); i++) { auto* device = this->devices[i]; @@ -511,25 +623,21 @@ void MeshDevice::load_sub_device_manager(MeshSubDeviceManagerId mesh_sub_device_ for (uint32_t i = 0; i < this->num_devices(); i++) { auto* device = this->devices[i]; auto sub_device_manager_id = mesh_sub_device_manager_id.sub_device_manager_ids[i]; - device->push_work([device, sub_device_manager_id]() { - device->load_sub_device_manager(sub_device_manager_id); - }); + device->push_work( + [device, sub_device_manager_id]() { device->load_sub_device_manager(sub_device_manager_id); }); } } void MeshDevice::clear_loaded_sub_device_manager() { for (auto* device : this->devices) { - device->push_work([device]() { - device->clear_loaded_sub_device_manager(); - }); + device->push_work([device]() { device->clear_loaded_sub_device_manager(); }); } } void MeshDevice::remove_sub_device_manager(MeshSubDeviceManagerId mesh_sub_device_manager_id) { for (uint32_t i = 0; i < this->num_devices(); i++) { auto* device = this->devices[i]; auto sub_device_manager_id = mesh_sub_device_manager_id.sub_device_manager_ids[i]; - device->push_work([device, sub_device_manager_id]() { - device->remove_sub_device_manager(sub_device_manager_id); - }); + device->push_work( + [device, sub_device_manager_id]() { device->remove_sub_device_manager(sub_device_manager_id); }); } } @@ -541,7 +649,8 @@ int MeshDevice::num_dram_channels() const { return this->reference_device()->num_dram_channels() * this->num_devices(); } -allocator::Statistics MeshDevice::get_memory_allocation_statistics(const BufferType &buffer_type, SubDeviceId sub_device_id) const { +allocator::Statistics MeshDevice::get_memory_allocation_statistics( + const BufferType& buffer_type, SubDeviceId sub_device_id) const { // With current implementation, we assume that all devices have the same memory allocation statistics. // This will be made more explicit in the future to have lock-step allocation across devices. // Right now, we just return the statistics of the first device. diff --git a/tt_metal/distributed/mesh_device.hpp b/tt_metal/distributed/mesh_device.hpp index 01a63d2e2865..8bb238b35c9c 100644 --- a/tt_metal/distributed/mesh_device.hpp +++ b/tt_metal/distributed/mesh_device.hpp @@ -48,6 +48,7 @@ struct MeshDeviceConfig { // SystemMesh creates a virtualization over the physical devices in the system. // It creates a logical 2D-mesh of devices and manages the mapping between logical and physical device coordinates. +// It serves as a query interface between the logical 2D coordinates to physical device IDs. class SystemMesh { private: friend class MeshDevice; @@ -70,6 +71,9 @@ class SystemMesh { const MeshShape& get_shape() const; size_t get_num_devices() const; + // Gets the physical device ID for a given logical row and column index + chip_id_t get_physical_device_id(size_t logical_row_idx, size_t logical_col_idx) const; + // Get the physical device IDs mapped to a MeshDevice std::vector get_mapped_physical_device_ids(const MeshDeviceConfig &config) const; }; @@ -79,7 +83,7 @@ class MeshDevice : public std::enable_shared_from_this { MeshDeviceID mesh_id; MeshShape mesh_device_shape; MeshType type; - std::shared_ptr primary_view; + std::unique_ptr view; std::map opened_devices; std::vector devices; std::vector> submeshes; // Parent owns submeshes and responsible fortheir destruction @@ -105,7 +109,10 @@ class MeshDevice : public std::enable_shared_from_this { MeshDevice(MeshDevice&&) = delete; MeshDevice& operator=(MeshDevice&&) = delete; - std::vector get_devices() const; + // A MeshDevice is a collection of devices arranged in a 2D grid. + // The type parameter allows the caller to specify how to linearize the devices in the mesh. + // If type is not provided, the default behavior is to return the devices based on the MeshType of the MeshDevice. + std::vector get_devices(const std::optional& type = std::nullopt) const; Device* get_device_index(size_t logical_device_id) const; Device* get_device(chip_id_t physical_device_id) const; Device* get_device(size_t row_idx, size_t col_idx) const; @@ -117,9 +124,25 @@ class MeshDevice : public std::enable_shared_from_this { size_t num_cols() const; MeshShape shape() const; + // Reshapes the logical mesh and re-maps the physical devices to the new logical coordinates. + // Reshaping Rules: + // 1. The old_shape volume must equal the new_shape volume (i.e. number of devices must remain constant) + // 2. Line-to-Line Reshaping (when either dimension is 1): + // - Always possible between 1xN and Nx1 shapes (e.g.: 1x8 <-> 8x1 + // 3. Grid-to-Grid Reshaping: + // - Only possible if the devices can form a connected physical mesh in the new shape + // - Must maintain physical connectivity between adjacent devices + // 4. Line-to-Grid Reshaping: + // - Only possible if the physical devices can form a connected physical mesh in the new shape + // - Example: 1x8 -> 2x4 is possible only if physical mesh permits a 2x4 configuration + // + // @throws std::runtime_error if any of the following constraints are not met: + // 1. The old_shape volume must equal the new_shape volume (i.e. number of devices must remain constant) + // 2. For Grid-to-Grid or Line-to-Grid reshaping: physical connectivity must be possible with current devices + void reshape(const MeshShape& new_shape); + void close_devices(); - std::shared_ptr get_view() const; - std::shared_ptr get_view(); + const MeshDeviceView& get_view() const; std::string to_string() const; MeshDeviceID get_mesh_id() const; diff --git a/tt_metal/distributed/mesh_device_view.cpp b/tt_metal/distributed/mesh_device_view.cpp index f9e115f0437f..1c71c877823a 100644 --- a/tt_metal/distributed/mesh_device_view.cpp +++ b/tt_metal/distributed/mesh_device_view.cpp @@ -12,7 +12,7 @@ namespace tt::tt_metal::distributed { static std::vector get_devices_from_coordinates( - MeshDeviceView& mesh, const std::vector& coords) { + const MeshDeviceView& mesh, const std::vector& coords) { std::vector devices; for (const auto& coord : coords) { if (auto device = mesh.get_device(coord.row, coord.col)) { @@ -52,11 +52,7 @@ MeshDeviceView::MeshDeviceView(std::vector devices, const Coordi initialize_from_devices(devices_, std::move(mapper)); } -MeshDeviceView::device_pointer MeshDeviceView::get_device(size_t row, size_t col) { - return const_cast(std::as_const(*this).get_device(row, col)); -} - -MeshDeviceView::const_device_pointer MeshDeviceView::get_device(size_t row, size_t col) const { +MeshDeviceView::device_pointer MeshDeviceView::get_device(size_t row, size_t col) const { for (const auto& device : devices_) { auto it = device_coordinates_.find(device->id()); if (it != device_coordinates_.end() && it->second.row == row && it->second.col == col) { @@ -66,7 +62,7 @@ MeshDeviceView::const_device_pointer MeshDeviceView::get_device(size_t row, size return nullptr; } -MeshDeviceView::DeviceView MeshDeviceView::get_devices(const Coordinate& start, const Coordinate& end) { +MeshDeviceView::DeviceView MeshDeviceView::get_devices(const Coordinate& start, const Coordinate& end) const { if (start.row > end.row || start.col > end.col) { log_fatal("Invalid coordinates: start {} must be less than or equal to end {}", start, end); } @@ -82,8 +78,8 @@ MeshDeviceView::DeviceView MeshDeviceView::get_devices(const Coordinate& start, return devices_in_region; } -MeshDeviceView::DeviceView MeshDeviceView::get_devices(const MeshShape& shape) { - return get_devices({0, 0}, {shape.num_rows - 1, shape.num_cols - 1}); +MeshDeviceView::DeviceView MeshDeviceView::get_devices(const MeshShape& submesh_shape) const { + return get_devices({0, 0}, {submesh_shape.num_rows - 1, submesh_shape.num_cols - 1}); } std::vector MeshDeviceView::get_devices_on_row(size_t row) const { @@ -214,7 +210,7 @@ std::vector MeshDeviceView::get_line_coordinates( } std::vector MeshDeviceView::get_ring_coordinates( - const MeshShape& ring_shape, const Coordinate& offset, size_t num_rows, size_t num_cols) { + const MeshShape& ring_shape, const Coordinate& offset, size_t num_rows, size_t num_cols) const { auto [start_row, start_col] = offset; auto [ring_rows, ring_cols] = ring_shape; auto end_row = start_row + ring_rows - 1; @@ -258,18 +254,18 @@ void MeshDeviceView::validate_coordinates() const { } } -std::vector MeshDeviceView::get_line_devices() { +std::vector MeshDeviceView::get_line_devices() const { auto boundary_coords = get_line_coordinates(this->num_rows() * this->num_cols(), this->top_left_, this->num_rows(), this->num_cols()); return get_devices_from_coordinates(*this, boundary_coords); } -std::vector MeshDeviceView::get_ring_devices() { +std::vector MeshDeviceView::get_ring_devices() const { auto boundary_coords = get_ring_coordinates(shape(), this->top_left_, this->num_rows(), this->num_cols()); return get_devices_from_coordinates(*this, boundary_coords); } -MeshDeviceView::DeviceView MeshDeviceView::get_devices(MeshType type) { +MeshDeviceView::DeviceView MeshDeviceView::get_devices(MeshType type) const { switch (type) { case MeshType::RowMajor: return this->devices_; case MeshType::Ring: return this->get_ring_devices(); diff --git a/tt_metal/distributed/mesh_device_view.hpp b/tt_metal/distributed/mesh_device_view.hpp index 31af7aba3764..0524814b7971 100644 --- a/tt_metal/distributed/mesh_device_view.hpp +++ b/tt_metal/distributed/mesh_device_view.hpp @@ -75,14 +75,13 @@ class MeshDeviceView { MeshDeviceView(const MeshDevice& mesh, Coordinate top_left, Coordinate bottom_right); MeshDeviceView(std::vector devices, const CoordinateMapper& mapper); - [[nodiscard]] device_pointer get_device(size_t row, size_t col); - [[nodiscard]] const_device_pointer get_device(size_t row, size_t col) const; + [[nodiscard]] device_pointer get_device(size_t row, size_t col) const; // Get devices spanning the rectangular region defined by the top-left and bottom-right coordinates // devices are returned in row-major order with start/end coordinates inclusive - [[nodiscard]] DeviceView get_devices(const Coordinate& start, const Coordinate& end); - [[nodiscard]] DeviceView get_devices(const MeshShape& shape); - [[nodiscard]] DeviceView get_devices(MeshType type = MeshType::RowMajor); + [[nodiscard]] DeviceView get_devices(const Coordinate& start, const Coordinate& end) const; + [[nodiscard]] DeviceView get_devices(const MeshShape& submesh_shape) const; + [[nodiscard]] DeviceView get_devices(MeshType type = MeshType::RowMajor) const; [[nodiscard]] DeviceView get_devices_on_row(size_t row) const; [[nodiscard]] DeviceView get_devices_on_column(size_t col) const; @@ -114,9 +113,9 @@ class MeshDeviceView { [[nodiscard]] static std::vector get_line_coordinates( size_t length, const Coordinate& offset, size_t num_rows, size_t num_cols); [[nodiscard]] std::vector get_ring_coordinates( - const MeshShape& ring_shape, const Coordinate& offset, size_t num_rows, size_t num_cols); - [[nodiscard]] std::vector get_ring_devices(); - [[nodiscard]] std::vector get_line_devices(); + const MeshShape& ring_shape, const Coordinate& offset, size_t num_rows, size_t num_cols) const; + [[nodiscard]] std::vector get_ring_devices() const; + [[nodiscard]] std::vector get_line_devices() const; private: std::vector devices_; diff --git a/ttnn/cpp/ttnn/distributed/api.cpp b/ttnn/cpp/ttnn/distributed/api.cpp index fee7fa1566c5..986f127f2b67 100644 --- a/ttnn/cpp/ttnn/distributed/api.cpp +++ b/ttnn/cpp/ttnn/distributed/api.cpp @@ -156,13 +156,13 @@ std::vector get_mapped_devices(const Tensor& tensor, MeshDevice& mesh_d } return workers; }; - if (mesh_device.get_view() != nullptr and std::holds_alternative(tensor.get_storage())) { + if (std::holds_alternative(tensor.get_storage())) { const auto& host_storage = std::get(tensor.get_storage()); return std::visit( tt::stl::overloaded{ [&](const ShardTensor2D& s) { - return mesh_device.get_view()->get_devices(MeshShape{s.shard_mesh.y, s.shard_mesh.x}); + return mesh_device.get_view().get_devices(MeshShape{s.shard_mesh.y, s.shard_mesh.x}); }, [&](const auto&) { return get_workers_for_tensor(); }}, host_storage.strategy); diff --git a/ttnn/cpp/ttnn/distributed/distributed_pybind.cpp b/ttnn/cpp/ttnn/distributed/distributed_pybind.cpp index 1e1ac97f507d..d41713d53f1d 100644 --- a/ttnn/cpp/ttnn/distributed/distributed_pybind.cpp +++ b/ttnn/cpp/ttnn/distributed/distributed_pybind.cpp @@ -100,7 +100,12 @@ void py_module(py::module& module) { "get_device", py::overload_cast(&MeshDevice::get_device, py::const_), py::return_value_policy::reference) - .def("get_devices", &MeshDevice::get_devices, py::return_value_policy::reference, R"doc( + .def( + "get_devices", + &MeshDevice::get_devices, + py::return_value_policy::reference, + py::arg("type") = py::none(), + R"doc( Get the devices in the device mesh. Returns: diff --git a/ttnn/cpp/ttnn/operations/ccl/all_gather/device/all_gather_op.cpp b/ttnn/cpp/ttnn/operations/ccl/all_gather/device/all_gather_op.cpp index e32b1232ae85..119e2d840198 100644 --- a/ttnn/cpp/ttnn/operations/ccl/all_gather/device/all_gather_op.cpp +++ b/ttnn/cpp/ttnn/operations/ccl/all_gather/device/all_gather_op.cpp @@ -300,7 +300,7 @@ Tensor all_gather( topology == ttnn::ccl::Topology::Linear, "This all_gather API with cluster_axis is currently supported only for the Linear topology"); const auto mesh_view = mesh_device.get_view(); - std::size_t num_devices = (cluster_axis == 0) ? mesh_view->num_rows() : mesh_view->num_cols(); + std::size_t num_devices = (cluster_axis == 0) ? mesh_view.num_rows() : mesh_view.num_cols(); int32_t rank = input_tensor.get_logical_shape().rank(); @@ -330,7 +330,7 @@ Tensor all_gather( const std::vector>& optional_output_tensors) mutable -> std::vector { const auto& input_device_tensor = input_tensors.at(0); - const auto coordinate = mesh_view->find_device(input_device_tensor.device()->id()); + const auto coordinate = mesh_view.find_device(input_device_tensor.device()->id()); const auto view_index = (cluster_axis == 0) ? coordinate.col : coordinate.row; const auto device_index = (cluster_axis == 0) ? coordinate.row : coordinate.col; @@ -341,7 +341,7 @@ Tensor all_gather( } else { new_coord.col = line_index % num_devices; } - return mesh_view->find_device_id(new_coord); + return mesh_view.find_device_id(new_coord); }; bool is_last_chip_in_clockwise_direction = device_index == (num_devices - 1); diff --git a/ttnn/cpp/ttnn/operations/ccl/reduce_scatter/device/reduce_scatter_op.cpp b/ttnn/cpp/ttnn/operations/ccl/reduce_scatter/device/reduce_scatter_op.cpp index 4f633e5d8bc7..a89e0407c3e5 100644 --- a/ttnn/cpp/ttnn/operations/ccl/reduce_scatter/device/reduce_scatter_op.cpp +++ b/ttnn/cpp/ttnn/operations/ccl/reduce_scatter/device/reduce_scatter_op.cpp @@ -196,7 +196,7 @@ Tensor reduce_scatter( topology == ttnn::ccl::Topology::Linear, "This all_gather API with cluster_axis is currently supported only for the Linear topology"); const auto mesh_view = mesh_device.get_view(); - std::size_t num_devices = (cluster_axis == 0) ? mesh_view->num_rows() : mesh_view->num_cols(); + std::size_t num_devices = (cluster_axis == 0) ? mesh_view.num_rows() : mesh_view.num_cols(); int16_t rank = input_tensor.get_logical_shape().rank(); @@ -227,7 +227,7 @@ Tensor reduce_scatter( const std::vector>& optional_output_tensors) mutable -> std::vector { const auto& input_device_tensor = input_tensors.at(0); - const auto coordinate = mesh_view->find_device(input_device_tensor.device()->id()); + const auto coordinate = mesh_view.find_device(input_device_tensor.device()->id()); const auto view_index = (cluster_axis == 0) ? coordinate.col : coordinate.row; const auto device_index = (cluster_axis == 0) ? coordinate.row : coordinate.col; @@ -238,7 +238,7 @@ Tensor reduce_scatter( } else { new_coord.col = line_index % num_devices; } - return mesh_view->find_device_id(new_coord); + return mesh_view.find_device_id(new_coord); }; bool is_last_chip_in_clockwise_direction = device_index == (num_devices - 1); From 6b90402910ee72ba163261f331781ed0522d4441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bojan=20Ro=C5=A1ko?= <156314064+broskoTT@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:49:29 +0100 Subject: [PATCH 33/87] Remove setup_core_to_tlb_map (#16048) ### Ticket Related to https://github.com/tenstorrent/tt-metal/issues/13948 ### Problem description This API call is unnecessary since all the needed information is already passed during "configure_tlb" calls. Related to https://github.com/tenstorrent/tt-umd/pull/403 ### What's changed - setup_core_to_tlb_map is removed, functionality should stay the same ### Checklist - [x] All post-commit tests : https://github.com/tenstorrent/tt-metal/actions/runs/12376643291 - [ ] Blackhole post-commit tests : https://github.com/tenstorrent/tt-metal/actions/runs/12376645486 - [x] (Single-card) Model perf tests : https://github.com/tenstorrent/tt-metal/actions/runs/12376647574 - [x] (Single-card) Device perf regressions : https://github.com/tenstorrent/tt-metal/actions/runs/12376649607 - [ ] (T3K) T3000 unit tests : https://github.com/tenstorrent/tt-metal/actions/runs/12376651625 - [ ] (T3K) T3000 demo tests : https://github.com/tenstorrent/tt-metal/actions/runs/12376653954 - [ ] (TG) TG unit tests : https://github.com/tenstorrent/tt-metal/actions/runs/12376656331 - [ ] (TG) TG demo tests : https://github.com/tenstorrent/tt-metal/actions/runs/12376658168 - [ ] (TGG) TGG unit tests : https://github.com/tenstorrent/tt-metal/actions/runs/12376660071 - [x] (TGG) TGG demo tests : https://github.com/tenstorrent/tt-metal/actions/runs/12376661988 --- tt_metal/llrt/tlb_config.cpp | 2 -- tt_metal/third_party/umd | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tt_metal/llrt/tlb_config.cpp b/tt_metal/llrt/tlb_config.cpp index dfb14fa88545..c099e3191f50 100644 --- a/tt_metal/llrt/tlb_config.cpp +++ b/tt_metal/llrt/tlb_config.cpp @@ -208,8 +208,6 @@ void configure_static_tlbs( device_driver.configure_tlb(mmio_device_id, dram_core, tlb_index, dram_addr, TLB_DATA::Posted); } } - - device_driver.setup_core_to_tlb_map(mmio_device_id, get_static_tlb_index); } } // namespace ll_api diff --git a/tt_metal/third_party/umd b/tt_metal/third_party/umd index cfadca1bbbab..bfbb7e969b15 160000 --- a/tt_metal/third_party/umd +++ b/tt_metal/third_party/umd @@ -1 +1 @@ -Subproject commit cfadca1bbbabf2d9cf3ab038322d7416d8059ff3 +Subproject commit bfbb7e969b15e82b3d4e8428c555e79c71520d1e From 5d0170e0a60435f88b4d2b0e86472e4df5d457c8 Mon Sep 17 00:00:00 2001 From: Artem Yerofieiev <169092593+ayerofieiev-tt@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:02:18 -0800 Subject: [PATCH 34/87] #0: Let sharded_to_interleaved handle interleaved input (#16116) ### Ticket None ### Problem description Found that op fails with wrong_optional_access error due to unchecked sharding spec. ### What's changed Check if tensor is sharded and return input tensor as is if it is already interleaved ### Checklist - [ ] [Post commit CI](https://github.com/tenstorrent/tt-metal/actions/runs/12382900338) --- .../sharded_to_interleaved/sharded_to_interleaved.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ttnn/cpp/ttnn/operations/data_movement/sharded/sharded_to_interleaved/sharded_to_interleaved.cpp b/ttnn/cpp/ttnn/operations/data_movement/sharded/sharded_to_interleaved/sharded_to_interleaved.cpp index 17b54533d7e8..ffac44cfca64 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/sharded/sharded_to_interleaved/sharded_to_interleaved.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/sharded/sharded_to_interleaved/sharded_to_interleaved.cpp @@ -17,10 +17,12 @@ ttnn::Tensor ShardedToInterleavedOperation::invoke( const MemoryConfig& memory_config, const std::optional& output_dtype, const std::optional& is_l1_aligned) { - std::vector output_tensors = {Tensor(operation::get_workers_for_op_output({input_tensor}))}; + if (!input_tensor.shard_spec().has_value()) { + return input_tensor; + } + std::vector output_tensors = {Tensor(operation::get_workers_for_op_output({input_tensor}))}; auto shard_spec = input_tensor.shard_spec().value(); - TT_FATAL(input_tensor.shard_spec().has_value(), "Error"); return operation::run( ShardedToInterleavedDeviceOperation{ .output_mem_config = memory_config, From 999327fcae40c0c84310f35a31ec3c61edec77dc Mon Sep 17 00:00:00 2001 From: Shwetank Singh <156869929+shwetankTT@users.noreply.github.com> Date: Wed, 18 Dec 2024 08:02:36 +0530 Subject: [PATCH 35/87] #0: separate validation of conv weight and bias. (#15990) ### Ticket #0 ### Problem description Validate weight and bias separately. Conv Assumes if weight is on the host then the bias will be on Host too. ### What's changed Deleted the validate_weight_and_bias function. Validating weight and bias separately. --- .../convnet_mnist/tests/test_performance.py | 2 +- models/demos/vgg/tt/ttnn_vgg.py | 2 + .../operations/test_prepare_conv_weights.py | 131 +++++++++++- .../ttnn/operations/conv/conv2d/conv2d.cpp | 7 +- .../conv/conv2d/prepare_conv2d_weights.cpp | 186 ++++++++++++------ .../conv/conv2d/prepare_conv2d_weights.hpp | 18 ++ 6 files changed, 279 insertions(+), 67 deletions(-) diff --git a/models/demos/convnet_mnist/tests/test_performance.py b/models/demos/convnet_mnist/tests/test_performance.py index e0aa4b052ab2..d6ccf0717d72 100644 --- a/models/demos/convnet_mnist/tests/test_performance.py +++ b/models/demos/convnet_mnist/tests/test_performance.py @@ -119,7 +119,7 @@ def test_perf_device_bare_metal_convnet_mnist(batch_size, expected_perf): subdir = "ttnn_convnet_mnist" num_iterations = 1 margin = 0.03 - expected_perf = 1753.5 if is_grayskull() else 2705.5 + expected_perf = 1800 if is_grayskull() else 2800.5 command = f"pytest tests/ttnn/integration_tests/convnet_mnist/test_convnet_mnist.py" cols = ["DEVICE FW", "DEVICE KERNEL", "DEVICE BRISC KERNEL"] diff --git a/models/demos/vgg/tt/ttnn_vgg.py b/models/demos/vgg/tt/ttnn_vgg.py index 82f5dd1c03d5..fe044e07665a 100644 --- a/models/demos/vgg/tt/ttnn_vgg.py +++ b/models/demos/vgg/tt/ttnn_vgg.py @@ -114,6 +114,7 @@ def ttnn_vgg16( tt_weight = parameters.features[conv_feature_ids[iter_conv_id]].weight tt_weight = ttnn.to_layout(ttnn.from_device(tt_weight), layout=ttnn.ROW_MAJOR_LAYOUT) tt_bias = parameters.features[conv_feature_ids[iter_conv_id]].bias + tt_bias = ttnn.to_layout(ttnn.from_device(tt_bias), layout=ttnn.ROW_MAJOR_LAYOUT) # Call ttnn.conv conv_op_cache = {} [tt_output_tensor_on_device, [out_height, out_width], [weights_device, bias_device]] = ttnn.conv2d( @@ -242,6 +243,7 @@ def ttnn_vgg11( tt_weight = parameters.features[conv_feature_ids_2[iter_conv_id]].weight tt_weight = ttnn.to_layout(ttnn.from_device(tt_weight), layout=ttnn.ROW_MAJOR_LAYOUT) tt_bias = parameters.features[conv_feature_ids_2[iter_conv_id]].bias + tt_bias = ttnn.to_layout(ttnn.from_device(tt_bias), layout=ttnn.ROW_MAJOR_LAYOUT) # Call ttnn.conv conv_op_cache = {} diff --git a/tests/ttnn/unit_tests/operations/test_prepare_conv_weights.py b/tests/ttnn/unit_tests/operations/test_prepare_conv_weights.py index 09cafdd0aca3..d57f81748b5f 100644 --- a/tests/ttnn/unit_tests/operations/test_prepare_conv_weights.py +++ b/tests/ttnn/unit_tests/operations/test_prepare_conv_weights.py @@ -186,12 +186,141 @@ def test_prepare_conv_weights( compute_config=compute_config, ) + tt_output_tensor = ttnn.from_device(tt_output_tensor_on_device) + torch_output_tensor = ttnn.to_torch(tt_output_tensor) + torch_output_tensor = torch_output_tensor[:, :, :, :output_channels] + torch_output_tensor = torch_output_tensor.reshape(torch_out_golden_tensor.shape) + + pcc = 0.99 + passing, pcc_msg = check_with_pcc_without_tensor_printout(torch_output_tensor, torch_out_golden_tensor, pcc=pcc) + logger.info(f"PCC = {pcc_msg}. Threshold = {pcc}") + assert passing + + +@skip_for_grayskull() +@skip_for_blackhole() +# @skip_for_wormhole_b0() +@pytest.mark.parametrize( + "batch_size, output_channels, input_channels, input_height, input_width, filter_height, filter_width, stride_h, stride_w, pad_h, pad_w, use_1d_systolic_array, config_override", + ( + # rn50 layer1 + (8, 64, 64, 56, 56, 3, 3, 1, 1, 1, 1, True, None), + (16, 64, 64, 56, 56, 3, 3, 1, 1, 1, 1, True, None), + (20, 64, 64, 56, 56, 3, 3, 1, 1, 1, 1, True, None), + ), +) +@pytest.mark.parametrize("packer_l1_acc", [True, False], ids=["pack_l1", "no_pack_l1"]) +@pytest.mark.parametrize("has_bias", [True, False], ids=["has_bias", "no_bias"]) +@pytest.mark.parametrize("device_params", [{"l1_small_size": 2**15}], indirect=True) +def test_prepare_bias( + batch_size, + output_channels, + input_channels, + input_height, + input_width, + filter_height, + filter_width, + stride_h, + stride_w, + pad_h, + pad_w, + use_1d_systolic_array, + packer_l1_acc, + config_override, + has_bias, + device, +): + if device.core_grid.y == 7: + pytest.skip("Issue #6992: Statically allocated circular buffers in program clash with L1 buffers on core range") + + if batch_size == 20 and ( + output_channels == 64 or (stride_h == 2 and (output_channels == 256 or output_channels == 128)) + ): + pytest.skip("Skipping test because it won't fit in L1!") + + inp_shape = (batch_size, input_channels, input_height, input_width) + conv_weight_shape = (output_channels, input_channels, filter_height, filter_width) + torch_weight_tensor = torch.randn(conv_weight_shape, dtype=torch.bfloat16) + torch_input_tensor = torch.randn(inp_shape, dtype=torch.bfloat16) + torch_bias_tensor = torch.randn((1, 1, 1, output_channels), dtype=torch.bfloat16) if has_bias else None + + torch_out_golden_tensor = torch.nn.functional.conv2d( + torch_input_tensor, + torch_weight_tensor, + bias=torch_bias_tensor.reshape(-1) if has_bias else None, + stride=(stride_h, stride_w), + padding=(pad_h, pad_w), + dilation=(1, 1), + groups=1, + ).permute(0, 2, 3, 1) + + tt_input_tensor = ttnn.from_torch(torch_input_tensor.transpose(-3, -2).transpose(-2, -1), ttnn.bfloat16) + tt_weight_tensor = ttnn.from_torch(torch_weight_tensor, ttnn.bfloat16) + tt_bias_tensor = ttnn.from_torch(torch_bias_tensor, ttnn.bfloat16) if has_bias else None + + conv_config = ttnn.Conv2dConfig( + dtype=ttnn.bfloat16, + weights_dtype=ttnn.bfloat16, + input_channels_alignment=(16 if input_channels == 16 and input_height == 115 else 32), + enable_act_double_buffer=False, + enable_split_reader=False, + enable_subblock_padding=False, + ) + compute_config = ttnn.init_device_compute_kernel_config(device.arch(), packer_l1_acc=packer_l1_acc) + if config_override and "act_block_h" in config_override: + conv_config.act_block_h_override = config_override["act_block_h"] + + if config_override and "act_block_w_div" in config_override: + conv_config.act_block_w_div = config_override["act_block_w_div"] + + if config_override and "num_cores_nhw" in config_override: + if config_override["num_cores_nhw"] == 98: + conv_config.core_grid = ttnn.CoreRangeSet({ttnn.CoreRange((0, 0), (11, 7)), ttnn.CoreRange((0, 8), (1, 8))}) + conv_config.override_sharding_config = True + print("Setting num_cores_nhw to 98") + + conv_kwargs = { + "input_layout": ttnn.ROW_MAJOR_LAYOUT, + "in_channels": input_channels, + "out_channels": output_channels, + "batch_size": batch_size, + "input_height": input_height, + "input_width": input_width, + "kernel_size": (filter_height, filter_width), + "stride": (stride_h, stride_w), + "padding": (pad_h, pad_w), + "dilation": (1, 1), + "groups": 1, + "device": device, + "conv_config": conv_config, + } + + tt_input_tensor = ttnn.to_device(tt_input_tensor, device) + + tt_bias_tensor_formatted = ( + ttnn.prepare_conv_bias( + bias_tensor=tt_bias_tensor, input_memory_config=tt_input_tensor.memory_config(), **conv_kwargs + ) + if has_bias + else None + ) + + tt_bias_tensor_formatted = ttnn.to_device(tt_bias_tensor_formatted, device) if has_bias else None + (k := next(iter(conv_kwargs)), conv_kwargs.pop(k)) ##removing 1st element from dict + tt_output_tensor_on_device = ttnn.conv2d( + input_tensor=tt_input_tensor, + weight_tensor=tt_weight_tensor, + bias_tensor=tt_bias_tensor_formatted, + **conv_kwargs, + compute_config=compute_config, + ) + tt_output_tensor = ttnn.from_device(tt_output_tensor_on_device) torch_output_tensor = ttnn.to_torch(tt_output_tensor) torch_output_tensor = torch_output_tensor[:, :, :, :output_channels] torch_output_tensor = torch_output_tensor.reshape(torch_out_golden_tensor.shape) - # + pcc = 0.99 passing, pcc_msg = check_with_pcc_without_tensor_printout(torch_output_tensor, torch_out_golden_tensor, pcc=pcc) logger.info(f"PCC = {pcc_msg}. Threshold = {pcc}") diff --git a/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d.cpp b/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d.cpp index bd754354e43d..3f88bcf04d8c 100644 --- a/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d.cpp +++ b/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d.cpp @@ -83,12 +83,7 @@ Result conv2d( ShardOrientation shard_orientation = conv_config.transpose_shards ? ShardOrientation::COL_MAJOR : ShardOrientation::ROW_MAJOR; - auto num_cores_c = shard_orientation == ShardOrientation::COL_MAJOR ? device->compute_with_storage_grid_size().y : device->compute_with_storage_grid_size().x; - auto elem_size = conv_config.weights_dtype == DataType::BFLOAT8_B ? 1 : 2; - bool is_non_tile_mul_width = - (conv_config.shard_layout == TensorMemoryLayout::BLOCK_SHARDED) && conv_config.act_block_h_override == 0 && - (conv_config.weights_dtype == DataType::BFLOAT8_B || conv_config.weights_dtype == DataType::BFLOAT16) && - conv_config.output_layout == Layout::ROW_MAJOR && ((elem_size * in_channels) % (16 * num_cores_c)) == 0; + bool is_non_tile_mul_width = check_non_tile_mul_width(device, conv_config, in_channels); DeviceComputeKernelConfig compute_config = compute_config_.value_or( init_device_compute_kernel_config(device->arch(), std::nullopt, MathFidelity::HiFi4, true, false, false)); diff --git a/ttnn/cpp/ttnn/operations/conv/conv2d/prepare_conv2d_weights.cpp b/ttnn/cpp/ttnn/operations/conv/conv2d/prepare_conv2d_weights.cpp index 5dbffe141666..0ba0363a9e6e 100644 --- a/ttnn/cpp/ttnn/operations/conv/conv2d/prepare_conv2d_weights.cpp +++ b/ttnn/cpp/ttnn/operations/conv/conv2d/prepare_conv2d_weights.cpp @@ -21,42 +21,71 @@ using sliding_window::ParallelConfig; namespace conv2d { -void validate_weight_and_bias_tensors( - const ttnn::Tensor& weight_tensor, std::optional& bias_tensor) { - TT_ASSERT(!ttnn::has_storage_type_of(weight_tensor, ttnn::DEVICE_STORAGE_TYPE)); - TT_ASSERT(weight_tensor.get_layout() == Layout::ROW_MAJOR); - TT_ASSERT(weight_tensor.get_shape().rank() == 4); - // TODO: enable this assert - // TT_ASSERT(weight_tensor.get_shape() == weight_tensor.get_legacy_shape()); - if (bias_tensor.has_value()) { - TT_ASSERT(!ttnn::has_storage_type_of(bias_tensor.value(), ttnn::DEVICE_STORAGE_TYPE)); - TT_ASSERT(bias_tensor.value().get_shape().rank() == 4); - TT_ASSERT(bias_tensor.value().get_layout() == Layout::ROW_MAJOR); - // TODO: enable this assert - // TT_ASSERT(bias_tensor.value().get_shape() == bias_tensor.value().get_legacy_shape()); - } -} - - void validate_weight_tensor(const ttnn::Tensor& weight_tensor) { - TT_ASSERT(!ttnn::has_storage_type_of(weight_tensor, ttnn::DEVICE_STORAGE_TYPE)); - TT_ASSERT(weight_tensor.get_layout() == Layout::ROW_MAJOR); - TT_ASSERT(weight_tensor.get_shape().rank() == 4); + TT_FATAL(!ttnn::has_storage_type_of(weight_tensor, ttnn::DEVICE_STORAGE_TYPE), "conv weight should be placed on host"); + TT_FATAL(weight_tensor.get_layout() == Layout::ROW_MAJOR, "conv weight layout should be in row_major layout"); + TT_FATAL(weight_tensor.get_shape().rank() == 4, "conv weight should be 4D tensor"); } void validate_bias_tensor(const ttnn::Tensor& bias_tensor) { - TT_ASSERT(!ttnn::has_storage_type_of(bias_tensor, ttnn::DEVICE_STORAGE_TYPE)); - TT_ASSERT(bias_tensor.get_shape().rank() == 4); - TT_ASSERT(bias_tensor.get_layout() == Layout::ROW_MAJOR); + TT_FATAL(!ttnn::has_storage_type_of(bias_tensor, ttnn::DEVICE_STORAGE_TYPE), "conv bias should be placed on host"); + TT_FATAL(bias_tensor.get_shape().rank() == 4, "bias tensor should be 4D tensor"); + TT_FATAL(bias_tensor.get_layout() == Layout::ROW_MAJOR, "bias tensor layout should be in row_major layout"); } void validate_weights_format(const std::string& weights_format) { TT_FATAL(weights_format.size() == 4, "weights_format must have exactly 4 characters"); - TT_ASSERT(weights_format.find("O") != string::npos, "weights_format must contain \"O\""); - TT_ASSERT(weights_format.find("I") != string::npos, "weights_format must contain \"I\""); - TT_ASSERT(weights_format.find("H") != string::npos, "weights_format must contain \"H\""); - TT_ASSERT(weights_format.find("W") != string::npos, "weights_format must contain \"W\""); - TT_ASSERT(weights_format == "OIHW", "Conv2d weights format must be \"OIHW\""); + TT_FATAL(weights_format.find("O") != string::npos, "weights_format must contain \"O\""); + TT_FATAL(weights_format.find("I") != string::npos, "weights_format must contain \"I\""); + TT_FATAL(weights_format.find("H") != string::npos, "weights_format must contain \"H\""); + TT_FATAL(weights_format.find("W") != string::npos, "weights_format must contain \"W\""); + TT_FATAL(weights_format == "OIHW", "Conv2d weights format must be \"OIHW\""); +} + +template +bool check_non_tile_mul_width( + T *device, + const Conv2dConfig& conv_config, + const uint32_t in_channels +){ + auto num_cores_c = conv_config.transpose_shards ? device->compute_with_storage_grid_size().y : device->compute_with_storage_grid_size().x; + auto elem_size = conv_config.weights_dtype == DataType::BFLOAT8_B ? 1 : 2; + bool is_non_tile_mul_width = + (conv_config.shard_layout == TensorMemoryLayout::BLOCK_SHARDED) && conv_config.act_block_h_override == 0 && + (conv_config.weights_dtype == DataType::BFLOAT8_B || conv_config.weights_dtype == DataType::BFLOAT16) && + conv_config.output_layout == Layout::ROW_MAJOR && ((elem_size * in_channels) % (16 * num_cores_c)) == 0; + return is_non_tile_mul_width; +} + +template +ttnn::Tensor conv_bias_layout_convert( + const ttnn::Tensor& bias_tensor, + DataType bias_dtype, + uint32_t weight_block_h_ntiles, + uint32_t weight_block_w_ntiles, + const ParallelConfig& parallel_config, + T * device, + uint32_t out_channels, + bool is_non_tile_mul_width) { + ttnn::Tensor bias_tensor_ = bias_tensor; + validate_bias_tensor(bias_tensor_); + if (!is_non_tile_mul_width) { + auto bias_shape = bias_tensor_.get_shape(); + TT_FATAL(bias_shape[3] == out_channels && bias_shape[0] == 1 && bias_shape[1] == 1 && bias_shape[2] == 1, "bias shape is not correct"); + tt::tt_metal::LegacyShape bias_channels_padded_shape = tt::tt_metal::LegacyShape( + std::array({1, 1, 32, round_up(out_channels, weight_block_w_ntiles * 32)})); + bias_tensor_ = ttnn::pad(bias_tensor_, bias_channels_padded_shape.to_array_4D(), tt::tt_metal::Array4D{0, 0, 0, 0}, 0); + bias_tensor_ = ttnn::to_layout( + bias_tensor_, Layout::TILE, std::nullopt, std::nullopt, (T*)nullptr); + if (bias_tensor_.get_dtype() != bias_dtype) { + bias_tensor_ = ttnn::to_dtype(bias_tensor_, bias_dtype); + } + } else { + uint32_t num_cores_channels = get_num_cores_channels_from_parallel_config(parallel_config); + bias_tensor_ = convert_conv_bias_tensor_to_tiled_layout_block_sharded( + bias_tensor_, num_cores_channels, bias_dtype); + } + return bias_tensor_; } template @@ -167,7 +196,7 @@ std::pair> prepare_conv_weights_biases const bool parameters_on_device, bool is_non_tile_mul_width) { - validate_weight_and_bias_tensors(weight_tensor, bias_tensor); + validate_weight_tensor(weight_tensor); ttnn::Tensor weight_tensor_; // tensor to return ttnn::Tensor bias_tensor_; @@ -257,23 +286,12 @@ std::pair> prepare_conv_weights_biases weight_tensor_ = ttnn::operations::core::to_device(weight_tensor_, device, std::nullopt); if (bias_tensor.has_value()) { - if (!is_non_tile_mul_width) { - bias_tensor_ = bias_tensor.value(); - auto bias_shape = bias_tensor_.get_shape(); - TT_ASSERT(bias_shape[3] == out_channels && bias_shape[0] == 1 && bias_shape[1] == 1 && bias_shape[2] == 1); - tt::tt_metal::LegacyShape bias_channels_padded_shape = tt::tt_metal::LegacyShape( - std::array({1, 1, 32, round_up(out_channels, weight_block_w_ntiles * 32)})); - bias_tensor_ = ttnn::pad(bias_tensor_, bias_channels_padded_shape.to_array_4D(), tt::tt_metal::Array4D{0, 0, 0, 0}, 0); - bias_tensor_ = ttnn::to_layout( - bias_tensor_, Layout::TILE, std::nullopt, std::nullopt, (T*)nullptr); - if (bias_tensor_.get_dtype() != weights_bias_dtype) { - bias_tensor_ = ttnn::to_dtype(bias_tensor_, weights_bias_dtype); - } - } else { - bias_tensor_ = convert_conv_bias_tensor_to_tiled_layout_block_sharded( - bias_tensor.value(), num_cores_channels, weights_bias_dtype); + bias_tensor_ = bias_tensor.value(); + bool is_bias_tensor_is_on_device = ttnn::is_tensor_on_device_or_multidevice(bias_tensor_); + if(!is_bias_tensor_is_on_device) { + bias_tensor_ = conv_bias_layout_convert(bias_tensor_, weights_bias_dtype, weight_block_h_ntiles, weight_block_w_ntiles, parallel_config, device, out_channels, is_non_tile_mul_width); + bias_tensor_ = ttnn::operations::core::to_device(bias_tensor_, device, std::nullopt); } - bias_tensor_ = ttnn::operations::core::to_device(bias_tensor_, device, std::nullopt); } return {weight_tensor_, bias_tensor.has_value() ? bias_tensor_ : std::optional()}; @@ -347,6 +365,7 @@ ttnn::Tensor prepare_conv_weights( shard_orientation, !use_non_tile_height); + bool is_non_tile_mul_width = check_non_tile_mul_width(device, conv_config, in_channels); std::optional bias_tensor = std::nullopt; ttnn::Tensor weight_tensor_on_device = weight_tensor; std::optional bias_tensor_on_device = bias_tensor; @@ -362,7 +381,8 @@ ttnn::Tensor prepare_conv_weights( groups, opt_conv_op_block_config.act_block_h_ntiles, input_width, - false); + false, + is_non_tile_mul_width); return weight_tensor_on_device; } @@ -420,20 +440,36 @@ ttnn::Tensor prepare_conv_bias( ); uint32_t weight_block_w_ntiles = opt_conv_op_block_config.out_subblock_w_ntiles; - validate_bias_tensor(bias_tensor); + ShardOrientation shard_orientation = + conv_config.transpose_shards ? ShardOrientation::COL_MAJOR : ShardOrientation::ROW_MAJOR; - ttnn::Tensor bias_tensor_; - bias_tensor_ = bias_tensor; - auto bias_shape = bias_tensor_.get_shape(); - TT_ASSERT(bias_shape[3] == out_channels && bias_shape[0] == 1 && bias_shape[1] == 1 && bias_shape[2] == 1); - tt::tt_metal::LegacyShape bias_channels_padded_shape = tt::tt_metal::LegacyShape( - std::array({1, 1, 32, tt::round_up(out_channels, weight_block_w_ntiles * 32)})); - bias_tensor_ = ttnn::pad(bias_tensor_, bias_channels_padded_shape.to_array_4D(), tt::tt_metal::Array4D({0, 0, 0, 0}), 0); - bias_tensor_ = ttnn::to_layout( - bias_tensor_, Layout::TILE, std::nullopt, std::nullopt, (T*)nullptr); - if (bias_tensor_.get_dtype() != conv_config.weights_dtype) { - bias_tensor_ = ttnn::to_dtype(bias_tensor_, conv_config.weights_dtype); - } + bool use_non_tile_height = conv_config.shard_layout.value() == TensorMemoryLayout::HEIGHT_SHARDED && out_channels <= 256 && conv_config.act_block_h_override == 0 && + (conv_config.dtype == DataType::BFLOAT16 || conv_config.dtype == DataType::FLOAT32) && conv_config.output_layout == Layout::ROW_MAJOR; + use_non_tile_height = use_non_tile_height && conv_config.input_channels_alignment != 16; + + ParallelConfig parallel_config = determine_parallel_config( + conv_config.shard_layout.value(), + batch_size, + in_channels, + output_height, + output_width, + out_channels, + device->compute_with_storage_grid_size(), + shard_orientation, + !use_non_tile_height); + + bool is_non_tile_mul_width = check_non_tile_mul_width(device, conv_config, in_channels); + ttnn::Tensor bias_tensor_ = bias_tensor; + bias_tensor_ = conv_bias_layout_convert( + bias_tensor_, + conv_config.weights_dtype, + opt_conv_op_block_config.act_block_h_ntiles, + weight_block_w_ntiles, + parallel_config, + device, + out_channels, + is_non_tile_mul_width + ); return bias_tensor_; } @@ -573,6 +609,38 @@ template ttnn::Tensor prepare_conv_bias( const std::optional& conv_config_, const std::optional& compute_config_); +template ttnn::Tensor conv_bias_layout_convert( + const ttnn::Tensor& bias_tensor, + DataType bias_dtype, + uint32_t weight_block_h_ntiles, + uint32_t weight_block_w_ntiles, + const sliding_window::ParallelConfig& parallel_config, + Device * device, + uint32_t out_channels, + bool is_non_tile_mul_width); + +template ttnn::Tensor conv_bias_layout_convert( + const ttnn::Tensor& bias_tensor, + DataType bias_dtype, + uint32_t weight_block_h_ntiles, + uint32_t weight_block_w_ntiles, + const sliding_window::ParallelConfig& parallel_config, + MeshDevice* device, + uint32_t out_channels, + bool is_non_tile_mul_width); + +template bool check_non_tile_mul_width( + Device *device, + const Conv2dConfig& conv_config, + const uint32_t in_channels +); + +template bool check_non_tile_mul_width( + MeshDevice *device, + const Conv2dConfig& conv_config, + const uint32_t in_channels +); + } // namespace conv2d } // namespace operations } // namespace ttnn diff --git a/ttnn/cpp/ttnn/operations/conv/conv2d/prepare_conv2d_weights.hpp b/ttnn/cpp/ttnn/operations/conv/conv2d/prepare_conv2d_weights.hpp index ad38e55e0ac2..221a9d230f52 100644 --- a/ttnn/cpp/ttnn/operations/conv/conv2d/prepare_conv2d_weights.hpp +++ b/ttnn/cpp/ttnn/operations/conv/conv2d/prepare_conv2d_weights.hpp @@ -18,6 +18,18 @@ namespace ttnn { namespace operations::conv { namespace conv2d { + +template +ttnn::Tensor conv_bias_layout_convert( + const ttnn::Tensor& bias_tensor, + DataType bias_dtype, + uint32_t weight_block_h_ntiles, + uint32_t weight_block_w_ntiles, + const sliding_window::ParallelConfig& parallel_config, + T * device, + uint32_t out_channels, + bool is_non_tile_mul_width); + template ttnn::Tensor prepare_conv_weights( const ttnn::Tensor& weight_tensor, @@ -73,6 +85,12 @@ std::pair> prepare_conv_weights_biases const bool parameters_on_device=true, bool is_non_tile_mul_width=false); +template +bool check_non_tile_mul_width( + T* device, + const Conv2dConfig& conv_config, + const uint32_t in_channels); + } // namespace conv2d } // namespace operations::conv } // namespace ttnn From a0857c2c6745e3a308f4bcf971bb67d00b49e10d Mon Sep 17 00:00:00 2001 From: Oleg Milyutin Date: Tue, 17 Dec 2024 22:01:11 -0500 Subject: [PATCH 36/87] #0: Minor refactor of pytensor and tensor implementation files (#16108) ### Checklist - [X] Local build and sanity check ttnn unit tests. - [X] [T3K unit tests](https://github.com/tenstorrent/tt-metal/actions/runs/12384480452/job/34570062697). --- ttnn/cpp/pybind11/pytensor.cpp | 17 ++++++------- ttnn/cpp/ttnn/tensor/tensor.cpp | 43 +++++++++++++-------------------- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/ttnn/cpp/pybind11/pytensor.cpp b/ttnn/cpp/pybind11/pytensor.cpp index cd381eabab5b..6f9ccc5ab649 100644 --- a/ttnn/cpp/pybind11/pytensor.cpp +++ b/ttnn/cpp/pybind11/pytensor.cpp @@ -9,7 +9,7 @@ #include #include -#include "tensor.hpp" +#include "ttnn/tensor/tensor.hpp" #include "tt_metal/graph/graph_tracking.hpp" #include "tt_metal/host_api.hpp" #include "tt_metal/tt_stl/overloaded.hpp" @@ -150,15 +150,12 @@ Tensor create_tt_tensor_from_py_data( std::size_t py_data_ptr, const TensorSpec& tensor_spec, Device* device, - bool override_enable_borrow, + bool force_disable_borrow, const std::function& on_creation_callback, const std::function& on_destruction_callback) { auto layout = tensor_spec.layout(); - bool enable_borrow = true; - if (layout != Layout::ROW_MAJOR or override_enable_borrow) { - enable_borrow = false; - } + const bool enable_borrow = layout == Layout::ROW_MAJOR and not force_disable_borrow; auto data_type = tensor_spec.data_type(); std::size_t num_elements = tensor_spec.logical_shape().volume(); @@ -256,7 +253,7 @@ Tensor convert_python_tensor_to_tt_tensor( const std::optional& optional_tile, const MemoryConfig& memory_config, Device* device, - bool override_enable_borrow = false) { + bool force_disable_borrow = false) { GraphTracker::instance().track_function_start( "tt::tt_metal::detail::convert_python_tensor_to_tt_tensor", py_tensor, @@ -265,7 +262,7 @@ Tensor convert_python_tensor_to_tt_tensor( optional_tile, memory_config, device, - override_enable_borrow); + force_disable_borrow); py::object torch = py::module_::import("torch"); py::object np = py::module_::import("numpy"); @@ -342,7 +339,7 @@ Tensor convert_python_tensor_to_tt_tensor( num_elements = py::cast(contiguous_py_tensor.attr("numel")()); py_data_ptr = py::cast(contiguous_py_tensor.attr("data_ptr")()); } else if (py::isinstance(py_tensor, np.attr("ndarray"))) { - TT_FATAL(!override_enable_borrow, "Disabling borrowed buffers for numpy tensors is untested!"); + TT_FATAL(!force_disable_borrow, "Disabling borrowed buffers for numpy tensors is untested!"); contiguous_py_tensor = np.attr("ascontiguousarray")(py_tensor); @@ -429,7 +426,7 @@ Tensor convert_python_tensor_to_tt_tensor( auto on_creation_callback = [tensor = contiguous_py_tensor] { tensor.inc_ref(); }; auto on_destruction_callback = [tensor = contiguous_py_tensor] { tensor.dec_ref(); }; auto output = create_tt_tensor_from_py_data( - py_data_ptr, tensor_spec, device, override_enable_borrow, on_creation_callback, on_destruction_callback); + py_data_ptr, tensor_spec, device, force_disable_borrow, on_creation_callback, on_destruction_callback); if (device) { output = output.to(device, memory_config); diff --git a/ttnn/cpp/ttnn/tensor/tensor.cpp b/ttnn/cpp/ttnn/tensor/tensor.cpp index 1057b1834966..5dd2a70106be 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor.cpp @@ -8,10 +8,10 @@ #include #include -#include "common/bfloat16.hpp" +#include "tt_metal/common/bfloat16.hpp" #include "impl/buffers/buffer_constants.hpp" #include "tt_metal/tt_stl/overloaded.hpp" -#include "tensor_ops.hpp" +#include "ttnn/tensor/tensor_ops.hpp" #include "ttnn/tensor/tensor_impl.hpp" #include "ttnn/tensor/tensor_impl_wrapper.hpp" #include "ttnn/tensor/tensor_utils.hpp" @@ -28,26 +28,9 @@ #include "ttnn/tensor/layout/tensor_layout.hpp" #include "ttnn/distributed/api.hpp" -using namespace tt::constants; - namespace tt::tt_metal { namespace { -MemoryConfig extract_memory_config(const Storage& storage) { - return std::visit( - [](const auto& storage) -> MemoryConfig { - using T = std::decay_t; - if constexpr (std::is_same_v) { - return storage.memory_config(); - } else if constexpr (std::is_same_v) { - return storage.memory_config(); - } else { - return MemoryConfig{}; - } - }, - storage); -} - template Tensor create_owned_tensor_from_span(tt::stl::Span data, const TensorSpec& spec) { // TODO: support tilized layouts. @@ -154,14 +137,22 @@ void Tensor::TensorAttributes::update_main_thread_ref_count(Device* worker, uint Tensor::Tensor( Storage storage, const ttnn::Shape& shape, DataType dtype, Layout layout, const std::optional& tile) { - if (tile.has_value()) { - if (tile->get_tile_shape()[0] != TILE_WIDTH or tile->get_tile_shape()[1] != TILE_HEIGHT) { - tt::log_warning( - "only matmul op and ccl all-gather currently supports the customized tile shape: {}", - tile->get_tile_shape()); - } + using namespace tt::constants; + + if (tile.has_value() and // + (tile->get_tile_shape()[0] != TILE_WIDTH or tile->get_tile_shape()[1] != TILE_HEIGHT)) { + tt::log_warning( + "only matmul op and ccl all-gather currently supports the customized tile shape: {}", + tile->get_tile_shape()); } - auto memory_config = extract_memory_config(storage); + + const auto memory_config = std::visit( + tt::stl::overloaded{ + [](const DeviceStorage& s) { return s.memory_config(); }, + [](const MultiDeviceStorage& s) { return s.memory_config(); }, + [](const Other&) { return MemoryConfig{}; }}, + storage); + init( std::move(storage), TensorSpec( From 51bedc860fc870c178ccf69dc7a79f8d6ab266ba Mon Sep 17 00:00:00 2001 From: Andrew Fuller Date: Tue, 17 Dec 2024 22:12:42 -0500 Subject: [PATCH 37/87] C++ files should not be part of the API of a library (#16123) ### Ticket None ### Problem description Very very rarely should we include a .cpp file (if intended, should call it .ipp, generally). This one looks like a typo and doesn't seem to break anything when removing. ### What's changed rm -rf ### Checklist - [x] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12379363947 --- .../moreh/moreh_dot/device/moreh_dot_program_factory.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_dot/device/moreh_dot_program_factory.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_dot/device/moreh_dot_program_factory.cpp index e5d3c403dc55..9343128bd60f 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_dot/device/moreh_dot_program_factory.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_dot/device/moreh_dot_program_factory.cpp @@ -5,7 +5,6 @@ #include "moreh_dot_device_operation.hpp" #include "ttnn/operations/moreh/moreh_helper_functions.hpp" #include "tt_metal/common/constants.hpp" -#include "tt_metal/tt_metal.cpp" namespace ttnn::operations::moreh::moreh_dot { MorehDotOperation::SingleCore::cached_program_t MorehDotOperation::SingleCore::create( From 636f5457f2c6202957ab346bfa0c875a3fed508c Mon Sep 17 00:00:00 2001 From: Uma Devi Selvaraj Date: Wed, 18 Dec 2024 11:58:01 +0530 Subject: [PATCH 38/87] #15857: Forge sweep test (#15858) ### Ticket #15857 ### Problem description Forge sweep tests ### What's changed - sweep test for the listed ops ### Checklist - [x] Abs - https://github.com/tenstorrent/tt-metal/actions/runs/12387270528 - [x] Sin - https://github.com/tenstorrent/tt-metal/actions/runs/12387288205 - [x] Cos - https://github.com/tenstorrent/tt-metal/actions/runs/12387294876 - [x] Exp - https://github.com/tenstorrent/tt-metal/actions/runs/12387315608 - [x] tanh - https://github.com/tenstorrent/tt-metal/actions/runs/12387321093 - [x] logit - https://github.com/tenstorrent/tt-metal/actions/runs/12387307541 --- .github/workflows/ttnn-run-sweeps.yaml | 6 + .../sweeps/eltwise/unary/abs/abs_forge.py | 84 ++++++++++++ .../sweeps/eltwise/unary/cos/cos_forge.py | 84 ++++++++++++ .../sweeps/eltwise/unary/exp/exp_forge.py | 121 ++++++++++++++++++ .../sweeps/eltwise/unary/logit/logit_forge.py | 110 ++++++++++++++++ .../sweeps/eltwise/unary/sin/sin_forge.py | 84 ++++++++++++ .../sweeps/eltwise/unary/tanh/tanh_forge.py | 97 ++++++++++++++ 7 files changed, 586 insertions(+) create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/abs/abs_forge.py create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/cos/cos_forge.py create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/exp/exp_forge.py create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/logit/logit_forge.py create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/sin/sin_forge.py create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/tanh/tanh_forge.py diff --git a/.github/workflows/ttnn-run-sweeps.yaml b/.github/workflows/ttnn-run-sweeps.yaml index 747f0dd546c4..ae4f6861d447 100644 --- a/.github/workflows/ttnn-run-sweeps.yaml +++ b/.github/workflows/ttnn-run-sweeps.yaml @@ -29,10 +29,13 @@ on: - eltwise.unary.hardsigmoid.hardsigmoid_pytorch2 - eltwise.unary.leaky_relu.leaky_relu_pytorch2 - eltwise.unary.abs.abs + - eltwise.unary.abs.abs_forge - eltwise.unary.cos.cos - eltwise.unary.cos.cos_pytorch2 + - eltwise.unary.cos.cos_forge - eltwise.unary.sin.sin - eltwise.unary.sin.sin_pytorch2 + - eltwise.unary.sin.sin_forge - eltwise.unary.tril.tril_pytorch2 - eltwise.unary.clamp.clamp - eltwise.unary.clamp.clamp_pytorch2 @@ -56,11 +59,13 @@ on: - eltwise.unary.elu.elu_pytorch2 - eltwise.unary.erfc.erfc - eltwise.unary.exp.exp + - eltwise.unary.exp.exp_forge - eltwise.unary.exp.exp_pytorch2 - eltwise.unary.exp2.exp2 - eltwise.unary.expm1.expm1 - eltwise.unary.tanh.tanh - eltwise.unary.tanh.tanh_pytorch2 + - eltwise.unary.tanh.tanh_forge - eltwise.unary.atanh.atanh - eltwise.unary.atan.atan - eltwise.unary.sign.sign @@ -181,6 +186,7 @@ on: - eltwise.unary.lgamma.lgamma - eltwise.unary.lgamma.lgamma_sharded - eltwise.unary.logit.logit + - eltwise.unary.logit.logit_forge - eltwise.unary.logit.logit_sharded - eltwise.unary.mish.mish - eltwise.unary.mish.mish_sharded diff --git a/tests/sweep_framework/sweeps/eltwise/unary/abs/abs_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/abs/abs_forge.py new file mode 100644 index 000000000000..993a2b297824 --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/abs/abs_forge.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [[15, 15]], + "input_a_dtype": [ttnn.float32], # [ttnn.int32] + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.abs) + torch_output_tensor = golden_function(torch_input_tensor_a) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.abs(input_tensor_a, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/cos/cos_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/cos/cos_forge.py new file mode 100644 index 000000000000..9868ca8d8955 --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/cos/cos_forge.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [[1, 23, 40, 64], [1, 32, 128], [1, 7, 64]], + "input_a_dtype": [ttnn.float32], + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.cos) + torch_output_tensor = golden_function(torch_input_tensor_a) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.cos(input_tensor_a, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/exp/exp_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/exp/exp_forge.py new file mode 100644 index 000000000000..718498891809 --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/exp/exp_forge.py @@ -0,0 +1,121 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [ + [1, 12, 201, 201], + [1, 8, 2048, 256], + [1, 1, 16384, 256], + [1, 16, 5, 5], + [1, 12, 197, 197], + [8, 920, 920], + [1, 16, 32, 32], + [1, 12, 8, 8], + [1, 5, 1200, 300], + [1, 16, 10, 10], + [1, 1, 19200, 300], + [1, 8, 256, 256], + [160], + [1, 6, 1, 15], + [1, 16, 1, 1], + [1, 12, 10, 10], + [1, 16, 1, 10], + [8, 100, 100], + [1, 5, 1024, 256], + [1, 12, 1, 1], + [1, 8, 10, 10], + [1, 12, 1, 10], + [19, 256008], + [8, 100, 920], + [1, 8, 1, 1], + [1, 8, 1, 10], + [1, 2, 4800, 300], + [1, 8, 256, 2048], + [1, 2, 4096, 256], + [1, 6, 15, 15], + [1, 6, 1, 1], + [1, 8, 300, 300], + [1, 10], + [3234, 1], + [1, 16, 197, 197], + ], + "input_a_dtype": [ttnn.float32], + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-80, high=80, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.exp) + torch_output_tensor = golden_function(torch_input_tensor_a) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.exp(input_tensor_a, memory_config=output_memory_config) + # ToDo: Update it once the tensor layout support with rank < 2 is supported in mid of Jan + output_tensor = ttnn.to_torch(result, torch_rank=len(input_shape)) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/logit/logit_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/logit/logit_forge.py new file mode 100644 index 000000000000..f6deddc569f5 --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/logit/logit_forge.py @@ -0,0 +1,110 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [ + [1, 1280, 32, 32], + [2, 7, 2048], + [1, 1920, 32, 32], + [1, 640, 64, 64], + [1, 2, 30, 40], + [1, 640, 32, 32], + [1, 320, 32, 32], + [1, 2, 60, 80], + [1, 320, 64, 64], + [1, 1, 480, 640], + [1, 960, 64, 64], + [1, 960, 32, 32], + [1, 1280, 16, 16], + [1, 2560, 16, 16], + [1, 640, 16, 16], + [1, 1920, 16, 16], + [1, 1280, 8, 8], + [1, 1, 256, 256], + [1, 2, 120, 160], + [1, 32, 11008], + [1, 2560, 8, 8], + [1, 50, 3072], + [6, 1, 100, 4], + ], + "eps": [2.0, 0.1, 10e-3], + "input_a_dtype": [ttnn.bfloat16], + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + eps, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.logit) + torch_output_tensor = golden_function(torch_input_tensor_a, eps=eps, device=device) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.logit(input_tensor_a, eps=eps, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.99), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/sin/sin_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/sin/sin_forge.py new file mode 100644 index 000000000000..5ae57f249e28 --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/sin/sin_forge.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [[1, 23, 40, 64], [1, 32, 128], [1, 7, 64]], + "input_a_dtype": [ttnn.float32], + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.sin) + torch_output_tensor = golden_function(torch_input_tensor_a) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.sin(input_tensor_a, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/tanh/tanh_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/tanh/tanh_forge.py new file mode 100644 index 000000000000..1293a7f07741 --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/tanh/tanh_forge.py @@ -0,0 +1,97 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [ + [1, 32, 6144], + [1, 15, 1024], + [1, 9, 4096], + [1, 5, 4096], + [1, 9, 16384], + [1, 9, 128], + [1, 14, 3072], + [1, 1, 1024], + [1, 9, 3072], + [1, 9, 8192], + [1, 768], + [1, 7, 3072], + ], + "input_a_dtype": [ttnn.bfloat16], + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.tanh) + torch_output_tensor = golden_function(torch_input_tensor_a) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.tanh(input_tensor_a, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] From 5a320fceb1ff490043dea484711cebdd25634652 Mon Sep 17 00:00:00 2001 From: umadevimcw Date: Wed, 11 Dec 2024 11:00:39 +0000 Subject: [PATCH 39/87] #15857: Add unary ops forge sweep tests --- .../sweeps/eltwise/unary/clamp/clamp_forge.py | 163 ++++++++++++++ .../sweeps/eltwise/unary/floor/floor_forge.py | 84 ++++++++ .../sweeps/eltwise/unary/log/log_forge.py | 84 ++++++++ .../sweeps/eltwise/unary/neg/neg_forge.py | 202 ++++++++++++++++++ .../sweeps/eltwise/unary/rsqrt/rsqrt_forge.py | 123 +++++++++++ .../sweeps/eltwise/unary/sqrt/sqrt_forge.py | 129 +++++++++++ 6 files changed, 785 insertions(+) create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/clamp/clamp_forge.py create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/floor/floor_forge.py create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/log/log_forge.py create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/neg/neg_forge.py create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/rsqrt/rsqrt_forge.py create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/sqrt/sqrt_forge.py diff --git a/tests/sweep_framework/sweeps/eltwise/unary/clamp/clamp_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/clamp/clamp_forge.py new file mode 100644 index 000000000000..cabedcd83af5 --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/clamp/clamp_forge.py @@ -0,0 +1,163 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [ + [120, 1], + [128], + [128], + [128], + [128], + [128, 1], + [128, 1], + [128, 1], + [128, 1], + [160], + [240, 1], + [27], + [27, 1], + [30, 1], + [320], + [320], + [320, 1], + [40], + [480, 1], + [60, 1], + [640], + [80], + [1, 1024, 512], + [1, 1024, 512], + [1, 1024, 640], + [1, 1024, 640], + [1, 10, 3072], + [1, 10, 3072], + [1, 10, 768], + [1, 10, 768], + [1, 1200, 1280], + [1, 1200, 1280], + [1, 1445, 768], + [1, 1445, 768], + [1, 1536], + [1, 1536], + [1, 16384, 128], + [1, 16384, 128], + [1, 16, 3072], + [1, 16, 3072], + [1, 19200, 256], + [1, 19200, 256], + [1, 197, 3072], + [1, 197, 3072], + [1, 197, 4096], + [1, 197, 4096], + [1, 19, 4096], + [1, 19, 4096], + [1, 201, 3072], + [1, 201, 3072], + [1, 2048, 768], + [1, 2048, 768], + [1, 256, 1024], + [1, 256, 1024], + [1, 256, 1280], + [1, 256, 1280], + [1, 256, 256], + [1, 256, 256], + [1, 256, 4096], + [1, 256, 4096], + [1, 256, 5120], + [1, 256, 5120], + [1, 25, 3072], + [1, 25, 3072], + [1, 300, 2048], + [1, 300, 2048], + [1, 3072, 8], + [1, 3072, 8], + [1, 4096, 1280], + [1, 4096, 1280], + [1, 4096, 256], + [1, 4096, 256], + [1, 4800, 512], + [1, 4800, 512], + [1, 64, 5120], + [1, 64, 5120], + [1, 7, 18176], + [1, 7, 18176], + ], + "input_a_dtype": [ttnn.float32], + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.clamp) + torch_output_tensor = golden_function(torch_input_tensor_a) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.clamp(input_tensor_a, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/floor/floor_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/floor/floor_forge.py new file mode 100644 index 000000000000..c84f35e8d98a --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/floor/floor_forge.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [[197], [19]], + "input_a_dtype": [ttnn.float32], + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.floor) + torch_output_tensor = golden_function(torch_input_tensor_a) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.floor(input_tensor_a, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/log/log_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/log/log_forge.py new file mode 100644 index 000000000000..c5ed180ee577 --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/log/log_forge.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [[15, 15], [19, 1], [1, 1]], + "input_a_dtype": [ttnn.float32], + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.log) + torch_output_tensor = golden_function(torch_input_tensor_a) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.log(input_tensor_a, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/neg/neg_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/neg/neg_forge.py new file mode 100644 index 000000000000..30a833b8493a --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/neg/neg_forge.py @@ -0,0 +1,202 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [ + [1, 1], + [1, 1, 7, 32], + [1, 32, 32, 64], + [1, 5, 16, 16], + [1, 71, 7, 32], + [19, 1], + [1, 1], + [1, 1024, 1], + [1024, 1, 1], + [1024, 1, 1], + [1, 10, 1], + [112, 1, 1], + [116, 1, 1], + [1, 1200, 1], + [120, 1, 1], + [128, 1, 1], + [128, 1, 1], + [128, 1, 1], + [128, 1, 1], + [1, 12, 1], + [1, 12, 1], + [134, 1, 1], + [1, 1445, 1], + [144, 1, 1], + [1, 14, 1], + [14, 1, 1], + [1, 14, 1], + [160, 1, 1], + [1, 16384, 1], + [168, 1, 1], + [16, 1, 1], + [16, 1, 1], + [16, 1, 1], + [1, 16, 1], + [184, 1, 1], + [1, 19200, 1], + [196, 1, 1], + [1, 197, 1], + [1, 197, 1], + [1, 19, 1], + [200, 1, 1], + [1, 201, 1], + [1, 2048, 1], + [20, 1, 1], + [240, 1, 1], + [24, 1, 1], + [24, 1, 1], + [1, 256, 1], + [256, 1, 1], + [256, 1, 1], + [256, 1, 1], + [1, 256, 1], + [256, 1, 1], + [1, 256, 1], + [256, 1, 1], + [256, 1, 1], + [1, 25, 1], + [272, 1, 1], + [28, 1, 1], + [1, 300, 1], + [1, 300, 1], + [1, 300, 1], + [1, 300, 1], + [320, 1, 1], + [320, 1, 1], + [1, 32, 1, 1], + [32, 1, 1], + [1, 32, 1], + [32, 1, 1], + [1, 32, 1, 1], + [32, 1, 1], + [1, 32, 1, 1], + [32, 1, 1], + [334, 1, 1], + [34, 1, 1], + [1, 4096, 1], + [40, 1, 1], + [40, 1, 1], + [40, 1, 1], + [462, 1, 1], + [46, 1, 1], + [1, 4800, 1], + [480, 1, 1], + [1, 50, 1], + [512, 1, 1], + [512, 1, 1], + [512, 1, 1], + [512, 1, 1], + [512, 1, 1], + [58, 1, 1], + [1, 5, 1], + [640, 1, 1], + [64, 1, 1], + [1, 64, 1], + [64, 1, 1], + [64, 1, 1], + [64, 1, 1], + [64, 1, 1], + [672, 1, 1], + [68, 1, 1], + [68, 1, 1], + [1, 6, 1], + [72, 1, 1], + [72, 1, 1], + [78, 1, 1], + [1, 7, 1], + [80, 1, 1], + [1, 8, 1], + [960, 1, 1], + [96, 1, 1], + [96, 1, 1], + [98, 1, 1], + [1, 9, 1], + [1, 9, 1], + [2, 7, 1], + [3, 1, 1], + [920, 1, 1], + ], + "input_a_dtype": [ttnn.float32], + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.neg) + torch_output_tensor = golden_function(torch_input_tensor_a) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.neg(input_tensor_a, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/rsqrt/rsqrt_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/rsqrt/rsqrt_forge.py new file mode 100644 index 000000000000..3e5c13132521 --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/rsqrt/rsqrt_forge.py @@ -0,0 +1,123 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [ + [1, 1], + [1, 1024, 1], + [1, 1024, 1, 1], + [1, 10, 1], + [1, 1200, 1], + [1, 128, 1, 1], + [1, 12, 1], + [1, 1445, 1], + [1, 14, 1], + [1, 15, 1], + [1, 16384, 1], + [1, 16, 1], + [1, 19200, 1], + [1, 197, 1], + [1, 19, 1], + [1, 1, 1], + [1, 201, 1], + [1, 2048, 1], + [1, 2048, 1, 1], + [1, 256, 1], + [1, 256, 1, 1], + [1, 25, 1], + [1, 300, 1], + [1, 32, 1], + [1, 32, 1, 1], + [1, 4096, 1], + [1, 4800, 1], + [1, 50, 1], + [1, 512, 1, 1], + [1, 5, 1], + [1, 64, 1], + [1, 64, 1, 1], + [1, 6, 1], + [1, 7, 1], + [1, 8, 1], + [1, 9, 1], + [2, 7, 1], + [920, 1, 1], + ], + "input_a_dtype": [ttnn.float32], + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.rsqrt) + torch_output_tensor = golden_function(torch_input_tensor_a) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.rsqrt(input_tensor_a, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/sqrt/sqrt_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/sqrt/sqrt_forge.py new file mode 100644 index 000000000000..70f3717083a3 --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/sqrt/sqrt_forge.py @@ -0,0 +1,129 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [ + [112], + [116], + [120], + [128], + [1280], + [134], + [14], + [144], + [16], + [160], + [168], + [184], + [192], + [196], + [20], + [200], + [2048], + [24], + [240], + [256], + [272], + [28], + [32], + [320], + [334], + [34], + [384], + [40], + [46], + [462], + [480], + [512], + [576], + [58], + [64], + [640], + [672], + [68], + [72], + [78], + [80], + [96], + [960], + [98], + ], + "input_a_dtype": [ttnn.float32], + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.sqrt) + torch_output_tensor = golden_function(torch_input_tensor_a) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.sqrt(input_tensor_a, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] From 91e86d12e348c85a61e5d95f188a3423279fd4da Mon Sep 17 00:00:00 2001 From: umadevimcw Date: Wed, 11 Dec 2024 11:18:15 +0000 Subject: [PATCH 40/87] #15857: Update sweep workflow file --- .github/workflows/ttnn-run-sweeps.yaml | 6 + .../sweeps/eltwise/unary/clamp/clamp_forge.py | 103 ++++------- .../sweeps/eltwise/unary/floor/floor_forge.py | 2 +- .../sweeps/eltwise/unary/log/log_forge.py | 4 +- .../sweeps/eltwise/unary/neg/neg_forge.py | 167 +++++++----------- .../sweeps/eltwise/unary/rsqrt/rsqrt_forge.py | 88 +++++---- .../sweeps/eltwise/unary/sqrt/sqrt_forge.py | 68 +++---- 7 files changed, 198 insertions(+), 240 deletions(-) diff --git a/.github/workflows/ttnn-run-sweeps.yaml b/.github/workflows/ttnn-run-sweeps.yaml index ae4f6861d447..703b17ba1c8d 100644 --- a/.github/workflows/ttnn-run-sweeps.yaml +++ b/.github/workflows/ttnn-run-sweeps.yaml @@ -38,6 +38,7 @@ on: - eltwise.unary.sin.sin_forge - eltwise.unary.tril.tril_pytorch2 - eltwise.unary.clamp.clamp + - eltwise.unary.clamp.clamp_forge - eltwise.unary.clamp.clamp_pytorch2 - eltwise.unary.clamp.clamp_min_pytorch2 - eltwise.unary.clip.clip @@ -45,6 +46,7 @@ on: - eltwise.unary.rsub.rsub - eltwise.unary.rsub.rsub_pytorch2 - eltwise.unary.rsqrt.rsqrt_pytorch2 + - eltwise.unary.rsqrt.rsqrt_forge - eltwise.unary.rdiv.rdiv - eltwise.unary.frac.frac - eltwise.unary.frac.frac_sharded @@ -53,6 +55,7 @@ on: - eltwise.unary.trunc.trunc - eltwise.unary.trunc.trunc_sharded - eltwise.unary.floor.floor + - eltwise.unary.floor.floor_forge - eltwise.unary.floor.floor_pytorch2 - eltwise.unary.clone.clone - eltwise.unary.elu.elu @@ -74,9 +77,11 @@ on: - eltwise.unary.relu6.relu6 - eltwise.unary.log.log - eltwise.unary.log.log_pytorch2 + - eltwise.unary.log.log_forge - eltwise.unary.log1p.log1p - eltwise.unary.log2.log2 - eltwise.unary.log10.log10 + - eltwise.unary.sqrt.sqrt_forge - eltwise.unary.bitwise.bitwise_and - eltwise.unary.bitwise.bitwise_left_shift - eltwise.unary.bitwise.bitwise_not @@ -90,6 +95,7 @@ on: - eltwise.unary.logical_not.logical_not_output - eltwise.unary.logical_not.logical_not_pytorch2 - eltwise.unary.neg.neg_pytorch2 + - eltwise.unary.neg.neg_forge - eltwise.unary.erf.erf - eltwise.unary.erfinv.erfinv - eltwise.unary.i0.i0 diff --git a/tests/sweep_framework/sweeps/eltwise/unary/clamp/clamp_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/clamp/clamp_forge.py index cabedcd83af5..c43227673db0 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/clamp/clamp_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/clamp/clamp_forge.py @@ -29,86 +29,51 @@ parameters = { "nightly": { "input_shape": [ - [120, 1], - [128], - [128], - [128], - [128], - [128, 1], - [128, 1], - [128, 1], - [128, 1], + [1, 4800, 512], + [1, 300, 2048], + [320, 1], + [27, 1], + [60, 1], + [1, 256, 5120], + [1, 197, 3072], + [1, 10, 3072], + [1, 3072, 8], [160], + [128, 1], + [1, 19, 4096], [240, 1], - [27], - [27, 1], - [30, 1], - [320], - [320], - [320, 1], - [40], + [1, 1445, 768], + [1, 64, 5120], + [1, 10, 768], + [1, 201, 3072], + [1, 25, 3072], [480, 1], - [60, 1], - [640], + [1, 16, 3072], + [1, 256, 256], [80], + [1, 16384, 128], + [1, 2048, 768], [1, 1024, 512], - [1, 1024, 512], - [1, 1024, 640], + [1, 197, 4096], [1, 1024, 640], - [1, 10, 3072], - [1, 10, 3072], - [1, 10, 768], - [1, 10, 768], - [1, 1200, 1280], - [1, 1200, 1280], - [1, 1445, 768], - [1, 1445, 768], - [1, 1536], + [40], + [1, 4096, 256], [1, 1536], - [1, 16384, 128], - [1, 16384, 128], - [1, 16, 3072], - [1, 16, 3072], - [1, 19200, 256], - [1, 19200, 256], - [1, 197, 3072], - [1, 197, 3072], - [1, 197, 4096], - [1, 197, 4096], - [1, 19, 4096], - [1, 19, 4096], - [1, 201, 3072], - [1, 201, 3072], - [1, 2048, 768], - [1, 2048, 768], - [1, 256, 1024], [1, 256, 1024], - [1, 256, 1280], - [1, 256, 1280], - [1, 256, 256], - [1, 256, 256], - [1, 256, 4096], + [128], + [1, 7, 18176], [1, 256, 4096], - [1, 256, 5120], - [1, 256, 5120], - [1, 25, 3072], - [1, 25, 3072], - [1, 300, 2048], - [1, 300, 2048], - [1, 3072, 8], - [1, 3072, 8], - [1, 4096, 1280], [1, 4096, 1280], - [1, 4096, 256], - [1, 4096, 256], - [1, 4800, 512], - [1, 4800, 512], - [1, 64, 5120], - [1, 64, 5120], - [1, 7, 18176], - [1, 7, 18176], + [30, 1], + [320], + [1, 256, 1280], + [27], + [640], + [1, 1200, 1280], + [120, 1], + [1, 19200, 256], ], - "input_a_dtype": [ttnn.float32], + "input_a_dtype": [["ttnn.int32", "ttnn.float32"]], "input_a_layout": [ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], diff --git a/tests/sweep_framework/sweeps/eltwise/unary/floor/floor_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/floor/floor_forge.py index c84f35e8d98a..8e75089b4136 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/floor/floor_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/floor/floor_forge.py @@ -29,7 +29,7 @@ parameters = { "nightly": { "input_shape": [[197], [19]], - "input_a_dtype": [ttnn.float32], + "input_a_dtype": [["ttnn.float32"]], "input_a_layout": [ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], diff --git a/tests/sweep_framework/sweeps/eltwise/unary/log/log_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/log/log_forge.py index c5ed180ee577..8c7ab9ca6197 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/log/log_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/log/log_forge.py @@ -28,8 +28,8 @@ parameters = { "nightly": { - "input_shape": [[15, 15], [19, 1], [1, 1]], - "input_a_dtype": [ttnn.float32], + "input_shape": [[19, 1], [1, 1], [15, 15]], + "input_a_dtype": [["ttnn.float32"]], "input_a_layout": [ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], diff --git a/tests/sweep_framework/sweeps/eltwise/unary/neg/neg_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/neg/neg_forge.py index 30a833b8493a..db989f0477e7 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/neg/neg_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/neg/neg_forge.py @@ -29,125 +29,84 @@ parameters = { "nightly": { "input_shape": [ - [1, 1], - [1, 1, 7, 32], - [1, 32, 32, 64], - [1, 5, 16, 16], - [1, 71, 7, 32], - [19, 1], - [1, 1], + [58, 1, 1], + [1, 16, 1], + [1, 201, 1], [1, 1024, 1], - [1024, 1, 1], - [1024, 1, 1], - [1, 10, 1], - [112, 1, 1], - [116, 1, 1], - [1, 1200, 1], - [120, 1, 1], - [128, 1, 1], - [128, 1, 1], - [128, 1, 1], - [128, 1, 1], - [1, 12, 1], - [1, 12, 1], [134, 1, 1], - [1, 1445, 1], - [144, 1, 1], - [1, 14, 1], - [14, 1, 1], [1, 14, 1], - [160, 1, 1], - [1, 16384, 1], - [168, 1, 1], - [16, 1, 1], - [16, 1, 1], - [16, 1, 1], - [1, 16, 1], - [184, 1, 1], - [1, 19200, 1], - [196, 1, 1], - [1, 197, 1], - [1, 197, 1], - [1, 19, 1], - [200, 1, 1], - [1, 201, 1], - [1, 2048, 1], [20, 1, 1], - [240, 1, 1], - [24, 1, 1], - [24, 1, 1], [1, 256, 1], - [256, 1, 1], - [256, 1, 1], - [256, 1, 1], - [1, 256, 1], - [256, 1, 1], - [1, 256, 1], - [256, 1, 1], - [256, 1, 1], - [1, 25, 1], - [272, 1, 1], - [28, 1, 1], - [1, 300, 1], - [1, 300, 1], - [1, 300, 1], - [1, 300, 1], - [320, 1, 1], - [320, 1, 1], - [1, 32, 1, 1], - [32, 1, 1], - [1, 32, 1], - [32, 1, 1], - [1, 32, 1, 1], - [32, 1, 1], - [1, 32, 1, 1], + [1, 5, 1], + [480, 1, 1], + [1, 7, 1], + [960, 1, 1], + [72, 1, 1], + [1024, 1, 1], + [1, 16384, 1], + [1, 1200, 1], + [2, 7, 1], [32, 1, 1], - [334, 1, 1], - [34, 1, 1], [1, 4096, 1], - [40, 1, 1], - [40, 1, 1], - [40, 1, 1], - [462, 1, 1], - [46, 1, 1], + [256, 1, 1], + [1, 64, 1], + [1, 9, 1], + [1, 5, 16, 16], + [640, 1, 1], + [160, 1, 1], + [1, 32, 1, 1], + [144, 1, 1], + [512, 1, 1], + [240, 1, 1], + [120, 1, 1], + [200, 1, 1], + [168, 1, 1], + [1, 32, 32, 64], + [3, 1, 1], [1, 4800, 1], - [480, 1, 1], + [16, 1, 1], + [28, 1, 1], + [116, 1, 1], + [128, 1, 1], [1, 50, 1], - [512, 1, 1], - [512, 1, 1], - [512, 1, 1], - [512, 1, 1], - [512, 1, 1], - [58, 1, 1], - [1, 5, 1], - [640, 1, 1], - [64, 1, 1], - [1, 64, 1], - [64, 1, 1], - [64, 1, 1], - [64, 1, 1], - [64, 1, 1], + [1, 19, 1], + [1, 1, 7, 32], + [1, 19200, 1], + [334, 1, 1], + [462, 1, 1], + [1, 8, 1], [672, 1, 1], - [68, 1, 1], - [68, 1, 1], - [1, 6, 1], - [72, 1, 1], - [72, 1, 1], + [920, 1, 1], [78, 1, 1], - [1, 7, 1], + [40, 1, 1], + [272, 1, 1], + [1, 1445, 1], + [34, 1, 1], + [46, 1, 1], + [24, 1, 1], + [1, 10, 1], + [112, 1, 1], + [1, 32, 1], + [1, 6, 1], [80, 1, 1], - [1, 8, 1], - [960, 1, 1], - [96, 1, 1], + [1, 12, 1], + [1, 197, 1], + [19, 1], + [68, 1, 1], [96, 1, 1], + [1, 1], + [1, 71, 7, 32], + [1, 2048, 1], + [14, 1, 1], [98, 1, 1], - [1, 9, 1], - [1, 9, 1], - [2, 7, 1], - [3, 1, 1], - [920, 1, 1], + [196, 1, 1], + [64, 1, 1], + [320, 1, 1], + [1, 25, 1], + [1, 300, 1], + [184, 1, 1], ], - "input_a_dtype": [ttnn.float32], + "input_a_dtype": [["ttnn.float32", "ttnn.int32", "ttnn.bfloat16"]], "input_a_layout": [ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], diff --git a/tests/sweep_framework/sweeps/eltwise/unary/rsqrt/rsqrt_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/rsqrt/rsqrt_forge.py index 3e5c13132521..aec05e77e1a4 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/rsqrt/rsqrt_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/rsqrt/rsqrt_forge.py @@ -29,46 +29,74 @@ parameters = { "nightly": { "input_shape": [ - [1, 1], + [1, 16, 1], + [1, 4800, 512], [1, 1024, 1], - [1, 1024, 1, 1], - [1, 10, 1], - [1, 1200, 1], - [1, 128, 1, 1], - [1, 12, 1], - [1, 1445, 1], [1, 14, 1], - [1, 15, 1], - [1, 16384, 1], - [1, 16, 1], - [1, 19200, 1], - [1, 197, 1], - [1, 19, 1], - [1, 1, 1], + [1, 300, 2048], [1, 201, 1], - [1, 2048, 1], - [1, 2048, 1, 1], [1, 256, 1], - [1, 256, 1, 1], - [1, 25, 1], - [1, 300, 1], - [1, 32, 1], - [1, 32, 1, 1], + [1, 5, 1], + [1, 7, 1], + [1, 256, 5120], + [1, 16384, 1], + [1, 1200, 1], + [1, 64, 1, 1], + [2, 7, 1], + [1, 197, 3072], + [1, 10, 3072], + [1, 3072, 8], [1, 4096, 1], - [1, 4800, 1], - [1, 50, 1], [1, 512, 1, 1], - [1, 5, 1], + [1, 128, 1, 1], [1, 64, 1], - [1, 64, 1, 1], - [1, 6, 1], - [1, 7, 1], - [1, 8, 1], [1, 9, 1], - [2, 7, 1], + [1, 19, 4096], + [1, 32, 1, 1], + [1, 15, 1], + [1, 1445, 768], + [1, 64, 5120], + [1, 10, 768], + [1, 201, 3072], + [1, 25, 3072], + [1, 256, 256], + [1, 16, 3072], + [1, 1024, 1, 1], + [1, 16384, 128], + [1, 2048, 768], + [1, 1024, 512], + [1, 4800, 1], + [1, 50, 1], + [1, 197, 4096], + [1, 1024, 640], + [1, 19, 1], + [1, 4096, 256], + [1, 19200, 1], + [1, 256, 1, 1], + [1, 8, 1], [920, 1, 1], + [1, 1536], + [1, 256, 1024], + [1, 1445, 1], + [1, 6, 1], + [1, 7, 18176], + [1, 10, 1], + [1, 32, 1], + [1, 256, 4096], + [1, 12, 1], + [1, 197, 1], + [1, 256, 1280], + [1, 1], + [1, 2048, 1, 1], + [1, 2048, 1], + [1, 1200, 1280], + [1, 300, 1], + [1, 4096, 1280], + [1, 25, 1], + [1, 1, 1], + [1, 19200, 256], ], - "input_a_dtype": [ttnn.float32], + "input_a_dtype": [["ttnn.bfloat16", "ttnn.float32"]], "input_a_layout": [ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], diff --git a/tests/sweep_framework/sweeps/eltwise/unary/sqrt/sqrt_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/sqrt/sqrt_forge.py index 70f3717083a3..0480536c8e9e 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/sqrt/sqrt_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/sqrt/sqrt_forge.py @@ -29,52 +29,52 @@ parameters = { "nightly": { "input_shape": [ - [112], - [116], - [120], - [128], - [1280], - [134], + [240], + [960], [14], - [144], - [16], - [160], - [168], - [184], - [192], - [196], + [72], + [78], + [1280], [20], [200], + [334], + [160], + [32], + [96], + [462], + [120], + [480], + [184], [2048], - [24], - [240], - [256], - [272], + [68], + [672], + [196], + [16], + [80], + [144], [28], - [32], - [320], - [334], + [272], [34], - [384], [40], + [98], + [168], [46], - [462], - [480], - [512], - [576], + [116], [58], [64], + [128], + [134], + [192], + [256], + [320], + [24], + [384], + [512], + [576], [640], - [672], - [68], - [72], - [78], - [80], - [96], - [960], - [98], + [112], ], - "input_a_dtype": [ttnn.float32], + "input_a_dtype": [["ttnn.float32"]], "input_a_layout": [ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], From 1cc047479fe3c99b2b76c8dcdbbd5cdca1df6858 Mon Sep 17 00:00:00 2001 From: umadevimcw Date: Wed, 11 Dec 2024 14:11:05 +0000 Subject: [PATCH 41/87] #15857: Update sweep tests #15857: Update clamp forge sweep tests #15857: Update floor sweep tests #15857: Update log forge sweep tests --- .github/workflows/ttnn-run-sweeps.yaml | 1 + .../sweeps/eltwise/unary/clamp/clamp_forge.py | 139 +++++++++++------ .../sweeps/eltwise/unary/floor/floor_forge.py | 13 +- .../sweeps/eltwise/unary/log/log_forge.py | 4 +- .../unary/logical_not/logical_not_forge.py | 84 ++++++++++ .../sweeps/eltwise/unary/neg/neg_forge.py | 144 ++++++++++++------ .../sweeps/eltwise/unary/rsqrt/rsqrt_forge.py | 9 +- .../sweeps/eltwise/unary/sqrt/sqrt_forge.py | 5 +- 8 files changed, 291 insertions(+), 108 deletions(-) create mode 100644 tests/sweep_framework/sweeps/eltwise/unary/logical_not/logical_not_forge.py diff --git a/.github/workflows/ttnn-run-sweeps.yaml b/.github/workflows/ttnn-run-sweeps.yaml index 703b17ba1c8d..bd3ea75303a8 100644 --- a/.github/workflows/ttnn-run-sweeps.yaml +++ b/.github/workflows/ttnn-run-sweeps.yaml @@ -92,6 +92,7 @@ on: - eltwise.unary.log_sigmoid.log_sigmoid - eltwise.unary.logical_not.logical_not_ - eltwise.unary.logical_not.logical_not + - eltwise.unary.logical_not.logical_not_forge - eltwise.unary.logical_not.logical_not_output - eltwise.unary.logical_not.logical_not_pytorch2 - eltwise.unary.neg.neg_pytorch2 diff --git a/tests/sweep_framework/sweeps/eltwise/unary/clamp/clamp_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/clamp/clamp_forge.py index c43227673db0..ae2f6befea86 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/clamp/clamp_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/clamp/clamp_forge.py @@ -28,52 +28,87 @@ parameters = { "nightly": { - "input_shape": [ - [1, 4800, 512], - [1, 300, 2048], - [320, 1], - [27, 1], - [60, 1], - [1, 256, 5120], - [1, 197, 3072], - [1, 10, 3072], - [1, 3072, 8], - [160], - [128, 1], - [1, 19, 4096], - [240, 1], - [1, 1445, 768], - [1, 64, 5120], - [1, 10, 768], - [1, 201, 3072], - [1, 25, 3072], - [480, 1], - [1, 16, 3072], - [1, 256, 256], - [80], - [1, 16384, 128], - [1, 2048, 768], - [1, 1024, 512], - [1, 197, 4096], - [1, 1024, 640], - [40], - [1, 4096, 256], - [1, 1536], - [1, 256, 1024], - [128], - [1, 7, 18176], - [1, 256, 4096], - [1, 4096, 1280], - [30, 1], - [320], - [1, 256, 1280], - [27], - [640], - [1, 1200, 1280], - [120, 1], - [1, 19200, 256], + "input_specs": [ + {"input_shape": [1, 4800, 512], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 4800, 512], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 300, 2048], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 300, 2048], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [320, 1], "min": 0.000000e00, "max": 3.190000e02}, + {"input_shape": [27, 1], "min": 0.000000e00, "max": 2.600000e01}, + {"input_shape": [60, 1], "min": 0.000000e00, "max": 2.900000e01}, + {"input_shape": [1, 256, 5120], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 256, 5120], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 197, 3072], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 197, 3072], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 10, 3072], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 10, 3072], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 3072, 8], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 3072, 8], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [160], "min": 0.000000e00, "max": 7.900000e01}, + {"input_shape": [128, 1], "min": 0.000000e00, "max": 1.270000e02}, + {"input_shape": [128, 1], "min": 0.000000e00, "max": 1.500000e01}, + {"input_shape": [128, 1], "min": 0.000000e00, "max": 3.100000e01}, + {"input_shape": [128, 1], "min": 0.000000e00, "max": 6.300000e01}, + {"input_shape": [1, 19, 4096], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 19, 4096], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [240, 1], "min": 0.000000e00, "max": 1.190000e02}, + {"input_shape": [1, 1445, 768], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 1445, 768], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 64, 5120], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 64, 5120], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 10, 768], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 10, 768], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 201, 3072], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 201, 3072], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 25, 3072], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 25, 3072], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [480, 1], "min": 0.000000e00, "max": 2.390000e02}, + {"input_shape": [1, 16, 3072], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 16, 3072], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 256, 256], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 256, 256], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [80], "min": 0.000000e00, "max": 3.900000e01}, + {"input_shape": [1, 16384, 128], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 16384, 128], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 2048, 768], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 2048, 768], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 1024, 512], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 1024, 512], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 197, 4096], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 197, 4096], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 1024, 640], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 1024, 640], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [40], "min": 0.000000e00, "max": 1.900000e01}, + {"input_shape": [1, 4096, 256], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 4096, 256], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 1536], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 1536], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 256, 1024], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 256, 1024], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [128], "min": 0.000000e00, "max": 1.270000e02}, + {"input_shape": [128], "min": 0.000000e00, "max": 1.500000e01}, + {"input_shape": [128], "min": 0.000000e00, "max": 3.100000e01}, + {"input_shape": [128], "min": 0.000000e00, "max": 6.300000e01}, + {"input_shape": [1, 7, 18176], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 7, 18176], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 256, 4096], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 256, 4096], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [1, 4096, 1280], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 4096, 1280], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [30, 1], "min": 0.000000e00, "max": 1.400000e01}, + {"input_shape": [320], "min": 0.000000e00, "max": 1.590000e02}, + {"input_shape": [320], "min": 0.000000e00, "max": 3.190000e02}, + {"input_shape": [1, 256, 1280], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 256, 1280], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [27], "min": 0.000000e00, "max": 2.600000e01}, + {"input_shape": [640], "min": 0.000000e00, "max": 3.190000e02}, + {"input_shape": [1, 1200, 1280], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 1200, 1280], "min": -1.000000e00, "max": 1.000000e00}, + {"input_shape": [120, 1], "min": 0.000000e00, "max": 5.900000e01}, + {"input_shape": [1, 19200, 256], "min": -4.000000e00, "max": 4.000000e00}, + {"input_shape": [1, 19200, 256], "min": -1.000000e00, "max": 1.000000e00}, ], - "input_a_dtype": [["ttnn.int32", "ttnn.float32"]], + "input_a_dtype": [ttnn.float32], # [ttnn.int32, ttnn.float32], "input_a_layout": [ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], @@ -95,7 +130,7 @@ def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: # The runner will call this run function with each test vector, and the returned results from this function will be stored. # If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. def run( - input_shape, + input_specs, input_a_dtype, input_a_layout, input_a_memory_config, @@ -107,10 +142,13 @@ def run( torch_input_tensor_a = gen_func_with_cast_tt( partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype - )(input_shape) + )(input_specs["input_shape"]) + + min_val = input_specs.get("min", None) + max_val = input_specs.get("max", None) golden_function = ttnn.get_golden_function(ttnn.clamp) - torch_output_tensor = golden_function(torch_input_tensor_a) + torch_output_tensor = golden_function(torch_input_tensor_a, min_val, max_val) input_tensor_a = ttnn.from_torch( torch_input_tensor_a, @@ -121,8 +159,9 @@ def run( ) start_time = start_measuring_time() - result = ttnn.clamp(input_tensor_a, memory_config=output_memory_config) - output_tensor = ttnn.to_torch(result) + result = ttnn.clamp(input_tensor_a, min=min_val, max=max_val, memory_config=output_memory_config) + # ToDo: Update it once the tensor layout support with rank < 2 is supported in mid of Jan + output_tensor = ttnn.to_torch(result, torch_rank=len(input_specs["input_shape"])) e2e_perf = stop_measuring_time(start_time) return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/floor/floor_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/floor/floor_forge.py index 8e75089b4136..11f179d09341 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/floor/floor_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/floor/floor_forge.py @@ -29,7 +29,7 @@ parameters = { "nightly": { "input_shape": [[197], [19]], - "input_a_dtype": [["ttnn.float32"]], + "input_a_dtype": [ttnn.float32], "input_a_layout": [ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], @@ -37,6 +37,14 @@ } +def mesh_device_fixture(): + device = ttnn.open_device(device_id=0) + assert ttnn.device.is_wormhole_b0(device), "This op is available for Wormhole_B0 only" + yield (device, "Wormhole_B0") + ttnn.close_device(device) + del device + + # Invalidate vector is called during the generation phase where each vector will be passed in. # If invalidated, the vector will still be stored but will be skipped. # Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. @@ -78,7 +86,8 @@ def run( start_time = start_measuring_time() result = ttnn.floor(input_tensor_a, memory_config=output_memory_config) - output_tensor = ttnn.to_torch(result) + # ToDo: Update it once the tensor layout support with rank < 2 is supported in mid of Jan + output_tensor = ttnn.to_torch(result, torch_rank=len(input_shape)) e2e_perf = stop_measuring_time(start_time) return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/log/log_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/log/log_forge.py index 8c7ab9ca6197..5d3c808accac 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/log/log_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/log/log_forge.py @@ -29,7 +29,7 @@ parameters = { "nightly": { "input_shape": [[19, 1], [1, 1], [15, 15]], - "input_a_dtype": [["ttnn.float32"]], + "input_a_dtype": [ttnn.float32], "input_a_layout": [ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], @@ -62,7 +62,7 @@ def run( torch.manual_seed(0) torch_input_tensor_a = gen_func_with_cast_tt( - partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + partial(torch_random, low=1, high=100, dtype=torch.float32), input_a_dtype )(input_shape) golden_function = ttnn.get_golden_function(ttnn.log) diff --git a/tests/sweep_framework/sweeps/eltwise/unary/logical_not/logical_not_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/logical_not/logical_not_forge.py new file mode 100644 index 000000000000..9003296b6bbe --- /dev/null +++ b/tests/sweep_framework/sweeps/eltwise/unary/logical_not/logical_not_forge.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +# TIMEOUT = 30 + +random.seed(0) + + +# Parameters provided to the test vector generator are defined here. +# They are defined as dict-type suites that contain the arguments to the run function as keys, and lists of possible inputs as values. +# Each suite has a key name (in this case "suite_1" and "suite_2") which will associate the test vectors to this specific suite of inputs. +# Developers can create their own generator functions and pass them to the parameters as inputs. + + +parameters = { + "nightly": { + "input_shape": [[1, 23, 40]], + "input_a_dtype": [ttnn.bfloat16], + "input_a_layout": [ttnn.TILE_LAYOUT], + "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], + }, +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT: + return True, "Row Major layout is not supported" + return False, None + + +# This is the run instructions for the test, defined by the developer. +# The run function must take the above-defined parameters as inputs. +# The runner will call this run function with each test vector, and the returned results from this function will be stored. +# If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. +def run( + input_shape, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + *, + device, +) -> list: + torch.manual_seed(0) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + golden_function = ttnn.get_golden_function(ttnn.logical_not) + torch_output_tensor = golden_function(torch_input_tensor_a) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.logical_not(input_tensor_a, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/neg/neg_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/neg/neg_forge.py index db989f0477e7..168fe800e4f7 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/neg/neg_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/neg/neg_forge.py @@ -29,84 +29,131 @@ parameters = { "nightly": { "input_shape": [ - [58, 1, 1], - [1, 16, 1], - [1, 201, 1], - [1, 1024, 1], - [134, 1, 1], [1, 14, 1], - [20, 1, 1], - [1, 256, 1], - [1, 5, 1], - [480, 1, 1], + [1, 5, 1200, 1], + [1, 5, 16, 16], + [1, 16, 32, 1], + [1, 1, 9, 9], + [1, 1, 19, 19], + [8, 920, 1], + [240, 1, 1], + [512, 1, 1], + [1, 32, 32, 64], + [1, 8, 10, 1], + [116, 1, 1], + [1, 12, 1, 1], + [1, 12, 201, 1], + [462, 1, 1], + [78, 1, 1], + [2, 1, 7, 7], + [1, 8, 2048, 1], + [1, 6, 1], + [1, 1, 16, 16], + [1, 6, 1, 1], + [58, 1, 1], + [1, 5, 1024, 1], [1, 7, 1], [960, 1, 1], - [72, 1, 1], [1024, 1, 1], - [1, 16384, 1], [1, 1200, 1], [2, 7, 1], - [32, 1, 1], - [1, 4096, 1], - [256, 1, 1], [1, 64, 1], + [1, 32, 1, 1], + [1, 16, 1, 1], + [1, 12, 197, 1], + [1], + [1, 16, 5, 1], + [1, 1, 1, 2048], + [128, 1, 1], + [1, 19200, 1], + [334, 1, 1], + [672, 1, 1], + [1, 1, 16384, 1], + [40, 1, 1], + [1, 1445, 1], + [80, 1, 1], + [68, 1, 1], + [1, 8, 300, 1], + [8, 100, 1], + [98, 1, 1], + [1, 1, 1, 201], + [320, 1, 1], + [1, 16, 1], + [1, 5, 1], + [134, 1, 1], + [20, 1, 1], + [480, 1, 1], + [1, 16, 197, 1], + [32, 1, 1], + [1, 1, 19200, 1], + [1280, 1, 1], [1, 9, 1], - [1, 5, 16, 16], [640, 1, 1], - [160, 1, 1], - [1, 32, 1, 1], - [144, 1, 1], - [512, 1, 1], - [240, 1, 1], + [2048, 1, 1], + [1, 12, 10, 1], [120, 1, 1], - [200, 1, 1], + [1, 6, 15, 1], + [1, 12, 8, 1], [168, 1, 1], - [1, 32, 32, 64], [3, 1, 1], - [1, 4800, 1], [16, 1, 1], - [28, 1, 1], - [116, 1, 1], - [128, 1, 1], [1, 50, 1], + [920, 1, 1], + [1, 1, 25, 25], + [384, 1, 1], + [1, 8, 1, 1], + [34, 1, 1], + [24, 1, 1], + [1, 8, 256, 1], + [1, 12, 1], + [19, 1], + [1, 71, 7, 32], + [1, 1, 1, 15], + [1, 1, 256, 256], + [1, 25, 1], + [1, 201, 1], + [1, 1024, 1], + [1, 256, 1], + [72, 1, 1], + [576, 1, 1], + [1, 16384, 1], + [192, 1, 1], + [1, 16, 10, 1], + [1, 1, 1, 8], + [1, 4096, 1], + [256, 1, 1], + [1, 1, 14, 14], + [160, 1, 1], + [1, 1, 10, 10], + [144, 1, 1], + [1, 2, 4096, 1], + [1, 1, 6, 6], + [200, 1, 1], + [1, 1, 1, 10], + [1, 4800, 1], + [28, 1, 1], [1, 19, 1], [1, 1, 7, 32], - [1, 19200, 1], - [334, 1, 1], - [462, 1, 1], [1, 8, 1], - [672, 1, 1], - [920, 1, 1], - [78, 1, 1], - [40, 1, 1], [272, 1, 1], - [1, 1445, 1], - [34, 1, 1], [46, 1, 1], - [24, 1, 1], [1, 10, 1], [112, 1, 1], [1, 32, 1], - [1, 6, 1], - [80, 1, 1], - [1, 12, 1], - [1, 197, 1], - [19, 1], - [68, 1, 1], [96, 1, 1], + [1, 2, 4800, 1], + [1, 1, 7, 7], + [1, 197, 1], [1, 1], - [1, 71, 7, 32], + [1, 1, 12, 12], [1, 2048, 1], [14, 1, 1], - [98, 1, 1], [196, 1, 1], [64, 1, 1], - [320, 1, 1], - [1, 25, 1], [1, 300, 1], [184, 1, 1], ], - "input_a_dtype": [["ttnn.float32", "ttnn.int32", "ttnn.bfloat16"]], + "input_a_dtype": [ttnn.float32, ttnn.bfloat16], # ttnn.int32], "input_a_layout": [ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], @@ -155,7 +202,8 @@ def run( start_time = start_measuring_time() result = ttnn.neg(input_tensor_a, memory_config=output_memory_config) - output_tensor = ttnn.to_torch(result) + # ToDo: Update it once the tensor layout support with rank < 2 is supported in mid of Jan + output_tensor = ttnn.to_torch(result, torch_rank=len(input_shape)) e2e_perf = stop_measuring_time(start_time) return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/rsqrt/rsqrt_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/rsqrt/rsqrt_forge.py index aec05e77e1a4..7ea01ad21ab1 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/rsqrt/rsqrt_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/rsqrt/rsqrt_forge.py @@ -86,17 +86,17 @@ [1, 12, 1], [1, 197, 1], [1, 256, 1280], - [1, 1], + # [1, 1], [1, 2048, 1, 1], [1, 2048, 1], [1, 1200, 1280], [1, 300, 1], [1, 4096, 1280], [1, 25, 1], - [1, 1, 1], + # [1, 1, 1], [1, 19200, 256], ], - "input_a_dtype": [["ttnn.bfloat16", "ttnn.float32"]], + "input_a_dtype": [ttnn.bfloat16, ttnn.float32], "input_a_layout": [ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], @@ -145,7 +145,8 @@ def run( start_time = start_measuring_time() result = ttnn.rsqrt(input_tensor_a, memory_config=output_memory_config) - output_tensor = ttnn.to_torch(result) + # ToDo: Update it once the tensor layout support with rank < 2 is supported in mid of Jan + output_tensor = ttnn.to_torch(result, torch_rank=len(input_shape)) e2e_perf = stop_measuring_time(start_time) return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/eltwise/unary/sqrt/sqrt_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/sqrt/sqrt_forge.py index 0480536c8e9e..ec3615e79231 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/sqrt/sqrt_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/sqrt/sqrt_forge.py @@ -74,7 +74,7 @@ [640], [112], ], - "input_a_dtype": [["ttnn.float32"]], + "input_a_dtype": [ttnn.float32], "input_a_layout": [ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG], @@ -123,7 +123,8 @@ def run( start_time = start_measuring_time() result = ttnn.sqrt(input_tensor_a, memory_config=output_memory_config) - output_tensor = ttnn.to_torch(result) + # ToDo: Update it once the tensor layout support with rank < 2 is supported in mid of Jan + output_tensor = ttnn.to_torch(result, torch_rank=len(input_shape)) e2e_perf = stop_measuring_time(start_time) return [check_with_pcc(torch_output_tensor, output_tensor, 0.999), e2e_perf] From b4af6c8df33ff215b7157867374c1ec9e5ede8fe Mon Sep 17 00:00:00 2001 From: Bryan Wilder Field Lozano Date: Wed, 18 Dec 2024 13:42:30 +0530 Subject: [PATCH 42/87] Fix some namespace pollution caused by `using namespace tt::tt_metal` (#16090) --- tt_metal/impl/buffers/buffer.cpp | 4 +- tt_metal/impl/dispatch/debug_tools.hpp | 5 +- ttnn/cpp/ttnn/decorators.hpp | 10 ++-- .../ttnn/distributed/distributed_tensor.cpp | 13 ++-- .../ttnn/distributed/distributed_tensor.hpp | 2 +- ttnn/cpp/ttnn/events.hpp | 7 ++- ttnn/cpp/ttnn/global_semaphore.cpp | 8 +-- ttnn/cpp/ttnn/global_semaphore.hpp | 4 +- .../conv/conv2d/device/conv2d_op.cpp | 2 +- .../conv/conv2d/device/conv2d_op.hpp | 20 +++---- .../conv2d_op_sharded_program_factory.cpp | 2 +- ...onv2d_op_width_sharded_program_factory.cpp | 2 +- .../operations/core/to_dtype/to_dtype_op.hpp | 40 +++++++------ .../concat/device/concat_device_operation.hpp | 6 +- .../copy/device/copy_device_operation.hpp | 9 +-- .../fill_rm/device/fill_rm_op.hpp | 4 +- .../fold/device/fold_device_op.hpp | 6 +- .../indexed_fill/device/indexed_fill_op.hpp | 6 +- .../device/non_zero_indices_op.hpp | 6 +- .../data_movement/pad/device/pad_op.cpp | 7 ++- .../data_movement/pad/device/pad_op.hpp | 4 +- .../pad/device/pad_program_factory.hpp | 16 ++--- .../device/permute_device_operation.hpp | 6 +- .../data_movement/repeat/device/repeat_op.hpp | 4 +- .../repeat/device/repeat_program_factory.hpp | 2 +- .../data_movement/repeat/repeat.cpp | 4 +- .../reshape_on_device/device/reshape_op.hpp | 4 +- .../device/reshape_program_factory.hpp | 4 +- .../reshape_on_device/reshape.cpp | 2 +- .../device/host/reshape_rm_host_prep.cpp | 7 ++- ...interleaved_to_sharded_program_factory.hpp | 2 +- .../interleaved_to_sharded_partial_op.hpp | 6 +- .../interleaved_to_sharded_partial.cpp | 4 +- .../sharded_to_interleaved_partial_op.hpp | 6 +- .../sharded_to_interleaved_partial.cpp | 2 +- .../data_movement/slice/device/slice_op.hpp | 2 +- .../data_movement/split/device/split_op.hpp | 2 +- .../split/device/split_program_factory.hpp | 3 +- .../transpose/device/transpose_op.hpp | 2 +- .../binary/device/binary_device_operation.hpp | 60 +++++++++---------- .../experimental/auto_format/auto_format.hpp | 18 +++--- .../kv_cache/device/update_cache_op.hpp | 12 ++-- .../operations/matmul/device/matmul_op.hpp | 18 +++--- ttnn/cpp/ttnn/tensor/tensor_impl.hpp | 2 +- 44 files changed, 188 insertions(+), 167 deletions(-) diff --git a/tt_metal/impl/buffers/buffer.cpp b/tt_metal/impl/buffers/buffer.cpp index 3502254d8d53..926c5c1d15be 100644 --- a/tt_metal/impl/buffers/buffer.cpp +++ b/tt_metal/impl/buffers/buffer.cpp @@ -405,7 +405,7 @@ CoreType Buffer::core_type() const { } bool Buffer::is_l1() const { - return ::is_l1(buffer_type()); + return tt::tt_metal::is_l1(buffer_type()); } bool Buffer::is_dram() const { return buffer_type() == BufferType::DRAM || buffer_type() == BufferType::TRACE; @@ -544,7 +544,7 @@ tt_metal::ShardSpec from_json_t::operator()(const nlohmann: const auto& shard_mode = from_json(json_object.at("mode")); const auto& physical_shard_shape = from_json>>(json_object.at("physical_shard_shape")); if (physical_shard_shape.has_value()) { - TT_FATAL(shard_mode == ShardMode::LOGICAL, "Physical shard shape can only be provided in logical sharding mode!"); + TT_FATAL(shard_mode == tt::tt_metal::ShardMode::LOGICAL, "Physical shard shape can only be provided in logical sharding mode!"); return tt_metal::ShardSpec{ from_json(json_object.at("grid")), from_json>(json_object.at("shape")), diff --git a/tt_metal/impl/dispatch/debug_tools.hpp b/tt_metal/impl/dispatch/debug_tools.hpp index 99a2372cbf60..5e84b58e43ff 100644 --- a/tt_metal/impl/dispatch/debug_tools.hpp +++ b/tt_metal/impl/dispatch/debug_tools.hpp @@ -17,6 +17,9 @@ void match_device_program_data_with_host_program_data(const char* host_file, con // Dumps host-side CQ data into files. void dump_cqs( - std::ofstream& cq_file, std::ofstream& iq_file, SystemMemoryManager& sysmem_manager, bool dump_raw_data = false); + std::ofstream& cq_file, + std::ofstream& iq_file, + tt::tt_metal::SystemMemoryManager& sysmem_manager, + bool dump_raw_data = false); } // end namespace internal diff --git a/ttnn/cpp/ttnn/decorators.hpp b/ttnn/cpp/ttnn/decorators.hpp index 370195f33d69..e1c5824b32ae 100644 --- a/ttnn/cpp/ttnn/decorators.hpp +++ b/ttnn/cpp/ttnn/decorators.hpp @@ -79,8 +79,8 @@ inline auto create_async_output_tensors( Tensors output_tensors; output_tensors.reserve(std::tuple_size_v); for (auto index = 0; index < std::tuple_size_v; index++) { - output_tensors.emplace_back( - Tensor(operation::get_workers_for_op_output(inputs, optional_inputs, enable_autoformat_device))); + output_tensors.emplace_back(Tensor( + tt::tt_metal::operation::get_workers_for_op_output(inputs, optional_inputs, enable_autoformat_device))); } return output_tensors; } else { @@ -265,7 +265,7 @@ struct registered_operation_t { detail::extract_args_to_vector>(std::forward(args)...); bool enable_autoformat = false; - operation::launch_op( + tt::tt_metal::operation::launch_op( [args...]( const Tensors& input_tensors, const OptionalConstTensors& optional_input_tensors, @@ -311,7 +311,7 @@ struct registered_operation_t { template auto operator()(args_t&&... args) const { tt::log_debug(tt::LogOp, "Started C++ ttnn operation: {}", std::string_view{cpp_fully_qualified_name}); - GraphTracker::instance().track_function_start(cpp_fully_qualified_name, args...); + tt::tt_metal::GraphTracker::instance().track_function_start(cpp_fully_qualified_name, args...); auto output = invoke(std::forward(args)...); // Should every output tensor be tracked? @@ -321,7 +321,7 @@ struct registered_operation_t { } */ - GraphTracker::instance().track_function_end(output); + tt::tt_metal::GraphTracker::instance().track_function_end(output); tt::log_debug(tt::LogOp, "Finished C++ ttnn operation: {}", std::string_view{cpp_fully_qualified_name}); return output; } diff --git a/ttnn/cpp/ttnn/distributed/distributed_tensor.cpp b/ttnn/cpp/ttnn/distributed/distributed_tensor.cpp index 4908413132f4..e8716199a636 100644 --- a/ttnn/cpp/ttnn/distributed/distributed_tensor.cpp +++ b/ttnn/cpp/ttnn/distributed/distributed_tensor.cpp @@ -25,7 +25,9 @@ class ReplicateTensorToMesh : public TensorToMesh { return tensors; } - DistributedTensorConfig config() const override { return DistributedTensorConfig{ReplicateTensor{num_devices_}}; } + tt::tt_metal::DistributedTensorConfig config() const override { + return tt::tt_metal::DistributedTensorConfig{ReplicateTensor{num_devices_}}; + } private: size_t num_devices_ = 0; @@ -39,7 +41,9 @@ class ShardTensorToMesh : public TensorToMesh { return experimental::xtensor::chunk(tensor, num_devices_, shard_dim_); } - DistributedTensorConfig config() const override { return DistributedTensorConfig{ShardTensor{shard_dim_}}; } + tt::tt_metal::DistributedTensorConfig config() const override { + return tt::tt_metal::DistributedTensorConfig{ShardTensor{shard_dim_}}; + } private: size_t num_devices_ = 0; @@ -94,7 +98,7 @@ class ShardTensorTo2dMesh : public TensorToMesh { return tensor_shards; } - DistributedTensorConfig config() const override { + tt::tt_metal::DistributedTensorConfig config() const override { return DistributedTensorConfig{ShardTensor2D{ShardMesh{mesh_shape_.num_rows, mesh_shape_.num_cols}}}; } @@ -178,7 +182,8 @@ std::unique_ptr concat_2d_mesh_to_tensor_composer(MeshDevice& mesh Tensor distribute_tensor(const Tensor& tensor, MeshDevice& mesh_device, TensorToMesh& mapper) { TT_FATAL( - tensor.storage_type() != StorageType::MULTI_DEVICE && tensor.storage_type() != StorageType::MULTI_DEVICE_HOST, + tensor.storage_type() != tt::tt_metal::StorageType::MULTI_DEVICE && + tensor.storage_type() != tt::tt_metal::StorageType::MULTI_DEVICE_HOST, "TensorToMesh does not support multi-device or multi-device host tensors; got storage type: {}", tensor.storage_type()); std::vector tensors = mapper.map(tensor); diff --git a/ttnn/cpp/ttnn/distributed/distributed_tensor.hpp b/ttnn/cpp/ttnn/distributed/distributed_tensor.hpp index 7aaee73caa4c..d8c8b060cf66 100644 --- a/ttnn/cpp/ttnn/distributed/distributed_tensor.hpp +++ b/ttnn/cpp/ttnn/distributed/distributed_tensor.hpp @@ -14,7 +14,7 @@ class TensorToMesh { public: virtual ~TensorToMesh() = default; virtual std::vector map(const Tensor& tensor) = 0; - virtual DistributedTensorConfig config() const = 0; + virtual tt::tt_metal::DistributedTensorConfig config() const = 0; }; // Composer interface that aggregates a multi-device tensor into a host tensor. diff --git a/ttnn/cpp/ttnn/events.hpp b/ttnn/cpp/ttnn/events.hpp index d4c409338c69..8030cd818ca2 100644 --- a/ttnn/cpp/ttnn/events.hpp +++ b/ttnn/cpp/ttnn/events.hpp @@ -17,11 +17,14 @@ struct MultiDeviceEvent { // Single Device APIs std::shared_ptr create_event(Device* device); void record_event( - uint8_t cq_id, const std::shared_ptr& event, const std::vector& sub_device_ids = {}); + uint8_t cq_id, + const std::shared_ptr& event, + const std::vector& sub_device_ids = {}); void wait_for_event(uint8_t cq_id, const std::shared_ptr& event); // Multi Device APIs MultiDeviceEvent create_event(MeshDevice* mesh_device); -void record_event(uint8_t cq_id, const MultiDeviceEvent& event, const std::vector& sub_device_ids = {}); +void record_event( + uint8_t cq_id, const MultiDeviceEvent& event, const std::vector& sub_device_ids = {}); void wait_for_event(uint8_t cq_id, const MultiDeviceEvent& event); } // namespace ttnn::events diff --git a/ttnn/cpp/ttnn/global_semaphore.cpp b/ttnn/cpp/ttnn/global_semaphore.cpp index 777fe337b718..cb5c158fa621 100644 --- a/ttnn/cpp/ttnn/global_semaphore.cpp +++ b/ttnn/cpp/ttnn/global_semaphore.cpp @@ -33,9 +33,9 @@ std::shared_ptr create_global_semaphore( return global_semaphore; } -DeviceAddr get_global_semaphore_address(const std::shared_ptr& global_semaphore) { +tt::tt_metal::DeviceAddr get_global_semaphore_address(const std::shared_ptr& global_semaphore) { auto* device = global_semaphore->device(); - DeviceAddr address = 0; + tt::tt_metal::DeviceAddr address = 0; device->push_work([&global_semaphore, &address] { address = global_semaphore->address(); }, /*blocking=*/true); return address; } @@ -70,8 +70,8 @@ MultiDeviceGlobalSemaphore create_global_semaphore( } return multi_device_global_semaphore; } -std::vector get_global_semaphore_address(const MultiDeviceGlobalSemaphore& global_semaphore) { - std::vector addresses(global_semaphore.global_semaphores.size()); +std::vector get_global_semaphore_address(const MultiDeviceGlobalSemaphore& global_semaphore) { + std::vector addresses(global_semaphore.global_semaphores.size()); const auto& global_semaphores = global_semaphore.global_semaphores; for (uint32_t i = 0; i < global_semaphores.size(); ++i) { const auto& global_semaphore = global_semaphores[i]; diff --git a/ttnn/cpp/ttnn/global_semaphore.hpp b/ttnn/cpp/ttnn/global_semaphore.hpp index 121e8c03cdf0..b9aca1bb2b35 100644 --- a/ttnn/cpp/ttnn/global_semaphore.hpp +++ b/ttnn/cpp/ttnn/global_semaphore.hpp @@ -22,7 +22,7 @@ std::shared_ptr create_global_semaphore( uint32_t initial_value, BufferType buffer_type = BufferType::L1, tt::stl::Span sub_device_ids = {}); -DeviceAddr get_global_semaphore_address(const std::shared_ptr& global_semaphore); +tt::tt_metal::DeviceAddr get_global_semaphore_address(const std::shared_ptr& global_semaphore); void reset_global_semaphore_value( const std::shared_ptr& global_semaphore, uint32_t reset_value, @@ -35,7 +35,7 @@ MultiDeviceGlobalSemaphore create_global_semaphore( uint32_t initial_value, BufferType buffer_type = BufferType::L1, tt::stl::Span sub_device_ids = {}); -std::vector get_global_semaphore_address(const MultiDeviceGlobalSemaphore& global_semaphore); +std::vector get_global_semaphore_address(const MultiDeviceGlobalSemaphore& global_semaphore); void reset_global_semaphore_value( const MultiDeviceGlobalSemaphore& global_semaphore, uint32_t reset_value, diff --git a/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op.cpp b/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op.cpp index 4f3d8d5c2ef6..9a06d506883c 100644 --- a/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op.cpp +++ b/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op.cpp @@ -72,7 +72,7 @@ Tensor optimized_conv_new(const Tensor& a, const Tensor &b, std::optional output_tensors = {Tensor(operation::get_workers_for_op_output({a, b}))}; + std::vector output_tensors = {Tensor(tt::tt_metal::operation::get_workers_for_op_output({a, b}))}; operation::launch_op( [sliding_window_config, output_channels, groups, untilize_out, fuse_relu, parallelization_config, block_config, memory_config, dtype, input_tensor_shape, use_shallow_conv_variant, compute_kernel_config, enable_act_double_buffer, enable_weights_double_buffer, enable_split_reader, enable_subblock_padding, use_non_tile_height] (const std::vector& input_tensors, const std::vector>& optional_input_tensors, const std::vector>& optional_output_tensors) mutable -> std::vector { diff --git a/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op.hpp b/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op.hpp index 17d22b48a4ba..a31b257fc8d6 100644 --- a/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op.hpp +++ b/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op.hpp @@ -43,14 +43,14 @@ struct OptimizedConvBlockConfig { uint32_t out_subblock_w_ntiles; }; -operation::ProgramWithCallbacks multi_core_optimized_conv_sharded_v2_new(const Tensor& a, const Tensor &b, const std::optional& bias, +tt::tt_metal::operation::ProgramWithCallbacks multi_core_optimized_conv_sharded_v2_new(const Tensor& a, const Tensor &b, const std::optional& bias, const sliding_window::SlidingWindowConfig& sliding_window_config, uint32_t output_channels, uint32_t groups, bool untilize_out, bool fuse_relu, const OptimizedConvParallelizationConfig& parallelization_config, const OptimizedConvBlockConfig& block_config, - DataType dtype, + tt::tt_metal::DataType dtype, std::array input_tensor_shape, bool use_shallow_conv_variant, std::optional compute_kernel_config, @@ -69,8 +69,8 @@ struct OptimizedConvNew { const uint32_t output_channels; const uint32_t groups; bool untilize_out, has_bias, fuse_relu; - MemoryConfig memory_config; - const DataType dtype; + tt::tt_metal::MemoryConfig memory_config; + const tt::tt_metal::DataType dtype; std::array input_tensor_shape; // For sharded input, input tensor shape is nonsense bool use_shallow_conv_variant; const DeviceComputeKernelConfig compute_kernel_config; @@ -85,8 +85,8 @@ struct OptimizedConvNew { bool has_bias, bool fuse_relu, const OptimizedConvParallelizationConfig& p_config, const OptimizedConvBlockConfig& b_config, - MemoryConfig memory_config, - DataType dtype, + tt::tt_metal::MemoryConfig memory_config, + tt::tt_metal::DataType dtype, std::array input_tensor_shape, bool use_shallow_conv_variant, const DeviceComputeKernelConfig compute_kernel_config, bool enable_act_double_buffer, bool enable_weights_double_buffer, bool enable_split_reader, bool enable_subblock_padding, bool use_non_tile_height) : output_channels(output_channels), @@ -109,9 +109,9 @@ struct OptimizedConvNew { void validate(const std::vector& input_tensors, const std::vector>& optional_input_tensors) const; std::vector compute_output_specs(const std::vector& input_tensors) const; - operation::ProgramWithCallbacks create_program(const std::vector& input_tensors, const std::vector>& optional_input_tensors, std::vector &output_tensors) const; + tt::tt_metal::operation::ProgramWithCallbacks create_program(const std::vector& input_tensors, const std::vector>& optional_input_tensors, std::vector &output_tensors) const; - operation::OpPerformanceModel create_op_performance_model(const std::vector& input_tensors, const std::vector>& optional_input_tensors, const std::vector &output_tensors) const; + tt::tt_metal::operation::OpPerformanceModel create_op_performance_model(const std::vector& input_tensors, const std::vector>& optional_input_tensors, const std::vector &output_tensors) const; static constexpr auto attribute_names = std::make_tuple( "parallelization_config", @@ -154,8 +154,8 @@ Tensor optimized_conv_new(const Tensor& a, const Tensor &b, std::optional input_tensor_shape, bool use_shallow_conv_variant, const DeviceComputeKernelConfig& compute_kernel_config, diff --git a/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op_sharded_program_factory.cpp b/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op_sharded_program_factory.cpp index 83a9c06ab0ad..6bac502d0d36 100644 --- a/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op_sharded_program_factory.cpp +++ b/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op_sharded_program_factory.cpp @@ -37,7 +37,7 @@ const uint32_t untilized_padded_out_cb = CBIndex::c_28; } // namespace CMAKE_UNIQUE_NAMESPACE } // namespace -operation::ProgramWithCallbacks multi_core_optimized_conv_width_sharded_v2_impl( +tt::tt_metal::operation::ProgramWithCallbacks multi_core_optimized_conv_width_sharded_v2_impl( tt_metal::Program& program, const Tensor& a, const Tensor& b, diff --git a/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op_width_sharded_program_factory.cpp b/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op_width_sharded_program_factory.cpp index 9e6b7d9130e1..62696da35ed0 100644 --- a/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op_width_sharded_program_factory.cpp +++ b/ttnn/cpp/ttnn/operations/conv/conv2d/device/conv2d_op_width_sharded_program_factory.cpp @@ -19,7 +19,7 @@ namespace conv2d { using namespace tt; -operation::ProgramWithCallbacks multi_core_optimized_conv_width_sharded_v2_impl( +tt::tt_metal::operation::ProgramWithCallbacks multi_core_optimized_conv_width_sharded_v2_impl( tt_metal::Program& program, const Tensor& a, const Tensor& b, diff --git a/ttnn/cpp/ttnn/operations/core/to_dtype/to_dtype_op.hpp b/ttnn/cpp/ttnn/operations/core/to_dtype/to_dtype_op.hpp index 4fa171be016a..34d13e66c139 100644 --- a/ttnn/cpp/ttnn/operations/core/to_dtype/to_dtype_op.hpp +++ b/ttnn/cpp/ttnn/operations/core/to_dtype/to_dtype_op.hpp @@ -24,17 +24,17 @@ inline Tensor convert_to_cpp_supported_dtype(const Tensor& input_tensor) { auto input_dtype = input_tensor.get_dtype(); auto buffer = std::visit( - [](auto&& storage) -> std::variant { + [](auto&& storage) -> std::variant { using T = std::decay_t; - if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { return storage.buffer; - } else if constexpr (std::is_same_v) { + } else if constexpr (std::is_same_v) { TT_THROW("Device input_tensor cannot be converted to torch"); - } else if constexpr (std::is_same_v) { + } else if constexpr (std::is_same_v) { return storage.buffer; - } else if constexpr (std::is_same_v) { + } else if constexpr (std::is_same_v) { TT_THROW("Tensor with MultiDeviceStorage cannot be converted to torch"); - } else if constexpr (std::is_same_v) { + } else if constexpr (std::is_same_v) { TT_THROW( "Tensor MultiDeviceHostStorage cannot be converted to torch directly. Use composer(..) " "functionality."); @@ -49,29 +49,33 @@ inline Tensor convert_to_cpp_supported_dtype(const Tensor& input_tensor) { std::holds_alternative(buffer), "Unexpected type {}", tt::stl::get_active_type_name_in_variant(buffer)); - auto uint32_data = std::get>(std::get(buffer)).get(); + auto uint32_data = + std::get>(std::get(buffer)) + .get(); auto float_unpacked_data = unpack_bfp8_tiles_into_float_vec(uint32_data, /*row_major_output=*/false, /*is_exp_a=*/false); - buffer = owned_buffer::create(std::move(float_unpacked_data)); + buffer = tt::tt_metal::owned_buffer::create(std::move(float_unpacked_data)); input_dtype = DataType::FLOAT32; } else if (input_dtype == DataType::BFLOAT4_B) { TT_ASSERT( std::holds_alternative(buffer), "Unexpected type {}", tt::stl::get_active_type_name_in_variant(buffer)); - auto uint32_data = std::get>(std::get(buffer)).get(); + auto uint32_data = + std::get>(std::get(buffer)) + .get(); auto float_unpacked_data = unpack_bfp4_tiles_into_float_vec(uint32_data, /*row_major_output=*/false, /*is_exp_a=*/false); - buffer = owned_buffer::create(std::move(float_unpacked_data)); + buffer = tt::tt_metal::owned_buffer::create(std::move(float_unpacked_data)); input_dtype = DataType::FLOAT32; } return std::visit( [&](auto&& buffer) -> Tensor { using T = std::decay_t; - if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { return Tensor{OwnedStorage{buffer}, input_tensor.get_shape(), input_dtype, input_tensor.get_layout()}; - } else if constexpr (std::is_same_v) { + } else if constexpr (std::is_same_v) { return Tensor{ BorrowedStorage{buffer, []() {}, []() {}}, input_tensor.get_shape(), @@ -85,7 +89,7 @@ inline Tensor convert_to_cpp_supported_dtype(const Tensor& input_tensor) { } template -inline std::vector cast(const borrowed_buffer::Buffer& input_buffer) { +inline std::vector cast(const tt::tt_metal::borrowed_buffer::Buffer& input_buffer) { std::vector output_vector(input_buffer.size()); for (auto index = 0; index < input_buffer.size(); ++index) { auto convert_value = [](auto&& value) { @@ -105,14 +109,14 @@ inline std::vector cast(const borrowed_buffer::Buffer& input_buffer) template Tensor create_owned_tensor(std::vector&& data, const Shape& shape, DataType data_type, Layout layout) { - auto buffer = owned_buffer::create(std::move(data)); + auto buffer = tt::tt_metal::owned_buffer::create(std::move(data)); auto storage = OwnedStorage{std::move(buffer)}; return Tensor(std::move(storage), shape, data_type, layout); } template inline Tensor create_tensor_from_buffer( - const borrowed_buffer::Buffer& input_buffer, + const tt::tt_metal::borrowed_buffer::Buffer& input_buffer, const Shape& shape, const Layout& input_layout, const DataType& dtype) { @@ -140,15 +144,15 @@ inline Tensor create_tensor_from_buffer( case DataType::BFLOAT8_B: case DataType::BFLOAT4_B: { auto data = cast(input_buffer); - auto buffer = owned_buffer::create(std::move(data)); + auto buffer = tt::tt_metal::owned_buffer::create(std::move(data)); auto tensor = Tensor(OwnedStorage{std::move(buffer)}, shape, DataType::FLOAT32, Layout::ROW_MAJOR).to(Layout::TILE); - auto output_float_data = owned_buffer::get_as(tensor).get(); + auto output_float_data = tt::tt_metal::owned_buffer::get_as(tensor).get(); auto output_packed_data = dtype == DataType::BFLOAT8_B ? pack_fp32_vec_as_bfp8_tiles(output_float_data, /*row_major_input=*/false, /*is_exp_a=*/false) : pack_fp32_vec_as_bfp4_tiles(output_float_data, /*row_major_input=*/false, /*is_exp_a=*/false); - auto output_buffer = owned_buffer::create(std::move(output_packed_data)); + auto output_buffer = tt::tt_metal::owned_buffer::create(std::move(output_packed_data)); return Tensor( OwnedStorage{std::move(output_buffer)}, shape, dtype, Layout::TILE); // has to be in tile layout } diff --git a/ttnn/cpp/ttnn/operations/data_movement/concat/device/concat_device_operation.hpp b/ttnn/cpp/ttnn/operations/data_movement/concat/device/concat_device_operation.hpp index 169a4e7818f9..57715ad89baf 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/concat/device/concat_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/concat/device/concat_device_operation.hpp @@ -14,11 +14,11 @@ enum class ConcatOpParallelizationStrategy { MULTI_CORE, SHARDED_MULTI_CORE }; struct ConcatDeviceOperation { uint32_t dim; unsigned int groups; - const MemoryConfig output_mem_config; + const tt::tt_metal::MemoryConfig output_mem_config; void validate(const std::vector& input_tensors) const; std::vector compute_output_shapes(const std::vector& input_tensors) const; std::vector create_output_tensors(const std::vector& input_tensors) const; - operation::ProgramWithCallbacks create_program( + tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; ConcatOpParallelizationStrategy get_parallelization_strategy(const std::vector& input_tensors) const; }; @@ -29,6 +29,6 @@ Tensor concat_impl( const std::vector& input_tensors, const std::int64_t dim = 0, unsigned int groups = 1, - const MemoryConfig& output_mem_config = operation::DEFAULT_OUTPUT_MEMORY_CONFIG); + const tt::tt_metal::MemoryConfig& output_mem_config = tt::tt_metal::operation::DEFAULT_OUTPUT_MEMORY_CONFIG); } // namespace ttnn::operations::data_movement diff --git a/ttnn/cpp/ttnn/operations/data_movement/copy/device/copy_device_operation.hpp b/ttnn/cpp/ttnn/operations/data_movement/copy/device/copy_device_operation.hpp index cdc952d01c40..525e0ba3233d 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/copy/device/copy_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/copy/device/copy_device_operation.hpp @@ -17,8 +17,8 @@ namespace ttnn::operations::data_movement { enum class CopyOpParallelizationStrategy { MULTI_CORE }; struct CopyDeviceOperation { - const MemoryConfig output_mem_config; - const DataType output_dtype; + const tt::tt_metal::MemoryConfig output_mem_config; + const tt::tt_metal::DataType output_dtype; void validate_with_output_tensors( const std::vector& input_tensors, const std::vector>& output_tensors) const; @@ -26,11 +26,12 @@ struct CopyDeviceOperation { std::vector create_output_tensors( const std::vector& input_tensors, const std::vector>& output_tensors) const; - operation::ProgramWithCallbacks create_program( + tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; CopyOpParallelizationStrategy get_parallelization_strategy(const std::vector& input_tensors) const; }; -operation::ProgramWithCallbacks copy_multi_core(const Tensor& input, const Tensor& output, bool backwards = false); +tt::tt_metal::operation::ProgramWithCallbacks copy_multi_core( + const Tensor& input, const Tensor& output, bool backwards = false); } // namespace ttnn::operations::data_movement diff --git a/ttnn/cpp/ttnn/operations/data_movement/fill_rm/device/fill_rm_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/fill_rm/device/fill_rm_op.hpp index 20e3ff3d446c..d644f7de832c 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/fill_rm/device/fill_rm_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/fill_rm/device/fill_rm_op.hpp @@ -23,12 +23,12 @@ namespace ttnn::operations::data_movement { struct FillRM { uint32_t N, C, H, W, hFill, wFill; float val_hi, val_lo; - const MemoryConfig output_mem_config; + const tt::tt_metal::MemoryConfig output_mem_config; void validate(const std::vector& input_tensors) const; std::vector compute_output_shapes(const std::vector& input_tensors) const; std::vector create_output_tensors(const std::vector& input_tensors) const; - operation::ProgramWithCallbacks create_program( + tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; }; diff --git a/ttnn/cpp/ttnn/operations/data_movement/fold/device/fold_device_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/fold/device/fold_device_op.hpp index c2ccc32f9000..202292dbfb9c 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/fold/device/fold_device_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/fold/device/fold_device_op.hpp @@ -29,8 +29,8 @@ struct Fold { struct SingleCore { struct shared_variables_t { - KernelHandle reader_kernel_id; - KernelHandle writer_kernel_id; + tt::tt_metal::KernelHandle reader_kernel_id; + tt::tt_metal::KernelHandle writer_kernel_id; }; using cached_program_t = ttnn::device_operation::CachedProgram; @@ -48,7 +48,7 @@ struct Fold { struct MultiCore { struct shared_variables_t { - KernelHandle writer_kernel_id; + tt::tt_metal::KernelHandle writer_kernel_id; uint32_t stride_h; uint32_t stride_w; uint32_t cb_src0; diff --git a/ttnn/cpp/ttnn/operations/data_movement/indexed_fill/device/indexed_fill_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/indexed_fill/device/indexed_fill_op.hpp index 225702b2f5ce..83936dee5ea7 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/indexed_fill/device/indexed_fill_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/indexed_fill/device/indexed_fill_op.hpp @@ -10,16 +10,16 @@ namespace ttnn::operations::data_movement { struct IndexedFill { - const MemoryConfig output_mem_config; + const tt::tt_metal::MemoryConfig output_mem_config; const int64_t dim; void validate(const std::vector& input_tensors) const; std::vector compute_output_shapes(const std::vector& input_tensors) const; std::vector create_output_tensors(const std::vector& input_tensors) const; - operation::ProgramWithCallbacks create_program( + tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; }; -operation::ProgramWithCallbacks indexed_fill_multi_core( +tt::tt_metal::operation::ProgramWithCallbacks indexed_fill_multi_core( const Tensor& batch_ids, const Tensor& input_a, const Tensor& input_b, const Tensor& output); } // namespace ttnn::operations::data_movement diff --git a/ttnn/cpp/ttnn/operations/data_movement/non_zero_indices/device/non_zero_indices_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/non_zero_indices/device/non_zero_indices_op.hpp index cdb1439f118c..4c45dd6be990 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/non_zero_indices/device/non_zero_indices_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/non_zero_indices/device/non_zero_indices_op.hpp @@ -17,15 +17,15 @@ namespace ttnn { namespace operations::data_movement { struct NonZeroIndices { - const MemoryConfig output_mem_config; + const tt::tt_metal::MemoryConfig output_mem_config; void validate(const std::vector& input_tensors) const; std::vector compute_output_shapes(const std::vector& input_tensors) const; std::vector create_output_tensors(const std::vector& input_tensors) const; - operation::ProgramWithCallbacks create_program( + tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; }; -operation::ProgramWithCallbacks non_zero_indices_single_core( +tt::tt_metal::operation::ProgramWithCallbacks non_zero_indices_single_core( const Tensor& input, const Tensor& out_num_indices, const Tensor& out_indices); } // namespace operations::data_movement diff --git a/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_op.cpp b/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_op.cpp index ef91a8bd3774..fc75c0ae5448 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_op.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_op.cpp @@ -16,9 +16,12 @@ void Pad::validate_with_output_tensors( auto padded_rank = input_tensor.padded_shape().rank(); TT_FATAL(logical_rank == padded_rank, "ttnn.pad: logical and padded shapes must have the same rank"); TT_FATAL(input_tensor.logical_shape().rank() <= 4, "ttnn.pad: input tensor rank currently must be 4 or less"); - TT_FATAL(input_tensor.storage_type() == StorageType::DEVICE, "Operand to pad needs to be on device!"); + TT_FATAL(input_tensor.storage_type() == tt::tt_metal::StorageType::DEVICE, "Operand to pad needs to be on device!"); TT_FATAL(input_tensor.buffer() != nullptr, "Operand to pad needs to be allocated in a buffer on device!"); - TT_FATAL(input_tensor.get_layout() == Layout::TILE || input_tensor.get_layout() == Layout::ROW_MAJOR, "Error"); + TT_FATAL( + input_tensor.get_layout() == tt::tt_metal::Layout::TILE || + input_tensor.get_layout() == tt::tt_metal::Layout::ROW_MAJOR, + "Error"); if (input_tensor.get_layout() == Layout::TILE) { TT_FATAL( (this->input_tensor_start[0] == 0 && this->input_tensor_start[1] == 0 && this->input_tensor_start[2] == 0 && diff --git a/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_op.hpp index 8390ed94896a..77462979b45b 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_op.hpp @@ -15,7 +15,7 @@ struct Pad { const tt::tt_metal::LegacyShape output_tensor_shape; const ttnn::SimpleShape input_tensor_start; const float pad_value; - const MemoryConfig output_mem_config; + const tt::tt_metal::MemoryConfig output_mem_config; const bool use_multicore; void validate_with_output_tensors( @@ -23,7 +23,7 @@ struct Pad { std::vector compute_output_shapes(const std::vector& input_tensors) const; std::vector create_output_tensors( const std::vector& input_tensors, const std::vector>& output_tensors) const; - operation::ProgramWithCallbacks create_program( + tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; static constexpr auto attribute_names = std::forward_as_tuple( "output_tensor_shape", "input_tensor_start", "pad_value", "output_mem_config", "use_multicore"); diff --git a/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_program_factory.hpp b/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_program_factory.hpp index db5236092b38..e0bc3ba78bfd 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_program_factory.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/pad/device/pad_program_factory.hpp @@ -6,56 +6,56 @@ namespace ttnn::operations::data_movement::detail { -operation::ProgramWithCallbacks pad_rm_reader_writer( +tt::tt_metal::operation::ProgramWithCallbacks pad_rm_reader_writer( const Tensor& a, Tensor& output, const tt::tt_metal::LegacyShape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, const float pad_value); -operation::ProgramWithCallbacks pad_rm_opt( +tt::tt_metal::operation::ProgramWithCallbacks pad_rm_opt( const Tensor& a, Tensor& output, const Shape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, const float pad_value); -operation::ProgramWithCallbacks pad_rm( +tt::tt_metal::operation::ProgramWithCallbacks pad_rm( const Tensor& a, Tensor& output, const Shape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, const float pad_value); -operation::ProgramWithCallbacks pad_tile( +tt::tt_metal::operation::ProgramWithCallbacks pad_tile( const Tensor& a, Tensor& output, const tt::tt_metal::LegacyShape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, const float pad_value); -operation::ProgramWithCallbacks pad_rm_reader_writer_multi_core( +tt::tt_metal::operation::ProgramWithCallbacks pad_rm_reader_writer_multi_core( const Tensor& a, Tensor& output, const tt::tt_metal::LegacyShape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, const float pad_value); -operation::ProgramWithCallbacks pad_rm_reader_writer_multi_core_v2( +tt::tt_metal::operation::ProgramWithCallbacks pad_rm_reader_writer_multi_core_v2( const Tensor& a, Tensor& output, const tt::tt_metal::LegacyShape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, const float pad_value); -operation::ProgramWithCallbacks pad_rm_sharded_height_only( +tt::tt_metal::operation::ProgramWithCallbacks pad_rm_sharded_height_only( const Tensor& a, Tensor& output, const tt::tt_metal::LegacyShape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, const float pad_value); -operation::ProgramWithCallbacks pad_rm_sharded_width_only( +tt::tt_metal::operation::ProgramWithCallbacks pad_rm_sharded_width_only( const Tensor& a, Tensor& output, const tt::tt_metal::LegacyShape& output_tensor_shape, diff --git a/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.hpp b/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.hpp index 05e251e8ca89..f925741f88eb 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/permute/device/permute_device_operation.hpp @@ -33,9 +33,9 @@ struct PermuteDeviceOperation { struct MultiCoreRowInvariant { // Shared variables are the variables that are shared between the create and override_runtime_arguments methods struct shared_variables_t { - KernelHandle unary_reader_kernel_id; - KernelHandle unary_writer_kernel_id; - CoreRangeSet core_range; + tt::tt_metal::KernelHandle unary_reader_kernel_id; + tt::tt_metal::KernelHandle unary_writer_kernel_id; + tt::tt_metal::CoreRangeSet core_range; }; using cached_program_t = ttnn::device_operation::CachedProgram; diff --git a/ttnn/cpp/ttnn/operations/data_movement/repeat/device/repeat_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/repeat/device/repeat_op.hpp index a49fe8306b77..44a854dcb350 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/repeat/device/repeat_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/repeat/device/repeat_op.hpp @@ -12,12 +12,12 @@ namespace ttnn::operations::data_movement { struct RepeatDeviceOperation { const uint32_t repeat_dim; const uint32_t num_repeats; - const MemoryConfig output_mem_config; + const tt::tt_metal::MemoryConfig output_mem_config; void validate(const std::vector& input_tensors) const; std::vector compute_output_shapes(const std::vector& input_tensors) const; std::vector create_output_tensors(const std::vector& input_tensors) const; - operation::ProgramWithCallbacks create_program( + tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; }; diff --git a/ttnn/cpp/ttnn/operations/data_movement/repeat/device/repeat_program_factory.hpp b/ttnn/cpp/ttnn/operations/data_movement/repeat/device/repeat_program_factory.hpp index 0af912eec0f9..70a08c8624d0 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/repeat/device/repeat_program_factory.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/repeat/device/repeat_program_factory.hpp @@ -7,7 +7,7 @@ namespace ttnn::operations::data_movement::detail { -operation::ProgramWithCallbacks repeat_multi_core( +tt::tt_metal::operation::ProgramWithCallbacks repeat_multi_core( const Tensor& input_tensor, const uint32_t repeat_dim, const uint32_t num_repeats, const Tensor& output); } // namespace ttnn::operations::data_movement::detail diff --git a/ttnn/cpp/ttnn/operations/data_movement/repeat/repeat.cpp b/ttnn/cpp/ttnn/operations/data_movement/repeat/repeat.cpp index b39b929d4bd4..1918167da4e4 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/repeat/repeat.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/repeat/repeat.cpp @@ -31,8 +31,8 @@ ttnn::Tensor RepeatOperation::invoke( repeated_logical_shape[dim] *= repeat_dims[dim]; } - std::vector output_tensors = {Tensor(operation::get_workers_for_op_output({input_tensor}))}; - operation::launch_op( + std::vector output_tensors = {Tensor(tt::tt_metal::operation::get_workers_for_op_output({input_tensor}))}; + tt::tt_metal::operation::launch_op( [&input_rank, &input_tensor, &repeat_dims, &memory_config_arg, &padded_input_shape]( const std::vector& input_tensors, const std::vector>& optional_input_tensors, diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_op.hpp index b76ca5c38fe9..0895ccaddf7f 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_op.hpp @@ -11,12 +11,12 @@ namespace ttnn::operations::data_movement { struct ReshapeDeviceOperation { const ttnn::Shape output_shape; - const MemoryConfig output_mem_config; + const tt::tt_metal::MemoryConfig output_mem_config; void validate(const std::vector& input_tensors) const; std::vector compute_output_shapes(const std::vector& input_tensors) const; std::vector create_output_tensors(const std::vector& input_tensors) const; - operation::ProgramWithCallbacks create_program( + tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; }; diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_program_factory.hpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_program_factory.hpp index b0164f713981..691b71d89095 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_program_factory.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/device/reshape_program_factory.hpp @@ -6,7 +6,7 @@ namespace ttnn::operations::data_movement::detail { -operation::ProgramWithCallbacks reshape_tile_single_core(const Tensor& a, Tensor& output); -operation::ProgramWithCallbacks reshape_rm_multi_core(const Tensor& a, Tensor& output); +tt::tt_metal::operation::ProgramWithCallbacks reshape_tile_single_core(const Tensor& a, Tensor& output); +tt::tt_metal::operation::ProgramWithCallbacks reshape_rm_multi_core(const Tensor& a, Tensor& output); } // namespace ttnn::operations::data_movement::detail diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.cpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.cpp index 0910eb284cfa..e326f4895da3 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_on_device/reshape.cpp @@ -74,7 +74,7 @@ ttnn::Tensor ReshapeOperation::invoke( return detail::manual_insertion( (tt::tt_metal::Tensor)input_tensor, output_shape, input_tensor.device(), output_mem_config); } - std::vector output_tensors = {Tensor(operation::get_workers_for_op_output({input_tensor}))}; + std::vector output_tensors = {Tensor(tt::tt_metal::operation::get_workers_for_op_output({input_tensor}))}; return operation::run(ReshapeDeviceOperation{output_shape, output_mem_config}, {input_tensor}).at(0); } diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/device/host/reshape_rm_host_prep.cpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/device/host/reshape_rm_host_prep.cpp index e46916bf9932..3de5030a2273 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/device/host/reshape_rm_host_prep.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/device/host/reshape_rm_host_prep.cpp @@ -29,7 +29,8 @@ namespace ttnn::operations::data_movement::rm_reshape{ -operation::ProgramWithCallbacks rm_reshape_preparer_single_risk(const Tensor& input, const Tensor& output) { +tt::tt_metal::operation::ProgramWithCallbacks rm_reshape_preparer_single_risk( + const Tensor& input, const Tensor& output) { tt::tt_metal::Program program = tt::tt_metal::CreateProgram(); //get datum size tt::DataFormat cb_data_format = tt::tt_metal::datatype_to_dataformat_converter(input.get_dtype()); @@ -145,7 +146,7 @@ operation::ProgramWithCallbacks rm_reshape_preparer_single_risk(const Tensor& in } auto override_runtime_args_callback = [reader_kernel_id, compute_with_storage_grid_size]( const void* operation, - const Program& program, + const tt::tt_metal::Program& program, const std::vector& input_tensors, const std::vector>&, const std::vector& output_tensors) { @@ -209,7 +210,7 @@ operation::ProgramWithCallbacks rm_reshape_preparer_single_risk(const Tensor& in return {.program = std::move(program)}; } -operation::ProgramWithCallbacks rm_reshape_preparer(const Tensor& input, const Tensor& output) { +tt::tt_metal::operation::ProgramWithCallbacks rm_reshape_preparer(const Tensor& input, const Tensor& output) { return rm_reshape_preparer_single_risk(input, output); } diff --git a/ttnn/cpp/ttnn/operations/data_movement/sharded/interleaved_to_sharded/device/interleaved_to_sharded_program_factory.hpp b/ttnn/cpp/ttnn/operations/data_movement/sharded/interleaved_to_sharded/device/interleaved_to_sharded_program_factory.hpp index 7d3b71af80c2..78e3c014c350 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/sharded/interleaved_to_sharded/device/interleaved_to_sharded_program_factory.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/sharded/interleaved_to_sharded/device/interleaved_to_sharded_program_factory.hpp @@ -9,6 +9,6 @@ namespace ttnn::operations::data_movement::detail { -operation::ProgramWithCallbacks interleaved_to_sharded_multi_core(const Tensor &a, const Tensor &output, bool keep_l1_aligned = false, uint32_t num_slices = 1, uint32_t slice_index = 0); +tt::tt_metal::operation::ProgramWithCallbacks interleaved_to_sharded_multi_core(const Tensor &a, const Tensor &output, bool keep_l1_aligned = false, uint32_t num_slices = 1, uint32_t slice_index = 0); } diff --git a/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/interleaved_to_sharded_partial/device/interleaved_to_sharded_partial_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/interleaved_to_sharded_partial/device/interleaved_to_sharded_partial_op.hpp index 9b5b840ed608..192eefab6242 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/interleaved_to_sharded_partial/device/interleaved_to_sharded_partial_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/interleaved_to_sharded_partial/device/interleaved_to_sharded_partial_op.hpp @@ -16,13 +16,13 @@ struct InterleavedToShardedPartialDeviceOperation { const tt::tt_metal::ShardSpec shard_spec; const uint32_t num_slices; const uint32_t slice_index; - const MemoryConfig output_mem_config; - const DataType output_dtype; + const tt::tt_metal::MemoryConfig output_mem_config; + const tt::tt_metal::DataType output_dtype; void validate(const std::vector& input_tensors) const; std::vector compute_output_shapes(const std::vector& input_tensors) const; std::vector create_output_tensors(const std::vector& input_tensors) const; - operation::ProgramWithCallbacks create_program( + tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; static constexpr auto attribute_names = diff --git a/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/interleaved_to_sharded_partial/interleaved_to_sharded_partial.cpp b/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/interleaved_to_sharded_partial/interleaved_to_sharded_partial.cpp index 7ac67f767a8b..fe4d71335e78 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/interleaved_to_sharded_partial/interleaved_to_sharded_partial.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/interleaved_to_sharded_partial/interleaved_to_sharded_partial.cpp @@ -20,9 +20,9 @@ ttnn::Tensor InterleavedToShardedPartialOperation::invoke( tt::tt_metal::TensorMemoryLayout shard_scheme, tt::tt_metal::ShardOrientation shard_orientation, const std::optional& data_type_arg) { - std::vector output_tensors = {Tensor(operation::get_workers_for_op_output({input_tensor}))}; + std::vector output_tensors = {Tensor(tt::tt_metal::operation::get_workers_for_op_output({input_tensor}))}; - bool row_wise = shard_orientation == ShardOrientation::ROW_MAJOR; + bool row_wise = shard_orientation == tt::tt_metal::ShardOrientation::ROW_MAJOR; CoreCoord grid_size; CoreRangeSet grid_set; std::visit( diff --git a/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/sharded_to_interleaved_partial/device/sharded_to_interleaved_partial_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/sharded_to_interleaved_partial/device/sharded_to_interleaved_partial_op.hpp index 54d765595c2a..a25030a4faa6 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/sharded_to_interleaved_partial/device/sharded_to_interleaved_partial_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/sharded_to_interleaved_partial/device/sharded_to_interleaved_partial_op.hpp @@ -12,13 +12,13 @@ namespace ttnn::operations::data_movement { struct ShardedToInterleavedPartialDeviceOperation { const uint32_t num_slices; const uint32_t slice_index; - const MemoryConfig output_mem_config; - const DataType output_dtype; + const tt::tt_metal::MemoryConfig output_mem_config; + const tt::tt_metal::DataType output_dtype; void validate(const std::vector& input_tensors) const; std::vector compute_output_shapes(const std::vector& input_tensors) const; std::vector create_output_tensors(const std::vector& input_tensors) const; - operation::ProgramWithCallbacks create_program( + tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; static constexpr auto attribute_names = diff --git a/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/sharded_to_interleaved_partial/sharded_to_interleaved_partial.cpp b/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/sharded_to_interleaved_partial/sharded_to_interleaved_partial.cpp index dea8c93b581b..3755265de73e 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/sharded_to_interleaved_partial/sharded_to_interleaved_partial.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/sharded_partial/sharded_to_interleaved_partial/sharded_to_interleaved_partial.cpp @@ -17,7 +17,7 @@ ttnn::Tensor ShardedToInterleavedPartialOperation::invoke( int64_t& slice_index, const std::optional& memory_config_arg, const std::optional& data_type_arg) { - std::vector output_tensors = {Tensor(operation::get_workers_for_op_output({input_tensor}))}; + std::vector output_tensors = {Tensor(tt::tt_metal::operation::get_workers_for_op_output({input_tensor}))}; auto memory_config = memory_config_arg.value_or(input_tensor.memory_config()); auto shard_spec = input_tensor.shard_spec().value(); diff --git a/ttnn/cpp/ttnn/operations/data_movement/slice/device/slice_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/slice/device/slice_op.hpp index d90562079770..f8aeaf30a243 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/slice/device/slice_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/slice/device/slice_op.hpp @@ -18,7 +18,7 @@ struct SliceDeviceOperation { const tt::tt_metal::LegacyShape slice_start; const tt::tt_metal::LegacyShape slice_end; const tt::tt_metal::LegacyShape step; - const MemoryConfig output_mem_config; + const tt::tt_metal::MemoryConfig output_mem_config; void validate_with_output_tensors( const std::vector& input_tensors, const std::vector>& output_tensors) const; diff --git a/ttnn/cpp/ttnn/operations/data_movement/split/device/split_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/split/device/split_op.hpp index 09f3c42c5e9d..f1f49ae21bfe 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/split/device/split_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/split/device/split_op.hpp @@ -12,7 +12,7 @@ namespace ttnn::operations::data_movement { struct SplitDeviceOperation { const int num_splits; const int dim; - const MemoryConfig output_mem_config; + const tt::tt_metal::MemoryConfig output_mem_config; void validate(const std::vector& input_tensors) const; std::vector compute_output_shapes(const std::vector& input_tensors) const; diff --git a/ttnn/cpp/ttnn/operations/data_movement/split/device/split_program_factory.hpp b/ttnn/cpp/ttnn/operations/data_movement/split/device/split_program_factory.hpp index 31dea711da40..36c1ec46b42c 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/split/device/split_program_factory.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/split/device/split_program_factory.hpp @@ -7,6 +7,5 @@ namespace ttnn::operations::data_movement::detail { operation::ProgramWithCallbacks split_last_dim_two_chunks_tiled( - const Tensor& input_tensor, std::vector& output_tensors, const MemoryConfig& mem_config); - + const Tensor& input_tensor, std::vector& output_tensors, const tt::tt_metal::MemoryConfig& mem_config); } diff --git a/ttnn/cpp/ttnn/operations/data_movement/transpose/device/transpose_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/transpose/device/transpose_op.hpp index fe2c05df803a..fe307fdb8722 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/transpose/device/transpose_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/transpose/device/transpose_op.hpp @@ -15,7 +15,7 @@ enum class TransposeOpParallelizationStrategy { MULTI_CORE_WH, MULTI_CORE_HC, MU struct Transpose { const TransposeOpDim dim; - const MemoryConfig output_mem_config; + const tt::tt_metal::MemoryConfig output_mem_config; const std::optional pad_value; void validate(const std::vector& input_tensors) const; diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.hpp index 048d086e4efb..e6c414a97642 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.hpp @@ -52,12 +52,12 @@ struct BinaryDeviceOperation { struct ElementWiseMultiCore { struct shared_variables_t { - KernelHandle binary_reader_kernel_id; - KernelHandle unary_writer_kernel_id; - KernelHandle eltwise_binary_kernel_id; - CBHandle cb_src0; - CBHandle cb_src1; - CBHandle cb_output; + tt::tt_metal::KernelHandle binary_reader_kernel_id; + tt::tt_metal::KernelHandle unary_writer_kernel_id; + tt::tt_metal::KernelHandle eltwise_binary_kernel_id; + tt::tt_metal::CBHandle cb_src0; + tt::tt_metal::CBHandle cb_src1; + tt::tt_metal::CBHandle cb_output; CoreCoord compute_with_storage_grid_size; uint32_t src0_single_tile_size; uint32_t src1_single_tile_size; @@ -79,12 +79,12 @@ struct BinaryDeviceOperation { struct ElementWiseMultiCoreSfpu { struct shared_variables_t { - KernelHandle binary_reader_kernel_id; - KernelHandle unary_writer_kernel_id; - KernelHandle eltwise_binary_kernel_id; - CBHandle cb_src0; - CBHandle cb_src1; - CBHandle cb_output; + tt::tt_metal::KernelHandle binary_reader_kernel_id; + tt::tt_metal::KernelHandle unary_writer_kernel_id; + tt::tt_metal::KernelHandle eltwise_binary_kernel_id; + tt::tt_metal::CBHandle cb_src0; + tt::tt_metal::CBHandle cb_src1; + tt::tt_metal::CBHandle cb_output; CoreCoord compute_with_storage_grid_size; uint32_t src0_single_tile_size; uint32_t src1_single_tile_size; @@ -105,9 +105,9 @@ struct BinaryDeviceOperation { }; struct BroadcastWidthMultiCore { struct shared_variables_t { - KernelHandle binary_reader_kernel_id; - KernelHandle unary_writer_kernel_id; - KernelHandle bcast_kernel_id; + tt::tt_metal::KernelHandle binary_reader_kernel_id; + tt::tt_metal::KernelHandle unary_writer_kernel_id; + tt::tt_metal::KernelHandle bcast_kernel_id; CoreCoord compute_with_storage_grid_size; }; using cached_program_t = ttnn::device_operation::CachedProgram; @@ -126,9 +126,9 @@ struct BinaryDeviceOperation { struct BroadcastHeightMultiCore { struct shared_variables_t { - KernelHandle binary_reader_kernel_id; - KernelHandle unary_writer_kernel_id; - KernelHandle bcast_kernel_id; + tt::tt_metal::KernelHandle binary_reader_kernel_id; + tt::tt_metal::KernelHandle unary_writer_kernel_id; + tt::tt_metal::KernelHandle bcast_kernel_id; CoreCoord compute_with_storage_grid_size; }; using cached_program_t = ttnn::device_operation::CachedProgram; @@ -147,15 +147,15 @@ struct BinaryDeviceOperation { struct BroadcastHeightAndWidthMultiCore { struct shared_variables_t { - KernelHandle binary_reader_kernel_id; - KernelHandle unary_writer_kernel_id; - KernelHandle bcast_kernel_id; + tt::tt_metal::KernelHandle binary_reader_kernel_id; + tt::tt_metal::KernelHandle unary_writer_kernel_id; + tt::tt_metal::KernelHandle bcast_kernel_id; CoreCoord compute_with_storage_grid_size; - CBHandle cb_src0; + tt::tt_metal::CBHandle cb_src0; uint32_t src0_single_tile_size; uint32_t src1_single_tile_size; uint32_t dst_single_tile_size; - CBHandle cb_output; + tt::tt_metal::CBHandle cb_output; }; using cached_program_t = ttnn::device_operation::CachedProgram; @@ -173,10 +173,10 @@ struct BinaryDeviceOperation { struct BroadcastHeightMultiCoreSharded { struct shared_variables_t { - KernelHandle binary_reader_kernel_id; - KernelHandle bcast_kernel_id; + tt::tt_metal::KernelHandle binary_reader_kernel_id; + tt::tt_metal::KernelHandle bcast_kernel_id; uint32_t cb_src0; - CBHandle out_cb; + tt::tt_metal::CBHandle out_cb; uint32_t ncores_x; }; @@ -196,10 +196,10 @@ struct BinaryDeviceOperation { struct BroadcastHeightMultiCoreShardedOptimized { struct shared_variables_t { - KernelHandle binary_reader_kernel_id; - KernelHandle bcast_kernel_id; + tt::tt_metal::KernelHandle binary_reader_kernel_id; + tt::tt_metal::KernelHandle bcast_kernel_id; uint32_t cb_src0; - CBHandle out_cb; + tt::tt_metal::CBHandle out_cb; uint32_t ncores_x; }; using cached_program_t = ttnn::device_operation::CachedProgram; @@ -237,7 +237,7 @@ struct BinaryDeviceOperation { static tt::stl::hash::hash_t compute_program_hash(const operation_attributes_t&, const tensor_args_t&); - static operation::OpPerformanceModel create_op_performance_model( + static tt::tt_metal::operation::OpPerformanceModel create_op_performance_model( const operation_attributes_t& attributes, const tensor_args_t& tensor_args, tensor_return_value_t& tensor_return_value); diff --git a/ttnn/cpp/ttnn/operations/experimental/auto_format/auto_format.hpp b/ttnn/cpp/ttnn/operations/experimental/auto_format/auto_format.hpp index 6ba7a70ffe0d..e9b8182533e9 100644 --- a/ttnn/cpp/ttnn/operations/experimental/auto_format/auto_format.hpp +++ b/ttnn/cpp/ttnn/operations/experimental/auto_format/auto_format.hpp @@ -73,8 +73,8 @@ class AutoFormat { const tt::tt_metal::LegacyShape& unpadded_shape, tt::tt_metal::Layout layout) { tt::tt_metal::LegacyShape padded_shape = unpadded_shape; switch (layout) { - case Layout::ROW_MAJOR: padded_shape = pad_to_rm_shape(unpadded_shape); break; - case Layout::TILE: padded_shape = pad_to_tile_shape(unpadded_shape); + case tt::tt_metal::Layout::ROW_MAJOR: padded_shape = pad_to_rm_shape(unpadded_shape); break; + case tt::tt_metal::Layout::TILE: padded_shape = pad_to_tile_shape(unpadded_shape); default: break; } return padded_shape; @@ -90,16 +90,18 @@ class AutoFormat { static bool legal_device_shape(const tt::tt_metal::LegacyShape& shape, tt::tt_metal::Layout layout) { switch (layout) { - case Layout::ROW_MAJOR: return legal_rm_shape(shape); - case Layout::TILE: return legal_tile_shape(shape); + case tt::tt_metal::Layout::ROW_MAJOR: return legal_rm_shape(shape); + case tt::tt_metal::Layout::TILE: return legal_tile_shape(shape); default: return true; } } static bool check_input_tensor_format( - const Tensor& a, const tt::tt_metal::LegacyShape& shape, tt::tt_metal::Layout target_layout = Layout::TILE) { + const Tensor& a, + const tt::tt_metal::LegacyShape& shape, + tt::tt_metal::Layout target_layout = tt::tt_metal::Layout::TILE) { if (a.get_layout() == target_layout && a.get_legacy_shape() == shape && - a.storage_type() == StorageType::DEVICE) { + a.storage_type() == tt::tt_metal::StorageType::DEVICE) { return true; } return false; @@ -134,8 +136,8 @@ class AutoFormat { const Tensor& output, const tt::tt_metal::LegacyShape& shape, tt::tt_metal::Device* device, - Layout target_layout, - std::optional target_mem_config = std::nullopt); + tt::tt_metal::Layout target_layout, + std::optional target_mem_config = std::nullopt); }; } // namespace ttnn::operations::experimental::auto_format diff --git a/ttnn/cpp/ttnn/operations/kv_cache/device/update_cache_op.hpp b/ttnn/cpp/ttnn/operations/kv_cache/device/update_cache_op.hpp index 431a64ed3388..80cf7da51f0c 100644 --- a/ttnn/cpp/ttnn/operations/kv_cache/device/update_cache_op.hpp +++ b/ttnn/cpp/ttnn/operations/kv_cache/device/update_cache_op.hpp @@ -15,13 +15,13 @@ enum class UpdateCacheOpParallelizationStrategy { MULTI_CORE }; enum class UpdateCacheOpType { FILL, UPDATE }; -operation::ProgramWithCallbacks update_cache_multi_core( +tt::tt_metal::operation::ProgramWithCallbacks update_cache_multi_core( const Tensor& cache_tensor, const Tensor& input_tensor, const uint32_t update_idx, const uint32_t batch_offset, ttnn::DeviceComputeKernelConfig compute_kernel_config); -operation::ProgramWithCallbacks fill_cache_multi_core( +tt::tt_metal::operation::ProgramWithCallbacks fill_cache_multi_core( const Tensor& cache_tensor, const Tensor& input_tensor, const uint32_t batch_idx, const uint32_t update_idx); struct UpdateCache { @@ -37,7 +37,7 @@ struct UpdateCache { std::vector compute_output_specs(const std::vector& input_tensors) const; std::vector create_output_tensors(const std::vector& input_tensors) const; - operation::ProgramWithCallbacks create_program( + tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; const operation::Hash compute_program_hash(const std::vector& input_tensors) const; @@ -45,8 +45,8 @@ struct UpdateCache { inline Tensor fill_cache_impl(const Tensor& cache_tensor, const Tensor& input_tensor, const uint32_t batch_idx) { std::vector dummy_output_tensors = { - Tensor(operation::get_workers_for_op_output({cache_tensor, input_tensor}))}; - operation::launch_op( + Tensor(tt::tt_metal::operation::get_workers_for_op_output({cache_tensor, input_tensor}))}; + tt::tt_metal::operation::launch_op( [batch_idx]( const std::vector& input_tensors, const std::vector>& optional_input_tensors, @@ -65,7 +65,7 @@ inline Tensor update_cache_impl( const uint32_t batch_offset, std::optional compute_kernel_config = std::nullopt) { std::vector dummy_output_tensors = { - Tensor(operation::get_workers_for_op_output({cache_tensor, input_tensor}))}; + Tensor(tt::tt_metal::operation::get_workers_for_op_output({cache_tensor, input_tensor}))}; operation::launch_op( [update_idx, batch_offset, compute_kernel_config]( const std::vector& input_tensors, diff --git a/ttnn/cpp/ttnn/operations/matmul/device/matmul_op.hpp b/ttnn/cpp/ttnn/operations/matmul/device/matmul_op.hpp index 9ef2eaeafb99..8815c6b52052 100644 --- a/ttnn/cpp/ttnn/operations/matmul/device/matmul_op.hpp +++ b/ttnn/cpp/ttnn/operations/matmul/device/matmul_op.hpp @@ -25,14 +25,14 @@ using ttnn::operations::unary::UnaryWithParam; /* * GENERAL MATMUL AND BMM */ -operation::ProgramWithCallbacks matmul_multi_core( +tt::tt_metal::operation::ProgramWithCallbacks matmul_multi_core( const Tensor& input_tensor_a, const Tensor& input_tensor_b, Tensor& output_tensor, bool bcast_batch); -operation::ProgramWithCallbacks matmul_multi_core_reuse( +tt::tt_metal::operation::ProgramWithCallbacks matmul_multi_core_reuse( const Tensor& input_tensor_a, const Tensor& input_tensor_b, Tensor& output_tensor, bool bcast_batch); -operation::ProgramWithCallbacks matmul_multi_core_reuse_mcast( +tt::tt_metal::operation::ProgramWithCallbacks matmul_multi_core_reuse_mcast( const Tensor& input_tensor_a, const Tensor& input_tensor_b, Tensor& output_tensor, bool bcast_batch); -operation::ProgramWithCallbacks matmul_multi_core_reuse_mcast_1d_optimized( +tt::tt_metal::operation::ProgramWithCallbacks matmul_multi_core_reuse_mcast_1d_optimized( const Tensor& input_tensor_a, const Tensor& input_tensor_b, const std::optional& bias, @@ -52,7 +52,7 @@ operation::ProgramWithCallbacks matmul_multi_core_reuse_mcast_1d_optimized( bool mcast_in0, bool gather_in0, bool untilize_out); -operation::ProgramWithCallbacks matmul_multi_core_reuse_dram_sharded_optimized( +tt::tt_metal::operation::ProgramWithCallbacks matmul_multi_core_reuse_dram_sharded_optimized( const Tensor& input_tensor_a, const Tensor& input_tensor_b, const std::optional& bias, @@ -66,7 +66,7 @@ operation::ProgramWithCallbacks matmul_multi_core_reuse_dram_sharded_optimized( bool skip_compute, bool skip_in0_mcast, bool skip_write_back); -operation::ProgramWithCallbacks matmul_multi_core_reuse_mcast_2d_optimized( +tt::tt_metal::operation::ProgramWithCallbacks matmul_multi_core_reuse_mcast_2d_optimized( const Tensor& input_tensor_a, const Tensor& input_tensor_b, const std::optional& bias, @@ -85,7 +85,7 @@ operation::ProgramWithCallbacks matmul_multi_core_reuse_mcast_2d_optimized( bool transpose_mcast, std::optional fused_activation, bool untilize_out); -operation::ProgramWithCallbacks bmm_multi_core_reuse_optimized( +tt::tt_metal::operation::ProgramWithCallbacks bmm_multi_core_reuse_optimized( const Tensor& input_tensor_a, const Tensor& input_tensor_b, Tensor& output_tensor, @@ -180,11 +180,11 @@ struct Matmul { const std::vector>& optional_input_tensors) const; std::vector compute_output_specs(const std::vector& input_tensors) const; std::vector create_output_tensors(const std::vector& input_tensors) const; - operation::ProgramWithCallbacks create_program( + tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, const std::vector>& optional_input_tensors, std::vector& output_tensors) const; - operation::OpPerformanceModel create_op_performance_model( + tt::tt_metal::operation::OpPerformanceModel create_op_performance_model( const std::vector& input_tensors, const std::vector>& optional_input_tensors, std::vector& output_tensors) const; diff --git a/ttnn/cpp/ttnn/tensor/tensor_impl.hpp b/ttnn/cpp/ttnn/tensor/tensor_impl.hpp index 0ceb2b9c1d1a..a6db8b143881 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_impl.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor_impl.hpp @@ -177,7 +177,7 @@ inline void read_data_from_device_buffer( template inline void read_data_from_device_buffer(DeviceBuffer device_buffer, std::vector& host_buffer) { - ::detail::ReadFromBuffer(device_buffer, host_buffer); + ::tt::tt_metal::detail::ReadFromBuffer(device_buffer, host_buffer); } // ====================================================================================== From 3be17c1611a131bf8e9512b2ebc3e950e8f38e45 Mon Sep 17 00:00:00 2001 From: Nikola Cvetkovic Date: Wed, 18 Dec 2024 12:13:17 +0100 Subject: [PATCH 43/87] #15713 Bad Eltwise Binary ZEROACC (#16094) ### Ticket #15713 ### Problem description Instruction for clearing the DEST (ZEROACC) doesn't work the same on BH and WH_B0 for 32-bit mode. On WH_B0, two ZEROACC instructions are needed to clear half of a face each, because ZEROACC clears8 x 16 datums (half of a face), if we use CLR_16 mode and use 32-bit DEST mode. On BH, however, this is not the case - CLR_16 mode will always clear 16 x 16 datums (a whole face), size of which in bytes is determined by fp32 bit of the instruction. ### What's changed Corrected ZEROACC call for clearing the DEST when we reuse it in eltwise binary ops. ### Checklist - [x] Post commit CI passes - [before pipeline broke](https://github.com/tenstorrent/tt-metal/actions/runs/12354236372 ), after pipeline broke [#22539](https://github.com/tenstorrent/tt-metal/actions/runs/12371210472), [#22474](https://github.com/tenstorrent/tt-metal/actions/runs/12357394873) - [x] Blackhole Post commit (if applicable) - [before pipeline broke](https://github.com/tenstorrent/tt-metal/actions/runs/12355477287), after pipeline broke [#2408](https://github.com/tenstorrent/tt-metal/actions/runs/12371201293) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [x] New/Existing tests provide coverage for changes --- tt_metal/third_party/tt_llk_blackhole | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tt_metal/third_party/tt_llk_blackhole b/tt_metal/third_party/tt_llk_blackhole index 973288fb014a..c5735f6d4a8b 160000 --- a/tt_metal/third_party/tt_llk_blackhole +++ b/tt_metal/third_party/tt_llk_blackhole @@ -1 +1 @@ -Subproject commit 973288fb014a22ce72cdba1c38a9f41f48532d6d +Subproject commit c5735f6d4a8b66b6e46f26c4c655abb694875bd7 From 05fee29670603f4dc8e5cd847c0609dc57e407ef Mon Sep 17 00:00:00 2001 From: Nemanja Grujic <109360083+nemanjagrujic@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:53:35 +0100 Subject: [PATCH 44/87] #15565 Fix unit test to show sharding ttnn.from_torch problems (#16088) #15565 ### Ticket [Link to Github Issue](https://github.com/tenstorrent/tt-metal/issues/15565) ### Problem description Issues with sharding giving low PCC in some cases need unit test which show it. ### What's changed Added better unit test to showcase sharding low PCC problems ### Checklist - [X] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12390885706 --- .../test_eltwise_block_sharded_spec.py | 276 ++++++++++++++++++ .../tensor/test_sharding_with_alignment.cpp | 45 +++ 2 files changed, 321 insertions(+) create mode 100644 tests/ttnn/python_api_testing/non_working_unit_tests/wormhole/test_eltwise_block_sharded_spec.py diff --git a/tests/ttnn/python_api_testing/non_working_unit_tests/wormhole/test_eltwise_block_sharded_spec.py b/tests/ttnn/python_api_testing/non_working_unit_tests/wormhole/test_eltwise_block_sharded_spec.py new file mode 100644 index 000000000000..88f9a193b179 --- /dev/null +++ b/tests/ttnn/python_api_testing/non_working_unit_tests/wormhole/test_eltwise_block_sharded_spec.py @@ -0,0 +1,276 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from loguru import logger +import random +import pytest +import torch +import ttnn + +from tests.ttnn.utils_for_testing import assert_with_pcc, check_with_pcc +from tests.ttnn.python_api_testing.sweep_tests import ttnn_ops +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_rand_inf + +Y, X = (8, 8) + + +def run_tests( + input_shape, + dtype, + dlayout, + tensor_memory_layout, + byffer_type, + shard_grid, + shard_shape, + shard_orientation, + halo, + torch_op, + ttnn_op, + gen_infs, + device, +): + random.seed(0) + data_seed = random.randint(0, 20000000) + torch.manual_seed(data_seed) + + if gen_infs: + torch_input_tensor_a = gen_rand_inf(input_shape, low=-100, high=100) + else: + torch_input_tensor_a = torch.Tensor(size=input_shape).uniform_(-50, 50).to(torch.bfloat16) + + torch_output_tensor = torch_input_tensor_a + + shard_spec = ttnn.ShardSpec(shard_grid, shard_shape, shard_orientation, halo) + sharded_config = ttnn.MemoryConfig(tensor_memory_layout, byffer_type, shard_spec) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=dtype, + layout=dlayout, + device=device, + memory_config=sharded_config, + ) + + output_tensor = input_tensor_a + output_tensor = ttnn.to_torch(output_tensor) + + [passed, message] = check_with_pcc(torch_output_tensor, output_tensor, 0.999) + assert passed, f"PCC={message}" + + +test_sweep_args = [ + ( + (256, 2, 5, 1536), # Tensor shape + ttnn.bfloat16, # Tensor dtype + ttnn.TILE_LAYOUT, # Tensor layout + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [320, 192], # shard shape + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (256, 2, 5, 1536), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [320, 192], + ttnn.ShardOrientation.ROW_MAJOR, + False, # halo + ), + ( + (256, 2, 5, 1536), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [320, 192], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 256, 2, 2304), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [64, 288], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 256, 2, 2304), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [64, 288], + ttnn.ShardOrientation.ROW_MAJOR, + False, # halo + ), + ( + (1, 256, 2, 2304), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [64, 288], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (32, 4, 8, 768), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [128, 96], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (32, 4, 8, 768), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [128, 96], + ttnn.ShardOrientation.ROW_MAJOR, + False, # halo + ), + ( + (32, 4, 8, 768), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [128, 96], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 25, 160, 32), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [32, 160], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 25, 160, 32), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [32, 160], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 2, 1248, 32), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [32, 1248], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 2, 1248, 32), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [32, 1248], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 2, 1472, 32), + ttnn.bfloat16, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [32, 1472], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (1, 2, 1472, 32), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [32, 1472], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), + ( + (2, 1, 224, 128), + ttnn.bfloat8_b, + ttnn.TILE_LAYOUT, + ttnn.TensorMemoryLayout.BLOCK_SHARDED, + ttnn.BufferType.L1, + ttnn.CoreRangeSet({ttnn.CoreRange(ttnn.CoreCoord(0, 0), ttnn.CoreCoord(7, 7))}), # core grid + [128, 224], + ttnn.ShardOrientation.COL_MAJOR, + False, # halo + ), +] + + +def nop(x, memory_config=None): + return x + + +@pytest.mark.parametrize( + "input_shape, dtype, dlayout, tensor_memory_layout, byffer_type, shard_grid, shard_shape, shard_orientation, halo", + (test_sweep_args), +) +def test_eltwise_nop( + input_shape, + dtype, + dlayout, + tensor_memory_layout, + byffer_type, + shard_grid, + shard_shape, + shard_orientation, + halo, + device, +): + run_tests( + input_shape, + dtype, + dlayout, + tensor_memory_layout, + byffer_type, + shard_grid, + shard_shape, + shard_orientation, + halo, + nop, + nop, + False, + device, + ) diff --git a/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp b/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp index 466d602d9908..4a722f844756 100644 --- a/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp +++ b/tests/ttnn/unit_tests/gtests/tensor/test_sharding_with_alignment.cpp @@ -1049,6 +1049,51 @@ INSTANTIATE_TEST_SUITE_P( CreateShardedTensorWithAlignmentExpected{ .physical_shape = Size{28, 9} } + }, + //////////////////////////////////////////////////////////////////// + // EXAMPLE 4: Some of block sharding failurs + //////////////////////////////////////////////////////////////////// + CreateShardedTensorWithAlignmentParams{ + CreateShardedTensorWithAlignmentInputs{ + .shape = SimpleShape{32, 4, 8, 768}, + .data_type = DataType::BFLOAT16, + .page_config = PageConfig(Layout::TILE), + .memory_config = + MemoryConfig{ + .memory_layout = TensorMemoryLayout::BLOCK_SHARDED, + .buffer_type = BufferType::L1, + .shard_spec = ShardSpec{ + num_cores_to_corerangeset(64, CoreCoord{8, 8}, /*row_wise=*/true), // tt::div_up(32 * 4 * 8, 128) * tt::div_up(768, 96) + {128, 96}, + ShardOrientation::ROW_MAJOR, + false, + ShardMode::PHYSICAL} + } + }, + CreateShardedTensorWithAlignmentExpected{ + .physical_shape = Size{1024, 768} + } + }, + CreateShardedTensorWithAlignmentParams{ + CreateShardedTensorWithAlignmentInputs{ + .shape = SimpleShape{32, 4, 8, 768}, + .data_type = DataType::BFLOAT16, + .page_config = PageConfig(Layout::TILE), + .memory_config = + MemoryConfig{ + .memory_layout = TensorMemoryLayout::BLOCK_SHARDED, + .buffer_type = BufferType::L1, + .shard_spec = ShardSpec{ + num_cores_to_corerangeset(64, CoreCoord{8, 8}, /*row_wise=*/true), // tt::div_up(32 * 4 * 8, 128) * tt::div_up(768, 96) + {128, 96}, + ShardOrientation::COL_MAJOR, + false, + ShardMode::PHYSICAL} + } + }, + CreateShardedTensorWithAlignmentExpected{ + .physical_shape = Size{1024, 768} + } } ) // Values // clang-format on From 8135dac6a3efec6ee83f02f2043172e54854f186 Mon Sep 17 00:00:00 2001 From: Colman Glagovich <114512306+cglagovichTT@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:08:55 -0500 Subject: [PATCH 45/87] Fix paged SDPA decode CB sizing issue (#16059) --- .../misc/test_scaled_dot_product_attention_decode.py | 11 ++++++----- .../transformer/sdpa_decode/device/sdpa_decode_op.cpp | 4 +++- .../device/sdpa_decode_program_factory.cpp | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/tt_eager/python_api_testing/unit_testing/misc/test_scaled_dot_product_attention_decode.py b/tests/tt_eager/python_api_testing/unit_testing/misc/test_scaled_dot_product_attention_decode.py index 1c908f0ab94f..a0293b072d68 100644 --- a/tests/tt_eager/python_api_testing/unit_testing/misc/test_scaled_dot_product_attention_decode.py +++ b/tests/tt_eager/python_api_testing/unit_testing/misc/test_scaled_dot_product_attention_decode.py @@ -717,11 +717,8 @@ def to_contiguous_cache(paged_cache, batch, num_kv, max_num_blocks_per_seq, bloc # Test when page_table does not contain blocks for full sequence length k_chunk_size = get_chunk_size(max_start_idx + 1, s) padded_layer_len = nearest_n(max_start_idx + 1, n=k_chunk_size) if causal else s - if causal: - last_block = max(1, math.ceil(padded_layer_len / block_size)) - tt_page_table = ttnn.Tensor(page_table[:, :last_block], ttnn.int32).to(device) - else: - tt_page_table = ttnn.Tensor(page_table, ttnn.int32).to(device) + + tt_page_table = ttnn.Tensor(page_table, ttnn.int32).to(device) program_config = ttnn.SDPAProgramConfig( compute_with_storage_grid_size=grid_size, # device.compute_with_storage_grid_size(), @@ -858,6 +855,7 @@ def to_contiguous_cache(paged_cache, batch, num_kv, max_num_blocks_per_seq, bloc # [4, 16, 4, 32768, 128, (8, 8), True], # [32, 32, 8, 4096, 128, (8, 8), True], # llama 3.1 8b [8, 16, 4, 4096, 128, (8, 2), True], # llama 3.1 8b N300 + [1, 8, 1, 128 * 1024, 128, (8, 4), True], # llama 3.1 8b N300 # [1, 8, 1, 32768, 128, (8, 1), True], # Llama2-70B # [16, 8, 1, 32768, 128, (8, 6), False, False], # Llama2-70B # [8, 8, 1, 32768, 128, (8, 6), True, False], # Llama2-70B @@ -869,6 +867,9 @@ def to_contiguous_cache(paged_cache, batch, num_kv, max_num_blocks_per_seq, bloc def test_sdpa_decode_paged_attention( device, b, nh, nkv, s, d, kv_dtype, grid_size, q_dtype, cur_pos_tensor, block_size, use_program_cache ): + if s == 128 * 1024 and block_size != 64: + # 128k sequence, block_size 64 tests the sizing of the page table CB + pytest.skip("Skipping test for seq_len=128k with block_size!=64") ttnn.device.DisablePersistentKernelCache() run_test_sdpa_decode_paged_attention( device, diff --git a/ttnn/cpp/ttnn/operations/transformer/sdpa_decode/device/sdpa_decode_op.cpp b/ttnn/cpp/ttnn/operations/transformer/sdpa_decode/device/sdpa_decode_op.cpp index 617a6b95b95f..dc5ed546f516 100644 --- a/ttnn/cpp/ttnn/operations/transformer/sdpa_decode/device/sdpa_decode_op.cpp +++ b/ttnn/cpp/ttnn/operations/transformer/sdpa_decode/device/sdpa_decode_op.cpp @@ -260,7 +260,9 @@ operation::Hash ScaledDotProductAttentionDecode::compute_program_hash( this->is_causal, has_attn_mask, has_cur_pos, - input_tensors); + input_tensors, + // Hash on page_table_tensor to properly size page table CB + optional_input_tensors.at(1)); } } // namespace ttnn::operations::transformer diff --git a/ttnn/cpp/ttnn/operations/transformer/sdpa_decode/device/sdpa_decode_program_factory.cpp b/ttnn/cpp/ttnn/operations/transformer/sdpa_decode/device/sdpa_decode_program_factory.cpp index 9615b729578d..e1f02e4c00ba 100644 --- a/ttnn/cpp/ttnn/operations/transformer/sdpa_decode/device/sdpa_decode_program_factory.cpp +++ b/ttnn/cpp/ttnn/operations/transformer/sdpa_decode/device/sdpa_decode_program_factory.cpp @@ -363,8 +363,8 @@ operation::ProgramWithCallbacks sdpa_decode_multi_core( page_table_stick_size = page_table_buffer->aligned_page_size(); // cb page_table - auto c_in9_config = CircularBufferConfig(page_table_tile_size, {{CBIndex::c_9, page_table_df}}) - .set_page_size(CBIndex::c_9, page_table_tile_size); + auto c_in9_config = CircularBufferConfig(page_table_stick_size, {{CBIndex::c_9, page_table_df}}) + .set_page_size(CBIndex::c_9, page_table_stick_size); auto cb_in9_id = CreateCircularBuffer(program, core_grid, c_in9_config); } From d5817c6090e9416fd392b3f45fc1438b871caaca Mon Sep 17 00:00:00 2001 From: John Bauman Date: Tue, 17 Dec 2024 20:05:21 +0000 Subject: [PATCH 46/87] #15018: Increase linking of program binary multicasts The BRISC, NCRISC, and set of all TRISCs each have their own entry in kq_transfer_info.dst_base_addrs, which prevents the current code from linking their transactions. Modify the code to avoid unlinking in the case where writes to the same set of cores happen to be contiguous (which happens to be always). This reduces the cost of linking and will help a lot more in the future, once we put barriers between unlinked mcasts. --- tt_metal/impl/dispatch/command_queue.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tt_metal/impl/dispatch/command_queue.cpp b/tt_metal/impl/dispatch/command_queue.cpp index 7a4dce42fdd2..74b010286900 100644 --- a/tt_metal/impl/dispatch/command_queue.cpp +++ b/tt_metal/impl/dispatch/command_queue.cpp @@ -1029,6 +1029,12 @@ void EnqueueProgramCommand::assemble_device_commands( read_length = max_paged_length_per_sub_cmd; write_length = read_length; } + if (!kernel_bins_dispatch_subcmds.back().empty()) { + auto& back = kernel_bins_dispatch_subcmds.back().back(); + if (back.noc_xy_addr != noc_encoding) { + back.flags = CQ_DISPATCH_CMD_PACKED_WRITE_LARGE_FLAG_UNLINK; + } + } kernel_bins_dispatch_subcmds.back().emplace_back(CQDispatchWritePackedLargeSubCmd{ .noc_xy_addr = noc_encoding, .addr = kernel_config_buffer_offset, @@ -1050,9 +1056,13 @@ void EnqueueProgramCommand::assemble_device_commands( } } } - // Unlink the last subcmd of the current core range - if (!write_linear) { - kernel_bins_dispatch_subcmds.back().back().flags |= CQ_DISPATCH_CMD_PACKED_WRITE_LARGE_FLAG_UNLINK; + } + // Unlink the last subcmd of every dispatch, to ensure we don't hold the + // path reservation for an incredible long time. This also prevents a hang + // if the next mcast is to a different destination. + for (auto& subcmd_list : kernel_bins_dispatch_subcmds) { + if (!subcmd_list.empty()) { + subcmd_list.back().flags |= CQ_DISPATCH_CMD_PACKED_WRITE_LARGE_FLAG_UNLINK; } } uint32_t pcie_alignment = hal.get_alignment(HalMemType::HOST); From 4bfb135987081451f54289cb1871b67dc0bf4283 Mon Sep 17 00:00:00 2001 From: John Bauman Date: Tue, 17 Dec 2024 20:05:29 +0000 Subject: [PATCH 47/87] #15018: Re-enable async dispatch and workaround NOC hang When async dispatch was previously enabled, we hit a hang in LLAMA that seems to occur when doing the path reserve for an mcast and a previous mcast is still active. To work around this, force write barriers before mcasts that aren't linked to earlier mcasts. The dispatcher is the only core that sends mcasts over VC 5, and even with multiple CQs only one can be mcasting programs at a time. --- .../dispatch/pgm_dispatch_golden.log | 14 +++---- .../dispatch/sweep_pgm_dispatch.sh | 2 + tt_metal/impl/dispatch/command_queue.cpp | 3 +- .../impl/dispatch/kernels/cq_dispatch.cpp | 38 ++++++++++++++++++- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/tests/tt_metal/tt_metal/perf_microbenchmark/dispatch/pgm_dispatch_golden.log b/tests/tt_metal/tt_metal/perf_microbenchmark/dispatch/pgm_dispatch_golden.log index 1dbc35bd9093..4decfc579157 100644 --- a/tests/tt_metal/tt_metal/perf_microbenchmark/dispatch/pgm_dispatch_golden.log +++ b/tests/tt_metal/tt_metal/perf_microbenchmark/dispatch/pgm_dispatch_golden.log @@ -1,7 +1,7 @@ -2.69, 2.69, 2.95, 3.10, 3.34, 3.98, 4.02, 4.90, 3.48, 5.54, 3.38, 4.37, 5.48, 11.21, 4.02, 4.02, 12.97, 12.29, 96.13, 107.32, 4.14, 15.38, 114.60, -2.70, 2.70, 2.96, 3.12, 3.37, 4.01, 4.05, 4.93, 3.54, 5.55, 3.39, 4.40, 5.55, 11.26, 4.04, 4.03, 13.02, 12.45, 96.91, 108.26, 4.14, 15.37, 114.52, -2.71, 2.71, 3.04, 3.24, 3.43, 4.09, 4.17, 5.03, 3.70, 5.58, 3.42, 4.48, 5.73, 11.44, 4.06, 4.12, 13.19, 13.00, 99.35, 111.19, 4.14, 15.39, 118.12, -2.77, 2.77, 3.23, 3.48, 3.69, 4.28, 4.25, 5.16, 3.99, 5.63, 3.46, 4.57, 5.94, 11.70, 4.11, 4.09, 13.40, 14.23, 102.77, 115.28, 4.14, 16.30, 122.22, -2.95, 2.95, 3.66, 4.06, 4.50, 4.91, 4.82, 5.75, 4.69, 5.84, 3.53, 5.01, 6.68, 12.44, 4.34, 4.30, 14.16, 17.92, 118.69, 133.56, 4.14, 20.50, 134.55, -3.26, 3.28, 4.58, 5.21, 6.08, 6.09, 6.46, 7.19, 6.21, 6.15, 3.80, 7.63, 8.22, 14.02, 4.63, 4.64, 15.70, 29.68, 152.22, 177.60, -3.56, 3.63, 5.47, 6.42, 7.71, 7.70, +2.70, 2.70, 2.92, 3.02, 3.15, 3.18, 3.50, 3.53, 3.46, 5.90, 4.07, 4.45, 5.69, 12.25, 4.05, 4.15, 11.55, 12.29, 74.37, 85.31, 4.13, 14.16, 109.50, +2.71, 2.71, 2.93, 3.05, 3.23, 3.23, 3.55, 3.56, 3.49, 5.91, 4.08, 4.46, 5.75, 12.28, 4.05, 4.16, 11.60, 12.38, 74.64, 85.66, 4.13, 14.23, 109.89, +2.71, 2.71, 3.02, 3.23, 3.39, 3.39, 3.72, 3.81, 3.67, 5.93, 4.10, 4.51, 6.04, 12.48, 4.06, 4.18, 11.78, 12.55, 75.12, 86.29, 4.13, 14.46, 110.95, +2.78, 2.78, 3.23, 3.40, 3.61, 3.63, 3.96, 3.94, 3.88, 6.00, 4.13, 4.64, 6.18, 12.73, 4.11, 4.21, 12.03, 13.06, 76.39, 87.99, 4.13, 15.40, 114.50, +3.00, 3.00, 3.66, 3.98, 4.32, 4.35, 4.66, 4.69, 4.64, 6.22, 4.21, 5.05, 6.78, 13.41, 4.32, 4.35, 12.73, 17.65, 82.46, 95.97, 4.13, 19.60, 122.75, +3.29, 3.29, 4.51, 5.12, 5.79, 5.79, 6.27, 7.16, 6.02, 6.49, 4.34, 7.78, 8.28, 14.76, 4.64, 4.59, 16.45, 29.33, 151.60, 176.73, +3.57, 3.57, 5.40, 6.27, 7.17, 7.21, diff --git a/tests/tt_metal/tt_metal/perf_microbenchmark/dispatch/sweep_pgm_dispatch.sh b/tests/tt_metal/tt_metal/perf_microbenchmark/dispatch/sweep_pgm_dispatch.sh index c961e018027b..f6be0820853e 100755 --- a/tests/tt_metal/tt_metal/perf_microbenchmark/dispatch/sweep_pgm_dispatch.sh +++ b/tests/tt_metal/tt_metal/perf_microbenchmark/dispatch/sweep_pgm_dispatch.sh @@ -38,6 +38,8 @@ for arg in "$@"; do esac done +set -x + # brisc only echo "###" brisc only build/test/tt_metal/perf_microbenchmark/dispatch/test_pgm_dispatch -w 5000 -s 256 -n -t $trace_option $eth_dispatch_option diff --git a/tt_metal/impl/dispatch/command_queue.cpp b/tt_metal/impl/dispatch/command_queue.cpp index 74b010286900..284b45886052 100644 --- a/tt_metal/impl/dispatch/command_queue.cpp +++ b/tt_metal/impl/dispatch/command_queue.cpp @@ -3056,8 +3056,7 @@ void HWCommandQueue::reset_config_buffer_mgr(const uint32_t num_entries) { } // Subtract 1 from the number of entries, so the watcher can read information (e.g. fired asserts) from the // previous launch message. - // TODO(jbauman): Give correct number once async bug is fixed. - this->config_buffer_mgr[i].init_add_buffer(0, 1); + this->config_buffer_mgr[i].init_add_buffer(0, launch_msg_buffer_num_entries - 1); } } diff --git a/tt_metal/impl/dispatch/kernels/cq_dispatch.cpp b/tt_metal/impl/dispatch/kernels/cq_dispatch.cpp index 647a6b484027..8a481438f387 100644 --- a/tt_metal/impl/dispatch/kernels/cq_dispatch.cpp +++ b/tt_metal/impl/dispatch/kernels/cq_dispatch.cpp @@ -573,6 +573,18 @@ void process_write_packed( // dst_addr << " " << ENDL(); uint32_t writes = 0; uint32_t mcasts = 0; + auto wait_for_barrier = [&]() { + if (!mcast) { + return; + } + noc_nonposted_writes_num_issued[noc_index] += writes; + noc_nonposted_writes_acked[noc_index] += mcasts; + writes = 0; + mcasts = 0; + // Workaround mcast path reservation hangs by always waiting for a write + // barrier before doing an mcast that isn't linked to a previous mcast. + noc_async_write_barrier(); + }; WritePackedSubCmd* sub_cmd_ptr = (WritePackedSubCmd*)l1_cache; while (count != 0) { uint32_t dst_noc = sub_cmd_ptr->noc_xy_addr; @@ -587,6 +599,7 @@ void process_write_packed( if (cb_fence == block_next_start_addr[rd_block_idx]) { orphan_size = cb_fence - data_ptr; if (orphan_size != 0) { + wait_for_barrier(); cq_noc_async_write_with_state(data_ptr, dst, orphan_size, num_dests); writes++; mcasts += num_dests; @@ -620,6 +633,7 @@ void process_write_packed( uint32_t remainder_xfer_size = xfer_size - orphan_size; // Creating full NOC addr not needed as we are not programming the noc coords uint32_t remainder_dst_addr = dst_addr + orphan_size; + wait_for_barrier(); cq_noc_async_write_with_state( data_ptr, remainder_dst_addr, remainder_xfer_size, num_dests); // Reset values expected below @@ -634,6 +648,7 @@ void process_write_packed( } } + wait_for_barrier(); cq_noc_async_write_with_state(data_ptr, dst, xfer_size, num_dests); writes++; mcasts += num_dests; @@ -684,12 +699,26 @@ void process_write_packed_large( CQDispatchWritePackedLargeSubCmd* sub_cmd_ptr = (CQDispatchWritePackedLargeSubCmd*)l1_cache; bool init_state = true; + bool must_barrier = true; while (count != 0) { uint32_t dst_addr = sub_cmd_ptr->addr + local_write_offset; uint32_t length = sub_cmd_ptr->length; uint32_t num_dests = sub_cmd_ptr->num_mcast_dests; uint32_t pad_size = align(length, alignment) - length; uint32_t unlink = sub_cmd_ptr->flags & CQ_DISPATCH_CMD_PACKED_WRITE_LARGE_FLAG_UNLINK; + auto wait_for_barrier = [&]() { + if (!must_barrier) { + return; + } + noc_nonposted_writes_num_issued[noc_index] += writes; + + mcasts += num_dests * writes; + noc_nonposted_writes_acked[noc_index] = mcasts; + writes = 0; + // Workaround mcast path reservation hangs by always waiting for a write + // barrier before doing an mcast that isn't linked to a previous mcast. + noc_async_write_barrier(); + }; // Only re-init state after we have unlinked the last transaction // Otherwise we assume NOC coord hasn't changed @@ -697,8 +726,8 @@ void process_write_packed_large( // to determine linking if (init_state) { uint32_t dst_noc = sub_cmd_ptr->noc_xy_addr; - // TODO: Linking should be set to true once atomic txn is handled properly cq_noc_async_write_init_state(0, get_noc_addr_helper(dst_noc, dst_addr)); + must_barrier = true; } sub_cmd_ptr++; @@ -731,10 +760,13 @@ void process_write_packed_large( uint32_t xfer_size; if (length > available_data) { xfer_size = available_data; + wait_for_barrier(); cq_noc_async_write_with_state_any_len(data_ptr, dst_addr, xfer_size, num_dests); + must_barrier = false; } else { xfer_size = length; if (unlink) { + wait_for_barrier(); uint32_t rem_xfer_size = cq_noc_async_write_with_state_any_len(data_ptr, dst_addr, xfer_size, num_dests); // Unset Link flag @@ -742,8 +774,12 @@ void process_write_packed_large( uint32_t data_offset = xfer_size - rem_xfer_size; cq_noc_async_write_with_state( data_ptr + data_offset, dst_addr + data_offset, rem_xfer_size, num_dests); + // Later writes must barrier, but the `must_barrier = true` in the `if (init_state)` block above + // will see to that. } else { + wait_for_barrier(); cq_noc_async_write_with_state_any_len(data_ptr, dst_addr, xfer_size, num_dests); + must_barrier = false; } } writes += div_up(xfer_size, NOC_MAX_BURST_SIZE); From 97aea7465be8ce5579043fadb2a112fcb8838e5b Mon Sep 17 00:00:00 2001 From: Borys Bradel <164946524+bbradelTT@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:47:22 -0500 Subject: [PATCH 48/87] #16119: Add forge traces to matmul and reduce sweeps (#16139) ### Ticket Link to Github Issue #16119 ### Problem description Forge traces were only recently provided and needed to be added to sweep tests ### What's changed - Added forge traces to sweep tests - Added local pytest ability ### Checklist - [ ] Post commit CI passes N/A - [ ] Blackhole Post commit (if applicable) N/A - [ ] Model regression CI testing passes (if applicable) N/A - [ ] Device performance regression CI testing passes (if applicable) N/A - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes N/A - [ ] New/Existing tests provide coverage for changes N/A Only changed traces. Does not affect main flow. ### Testing The following commands all produced expected output: ``` python3 tests/sweep_framework/sweeps_parameter_generator.py --module-name matmul.short.matmul_traces python3 tests/sweep_framework/sweeps_runner.py --module-name matmul.short.matmul_traces --dry-run python3 tests/sweep_framework/sweeps_parameter_generator.py --module-name reduction.traces.mean_traces python3 tests/sweep_framework/sweeps_runner.py --module-name reduction.traces.mean_traces --dry-run python3 tests/sweep_framework/sweeps_parameter_generator.py --module-name reduction.traces.sum_traces python3 tests/sweep_framework/sweeps_runner.py --module-name reduction.traces.sum_traces --dry-run python3 tests/sweep_framework/sweeps_parameter_generator.py --module-name reduction.traces.max_traces python3 tests/sweep_framework/sweeps_runner.py --module-name reduction.traces.max_traces --dry-run python3 tests/sweep_framework/sweeps_parameter_generator.py --module-name reduction.traces.argmax_traces python3 tests/sweep_framework/sweeps_runner.py --module-name reduction.traces.argmax_traces --dry-run python3 tests/sweep_framework/sweeps_parameter_generator.py --module-name reduction.traces.topk_traces python3 tests/sweep_framework/sweeps_runner.py --module-name reduction.traces.topk_traces --dry-run pytest tests/sweep_framework/sweeps/matmul/short/matmul_traces.py pytest tests/sweep_framework/sweeps/reduction/traces/argmax_traces.py pytest tests/sweep_framework/sweeps/reduction/traces/max_traces.py pytest tests/sweep_framework/sweeps/reduction/traces/mean_traces.py pytest tests/sweep_framework/sweeps/reduction/traces/sum_traces.py pytest tests/sweep_framework/sweeps/reduction/traces/topk_traces.py ``` --- .../sweeps/matmul/short/matmul_traces.py | 2304 ++++++++++++++++- .../sweeps/reduction/traces/argmax_traces.py | 37 +- .../sweeps/reduction/traces/max_traces.py | 379 ++- .../sweeps/reduction/traces/mean_traces.py | 25 +- .../sweeps/reduction/traces/sum_traces.py | 582 ++++- .../sweeps/reduction/traces/topk_traces.py | 25 +- 6 files changed, 3288 insertions(+), 64 deletions(-) diff --git a/tests/sweep_framework/sweeps/matmul/short/matmul_traces.py b/tests/sweep_framework/sweeps/matmul/short/matmul_traces.py index 0b6c9031553e..892b8beeadc1 100644 --- a/tests/sweep_framework/sweeps/matmul/short/matmul_traces.py +++ b/tests/sweep_framework/sweeps/matmul/short/matmul_traces.py @@ -4,8 +4,8 @@ from typing import Optional, Tuple +import pytest import torch - import ttnn from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time @@ -18,7 +18,7 @@ # to reason about if both tensors are the same rank, although some other # combinations may be valid. parameters = { - "default": { + "pytorch": { "params": [ (1, 1024, 1024, 1024), (1, 1024, 1024, 3072), @@ -116,6 +116,7 @@ (920, 256, 256, 256), ], "core_grid": [False], + "dtype": [ttnn.float32], }, "gpt": { "params": [ @@ -351,16 +352,2268 @@ (64, 12, 64, 1024, 64, 12, 1024, 64), ], "core_grid": [True, False], + "dtype": [ttnn.float32], + }, + "forge": { + "params": [ + ( + 1, + 1024, + 1024, + 1000, + ), + ( + 1, + 1024, + 1024, + 1024, + ), + ( + 1, + 1024, + 1024, + 32128, + ), + ( + 1, + 1024, + 1024, + 4096, + ), + ( + 1, + 1024, + 1024, + 512, + ), + ( + 1, + 1024, + 160, + 1, + 160, + 256, + ), + ( + 1, + 1024, + 640, + 1, + 640, + 160, + ), + ( + 1, + 12, + 12, + 3, + ), + ( + 1, + 12, + 12, + 64, + ), + ( + 1, + 1200, + 1280, + 1, + 1280, + 320, + ), + ( + 1, + 128, + 128, + 10, + ), + ( + 1, + 128, + 128, + 64, + ), + ( + 1, + 128, + 128, + 784, + ), + ( + 1, + 1280, + 1280, + 1000, + ), + ( + 1, + 1280, + 1280, + 1280, + ), + ( + 1, + 1280, + 1280, + 320, + ), + ( + 1, + 1280, + 1280, + 640, + ), + ( + 1, + 1536, + 1536, + 3129, + ), + ( + 1, + 16384, + 128, + 1, + 128, + 32, + ), + ( + 1, + 16384, + 256, + 1, + 256, + 32, + ), + ( + 1, + 16384, + 32, + 1, + 32, + 256, + ), + ( + 1, + 19200, + 256, + 1, + 256, + 64, + ), + ( + 1, + 19200, + 300, + 1, + 300, + 64, + ), + ( + 1, + 19200, + 64, + 1, + 64, + 300, + ), + ( + 1, + 2048, + 2048, + 1000, + ), + ( + 1, + 2048, + 2048, + 512, + ), + ( + 1, + 256, + 1024, + 1, + 1024, + 256, + ), + ( + 1, + 256, + 256, + 1, + 256, + 256, + ), + ( + 1, + 3, + 3, + 12, + ), + ( + 1, + 300, + 2048, + 1, + 2048, + 512, + ), + ( + 1, + 3072, + 3072, + 768, + ), + ( + 1, + 32, + 1, + 1, + 1, + 7, + ), + ( + 1, + 320, + 320, + 1280, + ), + ( + 1, + 384, + 384, + 512, + ), + ( + 1, + 4096, + 256, + 1, + 256, + 64, + ), + ( + 1, + 4096, + 4096, + 1024, + ), + ( + 1, + 4096, + 64, + 1, + 64, + 256, + ), + ( + 1, + 4800, + 512, + 1, + 512, + 128, + ), + ( + 1, + 512, + 512, + 1000, + ), + ( + 1, + 512, + 512, + 1024, + ), + ( + 1, + 512, + 512, + 2048, + ), + ( + 1, + 512, + 512, + 32128, + ), + ( + 1, + 512, + 512, + 384, + ), + ( + 1, + 512, + 512, + 512, + ), + ( + 1, + 64, + 1, + 1, + 1, + 32, + ), + ( + 1, + 64, + 64, + 12, + ), + ( + 1, + 64, + 64, + 128, + ), + ( + 1, + 768, + 768, + 1, + ), + ( + 1, + 768, + 768, + 1000, + ), + ( + 1, + 768, + 768, + 1536, + ), + ( + 1, + 768, + 768, + 2, + ), + ( + 1, + 768, + 768, + 3, + ), + ( + 1, + 768, + 768, + 3072, + ), + ( + 1, + 768, + 768, + 32128, + ), + ( + 1, + 768, + 768, + 512, + ), + ( + 1, + 768, + 768, + 768, + ), + ( + 1, + 784, + 784, + 128, + ), + ( + 1, + 9216, + 9216, + 128, + ), + ( + 10, + 1024, + 1024, + 1024, + ), + ( + 10, + 1024, + 1024, + 4096, + ), + ( + 10, + 2048, + 2048, + 512, + ), + ( + 10, + 3072, + 3072, + 768, + ), + ( + 10, + 4096, + 4096, + 1024, + ), + ( + 10, + 512, + 512, + 2048, + ), + ( + 10, + 512, + 512, + 512, + ), + ( + 10, + 768, + 768, + 250002, + ), + ( + 10, + 768, + 768, + 3072, + ), + ( + 10, + 768, + 768, + 768, + ), + ( + 100, + 192, + 192, + 4, + ), + ( + 100, + 192, + 192, + 92, + ), + ( + 100, + 2048, + 2048, + 256, + ), + ( + 100, + 256, + 256, + 2048, + ), + ( + 100, + 256, + 256, + 256, + ), + ( + 1024, + 160, + 160, + 160, + ), + ( + 1024, + 160, + 160, + 640, + ), + ( + 1024, + 2560, + 2560, + 640, + ), + ( + 1024, + 640, + 640, + 5120, + ), + ( + 1024, + 640, + 640, + 640, + ), + ( + 12, + 1, + 1, + 12, + 1, + 64, + ), + ( + 12, + 1, + 10, + 12, + 10, + 64, + ), + ( + 12, + 1, + 64, + 12, + 64, + 1, + ), + ( + 12, + 1, + 64, + 12, + 64, + 10, + ), + ( + 12, + 10, + 64, + 12, + 64, + 10, + ), + ( + 12, + 12, + 12, + 12, + 12, + 64, + ), + ( + 12, + 12, + 64, + 12, + 64, + 12, + ), + ( + 12, + 128, + 128, + 768, + ), + ( + 12, + 14, + 14, + 12, + 14, + 64, + ), + ( + 12, + 14, + 64, + 12, + 64, + 14, + ), + ( + 12, + 16, + 16, + 12, + 16, + 64, + ), + ( + 12, + 16, + 64, + 12, + 64, + 16, + ), + ( + 12, + 197, + 197, + 12, + 197, + 64, + ), + ( + 12, + 197, + 64, + 12, + 64, + 197, + ), + ( + 12, + 201, + 201, + 12, + 201, + 64, + ), + ( + 12, + 201, + 64, + 12, + 64, + 201, + ), + ( + 12, + 25, + 25, + 12, + 25, + 64, + ), + ( + 12, + 25, + 64, + 12, + 64, + 25, + ), + ( + 12, + 3072, + 3072, + 768, + ), + ( + 12, + 50, + 50, + 12, + 50, + 64, + ), + ( + 12, + 50, + 64, + 12, + 64, + 50, + ), + ( + 12, + 7, + 64, + 12, + 64, + 7, + ), + ( + 12, + 7, + 7, + 12, + 7, + 64, + ), + ( + 12, + 768, + 768, + 2, + ), + ( + 12, + 768, + 768, + 3072, + ), + ( + 12, + 768, + 768, + 768, + ), + ( + 12, + 8, + 64, + 12, + 64, + 8, + ), + ( + 12, + 8, + 8, + 12, + 8, + 64, + ), + ( + 12, + 9, + 64, + 12, + 64, + 9, + ), + ( + 12, + 9, + 9, + 12, + 9, + 64, + ), + ( + 1200, + 320, + 320, + 1280, + ), + ( + 1200, + 320, + 320, + 320, + ), + ( + 14, + 128, + 128, + 768, + ), + ( + 14, + 2048, + 2048, + 512, + ), + ( + 14, + 3072, + 3072, + 768, + ), + ( + 14, + 512, + 512, + 2048, + ), + ( + 14, + 512, + 512, + 512, + ), + ( + 14, + 768, + 768, + 2, + ), + ( + 14, + 768, + 768, + 3072, + ), + ( + 14, + 768, + 768, + 768, + ), + ( + 1445, + 192, + 192, + 192, + ), + ( + 1445, + 192, + 192, + 768, + ), + ( + 1445, + 768, + 768, + 192, + ), + ( + 15, + 1024, + 1024, + 512, + ), + ( + 15, + 384, + 384, + 512, + ), + ( + 15, + 512, + 512, + 1024, + ), + ( + 15, + 512, + 512, + 384, + ), + ( + 16, + 1, + 1, + 16, + 1, + 64, + ), + ( + 16, + 1, + 10, + 16, + 10, + 64, + ), + ( + 16, + 1, + 64, + 16, + 64, + 1, + ), + ( + 16, + 1, + 64, + 16, + 64, + 10, + ), + ( + 16, + 10, + 10, + 16, + 10, + 64, + ), + ( + 16, + 10, + 64, + 16, + 64, + 10, + ), + ( + 16, + 19, + 19, + 16, + 19, + 64, + ), + ( + 16, + 19, + 64, + 16, + 64, + 19, + ), + ( + 16, + 197, + 197, + 16, + 197, + 64, + ), + ( + 16, + 197, + 64, + 16, + 64, + 197, + ), + ( + 16, + 256, + 256, + 16, + 256, + 64, + ), + ( + 16, + 256, + 64, + 16, + 64, + 256, + ), + ( + 16, + 3072, + 3072, + 768, + ), + ( + 16, + 32, + 32, + 16, + 32, + 96, + ), + ( + 16, + 5, + 5, + 16, + 5, + 64, + ), + ( + 16, + 5, + 64, + 16, + 64, + 5, + ), + ( + 16, + 6, + 6, + 16, + 6, + 64, + ), + ( + 16, + 6, + 64, + 16, + 64, + 6, + ), + ( + 16, + 7, + 64, + 16, + 64, + 7, + ), + ( + 16, + 7, + 7, + 16, + 7, + 64, + ), + ( + 16, + 768, + 768, + 3072, + ), + ( + 16, + 768, + 768, + 768, + ), + ( + 16, + 9, + 128, + 16, + 128, + 9, + ), + ( + 16, + 9, + 64, + 16, + 64, + 9, + ), + ( + 16, + 9, + 9, + 16, + 9, + 128, + ), + ( + 16, + 9, + 9, + 16, + 9, + 64, + ), + ( + 16384, + 32, + 32, + 128, + ), + ( + 16384, + 32, + 32, + 32, + ), + ( + 19, + 1024, + 1024, + 1024, + ), + ( + 19, + 1024, + 1024, + 256008, + ), + ( + 19, + 1024, + 1024, + 4096, + ), + ( + 19, + 4096, + 4096, + 1024, + ), + ( + 19200, + 64, + 64, + 256, + ), + ( + 19200, + 64, + 64, + 64, + ), + ( + 197, + 1024, + 1024, + 1024, + ), + ( + 197, + 1024, + 1024, + 4096, + ), + ( + 197, + 3072, + 3072, + 768, + ), + ( + 197, + 4096, + 4096, + 1024, + ), + ( + 197, + 768, + 768, + 3072, + ), + ( + 197, + 768, + 768, + 768, + ), + ( + 2, + 4096, + 256, + 2, + 256, + 32, + ), + ( + 2, + 4096, + 32, + 2, + 32, + 256, + ), + ( + 2, + 4800, + 300, + 2, + 300, + 64, + ), + ( + 2, + 4800, + 64, + 2, + 64, + 300, + ), + ( + 2, + 512, + 512, + 1, + ), + ( + 2, + 512, + 512, + 512, + ), + ( + 201, + 3072, + 3072, + 768, + ), + ( + 201, + 768, + 768, + 3072, + ), + ( + 201, + 768, + 768, + 768, + ), + ( + 2048, + 768, + 768, + 1280, + ), + ( + 2048, + 768, + 768, + 256, + ), + ( + 2048, + 768, + 768, + 262, + ), + ( + 2048, + 768, + 768, + 768, + ), + ( + 25, + 3072, + 3072, + 768, + ), + ( + 25, + 768, + 768, + 2, + ), + ( + 25, + 768, + 768, + 3072, + ), + ( + 25, + 768, + 768, + 768, + ), + ( + 256, + 1024, + 1024, + 1024, + ), + ( + 256, + 1024, + 1024, + 2, + ), + ( + 256, + 1024, + 1024, + 4096, + ), + ( + 256, + 1280, + 1280, + 10240, + ), + ( + 256, + 1280, + 1280, + 1280, + ), + ( + 256, + 1280, + 1280, + 256, + ), + ( + 256, + 1280, + 1280, + 768, + ), + ( + 256, + 160, + 160, + 160, + ), + ( + 256, + 256, + 256, + 1024, + ), + ( + 256, + 256, + 256, + 256, + ), + ( + 256, + 256, + 256, + 512, + ), + ( + 256, + 32, + 32, + 32, + ), + ( + 256, + 4096, + 4096, + 1024, + ), + ( + 256, + 512, + 512, + 256, + ), + ( + 256, + 5120, + 5120, + 1280, + ), + ( + 256, + 64, + 64, + 64, + ), + ( + 256, + 768, + 768, + 512, + ), + ( + 3, + 1445, + 1445, + 3, + 1445, + 64, + ), + ( + 3, + 1445, + 64, + 3, + 64, + 1445, + ), + ( + 300, + 128, + 128, + 128, + ), + ( + 300, + 320, + 320, + 320, + ), + ( + 300, + 512, + 512, + 2048, + ), + ( + 300, + 512, + 512, + 512, + ), + ( + 300, + 64, + 64, + 64, + ), + ( + 32, + 11008, + 11008, + 4096, + ), + ( + 32, + 1536, + 1536, + 1536, + ), + ( + 32, + 1536, + 1536, + 250880, + ), + ( + 32, + 1536, + 1536, + 4608, + ), + ( + 32, + 1536, + 1536, + 6144, + ), + ( + 32, + 32, + 128, + 32, + 128, + 32, + ), + ( + 32, + 32, + 32, + 32, + 32, + 128, + ), + ( + 32, + 4096, + 4096, + 11008, + ), + ( + 32, + 4096, + 4096, + 32000, + ), + ( + 32, + 4096, + 4096, + 4096, + ), + ( + 32, + 6144, + 6144, + 1536, + ), + ( + 4096, + 1280, + 1280, + 320, + ), + ( + 4096, + 320, + 320, + 2560, + ), + ( + 4096, + 320, + 320, + 320, + ), + ( + 4096, + 64, + 64, + 256, + ), + ( + 4096, + 64, + 64, + 64, + ), + ( + 4800, + 128, + 128, + 128, + ), + ( + 4800, + 128, + 128, + 512, + ), + ( + 5, + 1024, + 1024, + 1024, + ), + ( + 5, + 1024, + 1024, + 3072, + ), + ( + 5, + 1024, + 1024, + 4096, + ), + ( + 5, + 1024, + 1024, + 51200, + ), + ( + 5, + 1024, + 256, + 5, + 256, + 32, + ), + ( + 5, + 1024, + 32, + 5, + 32, + 256, + ), + ( + 5, + 1200, + 300, + 5, + 300, + 64, + ), + ( + 5, + 1200, + 64, + 5, + 64, + 300, + ), + ( + 5, + 4096, + 4096, + 1024, + ), + ( + 50, + 3072, + 3072, + 768, + ), + ( + 50, + 768, + 768, + 3072, + ), + ( + 50, + 768, + 768, + 768, + ), + ( + 6, + 1, + 1, + 6, + 1, + 64, + ), + ( + 6, + 1, + 15, + 6, + 15, + 64, + ), + ( + 6, + 1, + 64, + 6, + 64, + 1, + ), + ( + 6, + 1, + 64, + 6, + 64, + 15, + ), + ( + 6, + 100, + 256, + 6, + 256, + 256, + ), + ( + 6, + 100, + 256, + 6, + 256, + 92, + ), + ( + 6, + 1024, + 1024, + 1024, + ), + ( + 6, + 1024, + 1024, + 4096, + ), + ( + 6, + 1024, + 1024, + 512, + ), + ( + 6, + 15, + 15, + 6, + 15, + 64, + ), + ( + 6, + 15, + 64, + 6, + 64, + 15, + ), + ( + 6, + 4096, + 4096, + 1024, + ), + ( + 6, + 512, + 512, + 1024, + ), + ( + 6, + 512, + 512, + 50272, + ), + ( + 600, + 256, + 256, + 256, + ), + ( + 600, + 256, + 256, + 4, + ), + ( + 64, + 1280, + 1280, + 10240, + ), + ( + 64, + 1280, + 1280, + 1280, + ), + ( + 64, + 5120, + 5120, + 1280, + ), + ( + 64, + 9, + 64, + 64, + 64, + 9, + ), + ( + 64, + 9, + 9, + 64, + 9, + 64, + ), + ( + 7, + 18176, + 18176, + 4544, + ), + ( + 7, + 3072, + 3072, + 768, + ), + ( + 7, + 4544, + 4544, + 18176, + ), + ( + 7, + 4544, + 4544, + 4544, + ), + ( + 7, + 4544, + 4544, + 4672, + ), + ( + 7, + 4544, + 4544, + 65024, + ), + ( + 7, + 768, + 768, + 2, + ), + ( + 7, + 768, + 768, + 2304, + ), + ( + 7, + 768, + 768, + 3072, + ), + ( + 7, + 768, + 768, + 768, + ), + ( + 71, + 7, + 64, + 71, + 64, + 7, + ), + ( + 71, + 7, + 7, + 71, + 7, + 64, + ), + ( + 8, + 1, + 1, + 8, + 1, + 64, + ), + ( + 8, + 1, + 10, + 8, + 10, + 64, + ), + ( + 8, + 1, + 64, + 8, + 64, + 1, + ), + ( + 8, + 1, + 64, + 8, + 64, + 10, + ), + ( + 8, + 10, + 10, + 8, + 10, + 64, + ), + ( + 8, + 10, + 64, + 8, + 64, + 10, + ), + ( + 8, + 100, + 100, + 8, + 100, + 32, + ), + ( + 8, + 100, + 32, + 8, + 32, + 100, + ), + ( + 8, + 100, + 32, + 8, + 32, + 920, + ), + ( + 8, + 100, + 920, + 8, + 920, + 32, + ), + ( + 8, + 1024, + 1024, + 8, + 1024, + 80, + ), + ( + 8, + 1024, + 80, + 8, + 80, + 1024, + ), + ( + 8, + 1024, + 80, + 8, + 80, + 9, + ), + ( + 8, + 1024, + 9, + 8, + 9, + 80, + ), + ( + 8, + 2048, + 256, + 8, + 256, + 96, + ), + ( + 8, + 2048, + 32, + 8, + 32, + 256, + ), + ( + 8, + 256, + 160, + 8, + 160, + 256, + ), + ( + 8, + 256, + 160, + 8, + 160, + 9, + ), + ( + 8, + 256, + 2048, + 8, + 2048, + 160, + ), + ( + 8, + 256, + 256, + 8, + 256, + 160, + ), + ( + 8, + 256, + 256, + 8, + 256, + 32, + ), + ( + 8, + 256, + 32, + 8, + 32, + 2048, + ), + ( + 8, + 256, + 32, + 8, + 32, + 256, + ), + ( + 8, + 256, + 9, + 8, + 9, + 160, + ), + ( + 8, + 300, + 300, + 8, + 300, + 64, + ), + ( + 8, + 300, + 64, + 8, + 64, + 300, + ), + ( + 8, + 4096, + 40, + 8, + 40, + 4096, + ), + ( + 8, + 4096, + 40, + 8, + 40, + 9, + ), + ( + 8, + 4096, + 4096, + 8, + 4096, + 40, + ), + ( + 8, + 4096, + 9, + 8, + 9, + 40, + ), + ( + 8, + 64, + 160, + 8, + 160, + 64, + ), + ( + 8, + 64, + 160, + 8, + 160, + 9, + ), + ( + 8, + 64, + 64, + 8, + 64, + 160, + ), + ( + 8, + 64, + 9, + 8, + 9, + 160, + ), + ( + 8, + 920, + 32, + 8, + 32, + 920, + ), + ( + 8, + 920, + 920, + 8, + 920, + 32, + ), + ( + 9, + 1024, + 1024, + 1024, + ), + ( + 9, + 1024, + 1024, + 128, + ), + ( + 9, + 1024, + 1024, + 4096, + ), + ( + 9, + 128, + 128, + 1024, + ), + ( + 9, + 128, + 128, + 2048, + ), + ( + 9, + 128, + 128, + 30000, + ), + ( + 9, + 128, + 128, + 4096, + ), + ( + 9, + 128, + 128, + 768, + ), + ( + 9, + 16384, + 16384, + 4096, + ), + ( + 9, + 2048, + 2048, + 128, + ), + ( + 9, + 2048, + 2048, + 2048, + ), + ( + 9, + 2048, + 2048, + 8192, + ), + ( + 9, + 3072, + 3072, + 768, + ), + ( + 9, + 4096, + 4096, + 1024, + ), + ( + 9, + 4096, + 4096, + 128, + ), + ( + 9, + 4096, + 4096, + 16384, + ), + ( + 9, + 4096, + 4096, + 4096, + ), + ( + 9, + 768, + 768, + 128, + ), + ( + 9, + 768, + 768, + 1280, + ), + ( + 9, + 768, + 768, + 3072, + ), + ( + 9, + 768, + 768, + 320, + ), + ( + 9, + 768, + 768, + 640, + ), + ( + 9, + 768, + 768, + 768, + ), + ( + 9, + 8192, + 8192, + 2048, + ), + ( + 920, + 1, + 256, + 920, + 256, + 256, + ), + ( + 920, + 2048, + 2048, + 256, + ), + ( + 920, + 256, + 256, + 2048, + ), + ( + 920, + 256, + 256, + 256, + ), + ], + "core_grid": [False], + "dtype": [ttnn.float32, ttnn.bfloat16], }, } -def run( - params, - core_grid, - *, - device, -) -> list: +def run_matmul(device, params, core_grid, dtype): if core_grid == False: grid = None else: @@ -373,8 +2626,8 @@ def run( torch_input_tensor1 = torch.rand(shape1, dtype=torch.float32) torch_output_tensor = torch.matmul(torch_input_tensor0, torch_input_tensor1) - input_tensor0 = ttnn.from_torch(torch_input_tensor0, dtype=ttnn.float32, layout=ttnn.TILE_LAYOUT, device=device) - input_tensor1 = ttnn.from_torch(torch_input_tensor1, dtype=ttnn.float32, layout=ttnn.TILE_LAYOUT, device=device) + input_tensor0 = ttnn.from_torch(torch_input_tensor0, dtype=dtype, layout=ttnn.TILE_LAYOUT, device=device) + input_tensor1 = ttnn.from_torch(torch_input_tensor1, dtype=dtype, layout=ttnn.TILE_LAYOUT, device=device) start_time = start_measuring_time() output_tensor = ttnn.matmul(input_tensor0, input_tensor1, core_grid=grid) @@ -382,3 +2635,34 @@ def run( e2e_perf = stop_measuring_time(start_time) expected_pcc = 0.99 return [check_with_pcc(torch_output_tensor, output_tensor, expected_pcc), e2e_perf] + + +@pytest.mark.parametrize("params", parameters["pytorch"]["params"]) +@pytest.mark.parametrize("core_grid", parameters["pytorch"]["core_grid"]) +@pytest.mark.parametrize("dtype", parameters["pytorch"]["dtype"]) +def test_pytorch(device, params, core_grid, dtype): + run_matmul(device, params, core_grid, dtype) + + +@pytest.mark.parametrize("params", parameters["gpt"]["params"]) +@pytest.mark.parametrize("core_grid", parameters["gpt"]["core_grid"]) +@pytest.mark.parametrize("dtype", parameters["gpt"]["dtype"]) +def test_gpt(device, params, core_grid, dtype): + run_matmul(device, params, core_grid, dtype) + + +@pytest.mark.parametrize("params", parameters["forge"]["params"]) +@pytest.mark.parametrize("core_grid", parameters["forge"]["core_grid"]) +@pytest.mark.parametrize("dtype", parameters["forge"]["dtype"]) +def test_forge(device, params, core_grid, dtype): + run_matmul(device, params, core_grid, dtype) + + +def run( + params, + core_grid, + dtype, + *, + device, +) -> list: + return run_matmul(device, params, core_grid, dtype) diff --git a/tests/sweep_framework/sweeps/reduction/traces/argmax_traces.py b/tests/sweep_framework/sweeps/reduction/traces/argmax_traces.py index 07e7d542ad8e..799b9e5ff043 100644 --- a/tests/sweep_framework/sweeps/reduction/traces/argmax_traces.py +++ b/tests/sweep_framework/sweeps/reduction/traces/argmax_traces.py @@ -4,8 +4,8 @@ from typing import Optional, Tuple +import pytest import torch - import ttnn from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time @@ -14,7 +14,7 @@ TIMEOUT = 15 parameters = { - "default": { + "pytorch": { "height": [1, 2], "width": [7, 51865], "dim": [-1], @@ -24,15 +24,7 @@ } -def run( - height, - width, - dim, - dtype, - layout, - *, - device, -) -> list: +def run_argmax(device, height, width, dim, dtype, layout): torch_input_tensor = torch.rand([height, width], dtype=torch.float32) torch_output_tensor = torch.argmax(torch_input_tensor, dim) @@ -42,5 +34,26 @@ def run( output_tensor = ttnn.argmax(input_tensor, dim=dim) output_tensor = ttnn.to_torch(output_tensor) e2e_perf = stop_measuring_time(start_time) - expected_pcc = 0.9999 + expected_pcc = 0.999 return [check_with_pcc(torch_output_tensor, output_tensor, expected_pcc), e2e_perf] + + +@pytest.mark.parametrize("height", parameters["pytorch"]["height"]) +@pytest.mark.parametrize("width", parameters["pytorch"]["width"]) +@pytest.mark.parametrize("dim", parameters["pytorch"]["dim"]) +@pytest.mark.parametrize("dtype", parameters["pytorch"]["dtype"]) +@pytest.mark.parametrize("layout", parameters["pytorch"]["layout"]) +def test_pytorch(device, height, width, dim, dtype, layout): + run_argmax(device, height, width, dim, dtype, layout) + + +def run( + height, + width, + dim, + dtype, + layout, + *, + device, +) -> list: + return run_argmax(device, height, width, dim, dtype, layout) diff --git a/tests/sweep_framework/sweeps/reduction/traces/max_traces.py b/tests/sweep_framework/sweeps/reduction/traces/max_traces.py index 93bd11879376..4ac5ebfb6935 100644 --- a/tests/sweep_framework/sweeps/reduction/traces/max_traces.py +++ b/tests/sweep_framework/sweeps/reduction/traces/max_traces.py @@ -4,8 +4,8 @@ from typing import Optional, Tuple +import pytest import torch - import ttnn from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time @@ -14,31 +14,376 @@ TIMEOUT = 15 parameters = { - "default": { - "height": [25], - "width": [4], + "pytorch": { + "params": [ + ((25, 4), None, False), + ], + "dtype": [ttnn.float32, ttnn.bfloat16], + "layout": [ttnn.TILE_LAYOUT, ttnn.ROW_MAJOR_LAYOUT], + }, + "forge": { + "params": [ + ( + ( + 1, + 1, + 16384, + 256, + ), + (3), + False, + ), + ( + ( + 1, + 1, + 19200, + 300, + ), + (3), + False, + ), + ( + ( + 1, + 10, + ), + (1), + False, + ), + ( + ( + 1, + 12, + 1, + 1, + ), + (3), + False, + ), + ( + ( + 1, + 12, + 1, + 10, + ), + (3), + False, + ), + ( + ( + 1, + 12, + 10, + 10, + ), + (3), + False, + ), + ( + ( + 1, + 12, + 197, + 197, + ), + (3), + False, + ), + ( + ( + 1, + 12, + 201, + 201, + ), + (3), + False, + ), + ( + ( + 1, + 12, + 8, + 8, + ), + (3), + False, + ), + ( + ( + 1, + 16, + 1, + 1, + ), + (3), + False, + ), + ( + ( + 1, + 16, + 1, + 10, + ), + (3), + False, + ), + ( + ( + 1, + 16, + 10, + 10, + ), + (3), + False, + ), + ( + ( + 1, + 16, + 197, + 197, + ), + (3), + False, + ), + ( + ( + 1, + 16, + 32, + 32, + ), + (3), + False, + ), + ( + ( + 1, + 16, + 5, + 5, + ), + (3), + False, + ), + ( + ( + 1, + 2, + 4096, + 256, + ), + (3), + False, + ), + ( + ( + 1, + 2, + 4800, + 300, + ), + (3), + False, + ), + ( + ( + 1, + 5, + 1024, + 256, + ), + (3), + False, + ), + ( + ( + 1, + 5, + 1200, + 300, + ), + (3), + False, + ), + ( + ( + 1, + 6, + 1, + 1, + ), + (3), + False, + ), + ( + ( + 1, + 6, + 1, + 15, + ), + (3), + False, + ), + ( + ( + 1, + 6, + 15, + 15, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 1, + 1, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 1, + 10, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 10, + 10, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 2048, + 256, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 256, + 2048, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 256, + 256, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 300, + 300, + ), + (3), + False, + ), + ( + ( + 8, + 100, + 100, + ), + (2), + False, + ), + ( + ( + 8, + 100, + 920, + ), + (2), + False, + ), + ( + ( + 8, + 920, + 920, + ), + (2), + False, + ), + ], "dtype": [ttnn.float32, ttnn.bfloat16], "layout": [ttnn.TILE_LAYOUT, ttnn.ROW_MAJOR_LAYOUT], - } + }, } -def run( - height, - width, - dtype, - layout, - *, - device, -) -> list: - torch_input_tensor = torch.rand([height, width], dtype=torch.float32) - torch_output_tensor = torch.max(torch_input_tensor) +def run_max(device, params, dtype, layout): + [input_shape, dim, keepdim] = params + torch_input_tensor = torch.rand(input_shape, dtype=torch.float32) + print(f"inputs: a{torch_input_tensor} b{dim} c{keepdim}") + if dim is None: + assert not keepdim + torch_output_tensor = torch.max(torch_input_tensor).values + else: + torch_output_tensor = torch.max(torch_input_tensor, dim=dim, keepdim=keepdim).values input_tensor = ttnn.from_torch(torch_input_tensor, dtype=dtype, layout=layout, device=device) start_time = start_measuring_time() - output_tensor = ttnn.max(input_tensor) + output_tensor = ttnn.max(input_tensor, dim=dim, keepdim=keepdim) output_tensor = ttnn.to_torch(output_tensor) e2e_perf = stop_measuring_time(start_time) - expected_pcc = 0.9999 + expected_pcc = 0.999 return [check_with_pcc(torch_output_tensor, output_tensor, expected_pcc), e2e_perf] + + +@pytest.mark.parametrize("params", parameters["pytorch"]["params"]) +@pytest.mark.parametrize("dtype", parameters["pytorch"]["dtype"]) +@pytest.mark.parametrize("layout", parameters["pytorch"]["layout"]) +def test_pytorch(device, params, dtype, layout): + run_max(device, params, dtype, layout) + + +@pytest.mark.parametrize("params", parameters["forge"]["params"]) +@pytest.mark.parametrize("dtype", parameters["forge"]["dtype"]) +@pytest.mark.parametrize("layout", parameters["forge"]["layout"]) +def test_forge(device, params, dtype, layout): + run_max(device, params, dtype, layout) + + +def run( + params, + dtype, + layout, + *, + device, +) -> list: + return run_max(device, params, dtype, layout) diff --git a/tests/sweep_framework/sweeps/reduction/traces/mean_traces.py b/tests/sweep_framework/sweeps/reduction/traces/mean_traces.py index 921bb7f4c979..b0e890b02996 100644 --- a/tests/sweep_framework/sweeps/reduction/traces/mean_traces.py +++ b/tests/sweep_framework/sweeps/reduction/traces/mean_traces.py @@ -4,8 +4,8 @@ from typing import Optional, Tuple +import pytest import torch - import ttnn from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time @@ -14,7 +14,7 @@ TIMEOUT = 15 parameters = { - "default": { + "pytorch": { "params": [ ((1, 1, 1024), (-1), True), ((1, 1, 512), (-1), True), @@ -108,11 +108,7 @@ } -def run( - params, - *, - device, -) -> list: +def run_mean(device, params): [input_shape, dim, keepdim] = params torch_input_tensor = torch.rand(input_shape, dtype=torch.float32) torch_output_tensor = torch.mean(torch_input_tensor, dim, keepdim) @@ -123,5 +119,18 @@ def run( output_tensor = ttnn.mean(input_tensor, dim=dim, keepdim=keepdim) output_tensor = ttnn.to_torch(output_tensor) e2e_perf = stop_measuring_time(start_time) - expected_pcc = 0.9999 + expected_pcc = 0.999 return [check_with_pcc(torch_output_tensor, output_tensor, expected_pcc), e2e_perf] + + +@pytest.mark.parametrize("params", parameters["pytorch"]["params"]) +def test_pytorch(device, params): + run_mean(device, params) + + +def run( + params, + *, + device, +) -> list: + return run_mean(device, params) diff --git a/tests/sweep_framework/sweeps/reduction/traces/sum_traces.py b/tests/sweep_framework/sweeps/reduction/traces/sum_traces.py index 94b0c4c4c154..9078b78229ca 100644 --- a/tests/sweep_framework/sweeps/reduction/traces/sum_traces.py +++ b/tests/sweep_framework/sweeps/reduction/traces/sum_traces.py @@ -4,8 +4,8 @@ from typing import Optional, Tuple +import pytest import torch - import ttnn from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time @@ -14,7 +14,7 @@ TIMEOUT = 15 parameters = { - "default": { + "pytorch": { "params": [ ((1, 1, 768), (0, 1), True), ((1, 1000), (0), True), @@ -73,15 +73,561 @@ ((2, 1), None, False), ((1), None, False), ], - } + }, + "forge": { + "params": [ + ( + ( + 1, + 1, + 1024, + ), + (2), + False, + ), + ( + ( + 1, + 1, + 16384, + 256, + ), + (3), + False, + ), + ( + ( + 1, + 1, + 19200, + 300, + ), + (3), + False, + ), + ( + ( + 1, + 1, + 512, + ), + (2), + False, + ), + ( + ( + 1, + 1, + 768, + ), + (2), + False, + ), + ( + ( + 1, + 10, + ), + (1), + False, + ), + ( + ( + 1, + 10, + 1024, + ), + (2), + False, + ), + ( + ( + 1, + 10, + 512, + ), + (2), + False, + ), + ( + ( + 1, + 10, + 768, + ), + (2), + False, + ), + ( + ( + 1, + 12, + 1, + 1, + ), + (3), + False, + ), + ( + ( + 1, + 12, + 1, + 10, + ), + (3), + False, + ), + ( + ( + 1, + 12, + 10, + 10, + ), + (3), + False, + ), + ( + ( + 1, + 12, + 16, + ), + (1), + False, + ), + ( + ( + 1, + 12, + 16, + ), + (2), + False, + ), + ( + ( + 1, + 12, + 197, + 197, + ), + (3), + False, + ), + ( + ( + 1, + 12, + 201, + 201, + ), + (3), + False, + ), + ( + ( + 1, + 12, + 8, + 8, + ), + (3), + False, + ), + ( + ( + 1, + 120, + 40, + 40, + ), + (2), + False, + ), + ( + ( + 1, + 1280, + 7, + 7, + ), + (2), + False, + ), + ( + ( + 1, + 15, + 512, + ), + (2), + False, + ), + ( + ( + 1, + 16, + 1, + 1, + ), + (3), + False, + ), + ( + ( + 1, + 16, + 1, + 10, + ), + (3), + False, + ), + ( + ( + 1, + 16, + 10, + 10, + ), + (3), + False, + ), + ( + ( + 1, + 16, + 197, + 197, + ), + (3), + False, + ), + ( + ( + 1, + 16, + 32, + 32, + ), + (3), + False, + ), + ( + ( + 1, + 16, + 5, + 5, + ), + (3), + False, + ), + ( + ( + 1, + 196, + 1024, + ), + (1), + False, + ), + ( + ( + 1, + 196, + 768, + ), + (1), + False, + ), + ( + ( + 1, + 2, + 4096, + 256, + ), + (3), + False, + ), + ( + ( + 1, + 2, + 4800, + 300, + ), + (3), + False, + ), + ( + ( + 1, + 2048, + 7, + 7, + ), + (2), + False, + ), + ( + ( + 1, + 32, + 4096, + ), + (2), + False, + ), + ( + ( + 1, + 480, + 10, + 10, + ), + (2), + False, + ), + ( + ( + 1, + 480, + 20, + 20, + ), + (2), + False, + ), + ( + ( + 1, + 5, + 1024, + 256, + ), + (3), + False, + ), + ( + ( + 1, + 5, + 1200, + 300, + ), + (3), + False, + ), + ( + ( + 1, + 512, + ), + (1), + False, + ), + ( + ( + 1, + 512, + 256, + ), + (2), + False, + ), + ( + ( + 1, + 512, + 7, + 7, + ), + (2), + False, + ), + ( + ( + 1, + 6, + 1, + 1, + ), + (3), + False, + ), + ( + ( + 1, + 6, + 1, + 15, + ), + (3), + False, + ), + ( + ( + 1, + 6, + 15, + 15, + ), + (3), + False, + ), + ( + ( + 1, + 672, + 10, + 10, + ), + (2), + False, + ), + ( + ( + 1, + 672, + 20, + 20, + ), + (2), + False, + ), + ( + ( + 1, + 72, + 40, + 40, + ), + (2), + False, + ), + ( + ( + 1, + 8, + 1, + 1, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 1, + 10, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 10, + 10, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 2048, + 256, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 256, + 2048, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 256, + 256, + ), + (3), + False, + ), + ( + ( + 1, + 8, + 300, + 300, + ), + (3), + False, + ), + ( + ( + 19, + 256008, + ), + (1), + False, + ), + ( + ( + 2, + 512, + ), + (1), + False, + ), + ( + ( + 8, + 100, + 100, + ), + (2), + False, + ), + ( + ( + 8, + 100, + 920, + ), + (2), + False, + ), + ( + ( + 8, + 920, + 920, + ), + (2), + False, + ), + ], + }, } -def run( - params, - *, - device, -) -> list: +def run_sum(device, params): [input_shape, dim, keepdim] = params torch_input_tensor = torch.rand(input_shape, dtype=torch.float32) torch_output_tensor = torch.sum(torch_input_tensor, dim, keepdim) @@ -92,5 +638,23 @@ def run( output_tensor = ttnn.sum(input_tensor, dim=dim, keepdim=keepdim) output_tensor = ttnn.to_torch(output_tensor) e2e_perf = stop_measuring_time(start_time) - expected_pcc = 0.9999 + expected_pcc = 0.999 return [check_with_pcc(torch_output_tensor, output_tensor, expected_pcc), e2e_perf] + + +@pytest.mark.parametrize("params", parameters["pytorch"]["params"]) +def test_pytorch(device, params): + run_sum(device, params) + + +@pytest.mark.parametrize("params", parameters["forge"]["params"]) +def test_forge(device, params): + run_sum(device, params) + + +def run( + params, + *, + device, +) -> list: + return run_sum(device, params) diff --git a/tests/sweep_framework/sweeps/reduction/traces/topk_traces.py b/tests/sweep_framework/sweeps/reduction/traces/topk_traces.py index 51de7b89117e..9a4492c6dffe 100644 --- a/tests/sweep_framework/sweeps/reduction/traces/topk_traces.py +++ b/tests/sweep_framework/sweeps/reduction/traces/topk_traces.py @@ -4,8 +4,8 @@ from typing import Optional, Tuple +import pytest import torch - import ttnn from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time @@ -14,7 +14,7 @@ TIMEOUT = 15 parameters = { - "default": { + "pytorch": { "params": [ ((1, 5), 3), ((1, 32), 3), @@ -25,11 +25,7 @@ } -def run( - params, - *, - device, -) -> list: +def run_topk(device, params): [input_shape, k] = params torch_input_tensor = torch.rand(input_shape, dtype=torch.float32) torch_output_tensor = torch.topk(torch_input_tensor, k) @@ -40,5 +36,18 @@ def run( output_tensor = ttnn.topk(input_tensor, k) output_tensor = ttnn.to_torch(output_tensor) e2e_perf = stop_measuring_time(start_time) - expected_pcc = 0.9999 + expected_pcc = 0.999 return [check_with_pcc(torch_output_tensor, output_tensor, expected_pcc), e2e_perf] + + +@pytest.mark.parametrize("params", parameters["pytorch"]["params"]) +def test_pytorch(device, params): + run_topk(device, params) + + +def run( + params, + *, + device, +) -> list: + return run_topk(device, params) From 808b43befbbd121be0a73055e433c8a4d8876481 Mon Sep 17 00:00:00 2001 From: Kalaivani Baskar <156762498+KalaivaniMCW@users.noreply.github.com> Date: Wed, 18 Dec 2024 22:34:33 +0530 Subject: [PATCH 49/87] #10034: Binary shift operators (#16055) ### Ticket Link to Github Issue #10034 ### Problem description Binary left/right shift operators ### What's changed add ttnn support for sfpu binary bitwise shift operators ### Checklist - [x] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12372840148 https://github.com/tenstorrent/tt-metal/actions/runs/12394449160 - [ ] Blackhole Post commit (if applicable) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [x] New/Existing tests provide coverage for changes --- .../eltwise/test_binary_composite.py | 109 +++++++++++++++++- .../operations/eltwise/test_binary_fp32.py | 46 +++++++- .../ttnn/operations/eltwise/binary/binary.cpp | 2 + .../eltwise/binary/binary_composite.hpp | 60 ++++++++++ .../eltwise/binary/binary_pybind.hpp | 16 +++ .../eltwise/binary/common/binary_op_types.hpp | 4 +- .../eltwise/binary/common/binary_op_utils.cpp | 8 ++ .../binary/device/binary_composite_op.cpp | 80 +++++++++++++ .../binary/device/binary_device_operation.cpp | 2 + .../compute/eltwise_binary_sfpu_kernel.cpp | 6 +- .../ttnn/operations/eltwise/unary/unary.hpp | 2 - .../operations/eltwise/unary/unary_pybind.hpp | 80 ------------- 12 files changed, 329 insertions(+), 86 deletions(-) diff --git a/tests/ttnn/unit_tests/operations/eltwise/test_binary_composite.py b/tests/ttnn/unit_tests/operations/eltwise/test_binary_composite.py index f7eff8fbd929..97cff73907d3 100644 --- a/tests/ttnn/unit_tests/operations/eltwise/test_binary_composite.py +++ b/tests/ttnn/unit_tests/operations/eltwise/test_binary_composite.py @@ -16,7 +16,6 @@ from models.utility_functions import is_grayskull, skip_for_grayskull from tests.tt_eager.python_api_testing.sweep_tests import ( comparison_funcs, - generation_funcs, ) @@ -1205,3 +1204,111 @@ def test_binary_prelu_1D_weight(input_shapes, weight, device): golden_tensor = golden_function(in_data1, weight) assert_with_pcc(golden_tensor, output_tensor, 0.999) + + +@pytest.mark.parametrize( + "input_shapes", + ( + (torch.Size([1, 1, 32, 32])), + (torch.Size([64, 64])), + (torch.Size([1, 1, 320, 384])), + (torch.Size([1, 3, 320, 384])), + ), +) +@skip_for_grayskull("Unsupported in Grayskull") +def test_binary_left_shift(input_shapes, device): + torch.manual_seed(213919) + in_data1 = torch.randint(-1000, 1000, input_shapes, dtype=torch.int32) + in_data2 = torch.randint(-20, 50, input_shapes, dtype=torch.int32) + input_tensor1 = ttnn.from_torch(in_data1, dtype=ttnn.int32, layout=ttnn.TILE_LAYOUT, device=device) + input_tensor2 = ttnn.from_torch(in_data2, dtype=ttnn.int32, layout=ttnn.TILE_LAYOUT, device=device) + + output_tensor = ttnn.bitwise_left_shift(input_tensor1, input_tensor2) + golden_function = ttnn.get_golden_function(ttnn.bitwise_left_shift) + golden_tensor = golden_function(in_data1, in_data2) + output_tensor = ttnn.to_torch(output_tensor) + + pcc = ttnn.pearson_correlation_coefficient(golden_tensor, output_tensor) + assert pcc >= 0.99 + + +@pytest.mark.parametrize( + "input_shapes", + ( + (torch.Size([1, 1, 32, 32])), + (torch.Size([64, 64])), + (torch.Size([1, 1, 320, 384])), + (torch.Size([1, 3, 320, 384])), + ), +) +@skip_for_grayskull("Unsupported in Grayskull") +def test_binary_right_shift(input_shapes, device): + torch.manual_seed(213919) + in_data1 = torch.randint(-1000, 1000, input_shapes, dtype=torch.int32) + in_data2 = torch.randint(0, 31, input_shapes, dtype=torch.int32) + input_tensor1 = ttnn.from_torch(in_data1, dtype=ttnn.int32, layout=ttnn.TILE_LAYOUT, device=device) + input_tensor2 = ttnn.from_torch(in_data2, dtype=ttnn.int32, layout=ttnn.TILE_LAYOUT, device=device) + + output_tensor = ttnn.bitwise_right_shift(input_tensor1, input_tensor2) + golden_function = ttnn.get_golden_function(ttnn.bitwise_right_shift) + golden_tensor = golden_function(in_data1, in_data2) + output_tensor = ttnn.to_torch(output_tensor) + + pcc = ttnn.pearson_correlation_coefficient(golden_tensor, output_tensor) + assert pcc >= 0.99 + + +@pytest.mark.parametrize( + "input_shapes", + ( + (torch.Size([1, 1, 32, 32])), + (torch.Size([64, 64])), + (torch.Size([1, 1, 320, 384])), + (torch.Size([1, 3, 320, 384])), + ), +) +@pytest.mark.parametrize( + "scalar", + {random.randint(0, 31)}, +) +@skip_for_grayskull("Unsupported in Grayskull") +def test_unary_left_shift(input_shapes, device, scalar): + torch.manual_seed(213919) + in_data1 = torch.randint(-1000, 1000, input_shapes, dtype=torch.int32) + input_tensor1 = ttnn.from_torch(in_data1, dtype=ttnn.int32, layout=ttnn.TILE_LAYOUT, device=device) + + output_tensor = ttnn.bitwise_left_shift(input_tensor1, scalar) + golden_function = ttnn.get_golden_function(ttnn.bitwise_left_shift) + golden_tensor = golden_function(in_data1, scalar) + output_tensor = ttnn.to_torch(output_tensor) + + pcc = ttnn.pearson_correlation_coefficient(golden_tensor, output_tensor) + assert pcc >= 0.99 + + +@pytest.mark.parametrize( + "input_shapes", + ( + (torch.Size([1, 1, 32, 32])), + (torch.Size([64, 64])), + (torch.Size([1, 1, 320, 384])), + (torch.Size([1, 3, 320, 384])), + ), +) +@pytest.mark.parametrize( + "scalar", + {random.randint(0, 31)}, +) +@skip_for_grayskull("Unsupported in Grayskull") +def test_unary_right_shift(input_shapes, device, scalar): + torch.manual_seed(213919) + in_data1 = torch.randint(-1000, 1000, input_shapes, dtype=torch.int32) + input_tensor1 = ttnn.from_torch(in_data1, dtype=ttnn.int32, layout=ttnn.TILE_LAYOUT, device=device) + + output_tensor = ttnn.bitwise_right_shift(input_tensor1, scalar) + golden_function = ttnn.get_golden_function(ttnn.bitwise_right_shift) + golden_tensor = golden_function(in_data1, scalar) + output_tensor = ttnn.to_torch(output_tensor) + + pcc = ttnn.pearson_correlation_coefficient(golden_tensor, output_tensor) + assert pcc >= 0.99 diff --git a/tests/ttnn/unit_tests/operations/eltwise/test_binary_fp32.py b/tests/ttnn/unit_tests/operations/eltwise/test_binary_fp32.py index c66b4536f2b9..2bd3cf956ac0 100644 --- a/tests/ttnn/unit_tests/operations/eltwise/test_binary_fp32.py +++ b/tests/ttnn/unit_tests/operations/eltwise/test_binary_fp32.py @@ -123,7 +123,7 @@ def test_mul_fp32(device, ttnn_function): @pytest.mark.parametrize( "ttnn_function", [ - ttnn.div, + ttnn.divide, ], ) # Torch num/ 0 = inf and 0/0 nan; TT num/ 0 = inf and 0/0=nan; in fp32 tile @@ -551,3 +551,47 @@ def test_bitwise_xor(device, ttnn_function): status = ttnn.pearson_correlation_coefficient(z_torch, tt_out) >= 0.999 assert status + + +@skip_for_grayskull("Unsupported dtype for Grayskull") +@pytest.mark.parametrize( + "ttnn_function", + [ + ttnn.bitwise_left_shift, + ], +) +def test_bitwise_left_shift(device, ttnn_function): + x_torch = torch.tensor([[99, 3, 100, 1, 72, 0, -100, 22, 12, 1000]], dtype=torch.int32) + y_torch = torch.tensor([[1, 2, 31, 4, 5, 0, -20, 1, -3, -25]], dtype=torch.int32) + golden_fn = ttnn.get_golden_function(ttnn_function) + z_torch = golden_fn(x_torch, y_torch) + x_tt = ttnn.from_torch(x_torch, dtype=ttnn.int32, layout=ttnn.TILE_LAYOUT, device=device) + y_tt = ttnn.from_torch(y_torch, dtype=ttnn.int32, layout=ttnn.TILE_LAYOUT, device=device) + z_tt = ttnn.from_torch(z_torch, dtype=ttnn.int32, layout=ttnn.TILE_LAYOUT, device=device) + z_tt_out = ttnn.bitwise_left_shift(x_tt, y_tt) + tt_out = ttnn.to_torch(z_tt_out) + + status = ttnn.pearson_correlation_coefficient(z_torch, tt_out) >= 0.999 + assert status + + +@skip_for_grayskull("Unsupported dtype for Grayskull") +@pytest.mark.parametrize( + "ttnn_function", + [ + ttnn.bitwise_right_shift, + ], +) +def test_bitwise_right_shift(device, ttnn_function): + x_torch = torch.tensor([[19, 3, 101, 21, 47, 0]], dtype=torch.int32) + y_torch = torch.tensor([[5, 2, 31, 4, 5, 0]], dtype=torch.int32) + golden_fn = ttnn.get_golden_function(ttnn_function) + z_torch = golden_fn(x_torch, y_torch) + x_tt = ttnn.from_torch(x_torch, dtype=ttnn.int32, layout=ttnn.TILE_LAYOUT, device=device) + y_tt = ttnn.from_torch(y_torch, dtype=ttnn.int32, layout=ttnn.TILE_LAYOUT, device=device) + z_tt = ttnn.from_torch(z_torch, dtype=ttnn.int32, layout=ttnn.TILE_LAYOUT, device=device) + z_tt_out = ttnn.bitwise_right_shift(x_tt, y_tt) + tt_out = ttnn.to_torch(z_tt_out) + + status = ttnn.pearson_correlation_coefficient(z_torch, tt_out) >= 0.999 + assert status diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/binary.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary/binary.cpp index 7ed428e147c9..e0593b7f52ff 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/binary.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/binary.cpp @@ -478,5 +478,7 @@ template struct BinaryOperationSfpu; template struct BinaryOperationSfpu; template struct BinaryOperationSfpu; template struct BinaryOperationSfpu; +template struct BinaryOperationSfpu; +template struct BinaryOperationSfpu; } // namespace ttnn::operations::binary diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/binary_composite.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary/binary_composite.hpp index c89bf48fae65..1981218a5e4e 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/binary_composite.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/binary_composite.hpp @@ -441,6 +441,62 @@ struct ExecuteBitwiseXor { const std::optional& optional_output_tensor = std::nullopt); }; +struct ExecuteBitwiseLeftShift { + static Tensor invoke( + uint8_t queue_id, + const Tensor& input_tensor_a_arg, + const Tensor& input_tensor_b_arg, + const std::optional& memory_config = std::nullopt, + const std::optional& optional_output_tensor = std::nullopt); + + static Tensor invoke( + const Tensor& input_tensor_a_arg, + const Tensor& input_tensor_b_arg, + const std::optional& memory_config = std::nullopt, + const std::optional& optional_output_tensor = std::nullopt); + + static Tensor invoke( + uint8_t queue_id, + const Tensor& input_tensor, + int32_t input_b, + const std::optional& memory_config = std::nullopt, + const std::optional& optional_output_tensor = std::nullopt); + + static Tensor invoke( + const Tensor& input_tensor, + int32_t input_b, + const std::optional& memory_config = std::nullopt, + const std::optional& optional_output_tensor = std::nullopt); +}; + +struct ExecuteBitwiseRightShift { + static Tensor invoke( + uint8_t queue_id, + const Tensor& input_tensor_a_arg, + const Tensor& input_tensor_b_arg, + const std::optional& memory_config = std::nullopt, + const std::optional& optional_output_tensor = std::nullopt); + + static Tensor invoke( + const Tensor& input_tensor_a_arg, + const Tensor& input_tensor_b_arg, + const std::optional& memory_config = std::nullopt, + const std::optional& optional_output_tensor = std::nullopt); + + static Tensor invoke( + uint8_t queue_id, + const Tensor& input_tensor, + int32_t input_b, + const std::optional& memory_config = std::nullopt, + const std::optional& optional_output_tensor = std::nullopt); + + static Tensor invoke( + const Tensor& input_tensor, + int32_t input_b, + const std::optional& memory_config = std::nullopt, + const std::optional& optional_output_tensor = std::nullopt); +}; + } // namespace binary } // namespace operations @@ -499,6 +555,10 @@ constexpr auto rsub = ttnn::register_operation_with_auto_launch_op<"ttnn::rsub", constexpr auto bitwise_and = ttnn::register_operation_with_auto_launch_op<"ttnn::bitwise_and", operations::binary::ExecuteBitwiseAnd>(); constexpr auto bitwise_or = ttnn::register_operation_with_auto_launch_op<"ttnn::bitwise_or", operations::binary::ExecuteBitwiseOr>(); constexpr auto bitwise_xor = ttnn::register_operation_with_auto_launch_op<"ttnn::bitwise_xor", operations::binary::ExecuteBitwiseXor>(); +constexpr auto bitwise_left_shift = ttnn:: + register_operation_with_auto_launch_op<"ttnn::bitwise_left_shift", operations::binary::ExecuteBitwiseLeftShift>(); +constexpr auto bitwise_right_shift = ttnn:: + register_operation_with_auto_launch_op<"ttnn::bitwise_right_shift", operations::binary::ExecuteBitwiseRightShift>(); constexpr auto pow = ttnn::register_operation_with_auto_launch_op<"ttnn::pow", operations::binary::ExecutePower>(); } // namespace ttnn diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/binary_pybind.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary/binary_pybind.hpp index 37b9e1f5c72e..24475b40ff77 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/binary_pybind.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/binary_pybind.hpp @@ -1572,6 +1572,22 @@ void py_module(py::module& module) { ". ", R"doc(INT32)doc"); + detail::bind_bitwise_binary_ops_operation( + module, + ttnn::bitwise_left_shift, + R"doc(Perform bitwise_left_shift operation on :attr:`input_tensor_a` by :attr:`input_tensor_b` and returns the tensor with the same layout as :attr:`input_tensor_a`. :attr:`input_tensor_b` has shift_bits which are integers within range (0, 31))doc", + R"doc(\mathrm{{output\_tensor}}_i = \verb|bitwise_and|(\mathrm{{input\_tensor\_a, input\_tensor\_b}}))doc", + ". ", + R"doc(INT32)doc"); + + detail::bind_bitwise_binary_ops_operation( + module, + ttnn::bitwise_right_shift, + R"doc(Perform bitwise_right_shift operation on :attr:`input_tensor_a` by :attr:`input_tensor_b` and returns the tensor with the same layout as :attr:`input_tensor_a`. :attr:`input_tensor_b` has shift_bits which are integers within range (0, 31))doc", + R"doc(\mathrm{{output\_tensor}}_i = \verb|bitwise_and|(\mathrm{{input\_tensor\_a, input\_tensor\_b}}))doc", + ". ", + R"doc(INT32)doc"); + auto prim_module = module.def_submodule("prim", "Primitive binary operations"); detail::bind_primitive_binary_operation( diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/common/binary_op_types.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary/common/binary_op_types.hpp index 35c306ad12cb..bee6a240300b 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/common/binary_op_types.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/common/binary_op_types.hpp @@ -29,6 +29,8 @@ enum class BinaryOpType { POWER, BITWISE_XOR, BITWISE_AND, - BITWISE_OR + BITWISE_OR, + LEFT_SHIFT, + RIGHT_SHIFT }; } diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/common/binary_op_utils.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary/common/binary_op_utils.cpp index 644baf5aec3a..8efea1c20f49 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/common/binary_op_utils.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/common/binary_op_utils.cpp @@ -204,6 +204,14 @@ std::map get_defines_fp32( new_defines.insert({"BITWISE_INIT", fmt::format("binary_bitwise_tile_init();")}); op_name = "xor_binary_tile"; break; + case BinaryOpType::LEFT_SHIFT: + new_defines.insert({"SHIFT_INIT", fmt::format("binary_shift_tile_init();")}); + op_name = "binary_left_shift_tile"; + break; + case BinaryOpType::RIGHT_SHIFT: + new_defines.insert({"SHIFT_INIT", fmt::format("binary_shift_tile_init();")}); + op_name = "binary_right_shift_tile"; + break; case BinaryOpType::LOGADDEXP: // PRE_IN0_0 ===> Applies prescaling for first input // PRE_IN1_0 ====> Applies prescaling for second input diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.cpp index a54d641d6e6c..9ce1d5d8be9f 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_composite_op.cpp @@ -900,4 +900,84 @@ Tensor ExecuteBitwiseXor::invoke( std::move(optional_output_tensor)); } +// Bitwise Left Shift +Tensor ExecuteBitwiseLeftShift::invoke( + uint8_t queue_id, + const Tensor& input_tensor_a, + const Tensor& input_tensor_b, + const std::optional& memory_config, + const std::optional& optional_output_tensor) { + return BinaryOperationSfpu::invoke( + queue_id, input_tensor_a, input_tensor_b, std::nullopt, memory_config, optional_output_tensor); +} + +Tensor ExecuteBitwiseLeftShift::invoke( + const Tensor& input_tensor_a, + const Tensor& input_tensor_b, + const std::optional& memory_config, + const std::optional& optional_output_tensor) { + return ExecuteBitwiseLeftShift::invoke( + ttnn::DefaultQueueId, input_tensor_a, input_tensor_b, memory_config, optional_output_tensor); +} + +Tensor ExecuteBitwiseLeftShift::invoke( + uint8_t queue_id, + const Tensor& input_tensor_a, + const int32_t input_b, + const std::optional& memory_config, + const std::optional& optional_output_tensor) { + return ttnn::operations::unary:: + ExecuteUnaryWithIntegerParameter::invoke( + queue_id, input_tensor_a, input_b, memory_config, optional_output_tensor); +} + +Tensor ExecuteBitwiseLeftShift::invoke( + const Tensor& input_tensor_a, + const int32_t input_b, + const std::optional& memory_config, + const std::optional& optional_output_tensor) { + return ExecuteBitwiseLeftShift::invoke( + ttnn::DefaultQueueId, input_tensor_a, input_b, memory_config, std::move(optional_output_tensor)); +} + +// Bitwise Right Shift +Tensor ExecuteBitwiseRightShift::invoke( + uint8_t queue_id, + const Tensor& input_tensor_a, + const Tensor& input_tensor_b, + const std::optional& memory_config, + const std::optional& optional_output_tensor) { + return BinaryOperationSfpu::invoke( + queue_id, input_tensor_a, input_tensor_b, std::nullopt, memory_config, optional_output_tensor); +} + +Tensor ExecuteBitwiseRightShift::invoke( + const Tensor& input_tensor_a, + const Tensor& input_tensor_b, + const std::optional& memory_config, + const std::optional& optional_output_tensor) { + return ExecuteBitwiseRightShift::invoke( + ttnn::DefaultQueueId, input_tensor_a, input_tensor_b, memory_config, optional_output_tensor); +} + +Tensor ExecuteBitwiseRightShift::invoke( + uint8_t queue_id, + const Tensor& input_tensor_a, + const int32_t input_b, + const std::optional& memory_config, + const std::optional& optional_output_tensor) { + return ttnn::operations::unary:: + ExecuteUnaryWithIntegerParameter::invoke( + queue_id, input_tensor_a, input_b, memory_config, optional_output_tensor); +} + +Tensor ExecuteBitwiseRightShift::invoke( + const Tensor& input_tensor_a, + const int32_t input_b, + const std::optional& memory_config, + const std::optional& optional_output_tensor) { + return ExecuteBitwiseRightShift::invoke( + ttnn::DefaultQueueId, input_tensor_a, input_b, memory_config, std::move(optional_output_tensor)); +} + } // namespace ttnn::operations::binary diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.cpp index a889ed6c7796..a88ed52047b4 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.cpp @@ -35,6 +35,8 @@ namespace utils { case BinaryOpType::LTE: case BinaryOpType::EQ: case BinaryOpType::NE: return (a == DataType::FLOAT32 && b == DataType::FLOAT32); + case BinaryOpType::LEFT_SHIFT: + case BinaryOpType::RIGHT_SHIFT: case BinaryOpType::BITWISE_XOR: case BinaryOpType::BITWISE_AND: case BinaryOpType::BITWISE_OR: return (a == DataType::INT32 && b == DataType::INT32); diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/kernels/compute/eltwise_binary_sfpu_kernel.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/kernels/compute/eltwise_binary_sfpu_kernel.cpp index f714d939fb57..6970e4aa4980 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/device/kernels/compute/eltwise_binary_sfpu_kernel.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/kernels/compute/eltwise_binary_sfpu_kernel.cpp @@ -11,11 +11,12 @@ #include "compute_kernel_api/eltwise_unary/eltwise_unary.h" #include "compute_kernel_api/eltwise_binary_sfpu.h" #include "compute_kernel_api/binary_bitwise_sfpu.h" +#include "compute_kernel_api/binary_shift.h" #include "compute_kernel_api/add_int32_sfpu.h" #define PRE_SCALE defined SFPU_OP_INIT_PRE_IN0_0 || defined SFPU_OP_INIT_PRE_IN1_0 -#if defined(ADD_INT32_INIT) || defined(BITWISE_INIT) +#if defined(ADD_INT32_INIT) || defined(BITWISE_INIT) || defined(SHIFT_INIT) #define INT32_INIT #endif @@ -120,6 +121,9 @@ void MAIN { #ifdef BITWISE_INIT BITWISE_INIT #endif +#ifdef SHIFT_INIT + SHIFT_INIT +#endif #ifdef BINARY_SFPU_OP BINARY_SFPU_OP diff --git a/ttnn/cpp/ttnn/operations/eltwise/unary/unary.hpp b/ttnn/cpp/ttnn/operations/eltwise/unary/unary.hpp index abe259f03de6..12bd0d72d54e 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/unary/unary.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/unary/unary.hpp @@ -365,8 +365,6 @@ REGISTER_UNARY_OPERATION_WITH_FLOAT_PARAMETER(ne_unary, UNARY_NE); // Unaries with integer parameter REGISTER_UNARY_OPERATION_WITH_INTEGER_PARAMETER(power, POWER, uint32_t); -REGISTER_UNARY_OPERATION_WITH_INTEGER_PARAMETER(bitwise_left_shift, LEFT_SHIFT, int32_t); -REGISTER_UNARY_OPERATION_WITH_INTEGER_PARAMETER(bitwise_right_shift, RIGHT_SHIFT, int32_t); // Other unaries constexpr auto dropout = diff --git a/ttnn/cpp/ttnn/operations/eltwise/unary/unary_pybind.hpp b/ttnn/cpp/ttnn/operations/eltwise/unary/unary_pybind.hpp index d343f42bb92f..0e921edd604d 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/unary/unary_pybind.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/unary/unary_pybind.hpp @@ -493,82 +493,6 @@ void bind_unary_operation_with_float_parameter( py::arg("queue_id") = 0}); } -template -void bind_unary_operation_with_integer_parameter( - py::module& module, - const unary_operation_t& operation, - const std::string& parameter_name, - const std::string& parameter_doc, - const std::string& supported_dtype = "INT32", - const std::string& note = "") { - - auto doc = fmt::format( - R"doc( - Applies {0} to :attr:`input_tensor` element-wise. - - .. math:: - \mathrm{{output\_tensor}}_i = \verb|{0}|(\mathrm{{input\_tensor}}_i) - - Args: - input_tensor (ttnn.Tensor): the input tensor. - {2} (int): {3}. - - Keyword Args: - memory_config (ttnn.MemoryConfig, optional): memory configuration for the operation. Defaults to `None`. - output_tensor (ttnn.Tensor, optional): preallocated output tensor. Defaults to `None`. - queue_id (int, optional): command queue id. Defaults to `0`. - - Returns: - ttnn.Tensor: the output tensor. - - Note: - Supported dtypes, layouts, and ranks: - - .. list-table:: - :header-rows: 1 - - * - Dtypes - - Layouts - - Ranks - * - {4} - - TILE - - 2, 3, 4 - - {5} - - Example: - >>> tensor = ttnn.from_torch(torch.tensor([[1, 2], [3, 4]], dtype=torch.int32), layout=ttnn.TILE_LAYOUT, device=device) - >>> {2} = 5 - >>> output = {1}(tensor, {2}) - )doc", - operation.base_name(), - operation.python_fully_qualified_name(), - parameter_name, - parameter_doc, - supported_dtype, - note); - - bind_registered_operation( - module, - operation, - doc, - ttnn::pybind_overload_t{ - [](const unary_operation_t& self, - const Tensor& input_tensor, - int parameter, - const std::optional& memory_config, - const std::optional& output_tensor, - const uint8_t& queue_id) { - return self(queue_id, input_tensor, parameter, memory_config, output_tensor); - }, - py::arg("input_tensor"), - py::arg(parameter_name.c_str()), - py::kw_only(), - py::arg("memory_config") = std::nullopt, - py::arg("output_tensor") = std::nullopt, - py::arg("queue_id") = 0}); -} - template void bind_unary_operation_with_dim_parameter( @@ -1741,10 +1665,6 @@ void py_module(py::module& module) { "This will carry out ReLU operation at min value instead of the standard 0", R"doc(BFLOAT16)doc", R"doc(System memory is not supported.)doc"); - // Unaries with integer parameter - detail::bind_unary_operation_with_integer_parameter(module, ttnn::bitwise_left_shift, "shift_bits", "integer within range (0, 31)", "INT32", "Support provided for Wormhole_B0 only."); - detail::bind_unary_operation_with_integer_parameter(module, ttnn::bitwise_right_shift, "shift_bits", "integer within range (0, 31)", "INT32", "Support provided for Wormhole_B0 only."); - // Unary ops with dim parameter detail::bind_unary_operation_with_dim_parameter( module, From 3ef683762eaa4bd602ec6f3f33aec875775265c5 Mon Sep 17 00:00:00 2001 From: Nathan Sidwell Date: Wed, 18 Dec 2024 14:24:47 -0500 Subject: [PATCH 50/87] #0: Remove incorrect memory span assert (#16136) ### Ticket n/A ### Problem description This assert was added when kernel data packing was implemented (data load address immediately after text). But that only worked for (non-idle) erisc kernels by accident due to another bug an obsolete workaround. I fixed this with ``` * 434bd8e565 2024-12-13 | #13944: Redesign memory packing API (#15980) ``` By not packing such erisc kernels. This worked in production builds because asserts are disabled, so I didn't fall over this problem. ### What's changed Remove asserts, update comments to reflect reality. working on updating non-idle erisc to allow packing (perhaps CI optimized builds should enable asserts, Remember CMAKE_BUILD_TYPE=RelWithDebInfo doesn't do that) ### Checklist - [Yes] Post commit CI passes - [ ] Blackhole Post commit (if applicable) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] New/Existing tests provide coverage for changes --- tt_metal/impl/program/program.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tt_metal/impl/program/program.cpp b/tt_metal/impl/program/program.cpp index b6b871e3be2c..2517c52c4629 100644 --- a/tt_metal/impl/program/program.cpp +++ b/tt_metal/impl/program/program.cpp @@ -1059,17 +1059,14 @@ void detail::Program_::populate_dispatch_data(Device *device) { for (size_t sub_kernel_index = 0; sub_kernel_index < binaries.size(); ++sub_kernel_index) { const ll_api::memory& kernel_bin = *binaries[sub_kernel_index]; - // Spans are now packed into one - // TODO: code below can be simplified w/ a single span + // TODO: Pack erisc spans too, and then everthing is + // one span uint32_t num_spans = kernel_bin.num_spans(); dst_base_addrs.resize(dst_base_addrs.size() + num_spans); page_offsets.resize(page_offsets.size() + num_spans); lengths.resize(lengths.size() + num_spans); riscvs.resize(riscvs.size() + num_spans); - TT_ASSERT(kernel_bin.num_spans() == 1); - - // TODO: spans are packed into 1 now, just grab it and go kernel_bin.process_spans([&](std::vector::const_iterator mem_ptr, uint64_t dst, uint32_t len) { // Set dst for eth kernels until they move to ring buffer From 8348c262735e8ae2b8f5f88cb48da492516ce1c1 Mon Sep 17 00:00:00 2001 From: Saad Jameel <163029024+sjameelTT@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:08:36 -0500 Subject: [PATCH 51/87] Add forge sweeps for slice and transpose (#16112) ### Ticket #16106: add forge slice sweeps #15945: add forge transpose sweeps ### Problem description There are new input parameters for forge models. These sweeps sweep all of them. ### What's changed So far, Forge is at 99% coverage whereas slice is at 80% coverage. ### Checklist - [ ] Post commit CI passes - [ ] Blackhole Post commit (if applicable) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [ ] New/Existing tests provide coverage for changes --- .github/workflows/ttnn-run-sweeps.yaml | 2 + .../sweeps/data_movement/slice/slice_forge.py | 82 +++++++++++++++++++ .../slice/slice_forge_processed.json | 1 + .../transpose/transpose_forge.py | 60 ++++++++++++++ .../transpose/transpose_forge_processed.json | 1 + 5 files changed, 146 insertions(+) create mode 100644 tests/sweep_framework/sweeps/data_movement/slice/slice_forge.py create mode 100644 tests/sweep_framework/sweeps/data_movement/slice/slice_forge_processed.json create mode 100644 tests/sweep_framework/sweeps/data_movement/transpose/transpose_forge.py create mode 100644 tests/sweep_framework/sweeps/data_movement/transpose/transpose_forge_processed.json diff --git a/.github/workflows/ttnn-run-sweeps.yaml b/.github/workflows/ttnn-run-sweeps.yaml index bd3ea75303a8..aa83fe8f9603 100644 --- a/.github/workflows/ttnn-run-sweeps.yaml +++ b/.github/workflows/ttnn-run-sweeps.yaml @@ -332,10 +332,12 @@ on: - data_movement.concat.concat_pytorch2 - data_movement.slice.slice_pytorch2_rm - data_movement.slice.slice_pytorch2_tiled + - data_movement.slice.slice_forge - data_movement.permute.permute - data_movement.permute.permute_pytorch2_tiled - data_movement.permute.permute_pytorch2_rm - data_movement.transpose.transpose_pytorch2 + - data_movement.transpose.transpose_forge - data_movement.transpose.transpose_interleaved - data_movement.transpose.t_pytorch2 - data_movement.copy.copy diff --git a/tests/sweep_framework/sweeps/data_movement/slice/slice_forge.py b/tests/sweep_framework/sweeps/data_movement/slice/slice_forge.py new file mode 100644 index 000000000000..f17ee7a91c28 --- /dev/null +++ b/tests/sweep_framework/sweeps/data_movement/slice/slice_forge.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple + +import json +import os +import torch +import random +import ttnn + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +TIMEOUT = 10 +random.seed(0) + +# Load the processed slice specs from slice_forge_processed.json +base_dir = os.path.dirname(os.path.abspath(__file__)) +json_path = os.path.join(base_dir, "slice_forge_processed.json") +with open(json_path, "r") as f: + processed_slice_specs = json.load(f) + +parameters = { + "nightly": { + "slice_specs": processed_slice_specs, + "dtype": [ttnn.bfloat16], + "layout": [ttnn.ROW_MAJOR_LAYOUT, ttnn.TILE_LAYOUT], + } +} + + +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["layout"] == ttnn.ROW_MAJOR_LAYOUT: + if test_vector["dtype"] == ttnn.bfloat8_b: + return True, "bfloat8_b not supported with ROW_MAJOR_LAYOUT" + if test_vector["dtype"] == ttnn.bfloat8_b: + if len(test_vector["slice_specs"]["dims"]) < 2: + return True, "bfloat8_b not supported with dims < 2" + + return False, None + + +def run( + slice_specs, + dtype, + layout, + *, + device, +): + device.enable_async(False) + + dims = slice_specs["dims"] + begins = slice_specs["begins"] + ends = slice_specs["ends"] + steps = slice_specs["step"] + + # Create the torch input tensor + tensor = torch_random(dims, -0.1, 0.1, dtype=torch.bfloat16) + + # Construct Python slice objects from begins, ends, steps + indices = [slice(begins[i], ends[i], steps[i]) for i in range(len(begins))] + + # Apply slicing to the torch tensor + torch_output_tensor = tensor[tuple(indices)] + + # Convert the input tensor to TTNN + ttnn_tensor = ttnn.from_torch(tensor, device=device, layout=layout, dtype=dtype) + + # Run the slicing on TTNN + start_time = start_measuring_time() + ttnn_output = ttnn.slice(ttnn_tensor, begins, ends, steps) + e2e_perf = stop_measuring_time(start_time) + + # Convert TTNN output back to torch + ttnn_output_tensor = ttnn.to_torch(ttnn_output) + + return [check_with_pcc(torch_output_tensor, ttnn_output_tensor, 0.999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/data_movement/slice/slice_forge_processed.json b/tests/sweep_framework/sweeps/data_movement/slice/slice_forge_processed.json new file mode 100644 index 000000000000..87763b4e73d6 --- /dev/null +++ b/tests/sweep_framework/sweeps/data_movement/slice/slice_forge_processed.json @@ -0,0 +1 @@ +[{"dims":[196,196,2],"begins":[0,0,0],"ends":[196,196,1],"step":[1,1,1]},{"dims":[196,196,2],"begins":[0,0,0],"ends":[196,196,1],"step":[1,1,1]},{"dims":[196,196,2],"begins":[0,0,1],"ends":[196,196,2],"step":[1,1,1]},{"dims":[196,196,2],"begins":[0,0,1],"ends":[196,196,2],"step":[1,1,1]},{"dims":[197],"begins":[0],"ends":[1],"step":[1]},{"dims":[197],"begins":[0],"ends":[1],"step":[1]},{"dims":[197,197],"begins":[0,0],"ends":[1,197],"step":[1,1]},{"dims":[197,197],"begins":[0,0],"ends":[1,197],"step":[1,1]},{"dims":[197,197],"begins":[0,0],"ends":[197,1],"step":[1,1]},{"dims":[197,197],"begins":[0,0],"ends":[197,1],"step":[1,1]},{"dims":[1,12],"begins":[0,0],"ends":[1,1],"step":[1,1]},{"dims":[1,12],"begins":[0,0],"ends":[1,1],"step":[1,1]},{"dims":[1,16],"begins":[0,0],"ends":[1,1],"step":[1,1]},{"dims":[1,16],"begins":[0,0],"ends":[1,1],"step":[1,1]},{"dims":[1,197,768],"begins":[0,0,0],"ends":[1,1,768],"step":[1,1,1]},{"dims":[1,197,768],"begins":[0,0,0],"ends":[1,1,768],"step":[1,1,1]},{"dims":[1,19],"begins":[0,18],"ends":[1,19],"step":[1,1]},{"dims":[1,19],"begins":[0,18],"ends":[1,19],"step":[1,1]},{"dims":[1,201,768],"begins":[0,0,0],"ends":[1,1,768],"step":[1,1,1]},{"dims":[1,201,768],"begins":[0,0,0],"ends":[1,1,768],"step":[1,1,1]},{"dims":[1,25,768],"begins":[0,0,0],"ends":[1,1,768],"step":[1,1,1]},{"dims":[1,25,768],"begins":[0,0,0],"ends":[1,1,768],"step":[1,1,1]},{"dims":[1,2],"begins":[0,1],"ends":[1,2],"step":[1,1]},{"dims":[1,2],"begins":[0,1],"ends":[1,2],"step":[1,1]},{"dims":[1,2,120,160],"begins":[0,0,0,0],"ends":[1,1,120,160],"step":[1,1,1,1]},{"dims":[1,2,120,160],"begins":[0,0,0,0],"ends":[1,1,120,160],"step":[1,1,1,1]},{"dims":[1,2,120,160],"begins":[0,1,0,0],"ends":[1,2,120,160],"step":[1,1,1,1]},{"dims":[1,2,120,160],"begins":[0,1,0,0],"ends":[1,2,120,160],"step":[1,1,1,1]},{"dims":[1,2,30,40],"begins":[0,0,0,0],"ends":[1,1,30,40],"step":[1,1,1,1]},{"dims":[1,2,30,40],"begins":[0,0,0,0],"ends":[1,1,30,40],"step":[1,1,1,1]},{"dims":[1,2,30,40],"begins":[0,1,0,0],"ends":[1,2,30,40],"step":[1,1,1,1]},{"dims":[1,2,30,40],"begins":[0,1,0,0],"ends":[1,2,30,40],"step":[1,1,1,1]},{"dims":[1,2,60,80],"begins":[0,0,0,0],"ends":[1,1,60,80],"step":[1,1,1,1]},{"dims":[1,2,60,80],"begins":[0,0,0,0],"ends":[1,1,60,80],"step":[1,1,1,1]},{"dims":[1,2,60,80],"begins":[0,1,0,0],"ends":[1,2,60,80],"step":[1,1,1,1]},{"dims":[1,2,60,80],"begins":[0,1,0,0],"ends":[1,2,60,80],"step":[1,1,1,1]},{"dims":[1,32,16,3,96],"begins":[0,0,0,0,0],"ends":[1,32,16,1,96],"step":[1,1,1,1,1]},{"dims":[1,32,16,3,96],"begins":[0,0,0,0,0],"ends":[1,32,16,1,96],"step":[1,1,1,1,1]},{"dims":[1,32,16,3,96],"begins":[0,0,0,1,0],"ends":[1,32,16,2,96],"step":[1,1,1,1,1]},{"dims":[1,32,16,3,96],"begins":[0,0,0,1,0],"ends":[1,32,16,2,96],"step":[1,1,1,1,1]},{"dims":[1,32,16,3,96],"begins":[0,0,0,2,0],"ends":[1,32,16,3,96],"step":[1,1,1,1,1]},{"dims":[1,32,16,3,96],"begins":[0,0,0,2,0],"ends":[1,32,16,3,96],"step":[1,1,1,1,1]},{"dims":[1,4251,192],"begins":[0,0,0],"ends":[1,1,192],"step":[1,1,1]},{"dims":[1,4251,192],"begins":[0,0,0],"ends":[1,1,192],"step":[1,1,1]},{"dims":[1,50,768],"begins":[0,0,0],"ends":[1,1,768],"step":[1,1,1]},{"dims":[1,50,768],"begins":[0,0,0],"ends":[1,1,768],"step":[1,1,1]},{"dims":[1,6],"begins":[0,5],"ends":[1,6],"step":[1,1]},{"dims":[1,6],"begins":[0,5],"ends":[1,6],"step":[1,1]},{"dims":[1,8,768],"begins":[0,0,0],"ends":[1,1,768],"step":[1,1,1]},{"dims":[1,8,768],"begins":[0,0,0],"ends":[1,1,768],"step":[1,1,1]},{"dims":[1,9,768],"begins":[0,0,0],"ends":[1,1,768],"step":[1,1,1]},{"dims":[1,9,768],"begins":[0,0,0],"ends":[1,1,768],"step":[1,1,1]},{"dims":[3234,4],"begins":[0,0],"ends":[3234,1],"step":[1,1]},{"dims":[3234,4],"begins":[0,0],"ends":[3234,1],"step":[1,1]},{"dims":[3234,4],"begins":[0,1],"ends":[3234,2],"step":[1,1]},{"dims":[3234,4],"begins":[0,1],"ends":[3234,2],"step":[1,1]},{"dims":[3234,4],"begins":[0,2],"ends":[3234,3],"step":[1,1]},{"dims":[3234,4],"begins":[0,2],"ends":[3234,3],"step":[1,1]},{"dims":[3234,4],"begins":[0,3],"ends":[3234,4],"step":[1,1]},{"dims":[3234,4],"begins":[0,3],"ends":[3234,4],"step":[1,1]},{"dims":[45],"begins":[44],"ends":[45],"step":[1]},{"dims":[45],"begins":[44],"ends":[45],"step":[1]},{"dims":[5],"begins":[4],"ends":[5],"step":[1]},{"dims":[5],"begins":[4],"ends":[5],"step":[1]},{"dims":[6,1,100,4],"begins":[5,0,0,0],"ends":[6,1,100,4],"step":[1,1,1,1]},{"dims":[6,1,100,4],"begins":[5,0,0,0],"ends":[6,1,100,4],"step":[1,1,1,1]},{"dims":[6,1,100,92],"begins":[5,0,0,0],"ends":[6,1,100,92],"step":[1,1,1,1]},{"dims":[6,1,100,92],"begins":[5,0,0,0],"ends":[6,1,100,92],"step":[1,1,1,1]},{"dims":[196,197],"begins":[0,1],"ends":[196,197],"step":[1,1]},{"dims":[196,197],"begins":[0,1],"ends":[196,197],"step":[1,1]},{"dims":[197,197],"begins":[1,0],"ends":[197,197],"step":[1,1]},{"dims":[197,197],"begins":[1,0],"ends":[197,197],"step":[1,1]},{"dims":[1,1024,5120],"begins":[0,0,0],"ends":[1,1024,2560],"step":[1,1,1]},{"dims":[1,1024,5120],"begins":[0,0,0],"ends":[1,1024,2560],"step":[1,1,1]},{"dims":[1,1024,5120],"begins":[0,0,2560],"ends":[1,1024,5120],"step":[1,1,1]},{"dims":[1,1024,5120],"begins":[0,0,2560],"ends":[1,1024,5120],"step":[1,1,1]},{"dims":[1,1445,192],"begins":[0,1345,0],"ends":[1,1445,192],"step":[1,1,1]},{"dims":[1,1445,192],"begins":[0,1345,0],"ends":[1,1445,192],"step":[1,1,1]},{"dims":[1,145,768],"begins":[0,1,0],"ends":[1,145,768],"step":[1,1,1]},{"dims":[1,145,768],"begins":[0,1,0],"ends":[1,145,768],"step":[1,1,1]},{"dims":[1,14,2],"begins":[0,0,0],"ends":[1,14,1],"step":[1,1,1]},{"dims":[1,14,2],"begins":[0,0,0],"ends":[1,14,1],"step":[1,1,1]},{"dims":[1,14,2],"begins":[0,0,1],"ends":[1,14,2],"step":[1,1,1]},{"dims":[1,14,2],"begins":[0,0,1],"ends":[1,14,2],"step":[1,1,1]},{"dims":[1,185,28,28],"begins":[0,128,0,0],"ends":[1,185,28,28],"step":[1,1,1,1]},{"dims":[1,185,28,28],"begins":[0,128,0,0],"ends":[1,185,28,28],"step":[1,1,1,1]},{"dims":[1,185,28,28],"begins":[0,0,0,0],"ends":[1,128,28,28],"step":[1,1,1,1]},{"dims":[1,185,28,28],"begins":[0,0,0,0],"ends":[1,128,28,28],"step":[1,1,1,1]},{"dims":[1,197,1024],"begins":[0,1,0],"ends":[1,197,1024],"step":[1,1,1]},{"dims":[1,197,1024],"begins":[0,1,0],"ends":[1,197,1024],"step":[1,1,1]},{"dims":[1,197,768],"begins":[0,1,0],"ends":[1,197,768],"step":[1,1,1]},{"dims":[1,197,768],"begins":[0,1,0],"ends":[1,197,768],"step":[1,1,1]},{"dims":[1,19],"begins":[0,0],"ends":[1,18],"step":[1,1]},{"dims":[1,19],"begins":[0,0],"ends":[1,18],"step":[1,1]},{"dims":[1,19],"begins":[0,1],"ends":[1,19],"step":[1,1]},{"dims":[1,19],"begins":[0,1],"ends":[1,19],"step":[1,1]},{"dims":[1,1,1,2],"begins":[0,0,0,0],"ends":[1,1,1,1],"step":[1,1,1,1]},{"dims":[1,1,1,2],"begins":[0,0,0,0],"ends":[1,1,1,1],"step":[1,1,1,1]},{"dims":[1,1,7,64],"begins":[0,0,0,0],"ends":[1,1,7,32],"step":[1,1,1,1]},{"dims":[1,1,7,64],"begins":[0,0,0,0],"ends":[1,1,7,32],"step":[1,1,1,1]},{"dims":[1,1,7,64],"begins":[0,0,0,32],"ends":[1,1,7,64],"step":[1,1,1,1]},{"dims":[1,1,7,64],"begins":[0,0,0,32],"ends":[1,1,7,64],"step":[1,1,1,1]},{"dims":[1,23,40],"begins":[0,22,0],"ends":[1,23,40],"step":[1,1,1]},{"dims":[1,23,40],"begins":[0,22,0],"ends":[1,23,40],"step":[1,1,1]},{"dims":[1,23,40],"begins":[0,0,39],"ends":[1,23,40],"step":[1,1,1]},{"dims":[1,23,40],"begins":[0,0,39],"ends":[1,23,40],"step":[1,1,1]},{"dims":[1,23,40,128],"begins":[0,0,0,0],"ends":[1,23,40,128],"step":[1,1,1,2]},{"dims":[1,23,40,128],"begins":[0,0,0,0],"ends":[1,23,40,128],"step":[1,1,1,2]},{"dims":[1,23,40,128],"begins":[0,0,0,1],"ends":[1,23,40,128],"step":[1,1,1,2]},{"dims":[1,23,40,128],"begins":[0,0,0,1],"ends":[1,23,40,128],"step":[1,1,1,2]},{"dims":[1,256,10240],"begins":[0,0,0],"ends":[1,256,5120],"step":[1,1,1]},{"dims":[1,256,10240],"begins":[0,0,0],"ends":[1,256,5120],"step":[1,1,1]},{"dims":[1,256,10240],"begins":[0,0,5120],"ends":[1,256,10240],"step":[1,1,1]},{"dims":[1,256,10240],"begins":[0,0,5120],"ends":[1,256,10240],"step":[1,1,1]},{"dims":[1,256,2],"begins":[0,0,0],"ends":[1,256,1],"step":[1,1,1]},{"dims":[1,256,2],"begins":[0,0,0],"ends":[1,256,1],"step":[1,1,1]},{"dims":[1,256,2],"begins":[0,0,1],"ends":[1,256,2],"step":[1,1,1]},{"dims":[1,256,2],"begins":[0,0,1],"ends":[1,256,2],"step":[1,1,1]},{"dims":[1,25,2],"begins":[0,0,0],"ends":[1,25,1],"step":[1,1,1]},{"dims":[1,25,2],"begins":[0,0,0],"ends":[1,25,1],"step":[1,1,1]},{"dims":[1,25,2],"begins":[0,0,1],"ends":[1,25,2],"step":[1,1,1]},{"dims":[1,25,2],"begins":[0,0,1],"ends":[1,25,2],"step":[1,1,1]},{"dims":[1,320],"begins":[0,0],"ends":[1,160],"step":[1,1]},{"dims":[1,320],"begins":[0,0],"ends":[1,160],"step":[1,1]},{"dims":[1,320],"begins":[0,160],"ends":[1,320],"step":[1,1]},{"dims":[1,320],"begins":[0,160],"ends":[1,320],"step":[1,1]},{"dims":[1,32,32,128],"begins":[0,0,0,0],"ends":[1,32,32,64],"step":[1,1,1,1]},{"dims":[1,32,32,128],"begins":[0,0,0,0],"ends":[1,32,32,64],"step":[1,1,1,1]},{"dims":[1,32,32,128],"begins":[0,0,0,64],"ends":[1,32,32,128],"step":[1,1,1,1]},{"dims":[1,32,32,128],"begins":[0,0,0,64],"ends":[1,32,32,128],"step":[1,1,1,1]},{"dims":[1,4096,2560],"begins":[0,0,0],"ends":[1,4096,1280],"step":[1,1,1]},{"dims":[1,4096,2560],"begins":[0,0,0],"ends":[1,4096,1280],"step":[1,1,1]},{"dims":[1,4096,2560],"begins":[0,0,1280],"ends":[1,4096,2560],"step":[1,1,1]},{"dims":[1,4096,2560],"begins":[0,0,1280],"ends":[1,4096,2560],"step":[1,1,1]},{"dims":[1,40],"begins":[0,0],"ends":[1,8],"step":[1,1]},{"dims":[1,40],"begins":[0,0],"ends":[1,8],"step":[1,1]},{"dims":[1,4251,192],"begins":[0,4151,0],"ends":[1,4251,192],"step":[1,1,1]},{"dims":[1,4251,192],"begins":[0,4151,0],"ends":[1,4251,192],"step":[1,1,1]},{"dims":[1,4251,192],"begins":[0,1,0],"ends":[1,4151,192],"step":[1,1,1]},{"dims":[1,4251,192],"begins":[0,1,0],"ends":[1,4151,192],"step":[1,1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,12],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,12],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,14],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,14],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,16],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,16],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,25],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,25],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,256],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,256],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,8],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,8],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,9],"step":[1,1]},{"dims":[1,512],"begins":[0,0],"ends":[1,9],"step":[1,1]},{"dims":[1,514],"begins":[0,0],"ends":[1,10],"step":[1,1]},{"dims":[1,514],"begins":[0,0],"ends":[1,10],"step":[1,1]},{"dims":[1,5,16,32],"begins":[0,0,0,0],"ends":[1,5,16,32],"step":[1,1,1,2]},{"dims":[1,5,16,32],"begins":[0,0,0,0],"ends":[1,5,16,32],"step":[1,1,1,2]},{"dims":[1,5,16,32],"begins":[0,0,0,1],"ends":[1,5,16,32],"step":[1,1,1,2]},{"dims":[1,5,16,32],"begins":[0,0,0,1],"ends":[1,5,16,32],"step":[1,1,1,2]},{"dims":[1,5,16,64],"begins":[0,0,0,0],"ends":[1,5,16,32],"step":[1,1,1,1]},{"dims":[1,5,16,64],"begins":[0,0,0,0],"ends":[1,5,16,32],"step":[1,1,1,1]},{"dims":[1,5,16,64],"begins":[0,0,0,32],"ends":[1,5,16,64],"step":[1,1,1,1]},{"dims":[1,5,16,64],"begins":[0,0,0,32],"ends":[1,5,16,64],"step":[1,1,1,1]},{"dims":[1,5,32],"begins":[0,0,0],"ends":[1,5,16],"step":[1,1,1]},{"dims":[1,5,32],"begins":[0,0,0],"ends":[1,5,16],"step":[1,1,1]},{"dims":[1,5,32],"begins":[0,0,16],"ends":[1,5,32],"step":[1,1,1]},{"dims":[1,5,32],"begins":[0,0,16],"ends":[1,5,32],"step":[1,1,1]},{"dims":[1,5,4,768],"begins":[0,0,0,0],"ends":[1,5,4,256],"step":[1,1,1,1]},{"dims":[1,5,4,768],"begins":[0,0,0,0],"ends":[1,5,4,256],"step":[1,1,1,1]},{"dims":[1,5,4,768],"begins":[0,0,0,256],"ends":[1,5,4,512],"step":[1,1,1,1]},{"dims":[1,5,4,768],"begins":[0,0,0,256],"ends":[1,5,4,512],"step":[1,1,1,1]},{"dims":[1,5,4,768],"begins":[0,0,0,512],"ends":[1,5,4,768],"step":[1,1,1,1]},{"dims":[1,5,4,768],"begins":[0,0,0,512],"ends":[1,5,4,768],"step":[1,1,1,1]},{"dims":[1,64,10240],"begins":[0,0,0],"ends":[1,64,5120],"step":[1,1,1]},{"dims":[1,64,10240],"begins":[0,0,0],"ends":[1,64,5120],"step":[1,1,1]},{"dims":[1,64,10240],"begins":[0,0,5120],"ends":[1,64,10240],"step":[1,1,1]},{"dims":[1,64,10240],"begins":[0,0,5120],"ends":[1,64,10240],"step":[1,1,1]},{"dims":[1,71,7,64],"begins":[0,0,0,0],"ends":[1,71,7,32],"step":[1,1,1,1]},{"dims":[1,71,7,64],"begins":[0,0,0,0],"ends":[1,71,7,32],"step":[1,1,1,1]},{"dims":[1,71,7,64],"begins":[0,0,0,32],"ends":[1,71,7,64],"step":[1,1,1,1]},{"dims":[1,71,7,64],"begins":[0,0,0,32],"ends":[1,71,7,64],"step":[1,1,1,1]},{"dims":[1,77],"begins":[0,0],"ends":[1,7],"step":[1,1]},{"dims":[1,77],"begins":[0,0],"ends":[1,7],"step":[1,1]},{"dims":[1,7,2304],"begins":[0,0,0],"ends":[1,7,768],"step":[1,1,1]},{"dims":[1,7,2304],"begins":[0,0,0],"ends":[1,7,768],"step":[1,1,1]},{"dims":[1,7,2304],"begins":[0,0,1536],"ends":[1,7,2304],"step":[1,1,1]},{"dims":[1,7,2304],"begins":[0,0,1536],"ends":[1,7,2304],"step":[1,1,1]},{"dims":[1,7,2304],"begins":[0,0,768],"ends":[1,7,1536],"step":[1,1,1]},{"dims":[1,7,2304],"begins":[0,0,768],"ends":[1,7,1536],"step":[1,1,1]},{"dims":[1,7,73,64],"begins":[0,0,0,0],"ends":[1,7,71,64],"step":[1,1,1,1]},{"dims":[1,7,73,64],"begins":[0,0,0,0],"ends":[1,7,71,64],"step":[1,1,1,1]},{"dims":[3234,4],"begins":[0,0],"ends":[3234,2],"step":[1,1]},{"dims":[3234,4],"begins":[0,0],"ends":[3234,2],"step":[1,1]},{"dims":[3234,4],"begins":[0,0],"ends":[3234,4],"step":[1,2]},{"dims":[3234,4],"begins":[0,0],"ends":[3234,4],"step":[1,2]},{"dims":[3234,4],"begins":[0,0],"ends":[3234,4],"step":[1,4]},{"dims":[3234,4],"begins":[0,0],"ends":[3234,4],"step":[1,4]},{"dims":[3234,4],"begins":[0,1],"ends":[3234,4],"step":[1,2]},{"dims":[3234,4],"begins":[0,1],"ends":[3234,4],"step":[1,2]},{"dims":[3234,4],"begins":[0,1],"ends":[3234,4],"step":[1,4]},{"dims":[3234,4],"begins":[0,1],"ends":[3234,4],"step":[1,4]},{"dims":[3234,4],"begins":[0,2],"ends":[3234,4],"step":[1,1]},{"dims":[3234,4],"begins":[0,2],"ends":[3234,4],"step":[1,1]},{"dims":[3234,4],"begins":[0,2],"ends":[3234,4],"step":[1,4]},{"dims":[3234,4],"begins":[0,2],"ends":[3234,4],"step":[1,4]},{"dims":[3234,4],"begins":[0,3],"ends":[3234,4],"step":[1,4]},{"dims":[3234,4],"begins":[0,3],"ends":[3234,4],"step":[1,4]},{"dims":[5],"begins":[4],"ends":[5],"step":[1]},{"dims":[5],"begins":[4],"ends":[5],"step":[1]},{"dims":[6],"begins":[5],"ends":[6],"step":[1]},{"dims":[6],"begins":[5],"ends":[6],"step":[1]},{"dims":[732,12],"begins":[0,0],"ends":[729,12],"step":[1,1]},{"dims":[732,12],"begins":[0,0],"ends":[729,12],"step":[1,1]},{"dims":[732,12],"begins":[729,0],"ends":[732,12],"step":[1,1]},{"dims":[732,12],"begins":[729,0],"ends":[732,12],"step":[1,1]},{"dims":[732,16],"begins":[0,0],"ends":[729,16],"step":[1,1]},{"dims":[732,16],"begins":[0,0],"ends":[729,16],"step":[1,1]},{"dims":[732,16],"begins":[729,0],"ends":[732,16],"step":[1,1]},{"dims":[732,16],"begins":[729,0],"ends":[732,16],"step":[1,1]},{"dims":[768],"begins":[0],"ends":[256],"step":[1]},{"dims":[768],"begins":[0],"ends":[256],"step":[1]},{"dims":[768],"begins":[256],"ends":[512],"step":[1]},{"dims":[768],"begins":[256],"ends":[512],"step":[1]},{"dims":[768],"begins":[512],"ends":[768],"step":[1]},{"dims":[768],"begins":[512],"ends":[768],"step":[1]},{"dims":[768,256],"begins":[0,0],"ends":[256,256],"step":[1,1]},{"dims":[768,256],"begins":[0,0],"ends":[256,256],"step":[1,1]},{"dims":[768,256],"begins":[256,0],"ends":[512,256],"step":[1,1]},{"dims":[768,256],"begins":[256,0],"ends":[512,256],"step":[1,1]},{"dims":[768,256],"begins":[512,0],"ends":[768,256],"step":[1,1]},{"dims":[768,256],"begins":[512,0],"ends":[768,256],"step":[1,1]}] diff --git a/tests/sweep_framework/sweeps/data_movement/transpose/transpose_forge.py b/tests/sweep_framework/sweeps/data_movement/transpose/transpose_forge.py new file mode 100644 index 000000000000..5d5a0b03594f --- /dev/null +++ b/tests/sweep_framework/sweeps/data_movement/transpose/transpose_forge.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +import json +import os +import torch +import random +import ttnn + +from typing import Optional, Tuple + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +TIMEOUT = 15 # longer timeout since permute calls transpose recursively +random.seed(0) + +# Load the processed transpose specs from transpose_forge_processed.json +base_dir = os.path.dirname(os.path.abspath(__file__)) +json_path = os.path.join(base_dir, "transpose_forge_processed.json") +with open(json_path, "r") as f: + processed_transpose_specs = json.load(f) + +parameters = { + "traces": { + "transpose_specs": processed_transpose_specs, # use the processed specs + "dtype": [ttnn.bfloat16], + "layout": [ttnn.ROW_MAJOR_LAYOUT, ttnn.TILE_LAYOUT], + } +} + + +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["layout"] == ttnn.ROW_MAJOR_LAYOUT: + if test_vector["dtype"] == ttnn.bfloat8_b: + return True, "bfloat8_b not supported with ROW_MAJOR_LAYOUT" + return False, None + + +def run( + transpose_specs, + dtype, + layout, + *, + device, +): + torch_input_tensor = torch_random( + transpose_specs["shape"], -0.1, 0.1, dtype=torch.bfloat16 + ) # returns a torch tensor + torch_output_tensor = torch.transpose(torch_input_tensor, transpose_specs["dim0"], transpose_specs["dim1"]) + + ttnn_input_tensor = ttnn.from_torch(torch_input_tensor, device=device, dtype=dtype, layout=layout) + + start_time = start_measuring_time() + ttnn_output = ttnn.transpose(ttnn_input_tensor, transpose_specs["dim0"], transpose_specs["dim1"]) + e2e_perf = stop_measuring_time(start_time) + + ttnn_output_tensor = ttnn.to_torch(ttnn_output) + return [check_with_pcc(torch_output_tensor, ttnn_output_tensor, 0.9999), e2e_perf] diff --git a/tests/sweep_framework/sweeps/data_movement/transpose/transpose_forge_processed.json b/tests/sweep_framework/sweeps/data_movement/transpose/transpose_forge_processed.json new file mode 100644 index 000000000000..abd816451317 --- /dev/null +++ b/tests/sweep_framework/sweeps/data_movement/transpose/transpose_forge_processed.json @@ -0,0 +1 @@ +[{"shape":[1,1024,14,14],"dim0":1,"dim1":2},{"shape":[1,14,1024,14],"dim0":2,"dim1":3},{"shape":[1,14,14,1024],"dim0":2,"dim1":3},{"shape":[1,14,1024,14],"dim0":1,"dim1":2},{"shape":[1,1024,14,14],"dim0":1,"dim1":2},{"shape":[1,14,1024,14],"dim0":2,"dim1":3},{"shape":[1,14,14,1024],"dim0":2,"dim1":3},{"shape":[1,14,1024,14],"dim0":1,"dim1":2},{"shape":[1,1024,14,14],"dim0":1,"dim1":2},{"shape":[1,14,1024,14],"dim0":2,"dim1":3},{"shape":[1,7,7,2048],"dim0":2,"dim1":3},{"shape":[1,7,2048,7],"dim0":1,"dim1":2},{"shape":[1,1024,14,14],"dim0":1,"dim1":2},{"shape":[1,14,1024,14],"dim0":2,"dim1":3},{"shape":[1,7,7,2048],"dim0":2,"dim1":3},{"shape":[1,7,2048,7],"dim0":1,"dim1":2},{"shape":[1,1024,14,14],"dim0":1,"dim1":2},{"shape":[1,14,1024,14],"dim0":2,"dim1":3},{"shape":[1,14,14,256],"dim0":2,"dim1":3},{"shape":[1,14,256,14],"dim0":1,"dim1":2},{"shape":[1,1024,14,14],"dim0":1,"dim1":2},{"shape":[1,14,1024,14],"dim0":2,"dim1":3},{"shape":[1,14,14,256],"dim0":2,"dim1":3},{"shape":[1,14,256,14],"dim0":1,"dim1":2},{"shape":[1,1024,14,14],"dim0":1,"dim1":2},{"shape":[1,14,1024,14],"dim0":2,"dim1":3},{"shape":[1,14,14,512],"dim0":2,"dim1":3},{"shape":[1,14,512,14],"dim0":1,"dim1":2},{"shape":[1,1024,14,14],"dim0":1,"dim1":2},{"shape":[1,14,1024,14],"dim0":2,"dim1":3},{"shape":[1,14,14,512],"dim0":2,"dim1":3},{"shape":[1,14,512,14],"dim0":1,"dim1":2},{"shape":[1,1024,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1024,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1024],"dim0":2,"dim1":3},{"shape":[1,16,1024,16],"dim0":1,"dim1":2},{"shape":[1,1024,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1024,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1024],"dim0":2,"dim1":3},{"shape":[1,16,1024,16],"dim0":1,"dim1":2},{"shape":[1,1024,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1024,16],"dim0":2,"dim1":3},{"shape":[1,16,16,255],"dim0":2,"dim1":3},{"shape":[1,16,255,16],"dim0":1,"dim1":2},{"shape":[1,1024,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1024,16],"dim0":2,"dim1":3},{"shape":[1,16,16,255],"dim0":2,"dim1":3},{"shape":[1,16,255,16],"dim0":1,"dim1":2},{"shape":[1,1024,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1024,16],"dim0":2,"dim1":3},{"shape":[1,16,16,512],"dim0":2,"dim1":3},{"shape":[1,16,512,16],"dim0":1,"dim1":2},{"shape":[1,1024,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1024,16],"dim0":2,"dim1":3},{"shape":[1,16,16,512],"dim0":2,"dim1":3},{"shape":[1,16,512,16],"dim0":1,"dim1":2},{"shape":[1,1024,28,28],"dim0":1,"dim1":2},{"shape":[1,28,1024,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,1024,28,28],"dim0":1,"dim1":2},{"shape":[1,28,1024,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,1024,45,80],"dim0":1,"dim1":2},{"shape":[1,45,1024,80],"dim0":2,"dim1":3},{"shape":[1,23,40,2048],"dim0":2,"dim1":3},{"shape":[1,23,2048,40],"dim0":1,"dim1":2},{"shape":[1,1024,45,80],"dim0":1,"dim1":2},{"shape":[1,45,1024,80],"dim0":2,"dim1":3},{"shape":[1,23,40,2048],"dim0":2,"dim1":3},{"shape":[1,23,2048,40],"dim0":1,"dim1":2},{"shape":[1,1024,45,80],"dim0":1,"dim1":2},{"shape":[1,45,1024,80],"dim0":2,"dim1":3},{"shape":[1,45,80,256],"dim0":2,"dim1":3},{"shape":[1,45,256,80],"dim0":1,"dim1":2},{"shape":[1,1024,45,80],"dim0":1,"dim1":2},{"shape":[1,45,1024,80],"dim0":2,"dim1":3},{"shape":[1,45,80,256],"dim0":2,"dim1":3},{"shape":[1,45,256,80],"dim0":1,"dim1":2},{"shape":[1,1024,45,80],"dim0":1,"dim1":2},{"shape":[1,45,1024,80],"dim0":2,"dim1":3},{"shape":[1,45,80,512],"dim0":2,"dim1":3},{"shape":[1,45,512,80],"dim0":1,"dim1":2},{"shape":[1,1024,45,80],"dim0":1,"dim1":2},{"shape":[1,45,1024,80],"dim0":2,"dim1":3},{"shape":[1,45,80,512],"dim0":2,"dim1":3},{"shape":[1,45,512,80],"dim0":1,"dim1":2},{"shape":[1,102,56,56],"dim0":1,"dim1":2},{"shape":[1,56,102,56],"dim0":2,"dim1":3},{"shape":[1,56,56,40],"dim0":2,"dim1":3},{"shape":[1,56,40,56],"dim0":1,"dim1":2},{"shape":[1,102,56,56],"dim0":1,"dim1":2},{"shape":[1,56,102,56],"dim0":2,"dim1":3},{"shape":[1,56,56,40],"dim0":2,"dim1":3},{"shape":[1,56,40,56],"dim0":1,"dim1":2},{"shape":[1,1072,7,7],"dim0":1,"dim1":2},{"shape":[1,7,1072,7],"dim0":2,"dim1":3},{"shape":[1,7,7,462],"dim0":2,"dim1":3},{"shape":[1,7,462,7],"dim0":1,"dim1":2},{"shape":[1,1072,7,7],"dim0":1,"dim1":2},{"shape":[1,7,1072,7],"dim0":2,"dim1":3},{"shape":[1,7,7,462],"dim0":2,"dim1":3},{"shape":[1,7,462,7],"dim0":1,"dim1":2},{"shape":[1,112,20,20],"dim0":1,"dim1":2},{"shape":[1,20,112,20],"dim0":2,"dim1":3},{"shape":[1,20,20,672],"dim0":2,"dim1":3},{"shape":[1,20,672,20],"dim0":1,"dim1":2},{"shape":[1,112,20,20],"dim0":1,"dim1":2},{"shape":[1,20,112,20],"dim0":2,"dim1":3},{"shape":[1,20,20,672],"dim0":2,"dim1":3},{"shape":[1,20,672,20],"dim0":1,"dim1":2},{"shape":[1,116,14,14],"dim0":1,"dim1":2},{"shape":[1,14,116,14],"dim0":2,"dim1":3},{"shape":[1,14,14,40],"dim0":2,"dim1":3},{"shape":[1,14,40,14],"dim0":1,"dim1":2},{"shape":[1,116,14,14],"dim0":1,"dim1":2},{"shape":[1,14,116,14],"dim0":2,"dim1":3},{"shape":[1,14,14,40],"dim0":2,"dim1":3},{"shape":[1,14,40,14],"dim0":1,"dim1":2},{"shape":[1,118,28,28],"dim0":1,"dim1":2},{"shape":[1,28,118,28],"dim0":2,"dim1":3},{"shape":[1,28,28,34],"dim0":2,"dim1":3},{"shape":[1,28,34,28],"dim0":1,"dim1":2},{"shape":[1,118,28,28],"dim0":1,"dim1":2},{"shape":[1,28,118,28],"dim0":2,"dim1":3},{"shape":[1,28,28,34],"dim0":2,"dim1":3},{"shape":[1,28,34,28],"dim0":1,"dim1":2},{"shape":[1,120,1,1],"dim0":1,"dim1":2},{"shape":[1,1,120,1],"dim0":2,"dim1":3},{"shape":[1,1,1,32],"dim0":2,"dim1":3},{"shape":[1,1,32,1],"dim0":1,"dim1":2},{"shape":[1,120,1,1],"dim0":1,"dim1":2},{"shape":[1,1,120,1],"dim0":2,"dim1":3},{"shape":[1,1,1,32],"dim0":2,"dim1":3},{"shape":[1,1,32,1],"dim0":1,"dim1":2},{"shape":[1,120,1,1],"dim0":1,"dim1":2},{"shape":[1,1,120,1],"dim0":2,"dim1":3},{"shape":[1,1,1,480],"dim0":2,"dim1":3},{"shape":[1,1,480,1],"dim0":1,"dim1":2},{"shape":[1,120,1,1],"dim0":1,"dim1":2},{"shape":[1,1,120,1],"dim0":2,"dim1":3},{"shape":[1,1,1,480],"dim0":2,"dim1":3},{"shape":[1,1,480,1],"dim0":1,"dim1":2},{"shape":[1,120,40,40],"dim0":1,"dim1":2},{"shape":[1,40,120,40],"dim0":2,"dim1":3},{"shape":[1,40,40,120],"dim0":2,"dim1":3},{"shape":[1,40,120,40],"dim0":1,"dim1":2},{"shape":[1,120,40,40],"dim0":1,"dim1":2},{"shape":[1,40,120,40],"dim0":2,"dim1":3},{"shape":[1,40,40,120],"dim0":2,"dim1":3},{"shape":[1,40,120,40],"dim0":1,"dim1":2},{"shape":[1,120,40,40],"dim0":1,"dim1":2},{"shape":[1,40,120,40],"dim0":2,"dim1":3},{"shape":[1,40,40,40],"dim0":2,"dim1":3},{"shape":[1,40,40,40],"dim0":1,"dim1":2},{"shape":[1,120,40,40],"dim0":1,"dim1":2},{"shape":[1,40,120,40],"dim0":2,"dim1":3},{"shape":[1,40,40,40],"dim0":2,"dim1":3},{"shape":[1,40,40,40],"dim0":1,"dim1":2},{"shape":[1,122,28,28],"dim0":1,"dim1":2},{"shape":[1,28,122,28],"dim0":2,"dim1":3},{"shape":[1,28,28,46],"dim0":2,"dim1":3},{"shape":[1,28,46,28],"dim0":1,"dim1":2},{"shape":[1,122,28,28],"dim0":1,"dim1":2},{"shape":[1,28,122,28],"dim0":2,"dim1":3},{"shape":[1,28,28,46],"dim0":2,"dim1":3},{"shape":[1,28,46,28],"dim0":1,"dim1":2},{"shape":[1,124,56,56],"dim0":1,"dim1":2},{"shape":[1,56,124,56],"dim0":2,"dim1":3},{"shape":[1,56,56,128],"dim0":2,"dim1":3},{"shape":[1,56,128,56],"dim0":1,"dim1":2},{"shape":[1,124,56,56],"dim0":1,"dim1":2},{"shape":[1,56,124,56],"dim0":2,"dim1":3},{"shape":[1,56,56,128],"dim0":2,"dim1":3},{"shape":[1,56,128,56],"dim0":1,"dim1":2},{"shape":[1,1280,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1280,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,1280,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1280,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,1280,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1280,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,1280,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1280,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,1280,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1280,16],"dim0":2,"dim1":3},{"shape":[1,8,8,1280],"dim0":2,"dim1":3},{"shape":[1,8,1280,8],"dim0":1,"dim1":2},{"shape":[1,1280,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1280,16],"dim0":2,"dim1":3},{"shape":[1,8,8,1280],"dim0":2,"dim1":3},{"shape":[1,8,1280,8],"dim0":1,"dim1":2},{"shape":[1,1280,30,40],"dim0":1,"dim1":2},{"shape":[1,30,1280,40],"dim0":2,"dim1":3},{"shape":[1,30,40,1280],"dim0":2,"dim1":3},{"shape":[1,30,1280,40],"dim0":1,"dim1":2},{"shape":[1,1280,30,40],"dim0":1,"dim1":2},{"shape":[1,30,1280,40],"dim0":2,"dim1":3},{"shape":[1,30,40,1280],"dim0":2,"dim1":3},{"shape":[1,30,1280,40],"dim0":1,"dim1":2},{"shape":[1,1280,32,32],"dim0":1,"dim1":2},{"shape":[1,32,1280,32],"dim0":2,"dim1":3},{"shape":[1,32,32,1280],"dim0":2,"dim1":3},{"shape":[1,32,1280,32],"dim0":1,"dim1":2},{"shape":[1,1280,32,32],"dim0":1,"dim1":2},{"shape":[1,32,1280,32],"dim0":2,"dim1":3},{"shape":[1,32,32,1280],"dim0":2,"dim1":3},{"shape":[1,32,1280,32],"dim0":1,"dim1":2},{"shape":[1,1280,32,32],"dim0":1,"dim1":2},{"shape":[1,32,1280,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,1280,32,32],"dim0":1,"dim1":2},{"shape":[1,32,1280,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,1280,32,32],"dim0":1,"dim1":2},{"shape":[1,32,1280,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,1280,32,32],"dim0":1,"dim1":2},{"shape":[1,32,1280,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,1280,8,8],"dim0":1,"dim1":2},{"shape":[1,8,1280,8],"dim0":2,"dim1":3},{"shape":[1,8,8,1280],"dim0":2,"dim1":3},{"shape":[1,8,1280,8],"dim0":1,"dim1":2},{"shape":[1,1280,8,8],"dim0":1,"dim1":2},{"shape":[1,8,1280,8],"dim0":2,"dim1":3},{"shape":[1,8,8,1280],"dim0":2,"dim1":3},{"shape":[1,8,1280,8],"dim0":1,"dim1":2},{"shape":[1,1280,8,8],"dim0":1,"dim1":2},{"shape":[1,8,1280,8],"dim0":2,"dim1":3},{"shape":[1,8,8,1280],"dim0":2,"dim1":3},{"shape":[1,8,1280,8],"dim0":1,"dim1":2},{"shape":[1,1280,8,8],"dim0":1,"dim1":2},{"shape":[1,8,1280,8],"dim0":2,"dim1":3},{"shape":[1,8,8,1280],"dim0":2,"dim1":3},{"shape":[1,8,1280,8],"dim0":1,"dim1":2},{"shape":[1,128,112,112],"dim0":1,"dim1":2},{"shape":[1,112,128,112],"dim0":2,"dim1":3},{"shape":[1,112,112,128],"dim0":2,"dim1":3},{"shape":[1,112,128,112],"dim0":1,"dim1":2},{"shape":[1,128,112,112],"dim0":1,"dim1":2},{"shape":[1,112,128,112],"dim0":2,"dim1":3},{"shape":[1,112,112,128],"dim0":2,"dim1":3},{"shape":[1,112,128,112],"dim0":1,"dim1":2},{"shape":[1,128,120,160],"dim0":1,"dim1":2},{"shape":[1,120,128,160],"dim0":2,"dim1":3},{"shape":[1,120,160,64],"dim0":2,"dim1":3},{"shape":[1,120,64,160],"dim0":1,"dim1":2},{"shape":[1,128,120,160],"dim0":1,"dim1":2},{"shape":[1,120,128,160],"dim0":2,"dim1":3},{"shape":[1,120,160,64],"dim0":2,"dim1":3},{"shape":[1,120,64,160],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":2,"dim1":3},{"shape":[1,128,128,128],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":2,"dim1":3},{"shape":[1,128,128,128],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":2,"dim1":3},{"shape":[1,64,64,256],"dim0":2,"dim1":3},{"shape":[1,64,256,64],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":2,"dim1":3},{"shape":[1,64,64,256],"dim0":2,"dim1":3},{"shape":[1,64,256,64],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":2,"dim1":3},{"shape":[1,128,128,64],"dim0":2,"dim1":3},{"shape":[1,128,64,128],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":2,"dim1":3},{"shape":[1,128,128,64],"dim0":2,"dim1":3},{"shape":[1,128,64,128],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":2,"dim1":3},{"shape":[1,128,128,64],"dim0":2,"dim1":3},{"shape":[1,128,64,128],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":1,"dim1":2},{"shape":[1,128,128,128],"dim0":2,"dim1":3},{"shape":[1,128,128,64],"dim0":2,"dim1":3},{"shape":[1,128,64,128],"dim0":1,"dim1":2},{"shape":[1,128,180,320],"dim0":1,"dim1":2},{"shape":[1,180,128,320],"dim0":2,"dim1":3},{"shape":[1,90,160,128],"dim0":2,"dim1":3},{"shape":[1,90,128,160],"dim0":1,"dim1":2},{"shape":[1,128,180,320],"dim0":1,"dim1":2},{"shape":[1,180,128,320],"dim0":2,"dim1":3},{"shape":[1,90,160,128],"dim0":2,"dim1":3},{"shape":[1,90,128,160],"dim0":1,"dim1":2},{"shape":[1,128,1,1],"dim0":1,"dim1":2},{"shape":[1,1,128,1],"dim0":2,"dim1":3},{"shape":[1,1,1,128],"dim0":2,"dim1":3},{"shape":[1,1,128,1],"dim0":1,"dim1":2},{"shape":[1,128,1,1],"dim0":1,"dim1":2},{"shape":[1,1,128,1],"dim0":2,"dim1":3},{"shape":[1,1,1,128],"dim0":2,"dim1":3},{"shape":[1,1,128,1],"dim0":1,"dim1":2},{"shape":[1,128,1,1],"dim0":1,"dim1":2},{"shape":[1,1,128,1],"dim0":2,"dim1":3},{"shape":[1,1,1,24],"dim0":2,"dim1":3},{"shape":[1,1,24,1],"dim0":1,"dim1":2},{"shape":[1,128,1,1],"dim0":1,"dim1":2},{"shape":[1,1,128,1],"dim0":2,"dim1":3},{"shape":[1,1,1,24],"dim0":2,"dim1":3},{"shape":[1,1,24,1],"dim0":1,"dim1":2},{"shape":[1,128,1,1],"dim0":1,"dim1":2},{"shape":[1,1,128,1],"dim0":2,"dim1":3},{"shape":[1,1,1,546],"dim0":2,"dim1":3},{"shape":[1,1,546,1],"dim0":1,"dim1":2},{"shape":[1,128,1,1],"dim0":1,"dim1":2},{"shape":[1,1,128,1],"dim0":2,"dim1":3},{"shape":[1,1,1,546],"dim0":2,"dim1":3},{"shape":[1,1,546,1],"dim0":1,"dim1":2},{"shape":[1,128,224,224],"dim0":1,"dim1":2},{"shape":[1,224,128,224],"dim0":2,"dim1":3},{"shape":[1,224,224,64],"dim0":2,"dim1":3},{"shape":[1,224,64,224],"dim0":1,"dim1":2},{"shape":[1,128,224,224],"dim0":1,"dim1":2},{"shape":[1,224,128,224],"dim0":2,"dim1":3},{"shape":[1,224,224,64],"dim0":2,"dim1":3},{"shape":[1,224,64,224],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,16],"dim0":2,"dim1":3},{"shape":[1,28,16,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,16],"dim0":2,"dim1":3},{"shape":[1,28,16,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,19],"dim0":2,"dim1":3},{"shape":[1,28,19,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,19],"dim0":2,"dim1":3},{"shape":[1,28,19,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,256],"dim0":2,"dim1":3},{"shape":[1,28,256,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,256],"dim0":2,"dim1":3},{"shape":[1,28,256,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,14,14,256],"dim0":2,"dim1":3},{"shape":[1,14,256,14],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,14,14,256],"dim0":2,"dim1":3},{"shape":[1,14,256,14],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,14,14,256],"dim0":2,"dim1":3},{"shape":[1,14,256,14],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,14,14,256],"dim0":2,"dim1":3},{"shape":[1,14,256,14],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,38],"dim0":2,"dim1":3},{"shape":[1,28,38,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,38],"dim0":2,"dim1":3},{"shape":[1,28,38,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,128,28,28],"dim0":1,"dim1":2},{"shape":[1,28,128,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,128,2,2],"dim0":1,"dim1":2},{"shape":[1,2,128,2],"dim0":2,"dim1":3},{"shape":[1,2,2,256],"dim0":2,"dim1":3},{"shape":[1,2,256,2],"dim0":1,"dim1":2},{"shape":[1,128,2,2],"dim0":1,"dim1":2},{"shape":[1,2,128,2],"dim0":2,"dim1":3},{"shape":[1,2,2,256],"dim0":2,"dim1":3},{"shape":[1,2,256,2],"dim0":1,"dim1":2},{"shape":[1,128,30,40],"dim0":1,"dim1":2},{"shape":[1,30,128,40],"dim0":2,"dim1":3},{"shape":[1,30,40,64],"dim0":2,"dim1":3},{"shape":[1,30,64,40],"dim0":1,"dim1":2},{"shape":[1,128,30,40],"dim0":1,"dim1":2},{"shape":[1,30,128,40],"dim0":2,"dim1":3},{"shape":[1,30,40,64],"dim0":2,"dim1":3},{"shape":[1,30,64,40],"dim0":1,"dim1":2},{"shape":[1,128,32,32],"dim0":1,"dim1":2},{"shape":[1,32,128,32],"dim0":2,"dim1":3},{"shape":[1,32,32,256],"dim0":2,"dim1":3},{"shape":[1,32,256,32],"dim0":1,"dim1":2},{"shape":[1,128,32,32],"dim0":1,"dim1":2},{"shape":[1,32,128,32],"dim0":2,"dim1":3},{"shape":[1,32,32,256],"dim0":2,"dim1":3},{"shape":[1,32,256,32],"dim0":1,"dim1":2},{"shape":[1,128,3,3],"dim0":1,"dim1":2},{"shape":[1,3,128,3],"dim0":2,"dim1":3},{"shape":[1,2,2,128],"dim0":2,"dim1":3},{"shape":[1,2,128,2],"dim0":1,"dim1":2},{"shape":[1,128,3,3],"dim0":1,"dim1":2},{"shape":[1,3,128,3],"dim0":2,"dim1":3},{"shape":[1,2,2,128],"dim0":2,"dim1":3},{"shape":[1,2,128,2],"dim0":1,"dim1":2},{"shape":[1,128,3,3],"dim0":1,"dim1":2},{"shape":[1,3,128,3],"dim0":2,"dim1":3},{"shape":[1,3,3,256],"dim0":2,"dim1":3},{"shape":[1,3,256,3],"dim0":1,"dim1":2},{"shape":[1,128,3,3],"dim0":1,"dim1":2},{"shape":[1,3,128,3],"dim0":2,"dim1":3},{"shape":[1,3,3,256],"dim0":2,"dim1":3},{"shape":[1,3,256,3],"dim0":1,"dim1":2},{"shape":[1,128,56,56],"dim0":1,"dim1":2},{"shape":[1,56,128,56],"dim0":2,"dim1":3},{"shape":[1,56,56,128],"dim0":2,"dim1":3},{"shape":[1,56,128,56],"dim0":1,"dim1":2},{"shape":[1,128,56,56],"dim0":1,"dim1":2},{"shape":[1,56,128,56],"dim0":2,"dim1":3},{"shape":[1,56,56,128],"dim0":2,"dim1":3},{"shape":[1,56,128,56],"dim0":1,"dim1":2},{"shape":[1,128,56,56],"dim0":1,"dim1":2},{"shape":[1,56,128,56],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,56,56],"dim0":1,"dim1":2},{"shape":[1,56,128,56],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,56,56],"dim0":1,"dim1":2},{"shape":[1,56,128,56],"dim0":2,"dim1":3},{"shape":[1,56,56,128],"dim0":2,"dim1":3},{"shape":[1,56,128,56],"dim0":1,"dim1":2},{"shape":[1,128,56,56],"dim0":1,"dim1":2},{"shape":[1,56,128,56],"dim0":2,"dim1":3},{"shape":[1,56,56,128],"dim0":2,"dim1":3},{"shape":[1,56,128,56],"dim0":1,"dim1":2},{"shape":[1,128,56,56],"dim0":1,"dim1":2},{"shape":[1,56,128,56],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,56,56],"dim0":1,"dim1":2},{"shape":[1,56,128,56],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,128,56,56],"dim0":1,"dim1":2},{"shape":[1,56,128,56],"dim0":2,"dim1":3},{"shape":[1,56,56,256],"dim0":2,"dim1":3},{"shape":[1,56,256,56],"dim0":1,"dim1":2},{"shape":[1,128,56,56],"dim0":1,"dim1":2},{"shape":[1,56,128,56],"dim0":2,"dim1":3},{"shape":[1,56,56,256],"dim0":2,"dim1":3},{"shape":[1,56,256,56],"dim0":1,"dim1":2},{"shape":[1,128,5,5],"dim0":1,"dim1":2},{"shape":[1,5,128,5],"dim0":2,"dim1":3},{"shape":[1,3,3,128],"dim0":2,"dim1":3},{"shape":[1,3,128,3],"dim0":1,"dim1":2},{"shape":[1,128,5,5],"dim0":1,"dim1":2},{"shape":[1,5,128,5],"dim0":2,"dim1":3},{"shape":[1,3,3,128],"dim0":2,"dim1":3},{"shape":[1,3,128,3],"dim0":1,"dim1":2},{"shape":[1,128,60,80],"dim0":1,"dim1":2},{"shape":[1,60,128,80],"dim0":2,"dim1":3},{"shape":[1,15,20,128],"dim0":2,"dim1":3},{"shape":[1,15,128,20],"dim0":1,"dim1":2},{"shape":[1,128,60,80],"dim0":1,"dim1":2},{"shape":[1,60,128,80],"dim0":2,"dim1":3},{"shape":[1,15,20,128],"dim0":2,"dim1":3},{"shape":[1,15,128,20],"dim0":1,"dim1":2},{"shape":[1,128,60,80],"dim0":1,"dim1":2},{"shape":[1,60,128,80],"dim0":2,"dim1":3},{"shape":[1,30,40,320],"dim0":2,"dim1":3},{"shape":[1,30,320,40],"dim0":1,"dim1":2},{"shape":[1,128,60,80],"dim0":1,"dim1":2},{"shape":[1,60,128,80],"dim0":2,"dim1":3},{"shape":[1,30,40,320],"dim0":2,"dim1":3},{"shape":[1,30,320,40],"dim0":1,"dim1":2},{"shape":[1,128,60,80],"dim0":1,"dim1":2},{"shape":[1,60,128,80],"dim0":2,"dim1":3},{"shape":[1,60,80,64],"dim0":2,"dim1":3},{"shape":[1,60,64,80],"dim0":1,"dim1":2},{"shape":[1,128,60,80],"dim0":1,"dim1":2},{"shape":[1,60,128,80],"dim0":2,"dim1":3},{"shape":[1,60,80,64],"dim0":2,"dim1":3},{"shape":[1,60,64,80],"dim0":1,"dim1":2},{"shape":[1,128,60,80],"dim0":1,"dim1":2},{"shape":[1,60,128,80],"dim0":2,"dim1":3},{"shape":[1,60,80,64],"dim0":2,"dim1":3},{"shape":[1,60,64,80],"dim0":1,"dim1":2},{"shape":[1,128,60,80],"dim0":1,"dim1":2},{"shape":[1,60,128,80],"dim0":2,"dim1":3},{"shape":[1,60,80,64],"dim0":2,"dim1":3},{"shape":[1,60,64,80],"dim0":1,"dim1":2},{"shape":[1,128,64,64],"dim0":1,"dim1":2},{"shape":[1,64,128,64],"dim0":2,"dim1":3},{"shape":[1,64,64,128],"dim0":2,"dim1":3},{"shape":[1,64,128,64],"dim0":1,"dim1":2},{"shape":[1,128,64,64],"dim0":1,"dim1":2},{"shape":[1,64,128,64],"dim0":2,"dim1":3},{"shape":[1,64,64,128],"dim0":2,"dim1":3},{"shape":[1,64,128,64],"dim0":1,"dim1":2},{"shape":[1,128,64,64],"dim0":1,"dim1":2},{"shape":[1,64,128,64],"dim0":2,"dim1":3},{"shape":[1,64,64,256],"dim0":2,"dim1":3},{"shape":[1,64,256,64],"dim0":1,"dim1":2},{"shape":[1,128,64,64],"dim0":1,"dim1":2},{"shape":[1,64,128,64],"dim0":2,"dim1":3},{"shape":[1,64,64,256],"dim0":2,"dim1":3},{"shape":[1,64,256,64],"dim0":1,"dim1":2},{"shape":[1,128,90,160],"dim0":1,"dim1":2},{"shape":[1,90,128,160],"dim0":2,"dim1":3},{"shape":[1,90,160,128],"dim0":2,"dim1":3},{"shape":[1,90,128,160],"dim0":1,"dim1":2},{"shape":[1,128,90,160],"dim0":1,"dim1":2},{"shape":[1,90,128,160],"dim0":2,"dim1":3},{"shape":[1,90,160,128],"dim0":2,"dim1":3},{"shape":[1,90,128,160],"dim0":1,"dim1":2},{"shape":[1,128,90,160],"dim0":1,"dim1":2},{"shape":[1,90,128,160],"dim0":2,"dim1":3},{"shape":[1,90,160,512],"dim0":2,"dim1":3},{"shape":[1,90,512,160],"dim0":1,"dim1":2},{"shape":[1,128,90,160],"dim0":1,"dim1":2},{"shape":[1,90,128,160],"dim0":2,"dim1":3},{"shape":[1,90,160,512],"dim0":2,"dim1":3},{"shape":[1,90,512,160],"dim0":1,"dim1":2},{"shape":[1,142,56,56],"dim0":1,"dim1":2},{"shape":[1,56,142,56],"dim0":2,"dim1":3},{"shape":[1,56,56,68],"dim0":2,"dim1":3},{"shape":[1,56,68,56],"dim0":1,"dim1":2},{"shape":[1,142,56,56],"dim0":1,"dim1":2},{"shape":[1,56,142,56],"dim0":2,"dim1":3},{"shape":[1,56,56,68],"dim0":2,"dim1":3},{"shape":[1,56,68,56],"dim0":1,"dim1":2},{"shape":[1,144,28,28],"dim0":1,"dim1":2},{"shape":[1,28,144,28],"dim0":2,"dim1":3},{"shape":[1,28,28,28],"dim0":2,"dim1":3},{"shape":[1,28,28,28],"dim0":1,"dim1":2},{"shape":[1,144,28,28],"dim0":1,"dim1":2},{"shape":[1,28,144,28],"dim0":2,"dim1":3},{"shape":[1,28,28,28],"dim0":2,"dim1":3},{"shape":[1,28,28,28],"dim0":1,"dim1":2},{"shape":[1,144,28,28],"dim0":1,"dim1":2},{"shape":[1,28,144,28],"dim0":2,"dim1":3},{"shape":[1,28,28,32],"dim0":2,"dim1":3},{"shape":[1,28,32,28],"dim0":1,"dim1":2},{"shape":[1,144,28,28],"dim0":1,"dim1":2},{"shape":[1,28,144,28],"dim0":2,"dim1":3},{"shape":[1,28,28,32],"dim0":2,"dim1":3},{"shape":[1,28,32,28],"dim0":1,"dim1":2},{"shape":[1,144,56,56],"dim0":1,"dim1":2},{"shape":[1,56,144,56],"dim0":2,"dim1":3},{"shape":[1,56,56,144],"dim0":2,"dim1":3},{"shape":[1,56,144,56],"dim0":1,"dim1":2},{"shape":[1,144,56,56],"dim0":1,"dim1":2},{"shape":[1,56,144,56],"dim0":2,"dim1":3},{"shape":[1,56,56,144],"dim0":2,"dim1":3},{"shape":[1,56,144,56],"dim0":1,"dim1":2},{"shape":[1,144,56,56],"dim0":1,"dim1":2},{"shape":[1,56,144,56],"dim0":2,"dim1":3},{"shape":[1,28,28,144],"dim0":2,"dim1":3},{"shape":[1,28,144,28],"dim0":1,"dim1":2},{"shape":[1,144,56,56],"dim0":1,"dim1":2},{"shape":[1,56,144,56],"dim0":2,"dim1":3},{"shape":[1,28,28,144],"dim0":2,"dim1":3},{"shape":[1,28,144,28],"dim0":1,"dim1":2},{"shape":[1,144,56,56],"dim0":1,"dim1":2},{"shape":[1,56,144,56],"dim0":2,"dim1":3},{"shape":[1,56,56,24],"dim0":2,"dim1":3},{"shape":[1,56,24,56],"dim0":1,"dim1":2},{"shape":[1,144,56,56],"dim0":1,"dim1":2},{"shape":[1,56,144,56],"dim0":2,"dim1":3},{"shape":[1,56,56,24],"dim0":2,"dim1":3},{"shape":[1,56,24,56],"dim0":1,"dim1":2},{"shape":[1,152,28,28],"dim0":1,"dim1":2},{"shape":[1,28,152,28],"dim0":2,"dim1":3},{"shape":[1,28,28,58],"dim0":2,"dim1":3},{"shape":[1,28,58,28],"dim0":1,"dim1":2},{"shape":[1,152,28,28],"dim0":1,"dim1":2},{"shape":[1,28,152,28],"dim0":2,"dim1":3},{"shape":[1,28,28,58],"dim0":2,"dim1":3},{"shape":[1,28,58,28],"dim0":1,"dim1":2},{"shape":[1,156,14,14],"dim0":1,"dim1":2},{"shape":[1,14,156,14],"dim0":2,"dim1":3},{"shape":[1,14,14,68],"dim0":2,"dim1":3},{"shape":[1,14,68,14],"dim0":1,"dim1":2},{"shape":[1,156,14,14],"dim0":1,"dim1":2},{"shape":[1,14,156,14],"dim0":2,"dim1":3},{"shape":[1,14,14,68],"dim0":2,"dim1":3},{"shape":[1,14,68,14],"dim0":1,"dim1":2},{"shape":[1,160,32,32],"dim0":1,"dim1":2},{"shape":[1,32,160,32],"dim0":2,"dim1":3},{"shape":[1,16,16,160],"dim0":2,"dim1":3},{"shape":[1,16,160,16],"dim0":1,"dim1":2},{"shape":[1,160,32,32],"dim0":1,"dim1":2},{"shape":[1,32,160,32],"dim0":2,"dim1":3},{"shape":[1,16,16,160],"dim0":2,"dim1":3},{"shape":[1,16,160,16],"dim0":1,"dim1":2},{"shape":[1,160,32,32],"dim0":1,"dim1":2},{"shape":[1,32,160,32],"dim0":2,"dim1":3},{"shape":[1,16,16,256],"dim0":2,"dim1":3},{"shape":[1,16,256,16],"dim0":1,"dim1":2},{"shape":[1,160,32,32],"dim0":1,"dim1":2},{"shape":[1,32,160,32],"dim0":2,"dim1":3},{"shape":[1,16,16,256],"dim0":2,"dim1":3},{"shape":[1,16,256,16],"dim0":1,"dim1":2},{"shape":[1,160,7,7],"dim0":1,"dim1":2},{"shape":[1,7,160,7],"dim0":2,"dim1":3},{"shape":[1,7,7,960],"dim0":2,"dim1":3},{"shape":[1,7,960,7],"dim0":1,"dim1":2},{"shape":[1,160,7,7],"dim0":1,"dim1":2},{"shape":[1,7,160,7],"dim0":2,"dim1":3},{"shape":[1,7,7,960],"dim0":2,"dim1":3},{"shape":[1,7,960,7],"dim0":1,"dim1":2},{"shape":[1,168,1,1],"dim0":1,"dim1":2},{"shape":[1,1,168,1],"dim0":2,"dim1":3},{"shape":[1,1,1,672],"dim0":2,"dim1":3},{"shape":[1,1,672,1],"dim0":1,"dim1":2},{"shape":[1,168,1,1],"dim0":1,"dim1":2},{"shape":[1,1,168,1],"dim0":2,"dim1":3},{"shape":[1,1,1,672],"dim0":2,"dim1":3},{"shape":[1,1,672,1],"dim0":1,"dim1":2},{"shape":[1,16,112,112],"dim0":1,"dim1":2},{"shape":[1,112,16,112],"dim0":2,"dim1":3},{"shape":[1,112,112,96],"dim0":2,"dim1":3},{"shape":[1,112,96,112],"dim0":1,"dim1":2},{"shape":[1,16,112,112],"dim0":1,"dim1":2},{"shape":[1,112,16,112],"dim0":2,"dim1":3},{"shape":[1,112,112,96],"dim0":2,"dim1":3},{"shape":[1,112,96,112],"dim0":1,"dim1":2},{"shape":[1,16,14,14],"dim0":1,"dim1":2},{"shape":[1,14,16,14],"dim0":2,"dim1":3},{"shape":[1,14,14,4],"dim0":2,"dim1":3},{"shape":[1,14,4,14],"dim0":1,"dim1":2},{"shape":[1,16,14,14],"dim0":1,"dim1":2},{"shape":[1,14,16,14],"dim0":2,"dim1":3},{"shape":[1,14,14,4],"dim0":2,"dim1":3},{"shape":[1,14,4,14],"dim0":1,"dim1":2},{"shape":[1,16,160,160],"dim0":1,"dim1":2},{"shape":[1,160,16,160],"dim0":2,"dim1":3},{"shape":[1,160,160,16],"dim0":2,"dim1":3},{"shape":[1,160,16,160],"dim0":1,"dim1":2},{"shape":[1,16,160,160],"dim0":1,"dim1":2},{"shape":[1,160,16,160],"dim0":2,"dim1":3},{"shape":[1,160,160,16],"dim0":2,"dim1":3},{"shape":[1,160,16,160],"dim0":1,"dim1":2},{"shape":[1,16,160,160],"dim0":1,"dim1":2},{"shape":[1,160,16,160],"dim0":2,"dim1":3},{"shape":[1,160,160,16],"dim0":2,"dim1":3},{"shape":[1,160,16,160],"dim0":1,"dim1":2},{"shape":[1,16,160,160],"dim0":1,"dim1":2},{"shape":[1,160,16,160],"dim0":2,"dim1":3},{"shape":[1,160,160,16],"dim0":2,"dim1":3},{"shape":[1,160,16,160],"dim0":1,"dim1":2},{"shape":[1,16,160,160],"dim0":1,"dim1":2},{"shape":[1,160,16,160],"dim0":2,"dim1":3},{"shape":[1,160,160,64],"dim0":2,"dim1":3},{"shape":[1,160,64,160],"dim0":1,"dim1":2},{"shape":[1,16,160,160],"dim0":1,"dim1":2},{"shape":[1,160,16,160],"dim0":2,"dim1":3},{"shape":[1,160,160,64],"dim0":2,"dim1":3},{"shape":[1,160,64,160],"dim0":1,"dim1":2},{"shape":[1,172,28,28],"dim0":1,"dim1":2},{"shape":[1,28,172,28],"dim0":2,"dim1":3},{"shape":[1,28,28,46],"dim0":2,"dim1":3},{"shape":[1,28,46,28],"dim0":1,"dim1":2},{"shape":[1,172,28,28],"dim0":1,"dim1":2},{"shape":[1,28,172,28],"dim0":2,"dim1":3},{"shape":[1,28,28,46],"dim0":2,"dim1":3},{"shape":[1,28,46,28],"dim0":1,"dim1":2},{"shape":[1,184,20,20],"dim0":1,"dim1":2},{"shape":[1,20,184,20],"dim0":2,"dim1":3},{"shape":[1,20,20,184],"dim0":2,"dim1":3},{"shape":[1,20,184,20],"dim0":1,"dim1":2},{"shape":[1,184,20,20],"dim0":1,"dim1":2},{"shape":[1,20,184,20],"dim0":2,"dim1":3},{"shape":[1,20,20,184],"dim0":2,"dim1":3},{"shape":[1,20,184,20],"dim0":1,"dim1":2},{"shape":[1,20,20,80],"dim0":2,"dim1":3},{"shape":[1,20,80,20],"dim0":1,"dim1":2},{"shape":[1,184,20,20],"dim0":1,"dim1":2},{"shape":[1,20,184,20],"dim0":2,"dim1":3},{"shape":[1,20,20,80],"dim0":2,"dim1":3},{"shape":[1,20,80,20],"dim0":1,"dim1":2},{"shape":[1,185,28,28],"dim0":1,"dim1":2},{"shape":[1,28,185,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,185,28,28],"dim0":1,"dim1":2},{"shape":[1,28,185,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,1920,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1920,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,1920,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1920,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,1920,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1920,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,1920,16,16],"dim0":1,"dim1":2},{"shape":[1,16,1920,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,1920,32,32],"dim0":1,"dim1":2},{"shape":[1,32,1920,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,1920,32,32],"dim0":1,"dim1":2},{"shape":[1,32,1920,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,1920,32,32],"dim0":1,"dim1":2},{"shape":[1,32,1920,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,1920,32,32],"dim0":1,"dim1":2},{"shape":[1,32,1920,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,192,14,14],"dim0":1,"dim1":2},{"shape":[1,14,192,14],"dim0":2,"dim1":3},{"shape":[1,14,14,64],"dim0":2,"dim1":3},{"shape":[1,14,64,14],"dim0":1,"dim1":2},{"shape":[1,192,14,14],"dim0":1,"dim1":2},{"shape":[1,14,192,14],"dim0":2,"dim1":3},{"shape":[1,14,14,64],"dim0":2,"dim1":3},{"shape":[1,14,64,14],"dim0":1,"dim1":2},{"shape":[1,192,28,28],"dim0":1,"dim1":2},{"shape":[1,28,192,28],"dim0":2,"dim1":3},{"shape":[1,28,28,192],"dim0":2,"dim1":3},{"shape":[1,28,192,28],"dim0":1,"dim1":2},{"shape":[1,192,28,28],"dim0":1,"dim1":2},{"shape":[1,28,192,28],"dim0":2,"dim1":3},{"shape":[1,28,28,192],"dim0":2,"dim1":3},{"shape":[1,28,192,28],"dim0":1,"dim1":2},{"shape":[1,192,28,28],"dim0":1,"dim1":2},{"shape":[1,28,192,28],"dim0":2,"dim1":3},{"shape":[1,14,14,192],"dim0":2,"dim1":3},{"shape":[1,14,192,14],"dim0":1,"dim1":2},{"shape":[1,192,28,28],"dim0":1,"dim1":2},{"shape":[1,28,192,28],"dim0":2,"dim1":3},{"shape":[1,14,14,192],"dim0":2,"dim1":3},{"shape":[1,14,192,14],"dim0":1,"dim1":2},{"shape":[1,192,28,28],"dim0":1,"dim1":2},{"shape":[1,28,192,28],"dim0":2,"dim1":3},{"shape":[1,28,28,32],"dim0":2,"dim1":3},{"shape":[1,28,32,28],"dim0":1,"dim1":2},{"shape":[1,192,28,28],"dim0":1,"dim1":2},{"shape":[1,28,192,28],"dim0":2,"dim1":3},{"shape":[1,28,28,32],"dim0":2,"dim1":3},{"shape":[1,28,32,28],"dim0":1,"dim1":2},{"shape":[1,196,14,14],"dim0":1,"dim1":2},{"shape":[1,14,196,14],"dim0":2,"dim1":3},{"shape":[1,14,14,40],"dim0":2,"dim1":3},{"shape":[1,14,40,14],"dim0":1,"dim1":2},{"shape":[1,196,14,14],"dim0":1,"dim1":2},{"shape":[1,14,196,14],"dim0":2,"dim1":3},{"shape":[1,14,14,40],"dim0":2,"dim1":3},{"shape":[1,14,40,14],"dim0":1,"dim1":2},{"shape":[1,1,28,28],"dim0":1,"dim1":2},{"shape":[1,28,1,28],"dim0":2,"dim1":3},{"shape":[1,28,28,16],"dim0":2,"dim1":3},{"shape":[1,28,16,28],"dim0":1,"dim1":2},{"shape":[1,1,28,28],"dim0":1,"dim1":2},{"shape":[1,28,1,28],"dim0":2,"dim1":3},{"shape":[1,28,28,16],"dim0":2,"dim1":3},{"shape":[1,28,16,28],"dim0":1,"dim1":2},{"shape":[1,1,28,28],"dim0":1,"dim1":2},{"shape":[1,28,1,28],"dim0":2,"dim1":3},{"shape":[1,26,26,32],"dim0":2,"dim1":3},{"shape":[1,26,32,26],"dim0":1,"dim1":2},{"shape":[1,1,28,28],"dim0":1,"dim1":2},{"shape":[1,28,1,28],"dim0":2,"dim1":3},{"shape":[1,26,26,32],"dim0":2,"dim1":3},{"shape":[1,26,32,26],"dim0":1,"dim1":2},{"shape":[1,200,20,20],"dim0":1,"dim1":2},{"shape":[1,20,200,20],"dim0":2,"dim1":3},{"shape":[1,20,20,200],"dim0":2,"dim1":3},{"shape":[1,20,200,20],"dim0":1,"dim1":2},{"shape":[1,200,20,20],"dim0":1,"dim1":2},{"shape":[1,20,200,20],"dim0":2,"dim1":3},{"shape":[1,20,20,200],"dim0":2,"dim1":3},{"shape":[1,20,200,20],"dim0":1,"dim1":2},{"shape":[1,200,20,20],"dim0":1,"dim1":2},{"shape":[1,20,200,20],"dim0":2,"dim1":3},{"shape":[1,20,20,80],"dim0":2,"dim1":3},{"shape":[1,20,80,20],"dim0":1,"dim1":2},{"shape":[1,200,20,20],"dim0":1,"dim1":2},{"shape":[1,20,200,20],"dim0":2,"dim1":3},{"shape":[1,20,20,80],"dim0":2,"dim1":3},{"shape":[1,20,80,20],"dim0":1,"dim1":2},{"shape":[1,2048,15,20],"dim0":1,"dim1":2},{"shape":[1,15,2048,20],"dim0":2,"dim1":3},{"shape":[1,15,20,2048],"dim0":2,"dim1":3},{"shape":[1,15,2048,20],"dim0":1,"dim1":2},{"shape":[1,2048,15,20],"dim0":1,"dim1":2},{"shape":[1,15,2048,20],"dim0":2,"dim1":3},{"shape":[1,15,20,2048],"dim0":2,"dim1":3},{"shape":[1,15,2048,20],"dim0":1,"dim1":2},{"shape":[1,2048,23,40],"dim0":1,"dim1":2},{"shape":[1,23,2048,40],"dim0":2,"dim1":3},{"shape":[1,23,40,256],"dim0":2,"dim1":3},{"shape":[1,23,256,40],"dim0":1,"dim1":2},{"shape":[1,2048,23,40],"dim0":1,"dim1":2},{"shape":[1,23,2048,40],"dim0":2,"dim1":3},{"shape":[1,23,40,256],"dim0":2,"dim1":3},{"shape":[1,23,256,40],"dim0":1,"dim1":2},{"shape":[1,2048,23,40],"dim0":1,"dim1":2},{"shape":[1,23,2048,40],"dim0":2,"dim1":3},{"shape":[1,23,40,512],"dim0":2,"dim1":3},{"shape":[1,23,512,40],"dim0":1,"dim1":2},{"shape":[1,2048,23,40],"dim0":1,"dim1":2},{"shape":[1,23,2048,40],"dim0":2,"dim1":3},{"shape":[1,23,40,512],"dim0":2,"dim1":3},{"shape":[1,23,512,40],"dim0":1,"dim1":2},{"shape":[1,2048,7,7],"dim0":1,"dim1":2},{"shape":[1,7,2048,7],"dim0":2,"dim1":3},{"shape":[1,7,7,512],"dim0":2,"dim1":3},{"shape":[1,7,512,7],"dim0":1,"dim1":2},{"shape":[1,2048,7,7],"dim0":1,"dim1":2},{"shape":[1,7,2048,7],"dim0":2,"dim1":3},{"shape":[1,7,7,512],"dim0":2,"dim1":3},{"shape":[1,7,512,7],"dim0":1,"dim1":2},{"shape":[1,218,28,28],"dim0":1,"dim1":2},{"shape":[1,28,218,28],"dim0":2,"dim1":3},{"shape":[1,28,28,78],"dim0":2,"dim1":3},{"shape":[1,28,78,28],"dim0":1,"dim1":2},{"shape":[1,218,28,28],"dim0":1,"dim1":2},{"shape":[1,28,218,28],"dim0":2,"dim1":3},{"shape":[1,28,28,78],"dim0":2,"dim1":3},{"shape":[1,28,78,28],"dim0":1,"dim1":2},{"shape":[1,236,14,14],"dim0":1,"dim1":2},{"shape":[1,14,236,14],"dim0":2,"dim1":3},{"shape":[1,14,14,68],"dim0":2,"dim1":3},{"shape":[1,14,68,14],"dim0":1,"dim1":2},{"shape":[1,236,14,14],"dim0":1,"dim1":2},{"shape":[1,14,236,14],"dim0":2,"dim1":3},{"shape":[1,14,14,68],"dim0":2,"dim1":3},{"shape":[1,14,68,14],"dim0":1,"dim1":2},{"shape":[1,240,20,20],"dim0":1,"dim1":2},{"shape":[1,20,240,20],"dim0":2,"dim1":3},{"shape":[1,240,20,20],"dim0":1,"dim1":2},{"shape":[1,20,240,20],"dim0":2,"dim1":3},{"shape":[1,20,20,80],"dim0":2,"dim1":3},{"shape":[1,20,80,20],"dim0":1,"dim1":2},{"shape":[1,240,40,40],"dim0":1,"dim1":2},{"shape":[1,40,240,40],"dim0":2,"dim1":3},{"shape":[1,20,20,240],"dim0":2,"dim1":3},{"shape":[1,20,240,20],"dim0":1,"dim1":2},{"shape":[1,240,40,40],"dim0":1,"dim1":2},{"shape":[1,40,240,40],"dim0":2,"dim1":3},{"shape":[1,20,20,240],"dim0":2,"dim1":3},{"shape":[1,20,240,20],"dim0":1,"dim1":2},{"shape":[1,24,1,1],"dim0":1,"dim1":2},{"shape":[1,1,24,1],"dim0":2,"dim1":3},{"shape":[1,1,1,72],"dim0":2,"dim1":3},{"shape":[1,1,72,1],"dim0":1,"dim1":2},{"shape":[1,24,1,1],"dim0":1,"dim1":2},{"shape":[1,1,24,1],"dim0":2,"dim1":3},{"shape":[1,1,1,72],"dim0":2,"dim1":3},{"shape":[1,1,72,1],"dim0":1,"dim1":2},{"shape":[1,24,56,56],"dim0":1,"dim1":2},{"shape":[1,56,24,56],"dim0":2,"dim1":3},{"shape":[1,56,56,144],"dim0":2,"dim1":3},{"shape":[1,56,144,56],"dim0":1,"dim1":2},{"shape":[1,24,56,56],"dim0":1,"dim1":2},{"shape":[1,56,24,56],"dim0":2,"dim1":3},{"shape":[1,56,56,144],"dim0":2,"dim1":3},{"shape":[1,56,144,56],"dim0":1,"dim1":2},{"shape":[1,24,56,56],"dim0":1,"dim1":2},{"shape":[1,56,24,56],"dim0":2,"dim1":3},{"shape":[1,56,56,14],"dim0":2,"dim1":3},{"shape":[1,56,14,56],"dim0":1,"dim1":2},{"shape":[1,24,56,56],"dim0":1,"dim1":2},{"shape":[1,56,24,56],"dim0":2,"dim1":3},{"shape":[1,56,56,14],"dim0":2,"dim1":3},{"shape":[1,56,14,56],"dim0":1,"dim1":2},{"shape":[1,24,80,80],"dim0":1,"dim1":2},{"shape":[1,80,24,80],"dim0":2,"dim1":3},{"shape":[1,80,80,72],"dim0":2,"dim1":3},{"shape":[1,80,72,80],"dim0":1,"dim1":2},{"shape":[1,24,80,80],"dim0":1,"dim1":2},{"shape":[1,80,24,80],"dim0":2,"dim1":3},{"shape":[1,80,80,72],"dim0":2,"dim1":3},{"shape":[1,80,72,80],"dim0":1,"dim1":2},{"shape":[1,2560,16,16],"dim0":1,"dim1":2},{"shape":[1,16,2560,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,2560,16,16],"dim0":1,"dim1":2},{"shape":[1,16,2560,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,2560,16,16],"dim0":1,"dim1":2},{"shape":[1,16,2560,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,2560,16,16],"dim0":1,"dim1":2},{"shape":[1,16,2560,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,2560,8,8],"dim0":1,"dim1":2},{"shape":[1,8,2560,8],"dim0":2,"dim1":3},{"shape":[1,8,8,1280],"dim0":2,"dim1":3},{"shape":[1,8,1280,8],"dim0":1,"dim1":2},{"shape":[1,2560,8,8],"dim0":1,"dim1":2},{"shape":[1,8,2560,8],"dim0":2,"dim1":3},{"shape":[1,8,8,1280],"dim0":2,"dim1":3},{"shape":[1,8,1280,8],"dim0":1,"dim1":2},{"shape":[1,2560,8,8],"dim0":1,"dim1":2},{"shape":[1,8,2560,8],"dim0":2,"dim1":3},{"shape":[1,8,8,1280],"dim0":2,"dim1":3},{"shape":[1,8,1280,8],"dim0":1,"dim1":2},{"shape":[1,2560,8,8],"dim0":1,"dim1":2},{"shape":[1,8,2560,8],"dim0":2,"dim1":3},{"shape":[1,8,8,1280],"dim0":2,"dim1":3},{"shape":[1,8,1280,8],"dim0":1,"dim1":2},{"shape":[1,256,10,10],"dim0":1,"dim1":2},{"shape":[1,10,256,10],"dim0":2,"dim1":3},{"shape":[1,5,5,256],"dim0":2,"dim1":3},{"shape":[1,5,256,5],"dim0":1,"dim1":2},{"shape":[1,256,10,10],"dim0":1,"dim1":2},{"shape":[1,10,256,10],"dim0":2,"dim1":3},{"shape":[1,5,5,256],"dim0":2,"dim1":3},{"shape":[1,5,256,5],"dim0":1,"dim1":2},{"shape":[1,256,112,112],"dim0":1,"dim1":2},{"shape":[1,112,256,112],"dim0":2,"dim1":3},{"shape":[1,112,112,128],"dim0":2,"dim1":3},{"shape":[1,112,128,112],"dim0":1,"dim1":2},{"shape":[1,256,112,112],"dim0":1,"dim1":2},{"shape":[1,112,256,112],"dim0":2,"dim1":3},{"shape":[1,112,112,128],"dim0":2,"dim1":3},{"shape":[1,112,128,112],"dim0":1,"dim1":2},{"shape":[1,256,120,160],"dim0":1,"dim1":2},{"shape":[1,120,256,160],"dim0":2,"dim1":3},{"shape":[1,120,160,256],"dim0":2,"dim1":3},{"shape":[1,120,256,160],"dim0":1,"dim1":2},{"shape":[1,256,120,160],"dim0":1,"dim1":2},{"shape":[1,120,256,160],"dim0":2,"dim1":3},{"shape":[1,120,160,256],"dim0":2,"dim1":3},{"shape":[1,120,256,160],"dim0":1,"dim1":2},{"shape":[1,256,128,128],"dim0":1,"dim1":2},{"shape":[1,128,256,128],"dim0":2,"dim1":3},{"shape":[1,128,128,150],"dim0":2,"dim1":3},{"shape":[1,128,150,128],"dim0":1,"dim1":2},{"shape":[1,256,128,128],"dim0":1,"dim1":2},{"shape":[1,128,256,128],"dim0":2,"dim1":3},{"shape":[1,128,128,150],"dim0":2,"dim1":3},{"shape":[1,128,150,128],"dim0":1,"dim1":2},{"shape":[1,256,14,14],"dim0":1,"dim1":2},{"shape":[1,14,256,14],"dim0":2,"dim1":3},{"shape":[1,14,14,1024],"dim0":2,"dim1":3},{"shape":[1,14,1024,14],"dim0":1,"dim1":2},{"shape":[1,256,14,14],"dim0":1,"dim1":2},{"shape":[1,14,256,14],"dim0":2,"dim1":3},{"shape":[1,14,14,1024],"dim0":2,"dim1":3},{"shape":[1,14,1024,14],"dim0":1,"dim1":2},{"shape":[1,256,14,14],"dim0":1,"dim1":2},{"shape":[1,14,256,14],"dim0":2,"dim1":3},{"shape":[1,14,14,256],"dim0":2,"dim1":3},{"shape":[1,14,256,14],"dim0":1,"dim1":2},{"shape":[1,256,14,14],"dim0":1,"dim1":2},{"shape":[1,14,256,14],"dim0":2,"dim1":3},{"shape":[1,14,14,256],"dim0":2,"dim1":3},{"shape":[1,14,256,14],"dim0":1,"dim1":2},{"shape":[1,256,14,14],"dim0":1,"dim1":2},{"shape":[1,14,256,14],"dim0":2,"dim1":3},{"shape":[1,7,7,512],"dim0":2,"dim1":3},{"shape":[1,7,512,7],"dim0":1,"dim1":2},{"shape":[1,256,14,14],"dim0":1,"dim1":2},{"shape":[1,14,256,14],"dim0":2,"dim1":3},{"shape":[1,7,7,512],"dim0":2,"dim1":3},{"shape":[1,7,512,7],"dim0":1,"dim1":2},{"shape":[1,256,14,14],"dim0":1,"dim1":2},{"shape":[1,14,256,14],"dim0":2,"dim1":3},{"shape":[1,7,7,512],"dim0":2,"dim1":3},{"shape":[1,7,512,7],"dim0":1,"dim1":2},{"shape":[1,256,14,14],"dim0":1,"dim1":2},{"shape":[1,14,256,14],"dim0":2,"dim1":3},{"shape":[1,7,7,512],"dim0":2,"dim1":3},{"shape":[1,7,512,7],"dim0":1,"dim1":2},{"shape":[1,256,16,16],"dim0":1,"dim1":2},{"shape":[1,16,256,16],"dim0":2,"dim1":3},{"shape":[1,16,16,512],"dim0":2,"dim1":3},{"shape":[1,16,512,16],"dim0":1,"dim1":2},{"shape":[1,256,16,16],"dim0":1,"dim1":2},{"shape":[1,16,256,16],"dim0":2,"dim1":3},{"shape":[1,16,16,512],"dim0":2,"dim1":3},{"shape":[1,16,512,16],"dim0":1,"dim1":2},{"shape":[1,256,180,320],"dim0":1,"dim1":2},{"shape":[1,180,256,320],"dim0":2,"dim1":3},{"shape":[1,180,320,128],"dim0":2,"dim1":3},{"shape":[1,180,128,320],"dim0":1,"dim1":2},{"shape":[1,256,180,320],"dim0":1,"dim1":2},{"shape":[1,180,256,320],"dim0":2,"dim1":3},{"shape":[1,180,320,128],"dim0":2,"dim1":3},{"shape":[1,180,128,320],"dim0":1,"dim1":2},{"shape":[1,256,180,320],"dim0":1,"dim1":2},{"shape":[1,180,256,320],"dim0":2,"dim1":3},{"shape":[1,90,160,512],"dim0":2,"dim1":3},{"shape":[1,90,512,160],"dim0":1,"dim1":2},{"shape":[1,256,180,320],"dim0":1,"dim1":2},{"shape":[1,180,256,320],"dim0":2,"dim1":3},{"shape":[1,90,160,512],"dim0":2,"dim1":3},{"shape":[1,90,512,160],"dim0":1,"dim1":2},{"shape":[1,180,320,64],"dim0":2,"dim1":3},{"shape":[1,180,64,320],"dim0":1,"dim1":2},{"shape":[1,256,180,320],"dim0":1,"dim1":2},{"shape":[1,180,256,320],"dim0":2,"dim1":3},{"shape":[1,180,320,64],"dim0":2,"dim1":3},{"shape":[1,180,64,320],"dim0":1,"dim1":2},{"shape":[1,256,28,28],"dim0":1,"dim1":2},{"shape":[1,28,256,28],"dim0":2,"dim1":3},{"shape":[1,28,28,20],"dim0":2,"dim1":3},{"shape":[1,28,20,28],"dim0":1,"dim1":2},{"shape":[1,256,28,28],"dim0":1,"dim1":2},{"shape":[1,28,256,28],"dim0":2,"dim1":3},{"shape":[1,28,28,20],"dim0":2,"dim1":3},{"shape":[1,28,20,28],"dim0":1,"dim1":2},{"shape":[1,256,28,28],"dim0":1,"dim1":2},{"shape":[1,28,256,28],"dim0":2,"dim1":3},{"shape":[1,28,28,256],"dim0":2,"dim1":3},{"shape":[1,28,256,28],"dim0":1,"dim1":2},{"shape":[1,256,28,28],"dim0":1,"dim1":2},{"shape":[1,28,256,28],"dim0":2,"dim1":3},{"shape":[1,28,28,256],"dim0":2,"dim1":3},{"shape":[1,28,256,28],"dim0":1,"dim1":2},{"shape":[1,256,28,28],"dim0":1,"dim1":2},{"shape":[1,28,256,28],"dim0":2,"dim1":3},{"shape":[1,28,28,256],"dim0":2,"dim1":3},{"shape":[1,28,256,28],"dim0":1,"dim1":2},{"shape":[1,256,28,28],"dim0":1,"dim1":2},{"shape":[1,28,256,28],"dim0":2,"dim1":3},{"shape":[1,28,28,256],"dim0":2,"dim1":3},{"shape":[1,28,256,28],"dim0":1,"dim1":2},{"shape":[1,256,28,28],"dim0":1,"dim1":2},{"shape":[1,28,256,28],"dim0":2,"dim1":3},{"shape":[1,14,14,256],"dim0":2,"dim1":3},{"shape":[1,14,256,14],"dim0":1,"dim1":2},{"shape":[1,256,28,28],"dim0":1,"dim1":2},{"shape":[1,28,256,28],"dim0":2,"dim1":3},{"shape":[1,14,14,256],"dim0":2,"dim1":3},{"shape":[1,14,256,14],"dim0":1,"dim1":2},{"shape":[1,256,28,28],"dim0":1,"dim1":2},{"shape":[1,28,256,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,256,28,28],"dim0":1,"dim1":2},{"shape":[1,28,256,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,256,28,28],"dim0":1,"dim1":2},{"shape":[1,28,256,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,256,28,28],"dim0":1,"dim1":2},{"shape":[1,28,256,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,256,2,2],"dim0":1,"dim1":2},{"shape":[1,2,256,2],"dim0":2,"dim1":3},{"shape":[1,2,2,24],"dim0":2,"dim1":3},{"shape":[1,2,24,2],"dim0":1,"dim1":2},{"shape":[1,256,2,2],"dim0":1,"dim1":2},{"shape":[1,2,256,2],"dim0":2,"dim1":3},{"shape":[1,2,2,24],"dim0":2,"dim1":3},{"shape":[1,2,24,2],"dim0":1,"dim1":2},{"shape":[1,256,2,2],"dim0":1,"dim1":2},{"shape":[1,2,256,2],"dim0":2,"dim1":3},{"shape":[1,2,2,256],"dim0":2,"dim1":3},{"shape":[1,2,256,2],"dim0":1,"dim1":2},{"shape":[1,256,2,2],"dim0":1,"dim1":2},{"shape":[1,2,256,2],"dim0":2,"dim1":3},{"shape":[1,2,2,256],"dim0":2,"dim1":3},{"shape":[1,2,256,2],"dim0":1,"dim1":2},{"shape":[1,256,2,2],"dim0":1,"dim1":2},{"shape":[1,2,256,2],"dim0":2,"dim1":3},{"shape":[1,2,2,546],"dim0":2,"dim1":3},{"shape":[1,2,546,2],"dim0":1,"dim1":2},{"shape":[1,256,2,2],"dim0":1,"dim1":2},{"shape":[1,2,256,2],"dim0":2,"dim1":3},{"shape":[1,2,2,546],"dim0":2,"dim1":3},{"shape":[1,2,546,2],"dim0":1,"dim1":2},{"shape":[1,256,2,2],"dim0":1,"dim1":2},{"shape":[1,2,256,2],"dim0":2,"dim1":3},{"shape":[1,2,2,64],"dim0":2,"dim1":3},{"shape":[1,2,64,2],"dim0":1,"dim1":2},{"shape":[1,256,2,2],"dim0":1,"dim1":2},{"shape":[1,2,256,2],"dim0":2,"dim1":3},{"shape":[1,2,2,64],"dim0":2,"dim1":3},{"shape":[1,2,64,2],"dim0":1,"dim1":2},{"shape":[1,256,32,32],"dim0":1,"dim1":2},{"shape":[1,32,256,32],"dim0":2,"dim1":3},{"shape":[1,32,32,128],"dim0":2,"dim1":3},{"shape":[1,32,128,32],"dim0":1,"dim1":2},{"shape":[1,256,32,32],"dim0":1,"dim1":2},{"shape":[1,32,256,32],"dim0":2,"dim1":3},{"shape":[1,32,32,128],"dim0":2,"dim1":3},{"shape":[1,32,128,32],"dim0":1,"dim1":2},{"shape":[1,256,32,32],"dim0":1,"dim1":2},{"shape":[1,32,256,32],"dim0":2,"dim1":3},{"shape":[1,32,32,256],"dim0":2,"dim1":3},{"shape":[1,32,256,32],"dim0":1,"dim1":2},{"shape":[1,256,32,32],"dim0":1,"dim1":2},{"shape":[1,32,256,32],"dim0":2,"dim1":3},{"shape":[1,32,32,256],"dim0":2,"dim1":3},{"shape":[1,32,256,32],"dim0":1,"dim1":2},{"shape":[1,256,32,32],"dim0":1,"dim1":2},{"shape":[1,32,256,32],"dim0":2,"dim1":3},{"shape":[1,32,32,512],"dim0":2,"dim1":3},{"shape":[1,32,512,32],"dim0":1,"dim1":2},{"shape":[1,256,32,32],"dim0":1,"dim1":2},{"shape":[1,32,256,32],"dim0":2,"dim1":3},{"shape":[1,32,32,512],"dim0":2,"dim1":3},{"shape":[1,32,512,32],"dim0":1,"dim1":2},{"shape":[1,256,3,3],"dim0":1,"dim1":2},{"shape":[1,3,256,3],"dim0":2,"dim1":3},{"shape":[1,3,3,128],"dim0":2,"dim1":3},{"shape":[1,3,128,3],"dim0":1,"dim1":2},{"shape":[1,256,3,3],"dim0":1,"dim1":2},{"shape":[1,3,256,3],"dim0":2,"dim1":3},{"shape":[1,3,3,128],"dim0":2,"dim1":3},{"shape":[1,3,128,3],"dim0":1,"dim1":2},{"shape":[1,3,3,24],"dim0":2,"dim1":3},{"shape":[1,3,24,3],"dim0":1,"dim1":2},{"shape":[1,256,3,3],"dim0":1,"dim1":2},{"shape":[1,3,256,3],"dim0":2,"dim1":3},{"shape":[1,3,3,24],"dim0":2,"dim1":3},{"shape":[1,3,24,3],"dim0":1,"dim1":2},{"shape":[1,256,3,3],"dim0":1,"dim1":2},{"shape":[1,3,256,3],"dim0":2,"dim1":3},{"shape":[1,3,3,256],"dim0":2,"dim1":3},{"shape":[1,3,256,3],"dim0":1,"dim1":2},{"shape":[1,256,3,3],"dim0":1,"dim1":2},{"shape":[1,3,256,3],"dim0":2,"dim1":3},{"shape":[1,3,3,256],"dim0":2,"dim1":3},{"shape":[1,3,256,3],"dim0":1,"dim1":2},{"shape":[1,256,3,3],"dim0":1,"dim1":2},{"shape":[1,3,256,3],"dim0":2,"dim1":3},{"shape":[1,3,3,546],"dim0":2,"dim1":3},{"shape":[1,3,546,3],"dim0":1,"dim1":2},{"shape":[1,256,3,3],"dim0":1,"dim1":2},{"shape":[1,3,256,3],"dim0":2,"dim1":3},{"shape":[1,3,3,546],"dim0":2,"dim1":3},{"shape":[1,3,546,3],"dim0":1,"dim1":2},{"shape":[1,256,45,80],"dim0":1,"dim1":2},{"shape":[1,45,256,80],"dim0":2,"dim1":3},{"shape":[1,45,80,1024],"dim0":2,"dim1":3},{"shape":[1,45,1024,80],"dim0":1,"dim1":2},{"shape":[1,256,45,80],"dim0":1,"dim1":2},{"shape":[1,45,256,80],"dim0":2,"dim1":3},{"shape":[1,45,80,1024],"dim0":2,"dim1":3},{"shape":[1,45,1024,80],"dim0":1,"dim1":2},{"shape":[1,256,45,80],"dim0":1,"dim1":2},{"shape":[1,45,256,80],"dim0":2,"dim1":3},{"shape":[1,45,80,256],"dim0":2,"dim1":3},{"shape":[1,45,256,80],"dim0":1,"dim1":2},{"shape":[1,256,45,80],"dim0":1,"dim1":2},{"shape":[1,45,256,80],"dim0":2,"dim1":3},{"shape":[1,45,80,256],"dim0":2,"dim1":3},{"shape":[1,45,256,80],"dim0":1,"dim1":2},{"shape":[1,256,56,56],"dim0":1,"dim1":2},{"shape":[1,56,256,56],"dim0":2,"dim1":3},{"shape":[1,56,56,128],"dim0":2,"dim1":3},{"shape":[1,56,128,56],"dim0":1,"dim1":2},{"shape":[1,256,56,56],"dim0":1,"dim1":2},{"shape":[1,56,256,56],"dim0":2,"dim1":3},{"shape":[1,56,56,128],"dim0":2,"dim1":3},{"shape":[1,56,128,56],"dim0":1,"dim1":2},{"shape":[1,256,56,56],"dim0":1,"dim1":2},{"shape":[1,56,256,56],"dim0":2,"dim1":3},{"shape":[1,56,56,256],"dim0":2,"dim1":3},{"shape":[1,56,256,56],"dim0":1,"dim1":2},{"shape":[1,256,56,56],"dim0":1,"dim1":2},{"shape":[1,56,256,56],"dim0":2,"dim1":3},{"shape":[1,56,56,256],"dim0":2,"dim1":3},{"shape":[1,56,256,56],"dim0":1,"dim1":2},{"shape":[1,256,56,56],"dim0":1,"dim1":2},{"shape":[1,56,256,56],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,256,56,56],"dim0":1,"dim1":2},{"shape":[1,56,256,56],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,256,56,56],"dim0":1,"dim1":2},{"shape":[1,56,256,56],"dim0":2,"dim1":3},{"shape":[1,56,56,64],"dim0":2,"dim1":3},{"shape":[1,56,64,56],"dim0":1,"dim1":2},{"shape":[1,256,56,56],"dim0":1,"dim1":2},{"shape":[1,56,256,56],"dim0":2,"dim1":3},{"shape":[1,56,56,64],"dim0":2,"dim1":3},{"shape":[1,56,64,56],"dim0":1,"dim1":2},{"shape":[1,256,5,5],"dim0":1,"dim1":2},{"shape":[1,5,256,5],"dim0":2,"dim1":3},{"shape":[1,5,5,512],"dim0":2,"dim1":3},{"shape":[1,5,512,5],"dim0":1,"dim1":2},{"shape":[1,256,5,5],"dim0":1,"dim1":2},{"shape":[1,5,256,5],"dim0":2,"dim1":3},{"shape":[1,5,5,512],"dim0":2,"dim1":3},{"shape":[1,5,512,5],"dim0":1,"dim1":2},{"shape":[1,256,64,64],"dim0":1,"dim1":2},{"shape":[1,64,256,64],"dim0":2,"dim1":3},{"shape":[1,64,64,128],"dim0":2,"dim1":3},{"shape":[1,64,128,64],"dim0":1,"dim1":2},{"shape":[1,256,64,64],"dim0":1,"dim1":2},{"shape":[1,64,256,64],"dim0":2,"dim1":3},{"shape":[1,64,64,128],"dim0":2,"dim1":3},{"shape":[1,64,128,64],"dim0":1,"dim1":2},{"shape":[1,256,64,64],"dim0":1,"dim1":2},{"shape":[1,64,256,64],"dim0":2,"dim1":3},{"shape":[1,64,64,128],"dim0":2,"dim1":3},{"shape":[1,64,128,64],"dim0":1,"dim1":2},{"shape":[1,256,64,64],"dim0":1,"dim1":2},{"shape":[1,64,256,64],"dim0":2,"dim1":3},{"shape":[1,64,64,128],"dim0":2,"dim1":3},{"shape":[1,64,128,64],"dim0":1,"dim1":2},{"shape":[1,256,64,64],"dim0":1,"dim1":2},{"shape":[1,64,256,64],"dim0":2,"dim1":3},{"shape":[1,64,64,255],"dim0":2,"dim1":3},{"shape":[1,64,255,64],"dim0":1,"dim1":2},{"shape":[1,256,64,64],"dim0":1,"dim1":2},{"shape":[1,64,256,64],"dim0":2,"dim1":3},{"shape":[1,64,64,255],"dim0":2,"dim1":3},{"shape":[1,64,255,64],"dim0":1,"dim1":2},{"shape":[1,256,64,64],"dim0":1,"dim1":2},{"shape":[1,64,256,64],"dim0":2,"dim1":3},{"shape":[1,64,64,256],"dim0":2,"dim1":3},{"shape":[1,64,256,64],"dim0":1,"dim1":2},{"shape":[1,256,64,64],"dim0":1,"dim1":2},{"shape":[1,64,256,64],"dim0":2,"dim1":3},{"shape":[1,64,64,256],"dim0":2,"dim1":3},{"shape":[1,64,256,64],"dim0":1,"dim1":2},{"shape":[1,256,64,64],"dim0":1,"dim1":2},{"shape":[1,64,256,64],"dim0":2,"dim1":3},{"shape":[1,32,32,512],"dim0":2,"dim1":3},{"shape":[1,32,512,32],"dim0":1,"dim1":2},{"shape":[1,256,64,64],"dim0":1,"dim1":2},{"shape":[1,64,256,64],"dim0":2,"dim1":3},{"shape":[1,32,32,512],"dim0":2,"dim1":3},{"shape":[1,32,512,32],"dim0":1,"dim1":2},{"shape":[1,256,90,160],"dim0":1,"dim1":2},{"shape":[1,90,256,160],"dim0":2,"dim1":3},{"shape":[1,45,80,256],"dim0":2,"dim1":3},{"shape":[1,45,256,80],"dim0":1,"dim1":2},{"shape":[1,256,90,160],"dim0":1,"dim1":2},{"shape":[1,90,256,160],"dim0":2,"dim1":3},{"shape":[1,45,80,256],"dim0":2,"dim1":3},{"shape":[1,45,256,80],"dim0":1,"dim1":2},{"shape":[1,262,28,28],"dim0":1,"dim1":2},{"shape":[1,28,262,28],"dim0":2,"dim1":3},{"shape":[1,28,28,256],"dim0":2,"dim1":3},{"shape":[1,28,256,28],"dim0":1,"dim1":2},{"shape":[1,262,28,28],"dim0":1,"dim1":2},{"shape":[1,28,262,28],"dim0":2,"dim1":3},{"shape":[1,28,28,256],"dim0":2,"dim1":3},{"shape":[1,28,256,28],"dim0":1,"dim1":2},{"shape":[1,272,7,7],"dim0":1,"dim1":2},{"shape":[1,7,272,7],"dim0":2,"dim1":3},{"shape":[1,7,7,160],"dim0":2,"dim1":3},{"shape":[1,7,160,7],"dim0":1,"dim1":2},{"shape":[1,272,7,7],"dim0":1,"dim1":2},{"shape":[1,7,272,7],"dim0":2,"dim1":3},{"shape":[1,7,7,160],"dim0":2,"dim1":3},{"shape":[1,7,160,7],"dim0":1,"dim1":2},{"shape":[1,276,28,28],"dim0":1,"dim1":2},{"shape":[1,28,276,28],"dim0":2,"dim1":3},{"shape":[1,28,28,34],"dim0":2,"dim1":3},{"shape":[1,28,34,28],"dim0":1,"dim1":2},{"shape":[1,276,28,28],"dim0":1,"dim1":2},{"shape":[1,28,276,28],"dim0":2,"dim1":3},{"shape":[1,28,28,34],"dim0":2,"dim1":3},{"shape":[1,28,34,28],"dim0":1,"dim1":2},{"shape":[1,28,28,28],"dim0":1,"dim1":2},{"shape":[1,28,28,28],"dim0":2,"dim1":3},{"shape":[1,28,28,16],"dim0":2,"dim1":3},{"shape":[1,28,16,28],"dim0":1,"dim1":2},{"shape":[1,28,28,28],"dim0":1,"dim1":2},{"shape":[1,28,28,28],"dim0":2,"dim1":3},{"shape":[1,28,28,16],"dim0":2,"dim1":3},{"shape":[1,28,16,28],"dim0":1,"dim1":2},{"shape":[1,296,28,28],"dim0":1,"dim1":2},{"shape":[1,28,296,28],"dim0":2,"dim1":3},{"shape":[1,28,28,134],"dim0":2,"dim1":3},{"shape":[1,28,134,28],"dim0":1,"dim1":2},{"shape":[1,296,28,28],"dim0":1,"dim1":2},{"shape":[1,28,296,28],"dim0":2,"dim1":3},{"shape":[1,28,28,134],"dim0":2,"dim1":3},{"shape":[1,28,134,28],"dim0":1,"dim1":2},{"shape":[1,304,14,14],"dim0":1,"dim1":2},{"shape":[1,14,304,14],"dim0":2,"dim1":3},{"shape":[1,14,14,116],"dim0":2,"dim1":3},{"shape":[1,14,116,14],"dim0":1,"dim1":2},{"shape":[1,304,14,14],"dim0":1,"dim1":2},{"shape":[1,14,304,14],"dim0":2,"dim1":3},{"shape":[1,14,14,116],"dim0":2,"dim1":3},{"shape":[1,14,116,14],"dim0":1,"dim1":2},{"shape":[1,310,28,28],"dim0":1,"dim1":2},{"shape":[1,28,310,28],"dim0":2,"dim1":3},{"shape":[1,28,28,58],"dim0":2,"dim1":3},{"shape":[1,28,58,28],"dim0":1,"dim1":2},{"shape":[1,310,28,28],"dim0":1,"dim1":2},{"shape":[1,28,310,28],"dim0":2,"dim1":3},{"shape":[1,28,28,58],"dim0":2,"dim1":3},{"shape":[1,28,58,28],"dim0":1,"dim1":2},{"shape":[1,320,14,14],"dim0":1,"dim1":2},{"shape":[1,14,320,14],"dim0":2,"dim1":3},{"shape":[1,14,14,40],"dim0":2,"dim1":3},{"shape":[1,14,40,14],"dim0":1,"dim1":2},{"shape":[1,320,14,14],"dim0":1,"dim1":2},{"shape":[1,14,320,14],"dim0":2,"dim1":3},{"shape":[1,14,14,40],"dim0":2,"dim1":3},{"shape":[1,14,40,14],"dim0":1,"dim1":2},{"shape":[1,320,30,40],"dim0":1,"dim1":2},{"shape":[1,30,320,40],"dim0":2,"dim1":3},{"shape":[1,15,20,320],"dim0":2,"dim1":3},{"shape":[1,15,320,20],"dim0":1,"dim1":2},{"shape":[1,320,30,40],"dim0":1,"dim1":2},{"shape":[1,30,320,40],"dim0":2,"dim1":3},{"shape":[1,15,20,320],"dim0":2,"dim1":3},{"shape":[1,15,320,20],"dim0":1,"dim1":2},{"shape":[1,320,30,40],"dim0":1,"dim1":2},{"shape":[1,30,320,40],"dim0":2,"dim1":3},{"shape":[1,15,20,512],"dim0":2,"dim1":3},{"shape":[1,15,512,20],"dim0":1,"dim1":2},{"shape":[1,320,30,40],"dim0":1,"dim1":2},{"shape":[1,30,320,40],"dim0":2,"dim1":3},{"shape":[1,15,20,512],"dim0":2,"dim1":3},{"shape":[1,15,512,20],"dim0":1,"dim1":2},{"shape":[1,320,30,40],"dim0":1,"dim1":2},{"shape":[1,30,320,40],"dim0":2,"dim1":3},{"shape":[1,30,40,64],"dim0":2,"dim1":3},{"shape":[1,30,64,40],"dim0":1,"dim1":2},{"shape":[1,320,30,40],"dim0":1,"dim1":2},{"shape":[1,30,320,40],"dim0":2,"dim1":3},{"shape":[1,30,40,64],"dim0":2,"dim1":3},{"shape":[1,30,64,40],"dim0":1,"dim1":2},{"shape":[1,320,32,32],"dim0":1,"dim1":2},{"shape":[1,32,320,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,320,32,32],"dim0":1,"dim1":2},{"shape":[1,32,320,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,320,32,32],"dim0":1,"dim1":2},{"shape":[1,32,320,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,320,32,32],"dim0":1,"dim1":2},{"shape":[1,32,320,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,320,64,64],"dim0":1,"dim1":2},{"shape":[1,64,320,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,320,64,64],"dim0":1,"dim1":2},{"shape":[1,64,320,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,320,64,64],"dim0":1,"dim1":2},{"shape":[1,64,320,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,320,64,64],"dim0":1,"dim1":2},{"shape":[1,64,320,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,320,64,64],"dim0":1,"dim1":2},{"shape":[1,64,320,64],"dim0":2,"dim1":3},{"shape":[1,32,32,320],"dim0":2,"dim1":3},{"shape":[1,32,320,32],"dim0":1,"dim1":2},{"shape":[1,320,64,64],"dim0":1,"dim1":2},{"shape":[1,64,320,64],"dim0":2,"dim1":3},{"shape":[1,32,32,320],"dim0":2,"dim1":3},{"shape":[1,32,320,32],"dim0":1,"dim1":2},{"shape":[1,320,64,64],"dim0":1,"dim1":2},{"shape":[1,64,320,64],"dim0":2,"dim1":3},{"shape":[1,64,64,4],"dim0":2,"dim1":3},{"shape":[1,64,4,64],"dim0":1,"dim1":2},{"shape":[1,320,64,64],"dim0":1,"dim1":2},{"shape":[1,64,320,64],"dim0":2,"dim1":3},{"shape":[1,64,64,4],"dim0":2,"dim1":3},{"shape":[1,64,4,64],"dim0":1,"dim1":2},{"shape":[1,320,7,7],"dim0":1,"dim1":2},{"shape":[1,7,320,7],"dim0":2,"dim1":3},{"shape":[1,7,7,1280],"dim0":2,"dim1":3},{"shape":[1,7,1280,7],"dim0":1,"dim1":2},{"shape":[1,320,7,7],"dim0":1,"dim1":2},{"shape":[1,7,320,7],"dim0":2,"dim1":3},{"shape":[1,7,7,1280],"dim0":2,"dim1":3},{"shape":[1,7,1280,7],"dim0":1,"dim1":2},{"shape":[1,328,28,28],"dim0":1,"dim1":2},{"shape":[1,28,328,28],"dim0":2,"dim1":3},{"shape":[1,28,28,320],"dim0":2,"dim1":3},{"shape":[1,28,320,28],"dim0":1,"dim1":2},{"shape":[1,328,28,28],"dim0":1,"dim1":2},{"shape":[1,28,328,28],"dim0":2,"dim1":3},{"shape":[1,28,28,320],"dim0":2,"dim1":3},{"shape":[1,28,320,28],"dim0":1,"dim1":2},{"shape":[1,32,112,112],"dim0":1,"dim1":2},{"shape":[1,112,32,112],"dim0":2,"dim1":3},{"shape":[1,112,112,16],"dim0":2,"dim1":3},{"shape":[1,112,16,112],"dim0":1,"dim1":2},{"shape":[1,32,112,112],"dim0":1,"dim1":2},{"shape":[1,112,32,112],"dim0":2,"dim1":3},{"shape":[1,112,112,16],"dim0":2,"dim1":3},{"shape":[1,112,16,112],"dim0":1,"dim1":2},{"shape":[1,32,112,112],"dim0":1,"dim1":2},{"shape":[1,112,32,112],"dim0":2,"dim1":3},{"shape":[1,112,112,32],"dim0":2,"dim1":3},{"shape":[1,112,32,112],"dim0":1,"dim1":2},{"shape":[1,32,112,112],"dim0":1,"dim1":2},{"shape":[1,112,32,112],"dim0":2,"dim1":3},{"shape":[1,112,112,32],"dim0":2,"dim1":3},{"shape":[1,112,32,112],"dim0":1,"dim1":2},{"shape":[1,32,112,112],"dim0":1,"dim1":2},{"shape":[1,112,32,112],"dim0":2,"dim1":3},{"shape":[1,112,112,64],"dim0":2,"dim1":3},{"shape":[1,112,64,112],"dim0":1,"dim1":2},{"shape":[1,32,112,112],"dim0":1,"dim1":2},{"shape":[1,112,32,112],"dim0":2,"dim1":3},{"shape":[1,112,112,64],"dim0":2,"dim1":3},{"shape":[1,112,64,112],"dim0":1,"dim1":2},{"shape":[1,32,112,112],"dim0":1,"dim1":2},{"shape":[1,112,32,112],"dim0":2,"dim1":3},{"shape":[1,112,112,64],"dim0":2,"dim1":3},{"shape":[1,112,64,112],"dim0":1,"dim1":2},{"shape":[1,32,112,112],"dim0":1,"dim1":2},{"shape":[1,112,32,112],"dim0":2,"dim1":3},{"shape":[1,112,112,64],"dim0":2,"dim1":3},{"shape":[1,112,64,112],"dim0":1,"dim1":2},{"shape":[1,32,120,160],"dim0":1,"dim1":2},{"shape":[1,120,32,160],"dim0":2,"dim1":3},{"shape":[1,120,160,2],"dim0":2,"dim1":3},{"shape":[1,120,2,160],"dim0":1,"dim1":2},{"shape":[1,32,120,160],"dim0":1,"dim1":2},{"shape":[1,120,32,160],"dim0":2,"dim1":3},{"shape":[1,120,160,2],"dim0":2,"dim1":3},{"shape":[1,120,2,160],"dim0":1,"dim1":2},{"shape":[1,32,128,128],"dim0":1,"dim1":2},{"shape":[1,128,32,128],"dim0":2,"dim1":3},{"shape":[1,16,16,32],"dim0":2,"dim1":3},{"shape":[1,16,32,16],"dim0":1,"dim1":2},{"shape":[1,32,128,128],"dim0":1,"dim1":2},{"shape":[1,128,32,128],"dim0":2,"dim1":3},{"shape":[1,16,16,32],"dim0":2,"dim1":3},{"shape":[1,16,32,16],"dim0":1,"dim1":2},{"shape":[1,32,128,128],"dim0":1,"dim1":2},{"shape":[1,128,32,128],"dim0":2,"dim1":3},{"shape":[1,64,64,64],"dim0":2,"dim1":3},{"shape":[1,64,64,64],"dim0":1,"dim1":2},{"shape":[1,32,128,128],"dim0":1,"dim1":2},{"shape":[1,128,32,128],"dim0":2,"dim1":3},{"shape":[1,64,64,64],"dim0":2,"dim1":3},{"shape":[1,64,64,64],"dim0":1,"dim1":2},{"shape":[1,32,128,128],"dim0":1,"dim1":2},{"shape":[1,128,32,128],"dim0":2,"dim1":3},{"shape":[1,128,128,64],"dim0":2,"dim1":3},{"shape":[1,128,64,128],"dim0":1,"dim1":2},{"shape":[1,32,128,128],"dim0":1,"dim1":2},{"shape":[1,128,32,128],"dim0":2,"dim1":3},{"shape":[1,128,128,64],"dim0":2,"dim1":3},{"shape":[1,128,64,128],"dim0":1,"dim1":2},{"shape":[1,32,1,1],"dim0":1,"dim1":2},{"shape":[1,1,32,1],"dim0":2,"dim1":3},{"shape":[1,1,1,120],"dim0":2,"dim1":3},{"shape":[1,1,120,1],"dim0":1,"dim1":2},{"shape":[1,32,1,1],"dim0":1,"dim1":2},{"shape":[1,1,32,1],"dim0":2,"dim1":3},{"shape":[1,1,1,120],"dim0":2,"dim1":3},{"shape":[1,1,120,1],"dim0":1,"dim1":2},{"shape":[1,32,256,256],"dim0":1,"dim1":2},{"shape":[1,256,32,256],"dim0":2,"dim1":3},{"shape":[1,256,256,1],"dim0":2,"dim1":3},{"shape":[1,256,1,256],"dim0":1,"dim1":2},{"shape":[1,32,256,256],"dim0":1,"dim1":2},{"shape":[1,256,32,256],"dim0":2,"dim1":3},{"shape":[1,256,256,1],"dim0":2,"dim1":3},{"shape":[1,256,1,256],"dim0":1,"dim1":2},{"shape":[1,32,256,256],"dim0":1,"dim1":2},{"shape":[1,256,32,256],"dim0":2,"dim1":3},{"shape":[1,256,256,32],"dim0":2,"dim1":3},{"shape":[1,256,32,256],"dim0":1,"dim1":2},{"shape":[1,32,256,256],"dim0":1,"dim1":2},{"shape":[1,256,32,256],"dim0":2,"dim1":3},{"shape":[1,256,256,32],"dim0":2,"dim1":3},{"shape":[1,256,32,256],"dim0":1,"dim1":2},{"shape":[1,32,256,256],"dim0":1,"dim1":2},{"shape":[1,256,32,256],"dim0":2,"dim1":3},{"shape":[1,256,256,64],"dim0":2,"dim1":3},{"shape":[1,256,64,256],"dim0":1,"dim1":2},{"shape":[1,32,256,256],"dim0":1,"dim1":2},{"shape":[1,256,32,256],"dim0":2,"dim1":3},{"shape":[1,256,256,64],"dim0":2,"dim1":3},{"shape":[1,256,64,256],"dim0":1,"dim1":2},{"shape":[1,32,26,26],"dim0":1,"dim1":2},{"shape":[1,26,32,26],"dim0":2,"dim1":3},{"shape":[1,24,24,64],"dim0":2,"dim1":3},{"shape":[1,24,64,24],"dim0":1,"dim1":2},{"shape":[1,32,26,26],"dim0":1,"dim1":2},{"shape":[1,26,32,26],"dim0":2,"dim1":3},{"shape":[1,24,24,64],"dim0":2,"dim1":3},{"shape":[1,24,64,24],"dim0":1,"dim1":2},{"shape":[1,32,28,28],"dim0":1,"dim1":2},{"shape":[1,28,32,28],"dim0":2,"dim1":3},{"shape":[1,28,28,192],"dim0":2,"dim1":3},{"shape":[1,28,192,28],"dim0":1,"dim1":2},{"shape":[1,32,28,28],"dim0":1,"dim1":2},{"shape":[1,28,32,28],"dim0":2,"dim1":3},{"shape":[1,28,28,192],"dim0":2,"dim1":3},{"shape":[1,28,192,28],"dim0":1,"dim1":2},{"shape":[1,32,30,40],"dim0":1,"dim1":2},{"shape":[1,30,32,40],"dim0":2,"dim1":3},{"shape":[1,30,40,2],"dim0":2,"dim1":3},{"shape":[1,30,2,40],"dim0":1,"dim1":2},{"shape":[1,32,30,40],"dim0":1,"dim1":2},{"shape":[1,30,32,40],"dim0":2,"dim1":3},{"shape":[1,30,40,2],"dim0":2,"dim1":3},{"shape":[1,30,2,40],"dim0":1,"dim1":2},{"shape":[1,32,512,512],"dim0":1,"dim1":2},{"shape":[1,512,32,512],"dim0":2,"dim1":3},{"shape":[1,256,256,64],"dim0":2,"dim1":3},{"shape":[1,256,64,256],"dim0":1,"dim1":2},{"shape":[1,32,512,512],"dim0":1,"dim1":2},{"shape":[1,512,32,512],"dim0":2,"dim1":3},{"shape":[1,256,256,64],"dim0":2,"dim1":3},{"shape":[1,256,64,256],"dim0":1,"dim1":2},{"shape":[1,32,60,80],"dim0":1,"dim1":2},{"shape":[1,60,32,80],"dim0":2,"dim1":3},{"shape":[1,60,80,2],"dim0":2,"dim1":3},{"shape":[1,60,2,80],"dim0":1,"dim1":2},{"shape":[1,32,60,80],"dim0":1,"dim1":2},{"shape":[1,60,32,80],"dim0":2,"dim1":3},{"shape":[1,60,80,2],"dim0":2,"dim1":3},{"shape":[1,60,2,80],"dim0":1,"dim1":2},{"shape":[1,34,28,28],"dim0":1,"dim1":2},{"shape":[1,28,34,28],"dim0":2,"dim1":3},{"shape":[1,28,28,20],"dim0":2,"dim1":3},{"shape":[1,28,20,28],"dim0":1,"dim1":2},{"shape":[1,34,28,28],"dim0":1,"dim1":2},{"shape":[1,28,34,28],"dim0":2,"dim1":3},{"shape":[1,28,28,20],"dim0":2,"dim1":3},{"shape":[1,28,20,28],"dim0":1,"dim1":2},{"shape":[1,360,14,14],"dim0":1,"dim1":2},{"shape":[1,14,360,14],"dim0":2,"dim1":3},{"shape":[1,14,14,68],"dim0":2,"dim1":3},{"shape":[1,14,68,14],"dim0":1,"dim1":2},{"shape":[1,360,14,14],"dim0":1,"dim1":2},{"shape":[1,14,360,14],"dim0":2,"dim1":3},{"shape":[1,14,14,68],"dim0":2,"dim1":3},{"shape":[1,14,68,14],"dim0":1,"dim1":2},{"shape":[1,368,28,28],"dim0":1,"dim1":2},{"shape":[1,28,368,28],"dim0":2,"dim1":3},{"shape":[1,28,28,98],"dim0":2,"dim1":3},{"shape":[1,28,98,28],"dim0":1,"dim1":2},{"shape":[1,368,28,28],"dim0":1,"dim1":2},{"shape":[1,28,368,28],"dim0":2,"dim1":3},{"shape":[1,28,28,98],"dim0":2,"dim1":3},{"shape":[1,28,98,28],"dim0":1,"dim1":2},{"shape":[1,384,14,14],"dim0":1,"dim1":2},{"shape":[1,14,384,14],"dim0":2,"dim1":3},{"shape":[1,14,14,384],"dim0":2,"dim1":3},{"shape":[1,14,384,14],"dim0":1,"dim1":2},{"shape":[1,384,14,14],"dim0":1,"dim1":2},{"shape":[1,14,384,14],"dim0":2,"dim1":3},{"shape":[1,14,14,384],"dim0":2,"dim1":3},{"shape":[1,14,384,14],"dim0":1,"dim1":2},{"shape":[1,384,14,14],"dim0":1,"dim1":2},{"shape":[1,14,384,14],"dim0":2,"dim1":3},{"shape":[1,14,14,64],"dim0":2,"dim1":3},{"shape":[1,14,64,14],"dim0":1,"dim1":2},{"shape":[1,384,14,14],"dim0":1,"dim1":2},{"shape":[1,14,384,14],"dim0":2,"dim1":3},{"shape":[1,14,14,64],"dim0":2,"dim1":3},{"shape":[1,14,64,14],"dim0":1,"dim1":2},{"shape":[1,384,14,14],"dim0":1,"dim1":2},{"shape":[1,14,384,14],"dim0":2,"dim1":3},{"shape":[1,14,14,96],"dim0":2,"dim1":3},{"shape":[1,14,96,14],"dim0":1,"dim1":2},{"shape":[1,384,14,14],"dim0":1,"dim1":2},{"shape":[1,14,384,14],"dim0":2,"dim1":3},{"shape":[1,14,14,96],"dim0":2,"dim1":3},{"shape":[1,14,96,14],"dim0":1,"dim1":2},{"shape":[1,384,64,64],"dim0":1,"dim1":2},{"shape":[1,64,384,64],"dim0":2,"dim1":3},{"shape":[1,64,64,128],"dim0":2,"dim1":3},{"shape":[1,64,128,64],"dim0":1,"dim1":2},{"shape":[1,384,64,64],"dim0":1,"dim1":2},{"shape":[1,64,384,64],"dim0":2,"dim1":3},{"shape":[1,64,64,128],"dim0":2,"dim1":3},{"shape":[1,64,128,64],"dim0":1,"dim1":2},{"shape":[1,3,224,224],"dim0":1,"dim1":2},{"shape":[1,224,3,224],"dim0":2,"dim1":3},{"shape":[1,14,14,1024],"dim0":2,"dim1":3},{"shape":[1,14,1024,14],"dim0":1,"dim1":2},{"shape":[1,3,224,224],"dim0":1,"dim1":2},{"shape":[1,224,3,224],"dim0":2,"dim1":3},{"shape":[1,14,14,1024],"dim0":2,"dim1":3},{"shape":[1,14,1024,14],"dim0":1,"dim1":2},{"shape":[1,3,224,224],"dim0":1,"dim1":2},{"shape":[1,224,3,224],"dim0":2,"dim1":3},{"shape":[1,112,112,32],"dim0":2,"dim1":3},{"shape":[1,112,32,112],"dim0":1,"dim1":2},{"shape":[1,3,224,224],"dim0":1,"dim1":2},{"shape":[1,224,3,224],"dim0":2,"dim1":3},{"shape":[1,112,112,32],"dim0":2,"dim1":3},{"shape":[1,112,32,112],"dim0":1,"dim1":2},{"shape":[1,3,224,224],"dim0":1,"dim1":2},{"shape":[1,224,3,224],"dim0":2,"dim1":3},{"shape":[1,224,224,64],"dim0":2,"dim1":3},{"shape":[1,224,64,224],"dim0":1,"dim1":2},{"shape":[1,3,224,224],"dim0":1,"dim1":2},{"shape":[1,224,3,224],"dim0":2,"dim1":3},{"shape":[1,224,224,64],"dim0":2,"dim1":3},{"shape":[1,224,64,224],"dim0":1,"dim1":2},{"shape":[1,3,224,224],"dim0":1,"dim1":2},{"shape":[1,224,3,224],"dim0":2,"dim1":3},{"shape":[1,112,112,64],"dim0":2,"dim1":3},{"shape":[1,112,64,112],"dim0":1,"dim1":2},{"shape":[1,3,224,224],"dim0":1,"dim1":2},{"shape":[1,224,3,224],"dim0":2,"dim1":3},{"shape":[1,112,112,64],"dim0":2,"dim1":3},{"shape":[1,112,64,112],"dim0":1,"dim1":2},{"shape":[1,3,224,224],"dim0":1,"dim1":2},{"shape":[1,224,3,224],"dim0":2,"dim1":3},{"shape":[1,14,14,768],"dim0":2,"dim1":3},{"shape":[1,14,768,14],"dim0":1,"dim1":2},{"shape":[1,3,224,224],"dim0":1,"dim1":2},{"shape":[1,224,3,224],"dim0":2,"dim1":3},{"shape":[1,14,14,768],"dim0":2,"dim1":3},{"shape":[1,14,768,14],"dim0":1,"dim1":2},{"shape":[1,3,224,224],"dim0":1,"dim1":2},{"shape":[1,224,3,224],"dim0":2,"dim1":3},{"shape":[1,7,7,768],"dim0":2,"dim1":3},{"shape":[1,7,768,7],"dim0":1,"dim1":2},{"shape":[1,3,224,224],"dim0":1,"dim1":2},{"shape":[1,224,3,224],"dim0":2,"dim1":3},{"shape":[1,7,7,768],"dim0":2,"dim1":3},{"shape":[1,7,768,7],"dim0":1,"dim1":2},{"shape":[1,3,256,256],"dim0":1,"dim1":2},{"shape":[1,256,3,256],"dim0":2,"dim1":3},{"shape":[1,3,256,256],"dim0":1,"dim1":2},{"shape":[1,256,3,256],"dim0":2,"dim1":3},{"shape":[1,256,256,32],"dim0":2,"dim1":3},{"shape":[1,256,32,256],"dim0":1,"dim1":2},{"shape":[1,3,320,320],"dim0":1,"dim1":2},{"shape":[1,320,3,320],"dim0":2,"dim1":3},{"shape":[1,160,160,16],"dim0":2,"dim1":3},{"shape":[1,160,16,160],"dim0":1,"dim1":2},{"shape":[1,3,320,320],"dim0":1,"dim1":2},{"shape":[1,320,3,320],"dim0":2,"dim1":3},{"shape":[1,160,160,16],"dim0":2,"dim1":3},{"shape":[1,160,16,160],"dim0":1,"dim1":2},{"shape":[1,3,384,512],"dim0":1,"dim1":2},{"shape":[1,384,3,512],"dim0":2,"dim1":3},{"shape":[1,12,16,768],"dim0":2,"dim1":3},{"shape":[1,12,768,16],"dim0":1,"dim1":2},{"shape":[1,3,384,512],"dim0":1,"dim1":2},{"shape":[1,384,3,512],"dim0":2,"dim1":3},{"shape":[1,12,16,768],"dim0":2,"dim1":3},{"shape":[1,12,768,16],"dim0":1,"dim1":2},{"shape":[1,3,480,640],"dim0":1,"dim1":2},{"shape":[1,480,3,640],"dim0":2,"dim1":3},{"shape":[1,120,160,64],"dim0":2,"dim1":3},{"shape":[1,120,64,160],"dim0":1,"dim1":2},{"shape":[1,3,480,640],"dim0":1,"dim1":2},{"shape":[1,480,3,640],"dim0":2,"dim1":3},{"shape":[1,120,160,64],"dim0":2,"dim1":3},{"shape":[1,120,64,160],"dim0":1,"dim1":2},{"shape":[1,3,512,512],"dim0":1,"dim1":2},{"shape":[1,512,3,512],"dim0":2,"dim1":3},{"shape":[1,512,512,32],"dim0":2,"dim1":3},{"shape":[1,512,32,512],"dim0":1,"dim1":2},{"shape":[1,3,512,512],"dim0":1,"dim1":2},{"shape":[1,512,3,512],"dim0":2,"dim1":3},{"shape":[1,512,512,32],"dim0":2,"dim1":3},{"shape":[1,512,32,512],"dim0":1,"dim1":2},{"shape":[1,3,512,512],"dim0":1,"dim1":2},{"shape":[1,512,3,512],"dim0":2,"dim1":3},{"shape":[1,128,128,32],"dim0":2,"dim1":3},{"shape":[1,128,32,128],"dim0":1,"dim1":2},{"shape":[1,3,512,512],"dim0":1,"dim1":2},{"shape":[1,512,3,512],"dim0":2,"dim1":3},{"shape":[1,128,128,32],"dim0":2,"dim1":3},{"shape":[1,128,32,128],"dim0":1,"dim1":2},{"shape":[1,3,512,672],"dim0":1,"dim1":2},{"shape":[1,512,3,672],"dim0":2,"dim1":3},{"shape":[1,32,42,192],"dim0":2,"dim1":3},{"shape":[1,32,192,42],"dim0":1,"dim1":2},{"shape":[1,3,512,672],"dim0":1,"dim1":2},{"shape":[1,512,3,672],"dim0":2,"dim1":3},{"shape":[1,32,42,192],"dim0":2,"dim1":3},{"shape":[1,32,192,42],"dim0":1,"dim1":2},{"shape":[1,3,720,1280],"dim0":1,"dim1":2},{"shape":[1,720,3,1280],"dim0":2,"dim1":3},{"shape":[1,360,640,64],"dim0":2,"dim1":3},{"shape":[1,360,64,640],"dim0":1,"dim1":2},{"shape":[1,3,720,1280],"dim0":1,"dim1":2},{"shape":[1,720,3,1280],"dim0":2,"dim1":3},{"shape":[1,360,640,64],"dim0":2,"dim1":3},{"shape":[1,360,64,640],"dim0":1,"dim1":2},{"shape":[1,40,40,40],"dim0":1,"dim1":2},{"shape":[1,40,40,40],"dim0":2,"dim1":3},{"shape":[1,40,40,120],"dim0":2,"dim1":3},{"shape":[1,40,120,40],"dim0":1,"dim1":2},{"shape":[1,40,40,40],"dim0":1,"dim1":2},{"shape":[1,40,40,40],"dim0":2,"dim1":3},{"shape":[1,40,40,120],"dim0":2,"dim1":3},{"shape":[1,40,120,40],"dim0":1,"dim1":2},{"shape":[1,40,40,40],"dim0":1,"dim1":2},{"shape":[1,40,40,40],"dim0":2,"dim1":3},{"shape":[1,40,40,240],"dim0":2,"dim1":3},{"shape":[1,40,240,40],"dim0":1,"dim1":2},{"shape":[1,40,40,40],"dim0":1,"dim1":2},{"shape":[1,40,40,40],"dim0":2,"dim1":3},{"shape":[1,40,40,240],"dim0":2,"dim1":3},{"shape":[1,40,240,40],"dim0":1,"dim1":2},{"shape":[1,40,56,56],"dim0":1,"dim1":2},{"shape":[1,56,40,56],"dim0":2,"dim1":3},{"shape":[1,56,56,14],"dim0":2,"dim1":3},{"shape":[1,56,14,56],"dim0":1,"dim1":2},{"shape":[1,40,56,56],"dim0":1,"dim1":2},{"shape":[1,56,40,56],"dim0":2,"dim1":3},{"shape":[1,56,56,14],"dim0":2,"dim1":3},{"shape":[1,56,14,56],"dim0":1,"dim1":2},{"shape":[1,428,14,14],"dim0":1,"dim1":2},{"shape":[1,14,428,14],"dim0":2,"dim1":3},{"shape":[1,14,14,116],"dim0":2,"dim1":3},{"shape":[1,14,116,14],"dim0":1,"dim1":2},{"shape":[1,428,14,14],"dim0":1,"dim1":2},{"shape":[1,14,428,14],"dim0":2,"dim1":3},{"shape":[1,14,14,116],"dim0":2,"dim1":3},{"shape":[1,14,116,14],"dim0":1,"dim1":2},{"shape":[1,466,28,28],"dim0":1,"dim1":2},{"shape":[1,28,466,28],"dim0":2,"dim1":3},{"shape":[1,28,28,168],"dim0":2,"dim1":3},{"shape":[1,28,168,28],"dim0":1,"dim1":2},{"shape":[1,466,28,28],"dim0":1,"dim1":2},{"shape":[1,28,466,28],"dim0":2,"dim1":3},{"shape":[1,28,28,168],"dim0":2,"dim1":3},{"shape":[1,28,168,28],"dim0":1,"dim1":2},{"shape":[1,46,28,28],"dim0":1,"dim1":2},{"shape":[1,28,46,28],"dim0":2,"dim1":3},{"shape":[1,28,28,16],"dim0":2,"dim1":3},{"shape":[1,28,16,28],"dim0":1,"dim1":2},{"shape":[1,46,28,28],"dim0":1,"dim1":2},{"shape":[1,28,46,28],"dim0":2,"dim1":3},{"shape":[1,28,28,16],"dim0":2,"dim1":3},{"shape":[1,28,16,28],"dim0":1,"dim1":2},{"shape":[1,480,10,10],"dim0":1,"dim1":2},{"shape":[1,10,480,10],"dim0":2,"dim1":3},{"shape":[1,10,10,24],"dim0":2,"dim1":3},{"shape":[1,10,24,10],"dim0":1,"dim1":2},{"shape":[1,480,10,10],"dim0":1,"dim1":2},{"shape":[1,10,480,10],"dim0":2,"dim1":3},{"shape":[1,10,10,24],"dim0":2,"dim1":3},{"shape":[1,10,24,10],"dim0":1,"dim1":2},{"shape":[1,10,10,256],"dim0":2,"dim1":3},{"shape":[1,10,256,10],"dim0":1,"dim1":2},{"shape":[1,480,10,10],"dim0":1,"dim1":2},{"shape":[1,10,480,10],"dim0":2,"dim1":3},{"shape":[1,10,10,256],"dim0":2,"dim1":3},{"shape":[1,10,256,10],"dim0":1,"dim1":2},{"shape":[1,10,10,480],"dim0":2,"dim1":3},{"shape":[1,10,480,10],"dim0":1,"dim1":2},{"shape":[1,480,10,10],"dim0":1,"dim1":2},{"shape":[1,10,480,10],"dim0":2,"dim1":3},{"shape":[1,10,10,480],"dim0":2,"dim1":3},{"shape":[1,10,480,10],"dim0":1,"dim1":2},{"shape":[1,480,10,10],"dim0":1,"dim1":2},{"shape":[1,10,480,10],"dim0":2,"dim1":3},{"shape":[1,10,10,480],"dim0":2,"dim1":3},{"shape":[1,10,480,10],"dim0":1,"dim1":2},{"shape":[1,480,10,10],"dim0":1,"dim1":2},{"shape":[1,10,480,10],"dim0":2,"dim1":3},{"shape":[1,10,10,480],"dim0":2,"dim1":3},{"shape":[1,10,480,10],"dim0":1,"dim1":2},{"shape":[1,480,10,10],"dim0":1,"dim1":2},{"shape":[1,10,480,10],"dim0":2,"dim1":3},{"shape":[1,10,10,546],"dim0":2,"dim1":3},{"shape":[1,10,546,10],"dim0":1,"dim1":2},{"shape":[1,480,10,10],"dim0":1,"dim1":2},{"shape":[1,10,480,10],"dim0":2,"dim1":3},{"shape":[1,10,10,546],"dim0":2,"dim1":3},{"shape":[1,10,546,10],"dim0":1,"dim1":2},{"shape":[1,10,10,80],"dim0":2,"dim1":3},{"shape":[1,10,80,10],"dim0":1,"dim1":2},{"shape":[1,480,10,10],"dim0":1,"dim1":2},{"shape":[1,10,480,10],"dim0":2,"dim1":3},{"shape":[1,10,10,80],"dim0":2,"dim1":3},{"shape":[1,10,80,10],"dim0":1,"dim1":2},{"shape":[1,480,1,1],"dim0":1,"dim1":2},{"shape":[1,1,480,1],"dim0":2,"dim1":3},{"shape":[1,1,1,120],"dim0":2,"dim1":3},{"shape":[1,1,120,1],"dim0":1,"dim1":2},{"shape":[1,480,1,1],"dim0":1,"dim1":2},{"shape":[1,1,480,1],"dim0":2,"dim1":3},{"shape":[1,1,1,120],"dim0":2,"dim1":3},{"shape":[1,1,120,1],"dim0":1,"dim1":2},{"shape":[1,480,20,20],"dim0":1,"dim1":2},{"shape":[1,20,480,20],"dim0":2,"dim1":3},{"shape":[1,20,20,112],"dim0":2,"dim1":3},{"shape":[1,20,112,20],"dim0":1,"dim1":2},{"shape":[1,480,20,20],"dim0":1,"dim1":2},{"shape":[1,20,480,20],"dim0":2,"dim1":3},{"shape":[1,20,20,112],"dim0":2,"dim1":3},{"shape":[1,20,112,20],"dim0":1,"dim1":2},{"shape":[1,480,20,20],"dim0":1,"dim1":2},{"shape":[1,20,480,20],"dim0":2,"dim1":3},{"shape":[1,20,20,480],"dim0":2,"dim1":3},{"shape":[1,20,480,20],"dim0":1,"dim1":2},{"shape":[1,480,20,20],"dim0":1,"dim1":2},{"shape":[1,20,480,20],"dim0":2,"dim1":3},{"shape":[1,20,20,480],"dim0":2,"dim1":3},{"shape":[1,20,480,20],"dim0":1,"dim1":2},{"shape":[1,4,64,64],"dim0":1,"dim1":2},{"shape":[1,64,4,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,4,64,64],"dim0":1,"dim1":2},{"shape":[1,64,4,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,512,14,14],"dim0":1,"dim1":2},{"shape":[1,14,512,14],"dim0":2,"dim1":3},{"shape":[1,14,14,1024],"dim0":2,"dim1":3},{"shape":[1,14,1024,14],"dim0":1,"dim1":2},{"shape":[1,512,14,14],"dim0":1,"dim1":2},{"shape":[1,14,512,14],"dim0":2,"dim1":3},{"shape":[1,14,14,1024],"dim0":2,"dim1":3},{"shape":[1,14,1024,14],"dim0":1,"dim1":2},{"shape":[1,512,14,14],"dim0":1,"dim1":2},{"shape":[1,14,512,14],"dim0":2,"dim1":3},{"shape":[1,7,7,512],"dim0":2,"dim1":3},{"shape":[1,7,512,7],"dim0":1,"dim1":2},{"shape":[1,512,14,14],"dim0":1,"dim1":2},{"shape":[1,14,512,14],"dim0":2,"dim1":3},{"shape":[1,7,7,512],"dim0":2,"dim1":3},{"shape":[1,7,512,7],"dim0":1,"dim1":2},{"shape":[1,512,15,20],"dim0":1,"dim1":2},{"shape":[1,15,512,20],"dim0":2,"dim1":3},{"shape":[1,15,20,64],"dim0":2,"dim1":3},{"shape":[1,15,64,20],"dim0":1,"dim1":2},{"shape":[1,512,15,20],"dim0":1,"dim1":2},{"shape":[1,15,512,20],"dim0":2,"dim1":3},{"shape":[1,15,20,64],"dim0":2,"dim1":3},{"shape":[1,15,64,20],"dim0":1,"dim1":2},{"shape":[1,512,16,16],"dim0":1,"dim1":2},{"shape":[1,16,512,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1024],"dim0":2,"dim1":3},{"shape":[1,16,1024,16],"dim0":1,"dim1":2},{"shape":[1,512,16,16],"dim0":1,"dim1":2},{"shape":[1,16,512,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1024],"dim0":2,"dim1":3},{"shape":[1,16,1024,16],"dim0":1,"dim1":2},{"shape":[1,512,16,16],"dim0":1,"dim1":2},{"shape":[1,16,512,16],"dim0":2,"dim1":3},{"shape":[1,16,16,256],"dim0":2,"dim1":3},{"shape":[1,16,256,16],"dim0":1,"dim1":2},{"shape":[1,512,16,16],"dim0":1,"dim1":2},{"shape":[1,16,512,16],"dim0":2,"dim1":3},{"shape":[1,16,16,256],"dim0":2,"dim1":3},{"shape":[1,16,256,16],"dim0":1,"dim1":2},{"shape":[1,512,16,16],"dim0":1,"dim1":2},{"shape":[1,16,512,16],"dim0":2,"dim1":3},{"shape":[1,16,16,512],"dim0":2,"dim1":3},{"shape":[1,16,512,16],"dim0":1,"dim1":2},{"shape":[1,512,16,16],"dim0":1,"dim1":2},{"shape":[1,16,512,16],"dim0":2,"dim1":3},{"shape":[1,16,16,512],"dim0":2,"dim1":3},{"shape":[1,16,512,16],"dim0":1,"dim1":2},{"shape":[1,512,23,40],"dim0":1,"dim1":2},{"shape":[1,23,512,40],"dim0":2,"dim1":3},{"shape":[1,23,40,2048],"dim0":2,"dim1":3},{"shape":[1,23,2048,40],"dim0":1,"dim1":2},{"shape":[1,512,23,40],"dim0":1,"dim1":2},{"shape":[1,23,512,40],"dim0":2,"dim1":3},{"shape":[1,23,40,2048],"dim0":2,"dim1":3},{"shape":[1,23,2048,40],"dim0":1,"dim1":2},{"shape":[1,512,23,40],"dim0":1,"dim1":2},{"shape":[1,23,512,40],"dim0":2,"dim1":3},{"shape":[1,23,40,512],"dim0":2,"dim1":3},{"shape":[1,23,512,40],"dim0":1,"dim1":2},{"shape":[1,512,23,40],"dim0":1,"dim1":2},{"shape":[1,23,512,40],"dim0":2,"dim1":3},{"shape":[1,23,40,512],"dim0":2,"dim1":3},{"shape":[1,23,512,40],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,14,14,1024],"dim0":2,"dim1":3},{"shape":[1,14,1024,14],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,14,14,1024],"dim0":2,"dim1":3},{"shape":[1,14,1024,14],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,19],"dim0":2,"dim1":3},{"shape":[1,28,19,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,19],"dim0":2,"dim1":3},{"shape":[1,28,19,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,256],"dim0":2,"dim1":3},{"shape":[1,28,256,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,256],"dim0":2,"dim1":3},{"shape":[1,28,256,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,38],"dim0":2,"dim1":3},{"shape":[1,28,38,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,38],"dim0":2,"dim1":3},{"shape":[1,28,38,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,512,28,28],"dim0":1,"dim1":2},{"shape":[1,28,512,28],"dim0":2,"dim1":3},{"shape":[1,28,28,512],"dim0":2,"dim1":3},{"shape":[1,28,512,28],"dim0":1,"dim1":2},{"shape":[1,512,32,32],"dim0":1,"dim1":2},{"shape":[1,32,512,32],"dim0":2,"dim1":3},{"shape":[1,16,16,1024],"dim0":2,"dim1":3},{"shape":[1,16,1024,16],"dim0":1,"dim1":2},{"shape":[1,512,32,32],"dim0":1,"dim1":2},{"shape":[1,32,512,32],"dim0":2,"dim1":3},{"shape":[1,16,16,1024],"dim0":2,"dim1":3},{"shape":[1,16,1024,16],"dim0":1,"dim1":2},{"shape":[1,512,32,32],"dim0":1,"dim1":2},{"shape":[1,32,512,32],"dim0":2,"dim1":3},{"shape":[1,32,32,255],"dim0":2,"dim1":3},{"shape":[1,32,255,32],"dim0":1,"dim1":2},{"shape":[1,512,32,32],"dim0":1,"dim1":2},{"shape":[1,32,512,32],"dim0":2,"dim1":3},{"shape":[1,32,32,255],"dim0":2,"dim1":3},{"shape":[1,32,255,32],"dim0":1,"dim1":2},{"shape":[1,512,32,32],"dim0":1,"dim1":2},{"shape":[1,32,512,32],"dim0":2,"dim1":3},{"shape":[1,32,32,256],"dim0":2,"dim1":3},{"shape":[1,32,256,32],"dim0":1,"dim1":2},{"shape":[1,512,32,32],"dim0":1,"dim1":2},{"shape":[1,32,512,32],"dim0":2,"dim1":3},{"shape":[1,32,32,256],"dim0":2,"dim1":3},{"shape":[1,32,256,32],"dim0":1,"dim1":2},{"shape":[1,512,32,32],"dim0":1,"dim1":2},{"shape":[1,32,512,32],"dim0":2,"dim1":3},{"shape":[1,32,32,256],"dim0":2,"dim1":3},{"shape":[1,32,256,32],"dim0":1,"dim1":2},{"shape":[1,512,32,32],"dim0":1,"dim1":2},{"shape":[1,32,512,32],"dim0":2,"dim1":3},{"shape":[1,32,32,256],"dim0":2,"dim1":3},{"shape":[1,32,256,32],"dim0":1,"dim1":2},{"shape":[1,512,45,80],"dim0":1,"dim1":2},{"shape":[1,45,512,80],"dim0":2,"dim1":3},{"shape":[1,23,40,512],"dim0":2,"dim1":3},{"shape":[1,23,512,40],"dim0":1,"dim1":2},{"shape":[1,512,45,80],"dim0":1,"dim1":2},{"shape":[1,45,512,80],"dim0":2,"dim1":3},{"shape":[1,23,40,512],"dim0":2,"dim1":3},{"shape":[1,23,512,40],"dim0":1,"dim1":2},{"shape":[1,512,56,56],"dim0":1,"dim1":2},{"shape":[1,56,512,56],"dim0":2,"dim1":3},{"shape":[1,56,56,256],"dim0":2,"dim1":3},{"shape":[1,56,256,56],"dim0":1,"dim1":2},{"shape":[1,512,56,56],"dim0":1,"dim1":2},{"shape":[1,56,512,56],"dim0":2,"dim1":3},{"shape":[1,56,56,256],"dim0":2,"dim1":3},{"shape":[1,56,256,56],"dim0":1,"dim1":2},{"shape":[1,512,5,5],"dim0":1,"dim1":2},{"shape":[1,5,512,5],"dim0":2,"dim1":3},{"shape":[1,5,5,128],"dim0":2,"dim1":3},{"shape":[1,5,128,5],"dim0":1,"dim1":2},{"shape":[1,512,5,5],"dim0":1,"dim1":2},{"shape":[1,5,512,5],"dim0":2,"dim1":3},{"shape":[1,5,5,128],"dim0":2,"dim1":3},{"shape":[1,5,128,5],"dim0":1,"dim1":2},{"shape":[1,512,5,5],"dim0":1,"dim1":2},{"shape":[1,5,512,5],"dim0":2,"dim1":3},{"shape":[1,5,5,24],"dim0":2,"dim1":3},{"shape":[1,5,24,5],"dim0":1,"dim1":2},{"shape":[1,512,5,5],"dim0":1,"dim1":2},{"shape":[1,5,512,5],"dim0":2,"dim1":3},{"shape":[1,5,5,24],"dim0":2,"dim1":3},{"shape":[1,5,24,5],"dim0":1,"dim1":2},{"shape":[1,512,5,5],"dim0":1,"dim1":2},{"shape":[1,5,512,5],"dim0":2,"dim1":3},{"shape":[1,5,5,512],"dim0":2,"dim1":3},{"shape":[1,5,512,5],"dim0":1,"dim1":2},{"shape":[1,512,5,5],"dim0":1,"dim1":2},{"shape":[1,5,512,5],"dim0":2,"dim1":3},{"shape":[1,5,5,512],"dim0":2,"dim1":3},{"shape":[1,5,512,5],"dim0":1,"dim1":2},{"shape":[1,512,5,5],"dim0":1,"dim1":2},{"shape":[1,5,512,5],"dim0":2,"dim1":3},{"shape":[1,5,5,546],"dim0":2,"dim1":3},{"shape":[1,5,546,5],"dim0":1,"dim1":2},{"shape":[1,512,5,5],"dim0":1,"dim1":2},{"shape":[1,5,512,5],"dim0":2,"dim1":3},{"shape":[1,5,5,546],"dim0":2,"dim1":3},{"shape":[1,5,546,5],"dim0":1,"dim1":2},{"shape":[1,512,60,80],"dim0":1,"dim1":2},{"shape":[1,60,512,80],"dim0":2,"dim1":3},{"shape":[1,60,80,512],"dim0":2,"dim1":3},{"shape":[1,60,512,80],"dim0":1,"dim1":2},{"shape":[1,512,60,80],"dim0":1,"dim1":2},{"shape":[1,60,512,80],"dim0":2,"dim1":3},{"shape":[1,60,80,512],"dim0":2,"dim1":3},{"shape":[1,60,512,80],"dim0":1,"dim1":2},{"shape":[1,512,7,7],"dim0":1,"dim1":2},{"shape":[1,7,512,7],"dim0":2,"dim1":3},{"shape":[1,7,7,2048],"dim0":2,"dim1":3},{"shape":[1,7,2048,7],"dim0":1,"dim1":2},{"shape":[1,512,7,7],"dim0":1,"dim1":2},{"shape":[1,7,512,7],"dim0":2,"dim1":3},{"shape":[1,7,7,2048],"dim0":2,"dim1":3},{"shape":[1,7,2048,7],"dim0":1,"dim1":2},{"shape":[1,512,7,7],"dim0":1,"dim1":2},{"shape":[1,7,512,7],"dim0":2,"dim1":3},{"shape":[1,7,7,512],"dim0":2,"dim1":3},{"shape":[1,7,512,7],"dim0":1,"dim1":2},{"shape":[1,512,7,7],"dim0":1,"dim1":2},{"shape":[1,7,512,7],"dim0":2,"dim1":3},{"shape":[1,7,7,512],"dim0":2,"dim1":3},{"shape":[1,7,512,7],"dim0":1,"dim1":2},{"shape":[1,512,90,160],"dim0":1,"dim1":2},{"shape":[1,90,512,160],"dim0":2,"dim1":3},{"shape":[1,45,80,1024],"dim0":2,"dim1":3},{"shape":[1,45,1024,80],"dim0":1,"dim1":2},{"shape":[1,512,90,160],"dim0":1,"dim1":2},{"shape":[1,90,512,160],"dim0":2,"dim1":3},{"shape":[1,45,80,1024],"dim0":2,"dim1":3},{"shape":[1,45,1024,80],"dim0":1,"dim1":2},{"shape":[1,512,90,160],"dim0":1,"dim1":2},{"shape":[1,90,512,160],"dim0":2,"dim1":3},{"shape":[1,512,90,160],"dim0":1,"dim1":2},{"shape":[1,90,512,160],"dim0":2,"dim1":3},{"shape":[1,90,160,128],"dim0":2,"dim1":3},{"shape":[1,90,128,160],"dim0":1,"dim1":2},{"shape":[1,90,160,256],"dim0":2,"dim1":3},{"shape":[1,90,256,160],"dim0":1,"dim1":2},{"shape":[1,512,90,160],"dim0":1,"dim1":2},{"shape":[1,90,512,160],"dim0":2,"dim1":3},{"shape":[1,90,160,256],"dim0":2,"dim1":3},{"shape":[1,90,256,160],"dim0":1,"dim1":2},{"shape":[1,544,14,14],"dim0":1,"dim1":2},{"shape":[1,14,544,14],"dim0":2,"dim1":3},{"shape":[1,14,14,196],"dim0":2,"dim1":3},{"shape":[1,14,196,14],"dim0":1,"dim1":2},{"shape":[1,544,14,14],"dim0":1,"dim1":2},{"shape":[1,14,544,14],"dim0":2,"dim1":3},{"shape":[1,14,14,196],"dim0":2,"dim1":3},{"shape":[1,14,196,14],"dim0":1,"dim1":2},{"shape":[1,54,56,56],"dim0":1,"dim1":2},{"shape":[1,56,54,56],"dim0":2,"dim1":3},{"shape":[1,56,56,24],"dim0":2,"dim1":3},{"shape":[1,56,24,56],"dim0":1,"dim1":2},{"shape":[1,54,56,56],"dim0":1,"dim1":2},{"shape":[1,56,54,56],"dim0":2,"dim1":3},{"shape":[1,56,56,24],"dim0":2,"dim1":3},{"shape":[1,56,24,56],"dim0":1,"dim1":2},{"shape":[1,576,14,14],"dim0":1,"dim1":2},{"shape":[1,14,576,14],"dim0":2,"dim1":3},{"shape":[1,14,14,576],"dim0":2,"dim1":3},{"shape":[1,14,576,14],"dim0":1,"dim1":2},{"shape":[1,576,14,14],"dim0":1,"dim1":2},{"shape":[1,14,576,14],"dim0":2,"dim1":3},{"shape":[1,14,14,576],"dim0":2,"dim1":3},{"shape":[1,14,576,14],"dim0":1,"dim1":2},{"shape":[1,576,14,14],"dim0":1,"dim1":2},{"shape":[1,14,576,14],"dim0":2,"dim1":3},{"shape":[1,7,7,576],"dim0":2,"dim1":3},{"shape":[1,7,576,7],"dim0":1,"dim1":2},{"shape":[1,576,14,14],"dim0":1,"dim1":2},{"shape":[1,14,576,14],"dim0":2,"dim1":3},{"shape":[1,7,7,576],"dim0":2,"dim1":3},{"shape":[1,7,576,7],"dim0":1,"dim1":2},{"shape":[1,576,14,14],"dim0":1,"dim1":2},{"shape":[1,14,576,14],"dim0":2,"dim1":3},{"shape":[1,14,14,96],"dim0":2,"dim1":3},{"shape":[1,14,96,14],"dim0":1,"dim1":2},{"shape":[1,576,14,14],"dim0":1,"dim1":2},{"shape":[1,14,576,14],"dim0":2,"dim1":3},{"shape":[1,14,14,96],"dim0":2,"dim1":3},{"shape":[1,14,96,14],"dim0":1,"dim1":2},{"shape":[1,576,7,7],"dim0":1,"dim1":2},{"shape":[1,7,576,7],"dim0":2,"dim1":3},{"shape":[1,7,7,160],"dim0":2,"dim1":3},{"shape":[1,7,160,7],"dim0":1,"dim1":2},{"shape":[1,576,7,7],"dim0":1,"dim1":2},{"shape":[1,7,576,7],"dim0":2,"dim1":3},{"shape":[1,7,7,160],"dim0":2,"dim1":3},{"shape":[1,7,160,7],"dim0":1,"dim1":2},{"shape":[1,58,28,28],"dim0":1,"dim1":2},{"shape":[1,28,58,28],"dim0":2,"dim1":3},{"shape":[1,28,28,20],"dim0":2,"dim1":3},{"shape":[1,28,20,28],"dim0":1,"dim1":2},{"shape":[1,58,28,28],"dim0":1,"dim1":2},{"shape":[1,28,58,28],"dim0":2,"dim1":3},{"shape":[1,28,28,20],"dim0":2,"dim1":3},{"shape":[1,28,20,28],"dim0":1,"dim1":2},{"shape":[1,62,28,28],"dim0":1,"dim1":2},{"shape":[1,28,62,28],"dim0":2,"dim1":3},{"shape":[1,28,28,28],"dim0":2,"dim1":3},{"shape":[1,28,28,28],"dim0":1,"dim1":2},{"shape":[1,62,28,28],"dim0":1,"dim1":2},{"shape":[1,28,62,28],"dim0":2,"dim1":3},{"shape":[1,28,28,28],"dim0":2,"dim1":3},{"shape":[1,28,28,28],"dim0":1,"dim1":2},{"shape":[1,640,16,16],"dim0":1,"dim1":2},{"shape":[1,16,640,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,640,16,16],"dim0":1,"dim1":2},{"shape":[1,16,640,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,640,16,16],"dim0":1,"dim1":2},{"shape":[1,16,640,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,640,16,16],"dim0":1,"dim1":2},{"shape":[1,16,640,16],"dim0":2,"dim1":3},{"shape":[1,16,16,1280],"dim0":2,"dim1":3},{"shape":[1,16,1280,16],"dim0":1,"dim1":2},{"shape":[1,640,32,32],"dim0":1,"dim1":2},{"shape":[1,32,640,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,640,32,32],"dim0":1,"dim1":2},{"shape":[1,32,640,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,640,32,32],"dim0":1,"dim1":2},{"shape":[1,32,640,32],"dim0":2,"dim1":3},{"shape":[1,640,32,32],"dim0":1,"dim1":2},{"shape":[1,32,640,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,640,32,32],"dim0":1,"dim1":2},{"shape":[1,32,640,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,640,32,32],"dim0":1,"dim1":2},{"shape":[1,32,640,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,640,32,32],"dim0":1,"dim1":2},{"shape":[1,32,640,32],"dim0":2,"dim1":3},{"shape":[1,16,16,640],"dim0":2,"dim1":3},{"shape":[1,16,640,16],"dim0":1,"dim1":2},{"shape":[1,640,32,32],"dim0":1,"dim1":2},{"shape":[1,32,640,32],"dim0":2,"dim1":3},{"shape":[1,16,16,640],"dim0":2,"dim1":3},{"shape":[1,16,640,16],"dim0":1,"dim1":2},{"shape":[1,640,64,64],"dim0":1,"dim1":2},{"shape":[1,64,640,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,640,64,64],"dim0":1,"dim1":2},{"shape":[1,64,640,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,640,64,64],"dim0":1,"dim1":2},{"shape":[1,64,640,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,640,64,64],"dim0":1,"dim1":2},{"shape":[1,64,640,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,640,64,64],"dim0":1,"dim1":2},{"shape":[1,64,640,64],"dim0":2,"dim1":3},{"shape":[1,64,64,640],"dim0":2,"dim1":3},{"shape":[1,64,640,64],"dim0":1,"dim1":2},{"shape":[1,640,64,64],"dim0":1,"dim1":2},{"shape":[1,64,640,64],"dim0":2,"dim1":3},{"shape":[1,64,64,640],"dim0":2,"dim1":3},{"shape":[1,64,640,64],"dim0":1,"dim1":2},{"shape":[1,640,7,7],"dim0":1,"dim1":2},{"shape":[1,7,640,7],"dim0":2,"dim1":3},{"shape":[1,7,7,160],"dim0":2,"dim1":3},{"shape":[1,7,160,7],"dim0":1,"dim1":2},{"shape":[1,640,7,7],"dim0":1,"dim1":2},{"shape":[1,7,640,7],"dim0":2,"dim1":3},{"shape":[1,7,7,160],"dim0":2,"dim1":3},{"shape":[1,7,160,7],"dim0":1,"dim1":2},{"shape":[1,64,112,112],"dim0":1,"dim1":2},{"shape":[1,112,64,112],"dim0":2,"dim1":3},{"shape":[1,112,112,128],"dim0":2,"dim1":3},{"shape":[1,112,128,112],"dim0":1,"dim1":2},{"shape":[1,64,112,112],"dim0":1,"dim1":2},{"shape":[1,112,64,112],"dim0":2,"dim1":3},{"shape":[1,112,112,128],"dim0":2,"dim1":3},{"shape":[1,112,128,112],"dim0":1,"dim1":2},{"shape":[1,64,112,112],"dim0":1,"dim1":2},{"shape":[1,112,64,112],"dim0":2,"dim1":3},{"shape":[1,56,56,64],"dim0":2,"dim1":3},{"shape":[1,56,64,56],"dim0":1,"dim1":2},{"shape":[1,64,112,112],"dim0":1,"dim1":2},{"shape":[1,112,64,112],"dim0":2,"dim1":3},{"shape":[1,56,56,64],"dim0":2,"dim1":3},{"shape":[1,56,64,56],"dim0":1,"dim1":2},{"shape":[1,64,120,160],"dim0":1,"dim1":2},{"shape":[1,120,64,160],"dim0":2,"dim1":3},{"shape":[1,60,80,128],"dim0":2,"dim1":3},{"shape":[1,60,128,80],"dim0":1,"dim1":2},{"shape":[1,64,120,160],"dim0":1,"dim1":2},{"shape":[1,120,64,160],"dim0":2,"dim1":3},{"shape":[1,60,80,128],"dim0":2,"dim1":3},{"shape":[1,60,128,80],"dim0":1,"dim1":2},{"shape":[1,64,120,160],"dim0":1,"dim1":2},{"shape":[1,120,64,160],"dim0":2,"dim1":3},{"shape":[1,120,160,32],"dim0":2,"dim1":3},{"shape":[1,120,32,160],"dim0":1,"dim1":2},{"shape":[1,64,120,160],"dim0":1,"dim1":2},{"shape":[1,120,64,160],"dim0":2,"dim1":3},{"shape":[1,120,160,32],"dim0":2,"dim1":3},{"shape":[1,120,32,160],"dim0":1,"dim1":2},{"shape":[1,64,120,160],"dim0":1,"dim1":2},{"shape":[1,120,64,160],"dim0":2,"dim1":3},{"shape":[1,15,20,64],"dim0":2,"dim1":3},{"shape":[1,15,64,20],"dim0":1,"dim1":2},{"shape":[1,64,120,160],"dim0":1,"dim1":2},{"shape":[1,120,64,160],"dim0":2,"dim1":3},{"shape":[1,15,20,64],"dim0":2,"dim1":3},{"shape":[1,15,64,20],"dim0":1,"dim1":2},{"shape":[1,64,128,128],"dim0":1,"dim1":2},{"shape":[1,128,64,128],"dim0":2,"dim1":3},{"shape":[1,128,128,128],"dim0":2,"dim1":3},{"shape":[1,128,128,128],"dim0":1,"dim1":2},{"shape":[1,64,128,128],"dim0":1,"dim1":2},{"shape":[1,128,64,128],"dim0":2,"dim1":3},{"shape":[1,128,128,128],"dim0":2,"dim1":3},{"shape":[1,128,128,128],"dim0":1,"dim1":2},{"shape":[1,64,128,128],"dim0":1,"dim1":2},{"shape":[1,128,64,128],"dim0":2,"dim1":3},{"shape":[1,64,128,128],"dim0":1,"dim1":2},{"shape":[1,128,64,128],"dim0":2,"dim1":3},{"shape":[1,128,128,64],"dim0":2,"dim1":3},{"shape":[1,128,64,128],"dim0":1,"dim1":2},{"shape":[1,64,14,14],"dim0":1,"dim1":2},{"shape":[1,14,64,14],"dim0":2,"dim1":3},{"shape":[1,14,14,384],"dim0":2,"dim1":3},{"shape":[1,14,384,14],"dim0":1,"dim1":2},{"shape":[1,64,14,14],"dim0":1,"dim1":2},{"shape":[1,14,64,14],"dim0":2,"dim1":3},{"shape":[1,14,14,384],"dim0":2,"dim1":3},{"shape":[1,14,384,14],"dim0":1,"dim1":2},{"shape":[1,64,160,160],"dim0":1,"dim1":2},{"shape":[1,160,64,160],"dim0":2,"dim1":3},{"shape":[1,80,80,64],"dim0":2,"dim1":3},{"shape":[1,80,64,80],"dim0":1,"dim1":2},{"shape":[1,64,160,160],"dim0":1,"dim1":2},{"shape":[1,160,64,160],"dim0":2,"dim1":3},{"shape":[1,80,80,64],"dim0":2,"dim1":3},{"shape":[1,80,64,80],"dim0":1,"dim1":2},{"shape":[1,64,180,320],"dim0":1,"dim1":2},{"shape":[1,180,64,320],"dim0":2,"dim1":3},{"shape":[1,180,320,256],"dim0":2,"dim1":3},{"shape":[1,180,256,320],"dim0":1,"dim1":2},{"shape":[1,64,180,320],"dim0":1,"dim1":2},{"shape":[1,180,64,320],"dim0":2,"dim1":3},{"shape":[1,180,320,256],"dim0":2,"dim1":3},{"shape":[1,180,256,320],"dim0":1,"dim1":2},{"shape":[1,64,180,320],"dim0":1,"dim1":2},{"shape":[1,180,64,320],"dim0":2,"dim1":3},{"shape":[1,64,180,320],"dim0":1,"dim1":2},{"shape":[1,180,64,320],"dim0":2,"dim1":3},{"shape":[1,180,320,64],"dim0":2,"dim1":3},{"shape":[1,180,64,320],"dim0":1,"dim1":2},{"shape":[1,64,180,320],"dim0":1,"dim1":2},{"shape":[1,180,64,320],"dim0":2,"dim1":3},{"shape":[1,180,320,64],"dim0":2,"dim1":3},{"shape":[1,180,64,320],"dim0":1,"dim1":2},{"shape":[1,64,180,320],"dim0":1,"dim1":2},{"shape":[1,180,64,320],"dim0":2,"dim1":3},{"shape":[1,180,320,64],"dim0":2,"dim1":3},{"shape":[1,180,64,320],"dim0":1,"dim1":2},{"shape":[1,64,1,1],"dim0":1,"dim1":2},{"shape":[1,1,64,1],"dim0":2,"dim1":3},{"shape":[1,1,1,128],"dim0":2,"dim1":3},{"shape":[1,1,128,1],"dim0":1,"dim1":2},{"shape":[1,64,1,1],"dim0":1,"dim1":2},{"shape":[1,1,64,1],"dim0":2,"dim1":3},{"shape":[1,1,1,128],"dim0":2,"dim1":3},{"shape":[1,1,128,1],"dim0":1,"dim1":2},{"shape":[1,64,224,224],"dim0":1,"dim1":2},{"shape":[1,224,64,224],"dim0":2,"dim1":3},{"shape":[1,224,224,1],"dim0":2,"dim1":3},{"shape":[1,224,1,224],"dim0":1,"dim1":2},{"shape":[1,64,224,224],"dim0":1,"dim1":2},{"shape":[1,224,64,224],"dim0":2,"dim1":3},{"shape":[1,224,224,1],"dim0":2,"dim1":3},{"shape":[1,224,1,224],"dim0":1,"dim1":2},{"shape":[1,64,224,224],"dim0":1,"dim1":2},{"shape":[1,224,64,224],"dim0":2,"dim1":3},{"shape":[1,224,224,64],"dim0":2,"dim1":3},{"shape":[1,224,64,224],"dim0":1,"dim1":2},{"shape":[1,64,224,224],"dim0":1,"dim1":2},{"shape":[1,224,64,224],"dim0":2,"dim1":3},{"shape":[1,224,224,64],"dim0":2,"dim1":3},{"shape":[1,224,64,224],"dim0":1,"dim1":2},{"shape":[1,64,256,256],"dim0":1,"dim1":2},{"shape":[1,256,64,256],"dim0":2,"dim1":3},{"shape":[1,128,128,128],"dim0":2,"dim1":3},{"shape":[1,128,128,128],"dim0":1,"dim1":2},{"shape":[1,64,256,256],"dim0":1,"dim1":2},{"shape":[1,256,64,256],"dim0":2,"dim1":3},{"shape":[1,128,128,128],"dim0":2,"dim1":3},{"shape":[1,128,128,128],"dim0":1,"dim1":2},{"shape":[1,64,256,256],"dim0":1,"dim1":2},{"shape":[1,256,64,256],"dim0":2,"dim1":3},{"shape":[1,256,256,32],"dim0":2,"dim1":3},{"shape":[1,256,32,256],"dim0":1,"dim1":2},{"shape":[1,64,256,256],"dim0":1,"dim1":2},{"shape":[1,256,64,256],"dim0":2,"dim1":3},{"shape":[1,256,256,32],"dim0":2,"dim1":3},{"shape":[1,256,32,256],"dim0":1,"dim1":2},{"shape":[1,64,256,256],"dim0":1,"dim1":2},{"shape":[1,256,64,256],"dim0":2,"dim1":3},{"shape":[1,256,256,32],"dim0":2,"dim1":3},{"shape":[1,256,32,256],"dim0":1,"dim1":2},{"shape":[1,64,256,256],"dim0":1,"dim1":2},{"shape":[1,256,64,256],"dim0":2,"dim1":3},{"shape":[1,256,256,32],"dim0":2,"dim1":3},{"shape":[1,256,32,256],"dim0":1,"dim1":2},{"shape":[1,64,2,2],"dim0":1,"dim1":2},{"shape":[1,2,64,2],"dim0":2,"dim1":3},{"shape":[1,1,1,64],"dim0":2,"dim1":3},{"shape":[1,1,64,1],"dim0":1,"dim1":2},{"shape":[1,64,2,2],"dim0":1,"dim1":2},{"shape":[1,2,64,2],"dim0":2,"dim1":3},{"shape":[1,1,1,64],"dim0":2,"dim1":3},{"shape":[1,1,64,1],"dim0":1,"dim1":2},{"shape":[1,64,30,40],"dim0":1,"dim1":2},{"shape":[1,30,64,40],"dim0":2,"dim1":3},{"shape":[1,30,40,32],"dim0":2,"dim1":3},{"shape":[1,30,32,40],"dim0":1,"dim1":2},{"shape":[1,64,30,40],"dim0":1,"dim1":2},{"shape":[1,30,64,40],"dim0":2,"dim1":3},{"shape":[1,30,40,32],"dim0":2,"dim1":3},{"shape":[1,30,32,40],"dim0":1,"dim1":2},{"shape":[1,64,480,640],"dim0":1,"dim1":2},{"shape":[1,480,64,640],"dim0":2,"dim1":3},{"shape":[1,480,640,1],"dim0":2,"dim1":3},{"shape":[1,480,1,640],"dim0":1,"dim1":2},{"shape":[1,64,480,640],"dim0":1,"dim1":2},{"shape":[1,480,64,640],"dim0":2,"dim1":3},{"shape":[1,480,640,1],"dim0":2,"dim1":3},{"shape":[1,480,1,640],"dim0":1,"dim1":2},{"shape":[1,64,480,640],"dim0":1,"dim1":2},{"shape":[1,480,64,640],"dim0":2,"dim1":3},{"shape":[1,480,640,64],"dim0":2,"dim1":3},{"shape":[1,480,64,640],"dim0":1,"dim1":2},{"shape":[1,64,480,640],"dim0":1,"dim1":2},{"shape":[1,480,64,640],"dim0":2,"dim1":3},{"shape":[1,480,640,64],"dim0":2,"dim1":3},{"shape":[1,480,64,640],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,56,56,128],"dim0":2,"dim1":3},{"shape":[1,56,128,56],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,56,56,128],"dim0":2,"dim1":3},{"shape":[1,56,128,56],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,28,28,128],"dim0":2,"dim1":3},{"shape":[1,28,128,28],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,56,56,14],"dim0":2,"dim1":3},{"shape":[1,56,14,56],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,56,56,14],"dim0":2,"dim1":3},{"shape":[1,56,14,56],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,56,56,256],"dim0":2,"dim1":3},{"shape":[1,56,256,56],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,56,56,256],"dim0":2,"dim1":3},{"shape":[1,56,256,56],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,56,56,64],"dim0":2,"dim1":3},{"shape":[1,56,64,56],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,56,56,64],"dim0":2,"dim1":3},{"shape":[1,56,64,56],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,56,56,64],"dim0":2,"dim1":3},{"shape":[1,56,64,56],"dim0":1,"dim1":2},{"shape":[1,64,56,56],"dim0":1,"dim1":2},{"shape":[1,56,64,56],"dim0":2,"dim1":3},{"shape":[1,56,56,64],"dim0":2,"dim1":3},{"shape":[1,56,64,56],"dim0":1,"dim1":2},{"shape":[1,64,60,80],"dim0":1,"dim1":2},{"shape":[1,60,64,80],"dim0":2,"dim1":3},{"shape":[1,60,80,32],"dim0":2,"dim1":3},{"shape":[1,60,32,80],"dim0":1,"dim1":2},{"shape":[1,64,60,80],"dim0":1,"dim1":2},{"shape":[1,60,64,80],"dim0":2,"dim1":3},{"shape":[1,60,80,32],"dim0":2,"dim1":3},{"shape":[1,60,32,80],"dim0":1,"dim1":2},{"shape":[1,64,64,64],"dim0":1,"dim1":2},{"shape":[1,64,64,64],"dim0":2,"dim1":3},{"shape":[1,64,64,128],"dim0":2,"dim1":3},{"shape":[1,64,128,64],"dim0":1,"dim1":2},{"shape":[1,64,64,64],"dim0":1,"dim1":2},{"shape":[1,64,64,64],"dim0":2,"dim1":3},{"shape":[1,64,64,128],"dim0":2,"dim1":3},{"shape":[1,64,128,64],"dim0":1,"dim1":2},{"shape":[1,64,64,64],"dim0":1,"dim1":2},{"shape":[1,64,64,64],"dim0":2,"dim1":3},{"shape":[1,32,32,160],"dim0":2,"dim1":3},{"shape":[1,32,160,32],"dim0":1,"dim1":2},{"shape":[1,64,64,64],"dim0":1,"dim1":2},{"shape":[1,64,64,64],"dim0":2,"dim1":3},{"shape":[1,32,32,160],"dim0":2,"dim1":3},{"shape":[1,32,160,32],"dim0":1,"dim1":2},{"shape":[1,64,64,64],"dim0":1,"dim1":2},{"shape":[1,64,64,64],"dim0":2,"dim1":3},{"shape":[1,16,16,64],"dim0":2,"dim1":3},{"shape":[1,16,64,16],"dim0":1,"dim1":2},{"shape":[1,64,64,64],"dim0":1,"dim1":2},{"shape":[1,64,64,64],"dim0":2,"dim1":3},{"shape":[1,16,16,64],"dim0":2,"dim1":3},{"shape":[1,16,64,16],"dim0":1,"dim1":2},{"shape":[1,64,80,80],"dim0":1,"dim1":2},{"shape":[1,80,64,80],"dim0":2,"dim1":3},{"shape":[1,80,80,24],"dim0":2,"dim1":3},{"shape":[1,80,24,80],"dim0":1,"dim1":2},{"shape":[1,64,80,80],"dim0":1,"dim1":2},{"shape":[1,80,64,80],"dim0":2,"dim1":3},{"shape":[1,80,80,24],"dim0":2,"dim1":3},{"shape":[1,80,24,80],"dim0":1,"dim1":2},{"shape":[1,654,14,14],"dim0":1,"dim1":2},{"shape":[1,14,654,14],"dim0":2,"dim1":3},{"shape":[1,14,14,640],"dim0":2,"dim1":3},{"shape":[1,14,640,14],"dim0":1,"dim1":2},{"shape":[1,654,14,14],"dim0":1,"dim1":2},{"shape":[1,14,654,14],"dim0":2,"dim1":3},{"shape":[1,14,14,640],"dim0":2,"dim1":3},{"shape":[1,14,640,14],"dim0":1,"dim1":2},{"shape":[1,672,10,10],"dim0":1,"dim1":2},{"shape":[1,10,672,10],"dim0":2,"dim1":3},{"shape":[1,672,10,10],"dim0":1,"dim1":2},{"shape":[1,10,672,10],"dim0":2,"dim1":3},{"shape":[1,10,10,80],"dim0":2,"dim1":3},{"shape":[1,10,80,10],"dim0":1,"dim1":2},{"shape":[1,672,1,1],"dim0":1,"dim1":2},{"shape":[1,1,672,1],"dim0":2,"dim1":3},{"shape":[1,1,1,168],"dim0":2,"dim1":3},{"shape":[1,1,168,1],"dim0":1,"dim1":2},{"shape":[1,672,1,1],"dim0":1,"dim1":2},{"shape":[1,1,672,1],"dim0":2,"dim1":3},{"shape":[1,1,1,168],"dim0":2,"dim1":3},{"shape":[1,1,168,1],"dim0":1,"dim1":2},{"shape":[1,672,20,20],"dim0":1,"dim1":2},{"shape":[1,20,672,20],"dim0":2,"dim1":3},{"shape":[1,672,20,20],"dim0":1,"dim1":2},{"shape":[1,20,672,20],"dim0":2,"dim1":3},{"shape":[1,20,20,112],"dim0":2,"dim1":3},{"shape":[1,20,112,20],"dim0":1,"dim1":2},{"shape":[1,672,20,20],"dim0":1,"dim1":2},{"shape":[1,20,672,20],"dim0":2,"dim1":3},{"shape":[1,20,20,24],"dim0":2,"dim1":3},{"shape":[1,20,24,20],"dim0":1,"dim1":2},{"shape":[1,672,20,20],"dim0":1,"dim1":2},{"shape":[1,20,672,20],"dim0":2,"dim1":3},{"shape":[1,20,20,24],"dim0":2,"dim1":3},{"shape":[1,20,24,20],"dim0":1,"dim1":2},{"shape":[1,672,20,20],"dim0":1,"dim1":2},{"shape":[1,20,672,20],"dim0":2,"dim1":3},{"shape":[1,20,20,546],"dim0":2,"dim1":3},{"shape":[1,20,546,20],"dim0":1,"dim1":2},{"shape":[1,672,20,20],"dim0":1,"dim1":2},{"shape":[1,20,672,20],"dim0":2,"dim1":3},{"shape":[1,20,20,546],"dim0":2,"dim1":3},{"shape":[1,20,546,20],"dim0":1,"dim1":2},{"shape":[1,672,20,20],"dim0":1,"dim1":2},{"shape":[1,20,672,20],"dim0":2,"dim1":3},{"shape":[1,20,20,672],"dim0":2,"dim1":3},{"shape":[1,20,672,20],"dim0":1,"dim1":2},{"shape":[1,672,20,20],"dim0":1,"dim1":2},{"shape":[1,20,672,20],"dim0":2,"dim1":3},{"shape":[1,20,20,672],"dim0":2,"dim1":3},{"shape":[1,20,672,20],"dim0":1,"dim1":2},{"shape":[1,672,20,20],"dim0":1,"dim1":2},{"shape":[1,20,672,20],"dim0":2,"dim1":3},{"shape":[1,10,10,672],"dim0":2,"dim1":3},{"shape":[1,10,672,10],"dim0":1,"dim1":2},{"shape":[1,672,20,20],"dim0":1,"dim1":2},{"shape":[1,20,672,20],"dim0":2,"dim1":3},{"shape":[1,10,10,672],"dim0":2,"dim1":3},{"shape":[1,10,672,10],"dim0":1,"dim1":2},{"shape":[1,68,14,14],"dim0":1,"dim1":2},{"shape":[1,14,68,14],"dim0":2,"dim1":3},{"shape":[1,14,14,40],"dim0":2,"dim1":3},{"shape":[1,14,40,14],"dim0":1,"dim1":2},{"shape":[1,68,14,14],"dim0":1,"dim1":2},{"shape":[1,14,68,14],"dim0":2,"dim1":3},{"shape":[1,14,14,40],"dim0":2,"dim1":3},{"shape":[1,14,40,14],"dim0":1,"dim1":2},{"shape":[1,72,1,1],"dim0":1,"dim1":2},{"shape":[1,1,72,1],"dim0":2,"dim1":3},{"shape":[1,72,1,1],"dim0":1,"dim1":2},{"shape":[1,1,72,1],"dim0":2,"dim1":3},{"shape":[1,1,1,24],"dim0":2,"dim1":3},{"shape":[1,1,24,1],"dim0":1,"dim1":2},{"shape":[1,72,40,40],"dim0":1,"dim1":2},{"shape":[1,40,72,40],"dim0":2,"dim1":3},{"shape":[1,72,40,40],"dim0":1,"dim1":2},{"shape":[1,40,72,40],"dim0":2,"dim1":3},{"shape":[1,40,40,40],"dim0":2,"dim1":3},{"shape":[1,40,40,40],"dim0":1,"dim1":2},{"shape":[1,72,80,80],"dim0":1,"dim1":2},{"shape":[1,80,72,80],"dim0":2,"dim1":3},{"shape":[1,80,80,24],"dim0":2,"dim1":3},{"shape":[1,80,24,80],"dim0":1,"dim1":2},{"shape":[1,72,80,80],"dim0":1,"dim1":2},{"shape":[1,80,72,80],"dim0":2,"dim1":3},{"shape":[1,80,80,24],"dim0":2,"dim1":3},{"shape":[1,80,24,80],"dim0":1,"dim1":2},{"shape":[1,72,80,80],"dim0":1,"dim1":2},{"shape":[1,80,72,80],"dim0":2,"dim1":3},{"shape":[1,80,80,72],"dim0":2,"dim1":3},{"shape":[1,80,72,80],"dim0":1,"dim1":2},{"shape":[1,72,80,80],"dim0":1,"dim1":2},{"shape":[1,80,72,80],"dim0":2,"dim1":3},{"shape":[1,80,80,72],"dim0":2,"dim1":3},{"shape":[1,80,72,80],"dim0":1,"dim1":2},{"shape":[1,72,80,80],"dim0":1,"dim1":2},{"shape":[1,80,72,80],"dim0":2,"dim1":3},{"shape":[1,40,40,72],"dim0":2,"dim1":3},{"shape":[1,40,72,40],"dim0":1,"dim1":2},{"shape":[1,72,80,80],"dim0":1,"dim1":2},{"shape":[1,80,72,80],"dim0":2,"dim1":3},{"shape":[1,40,40,72],"dim0":2,"dim1":3},{"shape":[1,40,72,40],"dim0":1,"dim1":2},{"shape":[1,740,14,14],"dim0":1,"dim1":2},{"shape":[1,14,740,14],"dim0":2,"dim1":3},{"shape":[1,14,14,334],"dim0":2,"dim1":3},{"shape":[1,14,334,14],"dim0":1,"dim1":2},{"shape":[1,740,14,14],"dim0":1,"dim1":2},{"shape":[1,14,740,14],"dim0":2,"dim1":3},{"shape":[1,14,14,334],"dim0":2,"dim1":3},{"shape":[1,14,334,14],"dim0":1,"dim1":2},{"shape":[1,768,32,32],"dim0":1,"dim1":2},{"shape":[1,32,768,32],"dim0":2,"dim1":3},{"shape":[1,32,32,256],"dim0":2,"dim1":3},{"shape":[1,32,256,32],"dim0":1,"dim1":2},{"shape":[1,768,32,32],"dim0":1,"dim1":2},{"shape":[1,32,768,32],"dim0":2,"dim1":3},{"shape":[1,32,32,256],"dim0":2,"dim1":3},{"shape":[1,32,256,32],"dim0":1,"dim1":2},{"shape":[1,782,7,7],"dim0":1,"dim1":2},{"shape":[1,7,782,7],"dim0":2,"dim1":3},{"shape":[1,7,7,1024],"dim0":2,"dim1":3},{"shape":[1,7,1024,7],"dim0":1,"dim1":2},{"shape":[1,782,7,7],"dim0":1,"dim1":2},{"shape":[1,7,782,7],"dim0":2,"dim1":3},{"shape":[1,7,7,1024],"dim0":2,"dim1":3},{"shape":[1,7,1024,7],"dim0":1,"dim1":2},{"shape":[1,78,28,28],"dim0":1,"dim1":2},{"shape":[1,28,78,28],"dim0":2,"dim1":3},{"shape":[1,28,28,16],"dim0":2,"dim1":3},{"shape":[1,28,16,28],"dim0":1,"dim1":2},{"shape":[1,78,28,28],"dim0":1,"dim1":2},{"shape":[1,28,78,28],"dim0":2,"dim1":3},{"shape":[1,28,28,16],"dim0":2,"dim1":3},{"shape":[1,28,16,28],"dim0":1,"dim1":2},{"shape":[1,78,28,28],"dim0":1,"dim1":2},{"shape":[1,28,78,28],"dim0":2,"dim1":3},{"shape":[1,28,28,34],"dim0":2,"dim1":3},{"shape":[1,28,34,28],"dim0":1,"dim1":2},{"shape":[1,78,28,28],"dim0":1,"dim1":2},{"shape":[1,28,78,28],"dim0":2,"dim1":3},{"shape":[1,28,28,34],"dim0":2,"dim1":3},{"shape":[1,28,34,28],"dim0":1,"dim1":2},{"shape":[1,78,56,56],"dim0":1,"dim1":2},{"shape":[1,56,78,56],"dim0":2,"dim1":3},{"shape":[1,56,56,24],"dim0":2,"dim1":3},{"shape":[1,56,24,56],"dim0":1,"dim1":2},{"shape":[1,78,56,56],"dim0":1,"dim1":2},{"shape":[1,56,78,56],"dim0":2,"dim1":3},{"shape":[1,56,56,24],"dim0":2,"dim1":3},{"shape":[1,56,24,56],"dim0":1,"dim1":2},{"shape":[1,800,7,7],"dim0":1,"dim1":2},{"shape":[1,7,800,7],"dim0":2,"dim1":3},{"shape":[1,7,7,272],"dim0":2,"dim1":3},{"shape":[1,7,272,7],"dim0":1,"dim1":2},{"shape":[1,800,7,7],"dim0":1,"dim1":2},{"shape":[1,7,800,7],"dim0":2,"dim1":3},{"shape":[1,7,7,272],"dim0":2,"dim1":3},{"shape":[1,7,272,7],"dim0":1,"dim1":2},{"shape":[1,80,10,10],"dim0":1,"dim1":2},{"shape":[1,10,80,10],"dim0":2,"dim1":3},{"shape":[1,10,10,480],"dim0":2,"dim1":3},{"shape":[1,10,480,10],"dim0":1,"dim1":2},{"shape":[1,80,10,10],"dim0":1,"dim1":2},{"shape":[1,10,80,10],"dim0":2,"dim1":3},{"shape":[1,10,10,480],"dim0":2,"dim1":3},{"shape":[1,10,480,10],"dim0":1,"dim1":2},{"shape":[1,80,20,20],"dim0":1,"dim1":2},{"shape":[1,20,80,20],"dim0":2,"dim1":3},{"shape":[1,20,20,184],"dim0":2,"dim1":3},{"shape":[1,20,184,20],"dim0":1,"dim1":2},{"shape":[1,80,20,20],"dim0":1,"dim1":2},{"shape":[1,20,80,20],"dim0":2,"dim1":3},{"shape":[1,20,20,184],"dim0":2,"dim1":3},{"shape":[1,20,184,20],"dim0":1,"dim1":2},{"shape":[1,80,20,20],"dim0":1,"dim1":2},{"shape":[1,20,80,20],"dim0":2,"dim1":3},{"shape":[1,20,20,200],"dim0":2,"dim1":3},{"shape":[1,20,200,20],"dim0":1,"dim1":2},{"shape":[1,80,20,20],"dim0":1,"dim1":2},{"shape":[1,20,80,20],"dim0":2,"dim1":3},{"shape":[1,20,20,200],"dim0":2,"dim1":3},{"shape":[1,20,200,20],"dim0":1,"dim1":2},{"shape":[1,80,20,20],"dim0":1,"dim1":2},{"shape":[1,20,80,20],"dim0":2,"dim1":3},{"shape":[1,20,20,480],"dim0":2,"dim1":3},{"shape":[1,20,480,20],"dim0":1,"dim1":2},{"shape":[1,80,20,20],"dim0":1,"dim1":2},{"shape":[1,20,80,20],"dim0":2,"dim1":3},{"shape":[1,20,20,480],"dim0":2,"dim1":3},{"shape":[1,20,480,20],"dim0":1,"dim1":2},{"shape":[1,94,28,28],"dim0":1,"dim1":2},{"shape":[1,28,94,28],"dim0":2,"dim1":3},{"shape":[1,28,28,28],"dim0":2,"dim1":3},{"shape":[1,28,28,28],"dim0":1,"dim1":2},{"shape":[1,94,28,28],"dim0":1,"dim1":2},{"shape":[1,28,94,28],"dim0":2,"dim1":3},{"shape":[1,28,28,28],"dim0":2,"dim1":3},{"shape":[1,28,28,28],"dim0":1,"dim1":2},{"shape":[1,960,32,32],"dim0":1,"dim1":2},{"shape":[1,32,960,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,960,32,32],"dim0":1,"dim1":2},{"shape":[1,32,960,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,960,32,32],"dim0":1,"dim1":2},{"shape":[1,32,960,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,960,32,32],"dim0":1,"dim1":2},{"shape":[1,32,960,32],"dim0":2,"dim1":3},{"shape":[1,32,32,640],"dim0":2,"dim1":3},{"shape":[1,32,640,32],"dim0":1,"dim1":2},{"shape":[1,960,64,64],"dim0":1,"dim1":2},{"shape":[1,64,960,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,960,64,64],"dim0":1,"dim1":2},{"shape":[1,64,960,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,960,64,64],"dim0":1,"dim1":2},{"shape":[1,64,960,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,960,64,64],"dim0":1,"dim1":2},{"shape":[1,64,960,64],"dim0":2,"dim1":3},{"shape":[1,64,64,320],"dim0":2,"dim1":3},{"shape":[1,64,320,64],"dim0":1,"dim1":2},{"shape":[1,960,7,7],"dim0":1,"dim1":2},{"shape":[1,7,960,7],"dim0":2,"dim1":3},{"shape":[1,7,7,160],"dim0":2,"dim1":3},{"shape":[1,7,160,7],"dim0":1,"dim1":2},{"shape":[1,960,7,7],"dim0":1,"dim1":2},{"shape":[1,7,960,7],"dim0":2,"dim1":3},{"shape":[1,7,7,160],"dim0":2,"dim1":3},{"shape":[1,7,160,7],"dim0":1,"dim1":2},{"shape":[1,960,7,7],"dim0":1,"dim1":2},{"shape":[1,7,960,7],"dim0":2,"dim1":3},{"shape":[1,7,7,320],"dim0":2,"dim1":3},{"shape":[1,7,320,7],"dim0":1,"dim1":2},{"shape":[1,960,7,7],"dim0":1,"dim1":2},{"shape":[1,7,960,7],"dim0":2,"dim1":3},{"shape":[1,7,7,320],"dim0":2,"dim1":3},{"shape":[1,7,320,7],"dim0":1,"dim1":2},{"shape":[1,960,7,7],"dim0":1,"dim1":2},{"shape":[1,7,960,7],"dim0":2,"dim1":3},{"shape":[1,7,7,960],"dim0":2,"dim1":3},{"shape":[1,7,960,7],"dim0":1,"dim1":2},{"shape":[1,960,7,7],"dim0":1,"dim1":2},{"shape":[1,7,960,7],"dim0":2,"dim1":3},{"shape":[1,7,7,960],"dim0":2,"dim1":3},{"shape":[1,7,960,7],"dim0":1,"dim1":2},{"shape":[1,96,112,112],"dim0":1,"dim1":2},{"shape":[1,112,96,112],"dim0":2,"dim1":3},{"shape":[1,56,56,96],"dim0":2,"dim1":3},{"shape":[1,56,96,56],"dim0":1,"dim1":2},{"shape":[1,96,112,112],"dim0":1,"dim1":2},{"shape":[1,112,96,112],"dim0":2,"dim1":3},{"shape":[1,56,56,96],"dim0":2,"dim1":3},{"shape":[1,56,96,56],"dim0":1,"dim1":2},{"shape":[1,96,14,14],"dim0":1,"dim1":2},{"shape":[1,14,96,14],"dim0":2,"dim1":3},{"shape":[1,14,14,576],"dim0":2,"dim1":3},{"shape":[1,14,576,14],"dim0":1,"dim1":2},{"shape":[1,96,14,14],"dim0":1,"dim1":2},{"shape":[1,14,96,14],"dim0":2,"dim1":3},{"shape":[1,14,14,576],"dim0":2,"dim1":3},{"shape":[1,14,576,14],"dim0":1,"dim1":2},{"shape":[1,96,56,56],"dim0":1,"dim1":2},{"shape":[1,56,96,56],"dim0":2,"dim1":3},{"shape":[1,56,56,24],"dim0":2,"dim1":3},{"shape":[1,56,24,56],"dim0":1,"dim1":2},{"shape":[1,96,56,56],"dim0":1,"dim1":2},{"shape":[1,56,96,56],"dim0":2,"dim1":3},{"shape":[1,56,56,24],"dim0":2,"dim1":3},{"shape":[1,56,24,56],"dim0":1,"dim1":2},{"shape":[1,98,28,28],"dim0":1,"dim1":2},{"shape":[1,28,98,28],"dim0":2,"dim1":3},{"shape":[1,28,28,20],"dim0":2,"dim1":3},{"shape":[1,28,20,28],"dim0":1,"dim1":2},{"shape":[1,98,28,28],"dim0":1,"dim1":2},{"shape":[1,28,98,28],"dim0":2,"dim1":3},{"shape":[1,28,28,20],"dim0":2,"dim1":3},{"shape":[1,28,20,28],"dim0":1,"dim1":2},{"shape":[10,10,16],"dim0":2,"dim1":1},{"shape":[10,16,10],"dim0":1,"dim1":0},{"shape":[10,10,16],"dim0":2,"dim1":1},{"shape":[10,16,10],"dim0":1,"dim1":0},{"shape":[10,10,8],"dim0":2,"dim1":1},{"shape":[10,8,10],"dim0":1,"dim1":0},{"shape":[10,10,8],"dim0":2,"dim1":1},{"shape":[10,8,10],"dim0":1,"dim1":0},{"shape":[15,15,6],"dim0":2,"dim1":1},{"shape":[15,6,15],"dim0":1,"dim1":0},{"shape":[15,15,6],"dim0":2,"dim1":1},{"shape":[15,6,15],"dim0":1,"dim1":0},{"shape":[18176,4544],"dim0":1,"dim1":0},{"shape":[18176,4544],"dim0":1,"dim1":0},{"shape":[197,197,12],"dim0":2,"dim1":1},{"shape":[197,12,197],"dim0":1,"dim1":0},{"shape":[197,197,12],"dim0":2,"dim1":1},{"shape":[197,12,197],"dim0":1,"dim1":0},{"shape":[197,197,16],"dim0":2,"dim1":1},{"shape":[197,16,197],"dim0":1,"dim1":0},{"shape":[197,197,16],"dim0":2,"dim1":1},{"shape":[197,16,197],"dim0":1,"dim1":0},{"shape":[1,1024,160],"dim0":2,"dim1":1},{"shape":[1,1024,160],"dim0":2,"dim1":1},{"shape":[1,1024,256],"dim0":2,"dim1":1},{"shape":[1,1024,256],"dim0":2,"dim1":1},{"shape":[1,1024,5,32],"dim0":2,"dim1":1},{"shape":[1,1024,5,32],"dim0":2,"dim1":1},{"shape":[1,10,12,64],"dim0":2,"dim1":1},{"shape":[1,10,12,64],"dim0":2,"dim1":1},{"shape":[1,1200,320],"dim0":2,"dim1":1},{"shape":[1,1200,320],"dim0":2,"dim1":1},{"shape":[1,1200,5,64],"dim0":2,"dim1":1},{"shape":[1,1200,5,64],"dim0":2,"dim1":1},{"shape":[1,120,160,64],"dim0":3,"dim1":2},{"shape":[1,120,64,160],"dim0":2,"dim1":1},{"shape":[1,120,160,64],"dim0":3,"dim1":2},{"shape":[1,120,64,160],"dim0":2,"dim1":1},{"shape":[1,1280,16,16],"dim0":2,"dim1":3},{"shape":[1,1280,16,16],"dim0":3,"dim1":1},{"shape":[1,1280,16,16],"dim0":2,"dim1":3},{"shape":[1,1280,16,16],"dim0":3,"dim1":1},{"shape":[1,1280,8,8],"dim0":2,"dim1":3},{"shape":[1,1280,8,8],"dim0":3,"dim1":1},{"shape":[1,1280,8,8],"dim0":2,"dim1":3},{"shape":[1,1280,8,8],"dim0":3,"dim1":1},{"shape":[1,128,128,32],"dim0":3,"dim1":2},{"shape":[1,128,32,128],"dim0":2,"dim1":1},{"shape":[1,128,128,32],"dim0":3,"dim1":2},{"shape":[1,128,32,128],"dim0":2,"dim1":1},{"shape":[1,128,300],"dim0":2,"dim1":1},{"shape":[1,128,300],"dim0":2,"dim1":1},{"shape":[1,12,12,64],"dim0":2,"dim1":1},{"shape":[1,12,12,64],"dim0":2,"dim1":1},{"shape":[1,12,197,64],"dim0":2,"dim1":1},{"shape":[1,12,197,64],"dim0":2,"dim1":1},{"shape":[1,12,201,64],"dim0":2,"dim1":1},{"shape":[1,12,201,64],"dim0":2,"dim1":1},{"shape":[1,12,27,27],"dim0":2,"dim1":3},{"shape":[1,12,27,27],"dim0":3,"dim1":1},{"shape":[1,12,27,27],"dim0":2,"dim1":3},{"shape":[1,12,27,27],"dim0":3,"dim1":1},{"shape":[1,12,64,8],"dim0":3,"dim1":2},{"shape":[1,12,64,8],"dim0":3,"dim1":2},{"shape":[1,12,8,64],"dim0":3,"dim1":2},{"shape":[1,12,8,64],"dim0":3,"dim1":2},{"shape":[1,1445,3,64],"dim0":2,"dim1":1},{"shape":[1,1445,3,64],"dim0":2,"dim1":1},{"shape":[1,14,12,64],"dim0":2,"dim1":1},{"shape":[1,14,12,64],"dim0":2,"dim1":1},{"shape":[1,15,20,512],"dim0":3,"dim1":2},{"shape":[1,15,512,20],"dim0":2,"dim1":1},{"shape":[1,15,20,512],"dim0":3,"dim1":2},{"shape":[1,15,512,20],"dim0":2,"dim1":1},{"shape":[1,160,256],"dim0":2,"dim1":1},{"shape":[1,160,256],"dim0":2,"dim1":1},{"shape":[1,16384,1,32],"dim0":2,"dim1":1},{"shape":[1,16384,1,32],"dim0":2,"dim1":1},{"shape":[1,16384,256],"dim0":2,"dim1":1},{"shape":[1,16384,256],"dim0":2,"dim1":1},{"shape":[1,16384,32],"dim0":2,"dim1":1},{"shape":[1,16384,32],"dim0":2,"dim1":1},{"shape":[1,16,16,1280],"dim0":3,"dim1":2},{"shape":[1,16,1280,16],"dim0":2,"dim1":1},{"shape":[1,16,16,1280],"dim0":3,"dim1":2},{"shape":[1,16,1280,16],"dim0":2,"dim1":1},{"shape":[1,16,16,256],"dim0":3,"dim1":2},{"shape":[1,16,256,16],"dim0":2,"dim1":1},{"shape":[1,16,16,256],"dim0":3,"dim1":2},{"shape":[1,16,256,16],"dim0":2,"dim1":1},{"shape":[1,16,197,64],"dim0":2,"dim1":1},{"shape":[1,16,197,64],"dim0":2,"dim1":1},{"shape":[1,16,27,27],"dim0":2,"dim1":3},{"shape":[1,16,27,27],"dim0":3,"dim1":1},{"shape":[1,16,27,27],"dim0":2,"dim1":3},{"shape":[1,16,27,27],"dim0":3,"dim1":1},{"shape":[1,16,32,96],"dim0":2,"dim1":1},{"shape":[1,16,32,96],"dim0":2,"dim1":1},{"shape":[1,16,5,64],"dim0":2,"dim1":1},{"shape":[1,16,5,64],"dim0":2,"dim1":1},{"shape":[1,19200,1,64],"dim0":2,"dim1":1},{"shape":[1,19200,1,64],"dim0":2,"dim1":1},{"shape":[1,19200,64],"dim0":2,"dim1":1},{"shape":[1,19200,64],"dim0":2,"dim1":1},{"shape":[1,197,12,64],"dim0":2,"dim1":1},{"shape":[1,197,12,64],"dim0":2,"dim1":1},{"shape":[1,197,16,64],"dim0":2,"dim1":1},{"shape":[1,197,16,64],"dim0":2,"dim1":1},{"shape":[1,1,12],"dim0":2,"dim1":1},{"shape":[1,12,1],"dim0":1,"dim1":0},{"shape":[1,1,12],"dim0":2,"dim1":1},{"shape":[1,12,1],"dim0":1,"dim1":0},{"shape":[1,1,16384,32],"dim0":2,"dim1":1},{"shape":[1,1,16384,32],"dim0":2,"dim1":1},{"shape":[1,1,16],"dim0":2,"dim1":1},{"shape":[1,16,1],"dim0":1,"dim1":0},{"shape":[1,1,16],"dim0":2,"dim1":1},{"shape":[1,16,1],"dim0":1,"dim1":0},{"shape":[1,1,19200,64],"dim0":2,"dim1":1},{"shape":[1,1,19200,64],"dim0":2,"dim1":1},{"shape":[1,1,6],"dim0":2,"dim1":1},{"shape":[1,6,1],"dim0":1,"dim1":0},{"shape":[1,1,6],"dim0":2,"dim1":1},{"shape":[1,6,1],"dim0":1,"dim1":0},{"shape":[1,1,8],"dim0":2,"dim1":1},{"shape":[1,8,1],"dim0":1,"dim1":0},{"shape":[1,1,8],"dim0":2,"dim1":1},{"shape":[1,8,1],"dim0":1,"dim1":0},{"shape":[1,201,12,64],"dim0":2,"dim1":1},{"shape":[1,201,12,64],"dim0":2,"dim1":1},{"shape":[1,2048,8,160],"dim0":2,"dim1":1},{"shape":[1,2048,8,160],"dim0":2,"dim1":1},{"shape":[1,2048,8,32],"dim0":2,"dim1":1},{"shape":[1,2048,8,32],"dim0":2,"dim1":1},{"shape":[1,23,40,256],"dim0":3,"dim1":2},{"shape":[1,23,256,40],"dim0":2,"dim1":1},{"shape":[1,23,40,256],"dim0":3,"dim1":2},{"shape":[1,23,256,40],"dim0":2,"dim1":1},{"shape":[1,256,16,64],"dim0":2,"dim1":1},{"shape":[1,256,16,64],"dim0":2,"dim1":1},{"shape":[1,256,1,32],"dim0":2,"dim1":1},{"shape":[1,256,1,32],"dim0":2,"dim1":1},{"shape":[1,256,256],"dim0":2,"dim1":1},{"shape":[1,256,256],"dim0":2,"dim1":1},{"shape":[1,256,2,32],"dim0":2,"dim1":1},{"shape":[1,256,2,32],"dim0":2,"dim1":1},{"shape":[1,256,512],"dim0":2,"dim1":1},{"shape":[1,256,512],"dim0":2,"dim1":1},{"shape":[1,256,5,32],"dim0":2,"dim1":1},{"shape":[1,256,5,32],"dim0":2,"dim1":1},{"shape":[1,256,8,160],"dim0":2,"dim1":1},{"shape":[1,256,8,160],"dim0":2,"dim1":1},{"shape":[1,256,8,32],"dim0":2,"dim1":1},{"shape":[1,256,8,32],"dim0":2,"dim1":1},{"shape":[1,256,8,96],"dim0":2,"dim1":1},{"shape":[1,256,8,96],"dim0":2,"dim1":1},{"shape":[1,256,920],"dim0":2,"dim1":1},{"shape":[1,920,256],"dim0":1,"dim1":0},{"shape":[1,256,920],"dim0":2,"dim1":1},{"shape":[1,920,256],"dim0":1,"dim1":0},{"shape":[1,25,12,64],"dim0":2,"dim1":1},{"shape":[1,25,12,64],"dim0":2,"dim1":1},{"shape":[1,27,27,12],"dim0":3,"dim1":2},{"shape":[1,27,12,27],"dim0":2,"dim1":1},{"shape":[1,27,27,12],"dim0":3,"dim1":2},{"shape":[1,27,12,27],"dim0":2,"dim1":1},{"shape":[1,27,27,16],"dim0":3,"dim1":2},{"shape":[1,27,16,27],"dim0":2,"dim1":1},{"shape":[1,27,27,16],"dim0":3,"dim1":2},{"shape":[1,27,16,27],"dim0":2,"dim1":1},{"shape":[1,2,4096,32],"dim0":2,"dim1":1},{"shape":[1,2,4096,32],"dim0":2,"dim1":1},{"shape":[1,2,4800,64],"dim0":2,"dim1":1},{"shape":[1,2,4800,64],"dim0":2,"dim1":1},{"shape":[1,300,1,64],"dim0":2,"dim1":1},{"shape":[1,300,1,64],"dim0":2,"dim1":1},{"shape":[1,300,2,64],"dim0":2,"dim1":1},{"shape":[1,300,2,64],"dim0":2,"dim1":1},{"shape":[1,300,5,64],"dim0":2,"dim1":1},{"shape":[1,300,5,64],"dim0":2,"dim1":1},{"shape":[1,300,8,64],"dim0":2,"dim1":1},{"shape":[1,300,8,64],"dim0":2,"dim1":1},{"shape":[1,30,40,320],"dim0":3,"dim1":2},{"shape":[1,30,320,40],"dim0":2,"dim1":1},{"shape":[1,30,40,320],"dim0":3,"dim1":2},{"shape":[1,30,320,40],"dim0":2,"dim1":1},{"shape":[1,320,300],"dim0":2,"dim1":1},{"shape":[1,320,300],"dim0":2,"dim1":1},{"shape":[1,320,64,64],"dim0":2,"dim1":3},{"shape":[1,320,64,64],"dim0":3,"dim1":1},{"shape":[1,320,64,64],"dim0":2,"dim1":3},{"shape":[1,320,64,64],"dim0":3,"dim1":1},{"shape":[1,32,256],"dim0":2,"dim1":1},{"shape":[1,32,256],"dim0":2,"dim1":1},{"shape":[1,32,32,160],"dim0":3,"dim1":2},{"shape":[1,32,160,32],"dim0":2,"dim1":1},{"shape":[1,32,32,160],"dim0":3,"dim1":2},{"shape":[1,32,160,32],"dim0":2,"dim1":1},{"shape":[1,32,32,640],"dim0":3,"dim1":2},{"shape":[1,32,640,32],"dim0":2,"dim1":1},{"shape":[1,32,32,640],"dim0":3,"dim1":2},{"shape":[1,32,640,32],"dim0":2,"dim1":1},{"shape":[1,3,1445,64],"dim0":2,"dim1":1},{"shape":[1,3,1445,64],"dim0":2,"dim1":1},{"shape":[1,3,16,16,16,16],"dim0":2,"dim1":4},{"shape":[1,3,16,16,16,16],"dim0":4,"dim1":5},{"shape":[1,3,16,16,16,16],"dim0":5,"dim1":1},{"shape":[1,3,16,16,16,16],"dim0":2,"dim1":4},{"shape":[1,3,16,16,16,16],"dim0":4,"dim1":5},{"shape":[1,3,16,16,16,16],"dim0":5,"dim1":1},{"shape":[1,4096,256],"dim0":2,"dim1":1},{"shape":[1,4096,256],"dim0":2,"dim1":1},{"shape":[1,4096,2,32],"dim0":2,"dim1":1},{"shape":[1,4096,2,32],"dim0":2,"dim1":1},{"shape":[1,4096,64],"dim0":2,"dim1":1},{"shape":[1,4096,64],"dim0":2,"dim1":1},{"shape":[1,4800,128],"dim0":2,"dim1":1},{"shape":[1,4800,128],"dim0":2,"dim1":1},{"shape":[1,4800,2,64],"dim0":2,"dim1":1},{"shape":[1,4800,2,64],"dim0":2,"dim1":1},{"shape":[1,5,1024,32],"dim0":2,"dim1":1},{"shape":[1,5,1024,32],"dim0":2,"dim1":1},{"shape":[1,5,1200,64],"dim0":2,"dim1":1},{"shape":[1,5,1200,64],"dim0":2,"dim1":1},{"shape":[1,5,16,64],"dim0":2,"dim1":1},{"shape":[1,5,16,64],"dim0":2,"dim1":1},{"shape":[1,60,80,128],"dim0":3,"dim1":2},{"shape":[1,60,128,80],"dim0":2,"dim1":1},{"shape":[1,60,80,128],"dim0":3,"dim1":2},{"shape":[1,60,128,80],"dim0":2,"dim1":1},{"shape":[1,640,32,32],"dim0":2,"dim1":3},{"shape":[1,640,32,32],"dim0":3,"dim1":1},{"shape":[1,640,32,32],"dim0":2,"dim1":3},{"shape":[1,640,32,32],"dim0":3,"dim1":1},{"shape":[1,64,256],"dim0":2,"dim1":1},{"shape":[1,64,256],"dim0":2,"dim1":1},{"shape":[1,64,300],"dim0":2,"dim1":1},{"shape":[1,64,300],"dim0":2,"dim1":1},{"shape":[1,64,64,320],"dim0":3,"dim1":2},{"shape":[1,64,320,64],"dim0":2,"dim1":1},{"shape":[1,64,64,320],"dim0":3,"dim1":2},{"shape":[1,64,320,64],"dim0":2,"dim1":1},{"shape":[1,64,64,64],"dim0":3,"dim1":2},{"shape":[1,64,64,64],"dim0":2,"dim1":1},{"shape":[1,64,64,64],"dim0":3,"dim1":2},{"shape":[1,64,64,64],"dim0":2,"dim1":1},{"shape":[1,6,4,10,10],"dim0":3,"dim1":1},{"shape":[1,10,4,6,10],"dim0":4,"dim1":2},{"shape":[1,6,4,10,10],"dim0":3,"dim1":1},{"shape":[1,10,4,6,10],"dim0":4,"dim1":2},{"shape":[1,6,4,1,1],"dim0":3,"dim1":1},{"shape":[1,1,4,6,1],"dim0":4,"dim1":2},{"shape":[1,6,4,1,1],"dim0":3,"dim1":1},{"shape":[1,1,4,6,1],"dim0":4,"dim1":2},{"shape":[1,6,4,20,20],"dim0":3,"dim1":1},{"shape":[1,20,4,6,20],"dim0":4,"dim1":2},{"shape":[1,6,4,20,20],"dim0":3,"dim1":1},{"shape":[1,20,4,6,20],"dim0":4,"dim1":2},{"shape":[1,6,4,2,2],"dim0":3,"dim1":1},{"shape":[1,2,4,6,2],"dim0":4,"dim1":2},{"shape":[1,6,4,2,2],"dim0":3,"dim1":1},{"shape":[1,2,4,6,2],"dim0":4,"dim1":2},{"shape":[1,6,4,3,3],"dim0":3,"dim1":1},{"shape":[1,3,4,6,3],"dim0":4,"dim1":2},{"shape":[1,6,4,3,3],"dim0":3,"dim1":1},{"shape":[1,3,4,6,3],"dim0":4,"dim1":2},{"shape":[1,6,4,5,5],"dim0":3,"dim1":1},{"shape":[1,5,4,6,5],"dim0":4,"dim1":2},{"shape":[1,6,4,5,5],"dim0":3,"dim1":1},{"shape":[1,5,4,6,5],"dim0":4,"dim1":2},{"shape":[1,6,91,10,10],"dim0":3,"dim1":1},{"shape":[1,10,91,6,10],"dim0":4,"dim1":2},{"shape":[1,6,91,10,10],"dim0":3,"dim1":1},{"shape":[1,10,91,6,10],"dim0":4,"dim1":2},{"shape":[1,6,91,1,1],"dim0":3,"dim1":1},{"shape":[1,1,91,6,1],"dim0":4,"dim1":2},{"shape":[1,6,91,1,1],"dim0":3,"dim1":1},{"shape":[1,1,91,6,1],"dim0":4,"dim1":2},{"shape":[1,6,91,20,20],"dim0":3,"dim1":1},{"shape":[1,20,91,6,20],"dim0":4,"dim1":2},{"shape":[1,6,91,20,20],"dim0":3,"dim1":1},{"shape":[1,20,91,6,20],"dim0":4,"dim1":2},{"shape":[1,6,91,2,2],"dim0":3,"dim1":1},{"shape":[1,2,91,6,2],"dim0":4,"dim1":2},{"shape":[1,6,91,2,2],"dim0":3,"dim1":1},{"shape":[1,2,91,6,2],"dim0":4,"dim1":2},{"shape":[1,6,91,3,3],"dim0":3,"dim1":1},{"shape":[1,3,91,6,3],"dim0":4,"dim1":2},{"shape":[1,6,91,3,3],"dim0":3,"dim1":1},{"shape":[1,3,91,6,3],"dim0":4,"dim1":2},{"shape":[1,6,91,5,5],"dim0":3,"dim1":1},{"shape":[1,5,91,6,5],"dim0":4,"dim1":2},{"shape":[1,6,91,5,5],"dim0":3,"dim1":1},{"shape":[1,5,91,6,5],"dim0":4,"dim1":2},{"shape":[1,71,7,64],"dim0":2,"dim1":1},{"shape":[1,71,7,64],"dim0":2,"dim1":1},{"shape":[1,768,8],"dim0":2,"dim1":1},{"shape":[1,768,8],"dim0":2,"dim1":1},{"shape":[1,7,12,64],"dim0":2,"dim1":1},{"shape":[1,7,12,64],"dim0":2,"dim1":1},{"shape":[1,8,2048,96],"dim0":2,"dim1":1},{"shape":[1,8,2048,96],"dim0":2,"dim1":1},{"shape":[1,8,256,160],"dim0":2,"dim1":1},{"shape":[1,8,256,160],"dim0":2,"dim1":1},{"shape":[1,8,256,32],"dim0":2,"dim1":1},{"shape":[1,8,256,32],"dim0":2,"dim1":1},{"shape":[1,8,300,64],"dim0":2,"dim1":1},{"shape":[1,8,300,64],"dim0":2,"dim1":1},{"shape":[1,8,768],"dim0":2,"dim1":1},{"shape":[1,8,768],"dim0":2,"dim1":1},{"shape":[1,8,8,1280],"dim0":3,"dim1":2},{"shape":[1,8,1280,8],"dim0":2,"dim1":1},{"shape":[1,8,8,1280],"dim0":3,"dim1":2},{"shape":[1,8,1280,8],"dim0":2,"dim1":1},{"shape":[1,9,12,64],"dim0":2,"dim1":1},{"shape":[1,9,12,64],"dim0":2,"dim1":1},{"shape":[1,9,16,128],"dim0":2,"dim1":1},{"shape":[1,9,16,128],"dim0":2,"dim1":1},{"shape":[1,9,16,64],"dim0":2,"dim1":1},{"shape":[1,9,16,64],"dim0":2,"dim1":1},{"shape":[1,9,64,64],"dim0":2,"dim1":1},{"shape":[1,9,64,64],"dim0":2,"dim1":1},{"shape":[2,196,196],"dim0":1,"dim1":2},{"shape":[2,196,196],"dim0":2,"dim1":0},{"shape":[2,196,196],"dim0":1,"dim1":2},{"shape":[2,196,196],"dim0":2,"dim1":0},{"shape":[4544,18176],"dim0":1,"dim1":0},{"shape":[4544,18176],"dim0":1,"dim1":0},{"shape":[4544,4544],"dim0":1,"dim1":0},{"shape":[4544,4544],"dim0":1,"dim1":0},{"shape":[4672,4544],"dim0":1,"dim1":0},{"shape":[4672,4544],"dim0":1,"dim1":0},{"shape":[1000,1280],"dim0":1,"dim1":0},{"shape":[1000,1280],"dim0":1,"dim1":0},{"shape":[1000,2048],"dim0":1,"dim1":0},{"shape":[1000,2048],"dim0":1,"dim1":0},{"shape":[1000,512],"dim0":1,"dim1":0},{"shape":[1000,512],"dim0":1,"dim1":0},{"shape":[1000,768],"dim0":1,"dim1":0},{"shape":[1000,768],"dim0":1,"dim1":0},{"shape":[100,8,32],"dim0":1,"dim1":0},{"shape":[100,8,32],"dim0":1,"dim1":0},{"shape":[10240,1280],"dim0":1,"dim1":0},{"shape":[10240,1280],"dim0":1,"dim1":0},{"shape":[1024,1024],"dim0":1,"dim1":0},{"shape":[1024,1024],"dim0":1,"dim1":0},{"shape":[1024,128],"dim0":1,"dim1":0},{"shape":[1024,128],"dim0":1,"dim1":0},{"shape":[1024,256],"dim0":1,"dim1":0},{"shape":[1024,256],"dim0":1,"dim1":0},{"shape":[1024,4096],"dim0":1,"dim1":0},{"shape":[1024,4096],"dim0":1,"dim1":0},{"shape":[1024,512],"dim0":1,"dim1":0},{"shape":[1024,512],"dim0":1,"dim1":0},{"shape":[10,128],"dim0":1,"dim1":0},{"shape":[10,128],"dim0":1,"dim1":0},{"shape":[11008,4096],"dim0":1,"dim1":0},{"shape":[11008,4096],"dim0":1,"dim1":0},{"shape":[1280,1280],"dim0":1,"dim1":0},{"shape":[1280,1280],"dim0":1,"dim1":0},{"shape":[1280,320],"dim0":1,"dim1":0},{"shape":[1280,320],"dim0":1,"dim1":0},{"shape":[1280,5120],"dim0":1,"dim1":0},{"shape":[1280,5120],"dim0":1,"dim1":0},{"shape":[1280,768],"dim0":1,"dim1":0},{"shape":[1280,768],"dim0":1,"dim1":0},{"shape":[128,1024],"dim0":1,"dim1":0},{"shape":[128,1024],"dim0":1,"dim1":0},{"shape":[128,128],"dim0":1,"dim1":0},{"shape":[128,128],"dim0":1,"dim1":0},{"shape":[128,2048],"dim0":1,"dim1":0},{"shape":[128,2048],"dim0":1,"dim1":0},{"shape":[128,32],"dim0":1,"dim1":0},{"shape":[128,32],"dim0":1,"dim1":0},{"shape":[128,4096],"dim0":1,"dim1":0},{"shape":[128,4096],"dim0":1,"dim1":0},{"shape":[128,512],"dim0":1,"dim1":0},{"shape":[128,512],"dim0":1,"dim1":0},{"shape":[128,64],"dim0":1,"dim1":0},{"shape":[128,64],"dim0":1,"dim1":0},{"shape":[128,768],"dim0":1,"dim1":0},{"shape":[128,768],"dim0":1,"dim1":0},{"shape":[128,784],"dim0":1,"dim1":0},{"shape":[128,784],"dim0":1,"dim1":0},{"shape":[128,9216],"dim0":1,"dim1":0},{"shape":[128,9216],"dim0":1,"dim1":0},{"shape":[12,3],"dim0":1,"dim1":0},{"shape":[12,3],"dim0":1,"dim1":0},{"shape":[12,64],"dim0":1,"dim1":0},{"shape":[12,64],"dim0":1,"dim1":0},{"shape":[1536,1536],"dim0":1,"dim1":0},{"shape":[1536,1536],"dim0":1,"dim1":0},{"shape":[1536,6144],"dim0":1,"dim1":0},{"shape":[1536,6144],"dim0":1,"dim1":0},{"shape":[1536,768],"dim0":1,"dim1":0},{"shape":[1536,768],"dim0":1,"dim1":0},{"shape":[160,160],"dim0":1,"dim1":0},{"shape":[160,160],"dim0":1,"dim1":0},{"shape":[160,640],"dim0":1,"dim1":0},{"shape":[160,640],"dim0":1,"dim1":0},{"shape":[16384,4096],"dim0":1,"dim1":0},{"shape":[16384,4096],"dim0":1,"dim1":0},{"shape":[16,19,64],"dim0":2,"dim1":1},{"shape":[16,19,64],"dim0":2,"dim1":1},{"shape":[16,32,96],"dim0":2,"dim1":1},{"shape":[16,32,96],"dim0":2,"dim1":1},{"shape":[192,192],"dim0":1,"dim1":0},{"shape":[192,192],"dim0":1,"dim1":0},{"shape":[192,768],"dim0":1,"dim1":0},{"shape":[192,768],"dim0":1,"dim1":0},{"shape":[1,1024,196],"dim0":2,"dim1":1},{"shape":[1,1024,196],"dim0":2,"dim1":1},{"shape":[1,1024,256],"dim0":2,"dim1":1},{"shape":[1,1024,640],"dim0":2,"dim1":1},{"shape":[1,1024,640],"dim0":2,"dim1":1},{"shape":[1,1024,8,80],"dim0":2,"dim1":1},{"shape":[1,1024,8,80],"dim0":2,"dim1":1},{"shape":[1,10,12,64],"dim0":2,"dim1":1},{"shape":[1,10,16,64],"dim0":2,"dim1":1},{"shape":[1,10,16,64],"dim0":2,"dim1":1},{"shape":[1,10,8,64],"dim0":2,"dim1":1},{"shape":[1,10,8,64],"dim0":2,"dim1":1},{"shape":[1,1200,1280],"dim0":2,"dim1":1},{"shape":[1,1200,1280],"dim0":2,"dim1":1},{"shape":[1,1280,1200],"dim0":2,"dim1":1},{"shape":[1,1280,1200],"dim0":2,"dim1":1},{"shape":[1,128,16384],"dim0":2,"dim1":1},{"shape":[1,128,16384],"dim0":2,"dim1":1},{"shape":[1,128,4800],"dim0":2,"dim1":1},{"shape":[1,128,4800],"dim0":2,"dim1":1},{"shape":[1,12,10,64],"dim0":3,"dim1":2},{"shape":[1,12,10,64],"dim0":3,"dim1":2},{"shape":[1,12,10,64],"dim0":2,"dim1":1},{"shape":[1,12,10,64],"dim0":2,"dim1":1},{"shape":[1,12,10,64],"dim0":3,"dim1":2},{"shape":[1,12,10,64],"dim0":3,"dim1":2},{"shape":[1,12,12,64],"dim0":3,"dim1":2},{"shape":[1,12,12,64],"dim0":3,"dim1":2},{"shape":[1,12,12,64],"dim0":2,"dim1":1},{"shape":[1,12,14,64],"dim0":3,"dim1":2},{"shape":[1,12,14,64],"dim0":3,"dim1":2},{"shape":[1,12,14,64],"dim0":2,"dim1":1},{"shape":[1,12,14,64],"dim0":2,"dim1":1},{"shape":[1,12,16,64],"dim0":3,"dim1":2},{"shape":[1,12,16,64],"dim0":3,"dim1":2},{"shape":[1,12,16,64],"dim0":2,"dim1":1},{"shape":[1,12,16,64],"dim0":2,"dim1":1},{"shape":[1,12,197,64],"dim0":3,"dim1":2},{"shape":[1,12,197,64],"dim0":3,"dim1":2},{"shape":[1,12,197,64],"dim0":3,"dim1":2},{"shape":[1,12,197,64],"dim0":3,"dim1":2},{"shape":[1,12,197,64],"dim0":2,"dim1":1},{"shape":[1,12,197,64],"dim0":2,"dim1":1},{"shape":[1,12,1,64],"dim0":2,"dim1":1},{"shape":[1,12,1,64],"dim0":2,"dim1":1},{"shape":[1,12,1,64],"dim0":3,"dim1":2},{"shape":[1,12,1,64],"dim0":3,"dim1":2},{"shape":[1,12,201,64],"dim0":3,"dim1":2},{"shape":[1,12,201,64],"dim0":3,"dim1":2},{"shape":[1,12,25,64],"dim0":3,"dim1":2},{"shape":[1,12,25,64],"dim0":3,"dim1":2},{"shape":[1,12,25,64],"dim0":2,"dim1":1},{"shape":[1,12,25,64],"dim0":2,"dim1":1},{"shape":[1,12,50,64],"dim0":3,"dim1":2},{"shape":[1,12,50,64],"dim0":3,"dim1":2},{"shape":[1,12,50,64],"dim0":2,"dim1":1},{"shape":[1,12,50,64],"dim0":2,"dim1":1},{"shape":[1,12,7,64],"dim0":3,"dim1":2},{"shape":[1,12,7,64],"dim0":3,"dim1":2},{"shape":[1,12,7,64],"dim0":2,"dim1":1},{"shape":[1,12,7,64],"dim0":2,"dim1":1},{"shape":[1,12,9,64],"dim0":3,"dim1":2},{"shape":[1,12,9,64],"dim0":3,"dim1":2},{"shape":[1,12,9,64],"dim0":2,"dim1":1},{"shape":[1,12,9,64],"dim0":2,"dim1":1},{"shape":[1,1445,3,64],"dim0":2,"dim1":1},{"shape":[1,144,768],"dim0":2,"dim1":1},{"shape":[1,144,768],"dim0":2,"dim1":1},{"shape":[1,14,12,64],"dim0":2,"dim1":1},{"shape":[1,15,6,64],"dim0":2,"dim1":1},{"shape":[1,15,6,64],"dim0":2,"dim1":1},{"shape":[1,160,1024],"dim0":2,"dim1":1},{"shape":[1,160,1024],"dim0":2,"dim1":1},{"shape":[1,16384,128],"dim0":2,"dim1":1},{"shape":[1,16384,128],"dim0":2,"dim1":1},{"shape":[1,16,10,64],"dim0":2,"dim1":1},{"shape":[1,16,10,64],"dim0":2,"dim1":1},{"shape":[1,16,10,64],"dim0":3,"dim1":2},{"shape":[1,16,10,64],"dim0":3,"dim1":2},{"shape":[1,16,12,64],"dim0":2,"dim1":1},{"shape":[1,16,12,64],"dim0":2,"dim1":1},{"shape":[1,16,197,64],"dim0":3,"dim1":2},{"shape":[1,16,197,64],"dim0":3,"dim1":2},{"shape":[1,16,19,64],"dim0":2,"dim1":1},{"shape":[1,16,19,64],"dim0":2,"dim1":1},{"shape":[1,16,1,64],"dim0":2,"dim1":1},{"shape":[1,16,1,64],"dim0":2,"dim1":1},{"shape":[1,16,1,64],"dim0":3,"dim1":2},{"shape":[1,16,1,64],"dim0":3,"dim1":2},{"shape":[1,16,256,64],"dim0":3,"dim1":2},{"shape":[1,16,256,64],"dim0":3,"dim1":2},{"shape":[1,16,256,64],"dim0":2,"dim1":1},{"shape":[1,16,256,64],"dim0":2,"dim1":1},{"shape":[1,16,5,64],"dim0":3,"dim1":2},{"shape":[1,16,5,64],"dim0":3,"dim1":2},{"shape":[1,16,6,64],"dim0":3,"dim1":2},{"shape":[1,16,6,64],"dim0":3,"dim1":2},{"shape":[1,16,6,64],"dim0":2,"dim1":1},{"shape":[1,16,6,64],"dim0":2,"dim1":1},{"shape":[1,16,9,128],"dim0":3,"dim1":2},{"shape":[1,16,9,128],"dim0":3,"dim1":2},{"shape":[1,16,9,128],"dim0":2,"dim1":1},{"shape":[1,16,9,128],"dim0":2,"dim1":1},{"shape":[1,16,9,64],"dim0":3,"dim1":2},{"shape":[1,16,9,64],"dim0":3,"dim1":2},{"shape":[1,16,9,64],"dim0":2,"dim1":1},{"shape":[1,16,9,64],"dim0":2,"dim1":1},{"shape":[1,19200,256],"dim0":2,"dim1":1},{"shape":[1,19200,256],"dim0":2,"dim1":1},{"shape":[1,192,1344],"dim0":2,"dim1":1},{"shape":[1,192,1344],"dim0":2,"dim1":1},{"shape":[1,197,12,64],"dim0":2,"dim1":1},{"shape":[1,19,16,64],"dim0":2,"dim1":1},{"shape":[1,19,16,64],"dim0":2,"dim1":1},{"shape":[1,1,12,64],"dim0":2,"dim1":1},{"shape":[1,1,12,64],"dim0":2,"dim1":1},{"shape":[1,1,16,64],"dim0":2,"dim1":1},{"shape":[1,1,16,64],"dim0":2,"dim1":1},{"shape":[1,1,256,32],"dim0":3,"dim1":2},{"shape":[1,1,256,32],"dim0":3,"dim1":2},{"shape":[1,1,300,64],"dim0":3,"dim1":2},{"shape":[1,1,300,64],"dim0":3,"dim1":2},{"shape":[1,1,6,64],"dim0":2,"dim1":1},{"shape":[1,1,6,64],"dim0":2,"dim1":1},{"shape":[1,1,7,64],"dim0":3,"dim1":2},{"shape":[1,1,7,64],"dim0":3,"dim1":2},{"shape":[1,1,8,64],"dim0":2,"dim1":1},{"shape":[1,1,8,64],"dim0":2,"dim1":1},{"shape":[1,2048,300],"dim0":2,"dim1":1},{"shape":[1,2048,300],"dim0":2,"dim1":1},{"shape":[1,256,1024],"dim0":2,"dim1":1},{"shape":[1,256,1024],"dim0":2,"dim1":1},{"shape":[1,256,16,64],"dim0":2,"dim1":1},{"shape":[1,256,19200],"dim0":2,"dim1":1},{"shape":[1,256,19200],"dim0":2,"dim1":1},{"shape":[1,256,256],"dim0":2,"dim1":1},{"shape":[1,256,4096],"dim0":2,"dim1":1},{"shape":[1,256,4096],"dim0":2,"dim1":1},{"shape":[1,256,8,160],"dim0":2,"dim1":1},{"shape":[1,25,12,64],"dim0":2,"dim1":1},{"shape":[1,2,256,32],"dim0":3,"dim1":2},{"shape":[1,2,256,32],"dim0":3,"dim1":2},{"shape":[1,2,300,64],"dim0":3,"dim1":2},{"shape":[1,2,300,64],"dim0":3,"dim1":2},{"shape":[1,300,2048],"dim0":2,"dim1":1},{"shape":[1,300,2048],"dim0":2,"dim1":1},{"shape":[1,320,1200],"dim0":2,"dim1":1},{"shape":[1,320,1200],"dim0":2,"dim1":1},{"shape":[1,32,16384],"dim0":2,"dim1":1},{"shape":[1,32,16384],"dim0":2,"dim1":1},{"shape":[1,32,16,96],"dim0":2,"dim1":1},{"shape":[1,32,16,96],"dim0":2,"dim1":1},{"shape":[1,32,32,128],"dim0":3,"dim1":2},{"shape":[1,32,32,128],"dim0":3,"dim1":2},{"shape":[1,32,32,128],"dim0":2,"dim1":1},{"shape":[1,32,32,128],"dim0":2,"dim1":1},{"shape":[1,32,7],"dim0":2,"dim1":1},{"shape":[1,32,7],"dim0":2,"dim1":1},{"shape":[1,3,1445,64],"dim0":3,"dim1":2},{"shape":[1,3,1445,64],"dim0":3,"dim1":2},{"shape":[1,3,1445,64],"dim0":2,"dim1":1},{"shape":[1,3,1445,64],"dim0":2,"dim1":1},{"shape":[1,4096,256],"dim0":2,"dim1":1},{"shape":[1,4096,8,40],"dim0":2,"dim1":1},{"shape":[1,4096,8,40],"dim0":2,"dim1":1},{"shape":[1,4150,192],"dim0":2,"dim1":1},{"shape":[1,4150,192],"dim0":2,"dim1":1},{"shape":[1,4800,512],"dim0":2,"dim1":1},{"shape":[1,4800,512],"dim0":2,"dim1":1},{"shape":[1,50,12,64],"dim0":2,"dim1":1},{"shape":[1,50,12,64],"dim0":2,"dim1":1},{"shape":[1,512],"dim0":1,"dim1":0},{"shape":[1,512],"dim0":1,"dim1":0},{"shape":[1,512,300],"dim0":2,"dim1":1},{"shape":[1,512,300],"dim0":2,"dim1":1},{"shape":[1,512,4800],"dim0":2,"dim1":1},{"shape":[1,512,4800],"dim0":2,"dim1":1},{"shape":[1,5,256,32],"dim0":3,"dim1":2},{"shape":[1,5,256,32],"dim0":3,"dim1":2},{"shape":[1,5,300,64],"dim0":3,"dim1":2},{"shape":[1,5,300,64],"dim0":3,"dim1":2},{"shape":[1,640,1024],"dim0":2,"dim1":1},{"shape":[1,640,1024],"dim0":2,"dim1":1},{"shape":[1,64,19200],"dim0":2,"dim1":1},{"shape":[1,64,19200],"dim0":2,"dim1":1},{"shape":[1,64,32],"dim0":2,"dim1":1},{"shape":[1,64,32],"dim0":2,"dim1":1},{"shape":[1,64,4096],"dim0":2,"dim1":1},{"shape":[1,64,4096],"dim0":2,"dim1":1},{"shape":[1,64,8,160],"dim0":2,"dim1":1},{"shape":[1,64,8,160],"dim0":2,"dim1":1},{"shape":[1,64,9,64],"dim0":3,"dim1":2},{"shape":[1,64,9,64],"dim0":3,"dim1":2},{"shape":[1,64,9,64],"dim0":2,"dim1":1},{"shape":[1,64,9,64],"dim0":2,"dim1":1},{"shape":[1,6,15,64],"dim0":2,"dim1":1},{"shape":[1,6,15,64],"dim0":2,"dim1":1},{"shape":[1,6,15,64],"dim0":3,"dim1":2},{"shape":[1,6,15,64],"dim0":3,"dim1":2},{"shape":[1,6,16,64],"dim0":2,"dim1":1},{"shape":[1,6,16,64],"dim0":2,"dim1":1},{"shape":[1,6,1,64],"dim0":2,"dim1":1},{"shape":[1,6,1,64],"dim0":2,"dim1":1},{"shape":[1,6,1,64],"dim0":3,"dim1":2},{"shape":[1,6,1,64],"dim0":3,"dim1":2},{"shape":[1,768],"dim0":1,"dim1":0},{"shape":[1,768],"dim0":1,"dim1":0},{"shape":[1,768,192],"dim0":2,"dim1":1},{"shape":[1,768,192],"dim0":2,"dim1":1},{"shape":[1,768,196],"dim0":2,"dim1":1},{"shape":[1,768,196],"dim0":2,"dim1":1},{"shape":[1,768,49],"dim0":2,"dim1":1},{"shape":[1,768,49],"dim0":2,"dim1":1},{"shape":[1,7,12,64],"dim0":2,"dim1":1},{"shape":[1,7,12,64],"dim0":2,"dim1":1},{"shape":[1,7,1,64],"dim0":2,"dim1":1},{"shape":[1,7,1,64],"dim0":2,"dim1":1},{"shape":[1,7,71,64],"dim0":2,"dim1":1},{"shape":[1,7,71,64],"dim0":2,"dim1":1},{"shape":[1,8,1024,80],"dim0":3,"dim1":2},{"shape":[1,8,1024,80],"dim0":3,"dim1":2},{"shape":[1,8,1024,80],"dim0":2,"dim1":1},{"shape":[1,8,1024,80],"dim0":2,"dim1":1},{"shape":[1,8,10,64],"dim0":2,"dim1":1},{"shape":[1,8,10,64],"dim0":2,"dim1":1},{"shape":[1,8,10,64],"dim0":3,"dim1":2},{"shape":[1,8,10,64],"dim0":3,"dim1":2},{"shape":[1,8,1,64],"dim0":2,"dim1":1},{"shape":[1,8,1,64],"dim0":2,"dim1":1},{"shape":[1,8,1,64],"dim0":3,"dim1":2},{"shape":[1,8,1,64],"dim0":3,"dim1":2},{"shape":[1,8,2048,32],"dim0":3,"dim1":2},{"shape":[1,8,2048,32],"dim0":3,"dim1":2},{"shape":[1,8,256,160],"dim0":3,"dim1":2},{"shape":[1,8,256,160],"dim0":3,"dim1":2},{"shape":[1,8,256,160],"dim0":2,"dim1":1},{"shape":[1,8,256,32],"dim0":3,"dim1":2},{"shape":[1,8,256,32],"dim0":3,"dim1":2},{"shape":[1,8,300,64],"dim0":3,"dim1":2},{"shape":[1,8,300,64],"dim0":3,"dim1":2},{"shape":[1,8,4096,40],"dim0":3,"dim1":2},{"shape":[1,8,4096,40],"dim0":3,"dim1":2},{"shape":[1,8,4096,40],"dim0":2,"dim1":1},{"shape":[1,8,4096,40],"dim0":2,"dim1":1},{"shape":[1,8,64,160],"dim0":3,"dim1":2},{"shape":[1,8,64,160],"dim0":3,"dim1":2},{"shape":[1,8,64,160],"dim0":2,"dim1":1},{"shape":[1,8,64,160],"dim0":2,"dim1":1},{"shape":[1,8,9,160],"dim0":3,"dim1":2},{"shape":[1,8,9,160],"dim0":3,"dim1":2},{"shape":[1,8,9,40],"dim0":3,"dim1":2},{"shape":[1,8,9,40],"dim0":3,"dim1":2},{"shape":[1,8,9,80],"dim0":3,"dim1":2},{"shape":[1,8,9,80],"dim0":3,"dim1":2},{"shape":[1,9,12,64],"dim0":2,"dim1":1},{"shape":[1,9,16,128],"dim0":2,"dim1":1},{"shape":[1,9,16,64],"dim0":2,"dim1":1},{"shape":[1,9,64,64],"dim0":2,"dim1":1},{"shape":[1,9,8,160],"dim0":2,"dim1":1},{"shape":[1,9,8,160],"dim0":2,"dim1":1},{"shape":[1,9,8,40],"dim0":2,"dim1":1},{"shape":[1,9,8,40],"dim0":2,"dim1":1},{"shape":[1,9,8,80],"dim0":2,"dim1":1},{"shape":[1,9,8,80],"dim0":2,"dim1":1},{"shape":[2048,128],"dim0":1,"dim1":0},{"shape":[2048,128],"dim0":1,"dim1":0},{"shape":[2048,2048],"dim0":1,"dim1":0},{"shape":[2048,2048],"dim0":1,"dim1":0},{"shape":[2048,256],"dim0":1,"dim1":0},{"shape":[2048,256],"dim0":1,"dim1":0},{"shape":[2048,512],"dim0":1,"dim1":0},{"shape":[2048,512],"dim0":1,"dim1":0},{"shape":[2048,8192],"dim0":1,"dim1":0},{"shape":[2048,8192],"dim0":1,"dim1":0},{"shape":[250002,768],"dim0":1,"dim1":0},{"shape":[250002,768],"dim0":1,"dim1":0},{"shape":[250880,1536],"dim0":1,"dim1":0},{"shape":[250880,1536],"dim0":1,"dim1":0},{"shape":[256008,1024],"dim0":1,"dim1":0},{"shape":[256008,1024],"dim0":1,"dim1":0},{"shape":[2560,320],"dim0":1,"dim1":0},{"shape":[2560,320],"dim0":1,"dim1":0},{"shape":[256,1024],"dim0":1,"dim1":0},{"shape":[256,1024],"dim0":1,"dim1":0},{"shape":[256,1280],"dim0":1,"dim1":0},{"shape":[256,1280],"dim0":1,"dim1":0},{"shape":[256,160],"dim0":1,"dim1":0},{"shape":[256,160],"dim0":1,"dim1":0},{"shape":[256,2048],"dim0":1,"dim1":0},{"shape":[256,2048],"dim0":1,"dim1":0},{"shape":[256,256],"dim0":1,"dim1":0},{"shape":[256,256],"dim0":1,"dim1":0},{"shape":[256,32],"dim0":1,"dim1":0},{"shape":[256,32],"dim0":1,"dim1":0},{"shape":[256,512],"dim0":1,"dim1":0},{"shape":[256,512],"dim0":1,"dim1":0},{"shape":[256,64],"dim0":1,"dim1":0},{"shape":[256,64],"dim0":1,"dim1":0},{"shape":[256,768],"dim0":1,"dim1":0},{"shape":[256,768],"dim0":1,"dim1":0},{"shape":[262,768],"dim0":1,"dim1":0},{"shape":[262,768],"dim0":1,"dim1":0},{"shape":[2,1024],"dim0":1,"dim1":0},{"shape":[2,1024],"dim0":1,"dim1":0},{"shape":[2,1],"dim0":1,"dim1":0},{"shape":[2,1],"dim0":1,"dim1":0},{"shape":[2,768],"dim0":1,"dim1":0},{"shape":[2,768],"dim0":1,"dim1":0},{"shape":[2,7,8,64],"dim0":2,"dim1":1},{"shape":[2,7,8,64],"dim0":2,"dim1":1},{"shape":[2,8,7,64],"dim0":3,"dim1":2},{"shape":[2,8,7,64],"dim0":3,"dim1":2},{"shape":[2,8,7,64],"dim0":2,"dim1":1},{"shape":[2,8,7,64],"dim0":2,"dim1":1},{"shape":[30000,128],"dim0":1,"dim1":0},{"shape":[30000,128],"dim0":1,"dim1":0},{"shape":[3072,1024],"dim0":1,"dim1":0},{"shape":[3072,1024],"dim0":1,"dim1":0},{"shape":[3072,768],"dim0":1,"dim1":0},{"shape":[3072,768],"dim0":1,"dim1":0},{"shape":[3129,1536],"dim0":1,"dim1":0},{"shape":[3129,1536],"dim0":1,"dim1":0},{"shape":[32000,4096],"dim0":1,"dim1":0},{"shape":[32000,4096],"dim0":1,"dim1":0},{"shape":[320,1280],"dim0":1,"dim1":0},{"shape":[320,1280],"dim0":1,"dim1":0},{"shape":[320,320],"dim0":1,"dim1":0},{"shape":[320,320],"dim0":1,"dim1":0},{"shape":[320,768],"dim0":1,"dim1":0},{"shape":[320,768],"dim0":1,"dim1":0},{"shape":[32128,1024],"dim0":1,"dim1":0},{"shape":[32128,1024],"dim0":1,"dim1":0},{"shape":[32128,512],"dim0":1,"dim1":0},{"shape":[32128,512],"dim0":1,"dim1":0},{"shape":[32128,768],"dim0":1,"dim1":0},{"shape":[32128,768],"dim0":1,"dim1":0},{"shape":[32,128],"dim0":1,"dim1":0},{"shape":[32,128],"dim0":1,"dim1":0},{"shape":[32,32],"dim0":1,"dim1":0},{"shape":[32,32],"dim0":1,"dim1":0},{"shape":[384,512],"dim0":1,"dim1":0},{"shape":[384,512],"dim0":1,"dim1":0},{"shape":[3,12],"dim0":1,"dim1":0},{"shape":[3,12],"dim0":1,"dim1":0},{"shape":[3,768],"dim0":1,"dim1":0},{"shape":[3,768],"dim0":1,"dim1":0},{"shape":[4096,1024],"dim0":1,"dim1":0},{"shape":[4096,1024],"dim0":1,"dim1":0},{"shape":[4096,11008],"dim0":1,"dim1":0},{"shape":[4096,11008],"dim0":1,"dim1":0},{"shape":[4096,128],"dim0":1,"dim1":0},{"shape":[4096,128],"dim0":1,"dim1":0},{"shape":[4096,16384],"dim0":1,"dim1":0},{"shape":[4096,16384],"dim0":1,"dim1":0},{"shape":[4096,4096],"dim0":1,"dim1":0},{"shape":[4096,4096],"dim0":1,"dim1":0},{"shape":[4608,1536],"dim0":1,"dim1":0},{"shape":[4608,1536],"dim0":1,"dim1":0},{"shape":[4,192],"dim0":1,"dim1":0},{"shape":[4,192],"dim0":1,"dim1":0},{"shape":[4,256],"dim0":1,"dim1":0},{"shape":[4,256],"dim0":1,"dim1":0},{"shape":[50272,512],"dim0":1,"dim1":0},{"shape":[50272,512],"dim0":1,"dim1":0},{"shape":[51200,1024],"dim0":1,"dim1":0},{"shape":[51200,1024],"dim0":1,"dim1":0},{"shape":[5120,640],"dim0":1,"dim1":0},{"shape":[5120,640],"dim0":1,"dim1":0},{"shape":[512,1024],"dim0":1,"dim1":0},{"shape":[512,1024],"dim0":1,"dim1":0},{"shape":[512,128],"dim0":1,"dim1":0},{"shape":[512,128],"dim0":1,"dim1":0},{"shape":[512,2048],"dim0":1,"dim1":0},{"shape":[512,2048],"dim0":1,"dim1":0},{"shape":[512,256],"dim0":1,"dim1":0},{"shape":[512,256],"dim0":1,"dim1":0},{"shape":[512,384],"dim0":1,"dim1":0},{"shape":[512,384],"dim0":1,"dim1":0},{"shape":[512,512],"dim0":1,"dim1":0},{"shape":[512,512],"dim0":1,"dim1":0},{"shape":[512,768],"dim0":1,"dim1":0},{"shape":[512,768],"dim0":1,"dim1":0},{"shape":[6144,1536],"dim0":1,"dim1":0},{"shape":[6144,1536],"dim0":1,"dim1":0},{"shape":[640,1280],"dim0":1,"dim1":0},{"shape":[640,1280],"dim0":1,"dim1":0},{"shape":[640,160],"dim0":1,"dim1":0},{"shape":[640,160],"dim0":1,"dim1":0},{"shape":[640,2560],"dim0":1,"dim1":0},{"shape":[640,2560],"dim0":1,"dim1":0},{"shape":[640,640],"dim0":1,"dim1":0},{"shape":[640,640],"dim0":1,"dim1":0},{"shape":[640,768],"dim0":1,"dim1":0},{"shape":[640,768],"dim0":1,"dim1":0},{"shape":[64,128],"dim0":1,"dim1":0},{"shape":[64,128],"dim0":1,"dim1":0},{"shape":[64,12],"dim0":1,"dim1":0},{"shape":[64,12],"dim0":1,"dim1":0},{"shape":[64,256],"dim0":1,"dim1":0},{"shape":[64,256],"dim0":1,"dim1":0},{"shape":[64,64],"dim0":1,"dim1":0},{"shape":[64,64],"dim0":1,"dim1":0},{"shape":[65024,4544],"dim0":1,"dim1":0},{"shape":[65024,4544],"dim0":1,"dim1":0},{"shape":[6,100,1,256],"dim0":2,"dim1":1},{"shape":[6,100,1,256],"dim0":2,"dim1":1},{"shape":[768,1280],"dim0":1,"dim1":0},{"shape":[768,1280],"dim0":1,"dim1":0},{"shape":[768,128],"dim0":1,"dim1":0},{"shape":[768,128],"dim0":1,"dim1":0},{"shape":[768,192],"dim0":1,"dim1":0},{"shape":[768,192],"dim0":1,"dim1":0},{"shape":[768,3072],"dim0":1,"dim1":0},{"shape":[768,3072],"dim0":1,"dim1":0},{"shape":[768,768],"dim0":1,"dim1":0},{"shape":[768,768],"dim0":1,"dim1":0},{"shape":[784,128],"dim0":1,"dim1":0},{"shape":[784,128],"dim0":1,"dim1":0},{"shape":[8192,2048],"dim0":1,"dim1":0},{"shape":[8192,2048],"dim0":1,"dim1":0},{"shape":[8,100,32],"dim0":2,"dim1":1},{"shape":[8,100,32],"dim0":2,"dim1":1},{"shape":[8,100,32],"dim0":1,"dim1":0},{"shape":[8,100,32],"dim0":1,"dim1":0},{"shape":[8,920,32],"dim0":2,"dim1":1},{"shape":[8,920,32],"dim0":2,"dim1":1},{"shape":[8,920,32],"dim0":1,"dim1":0},{"shape":[8,920,32],"dim0":1,"dim1":0},{"shape":[920,8,32],"dim0":1,"dim1":0},{"shape":[920,8,32],"dim0":1,"dim1":0},{"shape":[92,192],"dim0":1,"dim1":0},{"shape":[92,192],"dim0":1,"dim1":0},{"shape":[92,256],"dim0":1,"dim1":0},{"shape":[92,256],"dim0":1,"dim1":0}] From 8cde2b73ca71453df68f3f7b4348b2060612c09e Mon Sep 17 00:00:00 2001 From: Oleg Milyutin Date: Wed, 18 Dec 2024 18:23:27 -0500 Subject: [PATCH 52/87] #0: Move memory config serialization in the corresponding header away from types.hpp (#16151) There is a more appropriate place for the serialization logic, away from `types.hpp` / `types.cpp`. ### Checklist - [x] [ Post commit CI passes](https://github.com/tenstorrent/tt-metal/actions/runs/12400107352) - [x] [T3K model perf tests](https://github.com/tenstorrent/tt-metal/actions/runs/12400111574) Co-authored-by: Oleg Milyutin --- ttnn/cpp/ttnn/tensor/serialization.cpp | 96 ++++++++++++++++++++++---- ttnn/cpp/ttnn/tensor/serialization.hpp | 12 ++-- ttnn/cpp/ttnn/tensor/types.cpp | 72 ------------------- ttnn/cpp/ttnn/tensor/types.hpp | 6 -- 4 files changed, 89 insertions(+), 97 deletions(-) diff --git a/ttnn/cpp/ttnn/tensor/serialization.cpp b/ttnn/cpp/ttnn/tensor/serialization.cpp index 984f2993e299..b4efed0b21d6 100644 --- a/ttnn/cpp/ttnn/tensor/serialization.cpp +++ b/ttnn/cpp/ttnn/tensor/serialization.cpp @@ -15,13 +15,11 @@ #include "ttnn/tensor/types.hpp" #include "ttnn/distributed/types.hpp" -namespace tt { - -namespace tt_metal { +namespace tt::tt_metal { using MeshDevice = distributed::MeshDevice; -namespace detail { +namespace { static constexpr std::size_t SENTINEL_VALUE = std::numeric_limits::max(); @@ -191,7 +189,7 @@ Storage load_storage(std::ifstream& input_stream, DataType data_type, StorageTyp } } -} // namespace detail +} // namespace void dump_tensor( const std::string& file_name, const Tensor& tensor, const std::unordered_map& strategy) { @@ -205,7 +203,7 @@ void dump_tensor( auto layout = tensor.get_layout(); auto storage_type = tensor.storage_type(); - output_stream.write(reinterpret_cast(&detail::SENTINEL_VALUE), sizeof(std::size_t)); + output_stream.write(reinterpret_cast(&SENTINEL_VALUE), sizeof(std::size_t)); output_stream.write(reinterpret_cast(&VERSION_ID), sizeof(std::uint8_t)); output_stream.write(reinterpret_cast(&shape), sizeof(ttnn::Shape)); output_stream.write(reinterpret_cast(&data_type), sizeof(DataType)); @@ -230,16 +228,16 @@ void dump_tensor( [&output_stream, &strategy](const auto& storage) { using StorageType = std::decay_t; if constexpr (std::is_same_v) { - detail::dump_owned_storage(output_stream, storage); + dump_owned_storage(output_stream, storage); } else if constexpr (std::is_same_v) { - detail::dump_borrowed_storage(output_stream, storage); + dump_borrowed_storage(output_stream, storage); } else if constexpr (std::is_same_v) { TT_THROW("Device storage isn't supported"); } else if constexpr (std::is_same_v) { TT_THROW("Device storage isn't supported"); } else if constexpr (std::is_same_v) { auto distribute_config = get_distributed_tensor_config(strategy); - detail::dump_multi_device_host_storage(output_stream, storage, distribute_config); + dump_multi_device_host_storage(output_stream, storage, distribute_config); } else { raise_unsupported_storage(); } @@ -256,7 +254,7 @@ Tensor load_tensor_helper(const std::string& file_name, T device) { std::size_t read_sentinel; input_stream.read(reinterpret_cast(&read_sentinel), sizeof(read_sentinel)); - if (read_sentinel == detail::SENTINEL_VALUE) { + if (read_sentinel == SENTINEL_VALUE) { std::uint8_t version_id; input_stream.read(reinterpret_cast(&version_id), sizeof(version_id)); @@ -285,7 +283,7 @@ Tensor load_tensor_helper(const std::string& file_name, T device) { } } - auto storage = detail::load_storage(input_stream, data_type, storage_type, device); + auto storage = load_storage(input_stream, data_type, storage_type, device); auto tensor = Tensor(std::move(storage), shape, data_type, layout); if (device != nullptr) { @@ -305,7 +303,7 @@ Tensor load_tensor_helper(const std::string& file_name, T device) { input_stream.read(reinterpret_cast(&data_type), sizeof(DataType)); input_stream.read(reinterpret_cast(&layout), sizeof(Layout)); - auto storage = detail::load_owned_storage(input_stream, data_type); + auto storage = load_owned_storage(input_stream, data_type); auto tensor = Tensor(std::move(storage), shape, data_type, layout); if (device != nullptr) { tensor = tensor.to(device); @@ -322,6 +320,76 @@ Tensor load_tensor(const std::string& file_name, MeshDevice* device) { return load_tensor_helper(file_name, device); } -} // namespace tt_metal +void dump_memory_config(std::ostream& output_stream, const MemoryConfig& memory_config) { + output_stream.write(reinterpret_cast(&VERSION_ID), sizeof(std::uint8_t)); + output_stream.write(reinterpret_cast(&memory_config.memory_layout), sizeof(TensorMemoryLayout)); + output_stream.write(reinterpret_cast(&memory_config.buffer_type), sizeof(BufferType)); + + bool has_shard_spec = memory_config.shard_spec.has_value(); + output_stream.write(reinterpret_cast(&has_shard_spec), sizeof(bool)); + if (has_shard_spec) { + const auto& shard_spec = memory_config.shard_spec.value(); + const auto& core_ranges = shard_spec.grid.ranges(); + std::size_t num_core_ranges = core_ranges.size(); + output_stream.write(reinterpret_cast(&num_core_ranges), sizeof(std::size_t)); + for (const auto& core_range : core_ranges) { + output_stream.write(reinterpret_cast(&core_range), sizeof(CoreRange)); + } + output_stream.write(reinterpret_cast(&shard_spec.shape), sizeof(std::array)); + output_stream.write(reinterpret_cast(&shard_spec.orientation), sizeof(ShardOrientation)); + output_stream.write(reinterpret_cast(&shard_spec.halo), sizeof(bool)); + } +} + +void dump_memory_config(const std::string& file_name, const MemoryConfig& memory_config) { + std::ofstream output_stream(file_name, std::ios::out | std::ios::binary); + if (not output_stream) { + throw std::runtime_error(fmt::format("Cannot open \"{}\"", file_name)); + } + dump_memory_config(output_stream, memory_config); +} + +MemoryConfig load_memory_config(std::ifstream& input_stream) { + std::uint8_t version_id; + TensorMemoryLayout memory_layout; + BufferType buffer_type; + bool has_shard_spec; + input_stream.read(reinterpret_cast(&version_id), sizeof(std::uint8_t)); + if (version_id != VERSION_ID) { + throw std::runtime_error(fmt::format("Unsupported version_id: {}", version_id)); + } + input_stream.read(reinterpret_cast(&memory_layout), sizeof(TensorMemoryLayout)); + input_stream.read(reinterpret_cast(&buffer_type), sizeof(BufferType)); + input_stream.read(reinterpret_cast(&has_shard_spec), sizeof(bool)); + + std::optional shard_spec = std::nullopt; + if (has_shard_spec) { + std::size_t num_core_ranges; + std::set core_ranges; + std::array shape; + ShardOrientation orientation; + bool halo; + + input_stream.read(reinterpret_cast(&num_core_ranges), sizeof(std::size_t)); + for (auto index = 0; index < num_core_ranges; index++) { + CoreRange core_range{{}, {}}; + input_stream.read(reinterpret_cast(&core_range), sizeof(CoreRange)); + core_ranges.insert(core_range); + } + input_stream.read(reinterpret_cast(&shape), sizeof(std::array)); + input_stream.read(reinterpret_cast(&orientation), sizeof(ShardOrientation)); + input_stream.read(reinterpret_cast(&halo), sizeof(bool)); + shard_spec = {CoreRangeSet{core_ranges}, shape, orientation, halo}; + } + return MemoryConfig{memory_layout, buffer_type, shard_spec}; +} + +MemoryConfig load_memory_config(const std::string& file_name) { + std::ifstream input_stream(file_name, std::ios::in | std::ios::binary); + if (not input_stream) { + throw std::runtime_error(fmt::format("Cannot open \"{}\"", file_name)); + } + return load_memory_config(input_stream); +} -} // namespace tt +} // namespace tt::tt_metal diff --git a/ttnn/cpp/ttnn/tensor/serialization.hpp b/ttnn/cpp/ttnn/tensor/serialization.hpp index 7706cf2c186c..7fa8910049b0 100644 --- a/ttnn/cpp/ttnn/tensor/serialization.hpp +++ b/ttnn/cpp/ttnn/tensor/serialization.hpp @@ -9,9 +9,7 @@ #include #include -namespace tt { - -namespace tt_metal { +namespace tt::tt_metal { void dump_tensor( const std::string& file_name, const Tensor& tensor, const std::unordered_map& strategy); @@ -19,6 +17,10 @@ void dump_tensor( Tensor load_tensor(const std::string& file_name, Device* device = nullptr); Tensor load_tensor(const std::string& file_name, distributed::MeshDevice* device = nullptr); -} // namespace tt_metal +void dump_memory_config(std::ostream& output_stream, const MemoryConfig& memory_config); +void dump_memory_config(const std::string& file_name, const MemoryConfig& memory_config); + +MemoryConfig load_memory_config(std::ifstream& input_stream); +MemoryConfig load_memory_config(const std::string& file_name); -} // namespace tt +} // namespace tt::tt_metal diff --git a/ttnn/cpp/ttnn/tensor/types.cpp b/ttnn/cpp/ttnn/tensor/types.cpp index 6ba3893c8a1f..b2ad4eb875ad 100644 --- a/ttnn/cpp/ttnn/tensor/types.cpp +++ b/ttnn/cpp/ttnn/tensor/types.cpp @@ -226,78 +226,6 @@ std::ostream& operator<<(std::ostream& os, const MemoryConfig& config) { return os; } -void dump_memory_config(std::ostream& output_stream, const MemoryConfig& memory_config) { - output_stream.write(reinterpret_cast(&VERSION_ID), sizeof(std::uint8_t)); - output_stream.write(reinterpret_cast(&memory_config.memory_layout), sizeof(TensorMemoryLayout)); - output_stream.write(reinterpret_cast(&memory_config.buffer_type), sizeof(BufferType)); - - bool has_shard_spec = memory_config.shard_spec.has_value(); - output_stream.write(reinterpret_cast(&has_shard_spec), sizeof(bool)); - if (has_shard_spec) { - const auto& shard_spec = memory_config.shard_spec.value(); - const auto& core_ranges = shard_spec.grid.ranges(); - std::size_t num_core_ranges = core_ranges.size(); - output_stream.write(reinterpret_cast(&num_core_ranges), sizeof(std::size_t)); - for (const auto& core_range : core_ranges) { - output_stream.write(reinterpret_cast(&core_range), sizeof(CoreRange)); - } - output_stream.write(reinterpret_cast(&shard_spec.shape), sizeof(std::array)); - output_stream.write(reinterpret_cast(&shard_spec.orientation), sizeof(ShardOrientation)); - output_stream.write(reinterpret_cast(&shard_spec.halo), sizeof(bool)); - } -} - -void dump_memory_config(const std::string& file_name, const MemoryConfig& memory_config) { - std::ofstream output_stream(file_name, std::ios::out | std::ios::binary); - if (not output_stream) { - throw std::runtime_error(fmt::format("Cannot open \"{}\"", file_name)); - } - dump_memory_config(output_stream, memory_config); -} - -MemoryConfig load_memory_config(std::ifstream& input_stream) { - std::uint8_t version_id; - TensorMemoryLayout memory_layout; - BufferType buffer_type; - bool has_shard_spec; - input_stream.read(reinterpret_cast(&version_id), sizeof(std::uint8_t)); - if (version_id != VERSION_ID) { - throw std::runtime_error(fmt::format("Unsupported version_id: {}", version_id)); - } - input_stream.read(reinterpret_cast(&memory_layout), sizeof(TensorMemoryLayout)); - input_stream.read(reinterpret_cast(&buffer_type), sizeof(BufferType)); - input_stream.read(reinterpret_cast(&has_shard_spec), sizeof(bool)); - - std::optional shard_spec = std::nullopt; - if (has_shard_spec) { - std::size_t num_core_ranges; - std::set core_ranges; - std::array shape; - ShardOrientation orientation; - bool halo; - - input_stream.read(reinterpret_cast(&num_core_ranges), sizeof(std::size_t)); - for (auto index = 0; index < num_core_ranges; index++) { - CoreRange core_range{{}, {}}; - input_stream.read(reinterpret_cast(&core_range), sizeof(CoreRange)); - core_ranges.insert(core_range); - } - input_stream.read(reinterpret_cast(&shape), sizeof(std::array)); - input_stream.read(reinterpret_cast(&orientation), sizeof(ShardOrientation)); - input_stream.read(reinterpret_cast(&halo), sizeof(bool)); - shard_spec = {CoreRangeSet{core_ranges}, shape, orientation, halo}; - } - return MemoryConfig{memory_layout, buffer_type, shard_spec}; -} - -MemoryConfig load_memory_config(const std::string& file_name) { - std::ifstream input_stream(file_name, std::ios::in | std::ios::binary); - if (not input_stream) { - throw std::runtime_error(fmt::format("Cannot open \"{}\"", file_name)); - } - return load_memory_config(input_stream); -} - std::vector MultiDeviceStorage::get_buffers() const { std::lock_guard lock(buffer_mtx); std::vector buf_vec; diff --git a/ttnn/cpp/ttnn/tensor/types.hpp b/ttnn/cpp/ttnn/tensor/types.hpp index 58299ece07bd..33fd91e8b402 100644 --- a/ttnn/cpp/ttnn/tensor/types.hpp +++ b/ttnn/cpp/ttnn/tensor/types.hpp @@ -280,12 +280,6 @@ std::ostream& operator<<(std::ostream& os, const MemoryConfig& config); bool operator==(const MemoryConfig &config_a, const MemoryConfig &config_b); bool operator!=(const MemoryConfig &config_a, const MemoryConfig &config_b); -void dump_memory_config(std::ostream &output_stream, const MemoryConfig &memory_config); -void dump_memory_config(const std::string &file_name, const MemoryConfig &memory_config); - -MemoryConfig load_memory_config(std::ifstream &input_stream); -MemoryConfig load_memory_config(const std::string &file_name); - using OwnedBuffer = std::variant< owned_buffer::Buffer, owned_buffer::Buffer, From d40a9c712432a591404f74a69f83c7418bd983b2 Mon Sep 17 00:00:00 2001 From: asaigal Date: Tue, 10 Dec 2024 03:25:59 +0000 Subject: [PATCH 53/87] #16114: Allow Binarized Programs to be Reused across WH Devices - Virtual coordinates allow Kernel Binaries and Fast Dispatch Commands associated with a Program to be reused across devices with different harvesting configs - Test this feature out on WH, by using cached program attributes across devices and only rewriting Kernel Binaries to DRAM if a program is enqueued to a device for the first time - Add sanity and end to end Metal tests to verify this feature - Disabled for GS and BH (will hit an assert if programs are reused across devices on these archs) --- .../dispatch/dispatch_program/CMakeLists.txt | 1 + .../dispatch_program/test_program_reuse.cpp | 385 ++++++++++++++++++ .../misc/full_grid_eltwise_device_reuse.cpp | 47 +++ tt_metal/impl/device/device.cpp | 9 +- tt_metal/impl/dispatch/command_queue.cpp | 72 ++-- .../dispatch/program_command_sequence.hpp | 5 +- tt_metal/impl/program/program.cpp | 70 +++- tt_metal/impl/program/program.hpp | 11 +- 8 files changed, 550 insertions(+), 50 deletions(-) create mode 100644 tests/tt_metal/tt_metal/dispatch/dispatch_program/test_program_reuse.cpp create mode 100644 tests/tt_metal/tt_metal/test_kernels/misc/full_grid_eltwise_device_reuse.cpp diff --git a/tests/tt_metal/tt_metal/dispatch/dispatch_program/CMakeLists.txt b/tests/tt_metal/tt_metal/dispatch/dispatch_program/CMakeLists.txt index 261109184f83..81cd8829a9ef 100644 --- a/tests/tt_metal/tt_metal/dispatch/dispatch_program/CMakeLists.txt +++ b/tests/tt_metal/tt_metal/dispatch/dispatch_program/CMakeLists.txt @@ -3,6 +3,7 @@ set(UNIT_TESTS_DISPATCH_PROGRAM_SRC ${CMAKE_CURRENT_SOURCE_DIR}/test_dispatch.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_EnqueueProgram.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_sub_device.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_program_reuse.cpp ) add_library(unit_tests_dispatch_program_o STATIC ${UNIT_TESTS_DISPATCH_PROGRAM_SRC}) diff --git a/tests/tt_metal/tt_metal/dispatch/dispatch_program/test_program_reuse.cpp b/tests/tt_metal/tt_metal/dispatch/dispatch_program/test_program_reuse.cpp new file mode 100644 index 000000000000..ae554620b6cd --- /dev/null +++ b/tests/tt_metal/tt_metal/dispatch/dispatch_program/test_program_reuse.cpp @@ -0,0 +1,385 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +// Reusing programs in this context means enqueuing them across multiple devices, without creating a copy per device. +// This requires the program object to track state that is universal across devices (in our case through +// virtualization). The tests in this file create a single program and enqueue it across multiple devices. These tests +// are skipped for architectures that don't have Coordinate Virtualization enabled (i.e. GS and BH). These will be +// enabled on BH once FW is updated to support Virtual Coordinates. These tests are different from Single Device +// EnqueueProgram tests, since they validate state (Binaries, Semaphores and RTAs) across multiple devices, by comparing +// it with a single program on host. These tests are also structurally different from single device program tests. While +// those tests follow the pattern below: for device ... devices: +// p = CreateProgram() +// EnqueueProgram(p, device) +// The ones below follow this pattern: +// p = CreateProgram() +// for device ... devices: +// EnqueueProgram(p, device) +// This makes it non-trivial to share the host-setup code across tests. + +#include +#include +#include +#include + +#include "command_queue_fixture.hpp" + +namespace tt::tt_metal { +struct CBConfig { + uint32_t cb_id; + uint32_t num_pages; + uint32_t page_size; + DataFormat data_format; +}; + +struct DummyProgramConfig { + CoreRangeSet cr_set; + CBConfig cb_config; + uint32_t num_cbs; + uint32_t num_sems; +}; + +struct DummyProgramMultiCBConfig { + CoreRangeSet cr_set; + std::vector cb_config_vector; + uint32_t num_sems; +}; + +std::shared_ptr create_program_multi_core_rta( + const DummyProgramConfig& rta_program_config, + const DummyProgramConfig& sem_program_config, + const std::vector& dummy_cr0_args, + const std::vector& dummy_cr1_args, + const std::vector& sem_values, + const DummyProgramMultiCBConfig& cb_configs, + uint32_t base_addr) { + std::shared_ptr program = std::make_shared(); + CoreRangeSet rta_cr_set = rta_program_config.cr_set; + uint32_t rta_base_dm0 = base_addr; + uint32_t rta_base_dm1 = rta_base_dm0 + 1024 * sizeof(uint32_t); + uint32_t rta_base_compute = rta_base_dm1 + 2048 * sizeof(uint32_t); + std::map dm_defines0 = { + {"DATA_MOVEMENT", "1"}, + {"NUM_RUNTIME_ARGS", std::to_string(256)}, + {"RESULTS_ADDR", std::to_string(rta_base_dm0)}}; + std::map dm_defines1 = { + {"DATA_MOVEMENT", "1"}, + {"NUM_RUNTIME_ARGS", std::to_string(256)}, + {"RESULTS_ADDR", std::to_string(rta_base_dm1)}}; + std::map compute_defines = { + {"COMPUTE", "1"}, + {"NUM_RUNTIME_ARGS", std::to_string(256)}, + {"RESULTS_ADDR", std::to_string(rta_base_compute)}}; + + auto dummy_kernel0 = CreateKernel( + *program, + "tests/tt_metal/tt_metal/test_kernels/misc/runtime_args_kernel.cpp", + rta_cr_set, + DataMovementConfig{ + .processor = DataMovementProcessor::RISCV_0, .noc = NOC::RISCV_0_default, .defines = dm_defines0}); + + auto dummy_kernel1 = CreateKernel( + *program, + "tests/tt_metal/tt_metal/test_kernels/misc/runtime_args_kernel.cpp", + rta_cr_set, + DataMovementConfig{ + .processor = DataMovementProcessor::RISCV_1, .noc = NOC::RISCV_1_default, .defines = dm_defines1}); + + auto dummy_compute_kernel = CreateKernel( + *program, + "tests/tt_metal/tt_metal/test_kernels/misc/runtime_args_kernel.cpp", + rta_cr_set, + ComputeConfig{.defines = compute_defines}); + + auto it = rta_program_config.cr_set.ranges().begin(); + CoreRange core_range_0 = *it; + std::advance(it, 1); + CoreRange core_range_1 = *it; + + for (const CoreCoord& core_coord : core_range_0) { + SetRuntimeArgs(*program, dummy_kernel0, core_coord, dummy_cr0_args); + SetRuntimeArgs(*program, dummy_kernel1, core_coord, dummy_cr0_args); + SetRuntimeArgs(*program, dummy_compute_kernel, core_coord, dummy_cr0_args); + } + + for (const CoreCoord& core_coord : core_range_1) { + SetRuntimeArgs(*program, dummy_kernel0, core_coord, dummy_cr1_args); + SetRuntimeArgs(*program, dummy_kernel1, core_coord, dummy_cr1_args); + SetRuntimeArgs(*program, dummy_compute_kernel, core_coord, dummy_cr1_args); + } + for (auto sem_value : sem_values) { + CreateSemaphore(*program, sem_program_config.cr_set, sem_value); + } + for (auto cb_config : cb_configs.cb_config_vector) { + const uint32_t cb_id = cb_config.cb_id; + const uint32_t cb_num_pages = cb_config.num_pages; + const uint32_t page_size = cb_config.page_size; + const uint32_t cb_size = cb_num_pages * page_size; + const DataFormat data_format = cb_config.data_format; + const CircularBufferConfig circular_buffer_config = + CircularBufferConfig(cb_size, {{cb_id, data_format}}).set_page_size(cb_id, page_size); + const CBHandle cb_handle = CreateCircularBuffer(*program, cb_configs.cr_set, circular_buffer_config); + } + + return program; +} + +TEST_F(CommandQueueMultiDeviceFixture, TestProgramReuseSanity) { + // Sanity test: Create a program with Semaphores, CBs, RTAs and Kernel Binaries. + // Enqueue Program across all devices. + // Read L1 directly to ensure that all program attributes are correctly present. + if (devices_[0]->arch() != ARCH::WORMHOLE_B0) { + GTEST_SKIP(); + } + CoreCoord worker_grid_size = devices_[0]->compute_with_storage_grid_size(); + CoreRange cr0({0, 0}, {worker_grid_size.x - 1, 3}); + CoreRange cr1({0, 4}, {worker_grid_size.x - 1, worker_grid_size.y - 1}); + CoreRangeSet rta_cr_set(std::vector{cr0, cr1}); + DummyProgramConfig rta_program_config = {.cr_set = rta_cr_set}; + CoreRange sem_cr_range({0, 0}, {worker_grid_size.x - 1, worker_grid_size.y - 1}); + CoreRangeSet sem_cr_set = CoreRangeSet(sem_cr_range); + DummyProgramConfig sem_program_config = {.cr_set = sem_cr_set}; + + std::vector dummy_cr0_args; + std::vector dummy_cr1_args; + std::vector dummy_sems; + uint32_t num_runtime_args_for_cr0 = 32; + uint32_t num_runtime_args_for_cr1 = 35; + // Initialize RTA data across core_ranges + uint32_t start_idx = 25; + for (uint32_t i = 0; i < num_runtime_args_for_cr0; i++) { + dummy_cr0_args.push_back(start_idx++); + } + for (uint32_t i = 0; i < num_runtime_args_for_cr1; i++) { + dummy_cr1_args.push_back(start_idx++); + } + // Initialize Semaphore values + for (uint32_t i = 0; i < NUM_SEMAPHORES; i++) { + dummy_sems.push_back(i + 1); + } + + // Initialize CB Configs + CBConfig cb_config_0 = {.cb_id = 0, .num_pages = 1, .page_size = 2048, .data_format = DataFormat::Float16_b}; + CBConfig cb_config_1 = {.cb_id = 1, .num_pages = 2, .page_size = 4096, .data_format = DataFormat::Float16_b}; + CBConfig cb_config_2 = {.cb_id = 2, .num_pages = 2, .page_size = 2048, .data_format = DataFormat::Float16_b}; + CBConfig cb_config_3 = {.cb_id = 3, .num_pages = 4, .page_size = 2048, .data_format = DataFormat::Float16_b}; + + DummyProgramMultiCBConfig cb_config = { + .cr_set = sem_cr_set, .cb_config_vector = {cb_config_0, cb_config_1, cb_config_2, cb_config_3}}; + + uint32_t rta_base_addr = devices_[0]->get_base_allocator_addr(HalMemType::L1); + auto program = create_program_multi_core_rta( + rta_program_config, sem_program_config, dummy_cr0_args, dummy_cr1_args, dummy_sems, cb_config, rta_base_addr); + + uint32_t rta_base_dm0 = rta_base_addr; + uint32_t rta_base_dm1 = rta_base_dm0 + 1024 * sizeof(uint32_t); + uint32_t rta_base_compute = rta_base_dm1 + 2048 * sizeof(uint32_t); + uint32_t semaphore_buffer_size = dummy_sems.size() * hal.get_alignment(HalMemType::L1); + uint32_t cb_config_buffer_size = + NUM_CIRCULAR_BUFFERS * UINT32_WORDS_PER_LOCAL_CIRCULAR_BUFFER_CONFIG * sizeof(uint32_t); + for (auto device : devices_) { + log_info(LogTest, "Running test on {}", device->id()); + EnqueueProgram(device->command_queue(), *program, false); + Finish(device->command_queue()); + + for (const CoreCoord& core_coord : cr0) { + std::vector dummy_kernel0_args_readback; + detail::ReadFromDeviceL1( + device, + core_coord, + rta_base_dm0, + num_runtime_args_for_cr0 * sizeof(uint32_t), + dummy_kernel0_args_readback); + EXPECT_EQ(dummy_cr0_args, dummy_kernel0_args_readback); + + std::vector dummy_kernel1_args_readback; + detail::ReadFromDeviceL1( + device, + core_coord, + rta_base_dm1, + num_runtime_args_for_cr0 * sizeof(uint32_t), + dummy_kernel1_args_readback); + EXPECT_EQ(dummy_cr0_args, dummy_kernel1_args_readback); + + std::vector dummy_compute_args_readback; + detail::ReadFromDeviceL1( + device, + core_coord, + rta_base_compute, + num_runtime_args_for_cr0 * sizeof(uint32_t), + dummy_compute_args_readback); + EXPECT_EQ(dummy_cr0_args, dummy_compute_args_readback); + } + + for (const CoreCoord& core_coord : cr1) { + std::vector dummy_kernel0_args_readback; + detail::ReadFromDeviceL1( + device, + core_coord, + rta_base_dm0, + num_runtime_args_for_cr1 * sizeof(uint32_t), + dummy_kernel0_args_readback); + EXPECT_EQ(dummy_cr1_args, dummy_kernel0_args_readback); + + std::vector dummy_kernel1_args_readback; + detail::ReadFromDeviceL1( + device, + core_coord, + rta_base_dm1, + num_runtime_args_for_cr1 * sizeof(uint32_t), + dummy_kernel1_args_readback); + EXPECT_EQ(dummy_cr1_args, dummy_kernel1_args_readback); + + std::vector dummy_compute_args_readback; + detail::ReadFromDeviceL1( + device, + core_coord, + rta_base_compute, + num_runtime_args_for_cr1 * sizeof(uint32_t), + dummy_compute_args_readback); + EXPECT_EQ(dummy_cr1_args, dummy_compute_args_readback); + } + std::vector semaphore_vals; + for (const CoreCoord& core_coord : sem_cr_range) { + uint32_t expected_sem_idx = 0; + uint32_t sem_base_addr = program->get_sem_base_addr(devices_[0], core_coord, CoreType::WORKER); + detail::ReadFromDeviceL1(device, core_coord, sem_base_addr, semaphore_buffer_size, semaphore_vals); + for (uint32_t i = 0; i < semaphore_vals.size(); + i += (hal.get_alignment(HalMemType::L1) / sizeof(uint32_t))) { + EXPECT_EQ(semaphore_vals[i], dummy_sems[expected_sem_idx]); + expected_sem_idx++; + } + } + std::vector cb_config_vector; + for (const CoreCoord& core_coord : sem_cr_range) { + detail::ReadFromDeviceL1( + device, + core_coord, + program->get_cb_base_addr(device, core_coord, CoreType::WORKER), + cb_config_buffer_size, + cb_config_vector); + uint32_t cb_addr = device->get_base_allocator_addr(HalMemType::L1); + for (uint32_t i = 0; i < cb_config.cb_config_vector.size(); i++) { + const uint32_t index = cb_config.cb_config_vector[i].cb_id * sizeof(uint32_t); + const uint32_t cb_num_pages = cb_config.cb_config_vector[i].num_pages; + const uint32_t cb_size = cb_num_pages * cb_config.cb_config_vector[i].page_size; + const bool addr_match = cb_config_vector.at(index) == cb_addr; + const bool size_match = cb_config_vector.at(index + 1) == cb_size; + const bool num_pages_match = cb_config_vector.at(index + 2) == cb_num_pages; + EXPECT_TRUE(addr_match and size_match and num_pages_match); + + cb_addr += cb_size; + } + } + } +} + +TEST_F(CommandQueueMultiDeviceFixture, TestDataCopyComputeProgramReuse) { + // End to End full-grid test. Creates a Program with the same kernel running on the full logical grid. + // Each core reads from a buffer, performs a simple math operation on each datum in the buffer, and writes + // outputs in dedicated DRAM memory. + // The same program is enqueued across all devices, and results are validated. + if (devices_[0]->arch() != ARCH::WORMHOLE_B0) { + GTEST_SKIP(); + } + CoreCoord worker_grid_size = this->devices_[0]->compute_with_storage_grid_size(); + std::vector> input_buffers = {}; + std::vector> output_buffers = {}; + uint32_t single_tile_size = detail::TileSize(DataFormat::Float16_b); + + uint32_t num_tiles = 1; + uint32_t dram_buffer_size = single_tile_size * num_tiles; + for (auto device : this->devices_) { + InterleavedBufferConfig dram_config{ + .device = device, .size = dram_buffer_size, .page_size = dram_buffer_size, .buffer_type = BufferType::DRAM}; + for (std::size_t col_idx = 0; col_idx < worker_grid_size.x; col_idx++) { + for (std::size_t row_idx = 0; row_idx < worker_grid_size.y; row_idx++) { + input_buffers.push_back(CreateBuffer(dram_config)); + output_buffers.push_back(CreateBuffer(dram_config)); + } + } + } + + Program program = CreateProgram(); + auto full_grid = CoreRange({0, 0}, {worker_grid_size.x - 1, worker_grid_size.y - 1}); + auto reader_writer_kernel = CreateKernel( + program, + "tests/tt_metal/tt_metal/test_kernels/misc/full_grid_eltwise_device_reuse.cpp", + full_grid, + DataMovementConfig{.processor = DataMovementProcessor::RISCV_0, .noc = NOC::RISCV_0_default}); + + auto sem_scaling_factor = 2; + auto scaling_sem_idx = CreateSemaphore(program, full_grid, sem_scaling_factor); + uint32_t scaling_height_toggle = 16; + constexpr uint32_t src0_cb_index = CBIndex::c_0; + CircularBufferConfig cb_src0_config = + CircularBufferConfig(dram_buffer_size, {{src0_cb_index, DataFormat::Float16_b}}) + .set_page_size(src0_cb_index, single_tile_size); + + uint32_t add_factor = 64; + for (std::size_t col_idx = 0; col_idx < worker_grid_size.x; col_idx++) { + for (std::size_t row_idx = 0; row_idx < worker_grid_size.y; row_idx++) { + CoreCoord curr_core = {col_idx, row_idx}; + SetRuntimeArgs( + program, + reader_writer_kernel, + curr_core, + {input_buffers.at(col_idx * worker_grid_size.y + row_idx)->address(), + output_buffers.at(col_idx * worker_grid_size.y + row_idx)->address(), + 0, /* src_bank_id */ + 0, /* dst_bank_id */ + add_factor, + constants::TILE_HEIGHT, + constants::TILE_WIDTH, + scaling_sem_idx, + scaling_height_toggle}); + CBHandle cb_src0 = CreateCircularBuffer(program, curr_core, cb_src0_config); + } + } + + std::vector src_vec = create_constant_vector_of_bfloat16(dram_buffer_size, 1); + std::size_t buffer_idx = 0; + // Write constant inputs once + for (auto device : devices_) { + for (std::size_t col_idx = 0; col_idx < worker_grid_size.x; col_idx++) { + for (std::size_t row_idx = 0; row_idx < worker_grid_size.y; row_idx++) { + EnqueueWriteBuffer(device->command_queue(), input_buffers.at(buffer_idx), src_vec, false); + buffer_idx++; + } + } + } + // Run program multiple times with different RTAs and validate for each iteration + for (int iter = 0; iter < 100; iter++) { + log_info(LogTest, "Run iter {}", iter); + if (iter) { + auto& rtas = GetRuntimeArgs(program, reader_writer_kernel); + for (auto core : full_grid) { + rtas[core.x][core.y].at(4) = ((iter % 2) + 1) * add_factor; + } + } + for (auto device : devices_) { + EnqueueProgram(device->command_queue(), program, false); + } + + buffer_idx = 0; + for (auto device : devices_) { + for (std::size_t col_idx = 0; col_idx < worker_grid_size.x; col_idx++) { + for (std::size_t row_idx = 0; row_idx < worker_grid_size.y; row_idx++) { + std::vector dst_vec = {}; + EnqueueReadBuffer(device->command_queue(), output_buffers.at(buffer_idx), dst_vec, true); + buffer_idx++; + for (int i = 0; i < dst_vec.size(); i++) { + float ref_val = std::pow(2, (iter % 2) + 1); + if (i >= 512) { + ref_val = std::pow(2, 2 * ((iter % 2) + 1)); + } + EXPECT_EQ(dst_vec[i].to_float(), ref_val); + } + } + } + } + } +} + +} // namespace tt::tt_metal diff --git a/tests/tt_metal/tt_metal/test_kernels/misc/full_grid_eltwise_device_reuse.cpp b/tests/tt_metal/tt_metal/test_kernels/misc/full_grid_eltwise_device_reuse.cpp new file mode 100644 index 000000000000..2e67545ec733 --- /dev/null +++ b/tests/tt_metal/tt_metal/test_kernels/misc/full_grid_eltwise_device_reuse.cpp @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +// Basic Reader, Compute and Writer in RISC, for Program Dispatch Testing purposes only. +// Uses more Kernel/Program config attributes than other metal test kernels, validating +// dispatch of Kernel Binaries, CBs, Semaphores and Runtime Args. +void kernel_main() { + // src and dst addrs + uint32_t src_dram_addr = get_arg_val(0); + uint32_t dst_dram_addr = get_arg_val(1); + // src and dst bank_ids + uint32_t src_bank_id = get_arg_val(2); + uint32_t dst_bank_id = get_arg_val(3); + // Specify eltwise op params + uint32_t add_value = get_arg_val(4); + uint32_t tile_size_x = get_arg_val(5); + uint32_t tile_size_y = get_arg_val(6); + uint32_t scaling_sem_idx = get_arg_val(7); + uint32_t tile_toggle_val = get_arg_val(8); + + // NOC coords (x,y) depending on DRAM location on-chip + uint64_t src_dram_noc_addr = get_noc_addr_from_bank_id(src_bank_id, src_dram_addr); + uint64_t dst_dram_noc_addr = get_noc_addr_from_bank_id(dst_bank_id, dst_dram_addr); + // Input L1 buffer, read into + constexpr uint32_t cb_id_in0 = tt::CBIndex::c_0; // index=0 + uint32_t tile_size_bytes = get_tile_size(cb_id_in0); + uint32_t l1_write_addr = get_write_ptr(cb_id_in0); + + // Read data from DRAM -> L1 circular buffers + noc_async_read(src_dram_noc_addr, l1_write_addr, tile_size_bytes); + noc_async_read_barrier(); + + // Perform compute on data that was just read + uint16_t* data = (uint16_t*)l1_write_addr; + uint32_t* sem_addr = reinterpret_cast(get_semaphore(scaling_sem_idx)); + for (uint32_t i = 0; i < tile_size_x * tile_size_y; i++) { + if (i % (tile_size_x * tile_toggle_val) == 0 and i) { + noc_semaphore_inc(get_noc_addr(get_semaphore(scaling_sem_idx)), 2); + noc_async_atomic_barrier(); + } + data[i] = (*sem_addr) * add_value + data[i]; + } + // Write to dst buffer + noc_async_write(l1_write_addr, dst_dram_noc_addr, tile_size_bytes); + noc_async_write_barrier(); +} diff --git a/tt_metal/impl/device/device.cpp b/tt_metal/impl/device/device.cpp index 815973247225..57f681cd5f60 100644 --- a/tt_metal/impl/device/device.cpp +++ b/tt_metal/impl/device/device.cpp @@ -2983,7 +2983,14 @@ bool Device::initialize(const uint8_t num_hw_cqs, size_t l1_small_size, size_t t this->using_fast_dispatch = false; this->num_hw_cqs_ = num_hw_cqs; constexpr uint32_t harvesting_map_bits = 12; - this->build_key_ = ((uint32_t)this->num_hw_cqs_ << harvesting_map_bits) | tt::Cluster::instance().get_harvesting_mask(this->id()); + this->build_key_ = ((uint32_t)this->num_hw_cqs_ << harvesting_map_bits); + if (not hal.is_coordinate_virtualization_enabled()) { + // Coordinate virtualization is not enabled. For a single program, its associated binaries will vary across devices with different cores harvested. + this->build_key_ = (this->build_key_) | tt::Cluster::instance().get_harvesting_mask(this->id()); + } else { + // Coordinate Virtualization is enabled. Track only the number of harvested cores, instead of the exact harvesting configuration (this is not needed). + this->build_key_ = (this->build_key_) | (std::bitset(tt::Cluster::instance().get_harvesting_mask(this->id())).count()); + } this->initialize_cluster(); this->initialize_default_sub_device_state(l1_small_size, trace_region_size, l1_bank_remap); this->initialize_build(); diff --git a/tt_metal/impl/dispatch/command_queue.cpp b/tt_metal/impl/dispatch/command_queue.cpp index 284b45886052..7653e10b3b07 100644 --- a/tt_metal/impl/dispatch/command_queue.cpp +++ b/tt_metal/impl/dispatch/command_queue.cpp @@ -20,6 +20,7 @@ #include "dev_msgs.h" #include "device/device_handle.hpp" #include "llrt/hal.hpp" +#include "program_command_sequence.hpp" #include "tt_metal/command_queue.hpp" #include "tt_metal/common/assert.hpp" #include "tt_metal/common/logger.hpp" @@ -389,7 +390,7 @@ void EnqueueProgramCommand::assemble_stall_commands(ProgramCommandSequence& prog CQ_PREFETCH_CMD_BARE_MIN_SIZE + // CQ_PREFETCH_CMD_RELAY_INLINE + CQ_DISPATCH_CMD_WAIT CQ_PREFETCH_CMD_BARE_MIN_SIZE; // CQ_PREFETCH_CMD_STALL - program_command_sequence.stall_command_sequence = + program_command_sequence.stall_command_sequences[UncachedStallSequenceIdx] = HostMemDeviceCommand(uncached_cmd_sequence_sizeB); // Wait for Noc Write Barrier @@ -397,16 +398,16 @@ void EnqueueProgramCommand::assemble_stall_commands(ProgramCommandSequence& prog // Wait Noc Write Barrier, wait for binaries to be written to worker cores // Stall to allow binaries to commit to DRAM first // TODO: this can be removed for all but the first program run - program_command_sequence.stall_command_sequence.add_dispatch_wait_with_prefetch_stall( + program_command_sequence.stall_command_sequences[UncachedStallSequenceIdx].add_dispatch_wait_with_prefetch_stall( true, this->dispatch_message_addr, this->expected_num_workers_completed); } else { // Wait command so previous program finishes uint32_t cached_cmd_sequence_sizeB = CQ_PREFETCH_CMD_BARE_MIN_SIZE; // CQ_PREFETCH_CMD_RELAY_INLINE + CQ_DISPATCH_CMD_WAIT - program_command_sequence.stall_command_sequence = + program_command_sequence.stall_command_sequences[CachedStallSequenceIdx] = HostMemDeviceCommand(cached_cmd_sequence_sizeB); - program_command_sequence.stall_command_sequence.add_dispatch_wait( + program_command_sequence.stall_command_sequences[CachedStallSequenceIdx].add_dispatch_wait( false, this->dispatch_message_addr, this->expected_num_workers_completed); } } @@ -949,7 +950,10 @@ void EnqueueProgramCommand::assemble_device_commands( const uint32_t max_length_per_sub_cmd = dispatch_constants::get(this->dispatch_core_type).scratch_db_size() / 2; const uint32_t max_paged_length_per_sub_cmd = max_length_per_sub_cmd / HostMemDeviceCommand::PROGRAM_PAGE_SIZE * HostMemDeviceCommand::PROGRAM_PAGE_SIZE; - const auto &kernels_buffer = program.get_kernels_buffer(); + if (program_transfer_info.kernel_bins.size()) { + TT_FATAL(program.get_kernels_buffer(this->device).get(), "Expected Kernel Binary Buffer to be allocated for program."); + } + const auto kernels_buffer = program.get_kernels_buffer(this->device); for (const auto& [cores, num_mcast_dests, kg_transfer_info] : program_transfer_info.kernel_bins) { bool write_linear; uint32_t noc_encoding; @@ -1422,9 +1426,9 @@ void EnqueueProgramCommand::write_program_command_sequence( const ProgramCommandSequence& program_command_sequence, bool stall_first, bool stall_before_program) { TT_ASSERT(!(stall_first && stall_before_program)); uint32_t preamble_fetch_size_bytes = program_command_sequence.preamble_command_sequence.size_bytes(); - + auto& curr_stall_seq_idx = program_command_sequence.current_stall_seq_idx; uint32_t stall_fetch_size_bytes = - (stall_first || stall_before_program) ? program_command_sequence.stall_command_sequence.size_bytes() : 0; + (stall_first || stall_before_program) ? program_command_sequence.stall_command_sequences[curr_stall_seq_idx].size_bytes() : 0; uint32_t runtime_args_fetch_size_bytes = program_command_sequence.runtime_args_fetch_size_bytes; @@ -1451,7 +1455,7 @@ void EnqueueProgramCommand::write_program_command_sequence( if (stall_first) { // Must stall before writing runtime args this->manager.cq_write( - program_command_sequence.stall_command_sequence.data(), stall_fetch_size_bytes, write_ptr); + program_command_sequence.stall_command_sequences[curr_stall_seq_idx].data(), stall_fetch_size_bytes, write_ptr); write_ptr += stall_fetch_size_bytes; } @@ -1469,7 +1473,7 @@ void EnqueueProgramCommand::write_program_command_sequence( // Didn't stall before kernel config data, stall before remaining commands this->manager.cq_write( - program_command_sequence.stall_command_sequence.data(), stall_fetch_size_bytes, write_ptr); + program_command_sequence.stall_command_sequences[curr_stall_seq_idx].data(), stall_fetch_size_bytes, write_ptr); write_ptr += stall_fetch_size_bytes; this->manager.cq_write(program_command_sequence_data, program_rem_fetch_size_bytes, write_ptr); @@ -1499,7 +1503,7 @@ void EnqueueProgramCommand::write_program_command_sequence( this->manager.issue_queue_reserve(stall_fetch_size_bytes, this->command_queue_id); write_ptr = this->manager.get_issue_queue_write_ptr(this->command_queue_id); this->manager.cq_write( - program_command_sequence.stall_command_sequence.data(), stall_fetch_size_bytes, write_ptr); + program_command_sequence.stall_command_sequences[curr_stall_seq_idx].data(), stall_fetch_size_bytes, write_ptr); this->manager.issue_queue_push_back(stall_fetch_size_bytes, this->command_queue_id); // One fetch queue entry for just the wait and stall, very inefficient this->manager.fetch_queue_reserve_back(this->command_queue_id); @@ -1535,7 +1539,7 @@ void EnqueueProgramCommand::write_program_command_sequence( this->manager.issue_queue_reserve(stall_fetch_size_bytes, this->command_queue_id); write_ptr = this->manager.get_issue_queue_write_ptr(this->command_queue_id); this->manager.cq_write( - program_command_sequence.stall_command_sequence.data(), stall_fetch_size_bytes, write_ptr); + program_command_sequence.stall_command_sequences[curr_stall_seq_idx].data(), stall_fetch_size_bytes, write_ptr); this->manager.issue_queue_push_back(stall_fetch_size_bytes, this->command_queue_id); // One fetch queue entry for just the wait and stall, very inefficient this->manager.fetch_queue_reserve_back(this->command_queue_id); @@ -1588,14 +1592,21 @@ void EnqueueProgramCommand::process() { reservation = this->config_buffer_mgr.reserve(program.get_program_config_sizes()); } - // Cache is only usable if caching is enabled and program is finalized - // If cache has a program entry but the program is not finalized, then the cache is stale - // Currently this is mapped by device, but will be mapped by multiple values in the future + // Access the program command cache auto& cached_program_command_sequences = program.get_cached_program_command_sequences(); - uint64_t command_hash = this->device->id(); + // Start constructing the cache entry, using the build_key + uint64_t command_hash = device->build_key(); + if (not hal.is_coordinate_virtualization_enabled()) { + // When coordinate virtualization is not enabled, explicitly encode the device + // id into the command hash, to always assert on programs being reused across devices. + command_hash = (command_hash << 32) | (device->id()); + } + bool is_cached = program.is_cached(); // Program is cached, its is assocated command stream was previously generated auto cached_cmd_iter = cached_program_command_sequences.find(command_hash); - bool is_cached = program.is_cached() && cached_cmd_iter != cached_program_command_sequences.end(); - if (!is_cached) { + // Is the program was cached, the current command_hash, must match the one generated when the program was cached + // Finalizing the program multiple times/Regenerating program cache state is not currently supported + TT_FATAL((not is_cached) or cached_cmd_iter != cached_program_command_sequences.end(), "Enqueueing a Program across devices with different cores harvested is not supported, unless coordinate virtualization is enabled (only enabled on Wormhole and above)."); + if (program.get_program_binary_status(device->id()) == ProgramBinaryStatus::InFlight) { // assemble_stall_commands is hardcoded to always wait for everything for now. this->config_buffer_mgr.free(this->expected_num_workers_completed); } else { @@ -1626,6 +1637,7 @@ void EnqueueProgramCommand::process() { ProgramCommandSequence program_command_sequence; this->assemble_preamble_commands(program_command_sequence, kernel_config_addrs); this->assemble_stall_commands(program_command_sequence, true); + program_command_sequence.current_stall_seq_idx = UncachedStallSequenceIdx; // Runtime Args Command Sequence this->assemble_runtime_args_commands(program_command_sequence); @@ -1641,6 +1653,7 @@ void EnqueueProgramCommand::process() { this->assemble_stall_commands(program_command_sequence, false); cached_program_command_sequences.insert({command_hash, std::move(program_command_sequence)}); program.set_cached(); + program.set_program_binary_status(device->id(), ProgramBinaryStatus::Committed); } else { static constexpr uint32_t wait_count_offset = (sizeof(CQPrefetchCmd) + offsetof(CQDispatchCmd, wait.count)); static constexpr uint32_t tensix_l1_write_offset_offset = @@ -1649,8 +1662,14 @@ void EnqueueProgramCommand::process() { (sizeof(CQPrefetchCmd) + offsetof(CQDispatchCmd, set_write_offset.offset2)); auto& cached_program_command_sequence = cached_cmd_iter->second; - - cached_program_command_sequence.stall_command_sequence.update_cmd_sequence( + if (program.get_program_binary_status(device->id()) != ProgramBinaryStatus::Committed) { + cached_program_command_sequence.current_stall_seq_idx = UncachedStallSequenceIdx; + program.set_program_binary_status(device->id(), ProgramBinaryStatus::Committed); + } else { + cached_program_command_sequence.current_stall_seq_idx = CachedStallSequenceIdx; + } + auto& curr_stall_seq_idx = cached_program_command_sequence.current_stall_seq_idx; + cached_program_command_sequence.stall_command_sequences[curr_stall_seq_idx].update_cmd_sequence( wait_count_offset, &sync_count, sizeof(uint32_t)); cached_program_command_sequence.preamble_command_sequence.update_cmd_sequence( @@ -2489,12 +2508,15 @@ void HWCommandQueue::enqueue_program(Program& program, bool blocking) { TT_FATAL(sub_device_ids.size() == 1, "Programs must be executed on a single sub-device"); if (not program.is_finalized()) { program.finalize(device); - TT_FATAL(!this->manager.get_bypass_mode(), "Tracing should only be used when programs have been cached"); - if (const auto &kernels_buffer = program.get_kernels_buffer()) { - // Only stall for used sub-devices + } + if (program.get_program_binary_status(device->id()) == ProgramBinaryStatus::NotSent) { + // Write program binaries to device + program.allocate_kernel_bin_buf_on_device(device); + if (program.get_program_transfer_info().binary_data.size()) { this->enqueue_write_buffer( - *kernels_buffer, program.get_program_transfer_info().binary_data.data(), false, sub_device_ids); + *program.get_kernels_buffer(device), program.get_program_transfer_info().binary_data.data(), false, sub_device_ids); } + program.set_program_binary_status(device->id(), ProgramBinaryStatus::InFlight); } program.set_last_used_command_queue_for_testing(this); @@ -2502,7 +2524,7 @@ void HWCommandQueue::enqueue_program(Program& program, bool blocking) { #ifdef DEBUG if (tt::llrt::RunTimeOptions::get_instance().get_validate_kernel_binaries()) { TT_FATAL(!this->manager.get_bypass_mode(), "Tracing cannot be used while validating program binaries"); - if (const auto &buffer = program.get_kernels_buffer()) { + if (const auto buffer = program.get_kernels_buffer(device)) { std::vector read_data(buffer->page_size() * buffer->num_pages() / sizeof(uint32_t)); this->enqueue_read_buffer(*buffer, read_data.data(), true, sub_device_ids); TT_FATAL( @@ -2561,7 +2583,7 @@ void HWCommandQueue::enqueue_program(Program& program, bool blocking) { #ifdef DEBUG if (tt::llrt::RunTimeOptions::get_instance().get_validate_kernel_binaries()) { TT_FATAL(!this->manager.get_bypass_mode(), "Tracing cannot be used while validating program binaries"); - if (const auto& buffer = program.get_kernels_buffer()) { + if (const auto buffer = program.get_kernels_buffer(device)) { std::vector read_data(buffer->page_size() * buffer->num_pages() / sizeof(uint32_t)); this->enqueue_read_buffer(*buffer, read_data.data(), true, sub_device_ids); TT_FATAL( diff --git a/tt_metal/impl/dispatch/program_command_sequence.hpp b/tt_metal/impl/dispatch/program_command_sequence.hpp index c39a38c33d37..f2f5566682ee 100644 --- a/tt_metal/impl/dispatch/program_command_sequence.hpp +++ b/tt_metal/impl/dispatch/program_command_sequence.hpp @@ -20,9 +20,12 @@ class CircularBuffer; } // namespace v0 +constexpr uint32_t UncachedStallSequenceIdx = 0; +constexpr uint32_t CachedStallSequenceIdx = 1; struct ProgramCommandSequence { HostMemDeviceCommand preamble_command_sequence; - HostMemDeviceCommand stall_command_sequence; + uint32_t current_stall_seq_idx = 0; + HostMemDeviceCommand stall_command_sequences[2]; std::vector runtime_args_command_sequences; uint32_t runtime_args_fetch_size_bytes; HostMemDeviceCommand device_command_sequence; diff --git a/tt_metal/impl/program/program.cpp b/tt_metal/impl/program/program.cpp index 2517c52c4629..e5f0fa3adb25 100644 --- a/tt_metal/impl/program/program.cpp +++ b/tt_metal/impl/program/program.cpp @@ -51,9 +51,8 @@ void GenerateBinaries(Device *device, JitBuildOptions &build_options, const std: #endif size_t KernelCompileHash(const std::shared_ptr& kernel, JitBuildOptions &build_options, uint32_t build_key, size_t device_kernel_defines_hash) { - // Account for device id in hash because generated headers are dependent on harvesting config, which can differ per - // device This can be removed with https://github.com/tenstorrent/tt-metal/issues/3381 - + // Store the build key into the KernelCompile hash. This will be unique per command queue + // configuration (necessary for dispatch kernels). // Also account for watcher/dprint enabled in hash because they enable additional code to // be compiled into the kernel. string compile_hash_str = fmt::format( @@ -131,9 +130,19 @@ class Program_ { void allocate_circular_buffers(const Device *device); bool is_finalized() const; + void allocate_kernel_bin_buf_on_device(Device* device); void finalize(Device *device); bool is_cached() const { return this->cached_; } + ProgramBinaryStatus get_program_binary_status(std::size_t device_id) const { + if (auto it = this->binaries_on_device_.find(device_id); it != this->binaries_on_device_.end()) { + return it->second; + } + return ProgramBinaryStatus::NotSent; + } void set_cached() { this->cached_ = true; } + void set_program_binary_status(std::size_t device_id, ProgramBinaryStatus status) { + this->binaries_on_device_[device_id] = status; + } std::shared_ptr get_kernel(KernelHandle kernel_id) const; ProgramConfig& get_program_config(uint32_t programmable_core_type_index); @@ -156,7 +165,7 @@ class Program_ { std::vector> owned_buffer_pool = {}; // The buffer that holds the kernel/binaries/etc for this program - std::shared_ptr kernels_buffer = nullptr; + std::unordered_map> kernels_buffer_; ProgramTransferInfo program_transfer_info; bool finalized_; @@ -202,12 +211,13 @@ class Program_ { std::unordered_map> per_core_cb_indices_; std::unordered_map> per_core_local_cb_indices_; std::unordered_map> per_core_remote_cb_indices_; + std::unordered_map binaries_on_device_; // Used to generate circular buffer addresses. There is one CircularBufferAllocator per unique CoreRange std::vector cb_allocators_; std::vector semaphores_; - std::unordered_set compiled_; + std::unordered_set compiled_; bool local_circular_buffer_allocation_needed_; static constexpr uint8_t core_to_kernel_group_invalid_index = 0xff; @@ -247,11 +257,11 @@ class Program_ { // Ensures that statically allocated circular buffers do not grow into L1 buffer space void validate_circular_buffer_region(const Device *device); - void set_remote_circular_buffer_init(Device* device, const std::shared_ptr& kernel) const; + void set_remote_circular_buffer_init(const std::shared_ptr& kernel) const; - void set_cb_data_fmt( Device *device, const std::vector & crs, JitBuildOptions& build_options) const; + void set_cb_data_fmt(const std::vector & crs, JitBuildOptions& build_options) const; - void set_cb_tile_dims( Device *device, const std::vector & crs, JitBuildOptions& build_options) const; + void set_cb_tile_dims(const std::vector & crs, JitBuildOptions& build_options) const; void update_kernel_groups(uint32_t programmable_core_type_index); @@ -584,6 +594,7 @@ void detail::Program_::update_kernel_groups(uint32_t programmable_core_type_inde index++; } } + } void detail::Program_::CircularBufferAllocator::mark_address(uint64_t address, uint64_t size, uint64_t base_address) { @@ -888,7 +899,7 @@ std::vector> detail::Program_::logical_cores() const { std::vector> Program::logical_cores() const { return pimpl_->logical_cores(); } -void detail::Program_::set_remote_circular_buffer_init(Device* device, const std::shared_ptr& kernel) const { +void detail::Program_::set_remote_circular_buffer_init(const std::shared_ptr& kernel) const { const auto& kernel_defines = kernel->defines(); const std::string reserved_defines[] = {"ALIGN_LOCAL_CBS_TO_REMOTE_CBS", "UPDATE_REMOTE_CB_CONFIGS_IN_L1"}; for (const auto& str : reserved_defines) { @@ -939,7 +950,7 @@ void detail::Program_::set_remote_circular_buffer_init(Device* device, const std } } -void detail::Program_::set_cb_data_fmt(Device *device, const std::vector &crs, JitBuildOptions &build_options) const { +void detail::Program_::set_cb_data_fmt(const std::vector &crs, JitBuildOptions &build_options) const { //ZoneScoped; for (const auto& logical_cr : crs) { const auto& cbs_on_core = this->circular_buffers_on_corerange(logical_cr); @@ -952,7 +963,7 @@ void detail::Program_::set_cb_data_fmt(Device *device, const std::vector &crs, JitBuildOptions &build_options) const { +void detail::Program_::set_cb_tile_dims(const std::vector &crs, JitBuildOptions &build_options) const { //ZoneScoped; for (const auto &logical_cr : crs) { const auto& cbs_on_core = this->circular_buffers_on_corerange(logical_cr); @@ -1090,10 +1101,6 @@ void detail::Program_::populate_dispatch_data(Device *device) { } if (binaries_data.size() > 0) { - // We allocate program binaries top down to minimize fragmentation with other buffers in DRAM, which are typically allocated bottom up - this->kernels_buffer = Buffer::create( - device, binaries_data.size() * sizeof(uint32_t), HostMemDeviceCommand::PROGRAM_PAGE_SIZE, BufferType::DRAM, TensorMemoryLayout::INTERLEAVED, std::nullopt, false); - this->program_transfer_info.binary_data = binaries_data; } @@ -1106,7 +1113,6 @@ void detail::Program_::populate_dispatch_data(Device *device) { std::vector> dst_noc_multicast_info = device->extract_dst_noc_multicast_info>( kernel_group.core_ranges.ranges(), core_type); - std::vector kernel_ids; for (int dispatch_class = 0; dispatch_class < kernel_group.kernel_ids.size(); dispatch_class++) { auto &optional_id = kernel_group.kernel_ids[dispatch_class]; @@ -1450,6 +1456,16 @@ const std::vector &detail::Program_::determine_sub_device_ids(const return sub_device_ids->second; } +void detail::Program_::allocate_kernel_bin_buf_on_device(Device *device) { + // Allocate the DRAM kernel binary buffer for this program on the specified device, if not previously allocated. + // We allocate program binaries top down to minimize fragmentation with other buffers in DRAM, which are typically allocated bottom up + std::size_t binary_data_size_bytes = this->program_transfer_info.binary_data.size() * sizeof(uint32_t); + if (this->kernels_buffer_.find(device->id()) == this->kernels_buffer_.end() and binary_data_size_bytes) { + std::shared_ptr kernel_bin_buf = Buffer::create(device, binary_data_size_bytes, HostMemDeviceCommand::PROGRAM_PAGE_SIZE, BufferType::DRAM, TensorMemoryLayout::INTERLEAVED, std::nullopt, false); + this->kernels_buffer_[device->id()] = kernel_bin_buf; + } +} + void detail::Program_::finalize(Device *device) { // Store the number of tensix "go signals" for use by CQ // CQ iterates over these to update runtime addresses, needs to know when eth begins (after tensix) @@ -1499,11 +1515,13 @@ void detail::Program_::finalize(Device *device) { finalized_ = true; } +void Program::allocate_kernel_bin_buf_on_device(Device* device) { pimpl_->allocate_kernel_bin_buf_on_device(device); } + void Program::finalize(Device *device) { pimpl_->finalize(device); } void detail::Program_::compile(Device *device, bool fd_bootloader_mode) { //ZoneScoped; - if (compiled_.contains(device->id())) { + if (compiled_.contains(device->build_key())) { return; } // Clear the determined sub_device_ids when we compile the program for the first time @@ -1564,10 +1582,10 @@ void detail::Program_::compile(Device *device, bool fd_bootloader_mode) { JitBuildOptions build_options(device->build_env()); kernel->set_build_options(build_options); if (this->compiled_.empty()) { - this->set_remote_circular_buffer_init(device, kernel); + this->set_remote_circular_buffer_init(kernel); } - this->set_cb_data_fmt(device, kernel->logical_coreranges(), build_options); - this->set_cb_tile_dims(device, kernel->logical_coreranges(), build_options); + this->set_cb_data_fmt(kernel->logical_coreranges(), build_options); + this->set_cb_tile_dims(kernel->logical_coreranges(), build_options); auto kernel_hash = KernelCompileHash(kernel, build_options, device->build_key(), device->get_device_kernel_defines_hash()); std::string kernel_path_suffix = kernel->name() + "/" + std::to_string(kernel_hash) + "/"; @@ -1614,7 +1632,7 @@ void detail::Program_::compile(Device *device, bool fd_bootloader_mode) { if (detail::MemoryReporter::enabled()) { detail::MemoryReporter::inst().flush_program_memory_usage(get_id(), device); } - compiled_.insert(device->id()); + compiled_.insert(device->build_key()); } void Program::compile(Device *device, bool fd_bootloader_mode) { pimpl_->compile(device, fd_bootloader_mode); } @@ -1789,11 +1807,19 @@ bool Program::is_finalized() const { return pimpl_->is_finalized(); } bool Program::is_cached() const { return pimpl_->is_cached(); } void Program::set_cached() { pimpl_->set_cached(); } +ProgramBinaryStatus Program::get_program_binary_status(std::size_t device_id) const { return pimpl_->get_program_binary_status(device_id); } +void Program::set_program_binary_status(std::size_t device_id, ProgramBinaryStatus status) { pimpl_->set_program_binary_status(device_id, status); } + const std::vector &Program::determine_sub_device_ids(const Device *device) { return pimpl_->determine_sub_device_ids(device); } const ProgramTransferInfo &Program::get_program_transfer_info() const noexcept { return pimpl_->program_transfer_info; } -const std::shared_ptr &Program::get_kernels_buffer() const noexcept { return pimpl_->kernels_buffer; } +std::shared_ptr Program::get_kernels_buffer(Device* device) const noexcept { + if (auto it = pimpl_->kernels_buffer_.find(device->id()); it != pimpl_->kernels_buffer_.end()) { + return it->second; + } + return nullptr; +} const std::vector &Program::get_program_config_sizes() const noexcept { return pimpl_->program_config_sizes_; } diff --git a/tt_metal/impl/program/program.hpp b/tt_metal/impl/program/program.hpp index 2305723485b4..aee260a805ad 100644 --- a/tt_metal/impl/program/program.hpp +++ b/tt_metal/impl/program/program.hpp @@ -100,6 +100,12 @@ struct ProgramConfig { }; inline namespace v0 { +// Represents the status of Program Kernel Binaries in Device DRAM with respect to the dispatcher +enum class ProgramBinaryStatus : uint8_t { + NotSent = 0, // Binaries have not been written + InFlight = 1, // Fast Dispatch Commands to write the binaries to DRAM has been issued + Committed = 2, // Binaries have been commited to DRAM +}; class Program { public: @@ -151,7 +157,10 @@ class Program { bool is_finalized() const; bool is_cached() const; + ProgramBinaryStatus get_program_binary_status(std::size_t device_id) const; void set_cached(); + void set_program_binary_status(std::size_t device_id, ProgramBinaryStatus status); + void allocate_kernel_bin_buf_on_device(Device* device); void finalize(Device *device); std::shared_ptr get_kernel(KernelHandle kernel_id) const; @@ -202,7 +211,7 @@ class Program { friend detail::Internal_; const ProgramTransferInfo &get_program_transfer_info() const noexcept; - const std::shared_ptr &get_kernels_buffer() const noexcept; + std::shared_ptr get_kernels_buffer(Device* device) const noexcept; const std::vector &get_program_config_sizes() const noexcept; std::unordered_map &get_cached_program_command_sequences() noexcept; }; From f1ccbb69939ad812ee8037506b38eb4949d3d851 Mon Sep 17 00:00:00 2001 From: Shwetank Singh <156869929+shwetankTT@users.noreply.github.com> Date: Thu, 19 Dec 2024 07:01:49 +0530 Subject: [PATCH 54/87] #0: aligning conv2d transpose as conv (#16128) ### Ticket None ### Problem description aligning conv2d transpose as conv ### What's changed Created python interface for conv2d transpose. Align the implementation with conv2d. --- .../sweep_utils/conv_transpose2d_common.py | 4 +- .../operations/test_conv_transpose2d.py | 4 +- ttnn/ttnn/operations/transpose_conv2d.py | 79 +++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 ttnn/ttnn/operations/transpose_conv2d.py diff --git a/tests/sweep_framework/sweep_utils/conv_transpose2d_common.py b/tests/sweep_framework/sweep_utils/conv_transpose2d_common.py index 8e3c0e58af22..b4a8f29b2623 100644 --- a/tests/sweep_framework/sweep_utils/conv_transpose2d_common.py +++ b/tests/sweep_framework/sweep_utils/conv_transpose2d_common.py @@ -140,7 +140,7 @@ def run_full( {ttnn.CoreRange(core_grid[0], core_grid[1]), ttnn.CoreRange(core_grid[2], core_grid[3])} ) start_time = start_measuring_time() - [tt_output_tensor_on_device, out_height, out_width, weights_device, bias_device] = ttnn.conv_transpose2d( + [tt_output_tensor_on_device, [out_height, out_width], [weights_device, bias_device]] = ttnn.conv_transpose2d( input_tensor=tt_input_tensor, weight_tensor=tt_weight_tensor, in_channels=input_channels, @@ -157,6 +157,8 @@ def run_full( input_width=input_width, conv_config=conv_config, groups=groups, + return_output_dim=True, + return_weights_device=True, ) tt_output_tensor = ttnn.from_device(tt_output_tensor_on_device) diff --git a/tests/ttnn/unit_tests/operations/test_conv_transpose2d.py b/tests/ttnn/unit_tests/operations/test_conv_transpose2d.py index 63942ef0f8f6..9111418ae089 100644 --- a/tests/ttnn/unit_tests/operations/test_conv_transpose2d.py +++ b/tests/ttnn/unit_tests/operations/test_conv_transpose2d.py @@ -126,7 +126,7 @@ def run_conv_transpose2d( if config_override and "act_block_w_div" in config_override: conv_config.act_block_w_div = config_override["act_block_w_div"] - [tt_output_tensor_on_device, out_height, out_width, weights_device, bias_device] = ttnn.conv_transpose2d( + [tt_output_tensor_on_device, [out_height, out_width], [weights_device, bias_device]] = ttnn.conv_transpose2d( input_tensor=tt_input_tensor, weight_tensor=tt_weight_tensor, in_channels=input_channels, @@ -144,6 +144,8 @@ def run_conv_transpose2d( conv_config=conv_config, compute_config=compute_config, groups=groups, + return_output_dim=True, + return_weights_and_bias=True, ) logger.info(f"Conv2d Transpose Input = {(input_height, input_width)} Output = {out_height, out_width}") diff --git a/ttnn/ttnn/operations/transpose_conv2d.py b/ttnn/ttnn/operations/transpose_conv2d.py new file mode 100644 index 000000000000..0b1a927e347d --- /dev/null +++ b/ttnn/ttnn/operations/transpose_conv2d.py @@ -0,0 +1,79 @@ +# SPDX-FileCopyrightText: © 2023 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from loguru import logger + +from typing import Tuple, Union, Dict, Optional +import torch +import warnings +import math +import ttnn +from ttnn.device import ( + is_grayskull, + is_wormhole_b0, +) + +Conv2dConfig = ttnn._ttnn.operations.conv.Conv2dConfig + + +@ttnn.register_python_operation(name="ttnn.conv_transpose2d") +def conv_transpose2d( + *, + input_tensor: ttnn.Tensor, # may or may not be sharded + weight_tensor: ttnn.Tensor, + in_channels: int, + out_channels: int, + bias_tensor: ttnn.Tensor, + device: ttnn.Device, + kernel_size: Union[int, Tuple[int, int]], + stride: Union[int, Tuple[int, int]], + padding: Union[int, Tuple[int, int]], + output_padding: Union[int, Tuple[int, int]], + dilation: Union[int, Tuple[int, int]], + batch_size: int, + input_height: int, + input_width: int, + conv_config: Conv2dConfig = None, # config overrides by user + compute_config=None, # compute config overrides by user + groups: int = 1, + return_output_dim=False, + return_weights_and_bias=False, +) -> Tuple[ttnn.Tensor, int, int, ttnn.Tensor, ttnn.Tensor]: + ( + conv_output, + output_height, + output_width, + prepared_device_weight, + prepared_device_bias, + ) = ttnn._ttnn.operations.conv.conv_transpose2d( + input_tensor=input_tensor, + weight_tensor=weight_tensor, + in_channels=in_channels, + out_channels=out_channels, + bias_tensor=bias_tensor, + device=device, + kernel_size=kernel_size, + stride=stride, + padding=padding, + output_padding=output_padding, + dilation=dilation, + batch_size=batch_size, + input_height=input_height, + input_width=input_width, + conv_config=conv_config, + compute_config=compute_config, + groups=groups, + ) + + if return_output_dim and return_weights_and_bias: + return conv_output, [output_height, output_width], [prepared_device_weight, prepared_device_bias] + elif return_weights_and_bias: + return conv_output, [prepared_device_weight, prepared_device_bias] + elif return_output_dim: + return conv_output, [output_height, output_width] + else: + return conv_output + + +__all__ = [] From 26a041c2243c1b34162d6111c9e96d76d31968ac Mon Sep 17 00:00:00 2001 From: Amruth Sandhupatla Date: Wed, 18 Dec 2024 20:44:27 -0500 Subject: [PATCH 55/87] support missing cases for sweep tests (#15804) ### Ticket Link to Github Issue https://github.com/tenstorrent/tt-metal/issues/15549 ### Problem description Provide context for the problem. Support various combinations of missing test cases ### What's changed Describe the approach used to solve the problem. Summarize the changes made and its impact. Add all missing combinations of test case ### Checklist - [x] Post commit CI passes : https://github.com/tenstorrent/tt-metal/actions/runs/12396806647 - [x] Blackhole Post commit (if applicable) : https://github.com/tenstorrent/tt-metal/actions/runs/12396812182 - [x] Model regression CI testing passes (if applicable) : https://github.com/tenstorrent/tt-metal/actions/runs/12396814398 - [x] Device performance regression CI testing passes (if applicable) : https://github.com/tenstorrent/tt-metal/actions/runs/12396816791 - [x] New/Existing tests provide coverage for changes --------- Signed-off-by: Amruth Sandhupatla --- .../sweep_utils/reduction_common.py | 57 ++++++++++++ .../sweeps/reduction/argmax/argmax.py | 54 +++++++++-- .../reduction/backward/prod_bw/prod_bw.py | 70 +++++++++++--- .../sweeps/reduction/mean/mean.py | 63 ++++++++++++- .../sweep_framework/sweeps/reduction/prod.py | 54 ++++++++++- .../sweeps/reduction/std/std.py | 63 ++++++++++++- tests/sweep_framework/sweeps/reduction/sum.py | 91 +++++++++++++------ .../sweeps/reduction/topk/topk.py | 69 ++++++++++++-- .../sweeps/reduction/var/var.py | 66 +++++++++++++- 9 files changed, 512 insertions(+), 75 deletions(-) create mode 100644 tests/sweep_framework/sweep_utils/reduction_common.py diff --git a/tests/sweep_framework/sweep_utils/reduction_common.py b/tests/sweep_framework/sweep_utils/reduction_common.py new file mode 100644 index 000000000000..e110a9ec8fc5 --- /dev/null +++ b/tests/sweep_framework/sweep_utils/reduction_common.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. + +# SPDX-License-Identifier: Apache-2.0 + +from typing import Optional, Tuple +from functools import partial + +import torch +import random +import ttnn +from tests.sweep_framework.sweep_utils.utils import gen_shapes +from tests.tt_eager.python_api_testing.sweep_tests.generation_funcs import gen_func_with_cast_tt + +from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time +from models.utility_functions import torch_random + +# Override the default timeout in seconds for hang detection. +TIMEOUT = 30 + + +def run_sum( + input_shape, + dim, + keepdim, + input_a_dtype, + input_a_layout, + input_a_memory_config, + output_memory_config, + device, +) -> list: + data_seed = random.randint(0, 20000000) + torch.manual_seed(data_seed) + + torch_input_tensor_a = gen_func_with_cast_tt( + partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype + )(input_shape) + + dim = dim % len(input_shape) + + torch_output_tensor = torch.sum(torch_input_tensor_a, dim=dim, keepdim=keepdim) + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, + dtype=input_a_dtype, + layout=input_a_layout, + device=device, + memory_config=input_a_memory_config, + ) + + start_time = start_measuring_time() + result = ttnn.sum(input_tensor_a, dim=dim, memory_config=output_memory_config) + output_tensor = ttnn.to_torch(result) + e2e_perf = stop_measuring_time(start_time) + + pcc = check_with_pcc(torch_output_tensor, output_tensor, 0.999) + # print(f"input_shape {input_shape} pcc {pcc}") + return [pcc, e2e_perf] diff --git a/tests/sweep_framework/sweeps/reduction/argmax/argmax.py b/tests/sweep_framework/sweeps/reduction/argmax/argmax.py index 913d4178865d..573f5d5733fe 100644 --- a/tests/sweep_framework/sweeps/reduction/argmax/argmax.py +++ b/tests/sweep_framework/sweeps/reduction/argmax/argmax.py @@ -27,10 +27,38 @@ parameters = { "nightly": { "input_shape": gen_shapes([1, 1, 1, 1], [2, 6, 128, 128], [1, 1, 1, 1], 32) + + gen_shapes([1, 1, 1, 1], [2, 9, 167, 128], [1, 1, 1, 1], 32) + + gen_shapes([1, 1, 1, 1], [2, 6, 69, 129], [1, 1, 1, 1], 15) + gen_shapes([1, 1, 1], [6, 128, 128], [1, 1, 1], 32) - + gen_shapes([1, 1], [128, 128], [1, 1], 32), - "dim": [0, 1, 2, 3, None], - "input_a_dtype": [ttnn.bfloat16, ttnn.bfloat8_b], + + gen_shapes([1, 1, 1], [6, 128, 128], [1, 2, 3], 3) + + gen_shapes([1, 1, 1], [6, 127, 257], [1, 1, 1], 16) + + gen_shapes([1, 1], [128, 128], [1, 1], 32) + + gen_shapes([1, 1], [8, 100], [2, 3], 7) + + gen_shapes([1, 1], [255, 255], [1, 1], 4) + + gen_shapes([1], [128], [1], 32) + + gen_shapes([1], [128], [1], 7) + + gen_shapes([1], [250], [3], 4), + "dim": [ + 0, + 1, + 2, + 3, + None, + [0, 1], + [0, 2], + [0, 3], + [1, 2], + [1, 3], + [2, 3], + [0, 1, 2], + [0, 1, 3], + [0, 1, 3], + [0, 2, 3], + [1, 2, 3], + [0, 1, 2, 3], + ], + "keepdim": [True, False], + "input_a_dtype": [ttnn.float32, ttnn.bfloat16, ttnn.bfloat8_b], "input_layout": [ttnn.ROW_MAJOR_LAYOUT, ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], @@ -53,8 +81,21 @@ def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: return True, "Absolute value of dim must be less or equal than the rank of input tensor" if test_vector["input_layout"] == ttnn.TILE_LAYOUT: return True, "Tiled layout not supported" - if test_vector["input_layout"] == ttnn.ROW_MAJOR_LAYOUT and test_vector["input_a_dtype"] == ttnn.bfloat8_b: - return True, "bfloat8_b is only supported on tiled layout" + if test_vector["input_a_dtype"] != ttnn.bfloat16: + return True, "Only BFLOAT16 is supported for inputs!" + if test_vector["input_layout"] == ttnn.ROW_MAJOR_LAYOUT and not ( + test_vector["input_a_dtype"] == ttnn.float32 or test_vector["input_a_dtype"] == ttnn.bfloat16 + ): + return True, "Row major is only supported for fp32 & fp16" + if not test_vector["keepdim"]: + return True, "keepdim = false is not supported" + + device = ttnn.open_device(device_id=0) + if test_vector["input_a_dtype"] == ttnn.float32 and ttnn.device.is_grayskull(device): + return True, "Dest Fp32 mode is not supported for arch grayskull" + ttnn.close_device(device) + del device + return False, None @@ -65,6 +106,7 @@ def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: def run( input_shape, dim, + keepdim, input_a_dtype, input_layout, input_a_memory_config, @@ -83,7 +125,7 @@ def run( )(input_shape) golden_function = ttnn.get_golden_function(ttnn.argmax) - torch_output_tensor = golden_function(torch_input_tensor_a, dim=dim) + torch_output_tensor = golden_function(torch_input_tensor_a, dim=dim, keepdim=keepdim) input_tensor_a = ttnn.from_torch( torch_input_tensor_a, diff --git a/tests/sweep_framework/sweeps/reduction/backward/prod_bw/prod_bw.py b/tests/sweep_framework/sweeps/reduction/backward/prod_bw/prod_bw.py index 3c60e10d1486..6273d857e592 100644 --- a/tests/sweep_framework/sweeps/reduction/backward/prod_bw/prod_bw.py +++ b/tests/sweep_framework/sweeps/reduction/backward/prod_bw/prod_bw.py @@ -27,11 +27,41 @@ parameters = { "xfail": { "input_shape": gen_shapes([1, 1, 32, 32], [6, 12, 256, 256], [1, 1, 32, 32], 16) - + gen_shapes([1, 1, 1, 1], [6, 12, 256, 256], [1, 1, 1, 1], 2), - "dim": [0, 1, 2, 3], - "grad_dtype": [ttnn.bfloat16, ttnn.bfloat8_b], - "input_a_dtype": [ttnn.bfloat16], - "input_layout": [ttnn.TILE_LAYOUT], + + gen_shapes([1, 1, 1, 1], [6, 12, 256, 256], [1, 1, 1, 1], 2) + + gen_shapes([3, 4, 5, 6], [6, 12, 256, 256], [7, 8, 9, 10], 2) + + gen_shapes([1, 1, 1, 1], [6, 12, 187, 188], [1, 1, 1, 1], 7) + + gen_shapes([1, 32, 64], [6, 48, 128], [1, 1, 1], 2) + + gen_shapes([1, 32, 64], [6, 77, 128], [1, 1, 1], 7) + + gen_shapes([1, 32, 64], [6, 10222, 1023], [1, 1, 1], 8) + + gen_shapes([1, 1], [6, 6], [1, 1], 2) + + gen_shapes([1, 1], [7, 7], [1, 2], 3) + + gen_shapes([1, 1], [8, 8], [1, 3], 4) + + gen_shapes([1], [4], [1], 2) + + gen_shapes([1], [14], [11], 12) + + gen_shapes([1], [24], [21], 22), + "dim": [ + 0, + 1, + 2, + 3, + None, + [0, 1], + [0, 2], + [0, 3], + [1, 2], + [1, 3], + [2, 3], + [0, 1, 2], + [0, 1, 3], + [0, 1, 3], + [0, 2, 3], + [1, 2, 3], + [0, 1, 2, 3], + ], + "keepdim": [True, False], + "grad_dtype": [ttnn.float32, ttnn.bfloat16, ttnn.bfloat8_b], + "input_a_dtype": [ttnn.bfloat16, ttnn.bfloat8_b], + "input_layout": [ttnn.ROW_MAJOR_LAYOUT, ttnn.TILE_LAYOUT], "grad_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], @@ -45,10 +75,26 @@ def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: if test_vector["input_layout"] == ttnn.ROW_MAJOR_LAYOUT: return True, "Unary operation requires tensor to be in Tile layout when working with non-sharded input tensor" - if test_vector["input_layout"] == ttnn.ROW_MAJOR_LAYOUT and ( - test_vector["grad_dtype"] == ttnn.bfloat8_b or test_vector["input_a_dtype"] == ttnn.bfloat8_b - ): - return True, "bfloat8_b is only supported on tiled layout" + if test_vector["input_layout"] == ttnn.ROW_MAJOR_LAYOUT: + if (test_vector["input_a_dtype"] == ttnn.float32 and test_vector["grad_dtype"] == ttnn.float32) or ( + test_vector["input_a_dtype"] == ttnn.bfloat16 or test_vector["grad_dtype"] == ttnn.bfloat16 + ): + return False, None + else: + return True, "Row major is only supported for fp32 & fp16" + if not test_vector["keepdim"]: + return True, "keepdim = false is not supported" + if not isinstance(test_vector["dim"], int): + return True, "dim can only be integer value" + + device = ttnn.open_device(device_id=0) + if ( + test_vector["input_a_dtype"] == ttnn.float32 or test_vector["grad_dtype"] == ttnn.float32 + ) and ttnn.device.is_grayskull(device): + return True, "Dest Fp32 mode is not supported for arch grayskull" + ttnn.close_device(device) + del device + return False, None @@ -59,6 +105,7 @@ def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: def run( input_shape, dim, + keepdim, grad_dtype, input_a_dtype, input_layout, @@ -77,10 +124,7 @@ def run( torch_input_tensor_a.requires_grad = True torch_input_tensor_a.retain_grad() - max_dim = len(input_shape) - 1 - dim = random.randint(-max_dim - 1, max_dim) - - intermediate_result = torch.prod(torch_input_tensor_a, dim=dim, keepdim=True) + intermediate_result = torch.prod(torch_input_tensor_a, dim=dim, keepdim=keepdim) torch_grad_tensor = gen_func_with_cast_tt( partial(torch_random, low=-100, high=100, dtype=torch.float32), grad_dtype )(intermediate_result.shape) diff --git a/tests/sweep_framework/sweeps/reduction/mean/mean.py b/tests/sweep_framework/sweeps/reduction/mean/mean.py index b2c93e0c0d8d..c5662275dbb8 100644 --- a/tests/sweep_framework/sweeps/reduction/mean/mean.py +++ b/tests/sweep_framework/sweeps/reduction/mean/mean.py @@ -27,21 +27,74 @@ # Developers can create their own generator functions and pass them to the parameters as inputs. parameters = { "nightly": { - "input_shape": gen_shapes([1, 1, 1, 1], [2, 6, 128, 128], [1, 1, 32, 32], 32), - "input_a_dtype": [ttnn.bfloat16, ttnn.bfloat8_b], - "input_layout": [ttnn.TILE_LAYOUT], + "input_shape": gen_shapes([1, 1, 1, 1], [2, 6, 128, 128], [1, 1, 32, 32], 32) + + gen_shapes([1, 1, 1, 1], [2, 6, 110, 130], [1, 1, 32, 32], 31) + + gen_shapes([1, 1, 1, 1], [2, 6, 120, 140], [1, 1, 32, 32], 17) + + gen_shapes([1, 1, 1], [2, 6, 128], [1, 1, 32], 8) + + gen_shapes([1, 1, 1], [2, 7, 100], [1, 1, 64], 16) + + gen_shapes([1, 1, 1], [2, 8, 255], [1, 1, 122], 17) + + gen_shapes([1, 1], [2, 26], [1, 11], 2) + + gen_shapes([1, 1], [2, 36], [1, 12], 3) + + gen_shapes([1, 1], [2, 46], [1, 13], 4) + + gen_shapes([1], [2, 6], [1, 1], 2) + + gen_shapes([1], [2, 6], [1, 1], 2) + + gen_shapes([1], [10, 10], [2, 3], 2), + "dim": [ + 0, + 1, + 2, + 3, + None, + [0, 1], + [0, 2], + [0, 3], + [1, 2], + [1, 3], + [2, 3], + [0, 1, 2], + [0, 1, 3], + [0, 1, 3], + [0, 2, 3], + [1, 2, 3], + [0, 1, 2, 3], + ], + "keepdim": [True, False], + "input_a_dtype": [ttnn.float32, ttnn.bfloat16, ttnn.bfloat8_b], + "input_layout": [ttnn.ROW_MAJOR_LAYOUT, ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], }, } +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_layout"] == ttnn.ROW_MAJOR_LAYOUT and not ( + test_vector["input_a_dtype"] == ttnn.float32 or test_vector["input_a_dtype"] == ttnn.bfloat16 + ): + return True, "Row major is only supported for fp32 & fp16" + if not test_vector["keepdim"]: + return True, "keepdim = false is not supported" + + device = ttnn.open_device(device_id=0) + if test_vector["input_a_dtype"] == ttnn.float32 and ttnn.device.is_grayskull(device): + return True, "Dest Fp32 mode is not supported for arch grayskull" + ttnn.close_device(device) + del device + + return False, None + + # This is the run instructions for the test, defined by the developer. # The run function must take the above-defined parameters as inputs. # The runner will call this run function with each test vector, and the returned results from this function will be stored. # If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. def run( input_shape, + dim, + keepdim, input_a_dtype, input_layout, input_a_memory_config, @@ -60,7 +113,7 @@ def run( )(input_shape) golden_function = ttnn.get_golden_function(ttnn.mean) - torch_output_tensor = golden_function(torch_input_tensor_a, dim=-1, keepdim=True) + torch_output_tensor = golden_function(torch_input_tensor_a, dim=dim, keepdim=keepdim) input_tensor_a = ttnn.from_torch( torch_input_tensor_a, @@ -71,7 +124,7 @@ def run( ) start_time = start_measuring_time() - output_tensor = ttnn.mean(input_tensor_a, dim=-1, memory_config=output_memory_config) + output_tensor = ttnn.mean(input_tensor_a, dim=dim, memory_config=output_memory_config) output_tensor = ttnn.to_torch(output_tensor) e2e_perf = stop_measuring_time(start_time) diff --git a/tests/sweep_framework/sweeps/reduction/prod.py b/tests/sweep_framework/sweeps/reduction/prod.py index e5a098dcffa5..39add1f91fd6 100644 --- a/tests/sweep_framework/sweeps/reduction/prod.py +++ b/tests/sweep_framework/sweeps/reduction/prod.py @@ -30,16 +30,59 @@ + gen_shapes([32, 32], [256, 256], [32, 32], 2) + gen_shapes([1, 1, 1, 1], [6, 12, 256, 256], [1, 1, 1, 1], 2) + gen_shapes([1, 1, 1], [12, 256, 256], [1, 1, 1], 2) - + gen_shapes([1, 1], [256, 256], [1, 1], 2), - "dim": [0, 1, 2, 3], - "input_a_dtype": [ttnn.bfloat16, ttnn.bfloat8_b], - "input_a_layout": [ttnn.TILE_LAYOUT], + + gen_shapes([1, 1], [256, 256], [1, 1], 2) + + gen_shapes([1], [256], [1], 8) + + gen_shapes([1, 1, 1, 1], [6, 12, 200, 255], [1, 1, 1, 1], 5) + + gen_shapes([1, 1, 1], [12, 555, 128], [1, 1, 1], 4) + + gen_shapes([1, 1], [32, 32], [1, 1], 32), + "dim": [ + 0, + 1, + 2, + 3, + None, + [0, 1], + [0, 2], + [0, 3], + [1, 2], + [1, 3], + [2, 3], + [0, 1, 2], + [0, 1, 3], + [0, 1, 3], + [0, 2, 3], + [1, 2, 3], + [0, 1, 2, 3], + ], + "keepdim": [True, False], + "input_a_dtype": [ttnn.float32, ttnn.bfloat16, ttnn.bfloat8_b], + "input_a_layout": [ttnn.ROW_MAJOR_LAYOUT, ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], }, } +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT and not ( + test_vector["input_a_dtype"] == ttnn.float32 or test_vector["input_a_dtype"] == ttnn.bfloat16 + ): + return True, "Row major is only supported for fp32 & fp16" + if not test_vector["keepdim"]: + return True, "keepdim = false is not supported" + + device = ttnn.open_device(device_id=0) + if test_vector["input_a_dtype"] == ttnn.float32 and ttnn.device.is_grayskull(device): + return True, "Dest Fp32 mode is not supported for arch grayskull" + ttnn.close_device(device) + del device + + return False, None + + # This is the run instructions for the test, defined by the developer. # The run function must take the above-defined parameters as inputs. # The runner will call this run function with each test vector, and the returned results from this function will be stored. @@ -47,6 +90,7 @@ def run( input_shape, dim, + keepdim, input_a_dtype, input_a_layout, input_a_memory_config, @@ -63,7 +107,7 @@ def run( dim = dim % len(input_shape) - torch_output_tensor = torch.prod(torch_input_tensor_a, dim=dim, keepdim=True) + torch_output_tensor = torch.prod(torch_input_tensor_a, dim=dim, keepdim=keepdim) input_tensor_a = ttnn.from_torch( torch_input_tensor_a, diff --git a/tests/sweep_framework/sweeps/reduction/std/std.py b/tests/sweep_framework/sweeps/reduction/std/std.py index a39657d49e04..4f95723031bf 100644 --- a/tests/sweep_framework/sweeps/reduction/std/std.py +++ b/tests/sweep_framework/sweeps/reduction/std/std.py @@ -27,21 +27,74 @@ # Developers can create their own generator functions and pass them to the parameters as inputs. parameters = { "xfail": { - "input_shape": gen_shapes([1, 1, 1, 1], [2, 6, 128, 128], [1, 1, 32, 32], 32), - "input_a_dtype": [ttnn.bfloat16, ttnn.bfloat8_b], - "input_layout": [ttnn.TILE_LAYOUT], + "input_shape": gen_shapes([1, 1, 1, 1], [2, 6, 128, 128], [1, 1, 32, 32], 32) + + gen_shapes([1, 1, 1, 1], [2, 6, 254, 129], [1, 1, 20, 33], 33) + + gen_shapes([1, 1, 1, 1], [2, 7, 255, 130], [1, 1, 21, 34], 34) + + gen_shapes([1, 1, 1], [2, 6, 254], [1, 1, 32], 8) + + gen_shapes([1, 1, 1], [4, 12, 255], [1, 1, 32], 16) + + gen_shapes([1, 1, 1], [8, 18, 256], [1, 1, 32], 32) + + gen_shapes([1, 1], [2, 6], [1, 1], 2) + + gen_shapes([1, 1], [3, 7], [1, 1], 2) + + gen_shapes([1, 1], [4, 8], [1, 1], 2) + + gen_shapes([1], [32], [1], 4) + + gen_shapes([1], [33], [1], 5) + + gen_shapes([1], [34], [1], 6), + "dim": [ + 0, + 1, + 2, + 3, + None, + [0, 1], + [0, 2], + [0, 3], + [1, 2], + [1, 3], + [2, 3], + [0, 1, 2], + [0, 1, 3], + [0, 1, 3], + [0, 2, 3], + [1, 2, 3], + [0, 1, 2, 3], + ], + "keepdim": [True, False], + "input_a_dtype": [ttnn.float32, ttnn.bfloat16, ttnn.bfloat8_b], + "input_layout": [ttnn.ROW_MAJOR_LAYOUT, ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], }, } +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_layout"] == ttnn.ROW_MAJOR_LAYOUT and not ( + test_vector["input_a_dtype"] == ttnn.float32 or test_vector["input_a_dtype"] == ttnn.bfloat16 + ): + return True, "Row major is only supported for fp32 & fp16" + if not test_vector["keepdim"]: + return True, "keepdim = false is not supported" + + device = ttnn.open_device(device_id=0) + if test_vector["input_a_dtype"] == ttnn.float32 and ttnn.device.is_grayskull(device): + return True, "Dest Fp32 mode is not supported for arch grayskull" + ttnn.close_device(device) + del device + + return False, None + + # This is the run instructions for the test, defined by the developer. # The run function must take the above-defined parameters as inputs. # The runner will call this run function with each test vector, and the returned results from this function will be stored. # If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. def run( input_shape, + dim, + keepdim, input_a_dtype, input_layout, input_a_memory_config, @@ -60,7 +113,7 @@ def run( )(input_shape) golden_function = ttnn.get_golden_function(ttnn.std) - torch_output_tensor = golden_function(torch_input_tensor_a, dim=-1, keepdim=True) + torch_output_tensor = golden_function(torch_input_tensor_a, dim=dim, keepdim=keepdim) input_tensor_a = ttnn.from_torch( torch_input_tensor_a, @@ -71,7 +124,7 @@ def run( ) start_time = start_measuring_time() - output_tensor = ttnn.std(input_tensor_a, dim=-1, memory_config=output_memory_config) + output_tensor = ttnn.std(input_tensor_a, dim=dim, memory_config=output_memory_config) output_tensor = ttnn.to_torch(output_tensor) e2e_perf = stop_measuring_time(start_time) diff --git a/tests/sweep_framework/sweeps/reduction/sum.py b/tests/sweep_framework/sweeps/reduction/sum.py index cddaa82be784..7e89c43be052 100644 --- a/tests/sweep_framework/sweeps/reduction/sum.py +++ b/tests/sweep_framework/sweeps/reduction/sum.py @@ -13,6 +13,7 @@ from tests.ttnn.utils_for_testing import check_with_pcc, start_measuring_time, stop_measuring_time from models.utility_functions import torch_random +from tests.sweep_framework.sweep_utils.reduction_common import run_sum # Override the default timeout in seconds for hang detection. TIMEOUT = 30 @@ -30,16 +31,59 @@ + gen_shapes([32, 32], [256, 256], [32, 32], 2) + gen_shapes([1, 1, 1, 1], [6, 12, 256, 256], [1, 1, 1, 1], 2) + gen_shapes([1, 1, 1], [12, 256, 256], [1, 1, 1], 2) - + gen_shapes([1, 1], [256, 256], [1, 1], 2), - "dim": [0, 1, 2, 3], - "input_a_dtype": [ttnn.bfloat16, ttnn.bfloat8_b], - "input_a_layout": [ttnn.TILE_LAYOUT], + + gen_shapes([1, 1], [256, 256], [1, 1], 2) + + gen_shapes([1, 1, 1, 1], [6, 12, 100, 100], [1, 1, 1, 1], 2) + + gen_shapes([1, 1, 1], [12, 128, 128], [1, 1, 1], 7) + + gen_shapes([1, 1], [11, 11], [1, 1], 2) + + gen_shapes([1], [256], [16], 2), + "dim": [ + 0, + 1, + 2, + 3, + None, + [0, 1], + [0, 2], + [0, 3], + [1, 2], + [1, 3], + [2, 3], + [0, 1, 2], + [0, 1, 3], + [0, 1, 3], + [0, 2, 3], + [1, 2, 3], + [0, 1, 2, 3], + ], + "keepdim": [True, False], + "input_a_dtype": [ttnn.float32, ttnn.bfloat16, ttnn.bfloat8_b], + "input_a_layout": [ttnn.ROW_MAJOR_LAYOUT, ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], }, } +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_a_layout"] == ttnn.ROW_MAJOR_LAYOUT and not ( + test_vector["input_a_dtype"] == ttnn.float32 or test_vector["input_a_dtype"] == ttnn.bfloat16 + ): + return True, "Row major is only supported for fp32 & fp16" + if not test_vector["keepdim"]: + return True, "keepdim = false is not supported" + + device = ttnn.open_device(device_id=0) + if test_vector["input_a_dtype"] == ttnn.float32 and ttnn.device.is_grayskull(device): + return True, "Dest Fp32 mode is not supported for arch grayskull" + ttnn.close_device(device) + del device + + return False, None + + # This is the run instructions for the test, defined by the developer. # The run function must take the above-defined parameters as inputs. # The runner will call this run function with each test vector, and the returned results from this function will be stored. @@ -47,6 +91,7 @@ def run( input_shape, dim, + keepdim, input_a_dtype, input_a_layout, input_a_memory_config, @@ -54,31 +99,23 @@ def run( *, device, ) -> list: - data_seed = random.randint(0, 20000000) - torch.manual_seed(data_seed) + return run_sum( + input_shape, dim, keepdim, input_a_dtype, input_a_layout, input_a_memory_config, output_memory_config, device + ) - torch_input_tensor_a = gen_func_with_cast_tt( - partial(torch_random, low=-100, high=100, dtype=torch.float32), input_a_dtype - )(input_shape) - dim = dim % len(input_shape) - # print(f"dim {dim} input_shape {input_shape} input_a_dtype {input_a_dtype}") +import pytest - torch_output_tensor = torch.sum(torch_input_tensor_a, dim=dim, keepdim=True) - input_tensor_a = ttnn.from_torch( - torch_input_tensor_a, - dtype=input_a_dtype, - layout=input_a_layout, - device=device, - memory_config=input_a_memory_config, +@pytest.mark.parametrize( + "input_shape, dim, input_a_dtype, input_a_layout, input_a_memory_config, output_memory_config", + [ + ([7, 32, 4, 96], 3, True, ttnn.float32, ttnn.TILE_LAYOUT, ttnn.L1_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG), + ], +) +def test_reduction_sum_localrun_fail_only( + device, input_shape, dim, keepdim, input_a_dtype, input_a_layout, input_a_memory_config, output_memory_config +): + run_sum( + input_shape, dim, keepdim, input_a_dtype, input_a_layout, input_a_memory_config, output_memory_config, device ) - - start_time = start_measuring_time() - result = ttnn.sum(input_tensor_a, dim=dim, memory_config=output_memory_config) - output_tensor = ttnn.to_torch(result) - e2e_perf = stop_measuring_time(start_time) - - pcc = check_with_pcc(torch_output_tensor, output_tensor, 0.999) - # print(f"input_shape {input_shape} pcc {pcc}") - return [pcc, e2e_perf] diff --git a/tests/sweep_framework/sweeps/reduction/topk/topk.py b/tests/sweep_framework/sweeps/reduction/topk/topk.py index e38a9134e5c5..d65b9e498d96 100644 --- a/tests/sweep_framework/sweeps/reduction/topk/topk.py +++ b/tests/sweep_framework/sweeps/reduction/topk/topk.py @@ -28,12 +28,36 @@ parameters = { "nightly": { "input_shape": gen_shapes([1, 1, 32, 64], [2, 6, 128, 128], [1, 1, 32, 64], 64) - + gen_shapes([1, 32, 64], [12, 256, 1024], [1, 32, 64], 8) - + gen_shapes([32, 64], [256, 1024], [32, 64], 8), - "dim": [-1, -2, -3, -4], - "largest": [True], + + gen_shapes([1, 1, 33, 65], [2, 6, 127, 129], [1, 1, 33, 63], 128) + + gen_shapes([1, 1, 31, 63], [2, 6, 128, 128], [1, 1, 32, 64], 7) + + gen_shapes([1, 32, 64], [12, 200, 1025], [1, 32, 64], 8) + + gen_shapes([1, 32, 64], [12, 256, 1023], [1, 32, 164], 9) + + gen_shapes([1, 7, 20], [12, 300, 1024], [1, 32, 64], 10) + + gen_shapes([32, 64], [256, 1024], [32, 64], 8) + + gen_shapes([32, 6], [256, 404], [32, 264], 18) + + gen_shapes([32, 17], [256, 124], [32, 624], 28), + "dim": [ + 0, + 1, + 2, + 3, + None, + [0, 1], + [0, 2], + [0, 3], + [1, 2], + [1, 3], + [2, 3], + [0, 1, 2], + [0, 1, 3], + [0, 1, 3], + [0, 2, 3], + [1, 2, 3], + [0, 1, 2, 3], + ], + "largest": [True, False], "k": [32], # only k = 32 is supported for now - "input_a_dtype": [ttnn.bfloat16, ttnn.bfloat8_b], + "input_a_dtype": [ttnn.float32, ttnn.bfloat16, ttnn.bfloat8_b], "input_layout": [ttnn.TILE_LAYOUT, ttnn.ROW_MAJOR_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], @@ -42,10 +66,28 @@ "input_shape": gen_shapes([1, 1, 32, 64], [6, 12, 256, 1024], [1, 1, 32, 64], 64) + gen_shapes([1, 32, 64], [12, 256, 1024], [1, 32, 64], 8) + gen_shapes([32, 64], [256, 1024], [32, 64], 8), - "dim": [-1, -2, -3, -4], + "dim": [ + 0, + 1, + 2, + 3, + None, + [0, 1], + [0, 2], + [0, 3], + [1, 2], + [1, 3], + [2, 3], + [0, 1, 2], + [0, 1, 3], + [0, 1, 3], + [0, 2, 3], + [1, 2, 3], + [0, 1, 2, 3], + ], "largest": [True, False], "k": [32], - "input_a_dtype": [ttnn.bfloat16, ttnn.bfloat8_b], + "input_a_dtype": [ttnn.float32, ttnn.bfloat16, ttnn.bfloat8_b], "input_layout": [ttnn.TILE_LAYOUT, ttnn.ROW_MAJOR_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], @@ -65,8 +107,17 @@ def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: return True, "Absolute value of dim must be less or equal than the rank of input tensor" if test_vector["input_layout"] == ttnn.ROW_MAJOR_LAYOUT: return True, "Unary operation requires tensor to be in Tile layout when working with non-sharded input tensor" - if test_vector["input_layout"] == ttnn.ROW_MAJOR_LAYOUT and test_vector["input_a_dtype"] == ttnn.bfloat8_b: - return True, "bfloat8_b is only supported on tiled layout" + if test_vector["input_layout"] == ttnn.ROW_MAJOR_LAYOUT and not ( + test_vector["input_a_dtype"] == ttnn.float32 or test_vector["input_a_dtype"] == ttnn.bfloat16 + ): + return True, "Row major is only supported for fp32 & fp16" + + device = ttnn.open_device(device_id=0) + if test_vector["input_a_dtype"] == ttnn.float32 and ttnn.device.is_grayskull(device): + return True, "Dest Fp32 mode is not supported for arch grayskull" + ttnn.close_device(device) + del device + return False, None diff --git a/tests/sweep_framework/sweeps/reduction/var/var.py b/tests/sweep_framework/sweeps/reduction/var/var.py index 27f5dc70157d..ee158d188698 100644 --- a/tests/sweep_framework/sweeps/reduction/var/var.py +++ b/tests/sweep_framework/sweeps/reduction/var/var.py @@ -27,21 +27,77 @@ # Developers can create their own generator functions and pass them to the parameters as inputs. parameters = { "xfail": { - "input_shape": gen_shapes([1, 1, 1, 1], [2, 6, 128, 128], [1, 1, 32, 32], 32), - "input_a_dtype": [ttnn.bfloat16, ttnn.bfloat8_b], - "input_layout": [ttnn.TILE_LAYOUT], + "input_shape": gen_shapes([1, 1, 1, 1], [2, 6, 128, 128], [1, 1, 32, 32], 32) + + gen_shapes([1, 1, 1, 1], [2, 6, 128, 255], [1, 1, 132, 132], 42) + + gen_shapes([1, 1, 1, 1], [2, 6, 128, 255], [1, 1, 132, 132], 52) + + gen_shapes([1, 1, 1], [2, 6, 128], [1, 1, 32], 32) + + gen_shapes([1, 1, 1], [2, 16, 129], [1, 1, 64], 16) + + gen_shapes([1, 1, 1], [2, 26, 130], [1, 1, 128], 8) + + gen_shapes([1, 1], [2, 6], [1, 1], 4) + + gen_shapes([1, 1], [2, 16], [1, 1], 5) + + gen_shapes([1, 1], [2, 26], [1, 1], 6) + + gen_shapes([1], [8], [1], 2) + + gen_shapes([1], [18], [1], 2) + + gen_shapes([1], [28], [1], 2), + "dim": [ + 0, + 1, + 2, + 3, + None, + [0, 1], + [0, 2], + [0, 3], + [1, 2], + [1, 3], + [2, 3], + [0, 1, 2], + [0, 1, 3], + [0, 1, 3], + [0, 2, 3], + [1, 2, 3], + [0, 1, 2, 3], + ], + "keepdim": [True, False], + "input_a_dtype": [ttnn.float32, ttnn.bfloat16, ttnn.bfloat8_b], + "input_layout": [ttnn.ROW_MAJOR_LAYOUT, ttnn.TILE_LAYOUT], "input_a_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], "output_memory_config": [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG], }, } +# Invalidate vector is called during the generation phase where each vector will be passed in. +# If invalidated, the vector will still be stored but will be skipped. +# Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid. +def invalidate_vector(test_vector) -> Tuple[bool, Optional[str]]: + if test_vector["input_layout"] == ttnn.ROW_MAJOR_LAYOUT and not ( + test_vector["input_a_dtype"] == ttnn.float32 or test_vector["input_a_dtype"] == ttnn.bfloat16 + ): + return True, "Row major is only supported for fp32 & fp16" + if not test_vector["keepdim"]: + return True, "keepdim = false is not supported" + + if len(test_vector["input_shape"]) < 2: + return True, "For var with scalar(1-D) input, degrees of freedom will be <= 0." + + device = ttnn.open_device(device_id=0) + if test_vector["input_a_dtype"] == ttnn.float32 and ttnn.device.is_grayskull(device): + return True, "Dest Fp32 mode is not supported for arch grayskull" + ttnn.close_device(device) + del device + + return False, None + + # This is the run instructions for the test, defined by the developer. # The run function must take the above-defined parameters as inputs. # The runner will call this run function with each test vector, and the returned results from this function will be stored. # If you defined a mesh_device_fixture above, the object you yielded will be passed into this function as 'device'. Otherwise, it will be the default ttnn device opened by the infra. def run( input_shape, + dim, + keepdim, input_a_dtype, input_layout, input_a_memory_config, @@ -60,7 +116,7 @@ def run( )(input_shape) golden_function = ttnn.get_golden_function(ttnn.var) - torch_output_tensor = golden_function(torch_input_tensor_a, dim=-1, keepdim=True) + torch_output_tensor = golden_function(torch_input_tensor_a, dim=dim, keepdim=keepdim) input_tensor_a = ttnn.from_torch( torch_input_tensor_a, @@ -71,7 +127,7 @@ def run( ) start_time = start_measuring_time() - output_tensor = ttnn.var(input_tensor_a, dim=-1, memory_config=output_memory_config) + output_tensor = ttnn.var(input_tensor_a, dim=dim, memory_config=output_memory_config) output_tensor = ttnn.to_torch(output_tensor) e2e_perf = stop_measuring_time(start_time) From a8a812beba9230df1196fe9ac11bb7769f5f1d90 Mon Sep 17 00:00:00 2001 From: Kartik Paigwar <132708568+kpaigwar@users.noreply.github.com> Date: Wed, 18 Dec 2024 21:36:10 -0500 Subject: [PATCH 56/87] #0: added normalization details in the tech report (#15124) ### Problem description Added Distributed and Non-Distributed normalization details in LLM tech report. --- tech_reports/LLMs/llms.md | 149 +++++++++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 3 deletions(-) diff --git a/tech_reports/LLMs/llms.md b/tech_reports/LLMs/llms.md index 14d248e09698..e78a88af75b4 100644 --- a/tech_reports/LLMs/llms.md +++ b/tech_reports/LLMs/llms.md @@ -1,5 +1,6 @@ # LLMs in TT-NN -Authors: Mark O'Connor, Djordje Ivanovic, Jack (Xun) Cai + +Authors: Mark O'Connor, Djordje Ivanovic, Jack (Xun) Cai, Kartik Paigwar ## Contents - [LLMs in TT-NN](#llms-in-tt-nn) @@ -56,8 +57,150 @@ Other useful resources: - Iterative update system - When to use our fused op ### 2.3 Norm - - Replicated layernorm vs distributed layernorm - - Layernorm/rmsnorm weights in row major / wrapped around tile size trick + +Normalization is a critical operation in Large Language Models (LLMs), ensuring stable training and efficient inference. Two widely adopted normalization techniques in modern LLMs, **LayerNorm** and **RMSNorm**, are fully supported in TT-NN. + +#### Implementations of Normalization Operations + +TT-NN includes two primary implementations of normalization operations to handle diverse activation layouts efficiently: + +1. **Non-Distributed Norm** +2. **Distributed Norm** + + +#### 1. Non-Distributed Norm + +**Non-Distributed Norm** refers to the standard implementation of normalization operations applied to activations that are not distributed across multiple devices. This type of normalization is suitable for setups where the entire activation or embedding is available locally on a single device or is replicated identically across multiple devices in a data-parallel setup. This implementation supports both sharded and interleaved inputs. + +**Example: RMSNorm on Single Device (Decode Scenario)** + +```python +import torch +import ttnn + +def torch_rms_norm(x, gamma, eps): + return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + eps) * gamma + +batch, seq_len, embedding_dim = 32, 1, 8192 +torch_input = torch.randn((batch, seq_len, embedding_dim)) +torch_gamma = torch.randn((embedding_dim)) +torch_output = torch_rms_norm(torch_input, torch_gamma, eps=1e-5) + +# Reshape inputs/weights to 4D tensors +torch_input = torch_input.view(1, 1, batch, embedding_dim) # seq_len = 1 for decode +torch_gamma = torch_gamma.view(1, 1, 1, embedding_dim) + +# Convert tensors to TT-NN tensors +ttnn_input = ttnn.as_tensor( + torch_input, + device=device, + dtype=ttnn.bfloat16, + layout=ttnn.TILE_LAYOUT, + memory_config=ttnn.DRAM_MEMORY_CONFIG +) +ttnn_gamma = ttnn.as_tensor( + torch_gamma, + device=device, + dtype=ttnn.bfloat16, + layout=ttnn.TILE_LAYOUT, + memory_config=ttnn.DRAM_MEMORY_CONFIG +) + +# Perform RMSNorm +ttnn_output = ttnn.rms_norm(ttnn_input, epsilon=1e-5, weight=ttnn_gamma) +``` + +**Optimization for Efficient Weight Reads from DRAM** + +In above example, weights were traditionally pushed to device in **TILE layout**. But in this case, padding is required to match the TILE_HEIGHT. This padding increased memory footprint and reduced DRAM access efficiency. To address this, weights are now wrapped into **TILE_WIDTH** sticks and converted to **ROW_MAJOR_LAYOUT** without requiring any padding. This weight transformation doesn't have any overhead during runtime as its only performed once during initialization. + +```python +# Optimized Weight Layout for DRAM +torch_gamma = torch_gamma.view(1, 1, embedding_dim // TILE_WIDTH, TILE_WIDTH) +ttnn_gamma_rm = ttnn.as_tensor( + torch_gamma, + device=device, + dtype=ttnn.bfloat16, + layout=ttnn.ROW_MAJOR_LAYOUT, + memory_config=ttnn.DRAM_MEMORY_CONFIG +) +``` + + + + +#### 2. Distributed Norm + +The distributed implementation is designed for cases where activations are **sharded along the embedding dimension** across multiple devices. It ensures the correct computation of mean and variance across shards by leveraging cross-device communication. We provide support for both interleaved and width-sharded inputs. + +#### Steps to Perform Distributed Normalization on TT-Devices + +1. **Compute Local Statistics** + Each device computes the required statistics (e.g., \(E[x]\), \(E[x^2]\)) locally on its shard of the input tensor. + - For **RMSNorm**, only \(E[x^2]\) is required. + - For **LayerNorm**, both \(E[x]\) and \(E[x^2]\) are computed. + + ```python + tt_distributed_stats = ttnn.rms_norm_pre_all_gather(tt_distributed_input_tensor) + ``` + + - **Output**: A `stats` tensor of shape `[1, 1, batch, TILE_WIDTH * num_stats]`. + - **Note**: + - `num_stats=1` for RMSNorm. + - `num_stats=2` for LayerNorm. + - Only the first column of the stats tile contains meaningful data; the rest are padding. + +2. **Gather Statistics Across Devices** + The statistics are gathered from all devices along the specified dimension (`dim=3`) and replicated across the device mesh. + + ```python + tt_gathered_stats = ttnn.all_gather( + tt_distributed_stats, + dim=3, + num_links=1, + cluster_axis=1, + mesh_device=mesh_device, + memory_config=ttnn.DRAM_MEMORY_CONFIG, + topology=ttnn.Topology.Linear, + ) + ``` + + - **Output**: A tensor of shape `[1, 1, batch, TILE_WIDTH * num_stats * num_devices]`. + +3. **Global Normalization** + The gathered statistics are used to compute the global mean and variance, and normalization is performed on the sharded input. + + ```python + tt_distributed_output_tensor = ttnn.rms_norm_post_all_gather( + tt_distributed_input_tensor, + epsilon=eps, + weight=tt_distributed_weights, + program_config=sharded_program_config, + memory_config=ttnn.DRAM_MEMORY_CONFIG, + stats=tt_gathered_stats, + ) + ``` + - **Output**: A tensor of shape `[1, 1, batch, embedding_dim // num_devices]`. + + +#### Key Notes (Valid for Both Implementations): + +- **Interleaved Inputs**: + For interleaved inputs, the kernel parallelizes work across the sequence length (`seq_len`). + This makes it highly **optimal for prefill cases**, where the sequence length is large. + +- **Width-Sharded Inputs**: + For width-sharded inputs, the kernel splits the work across the embedding dimension. + This design is more **optimal for decode cases**, where the sequence length is typically `seq_len=1`. + + +#### References +- Non-Distributed Norm Op Code [[1]](https://github.com/tenstorrent/tt-metal/tree/main/ttnn/cpp/ttnn/operations/normalization/layernorm) [[2]](https://github.com/tenstorrent/tt-metal/tree/main/ttnn/cpp/ttnn/operations/normalization/rmsnorm) +- Distributed Norm Op Code [[3]](https://github.com/tenstorrent/tt-metal/tree/main/ttnn/cpp/ttnn/operations/normalization/layernorm_distributed) [[4]](https://github.com/tenstorrent/tt-metal/tree/main/ttnn/cpp/ttnn/operations/normalization/rmsnorm_distributed) +- Non-Distributed Norms Unit Tests [[5]](https://github.com/tenstorrent/tt-metal/blob/main/tests/tt_eager/python_api_testing/unit_testing/misc/test_layernorm_sharded.py) [[6]](https://github.com/tenstorrent/tt-metal/blob/main/tests/tt_eager/python_api_testing/unit_testing/misc/test_layernorm.py) +- Distributed Norms Unit Tests [[7]](https://github.com/tenstorrent/tt-metal/blob/main/tests/ttnn/unit_tests/operations/test_distributed_layernorm.py) [[8]](https://github.com/tenstorrent/tt-metal/blob/main/tests/ttnn/unit_tests/operations/test_distributed_layernorm_sharded.py) +- Distributed Norm in LLama3 [[9]](https://github.com/tenstorrent/tt-metal/blob/main/models/demos/llama3/tt/distributed_norm.py) + ### 2.4 Attention Attention in TT-NN is implemented in custom TT-NN kernels. In PyTorch, the attention op is usually implemented in the following way with 6 steps: From 444b0dcd057317039e5a20fe8509bf7dca33d8f8 Mon Sep 17 00:00:00 2001 From: Stanislav Minakov Date: Wed, 18 Dec 2024 23:39:01 -0800 Subject: [PATCH 57/87] Fix ttnn.from_torch for 0D/1D tensors with tile layout (#15882) ### Ticket https://github.com/tenstorrent/tt-metal/issues/15630 ### Problem description Since Shape/LegacyShape doesn't support different logical and padded ranks, we had to remove all usages of those classes on the way from pytorch to ttnn tensor. ### What's changed Major refactoring in `to_layout`, `pad` ops TensorLayout fixes for 0D/1D tensors ### Checklist - [x] [Post commit CI passes](https://github.com/tenstorrent/tt-metal/actions/runs/12398856356) - [ ] Blackhole Post commit (if applicable) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [ ] New/Existing tests provide coverage for changes --- .../tt_eager/ops/test_tilize_zero_padding.cpp | 2 +- ...test_tilize_zero_padding_channels_last.cpp | 2 +- .../ttnn/unit_tests/operations/test_matmul.py | 4 +- .../ttnn/unit_tests/test_to_and_from_torch.py | 19 ++ ttnn/cpp/pybind11/pytensor.cpp | 22 +- .../core/to_layout/to_layout_op.cpp | 265 ++++++++---------- .../core/work_split/work_split_tilize.hpp | 9 +- .../data_movement/concat/concat.cpp | 2 +- .../ttnn/operations/data_movement/pad/pad.cpp | 2 +- .../data_movement/reshape_view/reshape.cpp | 51 ++-- .../device/tilize_with_val_padding_op.cpp | 51 ++-- .../device/tilize_with_val_padding_op.hpp | 6 +- .../tilize_with_val_padding.cpp | 14 +- .../tilize_with_val_padding.hpp | 4 +- .../tilize_with_val_padding_pybind.hpp | 8 +- .../experimental/auto_format/auto_format.cpp | 3 +- .../moreh/moreh_helper_functions.cpp | 34 ++- ttnn/cpp/ttnn/tensor/layout/tensor_layout.cpp | 103 ++++--- ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp | 9 +- ttnn/cpp/ttnn/tensor/tensor.cpp | 45 ++- ttnn/cpp/ttnn/tensor/tensor.hpp | 9 +- ttnn/cpp/ttnn/tensor/tensor_impl.cpp | 135 +++++---- ttnn/cpp/ttnn/tensor/tensor_impl.hpp | 5 +- ttnn/cpp/ttnn/tensor/tensor_ops.cpp | 54 +--- ttnn/cpp/ttnn/tensor/tensor_ops.hpp | 2 +- ttnn/cpp/ttnn/tensor/types.hpp | 13 +- 26 files changed, 447 insertions(+), 426 deletions(-) diff --git a/tests/tt_eager/ops/test_tilize_zero_padding.cpp b/tests/tt_eager/ops/test_tilize_zero_padding.cpp index 580bd4102955..2cfd265e8882 100644 --- a/tests/tt_eager/ops/test_tilize_zero_padding.cpp +++ b/tests/tt_eager/ops/test_tilize_zero_padding.cpp @@ -46,7 +46,7 @@ int main(int argc, char** argv) { log_debug(LogTest, "Moving src data to host to validate"); Tensor host_a = a.cpu(); // Move tensor a to host to validate // TODO: Update when tensor.pad_to_tile() function is added - auto padded_shape = a.get_legacy_shape(); + auto padded_shape = a.get_padded_shape(); padded_shape[2] = round_up(padded_shape[2], TILE_HEIGHT); padded_shape[3] = round_up(padded_shape[3], TILE_WIDTH); Tensor padded_host_a = host_a.pad(padded_shape, ttnn::SimpleShape{0, 0, 0, 0}, 0); diff --git a/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp b/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp index e565c4d80269..cc37bd14bf71 100644 --- a/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp +++ b/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp @@ -49,7 +49,7 @@ int main(int argc, char** argv) { Tensor host_a = a.cpu(); // Move tensor a to host to validate Tensor g = Tensor(host_a.get_storage(), shape, DataType::BFLOAT16, Layout::ROW_MAJOR); // TODO: Update when tensor.pad_to_tile() function is added - auto padded_shape = g.get_legacy_shape(); + auto padded_shape = g.get_padded_shape(); padded_shape[2] = round_up(padded_shape[2], TILE_HEIGHT); padded_shape[3] = round_up(padded_shape[3], TILE_WIDTH); Tensor padded_g = g.pad(padded_shape, ttnn::SimpleShape{0, 0, 0, 0}, 0); diff --git a/tests/ttnn/unit_tests/operations/test_matmul.py b/tests/ttnn/unit_tests/operations/test_matmul.py index c411ab466311..ae04c188263e 100644 --- a/tests/ttnn/unit_tests/operations/test_matmul.py +++ b/tests/ttnn/unit_tests/operations/test_matmul.py @@ -1229,14 +1229,14 @@ def test_matmul_with_matched_width_height(device, m_size, k_size, n_size): def test_matmul_with_matched_width_height_from_1D(device, k_size, n_size): torch.manual_seed(0) - torch_input_tensor_a = torch.rand((k_size), dtype=torch.bfloat16) + torch_input_tensor_a = torch.rand((1, k_size), dtype=torch.bfloat16) torch_input_tensor_b = torch.rand((k_size, n_size), dtype=torch.bfloat16) torch_output_tensor = torch.matmul(torch_input_tensor_a, torch_input_tensor_b) input_tensor_a = ttnn.from_torch(torch_input_tensor_a, layout=ttnn.TILE_LAYOUT, device=device) input_tensor_b = ttnn.from_torch(torch_input_tensor_b, layout=ttnn.TILE_LAYOUT, device=device) output = input_tensor_a @ input_tensor_b - output = ttnn.to_torch(output, torch_rank=1) + output = ttnn.to_torch(output) assert len(output.shape) == len(torch_output_tensor.shape) assert output.shape == torch_output_tensor.shape diff --git a/tests/ttnn/unit_tests/test_to_and_from_torch.py b/tests/ttnn/unit_tests/test_to_and_from_torch.py index f3132050e531..539edf79084c 100644 --- a/tests/ttnn/unit_tests/test_to_and_from_torch.py +++ b/tests/ttnn/unit_tests/test_to_and_from_torch.py @@ -84,3 +84,22 @@ def test_from_torch_large(device): x_tensor = ttnn.from_torch(torch_x, layout=ttnn.TILE_LAYOUT) x_tensor = ttnn.to_torch(x_tensor) assert torch.allclose(torch_x, x_tensor) + + +@pytest.mark.parametrize( + "shape", + [ + (), + (1), + (2), + (0), + ], +) +@pytest.mark.parametrize("layout", [ttnn.ROW_MAJOR_LAYOUT, ttnn.TILE_LAYOUT]) +@pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16]) +def test_to_for_01_rank(shape, layout, dtype): + torch_input_tensor = torch.rand(shape, dtype=dtype) + tensor = ttnn.from_torch(torch_input_tensor, layout=layout) + # Conversion in the opposite direction is not yet supported + # torch_output_tensor = ttnn.to_torch(tensor) + # assert torch.allclose(torch_input_tensor, torch_output_tensor) diff --git a/ttnn/cpp/pybind11/pytensor.cpp b/ttnn/cpp/pybind11/pytensor.cpp index 6f9ccc5ab649..01379239ed3b 100644 --- a/ttnn/cpp/pybind11/pytensor.cpp +++ b/ttnn/cpp/pybind11/pytensor.cpp @@ -115,16 +115,13 @@ Tensor convert_float_vector_to_tt_tensor( Layout::TILE, layout); } + auto result_cpu_spec = TensorSpec( + ttnn::SimpleShape(shape), TensorLayout(data_type, PageConfig(Layout::TILE, tile), MemoryConfig{})); auto owned_buffer = create_owned_buffer_from_vector_of_floats(std::move(data), DataType::FLOAT32); auto float_tensor = Tensor(OwnedStorage{owned_buffer}, shape, DataType::FLOAT32, Layout::ROW_MAJOR, tile); - auto tile_val = tile.value_or(Tile()); - if (shape[2] % tile_val.get_height() != 0 || shape[3] % tile_val.get_width() != 0) { - auto padded_shape = shape; - padded_shape[2] = tt::round_up(shape[2], tile_val.get_height()); - padded_shape[3] = tt::round_up(shape[3], tile_val.get_width()); - - float_tensor = tensor_ops::tensor_pad( - float_tensor, LegacyShape(shape, padded_shape), ttnn::SimpleShape{0, 0, 0, 0}, 0); + if (result_cpu_spec.logical_shape() != result_cpu_spec.padded_shape()) { + float_tensor = + tensor_ops::tensor_pad(float_tensor, result_cpu_spec.padded_shape(), ttnn::SimpleShape{0, 0, 0, 0}, 0); } auto output_float_data = owned_buffer::get_as(float_tensor.to(Layout::TILE)).get(); auto output_packed_data = @@ -132,14 +129,16 @@ Tensor convert_float_vector_to_tt_tensor( ? pack_fp32_vec_as_bfp8_tiles(output_float_data, /*row_major_input=*/false, /*is_exp_a=*/false, tile) : pack_fp32_vec_as_bfp4_tiles(output_float_data, /*row_major_input=*/false, /*is_exp_a=*/false, tile); auto output_buffer = owned_buffer::create(std::move(output_packed_data)); - auto tensor = Tensor(std::move(OwnedStorage{std::move(output_buffer)}), shape, data_type, Layout::TILE, tile); + auto tensor = Tensor(std::move(OwnedStorage{std::move(output_buffer)}), result_cpu_spec); if (device) { return tensor.to(device, memory_config.value_or(MemoryConfig{})); } return tensor; } + auto result_cpu_spec = TensorSpec( + ttnn::SimpleShape(shape), TensorLayout(data_type, PageConfig(Layout::ROW_MAJOR, tile), MemoryConfig{})); auto owned_buffer = create_owned_buffer_from_vector_of_floats(std::move(data), data_type); - auto tensor = Tensor(OwnedStorage{owned_buffer}, shape, data_type, Layout::ROW_MAJOR, tile).to(layout); + auto tensor = Tensor(OwnedStorage{owned_buffer}, result_cpu_spec).to(layout); if (device) { return tensor.to(device, memory_config.value_or(MemoryConfig{})); } @@ -1212,7 +1211,8 @@ void pytensor_module(py::module& m_tensor) { const std::array& output_tensor_shape, const std::array& input_tensor_start, float pad_value) { - return self.pad(output_tensor_shape, ttnn::SimpleShape(input_tensor_start), pad_value); + return self.pad( + ttnn::SimpleShape(output_tensor_shape), ttnn::SimpleShape(input_tensor_start), pad_value); }, R"doc( Pad TT Tensor with given pad value ``arg2``. diff --git a/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.cpp b/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.cpp index e9a74e4706b1..a5df642fdc2a 100644 --- a/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.cpp +++ b/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.cpp @@ -42,6 +42,90 @@ inline bool use_multicore_device_tilize( return num_tiles_in_row <= max_tiles; } +bool requires_padding_change(const ttnn::Tensor& tensor, ttnn::Layout layout) { + auto tile = tensor.get_tensor_spec().tile(); + if (layout == Layout::ROW_MAJOR) { + // There shouldn't be extra paddings for Row Major layout + return tensor.logical_shape() != tensor.padded_shape(); + } + // It's okay for conversion to tile layout to preserve arbitrary padding as long as it satisfies the alignment + TensorSpec padded_spec( + tensor.padded_shape(), + TensorLayout(tensor.dtype(), PageConfig(layout, std::move(tile)), tensor.memory_config())); + return tensor.get_padded_shape() != padded_spec.padded_shape(); +} + +template +Tensor to_layout_impl_on_device( + const ttnn::Tensor& tensor_arg, + const ttnn::Layout layout, + const std::optional& dtype, + ttnn::MemoryConfig output_memory_config, + T* device) { + bool use_multicore_untilize = true; + bool use_multicore_tilize = use_multicore_device_tilize(tensor_arg, dtype); + + if (layout == ttnn::ROW_MAJOR_LAYOUT) { + TT_FATAL( + !dtype.has_value() || dtype.value() == tensor_arg.dtype(), + "dtype cannot be different from tensor dtype when converting to ROW_MAJOR_LAYOUT on device!"); + } + + if (!requires_padding_change(tensor_arg, layout)) { + if (layout == ttnn::ROW_MAJOR_LAYOUT) { + return ttnn::untilize(tensor_arg, output_memory_config, use_multicore_untilize); + } + return ttnn::tilize(tensor_arg, output_memory_config, dtype, use_multicore_tilize); + } + + auto tensor_shape = tensor_arg.get_logical_shape(); + + if (layout == ttnn::ROW_MAJOR_LAYOUT) { + if (tensor_arg.is_sharded()) { + const auto memory_config = tensor_arg.memory_config(); + output_memory_config = tt::tt_metal::MemoryConfig{memory_config.memory_layout, memory_config.buffer_type}; + } + SmallVector output_tensor_end; + for (auto index = 0; index < tensor_shape.rank(); ++index) { + output_tensor_end.push_back(tensor_shape[index] - 1); + } + + auto tensor = + ttnn::untilize_with_unpadding(tensor_arg, output_tensor_end, output_memory_config, use_multicore_untilize); + return ttnn::reshape(tensor, tensor_shape); + } + + TensorSpec result_spec( + tensor_arg.logical_shape(), + TensorLayout( + tensor_arg.dtype(), + PageConfig(layout, std::move(tensor_arg.tensor_spec().tile())), + tensor_arg.memory_config())); + + // ttnn::tilize_with_val_padding doesn't support height sharded tensors + // workaround by applying padding and then tilizing + if (tensor_arg.memory_config().memory_layout == TensorMemoryLayout::HEIGHT_SHARDED) { + ttnn::SmallVector> pad(result_spec.shape().rank()); + auto output_padding = result_spec.shape().padding(); + for (size_t i = 0; i < result_spec.padded_shape().rank(); i++) { + pad[i] = {output_padding[i].front, output_padding[i].back}; + } + auto tensor = ttnn::pad(0, tensor_arg, tt::stl::Span(pad), 0, true, std::nullopt); + return ttnn::tilize(tensor, output_memory_config, dtype, use_multicore_tilize); + } + + PadValue pad_value_variant; + if (tensor_arg.get_dtype() == ttnn::DataType::BFLOAT16 or tensor_arg.get_dtype() == ttnn::DataType::FLOAT32) { + pad_value_variant = 0.0f; + } else { + pad_value_variant = (uint32_t)0; + } + + auto tensor = ttnn::tilize_with_val_padding( + tensor_arg, result_spec.padded_shape(), pad_value_variant, output_memory_config, dtype, use_multicore_tilize); + return tensor.reshape(tensor_arg.logical_shape()); +} + template Tensor to_layout_impl( const ttnn::Tensor& tensor_arg, @@ -67,167 +151,44 @@ Tensor to_layout_impl( return tensor_arg; } - const std::set supported_layouts = { - ttnn::ROW_MAJOR_LAYOUT, - ttnn::TILE_LAYOUT, - }; - - if (supported_layouts.find(layout) == supported_layouts.end()) { + if (layout != ROW_MAJOR_LAYOUT && layout != TILE_LAYOUT) { TT_THROW("ttnn::to_layout: Unsupported layout conversion from {} to {}!", tensor_arg.get_layout(), layout); } - const auto requires_padding_change = - [](ttnn::Tensor& tensor, ttnn::Layout layout, const ttnn::Shape& shape) -> bool { - const auto intended_shape = shape; - const auto padded_shape = shape.with_tile_padding(); - if (layout == ttnn::ROW_MAJOR_LAYOUT and intended_shape != padded_shape) { - return true; - } - if (layout == ttnn::TILE_LAYOUT) { - auto tile_shape = tensor.tensor_spec().tile().get_tile_shape(); - if (padded_shape.rank() < 2 or padded_shape[-1] % tile_shape[1] != 0 or - padded_shape[-2] % tile_shape[0] != 0) { - return true; - } - } - return false; - }; - - const auto intended_shape = tensor_arg.get_shape(); - - auto tensor = tensor_arg; - const auto tile = tensor.get_tensor_spec().tile(); - - SmallVector output_shape; - if (layout == ttnn::TILE_LAYOUT and intended_shape.rank() < 2) { - output_shape.push_back(1); - tensor = ttnn::reshape( - tensor, - ttnn::Shape( - SmallVector{1, intended_shape[0]}, - SmallVector{1, tensor_arg.get_shape().with_tile_padding()[0]})); - } - for (auto index = 0; index < intended_shape.rank(); ++index) { - output_shape.push_back(intended_shape[index]); + auto output_memory_config = + memory_config.value_or(ttnn::get_memory_config(tensor_arg).value_or(ttnn::DRAM_MEMORY_CONFIG)); + + if (ttnn::is_tensor_on_device_or_multidevice(tensor_arg)) { + return to_layout_impl_on_device(tensor_arg, layout, dtype, std::move(output_memory_config), device); } - auto padded_output_shape = output_shape; - for (auto index = output_shape.size() - 2; index < output_shape.size(); ++index) { - padded_output_shape[index] = ttnn::pad_to_multiple_of_tile_size( - padded_output_shape[index], - (index == output_shape.size() - 2) ? tile.get_tile_shape()[0] : tile.get_tile_shape()[1]); + TT_ASSERT(not dtype.has_value(), "dtype cannot be specified when converting layout on host!"); + if (not requires_padding_change(tensor_arg, layout)) { + return device ? tensor_arg.to(layout, device) : tensor_arg.to(layout); } - auto output_memory_config = - memory_config.value_or(ttnn::get_memory_config(tensor).value_or(ttnn::DRAM_MEMORY_CONFIG)); + if (layout == ttnn::ROW_MAJOR_LAYOUT) { + auto tensor = device ? tensor_arg.to(layout, device) : tensor_arg.to(layout); + tensor = tensor.unpad_from_tile(tensor.get_logical_shape()); + return tensor.reshape(tensor_arg.logical_shape()); + } - if (ttnn::is_tensor_on_device_or_multidevice(tensor_arg)) { - bool use_multicore_untilize = true; - bool use_multicore_tilize = use_multicore_device_tilize(tensor, dtype); - - if (not requires_padding_change(tensor, layout, tensor.get_shape())) { - if (layout == ttnn::ROW_MAJOR_LAYOUT) { - TT_ASSERT(not dtype.has_value(), "dtype cannot be specified when converting to ROW_MAJOR_LAYOUT!"); - return ttnn::untilize(tensor, output_memory_config, use_multicore_untilize); - } else if (layout == ttnn::TILE_LAYOUT) { - if (tensor.is_sharded()) { - const auto shard_shape = get_memory_config(tensor).value().shard_spec.value().shape; - if (shard_shape[0] % ttnn::TILE_SIZE != 0 or shard_shape[1] % ttnn::TILE_SIZE != 0) { - TT_THROW( - "ttnn::to_layout: Sharded tensor must have shard shape that is a multiple of " - "TILE_SIZE!"); - } - } - return ttnn::tilize(tensor, output_memory_config, dtype, use_multicore_tilize); - } else { - throw std::runtime_error("ttnn::to_layout: Unsupported layout!"); - } - } else if (layout == ttnn::ROW_MAJOR_LAYOUT) { - TT_ASSERT(not dtype.has_value(), "dtype cannot be specified when converting to ROW_MAJOR_LAYOUT!"); - - if (tensor.is_sharded()) { - const auto memory_config = tensor.memory_config(); - output_memory_config = - tt::tt_metal::MemoryConfig{memory_config.memory_layout, memory_config.buffer_type}; - } - SmallVector output_tensor_end; - for (auto index = 0; index < tensor.get_shape().rank(); ++index) { - output_tensor_end.push_back(tensor.get_shape()[index] - 1); - } - - tensor = - ttnn::untilize_with_unpadding(tensor, output_tensor_end, output_memory_config, use_multicore_untilize); - return ttnn::reshape(tensor, ttnn::SimpleShape{output_shape}); - - } else if (layout == ttnn::TILE_LAYOUT) { - SmallVector padded_output_shape; - - for (int index = 0; index < tensor.get_shape().rank(); ++index) { - uint32_t second_last_rank = tensor.get_shape().rank() - 2; // h dim - uint32_t padded_value = - index < second_last_rank - ? tensor.get_shape()[index] - : ttnn::pad_to_multiple_of_tile_size( - tensor.get_shape()[index], - index == second_last_rank ? tile.get_tile_shape()[0] : tile.get_tile_shape()[1]); - padded_output_shape.push_back(padded_value); - } - if (tensor.memory_config().memory_layout == TensorMemoryLayout::HEIGHT_SHARDED) { - // ttnn::tilize_with_val_padding doesn't support height sharded tensors - // workaround by applying padding and then tilizing - SmallVector> padding = { - {0, 0}, - {0, 0}, - {0, padded_output_shape[2] - output_shape[2]}, - {0, padded_output_shape[3] - output_shape[3]}}; - tensor = ttnn::pad(0, tensor, padding, 0, true, std::nullopt); - return ttnn::tilize(tensor, output_memory_config, dtype, use_multicore_tilize); - } else { - PadValue pad_value_variant; - if (tensor.get_dtype() == ttnn::DataType::BFLOAT16 or tensor.get_dtype() == ttnn::DataType::FLOAT32) { - pad_value_variant = 0.0f; - } else { - pad_value_variant = (uint32_t)0; - } - - tensor = ttnn::tilize_with_val_padding( - tensor, padded_output_shape, pad_value_variant, output_memory_config, dtype, use_multicore_tilize); - } - - return ttnn::reshape(tensor, ttnn::Shape(tt::tt_metal::LegacyShape{output_shape, padded_output_shape})); - - } else { - TT_THROW("ttnn::to_layout: Unsupported output layout: {}!", layout); - } - } else { - TT_ASSERT(not dtype.has_value(), "dtype cannot be specified when converting layout on host!"); - if (not requires_padding_change(tensor, layout, tensor.get_shape())) { - return device ? tensor.to(layout, device) : tensor.to(layout); - } else if (layout == ttnn::ROW_MAJOR_LAYOUT) { - tensor = device ? tensor.to(layout, device) : tensor.to(layout); - tensor = tensor.unpad_from_tile(tensor.get_logical_shape()); - return ttnn::reshape(tensor, ttnn::SimpleShape{output_shape}); - } else if (layout == ttnn::TILE_LAYOUT) { - SmallVector padded_output_shape; - SmallVector padded_input_start; - for (int index = 0; index < tensor.get_shape().rank(); ++index) { - uint32_t second_last_rank = tensor.get_shape().rank() - 2; // h dim - uint32_t padded_value = - index < second_last_rank - ? tensor.get_shape()[index] - : ttnn::pad_to_multiple_of_tile_size( - tensor.get_shape()[index], - index == second_last_rank ? tile.get_tile_shape()[0] : tile.get_tile_shape()[1]); - padded_output_shape.push_back(padded_value); - padded_input_start.push_back(0); - } - tensor = tensor.pad(padded_output_shape, ttnn::SimpleShape(std::move(padded_input_start)), 0); - tensor = device ? tensor.to(layout, device) : tensor.to(layout); - return ttnn::reshape(tensor, ttnn::Shape(tt::tt_metal::LegacyShape{output_shape, padded_output_shape})); - } else { - TT_THROW("ttnn::to_layout: Unsupported output layout: {}!", layout); - } + SmallVector padded_input_start; + for (int index = 0; index < tensor_arg.get_logical_shape().rank(); ++index) { + padded_input_start.push_back(0); } + TensorSpec result_spec( + tensor_arg.padded_shape(), + TensorLayout::fromPaddedShape( + tensor_arg.dtype(), + PageConfig(layout, std::move(tensor_arg.tensor_spec().tile())), + tensor_arg.memory_config(), + tensor_arg.logical_shape(), + tensor_arg.padded_shape())); + + auto tensor = tensor_arg.pad(result_spec.padded_shape(), ttnn::SimpleShape(std::move(padded_input_start)), 0); + tensor = device ? tensor.to(layout, device) : tensor.to(layout); + return tensor.reshape(result_spec.logical_shape()); } } // namespace detail diff --git a/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp b/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp index cd783f161b57..eaddc1c2d14a 100644 --- a/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp +++ b/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp @@ -183,16 +183,15 @@ inline std::vector> distribute_work( uint32_t blocks_per_core, bool has_cliff, uint32_t nblocks_per_core_cliff) { - TT_FATAL( - logical_shape.rank() >= 2, "Logical shape rank needs to be >=2. Shape: {}", "Error", logical_shape, padding); + TT_FATAL(padding.rank() >= 2 && padding.rank() <= 4, "Rank needs to be >=2. Shape: {} {}", logical_shape, padding); auto input_w = logical_shape.rank() >= 4 ? logical_shape[-4] : 1; auto input_z = logical_shape.rank() >= 3 ? logical_shape[-3] : 1; auto input_y = logical_shape.rank() >= 2 ? logical_shape[-2] : 1; - auto padding_w = logical_shape.rank() >= 4 ? padding[padding.get_normalized_index(-4)].back : 0; - auto padding_z = logical_shape.rank() >= 3 ? padding[padding.get_normalized_index(-3)].back : 0; - auto padding_y = logical_shape.rank() >= 2 ? padding[padding.get_normalized_index(-2)].back : 0; + auto padding_w = padding.rank() >= 4 ? padding[-4].back : 0; + auto padding_z = padding.rank() >= 3 ? padding[-3].back : 0; + auto padding_y = padding.rank() >= 2 ? padding[-2].back : 0; // total work is a full rep followed by a padding. auto full_rep_blocks = FullRep(input_y, padding_y, input_z, padding_z, input_w).to_block_reps(); diff --git a/ttnn/cpp/ttnn/operations/data_movement/concat/concat.cpp b/ttnn/cpp/ttnn/operations/data_movement/concat/concat.cpp index c0fcd9fb3560..9161f24a2ed1 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/concat/concat.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/concat/concat.cpp @@ -146,7 +146,7 @@ MassagedConcat build_untilize_rm_retilize_concat( auto padded = pad_to_tile_vol(queue_id, output, 0.0f, true, output.memory_config()); concat_db_print(true, "[DEBUG] padded to tile layout, now tilizing."); auto tilized = - ttnn::tilize_with_val_padding(padded, padded.get_legacy_shape(), 0.0f, output.memory_config()); + ttnn::tilize_with_val_padding(padded, padded.get_padded_shape(), 0.0f, output.memory_config()); concat_db_print(true, "[DEBUG] tilized"); // need to reshape tilized result to logical concat output shape auto reshaped = ttnn::reshape( diff --git a/ttnn/cpp/ttnn/operations/data_movement/pad/pad.cpp b/ttnn/cpp/ttnn/operations/data_movement/pad/pad.cpp index f54a763b6388..f5f2b8cc3806 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/pad/pad.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/pad/pad.cpp @@ -34,7 +34,7 @@ static ttnn::Tensor pad_impl( return input_tensor; } else { return input_tensor.pad( - tt::tt_metal::LegacyShape(output_padded_shape), ttnn::SimpleShape{input_tensor_start}, value); + ttnn::SimpleShape(output_padded_shape), ttnn::SimpleShape(input_tensor_start), value); } } diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp index 924da1f446b8..ecc6d7f1c9b1 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp @@ -310,10 +310,9 @@ ttnn::Shape shape_corrector(const ttnn::Tensor& tensor, const ttnn::Shape& shape ttnn::Tensor ReshapeViewOperation::invoke( const ttnn::Tensor& tensor, const ttnn::Shape& input_shape, - const std::optional &memory_config, + const std::optional& memory_config, const uint8_t queue_id, - const std::optional &pad_value - ) { + const std::optional& pad_value) { MemoryConfig mem_config = memory_config.value_or(tensor.memory_config()); auto layout = tensor.get_layout(); auto tensor_shape = tensor.get_shape(); @@ -337,22 +336,24 @@ ttnn::Tensor ReshapeViewOperation::invoke( //The following case should only be called for the device storage case, the rest is a bandaid //for issue 15317 - + const uint32_t shape_last_dim = shape.rank() >= 1 ? shape[-1] : 1; + const uint32_t tensor_last_dim = tensor_shape.rank() >= 1 ? tensor_shape[-1] : 1; const uint32_t shape_second_last_dim = shape.rank() >= 2 ? shape[-2]:1; const uint32_t tensor_shape_second_last_dim = tensor_shape.rank() >= 2 ? tensor_shape[-2]:1; // Just edit shape if shape has a 0 dimension if (tensor.get_logical_volume() == 0) { - TT_FATAL(shape.logical_shape().volume() == 0 , "Tensor volume is 0, but shape's volume is not"); - TT_FATAL((tensor.storage_type() != StorageType::MULTI_DEVICE && - tensor.storage_type() != StorageType::MULTI_DEVICE_HOST), - "Reshaping a multi-device tensor with 0 volume is not supported"); + TT_FATAL(shape.logical_shape().volume() == 0, "Tensor volume is 0, but shape's volume is not"); + TT_FATAL( + (tensor.storage_type() != StorageType::MULTI_DEVICE && + tensor.storage_type() != StorageType::MULTI_DEVICE_HOST), + "Reshaping a multi-device tensor with 0 volume is not supported"); return tensor.reshape(shape); } TT_FATAL(shape.logical_shape().volume() != 0, "Tensor volume is not 0, but shape volume is 0"); bool this_is_view = - (tensor_shape[-1] == shape[-1]) && (mem_config.is_sharded() == tensor.memory_config().is_sharded()) && + (tensor_last_dim == shape_last_dim) && (mem_config.is_sharded() == tensor.memory_config().is_sharded()) && (mem_config.is_l1() == tensor.memory_config().is_l1()) && ((tensor.get_layout() == ttnn::ROW_MAJOR_LAYOUT) || // Its row major (tensor_shape_second_last_dim == shape_second_last_dim) || // Second last dimension is the same @@ -366,20 +367,19 @@ ttnn::Tensor ReshapeViewOperation::invoke( if (this_is_view) { return PerformView(tensor,shape, tile_first_dim, tile_second_dim); } - if(shape.logical_shape().volume() != tensor.get_logical_volume()) - { - //This is completely incorrect but it is due to issue 15137 or issue 15558 + if (shape.logical_shape().volume() != tensor.get_logical_volume()) { + // This is completely incorrect but it is due to issue 15137 or issue 15558 bool tile_tensor_view_reshape_possible = (layout == ttnn::Layout::TILE and shape.with_tile_padding().rank() >= 2 and - shape.with_tile_padding()[-2] % ttnn::TILE_SIZE == 0 and - shape.with_tile_padding()[-1] % ttnn::TILE_SIZE == 0 and - tensor_shape.with_tile_padding()[-1] == shape.with_tile_padding()[-1]); + shape.with_tile_padding()[-2] % ttnn::TILE_SIZE == 0 and + shape.with_tile_padding()[-1] % ttnn::TILE_SIZE == 0 and + tensor_shape.with_tile_padding()[-1] == shape.with_tile_padding()[-1]); if (tile_tensor_view_reshape_possible) { // This case has been allowed in the past though it means introducing padding values to the data return tensor.reshape(shape); } - //This is a completely incorrect test but it is due to issue 15558 + // This is a completely incorrect test but it is due to issue 15558 TT_FATAL(false, "Attempting to reshape between two shapes with different volumes"); } // Catch-all @@ -402,21 +402,20 @@ ttnn::Tensor ReshapeViewOperation::invoke( return invoke(tensor, shape,std::nullopt,0,std::nullopt); } -ttnn::Tensor ReshapeViewOperation::invoke( - const ttnn::Tensor& tensor, - const ttnn::SimpleShape& shape, - const std::optional &memory_config, - const uint8_t queue_id, - const std::optional &pad_value - ) { - return invoke(tensor, ttnn::Shape(shape.view()),memory_config,queue_id,pad_value); -} + ttnn::Tensor ReshapeViewOperation::invoke( + const ttnn::Tensor& tensor, + const ttnn::SimpleShape& shape, + const std::optional& memory_config, + const uint8_t queue_id, + const std::optional& pad_value) { + return invoke(tensor, ttnn::Shape(shape.view()), memory_config, queue_id, pad_value); + } ttnn::Tensor ReshapeViewOperation::invoke( const ttnn::Tensor& tensor, const ttnn::SimpleShape& shape ) { - return invoke(tensor, ttnn::Shape(shape.view()),std::nullopt,0,std::nullopt); + return invoke(tensor, ttnn::Shape(shape.view()), std::nullopt, 0, std::nullopt); } ttnn::Tensor ReshapeViewOperation::invoke( diff --git a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.cpp b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.cpp index 0677baaaa2cb..91eff6af9b34 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.cpp @@ -14,7 +14,7 @@ namespace ttnn::operations::data_movement { void TilizeWithValPadding::validate(const std::vector& input_tensors) const { const auto& input_tensor_a = input_tensors.at(0); - const auto& input_shape = input_tensor_a.get_legacy_shape(); + const auto& input_shape = input_tensor_a.get_padded_shape(); TT_FATAL(input_tensor_a.storage_type() == StorageType::DEVICE, "Operands need to be on device!"); TT_FATAL(input_tensor_a.buffer() != nullptr, "Operands need to be allocated in buffers on device!"); TT_FATAL(input_tensor_a.get_layout() == Layout::ROW_MAJOR, "Can only tilize row major data"); @@ -22,24 +22,23 @@ void TilizeWithValPadding::validate(const std::vector& input_tensors) co input_tensor_a.get_dtype() == DataType::BFLOAT16 or input_tensor_a.get_dtype() == DataType::UINT32 or input_tensor_a.get_dtype() == DataType::FLOAT32, "Can only tilize bfloat16/float32 or uint32 tensors"); - TT_FATAL(input_shape.rank() >= 2, "Input tensor must be of rank >2, but its shape is {}", input_shape); - for (auto i = 0; i < input_shape.rank(); i++) { + for (int i = -static_cast(input_shape.rank()); i >= 0; i--) { TT_FATAL( - input_shape[i] <= this->output_tensor_shape[i], + input_shape[i] <= this->output_padded_shape[i], "Output tensor shape {} must be greater than or equal to input shape {} in each dimension, but is smaller " "in dimension {}", - this->output_tensor_shape, + this->output_padded_shape, input_shape, i); } - uint32_t num_rows = this->output_tensor_shape[-1]; - uint32_t inner_dim = this->output_tensor_shape[-2]; + uint32_t num_rows = this->output_padded_shape[-1]; + uint32_t inner_dim = this->output_padded_shape[-2]; TT_FATAL( inner_dim % TILE_WIDTH == 0 && num_rows % TILE_HEIGHT == 0, "To be tilizable output tensor shape {} must be divisible by tile size ({}, {})", - output_tensor_shape, + output_padded_shape, TILE_WIDTH, TILE_HEIGHT); @@ -50,41 +49,33 @@ void TilizeWithValPadding::validate(const std::vector& input_tensors) co TT_FATAL( this->output_mem_config.memory_layout == input_tensor_a.memory_config().memory_layout, "Output tensor must have the same memory layout as input tensor"); - for (uint32_t i = 0; i < input_tensor_a.get_legacy_shape().rank(); i++) { + for (uint32_t i = 0; i < input_tensor_a.get_padded_shape().rank(); i++) { if (i != input_shape.rank() - 2) { - TT_FATAL(input_shape[i] == this->output_tensor_shape[i], "Error"); + TT_FATAL(input_shape[i] == this->output_padded_shape[i], "Error"); } } } } -std::vector TilizeWithValPadding::compute_output_shapes( +std::vector TilizeWithValPadding::compute_output_specs( const std::vector& input_tensors) const { - auto input_shape = input_tensors.at(0).get_legacy_shape(); - auto dimensions_pads = std::vector(); - for (auto index = 0; index < input_shape.rank(); index++) { - auto back = this->output_tensor_shape[index] - input_shape[index]; - dimensions_pads.push_back(Padding::PadDimension{.front = 0, .back = back}); - } - const auto padding = Padding(dimensions_pads, Padding::PadValue::Any); - return {tt::tt_metal::LegacyShape(this->output_tensor_shape, padding)}; -} - -std::vector TilizeWithValPadding::create_output_tensors( - const std::vector& input_tensors, const std::vector>& output_tensors) const { const auto& input_tensor_a = input_tensors.at(0); + auto input_shape = input_tensor_a.get_padded_shape(); + if (input_tensor_a.memory_config().is_sharded()) { - auto output_shape = this->compute_output_shapes(input_tensors).at(0); auto shard_spec = input_tensor_a.shard_spec().value(); - shard_spec.shape[0] = tt::tt_metal::compute_volume(output_shape) / output_shape[-1]; + shard_spec.shape[0] = output_padded_shape.volume() / output_padded_shape[-1]; auto mem_config = this->output_mem_config; mem_config.shard_spec = shard_spec; - return { - create_device_tensor(output_shape, this->output_dtype, Layout::TILE, input_tensor_a.device(), mem_config)}; - } else { - return operation::generic_create_output_tensors( - *this, input_tensors, this->output_dtype, Layout::TILE, this->output_mem_config); + return {TensorSpec( + input_shape, + TensorLayout::fromPaddedShape( + output_dtype, PageConfig(Layout::TILE), mem_config, input_shape, output_padded_shape))}; } + return {TensorSpec( + input_shape, + TensorLayout::fromPaddedShape( + output_dtype, PageConfig(Layout::TILE), output_mem_config, input_shape, output_padded_shape))}; } // TODO: If pad is called on a tile and output is not tile, we could untilize then pad, and output is RM diff --git a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.hpp index 2317ba86ab5f..3dc805e0f0b1 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.hpp @@ -13,16 +13,14 @@ namespace ttnn::operations::data_movement { struct TilizeWithValPadding { - const tt::tt_metal::LegacyShape output_tensor_shape; + const ttnn::SimpleShape output_padded_shape; const PadValue pad_value; const tt::tt_metal::MemoryConfig output_mem_config; const tt::tt_metal::DataType output_dtype; const bool use_multicore; void validate(const std::vector& input_tensors) const; - std::vector compute_output_shapes(const std::vector& input_tensors) const; - std::vector create_output_tensors( - const std::vector& input_tensors, const std::vector>& output_tensors) const; + std::vector compute_output_specs(const std::vector& input_tensors) const; tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; }; diff --git a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.cpp b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.cpp index 0e6e10445225..a5470e321229 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.cpp @@ -66,7 +66,7 @@ MassagedTilizeVal build_ndiml_tilize_val(BaseTilizeValType base_tilize) { .operation = std::move(base_tilize)}); } -tt::tt_metal::LegacyShape squeeze_output_shape(tt::tt_metal::LegacyShape output_shape) { +ttnn::SimpleShape squeeze_output_shape(ttnn::SimpleShape output_shape) { if (output_shape.rank() > 4) { std::array output_shape_4d; output_shape_4d[0] = 1; @@ -77,7 +77,7 @@ tt::tt_metal::LegacyShape squeeze_output_shape(tt::tt_metal::LegacyShape output_ output_shape_4d[1] = output_shape[1 + extra_rank]; output_shape_4d[2] = output_shape[2 + extra_rank]; output_shape_4d[3] = output_shape[3 + extra_rank]; - return tt::tt_metal::LegacyShape(output_shape_4d); + return ttnn::SimpleShape(output_shape_4d); } return output_shape; } @@ -85,7 +85,7 @@ tt::tt_metal::LegacyShape squeeze_output_shape(tt::tt_metal::LegacyShape output_ ttnn::Tensor ExecuteTilizeWithValPadding::invoke( uint8_t queue_id, const ttnn::Tensor& input_tensor, - const tt::tt_metal::LegacyShape& output_tensor_shape, + const ttnn::SimpleShape& output_padded_shape, const PadValue pad_value, const std::optional& memory_config, std::optional output_dtype, @@ -93,7 +93,7 @@ ttnn::Tensor ExecuteTilizeWithValPadding::invoke( auto base_tilize = [=](const ttnn::Tensor& input_tensor) { return operation::run( TilizeWithValPadding{ - squeeze_output_shape(output_tensor_shape), + squeeze_output_shape(output_padded_shape), pad_value, memory_config.value_or(input_tensor.memory_config()), output_dtype.value_or(input_tensor.get_dtype()), @@ -109,13 +109,13 @@ ttnn::Tensor ExecuteTilizeWithValPadding::invoke( ttnn::Tensor ExecuteTilizeWithValPadding::invoke( const ttnn::Tensor& input_tensor, - const tt::tt_metal::LegacyShape& output_tensor_shape, + const ttnn::SimpleShape& output_padded_shape, const PadValue pad_value, const std::optional& memory_config, std::optional output_dtype, bool use_multicore) { return invoke( - DefaultQueueId, input_tensor, output_tensor_shape, pad_value, memory_config, output_dtype, use_multicore); + DefaultQueueId, input_tensor, output_padded_shape, pad_value, memory_config, output_dtype, use_multicore); } ttnn::Tensor ExecuteTilizeWithZeroPadding::invoke( @@ -125,7 +125,7 @@ ttnn::Tensor ExecuteTilizeWithZeroPadding::invoke( std::optional output_dtype, bool use_multicore) { using namespace tt::constants; - auto shape = input_tensor.get_legacy_shape(); + auto shape = input_tensor.get_padded_shape(); shape[2] = tt::round_up(shape[2], tt::constants::TILE_HEIGHT); shape[3] = tt::round_up(shape[3], tt::constants::TILE_WIDTH); diff --git a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp index 92f8374e58fa..dec6a34333ce 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp @@ -18,7 +18,7 @@ struct ExecuteTilizeWithValPadding { static ttnn::Tensor invoke( uint8_t queue_id, const ttnn::Tensor& input_tensor, - const tt::tt_metal::LegacyShape& output_tensor_shape, + const ttnn::SimpleShape& output_padded_shape, const PadValue pad_value, const std::optional& memory_config = std::nullopt, std::optional output_dtype = std::nullopt, @@ -26,7 +26,7 @@ struct ExecuteTilizeWithValPadding { static ttnn::Tensor invoke( const ttnn::Tensor& input_tensor, - const tt::tt_metal::LegacyShape& output_tensor_shape, + const ttnn::SimpleShape& output_padded_shape, const PadValue pad_value, const std::optional& memory_config = std::nullopt, std::optional output_dtype = std::nullopt, diff --git a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding_pybind.hpp b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding_pybind.hpp index 0fc3cc271451..3915564394e1 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding_pybind.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding_pybind.hpp @@ -53,7 +53,13 @@ void bind_tilize_with_val_padding(py::module& module) { bool use_multicore, uint8_t queue_id) { return self( - queue_id, input_tensor, output_tensor_shape, value, memory_config, output_dtype, use_multicore); + queue_id, + input_tensor, + Shape(output_tensor_shape).padded_shape(), + value, + memory_config, + output_dtype, + use_multicore); }, py::arg("input_tensor"), py::arg("output_tensor_shape"), diff --git a/ttnn/cpp/ttnn/operations/experimental/auto_format/auto_format.cpp b/ttnn/cpp/ttnn/operations/experimental/auto_format/auto_format.cpp index 5e66f4033472..165376b82d8d 100644 --- a/ttnn/cpp/ttnn/operations/experimental/auto_format/auto_format.cpp +++ b/ttnn/cpp/ttnn/operations/experimental/auto_format/auto_format.cpp @@ -113,7 +113,8 @@ Tensor AutoFormat::format_input_tensor( } else { pad_value_variant = (uint32_t)pad_value; } - return ttnn::tilize_with_val_padding(formatted_input, padded_shape, pad_value_variant, mem_config); + return ttnn::tilize_with_val_padding( + formatted_input, Shape(padded_shape).padded_shape(), pad_value_variant, mem_config); } else if (formatted_input.get_layout() == Layout::TILE && target_layout == Layout::ROW_MAJOR) { formatted_input = ttnn::untilize(formatted_input, mem_config); return ttnn::pad( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp index 2b3cbb83cba5..bae91413f9ae 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp @@ -351,11 +351,12 @@ void validate_input_with_dim(const Tensor& input, const int64_t& dim) { void validate_output_with_keepdim(const Tensor& input, const Tensor& output, const int64_t& dim, const bool& keepdim) { auto input_shape = input.get_padded_shape(); auto input_shape_wo_padding = input.get_logical_shape(); - const auto input_rank = input_shape.rank(); + const auto input_rank = input_shape_wo_padding.rank(); + auto padded_dim = dim + input_shape.rank() - input_shape_wo_padding.rank(); const auto output_shape = output.get_padded_shape(); const auto output_shape_wo_padding = output.get_logical_shape(); - const auto output_rank = output_shape.rank(); + const auto output_rank = output_shape_wo_padding.rank(); const bool is_tile_dim = (dim == input_rank - 1 || dim == input_rank - 2); @@ -365,7 +366,7 @@ void validate_output_with_keepdim(const Tensor& input, const Tensor& output, con if (keepdim) { bool ranks_are_equal = (input_rank == output_rank); - input_shape[dim] = (is_tile_dim) ? (TILE_HEIGHT) : (1); + input_shape[padded_dim] = (is_tile_dim) ? (TILE_HEIGHT) : (1); input_shape_wo_padding[dim] = 1; if (!ranks_are_equal) { @@ -387,31 +388,36 @@ void validate_output_with_keepdim(const Tensor& input, const Tensor& output, con expand_to_max_dim(input_dim_wo_padding, input_shape_wo_padding); expand_to_max_dim(output_dim_wo_padding, output_shape_wo_padding); - for (int i = 0; i < input_rank; ++i) { + for (int i = 0; i < input_shape.rank(); ++i) { TT_FATAL(input_dim[i] == output_dim[i], "Error"); + } + for (int i = 0; i < input_shape_wo_padding.rank(); ++i) { TT_FATAL(input_dim_wo_padding[i] == output_dim_wo_padding[i], "Error"); } } else { ttnn::SmallVector expected_output_shape; - ttnn::SmallVector expected_output_shape_wo_padding; for (int i = 0; i < output_shape.rank(); ++i) { - if (i == dim && !is_tile_dim) { + if (i == padded_dim && !is_tile_dim) { expected_output_shape.push_back(1); - expected_output_shape_wo_padding.push_back(1); } expected_output_shape.push_back(output_shape[i]); + } + ttnn::SmallVector expected_output_shape_wo_padding; + for (int i = 0; i < output_shape_wo_padding.rank(); ++i) { + if (i == dim && !is_tile_dim) { + expected_output_shape_wo_padding.push_back(1); + } expected_output_shape_wo_padding.push_back(output_shape_wo_padding[i]); } - log_debug(LogOp, "{}:{} expected_output_shape {}", __func__, __LINE__, expected_output_shape); log_debug( LogOp, "{}:{} expected_output_shape_wo_padding {}", __func__, __LINE__, expected_output_shape_wo_padding); - for (int i = 0; i < input_rank; ++i) { - if (i == dim) { - continue; - } - TT_FATAL(input_shape[i] == expected_output_shape[i], "Error"); - TT_FATAL(input_shape_wo_padding[i] == expected_output_shape_wo_padding[i], "Error"); + + for (int i = 0; i < expected_output_shape.size(); ++i) { + TT_FATAL(i == padded_dim || input_shape[i] == expected_output_shape[i], "Error"); + } + for (int i = 0; i < expected_output_shape_wo_padding.size(); ++i) { + TT_FATAL(i == dim || input_shape_wo_padding[i] == expected_output_shape_wo_padding[i], "Error"); } } } diff --git a/ttnn/cpp/ttnn/tensor/layout/tensor_layout.cpp b/ttnn/cpp/ttnn/tensor/layout/tensor_layout.cpp index 339c919571ae..a60e916070a9 100644 --- a/ttnn/cpp/ttnn/tensor/layout/tensor_layout.cpp +++ b/ttnn/cpp/ttnn/tensor/layout/tensor_layout.cpp @@ -4,6 +4,8 @@ #include "tensor_layout.hpp" +#include "ttnn/tensor/tensor_utils.hpp" + namespace tt::tt_metal { namespace { @@ -18,25 +20,27 @@ size_t round_up(size_t value, size_t multiple) { }; Alignment legacyShapeToAlignment( - const ttnn::Shape& shape, const PageConfig& page_config, const MemoryConfig& memory_config) { - const auto& logical_shape = shape.logical_shape(); - const auto& legacy_padded_shape = shape.padded_shape(); - if (logical_shape == legacy_padded_shape) { + const ttnn::SimpleShape& logical_shape, + const ttnn::SimpleShape& padded_shape, + const PageConfig& page_config, + const MemoryConfig& memory_config) { + if (logical_shape == padded_shape) { return Alignment{}; } - const auto rank = legacy_padded_shape.rank(); + const auto rank = padded_shape.rank(); bool alignment_can_be_2D = true; for (int i = rank - 3; i >= 0; i--) { - alignment_can_be_2D &= logical_shape[i] == legacy_padded_shape[i]; + alignment_can_be_2D &= logical_shape[i] == padded_shape[i]; } // SHARDED if (memory_config.shard_spec.has_value()) { TT_FATAL( alignment_can_be_2D, - "Tensor with shape {} cannot be sharded because alignment will have rank greater than 2!", - shape); + "Tensor with shape {} ({}) cannot be sharded because alignment will have rank greater than 2!", + logical_shape, + padded_shape); if (page_config.get_layout() == Layout::ROW_MAJOR) { const auto& shard_spec = memory_config.shard_spec.value(); if (shard_spec.physical_shard_shape.has_value()) { @@ -52,10 +56,10 @@ Alignment legacyShapeToAlignment( ttnn::SmallVector values(std::min((int)rank, 2)); const auto alignment_size = values.size(); if (alignment_size >= 1) { - values[alignment_size - 1] = legacy_padded_shape[-1]; + values[alignment_size - 1] = padded_shape[-1]; } if (alignment_size == 2) { - values[alignment_size - 2] = legacy_padded_shape[-2]; + values[alignment_size - 2] = padded_shape[-2]; } Alignment result(std::move(values)); return result; @@ -64,11 +68,11 @@ Alignment legacyShapeToAlignment( // INTERLEAVED with (deprecated) non-height/width padding // NOTE: Rank > 2 is guaranteed in this case ttnn::SmallVector values(rank); - values[rank - 1] = legacy_padded_shape[-1]; - values[rank - 2] = legacy_padded_shape[-2]; + values[rank - 1] = padded_shape[-1]; + values[rank - 2] = padded_shape[-2]; for (int i = rank - 3; i >= 0; i--) { - values[i] = legacy_padded_shape[i] * values[i + 1]; + values[i] = padded_shape[i] * values[i + 1]; } for (auto& value : values) { @@ -101,15 +105,40 @@ TensorLayout TensorLayout::fromLegacyPaddedShape( dtype, page_config, memory_config, - CMAKE_UNIQUE_NAMESPACE::legacyShapeToAlignment(legacy_shape, page_config, memory_config)); + CMAKE_UNIQUE_NAMESPACE::legacyShapeToAlignment( + legacy_shape.logical_shape(), legacy_shape.padded_shape(), page_config, memory_config)); +} + +TensorLayout TensorLayout::fromPaddedShape( + DataType dtype, + const PageConfig& page_config, + const MemoryConfig& memory_config, + const ttnn::SimpleShape& logical_shape, + const ttnn::SimpleShape& padded_shape) { + return TensorLayout( + dtype, + page_config, + memory_config, + CMAKE_UNIQUE_NAMESPACE::legacyShapeToAlignment(logical_shape, padded_shape, page_config, memory_config)); } void TensorLayout::initialize_alignment() { - if (!alignment_.empty()) { + auto default_alignment = page_config_.create_default_alignment(dtype_, memory_config_); + if (alignment_.empty()) { + alignment_ = default_alignment; return; } - alignment_ = page_config_.create_default_alignment(dtype_, memory_config_); + ttnn::SmallVector result(std::max(alignment_.size(), default_alignment.size()), 1); + for (size_t i = 0; i < alignment_.size(); i++) { + result[i + result.size() - alignment_.size()] = alignment_[i]; + } + for (size_t i = 0; i < default_alignment.size(); i++) { + size_t result_idx = i + result.size() - default_alignment.size(); + result[result_idx] = CMAKE_UNIQUE_NAMESPACE::round_up(result[result_idx], default_alignment[i]); + } + + alignment_ = Alignment(std::move(result)); } void TensorLayout::validate_alignment() const { @@ -310,39 +339,31 @@ Size TensorLayout::compute_page_shape(const Size& physical_size) const { } Strides TensorLayout::compute_strides(const ttnn::SimpleShape& shape) const { - const int rank = static_cast(shape.rank()); - const int alignment_rank = static_cast(alignment_.size()); - - Strides strides(rank, 1); - for (int i = rank - 2; i >= 0; i--) { - strides[i] = strides[i + 1] * shape[i + 1]; - - const int alignment_index = i - (rank - alignment_rank) + 1; - if (alignment_index >= 0) { - strides[i] = CMAKE_UNIQUE_NAMESPACE::round_up(strides[i], alignment_[alignment_index]); - } - } - - return strides; + auto padded_shape = compute_padded_shape(shape); + return tt::tt_metal::compute_strides(padded_shape); } ttnn::SimpleShape TensorLayout::compute_padded_shape(const ttnn::SimpleShape& shape) const { - ttnn::SmallVector padded_shape(shape.rank()); + ttnn::SmallVector padded_shape(std::max(shape.rank(), alignment_.size())); int rank_index = static_cast(shape.rank()) - 1; int alignment_index = static_cast(alignment_.size()) - 1; + int padded_shape_index = static_cast(padded_shape.size() - 1); size_t accum_alignment = 1; - for (; rank_index >= 0 && alignment_index >= 0; rank_index--, alignment_index--) { + for (; alignment_index >= 0; rank_index--, alignment_index--, padded_shape_index--) { + uint32_t shape_value = rank_index >= 0 ? shape[rank_index] : 1; + uint32_t alignment_value = alignment_[alignment_index]; + uint32_t& padded_shape_value = padded_shape[padded_shape_index]; + // The last 2 dimensions of a shape are special if (rank_index >= static_cast(shape.rank()) - 2) { - padded_shape[rank_index] = CMAKE_UNIQUE_NAMESPACE::round_up(shape[rank_index], alignment_[alignment_index]); + padded_shape_value = CMAKE_UNIQUE_NAMESPACE::round_up(shape_value, alignment_value); } else { - if (accum_alignment % alignment_[alignment_index] == 0) { + if (accum_alignment % alignment_value == 0) { // Alignment for this dimension is redundant, ignoring - padded_shape[rank_index] = shape[rank_index]; - } else if (alignment_[alignment_index] % accum_alignment == 0) { - padded_shape[rank_index] = - CMAKE_UNIQUE_NAMESPACE::round_up(shape[rank_index], alignment_[alignment_index] / accum_alignment); + padded_shape_value = shape_value; + } else if (alignment_value % accum_alignment == 0) { + padded_shape_value = CMAKE_UNIQUE_NAMESPACE::round_up(shape_value, alignment_value / accum_alignment); } else { TT_THROW( "Padded shape can't be deducted from TensorLayout parameters {} and Shape {}", alignment_, shape); @@ -351,11 +372,11 @@ ttnn::SimpleShape TensorLayout::compute_padded_shape(const ttnn::SimpleShape& sh // Alignment doesn't accumulate on the last dimension of a shape if (rank_index != static_cast(shape.rank()) - 1) { - accum_alignment *= padded_shape[rank_index]; + accum_alignment *= padded_shape_value; } } - for (; rank_index >= 0; rank_index--) { - padded_shape[rank_index] = shape[rank_index]; + for (; rank_index >= 0; rank_index--, padded_shape_index--) { + padded_shape[padded_shape_index] = shape[rank_index]; } return ttnn::SimpleShape(std::move(padded_shape)); } diff --git a/ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp b/ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp index 2e9b24cb03a2..6625bb19ac61 100644 --- a/ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp +++ b/ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp @@ -14,7 +14,7 @@ namespace tt::tt_metal { -using Strides = std::vector; +using Strides = ttnn::SmallVector; // TensorLayout describes how a tensor is laid out in memory // It takes datatype, layout (eg. TILE vs. RM), memory (eg. DRAM vs. L1), sharding (ie. how you want to cut your logical @@ -31,6 +31,13 @@ class TensorLayout { const PageConfig& page_config, const MemoryConfig& memory_config, const ttnn::Shape& legacy_shape); + [[deprecated("Use of Padded Shape is deprecated")]] + static TensorLayout fromPaddedShape( + DataType dtype, + const PageConfig& page_config, + const MemoryConfig& memory_config, + const ttnn::SimpleShape& logical_shape, + const ttnn::SimpleShape& padded_shape); Layout get_layout() const { return page_config_.get_layout(); } PageConfig get_page_config() const { return page_config_; } diff --git a/ttnn/cpp/ttnn/tensor/tensor.cpp b/ttnn/cpp/ttnn/tensor/tensor.cpp index 5dd2a70106be..9ab439600b91 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor.cpp @@ -136,11 +136,14 @@ void Tensor::TensorAttributes::update_main_thread_ref_count(Device* worker, uint } Tensor::Tensor( - Storage storage, const ttnn::Shape& shape, DataType dtype, Layout layout, const std::optional& tile) { + Storage storage, + const ttnn::SimpleShape& logical_shape, + const ttnn::SimpleShape& padded_shape, + DataType dtype, + Layout layout, + const std::optional& tile) { using namespace tt::constants; - - if (tile.has_value() and // - (tile->get_tile_shape()[0] != TILE_WIDTH or tile->get_tile_shape()[1] != TILE_HEIGHT)) { + if (tile.has_value() && (tile->get_tile_shape()[0] != TILE_WIDTH || tile->get_tile_shape()[1] != TILE_HEIGHT)) { tt::log_warning( "only matmul op and ccl all-gather currently supports the customized tile shape: {}", tile->get_tile_shape()); @@ -156,10 +159,18 @@ Tensor::Tensor( init( std::move(storage), TensorSpec( - shape.logical_shape(), - TensorLayout::fromLegacyPaddedShape(dtype, PageConfig(layout, tile), memory_config, shape))); + logical_shape, + TensorLayout::fromLegacyPaddedShape( + dtype, + PageConfig(layout, tile), + memory_config, + ttnn::Shape(logical_shape.view(), padded_shape.view())))); } +Tensor::Tensor( + Storage storage, const ttnn::Shape& shape, DataType dtype, Layout layout, const std::optional& tile) : + Tensor(std::move(storage), shape.logical_shape(), shape.padded_shape(), dtype, layout, tile) {} + Tensor::Tensor(Storage storage, TensorSpec tensor_spec) { init(std::move(storage), std::move(tensor_spec)); } void Tensor::init(Storage storage, TensorSpec tensor_spec) { @@ -654,12 +665,18 @@ template std::vector Tensor::to_vector() const; template std::vector Tensor::to_vector() const; template std::vector Tensor::to_vector() const; -Tensor Tensor::to(Device* target_device, const MemoryConfig& mem_config,uint8_t cq_id, +Tensor Tensor::to( + Device* target_device, + const MemoryConfig& mem_config, + uint8_t cq_id, const std::vector& sub_device_ids) const { return tensor_ops::tensor_to(*this, target_device, mem_config, cq_id, sub_device_ids); } -Tensor Tensor::to(distributed::MeshDevice* mesh_device, const MemoryConfig& mem_config,uint8_t cq_id, +Tensor Tensor::to( + distributed::MeshDevice* mesh_device, + const MemoryConfig& mem_config, + uint8_t cq_id, const std::vector& sub_device_ids) const { std::vector workers_to_use = ttnn::distributed::get_mapped_devices(*this, *mesh_device); return tensor_ops::tensor_to(*this, workers_to_use, mem_config, cq_id, sub_device_ids); @@ -701,10 +718,8 @@ const std::string Tensor::write_to_string() const { return tensor_impl::to_strin void Tensor::print() const { tensor_ops::tensor_print(*this); } Tensor Tensor::pad( - const tt::tt_metal::LegacyShape& output_tensor_shape, - const ttnn::SimpleShape& input_tensor_start, - float pad_value) const { - return tensor_ops::tensor_pad(*this, output_tensor_shape, input_tensor_start, pad_value); + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) const { + return tensor_ops::tensor_pad(*this, output_padded_shape, input_tensor_start, pad_value); } Tensor Tensor::unpad(const ttnn::SimpleShape& output_tensor_start, const ttnn::SimpleShape& output_tensor_end) const { @@ -987,7 +1002,8 @@ void write_tensor( "Error"); std::visit( tt::stl::overloaded{ - [worker, worker_index, cq_id, &async_safe_tensor, sub_device_ids](const DeviceStorage& device_storage) { + [worker, worker_index, cq_id, &async_safe_tensor, sub_device_ids]( + const DeviceStorage& device_storage) { // Copying from host to a single device. void* host_data = std::visit( tt::stl::overloaded{ @@ -1014,7 +1030,8 @@ void write_tensor( /*blocking=*/false, sub_device_ids); }, - [worker, worker_index, cq_id, &async_safe_tensor, sub_device_ids](const MultiDeviceStorage& device_storage) { + [worker, worker_index, cq_id, &async_safe_tensor, sub_device_ids]( + const MultiDeviceStorage& device_storage) { // Copying from host to multi-device. TT_ASSERT( std::holds_alternative(async_safe_tensor.get_storage()), diff --git a/ttnn/cpp/ttnn/tensor/tensor.hpp b/ttnn/cpp/ttnn/tensor/tensor.hpp index 6827c4213206..726fa4bb1ce2 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor.hpp @@ -98,6 +98,13 @@ struct Tensor { DataType dtype, Layout layout, const std::optional& tile = std::nullopt); + Tensor( + Storage storage, + const ttnn::SimpleShape& logical_shape, + const ttnn::SimpleShape& padded_shape, + DataType dtype, + Layout layout, + const std::optional& tile = std::nullopt); Tensor(Storage storage, TensorSpec tensor_spec); // Constructors to initialize unpopulated tensor with workers and storage specified. Use this when creating tensor @@ -198,7 +205,7 @@ struct Tensor { Tensor to(Layout target_layout, distributed::MeshDevice* mesh_device) const; Tensor pad( - const tt::tt_metal::LegacyShape& output_tensor_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) const; diff --git a/ttnn/cpp/ttnn/tensor/tensor_impl.cpp b/ttnn/cpp/ttnn/tensor/tensor_impl.cpp index 3f731c97c654..51c038657987 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_impl.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor_impl.cpp @@ -175,7 +175,7 @@ void validate_on_device_dtype_and_layout( Tensor pad_bfloat8_b( const Tensor& tensor, - const tt::tt_metal::LegacyShape& output_tensor_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) { auto tile = tensor.get_tensor_spec().tile(); @@ -189,19 +189,22 @@ Tensor pad_bfloat8_b( auto float_tensor = Tensor( OwnedStorage{input_float_buffer}, tensor.get_legacy_shape(), DataType::FLOAT32, tensor.get_layout(), tile) - .pad(output_tensor_shape, input_tensor_start, pad_value); + .pad(output_padded_shape, input_tensor_start, pad_value); // Convert back to BFLOAT8_B auto output_float_data = owned_buffer::get_as(float_tensor).get(); auto output_packed_data = pack_fp32_vec_as_bfp8_tiles(output_float_data, /*row_major_input=*/false, /*is_exp_a=*/false, tile); auto output_uint32_buffer = owned_buffer::create(std::move(output_packed_data)); - return Tensor( - std::move(OwnedStorage{std::move(output_uint32_buffer)}), - float_tensor.get_legacy_shape(), - DataType::BFLOAT8_B, - tensor.get_layout(), - tile); + TensorSpec output_spec( + tensor.logical_shape(), + TensorLayout::fromPaddedShape( + DataType::BFLOAT8_B, + tensor.get_tensor_spec().page_config(), + MemoryConfig{}, + tensor.logical_shape(), + output_padded_shape)); + return Tensor(std::move(OwnedStorage{std::move(output_uint32_buffer)}), output_spec); } Tensor unpad_bfloat8_b( @@ -234,7 +237,7 @@ Tensor unpad_bfloat8_b( Tensor pad_bfloat4_b( const Tensor& tensor, - const tt::tt_metal::LegacyShape& output_tensor_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) { auto tile = tensor.get_tensor_spec().tile(); @@ -248,19 +251,22 @@ Tensor pad_bfloat4_b( auto float_tensor = Tensor( OwnedStorage{input_float_buffer}, tensor.get_legacy_shape(), DataType::FLOAT32, tensor.get_layout(), tile) - .pad(output_tensor_shape, input_tensor_start, pad_value); + .pad(output_padded_shape, input_tensor_start, pad_value); // Convert back to BFLOAT4_B auto output_float_data = owned_buffer::get_as(float_tensor).get(); auto output_packed_data = pack_fp32_vec_as_bfp4_tiles(output_float_data, /*row_major_input=*/false, /*is_exp_a=*/false, tile); auto output_uint32_buffer = owned_buffer::create(std::move(output_packed_data)); - return Tensor( - std::move(OwnedStorage{std::move(output_uint32_buffer)}), - float_tensor.get_legacy_shape(), - DataType::BFLOAT4_B, - tensor.get_layout(), - tile); + TensorSpec output_spec( + tensor.logical_shape(), + TensorLayout::fromPaddedShape( + DataType::BFLOAT4_B, + tensor.get_tensor_spec().page_config(), + MemoryConfig{}, + tensor.logical_shape(), + output_padded_shape)); + return Tensor(std::move(OwnedStorage{std::move(output_uint32_buffer)}), output_spec); } Tensor unpad_bfloat4_b( @@ -875,7 +881,15 @@ Tensor to_layout(const Tensor& tensor, Layout target_layout) { raise_unsupported_storage(); } return Tensor( - storage, tensor.get_legacy_shape(), tensor.get_dtype(), target_layout, tensor.get_tensor_spec().tile()); + storage, + TensorSpec( + tensor.get_logical_shape(), + TensorLayout::fromPaddedShape( + tensor.get_dtype(), + PageConfig(target_layout, tensor.get_tensor_spec().tile()), + MemoryConfig{}, + tensor.get_logical_shape(), + tensor.get_padded_shape()))); }, output_storage); } @@ -918,58 +932,62 @@ Tensor to_layout(const Tensor& tensor, Layout target_layout) { template Tensor pad( const Tensor& tensor, - const tt::tt_metal::LegacyShape& output_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) { if (ttnn::distributed::is_multi_device_tensor(tensor)) { return transform(tensor, [&](const Tensor& device_tensor) { - return pad(device_tensor, output_shape, input_tensor_start, pad_value); + return pad(device_tensor, output_padded_shape, input_tensor_start, pad_value); }); } + auto output_spec = TensorSpec( + tensor.get_logical_shape(), + TensorLayout::fromPaddedShape( + tensor.get_dtype(), + tensor.get_tensor_spec().page_config(), + MemoryConfig{}, + tensor.get_logical_shape(), + output_padded_shape)); + auto pad_value_ = static_cast(pad_value); - const auto input_shape = tensor.get_legacy_shape(); + const auto input_padded_shape = tensor.get_padded_shape(); const auto input_strides = tensor.strides(); - const auto input_data_type = tensor.get_dtype(); - - auto pad = [&input_shape, &input_strides, &input_data_type, &output_shape, &input_tensor_start, &pad_value_]( - const auto& input_buffer) { - auto compute_stride = [](const tt::tt_metal::LegacyShape& shape, uint32_t index) { - uint32_t stride = 1; - for (auto i = index + 1; i < shape.rank(); i++) { - stride *= shape[i]; - } - return stride; - }; + auto output_strides = output_spec.compute_strides(); + auto tensor_padded_shape = tensor.padded_shape(); + auto pad = [&](const auto& input_buffer) { ttnn::SmallVector> pad_size{}; - ttnn::SmallVector input_strides{}; - ttnn::SmallVector output_strides{}; - ttnn::SmallVector input_indices(input_shape.rank(), 0); + ttnn::SmallVector input_indices(tensor.padded_shape().rank(), 0); + + for (int index = 0; index < output_padded_shape.rank(); index++) { + uint32_t out_dim = output_padded_shape[index]; + + int tensor_idx = + index + static_cast(tensor_padded_shape.size()) - static_cast(output_padded_shape.size()); + uint32_t tensor_dim = tensor_idx >= 0 ? tensor_padded_shape[tensor_idx] : 1; + + int start_idx = + index + static_cast(input_tensor_start.size()) - static_cast(output_padded_shape.size()); + uint32_t start = start_idx >= 0 ? input_tensor_start[start_idx] : 0; - for (auto index = 0; index < output_shape.rank(); index++) { // Check if input tensor fits in output tensor given the input tensor start indices - TT_ASSERT( - input_shape[index] + input_tensor_start[index] <= output_shape[index], "Input tensor is out of bounds"); + TT_ASSERT(tensor_dim + start <= out_dim, "Input tensor is out of bounds"); // Figure out pad size on each dim - pad_size.push_back( - {input_tensor_start[index], output_shape[index] - input_shape[index] - input_tensor_start[index]}); - - input_strides.push_back(compute_stride(input_shape, index)); - output_strides.push_back(compute_stride(output_shape, index)); + pad_size.push_back({start, out_dim - tensor_dim - start}); } auto flat_output_index = 0; - auto output_buffer = owned_buffer::create(compute_volume(output_shape)); + auto output_buffer = owned_buffer::create(output_spec.padded_shape().volume()); std::function pad_to_tile = [&](std::size_t dim) -> void { for (auto i = 0; i < pad_size[dim][0] * output_strides[dim]; i++) { output_buffer[flat_output_index++] = pad_value_; } - for (auto i = 0; i < input_shape[dim]; i++) { + for (auto i = 0; i < input_padded_shape[dim]; i++) { input_indices[dim] = i; - if (dim == input_shape.rank() - 1) { + if (dim == input_padded_shape.rank() - 1) { auto flat_input_index = compute_flat_input_index(input_indices, input_strides); output_buffer[flat_output_index++] = input_buffer[flat_input_index]; } else { @@ -1006,61 +1024,56 @@ Tensor pad( } }, tensor.get_storage()); - return Tensor( - OwnedStorage{output_buffer}, - output_shape, - tensor.get_dtype(), - tensor.get_layout(), - tensor.get_tensor_spec().tile()); + return Tensor(OwnedStorage{output_buffer}, output_spec); } template Tensor pad( const Tensor& tensor, - const tt::tt_metal::LegacyShape& output_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); template Tensor pad( const Tensor& tensor, - const tt::tt_metal::LegacyShape& output_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); template Tensor pad( const Tensor& tensor, - const tt::tt_metal::LegacyShape& output_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); template Tensor pad( const Tensor& tensor, - const tt::tt_metal::LegacyShape& output_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); template Tensor pad( const Tensor& tensor, - const tt::tt_metal::LegacyShape& output_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); template Tensor pad( const Tensor& tensor, - const tt::tt_metal::LegacyShape& output_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); template <> Tensor pad( const Tensor& tensor, - const tt::tt_metal::LegacyShape& output_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) { - return pad_bfloat8_b(tensor, output_shape, input_tensor_start, pad_value); + return pad_bfloat8_b(tensor, output_padded_shape, input_tensor_start, pad_value); } template <> Tensor pad( const Tensor& tensor, - const tt::tt_metal::LegacyShape& output_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) { - return pad_bfloat4_b(tensor, output_shape, input_tensor_start, pad_value); + return pad_bfloat4_b(tensor, output_padded_shape, input_tensor_start, pad_value); } template diff --git a/ttnn/cpp/ttnn/tensor/tensor_impl.hpp b/ttnn/cpp/ttnn/tensor/tensor_impl.hpp index a6db8b143881..e0bb1149ef70 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_impl.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor_impl.hpp @@ -99,6 +99,9 @@ static ttnn::SmallVector to_4D_shape(const tt::tt_metal::LegacyShape& template typename BufferType> inline std::vector convert_layout_row_major_to_tile( const Size& shape, const Tile& tile, const BufferType& data_to_convert) { + if (shape.width() * shape.height() == 0) { + return std::vector(); + } TT_FATAL( (shape.height() % tile.get_tile_shape()[0] == 0 && shape.width() % tile.get_tile_shape()[1] == 0), "Unsupported shape for tensor conversion from row-major to tile layout. The tensor shape height and width must " @@ -211,7 +214,7 @@ Tensor to_layout_bfloat(const Tensor& tensor, Layout target_layout); template Tensor pad( const Tensor& tensor, - const tt::tt_metal::LegacyShape& output_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); diff --git a/ttnn/cpp/ttnn/tensor/tensor_ops.cpp b/ttnn/cpp/ttnn/tensor/tensor_ops.cpp index c2df9f3e4307..b5ca7d6dbb76 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_ops.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor_ops.cpp @@ -254,12 +254,12 @@ void tensor_print(const Tensor& input_tensor) { Tensor tensor_pad( const Tensor& input_tensor, - const tt::tt_metal::LegacyShape& output_tensor_shape, + const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) { ZoneScoped; GraphTracker::instance().track_function_start( - "Tensor::pad", input_tensor, output_tensor_shape, input_tensor_start, pad_value); + "Tensor::pad", input_tensor, output_padded_shape, input_tensor_start, pad_value); TT_ASSERT( input_tensor.storage_type() == StorageType::OWNED or input_tensor.storage_type() == StorageType::MULTI_DEVICE_HOST or @@ -273,17 +273,7 @@ Tensor tensor_pad( return input_tensor; } - auto input_shape = input_tensor.get_legacy_shape(); - auto dimensions_pads = std::vector(); - for (auto index = 0; index < input_shape.rank(); index++) { - auto front = input_tensor_start[index]; - auto back = output_tensor_shape[index] - (input_tensor_start[index] + input_shape[index]); - dimensions_pads.push_back(Padding::PadDimension{.front = front, .back = back}); - } - const auto padding = Padding(dimensions_pads, Padding::PadValue::Any); - auto output_shape_with_padding = tt::tt_metal::LegacyShape(output_tensor_shape, padding); - - auto output = tensor_impl::pad_wrapper(input_tensor, output_shape_with_padding, input_tensor_start, pad_value); + auto output = tensor_impl::pad_wrapper(input_tensor, output_padded_shape, input_tensor_start, pad_value); output = tt::tt_metal::set_tensor_id(output); GraphTracker::instance().track_function_end(output); return output; @@ -306,30 +296,26 @@ Tensor tensor_unpad( Tensor tensor_pad_to_tile(const Tensor& input_tensor, float pad_value) { ZoneScoped; GraphTracker::instance().track_function_start("Tensor::pad_to_tile", input_tensor, pad_value); - uint32_t height = input_tensor.get_legacy_shape()[-2]; - uint32_t width = input_tensor.get_legacy_shape()[-1]; + uint32_t height = input_tensor.get_logical_shape()[-2]; + uint32_t width = input_tensor.get_logical_shape()[-1]; uint32_t padded_height = round_up(height, constants::TILE_HEIGHT); uint32_t padded_width = round_up(width, constants::TILE_WIDTH); ttnn::SmallVector shape; - ttnn::SmallVector padded_shape; ttnn::SmallVector input_tensor_start; - for (auto index = 0; index < input_tensor.get_legacy_shape().rank() - 2; index++) { - shape.push_back(input_tensor.get_legacy_shape().without_padding()[index]); - padded_shape.push_back(input_tensor.get_legacy_shape()[index]); + for (auto index = 0; index < input_tensor.get_logical_shape().rank() - 2; index++) { + shape.push_back(input_tensor.get_logical_shape()[index]); input_tensor_start.push_back(0); } - shape.push_back(height); - shape.push_back(width); - padded_shape.push_back(padded_height); - padded_shape.push_back(padded_width); + shape.push_back(padded_height); + shape.push_back(padded_width); input_tensor_start.push_back(0); input_tensor_start.push_back(0); auto output = input_tensor.pad( - tt::tt_metal::LegacyShape(shape, padded_shape), ttnn::SimpleShape{std::move(input_tensor_start)}, pad_value); + ttnn::SimpleShape(std::move(shape)), ttnn::SimpleShape{std::move(input_tensor_start)}, pad_value); output = tt::tt_metal::set_tensor_id(output); GraphTracker::instance().track_function_end(output); return output; @@ -368,19 +354,7 @@ Tensor tensor_unpad_from_tile(const Tensor& input_tensor, const ttnn::SimpleShap Tensor tensor_reshape(const Tensor& input_tensor, const ttnn::Shape& new_shape) { ZoneScoped; GraphTracker::instance().track_function_start("Tensor::reshape", input_tensor, new_shape); - const auto& new_padded_shape = new_shape.padded_shape(); const auto tile = input_tensor.get_tensor_spec().tile(); - TT_ASSERT( - input_tensor.volume() == new_padded_shape.volume(), - "{} != {}", - input_tensor.volume(), - new_padded_shape.volume()); - if (input_tensor.get_layout() == Layout::TILE) { - TT_ASSERT( - new_padded_shape[-2] % tile.get_tile_shape()[0] == 0 && - new_padded_shape[-1] % tile.get_tile_shape()[1] == 0 && - "Expected a multiple of 32 for H, W (or -1 evaluating to such) in Tensor::reshape()!"); - } auto output = std::visit( [&input_tensor, &new_shape, &tile](auto&& storage) -> Tensor { using T = std::decay_t; @@ -424,12 +398,12 @@ Tensor tensor_reshape(const Tensor& input_tensor, const ttnn::Shape& new_shape) if (new_shape[-1] == 0 || shard_shape[1] == 0) { mul_div = 0; } else { - mul_div = new_shape[-1] > shard_shape[1] ? - (new_shape[-1] / shard_shape[1]) : - (shard_shape[1] / new_shape[-1]); + mul_div = new_shape[-1] > shard_shape[1] ? (new_shape[-1] / shard_shape[1]) + : (shard_shape[1] / new_shape[-1]); } - shard_spec.shape[0] = new_shape[-1] > shard_shape[1] ? shard_shape[0] / mul_div : shard_shape[0] * mul_div; + shard_spec.shape[0] = + new_shape[-1] > shard_shape[1] ? shard_shape[0] / mul_div : shard_shape[0] * mul_div; shard_spec.shape[1] = new_shape[-1]; shard_spec_buffer.page_shape = {1, new_shape[-1]}; diff --git a/ttnn/cpp/ttnn/tensor/tensor_ops.hpp b/ttnn/cpp/ttnn/tensor/tensor_ops.hpp index b65af33cb42a..392ae6e76654 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_ops.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor_ops.hpp @@ -45,7 +45,7 @@ void tensor_print(const Tensor& input_tensor); Tensor tensor_pad( const Tensor& input_tensor, - const tt::tt_metal::LegacyShape& output_tensor_shape, + const ttnn::SimpleShape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); diff --git a/ttnn/cpp/ttnn/tensor/types.hpp b/ttnn/cpp/ttnn/tensor/types.hpp index 33fd91e8b402..284520834d04 100644 --- a/ttnn/cpp/ttnn/tensor/types.hpp +++ b/ttnn/cpp/ttnn/tensor/types.hpp @@ -206,14 +206,13 @@ class LegacyShape { } } explicit LegacyShape(tt::stl::Span shape, tt::stl::Span shape_with_tile_padding) : - rank_(shape.size()), dimensions_{}, padding_{shape.size()} { - TT_ASSERT( - shape.size() == shape_with_tile_padding.size(), - "Shape and shape_with_tile_padding must have the same size"); - for (auto index = 0; index < shape.size(); index++) { - auto padded_dimension = shape_with_tile_padding[index]; + rank_(shape_with_tile_padding.size()), dimensions_{}, padding_{shape_with_tile_padding.size()} { + for (int index = 0; index < shape_with_tile_padding.size(); index++) { + int shape_index = index + static_cast(shape.size()) - static_cast(shape_with_tile_padding.size()); + int dimension = shape_index >= 0 ? shape[shape_index] : 1; + int padded_dimension = shape_with_tile_padding[index]; this->dimensions_[index] = padded_dimension; - this->padding_[index] = {.front = 0, .back = padded_dimension - shape[index]}; + this->padding_[index] = {.front = 0, .back = static_cast(padded_dimension - dimension)}; } } explicit LegacyShape(const ttnn::SmallVector& shape, const ttnn::SmallVector& shape_with_tile_padding) From 81782cc5955375e0ea6bc6ea58a88c56b14420b3 Mon Sep 17 00:00:00 2001 From: Stanislav Minakov Date: Thu, 19 Dec 2024 01:13:39 -0800 Subject: [PATCH 58/87] Port all Moreh OPs to compute_output_specs (#16160) ### Ticket ### Problem description We're continuing to port all OPs from compute_output_shapes to the new compute_output_specs ### What's changed Ported all Moreh OPs to compute_output_specs ### Checklist - [x] [Post commit CI passes](https://github.com/tenstorrent/tt-metal/actions/runs/12408552437) - [ ] Blackhole Post commit (if applicable) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [ ] New/Existing tests provide coverage for changes --- .../device/moreh_abs_pow_device_operation.cpp | 19 +++-- .../device/moreh_abs_pow_device_operation.hpp | 4 +- .../device/moreh_adam_device_operation.cpp | 70 ++++++++----------- .../device/moreh_adam_device_operation.hpp | 4 +- .../device/moreh_adamw_device_operation.cpp | 58 ++++++++++----- .../device/moreh_adamw_device_operation.hpp | 4 +- .../device/moreh_arange_device_operation.cpp | 36 ++++------ .../device/moreh_arange_device_operation.hpp | 4 +- ..._clip_grad_norm_step1_device_operation.cpp | 4 +- ..._clip_grad_norm_step1_device_operation.hpp | 4 +- ..._clip_grad_norm_step2_device_operation.cpp | 18 ++--- ..._clip_grad_norm_step2_device_operation.hpp | 4 +- ..._clip_grad_norm_step3_device_operation.cpp | 9 ++- ..._clip_grad_norm_step3_device_operation.hpp | 4 +- .../device/moreh_cumsum_device_operation.cpp | 19 ++--- .../device/moreh_cumsum_device_operation.hpp | 4 +- .../device/moreh_dot_device_operation.cpp | 16 ++--- .../device/moreh_dot_device_operation.hpp | 4 +- .../moreh_dot_backward_device_operation.cpp | 14 +++- .../moreh_dot_backward_device_operation.hpp | 4 +- .../device/fold_device_operation.cpp | 42 +++++------ .../device/fold_device_operation.hpp | 4 +- .../device/moreh_getitem_device_operation.cpp | 27 +++---- .../device/moreh_getitem_device_operation.hpp | 4 +- .../moreh_group_norm_device_operation.cpp | 58 ++++++++++----- .../moreh_group_norm_device_operation.hpp | 4 +- ...kward_gamma_beta_grad_device_operation.cpp | 52 ++++++++++---- ...kward_gamma_beta_grad_device_operation.hpp | 4 +- ...m_backward_input_grad_device_operation.cpp | 20 +++--- ...m_backward_input_grad_device_operation.hpp | 4 +- .../moreh_layer_norm_device_operation.cpp | 68 +++++++----------- .../moreh_layer_norm_device_operation.hpp | 4 +- ...kward_gamma_beta_grad_device_operation.cpp | 28 ++++---- ...kward_gamma_beta_grad_device_operation.hpp | 4 +- ...m_backward_input_grad_device_operation.cpp | 21 +++--- ...m_backward_input_grad_device_operation.hpp | 4 +- ...moreh_linear_backward_device_operation.cpp | 22 +++--- ...moreh_linear_backward_device_operation.hpp | 4 +- .../device/moreh_matmul_device_operation.cpp | 69 +++--------------- .../device/moreh_matmul_device_operation.hpp | 4 +- .../device/moreh_mean_device_operation.cpp | 31 +++++--- .../device/moreh_mean_device_operation.hpp | 4 +- .../moreh_mean_backward_device_operation.cpp | 38 +++------- .../moreh_mean_backward_device_operation.hpp | 4 +- .../moreh_nll_loss_step1_device_operation.cpp | 15 ++-- .../moreh_nll_loss_step1_device_operation.hpp | 4 +- .../moreh_nll_loss_step2_device_operation.cpp | 37 +++------- .../moreh_nll_loss_step2_device_operation.hpp | 4 +- ...reh_nll_loss_backward_device_operation.cpp | 15 ++-- ...reh_nll_loss_backward_device_operation.hpp | 4 +- ...ss_unreduced_backward_device_operation.cpp | 17 +++-- ...ss_unreduced_backward_device_operation.hpp | 4 +- .../device/moreh_norm_device_operation.cpp | 37 ++++------ .../device/moreh_norm_device_operation.hpp | 4 +- .../moreh_norm_backward_device_operation.cpp | 20 +++--- .../moreh_norm_backward_device_operation.hpp | 4 +- .../device/moreh_sgd_device_operation.cpp | 45 +++++++----- .../device/moreh_sgd_device_operation.hpp | 4 +- .../device/moreh_softmax_device_operation.cpp | 18 +++-- .../device/moreh_softmax_device_operation.hpp | 4 +- ...oreh_softmax_backward_device_operation.cpp | 20 +++--- ...oreh_softmax_backward_device_operation.hpp | 4 +- .../device/moreh_sum_device_operation.cpp | 21 +++--- .../device/moreh_sum_device_operation.hpp | 4 +- .../moreh_sum_backward_device_operation.cpp | 19 +++-- .../moreh_sum_backward_device_operation.hpp | 4 +- 66 files changed, 567 insertions(+), 568 deletions(-) diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_abs_pow/device/moreh_abs_pow_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_abs_pow/device/moreh_abs_pow_device_operation.cpp index b6b134ef637b..d4b8525961e6 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_abs_pow/device/moreh_abs_pow_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_abs_pow/device/moreh_abs_pow_device_operation.cpp @@ -44,12 +44,16 @@ void MorehAbsPowOperation::validate_on_program_cache_hit( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { validate_tensors(operation_attributes, tensor_args); }; -MorehAbsPowOperation::shape_return_value_t MorehAbsPowOperation::compute_output_shapes( +MorehAbsPowOperation::spec_return_value_t MorehAbsPowOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { + if (tensor_args.output.has_value()) { + return tensor_args.output->get_tensor_spec(); + } const auto& input = tensor_args.input; - const auto& input_shape = input.get_shape(); - return input_shape; -}; + return TensorSpec( + input.get_logical_shape(), + TensorLayout(input.get_dtype(), PageConfig(input.get_layout()), operation_attributes.memory_config)); +} MorehAbsPowOperation::tensor_return_value_t MorehAbsPowOperation::create_output_tensors( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { @@ -59,12 +63,7 @@ MorehAbsPowOperation::tensor_return_value_t MorehAbsPowOperation::create_output_ } log_debug(tt::LogOp, "{}:{} create output tensor", __func__, __LINE__); - return create_device_tensor( - compute_output_shapes(operation_attributes, tensor_args), - tensor_args.input.get_dtype(), - tensor_args.input.get_layout(), - tensor_args.input.device(), - operation_attributes.memory_config); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), tensor_args.input.device()); }; std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_abs_pow/device/moreh_abs_pow_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_abs_pow/device/moreh_abs_pow_device_operation.hpp index 586c82d16c1d..d7117e0d6b50 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_abs_pow/device/moreh_abs_pow_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_abs_pow/device/moreh_abs_pow_device_operation.hpp @@ -49,7 +49,7 @@ struct MorehAbsPowOperation { const std::optional& output; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; MOREH_ABS_POW_FACTORY_H(MorehAbsPowFactory) @@ -58,7 +58,7 @@ struct MorehAbsPowOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& input, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_adam/device/moreh_adam_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_adam/device/moreh_adam_device_operation.cpp index ea0112725b4d..0a1cd77ecce4 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_adam/device/moreh_adam_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_adam/device/moreh_adam_device_operation.cpp @@ -62,57 +62,49 @@ void MorehAdamOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehAdamOperation::shape_return_value_t MorehAdamOperation::compute_output_shapes( +MorehAdamOperation::spec_return_value_t MorehAdamOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - auto input_tensor_shape = tensor_args.param_in.get_shape(); + auto output_shape = tensor_args.param_in.get_logical_shape(); + auto dtype = tensor_args.param_in.get_dtype(); - return { - input_tensor_shape, - input_tensor_shape, - input_tensor_shape, - input_tensor_shape, - }; -}; + std::vector> ret; + TensorSpec out_spec( + output_shape, TensorLayout(dtype, PageConfig(Layout::TILE), operation_attributes.memory_config)); + for (int idx = 0; idx < 3; idx++) { + if (tensor_args.output_tensors.at(idx).has_value()) { + ret.push_back(tensor_args.output_tensors.at(idx)->get_tensor_spec()); + } else { + ret.push_back(out_spec); + } + } + if (tensor_args.output_tensors.at(3).has_value()) { + ret.push_back(tensor_args.output_tensors.at(3)->get_tensor_spec()); + } else if (operation_attributes.amsgrad) { + ret.push_back(out_spec); + } else { + ret.push_back(std::nullopt); + } + + return ret; +} MorehAdamOperation::tensor_return_value_t MorehAdamOperation::create_output_tensors( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - const auto& output_shapes = compute_output_shapes(operation_attributes, tensor_args); - auto dtype = tensor_args.param_in.get_dtype(); - Layout layout{Layout::TILE}; + const auto output_specs = compute_output_specs(operation_attributes, tensor_args); auto device = tensor_args.param_in.device(); std::vector> ret; auto memory_config = operation_attributes.memory_config; - auto idx = uint32_t{0}; - if (tensor_args.output_tensors.at(idx).has_value()) { - ret.push_back(tensor_args.output_tensors.at(idx).value()); - } else { - ret.push_back(create_device_tensor(output_shapes.at(idx).value(), dtype, layout, device, memory_config)); - } - ++idx; - - if (tensor_args.output_tensors.at(idx).has_value()) { - ret.push_back(tensor_args.output_tensors.at(idx).value()); - } else { - ret.push_back(create_device_tensor(output_shapes.at(idx).value(), dtype, layout, device, memory_config)); - } - ++idx; - - if (tensor_args.output_tensors.at(idx).has_value()) { - ret.push_back(tensor_args.output_tensors.at(idx).value()); - } else { - ret.push_back(create_device_tensor(output_shapes.at(idx).value(), dtype, layout, device, memory_config)); - } - ++idx; - - if (tensor_args.output_tensors.at(idx).has_value()) { - ret.push_back(tensor_args.output_tensors.at(idx).value()); - } else if (operation_attributes.amsgrad) { - ret.push_back(create_device_tensor(output_shapes.at(idx).value(), dtype, layout, device, memory_config)); + for (size_t idx = 0; idx < output_specs.size(); idx++) { + if (tensor_args.output_tensors.at(idx).has_value()) { + ret.push_back(tensor_args.output_tensors.at(idx).value()); + } else if (output_specs[idx].has_value()) { + ret.push_back(create_device_tensor(*output_specs[idx], device)); + } } - return std::move(ret); + return ret; } std::tuple MorehAdamOperation::invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_adam/device/moreh_adam_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_adam/device/moreh_adam_device_operation.hpp index dfd6edaf9225..17eab21fb797 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_adam/device/moreh_adam_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_adam/device/moreh_adam_device_operation.hpp @@ -37,7 +37,7 @@ struct MorehAdamOperation { const std::vector> output_tensors; }; - using shape_return_value_t = std::vector>; + using spec_return_value_t = std::vector>; using tensor_return_value_t = std::vector>; struct ProgramFactory { @@ -72,7 +72,7 @@ struct MorehAdamOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& param_in, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_adamw/device/moreh_adamw_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_adamw/device/moreh_adamw_device_operation.cpp index 0d656b9451b7..0ebdbdce5fba 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_adamw/device/moreh_adamw_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_adamw/device/moreh_adamw_device_operation.cpp @@ -65,51 +65,73 @@ void MorehAdamWDeviceOperation::validate_on_program_cache_hit( validate_inputs(attributes, tensor_args); } -MorehAdamWDeviceOperation::shape_return_value_t MorehAdamWDeviceOperation::compute_output_shapes( +MorehAdamWDeviceOperation::spec_return_value_t MorehAdamWDeviceOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - auto input_tensor_shape = tensor_args.param_in.get_shape(); + auto output_shape = tensor_args.param_in.get_logical_shape(); + auto dtype = tensor_args.param_in.get_dtype(); + auto memory_config = operation_attributes.memory_config; - return { - input_tensor_shape, - input_tensor_shape, - input_tensor_shape, - input_tensor_shape, - }; + std::vector> result; + TensorSpec outSpec(output_shape, TensorLayout(dtype, PageConfig(Layout::TILE), memory_config)); + + if (tensor_args.param_out.has_value()) { + result.push_back(tensor_args.param_out->get_tensor_spec()); + } else { + result.push_back(outSpec); + } + + if (tensor_args.exp_avg_out.has_value()) { + result.push_back(tensor_args.exp_avg_out->get_tensor_spec()); + } else { + result.push_back(outSpec); + } + + if (tensor_args.exp_avg_sq_out.has_value()) { + result.push_back(tensor_args.exp_avg_sq_out->get_tensor_spec()); + } else { + result.push_back(outSpec); + } + + if (tensor_args.max_exp_avg_sq_out.has_value()) { + result.push_back(tensor_args.max_exp_avg_sq_out->get_tensor_spec()); + } else if (operation_attributes.amsgrad) { + result.push_back(outSpec); + } else { + result.push_back(std::nullopt); + } + + return result; } MorehAdamWDeviceOperation::tensor_return_value_t MorehAdamWDeviceOperation::create_output_tensors( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - auto output_shapes = compute_output_shapes(operation_attributes, tensor_args); - auto dtype = tensor_args.param_in.get_dtype(); - Layout layout{Layout::TILE}; + auto output_specs = compute_output_specs(operation_attributes, tensor_args); auto device = tensor_args.param_in.device(); tensor_return_value_t result; - auto memory_config = operation_attributes.memory_config; - if (tensor_args.param_out.has_value()) { result.push_back(tensor_args.param_out.value()); } else { - result.push_back(create_device_tensor(output_shapes.at(0), dtype, layout, device, memory_config)); + result.push_back(create_device_tensor(*output_specs[0], device)); } if (tensor_args.exp_avg_out.has_value()) { result.push_back(tensor_args.exp_avg_out.value()); } else { - result.push_back(create_device_tensor(output_shapes.at(1), dtype, layout, device, memory_config)); + result.push_back(create_device_tensor(*output_specs[1], device)); } if (tensor_args.exp_avg_sq_out.has_value()) { result.push_back(tensor_args.exp_avg_sq_out.value()); } else { - result.push_back(create_device_tensor(output_shapes.at(2), dtype, layout, device, memory_config)); + result.push_back(create_device_tensor(*output_specs[2], device)); } if (tensor_args.max_exp_avg_sq_out.has_value()) { result.push_back(tensor_args.max_exp_avg_sq_out.value()); - } else if (operation_attributes.amsgrad) { - result.push_back(create_device_tensor(output_shapes.at(3), dtype, layout, device, memory_config)); + } else if (output_specs[3].has_value()) { + result.push_back(create_device_tensor(*output_specs[3], device)); } else { result.push_back(std::nullopt); } diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_adamw/device/moreh_adamw_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_adamw/device/moreh_adamw_device_operation.hpp index 67adc9a6d555..5ef19ca4bb6a 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_adamw/device/moreh_adamw_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_adamw/device/moreh_adamw_device_operation.hpp @@ -41,7 +41,7 @@ struct MorehAdamWDeviceOperation { const std::optional& max_exp_avg_sq_out; }; - using shape_return_value_t = std::vector; + using spec_return_value_t = std::vector>; using tensor_return_value_t = std::vector>; @@ -81,7 +81,7 @@ struct MorehAdamWDeviceOperation { static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_arange/device/moreh_arange_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_arange/device/moreh_arange_device_operation.cpp index 0d5187762ba8..4304c3efb2e0 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_arange/device/moreh_arange_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_arange/device/moreh_arange_device_operation.cpp @@ -55,39 +55,33 @@ void MorehArangeOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehArangeOperation::shape_return_value_t MorehArangeOperation::compute_output_shapes( +MorehArangeOperation::spec_return_value_t MorehArangeOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { + if (tensor_args.output.has_value()) { + return tensor_args.output->get_tensor_spec(); + } + uint32_t num_elems = static_cast( ceil((operation_attributes.end - operation_attributes.start) / operation_attributes.step)); if (operation_attributes.untilize_out) { - return ttnn::Shape(tt::tt_metal::LegacyShape({num_elems})); + return TensorSpec( + SimpleShape({num_elems}), + TensorLayout( + operation_attributes.dtype, PageConfig(Layout::ROW_MAJOR), operation_attributes.memory_config)); } - SmallVector output_size = { - tt::constants::TILE_HEIGHT, tt::round_up(num_elems, tt::constants::TILE_WIDTH)}; - - auto dimensions_pads = SmallVector(); - dimensions_pads.push_back(Padding::PadDimension{.front = 0, .back = 31}); - dimensions_pads.push_back( - Padding::PadDimension{.front = 0, .back = tt::round_up(num_elems, tt::constants::TILE_WIDTH) - num_elems}); - const auto padding = Padding(dimensions_pads, Padding::PadValue::Any); - - return ttnn::Shape{tt::tt_metal::LegacyShape(output_size, padding)}; + return TensorSpec( + SimpleShape({1, num_elems}), + TensorLayout(operation_attributes.dtype, PageConfig(Layout::TILE), operation_attributes.memory_config)); }; MorehArangeOperation::tensor_return_value_t MorehArangeOperation::create_output_tensors( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - const auto& output = tensor_args.output; - if (output.has_value()) { - return output.value(); + if (tensor_args.output.has_value()) { + return tensor_args.output.value(); } - return create_device_tensor( - compute_output_shapes(operation_attributes, tensor_args), - operation_attributes.dtype, - operation_attributes.untilize_out ? Layout::ROW_MAJOR : Layout::TILE, - tensor_args.any.device(), - operation_attributes.memory_config); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), tensor_args.any.device()); } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_arange/device/moreh_arange_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_arange/device/moreh_arange_device_operation.hpp index a4eae4dd3930..5e4d31aa3dc7 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_arange/device/moreh_arange_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_arange/device/moreh_arange_device_operation.hpp @@ -23,7 +23,7 @@ struct MorehArangeOperation { const std::optional& output; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct ProgramFactory { @@ -53,7 +53,7 @@ struct MorehArangeOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step1/device/moreh_clip_grad_norm_step1_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step1/device/moreh_clip_grad_norm_step1_device_operation.cpp index 65127d37be5f..0778f8256bf6 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step1/device/moreh_clip_grad_norm_step1_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step1/device/moreh_clip_grad_norm_step1_device_operation.cpp @@ -36,9 +36,9 @@ void MorehClipGradNormStep1Operation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehClipGradNormStep1Operation::shape_return_value_t MorehClipGradNormStep1Operation::compute_output_shapes( +MorehClipGradNormStep1Operation::spec_return_value_t MorehClipGradNormStep1Operation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - return {}; + return tensor_args.tmp_pow_sum.get_tensor_spec(); }; MorehClipGradNormStep1Operation::tensor_return_value_t MorehClipGradNormStep1Operation::create_output_tensors( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step1/device/moreh_clip_grad_norm_step1_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step1/device/moreh_clip_grad_norm_step1_device_operation.hpp index 518a4b03c968..80afbe6dd6a0 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step1/device/moreh_clip_grad_norm_step1_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step1/device/moreh_clip_grad_norm_step1_device_operation.hpp @@ -26,7 +26,7 @@ struct MorehClipGradNormStep1Operation { const Tensor& tmp_pow_sum; }; - using shape_return_value_t = SimpleShape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct ProgramFactory { @@ -58,7 +58,7 @@ struct MorehClipGradNormStep1Operation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const std::vector& inputs, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step2/device/moreh_clip_grad_norm_step2_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step2/device/moreh_clip_grad_norm_step2_device_operation.cpp index 29b009cf9b0a..41cadd155d18 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step2/device/moreh_clip_grad_norm_step2_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step2/device/moreh_clip_grad_norm_step2_device_operation.cpp @@ -35,10 +35,17 @@ void MorehClipGradNormStep2Operation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehClipGradNormStep2Operation::shape_return_value_t MorehClipGradNormStep2Operation::compute_output_shapes( +MorehClipGradNormStep2Operation::spec_return_value_t MorehClipGradNormStep2Operation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { + if (tensor_args.total_norm.has_value()) { + return tensor_args.total_norm->get_tensor_spec(); + } + // output total_norm 1 element - return SimpleShape{1, 1}; + return TensorSpec( + SimpleShape{1, 1}, + TensorLayout( + tensor_args.tmp_pow_sum.get_dtype(), PageConfig(Layout::TILE), operation_attributes.memory_config)); }; MorehClipGradNormStep2Operation::tensor_return_value_t MorehClipGradNormStep2Operation::create_output_tensors( @@ -46,14 +53,9 @@ MorehClipGradNormStep2Operation::tensor_return_value_t MorehClipGradNormStep2Ope if (tensor_args.total_norm.has_value()) { return tensor_args.total_norm.value(); } - const auto& total_norm_shape = compute_output_shapes(operation_attributes, tensor_args); return create_device_tensor( - total_norm_shape, - tensor_args.tmp_pow_sum.get_dtype(), - Layout::TILE, - tensor_args.tmp_pow_sum.device(), - operation_attributes.memory_config); + compute_output_specs(operation_attributes, tensor_args), tensor_args.tmp_pow_sum.device()); }; std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step2/device/moreh_clip_grad_norm_step2_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step2/device/moreh_clip_grad_norm_step2_device_operation.hpp index 73d1c514d6fd..01bce7483f5f 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step2/device/moreh_clip_grad_norm_step2_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step2/device/moreh_clip_grad_norm_step2_device_operation.hpp @@ -27,7 +27,7 @@ struct MorehClipGradNormStep2Operation { const std::optional& total_norm; }; - using shape_return_value_t = SimpleShape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct ProgramFactory { @@ -58,7 +58,7 @@ struct MorehClipGradNormStep2Operation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& tmp_pow_sum, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step3/device/moreh_clip_grad_norm_step3_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step3/device/moreh_clip_grad_norm_step3_device_operation.cpp index 823cbd24cc14..4b2b173461f8 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step3/device/moreh_clip_grad_norm_step3_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step3/device/moreh_clip_grad_norm_step3_device_operation.cpp @@ -37,9 +37,14 @@ void MorehClipGradNormStep3Operation::validate_on_program_cache_hit( }; // No output -MorehClipGradNormStep3Operation::shape_return_value_t MorehClipGradNormStep3Operation::compute_output_shapes( +MorehClipGradNormStep3Operation::spec_return_value_t MorehClipGradNormStep3Operation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - return {}; + std::vector output_specs; + output_specs.reserve(tensor_args.inputs.size()); + for (const auto& input : tensor_args.inputs) { + output_specs.push_back(input.get_tensor_spec()); + } + return output_specs; }; // No output diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step3/device/moreh_clip_grad_norm_step3_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step3/device/moreh_clip_grad_norm_step3_device_operation.hpp index d9bca4398558..a123a1bdd5c1 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step3/device/moreh_clip_grad_norm_step3_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_clip_grad_norm/moreh_clip_grad_norm_step3/device/moreh_clip_grad_norm_step3_device_operation.hpp @@ -26,7 +26,7 @@ struct MorehClipGradNormStep3Operation { const Tensor& clip_coef_clamped; }; - using shape_return_value_t = SimpleShape; + using spec_return_value_t = std::vector; using tensor_return_value_t = std::vector; struct ProgramFactory { @@ -57,7 +57,7 @@ struct MorehClipGradNormStep3Operation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const std::vector& inputs, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_cumsum/device/moreh_cumsum_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_cumsum/device/moreh_cumsum_device_operation.cpp index 021e6346446f..754c592da292 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_cumsum/device/moreh_cumsum_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_cumsum/device/moreh_cumsum_device_operation.cpp @@ -53,23 +53,24 @@ void MorehCumsumDeviceOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehCumsumDeviceOperation::shape_return_value_t MorehCumsumDeviceOperation::compute_output_shapes( +MorehCumsumDeviceOperation::spec_return_value_t MorehCumsumDeviceOperation::compute_output_specs( const operation_attributes_t&, const tensor_args_t& tensor_args) { + if (tensor_args.output.has_value()) { + return tensor_args.output->get_tensor_spec(); + } + const auto& input = tensor_args.input; - auto output_shape = input.get_shape(); - return output_shape; + return TensorSpec( + input.get_logical_shape(), TensorLayout(input.dtype(), PageConfig(input.layout()), MemoryConfig{})); } MorehCumsumDeviceOperation::tensor_return_value_t MorehCumsumDeviceOperation::create_output_tensors( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - const auto& input = tensor_args.input; - const auto& output = tensor_args.output; - if (output.has_value()) { - return output.value(); + if (tensor_args.output.has_value()) { + return tensor_args.output.value(); } - auto output_shape = compute_output_shapes(operation_attributes, tensor_args); - return create_device_tensor(output_shape, input.dtype(), input.layout(), input.device()); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), tensor_args.input.device()); } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_cumsum/device/moreh_cumsum_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_cumsum/device/moreh_cumsum_device_operation.hpp index 0b4006a0cc7b..29ce107fa762 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_cumsum/device/moreh_cumsum_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_cumsum/device/moreh_cumsum_device_operation.hpp @@ -23,7 +23,7 @@ struct MorehCumsumDeviceOperation { const std::optional& output; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct ProgramFactory { @@ -54,7 +54,7 @@ struct MorehCumsumDeviceOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& input, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_dot/device/moreh_dot_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_dot/device/moreh_dot_device_operation.cpp index 19923b819b7e..26345c2121b5 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_dot/device/moreh_dot_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_dot/device/moreh_dot_device_operation.cpp @@ -47,15 +47,16 @@ void MorehDotOperation::validate_on_program_cache_hit( validate(operation_attributes, tensor_args); } -MorehDotOperation::shape_return_value_t MorehDotOperation::compute_output_shapes( +MorehDotOperation::spec_return_value_t MorehDotOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { if (tensor_args.output.has_value()) { - return tensor_args.output.value().get_logical_shape(); + return tensor_args.output->get_tensor_spec(); } const auto& input = tensor_args.input_a; auto output_shape = input.get_logical_shape(); output_shape[3] = 1; - return ttnn::SimpleShape{std::move(output_shape)}; + return TensorSpec( + output_shape, TensorLayout(input.dtype(), PageConfig(input.layout()), operation_attributes.memory_config)); } MorehDotOperation::tensor_return_value_t MorehDotOperation::create_output_tensors( @@ -63,14 +64,7 @@ MorehDotOperation::tensor_return_value_t MorehDotOperation::create_output_tensor if (tensor_args.output.has_value()) { return tensor_args.output.value(); } - const auto output_shape = compute_output_shapes(operation_attributes, tensor_args); - const auto& input_tensor = tensor_args.input_a; - return create_device_tensor( - output_shape, - input_tensor.dtype(), - input_tensor.layout(), - input_tensor.device(), - operation_attributes.memory_config); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), tensor_args.input_a.device()); } std::tuple MorehDotOperation::invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_dot/device/moreh_dot_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_dot/device/moreh_dot_device_operation.hpp index 5fe907df84f6..72568bf1f8a4 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_dot/device/moreh_dot_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_dot/device/moreh_dot_device_operation.hpp @@ -25,7 +25,7 @@ struct MorehDotOperation { const std::optional& output; }; - using shape_return_value_t = SimpleShape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct SingleCore { @@ -53,7 +53,7 @@ struct MorehDotOperation { static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); static void validate(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_dot_backward/device/moreh_dot_backward_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_dot_backward/device/moreh_dot_backward_device_operation.cpp index 84e363bf3f90..6a5d09be191f 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_dot_backward/device/moreh_dot_backward_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_dot_backward/device/moreh_dot_backward_device_operation.cpp @@ -69,10 +69,18 @@ void MorehDotBackwardOperation::validate_on_program_cache_hit( validate_tensors(operation_attributes, tensor_args); } -MorehDotBackwardOperation::shape_return_value_t MorehDotBackwardOperation::compute_output_shapes( +MorehDotBackwardOperation::spec_return_value_t MorehDotBackwardOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - TT_FATAL(false, "This operation is in place, and as such, should not be computing output shapes."); - return {}; + std::vector> output_specs; + output_specs.reserve(tensor_args.output_tensors.size()); + for (const auto& output_tensor : tensor_args.output_tensors) { + if (output_tensor.has_value()) { + output_specs.push_back(output_tensor->get_tensor_spec()); + } else { + output_specs.push_back(std::nullopt); + } + } + return output_specs; } MorehDotBackwardOperation::tensor_return_value_t MorehDotBackwardOperation::create_output_tensors( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_dot_backward/device/moreh_dot_backward_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_dot_backward/device/moreh_dot_backward_device_operation.hpp index 7325b32d3f48..2b19874b1d5e 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_dot_backward/device/moreh_dot_backward_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_dot_backward/device/moreh_dot_backward_device_operation.hpp @@ -28,7 +28,7 @@ struct MorehDotBackwardOperation { const std::vector> output_tensors; }; - using shape_return_value_t = std::vector>; + using spec_return_value_t = std::vector>; using tensor_return_value_t = std::vector>; struct SingleCore { @@ -55,7 +55,7 @@ struct MorehDotBackwardOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_fold/device/fold_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_fold/device/fold_device_operation.cpp index 037d471dd132..adc0d462d10b 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_fold/device/fold_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_fold/device/fold_device_operation.cpp @@ -65,22 +65,31 @@ void MorehFoldOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehFoldOperation::shape_return_value_t MorehFoldOperation::compute_output_shapes( +MorehFoldOperation::spec_return_value_t MorehFoldOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { + if (tensor_args.output.has_value()) { + return tensor_args.output->get_tensor_spec(); + } + auto input_tensor_shape = tensor_args.input.get_logical_shape(); auto input_tensor_rank = tensor_args.input.get_logical_shape().rank(); uint32_t kernel_size_product = operation_attributes.kernel_size[0] * operation_attributes.kernel_size[1]; - if (input_tensor_rank == 3) { - uint32_t N = input_tensor_shape[0]; - uint32_t C = input_tensor_shape[1] / kernel_size_product; - auto output_shape = - ttnn::SimpleShape{N, C, operation_attributes.output_size[0], operation_attributes.output_size[1]}; - return output_shape; - } - // If input_tensor_rank == 2 - uint32_t C = input_tensor_shape[0] / kernel_size_product; - auto output_shape = ttnn::SimpleShape{C, operation_attributes.output_size[0], operation_attributes.output_size[1]}; - return output_shape; + auto output_shape = [&] { + if (input_tensor_rank == 3) { + uint32_t N = input_tensor_shape[0]; + uint32_t C = input_tensor_shape[1] / kernel_size_product; + return ttnn::SimpleShape{N, C, operation_attributes.output_size[0], operation_attributes.output_size[1]}; + } + // If input_tensor_rank == 2 + uint32_t C = input_tensor_shape[0] / kernel_size_product; + return ttnn::SimpleShape{C, operation_attributes.output_size[0], operation_attributes.output_size[1]}; + }(); + return TensorSpec( + output_shape, + TensorLayout( + tensor_args.input.get_dtype(), + PageConfig(tensor_args.input.get_layout()), + operation_attributes.memory_config)); }; MorehFoldOperation::tensor_return_value_t MorehFoldOperation::create_output_tensors( @@ -89,14 +98,7 @@ MorehFoldOperation::tensor_return_value_t MorehFoldOperation::create_output_tens return tensor_args.output.value(); } - const auto& output_shape = compute_output_shapes(operation_attributes, tensor_args); - - return create_device_tensor( - output_shape, - tensor_args.input.get_dtype(), - tensor_args.input.get_layout(), - tensor_args.input.device(), - operation_attributes.memory_config); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), tensor_args.input.device()); } std::tuple MorehFoldOperation::invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_fold/device/fold_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_fold/device/fold_device_operation.hpp index b825bc4757b7..2087327dd768 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_fold/device/fold_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_fold/device/fold_device_operation.hpp @@ -25,7 +25,7 @@ struct MorehFoldOperation { const std::optional& output; }; - using shape_return_value_t = SimpleShape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct ProgramFactory { @@ -55,7 +55,7 @@ struct MorehFoldOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& input, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_device_operation.cpp index 641f8bcd23f8..e8611b439738 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_device_operation.cpp @@ -86,11 +86,14 @@ void MorehGetItemOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehGetItemOperation::shape_return_value_t MorehGetItemOperation::compute_output_shapes( +MorehGetItemOperation::spec_return_value_t MorehGetItemOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { + if (tensor_args.output.has_value()) { + return {tensor_args.output->get_tensor_spec()}; + } + const auto& input_tensor = tensor_args.input; const auto index_dims = operation_attributes.index_dims; - auto input_layout = input_tensor.get_layout(); const auto& index_tensors = tensor_args.index_tensors; auto input_shape = input_tensor.get_shape(); auto output_shape = input_shape; @@ -170,8 +173,14 @@ MorehGetItemOperation::shape_return_value_t MorehGetItemOperation::compute_outpu output_shape = Shape(output_size_vec); } - return {output_shape}; -}; + return TensorSpec( + output_shape.logical_shape(), + TensorLayout::fromLegacyPaddedShape( + tensor_args.input.get_dtype(), + PageConfig(tensor_args.input.get_layout()), + operation_attributes.memory_config, + output_shape)); +} MorehGetItemOperation::tensor_return_value_t MorehGetItemOperation::create_output_tensors( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { @@ -180,14 +189,8 @@ MorehGetItemOperation::tensor_return_value_t MorehGetItemOperation::create_outpu return {tensor_args.output.value()}; } log_debug(tt::LogOp, "{}:{} create output tensor", __func__, __LINE__); - const auto& output_shape = compute_output_shapes(operation_attributes, tensor_args); - return create_device_tensor( - output_shape, - tensor_args.input.get_dtype(), - tensor_args.input.get_layout(), - tensor_args.input.device(), - operation_attributes.memory_config); -}; + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), tensor_args.input.device()); +} std::tuple MorehGetItemOperation::invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_device_operation.hpp index 865d9d23e53b..49beda5c4daa 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_getitem/device/moreh_getitem_device_operation.hpp @@ -27,7 +27,7 @@ struct MorehGetItemOperation { const std::optional& output; }; - using shape_return_value_t = ttnn::Shape; + using spec_return_value_t = ttnn::TensorSpec; using tensor_return_value_t = Tensor; struct MorehGetItemRmFactory { @@ -84,7 +84,7 @@ struct MorehGetItemOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& input, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm/device/moreh_group_norm_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm/device/moreh_group_norm_device_operation.cpp index a58b2fd43b45..11a37ec4c3f2 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm/device/moreh_group_norm_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm/device/moreh_group_norm_device_operation.cpp @@ -78,24 +78,53 @@ void MorehGroupNormOperation::validate_on_program_cache_hit( validate_tensors(operation_attributes, tensor_args); }; -MorehGroupNormOperation::shape_return_value_t MorehGroupNormOperation::compute_output_shapes( +MorehGroupNormOperation::spec_return_value_t MorehGroupNormOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { using namespace tt::constants; + auto dtype = tensor_args.input.get_dtype(); + Layout layout{Layout::TILE}; // mean, rstd (1, 1, N, num_groups) const auto output_shape = tensor_args.input.get_logical_shape(); const auto N = output_shape[0]; const auto num_groups = operation_attributes.num_groups; - SmallVector mean_rstd_origin_shape{1, 1, N, num_groups}; + SimpleShape mean_rstd_shape({1, 1, N, num_groups}); + + std::vector> result; + result.reserve(3); + + // output + if (tensor_args.output.has_value()) { + result.push_back(tensor_args.output->get_tensor_spec()); + } else { + result.push_back( + TensorSpec(output_shape, TensorLayout(dtype, PageConfig(layout), operation_attributes.memory_config))); + } + + // mean + if (tensor_args.mean.has_value()) { + result.push_back(tensor_args.mean->get_tensor_spec()); + } else if (operation_attributes.are_required_outputs[1]) { + result.push_back( + TensorSpec(mean_rstd_shape, TensorLayout(dtype, PageConfig(layout), operation_attributes.memory_config))); + } else { + result.push_back(std::nullopt); + } - SimpleShape mean_rstd_shape(std::move(mean_rstd_origin_shape)); - return {output_shape, mean_rstd_shape, mean_rstd_shape}; + // rstd + if (tensor_args.rstd.has_value()) { + result.push_back(tensor_args.rstd->get_tensor_spec()); + } else if (operation_attributes.are_required_outputs[2]) { + result.push_back( + TensorSpec(mean_rstd_shape, TensorLayout(dtype, PageConfig(layout), operation_attributes.memory_config))); + } else { + result.push_back(std::nullopt); + } + return result; } MorehGroupNormOperation::tensor_return_value_t MorehGroupNormOperation::create_output_tensors( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - const auto output_shapes = compute_output_shapes(operation_attributes, tensor_args); - auto dtype = tensor_args.input.get_dtype(); - Layout layout{Layout::TILE}; + const auto output_specs = compute_output_specs(operation_attributes, tensor_args); auto device = tensor_args.input.device(); std::vector> result; @@ -105,16 +134,14 @@ MorehGroupNormOperation::tensor_return_value_t MorehGroupNormOperation::create_o if (tensor_args.output.has_value()) { result.push_back(tensor_args.output.value()); } else { - result.push_back( - create_device_tensor(output_shapes[0].value(), dtype, layout, device, operation_attributes.memory_config)); + result.push_back(create_device_tensor(*output_specs[0], device)); } // mean if (tensor_args.mean.has_value()) { result.push_back(tensor_args.mean.value()); - } else if (operation_attributes.are_required_outputs[1]) { - result.push_back(create_device_tensor( - output_shapes[1].value(), dtype, layout, device, operation_attributes.mean_memory_config)); + } else if (output_specs[1].has_value()) { + result.push_back(create_device_tensor(*output_specs[1], device)); } else { result.push_back(std::nullopt); } @@ -122,13 +149,12 @@ MorehGroupNormOperation::tensor_return_value_t MorehGroupNormOperation::create_o // rstd if (tensor_args.rstd.has_value()) { result.push_back(tensor_args.rstd.value()); - } else if (operation_attributes.are_required_outputs[2]) { - result.push_back(create_device_tensor( - output_shapes[2].value(), dtype, layout, device, operation_attributes.rstd_memory_config)); + } else if (output_specs[2].has_value()) { + result.push_back(create_device_tensor(*output_specs[2], device)); } else { result.push_back(std::nullopt); } - return std::move(result); + return result; } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm/device/moreh_group_norm_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm/device/moreh_group_norm_device_operation.hpp index 94931056d4d0..554acf5bb73d 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm/device/moreh_group_norm_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm/device/moreh_group_norm_device_operation.hpp @@ -29,7 +29,7 @@ struct MorehGroupNormOperation { const std::optional rstd; }; - using shape_return_value_t = std::vector>; + using spec_return_value_t = std::vector>; using tensor_return_value_t = std::vector>; struct MorehGroupNormFactory { @@ -60,7 +60,7 @@ struct MorehGroupNormOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& input, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/gamma_beta_grad/moreh_group_norm_backward_gamma_beta_grad_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/gamma_beta_grad/moreh_group_norm_backward_gamma_beta_grad_device_operation.cpp index 01eed78ceba6..f91b229d6cfd 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/gamma_beta_grad/moreh_group_norm_backward_gamma_beta_grad_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/gamma_beta_grad/moreh_group_norm_backward_gamma_beta_grad_device_operation.cpp @@ -71,8 +71,8 @@ void MorehGroupNormBackwardGammaBetaGradOperation::validate_on_program_cache_hit validate_tensors(operation_attributes, tensor_args); } -MorehGroupNormBackwardGammaBetaGradOperation::shape_return_value_t -MorehGroupNormBackwardGammaBetaGradOperation::compute_output_shapes( +MorehGroupNormBackwardGammaBetaGradOperation::spec_return_value_t +MorehGroupNormBackwardGammaBetaGradOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { using namespace tt::constants; const auto& output_grad = tensor_args.output_grad; @@ -91,38 +91,62 @@ MorehGroupNormBackwardGammaBetaGradOperation::compute_output_shapes( dgamma_dbeta_padding[2] = Padding::PadDimension{0, TILE_HEIGHT - 1}; dgamma_dbeta_padding[3] = Padding::PadDimension{0, TILE_WIDTH - (c % TILE_WIDTH)}; Shape dgamma_dbeta_shape(tt::tt_metal::LegacyShape(dgamma_dbeta_origin_shape, dgamma_dbeta_padding)); - return {dgamma_dbeta_shape, dgamma_dbeta_shape}; + + auto dtype = tensor_args.output_grad.get_dtype(); + Layout layout{Layout::TILE}; + + std::vector> result(2); + const auto gamma_requires_grad = operation_attributes.are_required_outputs[0]; + const auto beta_requires_grad = operation_attributes.are_required_outputs[1]; + + if (gamma_requires_grad) { + if (tensor_args.gamma_grad.has_value()) { + result[0] = tensor_args.gamma_grad->get_tensor_spec(); + } else { + result[0] = TensorSpec( + dgamma_dbeta_shape.logical_shape(), + TensorLayout::fromLegacyPaddedShape( + dtype, PageConfig(layout), operation_attributes.gamma_grad_memory_config, dgamma_dbeta_shape)); + } + } + + if (beta_requires_grad) { + if (tensor_args.beta_grad.has_value()) { + result[1] = tensor_args.beta_grad->get_tensor_spec(); + } else { + result[1] = TensorSpec( + dgamma_dbeta_shape.logical_shape(), + TensorLayout::fromLegacyPaddedShape( + dtype, PageConfig(layout), operation_attributes.beta_grad_memory_config, dgamma_dbeta_shape)); + } + } + + return result; } MorehGroupNormBackwardGammaBetaGradOperation::tensor_return_value_t MorehGroupNormBackwardGammaBetaGradOperation::create_output_tensors( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - const auto& output_shapes = compute_output_shapes(operation_attributes, tensor_args); - auto dtype = tensor_args.output_grad.get_dtype(); - Layout layout{Layout::TILE}; + const auto output_specs = compute_output_specs(operation_attributes, tensor_args); auto device = tensor_args.output_grad.device(); std::vector> result(2); - const auto gamma_requires_grad = operation_attributes.are_required_outputs[0]; - const auto beta_requires_grad = operation_attributes.are_required_outputs[1]; // gamma_grad - if (gamma_requires_grad) { + if (output_specs[0].has_value()) { if (tensor_args.gamma_grad.has_value()) { result[0] = tensor_args.gamma_grad.value(); } else { - result[0] = create_device_tensor( - output_shapes[0].value(), dtype, layout, device, operation_attributes.gamma_grad_memory_config); + result[0] = create_device_tensor(*output_specs[0], device); } } // beta_grad - if (beta_requires_grad) { + if (output_specs[1].has_value()) { if (tensor_args.beta_grad.has_value()) { result[1] = tensor_args.beta_grad.value(); } else { - result[1] = create_device_tensor( - output_shapes[1].value(), dtype, layout, device, operation_attributes.beta_grad_memory_config); + result[1] = create_device_tensor(*output_specs[1], device); } } diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/gamma_beta_grad/moreh_group_norm_backward_gamma_beta_grad_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/gamma_beta_grad/moreh_group_norm_backward_gamma_beta_grad_device_operation.hpp index 77a64b72928e..58588762567b 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/gamma_beta_grad/moreh_group_norm_backward_gamma_beta_grad_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/gamma_beta_grad/moreh_group_norm_backward_gamma_beta_grad_device_operation.hpp @@ -32,7 +32,7 @@ struct MorehGroupNormBackwardGammaBetaGradOperation { const std::optional beta_grad; }; - using shape_return_value_t = std::vector>; + using spec_return_value_t = std::vector>; using tensor_return_value_t = std::vector>; struct MorehGroupNormBackwardGammaBetaGradFactory { @@ -63,7 +63,7 @@ struct MorehGroupNormBackwardGammaBetaGradOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& output_grad, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/input_grad/moreh_group_norm_backward_input_grad_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/input_grad/moreh_group_norm_backward_input_grad_device_operation.cpp index aeb95e80ef30..1882f3621d85 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/input_grad/moreh_group_norm_backward_input_grad_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/input_grad/moreh_group_norm_backward_input_grad_device_operation.cpp @@ -68,10 +68,18 @@ void MorehGroupNormBackwardInputGradOperation::validate_on_program_cache_hit( validate_tensors(operation_attributes, tensor_args); } -MorehGroupNormBackwardInputGradOperation::shape_return_value_t -MorehGroupNormBackwardInputGradOperation::compute_output_shapes( +MorehGroupNormBackwardInputGradOperation::spec_return_value_t +MorehGroupNormBackwardInputGradOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - return tensor_args.output_grad.get_shape(); + if (tensor_args.input_grad.has_value()) { + return {tensor_args.input_grad->get_tensor_spec()}; + } + return TensorSpec( + tensor_args.output_grad.get_logical_shape(), + TensorLayout( + tensor_args.output_grad.get_dtype(), + PageConfig(Layout::TILE), + operation_attributes.input_grad_memory_config)); } MorehGroupNormBackwardInputGradOperation::tensor_return_value_t @@ -82,11 +90,7 @@ MorehGroupNormBackwardInputGradOperation::create_output_tensors( } return create_device_tensor( - tensor_args.output_grad.get_shape(), - tensor_args.output_grad.get_dtype(), - Layout::TILE, - tensor_args.output_grad.device(), - operation_attributes.input_grad_memory_config); + compute_output_specs(operation_attributes, tensor_args), tensor_args.output_grad.device()); } std::tuple< diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/input_grad/moreh_group_norm_backward_input_grad_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/input_grad/moreh_group_norm_backward_input_grad_device_operation.hpp index dae49aa0b8d0..6d1269e2479f 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/input_grad/moreh_group_norm_backward_input_grad_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_group_norm_backward/device/input_grad/moreh_group_norm_backward_input_grad_device_operation.hpp @@ -23,7 +23,7 @@ struct MorehGroupNormBackwardInputGradOperation { const std::optional input_grad; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct MorehGroupNormBackwardInputGradFactory { @@ -54,7 +54,7 @@ struct MorehGroupNormBackwardInputGradOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& output_grad, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm/device/moreh_layer_norm_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm/device/moreh_layer_norm_device_operation.cpp index f4ab0503e8ee..362721ec8975 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm/device/moreh_layer_norm_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm/device/moreh_layer_norm_device_operation.cpp @@ -80,71 +80,51 @@ void MorehLayerNormOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehLayerNormOperation::shape_return_value_t MorehLayerNormOperation::compute_output_shapes( +MorehLayerNormOperation::spec_return_value_t MorehLayerNormOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { auto input = tensor_args.input; - auto normalized_dims = operation_attributes.normalized_dims; - auto input_shape = input.get_shape(); - auto input_shape_without_padding = input_shape.value.without_padding(); - auto input_rank = input_shape.rank(); - auto output_rank = input_rank - normalized_dims; - - ttnn::SmallVector output_size_vec; - ttnn::SmallVector dimensions_pads; - - if (output_rank == 1) { - output_size_vec.push_back(32); - dimensions_pads.push_back(Padding::PadDimension{.front = 0, .back = 31}); + std::vector> result(3); + + if (tensor_args.output.has_value()) { + result[0] = tensor_args.output->get_tensor_spec(); + } else { + result[0] = TensorSpec( + input.get_logical_shape(), + TensorLayout(input.get_dtype(), PageConfig(Layout::TILE), operation_attributes.memory_config)); } - for (uint32_t dim = 0; dim < output_rank; dim++) { - auto input_shape_without_padding_size = input_shape_without_padding[dim]; - if (is_hw_dim(dim, output_rank)) { - output_size_vec.push_back(round_up_to_mul32(input_shape_without_padding_size)); - auto padding_back = output_size_vec[dim] - input_shape_without_padding_size; - dimensions_pads.push_back(Padding::PadDimension{.front = 0, .back = padding_back}); - } else { - output_size_vec.push_back(input_shape_without_padding_size); - dimensions_pads.push_back(Padding::PadDimension{.front = 0, .back = 0}); - } + if (tensor_args.mean.has_value()) { + result[1] = tensor_args.mean->get_tensor_spec(); } - const auto padding = Padding(dimensions_pads, Padding::PadValue::Any); - auto mean_rstd_output_shape = Shape{tt::tt_metal::LegacyShape(output_size_vec, padding)}; - return {input_shape, mean_rstd_output_shape, mean_rstd_output_shape}; -}; + if (tensor_args.rstd.has_value()) { + result[2] = tensor_args.rstd->get_tensor_spec(); + } + + return result; +} MorehLayerNormOperation::tensor_return_value_t MorehLayerNormOperation::create_output_tensors( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - const auto& output_shapes = compute_output_shapes(operation_attributes, tensor_args); - auto input = tensor_args.input; - auto dtype = input.get_dtype(); - Layout layout{Layout::TILE}; - auto device = input.device(); + const auto output_specs = compute_output_specs(operation_attributes, tensor_args); - std::vector> result; - result.reserve(3); + std::vector> result(3); if (tensor_args.output.has_value()) { - result.push_back(tensor_args.output.value()); + result[0] = tensor_args.output.value(); } else { - result.push_back( - create_device_tensor(output_shapes.at(0).value, dtype, layout, device, operation_attributes.memory_config)); + result[0] = create_device_tensor(*output_specs.at(0), tensor_args.input.device()); } if (tensor_args.mean.has_value()) { - result.push_back(tensor_args.mean.value()); - } else { - result.push_back(std::nullopt); + result[1] = tensor_args.mean.value(); } if (tensor_args.rstd.has_value()) { - result.push_back(tensor_args.rstd.value()); - } else { - result.push_back(std::nullopt); + result[2] = tensor_args.rstd.value(); } - return std::move(result); + return result; } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm/device/moreh_layer_norm_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm/device/moreh_layer_norm_device_operation.hpp index 2c0334abc35e..d8179129bc5b 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm/device/moreh_layer_norm_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm/device/moreh_layer_norm_device_operation.hpp @@ -29,7 +29,7 @@ struct MorehLayerNormOperation { const std::optional& rstd; }; - using shape_return_value_t = std::vector; + using spec_return_value_t = std::vector>; using tensor_return_value_t = std::vector>; struct ProgramFactory { @@ -60,7 +60,7 @@ struct MorehLayerNormOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& input, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_gamma_beta_grad_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_gamma_beta_grad_device_operation.cpp index f36983178c45..cd280fb54a9f 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_gamma_beta_grad_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_gamma_beta_grad_device_operation.cpp @@ -26,30 +26,32 @@ void MorehLayerNormBackwardGammaBetaGradOperation::validate_on_program_cache_hit validate_inputs(operation_attributes, tensor_args); }; -MorehLayerNormBackwardGammaBetaGradOperation::shape_return_value_t -MorehLayerNormBackwardGammaBetaGradOperation::compute_output_shapes( +MorehLayerNormBackwardGammaBetaGradOperation::spec_return_value_t +MorehLayerNormBackwardGammaBetaGradOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - TT_FATAL(false, "The compute_output_shapes function in MorehLayerNormBackwardGammaBetaGrad is not implemented."); - return {}; + std::vector> result(2); + if (tensor_args.gamma_grad.has_value()) { + result[0] = tensor_args.gamma_grad->get_tensor_spec(); + } + + if (tensor_args.beta_grad.has_value()) { + result[1] = tensor_args.beta_grad->get_tensor_spec(); + } + return result; }; MorehLayerNormBackwardGammaBetaGradOperation::tensor_return_value_t MorehLayerNormBackwardGammaBetaGradOperation::create_output_tensors( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - std::vector> result; - result.reserve(2); + std::vector> result(2); if (tensor_args.gamma_grad.has_value()) { - result.push_back(tensor_args.gamma_grad.value()); - } else { - result.push_back(std::nullopt); + result[0] = tensor_args.gamma_grad.value(); } if (tensor_args.beta_grad.has_value()) { - result.push_back(tensor_args.beta_grad.value()); - } else { - result.push_back(std::nullopt); + result[1] = tensor_args.beta_grad.value(); } - return std::move(result); + return result; } std::tuple< diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_gamma_beta_grad_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_gamma_beta_grad_device_operation.hpp index c6ad15e73821..1cd516391c9a 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_gamma_beta_grad_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_gamma_beta_grad_device_operation.hpp @@ -27,7 +27,7 @@ struct MorehLayerNormBackwardGammaBetaGradOperation { const std::optional& beta_grad; }; - using shape_return_value_t = std::vector; + using spec_return_value_t = std::vector>; using tensor_return_value_t = std::vector>; struct ProgramFactory { @@ -58,7 +58,7 @@ struct MorehLayerNormBackwardGammaBetaGradOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& output_grad, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_input_grad_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_input_grad_device_operation.cpp index b57e371fb554..a74b01a40a93 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_input_grad_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_input_grad_device_operation.cpp @@ -27,12 +27,16 @@ void MorehLayerNormBackwardInputGradOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehLayerNormBackwardInputGradOperation::shape_return_value_t -MorehLayerNormBackwardInputGradOperation::compute_output_shapes( +MorehLayerNormBackwardInputGradOperation::spec_return_value_t +MorehLayerNormBackwardInputGradOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - auto input = tensor_args.input; - auto input_shape = input.get_shape(); - return tensor_args.input.get_shape(); + if (tensor_args.input_grad.has_value()) { + return tensor_args.input_grad->get_tensor_spec(); + } + return TensorSpec( + tensor_args.input.get_logical_shape(), + TensorLayout( + tensor_args.output_grad.get_dtype(), PageConfig(Layout::TILE), operation_attributes.memory_config)); }; MorehLayerNormBackwardInputGradOperation::tensor_return_value_t @@ -41,13 +45,8 @@ MorehLayerNormBackwardInputGradOperation::create_output_tensors( if (tensor_args.input_grad.has_value()) { return tensor_args.input_grad.value(); } - auto output_grad = tensor_args.output_grad; return create_device_tensor( - compute_output_shapes(operation_attributes, tensor_args), - output_grad.get_dtype(), - Layout::TILE, - output_grad.device(), - operation_attributes.memory_config); + compute_output_specs(operation_attributes, tensor_args), tensor_args.output_grad.device()); } std::tuple< diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_input_grad_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_input_grad_device_operation.hpp index fdfb4621253e..c5cd3c4b06e2 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_input_grad_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_layer_norm_backward/device/moreh_layer_norm_backward_input_grad_device_operation.hpp @@ -26,7 +26,7 @@ struct MorehLayerNormBackwardInputGradOperation { const std::optional& gamma; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct ProgramFactory { @@ -57,7 +57,7 @@ struct MorehLayerNormBackwardInputGradOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& output_grad, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_linear_backward/device/moreh_linear_backward_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_linear_backward/device/moreh_linear_backward_device_operation.cpp index 79b48f47a659..005103509281 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_linear_backward/device/moreh_linear_backward_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_linear_backward/device/moreh_linear_backward_device_operation.cpp @@ -43,9 +43,16 @@ void MorehBiasAddBackwardOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehBiasAddBackwardOperation::shape_return_value_t MorehBiasAddBackwardOperation::compute_output_shapes( +MorehBiasAddBackwardOperation::spec_return_value_t MorehBiasAddBackwardOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - return tensor_args.bias.value().get_shape(); + if (tensor_args.bias_grad.has_value()) { + return tensor_args.bias_grad->get_tensor_spec(); + } + TT_FATAL(tensor_args.bias.has_value(), "bias tensor should not be std::nullopt"); + auto dtype = tensor_args.bias.value().get_dtype(); + return TensorSpec( + tensor_args.bias->get_logical_shape(), + TensorLayout(dtype, PageConfig(Layout::TILE), operation_attributes.bias_grad_memory_config)); }; MorehBiasAddBackwardOperation::tensor_return_value_t MorehBiasAddBackwardOperation::create_output_tensors( @@ -54,15 +61,8 @@ MorehBiasAddBackwardOperation::tensor_return_value_t MorehBiasAddBackwardOperati return tensor_args.bias_grad.value(); } - TT_FATAL(tensor_args.bias.has_value(), "bias tensor should not be std::nullopt"); - const auto& output_shape = compute_output_shapes(operation_attributes, tensor_args); - auto dtype = tensor_args.bias.value().get_dtype(); - Layout layout{Layout::TILE}; - auto device = tensor_args.bias.value().device(); - - auto bias_grad_memory_config = operation_attributes.bias_grad_memory_config; - - return create_device_tensor(output_shape, dtype, layout, device, bias_grad_memory_config); + const auto output_spec = compute_output_specs(operation_attributes, tensor_args); + return create_device_tensor(output_spec, tensor_args.bias->device()); } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_linear_backward/device/moreh_linear_backward_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_linear_backward/device/moreh_linear_backward_device_operation.hpp index 9e61ea480f72..64c21089f64d 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_linear_backward/device/moreh_linear_backward_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_linear_backward/device/moreh_linear_backward_device_operation.hpp @@ -26,7 +26,7 @@ struct MorehBiasAddBackwardOperation { const std::optional& bias_grad; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct SingleCoreProgramFactory { @@ -76,7 +76,7 @@ struct MorehBiasAddBackwardOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& output_grad, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_matmul/device/moreh_matmul_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_matmul/device/moreh_matmul_device_operation.cpp index 7a414fb82b83..f854198baa56 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_matmul/device/moreh_matmul_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_matmul/device/moreh_matmul_device_operation.cpp @@ -102,57 +102,6 @@ void MorehMatmulOperation::validate_on_program_cache_miss( validate_inputs(operation_attributes, tensor_args); } -MorehMatmulOperation::shape_return_value_t compute_output_shapes( - const MorehMatmulOperation::operation_attributes_t& operation_attributes, - const MorehMatmulOperation::tensor_args_t& tensor_args) { - auto input_shape = tensor_args.input.get_shape().value; - auto other_shape = tensor_args.other.get_shape().value; - - auto transpose_input = operation_attributes.transpose_input; - auto transpose_other = operation_attributes.transpose_other; - - const auto& input_shape_wo_padding = input_shape.without_padding(); - const auto& other_shape_wo_padding = other_shape.without_padding(); - - auto h = (transpose_input) ? (input_shape[-1]) : (input_shape[-2]); - auto w = (transpose_other) ? (other_shape[-2]) : (other_shape[-1]); - auto h_wo_padding = (transpose_input) ? (input_shape_wo_padding[-1]) : (input_shape_wo_padding[-2]); - auto w_wo_padding = (transpose_other) ? (other_shape_wo_padding[-2]) : (other_shape_wo_padding[-1]); - - ttnn::SmallVector input_dim(tt::tt_metal::MAX_NUM_DIMENSIONS, 1); - ttnn::SmallVector other_dim(tt::tt_metal::MAX_NUM_DIMENSIONS, 1); - get_tensor_dim(input_dim, input_shape); - get_tensor_dim(other_dim, other_shape); - - int32_t output_rank = std::max(input_shape.rank(), other_shape.rank()); - log_debug( - tt::LogOp, - "{}:{} input, other, output rank {}, {}, {}", - __func__, - __LINE__, - input_shape.rank(), - other_shape.rank(), - output_rank); - - std::vector output_dim(output_rank); - // batch dims - for (int i = 0; i < output_rank - 2; ++i) { - int idx = output_rank - 1 - i; - TT_ASSERT(idx >= 0); - uint32_t max_dim = std::max(input_dim[idx], other_dim[idx]); - output_dim[i] = max_dim; - } - // matrix dims - output_dim[output_rank - 2] = h; - output_dim[output_rank - 1] = w; - - tt::tt_metal::LegacyShape output_shape{output_dim}; - auto padding = output_shape.padding(); - // padding for t logmatrix dims - padding[output_rank - 2] = Padding::PadDimension{0, h - h_wo_padding}; - padding[output_rank - 1] = Padding::PadDimension{0, w - w_wo_padding}; - return Shape({tt::tt_metal::LegacyShape(output_shape, padding)}); -} MorehMatmulOperation::tensor_return_value_t MorehMatmulOperation::create_output_tensors( const MorehMatmulOperation::operation_attributes_t& operation_attributes, const MorehMatmulOperation::tensor_args_t& tensor_args) { @@ -160,12 +109,7 @@ MorehMatmulOperation::tensor_return_value_t MorehMatmulOperation::create_output_ return tensor_args.output.value(); } - return create_device_tensor( - compute_output_shapes(operation_attributes, tensor_args), - tensor_args.input.get_dtype(), - Layout::TILE, - tensor_args.input.device(), - operation_attributes.output_memory_config); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), tensor_args.input.device()); }; MorehMatmulOperation::program_factory_t MorehMatmulOperation::select_program_factory( @@ -173,7 +117,7 @@ MorehMatmulOperation::program_factory_t MorehMatmulOperation::select_program_fac return MultiCoreProgramFactory{}; } -MorehMatmulOperation::shape_return_value_t MorehMatmulOperation::compute_output_shapes( +MorehMatmulOperation::spec_return_value_t MorehMatmulOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { const auto& input_shape = tensor_args.input.get_shape().value; const auto& other_shape = tensor_args.other.get_shape().value; @@ -219,7 +163,14 @@ MorehMatmulOperation::shape_return_value_t MorehMatmulOperation::compute_output_ // padding for t logmatrix dims padding[output_rank - 2] = Padding::PadDimension{0, h - h_wo_padding}; padding[output_rank - 1] = Padding::PadDimension{0, w - w_wo_padding}; - return Shape({tt::tt_metal::LegacyShape(output_shape, padding)}); + Shape result_shape({tt::tt_metal::LegacyShape(output_shape, padding)}); + return TensorSpec( + result_shape.logical_shape(), + TensorLayout::fromLegacyPaddedShape( + tensor_args.input.get_dtype(), + PageConfig(Layout::TILE), + operation_attributes.output_memory_config, + result_shape)); } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_matmul/device/moreh_matmul_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_matmul/device/moreh_matmul_device_operation.hpp index 55a88691a6a2..0b32ea32f7b9 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_matmul/device/moreh_matmul_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_matmul/device/moreh_matmul_device_operation.hpp @@ -30,7 +30,7 @@ struct MorehMatmulOperation { const std::optional& bias; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct MultiCoreProgramFactory { @@ -61,7 +61,7 @@ struct MorehMatmulOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& input, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_mean/device/moreh_mean_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_mean/device/moreh_mean_device_operation.cpp index e57147e02de4..dddd1999a8e0 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_mean/device/moreh_mean_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_mean/device/moreh_mean_device_operation.cpp @@ -53,8 +53,12 @@ void MorehMeanOperation::validate_on_program_cache_hit( validate_tensors(operation_attributes, tensor_args); }; -MorehMeanOperation::shape_return_value_t MorehMeanOperation::compute_output_shapes( +MorehMeanOperation::spec_return_value_t MorehMeanOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { + if (tensor_args.output.has_value()) { + return {tensor_args.output->get_tensor_spec()}; + } + auto input_shape = tensor_args.input.get_shape(); auto output_shape = input_shape; auto input_rank = input_shape.rank(); @@ -73,7 +77,14 @@ MorehMeanOperation::shape_return_value_t MorehMeanOperation::compute_output_shap output_shape.value[dim] = 1; } - return Shape(tt::tt_metal::LegacyShape(output_shape.value, padding)); + Shape result_shape(tt::tt_metal::LegacyShape(output_shape.value, padding)); + return TensorSpec( + result_shape.logical_shape(), + TensorLayout::fromLegacyPaddedShape( + tensor_args.input.get_dtype(), + PageConfig(tensor_args.input.get_layout()), + operation_attributes.memory_config, + result_shape)); } ttnn::SmallVector shape; @@ -95,7 +106,14 @@ MorehMeanOperation::shape_return_value_t MorehMeanOperation::compute_output_shap } auto padding = Padding(pad_dimensions, input_padding.pad_value()); - return Shape(tt::tt_metal::LegacyShape(shape, padding)); + Shape result_shape(tt::tt_metal::LegacyShape(shape, padding)); + return TensorSpec( + result_shape.logical_shape(), + TensorLayout::fromLegacyPaddedShape( + tensor_args.input.get_dtype(), + PageConfig(tensor_args.input.get_layout()), + operation_attributes.memory_config, + result_shape)); } MorehMeanOperation::tensor_return_value_t MorehMeanOperation::create_output_tensors( @@ -105,12 +123,7 @@ MorehMeanOperation::tensor_return_value_t MorehMeanOperation::create_output_tens return {output.value()}; } - return create_device_tensor( - compute_output_shapes(operation_attributes, tensor_args), - tensor_args.input.get_dtype(), - tensor_args.input.get_layout(), - tensor_args.input.device(), - operation_attributes.memory_config); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), tensor_args.input.device()); } std::tuple MorehMeanOperation::invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_mean/device/moreh_mean_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_mean/device/moreh_mean_device_operation.hpp index 743c2e1c162e..0c45c50f66d6 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_mean/device/moreh_mean_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_mean/device/moreh_mean_device_operation.hpp @@ -28,7 +28,7 @@ struct MorehMeanOperation { const std::optional& output; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct MorehMeanHFactory { @@ -103,7 +103,7 @@ struct MorehMeanOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& input, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_mean_backward/device/moreh_mean_backward_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_mean_backward/device/moreh_mean_backward_device_operation.cpp index e3626691b76e..5bbeda549776 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_mean_backward/device/moreh_mean_backward_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_mean_backward/device/moreh_mean_backward_device_operation.cpp @@ -36,31 +36,16 @@ void MorehMeanBackwardOperation::validate_on_program_cache_hit( validate_tensors(operation_attributes, tensor_args); }; -MorehMeanBackwardOperation::shape_return_value_t MorehMeanBackwardOperation::compute_output_shapes( +MorehMeanBackwardOperation::spec_return_value_t MorehMeanBackwardOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - auto input_grad_shape = operation_attributes.input_grad_shape.value(); - auto rank = input_grad_shape.rank(); - - ttnn::SmallVector shape; - ttnn::SmallVector dimensions_pads; - - for (uint32_t dim = 0; dim < rank; dim++) { - if (is_hw_dim(dim, rank)) { - uint32_t up32_shape = tt::round_up(input_grad_shape[dim], 32); - uint32_t padding_back = up32_shape - input_grad_shape[dim]; - shape.push_back(up32_shape); - dimensions_pads.push_back(Padding::PadDimension{.front = 0, .back = padding_back}); - - } else { - shape.push_back(input_grad_shape[dim]); - dimensions_pads.push_back(Padding::PadDimension{.front = 0, .back = 0}); - } + if (tensor_args.input_grad.has_value()) { + return tensor_args.input_grad->get_tensor_spec(); } - - const auto padding = Padding(dimensions_pads, Padding::PadValue::Any); - auto output_shape = Shape(tt::tt_metal::LegacyShape(shape, padding)); - - return output_shape; + auto input_grad_shape = operation_attributes.input_grad_shape.value().logical_shape(); + return TensorSpec( + input_grad_shape, + TensorLayout( + tensor_args.output_grad.get_dtype(), PageConfig(Layout::TILE), operation_attributes.memory_config)); } MorehMeanBackwardOperation::tensor_return_value_t MorehMeanBackwardOperation::create_output_tensors( @@ -70,12 +55,7 @@ MorehMeanBackwardOperation::tensor_return_value_t MorehMeanBackwardOperation::cr return tensor_args.input_grad.value(); } - return create_device_tensor( - compute_output_shapes(operation_attributes, tensor_args), - output_grad.get_dtype(), - Layout::TILE, - output_grad.device(), - operation_attributes.memory_config); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), output_grad.device()); } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_mean_backward/device/moreh_mean_backward_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_mean_backward/device/moreh_mean_backward_device_operation.hpp index ff1d17777e4e..84b87574a7e4 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_mean_backward/device/moreh_mean_backward_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_mean_backward/device/moreh_mean_backward_device_operation.hpp @@ -28,7 +28,7 @@ struct MorehMeanBackwardOperation { const std::optional& input_grad; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct MorehMeanBackwardFactory { @@ -59,7 +59,7 @@ struct MorehMeanBackwardOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& output_grad, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step1/device/moreh_nll_loss_step1_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step1/device/moreh_nll_loss_step1_device_operation.cpp index c0100d777765..1db8255ec9e2 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step1/device/moreh_nll_loss_step1_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step1/device/moreh_nll_loss_step1_device_operation.cpp @@ -40,23 +40,18 @@ void MorehNllLossStep1DeviceOperation::validate_on_program_cache_hit( validate_inputs(attributes, tensor_args); } -MorehNllLossStep1DeviceOperation::shape_return_value_t MorehNllLossStep1DeviceOperation::compute_output_shapes( +MorehNllLossStep1DeviceOperation::spec_return_value_t MorehNllLossStep1DeviceOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { const auto& target_tensor = tensor_args.target_tensor; - auto target_shape = target_tensor.get_shape(); - - return target_shape; + return TensorSpec( + target_tensor.get_logical_shape(), + TensorLayout(operation_attributes.dtype, PageConfig(Layout::TILE), operation_attributes.memory_config)); } MorehNllLossStep1DeviceOperation::tensor_return_value_t MorehNllLossStep1DeviceOperation::create_output_tensors( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - const auto& target_tensor = tensor_args.target_tensor; - auto output_shape = compute_output_shapes(operation_attributes, tensor_args); - Layout layout{Layout::TILE}; - auto device = tensor_args.target_tensor.device(); - return create_device_tensor( - output_shape, operation_attributes.dtype, layout, device, operation_attributes.memory_config); + compute_output_specs(operation_attributes, tensor_args), tensor_args.target_tensor.device()); } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step1/device/moreh_nll_loss_step1_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step1/device/moreh_nll_loss_step1_device_operation.hpp index 1808321ba293..62c52826a498 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step1/device/moreh_nll_loss_step1_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step1/device/moreh_nll_loss_step1_device_operation.hpp @@ -26,7 +26,7 @@ struct MorehNllLossStep1DeviceOperation { const std::optional& weight_tensor; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; @@ -61,7 +61,7 @@ struct MorehNllLossStep1DeviceOperation { static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step2/device/moreh_nll_loss_step2_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step2/device/moreh_nll_loss_step2_device_operation.cpp index 50944bfd6d8d..8b80e9e35da6 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step2/device/moreh_nll_loss_step2_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step2/device/moreh_nll_loss_step2_device_operation.cpp @@ -65,22 +65,26 @@ void MorehNllLossStep2DeviceOperation::validate_on_program_cache_hit( validate_inputs(attributes, tensor_args); } -MorehNllLossStep2DeviceOperation::shape_return_value_t MorehNllLossStep2DeviceOperation::compute_output_shapes( +MorehNllLossStep2DeviceOperation::spec_return_value_t MorehNllLossStep2DeviceOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { + if (operation_attributes.reduction == NONE && tensor_args.output_tensor.has_value()) { + return tensor_args.output_tensor->get_tensor_spec(); + } + const auto& input_tensor = tensor_args.input_tensor; auto input_shape = input_tensor.get_shape().value; auto input_shape_without_padding = input_shape.without_padding(); auto input_rank = input_shape.rank(); + auto dtype = tensor_args.input_tensor.get_dtype(); + Layout layout{Layout::TILE}; auto C = input_shape[1]; - ttnn::SmallVector dimensions_pads; ttnn::SmallVector output_shape_vec; // Need extend 1d output to 2d, because TT not support 1d tensor if (input_rank == 2) { output_shape_vec.push_back(1); - dimensions_pads.push_back(Padding::PadDimension{.front = 0, .back = 0}); } for (uint32_t dim = 0; dim < input_rank; dim++) { @@ -90,25 +94,10 @@ MorehNllLossStep2DeviceOperation::shape_return_value_t MorehNllLossStep2DeviceOp } output_shape_vec.push_back(input_shape_without_padding[dim]); - dimensions_pads.push_back(Padding::PadDimension{.front = 0, .back = 0}); } - // padding output - { - uint32_t output_rank = output_shape_vec.size(); - for (uint32_t dim = output_rank - 2; dim < output_rank; dim++) { - uint32_t up32_shape = tt::round_up(output_shape_vec[dim], 32); - uint32_t padding_back = up32_shape - output_shape_vec[dim]; - - output_shape_vec[dim] = up32_shape; - dimensions_pads[dim].back = padding_back; - } - } - - const auto padding = Padding(dimensions_pads, Padding::PadValue::Any); - auto output_shape = Shape(tt::tt_metal::LegacyShape{output_shape_vec, padding}); - - return output_shape; + auto output_shape = SimpleShape{output_shape_vec}; + return TensorSpec(output_shape, TensorLayout(dtype, PageConfig(layout), operation_attributes.memory_config)); } MorehNllLossStep2DeviceOperation::tensor_return_value_t MorehNllLossStep2DeviceOperation::create_output_tensors( @@ -119,12 +108,8 @@ MorehNllLossStep2DeviceOperation::tensor_return_value_t MorehNllLossStep2DeviceO // In case reduction is 'sum' or 'mean' we need to create a tensor to save loss result and reduce it to // tensor_args.output_tensor using moreh_sum() operation - auto output_shape = compute_output_shapes(operation_attributes, tensor_args); - auto dtype = tensor_args.input_tensor.get_dtype(); - Layout layout{Layout::TILE}; - auto device = tensor_args.input_tensor.device(); - - return create_device_tensor(output_shape, dtype, layout, device, operation_attributes.memory_config); + auto output_spec = compute_output_specs(operation_attributes, tensor_args); + return create_device_tensor(output_spec, tensor_args.input_tensor.device()); } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step2/device/moreh_nll_loss_step2_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step2/device/moreh_nll_loss_step2_device_operation.hpp index e22ababdd9ca..ec811680ee84 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step2/device/moreh_nll_loss_step2_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss/moreh_nll_loss_step2/device/moreh_nll_loss_step2_device_operation.hpp @@ -27,7 +27,7 @@ struct MorehNllLossStep2DeviceOperation { const std::optional& output_tensor; }; - using shape_return_value_t = ttnn::Shape; + using spec_return_value_t = ttnn::TensorSpec; using tensor_return_value_t = ttnn::Tensor; @@ -62,7 +62,7 @@ struct MorehNllLossStep2DeviceOperation { static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_backward/device/moreh_nll_loss_backward_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_backward/device/moreh_nll_loss_backward_device_operation.cpp index 89020047f7cc..8ad658cf4d6a 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_backward/device/moreh_nll_loss_backward_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_backward/device/moreh_nll_loss_backward_device_operation.cpp @@ -85,12 +85,14 @@ void MorehNllLossBackwardDeviceOperation::validate_on_program_cache_hit( validate_inputs(attributes, tensor_args); } -MorehNllLossBackwardDeviceOperation::shape_return_value_t MorehNllLossBackwardDeviceOperation::compute_output_shapes( +MorehNllLossBackwardDeviceOperation::spec_return_value_t MorehNllLossBackwardDeviceOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { + if (tensor_args.input_grad_tensor.has_value()) { + return {tensor_args.input_grad_tensor->get_tensor_spec()}; + } // To calculate the output shape, we need the channel_size. However, the required tensors, target and output_grad, // do not contain the channel_size information. - TT_FATAL(false, "moreh_nll_loss_backward not support create output tensors."); - return tensor_args.target_tensor.get_shape(); + TT_FATAL(false, "moreh_nll_loss_backward not support creating output tensors."); } MorehNllLossBackwardDeviceOperation::tensor_return_value_t MorehNllLossBackwardDeviceOperation::create_output_tensors( @@ -99,11 +101,8 @@ MorehNllLossBackwardDeviceOperation::tensor_return_value_t MorehNllLossBackwardD return {tensor_args.input_grad_tensor.value()}; } - auto output_shapes = compute_output_shapes(operation_attributes, tensor_args); - auto dtype = tensor_args.target_tensor.get_dtype(); - Layout layout{Layout::TILE}; - auto device = tensor_args.target_tensor.device(); - return create_device_tensor(output_shapes, dtype, layout, device, operation_attributes.memory_config); + return create_device_tensor( + compute_output_specs(operation_attributes, tensor_args), tensor_args.target_tensor.device()); } std::tuple< diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_backward/device/moreh_nll_loss_backward_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_backward/device/moreh_nll_loss_backward_device_operation.hpp index 9a9a9359b73e..c2a369a8d0bd 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_backward/device/moreh_nll_loss_backward_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_backward/device/moreh_nll_loss_backward_device_operation.hpp @@ -31,7 +31,7 @@ struct MorehNllLossBackwardDeviceOperation { const std::optional& input_grad_tensor; }; - using shape_return_value_t = ttnn::Shape; + using spec_return_value_t = ttnn::TensorSpec; using tensor_return_value_t = ttnn::Tensor; @@ -66,7 +66,7 @@ struct MorehNllLossBackwardDeviceOperation { static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_unreduced_backward/device/moreh_nll_loss_unreduced_backward_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_unreduced_backward/device/moreh_nll_loss_unreduced_backward_device_operation.cpp index b518b576a8b4..6291fc4e053a 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_unreduced_backward/device/moreh_nll_loss_unreduced_backward_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_unreduced_backward/device/moreh_nll_loss_unreduced_backward_device_operation.cpp @@ -83,13 +83,15 @@ void MorehNllLossUnreducedBackwardDeviceOperation::validate_on_program_cache_hit validate_inputs(attributes, tensor_args); } -MorehNllLossUnreducedBackwardDeviceOperation::shape_return_value_t -MorehNllLossUnreducedBackwardDeviceOperation::compute_output_shapes( +MorehNllLossUnreducedBackwardDeviceOperation::spec_return_value_t +MorehNllLossUnreducedBackwardDeviceOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { + if (tensor_args.input_grad_tensor.has_value()) { + return {tensor_args.input_grad_tensor->get_tensor_spec()}; + } // To calculate the output shape, we need the channel_size. However, the required tensors, target and output_grad, // do not contain the channel_size information. - TT_FATAL(false, "moreh_nll_loss_unreduced_backward not support create output tensors."); - return tensor_args.target_tensor.get_shape(); + TT_FATAL(false, "moreh_nll_loss_unreduced_backward not support creating output tensors."); } MorehNllLossUnreducedBackwardDeviceOperation::tensor_return_value_t @@ -99,11 +101,8 @@ MorehNllLossUnreducedBackwardDeviceOperation::create_output_tensors( return {tensor_args.input_grad_tensor.value()}; } - auto output_shapes = compute_output_shapes(operation_attributes, tensor_args); - auto dtype = tensor_args.target_tensor.get_dtype(); - Layout layout{Layout::TILE}; - auto device = tensor_args.target_tensor.device(); - return create_device_tensor(output_shapes, dtype, layout, device, operation_attributes.memory_config); + auto output_spec = compute_output_specs(operation_attributes, tensor_args); + return create_device_tensor(output_spec, tensor_args.target_tensor.device()); } std::tuple< diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_unreduced_backward/device/moreh_nll_loss_unreduced_backward_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_unreduced_backward/device/moreh_nll_loss_unreduced_backward_device_operation.hpp index 13774fbb83c2..b6a1cd9cfcc7 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_unreduced_backward/device/moreh_nll_loss_unreduced_backward_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_nll_loss_unreduced_backward/device/moreh_nll_loss_unreduced_backward_device_operation.hpp @@ -28,7 +28,7 @@ struct MorehNllLossUnreducedBackwardDeviceOperation { const std::optional& input_grad_tensor; }; - using shape_return_value_t = ttnn::Shape; + using spec_return_value_t = ttnn::TensorSpec; using tensor_return_value_t = ttnn::Tensor; @@ -63,7 +63,7 @@ struct MorehNllLossUnreducedBackwardDeviceOperation { static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_norm/device/moreh_norm_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_norm/device/moreh_norm_device_operation.cpp index 3664d4053643..e94da5c2ba46 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_norm/device/moreh_norm_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_norm/device/moreh_norm_device_operation.cpp @@ -131,38 +131,36 @@ void MorehNormOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehNormOperation::shape_return_value_t MorehNormOperation::compute_output_shapes( +MorehNormOperation::spec_return_value_t MorehNormOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - const auto& input_shape = tensor_args.input.get_legacy_shape(); + if (tensor_args.output.has_value()) { + return tensor_args.output->get_tensor_spec(); + } + + const auto& input_shape = tensor_args.input.get_logical_shape(); const auto input_rank = input_shape.rank(); const auto dim = operation_attributes.dim; const bool is_tile_dim = (dim == input_rank - 1 || dim == input_rank - 2); if (operation_attributes.keepdim) { auto shape = input_shape; - auto padding = shape.padding(); - if (is_tile_dim) { - shape[dim] = tt::constants::TILE_HEIGHT; - padding[dim] = Padding::PadDimension{0, 31}; - } else { - shape[dim] = 1; - } - return Shape{tt::tt_metal::LegacyShape(shape, padding)}; + shape[dim] = 1; + return TensorSpec( + shape, + TensorLayout(tensor_args.input.get_dtype(), PageConfig(Layout::TILE), operation_attributes.memory_config)); } ttnn::SmallVector shape; - ttnn::SmallVector pad_dimensions; - const std::size_t output_rank = is_tile_dim ? input_rank : input_rank - 1; - auto input_padding = input_shape.padding(); for (int i = 0; i < input_rank; ++i) { bool is_reduced_dim = (i == dim); if (is_reduced_dim && !is_tile_dim) { continue; } - shape.push_back((is_reduced_dim && is_tile_dim) ? (tt::constants::TILE_HEIGHT) : (input_shape[i])); - pad_dimensions.push_back((is_reduced_dim && is_tile_dim) ? (Padding::PadDimension{0, 31}) : (input_padding[i])); + shape.push_back(input_shape[i]); } - return Shape{tt::tt_metal::LegacyShape(shape, Padding(pad_dimensions, input_padding.pad_value()))}; + return TensorSpec( + ttnn::SimpleShape(shape), + TensorLayout(tensor_args.input.get_dtype(), PageConfig(Layout::TILE), operation_attributes.memory_config)); }; MorehNormOperation::tensor_return_value_t MorehNormOperation::create_output_tensors( @@ -172,12 +170,7 @@ MorehNormOperation::tensor_return_value_t MorehNormOperation::create_output_tens return output.value(); } const auto& input = tensor_args.input; - return create_device_tensor( - compute_output_shapes(operation_attributes, tensor_args), - input.get_dtype(), - Layout::TILE, - input.device(), - operation_attributes.memory_config); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), input.device()); } std::tuple MorehNormOperation::invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_norm/device/moreh_norm_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_norm/device/moreh_norm_device_operation.hpp index 8c83cbe8900b..d465478d0015 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_norm/device/moreh_norm_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_norm/device/moreh_norm_device_operation.hpp @@ -45,7 +45,7 @@ struct MorehNormOperation { const std::optional& output; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; DEFINE_PROGRAM_FACTORY(ProgramFactoryWOther) @@ -58,7 +58,7 @@ struct MorehNormOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_norm_backward/device/moreh_norm_backward_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_norm_backward/device/moreh_norm_backward_device_operation.cpp index 988c8d9301c2..c384c9aadba6 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_norm_backward/device/moreh_norm_backward_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_norm_backward/device/moreh_norm_backward_device_operation.cpp @@ -32,9 +32,17 @@ void MorehNormBackwardOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehNormBackwardOperation::shape_return_value_t MorehNormBackwardOperation::compute_output_shapes( +MorehNormBackwardOperation::spec_return_value_t MorehNormBackwardOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - return tensor_args.input.get_shape(); + if (tensor_args.input_grad.has_value()) { + return tensor_args.input_grad->get_tensor_spec(); + } + return TensorSpec( + tensor_args.input.get_logical_shape(), + TensorLayout( + tensor_args.input.get_dtype(), + PageConfig(tensor_args.input.get_layout()), + operation_attributes.memory_config)); }; MorehNormBackwardOperation::tensor_return_value_t MorehNormBackwardOperation::create_output_tensors( @@ -42,13 +50,7 @@ MorehNormBackwardOperation::tensor_return_value_t MorehNormBackwardOperation::cr if (tensor_args.input_grad.has_value()) { return tensor_args.input_grad.value(); } - const auto& input = tensor_args.input; - return create_device_tensor( - compute_output_shapes(operation_attributes, tensor_args), - input.get_dtype(), - input.get_layout(), - input.device(), - operation_attributes.memory_config); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), tensor_args.input.device()); } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_norm_backward/device/moreh_norm_backward_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_norm_backward/device/moreh_norm_backward_device_operation.hpp index 2ee554c685be..78b8d1f150eb 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_norm_backward/device/moreh_norm_backward_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_norm_backward/device/moreh_norm_backward_device_operation.hpp @@ -50,7 +50,7 @@ struct MorehNormBackwardOperation { const std::optional& input_grad; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; DEFINE_PROGRAM_FACTORY(ProgramFactory) @@ -61,7 +61,7 @@ struct MorehNormBackwardOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_sgd/device/moreh_sgd_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_sgd/device/moreh_sgd_device_operation.cpp index 678cd55535ab..b50c0eb9e601 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_sgd/device/moreh_sgd_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_sgd/device/moreh_sgd_device_operation.cpp @@ -45,18 +45,37 @@ void MorehSgdOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehSgdOperation::shape_return_value_t MorehSgdOperation::compute_output_shapes( +MorehSgdOperation::spec_return_value_t MorehSgdOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - auto input_tensor_shape = tensor_args.param_in.get_shape(); + auto input_tensor_shape = tensor_args.param_in.get_logical_shape(); + auto dtype = tensor_args.param_in.get_dtype(); + Layout layout{Layout::TILE}; + + std::vector> ret; + + if (tensor_args.param_out.has_value()) { + ret.push_back(tensor_args.param_out->get_tensor_spec()); + } else { + ret.push_back(TensorSpec( + input_tensor_shape, TensorLayout(dtype, PageConfig(layout), operation_attributes.param_out_memory_config))); + } + + if (tensor_args.momentum_buffer_out.has_value()) { + ret.push_back(tensor_args.momentum_buffer_out->get_tensor_spec()); + } else if (operation_attributes.momentum != 0.0f) { + ret.push_back(TensorSpec( + input_tensor_shape, + TensorLayout(dtype, PageConfig(layout), operation_attributes.momentum_buffer_out_memory_config))); + } else { + ret.push_back(std::nullopt); + } - return {input_tensor_shape, input_tensor_shape}; + return ret; }; MorehSgdOperation::tensor_return_value_t MorehSgdOperation::create_output_tensors( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - const auto& output_shapes = compute_output_shapes(operation_attributes, tensor_args); - auto dtype = tensor_args.param_in.get_dtype(); - Layout layout{Layout::TILE}; + const auto& output_specs = compute_output_specs(operation_attributes, tensor_args); auto device = tensor_args.param_in.device(); std::vector> ret; @@ -64,24 +83,18 @@ MorehSgdOperation::tensor_return_value_t MorehSgdOperation::create_output_tensor if (tensor_args.param_out.has_value()) { ret.push_back(tensor_args.param_out.value()); } else { - ret.push_back(create_device_tensor( - output_shapes.at(0).value(), dtype, layout, device, operation_attributes.param_out_memory_config)); + ret.push_back(create_device_tensor(*output_specs[0], device)); } if (tensor_args.momentum_buffer_out.has_value()) { ret.push_back(tensor_args.momentum_buffer_out.value()); - } else if (operation_attributes.momentum != 0.0f) { - ret.push_back(create_device_tensor( - output_shapes.at(1).value(), - dtype, - layout, - device, - operation_attributes.momentum_buffer_out_memory_config)); + } else if (output_specs[1].has_value()) { + ret.push_back(create_device_tensor(*output_specs[1], device)); } else { ret.push_back(std::nullopt); } - return std::move(ret); + return ret; } std::tuple MorehSgdOperation::invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_sgd/device/moreh_sgd_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_sgd/device/moreh_sgd_device_operation.hpp index ababc16fd384..c01f8c90234c 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_sgd/device/moreh_sgd_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_sgd/device/moreh_sgd_device_operation.hpp @@ -31,7 +31,7 @@ struct MorehSgdOperation { const std::optional& momentum_buffer_out; }; - using shape_return_value_t = std::vector>; + using spec_return_value_t = std::vector>; using tensor_return_value_t = std::vector>; struct ProgramFactory { @@ -63,7 +63,7 @@ struct MorehSgdOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& param_in, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_softmax/device/moreh_softmax_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_softmax/device/moreh_softmax_device_operation.cpp index 0b181c677fb5..3bdb4465545f 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_softmax/device/moreh_softmax_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_softmax/device/moreh_softmax_device_operation.cpp @@ -106,9 +106,18 @@ void MorehSoftmaxOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); } -MorehSoftmaxOperation::shape_return_value_t MorehSoftmaxOperation::compute_output_shapes( +MorehSoftmaxOperation::spec_return_value_t MorehSoftmaxOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - return tensor_args.input.get_shape(); + if (tensor_args.output.has_value()) { + return tensor_args.output->get_tensor_spec(); + } + + return TensorSpec( + tensor_args.input.get_logical_shape(), + TensorLayout( + tensor_args.input.get_dtype(), + PageConfig(tensor_args.input.get_layout()), + operation_attributes.memory_config)); } MorehSoftmaxOperation::tensor_return_value_t MorehSoftmaxOperation::create_output_tensors( @@ -118,10 +127,7 @@ MorehSoftmaxOperation::tensor_return_value_t MorehSoftmaxOperation::create_outpu return output.value(); } - const auto& input = tensor_args.input; - const auto& output_shape = input.get_shape(); - return create_device_tensor( - output_shape, input.get_dtype(), input.get_layout(), input.device(), operation_attributes.memory_config); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), tensor_args.input.device()); } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_softmax/device/moreh_softmax_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_softmax/device/moreh_softmax_device_operation.hpp index e8829852fdfc..dd2a713252f1 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_softmax/device/moreh_softmax_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_softmax/device/moreh_softmax_device_operation.hpp @@ -41,7 +41,7 @@ struct MorehSoftmaxOperation { const std::optional& output; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; #define DEFINE_SOFTMAX_FACTORY(factory_name) \ @@ -84,7 +84,7 @@ struct MorehSoftmaxOperation { static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); static void validate_inputs(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static MorehSoftmaxOpParallelizationStrategy get_parallelization_strategy( const operation_attributes_t&, const tensor_args_t&); diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_softmax_backward/device/moreh_softmax_backward_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_softmax_backward/device/moreh_softmax_backward_device_operation.cpp index cdc4071e6fe3..f456fadd25c8 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_softmax_backward/device/moreh_softmax_backward_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_softmax_backward/device/moreh_softmax_backward_device_operation.cpp @@ -95,9 +95,17 @@ void MorehSoftmaxBackwardOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); } -MorehSoftmaxBackwardOperation::shape_return_value_t MorehSoftmaxBackwardOperation::compute_output_shapes( +MorehSoftmaxBackwardOperation::spec_return_value_t MorehSoftmaxBackwardOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - return tensor_args.output_tensor.get_shape(); + if (tensor_args.input_grad_tensor.has_value()) { + return tensor_args.input_grad_tensor->get_tensor_spec(); + } + return TensorSpec( + tensor_args.output_tensor.get_logical_shape(), + TensorLayout( + tensor_args.output_tensor.get_dtype(), + PageConfig(tensor_args.output_tensor.get_layout()), + operation_attributes.memory_config)); } MorehSoftmaxBackwardOperation::tensor_return_value_t MorehSoftmaxBackwardOperation::create_output_tensors( @@ -107,14 +115,8 @@ MorehSoftmaxBackwardOperation::tensor_return_value_t MorehSoftmaxBackwardOperati return input_grad_tensor.value(); } - const auto& output_tensor = tensor_args.output_tensor; - const auto& input_grad_shape = output_tensor.get_shape(); return create_device_tensor( - input_grad_shape, - output_tensor.get_dtype(), - output_tensor.get_layout(), - output_tensor.device(), - operation_attributes.memory_config); + compute_output_specs(operation_attributes, tensor_args), tensor_args.output_tensor.device()); } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_softmax_backward/device/moreh_softmax_backward_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_softmax_backward/device/moreh_softmax_backward_device_operation.hpp index 68da88b09ebf..317c99ef9a4e 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_softmax_backward/device/moreh_softmax_backward_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_softmax_backward/device/moreh_softmax_backward_device_operation.hpp @@ -42,7 +42,7 @@ struct MorehSoftmaxBackwardOperation { const std::optional& input_grad_tensor; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; #define DEFINE_SOFTMAX_BACKWARD_FACTORY(factory_name) \ @@ -85,7 +85,7 @@ struct MorehSoftmaxBackwardOperation { static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); static void validate_inputs(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static MorehSoftmaxBackwardOpParallelizationStrategy get_parallelization_strategy( const operation_attributes_t&, const tensor_args_t&); diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_sum/device/moreh_sum_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_sum/device/moreh_sum_device_operation.cpp index f1c4fa7c67c8..883a86fc4bef 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_sum/device/moreh_sum_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_sum/device/moreh_sum_device_operation.cpp @@ -62,8 +62,12 @@ void MorehSumOperation::validate_on_program_cache_hit( validate_tensors(operation_attributes, tensor_args); }; -MorehSumOperation::shape_return_value_t MorehSumOperation::compute_output_shapes( +MorehSumOperation::spec_return_value_t MorehSumOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { + if (tensor_args.output.has_value()) { + return {tensor_args.output->get_tensor_spec()}; + } + const auto& input = tensor_args.input; const auto& input_shape = input.get_shape(); const auto input_rank = input_shape.rank(); @@ -115,7 +119,13 @@ MorehSumOperation::shape_return_value_t MorehSumOperation::compute_output_shapes } log_debug(tt::LogOp, "{}:{} output_shape {}", __func__, __LINE__, output_shape); - return {output_shape}; + return TensorSpec( + output_shape.logical_shape(), + TensorLayout::fromLegacyPaddedShape( + tensor_args.input.get_dtype(), + PageConfig(tensor_args.input.get_layout()), + operation_attributes.memory_config, + output_shape)); }; MorehSumOperation::tensor_return_value_t MorehSumOperation::create_output_tensors( @@ -126,12 +136,7 @@ MorehSumOperation::tensor_return_value_t MorehSumOperation::create_output_tensor } log_debug(tt::LogOp, "{}:{} create output tensor", __func__, __LINE__); - return create_device_tensor( - compute_output_shapes(operation_attributes, tensor_args), - tensor_args.input.get_dtype(), - tensor_args.input.get_layout(), - tensor_args.input.device(), - operation_attributes.memory_config); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), tensor_args.input.device()); } std::tuple MorehSumOperation::invoke( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_sum/device/moreh_sum_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_sum/device/moreh_sum_device_operation.hpp index f3ce14164592..8e14e460fc3a 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_sum/device/moreh_sum_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_sum/device/moreh_sum_device_operation.hpp @@ -48,7 +48,7 @@ struct MorehSumOperation { const std::optional& output; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; MOREH_SUM_FACTORY_H(MorehSumHFactory) @@ -69,7 +69,7 @@ struct MorehSumOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& input, diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_sum_backward/device/moreh_sum_backward_device_operation.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_sum_backward/device/moreh_sum_backward_device_operation.cpp index 9c350e9c1e68..6dda5336732f 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_sum_backward/device/moreh_sum_backward_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_sum_backward/device/moreh_sum_backward_device_operation.cpp @@ -96,9 +96,15 @@ void MorehSumBackwardOperation::validate_on_program_cache_hit( validate_inputs(operation_attributes, tensor_args); }; -MorehSumBackwardOperation::shape_return_value_t MorehSumBackwardOperation::compute_output_shapes( +MorehSumBackwardOperation::spec_return_value_t MorehSumBackwardOperation::compute_output_specs( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args) { - return tensor_args.input->get_shape(); + if (tensor_args.input_grad.has_value()) { + return tensor_args.input_grad->get_tensor_spec(); + } + TT_FATAL(tensor_args.input.has_value(), "input tensor should not be std::nullopt."); + return TensorSpec( + tensor_args.input->get_logical_shape(), + TensorLayout(tensor_args.input->get_dtype(), PageConfig(Layout::TILE), operation_attributes.memory_config)); }; MorehSumBackwardOperation::tensor_return_value_t MorehSumBackwardOperation::create_output_tensors( @@ -107,14 +113,7 @@ MorehSumBackwardOperation::tensor_return_value_t MorehSumBackwardOperation::crea if (input_grad.has_value()) { return input_grad.value(); } - auto input = tensor_args.input; - TT_FATAL(input.has_value(), "input tensor should not be std::nullopt."); - - auto dtype = input->dtype(); - Layout layout{Layout::TILE}; - auto device = input->device(); - auto memory_config = operation_attributes.memory_config; - return create_device_tensor(input->shape(), dtype, layout, device, memory_config); + return create_device_tensor(compute_output_specs(operation_attributes, tensor_args), tensor_args.input->device()); } std::tuple diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_sum_backward/device/moreh_sum_backward_device_operation.hpp b/ttnn/cpp/ttnn/operations/moreh/moreh_sum_backward/device/moreh_sum_backward_device_operation.hpp index 9d5e86c3873a..8c3eeef65b1f 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_sum_backward/device/moreh_sum_backward_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_sum_backward/device/moreh_sum_backward_device_operation.hpp @@ -23,7 +23,7 @@ struct MorehSumBackwardOperation { const std::optional& input_grad; }; - using shape_return_value_t = Shape; + using spec_return_value_t = TensorSpec; using tensor_return_value_t = Tensor; struct ProgramFactory { @@ -54,7 +54,7 @@ struct MorehSumBackwardOperation { static program_factory_t select_program_factory(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_miss(const operation_attributes_t&, const tensor_args_t&); static void validate_on_program_cache_hit(const operation_attributes_t&, const tensor_args_t&); - static shape_return_value_t compute_output_shapes(const operation_attributes_t&, const tensor_args_t&); + static spec_return_value_t compute_output_specs(const operation_attributes_t&, const tensor_args_t&); static tensor_return_value_t create_output_tensors(const operation_attributes_t&, const tensor_args_t&); static std::tuple invoke( const Tensor& output_grad, From f72effcf876cdd8058d95c4c6afe076458441466 Mon Sep 17 00:00:00 2001 From: Pavle Janevski <165378935+pjanevskiTT@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:33:07 +0100 Subject: [PATCH 59/87] Bump umd to fix grayskull cluster warning (#16126) --- tt_metal/third_party/umd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tt_metal/third_party/umd b/tt_metal/third_party/umd index bfbb7e969b15..e157d9b83b1e 160000 --- a/tt_metal/third_party/umd +++ b/tt_metal/third_party/umd @@ -1 +1 @@ -Subproject commit bfbb7e969b15e82b3d4e8428c555e79c71520d1e +Subproject commit e157d9b83b1e928d6e38473885db5ef4897199ef From 22ba0cc69a8f8b249b2e17bfeeda951162f17d01 Mon Sep 17 00:00:00 2001 From: Pavle Josipovic Date: Tue, 17 Dec 2024 15:43:00 +0000 Subject: [PATCH 60/87] Clean-up the usage of deallocate_activation --- .../ttnn/operations/conv/conv2d/conv2d.cpp | 27 +++++------------- .../operations/conv/conv2d/conv2d_utils.cpp | 15 +++++----- .../operations/conv/conv2d/conv2d_utils.hpp | 2 +- .../conv_transpose2d/conv_transpose2d.cpp | 28 ++++++------------- 4 files changed, 24 insertions(+), 48 deletions(-) diff --git a/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d.cpp b/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d.cpp index 3f88bcf04d8c..690cfcbde9a3 100644 --- a/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d.cpp +++ b/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d.cpp @@ -18,6 +18,7 @@ #include "ttnn/operations/conv/conv2d/conv2d_utils.hpp" #include "ttnn/operations/conv/conv2d/prepare_conv2d_weights.hpp" #include "ttnn/operations/conv/conv2d/device/conv2d_op.hpp" +#include "ttnn/operations/data_movement/move/move.hpp" #include "ttnn/operations/matmul/matmul.hpp" #include "ttnn/operations/sliding_window/halo/halo.hpp" #include "ttnn/operations/sliding_window/sliding_window.hpp" @@ -87,7 +88,7 @@ Result conv2d( DeviceComputeKernelConfig compute_config = compute_config_.value_or( init_device_compute_kernel_config(device->arch(), std::nullopt, MathFidelity::HiFi4, true, false, false)); - auto [input_tensor_post_tm, parallel_config, output_parallel_config, tensor_manipulated, use_non_tile_height] = + auto [input_tensor_post_tm, parallel_config, output_parallel_config, use_non_tile_height] = shard_or_reshard_tensor_if_required( device, input_tensor, @@ -100,14 +101,6 @@ Result conv2d( mm_conv, auto_shard, is_non_tile_mul_width); - if (tensor_manipulated) { - if (conv_config.deallocate_activation) { - ttnn::Tensor input_tensor_ = input_tensor; // TODO: allow in place modification of inputs to the op - input_tensor_.deallocate(); - // ttnn::operations::core::deallocate(input_tensor_); - } - conv_config.deallocate_activation = true; - } uint32_t round_up_size = !use_non_tile_height ? tt::constants::TILE_HEIGHT : 1; uint32_t nhw_out = batch_size * output_height * output_width; @@ -171,13 +164,8 @@ Result conv2d( } // if 1x1 conv w/ stride 1, convert input tensor to tile layout if required if (mm_conv) { - Tensor input_tensor_post_tm_out = ttnn::to_layout( + input_tensor_post_tm = ttnn::to_layout( input_tensor_post_tm, Layout::TILE, conv_config.dtype, input_tensor_post_tm.memory_config(), device); - if (conv_config.deallocate_activation) { - input_tensor_post_tm.deallocate(); - // ttnn::operations::core::deallocate(input_tensor_post_tm); - } - input_tensor_post_tm = input_tensor_post_tm_out; } // call optimized conv op or matmul micro op bool input_is_on_device = ttnn::is_tensor_on_device_or_multidevice(input_tensor_post_tm); @@ -219,15 +207,14 @@ Result conv2d( !use_non_tile_height); if (conv_config.deallocate_activation) { - ttnn::operations::core::deallocate(input_tensor_post_tm); + input_tensor_post_tm.deallocate(/*force*/true); } + input_tensor_post_tm = std::move(halo_output); + if (conv_config.reallocate_halo_output) { - auto move_output = ttnn::operations::core::reallocate(halo_output, input_tensor_post_tm.memory_config()); - ttnn::operations::core::deallocate(halo_output); - halo_output = move_output; + input_tensor_post_tm = ttnn::move(input_tensor_post_tm); } - input_tensor_post_tm = halo_output; } // call conv micro op diff --git a/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d_utils.cpp b/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d_utils.cpp index 08111cc07d9b..acd3453ecf53 100644 --- a/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d_utils.cpp +++ b/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d_utils.cpp @@ -12,6 +12,7 @@ #include "impl/buffers/buffer_constants.hpp" #include "ttnn/operations/core/core.hpp" #include "tt_metal/common/work_split.hpp" +#include "ttnn/operations/data_movement/move/move.hpp" #include "ttnn/operations/eltwise/unary/common/unary_op_utils.hpp" #include "ttnn/operations/data_movement/pad/pad.hpp" #include "ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.hpp" @@ -490,7 +491,7 @@ static std::tuple get_conv_padded_i TT_FATAL( (!input_tensor_on_device || input_tensor_.is_sharded()) || conv_config.shard_layout.has_value(), - "Tesor must be sharded or shard_layout must be set."); + "Tensor must be sharded or shard_layout must be set."); TensorMemoryLayout shard_layout; if (conv_config.shard_layout.has_value()) { @@ -631,7 +632,7 @@ static std::tuple get_conv_padded_i } template -std::tuple shard_or_reshard_tensor_if_required( +std::tuple shard_or_reshard_tensor_if_required( T* device, const ttnn::Tensor& input_tensor_, const Conv2dConfig& conv_config, @@ -710,8 +711,8 @@ std::tuple shard_or_re auto resharded_input_tensor = ttnn::to_memory_config( input_tensor, input_tensor_sharded_memory_config, std::nullopt); if (conv_config.deallocate_activation) { - input_tensor.deallocate(); - resharded_input_tensor = ttnn::operations::core::reallocate(resharded_input_tensor, resharded_input_tensor.memory_config()); + input_tensor.deallocate(/*force*/true); + resharded_input_tensor = ttnn::move(resharded_input_tensor); } input_tensor = resharded_input_tensor; } @@ -719,7 +720,7 @@ std::tuple shard_or_re input_tensor = ttnn::to_device(input_tensor, device, (auto_shard_mm ? ttnn::DRAM_MEMORY_CONFIG : input_tensor_sharded_memory_config)); } } - return {input_tensor, parallel_config, output_parallel_config, needs_shard_or_reshard, use_non_tile_height}; + return {input_tensor, parallel_config, output_parallel_config, use_non_tile_height}; } void validate_weight_and_bias_tensors( @@ -847,7 +848,7 @@ void adjust_conv_op_config_for_auto_shard_if_necessary( } } -template std::tuple +template std::tuple shard_or_reshard_tensor_if_required( Device* device, const ttnn::Tensor& input_tensor_, @@ -861,7 +862,7 @@ shard_or_reshard_tensor_if_required( bool auto_shard, bool is_non_tile_mul_width); -template std::tuple +template std::tuple shard_or_reshard_tensor_if_required( MeshDevice* device, const ttnn::Tensor& input_tensor_, diff --git a/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d_utils.hpp b/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d_utils.hpp index d8c2f358143a..69ce604a6713 100644 --- a/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d_utils.hpp +++ b/ttnn/cpp/ttnn/operations/conv/conv2d/conv2d_utils.hpp @@ -155,7 +155,7 @@ void adjust_conv_op_config_for_auto_shard_if_necessary( std::optional input_memory_config); template -std::tuple +std::tuple shard_or_reshard_tensor_if_required( T* device, const ttnn::Tensor& input_tensor_, diff --git a/ttnn/cpp/ttnn/operations/conv/conv_transpose2d/conv_transpose2d.cpp b/ttnn/cpp/ttnn/operations/conv/conv_transpose2d/conv_transpose2d.cpp index 14893eac16da..32e30e6bf5aa 100644 --- a/ttnn/cpp/ttnn/operations/conv/conv_transpose2d/conv_transpose2d.cpp +++ b/ttnn/cpp/ttnn/operations/conv/conv_transpose2d/conv_transpose2d.cpp @@ -6,6 +6,7 @@ #include #include "ttnn/operations/core/core.hpp" +#include "ttnn/operations/data_movement/move/move.hpp" #include "ttnn/operations/matmul/matmul.hpp" #include "ttnn/operations/conv/conv_transpose2d/conv_transpose2d.hpp" #include "ttnn/operations/conv/conv2d/conv2d_utils.hpp" @@ -190,7 +191,7 @@ Result conv_transpose2d( //Call Halo Transpose - auto [input_tensor_post_tm, parallel_config, output_parallel_config, tensor_manipulated, use_non_tile_height] = + auto [input_tensor_post_tm, parallel_config, output_parallel_config, use_non_tile_height] = shard_or_reshard_tensor_if_required( device, input_tensor, @@ -206,15 +207,6 @@ Result conv_transpose2d( uint32_t round_up_size = !use_non_tile_height ? tt::constants::TILE_HEIGHT : 1; - if (tensor_manipulated) { - if (conv_config.deallocate_activation) { - ttnn::Tensor input_tensor_ = input_tensor; // TODO: allow in place modification of inputs to the op - input_tensor_.deallocate(); - // ttnn::operations::core::deallocate(input_tensor_); - } - conv_config.deallocate_activation = true; - } - Tensor halo_output; if (!mm_conv) { sliding_window_config.num_cores_nhw = get_num_cores_nhw_from_parallel_config(parallel_config); @@ -232,12 +224,12 @@ Result conv_transpose2d( input_tensor_post_tm.memory_config()); if(conv_config.deallocate_activation) { - input_tensor_post_tm.deallocate(); + input_tensor_post_tm.deallocate(/*force*/true); log_debug(tt::LogOp, "Deallocate Input Tensor"); } + if (conv_config.reallocate_halo_output) { - auto move_output = ttnn::operations::core::reallocate(halo_output, halo_output.memory_config()); - halo_output = move_output; + halo_output = ttnn::move(halo_output); log_debug(tt::LogOp, "Reallocate Halo Output"); } } @@ -293,12 +285,12 @@ Result conv_transpose2d( input_width); } if (mm_conv) { - Tensor matmul_input = ttnn::to_layout( + input_tensor_post_tm = ttnn::to_layout( input_tensor_post_tm, Layout::TILE, conv_config.dtype, input_tensor_post_tm.memory_config(), device); std::optional program_config = std::nullopt; std::optional mm_output_memory_config = std::nullopt; - if (matmul_input.is_sharded()) { + if (input_tensor_post_tm.is_sharded()) { uint32_t num_cores_c = get_num_cores_channels_from_parallel_config(parallel_config); program_config = determine_matmul_op_config_from_conv_op_config( opt_conv_op_parallel_config, @@ -310,7 +302,7 @@ Result conv_transpose2d( mm_output_memory_config = conv_out_memory_config; } Tensor matmul_output = ttnn::linear( - matmul_input, + input_tensor_post_tm, weight_tensor_on_device, bias_tensor_on_device, false, @@ -319,10 +311,6 @@ Result conv_transpose2d( std::nullopt, program_config); - if (conv_config.deallocate_activation) { - ttnn::operations::core::deallocate(matmul_input); - } - if (memory_config.has_value() && memory_config.value() != matmul_output.memory_config()) { matmul_output = ttnn::to_memory_config(matmul_output, memory_config.value(), std::nullopt); } From 579a830095270fd79a2ffc8fd9668f0ebc8f84ff Mon Sep 17 00:00:00 2001 From: Pavle Josipovic Date: Wed, 18 Dec 2024 10:57:54 +0000 Subject: [PATCH 61/87] Fix Yolo4 --- models/demos/yolov4/ttnn/downsample4.py | 8 +++++++- models/demos/yolov4/ttnn/downsample5.py | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/models/demos/yolov4/ttnn/downsample4.py b/models/demos/yolov4/ttnn/downsample4.py index b47481b030cf..a3cec71bfdcb 100644 --- a/models/demos/yolov4/ttnn/downsample4.py +++ b/models/demos/yolov4/ttnn/downsample4.py @@ -15,7 +15,13 @@ def __init__(self, model) -> None: torch_model = model.torch_model self.torch_model = torch_model self.conv1 = Conv( - torch_model, "down4.conv1", [1, 40, 40, 256], (2, 2, 1, 1), reshard=True, height_sharding=False + torch_model, + "down4.conv1", + [1, 40, 40, 256], + (2, 2, 1, 1), + reshard=True, + height_sharding=False, + deallocate=False, ) self.conv2 = Conv(torch_model, "down4.conv2", [1, 20, 20, 512], (1, 1, 0, 0), deallocate=False) self.conv3 = Conv(torch_model, "down4.conv3", [1, 20, 20, 512], (1, 1, 0, 0)) diff --git a/models/demos/yolov4/ttnn/downsample5.py b/models/demos/yolov4/ttnn/downsample5.py index e125a6249df6..f946cc82a1fb 100644 --- a/models/demos/yolov4/ttnn/downsample5.py +++ b/models/demos/yolov4/ttnn/downsample5.py @@ -15,7 +15,13 @@ def __init__(self, model) -> None: torch_model = model.torch_model self.torch_model = torch_model self.conv1 = Conv( - torch_model, "down5.conv1", [1, 20, 20, 512], (2, 2, 1, 1), reshard=True, height_sharding=False + torch_model, + "down5.conv1", + [1, 20, 20, 512], + (2, 2, 1, 1), + reshard=True, + height_sharding=False, + deallocate=False, ) self.conv2 = Conv( torch_model, "down5.conv2", [1, 10, 10, 1024], (1, 1, 0, 0), width_sharding=True, deallocate=False From 19e9e34816bd8ee32121a6cdf311e675a1130eac Mon Sep 17 00:00:00 2001 From: Pavle Josipovic Date: Wed, 18 Dec 2024 14:01:40 +0000 Subject: [PATCH 62/87] Fix yolo4 neck test Test can't be re-run as inputs get deallocated. --- tests/ttnn/integration_tests/yolov4/test_ttnn_neck.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/ttnn/integration_tests/yolov4/test_ttnn_neck.py b/tests/ttnn/integration_tests/yolov4/test_ttnn_neck.py index 1943bc0dcd7b..c094b9d42269 100644 --- a/tests/ttnn/integration_tests/yolov4/test_ttnn_neck.py +++ b/tests/ttnn/integration_tests/yolov4/test_ttnn_neck.py @@ -64,10 +64,6 @@ def test_neck(device, reset_seeds, model_location_generator): torch_model.eval() result_ttnn = ttnn_model(device, ttnn_input_tensor) - start_time = time.time() - for x in range(2): - result_ttnn = ttnn_model(device, ttnn_input_tensor) - logger.info(f"Time taken: {time.time() - start_time}") result_1 = ttnn.to_torch(result_ttnn[0]) result_2 = ttnn.to_torch(result_ttnn[1]) From 12684c5a2ae0778f5cd14549b04555603ab69d6c Mon Sep 17 00:00:00 2001 From: Johanna Rock <129077594+johanna-rock-tt@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:51:38 +0100 Subject: [PATCH 63/87] llm tech report multi device section (#16180) --- tech_reports/LLMs/images/block_sharded.png | Bin 0 -> 1203081 bytes tech_reports/LLMs/images/column_parallel.png | Bin 0 -> 256459 bytes .../column_parallel_then_row_parallel.png | Bin 0 -> 409789 bytes tech_reports/LLMs/images/row_parallel.png | Bin 0 -> 299579 bytes tech_reports/LLMs/llms.md | 204 +++++++++++++++++- ...Programming_Mesh_of_Devices_with_TT-NN.md} | 0 .../images/image1.png | Bin .../images/image2.png | Bin .../images/image3.png | Bin .../images/image4_ring_all_gather.png | Bin .../images/image5_line_all_gather.png | Bin .../images/llama-3.1-70b-hybrid-dp-tp.png | Bin 12 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 tech_reports/LLMs/images/block_sharded.png create mode 100644 tech_reports/LLMs/images/column_parallel.png create mode 100644 tech_reports/LLMs/images/column_parallel_then_row_parallel.png create mode 100644 tech_reports/LLMs/images/row_parallel.png rename tech_reports/{Programming Mesh of Devices/Programming Mesh of Devices with TT-NN.md => Programming_Mesh_of_Devices/Programming_Mesh_of_Devices_with_TT-NN.md} (100%) rename tech_reports/{Programming Mesh of Devices => Programming_Mesh_of_Devices}/images/image1.png (100%) rename tech_reports/{Programming Mesh of Devices => Programming_Mesh_of_Devices}/images/image2.png (100%) rename tech_reports/{Programming Mesh of Devices => Programming_Mesh_of_Devices}/images/image3.png (100%) rename tech_reports/{Programming Mesh of Devices => Programming_Mesh_of_Devices}/images/image4_ring_all_gather.png (100%) rename tech_reports/{Programming Mesh of Devices => Programming_Mesh_of_Devices}/images/image5_line_all_gather.png (100%) rename tech_reports/{Programming Mesh of Devices => Programming_Mesh_of_Devices}/images/llama-3.1-70b-hybrid-dp-tp.png (100%) diff --git a/tech_reports/LLMs/images/block_sharded.png b/tech_reports/LLMs/images/block_sharded.png new file mode 100644 index 0000000000000000000000000000000000000000..eeb0e7e5ed69c25519b4c5c8036665962ae0cbce GIT binary patch literal 1203081 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%0A&ijOX-t5|zUyxQld;qy6Bq1F>E9vs(yn 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

L5~ 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

VRyrRAR?W*Liu=?iItW4>lzcguM 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 CcANwN literal 0 HcmV?d00001 diff --git a/tech_reports/LLMs/images/column_parallel.png b/tech_reports/LLMs/images/column_parallel.png new file mode 100644 index 0000000000000000000000000000000000000000..44f619401f66172c8e86c36dcc070d1d9bcb53c3 GIT binary patch literal 256459 zcmeGEXIPVI*98nKiVYDR1yrP1kq#mtARsDYp+gb~glecpM4EI_(2?pWML~*4O@%;0 zlOhbDR4F0!C`bvRNN)k&GxH31KjX|i-}mD^-aq#*FGIM>KKEX0?X}N~XL>rCoO_P$ z*|KE|=Z))E4Yq9Ajoz|l+ml^8z$ZIP>kI}bm5!!O&s=Cnw^ReFD z1q7QCwY6@yTHoc@DOay*e$2k59PsRyUq)d$k<|mdN1vk{g*c;h3{qcw6uF{g(BdXa zzpmI>(IDC(;=tzyYt-RlmG0ccxiFd)VGgn4ZUKvh(^d+fQdh?aeh5bIuzCM) zngU*@9JcS;FLdeQmaT06*Ma3#wp+W<;n&-<|F3UuobB_${YEn<{(n6E!}9Cw+bP#- zo$mdgck<`?!O8di_vAmV-G9UKxB28LfmR8*_Ew`k!^J)G*aMuR8R*!C!!YYrw z#@}A+e{e=t=aL?(nm>#4z$Wfw@+{U_ti6!$uzVcM-?!B0nXXVm*Z%l#$P{J-&XW3m zhwx_pOP6e4`~FldwL!l7PDP4j!IwvQRXnCzgkU)5KZ9F&*f0By;`M7BWPjZ8DEmYa z13&v__@dqAj4S&TPw~5bRv-D-o6!O{b31RxnIAW^y8Uj+T>C6@SS_HU!lT!nI{Qi2 z|NgQ>d&s|>$TsX@a#xt*O}^usC;BXYuTCOAbsR+aat)_^hY-T8S)+lAp}qmKo8SC# zkgU5^ouvBp;n`=@%QBOEawjtJHwB;>l9whB!GfR8AF7 zLe2Nh-~IWZe3lyoztqkK`6KvGB<~G`E2Q&#_PDeZ(AMVjTd(n<&d)A&?L7?XGhdsI z%~+W%3$R+?kSw@ljN?pH{qd`R32dOuf&E5&FCP=NHV7p!YNB8u!i}C5o6d?P3WUMo z+3A?_7JVW7qVER|b7^{3Q^4AcLp+L1ztDBBC}5Rgp}X_gz4D+B5Sf`ROt-#Nd#$CKvgu)eyW8*6s|Qw3hvtJbhZSZ(RkY`vXoF5M+*c>CS5 z)%li7T{UWJQ+X>B#k$WRCVCP)_s2=p^k|s5*Uk3Z1{=@tb1DaZ-LnhLNo}>?jKs

{+(+*`?`Xn&-|F% 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%0Aq5BG078dqDvB!Af8T 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

F0l`YPV){fMVP>+0Lq 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%0AV7ivWYmee2^!V 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 z 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

eMEToAkVp`{jtOo{a#l6iIIj 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:}j(xp3sC|3#)iF)uJEq~+(!~EMb`5=k%gtw*A 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∨{`C^elHyCQC(~)+TcoD@tzH1F=Gj7gKOYs#=*q| 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 gSy;;hmZLK0YsZ>x{JS)QFzU*pbS~ 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 P@J~fiO#yw^%>VxYy|l6) literal 0 HcmV?d00001 diff --git a/tech_reports/LLMs/images/row_parallel.png b/tech_reports/LLMs/images/row_parallel.png new file mode 100644 index 0000000000000000000000000000000000000000..c060611e3af5ba82d75e441e09cff9d727057801 GIT binary patch literal 299579 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�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

qz*w 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

bB6P8kET>}H7#~bXJAE8F?~)1j9D6j-cT{s8 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

a7+-`<~pA1vW zV*6M6jM;U2W8}MVj@#xv98a<`t7S=Y&1D}>CvlkTP&KTHIjGk5p*k*WmGwLSa;8TMf##N9`6gP8k#iYpJRmpJYA>rVek(Lel%B*R@P^R|@u6&KdZ z+3y7;TbP@#U=c7FR&iF3+1~yz+nfas@3~}Q^o6K$ye51+teuOD`0y4N_NHvZN}hWk zht;lMX1ds&!#~ba#s|^_o4pHTE~0~$|Ar0dO>PP^W-gTvZ!4CRoEKT~%R4sZO#-g> z%s_dAieSWZal@vPS2E3gQpO^i6Rdz8Xo_K$fB@SDrVNQA-V%w|&d~Xq$MD*VwOu-4U z^;!5?r1(xhF)a1quIn)g2iqUNuhnaC?4d3Il9h1V8v8a>=ruR)`pWp$B9!*~`}J~I zZht5HSGfFNi{X?Lf9!3r1fTXtRvk{5MhYw28A4tyYruScPHbER;Sj=6(@&KNsBXJ1 z(B8=<7ln$JEUS~g7}OmxP+&zGmTMj%FJjf{?Mh7(=~njG{*s?wNLX2+Uglufv-};x z93Xji1Q*(}HKxOikuQ-zx=OEN1-aSR#Q@Xsr3PJ~%TVI{e5MAm&u3aA8Zcc05{2_- z1|0G%L+{oTY*1l6jpj2*J_kJN+v4T4UI$HOyxI(&G*xbP)BbSlN9{r9oDAJ1`qa4uA3L${n(t{uW2(<=SexCustQ$L> zq@1O@slPWiMlz@LZhrfzT=JwFgV^P^&a+G3GPN3o%sq-dAH32hLm{^wb=0JC5WDP< zZP5H%P~DGZxVibzRt<=(yuM>zMAc1s@>Z9A#?XLdkKY?IZa?schZV|4&di3`W{Z)m zpx&l|0!jZPBC9~71l+l^$pkpP9r$oj9(a7S3rYVZZa*|64b#+@TDYyy;%OgS%9ll& zL-nloQ8&%s^}|Gvm_4SOI+_=MJ(&}*bMO0Ye%N(WH>4XqqTbQ?L(blzc5{qqM5lwx z=19Hv#1hSWJ~DPJW}311o1uQBYe=3RHGYiE2g~ja5Z!6$l$~MI^kD( zyyohUD#88OHoc9+j!HufDE5Y{TU2+#o;*#cUomWPGu12DP}#U$Q1lZk{BD7nbL1xo zQphihc0~0@FbkEsRQ0L}#-Yi}lO!o&ahrU|qXMP@3s2NC&+Ol?Ch?>i0T$7#gdawD z(wy!YCd(A6HKI(BJ}9nxakJP zEZ`NBgYA4(YajUn-^(pirO&+R3hT>>5kYmZOE)&i^_jIJ2d27!;6`^wwpfaq$X<+r zx>HDHm!<%3;B*;XRM%ka8Ix|hRX6BWrJqTAHG#=h30K@eeYHz7Od#P7)ad4vSYV8! zY)Dqs=Ma|dQ z`wIMgv>!MIgl_P1JAHyo_B2h~X^BI&Ev;ro<#zh*$~1O`6zfX@lKk)JI4DYf3Q2~d zE#6Y75(h^-?Hw_@@b@;$UW_)CGznQv(xkRI(%_Iq^)znnOidD|&oBBL%yH&9OoNmp zNnB+Nx_Zjl?y!H;NF#<^PKN|iE_BCUhLb}^g}MaBv{AmXaon}!H>i@?*i4J}vJx5q znc1Ff{lSg40xcuCU|{$_`2MhO_3-DGs|kj#WgXq7`LrsoT5jd=f&s;P`8)?}r7ij% zT=;h5PC@GBhJ4?81tPxnv*|XUW&cQ0wU4{@;J(!gk9$`M$14?Az(zMnK4E_ZtQO$P z2q6}rMm8HAGR0LPa~ob0WLbpQ)_bg=XH9!8+ z_?JBfjWP0<%0gRZn3+hsa?<6$qtO5-G+HQ)%H>nG_(u7fopWetNH(inH~(RxMDXQ% zV>w43~PwVCa5igFd_HAedet5)wgfs86RQ8aayt&lPi*Mq+j8>$#P>F+&8t z`a*t74Hs-!@Q`XGDTBwm<`3>9%X4y5^wZ9=^7>d#%2{ebYIho|M2qT?-Q7ii{m-j0 zq%r?|8U4NvqmhkC?kfrY22a|0!;`qrESe6&jTr)*Ed-bA`Fye)-`p*)3ZE#xtoI{?*SENFBav%t*7_y_wZltoqWF{_V z_~gsf+{}|m)48^`>5oJT?3d1?yLHQB!@+g1!=e|ZLjCD>euM2eAH`Y1*iK!IJO2aP}Yx%`G-o#xnBGg0Z*!pd{zWC&JqZHK_UUs##p8+m^$<}=v zS0y>nTjAMckze?7Ns6pcD?_8Sv@`=Qo*;o_$+A!uaFW-P7UW|y8hppL{KcqIA*l0d zzHQD%$fvUOKx-xtwAi>2mrj?N zJz73TAD7I+GlUuLZF!|2zBY{jbAH2FSAWd|J5%G3_>H)FTLRuCvXaj1OQR%E9qlPx zbxt0NW1&7yKak3khiZTAW`9?#Xl#*AF?LMbzSkthk<{4KPH*_*A`?cOKEqZiwSMY` z+3JK$QbLM7bKg#x96dwu*uGQg2!8o8rdlkW=N9zX5Le#vGnh1cl7D=)>W}``;+@g+ zE58~#{x`bhIvF|?ckfB^FLZf^OyR5yeMSka8@lYJc#=~)dM79WFLWM0S70;jJc|#z ziTAEmDr|ceYsK9$9crXYh5KE*n2%A>Pz*n2StP9@TU}J^&B@=O+{Q4DYzxebu9~v1 z4Kq=4+G;nlyh*j!-ri{py;}}90HZDM-2HU3bi~7A>R}f2QY;@-PjN5o6FVQT>P9Dh z%Z*#Pu`*~#WPql!rZB#-y;$+6C3rWWBr$4F9r8H1{0l3O(JQY4mjg+m=8fcbn zl3=&wR5YP)0bP=y{!Lgr2jP}SA?*oOTZn_+q>H^XmR(Zg|WipL}={} zg0<#zb=|vx1LoiD#WpT{ls|2ae_L0=P73aI*&kX@BZuUB>&S8Zda*lWg_m+Z=b^8G zLW<%3Kui4f_U@+xnNBZ}3Hi97ZU#;lw}{8tkk>OIA}~Y_R_;6_eap@Ipb#lEq@=jh zKC-Iz(0h3ibCt%cF`fRpbLJNvk;0FG3R(4bg>8T?H%I1m-yDz-`#!*6E5bEZRatY@ zXxHo_H{059de|qSZTh5IlRf*e&M#fCj`PS>hL#O z#M_k*PfSwq{6pnwlh~Yl5U_*~kRp=qe!`B%i*DN8R|QLYM62v+~84%QgM7X zHBWsLh>a0#K^^H}*)c5$LX|!00YEH_`Q`BQe)vPto!-zn31nFEgzmS2)#3D#M?Lv| z2L3{j3Sz(;XElWx%e(H(&-q?rYc)1<>s8n{%VHX>g_Iy!CbDwJu_4A`eooHx^v=#L zvjHr!HoN9^vyyp!&Mf(IMpDBSqKtmHfsod|CQ%+o(bNyY6ka?eZJ6NYzro$3X(wV$ zCdG);8S!I{+glwgA?|v+s-9wJwk{slf6_V!Y*yoW4P6qc83-C|W>hoK>$FDRe8Hw? z$;%)w!pAl~P>aoZnCFl<=u|6e{BU2l3lPN}TY+Xv&#YJh^jJV3(_MO-oqgn4dK2?F z>j`~kEJX2}c#mo#E|)asTDrg?pJqq|R_sjTw!2)=5kQar|lMdm@H2U zkK)1fTrsqRD-iS|cXdH9IDTg$?PCbPsLD`$eT9+YHM+F4H^LR7OUb?)8(Djaz79nF zMW!yqQ1q(6-gxh1;g-LI#46irQ@?TL1nL#-tL6@T`x)ZtyzEcpn`u312QshEe%E?> zLw6vbSM;nvq4G(8{@0~>ty)>W)eYa=wC>?TDg!=uu z>(Ez>^yR}xQN- z=**W%`kd?B)HR*Hzz36ZI|_nM)#QfMGQjmu>I;lSk^PI?6+?yQ(0mb}EEBe}_}$>n z9%)SV4O{1QUEL;|hsZ~g6Pekg5#aann>#yi;Y+Lo$ibs%DU+&^%a~!~{Wp5zk@=q2 zFh42AiQe+cC5tMX+DOjotnOhWj7HWX%lwU>ulU+2#)H=we?_Z5+2~oT$BCHRgYM>) z%fE8Bd#@Ak8I9xx$yX_N-*um)WL2W@<$u+HvNQP|tT#c+r=kqM{#L{?gynU;n0uaaf(Ji^a@vsn!h@?0r0kw6rdyq13yzXY&UFU4{C+^C$nd%UXK?MR zy7Ol2ckc(7>Fghw^TX_9ae+qE`Bn(P6pMVdcL-&YESFEG5oZ#LlE3)SRJ=D;YVp-ZfCzq=!&x^HQy>CR*_2(j_c#oV1un{j&lo!KoxQ77f$*;lz@*|HBMA^ z4j{6X12PqwChlq`RC<@&tjQyyVK&rA+$D-I*BEtaConlyE^V3t4xTfJg2R}VKWZ=^ zN2hOayq6C*>+|hpPKaaaAjC$baV{mE8|r`CE&Dz_tCR^5C61>1+EON>8u*Z3)peSs z`dE*J6ArFpp!&wPIY_($OByXMeqhp_-Fc)+9GH#0g?sgqx5>jZ7U+J}tRBqR#G|9snlw37(2v?3=vjUBFR~6GNZ~ffb+>#Ez zjb5*uJ<=i!M7zwLQ{vsaLKUguIulKcm%H8tf@@!G>69s@g9<6`iqW&o%}0A1!Qar( zJi}zc>&;~Rja=E@q1}}&J7G#Q5^n-R%8a{dzRpGuBis`9%_Gkk*33o1lJ=@yL#39Q z5p4IbHMwqVM0>e8Dfaclu*2*aV?=MgQ%&u$^28h(^4X;ZpVF(l3we?&N-qDEuR{^Q~j+u96Mp{|7x_emY*t+MiSn#U3a z#^UvJP0qQBQPG00;gQM+?wq^)3vd3>G@HQ~DwgdrJHA<@H=eAQK=vDY>lL(nF%7^{p6n%D% z;_A?A=G%3waE{S>rB?cERV1j#ni*%@r#5L3i)UL#Cfr7u&IB-+SYwTC1r~c{Re6IT z9Y9$8xm=OOp3z%D;BbMNhWZKDrC{=zo08)lMs&TF9HWfRY`sUWd`ixy0^8`N&%Bh_Go z9iMuiQWO5!gZ^V(1MsZGF}8tMxTIQrPy%77^ploPWRTniPnuX`BuHWRoRuwTEl=J> zpZSY|61Z}NyZ8aiS2Hm%9gns{KEi>6-?^<*Bix`Zj^Dfg5YUkxeyDR_g4=Opj7>EI zu}c~0XOF!?Bhe)NxgpQ$E_gWGzdcH%e*YS3A{qMS`orCn5jjDSe-G2HxzFZ&RsXOC zl=Rl>AcDJ7MI2l|b{Ockwb!?A4l^zDug|%BXB*P>6eAU%OxStVpZOilWL5|mL27*K zlNTQ`=WTaoiP|w^V1OTkmkJ>pYEf9~@ZHV%eT|xRS5CIiDw}sD18n6@CQUH1jFILV zPyY@QXPrP|7C`UmsCGMl*3MlJMoqgJvNa#`4Nd)pGBof)N~u4X{kc!d<1P)~Rh>%@ zc4p9u&TxBkNaAz?*}P6wY&(d0YD->sMXsRpkkjRTf%Ib(RVr&; z?KKfGM1PKUKy&F8zhl)2ZS?99t0jsGTKYil;g`dAy7rNOPXqo3ee&&S_Q!{3eNO~6 zB5}*&O0^+sn&KNF;-TqAT|}HoP;yeE$kxvtH5ml;q|VLQ8qRYtkpG$~bYv(N6quA$ z8p9Y>t4*|fXT3yK^JbEj`mz0zsOp>Dgg&NUsO?Ys*q4MxX%@F>ISseii}iN z!{?uFyE4f1lJ6EHs!d~sV~qREYgX2NWr*cW84hhA6gJPyIqUsT>B61RX}c{uwkd^E z8_2&Sz~PA#YGU>0ldaPU_={(Tuo`(ASG9aWr%CF|M!u;@H)7%>HK^}%Tz7Q~ky{;y zfaubL9XAdWzn2*$HJE4uALyue=el^uf;hAit@4m4;mE21B=_*rBLVqGN4`aranMJG z0)sh*>Xut0iI4=?m5i5(z$QpLbSFLA(pr5BMfb8pyW;#C=duH3n0LaRA2t1{LbmgS zDAX{q;o+(gkrk2@0nCxX@>hEeH3Qi()L(D0Lrpup?T&hy8@)D3Xuz#<1o<&LzI39Y zqjq^YBZgLW=WMfTjUXttZlpZnnTPpmGoqT=6{C~(n;|Ywzfg+*;L>V5)AnO4~cU zkpOpFjT>w9NOxOAkrdn_Gklhbcs={cZ#cmpQ4Jg7975pHO*`BuaJH(T46W@p*?3n$ z+bw}Vcu5k!M|2$=*%YYK!?oC8=VtF%`SW=(JaO|F#M&8rjS%u2yZ9>|IPD>%_2_@v zUhbW!BFYa}xWDw9{T?UV5yG%_0M<}3(WMJ-FXJa`_VDBaU$dr8U~mY$gdOs!11Aos zUQ;c-KdY1jXT0PbYP3ly0hc#|ltsR(f9v?l)NvhuI8Z)Lj$?gqw3R~N)loX~XykiS zE{?rJC23?9C%7AV_%tj^np+4-S95D$1Vi7YFNjLQKe(&PiHi+E%^B@aU*zX5 z4!CLF(fL&lxk}u}+z8fdHfCe%SpaO_HB|TRH4ax>5hq)V_A($GDsm95(=|v{{A(ds z1F`*OIPyi*s1lEMoL4)XK->+bV+_HL#FO6)BBC$l#w-q zS#9acQq2LK^!zhj(emhlf=mmPl%cG)^EGLzN{QLTb8=gUHrw(lnp?9u(|7BtOD4Ps zvJ;3^CFvew96Bm!jV`Y6t`n3|h9U=(IvU*@p=ZkMx-GPkGX>H4Hf68)D|aq2aRVE!>U3@MTOohY z7ewaFlE~`8ShMk0r{XtUUL<<}!|AjBl7VUcbTYVRuI+}{=_Z6^>a~v#_q?sG^D)rq z*5dOQ9PG-h;^VkybtC!s!2PDKu051}55oQ5u4WosO&HSQ4IEi(zf18XuA>OaMdHoa z$y^vPB~Ji`H3`}P&f8tj+5~~7#q!N5qt@>P*ghz22F_XVqh?BKmG?ijX?EPxP(ee` zdwn7ycOqw#6AHJMl3ft{6SR0vDs0&OVhvXzVK$fH1-{nmL-;bngv!|&$C9R6Z6?^- zbGa0-vzUHHf#BB(i458Ekx#;pM0@CWFSEiDrvzHWA|soU3K1-!D~`MSK})xI#X;ii zgW|ggo&fF)-0Qx}f0;u*YxO)ymt>VLK~ASjw3@fMom_K4WQE*!BB&TN`p;dh1viv3 zY3J$XG#7G2cNWbM;zp{BzedM>4+@cqQE-@DwXVGRV!pYG>}ixOjJeAN*u~RP%YlW% zR5;W0n%Cw4uawOmC2&CA7#T4!5K+Dh;Be?t2+ndQT8?i$s&Uj6BG_h6o~yUAxUPhM zKwx&x^pR`4*bdHlvfDnLPE7EjF0Mg&B?y|MH|VUEd&9jYf*RGDJp`}4}L4dYLb zk*CB7?I7En%yi0Z_cZL`a7?k!jV7ZhQ)6R|EyZOJHLXUL$!|q5k~#0kqjR(E9XT<| zjM9xH!BM`vzB%}( zF;+3NmTS&`yH~&hN+(8=VQl*nn6YXKfcNJ1pKi>J(MnD7n7b>F#6vbllBUb7lk@_b z+{(L~op;xwuPi8jsLHa2@Th8T=(rP{S&q5AJ4oW?vpH_N*seO-Ny_kau`tp9TdTGKBXG<}F9 z*6cSAO-D2yvlG2HlW^UutdNmj>~qD<=GB4rE`|V@Y>Wt!#Lg7|bnNt!>{i`#(V0`d z525=8|4Rf$DodYK(&~PvyKs6=`YqI3^hbJ9(ilVj zD@2_kTa_V;mYnop?FjsZa=tOKKFj?)NwqY8d`bgza(xECA*B(DTeKZsGXM~4u zv@&GS3FEI}r!MQc*QK*Ed+A4WKi8*=%65f9r3)_oC3rxSU?-mX!{O;8%O;)yrr*84 z2ax^8#Leek6wgU-(=`ZIJ)rcmExpC;vv;3f!KB zaQ%P&)?fa^|1-tE?#urrQ|unsLxUyGoH;}O;pjN^x=r*TN5}#br5WYzd(Y0CB|As{ zUo154)joRQ`QQETAIEFGf3FsP&;9;?{R`xjJZJlCE`7c9U!2YVobko8eY0o#&i|L! zaPqC!=akYot+@aD3nynJedD6it^dkO|2VJb)ReHq=#2l?w4^URrKGKm);RlxT6vUo Qo6e{!YbljKc>Lo30LV)`(EtDd literal 0 HcmV?d00001 diff --git a/tech_reports/LLMs/llms.md b/tech_reports/LLMs/llms.md index e78a88af75b4..73ec5d379ca7 100644 --- a/tech_reports/LLMs/llms.md +++ b/tech_reports/LLMs/llms.md @@ -1,6 +1,6 @@ # LLMs in TT-NN -Authors: Mark O'Connor, Djordje Ivanovic, Jack (Xun) Cai, Kartik Paigwar +Authors: Mark O'Connor, Djordje Ivanovic, Jack (Xun) Cai, Kartik Paigwar, Johanna Rock ## Contents - [LLMs in TT-NN](#llms-in-tt-nn) @@ -671,10 +671,206 @@ def forward( - submodules, tests - how to combine prefill and decode, - slicing prefill to fit in L1 + ### 3.3 Multi-Device - - device mesh - - column parallel followed by row parallel - - sharding, CCL ops, reducing CCL overheads, etc. + +Please note that this section refers to sharding schemes across devices and not on a multi-core level. For details about different matmul versions and sharding on a core level, please see the [matmul configuration section](#44-op-configs). + +There are two main approaches for scaling across multiple devices: `data parallel` and `tensor parallel`. + +In data parallel scaling there are _multiple independent_ instances of the model running in parallel so that multiple batches of users are processed at the same time. This mode is used to increase throughput. + +In tensor parallel scaling there is _one_ instance of the model executed on multiple devices, where single operations are distributed across devices. This mode allows larger models, that would not typically fit on a single device, to run on multiple devices, and usually also reduces latency. + +There are also hybrid forms of those two modes where a cluster of devices runs multiple independent instances of the model, but each of those model instances uses multiple chips in a tensor parallel fashion. + +In the report [Programming Mesh of Devices with TT-NN](../Programming_Mesh_of_Devices/Programming_Mesh_of_Devices_with_TT-NN.md), there is a good introduction to using TTNN's key concepts for scaling to multiple devices. It shows how to use a single handle for a mesh of devices, and how a tensor can be sharded or replicated to that mesh of devices (tensor parallelism). +The tensor handle is used analogously to single device tensors, with the only difference being that all operations on that tensor are then executed in parallel on each device and operate on their respective local chunk of data. + +TT-Metal supports different multi-device topologies. The most important ones for us are `Ring` topology, where all devices are connected in a ring shape with each other, and `Line` topology, where a (sub-)group of devices is connected in a line with each other. `Line` topology can be a 1D or 2D grid of devices, where each row and column are connected in a line. + +Below is a summary and example code of the most important concepts for mapping a tensor to a mesh of devices in TTNN: + +*Figure: Example usage of mesh_device, ShardTensorToMesh and ReplicateTensorToMesh* + +```python +import ttnn + +# 2x4 mesh_device, Topology Ring: devices are connected in a ring +mesh_device = ttnn.open_mesh_device(ttnn.MeshShape(2, 4), mesh_type=ttnn.MeshType.Ring) + +# Construct initial torch tensor +torch_tensor = torch.rand((1,1,32,256), dtype=torch.bfloat16) + +# Convert to ttnn.Tensor, tilize and move onto mesh_device (2x4 devices) by sharding in dimension 3 +# mesh_tensor_sharded contains data on all 8 devices, where each device has a 32x32 sized chunk of the data +mesh_tensor_sharded = ttnn.from_torch( + torch_input_tensor, + layout=ttnn.TILE_LAYOUT, + device=mesh_device, + mesh_mapper=ttnn.ShardTensorToMesh(mesh_device, dim=3), +) + +# Convert to ttnn.Tensor, tilize and move onto mesh_device (2x4 devices) by replication +# mesh_tensor_replicated contains data on all 8 devices, where each device has the same 32x256 sized tensor +mesh_tensor_replicated = ttnn.from_torch( + torch_input_tensor, + layout=ttnn.TILE_LAYOUT, + device=mesh_device, + mesh_mapper=ttnn.ReplicateTensorToMesh(mesh_device), +) +``` + +The second key concept to scaling a model to multiple devices are Collective Communication Library (CCL) operations. They are used to efficiently exchange data between multiple devices. TTNN currently supports the following CCL Operations: +- AllGather +- ReduceScatter +- AllReduce + +See the [CCL Developer Guide](../EthernetMultichip/CclDeveloperGuide.md) for more comprehensive coverage about CCL and their implementation details. Our library of supported operations can be found [here](../EthernetMultichip/CclDeveloperGuide.md#op-list-op-list). + +#### AllGather +The AllGather operation collects data from all devices, concatenating each chunk along a specified dimension. The result is stored on each device (replication). + +- Supported Topologies: Ring, Linear +- Supported number of links + - N300, T3000: 1 + - TG: 4 along cluster_axis=0, 3 along cluster_axis=1 +- Arguments + - mesh_tensor: a tensor mapped to a mesh_device via mesh_mapper + - dim: the dimension to concatenate + - num_links: number of ethernet links to be used + - cluster_axis: cluster axis to gather along + - mesh_device: mesh device the tensor is mapped to + +*Figure: Example usage of Ring All-Gather on 2x4 mesh_device* + +```py +# Execute All-Gather on the sharded tensor +# Assuming mesh_tensor_sharded is a sharded tensor over 8 devices where each devices contains a 32x32 sized chunk of data, the output_tensor is of size 32x256 +output_tensor = ttnn.all_gather(mesh_tensor_sharded, dim=3, num_links=1) +``` + +*Figure: Example usage of Linear All-Gather on 2x4 mesh_device* + +```py +# Execute All-Gather on the sharded tensor +# Assuming mesh_tensor_sharded is a sharded tensor over 2x4 devices where each devices contains a 32x32 sized chunk of data, the output_tensor is of size 32x128 where each row has the same data +output_tensor = ttnn.all_gather(mesh_tensor_sharded, dim=3, num_links=2, cluster_axis=1, mesh_device=mesh_device, topology=ttnn.Topology.Linear) +``` + +#### ReduceScatter +The ReduceScatter operation reduces the data across all devices and shards the result of the reduction over a specified dimension across all devices. + +- Supported Topologies: Ring, Linear +- Supported number of links: 1 +- Arguments + - mesh_tensor: a tensor mapped to a mesh_device via mesh_mapper + - dim: the dimension to concatenate + - cluster_axis: cluster axis to gather along + - num_links: number of ethernet links to be used + - topology: topology configuration ttnn.Ring or ttn.Linear + +*Figure: Example usage of Ring Reduce-Scatter on 2x4 mesh_device* + +```py +# Execute Reduce-Scatter on the sharded tensor +# Assuming mesh_tensor_sharded is a sharded tensor over 8 devices where each devices contains a 32x32 sized chunk of data, the output_tensor is again of size 32x32 on each devices but reduced over all devices +output_tensor = ttnn.reduce_scatter(mesh_tensor_sharded, dim=3, num_links=1) +``` + +*Figure: Example usage of Linear Reduce-Scatter on 2x4 mesh_device* + +```py +# Execute Reduce-Scatter on the sharded tensor +# Assuming mesh_tensor_sharded is a sharded tensor over 2x4 devices where each devices contains a 32x32 sized chunk of data, the output_tensor is of size 32x32 on each device but reduces over each row of devices +output_tensor = ttnn.reduce_scatter(mesh_tensor_sharded, dim=3, num_links=1, cluster_axis=1, mesh_device=mesh_device, topology=ttnn.Topology.Linear) +``` + +#### AllReduce +The AllReduce operation reduces data across all devices and stores the entire tensor on each device (replication). It is performed using an AllGather followed by a ReduceScatter. + +A fused version of AllReduce is planned, but currently only the composite of AllGather+ReduceScatter is supported. + +#### Sharding schemes for decode +In decode mode, activations are generally stored in L1 memory, while weights, which are too large, need to be stored in DRAM. The main bottleneck in decode mode is thereby DRAM bandwidth required to load model weights. + +The activations in decode mode are so small because they contain the batch size (=users) in the height dimension while sequence length is 1. +The only exception is the attention operations computing `softmax(Q*KˆT)*V`. The activation width is the model dim (e.g. 8192 for Llama3-70b). +Activations are not sharded in the height dimension; however, depending on the operation and model, they may be sharded in the width dimension. + +Matmul weights on the other hand can be sharded in width, height or both. Sharding weights across multiple devices significantly reduces DRAM pressure per device, resulting in notable latency improvements. Below is a summary of useful sharding schemes for sharding weights in decode mode. Which scheme to use will depend on the shape and size of the model weights and the target device topology. + +##### **1D Column parallel** + +Weights are sharded in width, such that each device contains a horizontal slice of the weights. For this scheme the activations need to be gathered beforehead, i.e. each device processes the whole activation. The result of a column parallel matmul is an activation that is sharded in width. An AllGather operation is used on dim=3 to gather (i.e., replicate) activations. + + + +##### **1D Row parallel** + +Weights are sharded in height, such that each device contains a vertical slice of the weights. For this scheme the activations need to be sharded beforehand, i.e. each device processes a width-shard of the activation. The result of a row parallel matmul are activation partials with the final result's output dimensions, each device containing a partial result. To reduce the activations, i.e. compute the final output, a ReduceScatter operation is used to compute the reduced result across all devices and shard the result along a specified dimension. +Additionally an AllGather operation is used (ReduceScatter+AllGather = AllReduce) to gather the reduced shards and thus replicate the final output on each device. + + + +##### **1D Column parallel followed by row parallel (1D weight sharding) ** + +1D Weight Sharding is a sharding scheme that combines column and row parallel matmuls and can reduce the data volume sent over CCL operation and thus speed up computation. It consists of a column parallel matmul followed by a row parallel matmul. In this scheme the initial activations are gathered, and the column parallel matmul produces width-sharded outputs. The row parallel matmul consumes those sharded activations and produces parial outputs. We need an AllReduce (ReduceScatter+AllGather) operation to compute the final reduced and gathered outputs. + +Optimization potential in this scheme depends highly on the input dimensions to the CCL operations. We can use this scheme for the MLP and any sequence of matmuls that expands and then narrows the output dimension again, becuase it moves the CCL operation to a more beneficial location in the computational graph and thus reduces the CCL data volume. + +Let's look at the MLP as concrete example: in Llama3-70b we have `FF1` and `FF3` with dimensions `[32, 8k] x [8k, 28k]` and then the `FF2` with dimension `[32, 28k] x [28k, 8k]`. +If we gather after `FF1` and `FF3` we have to gather activations of size `[32, 28k/num_devices] -> [32, 28k]` for each of `FF1` and `FF3`; after the `FF2` we'd need to gather again `[32, 8k/num_devices] -> [32, 8k]`. +If instead, we use the 1D weight sharding scheme and thus move the CCL operation after the `FF2`, we only have to ReduceScatter #num_devices partials of size `[32, 8k] -> [32, 8k/num_devices]` and then optionally AllGather to obtain the `[32, 8k]` gathered outputs. + + + +##### **2D Weight Sharding** + +In 2D Weight Sharding on a 2D cluster, weights are sharded both in width and height, such that each device contains a block of the weights. +For this scheme the activations are width-sharded along `cluster_axis=0` and are replicated along `cluster_axis=1`, and the weights are block-sharded. Thus, each device processes a width-shard of the activation, and a block of the weights where the activations are replicated over one axis but the weights are not. +The matmul result will be width-sharded along `cluster_axis=0` and contain partial results along `cluster_axis=1`. +Typically an AllReduce (ReduceScatter+AllGather) is used to first reduce along `cluster_axis=1` and then gather the shards along `cluster_axis=0`. + + + +##### **Optimal strategy** + +The optimal usage strategy of different parallelisation schemes depends on the specific shapes and model architecture, as well as the target device topology. To select the best parallelisation strategy, the overall data movement for each scheme can be computed; selecting the parallelisation stratgy with the lowest overall data movement will generally result in the best performance. + +To compute the data movement for a given parallelisation strategy, first the required sequence of parallelisation strategies and corresponding CCL operations is sketched out, and then the resulting dat movement is computed. The following table shows constraints on input and output activations for each parallelisation strategy. A partial activation always has to be reduced (ReduceScatter or AllReduce), while fractured activations may or may not need to be gathered, dependent on the consumer operation. A binary op for example is executed on the fractured activaiton to parallelise computation, while a matmul 1D column parallel operation requires inputs to be gathered in k. + +| Parallelisation strategy | Input activation requirement | Output activation requirement | +|---------------------------|-----------------|-----------------| +| 1D Column parallel | Gathered in k | Fractured in k | +| 1D row parallel | Fractured in k | Partials of full size | +| 1D column + row parallel | Gathered in k | Partials of full size | +| 2D parallel | Fractured in k | Partials over one cluster axis | + +The overall data movement (DM) is then computed using: + +| CCL operation | DM for Line topology | DM for Ring topology | +|-------------------|--------------------------|---------------------------| +| AllGather | DM = (K⋅N⋅DF/D)⋅(D−1)⋅D | DM = (K⋅N⋅DF)⋅D⋅log2(D) | +| ReduceScatter | DM = (K⋅N⋅DF)⋅(1-(1/D)) | DM = (K⋅N⋅DF) ⋅ (D-1) / D | + +where K and N are height and width of the weight tensor, DF is the data format multiplyer (number of bytes per datum) and D is the number of devices along the axis that the CCL operation is performed on. Ring topology is more optimised and results in less overall data movement. + + + +##### **Examplary parallelisation scheme: Llama3** + +For our [Llama3 family of models](../../models/demos/llama3) we are using the following sharding schemes in our multi-device architectures: + +| Matmul | N300 | T3000 | TG | +|-------------------|-----------------|-----------------|-----------------| +| [_QKV projection_](../../models/demos/llama3/tt/llama_attention.py) | Column parallel | Column parallel | 2D | +| [_Dense out_](../../models/demos/llama3/tt/llama_attention.py) | Row parallel | Row parallel | 2D | +| [_FF1_](../../models/demos/llama3/tt/llama_mlp.py) | Column parallel | Column parallel | 2D | +| [_FF3_](../../models/demos/llama3/tt/llama_mlp.py) | Column parallel | Column parallel | 2D | +| [_FF2_](../../models/demos/llama3/tt/llama_mlp.py) | Row parallel | Row parallel | 2D | + + ### 3.4 Continuous Batching - quick intro and how it is implemented in demos. ### 3.5 vLLM Integration diff --git a/tech_reports/Programming Mesh of Devices/Programming Mesh of Devices with TT-NN.md b/tech_reports/Programming_Mesh_of_Devices/Programming_Mesh_of_Devices_with_TT-NN.md similarity index 100% rename from tech_reports/Programming Mesh of Devices/Programming Mesh of Devices with TT-NN.md rename to tech_reports/Programming_Mesh_of_Devices/Programming_Mesh_of_Devices_with_TT-NN.md diff --git a/tech_reports/Programming Mesh of Devices/images/image1.png b/tech_reports/Programming_Mesh_of_Devices/images/image1.png similarity index 100% rename from tech_reports/Programming Mesh of Devices/images/image1.png rename to tech_reports/Programming_Mesh_of_Devices/images/image1.png diff --git a/tech_reports/Programming Mesh of Devices/images/image2.png b/tech_reports/Programming_Mesh_of_Devices/images/image2.png similarity index 100% rename from tech_reports/Programming Mesh of Devices/images/image2.png rename to tech_reports/Programming_Mesh_of_Devices/images/image2.png diff --git a/tech_reports/Programming Mesh of Devices/images/image3.png b/tech_reports/Programming_Mesh_of_Devices/images/image3.png similarity index 100% rename from tech_reports/Programming Mesh of Devices/images/image3.png rename to tech_reports/Programming_Mesh_of_Devices/images/image3.png diff --git a/tech_reports/Programming Mesh of Devices/images/image4_ring_all_gather.png b/tech_reports/Programming_Mesh_of_Devices/images/image4_ring_all_gather.png similarity index 100% rename from tech_reports/Programming Mesh of Devices/images/image4_ring_all_gather.png rename to tech_reports/Programming_Mesh_of_Devices/images/image4_ring_all_gather.png diff --git a/tech_reports/Programming Mesh of Devices/images/image5_line_all_gather.png b/tech_reports/Programming_Mesh_of_Devices/images/image5_line_all_gather.png similarity index 100% rename from tech_reports/Programming Mesh of Devices/images/image5_line_all_gather.png rename to tech_reports/Programming_Mesh_of_Devices/images/image5_line_all_gather.png diff --git a/tech_reports/Programming Mesh of Devices/images/llama-3.1-70b-hybrid-dp-tp.png b/tech_reports/Programming_Mesh_of_Devices/images/llama-3.1-70b-hybrid-dp-tp.png similarity index 100% rename from tech_reports/Programming Mesh of Devices/images/llama-3.1-70b-hybrid-dp-tp.png rename to tech_reports/Programming_Mesh_of_Devices/images/llama-3.1-70b-hybrid-dp-tp.png From 88b981d4c7315019d8e73dd865e4c99a5855023d Mon Sep 17 00:00:00 2001 From: Stuti Raizada <159130512+sraizada-tt@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:53:05 +0000 Subject: [PATCH 64/87] Add prefill v decode section to LLM tech report [section 3.2] (#15096) ### What's changed Add prefill v decode section to LLM tech report --------- --- tech_reports/LLMs/llms.md | 126 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 4 deletions(-) diff --git a/tech_reports/LLMs/llms.md b/tech_reports/LLMs/llms.md index 73ec5d379ca7..5caf68bad64d 100644 --- a/tech_reports/LLMs/llms.md +++ b/tech_reports/LLMs/llms.md @@ -1,6 +1,6 @@ # LLMs in TT-NN -Authors: Mark O'Connor, Djordje Ivanovic, Jack (Xun) Cai, Kartik Paigwar, Johanna Rock +Authors: Mark O'Connor, Djordje Ivanovic, Jack (Xun) Cai, Kartik Paigwar, Johanna Rock, Stuti Raizada ## Contents - [LLMs in TT-NN](#llms-in-tt-nn) @@ -667,10 +667,114 @@ def forward( ## 3. Features ### 3.1 Generative Decoding + ### 3.2 Prefill and Decode - - submodules, tests - - how to combine prefill and decode, - - slicing prefill to fit in L1 + +Large language models require two distinct phases for inference due to the fundamental nature of transformer attention and autoregressive generation: prefill and decode. + +In our LLM implementations, the prefill phase is done sequentially for each user, but parallel for the prompt tokens of each user. During prefill, the model computes attention scores for all prompt tokens against each other and populates the key-value (KV) cache which will speed up the computation of the decode phase. At the end of the prefill phase, the first token for the following autoregressive generation will also be computed. + +The decode phase is parallel-computed for all users, but sequential for each token within a batch of users. Each new token can only be generated after the previous one, as the model must maintain causality in attention computations. + +#### **Technical Implementation Differences** + +The intermediate activations in prefill mode are kept in DRAM, due to the large size of the tensors which contain the entire sequence length. In decode mode, the intermediate activations are kept in L1 memory instead, since in this mode the sequence length to compute is just 1 (one token at the time), reducing latency. + +##### 1. Reshaping for Large Matrix Multiplications + +Please see the [attention source code](../../models/demos/llama3/tt/llama_attention.py) for reference. + +In prefill mode, when the input sequence length is very large, the model reshapes its input tensors to process sequences in smaller chunks in parallel for larger matrix multiplications, such as `wqkv`, `wo` in the attention module, and `w1`, `w2`, `w3` in the MLP module. This reshaping prevents running out of memory in cases of long prefill sequence lengths. For instance: + +```python +if seq_len > 2048: + x_11SH = ttnn.reshape(x_11SH, [1, seq_len // 2048, 2048, -1]) + +xqkv_fused = ttnn.linear( + x_11SH, + self.wqkv, + dtype=ttnn.bfloat16, + memory_config=ttnn.DRAM_MEMORY_CONFIG, + compute_kernel_config=self.compute_kernel_config_hifi2, + program_config=self.model_config["XQKV_PREFILL_PROGCFG"](seq_len), +) +``` + +This reshaping is not needed for decode mode because it only processes one token at a time. Instead, the parallelization for decode mode is done over user batches, which currently only goes up to 32. + +##### 2. KV Cache Management + +The KV-cache is filled during prefill using the `ttnn.experimental.paged_fill_cache` operation. This supports page tables, which enables the hot-swapping of new users when the full model is deployed. + +```python +# Fill cache with initial states +ttnn.experimental.paged_fill_cache( + keys_BKSD, + k_fill, + page_table, + batch_idx=user_id +) +``` + +Similarly, during decode, the KV-cache update is done by `ttnn.experimental.paged_update_cache`, which updates the new KV values for all the users currently processing, with their respective positions. + +```python +# Update KV cache with a single new token +ttnn.experimental.paged_update_cache( + keys, + k_heads_1BKD, + update_idxs_tensor=current_pos, + page_table=page_table +) +``` + +##### 3. Attention Computation +###### Prefill: +```python +# Split q_heads into num_groups and kv_heads for parallel group computation for grouped query attention (GQA) +q_heads_84SD_8b = ttnn.reshape( + q_heads_1QSD_8b, + [self.n_local_kv_heads, self.n_local_heads // self.n_local_kv_heads, -1, self.head_dim] +) + +# Prefill implements causal masking across the full sequence +attn_output_84SD = ttnn.transformer.scaled_dot_product_attention( + q_heads_84SD_8b, + k_heads_K1SD_8b, + v_heads_V1SD_8b, + is_causal=True, # Ensures tokens only attend to previous tokens + scale=self.scale +) +``` + +###### Decode: +```python +# Decode uses cached states instead of recomputing +attn_output_11BH = ttnn.transformer.scaled_dot_product_attention_decode( + q_heads_1BQD, # Only new token query + keys, # Cached keys + values, # Cached values + cur_pos_tensor=current_pos # Track position for causal attention +) +``` + +##### 4. Slicing Before the LM Head +At the end of prefill, the model should generate the first decoded token, then signaling the start of the decode phase. To this end, the model slices the output of the last decoder layer to the last tile before computing the LM head. This is necessary because only last token from prefill is needed to start the autoregressive decoding. + +```python +x = ttnn.slice(x, (0, 0, get_last_token, 0), (1, 1, get_last_token + 32, x.shape[-1])) +``` + +#### **Prefill vs. Decode: Comparison Summary** + +| | Prefill Mode | Decode Mode | +| --- | --- | --- | +| Purpose | Bulk sequence processing for initialization or training | Incremental processing for autoregressive inference | +| Demo Parallelization | Sequential for each user, parallel for the sequence length of each user | Parallel for 32 users, sequential for each token within a batch of users | +| Batch and sequence Length | Processes long sequences (≥ 128 tokens), single user | Processes batch of users (≤ 32 users), single token | +| Memory Use | DRAM, with reshaping into smaller chunks for long sequence lengths | L1 on-chip memory for fast, low-latency processing | +| Attention | Handles sequences in bulk; more memory-intensive | Incremental attention with precomputed components | +| LM head slicing | Slices to last tile before Lm head matmul to extract the last token | Slicing not required | ### 3.3 Multi-Device @@ -1118,6 +1222,20 @@ self.compute_kernel_config_hifi2 = ttnn.WormholeComputeKernelConfig( As always, do not recreate these every single forward pass if you want your python thread to be fast (which you do). ### 4.8 Module Tests + +#### Llama3 Module and Test Differences + +In our current Llama3 model, the attention module class (`TtLlamaAttention`) implements two primary methods for attention computation: `forward_prefill` and `forward_decode`. +To test these, we provide two separate attention test files, `test_attention_decode` and `test_attention_prefill`, which create the appropriate input tensors: +- A tensor of size `(batch, dim)` in L1 for decode, +- A tensor of size `(seqlen, dim)` in DRAM for prefill. + +Each attention test compares the attention module output and KV-cache correlation between the PyTorch host implementation and the TTNN device implementation. + +The current version of the MLP module class (`TtLlamaMLP`) handles prefill and decode in the same file but has some technical differences (mentioned in the section below). + +The decoder module (which encapsulates both attention and MLP) and model module (which encapsulates the decoder and the remaining parts of the Llama3 model) also handle prefill and decode in the same file, but they call the respective modes within the attention and MLP modules. + ### 4.9 Performance Testing ### 4.10 Common Pitfalls #### 4.10.1 Error Messages From 91e3f221509ac539247a5cd1b9f379bbe7624cc4 Mon Sep 17 00:00:00 2001 From: Austin Ho Date: Fri, 13 Dec 2024 21:38:18 +0000 Subject: [PATCH 65/87] #15916: Update eltwise binary to support sharding on arbitrary cores on an arbitrary sub-device grid --- .../unit_testing/misc/test_eltwise_binary.py | 117 ++++++- ...est_scaled_dot_product_attention_decode.py | 2 +- .../unit_tests/operations/eltwise/test_add.py | 70 ++++ .../ttnn/unit_tests/operations/test_matmul.py | 1 + tt_metal/common/core_coord.cpp | 24 ++ tt_metal/common/core_coord.hpp | 4 + tt_metal/common/work_split.cpp | 180 +++++++--- tt_metal/common/work_split.hpp | 3 + .../binary/device/binary_device_operation.cpp | 45 ++- .../binary/device/binary_device_operation.hpp | 5 +- ...lement_wise_multi_core_program_factory.cpp | 283 +-------------- ...ement_wise_multi_core_sfpu_pgm_factory.cpp | 287 +-------------- ...wise_multi_core_program_factory_common.hpp | 330 ++++++++++++++++++ .../reader_bcast_h_sharded_optimised.cpp | 6 +- 14 files changed, 737 insertions(+), 620 deletions(-) create mode 100644 ttnn/cpp/ttnn/operations/eltwise/binary/device/eltwise_multi_core_program_factory_common.hpp diff --git a/tests/tt_eager/python_api_testing/unit_testing/misc/test_eltwise_binary.py b/tests/tt_eager/python_api_testing/unit_testing/misc/test_eltwise_binary.py index 7f636222bdf4..8ce5d8107c0a 100644 --- a/tests/tt_eager/python_api_testing/unit_testing/misc/test_eltwise_binary.py +++ b/tests/tt_eager/python_api_testing/unit_testing/misc/test_eltwise_binary.py @@ -7,9 +7,9 @@ from loguru import logger import ttnn -from models.utility_functions import untilize, comp_pcc -from models.utility_functions import is_grayskull -from models.utility_functions import torch2tt_tensor, tt2torch_tensor, pad_by_zero +from models.utility_functions import comp_pcc +from models.utility_functions import is_grayskull, run_for_wormhole_b0 +from models.utility_functions import torch2tt_tensor, tt2torch_tensor @pytest.mark.skipif(is_grayskull(), reason="GS does not support fp32") @@ -69,3 +69,114 @@ def test_run_elt_binary(dtype, test_func_name, torch_func_name, pre_in0_silu, de passing, output = comp_pcc(out, torch_func_name(in0, in1), 0.9999) logger.info(output) assert passing + + +@run_for_wormhole_b0() +@pytest.mark.parametrize("device_params", [{"dispatch_core_axis": ttnn.DispatchCoreAxis.COL}], indirect=True) +def test_run_elt_binary_add_with_sub_devices(device): + unharvested_grid_size = (7, 10) + compute_grid_size = device.compute_with_storage_grid_size() + if unharvested_grid_size[0] > compute_grid_size.x or unharvested_grid_size[1] > compute_grid_size.y: + pytest.skip(f"Need {unharvested_grid_size} grid size to run this test but core grid is {compute_grid_size}") + + shape = (1, 1, 32, 2048) + torch.manual_seed(10) + + start_core = ttnn.CoreCoord(1, 0) + core_grid = ttnn.CoreRangeSet( + [ + ttnn.CoreRange(ttnn.CoreCoord(1, 0), ttnn.CoreCoord(3, 9)), + ttnn.CoreRange(ttnn.CoreCoord(5, 0), ttnn.CoreCoord(6, 9)), + ] + ) + shard_grid = ttnn.num_cores_to_corerangeset_in_subcoregrids(start_core, 32, core_grid, row_wise=True) + shard_spec = ttnn.ShardSpec(shard_grid, (32, 64), ttnn.ShardOrientation.ROW_MAJOR, False) + output_mem_config = ttnn.MemoryConfig( + ttnn.TensorMemoryLayout.WIDTH_SHARDED, + ttnn.BufferType.L1, + shard_spec, + ) + in0 = torch.randn(shape).bfloat16().float() + in1 = torch.randn(shape).bfloat16().float() + in0_t = ttnn.from_torch( + in0, + device=device, + dtype=ttnn.bfloat16, + layout=ttnn.TILE_LAYOUT, + memory_config=ttnn.L1_MEMORY_CONFIG, + ) + in1_t = ttnn.from_torch( + in0, + device=device, + dtype=ttnn.bfloat8_b, + layout=ttnn.TILE_LAYOUT, + memory_config=output_mem_config, + ) + in1 = ttnn.to_torch(in1_t) + + out_t = ttnn.add(in0_t, in1_t, dtype=ttnn.bfloat16, memory_config=output_mem_config) + + out = ttnn.to_torch(out_t) + + passing, output = comp_pcc(out, torch.add(in0, in1), 0.9999) + logger.info(output) + assert passing + + +@run_for_wormhole_b0() +@pytest.mark.parametrize("device_params", [{"dispatch_core_axis": ttnn.DispatchCoreAxis.COL}], indirect=True) +def test_run_elt_binary_mul_with_sub_devices(device): + unharvested_grid_size = (7, 10) + compute_grid_size = device.compute_with_storage_grid_size() + if unharvested_grid_size[0] > compute_grid_size.x or unharvested_grid_size[1] > compute_grid_size.y: + pytest.skip(f"Need {unharvested_grid_size} grid size to run this test but core grid is {compute_grid_size}") + + shape = (1, 1, 32, 896) + torch.manual_seed(10) + + start_core = ttnn.CoreCoord(1, 0) + core_grid = ttnn.CoreRangeSet( + [ + ttnn.CoreRange(ttnn.CoreCoord(1, 0), ttnn.CoreCoord(3, 9)), + ttnn.CoreRange(ttnn.CoreCoord(5, 0), ttnn.CoreCoord(6, 9)), + ] + ) + shard_grid = ttnn.num_cores_to_corerangeset_in_subcoregrids(start_core, 28, core_grid, row_wise=True) + shard_spec = ttnn.ShardSpec(shard_grid, (32, 32), ttnn.ShardOrientation.ROW_MAJOR, False) + output_mem_config = ttnn.MemoryConfig( + ttnn.TensorMemoryLayout.WIDTH_SHARDED, + ttnn.BufferType.L1, + shard_spec, + ) + in0 = torch.randn(shape).bfloat16().float() + in1 = torch.randn(shape).bfloat16().float() + in0_t = ttnn.from_torch( + in0, + device=device, + dtype=ttnn.bfloat8_b, + layout=ttnn.TILE_LAYOUT, + memory_config=output_mem_config, + ) + in0 = ttnn.to_torch(in0_t) + in1_t = ttnn.from_torch( + in0, + device=device, + dtype=ttnn.bfloat8_b, + layout=ttnn.TILE_LAYOUT, + memory_config=output_mem_config, + ) + in1 = ttnn.to_torch(in1_t) + + out_t = ttnn.mul( + in0_t, + in1_t, + input_tensor_a_activation=ttnn.UnaryOpType.SILU, + dtype=ttnn.bfloat8_b, + memory_config=output_mem_config, + ) + + out = ttnn.to_torch(out_t) + torch_silu = torch.nn.SiLU() + passing, output = comp_pcc(out, torch.mul(torch_silu(in0), in1), 0.999) + logger.info(output) + assert passing diff --git a/tests/tt_eager/python_api_testing/unit_testing/misc/test_scaled_dot_product_attention_decode.py b/tests/tt_eager/python_api_testing/unit_testing/misc/test_scaled_dot_product_attention_decode.py index a0293b072d68..0ecd59ee433b 100644 --- a/tests/tt_eager/python_api_testing/unit_testing/misc/test_scaled_dot_product_attention_decode.py +++ b/tests/tt_eager/python_api_testing/unit_testing/misc/test_scaled_dot_product_attention_decode.py @@ -330,7 +330,7 @@ def run_test_sdpa_decode_single_iter( pytest.skip(f"Need {grid_size} grid size to run this test but core grid is {compute_grid_size}") else: unharvested_grid_size = (7, 10) - if compute_grid_size.x > unharvested_grid_size[0] or compute_grid_size.y > unharvested_grid_size[1]: + if unharvested_grid_size[0] > compute_grid_size.x or unharvested_grid_size[1] > compute_grid_size.y: pytest.skip(f"Need {unharvested_grid_size} grid size to run this test but core grid is {compute_grid_size}") if grid_size[0] * grid_size[1] > sub_core_grids.num_cores(): pytest.skip( diff --git a/tests/ttnn/unit_tests/operations/eltwise/test_add.py b/tests/ttnn/unit_tests/operations/eltwise/test_add.py index 9344e59ccf0a..f29d5b4783a7 100644 --- a/tests/ttnn/unit_tests/operations/eltwise/test_add.py +++ b/tests/ttnn/unit_tests/operations/eltwise/test_add.py @@ -515,3 +515,73 @@ def test_01_volume_tensors(device, data, memory_config): c = ttnn.to_torch(ttnn_c).reshape((-1)) assert c.tolist() == c_golden + + +@pytest.mark.parametrize("input_a_sharded", [True, False]) +@pytest.mark.parametrize("input_b_sharded", [True, False]) +@pytest.mark.parametrize("out_sharded", [True, False]) +@pytest.mark.parametrize("shard_orientation", [ttnn.ShardOrientation.ROW_MAJOR, ttnn.ShardOrientation.COL_MAJOR]) +def test_add_with_sub_devices(device, input_a_sharded, input_b_sharded, out_sharded, shard_orientation): + torch.manual_seed(0) + shape = (1, 1, 1024, 1024) + torch_input_tensor_a = torch.rand(shape, dtype=torch.bfloat16) + torch_input_tensor_b = torch.rand(shape, dtype=torch.bfloat16) + + if shard_orientation == ttnn.ShardOrientation.ROW_MAJOR: + shard_shape = (1024 // 8, 1024) + else: + shard_shape = (1024, 1024 // 8) + + core_range_set = ttnn.CoreRangeSet( + [ + ttnn.CoreRange(ttnn.CoreCoord(2, 2), ttnn.CoreCoord(3, 3)), + ttnn.CoreRange(ttnn.CoreCoord(1, 1), ttnn.CoreCoord(1, 1)), + ttnn.CoreRange(ttnn.CoreCoord(4, 0), ttnn.CoreCoord(4, 2)), + ] + ) + + height_sharded_mem_config = ttnn.create_sharded_memory_config( + shape=shard_shape, + core_grid=core_range_set, + strategy=ttnn.ShardStrategy.HEIGHT, + orientation=shard_orientation, + use_height_and_width_as_shard_shape=True, + ) + + torch_output_tensor = torch_input_tensor_a + torch_input_tensor_b + + input_tensor_a = ttnn.from_torch( + torch_input_tensor_a, layout=ttnn.TILE_LAYOUT, device=device, memory_config=ttnn.DRAM_MEMORY_CONFIG + ) + + if input_a_sharded: + input_tensor_a = ttnn.to_memory_config(input_tensor_a, height_sharded_mem_config) + + input_tensor_b = ttnn.from_torch( + torch_input_tensor_b, layout=ttnn.TILE_LAYOUT, device=device, memory_config=ttnn.DRAM_MEMORY_CONFIG + ) + + if input_b_sharded: + input_tensor_b = ttnn.to_memory_config(input_tensor_b, height_sharded_mem_config) + + if out_sharded: + out_mem_config = height_sharded_mem_config + else: + out_mem_config = ttnn.DRAM_MEMORY_CONFIG + + sub_device = ttnn.SubDevice( + [ + ttnn.CoreRangeSet( + [ + ttnn.CoreRange(ttnn.CoreCoord(1, 1), ttnn.CoreCoord(4, 4)), + ttnn.CoreRange(ttnn.CoreCoord(4, 0), ttnn.CoreCoord(5, 0)), + ] + ) + ] + ) + sub_device_manager_id = device.create_sub_device_manager([sub_device], 0) + device.load_sub_device_manager(sub_device_manager_id) + output_tensor = ttnn.add(input_tensor_a, input_tensor_b, memory_config=out_mem_config) + output_tensor = ttnn.to_torch(output_tensor) + assert ttnn.pearson_correlation_coefficient(torch_output_tensor, output_tensor) >= 0.99988 + assert output_tensor.shape == shape diff --git a/tests/ttnn/unit_tests/operations/test_matmul.py b/tests/ttnn/unit_tests/operations/test_matmul.py index ae04c188263e..3fcb58c528c2 100644 --- a/tests/ttnn/unit_tests/operations/test_matmul.py +++ b/tests/ttnn/unit_tests/operations/test_matmul.py @@ -93,6 +93,7 @@ def test_pytorch_2_0_failed_cases(device, m, k, n): assert_with_pcc(z_t, z) +@run_for_wormhole_b0() @pytest.mark.parametrize("device_params", [{"dispatch_core_axis": ttnn.DispatchCoreAxis.COL}], indirect=True) @pytest.mark.parametrize("m", [256]) @pytest.mark.parametrize("k", [256]) diff --git a/tt_metal/common/core_coord.cpp b/tt_metal/common/core_coord.cpp index 4e9a6123477f..c8281b40ac50 100644 --- a/tt_metal/common/core_coord.cpp +++ b/tt_metal/common/core_coord.cpp @@ -522,6 +522,30 @@ std::vector grid_to_cores_with_noop( return cores; } +// Noop cores are appended at the end with no guarantees on ordering +std::vector grid_to_cores_with_noop( + const CoreRangeSet& used_cores, const CoreRangeSet& all_cores, const bool row_wise) { + ZoneScoped; + TT_ASSERT(all_cores.contains(used_cores)); + // Most likely a lot of optimizations to do here + // Implemented this way for simplicity for now + std::vector cores; + cores.reserve(all_cores.num_cores()); + cores = corerange_to_cores(used_cores, std::nullopt, row_wise); + std::vector all_cores_vec = corerange_to_cores(all_cores, std::nullopt, row_wise); + auto sorted_used_cores = cores; + std::sort(sorted_used_cores.begin(), sorted_used_cores.end()); + std::sort(all_cores_vec.begin(), all_cores_vec.end()); + std::set_difference( + all_cores_vec.begin(), + all_cores_vec.end(), + sorted_used_cores.begin(), + sorted_used_cores.end(), + std::back_inserter(cores)); + + return cores; +} + std::vector corerange_to_cores(const CoreRangeSet& crs, std::optional max_cores, bool row_wise) { std::vector all_cores; auto num_cores = crs.num_cores(); diff --git a/tt_metal/common/core_coord.hpp b/tt_metal/common/core_coord.hpp index 93d55ac39f43..38dcdc225cb3 100644 --- a/tt_metal/common/core_coord.hpp +++ b/tt_metal/common/core_coord.hpp @@ -191,6 +191,10 @@ std::vector grid_to_cores_with_noop( const uint32_t grid_size_y, const bool row_wise = false); +// Noop cores are appended at the end with no guarantees on ordering +std::vector grid_to_cores_with_noop( + const CoreRangeSet& used_cores, const CoreRangeSet& all_cores, const bool row_wise = false); + std::vector corerange_to_cores( const CoreRangeSet& crs, std::optional max_cores = std::nullopt, bool row_wise = false); diff --git a/tt_metal/common/work_split.cpp b/tt_metal/common/work_split.cpp index ba687d9d3dab..ad04e169232b 100644 --- a/tt_metal/common/work_split.cpp +++ b/tt_metal/common/work_split.cpp @@ -268,66 +268,146 @@ CoreRangeSet num_cores_to_corerangeset_in_subcoregrids( std::tuple split_work_to_cores( const CoreCoord grid_size, const uint32_t units_to_divide, const bool row_wise) { ZoneScoped; - uint32_t num_cores_x = grid_size.x, num_cores_y = grid_size.y; - auto target_num_cores = std::min(units_to_divide, num_cores_x * num_cores_y); - CoreRangeSet all_cores = num_cores_to_corerangeset(target_num_cores, grid_size, row_wise); + if (units_to_divide == 0) { + return std::make_tuple(0, CoreRangeSet(), CoreRangeSet(), CoreRangeSet(), 0, 0); + } + uint32_t num_cores_x = grid_size.x, num_cores_y = grid_size.y, max_num_cores = num_cores_x * num_cores_y, + target_num_cores; + CoreRangeSet all_cores; + if (units_to_divide >= max_num_cores) { + target_num_cores = max_num_cores; + all_cores = CoreRangeSet(CoreRange({0, 0}, {num_cores_x - 1, num_cores_y - 1})); + } else { + target_num_cores = units_to_divide; + all_cores = num_cores_to_corerangeset(target_num_cores, grid_size, row_wise); + } CoreRangeSet core_group_1; CoreRangeSet core_group_2; - uint32_t units_per_core_group_1 = target_num_cores == 0 ? 0 : units_to_divide / target_num_cores; + uint32_t units_per_core_group_1 = units_to_divide / target_num_cores; uint32_t units_per_core_group_2 = 0; + uint32_t num_cores_with_more_work = units_to_divide % target_num_cores; // Evenly divided units to all target cores - if (target_num_cores == 0 || units_to_divide % target_num_cores == 0) { + if (units_to_divide % target_num_cores == 0) { core_group_1 = all_cores; - // Uneven division of units across cores - // This case should only be hit when there are more units of work than a full grid of cores - // which is implicitly assumed in the following logic - } else { + } + // Uneven division of units across cores + // This case should only be hit when there are more units of work than a full grid of cores + // which is implicitly assumed in the following logic + else { // Group of cores that do more work - core_group_1 = num_cores_to_corerangeset(units_to_divide % target_num_cores, grid_size, row_wise); - const auto& last_block_group_1 = (*core_group_1.ranges().rbegin()); - const auto& last_block_all_cores = (*all_cores.ranges().rbegin()); + uint32_t num_core_group_1_cores = num_cores_with_more_work; + uint32_t num_core_group_2_cores = target_num_cores - num_core_group_1_cores; + core_group_1 = num_cores_to_corerangeset(num_core_group_1_cores, grid_size, row_wise); + const auto& last_core_group_1 = (*core_group_1.ranges().rbegin()).end_coord; if (row_wise) { - // Case where only the last row is divided between core group 1 and 2 - if (last_block_group_1.end_coord.y == last_block_all_cores.end_coord.y && - last_block_group_1.end_coord.x != last_block_all_cores.end_coord.x) { - CoreRange leftover_block( - CoreCoord(last_block_group_1.end_coord.x + 1, last_block_group_1.end_coord.y), - last_block_all_cores.end_coord); - core_group_2 = CoreRangeSet(leftover_block); - } else { - std::vector core_group_2_set; - // Case where a middle row is divided between core group 1 and 2 - if (last_block_group_1.end_coord.x != num_cores_x - 1) { - core_group_2_set.emplace_back( - CoreCoord(last_block_group_1.end_coord.x + 1, last_block_group_1.end_coord.y), - CoreCoord(num_cores_x - 1, last_block_group_1.end_coord.y)); - } - // Remaining rows of cores that does less work - core_group_2_set.emplace_back( - CoreCoord(0, last_block_group_1.end_coord.y + 1), last_block_all_cores.end_coord); - core_group_2 = CoreRangeSet(std::move(core_group_2_set)); + // Start in the same row + if (last_core_group_1.x != num_cores_x - 1) { + core_group_2 = num_cores_to_corerangeset( + {last_core_group_1.x + 1, last_core_group_1.y}, num_core_group_2_cores, grid_size, row_wise); + } + // Start in the next row + else { + core_group_2 = num_cores_to_corerangeset( + {0, last_core_group_1.y + 1}, num_core_group_2_cores, grid_size, row_wise); } } else { - // Case where only the last column is divided between core group 1 and 2 - if (last_block_group_1.end_coord.x == last_block_all_cores.end_coord.x && - last_block_group_1.end_coord.y != last_block_all_cores.end_coord.y) { - CoreRange leftover_block( - CoreCoord(last_block_group_1.end_coord.x, last_block_group_1.end_coord.y + 1), - last_block_all_cores.end_coord); - core_group_2 = CoreRangeSet(leftover_block); - } else { - std::vector core_group_2_set; - // Case where a middle column is divided between core group 1 and 2 - if (last_block_group_1.end_coord.y != num_cores_y - 1) { - core_group_2_set.emplace_back( - CoreCoord(last_block_group_1.end_coord.x, last_block_group_1.end_coord.y + 1), - CoreCoord(last_block_group_1.end_coord.x, num_cores_y - 1)); - } - // Remaining columns of cores that does less work - core_group_2_set.emplace_back( - CoreCoord(last_block_group_1.end_coord.x + 1, 0), last_block_all_cores.end_coord); - core_group_2 = CoreRangeSet(std::move(core_group_2_set)); + // Start in the same column + if (last_core_group_1.y != num_cores_y - 1) { + core_group_2 = num_cores_to_corerangeset( + {last_core_group_1.x, last_core_group_1.y + 1}, num_core_group_2_cores, grid_size, row_wise); + } + // Start in the next column + else { + core_group_2 = num_cores_to_corerangeset( + {last_core_group_1.x + 1, 0}, num_core_group_2_cores, grid_size, row_wise); + } + } + units_per_core_group_2 = units_per_core_group_1; + units_per_core_group_1++; + } + + return std::make_tuple( + target_num_cores, all_cores, core_group_1, core_group_2, units_per_core_group_1, units_per_core_group_2); +} + +std::tuple split_work_to_cores( + const CoreRangeSet& core_grid, const uint32_t units_to_divide, const bool row_wise) { + ZoneScoped; + if (units_to_divide == 0) { + return std::make_tuple(0, CoreRangeSet(), CoreRangeSet(), CoreRangeSet(), 0, 0); + } + uint32_t max_num_cores = core_grid.num_cores(), target_num_cores; + TT_FATAL(max_num_cores > 0, "Core grid must contain at least one core"); + auto start_core = core_grid.ranges().begin()->start_coord; + CoreRangeSet all_cores; + if (units_to_divide >= max_num_cores) { + target_num_cores = max_num_cores; + all_cores = core_grid; + } else { + target_num_cores = units_to_divide; + all_cores = num_cores_to_corerangeset_in_subcoregrids(start_core, target_num_cores, core_grid, row_wise); + } + + CoreRangeSet core_group_1; + CoreRangeSet core_group_2; + uint32_t units_per_core_group_1 = units_to_divide / target_num_cores; + uint32_t units_per_core_group_2 = 0; + uint32_t num_cores_with_more_work = units_to_divide % target_num_cores; + // Evenly divided units to all target cores + if (target_num_cores == 0 || num_cores_with_more_work == 0) { + core_group_1 = all_cores; + } + // Uneven division of units across cores + // This case should only be hit when there are more units of work than a full grid of cores + // which is implicitly assumed in the following logic + else { + // Group of cores that do more work + uint32_t num_core_group_1_cores = num_cores_with_more_work; + uint32_t num_core_group_2_cores = target_num_cores - num_core_group_1_cores; + core_group_1 = + num_cores_to_corerangeset_in_subcoregrids(start_core, num_core_group_1_cores, core_grid, row_wise); + const auto& last_core_group_1 = (*core_group_1.ranges().rbegin()).end_coord; + const auto& core_grid_ranges = core_grid.ranges(); + uint32_t num_cores_counted = 0, i; + for (i = 0; i < core_grid_ranges.size(); i++) { + num_cores_counted += core_grid_ranges[i].size(); + if (num_cores_counted >= num_core_group_1_cores) { + break; + } + } + const auto& range_containing_last_core_group_1 = core_grid_ranges[i]; + // Start in next core range + if (last_core_group_1 == range_containing_last_core_group_1.end_coord) { + core_group_2 = num_cores_to_corerangeset_in_subcoregrids( + core_grid_ranges[i + 1].start_coord, num_core_group_2_cores, core_grid, row_wise); + } else if (row_wise) { + // Start in the same row + if (last_core_group_1.x != range_containing_last_core_group_1.end_coord.x) { + core_group_2 = num_cores_to_corerangeset_in_subcoregrids( + {last_core_group_1.x + 1, last_core_group_1.y}, num_core_group_2_cores, core_grid, row_wise); + } + // Start in the next row + else { + core_group_2 = num_cores_to_corerangeset_in_subcoregrids( + {range_containing_last_core_group_1.start_coord.x, last_core_group_1.y + 1}, + num_core_group_2_cores, + core_grid, + row_wise); + } + } else { + // Start in the same column + if (last_core_group_1.y != range_containing_last_core_group_1.end_coord.y) { + core_group_2 = num_cores_to_corerangeset_in_subcoregrids( + {last_core_group_1.x, last_core_group_1.y + 1}, num_core_group_2_cores, core_grid, row_wise); + } + // Start in the next column + else { + core_group_2 = num_cores_to_corerangeset_in_subcoregrids( + {last_core_group_1.x + 1, range_containing_last_core_group_1.end_coord.y}, + num_core_group_2_cores, + core_grid, + row_wise); } } units_per_core_group_2 = units_per_core_group_1; diff --git a/tt_metal/common/work_split.hpp b/tt_metal/common/work_split.hpp index 2b5ae0ecb9d8..f024f016e655 100644 --- a/tt_metal/common/work_split.hpp +++ b/tt_metal/common/work_split.hpp @@ -53,5 +53,8 @@ CoreRangeSet num_cores_to_corerangeset_in_subcoregrids( std::tuple split_work_to_cores( const CoreCoord grid_size, const uint32_t units_to_divide, const bool row_wise = false); +std::tuple split_work_to_cores( + const CoreRangeSet& core_grid, const uint32_t units_to_divide, const bool row_wise = false); + } // namespace tt_metal } // namespace tt diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.cpp index a88ed52047b4..606f52a07967 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.cpp @@ -353,6 +353,45 @@ BinaryDeviceOperation::invoke( output_dtype.value() == optional_output_tensor.value().get_dtype(), "If both output dtype and output tensor provided dtype should match"); } + CoreRangeSet worker_grid; + // We assert all shard specs are the same if sharded, so only need to check the first shard spec + // This will create the worker grid based on the used sub-devices when sharded + // Otherwise this will use all cores of the sub-devices + // TODO #13655: Note that the current program ingfrastructure still only supports a single sub-device per program + if (input_tensor_a_arg.is_sharded()) { + const auto& input_grid = input_tensor_a_arg.shard_spec().value().grid; + auto device = input_tensor_a_arg.device(); + for (const auto& sub_device_id : device->get_sub_device_ids()) { + const auto& sub_device_workers = device->worker_cores(HalProgrammableCoreType::TENSIX, sub_device_id); + if (sub_device_workers.intersects(input_grid)) { + worker_grid = worker_grid.merge(sub_device_workers); + } + } + } else if (input_tensor_b_arg.is_sharded()) { + const auto& input_grid = input_tensor_b_arg.shard_spec().value().grid; + auto device = input_tensor_b_arg.device(); + for (const auto& sub_device_id : device->get_sub_device_ids()) { + const auto& sub_device_workers = device->worker_cores(HalProgrammableCoreType::TENSIX, sub_device_id); + if (sub_device_workers.intersects(input_grid)) { + worker_grid = worker_grid.merge(sub_device_workers); + } + } + } else if (optional_output_tensor.has_value() && optional_output_tensor->is_sharded()) { + const auto& output_grid = optional_output_tensor->shard_spec().value().grid; + auto device = optional_output_tensor->device(); + for (const auto& sub_device_id : device->get_sub_device_ids()) { + const auto& sub_device_workers = device->worker_cores(HalProgrammableCoreType::TENSIX, sub_device_id); + if (sub_device_workers.intersects(output_grid)) { + worker_grid = worker_grid.merge(sub_device_workers); + } + } + } else { + auto device = input_tensor_a_arg.device(); + for (const auto& sub_device_id : device->get_sub_device_ids()) { + const auto& sub_device_workers = device->worker_cores(HalProgrammableCoreType::TENSIX, sub_device_id); + worker_grid = worker_grid.merge(sub_device_workers); + } + } return { operation_attributes_t{ @@ -360,8 +399,9 @@ BinaryDeviceOperation::invoke( std::move(activations), std::move(input_tensor_a_activation), std::nullopt, - memory_config.value_or(input_tensor_a_arg.memory_config()), + memory_config.value_or(optional_output_tensor.has_value() ? optional_output_tensor->memory_config() : input_tensor_a_arg.memory_config()), output_dtype.value_or(input_tensor_a_arg.get_dtype()), + std::move(worker_grid), std::nullopt}, tensor_args_t{input_tensor_a_arg, input_tensor_b_arg, optional_output_tensor}}; } @@ -382,6 +422,8 @@ BinaryDeviceOperation::invoke( "If both output dtype and output tensor provided dtype should match"); } + // Currently unused/unsupported + CoreRangeSet worker_grid = CoreRangeSet(); return { operation_attributes_t{ binary_op_type, @@ -390,6 +432,7 @@ BinaryDeviceOperation::invoke( scalar, memory_config.value_or(input_tensor_a_arg.memory_config()), output_dtype.value_or(input_tensor_a_arg.get_dtype()), + std::move(worker_grid), std::nullopt}, tensor_args_t{input_tensor_a_arg, std::nullopt, optional_output_tensor}}; } diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.hpp index e6c414a97642..c93997711235 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.hpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/binary_device_operation.hpp @@ -34,6 +34,7 @@ struct BinaryDeviceOperation { const std::optional scalar; const MemoryConfig memory_config; const DataType dtype; + const CoreRangeSet worker_grid; std::optional compute_kernel_config; tt::stl::hash::hash_t to_hash() const { @@ -58,7 +59,7 @@ struct BinaryDeviceOperation { tt::tt_metal::CBHandle cb_src0; tt::tt_metal::CBHandle cb_src1; tt::tt_metal::CBHandle cb_output; - CoreCoord compute_with_storage_grid_size; + CoreRangeSet all_device_cores; uint32_t src0_single_tile_size; uint32_t src1_single_tile_size; uint32_t dst_single_tile_size; @@ -85,7 +86,7 @@ struct BinaryDeviceOperation { tt::tt_metal::CBHandle cb_src0; tt::tt_metal::CBHandle cb_src1; tt::tt_metal::CBHandle cb_output; - CoreCoord compute_with_storage_grid_size; + CoreRangeSet all_device_cores; uint32_t src0_single_tile_size; uint32_t src1_single_tile_size; uint32_t dst_single_tile_size; diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/element_wise_multi_core_program_factory.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/element_wise_multi_core_program_factory.cpp index ceef285e7172..438ff62cb135 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/device/element_wise_multi_core_program_factory.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/element_wise_multi_core_program_factory.cpp @@ -5,6 +5,7 @@ #include #include "binary_device_operation.hpp" +#include "ttnn/cpp/ttnn/operations/eltwise/binary/device/eltwise_multi_core_program_factory_common.hpp" #include "ttnn/operations/eltwise/unary/common/unary_op_types.hpp" #include "tt_metal/common/work_split.hpp" @@ -15,276 +16,6 @@ namespace ttnn::operations::binary { -template -inline __attribute__((always_inline)) void set_eltwise_binary_runtime_args( - Program& program, - const Tensor& a, - const Tensor& b, - const Tensor& output, - const KernelHandle binary_reader_kernel_id, - const KernelHandle unary_writer_kernel_id, - const KernelHandle eltwise_binary_kernel_id, - const CBHandle cb_src0, - const CBHandle cb_src1, - const CBHandle cb_output, - const CoreCoord compute_with_storage_grid_size, - const uint32_t src0_single_tile_size, - const uint32_t src1_single_tile_size, - const uint32_t dst_single_tile_size) { - using namespace tt; - using namespace tt::tt_metal; - using namespace tt::constants; - - auto src_buffer_a = a.buffer(); - auto src_buffer_b = b.buffer(); - auto dst_buffer = output.buffer(); - - CoreRangeSet all_cores, core_group_1, core_group_2; - - std::optional shard_spec = std::nullopt; - std::optional sharded_layout = std::nullopt; - bool src0_sharded = a.memory_config().is_sharded(); - bool src1_sharded = b.memory_config().is_sharded(); - bool out_sharded = output.memory_config().is_sharded(); - - bool block_or_width_sharded = false; - if (src0_sharded) { - shard_spec = a.shard_spec().value(); - block_or_width_sharded = a.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED; - sharded_layout = a.memory_config().memory_layout; - } else if (src1_sharded) { - shard_spec = b.shard_spec().value(); - block_or_width_sharded = b.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED; - sharded_layout = b.memory_config().memory_layout; - } else if (out_sharded) { - shard_spec = output.shard_spec().value(); - block_or_width_sharded = output.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED; - sharded_layout = output.memory_config().memory_layout; - } - - uint32_t num_tiles = a.volume() / TILE_HW; - - uint32_t num_cores_x = compute_with_storage_grid_size.x; - uint32_t num_cores_y = compute_with_storage_grid_size.y; - uint32_t num_cores, num_tiles_per_core_group_1, num_tiles_per_core_group_2; - uint32_t num_cores_total = num_cores_x * num_cores_y; - - uint32_t block_size_per_core_group_1 = 1, block_size_per_core_group_2 = 1, max_block_size = 1; - - uint32_t block_cnt_per_core_group_1, block_cnt_per_core_group_2; - - bool row_major; - uint32_t block_height = 0, block_width = 0, block_size = 0, output_width = 0, last_unpadded_block_height = 0, - last_unpadded_block_width = 0; - CoreCoord end_core; - std::vector cores; - - if (shard_spec.has_value()) { - all_cores = shard_spec.value().grid; - num_cores = all_cores.num_cores(); - core_group_1 = all_cores; - core_group_2 = CoreRangeSet(); - num_tiles_per_core_group_1 = shard_spec.value().shape[0] * shard_spec.value().shape[1] / TILE_HW; - num_tiles_per_core_group_2 = 0; - block_size_per_core_group_1 = find_max_block_size(num_tiles_per_core_group_1); - max_block_size = block_size_per_core_group_1; - - block_cnt_per_core_group_1 = num_tiles_per_core_group_1 / block_size_per_core_group_1; - block_cnt_per_core_group_2 = num_tiles_per_core_group_2 / block_size_per_core_group_2; - row_major = shard_spec.value().orientation == ShardOrientation::ROW_MAJOR; - block_height = shard_spec.value().shape[0] / TILE_HEIGHT; - block_width = shard_spec.value().shape[1] / TILE_WIDTH; - if (block_or_width_sharded) { - block_size = block_width * block_height; - end_core = (*shard_spec.value().grid.ranges().begin()).end_coord; - output_width = output.get_legacy_shape()[-1] / TILE_WIDTH; - uint32_t output_height = output.volume() / output.get_legacy_shape()[-1] / TILE_HEIGHT; - last_unpadded_block_height = block_height - (round_up(output_height, block_height) - output_height); - last_unpadded_block_width = block_width - (round_up(output_width, block_width) - output_width); - } - auto bbox = core_group_1.bounding_box(); - cores = grid_to_cores_with_noop(bbox.end_coord.x, bbox.end_coord.y, num_cores_x, num_cores_y, row_major); - } else { - row_major = true; - std::tie( - num_cores, all_cores, core_group_1, core_group_2, num_tiles_per_core_group_1, num_tiles_per_core_group_2) = - tt::tt_metal::split_work_to_cores(compute_with_storage_grid_size, num_tiles, row_major); - block_cnt_per_core_group_1 = num_tiles_per_core_group_1; - block_cnt_per_core_group_2 = num_tiles_per_core_group_2; - cores = grid_to_cores(num_cores_total, num_cores_x, num_cores_y, row_major); - } - - uint32_t g1_numcores = core_group_1.num_cores(); - uint32_t g2_numcores = core_group_2.num_cores(); - - std::vector> binary_reader_args; - std::vector> eltwise_binary_args; - std::vector> unary_writer_args; - if constexpr (initialize_args) { - binary_reader_args = {cores.size(), std::vector(7)}; - eltwise_binary_args = {cores.size(), std::vector(2)}; - if (block_or_width_sharded and not out_sharded) { - unary_writer_args = {cores.size(), std::vector(7)}; - } else { - unary_writer_args = {cores.size(), std::vector(3)}; - } - } - - auto& cached_reader_args = GetRuntimeArgs(program, binary_reader_kernel_id); - auto& cached_eltwise_args = GetRuntimeArgs(program, eltwise_binary_kernel_id); - auto& cached_writer_args = GetRuntimeArgs(program, unary_writer_kernel_id); - - for (uint32_t i = 0, num_tiles_read = 0; i < num_cores_total; ++i) { - const CoreCoord& core = cores.at(i); - uint32_t num_tiles_per_core = 0; - uint32_t block_cnt_per_core = 0; - uint32_t block_size_per_core = 0; - uint32_t num_shardes_per_height = 0; - uint32_t num_shardes_per_width = 0; - uint32_t start_id = 0; - if (shard_spec.has_value()) { - if (sharded_layout == tt::tt_metal::TensorMemoryLayout::HEIGHT_SHARDED) { - num_shardes_per_height = num_cores; - num_shardes_per_width = 1; - } else if (sharded_layout == tt::tt_metal::TensorMemoryLayout::WIDTH_SHARDED) { - num_shardes_per_width = num_cores; - num_shardes_per_height = 1; - } else { // block sharded - auto bbox = core_group_1.bounding_box(); - if (shard_spec.value().orientation == ShardOrientation::ROW_MAJOR) { - num_shardes_per_height = bbox.end_coord.y - bbox.start_coord.y + 1; - num_shardes_per_width = bbox.end_coord.x - bbox.start_coord.x + 1; - } else { - num_shardes_per_height = bbox.end_coord.x - bbox.start_coord.x + 1; - num_shardes_per_width = bbox.end_coord.y - bbox.start_coord.y + 1; - } - } - start_id = (i / num_shardes_per_width) * (block_height * block_width * num_shardes_per_width) + - (i % num_shardes_per_width) * block_width; - } else { - start_id = num_tiles_read; - } - - if (i < g1_numcores) { - num_tiles_per_core = num_tiles_per_core_group_1; - block_cnt_per_core = block_cnt_per_core_group_1; - block_size_per_core = block_size_per_core_group_1; - } else if (i < num_cores) { - num_tiles_per_core = num_tiles_per_core_group_2; - block_cnt_per_core = block_cnt_per_core_group_2; - block_size_per_core = block_size_per_core_group_2; - } else { - // Zero out non-working cores RT args. Only necessary in override - // since initialization pushes zero vectors to unused cores. - if constexpr (!initialize_args) { - auto& reader_args = cached_reader_args.at(core.x).at(core.y); - reader_args[2] = 0; - auto& eltwise_args = cached_eltwise_args.at(core.x).at(core.y); - eltwise_args[0] = 0; - auto& writer_args = cached_writer_args.at(core.x).at(core.y); - writer_args[1] = 0; - } - continue; - } - if constexpr (initialize_args) { - binary_reader_args[i] = { - src_buffer_a->address(), - src_buffer_b->address(), - num_tiles_per_core, - start_id, - block_height, - block_width, - num_shardes_per_width, - num_shardes_per_width}; - eltwise_binary_args[i] = {block_cnt_per_core, block_size_per_core}; - } else { - auto& reader_args = cached_reader_args.at(core.x).at(core.y); - reader_args[0] = src_buffer_a->address(); - reader_args[1] = src_buffer_b->address(); - reader_args[2] = num_tiles_per_core; - reader_args[3] = start_id; - reader_args[4] = block_height; - reader_args[5] = block_width; - reader_args[6] = num_shardes_per_width; - auto& eltwise_args = cached_eltwise_args.at(core.x).at(core.y); - eltwise_args[0] = block_cnt_per_core; - eltwise_args[1] = block_size_per_core; - } - if (block_or_width_sharded and not out_sharded) { - uint32_t unpadded_block_height = block_height; - uint32_t unpadded_block_width = block_width; - if (row_major) { - if (core.x == end_core.x) { - unpadded_block_width = last_unpadded_block_width; - } - if (core.y == end_core.y) { - unpadded_block_height = last_unpadded_block_height; - } - } else { - if (core.y == end_core.y) { - unpadded_block_width = last_unpadded_block_width; - } - if (core.x == end_core.x) { - unpadded_block_height = last_unpadded_block_height; - } - } - if constexpr (initialize_args) { - unary_writer_args[i] = { - dst_buffer->address(), - block_height, - block_width, - unpadded_block_height, - unpadded_block_width, - output_width, - block_size, - (i / num_shardes_per_width) * (block_height * block_width * num_shardes_per_width) + - (i % num_shardes_per_width) * block_width, - 0}; - } else { - auto& writer_args = cached_writer_args.at(core.x).at(core.y); - writer_args[0] = dst_buffer->address(); - writer_args[1] = block_height; - writer_args[2] = block_width; - writer_args[3] = unpadded_block_height; - writer_args[4] = unpadded_block_width; - writer_args[5] = output_width; - writer_args[6] = block_size; - writer_args[7] = (i / num_shardes_per_width) * (block_height * block_width * num_shardes_per_width) + - (i % num_shardes_per_width) * block_width; - writer_args[8] = 0; - } - } else { - if constexpr (initialize_args) { - unary_writer_args[i] = {dst_buffer->address(), num_tiles_per_core, num_tiles_read}; - } else { - auto& writer_args = cached_writer_args.at(core.x).at(core.y); - writer_args[0] = dst_buffer->address(); - writer_args[1] = num_tiles_per_core; - writer_args[2] = num_tiles_read; - } - } - num_tiles_read += num_tiles_per_core; - } - - if constexpr (initialize_args) { - SetRuntimeArgs(program, binary_reader_kernel_id, cores, binary_reader_args); - SetRuntimeArgs(program, eltwise_binary_kernel_id, cores, eltwise_binary_args); - SetRuntimeArgs(program, unary_writer_kernel_id, cores, unary_writer_args); - } - - if (src0_sharded) { - UpdateDynamicCircularBufferAddressAndTotalSize( - program, cb_src0, *src_buffer_a, num_tiles_per_core_group_1 * src0_single_tile_size); - } - if (src1_sharded) { - UpdateDynamicCircularBufferAddressAndTotalSize( - program, cb_src1, *src_buffer_b, num_tiles_per_core_group_1 * src1_single_tile_size); - } - if (out_sharded) { - UpdateDynamicCircularBufferAddressAndTotalSize( - program, cb_output, *dst_buffer, num_tiles_per_core_group_1 * dst_single_tile_size); - } -} BinaryDeviceOperation::ElementWiseMultiCore::cached_program_t BinaryDeviceOperation::ElementWiseMultiCore::create( const operation_attributes_t& operation_attributes, const tensor_args_t& tensor_args, @@ -324,10 +55,6 @@ BinaryDeviceOperation::ElementWiseMultiCore::cached_program_t BinaryDeviceOperat bool src1_sharded = b->memory_config().is_sharded(); bool out_sharded = output.memory_config().is_sharded(); - auto compute_with_storage_grid_size = device->compute_with_storage_grid_size(); - uint32_t num_cores_x = compute_with_storage_grid_size.x; - uint32_t num_cores_y = compute_with_storage_grid_size.y; - bool block_or_width_sharded = false; if (src0_sharded) { @@ -350,7 +77,7 @@ BinaryDeviceOperation::ElementWiseMultiCore::cached_program_t BinaryDeviceOperat tt_metal::Buffer* dst_buffer = output.buffer(); TT_ASSERT(dst_buffer != nullptr, "Output buffer should be allocated on device!"); - auto all_device_cores = CoreRange({0, 0}, {num_cores_x - 1, num_cores_y - 1}); + const auto& all_device_cores = operation_attributes.worker_grid; uint32_t src0_cb_index = tt::CBIndex::c_0; uint32_t num_input_tiles = src0_sharded ? num_tiles_per_shard : 2 * max_block_size; @@ -465,7 +192,7 @@ BinaryDeviceOperation::ElementWiseMultiCore::cached_program_t BinaryDeviceOperat cb_src0, cb_src1, cb_output, - compute_with_storage_grid_size, + all_device_cores, src0_single_tile_size, src1_single_tile_size, dst_single_tile_size); @@ -478,7 +205,7 @@ BinaryDeviceOperation::ElementWiseMultiCore::cached_program_t BinaryDeviceOperat cb_src0, cb_src1, cb_output, - compute_with_storage_grid_size, + all_device_cores, src0_single_tile_size, src1_single_tile_size, dst_single_tile_size}}; @@ -506,7 +233,7 @@ void BinaryDeviceOperation::ElementWiseMultiCore::override_runtime_arguments( shared_variables.cb_src0, shared_variables.cb_src1, shared_variables.cb_output, - shared_variables.compute_with_storage_grid_size, + shared_variables.all_device_cores, shared_variables.src0_single_tile_size, shared_variables.src1_single_tile_size, shared_variables.dst_single_tile_size); diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/element_wise_multi_core_sfpu_pgm_factory.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/element_wise_multi_core_sfpu_pgm_factory.cpp index f23f9b7fe608..a362e5ee9309 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/device/element_wise_multi_core_sfpu_pgm_factory.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/element_wise_multi_core_sfpu_pgm_factory.cpp @@ -5,6 +5,7 @@ #include #include "binary_device_operation.hpp" +#include "ttnn/cpp/ttnn/operations/eltwise/binary/device/eltwise_multi_core_program_factory_common.hpp" #include "ttnn/operations/eltwise/unary/common/unary_op_types.hpp" #include "tt_metal/common/work_split.hpp" @@ -15,276 +16,6 @@ namespace ttnn::operations::binary { -template -inline __attribute__((always_inline)) void set_eltwise_binary_sfpu_runtime_args( - Program& program, - const Tensor& a, - const Tensor& b, - const Tensor& output, - const KernelHandle binary_reader_kernel_id, - const KernelHandle unary_writer_kernel_id, - const KernelHandle eltwise_binary_kernel_id, - const CBHandle cb_src0, - const CBHandle cb_src1, - const CBHandle cb_output, - const CoreCoord compute_with_storage_grid_size, - const uint32_t src0_single_tile_size, - const uint32_t src1_single_tile_size, - const uint32_t dst_single_tile_size) { - using namespace tt; - using namespace tt::tt_metal; - using namespace tt::constants; - - auto src_buffer_a = a.buffer(); - auto src_buffer_b = b.buffer(); - auto dst_buffer = output.buffer(); - - CoreRangeSet all_cores, core_group_1, core_group_2; - - std::optional shard_spec = std::nullopt; - std::optional sharded_layout = std::nullopt; - bool src0_sharded = a.memory_config().is_sharded(); - bool src1_sharded = b.memory_config().is_sharded(); - bool out_sharded = output.memory_config().is_sharded(); - - bool block_or_width_sharded = false; - if (src0_sharded) { - shard_spec = a.shard_spec().value(); - block_or_width_sharded = a.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED; - sharded_layout = a.memory_config().memory_layout; - } else if (src1_sharded) { - shard_spec = b.shard_spec().value(); - block_or_width_sharded = b.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED; - sharded_layout = b.memory_config().memory_layout; - } else if (out_sharded) { - shard_spec = output.shard_spec().value(); - block_or_width_sharded = output.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED; - sharded_layout = output.memory_config().memory_layout; - } - - uint32_t num_tiles = a.volume() / TILE_HW; - - uint32_t num_cores_x = compute_with_storage_grid_size.x; - uint32_t num_cores_y = compute_with_storage_grid_size.y; - uint32_t num_cores, num_tiles_per_core_group_1, num_tiles_per_core_group_2; - uint32_t num_cores_total = num_cores_x * num_cores_y; - - uint32_t block_size_per_core_group_1 = 1, block_size_per_core_group_2 = 1, max_block_size = 1; - - uint32_t block_cnt_per_core_group_1, block_cnt_per_core_group_2; - - bool row_major; - uint32_t block_height = 0, block_width = 0, block_size = 0, output_width = 0, last_unpadded_block_height = 0, - last_unpadded_block_width = 0; - CoreCoord end_core; - std::vector cores; - - if (shard_spec.has_value()) { - all_cores = shard_spec.value().grid; - num_cores = all_cores.num_cores(); - core_group_1 = all_cores; - core_group_2 = CoreRangeSet(); - num_tiles_per_core_group_1 = shard_spec.value().shape[0] * shard_spec.value().shape[1] / TILE_HW; - num_tiles_per_core_group_2 = 0; - block_size_per_core_group_1 = find_max_block_size(num_tiles_per_core_group_1); - max_block_size = block_size_per_core_group_1; - - block_cnt_per_core_group_1 = num_tiles_per_core_group_1 / block_size_per_core_group_1; - block_cnt_per_core_group_2 = num_tiles_per_core_group_2 / block_size_per_core_group_2; - row_major = shard_spec.value().orientation == ShardOrientation::ROW_MAJOR; - block_height = shard_spec.value().shape[0] / TILE_HEIGHT; - block_width = shard_spec.value().shape[1] / TILE_WIDTH; - if (block_or_width_sharded) { - block_size = block_width * block_height; - end_core = (*shard_spec.value().grid.ranges().begin()).end_coord; - output_width = output.get_legacy_shape()[-1] / TILE_WIDTH; - uint32_t output_height = output.volume() / output.get_legacy_shape()[-1] / TILE_HEIGHT; - last_unpadded_block_height = block_height - (round_up(output_height, block_height) - output_height); - last_unpadded_block_width = block_width - (round_up(output_width, block_width) - output_width); - } - auto bbox = core_group_1.bounding_box(); - cores = grid_to_cores_with_noop(bbox.end_coord.x, bbox.end_coord.y, num_cores_x, num_cores_y, row_major); - } else { - row_major = true; - std::tie( - num_cores, all_cores, core_group_1, core_group_2, num_tiles_per_core_group_1, num_tiles_per_core_group_2) = - tt::tt_metal::split_work_to_cores(compute_with_storage_grid_size, num_tiles, row_major); - block_cnt_per_core_group_1 = num_tiles_per_core_group_1; - block_cnt_per_core_group_2 = num_tiles_per_core_group_2; - cores = grid_to_cores(num_cores_total, num_cores_x, num_cores_y, row_major); - } - - uint32_t g1_numcores = core_group_1.num_cores(); - uint32_t g2_numcores = core_group_2.num_cores(); - - std::vector> binary_reader_args; - std::vector> eltwise_binary_args; - std::vector> unary_writer_args; - if constexpr (initialize_args) { - binary_reader_args = {cores.size(), std::vector(7)}; - eltwise_binary_args = {cores.size(), std::vector(2)}; - if (block_or_width_sharded and not out_sharded) { - unary_writer_args = {cores.size(), std::vector(7)}; - } else { - unary_writer_args = {cores.size(), std::vector(3)}; - } - } - - auto& cached_reader_args = GetRuntimeArgs(program, binary_reader_kernel_id); - auto& cached_eltwise_args = GetRuntimeArgs(program, eltwise_binary_kernel_id); - auto& cached_writer_args = GetRuntimeArgs(program, unary_writer_kernel_id); - - for (uint32_t i = 0, num_tiles_read = 0; i < num_cores_total; ++i) { - const CoreCoord& core = cores.at(i); - uint32_t num_tiles_per_core = 0; - uint32_t block_cnt_per_core = 0; - uint32_t block_size_per_core = 0; - uint32_t num_shardes_per_height = 0; - uint32_t num_shardes_per_width = 0; - uint32_t start_id = 0; - if (shard_spec.has_value()) { - if (sharded_layout == tt::tt_metal::TensorMemoryLayout::HEIGHT_SHARDED) { - num_shardes_per_height = num_cores; - num_shardes_per_width = 1; - } else if (sharded_layout == tt::tt_metal::TensorMemoryLayout::WIDTH_SHARDED) { - num_shardes_per_width = num_cores; - num_shardes_per_height = 1; - } else { // block sharded - auto bbox = core_group_1.bounding_box(); - if (shard_spec.value().orientation == ShardOrientation::ROW_MAJOR) { - num_shardes_per_height = bbox.end_coord.y - bbox.start_coord.y + 1; - num_shardes_per_width = bbox.end_coord.x - bbox.start_coord.x + 1; - } else { - num_shardes_per_height = bbox.end_coord.x - bbox.start_coord.x + 1; - num_shardes_per_width = bbox.end_coord.y - bbox.start_coord.y + 1; - } - } - start_id = (i / num_shardes_per_width) * (block_height * block_width * num_shardes_per_width) + - (i % num_shardes_per_width) * block_width; - } else { - start_id = num_tiles_read; - } - - if (i < g1_numcores) { - num_tiles_per_core = num_tiles_per_core_group_1; - block_cnt_per_core = block_cnt_per_core_group_1; - block_size_per_core = block_size_per_core_group_1; - } else if (i < num_cores) { - num_tiles_per_core = num_tiles_per_core_group_2; - block_cnt_per_core = block_cnt_per_core_group_2; - block_size_per_core = block_size_per_core_group_2; - } else { - // Zero out non-working cores RT args. Only necessary in override - // since initialization pushes zero vectors to unused cores. - if constexpr (!initialize_args) { - auto& reader_args = cached_reader_args.at(core.x).at(core.y); - reader_args[2] = 0; - auto& eltwise_args = cached_eltwise_args.at(core.x).at(core.y); - eltwise_args[0] = 0; - auto& writer_args = cached_writer_args.at(core.x).at(core.y); - writer_args[1] = 0; - } - continue; - } - if constexpr (initialize_args) { - binary_reader_args[i] = { - src_buffer_a->address(), - src_buffer_b->address(), - num_tiles_per_core, - start_id, - block_height, - block_width, - num_shardes_per_width, - num_shardes_per_width}; - eltwise_binary_args[i] = {block_cnt_per_core, block_size_per_core}; - } else { - auto& reader_args = cached_reader_args.at(core.x).at(core.y); - reader_args[0] = src_buffer_a->address(); - reader_args[1] = src_buffer_b->address(); - reader_args[2] = num_tiles_per_core; - reader_args[3] = start_id; - reader_args[4] = block_height; - reader_args[5] = block_width; - reader_args[6] = num_shardes_per_width; - auto& eltwise_args = cached_eltwise_args.at(core.x).at(core.y); - eltwise_args[0] = block_cnt_per_core; - eltwise_args[1] = block_size_per_core; - } - if (block_or_width_sharded and not out_sharded) { - uint32_t unpadded_block_height = block_height; - uint32_t unpadded_block_width = block_width; - if (row_major) { - if (core.x == end_core.x) { - unpadded_block_width = last_unpadded_block_width; - } - if (core.y == end_core.y) { - unpadded_block_height = last_unpadded_block_height; - } - } else { - if (core.y == end_core.y) { - unpadded_block_width = last_unpadded_block_width; - } - if (core.x == end_core.x) { - unpadded_block_height = last_unpadded_block_height; - } - } - if constexpr (initialize_args) { - unary_writer_args[i] = { - dst_buffer->address(), - block_height, - block_width, - unpadded_block_height, - unpadded_block_width, - output_width, - block_size, - (i / num_shardes_per_width) * (block_height * block_width * num_shardes_per_width) + - (i % num_shardes_per_width) * block_width, - 0}; - } else { - auto& writer_args = cached_writer_args.at(core.x).at(core.y); - writer_args[0] = dst_buffer->address(); - writer_args[1] = block_height; - writer_args[2] = block_width; - writer_args[3] = unpadded_block_height; - writer_args[4] = unpadded_block_width; - writer_args[5] = output_width; - writer_args[6] = block_size; - writer_args[7] = (i / num_shardes_per_width) * (block_height * block_width * num_shardes_per_width) + - (i % num_shardes_per_width) * block_width; - writer_args[8] = 0; - } - } else { - if constexpr (initialize_args) { - unary_writer_args[i] = {dst_buffer->address(), num_tiles_per_core, num_tiles_read}; - } else { - auto& writer_args = cached_writer_args.at(core.x).at(core.y); - writer_args[0] = dst_buffer->address(); - writer_args[1] = num_tiles_per_core; - writer_args[2] = num_tiles_read; - } - } - num_tiles_read += num_tiles_per_core; - } - - if constexpr (initialize_args) { - SetRuntimeArgs(program, binary_reader_kernel_id, cores, binary_reader_args); - SetRuntimeArgs(program, eltwise_binary_kernel_id, cores, eltwise_binary_args); - SetRuntimeArgs(program, unary_writer_kernel_id, cores, unary_writer_args); - } - - if (src0_sharded) { - UpdateDynamicCircularBufferAddressAndTotalSize( - program, cb_src0, *src_buffer_a, num_tiles_per_core_group_1 * src0_single_tile_size); - } - if (src1_sharded) { - UpdateDynamicCircularBufferAddressAndTotalSize( - program, cb_src1, *src_buffer_b, num_tiles_per_core_group_1 * src1_single_tile_size); - } - if (out_sharded) { - UpdateDynamicCircularBufferAddressAndTotalSize( - program, cb_output, *dst_buffer, num_tiles_per_core_group_1 * dst_single_tile_size); - } -} BinaryDeviceOperation::ElementWiseMultiCoreSfpu::cached_program_t BinaryDeviceOperation::ElementWiseMultiCoreSfpu::create( const operation_attributes_t& operation_attributes, @@ -325,10 +56,6 @@ BinaryDeviceOperation::ElementWiseMultiCoreSfpu::create( bool src1_sharded = b->memory_config().is_sharded(); bool out_sharded = output.memory_config().is_sharded(); - auto compute_with_storage_grid_size = device->compute_with_storage_grid_size(); - uint32_t num_cores_x = compute_with_storage_grid_size.x; - uint32_t num_cores_y = compute_with_storage_grid_size.y; - bool block_or_width_sharded = false; if (src0_sharded) { @@ -351,7 +78,7 @@ BinaryDeviceOperation::ElementWiseMultiCoreSfpu::create( tt_metal::Buffer* dst_buffer = output.buffer(); TT_ASSERT(dst_buffer != nullptr, "Output buffer should be allocated on device!"); - auto all_device_cores = CoreRange({0, 0}, {num_cores_x - 1, num_cores_y - 1}); + const auto& all_device_cores = operation_attributes.worker_grid; uint32_t src0_cb_index = tt::CBIndex::c_0; uint32_t num_input_tiles = src0_sharded ? num_tiles_per_shard : 2 * max_block_size; @@ -460,7 +187,7 @@ BinaryDeviceOperation::ElementWiseMultiCoreSfpu::create( .unpack_to_dest_mode = unpack_to_dest_mode, .defines = eltwise_defines}); - set_eltwise_binary_sfpu_runtime_args( + set_eltwise_binary_runtime_args( program, a, *b, @@ -471,7 +198,7 @@ BinaryDeviceOperation::ElementWiseMultiCoreSfpu::create( cb_src0, cb_src1, cb_output, - compute_with_storage_grid_size, + all_device_cores, src0_single_tile_size, src1_single_tile_size, dst_single_tile_size); @@ -484,7 +211,7 @@ BinaryDeviceOperation::ElementWiseMultiCoreSfpu::create( cb_src0, cb_src1, cb_output, - compute_with_storage_grid_size, + all_device_cores, src0_single_tile_size, src1_single_tile_size, dst_single_tile_size}}; @@ -501,7 +228,7 @@ void BinaryDeviceOperation::ElementWiseMultiCoreSfpu::override_runtime_arguments const auto& shared_variables = cached_program.shared_variables; - set_eltwise_binary_sfpu_runtime_args( + set_eltwise_binary_runtime_args( cached_program.program, input_tensor_a, *input_tensor_b, @@ -512,7 +239,7 @@ void BinaryDeviceOperation::ElementWiseMultiCoreSfpu::override_runtime_arguments shared_variables.cb_src0, shared_variables.cb_src1, shared_variables.cb_output, - shared_variables.compute_with_storage_grid_size, + shared_variables.all_device_cores, shared_variables.src0_single_tile_size, shared_variables.src1_single_tile_size, shared_variables.dst_single_tile_size); diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/eltwise_multi_core_program_factory_common.hpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/eltwise_multi_core_program_factory_common.hpp new file mode 100644 index 000000000000..b902dce459e9 --- /dev/null +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/eltwise_multi_core_program_factory_common.hpp @@ -0,0 +1,330 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "binary_device_operation.hpp" +#include "ttnn/operations/eltwise/unary/common/unary_op_types.hpp" + +#include "tt_metal/common/work_split.hpp" + +#include "tt_metal/common/constants.hpp" +#include "tt_metal/detail/util.hpp" +#include "tt_metal/host_api.hpp" + +namespace ttnn::operations::binary { + +template +inline __attribute__((always_inline)) void set_eltwise_binary_runtime_args( + Program& program, + const Tensor& a, + const Tensor& b, + const Tensor& output, + const KernelHandle binary_reader_kernel_id, + const KernelHandle unary_writer_kernel_id, + const KernelHandle eltwise_binary_kernel_id, + const CBHandle cb_src0, + const CBHandle cb_src1, + const CBHandle cb_output, + const CoreRangeSet& all_device_cores, + const uint32_t src0_single_tile_size, + const uint32_t src1_single_tile_size, + const uint32_t dst_single_tile_size) { + using namespace tt; + using namespace tt::tt_metal; + using namespace tt::constants; + + auto src_buffer_a = a.buffer(); + auto src_buffer_b = b.buffer(); + auto dst_buffer = output.buffer(); + + CoreRangeSet all_cores, core_group_1, core_group_2; + + std::optional shard_spec = std::nullopt; + std::optional sharded_layout = std::nullopt; + bool src0_sharded = a.memory_config().is_sharded(); + bool src1_sharded = b.memory_config().is_sharded(); + bool out_sharded = output.memory_config().is_sharded(); + + bool block_or_width_sharded = false; + if (src0_sharded) { + shard_spec = a.shard_spec().value(); + block_or_width_sharded = a.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED; + sharded_layout = a.memory_config().memory_layout; + } else if (src1_sharded) { + shard_spec = b.shard_spec().value(); + block_or_width_sharded = b.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED; + sharded_layout = b.memory_config().memory_layout; + } else if (out_sharded) { + shard_spec = output.shard_spec().value(); + block_or_width_sharded = output.memory_config().memory_layout != TensorMemoryLayout::HEIGHT_SHARDED; + sharded_layout = output.memory_config().memory_layout; + } + + // zero_start_grid is a flag to indicate that we are using a single rectangular grid that starts at (0, 0) + // as well as having the sharded tensors (if any) start at (0, 0) + // This will run the original work/core distribution algorithms that are specifically for this setup, as these + // are faster than the generic work/core distribution algorithms that work on arbitrary CoreRangeSets + bool zero_start_grid = false; + CoreCoord compute_with_storage_grid_size; + if (all_device_cores.size() == 1) { + const auto& cr = *all_device_cores.ranges().begin(); + if (cr.start_coord.x == 0 && cr.start_coord.y == 0) { + if (shard_spec.has_value()) { + const auto& shard_start_coord = shard_spec->grid.ranges()[0].start_coord; + if (shard_start_coord.x == 0 && shard_start_coord.y == 0) { + zero_start_grid = true; + compute_with_storage_grid_size = CoreCoord(cr.end_coord.x + 1, cr.end_coord.y + 1); + } + } else { + zero_start_grid = true; + compute_with_storage_grid_size = CoreCoord(cr.end_coord.x + 1, cr.end_coord.y + 1); + } + } + } + + uint32_t num_tiles = a.volume() / TILE_HW; + + uint32_t num_cores, num_tiles_per_core_group_1, num_tiles_per_core_group_2, num_cores_total; + if (zero_start_grid) { + num_cores_total = compute_with_storage_grid_size.x * compute_with_storage_grid_size.y; + } else { + num_cores_total = all_device_cores.num_cores(); + } + + uint32_t block_size_per_core_group_1 = 1, block_size_per_core_group_2 = 1, max_block_size = 1; + + uint32_t block_cnt_per_core_group_1, block_cnt_per_core_group_2; + + bool row_major; + uint32_t block_height = 0, block_width = 0, block_size = 0, output_width = 0, last_unpadded_block_height = 0, + last_unpadded_block_width = 0; + CoreCoord end_core; + std::vector cores; + + if (shard_spec.has_value()) { + all_cores = shard_spec.value().grid; + num_cores = all_cores.num_cores(); + core_group_1 = all_cores; + core_group_2 = CoreRangeSet(); + num_tiles_per_core_group_1 = shard_spec.value().shape[0] * shard_spec.value().shape[1] / TILE_HW; + num_tiles_per_core_group_2 = 0; + block_size_per_core_group_1 = find_max_block_size(num_tiles_per_core_group_1); + max_block_size = block_size_per_core_group_1; + + block_cnt_per_core_group_1 = num_tiles_per_core_group_1 / block_size_per_core_group_1; + block_cnt_per_core_group_2 = num_tiles_per_core_group_2 / block_size_per_core_group_2; + row_major = shard_spec.value().orientation == ShardOrientation::ROW_MAJOR; + block_height = shard_spec.value().shape[0] / TILE_HEIGHT; + block_width = shard_spec.value().shape[1] / TILE_WIDTH; + if (block_or_width_sharded) { + block_size = block_width * block_height; + end_core = (*shard_spec.value().grid.ranges().begin()).end_coord; + output_width = output.get_legacy_shape()[-1] / TILE_WIDTH; + uint32_t output_height = output.volume() / output.get_legacy_shape()[-1] / TILE_HEIGHT; + last_unpadded_block_height = block_height - (round_up(output_height, block_height) - output_height); + last_unpadded_block_width = block_width - (round_up(output_width, block_width) - output_width); + } + if (zero_start_grid) { + auto bbox = core_group_1.bounding_box(); + cores = grid_to_cores_with_noop( + bbox.end_coord.x, + bbox.end_coord.y, + compute_with_storage_grid_size.x, + compute_with_storage_grid_size.y, + row_major); + } else { + cores = grid_to_cores_with_noop(all_cores, all_device_cores, row_major); + } + } else { + row_major = true; + std::tie( + num_cores, all_cores, core_group_1, core_group_2, num_tiles_per_core_group_1, num_tiles_per_core_group_2) = + zero_start_grid ? tt::tt_metal::split_work_to_cores(compute_with_storage_grid_size, num_tiles, row_major) + : tt::tt_metal::split_work_to_cores(all_device_cores, num_tiles, row_major); + block_cnt_per_core_group_1 = num_tiles_per_core_group_1; + block_cnt_per_core_group_2 = num_tiles_per_core_group_2; + if (zero_start_grid) { + cores = grid_to_cores( + num_cores_total, compute_with_storage_grid_size.x, compute_with_storage_grid_size.y, row_major); + } else { + cores = corerange_to_cores(all_device_cores, {}, row_major); + } + } + + uint32_t g1_numcores = core_group_1.num_cores(); + uint32_t g2_numcores = core_group_2.num_cores(); + + std::vector> binary_reader_args; + std::vector> eltwise_binary_args; + std::vector> unary_writer_args; + if constexpr (initialize_args) { + binary_reader_args = {cores.size(), std::vector(7)}; + eltwise_binary_args = {cores.size(), std::vector(2)}; + if (block_or_width_sharded and not out_sharded) { + unary_writer_args = {cores.size(), std::vector(7)}; + } else { + unary_writer_args = {cores.size(), std::vector(3)}; + } + } + + auto& cached_reader_args = GetRuntimeArgs(program, binary_reader_kernel_id); + auto& cached_eltwise_args = GetRuntimeArgs(program, eltwise_binary_kernel_id); + auto& cached_writer_args = GetRuntimeArgs(program, unary_writer_kernel_id); + + for (uint32_t i = 0, num_tiles_read = 0; i < num_cores_total; ++i) { + const CoreCoord& core = cores.at(i); + uint32_t num_tiles_per_core = 0; + uint32_t block_cnt_per_core = 0; + uint32_t block_size_per_core = 0; + uint32_t num_shards_per_height = 0; + uint32_t num_shards_per_width = 0; + uint32_t start_id = 0; + if (shard_spec.has_value()) { + if (sharded_layout == tt::tt_metal::TensorMemoryLayout::HEIGHT_SHARDED) { + num_shards_per_height = num_cores; + num_shards_per_width = 1; + } else if (sharded_layout == tt::tt_metal::TensorMemoryLayout::WIDTH_SHARDED) { + num_shards_per_width = num_cores; + num_shards_per_height = 1; + } else { // block sharded + auto bbox = core_group_1.bounding_box(); + if (shard_spec.value().orientation == ShardOrientation::ROW_MAJOR) { + num_shards_per_height = bbox.end_coord.y - bbox.start_coord.y + 1; + num_shards_per_width = bbox.end_coord.x - bbox.start_coord.x + 1; + } else { + num_shards_per_height = bbox.end_coord.x - bbox.start_coord.x + 1; + num_shards_per_width = bbox.end_coord.y - bbox.start_coord.y + 1; + } + } + start_id = (i / num_shards_per_width) * (block_height * block_width * num_shards_per_width) + + (i % num_shards_per_width) * block_width; + } else { + start_id = num_tiles_read; + } + + if (i < g1_numcores) { + num_tiles_per_core = num_tiles_per_core_group_1; + block_cnt_per_core = block_cnt_per_core_group_1; + block_size_per_core = block_size_per_core_group_1; + } else if (i < num_cores) { + num_tiles_per_core = num_tiles_per_core_group_2; + block_cnt_per_core = block_cnt_per_core_group_2; + block_size_per_core = block_size_per_core_group_2; + } else { + // Zero out non-working cores RT args. Only necessary in override + // since initialization pushes zero vectors to unused cores. + if constexpr (!initialize_args) { + auto& reader_args = cached_reader_args.at(core.x).at(core.y); + reader_args[2] = 0; + auto& eltwise_args = cached_eltwise_args.at(core.x).at(core.y); + eltwise_args[0] = 0; + auto& writer_args = cached_writer_args.at(core.x).at(core.y); + writer_args[1] = 0; + } + continue; + } + if constexpr (initialize_args) { + binary_reader_args[i] = { + src_buffer_a->address(), + src_buffer_b->address(), + num_tiles_per_core, + start_id, + block_height, + block_width, + num_shards_per_width, + num_shards_per_width}; + eltwise_binary_args[i] = {block_cnt_per_core, block_size_per_core}; + } else { + auto& reader_args = cached_reader_args.at(core.x).at(core.y); + reader_args[0] = src_buffer_a->address(); + reader_args[1] = src_buffer_b->address(); + reader_args[2] = num_tiles_per_core; + reader_args[3] = start_id; + reader_args[4] = block_height; + reader_args[5] = block_width; + reader_args[6] = num_shards_per_width; + auto& eltwise_args = cached_eltwise_args.at(core.x).at(core.y); + eltwise_args[0] = block_cnt_per_core; + eltwise_args[1] = block_size_per_core; + } + if (block_or_width_sharded and not out_sharded) { + uint32_t unpadded_block_height = block_height; + uint32_t unpadded_block_width = block_width; + if (row_major) { + if (core.x == end_core.x) { + unpadded_block_width = last_unpadded_block_width; + } + if (core.y == end_core.y) { + unpadded_block_height = last_unpadded_block_height; + } + } else { + if (core.y == end_core.y) { + unpadded_block_width = last_unpadded_block_width; + } + if (core.x == end_core.x) { + unpadded_block_height = last_unpadded_block_height; + } + } + if constexpr (initialize_args) { + unary_writer_args[i] = { + dst_buffer->address(), + block_height, + block_width, + unpadded_block_height, + unpadded_block_width, + output_width, + block_size, + (i / num_shards_per_width) * (block_height * block_width * num_shards_per_width) + + (i % num_shards_per_width) * block_width, + 0}; + } else { + auto& writer_args = cached_writer_args.at(core.x).at(core.y); + writer_args[0] = dst_buffer->address(); + writer_args[1] = block_height; + writer_args[2] = block_width; + writer_args[3] = unpadded_block_height; + writer_args[4] = unpadded_block_width; + writer_args[5] = output_width; + writer_args[6] = block_size; + writer_args[7] = (i / num_shards_per_width) * (block_height * block_width * num_shards_per_width) + + (i % num_shards_per_width) * block_width; + writer_args[8] = 0; + } + } else { + if constexpr (initialize_args) { + unary_writer_args[i] = {dst_buffer->address(), num_tiles_per_core, num_tiles_read}; + } else { + auto& writer_args = cached_writer_args.at(core.x).at(core.y); + writer_args[0] = dst_buffer->address(); + writer_args[1] = num_tiles_per_core; + writer_args[2] = num_tiles_read; + } + } + num_tiles_read += num_tiles_per_core; + } + + if constexpr (initialize_args) { + SetRuntimeArgs(program, binary_reader_kernel_id, cores, binary_reader_args); + SetRuntimeArgs(program, eltwise_binary_kernel_id, cores, eltwise_binary_args); + SetRuntimeArgs(program, unary_writer_kernel_id, cores, unary_writer_args); + } + + if (src0_sharded) { + UpdateDynamicCircularBufferAddressAndTotalSize( + program, cb_src0, *src_buffer_a, num_tiles_per_core_group_1 * src0_single_tile_size); + } + if (src1_sharded) { + UpdateDynamicCircularBufferAddressAndTotalSize( + program, cb_src1, *src_buffer_b, num_tiles_per_core_group_1 * src1_single_tile_size); + } + if (out_sharded) { + UpdateDynamicCircularBufferAddressAndTotalSize( + program, cb_output, *dst_buffer, num_tiles_per_core_group_1 * dst_single_tile_size); + } +} + +} // namespace ttnn::operations::binary diff --git a/ttnn/cpp/ttnn/operations/eltwise/binary/device/kernels/dataflow/reader_bcast_h_sharded_optimised.cpp b/ttnn/cpp/ttnn/operations/eltwise/binary/device/kernels/dataflow/reader_bcast_h_sharded_optimised.cpp index 84e091ba56a5..464cdf72bd06 100644 --- a/ttnn/cpp/ttnn/operations/eltwise/binary/device/kernels/dataflow/reader_bcast_h_sharded_optimised.cpp +++ b/ttnn/cpp/ttnn/operations/eltwise/binary/device/kernels/dataflow/reader_bcast_h_sharded_optimised.cpp @@ -3,12 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 // This code is temporarily copied from ttnn/cpp/ttnn/operations/datamovement/binary/device/ to demonstrate -<<<<<<< HEAD -// the new ability to keep the CircularBufferConfigs continuous during dispatching. See the use of CBIndex::c_16 below. -======= // the new ability to keep the CircularBufferConfigs continuous during dispatching. See the use of CBIndex::c_2 below. ->>>>>>> 500923c2b7... #7493: Updating some ops to use c_2 instead of c_16 given the dependency on eltwise -// When broadcating is properly supported we expect this code to be deleted or refactored substantially. +// When broadcasting is properly supported we expect this code to be deleted or refactored substantially. #include #include "dataflow_api.h" From 03faa20d84855a82a9ca42a1e1e904c04913b4fe Mon Sep 17 00:00:00 2001 From: Austin Ho Date: Tue, 17 Dec 2024 23:03:49 +0000 Subject: [PATCH 66/87] #0: Fix core descriptor of col dispatch N150/N300 and fix caching of core descriptor to also include dispatch core axis Update device build key to include dispatch core type and axis since these affects the number of memory banks which are a compile time define for FW --- tt_metal/common/core_descriptor.cpp | 80 ++++++++++++------- tt_metal/common/core_descriptor.hpp | 67 ++-------------- .../core_descriptors/wormhole_b0_80_arch.yaml | 8 +- tt_metal/hw/inc/mod_div_lib.h | 7 ++ tt_metal/impl/allocator/allocator.cpp | 2 +- tt_metal/impl/device/device.cpp | 15 +++- .../impl/dispatch/dispatch_core_common.hpp | 26 +++--- 7 files changed, 104 insertions(+), 101 deletions(-) diff --git a/tt_metal/common/core_descriptor.cpp b/tt_metal/common/core_descriptor.cpp index 989f1dd66853..662b03e36582 100644 --- a/tt_metal/common/core_descriptor.cpp +++ b/tt_metal/common/core_descriptor.cpp @@ -10,15 +10,13 @@ namespace tt { const core_descriptor_t& get_core_descriptor_config( chip_id_t device_id, const uint8_t num_hw_cqs, const tt_metal::DispatchCoreConfig& dispatch_core_config) { - // {arch : {product : {num hardware command queues : config}}} - static std::unordered_map>> + // {arch : {product : {dispatch core axis: {num hardware command queues : config}}}} + static std::unordered_map< + ARCH, + std::unordered_map< + std::string, + std::unordered_map>>> config_by_arch; - // TODO: is there a better way to do this? - static CoreType previous_dispatch_core_type; - if (previous_dispatch_core_type != dispatch_core_config.get_core_type()) { - config_by_arch.clear(); - previous_dispatch_core_type = dispatch_core_config.get_core_type(); - } ARCH arch = tt::Cluster::instance().arch(); uint32_t harvesting_mask = tt::Cluster::instance().get_harvested_rows(device_id); @@ -45,15 +43,12 @@ const core_descriptor_t& get_core_descriptor_config( } } - if (config_by_arch.count(arch) and config_by_arch.at(arch).count(product_name) and - config_by_arch.at(arch).at(product_name).count(num_hw_cqs)) { - return config_by_arch.at(arch).at(product_name).at(num_hw_cqs); + std::unordered_map& config_by_num_cqs = + config_by_arch[arch][product_name][dispatch_core_config]; + if (config_by_num_cqs.count(num_hw_cqs)) { + return config_by_num_cqs.at(num_hw_cqs); } - std::unordered_map>& config_by_product = - config_by_arch[arch]; - std::unordered_map& config_by_num_cqs = config_by_product[product_name]; - YAML::Node core_descriptor_yaml = YAML::LoadFile(get_core_descriptor_file(arch, dispatch_core_config)); YAML::Node desc_yaml = core_descriptor_yaml[product_name] @@ -123,30 +118,61 @@ const core_descriptor_t& get_core_descriptor_config( } TT_ASSERT(dispatch_cores.size() || std::getenv("TT_METAL_SIMULATOR_EN"), "Dispatch cores size must be positive"); - config_by_num_cqs[num_hw_cqs] = core_descriptor_t{ - .compute_grid_size = compute_grid_size, - .relative_compute_cores = compute_cores, - .relative_storage_cores = storage_cores, - .storage_core_bank_size = storage_core_bank_size, - .relative_dispatch_cores = dispatch_cores, - }; - return config_by_arch.at(arch).at(product_name).at(num_hw_cqs); + CoreCoord grid_size = tt::Cluster::instance().get_soc_desc(device_id).worker_grid_size; + + std::vector logical_compute_cores; + logical_compute_cores.reserve(compute_cores.size()); + std::transform( + compute_cores.cbegin(), + compute_cores.cend(), + std::back_inserter(logical_compute_cores), + [&grid_size](RelativeCoreCoord rel_coord) { return get_core_coord_from_relative(rel_coord, grid_size); }); + + std::vector logical_storage_cores; + logical_storage_cores.reserve(storage_cores.size()); + std::transform( + storage_cores.cbegin(), + storage_cores.cend(), + std::back_inserter(logical_storage_cores), + [&grid_size](RelativeCoreCoord rel_coord) { return get_core_coord_from_relative(rel_coord, grid_size); }); + + std::vector logical_dispatch_cores; + logical_dispatch_cores.reserve(dispatch_cores.size()); + std::transform( + dispatch_cores.cbegin(), + dispatch_cores.cend(), + std::back_inserter(logical_dispatch_cores), + [&grid_size](RelativeCoreCoord rel_coord) { return get_core_coord_from_relative(rel_coord, grid_size); }); + + auto [it, _] = config_by_num_cqs.emplace(std::make_pair( + num_hw_cqs, + core_descriptor_t{ + .compute_grid_size = std::move(compute_grid_size), + .relative_compute_cores = std::move(compute_cores), + .relative_storage_cores = std::move(storage_cores), + .storage_core_bank_size = std::move(storage_core_bank_size), + .relative_dispatch_cores = std::move(dispatch_cores), + .logical_compute_cores = std::move(logical_compute_cores), + .logical_storage_cores = std::move(logical_storage_cores), + .logical_dispatch_cores = std::move(logical_dispatch_cores), + })); + return it->second; } const std::tuple& get_physical_worker_grid_config( - chip_id_t chip, uint8_t num_hw_cqs, const tt_metal::DispatchCoreConfig& dispatch_core_config) { + chip_id_t device_id, uint8_t num_hw_cqs, const tt_metal::DispatchCoreConfig& dispatch_core_config) { // Get logical compute grid dimensions and num workers static std::unordered_map> physical_grid_config_cache = {}; // Unique hash generated based on the config that's being queried uint32_t config_hash = ((uint8_t)(dispatch_core_config.get_core_type())) | ((uint8_t)(dispatch_core_config.get_dispatch_core_axis()) << 4) | (num_hw_cqs << 8) | - (chip << 16); + (device_id << 16); if (physical_grid_config_cache.find(config_hash) == physical_grid_config_cache.end()) { - auto worker_grid = tt::get_compute_grid_size(chip, num_hw_cqs, dispatch_core_config); + auto worker_grid = tt::get_compute_grid_size(device_id, num_hw_cqs, dispatch_core_config); std::size_t tensix_num_worker_cols = worker_grid.x; std::size_t tensix_num_worker_rows = worker_grid.y; uint32_t tensix_num_worker_cores = tensix_num_worker_cols * tensix_num_worker_rows; - const metal_SocDescriptor& soc_desc = tt::Cluster::instance().get_soc_desc(chip); + const metal_SocDescriptor& soc_desc = tt::Cluster::instance().get_soc_desc(device_id); // Get physical compute grid range based on SOC Desc and Logical Coords CoreCoord tensix_worker_start_phys = soc_desc.get_physical_core_from_logical_core( CoreCoord(0, 0), CoreType::WORKER); // Logical Worker Coords start at 0,0 diff --git a/tt_metal/common/core_descriptor.hpp b/tt_metal/common/core_descriptor.hpp index 7a7dc9b848d6..501cd43480e1 100644 --- a/tt_metal/common/core_descriptor.hpp +++ b/tt_metal/common/core_descriptor.hpp @@ -17,6 +17,9 @@ struct core_descriptor_t { std::vector relative_storage_cores; std::optional storage_core_bank_size = std::nullopt; std::vector relative_dispatch_cores; + std::vector logical_compute_cores; + std::vector logical_storage_cores; + std::vector logical_dispatch_cores; }; inline std::string get_core_descriptor_file( @@ -64,7 +67,7 @@ inline std::string get_core_descriptor_file( return ""; } -inline const std::string get_product_name(tt::ARCH arch, uint32_t num_harvested_rows) { +inline const std::string& get_product_name(tt::ARCH arch, uint32_t num_harvested_rows) { const static std::map> product_name = { {tt::ARCH::GRAYSKULL, {{0, "E150"}, {2, "E75"}}}, {tt::ARCH::WORMHOLE_B0, {{0, "galaxy"}, {1, "nebula_x1"}, {2, "nebula_x2"}}}, @@ -95,29 +98,11 @@ inline std::optional get_storage_core_bank_size( inline const std::vector& get_logical_storage_cores( chip_id_t device_id, const uint8_t num_hw_cqs, const tt_metal::DispatchCoreConfig& dispatch_core_config) { - static std::unordered_map>> - logical_storage_cores_by_device; - static CoreType previous_dispatch_core_type; - if (previous_dispatch_core_type != dispatch_core_config.get_core_type()) { - logical_storage_cores_by_device.clear(); - previous_dispatch_core_type = dispatch_core_config.get_core_type(); - } - auto& logical_storage_cores_by_cq = logical_storage_cores_by_device[device_id]; - if (auto it = logical_storage_cores_by_cq.find(num_hw_cqs); it != logical_storage_cores_by_cq.end()) { - return it->second; - } - CoreCoord grid_size = tt::Cluster::instance().get_soc_desc(device_id).worker_grid_size; - std::vector& logical_storage_cores = logical_storage_cores_by_cq[num_hw_cqs]; const core_descriptor_t& core_desc = get_core_descriptor_config(device_id, num_hw_cqs, dispatch_core_config); - std::transform( - core_desc.relative_storage_cores.cbegin(), - core_desc.relative_storage_cores.cend(), - std::back_inserter(logical_storage_cores), - [&grid_size](RelativeCoreCoord rel_coord) { return get_core_coord_from_relative(rel_coord, grid_size); }); - return logical_storage_cores; + return core_desc.logical_storage_cores; } -inline CoreCoord get_compute_grid_size( +inline const CoreCoord& get_compute_grid_size( chip_id_t device_id, const uint8_t num_hw_cqs, const tt_metal::DispatchCoreConfig& dispatch_core_config) { const core_descriptor_t& core_desc = get_core_descriptor_config(device_id, num_hw_cqs, dispatch_core_config); return core_desc.compute_grid_size; @@ -125,50 +110,14 @@ inline CoreCoord get_compute_grid_size( inline const std::vector& get_logical_compute_cores( chip_id_t device_id, const uint8_t num_hw_cqs, const tt_metal::DispatchCoreConfig& dispatch_core_config) { - static std::unordered_map>> - logical_compute_cores_by_device; - static CoreType previous_dispatch_core_type; - if (previous_dispatch_core_type != dispatch_core_config.get_core_type()) { - logical_compute_cores_by_device.clear(); - previous_dispatch_core_type = dispatch_core_config.get_core_type(); - } - auto& logical_compute_cores_by_cq = logical_compute_cores_by_device[device_id]; - if (auto it = logical_compute_cores_by_cq.find(num_hw_cqs); it != logical_compute_cores_by_cq.end()) { - return it->second; - } - CoreCoord grid_size = tt::Cluster::instance().get_soc_desc(device_id).worker_grid_size; - std::vector& logical_compute_cores = logical_compute_cores_by_cq[num_hw_cqs]; const core_descriptor_t& core_desc = get_core_descriptor_config(device_id, num_hw_cqs, dispatch_core_config); - std::transform( - core_desc.relative_compute_cores.cbegin(), - core_desc.relative_compute_cores.cend(), - std::back_inserter(logical_compute_cores), - [&grid_size](RelativeCoreCoord rel_coord) { return get_core_coord_from_relative(rel_coord, grid_size); }); - return logical_compute_cores; + return core_desc.logical_compute_cores; } inline const std::vector& get_logical_dispatch_cores( chip_id_t device_id, const uint8_t num_hw_cqs, const tt_metal::DispatchCoreConfig& dispatch_core_config) { - static std::unordered_map>> - logical_dispatch_cores_by_device; - static CoreType previous_dispatch_core_type; - if (previous_dispatch_core_type != dispatch_core_config.get_core_type()) { - logical_dispatch_cores_by_device.clear(); - previous_dispatch_core_type = dispatch_core_config.get_core_type(); - } - auto& logical_dispatch_cores_by_cq = logical_dispatch_cores_by_device[device_id]; - if (auto it = logical_dispatch_cores_by_cq.find(num_hw_cqs); it != logical_dispatch_cores_by_cq.end()) { - return it->second; - } - CoreCoord grid_size = tt::Cluster::instance().get_soc_desc(device_id).worker_grid_size; - std::vector& logical_dispatch_cores = logical_dispatch_cores_by_cq[num_hw_cqs]; const core_descriptor_t& core_desc = get_core_descriptor_config(device_id, num_hw_cqs, dispatch_core_config); - std::transform( - core_desc.relative_dispatch_cores.cbegin(), - core_desc.relative_dispatch_cores.cend(), - std::back_inserter(logical_dispatch_cores), - [&grid_size](RelativeCoreCoord rel_coord) { return get_core_coord_from_relative(rel_coord, grid_size); }); - return logical_dispatch_cores; + return core_desc.logical_dispatch_cores; } } // namespace tt diff --git a/tt_metal/core_descriptors/wormhole_b0_80_arch.yaml b/tt_metal/core_descriptors/wormhole_b0_80_arch.yaml index 0de1cb8b8cdb..a7d4d3baa424 100644 --- a/tt_metal/core_descriptors/wormhole_b0_80_arch.yaml +++ b/tt_metal/core_descriptors/wormhole_b0_80_arch.yaml @@ -120,7 +120,7 @@ nebula_x1: 1: compute_with_storage_grid_range: # Logical only start and end [x, y] start: [0, 0] - end: [6, 9] + end: [6, 8] tg_compute_with_storage_grid_range: # Logical only start and end [x, y] start: [0, 0] @@ -146,7 +146,7 @@ nebula_x1: 2: compute_with_storage_grid_range: # Logical only start and end [x, y] start: [0, 0] - end: [6, 9] + end: [6, 8] tg_compute_with_storage_grid_range: # Logical only start and end [x, y] start: [0, 0] @@ -202,7 +202,7 @@ nebula_x2: 1: compute_with_storage_grid_range: # Logical only start and end [x, y] start: [0, 0] - end: [6, 8] + end: [6, 7] storage_cores: # Relative to grid of tensix cores [] @@ -216,7 +216,7 @@ nebula_x2: 2: compute_with_storage_grid_range: # Logical only start and end [x, y] start: [0, 0] - end: [6, 8] + end: [6, 7] storage_cores: # Relative to grid of tensix cores [] diff --git a/tt_metal/hw/inc/mod_div_lib.h b/tt_metal/hw/inc/mod_div_lib.h index 418e514eb3a2..a280634195f5 100644 --- a/tt_metal/hw/inc/mod_div_lib.h +++ b/tt_metal/hw/inc/mod_div_lib.h @@ -39,6 +39,13 @@ inline __attribute__((always_inline)) uint32_t fast_udiv_56(uint32_t n) { return (((uint64_t)n * 0x24924925) >> 32) >> 3; } +inline __attribute__((always_inline)) uint32_t fast_udiv_63(uint32_t n) { + // Uses embedding style magic number + // * fixed point 1/63 then shifting. + // https://web.archive.org/web/20190703172151/http://www.hackersdelight.org/magic.htm + return (((uint64_t)n * 0x82082083) >> 32) >> 5; +} + inline __attribute__((always_inline)) uint32_t fast_udiv_70(uint32_t n) { // Uses embedding style magic number // * fixed point 1/70 then shifting. diff --git a/tt_metal/impl/allocator/allocator.cpp b/tt_metal/impl/allocator/allocator.cpp index af3b4bb49fac..0e64623892c8 100644 --- a/tt_metal/impl/allocator/allocator.cpp +++ b/tt_metal/impl/allocator/allocator.cpp @@ -37,7 +37,7 @@ void validate_num_banks(uint32_t num_banks, const BufferType& buffer_type, bool // Dataflow API does not have a working implementation of generic modulo to determine bank_id for interleaved // address gen For non pow2 num banks, special cases need to be added to avoid falling back to generic // implementation. See https://github.com/tenstorrent/tt-metal/issues/3321 - std::unordered_set acceptable_num_non_pow2_mem_banks = {12, 56, 70, 80, 94, 124, 130, 140}; + std::unordered_set acceptable_num_non_pow2_mem_banks = {12, 56, 63, 70, 80, 94, 124, 130, 140}; bool custom_mod_bank_id_calculation_exists = acceptable_num_non_pow2_mem_banks.count(num_banks) > 0; bool valid_num_banks = (is_pow2_num_banks or custom_mod_bank_id_calculation_exists or doesnt_support_interleaved); if (not valid_num_banks) { diff --git a/tt_metal/impl/device/device.cpp b/tt_metal/impl/device/device.cpp index 57f681cd5f60..899ceeae588b 100644 --- a/tt_metal/impl/device/device.cpp +++ b/tt_metal/impl/device/device.cpp @@ -2983,7 +2983,20 @@ bool Device::initialize(const uint8_t num_hw_cqs, size_t l1_small_size, size_t t this->using_fast_dispatch = false; this->num_hw_cqs_ = num_hw_cqs; constexpr uint32_t harvesting_map_bits = 12; - this->build_key_ = ((uint32_t)this->num_hw_cqs_ << harvesting_map_bits); + constexpr uint32_t num_hw_cq_bits = 8; + constexpr uint32_t dispatch_core_axis_bits = 1; + constexpr uint32_t dispatch_core_type_bits = 1; + static_assert(dispatch_core_manager::MAX_NUM_HW_CQS <= (1 << num_hw_cq_bits)); + static_assert(static_cast(DispatchCoreAxis::COUNT) <= (1 << dispatch_core_axis_bits)); + static_assert(static_cast(DispatchCoreType::COUNT) <= (1 << dispatch_core_type_bits)); + static_assert(harvesting_map_bits + num_hw_cq_bits + dispatch_core_axis_bits + dispatch_core_type_bits <= sizeof(this->build_key_) * CHAR_BIT); + + // num_hw_cqs, dispatch_core_axis, dispatch_core_type all change the number of banks, so need to be part of the + // build key since we have defines based on number of banks. + const auto& dispatch_core_config = dispatch_core_manager::instance().get_dispatch_core_config(this->id_); + this->build_key_ = (static_cast(dispatch_core_config.get_dispatch_core_type()) << (harvesting_map_bits + num_hw_cq_bits + dispatch_core_axis_bits)) | + (static_cast(dispatch_core_config.get_dispatch_core_axis()) << (harvesting_map_bits + num_hw_cq_bits)) | + (static_cast(num_hw_cqs_) << harvesting_map_bits); if (not hal.is_coordinate_virtualization_enabled()) { // Coordinate virtualization is not enabled. For a single program, its associated binaries will vary across devices with different cores harvested. this->build_key_ = (this->build_key_) | tt::Cluster::instance().get_harvesting_mask(this->id()); diff --git a/tt_metal/impl/dispatch/dispatch_core_common.hpp b/tt_metal/impl/dispatch/dispatch_core_common.hpp index 76fee394a1bd..dd621b5ab3f1 100644 --- a/tt_metal/impl/dispatch/dispatch_core_common.hpp +++ b/tt_metal/impl/dispatch/dispatch_core_common.hpp @@ -7,7 +7,7 @@ #include "common/core_descriptor.hpp" #include "tt_metal/common/core_coord.hpp" #include "tt_metal/llrt/get_platform_architecture.hpp" -#include +#include "tt_metal/tt_stl/reflection.hpp" namespace tt::tt_metal { @@ -28,15 +28,9 @@ enum DispatchWorkerType : uint32_t { COUNT = 13 }; -enum DispatchCoreType : uint32_t { - WORKER = 0, - ETH = 1, -}; +enum class DispatchCoreType : uint32_t { WORKER, ETH, COUNT }; -enum class DispatchCoreAxis { - ROW, - COL, -}; +enum class DispatchCoreAxis { ROW, COL, COUNT }; class DispatchCoreConfig { private: @@ -55,6 +49,9 @@ class DispatchCoreConfig { DispatchCoreConfig(DispatchCoreType type, DispatchCoreAxis axis) : type_(type), axis_(axis) {} + static constexpr auto attribute_names = std::forward_as_tuple("type", "axis"); + const auto attribute_values() const { return std::forward_as_tuple(this->type_, this->axis_); } + CoreType get_core_type() const { switch (type_) { case DispatchCoreType::WORKER: return CoreType::WORKER; @@ -75,3 +72,14 @@ class DispatchCoreConfig { }; } // namespace tt::tt_metal + +namespace std { + +template <> +struct hash { + std::size_t operator()(const tt::tt_metal::DispatchCoreConfig& dispatch_core_config) const { + return tt::stl::hash::hash_objects_with_default_seed(dispatch_core_config.attribute_values()); + } +}; + +} // namespace std From 908044890d97638ff6c0925888fe61de442c7b13 Mon Sep 17 00:00:00 2001 From: Salar Hosseini <159165450+skhorasganiTT@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:32:13 -0500 Subject: [PATCH 67/87] [skip ci] [LLM tech report] Add accuracy evaluation and debugging sections (#15190) --- tech_reports/LLMs/llms.md | 40 +++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/tech_reports/LLMs/llms.md b/tech_reports/LLMs/llms.md index 5caf68bad64d..5ead06620327 100644 --- a/tech_reports/LLMs/llms.md +++ b/tech_reports/LLMs/llms.md @@ -1012,10 +1012,42 @@ For performance work async mode should always be enabled. For debugging it can b - 1d, 2d, dram-sharded, ... - Implicitly padding weights in program config for matmuls ### 4.5 Accuracy - - How we measure it (PCC, perplexity, top-1/top-5, end-user tests, benchmarking) - - How much PCC is enough? Rules of thumb. - - Accuracy tests - - Debugging PCC issues + +While we work on maximizing the performance of large language models on Tenstorrent hardware, we must also ensure that the models are functionally correct and that they produce outputs of the expected quality. The subsections below will describe our methods for evaluating the accuracy (also referred to as functionality or correctness for our purposes) of a given model and how to debug issues pertaining to this. + +#### Accuracy Testing + +Below is a list of metrics that are used when evaluating accuracy: +- **Pearson Correlation Coefficient (PCC)**: A measure of the linear relationship between two variables, where a PCC of 1 indicates a perfect positive correlation, and a PCC of 0 indicates no linear correlation. +- **Top-1/5 accuracy**: A measure of how often the correct token appears as the Top-1/5 most likely tokens predicted by the model at each step in a sequence. +- **Perplexity**: Measures how well the LLM predicts the text in the dataset, and is computed as $e^{\text{(avg negative log likelihood)}}$. +- **Human ocular evaluation**: Manual assessment of the quality, coherence, and relevance of the text generated by a LLM. +- **Specialized benchmark eval scores**: Metrics that evaluate specific capabilities of LLMs, such as MMLU for multitask language understanding, or BIG-bench for diverse general knowledge tasks. + +In order to thoroughly test the accuracy of a model, a bottom up approach is taken such that sub-modules of the model are tested all the way up to the full token generation. +- **Sub-module unit tests**: Each sub-module of the model should have its own test. For example, the [llama3 models](https://github.com/tenstorrent/tt-metal/tree/main/models/demos/llama3) have a separate [MLP test](https://github.com/tenstorrent/tt-metal/blob/main/models/demos/llama3/tests/test_llama_mlp.py), [attention test](https://github.com/tenstorrent/tt-metal/blob/main/models/demos/llama3/tests/test_llama_attention.py), and [decoder layer test](https://github.com/tenstorrent/tt-metal/blob/main/models/demos/llama3/tests/test_llama_decoder.py). For each of these tests, the outputs produced by the TT implementation of the model are compared against those of the original reference model (typically from Hugging Face) on CPU for a small set of inputs. A good rule of thumb is that the MLP, attention, and other small sub-modules should have a PCC of ~0.999, while a PCC of ~0.998 would be reasonable for a full decoder layer. +- **Model-level unit tests**: In addition to the sub-module unit tests, there should also be unit tests for a full layer of the model with all sub-modules, and the full model comprising of all layers. For example, the [llama3 model test](https://github.com/tenstorrent/tt-metal/blob/main/models/demos/llama3/tests/test_llama_model.py) runs 1 or many layers of the model over multiple iterations and checks the PCC against the reference model. A rule of thumb is that the full model PCC should be approximately ~0.99. +- **Dataset evaluation**: Once a model has been brought up with sufficient accuracy on the smaller unit tests, it should be tested on a larger set of prompts such as a full dataset or a subset of it. For example, the [Falcon7b perplexity test](https://github.com/tenstorrent/tt-metal/blob/main/models/demos/falcon7b_common/tests/perplexity/test_perplexity_falcon.py) loads a subset of the [WikiText dataset](https://huggingface.co/datasets/Salesforce/wikitext) and computes several metrics (including perplexity and top-1/5 accuracy) for evaluating the TT model with respect to the ground truth from the dataset. The results of these metrics should be comparable (e.g. within a couple percentage points of difference) to those obtained from running the evaluation with the reference model on CPU / GPU. + +#### Debugging Accuracy + +If during model bringup or optimization it is observed that the model outputs do not seem reasonable or any of the evaluations above are failing, the following steps can be taken to debug the accuracy: +1. Locate the smallest module test that is failing. The fewer the number of operations that could be causing the issue, the easier it will be to debug the root cause. In most cases, the issue should be able to be found using a 1 layer or submodule test. + - If the submodule and 1 layer tests are all passing with high PCC, some possible explanations are that 1) there is some corruption of data happening when executing multiple layers or 2) the failure is specific to a certain distribution of input data. + - If the dataset evaluation or a human occular (qualitative) evaluation is failing while the unit tests are passing, some possible explanations are that 1) there has not been sufficient testing of consecutive token generations in the unit tests, or 2) the PCC targets in the unit tests are too low. +2. Once the smallest failing test has been found, it may be required to step through individual operations in the model and compare their outputs against that of the reference model. This can be achieved by manually setting breakpoints in the TT model execution and CPU model execution and comparing the outputs, or by storing intermediate outputs to files or intermediate variables within the model itself to compare once both models have executed. + +For the operations under suspicion, some possible things to try could be: +- Use higher precision dataformats or math fidelities (e.g. HiFi vs LoFi) +- Convert inputs and outputs to DRAM interleaved so that the problematic op(s) read/write to DRAM (as opposed to L1 or sharded) +- Remove custom program configs and try the ttnn defaults +- If using CCL operations, verify that the reduction dimensions are appropriately set (particularly for 2D weight sharding) +- If loading cached weights which may have had their memory configurations modified, try disabling loading from caches (or regenerating them) to ensure the weights are generated from the torch tensors +- If using sharded tensors, ensure that the sharding configurations of the producer and consumer ops match +- Verify that the reference model does not have any bugs (i.e. check if there were any recent fixes for the reference model on GitHub/HuggingFace) + +In some cases, it may be possible that the issue is not with the model and that there is a bug with a ttnn operation. If this is suspected, it should be verified using a unit test with the exact same input/output configurations and an issue should be filed to the tt-metal team. + ### 4.6 Performance Analysis ttnn performance has five components: From 03631ca6888c5cc26442f13e0318e4ad555b7e77 Mon Sep 17 00:00:00 2001 From: Brian Liu Date: Thu, 19 Dec 2024 02:20:40 +0000 Subject: [PATCH 68/87] #16165: Disabling test that depends on some machine state to pass - Test will fail if you reset machine and run independently --- .../ttnn/unit_tests/operations/eltwise/test_binary_composite.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ttnn/unit_tests/operations/eltwise/test_binary_composite.py b/tests/ttnn/unit_tests/operations/eltwise/test_binary_composite.py index 97cff73907d3..346c8549e981 100644 --- a/tests/ttnn/unit_tests/operations/eltwise/test_binary_composite.py +++ b/tests/ttnn/unit_tests/operations/eltwise/test_binary_composite.py @@ -990,6 +990,7 @@ def test_nei_ttnn(input_shapes, scalar, device): assert comp_pass +@pytest.mark.skip(reason="#16165: Test is broken if you run after individually.") @pytest.mark.parametrize( "input_shapes", ( From a41d357119cee5174e289b433b188f7722e85b83 Mon Sep 17 00:00:00 2001 From: Amruth Sandhupatla Date: Thu, 19 Dec 2024 11:51:46 -0500 Subject: [PATCH 69/87] enable dps ops for matmul (#15285) ### Ticket Link to Github Issue: https://github.com/tenstorrent/tt-metal/issues/15038 ### Problem description Provide context for the problem. Dps ops are not being generated in MLIR for matmul op. ### What's changed Describe the approach used to solve the problem. Summarize the changes made and its impact. Enabled std::optional for ourput tensor as seen for other dps supported ops such as Embedding & element-wise ops ### Checklist - [x] Post commit CI passes : https://github.com/tenstorrent/tt-metal/actions/runs/12204499266 - [x] Blackhole Post commit (if applicable) : https://github.com/tenstorrent/tt-metal/actions/runs/12204501225 - [x] Model regression CI testing passes (if applicable) : https://github.com/tenstorrent/tt-metal/actions/runs/12204503539 - [x] Device performance regression CI testing passes (if applicable) : https://github.com/tenstorrent/tt-metal/actions/runs/12204505818 - [x] New/Existing tests provide coverage for changes --------- Signed-off-by: Amruth Sandhupatla Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../ttnn/unit_tests/operations/test_linear.py | 48 +++++++++ .../ttnn/unit_tests/operations/test_matmul.py | 31 ++++++ .../device/all_gather_matmul_op.cpp | 7 +- .../device/all_gather_matmul_op.hpp | 3 +- .../operations/matmul/device/matmul_op.cpp | 100 +++++++++++++++--- .../operations/matmul/device/matmul_op.hpp | 21 ++-- ttnn/cpp/ttnn/operations/matmul/matmul.cpp | 24 +++-- ttnn/cpp/ttnn/operations/matmul/matmul.hpp | 9 +- .../ttnn/operations/matmul/matmul_pybind.cpp | 20 +++- 9 files changed, 226 insertions(+), 37 deletions(-) diff --git a/tests/ttnn/unit_tests/operations/test_linear.py b/tests/ttnn/unit_tests/operations/test_linear.py index 97df1e7b8ef2..29956039dc17 100644 --- a/tests/ttnn/unit_tests/operations/test_linear.py +++ b/tests/ttnn/unit_tests/operations/test_linear.py @@ -262,3 +262,51 @@ def test_bloom_ff2_linear(device): ) assert ttnn.pearson_correlation_coefficient(torch_output, output) >= 0.9992 + + +@pytest.mark.parametrize("batch_size", [1, 8]) +@pytest.mark.parametrize("m_size", [32, 64]) +@pytest.mark.parametrize("k_size", [1024, 2048]) +@pytest.mark.parametrize("n_size", [1024, 2048]) +@pytest.mark.parametrize("activation", [None, "relu"]) +def test_linear_by_passing_in_1D_systolic_array_program_config_and_optional_outout_tensor( + device, batch_size, m_size, k_size, n_size, activation +): + torch.manual_seed(0) + + torch_input_tensor_a = torch.randn((batch_size, m_size, k_size), dtype=torch.bfloat16) + torch_input_tensor_b = torch.randn((k_size, n_size), dtype=torch.bfloat16) + torch_output_tensor = torch_input_tensor_a @ torch_input_tensor_b + if activation == "relu": + torch_output_tensor = torch.relu(torch_output_tensor) + + input_tensor_a = ttnn.from_torch(torch_input_tensor_a, layout=ttnn.TILE_LAYOUT, device=device) + input_tensor_b = ttnn.from_torch(torch_input_tensor_b, layout=ttnn.TILE_LAYOUT, device=device) + + torch_opt_output_tensor = torch.zeros_like(torch_output_tensor) + optional_output_tensor = ttnn.from_torch(torch_opt_output_tensor, layout=ttnn.TILE_LAYOUT, device=device) + + output_tensor = ttnn.linear( + input_tensor_a, + input_tensor_b, + activation=activation, + core_grid=device.core_grid, + ) + + output_tensor = ttnn.to_torch(output_tensor) + + ttnn.linear( + input_tensor_a, + input_tensor_b, + activation=activation, + optional_output_tensor=optional_output_tensor, + core_grid=device.core_grid, + ) + + optional_output_tensor = ttnn.to_torch(optional_output_tensor) + + assert len(output_tensor.shape) == len(torch_output_tensor.shape) == len(optional_output_tensor.shape) + assert output_tensor.shape == torch_output_tensor.shape == optional_output_tensor.shape + assert_with_pcc(torch_output_tensor, output_tensor, 0.997) + assert_with_pcc(torch_output_tensor, optional_output_tensor, 0.997) + assert_with_pcc(optional_output_tensor, output_tensor, 0.997) diff --git a/tests/ttnn/unit_tests/operations/test_matmul.py b/tests/ttnn/unit_tests/operations/test_matmul.py index 3fcb58c528c2..1f69c3905138 100644 --- a/tests/ttnn/unit_tests/operations/test_matmul.py +++ b/tests/ttnn/unit_tests/operations/test_matmul.py @@ -2080,3 +2080,34 @@ def test_interleaved_input_sharded_output_matmul(device): output3 = ttnn.matmul(input_tensor_a, input_tensor_b, memory_config=out_mem_config) output_tensor = ttnn.to_torch(output3) assert_with_pcc(torch_output_tensor, output_tensor, pcc=pcc) + + +@pytest.mark.parametrize( + "n_size, c, m, k, n", + [ + (1, 1, 1024, 64, 512), + ], +) +def test_optional_output_argument(device, n_size, c, m, k, n): + torch.manual_seed(0) + + torch_input_tensor_a = torch.rand((n_size, c, m, k), dtype=torch.bfloat16) + torch_input_tensor_b = torch.rand((n_size, c, k, n), dtype=torch.bfloat16) + torch_output_tensor = torch.matmul(torch_input_tensor_a, torch_input_tensor_b) + torch_opt_output_tensor = torch.zeros_like(torch_output_tensor) + + input_tensor_a = ttnn.from_torch(torch_input_tensor_a, layout=ttnn.TILE_LAYOUT, device=device) + input_tensor_b = ttnn.from_torch(torch_input_tensor_b, layout=ttnn.TILE_LAYOUT, device=device) + optional_output_tensor = ttnn.from_torch(torch_opt_output_tensor, layout=ttnn.TILE_LAYOUT, device=device) + + output = ttnn.matmul(input_tensor_a, input_tensor_b) + output = ttnn.to_torch(output) + + ttnn.matmul(input_tensor_a, input_tensor_b, optional_output_tensor=optional_output_tensor) + optional_output_tensor = ttnn.to_torch(optional_output_tensor) + + assert len(output.shape) == len(torch_output_tensor.shape) == len(optional_output_tensor.shape) + assert output.shape == torch_output_tensor.shape == optional_output_tensor.shape + assert_with_pcc(torch_output_tensor, output, 0.999) + assert_with_pcc(torch_output_tensor, optional_output_tensor, 0.999) + assert_with_pcc(output, optional_output_tensor, 0.999) diff --git a/ttnn/cpp/ttnn/operations/experimental/ccl/all_gather_matmul/device/all_gather_matmul_op.cpp b/ttnn/cpp/ttnn/operations/experimental/ccl/all_gather_matmul/device/all_gather_matmul_op.cpp index 96b80a12e3b9..9016ceb4835c 100644 --- a/ttnn/cpp/ttnn/operations/experimental/ccl/all_gather_matmul/device/all_gather_matmul_op.cpp +++ b/ttnn/cpp/ttnn/operations/experimental/ccl/all_gather_matmul/device/all_gather_matmul_op.cpp @@ -20,7 +20,8 @@ namespace experimental { void AllGatherMatmul::validate( const std::vector& input_tensors, - const std::vector>& optional_input_tensors) const { + const std::vector>& optional_input_tensors, + const std::vector>& optional_output_tensors) const { TT_ASSERT( input_tensors.size() == 4, "AllGatherMatmul requires 4 input tensors: [input, weight, all_gather_output, datacopy_output]"); @@ -33,7 +34,7 @@ void AllGatherMatmul::validate( this->all_gather_struct.validate({input_tensor}); // Matmul validate. - this->matmul_struct.validate({all_gather_output_tensor, weight_tensor}, optional_input_tensors); + this->matmul_struct.validate({all_gather_output_tensor, weight_tensor}, optional_input_tensors, {}); // All Gather Matmul validate TT_FATAL(this->all_gather_struct.dim == 3, "AllGatherMatmul requires dim=3 for the AllGather operaitons."); @@ -73,7 +74,7 @@ std::vector AllGatherMatmul::compute_output_specs(const std::v // Matmul shape ttnn::TensorSpec matmul_output_specs = - this->matmul_struct.compute_output_specs({input_tensors[1], input_tensors[2]})[0]; + this->matmul_struct.compute_output_specs({input_tensors[1], input_tensors[2]}, {})[0]; return {all_gather_output_shape, matmul_output_specs, datacopy_output_shape}; } diff --git a/ttnn/cpp/ttnn/operations/experimental/ccl/all_gather_matmul/device/all_gather_matmul_op.hpp b/ttnn/cpp/ttnn/operations/experimental/ccl/all_gather_matmul/device/all_gather_matmul_op.hpp index c8af6cc9dd75..8afef89a6aa3 100644 --- a/ttnn/cpp/ttnn/operations/experimental/ccl/all_gather_matmul/device/all_gather_matmul_op.hpp +++ b/ttnn/cpp/ttnn/operations/experimental/ccl/all_gather_matmul/device/all_gather_matmul_op.hpp @@ -41,7 +41,8 @@ struct AllGatherMatmul { /* General */ void validate( const std::vector& input_tensors, - const std::vector>& optional_input_tensors) const; + const std::vector>& optional_input_tensors, + const std::vector>& optional_output_tensors = {std::nullopt}) const; std::vector compute_output_specs(const std::vector& input_tensors) const; std::vector create_output_tensors(const std::vector& input_tensors) const; operation::ProgramWithCallbacks create_program( diff --git a/ttnn/cpp/ttnn/operations/matmul/device/matmul_op.cpp b/ttnn/cpp/ttnn/operations/matmul/device/matmul_op.cpp index f3d0b732e62b..19535b430a84 100644 --- a/ttnn/cpp/ttnn/operations/matmul/device/matmul_op.cpp +++ b/ttnn/cpp/ttnn/operations/matmul/device/matmul_op.cpp @@ -1003,7 +1003,10 @@ namespace operations { namespace matmul { Matmul create_matmul_struct( - const Tensor& input_tensor_a, const Tensor& input_tensor_b, const struct Matmul& parameters) { + const Tensor& input_tensor_a, + const Tensor& input_tensor_b, + const struct Matmul& parameters, + const std::vector>& optional_output_tensors) { auto arch = input_tensor_a.device()->arch(); const bool has_user_grid = parameters.user_core_coord.has_value(); const bool has_program_config = parameters.program_config.has_value(); @@ -1022,16 +1025,48 @@ Matmul create_matmul_struct( bool broadcast_batch = parameters.bcast_batch.value_or(get_broadcast_batch(input_tensor_a, input_tensor_b, parameters.program_config)); TT_FATAL(!(has_user_grid && has_program_config), "Cannot use both user core grid/coordinates and a program config"); + + const bool is_optional_output_tensor = + !optional_output_tensors.empty() && optional_output_tensors.at(0).has_value(); + std::optional output_dtype = parameters.output_dtype; + MemoryConfig output_mem_config = parameters.output_mem_config; + + if (is_optional_output_tensor) { + const auto& optional_output_tensor = optional_output_tensors.at(0); + if (output_mem_config == operation::DEFAULT_OUTPUT_MEMORY_CONFIG) { + output_mem_config = optional_output_tensor->memory_config(); + } else { + TT_FATAL( + optional_output_tensor->memory_config() == output_mem_config, + "Memory config mismatch between optional output tensor {} & output tensor {}", + optional_output_tensor->memory_config(), + output_mem_config); + } + + if (output_dtype.has_value()) { + TT_FATAL( + optional_output_tensor->get_dtype() == output_dtype.value(), + "Type mismatch between optional output tensor {} & output tensor {}", + optional_output_tensor->get_dtype(), + output_dtype.value()); + } else { + output_dtype = optional_output_tensor->get_dtype(); + } + } else { + if (!output_dtype.has_value()) { + output_dtype = input_tensor_a.get_dtype(); + } + } + auto in0_tile = input_tensor_a.get_tensor_spec().tile(); auto in1_tile = input_tensor_b.get_tensor_spec().tile(); - tt::tt_metal::Tile output_tile = - get_output_tile(parameters.output_mem_config, in0_tile, in1_tile, parameters.output_tile); + tt::tt_metal::Tile output_tile = get_output_tile(output_mem_config, in0_tile, in1_tile, parameters.output_tile); return Matmul{ parameters.program_config, broadcast_batch, - parameters.output_mem_config, - parameters.output_dtype.value_or(input_tensor_a.get_dtype()), + output_mem_config, + output_dtype, kernel_config_val, parameters.untilize_out, parameters.user_core_coord, @@ -1047,9 +1082,11 @@ Tensor matmul( const Tensor& input_tensor_b, const std::optional& bias, const struct Matmul& parameters, - const uint8_t queue_id) { + const uint8_t queue_id, + const std::optional& optional_output_tensor) { std::vector> optional_input_tensors = {}; std::vector output_tensors; + if (bias.has_value()) { optional_input_tensors.push_back(bias.value()); output_tensors = { @@ -1068,21 +1105,23 @@ Tensor matmul( const auto& input_tensor_b = input_tensors.at(1); return operation::run( - create_matmul_struct(input_tensor_a, input_tensor_b, parameters), + create_matmul_struct(input_tensor_a, input_tensor_b, parameters, optional_output_tensors), {input_tensor_a, input_tensor_b}, optional_input_tensors, - {}, + optional_output_tensors, queue_id); }, {input_tensor_a, input_tensor_b}, output_tensors, - optional_input_tensors); + optional_input_tensors, + {optional_output_tensor}); return output_tensors.at(0); } void Matmul::validate( const std::vector& input_tensors, - const std::vector>& optional_input_tensors) const { + const std::vector>& optional_input_tensors, + const std::vector>& optional_output_tensors) const { TT_FATAL(input_tensors.size() == 2, "Error"); const auto& input_tensor_a = input_tensors.at(0); const auto& input_tensor_b = input_tensors.at(1); @@ -1113,6 +1152,28 @@ void Matmul::validate( a_shape[-1], b_shape[-2]); + const bool is_optional_output_tensor = !optional_output_tensors.empty() && optional_output_tensors.at(0).has_value(); + if (is_optional_output_tensor) { + const auto& optional_output_tensor_c = optional_output_tensors.at(0); + const auto& optional_output_tensor_shape = optional_output_tensor_c->get_logical_shape(); + const auto output_tensor_spec = this->compute_output_specs(input_tensors, {}).at(0); + TT_FATAL( + optional_output_tensor_shape == output_tensor_spec.logical_shape(), + "Shape of Optional Output Tensor {} doesnt match Output Tensor {}", + optional_output_tensor_shape, + output_tensor_spec.logical_shape()); + TT_FATAL( + optional_output_tensor_c->get_dtype() == this->output_dtype.value(), + "Type mismatch between optional output tensor {} & output tensor {}", + optional_output_tensor_c->get_dtype(), + this->output_dtype.value()); + TT_FATAL( + optional_output_tensor_c->memory_config() == this->output_mem_config, + "Memory config mismatch between optional output tensor {} & output tensor {}", + optional_output_tensor_c->memory_config(), + this->output_mem_config); + } + TT_FATAL(this->bcast_batch.has_value(), "Error: bcast_batch field should have been automatically populated"); TT_FATAL(this->output_tile.has_value(), "Error: output_tile field should have been automatically populated"); if (this->bcast_batch.value()) { @@ -1562,7 +1623,18 @@ void Matmul::validate( chosen_program_config); } -std::vector Matmul::compute_output_specs(const std::vector& input_tensors) const { +std::vector Matmul::compute_output_specs( + const std::vector& input_tensors, const std::vector>& optional_output_tensors) const { + TT_FATAL( + optional_output_tensors.size() <= 1, + "None or One Optional output tensor can be passed when accessing it for computing Matmul's output specs"); + + const bool is_optional_output_tensor = !optional_output_tensors.empty() && optional_output_tensors.at(0).has_value(); + + if (is_optional_output_tensor) { + return {optional_output_tensors.at(0)->get_tensor_spec()}; + } + const auto& input_tensor_a = input_tensors.at(0); const auto& input_tensor_b = input_tensors.at(1); const ttnn::SimpleShape input_shape_a = input_tensor_a.get_logical_shape(); @@ -1587,6 +1659,7 @@ std::vector Matmul::compute_output_specs(const std::vectoroutput_tile.value(); auto tile_width_ratio = output_tile.get_tile_shape()[1] / in1_tile_shape[1]; auto output_layout = this->untilize_out ? Layout::ROW_MAJOR : Layout::TILE; + TT_FATAL(this->output_dtype.has_value(), "Error"); if (this->output_mem_config.is_sharded()) { MatmulProgramConfig chosen_program_config = get_program_config(input_tensor_a, input_tensor_b, this); @@ -1733,8 +1806,9 @@ std::vector Matmul::compute_output_specs(const std::vector Matmul::create_output_tensors(const std::vector& input_tensors) const { - return operation::default_create_output_tensors(*this, input_tensors, {}); +std::vector Matmul::create_output_tensors( + const std::vector& input_tensors, const std::vector>& optional_output_tensors) const { + return operation::default_create_output_tensors(*this, input_tensors, optional_output_tensors); } operation::ProgramWithCallbacks Matmul::create_program( diff --git a/ttnn/cpp/ttnn/operations/matmul/device/matmul_op.hpp b/ttnn/cpp/ttnn/operations/matmul/device/matmul_op.hpp index 8815c6b52052..5324a0c2de2a 100644 --- a/ttnn/cpp/ttnn/operations/matmul/device/matmul_op.hpp +++ b/ttnn/cpp/ttnn/operations/matmul/device/matmul_op.hpp @@ -177,10 +177,15 @@ struct Matmul { void validate( const std::vector& input_tensors, - const std::vector>& optional_input_tensors) const; - std::vector compute_output_specs(const std::vector& input_tensors) const; - std::vector create_output_tensors(const std::vector& input_tensors) const; - tt::tt_metal::operation::ProgramWithCallbacks create_program( + const std::vector>& optional_input_tensors, + const std::vector>& optional_output_tensors = {std::nullopt}) const; + std::vector compute_output_specs( + const std::vector& input_tensors, + const std::vector>& optional_output_tensors = {std::nullopt}) const; + std::vector create_output_tensors( + const std::vector& input_tensors, + const std::vector>& optional_output_tensors = {std::nullopt}) const; + operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, const std::vector>& optional_input_tensors, std::vector& output_tensors) const; @@ -191,7 +196,10 @@ struct Matmul { }; Matmul create_matmul_struct( - const Tensor& input_tensor_a, const Tensor& input_tensor_b, const struct Matmul& parameters); + const Tensor& input_tensor_a, + const Tensor& input_tensor_b, + const struct Matmul& parameters, + const std::vector>& optional_output_tensors = {std::nullopt}); operation::ProgramWithCallbacks matmul_multi_core_reuse_mcast_1d_optimized_helper( tt::tt_metal::Program& program, @@ -221,7 +229,8 @@ Tensor matmul( const Tensor& input_tensor_b, const std::optional& bias = std::nullopt, const struct Matmul& parameters = Matmul{}, - const uint8_t queue_id = 0); + const uint8_t queue_id = 0, + const std::optional& optional_output_tensor = std::nullopt); } // namespace matmul diff --git a/ttnn/cpp/ttnn/operations/matmul/matmul.cpp b/ttnn/cpp/ttnn/operations/matmul/matmul.cpp index c160725ae4dd..db4e5c1e6b90 100644 --- a/ttnn/cpp/ttnn/operations/matmul/matmul.cpp +++ b/ttnn/cpp/ttnn/operations/matmul/matmul.cpp @@ -43,7 +43,8 @@ ttnn::Tensor bound_matmul( const ttnn::Tensor& input_tensor_b, const std::optional& bias, const struct Matmul& parameters, - const uint8_t& queue_id) { + const uint8_t& queue_id, + std::optional& optional_output_tensor) { const auto& input_tensor_a_adjusted = parameters.transpose_a ? ttnn::transpose(input_tensor_a, -1, -2, input_tensor_a.memory_config()) : input_tensor_a; @@ -76,8 +77,13 @@ ttnn::Tensor bound_matmul( } } - auto output_tensor = - matmul(input_tensor_a_adjusted, input_tensor_b_adjusted, post_process_bias ? std::nullopt : bias, parameters); + auto output_tensor = matmul( + input_tensor_a_adjusted, + input_tensor_b_adjusted, + post_process_bias ? std::nullopt : bias, + parameters, + 0, + optional_output_tensor = optional_output_tensor); if (post_process_bias) { output_tensor = ttnn::add(output_tensor, bias.value(), std::nullopt, parameters.output_mem_config); @@ -110,7 +116,8 @@ Tensor MatmulOperation::invoke( const std::optional& activation, const std::optional compute_kernel_config, const std::optional core_grid, - const std::optional& output_tile) { + const std::optional& output_tile, + std::optional optional_output_tensor) { std::optional user_core_coord; if (core_grid.has_value()) { user_core_coord = CoreCoord(core_grid->x, core_grid->y); @@ -133,7 +140,8 @@ Tensor MatmulOperation::invoke( transpose_a, transpose_b, output_tile}, - /*queue_id=*/0); + /*queue_id=*/0, + optional_output_tensor); } Tensor LinearOperation::invoke( @@ -148,7 +156,8 @@ Tensor LinearOperation::invoke( const std::optional& activation, const std::optional compute_kernel_config, const std::optional core_grid, - const std::optional& output_tile) { + const std::optional& output_tile, + std::optional optional_output_tensor) { std::optional user_core_coord; if (core_grid.has_value()) { user_core_coord = CoreCoord(core_grid->x, core_grid->y); @@ -173,7 +182,8 @@ Tensor LinearOperation::invoke( transpose_a, transpose_b, output_tile}, - /*queue_id=*/0); + /*queue_id=*/0, + optional_output_tensor); } } // namespace matmul diff --git a/ttnn/cpp/ttnn/operations/matmul/matmul.hpp b/ttnn/cpp/ttnn/operations/matmul/matmul.hpp index 1db1436c7b39..b35c28219e38 100644 --- a/ttnn/cpp/ttnn/operations/matmul/matmul.hpp +++ b/ttnn/cpp/ttnn/operations/matmul/matmul.hpp @@ -33,7 +33,8 @@ ttnn::Tensor bound_matmul( const ttnn::Tensor& input_tensor_b, const std::optional& bias, const struct Matmul& parameters, - const uint8_t& queue_id); + const uint8_t& queue_id, + std::optional& optional_output_tensor); struct MatmulOperation { static Tensor invoke( @@ -47,7 +48,8 @@ struct MatmulOperation { const std::optional& activation = std::nullopt, const std::optional compute_kernel_config = std::nullopt, const std::optional core_grid = std::nullopt, - const std::optional& output_tile = std::nullopt); + const std::optional& output_tile = std::nullopt, + std::optional optional_output_tensor = std::nullopt); }; struct LinearOperation { @@ -63,7 +65,8 @@ struct LinearOperation { const std::optional& activation = std::nullopt, const std::optional compute_kernel_config = std::nullopt, const std::optional core_grid = std::nullopt, - const std::optional& output_tile = std::nullopt); + const std::optional& output_tile = std::nullopt, + std::optional optional_output_tensor = std::nullopt); }; } // namespace matmul diff --git a/ttnn/cpp/ttnn/operations/matmul/matmul_pybind.cpp b/ttnn/cpp/ttnn/operations/matmul/matmul_pybind.cpp index de6d7348eb24..2bc4499ce172 100644 --- a/ttnn/cpp/ttnn/operations/matmul/matmul_pybind.cpp +++ b/ttnn/cpp/ttnn/operations/matmul/matmul_pybind.cpp @@ -276,6 +276,11 @@ void py_module(py::module& module) { compute_kernel_config (ttnn.DeviceComputeKernelConfig): the compute kernel configuration for the matmul operation. Defaults to `None`. core_grid (ttnn.CoreGrid): the grid on which to distribute the sharded tensor on (writes to the cores L1s). Defaults to `None`. output_tile (List of [int], optional): Specifies the output tile configuration. Defaults to `None`. + optional_output_tensor (ttnn.Tensor) : User provided on-device output tensor where the result of matmul is to be written. + If optional output tensor is specified, then dtype and memory config need to be checked as follows: + if they are default then they should be set based on optional output tensor + if the are not default then they should be compared and if there is a difference an error is reported + Returns: ttnn.Tensor: the output tensor. @@ -330,7 +335,8 @@ void py_module(py::module& module) { const std::optional& activation, const std::optional compute_kernel_config, const std::optional core_grid, - const std::optional& output_tile) -> ttnn::Tensor { + const std::optional& output_tile, + std::optional& optional_output_tensor) -> ttnn::Tensor { return self( input_tensor_a, input_tensor_b, @@ -342,7 +348,8 @@ void py_module(py::module& module) { activation, compute_kernel_config, core_grid, - output_tile); + output_tile, + optional_output_tensor); }, py::arg("input_tensor_a"), py::arg("input_tensor_b"), @@ -356,6 +363,7 @@ void py_module(py::module& module) { py::arg("compute_kernel_config") = std::nullopt, py::arg("core_grid") = std::nullopt, py::arg("output_tile") = std::nullopt, + py::arg("optional_output_tensor") = std::nullopt, }); bind_registered_operation( @@ -381,6 +389,7 @@ void py_module(py::module& module) { compute_kernel_config (ttnn.DeviceComputeKernelConfig, optional): the compute kernel configuration for the matmul operation. Defaults to `None`. core_grid (ttnn.CoreGrid, optional): the grid on which to distribute the sharded tensor on (writes to the cores L1s). Defaults to `None`. output_tile (List of [int], optional): Specifies the output tile configuration. Defaults to `None`. + optional_output_tensor (ttnn.Tensor) : User provided on-device output tensor where the result of linear is to be written. Returns: ttnn.Tensor: the output tensor. @@ -407,7 +416,8 @@ void py_module(py::module& module) { const std::optional& activation, const std::optional compute_kernel_config, const std::optional core_grid, - const std::optional& output_tile) -> ttnn::Tensor { + const std::optional& output_tile, + std::optional& optional_output_tensor) -> ttnn::Tensor { return self( input_tensor_a, input_tensor_b, @@ -420,7 +430,8 @@ void py_module(py::module& module) { activation, compute_kernel_config, core_grid, - output_tile); + output_tile, + optional_output_tensor); }, py::arg("input_tensor_a"), py::arg("input_tensor_b"), @@ -435,6 +446,7 @@ void py_module(py::module& module) { py::arg("compute_kernel_config") = std::nullopt, py::arg("core_grid") = std::nullopt, py::arg("output_tile") = std::nullopt, + py::arg("optional_output_tensor") = std::nullopt, }); } From ed9964a76a5f0daf3cadc2ad0c22f0be6807ab3c Mon Sep 17 00:00:00 2001 From: Andrew Fuller Date: Thu, 19 Dec 2024 12:10:21 -0500 Subject: [PATCH 70/87] Isolate tracy (#16161) ### Ticket #14001 ### Problem description Tracy is being referenced "behind the scenes" which breaks when things are shuffled around. ### What's changed Create a Tracy::TracyClient target that contains everything appropriate: * Headers always (even when disabled) * The #define to enable it, when enabled * compile/link flags as appropriate ### Checklist - [x] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12387386339 - [x] Device performance regression CI testing passes (if applicable) https://github.com/tenstorrent/tt-metal/actions/runs/12398730711 --- CMakeLists.txt | 9 +-------- cmake/tracy.cmake | 18 +++++++++++++----- tt_metal/CMakeLists.txt | 2 +- tt_metal/common/CMakeLists.txt | 2 ++ tt_metal/common/bfloat16.hpp | 2 +- tt_metal/common/bfloat4.hpp | 2 +- tt_metal/common/bfloat8.hpp | 2 +- tt_metal/common/blockfloat_common.hpp | 2 +- tt_metal/common/core_coord.cpp | 2 +- tt_metal/common/test_tiles.hpp | 2 +- tt_metal/common/utils.cpp | 2 +- tt_metal/common/work_split.cpp | 2 +- tt_metal/detail/CMakeLists.txt | 2 +- tt_metal/distributed/CMakeLists.txt | 2 +- tt_metal/impl/CMakeLists.txt | 1 + tt_metal/impl/device/device.cpp | 2 +- tt_metal/impl/dispatch/work_executor.hpp | 3 +-- tt_metal/impl/program/program.cpp | 2 +- tt_metal/jit_build/CMakeLists.txt | 2 +- tt_metal/jit_build/build.hpp | 2 +- tt_metal/llrt/CMakeLists.txt | 1 + tt_metal/llrt/tt_cluster.cpp | 2 +- tt_metal/tools/profiler/CMakeLists.txt | 2 +- tt_metal/tools/profiler/op_profiler.hpp | 4 ++-- tt_metal/tools/profiler/profiler.cpp | 2 +- tt_metal/tools/profiler/profiler.hpp | 4 ++-- tt_metal/tools/profiler/tt_metal_profiler.cpp | 2 +- tt_metal/tt_metal.cpp | 2 +- 28 files changed, 43 insertions(+), 39 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f929b62c9890..cbb0b4463122 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -253,18 +253,11 @@ add_link_options( if(ENABLE_CODE_TIMERS) add_compile_definitions(TT_ENABLE_CODE_TIMERS) endif() -if(ENABLE_TRACY) - add_compile_definitions(TRACY_ENABLE) - add_compile_options(-fno-omit-frame-pointer) - add_link_options(-rdynamic) -endif() +include(tracy) ############################################################################################################################ # Build subdirectories ############################################################################################################################ -if(ENABLE_TRACY) - include(tracy) -endif() add_subdirectory(tt_metal) add_subdirectory(ttnn) diff --git a/cmake/tracy.cmake b/cmake/tracy.cmake index f436d80dfd25..18f96a2f315f 100644 --- a/cmake/tracy.cmake +++ b/cmake/tracy.cmake @@ -1,17 +1,21 @@ # Built as outlined in Tracy documentation (pg.12) set(TRACY_HOME ${PROJECT_SOURCE_DIR}/tt_metal/third_party/tracy) +if(NOT ENABLE_TRACY) + # Stub Tracy::TracyClient to provide the headers which themselves provide stubs + add_library(TracyClient INTERFACE) + add_library(Tracy::TracyClient ALIAS TracyClient) + target_include_directories(TracyClient SYSTEM INTERFACE ${TRACY_HOME}/public) + return() +endif() + add_subdirectory(${TRACY_HOME}) + set_target_properties( TracyClient PROPERTIES EXCLUDE_FROM_ALL TRUE -) - -set_target_properties( - TracyClient - PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib" ARCHIVE_OUTPUT_DIRECTORY @@ -22,6 +26,10 @@ set_target_properties( "tracy" ) +target_compile_definitions(TracyClient PUBLIC TRACY_ENABLE) +target_compile_options(TracyClient PUBLIC -fno-omit-frame-pointer) +target_link_options(TracyClient PUBLIC -rdynamic) + # Our current fork of tracy does not have CMake support for these subdirectories # Once we update, we can change this include(ExternalProject) diff --git a/tt_metal/CMakeLists.txt b/tt_metal/CMakeLists.txt index 423edf77ff6d..2392de8f8c15 100644 --- a/tt_metal/CMakeLists.txt +++ b/tt_metal/CMakeLists.txt @@ -18,7 +18,7 @@ target_link_libraries( magic_enum fmt::fmt-header-only span - $<$:TracyClient> + Tracy::TracyClient PRIVATE profiler common diff --git a/tt_metal/common/CMakeLists.txt b/tt_metal/common/CMakeLists.txt index 24bb6ee63ea3..463cec72a179 100644 --- a/tt_metal/common/CMakeLists.txt +++ b/tt_metal/common/CMakeLists.txt @@ -22,6 +22,8 @@ target_link_libraries( Metalium::Metal::STL Metalium::Metal::LLRT umd::Firmware + PRIVATE + Tracy::TracyClient ) target_include_directories( diff --git a/tt_metal/common/bfloat16.hpp b/tt_metal/common/bfloat16.hpp index b332a9d2aeea..2068883bbb04 100644 --- a/tt_metal/common/bfloat16.hpp +++ b/tt_metal/common/bfloat16.hpp @@ -11,7 +11,7 @@ #include "tt_metal/common/assert.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" class bfloat16 { private: diff --git a/tt_metal/common/bfloat4.hpp b/tt_metal/common/bfloat4.hpp index dd02bf44f843..991a4ec21c2d 100644 --- a/tt_metal/common/bfloat4.hpp +++ b/tt_metal/common/bfloat4.hpp @@ -11,7 +11,7 @@ #include "tt_metal/common/assert.hpp" #include "tt_metal/common/tt_backend_api_types.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" #include "blockfloat_common.hpp" // TODO: empty struct to facilitate Tensor template logic. Reconsider how/why templating is supported in Tensor diff --git a/tt_metal/common/bfloat8.hpp b/tt_metal/common/bfloat8.hpp index 3bb1e064744a..d302cb6ac199 100644 --- a/tt_metal/common/bfloat8.hpp +++ b/tt_metal/common/bfloat8.hpp @@ -11,7 +11,7 @@ #include "tt_metal/common/assert.hpp" #include "tt_metal/common/tt_backend_api_types.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" #include "blockfloat_common.hpp" // TODO: empty struct to facilitate Tensor template logic. Reconsider how/why templating is supported in Tensor diff --git a/tt_metal/common/blockfloat_common.hpp b/tt_metal/common/blockfloat_common.hpp index acea3127c8b0..b29258fd9b8f 100644 --- a/tt_metal/common/blockfloat_common.hpp +++ b/tt_metal/common/blockfloat_common.hpp @@ -11,7 +11,7 @@ #include "tt_metal/common/assert.hpp" #include "tt_metal/common/tt_backend_api_types.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" #include "tt_metal/impl/tile/tile.hpp" inline uint8_t get_max_exp(const std::vector& vec, bool is_exp_a) { diff --git a/tt_metal/common/core_coord.cpp b/tt_metal/common/core_coord.cpp index c8281b40ac50..c30474c0aa45 100644 --- a/tt_metal/common/core_coord.cpp +++ b/tt_metal/common/core_coord.cpp @@ -15,7 +15,7 @@ #include "umd/device/tt_xy_pair.h" #include "tt_metal/common/assert.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" #include "tt_metal/tt_stl/reflection.hpp" #include "tt_metal/tt_stl/span.hpp" diff --git a/tt_metal/common/test_tiles.hpp b/tt_metal/common/test_tiles.hpp index 44e18fbc448e..e47fb65c85fe 100644 --- a/tt_metal/common/test_tiles.hpp +++ b/tt_metal/common/test_tiles.hpp @@ -14,7 +14,7 @@ #include "tt_metal/tt_stl/span.hpp" #include "tt_metal/common/constants.hpp" #include "tt_metal/common/assert.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" #include "math.hpp" namespace tests::utils { diff --git a/tt_metal/common/utils.cpp b/tt_metal/common/utils.cpp index 20d7a5cca8fa..06bef53c3322 100644 --- a/tt_metal/common/utils.cpp +++ b/tt_metal/common/utils.cpp @@ -4,7 +4,7 @@ #include "common/utils.hpp" #include -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" #include "llrt/rtoptions.hpp" #include diff --git a/tt_metal/common/work_split.cpp b/tt_metal/common/work_split.cpp index ad04e169232b..4ce3eb346e38 100644 --- a/tt_metal/common/work_split.cpp +++ b/tt_metal/common/work_split.cpp @@ -13,7 +13,7 @@ #include "tt_metal/common/assert.hpp" #include "tt_metal/common/core_coord.hpp" #include "tt_metal/common/math.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" namespace tt { namespace tt_metal { diff --git a/tt_metal/detail/CMakeLists.txt b/tt_metal/detail/CMakeLists.txt index f74f2f7b4023..0df4a83d4835 100644 --- a/tt_metal/detail/CMakeLists.txt +++ b/tt_metal/detail/CMakeLists.txt @@ -4,4 +4,4 @@ set(DETAIL_SRC ) add_library(detail OBJECT ${DETAIL_SRC}) -target_link_libraries(detail PUBLIC common) +target_link_libraries(detail PUBLIC common PRIVATE Metalium::Metal::Impl) diff --git a/tt_metal/distributed/CMakeLists.txt b/tt_metal/distributed/CMakeLists.txt index cc24b87a952d..8e330be700a1 100644 --- a/tt_metal/distributed/CMakeLists.txt +++ b/tt_metal/distributed/CMakeLists.txt @@ -5,4 +5,4 @@ set(DISTRIBUTED_SRC ) add_library(distributed OBJECT ${DISTRIBUTED_SRC}) -target_link_libraries(distributed PUBLIC common) +target_link_libraries(distributed PUBLIC common PRIVATE Metalium::Metal::Impl) diff --git a/tt_metal/impl/CMakeLists.txt b/tt_metal/impl/CMakeLists.txt index 7743041f668e..cad106642c9b 100644 --- a/tt_metal/impl/CMakeLists.txt +++ b/tt_metal/impl/CMakeLists.txt @@ -37,6 +37,7 @@ target_link_libraries( PUBLIC common Metalium::Metal::LLRT + Tracy::TracyClient PRIVATE Boost::smart_ptr range-v3::range-v3 diff --git a/tt_metal/impl/device/device.cpp b/tt_metal/impl/device/device.cpp index 899ceeae588b..c2c91eaab9ec 100644 --- a/tt_metal/impl/device/device.cpp +++ b/tt_metal/impl/device/device.cpp @@ -10,7 +10,7 @@ #include "tt_metal/impl/device/device.hpp" #include "tt_metal/impl/trace/trace.hpp" #include "tt_metal/common/core_descriptor.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" #include "tt_metal/detail/tt_metal.hpp" #include "impl/debug/dprint_server.hpp" #include "impl/debug/watcher_server.hpp" diff --git a/tt_metal/impl/dispatch/work_executor.hpp b/tt_metal/impl/dispatch/work_executor.hpp index 90c1afcf23d3..fdc16a9dc211 100644 --- a/tt_metal/impl/dispatch/work_executor.hpp +++ b/tt_metal/impl/dispatch/work_executor.hpp @@ -15,8 +15,7 @@ #include "common/env_lib.hpp" #include "lock_free_queue.hpp" -#include "tt_metal/third_party/tracy/public/common/TracySystem.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" #if defined(TRACY_ENABLE) #define TracyTTThreadName(name, id) \ diff --git a/tt_metal/impl/program/program.cpp b/tt_metal/impl/program/program.cpp index e5f0fa3adb25..d3cf81833f12 100644 --- a/tt_metal/impl/program/program.cpp +++ b/tt_metal/impl/program/program.cpp @@ -27,7 +27,7 @@ #include "tt_metal/jit_build/genfiles.hpp" #include "tt_metal/llrt/llrt.hpp" #include "tt_metal/program.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" namespace tt::tt_metal { diff --git a/tt_metal/jit_build/CMakeLists.txt b/tt_metal/jit_build/CMakeLists.txt index 89c228b656be..47cdc8804174 100644 --- a/tt_metal/jit_build/CMakeLists.txt +++ b/tt_metal/jit_build/CMakeLists.txt @@ -7,7 +7,7 @@ set(JIT_BUILD_SRCS ) add_library(jit_build OBJECT ${JIT_BUILD_SRCS}) -target_link_libraries(jit_build PUBLIC common) +target_link_libraries(jit_build PUBLIC common PRIVATE Tracy::TracyClient) if(DEFINED VERSION_HASH) target_compile_definitions(jit_build PRIVATE "-DGIT_COMMIT_HASH=\"${VERSION_HASH}\"") diff --git a/tt_metal/jit_build/build.hpp b/tt_metal/jit_build/build.hpp index 2a3ef1772e11..3b6aab749197 100644 --- a/tt_metal/jit_build/build.hpp +++ b/tt_metal/jit_build/build.hpp @@ -14,7 +14,7 @@ #include "jit_build/data_format.hpp" #include "jit_build/settings.hpp" #include "hostdevcommon/common_values.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" #include "tt_metal/tt_stl/aligned_allocator.hpp" #include "llrt/rtoptions.hpp" diff --git a/tt_metal/llrt/CMakeLists.txt b/tt_metal/llrt/CMakeLists.txt index 8b166cd491e9..db315ff636eb 100644 --- a/tt_metal/llrt/CMakeLists.txt +++ b/tt_metal/llrt/CMakeLists.txt @@ -97,5 +97,6 @@ target_link_libraries( HAL::grayskull HAL::wormhole HAL::blackhole + Tracy::TracyClient ) target_compile_options(llrt PRIVATE -Wno-int-to-pointer-cast) diff --git a/tt_metal/llrt/tt_cluster.cpp b/tt_metal/llrt/tt_cluster.cpp index 12f144dd7cea..67e7e6e33adb 100644 --- a/tt_metal/llrt/tt_cluster.cpp +++ b/tt_metal/llrt/tt_cluster.cpp @@ -42,7 +42,7 @@ #include "llrt/hal.hpp" -#include "third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" #include "umd/device/tt_simulation_device.h" #include "tt_metal/impl/debug/sanitize_noc_host.hpp" diff --git a/tt_metal/tools/profiler/CMakeLists.txt b/tt_metal/tools/profiler/CMakeLists.txt index 134f6fdd748e..bcc37b43d7bf 100644 --- a/tt_metal/tools/profiler/CMakeLists.txt +++ b/tt_metal/tools/profiler/CMakeLists.txt @@ -4,4 +4,4 @@ set(PROFILER_SRC ) add_library(profiler OBJECT ${PROFILER_SRC}) -target_link_libraries(profiler PUBLIC common) +target_link_libraries(profiler PUBLIC common PRIVATE Tracy::TracyClient) diff --git a/tt_metal/tools/profiler/op_profiler.hpp b/tt_metal/tools/profiler/op_profiler.hpp index 376f5b341505..d6a6dc278702 100644 --- a/tt_metal/tools/profiler/op_profiler.hpp +++ b/tt_metal/tools/profiler/op_profiler.hpp @@ -16,8 +16,8 @@ #include "tt_metal/impl/kernels/kernel.hpp" #include "ttnn/operation.hpp" #include "tt_metal/detail/tt_metal.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" -#include "tt_metal/third_party/tracy/public/tracy/TracyC.h" +#include "tracy/Tracy.hpp" +#include "tracy/TracyC.h" using json = nlohmann::json; diff --git a/tt_metal/tools/profiler/profiler.cpp b/tt_metal/tools/profiler/profiler.cpp index 0fe0dc5767d4..5f278b07c0da 100644 --- a/tt_metal/tools/profiler/profiler.cpp +++ b/tt_metal/tools/profiler/profiler.cpp @@ -15,7 +15,7 @@ #include "hostdevcommon/profiler_common.h" #include "llrt/rtoptions.hpp" #include "dev_msgs.h" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" #include "tt_metal/impl/device/device.hpp" namespace tt { diff --git a/tt_metal/tools/profiler/profiler.hpp b/tt_metal/tools/profiler/profiler.hpp index 54b7bb761127..76d050ba78fb 100644 --- a/tt_metal/tools/profiler/profiler.hpp +++ b/tt_metal/tools/profiler/profiler.hpp @@ -15,8 +15,8 @@ #include "llrt/llrt.hpp" #include "tools/profiler/profiler_state.hpp" #include "tools/profiler/common.hpp" -#include "tt_metal/third_party/tracy/public/tracy/TracyTTDevice.hpp" -#include "tt_metal/third_party/tracy/public/common/TracyTTDeviceData.hpp" +#include "tracy/TracyTTDevice.hpp" +#include "common/TracyTTDeviceData.hpp" using std::chrono::duration; using std::chrono::duration_cast; diff --git a/tt_metal/tools/profiler/tt_metal_profiler.cpp b/tt_metal/tools/profiler/tt_metal_profiler.cpp index 60e1bb8869b4..52aa4f70e143 100644 --- a/tt_metal/tools/profiler/tt_metal_profiler.cpp +++ b/tt_metal/tools/profiler/tt_metal_profiler.cpp @@ -14,7 +14,7 @@ #include "tt_metal/detail/tt_metal.hpp" -#include "tt_metal/third_party/tracy/public/tracy/TracyTTDevice.hpp" +#include "tracy/TracyTTDevice.hpp" #include "tt_metal/impl/device/device.hpp" namespace tt { diff --git a/tt_metal/tt_metal.cpp b/tt_metal/tt_metal.cpp index b8fdf165c52c..5ed2c55e5a80 100644 --- a/tt_metal/tt_metal.cpp +++ b/tt_metal/tt_metal.cpp @@ -30,7 +30,7 @@ #include "tt_metal/impl/sub_device/sub_device_types.hpp" #include "tt_metal/include/tt_metal/global_circular_buffer.hpp" #include "tt_metal/include/tt_metal/program.hpp" -#include "tt_metal/third_party/tracy/public/tracy/Tracy.hpp" +#include "tracy/Tracy.hpp" #include "tt_metal/graph/graph_tracking.hpp" From 5c91e97a37f0f9ea658010941eabaa34a16bf798 Mon Sep 17 00:00:00 2001 From: Denys Makoviichuk Date: Thu, 19 Dec 2024 09:43:30 -0800 Subject: [PATCH 71/87] [TT-Train ]added tests for sum and mean (#16152) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Problem description We need to know that ttnn reduce ops are almost the same as moreh. ### What's changed * Added sum test * Added mean test * Updated sum_over_dim Screenshot 2024-12-18 at 1 56 19 PM ### Checklist - [x] Post commit CI passes - [x] Blackhole Post commit (if applicable) - [x] Model regression CI testing passes (if applicable) - [x] Device performance regression CI testing passes (if applicable) - [x] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [x] New/Existing tests provide coverage for changes https://github.com/tenstorrent/tt-metal/actions/runs/12405523060 --------- Co-authored-by: Roman Furko --- .../ttml/ttnn_fixed/trivial_ttnn_ops.cpp | 38 ++++- .../ttml/ttnn_fixed/trivial_ttnn_ops.hpp | 7 + tt-train/tests/ttnn_fixed/reduce_ops_test.cpp | 133 ++++++++++++++++++ 3 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 tt-train/tests/ttnn_fixed/reduce_ops_test.cpp diff --git a/tt-train/sources/ttml/ttnn_fixed/trivial_ttnn_ops.cpp b/tt-train/sources/ttml/ttnn_fixed/trivial_ttnn_ops.cpp index 652073c583d4..ad864fa0b3df 100644 --- a/tt-train/sources/ttml/ttnn_fixed/trivial_ttnn_ops.cpp +++ b/tt-train/sources/ttml/ttnn_fixed/trivial_ttnn_ops.cpp @@ -7,19 +7,14 @@ #include #include +#include "autograd/auto_context.hpp" #include "core/compute_kernel_config.hpp" #include "core/tt_tensor_utils.hpp" namespace ttml::ttnn_fixed { tt::tt_metal::Tensor sum_over_dim(const tt::tt_metal::Tensor& t, uint32_t dim) { - return ttnn::moreh_sum( - t, - /* dim */ dim, - /* keep_dim */ true, - /* output */ std::nullopt, - /* output_mem_config */ std::nullopt, - /*compute_kernel_config */ core::ComputeKernelConfig::precise()); + return sum_ttnn(t, dim, /* keepdim */ true); } tt::tt_metal::Tensor sum_over_batch(const tt::tt_metal::Tensor& t) { @@ -54,4 +49,33 @@ tt::tt_metal::Tensor divide(const tt::tt_metal::Tensor& a, const tt::tt_metal::T return ttnn::multiply(a, inv_b); } +tt::tt_metal::Tensor mean_moreh(const tt::tt_metal::Tensor& t, int dim, bool keep_dim) { + auto res = ttnn::moreh_mean( + t, + dim, + keep_dim, + std::nullopt, + std::nullopt, + std::nullopt, + /* device_compute_kernel_config */ core::ComputeKernelConfig::precise()); + return res; +} +tt::tt_metal::Tensor mean_ttnn(const tt::tt_metal::Tensor& t, int dim, bool keep_dim) { + return ttnn::mean(t, dim, keep_dim, std::nullopt, core::ComputeKernelConfig::precise()); +} + +tt::tt_metal::Tensor sum_moreh(const tt::tt_metal::Tensor& t, int dim, bool keep_dim) { + auto res = ttnn::moreh_sum( + t, + dim, + keep_dim, + std::nullopt, + std::nullopt, + /* device_compute_kernel_config */ core::ComputeKernelConfig::precise()); + return res; +} +tt::tt_metal::Tensor sum_ttnn(const tt::tt_metal::Tensor& t, int dim, bool keep_dim) { + return ttnn::sum(t, dim, keep_dim, std::nullopt, core::ComputeKernelConfig::precise()); +} + } // namespace ttml::ttnn_fixed diff --git a/tt-train/sources/ttml/ttnn_fixed/trivial_ttnn_ops.hpp b/tt-train/sources/ttml/ttnn_fixed/trivial_ttnn_ops.hpp index 564c985c1984..c8a62d981bc1 100644 --- a/tt-train/sources/ttml/ttnn_fixed/trivial_ttnn_ops.hpp +++ b/tt-train/sources/ttml/ttnn_fixed/trivial_ttnn_ops.hpp @@ -5,6 +5,8 @@ #pragma once #include +#include "core/tt_tensor_utils.hpp" + namespace ttml::ttnn_fixed { tt::tt_metal::Tensor sum_over_dim(const tt::tt_metal::Tensor& t, uint32_t dim); @@ -13,4 +15,9 @@ tt::tt_metal::Tensor log_softmax(const tt::tt_metal::Tensor& t, int dim); tt::tt_metal::Tensor softmax(const tt::tt_metal::Tensor& t, int dim); tt::tt_metal::Tensor divide(const tt::tt_metal::Tensor& a, const tt::tt_metal::Tensor& b); +tt::tt_metal::Tensor mean_moreh(const tt::tt_metal::Tensor& t, int dim, bool keep_dim); +tt::tt_metal::Tensor mean_ttnn(const tt::tt_metal::Tensor& t, int dim, bool keep_dim); + +tt::tt_metal::Tensor sum_moreh(const tt::tt_metal::Tensor& t, int dim, bool keep_dim); +tt::tt_metal::Tensor sum_ttnn(const tt::tt_metal::Tensor& t, int dim, bool keep_dim); } // namespace ttml::ttnn_fixed diff --git a/tt-train/tests/ttnn_fixed/reduce_ops_test.cpp b/tt-train/tests/ttnn_fixed/reduce_ops_test.cpp new file mode 100644 index 000000000000..9c15b982e821 --- /dev/null +++ b/tt-train/tests/ttnn_fixed/reduce_ops_test.cpp @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: (c) 2024 Tenstorrent AI ULC +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include +#include +#include +#include +#include + +#include "autograd/auto_context.hpp" +#include "core/compute_kernel_config.hpp" +#include "core/device.hpp" +#include "core/tt_tensor_utils.hpp" +#include "ttnn_fixed/trivial_ttnn_ops.hpp" + +class ReduceOpTest : public ::testing::Test { +protected: + void SetUp() override { + ttml::autograd::ctx().open_device(); + } + + void TearDown() override { + ttml::autograd::ctx().close_device(); + } +}; + +TEST_F(ReduceOpTest, TestMeanDim0) { + xt::random::seed(42); + auto* device = &ttml::autograd::ctx().get_device(); + xt::xarray xtensor_a = xt::random::rand({128 * 64}, -0.5, 0.5).reshape({2, 1, 64, 64}); + + auto xtensor_a_tensor = ttml::core::from_xtensor(xtensor_a, device); + + auto ttnn_mean_dim0 = ttml::ttnn_fixed::mean_ttnn(xtensor_a_tensor, 0, true); + auto moreh_mean_dim0 = ttml::ttnn_fixed::mean_moreh(xtensor_a_tensor, 0, true); + + xt::xarray mean_xtensor = xt::mean(xtensor_a, {0}, xt::evaluation_strategy::immediate); + mean_xtensor.reshape({1, 1, 64, 64}); + + auto mean_ttnn = ttml::core::to_xtensor(ttnn_mean_dim0); + auto mean_moreh = ttml::core::to_xtensor(moreh_mean_dim0); + + EXPECT_TRUE(xt::allclose(mean_ttnn, mean_moreh, /*rtol=*/1e-4, /*atol=*/1e-3)); + EXPECT_TRUE(xt::allclose(mean_xtensor, mean_ttnn, /*rtol=*/1e-3, /*atol=*/1e-2)); + EXPECT_TRUE(xt::allclose(mean_xtensor, mean_moreh, /*rtol=*/1e-3, /*atol=*/1e-2)); +} + +TEST_F(ReduceOpTest, TestSumDim0) { + xt::random::seed(42); + auto* device = &ttml::autograd::ctx().get_device(); + xt::xarray xtensor_a = xt::random::rand({128 * 64}, -0.1, 0.1).reshape({2, 1, 64, 64}); + + auto xtensor_a_tensor = ttml::core::from_xtensor(xtensor_a, device); + + auto ttnn_sum_dim0 = ttml::ttnn_fixed::sum_ttnn(xtensor_a_tensor, 0, true); + auto moreh_sum_dim0 = ttml::ttnn_fixed::sum_moreh(xtensor_a_tensor, 0, true); + + xt::xarray sum_xtensor = xt::sum(xtensor_a, {0}, xt::evaluation_strategy::immediate); + sum_xtensor.reshape({1, 1, 64, 64}); + + auto sum_ttnn = ttml::core::to_xtensor(ttnn_sum_dim0); + auto sum_moreh = ttml::core::to_xtensor(moreh_sum_dim0); + + EXPECT_TRUE(xt::allclose(sum_ttnn, sum_moreh, /*rtol=*/1e-4, /*atol=*/1e-3)); + EXPECT_TRUE(xt::allclose(sum_xtensor, sum_ttnn, /*rtol=*/1e-2, /*atol=*/1e-2)); + EXPECT_TRUE(xt::allclose(sum_xtensor, sum_moreh, /*rtol=*/1e-2, /*atol=*/1e-2)); +} + +TEST_F(ReduceOpTest, TestMeanDim3) { + xt::random::seed(42); + auto* device = &ttml::autograd::ctx().get_device(); + xt::xarray xtensor_a = xt::random::rand({128 * 64}, -0.5, 0.5).reshape({2, 1, 64, 64}); + + auto xtensor_a_tensor = ttml::core::from_xtensor(xtensor_a, device); + + auto ttnn_mean_dim3 = ttml::ttnn_fixed::mean_ttnn(xtensor_a_tensor, 3, true); + auto moreh_mean_dim3 = ttml::ttnn_fixed::mean_moreh(xtensor_a_tensor, 3, true); + + xt::xarray mean_xtensor = xt::mean(xtensor_a, {3}, xt::evaluation_strategy::immediate); + mean_xtensor.reshape({2, 1, 64, 1}); + + auto mean_ttnn = ttml::core::to_xtensor(ttnn_mean_dim3); + auto mean_moreh = ttml::core::to_xtensor(moreh_mean_dim3); + + EXPECT_TRUE(xt::allclose(mean_ttnn, mean_moreh, /*rtol=*/1e-4, /*atol=*/1e-3)); + EXPECT_TRUE(xt::allclose(mean_xtensor, mean_ttnn, /*rtol=*/1e-3, /*atol=*/1e-2)); + EXPECT_TRUE(xt::allclose(mean_xtensor, mean_moreh, /*rtol=*/1e-3, /*atol=*/1e-2)); +} + +TEST_F(ReduceOpTest, TestSumDim3) { + xt::random::seed(42); + auto* device = &ttml::autograd::ctx().get_device(); + xt::xarray xtensor_a = xt::random::rand({128 * 64}, -0.1, 0.1).reshape({2, 1, 64, 64}); + + auto xtensor_a_tensor = ttml::core::from_xtensor(xtensor_a, device); + + auto ttnn_sum_dim3 = ttml::ttnn_fixed::sum_ttnn(xtensor_a_tensor, 3, true); + auto moreh_sum_dim3 = ttml::ttnn_fixed::sum_moreh(xtensor_a_tensor, 3, true); + + xt::xarray sum_xtensor = xt::sum(xtensor_a, {3}, xt::evaluation_strategy::immediate); + sum_xtensor.reshape({2, 1, 64, 1}); + + auto sum_ttnn = ttml::core::to_xtensor(ttnn_sum_dim3); + auto sum_moreh = ttml::core::to_xtensor(moreh_sum_dim3); + + EXPECT_TRUE(xt::allclose(sum_ttnn, sum_moreh, /*rtol=*/1e-4, /*atol=*/1e-3)); + EXPECT_TRUE(xt::allclose(sum_xtensor, sum_ttnn, /*rtol=*/1e-2, /*atol=*/1e-2)); + EXPECT_TRUE(xt::allclose(sum_xtensor, sum_moreh, /*rtol=*/1e-2, /*atol=*/1e-2)); +} + +TEST_F(ReduceOpTest, TestMeanLargeDim3) { + xt::random::seed(42); + auto* device = &ttml::autograd::ctx().get_device(); + xt::xarray xtensor_a = xt::random::rand({1024 * 1024}, -0.5, 0.5).reshape({2, 1, 512, 1024}); + + auto xtensor_a_tensor = ttml::core::from_xtensor(xtensor_a, device); + + auto ttnn_mean_dim3 = ttml::ttnn_fixed::mean_ttnn(xtensor_a_tensor, 3, true); + auto moreh_mean_dim3 = ttml::ttnn_fixed::mean_moreh(xtensor_a_tensor, 3, true); + + xt::xarray mean_xtensor = xt::mean(xtensor_a, {3}, xt::evaluation_strategy::immediate); + mean_xtensor.reshape({2, 1, 512, 1}); + + auto mean_ttnn = ttml::core::to_xtensor(ttnn_mean_dim3); + auto mean_moreh = ttml::core::to_xtensor(moreh_mean_dim3); + + EXPECT_TRUE(xt::allclose(mean_ttnn, mean_moreh, /*rtol=*/1e-4, /*atol=*/1e-3)); + EXPECT_TRUE(xt::allclose(mean_xtensor, mean_ttnn, /*rtol=*/1e-3, /*atol=*/1e-2)); + EXPECT_TRUE(xt::allclose(mean_xtensor, mean_moreh, /*rtol=*/1e-3, /*atol=*/1e-2)); +} From 485a18d1e01d6c8ef38051274cf1308c645e07c1 Mon Sep 17 00:00:00 2001 From: Raymond Kim <109366641+tt-rkim@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:27:59 -0500 Subject: [PATCH 72/87] #16184: Try using ecr to avoid rate limits of docker.io (#16201) ### Ticket #16184 ### Problem description Docker is hella rate limiting us. Switch to ECR so we don't run into it. ### What's changed Switch ubuntu base to ECR. ### Checklist - [x] Post commit CI passes - [ ] Blackhole Post commit (if applicable) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [ ] New/Existing tests provide coverage for changes --- dockerfile/ubuntu-20.04-amd64.Dockerfile | 2 +- dockerfile/ubuntu-22.04-amd64.Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dockerfile/ubuntu-20.04-amd64.Dockerfile b/dockerfile/ubuntu-20.04-amd64.Dockerfile index 29d846cd495b..df759a2c258f 100644 --- a/dockerfile/ubuntu-20.04-amd64.Dockerfile +++ b/dockerfile/ubuntu-20.04-amd64.Dockerfile @@ -1,5 +1,5 @@ # TT-METAL UBUNTU 20.04 AMD64 DOCKERFILE -FROM ubuntu:20.04 +FROM public.ecr.aws/ubuntu/ubuntu:20.04 ARG DEBIAN_FRONTEND=noninteractive ENV DOXYGEN_VERSION=1.9.6 diff --git a/dockerfile/ubuntu-22.04-amd64.Dockerfile b/dockerfile/ubuntu-22.04-amd64.Dockerfile index 404e161c543e..0999a5a2a25c 100644 --- a/dockerfile/ubuntu-22.04-amd64.Dockerfile +++ b/dockerfile/ubuntu-22.04-amd64.Dockerfile @@ -1,5 +1,5 @@ # TT-METAL UBUNTU 22.04 AMD64 DOCKERFILE -FROM ubuntu:22.04 +FROM public.ecr.aws/ubuntu/ubuntu:22.04 ARG DEBIAN_FRONTEND=noninteractive ARG UBUNTU_VERSION=22.04 From 3f540111480fc021f07d5fb67a906e1b0618060c Mon Sep 17 00:00:00 2001 From: John Bauman Date: Tue, 3 Dec 2024 15:37:04 +0000 Subject: [PATCH 73/87] #15221: Post completion messages to dispatch_s We never wait on the acks from these completion messages, so make them posted to avoid contention from a lot of replies being sent at once. In the case where every worker is sending them at the same time, this can halve the latency from 500ns to 250ns (on wormhole). --- tt_metal/hw/firmware/src/brisc.cc | 6 ++++-- tt_metal/hw/firmware/src/idle_erisc.cc | 10 +++++++++- tt_metal/hw/inc/ethernet/tunneling.h | 9 ++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/tt_metal/hw/firmware/src/brisc.cc b/tt_metal/hw/firmware/src/brisc.cc index f376b9746e7d..ec28c62af262 100644 --- a/tt_metal/hw/firmware/src/brisc.cc +++ b/tt_metal/hw/firmware/src/brisc.cc @@ -406,7 +406,8 @@ int main() { NOC_UNICAST_WRITE_VC, 1, 31 /*wrap*/, - false /*linked*/); + false /*linked*/, + true /*posted*/); } } @@ -529,7 +530,8 @@ int main() { NOC_UNICAST_WRITE_VC, 1, 31 /*wrap*/, - false /*linked*/); + false /*linked*/, + true /*posted*/); mailboxes->launch_msg_rd_ptr = (launch_msg_rd_ptr + 1) & (launch_msg_buffer_num_entries - 1); } } diff --git a/tt_metal/hw/firmware/src/idle_erisc.cc b/tt_metal/hw/firmware/src/idle_erisc.cc index a425dd5c49d7..9de56599b4d7 100644 --- a/tt_metal/hw/firmware/src/idle_erisc.cc +++ b/tt_metal/hw/firmware/src/idle_erisc.cc @@ -176,7 +176,15 @@ int main() { DISPATCH_MESSAGE_ADDR + mailboxes->go_message.dispatch_message_offset); DEBUG_SANITIZE_NOC_ADDR(noc_index, dispatch_addr, 4); CLEAR_PREVIOUS_LAUNCH_MESSAGE_ENTRY_FOR_WATCHER(); - noc_fast_atomic_increment(noc_index, NCRISC_AT_CMD_BUF, dispatch_addr, NOC_UNICAST_WRITE_VC, 1, 31 /*wrap*/, false /*linked*/); + noc_fast_atomic_increment( + noc_index, + NCRISC_AT_CMD_BUF, + dispatch_addr, + NOC_UNICAST_WRITE_VC, + 1, + 31 /*wrap*/, + false /*linked*/, + true /*posted*/); mailboxes->launch_msg_rd_ptr = (launch_msg_rd_ptr + 1) & (launch_msg_buffer_num_entries - 1); } diff --git a/tt_metal/hw/inc/ethernet/tunneling.h b/tt_metal/hw/inc/ethernet/tunneling.h index 375df30fa671..fbbf252619b9 100644 --- a/tt_metal/hw/inc/ethernet/tunneling.h +++ b/tt_metal/hw/inc/ethernet/tunneling.h @@ -109,7 +109,14 @@ void notify_dispatch_core_done(uint64_t dispatch_addr) { } DEBUG_SANITIZE_NOC_ADDR(noc_index, dispatch_addr, 4); noc_fast_atomic_increment( - noc_index, NCRISC_AT_CMD_BUF, dispatch_addr, NOC_UNICAST_WRITE_VC, 1, 31 /*wrap*/, false /*linked*/); + noc_index, + NCRISC_AT_CMD_BUF, + dispatch_addr, + NOC_UNICAST_WRITE_VC, + 1, + 31 /*wrap*/, + false /*linked*/, + true /*posted*/); } } // namespace internal_ From fa779b9381c79add28d40ce36f8408cd2bbafb8e Mon Sep 17 00:00:00 2001 From: Denys Makoviichuk Date: Thu, 19 Dec 2024 11:06:42 -0800 Subject: [PATCH 74/87] [TT-Train] Added softmax backward (#16168) ### Problem description * optimized spda with moreh softmax backward. * added precise() * add moreh log_softmax fwd and backward. * Fixed some tests. ### What's changed Describe the approach used to solve the problem. Summarize the changes made and its impact. ### Checklist - [x] Post commit CI passes - [x] Blackhole Post commit (if applicable) - [x] Model regression CI testing passes (if applicable) - [x] Device performance regression CI testing passes (if applicable) - [x] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [x] New/Existing tests provide coverage for changes --- .../configs/training_shakespear_nanogpt.yaml | 3 +- tt-train/sources/examples/nano_gpt/main.cpp | 27 +++++++++----- .../sources/ttml/core/ttnn_all_includes.hpp | 2 ++ tt-train/sources/ttml/ops/layernorm_op.cpp | 4 +-- tt-train/sources/ttml/ops/losses.cpp | 2 +- .../ttml/ops/scaled_dot_product_attention.cpp | 26 +++++++------- tt-train/sources/ttml/ops/unary_ops.cpp | 36 ++++++++++++++++++- tt-train/sources/ttml/ops/unary_ops.hpp | 2 +- tt-train/tests/model/nano_gpt_test.cpp | 5 +++ tt-train/tests/ops/unary_ops_test.cpp | 1 + 10 files changed, 81 insertions(+), 27 deletions(-) diff --git a/tt-train/configs/training_shakespear_nanogpt.yaml b/tt-train/configs/training_shakespear_nanogpt.yaml index 1c5977f32730..2c61fd0c7b82 100644 --- a/tt-train/configs/training_shakespear_nanogpt.yaml +++ b/tt-train/configs/training_shakespear_nanogpt.yaml @@ -7,7 +7,8 @@ training_config: max_steps: 5000 learning_rate: 0.0003 weight_decay: 0.01 - + use_moreh_adamw: true + use_kahan_summation: false transformer_config: num_heads: 6 embedding_dim: 384 diff --git a/tt-train/sources/examples/nano_gpt/main.cpp b/tt-train/sources/examples/nano_gpt/main.cpp index 541cb32c3de0..e447ce075136 100644 --- a/tt-train/sources/examples/nano_gpt/main.cpp +++ b/tt-train/sources/examples/nano_gpt/main.cpp @@ -137,6 +137,7 @@ struct TrainingConfig { uint32_t max_steps = 5000; float learning_rate = 3e-4F; float weight_decay = 1e-2F; + bool use_moreh_adamw = false; // works only for AdamW bool use_kahan_summation = false; // accumulate batches for gradient update @@ -160,6 +161,7 @@ TrainingConfig parse_config(const YAML::Node &yaml_config) { config.max_steps = training_config["max_steps"].as(); config.learning_rate = training_config["learning_rate"].as(); config.weight_decay = training_config["weight_decay"].as(); + config.use_moreh_adamw = training_config["use_moreh_adamw"].as(config.use_moreh_adamw); config.use_kahan_summation = training_config["use_kahan_summation"].as(config.use_kahan_summation); config.gradient_accumulation_steps = training_config["gradient_accumulation_steps"].as(config.gradient_accumulation_steps); @@ -335,12 +337,21 @@ int main(int argc, char **argv) { fmt::print(" Learning rate: {}\n", adamw_params.lr); fmt::print(" Weight decay: {}\n", adamw_params.weight_decay); fmt::print(" Use Kahan summation: {}\n", adamw_params.use_kahan_summation); - auto optimizer = ttml::optimizers::AdamW(model->parameters(), adamw_params); - auto scheduler = schedule_func(&optimizer, config.max_steps); + auto select_optimizer = [&model, + &adamw_params](bool use_moreh_adamw) -> std::unique_ptr { + if (use_moreh_adamw) { + return std::make_unique(model->parameters(), adamw_params); + } else { + return std::make_unique(model->parameters(), adamw_params); + } + }; + + auto optimizer = select_optimizer(config.use_moreh_adamw); + auto scheduler = schedule_func(optimizer.get(), config.max_steps); if (!config.model_path.empty() && std::filesystem::exists(config.model_path)) { fmt::print("Loading model from {}\n", config.model_path); load_training_state(config.model_path, model, scheduler, "transformer", "adamw"); - fmt::print("Model loaded after {} steps\n", optimizer.get_steps()); + fmt::print("Model loaded after {} steps\n", optimizer->get_steps()); } if (is_eval) { @@ -362,7 +373,7 @@ int main(int argc, char **argv) { for (auto [features, target, masks] : train_dataloader) { auto start_timer = std::chrono::high_resolution_clock::now(); if (gradient_accumulator_helper.should_zero_grad()) { - optimizer.zero_grad(); + optimizer->zero_grad(); } auto output = (*model)(features, masks); auto loss = ttml::ops::nll_loss(output, target); @@ -376,9 +387,9 @@ int main(int argc, char **argv) { gradient_accumulator_helper.update(loss_float, samples); if (gradient_accumulator_helper.should_step()) { - optimizer.step(); + optimizer->step(); scheduler->step(); - auto global_step = optimizer.get_steps(); + auto global_step = optimizer->get_steps(); fmt::print("Step: {}, Loss: {}\n", global_step, gradient_accumulator_helper.average_loss()); loss_meter.update(gradient_accumulator_helper.average_loss()); @@ -387,7 +398,7 @@ int main(int argc, char **argv) { {{"Step", (int)global_step}, {"Samples", (int)get_samples_count(global_step)}, {"Loss", loss_meter.average()}, - {"Learning rate", optimizer.get_lr()}}); + {"Learning rate", optimizer->get_lr()}}); loss_meter.reset(); } if (!config.model_path.empty() && global_step % config.model_save_interval == 0) { @@ -407,7 +418,7 @@ int main(int argc, char **argv) { (double)duration / 1000, device->num_program_cache_entries()); } - if (optimizer.get_steps() >= config.max_steps) { + if (optimizer->get_steps() >= config.max_steps) { break; } } diff --git a/tt-train/sources/ttml/core/ttnn_all_includes.hpp b/tt-train/sources/ttml/core/ttnn_all_includes.hpp index f7f5b8cb282e..09f0ffdcdf6d 100644 --- a/tt-train/sources/ttml/core/ttnn_all_includes.hpp +++ b/tt-train/sources/ttml/core/ttnn_all_includes.hpp @@ -20,6 +20,8 @@ #include // NOLINT #include // NOLINT #include // NOLINT +#include // NOLINT +#include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT diff --git a/tt-train/sources/ttml/ops/layernorm_op.cpp b/tt-train/sources/ttml/ops/layernorm_op.cpp index b2f66f37f8ed..03a036d561e1 100644 --- a/tt-train/sources/ttml/ops/layernorm_op.cpp +++ b/tt-train/sources/ttml/ops/layernorm_op.cpp @@ -40,7 +40,7 @@ autograd::TensorPtr layernorm( mean, rstd, /* memory_config */ std::nullopt, - /* compute_kernel_config */ std::nullopt); + /* compute_kernel_config */ core::ComputeKernelConfig::precise()); auto out = autograd::create_tensor(); out->set_value(out_tensors[0].value()); @@ -63,7 +63,7 @@ autograd::TensorPtr layernorm( gamma_grad, beta_grad, /* memory_config */ std::nullopt, - /* compute_kernel_config */ std::nullopt); + /* compute_kernel_config */ core::ComputeKernelConfig::precise()); tensor->add_grad(res[0].value()); gamma->add_grad(res[1].value()); diff --git a/tt-train/sources/ttml/ops/losses.cpp b/tt-train/sources/ttml/ops/losses.cpp index 105bc97fa309..4d72ea9aa28e 100644 --- a/tt-train/sources/ttml/ops/losses.cpp +++ b/tt-train/sources/ttml/ops/losses.cpp @@ -97,7 +97,7 @@ autograd::TensorPtr nll_loss( /* divisor_tensor */ divisor, /* ignore_index */ -100, /* memory_config */ std::nullopt, - /* compute_kernel_config */ std::nullopt); + /* compute_kernel_config */ core::ComputeKernelConfig::precise()); grad = ttnn::reshape(grad, prediction->get_value().shape()); prediction->add_grad(grad); }; diff --git a/tt-train/sources/ttml/ops/scaled_dot_product_attention.cpp b/tt-train/sources/ttml/ops/scaled_dot_product_attention.cpp index 26d3d448b194..681bad8cc30f 100644 --- a/tt-train/sources/ttml/ops/scaled_dot_product_attention.cpp +++ b/tt-train/sources/ttml/ops/scaled_dot_product_attention.cpp @@ -35,9 +35,9 @@ autograd::TensorPtr scaled_dot_product_attention( const std::optional& mask) { const float scale = 1.0F / std::sqrtf(static_cast(query->get_value().get_shape()[-1])); // (B, H, S, E) x (B, H, E, S) -> (B, H, S, S) - auto qk_t = matmul(query->get_value(), key->get_value(), /* transpose_a */ false, /* transpose_b */ true); - // (B, H, S, S) * scale - auto qk_scaled = ttnn::multiply(qk_t, scale); + auto q_scaled = ttnn::multiply(query->get_value(), scale); + auto qk_scaled = matmul(q_scaled, key->get_value(), /* transpose_a */ false, /* transpose_b */ true); + if (mask.has_value()) { qk_scaled = ttnn::where(mask.value()->get_value(), qk_scaled, /* other */ -1e9F); } @@ -56,28 +56,28 @@ autograd::TensorPtr scaled_dot_product_attention( auto grad_v = matmul(attention_weights, grad_output, /* transpose_a */ true, /* transpose_b */ false); auto grad_attention_weights = matmul(grad_output, value->get_value(), /* transpose_a */ false, /* transpose_b */ true); - auto grad_scaled_dot = ttnn::multiply( + auto grad_scaled_dot = ttnn::moreh_softmax_backward( attention_weights, - ttnn::subtract( - grad_attention_weights, - ttnn_fixed::sum_over_dim(ttnn::multiply(attention_weights, grad_attention_weights), 3))); - if (mask.has_value()) { - grad_scaled_dot = ttnn::multiply(grad_scaled_dot, mask.value()->get_value()); - } - + grad_attention_weights, + /* axis */ 3, + /* output */ std::nullopt, + ttnn::operations::moreh::moreh_softmax_backward::MorehSoftmaxBackwardOp::SOFTMAX, + ttnn::operations::moreh::moreh_softmax_backward::MorehSoftmaxBackwardOpParallelizationStrategy::NONE, + /* output_mem_config */ std::nullopt, + /* compute_kernel_config */ core::ComputeKernelConfig::precise()); + + grad_scaled_dot = ttnn::multiply(grad_scaled_dot, scale); auto grad_q = matmul( grad_scaled_dot, key->get_value(), /* transpose_a */ false, /* transpose_b */ false); - grad_q = ttnn::multiply(grad_q, scale); auto grad_k = matmul( grad_scaled_dot, query->get_value(), /* transpose_a */ true, /* transpose_b */ false); - grad_k = ttnn::multiply(grad_k, scale); query->add_grad(grad_q); key->add_grad(grad_k); diff --git a/tt-train/sources/ttml/ops/unary_ops.cpp b/tt-train/sources/ttml/ops/unary_ops.cpp index 5f13d8402538..b88eb0537e39 100644 --- a/tt-train/sources/ttml/ops/unary_ops.cpp +++ b/tt-train/sources/ttml/ops/unary_ops.cpp @@ -64,6 +64,34 @@ autograd::TensorPtr log_softmax(const autograd::TensorPtr& tensor, int dim) { return out; } +autograd::TensorPtr log_softmax_moreh(const autograd::TensorPtr& tensor, int dim) { + auto log_softmax = ttnn::moreh_softmax( + tensor->get_value(), + /* axis */ dim, + /* output */ std::nullopt, + ttnn::operations::moreh::moreh_softmax::MorehSoftmaxOp::LOGSOFTMAX, + ttnn::operations::moreh::moreh_softmax::MorehSoftmaxOpParallelizationStrategy::NONE, + /* output_mem_config */ std::nullopt, + /* compute_kernel_config */ core::ComputeKernelConfig::precise()); + auto out = autograd::create_tensor(log_softmax); + + autograd::GradFunction grad = [tensor, out, dim]() { + auto grad = ttnn::moreh_softmax_backward( + out->get_value(), + out->get_grad(), + /* axis */ dim, + /* output */ std::nullopt, + ttnn::operations::moreh::moreh_softmax_backward::MorehSoftmaxBackwardOp::LOGSOFTMAX, + ttnn::operations::moreh::moreh_softmax_backward::MorehSoftmaxBackwardOpParallelizationStrategy::NONE, + /* output_mem_config */ std::nullopt, + /* compute_kernel_config */ core::ComputeKernelConfig::precise()); + tensor->add_grad(grad); + }; + auto links = autograd::get_links(tensor); + out->set_node(autograd::ctx().add_backward_node(std::move(grad), links)); + return out; +} + autograd::TensorPtr mean(const autograd::TensorPtr& tensor) { auto shape = core::create_shape({1, 1, 1, 1}); autograd::TensorPtr out = autograd::create_tensor(core::from_vector({0.F}, shape, &autograd::ctx().get_device())); @@ -78,7 +106,13 @@ autograd::TensorPtr mean(const autograd::TensorPtr& tensor) { autograd::GradFunction grad = [tensor, out]() { auto resulting_shape = tensor->get_value().get_shape(); auto res = ttnn::moreh_mean_backward( - out->get_grad(), std::nullopt, false, resulting_shape, std::nullopt, std::nullopt, std::nullopt); + out->get_grad(), + std::nullopt, + false, + resulting_shape, + std::nullopt, + std::nullopt, + core::ComputeKernelConfig::precise()); tensor->add_grad(res); }; auto links = autograd::get_links(tensor); diff --git a/tt-train/sources/ttml/ops/unary_ops.hpp b/tt-train/sources/ttml/ops/unary_ops.hpp index 839e86a8fd01..669ee04233b0 100644 --- a/tt-train/sources/ttml/ops/unary_ops.hpp +++ b/tt-train/sources/ttml/ops/unary_ops.hpp @@ -14,5 +14,5 @@ autograd::TensorPtr mean(const autograd::TensorPtr& tensor); autograd::TensorPtr sum(const autograd::TensorPtr& tensor); autograd::TensorPtr broadcast_batch(const autograd::TensorPtr& tensor, uint32_t new_batch_dim); autograd::TensorPtr log_softmax(const autograd::TensorPtr& tensor, int dim); - +autograd::TensorPtr log_softmax_moreh(const autograd::TensorPtr& tensor, int dim); } // namespace ttml::ops diff --git a/tt-train/tests/model/nano_gpt_test.cpp b/tt-train/tests/model/nano_gpt_test.cpp index 5b10d94f2b6a..53b80a00ab1f 100644 --- a/tt-train/tests/model/nano_gpt_test.cpp +++ b/tt-train/tests/model/nano_gpt_test.cpp @@ -238,12 +238,17 @@ If one of these tests fails, it means one (or more) of the following: */ TEST_F(NanoGPTTest, AdamW) { + GTEST_SKIP() << "Skipping AdamW"; + return; if (should_run_tests()) { train_test(/* use_moreh_adamw */ false); } } TEST_F(NanoGPTTest, MorehAdamW) { + GTEST_SKIP() << "Skipping MorehAdamW"; + return; + if (should_run_tests()) { train_test(/* use_moreh_adamw */ true); } diff --git a/tt-train/tests/ops/unary_ops_test.cpp b/tt-train/tests/ops/unary_ops_test.cpp index b67a4617b9c2..f6cee5cfbb61 100644 --- a/tt-train/tests/ops/unary_ops_test.cpp +++ b/tt-train/tests/ops/unary_ops_test.cpp @@ -46,6 +46,7 @@ TEST_F(UnaryOpsTest, GlobalMean) { } TEST_F(UnaryOpsTest, LogSoftmax) { + GTEST_SKIP() << "Skipping LogSoftmax"; auto* device = &ttml::autograd::ctx().get_device(); std::vector test_data = {-0.1F, -0.2F, -0.3F, -0.4F, 0.F, -0.2F, -0.3F, -0.4F}; auto tensor = ttml::core::from_vector(test_data, ttml::core::create_shape({2, 1, 1, 4}), device); From e882125cb32a6bc2369280f1c57c297bac426b4e Mon Sep 17 00:00:00 2001 From: Martin Chang Date: Fri, 20 Dec 2024 03:44:26 +0800 Subject: [PATCH 75/87] Optimized FreeList allocator (#15536) ### Ticket [Link to Github Issue](https://github.com/tenstorrent/tt-metal/issues/15535) ### Problem description The current FreeList allocator is fairly slow. Epically under my GGML backend, it becomes unusable slow under high memory pressure. This can be mitigated by using an better optimized allocator. Detailed benchmark can be found my GitHub repo: https://github.com/marty1885/tt-alloc-opt. In short, the new allocator is over an order of magnitude faster (~60x) for large amounts of allocations (DRAM) and at least 2x on smaller allocations (L1). I have tested the new allocator it works against my GGML backend on GS. Due to networking issues, I don't have WH tests ready. I can't until after Thanksgiving. But still want to open the PR and start the discussion. Parameters like table sizes and base values most likely needs to be tuned for real world workloads. Even without changes, it should be faster then the existing one by default. ``` 2024-11-28T11:09:30+08:00 Running ./tt-alloc-opt Run on (16 X 5132 MHz CPU s) CPU Caches: L1 Data 32 KiB (x8) L1 Instruction 32 KiB (x8) L2 Unified 1024 KiB (x8) L3 Unified 16384 KiB (x1) Load Average: 1.02, 0.90, 0.84 ***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. ------------------------------------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------------------------------------- FreeListOpt/WorstCase 156211 ns 155689 ns 4428 FreeListOpt/MixedAllocations 59938 ns 59760 ns 11755 FreeListOpt/TypicalCase 32595 ns 32492 ns 21033 FreeListOpt/Small 3676 ns 3663 ns 179884 FreeListOpt/GetAvailableAddresses 409 ns 407 ns 1726829 FreeListOpt/Statistics 379 ns 378 ns 1848454 FreeListOpt/ShrinkReset 11.7 ns 11.6 ns 60177702 FreeList[BestMatch]/WorstCase 8430132 ns 8396434 ns 81 FreeList[BestMatch]/MixedAllocations 2219312 ns 2211079 ns 314 FreeList[BestMatch]/TypicalCase 863138 ns 860375 ns 802 FreeList[BestMatch]/Small 7968 ns 7944 ns 85656 FreeList[BestMatch]/GetAvailableAddresses 893 ns 890 ns 779186 FreeList[BestMatch]/Statistics 1107 ns 1103 ns 643461 FreeList[BestMatch]/ShrinkReset 3.44 ns 3.43 ns 204268507 FreeList[FirstMatch]/WorstCase 7409143 ns 7380524 ns 95 FreeList[FirstMatch]/MixedAllocations 2283444 ns 2274426 ns 296 FreeList[FirstMatch]/TypicalCase 864990 ns 861908 ns 809 FreeList[FirstMatch]/Small 7726 ns 7705 ns 91195 FreeList[FirstMatch]/GetAvailableAddresses 886 ns 882 ns 794174 FreeList[FirstMatch]/Statistics 1092 ns 1088 ns 645254 FreeList[FirstMatch]/ShrinkReset 3.47 ns 3.45 ns 202517500 ``` The idea of a segregated list comes from the [TLSF](http://www.gii.upv.es/tlsf/files/papers/ecrts04_tlsf.pdf) algorithm. However the intention is not to implement TLSF nor to make a real time allocator. To keep the behavior the same as the exiting FreeList implementation (L1 is small, reducing fragmentation is important) - 1) a full scan of blocks in the size class is done to determine the best place to allocate the new memory. 2) only one layer exists in this implementation's segregation list instead of two. As my synthetic benchmark indicating cache locality is more important then reduced number of blocks scanned. Though, it can be easily converted to a proper 2 layer design by changing how the index is calculated. I have WIP optimizations that should provide an extra 5~10% throughput. But again, I wat to start the discussion early as a new allocator is a big change. For license concerns. TT can have the file licensed under Apache 2.0. I licensed the version under 0BSD in my repo to make the process easy. ### What's changed * Implemented a new `FreeListOpt` allocator * Same algorithm as the existing `FreeList` allocator under BEST mode * Produces the same results * Removes the use of linked list and shared_ptr. Everything is in SoA to maximize cache efficency * Table to store free blocks by size so large allocations won't need to look at smaller blocks * Hash table to convert addresses back into blocks. No more list walk * Same memory coalescing as the existing allocator * Metadata blocks are reused internally to minimize memory allocation * Tests for the new allocator. * `dump_block` new accepts `ostream` instead of `ofstream` ### Checklist - [ ] Post commit CI passes - [ ] Blackhole Post commit (if applicable) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] New/Existing tests provide coverage for changes --- tests/tt_metal/tt_metal/api/CMakeLists.txt | 1 + .../test_free_list_opt_allocator.cpp | 342 ++++++++++ tt_metal/impl/CMakeLists.txt | 1 + .../algorithms/allocator_algorithm.hpp | 2 +- .../impl/allocator/algorithms/free_list.cpp | 4 +- .../impl/allocator/algorithms/free_list.hpp | 4 +- .../allocator/algorithms/free_list_opt.cpp | 630 ++++++++++++++++++ .../allocator/algorithms/free_list_opt.hpp | 126 ++++ tt_metal/impl/allocator/allocator.cpp | 6 +- 9 files changed, 1108 insertions(+), 8 deletions(-) create mode 100644 tests/tt_metal/tt_metal/api/allocator/test_free_list_opt_allocator.cpp create mode 100644 tt_metal/impl/allocator/algorithms/free_list_opt.cpp create mode 100644 tt_metal/impl/allocator/algorithms/free_list_opt.hpp diff --git a/tests/tt_metal/tt_metal/api/CMakeLists.txt b/tests/tt_metal/tt_metal/api/CMakeLists.txt index 864e4b911e9f..042e199476c5 100644 --- a/tests/tt_metal/tt_metal/api/CMakeLists.txt +++ b/tests/tt_metal/tt_metal/api/CMakeLists.txt @@ -1,5 +1,6 @@ set(UNIT_TESTS_API_SRC ${CMAKE_CURRENT_SOURCE_DIR}/allocator/test_free_list_allocator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/allocator/test_free_list_opt_allocator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/allocator/test_l1_banking_allocator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/circular_buffer/test_CircularBuffer_allocation.cpp ${CMAKE_CURRENT_SOURCE_DIR}/circular_buffer/test_CircularBuffer_creation.cpp diff --git a/tests/tt_metal/tt_metal/api/allocator/test_free_list_opt_allocator.cpp b/tests/tt_metal/tt_metal/api/allocator/test_free_list_opt_allocator.cpp new file mode 100644 index 000000000000..6d4488afa543 --- /dev/null +++ b/tests/tt_metal/tt_metal/api/allocator/test_free_list_opt_allocator.cpp @@ -0,0 +1,342 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include "allocator/allocator.hpp" +#include "tt_metal/impl/allocator/algorithms/free_list_opt.hpp" + +// UDL to convert integer literals to SI units +constexpr size_t operator"" _KiB(unsigned long long x) { return x * 1024; } +constexpr size_t operator"" _MiB(unsigned long long x) { return x * 1024 * 1024; } +constexpr size_t operator"" _GiB(unsigned long long x) { return x * 1024 * 1024 * 1024; } + +TEST(FreeListOptTest, Allocation) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto a = allocator.allocate(1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(a.value(), 0); + + auto b = allocator.allocate(1_KiB); + ASSERT_TRUE(b.has_value()); + ASSERT_EQ(b.value(), 1_KiB); +} + +TEST(FreeListOptTest, Alignment) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1, 1_KiB); + auto a = allocator.allocate(64); + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(a.value(), 0); + auto b = allocator.allocate(64); + ASSERT_TRUE(b.has_value()); + ASSERT_EQ(b.value(), 1_KiB); +} + +TEST(FreeListOptTest, MinAllocationSize) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1); + auto a = allocator.allocate(1); + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(a.value(), 0); + auto b = allocator.allocate(1); + ASSERT_TRUE(b.has_value()); + ASSERT_EQ(b.value(), 1_KiB); +} + +TEST(FreeListOptTest, Clear) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto a = allocator.allocate(1_KiB); + auto b = allocator.allocate(1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_TRUE(b.has_value()); + allocator.clear(); + auto c = allocator.allocate(1_KiB); + ASSERT_TRUE(c.has_value()); + ASSERT_EQ(c.value(), 0); +} + +TEST(FreeListOptTest, AllocationAndDeallocation) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + std::vector> allocations(10); + + // Deallocate in order + for(size_t i = 0; i < allocations.size(); i++) { + allocations[i] = allocator.allocate(1_KiB); + ASSERT_TRUE(allocations[i].has_value()); + } + + for(size_t i = allocations.size(); i > 0; i--) { + allocator.deallocate(allocations[i - 1].value()); + } + + // Deallocate in reverse order + for(size_t i = 0; i < allocations.size(); i++) { + allocations[i] = allocator.allocate(1_KiB); + ASSERT_TRUE(allocations[i].has_value()); + } + + for(size_t i = 0; i < allocations.size(); i++) { + allocator.deallocate(allocations[i].value()); + } +} + +TEST(FreeListOptTest, AllocateAtAddress) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto a = allocator.allocate(1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(a.value(), 0); + + auto b = allocator.allocate_at_address(1_KiB, 1_KiB); + ASSERT_TRUE(b.has_value()); + ASSERT_EQ(b.value(), 1_KiB); + + // Address is already allocated + auto c = allocator.allocate_at_address(1_KiB, 1_KiB); + ASSERT_FALSE(c.has_value()); + + auto d = allocator.allocate_at_address(2_KiB, 1_KiB); + ASSERT_TRUE(d.has_value()); + ASSERT_EQ(d.value(), 2_KiB); + + allocator.deallocate(a.value()); + auto e = allocator.allocate_at_address(0, 1_KiB); + ASSERT_TRUE(e.has_value()); + ASSERT_EQ(e.value(), 0); +} + +TEST(FreeListOptTest, AllocateAtAddressInteractions) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto wedge = allocator.allocate_at_address(32_KiB, 1_KiB); + + auto a = allocator.allocate(1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(a.value(), 0); + + auto z = allocator.allocate(1_KiB, false); + ASSERT_TRUE(z.has_value()); + ASSERT_EQ(z.value(), 32_KiB - 1_KiB); // Counterintuitive, but because we use BestFit, it will find the smaller block at the beginning + + auto b = allocator.allocate(1_KiB); + ASSERT_TRUE(b.has_value()); + ASSERT_EQ(b.value(), 1_KiB); +} + +TEST(FreeListOptTest, ShrinkAndReset) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto a = allocator.allocate(1_KiB); + auto b = allocator.allocate(1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_TRUE(b.has_value()); + allocator.deallocate(a.value()); + + allocator.shrink_size(1_KiB); + auto c = allocator.allocate_at_address(0, 1_KiB); + ASSERT_FALSE(c.has_value()); + + auto d = allocator.allocate_at_address(1_KiB, 1_KiB); + ASSERT_FALSE(d.has_value()); + + allocator.reset_size(); + allocator.deallocate(b.value()); + + auto e = allocator.allocate(2_KiB); + ASSERT_TRUE(e.has_value()); +} + +TEST(FreeListOptTest, Statistics) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto a = allocator.allocate(1_KiB); + auto b = allocator.allocate(1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_TRUE(b.has_value()); + allocator.deallocate(a.value()); + + auto stats = allocator.get_statistics(); + ASSERT_EQ(stats.total_allocated_bytes, 1_KiB); +} + +TEST(FreeListOptTest, AllocateFromTop) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto a = allocator.allocate(1_KiB, false); + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(a.value(), 1_GiB - 1_KiB); + + auto b = allocator.allocate(1_KiB, false); + ASSERT_TRUE(b.has_value()); + ASSERT_EQ(b.value(), 1_GiB - 2_KiB); + + auto c = allocator.allocate(1_KiB); + ASSERT_TRUE(c.has_value()); + ASSERT_EQ(c.value(), 0); +} + +TEST(FreeListOptTest, Coalescing) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto a = allocator.allocate(1_KiB); + auto b = allocator.allocate(1_KiB); + auto c = allocator.allocate(1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_TRUE(b.has_value()); + ASSERT_TRUE(c.has_value()); + allocator.deallocate(b.value()); + allocator.deallocate(a.value()); + + auto d = allocator.allocate(2_KiB); + ASSERT_TRUE(d.has_value()); + ASSERT_EQ(d.value(), 0); +} + +TEST(FreeListOptTest, CoalescingAfterResetShrink) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto a = allocator.allocate(1_KiB); + auto b = allocator.allocate(1_KiB); + auto c = allocator.allocate(1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_TRUE(b.has_value()); + ASSERT_TRUE(c.has_value()); + allocator.deallocate(b.value()); + allocator.deallocate(a.value()); + + allocator.shrink_size(1_KiB); + auto d = allocator.allocate(2_KiB); + allocator.reset_size(); + auto e = allocator.allocate(2_KiB); + ASSERT_TRUE(e.has_value()); + ASSERT_EQ(e.value(), 0); +} + +TEST(FreeListOptTest, OutOfMemory) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto a = allocator.allocate(1_GiB); + ASSERT_TRUE(a.has_value()); + auto b = allocator.allocate(1_KiB); + ASSERT_FALSE(b.has_value()); + + allocator.clear(); + auto c = allocator.allocate(1_GiB - 1_KiB); + ASSERT_TRUE(c.has_value()); + auto d = allocator.allocate(2_KiB); + ASSERT_FALSE(d.has_value()); +} + +TEST(FreeListOptTest, AvailableAddresses) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto a = allocator.allocate(1_KiB); + auto aval = allocator.available_addresses(1_KiB); + ASSERT_EQ(aval.size(), 1); + ASSERT_EQ(aval[0].first, 1_KiB); // Start address + ASSERT_EQ(aval[0].second, 1_GiB); // End address + allocator.clear(); + + a = allocator.allocate(1_KiB); + auto b = allocator.allocate(1_KiB); + auto c = allocator.allocate(1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(a.value(), 0); + ASSERT_TRUE(b.has_value()); + ASSERT_EQ(b.value(), 1_KiB); + ASSERT_TRUE(c.has_value()); + ASSERT_EQ(c.value(), 2_KiB); + allocator.deallocate(b.value()); + aval = allocator.available_addresses(1_KiB); + ASSERT_EQ(aval.size(), 2); + ASSERT_EQ(aval[0].first, 1_KiB); // Start address + ASSERT_EQ(aval[0].second, 2_KiB); // End address + ASSERT_EQ(aval[1].first, 3_KiB); // Start address + ASSERT_EQ(aval[1].second, 1_GiB); // End address + + allocator.clear(); + a = allocator.allocate(1_KiB); + b = allocator.allocate(1_KiB); + c = allocator.allocate(1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(a.value(), 0); + ASSERT_TRUE(b.has_value()); + ASSERT_EQ(b.value(), 1_KiB); + ASSERT_TRUE(c.has_value()); + ASSERT_EQ(c.value(), 2_KiB); + allocator.deallocate(b.value()); + aval = allocator.available_addresses(10_KiB); + ASSERT_EQ(aval.size(), 1); + ASSERT_EQ(aval[0].first, 3_KiB); // Start address + ASSERT_EQ(aval[0].second, 1_GiB); // End address +} + +TEST(FreeListOptTest, LowestOccupiedAddress) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto a = allocator.allocate(1_KiB); + auto b = allocator.allocate(1_KiB); + auto c = allocator.allocate(1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(a.value(), 0); + ASSERT_TRUE(b.has_value()); + ASSERT_EQ(b.value(), 1_KiB); + ASSERT_TRUE(c.has_value()); + ASSERT_EQ(c.value(), 2_KiB); + auto loa = allocator.lowest_occupied_address(); + ASSERT_EQ(loa.value(), 0); + allocator.deallocate(a.value()); + loa = allocator.lowest_occupied_address(); + ASSERT_EQ(loa.value(), 1_KiB); + allocator.deallocate(b.value()); + loa = allocator.lowest_occupied_address(); + ASSERT_EQ(loa.value(), 2_KiB); + allocator.deallocate(c.value()); + loa = allocator.lowest_occupied_address(); + ASSERT_FALSE(loa.has_value()); +} + +TEST(FreeListOptTest, LowestOccupiedAddressWithAllocateAt) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB); + auto a = allocator.allocate_at_address(1_KiB, 1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(a.value(), 1_KiB); + auto loa = allocator.lowest_occupied_address(); + ASSERT_EQ(loa.value(), 1_KiB); + allocator.deallocate(a.value()); + loa = allocator.lowest_occupied_address(); + ASSERT_FALSE(loa.has_value()); +} + +TEST(FreeListOptTest, FirstFit) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB, tt::tt_metal::allocator::FreeListOpt::SearchPolicy::FIRST); + auto a = allocator.allocate(1_KiB); + auto b = allocator.allocate(3_KiB); + auto c = allocator.allocate(1_KiB); + auto d = allocator.allocate(1_KiB); + auto e = allocator.allocate(1_KiB); + + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(a.value(), 0); + ASSERT_TRUE(b.has_value()); + ASSERT_EQ(b.value(), 1_KiB); + ASSERT_TRUE(c.has_value()); + ASSERT_EQ(c.value(), 4_KiB); + ASSERT_TRUE(d.has_value()); + ASSERT_EQ(d.value(), 5_KiB); + ASSERT_TRUE(e.has_value()); + ASSERT_EQ(e.value(), 6_KiB); + + allocator.deallocate(b.value()); + allocator.deallocate(d.value()); + + auto f = allocator.allocate(1_KiB); + ASSERT_TRUE(f.has_value()); + ASSERT_EQ(f.value(), 1_KiB); +} + +TEST(FreeListOptTest, FirstFitAllocateAtAddressInteractions) { + auto allocator = tt::tt_metal::allocator::FreeListOpt(1_GiB, 0, 1_KiB, 1_KiB, tt::tt_metal::allocator::FreeListOpt::SearchPolicy::FIRST); + auto wedge = allocator.allocate_at_address(32_KiB, 1_KiB); + + auto a = allocator.allocate(1_KiB); + ASSERT_TRUE(a.has_value()); + ASSERT_EQ(a.value(), 0); + + auto z = allocator.allocate(1_KiB, false); + ASSERT_TRUE(z.has_value()); + ASSERT_EQ(z.value(), 1_GiB - 1_KiB); + + auto b = allocator.allocate(1_KiB); + ASSERT_TRUE(b.has_value()); + ASSERT_EQ(b.value(), 1_KiB); +} diff --git a/tt_metal/impl/CMakeLists.txt b/tt_metal/impl/CMakeLists.txt index cad106642c9b..9bd981b89d3a 100644 --- a/tt_metal/impl/CMakeLists.txt +++ b/tt_metal/impl/CMakeLists.txt @@ -12,6 +12,7 @@ set(IMPL_SRC ${CMAKE_CURRENT_SOURCE_DIR}/buffers/semaphore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/kernels/kernel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/allocator/algorithms/free_list.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/allocator/algorithms/free_list_opt.cpp ${CMAKE_CURRENT_SOURCE_DIR}/allocator/allocator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/allocator/basic_allocator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/allocator/l1_banking_allocator.cpp diff --git a/tt_metal/impl/allocator/algorithms/allocator_algorithm.hpp b/tt_metal/impl/allocator/algorithms/allocator_algorithm.hpp index aa0bb5161012..6eec0150db8d 100644 --- a/tt_metal/impl/allocator/algorithms/allocator_algorithm.hpp +++ b/tt_metal/impl/allocator/algorithms/allocator_algorithm.hpp @@ -63,7 +63,7 @@ class Algorithm { virtual Statistics get_statistics() const = 0; - virtual void dump_blocks(std::ofstream& out) const = 0; + virtual void dump_blocks(std::ostream& out) const = 0; virtual void shrink_size(DeviceAddr shrink_size, bool bottom_up = true) = 0; diff --git a/tt_metal/impl/allocator/algorithms/free_list.cpp b/tt_metal/impl/allocator/algorithms/free_list.cpp index c320b205b90a..adc75c102e09 100644 --- a/tt_metal/impl/allocator/algorithms/free_list.cpp +++ b/tt_metal/impl/allocator/algorithms/free_list.cpp @@ -431,12 +431,12 @@ Statistics FreeList::get_statistics() const { return stats; } -void FreeList::dump_block(const boost::local_shared_ptr& block, std::ofstream& out) const { +void FreeList::dump_block(const boost::local_shared_ptr& block, std::ostream& out) const { auto alloc_status = this->is_allocated(block) ? "Y" : "N"; out << ",,," << (block->address + this->offset_bytes_) << "," << (block->size) << "," << alloc_status << "\n"; } -void FreeList::dump_blocks(std::ofstream& out) const { +void FreeList::dump_blocks(std::ostream& out) const { out << ",,Blocks:,Address (B),Size (B),Allocated (Y/N)\n"; boost::local_shared_ptr curr_block = this->block_head_; while (curr_block != nullptr) { diff --git a/tt_metal/impl/allocator/algorithms/free_list.hpp b/tt_metal/impl/allocator/algorithms/free_list.hpp index 6337e51f2ac3..467fc8c3ecea 100644 --- a/tt_metal/impl/allocator/algorithms/free_list.hpp +++ b/tt_metal/impl/allocator/algorithms/free_list.hpp @@ -36,7 +36,7 @@ class FreeList : public Algorithm { Statistics get_statistics() const; - void dump_blocks(std::ofstream& out) const; + void dump_blocks(std::ostream& out) const; void shrink_size(DeviceAddr shrink_size, bool bottom_up = true); @@ -66,7 +66,7 @@ class FreeList : public Algorithm { boost::local_shared_ptr next_free = nullptr; }; - void dump_block(const boost::local_shared_ptr& block, std::ofstream& out) const; + void dump_block(const boost::local_shared_ptr& block, std::ostream& out) const; bool is_allocated(const boost::local_shared_ptr& block) const; diff --git a/tt_metal/impl/allocator/algorithms/free_list_opt.cpp b/tt_metal/impl/allocator/algorithms/free_list_opt.cpp new file mode 100644 index 000000000000..bd24c0c871b8 --- /dev/null +++ b/tt_metal/impl/allocator/algorithms/free_list_opt.cpp @@ -0,0 +1,630 @@ +// SPDX-FileCopyrightText: © 2023 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "tt_metal/impl/allocator/algorithms/free_list_opt.hpp" +#include "common/assert.hpp" +#include "llrt/hal.hpp" +#include "tt_metal/impl/allocator/algorithms/allocator_algorithm.hpp" +#include +#include +#include +#include +#include +#include +#include + +inline size_t intlg2(size_t n) { + // std::log2() is slow + size_t count = 0; + while (n > 0) { + count++; + n >>= 1; + } + return count; +} + +inline size_t num_segerated_classes(size_t max_size_bytes, size_t size_segregated_base) { + size_t n = max_size_bytes / size_segregated_base; + ssize_t count = intlg2(n); + // 128MB as the last seggregated class size should be enough + // avoid having too many classes as iterating them is not free + ssize_t max_count = intlg2(128 * 1024 * 1024 / size_segregated_base); + return std::clamp(count, ssize_t{2}, max_count); +} + +namespace tt { + +namespace tt_metal { + +namespace allocator { + +FreeListOpt::FreeListOpt( + DeviceAddr max_size_bytes, + DeviceAddr offset_bytes, + DeviceAddr min_allocation_size, + DeviceAddr alignment, + SearchPolicy policy) : + policy_(policy), + size_segregated_count((num_segerated_classes(max_size_bytes, size_segregated_base))), + Algorithm(max_size_bytes, offset_bytes, min_allocation_size, alignment) { + // Reduce reallocations by reserving memory for free list components + constexpr size_t initial_block_count = 64; + block_address_.reserve(initial_block_count); + block_size_.reserve(initial_block_count); + block_prev_block_.reserve(initial_block_count); + block_next_block_.reserve(initial_block_count); + block_is_allocated_.reserve(initial_block_count); + free_meta_block_indices_.reserve(initial_block_count); + meta_block_is_allocated_.reserve(initial_block_count); + free_blocks_segregated_by_size_.resize(size_segregated_count); + for (auto& free_blocks : free_blocks_segregated_by_size_) { + free_blocks.reserve(initial_block_count); + } + allocated_block_table_.resize(n_alloc_table_buckets); + for (auto& bucket : allocated_block_table_) { + bucket.reserve(n_alloc_table_init_bucket_size); + } + + init(); +} + +void FreeListOpt::init() { + max_size_bytes_ += shrink_size_; + shrink_size_ = 0; + + block_address_.clear(); + block_size_.clear(); + block_prev_block_.clear(); + block_next_block_.clear(); + block_is_allocated_.clear(); + free_meta_block_indices_.clear(); + meta_block_is_allocated_.clear(); + for (auto& bucket : allocated_block_table_) { + bucket.clear(); + } + for (auto& free_blocks : free_blocks_segregated_by_size_) { + free_blocks.clear(); + } + + // Create a single block that spans the entire memory + block_address_.push_back(0); + block_size_.push_back(max_size_bytes_); + block_prev_block_.push_back(-1); + block_next_block_.push_back(-1); + block_is_allocated_.push_back(false); + meta_block_is_allocated_.push_back(true); + free_blocks_segregated_by_size_[get_size_segregated_index(max_size_bytes_)].push_back(0); +} + +std::optional FreeListOpt::allocate(DeviceAddr size_bytes, bool bottom_up, DeviceAddr address_limit) { + DeviceAddr alloc_size = align(std::max(size_bytes, min_allocation_size_)); + + // Find the best free block by looking at the segregated free blocks, if we can find a block in it's size class + // we can be confident that it's the best block to allocate from. Else, look at the next size class. However the + // blocks within a size class are not sorted by size, so we may not always find the best block. + + ssize_t target_block_index = -1; + size_t size_segregated_index = get_size_segregated_index(alloc_size); + TT_ASSERT(size_segregated_index < size_segregated_count, "Size segregated index out of bounds"); + std::vector* segregated_list = nullptr; + size_t segregated_item_index = 0; + DeviceAddr best_address = bottom_up ? ~(DeviceAddr)0 : 0; + + for (size_t i = size_segregated_index; i < free_blocks_segregated_by_size_.size(); i++) { + auto& free_blocks = free_blocks_segregated_by_size_[i]; + ssize_t increment = bottom_up ? 1 : -1; + for (ssize_t j = bottom_up ? 0 : free_blocks.size() - 1; j >= 0 && j < free_blocks.size(); j += increment) { + size_t block_index = free_blocks[j]; + if (policy_ == SearchPolicy::BEST) { + if (block_size_[block_index] == alloc_size) { + target_block_index = block_index; + segregated_list = &free_blocks; + segregated_item_index = j; + break; + } else if ( + block_size_[block_index] >= alloc_size && + (target_block_index == -1 || block_size_[block_index] < block_size_[target_block_index])) { + target_block_index = block_index; + segregated_list = &free_blocks; + segregated_item_index = j; + } + if (target_block_index != -1) { + break; + } + } else { + if (block_size_[block_index] < alloc_size) { + continue; + } + + bool address_better = + bottom_up ? block_address_[block_index] < best_address : block_address_[block_index] > best_address; + if (target_block_index == -1 || address_better) { + target_block_index = block_index; + segregated_list = &free_blocks; + segregated_item_index = j; + best_address = block_address_[block_index]; + break; + } + } + } + } + + if (target_block_index == -1) { + return std::nullopt; + } + TT_ASSERT(segregated_list != nullptr, "Segregated list is null"); + TT_ASSERT(segregated_item_index < segregated_list->size(), "Segregated item index out of bounds"); + TT_ASSERT( + block_is_allocated_[target_block_index] == false, "Block we are trying allocate from is already allocated"); + segregated_list->erase(segregated_list->begin() + segregated_item_index); + + // Allocate the block + size_t offset = 0; + if (!bottom_up) { + offset = block_size_[target_block_index] - alloc_size; + } + size_t allocated_block_index = allocate_in_block(target_block_index, alloc_size, offset); + DeviceAddr start_address = block_address_[allocated_block_index]; + if (start_address + offset_bytes_ < address_limit) { + TT_THROW( + "Out of Memory: Cannot allocate at an address below {}. Allocation at {}", + address_limit, + start_address + offset_bytes_); + } + update_lowest_occupied_address(start_address); + return start_address + offset_bytes_; +} + +std::optional FreeListOpt::allocate_at_address(DeviceAddr absolute_start_address, DeviceAddr size_bytes) { + // Nothing we can do but scan the free list + size_t alloc_size = align(std::max(size_bytes, min_allocation_size_)); + ssize_t target_block_index = -1; + DeviceAddr start_address = absolute_start_address - offset_bytes_; + for (size_t i = 0; i < block_address_.size(); i++) { + size_t block_start = block_address_[i]; + size_t block_end = block_start + block_size_[i]; + if (start_address >= block_start && start_address + alloc_size <= block_end) { + target_block_index = i; + break; + } + } + + if (target_block_index == -1 || block_is_allocated_[target_block_index]) { + return std::nullopt; + } + + // Find the relevant size segregated list + size_t size_segregated_index = get_size_segregated_index(block_size_[target_block_index]); + std::vector& segregated_list = free_blocks_segregated_by_size_[size_segregated_index]; + auto it = std::find(segregated_list.begin(), segregated_list.end(), target_block_index); + TT_ASSERT(it != segregated_list.end(), "Block not found in size segregated list"); + segregated_list.erase(it); + + size_t offset = start_address - block_address_[target_block_index]; + size_t alloc_block_index = allocate_in_block(target_block_index, alloc_size, offset); + update_lowest_occupied_address(start_address); + return absolute_start_address; +} + +size_t FreeListOpt::allocate_in_block(size_t block_index, DeviceAddr alloc_size, size_t offset) { + if (block_size_[block_index] == alloc_size && offset == 0) { + block_is_allocated_[block_index] = true; + insert_block_to_alloc_table(block_address_[block_index], block_index); + return block_index; + } + + bool left_aligned = offset == 0; + bool right_aligned = offset + alloc_size == block_size_[block_index]; + + // Create free space if not left/right aligned + if (!left_aligned) { + size_t free_block_size = offset; + DeviceAddr free_block_address = block_address_[block_index]; + ssize_t prev_block = block_prev_block_[block_index]; + ssize_t next_block = block_next_block_[block_index]; + block_size_[block_index] -= offset; + block_address_[block_index] += offset; + size_t new_block_index = alloc_meta_block(free_block_address, free_block_size, prev_block, block_index, false); + if (prev_block != -1) { + block_next_block_[prev_block] = new_block_index; + } + block_prev_block_[block_index] = new_block_index; + + insert_block_to_segregated_list(new_block_index); + } + + if (!right_aligned) { + size_t free_block_size = block_size_[block_index] - alloc_size; + DeviceAddr free_block_address = block_address_[block_index] + alloc_size; + ssize_t prev_block = block_index; + ssize_t next_block = block_next_block_[block_index]; + block_size_[block_index] -= free_block_size; + size_t new_block_index = alloc_meta_block(free_block_address, free_block_size, prev_block, next_block, false); + if (next_block != -1) { + block_prev_block_[next_block] = new_block_index; + } + block_next_block_[block_index] = new_block_index; + + insert_block_to_segregated_list(new_block_index); + } + block_is_allocated_[block_index] = true; + insert_block_to_alloc_table(block_address_[block_index], block_index); + + return block_index; +} + +void FreeListOpt::deallocate(DeviceAddr absolute_address) { + // The existing FreeList implementation does not check if the address is actually allocated. Just return if it's not + // Do we want to keep this behavior? + + DeviceAddr addr = absolute_address - offset_bytes_; + auto block_index_opt = get_and_remove_from_alloc_table(addr); + if (!block_index_opt.has_value()) { + return; + } + size_t block_index = *block_index_opt; + block_is_allocated_[block_index] = false; + ssize_t prev_block = block_prev_block_[block_index]; + ssize_t next_block = block_next_block_[block_index]; + + // Merge with previous block if it's free + if (prev_block != -1 && !block_is_allocated_[prev_block]) { + // Look into the size segregated list to remove the block + size_t size_segregated_index = get_size_segregated_index(block_size_[prev_block]); + std::vector& segregated_list = free_blocks_segregated_by_size_[size_segregated_index]; + auto it = std::find(segregated_list.begin(), segregated_list.end(), prev_block); + TT_ASSERT( + it != segregated_list.end(), + "Prev block {} not found in size segregated list during deallocation of block {}", + prev_block, + block_index); + segregated_list.erase(it); + + block_size_[prev_block] += block_size_[block_index]; + block_next_block_[prev_block] = next_block; + if (next_block != -1) { + block_prev_block_[next_block] = prev_block; + } + free_meta_block(block_index); + block_index = prev_block; + } + + // Merge with next block if it's free + if (next_block != -1 && !block_is_allocated_[next_block]) { + // Look into the size segregated list to remove the block + size_t size_segregated_index = get_size_segregated_index(block_size_[next_block]); + std::vector& segregated_list = free_blocks_segregated_by_size_[size_segregated_index]; + auto it = std::find(segregated_list.begin(), segregated_list.end(), next_block); + TT_ASSERT( + it != segregated_list.end(), + "Next block {} not found in size segregated list during deallocation of block {}", + next_block, + block_index); + segregated_list.erase(it); + + block_size_[block_index] += block_size_[next_block]; + block_next_block_[block_index] = block_next_block_[next_block]; + if (block_next_block_[next_block] != -1) { + block_prev_block_[block_next_block_[next_block]] = block_index; + } + free_meta_block(next_block); + } + + TT_ASSERT(lowest_occupied_address_.has_value(), "Lowest occupied address should have a value"); + if (addr <= *lowest_occupied_address_) { + lowest_occupied_address_ = std::nullopt; + ssize_t curr_block_index = block_next_block_[block_index]; + while (curr_block_index != -1) { + if (block_is_allocated_[curr_block_index]) { + lowest_occupied_address_ = block_address_[curr_block_index]; + break; + } + curr_block_index = block_next_block_[curr_block_index]; + } + } + // Update the segregated list + insert_block_to_segregated_list(block_index); +} + +std::vector> FreeListOpt::available_addresses(DeviceAddr size_bytes) const { + size_t alloc_size = align(std::max(size_bytes, min_allocation_size_)); + size_t size_segregated_index = get_size_segregated_index(alloc_size); + std::vector> addresses; + + for (size_t i = size_segregated_index; i < size_segregated_count; i++) { + for (size_t j = 0; j < free_blocks_segregated_by_size_[i].size(); j++) { + size_t block_index = free_blocks_segregated_by_size_[i][j]; + if (block_size_[block_index] >= alloc_size) { + addresses.push_back( + {block_address_[block_index], block_address_[block_index] + block_size_[block_index]}); + } + } + } + return addresses; +} + +size_t FreeListOpt::alloc_meta_block( + DeviceAddr address, DeviceAddr size, ssize_t prev_block, ssize_t next_block, bool is_allocated) { + size_t idx; + if (free_meta_block_indices_.empty()) { + idx = block_address_.size(); + block_address_.push_back(address); + block_size_.push_back(size); + block_prev_block_.push_back(prev_block); + block_next_block_.push_back(next_block); + block_is_allocated_.push_back(is_allocated); + meta_block_is_allocated_.push_back(true); + } else { + idx = free_meta_block_indices_.back(); + free_meta_block_indices_.pop_back(); + block_address_[idx] = address; + block_size_[idx] = size; + block_prev_block_[idx] = prev_block; + block_next_block_[idx] = next_block; + block_is_allocated_[idx] = is_allocated; + meta_block_is_allocated_[idx] = true; + } + return idx; +} + +void FreeListOpt::free_meta_block(size_t block_index) { + free_meta_block_indices_.push_back(block_index); + meta_block_is_allocated_[block_index] = false; +} + +void FreeListOpt::clear() { init(); } + +Statistics FreeListOpt::get_statistics() const { + // TODO: Cache the statistics + size_t total_allocated_bytes = 0; + size_t total_free_bytes = 0; + size_t largest_free_block_bytes = 0; + std::vector largest_free_block_addrs; + + for (size_t i = 0; i < block_address_.size(); i++) { + if (block_is_allocated_[i]) { + total_allocated_bytes += block_size_[i]; + } else { + total_free_bytes += block_size_[i]; + if (block_size_[i] >= largest_free_block_bytes) { + largest_free_block_bytes = block_size_[i]; + // XXX: This is going to overflow + largest_free_block_addrs.push_back(block_address_[i] + offset_bytes_); + } + } + } + + if (total_allocated_bytes == 0) { + total_free_bytes = max_size_bytes_; + largest_free_block_bytes = max_size_bytes_; + } + + return Statistics{ + .total_allocatable_size_bytes = max_size_bytes_, + .total_allocated_bytes = total_allocated_bytes, + .total_free_bytes = total_free_bytes, + .largest_free_block_bytes = largest_free_block_bytes, + // Why do we need largest_free_block_addrs? Without it the entire loop can be removed + // and statistics can be tracked during allocation and deallocation + .largest_free_block_addrs = std::move(largest_free_block_addrs), + }; +} + +void FreeListOpt::dump_blocks(std::ostream& out) const { + out << "FreeListOpt allocator info:" << std::endl; + out << "segregated free blocks by size:" << std::endl; + for (size_t i = 0; i < free_blocks_segregated_by_size_.size(); i++) { + if (i != free_blocks_segregated_by_size_.size() - 1) { + out << " Size class " << i << ": (" << size_t(size_segregated_base * (size_t{1} << i)) << " - " + << size_t(size_segregated_base * (size_t{1} << (i + 1))) << ") blocks: "; + } else { + out << " Size class " << i << ": (" << size_t(size_segregated_base * (size_t{1} << i)) + << " - inf) blocks: "; + } + for (size_t j = 0; j < free_blocks_segregated_by_size_[i].size(); j++) { + out << free_blocks_segregated_by_size_[i][j] << " "; + } + + out << std::endl; + } + + out << "Free slots in block table: "; + for (size_t i = 0; i < free_meta_block_indices_.size(); i++) { + out << free_meta_block_indices_[i] << " "; + } + out << std::endl; + + out << "Block table:" << std::endl; + auto leftpad = [](std::string str, size_t width) { + if (str.size() >= width) { + return str; + } + return std::string(width - str.size(), ' ') + str; + }; + auto leftpad_num = [leftpad](auto num, size_t width) { + // HACK: -1 for us means none + if (num == -1) { + return leftpad("none", width); + } + return leftpad(std::to_string(num), width); + }; + const size_t pad = 12; + std::array headers = {"Block", "Address", "Size", "PrevID", "NextID", "Allocated"}; + for (auto& header : headers) { + out << leftpad(header, pad) << " "; + } + out << std::endl; + for (size_t i = 0; i < block_address_.size(); i++) { + if (!meta_block_is_allocated_[i]) { + continue; + } + out << leftpad_num(i, pad) << " " << leftpad_num(block_address_[i], pad) << " " + << leftpad_num(block_size_[i], pad) << " " << leftpad_num(block_prev_block_[i], pad) << " " + << leftpad_num(block_next_block_[i], pad) << " " << leftpad(block_is_allocated_[i] ? "yes" : "no", pad) + << std::endl; + } +} + +void FreeListOpt::shrink_size(DeviceAddr shrink_size, bool bottom_up) { + if (shrink_size == 0) { + return; + } + TT_FATAL(bottom_up, "Shrinking from the top is currently not supported"); + TT_FATAL( + shrink_size <= this->max_size_bytes_, + "Shrink size {} must be smaller than max size {}", + shrink_size, + max_size_bytes_); + + // loop and scan the block list to find if the shrink cut into any allocated block + size_t block_to_shrink = -1; + DeviceAddr shrunk_address = shrink_size_ + shrink_size; + // TODO: There must be a way to force the beginning of all blocks be at index 0 + for (size_t i = 0; i < block_address_.size(); i++) { + if (!meta_block_is_allocated_[i]) { + continue; + } else if (block_is_allocated_[i]) { + TT_FATAL( + block_address_[i] >= shrunk_address, + "Shrink size {} cuts into allocated block at address {}", + shrunk_address, + block_address_[i]); + } else if (block_address_[i] <= shrunk_address && block_address_[i] + block_size_[i] >= shrunk_address) { + block_to_shrink = i; + break; + } + } + + TT_FATAL(block_to_shrink != -1, "Shrink size {} does not align with any block. This must be a bug", shrunk_address); + + // Find the relevant size segregated list + size_t size_segregated_index = get_size_segregated_index(block_size_[block_to_shrink]); + std::vector& segregated_list = free_blocks_segregated_by_size_[size_segregated_index]; + for (size_t i = 0; i < segregated_list.size(); i++) { + if (segregated_list[i] == block_to_shrink) { + segregated_list.erase(segregated_list.begin() + i); + break; + } + } + + // Shrink the block + block_size_[block_to_shrink] -= shrink_size; + max_size_bytes_ -= shrink_size; + shrink_size_ += shrink_size; + if (block_size_[block_to_shrink] == 0) { + block_prev_block_[block_next_block_[block_to_shrink]] = block_prev_block_[block_to_shrink]; + free_meta_block(block_to_shrink); + } else { + block_address_[block_to_shrink] += shrink_size; + insert_block_to_segregated_list(block_to_shrink); + } +} + +void FreeListOpt::reset_size() { + if (shrink_size_ == 0) { + return; + } + + // Create a new block, mark it as allocated and deallocate the old block so coalescing can happen + ssize_t lowest_block_index = -1; + for (size_t i = 0; i < block_address_.size(); i++) { + if (!meta_block_is_allocated_[i]) { + continue; + } + if (block_address_[i] == shrink_size_) { + lowest_block_index = i; + break; + } + } + TT_ASSERT(lowest_block_index != -1, "Lowest block not found during reset size"); + + // There 2 cases to consider: + // 1. The lowest block is is free, which means we can just modify it's attributes + // 2. The lowest block is allocated, which means we need to create a new block and deallocate the old one + if (!block_is_allocated_[lowest_block_index]) { + auto* segregated_list = + &free_blocks_segregated_by_size_[get_size_segregated_index(block_size_[lowest_block_index])]; + for (size_t i = 0; i < segregated_list->size(); i++) { + if ((*segregated_list)[i] == lowest_block_index) { + segregated_list->erase(segregated_list->begin() + i); + break; + } + } + block_size_[lowest_block_index] += shrink_size_; + block_address_[lowest_block_index] = 0; + insert_block_to_segregated_list(lowest_block_index); + } else { + size_t new_block_index = alloc_meta_block(0, shrink_size_, -1, lowest_block_index, false); + TT_ASSERT(block_prev_block_[lowest_block_index] == -1, "Lowest block should not have a previous block"); + block_prev_block_[lowest_block_index] = new_block_index; + insert_block_to_segregated_list(new_block_index); + } + + max_size_bytes_ += shrink_size_; + shrink_size_ = 0; +} + +void FreeListOpt::insert_block_to_segregated_list(size_t block_index) { + const size_t size_segregated_index = get_size_segregated_index(block_size_[block_index]); + auto& free_blocks = free_blocks_segregated_by_size_[size_segregated_index]; + // Pushing to the back is faster than sorted insertion. But it increases fragmentation + // free_blocks.push_back(block_index); + // The overhead is not worth it in benchmarks. Need real world data to confirm. But certainly it'll help with + // fragmentation + std::vector::iterator it; + // from experience, the lower bound is only faster after a certain number of elements + if (free_blocks.size() < 30) { + for (it = free_blocks.begin(); it != free_blocks.end(); it++) { + if (block_address_[*it] > block_address_[block_index]) { + break; + } + } + } else { + it = std::lower_bound(free_blocks.begin(), free_blocks.end(), block_index, [this](size_t a, size_t b) { + return block_address_[a] < block_address_[b]; + }); + } + free_blocks.insert(it, block_index); +} + +inline size_t FreeListOpt::hash_device_address(DeviceAddr address) { + // HACK: This hash is critical for performance, empirically found to be good for + // the specific usecase + return ((address) ^ (address >> 12) * 3) % n_alloc_table_buckets; +} +void FreeListOpt::insert_block_to_alloc_table(DeviceAddr address, size_t block_index) { + size_t bucket = hash_device_address(address); + allocated_block_table_[bucket].emplace_back(address, block_index); +} +bool FreeListOpt::is_address_in_alloc_table(DeviceAddr address) const { + size_t bucket = hash_device_address(address); + for (const auto& [addr, block_index] : allocated_block_table_[bucket]) { + if (addr == address) { + return true; + } + } + return false; +} +std::optional FreeListOpt::get_and_remove_from_alloc_table(DeviceAddr address) { + size_t bucket = hash_device_address(address); + // It's common to deallocate the last allocated block, so search from the back + for (ssize_t i = allocated_block_table_[bucket].size() - 1; i >= 0; i--) { + if (allocated_block_table_[bucket][i].first == address) { + auto res = allocated_block_table_[bucket][i].second; + allocated_block_table_[bucket].erase(allocated_block_table_[bucket].begin() + i); + return res; + } + } + return std::nullopt; +} + +void FreeListOpt::update_lowest_occupied_address(DeviceAddr address) { + if (!lowest_occupied_address_.has_value() || address < lowest_occupied_address_.value()) { + lowest_occupied_address_ = address; + } +} + +} // namespace allocator +} // namespace tt_metal +} // namespace tt diff --git a/tt_metal/impl/allocator/algorithms/free_list_opt.hpp b/tt_metal/impl/allocator/algorithms/free_list_opt.hpp new file mode 100644 index 000000000000..1a3a7f87405b --- /dev/null +++ b/tt_metal/impl/allocator/algorithms/free_list_opt.hpp @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. +// +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include +#include + +#include "tt_metal/impl/allocator/algorithms/allocator_algorithm.hpp" + +namespace tt { +namespace tt_metal { +namespace allocator { +// Essentially the same free list algorithm as FreeList with BestFit policy, but with (IMO absurdly) optimized code. +// Including +// - SoA instead of linked list for the free list +// - Size segregated to avoid unnecessary searches of smaller blocks +// - Hash table to store allocated blocks for faster block lookup during deallocation +// - Keeps metadata locality to avoid cache misses +// - Metadata reuse to avoid allocations +class FreeListOpt : public Algorithm { +public: + enum class SearchPolicy { + FIRST, + BEST, + }; + FreeListOpt( + DeviceAddr max_size_bytes, + DeviceAddr offset_bytes, + DeviceAddr min_allocation_size, + DeviceAddr alignment, + SearchPolicy policy = SearchPolicy::BEST); + void init() override; + + std::vector> available_addresses(DeviceAddr size_bytes) const override; + + std::optional allocate( + DeviceAddr size_bytes, bool bottom_up = true, DeviceAddr address_limit = 0) override; + + std::optional allocate_at_address(DeviceAddr absolute_start_address, DeviceAddr size_bytes) override; + + void deallocate(DeviceAddr absolute_address) override; + + void clear() override; + + Statistics get_statistics() const override; + + void dump_blocks(std::ostream& out) const override; + + void shrink_size(DeviceAddr shrink_size, bool bottom_up = true) override; + + void reset_size() override; + +private: + // SoA free list components + std::vector block_address_; + std::vector block_size_; + std::vector block_prev_block_; + std::vector block_next_block_; + std::vector block_is_allocated_; // not using bool to avoid compacting + std::vector meta_block_is_allocated_; // not using bool to avoid compacting + + // Metadata block indices that is not currently used (to reuse blocks instead of always allocating new ones) + std::vector free_meta_block_indices_; + + // Caches so most operations don't need to scan the entire free list. The allocated block table + // will not rehash as I find the cost to not be worth it + inline static constexpr size_t n_alloc_table_buckets = 512; // Number of buckets in the hash table + inline static constexpr size_t n_alloc_table_init_bucket_size = 10; // Initial size of each bucket + std::vector>> allocated_block_table_; + + // Size segregated list of free blocks. Idea comes from the TLSF paper, but instead of aiming for realtime + // the goal there is to not look at small blocks when allocating large blocks. Which the naive free list + // algorithm does not do. Confiugring these 2 parameters is needs real world data, but for now it's just + // number pulled out of thin air. Too low and it devolves into an array search, too high you pay cache misses + + // Size class index is calculated by taking the log2 of the block size divided by the base size + // ex: size = 2048, base = 1024, log2(2048/1024) = 1, so size class index = 1 + inline static constexpr size_t size_segregated_base = 1024; // in bytes + const size_t size_segregated_count; // Number of size classes + std::vector> free_blocks_segregated_by_size_; + + // internal functions + // Given a block index, mark a chunk (from block start + offset to block start + offset + alloc_size) as allocated + // Unused space is split into a new free block and retuened to the free list and the segregated list + // NOTE: This function DOES NOT remove block_index from the segregated list. Caller should do that + size_t allocate_in_block(size_t block_index, DeviceAddr alloc_size, size_t offset); + + inline size_t get_size_segregated_index(DeviceAddr size_bytes) const { + // std::log2 is SLOW, so we use a simple log2 implementation for integers. I assume GCC compiles this to a + // count leading zeros instruction then a subtraction. + size_t lg = 0; + size_t n = size_bytes / size_segregated_base; + while (n >>= 1) { + lg++; + } + return std::min(size_segregated_count - 1, lg); + } + // Put the block at block_index into the size segregated list at the appropriate index (data taken from + // the SoA vectors) + void insert_block_to_segregated_list(size_t block_index); + + // Allocate a new block and return the index to the block + size_t alloc_meta_block( + DeviceAddr address, DeviceAddr size, ssize_t prev_block, ssize_t next_block, bool is_allocated); + // Free the block at block_index and mark it as free + void free_meta_block(size_t block_index); + + // Operations on the allocated block table + static size_t hash_device_address(DeviceAddr address); + void insert_block_to_alloc_table(DeviceAddr address, size_t block_index); + bool is_address_in_alloc_table(DeviceAddr address) const; + std::optional get_and_remove_from_alloc_table(DeviceAddr address); + + void update_lowest_occupied_address(DeviceAddr address); + + size_t find_free_block(DeviceAddr size, bool bottom_up); + + SearchPolicy policy_; +}; + +} // namespace allocator +} // namespace tt_metal +} // namespace tt diff --git a/tt_metal/impl/allocator/allocator.cpp b/tt_metal/impl/allocator/allocator.cpp index 0e64623892c8..059c209d7a0c 100644 --- a/tt_metal/impl/allocator/allocator.cpp +++ b/tt_metal/impl/allocator/allocator.cpp @@ -7,7 +7,7 @@ #include #include "tt_metal/common/math.hpp" #include "tt_metal/detail/util.hpp" -#include "tt_metal/impl/allocator/algorithms/free_list.hpp" +#include "tt_metal/impl/allocator/algorithms/free_list_opt.hpp" #include "tt_metal/impl/buffers/buffer.hpp" namespace tt { @@ -27,8 +27,8 @@ static char const* get_memory_pool_name(BufferType buffer_type) { #endif void BankManager::init_allocator(DeviceAddr size_bytes, uint32_t alignment_bytes, DeviceAddr offset) { - this->allocator_ = - std::make_unique(size_bytes, offset, alignment_bytes, alignment_bytes, FreeList::SearchPolicy::FIRST); + this->allocator_ = std::make_unique( + size_bytes, offset, alignment_bytes, alignment_bytes, FreeListOpt::SearchPolicy::FIRST); } void validate_num_banks(uint32_t num_banks, const BufferType& buffer_type, bool disable_interleaved) { From 2cf151288360533a16fa9b2f59cbb737e77105a7 Mon Sep 17 00:00:00 2001 From: Andrew Fuller Date: Thu, 19 Dec 2024 15:00:42 -0500 Subject: [PATCH 76/87] Set the test data to be relative to the test binary (#16150) ### Ticket #16115 ### Problem description tt-train tests are a coin toss whether they're build + run on two machines that share the same path (runner+runner or runner_2+runner_2). A mismatch will fail the test as the abspath to find the test data is wrong. ### What's changed Moved the test data to be relative to the test binary. ### Checklist - [x] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12383793376 --- tt-train/tests/CMakeLists.txt | 2 +- tt-train/tests/model/nano_gpt_test.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tt-train/tests/CMakeLists.txt b/tt-train/tests/CMakeLists.txt index 5eee6b8e77b8..65d2d3dd8973 100644 --- a/tt-train/tests/CMakeLists.txt +++ b/tt-train/tests/CMakeLists.txt @@ -43,7 +43,7 @@ add_definitions(-DENABLE_CI_ONLY_TT_TRAIN_TESTS=0) # Define the target file location set(SHAKESPEARE_URL "https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt") -set(SHAKESPEARE_FILE "${CMAKE_SOURCE_DIR}/data/shakespeare.txt") +set(SHAKESPEARE_FILE "${CMAKE_CURRENT_BINARY_DIR}/shakespeare.txt") # Check if the file already exists before downloading if(NOT EXISTS "${SHAKESPEARE_FILE}") diff --git a/tt-train/tests/model/nano_gpt_test.cpp b/tt-train/tests/model/nano_gpt_test.cpp index 53b80a00ab1f..5dcf53d26d01 100644 --- a/tt-train/tests/model/nano_gpt_test.cpp +++ b/tt-train/tests/model/nano_gpt_test.cpp @@ -56,7 +56,7 @@ struct TrainingConfig { void train_test(bool use_moreh_adamw = false) { auto config = TrainingConfig(); config.transformer_config.dropout_prob = 0.0F; - config.data_path = std::string(TEST_DATA_DIR) + "/shakespeare.txt"; + config.data_path = "/shakespeare.txt"; // set seed ttml::autograd::ctx().set_seed(config.seed); From d47f7f8ed582bc718d173f42eb638a6436129f7f Mon Sep 17 00:00:00 2001 From: Borys Bradel <164946524+bbradelTT@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:16:16 -0500 Subject: [PATCH 77/87] #0: Fix matmul doc string (#16208) ### Ticket Link to Github Issue N/A ### Problem description Doc string formatting broke all post commit ### What's changed Fix formatting Tested locally: ``` ... dumping search index in English (code: en)... done dumping object inventory... done [app] emitting event: 'build-finished'(None,) build succeeded. ``` ### Checklist N/A - [ ] Post commit CI passes - [ ] Blackhole Post commit (if applicable) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [ ] New/Existing tests provide coverage for changes --- ttnn/cpp/ttnn/operations/matmul/matmul_pybind.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ttnn/cpp/ttnn/operations/matmul/matmul_pybind.cpp b/ttnn/cpp/ttnn/operations/matmul/matmul_pybind.cpp index 2bc4499ce172..3692ddc202c4 100644 --- a/ttnn/cpp/ttnn/operations/matmul/matmul_pybind.cpp +++ b/ttnn/cpp/ttnn/operations/matmul/matmul_pybind.cpp @@ -261,6 +261,9 @@ void py_module(py::module& module) { - Note: there are various additional constraints related to specific program configs chosen. Please look at the error messages carefully and fix problems appropriately. + - Note: If optional output tensor is specified, then dtype and memory config need to be checked as follows: + - if they are default then they should be set based on optional output tensor + - if the are not default then they should be compared and if there is a difference an error is reported Args: input_tensor_a (ttnn.Tensor): the first tensor to be multiplied. Needs to be on the device. @@ -276,10 +279,7 @@ void py_module(py::module& module) { compute_kernel_config (ttnn.DeviceComputeKernelConfig): the compute kernel configuration for the matmul operation. Defaults to `None`. core_grid (ttnn.CoreGrid): the grid on which to distribute the sharded tensor on (writes to the cores L1s). Defaults to `None`. output_tile (List of [int], optional): Specifies the output tile configuration. Defaults to `None`. - optional_output_tensor (ttnn.Tensor) : User provided on-device output tensor where the result of matmul is to be written. - If optional output tensor is specified, then dtype and memory config need to be checked as follows: - if they are default then they should be set based on optional output tensor - if the are not default then they should be compared and if there is a difference an error is reported + optional_output_tensor (ttnn.Tensor, optional): User provided on-device output tensor where the result of matmul is to be written. Defaults to `None`. Returns: @@ -389,7 +389,7 @@ void py_module(py::module& module) { compute_kernel_config (ttnn.DeviceComputeKernelConfig, optional): the compute kernel configuration for the matmul operation. Defaults to `None`. core_grid (ttnn.CoreGrid, optional): the grid on which to distribute the sharded tensor on (writes to the cores L1s). Defaults to `None`. output_tile (List of [int], optional): Specifies the output tile configuration. Defaults to `None`. - optional_output_tensor (ttnn.Tensor) : User provided on-device output tensor where the result of linear is to be written. + optional_output_tensor (ttnn.Tensor, optional): User provided on-device output tensor where the result of linear is to be written. Defaults to `None`. Returns: ttnn.Tensor: the output tensor. From 6e4f0e0039ad3c4300fab8ddc3a746aa0705777e Mon Sep 17 00:00:00 2001 From: Borys Bradel <164946524+bbradelTT@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:26:57 -0500 Subject: [PATCH 78/87] #0: remove spammy warning from conftest (#16198) ### Ticket Link to Github Issue N/A ### Problem description There's a lot of spammy warnings in all post commit that are requiring the download of the logs zip. E.g. https://github.com/tenstorrent/tt-metal/actions/runs/12404868937/job/34631200020 ``` 2024-12-19 03:05:01.259 | WARNING | tests.ttnn.conftest:pytest_collection_modifyitems:35 - > 202 This step has been truncated due to its large size. Download the full logs from the menu once the workflow run has completed. ``` ### What's changed Remove the warning. ### Checklist - [x] Post commit CI passes - failures are random docker issues and what's already on main, nothing related to this change. https://github.com/tenstorrent/tt-metal/actions/runs/12418092901/job/34670695481 - [ ] Blackhole Post commit (if applicable) N/A - [ ] Model regression CI testing passes (if applicable) N/A - [ ] Device performance regression CI testing passes (if applicable) N/A - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [ ] New/Existing tests provide coverage for changes N/A --- tests/ttnn/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ttnn/conftest.py b/tests/ttnn/conftest.py index fd71e6f487a1..914ac7bd0fce 100644 --- a/tests/ttnn/conftest.py +++ b/tests/ttnn/conftest.py @@ -32,7 +32,6 @@ def pytest_collection_modifyitems(config, items): logger.warning("Fast Runtime Mode is ON. Skipping tests tagged with @pytest.mark.requires_fast_runtime_mode_off") skip_unmarked = pytest.mark.skip(reason="Skipping test with requires_fast_runtime_mode_off") for item in items: - logger.warning(item.keywords) if "requires_fast_runtime_mode_off" in item.keywords: logger.warning(f"Skipping {item}") item.add_marker(skip_unmarked) From 44a84732cd30b94abba19081266110a4159ffaea Mon Sep 17 00:00:00 2001 From: Almeet Bhullar Date: Tue, 17 Dec 2024 23:01:32 +0000 Subject: [PATCH 79/87] Update generating unicast go signal commands to ensure dispatch write linear respects alignment --- tt_metal/impl/dispatch/command_queue.cpp | 16 +++++------- .../impl/dispatch/command_queue_interface.hpp | 2 +- tt_metal/impl/dispatch/device_command.hpp | 26 ++++++++++++++----- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/tt_metal/impl/dispatch/command_queue.cpp b/tt_metal/impl/dispatch/command_queue.cpp index 7653e10b3b07..9d6db6cdbf41 100644 --- a/tt_metal/impl/dispatch/command_queue.cpp +++ b/tt_metal/impl/dispatch/command_queue.cpp @@ -243,9 +243,7 @@ void EnqueueWriteShardedBufferCommand::add_dispatch_write(HugepageDeviceCommand& uint32_t data_size_bytes = this->pages_to_write * this->padded_page_size; const CoreCoord virtual_core = this->buffer.device()->virtual_core_from_logical_core(this->core, this->buffer.core_type()); - bool flush_prefetch = true; command_sequence.add_dispatch_write_linear( - flush_prefetch, 0, this->device->get_noc_unicast_encoding(this->noc_index, virtual_core), this->bank_base_address, @@ -288,11 +286,11 @@ void EnqueueWriteShardedBufferCommand::add_buffer_data(HugepageDeviceCommand& co void EnqueueWriteBufferCommand::process() { uint32_t num_worker_counters = this->sub_device_ids.size(); uint32_t data_size_bytes = this->pages_to_write * this->padded_page_size; - - uint32_t cmd_sequence_sizeB = - CQ_PREFETCH_CMD_BARE_MIN_SIZE + // CQ_PREFETCH_CMD_RELAY_INLINE + (CQ_DISPATCH_CMD_WRITE_PAGED or - // CQ_DISPATCH_CMD_WRITE_LINEAR) - data_size_bytes; + uint32_t pcie_alignment = hal.get_alignment(HalMemType::HOST); + uint32_t cmd_sequence_sizeB = align( + sizeof(CQPrefetchCmd) + // CQ_PREFETCH_CMD_RELAY_INLINE + sizeof(CQDispatchCmd) + // CQ_DISPATCH_CMD_WRITE_PAGED or CQ_DISPATCH_CMD_WRITE_LINEAR + data_size_bytes, pcie_alignment); if (this->issue_wait) { cmd_sequence_sizeB += CQ_PREFETCH_CMD_BARE_MIN_SIZE * num_worker_counters; // CQ_PREFETCH_CMD_RELAY_INLINE + CQ_DISPATCH_CMD_WAIT } @@ -973,8 +971,8 @@ void EnqueueProgramCommand::assemble_device_commands( if (write_linear) { kernel_bins_unicast_cmds.emplace_back(2 * CQ_PREFETCH_CMD_BARE_MIN_SIZE); cmd_sequence_sizeB += 2 * CQ_PREFETCH_CMD_BARE_MIN_SIZE; - kernel_bins_unicast_cmds.back().add_dispatch_write_linear( - false, // flush_prefetch + constexpr bool flush_prefetch = false; + kernel_bins_unicast_cmds.back().add_dispatch_write_linear( num_mcast_dests, // num_mcast_dests noc_encoding, // noc_xy_addr kg_transfer_info.dst_base_addrs[kernel_idx], diff --git a/tt_metal/impl/dispatch/command_queue_interface.hpp b/tt_metal/impl/dispatch/command_queue_interface.hpp index 2ea6d2e76e06..a19219a4ea4f 100644 --- a/tt_metal/impl/dispatch/command_queue_interface.hpp +++ b/tt_metal/impl/dispatch/command_queue_interface.hpp @@ -681,7 +681,7 @@ class SystemMemoryManager { return; } - // All data needs to be 32B aligned + // All data needs to be PCIE_ALIGNMENT aligned uint32_t push_size_16B = align(push_size_B, tt::tt_metal::hal.get_alignment(tt::tt_metal::HalMemType::HOST)) >> 4; diff --git a/tt_metal/impl/dispatch/device_command.hpp b/tt_metal/impl/dispatch/device_command.hpp index 86c70fcfe031..9f0b7aa4c6c0 100644 --- a/tt_metal/impl/dispatch/device_command.hpp +++ b/tt_metal/impl/dispatch/device_command.hpp @@ -219,9 +219,8 @@ class DeviceCommand { this->memcpy((char*)relay_paged_cmd_dst + sizeof(CQPrefetchCmd), &sub_cmds[offset_idx], sub_cmds_sizeB); } - template + template void add_dispatch_write_linear( - bool flush_prefetch, uint8_t num_mcast_dests, uint32_t noc_xy_addr, uint32_t addr, @@ -249,10 +248,25 @@ class DeviceCommand { initialize_write_cmd(write_cmd_dst); } - if constexpr (inline_data) { - TT_ASSERT(data != nullptr); // compiled out? - uint32_t increment_sizeB = align(data_sizeB, this->pcie_alignment); - this->add_data(data, data_sizeB, increment_sizeB); + // Case 1: flush_prefetch + // a) there is inline_data: data is provided here and follows prefetch relay inline and cq dispatch write linear so total increment size is: + // align(sizeof(CQPrefetchCmd) + sizeof(CQDispatchCmd) + data_sizeB, pcie_alignment) + // b) don't have inline_data: next command should be to add_data (don't do aligned increment) so total increment size is: + // sizeof(CQPrefetchCmd) + sizeof(CQDispatchCmd) + // Case 2: !flush_prefetch: no data, increment size is: + // align(sizeof(CQPrefetchCmd) + sizeof(CQDispatchCmd), pcie_alignment) + // Note that adding prefetch_relay_inline and writing the dispatch command already increment cmd_write_offsetB via calls to reserve_space + if constexpr (flush_prefetch) { + if constexpr (inline_data) { + TT_ASSERT(data != nullptr); // compiled out? + this->add_data(data, data_sizeB, data_sizeB); + // this->cmd_write_offsetB has been incremented by sizeof(CQPrefetchCmd) + sizeof(CQDispatchCmd) + data_sizeB + // need to ensure this is aligned for next cmds to be written at the correct location + this->cmd_write_offsetB = align(this->cmd_write_offsetB, this->pcie_alignment); + } + } else { + // Need to make sure next command that flushes prefetch is written to correctly aligned location + this->cmd_write_offsetB = align(this->cmd_write_offsetB, this->pcie_alignment); } } From 227d4e6a25d1437347327c675883d6d20b23ba8e Mon Sep 17 00:00:00 2001 From: Ammar Vora Date: Thu, 19 Dec 2024 14:46:58 -0700 Subject: [PATCH 80/87] LLM tech report sections 2.2, 2.5 (#15121) --- tech_reports/LLMs/llms.md | 363 +++++++++++++++++++++++++++++++++++++- 1 file changed, 360 insertions(+), 3 deletions(-) diff --git a/tech_reports/LLMs/llms.md b/tech_reports/LLMs/llms.md index 5ead06620327..3279e8dc714a 100644 --- a/tech_reports/LLMs/llms.md +++ b/tech_reports/LLMs/llms.md @@ -1,6 +1,6 @@ # LLMs in TT-NN -Authors: Mark O'Connor, Djordje Ivanovic, Jack (Xun) Cai, Kartik Paigwar, Johanna Rock, Stuti Raizada +Authors: Mark O'Connor, Djordje Ivanovic, Jack (Xun) Cai, Kartik Paigwar, Johanna Rock, Stuti Raizada, Ammar Vora ## Contents - [LLMs in TT-NN](#llms-in-tt-nn) @@ -54,8 +54,87 @@ Other useful resources: ## 2. Modules ### 2.1 Embedding ### 2.2 RoPE - - Iterative update system - - When to use our fused op + +For added performance, our implementation of Llama uses a fused operation to apply the Rotary Position Embeddings (RoPE), which can be accessed via `ttnn.experimental.rotary_embedding_llama` API. In the Attention module, this API is called twice, one for the queries and one for the keys respectively. + +Here is an example of how the fused RoPE op is used in the attention module: + +```py +q_heads = ttnn.experimental.rotary_embedding_llama( + q_heads_pre_rot, + cos_matrix, + sin_matrix, + transformation_matrix, + is_decode_mode="False" +) + +k_heads = ttnn.experimental.rotary_embedding_llama( + k_heads_pre_rot, + cos_matrix, + sin_matrix, + transformation_matrix, + is_decode_mode="False" +) +``` + +#### Setting up inputs to RoPE + +The fused operation uses a different parallelization scheme internally depending on of the model is in *prefill* or *decode* mode. Across these modes, the shapes and memory configs of the inputs vary and this table summararizes them below: + +| is_decode_mode | True | False | +|:---------------------:|:---------------------------------------------------:|:--------------------------------------------------:| +| Input | [1, batch, n_heads, head_dim],
HEIGHT_SHARDED in L1 | [1, n_heads, seq_len, head_dim],
INTERLEAVED in L1 | +| Cos/Sin Matrix | [1, batch, 1, head_dim],
HEIGHT_SHARDED in L1 | [1, 1, seq_len, head_dim],
INTERLEAVED in L1 | +| Transformation Matrix | [1, 1, TH * batch, TW],
HEIGHT_SHARDED in L1 | [1, 1, TH, TW],
INTERLEAVED in L1 | + +*Note: (TH, TW) = (TILE_HEIGHT, TILE_WIDTH)* + + +#### Decode mode specifics +The cos/sin matrices, are generated in two slightly different ways, depending on the mode of operation. For *prefill* mode, the cos/sin matrices are computed once at intialization using the *prefill* sequence length, and then passed into the RoPE op. However, in *decode* mode, since the position index of each user is updated from token-to-token, the cos/sin matrices need to be updated across iterations. Here, we leverage our `TtLlamaRotarySetup` module, that can be used at each decode iteration to get the corresponding cos/sin matrices. + +This is an example of how `TtLlamaRotarySetup` can be used in decode mode: +```py +from llama_rope import TtLlamaRotarySetup + +# Step 1: Create the setup object +rope_setup_decode = TtLlamaRotarySetup( + mesh_device, + head_dim, + max_seq_len, + rope_theta, + use_scaled_rope +) + +transformation_mats_decode = rope_setup_decode.get_trans_mats() + + +# Step 2: Get user position ids +# For example, batch number of users, each with different position ids +position_ids = torch.arange(batch) + + +# Step 3: Retreive the relevant cos/sin matrices +cos_sin_matrices = rope_setup_decode.get_rot_mats(position_ids) +cos_matrix, sin_matrix = cos_sin_matrices + + +# Step 4: Perform the RoPE operation +out = ttnn.experimental.rotary_embedding_llama( + x, # example input + cos_matrix + sin_matrix, + transformation_mats_decode, + is_decode_mode=True +) + +``` +
+ +#### Quick note about the transformation matrix +Due to the sparse nature of the transformation matrix, the fused RoPE op takes as input a tile-sized transformation matrix, and then reuses that tile across all subsequent operations. In *decode* mode, this matrix is replicated *batch* times, and then sharded over *batch* number of cores. As a result, each core receives a single, tile-sized transformation matrix. In contrast, the *prefill* mode implementation requires only a single tile-sized transformation matrix, and then distributes it across all the cores internally. + + ### 2.3 Norm Normalization is a critical operation in Large Language Models (LLMs), ensuring stable training and efficient inference. Two widely adopted normalization techniques in modern LLMs, **LayerNorm** and **RMSNorm**, are fully supported in TT-NN. @@ -451,6 +530,284 @@ TLDR -- here are some useful things about the attention ops to keep in mind that ### 2.5 MLP +The MLP for the Llama models is implemented in the the `TtLlamaMLP` module class. The tests for this module are available in the `test_llama_mlp.py` file. + +As an overview, the MLP performs the following operations on an input `x`: +``` +w1_out = FF1(x) +w3_out = FF3(x) +w2_in = SiLU(w1_out) * w3_out +y = FF2(w2_in) +``` +where FF1, FF2, and FF3 are linear transformations (matmuls) with weights `w1`, `w2`, and `w3` respectively. Since FF1 and FF3 share the same inputs, their optimizations are shared as well. + + +Let's dive into our implementation of MLP, and discuss what makes it performant across different WH systems. + +#### 0. Setup +When used in the model by the `TtLlamaDecoder` module class, the MLP class is initialized at the start, where the weights for `w1`, `w2`, and `w3` are loaded and fractured across devices in specific schemes, as outlined in the [Multi-Device](#33-multi-device) section. Specifically, in n300 and T3000 systems the weights are 1D column fractured, and in TG systems the weights are 2D fractured. + +```py +self.feed_forward = TtLlamaMLP( + mesh_device=mesh_device, + args=args, + state_dict=state_dict, + weight_cache_path=weight_cache_path, + layer_num=layer_num, + dtype=dtype, + model_config=self.model_config, +) +``` + +#### 1. Inputs +Then, at runtime, the `forward` function of `TtLlamaMLP` is called with a mode (*'prefill'* or *'decode'*), with inputs that are replicated across devices, for all WH system configurations. Note, in the actual model, the input `ff_in` is the output of the `norm` step prior to MLP (See norm section below). + +**Decode mode** + +In *decode* mode, the inputs have a maximum batch of 32, where each user only has a single token. As such, the inputs in *decode* mode are considered to be much smaller compared to in *prefill* mode, where the sequence length can be up to 128k. To make our subsequent matmul operations faster in *decode* mode, we can shard the input across L1, where they can be processed by the matmul, without any extra time for loading. The specific core grid to shard on, `mlp_core_grid` is chosen to be the lowest number of cores that the input can be width sharded on, while maintaining tile size. This is so we can minimize any communication delay over the NOC, when moving around the activations during the matmul. + + +```py +# ff_in shape: [1, 1, m, k] => [1, 1, batch, dim] +ff_in_memory_config = ttnn.create_sharded_memory_config( + (m, k // mlp_core_grid.num_cores), + mlp_core_grid, + ttnn.ShardStrategy.WIDTH, + ttnn.ShardOrientation.ROW_MAJOR, + use_height_and_width_as_shard_shape=True, +) +``` + +**Prefill mode** + +As mentioned before, the input in prefill mode can be very large, and may not fit in the available L1 space. As such, the inputs are stored in DRAM. + +```py +# ff_in shape: [1, 1, m, k] => [1, 1, seq_len, dim] +ff_in_memory_config = ttnn.DRAM_MEMORY_CONFIG +``` + +Note, similar to the Attention module, the matmul operation can exceed memory if the inputs are too large, and as a workaround, we push part of the sequence length into the batch dimension. +```py +# Reshape input to to fit on device and parallelize computation +if seq_len >= 1024: + ff_in = ttnn.reshape(ff_in, [1, seq_len // 1024, 1024, -1]) +``` + + + +#### 2. Setting up program configs for the matmuls +Depending on the mode of operation, the `forward` function of `TtLlamaMLP` instantiates different program configs for the matmuls of FF1/FF3, and FF2. + + +**Decode mode** + +Since the weights are much larger than the activations, and the weights must be loaded from DRAM, these matmul operations are DRAM-bound. This means that loading the weights from DRAM is a bottleneck, rather than the computation itself. As such, we use DRAM sharded matmuls in decode mode, which are more performant than regular mamtuls (See section _ for details). + +```py +_, _, m, k = ff_in.shape +n = hidden_dim // num_devices # Since w1/w3 are fractured on outer dim +pc1 = ttnn.MatmulMultiCoreReuseMultiCastDRAMShardedProgramConfig( + in0_block_w=math.ceil(k / (tile_size * ff1_num_cores)), + per_core_M=math.ceil(m / tile_size), + per_core_N=math.ceil(n / (tile_size * ff1_num_cores)), + fused_activation=None, +) + +k, n = n, k # Since FF1 is up projection and FF2 is down projection +pc2 = ttnn.MatmulMultiCoreReuseMultiCastDRAMShardedProgramConfig( + in0_block_w=math.ceil(k / (tile_size * ff2_num_cores)), + per_core_M=math.ceil(m / tile_size), + per_core_N=math.ceil(n / (tile_size * ff2_num_cores)), + fused_activation=None, +) +``` + +**Prefill mode** + +In prefill mode, since the activation and weights are similarly shaped, loading activations and weights from DRAM is no longer a bottleneck. Instead, for these compute bound matmul operations, we utilize a 2D matmul. + +The specific paramers for the program configs are chosen to maximize matmul performance, based on the shapes of the inputs. See section _ for more details. + +```py +# TODO: Move this function to a different section and just refer to it +def matmul_config( + m: int, + k: int, + n: int, + grid_size: Tuple[int, int], + in0_block_w: int = None, + fuse_batch: bool = False, + fused_activation=None, + ) -> ttnn.MatmulMultiCoreReuseMultiCastProgramConfig: + per_core_M = math.ceil(m / (tile_size * grid_size[1])) + per_core_N = math.ceil(n / (tile_size * grid_size[0])) + + out_subblock_h = 1 + out_subblock_w = get_out_subblock_w(per_core_N, out_subblock_h) + + if in0_block_w is None: + in0_block_w = min(4, max(1, k // (tile_size * grid_size[0]))) + + return ttnn.MatmulMultiCoreReuseMultiCastProgramConfig( + compute_with_storage_grid_size=grid_size, + in0_block_w=in0_block_w, + out_subblock_h=out_subblock_h, + out_subblock_w=out_subblock_w, + per_core_M=per_core_M, + per_core_N=per_core_N, + transpose_mcast=False, + fused_activation=fused_activation, + fuse_batch=fuse_batch, + ) + + +_, _, m, k = ff_in.shape +n = hidden_dim // num_devices +pc1 = matmul_config( + m=m, k=k, n=n, grid_size=(8, 8) +) + +k, n = n, k # Since FF1 is up projection and FF2 is down projection +pc1 = matmul_config( + m=m, k=k, n=n, grid_size=(8, 8) +) +``` + + +#### 3. FF1/FF3 matmul +The first set of operations in the MLP are: +```py +w1_out = FF1(x) +w3_out = FF3(x) +``` +Based on the program configs we computed beforehand, we perform the FF1/FF3 matmuls, making sure that the ouputs are L1 sharded in in decode mode, and interleaved in DRAM if in prefill mode. For the `compute_kernel_config`, we use `ttnn.MathFidelity.HiFi2` to retain accuracy while still being performant. Using `ttnn.MathFidelity.HiFi4` instead, would mean that this matmul would become compute bound. + +```py +compute_kernel_config_hifi2 = ttnn.WormholeComputeKernelConfig( + math_fidelity=ttnn.MathFidelity.HiFi2, + math_approx_mode=False, + fp32_dest_acc_en=False, + packer_l1_acc=True, +) + +w1_out = ttnn.linear( + ff_in, + w1, + compute_kernel_config=args.compute_kernel_config_hifi2, + core_grid=ttnn.CoreGrid(y=8, x=8) if not pc_1 else None, + dtype=ttnn.bfloat16, + program_config=pc_1, + memory_config=ttnn.L1_WIDTH_SHARDED_MEMORY_CONFIG if mode == "decode" else ttnn.DRAM_MEMORY_CONFIG, +) + +w3_out = ttnn.linear( + ff_in, + w3, + compute_kernel_config=args.compute_kernel_config_hifi2, + core_grid=ttnn.CoreGrid(y=8, x=8) if not pc_1 else None, + dtype=ttnn.bfloat16, + program_config=pc_1, + memory_config=ttnn.L1_WIDTH_SHARDED_MEMORY_CONFIG if mode == "decode" else ttnn.DRAM_MEMORY_CONFIG, +) +``` + +#### 3.1 FF1/FF3 Matmul with 2D Weight Fracturing + +In the case of TG systems, where we have access to a 2D device mesh, we can leverage 2D weight fracturing. For a weight tensor with shape `[1, 1, K, N]`, using 2D weight fracturing on a `(8, 4)` device mesh, the resulting shape on each device would be: `[1, 1, K / 4, N / 8]`. In other words, the inner dimension (K) of the matmul is spread out across 4 devices, and to complete the entire matmul operation, a reduction step across the partials is necessary. We do this using an all-reduce operation along the 4 devices in `cluster_axis=1` of the device mesh. +```py + w1_out = tt_all_reduce( + w1_out, + self.mesh_device, + cluster_axis=1, + num_links=2, + sharded=True if mode == "decode" else False, + memory_config=self.model_config["FF1_OUT_GATHERED_MEMCFG"] if mode == "decode" else None, + ) + w3_out = tt_all_reduce( + w3_out, + self.mesh_device, + cluster_axis=1, + num_links=2, + sharded=True if mode == "decode" else False, + memory_config=self.model_config["FF1_OUT_GATHERED_MEMCFG"] if mode == "decode" else None, + ) +``` + +#### 4. Multiply + fused SiLU activation + +The output of the FF1/FF3 matmuls are column fractured tensors (the extra all-reduce operation for TG systems ensures this). The next operation is: +```py +w2_in = SiLU(w1_out) * w3_out +``` +In ttnn, we have access to binary operations that can apply activations to any of the inputs, in a fused manner, leading to better performance as the inputs are only getting loaded/processed once. As such, the fused SiLU operation with the element-wise multiplication can be performed as follows: +```py +w2_in = ttnn.multiply( + w1_out, + w3_out, + memory_config=( + self.model_config["SHARDED_MLP2_INPUT_MEMCFG"] if TG else ttnn.L1_WIDTH_SHARDED_MEMORY_CONFIG + ) + if mode == "decode" + else ttnn.DRAM_MEMORY_CONFIG, + input_tensor_a_activation=ttnn.UnaryOpType.SILU, + dtype=ttnn.bfloat8_b, +) +``` + +Following our pattern mentioned before, the outputs are L1 sharded in `decode` mode and DRAM interleaved in `prefill` mode. + +#### 5. FF2 Matmul +The last computation in MLP is: +```py +y = FF2(w2_in) +``` +FF2 is a row-parallel matmul, meaning that that the weights are fractured across devices in the inner dim. The inputs of FF2, produced by FF1/FF3, are also fractured across devices in the same dimension and as a result, FF2 produces partial outputs across all devices. + +Here's what the call for the FF2 matmul looks like. Note, that once the matmul operations are completed, we can undo the reshape operation we performed on the inputs of MLP to fit the matmuls on device in `prefill`. +```py +w2_out = ttnn.linear( + w2_in, + self.w2, + compute_kernel_config=self.args.compute_kernel_config_hifi2_fp16, + core_grid=ttnn.CoreGrid(y=1, x=8) if not pc_2 else None, + dtype=ttnn.bfloat16, + program_config=pc_2, + memory_config=ttnn.L1_WIDTH_SHARDED_MEMORY_CONFIG if mode == "decode" else ttnn.DRAM_MEMORY_CONFIG, +) + +# Undo the reshape operation used to fit the matmul on device +if seq_len >= 1024: # Reshape back to intended shape + w2_out = ttnn.reshape(w2_out, [1, 1, seq_len, -1]) +``` + +5.1 Accumulating the partial outputs of FF2 + +Since the output of FF2 is the correct shape, but only a partial on each device. The output of the MLP module is required to be fractured, where each device has fully accumulated the inner dim of the matmul, but only has a fraction of the outer dim. There are two different cases to handle this, depending on if the WH system has a 1D or 2D device mesh. + +1. 1D Device Mesh (n300, T3000): reduce-scatter operation across all devices, resulting in outputs fractued in the outer dim. + ```py + w2_out_reduced = ttnn.reduce_scatter( + w2_out, + scatter_dim=3, + math_op=ttnn.ReduceType.Sum, + num_links=1, + memory_config=ttnn.DRAM_MEMORY_CONFIG if mode == "prefill" else ttnn.L1_MEMORY_CONFIG, + ) + ``` +2. 2D Device Mesh (TG): all-reduce operation along the same cluster axis as which the inner dimension is fractured on. The FF2 matmul inner dim is fractured across cluster axis 0 (row-parallel across 8 device), and the outer dim is fractured across cluster axis 1 (4 devices). Then an all-reduce performed on cluster axis 0 will accumulate the partials across the inner dim of the matmul and replicate them along all the devices in that axis, while still keeping them fractured across cluster axis 1 (4 devices). + ```py + w2_out_reduced = tt_all_reduce( + w2_out, + self.mesh_device, + cluster_axis=0, + num_links=2, + dim=0, + memory_config=(self.model_config["FF2_OUT_GATHERED_MEMCFG"], + sharded=(mode == "decode"), + ) + ``` + ### 2.6 Decoder

Decoder Diagram From 2df6b5408011ec3aa86f9c5f0dae51b4e3816b35 Mon Sep 17 00:00:00 2001 From: Denys Makoviichuk Date: Thu, 19 Dec 2024 14:13:23 -0800 Subject: [PATCH 81/87] [TT-Train] Fix tracy deps in the tt-train cmake (#16209) ### Problem description Cannot find Tracy.hpp ### What's changed * Added include dir. * Reverted precision in layernorm. ### Checklist - [x] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12420245426/job/34678114906 --- tt-train/sources/ttml/CMakeLists.txt | 1 + tt-train/sources/ttml/ops/layernorm_op.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tt-train/sources/ttml/CMakeLists.txt b/tt-train/sources/ttml/CMakeLists.txt index b4d151445b75..c2695bc18cc7 100644 --- a/tt-train/sources/ttml/CMakeLists.txt +++ b/tt-train/sources/ttml/CMakeLists.txt @@ -19,6 +19,7 @@ if(NOT TARGET Metalium::Metal) "$ENV{TT_METAL_HOME}" "$ENV{TT_METAL_HOME}/tt_metal" "$ENV{TT_METAL_HOME}/tt_metal/third_party/umd" + "$ENV{TT_METAL_HOME}/tt_metal/third_party/tracy/public" "$ENV{TT_METAL_HOME}/tt_metal/hw/inc/wormhole" "$ENV{TT_METAL_HOME}/tt_metal/hw/inc/wormhole/wormhole_b0_defines" "$ENV{TT_METAL_HOME}/tt_metal/hw/inc/" diff --git a/tt-train/sources/ttml/ops/layernorm_op.cpp b/tt-train/sources/ttml/ops/layernorm_op.cpp index 03a036d561e1..b2f66f37f8ed 100644 --- a/tt-train/sources/ttml/ops/layernorm_op.cpp +++ b/tt-train/sources/ttml/ops/layernorm_op.cpp @@ -40,7 +40,7 @@ autograd::TensorPtr layernorm( mean, rstd, /* memory_config */ std::nullopt, - /* compute_kernel_config */ core::ComputeKernelConfig::precise()); + /* compute_kernel_config */ std::nullopt); auto out = autograd::create_tensor(); out->set_value(out_tensors[0].value()); @@ -63,7 +63,7 @@ autograd::TensorPtr layernorm( gamma_grad, beta_grad, /* memory_config */ std::nullopt, - /* compute_kernel_config */ core::ComputeKernelConfig::precise()); + /* compute_kernel_config */ std::nullopt); tensor->add_grad(res[0].value()); gamma->add_grad(res[1].value()); From 5d0a53211ef2bfbf5f45b9f632bf360a4519a7d8 Mon Sep 17 00:00:00 2001 From: Almeet Bhullar Date: Thu, 19 Dec 2024 22:42:32 +0000 Subject: [PATCH 82/87] Updating Allocator docs to explain first fit usage --- README.md | 2 +- tech_reports/memory/allocator.md | 30 ++++++++++++++++++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 306acfcb9b95..a4e687fb6d06 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ Get started with [simple kernels](https://docs.tenstorrent.com/tt-metalium/lates - [Data Formats](./tech_reports/data_formats/data_formats.md) (updated Sept 7th) - [Reconfiguring Data Formats](./tech_reports/data_formats/reconfig_data_format.md) (updated Oct 17th) - [Handling special floating-point numbers](./tech_reports/Handling_Special_Value/special_values.md) (updated Oct 5th) -- [Allocator](./tech_reports/memory/allocator.md) (Updated Oct 30th) +- [Allocator](./tech_reports/memory/allocator.md) (Updated Dec 19th) - [Tensor Layouts](./tech_reports/tensor_layouts/tensor_layouts.md) (updated Sept 6th) - [Saturating DRAM Bandwidth](./tech_reports/Saturating_DRAM_bandwidth/Saturating_DRAM_bandwidth.md) (updated Sept 6th) - [Flash Attention on Wormhole](./tech_reports/FlashAttention/FlashAttention.md) (updated Sept 6th) diff --git a/tech_reports/memory/allocator.md b/tech_reports/memory/allocator.md index 4043199fe8a1..135baf4b3f9a 100644 --- a/tech_reports/memory/allocator.md +++ b/tech_reports/memory/allocator.md @@ -2,13 +2,19 @@ The Allocator maintains a view of on device memory across DRAM and SRAM (i.e. L1). It assigns addresses for `Buffers` and maintains a record of what is free or in-use. The allocator does not interact with on-device memory and is not responsible for copying data to/from the buffers. -1. [Memory Banks](#1-memory-banks) -2. [Lockstep Allocation](#2-lockstep-allocation) - - [2.1 Alignment](#21-alignment) -3. [SRAM Banks and CircularBuffers](#3-sram-banks-and-circularbuffers) -4. [Memory Reports](#4-memory-reports) +1. [Architecture](#1-memory-banks) +2. [Memory Banks](#2-memory-banks) +3. [Lockstep Allocation](#3-lockstep-allocation) + - [3.1 Alignment](#31-alignment) +4. [SRAM Banks and CircularBuffers](#4-sram-banks-and-circularbuffers) +5. [Memory Reports](#5-memory-reports) -## 1. Memory Banks +## 1. Architecture +We use a first fit memory algorithm to allow both top-down and bottom-up allocations in the same memory region. Buffers are able to specify whether they should be alloated in lower address space or higher address space. This enables Buffers of the same class to be grouped together without being fragmented by other Buffer classes. For example, user data Buffers are alloated bottom-up in DRAM while Program binaries are allocated top-down to avoid binaries fragmenting user data space. A use case for this in SRAM for L1 Buffers and CircularBuffers is described [below](#4-sram-banks-and-circularbuffers). Based on this, while best fit algorithms reduce fragmentation for Buffers of the same class they don't account for fragmentation caused by mixing different classes. + +Note: This hybrid of top-down and bottom-up alloations can also be enabled by dividing up available memory into different memory pools that are located at higher/lower address spaces and then allocating Buffers of a particular class into their associated memory pool. This approach has not been explored in detail and would first require collecting data to appropriately size these memory pools and then consider dynamic resizing. + +## 2. Memory Banks The number and size of DRAM and SRAM banks are architecture specific and derived from information specified in the SoC descriptor. @@ -22,7 +28,7 @@ There is one SRAM bank per Tensix compute core and `N` SRAM banks per storage on The SRAM bank size is the storage core bank size if specified otherwise it is `total SRAM size - reserved size`. Reserved region in SRAM is used to hold FW, Kernels, and related runtime metadata. -## 2. Lockstep Allocation +## 3. Lockstep Allocation We follow a `lockstep allocation` scheme across the banks. This means that each bank reserves the same amount of space for a given buffer regardless of how many banks the data is in. Lockstep allocation is illustrated in *Figure 2* @@ -36,17 +42,17 @@ Note: this example shows allocating interleaved buffers but a similar scheme is Managing allocations and deallocations is currently done using a free list. -### 2.1 Alignment +### 3.1 Alignment Allocations in DRAM and SRAM are done at `DRAM_ALIGNMENT` aligned addresses to ensure that reads from DRAM into SRAM do not violate NoC alignment constraints. When sizes in the buffer config do not respect this alignment, allocator pads each page or shard. This leads to some internal fragmentation in the allocated region. -## 3. SRAM Banks and CircularBuffers +## 4. SRAM Banks and CircularBuffers -DRAM buffers are allocated from low address to high address (bottom up) whereas SRAM buffers are allocatd from high address to low address (top down). Data within the allocated region always grows from low to high address. +DRAM buffers are allocated from low address to high address (bottom-up) whereas SRAM buffers are allocatd from high address to low address (top-down). Data within the allocated region always grows from low to high address. -SRAM buffers are allocated top down to avoid growing into `CircularBuffers (CBs)` which are *typically* not managed by the allocator and are placed after the reserved region as shown in *Figure 3*. Typically a CBs lifetime is tied to its corresponding `Program` with the `Program` being responsible for managing all CB addresses. To reduce memory footprint, a CB can share the same address space as a SRAM buffer. In this case the `Allocator` rather than the `Program` manages this CB's address. +SRAM buffers are allocated top down to avoid growing into `CircularBuffers (CBs)` which are *typically* not managed by the allocator and are placed after the reserved region as shown in *Figure 3*. Typically a CBs lifetime is tied to its corresponding `Program` with the `Program` being responsible for managing all CB addresses since its lifetime is within an Op. To reduce memory footprint, a CB can share the same address space as a SRAM buffer. In this case the `Allocator` rather than the `Program` manages this CB's address and the lifetime of the CB extends across Ops. @@ -54,7 +60,7 @@ SRAM buffers are allocated top down to avoid growing into `CircularBuffers (CBs) Note: before any `Program` is run on device there is a validation step to ensure that CBs and SRAM buffers do not overlap. -## 4. Memory Reports +## 5. Memory Reports Debug reports can be generated to get a dump of the memory state at a particular point in time or set to log memory state during each `Program`compile. From eacc150772450e8046249467ac4a0d61d3b3a319 Mon Sep 17 00:00:00 2001 From: Nour Ardo Date: Thu, 19 Dec 2024 18:20:35 -0500 Subject: [PATCH 83/87] Adding asserts for hanging cases in ND tilize/untilize support (#16170) ### Ticket [Link to Github Issue](https://github.com/tenstorrent/tt-metal/issues/16141) ### Problem description For ND support, tilize/untilize currently hang for shapes with values different than 1 for the dimensions greater than 4. ### What's changed Add FATALs to avoid these hangs ### Checklist - [x] Post commit CI passes https://github.com/tenstorrent/tt-metal/actions/runs/12416274632 - [ ] Blackhole Post commit (if applicable) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [ ] New/Existing tests provide coverage for changes --- .../core/to_layout/to_layout_op.cpp | 21 +++++++++++++++++-- .../core/work_split/work_split_tilize.hpp | 5 ++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.cpp b/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.cpp index a5df642fdc2a..0e1f7ba85bf1 100644 --- a/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.cpp +++ b/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.cpp @@ -23,6 +23,20 @@ namespace core { namespace detail { +inline bool validate_nd_support(const ttnn::Tensor& tensor_arg, const ttnn::Layout layout) { + const auto initial_shape = tensor_arg.get_shape(); + if (initial_shape.rank() > 4 && tensor_arg.get_layout() != layout) { + for (int i = 0; i < initial_shape.rank() - 4; i++) { + TT_FATAL( + initial_shape[i] == 1, + "For ND tensors, shape dimensions greater than 4 should be 1, shape at index{} is {}", + i, + initial_shape[i]); + } + } + return true; +} + // Issue #8617: Limitations on tensor width for multicore device tilize inline bool use_multicore_device_tilize( const Tensor& input, const std::optional& output_dtype) { @@ -73,8 +87,10 @@ Tensor to_layout_impl_on_device( if (!requires_padding_change(tensor_arg, layout)) { if (layout == ttnn::ROW_MAJOR_LAYOUT) { + validate_nd_support(tensor_arg, layout); return ttnn::untilize(tensor_arg, output_memory_config, use_multicore_untilize); } + validate_nd_support(tensor_arg, layout); return ttnn::tilize(tensor_arg, output_memory_config, dtype, use_multicore_tilize); } @@ -89,7 +105,7 @@ Tensor to_layout_impl_on_device( for (auto index = 0; index < tensor_shape.rank(); ++index) { output_tensor_end.push_back(tensor_shape[index] - 1); } - + validate_nd_support(tensor_arg, layout); auto tensor = ttnn::untilize_with_unpadding(tensor_arg, output_tensor_end, output_memory_config, use_multicore_untilize); return ttnn::reshape(tensor, tensor_shape); @@ -111,6 +127,7 @@ Tensor to_layout_impl_on_device( pad[i] = {output_padding[i].front, output_padding[i].back}; } auto tensor = ttnn::pad(0, tensor_arg, tt::stl::Span(pad), 0, true, std::nullopt); + validate_nd_support(tensor_arg, layout); return ttnn::tilize(tensor, output_memory_config, dtype, use_multicore_tilize); } @@ -120,7 +137,7 @@ Tensor to_layout_impl_on_device( } else { pad_value_variant = (uint32_t)0; } - + validate_nd_support(tensor_arg, layout); auto tensor = ttnn::tilize_with_val_padding( tensor_arg, result_spec.padded_shape(), pad_value_variant, output_memory_config, dtype, use_multicore_tilize); return tensor.reshape(tensor_arg.logical_shape()); diff --git a/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp b/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp index eaddc1c2d14a..7d6355ec37ef 100644 --- a/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp +++ b/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp @@ -183,7 +183,10 @@ inline std::vector> distribute_work( uint32_t blocks_per_core, bool has_cliff, uint32_t nblocks_per_core_cliff) { - TT_FATAL(padding.rank() >= 2 && padding.rank() <= 4, "Rank needs to be >=2. Shape: {} {}", logical_shape, padding); + TT_FATAL( + logical_shape.rank() >= 2, + "Only tensors >=2D, tensors are supported. Shape: {}", + logical_shape); auto input_w = logical_shape.rank() >= 4 ? logical_shape[-4] : 1; auto input_z = logical_shape.rank() >= 3 ? logical_shape[-3] : 1; From 936cac3f9bfcd15184f7000cd732a57e8d65132e Mon Sep 17 00:00:00 2001 From: Evan Smal Date: Thu, 19 Dec 2024 16:26:00 +0000 Subject: [PATCH 84/87] Fix ttnn.reallocate when unaligned RM tensors are used --- .../unit_tests/operations/test_reallocate.py | 53 +++++++++++++------ .../reader_unary_local_l1_copy_backwards.cpp | 2 +- .../move/device/move_program_factory.cpp | 18 ++----- 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/tests/ttnn/unit_tests/operations/test_reallocate.py b/tests/ttnn/unit_tests/operations/test_reallocate.py index 516209425f4a..4c2daad0e869 100644 --- a/tests/ttnn/unit_tests/operations/test_reallocate.py +++ b/tests/ttnn/unit_tests/operations/test_reallocate.py @@ -12,12 +12,9 @@ from models.utility_functions import is_wormhole_b0, is_blackhole -@pytest.mark.skipif(is_wormhole_b0() or is_blackhole(), reason="#7733: fix for sharding on whb0") -@pytest.mark.parametrize( - "mem_config", [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG, ttnn.L1_BLOCK_SHARDED_MEMORY_CONFIG] -) +@pytest.mark.parametrize("mem_config", [ttnn.DRAM_MEMORY_CONFIG, ttnn.L1_MEMORY_CONFIG]) @pytest.mark.parametrize("num_allocs", [1, 2, 3, 4]) -def test_ttnn_reallocate(device, mem_config, num_allocs): +def test_reallocate_interleaved(device, mem_config, num_allocs): width = 1024 height = 128 depth = 2 @@ -35,17 +32,6 @@ def test_ttnn_reallocate(device, mem_config, num_allocs): } ) - # If sharded, creat actual memory config - if mem_config == ttnn.L1_BLOCK_SHARDED_MEMORY_CONFIG: - shard_spec = ttnn.ShardSpec( - shard_grid, [batch * height * depth // 8, width], ttnn.ShardOrientation.ROW_MAJOR, False - ) - mem_config = ttnn.MemoryConfig( - ttnn.TensorMemoryLayout.HEIGHT_SHARDED, - ttnn.BufferType.L1, - shard_spec, - ) - torch_tensors = [] tensors = [] for i in range(num_allocs): @@ -72,3 +58,38 @@ def test_ttnn_reallocate(device, mem_config, num_allocs): assert new_address >= initial_address assert_with_pcc(torch_tensors[-1], ttnn.to_torch(tensors[-1]), 0.9999) + + +@pytest.mark.parametrize("layout", [ttnn.ROW_MAJOR_LAYOUT, ttnn.TILE_LAYOUT]) +@pytest.mark.parametrize("strategy", [ttnn.ShardStrategy.BLOCK, ttnn.ShardStrategy.HEIGHT]) +@pytest.mark.parametrize( + "input_shape, core_grid", + ( + ([1, 1, 32, 32], ttnn.CoreGrid(x=1, y=1)), + ([1, 1, 256, 256], ttnn.CoreGrid(x=2, y=2)), + ([1, 1, 4, 34], ttnn.CoreGrid(x=1, y=1)), # Checks unaligned RM shard + ([2, 2, 128, 1024], ttnn.CoreGrid(x=4, y=4)), + ), +) +def test_reallocate_sharded(device, input_shape, core_grid, strategy, layout): + if (input_shape[-1] % ttnn.TILE_SIZE != 0 or input_shape[-2] % ttnn.TILE_SIZE != 0) and layout == ttnn.TILE_LAYOUT: + pytest.skip("Shards must be aligned with tile layout") + + input_memory_config = ttnn.create_sharded_memory_config( + input_shape, core_grid, strategy, ttnn.ShardOrientation.ROW_MAJOR + ) + + torch_input_tensor = torch.rand(input_shape).to(dtype=torch.bfloat16) + input_tensor = ttnn.from_torch(torch_input_tensor, dtype=ttnn.bfloat16, layout=layout) + + dummy_tensor = torch.rand([1, 1, 512, 512]) + dummy_tensor = ttnn.from_torch(dummy_tensor, dtype=ttnn.bfloat16, layout=ttnn.ROW_MAJOR_LAYOUT) + dummy_tensor = ttnn.to_device(dummy_tensor, device, ttnn.L1_MEMORY_CONFIG) + + input_tensor = ttnn.to_device(input_tensor, device, input_memory_config) + + ttnn.deallocate(dummy_tensor) # make L1 space for reallocation + output_tensor = ttnn.reallocate(input_tensor) + + output_tensor = ttnn.to_torch(output_tensor) + assert_with_pcc(torch_input_tensor, output_tensor, 1.0) diff --git a/ttnn/cpp/ttnn/operations/data_movement/move/device/kernels/dataflow/reader_unary_local_l1_copy_backwards.cpp b/ttnn/cpp/ttnn/operations/data_movement/move/device/kernels/dataflow/reader_unary_local_l1_copy_backwards.cpp index aef7cb1d9917..a68c181f8e9f 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/move/device/kernels/dataflow/reader_unary_local_l1_copy_backwards.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/move/device/kernels/dataflow/reader_unary_local_l1_copy_backwards.cpp @@ -18,7 +18,7 @@ void kernel_main() { constexpr uint32_t src_cb_id = get_compile_time_arg_val(0); constexpr uint32_t dst_cb_id = get_compile_time_arg_val(1); - uint32_t src_cb_base_addr = get_write_ptr(src_cb_id); // TODO change to read + uint32_t src_cb_base_addr = get_read_ptr(src_cb_id); uint32_t dst_cb_base_addr = get_write_ptr(dst_cb_id); // Copy from top of src cb to top of dst cb (backwards) diff --git a/ttnn/cpp/ttnn/operations/data_movement/move/device/move_program_factory.cpp b/ttnn/cpp/ttnn/operations/data_movement/move/device/move_program_factory.cpp index 8f0e2df0c2a5..c3c7c6b852f0 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/move/device/move_program_factory.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/move/device/move_program_factory.cpp @@ -215,19 +215,10 @@ operation::ProgramWithCallbacks move_multi_core_sharded(const Tensor& input, Ten "Error"); const uint32_t src_cb_sharded = tt::CBIndex::c_0; const uint32_t dst_cb_sharded = tt::CBIndex::c_1; - uint32_t tile_size_bytes = tile_size(cb_data_format); - uint32_t shard_shape_num_tiles = tt::div_up(shard_shape[0] * shard_shape[1], TILE_HEIGHT * TILE_WIDTH); - uint32_t total_size_bytes = 0; - uint32_t page_size_bytes = 0; - if ((shard_shape[0] * shard_shape[1]) % (TILE_HEIGHT * TILE_WIDTH) == 0) { - uint32_t tile_size_bytes = tile_size(cb_data_format); - total_size_bytes = shard_shape_num_tiles * tile_size_bytes; - page_size_bytes = tile_size_bytes; - } else { - uint32_t datum_size_bytes = datum_size(cb_data_format); - total_size_bytes = shard_shape[0] * shard_shape[1] * datum_size_bytes; - page_size_bytes = shard_shape[1] * datum_size_bytes; - } + + uint32_t total_size_bytes = input.buffer()->aligned_size_per_bank(); + uint32_t page_size_bytes = input.buffer()->aligned_page_size(); + CircularBufferConfig src_cb_sharded_config = CircularBufferConfig(total_size_bytes, {{src_cb_sharded, cb_data_format}}) .set_page_size(src_cb_sharded, page_size_bytes); @@ -242,6 +233,7 @@ operation::ProgramWithCallbacks move_multi_core_sharded(const Tensor& input, Ten auto input_buffer_address = input.buffer()->address(); auto output_buffer_address = output.buffer()->address(); + TT_FATAL( output_buffer_address > input_buffer_address, "Expected output buffer to be allocated at a higher address than input buffer"); From 2336024ba165f929edf5b0e2cdeb563782ab33a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Choi=20HyungSuk=28=EC=B5=9C=ED=98=95=EC=84=9D=29?= Date: Fri, 20 Dec 2024 14:43:17 +0900 Subject: [PATCH 85/87] #15891: improve full accuracy and fix full bugs (#16182) ### Ticket https://github.com/tenstorrent/tt-metal/issues/15891#issue-2731910389 ### Problem description Many operations accept float scalar values as input, in addition to tensors. These values are used in computations within the kernel, but when the scalar values are treated as bfloat16, they are truncated without rounding. And truncation introduces more error compared to rounding. ### What's changed 1. The scalar values are rounded on the host, ensuring that when they are stored in the bfloat16 cb, the values with less error are used. 2. To expedite verification, I plan to apply the changes only to the "full" op and run the GitHub action to check for any issues. The changes for other ops will be included in a subsequent PR. 3. I have added a bfloat16 rounding test to the test_full. 4. I have fixed an issue with the full op's cache implementation. 5. Fix same issue in full_like op prev bfloat16 full result ``` torch_output tensor([[0.1504, 0.1504, 0.1504]], dtype=torch.bfloat16) tt_output_cpu TorchTensor([[0.1494, 0.1494, 0.1494]], dtype=torch.bfloat16) torch_output tensor([[-1.2031, -1.2031, -1.2031]], dtype=torch.bfloat16) tt_output_cpu TorchTensor([[-1.1953, -1.1953, -1.1953]], dtype=torch.bfloat16) ``` Now the result of "full" is exactly the same. ### Checklist - [ ] Post commit CI passes - [ ] Blackhole Post commit (if applicable) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [x] New/Existing tests provide coverage for changes --- tests/ttnn/unit_tests/operations/test_full.py | 40 +++++++------ .../unit_tests/operations/test_full_like.py | 58 +++++++++---------- .../full/device/full_program_factory.cpp | 41 ++++++++++--- .../full/device/kernels/writer_full.cpp | 4 +- .../full_like/device/full_like_factory.cpp | 42 +++++++++++--- 5 files changed, 123 insertions(+), 62 deletions(-) diff --git a/tests/ttnn/unit_tests/operations/test_full.py b/tests/ttnn/unit_tests/operations/test_full.py index eda2eabab079..12e08f191663 100644 --- a/tests/ttnn/unit_tests/operations/test_full.py +++ b/tests/ttnn/unit_tests/operations/test_full.py @@ -27,14 +27,14 @@ ) def test_full_int(device, input_shape, fill_value): torch_any = torch.randint(0, 100, (input_shape), dtype=torch.int32) - torch_output_tensor = torch.full(input_shape, fill_value) + torch_output = torch.full(input_shape, fill_value, dtype=torch.int32) any = ttnn.from_torch(torch_any, device=device, layout=ttnn.TILE_LAYOUT) - output_tensor = ttnn.moreh_full(input_shape, fill_value, any) - assert ttnn.is_tensor_storage_on_device(output_tensor) - output_tensor = ttnn.to_torch(output_tensor) + tt_output = ttnn.moreh_full(input_shape, fill_value, any) + assert ttnn.is_tensor_storage_on_device(tt_output) + tt_output_cpu = ttnn.to_torch(tt_output) - assert_equal(torch_output_tensor, output_tensor) + assert torch.equal(torch_output, tt_output_cpu) @pytest.mark.parametrize( @@ -43,12 +43,17 @@ def test_full_int(device, input_shape, fill_value): [1, 3], # single tile [32, 32], # single tile [5, 96, 64], # multiple tiles - [3, 91, 67, 77], # not multiple of 32 ], ) @pytest.mark.parametrize( "fill_value", - [0.15, -1.2], + [ + 3.14, + 2.00781250, # mantissa: 0000 0001, bf16 round down test + 2.00830080, # mantissa: 0000 0001 0001, bf16 round up test + 2.02343750, # mantissa: 0000 0011, bf16 round up test + -3.9921875, # test mantissa overflow. answer should be 4 + ], ) @pytest.mark.parametrize( "dtype", @@ -60,13 +65,13 @@ def test_full_int(device, input_shape, fill_value): def test_full_float(device, input_shape, fill_value, dtype): torch_any = torch.rand((input_shape), dtype=dtype) - torch_output_tensor = torch.full(input_shape, fill_value) + torch_output = torch.full(input_shape, fill_value, dtype=dtype) any = ttnn.from_torch(torch_any, device=device, layout=ttnn.TILE_LAYOUT) - output_tensor = ttnn.moreh_full(input_shape, fill_value, any) - assert ttnn.is_tensor_storage_on_device(output_tensor) - output_tensor = ttnn.to_torch(output_tensor) + tt_output = ttnn.moreh_full(input_shape, fill_value, any) + assert ttnn.is_tensor_storage_on_device(tt_output) + tt_output_cpu = ttnn.to_torch(tt_output) - assert_equal(torch_output_tensor, output_tensor) + assert torch.equal(torch_output, tt_output_cpu) @pytest.mark.parametrize( @@ -88,12 +93,12 @@ def test_full_float(device, input_shape, fill_value, dtype): def test_full_callback(device, input_shape, fill_value, layout, use_program_cache): for i in range(2): torch_any = torch.randint(0, 100, (input_shape), dtype=torch.int32) - torch_output_tensor = torch.full(input_shape, fill_value) + torch_output = torch.full(input_shape, fill_value, dtype=torch.int32) any = ttnn.from_torch(torch_any, device=device, layout=ttnn.TILE_LAYOUT) - output_tensor = ttnn.moreh_full(input_shape, fill_value, any) - assert ttnn.is_tensor_storage_on_device(output_tensor) - output_tensor = ttnn.to_torch(output_tensor) + tt_output = ttnn.moreh_full(input_shape, fill_value, any) + assert ttnn.is_tensor_storage_on_device(tt_output) + tt_output_cpu = ttnn.to_torch(tt_output) torch_dummy = torch.randn([32, 32]) ttnn_dummy = ttnn.from_torch(torch_dummy, device=device) if i == 0: @@ -101,4 +106,5 @@ def test_full_callback(device, input_shape, fill_value, layout, use_program_cach assert num_program_cache_entries > 0 else: assert device.num_program_cache_entries() == num_program_cache_entries - assert_equal(torch_output_tensor, output_tensor) + + assert torch.equal(torch_output, tt_output_cpu) diff --git a/tests/ttnn/unit_tests/operations/test_full_like.py b/tests/ttnn/unit_tests/operations/test_full_like.py index 92aa32fd5e03..cbac2a9d28bf 100644 --- a/tests/ttnn/unit_tests/operations/test_full_like.py +++ b/tests/ttnn/unit_tests/operations/test_full_like.py @@ -32,17 +32,15 @@ ], ) def test_full_like_int(device, input_shape, fill_value, layout): - torch_input_tensor = torch.randint(0, 100, (input_shape), dtype=torch.int32) - torch_output_tensor = torch.full_like(torch_input_tensor, fill_value) + torch_input = torch.randint(0, 100, (input_shape), dtype=torch.int32) + torch_output = torch.full_like(torch_input, fill_value, dtype=torch.int32) - input_tensor = ttnn.from_torch(torch_input_tensor, layout=layout, device=device) - input_tensor = ttnn.to_device(input_tensor, device) - output_tensor = ttnn.moreh_full_like(input_tensor, fill_value) - assert ttnn.is_tensor_storage_on_device(output_tensor) - output_tensor = ttnn.from_device(output_tensor) - output_tensor = ttnn.to_torch(output_tensor) + tt_input = ttnn.from_torch(torch_input, layout=layout, device=device) + tt_output = ttnn.moreh_full_like(tt_input, fill_value) + assert ttnn.is_tensor_storage_on_device(tt_output) + tt_output_cpu = ttnn.to_torch(tt_output) - assert_equal(torch_output_tensor, output_tensor) + assert torch.equal(torch_output, tt_output_cpu) @pytest.mark.parametrize( @@ -55,7 +53,13 @@ def test_full_like_int(device, input_shape, fill_value, layout): ) @pytest.mark.parametrize( "fill_value", - [0.15, -1.2], + [ + 3.14, + 2.00781250, # mantissa: 0000 0001, bf16 round down test + 2.00830080, # mantissa: 0000 0001 0001, bf16 round up test + 2.02343750, # mantissa: 0000 0011, bf16 round up test + -3.9921875, # test mantissa overflow. answer should be 4 + ], ) @pytest.mark.parametrize( "dtype", @@ -71,17 +75,15 @@ def test_full_like_int(device, input_shape, fill_value, layout): ], ) def test_full_like_float(device, input_shape, fill_value, dtype, layout): - torch_input_tensor = torch.rand((input_shape), dtype=dtype) - torch_output_tensor = torch.full_like(torch_input_tensor, fill_value) + torch_input = torch.rand((input_shape), dtype=dtype) + torch_output = torch.full_like(torch_input, fill_value, dtype=dtype) - input_tensor = ttnn.from_torch(torch_input_tensor, layout=layout, device=device) - input_tensor = ttnn.to_device(input_tensor, device) - output_tensor = ttnn.moreh_full_like(input_tensor, fill_value) - assert ttnn.is_tensor_storage_on_device(output_tensor) - output_tensor = ttnn.from_device(output_tensor) - output_tensor = ttnn.to_torch(output_tensor) + tt_input = ttnn.from_torch(torch_input, layout=layout, device=device) + tt_output = ttnn.moreh_full_like(tt_input, fill_value) + assert ttnn.is_tensor_storage_on_device(tt_output) + tt_output_cpu = ttnn.to_torch(tt_output) - assert_equal(torch_output_tensor, output_tensor) + assert torch.equal(torch_output, tt_output_cpu) @pytest.mark.parametrize( @@ -102,15 +104,13 @@ def test_full_like_float(device, input_shape, fill_value, dtype, layout): ) def test_full_like_callback(device, input_shape, fill_value, layout, use_program_cache): for i in range(2): - torch_input_tensor = torch.randint(0, 100, (input_shape), dtype=torch.int32) - torch_output_tensor = torch.full_like(torch_input_tensor, fill_value) - - input_tensor = ttnn.from_torch(torch_input_tensor, layout=layout, device=device) - input_tensor = ttnn.to_device(input_tensor, device) - output_tensor = ttnn.moreh_full_like(input_tensor, fill_value) - assert ttnn.is_tensor_storage_on_device(output_tensor) - output_tensor = ttnn.from_device(output_tensor) - output_tensor = ttnn.to_torch(output_tensor) + torch_input = torch.randint(0, 100, (input_shape), dtype=torch.int32) + torch_output = torch.full_like(torch_input, fill_value) + + tt_input = ttnn.from_torch(torch_input, layout=layout, device=device) + tt_output = ttnn.moreh_full_like(tt_input, fill_value) + assert ttnn.is_tensor_storage_on_device(tt_output) + tt_output_cpu = ttnn.to_torch(tt_output) if i == 0: num_program_cache_entries = device.num_program_cache_entries() assert num_program_cache_entries > 0 @@ -119,4 +119,4 @@ def test_full_like_callback(device, input_shape, fill_value, layout, use_program torch_dummy = torch.randn([32, 32]) tt_dummy = ttnn.from_torch(torch_dummy, device=device) - assert_equal(torch_output_tensor, output_tensor) + assert torch.equal(torch_output, tt_output_cpu) diff --git a/ttnn/cpp/ttnn/operations/full/device/full_program_factory.cpp b/ttnn/cpp/ttnn/operations/full/device/full_program_factory.cpp index ec13125d7628..883411de6801 100644 --- a/ttnn/cpp/ttnn/operations/full/device/full_program_factory.cpp +++ b/ttnn/cpp/ttnn/operations/full/device/full_program_factory.cpp @@ -9,6 +9,28 @@ using namespace tt; using namespace tt::constants; +// After the full modification and if there are no issues in the overall tests, it will be added to `bfloat16.hpp` and +// applied globally. +uint32_t get_bfloat16_rounded(const float val) { + uint32_t float_bits = *reinterpret_cast(&val); + + // upper 16 bits + uint16_t bfloat16_bits = float_bits >> 16; + + // check Guard, Round, Sticky bits from lower 16 bits + uint32_t lower_bits = float_bits & 0xFFFF; + uint32_t guard_bit = (lower_bits >> 15) & 1; + uint32_t round_bit = (lower_bits >> 14) & 1; + uint32_t sticky_bit = (lower_bits & 0x3FFF) != 0; + + // Tie-to-even rounding rule + if (guard_bit && (round_bit || sticky_bit || (bfloat16_bits & 1))) { + bfloat16_bits += 1; + } + + return static_cast(bfloat16_bits) << 16; +} + union datatype { uint32_t u32; float f32; @@ -29,12 +51,6 @@ FullOperation::ProgramFactory::cached_program_t FullOperation::ProgramFactory::c tt::DataFormat data_format = tt::tt_metal::datatype_to_dataformat_converter(dtype); uint32_t single_tile_size = tt::tt_metal::detail::TileSize(data_format); - if (std::holds_alternative(fill_value)) { - u.u32 = std::get(fill_value); - } else if (std::holds_alternative(fill_value)) { - u.f32 = std::get(fill_value); - } - // Create program Program program = Program(); @@ -57,6 +73,17 @@ FullOperation::ProgramFactory::cached_program_t FullOperation::ProgramFactory::c default: break; } + if (std::holds_alternative(fill_value)) { + u.u32 = std::get(fill_value); + } else if (std::holds_alternative(fill_value)) { + auto float_fill_value = std::get(fill_value); + if (dtype == DataType::BFLOAT16) { + u.u32 = get_bfloat16_rounded(float_fill_value); + } else { + u.f32 = float_fill_value; + } + } + auto writer_id = CreateWriteKernel( program, "ttnn/cpp/ttnn/operations/full/device/kernels/writer_full.cpp", @@ -78,7 +105,7 @@ FullOperation::ProgramFactory::cached_program_t FullOperation::ProgramFactory::c } else { TT_THROW("Core not in specified core ranges"); } - std::vector writer_args = {u.u32, output.buffer()->address(), num_tiles_per_core, tile_offset}; + std::vector writer_args = {output.buffer()->address(), u.u32, num_tiles_per_core, tile_offset}; SetRuntimeArgs(program, writer_id, core, writer_args); tile_offset += num_tiles_per_core; } diff --git a/ttnn/cpp/ttnn/operations/full/device/kernels/writer_full.cpp b/ttnn/cpp/ttnn/operations/full/device/kernels/writer_full.cpp index 763ddd158bd0..176e1195c7ee 100644 --- a/ttnn/cpp/ttnn/operations/full/device/kernels/writer_full.cpp +++ b/ttnn/cpp/ttnn/operations/full/device/kernels/writer_full.cpp @@ -13,8 +13,8 @@ typedef union { constexpr uint32_t onetile = 1; void kernel_main() { - uint32_t fill_value = get_arg_val(0); - uint32_t output_addr = get_arg_val(1); + uint32_t output_addr = get_arg_val(0); + uint32_t fill_value = get_arg_val(1); uint32_t num_tiles = get_arg_val(2); uint32_t start_id = get_arg_val(3); diff --git a/ttnn/cpp/ttnn/operations/full_like/device/full_like_factory.cpp b/ttnn/cpp/ttnn/operations/full_like/device/full_like_factory.cpp index 06fdb5b20dd1..a7a14a364fad 100644 --- a/ttnn/cpp/ttnn/operations/full_like/device/full_like_factory.cpp +++ b/ttnn/cpp/ttnn/operations/full_like/device/full_like_factory.cpp @@ -18,6 +18,28 @@ using namespace tt::tt_metal; using namespace tt::constants; using namespace tt::tt_metal::detail; +// After the full modification and if there are no issues in the overall tests, it will be added to `bfloat16.hpp` and +// applied globally. +uint32_t get_bfloat16_rounded(const float val) { + uint32_t float_bits = *reinterpret_cast(&val); + + // upper 16 bits + uint16_t bfloat16_bits = float_bits >> 16; + + // check Guard, Round, Sticky bits from lower 16 bits + uint32_t lower_bits = float_bits & 0xFFFF; + uint32_t guard_bit = (lower_bits >> 15) & 1; + uint32_t round_bit = (lower_bits >> 14) & 1; + uint32_t sticky_bit = (lower_bits & 0x3FFF) != 0; + + // Tie-to-even rounding rule + if (guard_bit && (round_bit || sticky_bit || (bfloat16_bits & 1))) { + bfloat16_bits += 1; + } + + return static_cast(bfloat16_bits) << 16; +} + union datatype { uint32_t u32; float f32; @@ -29,11 +51,6 @@ FullLikeOperation::ProgramFactory::cached_program_t FullLikeOperation::ProgramFa tensor_return_value_t& output) { auto input = tensor_args.input; auto fill_value = operation_attributes.fill_value; - if (std::holds_alternative(fill_value)) { - u.u32 = std::get(fill_value); - } else if (std::holds_alternative(fill_value)) { - u.f32 = std::get(fill_value); - } DataType dtype{operation_attributes.dtype}; Layout layout{operation_attributes.layout}; Device* device = input.device(); @@ -67,6 +84,17 @@ FullLikeOperation::ProgramFactory::cached_program_t FullLikeOperation::ProgramFa default: break; } + if (std::holds_alternative(fill_value)) { + u.u32 = std::get(fill_value); + } else if (std::holds_alternative(fill_value)) { + auto float_fill_value = std::get(fill_value); + if (dtype == DataType::BFLOAT16) { + u.u32 = get_bfloat16_rounded(float_fill_value); + } else { + u.f32 = float_fill_value; + } + } + std::vector writer_compile_time_args = {(uint32_t)cb_fill_value_id}; auto writer_id = CreateKernel( @@ -87,7 +115,7 @@ FullLikeOperation::ProgramFactory::cached_program_t FullLikeOperation::ProgramFa } else { TT_ASSERT(false, "Core not in specified core ranges"); } - SetRuntimeArgs(program, writer_id, core, {u.u32, output.buffer()->address(), num_tiles_per_core, tiles_offset}); + SetRuntimeArgs(program, writer_id, core, {output.buffer()->address(), u.u32, num_tiles_per_core, tiles_offset}); tiles_offset += num_tiles_per_core; } @@ -110,7 +138,7 @@ void FullLikeOperation::ProgramFactory::override_runtime_arguments( CoreCoord core = {i / num_cores_y, i % num_cores_y}; { auto& runtime_args = GetRuntimeArgs(program, writer_kernel_id, core); - runtime_args[1] = output_buffer_address; + runtime_args[0] = output_buffer_address; } } } From 2edaca753f226e9b6bf5a12f16786332bedac6fe Mon Sep 17 00:00:00 2001 From: Stanislav Minakov Date: Thu, 19 Dec 2024 23:08:15 -0800 Subject: [PATCH 86/87] Revert "Fix ttnn.from_torch for 0D/1D tensors with tile layout (#15882)" (#16222) ### Ticket Link to Github Issue ### Problem description Recently merged PR caused a lot of strange failures for T3K Resnet, Stable Diffusion, and Forge. Reverting PR for now and will investigate the issues more closely. ### What's changed This reverts commit 444b0dcd057317039e5a20fe8509bf7dca33d8f8. ### Checklist - [x] [Post commit CI passes](https://github.com/tenstorrent/tt-metal/actions/runs/12426434675) - [ ] Blackhole Post commit (if applicable) - [ ] Model regression CI testing passes (if applicable) - [ ] Device performance regression CI testing passes (if applicable) - [ ] **(For models and ops writers)** Full [new models](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) tests passes - [ ] New/Existing tests provide coverage for changes --- .../tt_eager/ops/test_tilize_zero_padding.cpp | 2 +- ...test_tilize_zero_padding_channels_last.cpp | 2 +- .../ttnn/unit_tests/operations/test_matmul.py | 4 +- .../ttnn/unit_tests/test_to_and_from_torch.py | 19 -- ttnn/cpp/pybind11/pytensor.cpp | 22 +- .../core/to_layout/to_layout_op.cpp | 273 ++++++++++-------- .../core/work_split/work_split_tilize.hpp | 6 +- .../data_movement/concat/concat.cpp | 2 +- .../ttnn/operations/data_movement/pad/pad.cpp | 2 +- .../data_movement/reshape_view/reshape.cpp | 4 +- .../device/tilize_with_val_padding_op.cpp | 51 ++-- .../device/tilize_with_val_padding_op.hpp | 6 +- .../tilize_with_val_padding.cpp | 14 +- .../tilize_with_val_padding.hpp | 4 +- .../tilize_with_val_padding_pybind.hpp | 8 +- .../experimental/auto_format/auto_format.cpp | 3 +- .../moreh/moreh_helper_functions.cpp | 34 +-- ttnn/cpp/ttnn/tensor/layout/tensor_layout.cpp | 103 +++---- ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp | 9 +- ttnn/cpp/ttnn/tensor/tensor.cpp | 29 +- ttnn/cpp/ttnn/tensor/tensor.hpp | 9 +- ttnn/cpp/ttnn/tensor/tensor_impl.cpp | 135 ++++----- ttnn/cpp/ttnn/tensor/tensor_impl.hpp | 5 +- ttnn/cpp/ttnn/tensor/tensor_ops.cpp | 46 ++- ttnn/cpp/ttnn/tensor/tensor_ops.hpp | 2 +- ttnn/cpp/ttnn/tensor/types.hpp | 13 +- 26 files changed, 396 insertions(+), 411 deletions(-) diff --git a/tests/tt_eager/ops/test_tilize_zero_padding.cpp b/tests/tt_eager/ops/test_tilize_zero_padding.cpp index 2cfd265e8882..580bd4102955 100644 --- a/tests/tt_eager/ops/test_tilize_zero_padding.cpp +++ b/tests/tt_eager/ops/test_tilize_zero_padding.cpp @@ -46,7 +46,7 @@ int main(int argc, char** argv) { log_debug(LogTest, "Moving src data to host to validate"); Tensor host_a = a.cpu(); // Move tensor a to host to validate // TODO: Update when tensor.pad_to_tile() function is added - auto padded_shape = a.get_padded_shape(); + auto padded_shape = a.get_legacy_shape(); padded_shape[2] = round_up(padded_shape[2], TILE_HEIGHT); padded_shape[3] = round_up(padded_shape[3], TILE_WIDTH); Tensor padded_host_a = host_a.pad(padded_shape, ttnn::SimpleShape{0, 0, 0, 0}, 0); diff --git a/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp b/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp index cc37bd14bf71..e565c4d80269 100644 --- a/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp +++ b/tests/tt_eager/ops/test_tilize_zero_padding_channels_last.cpp @@ -49,7 +49,7 @@ int main(int argc, char** argv) { Tensor host_a = a.cpu(); // Move tensor a to host to validate Tensor g = Tensor(host_a.get_storage(), shape, DataType::BFLOAT16, Layout::ROW_MAJOR); // TODO: Update when tensor.pad_to_tile() function is added - auto padded_shape = g.get_padded_shape(); + auto padded_shape = g.get_legacy_shape(); padded_shape[2] = round_up(padded_shape[2], TILE_HEIGHT); padded_shape[3] = round_up(padded_shape[3], TILE_WIDTH); Tensor padded_g = g.pad(padded_shape, ttnn::SimpleShape{0, 0, 0, 0}, 0); diff --git a/tests/ttnn/unit_tests/operations/test_matmul.py b/tests/ttnn/unit_tests/operations/test_matmul.py index 1f69c3905138..a57cb5f10533 100644 --- a/tests/ttnn/unit_tests/operations/test_matmul.py +++ b/tests/ttnn/unit_tests/operations/test_matmul.py @@ -1230,14 +1230,14 @@ def test_matmul_with_matched_width_height(device, m_size, k_size, n_size): def test_matmul_with_matched_width_height_from_1D(device, k_size, n_size): torch.manual_seed(0) - torch_input_tensor_a = torch.rand((1, k_size), dtype=torch.bfloat16) + torch_input_tensor_a = torch.rand((k_size), dtype=torch.bfloat16) torch_input_tensor_b = torch.rand((k_size, n_size), dtype=torch.bfloat16) torch_output_tensor = torch.matmul(torch_input_tensor_a, torch_input_tensor_b) input_tensor_a = ttnn.from_torch(torch_input_tensor_a, layout=ttnn.TILE_LAYOUT, device=device) input_tensor_b = ttnn.from_torch(torch_input_tensor_b, layout=ttnn.TILE_LAYOUT, device=device) output = input_tensor_a @ input_tensor_b - output = ttnn.to_torch(output) + output = ttnn.to_torch(output, torch_rank=1) assert len(output.shape) == len(torch_output_tensor.shape) assert output.shape == torch_output_tensor.shape diff --git a/tests/ttnn/unit_tests/test_to_and_from_torch.py b/tests/ttnn/unit_tests/test_to_and_from_torch.py index 539edf79084c..f3132050e531 100644 --- a/tests/ttnn/unit_tests/test_to_and_from_torch.py +++ b/tests/ttnn/unit_tests/test_to_and_from_torch.py @@ -84,22 +84,3 @@ def test_from_torch_large(device): x_tensor = ttnn.from_torch(torch_x, layout=ttnn.TILE_LAYOUT) x_tensor = ttnn.to_torch(x_tensor) assert torch.allclose(torch_x, x_tensor) - - -@pytest.mark.parametrize( - "shape", - [ - (), - (1), - (2), - (0), - ], -) -@pytest.mark.parametrize("layout", [ttnn.ROW_MAJOR_LAYOUT, ttnn.TILE_LAYOUT]) -@pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16]) -def test_to_for_01_rank(shape, layout, dtype): - torch_input_tensor = torch.rand(shape, dtype=dtype) - tensor = ttnn.from_torch(torch_input_tensor, layout=layout) - # Conversion in the opposite direction is not yet supported - # torch_output_tensor = ttnn.to_torch(tensor) - # assert torch.allclose(torch_input_tensor, torch_output_tensor) diff --git a/ttnn/cpp/pybind11/pytensor.cpp b/ttnn/cpp/pybind11/pytensor.cpp index 01379239ed3b..6f9ccc5ab649 100644 --- a/ttnn/cpp/pybind11/pytensor.cpp +++ b/ttnn/cpp/pybind11/pytensor.cpp @@ -115,13 +115,16 @@ Tensor convert_float_vector_to_tt_tensor( Layout::TILE, layout); } - auto result_cpu_spec = TensorSpec( - ttnn::SimpleShape(shape), TensorLayout(data_type, PageConfig(Layout::TILE, tile), MemoryConfig{})); auto owned_buffer = create_owned_buffer_from_vector_of_floats(std::move(data), DataType::FLOAT32); auto float_tensor = Tensor(OwnedStorage{owned_buffer}, shape, DataType::FLOAT32, Layout::ROW_MAJOR, tile); - if (result_cpu_spec.logical_shape() != result_cpu_spec.padded_shape()) { - float_tensor = - tensor_ops::tensor_pad(float_tensor, result_cpu_spec.padded_shape(), ttnn::SimpleShape{0, 0, 0, 0}, 0); + auto tile_val = tile.value_or(Tile()); + if (shape[2] % tile_val.get_height() != 0 || shape[3] % tile_val.get_width() != 0) { + auto padded_shape = shape; + padded_shape[2] = tt::round_up(shape[2], tile_val.get_height()); + padded_shape[3] = tt::round_up(shape[3], tile_val.get_width()); + + float_tensor = tensor_ops::tensor_pad( + float_tensor, LegacyShape(shape, padded_shape), ttnn::SimpleShape{0, 0, 0, 0}, 0); } auto output_float_data = owned_buffer::get_as(float_tensor.to(Layout::TILE)).get(); auto output_packed_data = @@ -129,16 +132,14 @@ Tensor convert_float_vector_to_tt_tensor( ? pack_fp32_vec_as_bfp8_tiles(output_float_data, /*row_major_input=*/false, /*is_exp_a=*/false, tile) : pack_fp32_vec_as_bfp4_tiles(output_float_data, /*row_major_input=*/false, /*is_exp_a=*/false, tile); auto output_buffer = owned_buffer::create(std::move(output_packed_data)); - auto tensor = Tensor(std::move(OwnedStorage{std::move(output_buffer)}), result_cpu_spec); + auto tensor = Tensor(std::move(OwnedStorage{std::move(output_buffer)}), shape, data_type, Layout::TILE, tile); if (device) { return tensor.to(device, memory_config.value_or(MemoryConfig{})); } return tensor; } - auto result_cpu_spec = TensorSpec( - ttnn::SimpleShape(shape), TensorLayout(data_type, PageConfig(Layout::ROW_MAJOR, tile), MemoryConfig{})); auto owned_buffer = create_owned_buffer_from_vector_of_floats(std::move(data), data_type); - auto tensor = Tensor(OwnedStorage{owned_buffer}, result_cpu_spec).to(layout); + auto tensor = Tensor(OwnedStorage{owned_buffer}, shape, data_type, Layout::ROW_MAJOR, tile).to(layout); if (device) { return tensor.to(device, memory_config.value_or(MemoryConfig{})); } @@ -1211,8 +1212,7 @@ void pytensor_module(py::module& m_tensor) { const std::array& output_tensor_shape, const std::array& input_tensor_start, float pad_value) { - return self.pad( - ttnn::SimpleShape(output_tensor_shape), ttnn::SimpleShape(input_tensor_start), pad_value); + return self.pad(output_tensor_shape, ttnn::SimpleShape(input_tensor_start), pad_value); }, R"doc( Pad TT Tensor with given pad value ``arg2``. diff --git a/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.cpp b/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.cpp index 0e1f7ba85bf1..5f5d1ac84665 100644 --- a/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.cpp +++ b/ttnn/cpp/ttnn/operations/core/to_layout/to_layout_op.cpp @@ -56,93 +56,6 @@ inline bool use_multicore_device_tilize( return num_tiles_in_row <= max_tiles; } -bool requires_padding_change(const ttnn::Tensor& tensor, ttnn::Layout layout) { - auto tile = tensor.get_tensor_spec().tile(); - if (layout == Layout::ROW_MAJOR) { - // There shouldn't be extra paddings for Row Major layout - return tensor.logical_shape() != tensor.padded_shape(); - } - // It's okay for conversion to tile layout to preserve arbitrary padding as long as it satisfies the alignment - TensorSpec padded_spec( - tensor.padded_shape(), - TensorLayout(tensor.dtype(), PageConfig(layout, std::move(tile)), tensor.memory_config())); - return tensor.get_padded_shape() != padded_spec.padded_shape(); -} - -template -Tensor to_layout_impl_on_device( - const ttnn::Tensor& tensor_arg, - const ttnn::Layout layout, - const std::optional& dtype, - ttnn::MemoryConfig output_memory_config, - T* device) { - bool use_multicore_untilize = true; - bool use_multicore_tilize = use_multicore_device_tilize(tensor_arg, dtype); - - if (layout == ttnn::ROW_MAJOR_LAYOUT) { - TT_FATAL( - !dtype.has_value() || dtype.value() == tensor_arg.dtype(), - "dtype cannot be different from tensor dtype when converting to ROW_MAJOR_LAYOUT on device!"); - } - - if (!requires_padding_change(tensor_arg, layout)) { - if (layout == ttnn::ROW_MAJOR_LAYOUT) { - validate_nd_support(tensor_arg, layout); - return ttnn::untilize(tensor_arg, output_memory_config, use_multicore_untilize); - } - validate_nd_support(tensor_arg, layout); - return ttnn::tilize(tensor_arg, output_memory_config, dtype, use_multicore_tilize); - } - - auto tensor_shape = tensor_arg.get_logical_shape(); - - if (layout == ttnn::ROW_MAJOR_LAYOUT) { - if (tensor_arg.is_sharded()) { - const auto memory_config = tensor_arg.memory_config(); - output_memory_config = tt::tt_metal::MemoryConfig{memory_config.memory_layout, memory_config.buffer_type}; - } - SmallVector output_tensor_end; - for (auto index = 0; index < tensor_shape.rank(); ++index) { - output_tensor_end.push_back(tensor_shape[index] - 1); - } - validate_nd_support(tensor_arg, layout); - auto tensor = - ttnn::untilize_with_unpadding(tensor_arg, output_tensor_end, output_memory_config, use_multicore_untilize); - return ttnn::reshape(tensor, tensor_shape); - } - - TensorSpec result_spec( - tensor_arg.logical_shape(), - TensorLayout( - tensor_arg.dtype(), - PageConfig(layout, std::move(tensor_arg.tensor_spec().tile())), - tensor_arg.memory_config())); - - // ttnn::tilize_with_val_padding doesn't support height sharded tensors - // workaround by applying padding and then tilizing - if (tensor_arg.memory_config().memory_layout == TensorMemoryLayout::HEIGHT_SHARDED) { - ttnn::SmallVector> pad(result_spec.shape().rank()); - auto output_padding = result_spec.shape().padding(); - for (size_t i = 0; i < result_spec.padded_shape().rank(); i++) { - pad[i] = {output_padding[i].front, output_padding[i].back}; - } - auto tensor = ttnn::pad(0, tensor_arg, tt::stl::Span(pad), 0, true, std::nullopt); - validate_nd_support(tensor_arg, layout); - return ttnn::tilize(tensor, output_memory_config, dtype, use_multicore_tilize); - } - - PadValue pad_value_variant; - if (tensor_arg.get_dtype() == ttnn::DataType::BFLOAT16 or tensor_arg.get_dtype() == ttnn::DataType::FLOAT32) { - pad_value_variant = 0.0f; - } else { - pad_value_variant = (uint32_t)0; - } - validate_nd_support(tensor_arg, layout); - auto tensor = ttnn::tilize_with_val_padding( - tensor_arg, result_spec.padded_shape(), pad_value_variant, output_memory_config, dtype, use_multicore_tilize); - return tensor.reshape(tensor_arg.logical_shape()); -} - template Tensor to_layout_impl( const ttnn::Tensor& tensor_arg, @@ -168,44 +81,172 @@ Tensor to_layout_impl( return tensor_arg; } - if (layout != ROW_MAJOR_LAYOUT && layout != TILE_LAYOUT) { + const std::set supported_layouts = { + ttnn::ROW_MAJOR_LAYOUT, + ttnn::TILE_LAYOUT, + }; + + if (supported_layouts.find(layout) == supported_layouts.end()) { TT_THROW("ttnn::to_layout: Unsupported layout conversion from {} to {}!", tensor_arg.get_layout(), layout); } - auto output_memory_config = - memory_config.value_or(ttnn::get_memory_config(tensor_arg).value_or(ttnn::DRAM_MEMORY_CONFIG)); - - if (ttnn::is_tensor_on_device_or_multidevice(tensor_arg)) { - return to_layout_impl_on_device(tensor_arg, layout, dtype, std::move(output_memory_config), device); + const auto requires_padding_change = + [](ttnn::Tensor& tensor, ttnn::Layout layout, const ttnn::Shape& shape) -> bool { + const auto intended_shape = shape; + const auto padded_shape = shape.with_tile_padding(); + if (layout == ttnn::ROW_MAJOR_LAYOUT and intended_shape != padded_shape) { + return true; + } + if (layout == ttnn::TILE_LAYOUT) { + auto tile_shape = tensor.tensor_spec().tile().get_tile_shape(); + if (padded_shape.rank() < 2 or padded_shape[-1] % tile_shape[1] != 0 or + padded_shape[-2] % tile_shape[0] != 0) { + return true; + } + } + return false; + }; + + const auto intended_shape = tensor_arg.get_shape(); + + auto tensor = tensor_arg; + const auto tile = tensor.get_tensor_spec().tile(); + + SmallVector output_shape; + if (layout == ttnn::TILE_LAYOUT and intended_shape.rank() < 2) { + output_shape.push_back(1); + tensor = ttnn::reshape( + tensor, + ttnn::Shape( + SmallVector{1, intended_shape[0]}, + SmallVector{1, tensor_arg.get_shape().with_tile_padding()[0]})); } - - TT_ASSERT(not dtype.has_value(), "dtype cannot be specified when converting layout on host!"); - if (not requires_padding_change(tensor_arg, layout)) { - return device ? tensor_arg.to(layout, device) : tensor_arg.to(layout); + for (auto index = 0; index < intended_shape.rank(); ++index) { + output_shape.push_back(intended_shape[index]); } - if (layout == ttnn::ROW_MAJOR_LAYOUT) { - auto tensor = device ? tensor_arg.to(layout, device) : tensor_arg.to(layout); - tensor = tensor.unpad_from_tile(tensor.get_logical_shape()); - return tensor.reshape(tensor_arg.logical_shape()); + auto padded_output_shape = output_shape; + for (auto index = output_shape.size() - 2; index < output_shape.size(); ++index) { + padded_output_shape[index] = ttnn::pad_to_multiple_of_tile_size( + padded_output_shape[index], + (index == output_shape.size() - 2) ? tile.get_tile_shape()[0] : tile.get_tile_shape()[1]); } - SmallVector padded_input_start; - for (int index = 0; index < tensor_arg.get_logical_shape().rank(); ++index) { - padded_input_start.push_back(0); + auto output_memory_config = + memory_config.value_or(ttnn::get_memory_config(tensor).value_or(ttnn::DRAM_MEMORY_CONFIG)); + + if (ttnn::is_tensor_on_device_or_multidevice(tensor_arg)) { + bool use_multicore_untilize = true; + bool use_multicore_tilize = use_multicore_device_tilize(tensor, dtype); + + if (not requires_padding_change(tensor, layout, tensor.get_shape())) { + if (layout == ttnn::ROW_MAJOR_LAYOUT) { + TT_ASSERT(not dtype.has_value(), "dtype cannot be specified when converting to ROW_MAJOR_LAYOUT!"); + validate_nd_support(tensor_arg, layout); + return ttnn::untilize(tensor, output_memory_config, use_multicore_untilize); + } else if (layout == ttnn::TILE_LAYOUT) { + if (tensor.is_sharded()) { + const auto shard_shape = get_memory_config(tensor).value().shard_spec.value().shape; + if (shard_shape[0] % ttnn::TILE_SIZE != 0 or shard_shape[1] % ttnn::TILE_SIZE != 0) { + TT_THROW( + "ttnn::to_layout: Sharded tensor must have shard shape that is a multiple of " + "TILE_SIZE!"); + } + } + validate_nd_support(tensor_arg, layout); + return ttnn::tilize(tensor, output_memory_config, dtype, use_multicore_tilize); + } else { + throw std::runtime_error("ttnn::to_layout: Unsupported layout!"); + } + } else if (layout == ttnn::ROW_MAJOR_LAYOUT) { + TT_ASSERT(not dtype.has_value(), "dtype cannot be specified when converting to ROW_MAJOR_LAYOUT!"); + + if (tensor.is_sharded()) { + const auto memory_config = tensor.memory_config(); + output_memory_config = + tt::tt_metal::MemoryConfig{memory_config.memory_layout, memory_config.buffer_type}; + } + SmallVector output_tensor_end; + for (auto index = 0; index < tensor.get_shape().rank(); ++index) { + output_tensor_end.push_back(tensor.get_shape()[index] - 1); + } + + validate_nd_support(tensor_arg, layout); + tensor = + ttnn::untilize_with_unpadding(tensor, output_tensor_end, output_memory_config, use_multicore_untilize); + return ttnn::reshape(tensor, ttnn::SimpleShape{output_shape}); + + } else if (layout == ttnn::TILE_LAYOUT) { + SmallVector padded_output_shape; + + for (int index = 0; index < tensor.get_shape().rank(); ++index) { + uint32_t second_last_rank = tensor.get_shape().rank() - 2; // h dim + uint32_t padded_value = + index < second_last_rank + ? tensor.get_shape()[index] + : ttnn::pad_to_multiple_of_tile_size( + tensor.get_shape()[index], + index == second_last_rank ? tile.get_tile_shape()[0] : tile.get_tile_shape()[1]); + padded_output_shape.push_back(padded_value); + } + if (tensor.memory_config().memory_layout == TensorMemoryLayout::HEIGHT_SHARDED) { + // ttnn::tilize_with_val_padding doesn't support height sharded tensors + // workaround by applying padding and then tilizing + SmallVector> padding = { + {0, 0}, + {0, 0}, + {0, padded_output_shape[2] - output_shape[2]}, + {0, padded_output_shape[3] - output_shape[3]}}; + tensor = ttnn::pad(0, tensor, padding, 0, true, std::nullopt); + validate_nd_support(tensor_arg, layout); + return ttnn::tilize(tensor, output_memory_config, dtype, use_multicore_tilize); + } else { + PadValue pad_value_variant; + if (tensor.get_dtype() == ttnn::DataType::BFLOAT16 or tensor.get_dtype() == ttnn::DataType::FLOAT32) { + pad_value_variant = 0.0f; + } else { + pad_value_variant = (uint32_t)0; + } + + validate_nd_support(tensor_arg, layout); + tensor = ttnn::tilize_with_val_padding( + tensor, padded_output_shape, pad_value_variant, output_memory_config, dtype, use_multicore_tilize); + } + + return ttnn::reshape(tensor, ttnn::Shape(tt::tt_metal::LegacyShape{output_shape, padded_output_shape})); + + } else { + TT_THROW("ttnn::to_layout: Unsupported output layout: {}!", layout); + } + } else { + TT_ASSERT(not dtype.has_value(), "dtype cannot be specified when converting layout on host!"); + if (not requires_padding_change(tensor, layout, tensor.get_shape())) { + return device ? tensor.to(layout, device) : tensor.to(layout); + } else if (layout == ttnn::ROW_MAJOR_LAYOUT) { + tensor = device ? tensor.to(layout, device) : tensor.to(layout); + tensor = tensor.unpad_from_tile(tensor.get_logical_shape()); + return ttnn::reshape(tensor, ttnn::SimpleShape{output_shape}); + } else if (layout == ttnn::TILE_LAYOUT) { + SmallVector padded_output_shape; + SmallVector padded_input_start; + for (int index = 0; index < tensor.get_shape().rank(); ++index) { + uint32_t second_last_rank = tensor.get_shape().rank() - 2; // h dim + uint32_t padded_value = + index < second_last_rank + ? tensor.get_shape()[index] + : ttnn::pad_to_multiple_of_tile_size( + tensor.get_shape()[index], + index == second_last_rank ? tile.get_tile_shape()[0] : tile.get_tile_shape()[1]); + padded_output_shape.push_back(padded_value); + padded_input_start.push_back(0); + } + tensor = tensor.pad(padded_output_shape, ttnn::SimpleShape(std::move(padded_input_start)), 0); + tensor = device ? tensor.to(layout, device) : tensor.to(layout); + return ttnn::reshape(tensor, ttnn::Shape(tt::tt_metal::LegacyShape{output_shape, padded_output_shape})); + } else { + TT_THROW("ttnn::to_layout: Unsupported output layout: {}!", layout); + } } - TensorSpec result_spec( - tensor_arg.padded_shape(), - TensorLayout::fromPaddedShape( - tensor_arg.dtype(), - PageConfig(layout, std::move(tensor_arg.tensor_spec().tile())), - tensor_arg.memory_config(), - tensor_arg.logical_shape(), - tensor_arg.padded_shape())); - - auto tensor = tensor_arg.pad(result_spec.padded_shape(), ttnn::SimpleShape(std::move(padded_input_start)), 0); - tensor = device ? tensor.to(layout, device) : tensor.to(layout); - return tensor.reshape(result_spec.logical_shape()); } } // namespace detail diff --git a/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp b/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp index 7d6355ec37ef..49eb8ed38840 100644 --- a/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp +++ b/ttnn/cpp/ttnn/operations/core/work_split/work_split_tilize.hpp @@ -192,9 +192,9 @@ inline std::vector> distribute_work( auto input_z = logical_shape.rank() >= 3 ? logical_shape[-3] : 1; auto input_y = logical_shape.rank() >= 2 ? logical_shape[-2] : 1; - auto padding_w = padding.rank() >= 4 ? padding[-4].back : 0; - auto padding_z = padding.rank() >= 3 ? padding[-3].back : 0; - auto padding_y = padding.rank() >= 2 ? padding[-2].back : 0; + auto padding_w = logical_shape.rank() >= 4 ? padding[padding.get_normalized_index(-4)].back : 0; + auto padding_z = logical_shape.rank() >= 3 ? padding[padding.get_normalized_index(-3)].back : 0; + auto padding_y = logical_shape.rank() >= 2 ? padding[padding.get_normalized_index(-2)].back : 0; // total work is a full rep followed by a padding. auto full_rep_blocks = FullRep(input_y, padding_y, input_z, padding_z, input_w).to_block_reps(); diff --git a/ttnn/cpp/ttnn/operations/data_movement/concat/concat.cpp b/ttnn/cpp/ttnn/operations/data_movement/concat/concat.cpp index 9161f24a2ed1..c0fcd9fb3560 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/concat/concat.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/concat/concat.cpp @@ -146,7 +146,7 @@ MassagedConcat build_untilize_rm_retilize_concat( auto padded = pad_to_tile_vol(queue_id, output, 0.0f, true, output.memory_config()); concat_db_print(true, "[DEBUG] padded to tile layout, now tilizing."); auto tilized = - ttnn::tilize_with_val_padding(padded, padded.get_padded_shape(), 0.0f, output.memory_config()); + ttnn::tilize_with_val_padding(padded, padded.get_legacy_shape(), 0.0f, output.memory_config()); concat_db_print(true, "[DEBUG] tilized"); // need to reshape tilized result to logical concat output shape auto reshaped = ttnn::reshape( diff --git a/ttnn/cpp/ttnn/operations/data_movement/pad/pad.cpp b/ttnn/cpp/ttnn/operations/data_movement/pad/pad.cpp index f5f2b8cc3806..f54a763b6388 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/pad/pad.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/pad/pad.cpp @@ -34,7 +34,7 @@ static ttnn::Tensor pad_impl( return input_tensor; } else { return input_tensor.pad( - ttnn::SimpleShape(output_padded_shape), ttnn::SimpleShape(input_tensor_start), value); + tt::tt_metal::LegacyShape(output_padded_shape), ttnn::SimpleShape{input_tensor_start}, value); } } diff --git a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp index ecc6d7f1c9b1..e3e7378d2f0f 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/reshape_view/reshape.cpp @@ -336,8 +336,6 @@ ttnn::Tensor ReshapeViewOperation::invoke( //The following case should only be called for the device storage case, the rest is a bandaid //for issue 15317 - const uint32_t shape_last_dim = shape.rank() >= 1 ? shape[-1] : 1; - const uint32_t tensor_last_dim = tensor_shape.rank() >= 1 ? tensor_shape[-1] : 1; const uint32_t shape_second_last_dim = shape.rank() >= 2 ? shape[-2]:1; const uint32_t tensor_shape_second_last_dim = tensor_shape.rank() >= 2 ? tensor_shape[-2]:1; @@ -353,7 +351,7 @@ ttnn::Tensor ReshapeViewOperation::invoke( TT_FATAL(shape.logical_shape().volume() != 0, "Tensor volume is not 0, but shape volume is 0"); bool this_is_view = - (tensor_last_dim == shape_last_dim) && (mem_config.is_sharded() == tensor.memory_config().is_sharded()) && + (tensor_shape[-1] == shape[-1]) && (mem_config.is_sharded() == tensor.memory_config().is_sharded()) && (mem_config.is_l1() == tensor.memory_config().is_l1()) && ((tensor.get_layout() == ttnn::ROW_MAJOR_LAYOUT) || // Its row major (tensor_shape_second_last_dim == shape_second_last_dim) || // Second last dimension is the same diff --git a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.cpp b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.cpp index 91eff6af9b34..0677baaaa2cb 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.cpp @@ -14,7 +14,7 @@ namespace ttnn::operations::data_movement { void TilizeWithValPadding::validate(const std::vector& input_tensors) const { const auto& input_tensor_a = input_tensors.at(0); - const auto& input_shape = input_tensor_a.get_padded_shape(); + const auto& input_shape = input_tensor_a.get_legacy_shape(); TT_FATAL(input_tensor_a.storage_type() == StorageType::DEVICE, "Operands need to be on device!"); TT_FATAL(input_tensor_a.buffer() != nullptr, "Operands need to be allocated in buffers on device!"); TT_FATAL(input_tensor_a.get_layout() == Layout::ROW_MAJOR, "Can only tilize row major data"); @@ -22,23 +22,24 @@ void TilizeWithValPadding::validate(const std::vector& input_tensors) co input_tensor_a.get_dtype() == DataType::BFLOAT16 or input_tensor_a.get_dtype() == DataType::UINT32 or input_tensor_a.get_dtype() == DataType::FLOAT32, "Can only tilize bfloat16/float32 or uint32 tensors"); + TT_FATAL(input_shape.rank() >= 2, "Input tensor must be of rank >2, but its shape is {}", input_shape); - for (int i = -static_cast(input_shape.rank()); i >= 0; i--) { + for (auto i = 0; i < input_shape.rank(); i++) { TT_FATAL( - input_shape[i] <= this->output_padded_shape[i], + input_shape[i] <= this->output_tensor_shape[i], "Output tensor shape {} must be greater than or equal to input shape {} in each dimension, but is smaller " "in dimension {}", - this->output_padded_shape, + this->output_tensor_shape, input_shape, i); } - uint32_t num_rows = this->output_padded_shape[-1]; - uint32_t inner_dim = this->output_padded_shape[-2]; + uint32_t num_rows = this->output_tensor_shape[-1]; + uint32_t inner_dim = this->output_tensor_shape[-2]; TT_FATAL( inner_dim % TILE_WIDTH == 0 && num_rows % TILE_HEIGHT == 0, "To be tilizable output tensor shape {} must be divisible by tile size ({}, {})", - output_padded_shape, + output_tensor_shape, TILE_WIDTH, TILE_HEIGHT); @@ -49,33 +50,41 @@ void TilizeWithValPadding::validate(const std::vector& input_tensors) co TT_FATAL( this->output_mem_config.memory_layout == input_tensor_a.memory_config().memory_layout, "Output tensor must have the same memory layout as input tensor"); - for (uint32_t i = 0; i < input_tensor_a.get_padded_shape().rank(); i++) { + for (uint32_t i = 0; i < input_tensor_a.get_legacy_shape().rank(); i++) { if (i != input_shape.rank() - 2) { - TT_FATAL(input_shape[i] == this->output_padded_shape[i], "Error"); + TT_FATAL(input_shape[i] == this->output_tensor_shape[i], "Error"); } } } } -std::vector TilizeWithValPadding::compute_output_specs( +std::vector TilizeWithValPadding::compute_output_shapes( const std::vector& input_tensors) const { - const auto& input_tensor_a = input_tensors.at(0); - auto input_shape = input_tensor_a.get_padded_shape(); + auto input_shape = input_tensors.at(0).get_legacy_shape(); + auto dimensions_pads = std::vector(); + for (auto index = 0; index < input_shape.rank(); index++) { + auto back = this->output_tensor_shape[index] - input_shape[index]; + dimensions_pads.push_back(Padding::PadDimension{.front = 0, .back = back}); + } + const auto padding = Padding(dimensions_pads, Padding::PadValue::Any); + return {tt::tt_metal::LegacyShape(this->output_tensor_shape, padding)}; +} +std::vector TilizeWithValPadding::create_output_tensors( + const std::vector& input_tensors, const std::vector>& output_tensors) const { + const auto& input_tensor_a = input_tensors.at(0); if (input_tensor_a.memory_config().is_sharded()) { + auto output_shape = this->compute_output_shapes(input_tensors).at(0); auto shard_spec = input_tensor_a.shard_spec().value(); - shard_spec.shape[0] = output_padded_shape.volume() / output_padded_shape[-1]; + shard_spec.shape[0] = tt::tt_metal::compute_volume(output_shape) / output_shape[-1]; auto mem_config = this->output_mem_config; mem_config.shard_spec = shard_spec; - return {TensorSpec( - input_shape, - TensorLayout::fromPaddedShape( - output_dtype, PageConfig(Layout::TILE), mem_config, input_shape, output_padded_shape))}; + return { + create_device_tensor(output_shape, this->output_dtype, Layout::TILE, input_tensor_a.device(), mem_config)}; + } else { + return operation::generic_create_output_tensors( + *this, input_tensors, this->output_dtype, Layout::TILE, this->output_mem_config); } - return {TensorSpec( - input_shape, - TensorLayout::fromPaddedShape( - output_dtype, PageConfig(Layout::TILE), output_mem_config, input_shape, output_padded_shape))}; } // TODO: If pad is called on a tile and output is not tile, we could untilize then pad, and output is RM diff --git a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.hpp b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.hpp index 3dc805e0f0b1..2317ba86ab5f 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/device/tilize_with_val_padding_op.hpp @@ -13,14 +13,16 @@ namespace ttnn::operations::data_movement { struct TilizeWithValPadding { - const ttnn::SimpleShape output_padded_shape; + const tt::tt_metal::LegacyShape output_tensor_shape; const PadValue pad_value; const tt::tt_metal::MemoryConfig output_mem_config; const tt::tt_metal::DataType output_dtype; const bool use_multicore; void validate(const std::vector& input_tensors) const; - std::vector compute_output_specs(const std::vector& input_tensors) const; + std::vector compute_output_shapes(const std::vector& input_tensors) const; + std::vector create_output_tensors( + const std::vector& input_tensors, const std::vector>& output_tensors) const; tt::tt_metal::operation::ProgramWithCallbacks create_program( const std::vector& input_tensors, std::vector& output_tensors) const; }; diff --git a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.cpp b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.cpp index a5470e321229..0e6e10445225 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.cpp +++ b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.cpp @@ -66,7 +66,7 @@ MassagedTilizeVal build_ndiml_tilize_val(BaseTilizeValType base_tilize) { .operation = std::move(base_tilize)}); } -ttnn::SimpleShape squeeze_output_shape(ttnn::SimpleShape output_shape) { +tt::tt_metal::LegacyShape squeeze_output_shape(tt::tt_metal::LegacyShape output_shape) { if (output_shape.rank() > 4) { std::array output_shape_4d; output_shape_4d[0] = 1; @@ -77,7 +77,7 @@ ttnn::SimpleShape squeeze_output_shape(ttnn::SimpleShape output_shape) { output_shape_4d[1] = output_shape[1 + extra_rank]; output_shape_4d[2] = output_shape[2 + extra_rank]; output_shape_4d[3] = output_shape[3 + extra_rank]; - return ttnn::SimpleShape(output_shape_4d); + return tt::tt_metal::LegacyShape(output_shape_4d); } return output_shape; } @@ -85,7 +85,7 @@ ttnn::SimpleShape squeeze_output_shape(ttnn::SimpleShape output_shape) { ttnn::Tensor ExecuteTilizeWithValPadding::invoke( uint8_t queue_id, const ttnn::Tensor& input_tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_tensor_shape, const PadValue pad_value, const std::optional& memory_config, std::optional output_dtype, @@ -93,7 +93,7 @@ ttnn::Tensor ExecuteTilizeWithValPadding::invoke( auto base_tilize = [=](const ttnn::Tensor& input_tensor) { return operation::run( TilizeWithValPadding{ - squeeze_output_shape(output_padded_shape), + squeeze_output_shape(output_tensor_shape), pad_value, memory_config.value_or(input_tensor.memory_config()), output_dtype.value_or(input_tensor.get_dtype()), @@ -109,13 +109,13 @@ ttnn::Tensor ExecuteTilizeWithValPadding::invoke( ttnn::Tensor ExecuteTilizeWithValPadding::invoke( const ttnn::Tensor& input_tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_tensor_shape, const PadValue pad_value, const std::optional& memory_config, std::optional output_dtype, bool use_multicore) { return invoke( - DefaultQueueId, input_tensor, output_padded_shape, pad_value, memory_config, output_dtype, use_multicore); + DefaultQueueId, input_tensor, output_tensor_shape, pad_value, memory_config, output_dtype, use_multicore); } ttnn::Tensor ExecuteTilizeWithZeroPadding::invoke( @@ -125,7 +125,7 @@ ttnn::Tensor ExecuteTilizeWithZeroPadding::invoke( std::optional output_dtype, bool use_multicore) { using namespace tt::constants; - auto shape = input_tensor.get_padded_shape(); + auto shape = input_tensor.get_legacy_shape(); shape[2] = tt::round_up(shape[2], tt::constants::TILE_HEIGHT); shape[3] = tt::round_up(shape[3], tt::constants::TILE_WIDTH); diff --git a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp index dec6a34333ce..92f8374e58fa 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding.hpp @@ -18,7 +18,7 @@ struct ExecuteTilizeWithValPadding { static ttnn::Tensor invoke( uint8_t queue_id, const ttnn::Tensor& input_tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_tensor_shape, const PadValue pad_value, const std::optional& memory_config = std::nullopt, std::optional output_dtype = std::nullopt, @@ -26,7 +26,7 @@ struct ExecuteTilizeWithValPadding { static ttnn::Tensor invoke( const ttnn::Tensor& input_tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_tensor_shape, const PadValue pad_value, const std::optional& memory_config = std::nullopt, std::optional output_dtype = std::nullopt, diff --git a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding_pybind.hpp b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding_pybind.hpp index 3915564394e1..0fc3cc271451 100644 --- a/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding_pybind.hpp +++ b/ttnn/cpp/ttnn/operations/data_movement/tilize_with_val_padding/tilize_with_val_padding_pybind.hpp @@ -53,13 +53,7 @@ void bind_tilize_with_val_padding(py::module& module) { bool use_multicore, uint8_t queue_id) { return self( - queue_id, - input_tensor, - Shape(output_tensor_shape).padded_shape(), - value, - memory_config, - output_dtype, - use_multicore); + queue_id, input_tensor, output_tensor_shape, value, memory_config, output_dtype, use_multicore); }, py::arg("input_tensor"), py::arg("output_tensor_shape"), diff --git a/ttnn/cpp/ttnn/operations/experimental/auto_format/auto_format.cpp b/ttnn/cpp/ttnn/operations/experimental/auto_format/auto_format.cpp index 165376b82d8d..5e66f4033472 100644 --- a/ttnn/cpp/ttnn/operations/experimental/auto_format/auto_format.cpp +++ b/ttnn/cpp/ttnn/operations/experimental/auto_format/auto_format.cpp @@ -113,8 +113,7 @@ Tensor AutoFormat::format_input_tensor( } else { pad_value_variant = (uint32_t)pad_value; } - return ttnn::tilize_with_val_padding( - formatted_input, Shape(padded_shape).padded_shape(), pad_value_variant, mem_config); + return ttnn::tilize_with_val_padding(formatted_input, padded_shape, pad_value_variant, mem_config); } else if (formatted_input.get_layout() == Layout::TILE && target_layout == Layout::ROW_MAJOR) { formatted_input = ttnn::untilize(formatted_input, mem_config); return ttnn::pad( diff --git a/ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp b/ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp index bae91413f9ae..2b3cbb83cba5 100644 --- a/ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp +++ b/ttnn/cpp/ttnn/operations/moreh/moreh_helper_functions.cpp @@ -351,12 +351,11 @@ void validate_input_with_dim(const Tensor& input, const int64_t& dim) { void validate_output_with_keepdim(const Tensor& input, const Tensor& output, const int64_t& dim, const bool& keepdim) { auto input_shape = input.get_padded_shape(); auto input_shape_wo_padding = input.get_logical_shape(); - const auto input_rank = input_shape_wo_padding.rank(); - auto padded_dim = dim + input_shape.rank() - input_shape_wo_padding.rank(); + const auto input_rank = input_shape.rank(); const auto output_shape = output.get_padded_shape(); const auto output_shape_wo_padding = output.get_logical_shape(); - const auto output_rank = output_shape_wo_padding.rank(); + const auto output_rank = output_shape.rank(); const bool is_tile_dim = (dim == input_rank - 1 || dim == input_rank - 2); @@ -366,7 +365,7 @@ void validate_output_with_keepdim(const Tensor& input, const Tensor& output, con if (keepdim) { bool ranks_are_equal = (input_rank == output_rank); - input_shape[padded_dim] = (is_tile_dim) ? (TILE_HEIGHT) : (1); + input_shape[dim] = (is_tile_dim) ? (TILE_HEIGHT) : (1); input_shape_wo_padding[dim] = 1; if (!ranks_are_equal) { @@ -388,36 +387,31 @@ void validate_output_with_keepdim(const Tensor& input, const Tensor& output, con expand_to_max_dim(input_dim_wo_padding, input_shape_wo_padding); expand_to_max_dim(output_dim_wo_padding, output_shape_wo_padding); - for (int i = 0; i < input_shape.rank(); ++i) { + for (int i = 0; i < input_rank; ++i) { TT_FATAL(input_dim[i] == output_dim[i], "Error"); - } - for (int i = 0; i < input_shape_wo_padding.rank(); ++i) { TT_FATAL(input_dim_wo_padding[i] == output_dim_wo_padding[i], "Error"); } } else { ttnn::SmallVector expected_output_shape; - for (int i = 0; i < output_shape.rank(); ++i) { - if (i == padded_dim && !is_tile_dim) { - expected_output_shape.push_back(1); - } - expected_output_shape.push_back(output_shape[i]); - } ttnn::SmallVector expected_output_shape_wo_padding; - for (int i = 0; i < output_shape_wo_padding.rank(); ++i) { + for (int i = 0; i < output_shape.rank(); ++i) { if (i == dim && !is_tile_dim) { + expected_output_shape.push_back(1); expected_output_shape_wo_padding.push_back(1); } + expected_output_shape.push_back(output_shape[i]); expected_output_shape_wo_padding.push_back(output_shape_wo_padding[i]); } + log_debug(LogOp, "{}:{} expected_output_shape {}", __func__, __LINE__, expected_output_shape); log_debug( LogOp, "{}:{} expected_output_shape_wo_padding {}", __func__, __LINE__, expected_output_shape_wo_padding); - - for (int i = 0; i < expected_output_shape.size(); ++i) { - TT_FATAL(i == padded_dim || input_shape[i] == expected_output_shape[i], "Error"); - } - for (int i = 0; i < expected_output_shape_wo_padding.size(); ++i) { - TT_FATAL(i == dim || input_shape_wo_padding[i] == expected_output_shape_wo_padding[i], "Error"); + for (int i = 0; i < input_rank; ++i) { + if (i == dim) { + continue; + } + TT_FATAL(input_shape[i] == expected_output_shape[i], "Error"); + TT_FATAL(input_shape_wo_padding[i] == expected_output_shape_wo_padding[i], "Error"); } } } diff --git a/ttnn/cpp/ttnn/tensor/layout/tensor_layout.cpp b/ttnn/cpp/ttnn/tensor/layout/tensor_layout.cpp index a60e916070a9..339c919571ae 100644 --- a/ttnn/cpp/ttnn/tensor/layout/tensor_layout.cpp +++ b/ttnn/cpp/ttnn/tensor/layout/tensor_layout.cpp @@ -4,8 +4,6 @@ #include "tensor_layout.hpp" -#include "ttnn/tensor/tensor_utils.hpp" - namespace tt::tt_metal { namespace { @@ -20,27 +18,25 @@ size_t round_up(size_t value, size_t multiple) { }; Alignment legacyShapeToAlignment( - const ttnn::SimpleShape& logical_shape, - const ttnn::SimpleShape& padded_shape, - const PageConfig& page_config, - const MemoryConfig& memory_config) { - if (logical_shape == padded_shape) { + const ttnn::Shape& shape, const PageConfig& page_config, const MemoryConfig& memory_config) { + const auto& logical_shape = shape.logical_shape(); + const auto& legacy_padded_shape = shape.padded_shape(); + if (logical_shape == legacy_padded_shape) { return Alignment{}; } - const auto rank = padded_shape.rank(); + const auto rank = legacy_padded_shape.rank(); bool alignment_can_be_2D = true; for (int i = rank - 3; i >= 0; i--) { - alignment_can_be_2D &= logical_shape[i] == padded_shape[i]; + alignment_can_be_2D &= logical_shape[i] == legacy_padded_shape[i]; } // SHARDED if (memory_config.shard_spec.has_value()) { TT_FATAL( alignment_can_be_2D, - "Tensor with shape {} ({}) cannot be sharded because alignment will have rank greater than 2!", - logical_shape, - padded_shape); + "Tensor with shape {} cannot be sharded because alignment will have rank greater than 2!", + shape); if (page_config.get_layout() == Layout::ROW_MAJOR) { const auto& shard_spec = memory_config.shard_spec.value(); if (shard_spec.physical_shard_shape.has_value()) { @@ -56,10 +52,10 @@ Alignment legacyShapeToAlignment( ttnn::SmallVector values(std::min((int)rank, 2)); const auto alignment_size = values.size(); if (alignment_size >= 1) { - values[alignment_size - 1] = padded_shape[-1]; + values[alignment_size - 1] = legacy_padded_shape[-1]; } if (alignment_size == 2) { - values[alignment_size - 2] = padded_shape[-2]; + values[alignment_size - 2] = legacy_padded_shape[-2]; } Alignment result(std::move(values)); return result; @@ -68,11 +64,11 @@ Alignment legacyShapeToAlignment( // INTERLEAVED with (deprecated) non-height/width padding // NOTE: Rank > 2 is guaranteed in this case ttnn::SmallVector values(rank); - values[rank - 1] = padded_shape[-1]; - values[rank - 2] = padded_shape[-2]; + values[rank - 1] = legacy_padded_shape[-1]; + values[rank - 2] = legacy_padded_shape[-2]; for (int i = rank - 3; i >= 0; i--) { - values[i] = padded_shape[i] * values[i + 1]; + values[i] = legacy_padded_shape[i] * values[i + 1]; } for (auto& value : values) { @@ -105,40 +101,15 @@ TensorLayout TensorLayout::fromLegacyPaddedShape( dtype, page_config, memory_config, - CMAKE_UNIQUE_NAMESPACE::legacyShapeToAlignment( - legacy_shape.logical_shape(), legacy_shape.padded_shape(), page_config, memory_config)); -} - -TensorLayout TensorLayout::fromPaddedShape( - DataType dtype, - const PageConfig& page_config, - const MemoryConfig& memory_config, - const ttnn::SimpleShape& logical_shape, - const ttnn::SimpleShape& padded_shape) { - return TensorLayout( - dtype, - page_config, - memory_config, - CMAKE_UNIQUE_NAMESPACE::legacyShapeToAlignment(logical_shape, padded_shape, page_config, memory_config)); + CMAKE_UNIQUE_NAMESPACE::legacyShapeToAlignment(legacy_shape, page_config, memory_config)); } void TensorLayout::initialize_alignment() { - auto default_alignment = page_config_.create_default_alignment(dtype_, memory_config_); - if (alignment_.empty()) { - alignment_ = default_alignment; + if (!alignment_.empty()) { return; } - ttnn::SmallVector result(std::max(alignment_.size(), default_alignment.size()), 1); - for (size_t i = 0; i < alignment_.size(); i++) { - result[i + result.size() - alignment_.size()] = alignment_[i]; - } - for (size_t i = 0; i < default_alignment.size(); i++) { - size_t result_idx = i + result.size() - default_alignment.size(); - result[result_idx] = CMAKE_UNIQUE_NAMESPACE::round_up(result[result_idx], default_alignment[i]); - } - - alignment_ = Alignment(std::move(result)); + alignment_ = page_config_.create_default_alignment(dtype_, memory_config_); } void TensorLayout::validate_alignment() const { @@ -339,31 +310,39 @@ Size TensorLayout::compute_page_shape(const Size& physical_size) const { } Strides TensorLayout::compute_strides(const ttnn::SimpleShape& shape) const { - auto padded_shape = compute_padded_shape(shape); - return tt::tt_metal::compute_strides(padded_shape); + const int rank = static_cast(shape.rank()); + const int alignment_rank = static_cast(alignment_.size()); + + Strides strides(rank, 1); + for (int i = rank - 2; i >= 0; i--) { + strides[i] = strides[i + 1] * shape[i + 1]; + + const int alignment_index = i - (rank - alignment_rank) + 1; + if (alignment_index >= 0) { + strides[i] = CMAKE_UNIQUE_NAMESPACE::round_up(strides[i], alignment_[alignment_index]); + } + } + + return strides; } ttnn::SimpleShape TensorLayout::compute_padded_shape(const ttnn::SimpleShape& shape) const { - ttnn::SmallVector padded_shape(std::max(shape.rank(), alignment_.size())); + ttnn::SmallVector padded_shape(shape.rank()); int rank_index = static_cast(shape.rank()) - 1; int alignment_index = static_cast(alignment_.size()) - 1; - int padded_shape_index = static_cast(padded_shape.size() - 1); size_t accum_alignment = 1; - for (; alignment_index >= 0; rank_index--, alignment_index--, padded_shape_index--) { - uint32_t shape_value = rank_index >= 0 ? shape[rank_index] : 1; - uint32_t alignment_value = alignment_[alignment_index]; - uint32_t& padded_shape_value = padded_shape[padded_shape_index]; - + for (; rank_index >= 0 && alignment_index >= 0; rank_index--, alignment_index--) { // The last 2 dimensions of a shape are special if (rank_index >= static_cast(shape.rank()) - 2) { - padded_shape_value = CMAKE_UNIQUE_NAMESPACE::round_up(shape_value, alignment_value); + padded_shape[rank_index] = CMAKE_UNIQUE_NAMESPACE::round_up(shape[rank_index], alignment_[alignment_index]); } else { - if (accum_alignment % alignment_value == 0) { + if (accum_alignment % alignment_[alignment_index] == 0) { // Alignment for this dimension is redundant, ignoring - padded_shape_value = shape_value; - } else if (alignment_value % accum_alignment == 0) { - padded_shape_value = CMAKE_UNIQUE_NAMESPACE::round_up(shape_value, alignment_value / accum_alignment); + padded_shape[rank_index] = shape[rank_index]; + } else if (alignment_[alignment_index] % accum_alignment == 0) { + padded_shape[rank_index] = + CMAKE_UNIQUE_NAMESPACE::round_up(shape[rank_index], alignment_[alignment_index] / accum_alignment); } else { TT_THROW( "Padded shape can't be deducted from TensorLayout parameters {} and Shape {}", alignment_, shape); @@ -372,11 +351,11 @@ ttnn::SimpleShape TensorLayout::compute_padded_shape(const ttnn::SimpleShape& sh // Alignment doesn't accumulate on the last dimension of a shape if (rank_index != static_cast(shape.rank()) - 1) { - accum_alignment *= padded_shape_value; + accum_alignment *= padded_shape[rank_index]; } } - for (; rank_index >= 0; rank_index--, padded_shape_index--) { - padded_shape[padded_shape_index] = shape[rank_index]; + for (; rank_index >= 0; rank_index--) { + padded_shape[rank_index] = shape[rank_index]; } return ttnn::SimpleShape(std::move(padded_shape)); } diff --git a/ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp b/ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp index 6625bb19ac61..2e9b24cb03a2 100644 --- a/ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp +++ b/ttnn/cpp/ttnn/tensor/layout/tensor_layout.hpp @@ -14,7 +14,7 @@ namespace tt::tt_metal { -using Strides = ttnn::SmallVector; +using Strides = std::vector; // TensorLayout describes how a tensor is laid out in memory // It takes datatype, layout (eg. TILE vs. RM), memory (eg. DRAM vs. L1), sharding (ie. how you want to cut your logical @@ -31,13 +31,6 @@ class TensorLayout { const PageConfig& page_config, const MemoryConfig& memory_config, const ttnn::Shape& legacy_shape); - [[deprecated("Use of Padded Shape is deprecated")]] - static TensorLayout fromPaddedShape( - DataType dtype, - const PageConfig& page_config, - const MemoryConfig& memory_config, - const ttnn::SimpleShape& logical_shape, - const ttnn::SimpleShape& padded_shape); Layout get_layout() const { return page_config_.get_layout(); } PageConfig get_page_config() const { return page_config_; } diff --git a/ttnn/cpp/ttnn/tensor/tensor.cpp b/ttnn/cpp/ttnn/tensor/tensor.cpp index 9ab439600b91..f72f33a5039a 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor.cpp @@ -136,14 +136,11 @@ void Tensor::TensorAttributes::update_main_thread_ref_count(Device* worker, uint } Tensor::Tensor( - Storage storage, - const ttnn::SimpleShape& logical_shape, - const ttnn::SimpleShape& padded_shape, - DataType dtype, - Layout layout, - const std::optional& tile) { + Storage storage, const ttnn::Shape& shape, DataType dtype, Layout layout, const std::optional& tile) { using namespace tt::constants; - if (tile.has_value() && (tile->get_tile_shape()[0] != TILE_WIDTH || tile->get_tile_shape()[1] != TILE_HEIGHT)) { + + if (tile.has_value() and // + (tile->get_tile_shape()[0] != TILE_WIDTH or tile->get_tile_shape()[1] != TILE_HEIGHT)) { tt::log_warning( "only matmul op and ccl all-gather currently supports the customized tile shape: {}", tile->get_tile_shape()); @@ -159,18 +156,10 @@ Tensor::Tensor( init( std::move(storage), TensorSpec( - logical_shape, - TensorLayout::fromLegacyPaddedShape( - dtype, - PageConfig(layout, tile), - memory_config, - ttnn::Shape(logical_shape.view(), padded_shape.view())))); + shape.logical_shape(), + TensorLayout::fromLegacyPaddedShape(dtype, PageConfig(layout, tile), memory_config, shape))); } -Tensor::Tensor( - Storage storage, const ttnn::Shape& shape, DataType dtype, Layout layout, const std::optional& tile) : - Tensor(std::move(storage), shape.logical_shape(), shape.padded_shape(), dtype, layout, tile) {} - Tensor::Tensor(Storage storage, TensorSpec tensor_spec) { init(std::move(storage), std::move(tensor_spec)); } void Tensor::init(Storage storage, TensorSpec tensor_spec) { @@ -718,8 +707,10 @@ const std::string Tensor::write_to_string() const { return tensor_impl::to_strin void Tensor::print() const { tensor_ops::tensor_print(*this); } Tensor Tensor::pad( - const ttnn::SimpleShape& output_padded_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) const { - return tensor_ops::tensor_pad(*this, output_padded_shape, input_tensor_start, pad_value); + const tt::tt_metal::LegacyShape& output_tensor_shape, + const ttnn::SimpleShape& input_tensor_start, + float pad_value) const { + return tensor_ops::tensor_pad(*this, output_tensor_shape, input_tensor_start, pad_value); } Tensor Tensor::unpad(const ttnn::SimpleShape& output_tensor_start, const ttnn::SimpleShape& output_tensor_end) const { diff --git a/ttnn/cpp/ttnn/tensor/tensor.hpp b/ttnn/cpp/ttnn/tensor/tensor.hpp index 726fa4bb1ce2..6827c4213206 100644 --- a/ttnn/cpp/ttnn/tensor/tensor.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor.hpp @@ -98,13 +98,6 @@ struct Tensor { DataType dtype, Layout layout, const std::optional& tile = std::nullopt); - Tensor( - Storage storage, - const ttnn::SimpleShape& logical_shape, - const ttnn::SimpleShape& padded_shape, - DataType dtype, - Layout layout, - const std::optional& tile = std::nullopt); Tensor(Storage storage, TensorSpec tensor_spec); // Constructors to initialize unpopulated tensor with workers and storage specified. Use this when creating tensor @@ -205,7 +198,7 @@ struct Tensor { Tensor to(Layout target_layout, distributed::MeshDevice* mesh_device) const; Tensor pad( - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) const; diff --git a/ttnn/cpp/ttnn/tensor/tensor_impl.cpp b/ttnn/cpp/ttnn/tensor/tensor_impl.cpp index 51c038657987..3f731c97c654 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_impl.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor_impl.cpp @@ -175,7 +175,7 @@ void validate_on_device_dtype_and_layout( Tensor pad_bfloat8_b( const Tensor& tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) { auto tile = tensor.get_tensor_spec().tile(); @@ -189,22 +189,19 @@ Tensor pad_bfloat8_b( auto float_tensor = Tensor( OwnedStorage{input_float_buffer}, tensor.get_legacy_shape(), DataType::FLOAT32, tensor.get_layout(), tile) - .pad(output_padded_shape, input_tensor_start, pad_value); + .pad(output_tensor_shape, input_tensor_start, pad_value); // Convert back to BFLOAT8_B auto output_float_data = owned_buffer::get_as(float_tensor).get(); auto output_packed_data = pack_fp32_vec_as_bfp8_tiles(output_float_data, /*row_major_input=*/false, /*is_exp_a=*/false, tile); auto output_uint32_buffer = owned_buffer::create(std::move(output_packed_data)); - TensorSpec output_spec( - tensor.logical_shape(), - TensorLayout::fromPaddedShape( - DataType::BFLOAT8_B, - tensor.get_tensor_spec().page_config(), - MemoryConfig{}, - tensor.logical_shape(), - output_padded_shape)); - return Tensor(std::move(OwnedStorage{std::move(output_uint32_buffer)}), output_spec); + return Tensor( + std::move(OwnedStorage{std::move(output_uint32_buffer)}), + float_tensor.get_legacy_shape(), + DataType::BFLOAT8_B, + tensor.get_layout(), + tile); } Tensor unpad_bfloat8_b( @@ -237,7 +234,7 @@ Tensor unpad_bfloat8_b( Tensor pad_bfloat4_b( const Tensor& tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) { auto tile = tensor.get_tensor_spec().tile(); @@ -251,22 +248,19 @@ Tensor pad_bfloat4_b( auto float_tensor = Tensor( OwnedStorage{input_float_buffer}, tensor.get_legacy_shape(), DataType::FLOAT32, tensor.get_layout(), tile) - .pad(output_padded_shape, input_tensor_start, pad_value); + .pad(output_tensor_shape, input_tensor_start, pad_value); // Convert back to BFLOAT4_B auto output_float_data = owned_buffer::get_as(float_tensor).get(); auto output_packed_data = pack_fp32_vec_as_bfp4_tiles(output_float_data, /*row_major_input=*/false, /*is_exp_a=*/false, tile); auto output_uint32_buffer = owned_buffer::create(std::move(output_packed_data)); - TensorSpec output_spec( - tensor.logical_shape(), - TensorLayout::fromPaddedShape( - DataType::BFLOAT4_B, - tensor.get_tensor_spec().page_config(), - MemoryConfig{}, - tensor.logical_shape(), - output_padded_shape)); - return Tensor(std::move(OwnedStorage{std::move(output_uint32_buffer)}), output_spec); + return Tensor( + std::move(OwnedStorage{std::move(output_uint32_buffer)}), + float_tensor.get_legacy_shape(), + DataType::BFLOAT4_B, + tensor.get_layout(), + tile); } Tensor unpad_bfloat4_b( @@ -881,15 +875,7 @@ Tensor to_layout(const Tensor& tensor, Layout target_layout) { raise_unsupported_storage(); } return Tensor( - storage, - TensorSpec( - tensor.get_logical_shape(), - TensorLayout::fromPaddedShape( - tensor.get_dtype(), - PageConfig(target_layout, tensor.get_tensor_spec().tile()), - MemoryConfig{}, - tensor.get_logical_shape(), - tensor.get_padded_shape()))); + storage, tensor.get_legacy_shape(), tensor.get_dtype(), target_layout, tensor.get_tensor_spec().tile()); }, output_storage); } @@ -932,62 +918,58 @@ Tensor to_layout(const Tensor& tensor, Layout target_layout) { template Tensor pad( const Tensor& tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) { if (ttnn::distributed::is_multi_device_tensor(tensor)) { return transform(tensor, [&](const Tensor& device_tensor) { - return pad(device_tensor, output_padded_shape, input_tensor_start, pad_value); + return pad(device_tensor, output_shape, input_tensor_start, pad_value); }); } - auto output_spec = TensorSpec( - tensor.get_logical_shape(), - TensorLayout::fromPaddedShape( - tensor.get_dtype(), - tensor.get_tensor_spec().page_config(), - MemoryConfig{}, - tensor.get_logical_shape(), - output_padded_shape)); - auto pad_value_ = static_cast(pad_value); - const auto input_padded_shape = tensor.get_padded_shape(); + const auto input_shape = tensor.get_legacy_shape(); const auto input_strides = tensor.strides(); - auto output_strides = output_spec.compute_strides(); - auto tensor_padded_shape = tensor.padded_shape(); + const auto input_data_type = tensor.get_dtype(); + + auto pad = [&input_shape, &input_strides, &input_data_type, &output_shape, &input_tensor_start, &pad_value_]( + const auto& input_buffer) { + auto compute_stride = [](const tt::tt_metal::LegacyShape& shape, uint32_t index) { + uint32_t stride = 1; + for (auto i = index + 1; i < shape.rank(); i++) { + stride *= shape[i]; + } + return stride; + }; - auto pad = [&](const auto& input_buffer) { ttnn::SmallVector> pad_size{}; - ttnn::SmallVector input_indices(tensor.padded_shape().rank(), 0); - - for (int index = 0; index < output_padded_shape.rank(); index++) { - uint32_t out_dim = output_padded_shape[index]; - - int tensor_idx = - index + static_cast(tensor_padded_shape.size()) - static_cast(output_padded_shape.size()); - uint32_t tensor_dim = tensor_idx >= 0 ? tensor_padded_shape[tensor_idx] : 1; - - int start_idx = - index + static_cast(input_tensor_start.size()) - static_cast(output_padded_shape.size()); - uint32_t start = start_idx >= 0 ? input_tensor_start[start_idx] : 0; + ttnn::SmallVector input_strides{}; + ttnn::SmallVector output_strides{}; + ttnn::SmallVector input_indices(input_shape.rank(), 0); + for (auto index = 0; index < output_shape.rank(); index++) { // Check if input tensor fits in output tensor given the input tensor start indices - TT_ASSERT(tensor_dim + start <= out_dim, "Input tensor is out of bounds"); + TT_ASSERT( + input_shape[index] + input_tensor_start[index] <= output_shape[index], "Input tensor is out of bounds"); // Figure out pad size on each dim - pad_size.push_back({start, out_dim - tensor_dim - start}); + pad_size.push_back( + {input_tensor_start[index], output_shape[index] - input_shape[index] - input_tensor_start[index]}); + + input_strides.push_back(compute_stride(input_shape, index)); + output_strides.push_back(compute_stride(output_shape, index)); } auto flat_output_index = 0; - auto output_buffer = owned_buffer::create(output_spec.padded_shape().volume()); + auto output_buffer = owned_buffer::create(compute_volume(output_shape)); std::function pad_to_tile = [&](std::size_t dim) -> void { for (auto i = 0; i < pad_size[dim][0] * output_strides[dim]; i++) { output_buffer[flat_output_index++] = pad_value_; } - for (auto i = 0; i < input_padded_shape[dim]; i++) { + for (auto i = 0; i < input_shape[dim]; i++) { input_indices[dim] = i; - if (dim == input_padded_shape.rank() - 1) { + if (dim == input_shape.rank() - 1) { auto flat_input_index = compute_flat_input_index(input_indices, input_strides); output_buffer[flat_output_index++] = input_buffer[flat_input_index]; } else { @@ -1024,56 +1006,61 @@ Tensor pad( } }, tensor.get_storage()); - return Tensor(OwnedStorage{output_buffer}, output_spec); + return Tensor( + OwnedStorage{output_buffer}, + output_shape, + tensor.get_dtype(), + tensor.get_layout(), + tensor.get_tensor_spec().tile()); } template Tensor pad( const Tensor& tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); template Tensor pad( const Tensor& tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); template Tensor pad( const Tensor& tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); template Tensor pad( const Tensor& tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); template Tensor pad( const Tensor& tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); template Tensor pad( const Tensor& tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); template <> Tensor pad( const Tensor& tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) { - return pad_bfloat8_b(tensor, output_padded_shape, input_tensor_start, pad_value); + return pad_bfloat8_b(tensor, output_shape, input_tensor_start, pad_value); } template <> Tensor pad( const Tensor& tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) { - return pad_bfloat4_b(tensor, output_padded_shape, input_tensor_start, pad_value); + return pad_bfloat4_b(tensor, output_shape, input_tensor_start, pad_value); } template diff --git a/ttnn/cpp/ttnn/tensor/tensor_impl.hpp b/ttnn/cpp/ttnn/tensor/tensor_impl.hpp index e0bb1149ef70..a6db8b143881 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_impl.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor_impl.hpp @@ -99,9 +99,6 @@ static ttnn::SmallVector to_4D_shape(const tt::tt_metal::LegacyShape& template typename BufferType> inline std::vector convert_layout_row_major_to_tile( const Size& shape, const Tile& tile, const BufferType& data_to_convert) { - if (shape.width() * shape.height() == 0) { - return std::vector(); - } TT_FATAL( (shape.height() % tile.get_tile_shape()[0] == 0 && shape.width() % tile.get_tile_shape()[1] == 0), "Unsupported shape for tensor conversion from row-major to tile layout. The tensor shape height and width must " @@ -214,7 +211,7 @@ Tensor to_layout_bfloat(const Tensor& tensor, Layout target_layout); template Tensor pad( const Tensor& tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); diff --git a/ttnn/cpp/ttnn/tensor/tensor_ops.cpp b/ttnn/cpp/ttnn/tensor/tensor_ops.cpp index b5ca7d6dbb76..c7234d8a1a7b 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_ops.cpp +++ b/ttnn/cpp/ttnn/tensor/tensor_ops.cpp @@ -254,12 +254,12 @@ void tensor_print(const Tensor& input_tensor) { Tensor tensor_pad( const Tensor& input_tensor, - const ttnn::SimpleShape& output_padded_shape, + const tt::tt_metal::LegacyShape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value) { ZoneScoped; GraphTracker::instance().track_function_start( - "Tensor::pad", input_tensor, output_padded_shape, input_tensor_start, pad_value); + "Tensor::pad", input_tensor, output_tensor_shape, input_tensor_start, pad_value); TT_ASSERT( input_tensor.storage_type() == StorageType::OWNED or input_tensor.storage_type() == StorageType::MULTI_DEVICE_HOST or @@ -273,7 +273,17 @@ Tensor tensor_pad( return input_tensor; } - auto output = tensor_impl::pad_wrapper(input_tensor, output_padded_shape, input_tensor_start, pad_value); + auto input_shape = input_tensor.get_legacy_shape(); + auto dimensions_pads = std::vector(); + for (auto index = 0; index < input_shape.rank(); index++) { + auto front = input_tensor_start[index]; + auto back = output_tensor_shape[index] - (input_tensor_start[index] + input_shape[index]); + dimensions_pads.push_back(Padding::PadDimension{.front = front, .back = back}); + } + const auto padding = Padding(dimensions_pads, Padding::PadValue::Any); + auto output_shape_with_padding = tt::tt_metal::LegacyShape(output_tensor_shape, padding); + + auto output = tensor_impl::pad_wrapper(input_tensor, output_shape_with_padding, input_tensor_start, pad_value); output = tt::tt_metal::set_tensor_id(output); GraphTracker::instance().track_function_end(output); return output; @@ -296,26 +306,30 @@ Tensor tensor_unpad( Tensor tensor_pad_to_tile(const Tensor& input_tensor, float pad_value) { ZoneScoped; GraphTracker::instance().track_function_start("Tensor::pad_to_tile", input_tensor, pad_value); - uint32_t height = input_tensor.get_logical_shape()[-2]; - uint32_t width = input_tensor.get_logical_shape()[-1]; + uint32_t height = input_tensor.get_legacy_shape()[-2]; + uint32_t width = input_tensor.get_legacy_shape()[-1]; uint32_t padded_height = round_up(height, constants::TILE_HEIGHT); uint32_t padded_width = round_up(width, constants::TILE_WIDTH); ttnn::SmallVector shape; + ttnn::SmallVector padded_shape; ttnn::SmallVector input_tensor_start; - for (auto index = 0; index < input_tensor.get_logical_shape().rank() - 2; index++) { - shape.push_back(input_tensor.get_logical_shape()[index]); + for (auto index = 0; index < input_tensor.get_legacy_shape().rank() - 2; index++) { + shape.push_back(input_tensor.get_legacy_shape().without_padding()[index]); + padded_shape.push_back(input_tensor.get_legacy_shape()[index]); input_tensor_start.push_back(0); } - shape.push_back(padded_height); - shape.push_back(padded_width); + shape.push_back(height); + shape.push_back(width); + padded_shape.push_back(padded_height); + padded_shape.push_back(padded_width); input_tensor_start.push_back(0); input_tensor_start.push_back(0); auto output = input_tensor.pad( - ttnn::SimpleShape(std::move(shape)), ttnn::SimpleShape{std::move(input_tensor_start)}, pad_value); + tt::tt_metal::LegacyShape(shape, padded_shape), ttnn::SimpleShape{std::move(input_tensor_start)}, pad_value); output = tt::tt_metal::set_tensor_id(output); GraphTracker::instance().track_function_end(output); return output; @@ -354,7 +368,19 @@ Tensor tensor_unpad_from_tile(const Tensor& input_tensor, const ttnn::SimpleShap Tensor tensor_reshape(const Tensor& input_tensor, const ttnn::Shape& new_shape) { ZoneScoped; GraphTracker::instance().track_function_start("Tensor::reshape", input_tensor, new_shape); + const auto& new_padded_shape = new_shape.padded_shape(); const auto tile = input_tensor.get_tensor_spec().tile(); + TT_ASSERT( + input_tensor.volume() == new_padded_shape.volume(), + "{} != {}", + input_tensor.volume(), + new_padded_shape.volume()); + if (input_tensor.get_layout() == Layout::TILE) { + TT_ASSERT( + new_padded_shape[-2] % tile.get_tile_shape()[0] == 0 && + new_padded_shape[-1] % tile.get_tile_shape()[1] == 0 && + "Expected a multiple of 32 for H, W (or -1 evaluating to such) in Tensor::reshape()!"); + } auto output = std::visit( [&input_tensor, &new_shape, &tile](auto&& storage) -> Tensor { using T = std::decay_t; diff --git a/ttnn/cpp/ttnn/tensor/tensor_ops.hpp b/ttnn/cpp/ttnn/tensor/tensor_ops.hpp index 392ae6e76654..b65af33cb42a 100644 --- a/ttnn/cpp/ttnn/tensor/tensor_ops.hpp +++ b/ttnn/cpp/ttnn/tensor/tensor_ops.hpp @@ -45,7 +45,7 @@ void tensor_print(const Tensor& input_tensor); Tensor tensor_pad( const Tensor& input_tensor, - const ttnn::SimpleShape& output_tensor_shape, + const tt::tt_metal::LegacyShape& output_tensor_shape, const ttnn::SimpleShape& input_tensor_start, float pad_value); diff --git a/ttnn/cpp/ttnn/tensor/types.hpp b/ttnn/cpp/ttnn/tensor/types.hpp index 284520834d04..33fd91e8b402 100644 --- a/ttnn/cpp/ttnn/tensor/types.hpp +++ b/ttnn/cpp/ttnn/tensor/types.hpp @@ -206,13 +206,14 @@ class LegacyShape { } } explicit LegacyShape(tt::stl::Span shape, tt::stl::Span shape_with_tile_padding) : - rank_(shape_with_tile_padding.size()), dimensions_{}, padding_{shape_with_tile_padding.size()} { - for (int index = 0; index < shape_with_tile_padding.size(); index++) { - int shape_index = index + static_cast(shape.size()) - static_cast(shape_with_tile_padding.size()); - int dimension = shape_index >= 0 ? shape[shape_index] : 1; - int padded_dimension = shape_with_tile_padding[index]; + rank_(shape.size()), dimensions_{}, padding_{shape.size()} { + TT_ASSERT( + shape.size() == shape_with_tile_padding.size(), + "Shape and shape_with_tile_padding must have the same size"); + for (auto index = 0; index < shape.size(); index++) { + auto padded_dimension = shape_with_tile_padding[index]; this->dimensions_[index] = padded_dimension; - this->padding_[index] = {.front = 0, .back = static_cast(padded_dimension - dimension)}; + this->padding_[index] = {.front = 0, .back = padded_dimension - shape[index]}; } } explicit LegacyShape(const ttnn::SmallVector& shape, const ttnn::SmallVector& shape_with_tile_padding) From 4512b9f490357b8b5647ae98ba89474ec6a6a0a9 Mon Sep 17 00:00:00 2001 From: umadevimcw Date: Fri, 20 Dec 2024 05:14:31 +0000 Subject: [PATCH 87/87] #15857: Skip abs forge for GS --- .../sweep_framework/sweeps/eltwise/unary/abs/abs_forge.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/sweep_framework/sweeps/eltwise/unary/abs/abs_forge.py b/tests/sweep_framework/sweeps/eltwise/unary/abs/abs_forge.py index 993a2b297824..14b14191ca37 100644 --- a/tests/sweep_framework/sweeps/eltwise/unary/abs/abs_forge.py +++ b/tests/sweep_framework/sweeps/eltwise/unary/abs/abs_forge.py @@ -37,6 +37,14 @@ } +def mesh_device_fixture(): + device = ttnn.open_device(device_id=0) + assert ttnn.device.is_wormhole_b0(device), "This op is available for Wormhole_B0 only" + yield (device, "Wormhole_B0") + ttnn.close_device(device) + del device + + # Invalidate vector is called during the generation phase where each vector will be passed in. # If invalidated, the vector will still be stored but will be skipped. # Returns False, None if the vector is valid, and True, str with a reason for invalidation if it is invalid.

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®zs~irV(3LTsYoa&PXo!iv6Bs32~m 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

qmn(NX|K2{*Y*^R0vx#Mkd1zpLyNC>vT0 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≫)T;D)(9Aya4V^Ws*ZaOR>4p!ZF6CICgsL0UEi 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�XM9U_bbC43RwehZ=mjy%T{>10K^a# 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μP;lOJtbw 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%0DLcsO#$|10Ss?1CrtX9zYtV4jG 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%0DKNlpoF5*0#hl$eGb!ZT7-lJc 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ξ=~yT7b+u1 zh|2*_u5wY*eR1vD4}p$0`ztt}@rb~$`>}`VzmSR02*FHvR3a?)hvO9SeIVU0;J!X( zXXp5vy8l0%ePvYCYum3Pt#k=e0@6~_jgrz0QqtYssgxjy2uMnU(hX7q(nEI)-CaX- z?%CVt-TOJ~Jzvg;z1P}{y~LUM&wXFlFXMwXw%fN{tZuUM{pJc?REn|+h8V z?nA(kI0U??7C;i5_EVinGvh+lK65xUT~bYh_Xkx?8+SmJ)c_UQ2Sx`f3^@p^MYVj4 z-x%t9)XU|olP#MsNB6mA%2r>m-a9J~SlG*{ysPOrIjs>HNieX@V*TQ;h&QiH5wemY z1M-eI2=1Ic^;_(UU(vmOY!Vs&ZPFT8Eb<&r(4dw4?349#kSb;PZ=b)6>JI5RP3J7ist_J={U zO_G$gW?!zlb^(5iP*JI(>^_&=DC*iSN@L-U8*=3@Oqz645d*Q7XbwAa@Ba_Crg%) zakptHTjd!CT6tl)X`TPbRGb@Y&hj?RRSMYa-Oxdu7-ln>B)x>y4n$3R%(91$ z#5ByPZWGcy&Vk7y#=h@D6+!}TR{gX;Cmv-Z;x8X+HRK|FH+u!}qGeN9dgi~$;eF+> zc3yxfs(uRIw~`X7=Q^eRsLDHPE&`jH&dS$;`K8srx>}&;XPzPFKg$=tW)&#`v5sI4 zsPlUy4&gn5*^F4j75uG8L>`}-fe~c1-qm`9>ccQ_uSH#+?Iug>0Gg&#gfkw*9NYkc z&ktC_GtMm>EP-;6lUSK4mM$g|ErnGeRa<=ROz;NTeJjpC^yfP zd#h3xPY-oZ{)v$32w|xCAqSq&#}CN_*23A!4siAZZHuA)cc}`(7bc2?(KD2hB9osB zUV8&CBwrB69v9G}fj~*AQKP6Mf1m+_ryS80MQvi2f+-UVyy(bU3})JPDiSfGNNgbx z^eon`^8a_%X#puw+dS>W1%yU4(o%^neL(~WYSsDgLY{g$rEZ--Cim2qVwU)(vXHS= zk)AC(>7m5UimInSp8_8=+k^Lllrdt69P-(LL+vC1x0)_AN_*= z!s5iAVuym&h3w~M@A{ZgQsM7QG|t*hb+>HYCj%fWXan34;X$o&?k5%Q$_+1GuUyN! z2aKEq6wWg8y{&L*p6RFX!fbE%J(vB#GTvtEcZ1#=@xC z1uyjlz5)@_g9hl${@`!ho0T$xV26gy%ny7C z-ixl=fwNy?sg?sJe$sWY9pBcyk8f?AcSthL z+=j>nI&^ISyKAuJd@QTz9)ytcdjDg;$X@o-HsOq^;|Zn$pXlcHksdxIDlAX~&#e{IO zfFG1w2vm?dS+rgfTq8qc_k+#hDjPxdeve~*{)=qJ@iB1LM9S$eznZ$|dgc|Mk1;ds zRI;;U!@r1V&jy2geBilcR?sG)Fp^~EA=2wvpHLS{vKg?7Le1|6BZW8JCyA^Exr#xN41mibK#>T0{yFCP0ItoVaK z(+La@@X8vHnjGXmmYZpdb&3?9$L61XZok z8YTL>KSn5gPhvmaPmkLG+(pmDxq1x6u^#b04aO4F1v*Lm->1YsDD7W|20jB2A<(O| zMM3_pdR?Ilgn!9GCmFI##z@z>E;uld*VaZ*xdgeqo<@Oa6OJZ+*DY*+&O4pDeti@{ zy?gA*ufF+(%7%jwkl0WxqyW(-Jo-^845tHj%J{JqEhi=C-g3K>8X)6S6zH;cf<>Ryfk0YdK`sX1G}H=O!p zzK!6_-!Y*L%)SZeAUn|(3|KRNz=?x_GqX!gnYokp*>52I$`ar(8~xqALfzKn{xtom zK6nI7U#`|O9t@>0ONYHQ9~j*&Q71z!4VOgwoth8%V%ZXm0y-Q7yz^U(7!4I*PXd`o z8KTf;Vu99QvdVhmjNB-$^NnLzT(GBW?UL!6n=x+10)QqK-L|;J08B_lREg8mir{rD zG6B?}fy={l8@M8$k8f%57q`u365c(< zAqFNcGAijGOmg=KX)}6=wi>Z#=rbt)#WZ>#ssN+wv_ZjHDfg{yy-;Zbtatuvg+GIK zRC#Z89rIw#XY<*fI@iHfs9zDuJ@JF&t#-XK30 zeSTRrLx(g^`qh~M%0|CTZ~nDmPQj|Be_j@2e1*}AjqWMVwIWtQPuj-e;IG_*8IQ-V@Zz0cBZ82X7|JR_-cSq%mIrEV{HVPVfh)r|B-8Q zns;X9t?#bzihv+X>$C6Qn{o6xv5>;D?f2c#@BZeInq+^O>5*EKDb|?a z_s(qVKn=Qj&DT}Zw<6#>IM`iX%o)b{X#eg(S$dqe>yC2xxq+WPwN!L=C%HHCP>9oN z&k&G^{WS09#Lxxszbnw~IKu$Aqkv9HE_Da2-=-uW#V?bd@O2`vbdpNY#UBF0lPU-; z6Kd_469>y{Hqc+iCaTiv`P_bd=WePT-sF2V3`_&DCGzT1vzJ&RUjQ2N#JT~!QTi2P zvw~l)fa})E;@l7I7hoR+?(8Lc{_>OeDc%w89RQ+#sj2HIF|2i%7$JE|$mtX#9! zu%9z~dRah3MCb{MUDM&D>3|2YA%(zWg@}a|z0Y>Oy@2ory8)M;^nj57qw<3+1Yg{; zC^uk;3CGWp`j`tC5{c&;$>~T=7ecL6_(vU%^uME?touCd-M0p^H+YOXqd!qu*AU#( z>0@og?{l#NS=@1N!r)>2UlpvT8=r2CjX&sts9m?FU{UdDB1+-et_qv_RXd3|z7;x*xH?GW z!Z!hbVkwo9g#?L7DEH!E<1!l&cFZTNMFXeqXYH*A;98fpov!>=p(qm6Ls8BWTJj`J z^_cwe(OZK{*!K}ZW5>H2m;h$FOv3B)Ks+QQj2Ac<(~Y0&^~MHp#&AkNUL2l)*CMD4 zqxXPY6Apeqdj9vz?D9uoz_Zli;bT27*$XgJaYs(mdOn8Z7A<|8_>Kg!aqgpX@WnIETeY7gJ_y!ww1RiFz1Q!GG8S|; zQ5arS5&ktg6xz1r9>#BSQ5c+3z~BA@IMIh`NnP&Az@l2}<@XYMGanU}seOo>#H_AM z0bY*L!<6NX{Bnc!_>3gr0BRH$vh$f##iKCMZ*VK}qMeHHNzB!pjQzp)rjh;=-ag4% zVfD%!lX4?h{5C(UZuik;f9rzyQ_VPY1?Dz_DeT~g9#bb-`5sqq8i(Au95$4naZxZ%KRyb^CB!{wza=cd9uzJA`# zEt_iFp#3^~r#r-VcDmuV{3Kjg?4@~PCna{gXbKeu^X=qapdhqBGLQ6?9{)rh=KA=z zHl{C!wp&3GPc{pM?VsgnpXeJDYUBjD@_^)(=HJlq?*yk;nmn1hPmv5-a)n2~+CRK5 z`VMf2apU}6HWwv2bNy#i%bB1AOC)q#qY&H}=lU^{5x$?5bsg zilE`XIQv^iIt=2o`~7Ln`bnG1?pI2u#+W>;O47g zR#m75$*;4TI`B`WmmzA%g=9W~hO{jEnw0gr{ z(4_1f&1*j(ev))iJX-%m=5x|holvWOq6v%5Z!R`pRk{yRDiZAU zb_)AmIF@z&0&u`effo?oLZ%GjWC?#R3jW+L(kS{W!D`$S z;uUJLI#v4r4T=N!+))zHh8=m{Q~I|!OcYcDe3yP8K#ho`i4)wBh|U?-_Ki`5=iZDW z^ryu3xP4xfPS5a~Es7;j0aqG#VqO9Iqt_QSE9C8|#YqjkS z7JMn;3-!xc(BI({a$1oFIX)ID^GPqvRY2d`l3vA42#M4FVW2O-6X(qz3o&^iQkBF? zU=e#g`jc?;nIjgt`dLAcNW=Z6Rp@qgYoMvl2L}&}yXb*V!V(UnB(}HwrBwA%l6mtSbv$)w^o_>=ak{5B-i3Yc(0c;U`Wa z*@roDFTKab<$j#DVkWMm?9*X-QIU`^H=4B zw@PqsD*b1+FohGPA5UM$2wqH~8e@Gykt1m7!RXn`#TWwH&j)EQ&L881bGl3Ga|hb% z51JUCu5mwC%qGkm&xm_cnaPjA>VoM|uc@K&g}NyVz1SIKd=G)d`Zx+t)z)_zb)B$| zQ(<>I;nSs;B!^2ul|P0GPvxEsJ6IfxsnvG`pEX;!Sf^Z*enlVa3VgN7B}T2VGUFhR ztx&WP$Wj|zh@~7PBYnK{{uVDT$gcl-;8ZQo&D1#%`EZUB-Shz3kx^hc{$|dZ#86K? zS_fU+ZP>B&#RuM?n+HTGuZZM2Si7_m?=jcgcNPeLCYQU%@DCQ8@fHWn+z*VErle*A zCZ6t4k(V1H?#aEUrJvOQ?DqZ4zRs;`fbe+`Xz@6wl(#`#!ngETIMhCwK7ZCz&1LJs zTxRj=rhaTAWcgz@H72VpL2p8CzmD55%JIZVX)MLF)rNy>uaiD%qIX+NfCHUe;hM*> z+!*7ai-mgSzIk{P6lB%JE_g60(0;!=Qy&g;>p2dI90eV2BBm}s(q-hXm&4<4f;>yJ z3qj|2On`VqB!h7AYav7P{a>NP` zHt21v(MZIRyq*NDx)Hi=!cH50rT;?`K6UKF z+2-!dZ@OyWXm6bHKx+OE?gkWBfe_y1M~ZkrLM8r?kU0YY-f~lwfIJXr%~|hDTCNi@ z>B5F5$HaR!-JI#r9dM##o3V$&3+1dv^9^TRh4apKDh6nW(_P24IRUR_d#ju!IRx^9 zc2nfnGA7Eg-zCP5_<$sk6F3P9RK)S7V1kPGU?o=%Fxs1~Q-Ff#=yg;+Cjrn#lawF# zVA&ryzFm44^0Xqwt^qfoAbhy=V$%L+EM188Bf;MxjLFGW6yL?XL4QID*Bp%ss$I3^ z1JL#AIEe#Qnl6&U1vI>ngP8D^;pVG7fu$BGBZ@ z9hW7gbj<;*E@N5O{1XBeDDKh}7rF)5y-2cfqGtaD;GW*grP-1VWlf*u12Vc9hb*Ii zmX<%m9U!#>IpKZgUq!P>{_CeEGM;W6Mi@aylV(%0hw7c&^l**{p%p|^^8;+|h z>CVe(<6S3T_&{0M8~?lL))!SqAT$|*&_2|9jBKxHRh=#xY`5cj)|HRa;DR!V?>kd< zM^k?s$df{O3@`SpvKR{LNnoWAWURZ{b)GQ!dfUpcc-d`#OTopr+_S*p!m#2dIb{&( z8bUUck=8YkLV7aSZ-4v6?qQ|{&mtxV*?)( z5i*=P;mCcdsVC>VZSB5+Q9@EF62rIhKTk*F9$2(HFXQ|qD)Q8nSb8W5q}pQtyJ$}n97Y5;i8PT zQX~DK#(!+OXtdypNk&hb>&~rE>szHO(F^75r_E`bEtDh3F3Z@KF;Jq5j<$>jX@tLS zS*0%cEZ2p)0$rDZQkwcdpD4!bu%juTNR^8B#j)v>U59qAhLlNJg5Cb=`v9TBS=jp{ z=tB^l2#XEY8Aum_zThXVy#m(mY+zsVylnzHa;c0(;ITZtPVKk{Ppi)fYXyiJsX+rk zsc4Trkn(+Esa-%=CT>N*Vg;sZsv8iyAPdN@a*n%fnYeuayW4PYkh+!)#@lB(u1I)a zn3W&eMi9esv`%ON_$_d|%3wHMnlwOH6qc4Jo?_DY0mr!aC!fO(B1oMetR5IbyKNOg z_iD<$d7x7vk7rP&jfrPAX(i@&+t~>-+<7@QH3iXK z$T(1DIlEbYk%wV@8?!91SKRUJ``Y=-LxN6k3HLPrUFFdtZxKbJh)n4=(Alygp4@d@@+;fQr>hKhJTjP9|+QUb| zW^IcDc4EhQyiQW*53$l16NihT6#P_zg~xs14iFYP?t7tDsKzj2#x&w6#C*?jR9Q-! zovq+A6z$p$yF_ikve^wpW|@q}{Hx#mpXc`;;to7_Et*K>zhFO63j_vuL~2)P_zIz< zP;C}C0iWfk&HSzn-Y&qDLA&o9ZGRmW8eJ79w=pCo2@X?URDAl__qm-Y0yquZOeLy- zh(O~!FPcH8Gj{uOvo3N+jAB%H0bAbK)wIujz3M-}Fkl-=PrJzP5$gD9jl)&!epo)B z7H~gScyw&ikUgZq2hZMHcsA1S{21&%Br`6e+7Asjktp6nR!j9N4C|%h` z&1sMXld91?3AUg#rF$qaazV2%-`*x!ciR2ZsoK~OZ0112Wzw|%mdF7d|=p4J^x_ka-Am2 zC!MQRTu`LUqoMrnZO~frOv&u&7Q8tEE*Q1MppaaBkVx9D;NHR+Xa!TbTuiPTz1U)x z2|N<^fLRvm-DNsB>yO>dnllUsn$FcUmTp(i2*is%xRZ<^QFDW-^xU33@A=Bt*H%^< z!RbKfdSCs#l&xgu$n;5o1gx=bBNf|@o0{lfvbHw}jv(y(L18|kmF;e9J#@D)7OjCt z>0+tw%5~1K)x``k>^>D+F@tn=WMshW^Tb|quAwK{gV{~28a|2%dIIA#1@NE>m>}YqU4|m2rskI z|F7qblIDJ@9^-}{J_x4RRi5(vb*UeAb9}u2;_z;$40nbpJ=W=Xa{A}&{#LyY;CT<} zxM7v_kYgz@;Q5QV{SD6Z^hBAYOO} z^uXHUeiH~RxXLuGxGK^47%xP+9xRBx0U!)sw*T2lf(0Z9tWZ5@71G>aW&AvT($$r_(Uo&8TiVzW6`H`RT5)9(<*s~d4oT_%Gi&=AvD2@ah+v)CTqow3&*2w zx)IRlnzqcwD1dx4&0IWE4H|#;IJvWp@y^=EorseN`30Z3%-iL|%2^u~2m5^OK0!{mCqv*p0NNms2ys1=cYJRck8$ptCD1@~8z$)J$gIW}_8A9R zr0du;4_ObssS3yN8~^^0?aM$k-0aR&U!_<>%HCF*KtI000jnTG9LvJBaG0TkRv6lm zaL&_~rYq5c&6_6b$I!G9)4}5L4}0iL%I_w=$hAIF#cggItgn_mS3b4T>FnzcX%NWj zT4j=d;WQ_TYP#2|HOb-ZIeZ&7+a=Lp-W<@lzikVQU$o8l92sLD#kprey>)D|D8q`X zJTLoc)$gH|_07Vr;BjD}PUJOXxNQP_AUN~~kh3hH3d!8o9;EtA9zz7|zV$=zR=Q^v zR+cTd8jjn@-gBZU7#?G4G)B%fA8OSFF<#e{4n#risZ$3%2OFLz1T!8aj-~Ku*}8-t zEI-ON9DGCSx)ZUby4I^UPo^ii>(UXaDXVd!X-$?eX61u zKeNh@eJmqV_uomX<2)LZNE=rm9|``-$vf`#NWFU41=;t*tI?MF5n*DJnf%?05BWD} zg4THE=E{VBt_x|zC0xrvKP-$R`wB7~y_mgxKRJGp3VN|ti6>t+Sm{ss3g3RF_$5bR zc@Cc5l!f8)a|GDCcPOBRRso1VRp!n8)8Rr|u-|^$7 zH6zHe*sEurak9Cw#RwrG-Keu(G(f=|{@ARMN8u+ti-UOs4jHl`B|@xK^N)L>#?^lTl}Sivb(_RFJ1(NiTz(>w>HGed#%2O59&lm@vHVmh&xrE4IcURzw@Qrk z#4`!`n=9Yfr;}(UV!vNfW|T%QSi^H@Zm$nstM7z+wFxPcfEY(tWj7w&1k@s<%dG`v!w+ z6n;5C`zRmTG%4OYVUq`5C{nyQC{2)z#9UO6p;nvB(VUe#oxi?qR4o_=hZrSyGXPkgiby8Et#$Ly?oH-V?~C&u<)i+!VoEV=HwnRZFDM=D>;0KxnP|IUKA82$<*SHB*n4cpqC6C< z0dBwiPC;I{a|r5ktreUb_Bi%p)X6nD7jkSdf^-KeUNw zjXmIg_#exB(VQf|=o8Jx!h3T|VqU4XAn=Aw(eL)PMn7Kf%I~^r`x(j$u^?J7a6t6D zH{v>=LkUpzToCT{zUlVT8fYoA2+=4|g~1+As-rSisDA2315?l)`1$f=3da(AN4h{a zOI;MSnT4Q5FhEesl=m*_qU-u$Pk=1&ilsEwyPi1vlDpmVo#$|ayS*1+o}$QQ(}LlU z#S8n2^77A~xlL{uTvk4Xja|_O*cu74j}0F+!^G6sok#d`aFtm{CL8u@Ip3Y-QXgp1 zhI6tZ=y~&EKPcf_GG!XhV4UIhNE!+b691PF1`$;p2gG4-$uxJXRbvg=nN`4+$`0^) zoI4pmC zz06&Ka=6Cuq!%KjFkC2=kjDuA@lQGDv2yj2x6-}SVpQLX>7oQ-qy^>ag(TmnOMctB zSKzp!T`Me`TZdaEtHqO5$;~wc+}T|z#G)vjLX32d?MKMOoWeN8{REXor4by%IS*<+ zFnN7+exnRxMO<$A=#=xST?@|8{2$s(Vd=OjuYd34-LCcUWjmD2x^_a^ERDZ(Wh~4W zqh#wv{C$h|)hX*t%Fn&yX1=^VaOLlJLgItyYj7q&C))PNu6I8PsjaTyl!6>|85rh#t`24*qi4!m3o(A+e3 zj-vJ#KN7tw6w`&&V&d|hme?Ro({^z~$_v*ekilUO2bJ;s;S)7* zT`?Tq!Sq$LYEQ6Yw=lFxp51xm14jP1P&V%hQP?*;4{yo-ZWG z|MwzPd^7m@)t437^iQq>2=TxV^ue)wi=Z5A*$Z*f&3#h&T<(GvB_W9PWF?mC5#!@i zY*9&hdQLE@Q3fJB8*XX6Rf9N)VY+ZHZ@G7IP8?nW+k0RDOhwG#88Me z=!z@*9d{Ck&95Eg8D_BMMLN|9-(Wnt#apgKe>VJf#J;ZRC(leppY^)xDP3QyesjyX zX>$9k=ahpmuxu#8?R`GH6%Y}t!P<4!%-2$1j;m+Z_>_h0Z7Nm+CYK&3mTMf2RnhG@ zw>MT&s4G-MB4&!A!Vs*vaOxbT@h~1#C8*?PTEm+8?02=xGu`OT6VIp~ z5zV_4w$L_S?hvs_iazBx75#ZHLm*b1B=ZNfPnN(Wy>o~xm~F>a94+W~-+q(T4Ple9 zd=keqhI8M^Eo9*5_HwcQnlNX!t5KzTsymI!bzTHjeqb|6-dBC2p_z-u7uZRNarEd< zcFxbXABLSh{Rx&-82pP!b|p47)i{=@PkuaOG+6oP3JU!7n$i>`so8tX>CcVw448eS z7|&gf*h#AF7S9(W^fpEQDqC%v=bf&vwa#;y^Owc*dIb|~gT9{4&m)#-Ri-EVY>{~g znw-S~uYXOR&I*U0X_uwk=xkKHQ_a^Ipc8D=EU#P)uB3b`y)f``Kcg`nOZK(i;a4II z*)Ey0jS*+dQR~Q0M=3rk1pNCyrd^y;8P#AE@da;I(Ze~X{K+z&G5KW(pft)R`_FE6 z&t&Lx8O1^YJg;39jQ5vH@6>zNEGnD|1Yg^2w@M-ZSv6u%LvHqnYA0f0n7a0b?_RAo z#)tcMpIqlzLDw!74*ffNhto6jBRJVOZu`03w%p5FF9)*S6A(6Vfqdci@DDW4qihG3 z9!uckmOQNuH(5kVJ6052oJ**;)ZTat!1eGW5algHr0Wov*K~K)e3wOJptk=MbnI#@ zwal3IeEtRszK8dl?-bxdIa_E`DlpSY9j_1Y96i;ovWj;ehcxar!t@nePX3HALc2>X zD?2?{j32f7E=2OEb@2%y?~R}2Xw zs;n&8IIJuO_#nZ9x=hLjJ;Qzhuy~jhHHGW(vqLi)T$~#QEkx-0L$N^>w zG@9r7x!#0&Y{YSwuGMUwC3u5huJJ8VAHA{nq*NDhbrJi<7rd#piJs6K{U%N4qe1dJ zw+iXS1Oq=AINJ^|XKy$y9ZW~hiWVs-2ugL-%4{@_1lLSxQMO!Aju__>HDhH&10rwkgl5l@r6qDm)UR2^7P>L#XYP9aEN+#~=RU1LND#4$ z(uu)uuL@)vIJ%9*#jJC$N$W9Uj^Cl>6VaI9p7EhyCAaJ+r)Ktvi1_Arg>!B+$sAP1 z1b^rNpC(={^G-#e&%b7q$7t=KsmTXd7=6j!qgm*_Ir{VqBqU}Pj1S@c8uP~WZiqu? zCiTr~r>!3Y)mW~N(3eNzr+gWK&t(L}zk$ilV0y^#?G7)mDa!kiG%xDo_;C7?i#Z}+EFTlaM5U_p_c(EmNMI9A z!3J9o;$JiQz59Wpuf<7RyixFUV#A}l?G=reMex@Is(RLCD;wU?tIqTM_Pot^Pd>e< z7UA(7oTS*R-!9GqV>W%oXC}>Pa&W1`WnKhKX0%Ff0LdJImz2<#77D&Qv5n*D))NUg!mb^u!I4=yDUxP$x9y_)V3DQ^s~ zMGe$-q2>PNzW?3!IW{EeMX2!Q2DRxCe9=*sB3qpm0CHNmsi`gg9Ti-pY^x6Xftqc# zYXh%NfsD`U-4ot?K3MV{fZ06Tmp?NAPlqt|ym?rHlfSXEg0O{yt2r;9!C*~jM)^h4 zq=tL{Sh!m9=T1u#3VL+`ceoWpcZq==!u4iMEndWcAGYmx4lI+>ATc+T!B9#!U;=V^ zx_z}a1=1zJSlt_E_9w`Lc%ox_4*-<=GOQ0tYk3SjR|(+7Gq{#IHFmq_m*Ereh64~d z8SQvejRw}+t)EyP&s|ydy#-u078tg`Z1oNI*=w3x+DPoF@$KAw;LfD>q4`2q{UiTk zh0p&Fm#-W-wx)T}IW-xeGR}toAN9jOkLq7}X6$fAQ;t&k(SMyfh&ZTB!!4_q{2#jS zxAbx!@+;J0RJe9wlRZ}x^R>a^p2}T!LqX%3Kiergf1B#dez7>EF#do@+!1CG9F zD~@5YB@x=zKDE;>=5{B)WCcYbo_Xrwd+cO!tuKLRMi)#_U9)9ky?>v790{VNAQKp) z2TxF{GN-FLvlN9-otqRyf1#g~;)}j9-RB^#&Y2HPkJ-^5V17k;4|@Te7Rk2wD{`Gf*wXaQ(p-JBM=laHH&>rz)UGK>ueVT<;5F>N0@Imp^n8zE8D{fSaE`Z#CS-ZaomdvW+R6)uEZTJ4J=CvOO}} zdcW=S?KIU9Sg760?^}4ClKD0C;Bx0qJrA;J0E3TGe0(HL~Ws=4Zjt7vxQxwBnm=f{;>W(vZmdWzV*W9wx?0oci^=pF;u(BhrtZc(V$X>Tg;5S;I}THM z(;zT582xeJ7xknb&0E#)HhCaiV>EpyJKY!`E%ak&2Re<7%rIsL8N=hwMI9V3TuOjS z{-rHn!3CWA(=Ix>GXwM^YUSM|xwF@qdSTlRyApd{$vcj6yLu_nFp;IE-s~vx>?MkI z9!>4+K;PuaPKFVdvp{3zkj;_25nx4<2gNne)P-3frgXdobl&g6Zty;zh2c3U9ReZi zoA}mnARk*j_aSxeV;^nu_0EwFVYJ}83jNHe{#u*j>IOc@09&ve_fsUF37RInwC~t)1xS zfy%khxhBv}0sN={pkh7MBEXp%o%j*}A`AioGlqwt2U?(M_9sh4`T}?v(%@V=-M#9H zrG|G{NoN!D*pbcw9bNbx6a*U?DI;GjohC|npHoC?mx^R=b_DUmL$j%?m4A9Tj@U5B z10nn8C5grV>*)FWKP6(o*b}jkk=6U}A78wp{2?8@e_v8$T_9PMgOO%$QAvajbA(@R zq+ogx82u>TVxZ!hIM*`yo1s~ON}B^}>@&*f=jtOJE_Ur@a1CkwOzXW$7Y+4=oOz0vjM1(jSFe5TRaw7 z9owGne0ej^`-*y|DQnUzXh+&dq|a*`cAePt#3k$T$lD5clCaN( z;ZUb6kQd6@Bos)$niPGG(-64x&nE?(*Wv`0G5)=&eKzFPH*C)e2v(4`96=I-Z@()sPE6?_Fi<--v4!arGC*7WE*kH zL+%DE^E_>1t8{94!|bJ;`8Smt?~Du}biHV~0SM}7zB099Y~KY?I!9N071xHRfWXg; z@H+!gm?qWE)Lin17Fw|U-AUmu2cU?7{oI_Neqo%Cf#tKvSj^fZv5H+TDOY$Tce8fx zOOyKVAR_jD8(@E*ZIV|`?xAAaS+DiEILgk1yDzrDA2-auqqz>MST>T((`0B0v@2+V zBWR#L>F3(vbmxS&d8z~$pyU~Yeb3-|gIVtFK}ZiRHU_`72VpSVwd}`&(qC?d=HhQ6 z*9Q-L;j#6A^ILB|;sP33skfbti8H{4gm;(Td?N&mVS%Jtm4;w{Ydp5P}ms(?}J-6$PM2Cgpfm6?EgbH`U9>DKqtceZ(V=~f}sRSCCVqc z6bj~4a@@wa;Olc&f&A!o8!`QhEOBN}F#zr4g%%Y;^k+!0^ls@*wO;ax=@hmo?y^OlE~(#C_jX9Wuq*$_Z5bI+fF?Ef z0{3KRvhP9jgCN=)*mqe1=CqNAtDwnRXV`ElyQ;$^T7uXaAhBwv(-U=qaeR#9)=nD+ zDR{X9jfQu}GxO*au~Pbrp4*@W&2Iv&?=X9{Au z__7srn%O@3s#$oOTq$-dv~o6}X3^>08(8ary_^<`5B@gIleD+${O|-LH?5pF zASjX}F#B!zshi537!x|@Cpmn}u&bG{0zFnGa5T2{x{*FEWVEe1Bt#7dDM z{UH-X*#benuVCi4Txdb?8KUyLS9Mo~{IeA)xx1r=qZjB}K35v4>L7kt^`M0~S6Z{w zt8Fd&*;E7pQ&@0F-bUq!f~?GqlalS>6Q9?NoXjUV3Hq7f(wmI)YI~0X7s|BZ zqT6v$i;O?6&+`JEu*+#E^;K^)6*HivTe|*(AON?o-M-gsjw@V2C3N|d=cUY zyL4)G!vqM4Nua65b8q1ICJ3OxU-TIm|D4$WN-=&H4z6DG5sOULfB%?Ro)Sc{&Db?W zU7lLS6Nxf()%_a35F-$O9`=L9tU)V9CsxHs{W$Jou3Wc`|FCkLIDX6D%DtYm367OTJTEY)z$Sjk)Nf$hwmy~J-n zZ;N=c7PGyAc13Y9$>egE<6oo5Z(o$CiJ=g@{3HQV1FA)s9*9JswBH2ng%W$Kd+t>b zo04}ObF#4GM{*+}TUI1BKOJU#AiD`(z=N8CY3g#r`KubQW;|2-(})`6qIf4Ven(;j zqEi9ILH9g10D3p8%T3tT-5I9wXk*KgQrm@^I;iGt`dJ6f@Wir9#mc{l@0T=(p_NB&BCDT)k>Tb2^q($*Liw+xC_(pmeSP^IzbHI0y4-C&jEhvc z>=AsTm_z}^s@>9A=kB51-=`n6KWD_hY8L}>uJBLs-6v74*!ML4p}CzS=RG#DCVM5Ndelbe+8umgDranOfjdN88u(sNcW8;Nv^+ zZeYhU==TbR>)P-KM<7LTb_M##otcQGxP^d!bkG0=969IFyV|6`r(K&+a> zr#y!$ByGs z?V%?BoPkOvXxZY~tr!S)vSa%&Z&L1o(ktOOzmO`lwJ*EY$7}@@mZFr1fxm#r>#>)+ z95{pBaCGNmqIXsbQ=;=@8UBdans8=_NmQvb0NnQ5-%M-k*rF83PSE+A)uy&#J5~d{Uo6Ej9_=xVUFp*oZ18u** zJ;#)K<(%yHJLwF_KywrHy%>~Da$d(<0L_!5Ml8KG07XW@8mBn*mrq`=K08}D`6Wn1 zRfKCK+* zZnJ1vR7!WLjc0nI)g1-jTVF<>WRA^6NKa2@z3aBu*OQH`kHZ9-y0M?kgh4QL`*x)V z2$+n;1ku%>N6p`C0KJ|92y{H9@dwWB0>7KfT=0us0N+;nD)jw-eWm_F0>IbKgdR?S zZM0-)|ML-o1L&iu);jAH<Yh)S%?cV#>-jBIXh=c6%FPs~E&$^cBBL$g2G)Ee)_i zNBkf-ZipvFTe1R`@&|p$NGB#b@fi)bClI%XMD92;P5pxV?GKH zUBgB7KHzZn4*Bwj1r*&v%T~*7Jy80PmjEi$J)3c8TeR9U3?#E~@Li_!Vq8!N;(}$O zzUl~+xKi0`KBBf6q^wbk6UxF60WxnY`?yO!Uum7UnT&cuj$yjzQ!nR`Lp=Ay6`byD z+m&371@_)T#f%x|D1`H$78y8uWV#jzw&-Guli-H(CWS%#DAo>g3k91mEL208NsKEg zufjYX{N@B&%^~YR!_DJ8QEHSnPe-=HiI!=0gz1`eg~qut?ta_QKe;{pz1=uyoVVaob*G5YNRF*HDD>I zEVkX_x1$=)V4{vkc*(6wWcd%nk&%H5A384^*QZKmf|RH9f9*V2@mj9lJm2N>Xzom4 zS|F^vqox|;r3qoMC8iQWeT*xOPBO77ur+xq&^DWt%$&-tmh>_=k~4)|GI4g`j2Ko! zh}>g+dt2mbZ87Q|KuW4e7{N_1GI#%w1Gf3H_b52vb@n%q>sTO8u&86<$H{Dz3LabJ z{PNhZhzpOFpQO1oAuqkaG3PA*`)LY!h7CS-(~lgU_LtSFrUD#7L@SyOg-zAhMc#=sj<8li!UOKY(oo zC7J-q0SD%IzoV%Wq6n=otnZzIESn*&30=IxA3SiDXMlWo7SG zQf6k!OcBS--h`u)(Ii`BJ4nbnM-h^hb?jqgor7Z?;~dBNT}O4__xJPo{dXScocH^> zuGf6N#&uoCXTIbA6zr9v%6s&H-p>^9M`(v@X;3pWCI9Czq<{6tfFJb!WvMc8aWWoY zvfrrDfQ2rSjZRCRTf+X(nwsD|zkZT7+f{uz{PH(`4w(bLqA0SvPN;Of?ZfX6kU$ zGD_IJprO2y2z=-jU+11HfA+4N1r97cFgJ*%G)2c)`d1dixSIA!JL8o2dfz8a(r2;J zpJM~x&Qczp|B=Zw{PyJb7g2w9r)!*h6c*9L=aX01#;FUL{J!rwQ*nAd_%Yk=*|h3z z*2|jSe#pn-P9c-v=#%?NB9K}+RwM_yMJwNoNo=tE`q}Bwg3niO<*3zFQ9eZtQTs`j z(|zwT@w{U-^UdGU216IbJ3g=6EEDAwvzR}ZkNu*Dm~Xcyvj4E1dv6o5GVc1(r)Yxa z99GLnExrsxU#(WTzlJw_?_}k!@T!Vh__9ZX!><6Z=kK3LwR(~7iI*BDPs(zjE=KU0j{RG=J{(E`8j&wVh`5ZL zU}|vHH_8=1hIRRBQOh3)bGeA-y)lovS6;1Mtt53+^OsN%*znClO0us7TP^(d7FN6h zerPYj0~ePbz?iR`Of5k1d-*8t*L{(x-J^YSlGJhaDHVA)=rmu0*RJ7cm7;T<^M)FC zhtK_l-A!pCo7{eKd@0J9uz8+}1=e2U`j)ryOg)!Y);oP72 z&0%(9)n0NpvLs}%_$q|vD2M9hv4m$aM;=zYp(czrp^RMl;E>6J{H{WsVdX2JGz1z< z3QSH>NtHXhVN~{R7mkbVtIjW;a5MnsBghK_>s4;qx}e@-7LlRvur!)2ce2ksy{gu4^{!cvjO)i3g18 zU;b5{{{&e4EE2#qH6K0k?Z1s8VPSD(Y&?{<@;O{S@2JuFV+h+pIcs;`h$Ctv?@bOb zUS@h+m}+-gLF`^FFi|;g!j{!i%VJ|L_D&WH+fo0bI?C|KRl@Se{VdMDLq#9$GKSJW zbC;Whw~kHbv13#Kpy@LIbtJsw3^+TzgZtzs`robT6?Dzu7}F~>es4O@KPx%^M*8Bo z@((Z42bMbbBTKpWs7tKNQ+C`_0@t#nmB;4Frxurdq{HDR9T8i_wH}W~1qbq zAe@wAUaC4r-ln`gpQ{~JV)E@9Bm09Z_Jtuk!!*AF2BUogdWt zRF)qjDGP43F`a1KsI46E;^pEsm-+au&WC5e^nwrT1EN{ywV!d#-lsa$1{7W$`B32^ zVrjOuz44UUx7O_R!-nr)%wFN{?u$&vg@2c4k+goiLQp&UQOLaBEp|RDwI?#%h41Km zI(Cmf;Cw#Zn@V>mDg9lJlTOQxd_p%{pCk5}FYC9QX@k7>x6N$!kDr9E^e9(%Tl#(l zp}@L~eNX?_;D39vFI*-EpHE!kPrbrMIyie#FcwaFQxg_*JWejd3me{wJWMw*sExY9 z@MG+n%6rxepl*vna_e38xnz|+HyFp%kr_!L(7W351RS+Js_jrc_2~Ub@(}d&i2lq4 zqos@+HY$wFloHix7qs|f4ucfnH;C2qy5vuZiBg^%58Uo%4DyvTRIns?$WMfS`Ha$^PYWRpU`yJ%scmilDml?KYl!} zy9}pN13fbZw)_Z`CnRh@v8l9bQavbp)ulx2X zjh@e0`(ojfS~U4lTbc>x3oldiaU))90Y+9!+KX}(_~ z-?ypxoa4pI+AAdn0Jx1j$^8FcvP6|ePEX)z-xL35Nl;5O zk=ybZ{dLICcJN{KO6H;ahh2hG%w&#qy8WGrFHJnrY@)#BG#LHz;f(v>Q!vn}F ztjAduWKu@EwjKMU$uwV$ZRhj_El3X`1{z+fqL;fN@@BCmjz=3b93YIY)jXOdhf2m^bMb5Acfb1~^`8hR?o38_f$O$&b3{hf z!8MJ#ptTA9ssvy`uuD*MnEjSbU!Hc-nKshnjQP(ZX`f@dT5>*qNbPdCc3zkkd1m;! z-j>lv%WUEv{&5hqOF-tw>!+%uoG(+Q%oX&qOTD1>=(OkEvmhtvZ;3l?{OE4!cqdT+ zj)oN~>i%To(K)9`u0#}-!bcPj5bqIZWZXcCmhL>jV zo&3JM;1{4aIkcMDI5HOMk63AyY#c-%)zu2bZX$hHNUWuxZ|uVe;}2+n&*LivgH6WD z-RG+Ia&RBaE}@Y{5A@7*=4*8)i5I}07SB^#{-7vy<+x#i;;YN)A(Y> ztFhMz^tKPRJcy~SVa z%uoI%$0gamldQIUj-#KLBi32&>iq2mXzPr4Jdv(>PE!(}`O$a%^o+*)uc|aJUnOdt z?&?{V6jTVeZ;X+Bwq}h{vbhe68y8Cq_u#s*E+%c?ytigm(=;;Q53j@+6P*SSCY4UX zA?{DpEi0W$y3Ik_t~#V>u$pn)j3j4Q4q@#*OQBc$^@MuhNa%JX|A(>~M;@;eb!%5g zKVcidUao#>LHyz+#J1gl8EN69TfoB8K)zxt!ZII6En7T(cOv2_o_9=TplubtQ3zVj zYr(!+yXek+uHe_O2s##i)v!Z{DO%Zvu4!p>r0VXB9g~RfTPi#S5u*-Nl+Lux%!GG- zV!wlu9ipXqEz%=}+F8>l>TO-_4p}c5sK@}v=lQ(m=w&&CpQE>Tsjtz@^vriwl9BYy zZrGw-NmdA2++dzh{fcQ;9JZP>s33jl54%IL+&R5czvha=V?#*ASGL{P&C)}+O!G?; zpwSIi9;9t2c@|4dG%c9XP*BpcJf!akw(_{y_|s0w-AUJA$-LGmLqjiEMx|;mYckqL z$sCfTj7RLD(ol8KzCm;5{)sZ>Vh?&qS{yzAYy&RJ*!!MQOujA}?GQ5OQ0Uu$u`H6t zF_TuAi!yeeX5_NpLRO$`&;mpf6`J5lJB@jAPh zw)GvDcd^_D6{OKuLh3Q4VeCdN@xU%6iE;B@oTJRcj(ZL8PNJS-{;X}! zcfq!&l+wpIHu$?z>AM&mX_0WeiprL|U4u~R*4G*QK6m@GVH?OovV=?{Mi}l^(bcxi zmQus)otnYgrr(J54hha}Ay28Sq4Br?V{l5VUb;SdL=s+8zWTcRVrRFKWrk<2GmEtJ zB+OyLF@_OR%j?Q;m=Q}bE9I^BxNB|D1ew@$HzLkmTCW(fL$FzLsh8qx%s%h*2S{I< zsFd?jV7a&Cji=y5*MtdqW{IclM)_0{Z1Pt$zA#%>!+k815qU-YT1$m0LMe>2(W)Tiy;!v7e)UbQGSwrQ|0+9dZSykocuGRxy+4KHo`gWJe@? zHEF``q+;FF1EG&OTLvq!yrX;DLt)PZupcxW^r?P!&^bNX9sNMu?lv&c(0Qn5O=@v{ zE&e`Z#E$sXEzk`&61w#?ikqwoMM0AsRXU-M%T-{gf55Fc4QD{d=dUY0Qy4Cid z9FcDI?Q(dXg=q(Rq;kleQf~+rv?!G;>u~04I=@cS;FBmuEo$$hPL5w1sGGM;v z`kRl4h$zX;?He824>~Jaiy7Te$d2+Atgl8a;Fv*j<5&VztonHSL&STP=x@bz&vusF zF?a}%O4V4cVSHEM>boNJJ3eA{gNAbnf?-5}F^i($@d<50aPP!2Rqfo!U~*!+c+ONm zx2Z8!xPb}VA5bQ@VG+Dxem))LRu#GuX!rU-x7soz#yWI00SXiWQ2?J0gy-R75IZrL zo=6pZwo2_js?th@w4q{6>=h;Udaq$aw(E*A2sIgZF^1Xg#8}i$Ec$xr=JnF<-CQ?( zF1k7dQ(aiPn_POzL=#5C;Mi6<-=FfodQk-QqG9WKz`uH-XVV2$i&5U`ug-Tt->crT zH^`H|#knzgYiUlR^F?pD|Il@nt?w8b_0iHwb`+26q!UXx58ZQr76-k`R^`+olhR=U zF-wNoIp?>7l#3%n&Rx@WwM%okN2VJ1$6jH~uI{>Q567IGKT0uwoWJY|82=gvn>yLZ zM$uh9*2~hyKKjYNLvy)2{9FsO%mhxr?I|Wf}Q)%WyIp@)H%Nj zz2aSXKKxl}T4Ln;#nNN@zJ(Lzc15MVo!v#9;BF$t%|YdAh@RJyW`Lc{M$E1yrv%^V ztgn;Cn)_Z`dH?R@dR~Zwmt46UW8hIoj*7#>HVXR0iRVixU69K81x!tP%>&g-2%{+7+~iVZD3)Y8zMD%5DP!PMuqlalXo|wOr;lcM3np)ut-1+P z$OL3Tzn*QGh#r~sraIvjJddk&3m!^`o9Wb8hE`A#Rtq&`^{A$gMhP{;Wn|)Gc9bB} zyD@wr1H$3w(*w+KBW}RImrXNuH$+RO;uY%L{35Y}b<(DEt))bnxt){*iN6NUpZ+pi zFWBQSF^O^!_ms4AZ0))fD-;?Y1~r=7ALQvf6_<9YhbwIF%ltfh!{V;WJ`r2*P0R~j zd`KU8TR!DbzPyO% z>Q{1c5|xW5%ii1Lx9Ehja znMccAx+35d^SE)Xv1PTpy~&$3Td4WvEK3UI*~BJb$&FKBd9>8nD18Q026U+ zUfGQJg>O#{=YraD8Muq8UEy9w5yw#`eE6Q|LeS)0gD=Ry7oZv4{F+N<%tEbFfRUXR-=xeUAYu=@#ZKlid z^HYJHi#vS)W_D?TxVWPQvSMOlE-E%l?#Q$C7-VoubobTw^o;fQI3&Tkg*BL|7^OC~ zGYWY32%%=ey=J-YDHlog5=TtTUu9$@tc9Hq1v7nhhG&mKyS4GyR7~j?G|d zZ!TjtPsIvX6Rk$r`d6pt-Be_GMpZP_7$riZM`!lBxUi-I2{{4lw}zT?4u=wQxYarJ zh-pet{%@!xJM_%$Ke$4*i$WU;*4BVj$dli|F6nK5nDeTokGE+0>FrcTM;b?jl76|b zRl9Z}baw|Dd|inV-DbY1E#$6Leqo`5%I+X~Sbuy%r|4G#wC8E?230n49!GVyHBz9t z!xk4DJuvoJ#K$TaY~mUW&IoY(^?jiybPG)<{g6^7`Dd53;XSc_}PsEVY`J;VJdfCB&m44bsv+uzaOR z^!muWZ%H&yh0i6Q(y;WN-m?=Y=l7~U$rS6K%9qWX=sGKAlBtB1hKxI1JsT-`m0vFM ze{`YETvbbBa6E7QAAlxD*FonbJTW zZ_-^quaTzF+>|LtoXfuyZbo^JulXHB`fQGE$B}Eda0-=&r<1;UbjEm(2R3yh_THJr zC4am#Z_wD?(_K`E4v4q#%q5l~Ba}qy&>S@{8V8xZs%Sh3w$_j+bLb`Wp=6D_f5p z^Y6Q~(P$UyTU7Yle|bycoy)2x4X59-=(*f^?<~xC7F@Kq6 z18;l@@QifcoCyXo1(RuI$gt%rsg@<=p?q0w*p&(00mM*5KrnuI6ybq)Aox(hF!)wx z47b%0E)}U!gXk;GpCIehCKZjT79UlyWBAei+5i+yM)%9PRs?Ac>>4BSk-j;pRlDSz zz$ms*4{w#E@p^ZkGFMSLcOpxZ>te-IuJVwTxz((ia@dP;+4*+Z?BPcFML z44c)y1`2e`&5!is2EGVWm)k%LiQiSe?BlXyh`I36>tzht>Z*!UXqQ-WB*)WZan)9x zSVU_OU#Jt&YQytq(v=i8jJcpm_w{Qb`j*{lRIieD^Y1`hPis24ss6e?VG&gsY(*J& zrl%4vO8K%_e2_2~IGBWxWrMP=2w0XH;kbfPiviS#^S}6=6s5YC8TwC2xD7}l*;sjR zS!a8MT3>dSqMu%i0H{z%rAWW{V;^nD@tx^tYi~@D0Aq!0n$Ce3{`KWRDe@yFIVk^8 z4$9+n9l+ENC{}*X4x!yFkMf;rE|0l*k9YmlMvpe_kSrALRYg8gob zNqnMpb7K}b!xOzuV^#vOFDh8^#O zBXbyhy>)QoR*kr))>O~;49vNslQ5bwb#Xq@<~ZF4Z|9E_mme%SwByI%(H?el#|h@g zJXcnjvvt}9^bM@eUiDC(Mu9Xys``z77y;_y`I-%zp)L+i7oPe z^&aV59%&5OJmO|4z`o3&OAK~(yMl!4_ajf${ZvOO6%*k~_Lp0pe=)E!%eINrS4aWD*Lt$Gjo@oJV9YE&e;l|>!bsAnEgB(VhxsM@*AKubw4)^{ zqtEWoX;1m{u>bV?=4?@I3huymD>f0DWfxd|u}yryCavR)P}gc2uO@n%QvZ`1u=BPG z-PW$*Z$9u)(>iyOA=YbX{4}|D1jQ z69!Aqqe6(GQt?G3Y=AJzJMHul1EbDsIO?m)xch9-uCNW@XPR9GGCS}fUicgzOh(W5^sM$lL^D=>W>j@0Nf=t z_Mc9>Zc|NR2{A=7u`Jj6#xuc&OY)!n6P)}7P!|OlsSp2&7=f4?Q)ov@M%K0{QgX5f zjMe#KYaJcjNKo_=9|^&na%xSir}JLb^_Z=~MCI-Fnoiw&aNryk_NVIfu2k{SHh8-S zdS>FIUwAXduryt=KbB7O+%VP$DNq)t+7ThEZQ*}YOXKs(X|AQTk)h(6cXq#&h%MPQ zbXFbq55!Zvzfnu}zgh6_kxZm%dCPIP9fXUj&!V@rB?hd>B62~)=6eE^pMB$ygDi~6 znL{D5U!l8iZ|UcrqS(|_S_FefhI^8_Gc~-I`<6y}YA4Eq^nexx1cQ)2qbXWb4M~kXhTzip zW)+RDYVB?l?`KnA->gHx=t(8JHydoVkz>JTL)u7>TPaT~%kd$iUlGwX6PW1BO|o&p z-#P@MKDxqHcJ`W7UuBsEa({c?S<6$hPLTFsHW@-Igu!SgG*u>t*`SZ+bxj8LMC^ZI zC%mgS$02o(Pqe(|^DO1@%qVRgNaDvPWkQnzm|{^dpMHd$T}Lg<^J1!<^Fz(LJ(zO( zPRPbLW(3-FF}rm;VJ$o3K)G!9l5${_*!?wza`eR@BIH_6&PH;2Z^^817PdcU zTS&Rfe3(%nUm(%UTD7TzKH%Vkd+d$|l-VH=}48l@_jAZ&c24paQj;ig)ulJvx$+N#(;9c9K4NXye`GN^L>p zrL^Rfo5XQ7BqzLQ6G>`xGn?|PI~guW_etZ%pm{e^lC{@8=U9&Lzb@f`KB|e0Mc_R9 z*~FiJ)q*@Vc%vb3*SP?ISPB<5ae<3$YVNAU4JWzkW>xqG&CGm5`SB|1PG<57Ep*YPhYl`kjlxzPH;DrJ9yr@i?Y9CB>f4J4!nt|&tV%vR?m>GsRHjg z;zFykKat;MV>Z0!tmelnw9cZFQ*FxxRYvl(`1QlDQB0caW~?7_NGh$_=0PE2h6tH6 zxZIK0o^Av6hA&<_I)>3p7b zbIm;1mco)tiaA)bxRkT*=`8|hUc2?_{w$1b*--7)Qy-VNJT1-=IX2VONSQ>7i>p~h zj7D>eda*DVV?i>a?-;u!vpe*(^8RAo+DyRoi$F%lh0(D7R*Tn(Gz#A zkVKna5Ydr)e`7>3J)77BXf3tToJs3|GB&&9`;R(OoFU<4I;j`87l6D3Y+D($ z0Rd8b>J1tDUqTI(h)r&~gOR{%4-w-~wF_Xq4BZ=kJ zdzQq?$=b#8Z0ymBT!E#w!E&MSjlp55L z&h_wOgyz61eo5c#PyCBH{{SM6OZ+*u#Yr=Nlkk+ZIwuzJ>*AM>GsJY})kE3+bc8mZ z%sW>m%{+NKa@7VBo<-yFwh+~y0EIJdsW-fr9)`Fkd0*VL>9CJ8l0MB9(DSKbZTJb5 zA2PUKmh?Ba#+o8)+(e~bzR|D=3_rn)bwk~h`kf{I!9X@YPL3i%u+sMtThq^Cl+pc~ zhJP~LlSM@T5=ca*c*=ul2EAR2Q>^T=4+K{R9oixBbDGngTg)zEA@miHCM_aPIR3VbIz#7@@ZZyOkuC z3X|V_qcUs7wXuIX|0dz z`>nUWrnb(dYwbcHPYFGo=iCumD*ov8jo-X@EmCH$rBPO*VbkPYUwEt-u2IQA| za>_zPi)Sza*Ggu8>?YnBk2$uG>>1cZu(oO-s&ks2_xT5~{P|#GcUXXt%Qx%?>))cp zYr!%vEscbV8Y|MyaG!epBh4C+Ff+4RP)q#)QYIyl7=g4Qp zs-|RQe)pZ}!_}}y8oLt8b}0J3e3{f_2~iewAGdyRN~FwaZ(*qQYTF32yL6olE?3x2 zol{*YdhW$n@pzZZgDWW?S_tYJO4T6^?DP+x`1eRz_%d&=iH7W$RMW0BJKLk+z8UDg zEu!GoP*Uu^T?D6|v#X3>;Bty*Y9OLebZh{cRK;w%_w#VMX8CN?%!ms!X=}E7OvLsk z7oK;=H7?6w1p~*~vRrK~aywEfCByYYD984pT^roiW zKriy`YmUnjK0xQGXUnYgQ|da~;E(4_ z&3DVigE5(3&r(Sh)0QrnP<{OzvXw~MuseK4sKrZ9fYJMWbC-71Zkqhg#$M>2_5Ai! zq_;`l1gmSv)`jPi#;f=c!mm*UY#nY*1FPY!QO(4l#MaA#{ybNkx0_Z%Nzp_h5P9bL z#@wZW&+NToV=8ffMfoo#S!ZYIP4FcZDE*awRZR*=q-1|*yyc`g6!K7Dl=mbyNxR1!Ys2#iOOC zQm${wxiql29gPPJzrJB;MD!cp&rhUG9bv`Dw><2G$2LfLyEuhDvda}xV_!yf!x;DSCLq!3v~wWS z&<4xQ41jw~%hn92Sh^%Va&z$wQ22adFUYllfOlL0-N4&XFy)6e(KK=h{8+c?R4Ay& z@GW!*