From 81e4c7f50ba3118ce341220d076a575c1af2bbb1 Mon Sep 17 00:00:00 2001 From: Ritwik Das Date: Wed, 2 Dec 2020 12:27:12 -0800 Subject: [PATCH 01/44] Fix trt Test --- tests/python/contrib/test_tensorrt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/contrib/test_tensorrt.py b/tests/python/contrib/test_tensorrt.py index de9822289528..47270c18e773 100644 --- a/tests/python/contrib/test_tensorrt.py +++ b/tests/python/contrib/test_tensorrt.py @@ -1058,7 +1058,7 @@ def test_tensorrt_dynamic_batch(): mod = tvm.IRModule() mod["main"] = f if use_trt: - mod = relay.tensorrt.EnableTrt(mod) + mod = tensorrt.partition_for_tensorrt(mod, params) if not skip_runtime_test(): with relay.build_config(opt_level=3): From 8e2ce9aff1cb0232f8cc67d3451c9d37908b7883 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 2 Dec 2020 21:36:43 +0000 Subject: [PATCH 02/44] Fixed stuff --- tests/python/contrib/test_tensorrt.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/python/contrib/test_tensorrt.py b/tests/python/contrib/test_tensorrt.py index de9822289528..4713130d7112 100644 --- a/tests/python/contrib/test_tensorrt.py +++ b/tests/python/contrib/test_tensorrt.py @@ -1050,26 +1050,26 @@ def test_tensorrt_dynamic_batch(): batches_to_test = [1, 1, 0, 2, 3, 0, 1, 3, 2] x_shape = (relay.Any(), 1, 8, 8) x_data = np.ones([max(batches_to_test)] + list(x_shape)[1:]).astype("float32") - result_dict = {} for use_trt in [True, False]: + result_dict = {} x = relay.var("x", shape=x_shape, dtype="float32") out = relay.nn.relu(x) f = relay.Function([x], out) mod = tvm.IRModule() mod["main"] = f if use_trt: - mod = relay.tensorrt.EnableTrt(mod) + mod, _ = tensorrt.partition_for_tensorrt(mod) if not skip_runtime_test(): with relay.build_config(opt_level=3): relay_exec = relay.create_executor("vm", mod=mod, ctx=tvm.cpu(0), target="llvm") for i, batch_size in enumerate(batches_to_test): - result_dict[(i, use_trt)] = relay_exec.evaluate()(x_data[:batch_size, ...]) + result_dict[use_trt] = relay_exec.evaluate()(x_data[:batch_size, ...]) - if not skip_runtime_test(): - for i in range(len(batches_to_test)): - assert_result_matches(result_dict[(i, True)], result_dict[(i, False)]) + if not skip_runtime_test(): + for i in range(len(batches_to_test)): + assert_result_dict_holds(result_dict) def test_tensorrt_dynamic_batch_conv(): @@ -1080,8 +1080,8 @@ def test_tensorrt_dynamic_batch_conv(): x_data = np.ones([max(batches_to_test)] + list(x_shape)[1:]).astype("float32") k_shape = (16, 32, 3, 3) params = {"kernel": np.random.uniform(-1, 1, k_shape).astype("float32")} - result_dict = {} for use_trt in [True, False]: + result_dict = {} x = relay.var("x", shape=x_shape, dtype="float32") kernel = relay.var("kernel", shape=k_shape, dtype="float32") out = relay.nn.conv2d(x, kernel, channels=16, kernel_size=(3, 3), groups=1) @@ -1089,20 +1089,18 @@ def test_tensorrt_dynamic_batch_conv(): mod = tvm.IRModule() mod["main"] = f if use_trt: - mod = tensorrt.partition_for_tensorrt(mod, params) + mod, _ = tensorrt.partition_for_tensorrt(mod, params) if not skip_runtime_test(): with relay.build_config(opt_level=3): relay_exec = relay.create_executor("vm", mod=mod, ctx=tvm.cpu(0), target="llvm") for i, batch_size in enumerate(batches_to_test): - result_dict[(i, use_trt)] = relay_exec.evaluate()( - x=x_data[:batch_size, ...], **params - ) + result_dict[use_trt] = relay_exec.evaluate()(x=x_data[:batch_size, ...], **params) - if not skip_runtime_test(): - for i in range(len(batches_to_test)): - assert_result_matches(result_dict[(i, True)], result_dict[(i, False)]) + if not skip_runtime_test(): + for i in range(len(batches_to_test)): + assert_result_dict_holds(result_dict) def test_maskrcnn_resnet50() -> None: From 4e160e986422d28ea35b390b0ec02961604815c7 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 2 Dec 2020 22:16:25 +0000 Subject: [PATCH 03/44] Done --- tests/python/contrib/test_tensorrt.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/python/contrib/test_tensorrt.py b/tests/python/contrib/test_tensorrt.py index 4713130d7112..8c63c7bdb15b 100644 --- a/tests/python/contrib/test_tensorrt.py +++ b/tests/python/contrib/test_tensorrt.py @@ -1050,8 +1050,8 @@ def test_tensorrt_dynamic_batch(): batches_to_test = [1, 1, 0, 2, 3, 0, 1, 3, 2] x_shape = (relay.Any(), 1, 8, 8) x_data = np.ones([max(batches_to_test)] + list(x_shape)[1:]).astype("float32") + result_arr = [{} for _ in range(len(batches_to_test))] for use_trt in [True, False]: - result_dict = {} x = relay.var("x", shape=x_shape, dtype="float32") out = relay.nn.relu(x) f = relay.Function([x], out) @@ -1065,23 +1065,23 @@ def test_tensorrt_dynamic_batch(): relay_exec = relay.create_executor("vm", mod=mod, ctx=tvm.cpu(0), target="llvm") for i, batch_size in enumerate(batches_to_test): - result_dict[use_trt] = relay_exec.evaluate()(x_data[:batch_size, ...]) + result_arr[i][use_trt] = relay_exec.evaluate()(x_data[:batch_size, ...]) - if not skip_runtime_test(): - for i in range(len(batches_to_test)): - assert_result_dict_holds(result_dict) + if not skip_runtime_test(): + for i in range(len(batches_to_test)): + assert_result_dict_holds(result_arr[i]) def test_tensorrt_dynamic_batch_conv(): if skip_codegen_test(): return - batches_to_test = [1, 1, 0, 2, 3, 0, 1, 3, 2] + batches_to_test = [1, 1, 2, 3, 1, 3, 2] x_shape = (relay.Any(), 32, 8, 8) x_data = np.ones([max(batches_to_test)] + list(x_shape)[1:]).astype("float32") k_shape = (16, 32, 3, 3) params = {"kernel": np.random.uniform(-1, 1, k_shape).astype("float32")} + result_arr = [{} for _ in range(len(batches_to_test))] for use_trt in [True, False]: - result_dict = {} x = relay.var("x", shape=x_shape, dtype="float32") kernel = relay.var("kernel", shape=k_shape, dtype="float32") out = relay.nn.conv2d(x, kernel, channels=16, kernel_size=(3, 3), groups=1) @@ -1096,11 +1096,11 @@ def test_tensorrt_dynamic_batch_conv(): relay_exec = relay.create_executor("vm", mod=mod, ctx=tvm.cpu(0), target="llvm") for i, batch_size in enumerate(batches_to_test): - result_dict[use_trt] = relay_exec.evaluate()(x=x_data[:batch_size, ...], **params) + result_arr[i][use_trt] = relay_exec.evaluate()(x_data[:batch_size, ...], **params) - if not skip_runtime_test(): - for i in range(len(batches_to_test)): - assert_result_dict_holds(result_dict) + if not skip_runtime_test(): + for i in range(len(batches_to_test)): + assert_result_dict_holds(result_arr[i]) def test_maskrcnn_resnet50() -> None: From 3104113046dd982b3bc20c3df91fb6b24442c902 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 2 Dec 2020 22:20:11 +0000 Subject: [PATCH 04/44] fix 0 --- tests/python/contrib/test_tensorrt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/contrib/test_tensorrt.py b/tests/python/contrib/test_tensorrt.py index 8c63c7bdb15b..aadfa1303655 100644 --- a/tests/python/contrib/test_tensorrt.py +++ b/tests/python/contrib/test_tensorrt.py @@ -1075,7 +1075,7 @@ def test_tensorrt_dynamic_batch(): def test_tensorrt_dynamic_batch_conv(): if skip_codegen_test(): return - batches_to_test = [1, 1, 2, 3, 1, 3, 2] + batches_to_test = [1, 1, 0, 2, 3, 0, 1, 3, 2] x_shape = (relay.Any(), 32, 8, 8) x_data = np.ones([max(batches_to_test)] + list(x_shape)[1:]).astype("float32") k_shape = (16, 32, 3, 3) From 24116fd698938dad751b2d6b8590da5b13ac5b35 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 3 Dec 2020 00:00:12 +0000 Subject: [PATCH 05/44] Trigger Build From e8b223fc37aeac06649fbefc7d567dd18e22b5c9 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 15 Dec 2020 01:29:05 +0000 Subject: [PATCH 06/44] Done --- 3rdparty/vta-hw | 2 +- include/tvm/topi/transform.h | 720 ++++++++++----------- python/tvm/relay/build_module.py | 3 + python/tvm/relay/expr.py | 7 + python/tvm/relay/frontend/common.py | 5 + python/tvm/relay/frontend/pytorch.py | 81 ++- python/tvm/relay/loops.py | 6 + python/tvm/relay/op/_tensor.py | 1 + python/tvm/relay/op/_transform.py | 11 + python/tvm/relay/op/contrib/tensorrt.py | 2 +- python/tvm/relay/op/strategy/generic.py | 9 + python/tvm/relay/op/transform.py | 5 + python/tvm/topi/__init__.py | 2 + python/tvm/topi/add2.py | 32 + python/tvm/topi/generic/search.py | 14 + python/tvm/topi/image/resize.py | 34 +- python/tvm/topi/transform.py | 19 + src/relay/op/tensor/transform.cc | 64 +- src/relay/transforms/type_infer.cc | 4 +- tests/python/contrib/test_street_small.jpg | Bin 0 -> 119244 bytes tests/python/contrib/test_tensorrt.py | 35 +- tests/python/relay/test_op_level3.py | 108 ++-- 22 files changed, 719 insertions(+), 445 deletions(-) create mode 100644 python/tvm/topi/add2.py create mode 100644 tests/python/contrib/test_street_small.jpg diff --git a/3rdparty/vta-hw b/3rdparty/vta-hw index 12fb486a491b..87ce9acfae55 160000 --- a/3rdparty/vta-hw +++ b/3rdparty/vta-hw @@ -1 +1 @@ -Subproject commit 12fb486a491b75d70ec4c5e0a0cd112ab49a95bc +Subproject commit 87ce9acfae550d1a487746e9d06c2e250076e54c diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index c866dfb7f86b..c90d7d7ac396 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -82,19 +82,18 @@ inline Tensor expand_dims(const Tensor& x, int axis, int num_newaxis = 1, new_shape.push_back(x->shape[i]); } - return compute( - new_shape, - [&](const Array& indices) { - Array idx; - for (size_t i = 0; i < static_cast(axis); ++i) { - idx.push_back(indices[i]); - } - for (size_t i = axis + num_newaxis; i < indices.size(); ++i) { - idx.push_back(indices[i]); - } - return x(idx); - }, - name, tag); + return compute(new_shape, + [&](const Array& indices) { + Array idx; + for (size_t i = 0; i < static_cast(axis); ++i) { + idx.push_back(indices[i]); + } + for (size_t i = axis + num_newaxis; i < indices.size(); ++i) { + idx.push_back(indices[i]); + } + return x(idx); + }, + name, tag); } /*! @@ -137,20 +136,19 @@ inline Tensor transpose(const Tensor& x, Array axes, std::string name = new_shape.push_back(x->shape[new_axis]); } - return compute( - new_shape, - [&](const Array& indices) { - std::vector idx; - for (size_t i = 0; i < axes.size(); ++i) { - idx.push_back(1); - } - for (size_t i = 0; i < axes.size(); ++i) { - int axis = static_cast(axes[i]->value); - idx[axis] = indices[i]; - } - return x(idx); - }, - name, tag); + return compute(new_shape, + [&](const Array& indices) { + std::vector idx; + for (size_t i = 0; i < axes.size(); ++i) { + idx.push_back(1); + } + for (size_t i = 0; i < axes.size(); ++i) { + int axis = static_cast(axes[i]->value); + idx[axis] = indices[i]; + } + return x(idx); + }, + name, tag); } /*! @@ -246,8 +244,8 @@ inline Tensor reshape(const Tensor& x, Array newshape, std::string nam } if (is_empty_shape(target_shape)) { - return compute( - target_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, name, tag); + return compute(target_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, + name, tag); } else { return compute( target_shape, @@ -353,22 +351,21 @@ inline Tensor squeeze(const Tensor& x, Array axis, bool atleast1d = fal out_shape.push_back(1); } - return compute( - out_shape, - [&](const Array& indices) { - Array real_indices; - int flag = 0; - for (size_t i = 0; i < ndim; ++i) { - if (axis_set.count(static_cast(i)) == 0) { - real_indices.push_back(indices[i - flag]); - } else { - real_indices.push_back(0); - flag += 1; - } - } - return x(real_indices); - }, - name, tag); + return compute(out_shape, + [&](const Array& indices) { + Array real_indices; + int flag = 0; + for (size_t i = 0; i < ndim; ++i) { + if (axis_set.count(static_cast(i)) == 0) { + real_indices.push_back(indices[i - flag]); + } else { + real_indices.push_back(0); + flag += 1; + } + } + return x(real_indices); + }, + name, tag); } /*! @@ -406,28 +403,27 @@ inline Tensor concatenate(const Array& inputs, int axis = 0, std::string out_shape.push_back(i == static_cast(axis) ? join_size : inputs[0]->shape[i]); } - return compute( - out_shape, - [&](const Array& indices) { - auto ret = inputs[0](indices); - auto ind = indices[axis]; - for (size_t i = 0; i < inputs.size() - 1; ++i) { - ind -= axis_sizes[i]; - - Array idx; - for (size_t i = 0; i < static_cast(axis); ++i) { - idx.push_back(indices[i]); - } - idx.push_back(ind); - for (size_t i = axis + 1; i < indices.size(); ++i) { - idx.push_back(indices[i]); - } - - ret = tvm::if_then_else(ind >= 0, inputs[i + 1](idx), ret); - } - return ret; - }, - name, tag); + return compute(out_shape, + [&](const Array& indices) { + auto ret = inputs[0](indices); + auto ind = indices[axis]; + for (size_t i = 0; i < inputs.size() - 1; ++i) { + ind -= axis_sizes[i]; + + Array idx; + for (size_t i = 0; i < static_cast(axis); ++i) { + idx.push_back(indices[i]); + } + idx.push_back(ind); + for (size_t i = axis + 1; i < indices.size(); ++i) { + idx.push_back(indices[i]); + } + + ret = tvm::if_then_else(ind >= 0, inputs[i + 1](idx), ret); + } + return ret; + }, + name, tag); } /*! @@ -458,20 +454,19 @@ inline Tensor stack(const Array& inputs, int axis = 0, std::string name for (size_t i = static_cast(axis); i < static_cast(ndim); ++i) out_shape.push_back(inputs[0]->shape[i]); - return compute( - out_shape, - [&](const Array& indices) { - Array idx; - for (size_t i = 0; i < indices.size(); ++i) - if (i != static_cast(axis)) idx.push_back(indices[i]); - auto ind = indices[axis]; - auto ret = inputs[0](idx); - for (int i = 0; i < static_cast(inputs.size() - 1); ++i) { - ret = tvm::if_then_else(ind == i + 1, inputs[i + 1](idx), ret); - } - return ret; - }, - name, tag); + return compute(out_shape, + [&](const Array& indices) { + Array idx; + for (size_t i = 0; i < indices.size(); ++i) + if (i != static_cast(axis)) idx.push_back(indices[i]); + auto ind = indices[axis]; + auto ret = inputs[0](idx); + for (int i = 0; i < static_cast(inputs.size() - 1); ++i) { + ret = tvm::if_then_else(ind == i + 1, inputs[i + 1](idx), ret); + } + return ret; + }, + name, tag); } /*! @@ -529,22 +524,21 @@ inline Array split(const Tensor& x, Array split_indices, int a Array result; for (size_t i = 0; i < begin_ids.size(); ++i) { - result.push_back(compute( - out_shapes[i], - [&](const Array& indices) { - auto begin = begin_ids[i]; - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(indices[j]); - } - real_indices.push_back(indices[axis] + begin); - for (size_t j = axis + 1; j < indices.size(); ++j) { - real_indices.push_back(indices[j]); - } - - return x(real_indices); - }, - name, tag)); + result.push_back(compute(out_shapes[i], + [&](const Array& indices) { + auto begin = begin_ids[i]; + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(indices[j]); + } + real_indices.push_back(indices[axis] + begin); + for (size_t j = axis + 1; j < indices.size(); ++j) { + real_indices.push_back(indices[j]); + } + + return x(real_indices); + }, + name, tag)); } return result; @@ -572,16 +566,15 @@ inline te::Tensor dynamic_strided_slice(const te::Tensor& x, const te::Tensor& b for (int64_t i = 0; i < src_tensor_dim; ++i) { out_shape.push_back(tvm::tir::Var("dim")); } - return te::compute( - out_shape, - [&](const Array& indices) { - Array real_indices; - for (int32_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides(i) + begin(i)); - } - return x(real_indices); - }, - name, tag); + return te::compute(out_shape, + [&](const Array& indices) { + Array real_indices; + for (int32_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides(i) + begin(i)); + } + return x(real_indices); + }, + name, tag); } /*! @@ -678,16 +671,15 @@ inline Tensor strided_slice(const Tensor& x, const Array& begin, const out_shape.push_back(slice_size); } - return compute( - out_shape, - [&](const Array& indices) { - Array real_indices; - for (size_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); - } - return x(real_indices); - }, - name, tag); + return compute(out_shape, + [&](const Array& indices) { + Array real_indices; + for (size_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); + } + return x(real_indices); + }, + name, tag); } /*! @@ -754,13 +746,12 @@ inline Tensor take(const Tensor& a, const Tensor& indices, std::string mode = "c } if (mode == "clip") { - return compute( - out_shape, - [&](const Array& out_index) { - auto idx = tvm::min(tvm::max(0, indices(out_index)), a_size - 1); - return a(UnravelIndex(idx, a_shape)); - }, - name, tag); + return compute(out_shape, + [&](const Array& out_index) { + auto idx = tvm::min(tvm::max(0, indices(out_index)), a_size - 1); + return a(UnravelIndex(idx, a_shape)); + }, + name, tag); } else if (mode == "fast") { LOG(WARNING) << "Fast mode segfaults when there are out-of-bounds indices. " "Make sure input indices are in bound"; @@ -769,13 +760,12 @@ inline Tensor take(const Tensor& a, const Tensor& indices, std::string mode = "c [&](const Array& out_index) { return a(UnravelIndex(indices(out_index), a_shape)); }, name, tag); } else { // mode == "wrap" - return compute( - out_shape, - [&](const Array& out_index) { - auto idx = truncmod(truncmod(indices(out_index), a_size) + a_size, a_size); - return a(UnravelIndex(idx, a_shape)); - }, - name, tag); + return compute(out_shape, + [&](const Array& out_index) { + auto idx = truncmod(truncmod(indices(out_index), a_size) + a_size, a_size); + return a(UnravelIndex(idx, a_shape)); + }, + name, tag); } } @@ -799,19 +789,18 @@ inline Tensor sequence_mask(const Tensor& data, const Tensor& valid_length, doub auto length_dim = data->shape[axis]; auto batch_dim = data->shape[1 - axis]; Array out_shape = data->shape; - Tensor out = compute( - out_shape, - [&](const Array& out_index) { - Array len_index; - auto tid = out_index[axis]; - auto bid = out_index[1 - axis]; - len_index.push_back(bid); - PrimExpr ret = - tvm::if_then_else(tvm::cast(valid_length->dtype, tid) >= valid_length(len_index), - tvm::tir::make_const(data->dtype, mask_value), data(out_index)); - return ret; - }, - name, tag); + Tensor out = compute(out_shape, + [&](const Array& out_index) { + Array len_index; + auto tid = out_index[axis]; + auto bid = out_index[1 - axis]; + len_index.push_back(bid); + PrimExpr ret = tvm::if_then_else( + tvm::cast(valid_length->dtype, tid) >= valid_length(len_index), + tvm::tir::make_const(data->dtype, mask_value), data(out_index)); + return ret; + }, + name, tag); return out; } @@ -849,66 +838,64 @@ inline Tensor take(const Tensor& a, const Tensor& indices, int axis, std::string } } if (mode == "clip") { - return compute( - out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { - indices_position.push_back(out_index[j]); - } - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(out_index[j]); - } - auto idx = tvm::min(tvm::max(0, indices(indices_position)), axis_dim - 1); - real_indices.push_back(idx); - for (size_t j = axis + indices_len; j < out_index.size(); ++j) { - real_indices.push_back(out_index[j]); - } - return a(real_indices); - }, - name, tag); + return compute(out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { + indices_position.push_back(out_index[j]); + } + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(out_index[j]); + } + auto idx = tvm::min(tvm::max(0, indices(indices_position)), axis_dim - 1); + real_indices.push_back(idx); + for (size_t j = axis + indices_len; j < out_index.size(); ++j) { + real_indices.push_back(out_index[j]); + } + return a(real_indices); + }, + name, tag); } else if (mode == "fast") { LOG(WARNING) << "Fast mode segfaults when there are out-of-bounds indices. " "Make sure input indices are in bound"; - return compute( - out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { - indices_position.push_back(out_index[j]); - } - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(out_index[j]); - } - real_indices.push_back(indices(indices_position)); - for (size_t j = axis + indices_len; j < out_index.size(); ++j) { - real_indices.push_back(out_index[j]); - } - return a(real_indices); - }, - name, tag); + return compute(out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { + indices_position.push_back(out_index[j]); + } + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(out_index[j]); + } + real_indices.push_back(indices(indices_position)); + for (size_t j = axis + indices_len; j < out_index.size(); ++j) { + real_indices.push_back(out_index[j]); + } + return a(real_indices); + }, + name, tag); } else { // mode == "wrap" - return compute( - out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { - indices_position.push_back(out_index[j]); - } - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(out_index[j]); - } - auto idx = truncmod(truncmod(indices(indices_position), axis_dim) + axis_dim, axis_dim); - real_indices.push_back(idx); - for (size_t j = axis + indices_len; j < out_index.size(); ++j) { - real_indices.push_back(out_index[j]); - } - return a(real_indices); - }, - name, tag); + return compute(out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { + indices_position.push_back(out_index[j]); + } + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(out_index[j]); + } + auto idx = truncmod(truncmod(indices(indices_position), axis_dim) + axis_dim, + axis_dim); + real_indices.push_back(idx); + for (size_t j = axis + indices_len; j < out_index.size(); ++j) { + real_indices.push_back(out_index[j]); + } + return a(real_indices); + }, + name, tag); } } @@ -984,20 +971,19 @@ inline Tensor repeat(const Tensor& x, int repeats, int axis, std::string name = new_shape.push_back(x->shape[i]); } - return compute( - new_shape, - [&](const Array& indices) { - Array idx; - for (size_t i = 0; i < static_cast(axis); ++i) { - idx.push_back(indices[i]); - } - idx.push_back(indexdiv(indices[axis], repeats)); - for (size_t i = axis + 1; i < indices.size(); ++i) { - idx.push_back(indices[i]); - } - return x(idx); - }, - name, tag); + return compute(new_shape, + [&](const Array& indices) { + Array idx; + for (size_t i = 0; i < static_cast(axis); ++i) { + idx.push_back(indices[i]); + } + idx.push_back(indexdiv(indices[axis], repeats)); + for (size_t i = axis + 1; i < indices.size(); ++i) { + idx.push_back(indices[i]); + } + return x(idx); + }, + name, tag); } /*! @@ -1035,22 +1021,22 @@ inline Tensor tile(const Tensor& x, Array reps, std::string name = "T_t for (size_t i = 0; i < tdim; ++i) new_shape.push_back(data_shape[i] * reps_shape[i]); if (is_empty_shape(new_shape)) { - return compute( - new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, name, tag); + return compute(new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, + name, tag); } else { - return compute( - new_shape, - [&](const Array& indices) { - Array idx; - if (ndim >= rdim) { - for (size_t i = 0; i < ndim; ++i) idx.push_back(indexmod(indices[i], x->shape[i])); - } else { - for (size_t i = 0; i < ndim; ++i) - idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); - } - return x(idx); - }, - name, tag); + return compute(new_shape, + [&](const Array& indices) { + Array idx; + if (ndim >= rdim) { + for (size_t i = 0; i < ndim; ++i) + idx.push_back(indexmod(indices[i], x->shape[i])); + } else { + for (size_t i = 0; i < ndim; ++i) + idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); + } + return x(idx); + }, + name, tag); } } @@ -1069,25 +1055,24 @@ inline Tensor dyn_tile(const Tensor& x, Array new_shape, size_t rdim, std::string name = "T_tile", std::string tag = kBroadcast) { size_t ndim = x->shape.size(); if (is_empty_shape(new_shape)) { - return compute( - new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, name, tag); + return compute(new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, + name, tag); } else { - return compute( - new_shape, - [&](const Array& indices) { - Array idx; - if (ndim >= rdim) { - for (size_t i = 0; i < ndim; ++i) { - idx.push_back(indexmod(indices[i], x->shape[i])); - } - } else { - for (size_t i = 0; i < ndim; ++i) { - idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); - } - } - return x(idx); - }, - name, tag); + return compute(new_shape, + [&](const Array& indices) { + Array idx; + if (ndim >= rdim) { + for (size_t i = 0; i < ndim; ++i) { + idx.push_back(indexmod(indices[i], x->shape[i])); + } + } else { + for (size_t i = 0; i < ndim; ++i) { + idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); + } + } + return x(idx); + }, + name, tag); } } @@ -1119,24 +1104,23 @@ inline Tensor gather(const Tensor& data, int axis, const Tensor& indices, out_shape.push_back(indices->shape[i]); } - return compute( - out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t i = 0; i < ndim_i; ++i) { - indices_position.push_back(out_index[i]); - } - Array real_indices; - for (size_t i = 0; i < ndim_i; ++i) { - if (i == (size_t)axis) { - real_indices.push_back(indices(indices_position)); - } else { - real_indices.push_back(indices_position[i]); - } - } - return data(real_indices); - }, - name, tag); + return compute(out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t i = 0; i < ndim_i; ++i) { + indices_position.push_back(out_index[i]); + } + Array real_indices; + for (size_t i = 0; i < ndim_i; ++i) { + if (i == (size_t)axis) { + real_indices.push_back(indices(indices_position)); + } else { + real_indices.push_back(indices_position[i]); + } + } + return data(real_indices); + }, + name, tag); } /*! @@ -1349,18 +1333,38 @@ inline Array meshgrid(const Array& inputs, const std::string& in } Array result; for (size_t i = 0; i < inputs.size(); ++i) { - result.push_back(compute( - out_shape, - [&](const Array& indices) { - const int src_index = (cartesian_indexing && i < 2) ? 1 - i : i; - Array real_indices = {indices[src_index]}; - return inputs[i](real_indices); - }, - name, tag)); + result.push_back(compute(out_shape, + [&](const Array& indices) { + const int src_index = (cartesian_indexing && i < 2) ? 1 - i : i; + Array real_indices = {indices[src_index]}; + return inputs[i](real_indices); + }, + name, tag)); } return result; } +inline Tensor add2(const Tensor& data1, const Tensor& data2, std::string name = "T_add2", + std::string tag = kInjective) { + // Array out_shape; + // for (size_t i = 0; i < inputs[0]->shape.size(); ++i) { + // out_shape.push_back(inputs[0]->shape[i]); + // } + // Array result; + // for (size_t i = 0; i < inputs.size(); ++i) { + // result.push_back(compute(out_shape, + // [&](const Array& indices) { + // const int src_index = (cartesian_indexing && i < 2) ? 1 - i : i; + // Array real_indices = {indices[src_index]}; + // return inputs[i](real_indices); + // }, + // name, tag)); + // } + // return result; + return compute(data1->shape, + [&](const Array& i) { return (data1(i) + data2(i) + data1(i) + data2(i)); }, + name, tag); +} /*! * \brief Transform the layout according to \p src_layout and \p dst_layout * \param src the source input. @@ -1390,14 +1394,13 @@ inline Tensor layout_transform(const Tensor& src, const std::string& src_layout, Array dst_shape = layout_converter.ForwardShape(src->shape); - return compute( - dst_shape, - [&](const Array& dst_indices) { - Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); - Array src_indices = layout_converter.BackwardIndex(dst_indices_expr); - return src(src_indices); - }, - name, tag); + return compute(dst_shape, + [&](const Array& dst_indices) { + Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); + Array src_indices = layout_converter.BackwardIndex(dst_indices_expr); + return src(src_indices); + }, + name, tag); } /*! @@ -1412,17 +1415,16 @@ inline Tensor shape(const Tensor& src, DataType dtype, const std::string name = const std::string tag = kInjective) { int ndim = static_cast(src->shape.size()); Array out_shape{ndim}; - return compute( - out_shape, - [&](const Array& indices) { - auto idx = indices[0]; - PrimExpr ret = 0; - for (int i = 0; i < ndim; ++i) { - ret = tvm::if_then_else(idx == i, src->shape[i], ret); - } - return tvm::cast(dtype, ret); - }, - name, tag); + return compute(out_shape, + [&](const Array& indices) { + auto idx = indices[0]; + PrimExpr ret = 0; + for (int i = 0; i < ndim; ++i) { + ret = tvm::if_then_else(idx == i, src->shape[i], ret); + } + return tvm::cast(dtype, ret); + }, + name, tag); } /*! @@ -1438,16 +1440,15 @@ inline Tensor ndarray_size(const Tensor& src, const DataType& dtype, const std::string& tag = kInjective) { int ndim = static_cast(src->shape.size()); Array out_ndarray_size = {}; - return compute( - out_ndarray_size, - [&](const Array& indices) { - PrimExpr ret = 1; - for (int i = 0; i < ndim; ++i) { - ret *= src->shape[i]; - } - return tvm::cast(dtype, ret); - }, - name, tag); + return compute(out_ndarray_size, + [&](const Array& indices) { + PrimExpr ret = 1; + for (int i = 0; i < ndim; ++i) { + ret *= src->shape[i]; + } + return tvm::cast(dtype, ret); + }, + name, tag); } /*! @@ -1483,22 +1484,22 @@ inline Tensor one_hot(const Tensor& indices, const PrimExpr on_value, const Prim PrimExpr on_value_cast = cast(dtype, on_value); PrimExpr off_value_cast = cast(dtype, off_value); - return compute( - oshape, - [&](const Array& iter_vars) { - Array indices_indices; - for (size_t i = 0; i < iter_vars.size(); i++) { - if (static_cast(i) == true_axis) { - continue; - } - - indices_indices.push_back(iter_vars[i]); - } - - auto idx = iter_vars[true_axis]; - return tir::Select(indices(indices_indices) == idx, on_value_cast, off_value_cast); - }, - name, tag); + return compute(oshape, + [&](const Array& iter_vars) { + Array indices_indices; + for (size_t i = 0; i < iter_vars.size(); i++) { + if (static_cast(i) == true_axis) { + continue; + } + + indices_indices.push_back(iter_vars[i]); + } + + auto idx = iter_vars[true_axis]; + return tir::Select(indices(indices_indices) == idx, on_value_cast, + off_value_cast); + }, + name, tag); } /*! @@ -1525,29 +1526,29 @@ inline Tensor sparse_to_dense(const Tensor& sparse_indices, const Array& indices) { - PrimExpr ret = default_value; - if (0 == rank_sparse_indices) { - ret = if_then_else(indices[0] == sparse_indices[0], sparse_values[0], ret); - } else if (1 == rank_sparse_indices) { - for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { - ret = if_then_else(indices[0] == sparse_indices[j], sparse_values[j], ret); - } - } else { - for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { - PrimExpr aggregate_condition; - for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { - PrimExpr comparision = indices[k] == sparse_indices[j][k]; - aggregate_condition = 0 == k ? comparision : aggregate_condition && comparision; - } - ret = if_then_else(aggregate_condition, sparse_values[j], ret); - } - } - return ret; - }, - name, tag); + return compute(oshape, + [&](const Array& indices) { + PrimExpr ret = default_value; + if (0 == rank_sparse_indices) { + ret = if_then_else(indices[0] == sparse_indices[0], sparse_values[0], ret); + } else if (1 == rank_sparse_indices) { + for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { + ret = if_then_else(indices[0] == sparse_indices[j], sparse_values[j], ret); + } + } else { + for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { + PrimExpr aggregate_condition; + for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { + PrimExpr comparision = indices[k] == sparse_indices[j][k]; + aggregate_condition = + 0 == k ? comparision : aggregate_condition && comparision; + } + ret = if_then_else(aggregate_condition, sparse_values[j], ret); + } + } + return ret; + }, + name, tag); } /*! @@ -1668,25 +1669,24 @@ inline Tensor adv_index(const Tensor& data, const Array& indices, oshape.push_back(data->shape[i]); } - return compute( - oshape, - [&](const Array& iter_var) { - Array tensor_indices; - for (size_t i = 0; i < broadcast_shape.size(); ++i) { - tensor_indices.push_back(iter_var[i]); - } - - Array real_indices; - for (size_t i = 0; i < bindices.size(); ++i) { - real_indices.push_back(bindices[i](tensor_indices)); - } - for (size_t i = broadcast_shape.size(); i < iter_var.size(); ++i) { - real_indices.push_back(iter_var[i]); - } - - return data(real_indices); - }, - name, tag); + return compute(oshape, + [&](const Array& iter_var) { + Array tensor_indices; + for (size_t i = 0; i < broadcast_shape.size(); ++i) { + tensor_indices.push_back(iter_var[i]); + } + + Array real_indices; + for (size_t i = 0; i < bindices.size(); ++i) { + real_indices.push_back(bindices[i](tensor_indices)); + } + for (size_t i = broadcast_shape.size(); i < iter_var.size(); ++i) { + real_indices.push_back(iter_var[i]); + } + + return data(real_indices); + }, + name, tag); } } // namespace topi diff --git a/python/tvm/relay/build_module.py b/python/tvm/relay/build_module.py index 5dc6f81b97a2..6a059c96b0c1 100644 --- a/python/tvm/relay/build_module.py +++ b/python/tvm/relay/build_module.py @@ -244,6 +244,9 @@ def build(mod, target=None, target_host=None, params=None, mod_name="default"): # pylint: enable=line-too-long # fmt: on if not isinstance(mod, (IRModule, _function.Function)): + + # print(f"Mod : { mod.astext(show_meta_data=False)}") + # print(f"Type Mod: {type(mod)}") raise ValueError("Type of input parameter mod must be tvm.IRModule") if isinstance(mod, _function.Function): diff --git a/python/tvm/relay/expr.py b/python/tvm/relay/expr.py index 7b6e4b4ccf80..200079beb653 100644 --- a/python/tvm/relay/expr.py +++ b/python/tvm/relay/expr.py @@ -499,6 +499,9 @@ def const(value, dtype=None): - bool maps to "bool" - other using the same default rule as numpy. """ + if isinstance(value, tuple) and len(value) == 1: + value = value[0] + if isinstance(value, (_base.numeric_types, (bool, list))): value = _np.array(value, dtype=dtype) @@ -514,6 +517,10 @@ def const(value, dtype=None): value = _nd.array(value) if not isinstance(value, _nd.NDArray): + # import pdb + + # pdb.set_trace() + print(f"Value : {value}, Type: {type(value)}") raise ValueError("value has to be scalar or NDArray") return Constant(value) diff --git a/python/tvm/relay/frontend/common.py b/python/tvm/relay/frontend/common.py index ae51f2155402..7b6a7f1f1dcd 100644 --- a/python/tvm/relay/frontend/common.py +++ b/python/tvm/relay/frontend/common.py @@ -269,6 +269,11 @@ def get_relay_op(op_name): # try search op in various modules for candidate in (_op, _op.nn, _op.image, _op.vision, _op.contrib): op = getattr(candidate, op_name, None) + # if op_name == "floor_mod" and op is not None: + # import pdb + + # pdb.set_trace() + # print(op) if op is not None: break if not op: diff --git a/python/tvm/relay/frontend/pytorch.py b/python/tvm/relay/frontend/pytorch.py index 38478e27ff92..393a240210a0 100644 --- a/python/tvm/relay/frontend/pytorch.py +++ b/python/tvm/relay/frontend/pytorch.py @@ -136,6 +136,12 @@ def _is_quantized_tensor(data, prelude): def _elemwise(name): def _impl(inputs, input_types): data0, data1 = _pytorch_promote_types(inputs[:2], input_types[:2]) + + # if name == "floor_mod": + # x = get_relay_op(name)(data0, data1) + # import pdb + + # pdb.set_trace() return get_relay_op(name)(data0, data1) return _impl @@ -542,27 +548,49 @@ def _full_impl(data, fill_value, dtype): size = [] need_reshape = False new_shape = [] - for dim in data: - if isinstance(dim, _expr.Expr): - if isinstance(dim, _expr.Constant): - dim = int(dim.data.asnumpy()) + if isinstance(data, list): + for dim in data: + if isinstance(dim, _expr.Expr): + if isinstance(dim, _expr.Constant): + dim = int(dim.data.asnumpy()) + if isinstance(size, list): + size.append(dim) + new_shape.append(dim) + else: + dim, success = try_infer_value(dim, lambda ret: int(ret), lambda: 0) + new_shape.append(dim) + + if success: + if isinstance(size, list): + size.append(dim) + else: + size = None + need_reshape = True + else: if isinstance(size, list): size.append(dim) new_shape.append(dim) + else: + if isinstance(data, _expr.Expr): + if isinstance(data, _expr.Constant): + data = int(data.data.asnumpy()) + if isinstance(size, list): + size.append(data) + new_shape.append(data) else: - dim, success = try_infer_value(dim, lambda ret: int(ret), lambda: 0) - new_shape.append(dim) + data, success = try_infer_value(data, lambda ret: int(ret), lambda: 0) + new_shape.append(data) if success: if isinstance(size, list): - size.append(dim) + size.append(data) else: size = None need_reshape = True else: if isinstance(size, list): - size.append(dim) - new_shape.append(dim) + size.append(data) + new_shape.append(data) if size is None: tmp = [] @@ -585,11 +613,11 @@ def _impl(inputs, input_types): if not isinstance(data, (_expr.Expr, list, torch.Tensor, np.ndarray)): msg = "Data type %s could not be parsed in ones op" % (type(data)) raise AssertionError(msg) - if inputs[1] is not None: dtype = _convert_dtype_value(inputs[1]) else: dtype = default_dtype + return _full_impl(data, 1, dtype) return _impl @@ -1276,18 +1304,28 @@ def _impl_dynamic(inp, axis): return shape_dynamic def _impl(inputs, input_types): + # print(f"Inputs:{inputs[0]}, input_types {type(inputs[0])}") shape = _infer_shape(inputs[0], prelude.mod) axis = None if len(inputs) > 1: axis = int(inputs[1]) + # print(f"AXIS = {axis}") if any(map(lambda s: isinstance(s, tvm.tir.expr.Any), shape)): if axis is None or isinstance(shape[axis], tvm.tir.expr.Any): + # print("STUPID DYNAMIC") return _impl_dynamic(inputs[0], axis) if axis is not None: + # print("AXIS EXISTS") return _expr.const(shape[axis]) + # print(f"Shape:{shape}") return _expr.const(shape) + # except: + # return _expr.const(shape[0]) + # # import pdb + + # # pdb.set_trace() return _impl @@ -1759,6 +1797,13 @@ def _impl(inputs, input_types): return _impl +def _listify(): + def _impl(inputs, input_types): + return list(inputs[0]) + + return _impl + + def _none(): def _impl(inputs, input_types): return None @@ -2202,6 +2247,19 @@ def _impl(inputs, input_types): return _impl +def _remainder(): + def _impl(inputs, input_types): + lhs = inputs[0] + rhs = inputs[1] + return _op.subtract( + lhs, + _op.multiply(rhs, _op.floor_divide(lhs, rhs)), + ) + # return _op.floor_mod(inputs[0], inputs[1]) + + return _impl + + def _roi_align(prelude): def _impl(inputs, input_types): data = inputs[0] @@ -2736,6 +2794,9 @@ def _get_convert_map(prelude, default_dtype): "aten::bincount": _bincount(), "aten::scatter_add": _scatter_add(), "aten::__not__": _logical_not(), + "aten::remainder": _elemwise("floor_mod"), + # "aten::remainder": _remainder(), + "aten::list": _listify(), } return convert_map diff --git a/python/tvm/relay/loops.py b/python/tvm/relay/loops.py index 6c2ab2e23d72..ac7271e8093e 100644 --- a/python/tvm/relay/loops.py +++ b/python/tvm/relay/loops.py @@ -48,6 +48,7 @@ def while_loop(cond, loop_vars, loop_bodies): loop: relay.Expr The loop expression. """ + sb = ScopeBuilder() loop = _expr.Var("while_loop") fresh_vars = [] @@ -57,7 +58,12 @@ def while_loop(cond, loop_vars, loop_bodies): new_var = _expr.var(name, type_annotation=sb.type_of(loop_var)) fresh_vars.append(new_var) + # import pdb + + # pdb.set_trace() + print(f"Condition Fresh Vars = {cond(*fresh_vars)}") with sb.if_scope(cond(*fresh_vars)): + sb.ret(loop(*loop_bodies(*fresh_vars))) with sb.else_scope(): sb.ret(_expr.Tuple(fresh_vars)) diff --git a/python/tvm/relay/op/_tensor.py b/python/tvm/relay/op/_tensor.py index 0c875045032f..69f5b8edf9d5 100644 --- a/python/tvm/relay/op/_tensor.py +++ b/python/tvm/relay/op/_tensor.py @@ -270,6 +270,7 @@ def elemwise_shape_func(attrs, inputs, _): register_shape_func("fast_exp", False, elemwise_shape_func) register_shape_func("fast_tanh", False, elemwise_shape_func) register_shape_func("fast_erf", False, elemwise_shape_func) +register_shape_func("ceil", False, elemwise_shape_func) register_shape_func("floor", False, elemwise_shape_func) register_shape_func("log", False, elemwise_shape_func) register_shape_func("device_copy", False, elemwise_shape_func) diff --git a/python/tvm/relay/op/_transform.py b/python/tvm/relay/op/_transform.py index e1cb9e9d5711..093eee39fca5 100644 --- a/python/tvm/relay/op/_transform.py +++ b/python/tvm/relay/op/_transform.py @@ -63,6 +63,8 @@ _reg.register_injective_schedule("sparse_to_dense") _reg.register_injective_schedule("matrix_set_diag") _reg.register_injective_schedule("adv_index") +_reg.register_injective_schedule("add2") + # concatenate _reg.register_schedule("concatenate", strategy.schedule_concatenate) @@ -115,6 +117,15 @@ def compute_scatter_add(attrs, inputs, output_type): _reg.register_strategy("scatter_add", strategy.scatter_add_strategy) + +# @_reg.register_compute("add2") +# def compute_scatter_add(attrs, inputs, output_type): +# """Compute definition of scatter_add""" +# return [topi.add2(inputs[0], inputs[1])] + + +# _reg.register_schedule("add2", strategy.add2_strategy) + # scatter @_reg.register_compute("scatter_nd") def compute_scatter_nd(attrs, inputs, output_type): diff --git a/python/tvm/relay/op/contrib/tensorrt.py b/python/tvm/relay/op/contrib/tensorrt.py index acd4f4740b2d..ff1103ff72ec 100644 --- a/python/tvm/relay/op/contrib/tensorrt.py +++ b/python/tvm/relay/op/contrib/tensorrt.py @@ -625,7 +625,7 @@ def reshape_annotate_fn(expr): # pylint: disable=unused-variable if dynamic_reshape: # Make sure that the batch dim is unmodified. if int(new_shape[0]) < 0: - for shape_val, new_shape_val in enumerate(shape[1:], new_shape[1:]): + for shape_val, new_shape_val in zip(shape[1:], new_shape[1:]): if not ( isinstance(shape_val, int) and isinstance(new_shape_val, int) diff --git a/python/tvm/relay/op/strategy/generic.py b/python/tvm/relay/op/strategy/generic.py index ac9d3b157ec4..018cd4ae9801 100644 --- a/python/tvm/relay/op/strategy/generic.py +++ b/python/tvm/relay/op/strategy/generic.py @@ -1063,6 +1063,15 @@ def scatter_add_strategy(attrs, outs, out_type, target): return strategy +# @override_native_generic_func("add2_strategy") +# def add2_strategy(attrs, outs, out_type, target): +# strategy = _op.OpStrategy() +# strategy.add_implementation( +# wrap_compute_scatter(topi.add2), +# wrap_topi_schedule(topi.generic.schedule_add2), +# name="scatter_add.generic", +# ) +# return strategy # scatter_nd @override_native_generic_func("scatter_nd_strategy") def scatter_nd_strategy(attrs, inputs, out_type, target): diff --git a/python/tvm/relay/op/transform.py b/python/tvm/relay/op/transform.py index 7e7f9b299593..02efc8a870f4 100644 --- a/python/tvm/relay/op/transform.py +++ b/python/tvm/relay/op/transform.py @@ -1320,3 +1320,8 @@ def adv_index(inputs): Output tensor. """ return _make.adv_index(Tuple(inputs)) + + +def add2(data1, data2): + + return _make.add2(data1, data2) \ No newline at end of file diff --git a/python/tvm/topi/__init__.py b/python/tvm/topi/__init__.py index 97951d941f64..f5f3f5bf187e 100644 --- a/python/tvm/topi/__init__.py +++ b/python/tvm/topi/__init__.py @@ -39,6 +39,8 @@ from .sort import * from .scatter import * from .scatter_add import * + +# from .add2 import * from .argwhere import * from . import generic from . import nn diff --git a/python/tvm/topi/add2.py b/python/tvm/topi/add2.py new file mode 100644 index 000000000000..8c5685ea6874 --- /dev/null +++ b/python/tvm/topi/add2.py @@ -0,0 +1,32 @@ +# from tvm.te import hybrid + +# @hybrid.script +# def add_2_helper(data1, data2): +# out = output_tensor(data1.shape, data1.dtype) +# out = data1 + data2 +# return + + +# def add2(data1, data2): +# """Update data by adding values in updates at positions defined by indices + +# Parameters +# ---------- +# data : relay.Expr +# The input data to the operator. + +# indices : relay.Expr +# The index locations to update. + +# updates : relay.Expr +# The values to update. + +# axis : int +# The axis to scatter_add on + +# Returns +# ------- +# ret : relay.Expr +# The computed result. +# """ +# return data1 + data2 diff --git a/python/tvm/topi/generic/search.py b/python/tvm/topi/generic/search.py index b3c8772046fd..c848dabb209a 100644 --- a/python/tvm/topi/generic/search.py +++ b/python/tvm/topi/generic/search.py @@ -66,3 +66,17 @@ def schedule_scatter_add(outs): The computation schedule for the op. """ return _default_schedule(outs, False) + + +# def schedule_add2(outs): +# """Schedule for scatter_add operator. +# Parameters +# ---------- +# outs: Array of Tensor +# The computation graph description of scatter_add. +# Returns +# ------- +# s: Schedule +# The computation schedule for the op. +# """ +# return _default_schedule(outs, False) \ No newline at end of file diff --git a/python/tvm/topi/image/resize.py b/python/tvm/topi/image/resize.py index 103850de4923..ef6706ab094c 100644 --- a/python/tvm/topi/image/resize.py +++ b/python/tvm/topi/image/resize.py @@ -528,58 +528,58 @@ def _cast_output(value, data_dtype="float32", out_dtype=None): yfract = in_y - te.floor(in_y) # 1st row - p00 = _get_pixel( + p00 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint - 1, xint - 1, cc, inum, ic ) - p10 = _get_pixel( + p10 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint - 1, xint + 0, cc, inum, ic ) - p20 = _get_pixel( + p20 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint - 1, xint + 1, cc, inum, ic ) - p30 = _get_pixel( + p30 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint - 1, xint + 2, cc, inum, ic ) # 2nd row - p01 = _get_pixel( + p01 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 0, xint - 1, cc, inum, ic ) - p11 = _get_pixel( + p11 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 0, xint + 0, cc, inum, ic ) - p21 = _get_pixel( + p21 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 0, xint + 1, cc, inum, ic ) - p31 = _get_pixel( + p31 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 0, xint + 2, cc, inum, ic ) # 3rd row - p02 = _get_pixel( + p02 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 1, xint - 1, cc, inum, ic ) - p12 = _get_pixel( + p12 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 1, xint + 0, cc, inum, ic ) - p22 = _get_pixel( + p22 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 1, xint + 1, cc, inum, ic ) - p32 = _get_pixel( + p32 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 1, xint + 2, cc, inum, ic ) # 4th row - p03 = _get_pixel( + p03 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 2, xint - 1, cc, inum, ic ) - p13 = _get_pixel( + p13 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 2, xint + 0, cc, inum, ic ) - p23 = _get_pixel( + p23 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 2, xint + 1, cc, inum, ic ) - p33 = _get_pixel( + p33 = get_2d_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 2, xint + 2, cc, inum, ic ) @@ -711,7 +711,7 @@ def _bicubic(*indices): in_w, size[0], size[1], - layout, + layout=layout, coordinate_transformation_mode=coordinate_transformation_mode, out_dtype=out_dtype, ) diff --git a/python/tvm/topi/transform.py b/python/tvm/topi/transform.py index 6ddbc73e4666..62ab91a829e3 100644 --- a/python/tvm/topi/transform.py +++ b/python/tvm/topi/transform.py @@ -931,3 +931,22 @@ def adv_index(data, indices): Output tensor """ return cpp.adv_index(data, indices) + + +def add2(data1, data2): + """Numpy style indexing with tensors. + + Parameters + ---------- + data : tvm.te.Tensor + Input data. + + indices : A list of tvm.te.Tensor + Tensor index. + + Returns + ------- + result : tvm.te.Tensor + Output tensor + """ + return cpp.add2(data1, data2) diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index 5a13e9a5f923..e19e629c200c 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1339,12 +1339,11 @@ inline te::Tensor DynamicArange(const te::Tensor& start, const te::Tensor& stop, std::string name = "T_arange_dynamic", std::string tag = topi::kInjective) { tvm::PrimExpr num_elem = tvm::tir::Var("num_elem"); - return te::compute( - {num_elem}, - [&](const Array& indices) { - return tvm::cast(dtype, start[0] + step[0] * indices[0]); - }, - name, tag); + return te::compute({num_elem}, + [&](const Array& indices) { + return tvm::cast(dtype, start[0] + step[0] * indices[0]); + }, + name, tag); } Array ArangeCompute(const Attrs& attrs, const Array& inputs, @@ -1553,6 +1552,39 @@ RELAY_REGISTER_OP("meshgrid") .set_attr("FTVMCompute", MeshgridCompute) .set_attr("TOpPattern", kInjective); +bool Add2Rel(const Array& types, int num_inputs, const Attrs& raw_attrs, + const TypeReporter& reporter) { + // types: [data1, data2, result] + ICHECK_EQ(types.size(), 3); + reporter->Assign(types[1], types[0]); + return true; +} + +Array Add2Compute(const Attrs& attrs, const Array& inputs, + const Type& out_type) { + return {topi::add2(inputs[0], inputs[1])}; +} + +Expr MakeAdd2(Expr data1, Expr data2) { + static const Op& op = Op::Get("add2"); + return Call(op, {data1, data2}, Attrs(), {}); +} + +TVM_REGISTER_GLOBAL("relay.op._make.add2").set_body_typed(MakeAdd2); + +RELAY_REGISTER_OP("add2") + .describe(R"code(Return twice of normal addition of two tensors. + +)code" TVM_ADD_FILELINE) + .set_num_inputs(2) + .add_argument("data1", "Tensor", "The first tensor") + .add_argument("data2", "Tensor", "The second tensor") + .set_support_level(3) + .add_type_rel("Add2", Add2Rel) + .set_attr("TOpPattern", kInjective) + .set_attr("FTVMCompute", Add2Compute); +// .set_attr("FTVMCompute", Add2Compute) + // tile operator TVM_REGISTER_NODE_TYPE(TileAttrs); @@ -2404,16 +2436,16 @@ Array StridedSliceCompute(const Attrs& attrs, const Arrayvalue : 1))); } - return Array{te::compute( - out_shape, - [&](const Array& indices) { - Array real_indices; - for (size_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); - } - return input(real_indices); - }, - std::string{"T_strided_slice_dynamic"}, std::string{topi::kInjective})}; + return Array{ + te::compute(out_shape, + [&](const Array& indices) { + Array real_indices; + for (size_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); + } + return input(real_indices); + }, + std::string{"T_strided_slice_dynamic"}, std::string{topi::kInjective})}; } return Array{topi::strided_slice(inputs[0], begin, end, strides, param->slice_mode)}; } diff --git a/src/relay/transforms/type_infer.cc b/src/relay/transforms/type_infer.cc index 327b5d1e260a..2567adb40490 100644 --- a/src/relay/transforms/type_infer.cc +++ b/src/relay/transforms/type_infer.cc @@ -572,9 +572,7 @@ class TypeInferencer : private ExprFunctor, return FuncType(c->inputs, TypeCall(c->belong_to, types), td->type_vars, {}); } - void Solve() { - solver_.Solve(); - } + void Solve() { solver_.Solve(); } }; class TypeInferencer::Resolver : public MixedModeMutator, PatternMutator { diff --git a/tests/python/contrib/test_street_small.jpg b/tests/python/contrib/test_street_small.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8a9f15ee972790f8fa7c99293f2ffb872a2d72ed GIT binary patch literal 119244 zcmbTcWl$Vn)IK=4dvJGxYj6v}g9L(maCdhI?jAe@2<{Any9Svc!QGu12rz^J{`}sz zYWK^2*xl3Br@E_8-#SmfdPO>fPh4R@G=abef1ONf82X@_J0Kd5eXRu746jxtXBgd z{;T^)NUu(#yn6g<9sGJ7fJ}fwNY5vWO7zJBjlum5e|TC6I^+AM0b;FL2$R4Uj|dD* z5>hg93T75owzuqpLhpn{M8)Jj$SWu+DXVCI*3s3|H!!rcvbM3cvv=_H^7ird^ACvp z8WkNA8yBCRk(rg9^DQ^;M`>C4&x*>b>gJYKU>m5tqjPX*cw}^JeB#&K{KB8brR9~? zo!!0tgTtfalT+yR&EMO*d)ULj|8OAykp36etNwq)MevFX@xM7h`wtfaqVMaDM1YJ! z&xcAV`w7j$orr-y9R1Dvw34O)3`PMh2=Nz>Sxgcp!5wDkf6)F1+5bCW5&yrC{a?WT zZ(OSYY$Sx&$wMLlNCTeW-~a9=XD;P29Teb=hDjN}03sru7b&&|9-laNQX*UDgdDR{ zv;z+<9xEd|!%Ev!csT=bBVjpluZG`YHJ?*<#-eJqE{N;vnPaAfIgO68C)3N4hB5dS zQo8-tjPK#Z0mnvg>scV#LA=Ng#4r8u)NW&rY|-ea&1zWJj-03t@`ZwA&9JmH59}@8 z5bY|Tmn()}pyBhR8V!>F-G$2Os>r34Ym0?W+ylCuud}rD^sl6XP9QynvQSgm+qT(5 zQ%>kz;WjNgEaZ^ORJ_CMtnP!FwMV*GRA+0Gj7+k|rYF@f9g~Y(svN#s5YA3$f|OX^ z)mZbY5GaO=j)U$dOqVMYlMJtLi1$}Lft$y07)N*q6g&vKmGj(9iCb0pQBCgbT(VR< zgs`9x;vqBo`s_yhB?15PCilSMAuKS*VKY1_Qae|vgc3W1HA7$`b6TNwES4Ya2;BhW z##?}h&N^0OsGT&sMK+k}OdKk(7fP}+i5JULD!#|F8%P>$^X(G6GDGcwyOT3vracCT z--3Y7YoIx!@Aw`1(0b`%=|e+c=3z%y$K8syu#c3;BP4@5I$7adH^Pr1M*_RZv#Wl2 z8ZD|cOwht~RG-s6+e8;F51n4?`~AD=z^%bPS~D2doyG#tk%?eSv1Wb1Mb&Eb`eUDT z$0wGn$_Dn#a?)9P}uvi=5qzEtrPOMKe%-7Cx22L7_~H)iPVQ8lv7vyycyyG0hb5FJ8@Kwt&@9SJn% zeF&lIm_MR7rDRLn^+EGtSS9rp-Dpkf>1Xo?EG5C&7+wH(;ZU#H5mXX3V#GDdC~LKv zfjwF_iS7M6u+X$_ZM9Z)o{`sw%2C3o6el3Ct)-pDB|;*Xz`)X(m(4gLU_W~tiS~W4 zqrG5_oeM+4`AKwK3)$~o(-3nH1Tx7$AL(`C_p3`P9lUb>KUf$e5$e2jeQd@>M+2cZ z!Dt`<uiXUG*YsUS3C@YTs>{u~CGk$FD}q?mo@_qVhw!M7&RnS_wmh zN)r+#M9ljX;S1g`6izAp->mHszcyFrq=BY0B|kx1VVJQ??32kx3rF5=Av{kd64c+} z@693%N3NnA&c2^a)rbY@kH&E@{Ctqh)GfRLp#@C`1!4aTBgV6ta-8y;oH%<%&gCUt8Py zeUN-nnVUq*t)}(EfiI#PxP{8S1)6Xd3A6nH7hV+N!G?_uUOS|X_F2baPBRj|kN7^` zAEw0Q%m^TYdtYDHL373u8uVc^8dR_l9G@d454~+hP z$l$ij7IQ_Xb5meitsoaPe_PV z)5Q{q`c zNQY@SI@??AB)mN!%AVY|NnSPf7PgZX<3-}#`xYP&RRLtHB{*qiE=aA+{)UXYTb@9f zYdkS3e&*Wk1;94M%ieBTV7q=)G#^43vSMg*dP)+)34!wmM_(^_Y4a0N&yBN?>+}1@ z8Phzfi($sd!1x|5VJauLICvw^)Zs_prg^d$E3ypm#;Po7D0q^vEf%*P_R(ny3yzeUdOoM_hhIzw`i@X?m@sblW;^Tqt0AjJu8Bo9 zJ!zbZEu~IA3O_GMLvq$(2e*B=SZCjZPCo5pt}rK~WrR<>03>l6lDcsoEgd$IbA~#v zV!Oo_DV*~yobTh_apWqYO`!RBrl^Y&3-LBrq01H8JAUQUjIy6u`$uc61eu25;jmo6 zejW@=>=E?cHpKQ)_zlzo>oiniH2z#N#TaxVQj&bu{8)C&S#jI&J=8Ytb(P7#Q4$J{4kDWeQi2K3A{Dde=9GX{S_kEA z_QDfU%>CTFxB<3JwH`2b{p!>Eh0_sXJq%S<+Nw}&pkgT2NvdKd>L2x^?L?aXP-)AN z5Kp)*gtM0w8r0;lI`jPw6&bIhY~;&EM!L-71ErrNpP5Y%b1ST-{G^k3vt#euEr)-o z4_95QSGak?WO247V+;J*V0`V~Ju+SK*A}VQ>&vaYM_xKt7c_?|F^9<+D&V0Ws&YPQ z#%09fw!3wCE3tYs^v^cNId5}Nq*R^~k^WNL7IfQy1D)&tm$d2(zS^QXlICmpaR3XZ zYMfx{^5$G3IC4^QUn)3)J~>e;`A*Jr@`!POZ?i=Pa$zmaOKf2hyEHNwS{Y zldw>ndI8A302Uiq=Yp)gUjSIB&!w~O5R`s4@lWOY?3Wy=)TIu9wfKZ1QYaMcB&o=;YfT+JND=!~2?KJ=_u%O0Up z_7xUyys@rGxvzxfW9SM}fJ9+p*PnHHhOG1UMsjGKG->_0!;Xqw$9hz2;NB2A0ZHKS zyni|kLz`10Yr}NPkugmjR8sKb{o(x(MQsyhm2xV&Pb$WP4C^h~*=B+%lPF88?LvT5 zxdmDLuU+d{FsJ(%%~hTPw+PaFxyAFi8HuykwARs1{z=!ORp4Wlp(%3g0i!$_D_QDM z!E>1(-cWJ8p-bUj9nbJH=by_%Bh~SPv^XDn*?k_<6`^D47Sd6T$W6pWp<+VV+@eoU z8erZ!exXC&#`%H#>|UDjOsjMP6-56oUF}w?r+MMll7c}-i01 zIZ$!_v<<|S{pdElN1Hb7=@GKghvYo@AK%z78cLA-`b zTob>%dRV{#L4+w0XC2pJ&fVd-N`>>_S=ewxtpA>OtXP^dr;3$~wxr&1=&ri6wO60g z$QI!J;6NKMO&dhNd;Cdo+4VEmU`8LB zH}$E*TV*?uUlNn1F|9L+#mGS#F58j4Giud}C_%F?fEL}9^P38XObgU6#yA zG8D^*9sMS-2v)Jfa%k)`IW#5gV5t?u@Nl{wnL+n!B`5m0ri@fi)rh!*_$F(1kY+R* zBlA74{21D1x9ZSH7J@c7f1I^#&vXmQVc6CV0siG1q?&YOA59?NNUxx7 zKO)`_L7xR)5t!CW(awFS{l-1}6R~oSb^QfUY}jG1_Dw^n;xJ$=DtEMyQmxAeRd%;g z+Wvt=7cw>1zTD{->g)79@*tzEHmLpA{k3j=r=v$oT~NXeeY zq$94huZ!2a#ZH}#+rtiDYeGew>FzINR$qLno;H!Fu&pc;^G5Ns(mNf;|6Z-&so6OJ zomKodbpIVnvrFJKvyYat_Gt#*m_9gr&@4?Ap@%?`7*TumASq)BW=I=jG*#s*CnWD? zLrzyY`uGx@bxuVlkTTBV^M|+EhVdNkq|oME>JR8sR&A-!B#ts7ABr7T-&9XpeFz@T zkopSCfeH^60cLYP|Ea%JfPXhK(#x8piDW#W{qEwh=n~Z2hwY%71w&nt=*d0bTFyd!Wk*EWuigC5jjp zZ9c>d^^(VrBM(gqH2DX3D5#4b0pdp%q}YQ*A-fM0{*NE?-ALv*!va10;5GSwdd1Ij zMoJ)eDQ_^9IXL_HXvagv$|-pDaP5JYrM+Sypvpmp#oM(QO}}VXw(lX-p%l}dOTE;7 zIbFCN1Xw#K{d5aoX-_Fc8G|&LqjM1^ zb8%%?D{?pzvn6mQGgiw*c8a@dyZ$OnR~M5cG@(Vnjdpq7n4Hj%ECSauu=9Szs6NIq z5#*8+EF&sCmUt$I82bRsU{e2tlUSiez}-+n2cJ~GkSmpU0U^n0C7qAfQ*ryh-id}V zWB}gKvO@8)yXSY_wd~=M!W|)ZKdcB0lYEfV*Wu%7IpCRquDz4Ulda#O7F+nzad4qM zvo|9yba~_wZ^c*lMY4L=LgJ0wBt_`BMMIPugvr()e%}8+8}fYXQI|V)+SiO)v$OI3 zUK{E>Q(EF@v#V45asIZevUl}V`cK>jF{&Tym+B$~J-Up$t@ACYoXlc3N_7ww#3upS zP&Z|J;wawDZ3&OUuh8`dg4|TTv3rd3HPn%kzU-zZf0o8nC6xPj!-^abWxYMP=RqpGI}-(i4iARl4GW_ zLaV6uZv##WKP(crq8^rG3^|HM)>;p>8}b&K93RjAK0?UYF-jx9YBkuVL>1nSdDn*s z3kW58{`E}PG>bpM+z@-%0k2=SoUwZJd~xoq{)pZ^H;sOulfGO@oCt0&g^fZ4I{;w}2n zZ^oaYLsYTkSzTQyOg?5LUbOW17OoDNiG(~F%mFuavHUIC15>TsgcW8hA7oEu<%)fY z!OxROOjvBdV)iCLpUQ!akCtm3 zYCXS?5ERfX(^pPQCG^hYgnAkhxM}W=L92iLtZX`pJVtUOsaoaSo&G_Tzzze@t2n&4 zAe~)l{L^QWh1v!oNfh%U(b^SL>e08@m$ltv1ELQBRmcdDb&s58LukW~&CU;hrk+*3^b9JXd;gW)L;SPyEutm90e|MfD*rp5TP>i^@>YH z!NuN{`@O?qv192tL`|qXAH-SNP?#N^{oL`R}o1hw|l@n=f?2cfieS z_jFP#V*p>;7RT{my}e$eoG+qkjWM{7S?}@73*bOnGndNt@+diAY3j3m7vpNwio=_@ zO%6XVM`q=3oQc0rv4<}2Byg7*dw+++_bh>?7M!Jk-zXtMI z?}o6|7r>}$!d1e(rBS|7yvXL!ua1=QPC}gF1kETA0Et(ZX+J!h%gh|MYs6uOyJ4nY zojQOt$$`&HtmiJB6~xhCpNAIFD`>w(#QNt;XG<&8N^j1{U}jL3@E5Z+PB<_OfK$;R zN;Vw`lMu93FIAb@5QL{N^wJNyb)79~Z%Xk$|LQ{&ERGH+ypIlI&-VST=7OBMWtKxX zUe)M-uI)NAT!1>>FVc`5gah$0qYC-}Q3&@BXwrvO^SSMAEH{71@g=$DnZjkVwA)9> zdfN3mP+Z@7V><^dj-iM;Oz?8C=v5Wv!gLVkPK?&~DJ}%ihof<-BY?|;=pkK$P`|z5 zXA($uO)%3Zv$HR=myDX!auZ~9J~PI*x7FiN;vzy9mkZ(E(y1NgC1txj#XmH}PYGVr zEUMy@U@ZG|u-}&L;`cQ8@x>mE#+Ai5nJ>Zg+WwYV0h^fTDfNjPPkw|LhIyiO~qWp3SI zzi#=c<{af`ji$yZ!-gy>H~G7q{yj@ab@sMc2>mJVGPcj5p?>>gBe-1a_DhkfKT1~@ zBIV?V3~Uc{kC>_-Ydfw9i_^2+Lg_;fhQP_5tP3 z!FRT8ela_W@vi$F9jy@aG*w01~F{+lGR=XFRz0LXgpSSisl_4MWrO zaz|Hd-s-hT(4+kvhk*qr@%S=yMqQRwgls3tUvJoe(<_wZ)Q~mWJK^M zy8nxgGSbmTa)3F+16h^Gj2Q3BYFi;r)&Ac-DZA+n$K}VGJMG>$X>(`v^irFc+UT`J zl*iy3+iRiiV#LdfbGYrUUsEeBJ_$msM5gwWzFHzU+AEe=t8ac#wVR09fR-5 zE;BO5WwTvj=#u_p+IMFCBi$gFYN`E_yHg$(Z#tV(b>qxunr%yRnk*>z0%2!^KQ!)N za%t5FSr&7q!c@<2MT+Uei1&fZADwQnFj=3sn~iq&*G?5^rs*dQ%VziG|!{GkqIXA7hIbkb{d6gPBn~a zp$!DE;vi;d(%)aFA2VE-Xzvt3b-~yg^dkig;jT`9xm=WQP2MvnbCTxS>QHNV!j@X? zo|$Kbz&I>5j+}acsYwJK*KfFZ$X3_5BA1RwMo-udomKozgNWNSN5Ej(?G0%~$WkPi z^WS?!?^eB=1KG#sQ#_h>PL000w_Og*wZHP}OKOb$NnQZ9c5^#R6a3uo?l;0ji2Gry zSjY82az`diV467Iw6qlIj)DDa_jyQt#jq(iH=iGbXDQh2R2mp`Q{_gCvFX+~?JN#Ypu568cg z+ADBBJJWG7i3n=iLz0Luo#9|gduqO6&45EUq}FOCAImPAc3!{PTBHebELMge;z z^~4uG(^;lINRi*Bqrd5?9b=Lbg%_%Ve%NcfYulC%;t#G1N+DDvFf23wxrdyeVdkAu zv$N`!Y0a`Ebl#QSD*$JN1=sk|+>iF+=3ykNJj<>0GqT7a5nFAiwZ4{-?~V?;zFFY_ z!BV*wX3F-RAyqpT)%vHx@3$s_?|-*ePv^KtrD9(&`|eXw`HTczRJNKq+Qvy~joGg% zqWzS29^!6&yHG0HQErpJ2`(uLRetYYdEt94;F3ddVL9Ov;?n)+1%S4`wt9NPd|qOy zmBc8@ib<`^+F%|!!=AOHh2WX&DA#YOp0mqCa*~JJ6~tA?!?O+Q>R>{^)?la+WetyF z+W)7wKr2rERxl8_nDWL9MT$<@DMYz7P&fq%C1$M-e0^K-JYC$v6Zy<~d}Oj%zshC_ zT0VI*Vo=6)owi_(B8s*Upr;DOecU!1dI5ZWW`p=-Kv9oO7QCssO2VCg5X#|FTWeZt z5@+&ryDe|Kk6~>~C-jm)+8PiN2F)M|b|gC{T7l7NMY@TIMvQa?E@QH5^>0mpCC93Y zAu%m*DVWB0KO?1-p|(6P1f z5k2PjO9btLgP5vkoxRSZi=iFyx32{UIDLsm)sB)t)M|N8`vA50QjuTIT@(zoJGoL8 zK`ALkPBf{%@}Y}JbTC3nyP&AYVefre>CeQs3qMX-#jkHcaH7}sHi-tawUtzg5N01( zDV73z{A0x@zeJ8HY-PDb9b7ZhZTl$i_s>76usB|Dnq)cn42NdRDfem|W#As!yPi>p z$XFsTCNaqDt(LC}OYxTs7=b=%A5jPy%uXV}_20*^7r_}Hrb zWg&OVE(?^nzY7RCn4UVAaO`=1(maF&$Vhin@Eg1)b zn)-XzBew&NDxUrW6rqqUTr1=6U~NOyIML5*7<)n-F~q|?=AYRp3SuvwWPQ*ptoxaT zUEJGg55BjUkgm{p1{#AJJ2H^eXCvy}J3YbsY~B^K#ODj9KeJ1rdb@bB!t$)>y^5!q2Y-&Vqv_pA&qvO1%@u%rbpUEOha>l$%E(sooE?qW&=sB;X zGk`w-S2E7hME1EO(yX-MUD8eNQke3&^H( zb;?4cLMZTD+K}EZxa%ceq=ypx-fnMC>Q>usH=5S`fJM_Klf5hJ!OMudQe5Y{x{|G05jb?3?onJL1`SX6g0aA|DRRzoqxQ<{NyiqlKpqImX}#y z*hTj9yhj5a`UXt(FT;N-j=01-Gxb1;-+lhdnzSfadtk#vV+-)HC`?5=R zrIEBka;lUq!}#vbyxIksAlrklpL-bB@yKkI=4Ld66&jNeLc6v#E|>)=?1(iaI82BP z1c=Yb@h)F;&!%MV|81yv0oZIs>(agn!G|RbEZt8d3dhRjfRa9?#&XR729Pe(<`dIQ zCG5-9wF1rq1ggkXa8>0yqIO-ALtBd@z0paB|4otf2E8|O{mWtV%NLCo65u}Vy@e-5 z5BGZkMAcRJ$?f`-6Ul~T&~l3#xl!wqlUd9$xqNrOm~o`M1JCWwdl!-j$sZ1SkDG3f zJqulMK-N^3;5wy23XUCGCj6_PkS*;M2}`t;=n5{rAfQn{vAdS6nAWjvpMcW>*C$=u z;k&Pe(Q?BfDCyXp>cNg+0$9jO>Pt1oMpJTSAGhCQnXSmSrx|9w*AwGjX0tjSelea( zsW9PiDPh)WF`tA-kGM^b^j+O72~Prz1fv7T$qmjFKARdj|e2-+eHg?phyzNY`Ebxoz!JxQ5F&a7#`T zU8+H>>;_Rpi!~`t7NErFR>d6cra^2pY2Ut>7=7bS%$-mlEAf?LQq(}olQwo+0aLnD zB~<{-2)LGxrGR#5#~rg6&);SSYTo;{9SY7^nFO5V#-0@CxJ;Q_W+dX)=yWUY8E|zQ zc)B{4G7sA|`s?ABYjPx7BCcZ#=U3afgWzN*tGrUL zfUCI0u`x01B9cFxOl_bZS*jNKT@+Td6$Y)zQv`A}Z^@#o{gLA*o4wi#486dPcje{W06mip{p4@d0ZSWwE%zf0M0RF~UN{zYyG9$f zYDHLe%0HvYUjW`b*~8=r*!%eUVMa{QjJ*5Pg=EweAut0$5lc|^!am${#|$S(7(#qB zbcMUgl-))&hArjNBKW9%!MDxc$Ov$c2tXOzV1na8a*SFbW3dwCyM@kEx;`{~s`o@ZgfHHS;-grHtap%|`+-dzcW_X# z2zF9pR64D+$l*F3IrtKGTIX?2kd*dHx@bVOIl9Pry;EKTaA?7`20q(nwOu{pqgW9- zxB~gv0y!JqB!Fs++&5xE#@ zle8%C|B|V-I)QEVWWw>N*&m&dUotK$na-}8Y;@!ZXa@+N5j9xJ7wT%9C7qX#D-udD zjk5#FXT2mcK0pysi^HRA= z#p7Y^U5Ia=i?PPuN(zs;Is6+ft!BzI<~_M52rgpe5DaULMPOD*3~jjzzXGk|#TY5& zYc@U=cro1FHH|k-mmvGMIC~1PSH73UdxM`fiGOozy=r7Tw1pQ!?r2!PQB(>sQ;b+e zr+@8@LiXKaHcKK>JtO$#*f>!U)_w6?%f3GVA7@>uw&S~LEZkhL1|WdNYhT!L5c;IQ+gs6^(#ha0hlxFA+nD6e(pz5=}(80zMJ_@fwG1`?DJt(#hL2$3${M`nbVm^(_ z#i+2Q2)oQ!iO~MSoh#+130J1x8$~NQl$Lp>Y~}rEWy^Gr;-D+n zZ+GO9F&~%4%v$S<{-umy!Q@{6=@FOSnXIf_^!?;l*LjabI~#+cgB;k*%C1r$+4gaj z&tOYK(!k;bp%iSm@q)A9G@)um(ZB5x-dr70r&@HIvm!+sxM+h0m$J?Sjj7OM?QqAu zS0S#o2FKKc`W|QQ_+>D{e!x;RT(Kkjapd3EYn7P$F%&>QHLAE*e?3?~WptA+@m*cr zhy^(~i#R>xhWvPD_?p*ENAE!|*Y9l5ZP~HKNY4G057DJ%mF}V61W5Pr-0BI0N+eCK zF0&^2>_Ti z?P{?nW+3~*edp1}8X>4{Ev=w<4EWYWnK1n>5!?uIiTfElc83M>n-*`1ToigeLuGlyBzB zH}Q|I6l4I!_c79j_NPWFW8kLBjeAgHa5{9%*A+%QbQ ze!3DFQqOx3-*DNqSVW-cPXkzl8q2lh`G)t!|E4i3s9&5*CFP!lCsf0Opg$XGa7=Eo z{FpU}jL-Vz5m6qk4jG1d)WQbyH4i?MkPT7Y^`VDI!>|TvsOO!#eLaIKarj~DV->Lk zi%uHYn{rBXNol-_DVMzio<8sS@_qub-?3u~S$cI9`Z*~uVsEuL)1*PAm6s$u@sb2J z(EVH-b$;`dgBBXTWTRg&cPfNRF`0blW>jAD&$?y+c(R8d< zV1ba*30ShOhY1+Z)KmtE&kBxT3UPUe*W}VAh51iiVrVK|C6~Sc>P!Q7Y2U!MhTl!) zEydbe=`am4i%^Anxqq?`A#3l(&rVk4XQL}Z7=Hnr%_r6dn9f0$Ph6h`PF5YBiih$T zCqOChY?td^00`tl36*dSSWJBfuW`a>>(Mp+rsQCCjXkY1DeQ&6m71$73W<~bPN_Jb z6Nbd1Ca$ksDeY;!QAO)Aj~LM_Pgez4;~L`H7rlR66#4!#Xn_^Iy@MF^qUKjgF6Eiad%ctKFN>mi`vH2OGl8?duLgI!>7ujCw zx!ZZNmiR#u(VB`=7~6@D?(YMRIaE9DXa~S68LE&*)g<+07aDzU2?1X6ptMm8Dz@{Q z_NM7To#=e$@{>a>kx!pK=@5Sg@B+pf_bPx=tDAqEtlP6S%grc#Ja_{L{#NE28JxRh zZ$u^pVU(Ezv|nj;uN<$%+lHCy&sB!TbG94v6_|XjUc^^*f@RqT^>Vdof>s6L#ZHKx z3p=vX>cB{d^vJsq@^wE5QeP&^w>DDZ^JL1Q1ohp!qA`QU$Q_|cQIa}?wCC@FTO0D<-N1`De#obNAK0$1z__?+wz z+!{EeO2VRbRuYp&nmH2vIX7nldGJq2K)*{67w2Z*8;(|9^FP>)>lY5-o?tbO1Hwua zyWvc|ZA;x`ETGR)Ud<)@*B-J=w`WEwKR(%0bypnaj0B2U!;*H1<7Y5F=HV1XOY@>8 zkg6;j-|kV$oZ?aA1`FTWJXQDN!japdiqX$Je!uNn)*YIR4JZqxd<;q*{c;YjOU(gn zl2Z57XVcHjEBSSv!%H~9SZ{wjefXqCaxkPOz(Id5KqGUmAUZK-xy=(JhBn;8W3VXU z*l_kvH-xaYEpKK?HXEU9N;QfzXt|&Xvz9b1oN>PZdN3C-p{i4nqdi-n5%KXbgg<1_ z3_FAYmae9!4}03FNl+5<3fQnVtILpVXev74y z$jHGZ&%LdcwSf?FSWB&+#$fN8bVm`MOXoxok!q}kGAlvhpiHkN0pZi{u1~#Zl5VE> zkcBPm7l7ULyLq_|z711*k8v`BRsjy2YHrE{02{J^%gi}Zf2I4-ssq=?$l4_XcWnt} z9d|I!Q$?`7J54!L^Pv`E^``##BN`)D3-*TpzXGjQGeAxCsWpY9`@@y;P!CImm)3tL;eR8 zg9)o^r-@Ya{Y1~P$`g=;GjYORar8SHk>-||Cx#U+Yv(lMA);D#_3H%M0>DF@G&f*| zZ^YM}eXd0Jmgia&s#&F*RnoBxqusebAAp)|U-XSUf}+rYk`vgov5|0q%(aJ zL~@IRM~FNS^rIb~6}wi$-jAuaKzt(@mAsxr>nWyWLVkx^+wEh{ZL+7TK2-QJSant)=sZ>hP1#Z+;0$Iadw2HN8uCaJ@td2Zg4t*$AdkyRc-dc#XjbJAB= zQA1mXSK?aPdYzYwZnK$bhCoZn<8{_MTy6f!#+0+Gy>*GFC8(CddNlcnxYMy0pqyF?MVJndXnhMPhdoREL|IC?^DeW4}GB1-qm}6+4 zbAGKH%%A|NCcAbv3aeGdNio|22h>*Avt-k2Cv}*t%{PmBifLu~pIe^w5lTC8QE=qv zK%IeSL%<>q94S92kT`0C=UmI@cnPWwe#;oafvr{t(1Otl?v+~U# z1``OWSJKU~si(O+{`B(gc9Kl``^q#YGFDnPGL#Uf2sB9c1QW&aGs-LR=e?loimx)1 zCx%mk0VH-=A;FLJ^hGxw%aSYu#(+B)VTERAb?i@$*xBe;tH3NiXLRKlVq2&}HG7zK zaq4AC@f6#)U13T$pJChwy^j(cFH3L}%T44nbqn>jIjXRX1TS_!j+$aUfr3ziS@LV( zHjtEj%Ek@!_ZVoKzk@?*{t!LC(!Pm9o$)A)>^p&%g|2S`%A1Y3bIsdo-Uuikv@U;? zI3o#~uAdpO|=#+5N(3h1F`ar6kHBU+3^?;8R^5W!uS!UjZ3w7 z4^ud<4-^~(O>{{5h>p#c@0FpxLwyl-7PYFDZk^FJl5h?e>+u_N(1Y-sRU@qVvjy|T zt(1maQ7N3$z2PGAwv3@BO2u}P&YGtN%DPpC?`u(1-zJWl&gOuj*3(F4zn_uq^h_Dd zxvwY!J1iLOqKwRwrGpfzCXR}LQY8?ErCr~czZfn-YFuquylw4EfB&Q+_gw!#P_|mxyrQQnt51F0)l${ZTL~T%&|xk1#Y3z4Ld zduDpEEs3R}fwhLiHx)CXo2-nVJ;Y}27GT9I*||hp0h`=KjZU)ov2JHdnye>Y(h_UD z#hxCJ_|AQdUV)e06$3V80x^yD!XbU%xj;E>Yc#D!LTAuE4roC*m-(ThVZrXHbtrxz zuE>$$ad6K^uW$7Nh$*)DsN4jRimUsLw^yuJmvp1U1r zlMi~)n`Y+`)Xt>ui`%|A<`cRh`E<$?EsY;Sqyfbo8!V#Ec~tz=*ZEoX4z&5pUt*GO zYn-|z&s|x3y)AvC`0dt!#C*$lR!=J4zm*iD#|5pd*LAtmQ|4T)@%S_XG#V1ufeXwH z`v~&=BIE$N7l25$kLVv2$Et6f+^BpQXD2GF&L~t0GeHv(`As7Hyl5guO2OmEl0{JdkOK_4`{Pla=@5@U=#K`d=#BZ#TVDh@Fm|n`(-g&{tDga8|7gnr`u~TWo zr9Z9WO*?@NK-nn1WordTDuNFU5xh}iNQbW3&1Lj6z|C9dHw;U9r4tmh7 z7=}poHp@MLq0SpIA2N%yf1zSG52czz zApoR*OCdCTZe5}^@5-$dtsTnUNGq@A8rFtmdG&sK!P&dH3f^G*kw38Svwtk#!gMcE z56$u_C$8qr$XHHg;ILx5C#F}_B@Z>_`!_f8N{v$ejaz_O#Ky>%$P!?cHdd##B`27l zm5Y~s9kjgeKBCfOoRQw%zn@Wjpv^s~qeq6xmo}nGLqKUq71FwzrjeDk1EL3s7p@$m z8D`a4Xs1g)Tjps3=#Cmda!T);_n5Y+%r@f)&` zTm-@gehm0J(v~c4a!AoZ)PbQE4#!-(-_``koAv**_ zjZ}d~%OxyKeKNwab;4@ZI3 zwX=V9Ukwxbd99PBfq?u*<@Zzvo{?#(%Vd93u;5Iu**pns`=nO{1bG?@`-{4eTza66 z^V%zFN|!w0<>TnSAhXtc{Dp_D?|(mZAvUSj-u;KtF#M8R=#^_r*p=%%NYth~{d^7<1DQoeMl{!*j7j~O>BB8aHo_fdt5u$+KnratiXPM9#i8eh4TcIz}W6GstKIt zmVMikH>b#j+)!>Z)2P}*iWg25R$|S;PQ30x+`vThC#kHx z5Glw}ED%gU8BG3#4_wAzs_ae(tocre^Mg( zbq$QN9_d*RJ6UnI-T>=gx033TH5|c5_y)rZtZ?V=I$^#ge&D;FMIkf7)9iP7R-v|p8outW z@7DCoI1)2{C#Hd$7>N%;$e>#T7mRlWkR>>y+bS$ZZT_OZ_8?nhW8f}@t1qa;7!eUV zgK(A@fBM)k(0VWn-lb?D~N@* zT&TJ~>IM1umFd9fcY5YT6p@BzrZA{+txoZ1Q+ixi;3_arCS~H{CsEo`Kb;Uc4~nAWa1(&Ng=yi*0<$iebVv_QoRnMM<(7XWUnP(1P$eX&Zr z4r9h|byUCv7%BepwWAxss5fsk@Jv$dXwKH9zK>hr8kFm}; zV{8*IH>oEjyql5X`_s;2P<2h1!dC8s6Y5D((H`3~E95Zqm2^G14aWQEg5A;$x>x@i zbSX1Pl(tMo8&b;Ey4sllPb?s9H6z`1;|*R`_#S|#QNrw2#!#B`Jl=a@BabD zKsmokJNZ{JG%^--%0Y5R_vLo|n z8#u31@n7tp;4NELn_INAn$d4Aoz}}w@dPN40^e~%#KbC-fD17Ib>N!t&)Q$%j2{ei zgQ8f^JKD>69I!O^B3zRj?!zjm>^Le2#w*{!PXX;0BD{_~Txu#UyW8eR;tz};5OsTf zvO?A|uv4}uS#k$onDf)nfmC%kSH`yxM}MM3Z)DpNNUyhlMjNny+Ux0$VO!GZ)|!`w z)&MtSCkaSGmY0Q0UQ~5IUM-s>}ldlUk!X* z@b8Lj>{3}Y>+9sXW|%Hb&8k|aPi5aQ1miz2;N(}Wc*pj@@aKy>d3F0V>An)ZlU0hx zQM9ZY5H{sNi80ao=yID_h}3?vwEMN=;#BwX(N|QDyUPoPbE*0))RCn^@+;=+#j#IQ`;x_E0f{st^ak7--czTYmJx2!^ z!P;p30{A2G!@%%<&01%V{BNyk`U>h=g3chhy&WT!1BC|+_yBYr#~h6O&GGm4hwx91 zJ|f<0J`P*mMi!OCCM$BHKp65E{G@U^k-Xvrb|Bu>+-g# z;%3o7irRThgY2^ML=Tw&+RvP-f-`_QZAVe@{4qgkp?C|$`f|;492UX+4zXKOD5oR2E@a>_l7Gv9YF>)x?!{6p~C*CeI%xatL}FV~Ict&fOa1xd3Nh44^v z%3@A{f4%;5K%Audm`$hngIK*c8kOYIO3m|0E0Ryto=tj>?6=^kZ2mR)X8qJQ%~~a6 zj-i_e@)_p2T{px!Cc9}Z-ifBmi+K=8XDSEG9D#s(e3<-4*1cEYcBN}$U!kYiHlgOScf`tcD44p^gfd-j#-JzV@jFzAGS}c%h6T=pCE=Q6 z)gy@BtWK`6`7I$kK`H?__3C<%6v+?4pABB;2+-jaB@%$#{ zNZwy972REq<{5K=g57cJ$E9TBi%lw(TD_jy-?fP6%cTeGaLQb*tMh4P=Klc5-m&;4 z@cz$IjkQ5_4Ww{7%O%~~fbwAoWo1!?`LIDa+&wut9tH3pfi+zhNt0U3-0G7`fepb> zLNf!5a4;}=BOsh+li}8jH;lXzx<;pacXMZNs^Hp3a*-{fGVU`H!!5~Sj(vtrZCv~! z@Li?1oo^i15(jTC-R_k>(%T$}!w+4(N2jGJPNgZzoa#xxBsz3yViao9!@)+{ru*Cc zh^)R7cpgD;KF2Kbqe=jha=Z)>dX705Cz20J<$f^uPr{xYFioLbi*}Hecv5oPhR}E5 zl12_kCz1N~_P-506qoi9+-WQpXc?~~mj3`E7vO~@NFUxg>GJvzE1USI;O_@$J{gMl zNM#p|cCpTa;I`E$?T$_ka1Xa0wWb=g`qXbZKPJE6iy4QEa8tD!X{EY)_tQ@^xtHQM z!}z=-94)TPb8{thy4H?^wr#-&B z=j&OLMGQ&XIqQnbZwzbx8P#K#RPf!!leSiKaW>}54E)~x`&NrPNCt3lr;kr+?!!g$ z`50Apkv*0Irbr(zTC}9Bp_c@GFa=VDf~t+jjB+Y?-SRljN2glsO3vm5*jx+}F^;0F ztN!hBK;!$MRVdsE6fqo*!;w-+I)%^6+Ze4kGM&@8Wmx1g5P0w3r+)PL1EUmWT#h^B zel=Mp$^xHz7$5>oSn}^?e=tVPyZgj`ng0L^6qiwQ$aHKOatZs~WNs%u^?_JB1BLoy`qPbsYZhK-Bo6$4FUFPP3KAibPzen$ZgbQ8^ro!C$}bo! zGlEwnA8sn-R<`k#b$}n219v};PHC5{gvJ7omAL?Qp-JCC@};{VRGwkDE&%M@W{~{VhwPlGPC@MMQnsh!>PJenv7w-k3Twdzf`#1J)o+&;Z_=40f^pf&Ws~F%C zt;e@+--oR~*@FHlbd6ToAu)q>&QBwjJuorTr#Ss7KV@lJ=S1-hyywdQ0EvYgjU()@p~>H|-)v7D}> zsa$5wdnF9hr-6I>04KS7_F5-)-=|n&gQ-MO4*pq}|BQeqNXZP&^K1s_l^O{FqPww3p_y&EC z!^8V~;nmgktOT{+f@hxu0F2Ttl%aBX8RwJ7LU-a8ia&xI8xUbBaVzwS%wByyx+op2>qzPYY!e@_@m*ch;4id_V$7cm~1s$t4}r< z+W^SuuF^H;#8>9u!=q8WwZFIzYZ7dZAsmNrV}KU`VD1d0 zSFirYH@c>&O{8fd2t#do^!(oDJ--q5B_+{fONHmxxTU*%PX=9RSenICO zk4$#2rM?~b6GiX`hkvpBJ8H=QEba@IB;y;hdFZ3^tl^kfk45(*Tuu)UPYnu@Zu0#P z%5RO|2E1SJo5!v2r{cGc#-pj+TqG{*ZnE2!Xk(kqd1aC^2HA%te)k7&Cb(aM_m`>h zGsC*X@yDkunxxkD%(rghaC<+w*9?y$gY2C?JDYZ7YPpO3_>W# z8*S{DKQKFq^Aur*-;{OB0rQ53Zx4pNRp3`^aS0mTBB{+Bw0-KBBs1 zh?PoiEo##_p+YIfN733p#1m;?ZD!UMe=bn2hsCmQLdU5F4yB13v4VFVgjUVBjoKMx ziVaXkzi5qs{{V!ehZ#RJFOUH|f(Qd2CP&C~jRw7M_DQVe)+}UshPjN}w#m!5{ zRMks{nYx^$}3CBnL$j-v|7J7=eFervlYj4ETJ8j9;vmmMib4CNQ8 z&uKp!elDl_OkO|n3}ztFtB(?15C#V9hnEC>-a2&8L0;ve_+V;6%Ui$krlyt?>0ww8 z5t}s=lM@-Yu>cZ`0UU5Kj@9zSUl9hApj_WZuO4Lv-PE0=@s&6OmFhFcC!q`<>z{?` z9RC0a{4YJ51^Z5yr!iNSATrib9Fr=Q^IpzzMvfB=dP>*qd0BNzm9Y`$cMER} z=^G(yUljP7Nh4$P!SNz8sBk$|EDDaio)53i2>HAA+R|)nEaKZW+_U^cpX{#e% zw1a(`e9^^>fqqU5Y5?kZ#_pDc2w5^B%M-VZoObO@_!6<){670i z-Kk{0cznJ6_QuQU+&^55k)DRAYPXG`>9-g6(%Qz?02s_us~0=6QHUoWcpp)oJ!{pg z2s*Njy6AlL-|nKUdcVNmFH6&P9Z*bBU&HoSP{<|T1gYY^Ek@k(a*?v&;Bv#gU-4y> z{vQ3cyj7~(#TmP^o5T$uA9E2wd%pyno)NL@ftq)QGzq>O{?D3M#p$o$xwY0c3ky3) zXCV1|MeXE>%hi>3+QTma{?#As)ngUU!ygk!B4VqoSg38^#9_R^nt#O22JcR?yO(5lGpx{gaTWo#GnERhj0PZ_07$j_Jz9Ja z*8EeY>t6`9uM+6CHaP=b$0J;o43066x%I*PE9XsTBIQ!(`#0b<_rZ%_3j9O4)+{ce((Jx1 zXv0=Io%~N5N&TpsB>wTLRJuHnRnP#O-ylgQ+Je7_SuApM5-O@A6{#@|z&r_`Vtgqv<;d~bOa96KBg zvB@|Y=}WKtRQQwPjS|ztekSq$v8ZXbAw`Xa=A|sKTVP;$akFhq4n9ygt-Y)j^f~>M zkGysLBj7K^{Zjkk#-FUUgM4z(f8i~%j_3v&Mcul_@sLN#tnx>ydCWSLDlyW&6!_QU zojc-h#a(;HS~i~6wi=eFEV^Bj9Q~5db}jA4v~dKCdngr@o+$A&O>FUNcWTyl?Qdl3 zI}aizXJ(B#`Ba%CBn}CY&+#1RGnakRl5%q1rlNCASk@8cT9NEK*hpKU;L){CduKn& zmyF;qW5$0fOiWK816dY&2f*(S&*FGB9}Q{I2`_Hvwp&2V2bkEHU=lzbdXv-fuYK^Z z!`(aKm6;wl@n(@}s9M9H?D2kvV*Qk_82aRo=U+4UG8;R+GVacM{{SK6x`amiwv3SL z$sGqjt$Q!STMrLt^DUXs?XNC^C@(B>&d;3mjx{G8MsbqBB%{Y)qpDQK_7c>C%5=l*cwN|+s_bbuWG*!V_SKEjf8q_y^iG|gM!H{ znD@$_dm7vD*X+G(YRa-$Xm$}tg@QP(P0Wh9;AVDi!}vhQ<4*TE{hT+s@kO?^7M*gp z&^xS%cSd}^r|5X7Zgu9no$ju#rjkb_yRjvXef!twcA5Jvcy{zWdbjqLq#c8OntAp@ zJ8>F-F~{Lt2kj%_UxMER{4Z>Ej|5uznvL?TQLq8o5x2Q_Ah2EnpZe%KW{-8fHyVU_ zdf51_b-Q@Z*i5-@z;))Nk5iF=ks!G_JP{e=+Pzlwv=1KYPjBIW2g0b#e#-H zV-|Q9_Rp5)-|Vx72kwaZUfudsf?N5n6&p*tW+!L@CM1)d*!Dk_eY^1E_CNS_;opZ* zcY&Rk)=grrmnvV+FL3G z5l7|BsH9`BQcrSEOyY#q^d?ke6?DxFH(nZEj|hw52Drg32ih*KEM;Zf6b;-W;d74X z0QEJ{dL9Bnk@~ zXN8K8#4|4){j>gf=Drk9YySWWPs5;uSKDVH{q6|@bZ(#SkESSgQMszv@!eu=SH%Kt zZ&aRUdsiuNOOmYI0$34}-Ff{hCBmzcxghnx&TG?rL*duabj?>xxJdlZKYy04q=Dvl z1EB|#{HxFJ2bCenBLMcU+%l7fhdo^+Q;DQx0y}f^j;wK-wQp}by0nrg&t=KS=S+c> zW6!T3dJ3~{$9kL|_~Y=drG8@@M4mfHl;%e-+wWu2rL>MMxGS9de~mDZry+ix)V9p~ zR1gaV?b@j(OLmu$JaYWu3KDb11zkq{&|;+RY!g%?kTV8!1mO1T?^B5%Z$=b%U-86BK!#>}QS2MJmSn@&d zS+aPB+V|zs^vzD>4xU|vfPQQMsqAjvSY4-yqI7IDZbs$*0DGRi(HX()MB_E9Ei|Ek zRU8A(DMi3jy$Klu1#_I&J+11RfYgnhmHonj3b~5IOf$%GSY)v~RB!+Xo)4{NUC%Np z{?@d=B%Bkf>#^G*{{Xge^Y~`5mLf5gjbny#SGo7U?3FZUPS?UhA3BU}A3#$ocsSsC zXE^JOW0}8Yy&7Ex%UinDCPU}T6qbz>hQxSa0qcX%<0RpiI5`#8{2A~vcrrag)@v4s zG^z~k35-nYSTmjH%w{yaWVs&ya*KmY?hPFUlT zc&~|+*Sx(CuSwYxY5xEgb(p>%Yd;CBd@9mUsolwMsB6g2fo=Rs6pl$BBW&N0SRJ`6 zJa6OOwwbBH;th7<=)zOM-cOS;K;~FyyKs7w(ruu6hwf|AyhE$$mlv~XmzQV*p=mcT z2msnSJpjuNzlYu9%l;@^Myam(wwtXW@Lk#=Ag0MYi^kjmA2Hm~6OJ&02D&KJl;*FY z&sH&ldLGyCuf$gx-|Y2cr$^zbXBVFjwA6W3+!xN91Cjub9dOtrZUc@EYuWw|*xl*g zAMo9+?3TK8clxH8a+2y7rYWx^4+}gIqkvuVbA<=)^1x)*l77iCq@Myj8Gf4Wo}CJK zGQtl%WJ`AQ1(CNhNi>a^*>@ega0yg6ETo^IKW9ij9O@UBTBnCBp}4=X5n5epPabBN z#tN?IZ<&tVa@_ORir#OM_E$Nr&lJy#KWcyYN;EHyc2>R@ypjm+uk~Yj9iqn4J1Ret z@vs}Cb#^O+71#d&!8elcNz`og%}VxbnG?eIW@+x{FDsw6MBg@f{v-VR)@SWa zpbP&1k5{X0YaELm{;0Are=Qxn^-ZmScD6DV@rDD@aCy%Rc)Q{^!Cfm)_>JHz-7Z+> z(~`n_4NWDuK=%{dt4LZWLLcRcxyUM{f=R&g)n#V~x!*c+bk{DgL)3q4uN3%qU-533 zng+3Lde+wRt;n~yR|vqa6p(NZaxt8P*A?A<(Z3fw53GDFm&2YK@m-zm)!eX!hSqT= z#aRQA#dEiX@7#}i_}k)V#BYlBR zgNoso_Kx`XYSBqI?6sgqWst0YX|mK6%PC-ZYo3E+f8Qt4ojf~~Zkqei(L6;iN>Ff@ zx%i&b`%lhwZ`vQjcee<0dtq$EEw7keTGGUkl74fP182WclU^G(G|h9s@(5ZLw$|)U0f~H+ZjYJEz7W1m6vK9vj7 zo-I~KS)HU-e-T?sr01_O{QbIfz+B{4JqL(&8*;(3B2Oa8EN14)LK_4gLXSc*{6HL$ z<_z4pzSMl_p_ce4jdvnL9-0+-13R9t-|{&z@BN*lgZ}`9nc=y-GrasE@VSmFkm!kV zsf=#UTmC;Xe&>Ak_2$0G_;0JMzX-GqPe#)f7V6hWXsxZJM2;10G@mU@%s@~S02q_d zXSw(jO8(BZ(UGr-ON1>Hw(N&-80rV7%m#f)^slzQ7R7ZR!S4#`#=`8pFlk@8HYoCK zOB;yJV|P$6c9W7Z&!#Kd&SMx+YCqP89tts1r&F(EyitEOv~bU)NX%p}A(d@p1Q0pM zIqT4lqX(nc%wO6(YxZlN%FDzV1$N0A;r zTo9P+!yM#_@G^RHSm?Kfb7g&10PO zc|gbm+a2}C!M!`h6E3Ubj}Y3~MHiJcdVK8b=Y~e|Nt7u7Gb?li>;;BS&}R5g3_8cd z?K4T#bXB^yvW@N}dvZfbE6AH*ZN*0S`#Bqxjw4e1n3z|8bMQ;zM~nPDVd6g!cwOwR zd_}L=%WJ7m98vkU=#D43jspJxyHt+i21LMCM?!#%G4i$TGxs8hC}fw=gEsp+x}O|?7QsdWfcm@Q>n z`+Jpg3_uYhLXM<`1w$@570((|mLr+U_f|)zPns#FyB>f400jxRg*06f_66D+(iL5a zjC}2%r{4X2>c8w|3^!j1z9dO%jDk1OjGN`%pWfV#{``Nyhpj{VLHM5M;|GT{OMekq zn_af@8KsXgg$W~LA@j>@dG_6sYhmxhu~j;b+3qjvcJYJ1Nds!&AhqQ{5~O!j_Y}L z-GS_<@~)JBN z;Ng%P%k*LosLy}y(|mF8(@H-PBfd`>z0g)FCXVUDZsWTGR376eKDF=RN<_O+W6IN!b}|`7Z}>x zf%#N$ypF6#UYMlkQN0#~b3SCPx$+*T`xN|R(eDz&Qin{mnosp{Txtl6z<0qp#aTbF zpT}`*FP}=Y5IX1W0^|h8_qTM%y?qVwZ{aqVW#jd2C*l;B>SuD(hC6}WdXg|Y^MZ4Z z+*fU^d@%6LUk6fakBcc5%ZrdIc z;PYwPO}N(W;ez&NSuR>Z_g#R>fu5jXegyP2i>3TNg5YVlLsnVDcRp;E``PvtTmnlB zF+F(WjC0L;=YxDdb-gXERQZf>xZn@Vwlh^2^GBJpDz%c5idQ>rBS7%oye&QT_{1rd z-JD3-0{{-$=kTl0+<02X2=9bHE9WYsxFD}@sr&%?Ni?Z=w80@&XAUKUBd{B24<3J7z}~X`v5!rO?(0S zUi??L_}%dpbt_`f>2Z&<*#Uq8Zo!l4PELA(f!e->{jdHd+V}&(QFubanOjC}zgC%7llzA)D>t`xy}56aQLS0C?Wlk3m&t&|}JbhQ$xIrTn~_)+m!_GbN` z{O=HI-WAm~YuP84$QH0{ScyL?GfGgF!3*+?08S4io<8^X$ndv|{x7e^-xcVS&26W# zkHg*_K&uMIq~OaY?CveGn1wsG1^HKs;dN+ySK@CDUHGrWsDcM$Q0Bn*M~ zlc##-d@bSYDQrZIX&XAs!^~yL7-NhQE0)Ul>U1euH$8jfhwZcRFZP4*<kN)w9rNtHyTlRXo{9vJx3u4t2Mcl!RBXJBO}^4iMRcb>8C+`G=-ya5?K z0j>+-XMrz#SKzB(5nSu{;G|6HZyPE_7(j3a26)G>rF{qB>x7rUa_F{pv3Zi)&v=oQ z`>{zp@ge#H2?y!)uN?iEyg_Gk;VUl+3mM*NZSKi@0EoU+jn}Ur9+jMG%ievBthrI@ z{{RDiF@DXT1bhdr_*dg*jx}Eq>fR{2O)B=|MO(6#OB4wVerpVY=J}+7p2YQ9`Lo0~ zkV`X7ZG{p!+pv72Jn`+)y@TRsgNCp0`s>8dUdwND_VeXEpBq~{`Vv9lw+D}5itx=x zO4cJ|vs1f}*$D}fwgqu+mlkg4;# zGq;7yFc>4RI6TzKW)U+e+wz`sf@>ebx~#gck8OD|D(@I95BIUnb$TC$HIEU>1%oyRzeKa_Xm^{K2Z z{70dQ63*XLOCqf(dtEXsU!1l|sgo_VbsX{f)Q3vd{3)tk_X%J|k)zLuZ zt+bn41?iu>`6K@Tt@-Dtgf#oRzwLCgRGB2)C3#j!7?3lUQHJZ(RY6MX!8a8F-YA;V zT~U71_HQU#ss8{>)Sei#mgyX0bDvZ6s+P9Vl7mXNwJb^h0FXz9-d4~1^Zx)kzkHYD zSxBrQy=OMSKiF*IW&kl9$tT?B)34!8y(F$Rk%bw^)_hT(KkJ-BkEha=Rcee>zsQ84 zE7z9KT<1Njn)qkpeOFuYzlp4`bn_6g)$Z&g3j5WT0cZrB1~(a71E|~IJabq#`r-O>Cl@e~dgb=(eQ+~DpxRr4~#t7X7aNzZ*4Mk)`v$2m#K z-1dLjkKs4QuMz&q{uKC?r+(1B1n|C*Z)s_!f5m32Wg2aP8BuNsU|phQz$=!+n)M&q z^Td8P_*WLECyTsO;eQi0v8js<0{;L)@otQ^cQ+8>8W`QDjihbT$MX4RKqQP68-P6T z?6-5_uZZ99Pk)Biv-pPdLb%cXD)@btdq`!H=6joFRE;hCly(y|aN!t?D}@KPeUit* zKeHvL#%&wMns%eFKZ!g!@e1NSM%!F@Mb!Gfv0`mwYoU0m%o-?>q7e`QKvhmySE(E> z7FXuowsLzs#l=dUJIA^6=fv-YUN-oT@v7oGJtIe#RJhbN05s`5L8g7UNh77p+xZdR zIou=5vLBfN1QMXNaUz1 zYXUeKtDXz}p1fD_OTl_qiM}6QX;-!u_xBeN%VOaddmDh!m2zGu9(v_lvW$%2pRWh( zDe$8E;Jw$v9a~)0yho$m-2VV+>315X)w0bU<9V)bg~S%<(n%{y`=yXdSR7%81>dvZ z!<`4=4~K1j8F**m2Bo4{H1908S69n&(_Bc3{$z~Ggyugi5)^E3VhObaR&vU)ockRx z#A0PZw*B<{t#7{fJ{kDcr{8!Z;tiIWs(4=JDRgU%m&hySZzpMvoIr%1Cu>!LmY`SLRgDY**WVwr9it01QLo zZxHyJOF5qYEj|WD01%-*&cT$H;0=qw9GskYKPveC-%rtXmDMfUDP+63^5U5$XKZ_gdH5WsOh|~i4>*N!n)Lf_UsncP!jwD#s?ht9WXz+&y(zA zrE6U5_K~)uW}eaX=`IsYS(tpPkv^L0pDPw^-e?4m?)Aqw%=k`;w4r5EeTY~KPfwK**N6jpZFQAAxdzcG=DN@ILaqPu>QwQ zW|qU@UWA|^b#$?c7Hk2H%(ETD#X_h^f_cK`}Ef#nLZY)YfX^)bxK6>lgn3 zC9Ky2Rh{u~Gq~Sv8oZ8KA}_HdByLp)ViSY)zr(NDL*a(C1>T~3U8rjC+3GhIvw~XK zEby~MA`O8$Fe9E00R)U1%>BGR2mBV%f8d_hUlYCnXtJaX z0mC=W4|1Rmes!-;2Zg5wloho{CM%Yr=aX&upErKYJ|6h1;xB|+CAWdJJxtreW^Xnp zXx3Q_>FyYBGrMl_KJ-kDM>(@oe%Ja5It41$hUKd^-)NiS-t>^3jHya=U~GM%Rug*kJLJC6k^= zFOiIK@9Sc9JA}2;{4uOvqp4;-NP`@a*=60wBOIKMdf~n*{44QAl+wz-5A`@_oh3=G zF0CfrBm^l^+ypFkM~$I*Kso0=Z4PM8^4RO1vC!}@9zg}?!ha7rOtU)&W*_wslRdcL zlaGH)R+sFxVrGw0vuK`4X49eag2#>0hqWYU7#R&9&T+xcIj*n4z7z4iuZJNscsSY1 z0?14=S;+E43w_lDV6g+9{13<9z7l@V+Lw;>dp{J}_yY3oS(AECtZETkssqkZE!De_ zaM(On^(xbiDO2d)rP1TW1PczCFEiPA&n%EnG0>ejzoO)+QGUU);6^t!@I8%-FV{5;*O7Rd~B^exo)nb zc%fSfwFhTlBtk|cyjDAg(Qe10HFUzgSjUy@bGSvj*yC?L2Az8D*I)3~v!cOmss)zK zVws*Pqmc}&<&ZvLP!37-$J`laX@Z6LC0BVawmG4iR_OCC@@-Rj> z`FU-JkiRwtb6d7E{4>!!A>rQ)_-Ep#o#psNVYiN1CP_<(t);dK;1Tof^9jZXDoFrj zRu$jFkA$&G&3*A5F3M{SsLG#eOq;ik@g77G;D#-D&$)8S@Wp~Qfkb4}=3kK!rwBWw zVR$R{eAIM{NOhYHLqxr^xQI^_(HA~+ZZ{?$l2n2*j8z{Re$V>Ouc*nZSX^kdO)(S8+4tlG&ZS`y2S_r)Ra{@l)BH(o8PKoR;wzSI@+L+Cu~tS0?vTnIXww%DE1D2<(aO#L(%T90=2MFHQ6@yNX1oCCk*`Xy-JUA*!#8BDLY!) zBhE?5^Bm+hH!h@&EJ8c8_MSQ#adwg4y!rl~v;bw_)1=gy9ZBQuX zZ9`>K*XLoMqIx$aC_1jzv83dq)cJe%g!q|v@l)a+q2ifXtPL?sIL1en0(__l&NH_J z;~z5~E6=rrXzb!?mvA8GobDg~y1%NU$XJ*$@_@h-`c+R5!!Xlek;w`&q?6N@u8Pq2 zz1@*io`<+YrD^hh&i2=6VlDliu6&i_ah}lUkH;g_`)0ha!|?f5avho5c_1Uu20pdx znlM|B+4=?Z0D9O*_}`LX19fW zK%N-*p9TB8s}#C&$8ga;97bepkb4od5%jN2`1hn~ns0-28@)4Ap5_~`6k5!z_XWZI z%zeWnVNxw-)sZnYx=?l?lm7rO6DaHS6}_r>!%@_{4`bo0n2X0@tHUj{m?W;rhQh0VqY;5KR^5Hz2G=UMNWm#Lyy_{~^eq|<7FxeSyoaA_q z?3*-GJ*>?P(s@>wDkF(EJ28eu7#Z!mzfOA!_U#{2n#vthOnX?T-C)yikxnxjvB<&e z^Byb4{{UlYWV7)`j;>S6d$*4gfCf$@1dM`&YAq_oy5dnvF#N0Iklm!oRUU#UG5;+Mbv2 z{{Z36yW&k>Pqh1F*18s(ada)>jDlH~IM^`B!C-JQGI_5%z0iDB;Me;ni99!}Xv|#s zZF8r^r$EC1{Ia}Yo;w168ugFaYxddr^ZOt8yIS!Nz`uwe*!q^Uq8&2&N7J7Dn%USA z79$&~w&YSD?|i%vGhS(}HnFN|5^6(Ay^cv_F7m;k!2oag9OSieUl%M?qb^=Ww{~?m zN7$sVb0!%#%I)U)g^od0*YH$JUf6gna(>!Z<(CM0H zpcL5Y(wC0i8)$M#B(WTlbJXXKE1Ql9K*>@8=M28ssp5|y12PUe?yW5u!J-qhy4?0J z_$WujKiK!s>IV$nK1sqT*-W_-{zo@0NX5 z13#8Fq)js_5uD_JdF@uMG;JcppR(C$F!6x?Lc4xvntW0$Bjo{j{vI<=^EQv(`u92J zo2PeiDOmd(;MI#K#?KifmivCub$8|xRammi0nU9tcs`&WK2uVBFNaQ#<424pH~Vf3 z`?hhCrcCkQ0Jbye-;GrGZ+_OFv{#Jdo@p5pdx+6wI7o|1n8q=awlH(a!f-K31Oi-==*3Imp%-c=y+pSnLjwee2(*Q!3TyCjZR;u_n-`$At`Tj}yf6mD6W z*M>`Pn}Edq;Cl|d`)_{IGuinXUF%#)8cpC0ebr+)&zTjYq6{CpUO;)zVmsG0W)C!qqhV~f%>d7SIBepz{{U)TIBDtO{VvT_B_0aCNuFd4*4D+c4>`g4cMs1M z*+FR&maenYYadJh0KqhV9BAJX{{Ub)t#34Yy;e;#SxtT&GS!3qu3KyEMLd>68-|UM z3bbGi>M^-8SbZ6J;C}=7o54_B=zk1bNSnf*F0%0jo|ECtI@?ZNM^d$kCX9s1LmPR* zH!RBGk}au_)GC4!Vj${j!VS_lUYMOxsok=RNA*9R^@VG zBZK!z;}zc8d}!44dlvYI@aMwX-luN1lTQte?~CEJYsLhlJ6p$MVvYR4EQ+3IlpaTH z0>>WGsX`6kt2utPj*U3R5@|tG>%u-L(Csw;0EkVfc%Q=;x}Ll)^;>Is z{@)Y8u~>`6(bbDdADpqEBqVFJ#Tb_{*!b7`J6?Q9v+(YXsOVlMywfymEBh;JMb|BC zU?MvSZY?A>wpL~}Q!qbS9Twl<<>sFyj7}gH-Z+TM z3Ni@s8bfM)rBp=$QB_9K#zt#z!@uxUKNnfsG+q|?A>vJ8Zf;&wy1&+?oGw85H#NB0 zqXgp|Wc$-{qSl2+9h=5Vaht!(wU2R&T==smg!C&oz7c(wPSHF)G*%b7qiwjewua@c zzEyfdx5Y_1w$uOJH8>HZmfSNLV(?+j@J#2zf4N!6~c;`?r!Cb4k^x?Ppb zutgy&9mx`x`H-<=$t{}sqr{)Ix5gihQ>kE|i`stht|Lw7&bEx%*t{7PHv+t5CE^zSSO&AeCJ(0)3njo}B!+2ep1~Yo8i) z)%cDk@c#ge{7o&pS*Vn0x^29ZMRDZ;C`Xbg!l-rmc7Q@0Zg$-7_E(KQBzUV_@NLJ0 zwGR+m>Gref%clbuhqVZdQ3w_#WM`BjkT7yV8-UNHeC^>aD$hjt&!+r7hhOmCt7i$# z^iaUj-NqSYF>OVe_kH9HSOdzAcqeL!RC?}t_H?DbgDgB>sa;v4&8vJ?gouFOGM z(*5*4TDKF@i*fc(OZMn}#-A2+UkXEg7l-@^if*Qw_H|pOi5^)SbGGprP0rhnPg9b5 z^NIUZ-fABgz5-}}1AIdknWO1*>H2=DtZFdp=gJT$jzKFE5JiPNmxyLX#uZfUUE}3@ zKiNCRI-iMP(WUr>E$zj&r{vuvHxn(NPW{t2gm_^$9;Z1;x83p_*WH6C(Ej; zxVo(;%Cw~yyyeYhqSalkuJkpIV?0!E$m8q(007n+c8~CH_GZ&Q4EQ1m{{XXN@phf5 zXcr-5nde8l5;_*h^5l%Tlbymv48s9gS9ikRDDV%5yg-qBPqDw%h2^By`em!KdChh} zlPl(!RTxDaq&7jw7$lKiDgOWj^8KScTl+x#J5K=oNzpXhyBk|Ot8F__wnJ|rOBK_X z$&vt}6sl?v803tuJeK(Z0h+F}`yOe(4?k$%4)~%giQw>^+?s;l-P_6$wDxT}@I@ft zfL3oQv5XP{050Jd&G5Gw;&A!I_-~0SDA$U;Iq7PV=$q2g)8=i5$2u)S+TO>~J}>>S zJVoI>16tRH2pKxcOHqcVv|jdvABBwzvoAXm))0JPuj zi}B-1@oCchQQ+SVTV2a;lQo15EB%NtBtEWTdGTP!V7p@ z^O75TupLj_^skt0ya(|s#A_VdFT<r%?VrZF%@6il@#d`g z)^*!UAaKgI%F`0L?~tkw(6tpXxGHhIM{k{s;aa^%Xh#14iSIwNPwg*z@nc@`7lt(- z3MHaGoR^|?xiS*zduI}^Hi3Z~;GQx-{`GerEBIUR$3VK*ekE(y8iWt6&8Lgjw?>;} zvCRTTA&Db)-7Cjzi~_N5E6H6z6)8cl7@NB7*!N_yK3(xcm*G_>)q#S>(9UoBK0Mgj`6HS*%h) zD@SXW3mk|{(gPy4H_5vor;Pp^{1x$%zlilu7JmkLXZATRZvN9W7hWJp1k-PMLpn6z5?)6nkTO^U z(Ll-ZH|>M)8^c#0*c$GEa>mX_+TKW-9nvmbs|G0f5+0c!Hx<eW)d9M~nUttmK?JZ@7;}6!_zU8V zBIm|X>zb6d4>hz__UWNSQVAImVUca5vnF`l@6|5V7+B4u?--lPmntz8i z$t)t&S|_@m@^&WI^+#oOqx>!LPMxaS>30{NT)GySYTIqv0UfQp zmoiyT0Avfav%c9EayOQ^;8l-?-Uo+GLZV4#^Dd>Hd1VS7StWLoM{TMUGcz+CssJZ| z2Njv%c{E=VB)47~@a@Hna~PwMX1RGY96;=i0cHsj7Hz070Ve=t*QbLE82c7Ot}UWx(7sdpzkxY3hTyJkp} zVk~g^Luw3#Dpj$bK_Hyt87Dn?s9?2{+Az+fAYKU=9<^Tf`Ws*srNPMG^*uit-P42E z!t+{?_?yJKZ-u-+ed6B|U6}1O`*xbzixkw4;7`a!fh7l4a%2VxlzgQ6i5F6LnHpVp4|Ma zeGAH=RS5&A^!k5=b<&3<4COv)q+seGF5*jY4_x%BI^oWosvB_`$j^M5)YagBEbjv= z+@3R8x|~t9f)Ggqc5~~`{{Uac-@%<@;@p1Som(t0@GMV_%mcPrp;mMEHS5GEk!@r#8uH z^JekN=Vs*gcKMx}c#cb(PwhB-DXdJBrWR$=r4i=<6S^U z0CSFdgX>x-!ZK)0$*l;ke6x66ZKG1hfRTj<^6G!WtVJj>h6;0mkyNj4Ac;YibzbDN zV-(1IMWVwR@6_VY@Th!~&pj(d;~fOl-J3L~a=}hAJ7$`MDB5x}&sxlv#d>6<2n@0l z{4qkr`gOqKp1P&LU?9;i-y`KBAO~LgBCX9|LSHvTY72%?Rx^x_Mh7SBPTmxvujf{iMAkguF&yT}5tBl-LP0G~>G@}G1Z5ywipJO{5V2J%~}B8>{<{he2abwSnoWrb-k z!VOKr8N5=C90n+}B4e+8wS`Q69z9Lg7Pa+9H?sh)LXpq5J%61exI!1M?ETYSFZ?QP zGCOykU45Mt<&WDOjAPsU@mSiIf;7E0>f#x6xFrzzMOpIYgTj&ma(Mgon#?J^kk%G< z)cR*Y*5)6ypNM0c?hMQe$1`Iwta}$Z-Q7XYZNz#Cm%+Mzxo`1PSMgMqcP7sN09F=) zSr~J&EDzewSc^AYTBTPPStYVUAc90eJ#-2&ussaA?2Ug^o z=`J--7T@@Wd-!!Zt?i?6cPEo8&F0S~yh*t>?L}>nE>8my0L*p_cyPkarF}F#sp4S` zwJiA3*79H4GSVwHdy_2I#KUg^U=wPC^8$MUdUwaEFa4nO>-gHv=JQyz)9qo>X0fog z`I>peYS1$_R0S;Xl{}ru%IB8C>-aNQ@SM7(i{5z7-%#-l(UwRex4SaOD};7!&uh3A zQMmr~)x8dDh4|&-+d1`JKEUa4#MdD%UP#f@5&=!cS(_htlaY^=PZ?~|qTx8e#ntw) za!Bt#VON^-L-=8FGcby7Atp zJB8J4^-G8$Di%9zM!};+11hFPb^!N&ro0RG64I`3yg8;xXQfKlR$BQM0GL)Or4lPC zlpmag$vDFup|~IrKC=Ck{v2vLSAp$3FQn-j6{?k$W^2(c<3gt+Fp%*DW+b@W7%o7; z>rSUHJ5QifjGR4AgX2fRZ-XoGH(BwYihMn##dCLb9(#+Bs&^RF6O-S8_~i99nzx@0 z^owf^BSo+m2#_j8_DgbA@CQcWcmVgQe{Am%S!nmZDZRJ8hTl?y!!pflw`ZFhh-778 z4?S_vsP!Yag=yX;)-}%_{4~|H#JRk*w9xKa zP7a(~B6TNzRD1sb#!rb0@JHaK&%~`a!Es+ae`OKa%QGNvJIezt8c0bVVc}J>Mh6_# z>peF6;>Uv{)pf5ES=idmr$ccinp?>Xhzw>%#Kuv-3JVeuc7k#^$-wdVfpy=G-?K{i zKUvjmB9~N4Nn@Vc)m;ikW0fNa;e$9tR4l+LDqCtV1FP^ygCE1b1=F;~jw5qvZY=Kp z&{1MAq-dzhIKv&gSg-d$zyt#8K3ab6IhS{Wut8Ow7KwPhx$G!nP8vM`rPo~}I-|$lZ z02S!ZdpjkcjrF*r69xM}0vta7A`xt1nqTYc;xN>083%O z{Cz8*HDBxw{zqO7TNIi)m$X2P2vXfLR=LlMb~f$skHpZVC`)*D`2slMiMMb+Q(Z)- z0KW5%=K`^Ob*DC|;LTf2S72w;r;K1{JELLPdYbt)q_qCEJ()JsKQX)=<8O$b0=n_0 zyWq>{5*R#PaW(r~K@^}&FRqdo3nI5hmQI8CfhUUU?K}zlLF-x^nhmTvVpv_?>H1`r zzhZ(kEN2S?ZwZ6~OXW=ESc#j4Rbswf{{RV(iF`BSzZ4$}_>%ihH~t{hmh4&GNeP-i zwoUT6LN_lb{j)fUcKQfY0YzmOr4GKC**ak)%shUzviObY(8 z%P@1Vl~Jh`3%JL1cJxQ{?qP_HEXJ%R`QQn z5-I0dBeRkqsEDzlMbvHn@j<#~Q=A!M^skS<6*Vsm{?D4%j(!;3CC;aDZw-!z<&v_! z-PO!?k?jZO5+suD+QT5kK^zfZJvWIy5Z!ow;_^R^J|a4$_JIxk+TC2;?UP$YR#42b za#hI-AtVi>=jooKMEJw-FTpyz`hA~_TU^ns?lkMIHs4XPnqS^T(gvS&(XW#{CO8Cg z3?>HMzy)Z3m^@?fhY6bF*AieTw_2N0t4&7AtYg~l*Rr?wk@PuMGSF0|FGqCWq1OJ! zpA9U(X5Sz9ul8|k(|>6ly|0NpIjK)>t3Av!#~r2X9i;hav)e$S3CRx`IVQLMH+ZAQ zpA}$l1$;2oY^yYPO6@ZVSe0E7>~+EjXGk*C|Q z*cC^cArdr<;JllgIRxY!obpY22Z!dlxW0_7t+Y|9sUa=MAeUACBanF~80lR1#JG0& zyu=&kYbcqIBlx5PpTO7X*t~Wt+49O(U5~iK;V{vfcDc-JQ^jed%_LfcG0v*SHkv>H zUuIaon6vR=mEBHRFS zR1kp1;ZFps6d?2SO`~|YY4p-k60hO4Marz6`HE|V&%5s)wm9QWfsm1AGhwVRIzTi8RS-d@U=)-uTR`AFDF zH?rd@Frf&}27JNS<}^y3DZ5`w`4XF2^*iaWqHBwJW+7BvBeO8ZK5lXNX1-7Tuwz?q zjoPlSWie5G95*|Z;E>TQVDLR*W6A5#b+4mj)aSjnnJ#qJwp3!FXDUpqv}L|RKp7-s zIpgVG0r5w{{v`4L0EcAp)uo5pt!yr5x1Qvc8+^Aa!bK-{-UkF{9G~LFdKok2r#gL| z&Mapw8quou*z?^N;vKb@gfDzGs@z=2*AoeCw@TYmK)kjGugW>e`A1GGqWEQLYw;gc z)BG`SeR1~dsrI6*L6Zu17bLGhqj&!RUbtuQW}|q9TWvaAE_#8qoCCqnIXk~0?_XJd zz@Gqyjp5h$rK{O(pH~eYo+UsTErWgTq ztrNpq6}^;@PU#w=xph)X3=9%_l6m@^*Q8rqXrj|LT2l!aoV)Xk`g8t&8pP4GWrj6@ zQytC+PioS>b7g2{KsRFur`M)yxnbdZtNrK9`$;{L4IG(R(FWyK!8`$v$NJX~ z;~y1xd%&I?)O>NOsAaQ(rEt%KGoBJL$5O>qb^G61*wkaYxgJ@`CAUfHPx;M!)B9um zQI7XmhvANqavCi)gxZ$V#FqtrRsR5hD;_;u=kC`{Dl45U8M?08Gsiq<;(b@f9wWW+ zF0*oFxxTn*ZV5OWi6;Zl4uEt33c60PGM^nXFxSM9;PZ`0-Ls_@ih^SLJ=tsD& znLSKmn<{DyvL;uk90GZ)jZ}}as;EDEBo1+m{{WBq&0M;?42J}5E$LKloRm_`3JhnA z{{Wtq%~9*A*+w>r?4Prw*zq-pBO?sC9sWXprFy^ZpW<7cBjF9UhiN#DZBtW;7B&0N z5LOO4_AI&M`Pa;_c)MEg&YN$2pxw!9BucDq@CF!XJ9FC@&UneL@8cK7T}R@!hvCz4 zZjhydJeLx_a?K=r+Y9If3xYcDHTi!MWcBepMTW*CoFl)=?E7qjq&l zKh2+*5{?Hz*gZfZhPQnM%n;3O2~mjH5OaXe{2qGwcsTc~8ZU-4`CfaiTHYD+3}xe( zpDYB70lOdV5_@*()O*QObla)udb5mce(r-xDc}Aa>VM&Ud}^2BIlAyY%*wJfmzNU6 z+Zq@Qi4f$Te|gBpe);s{=nvRD_>Sk{&CaXh9bV${O+rH>&2uX`QVCL{c6wmf%?;vf zpAt=Zp!ibWTa$KzE33(vl^{rtI6Se;W0Ql&LBX%Fe`Rk5+;}tLjjoY+PneRhqD-0^p}7*TmK`?& zcK~yYb|n54#$J3K@!HwkEVoxT7jbz_C9RFJ@T~((a!zrCR&4Mvy?a-pc%NLh@b$ZE z_jcx3?In(BQ8AYbB9fUtxWV={>)sKyi^X3Bb#EL`BoS&eThC(=jwcBmH%o850E`wb z6mZ4{2H}?@cRBr&1l!8vB>FGN_`2My5i72z#~Sy;87$_S#^c5}GF-tpcpx6f z0f)>8^cm+obvA#qu9p{tZFLPqO>=R1crD`JGZx8XoO9C(ah}Rgdi7mP;Z>)I?M?QH zZDswNVQn0r=_Xk27H1`Z8F?Xf+Pj!AjZuDP^2XfP)lABkJ{o-&>}{nq@=0pav69dh zRApVvNpSK=>Pr%Vlb6_7@ztw5J{8JRyIoA&B`V29=+08}_Gj>oy|vbnVWe!5(%uV* z=eU(tIUY%3#C)IbN=8o`l#aYt8LR16KMnO;yEybaCy`lmCH10DAR;uz3E?s_c4I4? z9zn0Db-xbiP2sDff2YYIk{H9rmV3ml9H7Fv+zFHCd?s?-t8=^`6!@p2Ug|z3)h)F< zJ9~MpwF|phAh%-68$+@z1~@Iye8gn(O?nMCK`yStgqJh9!dU!b*F2VlUfS7hKl1OV z!x{WGu6iFz@3b8<4P#7-{^k#yzfl#J)_?=s)367Wb_`{&uaD$ z4cd!sE(IWhA&GIf7y&@8oVuD*cSj~RZcts0YsNAsgY?Z3_Sk~b$*UtLm5_L_Adnb( zVz{3l{6o~NbqHj%memZl;eaYZ9Y=QNy(8kTj&Jns5+p{%f7(xs@OOQS2iqHeoq0FL zp9oI|r()B{qjsE}4o_D8copYn6yUKg=c|=BC`B&C?*aI$Sn&+n#lEMhZeQ${k|d7+ zD=hIaJadtc!>wZYmss&N=AhQl>$l9TKI+OqJ+aoN_%Wc&x~8QhtAu?5^a#MtaSV6T^T0b!isUALI=7Ml3{{R&9+2?`cNW%f3=-YOU zqm3%eW8bx0Wf`lZlB8!%u4CG~Pw+3sQA4U+-s@>&ab|7WRr@nH+8TNMv$2R=FxWnP zg~kGw1if(DKf`^0;x4!~4-j~Q=4+E|o?Oo$W(W)8b1`PX*^K8sF~xls@nc@P@NSu^ zSootw)ii;BX{6lg@2T1T>II5qGD9SLjG{1F-2fprhm9FVz=O^_4RfdIM&@yO3M`T! zNY$hBTq1m`tCDu@!Oj2!jCQUp(c)=KmX-GCu61SJ%5ZL_zGQk|!u=1zI+S{))$PP` zc}~rA(tV}Dz#l5g!AT(f(m=`U$u;SJ13nvg1LEeLsp`7-fhW73(ko^2HG4?x1*nqV z=*Kx&nC=of!SgTt^ei*Caq@o^_`cg!vW@LiQ=S03rgX890?wsz84@aO=tmyF=bH4N z+2U^)>;C`_JV&Q^N5i^`YFAepRB3G-iNYk8<)@I$r!mG)+L#7^;*d4|DK4{{RbkOTm8+{4uG+@Z-Za`YxXgI-a8q?wNTjs=PTya7(yn3YP02 z+m$05atbYv*-jq|d`|H%*t|XQN5prQw&ZVHTiIb81}(pIXux2PxCbmUdBJ~+{5$bO zO4LlcmbLcn2J^wQ!K)j)c&+@IhL@s-w^ z@)>R}RU>#j#tkH1R0>H@Mi=KfC+Gnu2ME4^o?zer46MZ! zM;vm0+GgbOXN){6;~yH&d*S_R*H>BMhF56iisC5MqCzDuSSiR{00|smU}dWQoYq=3 zr1JU7`;+FDE4We9f_d@~oDWUh4EyG3d5Lv@qVmsa@H^ggjng7VRo4F^O0lMtI8;z-r&!(K5LuDCm2)g#2f#X&x36YnrT< z&|KRcmseVYAuK@wL(8$7<|=pN85soPzdk%W;tf83_$o%duScocOAe#s3;Tg{Yj(24 z?>w&KW2q|t06X9j*A?UUKL@;Jtp&K$?{!=6HHOxj8P%bg+W&GbvYCRyN%lz zl_V==Q!My73j3j}Q7(%Q0y+V=U0^wY;{Ztu5ce5!ptmIyJqt*H|II zU%nuh1qkd*FR{MY1V0@mNE;o65u1K85@g2tVubqus$m2zBQe-&kFc!;cl0E zWR`B)Rjt;Y582-FVx8r8J0Ej9+*|D%erL9rF#-1voBSp4t6tUOxz<179X7Gv+_d`y z%_@+uJAk{ECRHD4a7zF_RO-q_NAVZoz3Tio@jr;>^20%(T<#Gk-a!idzcD%g091#7 z4s+^j^X@#zb51&~hp$FTlqo4E7tbXwXB}U>wN`7aBr2=ZNDY@A3^v_!kVvz z^{a~yhaMWVw$fk4ULi~Q;nCt*tm7AweUYv60*P@PE`xBBA83pc%NRtX@W;khRyu~S zt!keR_3bb0`b~w7u_EXjqx*fkT|L~nv-2a45Yr&u)=q~#{gfJpqxOqqDk*6sYoJ$p z5M(%PgTW*Lo}gyFcO~%UW%!S*VlgUDTUznoer;X+yB>}ePYYR9ifykWhw(Sv#&f{H^*H2$EAAH$VCh;@*XGYf=J==XrDNZ3 ze{U<;Y~!8}3hC+Qrr?c!&d7NfU=$C2z53TbsD9r*E_Nk!Ef^@z5+ua_9M_5KIs{4O zzL95kzzG^wnkELVR7QPib&yEQ%-fB-1dn7W+fn6P;`z`#FB&)8Csr>EV=kQzwX0lOX)|gG{jVjIYSS12+`%)^@sOnPxPo{U@0wTq6qojF@O7jbcAw!- z5P25SEOzi)*ub&H8m`q)8gYOC10T-5VECzLV{4$qP&2d7hG%j}0Z&i!+Ps%jjU$Be(k8~C^4XT)o3yRQ*=k4T1DZf+V|iD>9lTE- zWM+UI(by;+YzYl{cF7VKH)bH8sSbN)4!bcTvi z_Lcc?O5_}8{F+|grMEO#YAxv!>iDn3w>oV1(%V1;Xb8f$CvuKQsG9zf%KcW5F!IYki zMn*V2Ym>Bw89p7rJf&b(0&={cnSU?-y?Zai%?IIl@a2l?dc;3#co)vKlJF0k9JbIG zZ$fz^j=r_>+)XKA=|Zfvb3*?Y0tJjCw}or^yey`%_le{yxGTmFz{EyuL}s7Zcv0I3_`>V9y?dFTl`J% z=96G!)8M#}DtzT)+uZce;num>yno>_r?j}ykJ*5ia>UUzmeBA^V6j7tL3oZJ4ES*Kv6gjP6w_E2%rUa~?S$ zh|@;budPHrA=7Q*x-m~?(X^&XBj*YWHc$Gu9I-9H&0DdHNSDO1Ti@QUr*U&_A(5{Y zR4gwUk=`AcF09aita4AxPio8YMWyeCwToNZE9;BfTWJt|noIeoSjv`NpgfGR$%JAN z6dk3PX*)Ljt{S|vOpZ{f?JL^;chK}_2vV-49gj%UbWJDi2|mAfwhcP&3#cZSc>sA* z2^_W#V;{L;j1WR+7+iQ>q#s(>uC!@!EqKPMq+RHGifB5GoOah%r%{T3 zAcC%AHnDG3z}=7m+`tpplbZVJJ5oye=y>W)*NR&+m$}j;w~i~pv7(+rzbpL11wkD8 z?jMG0r?y0n(m7&bk1ap|=RA7!uN;S4xw-K*sMYleq_d87X<{x)?IdpCo-zRZxvyLB z-OLyIL=r-vMC-Z~Two4Rsc)JVHNkvt zu!U|k*{5)!wA2`8(}tDuNezVc;&kn-b9I_>03(Md6~waXt@~Uwto&Q z=h<&yOQUR&0L1*Q@Aa=|@wTgZW#P>~RJS`k-wf%F#{h!MXA1G2xGUDSgd-Ppc(|BK zcDp{f{{VuC>YB!@@b6c@)^*DXu_8~UO=CJrZy|Yh+%pnG<&5^XD#PSHW2guM2KnFO zAB!&ZPZ?kMe$P>Rb%|mag~=-3Qm7zsRPcB_=Oc=%@&5q!vWw!6m*G?J!~PM(yoxEV zq7yO;hMFj(6BQs2E*lR}vP$W^uMO`@4ZFo!DMCHUoL@#t(-+1n{%!+6(J)ui891tk*U($W`vkOR)f7 znkL$gSzPUH*vTMm^p6gBsA=8`(sawo5<95$^@?;!1D`a;7EBS4PDgBlM|$%uTUOO| zuZsHCh-aAr(X{B^=JATl3TE;(nLtnhR@t4vE)JZ|TXq#v$+GXBV)5xy|n>AF{pe`&vmnqHS@Dqh2JrrSE)rN_*;Qtr#; z?QO(>TXq2H&xw!DGTa@1XIiDDy|sUlwjLAqD#x{{Y#G;ye60y7-ggj}+?mVk?)`^sBgRtm9c?hIrB>lGbOD#BUyD z-lLTYK3&Jg^-qqX#`5M^uCFgqr9URB=2;``K6p|fPEL7K$j_)bJi?87DpYRM+eT|o z*}_+;W9*9`4a2YeG}ECKa~L5$XJXVjl&+J@H#)( zd*Ri`jJyT=Wc(u4{7+%3TFl-PxroR-i-^4Vx2W^s3|S{qua??CBO8IQ%P$)Id-$)T z-wRDQ#`COhyMpXo5uE=3cL%BK^{kyg;|Ii_6x-e=x8m#BTH4vAfufMbv9ZbdFgdS3 z5t3uG3prPDi`PeI=z22FFche|YL(^RZ@Kh7=J4KxbqyxpMol&=d&pu?(M>Qy08!lW zpVR4Be;Ixr{{U_1c76~yT5tBYsdCmfP%YGRNgtUT9l>+B0fCYSdieLow=!xLS`Esi z=62J{+{rA>A)WWJ--ah2D=L6E!e^dKbf;Ur`s@{{V+>=GUz}M2Tal37^Wg zMz(W?8^ZIlP(DU%oPq%3=cbLI3-8}j2@!Bvr`H#WQa+e5;#7Fz8n7l zf|+PGaQMT*m$Gcd#g+4wROD@V2IK3Ha6jEO*%+y{7u1;@4+7s{4vsg8N+kn zZF1LIu({VHg)cPN(`=VgHs%aL*o;|;EbIX-*DSe{U-<9f%Wn`v;oUC6`u^g5IWI=o z1WDu-l1G!woW~y6;)+LdSQXp11@epYSL{FWhv9#PKW6QJ!5%Y9=Z8zzWz}w0!e)iS zM{F+Cqoj!8fz#w*0U0W)P8ikZ{{Vu7e#zQT+Vt%s;U2nRxrObgNwm9AuL~eXh>x^4 z?$~h4(B$MCn(t1O6`r;{`KeB$XQ%kz;%pu{@PCQ4%S14r>}d5JKH@ug^A)osQX)l^&JNu}5$w-4qNj!mQ! z$;ll}YIyhl3OVr0Rq%EH0EN6W@Ur^e3&`b-SWgr|ckbNy3VAs#-=4MP-Ut1lFE2bb ztm~Sdn{6S|F09Rslrk9J;JRER5t77^xCD>+>C=&1wI@z>;eD^EP>iDneuCM_@Z$1& zUkTXBcN4;wO^0idD6mEl#`NsoZtKtyGFC|zOmy?4^X{muNk!2?QYA*yvDC`vq!o_ei>Z} z0CLf$NXAFnC&S(lu}gbstl)nn1i1TU%*(={P2OfUmf=a@4&Igdsqh!$=ZwA)pR+zuRZx@9j6@t!Ka&+AoK+=_RUeA~03N(YJG?!Xmo-}BEOna4`^+w%6_1l2UykTU-OWfHCk z<0#n2fBwCE!=e0A@d3Jm+SDQzkrcdk$O#3#ai0GG=dF8%--$IV9{_5))z#)o>8^}q z{_)+k9-Yd8j=8QXiuPuS=y?~!JwsQ&&`?}y=^Do{I5QSG!5)JhYt3~xk>>K=CH5fK zr}*kcn?W8_ySA{;9X;#Mo)0!)Qrpg8rwM{{`Btpwgx&QriUVnIO7J5G{{S8p*nZDi zmA04h2Fp>rouh^+^CYMNAQ+|SKBQJP{k722?xdGUmIYYX1){(wdhwDgU-o~qj`QLb zjjZa}lH>z{&V08Y){(LIPl#N2yY^|ddn;KZj!icBM9GGA5lEsp48te=SZ+s74;Ash z#tkC&4Hc!n)a1FtuEZ#yXE^Ku>7UNN*|Auy{sdatT)c95dLE(~&|`D?Ww`dq81(|a zIrzvWn?jJSi0n|krLIR0b`Yi27eqAd?ZdGx5j>QFJ0c1AH+n!borP-IsyJ3_z@fIT)C(BcgeRKJoS2yBaE?p;AEvQ5lg5+U=1y4izb*Z{G zQM7Jg!KWK{x{YGpn^S?exGI08H%duubszICAjJj z{{ULC{A+1ZA1(z{Pm8}1wHH_= z_<^e+Zaz!ta!1e}YPX3rQERG22uuR!Xe9ptb-AqD8&aMkf+7lmBIGFR$mXM+TIG8` zddEsGwcTbyH4hPL(n?TKk!Ze0N5ws2aPlz_-OCiAHwUYcy`ZDlJdhrF)5Dr7^Li$K6*474irC6pP_U?Cank7+5F49}j8TL|6J;%d6OEv*yYv9d<@7(D14^ zI4GfaDd;m@G-FCW>y4ZKwd{GbS4pR_@nm{czLyl*4x6bpjlQWP#bkcSWif$h_Ynfw z05FYXz&H%eAS3&^o+j{gS{#m}{~ z0ElN>uMBCEMPXs5Ndvvul?}{H=380NM#&)N3#DR*A!R*qLCx_Ffvl&BZ+tgkj-f2A z=9Lf~PPU~J< zEO{Y$;1F}gd$Z~HUNrF4#3DzyxG^HPkidPZff7Q@I&66Yw?B1*U~a;nojxQa61}YG z3#q%kbY(dVq+0D|K7~L(iLXk!cI7;JdgCJ2->K$zQZM$4f}s4X(Yk!1y{F-$Eysm) z*L)u;0vIy?0JKGX{{VlkOf{#sx3>98FD!cS2TJ#UgwJy~fpp8Za28~6%!i%AM<0cC z;A1G%)acBsLUhth)$#7hJRGu#g86#H3P9j<4bQi*uO|JZTN+k?`N1|#h0k1Ky>sGA zqIeOBl|z5RL1v}QodLJJ;QJf{{{ZW+8vUTJrMl7d45%aQauCW7KQi|1*ZEhMn@07W zk86jj!U`$*oaew<0rA}N1^~B*HL#~Xx>eZc)BgahD<|SI?bl#az%j4PdyJpyTE7E{ zK0PtAsPi;ET|phm({6wti8Yh)Fv%vX1FG;7?t$0lJlCB`dzNRdi>puTsjkN(HssXQ zzA`c2IIm>!<;}nRE8QIZpLlNy+VpZWwKn$Y)A`qt>6#v|;!hIW=sF#Qw^x@fwhL$! zvq*4G&<7`hUd8cGL)JVg;9Vz3*DR;Ay0`GOeta?^M^D~O!5>U|aa{^Z_Ud?5rl+y% zzp~ftpWu($U*S%P@t5Ksi8VL8mLXvsm8-0AM+KWTv?vKuPS~M|Mo$BeOiSXw*zd=G z5%pU=Ufbe7hxCgbDF%$oaU(ptEoPqHc`>m@;PH)!Tjo>CuF<>7zhR#UTkE%f5Omn( zk=8$i8qM1i7e|I`7GAl}Q}66~t}Ekhp1Gj-hTg{8QXogC!uw&6m14*rIraIm&pcOM zEM06nYMZsrY0-^ei=p=Kj6Z9gBldUj?}vUL+~}{bT-#_+rPbz-<46obSo0acI(a;Z z(e|D|d}WB-cx#{dD8!y9lU2F!Ps87`YPw~aZAQZKcrUC&ydA4cbp7yn4Tfo*SWW zw{ylZFkhS%`|zRZ{66*1Qfjiak-TS851Qwte#V~}d>in0;`X_F<2%W1H2q7%b6B;? zS-ik*o);?184PzMPPrWqKVPn6@gKtf01@aAUs~&5=tjyLw7WWW;=*HVaU^bJ$We%z zGT9)5!(g@t=ih_$meqAFU_lRkG+lwg?jJ-T{TZ-<(7-lP4n(5NEcOO{y%?0lp7*LnL!Xb|XQ zO}2PuferSj_IM;*G+;`ZQ-RJhIq%oKa~}vSn&wXtc@9UH;j5__o(T$9AaHVixc282 z-vt#-Sml~(PUl_XT?&5+S$I2B0^UJ=rZwDtZMkJU2HMLaZEoM*Q`nJS^ZNmONwT;2 zhk4=cS4oNPb$=3R@XLgY$FRyARl#!dmU8i?nZv*9^a4pT{kyTSy(Zn~QO8ETf>|QtA$K z{9}#{TxrEiQ#h$kN9eAvG%@P;3oM5_$uUmW|@ujjuGMR2Pxe zOk)S++V_w~$+g1;U6L^9YrOrBJ|k-3{93lLEv?MRcwZEuGj?d zcIS>O%g5Ko(xl}|H?_YsYZ@4Om13zq`aYhvv5#~66ZoK7NVfXo?o`Z1OCp=j3BV;l z?T^TJ0QoM3ZFHUi)wPtlVJ)_=VQ(9#P!vlQ>%SfOhCX|3_ciyvpW`14jcQFw_rw~6 zX&jCHpJRQhBSm*+-e8S{j#eYI{_v3dm!=5aUk>8gFT=1TUuJ-yAVNLh|?I}iZE_2ZLVF14m= zv1wjpHY{!Yg0FKR+KfXILZ0M$4EC*0huWMrG5B^1nA%O3+LJn{8^3>P6hV*sAcZ{k z2j0EN*`tbachwtT2sG<`YsVK#Wk}?=&^1}JKPt*BCIJ+640m7>a7j^*S-u5)OuYS@ zyiKV1?inoZuC*;DTLEV(mh#c&hHd16Hojp@b>NneiI*1|{oHXw{f9T% zq<6qF28@N{sW>Aarxna={{Rj(JM@NI8;$dAQ8FBPVfKyqlW|v2+2m)8=B83t<$DVb zrqkuz%G5kPr+At}f8ocGC5Wl}9K;hM4l<{V91PAQ zZco%=qfI|w@ZPxvwZu$hRzkZDKrdM zg;aMcs!n(a6a#^tyGMII3%Wi$*9MuO=)3L~Q0qFmP=h%e+rLl8&~sAMAH8c1Uhle% z{{Vwu2tFfxUHI2~;ctfi6TQ{-3-*#3Z}7v%160#I zMz?8sbE@lBT)n)j8CVec5)>{a8`?Y&gZtnQV*dbwfPcYEJ|y^`$J&*L!(SW8{{RTa zlF%-g5&$ki(Y`2E?{8z6I$f~{4#6oxBRmy|J~F^B&RUnnof_v&wui;O5wO&C2%rM% zP}FVKSsmNvVp+#_W^8(sTf+?H6?gJErCleX^WL+h$E#>!*nC0Kt>zL8TfNhaa6uos zNfh?{x{j5{-gr*V+Thz=c&^6jh|07x%H!7~_h9xNamS^78*8q7Elqn;@fF8~G~2|~ zRuF9-$~~6mH7a&+al7Rw)Qt1n#J(qdFVwY7Qv1d_Ua4XvmO~)ZY^_8o5_SkPgMopJ z0(mDSoXt!m;Txn7uTD}*Oy>1}4BNq^%&|=inW9_>gqi!m{{R|++aHZh@V~;4*nh=Q zKZ^A0DJ^cs%W3W$N_GIjmtwX$;2%-=*Cj3W+)wsVb0Yktvu_}M1u`k8P`jsWD9AZE zeshw4&uZk0X})8&Ud1nQKb^lzA7|+w82mlsKZj6wLrL+ZKihL@vOHF*!Xz>V1;?0J z0PMNQzy}-?o;)w(W$vy#AuX-#?ZY^^MUf_vv&J$AJ@Z~)7NsTal0_UlM0>Z)Pc58e zd+<7Y)%WpD?dFXyg*6MA9zQ5FKsI);Imb-nj+yFcvTs8erjhF32Q?oN_>;o$X_i_& zqs46+#?qLWvE!*buumSJjcxds!WNohM4FX=EaRfarzCpr$;DdzmHaf9o*rfjLq;QK zD8v#BOU6GRt#+O)(BRUbOT#34CA?oW&4Go-*N%A|s|iWQb~+&z<92zl@Qji}=Xn7l zmdavLVMW>dru0yKYPj%DlYM9eRoB2?bJXY4kIKC}QnjAX4My*k(7qs_KM zBzA7gxwshJ$RDkE?~ft!{B>&5t0-lgarXr)=nn|X4!7`SwOFmpeszt~l+P=)$fy4R zK{erD5H)QIJyJ`@nHqa}BuG&oJ$7z59mWN8I6q`^)7G}9GvhmG?XRt8+8u<$8^%Z7 zY>#nNyd8PrjVIz|rSFRTIdiGnDICDMoFV?K!G7W9&Z-f5AjQXrBytar_1FYUcj{!@5Xf3gz;uNB*V&iDTS7d|3*HGFaL1I2g0 zXqxvbliYmsZB&s9$VuQaQ^?5L2Yxgby{)p_&UeFdBzWWwB0w5Awu}G|?&EGjI0s8h z@CDC?H4Dh%isnmeKs?>nn{{V`NaG&1rToZmo3d-8TCvYiT4(#5PEB;Y=}rGOi?Ic8`|4)8dAseX3vCNq=c{3tOtf zw~|ERVqqas5s$2YD`3CQdY;0*cN3g@ULcMhl&>2!*K2;J)bP%p2cB`)bkCZ#IJfJP z!x_kXe6b^@53PIe!Wn1(0EJznl3AT2l!DxU%V`Jn73XnZ8=n_Trpcm4U}eHgBN;z7 zG1J<;U%|Gf*TYtp-bTU$hmU9*NRhLVUuTAus-m+>u^Q;h!r zqh9*|0FUyoEBM!Fvv_yIvUy~mvdzdHa8vq``PZj>QgWXUgnM}!?YUqx$L?;2*S&DR z7p>xpz&Zu%$U+Nyobkc=qyDw#W|wuFJ-m_o@cEnAjdtO<%R4qx!9qU zMsRbuepThi;b*srN#i5vMf*3a#rs0|S!0Zu8&`=!jtLxsKQGd~v-a|}RPlx4s+DH& z+(T-PpX+*O>0dZ_d&heBggzlyc<)4!BD&J9mTOm)e8az-WFDigf2DgT#*Z6oe-r#W zqfE_+WU&l1Pl}C(?UWzt6X<9R~JwN^lp=ksBF8JXH zixZEBHCxa_bM`p+jpP3Sfe>i(X!nyxKZpEF9Qufr;m|UNAcjZC9E^O` z@s3?4!&tD?JU3#{SVs@pB!$?ymG?-@>_Eo$U8f)_v5oK)4WqiOB^s7a;YM`l7j$lD zek#_cOGngn#kG!N=H1QMEU_w`xl*9ylxE$>%Z#WaCkw)wv{Qc6dPVKK7gf`yjfoi_ z>Sp}%+_AO<^spZZ&I|^?RJk*A_4ZLH2|g*hyD?gLZe@<;p2WH#`rh zcy0dxXh-oTk3FP%Z2G;-R&ZN9vUy1?Zd4}nSjMJ6yPUB*da=t^T1H&2{sF6pJTC>I zZ@?Ziw(wS?uS+I{d8yBI(Lrx(XTv*|m~wqSZb0XrE75*2{C@DK#t#`=!DZp=SS>9y z80@FGTX_77iB8!ixiM`hf&N?~FI5fluGrJTyhX2F=$gi{4ZX{xW#oeCVsbp?CBcmQ z5s=+@K8vQR-D}$A>_o?B1IXALc;72wf*~uoAtb7! zveZ;Q-kxPp=2nvEULUg7zj^T9+WShp(vsT$08y6bNm#DF`@4&G-0T8ItYJc-n}CGi zWaDq)C+wBtOARXH#Jc>3-c`3gOr&5g;4sSAXi*Xxy5X~z{%M+P=`Py%@yEC(@d2#%s#XoDGf`1Xd6nqu&Hi_`z z;?#T}s%X((S?abLnRzt(nO-=APbnKjD=9@TH!j9+-u=g3I`pblUhMT}sfMjaonCoD z>20a`>-$4^*GAUUB+Nu?u3SfgQ+Pd5f`mDH9z{xyrAH^#oL7MU3h0-*UxWNd;?Ex-pHRN} z^cA(0UMo$2Zu>(omAs%v^2Y6|>$rx>k5Tvou3Kr|9lh|LfZu1Z@Q1{&?Rl*=0u}u0 z^zvB|hZ%}JC=Zy8%_4xTc&|?I@@bzIzi#WBi%V-sbl(i=PaHaWX_2+0cGlL`+Ife{ zRaG#eJ`s5f<%SsrYskNA&xL*s_#g30#ae%dz8_v(MD|vizLECd66NeIr;0QYEOL`I z*p79bbCT+-m(J4RDK{pcQ=xLk$!P7=>ou>7`mT+y-A?xfl6_KngC0)Tm75=iagS=m z@ehGKNBc^6@56d0f}Y0W<4ulh+2Yiqo#1%9`&i1d+ru=6b2=%+WB^%?04BWCM)94; zi@XzKeSYDtkUCt*gmS^U{JkNahX_qI0i5EdyoS9jzcyFp=*@aMt@;OC3HH}L`GzVQ>@TwGdN+4yqZ z=leX<+dL*T84E0iNeCohZ%ip;w4T-<8+>2!_rV_#{3Dmb{uI&ld2FK9tOk=E+j+O& zYqxi1k>#ASh}!574nu9i?ZB>Iz#kOsJP-R%cnbb|cp=hsUl-_iZ*RH$lot0;yvoF= z1y7dwGn8j%MpgwOH_R=@$2>h_|xH!2}M7|Z-|;cop)#92<|NY zAKOaLc@5<5*E?>`#epVRWHC3E%reAeZZaPY_;LFJd{({GG_MzU686_wlJ`)xeOFFf zr-#_>00GHL}q>HyDO1i+QeQF=0`*jiWd`;IYTE zoOi4hI#e-VR*ZDE=JKstr5|r8YuNb<_P^CUf2+kW!&k8v7M>P?;agi>Db=HhI~S9M z`C~!}JN6U@*Ki(D;qMFiJ_+#k?tm3F11rg?NI!JTY9}fN{olMYZa*mi;vh!g#~)05 zdGUq%{hrVJ1~_NGbhf#;@{6;nR?I|l2qa?!9M_fjGf&a&tu-AlN6{E+8a33f1b3?) z)E86RUZy2vWhl{nvnfKX2#*9XB-f!w1s{ewTI}P*SgU;q=nZIS8s??qd)TdfH*0Zqc(JIP%E@@h zAxo3lNh9Vb3K?=mcOMn?e~a3$#OrN4;%)7P)Ea#M0A{h0%TKen3dhT4%G*YPSgy>2 zpSn3AL9d}dW^Vv|A^n*?0!QM%9r#i`d&fGo%cxDJ$p|TEp;SR>HaSv?=1F2Be)lms zQx4O~7&)cw?6tP$_<2-}o20Ly>_4z)?2X|RnsKI%8g|hRb0R zMpGz|?q-q5FOWjX8CO3_e$bz@zrn2|;Ux2TYvA684Vy$Jg6G3pRkoBNn8Xq`f3vPk zN&*60$;Qw?Rp-S#0r987UK+Hvy7-y#8ar1g#!zi!HyVVbhMkpTQm6|upaTI_K3+a! z?^-^s;!hIjOXB|k3yF1|DsPuj(k!Nx7WUna)cntHFnZ)yeee_D4Ufi4y??{g ze$ZYR@Lr2=Y}&hA_{Ue4&e~86Yb-0}MIm{&eBNF%$fp@26!6t$P4cyIR;H>hXy|d? zEY~I0ykDnjvTt-Z9%Q9Xcl7DkBOixa>Gct#YO{Z8YBEJ_3O?pZWWn4|RtJjbd`IAm zpN78}EVSr#DDJe&R#+_csqAho;JZdwlg>zPQYpk>Il**BAu>S7uB*m=7x;It_f z{{Z8@fo9hQWil?9+n1rhK5^@TlkJN3DAjXLN!^})BL_SsMNX_-Uiz;6PCLYYGVp|w zTv}+_mzdC7yos~Ri6q8;MRR~#fyZ)wxYqEGgzUAwMkwy$xxBWywG&%kS_Wtzk_wg` z7-Ofus3N&v8GIY?j)kOL>v~7WttV8L6wG!oMIuWpXQJe`4^xBb!V9sEY|&w!zi zP0_UjHMDsK*5M0A4tST=++${5#Th+dC~|Q?^?q)`Stl`aOgxB#v!j zjeNUBNMn^P;Dr`4(Ii_*A*-HB4yM|d8j@b=`SbAC{tbipq45gR712LzPli_-grT!% zV%LuD7lvdLLlKZJPEG*htxw|L_%(m_gz%ir7MJng!*_Rx+l+U*WcKPo{vgU_ET^0u zxHvw93tl+=vOHI>d{xnWU+}M1yzr)#r9%yehcv4t5i3I*;aWqx=Ou=J*b1XLQ0@AC z{{RHe{{VuAcsKUx(r^9~d_X9kFwnzIX>$3D>Q;H!E+aYlWL&Dqy%;h!4qt6@O~alk zG`G;VSw>u~>d($!gJ1AMpW1`rb&R&!kBqz}Bb}QXE6c5{6%~8if=A1qM|#`%r~d#1 zZT+JBQKEf=!&woZBulOjryJSDKd)dfj z0d1~)8Khv221JQw0PcQZ7mlZ;Ot7-DlX0`xzvyVIPAb+rTYvZ@ckOH89eV4**LJo# zfEq-%aNOHTIki@hNf3=^mK0MU`^E~SMl!NTYmj-!Dsl+UK>*e@ zxBM3m{t8p2>LSlr__GY#PBvV4OGpY&)LIU})?ZeO)iwBnsZs8aP4Msh6MOcL@YllV zv@eNY2z3>)vXSmC?JV!DZe_b7W|?8xZ9I|`W(pf`&9t6Hc?a!V@M;f=+I@)d&x7=> z3gbzNIIbl7BHEjB(iHMpQ3uMT6SD-a7~_+VQt(gw7k~EEu#Bd!;co@_faIjFs%S45 z$OPkkw9uw|;t>24y(gX-e2xOU02o5~*06S0)CmYVm8>h<~ z$(a8Dv($P|gSFdzdM$R+<~gmu(^ljaR2!7AEHlu6b6%?#kFWe!;us#i_ zk}I1jCskO_&od~DGLe(RHw&oUcwfU;ml`L-ttqXoODuBU%-g591Aqr)-PGU#mOg-2 zMh_2#tykJgH*qHsjK)!*=~J6gmi9eA!1_0aHH)!ptXrLXUAh}B6V7$EvN;DOayMhC zFOYiUy?UOZ`!!hJ={9~Pv+=TAYkG4D?Ch+TD3Lhj3p-?-0CE7y7#Qb1P5U%!9vxY( zZto{n^4R^RSn_<#0hJxd2d}BmYWFkZe*o(`Tvz&Dp{82T03K5vR^HlN`W4GD>Nq36 z*1tOAUNpx?9Pk)7%Lx2hEnoQ~^u7z=+`c(%jLi$tT%v zAhVLz=gPM8-c6gxh$waBvmS?o*qjl~ct=;%+RE=k)n|_4^6SbDmT1j@frNwR01fQ< zETK+AF#v#9qwyB5`UaaPieR>RA}p(Vihfr^fEXSIM;!WnE6TO`>~5~$mq@g@)9#vg zSnez)coG>)0gaKd-b`JYcN~xoHalgL*TnG-6ZaJqDnT``nmkS_!BELD9MVm~TAfFT z{7m-~&8p9H6l)yiVJ&umUdpP99aVa~y9nFB1=x@RjCr?>Us}|3$Tb~8+2SJVJ9JlY z@3<|&B#^7rlHYYe-GBfCH#Lt2=`!62H9chqfNXQuqN`~4+ zYWPm)T=2!^v-o3KeK9WQHaBm%ZUZtenv?EzeC~=s4j6pNh5(LzjH`ulm~6-EwJIq~ zuKlche6#E(E{mlIw2|S1!&;5MiC#sqkfRKSEO_@V2FM;5aqYVvt26%jLU2 zAG_YpJ$d;+&t5wYJ(}?~mDMfuZ5zjT6PI{}(Y>;_G8Nr1lN%5!WJWQ`JU47sZ^a)B zc!yN*{k6uQb$f8fNttC^J9T!B;K?fE3b<|xsN)2w>T_RfhOFUI?H*-nlpQ@E;CN5O z`%A^}EH_f^FwdzWL6ON=FD_hr^_2jrE5?G^C0oa*Ol|d&mWS)m0j+0EG3eH^kQ(ba2Zo_RY3PVY0#l_RZ8%Zpn zhAuMLBaGK$u2^e2{-30sUPE;?w}%!sX(ikOE!EIfB=!mv0oVan(^fcDNVy(``%Fpk z4&plx8GKIoFo~($&-Po*UJXw(Y8MQnVU?l3VyqDe8vtFPalo$<*6n;R;y(kA5{)v; zNFUm-ZETTvhT?BBNa8M8;+lCdr5kE611$K+=xz9iE>>Et}%ePBrV~7`DzmZNs z2ZMOSk;hI)T-PCQ@jqAa+*eWfe?!uA`EM;A*t9otu48O&T$zX<;A9bl(2Q5#<~TZX z!#Yx_2+O*UPTkKJE5$~RDO8;)PCv@#*1hgl@jrn4Cu3qS{4=Ip$!Bq>tZ+wtFPiBL zsAL3{!jb^NAxBShT0aNA7U$wz?I((Dv}ori-O!}c)fQ_24T2_BbWE}0`jLftyq%_HpS2nYb(#zTtp+wT(}4Cy-F ziE5Chq?)(co?ka~iXdH#BVstq=YU2=eJkMHRAtJY?bP}Tl2etA9^pAr70JBGjd>5$MT0Q=! zrXLaO>m~duCYIrw$c%#`kcn;}LNGG>h{ok7;7hvJV{TOK%Cu6lxz&f1xonGn+nNuDJY|1><1ZKLR=U546`IiuZXt-s z{{UzWvtd6V^8!1u+?X9OD%XI1Dtuz`9<2Inc&A9R(4|>o(U$7lPM%kcg;5-F?cF=R z&CFy_u5zK2kPnIK{v)*biQ+9|#8Ft;*~O;a*^d$}q)QaB+e>t+HX+LP8Q3-tEpVA3 zPo;b;)HHirYb*OrWqdbr;r(1{hs2VvlNwtpg;bU$j!E#(or)zpos~i};mkF2p-Sh% z{{Xj7!0jjDcgFjZ;e8df{U=$rOY0jHxmn|d+1fdhDHT95`O(PAmP)2r@&?4na9L}%{{VuU zcz?zJ02mv>pBKC(;Aepkh_5VeEcClTu`S%q3APbiv%3K)hIZM?tc0K+I-v94+6(>( zU;Ax-&RP%rE)R%a2DrV@VAJ7!Q^b0Frf4*|W^W}DNe)S6k)u%fD8-NlIjyP1O<9~3 zVBt`c_ImxIbqR+P%6S$6DMFi!!I1$no}uZ+JG zJ~Qi{A@LWAbR9a@O*$woEWDdPu)V#!a!9))jxCEL9n4*wUHjyB_P^STMEFJU*TbF{ z@dt+1JwESK&|tm4)AYxcQ|%MS8?-C9hQa`T{vSM?DNMopPH27;pTbgnQ1I=o+iBh@ z@az%a+Qkb;6gR0n4486Ab(%fQ2Mih}Vh-$%2;y66(ce$F?5j={X55{udEdi7f%>yOr3+6yByb|$E+PX7fpo>R~P`e$0v@J=RdVH@M`}63@o6JF5|>eJ1T?I?DLM9 z8Cy@dxR%-*ATe$vM#!0q85wp&zcPR} zf(8aF=O2v!02FmCd&M_5YpTa{rp*$IaSmByhiC>RcTo9WS0ra6JoPs|6lu~!b>cgC z;=#D^Zlr}a5)RmR{{ZZ@=i26;1?8@s%Ghn7raKnsKQGq0u$Wk3_IEtGwQ5ajP| zZ~p)s`19f==f~6G-wkNDmijDuT4-8S6Wd!{tCkIMCT3|1BBW`Akr(8NhBi2p);bT*Ww%0x9<4?sehx6(R+AqXQOVzc$f=Qx+%IORxCfZjsz=t7A41yJh2m96K zULyGEsC+N@*{a$20vioXUtY~^Z+CZYIjTi)?k`yry)dh6bsOe5ohf|&kHDaXJr_f)qZ^FxuiNCeguZaFC$hVeSM~33@ z9nG7oV)!YwwzrM40BlV>GDsUMB1I@9huvO-`)KRhe}X$cj%$bkg%h!=7oDhe;m zJpef)j~D&4EpDx}Ypp}cGdxz&T-{wWe1_OuM+l8L#!JiQIPOR%0H_$zR55sUE&l+_ z?vt;9#92#U^FD?6U-4J=UB1z+b&Jo2+D5m3VPgyytTf2w6R^Q-$mqZ`GA1xf3}YWK ztsOJ=j_|*N{82s5t>N29^$VNUy1cTsx^{@2%0;OX&9*EmfV(geurLPTe<*xYtX=qG zFSU3l#P`+;@@4HU?pP=XK8K&jJ!;;I@e|@Hi_5g}g!9iJQ4PD>BbIVL=-`~1>iX59 zlu^d%UAEcvFZ>ld#QGnK{{U#~?-lrOS4)^ReG0~Frw8szkc)7?jBz8MQrWMIb!md$ zJ6l_-;g|*-7EB(#yjOAY7hcu%cRGiOZl1#N?co#J+$r-U%xcKk12`yG9@stWosYu$ z=(KG~!dga1*!NsWM{*aU{wFo(Rh{ZK&XuGQ(CFIb{k^hF2q=oU^O_*r6!bX9Z_A&{ zv+!q%BDC3g4y!zKE3DB^1MHGHBV};vMli}o;ol)o7_TgCO5;bJV6uq(!Bui`xZ@cs zk~yyH;SY&CC!^{Osrc(pgHeH?b(2oN)Gn>kP7c-&6jL0j!(c8CHP2EKl4&~~m<&`f zc(_)Y=8Rfxcd=*UZ;5po-rvMGH&Ra`PA5NUb`2>lg4k|(E%d-0Ys@9L)bFO2;N;^t z-1t2|rfbxGH+)R^Y4Kjx*GKqG;9WPvFJlpo<}dAgtFN}V?y%wJi9#L7=cS1u)l6sw-SDjJM zvsa6y7L}5-@^otn>i#eIwei~S-v0o?{{RnkOFcsCM$;~ve~2{;xn+vOPgWZw5xmep znqvY@pehtHdGPud{1boSJ;&|q@wVUK_rpJix>k~a{jbCxBhulxvuhh~m@|_Mf);GC z*pefGf_50u*1t=BDSyEYz6AdO!gpoj4}@Q}Uy8IVEjI4L`sc$gMcj9n)^EBQS>1}O zqcpCbWO9TvI)D|9eH7I>d(PKBY4g2~pW*k$pM<}%2f!;|3HZmxIxqI_ou=E#;&`;@ zhkTlY!l9NpVFPRlutRSes93F`hBd-OG5P8HWc+jS5A7N8n)Ae0uPWGRx^ktxl#+Ri z6U?!`5y4VHQ3+srN#^7L*ss#>hW`Mu&+K3D#c#A97JM_g@sT$lXuFfdccS6eSqJ*T zGGpgMnruvRvQMYOaB@yJGf5aDuOzYeHHvVH z3RK!npMyGekBVp2;no{Yxww{Gt)e7ln7LdaRrMKM0mwM7*5CLeKl~GS#2*1JllGqY z*7MnTm95?#6T{3(ojN%q3nbSAKkJGyQ6EUdzVDw47u2>t5Hw`ciuh;3x(1H~kR8%# zR>`xDly17IR_&n6_MgEh?X7@@HVTcwybO|X0|Ie^|sL@N^+iJvjPKil_VZi zaC#n^n7KN9uw1G9^f87VDxBB3%>MwvMgIV8@7XI%@J_G$GW<*MoW4HPb$u#XZ@ew0 z$%3mI7Y=0{BaN9{5ZT7k7y>^mJW;9s&gT0~li@G?5#vzRBh{XD(T9jUeSGhGii!W>~& ze&$mQlr`L4BmHr;B8Z`#tWh(n(ApFYb%)JBs6W zPI{chH;0UQRsR4V^ExKy)uZ#T#GkcKf%KbrPK4hZy5gA_EtfzEx+KMKMm|G6*kEX_SciL=hc>V!0E<6o-6c!NohGn0ZzHN)v&vtRrX{{TR~7ak$; zPl|P{F(XXYf3>aRk+H^2wX83W4@Nobjtw^o4QQX^{$QN!esxLvba)A2@^1eCVNZtT zWhCr^{{UTx0(QmUlyxXk|maNg@0bfw=o3J_Yx#QOpG=ZfdFQ`7Y|j*-;xPlDgHKl~G8 zK#KRl+M)QLXQ1nmh1PU`6t$K8q=iNjXp-6p-YMhU-dw@*yO~I2XGXpIHGa%GP1o(M zpzGfcJW`$^@GgaK{{RUNovTd(!FJIojM|A|1Q9aLac3hO?P%CIAdsWlHShR0cfc1n zPMSaMHK^JmoCLh^x=vS~pDo0G-K#s`m+a}{KiKp3wY~7*@tl4mx6|}pJ44iN<Be!u09V2q7m4+MjUO2s!Fog}miHyv=6FWo zZxWDRM(nBwa(a0|Bk-X7=4NU*;rfKs%iIz>ueH61_%UoAbQt=e$syxG+zkV!SMINvxy|RZ!=N6;F&-^ zo|)}mpU>K6*{tOadD~~|l=(0jbRj)CQcUzu4g6xT@#WpthV$s+JEbwl_N(~A$_d6J zjhg^-{6OZoYh5DiMAq+gTb)UvzOrE5{7GBqy;|1aHqA4At+A`l9JCCcWYS`fa|K8cPJtrs762NbMqw6&WnU za3t;u2mq~pp9%X6K0dA;-8)@+uE)f2l^T_>6zg*HE7{w#Gc_wce#Z6Uw!688@6A42 zTybx1M0*PG8P$$iQU5D+u?6O%wZ)al#M|BvbVBmVUkkWzbwk2_ z@z-i@$p-^PzVKI!EZXzK+PqWQy^c#Ynx7@qfq)P15GY)PRsjZjlUDx32EC`~`d8V6 zmvNOu^BI711i@7KkF*w+Cnd3wxDmSmSLpe6D>7KWV%lk~?XUbf`5k-?5}h|U2N!eJ zbf1hZqiJ_qcDHG{)g+c%yE#@!;#lAK4gry$a;Q1^%xVAys<(#!0B9X|O0^o&SzAn( z^M_#Dk>fs0Y?&nTIAfmSunER%mC}ZZtzFr8eY87qqFv2#64 z3}cVVyS;b9@}G!Fpj{+ne`HA{VXh2t#&^Sj4hdWUxL-sa2`04fz8Ed!DHXi&NK}6f z(vj2LR&%L#tZjwE$}r`dyGM<9O2+j(ail!ZH(9pS);}}IxJd>lPvJTB=bqhb0lozIrhg9GX>!@GnH{Rf zBCCQj6n6%rn(pTEhreqdJ@{HZK-`NLyLn?jGB}HXMo(|Ty++^9Z5jrV33k!;432-} z=5j~jky}zs4RDusrD;^puAZ!{_p}w3^+JE4lDK|>%zJOh(!8z z#6X^Wkf-rH3d*?fXN4wH=QPSQ-Hu7erys2^Yc};doFaa6c*g7E-@?y?I&J5RJ_g)r zlXzQ8g3`-gZC(iqP2`JZm5?FMSdRTj=hx(|Pgc5%QIqV~rLo4~-S|I^e#`uH{hoX+ z@mt|9i987f(#dguq)!!ubIw<7*2CmJbAh&9j0xalJ*)Gx;%EF4bM~?Db-li+qUaiS zw2udydHs#6!dl#clA9)El2+}A)$)21#VEM1CdT4?{iFD|P}22_eQq1GZ#AsTJaH%{-Xm=_x|Oqx#Ihv5Y1#l+kf7Qafg7F! zD1U|gQ}FBJ2g7}AX&yK5t^S#MxGiI<*h_6PjN|4>6e&&f`q8)bz_( zF0~uY9@2e6H@8$kN(|w+5n*XFfgin~0s#o9Kj;R{pa%}-sol^XoR z_IR!ne5xBhXd#YQBZHDbAmaoY^>2k9D%8AH;YjZ9t>M47o>PA--RY2B!)mH1^H zus|m)SN>WI44hYsc*DS&-nXo^=aF@LaCVrZhaOBthziW$Fx!*2YXv7GpG%(&{B7|I z;SY#4FNe39-Q?DHI%?cRH0fiyQxuJ#-Lg4CiU;3bRF(r9-l1J+_cl(-);|33POQ7~MG`o-nig%FpKE-XPcWUiLKS}Y=U=c_(X{{VuJOQlWXzX4cyU&k zEK&pouzC`x4l(m(k0QR5@jt*HgP+>ZuInGNH;69gmG_&2;yaL$s4PHO(k+OJ7~w$2 zBigl-N&TFzd?BTHL&YBpd{J$t=<^`c^w|(x!wQaY7DAw*z{-(^?hQm@qX@;tyF1@c z^EoH))NbovQ9J|SKZQOl@MfFhJ54o;>NZhM=_0v!+@?ypl0aZd1Oh-HjGR{0$H1?F zy0w#9c#B58buw?;7LgRdFc=39GJE$OYJUuTO#Pqqn<(^M15Vep$YRKf*>o5Y5qUfa z?l3}W!*l(KCx-OhehGiH zZCIO2xE4i{MTR8~+ct(!vH&rGo~FLVw(-fCuPaRn~DloEJT%!-cqT})Z0QJ%F z7Ny{y5NP*0HQ$T$h@zebMVI?YV_}hi%2=sZ1mt%FW}GW~JL>f__HIv8;{O01e!`y= zb&nnEdiREOlv_ae66qEid`l$LjGrzD;a}q^kg7k3C>?S-Z-bw+Zh@oe`hL6MzZ0*C z^$i;9P_nwX(jjHGmg-2nhA>MojNHS?D$)ozlO?dZucv$qd#qVN$5{BK;k&8hm60T} zxrHYohR`y%3_26hGg+EPz|RHg-VV6V$BICM$YWs=6*QIbGJxh@p>g_Ing0RxlJ zaa&Qua?MWqqH5B-Tsxl{>R$q{J}h|ZT@S;urJ_frz2nawCxT#Sbpk0PJ0(;{jw0$x zmyOEsMRS_3!OctI2C1!hZ&7I_SGKa6IUX&dOvvEv0Gxn$B;%jV{pF|pEnO!{w9s^x zxwo>hfWc*FZ#<$X6M#Y>ZKFF^k;w`MPAkg4Y2Ss-4vnYBJTv{CY5xGyuWluMmdXkd zoP*aS0pAs^YVfO3x75y-9&kz#S395CGA(aH_$LpDwXleMQE=bc?!W6UJglUWpY}tv ziaPRGbRbq|?J?nPOUAag-VK&`<%T!|T}6^P&f*RZKnH?2>E5$+e~!AQy>VgTe*}0U z@iZ+Im^4OPVD^^iiik&@j7!8xmM0{G(;~cs;)jpCPW~}{2m4}KFK(ThXzk?Lh36!m zpmUC$>#~ZEB8@etkn2ivsO47o>vNft_J8o?`svWD?OyVDhHeZ}J0g;J0|(RB_04;y z?Ah>l;m?BRxYPVWWY({8KsPp!HuO=nfTtfT0tp9@Pf=c52gQ#T_)5iejS}f>kb+!W zSVmZ$R^ z-@~>Z3jL&RCGbp3c^0jt*j`)Q%Nl};AV?(nK|WvaVUX=Ss*nao);=*Vr7pXz+jw&O z#Qy->rtNE5*k#37nq>Qz$_DJKBlEIlD5awS{ILW$u5}|SmTXXaf%fRqu2q!9>THy zFY2Bm@uz@wj}Q1iNEf&F8hnD*#!L9^MbiP12%1B(CXlYc?}bvpu>czUq5lAaZT{b1 z9eh*!K{lW9o5!~rj*;T+MdGxI#Qvda-Y`-_`MokS z4_~Ed{n;ehD(a`9G%Ht4o;t_+E@r~bgg#%?Ps)9`C>)_2@7U0jk}EJ zIOmT<@b8Pf%_$UI9AxL74r-5x^-Eo2QIAu-%TI6e$12Lp^7Hbx2;Mm-Yh`oPR;yKX zZC4PhsIEk;a^lX<#GWJ5u65*sGY0jMC2&!9h1>%$0F19(55uK$KOVjVzl`cmu%R4Yj!O zM0#eQYb;j&VjRY;BkleX)RKKOj!$aw89XiV`rQGK<4=TTE6&&O6~F_ZZA*^))<=mC z$1e=&6KgvE0LC8zTUx8HnQ=FR?vMg_8UFx@i646adyz@hf>&}yMx9EHMLV6xjlMK5 zg5-{UQ$oLcS?3VLa|1*}$VNhdWK)xd?T$Ghb6-0AXTG%X{{X~~5PVG6^_z0HjEie$ zDi@A2h^f9z9jm*bm_7fsjF4L;lVnvS7$a5me=yPc7i4DF2JMm@@dn)od5KC6t3 zsNo+h71XExN9dU!4pYE#;xVf7e+)m&i{gig{7LcW$I0;9;cPNqUEf?0EW|8PAkWO~ z$}p%qvF8=+pA9}5{4M>I^b~_lx=a0jct`f0t`k0SJhW<#4%q(y9(t47pclUhzh*rG zPXu@#3EpXjH9bb*7tgwWerGv21F{T(`IE_bR>Q}i8@00!gEZMLECkCO+KtuD76M2I zc1YYnISMh?(34-ExYx!<>iA^+pI6pizDLCLq@3WNOykzRCe%J5j~e)@>s*!=h)*}}Em?{3qtD8@Bc40= zuYLajf{XkJ(7q4&x5Bz+l_kqXsp*n5*HF+ zB3S-mg&?r6yU93imKQJf>h5p9MdW-wVa3wN6+iS>Jk;w#B%SdKB&SATE&c55CMv9$2EgCucU2|jE0i_Dmy=W3>Kqqgtl zc^(Z+pE0kGRWORuPkoW~i#E%sU|PgV>8-k-IcOgeJZtc?#J4sVEhHDVmkkZ=rN|)e zQb7z4rU>m{Li{cG=cV}LRkPN0Sz{7H>aamH;6)Vd>y~CGf>(Cle)ciWLGZ8IMoT}4 zdi4JQ6Ffg@ahp;&7*QZ+Iz7;==uDnkbhlP|$EawceATg9D1UPJs zzPT8$)9}%+2*yuIb+_VvO^tYYRGl>Wq0#j|_wb*Fd?lgymeKU@6Ov=QPqwAq_L`zZ z*g-57?cI+fiVpd__?& zWdYaj{_ME`HqfA86M#7bAXhfNFw-QM8*O3+EO^-{Kdxzy`#b0oN2S|o+H{v%p_PpB zFkq_UF(FhA0Bj=4ax;(*J*p4*SDqM_EK8z5Pw^87{d(5W!~0)_lS>-;FD0p@z9W+o zOCF+fK*3qDgZ_V&I@iUT^h{+M%;9s&tmph|9eyA9LPEgk7M~;X+XivkohQTp01YBD z+t>)mJeZuR$Kj4D{-Y-Jk#8RhS)-=Xwa*{;msPgVJU4Nv-(TNOu-aN!qsuJr3PXJc)p_RDRtjt&la8UC~;x2ea4mScc1 zmN_4-bDacNzYOkD++Is1vj%;tpK1Zm(Ki;y)K*Q`!^_oX*kz5B{{RG?K7g9v8tv0= z9%>8@07gN%z5zMF`hGOouP)gXtgC`Lh{yG!><>5iqwoVq_^aS6-vIdPE33US^)m6u z*m&e#QdDB6WQyN1u;dVWf<=4}m znTo>4cgo6G?igHig4Ozbw;H*RYg{m2;*6*P{hVW&`MLW^9{#~bzcX^ab@AJ59@kG zjMp*GC8f@-xy!CsC(31vF4l4PMhlWaENc_O@ZZDZ+rNl9q_=nWg5J{0PrbQ_OELzG zGcWJS+5liPxGpjknUUX5|25Yky%`4>?X-{nYIpOSD$DC$5wfOhm;4yNZxx4t=+)(IT)LADAr z>%)JZYnT0(yd`g@cz;mUEg;p_E2)uVmr>IVq>}|4FjFLer_6Sc$BgtOf$29of0?*% zkmMZaI3w_`)oUJgM3mXiUU)l0yM;y0g=Z$w@^20D{=ctU%((b}U>ulqhC;-gBLV>Q z2hzHWojtb`9juIUNFm2RpY=6XLco(Hz0&RA9fXtg!RDzekhD2Ve+52^ewwbE1%gMJve5*5-k@g%=~N79#Z=^DpfUkG+;asCxBMf(id;b9Sss}o6m`mv~4nYUY>q0?u=NA2%JO^nr z%NCb(wYE|25;>v;wWZbwi$VEl)V!m>O=;BOUpi%YoC zuRbTwJ*~kT?Ixb!E8v0`=J|UL-9SC7>T}^w4a>jFf*@F7xFaL4W9?Kfej4~razg3$ zrvs3#M_;L^<-X`@T)L;J^1p%n1Mx56r-s|ZJ`vP3cC>qwJeKGpc_wzo<|ah%fHs0S z0C9@*Z;O5f@gAG;O7q5^Dz}9$FSLt&rU08rQtIDuU@+LZ9dK0t01zkc3-5$J54ekC zX(-qyB_;m=>-&$(-oADHwR|t8cs}Dvx6&+3B71q7Ku%bYPy?I{oFe95d_OD)HeZk!fypppgw{{ZV&uZ#7~8^RtBe-i3*NU>PJq9lp1(y_o)1E(jF zdkmh&xxW#|ac?qSGz?eF`|kl6?a#^wOm{WX&-*QWZ2hSI9@%TZ4=*%GbekLLA)X7n z$s&>N7C75GL}hk`kYJKIX5zj4P9f2x+tBhTP*rA;=bFB?rTj$jvg?{(hM>}7y^=|0 zhAHKcOD1sAlPj^wz#JTWqX1;r4`E^boji~zr}C;0-d{!LjQxT)m~Q z?^Lfi=HA-tS@CBn!cDz|KJ(mppUxS3B{GOqCNxv4w9XlkDajcqfQu zKyiQ$eqok4I3AqiCsyx8ytV-v+C+9L%Mo9gw>)Fef=As8v8aqgzMpZm?>0X`iTmA|A@#o>s#lH!BL)Br?t}L`)7U{RQ>1V1d$SvZ! zNhEa`3VuXxGn3O9tqnW&f&HH@?pa^N8vTikt`_%5S2;Nt#Bw+5SEu;-@ID3EVewMv z9&x`xxCgIdGyJ>P&dNBO=`^pcF9s|?$8Lh6gMUqsIiFRuj19v`#LxK2L=BMpV@jJr$ z*TX*t=)MuvzTKI1E8Ce2MFOmogpDx35OE7h{{U^ydB_>Q3;zIugMQJT5w(X$wDFYE zTSpo~sXXt1fNjSI&{p=BDkDqkeyjfg!ua@Ka{}-1#n#~9e7gL=exv;2xUbr8$37DA zFYM>z$UHk`c?^F)U9@8>V^@&2|U=3R4e?{5f~v{{RvEC9=1))U93%v!m%&k-13}s}1QIwg_X9yA+o( zR!p1Wu8XF8C-@C%@lQbUBtu!zwH-fHeLibLyUw?rWQI899I0hwmAT6ZFjRo7KSC}( zIQTa5!aY|`*Sy$e3?Y`%_?X`r2L%o~*XMV}O>@Iu8~vOb*>ouW)2Q26M{ji9S%rT^#=)Pwf# z;ittT;~Nhv!@fDTwJ?&61;(x9usw1CAd&7fT|3--NFb)0;sm)T_@dMUgZSkBb@&b9 zf7%Pj+S{?Rzn0&7W!l=_I9UGx-?bf2`R1~d<0r%@qEP-N)Kmex{d;rM{{XLA!N%5X zpM3s;=3k8PrvCsfnD8=YQ{NocZQJ~CIX_~MC^$J0pYzl7ufS;jI{ZkD%g5raL0Aq7 z-4K7zHRxUw__y(6QSjV%UK#lBdvB_k9lx^fZyFIM2>?eXO0=C0*4PgK7AF;J+1}jW zfY&nI{Y3q$KWk5lU$e8=J;bYM_a8jM?#d}5X!m0$ayB`~-5EUvaeuWh?IrO8#a|12 zJL20tX*BH%SiXB{b=ylRQ%s&jP+!P@DMiUrN}nt+45S{0z90CH{{RIX__O2R1z3D3 z)_fDAL8R%Lpt-iRmfBXBN)uz5ZX|Y|VnJ+qk}yo=0-ES_kJ@L**Sr~^Xt%OlSz6od z()9lT6UQE-YoolZP9caaV6OgmFf2K2=O&J5TE!@ zuVrHwogCWEwJ+OdU%e~CE!a=q91@;bKsX3F%w9G4vGI@NU%|KWPrO& zH1PZfO^R|ex#L)|hCJo?2f597&&LfX!+tV_d{Lw8Lr&Drn+>M2k=&b$b<~q0Sh{GIlz{fSn zsNq~3R1&|D(D68@D9Tf9uQq$o(!5XcCI~gnX5!}5Tw9}0_Nj@Re$X2szYb40trx%X zAH!b_M`*tl^~)V9)x5!OJV7PAF`iE43G(hIo(4%Zh4FXe{{V-+6I-=}^Q`)w%q^nh zF>%%ViupIULwZY*sqn)#$ce z4`BVFziRIZc%>|~qW8WdZ6*ebPPo>tUO{XQH*LuUi7E+Z=E`lI zH;r`-UgCH)IIdV)&ZNdMk1dA`xgGP0r)7UWK+DK;oy3#R)DOm@w!OHuvy#wCN;++o zT@`^RxX8w7uv(`L=KyjJa@jvnD&(Pi#aPWsqgI=_*LZVTl2rRUhA7`Ft3$bQ(2ksO zn)(~|b@&tET?4~Q;)@+!n&VW8PqjxSpdn)?=%C4G~~6q>Ma(XCw#u=PSiJKpMsJcvi;X*tN`p$z+ipUnhCk z9|!Ma8T@K=p4Q?_eenS*t9C`IQ1KYT#$0I^W zRA6}di31;?t*PT{Q(GoYTrE1Ya=S;FCb1Qa$s~>ug9h5?8RI=qZ}G2S_)YOo;vM&c z+ra+-5B?!MQQHYEgl8_X!E(-IMIXXj=FT|j9#Zx?iQw9$)kad5z~5&(GuL4qoj*S9w3oQ2>FwlGdBrCmBXAIy$I zBgd-|jP?AgDKzT=xn_9A8wy5v2iK2Y{{YIE94CzaBkA7{d@ZPW$HjW$S!tSOvrl<( zG@K#<>C~RVMfe@Khh$K3|L8E0^H^0K8KpOFzjPT&1oFYl4ZIcv9so7eM?RTz z5r=70Yqq@A7{LCkj&er1+YW|fO0{>X6eBnCqtU~T^mQYt&gE~1dqa>WYVG3BbFm+ z3qt4uh6M&k2Ll8${{X;ERvM$bmdWFw134f4deF4e{1fpP;nlXet9Un0)NVA?4QBIC z9MYJblrh3ap-4D5#_Vn+WLGOUgZ>*!bk~pYw?=k(a%G=Rjojy)&&z~Y3^^s9)>^ITfS23VVgVlpr=yH)w?>C&vVyw?s$+Y+7-}Ah)2FdHyvQI(!(hUjZKpJpy)Re%+J+b~xJG+6!w034P%?$C34b1332kLmJ zP}*E8wXM;8yc|oA52x#0sPLS(H(^n&)CL33mP56D$yH;Hhk#8%s`v@oWsXQJFC78f zCA+ay2OZs1=e`F_^T?v@0m(FS5xG_Q#xu{q_*csR0Ps>Hsc8^+Z&QlSNg}J5%B4jc93bzx#$-0@&*S$T&VoJ0iKxW{v~`0(*7p+Lg&H0 zG4QMER<}*L)>%L*z!0w+E*TthLk91TnXRZ+_7zmo6)DD~99j882-CHlMPQ0{igTWf zib&2lD1LGN$;spL`lsMmiPPbC>_g*29O*UGclSP9+ei{fMbtKyu}C^?VEJHhd0+N% zUp9Wyo&bM91mIENzliOrX%}7^ z)bFK^3-ws;e|ki3wZoxcI)KWjpGx`~ZjLsRgq@pdo;IJO67EZd8R&D4r;*fm0=+N(39azw{u3|SPTRycQI?NG)2^?4uPp44x{GB} zMlitKZy)u~)~7H^ns?|{8eH~06aES<@DeLlxA^hkeLgWB*zLSQX#{u(6fAH|6kr9~ z79;eJ1D3CuyeZ;O4O?HJIz)!yWd#w45k_}so~3~VuT{oJTvzJfiasCd-Y4+&_lC8t zIvblS`*|&Hpp@ZWH(jhc_vgRU7_ZBZ+K1p5i$7+Minl%s)P(ccIQveIdO#k1s}dCd z0KB*=+zwQbFhM?U2NL4aZ&k6`h{LJJL+y{*pT!;)_^;s=()DW_Ij`(v0Y%J6<>43$ z_rcsp0Q+LQy;I<4!@XuNF7v@w1UG2xqCz_N3JL4pz7+kIJ~-caKgD*sgc5%GDF*lp zfMrs7Uwna&u05;tN5VSb@s5FQ;%y34p4##N=wmq})DhQkIR~dqRpxkTse5{V48H~} z6-RGnE!X6DC&bVACjS7yKZ#nCYWJg7)23wrjXGphbgrr>f2Bl#caNg`d9Eh`h8?tTad@jZIi zlKH!x$HSf>{{Vtv_|To&AH0ED!zu=r7w4I^fq~3T|?N^Wz z#u&-=<|_RaXv3&4jl;0oaqrflcp@8_=Od;@)&BtXQ@pLHTT}CY;>Ya!`(R%1yy|vL}Xziu2mfgk+GY~Mrv6gZai~{Y9XBFdf{>%RWv~GiS_N)H@f%?>eTbQNO zC16k0KR&hl0Br2pzjHY`Ba*B5^fbw|?JtEF0FpQiF@uk#3ABLxivIwFBjU!Ep*0WL z+GN|n66n`JoL?u@FYYgjg!qw8<6RcMnNojAJV^Fj*q66$dV4( z3_5<5Uhl$ME%7fcm7IZ2S9}|>&$`m)@)eQzuznqQ;Uo=l;Y~;`M(`~hV18BJmeloF zEp#6Y_y<$6yjXT^F=R+Fz|O#W1J^k<`o??@;qc`D0AT4(2s^>FU;ZYjYyJSxZtN~C zWAH|h=4hEwmJ+O_gUG=3rl!-_6mIN(OKNf5S-JZl(&9=5T@@g^to(Uv(mt=ldELlio4vnb!XRs) zva>&Mx!r->;~arr-SI=hek8K+Cx`8PSKw$h7nadtw!PA1xLNI`!2Q_)wi0qqN}S`2 zSGD{g_+8;Ii(dw!_%ZPdMO`Pw7j_R6b86q{uJ}@8k)*g0#g!p)cbxIS=}^;6Td5CI z;VpN?*ZRG~-D(#_JlVxs7-FIEt}`%bz{n| zPaq!o?NsA5v_zxIZ2TeOtxZhUS`F-p{phWn{y78Mrnc}Wige{&Q&rL>f;i92(+#KT zj^;lXr0g&&?usFsJdRNc?0Ps+)8%glL!rLo&jj5XVQUiQr=EAEU zxb>)ul%0xcBz#cVE^z+5pISthY=DOP0)D;!02(Eb2HY2pdkWb2Y2eLE;wO!?{{RPR zkp`Cj?PCSHC?}GBgD3E+dJ=kf?74lQcne%UJMnjfr_>i=TN0A(@=ketVD8A^bm~og zF`$0Z{{RW~MRuP?)GeMOH>Ttuk77t9A52!Qm+YPJ;>Sa^(pJOE5Jae2JDBb^ETMMILlsJQecwfN=mmr%GC>34GhJv`X6`mTActA82% zIJ%E~UMPfM=W9bC{a6a;TlPfo;z(ai@g?kgPa;SW@_wpTmT%dzdu2qo*W;9y81o`A zkH8Jw)V^74Ro7#sgIMrg(=lHX+u$BD+kcs>scGT+!al{V*~z$Pi-f@A{qN;mHTS?@ z69N8UVJ+7^?)z^jj02_dGgVRPX3&G z)R0*GNsb_=PSw~BsBPSl?0BTLTY_}@uf+9nXEZ>}tV!Mbzx1Ufo+~F=@n6P4s6`iq zWk@X{U-XG)v$T+}O{a4%;q}gWuRCl102gU8eX2`cPTiFU&fZ`k`j3C>Rh!};k8JJy zn|*fW6C{M4Lj#fMz~Ylq@->3BLwQi&1%GT#>aA)wc#ca&j@)Rz9DuXtstF*7Pf@ zxpx8*(&VEzUIO`Y9tLyQ-=H<^9|~lHQ1JcDuYNgCI zqw5d&QLyS~`(gDhAH({t(5CA`)s{6XGr^5c-MKj-a9e1}#{!-I0Kv^l{#DaFf2{ax z#1?AAE~58l+T;##{;5d{GH|=FdK~aYQN?Bu(xp58AMhXGOzB}{?-|*DUo-id{iVJE zd|UVt@hX1=d`i${isvQmF0KA}uOTFXG!4L!2~*qr9&v#eq>znUNhp)3H(3PwN~*awz{6Rs$=&t>KBG~ zC$>l>Rx#`bcmPw!;^kf{7Hg}ycv>-pu8$~97sWm%yiI?_H!UZe*t54dP|L&x*N8qC zcmq)J4~lLs;kVMQEcBPTjvUAdEQk)+)dO;HyS9VYvi|^RkJWhElE*zdplU^m3YRgf$e_+wX5F@ z$eta%hD(cSQ#RopS*{{{D@Lf;EHF+&Hb6gg^&NJzZ>4G4#I~y&G%iVj)I-Z|s(%8GU46%S!mp;Jf9m0{pBEFUQZSdCn zUeP6+;;+Pqc`Yt{ts)s<-z*k^Rf021E=XAnfDCY;a0?JN%jGI{oL^HMTyG1Ue6iTd zt0a4XKP~_x89uu@;P>aAn5eEa+Y;#Z*9yg#<%q`pa1J*DIp^tKyC1{PgI4Hm=ka!> z$NF+S$W4IA(XIc_99Fa|j5+P$Ld!T$iW{kkx> z*7cTB6=L$WSdjM4Lqm)LJ7n=l{uuq7TjaUd^(mQOaapzKmudQao8FSYV6!}F-stW! zLlW#Dl3|faQUD}n&(}G@0|U^*_M7OJY`Tmju;GM!56!-#-o4t#!G8fYNxYpON7OYb zR0ephb+IcV@$(f+eg;4p^{I6~hMxtj;WFHOG}dDqvda3Ks-B(9BhYm8`qTLbD?WLb z#1Olf3qsDScZG}+aKL0?j~#i(dTVR2m1yn+QRXk>gM;n&xft~9Yu{z?*X;Ld<)`>> zAeDe(CD$1k^*JgT3v~YgIIL|-582{L1a{vK(L%P>Xf?N!sOgg_2s?9vdt^|XzQa*x zjHigLk-45AbCbCW%qt2-o>uB6pmlyVgV?zMtXW2@;Dx~>}%lv0D<0Aa?9|l zcLv4Fa5`7dx=pp!qTAd{dSXd=SH=_{PuJSMx&Huy%WJ*` z*S~70A@GNTmr8?2@O_n)+zm9}J<*nXfg*{dk&a=xfMk)vjxovkYS&V-)3oR;HEShk zE>wvd&nn778~_5G{M&so!LMr%%BjXu=#M(GeAY*4qiEWHjr2#cxV(l&lM5M+LAhid z2^sI-wkzwe_$Eiety95%2iH7(spv( z*uUb&r>A%jcvee!EQ+0RaJ64z~^V`m+bZ8Uxc3t zJ`i|!z@8hj({;TsOw$l)dY#6bE3L$G6ET==IQ{IBNk4oUimBQZ1&_x@wG~b))Y}Vy zbYbl$yLvCw^A8c~Hu@BdF1#OiBDnb`u(G(2V*q3rkc^L)j-=!OabG=t)}IPIJ^Mub zJeyA6tJ`ZDcKeM1^$ikukz4{=H+Hs0`NDDXQbw~y@Uy~_0LND(_>Q5zHj#j`$pPHRQ%>5>kr%PPYk3 zTF3L-K8>yTQ(S9ny*lF7=GtG(LNwv!C`M6hoUqv zTiwJY!(K~lu?AT@kmw~275t?eNd>Fs@7XK%P1L_>Ux_vv4vPrUZ1nqZz9H7#oG%B>Q8&8S`-?^R&*1MAXv?l?TAN%# z>fTsbhSTJb0@jJOM;LIxoP9^PLG4whw4HMkO|UL{GUtxwy^`DFSB@-E z*IR5}V)Gwyr#^Kd4B*q`iQIqMB-`c)_m-dGEy%d4t@s#L8 z7EmJ?9Qt%Wl|=slw136x95eXBQ5s=`vPKG?Pb8=5pTe4w=s#ysX=CU4EidI#V|g2l z0<$hVet%kv?01{S`D7%Fpe_OF>G@Zrc<=rSTV>#)_Im#S#~WywNOJ@cB!#+;F&R>R zro5-czwlVEhkDK1L8*L0@nymhwZgI+5zG@5Rse=KA}ZF6B^w83<(yUUzPV`al*j(GQ-WzCR%Z7&3yt0CFe75C@%ARs4Fve+dV#p|)= zKNx&Z@nhkyf$w}l@Ow@=gf9`3Uhvi97l%`~12`(cq=XOQb#-6}$4dDtRrtC41$=%F z55(_TPH;j*-1k77_z*y1_oR-CYk$9uU_PUMRL$26(dRb&~yMB#i zcCcXBIE##sG7dAyJQ6GO)BXyF`zA%CUQ7Ez_#;8Lv$wmPF1u%8Y>`DAjexTxae#=Y z05DJnNI2xtPNknKMY>XPx<_5`qg3#RhCDwOf&Tytu5C`n7ulxjR`Zrq7iBnn1_K|+ zX9S-X{{Vu5c%MeOy3sUEBgXBl1dds!``C$z00WXnPc`JOlsvVXA5IT|qUw95(3XRzgSK1dM_K6}|gxTzDRDjaRGi`a5*fS}3KrHe+-|$crNt z3NWKB_eV@)j+JSpBOPsYc=4AYfW(u=E9#&4C%^3F;vW>=T>Mm#+ScCKc{+U3L`arK z`{j8bbYOStYv!*BXd_$k4X&3fg*O)}g}^5nIL;5CuiW3+Lt6NK@MqyRli(kRzZt~3 zHkYS`vAb;+M22Y?W!x^-EO!%@ZVBvaX4-~>O6>f^{j6`if2H{Nm9W>XH48@l#l_^< zvp73;ATXd%N6ou?4oDz!MrVh1!=YZHzEAOQ?Gf>J zTGq6$7I-IXo!ha9l!F_Wy*YjIQlFb9mpkhBRb~qm{IASmc zLE^p3;MeVW;NKB=raK>qcQSYy{{T+1XsopTQVC;=abKCYBzajFe4rH_2;I$RE_WRj z#~8*>Sy=D968`}3up06o3u#x^5^3`YuVJ4|nkgWY9HXnCDo1f$6~Du87fU9?;T=F8 zg++v%^f>#!gU&Pohu*E9M&Yi3AHr0 zG2M6y?5SeI&C?~$Ny$8^?_I>cGV!KNU+|G#vL3?e{W0t5SL}RkxePuc(kHu4jpgdH zChvYje=1z;J%vuk)bH_M#ojB0^xc2Nz&w%epGvxn#?Kx`SjW3-u6W$LmpK_HAY6mu z7mB={l6*$icY*U5wFz;KPj)@A?agLtIu)Gu!pl*$Nl_bmD#a+291c;y1sj}jbJnR( zrE6AltNo7V_S<&PIy+snMA;by6k&#Q^C(h2Pn7)4Q_mx!G~vy$)#+atJYk~1+I0RS zySzs%OuB@NAr3$p^IUFTnc7ayIUMGxpNzgUxYbRzm2f4RMo?Jig;op+^v6E< zu3i}QtH@hWiZz}vQWW3v9`N)TZRf*nc4VNRZ`4I004c>b6*?0Pd1@+wqiM*cLjBxy zI!L|{c;ifowM+ZW8Ck$0D}NGMZa_bUQD@(tykjKPI*x%ai!_Um4eItYT|s+p*7MuS zT<=mg;{f&fvC}#Ct<}@OwHGl@9Ijt!MaUyP7!sgk+cl-(xV$lH?waPGad4hdF+mN$ zhv)B-(XjUEj%(FWjAP1-x0a~*L&csF@#pN3@tWTD!flr}7ZKgqTzsx4fSv;dBoanZ z*E|rw^sl)50r7vpAGFuOmhq>CU{CFBJ&;`K7cQl(pZ;1P&OTFyWqt!}10&|g3;Rg? z7O?om;R*EZGRZA|Am_QrH+i-%7V~mcmlfUlIcRpa}4D(398HasL47iFCMu^IX0g zl~jyio<>KlJF;w08j%wbBW1InpYh;z$6AO@43d1|N6bbK86%IU*RM*meQzX0qT9FK zELmes*w0Q+KY%q<+Dg)fYiSD}j1U3%OfrnhT$wcE};Lm(KqHmSk>`c@CvW zA*yS$aU^R436vn)yteFv_4LQ--l)am9~kKpiFDmo>lydnr#z734nBbG9Pw8gu}@TF zsLn4&tbT2N(LV(KJ$};P5ue1~JMmA0ygRAJJgbYfvb)pP;IGIXnJrpL+sjCRc{11> z;N-Wle$PL#U+nbt;};~n;!;;l-?<&Li|gRNaCzJ$)t<*UDxRQY9M$wkJ&*R6d? z<3-oJcWW(|h;uBw4pz`n`hBOF>51>#0&tNOPUxE-w3&b|K z80Y{Wm0W}2P0gYG=~hm66d5@0+b6waIZeBrQJohQu5-5DKiBRd2yBk{KUsI9z(n9n48V+EKm?%Z@N#Bty0UBAMggPt4s zOQ0`@{4=H)^z?Gk*uBKQQn>&Y!nh^49Gvv#yV2oWZ#6D1HG#BXnA%hzBkhTN-Y*5TL?vNZF*m!Iybl%PJJEn2p{ z8hyOD)1bO$8-8-i%hY6Gk&&OKM?p@4-p*-E15&igvC0`&Is6Y=mF;Yq$NWxhBOqXt zGK~9l=cOj))IWF8kT#ube zpKA9K$kDPERa}q*0lB&jqmoD+c%dfUkaNL%$mKpZd}Z(l!2bXX%i>=cUj)K1wzOG5 zl41w&jPO12>BW4@d#ZlYAGOuNyiG#y!u}PN!&+O}#7))AWZ_&ZGO8{*JcJXEkzb`- z_)YNd#~-&R#rZxec+*d~yV0PyDdF7;D_5Ci5t0n06;9+*Ml-`X$@xLAu>2M9{@T`K z;hh@F-U(x8j^S^v65JUVf*whkR5t)%k<)?Nx+K*PbuW15bB$^0?2nr~6Z-)CGVt&h zTHTB{myZPn@Yo?*+&`e zkb>Mr`7L_|?V@S7w;Ck3VWhWqMli}98wUidf>nnjl_VciRpp-A&c$x_kCh2x(E*Y9 zkIu4ktGj0Kr$H@`%J18oOYo1vuZy={0I={CjhoxsEVj3CT3cH)WD+C`Wf~y|k+wzM z$0wy({g3=r@%!N|oc{n9{vBuwsc5${7_}`XO5CG)Y1FH*SXu)k_; zOI`h-e`t>qc!R^*bhk_Wrj}M=0V)6@Bn0*ifsTKQzeqk3{0Q*ZgFXr9{{RZ4(dBfq z)8j(mB1k62=8;%7RkspyFh*-yj#T3m)y_^;I<3cKQ1~11@4(+5d>`z3r|{{V&4;HxR4wu)sQB(Z`pklxxmVB9LN2;ksIpx`lA04ZT#U-(nRx(|>1 zD`(=54Z!y{T9vBLZ#wMd89~S1$UQ;)Y9(kjC~S18DaFD|QoqAr82B^Cejc^)Ery$8 zaUH}zytli$oS0R9^KFJjlziC-Ab@zu=p^{1@MFL~8GIF~_%p=1_lK?Qbz4S)dm$dC zc5WEXObwC`&5*~Z9+l?$kJ`Uye~PKB$!_s@Tf}#2mwSs9vAS{;$W>%OQHFE4?G4HM z#=Ad_-xPc?@H0UE+VRe%XKsxFn%X3kZo6C(Jh8a$&mG9mQ`(&4DK$9tF{dR?Ue2da z$ozW!tAAz>5`N7801~C}jsCL+r4HwNO+wbz<|w96T3i z@FmomZnJBD0Rl*7Rz@Q@AddL2w!AO#f5l(4hk!0T8{sb>&7o?RcM*+nNQ6VELAM4- z2*FuE88D5Z^PUf1hV=PF7b%_wLC8G^;a{S^u}{H&3i#vpbg=Pfhpk~F1 z%#2ir{{U!=5ZTI*2={Jk7G`jSrtFTx;TPOw~ZNWbd- zk(Ku^*0OvXK=Lk^`B$O}f%rq*>r zI+ix-0Pb80!qalRozA$zor_*8__wUS@e=R$e${64 zY~)tF@blfsRg6W5XmiiZq!4|<^{pJERyXR5-3d~AXwTE8_;K+60L7p2Fqgub72Gnf z&v$J+GGzDpg6HwgbGo1Gz3_iow~}op27s3>zTWMaWd8uPcK|bY*dNtbT-n+*qK-dH(>GMLSnrpnlg{SC&`AzYkQq4rH{vEA{ypbN>K+Q&9PXty(%A+0m?w z>1A)MndWB1QO6PU3l1@}Ah%wfxE;oHExsIU*6AFB!@AU&McPe+o^g!hX~;g+rw_%= zQ%LYtr-EaG)$D9h63Tt@z*lcZCfvv75;$g)b2$6t=A(n-6p&jg%crE$d7$~JF*(BY z0fd`JJv~C6p-2>InmE^69R1J1{{Ru)YAveinib`wq${{(Q5>TrWzet(JmH2L#2*#BEvIRo8ne`cS?UI9PM0S8wUchtAz+PVkR9)p zUBD8gj4t7oc+>VRC#y8WLcFS^?3c*#uZXvocN+9s&GwJw#{-5QOh6_!@`-{;QGwT- zj`i){7}Y!{qIi2x(xjQ>mfFol%fU2 z8O(oYk|g_e!CYo88!p3X&SNdf+{?xjC5S4lt03Kg*&_jf z%Kh97pH04g1yh8iruFD`V(8aNa;*-dOY!f5Bpy^UrohMLl4lsk21gk0+=|?NYr^+e z3c5aK+^k&9aIv<1u0NEN(+AOR^Vrou$=M=c2aBDG*25?S?W>E zs50K!ETB&cOp!aUAmPs7Iv#fMpQUy_C-HCW@8UlS-sn&8{{H42O44h2Wx3Zxepqn8 zG1?dtgVQU>7;bY|)Q3D;u}bin@~^;;iW>KYJZ)y0R+4oaOTDRkr}>*wMcv(p0D+tn z&q6bb`U6_{Vs03=f*gi^<7;1gdh=^iln zm+>#dW5B-yd zDdDdg>n_VQx|X-+PR2#cF4)#}*g4upO6>|t?hMbzLY^WIm!as@`kW*0d(X+9vEg4I zcz5E~gxdFlbn9JLMvxC8^H9)ahC8vfNoeDiOtJ>t0}w&S)D6|wb=croX0p?HD0aS{ zNIt{&gB*A1-nP$;ZFMUo)M5BtqNThMw!sW{YZ8z-+(h3&jNoy_W?%A4l1{a{uB7?z_(9#d*Llo7#2V{!)DUyj)k$>6k~bzxmM)wx+MDI~$ZY;zhOML7 z#-3!Gf~XlF01hy4KTk?WF&5aVfWtdNj(spdG}~-`&YiY7yN?g%Gv?|SYBEC)wzvoA z8I4rC(rm@RTZtccNApW^NXYj803y1%ZS2alKpt3a$Rm(>$j3a2g5u83IZQEKNQ4c= zJfnfs;PH;VhrJ=FQ(X>He-By4-dEUFlyJ)+10Uz}tz8?!J|xwx`RflI zd|h_iuwAE%l?u7f1eOEP_v?Yyy6*`5Ch_-)P)nltI9$XStKC`M!to*FhHd`O_GN|`E@L0+q)9(MKp<4Ob83dG zPC7{$EoUUK`Hc=R7EL&%a(O-L=Cs?;Oh*B>b!!AC5oz^ywL+ZIWS%usfMPnZ|$4Dh<_>(9C;# zlu{#UH+nq1dG*IUSIVF8QI8kv-XYbzE&Dlme(p%Fd_{JzcIw=uVX!zIhtB^1@D6<| z>(bCn7ZRD5uglN59ddsP_?N{VU&KH7DaF+Q{Xhc0y~!p$4@^{zvOYuQpve4ZewDQi zYG1Osm3GxHT@R>!41788m&4zNx^IWHO+MxcVum!@(?((usQEIXHv$Gb^(6PNNznXH zb7yrO#<{AS`(;AEWYjEH`UrvK?T$ynl6$BJ<~;LQX=OVm=wwkI09e@$bK9NWzLfiW zMTQa%;BpCI#NhoipMS=(-QAR_!A8i}*HhuAiZ$4EEp}P->ogdi9T!Ge{N=_n3tSNq z<0l9~=PS-@$UklW02q8G_$m82_>09}5Aj*MxqBgJq@!QTvkB%qRm#1pD=LO0g_Ag3 zfN(`@d{Xfyr||1Re-guUZtuBL*={5eqRQ=oxVIZPB$7q|=Zx3qXYJ|niTq4cPu6^P zHF&LLk*;9bwg;H!h$B!H6@KV^aslI<*3{yqC4G!)H%dA)rvCtfaQrs#&yW0nqFL&m z8`pJPTTNznU(M;#g ziMKq8M0f`ao%{v;cho*JYyKjH%dJ>hTuzJ|?H+a1QYTNY4ulfdRp3cpEF5oDft`efuHyouduvRs%l!uoL*c*dj!!k%MI0(Oh_n33zRHIeMvr~ z^{(mz9;w+KZqU zyf3Bc7gx5ner$lpd2qx>Be`s`8w&v3bAyqAUO}vUSooFji^a!L_#>w*(0Kat0e-Pt z8K<=A}AQaaKPuyg{X1czeWGIyK7!CAG`JAO|CIu~z&t4Stya z0Kq0ClKb{@x(O)X2DdKdbluiCusHl{^Gn2c7Z-jZp7L1&Jd>7SRFV#J-oE_*0D?&$ zA8LLB@g|Yse-P=ma6_oUERpIuESFwuIuY_RTtpKBF_1%K^O2lY)s5-t(!|nmjv;nF zrudKG7nLQ~G|e?-^8gx^lFN^@M!g+5$d!77o<|&TM~ZmM;opaKuZW%&@phG}*zfS% zPdpl3%%V82t%+>S7|9W|Z}-y!B})OjD_=>$;BOV{5Pg>W;(eu+(>Bo^iomj;_ig0! z<#Ue6r2Tt*lkubWPyMMR__KZEKMi;useo1I}$z2 zJ!)x2PjhTcsV!plDQUm7mxq2ce00!$3+p}(xwE*u)!{&3pUv}S8`d=17c3-F1~?}p ziu*e2!g|JsbN!#L>9g4~FECFNBdF<}r<{J3`P1QFg1Wble`yKr_5T1IJKbAJ9mUL_ z*uKR`YaYt5+m15LTsFNfNEva`NOG8H9vbjP8rt3@kv>Q)}A zyK*#FNis&}7 zT-Vwc{wbF3)LP9IlP)l1O)?0aClck)TYRgSIJ(vU~#~xeihAS zC&Qf*TZLtI(jYQ?rvUk2o_kkWr0G%kf)~5fBNC#h%SOW(KaFL}B<*8WR*x#wA@T6hUwvbc;TbvM1NEqUvvC#GX zUh?7}3wU}tE$yM+_Um`LMqFW>AXO{}KPrL%BR_X1*x~R`g(t8}=zb3Pmf|UXXzt1CwJL0ueq}9wm zwHs)UpYATM(M#KEkSj89H?Pb+cVPC$4pXZ&&8lFoA8rT(JmSJlHX6%8aG}(`rJg^@xfM7 z3C}pLdgI{!x2O{Yx_$JWKwq)QqmKUoGGnmsQ^`^noq<7@fz#n z#;5(J_abR-<R*i=sAI5eSj$;#lM(h%N{o}?*kwV5WUC4 zui7DQ^?_mI{{RK(vD(U1MSG{($>v7nl1q|gcPG?ykgX0H= z{6iFI878{dAX(!ioNtgxmjvfK^4&1Q7(E@Y?Kh)dPOxbjo`WHCg;+HKE>Ae&Wal{I zsiybTl~FaSINypokL>T`8;I__Ij&8l+)X(9ZOyif^O?pl309E-$IHm)12xik1K_@m zf25eaGx0A~xPn-CYi$XmR{@R&)-DhY$M`|{o18WeZx4vRFELz)2^k#eMS~kY^VU?ITCKiR|>;DE5<%4e$5)sf>3H!Cev1pnN-PQgk37+ zw$dZ}$-o2#U`ZJqd02M~@huin$Pz;BGjD%y;>s;50JaM6D(FnBd zN5VRjGkowJM)epkLVkTe9CSSPJ?ZjAuA+FE`54insn~5}U5YksIUj~Iz^NjdUG2Dsj?ID!ZLk1}+t32?K4FqNzOh$7I7q=I+s;VNOWptg&*R&vD$QHtp1$^qG}~ZiXUfd> zi4V#%$&9?9X9J`5rlDkF>rKk-oqOrfZZ}cGc0rJ~*lnb7$tMHlCmjBRtwc26-g${< zsmCI&JjB!HLI>cW{uSohpT*CKcZeHI@pb*XlE63ieUcTy&kE5=yX7OR0iDA3Sxl;h_xjO&m~BOvm@=cot|=m{M?MkIf?$Bf?H zn@act;1qQ^4%#D-c;l6r5%s`4bfxU&)i1npoR-YYwD7jOKba1zr%4)vgmU8z$5H{~ zw;cUz;$0_6m%<7NWBuZD{y45DMfkUKVx%r}=s^22{rq{$|{?Xsq z7rqlq%WLQ^bqia_ZmsQRy-?EyJGKs3fLAy^+;tW3hmQXMW}k;XBKW_j>uuvJJB=!F z_KS@^d+nfAfa>*D! zbV^24o-j%_G0@fp&x-yBc*^!muMzwaw$$}jy7Pvut!pyRdn=v4yJt|Flw&okwX+A2_$%RrKJ@4+|g`p)d(xfYbk(D3< z*(a9doR6@Os?y&J6VAk&?ab^h`)f+Qa6k-@JOvDG{Y716i6_;q5_ zTd^!+)NcgnfL&Z^VfG!sPjhnSE(hE)$k@kBV>}L&;^y7i z6zWTx;b|U?d+{57>v>)V+az(TIY#UlCo7(u;=W(_Q>f|MU;Gr0QPq(^w$c19 zW0eQW19-*_cGv3iBx5SnUegAb=~; z;M2ddE+o0J0x4vG#pXhar(sjLbmKYalfmi5eBt|Ke$JjO@kX<0@pHl2O!|$^m24AG z(`;^ z%y6sa&VnT*w&2Msk+|cj1LmvEQo~G_S@5=}qv|&olfEJvjf`n>TaBX)1g?ZP2V{s& zPETs*teRxIUdZxa zjk*VhbRUTto|oZcsY?a4zE*F7`JzP_aKR4MwmGIi1q&fg+48U*HOB%jx9`!Ss=T&c~obWED}T{gOS1hGn~?mNhf0E zN-iANKUBO|@cKKA7Huw1+deznSraK>d`op@Y!9G@!WBRX&KF_#!94zAd{+27@w3JM z0JOi0d=YJ{+Fx4f*GSfL+gwTZX(KDRNSz2=&h(k>jckcl**fE#dk z44w!bPp9cVAG`4mqaL4Y3Qsc-HkirSF_KP31_0}xJ!`{!UnZaN!}h9<+e1EghWrB_ z#<^ZnYoc+VMmxFZvJb6kPASUv*oQ3-r7bl-h1ygn zM2mF(ozl7;2gct5NiggBzlxNkK)={6Ew;3TbW;2g?r?eO#d$R4B))8oElyv0Zq_;4S2aJ1M+yo5((6O=3V8;BE4` z_3A5}lf}A5m#($lmW*{RJP(%BSGatfjAWa3h?Ddq@vZ@Nn?*ty9Th_o#POf4H9SQV zD>QNWYuTie2P2XPJksS%ZL{>Ce%8MR?XLd->H`x4!7{Z+Y04O|D`$GI#@UE8LB7@++hHs!)yweVs zHP!5~r+Fku<=q;N2@C<+bH#jb1;>!mI$chwDa>q+L5+t}2qYjl@6LJP9Mh!MylE_R zX_}_7b8&3)hBo&z7m84MSvGFk!AT%92G4$^Q?wUA-D-OG#BbV%#C|go>-v9(qta}p zc?@z|zM%wn1rIws+4i1BFt{H#Kx>QCeld9O&$ra?rPXYeg3A$PE#@O&tBBf04!FiJ zaC#hqO>_Ph@mGp|9>qSF;T?YF&I^JkyVGp->x(;i-2VU*q!NY0C?K%G3P>0|GxfVQ zlT@2p)VIYX(zLRfZk>T zGkt9&a78+mC+!lY@nU2Ek+ckOK|RhoXCxY>efGO--P&tcFgoQUg)SvjQa67Jumth6 zoE{H3+eMNc&9C@aTHU(G6n8T~3J{?rL}wXnl>m&A4`I9I&q}rb01I=(@Y}tR@sEfh ziX~FcFC_pD4>)XN1ab+$aq24{R@ZMWW)9P^;^k|YFs_)%EuTC#dms* zq!3SR9+ckgbG32c-A_rg7h2YhZKnrcmF~4z)iA&v#1J#vjCBXG!C;k) zl-D&e9D0?OmvMC;%)kqU`Ej0i8CR(t&T;sff=>|ZklgM5&f4h*%>qiSWF>Kq9|t>z zc|8t0W(3uIKc-(WHa54{A%aL_ytwkt;C@5ougt^dz}zqaKDp6*uMTQ)wDyYeNgQk@ zf-Q{_FkV-F_GfJETo4C6anQRNHMhrairz|SrteaM-WhkS_+M;S&XFgbZfdE)~(AQS6V&mk)n*T-HXONEyE!uF>!It5~z%t2HHBwz*$lwdKOXKxuJjGC6v zLS&II$OR+v;zGdzJe)Sx1m~XEHX zK^cD1eZt0twx_7er%J-&;f_?MN+{j55DNfHaodi)39P%VOH|YxTHESRnXqI@$OaVz z1sDK~XRu&SdG?1&hTz%4x9F1uOj~S}#JTD+NF`1Jj9~WVdrx}z_HNfiTbqL#sv^ZE z#vunJ#)s1dv66Ag+%r7W>cd1b;e1~U%9i%fgBktUH!PVT{Gfvw$si2!I`+;4l4=^F z$Svm3BoQ;JGl3gmwlWkeWPo_i(UJk?qVu&&a?n97v~b39RK7R%0PjKm(fNTOj=e=# zj{g8(^S;p{S~|PoibP>J$mK)hcLa3CNd$HjU5`TPxrOY>lmuZKmSJrn+Av5wbDSyY z3F}mo#5!HWvPUayQ}+#oiyn9bl7MF?wnwLWV##|pTK(+pcYLU=ZE{9PByM(zV_x6J zaxi%8ht za#^}_#y<*4HC<*`@`jpAyB1Umfk7Ari9JWu=RLbn3Eh&}>y{00@jJ9EW8V;PT}eCz z41|R}cqbzV@p-crUSf^LDCG=sgN_4abnZJ;c6UnhZfTkr++eVLCgalwjoHZQ z!O!9?M{Rt_^I6FE5vbtJ8iZ4Uj4;9HAmb-F6ayef+iqO55resc6UpPQa4|{dJW{hk z=Bha^Lbe8d&T)@ppImWNE<7`%UR^cron>a1Kp3aFitxoMvHk-n8 zc5$5ZigmY$v|UiWwVlwqD+icOoQWXEA9Ti9Pkx#Er21skFJj!fMWn2=e`#uw$fqR5 zeB-C_WVf&5_|%UDsv}1=?Zkn@Wrf?ec>0sa)YV8Nxq{U$4uf=z{{TEbdPo;;Hv{tk zc^Kob%sH(~Tg%C#Ng(jBRbC8%UvW7ZB}*UFXY$|~1Zxlm1-QAxNy9D3?tAbKa!*h@ zb>g@$+Be|#kMXDB#pi_DbxXw_ZkurP&H^J63s(5xypsLn_L_rU9()F)?jEfMBlu-C@Pd}-nD_)xw& zlk7e=@W$(HIy-$itM6TVexH*9Ll@_CCmp4KO>1%fjsiuEo$TKHIDs-braU5lHbZ!`z-FQ z5iRB~13AV%VV_U0BQ-_~8(%r+dt@Ys8(!JFb~sbGVSg&p9||qoTK%CuKloR|R-1D6R(iF*r++>P z3=%6>Wdw`?z2n9QQVn>=#XsAs@8UIt{{RBNVSfnT>DE_~8-07nI;)4ix{0y7E~5}e z*i+=hIXK|;tszQNSLxhNMNiuQ0LNNxzkO@_6#NI&qMmEHW0%Kz^m3C9k79#5}Nr4w=5 zJAYb3B{1@J zIIqwD037RI3culKYQ8-1#h2M5)bC8GVkcQlIRlO5B16;oRCVcJS9q)8hlqSvqsgN9 zrqas(+>+^Ub2GA>VCOPpC3hZk*zw%?KBM~!d^)_`vbGO?G2 zDM)tc08%|ljztgIi{Q(6{{Z77d_D1}j-&9!{j{b(FI0VE}% zH?sI^qPK^Yu2{!RD^3686Awhkv zG08tSAc9FhHfuKHL^kqEX%37YXhBu`I1UM1=VL}lexG?sUj4>=Kf_vtmKO3^%1;i=F1=)Thg z+hr~DZDolFQoH~`Be`yrp+gaCxpOX>etL~g7PXn`1H!t_@hfQ;3lwS!N>GIZr)>5O z_=CZ*M2RB|3Lm41rZb)axr~{s9{2%ZJmGGy*k!W#C=1(hmnw9f{zI^O5**=3K89e~3KZAY^ z@aMscSzw7Y?OIuG5_`+NQV?XGKnMVkGE=G9lD@p;p6gTb5!R868b+A{yxE#N7z|*8 z*I_v$h5j509Z9Kj#yWSo3NhaQ03zAc-?(9YdQt&ktF&hy^MmCNF$k;o}K>yAz89$J{Y^2CzDy5 zG7sJvtbh^!0N*du-v{Z^nwomq{levEdwF-M-O9H2wwAF7qyb`@@HX^g&=Q#;a5|jw zI`(2Nrwy4{tYDD37~|V8Za@H#c9Kpw!2osVvHHWry1uEQU21*@{{V$=qCNG5UQNVz zo-vioY*l1X>-H%DW0Y*MIRt}%aCwX0g+2=L){mnsz7B?YHCH>eNc=YxntW1%(Ym|Z zG;{1$$Y~l7qZ^4(Hw}4OA+zRNe-djDS<4NcnQlxAkOOT4YMy|B)8!?BVUjV>X1zqop+*YO>j_yX=Q0C^9d@dyFd=a78$_m214*_-Sr>Y>%lfU<5)-V+fLM?JEOU| z)2E-wkPZPxdxN=1#sJ`gF@e&>zwE2KC5cyH*d%0p%mxoQH6`Ysbzup*g2p>z2qSgX zz(A@)sUA@v;1$kD1Qi$qX%*Z2K=?=C4~7;6pz(deIgxyeHI!l~+vNm^ty)64Pz#Tj zBa_run9Hx}8kF0u72^pjEE3N!21f*g^J4?=fr1sdVmcfkG+OgQxYL^U+sSxBrt6gf znUfhYhTM2Pz#tKVanhxN$*v~?(V=@*jh-pk#EQ}Uq&6I>Do=5d&!#G+zlklbtX}I* znki%XSkG|_@Nxo`khU_|CmGIj*PvwnD7A{^r@7TcYU{c<9t8P`UNX-6fE|K?)42+? z0WIaU8A(mjEKRwebYYn1AKh)EI3qmgIp-#%lwQRUh3$98BZG`*1Dv)%=s?CgdgnNz z*Id)Jb|XvF&`PlGcqP~fCvMZVP6-Df;EoS*$(T#2-e37}z@A(&5-;vii-z^o)}FUj+QGE@Qy%X6OR8COspCgM?Ve2F7AddHXP z!xr30B$3a2t^w74q&_wB_tg^_2p+w&Q04{nC{BAkt?v4*M17|lrpRZ|Jd)c<2h+7K`vfOz9 zf}rkjOYZsk91($z_^UGA>(i8)OWUaJ+h~L)#w*Y81!M9R9ZpEdJonhWU2GVov9`{5 zjRZ6{o}GZ(>lV#+e{ftcTh z$^QUo9CXT&M&QJ?5?!L(3u|k6)@)`efGdx@r;teG0(r?CR8a zp92!zc~OQq9f=A-BdG(ppf^S9`>V@^R0v8~X=L2v&{{XZGaH64t z=U9&BA$zFZvs<{8?b!h>wC)N(Qo}hM@lNLZkPb6=Z;w9o(Ka$x~k1< zIfqk{=t~cn#UzA&?GGSeMy-LsELfi9js;e=xrWNjNgH3p^C=3CBKc9^V7Dwr3Hg9L zkN6fVPf)Um%Onu!aoKr$nc#{^7_dAq%=@=s0o?H2MMo8skijXnyGw{7ZJ}WKUmRQ1I3$);+M9<=?#D%-Et14|IoMb5{p~h$uRxQhMb9sL)l(&l0#4>#CNP2=8x68)gn;Z^*Hfr|Q zSMD`AhUSIW%D9@|DFKU*m@7oVzVi&{fMvB5;S9I05cqN26|)odW>YY2+wHErS{47xTg{B zbxU&^6a+* z5nC;?o#QSDAKl9V$T%N&IR_wOqt&Iq)|%SRNux2eWo3pvN?4X|0r{0mhU?tq08g$i zS!g<8xmC3>F4oHd7#!mPNg=*aGD;4(=b^0!@cc}6+DguCffF=ka=USi4W}amlhE=& z=YrQ0z0Q754C?S($7$f-6P*m&s>ZSXfv#Yfa!x~#!<_VG>&QK*LE>MFnq9@^rm1J9 z+^xRB6y8}wDPjgsn)qxkNeW3Pj0)*>9VY5zXeGFiMyGDyF!jOcdSH$*$i{sJXvw9u zz=>js-DBFQ8C&H5=V{{@>F5a<>%lc1rZwKP;!DdZ{H+4!5+oaT2aQ6Ic}<|H#^7>t z4;||c)5f>58RAbH+QW2Va8l-8LuY8hO1M1;#?A|L+g%=^;HajO86mcY-dm59-FZq+ z0Fp7!9Ay6hF-6D2iz~>OYY^E;+f;d`7L1RZk&;euI&;&G{4~%jvzOI8P%bSM>~*NF zE~EbdSstDv!94u9R69WfJfFuH$1mgG3m+cXM`Pm84?d%24#rE{SR-V}AeE8S$rE6G z>`42BIFhz9G##xKwN>zJwR7$VO;U6pV?zv)8>~`_;=!MPewTgAvUe%#Q@}t zD2sXY!FKDAK)@On{{RMmXh|%MzO(yH&erHoV~)aCP@}K>w$C1?9=IJxt=;&B*GZCN zsOkRz+3_8Xi|8VVpvb`8oO6%6g*ZO-rDvyU8lAL8$5*pTB zomFbl>MJhCh-rVamV6+_XM4G}Wt#2L@vG83SupDxv;{<%lLE{+wX#U39z}q~g zGPqds*}?DEztcaJOzKph#O_s$2$xv5aAwwF57Ez&95U=BDh9`M?-Gcpit=qhsQm8&4}vxlO%|wDOWT`^4u5 zj@_|cU9OR&mn$nQs+h@4E(zdpbH;yOIHheTOcEb5;Yns8L2g3#;{z0i(meN7Pa0m6 zAn^s{PE_t>T>E}JckB69R62i-ytW6$v$8f+dALFbPXllqoY$x}iKfU5k8=2vA!Jp~ zIN)?3^*nMarT2w2>zGlZA%eFud0V#u*zNZ3M!iDEnw=BIlFW^7uib89FvrRcKpb+w z^Uve>ns~MG0o=Emo$SE2(#BO#zktSiXEokU;VWRiTu3tH0rC|ghXf9X(xsQe?I~4% zI4dR?4Zx45KHj|#9P%CX7gLKK8cT>m-Pntb-+3kqT!GF`?@w?lDSjJxEtFg@g{)fz zzkK${NIB{3I(Dx?vGDcOQLK>0&as>}@JDQQG+gNS^A>?tBv7Ob$Ov8sezdP(cY7Rl zpMg9vV+fklNw5+-Wu=bA7bl>}1M;m+J4ms!XSDkmKu9?;C=75(8+|cd9j2EvFplXI zs;&q?7%|U&e=4*zt2?jU-rMbH$SB$S#QT3-e<}fLjEh}9*4r@MCA$!qQmP3!AKe4+ z{3=-C(&K-SvTk3Pf}mjW>7Mna7l*Xv4!LsH^Mqdx3-t}Pj_Tp(UW!=Bop9##BCsgHuT^crLTU}zZSJih#yndtt_s# z?zar~(iH#|;bw|L*bUG!u?^S}pDg=ss$+v}7cv-=Zeq8WXa5)L91hEhNSdad1jYt?xQi9FihIOq5snzi`@zmWWc9-i zFfhrs#a1YArv~krk3_|1zE#t>J98v!9sNjW)gGmXEA1nye3C?DLvy!<3dLDNZYoL0Il;#n=aNlb zitk9&Gi#=54n9miZ9sM)bRck}Cj%Wu26#D!Ic&|LyE6TnNWlH<(Cm{Xi4DI1;9~$| zAo}NkZ(>_if-SbN>Q;$5F2+&=o(u~ zyVi+Our^Sdl}^v(}*0pw)&0>v#}()Uicl`kyaZe7B? z&CRn$te}!`S$7l9Mi|^W<2jCaX1RtJ0oCJXClNO36lWZ{8%YC^oD7U)WErl!L2&nr zbzuy0IRV~Vc!D@Q{_SN0a^M_y9D+ti5y!7xr^Shsz zp5OphWL8n#$sBqmo{w}I`FVG_zbM4liR&$#qjf6z7W9v6}>2B+#2rI zNbtZBozgbNe18@?01h*X!%rJDmn!;I^~5sCa!I?mX&95n0DY`WX9EW#fq~6Kb>puS zY8r^qR_f{~fGQ+g##atT;euryamgwbfM8ExY1DS^cRD}AFBPq;+sS8bZ5iFUm_(7B zw;A$DBOGTK2L~SVU;G}`?37$;diIxYvat&##l(?`cNQlBfyO>uV3Wz@U~cnUFCO@o z%Kl4h>kU5gH;8%dsN6UR(~oDN2B!{Z%6r27r8gKaFQpA8Jc*&=03uq$g?PvI-pa*$&n4^$N=`x{Et%e1NB;*afNbAiyT?fO~ zW+Z05(Z*;U)`y0NC&RbkQ=TFwYrB=)$F=$ zip_Ol;%#qHl*nEwe#I5FyKNacCSc2)XXONApy^n*iE*g8TZ>z%Jh6v_+{j{Jyf7-8 zffyY+oF3d%66&5SxF2h}F(l5RfR@o_P&}}A0WK8?B!lvf_&w>v#QOG`H2xpC(6sAV zoHUce9+kFa0A~#3Cg$2mIYPiT0mcCV+`lx}QrgP#X^NK8u@Xh*1}h=K!si{-XKqF? zagZxM+Er+z6W-bRQ^4?`Ek~*xaE~7}Kjj#`xqCDhbcqDYjKr{lmNsF8*C&<4a`m&J8ZESaxuZe{{R^v0C2<_h6ubkXEvhVA3jtu?3OG*Hz>gwEWCq- z&m-`w`g9%{m@b;yHk~w3;9KS7*%=^^>d{Cyovzs&oCDOH6`M`cZ8a9Pic7?HV)99A zJ5L;O?<`0xmnV7^VPLfRx%-8AMzf54*9;i}1mXTr%YwMxd1k=U zZS;$cYBbdB(CO2nB$JC%J<5b{ka=%$7V=3LR&m!L_Nw< z89@q0)|VWp=LF#7XComsaop-}FKi|aVW!&Y_MTysHO0-9#A;(cXTscmMt)wI2CNurD*+}mpCQD!PZ$ZIAjr*8?kWMiCjT;0oR_OqAMY~}KTWsSNh zOpFpA=2BRKz!BMc)UZK4gRGM362@c^7~>JKL~IO$*RKR%=hu-;O`2oE+Ed#}s%bi< z!*2jgq$8JNFcq0fvTWsyl}(fDY}auQFGBQ5s=e2p~h&&UkX}1vHcy0!_iq*p|pJm7-K0mHgxpsv>Ocg6o&c&}5xzToKJnLw zO{JH>FAiy1n;{oBdRTOcNLCw?4iO3D1z_i#0!|GLO8S0bq4GYV<9#;5VWn%(UfW%? zM{JhiV-YduB1zP2Zi5?n$EQd&?;hUyG0CKA&aow^EToxaxnK(&p&1mC01DiE-IQe4 z(AwU;p=;VwX_EL~NVIilChJDGx{6Z3Fhm>fhyy2++yR}{j zY0@Pzs8SS#V*da?`&rK&`O0@5hEIpQb))LqTye{$-%QJrv&(G)lsp~Z>luLe$Mam; zg8N0*C2zHe<8lhe<~kBX0&>798*zej(>?QB9}Rv4d^)(DwO@&!816I+xIj933ppn; zKR8hK>dK1546!*ovw}et>3$ylf_!PL=*!|ChQG9jgm2q%bhWr5vvX=hLOKxDdxe=VStX?qWjNts6Mlrzz@@dU$szkx8bm${38!%5a5(lr# z7zFj|LF5cqqBrc*`&4*Z!b{C!*Gtu{qL7t)4N}f|!6lc>5pORd0;C?Em|SMDFKzr` z;p>@y;VkgBt)odAhL$ZyMtDSE?it)S0HlNS07f&<6kT6NFEf;J;u|txpUQ_W0m&yh zKI4wu=RG~LJ+F(Vc^=(}$BpgNRdK|S*{io}x5@~(S)pkt@&)6>2%P3EJ`3&^09l3Zbq zdUor_Tz54kzK)~KO*Z91FgQ^e2e>@o5Kcdz8RCI0&H^8`NX(a_eXq&;*jjo%-h*U7)J2i9>BpNV}p`%KpvfPbCKvNE@Mkut?|b> zX31QZB;$erJY%QRBDzaW1o_3Ix&^^2h8s(q41C$g9CPc_9iko|zY?U^HX)uf%%RHy zI`Bz2>G{wK&toFl;u(fZBKwmq2idnCxjYg%{{R|>TWvWeSgtKuLz3Q9(S-nlc*y!5 zd*h(%TK7I2ovl(ytmH=yz`2i+&r)&5F;PXL-FfDC<}TAJurfFyvNPNO0l?=tIq5+5 zD1?ts5N|5p z$tLf$+gs%AI8(+3c?4sq;+9=5;#r>ZYm|6!SOEV(0KcuwWspDclEfc=I26;TYhxd3 zc+@J8p*~W6xj(5tmoy66oz|hCYLncJT0IsM`=H4c#n#kzZ{ZknSb?4Zp2F^P)t1naJCW z1i+2rB;y1sw$&#*e2=j3$Hgr!8MK@K0EZft&9R5ejw@iv9B8>AP`do*a-0_Gc7Q%? z)3vu6xn<_Pvb0sWxYH+(e4jZ3p6Um2EA47wQH(mJ+*OTNP9p1;HD59Gr}Q7IVloX7j_|Ez>kJ ztm@wmwMgKZSius+WWd91AmKc)#hVQ)Nv9{W3~(ISn8@#Z&_0yD)CT3KB{F6^vIszQToHwzOSuPj4@<(%#V zX~zmMdCy&Mq|K^XPpHSK+<9-47m;Bo0f{O~NCFen?vvZ0#d2DdUL?G}hfUJ-r;-bZ z<47m53me2Yxdq-*H-L9!WsY2Z+fj76gviBqW{}*Cc~&zL_*|EN97{T}5Q;&PKz%_YIj`@J2Q% zEkHXE-A=l8$nLI^C=pb{nF4u=6cFvTfXM@qj4nD1aIsumMuaz5O#Y`MLP?+W7&jm$H+at;XS2Yt(0koXT+g(6GzL3S8B zPaI%?LXErCKm&F`Ba(7S&f;hT7f{r7;cx!{66+r^v+~7v0Zpx*4&^6zKc9a>wo-VX zQ@Bgmm&{l#EEV$&>HkJFT6GVxnV16dK|IB#1kQd zb4U*jwRdnnW7PWb(-=CnkHX&w&jr=}rIax{s>IW2VOCWQoHLeG2R!`DxKWjE4&L6{ z9|~!~N%0JKESDIXL2%0PsRcsrQbz7h8FwbD2RH`HF4B zWl6!t0px>%0NHAB`hSc3KJrIxq4*;5@nn#g7fIGM$Br|GP#IrRW<_np6P>&R!RXs7 z4SYkgFNXKAK?EQy7k)0%k=HpNd(V}4>Bu#ou!C9ahhKB%2qh-SZTT(4X107 z#iK_uowjgzy2V5)$D#=CI3IL?yLbQt#zh!BM{}Y5n@hL&d9P0e&K)JWg5p?M4!dPk zWNa~T4nQY%PbAlDCDbAU+Uh`3U9rL*?Icr{@0P$kvu6Vbj&oAQcXwr|?zXm6aVFP- z=@pqawhEHcGX(*TagsC0G}F^!Td|3z+C#4huOsm5TSYGSWxJ8F5WoO3kPb+}&KMFt z@#hAq;oBRi8%npdvXU6`W||OjDOk@LBOvu&oep@$2AsA!-lM5cHOwm=xJOxSuI+?Q zr+>)UAAf+{9N-UKz&$N|OAefeP0}?8lGZ61U{L9`Sx6%+P$@WIKp-6UJdg})eJWVA z;F@KfxetV+7GN>6a?V zvyG6f;zm)&%Yl!*@{&N$AY}Ct%ViYTv)f->6lML?*6xjw$59%da0gz%5CO>tMA_M_ z?G63Q$Ttb6`!(94tA{RK&ejBD?+#DOayc~=_F4tvBSEQI$1B@_8X1+hg$e=Mpfdm$ zow@40&mca9JvU7#^TBB{N?+w@?o^?EcqrsED8>#!qkwUfK-$nJwY}5eh9y>JmQ%U}Mki<>f?KYBPOGXVLB_+*=!d zV;0Bjhxl@_-*4j?%+4KT3Q)oF1fr3(T&=Tr%*E)8Vka-%8$AQ->&bzh@bjpHUfyaDj zwMT8FNu@z0mXumcXOncIOK1t&+~HYKcpNe1i6L@MFnZf>gjy;|i|>dHh?uU+c#~l0 zdCpj#0p#bQ9Wpc|@VCO{m-{~d0AJMPDHju2UA#p{U%s!B-#1*6aqUY`cSz(P#J(lB z(B8wt`h?TRJ;J@za!BYM{{V%shoR#*9RT#=j)CzW=J4F;)_y6yw8X!^wu-6c%^Rz*+Vj?n9cT71Rw8Y zu6m3P{{WsTy1$4#7p~d;mr?LdrRsT@8HyrP0OuJXIT<<3sS2HmJ%B-)1Qp2dDu+8@R_j_TsbT_;IIdG6vP)w36KwgCttj z!YTquEts7G=e~OKK@dS@XBImR)~ zSJkzzgZCEd*3#VB-fsJ|U84pd44xDmcEL5~GJF&89v1IVitMWrBY3<`f3=YDjlO6Y z$Ix;3RQ?zE8R1_DYI5sV`jbwxkf6zNFPflZ0iVnz#t8tP!kbY`a<@7?OU0fDw6zxL ztqEE=GbCu6-46r}pk$0=jApY3iu4=XgpND=y~bjJCAE_*z;Xa=X9EWVCz{kg9MG?# z6W(dIlN(|CjsF0>01iO_lY%(G>ye(^OEsmHt+HKPt-Ac+hm3i6$iX0z4s+blxmhaP zn3DLW-V26?*G<$>+?EWwO~FSw8N&n$dv#$`pI+pX^`;GDPBQI{k&r$?ciqPxhq)wv6j?39bDp*FHidtu zG|8l~G2rZ2lQ|g305>@NF`j9bn(f8R1+2V3Bxw|xP17RmUV|%>>(hhyb6q{hh-__- zk#b>8r#@uHtupZ!1pB=+>9aitBvAFBZY1?4Kg-{Or&oF zisf)JODG|S1a%!jrp2pxa`Bet-%_;%zYMpLNVW@7HMR)wrMT2 zNDHqrBrKtd@zfZ16cfx!{`eo6T1G<|o%Q zzY=Q~IE`2|TCq`p2IksWBOSTwFatIh8%o2gYCa*?9qctozF1J0jj{t=Tqbg(UR~vDTI3S9? zf3Ynkk5HfDHl?b@qz@tEonnBg<#%~oTOdAi8<0j%QVF{q2A^f&tyQMfp=&Aftt!aE zH9JN~Vn`%$@A%e*zl7w1>&~(8<(tSO#Dp;|klX^xoQ=eAGuQIKie$RB`xJVk2;$t% zmfEJ|WWn5-WDKp#43x)Dmj{UC(B;*xn*RF2Lm7@a4AEOfn8|Re$9!ky+!%B`lY`ha zg6B)CTxoE`)>_W5rr9h+0uxba-A>$KfWw|T6P^zpssU&4?hQa{v1|Htn@}o5A~zY@ zFiPc#+<7FfNy#TUB=VZY)~9I#TWKC8XD<8Wchc=78?%DVhz3fXr*?n5dy2g-tKz#x zc=`NZWoT{>`9os;{w&goc z0c;#&1tb^H(tn5c_?}d|)EZI&Lw7njbpvkIEih&rjz}C6^7Hc4zwk7h1XfovBa)^) z;twM{h9r5Vvc#N#IKbzy&X?lNozlg3H;bdxU`wGJMW^2@Gda%&L|58ypkrqkY@RSO zZ@xaYt<}7@eju9a*+jtGL!y@`%D@2s0J;F-iw+9)&Iia#vlG3|G2kB^h^0^My9S9- z#j<&RNsBlm{aM_1W2tO_PCim`ZchOCjS3~9(=FtUfJvKPn6oa>3zU0z#s+xFW0T6{ zXOGrCC+WK7&AL6bbBSPv?IzQcdq^5HAoD!1IRUbMVlp$322DlR{4r;x8=Wfl)@h=( zK+7e=$t2892gn$zC{@Ys#GDl@MKpxdJr3jH@5Ec%Yj|xf^h$-HxVo@f^Bc_yU}O!D0mcf0k&sCk zp(e+E=eAw=66)y$`hJI|Iy8F~8va`?p_F{9<;26&9mpL;R@5|48fX@7wsw+S#*xPv zk4*7h#Ih*>F5;6aCj)L+oZ~!quPaSY#87IMw_{4MM2mbP(rrQGD=*C$JK59#t}?9I zAc9K~*0uMG^d-1Unfy95spVfcY+hk?O zOP>e$xw7i9$q$UI<5p~+oA>vzWHCJs4?O<>7E&EkPo5^dx7DtrdB#YW#ir|wWmGIz z5=YY~uR=i2e>498gwDqBMw%=Sx9svPmQ&0FIo*N-ENK|Z^Mb4f(m~*g=sYvwpBn0t z{{U!co)gqAZNSRMW23t{jFX+xLhKxcAgBziN$r72r`T7q(A?Z#`BuRA^6`~hB~0qY zz|KxK;4f^Fa&gxps!if4EtE5OqgLGPe8{AN1{((4{rIwRkVY~0di=zmdsgsA$KAeN zx56J8Sb&Pc=4~G2+b0M2Yq~AWRQY2gA9aby=A%7fjf?y}OS78_OL}QeDdJ3M*SK;5`70V}M38lhdf-mv@@1 zkw}JXq**SQ#UxWg!IL166U#Wr$ioAXjzP&f8b|ES`%GWQ=6HL-mlv|UfHlm`5G@*v zD{V5VIp>zi$547!ykD{}?RhnvntiXqU15xpfYYJ3E5E(~MNN`slH`wA5^TDGsx5A3JSz6ltPTK|58> z-lrqxA9x;k&rPWQ#hidGmdGrLy;3NA*<6u@ zD1ncdHx|Z5X&I!3T_zNIun>c)zoi zinXS>`*3(M85{R;uAeI^u0a8WGfltc@4)1c1!Sb(v+c&D;@kGm@RqqI_ZI0Ng>{#B zw>IDt%d~7`o=IYI-1ZOIN&S{9n?vGHjUHopH;m*+l>-PFpkm3;lG_&qlaIaCm$}7D zFT~Fft3jZnk@0NJ79P_o?5Z%M~mXsq2B2IO&(%{6W+f)g0ekJhuUi zGWp|TfPP>&1A@p7NaTGnKzNt-b<%C5AKRWTj#fe?8t;cRn+AT{m_D-*81>tLe=55_ zjQ$hox*GV4Q}9K#<*@}t%zi)6G}LgzCvzsPD9#3R$>j0ck7FxchcgE8cD43+Z*3>J zy9|Iku_e@U0VN0rf;rDYj!r6#?}~Lh##toQZ=;4G``;vxfsRMc$DY^&9FJ4YTek`#^*N|*{xy6Iv>tYo;9GbYg2FvhP}Oe0 zWdp0*_++UdW1-{nr|hJqV7hvdSnK+uT~7v|c_rkz&gYs&B@RhDM%|-~dlA@?QOmAs z69sc2k)s4ocdJG?{w_BzIpE}DlhYMQpT;q?+ayZ z<>!p=E7R*zX}`24k!fnvkJ<0TR_?NLbe{=d$N>kI{{TqW;Ny}*Z37s`2Q<|-!s+Z} zNvbuRau_mQ2ruO@-GVqE1{lf4Fa|TvOdNZU7te1Ak>O~S$v%9#hnR!-Knl4$VYBk% zr>HgBTz=NRD73fpd@JF5+Zhp^c17^hPmuu!0f?K#?n3eg;x?X{;MG?Br+z-_S0$eA z#X>3H6yFat7)be!et$Ce&=a&bUt>qIEi?Jk-_3^F;%!Z$%9FcX&4v)pXHH8 z9ng#$-7CaalFby}6}z^#Py#i~5~&Te%Qmwt#MRyzw3JB}N8+aV#YA$D)DnRGZ>g z?I)?+1=PH4bA4?f{onSVh&)fdzHUBotj4%MDd1oRl(hVWYTN8e3D&A0wG;*Uu0a4>prh2Vs`>^Mgiy2uoJ+#?2>(-TDa0?yJLm& zbY^5Du+7`t1CDZY&!EjsqkUiCJ2k23)BFPX{{UvpFT5YDX~`4(i>T>0AsB*wV$B%a z*nmfK=~6@RtH;qNm8tlP!&an5Jh^VQ>uW&VbGc-Zan5jk0nRc_a58u+Qic-u7TP3? z!!QxxJqAFa0x%4=5)w0ye?lsIABNIi-Yhp)xWJUjw<6sXs)aZf`vB)PJo<-A89y!#KGNk0^Y@{3vWAQkqxh=RWtxHy`@T@$MTK?2N8Ifges~xqTqjiokg*Nui zJa9A88uQ#!WloO~$1XuF#rafjmb0$FKZ@Fn=@Kt$E(RplZ6fX*CZMc$)hN z110x}Zzajxaq_%_jlEA?(%Sqv@S=fnK8L0pFxert4(GN3etkQC^^VABIJ|jx4a3Un2w|T2 z=aN59dR)+b2^()Gg*7Mx!SLfyhXZn@$B$GTd+gEW+uxkynu}F`4NG{U(?`&Rl11`= zX!x4`P^!MAbZD2SxXm6$jIvEN>`Mc){E|NmRCMEm+tQyD))x{4fxN~)Fxh}O>9-m7 z_xH^!raNeRj6buV?OUm|uVec<_|g#4A%<(s4rocoUzZ1J{HQQbImSL+)uC(q7=GN^ z+PBz#2x`i&h97Ueg%u9=$SU%TF~Sl@7$=1|t`k%Jq0*M?O9eN9xo`829 zDmYHEUvvKeF?-!lThjjk;GKWBn;FgD!wbe)fsRPVzota4GC^{UGh~q8-dy9HaaUmf z0D^7)*>YN}_x=X3u~bw7+ULbq>&`hWV`g4(6o5JP7!~u~_20w&e(w_aTVB=6WJtg8 zo@!3&f4|M+M#u^N7Hr_-I5@4OcrW37y~14HcpXBcj6L^=t|TB3c9G{ZFF5FZd*tCi zt1iEX{1Mtr`eA?Ip8o)}HkPdVuD9^#PDy7D(B62T$Ps5hbj27Wj;A>|>Nzb+*Y->J zjo{5+?Ux_4hrwuaI1lkB?7IydiqeHTv zs@~3 z{{RG5k?h)^!mkV1+ax!?wO7GgX(M17N&Y0kc&~%~&CvndjAPRP0ZDz}&)Is@PP!WJ z?Zxo_0871{5n`WC)grrC3buYgk_Osw_e&AmrF{EmFN1WFM~A#Aq3O3Dc0;BlaFPz; zyD5yf86y}6KAd(sPsFMG8?rq=#~PKa{#urT9co}fZKa0?gZFYsDnlF|K?%x-ME?K< z`Ukz#)$tC4`y@S#ns&4Q00lqr->sH_95`g_A9iF?sPvA{AIbbJ6L~bcrN|&!9d(!>~Se>I3olDiqF-&H~SE2Q6=uJ zqkhrP8cGPbm*L_noCA<0lWP!iM=W{|N`J%e+XLgD!+kbY@IJX?Z)t9%M{OU6Y}?IH zc^MG~1cT5L0Ui3+seCy90D_eKNYkT+KZyDUm#fKeOGS2yY^8t@2?q+oOk`jLQ=Dav zXuKY(H~#>)zu*O(^gM4*@Xzd0@=t4{{5tsGsI;;xJQ|;c>=hK@umP6y_h99)qYIOq zX18Xx{g`xf=eO*;;``W{lrr7;(@4|FU_b~0-~51N4ZXVoUiEAHeta?3H5s*CNxWgD zT_k@qXxg+_7SC_N-R0b}qB!}pwmjn>H#n_b5B9$J3*udH>X#q1Ux;jNq+m{ue`K+o zu0Y>CrLkpR`4WMH*J;HOm7cW!02U7C%hx_C_$i~hOJn~41pe`KfGH7_`15Y;LMX}G z9FmiOR1z|J4D_sMK0o{lO-@KIe_}mHPM%~e?W}n2>if?B0KPQY9lCVRK3@D+*mD-LhezXN%?SR+C@EK`Tqc!C(7Di?GK{r76RA7-?Qh! zKOjEXVb`uTd0Il;h6tKsv*$eHC-baLZ{z30>upLKv-=@_&a>_;66s;po^>GMh8RVH z4s((K>PW9=*MDO_g8mfKZTv&yZ-^S+iFba>4T@cOB2eolK?njdU*jYvnUjJ!Ad1G* z{{Ug%i`t&Eb7kRQ73jBGY!>EQ7S(KT^y{gjAYq;}9GeL&envj01Dq#TDQOk>jU&%& zKWZO{8fjfZ&*1OFjS)90wE9nkuBQOx@t4%o82S%R^|Pw}*MGF*?N15(ThVP!2{A?R zvI$uL?}yYS+yV8#!OnXd-Z1@_KWLu{BWexc2fem|l~#Q=>N|vSpW%!-l;o0k1w3aU z9-IyLhCF{RnJ&E_fpt{2v$!d9D{1niaI)hu7gPuU`A8jk$sVuUUu7SX9hn`+?N$4C zYq!V#F6M=GX*6dNf5N5HZQcG{;1`Bczc9hw_kHori_eXpABDVYtb9_|lHvFH?cw_! z7RaDng4zDf+vq?JNe9;;bMg3>Q8v&)1-h%8G?vW4QMVSt?qtSFssn{QFiemLrhjjE z-be~tXi@n~5Fu7@5QPK#tCAF{B$3DfoMQm?(_35aU*szqT9?Le+Jap^;XFJ0OMF+g zp4=-(Xa4{R#1J7$AUh>%7IAa@x1z#OCTPxhw0xe;n#v=_vEb}6Cxe%0{;`A3>I z?oKC*AZPCY25=5CGLY;3CA8C-qSAEs$T+n#ZW43{%aaJe{{R*SLD+J82`}|sS_37E zTiQ)^5>{@G!``b9QE6B>oB9C;Dl@HW?)@BQyFm{p7bKdTaeRFdhw)0qQk-q-`nBqQ3 zETvtP1BMHnl12zWdmwm<&qpEXj$1^49~c0l{{W+M-Lgo=d!7Z`N%fN~_fmLr zYh{yjvX~%$JP(+hi30)2W0f6w$pvb}_gbZ@5n*CyKmv(YGaEMG8Ax5gWpW40lbqn6 zlocwqWVK+dmV+$*9e6S`A%o%Phb?9be4%UM>sxkyG8C#RDz0;q2tO#{utC(*8^oFj zLE-I2T`@w0nKX;%iw>aU3O0=LeXujPo-xwn#E?i5Ed_Bl&C$!L&dvr-NWwmOKXfoW z^x#rQuU)+FCH9jNKtq_zxSC=%_3f?uDFO)!s<2a*JD)5LamnLwW5#){+g}hu z)=zT!htCe;&`ARYa6883M*c85FHG}HU0UM#=8D?b$ZcdNH_#XfDu0AA!!~d`jP%ZV zT=Kr)^)aRJ&73f`_LZdR_Kg-5ztOg32XrwHn2rb}VU8OJNCL>dy}Yy+)^N=motljvHuMBb7#Tqn{JaS%5xW>eSMht$vNh{NE&#WS=6}xehh}%u_t?r{;7C_S5A1*8FkQj`RIcXcq1gTyD7&rrp)V}zr>Rk*UU zj?Yr?{Demi&op0eM8P=-WN^m-{oSAuz&sOESZVq^6I?+(++S#lMOTh8<(E7H!)lbz z9FTLLmpC;-KNIWl&e2=iCJd^o$u>Sy$Rxh(5su^xDd6I?^)D0Me(XqKDJ*IsRsu6K zju_#FF@iYSNZZNjMxkVcg4#Lmz_@|sMqm|W9%0W1ETmwL0UZuO>6}xg({E$Anrkfr z86ciVGs3qRl0L@+3^~Jc3Xn)Ved5>Fh0GRmO>Bxz77eflQhI^Bk-+PaIV0OOI$rC~ zaBc0N8Ql2u9YM)G4gei<$Q)pjI?$bg>P(4sr(4A>rL!uqD#e|K0pO-dJGeN(?ZFtu zL3{p^BDT7YDWcwgcIuBF?buPZzFu-TImsM%r%f%%xG|`T6C2>pEsB>o8Emj$Zg4Z# z0-q+N(?HT%Bg^w+WB@2(*oR^i9!SP>la8j9!6P}&mt{DEPL<}AbvOp9=V=&pbFhxDp|^sC|hi$Cnp2c<2ffhQPefLUcxy9Ld|sfZRBzZ!Ec;p ze(?M=(Dv`vW2dFM6ur^}GwyigaHY0$oNn4Wvo=OLoSvr=eWLc@vshkkha<`icBupr zpPPZsGINimMW^cD*=rT0-jiu`?l4pB(~p~gyILT;kE!I2F;3xbgo4p$xi-@WD<0Qq zy!MDLdhG~TjC!6w3>x=WiD3xZ46Fyrizap+2;I&HQO@e?yf5(k;!ng7x_^Vb6L)(# zOoXtvkK`&W4*PN(vjBeb5&$D44E=ehO{smN5^a_k*nr!naq{qbgMvB7e$-qo zVs9pStj)YZLbGmg%DbJwz&XGKan_?PsS9#~**w7DNa~w<40QGWbsU~00%bs|H()cu zV=s}AM+`^@*Bs)XCZwWOx?7pSWh~KoL3!Q5$jQz=rz47O+(~FW*v#ndacWiA{H(Db zPfYuMzVtSoJa)?#@g~AVWP(Uxi~@MiA5Nc?Ew9@nd5Z3=(rw=;++2>_VB-U+9CyVk zYBD%7v}&#ZPnffeW2oA3j(;>OLllC>Cq0sUeGUk2t)st~;IFFCBO}?b@I|Bbwq? zwVPLrBgdG;7%tct$k=m?f!8_U_ot|pwl*VSJ)Ce{TC7tyG8L0Nk~`#mbB=i!9VKV&oad7q=VdLKnJgLOt96pxo2BzYZ}omqS@coQ8EpcgWa16lMRZoRBgyN$hVeZ8Wzw_8LoDvQ4&2c+t?7 zJ$_v640eEc#?=HV=~2ybqsK2cjO3C5+N^sVfUG#d>6{(Ik@Et8#kKIaiM7!!?uVvp zv!q{nZ7ijoQ`_W^C-FJ=#wwPmB+(mvT3(sei=_$Kt0B|=Qz;p*7U~z-S05JSh;p^$78it!_jU00Zb-N{sk%hwi$7tuU?tM;d zSUuc!!U+VH(@NO4xM^i~+CceK9ltOEa7hE81CyScPt$Hqw9zHYOc79Q7tpI^hy?j+ z6|v4ZAdT7VY7JY%($8!5RIpr}=`>55h}mtm zc&B+m%6Xtm7h=Q$N{nNky>ZB@@LFE!@S&eXLbB=A8!t;@x4C~2=HgMXIm$Cw9}$Na;jfFo5&RDzz3{(^^_v~j zCBxg<-pLBN$T*P$1zS28X-&U-s4b_oT)w zH7^vwtk?w}b)KuI!6}*|hjeo@D3Go@mQr)q*2cN}bo@XP#dD!}M^Dp=9g7x~sNKOC z=OC@KTuE%C40VCVPIJdUIX8~{8>wAmQ}LCyo{~)>d6JU8YJ#8!RwHIIe~1Pd$QT%l zT=9p)E1;Tvw~GwsSa-^jST6a~3_j=0yY7YOu1UuDCZFM7iGC^9H3w_Yj#`zB6U2!u+MM^%xd4Zf zBzP8mn?Y<5mBa1;`!voWyuNkuOAMFwL(Rkmn-^4_>RvuDnx_pVS@sbuu00SY8PUFTlsN`s9 z@_y9zn$mcu;pf2!tfz_ANG8@iRb^o$OfXqoO)5Laaj}$~FzeU>(`#)$W4h{hy1&Bx zPez+<$A%u=WP$Fs85(I`AaFLzlCOeFgamSk`$dR;b zxfxu@n@Nf+ylSc+=3M-_P`@@SHva(PC;Swu`%;em6Tr5*e88CW>wg#cTwADJ$9B&+ z9Q@Do0lWza-M}?p!8c#<|EGB@#>nE;Om-j&zyC4I3(w#hITxVYvjWVL%|)ccjh#00mIJ)1OF}%OADpkNiV-to%X!pmZDiyO@H_cP59eo2Ti5^G#r^ z!~mcY;0ytQwLqs%Hu#s~HReM1It|slcUCgXcPNNYc+U`TCykrX`I3fA5J4X(&rAVX zuz0V-vR(NWdW=tbCgqeQueh8L2H45YLXH3jxj5kL+tUTKzAG{+(Pg%{?%*ff3Q!)e+;sjKWUvy$}p(G@TQG$u1E15y0@4QINlzKpy_Ag{t#cZ>O(nFb$sXnfa>^9sZ6|Rjra9axG>un4yN&ca zHI~X)B@J+r8~}0y5W7Q+e8(W3oM!^P67%*`x|&HYFaAE<+D_-@njZ;TK!c9KCb&_@ zsBGtuYR!l2i}1-~`&Wp*FL;9D>|vypFJ9SQh|cdWJt2^SJ#*?g!J^B!uXVBJ#^%RU zPqNwGO*kiVzQQ-CAZ-jrNEu$cMpT}9RY?uh4~xse{Gmn*bQ}TBVAM44*~h|Cn~hS>;$EgE z0&pnbVA;eaRX@Y*e@?W0{G_n zguf)ZwuyCREM+<}hSy>$aB>+}IO$X_wQt!sPQNo-e##y?xVg4$nc>uKJiti|cD$Or z-B^><@=v`|r0sw59sG|dxt0qlMbGwq_1ecW0Jg1j5oRP{jlV7ekOtrfCnq@-TT<2_ z)W6W};F2j(7%Y2aE>z?Yp<-lX(>QK_ybn(P{{Z%Z{h0J%_ZA=UPtS|DF+vKMaST2Hw5$3DLGL;f3k;KiovT|fQ_fAKu5r)wqG!<{h_PBE6wf!`f|wWrmkeZLeJ zIBP3^5T$@kZbW!OlytndiA?K)c9s&TPC^1Oah@~HO&5q~5=V354PQ;Pj${R1(ny;M zr#JvjkT@W4J78e(>sm+c2D*iSz3}h+6DrEy91kq&8t#i{Is$ve6|6HZJ&<7g)y-S> zrSb2B^uM*~AF?-vEzgu)Vzu$!whE9lhx;&&H0jA^RT#-4rc@kVn_qhW05NLM3X{d& z9@cGSf*RMOrqGFTeb*3!97Nh& zBV5OE9go7#8P5YEd63(9dhxct(h0%_Mj(@x9YGx`3H%N58c3r1Z^GXeTSp#IVm=qW zwE%4k^BJQIN6I}qcgfFJuY7L!<7DybH?ep|8$bxZlGovOl8+F<`GA_;&NH-k^&kL9 zqh%~SDI&qt^Bx1-{ttrLMCF( zxq_lzGYlS|G9IL0*H3TzP56f5HN21G_OBFd3Hwc##2aYhWjGn#1={hEo;Vrp#cDT? zJ|JorR`#>_zv3>T=106r-Y~hE23`-$P1B*^@!#;JWS*jTIm5b?2HjzuAl9%Tn$bX$wZErzkL>>d<0}~BYmXavM#?DW<*xNRsDM-b;dLFbVtDK6^fk3{@X_PaUei$h zq1Ek@AQsa@x6!2pa&QqfQ6rPj9eZ>*t&9HvkSa%|LOxx<2v9I5fK>GQbnnG;`n{)# zE~WEQ@uP}NIyp_d&|v7 z!u}|^(%R;6G_g!P=$Z4Am5>sE^TM)@bB|q{KZt%b`%HHFmxk@G;}R&6ODndHK%4^0 z8zEei&~NGaR9&v4HmyHqZ-Dxo{{U;b*1U5J!SnKXst!&@4pz`{i~ywL0~oC&{ht00 zTU~D3;wz>KNc&!_gz@hhCsWrI%3l0zw$XJIw}-jzpWl(v3pAJC&L{jJi8A7*}LEgCb_!1W?_Olt;o;v z{V^5yfjk9zj$gv|5R#`RJDbd$oZy$aZ)#bmkXy*b0hOFE!8=0p(~^3A6w7ug`1G$OtqT_9lY4wkIt^z_#%ElU zyTbZNB%bAkp6Rq_v5z}1e&~#*p?I}kIX$O+*Y}JO0n=>p6~Ww z3~7tme5n4*&%fD(f2A6pyQo<){>0FXXvYQO@gA|j#~1?rN9k0B>FtB3fPaYZ1C!8T9DfnPs&{&? zj-`}Y$>STRFz(C88kprMpl)X506tTIa6vgEBR17Fs(*lMbFPE_3HAF&Ng%zs*Zd`? z>}T_)^JjpCC-|4_?zsQ~)bo}Y=qbzp00iv#$7AAD*6)5V{4{kDkYT;nWKG2NCv~wq zxEz9~8P7ScX8!*G#jx7>wl~YYTm3DZoP)WtKnEp;03)$IxT=XZxfDgB@g>2UM>5VM za~mlNM+I;IL7LKs;1_}6xi+_79Q1hSV9XlXEal3MHw;~l21ZXGhDCWj&xj_y`!$ZN(Yq$i zq9hT8P{#=9yad4Ov~}d1@m_BcX)tLib1HnzFoIOtVRUfgrNKuZA90JV)^|$5)0K zlg+x1N0CfBm~00UIZ%6X&*Vp*+eu*a3y9&8=_WpMMJ@`IDyMMZ@(2KsdvqXWZP?sN zY?0mSF-ZUn5gIAf31rHK1<|mqypj}?#?mUPcuz5)Kacq;>zZ0 zen=;iC5tIMbjbq^>B9J<;UDbTsvkE@{{V!gM2Mz4apD+JF|;Vg`yBK*qf{?PEA#K`J@Uc+M(#Y^6(kb7?9R49?5|Qz^+AP#bOlU@?#3 z>NCQ)u5MZUBmJBsQ)#Z9dg&r|l~VToWl$NjkKGt@I2@dxmyUw0>7Ee%nr$GEYF`U9 z*`Z&SDWlZ2K=Omm;ulc?Gu#4e2m3za&c|n#zG{KPV+S6YRLDMzMc!t3(?4q+s ze95C?u)`39I))j+9!p7(K~>~0A9+s{)RG0*?(cjL`#VK3xzj!j=&&T%l0UU-dYE@4 z2F@kAanr7SM;z1|p0)cr_*^B+073n!sLIRbCJt~q0zgrn$Bo?cgBOQ9KW}*cc!J`_(rZJPvVfwh zylc4;c5T|+{pH65e86rg$z2Z0*FApk;;+CIc&|J&`#NYdTmn) z;ps2rwTk4phUPYmDmDV`l`(^WIxaf=y-jsm2EC=}caUio-Wk7!;mbmj>M*?0E24#UI`h=`MJT!HO4=QP5qdj?@yV;NCJhM z5wI#4E;@|lbnHMKvn)(j(p_C%NUf$pzAKw&ksXLu#^&pdtiz4mo>z>eD|AB9YHh#m z4e{GrvPQP??S2c$~6N!vV zs@x+eJK~a8?0Mw$=BUf!?RXILU6~0ZAx3l5<0RwWyz|5V02Q=-W(IpyP(j-@tcn$s z4!mdE-?8bM=wjC`ZdJvDZ%`L`P5?L@XFPN~k56+&*zRiGc((bZ`ER6;XWs^8$UmqA z_UZhojjCQakyItQl4L$qk}z!GJAu>mAa%zksY54~ZUB!8 zSe0cY;Ql>1{V2V{$k$oqRAw^VbG5J~W$o9mOq}3UpKC~DS)^wnFwCPIfs#)obC2XJ z8v9b25DrK!wU8Y4C%;|?Z%<)WjRZIph}zye zc>d{Dmx82ZbI|+#b(MFhd9a&%g^Z?lux1P!gT~T003Xw}VjXr8ra3Kx4X4oUUI8HX z9MsRO8B_yv6l}zRs~8}10U%?Jy*+Wspx^+Bp?qTt34qOjr;>Qp1m%fsame{Q7Y4 z^T(U0Q5VTG3)D^ zs-F{K)GuDv#t4XufwdgrPC3XtWcm+Np4AP{fjluP$s8JdHs(|z-VkCWdzB#j@##@T z;Ef?6hT6@p0RYB!-O0Fdw>jOG=RJDky;8M=_atu{tZnjXQ3JbX>xFf~fKT2p#t6nZ z=abY`X?`W?amLYH=~l`LV2$zy!jYT;M|1Ct_Ul!3FNB%~u6+CCzmIPYBi3&s$qSxL za)H?7XB|B&Iv;_b53GY-K8bfAmCF^n)vl*dTLT+pmAd7*@5t##x*W$xW$_ZmX~IFG zMx}y|C3Cp)0RuS$sKyRG$<1TTy>;;X`~0nqRd)!cPakmfK`M@Qm9l zZ9bnU9dr5g%|CVS39_W$9X=20hDD#oT2ALw32K_eOOifrHooGx9{K0fiyEZ=01muk zW0cWt?(Kq~kY3DIMI?+8t^pYUZ9Q|yAmF;k;V;5{Bs*Jp3qydMs|d8nS2^l>@xta1}uON2sOl+d=Mf-URqn@JHc>q?-4GejCB3-L6?C zj_%>MWR1Z~%EdY^0Kf%7$4qoumx;V5du$(E(X^*%!4S!Qi02*tXBqAZ!Q&^6Yhkra zwi;9p#|0TX@xbYh{{W>*nz0!xJdq(e3OZzT!5@I*)32zTz1tIaIpOh(!CG+^Hy$L? zZ6%5VL=s%0VKJT9>yDhB593j{#{U2V$0T}C@rIW%4V4gTiI&T9HiC13#}(AvYogLc zwtYp3R9rEWAdS6!`k!8v9nQ4|He4$P=-yx|Xwu^IIm@pdM6tr-N!G4b zW!)Rv=yy{_Vbx;*o&^czB$Se!u72)}7E@oSfGFH@MqtW#CVEXylp4n=Zi8%6jg1 zY_m6BNhEvpt!Qw$YNfJ;@jyMOK?qNeExH z5P1ZH&rAW5arjpqH^lz{3qc%n+F4x{WBbkJ5CRVaZsKxsI*(ta7T*-3yipW3mdoa| zD#`Z0Fq7B}CJE2J2OVjpaW!+MpH=ei1XH;?hSmqzbCcT{qD??V%eY_xyCPl=c)&Hp zYCjP*`E1(LPq4Rnddoa41EC+qweT<(C)APYSQGfu#CF%JpYV{gv5~jp*#ft3lm%gq z{1MiT2eLg_X)nP*2sp?%>-4B+@f>kVEvG87sK(v=uYQ&0a%;N8dRrYsTb-_; zK`C)^z?C`RC?F@(KAaQLkK!Bq+w__ZUG1k-+RZ#=fFybo$6x;dT{qAzq3Ow~Ni=?3 zQYvA(o=4+VzqMyadv{^UJ4ot(F4?a?Ywr+U*g`cua(u}*?~Guol+PPNw+DmC2a-Ef kw_YLDZS^OL+U<-?ib9K!-!|TPWf<-W=cj5n(FKwJ*$0t np.ndarray: ) +def test_tensorrt_conv_bn_3d(): + if skip_codegen_test(): + return + batches_to_test = [1, 1, 0, 2, 3, 0, 1, 3, 2] + x_shape = (relay.Any(), 3, 10, 224, 224) + x_data = np.ones([max(batches_to_test)] + list(x_shape)[1:]).astype("float32") + k_shape = (64, 3, 3, 3, 3) + params = {"kernel": np.random.uniform(-1, 1, k_shape).astype("float32")} + result_arr = [{} for _ in range(len(batches_to_test))] + for use_trt in [True, False]: + x = relay.var("x", shape=x_shape, dtype="float32") + kernel = relay.var("kernel", shape=k_shape, dtype="float32") + out = relay.nn.conv3d(x, kernel, channels=16, kernel_size=(3, 3), groups=1) + out = relay.nn.batch_norm(out, 0.1, 0.1, 0.1, 0.1) + f = relay.Function([x, kernel], out) + mod = tvm.IRModule() + mod["main"] = f + if use_trt: + mod, _ = tensorrt.partition_for_tensorrt(mod, params) + + if not skip_runtime_test(): + with relay.build_config(opt_level=3): + relay_exec = relay.create_executor("vm", mod=mod, ctx=tvm.cpu(0), target="llvm") + + for i, batch_size in enumerate(batches_to_test): + result_arr[i][use_trt] = relay_exec.evaluate()(x_data[:batch_size, ...], **params) + + if not skip_runtime_test(): + for i in range(len(batches_to_test)): + assert_result_dict_holds(result_arr[i]) + + if __name__ == "__main__": - pytest.main([__file__]) + test_tensorrt_conv_bn_3d() + # pytest.main([__file__]) diff --git a/tests/python/relay/test_op_level3.py b/tests/python/relay/test_op_level3.py index 82d056381666..15202c59cdef 100644 --- a/tests/python/relay/test_op_level3.py +++ b/tests/python/relay/test_op_level3.py @@ -1035,6 +1035,41 @@ def verify_scatter_add(dshape, ishape, axis=0): verify_scatter_add((16, 16, 4, 5), (16, 16, 4, 5), 3) +@tvm.testing.uses_gpu +def test_add2(): + def ref_scatter_add(data1, data2): + return 2 * (data1 + data2) + + def verify_add2(dshape): + d1 = relay.var("d1", relay.TensorType(dshape, "float32")) + d2 = relay.var("d2", relay.TensorType(dshape, "float32")) + out = relay.var("out", relay.TensorType(dshape, "float32")) + z = relay.op.add2(d1, d2) + + func = relay.Function([d1, d2], z) + + data1_np = np.random.uniform(size=dshape).astype("float32") + data2_np = np.random.uniform(size=dshape).astype("float32") + ref_res = ref_scatter_add(data1_np, data2_np) + for target, ctx in tvm.testing.enabled_targets(): + for kind in ["graph", "debug"]: + print("HERE") + intrp = relay.create_executor(kind, ctx=ctx, target=target) + print("HERE2") + op_res = intrp.evaluate(func)(data1_np, data2_np) + print("HERE3") + tvm.testing.assert_allclose(op_res.asnumpy(), ref_res, rtol=1e-5) + + verify_add2((10,)) + verify_add2((10, 5)) + verify_add2((12, 4)) + verify_add2((2, 3, 4)) + verify_add2((2, 3, 4, 5)) + verify_add2((6, 3, 4, 5)) + verify_add2((2, 3, 8, 5)) + verify_add2((16, 16, 4, 5)) + + @tvm.testing.uses_gpu def test_gather(): def verify_gather(data, axis, indices, ref_res): @@ -1306,39 +1341,40 @@ def verify_adv_index(data_shape, index_shapes): if __name__ == "__main__": - test_cast() - test_zeros_ones() - test_unary_identity() - test_clip() - test_transpose_infer_type() - test_transpose() - test_reshape_infer_type() - test_reshape() - test_reshape_fail() - test_reshape_like_infer_type() - test_reshape_like() - test_take_infer_type() - test_take() - test_full_infer_type() - test_full() - test_full_like_infer_type() - test_full_like() - test_infer_type_leaky_relu() - test_infer_type_prelu() - test_squeeze() - test_squeeze_infer_type() - test_squeeze_bad_axes_infer_type() - test_split_infer_type() - test_arange() - test_meshgrid() - test_reverse() - test_stack() - test_tile() - test_repeat() - test_gather_nd() - test_isfinite() - test_isinf() - test_unravel_index() - test_sparse_to_dense() - test_fixed_point_multiply() - test_adv_index() + test_add2() + # test_cast() + # test_zeros_ones() + # test_unary_identity() + # test_clip() + # test_transpose_infer_type() + # test_transpose() + # test_reshape_infer_type() + # test_reshape() + # test_reshape_fail() + # test_reshape_like_infer_type() + # test_reshape_like() + # test_take_infer_type() + # test_take() + # test_full_infer_type() + # test_full() + # test_full_like_infer_type() + # test_full_like() + # test_infer_type_leaky_relu() + # test_infer_type_prelu() + # test_squeeze() + # test_squeeze_infer_type() + # test_squeeze_bad_axes_infer_type() + # test_split_infer_type() + # test_arange() + # test_meshgrid() + # test_reverse() + # test_stack() + # test_tile() + # test_repeat() + # test_gather_nd() + # test_isfinite() + # test_isinf() + # test_unravel_index() + # test_sparse_to_dense() + # test_fixed_point_multiply() + # test_adv_index() From ff29e0caadf8a0c6168937fd8e3054f302f0d410 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 17 Dec 2020 19:24:35 +0000 Subject: [PATCH 07/44] SparseReshapeOp --- include/tvm/relay/attrs/transform.h | 7 + include/tvm/topi/transform.h | 188 ++++++++++++++++++--- python/tvm/relay/op/_tensor.py | 4 +- python/tvm/relay/op/_transform.py | 16 +- python/tvm/relay/op/strategy/generic.py | 9 - python/tvm/relay/op/transform.py | 12 +- python/tvm/topi/__init__.py | 1 - python/tvm/topi/add2.py | 32 ---- python/tvm/topi/generic/search.py | 14 -- python/tvm/topi/transform.py | 23 ++- src/relay/op/tensor/transform.cc | 105 +++++++++--- tests/python/relay/test_op_level3.py | 210 +++++++++++++++++++----- 12 files changed, 482 insertions(+), 139 deletions(-) delete mode 100644 python/tvm/topi/add2.py diff --git a/include/tvm/relay/attrs/transform.h b/include/tvm/relay/attrs/transform.h index 3ed6b8352845..86042d0220fd 100644 --- a/include/tvm/relay/attrs/transform.h +++ b/include/tvm/relay/attrs/transform.h @@ -387,6 +387,13 @@ struct SparseToDenseAttrs : public tvm::AttrsNode { } }; // struct SparseToDenseAttrs +struct SparseFillEmptyRowsAttrs : public tvm::AttrsNode { + Array dense_shape; + + TVM_DECLARE_ATTRS(SparseFillEmptyRowsAttrs, "relay.attrs.SparseFillEmptyRowsAttrs") { + TVM_ATTR_FIELD(dense_shape).describe("Shape of the dense output tensor"); + } +}; // struct SparseFillEmptyRowsAttrs /*! \brief Attributes for ndarray_size operator */ struct NdarraySizeAttrs : public tvm::AttrsNode { DataType dtype; diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index c90d7d7ac396..be31a77bb660 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -501,7 +501,7 @@ inline Array split(const Tensor& x, Array split_indices, int a begin_ids.push_back(idx); } - Array > out_shapes; + Array> out_shapes; for (size_t i = 0; i < begin_ids.size(); ++i) { PrimExpr out_axis_size; if (i == begin_ids.size() - 1) { @@ -1344,27 +1344,173 @@ inline Array meshgrid(const Array& inputs, const std::string& in return result; } -inline Tensor add2(const Tensor& data1, const Tensor& data2, std::string name = "T_add2", - std::string tag = kInjective) { - // Array out_shape; - // for (size_t i = 0; i < inputs[0]->shape.size(); ++i) { - // out_shape.push_back(inputs[0]->shape[i]); - // } - // Array result; - // for (size_t i = 0; i < inputs.size(); ++i) { - // result.push_back(compute(out_shape, - // [&](const Array& indices) { - // const int src_index = (cartesian_indexing && i < 2) ? 1 - i : i; - // Array real_indices = {indices[src_index]}; - // return inputs[i](real_indices); - // }, - // name, tag)); - // } - // return result; - return compute(data1->shape, - [&](const Array& i) { return (data1(i) + data2(i) + data1(i) + data2(i)); }, - name, tag); +inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Tensor& sparse_values, + const Tensor& default_value, + const Array& dense_shape, + const std::string name = "T_sparsefillemptyrows", + std::string tag = kInjective) { + Array result; + Array sp_ordered_output_shape; + sp_ordered_output_shape.push_back(dense_shape[0] + sparse_indices->shape[0]); + if (sparse_indices->shape.size() > 1) { + sp_ordered_output_shape.push_back(sparse_indices->shape[1]); + } + int num_rows = static_cast(dense_shape[0]) + GetConstInt(sparse_indices->shape[0]); + int num_cols = GetConstInt(sparse_indices->shape[1]); + std::vector> sp_ordered_output( + num_rows, std::vector(num_cols, PrimExpr(-1))); + + // std::vector> vec(100, std::vector(400, 0)); + std::vector missing_indices; + std::vector current_missing_index{0}; + std::vector total_missing_indices{0}; + auto empty_row_indicator = + tvm::te::compute(Array{dense_shape[0]}, [&](const Array& indices) { + // for (int i = 0; i < GetConstInt(sparse_indices->shape[0]); ++i) { + // sparse_indices[i] + // } + PrimExpr current_number = sparse_indices[indices[0] - total_missing_indices[0]]; + + bool cur_flag = true; + for (; cur_flag;) { + PrimExpr ret = if_then_else(current_number <= current_missing_index[0], 1, -1); + if (ret.as()->value == 1) { + PrimExpr ret2 = if_then_else(current_number == current_missing_index[0], 1, -1); + if (ret2.as()->value == 1) { + current_missing_index[0]++; + return PrimExpr(Bool(1)); + } else { + current_number += 1; + } + } else { + total_missing_indices[0]++; + } + } + return PrimExpr(Bool(1)); + }); + result.push_back(compute(sp_ordered_output_shape, + [&](const Array& indices) { + PrimExpr ret = -1; + // ret += missing_index; + // int missing_index = 0; + // PrimExpr current_missing_index = 0; + // PrimExpr count_missing_indices = 0; + // for (int i = 0; i < GetConstInt(sparse_indices->shape[0]); ++i) + // { + // PrimExpr is_missing_index = if_then_else(sparse_indices[i][0] + // <= current_missing_index, + // current_missing_index, + // -1); + // if (const IntImmNode* op = is_missing_index.as()) + // { + // if (op->value == -1) { + // PrimExpr on_current_indices = + // if_then_else(indices[0] == i + count_missing_indices, + // current_missing_index, -1); + // if (const IntImmNode* op = + // is_missing_index.as()) + // { + // if (op->value == -1) { + // continue; + // } else { + // for (int j = 0; j < 6; ++j) { + // break; + // } + // } + // } + // count_missing_indices += 1; + // } else { + // PrimExpr current_missing_index = + // if_then_else(sparse_indices[i][0] == + // current_missing_index, + // current_missing_index + 1, + // current_missing_index); + // } + // } + // } + return ret; + }, + name, tag)); + result.push_back(compute(Array{dense_shape[0]}, + [&](const Array& i) { + PrimExpr ret = Bool(1); + return ret; + }, + name, tag)); + return result; } + +inline Array SparseReshape(const Tensor& sparse_indices, const Tensor& sparse_values, + const Tensor& prev_shape, const Tensor& new_shape, + const std::string name = "T_sparsereshape", + std::string tag = kInjective) { + Array result; + Array new_sparse_indices_shape{sparse_indices->shape[0], new_shape->shape[0]}; + std::vector multipliers(GetConstInt(prev_shape->shape[0]), 1); + std::vector dividers(GetConstInt(new_shape->shape[0]), 1); + + tvm::te::compute(Array{1}, [&](const Array& indices) { + tvm::PrimExpr total_ele = prev_shape[0]; + for (int i = GetConstInt(prev_shape->shape[0]) - 2; i >= 0; --i) { + multipliers[i] = prev_shape[i + 1] * multipliers[i + 1]; + total_ele *= prev_shape[i + 1]; + } + PrimExpr division_total_ele = 1; + for (int i = 0; i < GetConstInt(new_shape->shape[0]); ++i) { + division_total_ele *= if_then_else(new_shape[i] != -1, new_shape[i], 1); + } + for (int i = GetConstInt(new_shape->shape[0]) - 2; i >= 0; --i) { + dividers[i] = dividers[i + 1] * if_then_else(new_shape[i + 1] != -1, new_shape[i + 1], + div(total_ele, division_total_ele)); + } + return PrimExpr(1); + }); + + result.push_back(compute(new_sparse_indices_shape, + [&](const Array& indices) { + PrimExpr flattened_idx = 0; + if (sparse_indices->shape.size() == 1) { + flattened_idx += sparse_indices[indices[0]]; + } else { + for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { + flattened_idx += (sparse_indices[indices[0]][k] * multipliers[k]); + } + } + Array new_sparse_indices; + if (GetConstInt(new_shape->shape[0]) != 1) { + for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { + new_sparse_indices.push_back(floordiv(flattened_idx, dividers[i])); + flattened_idx = floormod(flattened_idx, dividers[i]); + } + PrimExpr ret = -1; + + for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { + // auto ret = tir::Select(indices[1] == i, new_sparse_indices[i], + // -1); + if (indices.size() == 1) { + return new_sparse_indices[0]; + } else { + ret = if_then_else(indices[1] == i, new_sparse_indices[i], ret); + // PrimExpr cond = (ret == -1); + if (const IntImmNode* op = ret.as()) { + if (op->value == -1) { + continue; + } else { + break; + } + } + } + } + return ret; + } else { + return flattened_idx; + } + }, + name, tag)); + result.push_back(compute(sparse_values->shape, + [&](const Array& i) { return (sparse_values(i)); }, name, tag)); + return result; +} // namespace topi /*! * \brief Transform the layout according to \p src_layout and \p dst_layout * \param src the source input. diff --git a/python/tvm/relay/op/_tensor.py b/python/tvm/relay/op/_tensor.py index 69f5b8edf9d5..f199ac563a1f 100644 --- a/python/tvm/relay/op/_tensor.py +++ b/python/tvm/relay/op/_tensor.py @@ -21,10 +21,10 @@ from tvm import topi from tvm.runtime import convert -from .op import register_compute, register_shape_func +from .op import register_compute, register_shape_func, register_strategy from .op import register_broadcast_schedule, register_injective_schedule from .op import register_pattern, OpPattern - +from . import strategy register_broadcast_schedule("log") register_broadcast_schedule("log2") diff --git a/python/tvm/relay/op/_transform.py b/python/tvm/relay/op/_transform.py index 093eee39fca5..3f2d37d4868c 100644 --- a/python/tvm/relay/op/_transform.py +++ b/python/tvm/relay/op/_transform.py @@ -63,7 +63,8 @@ _reg.register_injective_schedule("sparse_to_dense") _reg.register_injective_schedule("matrix_set_diag") _reg.register_injective_schedule("adv_index") -_reg.register_injective_schedule("add2") +_reg.register_injective_schedule("sparsefillemptyrows") +_reg.register_injective_schedule("sparsereshape") # concatenate @@ -126,6 +127,18 @@ def compute_scatter_add(attrs, inputs, output_type): # _reg.register_schedule("add2", strategy.add2_strategy) + +# sparsefillemptyrows +# @_reg.register_compute("sparsefillemptyrows") +# def compute_sparsefillemptyrows(attrs, inputs, output_type): +# """Compute definition of sparsefillemptyrows""" +# return [topi.sparsefillemptyrows(inputs[0], inputs[1], inputs[2], inputs[3])] + + +# _reg.register_schedule("sparsefillemptyrows", strategy.schedule_sparsefillemptyrows) + +# _reg.register_strategy("sparsefillemptyrows", strategy.sparsefillemptyrows_strategy) + # scatter @_reg.register_compute("scatter_nd") def compute_scatter_nd(attrs, inputs, output_type): @@ -456,6 +469,7 @@ def argwhere_shape_func(attrs, inputs, out_ndims): _reg.register_shape_func("scatter", False, elemwise_shape_func) _reg.register_shape_func("scatter_add", False, elemwise_shape_func) +# _reg.register_shape_func("sparsefillemptyrows", False, elemwise_shape_func) @script diff --git a/python/tvm/relay/op/strategy/generic.py b/python/tvm/relay/op/strategy/generic.py index 018cd4ae9801..ac9d3b157ec4 100644 --- a/python/tvm/relay/op/strategy/generic.py +++ b/python/tvm/relay/op/strategy/generic.py @@ -1063,15 +1063,6 @@ def scatter_add_strategy(attrs, outs, out_type, target): return strategy -# @override_native_generic_func("add2_strategy") -# def add2_strategy(attrs, outs, out_type, target): -# strategy = _op.OpStrategy() -# strategy.add_implementation( -# wrap_compute_scatter(topi.add2), -# wrap_topi_schedule(topi.generic.schedule_add2), -# name="scatter_add.generic", -# ) -# return strategy # scatter_nd @override_native_generic_func("scatter_nd_strategy") def scatter_nd_strategy(attrs, inputs, out_type, target): diff --git a/python/tvm/relay/op/transform.py b/python/tvm/relay/op/transform.py index 02efc8a870f4..ba11488763a5 100644 --- a/python/tvm/relay/op/transform.py +++ b/python/tvm/relay/op/transform.py @@ -1324,4 +1324,14 @@ def adv_index(inputs): def add2(data1, data2): - return _make.add2(data1, data2) \ No newline at end of file + return _make.add2(data1, data2) + + +def sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value): + + return _make.sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value) + + +def sparsereshape(sparse_indices, sparse_values, prev_shape, new_shape): + + return _make.sparsereshape(sparse_indices, sparse_values, prev_shape, new_shape) \ No newline at end of file diff --git a/python/tvm/topi/__init__.py b/python/tvm/topi/__init__.py index f5f3f5bf187e..fb962c28de28 100644 --- a/python/tvm/topi/__init__.py +++ b/python/tvm/topi/__init__.py @@ -40,7 +40,6 @@ from .scatter import * from .scatter_add import * -# from .add2 import * from .argwhere import * from . import generic from . import nn diff --git a/python/tvm/topi/add2.py b/python/tvm/topi/add2.py deleted file mode 100644 index 8c5685ea6874..000000000000 --- a/python/tvm/topi/add2.py +++ /dev/null @@ -1,32 +0,0 @@ -# from tvm.te import hybrid - -# @hybrid.script -# def add_2_helper(data1, data2): -# out = output_tensor(data1.shape, data1.dtype) -# out = data1 + data2 -# return - - -# def add2(data1, data2): -# """Update data by adding values in updates at positions defined by indices - -# Parameters -# ---------- -# data : relay.Expr -# The input data to the operator. - -# indices : relay.Expr -# The index locations to update. - -# updates : relay.Expr -# The values to update. - -# axis : int -# The axis to scatter_add on - -# Returns -# ------- -# ret : relay.Expr -# The computed result. -# """ -# return data1 + data2 diff --git a/python/tvm/topi/generic/search.py b/python/tvm/topi/generic/search.py index c848dabb209a..b3c8772046fd 100644 --- a/python/tvm/topi/generic/search.py +++ b/python/tvm/topi/generic/search.py @@ -66,17 +66,3 @@ def schedule_scatter_add(outs): The computation schedule for the op. """ return _default_schedule(outs, False) - - -# def schedule_add2(outs): -# """Schedule for scatter_add operator. -# Parameters -# ---------- -# outs: Array of Tensor -# The computation graph description of scatter_add. -# Returns -# ------- -# s: Schedule -# The computation schedule for the op. -# """ -# return _default_schedule(outs, False) \ No newline at end of file diff --git a/python/tvm/topi/transform.py b/python/tvm/topi/transform.py index 62ab91a829e3..1ff5a8096ba4 100644 --- a/python/tvm/topi/transform.py +++ b/python/tvm/topi/transform.py @@ -933,7 +933,7 @@ def adv_index(data, indices): return cpp.adv_index(data, indices) -def add2(data1, data2): +def sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_shape): """Numpy style indexing with tensors. Parameters @@ -949,4 +949,23 @@ def add2(data1, data2): result : tvm.te.Tensor Output tensor """ - return cpp.add2(data1, data2) + return cpp.sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_shape) + + +def sparsereshape(sparse_indices, sparse_values, prev_shape, new_shape): + """Numpy style indexing with tensors. + + Parameters + ---------- + data : tvm.te.Tensor + Input data. + + indices : A list of tvm.te.Tensor + Tensor index. + + Returns + ------- + result : tvm.te.Tensor + Output tensor + """ + return cpp.sparsereshape(sparse_indices, sparse_values, prev_shape, new_shape) \ No newline at end of file diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index e19e629c200c..8d21b9262cd6 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1552,38 +1552,105 @@ RELAY_REGISTER_OP("meshgrid") .set_attr("FTVMCompute", MeshgridCompute) .set_attr("TOpPattern", kInjective); -bool Add2Rel(const Array& types, int num_inputs, const Attrs& raw_attrs, - const TypeReporter& reporter) { - // types: [data1, data2, result] - ICHECK_EQ(types.size(), 3); - reporter->Assign(types[1], types[0]); +bool SparseReshapeRel(const Array& types, int num_inputs, const Attrs& raw_attrs, + const TypeReporter& reporter) { + // types: [sparse_indices, sparse_values, prev_shape, new_shape, result] + ICHECK_EQ(types.size(), 5); + + std::vector fields; + auto sparse_indices = types[0].as(); + auto sparse_values = types[1].as(); + auto new_shape = types[3].as(); + + Array new_sparse_indices_shape{sparse_indices->shape[0], new_shape->shape[0]}; + fields.push_back(TensorType(new_sparse_indices_shape, sparse_indices->dtype)); + fields.push_back(TensorType(sparse_values->shape, sparse_values->dtype)); + + reporter->Assign(types[4], TupleType(Array(fields))); return true; } -Array Add2Compute(const Attrs& attrs, const Array& inputs, - const Type& out_type) { - return {topi::add2(inputs[0], inputs[1])}; +Array SparseReshapeCompute(const Attrs& attrs, const Array& inputs, + const Type& out_type) { + return {topi::SparseReshape(inputs[0], inputs[1], inputs[2], inputs[3])}; } -Expr MakeAdd2(Expr data1, Expr data2) { - static const Op& op = Op::Get("add2"); - return Call(op, {data1, data2}, Attrs(), {}); +Expr MakeSparseReshape(Expr sparse_indices, Expr sparse_values, Expr prev_shape, Expr new_shape) { + static const Op& op = Op::Get("sparsereshape"); + return Call(op, {sparse_indices, sparse_values, prev_shape, new_shape}, Attrs(), {}); } -TVM_REGISTER_GLOBAL("relay.op._make.add2").set_body_typed(MakeAdd2); +TVM_REGISTER_GLOBAL("relay.op._make.sparsereshape").set_body_typed(MakeSparseReshape); -RELAY_REGISTER_OP("add2") +RELAY_REGISTER_OP("sparsereshape") .describe(R"code(Return twice of normal addition of two tensors. )code" TVM_ADD_FILELINE) - .set_num_inputs(2) - .add_argument("data1", "Tensor", "The first tensor") - .add_argument("data2", "Tensor", "The second tensor") + .set_num_inputs(4) + .add_argument("sparse_indices", "Tensor", "The first tensor") + .add_argument("sparse_values", "Tensor", "The second tensor") + .add_argument("prev_shape", "Tensor", "The third tensor") + .add_argument("new_shape", "Tensor", "The fourth tensor") + .add_type_rel("sparsereshape", SparseReshapeRel) + .set_attr("TOpPattern", kInjective) + .set_support_level(3) + .set_attr("FTVMCompute", SparseReshapeCompute); + +TVM_REGISTER_NODE_TYPE(SparseFillEmptyRowsAttrs); + +bool SparseFillEmptyRowsRel(const Array& types, int num_inputs, const Attrs& attrs, + const TypeReporter& reporter) { + // types: [ sparse_indices, sparse_values, default_value, result] + ICHECK_EQ(types.size(), 4); + ICHECK_EQ(num_inputs, 3); + std::vector fields; + auto sparse_indices = types[0].as(); + + const auto* param = attrs.as(); + CHECK(param != nullptr); + + Array sp_ordered_output_shape; + sp_ordered_output_shape.push_back(param->dense_shape[0] + sparse_indices->shape[0]); + if (sparse_indices->shape.size() > 1) { + sp_ordered_output_shape.push_back(sparse_indices->shape[1]); + } + fields.push_back(TensorType(sp_ordered_output_shape, sparse_indices->dtype)); + fields.push_back(TensorType(Array{param->dense_shape[0]}, tvm::DataType::Bool())); + reporter->Assign(types[3], TupleType(Array(fields))); + return true; +} + +Array SparseFillEmptyRowsCompute(const Attrs& attrs, const Array& inputs, + const Type& out_type) { + CHECK_EQ(inputs.size(), 3); + const auto* param = attrs.as(); + CHECK(param != nullptr); + return {topi::SparseFillEmptyRows(inputs[0], inputs[1], inputs[2], param->dense_shape)}; +} + +Expr MakeSparseFillEmptyRows(Expr sparse_indices, Expr sparse_values, Expr default_value, + Array dense_shape) { + auto attrs = make_object(); + attrs->dense_shape = std::move(dense_shape); + static const Op& op = Op::Get("sparsefillemptyrows"); + return Call(op, {sparse_indices, sparse_values, default_value}, Attrs(attrs), {}); +} + +TVM_REGISTER_GLOBAL("relay.op._make.sparsefillemptyrows").set_body_typed(MakeSparseFillEmptyRows); + +RELAY_REGISTER_OP("sparsefillemptyrows") + .describe(R"code(Return twice of normal addition of two tensors. + +)code" TVM_ADD_FILELINE) + .set_num_inputs(3) + .set_attrs_type() + .add_argument("sparse_indices", "Tensor", "The first tensor") + .add_argument("sparse_values", "Tensor", "The second tensor") + .add_argument("default_value", "Tensor", "The third tensor") + .add_type_rel("sparsefillemptyrows", SparseFillEmptyRowsRel) .set_support_level(3) - .add_type_rel("Add2", Add2Rel) .set_attr("TOpPattern", kInjective) - .set_attr("FTVMCompute", Add2Compute); -// .set_attr("FTVMCompute", Add2Compute) + .set_attr("FTVMCompute", SparseFillEmptyRowsCompute); // tile operator TVM_REGISTER_NODE_TYPE(TileAttrs); diff --git a/tests/python/relay/test_op_level3.py b/tests/python/relay/test_op_level3.py index 15202c59cdef..e04f18129808 100644 --- a/tests/python/relay/test_op_level3.py +++ b/tests/python/relay/test_op_level3.py @@ -1016,58 +1016,191 @@ def verify_scatter_add(dshape, ishape, axis=0): ref_res = ref_scatter_add(data_np, indices_np, updates_np, axis) for target, ctx in tvm.testing.enabled_targets(): + if target == "nvptx": + continue + print(target) for kind in ["graph", "debug"]: intrp = relay.create_executor(kind, ctx=ctx, target=target) op_res = intrp.evaluate(func)(data_np, indices_np, updates_np) tvm.testing.assert_allclose(op_res.asnumpy(), ref_res, rtol=1e-5) - verify_scatter_add((10,), (10,), 0) + # verify_scatter_add((10,), (10,), 0) verify_scatter_add((10, 5), (10, 5), -2) - verify_scatter_add((10, 5), (10, 5), -1) - verify_scatter_add((10, 5), (3, 5), 0) - verify_scatter_add((12, 4), (7, 2), 1) - verify_scatter_add((2, 3, 4), (1, 3, 4), 0) - verify_scatter_add((2, 3, 4), (2, 1, 4), 1) - verify_scatter_add((2, 3, 4), (2, 3, 1), 2) - verify_scatter_add((2, 3, 4, 5), (1, 3, 4, 5), 0) - verify_scatter_add((6, 3, 4, 5), (2, 3, 4, 5), 1) - verify_scatter_add((2, 3, 8, 5), (2, 3, 1, 1), 2) - verify_scatter_add((16, 16, 4, 5), (16, 16, 4, 5), 3) + # verify_scatter_add((10, 5), (10, 5), -1) + # verify_scatter_add((10, 5), (3, 5), 0) + # verify_scatter_add((12, 4), (7, 2), 1) + # verify_scatter_add((2, 3, 4), (1, 3, 4), 0) + # verify_scatter_add((2, 3, 4), (2, 1, 4), 1) + # verify_scatter_add((2, 3, 4), (2, 3, 1), 2) + # verify_scatter_add((2, 3, 4, 5), (1, 3, 4, 5), 0) + # verify_scatter_add((6, 3, 4, 5), (2, 3, 4, 5), 1) + # verify_scatter_add((2, 3, 8, 5), (2, 3, 1, 1), 2) + # verify_scatter_add((16, 16, 4, 5), (16, 16, 4, 5), 3) @tvm.testing.uses_gpu -def test_add2(): - def ref_scatter_add(data1, data2): - return 2 * (data1 + data2) +def test_sparsefillemptyrows(): + def ref_sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_shape): + new_sparse_indices = -1 * np.ones( + (sparse_indices.shape[0] + dense_shape[0], sparse_indices.shape[1]) + ) + empty_row_indicator = np.ones(dense_shape[0], dtype=bool) + + return new_sparse_indices, empty_row_indicator - def verify_add2(dshape): - d1 = relay.var("d1", relay.TensorType(dshape, "float32")) - d2 = relay.var("d2", relay.TensorType(dshape, "float32")) - out = relay.var("out", relay.TensorType(dshape, "float32")) - z = relay.op.add2(d1, d2) + def verify_sparsefillemptyrows( + sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np + ): + sparse_indices = relay.var( + "sparse_indices", + relay.TensorType(sparse_indices_np.shape, str(sparse_indices_np.dtype)), + ) + sparse_values = relay.var( + "sparse_values", relay.TensorType(sparse_values_np.shape, str(sparse_values_np.dtype)) + ) + default_value = relay.var( + "default_value", relay.TensorType(default_value_np.shape, str(default_value_np.dtype)) + ) + z = relay.op.sparsefillemptyrows( + sparse_indices, sparse_values, default_value, list(dense_shape_np) + ) - func = relay.Function([d1, d2], z) + func = relay.Function([sparse_indices, sparse_values, default_value], z) - data1_np = np.random.uniform(size=dshape).astype("float32") - data2_np = np.random.uniform(size=dshape).astype("float32") - ref_res = ref_scatter_add(data1_np, data2_np) + ref_res = ref_sparsefillemptyrows( + sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np + ) for target, ctx in tvm.testing.enabled_targets(): + if target == "nvptx": + continue for kind in ["graph", "debug"]: - print("HERE") intrp = relay.create_executor(kind, ctx=ctx, target=target) - print("HERE2") - op_res = intrp.evaluate(func)(data1_np, data2_np) - print("HERE3") - tvm.testing.assert_allclose(op_res.asnumpy(), ref_res, rtol=1e-5) + op_res = intrp.evaluate(func)(sparse_indices_np, sparse_values_np, default_value_np) + for op_res_item, ref_res_item in zip(op_res, ref_res): + print(op_res_item, ref_res_item) + tvm.testing.assert_allclose(op_res_item.asnumpy(), ref_res_item, rtol=1e-5) + + sparse_indices_np = np.array([[0, 1], [0, 3], [2, 0], [3, 1]], dtype=np.int32) + sparse_values_np = np.array([1, 2, 3, 4], dtype=np.int32) + dense_shape_np = np.array([5, 6], dtype=np.int32) + default_value_np = np.array([10], dtype=np.int32) + verify_sparsefillemptyrows( + sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np + ) + print("Sparse Fill Empty Rows Verified !!") + + +@tvm.testing.uses_gpu +def test_sparsereshape(): + def ref_sparsereshape(sparse_indices, sparse_values, prev_shape, new_shape): + # sparse_indices[0][0] = 1 + new_sparse_indices = np.ones( + (sparse_values.shape[0], new_shape.shape[0]), dtype=sparse_indices.dtype + ) + multipliers = np.ones(prev_shape.shape[0]) + dividers = np.ones(new_shape.shape[0]) + total_ele = np.prod(prev_shape) + division_total_ele = 1 + for i in range(new_shape.shape[0]): + if new_shape[i] == -1: + continue + division_total_ele *= new_shape[i] + for i in range(prev_shape.shape[0] - 2, -1, -1): + multipliers[i] = prev_shape[i + 1] * multipliers[i + 1] + for i in range(new_shape.shape[0] - 2, -1, -1): + if new_shape[i + 1] == -1: + dividers[i] = (total_ele // division_total_ele) * dividers[i + 1] + else: + dividers[i] = new_shape[i + 1] * dividers[i + 1] + for row_num, sparse_row in enumerate(sparse_indices): + flat_idx = 0 + if len(sparse_indices.shape) != 1: + for i, ele in enumerate(sparse_row): + flat_idx += sparse_row[i] * multipliers[i] + else: + flat_idx += sparse_row + if len(new_sparse_indices.shape) != 1: + for i in range(new_sparse_indices.shape[1]): + new_sparse_indices[row_num][i] = flat_idx // dividers[i] + flat_idx = flat_idx % dividers[i] + else: + new_sparse_indices[row_num] = flat_idx - verify_add2((10,)) - verify_add2((10, 5)) - verify_add2((12, 4)) - verify_add2((2, 3, 4)) - verify_add2((2, 3, 4, 5)) - verify_add2((6, 3, 4, 5)) - verify_add2((2, 3, 8, 5)) - verify_add2((16, 16, 4, 5)) + return ( + new_sparse_indices, + sparse_values, + ) + + def verify_sparsereshape(sparse_indices_np, sparse_values_np, dense_shape_np, default_value_np): + sparse_indices = relay.var( + "sparse_indices", + relay.TensorType(sparse_indices_np.shape, str(sparse_indices_np.dtype)), + ) + sparse_values = relay.var( + "sparse_values", relay.TensorType(sparse_values_np.shape, str(sparse_values_np.dtype)) + ) + dense_shape = relay.var( + "dense_shape", relay.TensorType(dense_shape_np.shape, str(dense_shape_np.dtype)) + ) + default_value = relay.var( + "default_value", relay.TensorType(default_value_np.shape, str(default_value_np.dtype)) + ) + z = relay.op.sparsereshape(sparse_indices, sparse_values, dense_shape, default_value) + + func = relay.Function([sparse_indices, sparse_values, dense_shape, default_value], z) + + ref_res = ref_sparsereshape( + sparse_indices_np, sparse_values_np, dense_shape_np, default_value_np + ) + for target, ctx in tvm.testing.enabled_targets(): + if target == "nvptx": + continue + for kind in ["graph", "debug"]: + intrp = relay.create_executor(kind, ctx=ctx, target=target) + op_res = intrp.evaluate(func)( + sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np + ) + for op_res_item, ref_res_item in zip(op_res, ref_res): + print(f"Op Res: {op_res_item}, Ref Res: {ref_res_item}") + tvm.testing.assert_allclose( + op_res_item.asnumpy(), ref_res_item, rtol=1e-5, atol=1e-5 + ) + + sparse_indices_np = np.array( + [[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 2, 3]], dtype=np.int32 + ) + sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) + prev_shape_np = np.array([2, 3, 6], dtype=np.int32) + new_shape_np = np.array([9, 4], dtype=np.int32) + verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) + + # sparse_indices_np = np.array( + # [[0, 0, 0, 0], [0, 0, 1, 2], [0, 1, 0, 3], [1, 0, 0, 4], [1, 2, 3, 6]], dtype=np.int32 + # ) + # sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) + # prev_shape_np = np.array([2, 3, 6, 7], dtype=np.int32) + # new_shape_np = np.array([9, -1, 7], dtype=np.int32) + # verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) + + # sparse_indices_np = np.array([[0, 0], [0, 1], [3, 4], [4, 3], [7, 3]], dtype=np.int32) + # sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) + # prev_shape_np = np.array([9, 4], dtype=np.int32) + # new_shape_np = np.array([2, -1, 6], dtype=np.int32) + # verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) + + # sparse_indices_np = np.array([[0, 0], [0, 1], [3, 4], [4, 3], [7, 3]], dtype=np.int32) + # sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) + # prev_shape_np = np.array([9, 4], dtype=np.int32) + # new_shape_np = np.array([-1], dtype=np.int32) + # verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) + + # sparse_indices_np = np.array([0, 5, 10, 20, 24], dtype=np.int32) + # sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) + # prev_shape_np = np.array([25], dtype=np.int32) + # new_shape_np = np.array([5, 5], dtype=np.int32) + # verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) + + # print("Sparse Reshape Verified !!") @tvm.testing.uses_gpu @@ -1341,7 +1474,10 @@ def verify_adv_index(data_shape, index_shapes): if __name__ == "__main__": - test_add2() + # test_add2() + # test_scatter_add() + test_sparsefillemptyrows() + # test_sparsereshape() # test_cast() # test_zeros_ones() # test_unary_identity() From fe3f7ded49d1e10c9ef1ec26e1870061a94adaa6 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 17 Dec 2020 19:27:56 +0000 Subject: [PATCH 08/44] Remove Build Module changes --- python/tvm/relay/build_module.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/python/tvm/relay/build_module.py b/python/tvm/relay/build_module.py index 6a059c96b0c1..5dc6f81b97a2 100644 --- a/python/tvm/relay/build_module.py +++ b/python/tvm/relay/build_module.py @@ -244,9 +244,6 @@ def build(mod, target=None, target_host=None, params=None, mod_name="default"): # pylint: enable=line-too-long # fmt: on if not isinstance(mod, (IRModule, _function.Function)): - - # print(f"Mod : { mod.astext(show_meta_data=False)}") - # print(f"Type Mod: {type(mod)}") raise ValueError("Type of input parameter mod must be tvm.IRModule") if isinstance(mod, _function.Function): From 3f5de527faf882cb34537593ad7c3ddc0e53925b Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 17 Dec 2020 21:34:31 +0000 Subject: [PATCH 09/44] Reset non-op changes --- tests/python/contrib/test_tensorrt.py | 35 +-------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/tests/python/contrib/test_tensorrt.py b/tests/python/contrib/test_tensorrt.py index 1c9b423b4987..9b62ee2c4087 100644 --- a/tests/python/contrib/test_tensorrt.py +++ b/tests/python/contrib/test_tensorrt.py @@ -1228,38 +1228,5 @@ def get_maskrcnn_input(in_size: int) -> np.ndarray: ) -def test_tensorrt_conv_bn_3d(): - if skip_codegen_test(): - return - batches_to_test = [1, 1, 0, 2, 3, 0, 1, 3, 2] - x_shape = (relay.Any(), 3, 10, 224, 224) - x_data = np.ones([max(batches_to_test)] + list(x_shape)[1:]).astype("float32") - k_shape = (64, 3, 3, 3, 3) - params = {"kernel": np.random.uniform(-1, 1, k_shape).astype("float32")} - result_arr = [{} for _ in range(len(batches_to_test))] - for use_trt in [True, False]: - x = relay.var("x", shape=x_shape, dtype="float32") - kernel = relay.var("kernel", shape=k_shape, dtype="float32") - out = relay.nn.conv3d(x, kernel, channels=16, kernel_size=(3, 3), groups=1) - out = relay.nn.batch_norm(out, 0.1, 0.1, 0.1, 0.1) - f = relay.Function([x, kernel], out) - mod = tvm.IRModule() - mod["main"] = f - if use_trt: - mod, _ = tensorrt.partition_for_tensorrt(mod, params) - - if not skip_runtime_test(): - with relay.build_config(opt_level=3): - relay_exec = relay.create_executor("vm", mod=mod, ctx=tvm.cpu(0), target="llvm") - - for i, batch_size in enumerate(batches_to_test): - result_arr[i][use_trt] = relay_exec.evaluate()(x_data[:batch_size, ...], **params) - - if not skip_runtime_test(): - for i in range(len(batches_to_test)): - assert_result_dict_holds(result_arr[i]) - - if __name__ == "__main__": - test_tensorrt_conv_bn_3d() - # pytest.main([__file__]) + pytest.main([__file__]) From a521c1b3173fc50e32cc508e20bac44e47963213 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 17 Dec 2020 21:36:05 +0000 Subject: [PATCH 10/44] Remove stuff --- tests/python/contrib/test_street_small.jpg | Bin 119244 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/python/contrib/test_street_small.jpg diff --git a/tests/python/contrib/test_street_small.jpg b/tests/python/contrib/test_street_small.jpg deleted file mode 100644 index 8a9f15ee972790f8fa7c99293f2ffb872a2d72ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 119244 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 Date: Thu, 17 Dec 2020 21:38:43 +0000 Subject: [PATCH 11/44] More changes --- python/tvm/relay/op/_transform.py | 21 --------------- python/tvm/relay/op/contrib/tensorrt.py | 2 +- python/tvm/topi/__init__.py | 1 - python/tvm/topi/image/resize.py | 34 ++++++++++++------------- 4 files changed, 18 insertions(+), 40 deletions(-) diff --git a/python/tvm/relay/op/_transform.py b/python/tvm/relay/op/_transform.py index a3c4f968099f..20e73a40e4a9 100644 --- a/python/tvm/relay/op/_transform.py +++ b/python/tvm/relay/op/_transform.py @@ -106,27 +106,6 @@ def compute_scatter_add(attrs, inputs, output_type): _reg.register_strategy("scatter_add", strategy.scatter_add_strategy) - -# @_reg.register_compute("add2") -# def compute_scatter_add(attrs, inputs, output_type): -# """Compute definition of scatter_add""" -# return [topi.add2(inputs[0], inputs[1])] - - -# _reg.register_schedule("add2", strategy.add2_strategy) - - -# sparsefillemptyrows -# @_reg.register_compute("sparsefillemptyrows") -# def compute_sparsefillemptyrows(attrs, inputs, output_type): -# """Compute definition of sparsefillemptyrows""" -# return [topi.sparsefillemptyrows(inputs[0], inputs[1], inputs[2], inputs[3])] - - -# _reg.register_schedule("sparsefillemptyrows", strategy.schedule_sparsefillemptyrows) - -# _reg.register_strategy("sparsefillemptyrows", strategy.sparsefillemptyrows_strategy) - # scatter @_reg.register_compute("scatter_nd") def compute_scatter_nd(attrs, inputs, output_type): diff --git a/python/tvm/relay/op/contrib/tensorrt.py b/python/tvm/relay/op/contrib/tensorrt.py index 1efce753cf18..bda71468d9e2 100644 --- a/python/tvm/relay/op/contrib/tensorrt.py +++ b/python/tvm/relay/op/contrib/tensorrt.py @@ -631,7 +631,7 @@ def reshape_annotate_fn(expr): # pylint: disable=unused-variable if dynamic_reshape: # Make sure that the batch dim is unmodified. if int(new_shape[0]) < 0: - for shape_val, new_shape_val in zip(shape[1:], new_shape[1:]): + for shape_val, new_shape_val in enumerate(shape[1:], new_shape[1:]): if not ( isinstance(shape_val, int) and isinstance(new_shape_val, int) diff --git a/python/tvm/topi/__init__.py b/python/tvm/topi/__init__.py index fb962c28de28..97951d941f64 100644 --- a/python/tvm/topi/__init__.py +++ b/python/tvm/topi/__init__.py @@ -39,7 +39,6 @@ from .sort import * from .scatter import * from .scatter_add import * - from .argwhere import * from . import generic from . import nn diff --git a/python/tvm/topi/image/resize.py b/python/tvm/topi/image/resize.py index ef6706ab094c..103850de4923 100644 --- a/python/tvm/topi/image/resize.py +++ b/python/tvm/topi/image/resize.py @@ -528,58 +528,58 @@ def _cast_output(value, data_dtype="float32", out_dtype=None): yfract = in_y - te.floor(in_y) # 1st row - p00 = get_2d_pixel( + p00 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint - 1, xint - 1, cc, inum, ic ) - p10 = get_2d_pixel( + p10 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint - 1, xint + 0, cc, inum, ic ) - p20 = get_2d_pixel( + p20 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint - 1, xint + 1, cc, inum, ic ) - p30 = get_2d_pixel( + p30 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint - 1, xint + 2, cc, inum, ic ) # 2nd row - p01 = get_2d_pixel( + p01 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 0, xint - 1, cc, inum, ic ) - p11 = get_2d_pixel( + p11 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 0, xint + 0, cc, inum, ic ) - p21 = get_2d_pixel( + p21 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 0, xint + 1, cc, inum, ic ) - p31 = get_2d_pixel( + p31 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 0, xint + 2, cc, inum, ic ) # 3rd row - p02 = get_2d_pixel( + p02 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 1, xint - 1, cc, inum, ic ) - p12 = get_2d_pixel( + p12 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 1, xint + 0, cc, inum, ic ) - p22 = get_2d_pixel( + p22 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 1, xint + 1, cc, inum, ic ) - p32 = get_2d_pixel( + p32 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 1, xint + 2, cc, inum, ic ) # 4th row - p03 = get_2d_pixel( + p03 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 2, xint - 1, cc, inum, ic ) - p13 = get_2d_pixel( + p13 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 2, xint + 0, cc, inum, ic ) - p23 = get_2d_pixel( + p23 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 2, xint + 1, cc, inum, ic ) - p33 = get_2d_pixel( + p33 = _get_pixel( data, layout, boxes, image_height, image_width, box_idx, c, yint + 2, xint + 2, cc, inum, ic ) @@ -711,7 +711,7 @@ def _bicubic(*indices): in_w, size[0], size[1], - layout=layout, + layout, coordinate_transformation_mode=coordinate_transformation_mode, out_dtype=out_dtype, ) From c32b2dd1ba19ab82dc72378eda6de59cf8cc0f4d Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 17 Dec 2020 21:40:38 +0000 Subject: [PATCH 12/44] Op Level 3 --- tests/python/relay/test_op_level3.py | 77 ++++++++++++++-------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/tests/python/relay/test_op_level3.py b/tests/python/relay/test_op_level3.py index 142e93a23466..51c62bf4f264 100644 --- a/tests/python/relay/test_op_level3.py +++ b/tests/python/relay/test_op_level3.py @@ -1481,43 +1481,42 @@ def verify_adv_index(data_shape, index_shapes): if __name__ == "__main__": - # test_add2() - # test_scatter_add() + test_scatter_add() test_sparsefillemptyrows() - # test_sparsereshape() - # test_cast() - # test_zeros_ones() - # test_unary_identity() - # test_clip() - # test_transpose_infer_type() - # test_transpose() - # test_reshape_infer_type() - # test_reshape() - # test_reshape_fail() - # test_reshape_like_infer_type() - # test_reshape_like() - # test_take_infer_type() - # test_take() - # test_full_infer_type() - # test_full() - # test_full_like_infer_type() - # test_full_like() - # test_infer_type_leaky_relu() - # test_infer_type_prelu() - # test_squeeze() - # test_squeeze_infer_type() - # test_squeeze_bad_axes_infer_type() - # test_split_infer_type() - # test_arange() - # test_meshgrid() - # test_reverse() - # test_stack() - # test_tile() - # test_repeat() - # test_gather_nd() - # test_isfinite() - # test_isinf() - # test_unravel_index() - # test_sparse_to_dense() - # test_fixed_point_multiply() - # test_adv_index() + test_sparsereshape() + test_cast() + test_zeros_ones() + test_unary_identity() + test_clip() + test_transpose_infer_type() + test_transpose() + test_reshape_infer_type() + test_reshape() + test_reshape_fail() + test_reshape_like_infer_type() + test_reshape_like() + test_take_infer_type() + test_take() + test_full_infer_type() + test_full() + test_full_like_infer_type() + test_full_like() + test_infer_type_leaky_relu() + test_infer_type_prelu() + test_squeeze() + test_squeeze_infer_type() + test_squeeze_bad_axes_infer_type() + test_split_infer_type() + test_arange() + test_meshgrid() + test_reverse() + test_stack() + test_tile() + test_repeat() + test_gather_nd() + test_isfinite() + test_isinf() + test_unravel_index() + test_sparse_to_dense() + test_fixed_point_multiply() + test_adv_index() From fa5def3a923c61f7318677a1c052b3c87a9189f2 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 17 Dec 2020 22:31:28 +0000 Subject: [PATCH 13/44] Make Op changes only --- include/tvm/topi/transform.h | 55 ++++++++++++++-------------- python/tvm/relay/expr.py | 7 ---- python/tvm/relay/frontend/common.py | 5 --- python/tvm/relay/loops.py | 6 --- python/tvm/relay/op/_tensor.py | 5 +-- python/tvm/relay/op/_transform.py | 1 - python/tvm/relay/op/transform.py | 5 --- src/relay/op/tensor/transform.cc | 2 - src/relay/transforms/type_infer.cc | 4 +- tests/python/relay/test_op_level3.py | 55 +++++++++++----------------- 10 files changed, 54 insertions(+), 91 deletions(-) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index c32e7ff35ad0..826df0010cf8 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -608,16 +608,15 @@ inline Tensor strided_slice(const Tensor& x, const Array& begin, for (size_t i = 0; i < src_tensor_dim; ++i) { out_shape.push_back(indexdiv(end[i] - begin[i], strides[i])); } - return te::compute( - out_shape, - [&](const Array& indices) { - Array real_indices; - for (size_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides[i] + begin[i]); - } - return x(real_indices); - }, - name, tag); + return te::compute(out_shape, + [&](const Array& indices) { + Array real_indices; + for (size_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides[i] + begin[i]); + } + return x(real_indices); + }, + name, tag); } // Setup the ranges. @@ -1536,6 +1535,7 @@ inline Array SparseReshape(const Tensor& sparse_indices, const Tensor& s [&](const Array& i) { return (sparse_values(i)); }, name, tag)); return result; } // namespace topi + /*! * \brief Transform the layout according to \p src_layout and \p dst_layout * \param src the source input. @@ -1622,24 +1622,23 @@ inline Tensor auto_scheduler_layout_transform(const Tensor& src, const String& s parse_auto_scheduler_layout(src_layout, &src_shape, &src_axes); parse_auto_scheduler_layout(dst_layout, &dst_shape, &dst_axes); - return compute( - dst_shape, - [&](const Array& dst_indices) { - Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); - Array src_indices; - for (const std::string& src_axis : src_axes) { - PrimExpr src_index = 0; - CHECK_EQ(dst_indices_expr.size(), dst_axes.size()); - for (size_t i = 0; i < dst_axes.size(); ++i) { - if (dst_axes[i] == src_axis) { - src_index = src_index * dst_shape[i] + dst_indices_expr[i]; - } - } - src_indices.push_back(src_index); - } - return src(src_indices); - }, - name, tag); + return compute(dst_shape, + [&](const Array& dst_indices) { + Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); + Array src_indices; + for (const std::string& src_axis : src_axes) { + PrimExpr src_index = 0; + CHECK_EQ(dst_indices_expr.size(), dst_axes.size()); + for (size_t i = 0; i < dst_axes.size(); ++i) { + if (dst_axes[i] == src_axis) { + src_index = src_index * dst_shape[i] + dst_indices_expr[i]; + } + } + src_indices.push_back(src_index); + } + return src(src_indices); + }, + name, tag); } /*! diff --git a/python/tvm/relay/expr.py b/python/tvm/relay/expr.py index 200079beb653..7b6e4b4ccf80 100644 --- a/python/tvm/relay/expr.py +++ b/python/tvm/relay/expr.py @@ -499,9 +499,6 @@ def const(value, dtype=None): - bool maps to "bool" - other using the same default rule as numpy. """ - if isinstance(value, tuple) and len(value) == 1: - value = value[0] - if isinstance(value, (_base.numeric_types, (bool, list))): value = _np.array(value, dtype=dtype) @@ -517,10 +514,6 @@ def const(value, dtype=None): value = _nd.array(value) if not isinstance(value, _nd.NDArray): - # import pdb - - # pdb.set_trace() - print(f"Value : {value}, Type: {type(value)}") raise ValueError("value has to be scalar or NDArray") return Constant(value) diff --git a/python/tvm/relay/frontend/common.py b/python/tvm/relay/frontend/common.py index 0353dfe57b3b..8c74f3a54138 100644 --- a/python/tvm/relay/frontend/common.py +++ b/python/tvm/relay/frontend/common.py @@ -269,11 +269,6 @@ def get_relay_op(op_name): # try search op in various modules for candidate in (_op, _op.nn, _op.image, _op.vision, _op.contrib): op = getattr(candidate, op_name, None) - # if op_name == "floor_mod" and op is not None: - # import pdb - - # pdb.set_trace() - # print(op) if op is not None: break if not op: diff --git a/python/tvm/relay/loops.py b/python/tvm/relay/loops.py index ac7271e8093e..6c2ab2e23d72 100644 --- a/python/tvm/relay/loops.py +++ b/python/tvm/relay/loops.py @@ -48,7 +48,6 @@ def while_loop(cond, loop_vars, loop_bodies): loop: relay.Expr The loop expression. """ - sb = ScopeBuilder() loop = _expr.Var("while_loop") fresh_vars = [] @@ -58,12 +57,7 @@ def while_loop(cond, loop_vars, loop_bodies): new_var = _expr.var(name, type_annotation=sb.type_of(loop_var)) fresh_vars.append(new_var) - # import pdb - - # pdb.set_trace() - print(f"Condition Fresh Vars = {cond(*fresh_vars)}") with sb.if_scope(cond(*fresh_vars)): - sb.ret(loop(*loop_bodies(*fresh_vars))) with sb.else_scope(): sb.ret(_expr.Tuple(fresh_vars)) diff --git a/python/tvm/relay/op/_tensor.py b/python/tvm/relay/op/_tensor.py index dbe79a1209ad..6fc423371325 100644 --- a/python/tvm/relay/op/_tensor.py +++ b/python/tvm/relay/op/_tensor.py @@ -21,10 +21,10 @@ from tvm import topi from tvm.runtime import convert -from .op import register_compute, register_shape_func, register_strategy +from .op import register_compute, register_shape_func from .op import register_broadcast_schedule, register_injective_schedule from .op import register_pattern, OpPattern -from . import strategy + register_broadcast_schedule("log") register_broadcast_schedule("log2") @@ -274,7 +274,6 @@ def elemwise_shape_func(attrs, inputs, _): register_shape_func("fast_exp", False, elemwise_shape_func) register_shape_func("fast_tanh", False, elemwise_shape_func) register_shape_func("fast_erf", False, elemwise_shape_func) -register_shape_func("ceil", False, elemwise_shape_func) register_shape_func("floor", False, elemwise_shape_func) register_shape_func("log", False, elemwise_shape_func) register_shape_func("device_copy", False, elemwise_shape_func) diff --git a/python/tvm/relay/op/_transform.py b/python/tvm/relay/op/_transform.py index 20e73a40e4a9..4f31dbde152a 100644 --- a/python/tvm/relay/op/_transform.py +++ b/python/tvm/relay/op/_transform.py @@ -436,7 +436,6 @@ def argwhere_shape_func(attrs, inputs, out_ndims): _reg.register_shape_func("scatter", False, elemwise_shape_func) _reg.register_shape_func("scatter_add", False, elemwise_shape_func) -# _reg.register_shape_func("sparsefillemptyrows", False, elemwise_shape_func) @script diff --git a/python/tvm/relay/op/transform.py b/python/tvm/relay/op/transform.py index ba11488763a5..5b4573e1d826 100644 --- a/python/tvm/relay/op/transform.py +++ b/python/tvm/relay/op/transform.py @@ -1322,11 +1322,6 @@ def adv_index(inputs): return _make.adv_index(Tuple(inputs)) -def add2(data1, data2): - - return _make.add2(data1, data2) - - def sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value): return _make.sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value) diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index 6bb3974cb32d..4348a60bb5c3 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1584,7 +1584,6 @@ TVM_REGISTER_GLOBAL("relay.op._make.sparsereshape").set_body_typed(MakeSparseRes RELAY_REGISTER_OP("sparsereshape") .describe(R"code(Return twice of normal addition of two tensors. - )code" TVM_ADD_FILELINE) .set_num_inputs(4) .add_argument("sparse_indices", "Tensor", "The first tensor") @@ -1640,7 +1639,6 @@ TVM_REGISTER_GLOBAL("relay.op._make.sparsefillemptyrows").set_body_typed(MakeSpa RELAY_REGISTER_OP("sparsefillemptyrows") .describe(R"code(Return twice of normal addition of two tensors. - )code" TVM_ADD_FILELINE) .set_num_inputs(3) .set_attrs_type() diff --git a/src/relay/transforms/type_infer.cc b/src/relay/transforms/type_infer.cc index 2567adb40490..327b5d1e260a 100644 --- a/src/relay/transforms/type_infer.cc +++ b/src/relay/transforms/type_infer.cc @@ -572,7 +572,9 @@ class TypeInferencer : private ExprFunctor, return FuncType(c->inputs, TypeCall(c->belong_to, types), td->type_vars, {}); } - void Solve() { solver_.Solve(); } + void Solve() { + solver_.Solve(); + } }; class TypeInferencer::Resolver : public MixedModeMutator, PatternMutator { diff --git a/tests/python/relay/test_op_level3.py b/tests/python/relay/test_op_level3.py index 51c62bf4f264..de4265a5a5b0 100644 --- a/tests/python/relay/test_op_level3.py +++ b/tests/python/relay/test_op_level3.py @@ -1016,9 +1016,6 @@ def verify_scatter_add(dshape, ishape, axis=0, dtype="float32"): ref_res = ref_scatter_add(data_np, indices_np, updates_np, axis) for target, ctx in tvm.testing.enabled_targets(): - if target == "nvptx": - continue - print(target) for kind in ["graph", "debug"]: if target == "nvptx" and dtype == "float32" and len(dshape) == 1: # scatter_add 1D on GPU is implemented via atomic. @@ -1078,8 +1075,6 @@ def verify_sparsefillemptyrows( sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np ) for target, ctx in tvm.testing.enabled_targets(): - if target == "nvptx": - continue for kind in ["graph", "debug"]: intrp = relay.create_executor(kind, ctx=ctx, target=target) op_res = intrp.evaluate(func)(sparse_indices_np, sparse_values_np, default_value_np) @@ -1094,7 +1089,6 @@ def verify_sparsefillemptyrows( verify_sparsefillemptyrows( sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np ) - print("Sparse Fill Empty Rows Verified !!") @tvm.testing.uses_gpu @@ -1160,8 +1154,6 @@ def verify_sparsereshape(sparse_indices_np, sparse_values_np, dense_shape_np, de sparse_indices_np, sparse_values_np, dense_shape_np, default_value_np ) for target, ctx in tvm.testing.enabled_targets(): - if target == "nvptx": - continue for kind in ["graph", "debug"]: intrp = relay.create_executor(kind, ctx=ctx, target=target) op_res = intrp.evaluate(func)( @@ -1181,33 +1173,31 @@ def verify_sparsereshape(sparse_indices_np, sparse_values_np, dense_shape_np, de new_shape_np = np.array([9, 4], dtype=np.int32) verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) - # sparse_indices_np = np.array( - # [[0, 0, 0, 0], [0, 0, 1, 2], [0, 1, 0, 3], [1, 0, 0, 4], [1, 2, 3, 6]], dtype=np.int32 - # ) - # sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) - # prev_shape_np = np.array([2, 3, 6, 7], dtype=np.int32) - # new_shape_np = np.array([9, -1, 7], dtype=np.int32) - # verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) - - # sparse_indices_np = np.array([[0, 0], [0, 1], [3, 4], [4, 3], [7, 3]], dtype=np.int32) - # sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) - # prev_shape_np = np.array([9, 4], dtype=np.int32) - # new_shape_np = np.array([2, -1, 6], dtype=np.int32) - # verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) + sparse_indices_np = np.array( + [[0, 0, 0, 0], [0, 0, 1, 2], [0, 1, 0, 3], [1, 0, 0, 4], [1, 2, 3, 6]], dtype=np.int32 + ) + sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) + prev_shape_np = np.array([2, 3, 6, 7], dtype=np.int32) + new_shape_np = np.array([9, -1, 7], dtype=np.int32) + verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) - # sparse_indices_np = np.array([[0, 0], [0, 1], [3, 4], [4, 3], [7, 3]], dtype=np.int32) - # sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) - # prev_shape_np = np.array([9, 4], dtype=np.int32) - # new_shape_np = np.array([-1], dtype=np.int32) - # verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) + sparse_indices_np = np.array([[0, 0], [0, 1], [3, 4], [4, 3], [7, 3]], dtype=np.int32) + sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) + prev_shape_np = np.array([9, 4], dtype=np.int32) + new_shape_np = np.array([2, -1, 6], dtype=np.int32) + verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) - # sparse_indices_np = np.array([0, 5, 10, 20, 24], dtype=np.int32) - # sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) - # prev_shape_np = np.array([25], dtype=np.int32) - # new_shape_np = np.array([5, 5], dtype=np.int32) - # verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) + sparse_indices_np = np.array([[0, 0], [0, 1], [3, 4], [4, 3], [7, 3]], dtype=np.int32) + sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) + prev_shape_np = np.array([9, 4], dtype=np.int32) + new_shape_np = np.array([-1], dtype=np.int32) + verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) - # print("Sparse Reshape Verified !!") + sparse_indices_np = np.array([0, 5, 10, 20, 24], dtype=np.int32) + sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) + prev_shape_np = np.array([25], dtype=np.int32) + new_shape_np = np.array([5, 5], dtype=np.int32) + verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) @tvm.testing.uses_gpu @@ -1481,7 +1471,6 @@ def verify_adv_index(data_shape, index_shapes): if __name__ == "__main__": - test_scatter_add() test_sparsefillemptyrows() test_sparsereshape() test_cast() From dc8d1cea766cc87c667749e9a3cb2e1112b8a25e Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 17 Dec 2020 22:33:07 +0000 Subject: [PATCH 14/44] Formatting Changes --- include/tvm/topi/transform.h | 923 +++++++++++++++-------------------- 1 file changed, 389 insertions(+), 534 deletions(-) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index 826df0010cf8..a04762f28feb 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -82,18 +82,19 @@ inline Tensor expand_dims(const Tensor& x, int axis, int num_newaxis = 1, new_shape.push_back(x->shape[i]); } - return compute(new_shape, - [&](const Array& indices) { - Array idx; - for (size_t i = 0; i < static_cast(axis); ++i) { - idx.push_back(indices[i]); - } - for (size_t i = axis + num_newaxis; i < indices.size(); ++i) { - idx.push_back(indices[i]); - } - return x(idx); - }, - name, tag); + return compute( + new_shape, + [&](const Array& indices) { + Array idx; + for (size_t i = 0; i < static_cast(axis); ++i) { + idx.push_back(indices[i]); + } + for (size_t i = axis + num_newaxis; i < indices.size(); ++i) { + idx.push_back(indices[i]); + } + return x(idx); + }, + name, tag); } /*! @@ -136,19 +137,20 @@ inline Tensor transpose(const Tensor& x, Array axes, std::string name = new_shape.push_back(x->shape[new_axis]); } - return compute(new_shape, - [&](const Array& indices) { - std::vector idx; - for (size_t i = 0; i < axes.size(); ++i) { - idx.push_back(1); - } - for (size_t i = 0; i < axes.size(); ++i) { - int axis = static_cast(axes[i]->value); - idx[axis] = indices[i]; - } - return x(idx); - }, - name, tag); + return compute( + new_shape, + [&](const Array& indices) { + std::vector idx; + for (size_t i = 0; i < axes.size(); ++i) { + idx.push_back(1); + } + for (size_t i = 0; i < axes.size(); ++i) { + int axis = static_cast(axes[i]->value); + idx[axis] = indices[i]; + } + return x(idx); + }, + name, tag); } /*! @@ -244,8 +246,8 @@ inline Tensor reshape(const Tensor& x, Array newshape, std::string nam } if (is_empty_shape(target_shape)) { - return compute(target_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, - name, tag); + return compute( + target_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, name, tag); } else { return compute( target_shape, @@ -351,21 +353,22 @@ inline Tensor squeeze(const Tensor& x, Array axis, bool atleast1d = fal out_shape.push_back(1); } - return compute(out_shape, - [&](const Array& indices) { - Array real_indices; - int flag = 0; - for (size_t i = 0; i < ndim; ++i) { - if (axis_set.count(static_cast(i)) == 0) { - real_indices.push_back(indices[i - flag]); - } else { - real_indices.push_back(0); - flag += 1; - } - } - return x(real_indices); - }, - name, tag); + return compute( + out_shape, + [&](const Array& indices) { + Array real_indices; + int flag = 0; + for (size_t i = 0; i < ndim; ++i) { + if (axis_set.count(static_cast(i)) == 0) { + real_indices.push_back(indices[i - flag]); + } else { + real_indices.push_back(0); + flag += 1; + } + } + return x(real_indices); + }, + name, tag); } /*! @@ -403,27 +406,28 @@ inline Tensor concatenate(const Array& inputs, int axis = 0, std::string out_shape.push_back(i == static_cast(axis) ? join_size : inputs[0]->shape[i]); } - return compute(out_shape, - [&](const Array& indices) { - auto ret = inputs[0](indices); - auto ind = indices[axis]; - for (size_t i = 0; i < inputs.size() - 1; ++i) { - ind -= axis_sizes[i]; - - Array idx; - for (size_t i = 0; i < static_cast(axis); ++i) { - idx.push_back(indices[i]); - } - idx.push_back(ind); - for (size_t i = axis + 1; i < indices.size(); ++i) { - idx.push_back(indices[i]); - } - - ret = tvm::if_then_else(ind >= 0, inputs[i + 1](idx), ret); - } - return ret; - }, - name, tag); + return compute( + out_shape, + [&](const Array& indices) { + auto ret = inputs[0](indices); + auto ind = indices[axis]; + for (size_t i = 0; i < inputs.size() - 1; ++i) { + ind -= axis_sizes[i]; + + Array idx; + for (size_t i = 0; i < static_cast(axis); ++i) { + idx.push_back(indices[i]); + } + idx.push_back(ind); + for (size_t i = axis + 1; i < indices.size(); ++i) { + idx.push_back(indices[i]); + } + + ret = tvm::if_then_else(ind >= 0, inputs[i + 1](idx), ret); + } + return ret; + }, + name, tag); } /*! @@ -454,19 +458,20 @@ inline Tensor stack(const Array& inputs, int axis = 0, std::string name for (size_t i = static_cast(axis); i < static_cast(ndim); ++i) out_shape.push_back(inputs[0]->shape[i]); - return compute(out_shape, - [&](const Array& indices) { - Array idx; - for (size_t i = 0; i < indices.size(); ++i) - if (i != static_cast(axis)) idx.push_back(indices[i]); - auto ind = indices[axis]; - auto ret = inputs[0](idx); - for (int i = 0; i < static_cast(inputs.size() - 1); ++i) { - ret = tvm::if_then_else(ind == i + 1, inputs[i + 1](idx), ret); - } - return ret; - }, - name, tag); + return compute( + out_shape, + [&](const Array& indices) { + Array idx; + for (size_t i = 0; i < indices.size(); ++i) + if (i != static_cast(axis)) idx.push_back(indices[i]); + auto ind = indices[axis]; + auto ret = inputs[0](idx); + for (int i = 0; i < static_cast(inputs.size() - 1); ++i) { + ret = tvm::if_then_else(ind == i + 1, inputs[i + 1](idx), ret); + } + return ret; + }, + name, tag); } /*! @@ -501,7 +506,7 @@ inline Array split(const Tensor& x, Array split_indices, int a begin_ids.push_back(idx); } - Array> out_shapes; + Array > out_shapes; for (size_t i = 0; i < begin_ids.size(); ++i) { PrimExpr out_axis_size; if (i == begin_ids.size() - 1) { @@ -524,21 +529,22 @@ inline Array split(const Tensor& x, Array split_indices, int a Array result; for (size_t i = 0; i < begin_ids.size(); ++i) { - result.push_back(compute(out_shapes[i], - [&](const Array& indices) { - auto begin = begin_ids[i]; - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(indices[j]); - } - real_indices.push_back(indices[axis] + begin); - for (size_t j = axis + 1; j < indices.size(); ++j) { - real_indices.push_back(indices[j]); - } - - return x(real_indices); - }, - name, tag)); + result.push_back(compute( + out_shapes[i], + [&](const Array& indices) { + auto begin = begin_ids[i]; + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(indices[j]); + } + real_indices.push_back(indices[axis] + begin); + for (size_t j = axis + 1; j < indices.size(); ++j) { + real_indices.push_back(indices[j]); + } + + return x(real_indices); + }, + name, tag)); } return result; @@ -566,15 +572,16 @@ inline te::Tensor dynamic_strided_slice(const te::Tensor& x, const te::Tensor& b for (int64_t i = 0; i < src_tensor_dim; ++i) { out_shape.push_back(tvm::tir::Var("dim")); } - return te::compute(out_shape, - [&](const Array& indices) { - Array real_indices; - for (int32_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides(i) + begin(i)); - } - return x(real_indices); - }, - name, tag); + return te::compute( + out_shape, + [&](const Array& indices) { + Array real_indices; + for (int32_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides(i) + begin(i)); + } + return x(real_indices); + }, + name, tag); } /*! @@ -608,15 +615,16 @@ inline Tensor strided_slice(const Tensor& x, const Array& begin, for (size_t i = 0; i < src_tensor_dim; ++i) { out_shape.push_back(indexdiv(end[i] - begin[i], strides[i])); } - return te::compute(out_shape, - [&](const Array& indices) { - Array real_indices; - for (size_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides[i] + begin[i]); - } - return x(real_indices); - }, - name, tag); + return te::compute( + out_shape, + [&](const Array& indices) { + Array real_indices; + for (size_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides[i] + begin[i]); + } + return x(real_indices); + }, + name, tag); } // Setup the ranges. @@ -695,15 +703,16 @@ inline Tensor strided_slice(const Tensor& x, const Array& begin, out_shape.push_back(slice_size); } - return compute(out_shape, - [&](const Array& indices) { - Array real_indices; - for (size_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); - } - return x(real_indices); - }, - name, tag); + return compute( + out_shape, + [&](const Array& indices) { + Array real_indices; + for (size_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); + } + return x(real_indices); + }, + name, tag); } /*! @@ -770,12 +779,13 @@ inline Tensor take(const Tensor& a, const Tensor& indices, std::string mode = "c } if (mode == "clip") { - return compute(out_shape, - [&](const Array& out_index) { - auto idx = tvm::min(tvm::max(0, indices(out_index)), a_size - 1); - return a(UnravelIndex(idx, a_shape)); - }, - name, tag); + return compute( + out_shape, + [&](const Array& out_index) { + auto idx = tvm::min(tvm::max(0, indices(out_index)), a_size - 1); + return a(UnravelIndex(idx, a_shape)); + }, + name, tag); } else if (mode == "fast") { LOG(WARNING) << "Fast mode segfaults when there are out-of-bounds indices. " "Make sure input indices are in bound"; @@ -784,12 +794,13 @@ inline Tensor take(const Tensor& a, const Tensor& indices, std::string mode = "c [&](const Array& out_index) { return a(UnravelIndex(indices(out_index), a_shape)); }, name, tag); } else { // mode == "wrap" - return compute(out_shape, - [&](const Array& out_index) { - auto idx = truncmod(truncmod(indices(out_index), a_size) + a_size, a_size); - return a(UnravelIndex(idx, a_shape)); - }, - name, tag); + return compute( + out_shape, + [&](const Array& out_index) { + auto idx = truncmod(truncmod(indices(out_index), a_size) + a_size, a_size); + return a(UnravelIndex(idx, a_shape)); + }, + name, tag); } } @@ -813,18 +824,19 @@ inline Tensor sequence_mask(const Tensor& data, const Tensor& valid_length, doub auto length_dim = data->shape[axis]; auto batch_dim = data->shape[1 - axis]; Array out_shape = data->shape; - Tensor out = compute(out_shape, - [&](const Array& out_index) { - Array len_index; - auto tid = out_index[axis]; - auto bid = out_index[1 - axis]; - len_index.push_back(bid); - PrimExpr ret = tvm::if_then_else( - tvm::cast(valid_length->dtype, tid) >= valid_length(len_index), - tvm::tir::make_const(data->dtype, mask_value), data(out_index)); - return ret; - }, - name, tag); + Tensor out = compute( + out_shape, + [&](const Array& out_index) { + Array len_index; + auto tid = out_index[axis]; + auto bid = out_index[1 - axis]; + len_index.push_back(bid); + PrimExpr ret = + tvm::if_then_else(tvm::cast(valid_length->dtype, tid) >= valid_length(len_index), + tvm::tir::make_const(data->dtype, mask_value), data(out_index)); + return ret; + }, + name, tag); return out; } @@ -862,64 +874,66 @@ inline Tensor take(const Tensor& a, const Tensor& indices, int axis, std::string } } if (mode == "clip") { - return compute(out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { - indices_position.push_back(out_index[j]); - } - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(out_index[j]); - } - auto idx = tvm::min(tvm::max(0, indices(indices_position)), axis_dim - 1); - real_indices.push_back(idx); - for (size_t j = axis + indices_len; j < out_index.size(); ++j) { - real_indices.push_back(out_index[j]); - } - return a(real_indices); - }, - name, tag); + return compute( + out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { + indices_position.push_back(out_index[j]); + } + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(out_index[j]); + } + auto idx = tvm::min(tvm::max(0, indices(indices_position)), axis_dim - 1); + real_indices.push_back(idx); + for (size_t j = axis + indices_len; j < out_index.size(); ++j) { + real_indices.push_back(out_index[j]); + } + return a(real_indices); + }, + name, tag); } else if (mode == "fast") { LOG(WARNING) << "Fast mode segfaults when there are out-of-bounds indices. " "Make sure input indices are in bound"; - return compute(out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { - indices_position.push_back(out_index[j]); - } - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(out_index[j]); - } - real_indices.push_back(indices(indices_position)); - for (size_t j = axis + indices_len; j < out_index.size(); ++j) { - real_indices.push_back(out_index[j]); - } - return a(real_indices); - }, - name, tag); + return compute( + out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { + indices_position.push_back(out_index[j]); + } + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(out_index[j]); + } + real_indices.push_back(indices(indices_position)); + for (size_t j = axis + indices_len; j < out_index.size(); ++j) { + real_indices.push_back(out_index[j]); + } + return a(real_indices); + }, + name, tag); } else { // mode == "wrap" - return compute(out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { - indices_position.push_back(out_index[j]); - } - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(out_index[j]); - } - auto idx = truncmod(truncmod(indices(indices_position), axis_dim) + axis_dim, - axis_dim); - real_indices.push_back(idx); - for (size_t j = axis + indices_len; j < out_index.size(); ++j) { - real_indices.push_back(out_index[j]); - } - return a(real_indices); - }, - name, tag); + return compute( + out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { + indices_position.push_back(out_index[j]); + } + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(out_index[j]); + } + auto idx = truncmod(truncmod(indices(indices_position), axis_dim) + axis_dim, axis_dim); + real_indices.push_back(idx); + for (size_t j = axis + indices_len; j < out_index.size(); ++j) { + real_indices.push_back(out_index[j]); + } + return a(real_indices); + }, + name, tag); } } @@ -995,19 +1009,20 @@ inline Tensor repeat(const Tensor& x, int repeats, int axis, std::string name = new_shape.push_back(x->shape[i]); } - return compute(new_shape, - [&](const Array& indices) { - Array idx; - for (size_t i = 0; i < static_cast(axis); ++i) { - idx.push_back(indices[i]); - } - idx.push_back(indexdiv(indices[axis], repeats)); - for (size_t i = axis + 1; i < indices.size(); ++i) { - idx.push_back(indices[i]); - } - return x(idx); - }, - name, tag); + return compute( + new_shape, + [&](const Array& indices) { + Array idx; + for (size_t i = 0; i < static_cast(axis); ++i) { + idx.push_back(indices[i]); + } + idx.push_back(indexdiv(indices[axis], repeats)); + for (size_t i = axis + 1; i < indices.size(); ++i) { + idx.push_back(indices[i]); + } + return x(idx); + }, + name, tag); } /*! @@ -1045,22 +1060,22 @@ inline Tensor tile(const Tensor& x, Array reps, std::string name = "T_t for (size_t i = 0; i < tdim; ++i) new_shape.push_back(data_shape[i] * reps_shape[i]); if (is_empty_shape(new_shape)) { - return compute(new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, - name, tag); + return compute( + new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, name, tag); } else { - return compute(new_shape, - [&](const Array& indices) { - Array idx; - if (ndim >= rdim) { - for (size_t i = 0; i < ndim; ++i) - idx.push_back(indexmod(indices[i], x->shape[i])); - } else { - for (size_t i = 0; i < ndim; ++i) - idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); - } - return x(idx); - }, - name, tag); + return compute( + new_shape, + [&](const Array& indices) { + Array idx; + if (ndim >= rdim) { + for (size_t i = 0; i < ndim; ++i) idx.push_back(indexmod(indices[i], x->shape[i])); + } else { + for (size_t i = 0; i < ndim; ++i) + idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); + } + return x(idx); + }, + name, tag); } } @@ -1079,24 +1094,25 @@ inline Tensor dyn_tile(const Tensor& x, Array new_shape, size_t rdim, std::string name = "T_tile", std::string tag = kBroadcast) { size_t ndim = x->shape.size(); if (is_empty_shape(new_shape)) { - return compute(new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, - name, tag); + return compute( + new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, name, tag); } else { - return compute(new_shape, - [&](const Array& indices) { - Array idx; - if (ndim >= rdim) { - for (size_t i = 0; i < ndim; ++i) { - idx.push_back(indexmod(indices[i], x->shape[i])); - } - } else { - for (size_t i = 0; i < ndim; ++i) { - idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); - } - } - return x(idx); - }, - name, tag); + return compute( + new_shape, + [&](const Array& indices) { + Array idx; + if (ndim >= rdim) { + for (size_t i = 0; i < ndim; ++i) { + idx.push_back(indexmod(indices[i], x->shape[i])); + } + } else { + for (size_t i = 0; i < ndim; ++i) { + idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); + } + } + return x(idx); + }, + name, tag); } } @@ -1128,23 +1144,24 @@ inline Tensor gather(const Tensor& data, int axis, const Tensor& indices, out_shape.push_back(indices->shape[i]); } - return compute(out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t i = 0; i < ndim_i; ++i) { - indices_position.push_back(out_index[i]); - } - Array real_indices; - for (size_t i = 0; i < ndim_i; ++i) { - if (i == (size_t)axis) { - real_indices.push_back(indices(indices_position)); - } else { - real_indices.push_back(indices_position[i]); - } - } - return data(real_indices); - }, - name, tag); + return compute( + out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t i = 0; i < ndim_i; ++i) { + indices_position.push_back(out_index[i]); + } + Array real_indices; + for (size_t i = 0; i < ndim_i; ++i) { + if (i == (size_t)axis) { + real_indices.push_back(indices(indices_position)); + } else { + real_indices.push_back(indices_position[i]); + } + } + return data(real_indices); + }, + name, tag); } /*! @@ -1357,185 +1374,18 @@ inline Array meshgrid(const Array& inputs, const std::string& in } Array result; for (size_t i = 0; i < inputs.size(); ++i) { - result.push_back(compute(out_shape, - [&](const Array& indices) { - const int src_index = (cartesian_indexing && i < 2) ? 1 - i : i; - Array real_indices = {indices[src_index]}; - return inputs[i](real_indices); - }, - name, tag)); - } - return result; -} - -inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Tensor& sparse_values, - const Tensor& default_value, - const Array& dense_shape, - const std::string name = "T_sparsefillemptyrows", - std::string tag = kInjective) { - Array result; - Array sp_ordered_output_shape; - sp_ordered_output_shape.push_back(dense_shape[0] + sparse_indices->shape[0]); - if (sparse_indices->shape.size() > 1) { - sp_ordered_output_shape.push_back(sparse_indices->shape[1]); + result.push_back(compute( + out_shape, + [&](const Array& indices) { + const int src_index = (cartesian_indexing && i < 2) ? 1 - i : i; + Array real_indices = {indices[src_index]}; + return inputs[i](real_indices); + }, + name, tag)); } - int num_rows = static_cast(dense_shape[0]) + GetConstInt(sparse_indices->shape[0]); - int num_cols = GetConstInt(sparse_indices->shape[1]); - std::vector> sp_ordered_output( - num_rows, std::vector(num_cols, PrimExpr(-1))); - - // std::vector> vec(100, std::vector(400, 0)); - std::vector missing_indices; - std::vector current_missing_index{0}; - std::vector total_missing_indices{0}; - auto empty_row_indicator = - tvm::te::compute(Array{dense_shape[0]}, [&](const Array& indices) { - // for (int i = 0; i < GetConstInt(sparse_indices->shape[0]); ++i) { - // sparse_indices[i] - // } - PrimExpr current_number = sparse_indices[indices[0] - total_missing_indices[0]]; - - bool cur_flag = true; - for (; cur_flag;) { - PrimExpr ret = if_then_else(current_number <= current_missing_index[0], 1, -1); - if (ret.as()->value == 1) { - PrimExpr ret2 = if_then_else(current_number == current_missing_index[0], 1, -1); - if (ret2.as()->value == 1) { - current_missing_index[0]++; - return PrimExpr(Bool(1)); - } else { - current_number += 1; - } - } else { - total_missing_indices[0]++; - } - } - return PrimExpr(Bool(1)); - }); - result.push_back(compute(sp_ordered_output_shape, - [&](const Array& indices) { - PrimExpr ret = -1; - // ret += missing_index; - // int missing_index = 0; - // PrimExpr current_missing_index = 0; - // PrimExpr count_missing_indices = 0; - // for (int i = 0; i < GetConstInt(sparse_indices->shape[0]); ++i) - // { - // PrimExpr is_missing_index = if_then_else(sparse_indices[i][0] - // <= current_missing_index, - // current_missing_index, - // -1); - // if (const IntImmNode* op = is_missing_index.as()) - // { - // if (op->value == -1) { - // PrimExpr on_current_indices = - // if_then_else(indices[0] == i + count_missing_indices, - // current_missing_index, -1); - // if (const IntImmNode* op = - // is_missing_index.as()) - // { - // if (op->value == -1) { - // continue; - // } else { - // for (int j = 0; j < 6; ++j) { - // break; - // } - // } - // } - // count_missing_indices += 1; - // } else { - // PrimExpr current_missing_index = - // if_then_else(sparse_indices[i][0] == - // current_missing_index, - // current_missing_index + 1, - // current_missing_index); - // } - // } - // } - return ret; - }, - name, tag)); - result.push_back(compute(Array{dense_shape[0]}, - [&](const Array& i) { - PrimExpr ret = Bool(1); - return ret; - }, - name, tag)); return result; } -inline Array SparseReshape(const Tensor& sparse_indices, const Tensor& sparse_values, - const Tensor& prev_shape, const Tensor& new_shape, - const std::string name = "T_sparsereshape", - std::string tag = kInjective) { - Array result; - Array new_sparse_indices_shape{sparse_indices->shape[0], new_shape->shape[0]}; - std::vector multipliers(GetConstInt(prev_shape->shape[0]), 1); - std::vector dividers(GetConstInt(new_shape->shape[0]), 1); - - tvm::te::compute(Array{1}, [&](const Array& indices) { - tvm::PrimExpr total_ele = prev_shape[0]; - for (int i = GetConstInt(prev_shape->shape[0]) - 2; i >= 0; --i) { - multipliers[i] = prev_shape[i + 1] * multipliers[i + 1]; - total_ele *= prev_shape[i + 1]; - } - PrimExpr division_total_ele = 1; - for (int i = 0; i < GetConstInt(new_shape->shape[0]); ++i) { - division_total_ele *= if_then_else(new_shape[i] != -1, new_shape[i], 1); - } - for (int i = GetConstInt(new_shape->shape[0]) - 2; i >= 0; --i) { - dividers[i] = dividers[i + 1] * if_then_else(new_shape[i + 1] != -1, new_shape[i + 1], - div(total_ele, division_total_ele)); - } - return PrimExpr(1); - }); - - result.push_back(compute(new_sparse_indices_shape, - [&](const Array& indices) { - PrimExpr flattened_idx = 0; - if (sparse_indices->shape.size() == 1) { - flattened_idx += sparse_indices[indices[0]]; - } else { - for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { - flattened_idx += (sparse_indices[indices[0]][k] * multipliers[k]); - } - } - Array new_sparse_indices; - if (GetConstInt(new_shape->shape[0]) != 1) { - for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { - new_sparse_indices.push_back(floordiv(flattened_idx, dividers[i])); - flattened_idx = floormod(flattened_idx, dividers[i]); - } - PrimExpr ret = -1; - - for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { - // auto ret = tir::Select(indices[1] == i, new_sparse_indices[i], - // -1); - if (indices.size() == 1) { - return new_sparse_indices[0]; - } else { - ret = if_then_else(indices[1] == i, new_sparse_indices[i], ret); - // PrimExpr cond = (ret == -1); - if (const IntImmNode* op = ret.as()) { - if (op->value == -1) { - continue; - } else { - break; - } - } - } - } - return ret; - } else { - return flattened_idx; - } - }, - name, tag)); - result.push_back(compute(sparse_values->shape, - [&](const Array& i) { return (sparse_values(i)); }, name, tag)); - return result; -} // namespace topi - /*! * \brief Transform the layout according to \p src_layout and \p dst_layout * \param src the source input. @@ -1565,13 +1415,14 @@ inline Tensor layout_transform(const Tensor& src, const std::string& src_layout, Array dst_shape = layout_converter.ForwardShape(src->shape); - return compute(dst_shape, - [&](const Array& dst_indices) { - Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); - Array src_indices = layout_converter.BackwardIndex(dst_indices_expr); - return src(src_indices); - }, - name, tag); + return compute( + dst_shape, + [&](const Array& dst_indices) { + Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); + Array src_indices = layout_converter.BackwardIndex(dst_indices_expr); + return src(src_indices); + }, + name, tag); } /*! \brief Utility function for auto_scheduler_layout_transform */ @@ -1622,23 +1473,24 @@ inline Tensor auto_scheduler_layout_transform(const Tensor& src, const String& s parse_auto_scheduler_layout(src_layout, &src_shape, &src_axes); parse_auto_scheduler_layout(dst_layout, &dst_shape, &dst_axes); - return compute(dst_shape, - [&](const Array& dst_indices) { - Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); - Array src_indices; - for (const std::string& src_axis : src_axes) { - PrimExpr src_index = 0; - CHECK_EQ(dst_indices_expr.size(), dst_axes.size()); - for (size_t i = 0; i < dst_axes.size(); ++i) { - if (dst_axes[i] == src_axis) { - src_index = src_index * dst_shape[i] + dst_indices_expr[i]; - } - } - src_indices.push_back(src_index); - } - return src(src_indices); - }, - name, tag); + return compute( + dst_shape, + [&](const Array& dst_indices) { + Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); + Array src_indices; + for (const std::string& src_axis : src_axes) { + PrimExpr src_index = 0; + CHECK_EQ(dst_indices_expr.size(), dst_axes.size()); + for (size_t i = 0; i < dst_axes.size(); ++i) { + if (dst_axes[i] == src_axis) { + src_index = src_index * dst_shape[i] + dst_indices_expr[i]; + } + } + src_indices.push_back(src_index); + } + return src(src_indices); + }, + name, tag); } /*! @@ -1653,16 +1505,17 @@ inline Tensor shape(const Tensor& src, DataType dtype, const std::string name = const std::string tag = kInjective) { int ndim = static_cast(src->shape.size()); Array out_shape{ndim}; - return compute(out_shape, - [&](const Array& indices) { - auto idx = indices[0]; - PrimExpr ret = 0; - for (int i = 0; i < ndim; ++i) { - ret = tvm::if_then_else(idx == i, src->shape[i], ret); - } - return tvm::cast(dtype, ret); - }, - name, tag); + return compute( + out_shape, + [&](const Array& indices) { + auto idx = indices[0]; + PrimExpr ret = 0; + for (int i = 0; i < ndim; ++i) { + ret = tvm::if_then_else(idx == i, src->shape[i], ret); + } + return tvm::cast(dtype, ret); + }, + name, tag); } /*! @@ -1678,15 +1531,16 @@ inline Tensor ndarray_size(const Tensor& src, const DataType& dtype, const std::string& tag = kInjective) { int ndim = static_cast(src->shape.size()); Array out_ndarray_size = {}; - return compute(out_ndarray_size, - [&](const Array& indices) { - PrimExpr ret = 1; - for (int i = 0; i < ndim; ++i) { - ret *= src->shape[i]; - } - return tvm::cast(dtype, ret); - }, - name, tag); + return compute( + out_ndarray_size, + [&](const Array& indices) { + PrimExpr ret = 1; + for (int i = 0; i < ndim; ++i) { + ret *= src->shape[i]; + } + return tvm::cast(dtype, ret); + }, + name, tag); } /*! @@ -1722,22 +1576,22 @@ inline Tensor one_hot(const Tensor& indices, const PrimExpr on_value, const Prim PrimExpr on_value_cast = cast(dtype, on_value); PrimExpr off_value_cast = cast(dtype, off_value); - return compute(oshape, - [&](const Array& iter_vars) { - Array indices_indices; - for (size_t i = 0; i < iter_vars.size(); i++) { - if (static_cast(i) == true_axis) { - continue; - } - - indices_indices.push_back(iter_vars[i]); - } - - auto idx = iter_vars[true_axis]; - return tir::Select(indices(indices_indices) == idx, on_value_cast, - off_value_cast); - }, - name, tag); + return compute( + oshape, + [&](const Array& iter_vars) { + Array indices_indices; + for (size_t i = 0; i < iter_vars.size(); i++) { + if (static_cast(i) == true_axis) { + continue; + } + + indices_indices.push_back(iter_vars[i]); + } + + auto idx = iter_vars[true_axis]; + return tir::Select(indices(indices_indices) == idx, on_value_cast, off_value_cast); + }, + name, tag); } /*! @@ -1764,29 +1618,29 @@ inline Tensor sparse_to_dense(const Tensor& sparse_indices, const Array& indices) { - PrimExpr ret = default_value; - if (0 == rank_sparse_indices) { - ret = if_then_else(indices[0] == sparse_indices[0], sparse_values[0], ret); - } else if (1 == rank_sparse_indices) { - for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { - ret = if_then_else(indices[0] == sparse_indices[j], sparse_values[j], ret); - } - } else { - for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { - PrimExpr aggregate_condition; - for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { - PrimExpr comparision = indices[k] == sparse_indices[j][k]; - aggregate_condition = - 0 == k ? comparision : aggregate_condition && comparision; - } - ret = if_then_else(aggregate_condition, sparse_values[j], ret); - } - } - return ret; - }, - name, tag); + return compute( + oshape, + [&](const Array& indices) { + PrimExpr ret = default_value; + if (0 == rank_sparse_indices) { + ret = if_then_else(indices[0] == sparse_indices[0], sparse_values[0], ret); + } else if (1 == rank_sparse_indices) { + for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { + ret = if_then_else(indices[0] == sparse_indices[j], sparse_values[j], ret); + } + } else { + for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { + PrimExpr aggregate_condition; + for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { + PrimExpr comparision = indices[k] == sparse_indices[j][k]; + aggregate_condition = 0 == k ? comparision : aggregate_condition && comparision; + } + ret = if_then_else(aggregate_condition, sparse_values[j], ret); + } + } + return ret; + }, + name, tag); } /*! @@ -1907,24 +1761,25 @@ inline Tensor adv_index(const Tensor& data, const Array& indices, oshape.push_back(data->shape[i]); } - return compute(oshape, - [&](const Array& iter_var) { - Array tensor_indices; - for (size_t i = 0; i < broadcast_shape.size(); ++i) { - tensor_indices.push_back(iter_var[i]); - } - - Array real_indices; - for (size_t i = 0; i < bindices.size(); ++i) { - real_indices.push_back(bindices[i](tensor_indices)); - } - for (size_t i = broadcast_shape.size(); i < iter_var.size(); ++i) { - real_indices.push_back(iter_var[i]); - } - - return data(real_indices); - }, - name, tag); + return compute( + oshape, + [&](const Array& iter_var) { + Array tensor_indices; + for (size_t i = 0; i < broadcast_shape.size(); ++i) { + tensor_indices.push_back(iter_var[i]); + } + + Array real_indices; + for (size_t i = 0; i < bindices.size(); ++i) { + real_indices.push_back(bindices[i](tensor_indices)); + } + for (size_t i = broadcast_shape.size(); i < iter_var.size(); ++i) { + real_indices.push_back(iter_var[i]); + } + + return data(real_indices); + }, + name, tag); } } // namespace topi From 2d48888b292a5967570c38d58d72dbb0529c80f9 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 17 Dec 2020 22:45:17 +0000 Subject: [PATCH 15/44] Only Transform changes --- include/tvm/topi/transform.h | 167 +++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index a04762f28feb..3f2c22cf024e 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -1386,6 +1386,173 @@ inline Array meshgrid(const Array& inputs, const std::string& in return result; } +inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Tensor& sparse_values, + const Tensor& default_value, + const Array& dense_shape, + const std::string name = "T_sparsefillemptyrows", + std::string tag = kInjective) { + Array result; + Array sp_ordered_output_shape; + sp_ordered_output_shape.push_back(dense_shape[0] + sparse_indices->shape[0]); + if (sparse_indices->shape.size() > 1) { + sp_ordered_output_shape.push_back(sparse_indices->shape[1]); + } + int num_rows = static_cast(dense_shape[0]) + GetConstInt(sparse_indices->shape[0]); + int num_cols = GetConstInt(sparse_indices->shape[1]); + std::vector> sp_ordered_output( + num_rows, std::vector(num_cols, PrimExpr(-1))); + + // std::vector> vec(100, std::vector(400, 0)); + std::vector missing_indices; + std::vector current_missing_index{0}; + std::vector total_missing_indices{0}; + auto empty_row_indicator = + tvm::te::compute(Array{dense_shape[0]}, [&](const Array& indices) { + // for (int i = 0; i < GetConstInt(sparse_indices->shape[0]); ++i) { + // sparse_indices[i] + // } + PrimExpr current_number = sparse_indices[indices[0] - total_missing_indices[0]]; + + bool cur_flag = true; + for (; cur_flag;) { + PrimExpr ret = if_then_else(current_number <= current_missing_index[0], 1, -1); + if (ret.as()->value == 1) { + PrimExpr ret2 = if_then_else(current_number == current_missing_index[0], 1, -1); + if (ret2.as()->value == 1) { + current_missing_index[0]++; + return PrimExpr(Bool(1)); + } else { + current_number += 1; + } + } else { + total_missing_indices[0]++; + } + } + return PrimExpr(Bool(1)); + }); + result.push_back(compute(sp_ordered_output_shape, + [&](const Array& indices) { + PrimExpr ret = -1; + // ret += missing_index; + // int missing_index = 0; + // PrimExpr current_missing_index = 0; + // PrimExpr count_missing_indices = 0; + // for (int i = 0; i < GetConstInt(sparse_indices->shape[0]); ++i) + // { + // PrimExpr is_missing_index = if_then_else(sparse_indices[i][0] + // <= current_missing_index, + // current_missing_index, + // -1); + // if (const IntImmNode* op = is_missing_index.as()) + // { + // if (op->value == -1) { + // PrimExpr on_current_indices = + // if_then_else(indices[0] == i + count_missing_indices, + // current_missing_index, -1); + // if (const IntImmNode* op = + // is_missing_index.as()) + // { + // if (op->value == -1) { + // continue; + // } else { + // for (int j = 0; j < 6; ++j) { + // break; + // } + // } + // } + // count_missing_indices += 1; + // } else { + // PrimExpr current_missing_index = + // if_then_else(sparse_indices[i][0] == + // current_missing_index, + // current_missing_index + 1, + // current_missing_index); + // } + // } + // } + return ret; + }, + name, tag)); + result.push_back(compute(Array{dense_shape[0]}, + [&](const Array& i) { + PrimExpr ret = Bool(1); + return ret; + }, + name, tag)); + return result; +} + +inline Array SparseReshape(const Tensor& sparse_indices, const Tensor& sparse_values, + const Tensor& prev_shape, const Tensor& new_shape, + const std::string name = "T_sparsereshape", + std::string tag = kInjective) { + Array result; + Array new_sparse_indices_shape{sparse_indices->shape[0], new_shape->shape[0]}; + std::vector multipliers(GetConstInt(prev_shape->shape[0]), 1); + std::vector dividers(GetConstInt(new_shape->shape[0]), 1); + + tvm::te::compute(Array{1}, [&](const Array& indices) { + tvm::PrimExpr total_ele = prev_shape[0]; + for (int i = GetConstInt(prev_shape->shape[0]) - 2; i >= 0; --i) { + multipliers[i] = prev_shape[i + 1] * multipliers[i + 1]; + total_ele *= prev_shape[i + 1]; + } + PrimExpr division_total_ele = 1; + for (int i = 0; i < GetConstInt(new_shape->shape[0]); ++i) { + division_total_ele *= if_then_else(new_shape[i] != -1, new_shape[i], 1); + } + for (int i = GetConstInt(new_shape->shape[0]) - 2; i >= 0; --i) { + dividers[i] = dividers[i + 1] * if_then_else(new_shape[i + 1] != -1, new_shape[i + 1], + div(total_ele, division_total_ele)); + } + return PrimExpr(1); + }); + + result.push_back(compute(new_sparse_indices_shape, + [&](const Array& indices) { + PrimExpr flattened_idx = 0; + if (sparse_indices->shape.size() == 1) { + flattened_idx += sparse_indices[indices[0]]; + } else { + for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { + flattened_idx += (sparse_indices[indices[0]][k] * multipliers[k]); + } + } + Array new_sparse_indices; + if (GetConstInt(new_shape->shape[0]) != 1) { + for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { + new_sparse_indices.push_back(floordiv(flattened_idx, dividers[i])); + flattened_idx = floormod(flattened_idx, dividers[i]); + } + PrimExpr ret = -1; + + for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { + // auto ret = tir::Select(indices[1] == i, new_sparse_indices[i], + // -1); + if (indices.size() == 1) { + return new_sparse_indices[0]; + } else { + ret = if_then_else(indices[1] == i, new_sparse_indices[i], ret); + // PrimExpr cond = (ret == -1); + if (const IntImmNode* op = ret.as()) { + if (op->value == -1) { + continue; + } else { + break; + } + } + } + } + return ret; + } else { + return flattened_idx; + } + }, + name, tag)); + result.push_back(compute(sparse_values->shape, + [&](const Array& i) { return (sparse_values(i)); }, name, tag)); + return result; +} // namespace topi /*! * \brief Transform the layout according to \p src_layout and \p dst_layout * \param src the source input. From 2e017fd1c811a8dde847b5c3b554de3faa7f3b82 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 17 Dec 2020 22:50:33 +0000 Subject: [PATCH 16/44] Correct Clang format version --- include/tvm/topi/transform.h | 189 ++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 93 deletions(-) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index 3f2c22cf024e..b9900cff704c 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -506,7 +506,7 @@ inline Array split(const Tensor& x, Array split_indices, int a begin_ids.push_back(idx); } - Array > out_shapes; + Array> out_shapes; for (size_t i = 0; i < begin_ids.size(); ++i) { PrimExpr out_axis_size; if (i == begin_ids.size() - 1) { @@ -1430,55 +1430,57 @@ inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Ten } return PrimExpr(Bool(1)); }); - result.push_back(compute(sp_ordered_output_shape, - [&](const Array& indices) { - PrimExpr ret = -1; - // ret += missing_index; - // int missing_index = 0; - // PrimExpr current_missing_index = 0; - // PrimExpr count_missing_indices = 0; - // for (int i = 0; i < GetConstInt(sparse_indices->shape[0]); ++i) - // { - // PrimExpr is_missing_index = if_then_else(sparse_indices[i][0] - // <= current_missing_index, - // current_missing_index, - // -1); - // if (const IntImmNode* op = is_missing_index.as()) - // { - // if (op->value == -1) { - // PrimExpr on_current_indices = - // if_then_else(indices[0] == i + count_missing_indices, - // current_missing_index, -1); - // if (const IntImmNode* op = - // is_missing_index.as()) - // { - // if (op->value == -1) { - // continue; - // } else { - // for (int j = 0; j < 6; ++j) { - // break; - // } - // } - // } - // count_missing_indices += 1; - // } else { - // PrimExpr current_missing_index = - // if_then_else(sparse_indices[i][0] == - // current_missing_index, - // current_missing_index + 1, - // current_missing_index); - // } - // } - // } - return ret; - }, - name, tag)); - result.push_back(compute(Array{dense_shape[0]}, - [&](const Array& i) { - PrimExpr ret = Bool(1); - return ret; - }, - name, tag)); + result.push_back(compute( + sp_ordered_output_shape, + [&](const Array& indices) { + PrimExpr ret = -1; + // ret += missing_index; + // int missing_index = 0; + // PrimExpr current_missing_index = 0; + // PrimExpr count_missing_indices = 0; + // for (int i = 0; i < GetConstInt(sparse_indices->shape[0]); ++i) + // { + // PrimExpr is_missing_index = if_then_else(sparse_indices[i][0] + // <= current_missing_index, + // current_missing_index, + // -1); + // if (const IntImmNode* op = is_missing_index.as()) + // { + // if (op->value == -1) { + // PrimExpr on_current_indices = + // if_then_else(indices[0] == i + count_missing_indices, + // current_missing_index, -1); + // if (const IntImmNode* op = + // is_missing_index.as()) + // { + // if (op->value == -1) { + // continue; + // } else { + // for (int j = 0; j < 6; ++j) { + // break; + // } + // } + // } + // count_missing_indices += 1; + // } else { + // PrimExpr current_missing_index = + // if_then_else(sparse_indices[i][0] == + // current_missing_index, + // current_missing_index + 1, + // current_missing_index); + // } + // } + // } + return ret; + }, + name, tag)); + result.push_back(compute( + Array{dense_shape[0]}, + [&](const Array& i) { + PrimExpr ret = Bool(1); + return ret; + }, + name, tag)); return result; } @@ -1508,49 +1510,50 @@ inline Array SparseReshape(const Tensor& sparse_indices, const Tensor& s return PrimExpr(1); }); - result.push_back(compute(new_sparse_indices_shape, - [&](const Array& indices) { - PrimExpr flattened_idx = 0; - if (sparse_indices->shape.size() == 1) { - flattened_idx += sparse_indices[indices[0]]; - } else { - for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { - flattened_idx += (sparse_indices[indices[0]][k] * multipliers[k]); - } - } - Array new_sparse_indices; - if (GetConstInt(new_shape->shape[0]) != 1) { - for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { - new_sparse_indices.push_back(floordiv(flattened_idx, dividers[i])); - flattened_idx = floormod(flattened_idx, dividers[i]); - } - PrimExpr ret = -1; - - for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { - // auto ret = tir::Select(indices[1] == i, new_sparse_indices[i], - // -1); - if (indices.size() == 1) { - return new_sparse_indices[0]; - } else { - ret = if_then_else(indices[1] == i, new_sparse_indices[i], ret); - // PrimExpr cond = (ret == -1); - if (const IntImmNode* op = ret.as()) { - if (op->value == -1) { - continue; - } else { - break; - } - } - } - } - return ret; - } else { - return flattened_idx; - } - }, - name, tag)); - result.push_back(compute(sparse_values->shape, - [&](const Array& i) { return (sparse_values(i)); }, name, tag)); + result.push_back(compute( + new_sparse_indices_shape, + [&](const Array& indices) { + PrimExpr flattened_idx = 0; + if (sparse_indices->shape.size() == 1) { + flattened_idx += sparse_indices[indices[0]]; + } else { + for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { + flattened_idx += (sparse_indices[indices[0]][k] * multipliers[k]); + } + } + Array new_sparse_indices; + if (GetConstInt(new_shape->shape[0]) != 1) { + for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { + new_sparse_indices.push_back(floordiv(flattened_idx, dividers[i])); + flattened_idx = floormod(flattened_idx, dividers[i]); + } + PrimExpr ret = -1; + + for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { + // auto ret = tir::Select(indices[1] == i, new_sparse_indices[i], + // -1); + if (indices.size() == 1) { + return new_sparse_indices[0]; + } else { + ret = if_then_else(indices[1] == i, new_sparse_indices[i], ret); + // PrimExpr cond = (ret == -1); + if (const IntImmNode* op = ret.as()) { + if (op->value == -1) { + continue; + } else { + break; + } + } + } + } + return ret; + } else { + return flattened_idx; + } + }, + name, tag)); + result.push_back(compute( + sparse_values->shape, [&](const Array& i) { return (sparse_values(i)); }, name, tag)); return result; } // namespace topi /*! From b7000acb9adf727218377805de7d7191f425d9f5 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 17 Dec 2020 22:53:13 +0000 Subject: [PATCH 17/44] Reset_to_clang-format-10 --- src/relay/op/tensor/transform.cc | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index 4348a60bb5c3..e87b7fb150ab 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1339,11 +1339,12 @@ inline te::Tensor DynamicArange(const te::Tensor& start, const te::Tensor& stop, std::string name = "T_arange_dynamic", std::string tag = topi::kInjective) { tvm::PrimExpr num_elem = tvm::tir::Var("num_elem"); - return te::compute({num_elem}, - [&](const Array& indices) { - return tvm::cast(dtype, start[0] + step[0] * indices[0]); - }, - name, tag); + return te::compute( + {num_elem}, + [&](const Array& indices) { + return tvm::cast(dtype, start[0] + step[0] * indices[0]); + }, + name, tag); } Array ArangeCompute(const Attrs& attrs, const Array& inputs, @@ -2500,16 +2501,16 @@ Array StridedSliceCompute(const Attrs& attrs, const Arrayvalue : 1))); } - return Array{ - te::compute(out_shape, - [&](const Array& indices) { - Array real_indices; - for (size_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); - } - return input(real_indices); - }, - std::string{"T_strided_slice_dynamic"}, std::string{topi::kInjective})}; + return Array{te::compute( + out_shape, + [&](const Array& indices) { + Array real_indices; + for (size_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); + } + return input(real_indices); + }, + std::string{"T_strided_slice_dynamic"}, std::string{topi::kInjective})}; } else { for (size_t i = 0; i < begin.size(); ++i) { begin_expr.push_back(begin[i]); From 00e322010b1fdc5eace3307d86c3e7c814dd7986 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 18 Dec 2020 11:37:28 +0000 Subject: [PATCH 18/44] Finish implementation of SparseFillEmptyRowsOp --- include/tvm/topi/transform.h | 934 ++++++++++++--------------- tests/python/relay/test_op_level3.py | 13 + 2 files changed, 443 insertions(+), 504 deletions(-) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index b9900cff704c..b47a9860547b 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -82,19 +82,18 @@ inline Tensor expand_dims(const Tensor& x, int axis, int num_newaxis = 1, new_shape.push_back(x->shape[i]); } - return compute( - new_shape, - [&](const Array& indices) { - Array idx; - for (size_t i = 0; i < static_cast(axis); ++i) { - idx.push_back(indices[i]); - } - for (size_t i = axis + num_newaxis; i < indices.size(); ++i) { - idx.push_back(indices[i]); - } - return x(idx); - }, - name, tag); + return compute(new_shape, + [&](const Array& indices) { + Array idx; + for (size_t i = 0; i < static_cast(axis); ++i) { + idx.push_back(indices[i]); + } + for (size_t i = axis + num_newaxis; i < indices.size(); ++i) { + idx.push_back(indices[i]); + } + return x(idx); + }, + name, tag); } /*! @@ -137,20 +136,19 @@ inline Tensor transpose(const Tensor& x, Array axes, std::string name = new_shape.push_back(x->shape[new_axis]); } - return compute( - new_shape, - [&](const Array& indices) { - std::vector idx; - for (size_t i = 0; i < axes.size(); ++i) { - idx.push_back(1); - } - for (size_t i = 0; i < axes.size(); ++i) { - int axis = static_cast(axes[i]->value); - idx[axis] = indices[i]; - } - return x(idx); - }, - name, tag); + return compute(new_shape, + [&](const Array& indices) { + std::vector idx; + for (size_t i = 0; i < axes.size(); ++i) { + idx.push_back(1); + } + for (size_t i = 0; i < axes.size(); ++i) { + int axis = static_cast(axes[i]->value); + idx[axis] = indices[i]; + } + return x(idx); + }, + name, tag); } /*! @@ -246,8 +244,8 @@ inline Tensor reshape(const Tensor& x, Array newshape, std::string nam } if (is_empty_shape(target_shape)) { - return compute( - target_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, name, tag); + return compute(target_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, + name, tag); } else { return compute( target_shape, @@ -353,22 +351,21 @@ inline Tensor squeeze(const Tensor& x, Array axis, bool atleast1d = fal out_shape.push_back(1); } - return compute( - out_shape, - [&](const Array& indices) { - Array real_indices; - int flag = 0; - for (size_t i = 0; i < ndim; ++i) { - if (axis_set.count(static_cast(i)) == 0) { - real_indices.push_back(indices[i - flag]); - } else { - real_indices.push_back(0); - flag += 1; - } - } - return x(real_indices); - }, - name, tag); + return compute(out_shape, + [&](const Array& indices) { + Array real_indices; + int flag = 0; + for (size_t i = 0; i < ndim; ++i) { + if (axis_set.count(static_cast(i)) == 0) { + real_indices.push_back(indices[i - flag]); + } else { + real_indices.push_back(0); + flag += 1; + } + } + return x(real_indices); + }, + name, tag); } /*! @@ -406,28 +403,27 @@ inline Tensor concatenate(const Array& inputs, int axis = 0, std::string out_shape.push_back(i == static_cast(axis) ? join_size : inputs[0]->shape[i]); } - return compute( - out_shape, - [&](const Array& indices) { - auto ret = inputs[0](indices); - auto ind = indices[axis]; - for (size_t i = 0; i < inputs.size() - 1; ++i) { - ind -= axis_sizes[i]; - - Array idx; - for (size_t i = 0; i < static_cast(axis); ++i) { - idx.push_back(indices[i]); - } - idx.push_back(ind); - for (size_t i = axis + 1; i < indices.size(); ++i) { - idx.push_back(indices[i]); - } - - ret = tvm::if_then_else(ind >= 0, inputs[i + 1](idx), ret); - } - return ret; - }, - name, tag); + return compute(out_shape, + [&](const Array& indices) { + auto ret = inputs[0](indices); + auto ind = indices[axis]; + for (size_t i = 0; i < inputs.size() - 1; ++i) { + ind -= axis_sizes[i]; + + Array idx; + for (size_t i = 0; i < static_cast(axis); ++i) { + idx.push_back(indices[i]); + } + idx.push_back(ind); + for (size_t i = axis + 1; i < indices.size(); ++i) { + idx.push_back(indices[i]); + } + + ret = tvm::if_then_else(ind >= 0, inputs[i + 1](idx), ret); + } + return ret; + }, + name, tag); } /*! @@ -458,20 +454,19 @@ inline Tensor stack(const Array& inputs, int axis = 0, std::string name for (size_t i = static_cast(axis); i < static_cast(ndim); ++i) out_shape.push_back(inputs[0]->shape[i]); - return compute( - out_shape, - [&](const Array& indices) { - Array idx; - for (size_t i = 0; i < indices.size(); ++i) - if (i != static_cast(axis)) idx.push_back(indices[i]); - auto ind = indices[axis]; - auto ret = inputs[0](idx); - for (int i = 0; i < static_cast(inputs.size() - 1); ++i) { - ret = tvm::if_then_else(ind == i + 1, inputs[i + 1](idx), ret); - } - return ret; - }, - name, tag); + return compute(out_shape, + [&](const Array& indices) { + Array idx; + for (size_t i = 0; i < indices.size(); ++i) + if (i != static_cast(axis)) idx.push_back(indices[i]); + auto ind = indices[axis]; + auto ret = inputs[0](idx); + for (int i = 0; i < static_cast(inputs.size() - 1); ++i) { + ret = tvm::if_then_else(ind == i + 1, inputs[i + 1](idx), ret); + } + return ret; + }, + name, tag); } /*! @@ -529,22 +524,21 @@ inline Array split(const Tensor& x, Array split_indices, int a Array result; for (size_t i = 0; i < begin_ids.size(); ++i) { - result.push_back(compute( - out_shapes[i], - [&](const Array& indices) { - auto begin = begin_ids[i]; - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(indices[j]); - } - real_indices.push_back(indices[axis] + begin); - for (size_t j = axis + 1; j < indices.size(); ++j) { - real_indices.push_back(indices[j]); - } - - return x(real_indices); - }, - name, tag)); + result.push_back(compute(out_shapes[i], + [&](const Array& indices) { + auto begin = begin_ids[i]; + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(indices[j]); + } + real_indices.push_back(indices[axis] + begin); + for (size_t j = axis + 1; j < indices.size(); ++j) { + real_indices.push_back(indices[j]); + } + + return x(real_indices); + }, + name, tag)); } return result; @@ -572,16 +566,15 @@ inline te::Tensor dynamic_strided_slice(const te::Tensor& x, const te::Tensor& b for (int64_t i = 0; i < src_tensor_dim; ++i) { out_shape.push_back(tvm::tir::Var("dim")); } - return te::compute( - out_shape, - [&](const Array& indices) { - Array real_indices; - for (int32_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides(i) + begin(i)); - } - return x(real_indices); - }, - name, tag); + return te::compute(out_shape, + [&](const Array& indices) { + Array real_indices; + for (int32_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides(i) + begin(i)); + } + return x(real_indices); + }, + name, tag); } /*! @@ -615,16 +608,15 @@ inline Tensor strided_slice(const Tensor& x, const Array& begin, for (size_t i = 0; i < src_tensor_dim; ++i) { out_shape.push_back(indexdiv(end[i] - begin[i], strides[i])); } - return te::compute( - out_shape, - [&](const Array& indices) { - Array real_indices; - for (size_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides[i] + begin[i]); - } - return x(real_indices); - }, - name, tag); + return te::compute(out_shape, + [&](const Array& indices) { + Array real_indices; + for (size_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides[i] + begin[i]); + } + return x(real_indices); + }, + name, tag); } // Setup the ranges. @@ -703,16 +695,15 @@ inline Tensor strided_slice(const Tensor& x, const Array& begin, out_shape.push_back(slice_size); } - return compute( - out_shape, - [&](const Array& indices) { - Array real_indices; - for (size_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); - } - return x(real_indices); - }, - name, tag); + return compute(out_shape, + [&](const Array& indices) { + Array real_indices; + for (size_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); + } + return x(real_indices); + }, + name, tag); } /*! @@ -779,13 +770,12 @@ inline Tensor take(const Tensor& a, const Tensor& indices, std::string mode = "c } if (mode == "clip") { - return compute( - out_shape, - [&](const Array& out_index) { - auto idx = tvm::min(tvm::max(0, indices(out_index)), a_size - 1); - return a(UnravelIndex(idx, a_shape)); - }, - name, tag); + return compute(out_shape, + [&](const Array& out_index) { + auto idx = tvm::min(tvm::max(0, indices(out_index)), a_size - 1); + return a(UnravelIndex(idx, a_shape)); + }, + name, tag); } else if (mode == "fast") { LOG(WARNING) << "Fast mode segfaults when there are out-of-bounds indices. " "Make sure input indices are in bound"; @@ -794,13 +784,12 @@ inline Tensor take(const Tensor& a, const Tensor& indices, std::string mode = "c [&](const Array& out_index) { return a(UnravelIndex(indices(out_index), a_shape)); }, name, tag); } else { // mode == "wrap" - return compute( - out_shape, - [&](const Array& out_index) { - auto idx = truncmod(truncmod(indices(out_index), a_size) + a_size, a_size); - return a(UnravelIndex(idx, a_shape)); - }, - name, tag); + return compute(out_shape, + [&](const Array& out_index) { + auto idx = truncmod(truncmod(indices(out_index), a_size) + a_size, a_size); + return a(UnravelIndex(idx, a_shape)); + }, + name, tag); } } @@ -824,19 +813,18 @@ inline Tensor sequence_mask(const Tensor& data, const Tensor& valid_length, doub auto length_dim = data->shape[axis]; auto batch_dim = data->shape[1 - axis]; Array out_shape = data->shape; - Tensor out = compute( - out_shape, - [&](const Array& out_index) { - Array len_index; - auto tid = out_index[axis]; - auto bid = out_index[1 - axis]; - len_index.push_back(bid); - PrimExpr ret = - tvm::if_then_else(tvm::cast(valid_length->dtype, tid) >= valid_length(len_index), - tvm::tir::make_const(data->dtype, mask_value), data(out_index)); - return ret; - }, - name, tag); + Tensor out = compute(out_shape, + [&](const Array& out_index) { + Array len_index; + auto tid = out_index[axis]; + auto bid = out_index[1 - axis]; + len_index.push_back(bid); + PrimExpr ret = tvm::if_then_else( + tvm::cast(valid_length->dtype, tid) >= valid_length(len_index), + tvm::tir::make_const(data->dtype, mask_value), data(out_index)); + return ret; + }, + name, tag); return out; } @@ -874,66 +862,64 @@ inline Tensor take(const Tensor& a, const Tensor& indices, int axis, std::string } } if (mode == "clip") { - return compute( - out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { - indices_position.push_back(out_index[j]); - } - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(out_index[j]); - } - auto idx = tvm::min(tvm::max(0, indices(indices_position)), axis_dim - 1); - real_indices.push_back(idx); - for (size_t j = axis + indices_len; j < out_index.size(); ++j) { - real_indices.push_back(out_index[j]); - } - return a(real_indices); - }, - name, tag); + return compute(out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { + indices_position.push_back(out_index[j]); + } + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(out_index[j]); + } + auto idx = tvm::min(tvm::max(0, indices(indices_position)), axis_dim - 1); + real_indices.push_back(idx); + for (size_t j = axis + indices_len; j < out_index.size(); ++j) { + real_indices.push_back(out_index[j]); + } + return a(real_indices); + }, + name, tag); } else if (mode == "fast") { LOG(WARNING) << "Fast mode segfaults when there are out-of-bounds indices. " "Make sure input indices are in bound"; - return compute( - out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { - indices_position.push_back(out_index[j]); - } - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(out_index[j]); - } - real_indices.push_back(indices(indices_position)); - for (size_t j = axis + indices_len; j < out_index.size(); ++j) { - real_indices.push_back(out_index[j]); - } - return a(real_indices); - }, - name, tag); + return compute(out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { + indices_position.push_back(out_index[j]); + } + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(out_index[j]); + } + real_indices.push_back(indices(indices_position)); + for (size_t j = axis + indices_len; j < out_index.size(); ++j) { + real_indices.push_back(out_index[j]); + } + return a(real_indices); + }, + name, tag); } else { // mode == "wrap" - return compute( - out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { - indices_position.push_back(out_index[j]); - } - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(out_index[j]); - } - auto idx = truncmod(truncmod(indices(indices_position), axis_dim) + axis_dim, axis_dim); - real_indices.push_back(idx); - for (size_t j = axis + indices_len; j < out_index.size(); ++j) { - real_indices.push_back(out_index[j]); - } - return a(real_indices); - }, - name, tag); + return compute(out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { + indices_position.push_back(out_index[j]); + } + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(out_index[j]); + } + auto idx = truncmod(truncmod(indices(indices_position), axis_dim) + axis_dim, + axis_dim); + real_indices.push_back(idx); + for (size_t j = axis + indices_len; j < out_index.size(); ++j) { + real_indices.push_back(out_index[j]); + } + return a(real_indices); + }, + name, tag); } } @@ -1009,20 +995,19 @@ inline Tensor repeat(const Tensor& x, int repeats, int axis, std::string name = new_shape.push_back(x->shape[i]); } - return compute( - new_shape, - [&](const Array& indices) { - Array idx; - for (size_t i = 0; i < static_cast(axis); ++i) { - idx.push_back(indices[i]); - } - idx.push_back(indexdiv(indices[axis], repeats)); - for (size_t i = axis + 1; i < indices.size(); ++i) { - idx.push_back(indices[i]); - } - return x(idx); - }, - name, tag); + return compute(new_shape, + [&](const Array& indices) { + Array idx; + for (size_t i = 0; i < static_cast(axis); ++i) { + idx.push_back(indices[i]); + } + idx.push_back(indexdiv(indices[axis], repeats)); + for (size_t i = axis + 1; i < indices.size(); ++i) { + idx.push_back(indices[i]); + } + return x(idx); + }, + name, tag); } /*! @@ -1060,22 +1045,22 @@ inline Tensor tile(const Tensor& x, Array reps, std::string name = "T_t for (size_t i = 0; i < tdim; ++i) new_shape.push_back(data_shape[i] * reps_shape[i]); if (is_empty_shape(new_shape)) { - return compute( - new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, name, tag); + return compute(new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, + name, tag); } else { - return compute( - new_shape, - [&](const Array& indices) { - Array idx; - if (ndim >= rdim) { - for (size_t i = 0; i < ndim; ++i) idx.push_back(indexmod(indices[i], x->shape[i])); - } else { - for (size_t i = 0; i < ndim; ++i) - idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); - } - return x(idx); - }, - name, tag); + return compute(new_shape, + [&](const Array& indices) { + Array idx; + if (ndim >= rdim) { + for (size_t i = 0; i < ndim; ++i) + idx.push_back(indexmod(indices[i], x->shape[i])); + } else { + for (size_t i = 0; i < ndim; ++i) + idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); + } + return x(idx); + }, + name, tag); } } @@ -1094,25 +1079,24 @@ inline Tensor dyn_tile(const Tensor& x, Array new_shape, size_t rdim, std::string name = "T_tile", std::string tag = kBroadcast) { size_t ndim = x->shape.size(); if (is_empty_shape(new_shape)) { - return compute( - new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, name, tag); + return compute(new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, + name, tag); } else { - return compute( - new_shape, - [&](const Array& indices) { - Array idx; - if (ndim >= rdim) { - for (size_t i = 0; i < ndim; ++i) { - idx.push_back(indexmod(indices[i], x->shape[i])); - } - } else { - for (size_t i = 0; i < ndim; ++i) { - idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); - } - } - return x(idx); - }, - name, tag); + return compute(new_shape, + [&](const Array& indices) { + Array idx; + if (ndim >= rdim) { + for (size_t i = 0; i < ndim; ++i) { + idx.push_back(indexmod(indices[i], x->shape[i])); + } + } else { + for (size_t i = 0; i < ndim; ++i) { + idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); + } + } + return x(idx); + }, + name, tag); } } @@ -1144,24 +1128,23 @@ inline Tensor gather(const Tensor& data, int axis, const Tensor& indices, out_shape.push_back(indices->shape[i]); } - return compute( - out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t i = 0; i < ndim_i; ++i) { - indices_position.push_back(out_index[i]); - } - Array real_indices; - for (size_t i = 0; i < ndim_i; ++i) { - if (i == (size_t)axis) { - real_indices.push_back(indices(indices_position)); - } else { - real_indices.push_back(indices_position[i]); - } - } - return data(real_indices); - }, - name, tag); + return compute(out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t i = 0; i < ndim_i; ++i) { + indices_position.push_back(out_index[i]); + } + Array real_indices; + for (size_t i = 0; i < ndim_i; ++i) { + if (i == (size_t)axis) { + real_indices.push_back(indices(indices_position)); + } else { + real_indices.push_back(indices_position[i]); + } + } + return data(real_indices); + }, + name, tag); } /*! @@ -1374,14 +1357,13 @@ inline Array meshgrid(const Array& inputs, const std::string& in } Array result; for (size_t i = 0; i < inputs.size(); ++i) { - result.push_back(compute( - out_shape, - [&](const Array& indices) { - const int src_index = (cartesian_indexing && i < 2) ? 1 - i : i; - Array real_indices = {indices[src_index]}; - return inputs[i](real_indices); - }, - name, tag)); + result.push_back(compute(out_shape, + [&](const Array& indices) { + const int src_index = (cartesian_indexing && i < 2) ? 1 - i : i; + Array real_indices = {indices[src_index]}; + return inputs[i](real_indices); + }, + name, tag)); } return result; } @@ -1397,90 +1379,40 @@ inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Ten if (sparse_indices->shape.size() > 1) { sp_ordered_output_shape.push_back(sparse_indices->shape[1]); } - int num_rows = static_cast(dense_shape[0]) + GetConstInt(sparse_indices->shape[0]); - int num_cols = GetConstInt(sparse_indices->shape[1]); - std::vector> sp_ordered_output( - num_rows, std::vector(num_cols, PrimExpr(-1))); - - // std::vector> vec(100, std::vector(400, 0)); - std::vector missing_indices; - std::vector current_missing_index{0}; - std::vector total_missing_indices{0}; auto empty_row_indicator = - tvm::te::compute(Array{dense_shape[0]}, [&](const Array& indices) { - // for (int i = 0; i < GetConstInt(sparse_indices->shape[0]); ++i) { - // sparse_indices[i] - // } - PrimExpr current_number = sparse_indices[indices[0] - total_missing_indices[0]]; - - bool cur_flag = true; - for (; cur_flag;) { - PrimExpr ret = if_then_else(current_number <= current_missing_index[0], 1, -1); - if (ret.as()->value == 1) { - PrimExpr ret2 = if_then_else(current_number == current_missing_index[0], 1, -1); - if (ret2.as()->value == 1) { - current_missing_index[0]++; - return PrimExpr(Bool(1)); - } else { - current_number += 1; - } + compute(Array{dense_shape[0]}, [&](const Array& indices) { + PrimExpr ret = PrimExpr(Bool(1)); + for (int i = 0; i < GetConstInt(sparse_indices->shape[0]); ++i) { + PrimExpr sparse_index; + if (sparse_indices->shape.size() == 1) { + sparse_index = sparse_indices[i]; } else { - total_missing_indices[0]++; + sparse_index = sparse_indices[i][0]; } + ret = if_then_else(sparse_index == indices[0], PrimExpr(Bool(0)), ret); } - return PrimExpr(Bool(1)); + return ret; }); result.push_back(compute( sp_ordered_output_shape, [&](const Array& indices) { PrimExpr ret = -1; - // ret += missing_index; - // int missing_index = 0; - // PrimExpr current_missing_index = 0; - // PrimExpr count_missing_indices = 0; - // for (int i = 0; i < GetConstInt(sparse_indices->shape[0]); ++i) - // { - // PrimExpr is_missing_index = if_then_else(sparse_indices[i][0] - // <= current_missing_index, - // current_missing_index, - // -1); - // if (const IntImmNode* op = is_missing_index.as()) - // { - // if (op->value == -1) { - // PrimExpr on_current_indices = - // if_then_else(indices[0] == i + count_missing_indices, - // current_missing_index, -1); - // if (const IntImmNode* op = - // is_missing_index.as()) - // { - // if (op->value == -1) { - // continue; - // } else { - // for (int j = 0; j < 6; ++j) { - // break; - // } - // } - // } - // count_missing_indices += 1; - // } else { - // PrimExpr current_missing_index = - // if_then_else(sparse_indices[i][0] == - // current_missing_index, - // current_missing_index + 1, - // current_missing_index); - // } - // } - // } - return ret; - }, - name, tag)); - result.push_back(compute( - Array{dense_shape[0]}, - [&](const Array& i) { - PrimExpr ret = Bool(1); + ret = if_then_else(indices[0] < sparse_indices->shape[0], sparse_indices(indices), ret); + PrimExpr empty_row_count = 0; + for (int i = 0; i < static_cast(dense_shape[0]); ++i) { + empty_row_count = + if_then_else(empty_row_indicator[i], empty_row_count, empty_row_count + 1); + PrimExpr condition = + (indices[0] == sparse_indices->shape[0] + empty_row_count - 1) && empty_row_count > 0; + ret = if_then_else(condition, i, ret); + if (indices.size() > 1) { + ret = if_then_else(condition && indices[1] == 1, 0, ret); + } + } return ret; }, name, tag)); + result.push_back(empty_row_indicator); return result; } @@ -1510,50 +1442,49 @@ inline Array SparseReshape(const Tensor& sparse_indices, const Tensor& s return PrimExpr(1); }); - result.push_back(compute( - new_sparse_indices_shape, - [&](const Array& indices) { - PrimExpr flattened_idx = 0; - if (sparse_indices->shape.size() == 1) { - flattened_idx += sparse_indices[indices[0]]; - } else { - for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { - flattened_idx += (sparse_indices[indices[0]][k] * multipliers[k]); - } - } - Array new_sparse_indices; - if (GetConstInt(new_shape->shape[0]) != 1) { - for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { - new_sparse_indices.push_back(floordiv(flattened_idx, dividers[i])); - flattened_idx = floormod(flattened_idx, dividers[i]); - } - PrimExpr ret = -1; - - for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { - // auto ret = tir::Select(indices[1] == i, new_sparse_indices[i], - // -1); - if (indices.size() == 1) { - return new_sparse_indices[0]; - } else { - ret = if_then_else(indices[1] == i, new_sparse_indices[i], ret); - // PrimExpr cond = (ret == -1); - if (const IntImmNode* op = ret.as()) { - if (op->value == -1) { - continue; - } else { - break; - } - } - } - } - return ret; - } else { - return flattened_idx; - } - }, - name, tag)); - result.push_back(compute( - sparse_values->shape, [&](const Array& i) { return (sparse_values(i)); }, name, tag)); + result.push_back(compute(new_sparse_indices_shape, + [&](const Array& indices) { + PrimExpr flattened_idx = 0; + if (sparse_indices->shape.size() == 1) { + flattened_idx += sparse_indices[indices[0]]; + } else { + for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { + flattened_idx += (sparse_indices[indices[0]][k] * multipliers[k]); + } + } + Array new_sparse_indices; + if (GetConstInt(new_shape->shape[0]) != 1) { + for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { + new_sparse_indices.push_back(floordiv(flattened_idx, dividers[i])); + flattened_idx = floormod(flattened_idx, dividers[i]); + } + PrimExpr ret = -1; + + for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { + // auto ret = tir::Select(indices[1] == i, new_sparse_indices[i], + // -1); + if (indices.size() == 1) { + return new_sparse_indices[0]; + } else { + ret = if_then_else(indices[1] == i, new_sparse_indices[i], ret); + // PrimExpr cond = (ret == -1); + if (const IntImmNode* op = ret.as()) { + if (op->value == -1) { + continue; + } else { + break; + } + } + } + } + return ret; + } else { + return flattened_idx; + } + }, + name, tag)); + result.push_back(compute(sparse_values->shape, + [&](const Array& i) { return (sparse_values(i)); }, name, tag)); return result; } // namespace topi /*! @@ -1585,14 +1516,13 @@ inline Tensor layout_transform(const Tensor& src, const std::string& src_layout, Array dst_shape = layout_converter.ForwardShape(src->shape); - return compute( - dst_shape, - [&](const Array& dst_indices) { - Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); - Array src_indices = layout_converter.BackwardIndex(dst_indices_expr); - return src(src_indices); - }, - name, tag); + return compute(dst_shape, + [&](const Array& dst_indices) { + Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); + Array src_indices = layout_converter.BackwardIndex(dst_indices_expr); + return src(src_indices); + }, + name, tag); } /*! \brief Utility function for auto_scheduler_layout_transform */ @@ -1643,24 +1573,23 @@ inline Tensor auto_scheduler_layout_transform(const Tensor& src, const String& s parse_auto_scheduler_layout(src_layout, &src_shape, &src_axes); parse_auto_scheduler_layout(dst_layout, &dst_shape, &dst_axes); - return compute( - dst_shape, - [&](const Array& dst_indices) { - Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); - Array src_indices; - for (const std::string& src_axis : src_axes) { - PrimExpr src_index = 0; - CHECK_EQ(dst_indices_expr.size(), dst_axes.size()); - for (size_t i = 0; i < dst_axes.size(); ++i) { - if (dst_axes[i] == src_axis) { - src_index = src_index * dst_shape[i] + dst_indices_expr[i]; - } - } - src_indices.push_back(src_index); - } - return src(src_indices); - }, - name, tag); + return compute(dst_shape, + [&](const Array& dst_indices) { + Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); + Array src_indices; + for (const std::string& src_axis : src_axes) { + PrimExpr src_index = 0; + CHECK_EQ(dst_indices_expr.size(), dst_axes.size()); + for (size_t i = 0; i < dst_axes.size(); ++i) { + if (dst_axes[i] == src_axis) { + src_index = src_index * dst_shape[i] + dst_indices_expr[i]; + } + } + src_indices.push_back(src_index); + } + return src(src_indices); + }, + name, tag); } /*! @@ -1675,17 +1604,16 @@ inline Tensor shape(const Tensor& src, DataType dtype, const std::string name = const std::string tag = kInjective) { int ndim = static_cast(src->shape.size()); Array out_shape{ndim}; - return compute( - out_shape, - [&](const Array& indices) { - auto idx = indices[0]; - PrimExpr ret = 0; - for (int i = 0; i < ndim; ++i) { - ret = tvm::if_then_else(idx == i, src->shape[i], ret); - } - return tvm::cast(dtype, ret); - }, - name, tag); + return compute(out_shape, + [&](const Array& indices) { + auto idx = indices[0]; + PrimExpr ret = 0; + for (int i = 0; i < ndim; ++i) { + ret = tvm::if_then_else(idx == i, src->shape[i], ret); + } + return tvm::cast(dtype, ret); + }, + name, tag); } /*! @@ -1701,16 +1629,15 @@ inline Tensor ndarray_size(const Tensor& src, const DataType& dtype, const std::string& tag = kInjective) { int ndim = static_cast(src->shape.size()); Array out_ndarray_size = {}; - return compute( - out_ndarray_size, - [&](const Array& indices) { - PrimExpr ret = 1; - for (int i = 0; i < ndim; ++i) { - ret *= src->shape[i]; - } - return tvm::cast(dtype, ret); - }, - name, tag); + return compute(out_ndarray_size, + [&](const Array& indices) { + PrimExpr ret = 1; + for (int i = 0; i < ndim; ++i) { + ret *= src->shape[i]; + } + return tvm::cast(dtype, ret); + }, + name, tag); } /*! @@ -1746,22 +1673,22 @@ inline Tensor one_hot(const Tensor& indices, const PrimExpr on_value, const Prim PrimExpr on_value_cast = cast(dtype, on_value); PrimExpr off_value_cast = cast(dtype, off_value); - return compute( - oshape, - [&](const Array& iter_vars) { - Array indices_indices; - for (size_t i = 0; i < iter_vars.size(); i++) { - if (static_cast(i) == true_axis) { - continue; - } - - indices_indices.push_back(iter_vars[i]); - } - - auto idx = iter_vars[true_axis]; - return tir::Select(indices(indices_indices) == idx, on_value_cast, off_value_cast); - }, - name, tag); + return compute(oshape, + [&](const Array& iter_vars) { + Array indices_indices; + for (size_t i = 0; i < iter_vars.size(); i++) { + if (static_cast(i) == true_axis) { + continue; + } + + indices_indices.push_back(iter_vars[i]); + } + + auto idx = iter_vars[true_axis]; + return tir::Select(indices(indices_indices) == idx, on_value_cast, + off_value_cast); + }, + name, tag); } /*! @@ -1788,29 +1715,29 @@ inline Tensor sparse_to_dense(const Tensor& sparse_indices, const Array& indices) { - PrimExpr ret = default_value; - if (0 == rank_sparse_indices) { - ret = if_then_else(indices[0] == sparse_indices[0], sparse_values[0], ret); - } else if (1 == rank_sparse_indices) { - for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { - ret = if_then_else(indices[0] == sparse_indices[j], sparse_values[j], ret); - } - } else { - for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { - PrimExpr aggregate_condition; - for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { - PrimExpr comparision = indices[k] == sparse_indices[j][k]; - aggregate_condition = 0 == k ? comparision : aggregate_condition && comparision; - } - ret = if_then_else(aggregate_condition, sparse_values[j], ret); - } - } - return ret; - }, - name, tag); + return compute(oshape, + [&](const Array& indices) { + PrimExpr ret = default_value; + if (0 == rank_sparse_indices) { + ret = if_then_else(indices[0] == sparse_indices[0], sparse_values[0], ret); + } else if (1 == rank_sparse_indices) { + for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { + ret = if_then_else(indices[0] == sparse_indices[j], sparse_values[j], ret); + } + } else { + for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { + PrimExpr aggregate_condition; + for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { + PrimExpr comparision = indices[k] == sparse_indices[j][k]; + aggregate_condition = + 0 == k ? comparision : aggregate_condition && comparision; + } + ret = if_then_else(aggregate_condition, sparse_values[j], ret); + } + } + return ret; + }, + name, tag); } /*! @@ -1931,25 +1858,24 @@ inline Tensor adv_index(const Tensor& data, const Array& indices, oshape.push_back(data->shape[i]); } - return compute( - oshape, - [&](const Array& iter_var) { - Array tensor_indices; - for (size_t i = 0; i < broadcast_shape.size(); ++i) { - tensor_indices.push_back(iter_var[i]); - } - - Array real_indices; - for (size_t i = 0; i < bindices.size(); ++i) { - real_indices.push_back(bindices[i](tensor_indices)); - } - for (size_t i = broadcast_shape.size(); i < iter_var.size(); ++i) { - real_indices.push_back(iter_var[i]); - } - - return data(real_indices); - }, - name, tag); + return compute(oshape, + [&](const Array& iter_var) { + Array tensor_indices; + for (size_t i = 0; i < broadcast_shape.size(); ++i) { + tensor_indices.push_back(iter_var[i]); + } + + Array real_indices; + for (size_t i = 0; i < bindices.size(); ++i) { + real_indices.push_back(bindices[i](tensor_indices)); + } + for (size_t i = broadcast_shape.size(); i < iter_var.size(); ++i) { + real_indices.push_back(iter_var[i]); + } + + return data(real_indices); + }, + name, tag); } } // namespace topi diff --git a/tests/python/relay/test_op_level3.py b/tests/python/relay/test_op_level3.py index de4265a5a5b0..d32d3743f583 100644 --- a/tests/python/relay/test_op_level3.py +++ b/tests/python/relay/test_op_level3.py @@ -1048,7 +1048,15 @@ def ref_sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_ new_sparse_indices = -1 * np.ones( (sparse_indices.shape[0] + dense_shape[0], sparse_indices.shape[1]) ) + empty_row_indicator = np.ones(dense_shape[0], dtype=bool) + for i in range(sparse_indices.shape[0]): + if len(sparse_indices.shape) == 1: + element = sparse_indices[i] + else: + element = sparse_indices[i][0] + + empty_row_indicator[element] = False return new_sparse_indices, empty_row_indicator @@ -1075,6 +1083,8 @@ def verify_sparsefillemptyrows( sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np ) for target, ctx in tvm.testing.enabled_targets(): + if target == "nvptx": + continue for kind in ["graph", "debug"]: intrp = relay.create_executor(kind, ctx=ctx, target=target) op_res = intrp.evaluate(func)(sparse_indices_np, sparse_values_np, default_value_np) @@ -1472,6 +1482,9 @@ def verify_adv_index(data_shape, index_shapes): if __name__ == "__main__": test_sparsefillemptyrows() + import sys + + sys.exit() test_sparsereshape() test_cast() test_zeros_ones() From ba8bfed667e741a2bbded0861f13126768d68711 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 18 Dec 2020 20:37:00 +0000 Subject: [PATCH 19/44] Remove SparseReshape --- include/tvm/topi/transform.h | 72 ----------------- python/tvm/relay/op/_transform.py | 2 - python/tvm/relay/op/transform.py | 5 -- python/tvm/topi/transform.py | 19 ----- src/relay/op/tensor/transform.cc | 74 ++++-------------- tests/python/relay/test_op_level3.py | 113 --------------------------- 6 files changed, 15 insertions(+), 270 deletions(-) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index b47a9860547b..d83066dc4275 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -1415,78 +1415,6 @@ inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Ten result.push_back(empty_row_indicator); return result; } - -inline Array SparseReshape(const Tensor& sparse_indices, const Tensor& sparse_values, - const Tensor& prev_shape, const Tensor& new_shape, - const std::string name = "T_sparsereshape", - std::string tag = kInjective) { - Array result; - Array new_sparse_indices_shape{sparse_indices->shape[0], new_shape->shape[0]}; - std::vector multipliers(GetConstInt(prev_shape->shape[0]), 1); - std::vector dividers(GetConstInt(new_shape->shape[0]), 1); - - tvm::te::compute(Array{1}, [&](const Array& indices) { - tvm::PrimExpr total_ele = prev_shape[0]; - for (int i = GetConstInt(prev_shape->shape[0]) - 2; i >= 0; --i) { - multipliers[i] = prev_shape[i + 1] * multipliers[i + 1]; - total_ele *= prev_shape[i + 1]; - } - PrimExpr division_total_ele = 1; - for (int i = 0; i < GetConstInt(new_shape->shape[0]); ++i) { - division_total_ele *= if_then_else(new_shape[i] != -1, new_shape[i], 1); - } - for (int i = GetConstInt(new_shape->shape[0]) - 2; i >= 0; --i) { - dividers[i] = dividers[i + 1] * if_then_else(new_shape[i + 1] != -1, new_shape[i + 1], - div(total_ele, division_total_ele)); - } - return PrimExpr(1); - }); - - result.push_back(compute(new_sparse_indices_shape, - [&](const Array& indices) { - PrimExpr flattened_idx = 0; - if (sparse_indices->shape.size() == 1) { - flattened_idx += sparse_indices[indices[0]]; - } else { - for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { - flattened_idx += (sparse_indices[indices[0]][k] * multipliers[k]); - } - } - Array new_sparse_indices; - if (GetConstInt(new_shape->shape[0]) != 1) { - for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { - new_sparse_indices.push_back(floordiv(flattened_idx, dividers[i])); - flattened_idx = floormod(flattened_idx, dividers[i]); - } - PrimExpr ret = -1; - - for (int i = 0; i < GetConstInt(new_shape->shape[0]); i++) { - // auto ret = tir::Select(indices[1] == i, new_sparse_indices[i], - // -1); - if (indices.size() == 1) { - return new_sparse_indices[0]; - } else { - ret = if_then_else(indices[1] == i, new_sparse_indices[i], ret); - // PrimExpr cond = (ret == -1); - if (const IntImmNode* op = ret.as()) { - if (op->value == -1) { - continue; - } else { - break; - } - } - } - } - return ret; - } else { - return flattened_idx; - } - }, - name, tag)); - result.push_back(compute(sparse_values->shape, - [&](const Array& i) { return (sparse_values(i)); }, name, tag)); - return result; -} // namespace topi /*! * \brief Transform the layout according to \p src_layout and \p dst_layout * \param src the source input. diff --git a/python/tvm/relay/op/_transform.py b/python/tvm/relay/op/_transform.py index 4f31dbde152a..aa816997b51a 100644 --- a/python/tvm/relay/op/_transform.py +++ b/python/tvm/relay/op/_transform.py @@ -64,8 +64,6 @@ _reg.register_injective_schedule("matrix_set_diag") _reg.register_injective_schedule("adv_index") _reg.register_injective_schedule("sparsefillemptyrows") -_reg.register_injective_schedule("sparsereshape") - # concatenate _reg.register_schedule("concatenate", strategy.schedule_concatenate) diff --git a/python/tvm/relay/op/transform.py b/python/tvm/relay/op/transform.py index 5b4573e1d826..0838c4ea1a72 100644 --- a/python/tvm/relay/op/transform.py +++ b/python/tvm/relay/op/transform.py @@ -1325,8 +1325,3 @@ def adv_index(inputs): def sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value): return _make.sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value) - - -def sparsereshape(sparse_indices, sparse_values, prev_shape, new_shape): - - return _make.sparsereshape(sparse_indices, sparse_values, prev_shape, new_shape) \ No newline at end of file diff --git a/python/tvm/topi/transform.py b/python/tvm/topi/transform.py index 1ff5a8096ba4..c7ea833fe8db 100644 --- a/python/tvm/topi/transform.py +++ b/python/tvm/topi/transform.py @@ -950,22 +950,3 @@ def sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_shap Output tensor """ return cpp.sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_shape) - - -def sparsereshape(sparse_indices, sparse_values, prev_shape, new_shape): - """Numpy style indexing with tensors. - - Parameters - ---------- - data : tvm.te.Tensor - Input data. - - indices : A list of tvm.te.Tensor - Tensor index. - - Returns - ------- - result : tvm.te.Tensor - Output tensor - """ - return cpp.sparsereshape(sparse_indices, sparse_values, prev_shape, new_shape) \ No newline at end of file diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index e87b7fb150ab..bd191d739994 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1339,12 +1339,11 @@ inline te::Tensor DynamicArange(const te::Tensor& start, const te::Tensor& stop, std::string name = "T_arange_dynamic", std::string tag = topi::kInjective) { tvm::PrimExpr num_elem = tvm::tir::Var("num_elem"); - return te::compute( - {num_elem}, - [&](const Array& indices) { - return tvm::cast(dtype, start[0] + step[0] * indices[0]); - }, - name, tag); + return te::compute({num_elem}, + [&](const Array& indices) { + return tvm::cast(dtype, start[0] + step[0] * indices[0]); + }, + name, tag); } Array ArangeCompute(const Attrs& attrs, const Array& inputs, @@ -1553,49 +1552,6 @@ RELAY_REGISTER_OP("meshgrid") .set_attr("FTVMCompute", MeshgridCompute) .set_attr("TOpPattern", kInjective); -bool SparseReshapeRel(const Array& types, int num_inputs, const Attrs& raw_attrs, - const TypeReporter& reporter) { - // types: [sparse_indices, sparse_values, prev_shape, new_shape, result] - ICHECK_EQ(types.size(), 5); - - std::vector fields; - auto sparse_indices = types[0].as(); - auto sparse_values = types[1].as(); - auto new_shape = types[3].as(); - - Array new_sparse_indices_shape{sparse_indices->shape[0], new_shape->shape[0]}; - fields.push_back(TensorType(new_sparse_indices_shape, sparse_indices->dtype)); - fields.push_back(TensorType(sparse_values->shape, sparse_values->dtype)); - - reporter->Assign(types[4], TupleType(Array(fields))); - return true; -} - -Array SparseReshapeCompute(const Attrs& attrs, const Array& inputs, - const Type& out_type) { - return {topi::SparseReshape(inputs[0], inputs[1], inputs[2], inputs[3])}; -} - -Expr MakeSparseReshape(Expr sparse_indices, Expr sparse_values, Expr prev_shape, Expr new_shape) { - static const Op& op = Op::Get("sparsereshape"); - return Call(op, {sparse_indices, sparse_values, prev_shape, new_shape}, Attrs(), {}); -} - -TVM_REGISTER_GLOBAL("relay.op._make.sparsereshape").set_body_typed(MakeSparseReshape); - -RELAY_REGISTER_OP("sparsereshape") - .describe(R"code(Return twice of normal addition of two tensors. -)code" TVM_ADD_FILELINE) - .set_num_inputs(4) - .add_argument("sparse_indices", "Tensor", "The first tensor") - .add_argument("sparse_values", "Tensor", "The second tensor") - .add_argument("prev_shape", "Tensor", "The third tensor") - .add_argument("new_shape", "Tensor", "The fourth tensor") - .add_type_rel("sparsereshape", SparseReshapeRel) - .set_attr("TOpPattern", kInjective) - .set_support_level(3) - .set_attr("FTVMCompute", SparseReshapeCompute); - TVM_REGISTER_NODE_TYPE(SparseFillEmptyRowsAttrs); bool SparseFillEmptyRowsRel(const Array& types, int num_inputs, const Attrs& attrs, @@ -2501,16 +2457,16 @@ Array StridedSliceCompute(const Attrs& attrs, const Arrayvalue : 1))); } - return Array{te::compute( - out_shape, - [&](const Array& indices) { - Array real_indices; - for (size_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); - } - return input(real_indices); - }, - std::string{"T_strided_slice_dynamic"}, std::string{topi::kInjective})}; + return Array{ + te::compute(out_shape, + [&](const Array& indices) { + Array real_indices; + for (size_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); + } + return input(real_indices); + }, + std::string{"T_strided_slice_dynamic"}, std::string{topi::kInjective})}; } else { for (size_t i = 0; i < begin.size(); ++i) { begin_expr.push_back(begin[i]); diff --git a/tests/python/relay/test_op_level3.py b/tests/python/relay/test_op_level3.py index d32d3743f583..dc1d41c94be4 100644 --- a/tests/python/relay/test_op_level3.py +++ b/tests/python/relay/test_op_level3.py @@ -1101,115 +1101,6 @@ def verify_sparsefillemptyrows( ) -@tvm.testing.uses_gpu -def test_sparsereshape(): - def ref_sparsereshape(sparse_indices, sparse_values, prev_shape, new_shape): - # sparse_indices[0][0] = 1 - new_sparse_indices = np.ones( - (sparse_values.shape[0], new_shape.shape[0]), dtype=sparse_indices.dtype - ) - multipliers = np.ones(prev_shape.shape[0]) - dividers = np.ones(new_shape.shape[0]) - total_ele = np.prod(prev_shape) - division_total_ele = 1 - for i in range(new_shape.shape[0]): - if new_shape[i] == -1: - continue - division_total_ele *= new_shape[i] - for i in range(prev_shape.shape[0] - 2, -1, -1): - multipliers[i] = prev_shape[i + 1] * multipliers[i + 1] - for i in range(new_shape.shape[0] - 2, -1, -1): - if new_shape[i + 1] == -1: - dividers[i] = (total_ele // division_total_ele) * dividers[i + 1] - else: - dividers[i] = new_shape[i + 1] * dividers[i + 1] - for row_num, sparse_row in enumerate(sparse_indices): - flat_idx = 0 - if len(sparse_indices.shape) != 1: - for i, ele in enumerate(sparse_row): - flat_idx += sparse_row[i] * multipliers[i] - else: - flat_idx += sparse_row - if len(new_sparse_indices.shape) != 1: - for i in range(new_sparse_indices.shape[1]): - new_sparse_indices[row_num][i] = flat_idx // dividers[i] - flat_idx = flat_idx % dividers[i] - else: - new_sparse_indices[row_num] = flat_idx - - return ( - new_sparse_indices, - sparse_values, - ) - - def verify_sparsereshape(sparse_indices_np, sparse_values_np, dense_shape_np, default_value_np): - sparse_indices = relay.var( - "sparse_indices", - relay.TensorType(sparse_indices_np.shape, str(sparse_indices_np.dtype)), - ) - sparse_values = relay.var( - "sparse_values", relay.TensorType(sparse_values_np.shape, str(sparse_values_np.dtype)) - ) - dense_shape = relay.var( - "dense_shape", relay.TensorType(dense_shape_np.shape, str(dense_shape_np.dtype)) - ) - default_value = relay.var( - "default_value", relay.TensorType(default_value_np.shape, str(default_value_np.dtype)) - ) - z = relay.op.sparsereshape(sparse_indices, sparse_values, dense_shape, default_value) - - func = relay.Function([sparse_indices, sparse_values, dense_shape, default_value], z) - - ref_res = ref_sparsereshape( - sparse_indices_np, sparse_values_np, dense_shape_np, default_value_np - ) - for target, ctx in tvm.testing.enabled_targets(): - for kind in ["graph", "debug"]: - intrp = relay.create_executor(kind, ctx=ctx, target=target) - op_res = intrp.evaluate(func)( - sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np - ) - for op_res_item, ref_res_item in zip(op_res, ref_res): - print(f"Op Res: {op_res_item}, Ref Res: {ref_res_item}") - tvm.testing.assert_allclose( - op_res_item.asnumpy(), ref_res_item, rtol=1e-5, atol=1e-5 - ) - - sparse_indices_np = np.array( - [[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 2, 3]], dtype=np.int32 - ) - sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) - prev_shape_np = np.array([2, 3, 6], dtype=np.int32) - new_shape_np = np.array([9, 4], dtype=np.int32) - verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) - - sparse_indices_np = np.array( - [[0, 0, 0, 0], [0, 0, 1, 2], [0, 1, 0, 3], [1, 0, 0, 4], [1, 2, 3, 6]], dtype=np.int32 - ) - sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) - prev_shape_np = np.array([2, 3, 6, 7], dtype=np.int32) - new_shape_np = np.array([9, -1, 7], dtype=np.int32) - verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) - - sparse_indices_np = np.array([[0, 0], [0, 1], [3, 4], [4, 3], [7, 3]], dtype=np.int32) - sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) - prev_shape_np = np.array([9, 4], dtype=np.int32) - new_shape_np = np.array([2, -1, 6], dtype=np.int32) - verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) - - sparse_indices_np = np.array([[0, 0], [0, 1], [3, 4], [4, 3], [7, 3]], dtype=np.int32) - sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) - prev_shape_np = np.array([9, 4], dtype=np.int32) - new_shape_np = np.array([-1], dtype=np.int32) - verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) - - sparse_indices_np = np.array([0, 5, 10, 20, 24], dtype=np.int32) - sparse_values_np = np.array([7, 5, 6, 3, 9], dtype=np.int32) - prev_shape_np = np.array([25], dtype=np.int32) - new_shape_np = np.array([5, 5], dtype=np.int32) - verify_sparsereshape(sparse_indices_np, sparse_values_np, prev_shape_np, new_shape_np) - - @tvm.testing.uses_gpu def test_gather(): def verify_gather(data, axis, indices, ref_res): @@ -1482,10 +1373,6 @@ def verify_adv_index(data_shape, index_shapes): if __name__ == "__main__": test_sparsefillemptyrows() - import sys - - sys.exit() - test_sparsereshape() test_cast() test_zeros_ones() test_unary_identity() From 0ce98efa01a239d7d16f811230cc8aa174021839 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 18 Dec 2020 21:55:32 +0000 Subject: [PATCH 20/44] Clang --- include/tvm/topi/transform.h | 756 ++++++++++++++++++----------------- 1 file changed, 390 insertions(+), 366 deletions(-) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index d83066dc4275..113f7b21f85f 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -82,18 +82,19 @@ inline Tensor expand_dims(const Tensor& x, int axis, int num_newaxis = 1, new_shape.push_back(x->shape[i]); } - return compute(new_shape, - [&](const Array& indices) { - Array idx; - for (size_t i = 0; i < static_cast(axis); ++i) { - idx.push_back(indices[i]); - } - for (size_t i = axis + num_newaxis; i < indices.size(); ++i) { - idx.push_back(indices[i]); - } - return x(idx); - }, - name, tag); + return compute( + new_shape, + [&](const Array& indices) { + Array idx; + for (size_t i = 0; i < static_cast(axis); ++i) { + idx.push_back(indices[i]); + } + for (size_t i = axis + num_newaxis; i < indices.size(); ++i) { + idx.push_back(indices[i]); + } + return x(idx); + }, + name, tag); } /*! @@ -136,19 +137,20 @@ inline Tensor transpose(const Tensor& x, Array axes, std::string name = new_shape.push_back(x->shape[new_axis]); } - return compute(new_shape, - [&](const Array& indices) { - std::vector idx; - for (size_t i = 0; i < axes.size(); ++i) { - idx.push_back(1); - } - for (size_t i = 0; i < axes.size(); ++i) { - int axis = static_cast(axes[i]->value); - idx[axis] = indices[i]; - } - return x(idx); - }, - name, tag); + return compute( + new_shape, + [&](const Array& indices) { + std::vector idx; + for (size_t i = 0; i < axes.size(); ++i) { + idx.push_back(1); + } + for (size_t i = 0; i < axes.size(); ++i) { + int axis = static_cast(axes[i]->value); + idx[axis] = indices[i]; + } + return x(idx); + }, + name, tag); } /*! @@ -244,8 +246,8 @@ inline Tensor reshape(const Tensor& x, Array newshape, std::string nam } if (is_empty_shape(target_shape)) { - return compute(target_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, - name, tag); + return compute( + target_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, name, tag); } else { return compute( target_shape, @@ -351,21 +353,22 @@ inline Tensor squeeze(const Tensor& x, Array axis, bool atleast1d = fal out_shape.push_back(1); } - return compute(out_shape, - [&](const Array& indices) { - Array real_indices; - int flag = 0; - for (size_t i = 0; i < ndim; ++i) { - if (axis_set.count(static_cast(i)) == 0) { - real_indices.push_back(indices[i - flag]); - } else { - real_indices.push_back(0); - flag += 1; - } - } - return x(real_indices); - }, - name, tag); + return compute( + out_shape, + [&](const Array& indices) { + Array real_indices; + int flag = 0; + for (size_t i = 0; i < ndim; ++i) { + if (axis_set.count(static_cast(i)) == 0) { + real_indices.push_back(indices[i - flag]); + } else { + real_indices.push_back(0); + flag += 1; + } + } + return x(real_indices); + }, + name, tag); } /*! @@ -403,27 +406,28 @@ inline Tensor concatenate(const Array& inputs, int axis = 0, std::string out_shape.push_back(i == static_cast(axis) ? join_size : inputs[0]->shape[i]); } - return compute(out_shape, - [&](const Array& indices) { - auto ret = inputs[0](indices); - auto ind = indices[axis]; - for (size_t i = 0; i < inputs.size() - 1; ++i) { - ind -= axis_sizes[i]; - - Array idx; - for (size_t i = 0; i < static_cast(axis); ++i) { - idx.push_back(indices[i]); - } - idx.push_back(ind); - for (size_t i = axis + 1; i < indices.size(); ++i) { - idx.push_back(indices[i]); - } - - ret = tvm::if_then_else(ind >= 0, inputs[i + 1](idx), ret); - } - return ret; - }, - name, tag); + return compute( + out_shape, + [&](const Array& indices) { + auto ret = inputs[0](indices); + auto ind = indices[axis]; + for (size_t i = 0; i < inputs.size() - 1; ++i) { + ind -= axis_sizes[i]; + + Array idx; + for (size_t i = 0; i < static_cast(axis); ++i) { + idx.push_back(indices[i]); + } + idx.push_back(ind); + for (size_t i = axis + 1; i < indices.size(); ++i) { + idx.push_back(indices[i]); + } + + ret = tvm::if_then_else(ind >= 0, inputs[i + 1](idx), ret); + } + return ret; + }, + name, tag); } /*! @@ -454,19 +458,20 @@ inline Tensor stack(const Array& inputs, int axis = 0, std::string name for (size_t i = static_cast(axis); i < static_cast(ndim); ++i) out_shape.push_back(inputs[0]->shape[i]); - return compute(out_shape, - [&](const Array& indices) { - Array idx; - for (size_t i = 0; i < indices.size(); ++i) - if (i != static_cast(axis)) idx.push_back(indices[i]); - auto ind = indices[axis]; - auto ret = inputs[0](idx); - for (int i = 0; i < static_cast(inputs.size() - 1); ++i) { - ret = tvm::if_then_else(ind == i + 1, inputs[i + 1](idx), ret); - } - return ret; - }, - name, tag); + return compute( + out_shape, + [&](const Array& indices) { + Array idx; + for (size_t i = 0; i < indices.size(); ++i) + if (i != static_cast(axis)) idx.push_back(indices[i]); + auto ind = indices[axis]; + auto ret = inputs[0](idx); + for (int i = 0; i < static_cast(inputs.size() - 1); ++i) { + ret = tvm::if_then_else(ind == i + 1, inputs[i + 1](idx), ret); + } + return ret; + }, + name, tag); } /*! @@ -501,7 +506,7 @@ inline Array split(const Tensor& x, Array split_indices, int a begin_ids.push_back(idx); } - Array> out_shapes; + Array > out_shapes; for (size_t i = 0; i < begin_ids.size(); ++i) { PrimExpr out_axis_size; if (i == begin_ids.size() - 1) { @@ -524,21 +529,22 @@ inline Array split(const Tensor& x, Array split_indices, int a Array result; for (size_t i = 0; i < begin_ids.size(); ++i) { - result.push_back(compute(out_shapes[i], - [&](const Array& indices) { - auto begin = begin_ids[i]; - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(indices[j]); - } - real_indices.push_back(indices[axis] + begin); - for (size_t j = axis + 1; j < indices.size(); ++j) { - real_indices.push_back(indices[j]); - } - - return x(real_indices); - }, - name, tag)); + result.push_back(compute( + out_shapes[i], + [&](const Array& indices) { + auto begin = begin_ids[i]; + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(indices[j]); + } + real_indices.push_back(indices[axis] + begin); + for (size_t j = axis + 1; j < indices.size(); ++j) { + real_indices.push_back(indices[j]); + } + + return x(real_indices); + }, + name, tag)); } return result; @@ -566,15 +572,16 @@ inline te::Tensor dynamic_strided_slice(const te::Tensor& x, const te::Tensor& b for (int64_t i = 0; i < src_tensor_dim; ++i) { out_shape.push_back(tvm::tir::Var("dim")); } - return te::compute(out_shape, - [&](const Array& indices) { - Array real_indices; - for (int32_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides(i) + begin(i)); - } - return x(real_indices); - }, - name, tag); + return te::compute( + out_shape, + [&](const Array& indices) { + Array real_indices; + for (int32_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides(i) + begin(i)); + } + return x(real_indices); + }, + name, tag); } /*! @@ -608,15 +615,16 @@ inline Tensor strided_slice(const Tensor& x, const Array& begin, for (size_t i = 0; i < src_tensor_dim; ++i) { out_shape.push_back(indexdiv(end[i] - begin[i], strides[i])); } - return te::compute(out_shape, - [&](const Array& indices) { - Array real_indices; - for (size_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides[i] + begin[i]); - } - return x(real_indices); - }, - name, tag); + return te::compute( + out_shape, + [&](const Array& indices) { + Array real_indices; + for (size_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides[i] + begin[i]); + } + return x(real_indices); + }, + name, tag); } // Setup the ranges. @@ -695,15 +703,16 @@ inline Tensor strided_slice(const Tensor& x, const Array& begin, out_shape.push_back(slice_size); } - return compute(out_shape, - [&](const Array& indices) { - Array real_indices; - for (size_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); - } - return x(real_indices); - }, - name, tag); + return compute( + out_shape, + [&](const Array& indices) { + Array real_indices; + for (size_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); + } + return x(real_indices); + }, + name, tag); } /*! @@ -770,12 +779,13 @@ inline Tensor take(const Tensor& a, const Tensor& indices, std::string mode = "c } if (mode == "clip") { - return compute(out_shape, - [&](const Array& out_index) { - auto idx = tvm::min(tvm::max(0, indices(out_index)), a_size - 1); - return a(UnravelIndex(idx, a_shape)); - }, - name, tag); + return compute( + out_shape, + [&](const Array& out_index) { + auto idx = tvm::min(tvm::max(0, indices(out_index)), a_size - 1); + return a(UnravelIndex(idx, a_shape)); + }, + name, tag); } else if (mode == "fast") { LOG(WARNING) << "Fast mode segfaults when there are out-of-bounds indices. " "Make sure input indices are in bound"; @@ -784,12 +794,13 @@ inline Tensor take(const Tensor& a, const Tensor& indices, std::string mode = "c [&](const Array& out_index) { return a(UnravelIndex(indices(out_index), a_shape)); }, name, tag); } else { // mode == "wrap" - return compute(out_shape, - [&](const Array& out_index) { - auto idx = truncmod(truncmod(indices(out_index), a_size) + a_size, a_size); - return a(UnravelIndex(idx, a_shape)); - }, - name, tag); + return compute( + out_shape, + [&](const Array& out_index) { + auto idx = truncmod(truncmod(indices(out_index), a_size) + a_size, a_size); + return a(UnravelIndex(idx, a_shape)); + }, + name, tag); } } @@ -813,18 +824,19 @@ inline Tensor sequence_mask(const Tensor& data, const Tensor& valid_length, doub auto length_dim = data->shape[axis]; auto batch_dim = data->shape[1 - axis]; Array out_shape = data->shape; - Tensor out = compute(out_shape, - [&](const Array& out_index) { - Array len_index; - auto tid = out_index[axis]; - auto bid = out_index[1 - axis]; - len_index.push_back(bid); - PrimExpr ret = tvm::if_then_else( - tvm::cast(valid_length->dtype, tid) >= valid_length(len_index), - tvm::tir::make_const(data->dtype, mask_value), data(out_index)); - return ret; - }, - name, tag); + Tensor out = compute( + out_shape, + [&](const Array& out_index) { + Array len_index; + auto tid = out_index[axis]; + auto bid = out_index[1 - axis]; + len_index.push_back(bid); + PrimExpr ret = + tvm::if_then_else(tvm::cast(valid_length->dtype, tid) >= valid_length(len_index), + tvm::tir::make_const(data->dtype, mask_value), data(out_index)); + return ret; + }, + name, tag); return out; } @@ -862,64 +874,66 @@ inline Tensor take(const Tensor& a, const Tensor& indices, int axis, std::string } } if (mode == "clip") { - return compute(out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { - indices_position.push_back(out_index[j]); - } - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(out_index[j]); - } - auto idx = tvm::min(tvm::max(0, indices(indices_position)), axis_dim - 1); - real_indices.push_back(idx); - for (size_t j = axis + indices_len; j < out_index.size(); ++j) { - real_indices.push_back(out_index[j]); - } - return a(real_indices); - }, - name, tag); + return compute( + out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { + indices_position.push_back(out_index[j]); + } + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(out_index[j]); + } + auto idx = tvm::min(tvm::max(0, indices(indices_position)), axis_dim - 1); + real_indices.push_back(idx); + for (size_t j = axis + indices_len; j < out_index.size(); ++j) { + real_indices.push_back(out_index[j]); + } + return a(real_indices); + }, + name, tag); } else if (mode == "fast") { LOG(WARNING) << "Fast mode segfaults when there are out-of-bounds indices. " "Make sure input indices are in bound"; - return compute(out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { - indices_position.push_back(out_index[j]); - } - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(out_index[j]); - } - real_indices.push_back(indices(indices_position)); - for (size_t j = axis + indices_len; j < out_index.size(); ++j) { - real_indices.push_back(out_index[j]); - } - return a(real_indices); - }, - name, tag); + return compute( + out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { + indices_position.push_back(out_index[j]); + } + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(out_index[j]); + } + real_indices.push_back(indices(indices_position)); + for (size_t j = axis + indices_len; j < out_index.size(); ++j) { + real_indices.push_back(out_index[j]); + } + return a(real_indices); + }, + name, tag); } else { // mode == "wrap" - return compute(out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { - indices_position.push_back(out_index[j]); - } - Array real_indices; - for (size_t j = 0; j < static_cast(axis); ++j) { - real_indices.push_back(out_index[j]); - } - auto idx = truncmod(truncmod(indices(indices_position), axis_dim) + axis_dim, - axis_dim); - real_indices.push_back(idx); - for (size_t j = axis + indices_len; j < out_index.size(); ++j) { - real_indices.push_back(out_index[j]); - } - return a(real_indices); - }, - name, tag); + return compute( + out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t j = axis; j < static_cast(axis + indices_len); ++j) { + indices_position.push_back(out_index[j]); + } + Array real_indices; + for (size_t j = 0; j < static_cast(axis); ++j) { + real_indices.push_back(out_index[j]); + } + auto idx = truncmod(truncmod(indices(indices_position), axis_dim) + axis_dim, axis_dim); + real_indices.push_back(idx); + for (size_t j = axis + indices_len; j < out_index.size(); ++j) { + real_indices.push_back(out_index[j]); + } + return a(real_indices); + }, + name, tag); } } @@ -995,19 +1009,20 @@ inline Tensor repeat(const Tensor& x, int repeats, int axis, std::string name = new_shape.push_back(x->shape[i]); } - return compute(new_shape, - [&](const Array& indices) { - Array idx; - for (size_t i = 0; i < static_cast(axis); ++i) { - idx.push_back(indices[i]); - } - idx.push_back(indexdiv(indices[axis], repeats)); - for (size_t i = axis + 1; i < indices.size(); ++i) { - idx.push_back(indices[i]); - } - return x(idx); - }, - name, tag); + return compute( + new_shape, + [&](const Array& indices) { + Array idx; + for (size_t i = 0; i < static_cast(axis); ++i) { + idx.push_back(indices[i]); + } + idx.push_back(indexdiv(indices[axis], repeats)); + for (size_t i = axis + 1; i < indices.size(); ++i) { + idx.push_back(indices[i]); + } + return x(idx); + }, + name, tag); } /*! @@ -1045,22 +1060,22 @@ inline Tensor tile(const Tensor& x, Array reps, std::string name = "T_t for (size_t i = 0; i < tdim; ++i) new_shape.push_back(data_shape[i] * reps_shape[i]); if (is_empty_shape(new_shape)) { - return compute(new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, - name, tag); + return compute( + new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, name, tag); } else { - return compute(new_shape, - [&](const Array& indices) { - Array idx; - if (ndim >= rdim) { - for (size_t i = 0; i < ndim; ++i) - idx.push_back(indexmod(indices[i], x->shape[i])); - } else { - for (size_t i = 0; i < ndim; ++i) - idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); - } - return x(idx); - }, - name, tag); + return compute( + new_shape, + [&](const Array& indices) { + Array idx; + if (ndim >= rdim) { + for (size_t i = 0; i < ndim; ++i) idx.push_back(indexmod(indices[i], x->shape[i])); + } else { + for (size_t i = 0; i < ndim; ++i) + idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); + } + return x(idx); + }, + name, tag); } } @@ -1079,24 +1094,25 @@ inline Tensor dyn_tile(const Tensor& x, Array new_shape, size_t rdim, std::string name = "T_tile", std::string tag = kBroadcast) { size_t ndim = x->shape.size(); if (is_empty_shape(new_shape)) { - return compute(new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, - name, tag); + return compute( + new_shape, [&](const Array& indices) { return tvm::cast(x->dtype, 0); }, name, tag); } else { - return compute(new_shape, - [&](const Array& indices) { - Array idx; - if (ndim >= rdim) { - for (size_t i = 0; i < ndim; ++i) { - idx.push_back(indexmod(indices[i], x->shape[i])); - } - } else { - for (size_t i = 0; i < ndim; ++i) { - idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); - } - } - return x(idx); - }, - name, tag); + return compute( + new_shape, + [&](const Array& indices) { + Array idx; + if (ndim >= rdim) { + for (size_t i = 0; i < ndim; ++i) { + idx.push_back(indexmod(indices[i], x->shape[i])); + } + } else { + for (size_t i = 0; i < ndim; ++i) { + idx.push_back(indexmod(indices[rdim - ndim + i], x->shape[i])); + } + } + return x(idx); + }, + name, tag); } } @@ -1128,23 +1144,24 @@ inline Tensor gather(const Tensor& data, int axis, const Tensor& indices, out_shape.push_back(indices->shape[i]); } - return compute(out_shape, - [&](const Array& out_index) { - Array indices_position; - for (size_t i = 0; i < ndim_i; ++i) { - indices_position.push_back(out_index[i]); - } - Array real_indices; - for (size_t i = 0; i < ndim_i; ++i) { - if (i == (size_t)axis) { - real_indices.push_back(indices(indices_position)); - } else { - real_indices.push_back(indices_position[i]); - } - } - return data(real_indices); - }, - name, tag); + return compute( + out_shape, + [&](const Array& out_index) { + Array indices_position; + for (size_t i = 0; i < ndim_i; ++i) { + indices_position.push_back(out_index[i]); + } + Array real_indices; + for (size_t i = 0; i < ndim_i; ++i) { + if (i == (size_t)axis) { + real_indices.push_back(indices(indices_position)); + } else { + real_indices.push_back(indices_position[i]); + } + } + return data(real_indices); + }, + name, tag); } /*! @@ -1357,13 +1374,14 @@ inline Array meshgrid(const Array& inputs, const std::string& in } Array result; for (size_t i = 0; i < inputs.size(); ++i) { - result.push_back(compute(out_shape, - [&](const Array& indices) { - const int src_index = (cartesian_indexing && i < 2) ? 1 - i : i; - Array real_indices = {indices[src_index]}; - return inputs[i](real_indices); - }, - name, tag)); + result.push_back(compute( + out_shape, + [&](const Array& indices) { + const int src_index = (cartesian_indexing && i < 2) ? 1 - i : i; + Array real_indices = {indices[src_index]}; + return inputs[i](real_indices); + }, + name, tag)); } return result; } @@ -1415,6 +1433,7 @@ inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Ten result.push_back(empty_row_indicator); return result; } + /*! * \brief Transform the layout according to \p src_layout and \p dst_layout * \param src the source input. @@ -1444,13 +1463,14 @@ inline Tensor layout_transform(const Tensor& src, const std::string& src_layout, Array dst_shape = layout_converter.ForwardShape(src->shape); - return compute(dst_shape, - [&](const Array& dst_indices) { - Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); - Array src_indices = layout_converter.BackwardIndex(dst_indices_expr); - return src(src_indices); - }, - name, tag); + return compute( + dst_shape, + [&](const Array& dst_indices) { + Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); + Array src_indices = layout_converter.BackwardIndex(dst_indices_expr); + return src(src_indices); + }, + name, tag); } /*! \brief Utility function for auto_scheduler_layout_transform */ @@ -1501,23 +1521,24 @@ inline Tensor auto_scheduler_layout_transform(const Tensor& src, const String& s parse_auto_scheduler_layout(src_layout, &src_shape, &src_axes); parse_auto_scheduler_layout(dst_layout, &dst_shape, &dst_axes); - return compute(dst_shape, - [&](const Array& dst_indices) { - Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); - Array src_indices; - for (const std::string& src_axis : src_axes) { - PrimExpr src_index = 0; - CHECK_EQ(dst_indices_expr.size(), dst_axes.size()); - for (size_t i = 0; i < dst_axes.size(); ++i) { - if (dst_axes[i] == src_axis) { - src_index = src_index * dst_shape[i] + dst_indices_expr[i]; - } - } - src_indices.push_back(src_index); - } - return src(src_indices); - }, - name, tag); + return compute( + dst_shape, + [&](const Array& dst_indices) { + Array dst_indices_expr(dst_indices.begin(), dst_indices.end()); + Array src_indices; + for (const std::string& src_axis : src_axes) { + PrimExpr src_index = 0; + CHECK_EQ(dst_indices_expr.size(), dst_axes.size()); + for (size_t i = 0; i < dst_axes.size(); ++i) { + if (dst_axes[i] == src_axis) { + src_index = src_index * dst_shape[i] + dst_indices_expr[i]; + } + } + src_indices.push_back(src_index); + } + return src(src_indices); + }, + name, tag); } /*! @@ -1532,16 +1553,17 @@ inline Tensor shape(const Tensor& src, DataType dtype, const std::string name = const std::string tag = kInjective) { int ndim = static_cast(src->shape.size()); Array out_shape{ndim}; - return compute(out_shape, - [&](const Array& indices) { - auto idx = indices[0]; - PrimExpr ret = 0; - for (int i = 0; i < ndim; ++i) { - ret = tvm::if_then_else(idx == i, src->shape[i], ret); - } - return tvm::cast(dtype, ret); - }, - name, tag); + return compute( + out_shape, + [&](const Array& indices) { + auto idx = indices[0]; + PrimExpr ret = 0; + for (int i = 0; i < ndim; ++i) { + ret = tvm::if_then_else(idx == i, src->shape[i], ret); + } + return tvm::cast(dtype, ret); + }, + name, tag); } /*! @@ -1557,15 +1579,16 @@ inline Tensor ndarray_size(const Tensor& src, const DataType& dtype, const std::string& tag = kInjective) { int ndim = static_cast(src->shape.size()); Array out_ndarray_size = {}; - return compute(out_ndarray_size, - [&](const Array& indices) { - PrimExpr ret = 1; - for (int i = 0; i < ndim; ++i) { - ret *= src->shape[i]; - } - return tvm::cast(dtype, ret); - }, - name, tag); + return compute( + out_ndarray_size, + [&](const Array& indices) { + PrimExpr ret = 1; + for (int i = 0; i < ndim; ++i) { + ret *= src->shape[i]; + } + return tvm::cast(dtype, ret); + }, + name, tag); } /*! @@ -1601,22 +1624,22 @@ inline Tensor one_hot(const Tensor& indices, const PrimExpr on_value, const Prim PrimExpr on_value_cast = cast(dtype, on_value); PrimExpr off_value_cast = cast(dtype, off_value); - return compute(oshape, - [&](const Array& iter_vars) { - Array indices_indices; - for (size_t i = 0; i < iter_vars.size(); i++) { - if (static_cast(i) == true_axis) { - continue; - } - - indices_indices.push_back(iter_vars[i]); - } - - auto idx = iter_vars[true_axis]; - return tir::Select(indices(indices_indices) == idx, on_value_cast, - off_value_cast); - }, - name, tag); + return compute( + oshape, + [&](const Array& iter_vars) { + Array indices_indices; + for (size_t i = 0; i < iter_vars.size(); i++) { + if (static_cast(i) == true_axis) { + continue; + } + + indices_indices.push_back(iter_vars[i]); + } + + auto idx = iter_vars[true_axis]; + return tir::Select(indices(indices_indices) == idx, on_value_cast, off_value_cast); + }, + name, tag); } /*! @@ -1643,29 +1666,29 @@ inline Tensor sparse_to_dense(const Tensor& sparse_indices, const Array& indices) { - PrimExpr ret = default_value; - if (0 == rank_sparse_indices) { - ret = if_then_else(indices[0] == sparse_indices[0], sparse_values[0], ret); - } else if (1 == rank_sparse_indices) { - for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { - ret = if_then_else(indices[0] == sparse_indices[j], sparse_values[j], ret); - } - } else { - for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { - PrimExpr aggregate_condition; - for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { - PrimExpr comparision = indices[k] == sparse_indices[j][k]; - aggregate_condition = - 0 == k ? comparision : aggregate_condition && comparision; - } - ret = if_then_else(aggregate_condition, sparse_values[j], ret); - } - } - return ret; - }, - name, tag); + return compute( + oshape, + [&](const Array& indices) { + PrimExpr ret = default_value; + if (0 == rank_sparse_indices) { + ret = if_then_else(indices[0] == sparse_indices[0], sparse_values[0], ret); + } else if (1 == rank_sparse_indices) { + for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { + ret = if_then_else(indices[0] == sparse_indices[j], sparse_values[j], ret); + } + } else { + for (int j = 0; j < GetConstInt(sparse_indices->shape[0]); j++) { + PrimExpr aggregate_condition; + for (int k = 0; k < GetConstInt(sparse_indices->shape[1]); k++) { + PrimExpr comparision = indices[k] == sparse_indices[j][k]; + aggregate_condition = 0 == k ? comparision : aggregate_condition && comparision; + } + ret = if_then_else(aggregate_condition, sparse_values[j], ret); + } + } + return ret; + }, + name, tag); } /*! @@ -1786,24 +1809,25 @@ inline Tensor adv_index(const Tensor& data, const Array& indices, oshape.push_back(data->shape[i]); } - return compute(oshape, - [&](const Array& iter_var) { - Array tensor_indices; - for (size_t i = 0; i < broadcast_shape.size(); ++i) { - tensor_indices.push_back(iter_var[i]); - } - - Array real_indices; - for (size_t i = 0; i < bindices.size(); ++i) { - real_indices.push_back(bindices[i](tensor_indices)); - } - for (size_t i = broadcast_shape.size(); i < iter_var.size(); ++i) { - real_indices.push_back(iter_var[i]); - } - - return data(real_indices); - }, - name, tag); + return compute( + oshape, + [&](const Array& iter_var) { + Array tensor_indices; + for (size_t i = 0; i < broadcast_shape.size(); ++i) { + tensor_indices.push_back(iter_var[i]); + } + + Array real_indices; + for (size_t i = 0; i < bindices.size(); ++i) { + real_indices.push_back(bindices[i](tensor_indices)); + } + for (size_t i = broadcast_shape.size(); i < iter_var.size(); ++i) { + real_indices.push_back(iter_var[i]); + } + + return data(real_indices); + }, + name, tag); } } // namespace topi From c2fd0714659f57c9ca0dcbafc6a5bd72978eba3a Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 18 Dec 2020 21:58:06 +0000 Subject: [PATCH 21/44] Formats --- src/relay/op/tensor/transform.cc | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index bd191d739994..4d5948cd88a9 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1339,11 +1339,12 @@ inline te::Tensor DynamicArange(const te::Tensor& start, const te::Tensor& stop, std::string name = "T_arange_dynamic", std::string tag = topi::kInjective) { tvm::PrimExpr num_elem = tvm::tir::Var("num_elem"); - return te::compute({num_elem}, - [&](const Array& indices) { - return tvm::cast(dtype, start[0] + step[0] * indices[0]); - }, - name, tag); + return te::compute( + {num_elem}, + [&](const Array& indices) { + return tvm::cast(dtype, start[0] + step[0] * indices[0]); + }, + name, tag); } Array ArangeCompute(const Attrs& attrs, const Array& inputs, @@ -2457,16 +2458,16 @@ Array StridedSliceCompute(const Attrs& attrs, const Arrayvalue : 1))); } - return Array{ - te::compute(out_shape, - [&](const Array& indices) { - Array real_indices; - for (size_t i = 0; i < src_tensor_dim; ++i) { - real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); - } - return input(real_indices); - }, - std::string{"T_strided_slice_dynamic"}, std::string{topi::kInjective})}; + return Array{te::compute( + out_shape, + [&](const Array& indices) { + Array real_indices; + for (size_t i = 0; i < src_tensor_dim; ++i) { + real_indices.push_back(indices[i] * strides_expr[i] + begin_expr[i]); + } + return input(real_indices); + }, + std::string{"T_strided_slice_dynamic"}, std::string{topi::kInjective})}; } else { for (size_t i = 0; i < begin.size(); ++i) { begin_expr.push_back(begin[i]); From ef7845e96a653febac9f30255a1ed62f6db45883 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 21 Dec 2020 08:32:32 +0000 Subject: [PATCH 22/44] New Tests + outputs --- include/tvm/topi/transform.h | 38 ++++++++++++++++-- python/tvm/relay/op/transform.py | 9 +++-- src/relay/op/tensor/transform.cc | 6 ++- tests/python/relay/test_op_level3.py | 59 +++++++++++++++++++++++----- 4 files changed, 93 insertions(+), 19 deletions(-) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index 113f7b21f85f..08349405b530 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -1419,18 +1419,48 @@ inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Ten PrimExpr empty_row_count = 0; for (int i = 0; i < static_cast(dense_shape[0]); ++i) { empty_row_count = - if_then_else(empty_row_indicator[i], empty_row_count, empty_row_count + 1); - PrimExpr condition = - (indices[0] == sparse_indices->shape[0] + empty_row_count - 1) && empty_row_count > 0; + if_then_else(empty_row_indicator[i], empty_row_count + 1, empty_row_count); + PrimExpr at_correct_index = + (indices[0] == (sparse_indices->shape[0] + empty_row_count - 1)); + PrimExpr condition = at_correct_index && empty_row_indicator[i]; + ret = if_then_else(condition, i, ret); if (indices.size() > 1) { - ret = if_then_else(condition && indices[1] == 1, 0, ret); + ret = if_then_else(condition && indices[1] > 0, 0, ret); } } return ret; }, name, tag)); result.push_back(empty_row_indicator); + result.push_back(compute( + Array{sp_ordered_output_shape[0]}, + [&](const Array& indices) { + PrimExpr ret = -1; + ret = if_then_else(indices[0] < sparse_values->shape[0], sparse_values(indices), ret); + PrimExpr empty_row_count = 0; + for (int i = 0; i < static_cast(dense_shape[0]); ++i) { + empty_row_count = + if_then_else(empty_row_indicator[i], empty_row_count + 1, empty_row_count); + PrimExpr condition = + (indices[0] == sparse_values->shape[0] + empty_row_count - 1) && empty_row_count > 0; + ret = if_then_else(condition, default_value[0], ret); + } + return ret; + }, + name, tag)); + result.push_back(compute( + Array{1}, + [&](const Array& indices) { + PrimExpr new_sparse_values_len = sparse_values->shape[0]; + PrimExpr empty_row_count = 0; + for (int i = 0; i < static_cast(dense_shape[0]); ++i) { + new_sparse_values_len = if_then_else(empty_row_indicator[i], new_sparse_values_len + 1, + new_sparse_values_len); + } + return new_sparse_values_len; + }, + name, tag)); return result; } diff --git a/python/tvm/relay/op/transform.py b/python/tvm/relay/op/transform.py index 0838c4ea1a72..3d1bdf4d28d7 100644 --- a/python/tvm/relay/op/transform.py +++ b/python/tvm/relay/op/transform.py @@ -1322,6 +1322,9 @@ def adv_index(inputs): return _make.adv_index(Tuple(inputs)) -def sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value): - - return _make.sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value) +def sparsefillemptyrows( + sparse_indices, sparse_values, dense_shape, default_value, return_as_tuple=True +): + return TupleWrapper( + _make.sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value), 4 + ) diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index 4d5948cd88a9..6a87a8aa6cea 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1557,12 +1557,12 @@ TVM_REGISTER_NODE_TYPE(SparseFillEmptyRowsAttrs); bool SparseFillEmptyRowsRel(const Array& types, int num_inputs, const Attrs& attrs, const TypeReporter& reporter) { - // types: [ sparse_indices, sparse_values, default_value, result] + // types: [ sparse_indices, sparse_values, default_values, result] ICHECK_EQ(types.size(), 4); ICHECK_EQ(num_inputs, 3); std::vector fields; auto sparse_indices = types[0].as(); - + auto default_value = types[2].as(); const auto* param = attrs.as(); CHECK(param != nullptr); @@ -1573,6 +1573,8 @@ bool SparseFillEmptyRowsRel(const Array& types, int num_inputs, const Attr } fields.push_back(TensorType(sp_ordered_output_shape, sparse_indices->dtype)); fields.push_back(TensorType(Array{param->dense_shape[0]}, tvm::DataType::Bool())); + fields.push_back(TensorType(Array{sp_ordered_output_shape[0]}, default_value->dtype)); + fields.push_back(TensorType(Array{1}, tvm::DataType::Int(32))); reporter->Assign(types[3], TupleType(Array(fields))); return true; } diff --git a/tests/python/relay/test_op_level3.py b/tests/python/relay/test_op_level3.py index dc1d41c94be4..fee57ac8228d 100644 --- a/tests/python/relay/test_op_level3.py +++ b/tests/python/relay/test_op_level3.py @@ -1048,17 +1048,26 @@ def ref_sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_ new_sparse_indices = -1 * np.ones( (sparse_indices.shape[0] + dense_shape[0], sparse_indices.shape[1]) ) - empty_row_indicator = np.ones(dense_shape[0], dtype=bool) + new_sparse_values = -1 * np.ones(sparse_indices.shape[0] + dense_shape[0]) + slice_element_index = np.array(sparse_indices.shape[0], dtype=np.int32) + for i in range(sparse_indices.shape[0]): - if len(sparse_indices.shape) == 1: - element = sparse_indices[i] - else: - element = sparse_indices[i][0] + empty_row_indicator[sparse_indices[i][0]] = False - empty_row_indicator[element] = False + new_sparse_indices[: sparse_indices.shape[0]][:] = sparse_indices[:] + new_sparse_values[: sparse_values.shape[0]] = sparse_values[:] + new_sparse_indices_index = sparse_indices.shape[0] - return new_sparse_indices, empty_row_indicator + for empty_row_index, element in enumerate(empty_row_indicator): + if element: + new_sparse_indices[new_sparse_indices_index, 0] = empty_row_index + new_sparse_indices[new_sparse_indices_index, 1:] = 0 + new_sparse_values[new_sparse_indices_index] = default_value[0] + new_sparse_indices_index += 1 + slice_element_index += 1 + + return new_sparse_indices, empty_row_indicator, new_sparse_values, slice_element_index def verify_sparsefillemptyrows( sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np @@ -1075,8 +1084,7 @@ def verify_sparsefillemptyrows( ) z = relay.op.sparsefillemptyrows( sparse_indices, sparse_values, default_value, list(dense_shape_np) - ) - + ).astuple() func = relay.Function([sparse_indices, sparse_values, default_value], z) ref_res = ref_sparsefillemptyrows( @@ -1089,7 +1097,6 @@ def verify_sparsefillemptyrows( intrp = relay.create_executor(kind, ctx=ctx, target=target) op_res = intrp.evaluate(func)(sparse_indices_np, sparse_values_np, default_value_np) for op_res_item, ref_res_item in zip(op_res, ref_res): - print(op_res_item, ref_res_item) tvm.testing.assert_allclose(op_res_item.asnumpy(), ref_res_item, rtol=1e-5) sparse_indices_np = np.array([[0, 1], [0, 3], [2, 0], [3, 1]], dtype=np.int32) @@ -1100,6 +1107,38 @@ def verify_sparsefillemptyrows( sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np ) + sparse_indices_np = np.array([[1, 1, 1], [1, 3, 1], [2, 0, 5], [3, 1, 6]], dtype=np.int32) + sparse_values_np = np.array([1, 2, 3, 4], dtype=np.int32) + dense_shape_np = np.array([7, 7, 7], dtype=np.int32) + default_value_np = np.array([10], dtype=np.int32) + verify_sparsefillemptyrows( + sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np + ) + + sparse_indices_np = np.array([[1], [2]], dtype=np.int32) + sparse_values_np = np.array([7, 8], dtype=np.int32) + dense_shape_np = np.array([5], dtype=np.int32) + default_value_np = np.array([4], dtype=np.int32) + verify_sparsefillemptyrows( + sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np + ) + + sparse_indices_np = np.ones((0, 1), dtype=np.int32) + sparse_values_np = np.array([], dtype=np.int32) + dense_shape_np = np.array([5], dtype=np.int32) + default_value_np = np.array([4], dtype=np.int32) + verify_sparsefillemptyrows( + sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np + ) + + sparse_indices_np = np.ones((0, 3), dtype=np.int32) + sparse_values_np = np.array([], dtype=np.int32) + dense_shape_np = np.array([9, 3, 7], dtype=np.int32) + default_value_np = np.array([100], dtype=np.int32) + verify_sparsefillemptyrows( + sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np + ) + @tvm.testing.uses_gpu def test_gather(): From 51961890f3b2fbfa6c7a29704db10a1fc1a82862 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 21 Dec 2020 11:18:07 +0000 Subject: [PATCH 23/44] Add comments --- include/tvm/relay/attrs/transform.h | 1 + include/tvm/topi/transform.h | 12 +++++ python/tvm/relay/op/transform.py | 75 ++++++++++++++++++++++++++++ python/tvm/topi/transform.py | 73 ++++++++++++++++++++++++--- tests/python/relay/test_op_level3.py | 23 +++++++-- 5 files changed, 172 insertions(+), 12 deletions(-) diff --git a/include/tvm/relay/attrs/transform.h b/include/tvm/relay/attrs/transform.h index db905faa03fc..68688dd61064 100644 --- a/include/tvm/relay/attrs/transform.h +++ b/include/tvm/relay/attrs/transform.h @@ -401,6 +401,7 @@ struct SparseToDenseAttrs : public tvm::AttrsNode { } }; // struct SparseToDenseAttrs +/*! \brief Attributes used in sparsefillemptyRows operator */ struct SparseFillEmptyRowsAttrs : public tvm::AttrsNode { Array dense_shape; diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index 08349405b530..2c1935ab5b8e 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -1386,6 +1386,18 @@ inline Array meshgrid(const Array& inputs, const std::string& in return result; } +/*! + * \brief Fill Empty rows of a sparse tensor with default value + * + * \param sparse_indices Indices where values of the dense tensor exist + * \param sparse_values Values at the above indices respectively + * \param default_value Default value at to be used at empty rows + * \param dense_shape Dense shape of the sparse tensor + * \param name The name of the operation + * \param tag The tag to mark the operation + * \return A Tensor whose op member is the SparseFillEmptyRows operation + */ + inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Tensor& sparse_values, const Tensor& default_value, const Array& dense_shape, diff --git a/python/tvm/relay/op/transform.py b/python/tvm/relay/op/transform.py index 3d1bdf4d28d7..004b289acae0 100644 --- a/python/tvm/relay/op/transform.py +++ b/python/tvm/relay/op/transform.py @@ -1325,6 +1325,81 @@ def adv_index(inputs): def sparsefillemptyrows( sparse_indices, sparse_values, dense_shape, default_value, return_as_tuple=True ): + """ + Fill first column of the empty rows with default values for a sparse array. + + Parameters + ---------- + sparse_indices : relay.Expr + A 2-D tensor[N, n_dim] of integers containing location of sparse values, where N is the + number of sparse values and n_dim is the number of dimensions of the dense_shape + + sparse_values : relay.Expr + A 1-D tensor[N] containing the sparse values for the sparse indices. + + dense_shape : relay.Expr + A list of integers. Shape of the dense output tensor. + + default_value : relay.Expr + A 0-D tensor containing the default value for the remaining locations. + Defaults to 0. + + Returns + ------- + TupleWrapper with the following four outputs + + new_sparse_indices : relay.Expr + A 2-D tensor[N + dense_shape[0], n_dim] of integers containing location of new sparse + indices where N is the number of sparse values. It is filled with -1 at to_be_discarded + indices. + + empty_row_indicator : relay.Expr + A 1-D Boolean tensor[dense_shape[0]] indicating whether the particular row is empty + + new_sparse_values : relay.Expr + A 1-D tensor[dense_shape[0]] containing the sparse values for the sparse indices. It is + filled with -1 at to_be_discarded indices. + + slice_element_index : relay.Expr + A 1-D tensor containing the amount of elements in the sparse_indices and new_sparse_values + expression to be sliced in a future op discarding non-useful elements in new_sparse_indices + and new_sparse_values + + Examples + ------- + + .. code-block:: python + + sparse_indices = [[0, 1], + [0, 3], + [2, 0], + [3, 1]] + sparse_values = [1, 2, 3, 4] + default_value = [10] + dense_shape = [5, 6] + new_sparse_indices, empty_row_indicator, new_sparse_values, slice_element_index = + relay.sparsereshape( + sparse_indices, + sparse_values, + prev_shape, + new_shape) + new_sparse_indices = [[0, 1], + [0, 3], + [2, 0], + [3, 1], + [1, 0], + [4, 0], + [-1, -1], + [-1, -1], + [-1, -1]] + + empty_row_indicator = [False, True, False, False, True] + + new_sparse_values = [1, 2, 3, 4, 10, 10, -1, -1, -1] + + slice_element_index = [6] + """ + return TupleWrapper( _make.sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value), 4 ) diff --git a/python/tvm/topi/transform.py b/python/tvm/topi/transform.py index c7ea833fe8db..6f60b9d988f0 100644 --- a/python/tvm/topi/transform.py +++ b/python/tvm/topi/transform.py @@ -934,19 +934,78 @@ def adv_index(data, indices): def sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_shape): - """Numpy style indexing with tensors. + """ + Fill first column of the empty rows with default values for a sparse array. Parameters ---------- - data : tvm.te.Tensor - Input data. + sparse_indices : relay.Expr + A 2-D tensor[N, n_dim] of integers containing location of sparse values, where N is the + number of sparse values and n_dim is the number of dimensions of the dense_shape - indices : A list of tvm.te.Tensor - Tensor index. + sparse_values : relay.Expr + A 1-D tensor[N] containing the sparse values for the sparse indices. + + dense_shape : relay.Expr + A list of integers. Shape of the dense output tensor. + + default_value : relay.Expr + A 0-D tensor containing the default value for the remaining locations. + Defaults to 0. Returns ------- - result : tvm.te.Tensor - Output tensor + TupleWrapper with the following four outputs + + new_sparse_indices : relay.Expr + A 2-D tensor[N + dense_shape[0], n_dim] of integers containing location of new sparse + indices where N is the number of sparse values. It is filled with -1 at to_be_discarded + indices. + + empty_row_indicator : relay.Expr + A 1-D Boolean tensor[dense_shape[0]] indicating whether the particular row is empty + + new_sparse_values : relay.Expr + A 1-D tensor[dense_shape[0]] containing the sparse values for the sparse indices. It is + filled with -1 at to_be_discarded indices. + + slice_element_index : relay.Expr + A 1-D tensor containing the amount of elements in the sparse_indices and new_sparse_values + expression to be sliced in a future op discarding non-useful elements in new_sparse_indices + and new_sparse_values + + Examples + ------- + + .. code-block:: python + + sparse_indices = [[0, 1], + [0, 3], + [2, 0], + [3, 1]] + sparse_values = [1, 2, 3, 4] + default_value = [10] + dense_shape = [5, 6] + new_sparse_indices, empty_row_indicator, new_sparse_values, slice_element_index = + relay.sparsereshape( + sparse_indices, + sparse_values, + prev_shape, + new_shape) + new_sparse_indices = [[0, 1], + [0, 3], + [2, 0], + [3, 1], + [1, 0], + [4, 0], + [-1, -1], + [-1, -1], + [-1, -1]] + + empty_row_indicator = [False, True, False, False, True] + + new_sparse_values = [1, 2, 3, 4, 10, 10, -1, -1, -1] + + slice_element_index = [6] """ return cpp.sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_shape) diff --git a/tests/python/relay/test_op_level3.py b/tests/python/relay/test_op_level3.py index fee57ac8228d..da76ef2c3000 100644 --- a/tests/python/relay/test_op_level3.py +++ b/tests/python/relay/test_op_level3.py @@ -1044,7 +1044,16 @@ def verify_scatter_add(dshape, ishape, axis=0, dtype="float32"): @tvm.testing.uses_gpu def test_sparsefillemptyrows(): - def ref_sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_shape): + def ref_sparsefillemptyrows( + sparse_indices: np.ndarray, + sparse_values: np.ndarray, + default_value: np.ndarray, + dense_shape: np.ndarray, + ) -> None: + """ + This function calculates the expected output of sparsefillemptyrows operator given the + inputs. + """ new_sparse_indices = -1 * np.ones( (sparse_indices.shape[0] + dense_shape[0], sparse_indices.shape[1]) ) @@ -1070,8 +1079,14 @@ def ref_sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_ return new_sparse_indices, empty_row_indicator, new_sparse_values, slice_element_index def verify_sparsefillemptyrows( - sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np - ): + sparse_indices_np: np.ndarray, + sparse_values_np: np.ndarray, + default_value_np: np.ndarray, + dense_shape_np: np.ndarray, + ) -> None: + """ + This function verifies the relay output of sparsefillemptyrows with its expected output. + """ sparse_indices = relay.var( "sparse_indices", relay.TensorType(sparse_indices_np.shape, str(sparse_indices_np.dtype)), @@ -1091,8 +1106,6 @@ def verify_sparsefillemptyrows( sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np ) for target, ctx in tvm.testing.enabled_targets(): - if target == "nvptx": - continue for kind in ["graph", "debug"]: intrp = relay.create_executor(kind, ctx=ctx, target=target) op_res = intrp.evaluate(func)(sparse_indices_np, sparse_values_np, default_value_np) From af15ca795d93caa0cb5f13bcc7342e425f06db3b Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 21 Dec 2020 12:27:34 +0000 Subject: [PATCH 24/44] Clang format --- include/tvm/topi/transform.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index 2c1935ab5b8e..506cd66d1cf2 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -1387,15 +1387,15 @@ inline Array meshgrid(const Array& inputs, const std::string& in } /*! - * \brief Fill Empty rows of a sparse tensor with default value - * + * \brief Fill Empty rows of a sparse tensor with default value + * * \param sparse_indices Indices where values of the dense tensor exist * \param sparse_values Values at the above indices respectively * \param default_value Default value at to be used at empty rows * \param dense_shape Dense shape of the sparse tensor * \param name The name of the operation * \param tag The tag to mark the operation - * \return A Tensor whose op member is the SparseFillEmptyRows operation + * \return A Tensor whose op member is the SparseFillEmptyRows operation */ inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Tensor& sparse_values, From 1666e8ccb71bf52f5b273e8b8868942dfab8d1c7 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 21 Dec 2020 12:42:16 +0000 Subject: [PATCH 25/44] Black --- python/tvm/relay/op/transform.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/python/tvm/relay/op/transform.py b/python/tvm/relay/op/transform.py index 004b289acae0..ac0cd70c67ae 100644 --- a/python/tvm/relay/op/transform.py +++ b/python/tvm/relay/op/transform.py @@ -1322,9 +1322,7 @@ def adv_index(inputs): return _make.adv_index(Tuple(inputs)) -def sparsefillemptyrows( - sparse_indices, sparse_values, dense_shape, default_value, return_as_tuple=True -): +def sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value): """ Fill first column of the empty rows with default values for a sparse array. From d532872ad4bb90e8b71cd057a1df6e1ab7980793 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 21 Dec 2020 19:37:43 +0000 Subject: [PATCH 26/44] Change name --- include/tvm/topi/transform.h | 6 +++--- python/tvm/relay/op/transform.py | 4 ++-- src/relay/op/tensor/transform.cc | 12 ++++++------ tests/python/relay/test_op_level3.py | 26 +++++++++++++------------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index 506cd66d1cf2..375346715b22 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -1388,20 +1388,20 @@ inline Array meshgrid(const Array& inputs, const std::string& in /*! * \brief Fill Empty rows of a sparse tensor with default value - * + * * \param sparse_indices Indices where values of the dense tensor exist * \param sparse_values Values at the above indices respectively * \param default_value Default value at to be used at empty rows * \param dense_shape Dense shape of the sparse tensor * \param name The name of the operation * \param tag The tag to mark the operation + * * \return A Tensor whose op member is the SparseFillEmptyRows operation */ - inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Tensor& sparse_values, const Tensor& default_value, const Array& dense_shape, - const std::string name = "T_sparsefillemptyrows", + const std::string name = "T_sparse_fill_empty_rows", std::string tag = kInjective) { Array result; Array sp_ordered_output_shape; diff --git a/python/tvm/relay/op/transform.py b/python/tvm/relay/op/transform.py index ac0cd70c67ae..109abff715fd 100644 --- a/python/tvm/relay/op/transform.py +++ b/python/tvm/relay/op/transform.py @@ -1322,7 +1322,7 @@ def adv_index(inputs): return _make.adv_index(Tuple(inputs)) -def sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value): +def sparse_fill_empty_rows(sparse_indices, sparse_values, dense_shape, default_value): """ Fill first column of the empty rows with default values for a sparse array. @@ -1399,5 +1399,5 @@ def sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_valu """ return TupleWrapper( - _make.sparsefillemptyrows(sparse_indices, sparse_values, dense_shape, default_value), 4 + _make.sparse_fill_empty_rows(sparse_indices, sparse_values, dense_shape, default_value), 4 ) diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index 6a87a8aa6cea..74c5dc92d631 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1591,21 +1591,21 @@ Expr MakeSparseFillEmptyRows(Expr sparse_indices, Expr sparse_values, Expr defau Array dense_shape) { auto attrs = make_object(); attrs->dense_shape = std::move(dense_shape); - static const Op& op = Op::Get("sparsefillemptyrows"); + static const Op& op = Op::Get("sparse_fill_empty_rows"); return Call(op, {sparse_indices, sparse_values, default_value}, Attrs(attrs), {}); } -TVM_REGISTER_GLOBAL("relay.op._make.sparsefillemptyrows").set_body_typed(MakeSparseFillEmptyRows); +TVM_REGISTER_GLOBAL("relay.op._make.sparse_fill_empty_rows").set_body_typed(MakeSparseFillEmptyRows); -RELAY_REGISTER_OP("sparsefillemptyrows") - .describe(R"code(Return twice of normal addition of two tensors. -)code" TVM_ADD_FILELINE) +RELAY_REGISTER_OP("sparse_fill_empty_rows") + .describe(R"code(Return representation of a sparse tensor with empty rows filled with default + value.)code" TVM_ADD_FILELINE) .set_num_inputs(3) .set_attrs_type() .add_argument("sparse_indices", "Tensor", "The first tensor") .add_argument("sparse_values", "Tensor", "The second tensor") .add_argument("default_value", "Tensor", "The third tensor") - .add_type_rel("sparsefillemptyrows", SparseFillEmptyRowsRel) + .add_type_rel("sparse_fill_empty_rows", SparseFillEmptyRowsRel) .set_support_level(3) .set_attr("TOpPattern", kInjective) .set_attr("FTVMCompute", SparseFillEmptyRowsCompute); diff --git a/tests/python/relay/test_op_level3.py b/tests/python/relay/test_op_level3.py index da76ef2c3000..0fff93634e1f 100644 --- a/tests/python/relay/test_op_level3.py +++ b/tests/python/relay/test_op_level3.py @@ -1043,15 +1043,15 @@ def verify_scatter_add(dshape, ishape, axis=0, dtype="float32"): @tvm.testing.uses_gpu -def test_sparsefillemptyrows(): - def ref_sparsefillemptyrows( +def test_sparse_fill_empty_rows(): + def ref_sparse_fill_empty_rows( sparse_indices: np.ndarray, sparse_values: np.ndarray, default_value: np.ndarray, dense_shape: np.ndarray, ) -> None: """ - This function calculates the expected output of sparsefillemptyrows operator given the + This function calculates the expected output of sparse_fill_empty_rows operator given the inputs. """ new_sparse_indices = -1 * np.ones( @@ -1078,14 +1078,14 @@ def ref_sparsefillemptyrows( return new_sparse_indices, empty_row_indicator, new_sparse_values, slice_element_index - def verify_sparsefillemptyrows( + def verify_sparse_fill_empty_rows( sparse_indices_np: np.ndarray, sparse_values_np: np.ndarray, default_value_np: np.ndarray, dense_shape_np: np.ndarray, ) -> None: """ - This function verifies the relay output of sparsefillemptyrows with its expected output. + This function verifies the relay output of sparse_fill_empty_rows with its expected output. """ sparse_indices = relay.var( "sparse_indices", @@ -1097,12 +1097,12 @@ def verify_sparsefillemptyrows( default_value = relay.var( "default_value", relay.TensorType(default_value_np.shape, str(default_value_np.dtype)) ) - z = relay.op.sparsefillemptyrows( + z = relay.op.sparse_fill_empty_rows( sparse_indices, sparse_values, default_value, list(dense_shape_np) ).astuple() func = relay.Function([sparse_indices, sparse_values, default_value], z) - ref_res = ref_sparsefillemptyrows( + ref_res = ref_sparse_fill_empty_rows( sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np ) for target, ctx in tvm.testing.enabled_targets(): @@ -1116,7 +1116,7 @@ def verify_sparsefillemptyrows( sparse_values_np = np.array([1, 2, 3, 4], dtype=np.int32) dense_shape_np = np.array([5, 6], dtype=np.int32) default_value_np = np.array([10], dtype=np.int32) - verify_sparsefillemptyrows( + verify_sparse_fill_empty_rows( sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np ) @@ -1124,7 +1124,7 @@ def verify_sparsefillemptyrows( sparse_values_np = np.array([1, 2, 3, 4], dtype=np.int32) dense_shape_np = np.array([7, 7, 7], dtype=np.int32) default_value_np = np.array([10], dtype=np.int32) - verify_sparsefillemptyrows( + verify_sparse_fill_empty_rows( sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np ) @@ -1132,7 +1132,7 @@ def verify_sparsefillemptyrows( sparse_values_np = np.array([7, 8], dtype=np.int32) dense_shape_np = np.array([5], dtype=np.int32) default_value_np = np.array([4], dtype=np.int32) - verify_sparsefillemptyrows( + verify_sparse_fill_empty_rows( sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np ) @@ -1140,7 +1140,7 @@ def verify_sparsefillemptyrows( sparse_values_np = np.array([], dtype=np.int32) dense_shape_np = np.array([5], dtype=np.int32) default_value_np = np.array([4], dtype=np.int32) - verify_sparsefillemptyrows( + verify_sparse_fill_empty_rows( sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np ) @@ -1148,7 +1148,7 @@ def verify_sparsefillemptyrows( sparse_values_np = np.array([], dtype=np.int32) dense_shape_np = np.array([9, 3, 7], dtype=np.int32) default_value_np = np.array([100], dtype=np.int32) - verify_sparsefillemptyrows( + verify_sparse_fill_empty_rows( sparse_indices_np, sparse_values_np, default_value_np, dense_shape_np ) @@ -1424,7 +1424,7 @@ def verify_adv_index(data_shape, index_shapes): if __name__ == "__main__": - test_sparsefillemptyrows() + test_sparse_fill_empty_rows() test_cast() test_zeros_ones() test_unary_identity() From 5100d8ab9bfb4d4b9f0c52005a6c1442509d2c33 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 21 Dec 2020 19:39:43 +0000 Subject: [PATCH 27/44] Change name --- python/tvm/relay/op/_transform.py | 2 +- python/tvm/topi/transform.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/tvm/relay/op/_transform.py b/python/tvm/relay/op/_transform.py index aa816997b51a..f66d5ea76c4f 100644 --- a/python/tvm/relay/op/_transform.py +++ b/python/tvm/relay/op/_transform.py @@ -63,7 +63,7 @@ _reg.register_injective_schedule("sparse_to_dense") _reg.register_injective_schedule("matrix_set_diag") _reg.register_injective_schedule("adv_index") -_reg.register_injective_schedule("sparsefillemptyrows") +_reg.register_injective_schedule("sparse_fill_empty_rows") # concatenate _reg.register_schedule("concatenate", strategy.schedule_concatenate) diff --git a/python/tvm/topi/transform.py b/python/tvm/topi/transform.py index 6f60b9d988f0..32068cff215a 100644 --- a/python/tvm/topi/transform.py +++ b/python/tvm/topi/transform.py @@ -933,7 +933,7 @@ def adv_index(data, indices): return cpp.adv_index(data, indices) -def sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_shape): +def sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_shape): """ Fill first column of the empty rows with default values for a sparse array. @@ -1008,4 +1008,4 @@ def sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_shap slice_element_index = [6] """ - return cpp.sparsefillemptyrows(sparse_indices, sparse_values, default_value, dense_shape) + return cpp.sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_shape) From 4f733a884adab8de7b3725b77fd09ff2f0e317e6 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 21 Dec 2020 19:45:42 +0000 Subject: [PATCH 28/44] Clang --- include/tvm/topi/transform.h | 4 ++-- src/relay/op/tensor/transform.cc | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index 375346715b22..baa1849dcd59 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -1388,14 +1388,14 @@ inline Array meshgrid(const Array& inputs, const std::string& in /*! * \brief Fill Empty rows of a sparse tensor with default value - * + * * \param sparse_indices Indices where values of the dense tensor exist * \param sparse_values Values at the above indices respectively * \param default_value Default value at to be used at empty rows * \param dense_shape Dense shape of the sparse tensor * \param name The name of the operation * \param tag The tag to mark the operation - * + * * \return A Tensor whose op member is the SparseFillEmptyRows operation */ inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Tensor& sparse_values, diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index 74c5dc92d631..6be7e774b712 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1595,7 +1595,8 @@ Expr MakeSparseFillEmptyRows(Expr sparse_indices, Expr sparse_values, Expr defau return Call(op, {sparse_indices, sparse_values, default_value}, Attrs(attrs), {}); } -TVM_REGISTER_GLOBAL("relay.op._make.sparse_fill_empty_rows").set_body_typed(MakeSparseFillEmptyRows); +TVM_REGISTER_GLOBAL("relay.op._make.sparse_fill_empty_rows") + .set_body_typed(MakeSparseFillEmptyRows); RELAY_REGISTER_OP("sparse_fill_empty_rows") .describe(R"code(Return representation of a sparse tensor with empty rows filled with default From f7526b8c32ca9463834f47c1c734a42cf4c62df2 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 22 Dec 2020 05:23:09 +0000 Subject: [PATCH 29/44] PR Comments --- src/relay/op/tensor/transform.cc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index 6be7e774b712..28a8f9110881 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1558,13 +1558,13 @@ TVM_REGISTER_NODE_TYPE(SparseFillEmptyRowsAttrs); bool SparseFillEmptyRowsRel(const Array& types, int num_inputs, const Attrs& attrs, const TypeReporter& reporter) { // types: [ sparse_indices, sparse_values, default_values, result] - ICHECK_EQ(types.size(), 4); - ICHECK_EQ(num_inputs, 3); + ICHECK_EQ(types.size(), 4) << "SparseFillEmptyRowsRel expects 4 arguments but provided " + << types.size(); std::vector fields; auto sparse_indices = types[0].as(); auto default_value = types[2].as(); const auto* param = attrs.as(); - CHECK(param != nullptr); + ICHECK(param != nullptr); Array sp_ordered_output_shape; sp_ordered_output_shape.push_back(param->dense_shape[0] + sparse_indices->shape[0]); @@ -1581,9 +1581,10 @@ bool SparseFillEmptyRowsRel(const Array& types, int num_inputs, const Attr Array SparseFillEmptyRowsCompute(const Attrs& attrs, const Array& inputs, const Type& out_type) { - CHECK_EQ(inputs.size(), 3); + ICHECK_EQ(inputs.size(), 3) << "SparseFillEmptyRowsCompute expects 3 arguments but provided " + << inputs.size(); const auto* param = attrs.as(); - CHECK(param != nullptr); + ICHECK(param != nullptr); return {topi::SparseFillEmptyRows(inputs[0], inputs[1], inputs[2], param->dense_shape)}; } From f4b14ce5f5a574f957b291f3fb0d54bcebc8659f Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 22 Dec 2020 05:25:23 +0000 Subject: [PATCH 30/44] Explanation --- python/tvm/relay/op/transform.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/tvm/relay/op/transform.py b/python/tvm/relay/op/transform.py index 109abff715fd..92f3142bafb0 100644 --- a/python/tvm/relay/op/transform.py +++ b/python/tvm/relay/op/transform.py @@ -1348,8 +1348,9 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, dense_shape, default_v new_sparse_indices : relay.Expr A 2-D tensor[N + dense_shape[0], n_dim] of integers containing location of new sparse - indices where N is the number of sparse values. It is filled with -1 at to_be_discarded - indices. + indices where N is the number of sparse values. It is filled with -1 at irrelevant indices + which will be sliced in a future op discarding non-useful elements. This is done since the + real rows of new_sparse_indices depends on the input. empty_row_indicator : relay.Expr A 1-D Boolean tensor[dense_shape[0]] indicating whether the particular row is empty From fdf7a58eb8b548d66dd947ae5606b2f4ffd3ba4a Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 22 Dec 2020 05:59:32 +0000 Subject: [PATCH 31/44] Fix sphinx --- python/tvm/relay/op/transform.py | 64 +++++++++++++++--------------- python/tvm/topi/transform.py | 68 ++++++++++++++++---------------- 2 files changed, 65 insertions(+), 67 deletions(-) diff --git a/python/tvm/relay/op/transform.py b/python/tvm/relay/op/transform.py index 92f3142bafb0..50a5b7c24630 100644 --- a/python/tvm/relay/op/transform.py +++ b/python/tvm/relay/op/transform.py @@ -1325,6 +1325,7 @@ def adv_index(inputs): def sparse_fill_empty_rows(sparse_indices, sparse_values, dense_shape, default_value): """ Fill first column of the empty rows with default values for a sparse array. + It returns a TupleWrapper with four outputs Parameters ---------- @@ -1344,8 +1345,6 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, dense_shape, default_v Returns ------- - TupleWrapper with the following four outputs - new_sparse_indices : relay.Expr A 2-D tensor[N + dense_shape[0], n_dim] of integers containing location of new sparse indices where N is the number of sparse values. It is filled with -1 at irrelevant indices @@ -1357,7 +1356,7 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, dense_shape, default_v new_sparse_values : relay.Expr A 1-D tensor[dense_shape[0]] containing the sparse values for the sparse indices. It is - filled with -1 at to_be_discarded indices. + filled with -1 at to_be_discarded indices slice_element_index : relay.Expr A 1-D tensor containing the amount of elements in the sparse_indices and new_sparse_values @@ -1366,39 +1365,38 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, dense_shape, default_v Examples ------- - .. code-block:: python - sparse_indices = [[0, 1], - [0, 3], - [2, 0], - [3, 1]] - sparse_values = [1, 2, 3, 4] - default_value = [10] - dense_shape = [5, 6] - new_sparse_indices, empty_row_indicator, new_sparse_values, slice_element_index = - relay.sparsereshape( - sparse_indices, - sparse_values, - prev_shape, - new_shape) - new_sparse_indices = [[0, 1], - [0, 3], - [2, 0], - [3, 1], - [1, 0], - [4, 0], - [-1, -1], - [-1, -1], - [-1, -1]] - - empty_row_indicator = [False, True, False, False, True] - - new_sparse_values = [1, 2, 3, 4, 10, 10, -1, -1, -1] - - slice_element_index = [6] - """ + sparse_indices = [[0, 1], + [0, 3], + [2, 0], + [3, 1]] + sparse_values = [1, 2, 3, 4] + default_value = [10] + dense_shape = [5, 6] + new_sparse_indices, empty_row_indicator, new_sparse_values, slice_element_index = + relay.sparse_fill_empty_rows( + sparse_indices, + sparse_values, + default_value, + dense_shape) + new_sparse_indices = [[0, 1], + [0, 3], + [2, 0], + [3, 1], + [1, 0], + [4, 0], + [-1, -1], + [-1, -1], + [-1, -1]] + + empty_row_indicator = [False, True, False, False, True] + + new_sparse_values = [1, 2, 3, 4, 10, 10, -1, -1, -1] + + slice_element_index = [6] + """ return TupleWrapper( _make.sparse_fill_empty_rows(sparse_indices, sparse_values, dense_shape, default_value), 4 ) diff --git a/python/tvm/topi/transform.py b/python/tvm/topi/transform.py index 32068cff215a..a77cf4c70c90 100644 --- a/python/tvm/topi/transform.py +++ b/python/tvm/topi/transform.py @@ -936,6 +936,7 @@ def adv_index(data, indices): def sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_shape): """ Fill first column of the empty rows with default values for a sparse array. + It returns a TupleWrapper with four outputs Parameters ---------- @@ -955,19 +956,18 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_s Returns ------- - TupleWrapper with the following four outputs - new_sparse_indices : relay.Expr A 2-D tensor[N + dense_shape[0], n_dim] of integers containing location of new sparse - indices where N is the number of sparse values. It is filled with -1 at to_be_discarded - indices. + indices where N is the number of sparse values. It is filled with -1 at irrelevant indices + which will be sliced in a future op discarding non-useful elements. This is done since the + real rows of new_sparse_indices depends on the input. empty_row_indicator : relay.Expr A 1-D Boolean tensor[dense_shape[0]] indicating whether the particular row is empty new_sparse_values : relay.Expr A 1-D tensor[dense_shape[0]] containing the sparse values for the sparse indices. It is - filled with -1 at to_be_discarded indices. + filled with -1 at to_be_discarded indices slice_element_index : relay.Expr A 1-D tensor containing the amount of elements in the sparse_indices and new_sparse_values @@ -976,36 +976,36 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_s Examples ------- - .. code-block:: python - sparse_indices = [[0, 1], - [0, 3], - [2, 0], - [3, 1]] - sparse_values = [1, 2, 3, 4] - default_value = [10] - dense_shape = [5, 6] - new_sparse_indices, empty_row_indicator, new_sparse_values, slice_element_index = - relay.sparsereshape( - sparse_indices, - sparse_values, - prev_shape, - new_shape) - new_sparse_indices = [[0, 1], - [0, 3], - [2, 0], - [3, 1], - [1, 0], - [4, 0], - [-1, -1], - [-1, -1], - [-1, -1]] - - empty_row_indicator = [False, True, False, False, True] - - new_sparse_values = [1, 2, 3, 4, 10, 10, -1, -1, -1] - - slice_element_index = [6] + sparse_indices = [[0, 1], + [0, 3], + [2, 0], + [3, 1]] + sparse_values = [1, 2, 3, 4] + default_value = [10] + dense_shape = [5, 6] + new_sparse_indices, empty_row_indicator, new_sparse_values, slice_element_index = + relay.sparse_fill_empty_rows( + sparse_indices, + sparse_values, + default_value, + dense_shape) + new_sparse_indices = [[0, 1], + [0, 3], + [2, 0], + [3, 1], + [1, 0], + [4, 0], + [-1, -1], + [-1, -1], + [-1, -1]] + + empty_row_indicator = [False, True, False, False, True] + + new_sparse_values = [1, 2, 3, 4, 10, 10, -1, -1, -1] + + slice_element_index = [6] + """ return cpp.sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_shape) From 8a481cec482751bae962036220abd37800cd6417 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 22 Dec 2020 11:01:07 +0000 Subject: [PATCH 32/44] Revert 3rd party back to main --- 3rdparty/vta-hw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/vta-hw b/3rdparty/vta-hw index 87ce9acfae55..57db5a718c74 160000 --- a/3rdparty/vta-hw +++ b/3rdparty/vta-hw @@ -1 +1 @@ -Subproject commit 87ce9acfae550d1a487746e9d06c2e250076e54c +Subproject commit 57db5a718c74a788c98120ebbe1230797be698c8 From ec25faba5ed2b04507b693d6d7423275bc00204f Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 30 Dec 2020 11:17:41 +0000 Subject: [PATCH 33/44] Add TF Frontend Code --- include/tvm/topi/transform.h | 8 +-- python/tvm/relay/frontend/tensorflow.py | 52 ++++++++++++++ python/tvm/relay/op/transform.py | 10 +-- python/tvm/topi/transform.py | 6 +- .../frontend/tensorflow/test_forward.py | 71 ++++++++++++++++++- tests/python/relay/test_op_level3.py | 3 +- 6 files changed, 135 insertions(+), 15 deletions(-) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index baa1849dcd59..9348b7fcd815 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -1464,13 +1464,11 @@ inline Array SparseFillEmptyRows(const Tensor& sparse_indices, const Ten result.push_back(compute( Array{1}, [&](const Array& indices) { - PrimExpr new_sparse_values_len = sparse_values->shape[0]; - PrimExpr empty_row_count = 0; + PrimExpr non_empty_rows = 0; for (int i = 0; i < static_cast(dense_shape[0]); ++i) { - new_sparse_values_len = if_then_else(empty_row_indicator[i], new_sparse_values_len + 1, - new_sparse_values_len); + non_empty_rows = if_then_else(empty_row_indicator[i], non_empty_rows, non_empty_rows + 1); } - return new_sparse_values_len; + return non_empty_rows; }, name, tag)); return result; diff --git a/python/tvm/relay/frontend/tensorflow.py b/python/tvm/relay/frontend/tensorflow.py index d5746a38582c..42a4a3c1eff8 100644 --- a/python/tvm/relay/frontend/tensorflow.py +++ b/python/tvm/relay/frontend/tensorflow.py @@ -1365,6 +1365,57 @@ def _impl(inputs, attr, params, mod): return _impl +def _sparse_fill_empty_rows(): + def _impl(inputs, attr, params, mod): + assert len(inputs) == 4, "There should be 4 input tensors" + + indices_tensor = _infer_value(inputs[0], params, mod).asnumpy() + values_tensor = _infer_value(inputs[1], params, mod).asnumpy() + dense_shape_tensor = _infer_value(inputs[2], params, mod).asnumpy() + default_value_tensor = _infer_value(inputs[3], params, mod).asnumpy().reshape(1) + + indices_data = _expr.const(indices_tensor, indices_tensor.dtype) + values_data = _expr.const(values_tensor, values_tensor.dtype) + default_value_data = _expr.const(default_value_tensor, default_value_tensor.dtype) + + ( + new_sparse_indices, + empty_row_indicator, + new_sparse_values, + non_empty_rows, + ) = get_relay_op("sparse_fill_empty_rows")( + indices_data, values_data, default_value_data, list(dense_shape_tensor) + ) + first_row = get_relay_op("split")(new_sparse_indices, indices_tensor.shape[1], axis=1) + sorted_indices = _op.argsort(_op.squeeze(first_row[0])) + sorted_sparse_indices = get_relay_op("take")( + new_sparse_indices, sorted_indices, axis=0, mode="clip" + ) + + final_sparse_indices = _op.strided_slice( + _op.take(new_sparse_indices, sorted_indices, axis=0), + begin=_op.concatenate([non_empty_rows, _expr.const([0])], 0), + end=[-1, -1], + strides=[1, 1], + slice_mode="size", + ) + + final_sparse_values = _op.strided_slice( + _op.take(new_sparse_values, sorted_indices), + begin=non_empty_rows, + end=_expr.const([-1]), + slice_mode="size", + ) + + return ( + final_sparse_indices, + final_sparse_values, + empty_row_indicator, + ) + + return _impl + + def _bias_add(): def _impl(inputs, attr, params, mod): # Must expand for proper broadcasting in NCHW. @@ -2422,6 +2473,7 @@ def _impl(inputs, attr, params, mod): "SpaceToBatchND": _space_to_batch_nd(), "SpaceToDepth": _space_to_depth(), "SparseToDense": _sparse_to_dense(), + "SparseFillEmptyRows": _sparse_fill_empty_rows(), "SparseTensorDenseMatMul": _sparse_tensor_dense_matmul(), "Split": _split(False), "SplitV": _split(True), diff --git a/python/tvm/relay/op/transform.py b/python/tvm/relay/op/transform.py index 50a5b7c24630..77d5de83dddc 100644 --- a/python/tvm/relay/op/transform.py +++ b/python/tvm/relay/op/transform.py @@ -1322,7 +1322,7 @@ def adv_index(inputs): return _make.adv_index(Tuple(inputs)) -def sparse_fill_empty_rows(sparse_indices, sparse_values, dense_shape, default_value): +def sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_shape): """ Fill first column of the empty rows with default values for a sparse array. It returns a TupleWrapper with four outputs @@ -1336,13 +1336,13 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, dense_shape, default_v sparse_values : relay.Expr A 1-D tensor[N] containing the sparse values for the sparse indices. - dense_shape : relay.Expr - A list of integers. Shape of the dense output tensor. - default_value : relay.Expr A 0-D tensor containing the default value for the remaining locations. Defaults to 0. + dense_shape : relay.Expr + A list of integers. Shape of the dense output tensor. + Returns ------- new_sparse_indices : relay.Expr @@ -1398,5 +1398,5 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, dense_shape, default_v """ return TupleWrapper( - _make.sparse_fill_empty_rows(sparse_indices, sparse_values, dense_shape, default_value), 4 + _make.sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_shape), 4 ) diff --git a/python/tvm/topi/transform.py b/python/tvm/topi/transform.py index a77cf4c70c90..8139c21869ae 100644 --- a/python/tvm/topi/transform.py +++ b/python/tvm/topi/transform.py @@ -947,13 +947,13 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_s sparse_values : relay.Expr A 1-D tensor[N] containing the sparse values for the sparse indices. - dense_shape : relay.Expr - A list of integers. Shape of the dense output tensor. - default_value : relay.Expr A 0-D tensor containing the default value for the remaining locations. Defaults to 0. + dense_shape : relay.Expr + A list of integers. Shape of the dense output tensor. + Returns ------- new_sparse_indices : relay.Expr diff --git a/tests/python/frontend/tensorflow/test_forward.py b/tests/python/frontend/tensorflow/test_forward.py index 22ed6c5b2edf..ac1ec40d7ff5 100644 --- a/tests/python/frontend/tensorflow/test_forward.py +++ b/tests/python/frontend/tensorflow/test_forward.py @@ -1811,6 +1811,73 @@ def test_forward_sparse_dense_matmul(): ) +####################################################################### +# SparseFillEmptyRows +# ------------ + + +def _test_sparse_fill_empty_rows(indices_np, values_np, default_value, dense_shape_np): + with tf.Graph().as_default(): + sp_input = tf.sparse.SparseTensor( + indices=indices_np, values=values_np, dense_shape=dense_shape_np + ) + result = tf.sparse.fill_empty_rows(sp_input, default_value, name="sparse_fill_empty_rows") + compare_tf_with_tvm( + None, + "", + [ + "sparse_fill_empty_rows/SparseFillEmptyRows:0", + "sparse_fill_empty_rows/SparseFillEmptyRows:1", + "sparse_fill_empty_rows/SparseFillEmptyRows:2", + "SparseTensor/dense_shape:0", + ], + ) + + +def test_forward_sparse_fill_empty_rows(): + """ sparse_fill_empty_rows op test""" + ################################################################### + # + # In order to create a SparseTensor, it requires 3 input as below: + # SparseTensor(indices=[[0, 0], [1, 2]], values=[1, 2], dense_shape=[3, 4]) + # + # Above Sparse can be represented in Dense as below : + # [[1, 0, 0, 0] + # [0, 0, 2, 0] + # [0, 0, 0, 0]] + # + # ------------------------------------------------------------------ + sparse_indices_np = np.array([[0, 1], [0, 3], [2, 0], [3, 1]], dtype=np.int32) + sparse_values_np = np.array([1, 2, 3, 4], dtype=np.int32) + dense_shape_np = np.array([5, 6], dtype=np.int32) + default_value = 10 + _test_sparse_fill_empty_rows(sparse_indices_np, sparse_values_np, default_value, dense_shape_np) + + sparse_indices_np = np.array([[1, 1, 1], [1, 3, 1], [2, 0, 5], [3, 1, 6]], dtype=np.int32) + sparse_values_np = np.array([1, 2, 3, 4], dtype=np.int32) + dense_shape_np = np.array([7, 7, 7], dtype=np.int32) + default_value_np = np.array([10], dtype=np.int32) + _test_sparse_fill_empty_rows(sparse_indices_np, sparse_values_np, default_value, dense_shape_np) + + sparse_indices_np = np.array([[1], [2]], dtype=np.int32) + sparse_values_np = np.array([7, 8], dtype=np.int32) + dense_shape_np = np.array([5], dtype=np.int32) + default_value_np = np.array([4], dtype=np.int32) + _test_sparse_fill_empty_rows(sparse_indices_np, sparse_values_np, default_value, dense_shape_np) + + sparse_indices_np = np.ones((0, 1), dtype=np.int32) + sparse_values_np = np.array([], dtype=np.int32) + dense_shape_np = np.array([5], dtype=np.int32) + default_value_np = np.array([4], dtype=np.int32) + _test_sparse_fill_empty_rows(sparse_indices_np, sparse_values_np, default_value, dense_shape_np) + + sparse_indices_np = np.ones((0, 3), dtype=np.int32) + sparse_values_np = np.array([], dtype=np.int32) + dense_shape_np = np.array([9, 3, 7], dtype=np.int32) + default_value_np = np.array([100], dtype=np.int32) + _test_sparse_fill_empty_rows(sparse_indices_np, sparse_values_np, default_value, dense_shape_np) + + ####################################################################### # StridedSlice # ------------ @@ -4682,4 +4749,6 @@ def lstm_cell(): if __name__ == "__main__": - pytest.main([__file__]) + # test_forward_sparse_dense_matmul() + test_forward_sparse_fill_empty_rows() + # pytest.main([__file__]) diff --git a/tests/python/relay/test_op_level3.py b/tests/python/relay/test_op_level3.py index 0fff93634e1f..49ad8729be8d 100644 --- a/tests/python/relay/test_op_level3.py +++ b/tests/python/relay/test_op_level3.py @@ -1059,7 +1059,7 @@ def ref_sparse_fill_empty_rows( ) empty_row_indicator = np.ones(dense_shape[0], dtype=bool) new_sparse_values = -1 * np.ones(sparse_indices.shape[0] + dense_shape[0]) - slice_element_index = np.array(sparse_indices.shape[0], dtype=np.int32) + slice_element_index = np.array(0, dtype=np.int32) for i in range(sparse_indices.shape[0]): empty_row_indicator[sparse_indices[i][0]] = False @@ -1074,6 +1074,7 @@ def ref_sparse_fill_empty_rows( new_sparse_indices[new_sparse_indices_index, 1:] = 0 new_sparse_values[new_sparse_indices_index] = default_value[0] new_sparse_indices_index += 1 + else: slice_element_index += 1 return new_sparse_indices, empty_row_indicator, new_sparse_values, slice_element_index From 72d5cdeb531fd02f9ffc5f319c7eac6b2a38d5a6 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 30 Dec 2020 11:21:01 +0000 Subject: [PATCH 34/44] Linter --- python/tvm/relay/frontend/tensorflow.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/python/tvm/relay/frontend/tensorflow.py b/python/tvm/relay/frontend/tensorflow.py index 42a4a3c1eff8..65ba2dcd0c54 100644 --- a/python/tvm/relay/frontend/tensorflow.py +++ b/python/tvm/relay/frontend/tensorflow.py @@ -1388,9 +1388,6 @@ def _impl(inputs, attr, params, mod): ) first_row = get_relay_op("split")(new_sparse_indices, indices_tensor.shape[1], axis=1) sorted_indices = _op.argsort(_op.squeeze(first_row[0])) - sorted_sparse_indices = get_relay_op("take")( - new_sparse_indices, sorted_indices, axis=0, mode="clip" - ) final_sparse_indices = _op.strided_slice( _op.take(new_sparse_indices, sorted_indices, axis=0), From a61647e5df618ec29da5a3e1972a5524202b2528 Mon Sep 17 00:00:00 2001 From: Ritwik Das Date: Wed, 30 Dec 2020 03:23:03 -0800 Subject: [PATCH 35/44] Update src/relay/op/tensor/transform.cc Co-authored-by: Tristan Konolige --- src/relay/op/tensor/transform.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index 28a8f9110881..a1f81b8276b8 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1581,8 +1581,7 @@ bool SparseFillEmptyRowsRel(const Array& types, int num_inputs, const Attr Array SparseFillEmptyRowsCompute(const Attrs& attrs, const Array& inputs, const Type& out_type) { - ICHECK_EQ(inputs.size(), 3) << "SparseFillEmptyRowsCompute expects 3 arguments but provided " - << inputs.size(); + ICHECK_EQ(inputs.size(), 3) << "SparseFillEmptyRowsCompute expects 3 arguments but " << inputs.size() << " were provided."; const auto* param = attrs.as(); ICHECK(param != nullptr); return {topi::SparseFillEmptyRows(inputs[0], inputs[1], inputs[2], param->dense_shape)}; From e396514d9d2fc100c02eb4274203c6f2a6efd56f Mon Sep 17 00:00:00 2001 From: Ritwik Das Date: Wed, 30 Dec 2020 03:23:56 -0800 Subject: [PATCH 36/44] Update src/relay/op/tensor/transform.cc Co-authored-by: Tristan Konolige --- src/relay/op/tensor/transform.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index a1f81b8276b8..47d38e774510 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1583,7 +1583,7 @@ Array SparseFillEmptyRowsCompute(const Attrs& attrs, const Array(); - ICHECK(param != nullptr); + ICHECK_NOTNULL(param); return {topi::SparseFillEmptyRows(inputs[0], inputs[1], inputs[2], param->dense_shape)}; } From 0af56dce34b4d4acb4e39b0ce20c36f1e4cae256 Mon Sep 17 00:00:00 2001 From: Ritwik Das Date: Wed, 30 Dec 2020 03:24:03 -0800 Subject: [PATCH 37/44] Update src/relay/op/tensor/transform.cc Co-authored-by: Tristan Konolige --- src/relay/op/tensor/transform.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index 47d38e774510..3fd7d3cedc0f 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1558,8 +1558,7 @@ TVM_REGISTER_NODE_TYPE(SparseFillEmptyRowsAttrs); bool SparseFillEmptyRowsRel(const Array& types, int num_inputs, const Attrs& attrs, const TypeReporter& reporter) { // types: [ sparse_indices, sparse_values, default_values, result] - ICHECK_EQ(types.size(), 4) << "SparseFillEmptyRowsRel expects 4 arguments but provided " - << types.size(); + ICHECK_EQ(types.size(), 4) << "SparseFillEmptyRowsRel expects 4 arguments but " << types.size() << " were provided."; std::vector fields; auto sparse_indices = types[0].as(); auto default_value = types[2].as(); From dd801d6afd1ddc317e843790e894dbaf0375ca05 Mon Sep 17 00:00:00 2001 From: Ritwik Das Date: Wed, 30 Dec 2020 03:24:10 -0800 Subject: [PATCH 38/44] Update src/relay/op/tensor/transform.cc Co-authored-by: Tristan Konolige --- src/relay/op/tensor/transform.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index 3fd7d3cedc0f..56aa3c522344 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1563,7 +1563,7 @@ bool SparseFillEmptyRowsRel(const Array& types, int num_inputs, const Attr auto sparse_indices = types[0].as(); auto default_value = types[2].as(); const auto* param = attrs.as(); - ICHECK(param != nullptr); + ICHECK_NOTNULL(param); Array sp_ordered_output_shape; sp_ordered_output_shape.push_back(param->dense_shape[0] + sparse_indices->shape[0]); From 3a19b7ff2bca335587f354d5e81c629d945df874 Mon Sep 17 00:00:00 2001 From: Ritwik Das Date: Wed, 30 Dec 2020 03:24:17 -0800 Subject: [PATCH 39/44] Update include/tvm/topi/transform.h Co-authored-by: Tristan Konolige --- include/tvm/topi/transform.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/tvm/topi/transform.h b/include/tvm/topi/transform.h index 9348b7fcd815..46029299c2eb 100644 --- a/include/tvm/topi/transform.h +++ b/include/tvm/topi/transform.h @@ -1387,7 +1387,7 @@ inline Array meshgrid(const Array& inputs, const std::string& in } /*! - * \brief Fill Empty rows of a sparse tensor with default value + * \brief Fill empty rows of a sparse tensor with default values * * \param sparse_indices Indices where values of the dense tensor exist * \param sparse_values Values at the above indices respectively From bddf3cf58df357878986cb2bc4e8c11bc06c4b5e Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 30 Dec 2020 17:41:12 +0000 Subject: [PATCH 40/44] Fix --- include/tvm/support/logging.h | 8 ++++---- src/relay/op/tensor/transform.cc | 6 ++++-- tests/python/frontend/tensorflow/test_forward.py | 1 - 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/tvm/support/logging.h b/include/tvm/support/logging.h index d98363ea1c1b..ced1902a1bd1 100644 --- a/include/tvm/support/logging.h +++ b/include/tvm/support/logging.h @@ -139,10 +139,10 @@ constexpr const char* kTVM_INTERNAL_ERROR_MESSAGE = #define ICHECK_GE(x, y) ICHECK_BINARY_OP(_GE, >=, x, y) #define ICHECK_EQ(x, y) ICHECK_BINARY_OP(_EQ, ==, x, y) #define ICHECK_NE(x, y) ICHECK_BINARY_OP(_NE, !=, x, y) -#define ICHECK_NOTNULL(x) \ - ((x) == nullptr ? dmlc::LogMessageFatal(__FILE__, __LINE__).stream() \ - << tvm::kTVM_INTERNAL_ERROR_MESSAGE << __INDENT << "Check not null: " #x \ - << ' ', \ +#define ICHECK_NOTNULL(x) \ + ((x) == nullptr ? dmlc::LogMessageFatal(__FILE__, __LINE__).stream() \ + << tvm::kTVM_INTERNAL_ERROR_MESSAGE << ICHECK_INDENT \ + << "Check not null: " #x << ' ', \ (x) : (x)) // NOLINT(*) /*! \brief The diagnostic level, controls the printing of the message. */ diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index 56aa3c522344..d42f0ff874c7 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1558,7 +1558,8 @@ TVM_REGISTER_NODE_TYPE(SparseFillEmptyRowsAttrs); bool SparseFillEmptyRowsRel(const Array& types, int num_inputs, const Attrs& attrs, const TypeReporter& reporter) { // types: [ sparse_indices, sparse_values, default_values, result] - ICHECK_EQ(types.size(), 4) << "SparseFillEmptyRowsRel expects 4 arguments but " << types.size() << " were provided."; + ICHECK_EQ(types.size(), 4) << "SparseFillEmptyRowsRel expects 4 arguments but " << types.size() + << " were provided."; std::vector fields; auto sparse_indices = types[0].as(); auto default_value = types[2].as(); @@ -1580,7 +1581,8 @@ bool SparseFillEmptyRowsRel(const Array& types, int num_inputs, const Attr Array SparseFillEmptyRowsCompute(const Attrs& attrs, const Array& inputs, const Type& out_type) { - ICHECK_EQ(inputs.size(), 3) << "SparseFillEmptyRowsCompute expects 3 arguments but " << inputs.size() << " were provided."; + ICHECK_EQ(inputs.size(), 3) << "SparseFillEmptyRowsCompute expects 3 arguments but " + << inputs.size() << " were provided."; const auto* param = attrs.as(); ICHECK_NOTNULL(param); return {topi::SparseFillEmptyRows(inputs[0], inputs[1], inputs[2], param->dense_shape)}; diff --git a/tests/python/frontend/tensorflow/test_forward.py b/tests/python/frontend/tensorflow/test_forward.py index ac1ec40d7ff5..a1c5e7a9b3ef 100644 --- a/tests/python/frontend/tensorflow/test_forward.py +++ b/tests/python/frontend/tensorflow/test_forward.py @@ -4749,6 +4749,5 @@ def lstm_cell(): if __name__ == "__main__": - # test_forward_sparse_dense_matmul() test_forward_sparse_fill_empty_rows() # pytest.main([__file__]) From 0ce96772c4f979f4bbf840bdf3fc1249e6ab0956 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 30 Dec 2020 17:41:50 +0000 Subject: [PATCH 41/44] revert --- tests/python/frontend/tensorflow/test_forward.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/python/frontend/tensorflow/test_forward.py b/tests/python/frontend/tensorflow/test_forward.py index a1c5e7a9b3ef..40048b52bca9 100644 --- a/tests/python/frontend/tensorflow/test_forward.py +++ b/tests/python/frontend/tensorflow/test_forward.py @@ -4749,5 +4749,4 @@ def lstm_cell(): if __name__ == "__main__": - test_forward_sparse_fill_empty_rows() - # pytest.main([__file__]) + pytest.main([__file__]) From d03ddc86481a016f935a617c209de828bad70703 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 30 Dec 2020 18:01:41 +0000 Subject: [PATCH 42/44] Make Docs better --- python/tvm/relay/op/transform.py | 16 ++++++++-------- python/tvm/topi/transform.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/python/tvm/relay/op/transform.py b/python/tvm/relay/op/transform.py index 77d5de83dddc..e2314e6f81f1 100644 --- a/python/tvm/relay/op/transform.py +++ b/python/tvm/relay/op/transform.py @@ -1337,8 +1337,7 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_s A 1-D tensor[N] containing the sparse values for the sparse indices. default_value : relay.Expr - A 0-D tensor containing the default value for the remaining locations. - Defaults to 0. + A 1-D tensor containing the default value for the remaining locations. dense_shape : relay.Expr A list of integers. Shape of the dense output tensor. @@ -1356,12 +1355,13 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_s new_sparse_values : relay.Expr A 1-D tensor[dense_shape[0]] containing the sparse values for the sparse indices. It is - filled with -1 at to_be_discarded indices + filled with -1 at indices which will be discarded in the following strided_slice op. + This is done since the real rows of new_sparse_indices depends on the input. - slice_element_index : relay.Expr - A 1-D tensor containing the amount of elements in the sparse_indices and new_sparse_values - expression to be sliced in a future op discarding non-useful elements in new_sparse_indices - and new_sparse_values + non_empty_rows : relay.Expr + A 1-D tensor containing the amount of non-empty rows in the sparse_indices. This value will + be used to slice irrelevant indices(filled with -1) in new_sparse_values and + new_sparse_indices Examples ------- @@ -1394,7 +1394,7 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_s new_sparse_values = [1, 2, 3, 4, 10, 10, -1, -1, -1] - slice_element_index = [6] + slice_element_index = [3] """ return TupleWrapper( diff --git a/python/tvm/topi/transform.py b/python/tvm/topi/transform.py index 8139c21869ae..c759c3d09960 100644 --- a/python/tvm/topi/transform.py +++ b/python/tvm/topi/transform.py @@ -948,8 +948,7 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_s A 1-D tensor[N] containing the sparse values for the sparse indices. default_value : relay.Expr - A 0-D tensor containing the default value for the remaining locations. - Defaults to 0. + A 1-D tensor containing the default value for the remaining locations. dense_shape : relay.Expr A list of integers. Shape of the dense output tensor. @@ -967,12 +966,13 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_s new_sparse_values : relay.Expr A 1-D tensor[dense_shape[0]] containing the sparse values for the sparse indices. It is - filled with -1 at to_be_discarded indices + filled with -1 at indices which will be discarded in the following strided_slice op. + This is done since the real rows of new_sparse_indices depends on the input. - slice_element_index : relay.Expr - A 1-D tensor containing the amount of elements in the sparse_indices and new_sparse_values - expression to be sliced in a future op discarding non-useful elements in new_sparse_indices - and new_sparse_values + non_empty_rows : relay.Expr + A 1-D tensor containing the amount of non-empty rows in the sparse_indices. This value will + be used to slice irrelevant indices(filled with -1) in new_sparse_values and + new_sparse_indices Examples ------- @@ -1005,7 +1005,7 @@ def sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_s new_sparse_values = [1, 2, 3, 4, 10, 10, -1, -1, -1] - slice_element_index = [6] + slice_element_index = [3] """ return cpp.sparse_fill_empty_rows(sparse_indices, sparse_values, default_value, dense_shape) From 64684153706ca553cb865b54b3b96b607efc5b8c Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 30 Dec 2020 18:46:51 +0000 Subject: [PATCH 43/44] Make descriptions better --- src/relay/op/tensor/transform.cc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/relay/op/tensor/transform.cc b/src/relay/op/tensor/transform.cc index d42f0ff874c7..49bfefb2bf6f 100644 --- a/src/relay/op/tensor/transform.cc +++ b/src/relay/op/tensor/transform.cc @@ -1557,7 +1557,7 @@ TVM_REGISTER_NODE_TYPE(SparseFillEmptyRowsAttrs); bool SparseFillEmptyRowsRel(const Array& types, int num_inputs, const Attrs& attrs, const TypeReporter& reporter) { - // types: [ sparse_indices, sparse_values, default_values, result] + // types: [ sparse_indices, sparse_values, default_value, result] ICHECK_EQ(types.size(), 4) << "SparseFillEmptyRowsRel expects 4 arguments but " << types.size() << " were provided."; std::vector fields; @@ -1604,9 +1604,14 @@ RELAY_REGISTER_OP("sparse_fill_empty_rows") value.)code" TVM_ADD_FILELINE) .set_num_inputs(3) .set_attrs_type() - .add_argument("sparse_indices", "Tensor", "The first tensor") - .add_argument("sparse_values", "Tensor", "The second tensor") - .add_argument("default_value", "Tensor", "The third tensor") + .add_argument( + "sparse_indices", "Tensor", + "A 2-D tensor[N, n_dim] of integers containing location of sparse values, where N is the" + "number of sparse values and n_dim is the number of dimensions of the dense_shape") + .add_argument("sparse_values", "Tensor", + "A 1-D tensor[N] containing the sparse values for the sparse indices") + .add_argument("default_value", "Tensor", + "A tensor containing the default value for the remaining locations") .add_type_rel("sparse_fill_empty_rows", SparseFillEmptyRowsRel) .set_support_level(3) .set_attr("TOpPattern", kInjective) From 6ba75bd0b1de969c9d837eca0f74a87d6eb5468a Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 30 Dec 2020 19:07:49 +0000 Subject: [PATCH 44/44] Typo --- python/tvm/relay/frontend/tensorflow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tvm/relay/frontend/tensorflow.py b/python/tvm/relay/frontend/tensorflow.py index 65ba2dcd0c54..5b6f64bf58b6 100644 --- a/python/tvm/relay/frontend/tensorflow.py +++ b/python/tvm/relay/frontend/tensorflow.py @@ -1386,8 +1386,8 @@ def _impl(inputs, attr, params, mod): ) = get_relay_op("sparse_fill_empty_rows")( indices_data, values_data, default_value_data, list(dense_shape_tensor) ) - first_row = get_relay_op("split")(new_sparse_indices, indices_tensor.shape[1], axis=1) - sorted_indices = _op.argsort(_op.squeeze(first_row[0])) + first_column = get_relay_op("split")(new_sparse_indices, indices_tensor.shape[1], axis=1) + sorted_indices = _op.argsort(_op.squeeze(first_column[0])) final_sparse_indices = _op.strided_slice( _op.take(new_sparse_indices, sorted_indices, axis=0),