From 2b7fbc5f61116b7431f4f4241501b5e73fd38444 Mon Sep 17 00:00:00 2001 From: kshitij12345 Date: Thu, 20 Jun 2019 11:34:19 +0530 Subject: [PATCH 001/813] [bug] fix higher grad log (#15120) * fix bug with higher order log implementation. * bug: the head_grads were not preserved in higher order. * add test to validate the fix of the same. * fix grad for head_grads and update relevant test * address comments * remove assertion for y_grad gradient. * rename variables. * fix and update computation. * address comments * explicitly pass arguments with name. * fix mistyped comment. Co-Authored-By: Lin Yuan --- .../tensor/elemwise_unary_op_basic.cc | 68 ++++++++++++------- .../python/unittest/test_higher_order_grad.py | 21 ++++-- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/operator/tensor/elemwise_unary_op_basic.cc b/src/operator/tensor/elemwise_unary_op_basic.cc index f2b8dd6b1314..98dc8dad825f 100644 --- a/src/operator/tensor/elemwise_unary_op_basic.cc +++ b/src/operator/tensor/elemwise_unary_op_basic.cc @@ -1090,20 +1090,26 @@ MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU_DR(_backward_log, unary_bwd) .set_attr("FGradient", [](const nnvm::NodePtr& n, const std::vector& ograds) { - // For f(x) -> f = log + // ograds[0]: dL/dxgrad + // inputs[0]: dL/dy + // inputs[1]: x + // f(x) = y = log(x) + // f'(x) = 1/x // f''(x) = -1 * (f'(x) * f'(x)) - auto gx = nnvm::NodeEntry{n}; - auto ggx_mid = MakeNode("elemwise_mul", n->attrs.name + "_backward_mid_grad_grad", - {gx, gx}, nullptr, &n); - auto ggx = MakeNode("negative", n->attrs.name + "_backward_grad_grad", - {nnvm::NodeEntry{ggx_mid}}, nullptr, &n); + auto dydx_mul_dldy = nnvm::NodeEntry{n}; // f'(x) * head_grads + auto dlogx = MakeNode("reciprocal", n->attrs.name + "_dlogx", + {n->inputs[1]}, nullptr, &n); + auto d2ydx2_mid = MakeNode("elemwise_mul", n->attrs.name + "_d2ydx2_mid", + {dydx_mul_dldy, nnvm::NodeEntry{dlogx}}, nullptr, &n); + auto d2ydx2 = MakeNode("negative", n->attrs.name + "_d2ydx2", + {nnvm::NodeEntry{d2ydx2_mid}}, nullptr, &n); std::vector ret; ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad", - {ograds[0], gx}, nullptr, &n)); + {ograds[0], nnvm::NodeEntry{dlogx}}, nullptr, &n)); ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad_inp", - {ograds[0], nnvm::NodeEntry{ggx}}, nullptr, &n)); + {ograds[0], nnvm::NodeEntry{d2ydx2}}, nullptr, &n)); return ret; }); @@ -1111,23 +1117,28 @@ MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU_DR(_backward_log10, unary_bwd) .set_attr("FGradient", [](const nnvm::NodePtr& n, const std::vector& ograds) { - // For f(x) -> f = log10 + // ograds[0]: dL/dxgrad + // inputs[0]: dL/dy + // inputs[1]: x + // f(x) = y = log10(x) // f'(x) = 1 / (log(10) * x) // f''(x) = -1 * (f'(x) * 1/x) - auto gx = nnvm::NodeEntry{n, 0, 0}; - auto g_lx = MakeNode("reciprocal", n->attrs.name + "_backward_log_grad", + auto dydx_mul_dldy = nnvm::NodeEntry{n}; // f'(x) * head_grads + auto dydx = MakeNode("elemwise_div", n->attrs.name + "_dydx", + {n->inputs[0]}, nullptr, &n); + auto dlogx = MakeNode("reciprocal", n->attrs.name + "_dlogx", {n->inputs[1]}, nullptr, &n); - auto ggx_mid = MakeNode("elemwise_mul", n->attrs.name + "_backward_mid_grad_grad", - {gx, nnvm::NodeEntry{g_lx}}, nullptr, &n); - auto ggx = MakeNode("negative", n->attrs.name + "_backward_grad_grad", - {nnvm::NodeEntry{ggx_mid}}, nullptr, &n); + auto d2ydx2_mid = MakeNode("elemwise_mul", n->attrs.name + "_d2ydx2_mid", + {dydx_mul_dldy, nnvm::NodeEntry{dlogx}}, nullptr, &n); + auto d2ydx2 = MakeNode("negative", n->attrs.name + "_d2ydx2", + {nnvm::NodeEntry{d2ydx2_mid}}, nullptr, &n); std::vector ret; ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad", - {ograds[0], gx}, nullptr, &n)); + {ograds[0], nnvm::NodeEntry{dydx}}, nullptr, &n)); ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad_inp", - {ograds[0], nnvm::NodeEntry{ggx}}, nullptr, &n)); + {ograds[0], nnvm::NodeEntry{d2ydx2}}, nullptr, &n)); return ret; }); @@ -1135,23 +1146,28 @@ MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU_DR(_backward_log2, unary_bwd) .set_attr("FGradient", [](const nnvm::NodePtr& n, const std::vector& ograds) { - // For f(x) -> f = log2 + // ograds[0]: dL/dxgrad + // inputs[0]: dL/dy + // inputs[1]: x + // f(x) = y = log2(x) // f'(x) = 1 / (log(2) * x) // f''(x) = -1 * (f'(x) * 1/x) - auto gx = nnvm::NodeEntry{n}; - auto g_lx = MakeNode("reciprocal", n->attrs.name + "_backward_log_grad", + auto dydx_mul_dldy = nnvm::NodeEntry{n}; // f'(x) * head_grads + auto dydx = MakeNode("elemwise_div", n->attrs.name + "_dydx", + {n->inputs[0]}, nullptr, &n); + auto dlogx = MakeNode("reciprocal", n->attrs.name + "_dlogx", {n->inputs[1]}, nullptr, &n); - auto ggx_mid = MakeNode("elemwise_mul", n->attrs.name + "_backward_mid_grad_grad", - {gx, nnvm::NodeEntry{g_lx}}, nullptr, &n); - auto ggx = MakeNode("negative", n->attrs.name + "_backward_grad_grad", - {nnvm::NodeEntry{ggx_mid}}, nullptr, &n); + auto d2ydx2_mid = MakeNode("elemwise_mul", n->attrs.name + "_d2ydx2_mid", + {dydx_mul_dldy, nnvm::NodeEntry{dlogx}}, nullptr, &n); + auto d2ydx2 = MakeNode("negative", n->attrs.name + "_d2ydx2", + {nnvm::NodeEntry{d2ydx2_mid}}, nullptr, &n); std::vector ret; ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad", - {ograds[0], gx}, nullptr, &n)); + {ograds[0], nnvm::NodeEntry{dydx}}, nullptr, &n)); ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad_inp", - {ograds[0], nnvm::NodeEntry{ggx}}, nullptr, &n)); + {ograds[0], nnvm::NodeEntry{d2ydx2}}, nullptr, &n)); return ret; }); diff --git a/tests/python/unittest/test_higher_order_grad.py b/tests/python/unittest/test_higher_order_grad.py index 77bfa68157aa..4f1ea9a6c7b8 100644 --- a/tests/python/unittest/test_higher_order_grad.py +++ b/tests/python/unittest/test_higher_order_grad.py @@ -108,13 +108,26 @@ def grad_grad_op(x): def check_second_order_unary(x, op, grad_grad_op): x = nd.array(x) - expect_grad_grad = grad_grad_op(x) + grad_grad_x = grad_grad_op(x) x.attach_grad() + + # Manual head_grads. + y_grad = nd.random.normal(shape=x.shape) + head_grad_grads = nd.random.normal(shape=x.shape) + + # Perform compute. with autograd.record(): y = op(x) - y_grad = autograd.grad(y, x, create_graph=True, retain_graph=True)[0] - y_grad.backward() - assert_almost_equal(expect_grad_grad.asnumpy(), x.grad.asnumpy()) + x_grad = autograd.grad(heads=y, variables=x, head_grads=y_grad, + create_graph=True, retain_graph=True)[0] + x_grad.backward(head_grad_grads) + + # Compute expected values. + expected_grad_grad = grad_grad_x.asnumpy() * head_grad_grads.asnumpy() * \ + y_grad.asnumpy() + + # Validate the gradients. + assert_almost_equal(expected_grad_grad, x.grad.asnumpy()) if __name__ == '__main__': From 12c422697413cfd69d846cdb4d1d487c487220e2 Mon Sep 17 00:00:00 2001 From: Lin Yuan Date: Thu, 20 Jun 2019 02:27:45 -0700 Subject: [PATCH 002/813] update committer info (#15289) --- CONTRIBUTORS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c76f8c6edbc8..43d988037de9 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -71,6 +71,8 @@ The committers are the granted write access to the project. - Tao is a major contributor to the MXNet MKL-DNN backend and performance on CPU. * [Zach Kimberg](https://github.com/zachgk) - Zach is one of the major maintainers of the MXNet Scala package. +* [Lin Yuan](https://github.com/apeforest) + - Lin supports MXNet distributed training using Horovod and is also a major contributor to higher order gradients. ### Become a Committer @@ -199,7 +201,6 @@ List of Contributors * [Thomas Delteil](https://github.com/ThomasDelteil) * [Jesse Brizzi](https://github.com/jessebrizzi) * [Hang Zhang](http://hangzh.com) -* [Lin Yuan](https://github.com/apeforest) * [Kou Ding](https://github.com/chinakook) * [Istvan Fehervari](https://github.com/ifeherva) * [Aaron Markham](https://github.com/aaronmarkham) From 2de0db0911f2e71728fa85ab342bd99a10974fc9 Mon Sep 17 00:00:00 2001 From: Piyush Ghai Date: Thu, 20 Jun 2019 13:03:48 -0700 Subject: [PATCH 003/813] Showing proper error when csr array is not 2D in shape. (#15242) * Showing proper error when csr array is not 2D in shape. * Fixed failing CI * Nudge to CI --- python/mxnet/ndarray/ndarray.py | 4 ++++ tests/python/unittest/test_sparse_ndarray.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/python/mxnet/ndarray/ndarray.py b/python/mxnet/ndarray/ndarray.py index 7e21daedcde1..3fb1af6a7336 100644 --- a/python/mxnet/ndarray/ndarray.py +++ b/python/mxnet/ndarray/ndarray.py @@ -2227,6 +2227,10 @@ def tostype(self, stype): NDArray, CSRNDArray or RowSparseNDArray A copy of the array with the chosen storage stype """ + if stype == 'csr' and len(self.shape) != 2: + raise ValueError("To convert to a CSR, the NDArray should be 2 Dimensional. Current " + "shape is %s" % str(self.shape)) + return op.cast_storage(self, stype=stype) def to_dlpack_for_read(self): diff --git a/tests/python/unittest/test_sparse_ndarray.py b/tests/python/unittest/test_sparse_ndarray.py index 3b4c684e8696..9a1fce4ff197 100644 --- a/tests/python/unittest/test_sparse_ndarray.py +++ b/tests/python/unittest/test_sparse_ndarray.py @@ -963,6 +963,11 @@ def test_sparse_nd_check_format(): indptr_list = [0, -2, 2, 3] a = mx.nd.sparse.csr_matrix((data_list, indices_list, indptr_list), shape=shape) assertRaises(mx.base.MXNetError, a.check_format) + # CSR format should be 2 Dimensional. + a = mx.nd.array([1, 2, 3]) + assertRaises(ValueError, a.tostype, 'csr') + a = mx.nd.array([[[1, 2, 3]]]) + assertRaises(ValueError, a.tostype, 'csr') # Row Sparse format indices should be less than the number of rows shape = (3, 2) data_list = [[1, 2], [3, 4]] From c45d23b5e89344ecd4151097f94e3478803933cc Mon Sep 17 00:00:00 2001 From: Przemyslaw Tredak Date: Thu, 20 Jun 2019 16:50:54 -0700 Subject: [PATCH 004/813] Proper bulking of ops not using FCompute (#15272) * Less syncs * Trigger CI --- src/imperative/imperative_utils.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imperative/imperative_utils.h b/src/imperative/imperative_utils.h index 4e63e4d2b3be..b867162abc9b 100644 --- a/src/imperative/imperative_utils.h +++ b/src/imperative/imperative_utils.h @@ -464,7 +464,7 @@ inline void PushFComputeEx(const FComputeEx& fn, InvalidateOutputs(outputs, req); #endif fn(attrs, opctx, inputs, req, outputs); - if (ctx.dev_mask() == gpu::kDevMask && exec_type == ExecType::kSync) { + if (ctx.dev_mask() == gpu::kDevMask && exec_type == ExecType::kSync && !rctx.is_bulk) { rctx.get_stream()->Wait(); } }; @@ -512,7 +512,7 @@ inline void PushOperator(const OpStatePtr& state, #endif fcompute_ex(state, opctx, inputs, req, outputs); if (ctx.dev_mask() == gpu::kDevMask && exec_type == ExecType::kSync - && rctx.get_stream()) { + && rctx.get_stream() && !rctx.is_bulk) { rctx.get_stream()->Wait(); } }; @@ -562,7 +562,7 @@ inline void PushOperator(const OpStatePtr& state, // post-fcompute fallback, cast to original storage type, if necessary CastNonDefaultStorage(post_temp_src, post_temp_dst, opctx, is_gpu); if (is_gpu && exec_type == ExecType::kSync - && rctx.get_stream()) { + && rctx.get_stream() && !rctx.is_bulk) { rctx.get_stream()->Wait(); } }; From 4a9e9f67a5e7e5ddf2e394c6381f4f6449626af2 Mon Sep 17 00:00:00 2001 From: Disi A Date: Fri, 21 Jun 2019 04:02:38 -0400 Subject: [PATCH 005/813] Typo fix in plan_memory relase -> release. (#15299) --- src/nnvm/plan_memory.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnvm/plan_memory.cc b/src/nnvm/plan_memory.cc index ce442ed1a0cf..427d1c751cba 100644 --- a/src/nnvm/plan_memory.cc +++ b/src/nnvm/plan_memory.cc @@ -301,7 +301,7 @@ size_t AllocMemory(const Graph& ret, const IndexedGraph& idx, auto sid = storage[eid]; // storage_ref_count == 0 means it is taken by inplace op if (sid < 0) continue; - // if we decrease it to zero, means we are ready to relase + // if we decrease it to zero, means we are ready to release --storage_ref_count[sid]; if (storage_ref_count[sid] == 0) { allocator->Release(sid, nid); From 3f8fd005402968e77ecb05eb3c73eb1c5e35b332 Mon Sep 17 00:00:00 2001 From: Zhaoqi Zhu Date: Fri, 21 Jun 2019 13:18:28 -0700 Subject: [PATCH 006/813] Fixing duplication in operator profiling (#15240) * initial * adding a bool in ProfileStat to control if add to aggregate stats * add test case * fix style * stylefix * testcases * fix type * fix comment * Update profiler.h --- src/profiler/aggregate_stats.cc | 4 +++- src/profiler/profiler.h | 15 +++++++++++++++ tests/python/unittest/test_profiler.py | 22 ++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/profiler/aggregate_stats.cc b/src/profiler/aggregate_stats.cc index ff3894650545..98ac5e96b761 100644 --- a/src/profiler/aggregate_stats.cc +++ b/src/profiler/aggregate_stats.cc @@ -85,7 +85,9 @@ inline std::priority_queue void AggregateStats::OnProfileStat(const ProfileStat& stat) { std::unique_lock lk(m_); - stat.SaveAggregate(&stats_[stat.categories_.c_str()][stat.name_.c_str()]); + if (stat.enable_aggregate_) { + stat.SaveAggregate(&stats_[stat.categories_.c_str()][stat.name_.c_str()]); + } } void AggregateStats::DumpTable(std::ostream& os, int sort_by, int ascending) { diff --git a/src/profiler/profiler.h b/src/profiler/profiler.h index f9eb0af9acc1..a6d9ecf06fee 100644 --- a/src/profiler/profiler.h +++ b/src/profiler/profiler.h @@ -128,6 +128,9 @@ struct ProfileStat { /*! \brief operation categories (comma-delimited) */ profile_stat_string categories_; + /*! \brief whether to add this stat to AggregateStats */ + bool enable_aggregate_ = true; + /* !\brief Process id */ size_t process_id_ = current_process_id(); @@ -807,6 +810,13 @@ struct ProfileTask : public ProfileDuration { ProfileObjectType type() const override { return kTask; } + /*! + * \brief Whether to add stat to AggregateStats + */ + void enableAggregateStats(bool enabled = true) { + enable_aggregate_ = enabled; + } + protected: /*! * \brief Task statistic object @@ -831,6 +841,7 @@ struct ProfileTask : public ProfileDuration { inline void SendStat() { Profiler::Get()->AddNewProfileStat([this](ProfileTaskStat *stat) { stat->categories_.set(domain_->name()); + stat->enable_aggregate_ = enable_aggregate_; }, name_.c_str(), start_time_, ProfileStat::NowInMicrosec()); } /*! \brief Task name */ @@ -843,6 +854,8 @@ struct ProfileTask : public ProfileDuration { VTUNE_ONLY_CODE(std::unique_ptr vtune_task_); /*! \brief NVTX duration object */ NVTX_ONLY_CODE(std::unique_ptr nvtx_duration_); + /*! \brief whether to add this stat to AggregateStats */ + bool enable_aggregate_ = true; protected: /*! \brief Task's start tick */ @@ -1150,6 +1163,8 @@ struct ProfileOperator : public ProfileEvent { , as_task_(name, &domain_) , name_(name) , attributes_(attributes) { + // make as_task_ not to add stat to AggregateStats; otherwise we will add twice + as_task_.enableAggregateStats(false); SetCategories(domain_.name()); } /*! diff --git a/tests/python/unittest/test_profiler.py b/tests/python/unittest/test_profiler.py index 09e571be0088..b76bfbc82d12 100644 --- a/tests/python/unittest/test_profiler.py +++ b/tests/python/unittest/test_profiler.py @@ -269,6 +269,28 @@ def check_sorting(debug_str, sort_by, ascending): check_sorting(debug_str, sb, asc) profiler.set_state('stop') +def test_aggregate_duplication(): + file_name = 'test_aggregate_duplication.json' + enable_profiler(profile_filename = file_name, run=True, continuous_dump=True, \ + aggregate_stats=True) + inp = mx.nd.zeros(shape=(100, 100)) + y = mx.nd.sqrt(inp) + inp = inp + 1 + inp = inp + 1 + mx.nd.waitall() + profiler.dump(False) + debug_str = profiler.dumps(format = 'json') + target_dict = json.loads(debug_str) + assert 'Time' in target_dict and 'operator' in target_dict['Time'] \ + and 'sqrt' in target_dict['Time']['operator'] \ + and 'Count' in target_dict['Time']['operator']['sqrt'] \ + and '_plus_scalar' in target_dict['Time']['operator'] \ + and 'Count' in target_dict['Time']['operator']['_plus_scalar'] + # they are called once and twice respectively + assert target_dict['Time']['operator']['sqrt']['Count'] == 1 + assert target_dict['Time']['operator']['_plus_scalar']['Count'] == 2 + profiler.set_state('stop') + if __name__ == '__main__': import nose nose.runmodule() From 8b5f376d17b644385706016caf8b1e58e95d96df Mon Sep 17 00:00:00 2001 From: Rohit Kumar Srivastava Date: Fri, 21 Jun 2019 13:21:20 -0700 Subject: [PATCH 007/813] [MXNET-1413] Adding Large Tensor support for sort operators (#15170) --- src/operator/tensor/init_op.h | 2 +- src/operator/tensor/ordering_op-inl.h | 190 +++++++++++++++----------- tests/nightly/test_large_array.py | 26 ++++ tests/python/unittest/test_ndarray.py | 13 +- 4 files changed, 149 insertions(+), 82 deletions(-) diff --git a/src/operator/tensor/init_op.h b/src/operator/tensor/init_op.h index fd491534f83a..c7a10541adbd 100644 --- a/src/operator/tensor/init_op.h +++ b/src/operator/tensor/init_op.h @@ -487,7 +487,7 @@ void EyeFill(const nnvm::NodeAttrs& attrs, struct range_fwd { template - MSHADOW_XINLINE static void Map(index_t i, int repeat, DType start, DType step, + MSHADOW_XINLINE static void Map(index_t i, index_t repeat, DType start, DType step, int req, DType* out) { KERNEL_ASSIGN(out[i], req, start + (i/repeat) * step); } diff --git a/src/operator/tensor/ordering_op-inl.h b/src/operator/tensor/ordering_op-inl.h index 1dda90104205..98bca3a43c60 100644 --- a/src/operator/tensor/ordering_op-inl.h +++ b/src/operator/tensor/ordering_op-inl.h @@ -81,12 +81,18 @@ struct TopKParam : public dmlc::Parameter { .describe("Whether to choose k largest or k smallest elements." " Top K largest elements will be chosen if set to false."); DMLC_DECLARE_FIELD(dtype) + // TODO(srivrohi): remove support for real data type in mxnet-2.0 .add_enum("uint8", mshadow::kUint8) .add_enum("int32", mshadow::kInt32) + .add_enum("int64", mshadow::kInt64) .add_enum("float16", mshadow::kFloat16) .add_enum("float32", mshadow::kFloat32) .add_enum("float64", mshadow::kFloat64) - .set_default(mshadow::kFloat32) +#if MXNET_USE_INT64_TENSOR_SIZE == 1 + .set_default(mshadow::kInt64) +#else + .set_default(mshadow::kInt32) +#endif .describe("DType of the output indices when ret_typ is \"indices\" or \"both\". " "An error will be raised if the selected data type cannot precisely represent the " "indices."); @@ -116,21 +122,33 @@ struct ArgSortParam : public dmlc::Parameter { DMLC_DECLARE_FIELD(is_ascend).set_default(true) .describe("Whether to sort in ascending or descending order."); DMLC_DECLARE_FIELD(dtype) + // TODO(srivrohi): remove support for real data type in mxnet-2.0 .add_enum("uint8", mshadow::kUint8) .add_enum("int32", mshadow::kInt32) + .add_enum("int64", mshadow::kInt64) .add_enum("float16", mshadow::kFloat16) .add_enum("float32", mshadow::kFloat32) .add_enum("float64", mshadow::kFloat64) - .set_default(mshadow::kFloat32) +#if USE_INT64_TENSOR_SIZE == 1 + .set_default(mshadow::kInt64) +#else + .set_default(mshadow::kInt32) +#endif .describe("DType of the output indices. It is only valid when ret_typ is \"indices\" or" " \"both\". An error will be raised if the selected data type cannot precisely " "represent the indices."); } }; -inline void ParseTopKParam(const mxnet::TShape& src_shape, const TopKParam& param, - mxnet::TShape *target_shape, int *batch_size, int *element_num, - int *axis, int *k, bool *do_transpose, bool *is_ascend) { +inline void ParseTopKParam(const TShape& src_shape, + const TopKParam& param, + TShape *target_shape, + size_t *batch_size, + index_t *element_num, + int *axis, + index_t *k, + bool *do_transpose, + bool *is_ascend) { *do_transpose = false; *k = param.k; *is_ascend = param.is_ascend; @@ -179,14 +197,14 @@ using namespace mshadow; struct fill_ind_to_one { template - MSHADOW_XINLINE static void Map(int i, const int* indices, DType* out) { + MSHADOW_XINLINE static void Map(int i, const index_t* indices, DType* out) { out[indices[i]] = static_cast(1); } }; struct fill_ind { template - MSHADOW_XINLINE static void Map(int i, const int* indices, const DType* val, + MSHADOW_XINLINE static void Map(int i, const index_t* indices, const DType* val, int req, DType* out) { KERNEL_ASSIGN(out[indices[i]], req, val[i]); } @@ -194,39 +212,43 @@ struct fill_ind { template MSHADOW_FORCE_INLINE void TopKSort(const Tensor& dat, - const Tensor& ind, + const Tensor& ind, const Tensor& work, - int K, int N, bool is_ascend, + index_t K, index_t N, bool is_ascend, Stream *s) { // Use full sort when K is relatively large. const bool full_sort(K*8 > N); // Batch size. - const int M(work.size(0)/(sizeof(DType)*N)); + const index_t M(work.size(0)/(sizeof(DType)*N)); const int omp_threads(engine::OpenMP::Get()->GetRecommendedOMPThreadCount()); #pragma omp parallel for num_threads(omp_threads) - for (int i = 0; i < M; ++i) { + for (index_t i = 0; i < M; ++i) { // Tensor `work` stores the flattened source data, while `dat` stores the sorted result. DType *vals = reinterpret_cast(work.dptr_); DType *sorted_vals = dat.dptr_+i*N; - int *indices = ind.dptr_+i*N; + index_t *indices = ind.dptr_+i*N; if (is_ascend) { if (full_sort) { std::sort(indices, indices+N, - [&](const int& i1, const int& i2){ return vals[i1] < vals[i2]; }); + [&](const index_t& i1, const index_t& i2){ + return vals[i1] < vals[i2]; }); } else { std::partial_sort(indices, indices+K, indices+N, - [&](const int& i1, const int& i2){ return vals[i1] < vals[i2]; }); + [&](const index_t& i1, const index_t& i2){ + return vals[i1] < vals[i2]; }); } } else { if (full_sort) { std::sort(indices, indices+N, - [&](const int& i1, const int& i2){ return vals[i1] > vals[i2]; }); + [&](const index_t& i1, const index_t& i2){ + return vals[i1] > vals[i2]; }); } else { std::partial_sort(indices, indices+K, indices+N, - [&](const int& i1, const int& i2){ return vals[i1] > vals[i2]; }); + [&](const index_t& i1, const index_t& i2){ + return vals[i1] > vals[i2]; }); } } - for (int j = 0; j < K; ++j) { + for (index_t j = 0; j < K; ++j) { sorted_vals[j] = vals[indices[j]]; } } @@ -235,18 +257,19 @@ MSHADOW_FORCE_INLINE void TopKSort(const Tensor& dat, #ifdef __CUDACC__ template -MSHADOW_XINLINE bool TopKCompare(DType val1, int ind1, DType val2, int ind2, bool is_ascend) { +MSHADOW_XINLINE bool TopKCompare(DType val1, index_t ind1, DType val2, index_t ind2, + bool is_ascend) { // Negative indices denote undefined values which are considered arbitrary small resp. large. return (ind2 < 0) || (ind1 >= 0 && ((is_ascend && val1 < val2) || (!is_ascend && val1 > val2))); } template -MSHADOW_XINLINE void MergeTopK(int K, DType *val1, int *ind1, DType *val2, int *ind2, +MSHADOW_XINLINE void MergeTopK(index_t K, DType *val1, index_t *ind1, DType *val2, index_t *ind2, bool is_ascend) { // In-place merge of two sorted top-K lists into val1/ind1. First determine the intervals // [0,..,i1], [0,..i2] of the two lists that will be part of the merged list. - int i1(K-1), i2(K-1); - for (int i = 0; i < K; ++i) { + index_t i1(K-1), i2(K-1); + for (index_t i = 0; i < K; ++i) { if (TopKCompare(val1[i1], ind1[i1], val2[i2], ind2[i2], is_ascend)) { --i2; } else { @@ -254,7 +277,7 @@ MSHADOW_XINLINE void MergeTopK(int K, DType *val1, int *ind1, DType *val2, int * } } // Now merge the lists from back to front. - for (int i = K; i--;) { + for (index_t i = K; i--;) { if (i2 < 0 || i1 >= 0 && TopKCompare(val2[i2], ind2[i2], val1[i1], ind1[i1], is_ascend)) { val1[i] = val1[i1]; ind1[i] = ind1[i1]; @@ -268,28 +291,29 @@ MSHADOW_XINLINE void MergeTopK(int K, DType *val1, int *ind1, DType *val2, int * } template -__global__ void PartialSortSmallK(int K, int N, DType *val, int *ind, bool is_ascend) { +__global__ void PartialSortSmallK(index_t K, index_t N, DType *val, index_t *ind, bool is_ascend) { // Buffer for pairwise reduction. - extern __shared__ int buff[]; + extern __shared__ index_t buff[]; // Start of buffer sections associated with this thread. - const int offset(threadIdx.x*K); - int *ind_buff = &buff[offset]; + const index_t offset(threadIdx.x*K); + index_t *ind_buff = &buff[offset]; DType *val_buff = reinterpret_cast(&buff[blockDim.x*K])+offset; // Initialize top-K values for this thread. - for (int i = 0; i < K; ++i) { + for (index_t i = 0; i < K; ++i) { ind_buff[i] = -1; } // Range of values this thread cares about. Each thread block processes // a different batch item (i.e. a different set of ind/val where we // have to select the top-K elements). All threads within the same // block work on the same batch item. - const int first(blockIdx.x*N+threadIdx.x), last((blockIdx.x+1)*N); + const index_t first(blockIdx.x*N+threadIdx.x), last((blockIdx.x+1)*N); // Select top-K from this range and store it sorted in the buffer. // We assume a small K, so linear insertion is o.k. - for (int i = first; i < last; i += blockDim.x) { + for (index_t i = first; i < last; i += blockDim.x) { DType cur_val(val[i]); - int cur_ind(ind[i]); - for (int j = K; j-- && TopKCompare(cur_val, cur_ind, val_buff[j], ind_buff[j], is_ascend); ) { + index_t cur_ind(ind[i]); + for (index_t j = K; j-- && TopKCompare(cur_val, cur_ind, val_buff[j], + ind_buff[j], is_ascend); ) { if (j+1 < K) { val_buff[j+1] = val_buff[j]; ind_buff[j+1] = ind_buff[j]; @@ -300,7 +324,7 @@ __global__ void PartialSortSmallK(int K, int N, DType *val, int *ind, bool is_as } // Recursive merge of sorted lists for this thread block. Note that blockDim.x is not // necessary a power of two, therefore the additional checks for last_s. - for (unsigned int s = (blockDim.x+1)/2, last_s = blockDim.x; + for (index_t s = (blockDim.x+1)/2, last_s = blockDim.x; last_s > 1; last_s = s, s = (s+1)/2) { __syncthreads(); if (threadIdx.x < s && threadIdx.x+s < last_s) { @@ -309,7 +333,7 @@ __global__ void PartialSortSmallK(int K, int N, DType *val, int *ind, bool is_as } // Final updates on master thread. if (threadIdx.x == 0) { - for (int i = 0; i < K; ++i) { + for (index_t i = 0; i < K; ++i) { ind[blockIdx.x*N+i] = ind_buff[i]; val[blockIdx.x*N+i] = val_buff[i]; } @@ -318,20 +342,21 @@ __global__ void PartialSortSmallK(int K, int N, DType *val, int *ind, bool is_as template MSHADOW_FORCE_INLINE void TopKSort(const Tensor& dat, - const Tensor& ind, + const Tensor& ind, const Tensor& work, - int K, int N, bool is_ascend, + index_t K, index_t N, bool is_ascend, Stream *s) { // Use full sort for all but very small K for which we // can do a partial sort entirely within shared memory. const bool full_sort(K > 5); // Batch size. - const int M(dat.size(0)/N); + const index_t M(dat.size(0)/N); if (full_sort) { // Divide workspace into two parts. The first one is needed to store batch ids. - size_t alignment = std::max(sizeof(DType), sizeof(int)); - size_t id_size = PadBytes(sizeof(int) * ind.size(0), alignment); - Tensor batch_id(reinterpret_cast(work.dptr_), Shape1(ind.size(0)), s); + size_t alignment = std::max(sizeof(DType), sizeof(index_t)); + size_t id_size = PadBytes(sizeof(index_t) * ind.size(0), alignment); + Tensor batch_id(reinterpret_cast(work.dptr_), + Shape1(ind.size(0)), s); Tensor sort_work(work.dptr_+id_size, Shape1(work.size(0)-id_size), s); mxnet::op::SortByKey(dat, ind, is_ascend, &sort_work); if (M > 1) { @@ -380,20 +405,22 @@ void TopKImpl(const RunContext &ctx, Tensor workspace; Tensor temp_workspace; Tensor sorted_dat; - Tensor indices, sel_indices; - int batch_size, element_num; // number of batches + the size of each batch + Tensor indices, sel_indices; + size_t batch_size = 0; + index_t element_num = 0; // number of batches + the size of each batch int axis = 0; bool do_transpose = false; bool is_ascend = false; - int k = 0; + index_t k = 0; size_t alignment = std::max(sizeof(DType), sizeof(int)); mxnet::TShape target_shape; ParseTopKParam(src.shape_, param, &target_shape, &batch_size, &element_num, &axis, &k, &do_transpose, &is_ascend); - CHECK_LE(element_num, mxnet::common::MaxIntegerValue()) - << "'IDType' does not have a sufficient precision to represent the indices of the input array. " - << "The total element_num is " << element_num << ", but the selected IDType can only represent " - << mxnet::common::MaxIntegerValue() << " elements"; + CHECK_LE(element_num, mxnet::common::MaxIntegerValue()) + << "'index_t' does not have a sufficient precision to represent " + << "the indices of the input array. The total element_num is " + << element_num << ", but the selected index_t can only represent " + << mxnet::common::MaxIntegerValue() << " elements"; Tensor dat = src.FlatTo3D(axis, axis, s); size_t temp_size = 0; // Temp space needed by the gpu-based full sorts. @@ -404,11 +431,11 @@ void TopKImpl(const RunContext &ctx, temp_size = std::max(temp_size, mxnet::op::SortByKeyWorkspaceSize(src.Size())); // Additional temp space for gpu full sorts for batch ids. - temp_size += PadBytes(sizeof(int) * src.Size(), alignment); + temp_size += PadBytes(sizeof(index_t) * src.Size(), alignment); // Temp space for cpu sorts. temp_size = std::max(temp_size, static_cast(sizeof(DType) * src.Size())); size_t workspace_size = temp_size + PadBytes(sizeof(DType) * src.Size(), alignment) - + PadBytes(sizeof(int) * src.Size(), alignment); + + PadBytes(sizeof(index_t) * src.Size(), alignment); if (param.ret_typ == topk_enum::kReturnMask) { workspace_size += PadBytes(sizeof(int) * batch_size * k, alignment); } @@ -417,14 +444,14 @@ void TopKImpl(const RunContext &ctx, sorted_dat = Tensor(reinterpret_cast(workspace_curr_ptr), Shape1(src.Size()), s); // contain sorted dat workspace_curr_ptr += PadBytes(sizeof(DType) * src.Size(), alignment); - indices = Tensor(reinterpret_cast(workspace_curr_ptr), + indices = Tensor(reinterpret_cast(workspace_curr_ptr), Shape1(src.Size()), s); // indices in the original matrix - workspace_curr_ptr += PadBytes(sizeof(int) * src.Size(), alignment); + workspace_curr_ptr += PadBytes(sizeof(index_t) * src.Size(), alignment); if (param.ret_typ == topk_enum::kReturnMask) { - sel_indices = Tensor(reinterpret_cast(workspace_curr_ptr), + sel_indices = Tensor(reinterpret_cast(workspace_curr_ptr), Shape1(batch_size * k), s); - workspace_curr_ptr += PadBytes(sizeof(int) * batch_size * k, alignment); + workspace_curr_ptr += PadBytes(sizeof(index_t) * batch_size * k, alignment); CHECK_EQ(sel_indices.CheckContiguous(), true); } @@ -454,7 +481,7 @@ void TopKImpl(const RunContext &ctx, workspace_curr_ptr += temp_size; } - mxnet_op::Kernel::Launch(s, batch_size * element_num, 1, 0, 1, + mxnet_op::Kernel::Launch(s, batch_size * element_num, 1, index_t{0}, index_t{1}, kWriteTo, indices.dptr_); CHECK_EQ(indices.CheckContiguous(), true); @@ -551,7 +578,7 @@ void TopK(const nnvm::NodeAttrs& attrs, }); } else { MXNET_NO_FLOAT16_TYPE_SWITCH(inputs[0].type_flag_, DType, { - TopKImpl(ctx.run_ctx, ctx.requested[0], req, inputs[0], outputs, param); + TopKImpl(ctx.run_ctx, ctx.requested[0], req, inputs[0], outputs, param); }); } } @@ -569,7 +596,8 @@ void Sort(const nnvm::NodeAttrs& attrs, topk_param.k = 0; topk_param.ret_typ = topk_enum::kReturnValue; MXNET_NO_FLOAT16_TYPE_SWITCH(inputs[0].type_flag_, DType, { - TopKImpl(ctx.run_ctx, ctx.requested[0], req, inputs[0], outputs, topk_param); + TopKImpl(ctx.run_ctx, ctx.requested[0], req, inputs[0], + outputs, topk_param); }); } @@ -605,30 +633,32 @@ void TopKBackwardImpl(const OpContext &ctx, using namespace mshadow::expr; Stream *s = ctx.run_ctx.get_stream(); CHECK(param.ret_typ == topk_enum::kReturnValue || param.ret_typ == topk_enum::kReturnBoth); - int batch_size, element_num; // number of batches + the size of each batch + size_t batch_size = 0; + index_t element_num = 0; // number of batches + the size of each batch int axis = 0; bool do_transpose = false; bool is_ascend = false; - int k = 0; + index_t k = 0; mxnet::TShape target_shape; ParseTopKParam(outputs[0].shape_, param, &target_shape, &batch_size, &element_num, &axis, &k, &do_transpose, &is_ascend); CHECK_LE(element_num, mxnet::common::MaxIntegerValue()) - << "'IDType' does not have a sufficient precision to represent the indices of the input array. " - << "The total element_num is " << element_num << ", but the selected IDType can only represent " + << "'IDType' does not have a sufficient precision to represent " + << "the indices of the input array. The total element_num is " << element_num + << ", but the selected index_t can only represent " << mxnet::common::MaxIntegerValue() << " elements"; - Tensor workspace = - ctx.requested[0].get_space_typed(Shape1(batch_size * k + batch_size), s); - Tensor sel_indices = - Tensor(workspace.dptr_, Shape1(batch_size * k), s); - Tensor batch_shift = - Tensor(workspace.dptr_ + batch_size * k, Shape1(batch_size), s); + Tensor workspace = + ctx.requested[0].get_space_typed(Shape1(batch_size * k + batch_size), s); + Tensor sel_indices = + Tensor(workspace.dptr_, Shape1(batch_size * k), s); + Tensor batch_shift = + Tensor(workspace.dptr_ + batch_size * k, Shape1(batch_size), s); Tensor out_grad = inputs[0].get_with_shape(Shape2(inputs[0].shape_.Size(), 1), s); Tensor in_grad = outputs[0].get_with_shape(Shape2(outputs[0].shape_.Size(), 1), s); - mxnet_op::Kernel::Launch(s, batch_size, 1, 0, element_num, kWriteTo, + mxnet_op::Kernel::Launch(s, batch_size, 1, index_t{0}, element_num, kWriteTo, batch_shift.dptr_); if (do_transpose) { Tensor indices = inputs[2].FlatTo1D(s); @@ -639,13 +669,13 @@ void TopKBackwardImpl(const OpContext &ctx, mxnet::TShape(Shape3(src_shape[0], src_shape[2], k))), Shape3(0, 2, 1)), Shape1(batch_size * k)); - sel_indices += tcast(indices); + sel_indices += tcast(indices); sel_indices = transpose_indices(sel_indices, Shape3(src_shape[0], src_shape[2], src_shape[1]), Shape3(0, 2, 1)); } else { Tensor indices = inputs[2].get_with_shape(Shape2(batch_size, k), s); - sel_indices = reshape(tcast(indices) + + sel_indices = reshape(tcast(indices) + broadcast_to(inplace_reshape(batch_shift, Shape2(batch_size, 1)), mxnet::TShape(Shape2(batch_size, k))), Shape1(batch_size * k)); @@ -680,7 +710,7 @@ void TopKBackward_(const nnvm::NodeAttrs& attrs, }); } else if (param.ret_typ == topk_enum::kReturnValue) { MXNET_NO_FLOAT16_TYPE_SWITCH(inputs[0].type_flag_, DType, { - TopKBackwardImpl(ctx, inputs, req, outputs, param); + TopKBackwardImpl(ctx, inputs, req, outputs, param); }); } else { LOG(FATAL) << "Not Implemented"; @@ -715,14 +745,11 @@ inline bool TopKType(const nnvm::NodeAttrs& attrs, size_t out_size = out_attrs->size(); CHECK_EQ(in_size, 1); CHECK(out_size == 1 || out_size == 2); + // out_attr[0] -> stores value + // out_attr[1] -> stores indices if (out_size > 1) { - if (param.ret_typ == topk_enum::kReturnValue) { - CHECK(type_assign(&(*out_attrs)[1], mshadow::kInt32)) + CHECK(type_assign(&(*out_attrs)[1], param.dtype)) << "Failed to set the type of ret_indices."; - } else { - CHECK(type_assign(&(*out_attrs)[1], param.dtype)) - << "Failed to set the type of ret_indices."; - } } if (param.ret_typ == topk_enum::kReturnIndices) { CHECK(type_assign(&(*out_attrs)[0], param.dtype)) @@ -752,11 +779,12 @@ inline bool TopKShapeImpl(const TopKParam& param, CHECK_EQ(out_attrs->size(), 2U); } mxnet::TShape& in_shape = (*in_attrs)[0]; - int batch_size, element_num; // number of batches + the size of each batch + size_t batch_size = 0; + index_t element_num = 0; // number of batches + the size of each batch int axis = 0; bool do_transpose = false; bool is_ascend = false; - int k = 0; + index_t k = 0; mxnet::TShape target_shape; ParseTopKParam(in_shape, param, &target_shape, &batch_size, &element_num, &axis, &k, &do_transpose, &is_ascend); @@ -785,8 +813,12 @@ inline bool SortType(const nnvm::NodeAttrs& attrs, size_t out_size = out_attrs->size(); CHECK_EQ(in_size, 1); CHECK_EQ(out_size, 2); +#if MXNET_USE_INT64_TENSOR_SIZE == 1 + CHECK(type_assign(&(*out_attrs)[1], mshadow::kInt64)) +#else CHECK(type_assign(&(*out_attrs)[1], mshadow::kInt32)) - << "Failed to set the type of ret_indices to int32."; +#endif + << "Failed to set the type of ret_indices"; CHECK(type_assign(&data_type, (*in_attrs)[0])) << "Incompatible dtype of input, in_attrs[0]=" << (*in_attrs)[0]; CHECK(type_assign(&data_type, (*out_attrs)[0])) << "Incompatible dtype of output, out_attrs[0]=" @@ -816,7 +848,7 @@ inline bool ArgSortType(const nnvm::NodeAttrs& attrs, std::vector *out_attrs) { const ArgSortParam& param = nnvm::get(attrs.parsed); CHECK(type_assign(&(*out_attrs)[0], param.dtype)) - << "Failed to set the type of ret_indices to int32."; + << "Failed to set the type of ret_indices."; return true; } diff --git a/tests/nightly/test_large_array.py b/tests/nightly/test_large_array.py index cbba608d5d2f..0df481a01987 100644 --- a/tests/nightly/test_large_array.py +++ b/tests/nightly/test_large_array.py @@ -326,6 +326,32 @@ def test_softmax(): assert_almost_equal(output.asnumpy(), true_output, rtol=1e-5, atol=1e-5) +def test_argsort(): + b = create_2d_tensor(rows=LARGE_X, columns=SMALL_Y) + s = nd.argsort(b, axis=0, is_ascend=False, dtype=np.int64) + mx.nd.waitall() + assert (s[0].asnumpy() == (LARGE_X - 1)).all() + + +def test_sort(): + b = create_2d_tensor(rows=LARGE_X, columns=SMALL_Y) + s = nd.sort(b, axis=0, is_ascend=False) + assert np.sum(s[-1][SMALL_Y//2:SMALL_Y].asnumpy() == 0).all() + s = nd.sort(b, is_ascend=False) + assert np.sum(s[0].asnumpy() == 0).all() + + +def test_topk(): + b = create_2d_tensor(rows=LARGE_X, columns=SMALL_Y) + k = nd.topk(b, k=10, axis=0, dtype=np.int64) + assert np.sum(k.asnumpy() == (LARGE_X - 1)) == SMALL_Y + ind, val = mx.nd.topk(b, k=3, axis=0, dtype=np.int64, ret_typ="both", is_ascend=False) + assert np.all(ind == val) + b = create_2d_tensor(rows=SMALL_Y, columns=LARGE_X) + l = nd.topk(b, k=1, axis=-1, dtype=np.int64, ret_typ="value") + assert l.sum() == np.sum(np.arange(0, SMALL_Y)) + + if __name__ == '__main__': import nose nose.runmodule() diff --git a/tests/python/unittest/test_ndarray.py b/tests/python/unittest/test_ndarray.py index e5315900c725..d84b4f082b63 100644 --- a/tests/python/unittest/test_ndarray.py +++ b/tests/python/unittest/test_ndarray.py @@ -29,6 +29,7 @@ from mxnet.test_utils import np_reduce from mxnet.test_utils import same from mxnet.test_utils import random_sample, rand_shape_nd +from mxnet import runtime from numpy.testing import assert_allclose import mxnet.autograd @@ -747,6 +748,7 @@ def test_linspace(): def test_order(): ctx = default_context() dat_size = 5 + is_large_tensor_enabled = runtime.Features().is_enabled('INT64_TENSOR_SIZE') def gt_topk(dat, axis, ret_typ, k, is_ascend): if ret_typ == "indices": if is_ascend: @@ -819,7 +821,11 @@ def get_large_matrix(): # test for ret_typ=indices nd_ret_topk = mx.nd.topk(a_nd, axis=1, ret_typ="indices", k=3, is_ascend=True).asnumpy() - assert nd_ret_topk.dtype == np.float32 # Test the default dtype + # Test the default dtype + if is_large_tensor_enabled: + assert nd_ret_topk.dtype == np.int64 + else: + assert nd_ret_topk.dtype == np.int32 gt = gt_topk(a_npy, axis=1, ret_typ="indices", k=3, is_ascend=True) assert_almost_equal(nd_ret_topk, gt) nd_ret_topk = mx.nd.topk(a_nd, axis=3, ret_typ="indices", k=2, is_ascend=False, dtype=np.float64).asnumpy() @@ -860,7 +866,10 @@ def get_large_matrix(): nd_ret_topk_val = nd_ret_topk_val.asnumpy() nd_ret_topk_ind = nd_ret_topk_ind.asnumpy() assert nd_ret_topk_val.dtype == dtype - assert nd_ret_topk_ind.dtype == np.float32 + if is_large_tensor_enabled: + assert nd_ret_topk_ind.dtype == np.int64 + else: + assert nd_ret_topk_ind.dtype == np.int32 gt_val = gt_topk(a_npy, axis=1, ret_typ="value", k=3, is_ascend=True) gt_ind = gt_topk(a_npy, axis=1, ret_typ="indices", k=3, is_ascend=True) assert_almost_equal(nd_ret_topk_val, gt_val) From b4ce4e714a59f9f34d3db751e102609a34a9ce4e Mon Sep 17 00:00:00 2001 From: pengxin99 Date: Sat, 22 Jun 2019 07:30:33 +0800 Subject: [PATCH 008/813] improve layernorm CPU performance (#15313) --- src/operator/mkl_functions-inl.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/operator/mkl_functions-inl.h b/src/operator/mkl_functions-inl.h index 608034732e0e..ae23b1e1d874 100644 --- a/src/operator/mkl_functions-inl.h +++ b/src/operator/mkl_functions-inl.h @@ -137,23 +137,24 @@ MSHADOW_XINLINE static void LayerNormLastDim(index_t m, for (index_t i = 0; i < m; i++) { DType* in_offset = a + i * n; DType* out_offset = b + i * n; + DType x_sum = 0.0f; + DType x_square_sum = 0.0f; - sum_(n, in_offset, &(mean[i])); - mean[i] /= n; - var[i] = 0.0f; #if !defined(_MSC_VER) #pragma omp simd #endif for (index_t j = 0; j < n; j++) { - out_offset[j] = in_offset[j] - mean[i]; - var[i] += out_offset[j] * out_offset[j]; + x_sum += in_offset[j]; + x_square_sum += in_offset[j] * in_offset[j]; } - var[i] = math::sqrt(var[i] / n + eps); + mean[i] = x_sum / n; + var[i] = math::sqrt(x_square_sum / n - mean[i] * mean[i] + eps); + #if !defined(_MSC_VER) #pragma omp simd #endif for (index_t j = 0; j < n; j++) { - out_offset[j] = out_offset[j] * gamma[j] / var[i] + beta[j]; + out_offset[j] = (in_offset[j] - mean[i]) * gamma[j] / var[i] + beta[j]; } } } From e6fad30e45e6ec0ddef5c18093e8163cd2a7c62c Mon Sep 17 00:00:00 2001 From: Zixuan Wei <9527221+zixuanweeei@users.noreply.github.com> Date: Sat, 22 Jun 2019 14:26:27 +0800 Subject: [PATCH 009/813] Efficient MXNet sampling in the multinomial distribution (#15311) * Effective multinomial * Meaningful uniform data pointer as input * Remove beginning Zeros from CDFs * Double precision for accumulated var --- src/operator/random/sample_multinomial_op.h | 42 ++++++++++++--------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/operator/random/sample_multinomial_op.h b/src/operator/random/sample_multinomial_op.h index 377df4f313da..5a0b9bb21acb 100644 --- a/src/operator/random/sample_multinomial_op.h +++ b/src/operator/random/sample_multinomial_op.h @@ -122,25 +122,29 @@ inline bool SampleMultinomialOpType(const nnvm::NodeAttrs& attrs, struct SampleMultinomialKernel { template MSHADOW_XINLINE static void Map(int i, index_t K, index_t M, - DType* dist, float* uniform, IType* out, - DType* prob) { + DType* dist, float* uniform, float* cum_table, + IType* out, DType* prob) { + double acc = 0.0; + // CDF table + for (index_t c = 0; c < K; ++c) { + acc += dist[i*K + c]; + cum_table[i*K + c] = static_cast(acc); + } for (index_t j = 0; j < M; ++j) { + index_t left = 0, right = K; + index_t middle = left + (right - left) / 2; DType loc = static_cast(uniform[i*M + j]); - DType acc = 0; - bool found = false; - for (index_t k = 0; k < K; ++k) { - acc += dist[i*K + k]; - if (acc > loc) { - found = true; - out[i*M + j] = static_cast(k); - if (prob != nullptr) prob[i*M + j] = logf(dist[i*K + k]); - break; + while (right - left > 0) { + middle = left + (right - left) / 2; + DType cum_prob = cum_table[i*K + middle]; + if (cum_prob < loc) { + left = middle + 1; + } else { + right = middle; } } - if (!found) { - out[i*M + j] = static_cast(K-1); - if (prob != nullptr) prob[i*M + j] = logf(dist[i*K + K - 1]); - } + out[i*M + j] = static_cast(left); + if (prob != nullptr) prob[i*M + j] = logf(dist[i*K + left]); } } }; @@ -163,12 +167,14 @@ void SampleMultinomialForward(const nnvm::NodeAttrs& attrs, Stream *s = ctx.get_stream(); MSHADOW_REAL_TYPE_SWITCH(inputs[0].type_flag_, DType, { Random *prnd = ctx.requested[0].get_random(s); - Tensor uniform = - ctx.requested[1].get_space_typed(Shape1(N*M), s); + Tensor workspace = + ctx.requested[1].get_space_typed(Shape1(N*M + N*K), s); + Tensor uniform(workspace.dptr_, Shape1(N*M)); prnd->SampleUniform(&uniform, 0, 1); MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, IType, { Kernel::Launch( - s, N, K, M, inputs[0].dptr(), uniform.dptr_, outputs[0].dptr(), + s, N, K, M, inputs[0].dptr(), uniform.dptr_, workspace.dptr_ + N*M, + outputs[0].dptr(), param.get_prob ? outputs[1].dptr() : nullptr); }); }); From 0340536394bf8205b5394bc9f60649b28d0a2e4f Mon Sep 17 00:00:00 2001 From: nihui Date: Sat, 22 Jun 2019 16:44:29 +0800 Subject: [PATCH 010/813] indent changes (#15321) --- src/io/image_aug_default.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/io/image_aug_default.cc b/src/io/image_aug_default.cc index a1975ef487b7..0e84b7c9dee9 100644 --- a/src/io/image_aug_default.cc +++ b/src/io/image_aug_default.cc @@ -163,8 +163,8 @@ struct DefaultImageAugmentParam : public dmlc::Parameter Date: Sat, 22 Jun 2019 19:35:45 +0200 Subject: [PATCH 011/813] Extend Clojure BERT example (#15023) * Clojure predictor example: add rich comment This provides an entry point for folks working on this example in their REPL rather than the command line. * Clojure BERT example: refactor prepare-data fn for purity * Clojure BERT example: test fitted model on samples * Clojure BERT example: namespace docstring & comment * Clojure BERT example: format intro, add references * Clojure BERT example: minor refactor * Clojure BERT example: trim sentence pair explorations * Clojure BERT example: port experiment to iPynb * Clojure BERT example: fix test Underlying fn was refactored * Clojure BERT example: add sentence-pair prediction test --- .../examples/bert/fine-tune-bert.ipynb | 145 ++++++++++++++++-- .../src/bert/bert_sentence_classification.clj | 113 +++++++++++--- .../bert_sentence_classification_test.clj | 24 ++- .../predictor/src/infer/predictor_example.clj | 6 + 4 files changed, 248 insertions(+), 40 deletions(-) diff --git a/contrib/clojure-package/examples/bert/fine-tune-bert.ipynb b/contrib/clojure-package/examples/bert/fine-tune-bert.ipynb index 425a9993ad93..5934477ea338 100644 --- a/contrib/clojure-package/examples/bert/fine-tune-bert.ipynb +++ b/contrib/clojure-package/examples/bert/fine-tune-bert.ipynb @@ -10,15 +10,16 @@ "\n", "Pre-trained language representations have been shown to improve many downstream NLP tasks such as question answering, and natural language inference. To apply pre-trained representations to these tasks, there are two strategies:\n", "\n", - "feature-based approach, which uses the pre-trained representations as additional features to the downstream task.\n", - "fine-tuning based approach, which trains the downstream tasks by fine-tuning pre-trained parameters.\n", - "While feature-based approaches such as ELMo [3] (introduced in the previous tutorial) are effective in improving many downstream tasks, they require task-specific architectures. Devlin, Jacob, et al proposed BERT [1] (Bidirectional Encoder Representations from Transformers), which fine-tunes deep bidirectional representations on a wide range of tasks with minimal task-specific parameters, and obtained state- of-the-art results.\n", + " - **feature-based approach**, which uses the pre-trained representations as additional features to the downstream task.\n", + " - **fine-tuning based approach**, which trains the downstream tasks by fine-tuning pre-trained parameters.\n", + " \n", + "While feature-based approaches such as ELMo [1] are effective in improving many downstream tasks, they require task-specific architectures. Devlin, Jacob, et al proposed BERT [2] (Bidirectional Encoder Representations from Transformers), which fine-tunes deep bidirectional representations on a wide range of tasks with minimal task-specific parameters, and obtained state- of-the-art results.\n", "\n", "In this tutorial, we will focus on fine-tuning with the pre-trained BERT model to classify semantically equivalent sentence pairs. Specifically, we will:\n", "\n", - "load the state-of-the-art pre-trained BERT model and attach an additional layer for classification,\n", - "process and transform sentence pair data for the task at hand, and\n", - "fine-tune BERT model for sentence classification.\n", + " 1. load the state-of-the-art pre-trained BERT model and attach an additional layer for classification\n", + " 2. process and transform sentence pair data for the task at hand, and \n", + " 3. fine-tune BERT model for sentence classification.\n", "\n" ] }, @@ -59,6 +60,7 @@ " [org.apache.clojure-mxnet.callback :as callback]\n", " [org.apache.clojure-mxnet.context :as context]\n", " [org.apache.clojure-mxnet.dtype :as dtype]\n", + " [org.apache.clojure-mxnet.infer :as infer]\n", " [org.apache.clojure-mxnet.eval-metric :as eval-metric]\n", " [org.apache.clojure-mxnet.io :as mx-io]\n", " [org.apache.clojure-mxnet.layout :as layout]\n", @@ -89,7 +91,7 @@ "\n", "![bert](https://gluon-nlp.mxnet.io/_images/bert-sentence-pair.png)\n", "\n", - "where the model takes a pair of sequences and pools the representation of the first token in the sequence. Note that the original BERT model was trained for masked language model and next sentence prediction tasks, which includes layers for language model decoding and classification. These layers will not be used for fine-tuning sentence pair classification.\n", + "where the model takes a pair of sequences and *pools* the representation of the first token in the sequence. Note that the original BERT model was trained for masked language model and next sentence prediction tasks, which includes layers for language model decoding and classification. These layers will not be used for fine-tuning sentence pair classification.\n", "\n", "Let's load the pre-trained BERT using the module API in MXNet." ] @@ -114,12 +116,15 @@ ], "source": [ "(def model-path-prefix \"data/static_bert_base_net\")\n", + "\n", ";; the vocabulary used in the model\n", "(def vocab (bert-util/get-vocab))\n", - ";; the input question\n", + "\n", ";; the maximum length of the sequence\n", "(def seq-length 128)\n", "\n", + "(def batch-size 32)\n", + "\n", "(def bert-base (m/load-checkpoint {:prefix model-path-prefix :epoch 0}))" ] }, @@ -291,7 +296,7 @@ "source": [ "(defn pre-processing\n", " \"Preprocesses the sentences in the format that BERT is expecting\"\n", - " [ctx idx->token token->idx train-item]\n", + " [idx->token token->idx train-item]\n", " (let [[sentence-a sentence-b label] train-item\n", " ;;; pre-processing tokenize sentence\n", " token-1 (bert-util/tokenize (string/lower-case sentence-a))\n", @@ -319,7 +324,7 @@ "(def idx->token (:idx->token vocab))\n", "(def token->idx (:token->idx vocab))\n", "(def dev (context/default-context))\n", - "(def processed-datas (mapv #(pre-processing dev idx->token token->idx %) data-train-raw))\n", + "(def processed-datas (mapv #(pre-processing idx->token token->idx %) data-train-raw))\n", "(def train-count (count processed-datas))\n", "(println \"Train Count is = \" train-count)\n", "(println \"[PAD] token id = \" (get token->idx \"[PAD]\"))\n", @@ -375,8 +380,6 @@ " (into []))\n", " :train-num (count processed-datas)})\n", "\n", - "(def batch-size 32)\n", - "\n", "(def train-data\n", " (let [{:keys [data0s data1s data2s labels train-num]} prepared-data\n", " data-desc0 (mx-io/data-desc {:name \"data0\"\n", @@ -480,7 +483,7 @@ "(def num-epoch 3)\n", "\n", "(def fine-tune-model (m/module model-sym {:contexts [dev]\n", - " :data-names [\"data0\" \"data1\" \"data2\"]}))\n", + " :data-names [\"data0\" \"data1\" \"data2\"]}))\n", "\n", "(m/fit fine-tune-model {:train-data train-data :num-epoch num-epoch\n", " :fit-params (m/fit-params {:allow-missing true\n", @@ -489,6 +492,122 @@ " :optimizer (optimizer/adam {:learning-rate 5e-6 :episilon 1e-9})\n", " :batch-end-callback (callback/speedometer batch-size 1)})})\n" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Explore results from the fine-tuned model\n", + "\n", + "Now that our model is fitted, we can use it to infer semantic equivalence of arbitrary sentence pairs. Note that for demonstration purpose we skipped the warmup learning rate schedule and validation on dev dataset used in the original implementation. This means that our model's performance will be significantly less than optimal. Please visit [here](https://gluon-nlp.mxnet.io/model_zoo/bert/index.html) for the complete fine-tuning scripts (using Python and GluonNLP).\n", + "\n", + "To do inference with our model we need a predictor. It must have a batch size of 1 so we can feed the model a single sentence pair." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "#'bert.bert-sentence-classification/fine-tuned-predictor" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(def fine-tuned-prefix \"fine-tune-sentence-bert\")\n", + "\n", + "(m/save-checkpoint fine-tune-model {:prefix fine-tuned-prefix :epoch 3})\n", + "\n", + "(def fine-tuned-predictor\n", + " (infer/create-predictor (infer/model-factory fine-tuned-prefix\n", + " [{:name \"data0\" :shape [1 seq-length] :dtype dtype/FLOAT32 :layout layout/NT}\n", + " {:name \"data1\" :shape [1 seq-length] :dtype dtype/FLOAT32 :layout layout/NT}\n", + " {:name \"data2\" :shape [1] :dtype dtype/FLOAT32 :layout layout/N}])\n", + " {:epoch 3}))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can write a function that feeds a sentence pair to the fine-tuned model:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "#'bert.bert-sentence-classification/predict-equivalence" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(defn predict-equivalence\n", + " [predictor sentence1 sentence2]\n", + " (let [vocab (bert.util/get-vocab)\n", + " processed-test-data (mapv #(pre-processing (:idx->token vocab)\n", + " (:token->idx vocab) %)\n", + " [[sentence1 sentence2]])\n", + " prediction (infer/predict-with-ndarray predictor\n", + " [(ndarray/array (slice-inputs-data processed-test-data 0) [1 seq-length])\n", + " (ndarray/array (slice-inputs-data processed-test-data 1) [1 seq-length])\n", + " (ndarray/array (slice-inputs-data processed-test-data 2) [1])])]\n", + " (ndarray/->vec (first prediction))))" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.2633881 0.7366119]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + ";; Modify an existing sentence pair to test:\n", + ";; [\"1\"\n", + ";; \"69773\"\n", + ";; \"69792\"\n", + ";; \"Cisco pared spending to compensate for sluggish sales .\"\n", + ";; \"In response to sluggish sales , Cisco pared spending .\"]\n", + "(predict-equivalence fine-tuned-predictor\n", + " \"The company cut spending to compensate for weak sales .\"\n", + " \"In response to poor sales results, the company cut spending .\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "[1] Peters, Matthew E., et al. “Deep contextualized word representations.” arXiv preprint arXiv:1802.05365 (2018).\n", + "\n", + "[2] Devlin, Jacob, et al. “Bert: Pre-training of deep bidirectional transformers for language understanding.” arXiv preprint arXiv:1810.04805 (2018)." + ] } ], "metadata": { diff --git a/contrib/clojure-package/examples/bert/src/bert/bert_sentence_classification.clj b/contrib/clojure-package/examples/bert/src/bert/bert_sentence_classification.clj index 8c056b719feb..6ec4d586ad17 100644 --- a/contrib/clojure-package/examples/bert/src/bert/bert_sentence_classification.clj +++ b/contrib/clojure-package/examples/bert/src/bert/bert_sentence_classification.clj @@ -16,12 +16,21 @@ ;; (ns bert.bert-sentence-classification + "Fine-tuning Sentence Pair Classification with BERT + This tutorial focuses on fine-tuning with the pre-trained BERT model to classify semantically equivalent sentence pairs. + + Specifically, we will: + 1. load the state-of-the-art pre-trained BERT model + 2. attach an additional layer for classification + 3. process and transform sentence pair data for the task at hand + 4. fine-tune BERT model for sentence classification" (:require [bert.util :as bert-util] [clojure-csv.core :as csv] [clojure.string :as string] [org.apache.clojure-mxnet.callback :as callback] [org.apache.clojure-mxnet.context :as context] [org.apache.clojure-mxnet.dtype :as dtype] + [org.apache.clojure-mxnet.infer :as infer] [org.apache.clojure-mxnet.io :as mx-io] [org.apache.clojure-mxnet.layout :as layout] [org.apache.clojure-mxnet.module :as m] @@ -29,8 +38,25 @@ [org.apache.clojure-mxnet.optimizer :as optimizer] [org.apache.clojure-mxnet.symbol :as sym])) +;; Pre-trained language representations have been shown to improve +;; many downstream NLP tasks such as question answering, and natural +;; language inference. To apply pre-trained representations to these +;; tasks, there are two strategies: + +;; * feature-based approach, which uses the pre-trained representations as additional features to the downstream task. +;; * fine-tuning based approach, which trains the downstream tasks by fine-tuning pre-trained parameters. + +;; While feature-based approaches such as ELMo are effective in +;; improving many downstream tasks, they require task-specific +;; architectures. Devlin, Jacob, et al proposed BERT (Bidirectional +;; Encoder Representations from Transformers), which fine-tunes deep +;; bidirectional representations on a wide range of tasks with minimal +;; task-specific parameters, and obtained state-of-the-art results. + (def model-path-prefix "data/static_bert_base_net") -;; epoch number of the model + +(def fine-tuned-prefix "fine-tune-sentence-bert") + ;; the maximum length of the sequence (def seq-length 128) @@ -38,20 +64,19 @@ "Preprocesses the sentences in the format that BERT is expecting" [idx->token token->idx train-item] (let [[sentence-a sentence-b label] train-item - ;;; pre-processing tokenize sentence + ;; pre-processing tokenize sentence token-1 (bert-util/tokenize (string/lower-case sentence-a)) token-2 (bert-util/tokenize (string/lower-case sentence-b)) valid-length (+ (count token-1) (count token-2)) - ;;; generate token types [0000...1111...0000] + ;; generate token types [0000...1111...0000] qa-embedded (into (bert-util/pad [] 0 (count token-1)) - (bert-util/pad [] 1 (count token-2))) token-types (bert-util/pad qa-embedded 0 seq-length) - ;;; make BERT pre-processing standard + ;; make BERT pre-processing standard token-2 (conj token-2 "[SEP]") token-1 (into [] (concat ["[CLS]"] token-1 ["[SEP]"] token-2)) tokens (bert-util/pad token-1 "[PAD]" seq-length) - ;;; pre-processing - token to index translation + ;; pre-processing - token to index translation indexes (bert-util/tokens->idxs token->idx tokens)] {:input-batch [indexes token-types @@ -83,19 +108,18 @@ (defn get-raw-data [] (csv/parse-csv (string/replace (slurp "data/dev.tsv") "\"" "") - :delimiter \tab - :strict true)) + :delimiter \tab + :strict true)) (defn prepare-data - "This prepares the senetence pairs into NDArrays for use in NDArrayIterator" - [] - (let [raw-file (get-raw-data) - vocab (bert-util/get-vocab) + "This prepares the sentence pairs into NDArrays for use in NDArrayIterator" + [raw-data] + (let [vocab (bert-util/get-vocab) idx->token (:idx->token vocab) token->idx (:token->idx vocab) - data-train-raw (->> raw-file + data-train-raw (->> raw-data (mapv #(vals (select-keys % [3 4 0]))) - (rest) ;;drop header + (rest) ; drop header (into [])) processed-datas (mapv #(pre-processing idx->token token->idx %) data-train-raw)] {:data0s (slice-inputs-data processed-datas 0) @@ -111,7 +135,7 @@ [dev num-epoch] (let [bert-base (m/load-checkpoint {:prefix model-path-prefix :epoch 0}) model-sym (fine-tune-model (m/symbol bert-base) {:num-classes 2 :dropout 0.1}) - {:keys [data0s data1s data2s labels train-num]} (prepare-data) + {:keys [data0s data1s data2s labels train-num]} (prepare-data (get-raw-data)) batch-size 32 data-desc0 (mx-io/data-desc {:name "data0" :shape [train-num seq-length] @@ -138,14 +162,16 @@ {:label {label-desc (ndarray/array labels [train-num] {:ctx dev})} :data-batch-size batch-size}) - model (m/module model-sym {:contexts [dev] - :data-names ["data0" "data1" "data2"]})] - (m/fit model {:train-data train-data :num-epoch num-epoch - :fit-params (m/fit-params {:allow-missing true - :arg-params (m/arg-params bert-base) - :aux-params (m/aux-params bert-base) - :optimizer (optimizer/adam {:learning-rate 5e-6 :episilon 1e-9}) - :batch-end-callback (callback/speedometer batch-size 1)})}))) + fitted-model (m/fit (m/module model-sym {:contexts [dev] + :data-names ["data0" "data1" "data2"]}) + {:train-data train-data :num-epoch num-epoch + :fit-params (m/fit-params {:allow-missing true + :arg-params (m/arg-params bert-base) + :aux-params (m/aux-params bert-base) + :optimizer (optimizer/adam {:learning-rate 5e-6 :epsilon 1e-9}) + :batch-end-callback (callback/speedometer batch-size 1)})})] + (m/save-checkpoint fitted-model {:prefix fine-tuned-prefix :epoch num-epoch}) + fitted-model)) (defn -main [& args] (let [[dev-arg num-epoch-arg] args @@ -154,7 +180,46 @@ (println "Running example with " dev " and " num-epoch " epochs ") (train dev num-epoch))) +;; For evaluating the model +(defn predict-equivalence + "Get the fine-tuned model's opinion on whether two sentences are equivalent:" + [predictor sentence1 sentence2] + (let [vocab (bert.util/get-vocab) + processed-test-data (mapv #(pre-processing (:idx->token vocab) + (:token->idx vocab) %) + [[sentence1 sentence2]]) + prediction (infer/predict-with-ndarray predictor + [(ndarray/array (slice-inputs-data processed-test-data 0) [1 seq-length]) + (ndarray/array (slice-inputs-data processed-test-data 1) [1 seq-length]) + (ndarray/array (slice-inputs-data processed-test-data 2) [1])])] + (ndarray/->vec (first prediction)))) + (comment (train (context/cpu 0) 3) - (m/save-checkpoint model {:prefix "fine-tune-sentence-bert" :epoch 3})) + + (m/save-checkpoint model {:prefix fine-tuned-prefix :epoch 3}) + + + ;;;; Explore results from the fine-tuned model + + ;; We need a predictor with a batch size of 1, so we can feed the + ;; model a single sentence pair. + (def fine-tuned-predictor + (infer/create-predictor (infer/model-factory fine-tuned-prefix + [{:name "data0" :shape [1 seq-length] :dtype dtype/FLOAT32 :layout layout/NT} + {:name "data1" :shape [1 seq-length] :dtype dtype/FLOAT32 :layout layout/NT} + {:name "data2" :shape [1] :dtype dtype/FLOAT32 :layout layout/N}]) + {:epoch 3})) + + ;; Modify an existing sentence pair to test: + ;; ["1" + ;; "69773" + ;; "69792" + ;; "Cisco pared spending to compensate for sluggish sales ." + ;; "In response to sluggish sales , Cisco pared spending ."] + (predict-equivalence fine-tuned-predictor + "The company cut spending to compensate for weak sales ." + "In response to poor sales results, the company cut spending .") + + ) diff --git a/contrib/clojure-package/examples/bert/test/bert/bert_sentence_classification_test.clj b/contrib/clojure-package/examples/bert/test/bert/bert_sentence_classification_test.clj index 355f23ea3cfd..c26301e34fe6 100644 --- a/contrib/clojure-package/examples/bert/test/bert/bert_sentence_classification_test.clj +++ b/contrib/clojure-package/examples/bert/test/bert/bert_sentence_classification_test.clj @@ -26,6 +26,7 @@ [org.apache.clojure-mxnet.context :as context] [org.apache.clojure-mxnet.dtype :as dtype] [org.apache.clojure-mxnet.eval-metric :as eval-metric] + [org.apache.clojure-mxnet.infer :as infer] [org.apache.clojure-mxnet.io :as mx-io] [org.apache.clojure-mxnet.layout :as layout] [org.apache.clojure-mxnet.ndarray :as ndarray] @@ -34,6 +35,8 @@ (def model-dir "data/") +(def test-prefix "test-fine-tuning-bert-sentence-pairs") + (when-not (.exists (io/file (str model-dir "static_bert_qa-0002.params"))) (println "Downloading bert qa data") (sh "./get_bert_data.sh")) @@ -47,7 +50,7 @@ num-epoch 1 bert-base (m/load-checkpoint {:prefix model-path-prefix :epoch 0}) model-sym (fine-tune-model (m/symbol bert-base) {:num-classes 2 :dropout 0.1}) - {:keys [data0s data1s data2s labels train-num]} (prepare-data) + {:keys [data0s data1s data2s labels train-num]} (prepare-data (get-raw-data)) batch-size 32 data-desc0 (mx-io/data-desc {:name "data0" :shape [train-num seq-length] @@ -82,5 +85,20 @@ :aux-params (m/aux-params bert-base) :optimizer (optimizer/adam {:learning-rate 5e-6 :episilon 1e-9}) :batch-end-callback (callback/speedometer batch-size 1)})}) - (is (< 0.5 (-> (m/score model {:eval-data train-data :eval-metric (eval-metric/accuracy) }) - (last))))))) + (m/save-checkpoint model {:prefix test-prefix :epoch num-epoch}) + (testing "accuracy" + (is (< 0.5 (last (m/score model {:eval-data train-data :eval-metric (eval-metric/accuracy)}))))) + (testing "prediction" + (let [test-predictor (infer/create-predictor (infer/model-factory test-prefix + [{:name "data0" :shape [1 seq-length] :dtype dtype/FLOAT32 :layout layout/NT} + {:name "data1" :shape [1 seq-length] :dtype dtype/FLOAT32 :layout layout/NT} + {:name "data2" :shape [1] :dtype dtype/FLOAT32 :layout layout/N}]) + {:epoch num-epoch}) + prediction (predict-equivalence test-predictor + "The company cut spending to compensate for weak sales ." + "In response to poor sales results, the company cut spending .")] + ;; We can't say much about how the model will find this prediction, so we test only the prediction's shape. + (is (vector? prediction)) + (is (number? (first prediction))) + (is (number? (second prediction))) + (is (= 2 (count prediction)))))))) diff --git a/contrib/clojure-package/examples/infer/predictor/src/infer/predictor_example.clj b/contrib/clojure-package/examples/infer/predictor/src/infer/predictor_example.clj index 05eb0add3138..41a003a86ce0 100644 --- a/contrib/clojure-package/examples/infer/predictor/src/infer/predictor_example.clj +++ b/contrib/clojure-package/examples/infer/predictor/src/infer/predictor_example.clj @@ -99,3 +99,9 @@ (:help options) (println summary) (some? errors) (println (join "\n" errors)) :else (run-predictor options)))) + +(comment + (run-predictor {:model-path-prefix "models/resnet-18/resnet-18" + :input-image "images/kitten.jpg"}) + + ) From 7fe478afc4f8bed58b9816ddce57a3c75997c9f2 Mon Sep 17 00:00:00 2001 From: Pedro Larroy Date: Mon, 24 Jun 2019 01:20:33 -0700 Subject: [PATCH 012/813] Fix build_ccache_wrappers: (#14631) * Fix broken links * Make it idempotent fixes https://github.com/apache/incubator-mxnet/pull/13456 fixes https://github.com/apache/incubator-mxnet/issues/14117 fixes https://github.com/apache/incubator-mxnet/issues/11516 --- ci/docker/runtime_functions.sh | 49 ++++++++++++++-------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index 1ad67280617d..0fef2a049125 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -86,37 +86,29 @@ build_ccache_wrappers() { # But in the beginning, we'll make this opt-in. In future, loads of processes like # the scala make step or numpy compilation and other pip package generations # could be heavily sped up by using ccache as well. - mkdir /tmp/ccache-redirects + mkdir -p /tmp/ccache-redirects export PATH=/tmp/ccache-redirects:$PATH - ln -s ccache /tmp/ccache-redirects/gcc - ln -s ccache /tmp/ccache-redirects/gcc-8 - ln -s ccache /tmp/ccache-redirects/g++ - ln -s ccache /tmp/ccache-redirects/g++-8 - ln -s ccache /tmp/ccache-redirects/nvcc - ln -s ccache /tmp/ccache-redirects/clang++-3.9 - ln -s ccache /tmp/ccache-redirects/clang-3.9 - ln -s ccache /tmp/ccache-redirects/clang++-5.0 - ln -s ccache /tmp/ccache-redirects/clang-5.0 - ln -s ccache /tmp/ccache-redirects/clang++-6.0 - ln -s ccache /tmp/ccache-redirects/clang-6.0 - ln -s ccache /usr/local/bin/gcc - ln -s ccache /usr/local/bin/gcc-8 - ln -s ccache /usr/local/bin/g++ - ln -s ccache /usr/local/bin/g++-8 - ln -s ccache /usr/local/bin/nvcc - ln -s ccache /usr/local/bin/clang++-3.9 - ln -s ccache /usr/local/bin/clang-3.9 - ln -s ccache /usr/local/bin/clang++-5.0 - ln -s ccache /usr/local/bin/clang-5.0 - ln -s ccache /usr/local/bin/clang++-6.0 - ln -s ccache /usr/local/bin/clang-6.0 - - export NVCC=ccache + CCACHE=`which ccache` + ln -sf $CCACHE /tmp/ccache-redirects/gcc + ln -sf $CCACHE /tmp/ccache-redirects/gcc-8 + ln -sf $CCACHE /tmp/ccache-redirects/g++ + ln -sf $CCACHE /tmp/ccache-redirects/g++-8 + ln -sf $CCACHE /tmp/ccache-redirects/clang++-3.9 + ln -sf $CCACHE /tmp/ccache-redirects/clang-3.9 + ln -sf $CCACHE /tmp/ccache-redirects/clang++-5.0 + ln -sf $CCACHE /tmp/ccache-redirects/clang-5.0 + ln -sf $CCACHE /tmp/ccache-redirects/clang++-6.0 + ln -sf $CCACHE /tmp/ccache-redirects/clang-6.0 + #Doesn't work: https://github.com/ccache/ccache/issues/373 + # ln -sf $CCACHE /tmp/ccache-redirects/nvcc + # ln -sf $CCACHE /tmp/ccache-redirects/nvcc + # export NVCC="/tmp/ccache-redirects/nvcc" # Uncomment if you would like to debug CCache hit rates. # You can monitor using tail -f ccache-log - # export CCACHE_LOGFILE=/work/mxnet/ccache-log - # export CCACHE_DEBUG=1 + #export CCACHE_LOGFILE=/work/mxnet/ccache-log + #export CCACHE_LOGFILE=/tmp/ccache-log + #export CCACHE_DEBUG=1 } build_wheel() { @@ -689,8 +681,7 @@ build_ubuntu_gpu_mkldnn_nocudnn() { build_ubuntu_gpu_cuda100_cudnn7() { set -ex - # unfortunately this build has problems in 3rdparty dependencies with ccache and make - # build_ccache_wrappers + build_ccache_wrappers make \ DEV=1 \ ENABLE_TESTCOVERAGE=1 \ From 51acd4d328c6da475c058806565d93bcddee7402 Mon Sep 17 00:00:00 2001 From: Maxim Berman Date: Tue, 25 Jun 2019 16:11:13 -0700 Subject: [PATCH 013/813] [MXNET-1086] added sub and mul to ONNX->TensorRT conversion (#15344) * added sub and mul to ONNX->TensorRT conversion * add test for elementwise ops in TRT --- CMakeLists.txt | 2 +- .../subgraph/tensorrt/nnvm_to_onnx-inl.h | 12 ++++ .../subgraph/tensorrt/nnvm_to_onnx.cc | 12 ++++ tests/python/tensorrt/test_ops.py | 68 +++++++++++++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 tests/python/tensorrt/test_ops.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 2142a09d6d2e..0148ac302d54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ mxnet_option(ENABLE_CUDA_RTC "Build with CUDA runtime compilation support" mxnet_option(BUILD_CPP_EXAMPLES "Build cpp examples" ON) mxnet_option(INSTALL_EXAMPLES "Install the example source files." OFF) mxnet_option(USE_SIGNAL_HANDLER "Print stack traces on segfaults." ON) -mxnet_option(USE_TENSORRT "Enable infeference optimization with TensorRT." OFF) +mxnet_option(USE_TENSORRT "Enable inference optimization with TensorRT." OFF) mxnet_option(USE_ASAN "Enable Clang/GCC ASAN sanitizers." OFF) mxnet_option(ENABLE_TESTCOVERAGE "Enable compilation with test coverage metric output" OFF) mxnet_option(USE_INT64_TENSOR_SIZE "Use int64_t to represent the total number of elements in a tensor" OFF) diff --git a/src/operator/subgraph/tensorrt/nnvm_to_onnx-inl.h b/src/operator/subgraph/tensorrt/nnvm_to_onnx-inl.h index 4a88aee886db..edf4d357e922 100644 --- a/src/operator/subgraph/tensorrt/nnvm_to_onnx-inl.h +++ b/src/operator/subgraph/tensorrt/nnvm_to_onnx-inl.h @@ -126,6 +126,16 @@ void ConvertElementwiseAdd(NodeProto *node_proto, const nnvm::IndexedGraph &ig, const array_view &inputs); +void ConvertElementwiseSub(NodeProto *node_proto, + const NodeAttrs &attrs, + const nnvm::IndexedGraph &ig, + const array_view &inputs); + +void ConvertElementwiseMul(NodeProto *node_proto, + const NodeAttrs &attrs, + const nnvm::IndexedGraph &ig, + const array_view &inputs); + void ConvertConcatenate(NodeProto *node_proto, const NodeAttrs &attrs, const nnvm::IndexedGraph &ig, @@ -152,6 +162,8 @@ static const std::unordered_map converter_map = {"Concat", ConvertConcatenate}, {"Dropout", ConvertDropout}, {"elemwise_add", ConvertElementwiseAdd}, + {"elemwise_sub", ConvertElementwiseSub}, + {"elemwise_mul", ConvertElementwiseMul}, {"Flatten", ConvertFlatten}, {"FullyConnected", ConvertFullyConnected}, {"Pad", ConvertPad}, diff --git a/src/operator/subgraph/tensorrt/nnvm_to_onnx.cc b/src/operator/subgraph/tensorrt/nnvm_to_onnx.cc index da89c2b476ee..9d98a48c2ec2 100644 --- a/src/operator/subgraph/tensorrt/nnvm_to_onnx.cc +++ b/src/operator/subgraph/tensorrt/nnvm_to_onnx.cc @@ -393,6 +393,18 @@ void ConvertElementwiseAdd(NodeProto* node_proto, const NodeAttrs& /*attrs*/, node_proto->set_op_type("Add"); } +void ConvertElementwiseSub(NodeProto* node_proto, const NodeAttrs& /*attrs*/, + const nnvm::IndexedGraph& /*ig*/, + const array_view& /*inputs*/) { + node_proto->set_op_type("Sub"); +} + +void ConvertElementwiseMul(NodeProto* node_proto, const NodeAttrs& /*attrs*/, + const nnvm::IndexedGraph& /*ig*/, + const array_view& /*inputs*/) { + node_proto->set_op_type("Mul"); +} + void ConvertConcatenate(NodeProto* node_proto, const NodeAttrs& attrs, const nnvm::IndexedGraph& /*ig*/, const array_view& /*inputs*/) { diff --git a/tests/python/tensorrt/test_ops.py b/tests/python/tensorrt/test_ops.py new file mode 100644 index 000000000000..2df9104aa06c --- /dev/null +++ b/tests/python/tensorrt/test_ops.py @@ -0,0 +1,68 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from mxnet.test_utils import assert_almost_equal +import mxnet as mx +import numpy as np +import os + +def check_elementwise_random(op='sum', shape=(1, 3, 224, 224)): + """ + Check elementwise operators with vanilla/TensorRT executors with uniform random tensors + """ + a = mx.sym.Variable('a') + b = mx.sym.Variable('b') + if op == 'sum': + sym = a + b + elif op == 'sub': + sym = a - b + elif op == 'mul': + sym = a * b + + a_data = mx.ndarray.random.uniform(shape=shape, ctx=mx.gpu()) + b_data = mx.ndarray.random.uniform(shape=shape, ctx=mx.gpu()) + + executor = sym.simple_bind(ctx=mx.gpu(), a=shape, b=shape, + grad_req='null', force_rebind=True) + y = executor.forward(is_train=False, a=a_data, b=b_data) + trt_sym = sym.get_backend_symbol('TensorRT') + original_precision_value = mx.contrib.tensorrt.get_use_fp16() + try: + mx.contrib.tensorrt.set_use_fp16(True) + executor = trt_sym.simple_bind(ctx=mx.gpu(), a=shape, b=shape, + grad_req='null', force_rebind=True) + y_trt = executor.forward(is_train=False, a=a_data, b=b_data) + mx.contrib.tensorrt.set_use_fp16(False) + executor = trt_sym.simple_bind(ctx=mx.gpu(), a=shape, b=shape, + grad_req='null', force_rebind=True) + y_trt_fp32 = executor.forward(is_train=False, a=a_data, b=b_data) + assert_almost_equal(y[0].asnumpy(), y_trt[0].asnumpy(), 1e-1, 1e-2) + assert_almost_equal(y[0].asnumpy(), y_trt_fp32[0].asnumpy(), 1e-4, 1e-4) + finally: + mx.contrib.tensorrt.set_use_fp16(original_precision_value) + + +def test_elementwise(): + for op in ['sum', 'sub', 'mul']: + for shape in [(20, 25), (3, 4, 20), (1, 3, 20, 25), (10, 10, 100, 100)]: + for itry in range(10): + check_elementwise_random(op, shape) + + +if __name__ == '__main__': + import nose + nose.runmodule() From 009907a8edde855c81078ac24fdedf188fa870d4 Mon Sep 17 00:00:00 2001 From: Wuxun Zhang Date: Thu, 27 Jun 2019 06:50:05 +0800 Subject: [PATCH 014/813] [C++] Improve inference script to support benchmark on Imagenet (#15164) * refactor C++ inference script to support benchmark on Imagenet * improve README, add INT8 test and some fixes * fix cpplint * some changes and code rebase * fix CI * refresh performance numbers in README * address comments * fix lint * address comments * improve README * separate timing function for Windows and Linux * retrigger CI --- cpp-package/example/inference/README.md | 108 +++- .../example/inference/imagenet_inference.cpp | 610 ++++++++++++++++++ .../example/inference/inception_inference.cpp | 444 ------------- .../inference/unit_test_imagenet_inference.sh | 63 ++ .../unit_test_inception_inference.sh | 42 -- cpp-package/example/mlp_csv.cpp | 1 + cpp-package/include/mxnet-cpp/initializer.h | 50 ++ cpp-package/include/mxnet-cpp/io.h | 2 + cpp-package/tests/ci_test.sh | 4 +- example/quantization/README.md | 18 +- 10 files changed, 830 insertions(+), 512 deletions(-) create mode 100644 cpp-package/example/inference/imagenet_inference.cpp delete mode 100644 cpp-package/example/inference/inception_inference.cpp create mode 100755 cpp-package/example/inference/unit_test_imagenet_inference.sh delete mode 100755 cpp-package/example/inference/unit_test_inception_inference.sh diff --git a/cpp-package/example/inference/README.md b/cpp-package/example/inference/README.md index 865388b549db..272586da5da9 100644 --- a/cpp-package/example/inference/README.md +++ b/cpp-package/example/inference/README.md @@ -30,34 +30,112 @@ To build examples use following commands: This directory contains following examples. In order to run the examples, ensure that the path to the MXNet shared library is added to the OS specific environment variable viz. **LD\_LIBRARY\_PATH** for Linux, Mac and Ubuntu OS and **PATH** for Windows OS. -### [inception_inference.cpp]() +## [imagenet_inference.cpp]() -This example demonstrates image classification workflow with pre-trained models using MXNet C++ API. The command line parameters the example can accept are as shown below: +This example demonstrates image classification workflow with pre-trained models using MXNet C++ API. Now this script also supports inference with quantized CNN models generated by Intel® MKL-DNN (see this [quantization flow](https://github.com/apache/incubator-mxnet/blob/master/example/quantization/README.md)). By using C++ API, the latency of most models will be reduced to some extent compared with current Python implementation. +Most of CNN models have been tested on Linux systems. And 50000 images are used to collect accuracy numbers. Please refer to this [README](https://github.com/apache/incubator-mxnet/blob/master/example/quantization/README.md) for more details about accuracy. + +The following performance numbers are collected via using C++ inference API on AWS EC2 C5.12xlarge. The environment variables are set like below: + +``` +export KMP_AFFINITY=granularity=fine,noduplicates,compact,1,0 +export OMP_NUM_THREADS=$(vCPUs/2) +export MXNET_SUBGRAPH_BACKEND=MKLDNN +export MXNET_ENGINE_TYPE=NaiveEngine +``` +Also users are recommended to use ```numactl``` or ```taskset``` to bind a running process to the specified cores. + +| Model | Dataset |BS=1 (imgs/sec) |BS=64 (imgs/sec) | +|:---|:---|:---:|:---:| +| | |FP32 / INT8 | FP32 / INT8 | +| ResNet18-V1 | [Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) |369.00 / 778.82|799.7 / 2598.04| +| ResNet50-V1 | [Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) |160.72 / 405.84|349.73 / 1297.65 | +| ResNet101-V1 | [Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) | 89.56 / 197.55| 193.25 / 740.47| +|Squeezenet 1.0|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) | 294.46 / 899.28| 857.70 / 3065.13| +|MobileNet 1.0|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) |554.94 / 676.59|1279.44 / 3393.43| +|MobileNetV2 1.0|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) |303.40 / 776.40|994.25 / 4227.77| +|Inception V3|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) |108.20 / 219.20 | 232.22 / 870.09 | +|ResNet152-V2|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) |52.28 / 64.62|107.03 / 134.04 | +|Inception-BN|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) | 211.86 / 306.37| 632.79 / 2115.28| + +The command line to launch inference by this script can accept are as shown below: ``` -./inception_inference --help +./imagenet_inference --help Usage: -inception_inference --symbol - --params - --image + --params_file + --dataset + --data_nthreads + --input_shape ] + --rgb_mean + --rgb_std + --batch_size + --num_skipped_batches + --num_inference_batches + --data_layer_type + --gpu + --benchmark ``` -The model json and param file and synset files are required to run this example. The sample command line is as follows: +Follow the below steps to do inference with more models. + +- Download the pre-trained FP32 models into ```./model``` directory. +- Refer this [README](https://github.com/apache/incubator-mxnet/blob/master/example/quantization/README.md) to generate the corresponding quantized models and also put them into ```./model``` directory. +- Prepare [validation dataset](http://data.mxnet.io/data/val_256_q90.rec) and put it into ```./data``` directory. + +The below command lines show how to run inference with FP32/INT8 resnet50_v1 model. Because the C++ inference script provides the almost same command line as this [Python script](https://github.com/apache/incubator-mxnet/blob/master/example/quantization/imagenet_inference.py) and then users can easily go from Python to C++. ``` +# set MKLDNN as subgraph backend +export MXNET_SUBGRAPH_BACKEND=MKLDNN + +# FP32 inference +./imagenet_inference --symbol_file "./model/resnet50_v1-symbol.json" --params_file "./model/resnet50_v1-0000.params" --dataset "./data/val_256_q90.rec" --rgb_mean "123.68 116.779 103.939" --rgb_std "58.393 57.12 57.375" --batch_size 64 --num_skipped_batches 50 --num_inference_batches 500 + +# INT8 inference +./imagenet_inference --symbol_file "./model/resnet50_v1-quantized-5batches-naive-symbol.json" --params_file "./model/resnet50_v1-quantized-0000.params" --dataset "./data/val_256_q90.rec" --rgb_mean "123.68 116.779 103.939" --rgb_std "58.393 57.12 57.375" --batch_size 64 --num_skipped_batches 50 --num_inference_batches 500 + +# FP32 dummy data +./imagenet_inference --symbol_file "./model/resnet50_v1-symbol.json" --batch_size 64 --num_inference_batches 500 --benchmark + +# INT8 dummy data +./imagenet_inference --symbol_file "./model/resnet50_v1-quantized-5batches-naive-symbol.json" --batch_size 64 --num_inference_batches 500 --benchmark -./inception_inference --symbol "./model/Inception-BN-symbol.json" --params "./model/Inception-BN-0126.params" --synset "./model/synset.txt" --mean "./model/mean_224.nd" --image "./model/dog.jpg" ``` -Alternatively, The script [unit_test_inception_inference.sh]() downloads the pre-trained **Inception** model and a test image. The users can invoke this script as follows: +For a quick inference test, users can directly run [unit_test_imagenet_inference.sh]() by using the below command. This script will automatically download the pre-trained **Inception-Bn** and **resnet50_v1_int8** model and **validation dataset** which are required for inference. ``` -./unit_test_inception_inference.sh +./unit_test_imagenet_inference.sh +``` +And you may get the similiar outputs like below: +``` +>>> INFO: FP32 real data +imagenet_inference.cpp:282: Loading the model from ./model/Inception-BN-symbol.json +imagenet_inference.cpp:295: Loading the model parameters from ./model/Inception-BN-0126.params +imagenet_inference.cpp:443: INFO:Dataset for inference: ./data/val_256_q90.rec +imagenet_inference.cpp:444: INFO:label_name = softmax_label +imagenet_inference.cpp:445: INFO:rgb_mean: (123.68, 116.779, 103.939) +imagenet_inference.cpp:447: INFO:rgb_std: (1, 1, 1) +imagenet_inference.cpp:449: INFO:Image shape: (3, 224, 224) +imagenet_inference.cpp:451: INFO:Finished inference with: 500 images +imagenet_inference.cpp:453: INFO:Batch size = 1 for inference +imagenet_inference.cpp:454: INFO:Accuracy: 0.744 +imagenet_inference.cpp:455: INFO:Throughput: xxxx images per second + +>>> INFO: FP32 dummy data +imagenet_inference.cpp:282: Loading the model from ./model/Inception-BN-symbol.json +imagenet_inference.cpp:372: Running the forward pass on model to evaluate the performance.. +imagenet_inference.cpp:387: benchmark completed! +imagenet_inference.cpp:388: batch size: 1 num batch: 500 throughput: xxxx imgs/s latency:xxxx ms + +>>> INFO: INT8 dummy data +imagenet_inference.cpp:282: Loading the model from ./model/resnet50_v1_int8-symbol.json +imagenet_inference.cpp:372: Running the forward pass on model to evaluate the performance.. +imagenet_inference.cpp:387: benchmark completed! +imagenet_inference.cpp:388: batch size: 1 num batch: 500 throughput: xxxx imgs/s latency:xxxx ms ``` -### [sentiment_analysis_rnn.cpp]() +## [sentiment_analysis_rnn.cpp]() This example demonstrates how you can load a pre-trained RNN model and use it to predict the sentiment expressed in the given movie review with the MXNet C++ API. The example is capable of processing variable legnth inputs. It performs the following tasks - Loads the pre-trained RNN model. - Loads the dictionary file containing the word to index mapping. diff --git a/cpp-package/example/inference/imagenet_inference.cpp b/cpp-package/example/inference/imagenet_inference.cpp new file mode 100644 index 000000000000..7eaf991ada4e --- /dev/null +++ b/cpp-package/example/inference/imagenet_inference.cpp @@ -0,0 +1,610 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * This example demonstrates image classification workflow with pre-trained models using MXNet C++ API. + * The example performs following tasks. + * 1. Load the pre-trained model. + * 2. Load the parameters of pre-trained model. + * 3. Load the inference dataset and create a new ImageRecordIter. + * 4. Run the forward pass and obtain throughput & accuracy. + */ +#ifndef _WIN32 +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mxnet/c_api.h" +#include "mxnet/tuple.h" +#include "mxnet-cpp/MxNetCpp.h" +#include "mxnet-cpp/initializer.h" + +using namespace mxnet::cpp; + +double ms_now() { + double ret; +#ifdef _WIN32 + auto timePoint = std::chrono::high_resolution_clock::now().time_since_epoch(); + ret = std::chrono::duration(timePoint).count(); +#else + struct timeval time; + gettimeofday(&time, NULL); + ret = 1e+3 * time.tv_sec + 1e-3 * time.tv_usec; +#endif + return ret; +} + + +// define the data type for NDArray, aliged with the definition in mshadow/base.h +enum TypeFlag { + kFloat32 = 0, + kFloat64 = 1, + kFloat16 = 2, + kUint8 = 3, + kInt32 = 4, + kInt8 = 5, + kInt64 = 6, +}; + +/* + * class Predictor + * + * This class encapsulates the functionality to load the model, prepare dataset and run the forward pass. + */ + +class Predictor { + public: + Predictor() {} + Predictor(const std::string& model_json_file, + const std::string& model_params_file, + const Shape& input_shape, + bool use_gpu, + const std::string& dataset, + const int data_nthreads, + const std::string& data_layer_type, + const std::vector& rgb_mean, + const std::vector& rgb_std, + int shuffle_chunk_seed, + int seed, bool benchmark); + void BenchmarkScore(int num_inference_batches); + void Score(int num_skipped_batches, int num_inference_batches); + ~Predictor(); + + private: + bool CreateImageRecordIter(); + bool AdvanceDataIter(int skipped_batches); + void LoadModel(const std::string& model_json_file); + void LoadParameters(const std::string& model_parameters_file); + void InitParameters(); + + inline bool FileExists(const std::string &name) { + std::ifstream fhandle(name.c_str()); + return fhandle.good(); + } + int GetDataLayerType(); + + std::map args_map_; + std::map aux_map_; + Symbol net_; + Executor *executor_; + Shape input_shape_; + Context global_ctx_ = Context::cpu(); + + MXDataIter *val_iter_; + bool use_gpu_; + std::string dataset_; + int data_nthreads_; + std::string data_layer_type_; + std::vector rgb_mean_; + std::vector rgb_std_; + int shuffle_chunk_seed_; + int seed_; + bool benchmark_; +}; + + +/* + * The constructor takes following parameters as input: + * 1. model_json_file: The model in json formatted file. + * 2. model_params_file: File containing model parameters + * 3. input_shape: Shape of input data to the model. Since this class will be running one inference at a time, + * the input shape is required to be in format Shape(1, number_of_channels, height, width) + * The input image will be resized to (height x width) size before running the inference. + * 4. use_gpu: determine if run inference on GPU + * 5. dataset: data file (.rec) to be used for inference + * 6. data_nthreads: number of threads for data loading + * 7. data_layer_type: data type for data layer + * 8. rgb_mean: mean value to be subtracted on R/G/B channel + * 9. rgb_std: standard deviation on R/G/B channel + * 10. shuffle_chunk_seed: shuffling chunk seed + * 11. seed: shuffling seed + * 12. benchmark: use dummy data for inference + * + * The constructor will: + * 1. Create ImageRecordIter based on the given dataset file. + * 2. Load the model and parameter files. + * 3. Infer and construct NDArrays according to the input argument and create an executor. + */ +Predictor::Predictor(const std::string& model_json_file, + const std::string& model_params_file, + const Shape& input_shape, + bool use_gpu, + const std::string& dataset, + const int data_nthreads, + const std::string& data_layer_type, + const std::vector& rgb_mean, + const std::vector& rgb_std, + int shuffle_chunk_seed, + int seed, bool benchmark) + : input_shape_(input_shape), + use_gpu_(use_gpu), + dataset_(dataset), + data_nthreads_(data_nthreads), + data_layer_type_(data_layer_type), + rgb_mean_(rgb_mean), + rgb_std_(rgb_std), + shuffle_chunk_seed_(shuffle_chunk_seed), + seed_(seed), + benchmark_(benchmark) { + if (use_gpu) { + global_ctx_ = Context::gpu(); + } + + // initilize data iterator + if (!benchmark_ && !CreateImageRecordIter()) { + LG << "Error: failed to create ImageRecordIter"; + throw std::runtime_error("ImageRecordIter cannot be created"); + } + + // Load the model + LoadModel(model_json_file); + // Initilize the parameters + // benchmark=false, load parameters from file + // benchmark=true, randomly initialize parameters + if (!benchmark_) { + LoadParameters(model_params_file); + } else { + InitParameters(); + } + + int dtype = GetDataLayerType(); + if (dtype == -1) { + throw std::runtime_error("Unsupported data layer type..."); + } + args_map_["data"] = NDArray(input_shape_, global_ctx_, false, dtype); + Shape label_shape(input_shape_[0]); + args_map_["softmax_label"] = NDArray(label_shape, global_ctx_, false); + std::vector arg_arrays; + std::vector grad_arrays; + std::vector grad_reqs; + std::vector aux_arrays; + + // infer and create ndarrays according to the given input ndarrays. + net_.InferExecutorArrays(global_ctx_, &arg_arrays, &grad_arrays, &grad_reqs, + &aux_arrays, args_map_, std::map(), + std::map(), aux_map_); + for (auto& i : grad_reqs) i = OpReqType::kNullOp; + + // Create an executor after binding the model to input parameters. + executor_ = new Executor(net_, global_ctx_, arg_arrays, grad_arrays, grad_reqs, aux_arrays); +} + +/* + * The following function is used to get the data layer type for input data + */ +int Predictor::GetDataLayerType() { + int ret_type = -1; + if (data_layer_type_ == "float32") { + ret_type = kFloat32; + } else if (data_layer_type_ == "int8") { + ret_type = kInt8; + } else if (data_layer_type_ == "uint8") { + ret_type = kUint8; + } else { + LG << "Unsupported data layer type " << data_layer_type_ << "..." + << "Please use one of {float32, int8, uint8}"; + } + return ret_type; +} + +/* + * create a new ImageRecordIter according to the given parameters + */ +bool Predictor::CreateImageRecordIter() { + val_iter_ = new MXDataIter("ImageRecordIter"); + if (!FileExists(dataset_)) { + LG << "Error: " << dataset_ << " must be provided"; + return false; + } + + std::vector shape_vec; + for (index_t i = 1; i < input_shape_.ndim(); i++) + shape_vec.push_back(input_shape_[i]); + mxnet::TShape data_shape(shape_vec.begin(), shape_vec.end()); + + // set image record parser parameters + val_iter_->SetParam("path_imgrec", dataset_); + val_iter_->SetParam("label_width", 1); + val_iter_->SetParam("data_shape", data_shape); + val_iter_->SetParam("preprocess_threads", data_nthreads_); + val_iter_->SetParam("shuffle_chunk_seed", shuffle_chunk_seed_); + + // set Batch parameters + val_iter_->SetParam("batch_size", input_shape_[0]); + + // image record parameters + val_iter_->SetParam("shuffle", true); + val_iter_->SetParam("seed", seed_); + + // set normalize parameters + val_iter_->SetParam("mean_r", rgb_mean_[0]); + val_iter_->SetParam("mean_g", rgb_mean_[1]); + val_iter_->SetParam("mean_b", rgb_mean_[2]); + val_iter_->SetParam("std_r", rgb_std_[0]); + val_iter_->SetParam("std_g", rgb_std_[1]); + val_iter_->SetParam("std_b", rgb_std_[2]); + + // set prefetcher parameters + if (use_gpu_) { + val_iter_->SetParam("ctx", "gpu"); + } else { + val_iter_->SetParam("ctx", "cpu"); + } + val_iter_->SetParam("dtype", data_layer_type_); + + val_iter_->CreateDataIter(); + return true; +} + +/* + * The following function loads the model from json file. + */ +void Predictor::LoadModel(const std::string& model_json_file) { + if (!FileExists(model_json_file)) { + LG << "Model file " << model_json_file << " does not exist"; + throw std::runtime_error("Model file does not exist"); + } + LG << "Loading the model from " << model_json_file << std::endl; + net_ = Symbol::Load(model_json_file); +} + + +/* + * The following function loads the model parameters. + */ +void Predictor::LoadParameters(const std::string& model_parameters_file) { + if (!FileExists(model_parameters_file)) { + LG << "Parameter file " << model_parameters_file << " does not exist"; + throw std::runtime_error("Model parameters does not exist"); + } + LG << "Loading the model parameters from " << model_parameters_file << std::endl; + std::map parameters; + NDArray::Load(model_parameters_file, 0, ¶meters); + for (const auto &k : parameters) { + if (k.first.substr(0, 4) == "aux:") { + auto name = k.first.substr(4, k.first.size() - 4); + aux_map_[name] = k.second.Copy(global_ctx_); + } + if (k.first.substr(0, 4) == "arg:") { + auto name = k.first.substr(4, k.first.size() - 4); + args_map_[name] = k.second.Copy(global_ctx_); + } + } + /*WaitAll is need when we copy data between GPU and the main memory*/ + NDArray::WaitAll(); +} + +/* + * The following function randomly initializes the parameters when benchmark_ is true. + */ +void Predictor::InitParameters() { + std::vector data_shape; + for (index_t i = 0; i < input_shape_.ndim(); i++) { + data_shape.push_back(input_shape_[i]); + } + + std::map > arg_shapes; + std::vector > aux_shapes, in_shapes, out_shapes; + arg_shapes["data"] = data_shape; + net_.InferShape(arg_shapes, &in_shapes, &aux_shapes, &out_shapes); + + // initializer to call + Xavier xavier(Xavier::uniform, Xavier::avg, 2.0f); + + auto arg_name_list = net_.ListArguments(); + for (index_t i = 0; i < in_shapes.size(); i++) { + const auto &shape = in_shapes[i]; + const auto &arg_name = arg_name_list[i]; + int paramType = kFloat32; + if (Initializer::StringEndWith(arg_name, "weight_quantize") || + Initializer::StringEndWith(arg_name, "bias_quantize")) { + paramType = kInt8; + } + NDArray tmp_arr(shape, global_ctx_, false, paramType); + xavier(arg_name, &tmp_arr); + args_map_[arg_name] = tmp_arr.Copy(global_ctx_); + } + + auto aux_name_list = net_.ListAuxiliaryStates(); + for (index_t i = 0; i < aux_shapes.size(); i++) { + const auto &shape = aux_shapes[i]; + const auto &aux_name = aux_name_list[i]; + NDArray tmp_arr(shape, global_ctx_, false); + xavier(aux_name, &tmp_arr); + aux_map_[aux_name] = tmp_arr.Copy(global_ctx_); + } + /*WaitAll is need when we copy data between GPU and the main memory*/ + NDArray::WaitAll(); +} + +/* + * The following function runs the forward pass on the model + * and use dummy data for benchmark. + */ +void Predictor::BenchmarkScore(int num_inference_batches) { + // Create dummy data + std::vector dummy_data(input_shape_.Size()); + std::default_random_engine generator; + std::uniform_real_distribution val(0.0f, 1.0f); + for (size_t i = 0; i < static_cast(input_shape_.Size()); ++i) { + dummy_data[i] = static_cast(val(generator)); + } + executor_->arg_dict()["data"].SyncCopyFromCPU( + dummy_data.data(), + input_shape_.Size()); + NDArray::WaitAll(); + + LG << "Running the forward pass on model to evaluate the performance.."; + + // warm up. + for (int i = 0; i < 5; i++) { + executor_->Forward(false); + NDArray::WaitAll(); + } + + // Run the forward pass. + double ms = ms_now(); + for (int i = 0; i < num_inference_batches; i++) { + executor_->Forward(false); + NDArray::WaitAll(); + } + ms = ms_now() - ms; + LG << " benchmark completed!"; + LG << " batch size: " << input_shape_[0] << " num batch: " << num_inference_batches + << " throughput: " << 1000.0 * input_shape_[0] * num_inference_batches / ms + << " imgs/s latency:" << ms / input_shape_[0] / num_inference_batches << " ms"; +} + +/* + * \param skipped_batches skip the first number of batches + * + */ +bool Predictor::AdvanceDataIter(int skipped_batches) { + assert(skipped_batches >= 0); + if (skipped_batches == 0) return true; + int skipped_count = 0; + while (val_iter_->Next()) { + if (++skipped_count >= skipped_batches) break; + } + if (skipped_count != skipped_batches) return false; + return true; +} + +/* + * The following function runs the forward pass on the model + * and use real data for testing accuracy and performance. + */ +void Predictor::Score(int num_skipped_batches, int num_inference_batches) { + // Create metrics + Accuracy val_acc; + + val_iter_->Reset(); + val_acc.Reset(); + int nBatch = 0; + + if (!AdvanceDataIter(num_skipped_batches)) { + LG << "skipped batches should less than total batches!"; + return; + } + + double ms = ms_now(); + while (val_iter_->Next()) { + auto data_batch = val_iter_->GetDataBatch(); + data_batch.data.CopyTo(&args_map_["data"]); + data_batch.label.CopyTo(&args_map_["softmax_label"]); + NDArray::WaitAll(); + + // running on forward pass + executor_->Forward(false); + NDArray::WaitAll(); + val_acc.Update(data_batch.label, executor_->outputs[0]); + + if (++nBatch >= num_inference_batches) { + break; + } + } + ms = ms_now() - ms; + auto args_name = net_.ListArguments(); + LG << "INFO:" << "Dataset for inference: " << dataset_; + LG << "INFO:" << "label_name = " << args_name[args_name.size()-1]; + LG << "INFO:" << "rgb_mean: " << "(" << rgb_mean_[0] << ", " << rgb_mean_[1] + << ", " << rgb_mean_[2] << ")"; + LG << "INFO:" << "rgb_std: " << "(" << rgb_std_[0] << ", " << rgb_std_[1] + << ", " << rgb_std_[2] << ")"; + LG << "INFO:" << "Image shape: " << "(" << input_shape_[1] << ", " + << input_shape_[2] << ", " << input_shape_[3] << ")"; + LG << "INFO:" << "Finished inference with: " << nBatch * input_shape_[0] + << " images "; + LG << "INFO:" << "Batch size = " << input_shape_[0] << " for inference"; + LG << "INFO:" << "Accuracy: " << val_acc.Get(); + LG << "INFO:" << "Throughput: " << (1000.0 * nBatch * input_shape_[0] / ms) + << " images per second"; +} + +Predictor::~Predictor() { + if (executor_) { + delete executor_; + } + if (!benchmark_ && val_iter_) { + delete val_iter_; + } + MXNotifyShutdown(); +} + +/* + * Convert the input string of number into the vector. + */ +template +std::vector createVectorFromString(const std::string& input_string) { + std::vector dst_vec; + char *p_next; + T elem; + bool bFloat = std::is_same::value; + if (!bFloat) { + elem = strtol(input_string.c_str(), &p_next, 10); + } else { + elem = strtof(input_string.c_str(), &p_next); + } + + dst_vec.push_back(elem); + while (*p_next) { + if (!bFloat) { + elem = strtol(p_next, &p_next, 10); + } else { + elem = strtof(p_next, &p_next); + } + dst_vec.push_back(elem); + } + return dst_vec; +} + +void printUsage() { + std::cout << "Usage:" << std::endl; + std::cout << "imagenet_inference --symbol_file " << std::endl + << "--params_file " << std::endl + << "--dataset " << std::endl + << "--data_nthreads " << std::endl + << "--input_shape ] " << std::endl + << "--rgb_mean " + << std::endl + << "--rgb_std " << std::endl + << "--batch_size " << std::endl + << "--num_skipped_batches " << std::endl + << "--num_inference_batches " << std::endl + << "--data_layer_type " << std::endl + << "--gpu " << std::endl + << "--benchmark " + << std::endl; +} + +int main(int argc, char** argv) { + std::string model_file_json; + std::string model_file_params; + std::string dataset(""); + std::string input_rgb_mean("0 0 0"); + std::string input_rgb_std("1 1 1"); + bool use_gpu = false; + bool benchmark = false; + int batch_size = 64; + int num_skipped_batches = 0; + int num_inference_batches = 100; + std::string data_layer_type("float32"); + std::string input_shape("3 224 224"); + int seed = 48564309; + int shuffle_chunk_seed = 3982304; + int data_nthreads = 60; + + int index = 1; + while (index < argc) { + if (strcmp("--symbol_file", argv[index]) == 0) { + index++; + model_file_json = (index < argc ? argv[index]:""); + } else if (strcmp("--params_file", argv[index]) == 0) { + index++; + model_file_params = (index < argc ? argv[index]:""); + } else if (strcmp("--dataset", argv[index]) == 0) { + index++; + dataset = (index < argc ? argv[index]:dataset); + } else if (strcmp("--data_nthreads", argv[index]) == 0) { + index++; + data_nthreads = strtol(argv[index], nullptr, 10); + } else if (strcmp("--input_shape", argv[index]) == 0) { + index++; + input_shape = (index < argc ? argv[index]:input_shape); + } else if (strcmp("--rgb_mean", argv[index]) == 0) { + index++; + input_rgb_mean = (index < argc ? argv[index]:input_rgb_mean); + } else if (strcmp("--rgb_std", argv[index]) == 0) { + index++; + input_rgb_std = (index < argc ? argv[index]:input_rgb_std); + } else if (strcmp("--batch_size", argv[index]) == 0) { + index++; + batch_size = strtol(argv[index], nullptr, 10); + } else if (strcmp("--num_skipped_batches", argv[index]) == 0) { + index++; + num_skipped_batches = strtol(argv[index], nullptr, 10); + } else if (strcmp("--num_inference_batches", argv[index]) == 0) { + index++; + num_inference_batches = strtol(argv[index], nullptr, 10); + } else if (strcmp("--data_layer_type", argv[index]) == 0) { + index++; + data_layer_type = (index < argc ? argv[index]:data_layer_type); + } else if (strcmp("--gpu", argv[index]) == 0) { + use_gpu = true; + } else if (strcmp("--benchmark", argv[index]) == 0) { + benchmark = true; + } else if (strcmp("--help", argv[index]) == 0) { + printUsage(); + return 0; + } + index++; + } + + if (model_file_json.empty() || (!benchmark && model_file_params.empty())) { + LG << "ERROR: Model details such as symbol, param files are not specified"; + printUsage(); + return 1; + } + std::vector input_dimensions = createVectorFromString(input_shape); + input_dimensions.insert(input_dimensions.begin(), batch_size); + Shape input_data_shape(input_dimensions); + + std::vector rgb_mean = createVectorFromString(input_rgb_mean); + std::vector rgb_std = createVectorFromString(input_rgb_std); + + // Initialize the predictor object + Predictor predict(model_file_json, model_file_params, input_data_shape, use_gpu, dataset, + data_nthreads, data_layer_type, rgb_mean, rgb_std, shuffle_chunk_seed, + seed, benchmark); + + if (benchmark) { + predict.BenchmarkScore(num_inference_batches); + } else { + predict.Score(num_skipped_batches, num_inference_batches); + } + return 0; +} diff --git a/cpp-package/example/inference/inception_inference.cpp b/cpp-package/example/inference/inception_inference.cpp deleted file mode 100644 index cb952aa69f54..000000000000 --- a/cpp-package/example/inference/inception_inference.cpp +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * This example demonstrates image classification workflow with pre-trained models using MXNet C++ API. - * The example performs following tasks. - * 1. Load the pre-trained model. - * 2. Load the parameters of pre-trained model. - * 3. Load the image to be classified in to NDArray. - * 4. Normalize the image using the mean of images that were used for training. - * 5. Run the forward pass and predict the input image. - */ - -#include -#include -#include -#include -#include -#include -#include "mxnet-cpp/MxNetCpp.h" -#include - -using namespace mxnet::cpp; - -static mx_float DEFAULT_MEAN_R = 123.675; -static mx_float DEFAULT_MEAN_G = 116.28; -static mx_float DEFAULT_MEAN_B = 103.53; -/* - * class Predictor - * - * This class encapsulates the functionality to load the model, process input image and run the forward pass. - */ - -class Predictor { - public: - Predictor() {} - Predictor(const std::string& model_json_file, - const std::string& model_params_file, - const Shape& input_shape, - bool gpu_context_type = false, - const std::string& synset_file = "", - const std::string& mean_image_file = ""); - void PredictImage(const std::string& image_file); - ~Predictor(); - - private: - void LoadModel(const std::string& model_json_file); - void LoadParameters(const std::string& model_parameters_file); - void LoadSynset(const std::string& synset_file); - NDArray LoadInputImage(const std::string& image_file); - void LoadMeanImageData(); - void LoadDefaultMeanImageData(); - void NormalizeInput(const std::string& mean_image_file); - inline bool FileExists(const std::string& name) { - struct stat buffer; - return (stat(name.c_str(), &buffer) == 0); - } - NDArray mean_img; - std::map args_map; - std::map aux_map; - std::vector output_labels; - Symbol net; - Executor *executor; - Shape input_shape; - NDArray mean_image_data; - NDArray std_dev_image_data; - Context global_ctx = Context::cpu(); - std::string mean_image_file; -}; - - -/* - * The constructor takes following parameters as input: - * 1. model_json_file: The model in json formatted file. - * 2. model_params_file: File containing model parameters - * 3. synset_file: File containing the list of image labels - * 4. input_shape: Shape of input data to the model. Since this class will be running one inference at a time, - * the input shape is required to be in format Shape(1, number_of_channels, height, width) - * The input image will be resized to (height x width) size before running the inference. - * The constructor will: - * 1. Load the model and parameter files. - * 2. Load the synset file. - * 3. Invoke the SimpleBind to bind the input argument to the model and create an executor. - * - * The SimpleBind is expected to be invoked only once. - */ -Predictor::Predictor(const std::string& model_json_file, - const std::string& model_params_file, - const Shape& input_shape, - bool gpu_context_type, - const std::string& synset_file, - const std::string& mean_image_file): - input_shape(input_shape), - mean_image_file(mean_image_file) { - if (gpu_context_type) { - global_ctx = Context::gpu(); - } - // Load the model - LoadModel(model_json_file); - - // Load the model parameters. - LoadParameters(model_params_file); - - /* - * The data will be used to output the exact label that matches highest output of the model. - */ - LoadSynset(synset_file); - - /* - * Load the mean image data if specified. - */ - if (!mean_image_file.empty()) { - LoadMeanImageData(); - } else { - LG << "Mean image file for normalizing the input is not provide." - << " We will use the default mean values for R,G and B channels."; - LoadDefaultMeanImageData(); - } - - // Create an executor after binding the model to input parameters. - args_map["data"] = NDArray(input_shape, global_ctx, false); - executor = net.SimpleBind(global_ctx, args_map, std::map(), - std::map(), aux_map); -} - -/* - * The following function loads the model from json file. - */ -void Predictor::LoadModel(const std::string& model_json_file) { - if (!FileExists(model_json_file)) { - LG << "Model file " << model_json_file << " does not exist"; - throw std::runtime_error("Model file does not exist"); - } - LG << "Loading the model from " << model_json_file << std::endl; - net = Symbol::Load(model_json_file); -} - - -/* - * The following function loads the model parameters. - */ -void Predictor::LoadParameters(const std::string& model_parameters_file) { - if (!FileExists(model_parameters_file)) { - LG << "Parameter file " << model_parameters_file << " does not exist"; - throw std::runtime_error("Model parameters does not exist"); - } - LG << "Loading the model parameters from " << model_parameters_file << std::endl; - std::map parameters; - NDArray::Load(model_parameters_file, 0, ¶meters); - for (const auto &k : parameters) { - if (k.first.substr(0, 4) == "aux:") { - auto name = k.first.substr(4, k.first.size() - 4); - aux_map[name] = k.second.Copy(global_ctx); - } - if (k.first.substr(0, 4) == "arg:") { - auto name = k.first.substr(4, k.first.size() - 4); - args_map[name] = k.second.Copy(global_ctx); - } - } - /*WaitAll is need when we copy data between GPU and the main memory*/ - NDArray::WaitAll(); -} - - -/* - * The following function loads the synset file. - * This information will be used later to report the label of input image. - */ -void Predictor::LoadSynset(const std::string& synset_file) { - if (!FileExists(synset_file)) { - LG << "Synset file " << synset_file << " does not exist"; - throw std::runtime_error("Synset file does not exist"); - } - LG << "Loading the synset file."; - std::ifstream fi(synset_file.c_str()); - if (!fi.is_open()) { - std::cerr << "Error opening synset file " << synset_file << std::endl; - throw std::runtime_error("Error in opening the synset file."); - } - std::string synset, lemma; - while (fi >> synset) { - getline(fi, lemma); - output_labels.push_back(lemma); - } - fi.close(); -} - - -/* - * The following function loads the mean data from mean image file. - * This data will be used for normalizing the image before running the forward - * pass. - * The output data has the same shape as that of the input image data. - */ -void Predictor::LoadMeanImageData() { - LG << "Load the mean image data that will be used to normalize " - << "the image before running forward pass."; - mean_image_data = NDArray(input_shape, global_ctx, false); - mean_image_data.SyncCopyFromCPU( - NDArray::LoadToMap(mean_image_file)["mean_img"].GetData(), - input_shape.Size()); -} - - -/* - * The following function loads the default mean values for - * R, G and B channels into NDArray that has the same shape as that of - * input image. - */ -void Predictor::LoadDefaultMeanImageData() { - LG << "Loading the default mean image data"; - std::vector array; - /*resize pictures to (224, 224) according to the pretrained model*/ - int height = input_shape[2]; - int width = input_shape[3]; - int channels = input_shape[1]; - std::vector default_means; - default_means.push_back(DEFAULT_MEAN_R); - default_means.push_back(DEFAULT_MEAN_G); - default_means.push_back(DEFAULT_MEAN_B); - for (int c = 0; c < channels; ++c) { - for (int i = 0; i < height; ++i) { - for (int j = 0; j < width; ++j) { - array.push_back(default_means[c]); - } - } - } - mean_image_data = NDArray(input_shape, global_ctx, false); - mean_image_data.SyncCopyFromCPU(array.data(), input_shape.Size()); -} - - -/* - * The following function loads the input image into NDArray. - */ -NDArray Predictor::LoadInputImage(const std::string& image_file) { - if (!FileExists(image_file)) { - LG << "Image file " << image_file << " does not exist"; - throw std::runtime_error("Image file does not exist"); - } - LG << "Loading the image " << image_file << std::endl; - std::vector array; - cv::Mat mat = cv::imread(image_file); - /*resize pictures to (224, 224) according to the pretrained model*/ - int height = input_shape[2]; - int width = input_shape[3]; - int channels = input_shape[1]; - cv::resize(mat, mat, cv::Size(height, width)); - for (int c = 0; c < channels; ++c) { - for (int i = 0; i < height; ++i) { - for (int j = 0; j < width; ++j) { - array.push_back(static_cast(mat.data[(i * height + j) * 3 + c])); - } - } - } - NDArray image_data = NDArray(input_shape, global_ctx, false); - image_data.SyncCopyFromCPU(array.data(), input_shape.Size()); - return image_data; -} - - -/* - * The following function runs the forward pass on the model. - * The executor is created in the constructor. - * - */ -void Predictor::PredictImage(const std::string& image_file) { - // Load the input image - NDArray image_data = LoadInputImage(image_file); - - // Normalize the image - image_data.Slice(0, 1) -= mean_image_data; - - LG << "Running the forward pass on model to predict the image"; - /* - * The executor->arg_arrays represent the arguments to the model. - * - * Copying the image_data that contains the NDArray of input image - * to the arg map of the executor. The input is stored with the key "data" in the map. - * - */ - image_data.CopyTo(&(executor->arg_dict()["data"])); - - // Run the forward pass. - executor->Forward(false); - - // The output is available in executor->outputs. - auto array = executor->outputs[0].Copy(Context::cpu()); - /* - * Find out the maximum accuracy and the index associated with that accuracy. - * This is done by using the argmax operator on NDArray. - */ - auto predicted = array.ArgmaxChannel(); - /* - * Wait until all the previous write operations on the 'predicted' - * NDArray to be complete before we read it. - * This method guarantees that all previous write operations that pushed into the backend engine - * for execution are actually finished. - */ - predicted.WaitToRead(); - - int best_idx = predicted.At(0); - float best_accuracy = array.At(0, best_idx); - - if (output_labels.empty()) { - LG << "The model predicts the highest accuracy of " << best_accuracy << " at index " - << best_idx; - } else { - LG << "The model predicts the input image to be a [" << output_labels[best_idx] - << " ] with Accuracy = " << best_accuracy << std::endl; - } -} - - -Predictor::~Predictor() { - delete executor; - MXNotifyShutdown(); -} - - -/* - * Convert the input string of number of hidden units into the vector of integers. - */ -std::vector getShapeDimensions(const std::string& hidden_units_string) { - std::vector dimensions; - char *p_next; - int num_unit = strtol(hidden_units_string.c_str(), &p_next, 10); - dimensions.push_back(num_unit); - while (*p_next) { - num_unit = strtol(p_next, &p_next, 10); - dimensions.push_back(num_unit); - } - return dimensions; -} - -void printUsage() { - std::cout << "Usage:" << std::endl; - std::cout << "inception_inference --symbol " << std::endl - << "--params " << std::endl - << "--image " << std::endl - << "--synset " << std::endl - << "[--input_shape ] " << std::endl - << "[--mean ] " - << std::endl - << "[--gpu ]" - << std::endl; -} - -int main(int argc, char** argv) { - std::string model_file_json; - std::string model_file_params; - std::string synset_file = ""; - std::string mean_image = ""; - std::string input_image = ""; - bool gpu_context_type = false; - - std::string input_shape = "3 224 224"; - int index = 1; - while (index < argc) { - if (strcmp("--symbol", argv[index]) == 0) { - index++; - model_file_json = (index < argc ? argv[index]:""); - } else if (strcmp("--params", argv[index]) == 0) { - index++; - model_file_params = (index < argc ? argv[index]:""); - } else if (strcmp("--synset", argv[index]) == 0) { - index++; - synset_file = (index < argc ? argv[index]:""); - } else if (strcmp("--mean", argv[index]) == 0) { - index++; - mean_image = (index < argc ? argv[index]:""); - } else if (strcmp("--image", argv[index]) == 0) { - index++; - input_image = (index < argc ? argv[index]:""); - } else if (strcmp("--input_shape", argv[index]) == 0) { - index++; - input_shape = (index < argc ? argv[index]:input_shape); - } else if (strcmp("--gpu", argv[index]) == 0) { - gpu_context_type = true; - } else if (strcmp("--help", argv[index]) == 0) { - printUsage(); - return 0; - } - index++; - } - - if (model_file_json.empty() || model_file_params.empty() || synset_file.empty()) { - LG << "ERROR: Model details such as symbol, param and/or synset files are not specified"; - printUsage(); - return 1; - } - - if (input_image.empty()) { - LG << "ERROR: Path to the input image is not specified."; - printUsage(); - return 1; - } - - std::vector input_dimensions = getShapeDimensions(input_shape); - - /* - * Since we are running inference for 1 image, add 1 to the input_dimensions so that - * the shape of input data for the model will be - * {no. of images, channels, height, width} - */ - input_dimensions.insert(input_dimensions.begin(), 1); - - Shape input_data_shape(input_dimensions); - - try { - // Initialize the predictor object - Predictor predict(model_file_json, model_file_params, input_data_shape, gpu_context_type, - synset_file, mean_image); - - // Run the forward pass to predict the image. - predict.PredictImage(input_image); - } catch (std::runtime_error &error) { - LG << "Execution failed with ERROR: " << error.what(); - } catch (...) { - /* - * If underlying MXNet code has thrown an exception the error message is - * accessible through MXGetLastError() function. - */ - LG << "Execution failed with following MXNet error"; - LG << MXGetLastError(); - } - return 0; -} diff --git a/cpp-package/example/inference/unit_test_imagenet_inference.sh b/cpp-package/example/inference/unit_test_imagenet_inference.sh new file mode 100755 index 000000000000..c645388cd419 --- /dev/null +++ b/cpp-package/example/inference/unit_test_imagenet_inference.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -ex +# create ./model directory if not existed +if [ ! -d model ]; then + mkdir -p model +fi +# create ./data directory if not existed +if [ ! -d data ]; then + mkdir -p data +fi +# Downloading the data and model if not existed +model_file=./model/Inception-BN-symbol.json +params_file=./model/Inception-BN-0126.params +if [ ! -f ${model_file} ] || [ ! -f ${params_file} ]; then + wget -nc http://data.mxnet.io/models/imagenet/inception-bn.tar.gz + tar -xvzf inception-bn.tar.gz -C model +fi +cd model +wget -nc https://raw.githubusercontent.com/dmlc/gluon-cv/master/gluoncv/model_zoo/quantized/resnet50_v1_int8-symbol.json +cd ../data +wget -nc http://data.mxnet.io/data/val_256_q90.rec +cd .. + +# Running inference on imagenet. +if [ "$(uname)" == "Darwin" ]; then + echo ">>> INFO: FP32 real data" + DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:../../../lib ./imagenet_inference --symbol_file "./model/Inception-BN-symbol.json" --params_file "./model/Inception-BN-0126.params" --dataset "./data/val_256_q90.rec" --rgb_mean "123.68 116.779 103.939" --batch_size 1 --num_skipped_batches 50 --num_inference_batches 500 + + echo ">>> INFO: FP32 dummy data" + DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:../../../lib ./imagenet_inference --symbol_file "./model/Inception-BN-symbol.json" --batch_size 1 --num_inference_batches 500 --benchmark +else + echo ">>> INFO: FP32 real data" + LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:../../../lib ./imagenet_inference --symbol_file "./model/Inception-BN-symbol.json" --params_file "./model/Inception-BN-0126.params" --dataset "./data/val_256_q90.rec" --rgb_mean "123.68 116.779 103.939" --batch_size 1 --num_skipped_batches 50 --num_inference_batches 500 + + echo ">>> INFO: FP32 dummy data" + LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:../../../lib ./imagenet_inference --symbol_file "./model/Inception-BN-symbol.json" --batch_size 1 --num_inference_batches 500 --benchmark + + lib_name=$(ls -a ../../../lib | grep -oE 'mkldnn' | tail -1) + if [[ -n ${lib_name} ]] && [[ 'mkldnn' =~ ${lib_name} ]]; then + echo ">>> INFO: INT8 dummy data" + LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:../../../lib ./imagenet_inference --symbol_file "./model/resnet50_v1_int8-symbol.json" --batch_size 1 --num_inference_batches 500 --benchmark + else + echo "Skipped INT8 test because mkldnn was not found which is required for running inference with quantized models." + fi +fi diff --git a/cpp-package/example/inference/unit_test_inception_inference.sh b/cpp-package/example/inference/unit_test_inception_inference.sh deleted file mode 100755 index c3c4630f6e4a..000000000000 --- a/cpp-package/example/inference/unit_test_inception_inference.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash - -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -# Downloading the data and model -mkdir -p model -wget -nc http://data.mxnet.io/models/imagenet/inception-bn.tar.gz -wget -nc -O model/dog.jpg https://github.com/dmlc/web-data/blob/master/mxnet/doc/tutorials/python/predict_image/dog.jpg?raw=true -wget -nc -O model/mean_224.nd https://github.com/dmlc/web-data/raw/master/mxnet/example/feature_extract/mean_224.nd -tar -xvzf inception-bn.tar.gz -C model - - -# Running the example with dog image. -if [ "$(uname)" == "Darwin" ]; then - DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:../../../lib ./inception_inference --symbol "./model/Inception-BN-symbol.json" --params "./model/Inception-BN-0126.params" --synset "./model/synset.txt" --mean "./model/mean_224.nd" --image "./model/dog.jpg" 2&> inception_inference.log -else - LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:../../../lib ./inception_inference --symbol "./model/Inception-BN-symbol.json" --params "./model/Inception-BN-0126.params" --synset "./model/synset.txt" --mean "./model/mean_224.nd" --image "./model/dog.jpg" 2&> inception_inference.log -fi -result=`grep -c "pug-dog" inception_inference.log` -if [ $result == 1 ]; -then - echo "PASS: inception_inference correctly identified the image." - exit 0 -else - echo "FAIL: inception_inference FAILED to identify the image." - exit 1 -fi diff --git a/cpp-package/example/mlp_csv.cpp b/cpp-package/example/mlp_csv.cpp index 0d9103783a79..09842e3b402b 100644 --- a/cpp-package/example/mlp_csv.cpp +++ b/cpp-package/example/mlp_csv.cpp @@ -25,6 +25,7 @@ * CSV format. */ #include +#include #include "utils.h" #include "mxnet-cpp/MxNetCpp.h" diff --git a/cpp-package/include/mxnet-cpp/initializer.h b/cpp-package/include/mxnet-cpp/initializer.h index 021808b38e34..34725b9dfa81 100644 --- a/cpp-package/include/mxnet-cpp/initializer.h +++ b/cpp-package/include/mxnet-cpp/initializer.h @@ -30,6 +30,7 @@ #include #include #include +#include #include "mxnet-cpp/ndarray.h" namespace mxnet { @@ -67,6 +68,14 @@ class Initializer { InitZero(arr); } else if (StringEndWith(name, "moving_avg")) { InitZero(arr); + } else if (StringEndWith(name, "min")) { + InitZero(arr); + } else if (StringEndWith(name, "max")) { + InitOne(arr); + } else if (StringEndWith(name, "weight_quantize")) { + InitQuantizedWeight(arr); + } else if (StringEndWith(name, "bias_quantize")) { + InitQuantizedBias(arr); } else { InitDefault(arr); } @@ -91,6 +100,14 @@ class Initializer { virtual void InitGamma(NDArray* arr) { (*arr) = 1.0f; } virtual void InitBeta(NDArray* arr) { (*arr) = 0.0f; } virtual void InitWeight(NDArray* arr) {} + virtual void InitQuantizedWeight(NDArray* arr) { + std::default_random_engine generator; + std::uniform_int_distribution _val(-127, 127); + (*arr) = _val(generator); + } + virtual void InitQuantizedBias(NDArray* arr) { + (*arr) = 0; + } virtual void InitDefault(NDArray* arr) {} }; @@ -122,6 +139,14 @@ class Uniform : public Initializer { Uniform(float begin, float end) : begin(begin), end(end) {} void operator()(const std::string &name, NDArray *arr) override { + if (StringEndWith(name, "weight_quantize")) { + InitQuantizedWeight(arr); + return; + } + if (StringEndWith(name, "bias_quantize")) { + InitQuantizedBias(arr); + return; + } NDArray::SampleUniform(begin, end, arr); } protected: @@ -133,6 +158,14 @@ class Normal : public Initializer { Normal(float mu, float sigma) : mu(mu), sigma(sigma) {} void operator()(const std::string &name, NDArray *arr) override { + if (StringEndWith(name, "weight_quantize")) { + InitQuantizedWeight(arr); + return; + } + if (StringEndWith(name, "bias_quantize")) { + InitQuantizedBias(arr); + return; + } NDArray::SampleGaussian(mu, sigma, arr); } protected: @@ -143,6 +176,14 @@ class Bilinear : public Initializer { public: Bilinear() {} void operator()(const std::string &name, NDArray *arr) override { + if (StringEndWith(name, "weight_quantize")) { + InitQuantizedWeight(arr); + return; + } + if (StringEndWith(name, "bias_quantize")) { + InitQuantizedBias(arr); + return; + } InitBilinear(arr); } }; @@ -164,6 +205,15 @@ class Xavier : public Initializer { : rand_type(rand_type), factor_type(factor_type), magnitude(magnitude) {} void operator()(const std::string &name, NDArray* arr) override { + if (StringEndWith(name, "weight_quantize")) { + InitQuantizedWeight(arr); + return; + } + if (StringEndWith(name, "bias_quantize")) { + InitQuantizedBias(arr); + return; + } + Shape shape(arr->GetShape()); float hw_scale = 1.0f; if (shape.ndim() > 2) { diff --git a/cpp-package/include/mxnet-cpp/io.h b/cpp-package/include/mxnet-cpp/io.h index 7099d7d46fee..7d2d620bd886 100644 --- a/cpp-package/include/mxnet-cpp/io.h +++ b/cpp-package/include/mxnet-cpp/io.h @@ -60,6 +60,8 @@ class DataIter { return DataBatch{GetData(), GetLabel(), GetPadNum(), GetIndex()}; } void Reset() { BeforeFirst(); } + + virtual ~DataIter() = default; }; class MXDataIterMap { diff --git a/cpp-package/tests/ci_test.sh b/cpp-package/tests/ci_test.sh index ef7fceacfd6e..e90493442246 100755 --- a/cpp-package/tests/ci_test.sh +++ b/cpp-package/tests/ci_test.sh @@ -63,8 +63,8 @@ cp ../../build/cpp-package/example/test_ndarray_copy . sh unittests/unit_test_mlp_csv.sh cd inference -cp ../../../build/cpp-package/example/inception_inference . -./unit_test_inception_inference.sh +cp ../../../build/cpp-package/example/imagenet_inference . +./unit_test_imagenet_inference.sh cp ../../../build/cpp-package/example/sentiment_analysis_rnn . ./unit_test_sentiment_analysis_rnn.sh diff --git a/example/quantization/README.md b/example/quantization/README.md index 93a14cf473ad..40f1371c33cc 100644 --- a/example/quantization/README.md +++ b/example/quantization/README.md @@ -27,15 +27,15 @@ The following models have been tested on Linux systems. | Model | Source | Dataset | FP32 Accuracy (top-1/top-5)| INT8 Accuracy (top-1/top-5)| |:---|:---|---|:---:|:---:| -| [ResNet18-V1](#3) | [Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html) | [Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) |70.07%/89.30%|69.85%/89.23%| -| [ResNet50-V1](#3) | [Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html) | [Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) | 75.87%/92.72% | 75.71%/92.65% | -| [ResNet101-V1](#3) | [Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html) | [Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) | 77.3%/93.58% | 77.09%/93.41% | -|[Squeezenet 1.0](#4)|[Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|57.01%/79.71%|56.62%/79.55%| -|[MobileNet 1.0](#5)|[Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|69.76%/89.32%|69.61%/89.09%| -|[MobileNetV2 1.0](#6)|[Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|70.14%/89.60%|69.53%/89.24%| -|[Inception V3](#7)|[Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|76.49%/93.10% |76.38%/93% | -|[ResNet152-V2](#8)|[MXNet ModelZoo](http://data.mxnet.io/models/imagenet/resnet/152-layers/)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|76.76%/93.03%|76.48%/92.96%| -|[Inception-BN](#9)|[MXNet ModelZoo](http://data.mxnet.io/models/imagenet/inception-bn/)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|72.09%/90.60%|72.00%/90.53%| +| [ResNet18-V1](#3) | [Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html) | [Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) |70.15%/89.38%|69.92%/89.26%| +| [ResNet50-V1](#3) | [Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html) | [Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) | 76.34%/93.13% | 75.91%/92.95% | +| [ResNet101-V1](#3) | [Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html) | [Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec) | 77.33%/93.59% | 77.05%/93.43% | +|[Squeezenet 1.0](#4)|[Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|56.98%/79.20%|52.98%/77.21%| +|[MobileNet 1.0](#5)|[Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|72.23%/90.64%|72.03%/90.42%| +|[MobileNetV2 1.0](#6)|[Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|70.27%/89.62%|69.70%/89.26%| +|[Inception V3](#7)|[Gluon-CV](https://gluon-cv.mxnet.io/model_zoo/classification.html)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|77.76%/93.83% |77.87%/93.78% | +|[ResNet152-V2](#8)|[MXNet ModelZoo](http://data.mxnet.io/models/imagenet/resnet/152-layers/)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|76.65%/93.07%|76.36%/92.89%| +|[Inception-BN](#9)|[MXNet ModelZoo](http://data.mxnet.io/models/imagenet/inception-bn/)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|72.28%/90.63%|72.20%/90.56%| | [SSD-VGG16](#10) | [example/ssd](https://github.com/apache/incubator-mxnet/tree/master/example/ssd) | VOC2007/2012 | 0.8366 mAP | 0.8364 mAP | | [SSD-VGG16](#10) | [example/ssd](https://github.com/apache/incubator-mxnet/tree/master/example/ssd) | COCO2014 | 0.2552 mAP | 0.253 mAP | From ba30644612357930fd4543f01800d89be7963f8e Mon Sep 17 00:00:00 2001 From: Pedro Larroy Date: Wed, 26 Jun 2019 17:13:53 -0700 Subject: [PATCH 015/813] [DOC] Clarify that global pooling is going to reset padding (#15269) This behaviour changed from older MXNet versions in which global pooling would consider padding. This clarifies the user documentation. See also #14421 --- src/operator/nn/pooling.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/operator/nn/pooling.cc b/src/operator/nn/pooling.cc index 870557756128..41a486e141d1 100644 --- a/src/operator/nn/pooling.cc +++ b/src/operator/nn/pooling.cc @@ -389,8 +389,8 @@ The definition of *f* depends on ``pooling_convention``, which has two options: f(x, k, p, s) = ceil((x+2*p-k)/s)+1 -But ``global_pool`` is set to be true, then do a global pooling, namely reset -``kernel=(height, width)``. +When ``global_pool`` is set to be true, then global pooling is performed. It will reset +``kernel=(height, width)`` and set the appropiate padding to 0. Three pooling options are supported by ``pool_type``: From 582489cebc16a8af21738281dcaee5eee54e478d Mon Sep 17 00:00:00 2001 From: Zhennan Qin Date: Thu, 27 Jun 2019 11:08:26 +0800 Subject: [PATCH 016/813] Fix Cached_op with static_shape=true (#15298) * Fix * run ci --- src/imperative/cached_op.cc | 7 ++++-- src/nnvm/legacy_op_util.cc | 47 +++++++++++++++---------------------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/imperative/cached_op.cc b/src/imperative/cached_op.cc index d7e1543ec781..efe38019cfda 100644 --- a/src/imperative/cached_op.cc +++ b/src/imperative/cached_op.cc @@ -81,6 +81,7 @@ struct CachedOp::CachedOpState { std::vector buff; std::vector arrays; + std::vector arrays_with_in_out; std::vector array_reqs; std::vector op_states; @@ -762,7 +763,8 @@ OpStatePtr CachedOp::StaticForward( // We are going to add input and output arrays to the array list. // The input and output arrays should only be valid for this run, // so we shouldn't modify the state's array list. - auto arrays = state.arrays; + state.arrays_with_in_out = state.arrays; + auto& arrays = state.arrays_with_in_out; if (config_.static_shape) { for (auto i : config_.param_indices) { auto nid = idx.input_nodes()[i]; @@ -1063,7 +1065,8 @@ void CachedOp::StaticBackward( // We are going to add input and output arrays to the array list. // The input and output arrays should only be valid for this run, // so we shouldn't modify the state's array list. - auto arrays = state.arrays; + state.arrays_with_in_out = state.arrays; + auto& arrays = state.arrays_with_in_out; for (size_t i = 0; i < state.info.bwd_input_eid.size(); ++i) { auto eid = state.info.bwd_input_eid[i]; if (eid == kEidNotExist) { diff --git a/src/nnvm/legacy_op_util.cc b/src/nnvm/legacy_op_util.cc index 698666f94d90..3e03b6bd2d16 100644 --- a/src/nnvm/legacy_op_util.cc +++ b/src/nnvm/legacy_op_util.cc @@ -79,7 +79,6 @@ class OperatorState { public: OperatorState(Operator *opr, const OperatorProperty *prop) { opr_ = opr; - fwd_init_ = bwd_init_ = false; in_data_fwd_.resize(prop->ListArguments().size()); in_data_bwd_.resize(prop->ListArguments().size()); @@ -110,19 +109,16 @@ class OperatorState { const std::vector& inputs, const std::vector& req, const std::vector& outputs) { - if (!fwd_init_) { - CHECK_EQ(inputs.size(), in_data_fwd_.size() + aux_data_.size()); - CHECK_EQ(outputs.size(), out_data_.size()); - // in_data_bwd_ has the same tblobs as the ones in in_data_fwd_, except that the ones - // referred by arg_data_ptr_ will be overriden - for (size_t i = 0; i < in_data_fwd_.size(); ++i) in_data_fwd_[i] = inputs[i]; - for (size_t i = 0; i < in_data_fwd_.size(); ++i) in_data_bwd_[i] = inputs[i]; - for (size_t i = 0; i < aux_data_.size(); ++i) { - aux_data_[i] = inputs[i + in_data_fwd_.size()]; - } - for (size_t i = 0; i < out_data_.size(); ++i) out_data_[i] = outputs[i]; - fwd_init_ = true; + CHECK_EQ(inputs.size(), in_data_fwd_.size() + aux_data_.size()); + CHECK_EQ(outputs.size(), out_data_.size()); + // in_data_bwd_ has the same tblobs as the ones in in_data_fwd_, except that the ones + // referred by arg_data_ptr_ will be overriden + for (size_t i = 0; i < in_data_fwd_.size(); ++i) in_data_fwd_[i] = inputs[i]; + for (size_t i = 0; i < in_data_fwd_.size(); ++i) in_data_bwd_[i] = inputs[i]; + for (size_t i = 0; i < aux_data_.size(); ++i) { + aux_data_[i] = inputs[i + in_data_fwd_.size()]; } + for (size_t i = 0; i < out_data_.size(); ++i) out_data_[i] = outputs[i]; opr_->Forward(ctx, in_data_fwd_, req, out_data_, aux_data_); } @@ -130,27 +126,22 @@ class OperatorState { const std::vector& inputs, const std::vector& req, const std::vector& outputs) { - if (!bwd_init_) { - CHECK(fwd_init_); - CHECK_EQ(arg_data_ptr_.size() + aux_data_.size(), inputs.size()); - // override tblobs pointed by arg_data_ptr_ since they might not contain - // initialized data during forward pass. - for (size_t i = 0; i < arg_data_ptr_.size(); ++i) { - *arg_data_ptr_[i] = inputs[i]; - } - for (size_t i = 0; i < aux_data_.size(); ++i) { - aux_data_[i] = inputs[inputs.size() - aux_data_.size() + i]; - } - CHECK_EQ(outputs.size(), in_grad_.size()); - for (size_t i = 0; i < outputs.size(); ++i) in_grad_[i] = outputs[i]; - bwd_init_ = true; + CHECK_EQ(arg_data_ptr_.size() + aux_data_.size(), inputs.size()); + // override tblobs pointed by arg_data_ptr_ since they might not contain + // initialized data during forward pass. + for (size_t i = 0; i < arg_data_ptr_.size(); ++i) { + *arg_data_ptr_[i] = inputs[i]; + } + for (size_t i = 0; i < aux_data_.size(); ++i) { + aux_data_[i] = inputs[inputs.size() - aux_data_.size() + i]; } + CHECK_EQ(outputs.size(), in_grad_.size()); + for (size_t i = 0; i < outputs.size(); ++i) in_grad_[i] = outputs[i]; opr_->Backward(ctx, out_grad_, in_data_bwd_, out_data_, req, in_grad_, aux_data_); } private: Operator *opr_; - bool fwd_init_, bwd_init_; // input data blobs for forward and backward // in_data_fwd_ and in_data_bwd_ will hold different tblobs when StorageFallbackOpExecutor // performs storage fallback on a non-default input NDArray. The one in in_data_fwd_ is From cd19367dcbd7fb022c0ffc13face6c45c7e2aacb Mon Sep 17 00:00:00 2001 From: Hao Jin Date: Fri, 28 Jun 2019 11:23:46 +0800 Subject: [PATCH 017/813] add 'asnumpy' dtype option to check_symbolic_backward (#15186) --- python/mxnet/test_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/mxnet/test_utils.py b/python/mxnet/test_utils.py index bd102412c6e2..d247c0fcde95 100644 --- a/python/mxnet/test_utils.py +++ b/python/mxnet/test_utils.py @@ -1133,7 +1133,7 @@ def check_symbolic_backward(sym, location, out_grads, expected, rtol=1e-5, atol= >>> grad_expected = ograd.copy().asnumpy() >>> check_symbolic_backward(sym_add, [mat1, mat2], [ograd], [grad_expected, grad_expected]) """ - assert dtype in (np.float16, np.float32, np.float64) + assert dtype == 'asnumpy' or dtype in (np.float16, np.float32, np.float64) if ctx is None: ctx = default_context() @@ -1146,7 +1146,7 @@ def check_symbolic_backward(sym, location, out_grads, expected, rtol=1e-5, atol= args_grad_npy = {k:np.random.normal(size=v.shape) for k, v in expected.items()} args_grad_data = {} for k, v in args_grad_npy.items(): - nd = mx.nd.array(v, ctx=ctx, dtype=dtype) + nd = mx.nd.array(v, ctx=ctx, dtype=expected[k].dtype if dtype == "asnumpy" else dtype) if grad_stypes is not None and k in grad_stypes: stype = grad_stypes[k] if stype is not None and stype != 'default': @@ -1170,7 +1170,7 @@ def check_symbolic_backward(sym, location, out_grads, expected, rtol=1e-5, atol= outg = list() for arr in out_grads: if isinstance(arr, np.ndarray): - outg.append(mx.nd.array(arr, ctx=ctx, dtype=dtype)) + outg.append(mx.nd.array(arr, ctx=ctx, dtype=arr.dtype if dtype == "asnumpy" else dtype)) else: outg.append(arr) out_grads = outg @@ -1178,7 +1178,7 @@ def check_symbolic_backward(sym, location, out_grads, expected, rtol=1e-5, atol= outg = dict() for k, v in out_grads.items(): if isinstance(v, np.ndarray): - outg[k] = mx.nd.array(v, ctx=ctx, dtype=dtype) + outg[k] = mx.nd.array(v, ctx=ctx, dtype=v.dtype if dtype == "asnumpy" else dtype) else: outg[k] = v out_grads = outg From 92fce90671b6665fed9e4b92e76a9cefda288fc5 Mon Sep 17 00:00:00 2001 From: Zhaoqi Zhu Date: Fri, 28 Jun 2019 08:39:06 -0700 Subject: [PATCH 018/813] Custom Operator Profiling Enhancement (#15210) * working version * style fix * several fixes * resolve issues in the comments * revert to using thread-safe Get() for singleton class CustomOpProfiler * indentation * Now supports Naive Engine * style fix * tidiness * tests added * style fix * add a new test case which has multiple custom ops * testcases fix * fix * fix style * minor naive engine fix * simplify some branching logic * better desing style * fix * fix * fix * fix * fix * fix * add isprofiling check to onCustomStart * fix * rename dummy_wait * fix conflict * improve test * fix * fix test cases * fix test cases * fix testcases * revert back to reduce overhead * fix style * Re-Trigger build * rename var * Re-Trigger build --- src/engine/naive_engine.cc | 9 +- src/engine/threaded_engine.cc | 10 +- src/engine/threaded_engine.h | 1 + src/operator/custom/custom-inl.h | 23 +++- src/operator/custom/custom.cc | 4 +- src/profiler/custom_op_profiler.h | 125 +++++++++++++++++++ src/profiler/profiler.h | 53 +++++++-- tests/python/unittest/test_profiler.py | 159 +++++++++++++++++++++++++ 8 files changed, 365 insertions(+), 19 deletions(-) create mode 100644 src/profiler/custom_op_profiler.h diff --git a/src/engine/naive_engine.cc b/src/engine/naive_engine.cc index c321e48d562d..9f44b55cfcaf 100644 --- a/src/engine/naive_engine.cc +++ b/src/engine/naive_engine.cc @@ -29,6 +29,7 @@ #include "../profiler/profiler.h" #include "./openmp.h" #include "../common/object_pool.h" +#include "../profiler/custom_op_profiler.h" namespace mxnet { namespace engine { @@ -160,15 +161,19 @@ class NaiveEngine final : public Engine { profiler::Profiler *profiler = profiler::Profiler::Get(); NaiveOpr *opr = nullptr; const bool profiling = opr_name && profiler->IsProfiling(profiler::Profiler::kImperative); + // GenerateDisplayName() will return a pointer to the correct name of the operator + const char* display_name = profiling ? + profiler::CustomOpProfiler::Get()->GenerateDisplayName(opr_name) : + opr_name; if (profiling) { opr = NewOperator(exec_fun, const_vars, mutable_vars, - prop, opr_name)->Cast(); + prop, display_name)->Cast(); opr->profiling = profiling; std::unique_ptr attrs; if (profiler->AggregateEnabled()) { attrs.reset(new profiler::ProfileOperator::Attributes()); } - opr->opr_profile.reset(new profiler::ProfileOperator(opr->opr_name, attrs.release())); + opr->opr_profile.reset(new profiler::ProfileOperator(display_name, attrs.release())); opr->opr_profile->start(exec_ctx.dev_type, exec_ctx.dev_id); } if (exec_ctx.dev_mask() == gpu::kDevMask) { diff --git a/src/engine/threaded_engine.cc b/src/engine/threaded_engine.cc index 38311908bdcd..4375149c729c 100644 --- a/src/engine/threaded_engine.cc +++ b/src/engine/threaded_engine.cc @@ -287,8 +287,11 @@ void ThreadedEngine::DeleteOperator(OprHandle op) { void ThreadedEngine::Push(OprHandle op, Context exec_ctx, int priority, bool profiling) { BulkFlush(); - ThreadedOpr* threaded_opr = ThreadedOpr::CastFromBase(op); + if (profiling) { + threaded_opr->opr_name = + profiler::CustomOpProfiler::Get()->GenerateDisplayName(threaded_opr->opr_name); + } OprBlock* opr_block = OprBlock::New(); opr_block->opr = threaded_opr; @@ -333,9 +336,10 @@ void ThreadedEngine::PushAsync(AsyncFn fn, Context exec_ctx, << device_count_; } #endif - ThreadedOpr *opr = NewOperator(std::move(fn), const_vars, mutable_vars, prop, opr_name, wait); - opr->temporary = true; const bool profiling = profiler_->IsProfiling(profiler::Profiler::kImperative); + ThreadedOpr *opr = NewOperator(std::move(fn), const_vars, mutable_vars, + prop, opr_name, wait); + opr->temporary = true; Push(opr, exec_ctx, priority, profiling); } diff --git a/src/engine/threaded_engine.h b/src/engine/threaded_engine.h index c39f322596ae..3d56b7c79b90 100644 --- a/src/engine/threaded_engine.h +++ b/src/engine/threaded_engine.h @@ -43,6 +43,7 @@ #include "../profiler/profiler.h" #include "./openmp.h" #include "../common/object_pool.h" +#include "../profiler/custom_op_profiler.h" namespace mxnet { namespace engine { diff --git a/src/operator/custom/custom-inl.h b/src/operator/custom/custom-inl.h index 3bf63b75cfdb..1173b14eb2fe 100644 --- a/src/operator/custom/custom-inl.h +++ b/src/operator/custom/custom-inl.h @@ -43,6 +43,7 @@ #include #include #include "../operator_common.h" +#include "../../profiler/custom_op_profiler.h" namespace mxnet { namespace op { @@ -76,9 +77,16 @@ class CustomOperator { bool training, const std::vector& arrs, const std::vector& tags, const std::unordered_set& output_tags, - const std::vector& outputs) { + const std::vector& outputs, + const std::string op_type = "") { if (naive_engine_) { - func(); + if (profiler::Profiler::Get()->IsProfiling(profiler::Profiler::kImperative)) { + profiler::CustomOpProfiler::Get()->OnCustomBegin(op_type); + func(); + profiler::CustomOpProfiler::Get()->OnCustomEnd(); + } else { + func(); + } for (size_t i = 0, out_idx = 0; i < arrs.size(); i++) { if (arrs[i].storage_type() == kDefaultStorage || arrs[i].storage_type() == kUndefinedStorage) @@ -97,7 +105,13 @@ class CustomOperator { bool prev_training = Imperative::Get()->set_is_training(training); try { - func(); + if (profiler::Profiler::Get()->IsProfiling(profiler::Profiler::kImperative)) { + profiler::CustomOpProfiler::Get()->OnCustomBegin(op_type); + func(); + profiler::CustomOpProfiler::Get()->OnCustomEnd(); + } else { + func(); + } } catch (dmlc::Error& e) { exception_ = std::make_shared(std::current_exception()); @@ -143,8 +157,7 @@ class CustomOperator { ctx.async_on_complete(); }, - ctx.run_ctx.ctx, vars, vars2, FnProperty::kNoSkip, 0, - "CustomOperator"); + ctx.run_ctx.ctx, vars, vars2, FnProperty::kNoSkip, 0, "CustomOperatorWait"); }); // increase num_threads if there is not enough threads to execute custom operator if (q_.size() > num_free_threads_) diff --git a/src/operator/custom/custom.cc b/src/operator/custom/custom.cc index 77fe2e6e4b1c..5d2c284be46f 100644 --- a/src/operator/custom/custom.cc +++ b/src/operator/custom/custom.cc @@ -345,7 +345,7 @@ void ForwardEx(const OpStatePtr& state, const OpContext& ctx, static_cast(ctx.is_train), params.info->contexts[kCustomOpForward])); }, - ctx, false, ctx.is_train, cpys, tags, output_tags, outputs); + ctx, false, ctx.is_train, cpys, tags, output_tags, outputs, params.op_type); } void BackwardEx(const OpStatePtr& state, const OpContext& ctx, @@ -415,7 +415,7 @@ void BackwardEx(const OpStatePtr& state, const OpContext& ctx, ptrs.size(), const_cast(ptrs.data()), const_cast(tags.data()), reinterpret_cast(req.data()), static_cast(ctx.is_train), params.info->contexts[kCustomOpBackward])); - }, ctx, false, ctx.is_train, cpys, tags, output_tags, outputs); + }, ctx, false, ctx.is_train, cpys, tags, output_tags, outputs, "_backward_" + params.op_type); } // infer storage backward function for custom op which assigns kDefaultStorage for diff --git a/src/profiler/custom_op_profiler.h b/src/profiler/custom_op_profiler.h new file mode 100644 index 000000000000..9449e5b84a3a --- /dev/null +++ b/src/profiler/custom_op_profiler.h @@ -0,0 +1,125 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +#ifndef MXNET_PROFILER_CUSTOM_OP_PROFILER_H_ +#define MXNET_PROFILER_CUSTOM_OP_PROFILER_H_ + +#include +#include +#include +#include +#include "./profiler.h" + +namespace mxnet { +namespace profiler { + +using Tid = std::thread::id; +using TaskPtr = std::unique_ptr; + + /*! + * \brief Singleton class to assist profiling python callback of custom operators + * and to assist linking sub-operators to custom operators + */ +class CustomOpProfiler { + public: + static CustomOpProfiler* Get() { + static std::mutex mtx; + static std::unique_ptr prof = nullptr; + if (!prof) { + std::unique_lock lk(mtx); + if (!prof) + prof = std::make_unique(); + } + return prof.get(); + } + /*! + * \brief Called before the callback of custom operators to start a profile task for python + * code execution time + * \param op_type The registed name of the custom operator + */ + void OnCustomBegin(const std::string& op_type) { + const Tid tid = std::this_thread::get_id(); + const std::string task_name = MakePythonCodeName(op_type); + std::lock_guard lock(mutex_); + tid_to_op_type_[tid] = op_type; + tasks_[tid] = std::make_unique(task_name.c_str(), &custom_op_domain); + tasks_[tid]->start(); + } + + /*! + * \brief Called after the callback of custom operators to stop the profile task for python + * code execution time + */ + void OnCustomEnd() { + const Tid tid = std::this_thread::get_id(); + std::lock_guard lock(mutex_); + tid_to_op_type_.erase(tid); + // this should never fail + CHECK(tasks_.find(tid) != tasks_.end()) << "thread_id not found. " << + "Please use OnCustomBegin() and OnCustomEnd() in pairs."; + tasks_[tid]->stop(); + tasks_.erase(tid); + } + + /*! + * \brief Generate a display name for sub-operators, which is the name used for OprBlock + * and later by profiler, and store it in a unordered_set so that it can be referenced + * in the future. + * Notice if the operator is not a sub-operator, just return the char pointer back. + * \param op_type The registed name of the operator + * \return Returns a pointer to the display name generated + */ + const char* GenerateDisplayName(const char* op_type) { + if (!op_type) { + return nullptr; + } + Tid tid = std::this_thread::get_id(); + std::lock_guard lock(mutex_); + if (tid_to_op_type_.find(tid) == tid_to_op_type_.end()) { + return op_type; + } + std::string name = MakeSubOperatorName(tid, op_type); + return display_names_.insert(name).first->c_str(); + } + + private: + /* !\brief make the display name for sub-operators */ + inline std::string MakeSubOperatorName(const Tid& tid, const char* op_type) { + return tid_to_op_type_[tid] + "::" + std::string(op_type); + } + /* !\brief make the display name for the pure python call back function i.e. + * forward() or backward() in the custom operator definition + */ + inline std::string MakePythonCodeName(const std::string& op_type) { + return op_type + "::pure_python"; + } + /*! \brief class mutex */ + std::mutex mutex_; + /* !\brief display names for sub-operators in custom ops */ + std::unordered_set display_names_; + /* !\brief profiling tasks for pure python code in custom operators */ + std::unordered_map tasks_; + /* !\brief the maping from thread id to the registered name op the custom operator + * that is runnin on that thread + */ + std::unordered_map tid_to_op_type_; +}; +} // namespace profiler +} // namespace mxnet + +#endif // MXNET_PROFILER_CUSTOM_OP_PROFILER_H_ diff --git a/src/profiler/profiler.h b/src/profiler/profiler.h index a6d9ecf06fee..49ef99da5bba 100644 --- a/src/profiler/profiler.h +++ b/src/profiler/profiler.h @@ -790,6 +790,13 @@ struct ProfileTask : public ProfileDuration { NVTX_ONLY_CODE(nvtx_duration_.reset(new nvtx::NVTXDuration(name))); } + /*! + * \brief Set the domain + */ + void setDomain(ProfileDomain* domain) { + domain_ = domain; + } + /*! * \brief Start the profiling scope */ @@ -1111,6 +1118,8 @@ struct ProfileMarker { VTUNE_ONLY_CODE(std::unique_ptr vtune_instant_marker_); }; +static ProfileDomain custom_op_domain("Custom Operator"); + /*! * \brief Operator profiler object. Logs as both an independent event and a task in * the operator domain @@ -1162,10 +1171,16 @@ struct ProfileOperator : public ProfileEvent { : ProfileEvent(name) , as_task_(name, &domain_) , name_(name) - , attributes_(attributes) { + , attributes_(attributes) + , profiling_(!IsDeprecatedOperator(name)) { + if (IsSubOperatorOfCustom(name)) { + as_task_.setDomain(&custom_op_domain); + SetCategories(custom_op_domain.name()); + } else { + SetCategories(domain_.name()); + } // make as_task_ not to add stat to AggregateStats; otherwise we will add twice as_task_.enableAggregateStats(false); - SetCategories(domain_.name()); } /*! * \brief Start the profiling scope @@ -1175,15 +1190,19 @@ struct ProfileOperator : public ProfileEvent { void start(mxnet::Context::DeviceType dev_type, uint32_t dev_id) { dev_type_ = dev_type; dev_id_ = dev_id; - ProfileEvent::start(); - as_task_.start(); + if (profiling_) { + ProfileEvent::start(); + as_task_.start(); + } } /*! * \brief Stop the profiling scope */ void stop() override { - as_task_.stop(); - ProfileEvent::stop(); + if (profiling_) { + as_task_.stop(); + ProfileEvent::stop(); + } } /*! @@ -1208,7 +1227,11 @@ struct ProfileOperator : public ProfileEvent { if (attributes) { name_.append(attributes->to_string().c_str()); } - categories_.set("operator"); + if (IsSubOperatorOfCustom(name)) { + categories_.set(custom_op_domain.name()); + } else { + categories_.set("operator"); + } items_[kStart].timestamp_ = start_time; items_[kStop].timestamp_ = stop_time; } @@ -1228,6 +1251,20 @@ struct ProfileOperator : public ProfileEvent { start_time_, ProfileStat::NowInMicrosec(), attributes_.get()); } + /*! + * \brief Check if this operator is no longer profiled + * Notice that this operator may still be used for e.g synchronization + */ + inline static bool IsDeprecatedOperator(const char* name) { + return strcmp(name, "CustomOperatorWait") == 0 || + strcmp(name, "Custom") == 0 || strcmp(name, "_backward_Custom") == 0; + } + /*! + * \brief Check if this operator a sub-operator of a custom operator + */ + inline static bool IsSubOperatorOfCustom(const char* name) { + return strstr(name, "::"); + } /*! \brief Also log the operator as a task in the operator domain */ ProfileTask as_task_; /* !\brief Operator name */ @@ -1240,6 +1277,8 @@ struct ProfileOperator : public ProfileEvent { static ProfileDomain domain_; /*! \brief Optional operator attributes */ std::unique_ptr attributes_; + /*! \brief Whether to profile or not */ + const bool profiling_; }; /* diff --git a/tests/python/unittest/test_profiler.py b/tests/python/unittest/test_profiler.py index b76bfbc82d12..ab7d29f104ca 100644 --- a/tests/python/unittest/test_profiler.py +++ b/tests/python/unittest/test_profiler.py @@ -23,6 +23,7 @@ import os import json from collections import OrderedDict +from common import run_in_spawned_process def enable_profiler(profile_filename, run=True, continuous_dump=False, aggregate_stats=False): profiler.set_config(profile_symbolic=True, @@ -291,6 +292,164 @@ def test_aggregate_duplication(): assert target_dict['Time']['operator']['_plus_scalar']['Count'] == 2 profiler.set_state('stop') +def test_custom_operator_profiling(seed = None, file_name = None): + class Sigmoid(mx.operator.CustomOp): + def forward(self, is_train, req, in_data, out_data, aux): + x = in_data[0].asnumpy() + import numpy as np + y = 1.0 / (1.0 + np.exp(-x)) + self.assign(out_data[0], req[0], mx.nd.array(y)) + # Create a dummy matrix using nd.zeros. Test if 'MySigmoid::_zeros' is in dump file + dummy = mx.nd.zeros(shape=(100, 100)) + + def backward(self, req, out_grad, in_data, out_data, in_grad, aux): + y = out_data[0].asnumpy() + dy = out_grad[0].asnumpy() + dx = dy*(1.0 - y)*y + self.assign(in_grad[0], req[0], mx.nd.array(dx)) + + @mx.operator.register('MySigmoid') + class SigmoidProp(mx.operator.CustomOpProp): + def __init__(self): + super(SigmoidProp, self).__init__(True) + + def list_arguments(self): + return ['data'] + + def list_outputs(self): + return ['output'] + + def infer_shape(self, in_shapes): + data_shape = in_shapes[0] + output_shape = data_shape + return (data_shape,), (output_shape,), () + + def create_operator(self, ctx, in_shapes, in_dtypes): + return Sigmoid() + + if file_name is None: + file_name = 'test_custom_operator_profiling.json' + enable_profiler(profile_filename = file_name, run=True, continuous_dump=True,\ + aggregate_stats=True) + x = mx.nd.array([0, 1, 2, 3]) + x.attach_grad() + with mx.autograd.record(): + y = mx.nd.Custom(x, op_type='MySigmoid') + y.backward() + mx.nd.waitall() + profiler.dump(False) + debug_str = profiler.dumps(format = 'json') + target_dict = json.loads(debug_str) + assert 'Time' in target_dict and 'Custom Operator' in target_dict['Time'] \ + and 'MySigmoid::pure_python' in target_dict['Time']['Custom Operator'] \ + and '_backward_MySigmoid::pure_python' in target_dict['Time']['Custom Operator'] \ + and 'MySigmoid::_zeros' in target_dict['Time']['Custom Operator'] + profiler.set_state('stop') + +def test_custom_operator_profiling_multiple_custom_ops_imperative(seed = None, \ + mode = 'imperative', file_name = None): + class MyAdd(mx.operator.CustomOp): + def forward(self, is_train, req, in_data, out_data, aux): + self.assign(out_data[0], req[0], in_data[0] + 1) + + def backward(self, req, out_grad, in_data, out_data, in_grad, aux): + self.assign(in_grad[0], req[0], out_grad[0]) + + @mx.operator.register('MyAdd1') + class MyAdd1Prop(mx.operator.CustomOpProp): + def __init__(self): + super(MyAdd1Prop, self).__init__(need_top_grad=True) + + def list_arguments(self): + return ['data'] + + def list_outputs(self): + return ['output'] + + def infer_shape(self, in_shape): + # inputs, outputs, aux + return [in_shape[0]], [in_shape[0]], [] + + def create_operator(self, ctx, shapes, dtypes): + return MyAdd() + + @mx.operator.register('MyAdd2') + class MyAdd2Prop(mx.operator.CustomOpProp): + def __init__(self): + super(MyAdd2Prop, self).__init__(need_top_grad=True) + + def list_arguments(self): + return ['data'] + + def list_outputs(self): + return ['output'] + + def infer_shape(self, in_shape): + # inputs, outputs, aux + return [in_shape[0]], [in_shape[0]], [] + + def create_operator(self, ctx, shapes, dtypes): + return MyAdd() + + if file_name is None: + file_name = 'test_custom_operator_profiling_multiple_custom_ops_imperative.json' + enable_profiler(profile_filename = file_name, run=True, continuous_dump=True,\ + aggregate_stats=True) + inp = mx.nd.zeros(shape=(100, 100)) + if mode == 'imperative': + x = inp + 1 + y = mx.nd.Custom(inp, op_type='MyAdd1') + z = mx.nd.Custom(inp, op_type='MyAdd2') + elif mode == 'symbolic': + a = mx.symbol.Variable('a') + b = a + 1 + c = mx.symbol.Custom(data=a, op_type='MyAdd1') + d = mx.symbol.Custom(data=a, op_type='MyAdd2') + b.bind(mx.cpu(), {'a': inp}).forward() + c.bind(mx.cpu(), {'a': inp}).forward() + d.bind(mx.cpu(), {'a': inp}).forward() + mx.nd.waitall() + profiler.dump(False) + debug_str = profiler.dumps(format = 'json') + target_dict = json.loads(debug_str) + ''' + We are calling _plus_scalar within MyAdd1 and MyAdd2 and outside both the custom + operators, so in aggregate stats we should have three different kinds of + _plus_scalar under domains "Custom Operator" and "operator" + ''' + assert 'Time' in target_dict and 'Custom Operator' in target_dict['Time'] \ + and 'MyAdd1::pure_python' in target_dict['Time']['Custom Operator'] \ + and 'MyAdd2::pure_python' in target_dict['Time']['Custom Operator'] \ + and 'MyAdd1::_plus_scalar' in target_dict['Time']['Custom Operator'] \ + and 'MyAdd2::_plus_scalar' in target_dict['Time']['Custom Operator'] \ + and '_plus_scalar' not in target_dict['Time']['Custom Operator'] \ + and 'operator' in target_dict['Time'] \ + and '_plus_scalar' in target_dict['Time']['operator'] + profiler.set_state('stop') + +def test_custom_operator_profiling_multiple_custom_ops_symbolic(): + run_in_spawned_process(test_custom_operator_profiling_multiple_custom_ops_imperative, \ + {'MXNET_EXEC_BULK_EXEC_INFERENCE' : 0, \ + 'MXNET_EXEC_BULK_EXEC_TRAIN' : 0}, \ + 'symbolic', \ + 'test_custom_operator_profiling_multiple_custom_ops_symbolic.json') + +def test_custom_operator_profiling_naive_engine(): + # run the three tests above using Naive Engine + run_in_spawned_process(test_custom_operator_profiling, \ + {'MXNET_ENGINE_TYPE' : "NaiveEngine"}, \ + 'test_custom_operator_profiling_naive.json') + run_in_spawned_process(test_custom_operator_profiling_multiple_custom_ops_imperative, \ + {'MXNET_ENGINE_TYPE' : "NaiveEngine"}, \ + 'imperative', \ + 'test_custom_operator_profiling_multiple_custom_ops_imperative_naive.json') + run_in_spawned_process(test_custom_operator_profiling_multiple_custom_ops_imperative, \ + {'MXNET_ENGINE_TYPE' : "NaiveEngine", \ + 'MXNET_EXEC_BULK_EXEC_INFERENCE' : 0, \ + 'MXNET_EXEC_BULK_EXEC_TRAIN' : 0}, \ + 'symbolic', \ + 'test_custom_operator_profiling_multiple_custom_ops_symbolic_naive.json') + if __name__ == '__main__': import nose nose.runmodule() From e8f3e91a713909d633c457ceae8affcf8214ff84 Mon Sep 17 00:00:00 2001 From: Sandeep Krishnamurthy Date: Fri, 28 Jun 2019 11:02:32 -0700 Subject: [PATCH 019/813] [Opperf] Make module/namespace of the operator parameterized (#15226) * Make module/namespace parameterized to choose between mx.nd or mx.np * Fix comments * Add automated way to fetch compile/runtime flags for MXNet * Fix warmup and runs count * Fix Pooling operator benchmarks --- benchmark/opperf/README.md | 5 +- .../opperf/nd_operations/binary_operators.py | 8 +- .../opperf/nd_operations/gemm_operators.py | 13 +- .../nd_operations/nn_activation_operators.py | 15 +- .../nd_operations/nn_basic_operators.py | 10 +- .../opperf/nd_operations/nn_conv_operators.py | 20 +- .../random_sampling_operators.py | 6 +- .../nd_operations/reduction_operators.py | 6 +- .../opperf/nd_operations/unary_operators.py | 6 +- benchmark/opperf/opperf.py | 17 +- .../mxnet_operator_benchmark_results_gpu.md | 322 ++++++++++++++++++ benchmark/opperf/rules/default_params.py | 10 +- benchmark/opperf/utils/common_utils.py | 22 +- benchmark/opperf/utils/op_registry_utils.py | 26 +- 14 files changed, 421 insertions(+), 65 deletions(-) create mode 100644 benchmark/opperf/results/mxnet_operator_benchmark_results_gpu.md diff --git a/benchmark/opperf/README.md b/benchmark/opperf/README.md index 99c75be2bf7b..132eb84bf650 100644 --- a/benchmark/opperf/README.md +++ b/benchmark/opperf/README.md @@ -24,10 +24,11 @@ With this utility, for each MXNet operator you can get the following details: **Timing** 1. Forward execution time 2. Backward execution time -3. Time spent for memory management **Memory** -1. Total memory allocated +1. Average and Max memory allocated + +NOTE: This is the `pool memory`. It does not reflect the exact memory requested by the operator. # Motivation diff --git a/benchmark/opperf/nd_operations/binary_operators.py b/benchmark/opperf/nd_operations/binary_operators.py index 7f93621eb2ec..cca8f9d1f2ad 100644 --- a/benchmark/opperf/nd_operations/binary_operators.py +++ b/benchmark/opperf/nd_operations/binary_operators.py @@ -38,7 +38,7 @@ get_all_elemen_wise_binary_operators -def run_mx_binary_broadcast_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs=50): +def run_mx_binary_broadcast_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): """Runs benchmarks with the given context and precision (dtype)for all the binary broadcast operators in MXNet. @@ -48,9 +48,9 @@ def run_mx_binary_broadcast_operators_benchmarks(ctx=mx.cpu(), dtype='float32', Context to run benchmarks dtype: str, default 'float32' Precision to use for benchmarks - warmup: int, default 10 + warmup: int, default 25 Number of times to run for warmup - runs: int, default 50 + runs: int, default 100 Number of runs to capture benchmark results Returns @@ -65,7 +65,7 @@ def run_mx_binary_broadcast_operators_benchmarks(ctx=mx.cpu(), dtype='float32', return mx_binary_op_results -def run_mx_binary_element_wise_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs=50): +def run_mx_binary_element_wise_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): """Runs benchmarks with the given context and precision (dtype)for all the binary element_wise operators in MXNet. diff --git a/benchmark/opperf/nd_operations/gemm_operators.py b/benchmark/opperf/nd_operations/gemm_operators.py index 69a0f4c23121..e1eaabc78954 100644 --- a/benchmark/opperf/nd_operations/gemm_operators.py +++ b/benchmark/opperf/nd_operations/gemm_operators.py @@ -16,10 +16,9 @@ # under the License. import mxnet as mx -from mxnet import nd from benchmark.opperf.utils.benchmark_utils import run_performance_test from benchmark.opperf.utils.common_utils import merge_map_list - +from benchmark.opperf.rules.default_params import MX_OP_MODULE """Performance benchmark tests for MXNet NDArray GEMM Operators. 1. dot @@ -35,7 +34,7 @@ """ -def run_gemm_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs=50): +def run_gemm_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): """Runs benchmarks with the given context and precision (dtype)for all the GEMM operators (dot, batch_dot) in MXNet. @@ -45,9 +44,9 @@ def run_gemm_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs Context to run benchmarks dtype: str, default 'float32' Precision to use for benchmarks - warmup: int, default 10 + warmup: int, default 25 Number of times to run for warmup - runs: int, default 50 + runs: int, default 100 Number of runs to capture benchmark results Returns @@ -57,7 +56,7 @@ def run_gemm_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs """ # Benchmark tests for dot and batch_dot operators dot_benchmark_res = run_performance_test( - [nd.dot], run_backward=True, + [getattr(MX_OP_MODULE, "dot")], run_backward=True, dtype=dtype, ctx=ctx, inputs=[{"lhs": (1024, 1024), "rhs": (1024, 1024)}, @@ -71,7 +70,7 @@ def run_gemm_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs warmup=warmup, runs=runs) batch_dot_benchmark_res = run_performance_test( - [nd.batch_dot], run_backward=True, + [getattr(MX_OP_MODULE, "batch_dot")], run_backward=True, dtype=dtype, ctx=ctx, inputs=[{"lhs": (32, 1024, 1024), "rhs": (32, 1024, 1024)}, diff --git a/benchmark/opperf/nd_operations/nn_activation_operators.py b/benchmark/opperf/nd_operations/nn_activation_operators.py index 16ea2c6f64f4..ae5ec90f367e 100644 --- a/benchmark/opperf/nd_operations/nn_activation_operators.py +++ b/benchmark/opperf/nd_operations/nn_activation_operators.py @@ -16,9 +16,9 @@ # under the License. import mxnet as mx -from mxnet import nd from benchmark.opperf.utils.benchmark_utils import run_performance_test from benchmark.opperf.utils.common_utils import merge_map_list +from benchmark.opperf.rules.default_params import MX_OP_MODULE """Performance benchmark tests for MXNet NDArray Activation Operators. @@ -35,7 +35,7 @@ """ -def run_activation_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs=50): +def run_activation_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): """Runs benchmarks with the given context and precision (dtype)for all the activation operators (relu, sigmoid, softmax) in MXNet. @@ -45,9 +45,9 @@ def run_activation_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10 Context to run benchmarks dtype: str, default 'float32' Precision to use for benchmarks - warmup: int, default 10 + warmup: int, default 25 Number of times to run for warmup - runs: int, default 50 + runs: int, default 100 Number of runs to capture benchmark results Returns @@ -56,7 +56,7 @@ def run_activation_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10 """ # Relu and its variation - relu_benchmark_res = run_performance_test([nd.LeakyReLU], + relu_benchmark_res = run_performance_test([getattr(MX_OP_MODULE, "LeakyReLU")], run_backward=True, dtype=dtype, ctx=ctx, @@ -78,7 +78,7 @@ def run_activation_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10 # Sigmoid => Covered as part of Unary ops # Hard_Sigmoid - hard_sigmoid_benchmark_res = run_performance_test([nd.hard_sigmoid], + hard_sigmoid_benchmark_res = run_performance_test([getattr(MX_OP_MODULE, "hard_sigmoid")], run_backward=True, dtype=dtype, ctx=ctx, @@ -90,7 +90,8 @@ def run_activation_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10 runs=runs) # Softmax, LogSoftmax - softmax_benchmark_res = run_performance_test([nd.softmax, nd.log_softmax], + softmax_benchmark_res = run_performance_test([getattr(MX_OP_MODULE, "softmax"), + getattr(MX_OP_MODULE, "log_softmax")], run_backward=True, dtype=dtype, ctx=ctx, diff --git a/benchmark/opperf/nd_operations/nn_basic_operators.py b/benchmark/opperf/nd_operations/nn_basic_operators.py index d91b285f41aa..597f7719d2cb 100644 --- a/benchmark/opperf/nd_operations/nn_basic_operators.py +++ b/benchmark/opperf/nd_operations/nn_basic_operators.py @@ -16,9 +16,9 @@ # under the License. import mxnet as mx -from mxnet import nd from benchmark.opperf.utils.benchmark_utils import run_performance_test from benchmark.opperf.utils.common_utils import merge_map_list +from benchmark.opperf.rules.default_params import MX_OP_MODULE """Performance benchmark tests for MXNet NDArray basic NN Operators. @@ -29,9 +29,9 @@ """ -def run_nn_basic_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs=50): +def run_nn_basic_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): # FullyConnnected operator benchmarks - fc_benchmark_res = run_performance_test([nd.FullyConnected], + fc_benchmark_res = run_performance_test([getattr(MX_OP_MODULE, "FullyConnected")], run_backward=True, dtype=dtype, ctx=ctx, @@ -49,7 +49,7 @@ def run_nn_basic_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs=runs) # Dropout benchmarks - dropout_benchmark_res = run_performance_test([nd.Dropout], + dropout_benchmark_res = run_performance_test([getattr(MX_OP_MODULE, "Dropout")], run_backward=True, dtype=dtype, ctx=ctx, @@ -62,7 +62,7 @@ def run_nn_basic_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, warmup=warmup, runs=runs) # BatchNorm benchmarks - batchnorm_benchmark_res = run_performance_test([nd.BatchNorm], + batchnorm_benchmark_res = run_performance_test([getattr(MX_OP_MODULE, "BatchNorm")], run_backward=True, dtype=dtype, ctx=ctx, diff --git a/benchmark/opperf/nd_operations/nn_conv_operators.py b/benchmark/opperf/nd_operations/nn_conv_operators.py index e4749ec90de4..e42205f8f56c 100644 --- a/benchmark/opperf/nd_operations/nn_conv_operators.py +++ b/benchmark/opperf/nd_operations/nn_conv_operators.py @@ -16,9 +16,9 @@ # under the License. import mxnet as mx -from mxnet import nd from benchmark.opperf.utils.benchmark_utils import run_performance_test from benchmark.opperf.utils.common_utils import merge_map_list +from benchmark.opperf.rules.default_params import MX_OP_MODULE """Performance benchmark tests for MXNet NDArray Convolution and Pooling Operators. @@ -51,7 +51,7 @@ """ -def run_pooling_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs=50): +def run_pooling_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): pool_types = ['avg', 'max', 'sum'] global_pool_types = [0, 1] @@ -61,7 +61,7 @@ def run_pooling_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, r for pool_type in pool_types: for global_pool in global_pool_types: for pool1d_data in [(32, 3, 256), (32, 3, 64)]: - pool1d_benchmark_res += run_performance_test([nd.Pooling], + pool1d_benchmark_res += run_performance_test([getattr(MX_OP_MODULE, "Pooling")], run_backward=True, dtype=dtype, ctx=ctx, @@ -70,13 +70,12 @@ def run_pooling_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, r "pool_type": pool_type, "global_pool": global_pool, "stride": 1, - "pad": 1, - "layout": 'NCW'} + "pad": 1} ], warmup=warmup, runs=runs) for pool2d_data in [(32, 3, 256, 256), (32, 3, 64, 64)]: - pool2d_benchmark_res += run_performance_test([nd.Pooling], + pool2d_benchmark_res += run_performance_test([getattr(MX_OP_MODULE, "Pooling")], run_backward=True, dtype=dtype, ctx=ctx, @@ -85,8 +84,7 @@ def run_pooling_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, r "pool_type": pool_type, "global_pool": global_pool, "stride": (1, 1), - "pad": (0, 0), - "layout": 'NCHW'} + "pad": (0, 0)} ], warmup=warmup, runs=runs) @@ -95,11 +93,11 @@ def run_pooling_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, r return mx_pooling_op_results -def run_convolution_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs=50): +def run_convolution_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): # Conv1D Benchmarks conv1d_benchmark_res = [] for conv_data in [(32, 3, 256), (32, 3, 64)]: - conv1d_benchmark_res += run_performance_test([nd.Convolution], + conv1d_benchmark_res += run_performance_test([getattr(MX_OP_MODULE, "Convolution")], run_backward=True, dtype=dtype, ctx=ctx, @@ -118,7 +116,7 @@ def run_convolution_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=1 # Conv2D Benchmarks conv2d_benchmark_res = [] for conv_data in [(32, 3, 256, 256), (32, 3, 64, 64)]: - conv2d_benchmark_res += run_performance_test([nd.Convolution], + conv2d_benchmark_res += run_performance_test([getattr(MX_OP_MODULE, "Convolution")], run_backward=True, dtype=dtype, ctx=ctx, diff --git a/benchmark/opperf/nd_operations/random_sampling_operators.py b/benchmark/opperf/nd_operations/random_sampling_operators.py index bad8c8e4c040..d0cf2c2fea4a 100644 --- a/benchmark/opperf/nd_operations/random_sampling_operators.py +++ b/benchmark/opperf/nd_operations/random_sampling_operators.py @@ -34,7 +34,7 @@ from benchmark.opperf.utils.op_registry_utils import get_all_random_sampling_operators -def run_mx_random_sampling_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs=50): +def run_mx_random_sampling_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): """Runs benchmarks with the given context and precision (dtype)for all the random sampling operators in MXNet. @@ -44,9 +44,9 @@ def run_mx_random_sampling_operators_benchmarks(ctx=mx.cpu(), dtype='float32', w Context to run benchmarks dtype: str, default 'float32' Precision to use for benchmarks - warmup: int, default 10 + warmup: int, default 25 Number of times to run for warmup - runs: int, default 50 + runs: int, default 100 Number of runs to capture benchmark results Returns diff --git a/benchmark/opperf/nd_operations/reduction_operators.py b/benchmark/opperf/nd_operations/reduction_operators.py index 5bfe06621136..e34debc95810 100644 --- a/benchmark/opperf/nd_operations/reduction_operators.py +++ b/benchmark/opperf/nd_operations/reduction_operators.py @@ -31,7 +31,7 @@ from benchmark.opperf.utils.benchmark_utils import run_op_benchmarks -def run_mx_reduction_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs=50): +def run_mx_reduction_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): """Runs benchmarks with the given context and precision (dtype)for all the reduction operators in MXNet. @@ -41,9 +41,9 @@ def run_mx_reduction_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup= Context to run benchmarks dtype: str, default 'float32' Precision to use for benchmarks - warmup: int, default 10 + warmup: int, default 25 Number of times to run for warmup - runs: int, default 50 + runs: int, default 100 Number of runs to capture benchmark results Returns diff --git a/benchmark/opperf/nd_operations/unary_operators.py b/benchmark/opperf/nd_operations/unary_operators.py index a562eebf2a92..790a4c12b64b 100644 --- a/benchmark/opperf/nd_operations/unary_operators.py +++ b/benchmark/opperf/nd_operations/unary_operators.py @@ -35,7 +35,7 @@ from benchmark.opperf.utils.benchmark_utils import run_op_benchmarks -def run_mx_unary_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs=50): +def run_mx_unary_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): """Runs benchmarks with the given context and precision (dtype)for all the unary operators in MXNet. @@ -45,9 +45,9 @@ def run_mx_unary_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, Context to run benchmarks dtype: str, default 'float32' Precision to use for benchmarks - warmup: int, default 10 + warmup: int, default 25 Number of times to run for warmup - runs: int, default 50 + runs: int, default 100 Number of runs to capture benchmark results Returns diff --git a/benchmark/opperf/opperf.py b/benchmark/opperf/opperf.py index 34c6cf96b723..68b404185bf8 100755 --- a/benchmark/opperf/opperf.py +++ b/benchmark/opperf/opperf.py @@ -40,7 +40,8 @@ from benchmark.opperf.nd_operations.nn_basic_operators import run_nn_basic_operators_benchmarks from benchmark.opperf.utils.common_utils import merge_map_list, save_to_file -from benchmark.opperf.utils.op_registry_utils import get_operators_with_no_benchmark +from benchmark.opperf.utils.op_registry_utils import get_operators_with_no_benchmark,\ + get_current_runtime_features def run_all_mxnet_operator_benchmarks(ctx=mx.cpu(), dtype='float32'): @@ -102,17 +103,18 @@ def _parse_mxnet_context(ctx): device_id = int(ctx[4:-1]) return mx.gpu(device_id) + def main(): # 1. GET USER INPUTS - parser = argparse.ArgumentParser( - description='Run all the MXNet operators (NDArray) benchmarks') + parser = argparse.ArgumentParser(description='Run all the MXNet operator benchmarks') parser.add_argument('--ctx', type=str, default='cpu', help='Global context to run all benchmarks. By default, cpu on a ' 'CPU machine, gpu(0) on a GPU machine. ' 'Valid Inputs - cpu, gpu, gpu(0), gpu(1)...') parser.add_argument('--dtype', type=str, default='float32', help='DType (Precision) to run benchmarks. By default, ' - 'float32. Valid Inputs - float32, float64.') + 'float32. Valid Inputs - float32, float64, int32, ' + 'int64') parser.add_argument('-f', '--output-format', type=str, default='json', choices=['json', 'md'], help='Benchmark result output format. By default, json. ' @@ -129,17 +131,20 @@ def main(): # 2. RUN BENCHMARKS ctx = _parse_mxnet_context(args.ctx) dtype = args.dtype - final_benchmark_results = run_all_mxnet_operator_benchmarks(ctx=ctx, dtype=args.dtype) + final_benchmark_results = run_all_mxnet_operator_benchmarks(ctx=ctx, dtype=dtype) # 3. PREPARE OUTPUTS - save_to_file(final_benchmark_results, args.output_file, args.output_format) + run_time_features = get_current_runtime_features() + save_to_file(final_benchmark_results, args.output_file, args.output_format, run_time_features) # 4. Generate list of MXNet operators not covered in benchmarks ops_not_covered = get_operators_with_no_benchmark(final_benchmark_results.keys()) for idx, op in enumerate(ops_not_covered): print(f"{idx}. {op}") + return 0 + if __name__ == '__main__': sys.exit(main()) diff --git a/benchmark/opperf/results/mxnet_operator_benchmark_results_gpu.md b/benchmark/opperf/results/mxnet_operator_benchmark_results_gpu.md new file mode 100644 index 000000000000..284eb7cc78e3 --- /dev/null +++ b/benchmark/opperf/results/mxnet_operator_benchmark_results_gpu.md @@ -0,0 +1,322 @@ + + + + + + + + + + + + + + + + + +# MXNet GPU Operator Benchmarks + +## Settings + +1. MXNet - v1.5.0 with CUDA 10.1 +2. Instance - P3.2x + +| Operator | Avg Forward Time (ms) | Avg. Backward Time (ms) | Max Mem Usage (Storage) (Bytes) | Inputs | +| :---: | :---: | :---: | :---:| :--- | +| rcbrt | 0.0384 | 0.0393 | 2097.1521 | {'data': (1024, 1024)} | +| rcbrt | 0.0268 | 0.0252 | 20.0 | {'data': (10000, 1)} | +| rcbrt | 0.0378 | 0.039 | 2000.0 | {'data': (10000, 100)} | +| min | 0.0679 | 0.0955 | 0.002 | {'axis': (), 'data': (1024, 1024)} | +| min | 0.0337 | 0.022 | 0.002 | {'axis': 0, 'data': (10000, 1)} | +| min | 0.0669 | 0.0921 | 0.002 | {'axis': (0, 1), 'data': (10000, 100)} | +| broadcast_maximum | 0.0284 | 0.0308 | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| Flatten | 0.0416 | --- | 2097.1521 | {'data': (1024, 1024)} | +| Flatten | 0.03 | --- | 20.0 | {'data': (10000, 1)} | +| Flatten | 0.0437 | --- | 2000.0 | {'data': (10000, 100)} | +| stop_gradient | --- | --- | 2097.1521 | {'data': (1024, 1024)} | +| stop_gradient | --- | --- | 20.0 | {'data': (10000, 1)} | +| stop_gradient | --- | --- | 2000.0 | {'data': (10000, 100)} | +| broadcast_minimum | 0.0284 | 0.0294 | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| random_generalized_negative_binomial | 63.299 | --- | 2097.1521 | {'shape': (1024, 1024)} | +| random_generalized_negative_binomial | 0.6491 | --- | 20.0 | {'shape': (10000, 1)} | +| random_generalized_negative_binomial | 60.3705 | --- | 2000.0 | {'shape': (10000, 100)} | +| broadcast_sub | 0.0198 | 0.0208 | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| round | 0.0363 | --- | 2097.1521 | {'data': (1024, 1024)} | +| round | 0.0248 | --- | 20.0 | {'data': (10000, 1)} | +| round | 0.0358 | --- | 2000.0 | {'data': (10000, 100)} | +| cosh | 0.0384 | 0.0389 | 2097.1521 | {'data': (1024, 1024)} | +| cosh | 0.0271 | 0.0255 | 20.0 | {'data': (10000, 1)} | +| cosh | 0.0378 | 0.0387 | 2000.0 | {'data': (10000, 100)} | +| max | 0.068 | 0.095 | 0.002 | {'axis': (), 'data': (1024, 1024)} | +| max | 0.0336 | 0.0219 | 0.002 | {'axis': 0, 'data': (10000, 1)} | +| max | 0.0665 | 0.0918 | 0.002 | {'axis': (0, 1), 'data': (10000, 100)} | +| tanh | 0.0391 | 0.038 | 2097.1521 | {'data': (1024, 1024)} | +| tanh | 0.0271 | 0.025 | 20.0 | {'data': (10000, 1)} | +| tanh | 0.0373 | 0.0376 | 2000.0 | {'data': (10000, 100)} | +| relu | 0.0382 | 0.0383 | 2097.1521 | {'data': (1024, 1024)} | +| relu | 0.0272 | 0.0251 | 20.0 | {'data': (10000, 1)} | +| relu | 0.0383 | 0.038 | 2000.0 | {'data': (10000, 100)} | +| negative | 0.0367 | --- | 2097.1521 | {'data': (1024, 1024)} | +| negative | 0.025 | --- | 20.0 | {'data': (10000, 1)} | +| negative | 0.0361 | --- | 2000.0 | {'data': (10000, 100)} | +| random_randint | 9.0025 | --- | 2097.1521 | {'shape': (1024, 1024), 'high': 5, 'low': 0} | +| random_randint | 0.0976 | --- | 20.0 | {'shape': (10000, 1), 'high': 5, 'low': 0} | +| random_randint | 8.589 | --- | 2000.0 | {'shape': (10000, 100), 'high': 5, 'low': 0} | +| trunc | 0.0367 | --- | 2097.1521 | {'data': (1024, 1024)} | +| trunc | 0.0251 | --- | 20.0 | {'data': (10000, 1)} | +| trunc | 0.0359 | --- | 2000.0 | {'data': (10000, 100)} | +| log_softmax | 0.0433 | 0.0387 | 2097.1521 | {'axis': -1, 'data': (1024, 1024), 'temperature': 0.5} | +| log_softmax | 0.055 | 0.0386 | 20.0 | {'axis': -1, 'data': (10000, 1), 'temperature': 0.5} | +| log_softmax | 0.08 | 0.0553 | 2000.0 | {'axis': -1, 'data': (10000, 100), 'temperature': 0.5} | +| broadcast_greater_equal | 0.0191 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| arctan | 0.0386 | 0.04 | 2097.1521 | {'data': (1024, 1024)} | +| arctan | 0.0266 | 0.0248 | 20.0 | {'data': (10000, 1)} | +| arctan | 0.0378 | 0.0394 | 2000.0 | {'data': (10000, 100)} | +| sqrt | 0.0385 | 0.041 | 2097.1521 | {'data': (1024, 1024)} | +| sqrt | 0.0269 | 0.025 | 20.0 | {'data': (10000, 1)} | +| sqrt | 0.0378 | 0.0397 | 2000.0 | {'data': (10000, 100)} | +| dot | 0.215 | 0.4045 | 2097.1521 | {'lhs': (1024, 1024), 'rhs': (1024, 1024)} | +| dot | 0.031 | 0.0633 | 2000.0 | {'lhs': (1000, 10), 'transpose_b': True, 'rhs': (1000, 10)} | +| dot | 0.042 | 0.0388 | 0.2 | {'lhs': (1000, 1), 'transpose_b': True, 'transpose_a': True, 'rhs': (100, 1000)} | +| floor | 0.0366 | --- | 2097.1521 | {'data': (1024, 1024)} | +| floor | 0.0249 | --- | 20.0 | {'data': (10000, 1)} | +| floor | 0.0366 | --- | 2000.0 | {'data': (10000, 100)} | +| broadcast_logical_xor | 0.0264 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| shuffle | 0.1016 | --- | 2097.1521 | {'data': (1024, 1024)} | +| shuffle | 0.2845 | --- | 60.0 | {'data': (10000, 1)} | +| shuffle | 0.2798 | --- | 2000.0 | {'data': (10000, 100)} | +| nansum | 0.0741 | 0.0948 | 0.002 | {'axis': (), 'data': (1024, 1024)} | +| nansum | 0.0368 | 0.0221 | 0.002 | {'axis': 0, 'data': (10000, 1)} | +| nansum | 0.0731 | 0.0918 | 0.002 | {'axis': (0, 1), 'data': (10000, 100)} | +| nanprod | 0.0717 | 0.0955 | 0.002 | {'axis': (), 'data': (1024, 1024)} | +| nanprod | 0.0344 | 0.0223 | 0.002 | {'axis': 0, 'data': (10000, 1)} | +| nanprod | 0.0711 | 0.0921 | 0.002 | {'axis': (0, 1), 'data': (10000, 100)} | +| broadcast_div | 0.02 | 0.0227 | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| LeakyReLU | 0.0326 | 0.0333 | 2097.1521 | {'slope': 0.1, 'data': (1024, 1024), 'act_type': 'leaky'} | +| LeakyReLU | 0.0347 | 0.0199 | 20.0 | {'slope': 0.1, 'data': (10000, 1), 'act_type': 'leaky'} | +| LeakyReLU | 0.0316 | 0.0327 | 2000.0 | {'slope': 0.1, 'data': (10000, 100), 'act_type': 'leaky'} | +| LeakyReLU | 0.0328 | 0.0334 | 2097.1521 | {'slope': 0.1, 'data': (1024, 1024), 'act_type': 'elu'} | +| LeakyReLU | 0.0208 | 0.0194 | 20.0 | {'slope': 0.1, 'data': (10000, 1), 'act_type': 'elu'} | +| LeakyReLU | 0.0315 | 0.0326 | 2000.0 | {'slope': 0.1, 'data': (10000, 100), 'act_type': 'elu'} | +| LeakyReLU | 0.0323 | 0.033 | 2097.1521 | {'data': (1024, 1024), 'act_type': 'selu'} | +| LeakyReLU | 0.0209 | 0.0194 | 20.0 | {'data': (10000, 1), 'act_type': 'selu'} | +| LeakyReLU | 0.0315 | 0.0325 | 2000.0 | {'data': (10000, 100), 'act_type': 'selu'} | +| LeakyReLU | 0.0349 | 0.0917 | 2097.1521 | {'gamma': (1, 1024), 'data': (1024, 1024), 'act_type': 'prelu'} | +| LeakyReLU | 0.0244 | 0.0465 | 20.0 | {'gamma': (1, 1), 'data': (10000, 1), 'act_type': 'prelu'} | +| LeakyReLU | 0.034 | 0.1003 | 2000.0 | {'gamma': (1, 100), 'data': (10000, 100), 'act_type': 'prelu'} | +| FullyConnected | 0.3141 | 0.3486 | 4.096 | {'weight': (64, 196608), 'num_hidden': 64, 'data': (32, 3, 256, 256), 'bias': (64,), 'flatten': True} | +| FullyConnected | 0.1381 | 0.5142 | 3145.728 | {'weight': (64, 256), 'num_hidden': 64, 'data': (32, 3, 256, 256), 'bias': (64,), 'flatten': False} | +| square | 0.038 | 0.038 | 2097.1521 | {'data': (1024, 1024)} | +| square | 0.0281 | 0.0262 | 20.0 | {'data': (10000, 1)} | +| square | 0.0424 | 0.0422 | 2000.0 | {'data': (10000, 100)} | +| elemwise_mul | 0.0186 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| broadcast_not_equal | 0.0262 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| broadcast_logical_or | 0.019 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| logical_not | 0.0366 | --- | 2097.1521 | {'data': (1024, 1024)} | +| logical_not | 0.0247 | --- | 20.0 | {'data': (10000, 1)} | +| logical_not | 0.0357 | --- | 2000.0 | {'data': (10000, 100)} | +| broadcast_add | 0.0284 | 0.0289 | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| broadcast_hypot | 0.0202 | 0.0233 | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| sign | 0.038 | 0.0333 | 2097.1521 | {'data': (1024, 1024)} | +| sign | 0.0269 | 0.0252 | 20.0 | {'data': (10000, 1)} | +| sign | 0.0379 | 0.0326 | 2000.0 | {'data': (10000, 100)} | +| arccos | 0.0385 | 0.0424 | 2097.1521 | {'data': (1024, 1024)} | +| arccos | 0.0268 | 0.0253 | 20.0 | {'data': (10000, 1)} | +| arccos | 0.0374 | 0.0413 | 2000.0 | {'data': (10000, 100)} | +| erf | 0.0383 | 0.0384 | 2097.1521 | {'data': (1024, 1024)} | +| erf | 0.0266 | 0.0247 | 20.0 | {'data': (10000, 1)} | +| erf | 0.0374 | 0.0384 | 2000.0 | {'data': (10000, 100)} | +| degrees | 0.0375 | 0.0326 | 2097.1521 | {'data': (1024, 1024)} | +| degrees | 0.0265 | 0.0247 | 20.0 | {'data': (10000, 1)} | +| degrees | 0.0376 | 0.0317 | 2000.0 | {'data': (10000, 100)} | +| fix | 0.0366 | --- | 2097.1521 | {'data': (1024, 1024)} | +| fix | 0.0251 | --- | 20.0 | {'data': (10000, 1)} | +| fix | 0.0362 | --- | 2000.0 | {'data': (10000, 100)} | +| cos | 0.0386 | 0.0389 | 2097.1521 | {'data': (1024, 1024)} | +| cos | 0.0276 | 0.0255 | 20.0 | {'data': (10000, 1)} | +| cos | 0.0385 | 0.0388 | 2000.0 | {'data': (10000, 100)} | +| broadcast_plus | --- | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| sample_exponential | 0.1359 | --- | 4194.3042 | {'lam': [1.0, 8.5], 'shape': (1024, 1024)} | +| sample_exponential | 0.0734 | --- | 40.0 | {'lam': [1.0, 8.5], 'shape': (10000, 1)} | +| sample_exponential | 0.1323 | --- | 4000.0 | {'lam': [1.0, 8.5], 'shape': (10000, 100)} | +| make_loss | 0.0415 | --- | 2097.1521 | {'data': (1024, 1024)} | +| make_loss | 0.0301 | --- | 20.0 | {'data': (10000, 1)} | +| make_loss | 0.0432 | --- | 2000.0 | {'data': (10000, 100)} | +| argmax_channel | 0.288 | --- | 2.048 | {'data': (1024, 1024)} | +| argmax_channel | 0.0264 | --- | 20.0 | {'data': (10000, 1)} | +| argmax_channel | 0.0439 | --- | 20.0 | {'data': (10000, 100)} | +| BlockGrad | 0.042 | --- | 2097.1521 | {'data': (1024, 1024)} | +| BlockGrad | 0.0295 | --- | 20.0 | {'data': (10000, 1)} | +| BlockGrad | 0.0438 | --- | 2000.0 | {'data': (10000, 100)} | +| BatchNorm | 1.1379 | 2.3158 | 12582.9238 | {'gamma': (3,), 'moving_var': (3,), 'data': (32, 3, 256, 256), 'beta': (3,), 'moving_mean': (3,)} | +| BatchNorm | 1.7465 | 3.5398 | 19200.0117 | {'gamma': (3,), 'moving_var': (3,), 'data': (32, 3, 10000, 10), 'beta': (3,), 'moving_mean': (3,)} | +| sample_negative_binomial | 1.5065 | --- | 4194.3042 | {'shape': (1024, 1024), 'p': [0.4, 0.77], 'k': [20, 49]} | +| sample_negative_binomial | 0.8542 | --- | 40.0 | {'shape': (10000, 1), 'p': [0.4, 0.77], 'k': [20, 49]} | +| sample_negative_binomial | 1.476 | --- | 4000.0 | {'shape': (10000, 100), 'p': [0.4, 0.77], 'k': [20, 49]} | +| batch_dot | 4.7282 | 9.3181 | 67108.8672 | {'lhs': (32, 1024, 1024), 'rhs': (32, 1024, 1024)} | +| batch_dot | 0.3021 | 1.229 | 64000.0 | {'lhs': (32, 1000, 10), 'transpose_b': True, 'rhs': (32, 1000, 10)} | +| batch_dot | 0.0523 | 0.0636 | 6.4 | {'lhs': (32, 1000, 1), 'transpose_b': True, 'transpose_a': True, 'rhs': (32, 100, 1000)} | +| rsqrt | 0.0386 | 0.0422 | 2097.1521 | {'data': (1024, 1024)} | +| rsqrt | 0.0268 | 0.0252 | 20.0 | {'data': (10000, 1)} | +| rsqrt | 0.0377 | 0.0408 | 2000.0 | {'data': (10000, 100)} | +| broadcast_logical_and | 0.0191 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| mean | 0.0818 | 0.043 | 0.002 | {'axis': (), 'data': (1024, 1024)} | +| mean | 0.0384 | 0.026 | 0.002 | {'axis': 0, 'data': (10000, 1)} | +| mean | 0.0801 | 0.0421 | 0.002 | {'axis': (0, 1), 'data': (10000, 100)} | +| ceil | 0.0363 | --- | 2097.1521 | {'data': (1024, 1024)} | +| ceil | 0.0249 | --- | 20.0 | {'data': (10000, 1)} | +| ceil | 0.0357 | --- | 2000.0 | {'data': (10000, 100)} | +| min_axis | 0.0569 | --- | 0.002 | {'axis': (), 'data': (1024, 1024)} | +| min_axis | 0.0319 | --- | 0.002 | {'axis': 0, 'data': (10000, 1)} | +| min_axis | 0.0563 | --- | 0.002 | {'axis': (0, 1), 'data': (10000, 100)} | +| sigmoid | 0.0395 | 0.0392 | 2097.1521 | {'data': (1024, 1024)} | +| sigmoid | 0.0412 | 0.0256 | 20.0 | {'data': (10000, 1)} | +| sigmoid | 0.0378 | 0.0384 | 2000.0 | {'data': (10000, 100)} | +| broadcast_power | 0.0209 | 0.023 | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| gamma | 0.0415 | 0.0761 | 2097.1521 | {'data': (1024, 1024)} | +| gamma | 0.0279 | 0.0279 | 20.0 | {'data': (10000, 1)} | +| gamma | 0.041 | 0.0736 | 2000.0 | {'data': (10000, 100)} | +| radians | 0.0378 | 0.0327 | 2097.1521 | {'data': (1024, 1024)} | +| radians | 0.0268 | 0.0248 | 20.0 | {'data': (10000, 1)} | +| radians | 0.0373 | 0.032 | 2000.0 | {'data': (10000, 100)} | +| prod | 0.0671 | 0.0949 | 0.002 | {'axis': (), 'data': (1024, 1024)} | +| prod | 0.0334 | 0.0218 | 0.002 | {'axis': 0, 'data': (10000, 1)} | +| prod | 0.0663 | 0.091 | 0.002 | {'axis': (0, 1), 'data': (10000, 100)} | +| abs | 0.0382 | 0.039 | 2097.1521 | {'data': (1024, 1024)} | +| abs | 0.0271 | 0.0254 | 20.0 | {'data': (10000, 1)} | +| abs | 0.0369 | 0.0381 | 2000.0 | {'data': (10000, 100)} | +| reciprocal | 0.0378 | 0.0399 | 2097.1521 | {'data': (1024, 1024)} | +| reciprocal | 0.0267 | 0.0248 | 20.0 | {'data': (10000, 1)} | +| reciprocal | 0.0374 | 0.0391 | 2000.0 | {'data': (10000, 100)} | +| sample_generalized_negative_binomial | 1.0903 | --- | 4194.3042 | {'mu': [2.0, 2.5], 'alpha': [0.0, 2.5], 'shape': (1024, 1024)} | +| sample_generalized_negative_binomial | 0.6051 | --- | 40.0 | {'mu': [2.0, 2.5], 'alpha': [0.0, 2.5], 'shape': (10000, 1)} | +| sample_generalized_negative_binomial | 1.0655 | --- | 4000.0 | {'mu': [2.0, 2.5], 'alpha': [0.0, 2.5], 'shape': (10000, 100)} | +| rint | 0.0369 | --- | 2097.1521 | {'data': (1024, 1024)} | +| rint | 0.025 | --- | 20.0 | {'data': (10000, 1)} | +| rint | 0.0357 | --- | 2000.0 | {'data': (10000, 100)} | +| arcsin | 0.0383 | 0.0412 | 2097.1521 | {'data': (1024, 1024)} | +| arcsin | 0.0269 | 0.0253 | 20.0 | {'data': (10000, 1)} | +| arcsin | 0.038 | 0.0402 | 2000.0 | {'data': (10000, 100)} | +| sample_poisson | 0.4035 | --- | 4194.3042 | {'lam': [1.0, 8.5], 'shape': (1024, 1024)} | +| sample_poisson | 0.3288 | --- | 40.0 | {'lam': [1.0, 8.5], 'shape': (10000, 1)} | +| sample_poisson | 0.4029 | --- | 4000.0 | {'lam': [1.0, 8.5], 'shape': (10000, 100)} | +| Pooling | 0.0225 | 0.0262 | 49.152 | {'stride': 1, 'pool_type': 'avg', 'data': (32, 3, 256), 'layout': 'NCW', 'global_pool': 0, 'kernel': 3, 'pad': 1} | +| sample_uniform | 0.1386 | --- | 4194.3042 | {'shape': (1024, 1024), 'high': [1.0, 3.7], 'low': [0.0, 2.5]} | +| sample_uniform | 0.0776 | --- | 40.0 | {'shape': (10000, 1), 'high': [1.0, 3.7], 'low': [0.0, 2.5]} | +| sample_uniform | 0.1358 | --- | 4000.0 | {'shape': (10000, 100), 'high': [1.0, 3.7], 'low': [0.0, 2.5]} | +| ones_like | 0.0326 | --- | 2097.1521 | {'data': (1024, 1024)} | +| ones_like | 0.0251 | --- | 40.0 | {'data': (10000, 1)} | +| ones_like | 0.032 | --- | 2000.0 | {'data': (10000, 100)} | +| identity | --- | --- | 2097.1521 | {'data': (1024, 1024)} | +| identity | --- | --- | 40.0 | {'data': (10000, 1)} | +| identity | --- | --- | 2000.0 | {'data': (10000, 100)} | +| broadcast_lesser_equal | 0.0189 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| broadcast_lesser | 0.0189 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| zeros_like | 0.0326 | --- | 2097.1521 | {'data': (1024, 1024)} | +| zeros_like | 0.0252 | --- | 40.0 | {'data': (10000, 1)} | +| zeros_like | 0.0321 | --- | 2000.0 | {'data': (10000, 100)} | +| random_uniform | 3.1027 | --- | 2097.1521 | {'shape': (1024, 1024), 'high': 5, 'low': 0} | +| random_uniform | 0.0588 | --- | 20.0 | {'shape': (10000, 1), 'high': 5, 'low': 0} | +| random_uniform | 2.9599 | --- | 2000.0 | {'shape': (10000, 100), 'high': 5, 'low': 0} | +| broadcast_mod | 0.0196 | 0.0227 | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| cbrt | 0.0383 | 0.0405 | 2097.1521 | {'data': (1024, 1024)} | +| cbrt | 0.0272 | 0.0251 | 20.0 | {'data': (10000, 1)} | +| cbrt | 0.0377 | 0.0394 | 2000.0 | {'data': (10000, 100)} | +| broadcast_minus | --- | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| random_negative_binomial | 52.4089 | --- | 2097.1521 | {'shape': (1024, 1024), 'p': 1, 'k': 1} | +| random_negative_binomial | 0.5224 | --- | 20.0 | {'shape': (10000, 1), 'p': 1, 'k': 1} | +| random_negative_binomial | 49.9987 | --- | 2000.0 | {'shape': (10000, 100), 'p': 1, 'k': 1} | +| log2 | 0.0382 | 0.0405 | 2097.1521 | {'data': (1024, 1024)} | +| log2 | 0.0269 | 0.0253 | 20.0 | {'data': (10000, 1)} | +| log2 | 0.0376 | 0.0403 | 2000.0 | {'data': (10000, 100)} | +| broadcast_greater | 0.0191 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| max_axis | 0.0564 | --- | 0.002 | {'axis': (), 'data': (1024, 1024)} | +| max_axis | 0.0315 | --- | 0.002 | {'axis': 0, 'data': (10000, 1)} | +| max_axis | 0.0561 | --- | 0.002 | {'axis': (0, 1), 'data': (10000, 100)} | +| sample_gamma | 0.3435 | --- | 4194.3042 | {'alpha': [0.0, 2.5], 'beta': [1.0, 0.7], 'shape': (1024, 1024)} | +| sample_gamma | 0.2938 | --- | 40.0 | {'alpha': [0.0, 2.5], 'beta': [1.0, 0.7], 'shape': (10000, 1)} | +| sample_gamma | 0.341 | --- | 4000.0 | {'alpha': [0.0, 2.5], 'beta': [1.0, 0.7], 'shape': (10000, 100)} | +| sin | 0.0382 | 0.0386 | 2097.1521 | {'data': (1024, 1024)} | +| sin | 0.0268 | 0.0244 | 20.0 | {'data': (10000, 1)} | +| sin | 0.0374 | 0.0386 | 2000.0 | {'data': (10000, 100)} | +| sum | 0.0797 | 0.0341 | 0.002 | {'axis': (), 'data': (1024, 1024)} | +| sum | 0.0362 | 0.021 | 0.002 | {'axis': 0, 'data': (10000, 1)} | +| sum | 0.0771 | 0.0335 | 0.002 | {'axis': (0, 1), 'data': (10000, 100)} | +| erfinv | 0.1069 | 0.1112 | 2097.1521 | {'data': (1024, 1024)} | +| erfinv | 0.029 | 0.0271 | 20.0 | {'data': (10000, 1)} | +| erfinv | 0.1043 | 0.1055 | 2000.0 | {'data': (10000, 100)} | +| random_gamma | 46.4529 | --- | 2097.1521 | {'shape': (1024, 1024)} | +| random_gamma | 0.466 | --- | 20.0 | {'shape': (10000, 1)} | +| random_gamma | 44.4208 | --- | 2000.0 | {'shape': (10000, 100)} | +| broadcast_mul | 0.019 | 0.0222 | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| arccosh | 0.0405 | 0.0414 | 2097.1521 | {'data': (1024, 1024)} | +| arccosh | 0.0269 | 0.0253 | 20.0 | {'data': (10000, 1)} | +| arccosh | 0.0396 | 0.0402 | 2000.0 | {'data': (10000, 100)} | +| log1p | 0.0383 | 0.0399 | 2097.1521 | {'data': (1024, 1024)} | +| log1p | 0.0269 | 0.0248 | 20.0 | {'data': (10000, 1)} | +| log1p | 0.0376 | 0.0394 | 2000.0 | {'data': (10000, 100)} | +| size_array | 0.0229 | --- | 0.004 | {'data': (1024, 1024)} | +| size_array | 0.0367 | --- | 0.004 | {'data': (10000, 1)} | +| size_array | 0.022 | --- | 0.004 | {'data': (10000, 100)} | +| arcsinh | 0.0396 | 0.0406 | 2097.1521 | {'data': (1024, 1024)} | +| arcsinh | 0.0269 | 0.0249 | 20.0 | {'data': (10000, 1)} | +| arcsinh | 0.0388 | 0.0396 | 2000.0 | {'data': (10000, 100)} | +| elemwise_div | 0.0188 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| elemwise_add | 0.019 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| elemwise_sub | 0.0186 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| arctanh | 0.0385 | 0.0397 | 2097.1521 | {'data': (1024, 1024)} | +| arctanh | 0.0266 | 0.0245 | 20.0 | {'data': (10000, 1)} | +| arctanh | 0.0376 | 0.0391 | 2000.0 | {'data': (10000, 100)} | +| log | 0.0381 | 0.0399 | 2097.1521 | {'data': (1024, 1024)} | +| log | 0.0268 | 0.025 | 20.0 | {'data': (10000, 1)} | +| log | 0.0377 | 0.0394 | 2000.0 | {'data': (10000, 100)} | +| gammaln | 0.0461 | 0.0664 | 2097.1521 | {'data': (1024, 1024)} | +| gammaln | 0.0273 | 0.0266 | 20.0 | {'data': (10000, 1)} | +| gammaln | 0.0455 | 0.0645 | 2000.0 | {'data': (10000, 100)} | +| Dropout | 0.122 | 0.0957 | 25165.8242 | {'mode': 'always', 'data': (32, 3, 256, 256), 'p': 0.5} | +| Dropout | 0.0739 | 0.0241 | 400.0 | {'mode': 'always', 'data': (10000, 10), 'p': 0.5} | +| softmax | 0.0454 | 0.0438 | 2097.1521 | {'axis': -1, 'data': (1024, 1024), 'temperature': 0.5} | +| softmax | 0.056 | 0.0377 | 20.0 | {'axis': -1, 'data': (10000, 1), 'temperature': 0.5} | +| softmax | 0.0805 | 0.0609 | 2000.0 | {'axis': -1, 'data': (10000, 100), 'temperature': 0.5} | +| expm1 | 0.0386 | 0.0387 | 2097.1521 | {'data': (1024, 1024)} | +| expm1 | 0.0275 | 0.025 | 20.0 | {'data': (10000, 1)} | +| expm1 | 0.0375 | 0.0381 | 2000.0 | {'data': (10000, 100)} | +| log10 | 0.0382 | 0.0403 | 2097.1521 | {'data': (1024, 1024)} | +| log10 | 0.0274 | 0.0251 | 20.0 | {'data': (10000, 1)} | +| log10 | 0.0369 | 0.0386 | 2000.0 | {'data': (10000, 100)} | +| random_poisson | 15.4201 | --- | 2097.1521 | {'shape': (1024, 1024)} | +| random_poisson | 0.176 | --- | 20.0 | {'shape': (10000, 1)} | +| random_poisson | 14.9428 | --- | 2000.0 | {'shape': (10000, 100)} | +| sinh | 0.0381 | 0.0381 | 2097.1521 | {'data': (1024, 1024)} | +| sinh | 0.0269 | 0.0246 | 20.0 | {'data': (10000, 1)} | +| sinh | 0.0376 | 0.038 | 2000.0 | {'data': (10000, 100)} | +| random_normal | 16.4453 | --- | 2097.1521 | {'shape': (1024, 1024)} | +| random_normal | 0.1864 | --- | 20.0 | {'shape': (10000, 1)} | +| random_normal | 15.553 | --- | 2000.0 | {'shape': (10000, 100)} | +| hard_sigmoid | 0.031 | 0.0336 | 2097.1521 | {'alpha': 0.25, 'data': (1024, 1024), 'beta': 0.5} | +| hard_sigmoid | 0.0197 | 0.0182 | 20.0 | {'alpha': 0.25, 'data': (10000, 1), 'beta': 0.5} | +| hard_sigmoid | 0.0303 | 0.0322 | 2000.0 | {'alpha': 0.25, 'data': (10000, 100), 'beta': 0.5} | +| flatten | --- | --- | 2097.1521 | {'data': (1024, 1024)} | +| flatten | --- | --- | 20.0 | {'data': (10000, 1)} | +| flatten | --- | --- | 2000.0 | {'data': (10000, 100)} | +| random_exponential | 15.9188 | --- | 2097.1521 | {'shape': (1024, 1024)} | +| random_exponential | 0.1663 | --- | 40.0 | {'shape': (10000, 1)} | +| random_exponential | 15.1982 | --- | 2000.0 | {'shape': (10000, 100)} | +| tan | 0.0395 | 0.0377 | 2097.1521 | {'data': (1024, 1024)} | +| tan | 0.027 | 0.025 | 20.0 | {'data': (10000, 1)} | +| tan | 0.0384 | 0.037 | 2000.0 | {'data': (10000, 100)} | +| broadcast_equal | 0.019 | --- | 0.012 | {'lhs': [(1024, 1024), (10000, 10), (10000, 1)], 'rhs': [(1024, 1024), (10000, 10), (10000, 1)]} | +| softsign | 0.0384 | 0.0402 | 2097.1521 | {'data': (1024, 1024)} | +| softsign | 0.0269 | 0.025 | 20.0 | {'data': (10000, 1)} | +| softsign | 0.0375 | 0.04 | 2000.0 | {'data': (10000, 100)} | +| Convolution | 0.0578 | 0.1121 | 1040.384 | {'stride': (1,), 'data': (32, 3, 256), 'bias': (64,), 'layout': 'NCW', 'dilate': (1,), 'kernel': (3,), 'weight': (64, 3, 3), 'pad': (0,), 'num_filter': 64} | +| sample_normal | 0.1549 | --- | 4194.3042 | {'mu': [2.0, 2.5], 'sigma': [1.0, 3.7], 'shape': (1024, 1024)} | +| sample_normal | 0.1027 | --- | 40.0 | {'mu': [2.0, 2.5], 'sigma': [1.0, 3.7], 'shape': (10000, 1)} | +| sample_normal | 0.1522 | --- | 4000.0 | {'mu': [2.0, 2.5], 'sigma': [1.0, 3.7], 'shape': (10000, 100)} | +| exp | 0.0365 | --- | 2097.1521 | {'data': (1024, 1024)} | +| exp | 0.0246 | --- | 20.0 | {'data': (10000, 1)} | +| exp | 0.0356 | --- | 2000.0 | {'data': (10000, 100)} | +| sum_axis | 0.0637 | --- | 0.004 | {'axis': (), 'data': (1024, 1024)} | +| sum_axis | 0.0351 | --- | 0.002 | {'axis': 0, 'data': (10000, 1)} | +| sum_axis | 0.0631 | --- | 0.002 | {'axis': (0, 1), 'data': (10000, 100)} | \ No newline at end of file diff --git a/benchmark/opperf/rules/default_params.py b/benchmark/opperf/rules/default_params.py index 59b2aff53570..df6cdae0c48d 100644 --- a/benchmark/opperf/rules/default_params.py +++ b/benchmark/opperf/rules/default_params.py @@ -14,10 +14,16 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import sys + +# We will use all operators inside NDArray Module +# If you want to run benchmark for all operators in different namespace, +# for example mxnet.numpy.op, update here. All operators for benchmarks +# will be picked up from this module +MX_OP_MODULE = sys.modules["mxnet.ndarray.op"] -"""Default Input Tensor shapes to use for benchmarking""" -"""""" +"""Default Input Tensor shapes to use for benchmarking""" # For Unary operators like abs, arccos, arcsin etc.. DEFAULT_DATA = [(1024, 1024), (10000, 1), (10000, 100)] diff --git a/benchmark/opperf/utils/common_utils.py b/benchmark/opperf/utils/common_utils.py index 9fe2e19b13b3..0b4fab97811a 100644 --- a/benchmark/opperf/utils/common_utils.py +++ b/benchmark/opperf/utils/common_utils.py @@ -44,7 +44,7 @@ def merge_map_list(map_list): return dict(ChainMap(*map_list)) -def save_to_file(inp_dict, out_filepath, out_format='json'): +def save_to_file(inp_dict, out_filepath, out_format='json', runtime_features=None): """Saves the given input dictionary to the given output file. By default, saves the input dictionary as JSON file. Other supported formats include: @@ -58,6 +58,8 @@ def save_to_file(inp_dict, out_filepath, out_format='json'): Output file path out_format: str, default 'json' Format of the output file. Supported options - 'json', 'md'. Default - json. + runtime_features: map + Dictionary of runtime_features. """ if out_format == 'json': @@ -67,7 +69,7 @@ def save_to_file(inp_dict, out_filepath, out_format='json'): elif out_format == 'md': # Save as md with open(out_filepath, "w") as result_file: - result_file.write(_prepare_markdown(inp_dict)) + result_file.write(_prepare_markdown(inp_dict, runtime_features)) else: raise ValueError("Invalid output file format provided - '{}'. Supported - json, md".format(format)) @@ -107,11 +109,19 @@ def _prepare_op_benchmark_result(op, op_bench_result): max_mem_usage, inputs) -def _prepare_markdown(results): - results_markdown = [ +def _prepare_markdown(results, runtime_features=None): + results_markdown = [] + if runtime_features and 'runtime_features' in runtime_features: + results_markdown.append("# Runtime Features") + idx = 0 + for key, value in runtime_features['runtime_features'].items(): + results_markdown.append('{}. {} : {}'.format(idx, key, value)) + + results_markdown.append("# Benchmark Results") + results_markdown.append( "| Operator | Avg Forward Time (ms) | Avg. Backward Time (ms) | Max Mem Usage (Storage) (Bytes)" - " | Inputs |", - "| :---: | :---: | :---: | :---:| :--- |"] + " | Inputs |") + results_markdown.append("| :---: | :---: | :---: | :---:| :--- |") for op, op_bench_results in sorted(results.items(), key=itemgetter(0)): for op_bench_result in op_bench_results: diff --git a/benchmark/opperf/utils/op_registry_utils.py b/benchmark/opperf/utils/op_registry_utils.py index f5b47cb7a9e4..5da0e322eccc 100644 --- a/benchmark/opperf/utils/op_registry_utils.py +++ b/benchmark/opperf/utils/op_registry_utils.py @@ -17,13 +17,11 @@ """Utilities to interact with MXNet operator registry.""" import ctypes -import sys +from operator import itemgetter +from mxnet import runtime from mxnet.base import _LIB, check_call, py_str, OpHandle, c_str, mx_uint -from benchmark.opperf.rules.default_params import DEFAULTS_INPUTS - -# We will use all operators inside NDArray Module -mx_nd_module = sys.modules["mxnet.ndarray.op"] +from benchmark.opperf.rules.default_params import DEFAULTS_INPUTS, MX_OP_MODULE # Operators where parameter have special criteria that cannot be cleanly automated. # Example: sample_multinomial operator has a parameter 'data'. It expects values to sum up to 1. @@ -62,7 +60,7 @@ def _select_ops(operator_names, filters=("_contrib", "_"), merge_op_forward_back for cur_op_name in operator_names: if not cur_op_name.startswith(filters): mx_operators[cur_op_name] = {"has_backward": False, - "nd_op_handle": getattr(mx_nd_module, cur_op_name)} + "nd_op_handle": getattr(MX_OP_MODULE, cur_op_name)} if cur_op_name.startswith("_backward_"): operators_with_backward.append(cur_op_name) @@ -329,3 +327,19 @@ def get_operators_with_no_benchmark(operators_with_benchmark): """ all_mxnet_operators = _get_all_mxnet_operators().keys() return list(set(all_mxnet_operators) - set(operators_with_benchmark)) + + +def get_current_runtime_features(): + """Get all current runtime time flags/configuration for MXNet. + + Returns + ------- + Map of current runtime features such as compile flags used by MXNet. + Example: {'runtime_features': {'OPENCV' : '✔ OPENCV', 'CUDA': '✖ CUDA'}} + """ + features = runtime.Features() + runtime_features = {} + for feature, config in sorted(features.items(), key=itemgetter(0)): + runtime_features[feature] = config + + return {'runtime_features': runtime_features} From 8aaacde92987bb984b100eeec9557ec204ff0610 Mon Sep 17 00:00:00 2001 From: Haibin Lin Date: Fri, 28 Jun 2019 11:42:01 -0700 Subject: [PATCH 020/813] Update sparse_retain Documentation (#15394) * Update sparse_retain.cc * Update sparse_retain.cc --- src/operator/tensor/sparse_retain.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/operator/tensor/sparse_retain.cc b/src/operator/tensor/sparse_retain.cc index 007541b00b37..b1f38c6d13b1 100644 --- a/src/operator/tensor/sparse_retain.cc +++ b/src/operator/tensor/sparse_retain.cc @@ -31,7 +31,7 @@ namespace op { // accepts row-sparse format ndarrays. It will be registered // under mxnet.ndarray.sparse with name retain. NNVM_REGISTER_OP(_sparse_retain) -.describe(R"code(pick rows specified by user input index array from a row sparse matrix +.describe(R"code(Pick rows specified by user input index array from a row sparse matrix and save them in the output sparse matrix. Example:: @@ -39,10 +39,10 @@ Example:: data = [[1, 2], [3, 4], [5, 6]] indices = [0, 1, 3] shape = (4, 2) - rsp_in = row_sparse(data, indices) + rsp_in = row_sparse_array(data, indices) to_retain = [0, 3] rsp_out = retain(rsp_in, to_retain) - rsp_out.values = [[1, 2], [5, 6]] + rsp_out.data = [[1, 2], [5, 6]] rsp_out.indices = [0, 3] The storage type of ``retain`` output depends on storage types of inputs From 11e6d4519a3c7d8b64ab2e1b4c05bb5626fa9ae8 Mon Sep 17 00:00:00 2001 From: Haibin Lin Date: Fri, 28 Jun 2019 14:21:32 -0700 Subject: [PATCH 021/813] [AMP] Move topk from FP16_FP32_FUNCS to FP32_FUNCS (#15342) --- python/mxnet/contrib/amp/lists/symbol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mxnet/contrib/amp/lists/symbol.py b/python/mxnet/contrib/amp/lists/symbol.py index 9c99340ab75d..066618b4073d 100644 --- a/python/mxnet/contrib/amp/lists/symbol.py +++ b/python/mxnet/contrib/amp/lists/symbol.py @@ -340,7 +340,6 @@ 'take', 'tanh', 'tile', - 'topk', 'transpose', 'trunc', 'uniform', @@ -463,6 +462,7 @@ '_sparse_norm', '_sparse_rsqrt', 'argsort', + 'topk', # Neural network 'SoftmaxOutput', From ca565a00285d4fb0ca77ba9dc651a07ce1f01b24 Mon Sep 17 00:00:00 2001 From: Anirudh Subramanian Date: Fri, 28 Jun 2019 14:22:04 -0700 Subject: [PATCH 022/813] Conversion from FP32 model to Mixed Precision model (#15118) * Initial AMP commit * Fix * Merge AMP Changes * AMP Changes to support conditional op names switch * Add example and fix issues with AMP conversion * Remove amp convert symbol test * Fix comment for inference use case * Remove input_names for convert_hybrid_block * Check all conditions * Fix lint * Fix error_str for load_dict * Fix lint, Add tests, fix bugs, add examples * Fix warnings * Add license for example script * Remove gpu test and move tests to test_contrib_amp * Clean up AMP tests * Add additional comments, add tutorial * Move the test to gpu dir * Make the code python3 compatible * Upgrade archive utility, fixes: #15084 * Allow AR path to be chosen by user * Use current_context in tutorial * Update __all__ * Merge with load params API changes * Revert "Allow AR path to be chosen by user" This reverts commit 94156b631102e8a1086b3f8b693f2994932d4035. * Revert "Upgrade archive utility, fixes: #15084" This reverts commit ea7dd3298cb71f5ce096fe407d7a8f0cc42d43dd. * Set numpy dtype to float32 * Address review comments * Add range based for * Change quantized to low precision * Fix lint * Fix pylint * Forward args for Node::Create * Fixes * Add dtype casting wherever needed * Fix lint in source * Add cast_optional_params to example * Tweak example * Add README * Add README * Add cast_optional_params test for convert_model and convert_hybrid_bloc --- docs/tutorials/amp/amp_tutorial.md | 42 ++ example/automatic-mixed-precision/README.md | 35 ++ .../amp_model_conversion.py | 119 +++++ example/automatic-mixed-precision/common | 1 + include/mxnet/c_api.h | 49 ++ python/mxnet/contrib/amp/amp.py | 353 ++++++++++++++- python/mxnet/gluon/parameter.py | 44 +- python/mxnet/module/executor_group.py | 8 + python/mxnet/test_utils.py | 83 ++++ src/c_api/c_api_symbolic.cc | 204 +++++++++ src/nnvm/amp_infer_unknown.cc | 148 ++++++ src/nnvm/low_precision_pass.cc | 261 +++++++++++ tests/python/gpu/test_contrib_amp.py | 428 ++++++++++++++++++ tests/python/tensorrt/test_tensorrt_lenet5.py | 2 +- tests/python/unittest/test_contrib_amp.py | 85 ---- 15 files changed, 1766 insertions(+), 96 deletions(-) create mode 100644 example/automatic-mixed-precision/README.md create mode 100644 example/automatic-mixed-precision/amp_model_conversion.py create mode 120000 example/automatic-mixed-precision/common create mode 100644 src/nnvm/amp_infer_unknown.cc create mode 100644 src/nnvm/low_precision_pass.cc create mode 100644 tests/python/gpu/test_contrib_amp.py delete mode 100644 tests/python/unittest/test_contrib_amp.py diff --git a/docs/tutorials/amp/amp_tutorial.md b/docs/tutorials/amp/amp_tutorial.md index be18929e23a9..31cd23b19ae7 100644 --- a/docs/tutorials/amp/amp_tutorial.md +++ b/docs/tutorials/amp/amp_tutorial.md @@ -31,12 +31,14 @@ For demonstration purposes we will use synthetic data loader. ```python +import os import logging import warnings import time import mxnet as mx import mxnet.gluon as gluon from mxnet import autograd +from mxnet.test_utils import download_model import gluoncv as gcv from gluoncv.model_zoo import get_model @@ -249,6 +251,46 @@ for epoch in range(1): We got 60% speed increase from 3 additional lines of code! +## Inference with AMP + +To do inference with mixed precision for a trained model in FP32, you can use the conversion APIs: `amp.convert_model` for symbolic model and `amp.convert_hybrid_block` for gluon models. The conversion APIs will take the FP32 model as input and will return a mixed precision model, which can be used to run inference. Below, we demonstrate for a gluon model and a symbolic model: 1. Conversion from FP32 model to mixed precision model 2. Run inference on the mixed precision model. + +```python +with mx.Context(mx.gpu(0)): + # Below is an example of converting a gluon hybrid block to a mixed precision block + model = get_model("resnet50_v1") + model.collect_params().initialize(ctx=mx.current_context()) + model.hybridize() + model(mx.nd.zeros((1, 3, 224, 224))) + converted_model = amp.convert_hybrid_block(model) + + # Run dummy inference with the converted gluon model + result = converted_model.forward(mx.nd.random.uniform(shape=(1, 3, 224, 224), + dtype=np.float32)) + + # Below is an example of converting a symbolic model to a mixed precision model + dir_path = os.path.dirname(os.path.realpath(__file__)) + model_path = os.path.join(dir_path, 'model') + if not os.path.isdir(model_path): + os.mkdir(model_path) + prefix, epoch = mx.test_utils.download_model("imagenet1k-resnet-18", dst_dir=model_path) + sym, arg_params, aux_params = mx.model.load_checkpoint(prefix, epoch) + result_sym, result_arg_params, result_aux_params = amp.convert_model(sym, + arg_params, + aux_params) + + # Run dummy inference with the converted symbolic model + mod = mx.mod.Module(result_sym, data_names=["data"], label_names=["softmax_label"], context=mx.current_context()) + mod.bind(data_shapes=[['data', (1, 3, 224, 224)]], label_shapes=[['softmax_label', (1,)]]) + mod.set_params(result_arg_params, result_aux_params) + mod.forward(mx.io.DataBatch(data=[mx.nd.ones((1, 3, 224, 224))], + label=[mx.nd.ones((1,))])) + mod.get_outputs()[0].wait_to_read() + print("Conversion and Inference completed successfully") +``` + + + ## Current limitations of AMP - AMP's dynamic loss scaling currently supports only Gluon trainer with `update_on_kvstore=False` option set diff --git a/example/automatic-mixed-precision/README.md b/example/automatic-mixed-precision/README.md new file mode 100644 index 000000000000..49147cd87242 --- /dev/null +++ b/example/automatic-mixed-precision/README.md @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + +# Conversion of FP32 models to Mixed Precision Models + + +This folder contains examples for converting FP32 models to mixed precision models. The script allows for converting FP32 symbolic models or gluon models to mixed precision model. + +## Basic Usages + +1. AMP Model Conversion for a gluon model, casting the params wherever possible to FP16. The below script will convert the `resnet101_v1` model to Mixed Precision Model and cast params to FP16 wherever possible, load this converted model and run inference on it. + +```bash +python amp_model_conversion.py --model resnet101_v1 --use-gluon-model --run-dummy-inference --cast-optional-params +``` + +2. AMP Model Conversion for a symbolic model, keeping the params in FP32 wherever possible (--cast-optional-params not used). + +```bash +python amp_model_conversion.py --model imagenet1k-resnet-152 --run-dummy-inference +``` diff --git a/example/automatic-mixed-precision/amp_model_conversion.py b/example/automatic-mixed-precision/amp_model_conversion.py new file mode 100644 index 000000000000..fcc2ad69dd62 --- /dev/null +++ b/example/automatic-mixed-precision/amp_model_conversion.py @@ -0,0 +1,119 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import os +import logging +import argparse +import mxnet as mx +from common import modelzoo +import gluoncv +from gluoncv.model_zoo import get_model +from mxnet.contrib.amp import amp +import numpy as np + +def download_model(model_name, logger=None): + dir_path = os.path.dirname(os.path.realpath(__file__)) + model_path = os.path.join(dir_path, 'model') + if logger is not None: + logger.info('Downloading model {}... into path {}'.format(model_name, model_path)) + return modelzoo.download_model(args.model, os.path.join(dir_path, 'model')) + + +def save_symbol(fname, sym, logger=None): + if logger is not None: + logger.info('Saving symbol into file at {}'.format(fname)) + sym.save(fname, remove_amp_cast=False) + + +def save_params(fname, arg_params, aux_params, logger=None): + if logger is not None: + logger.info('Saving params into file at {}'.format(fname)) + save_dict = {('arg:%s' % k): v.as_in_context(mx.cpu()) for k, v in arg_params.items()} + save_dict.update({('aux:%s' % k): v.as_in_context(mx.cpu()) for k, v in aux_params.items()}) + mx.nd.save(fname, save_dict) + + +if __name__ == '__main__': + symbolic_models = ['imagenet1k-resnet-152', + 'imagenet1k-resnet-18', + 'imagenet1k-resnet-34', + 'imagenet1k-resnet-50', + 'imagenet1k-resnet-101', + 'imagenet1k-resnext-50', + 'imagenet1k-resnext-101', + 'imagenet1k-resnext-101-64x4d', + 'imagenet11k-place365ch-resnet-152', + 'imagenet11k-place365ch-resnet-50'] + gluon_models = ['resnet18_v1', + 'resnet50_v1', + 'resnet101_v1', + 'squeezenet1.0', + 'mobilenet1.0', + 'mobilenetv2_1.0', + 'inceptionv3'] + models = symbolic_models + gluon_models + + parser = argparse.ArgumentParser(description='Convert a provided FP32 model to a mixed precision model') + parser.add_argument('--model', type=str, choices=models) + parser.add_argument('--run-dummy-inference', action='store_true', default=False, + help='Will generate random input of shape (1, 3, 224, 224) ' + 'and run a dummy inference forward pass') + parser.add_argument('--use-gluon-model', action='store_true', default=False, + help='If enabled, will download pretrained model from Gluon-CV ' + 'and convert to mixed precision model ') + parser.add_argument('--cast-optional-params', action='store_true', default=False, + help='If enabled, will try to cast params to target dtype wherever possible') + args = parser.parse_args() + logging.basicConfig() + logger = logging.getLogger('logger') + logger.setLevel(logging.INFO) + + if not args.use_gluon_model: + assert args.model in symbolic_models, "Please choose one of the available symbolic models: {} \ + If you want to use gluon use the script with --use-gluon-model".format(symbolic_models) + + prefix, epoch = download_model(model_name=args.model, logger=logger) + sym, arg_params, aux_params = mx.model.load_checkpoint(prefix, epoch) + result_sym, result_arg_params, result_aux_params = amp.convert_model(sym, arg_params, aux_params, + cast_optional_params=args.cast_optional_params) + sym_name = "%s-amp-symbol.json" % (prefix) + save_symbol(sym_name, result_sym, logger) + param_name = '%s-%04d.params' % (prefix + '-amp', epoch) + save_params(param_name, result_arg_params, result_aux_params, logger) + if args.run_dummy_inference: + logger.info("Running inference on the mixed precision model with dummy input, batch size: 1") + mod = mx.mod.Module(result_sym, data_names=['data'], label_names=['softmax_label'], context=mx.gpu(0)) + mod.bind(data_shapes=[['data', (1, 3, 224, 224)]], label_shapes=[['softmax_label', (1,)]]) + mod.set_params(arg_params, aux_params) + mod.forward(mx.io.DataBatch(data=[mx.nd.ones((1, 3, 224, 224))], + label=[mx.nd.ones((1,))])) + result = mod.get_outputs()[0].asnumpy() + logger.info("Inference run successfully") + else: + assert args.model in gluon_models, "Please choose one of the available gluon models: {} \ + If you want to use symbolic model instead, remove --use-gluon-model when running the script".format(gluon_models) + net = gluoncv.model_zoo.get_model(args.model, pretrained=True) + net.hybridize() + result_before1 = net.forward(mx.nd.zeros((1, 3, 224, 224))) + net.export("{}".format(args.model)) + net = amp.convert_hybrid_block(net, cast_optional_params=args.cast_optional_params) + net.export("{}-amp".format(args.model), remove_amp_cast=False) + if args.run_dummy_inference: + logger.info("Running inference on the mixed precision model with dummy inputs, batch size: 1") + result_after = net.forward(mx.nd.zeros((1, 3, 224, 224), dtype=np.float32, ctx=mx.gpu(0))) + result_after = net.forward(mx.nd.zeros((1, 3, 224, 224), dtype=np.float32, ctx=mx.gpu(0))) + logger.info("Inference run successfully") diff --git a/example/automatic-mixed-precision/common b/example/automatic-mixed-precision/common new file mode 120000 index 000000000000..cafb9140ab6a --- /dev/null +++ b/example/automatic-mixed-precision/common @@ -0,0 +1 @@ +../image-classification/common \ No newline at end of file diff --git a/include/mxnet/c_api.h b/include/mxnet/c_api.h index a2da6db978cb..bd30e44f910c 100644 --- a/include/mxnet/c_api.h +++ b/include/mxnet/c_api.h @@ -1764,6 +1764,55 @@ MXNET_DLL int MXQuantizeSymbol(SymbolHandle sym_handle, SymbolHandle *ret_sym_ha const mx_uint num_offline, const char **offline_params, const char *quantized_dtype, const bool calib_quantize); +/*! + * \brief Convert a symbol into a mixed precision symbol with cast operators for target dtype casting + * \param sym_handle symbol to be converted + * \param ret_sym_handle mixed precision symbol result + * \param num_args number of arguments for known dtypes + * \param arg_type_data arg types of the arguments + * \param target_dtype target_dtype for mixed precision symbol + * \param cast_optional_params whether to cast optional params to target_dtype + * \param num_target_dtype_op_names number of ops to be casted to target_dtype + * \param num_fp32_op_names number of ops to be casted to FP32 + * \param num_widest_dtype_op_names number of ops to be casted to widest dtype + * \param num_conditional_fp32_op_names number of ops to be casted to FP32 based on a condition + * \param num_excluded_symbols number of symbols to be excluded from casting + * \param num_model_params number of model parameters + * \param num_widest_dtype_op_names number of ops to be casted to the widest dtype + * \param num_conditional_fp32_op_names number of ops to be cast to fp32 based on precision + * \param target_dtype_op_names op names to be casted to target_dtype + * \param fp32_op_names op names to be casted to fp32 + * \param widest_dtype_op_names names to be casted to widest dtype + * \param conditional_fp32_op_names names to be casted to FP32 conditionally + * \param excluded_symbols symbol names to be excluded from casting + * \param param_names param names for conditional FP32 casting + * \param param_values param values for conditional FP32 casting + * \param arg_names argument names for which type information is provided + * \param model_param_names names for model parameters + */ +MXNET_DLL int MXReducePrecisionSymbol(SymbolHandle sym_handle, + SymbolHandle *ret_sym_handle, + mx_uint num_args, + const int* arg_type_data, + mx_uint num_ind_ptr, + const int* ind_ptr, + const int* target_dtype, + const int cast_optional_params, + const mx_uint num_target_dtype_op_names, + const mx_uint num_fp32_op_names, + const mx_uint num_widest_dtype_op_names, + const mx_uint num_conditional_fp32_op_names, + const mx_uint num_excluded_symbols, + const mx_uint num_model_params, + const char **target_dtype_op_names, + const char **fp32_op_names, + const char **widest_dtype_op_names, + const char **conditional_fp32_op_names, + const char **excluded_symbols, + const char **conditional_param_names, + const char **conditional_param_vals, + const char **model_param_names, + const char **arg_names); /*! * \brief Set calibration table to node attributes in the sym * \param sym_handle symbol whose node attributes are to be set by calibration table diff --git a/python/mxnet/contrib/amp/amp.py b/python/mxnet/contrib/amp/amp.py index bb3972092139..ef2f7209d946 100755 --- a/python/mxnet/contrib/amp/amp.py +++ b/python/mxnet/contrib/amp/amp.py @@ -17,21 +17,28 @@ # coding: utf-8 """Functions for enabling AMP (automatic mixed precision).""" -__all__ = ['init', 'init_trainer', 'scale_loss', 'unscale'] +__all__ = ['init', 'init_trainer', 'scale_loss', 'unscale', 'convert_model', + 'convert_hybrid_block', 'list_fp16_ops', 'list_fp32_ops', + 'list_fp16_fp32_ops', 'list_conditional_fp32_ops', + 'convert_symbol'] from types import MethodType +from array import array +import ctypes import logging import contextlib import numpy as np from ... import symbol +from ...context import gpu from ...symbol import Symbol from ...symbol import contrib as symbol_contrib from ... import ndarray -from ...ndarray import NDArray +from ...ndarray import NDArray, _DTYPE_NP_TO_MX, _DTYPE_MX_TO_NP from . import lists from ...gluon import trainer from ... import base +from ...base import c_str_array, SymbolHandle, check_call, _LIB, mx_uint, c_array_buf from ... import optimizer as opt from .loss_scaler import LossScaler @@ -342,3 +349,345 @@ def unscale(optimizer_or_trainer): else: raise TypeError("optimizer_or_trainer should be a Gluon Trainer or " "an optimizer, instead is %s" % type(optimizer_or_trainer)) + +def convert_symbol(sym, target_dtype="float16", target_dtype_ops=None, + fp32_ops=None, conditional_fp32_ops=None, + excluded_sym_names=None, data_names=None, + cast_optional_params=False): + """Given a symbol object representing a neural network of data type FP32 and target_dtype, + add cast layers according to the op lists (target_dtype_ops, fp32_ops, + conditional_fp32_ops) if provided, otherwise use the default + lists provided by the framework. + + Parameters + ---------- + sym : Symbol + FP32 neural network symbol + target_dtype : str or numpy, optional defaults to float16 + currently only supports float16. The target dtype indicates to add cast layers + when possible so that lower precision computation can be leveraged. + target_dtype_ops : list of strs, optional + Override the list of operator names casted to the target_dtype. + If None, uses the framework's default list to be casted to target_dtype. + fp32_ops : list of strs, optional + Override the list of operator names casted to FP32. + If None, uses the framework's default list to be casted to FP32. + conditional_fp32_ops : list of (string, string, list of string), optional + Override the list of functions to be casted to FP32. + The format of the list is + (name of the function, name of the parameter, + list of values of the parameter that make the operator to be casted to FP32) + excluded_sym_names : list of strs, optional + A list of strings that represent the names of symbols that users want to exclude + from being casted to FP16 or FP32. + data_names : list of strs, optional + A list of strings that represent input data tensor names to the model + cast_optional_params : bool, default False + Whether to cast the arg_params and aux_params that don't require to be in FP16 + because of a cast layer following it, but will reduce the computation and memory + overhead of the model if casted. + """ + assert isinstance(sym, Symbol), "First argument to convert_symbol should be Symbol" + + if target_dtype != "float16": + raise ValueError("Only target_dtype float16 is supported currently") + + if target_dtype_ops is not None: + assert isinstance(target_dtype_ops, list), "target_dtype_ops should be a list of strs" + else: + target_dtype_ops = lists.symbol.FP16_FUNCS + + if fp32_ops is not None: + assert isinstance(fp32_ops, list), "fp32_ops should be a list of strs" + else: + fp32_ops = lists.symbol.FP32_FUNCS + + if conditional_fp32_ops is not None: + assert isinstance(conditional_fp32_ops, list), "conditional_fp32_ops should be a list" + else: + conditional_fp32_ops = lists.symbol.CONDITIONAL_FP32_FUNCS + + original_conditional_op_names = [] + conditional_op_names = [] + param_names = [] + param_vals = [] + indptr = [0] + for conditional_fp32_op in conditional_fp32_ops: + assert isinstance(conditional_fp32_op[0], str) and isinstance(conditional_fp32_op[1], str) \ + and isinstance(conditional_fp32_op[2], list), "conditional_fp32_ops should be a list of " \ + "(str, str, list of str)" + param_vals += conditional_fp32_op[2] + indptr.append(len(param_vals)) + param_names.append(conditional_fp32_op[1]) + conditional_op_names.append(conditional_fp32_op[0]) + + if excluded_sym_names is not None: + assert isinstance(excluded_sym_names, list), "excluded_sym_names should be a list of strs" + else: + excluded_sym_names = [] + + for original_conditional_fp32_op in lists.symbol.CONDITIONAL_FP32_FUNCS: + original_conditional_op_names.append(original_conditional_fp32_op[0]) + + # Op lists should not have intersection + common_ops = set(target_dtype_ops) & set(fp32_ops) + assert len(common_ops) == 0, "Ops cannot be in two or more lists. " \ + "Common ops in target_dtype_ops and fp32_ops {}".format(common_ops) + common_ops = set(target_dtype_ops) & set(conditional_op_names) + assert len(common_ops) == 0, "Ops cannot be in two or more lists. " \ + "Common ops in target_dtype_ops and conditional_fp32_ops {}".format(common_ops) + common_ops = set(conditional_op_names) & set(fp32_ops) + assert len(common_ops) == 0, "Ops cannot be in two or more lists. " \ + "Common ops in fp32_ops and conditional_fp32_ops {}".format(common_ops) + + combined_ops = set(target_dtype_ops + fp32_ops + conditional_op_names) + all_fp16_fp32_ops = set(lists.symbol.FP16_FUNCS + lists.symbol.FP32_FUNCS + + lists.symbol.FP16_FP32_FUNCS + original_conditional_op_names) + + illegal_ops = combined_ops - all_fp16_fp32_ops + assert not illegal_ops, '''Can only choose ops from one of the three lists + for fp16_ops and fp32_ops + 1. amp.list_fp16_ops() + 2. amp.list_fp32_ops() + 3. amp.list_fp16_fp32_ops() + 4. amp.list_conditional_fp32_ops() + Op %s not in any of them''' % (illegal_ops) + + widest_dtype_ops = lists.symbol.WIDEST_TYPE_CASTS + target_dtype = _DTYPE_NP_TO_MX[np.dtype(target_dtype).type] + + # Prepare a data_names list based on list_inputs if its not provided + # Add all names in list for the nodes in the symbol which don't have + # __dtype__ set + attr_dict = sym.attr_dict() + if data_names is None: + data_names = [] + for sym_name in sym.list_inputs(): + if not sym_name in attr_dict: + data_names.append(sym_name) + continue + if not "__dtype__" in attr_dict[sym_name]: + data_names.append(sym_name) + model_param_names = list(set(sym.list_inputs()) - set(data_names)) + + # Since assumption is that it is a FP32 model, set dtypes for all + # data_names to float32 + str_keys = [] + sdata = [] + for k in data_names: + str_keys.append(k) + sdata.append(0) + keys = c_str_array(str_keys) + + out = SymbolHandle() + check_call(_LIB.MXReducePrecisionSymbol(sym.handle, + ctypes.byref(out), + mx_uint(len(sdata)), + c_array_buf(ctypes.c_int, array('i', sdata)), + mx_uint(len(indptr)), + c_array_buf(ctypes.c_int, array('i', indptr)), + ctypes.byref(ctypes.c_int(target_dtype)), + ctypes.c_int(cast_optional_params), + mx_uint(len(target_dtype_ops)), + mx_uint(len(fp32_ops)), + mx_uint(len(widest_dtype_ops)), + mx_uint(len(conditional_op_names)), + mx_uint(len(excluded_sym_names)), + mx_uint(len(model_param_names)), + c_str_array(target_dtype_ops), + c_str_array(fp32_ops), + c_str_array(widest_dtype_ops), + c_str_array(conditional_op_names), + c_str_array(excluded_sym_names), + c_str_array(param_names), + c_str_array(param_vals), + c_str_array(model_param_names), + keys)) + return Symbol(out) + +def convert_model(sym, arg_params, aux_params, target_dtype="float16", target_dtype_ops=None, + fp32_ops=None, conditional_fp32_ops=None, excluded_sym_names=None, + cast_optional_params=False): + """API for converting a model from FP32 model to a mixed precision model. + MXNet tries to convert the FP32 model to mixed precision model by adding + cast layers using amp_cast and amp_multicast operators which can be used for inference use cases. + The decision on which cast layer to add is based on hardcoded lists for Automatic Mixed Precision + in MXNet. These lists can be overridden by the user by providing their own lists + using : targe_precision_ops, fp32_ops, widest_precision_ops, conditional_fp32_ops + + arg_params : dict + Dictionary of name to `NDArray`. + aux_params : dict + Dictionary of name to `NDArray`. + target_dtype : str + Currently only supports float16. The target dtype indicates to add cast layers + when possible so that lower precision computation can be leveraged. + target_dtype_ops : list of strs + Override the list of operator names casted to target_dtype. + If None, uses the framework's default list to be casted to target dtype. + fp32_ops : list of strs + Override the lists of operator names casted to FP32. + If None, uses the framework's default list to be casted to FP32. + widest_dtype_ops : list of strs + A list of op names provided by user which should run in widest precision among its inputs. + If None, uses the framework's default list of widest_precision_ops. + conditional_fp32_ops : list of (string, string, list of string) + Override the list of operators to be casted to FP32. + The format of the list is + (name of the function, name of the parameter, + list of values of the parameter that make the operator to be casted to + fp32) + excluded_sym_names : list of strs + A list of strings that represent the names of symbols that users want to exclude + from being executed in lower precision. + cast_optional_params : bool, default False + Whether to cast the arg_params and aux_params that don't require to be in FP16 + because of a cast layer following it, but will reduce the computation and memory + overhead of the model if casted. + """ + if excluded_sym_names is None: + excluded_sym_names = [] + if not isinstance(excluded_sym_names, list): + raise ValueError('excluded_sym_names must be a list of strings representing' + ' the names of the symbols that should not be casted,' + ' while received type %s' % str(type(excluded_sym_names))) + + if target_dtype != "float16": + raise ValueError("Only target_dtype float16 is supported currently") + + assert isinstance(sym, Symbol), "First argument to convert_model should be Symbol" + assert isinstance(arg_params, dict), "Second argument to convert_model should be a dict of name to ndarray" + assert isinstance(aux_params, dict), "Third argument to convert_model should be a dict of name to ndarray" + + param_names = list(arg_params.keys()) + list(aux_params.keys()) + + # Only pass non params as data_names, param types can be inferred + data_names = list(set(sym.list_inputs()) - set(param_names)) + + sym = convert_symbol(sym, target_dtype, target_dtype_ops, + fp32_ops, conditional_fp32_ops, + excluded_sym_names, data_names, + cast_optional_params) + + # If dtype is set for params, cast the param to that dtype + attr_dict = sym.attr_dict() + for sym_name in sym.list_arguments(): + if sym_name in attr_dict and "__dtype__" in attr_dict[sym_name]: + if attr_dict[sym_name]["__dtype__"] != "-1": + typ = _DTYPE_MX_TO_NP[int(attr_dict[sym_name]["__dtype__"])] + arg_params[sym_name] = arg_params[sym_name].astype(typ) + + for sym_name in sym.list_auxiliary_states(): + if sym_name in attr_dict and "__dtype__" in attr_dict[sym_name]: + if attr_dict[sym_name]["__dtype__"] != "-1": + typ = _DTYPE_MX_TO_NP[int(attr_dict[sym_name]["__dtype__"])] + aux_params[sym_name] = aux_params[sym_name].astype(typ) + + # Return the converted symbol and casted params + return sym, arg_params, aux_params + +def convert_hybrid_block(block, target_dtype="float16", target_dtype_ops=None, + fp32_ops=None, conditional_fp32_ops=None, + excluded_sym_names=None, ctx=gpu(0), + cast_optional_params=False): + """Given a hybrid block/symbol block representing a FP32 model and a target_dtype, + return a block with mixed precision support which can be used for inference use cases. + + Parameters + ---------- + block : HybridBlock or SymbolBlock object + FP32 HybridBlock or SymbolBlock object + target_dtype : str or numpy + currently only supports fp16. The target dtype indicates to add cast layers + when possible so that lower precision computation can be leveraged. + target_precision_ops : list of strs + Override the list of operator names casted to target_dtype. + If None, uses the framework's default list to be casted to FP32. + conditional_fp32_ops : list of (str, str, list of str) + Override the list of functions to be casted to FP32. + The format of the list is + (name of the function, name of the parameter, + list of values of the parameter that make the operator to be casted to FP32 + excluded_sym_names : list of strs + A list of strings that represent the names of symbols that users want to exclude + from being quantized + ctx : Context + Context on which model parameters should live + cast_optional_params : bool, default False + Whether to cast the arg_params and aux_params that don't require to be in FP16 + because of a cast layer following it, but will reduce the computation and memory + overhead of the model if casted. + """ + from ...gluon import HybridBlock, SymbolBlock + assert isinstance(block, HybridBlock), "block input should be a HybridBlock" + if not block._cached_graph: + raise RuntimeError( + "Please first call block.hybridize() and then run forward with " + "this block at least once before calling export.") + + # Prepare inputs to pass to the convert_symbol API + inputs, sym = block._cached_graph + input_names = [] + for inp in inputs: + input_names.append(inp.name) + converted_sym = convert_symbol(sym, target_dtype, target_dtype_ops, + fp32_ops, conditional_fp32_ops, + excluded_sym_names, data_names=input_names, + cast_optional_params=cast_optional_params) + + arg_names = set(converted_sym.list_arguments()) + aux_names = set(converted_sym.list_auxiliary_states()) + arg_dict = {} + + # If dtype for the param was set in the json, cast the + # param to this dtype + attr_dict = converted_sym.attr_dict() + for name, param in block.collect_params().items(): + if name in arg_names: + arg_dict['arg:%s'%name] = param._reduce() + if name in attr_dict and "__dtype__" in attr_dict[name]: + if attr_dict[name]["__dtype__"] != "-1": + typ = _DTYPE_MX_TO_NP[int(attr_dict[name]["__dtype__"])] + arg_dict['arg:%s'%name] = arg_dict['arg:%s'%name].astype(typ) + else: + assert name in aux_names + arg_dict['aux:%s'%name] = param._reduce() + if name in attr_dict and "__dtype__" in attr_dict[name]: + if attr_dict[name]["__dtype__"] != "-1": + typ = _DTYPE_MX_TO_NP[int(attr_dict[name]["__dtype__"])] + arg_dict['aux:%s'%name] = arg_dict['aux:%s'%name].astype(typ) + + # Create a symbolblock and cast the params to the dtypes based + # on the dtype information from the converted_symbol + ret = SymbolBlock(converted_sym, inputs) + for key, param in ret.collect_params().items(): + arg_param_name = "arg:%s" % key + if arg_param_name in arg_dict and param.dtype != arg_dict[arg_param_name].dtype: + param.cast(arg_dict[arg_param_name].dtype) + + aux_param_name = "aux:%s" % key + if aux_param_name in arg_dict and param.dtype != arg_dict[aux_param_name].dtype: + param.cast(arg_dict[aux_param_name].dtype) + + ret.collect_params().load_dict(arg_dict, ctx=ctx) + return ret + +def list_fp16_ops(): + """Get the default list of FP16 ops for AMP + """ + return lists.symbol.FP16_FUNCS + +def list_fp32_ops(): + """Get the default list of FP32 ops for AMP + """ + return lists.symbol.FP32_FUNCS + +def list_fp16_fp32_ops(): + """Get the default list of ops which run in both FP16 and FP32 + """ + return lists.symbol.FP16_FP32_FUNCS + +def list_conditional_fp32_ops(): + """Get the conditional fp32 ops list + """ + return lists.symbol.CONDITIONAL_FP32_FUNCS diff --git a/python/mxnet/gluon/parameter.py b/python/mxnet/gluon/parameter.py index 548407584715..a174d82341af 100644 --- a/python/mxnet/gluon/parameter.py +++ b/python/mxnet/gluon/parameter.py @@ -16,7 +16,7 @@ # under the License. # coding: utf-8 -# pylint: disable=unnecessary-pass +# pylint: disable=unnecessary-pass, too-many-lines """Neural network parameter.""" __all__ = ['DeferredInitializationError', 'Parameter', 'Constant', 'ParameterDict', 'tensor_types'] @@ -955,23 +955,51 @@ def load(self, filename, ctx=None, allow_missing=False, assert name.startswith(restore_prefix), \ "restore_prefix is '%s' but Parameters name '%s' does not start " \ "with '%s'"%(restore_prefix, name, restore_prefix) - lprefix = len(restore_prefix) ndarray_load = ndarray.load(filename) + self.load_dict(ndarray_load, ctx, allow_missing, + ignore_extra, restore_prefix, filename, cast_dtype, dtype_source) + + def load_dict(self, param_dict, ctx=None, allow_missing=False, + ignore_extra=False, restore_prefix='', filename=None, cast_dtype=False, + dtype_source="current"): + """Load parameters from dict + + Parameters + ---------- + param_dict : dict + Dictionary containing model parameters, preprended with arg: and aux: names + ctx : Context or list of Context + Context(s) initialize loaded parameters on. + allow_missing : bool, default False + Whether to silently skip loading parameters not represented in the file. + ignore_extra : bool, default False + Whether to silently ignore parameters from the file that are not + present in this ParameterDict. + restore_prefix : str, default '' + prepend prefix to names of stored parameters before loading + filename : str, default None + cast_dtype : bool, default False + Cast the data type of the NDArray loaded from the checkpoint to the dtype + provided by the Parameter if any + """ + lprefix = len(restore_prefix) loaded = [(k[4:] if k.startswith('arg:') or k.startswith('aux:') else k, v) \ - for k, v in ndarray_load.items()] if isinstance(ndarray_load, dict) else ndarray_load + for k, v in param_dict.items()] if isinstance(param_dict, dict) else param_dict arg_dict = {restore_prefix+k: v for k, v in loaded} + error_str = "file: %s" % (filename) if filename else "param_dict" if not allow_missing: for name in self.keys(): assert name in arg_dict, \ - "Parameter '%s' is missing in file '%s', which contains parameters: %s. " \ + "Parameter '%s' is missing in %s, which contains parameters: %s. " \ "Please make sure source and target networks have the same prefix."%( - name[lprefix:], filename, _brief_print_list(arg_dict.keys())) + name[lprefix:], error_str, _brief_print_list(arg_dict.keys())) for name in arg_dict: if name not in self._params: assert ignore_extra, \ - "Parameter '%s' loaded from file '%s' is not present in ParameterDict, " \ + "Parameter '%s' loaded from %s is not present in ParameterDict, " \ "choices are: %s. Set ignore_extra to True to ignore. " \ "Please make sure source and target networks have the same prefix."%( - name[lprefix:], filename, _brief_print_list(self._params.keys())) + name[lprefix:], error_str, _brief_print_list(self._params.keys())) continue - self[name]._load_init(arg_dict[name], ctx, cast_dtype=cast_dtype, dtype_source=dtype_source) + self[name]._load_init(arg_dict[name], ctx, cast_dtype=cast_dtype, + dtype_source=dtype_source) diff --git a/python/mxnet/module/executor_group.py b/python/mxnet/module/executor_group.py index c4050699bd52..637acce317cc 100755 --- a/python/mxnet/module/executor_group.py +++ b/python/mxnet/module/executor_group.py @@ -26,6 +26,7 @@ from .. import ndarray as nd from ..io import DataDesc from ..executor_manager import _split_input_slice +from ..ndarray import _DTYPE_MX_TO_NP def _load_general(data, targets, major_axis): @@ -651,6 +652,13 @@ def _bind_ith_exec(self, i, data_shapes, label_shapes, shared_group): input_shapes.update(dict(label_shapes)) input_types = {x.name: x.dtype for x in data_shapes} + attr_dict = self.symbol.attr_dict() + + for sym_name in self.symbol.list_inputs(): + if sym_name in input_types and sym_name in attr_dict \ + and "__dtype__" in attr_dict[sym_name] and attr_dict[sym_name]["__dtype__"] != "-1": + input_types[sym_name] = _DTYPE_MX_TO_NP[int(attr_dict[sym_name]["__dtype__"])] + if label_shapes is not None: input_types.update({x.name: x.dtype for x in label_shapes}) diff --git a/python/mxnet/test_utils.py b/python/mxnet/test_utils.py index d247c0fcde95..aa46a9628b81 100644 --- a/python/mxnet/test_utils.py +++ b/python/mxnet/test_utils.py @@ -29,6 +29,7 @@ import logging import bz2 import zipfile +import json from contextlib import contextmanager import numpy as np import numpy.testing as npt @@ -1521,6 +1522,72 @@ def download(url, fname=None, dirname=None, overwrite=False, retries=5): logging.info("downloaded %s into %s successfully", url, fname) return fname +def download_model(model_name, dst_dir='./', meta_info=None): + """Download a model from data.mxnet.io + + Parameters + ---------- + model_name : str + Model name to download + dst_dir : str + Destination Directory to download the model + meta_info : dict of dict + Mapping from model_name to dict of the following structure: + {'symbol': url, 'params': url} + + Returns + ------- + Two element tuple containing model_name and epoch for the params saved + """ + _base_model_url = 'http://data.mxnet.io/models/' + _default_model_info = { + 'imagenet1k-inception-bn': {'symbol':_base_model_url+'imagenet/inception-bn/Inception-BN-symbol.json', + 'params':_base_model_url+'imagenet/inception-bn/Inception-BN-0126.params'}, + 'imagenet1k-resnet-18': {'symbol':_base_model_url+'imagenet/resnet/18-layers/resnet-18-symbol.json', + 'params':_base_model_url+'imagenet/resnet/18-layers/resnet-18-0000.params'}, + 'imagenet1k-resnet-34': {'symbol':_base_model_url+'imagenet/resnet/34-layers/resnet-34-symbol.json', + 'params':_base_model_url+'imagenet/resnet/34-layers/resnet-34-0000.params'}, + 'imagenet1k-resnet-50': {'symbol':_base_model_url+'imagenet/resnet/50-layers/resnet-50-symbol.json', + 'params':_base_model_url+'imagenet/resnet/50-layers/resnet-50-0000.params'}, + 'imagenet1k-resnet-101': {'symbol':_base_model_url+'imagenet/resnet/101-layers/resnet-101-symbol.json', + 'params':_base_model_url+'imagenet/resnet/101-layers/resnet-101-0000.params'}, + 'imagenet1k-resnet-152': {'symbol':_base_model_url+'imagenet/resnet/152-layers/resnet-152-symbol.json', + 'params':_base_model_url+'imagenet/resnet/152-layers/resnet-152-0000.params'}, + 'imagenet1k-resnext-50': {'symbol':_base_model_url+'imagenet/resnext/50-layers/resnext-50-symbol.json', + 'params':_base_model_url+'imagenet/resnext/50-layers/resnext-50-0000.params'}, + 'imagenet1k-resnext-101': {'symbol':_base_model_url+'imagenet/resnext/101-layers/resnext-101-symbol.json', + 'params':_base_model_url+'imagenet/resnext/101-layers/resnext-101-0000.params'}, + 'imagenet1k-resnext-101-64x4d': + {'symbol':_base_model_url+'imagenet/resnext/101-layers/resnext-101-64x4d-symbol.json', + 'params':_base_model_url+'imagenet/resnext/101-layers/resnext-101-64x4d-0000.params'}, + 'imagenet11k-resnet-152': + {'symbol':_base_model_url+'imagenet-11k/resnet-152/resnet-152-symbol.json', + 'params':_base_model_url+'imagenet-11k/resnet-152/resnet-152-0000.params'}, + 'imagenet11k-place365ch-resnet-152': + {'symbol':_base_model_url+'imagenet-11k-place365-ch/resnet-152-symbol.json', + 'params':_base_model_url+'imagenet-11k-place365-ch/resnet-152-0000.params'}, + 'imagenet11k-place365ch-resnet-50': + {'symbol':_base_model_url+'imagenet-11k-place365-ch/resnet-50-symbol.json', + 'params':_base_model_url+'imagenet-11k-place365-ch/resnet-50-0000.params'}, + } + + + if meta_info is None: + meta_info = _default_model_info + meta_info = dict(meta_info) + if model_name not in meta_info: + return (None, 0) + if not os.path.isdir(dst_dir): + os.mkdir(dst_dir) + meta = dict(meta_info[model_name]) + assert 'symbol' in meta, "missing symbol url" + model_name = os.path.join(dst_dir, model_name) + mx.test_utils.download(meta['symbol'], model_name+'-symbol.json') + assert 'params' in meta, "mssing parameter file url" + mx.test_utils.download(meta['params'], model_name+'-0000.params') + return (model_name, 0) + + def get_mnist(): """Download and load the MNIST dataset @@ -2073,6 +2140,22 @@ def compare_optimizer(opt1, opt2, shape, dtype, w_stype='default', g_stype='defa compare_ndarray_tuple(state1, state2, rtol=rtol, atol=atol) assert_almost_equal(w1.asnumpy(), w2.asnumpy(), rtol=rtol, atol=atol) + +def same_symbol_structure(sym1, sym2): + """Compare two symbols to check if they have the same computation graph structure. + Returns true if operator corresponding to a particular node id is same in both + symbols for all nodes + """ + conf = json.loads(sym1.tojson()) + nodes = conf["nodes"] + conf2 = json.loads(sym2.tojson()) + nodes2 = conf2["nodes"] + for node1, node2 in zip(nodes, nodes2): + if node1["op"] != node2["op"]: + return False + return True + + class EnvManager(object): """Environment variable setter and unsetter via with idiom""" def __init__(self, key, val): diff --git a/src/c_api/c_api_symbolic.cc b/src/c_api/c_api_symbolic.cc index 4c6229ee29b0..80ae5438c20d 100644 --- a/src/c_api/c_api_symbolic.cc +++ b/src/c_api/c_api_symbolic.cc @@ -810,6 +810,210 @@ int MXQuantizeSymbol(SymbolHandle sym_handle, API_END_HANDLE_ERROR(delete s); } +// helper function to add mapping of node_name -> dtype map +// for the given indexed graph and inferred_dtypes +static void _SetInputDTypes( + const nnvm::IndexedGraph& idx, + const nnvm::DTypeVector& inferred_dtypes, + std::unordered_map* node_name_dtype_map, + std::unordered_map* node_without_dtype_map) { + const std::string dtype_keyword = "__dtype__"; + for (uint32_t nid : idx.input_nodes()) { + const auto& node = idx[nid].source; + const auto& node_with_dtype = node->attrs.dict.find(dtype_keyword); + // input nodes classified into nodes_with_dtype, nodes_without_dtype + // This classification required because if param_names not provided + // we want to update dtypes of only those nodes which have dtypes set + // inferred_dtypes are obtained for the nodes, if unknown + // dtype is set to fp32 + if (node_with_dtype != node->attrs.dict.end()) { + if (inferred_dtypes[idx.entry_id(nid, 0)] == -1) { + (*node_name_dtype_map)[node->attrs.name] = 0; + } else { + (*node_name_dtype_map)[node->attrs.name] = + inferred_dtypes[idx.entry_id(nid, 0)]; + } + } else { + if (inferred_dtypes[idx.entry_id(nid, 0)] == -1) { + (*node_without_dtype_map)[node->attrs.name] = 0; + } else { + (*node_without_dtype_map)[node->attrs.name] = + inferred_dtypes[idx.entry_id(nid, 0)]; + } + } + } +} + +// helper function update the node dtype attrs for a vector of nodeptrs +// given the node name to dtype information and the names of model_params +// if model_params is provided the function will dtype of only model params. +// if model_params is empty, the function will dtype of all nodes which had +// a prior dtype set. +// args is a const_reference vector of NodePtrs. NodePtrs are immutable but +// the Nodes they are pointing will be mutated in this function +static void _UpdateSymDTypeAttrs( + const std::unordered_map& node_name_dtype_map, + const std::unordered_map& node_without_dtype_map, + const std::unordered_set& model_params, + const std::vector& args) { + const std::string dtype_keyword = "__dtype__"; + + // Update args to have the right dtype attrs + if (model_params.size() > 0) { + // if model params provided, set dtype only for model params + for (size_t i = 0; i < args.size(); ++i) { + const std::string& node_name = args[i]->attrs.name; + auto it_model_params = model_params.find(node_name); + auto it_with_dtype = node_name_dtype_map.find(node_name); + auto it_without_dtype = node_without_dtype_map.find(node_name); + if (it_model_params != model_params.end()) { + // need to update __dtype__ attribute if already set, else set it + if (it_with_dtype != node_name_dtype_map.end()) { + args[i]->attrs.dict[dtype_keyword] = + std::to_string(it_with_dtype->second); + } else { + CHECK(it_without_dtype != node_without_dtype_map.end()) + << "make sure all nodes without dtype have properly been added " + "in node_without_dtype_map"; + args[i]->attrs.dict[dtype_keyword] = + std::to_string(it_without_dtype->second); + } + } + } + } else { + // if model params not provided, update __dtype__ for all inputs, + // which already had it set, don't touch the rest + for (size_t i = 0; i < args.size(); ++i) { + auto it = node_name_dtype_map.find(args[i]->attrs.name); + if (it != node_name_dtype_map.end()) { + if (args[i]->attrs.dict.find(dtype_keyword) != + args[i]->attrs.dict.end()) { + args[i]->attrs.dict[dtype_keyword] = std::to_string(it->second); + } + } + } + } +} + +int MXReducePrecisionSymbol(SymbolHandle sym_handle, + SymbolHandle *ret_sym_handle, + mx_uint num_args, + const int *arg_type_data, + mx_uint num_ind_ptr, + const int* ind_ptr, + const int* target_dtype, + const int cast_optional_params, + const mx_uint num_target_dtype_op_names, + const mx_uint num_fp32_op_names, + const mx_uint num_widest_dtype_op_names, + const mx_uint num_conditional_fp32_op_names, + const mx_uint num_excluded_symbols, + const mx_uint num_model_params, + const char **target_dtype_op_names, + const char **fp32_op_names, + const char **widest_dtype_op_names, + const char **conditional_fp32_op_names, + const char **excluded_symbols, + const char **param_names, + const char **param_vals, + const char **model_param_names, + const char **arg_names) { + nnvm::Symbol *result_sym = new nnvm::Symbol(); + API_BEGIN(); + nnvm::Symbol *sym = static_cast(sym_handle); + nnvm::Graph g = Symbol2Graph(*sym); + std::unordered_set target_dtype_ops; + std::unordered_set fp32_ops; + std::unordered_set widest_dtype_ops; + std::unordered_set excluded_syms; + std::unordered_set model_params; + + // conditional_fp32_ops contains the mapping of op_name -> (map of param_name -> param_values) + // which need to be conditionally selected to be casted to FP32 + std::unordered_map>> conditional_fp32_ops; + int target_dt = *target_dtype; + + for (size_t i = 0; i < num_target_dtype_op_names; ++i) { + target_dtype_ops.emplace(target_dtype_op_names[i]); + } + for (size_t i = 0; i < num_fp32_op_names; ++i) { + fp32_ops.emplace(fp32_op_names[i]); + } + for (size_t i = 0; i < num_widest_dtype_op_names; ++i) { + widest_dtype_ops.emplace(widest_dtype_op_names[i]); + } + for (size_t i = 0; i < num_excluded_symbols; ++i) { + excluded_syms.emplace(excluded_symbols[i]); + } + for (size_t i = 0; i < num_model_params; ++i) { + model_params.emplace(model_param_names[i]); + } + + for (size_t i = 0; i < num_ind_ptr - 1; ++i) { + for (int j = ind_ptr[i]; j < ind_ptr[i + 1]; ++j) { + conditional_fp32_ops[conditional_fp32_op_names[i]][param_names[i]] + .emplace_back(std::string(param_vals[j])); + } + } + + std::unordered_map kwargs; + std::unordered_map node_name_dtype_map, node_without_dtype_map; + nnvm::DTypeVector arg_types(g.indexed_graph().input_nodes().size(), -1); + for (mx_uint i = 0; i < num_args; ++i) { + kwargs[arg_names[i]] = arg_type_data[i]; + node_name_dtype_map[arg_names[i]] = arg_type_data[i]; + } + mxnet::MatchArguments(g.indexed_graph(), kwargs, &arg_types, "InferType"); + + g.attrs["target_dtype_ops"] = + std::make_shared(std::move(target_dtype_ops)); + g.attrs["fp32_ops"] = std::make_shared(std::move(fp32_ops)); + g.attrs["widest_dtype_ops"] = + std::make_shared(std::move(widest_dtype_ops)); + g.attrs["conditional_fp32_ops"] = + std::make_shared(std::move(conditional_fp32_ops)); + g.attrs["excluded_syms"] = + std::make_shared(std::move(excluded_syms)); + g.attrs["target_dtype"] = std::make_shared(target_dt); + + g = ApplyPass(std::move(g), "ReducePrecision"); + // Need to run type inference since it is possible that inferred + // type of some inputs has changed + g = mxnet::exec::InferType(std::move(g), std::move(arg_types), ""); + const nnvm::DTypeVector &inferred_dtypes = + g.GetAttr("dtype"); + + g.attrs["inferred_dtypes"] = std::make_shared(std::move(inferred_dtypes)); + g.attrs["target_dtype"] = std::make_shared(target_dt); + + if (cast_optional_params) { + g = ApplyPass(std::move(g), "AMPInferUnknown"); + const nnvm::DTypeVector &inferred_dtype_result = + g.GetAttr("inferred_dtype_result"); + const nnvm::IndexedGraph &idx = g.indexed_graph(); + // set node name -> input dtype mapping using infer dtype + _SetInputDTypes(idx, inferred_dtype_result, &node_name_dtype_map, &node_without_dtype_map); + } else { + const nnvm::IndexedGraph &idx = g.indexed_graph(); + // set node name -> input dtype mapping using infer dtype + _SetInputDTypes(idx, inferred_dtypes, &node_name_dtype_map, &node_without_dtype_map); + } + + + result_sym->outputs = g.outputs; + *ret_sym_handle = result_sym; + nnvm::Symbol *ret_sym = static_cast(*ret_sym_handle); + const std::vector& args = ret_sym->ListInputs(nnvm::Symbol::kAll); + + // update symbol dtype attrs using the node name -> dtype mapping, if dtype is already set + // in the symbol, else set dtype for the model_params + _UpdateSymDTypeAttrs(node_name_dtype_map, node_without_dtype_map, model_params, args); + + API_END_HANDLE_ERROR(delete result_sym); +} + int MXSetCalibTableToQuantizedSymbol(SymbolHandle qsym_handle, const mx_uint num_layers, const char** layer_names, diff --git a/src/nnvm/amp_infer_unknown.cc b/src/nnvm/amp_infer_unknown.cc new file mode 100644 index 000000000000..1de3104d054f --- /dev/null +++ b/src/nnvm/amp_infer_unknown.cc @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2016 by Contributors + * \file low_precision_pass.cc + * \brief Use the Mixed Precision Model to infer the dtypes of + * unknown input nodes + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/utils.h" +#include "../operator/tensor/amp_cast.h" + +namespace mxnet { +using nnvm::Graph; +using nnvm::NodePtr; +using nnvm::NodeEntry; +using dmlc::any; +using mxnet::op::AMPCastParam; + +// If a var node is not visited, visit it and set inferred_dtype_result as result_dtype, +// If already visited compare the result_dtype with existing inferred_dtype_result +static void CheckAndUpdateInferredDtypes( + const nnvm::DTypeVector &inferred_dtypes, const nnvm::IndexedGraph &idx, + const NodeEntry &node_entry, + mshadow::TypeFlag result_dtype, + std::unordered_map *visited_vars, + nnvm::DTypeVector *inferred_dtype_result) { + const NodePtr &input_node = node_entry.node; + if (!visited_vars->count(input_node->attrs.name)) { + if ((*inferred_dtype_result)[idx.entry_id(node_entry)] == -1) { + (*visited_vars)[input_node->attrs.name] = result_dtype; + (*inferred_dtype_result)[idx.entry_id(node_entry)] = result_dtype; + } + } else { + auto it = visited_vars->find(input_node->attrs.name); + CHECK(it != visited_vars->end()); + if (it->second != result_dtype) { + (*inferred_dtype_result)[idx.entry_id(node_entry)] = + inferred_dtypes[idx.entry_id(node_entry)]; + } + } +} + +// Graph pass to infer unknown nodes which are input nodes +// as FP16 if possible +Graph AMPInferUnknown(Graph &&src) { + const nnvm::DTypeVector &inferred_dtypes = + src.GetAttr("inferred_dtypes"); + const int target_dtype = src.GetAttr("target_dtype"); + CHECK(target_dtype == mshadow::kFloat16) + << "Only float16 target_dtype is supported yet"; + + nnvm::DTypeVector inferred_dtype_result(inferred_dtypes); + const nnvm::IndexedGraph &idx = src.indexed_graph(); + + std::unordered_map visited_vars; + + // Visits all nodes which are amp_cast and amp_multicast, + // and check if inputs to these nodes are variables. + // If input nodes are variables, set dtype for these inputs + // and check for conflicts if an input node goes to two cast nodes + DFSVisit(src.outputs, [&](const NodePtr &node) { + if (!node->is_variable()) { + std::string op_name = node->op()->name; + + if (op_name == "amp_cast") { + // for amp_cast set inferred_dtypes for input_nodes and add + // to visited_vars, if a var is being visited second time + // and already has dtype set, make sure the dtype inferred again + // is same, otherwise reset dtype to original dtype + for (const NodeEntry &node_entry : node->inputs) { + const NodePtr &input_node = node_entry.node; + if (input_node->is_variable() && + (node->attrs.dict.find("dtype") != node->attrs.dict.end())) { + const AMPCastParam ¶m = + nnvm::get(node->attrs.parsed); + CHECK(param.dtype != -1) + << "amp_cast node shouldn't have unknown dtype"; + CheckAndUpdateInferredDtypes(inferred_dtypes, idx, node_entry, + static_cast(param.dtype), + &visited_vars, &inferred_dtype_result); + } + } + } else if (op_name == "amp_multicast") { + // for amp_multicast, for non var input nodes, keep track of biggest dtype. + // If the biggest dtype is same as target_dtype, set this for the input_var nodes + // if it is not already set + mshadow::TypeFlag max_dtype = static_cast(target_dtype); + for (const NodeEntry& node_entry : node->inputs) { + const NodePtr& input_node = node_entry.node; + if (!input_node->is_variable()) { + // if one input is not a variable then don't infer the dtype of other + // input node dtypes + max_dtype = mshadow::kFloat32; + } + } + if (max_dtype == target_dtype) { + for (const NodeEntry &node_entry : node->inputs) { + const NodePtr &input_node = node_entry.node; + if (input_node->is_variable()) { + CheckAndUpdateInferredDtypes(inferred_dtypes, idx, node_entry, + max_dtype, &visited_vars, + &inferred_dtype_result); + } + } + } + } + } + }); + + Graph ret; + ret.attrs["inferred_dtype_result"] = + std::make_shared(std::move(inferred_dtype_result)); + ret.outputs = std::move(src.outputs); + return ret; +} + +NNVM_REGISTER_PASS(AMPInferUnknown) + .describe("Infer dtypes of different nodes for the mixed precision model") + .set_body(AMPInferUnknown) + .set_change_graph(true) + .provide_graph_attr("inferred_dtypes"); +} // namespace mxnet diff --git a/src/nnvm/low_precision_pass.cc b/src/nnvm/low_precision_pass.cc new file mode 100644 index 000000000000..7cd0178108f4 --- /dev/null +++ b/src/nnvm/low_precision_pass.cc @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2016 by Contributors + * \file low_precision_pass.cc + * \brief Return new graph with amp_cast and amp_multicast operators added wherever required + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace mxnet { +using nnvm::Symbol; +using nnvm::Node; +using nnvm::NodePtr; +using nnvm::NodeEntry; +using nnvm::Graph; + +// create a node for operator : op_name with name : node_name +static NodePtr CreateNode(std::string op_name, std::string node_name) { + NodePtr node = Node::Create(); + node->attrs.name = node_name; + if (op_name == "nullptr") { + node->attrs.op = nullptr; + // ugly workaround because VariableParam is not exposed + node->attrs.parsed = nnvm::Symbol::CreateVariable(node->attrs.name) + .outputs[0] + .node->attrs.parsed; + } else { + node->attrs.op = Op::Get(op_name); + } + return node; +} + +static NodePtr InsertNode(std::string op_name, std::string node_name, NodePtr current, + NodeEntry previous) { + NodePtr node = CreateNode(op_name, node_name); + node->inputs.emplace_back(previous); + current->inputs.emplace_back(NodeEntry{node, 0, 0}); + return node; +} + +// get suffix for a node entry so that it can be used for amp_cast/amp_multicast node name +static std::string GetSuffix(const nnvm::NodeEntry &node_entry, + const std::unordered_map &mirror_map) { + static const auto &flist_outputs = + nnvm::Op::GetAttr("FListOutputNames"); + std::string suffix = ""; + NodePtr mirror_node = mirror_map.at(node_entry.node.get()); + if (mirror_node->op() != nullptr) { + auto list_output_names_func = flist_outputs.get(node_entry.node->op(), nullptr); + if (list_output_names_func != nullptr) { + std::vector names = list_output_names_func(node_entry.node->attrs); + suffix = "_" + names[node_entry.index]; + } else { + suffix = "_" + std::to_string(node_entry.index); + } + } + return suffix; +} + +// add amp_cast node between curr_node and input +static void AddCastNode(const nnvm::NodeEntry &e, const std::string &suffix, + const nnvm::NodeEntry &input, const std::string dtype, + nnvm::NodeEntryMap *mirror_entry_map, + NodePtr curr_node) { + NodePtr cast_node = + InsertNode("amp_cast", e.node->attrs.name + suffix + "_amp_cast_" + dtype, + curr_node, input); + cast_node->attrs.dict["dtype"] = dtype; + cast_node->op()->attr_parser(&(cast_node->attrs)); + (*mirror_entry_map)[e] = NodeEntry{std::move(cast_node), 0, e.version}; + return; +} + +// add amp_multicast node between curr_node and inputs +static void AddMultiCastNode(const std::vector &inputs, + const std::string &node_name, + const std::unordered_map &mirror_map, + NodePtr curr_node) { + NodePtr node = + CreateNode("amp_multicast", + inputs[0].node->attrs.name + node_name + "_amp_multicast"); + for (const auto &node_entry : inputs) { + NodePtr mirror_node = mirror_map.at(node_entry.node.get()); + NodeEntry mirror_entry = NodeEntry{std::move(mirror_node), node_entry.index, + node_entry.version}; + node->inputs.emplace_back(mirror_entry); + } + node->attrs.dict["num_outputs"] = std::to_string(inputs.size()); + node->op()->attr_parser(&(node->attrs)); + for (uint32_t i = 0; i < inputs.size(); ++i) { + const auto &e = inputs[i]; + curr_node->inputs.emplace_back( + NodeEntry{node, static_cast(i), e.version}); + } + return; +} + +static bool CheckConditionalFP32( + const std::unordered_map< + std::string, std::unordered_map>> + &conditional_fp32_ops, + const std::unordered_set &excluded_syms, NodePtr node) { + if (node->is_variable() || (excluded_syms.count(node->attrs.name) > 0) || + conditional_fp32_ops.count(node->op()->name) == 0) { + return false; + } else { + // Iterate through all conditional ops + auto it = conditional_fp32_ops.find(node->op()->name); + if (it != conditional_fp32_ops.end()) { + auto it_params = it->second; + // For each param name, iterate through param values to check + // if the provided param name is equal to any of the values + for (auto it_param = it_params.begin(); it_param != it_params.end(); + it_param++) { + auto param_key = node->attrs.dict.find(it_param->first); + if (param_key != node->attrs.dict.end()) { + auto it_param_vals = it_param->second; + if (std::find(it_param_vals.begin(), it_param_vals.end(), + param_key->second) != it_param_vals.end()) { + return true; + } + } + } + } + return false; + } +} + +Graph ReducePrecision(Graph &&src) { + const auto target_dtype_ops = + src.GetAttr>("target_dtype_ops"); + const auto fp32_ops = + src.GetAttr>("fp32_ops"); + const auto widest_dtype_ops = + src.GetAttr>("widest_dtype_ops"); + const auto target_dtype = src.GetAttr("target_dtype"); + const auto excluded_syms = src.GetAttr>("excluded_syms"); + const auto conditional_fp32_ops = src.GetAttr>>>( + "conditional_fp32_ops"); + + CHECK(target_dtype == mshadow::kFloat16) + << "Only float16 target_dtype is supported yet"; + + // Additional data structures to share common cast node inputs among different nodes + std::unordered_map mirror_map; + nnvm::NodeEntryMap mirror_fp32_map; + nnvm::NodeEntryMap mirror_target_dtype_map; + + // Visit nodes in a topologically sorted order + DFSVisit(src.outputs, [&](const NodePtr &node) { + NodePtr new_node = Node::Create(*node); + new_node->inputs.clear(); + + /* 1. for node which needs to run in FP32 mode, add amp_cast operators + * (to fp32) after its inputs + * 2. for node which needs to run in FP16 mode, add amp_cast operators + * (to target_dtype) after its inputs + * 3. for nodes which need to run in widest dtype among its inputs, add + * amp_multicast operators between op and its inputs + * 4. for nodes which need to run in FP32 mode, based on a specific condition, + * check the condition, and if true add amp_cast (to fp32) after its inputs + * 4. for other nodes, create copy node and add it to the mirror_map + */ + if (!node->is_variable() && fp32_ops.count(node->op()->name) > 0 && + excluded_syms.count(node->attrs.name) == 0) { + for (const auto& node_entry : node->inputs) { + if (mirror_fp32_map.count(node_entry)) { + new_node->inputs.emplace_back(mirror_fp32_map[node_entry]); + } else { + NodePtr mirror_node = mirror_map.at(node_entry.node.get()); + NodeEntry mirror_entry = NodeEntry{mirror_node, node_entry.index, node_entry.version}; + std::string suffix = GetSuffix(node_entry, mirror_map); + AddCastNode(node_entry, suffix, mirror_entry, "float32", &mirror_fp32_map, + new_node); + } + } + } else if (!node->is_variable() && + target_dtype_ops.count(node->op()->name) > 0 && + excluded_syms.count(node->attrs.name) == 0) { + for (const auto& node_entry : node->inputs) { + if (mirror_target_dtype_map.count(node_entry)) { + new_node->inputs.emplace_back(mirror_target_dtype_map[node_entry]); + } else { + NodePtr mirror_node = mirror_map.at(node_entry.node.get()); + NodeEntry mirror_entry = NodeEntry{mirror_node, node_entry.index, node_entry.version}; + std::string suffix = GetSuffix(node_entry, mirror_map); + AddCastNode(node_entry, suffix, mirror_entry, "float16", + &mirror_target_dtype_map, new_node); + } + } + } else if (!node->is_variable() && + widest_dtype_ops.count(node->op()->name) > 0 && + excluded_syms.count(node->attrs.name) == 0) { + CHECK(node->inputs.size() > 0) + << "Please check the symbol. node name: " << node->attrs.name + << "op name " << node->op()->name << " has no inputs." + << "It is likely that something went wrong during symbolic construction."; + const auto &e = node->inputs[0]; + std::string suffix = GetSuffix(e, mirror_map); + AddMultiCastNode(node->inputs, suffix, mirror_map, new_node); + } else if (CheckConditionalFP32(conditional_fp32_ops, excluded_syms, node)) { + for (const auto& node_entry : node->inputs) { + if (mirror_fp32_map.count(node_entry)) { + new_node->inputs.emplace_back(mirror_fp32_map[node_entry]); + } else { + NodePtr mirror_node = mirror_map.at(node_entry.node.get()); + NodeEntry mirror_entry = NodeEntry{mirror_node, node_entry.index, node_entry.version}; + std::string suffix = GetSuffix(node_entry, mirror_map); + AddCastNode(node_entry, suffix, mirror_entry, "float32", &mirror_fp32_map, + new_node); + } + } + } else { + for (const auto& node_entry : node->inputs) { + NodePtr mirror_node = mirror_map.at(node_entry.node.get()); + new_node->inputs.emplace_back(mirror_node, node_entry.index, node_entry.version); + } + } + mirror_map[node.get()] = std::move(new_node); + }); + + std::vector outputs; + for (const auto& e : src.outputs) { + outputs.emplace_back(mirror_map.at(e.node.get()), e.index, e.version); + } + + Graph ret; + ret.outputs = std::move(outputs); + return ret; +} + +NNVM_REGISTER_PASS(ReducePrecision) + .describe("add cast layers for low precision inference") + .set_body(ReducePrecision) + .set_change_graph(true); +} // namespace mxnet diff --git a/tests/python/gpu/test_contrib_amp.py b/tests/python/gpu/test_contrib_amp.py new file mode 100644 index 000000000000..7927cc99160b --- /dev/null +++ b/tests/python/gpu/test_contrib_amp.py @@ -0,0 +1,428 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import os +import sys +import mxnet as mx +import numpy as np +import warnings +import collections +import ctypes +import mxnet.contrib.amp as amp +from nose.tools import assert_raises +from mxnet.test_utils import set_default_context, download_model, same_symbol_structure +from mxnet.gluon.model_zoo.vision import get_model +from mxnet.gluon import SymbolBlock +from mxnet.contrib.amp import amp +curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) +sys.path.insert(0, os.path.join(curr_path, '../unittest')) +from common import with_seed, teardown + +def test_amp_coverage(): + conditional = [item[0] for item in amp.lists.symbol.CONDITIONAL_FP32_FUNCS] + + # Check for duplicates + for a in [amp.lists.symbol.FP16_FUNCS, + amp.lists.symbol.FP16_FP32_FUNCS, + amp.lists.symbol.FP32_FUNCS, + amp.lists.symbol.WIDEST_TYPE_CASTS, + conditional]: + ret = [item for item, count in collections.Counter(a).items() if count > 1] + assert ret == [], "Elements " + str(ret) + " are duplicated in the AMP lists." + + t = [] + for a in [amp.lists.symbol.FP16_FUNCS, + amp.lists.symbol.FP16_FP32_FUNCS, + amp.lists.symbol.FP32_FUNCS, + amp.lists.symbol.WIDEST_TYPE_CASTS, + conditional]: + t += a + ret = [item for item, count in collections.Counter(t).items() if count > 1] + assert ret == [], "Elements " + str(ret) + " exist in more than 1 AMP list." + + # Check the coverage + py_str = lambda x: x.decode('utf-8') + + plist = ctypes.POINTER(ctypes.c_char_p)() + size = ctypes.c_uint() + + mx.base._LIB.MXListAllOpNames(ctypes.byref(size), + ctypes.byref(plist)) + op_names = [] + for i in range(size.value): + s = py_str(plist[i]) + if not s.startswith("_backward") \ + and not s.startswith("_contrib_backward_"): + op_names.append(s) + + ret1 = set(op_names) - set(t) + + if ret1 != set(): + warnings.warn("Operators " + str(ret1) + " do not exist in AMP lists (in " + "python/mxnet/contrib/amp/lists/symbol.py) - please add them. " + """Please follow these guidelines for choosing a proper list: + - if your operator is not to be used in a computational graph + (e.g. image manipulation operators, optimizers) or does not have + inputs, put it in FP16_FP32_FUNCS list, + - if your operator requires FP32 inputs or is not safe to use with lower + precision, put it in FP32_FUNCS list, + - if your operator supports both FP32 and lower precision, has + multiple inputs and expects all inputs to be of the same + type, put it in WIDEST_TYPE_CASTS list, + - if your operator supports both FP32 and lower precision and has + either a single input or supports inputs of different type, + put it in FP16_FP32_FUNCS list, + - if your operator is both safe to use in lower precision and + it is highly beneficial to use it in lower precision, then + put it in FP16_FUNCS (this is unlikely for new operators) + - If you are not sure which list to choose, FP32_FUNCS is the + safest option""") + +@with_seed() +def test_amp_conversion(): + def check_amp_convert_symbol(): + x = mx.sym.var("x") + y = mx.sym.var("y") + z = mx.sym.FullyConnected(x, y, num_hidden=10, no_bias=True) + siny = mx.sym.sin(y) + res = z + siny + # Compare symbols with similar computation graphs created using convert_symbol and manually. + res_converted = amp.convert_symbol(res, target_dtype="float16", + target_dtype_ops=["FullyConnected"], + fp32_ops=["sin"]) + + x_fp16 = mx.sym.amp_cast(x, dtype="float16") + y_fp16 = mx.sym.amp_cast(y, dtype="float16") + amp_casted_siny = mx.sym.sin(mx.sym.amp_cast(y, dtype="float32")) + z = mx.sym.FullyConnected(x_fp16, y_fp16, num_hidden=10, no_bias=True) + outs = mx.sym.amp_multicast(z, amp_casted_siny, num_outputs=2) + res_expected = outs[0] + outs[1] + assert same_symbol_structure(res_converted, res_expected), \ + "convert_symbol generating wrong computation graph" + + # convert_symbol called with incorrect inputs + assert_raises(AssertionError, amp.convert_symbol, res, + target_dtype="float16", target_dtype_ops=["FullyConnected"], + fp32_ops=["elemwise_add"]) + assert_raises(AssertionError, amp.convert_symbol, res, + target_dtype="float16", target_dtype_ops=["FullyConnected"], + fp32_ops=["Activation"], + conditional_fp32_ops=[('Activation', 'act_type', ['selu'])]) + assert_raises(AssertionError, amp.convert_symbol, res, + target_dtype="float16", target_dtype_ops=["Activation"], + fp32_ops=["Activation"], + conditional_fp32_ops=[('Activation', 'act_type', ['selu'])]) + assert_raises(AssertionError, amp.convert_symbol, res, + target_dtype="float16", target_dtype_ops=["FullyConnected"], + fp32_ops=["FullyConnected"]) + + # Test for op in conditional ops with condition not satisfied + x = mx.sym.var("x") + y = mx.sym.var("y") + fc_cond = mx.sym.FullyConnected(x, y, num_hidden=10, no_bias=True) + res_converted = amp.convert_symbol(fc_cond, target_dtype="float16", + target_dtype_ops=[], + fp32_ops=["sin"], + conditional_fp32_ops=[("FullyConnected", "no_bias", ["False"])]) + + res_expected = mx.sym.FullyConnected(x, y, num_hidden=10, no_bias=True) + assert same_symbol_structure(res_converted, res_expected), \ + "convert_symbol generating wrong computation graph when conditional ops is used" + + # Test for op in conditional ops with condition satisfied + res_converted = amp.convert_symbol(fc_cond, target_dtype="float16", target_dtype_ops=[], + fp32_ops=["sin"], + conditional_fp32_ops=[("FullyConnected", "no_bias", ["True"])]) + x_fp32 = mx.sym.amp_cast(x, dtype="float32") + y_fp32 = mx.sym.amp_cast(y, dtype="float32") + res_expected = mx.sym.FullyConnected(x_fp32, y_fp32, num_hidden=10, no_bias=True) + assert same_symbol_structure(res_converted, res_expected), \ + "convert_symbol generating wrong computation graph when conditional ops used with satisfying condition" + + # Test with a real world model, default inputs for convert_symbol + dir_path = os.path.dirname(os.path.realpath(__file__)) + model_path = os.path.join(dir_path, 'model') + if not os.path.isdir(model_path): + os.mkdir(model_path) + + prefix, epoch = download_model("imagenet1k-resnet-18", dst_dir=model_path) + sym, arg_params, aux_params = mx.model.load_checkpoint(prefix, epoch) + inputs = {} + inputs['data'] = mx.nd.ones((1, 3, 224, 224)) + inputs.update(arg_params) + converted_sym = amp.convert_symbol(sym) + exe = converted_sym.simple_bind(mx.gpu(0), data=(1, 3, 224, 224), grad_req='null') + exe.forward(is_train=False, **inputs) + exe.outputs[0].asnumpy() + + inputs2 = {} + inputs2['data'] = mx.nd.ones((1, 3, 224, 224)) + inputs2['fc1_weight'] = inputs['fc1_weight'].astype(np.float16) + inputs2['fc1_bias'] = inputs['fc1_bias'].astype(np.float16) + + # Test with a real world model, tweak inputs for convert_symbol + converted_sym = amp.convert_symbol(sym, target_dtype="float16", + target_dtype_ops=["Convolution"], data_names=["data"], + cast_optional_params=True) + converted_sym2 = amp.convert_symbol(sym, target_dtype="float16", + target_dtype_ops=["Convolution"], data_names=["data"], + cast_optional_params=False) + + exe = converted_sym.simple_bind(mx.gpu(0), data=(1, 3, 224, 224), grad_req='null') + exe2 = converted_sym2.simple_bind(mx.gpu(), data=(1, 3, 224, 224), grad_req='null') + + converted_args = converted_sym.list_arguments() + converted_auxs = converted_sym.list_auxiliary_states() + for i, key in enumerate(exe.arg_arrays): + if converted_args[i] in arg_params: + arg_params[converted_args[i]] = arg_params[converted_args[i]].astype(exe.arg_arrays[i].dtype) + for i, key in enumerate(exe.aux_arrays): + if converted_auxs[i] in aux_params: + aux_params[converted_auxs[i]] = aux_params[converted_auxs[i]].astype(exe.aux_arrays[i].dtype) + + inputs2.update(arg_params) + exe.forward(is_train=False, **inputs2) + exe.outputs[0].wait_to_read() + + inputs['fc1_weight'] = inputs['fc1_weight'].astype(np.float16) + inputs['fc1_bias'] = inputs['fc1_bias'].astype(np.float16) + exe2.forward(is_train=False, **inputs) + exe2.outputs[0].wait_to_read() + + + def check_amp_convert_model(): + # Test with real world model, default inputs for convert_model + dir_path = os.path.dirname(os.path.realpath(__file__)) + model_path = os.path.join(dir_path, 'model') + if not os.path.isdir(model_path): + os.mkdir(model_path) + prefix, epoch = download_model("imagenet1k-resnet-18", dst_dir=model_path) + + sym, arg_params, aux_params = mx.model.load_checkpoint(prefix, epoch) + + # Test with real world model, tweak inputs for convert_model + result_sym, result_arg_params, result_aux_params = amp.convert_model(sym, + arg_params, + aux_params, + target_dtype="float16", + target_dtype_ops=["Convolution"]) + mod = mx.mod.Module(result_sym, data_names=["data"], label_names=["softmax_label"], context=mx.gpu()) + mod.bind(data_shapes=[['data', (1, 3, 224, 224)]], label_shapes=[['softmax_label', (1,)]]) + + mod.set_params(result_arg_params, result_aux_params) + mod.forward(mx.io.DataBatch(data=[mx.nd.ones((1, 3, 224, 224))], + label=[mx.nd.ones((1,))])) + mod.get_outputs()[0].asnumpy() + assert mod._arg_params["stage2_unit1_conv2_weight"].dtype == np.float32 + + # Call convert_model with cast_optional_params set to True + result_sym, result_arg_params, result_aux_params = amp.convert_model(sym, + arg_params, + aux_params, + target_dtype="float16", + target_dtype_ops=["Convolution"], cast_optional_params=True) + mod = mx.mod.Module(result_sym, data_names=["data"], label_names=["softmax_label"], context=mx.gpu()) + mod.bind(data_shapes=[['data', (1, 3, 224, 224)]], label_shapes=[['softmax_label', (1,)]]) + mod.set_params(result_arg_params, result_aux_params) + mod.forward(mx.io.DataBatch(data=[mx.nd.ones((1, 3, 224, 224))], + label=[mx.nd.ones((1,))])) + mod.get_outputs()[0].asnumpy() + assert mod._arg_params["stage2_unit1_conv2_weight"].dtype == np.float16 + + + def check_amp_convert_hybrid_block(): + # Test conversion for hybrid block on CPU + model_cpu = get_model("resnet50_v1") + model_cpu.collect_params().initialize(ctx=mx.cpu()) + model_cpu.hybridize() + model_cpu(mx.nd.random.uniform(0, 1, shape=(1, 3, 224, 224), ctx=mx.cpu())) + converted_model_cpu = amp.convert_hybrid_block(model_cpu) + + # Test with real world model, default inputs for convert_hybrid_block + model = get_model("resnet50_v1") + model.collect_params().initialize(ctx=mx.gpu()) + model.hybridize() + model(mx.nd.zeros((1, 3, 224, 224))) + converted_model = amp.convert_hybrid_block(model) + result = converted_model.forward(mx.nd.zeros((1, 3, 224, 224), + dtype=np.float32)) + result = converted_model.forward(mx.nd.zeros((1, 3, 224, 224), + dtype=np.float32)) + + # Test with real world model, tweak inputs for convert_hybrid_block + converted_model = amp.convert_hybrid_block(model, target_dtype="float16", + target_dtype_ops=["Convolution"]) + result = converted_model.forward(mx.nd.zeros((1, 3, 224, 224), + dtype=np.float32)) + result = converted_model.forward(mx.nd.zeros((1, 3, 224, 224), + dtype=np.float32)) + + # Check symbolic block + dir_path = os.path.dirname(os.path.realpath(__file__)) + model_path = os.path.join(dir_path, 'model') + if not os.path.isdir(model_path): + os.mkdir(model_path) + prefix, epoch = download_model("imagenet1k-resnet-18", dst_dir=model_path) + net = SymbolBlock.imports(os.path.join(model_path, "imagenet1k-resnet-18-symbol.json"), + input_names=["data", "softmax_label"], + param_file=os.path.join(model_path, "imagenet1k-resnet-18-0000.params")) + net.collect_params().reset_ctx(ctx=mx.gpu()) + net.hybridize() + net(mx.nd.zeros((1, 3, 224, 224)), mx.nd.zeros((1,))) + converted_model = amp.convert_hybrid_block(net) + result = converted_model.forward(mx.nd.zeros((1, 3, 224, 224)), mx.nd.zeros((1,))) + result = converted_model.forward(mx.nd.zeros((1, 3, 224, 224)), mx.nd.zeros((1,))) + + # Check symbolic block, tweaked inputs + converted_model = amp.convert_hybrid_block(net, target_dtype="float16", target_dtype_ops=["Convolution"]) + result = converted_model.forward(mx.nd.zeros((1, 3, 224, 224)), mx.nd.zeros((1, ))) + result = converted_model.forward(mx.nd.zeros((1, 3, 224, 224)), mx.nd.zeros((1, ))) + params = converted_model.collect_params() + assert params["stage2_unit1_conv2_weight"].dtype == np.float32 + + # Pass cast_optional_params as True to convert_hybrid_block + converted_model = amp.convert_hybrid_block(net, target_dtype="float16", target_dtype_ops=["Convolution"], + cast_optional_params=True) + params = converted_model.collect_params() + assert params["stage2_unit1_conv2_weight"].dtype == np.float16 + + with mx.Context(mx.gpu(0)): + check_amp_convert_symbol() + check_amp_convert_model() + check_amp_convert_hybrid_block() + + +@with_seed() +def test_module_backward_compatibility(): + channel_num = 10 + conv_layer_filter_dims = [2, 3] + conv_layer_strides = [1, 1] + dimension = 5 + data_len = 10 + + data = mx.sym.var("data") + conv = mx.sym.Convolution(data, + num_filter=channel_num, + kernel=tuple(conv_layer_filter_dims), + stride=tuple(conv_layer_strides)) + + bn = mx.sym.BatchNorm(conv, + eps=0.001, + momentum=0.9, + fix_gamma=False, + use_global_stats=False, + output_mean_var=False, + name="conv0_batchnorm") + fc = mx.sym.FullyConnected(bn, num_hidden=10, name="fullyconnected") + mod = mx.mod.Module(fc, data_names=["data"], context=mx.gpu(0)) + mod.bind(data_shapes=[['data', (1, 3, 224, 224)]]) + mod.init_params() + + arg_params, aux_params = mod.get_params() + for param_key, param_val in arg_params.items(): + assert param_val.dtype == np.float32, "Incorrect inference type for arg_params," \ + "please check simple_bind for module executor" + for param_key, param_val in aux_params.items(): + assert param_val.dtype == np.float32, "Incorrect inference type for aux_params," \ + "please check simple_bind for module executor" + + + sym, arg_params, aux_params = amp.convert_model(mod._symbol, mod._arg_params, mod._aux_params, target_dtype_ops=["Convolution"]) + mod = mx.mod.Module(sym, data_names=["data"], context=mx.gpu(0)) + mod.bind(data_shapes=[['data', (1, 3, 224, 224)]]) + mod.set_params(arg_params, aux_params) + assert arg_params["fullyconnected_weight"].dtype == np.float16, \ + "Module API is overwriting the inferred dtype for a mixed precision model" + + +@with_seed() +def test_fp16_casting(): + data = mx.sym.var("data") + out1 = mx.sym.amp_cast(data, dtype="float16") + out2 = mx.sym.amp_cast(data, dtype="float32") + out3 = mx.sym.amp_cast(data, dtype="float16") + # When two ops from data, with different dtypes, + # data should be float32 + res = mx.sym.Group([out1, out2]) + final_res = amp.convert_symbol(res, data_names=[], cast_optional_params=True) + exe = final_res.simple_bind(ctx=mx.gpu(), data=(1, 2)) + assert exe.arg_arrays[0].dtype == np.float32 + + # When two ops from data, both casted to float16, + # data should be float16 + res = mx.sym.Group([out1, out3]) + final_res = amp.convert_symbol(res, data_names=[], cast_optional_params=True) + exe = final_res.simple_bind(ctx=mx.gpu(), data=(1, 2)) + assert exe.arg_arrays[0].dtype == np.float16 + + # AMP Multicast test where one node is float32, another is float16 + data = mx.sym.var("data", dtype=np.float32) + data2 = mx.sym.var("data2", dtype=np.float16) + out4 = mx.sym.amp_multicast(data, data2, num_outputs=2) + final_res = amp.convert_symbol(out4, cast_optional_params=True) + exe = final_res.simple_bind(ctx=mx.gpu(), data2=(1, 2), data=(1, 2)) + assert exe.arg_arrays[0].dtype == np.float16 + + # AMP Multicast test where two non input nodes are float16, + # and one input node is float32 + data = mx.sym.var("data", dtype=np.float32) + data2 = mx.sym.var("data2", dtype=np.float16) + data3 = mx.sym.var("data3", dtype=np.float16) + out5 = mx.sym.amp_multicast(data, + mx.sym.elemwise_add(data2, data3), + num_outputs=2) + final_res = amp.convert_symbol(out5, target_dtype_ops=[], + fp32_ops=[], cast_optional_params=True) + exe = final_res.simple_bind(ctx=mx.gpu(), data=(1, 2), data2=(1, 2), data3=(1, 2)) + assert exe.arg_arrays[0].dtype == np.float32 + + # AMP Multicast test where three input nodes one fp16, one fp32 + # one unknown + data = mx.sym.var("data", dtype=np.float16) + data2 = mx.sym.var("data2", dtype=np.float32) + data3 = mx.sym.var("data3") + out6 = mx.sym.amp_multicast(data, data2, data3, num_outputs=3) + final_res = amp.convert_symbol(out6, target_dtype_ops=[], + fp32_ops=[], cast_optional_params=True) + exe = final_res.simple_bind(ctx=mx.gpu(), data=(1, 2), data2=(1, 2), + data3=(1, 2)) + assert exe.arg_arrays[2].dtype == np.float32 + + # Input node to amp_multicast and amp_cast, if dtypes conflict + # and input node is already fp16, it should still be fp16 + data = mx.sym.var("data", dtype=np.float16) + data2 = mx.sym.var("data2", dtype=np.float32) + out7 = mx.sym.Group([mx.sym.amp_multicast(data, data2, num_outputs=2), mx.sym.amp_cast(data, dtype="float16")]) + final_res = amp.convert_symbol(out7, target_dtype_ops=[], + fp32_ops=[], cast_optional_params=True) + exe = final_res.simple_bind(ctx=mx.gpu(), data=(1, 2), data2=(1, 2)) + assert exe.arg_arrays[0].dtype == np.float16 + + # Input node to amp_multicast and amp_cast, if dtypes conflict + # and input node is already fp32, it should be changed to fp16 + data = mx.sym.var("data", dtype=np.float32) + data2 = mx.sym.var("data2", dtype=np.float16) + out8 = mx.sym.Group([mx.sym.amp_multicast(data, data2, num_outputs=2), mx.sym.amp_cast(data, dtype="float16")]) + final_res = amp.convert_symbol(out8, target_dtype_ops=[], + fp32_ops=[], cast_optional_params=True) + exe = final_res.simple_bind(ctx=mx.gpu(), data=(1, 2), data2=(1, 2)) + assert exe.arg_arrays[0].dtype == np.float16 + + +if __name__ == '__main__': + import nose + nose.runmodule() diff --git a/tests/python/tensorrt/test_tensorrt_lenet5.py b/tests/python/tensorrt/test_tensorrt_lenet5.py index ce88b9de3f5c..d105d6517887 100644 --- a/tests/python/tensorrt/test_tensorrt_lenet5.py +++ b/tests/python/tensorrt/test_tensorrt_lenet5.py @@ -46,7 +46,7 @@ def run_inference(sym, arg_params, aux_params, mnist, all_test_labels, batch_siz # Get this value from all_test_labels # Also get classes from the dataset num_ex = 10000 - all_preds = np.zeros([num_ex, 10]) + all_preds = np.zeros([num_ex, 10], dtype=np.float32) test_iter = mx.io.NDArrayIter(mnist['test_data'], mnist['test_label'], batch_size) example_ct = 0 diff --git a/tests/python/unittest/test_contrib_amp.py b/tests/python/unittest/test_contrib_amp.py deleted file mode 100644 index 13048c35371e..000000000000 --- a/tests/python/unittest/test_contrib_amp.py +++ /dev/null @@ -1,85 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -import mxnet as mx -import warnings -import collections -import ctypes -import mxnet.contrib.amp as amp - -def test_amp_coverage(): - conditional = [item[0] for item in amp.lists.symbol.CONDITIONAL_FP32_FUNCS] - - # Check for duplicates - for a in [amp.lists.symbol.FP16_FUNCS, - amp.lists.symbol.FP16_FP32_FUNCS, - amp.lists.symbol.FP32_FUNCS, - amp.lists.symbol.WIDEST_TYPE_CASTS, - conditional]: - ret = [item for item, count in collections.Counter(a).items() if count > 1] - assert ret == [], "Elements " + str(ret) + " are duplicated in the AMP lists." - - t = [] - for a in [amp.lists.symbol.FP16_FUNCS, - amp.lists.symbol.FP16_FP32_FUNCS, - amp.lists.symbol.FP32_FUNCS, - amp.lists.symbol.WIDEST_TYPE_CASTS, - conditional]: - t += a - ret = [item for item, count in collections.Counter(t).items() if count > 1] - assert ret == [], "Elements " + str(ret) + " exist in more than 1 AMP list." - - # Check the coverage - py_str = lambda x: x.decode('utf-8') - - plist = ctypes.POINTER(ctypes.c_char_p)() - size = ctypes.c_uint() - - mx.base._LIB.MXListAllOpNames(ctypes.byref(size), - ctypes.byref(plist)) - op_names = [] - for i in range(size.value): - s = py_str(plist[i]) - if not s.startswith("_backward") \ - and not s.startswith("_contrib_backward_"): - op_names.append(s) - - ret1 = set(op_names) - set(t) - - if ret1 != set(): - warnings.warn("Operators " + str(ret1) + " do not exist in AMP lists (in " - "python/mxnet/contrib/amp/lists/symbol.py) - please add them. " - """Please follow these guidelines for choosing a proper list: - - if your operator is not to be used in a computational graph - (e.g. image manipulation operators, optimizers) or does not have - inputs, put it in FP16_FP32_FUNCS list, - - if your operator requires FP32 inputs or is not safe to use with lower - precision, put it in FP32_FUNCS list, - - if your operator supports both FP32 and lower precision, has - multiple inputs and expects all inputs to be of the same - type, put it in WIDEST_TYPE_CASTS list, - - if your operator supports both FP32 and lower precision and has - either a single input or supports inputs of different type, - put it in FP16_FP32_FUNCS list, - - if your operator is both safe to use in lower precision and - it is highly beneficial to use it in lower precision, then - put it in FP16_FUNCS (this is unlikely for new operators) - - If you are not sure which list to choose, FP32_FUNCS is the - safest option""") - -if __name__ == '__main__': - test_amp_coverage() From 06df38c5e6582be119879b0988b3c90d5e2b76b2 Mon Sep 17 00:00:00 2001 From: Zhennan Qin Date: Sat, 29 Jun 2019 15:06:04 +0800 Subject: [PATCH 023/813] point fix the vector declaration in MultiBoxDetection (#15300) * point fix the vector declaration in MultiBoxDetection * Rerun ci * run ci * CI --- src/operator/contrib/multibox_detection.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/operator/contrib/multibox_detection.cc b/src/operator/contrib/multibox_detection.cc index 65fe5f1208bb..37bb5a500d71 100644 --- a/src/operator/contrib/multibox_detection.cc +++ b/src/operator/contrib/multibox_detection.cc @@ -98,8 +98,7 @@ inline void MultiBoxDetectionForward(const Tensor &out, const DType *p_anchor = anchors.dptr_; const int omp_threads = mxnet::engine::OpenMP::Get()->GetRecommendedOMPThreadCount(); - std::vector outputs; - outputs.reserve(num_anchors * 6); + std::vector outputs(num_anchors * 6); for (int nbatch = 0; nbatch < num_batches; ++nbatch) { const DType *p_cls_prob = cls_prob.dptr_ + nbatch * num_classes * num_anchors; const DType *p_loc_pred = loc_pred.dptr_ + nbatch * num_anchors * 4; From b869ecd65fe4779f31845993c8d5744e5343ec49 Mon Sep 17 00:00:00 2001 From: Alexander Chalk Date: Sun, 30 Jun 2019 15:58:35 -0400 Subject: [PATCH 024/813] [Clojure] Add fastText example (#15340) * Add fastText to CNN text classification examples * Update repl running instructions * Complete solution with OOM workaround * Complete solution with smaller fastText dataset * Add approx validation accuracy to readme * Add threading macro * Use consistent fasttext casing * Add bangs to io reader functions * Reference default context setting in readme * Change fasttext references in readme * Add data fetching shellscript for fasttext --- .../cnn-text-classification/README.md | 22 +++++++++-- .../get_fasttext_data.sh | 24 ++++++++++++ .../cnn_text_classification/data_helper.clj | 37 ++++++++++++++----- 3 files changed, 69 insertions(+), 14 deletions(-) create mode 100755 contrib/clojure-package/examples/cnn-text-classification/get_fasttext_data.sh diff --git a/contrib/clojure-package/examples/cnn-text-classification/README.md b/contrib/clojure-package/examples/cnn-text-classification/README.md index f2ed939bee16..8f8e6200ec7c 100644 --- a/contrib/clojure-package/examples/cnn-text-classification/README.md +++ b/contrib/clojure-package/examples/cnn-text-classification/README.md @@ -29,8 +29,7 @@ You also must download the glove word embeddings. The suggested one to use is th ## Usage You can run through the repl with -`(train-convnet {:embedding-size 50 :batch-size 100 :test-size 100 :num-epoch 10 :max-examples 1000 :pretrained-embedding :glove})` - +`(train-convnet {:devs [(context/default-context)] :embedding-size 50 :batch-size 100 :test-size 100 :num-epoch 10 :max-examples 1000 :pretrained-embedding :glove})` or `JVM_OPTS="-Xmx1g" lein run` (cpu) @@ -49,6 +48,21 @@ and then run - `lein uberjar` - `java -Xms1024m -Xmx2048m -jar target/cnn-text-classification-0.1.0-SNAPSHOT-standalone.jar` +## Usage with fastText + +Using fastText instead of glove is fairly straightforward, as the pretrained embedding format is very similar. + +Download the 'Simple English' pretrained wiki word vectors (text) from the fastText +[site](https://fasttext.cc/docs/en/pretrained-vectors.html) and place them in the +`data/fasttext` directory. Alternatively just run `./get_fasttext_data.sh`. + +Then you can run training on a subset of examples through the repl using: +``` +(train-convnet {:devs [(context/default-context)] :embedding-size 300 :batch-size 100 :test-size 100 :num-epoch 10 :max-examples 1000 :pretrained-embedding :fasttext}) +``` + +Expect a validation accuracy of `~0.67` with the above parameters. + ## Usage with word2vec You can also use word2vec embeddings in order to train the text classification model. @@ -58,7 +72,7 @@ you'll need to unzip them and place them in the `contrib/clojure-package/data` d Then you can run training on a subset of examples through the repl using: ``` -(train-convnet {:embedding-size 300 :batch-size 100 :test-size 100 :num-epoch 10 :max-examples 1000 :pretrained-embedding :word2vec}) +(train-convnet {:devs [(context/default-context)] :embedding-size 300 :batch-size 100 :test-size 100 :num-epoch 10 :max-examples 1000 :pretrained-embedding :word2vec}) ``` Note that loading word2vec embeddings consumes memory and takes some time. @@ -66,7 +80,7 @@ You can also train them using `JVM_OPTS="-Xmx8g" lein run` once you've modified the parameters to `train-convnet` (see above) in `src/cnn_text_classification/classifier.clj`. In order to run training with word2vec on the complete data set, you will need to run: ``` -(train-convnet {:embedding-size 300 :batch-size 100 :test-size 1000 :num-epoch 10 :pretrained-embedding :word2vec}) +(train-convnet {:devs [(context/default-context)] :embedding-size 300 :batch-size 100 :test-size 1000 :num-epoch 10 :pretrained-embedding :word2vec}) ``` You should be able to achieve an accuracy of `~0.78` using the parameters above. diff --git a/contrib/clojure-package/examples/cnn-text-classification/get_fasttext_data.sh b/contrib/clojure-package/examples/cnn-text-classification/get_fasttext_data.sh new file mode 100755 index 000000000000..2bfe96659402 --- /dev/null +++ b/contrib/clojure-package/examples/cnn-text-classification/get_fasttext_data.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -evx + +mkdir -p data/fasttext +cd data/fasttext +wget https://dl.fbaipublicfiles.com/fasttext/vectors-wiki/wiki.simple.vec diff --git a/contrib/clojure-package/examples/cnn-text-classification/src/cnn_text_classification/data_helper.clj b/contrib/clojure-package/examples/cnn-text-classification/src/cnn_text_classification/data_helper.clj index 82ba13087a37..df132c3167cd 100644 --- a/contrib/clojure-package/examples/cnn-text-classification/src/cnn_text_classification/data_helper.clj +++ b/contrib/clojure-package/examples/cnn-text-classification/src/cnn_text_classification/data_helper.clj @@ -33,6 +33,8 @@ [embedding-size] (format "data/glove/glove.6B.%dd.txt" embedding-size)) +(def fasttext-file-path "data/fasttext/wiki.simple.vec") + (defn r-string "Reads a string from the given DataInputStream `dis` until a space or newline is reached." [dis] @@ -62,7 +64,7 @@ vect (mapv (fn [_] (read-float dis)) (range embedding-size))] (cons [word vect] (lazy-seq (load-w2v-vectors dis embedding-size (dec num-vectors))))))) -(defn load-word2vec-model +(defn load-word2vec-model! "Loads the word2vec model stored in a binary format from the given `path`. By default only the first 100 embeddings are loaded." ([path embedding-size opts] @@ -75,8 +77,8 @@ _ (println "Processing with " {:dim dim :word-size word-size} " loading max vectors " max-vectors) _ (if (not= embedding-size dim) (throw (ex-info "Mismatch in embedding size" - {:input-embedding-size embedding-size - :word2vec-embedding-size dim}))) + {:input-embedding-size embedding-size + :word2vec-embedding-size dim}))) vectors (load-w2v-vectors dis dim max-vectors) word2vec (if vocab (->> vectors @@ -88,17 +90,30 @@ (println "Finished") {:num-embed dim :word2vec word2vec}))) ([path embedding-size] - (load-word2vec-model path embedding-size {:max-vectors 100}))) + (load-word2vec-model! path embedding-size {:max-vectors 100}))) -(defn read-text-embedding-pairs [rdr] - (for [^String line (line-seq rdr) +(defn read-text-embedding-pairs [pairs] + (for [^String line pairs :let [fields (.split line " ")]] [(aget fields 0) (mapv #(Float/parseFloat ^String %) (rest fields))])) -(defn load-glove [glove-file-path] +(defn load-glove! [glove-file-path] (println "Loading the glove pre-trained word embeddings from " glove-file-path) - (into {} (read-text-embedding-pairs (io/reader glove-file-path)))) + (->> (io/reader glove-file-path) + line-seq + read-text-embedding-pairs + (into {}))) + +(def remove-fasttext-metadata rest) + +(defn load-fasttext! [fasttext-file-path] + (println "Loading the fastText pre-trained word embeddings from " fasttext-file-path) + (->> (io/reader fasttext-file-path) + line-seq + remove-fasttext-metadata + read-text-embedding-pairs + (into {}))) (defn clean-str [s] (-> s @@ -188,9 +203,11 @@ sentences-padded (pad-sentences sentences) vocab (build-vocab sentences-padded) vocab-embeddings (case pretrained-embedding - :glove (->> (load-glove (glove-file-path embedding-size)) + :glove (->> (load-glove! (glove-file-path embedding-size)) (build-vocab-embeddings vocab embedding-size)) - :word2vec (->> (load-word2vec-model w2v-file-path embedding-size {:vocab vocab}) + :fasttext (->> (load-fasttext! fasttext-file-path) + (build-vocab-embeddings vocab embedding-size)) + :word2vec (->> (load-word2vec-model! w2v-file-path embedding-size {:vocab vocab}) (:word2vec) (build-vocab-embeddings vocab embedding-size)) vocab) From d74b9930ede1ff814c8f06fc5d63bee309f8e551 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Srivastava Date: Mon, 1 Jul 2019 09:46:57 -0700 Subject: [PATCH 025/813] Revert default return type for indices in argsort() and topk() back to float32 (#15360) --- src/operator/tensor/ordering_op-inl.h | 25 +++++++++++++------------ tests/python/unittest/test_ndarray.py | 10 ++-------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/operator/tensor/ordering_op-inl.h b/src/operator/tensor/ordering_op-inl.h index 98bca3a43c60..880acf1f4cae 100644 --- a/src/operator/tensor/ordering_op-inl.h +++ b/src/operator/tensor/ordering_op-inl.h @@ -88,11 +88,7 @@ struct TopKParam : public dmlc::Parameter { .add_enum("float16", mshadow::kFloat16) .add_enum("float32", mshadow::kFloat32) .add_enum("float64", mshadow::kFloat64) -#if MXNET_USE_INT64_TENSOR_SIZE == 1 - .set_default(mshadow::kInt64) -#else - .set_default(mshadow::kInt32) -#endif + .set_default(mshadow::kFloat32) .describe("DType of the output indices when ret_typ is \"indices\" or \"both\". " "An error will be raised if the selected data type cannot precisely represent the " "indices."); @@ -129,11 +125,7 @@ struct ArgSortParam : public dmlc::Parameter { .add_enum("float16", mshadow::kFloat16) .add_enum("float32", mshadow::kFloat32) .add_enum("float64", mshadow::kFloat64) -#if USE_INT64_TENSOR_SIZE == 1 - .set_default(mshadow::kInt64) -#else - .set_default(mshadow::kInt32) -#endif + .set_default(mshadow::kFloat32) .describe("DType of the output indices. It is only valid when ret_typ is \"indices\" or" " \"both\". An error will be raised if the selected data type cannot precisely " "represent the indices."); @@ -748,8 +740,17 @@ inline bool TopKType(const nnvm::NodeAttrs& attrs, // out_attr[0] -> stores value // out_attr[1] -> stores indices if (out_size > 1) { - CHECK(type_assign(&(*out_attrs)[1], param.dtype)) - << "Failed to set the type of ret_indices."; + if (param.ret_typ == topk_enum::kReturnValue) { +#if MXNET_USE_INT64_TENSOR_SIZE == 1 + CHECK(type_assign(&(*out_attrs)[1], mshadow::kInt64)) +#else + CHECK(type_assign(&(*out_attrs)[1], mshadow::kInt32)) +#endif + << "Failed to set the type of ret_indices."; + } else { + CHECK(type_assign(&(*out_attrs)[1], param.dtype)) + << "Failed to set the type of ret_indices."; + } } if (param.ret_typ == topk_enum::kReturnIndices) { CHECK(type_assign(&(*out_attrs)[0], param.dtype)) diff --git a/tests/python/unittest/test_ndarray.py b/tests/python/unittest/test_ndarray.py index d84b4f082b63..f40bb3053358 100644 --- a/tests/python/unittest/test_ndarray.py +++ b/tests/python/unittest/test_ndarray.py @@ -822,10 +822,7 @@ def get_large_matrix(): # test for ret_typ=indices nd_ret_topk = mx.nd.topk(a_nd, axis=1, ret_typ="indices", k=3, is_ascend=True).asnumpy() # Test the default dtype - if is_large_tensor_enabled: - assert nd_ret_topk.dtype == np.int64 - else: - assert nd_ret_topk.dtype == np.int32 + assert nd_ret_topk.dtype == np.float32 gt = gt_topk(a_npy, axis=1, ret_typ="indices", k=3, is_ascend=True) assert_almost_equal(nd_ret_topk, gt) nd_ret_topk = mx.nd.topk(a_nd, axis=3, ret_typ="indices", k=2, is_ascend=False, dtype=np.float64).asnumpy() @@ -866,10 +863,7 @@ def get_large_matrix(): nd_ret_topk_val = nd_ret_topk_val.asnumpy() nd_ret_topk_ind = nd_ret_topk_ind.asnumpy() assert nd_ret_topk_val.dtype == dtype - if is_large_tensor_enabled: - assert nd_ret_topk_ind.dtype == np.int64 - else: - assert nd_ret_topk_ind.dtype == np.int32 + assert nd_ret_topk_ind.dtype == np.float32 gt_val = gt_topk(a_npy, axis=1, ret_typ="value", k=3, is_ascend=True) gt_ind = gt_topk(a_npy, axis=1, ret_typ="indices", k=3, is_ascend=True) assert_almost_equal(nd_ret_topk_val, gt_val) From 7210cc459d3b9e9780998f9a9ec4ebc2f6a9d749 Mon Sep 17 00:00:00 2001 From: Aaron Markham Date: Mon, 1 Jul 2019 16:20:39 -0700 Subject: [PATCH 026/813] nano instructions (#15117) * nano instructions * bumping CI * update to have working whl files for python 3 and 2 --- docs/install/index.md | 85 +--------- docs/install/install-jetson.md | 289 +++++++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+), 82 deletions(-) create mode 100644 docs/install/install-jetson.md diff --git a/docs/install/index.md b/docs/install/index.md index 5fef5ca47e57..ac7bd048a588 100644 --- a/docs/install/index.md +++ b/docs/install/index.md @@ -26,6 +26,7 @@ c_plus_plus.md centos_setup.md download.md + install-jetson.md java_setup.md osx_setup.md scala_setup.md @@ -1447,89 +1448,9 @@ You are now ready to run MXNet on your Raspberry Pi device. You can get started
-# Nvidia Jetson TX family +# NVIDIA Jetson Devices -MXNet supports the Ubuntu Arch64 based operating system so you can run MXNet on NVIDIA Jetson Devices. - -These instructions will walk through how to build MXNet for the Pascal based [NVIDIA Jetson TX2](http://www.nvidia.com/object/embedded-systems-dev-kits-modules.html) and install the corresponding python language bindings. - -For the purposes of this install guide we will assume that CUDA is already installed on your Jetson device. - -**Install MXNet** - -Installing MXNet is a two-step process: - -1. Build the shared library from the MXNet C++ source code. -2. Install the supported language-specific packages for MXNet. - -**Step 1** Build the Shared Library - -You need the following additional dependencies: - -- Git (to pull code from GitHub) - -- libatlas (for linear algebraic operations) - -- libopencv (for computer vision operations) - -- python pip (to load relevant python packages for our language bindings) - -Install these dependencies using the following commands in any directory: - -``` - sudo apt-get update - sudo apt-get -y install git build-essential libatlas-base-dev libopencv-dev graphviz python-pip - sudo pip install pip --upgrade - sudo pip install setuptools numpy --upgrade - sudo pip install graphviz==0.8.4 \ - jupyter -``` - -Clone the MXNet source code repository using the following `git` command in your home directory: -``` - git clone https://github.com/apache/incubator-mxnet.git --recursive - cd incubator-mxnet -``` - -Edit the Makefile to install the MXNet with CUDA bindings to leverage the GPU on the Jetson: -``` - cp make/crosscompile.jetson.mk config.mk -``` - -Edit the Mshadow Makefile to ensure MXNet builds with Pascal's hardware level low precision acceleration by editing 3rdparty/mshadow/make/mshadow.mk and adding the following after line 122: -``` -MSHADOW_CFLAGS += -DMSHADOW_USE_PASCAL=1 -``` - -Now you can build the complete MXNet library with the following command: -``` - make -j $(nproc) -``` - -Executing this command creates a file called `libmxnet.so` in the mxnet/lib directory. - -**Step 2** Install MXNet Python Bindings - -To install Python bindings run the following commands in the MXNet directory: - -``` - cd python - pip install --upgrade pip - pip install -e . -``` - -Note that the `-e` flag is optional. It is equivalent to `--editable` and means that if you edit the source files, these changes will be reflected in the package installed. - -Add the mxnet folder to the path: - -``` - cd .. - export MXNET_HOME=$(pwd) - echo "export PYTHONPATH=$MXNET_HOME/python:$PYTHONPATH" >> ~/.rc - source ~/.rc -``` - -You are now ready to run MXNet on your NVIDIA Jetson TX2 device. +To install MXNet on a Jetson TX or Nano, please refer to the [Jetson installation guide](install-jetson.html).
diff --git a/docs/install/install-jetson.md b/docs/install/install-jetson.md new file mode 100644 index 000000000000..a93f930a8182 --- /dev/null +++ b/docs/install/install-jetson.md @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + +# Install MXNet on a Jetson + +MXNet supports the Ubuntu Arch64 based operating system so you can run MXNet on NVIDIA Jetson Devices, such as the [TX2](http://www.nvidia.com/object/embedded-systems-dev-kits-modules.html) or [Nano](https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit). + +These instructions will walk through how to build MXNet and install the corresponding language bindings. Python is the default binding, but you may also try out one of the many language bindings MXNet has to offer. These instructions also cover how to setup MXNet's Java Inference API. + +For the purposes of this install guide we will assume that CUDA is already installed on your Jetson device. The disk image provided by NVIDIA's getting started guides will have the Jetson toolkit preinstalled, and this also includes CUDA. You should double check what versions are installed and which version you plan to use. + +You have several options for installing MXNet: +1. Use a Jetson MXNet pip wheel for Python development. +2. Use precompiled Jetson MXNet binaries. +3. Build MXNet from source + * On a faster Linux computer using cross-compilation + * On the Jetson itself (very slow and not recommended) + + +## Prerequisites +To build from source or to use the Python wheel, you must install the following dependencies on your Jetson. +Cross-compiling will require dependencies installed on that machine as well. + +### Python API + +To use the Python API you need the following dependencies: + +```bash +sudo apt update +sudo apt -y install \ + build-essential \ + git \ + graphviz \ + libatlas-base-dev \ + libopencv-dev \ + python-pip + +sudo pip install --upgrade \ + pip \ + setuptools + +sudo pip install \ + graphviz==0.8.4 \ + jupyter \ + numpy==1.15.2 +``` + +If you plan to cross-compile you will need to install these dependencies on that computer as well. + + + +### Configure CUDA + +You can check to see what version of CUDA is running with `nvcc`. + +```bash +nvcc --version +``` + +To switch CUDA versions on a device or computer that has more than one version installed, use the following and replace the version as appropriate. + +```bash +sudo rm /usr/local/cuda +sudo ln -s /usr/local/cuda-10.0 /usr/local/cuda +``` + +**Note:** When cross-compiling, change the CUDA version on the host computer you're using to match the version you're running on your Jetson device. +**Note:** CUDA 10.1 is recommended but doesn't ship with the Nano's SD card image. You may want to go through CUDA upgrade steps first. + +### Download the source & setup some environment variables: + +These steps are optional, but some of the following instructions expect MXNet source files and the `MXNET_HOME` environment variable. + +Clone the MXNet source code repository using the following `git` command in your home directory: + +```bash +git clone --recursive https://github.com/apache/incubator-mxnet.git mxnet +cd mxnet +``` + +Setup your environment variables for MXNet. + +```bash +cd .. +export MXNET_HOME=$(pwd) +echo "export PYTHONPATH=$MXNET_HOME/python:$PYTHONPATH" >> ~/.rc +source ~/.rc +``` + +**Note:** Change the `~/.rc` steps according to how you prefer to use your shell. Otherwise, your environment variables will be gone after you logout. + + +## Install MXNet for Python + +To use a prepared Python wheel, download it to your Jetson, and run it. +* [MXNet 1.4.0 - Python 3](https://s3.us-east-2.amazonaws.com/mxnet-public/install/jetson/1.4.0/mxnet-1.4.0-cp36-cp36m-linux_aarch64.whl) +* [MXNet 1.4.0 - Python 2](https://s3.us-east-2.amazonaws.com/mxnet-public/install/jetson/1.4.0/mxnet-1.4.0-cp27-cp27mu-linux_aarch64.whl) + + +It should download the required dependencies, but if you have issues, +install the dependencies in the prerequisites section, then run the pip wheel. + +```bash +sudo pip install mxnet-1.4.0-cp36-cp36m-linux_aarch64.whl +``` + + + +## Use a Pre-compiled MXNet Binary + +If you want to just use a pre-compiled binary you can download it from S3: +* https://s3.us-east-2.amazonaws.com/mxnet-public/install/jetson/1.4.1/libmxnet.so + +Place this file in `$MXNET_HOME/lib`. + +To use this with the MXNet Python binding, you must match the source directory's checked out version with the binary's source version, then install it with pip. + +```bash +cd $MXNET_HOME +git checkout v1.4.x +git submodule update --init +cd python +sudo pip install -e . +``` + +## Build MXNet from Source + +Installing MXNet from source is a two-step process: + +1. Build the shared library from the MXNet C++ source code. +2. Install the supported language-specific packages for MXNet. + +You can use a Docker method or you can build from source manually. + +### Docker + +You must have installed Docker and be able to run `docker` without `sudo`. +Follow these [setup instructions to get to this point](https://docs.docker.com/install/linux/#manage-docker-as-a-non-root-user). +Then run the following to execute cross-compilation via Docker. + +```bash +$MXNET_HOME/ci/build.py -p jetson +``` + +### Manual + +**Step 1** Build the Shared Library + +(Skip this sub-step for compiling on the Jetson device directly.) +Edit the Makefile to install the MXNet with CUDA bindings to leverage the GPU on the Jetson: + +```bash +cp $MXNET_HOME/make/crosscompile.jetson.mk config.mk +``` + +Now edit `config.mk` to make some additional changes for the Nano. Update the following settings: + +1. Update the CUDA path. `USE_CUDA_PATH = /usr/local/cuda` +2. Add `-gencode arch=compute-63, code=sm_62` to the `CUDA_ARCH` setting. +3. Update the NVCC settings. `NVCCFLAGS := -m64` +4. (optional, but recommended) Turn on OpenCV. `USE_OPENCV = 1` + +Now edit the Mshadow Makefile to ensure MXNet builds with Pascal's hardware level low precision acceleration by editing `3rdparty/mshadow/make/mshadow.mk`. +The last line has `MSHADOW_USE_PASCAL` set to `0`. Change this to `1` to enable it. + +```bash +MSHADOW_CFLAGS += -DMSHADOW_USE_PASCAL=1 +``` + +Now you can build the complete MXNet library with the following command: + +```bash +cd $MXNET_HOME +make -j $(nproc) +``` + +Executing this command creates a file called `libmxnet.so` in the `mxnet/lib` directory. + +**Step 2** Install MXNet Python Bindings (optional) + +To install Python bindings run the following commands in the MXNet directory: + +```bash +cd $MXNET_HOME/python +sudo pip install -e . +``` + +Note that the `-e` flag is optional. It is equivalent to `--editable` and means that if you edit the source files, these changes will be reflected in the package installed. + +**Step 3** Install the MXNet Java & Scala Bindings (optional) + +Change directories to `scala-package` and run `mvn install`. + +```bash +cd $MXNET_HOME/scala-package +mvn install +``` + +This creates the required `.jar` file to use in your Java or Scala projects. + +## Conclusion and Next Steps + +You are now ready to run MXNet on your NVIDIA Jetson TX2 or Nano device. +You can verify your MXNet Python installation with the following: + +```python +import mxnet +mxnet.__version__ +``` + +You can also verify MXNet can use your GPU with the following test: + +```python +import mxnet as mx +a = mx.nd.ones((2, 3), mx.gpu()) +b = a * 2 + 1 +b.asnumpy() +``` + +If everything is working, it will report the version number. +For assistance, head over to the [MXNet Forum](https://discuss.mxnet.io/). From c6bb2cec919851afa04dbeda96bc1cab588c0fba Mon Sep 17 00:00:00 2001 From: Zhennan Qin Date: Wed, 3 Jul 2019 01:56:52 +0800 Subject: [PATCH 027/813] Use omp threads for cpu data loader (#15379) * Use omp threads for cpu data loader * fix lint * CI --- src/io/iter_image_recordio_2.cc | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/io/iter_image_recordio_2.cc b/src/io/iter_image_recordio_2.cc index 5d9e81d3f6b4..565b67104149 100644 --- a/src/io/iter_image_recordio_2.cc +++ b/src/io/iter_image_recordio_2.cc @@ -134,19 +134,25 @@ inline void ImageRecordIOParser2::Init( record_param_.InitAllowUnknown(kwargs); batch_param_.InitAllowUnknown(kwargs); normalize_param_.InitAllowUnknown(kwargs); + PrefetcherParam prefetch_param; + prefetch_param.InitAllowUnknown(kwargs); n_parsed_ = 0; overflow = false; rnd_.seed(kRandMagic + record_param_.seed); int maxthread, threadget; - #pragma omp parallel - { - // be conservative, set number of real cores - maxthread = std::max(omp_get_num_procs(), 1); - } - param_.preprocess_threads = std::min(maxthread, param_.preprocess_threads); - #pragma omp parallel num_threads(param_.preprocess_threads) - { - threadget = omp_get_num_threads(); + if (prefetch_param.ctx == PrefetcherParam::CtxType::kCPU) { + threadget = engine::OpenMP::Get()->GetRecommendedOMPThreadCount(); + } else { + #pragma omp parallel + { + // be conservative, set number of real cores + maxthread = std::max(omp_get_num_procs() / 2, 1); + } + param_.preprocess_threads = std::min(maxthread, param_.preprocess_threads); + #pragma omp parallel num_threads(param_.preprocess_threads) + { + threadget = omp_get_num_threads(); + } } param_.preprocess_threads = threadget; @@ -822,7 +828,8 @@ class ImageRecordIter2Wrapper : public IIterator { dtype = prefetch_param.dtype.value(); } if (prefetch_param.ctx == PrefetcherParam::CtxType::kCPU) { - LOG(INFO) << "Create ImageRecordIter2 optimized for CPU backend."; + LOG(INFO) << "Create ImageRecordIter2 optimized for CPU backend." + << "Use omp threads instead of preprocess_threads."; switch (dtype) { case mshadow::kFloat32: record_iter_ = new ImageRecordIter2CPU(); From 512a491547c17f4caffc4cc962d95a6467186a3d Mon Sep 17 00:00:00 2001 From: Zhaoqi Zhu Date: Tue, 2 Jul 2019 20:55:18 -0700 Subject: [PATCH 028/813] Temporarily Commenting out Flaky Test (#15436) * comment out flaky test * Update test_profiler.py * Update test_profiler.py * Update test_profiler.py --- tests/python/unittest/test_profiler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/python/unittest/test_profiler.py b/tests/python/unittest/test_profiler.py index ab7d29f104ca..b9447f951764 100644 --- a/tests/python/unittest/test_profiler.py +++ b/tests/python/unittest/test_profiler.py @@ -24,6 +24,7 @@ import json from collections import OrderedDict from common import run_in_spawned_process +import unittest def enable_profiler(profile_filename, run=True, continuous_dump=False, aggregate_stats=False): profiler.set_config(profile_symbolic=True, @@ -427,6 +428,7 @@ def create_operator(self, ctx, shapes, dtypes): and '_plus_scalar' in target_dict['Time']['operator'] profiler.set_state('stop') +@unittest.skip("Flaky test https://github.com/apache/incubator-mxnet/issues/15406") def test_custom_operator_profiling_multiple_custom_ops_symbolic(): run_in_spawned_process(test_custom_operator_profiling_multiple_custom_ops_imperative, \ {'MXNET_EXEC_BULK_EXEC_INFERENCE' : 0, \ @@ -434,6 +436,7 @@ def test_custom_operator_profiling_multiple_custom_ops_symbolic(): 'symbolic', \ 'test_custom_operator_profiling_multiple_custom_ops_symbolic.json') +@unittest.skip("Flaky test https://github.com/apache/incubator-mxnet/issues/15406") def test_custom_operator_profiling_naive_engine(): # run the three tests above using Naive Engine run_in_spawned_process(test_custom_operator_profiling, \ From 15475788cee87eb6c6b08ddd0af245af7c05536f Mon Sep 17 00:00:00 2001 From: Disi A Date: Wed, 3 Jul 2019 00:05:24 -0400 Subject: [PATCH 029/813] Remove mhard-float option. This is already deprecated by Google. (#15435) --- amalgamation/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/amalgamation/Makefile b/amalgamation/Makefile index d4b2ee0ef228..701c1f155e47 100644 --- a/amalgamation/Makefile +++ b/amalgamation/Makefile @@ -114,8 +114,8 @@ jni_libmxnet_predict.so: jni_libmxnet_predict.o ifneq ($(ANDROID), 1) android: else - CFLAGS+= -mhard-float -D_NDK_MATH_NO_SOFTFP=1 -O3 - LDFLAGS+= -Wl,--no-warn-mismatch -lm_hard + CFLAGS+= -O3 + LDFLAGS+= -Wl,--no-warn-mismatch -lm_hard android: jni_libmxnet_predict.so endif From 6a8d9eb5fd4f7133c094149dc80a3a236534f223 Mon Sep 17 00:00:00 2001 From: Lin Yuan Date: Tue, 2 Jul 2019 22:53:39 -0700 Subject: [PATCH 030/813] [MXNET-978] Higher order gradient for sigmoid (#15288) * try to add support some ops * add unit test for second order grad * implement grad for relu and add unit test * fix lint * register FGradient attribute for backward relu * resolve conflict * remove unused imports * change gradient using set_attr * remove higher order grad test for negative(x) * fix lint * reverse indent * remove unused backward operator * refactor backward for sin(x) and cos(x) * change value init to list init * change to list initialization * generate random shape in test * fix a bug in second order backward * fix lint * fix lint * address reviewer comment and renaming * test 2nd order gradient for sigmoid * higher order grads for sigmoid * add unit test * remove blank lines * update test * fix lint * fix third order gradient for sigmoid --- src/common/exec_utils.h | 5 ++-- src/imperative/imperative.cc | 4 +++ .../tensor/elemwise_unary_op_basic.cc | 30 ++++++++++++++++++- src/operator/tensor/elemwise_unary_op_trig.cc | 4 +-- .../python/unittest/test_higher_order_grad.py | 17 +++++++++++ 5 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/common/exec_utils.h b/src/common/exec_utils.h index 0551b429f17e..d8b7a33bf22b 100644 --- a/src/common/exec_utils.h +++ b/src/common/exec_utils.h @@ -286,7 +286,6 @@ inline void LogMemoryPlan(const nnvm::Graph& g) { const auto &idx = g.indexed_graph(); const auto& vshape = g.GetAttr("shape"); const auto& vtype = g.GetAttr("dtype"); - const auto& vstorage = g.GetAttr("storage_id"); // find node range uint32_t node_start = 0, node_end = idx.num_nodes(); if (g.attrs.count("node_range")) { @@ -304,13 +303,13 @@ inline void LogMemoryPlan(const nnvm::Graph& g) { auto eid = idx.entry_id(e); size_t kilo_bytes = vshape[eid].Size() * mshadow::mshadow_sizeof(vtype[eid]) / 1024; LOG(INFO) << "\t\tinput " << eid << ": " << vshape[eid] << " (" - << kilo_bytes << " KB) -> " << storage_str(vstorage[eid]); + << kilo_bytes << " KB)"; } for (uint32_t index = 0; index < inode.source->num_outputs(); ++index) { uint32_t eid = idx.entry_id(nid, index); size_t kilo_bytes = vshape[eid].Size() * mshadow::mshadow_sizeof(vtype[eid]) / 1024; LOG(INFO) << "\t\toutput " << eid << ": " << vshape[eid] << " (" - << kilo_bytes << " KB) -> " << storage_str(vstorage[eid]); + << kilo_bytes << " KB)"; } } } diff --git a/src/imperative/imperative.cc b/src/imperative/imperative.cc index d8fba1c169ec..e2c0c9d4c9d4 100644 --- a/src/imperative/imperative.cc +++ b/src/imperative/imperative.cc @@ -501,6 +501,10 @@ std::vector Imperative::Backward( } } + if (dmlc::GetEnv("MXNET_MEM_PLAN_VERBOSE_LOGGING", false)) { + common::LogMemoryPlan(graph); + } + // Execution bool prev_recording = set_is_recording(create_graph); diff --git a/src/operator/tensor/elemwise_unary_op_basic.cc b/src/operator/tensor/elemwise_unary_op_basic.cc index 98dc8dad825f..26c74085dbe6 100644 --- a/src/operator/tensor/elemwise_unary_op_basic.cc +++ b/src/operator/tensor/elemwise_unary_op_basic.cc @@ -121,7 +121,35 @@ The storage type of ``sigmoid`` output is always dense .set_attr("FGradient", ElemwiseGradUseOut{"_backward_sigmoid"}); MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU(_backward_sigmoid, - unary_bwd); + unary_bwd) +.set_attr("FGradient", + [](const nnvm::NodePtr& n, const std::vector& ograds) { + // n->inputs[0] : y_grad + // n->inputs[1] : f(x) = sigmoid(x) + // ograds[0] : head_grads + // f''(x) = f'(x) * (1 - 2*f(x)) + // NodeEntry{n} : y_grad * f'(x) + auto ones = MakeNode("ones_like", n->attrs.name + "_grad_ones", {n->inputs[1]}, nullptr, &n); + const std::unordered_map args = {{"scalar", "2.0"}}; + auto two_y = MakeNode("_mul_scalar", n->attrs.name + "_mul_two", {n->inputs[1]}, &args, &n); + auto one_minus_two_y = MakeNode("elemwise_sub", n->attrs.name + "_grad_sub", + {nnvm::NodeEntry{ones}, nnvm::NodeEntry{two_y}}, nullptr, &n); + auto grad_grad_mid = MakeNode("elemwise_mul", n->attrs.name + "_grad_mul", + {n->inputs[0], nnvm::NodeEntry{one_minus_two_y}}, nullptr, &n); + auto dydx = MakeNode("elemwise_div", n->attrs.name + "_grad_div", + {nnvm::NodeEntry{n}, n->inputs[0]}, nullptr, &n); + + // when building gradient graph, the backward node of n->inputs[1] will be + // added to the graph again, therefore f`(x) will be multiplied + std::vector ret; + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "backward_grad_grad", + {ograds[0], nnvm::NodeEntry{dydx}}, nullptr, &n)); + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "backward_grad_grad_in", + {ograds[0], nnvm::NodeEntry{grad_grad_mid}}, nullptr, &n)); + return ret; + }); + + DMLC_REGISTER_PARAMETER(HardSigmoidParam); MXNET_OPERATOR_REGISTER_UNARY(hard_sigmoid) diff --git a/src/operator/tensor/elemwise_unary_op_trig.cc b/src/operator/tensor/elemwise_unary_op_trig.cc index b7cf76e4eb2d..13410e9422a5 100644 --- a/src/operator/tensor/elemwise_unary_op_trig.cc +++ b/src/operator/tensor/elemwise_unary_op_trig.cc @@ -49,7 +49,7 @@ The storage type of ``sin`` output depends upon the input storage type: MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU_DR(_backward_sin, unary_bwd) .set_attr("FGradient", [](const nnvm::NodePtr& n, const std::vector& ograds) { - // ograds[0]: d^2L/dx^2 + // ograds[0]: head_grad_grads (dL/dxgrad) // inputs[0]: dL/dy // inputs[1]: x (ElemwiseUseIn) // f(x) = sin(x) @@ -92,7 +92,7 @@ The storage type of ``cos`` output is always dense MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU(_backward_cos, unary_bwd) .set_attr("FGradient", [](const nnvm::NodePtr& n, const std::vector& ograds) { - // ograds[0]: d^2L/dx^2 + // ograds[0]: head_grad_grads (dL/dx_grad) // inputs[0]: dL/dy // inputs[1]: x (ElemwiseUseIn) // f(x) = cos(x) diff --git a/tests/python/unittest/test_higher_order_grad.py b/tests/python/unittest/test_higher_order_grad.py index 4f1ea9a6c7b8..ad14c5050c1b 100644 --- a/tests/python/unittest/test_higher_order_grad.py +++ b/tests/python/unittest/test_higher_order_grad.py @@ -106,6 +106,23 @@ def grad_grad_op(x): check_second_order_unary(array, log10, grad_grad_op) +@with_seed() +def test_sigmoid(): + def sigmoid(x): + return nd.sigmoid(x) + + def grad_op(x): + return sigmoid(x) * (1 - sigmoid(x)) + + def grad_grad_op(x): + return grad_op(x) * (1 - 2 * sigmoid(x)) + + for dim in range(1, 5): + shape = rand_shape_nd(dim) + array = random_arrays(shape) + check_second_order_unary(array, sigmoid, grad_grad_op) + + def check_second_order_unary(x, op, grad_grad_op): x = nd.array(x) grad_grad_x = grad_grad_op(x) From 3df3e2c6160b63b39510483bcdf8111398c21f2a Mon Sep 17 00:00:00 2001 From: Thom Lane Date: Wed, 3 Jul 2019 09:31:49 -0700 Subject: [PATCH 031/813] [TUTORIAL] Gluon performance tips and tricks (#15427) * Adding Gluon performance tips and tricks tutorial. Summary of a number of performance features provided in MXNet. Useful for beginner audience. * Added licence and download buttons. * Updates based on feedback. * Updates based on feedback. * Update test_tutorials.py * Update index.md * Update performance.md * Update performance.md --- docs/tutorials/gluon/performance.md | 483 ++++++++++++++++++++++++++++ docs/tutorials/index.md | 1 + tests/tutorials/test_tutorials.py | 3 + 3 files changed, 487 insertions(+) create mode 100644 docs/tutorials/gluon/performance.md diff --git a/docs/tutorials/gluon/performance.md b/docs/tutorials/gluon/performance.md new file mode 100644 index 000000000000..27a7e9ee6517 --- /dev/null +++ b/docs/tutorials/gluon/performance.md @@ -0,0 +1,483 @@ + + + + + + + + + + + + + + + + + +# Gluon Performance Tips & Tricks + +Compared to traditional machine learning methods, the field of deep-learning has increased model accuracy across a wide range of tasks, but it has also increased the amount of computation required for model training and inference. Specialised hardware chips, such as GPUs and FPGAs, can speed up the execution of networks, but it can sometimes be hard to write code that uses the hardware to its full potential. We will be looking at a few simple tips and trick in this tutorial that you can use to speed up training and ultimately save on training costs. You'll find most of these tips and tricks useful for inference too. + +We'll start by writing some code to train an image classification network for the CIFAR-10 dataset, and then benchmark the throughput of the network in terms of samples processed per second. After some performance analysis, we'll identify the bottlenecks (i.e. the components limiting throughput) and improve the training speed step-by-step. We'll bring together all the tips and tricks at the end and evaluate our performance gains. + + +```python +from __future__ import print_function +import multiprocessing +import time +import mxnet as mx +import numpy as np +``` + +An Amazon EC2 p3.2xlarge instance was used to benchmark the code in this tutorial. You are likely to get different results and find different bottlenecks on other hardware, but these tips and tricks should still help improve training speed for bottleneck components. A GPU is recommended for this tutorial. + + +```python +ctx = mx.gpu() if mx.test_utils.list_gpus() else mx.cpu() +print("Using {} context.".format(ctx)) +``` + + Using gpu(0) context. + + +We'll use the `CIFAR10` dataset provided out-of-the-box with Gluon. + + +```python +dataset = mx.gluon.data.vision.CIFAR10(train=True) +print('{} samples'.format(len(dataset))) +``` + + 50000 samples + + +So we can learn how to identify training bottlenecks, let's intentionally introduce a bottleneck by adding a short `sleep` into the data loading pipeline. We transform each 32x32 CIFAR-10 image to 224x224 so we can use it with the ResNet-50 network designed for ImageNet. [CIFAR-10 specific ResNet networks](https://gluon-cv.mxnet.io/api/model_zoo.html#gluoncv.model_zoo.get_cifar_resnet) exist but we use the more standard ImageNet variants in this example. + + +```python +def transform_fn(x): + time.sleep(0.01) # artificial slow-down + image = mx.image.imresize(x, w=224, h=224) + return image.astype('float32').transpose((2, 0, 1)) + +dataset = dataset.transform_first(transform_fn) +``` + +Setting our batch size to 16, we can create the `DataLoader`. + + +```python +batch_size = 16 +dataloader = mx.gluon.data.DataLoader(dataset, + batch_size=batch_size, + shuffle=True, + last_batch="discard") +print('{} batches'.format(len(dataloader))) +``` + + 3125 batches + + +Up next, we create all of the other components required for training, such as the network, the loss function, the evaluation metric and parameter trainer. + + +```python +net = mx.gluon.model_zoo.vision.resnet50_v2(pretrained=False, ctx=ctx) +net.initialize(mx.init.Xavier(magnitude=2.3), ctx=ctx) +loss_fn = mx.gluon.loss.SoftmaxCrossEntropyLoss() +metric = mx.metric.Accuracy() +learning_rate = 0.001 +trainer = mx.gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': learning_rate}) +``` + +## Initial Benchmark + +As a starting point, let's benchmark the throughput of our training loop: calculating the average samples per second across 25 iterations, where each iteration is a batch of 16 samples. We'll run a single forward pass through the network before starting our benchmark timer to avoid including shape inference and lazy initialization in the throughput calculations. + + +```python +def single_forward(net, dataloader, dtype='float32'): + data, label = next(iter(dataloader)) + data = data.astype(dtype) + data = data.as_in_context(ctx) + pred = net(data) + pred.wait_to_read() +``` + + +```python +single_forward(net, dataloader) +iters = 25 +num_samples = 0 +num_iters = 0 +start_time = time.time() +for iter_idx, (data, label) in enumerate(dataloader): + num_samples += data.shape[0] + num_iters += 1 + data = data.as_in_context(ctx) + label = label.as_in_context(ctx) + with mx.autograd.record(): + pred = net(data) + loss = loss_fn(pred, label) + loss.backward() + trainer.step(data.shape[0]) + metric.update(label, pred) + print('.', end='') + if num_iters >= iters: + break +mx.nd.waitall() +end_time = time.time() +total_time = end_time - start_time +print('\n') +print('average iterations/sec: {:.4f}'.format(num_iters/total_time)) +print('average samples/sec: {:.4f}'.format(num_samples/total_time)) +``` + + ......................... + + average iterations/sec: 4.4936 + average samples/sec: 71.8975 + + +Although ~70 samples per second might sound respectable, let's see if we can do any better by identifying the bottleneck in the training loop and optimizing that component. A significant amount of time can be wasted by optimizing components that aren't bottlenecks. + +## Identifying the bottleneck + +Monitoring the CPU (with `top`) and GPU utilization (with `nvidia-smi`) provide clues as to where potential bottlenecks lie. With the example above, when simultaneously running these monitoring tool, you might spot a single process on the CPU fixed at ~100% utilization while the GPU utilization behaves erratically and often falls to ~0%. Seeing behaviour like can indicate the CPU is struggling to process data and the GPU is being starved of data. + +MXNet's Profiler is another highly recommended tool for identifying bottlenecks, since it gives timing data for individual MXNet operations. Check out [this comprehensive tutorial](https://mxnet.incubator.apache.org/versions/master/tutorials/python/profiler.html) for more details. As a simpler form of analysis, we will split our training loop into two common components: + +1. Data Loading +2. Network Execution (forward and backward passes) + +We define two function to independently benchmark these components: `benchmark_dataloader` and `benchmark_network`. + + +```python +def benchmark_dataloader(dataloader, iters=25): + num_samples = 0 + num_iters = 0 + start_time = time.time() + startup_time = None + for iter_idx, sample in enumerate(dataloader): + if startup_time is None: + startup_time = time.time() + num_samples += sample[0].shape[0] + num_iters += 1 + if num_iters >= iters: + break + print('.', end='') + end_time = time.time() + total_time = end_time - start_time + total_startup_time = startup_time - start_time + total_iter_time = end_time - startup_time + print('\n') + print('total startup time: {:.4f}'.format(total_startup_time)) + print('average iterations/sec: {:.4f}'.format(num_iters/total_iter_time)) + print('average samples/sec: {:.4f}'.format(num_samples/total_iter_time)) + + +def benchmark_network(data, label, net, loss_fn, trainer, iters=25): + num_samples = 0 + num_iters = 0 + mx.nd.waitall() + start_time = time.time() + for iter_idx in range(iters): + num_samples += data.shape[0] + num_iters += 1 + with mx.autograd.record(): + pred = net(data) + loss = loss_fn(pred, label) + loss.backward() + trainer.step(data.shape[0]) + mx.nd.waitall() + if num_iters >= iters: + break + print('.', end='') + end_time = time.time() + total_time = end_time - start_time + print('\n') + print('average iterations/sec: {:.4f}'.format(num_iters/total_time)) + print('average samples/sec: {:.4f}'.format(num_samples/total_time)) +``` + +Our `benchmark_dataloader` function just loops through the `DataLoader` for a given number of iterations: it doesn't transfer the data to the correct context or pass it to the network. Our `benchmark_network` function just performs a forward and backward pass on an identical (and pre-transferred) batch of data: it doesn't require new data to be loaded. We'll run both of these functions now. + + +```python +print('\n', '### benchmark_dataloader', '\n') +benchmark_dataloader(dataloader) +print('\n', '### benchmark_network', '\n') +data, label = next(iter(dataloader)) +data = data.as_in_context(ctx) +label = label.as_in_context(ctx) +benchmark_network(data, label, net, loss_fn, trainer) +``` + + + ### benchmark_dataloader + + ........................ + + total startup time: 0.1697 + average iterations/sec: 6.2201 + average samples/sec: 99.5217 + + ### benchmark_network + + ........................ + + average iterations/sec: 15.1908 + average samples/sec: 243.0525 + + +Our data loading pipeline appears to be the bottleneck for training: ~100 samples/second compared with ~250 samples/second for network execution. One limiting factor could be disk throughput when reading samples (using a SSD instead of HDD can help with this), but in this case we intentionally added a delay in data transformation. Augmentation can often be a bottleneck in training if the following trick isn't applied. + +## Tips & Tricks #1: Use multiple workers on `DataLoader` + +In the previous section, we established that the data loading component of the training loop was the bottleneck. Instead of simply removing the artificial delay, let's assume it was some pre-processing or augmentation step that couldn't be removed. We found that the CPU utilization was fixed at 100%, but this was just for a single core. Usually machines have multiple cores and with one easy trick we can leverage more CPU cores to pre-process the data. Setting `num_workers` on the `DataLoader` will result in multiple workers being used to preprocess the data. We can use `multiprocessing.cpu_count()` to find the number of CPU cores available on the machine, and we save 1 core for the main thread. + + +```python +num_workers = multiprocessing.cpu_count() - 1 +dataloader = mx.gluon.data.DataLoader(dataset, + batch_size=batch_size, + shuffle=True, + last_batch="discard", + num_workers=num_workers) +print('Using {} workers for DataLoader.'.format(num_workers)) +``` + + Using 7 workers for DataLoader. + + +We benchmark the two main components once again: + + +```python +print('\n', '### benchmark_dataloader', '\n') +benchmark_dataloader(dataloader) +print('\n', '### benchmark_network', '\n') +data, label = next(iter(dataloader)) +data = data.as_in_context(ctx) +label = label.as_in_context(ctx) +benchmark_network(data, label, net, loss_fn, trainer, iters=10) +``` + + + ### benchmark_dataloader + + ........................ + + total startup time: 0.1935 + average iterations/sec: 46.2375 + average samples/sec: 739.7994 + + ### benchmark_network + + ......... + + average iterations/sec: 14.8614 + average samples/sec: 237.7819 + + +Our data loading pipeline is no longer the bottleneck for training throughput: ~700 samples per second versus ~250 samples per second for network execution as before. Check out [NVIDIA DALI](https://docs.nvidia.com/deeplearning/sdk/dali-developer-guide/docs/examples/mxnet/gluon.html) if you need to futher optimize your data pipeline. We can now focus our attention on improving the network throughput. + +## Tips & Tricks #2: Hybridize the network + +Gluon networks run in imperative mode by default, executing `NDArray` operations as the lines of code are stepped through one by one. While in imperative mode, debugging is often simplified and more flexible networks can be defined (using Python control flow). But this comes at a slight cost in terms of throughput. Since the network doesn't know what line of code will be run next, the network operations cannot be optimized and additional memory allocations are required (which all takes time). Most networks though can be written as `HybridBlocks` and, with the `hybridize` method, can be converted to symbolic mode execution. We can expect throughput to increase slightly in this mode. Watch out though: debugging can get more complicated. Setting `static_alloc=True` and `static_shape=True` reduce the number of memory allocations required while training. Once again, we run `single_forward` to force the hybridization process to occur before benchmarking. + + +```python +net.hybridize(static_alloc=True, static_shape=True) +single_forward(net, dataloader) +``` + + +```python +print('\n', '### benchmark_dataloader', '\n') +benchmark_dataloader(dataloader) +print('\n', '### benchmark_network', '\n') +data, label = next(iter(dataloader)) +data = data.as_in_context(ctx) +label = label.as_in_context(ctx) +benchmark_network(data, label, net, loss_fn, trainer) +``` + + + ### benchmark_dataloader + + ........................ + + total startup time: 0.1847 + average iterations/sec: 46.3004 + average samples/sec: 740.8072 + + ### benchmark_network + + ........................ + + average iterations/sec: 16.5738 + average samples/sec: 265.1812 + + +We can see quite a modest ~10% increase in throughput after hybridization. Gains can depend on a number of factors including the network architecture and the batch size used (a larger increase expected for smaller batch size). Our network execution is still the bottleneck in training so let's focus on that again. + +## Tips & Tricks #3: Increase the batch size + +GPUs are optimized for high throughput and they do this by performing many operations in parallel. Our NVIDIA Tesla V100 GPU utilization peaks at ~85% while running the last example. Although this is already quite high, there's still room improvement given this metric shows the percentage of time *at least one* kernel is running (over the last 1 second by default). Given we have enough memory available, the throughput of the network can be improved by increasing the batch size since more samples are processed in parallel. At this stage we're using approximately 1/4 of the available GPU memory, so let's increase out batch size by a factor of 4, from 16 to 64. Changing the batch size does have some side effects though. Using the same optimizer with same hyperparameters often leads to slower convergence. More gradients, from more samples, are averaged which leads to a smaller variance in the batch gradient overall. One simple trick to mitigate this is to increase the learning rate by the same factor: so in this case, from 0.001 to 0.004. + + +```python +batch_size = batch_size * 4 +print('batch_size: {}'.format(batch_size)) +learning_rate = learning_rate * 4 +print('learning_rate: {}'.format(learning_rate)) +dataloader = mx.gluon.data.DataLoader(dataset, + batch_size=batch_size, + shuffle=True, + last_batch="discard", + num_workers=num_workers) +trainer = mx.gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': learning_rate}) +single_forward(net, dataloader) +``` + + batch_size: 64 + learning_rate: 0.004 + + + +```python +print('\n', '### benchmark_dataloader', '\n') +benchmark_dataloader(dataloader) +print('\n', '### benchmark_network', '\n') +data, label = next(iter(dataloader)) +data = data.as_in_context(ctx) +label = label.as_in_context(ctx) +benchmark_network(data, label, net, loss_fn, trainer) +``` + + + ### benchmark_dataloader + + ........................ + + total startup time: 0.7195 + average iterations/sec: 11.7149 + average samples/sec: 749.7521 + + ### benchmark_network + + ........................ + + average iterations/sec: 5.5370 + average samples/sec: 354.3710 + + +Once again, we see improvements in throughput. ~30% higher this time. Checking GPU memory usage, we still have room to increase the batch size higher than 64 (on NVIDIA Tesla V100). When the batch size starts to reach very large numbers (>512), simple tricks such as linear scaling of the learning rate might be insufficient for maintaining good convergence. Consider using a [warm-up learning rate schedule](https://mxnet.incubator.apache.org/versions/master/tutorials/gluon/learning_rate_schedules_advanced.html) and changing to specialized optimizers such as [LBSGD](https://mxnet.incubator.apache.org/api/python/optimization/optimization.html#mxnet.optimizer.LBSGD). + +## Tips & Tricks #4: Using Mixed-Precision (`float32` and `float16`) + +Model execution is still our bottleneck, so let's try out a new trick called [mixed precision](https://mxnet.incubator.apache.org/versions/master/faq/float16.html) training. Some recent GPUs have cores that are optimized for 'half-precision' (i.e. `float16`) operations and they can be much faster than their 'full-precision' (i.e. `float32`) counterparts. Given all of the randomness already in neural network training, this reduction in precision doesn't significantly impact the model accuracy in many cases. Convergence is slightly better when you keep the network parameters at full-precision but forward and backward passes can be performed at half-precision: hence the term 'mixed-precision'. Also check out [Automatic Mixed Precision](https://mxnet.incubator.apache.org/versions/master/tutorials/amp/amp_tutorial.html) (AMP) for a more automated way of optimizing your network. + +We need to `cast` the network to `'float16'`, configure our optimizer to use `multi_precision` and convert our input data types to `'float16'` too. + + +```python +net.cast('float16') +trainer = mx.gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': learning_rate, + 'multi_precision': True}) +single_forward(net, dataloader, dtype='float16') +``` + + +```python +print('\n', '### benchmark_dataloader', '\n') +benchmark_dataloader(dataloader) +print('\n', '### benchmark_network', '\n') +data, label = next(iter(dataloader)) +data = data.astype('float16').as_in_context(ctx) +label = label.astype('float16').as_in_context(ctx) +benchmark_network(data, label, net, loss_fn, trainer) +``` + + + ### benchmark_dataloader + + ........................ + + total startup time: 0.7006 + average iterations/sec: 11.6537 + average samples/sec: 745.8338 + + ### benchmark_network + + ........................ + + average iterations/sec: 11.3614 + average samples/sec: 727.1298 + + +Overall we see a substantial increase in training throughput: double compared to full-precision training. + +## Tips & Tricks #5: Others + +Many other tips and tricks exist for optimizing the throughput of training. + +One area we didn't explicitly benchmark in this tutorial is data transfer from CPU to GPU memory. Usually this isn't an issue, but for very large arrays this can become a bottleneck too. You might be able to compress your data significantly before transferring if your data is sparse (i.e. mostly zero values). Check out the [sparse array tutorial](https://mxnet.incubator.apache.org/versions/master/tutorials/index.html) for more details and an example of how this can impact training speed. + +Another useful trick if data pre-processing or data transfer is the bottleneck is pre-fetching batches. You can write your training loop to transfer the next batch of data to GPU before processing the current batch. Once again, this trick is memory permitting. + +And finally, if you are an advanced user, check out the various [environment variables](https://mxnet.incubator.apache.org/faq/env_var.html) that can be configured to change the behaviour of the MXNet backend. + +## Final Benchmark + +We will now combine all of the above tricks and tips in the complete training loop and compare to the initial benchmark. + + +```python +iters = 25 +num_samples = 0 +num_iters = 0 +start_time = time.time() +for iter_idx, (data, label) in enumerate(dataloader): + num_samples += data.shape[0] + num_iters += 1 + data = data.as_in_context(ctx).astype('float16') + label = label.as_in_context(ctx).astype('float16') + with mx.autograd.record(): + pred = net(data) + loss = loss_fn(pred, label) + loss.backward() + trainer.step(data.shape[0]) + metric.update(label, pred) + print('.', end='') + if num_iters >= iters: + break +end_time = time.time() +total_time = end_time - start_time +print('\n') +print('average iterations/sec: {:.4f}'.format(num_iters/total_time)) +print('average samples/sec: {:.4f}'.format(num_samples/total_time)) +``` + + ......................... + + average iterations/sec: 7.9435 + average samples/sec: 508.3821 + + +Using the above tips and tricks we managed to increase the throughput of training by ~600% from the initial benchmark! Our training throughput is less than the throughput of the individual components we tested, but there are additional overheads that we didn't previously measure (such as data transfer to GPU). + +## Conclusion + +We learned a number of tips and tricks to optimize the throughput of training, and they lead to a considerable increase compared to our initial baseline. As general rules, set `num_workers` on the `DataLoader` to >0, and hybridize your network if you're not debugging. You should increase `batch_size` where possible, but do this with care because of its potential effects on convergence. And finally, consider mixed precision training for substantial speed-ups. + +## Recommended Next Steps + +* Use the [MXNet Profiler](https://mxnet.incubator.apache.org/versions/master/tutorials/python/profiler.html) to identify additional bottlenecks and other areas for optimization. +* Check out the [hybridization tutorial](https://mxnet.incubator.apache.org/versions/master/tutorials/gluon/hybrid.html) for more details on how to write custom `HybridBlock`s. +* Consider using [multi-GPU](https://mxnet.incubator.apache.org/versions/master/tutorials/gluon/multi_gpu.html) and [multi-host](https://github.com/apache/incubator-mxnet/tree/master/example/distributed_training) training if you reach the limits of single GPU training. + + diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index 32bdf8aba4cc..d66e6c8d9a25 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -91,6 +91,7 @@ Select API:  * [Image similiarity search with InfoGAN](/tutorials/gluon/info_gan.html) * Practitioner Guides * [Gotchas using NumPy](/tutorials/gluon/gotchas_numpy_in_mxnet.html) + * [Gluon Performance Tips & Tricks](/tutorials/gluon/performance.html) * [Multi-GPU training](/tutorials/gluon/multi_gpu.html) (new!) ([Alternative](http://gluon.mxnet.io/chapter07_distributed-learning/multiple-gpus-gluon.html) External link) * [Checkpointing and Model Serialization (a.k.a. saving and loading)](/tutorials/gluon/save_load_params.html) External link ([Alternative](http://gluon.mxnet.io/chapter03_deep-neural-networks/serialization.html)) * [Distributed Training](https://github.com/apache/incubator-mxnet/tree/master/example/distributed_training) diff --git a/tests/tutorials/test_tutorials.py b/tests/tutorials/test_tutorials.py index b5f84d550636..829d56f419af 100644 --- a/tests/tutorials/test_tutorials.py +++ b/tests/tutorials/test_tutorials.py @@ -114,6 +114,9 @@ def test_gluon_save_load_params(): def test_gluon_hybrid(): assert _test_tutorial_nb('gluon/hybrid') + +def test_gluon_performance(): + assert _test_tutorial_nb('gluon/performance') def test_gluon_pretrained_models(): assert _test_tutorial_nb('gluon/pretrained_models') From d49445fcdc01102bea04e5dd83272f928d557bb6 Mon Sep 17 00:00:00 2001 From: Zhaoqi Zhu Date: Wed, 3 Jul 2019 10:26:04 -0700 Subject: [PATCH 032/813] Updating profiler tutorial to include new custom operator profiling (#15403) * update profiler tutorial * Update profiler.md * Update profiler.md * Update profiler.md * Update docs/tutorials/python/profiler.md Co-Authored-By: Aaron Markham * Update docs/tutorials/python/profiler.md Co-Authored-By: Aaron Markham * Update docs/tutorials/python/profiler.md Co-Authored-By: Aaron Markham * Update profiler.md change image url to dmlc and add a code example * Update profiler.md * Update profiler.md * Update profiler.md * Update profiler.md * Re-trigger build * Update profiler.md --- docs/tutorials/python/profiler.md | 75 +++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/docs/tutorials/python/profiler.md b/docs/tutorials/python/profiler.md index 9eed452c2e27..f2da62833fcf 100644 --- a/docs/tutorials/python/profiler.md +++ b/docs/tutorials/python/profiler.md @@ -206,6 +206,81 @@ Let's zoom in to check the time taken by operators The above picture visualizes the sequence in which the operators were executed and the time taken by each operator. +### Profiling Custom Operators +Should the existing NDArray operators fail to meet all your model's needs, MXNet supports [Custom Operators](https://mxnet.incubator.apache.org/versions/master/tutorials/gluon/customop.html) that you can define in Python. In `forward()` and `backward()` of a custom operator, there are two kinds of code: "pure Python" code (NumPy operators included) and "sub-operators" (NDArray operators called within `forward()` and `backward()`). With that said, MXNet can profile the execution time of both kinds without additional setup. Specifically, the MXNet profiler will break a single custom operator call into a pure Python event and several sub-operator events if there are any. Furthermore, all of those events will have a prefix in their names, which is, conveniently, the name of the custom operator you called. + +Let's try profiling custom operators with the following code example: + +```python + +import mxnet as mx +from mxnet import nd +from mxnet import profiler + +class MyAddOne(mx.operator.CustomOp): + def forward(self, is_train, req, in_data, out_data, aux): + self.assign(out_data[0], req[0], in_data[0]+1) + + def backward(self, req, out_grad, in_data, out_data, in_grad, aux): + self.assign(in_grad[0], req[0], out_grad[0]) + +@mx.operator.register('MyAddOne') +class CustomAddOneProp(mx.operator.CustomOpProp): + def __init__(self): + super(CustomAddOneProp, self).__init__(need_top_grad=True) + + def list_arguments(self): + return ['data'] + + def list_outputs(self): + return ['output'] + + def infer_shape(self, in_shape): + return [in_shape[0]], [in_shape[0]], [] + + def create_operator(self, ctx, shapes, dtypes): + return MyAddOne() + + +inp = mx.nd.zeros(shape=(500, 500)) + +profiler.set_config(profile_all=True, continuous_dump = True) +profiler.set_state('run') + +w = nd.Custom(inp, op_type="MyAddOne") + +mx.nd.waitall() + +profiler.set_state('stop') +profiler.dump() +``` + +Here, we have created a custom operator called `MyAddOne`, and within its `forward()` function, we simply add one to the input. We can visualize the dump file in `chrome://tracing/`: + +![Custom Operator Profiling Screenshot](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/tutorials/python/profiler/profiler_output_custom_operator_chrome.png) + +As shown by the screenshot, in the **Custom Operator** domain where all the custom operator-related events fall into, we can easily visualize the execution time of each segment of `MyAddOne`. We can tell that `MyAddOne::pure_python` is executed first. We also know that `CopyCPU2CPU` and `_plus_scalr` are two "sub-operators" of `MyAddOne` and the sequence in which they are executed. + +Please note that: to be able to see the previously described information, you need to set `profile_imperative` to `True` even when you are using custom operators in [symbolic mode](https://mxnet.incubator.apache.org/versions/master/tutorials/basic/symbol.html) (refer to the code snippet below, which is the symbolic-mode equivelent of the code example above). The reason is that within custom operators, pure python code and sub-operators are still called imperatively. + +```python +# Set profile_all to True +profiler.set_config(profile_all=True, aggregate_stats=True, continuous_dump = True) +# OR, Explicitly Set profile_symbolic and profile_imperative to True +profiler.set_config(profile_symbolic = True, profile_imperative = True, \ + aggregate_stats=True, continuous_dump = True) + +profiler.set_state('run') +# Use Symbolic Mode +a = mx.symbol.Variable('a') +b = mx.symbol.Custom(data=a, op_type='MyAddOne') +c = b.bind(mx.cpu(), {'a': inp}) +y = c.forward() +mx.nd.waitall() +profiler.set_state('stop') +profiler.dump() +``` + ## Advanced: Using NVIDIA Profiling Tools MXNet's Profiler is the recommended starting point for profiling MXNet code, but NVIDIA also provides a couple of tools for low-level profiling of CUDA code: [NVProf](https://devblogs.nvidia.com/cuda-pro-tip-nvprof-your-handy-universal-gpu-profiler/), [Visual Profiler](https://developer.nvidia.com/nvidia-visual-profiler) and [Nsight Compute](https://developer.nvidia.com/nsight-compute). You can use these tools to profile all kinds of executables, so they can be used for profiling Python scripts running MXNet. And you can use these in conjunction with the MXNet Profiler to see high-level information from MXNet alongside the low-level CUDA kernel information. From 5078853650cfe45869b808f3198abc4735b96f25 Mon Sep 17 00:00:00 2001 From: Aaron Markham Date: Wed, 3 Jul 2019 12:40:52 -0700 Subject: [PATCH 033/813] remove comments from nano instructions (#15433) * remove comments * remove java references --- docs/install/install-jetson.md | 60 +--------------------------------- 1 file changed, 1 insertion(+), 59 deletions(-) diff --git a/docs/install/install-jetson.md b/docs/install/install-jetson.md index a93f930a8182..6b0d65060342 100644 --- a/docs/install/install-jetson.md +++ b/docs/install/install-jetson.md @@ -19,7 +19,7 @@ MXNet supports the Ubuntu Arch64 based operating system so you can run MXNet on NVIDIA Jetson Devices, such as the [TX2](http://www.nvidia.com/object/embedded-systems-dev-kits-modules.html) or [Nano](https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit). -These instructions will walk through how to build MXNet and install the corresponding language bindings. Python is the default binding, but you may also try out one of the many language bindings MXNet has to offer. These instructions also cover how to setup MXNet's Java Inference API. +These instructions will walk through how to build MXNet and install MXNet's Python language binding. For the purposes of this install guide we will assume that CUDA is already installed on your Jetson device. The disk image provided by NVIDIA's getting started guides will have the Jetson toolkit preinstalled, and this also includes CUDA. You should double check what versions are installed and which version you plan to use. @@ -61,22 +61,6 @@ sudo pip install \ If you plan to cross-compile you will need to install these dependencies on that computer as well. - - ### Configure CUDA You can check to see what version of CUDA is running with `nvcc`. @@ -132,48 +116,6 @@ install the dependencies in the prerequisites section, then run the pip wheel. sudo pip install mxnet-1.4.0-cp36-cp36m-linux_aarch64.whl ``` - - ## Use a Pre-compiled MXNet Binary If you want to just use a pre-compiled binary you can download it from S3: From fc54781e08c3d20c184fdf108ee993d349542384 Mon Sep 17 00:00:00 2001 From: Haohuan Wang Date: Wed, 3 Jul 2019 17:27:45 -0700 Subject: [PATCH 034/813] enable TensorRT integration with cpp api (#15335) --- cpp-package/example/inference/README.md | 14 +++ .../example/inference/imagenet_inference.cpp | 104 ++++++++++++---- cpp-package/include/mxnet-cpp/MxNetCpp.h | 1 + cpp-package/include/mxnet-cpp/contrib.h | 115 ++++++++++++++++++ cpp-package/include/mxnet-cpp/symbol.h | 17 +++ cpp-package/include/mxnet-cpp/symbol.hpp | 35 ++++++ 6 files changed, 260 insertions(+), 26 deletions(-) create mode 100644 cpp-package/include/mxnet-cpp/contrib.h diff --git a/cpp-package/example/inference/README.md b/cpp-package/example/inference/README.md index 272586da5da9..81cf9d856c23 100644 --- a/cpp-package/example/inference/README.md +++ b/cpp-package/example/inference/README.md @@ -75,6 +75,7 @@ imagenet_inference --symbol_file --num_inference_batches --data_layer_type --gpu + --enableTRT " --benchmark ``` @@ -134,6 +135,19 @@ imagenet_inference.cpp:372: Running the forward pass on model to evaluate the pe imagenet_inference.cpp:387: benchmark completed! imagenet_inference.cpp:388: batch size: 1 num batch: 500 throughput: xxxx imgs/s latency:xxxx ms ``` +For running this example with TensorRT, you can quickly try the following example to run a benchmark test for testing Inception BN: +``` +./imagenet_inference --symbol_file "./model/Inception-BN-symbol.json" --params_file "./model/Inception-BN-0126.params" --batch_size 16 --num_inference_batches 500 --benchmark --enableTRT +``` +Sample output will looks like this (the example is running on a AWS P3.2xl machine): +``` +imagenet_inference.cpp:302: Loading the model from ./model/Inception-BN-symbol.json +build_subgraph.cc:686: start to execute partition graph. +imagenet_inference.cpp:317: Loading the model parameters from ./model/Inception-BN-0126.params +imagenet_inference.cpp:424: Running the forward pass on model to evaluate the performance.. +imagenet_inference.cpp:439: benchmark completed! +imagenet_inference.cpp:440: batch size: 16 num batch: 500 throughput: 6284.78 imgs/s latency:0.159115 ms +``` ## [sentiment_analysis_rnn.cpp]() This example demonstrates how you can load a pre-trained RNN model and use it to predict the sentiment expressed in the given movie review with the MXNet C++ API. The example is capable of processing variable legnth inputs. It performs the following tasks diff --git a/cpp-package/example/inference/imagenet_inference.cpp b/cpp-package/example/inference/imagenet_inference.cpp index 7eaf991ada4e..4f5a3bb8bbe6 100644 --- a/cpp-package/example/inference/imagenet_inference.cpp +++ b/cpp-package/example/inference/imagenet_inference.cpp @@ -82,6 +82,7 @@ class Predictor { const std::string& model_params_file, const Shape& input_shape, bool use_gpu, + bool enable_tensorrt, const std::string& dataset, const int data_nthreads, const std::string& data_layer_type, @@ -98,6 +99,13 @@ class Predictor { bool AdvanceDataIter(int skipped_batches); void LoadModel(const std::string& model_json_file); void LoadParameters(const std::string& model_parameters_file); + void SplitParamMap(const std::map ¶mMap, + std::map *argParamInTargetContext, + std::map *auxParamInTargetContext, + Context targetContext); + void ConvertParamMapToTargetContext(const std::map ¶mMap, + std::map *paramMapInTargetContext, + Context targetContext); void InitParameters(); inline bool FileExists(const std::string &name) { @@ -115,6 +123,7 @@ class Predictor { MXDataIter *val_iter_; bool use_gpu_; + bool enable_tensorrt_; std::string dataset_; int data_nthreads_; std::string data_layer_type_; @@ -134,14 +143,15 @@ class Predictor { * the input shape is required to be in format Shape(1, number_of_channels, height, width) * The input image will be resized to (height x width) size before running the inference. * 4. use_gpu: determine if run inference on GPU - * 5. dataset: data file (.rec) to be used for inference - * 6. data_nthreads: number of threads for data loading - * 7. data_layer_type: data type for data layer - * 8. rgb_mean: mean value to be subtracted on R/G/B channel - * 9. rgb_std: standard deviation on R/G/B channel - * 10. shuffle_chunk_seed: shuffling chunk seed - * 11. seed: shuffling seed - * 12. benchmark: use dummy data for inference + * 5. enable_tensorrt: determine if enable TensorRT + * 6. dataset: data file (.rec) to be used for inference + * 7. data_nthreads: number of threads for data loading + * 8. data_layer_type: data type for data layer + * 9. rgb_mean: mean value to be subtracted on R/G/B channel + * 10. rgb_std: standard deviation on R/G/B channel + * 11. shuffle_chunk_seed: shuffling chunk seed + * 12. seed: shuffling seed + * 13. benchmark: use dummy data for inference * * The constructor will: * 1. Create ImageRecordIter based on the given dataset file. @@ -152,6 +162,7 @@ Predictor::Predictor(const std::string& model_json_file, const std::string& model_params_file, const Shape& input_shape, bool use_gpu, + bool enable_tensorrt, const std::string& dataset, const int data_nthreads, const std::string& data_layer_type, @@ -161,6 +172,7 @@ Predictor::Predictor(const std::string& model_json_file, int seed, bool benchmark) : input_shape_(input_shape), use_gpu_(use_gpu), + enable_tensorrt_(enable_tensorrt), dataset_(dataset), data_nthreads_(data_nthreads), data_layer_type_(data_layer_type), @@ -182,12 +194,12 @@ Predictor::Predictor(const std::string& model_json_file, // Load the model LoadModel(model_json_file); // Initilize the parameters - // benchmark=false, load parameters from file - // benchmark=true, randomly initialize parameters - if (!benchmark_) { - LoadParameters(model_params_file); - } else { + // benchmark=true && model_params_file.empty(), randomly initialize parameters + // else, load parameters + if (benchmark_ && model_params_file.empty()) { InitParameters(); + } else { + LoadParameters(model_params_file); } int dtype = GetDataLayerType(); @@ -289,9 +301,11 @@ void Predictor::LoadModel(const std::string& model_json_file) { } LG << "Loading the model from " << model_json_file << std::endl; net_ = Symbol::Load(model_json_file); + if (enable_tensorrt_) { + net_ = net_.GetBackendSymbol("TensorRT"); + } } - /* * The following function loads the model parameters. */ @@ -303,20 +317,50 @@ void Predictor::LoadParameters(const std::string& model_parameters_file) { LG << "Loading the model parameters from " << model_parameters_file << std::endl; std::map parameters; NDArray::Load(model_parameters_file, 0, ¶meters); - for (const auto &k : parameters) { - if (k.first.substr(0, 4) == "aux:") { - auto name = k.first.substr(4, k.first.size() - 4); - aux_map_[name] = k.second.Copy(global_ctx_); - } - if (k.first.substr(0, 4) == "arg:") { - auto name = k.first.substr(4, k.first.size() - 4); - args_map_[name] = k.second.Copy(global_ctx_); - } + if (enable_tensorrt_) { + std::map intermediate_args_map; + std::map intermediate_aux_map; + SplitParamMap(parameters, &intermediate_args_map, &intermediate_aux_map, Context::cpu()); + contrib::InitTensorRTParams(net_, &intermediate_args_map, &intermediate_aux_map); + ConvertParamMapToTargetContext(intermediate_args_map, &args_map_, global_ctx_); + ConvertParamMapToTargetContext(intermediate_aux_map, &aux_map_, global_ctx_); + } else { + SplitParamMap(parameters, &args_map_, &aux_map_, global_ctx_); } /*WaitAll is need when we copy data between GPU and the main memory*/ NDArray::WaitAll(); } +/* + * The following function split loaded param map into arg parm + * and aux param with target context + */ +void Predictor::SplitParamMap(const std::map ¶mMap, + std::map *argParamInTargetContext, + std::map *auxParamInTargetContext, + Context targetContext) { + for (const auto& pair : paramMap) { + std::string type = pair.first.substr(0, 4); + std::string name = pair.first.substr(4); + if (type == "arg:") { + (*argParamInTargetContext)[name] = pair.second.Copy(targetContext); + } else if (type == "aux:") { + (*auxParamInTargetContext)[name] = pair.second.Copy(targetContext); + } + } +} + +/* + * The following function copy the param map into the target context + */ +void Predictor::ConvertParamMapToTargetContext(const std::map ¶mMap, + std::map *paramMapInTargetContext, + Context targetContext) { + for (const auto& pair : paramMap) { + (*paramMapInTargetContext)[pair.first] = pair.second.Copy(targetContext); + } +} + /* * The following function randomly initializes the parameters when benchmark_ is true. */ @@ -517,6 +561,8 @@ void printUsage() { << "--data_layer_type " << std::endl << "--gpu " << std::endl + << "--enableTRT " << std::endl << "--benchmark " << std::endl; } @@ -528,6 +574,7 @@ int main(int argc, char** argv) { std::string input_rgb_mean("0 0 0"); std::string input_rgb_std("1 1 1"); bool use_gpu = false; + bool enable_tensorrt = false; bool benchmark = false; int batch_size = 64; int num_skipped_batches = 0; @@ -575,6 +622,9 @@ int main(int argc, char** argv) { data_layer_type = (index < argc ? argv[index]:data_layer_type); } else if (strcmp("--gpu", argv[index]) == 0) { use_gpu = true; + } else if (strcmp("--enableTRT", argv[index]) == 0) { + use_gpu = true; + enable_tensorrt = true; } else if (strcmp("--benchmark", argv[index]) == 0) { benchmark = true; } else if (strcmp("--help", argv[index]) == 0) { @@ -584,7 +634,9 @@ int main(int argc, char** argv) { index++; } - if (model_file_json.empty() || (!benchmark && model_file_params.empty())) { + if (model_file_json.empty() + || (!benchmark && model_file_params.empty()) + || (enable_tensorrt && model_file_params.empty())) { LG << "ERROR: Model details such as symbol, param files are not specified"; printUsage(); return 1; @@ -597,8 +649,8 @@ int main(int argc, char** argv) { std::vector rgb_std = createVectorFromString(input_rgb_std); // Initialize the predictor object - Predictor predict(model_file_json, model_file_params, input_data_shape, use_gpu, dataset, - data_nthreads, data_layer_type, rgb_mean, rgb_std, shuffle_chunk_seed, + Predictor predict(model_file_json, model_file_params, input_data_shape, use_gpu, enable_tensorrt, + dataset, data_nthreads, data_layer_type, rgb_mean, rgb_std, shuffle_chunk_seed, seed, benchmark); if (benchmark) { diff --git a/cpp-package/include/mxnet-cpp/MxNetCpp.h b/cpp-package/include/mxnet-cpp/MxNetCpp.h index 7ac039dd8816..a513565377fd 100644 --- a/cpp-package/include/mxnet-cpp/MxNetCpp.h +++ b/cpp-package/include/mxnet-cpp/MxNetCpp.h @@ -39,5 +39,6 @@ #include "mxnet-cpp/io.hpp" #include "mxnet-cpp/metric.h" #include "mxnet-cpp/initializer.h" +#include "mxnet-cpp/contrib.h" #endif // MXNET_CPP_MXNETCPP_H_ diff --git a/cpp-package/include/mxnet-cpp/contrib.h b/cpp-package/include/mxnet-cpp/contrib.h new file mode 100644 index 000000000000..890ab2bf0062 --- /dev/null +++ b/cpp-package/include/mxnet-cpp/contrib.h @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! +* Copyright (c) 2019 by Contributors +* \file contrib.h +* \brief utility function to enable some contrib features +* \author Haohuan Wang +*/ +#ifndef MXNET_CPP_CONTRIB_H_ +#define MXNET_CPP_CONTRIB_H_ + +#include +#include +#include +#include +#include "mxnet-cpp/symbol.h" + +namespace mxnet { +namespace cpp { +namespace details { + + /*! + * split a string with the given delimiter + * @param str string to be parsed + * @param delimiter delimiter + * @return delimited list of string + */ + inline std::vector split(const std::string& str, const std::string& delimiter) { + std::vector splitted; + size_t last = 0; + size_t next = 0; + while ((next = str.find(delimiter, last)) != std::string::npos) { + splitted.push_back(str.substr(last, next - last)); + last = next + 1; + } + splitted.push_back(str.substr(last)); + return splitted; + } + +} // namespace details + +namespace contrib { + + // needs to be same with + // https://github.com/apache/incubator-mxnet/blob/1c874cfc807cee755c38f6486e8e0f4d94416cd8/src/operator/subgraph/tensorrt/tensorrt-inl.h#L190 + static const std::string TENSORRT_SUBGRAPH_PARAM_IDENTIFIER = "subgraph_params_names"; + // needs to be same with + // https://github.com/apache/incubator-mxnet/blob/master/src/operator/subgraph/tensorrt/tensorrt.cc#L244 + static const std::string TENSORRT_SUBGRAPH_PARAM_PREFIX = "subgraph_param_"; + /*! + * this is a mimic to https://github.com/apache/incubator-mxnet/blob/master/python/mxnet/contrib/tensorrt.py#L37 + * @param symbol symbol that already called subgraph api + * @param argParams original arg params, params needed by tensorrt will be removed after calling this function + * @param auxParams original aux params, params needed by tensorrt will be removed after calling this function + */ + inline void InitTensorRTParams(const mxnet::cpp::Symbol& symbol, + std::map *argParams, + std::map *auxParams) { + mxnet::cpp::Symbol internals = symbol.GetInternals(); + mx_uint numSymbol = internals.GetNumOutputs(); + for (mx_uint i = 0; i < numSymbol; ++i) { + std::map attrs = internals[i].ListAttributes(); + if (attrs.find(TENSORRT_SUBGRAPH_PARAM_IDENTIFIER) != attrs.end()) { + std::string new_params_names; + std::map tensorrtParams; + std::vector keys = details::split( + attrs[TENSORRT_SUBGRAPH_PARAM_IDENTIFIER], ";"); + for (const auto& key : keys) { + if (argParams->find(key) != argParams->end()) { + new_params_names += key + ";"; + tensorrtParams[TENSORRT_SUBGRAPH_PARAM_PREFIX + key] = (*argParams)[key]; + argParams->erase(key); + } else if (auxParams->find(key) != auxParams->end()) { + new_params_names += key + ";"; + tensorrtParams[TENSORRT_SUBGRAPH_PARAM_PREFIX + key] = (*auxParams)[key]; + auxParams->erase(key); + } + } + std::map new_attrs = {}; + for (const auto& kv : tensorrtParams) { + // passing the ndarray address into TRT node attributes to get the weight + uint64_t address = reinterpret_cast(kv.second.GetHandle()); + new_attrs[kv.first] = std::to_string(address); + } + if (!new_attrs.empty()) { + internals[i].SetAttributes(new_attrs); + internals[i].SetAttribute(TENSORRT_SUBGRAPH_PARAM_IDENTIFIER, + new_params_names.substr(0, new_params_names.length() - 1)); + } + } + } +} + +} // namespace contrib +} // namespace cpp +} // namespace mxnet + +#endif // MXNET_CPP_CONTRIB_H_ diff --git a/cpp-package/include/mxnet-cpp/symbol.h b/cpp-package/include/mxnet-cpp/symbol.h index a25824cad602..d72eeaad1a5a 100644 --- a/cpp-package/include/mxnet-cpp/symbol.h +++ b/cpp-package/include/mxnet-cpp/symbol.h @@ -178,6 +178,23 @@ class Symbol { std::vector ListOutputs() const; /*! \return get the descriptions of auxiliary data for this symbol */ std::vector ListAuxiliaryStates() const; + /*! \return get all attributes for this symbol */ + std::map ListAttributes() const; + /*! + * \brief set key-value attribute to the symbol + * @param key string represent the key for the attribute + * @param value string represent the value for the attribute + */ + void SetAttribute(const std::string& key, const std::string& value); + /*! + * \brief set a series of key-value attribute to the symbol + * @param attrs string:string map represent the key value attributes + */ + void SetAttributes(const std::map& attrs); + /*! \return get number of outputs for this symbol */ + mx_uint GetNumOutputs() const; + /*! \return get the new symbol through subgraph API for this symbol */ + mxnet::cpp::Symbol GetBackendSymbol(const std::string& backendName) const; /*! \return get the name of the symbol */ std::string GetName() const; /*! diff --git a/cpp-package/include/mxnet-cpp/symbol.hpp b/cpp-package/include/mxnet-cpp/symbol.hpp index 2e3fb7a2d5de..811d894e0ffa 100644 --- a/cpp-package/include/mxnet-cpp/symbol.hpp +++ b/cpp-package/include/mxnet-cpp/symbol.hpp @@ -172,6 +172,41 @@ inline std::vector Symbol::ListAuxiliaryStates() const { return ret; } +inline std::map Symbol::ListAttributes() const { + mx_uint size; + const char** pairs; + CHECK_EQ(MXSymbolListAttrShallow(GetHandle(), &size, &pairs), 0); + std::map attributes; + for (mx_uint i = 0; i < size; ++i) { + // pairs is 2 * size with key, value pairs according to + // https://github.com/apache/incubator-mxnet/blob/master/include/mxnet/c_api.h#L1428 + attributes[pairs[2 * i]] = pairs[2 * i + 1]; + } + return attributes; +} + +inline void Symbol::SetAttribute(const std::string &key, const std::string &value) { + CHECK_EQ(MXSymbolSetAttr(GetHandle(), key.c_str(), value.c_str()), 0); +} + +inline void Symbol::SetAttributes(const std::map &attrs) { + for (const auto& kv : attrs) { + SetAttribute(kv.first, kv.second); + } +} + +inline mx_uint Symbol::GetNumOutputs() const { + mx_uint numOutputs; + CHECK_EQ(MXSymbolGetNumOutputs(GetHandle(), &numOutputs), 0); + return numOutputs; +} + +inline mxnet::cpp::Symbol Symbol::GetBackendSymbol(const std::string &backendName) const { + SymbolHandle symbolHandle; + CHECK_EQ(MXGenBackendSubgraph(GetHandle(), backendName.c_str(), &symbolHandle), 0); + return mxnet::cpp::Symbol(symbolHandle); +} + inline std::string Symbol::GetName() const { int success; const char* out_name; From faccc59bc0ed7e22933c1f86f3aabac6f13fe1a9 Mon Sep 17 00:00:00 2001 From: Pedro Larroy Date: Wed, 3 Jul 2019 17:36:56 -0700 Subject: [PATCH 035/813] Fix memory leak in NaiveEngine (#15405) * Fix memory leak in NaiveEngine Fixes #15375 * Fix lint --- src/engine/naive_engine.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/engine/naive_engine.cc b/src/engine/naive_engine.cc index 9f44b55cfcaf..9cfe9b28f862 100644 --- a/src/engine/naive_engine.cc +++ b/src/engine/naive_engine.cc @@ -159,15 +159,18 @@ class NaiveEngine final : public Engine { NaiveEngine::OnComplete, nullptr); this->req_completed_ = false; profiler::Profiler *profiler = profiler::Profiler::Get(); - NaiveOpr *opr = nullptr; + auto opr_deleter = [this](NaiveOpr* p) { + this->DeleteOperator(p); + }; + std::unique_ptr opr(nullptr, opr_deleter); const bool profiling = opr_name && profiler->IsProfiling(profiler::Profiler::kImperative); // GenerateDisplayName() will return a pointer to the correct name of the operator const char* display_name = profiling ? profiler::CustomOpProfiler::Get()->GenerateDisplayName(opr_name) : opr_name; if (profiling) { - opr = NewOperator(exec_fun, const_vars, mutable_vars, - prop, display_name)->Cast(); + opr.reset(NewOperator(exec_fun, const_vars, mutable_vars, + prop, display_name)->Cast()); opr->profiling = profiling; std::unique_ptr attrs; if (profiler->AggregateEnabled()) { From d7c542af3b5c6ab35acad93d3b1114cd492f98cd Mon Sep 17 00:00:00 2001 From: Thom Lane Date: Wed, 3 Jul 2019 20:13:32 -0700 Subject: [PATCH 036/813] [TUTORIAL] Gluon and Sparse NDArray (#15396) * Added Gluon Sparse NDArray tutorial. * Updates to fix ASCII issue and feedback changes. --- docs/tutorials/index.md | 2 +- docs/tutorials/sparse/train_gluon.md | 470 +++++++++++++++++++++++++++ tests/tutorials/test_tutorials.py | 3 + 3 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 docs/tutorials/sparse/train_gluon.md diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index d66e6c8d9a25..6e31e825e2ca 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -113,9 +113,9 @@ Select API:  * [Difference between reshape and transpose](/tutorials/basic/reshape_transpose.html) * [NDArray Indexing](https://mxnet.incubator.apache.org/tutorials/basic/ndarray_indexing.html) * Sparse NDArray + * [Introduction to Sparse NDArray](/tutorials/sparse/train_gluon.html) * [Sparse Gradient Updates (RowSparseNDArray)](/tutorials/sparse/row_sparse.html) * [Compressed Sparse Row Storage Format (CSRNDArray)](/tutorials/sparse/csr.html) - * [Linear Regression with Sparse Symbols](/tutorials/sparse/train.html) * Symbol * [Symbol API](/tutorials/basic/symbol.html) (Caution: written before Gluon existed) * KVStore diff --git a/docs/tutorials/sparse/train_gluon.md b/docs/tutorials/sparse/train_gluon.md new file mode 100644 index 000000000000..e9c5301aed84 --- /dev/null +++ b/docs/tutorials/sparse/train_gluon.md @@ -0,0 +1,470 @@ + + + + + + + + + + + + + + + + + + +# Sparse NDArrays with Gluon + +When working on machine learning problems, you may encounter situations where the input data is sparse (i.e. the majority of values are zero). One example of this is in recommendation systems. You could have millions of user and product features, but only a few of these features are present for each sample. Without special treatment, the sheer magnitude of the feature space can lead to out-of-memory situations and cause significant slowdowns when training and making predictions. + +MXNet supports a number of sparse storage types (often called 'stype' for short) for these situations. In this tutorial, we'll start by generating some sparse data, write it to disk in the LibSVM format and then read back using the [`LibSVMIter`](https://mxnet.incubator.apache.org/api/python/io/io.html) for training. We use the Gluon API to train the model and leverage sparse storage types such as [`CSRNDArray`](https://mxnet.incubator.apache.org/api/python/ndarray/sparse.html?highlight=csrndarray#mxnet.ndarray.sparse.CSRNDArray) and [`RowSparseNDArray`](https://mxnet.incubator.apache.org/api/python/ndarray/sparse.html?highlight=rowsparsendarray#mxnet.ndarray.sparse.RowSparseNDArray) to maximise performance and memory efficiency. + + +```python +import mxnet as mx +import numpy as np +import time +``` + +### Generating Sparse Data + +You will most likely have a sparse dataset in mind already if you're reading this tutorial, but let's create a dummy dataset to use in the examples that follow. Using `rand_ndarray` we will generate 1000 samples, each with 1,000,000 features of which 99.999% of values will be zero (i.e. 10 non-zero features for each sample). We take this as our input data for training and calculate a label based on an arbitrary rule: whether the feature sum is higher than average. + + +```python +num_samples = 1000 +num_features = 1000000 +data = mx.test_utils.rand_ndarray((num_samples, num_features), stype='csr', density=0.00001) +# generate label: 1 if row sum above average, 0 otherwise. +label = data.sum(axis=1) > data.sum(axis=1).mean() +``` + + +```python +print(type(data)) +print(data[:10].asnumpy()) +print('{:,.0f} elements'.format(np.product(data.shape))) +print('{:,.0f} non-zero elements'.format(data.data.size)) +``` + + + [[0. 0. 0. ... 0. 0. 0.] + [0. 0. 0. ... 0. 0. 0.] + [0. 0. 0. ... 0. 0. 0.] + ... + [0. 0. 0. ... 0. 0. 0.] + [0. 0. 0. ... 0. 0. 0.] + [0. 0. 0. ... 0. 0. 0.]] + 1,000,000,000 elements + 10,000 non-zero elements + + +Our storage type is CSR (Compressed Sparse Row) which is the ideal type for sparse data along multiple axes. See [this in-depth tutorial](https://mxnet.incubator.apache.org/versions/master/tutorials/sparse/csr.html) for more information. Just to confirm the generation process ran correctly, we can see that the vast majority of values are indeed zero. One of the first questions to ask would be how much memory is saved by storing this data in a [`CSRNDArray`](https://mxnet.incubator.apache.org/api/python/ndarray/sparse.html?highlight=csrndarray#mxnet.ndarray.sparse.CSRNDArray) versus a standard [`NDArray`](https://mxnet.incubator.apache.org/versions/master/api/python/ndarray/sparse.html?highlight=ndarray#module-mxnet.ndarray). Since sparse arrays are constructed from many components (e.g. `data`, `indices` and `indptr`) we define a function called `get_nbytes` to calculate the number of bytes taken in memory to store an array. We compare the same data stored in a standard [`NDArray`](https://mxnet.incubator.apache.org/versions/master/api/python/ndarray/sparse.html?highlight=ndarray#module-mxnet.ndarray) (with `data.tostype('default')`) to the [`CSRNDArray`](https://mxnet.incubator.apache.org/api/python/ndarray/sparse.html?highlight=csrndarray#mxnet.ndarray.sparse.CSRNDArray). + + +```python +def get_nbytes(array): + fn = lambda a: a.size * np.dtype(a).itemsize + if isinstance(array, mx.ndarray.sparse.CSRNDArray): + return fn(array.data) + fn(array.indices) + fn(array.indptr) + elif isinstance(array, mx.ndarray.sparse.RowSparseNDArray): + return fn(array.data) + fn(array.indices) + elif isinstance(array, mx.ndarray.NDArray): + return fn(array) + else: + TypeError('{} not supported'.format(type(array))) +``` + + +```python +print('NDarray:', get_nbytes(data.tostype('default'))/1000000, 'MBs') +print('CSRNDArray', get_nbytes(data)/1000000, 'MBs') +``` + + NDarray: 4000.0 MBs + CSRNDArray 0.128008 MBs + + +Given the extremely high sparsity of the data, we observe a huge memory saving here! 0.13 MBs versus 4 GBs: ~30,000 times smaller. You can experiment with the amount of sparsity and see how these two storage types compare. When the number of non-zero values increases, this difference will reduce. And when the number of non-zero values exceeds ~1/3 you will find that this sparse storage type take more memory than dense! So use wisely. + +### Writing Sparse Data + +Since there is such a large size difference between dense and sparse storage formats here, we ideally want to store the data on disk in a sparse storage format too. MXNet supports a format called LibSVM and has a data iterator called [`LibSVMIter`](https://mxnet.incubator.apache.org/api/python/io/io.html?highlight=libsvmiter) specifically for data formatted this way. + +A LibSVM file has a row for each sample, and each row starts with the label: in this case `0.0` or `1.0` since we have a classification task. After this we have a variable number of `key:value` pairs separated by spaces, where the key is column/feature index and the value is the value of that feature. When working with your own sparse data in a custom format you should try to convert your data into this format. We define a `save_as_libsvm` function to save the `data` ([`CSRNDArray`](https://mxnet.incubator.apache.org/versions/master/api/python/ndarray/sparse.html?highlight=csrndarray#mxnet.ndarray.sparse.CSRNDArray)) and `label` (`NDArray`) to disk in LibSVM format. + + +```python +def save_as_libsvm(filepath, data, label): + with open(filepath, 'w') as openfile: + for row_idx in range(data.shape[0]): + data_sample = data[row_idx] + label_sample = label[row_idx] + col_idxs = data_sample.indices.asnumpy().tolist() + values = data_sample.data.asnumpy().tolist() + label_str = str(label_sample.asscalar()) + value_strs = ['{}:{}'.format(idx, value) for idx, value in zip(col_idxs, values)] + value_str = " ".join(value_strs) + sample_str = '{} {}\n'.format(label_str, value_str) + openfile.write(sample_str) +``` + + +```python +filepath = 'dataset.libsvm' +save_as_libsvm(filepath, data, label) +``` + +We have now written the `data` and `label` to disk, and can inspect the first 10 lines of the file: + + +```python +with open(filepath, 'r') as openfile: + lines = [openfile.readline() for _ in range(10)] +for line in lines: + print(line[:80] + '...' if len(line) > 80 else line) +``` + + 0.0 35454:0.22486156225204468 80954:0.39130592346191406 81941:0.1988530308008194... + 1.0 37029:0.5980494618415833 52916:0.15797750651836395 71623:0.32251599431037903... + 1.0 89962:0.47770974040031433 216426:0.21326342225074768 271027:0.18589609861373... + 1.0 7071:0.9432336688041687 81664:0.7788773775100708 117459:0.8166475296020508 4... + 0.0 380966:0.16906292736530304 394363:0.7987179756164551 458442:0.56873309612274... + 0.0 89361:0.9099966287612915 141813:0.5927085280418396 282489:0.293381005525589 ... + 0.0 150427:0.4747847020626068 169376:0.2603490948677063 179377:0.237988427281379... + 0.0 49774:0.2822582423686981 91245:0.5794865489006042 102970:0.7004560232162476 ... + 1.0 97133:0.0024336236529052258 109855:0.9895315766334534 116765:0.2465638816356... + 0.0 803440:0.4020800292491913 + + + +Some storage overhead is introduced by serializing the data as characters (with spaces and colons). `dataset.libsvm` is 250 KBs but the original `data` and `label` were 132 KBs combined. Compared with the 4GB dense `NDArray` though, this isn't a huge issue. + +### Reading Sparse Data + +Using [`LibSVMIter`](https://mxnet.incubator.apache.org/api/python/io/io.html?highlight=libsvmiter), we can quickly and easily load data into batches ready for training. Although Gluon [`Dataset`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/data.html?highlight=dataset#mxnet.gluon.data.Dataset)s can be written to return sparse arrays, Gluon [`DataLoader`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/data.html?highlight=dataloader#mxnet.gluon.data.DataLoader)s currently convert each sample to dense before stacking up to create the batch. As a result, [`LibSVMIter`](https://mxnet.incubator.apache.org/api/python/io/io.html?highlight=libsvmiter) is the recommended method of loading sparse data in batches. + +Similar to using a [`DataLoader`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/data.html?highlight=dataloader#mxnet.gluon.data.DataLoader), you must specify the required `batch_size`. Since we're dealing with sparse data and the column shape isn't explicitly stored in the LibSVM file, we additionally need to provide the shape of the data and label. Our [`LibSVMIter`](https://mxnet.incubator.apache.org/api/python/io/io.html?highlight=libsvmiter) returns batches in a slightly different form to a [`DataLoader`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/data.html?highlight=dataloader#mxnet.gluon.data.DataLoader). We get `DataBatch` objects instead of `tuple`. See the [appendix of this tutorial](https://mxnet.incubator.apache.org/versions/master/tutorials/gluon/datasets.html) for more information. + + +```python +data_iter = mx.io.LibSVMIter(data_libsvm='test.libsvm', data_shape=(num_features,), label_shape=(1,), batch_size=10) +for batch in data_iter: + data = batch.data[0] + print('data.stype: {}'.format(data.stype)) + label = batch.label[0] + print('label.stype: {}'.format(label.stype)) + break +``` + + data.stype: csr + label.stype: default + + +We can see that `data` and `label` are in the appropriate storage formats, given their sparse and dense values respectively. We can avoid out-of-memory issues that might have occurred if `data` was in dense storage format. Another benefit of storing the data efficiently is the reduced data transfer time when using GPUs. Although the transfer time for a single batch is small, we transfer `data` and `label` to the GPU every iteration so this time can become significant. We will time the transfer of the sparse `data` to GPU (if available) and compare to the time for its dense counterpart. + + +```python +ctx = mx.gpu() if mx.test_utils.list_gpus() else mx.cpu() +``` + + +```python +%%timeit +data_on_ctx = data.as_in_context(ctx) +data_on_ctx.wait_to_read() +``` + + 192 microseconds +- 51.1 microseconds per loop (mean +- std. dev. of 7 runs, 1 loop each) + + + +```python +print('sparse batch: {} MBs'.format(get_nbytes(data)/1000000)) +data = data.tostype('default') # avoid timing this sparse to dense conversion +print('dense batch: {} MBs'.format(get_nbytes(data)/1000000)) +``` + + sparse batch: 0.001348 MBs + dense batch: 40.0 MBs + + + +```python +%%timeit +data_on_ctx = data.as_in_context(ctx) +data_on_ctx.wait_to_read() +``` + + 4 ms +- 36.8 microseconds per loop (mean +- std. dev. of 7 runs, 100 loops each) + + +Although results will change depending on system specifications and degree of sparsity, the sparse array can be transferred from CPU to GPU significantly faster than the dense array. We see a ~25x speed up for sparse vs dense for this specific batch of data. + +## Gluon Models for Sparse Data + +Our next step is to define a network. We have an input of 1,000,000 features and we want to make a binary prediction. We don't have any spatial or temporal relationships between features, so we'll use a 3 layer fully-connected network where the last layer has 1 output unit (with sigmoid activation). Since we're working with sparse data, we'd ideally like to use network operators that can exploit this sparsity for improved performance and memory efficiency. + +Gluon's [`nn.Dense`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/nn.html?highlight=dense#mxnet.gluon.nn.Dense) block can used with [`CSRNDArray`](https://mxnet.incubator.apache.org/api/python/ndarray/sparse.html?highlight=csrndarray#mxnet.ndarray.sparse.CSRNDArray) input arrays but it doesn't exploit the sparsity. Under the hood, [`Dense`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/nn.html?highlight=dense#mxnet.gluon.nn.Dense) uses the [`FullyConnected`](https://mxnet.incubator.apache.org/versions/master/api/python/ndarray/ndarray.html?highlight=fullyconnected#mxnet.ndarray.FullyConnected) operator which isn't optimized for [`CSRNDArray`](https://mxnet.incubator.apache.org/api/python/ndarray/sparse.html?highlight=csrndarray#mxnet.ndarray.sparse.CSRNDArray) arrays. We'll implement a `Block` that does exploit this sparsity, *but first*, let's just remind ourselves of the [`Dense`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/nn.html?highlight=dense#mxnet.gluon.nn.Dense) implementation by creating an equivalent `Block` called `FullyConnected`. + + +```python +class FullyConnected(mx.gluon.HybridBlock): + def __init__(self, in_units, units): + super(FullyConnected, self).__init__() + with self.name_scope(): + self._units = units + self.weight = self.params.get('weight', shape=(units, in_units), + init=None, allow_deferred_init=True, + dtype='float32', stype='default', grad_stype='default') + self.bias = self.params.get('bias', shape=(units), + init='zeros', allow_deferred_init=True, + dtype='float32', stype='default', grad_stype='default') + + def hybrid_forward(self, F, x, weight, bias): + return F.FullyConnected(x, weight, bias, num_hidden=self._units) +``` + +Our `weight` and `bias` parameters are dense (see `stype='default'`) and so are their gradients (see `grad_stype='default'`). Our `weight` parameter has shape `(units, in_units)` because the [`FullyConnected`](https://mxnet.incubator.apache.org/versions/master/api/python/ndarray/ndarray.html?highlight=fullyconnected#mxnet.ndarray.FullyConnected) operator performs the following calculation: + +$$Y = XW^T + b$$ + +We could instead have created our parameter with shape `(in_units, units)` and avoid the transpose of the weight matrix. We'll see why this is so important later on. And instead of [`FullyConnected`](https://mxnet.incubator.apache.org/versions/master/api/python/ndarray/ndarray.html?highlight=fullyconnected#mxnet.ndarray.FullyConnected) we could have used [`mx.sparse.dot`](https://mxnet.incubator.apache.org/versions/master/api/python/ndarray/sparse.html?highlight=sparse.dot#mxnet.ndarray.sparse.dot) to fully exploit the sparsity of the [`CSRNDArray`](https://mxnet.incubator.apache.org/api/python/ndarray/sparse.html?highlight=csrndarray#mxnet.ndarray.sparse.CSRNDArray) input arrays. We'll now implement an alternative `Block` called `FullyConnectedSparse` using these ideas. We take `grad_stype` of the `weight` as an argument (called `weight_grad_stype`), since we're going to change this later on. + + +```python +class FullyConnectedSparse(mx.gluon.HybridBlock): + def __init__(self, in_units, units, weight_grad_stype='default'): + super(FullyConnectedSparse, self).__init__() + with self.name_scope(): + self._units = units + self.weight = self.params.get('weight', shape=(in_units, units), + init=None, allow_deferred_init=True, + dtype='float32', stype='default', grad_stype=weight_grad_stype) + self.bias = self.params.get('bias', shape=(units), + init='zeros', allow_deferred_init=True, + dtype='float32', stype='default', grad_stype='default') + + def hybrid_forward(self, F, x, weight, bias): + return F.sparse.dot(x, weight) + bias +``` + +Once again, we're using a dense `weight`, so both `FullyConnected` and `FullyConnectedSparse` will return dense array outputs. When constructing a multi-layer network therefore, only the first layer needs to be optimized for sparse inputs. Our first layer is often responsible for reducing the feature dimension dramatically (e.g. 1,000,000 features down to 128 features). We'll set the number of units in our 3 layers to be 128, 8 and 1. + +We will use [`timeit`](https://docs.python.org/2/library/timeit.html) to check the performance of these two variants, and analyse some [MXNet Profiler](https://mxnet.incubator.apache.org/versions/master/tutorials/python/profiler.html) traces that have been created from these benchmarks. Additionally, we will inspect the memory usage of the weights (and gradients) using the `print_memory_allocation` function defined below: + + +```python +def print_memory_allocation(net, block_idxs): + blocks = [net[block_idx] for block_idx in block_idxs] + weight_nbytes = [get_nbytes(b.weight.data()) for b in blocks] + weight_nbytes_pct = [b/sum(weight_nbytes) for b in weight_nbytes] + weight_grad_nbytes = [get_nbytes(b.weight.grad()) for b in blocks] + weight_grad_nbytes_pct = [b/sum(weight_grad_nbytes) for b in weight_grad_nbytes] + print("Memory Allocation for Weight:") + for i in range(len(block_idxs)): + print('{:7.3f} MBs ({:7.3f}%) for {:<40}'.format(weight_nbytes[i]/1000000, + weight_nbytes_pct[i]*100, + blocks[i].name)) + print("Memory Allocation for Weight Gradient:") + for i in range(len(block_idxs)): + print('{:7.3f} MBs ({:7.3f}%) for {:<40}'.format(weight_grad_nbytes[i]/1000000, + weight_grad_nbytes_pct[i]*100, + blocks[i].name)) +``` + +### Benchmark: `FullyConnected` + +We'll create a network using `nn.Dense` and benchmark the training. + +```python +net = mx.gluon.nn.Sequential() +net.add( + mx.gluon.nn.Dense(in_units=num_features, units=128), + mx.gluon.nn.Activation('sigmoid'), + mx.gluon.nn.Dense(in_units=128, units=8), + mx.gluon.nn.Activation('sigmoid'), + mx.gluon.nn.Dense(in_units=8, units=1), + mx.gluon.nn.Activation('sigmoid'), +) +net.initialize(ctx=ctx) +trainer = mx.gluon.Trainer(net.collect_params(), 'sgd') +loss_fn = mx.gluon.loss.SigmoidBinaryCrossEntropyLoss() +``` + + +```python +%%timeit +data_iter.reset() +for batch in data_iter: + data = batch.data[0] + data = data.as_in_context(ctx) + label = batch.label[0].as_in_context(ctx) + with mx.autograd.record(): + pred = net(data) + loss = loss_fn(pred, label) + loss.backward() + trainer.step(data.shape[0]) + loss.wait_to_read() +``` + + 532 ms +- 3.47 ms per loop (mean +- std. dev. of 7 runs, 1 loop each) + + +![fully connected](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/doc/tutorials/ndarray/sparse/fully_connected.png) + +We can see the first [`FullyConnected`](https://mxnet.incubator.apache.org/versions/master/api/python/ndarray/ndarray.html?highlight=fullyconnected#mxnet.ndarray.FullyConnected) operator takes a significant proportion of time to execute (~25% of the iteration) because there are 1,000,000 input features (to 128). After this, the other [`FullyConnected`](https://mxnet.incubator.apache.org/versions/master/api/python/ndarray/ndarray.html?highlight=fullyconnected#mxnet.ndarray.FullyConnected) operators are much faster because they have input features of 128 (to 8) and 8 (to 1). On the backward pass, we see the same pattern (but in reverse). And finally, the parameter update step takes a large amount of time on the weight matrix of the first `FullyConnected` `Block`. When checking the memory allocations below, we can see the weight matrix of the first `FullyConnected` `Block` is responsible for 99.999% of the memory compared to other [`FullyConnected`](https://mxnet.incubator.apache.org/versions/master/api/python/ndarray/ndarray.html?highlight=fullyconnected#mxnet.ndarray.FullyConnected) weight matrices. + + +```python +print_memory_allocation(net, block_idxs=[0, 2, 4]) +``` + + Memory Allocation for Weight: + 512.000 MBs ( 99.999%) for dense0 + 0.004 MBs ( 0.001%) for dense1 + 0.000 MBs ( 0.000%) for dense2 + Memory Allocation for Weight Gradient: + 512.000 MBs ( 99.999%) for dense0 + 0.004 MBs ( 0.001%) for dense1 + 0.000 MBs ( 0.000%) for dense2 + + +### Benchmark: `FullyConnectedSparse` + +We will now switch the first layer from `FullyConnected` to `FullyConnectedSparse`. + + +```python +net = mx.gluon.nn.Sequential() +net.add( + FullyConnectedSparse(in_units=num_features, units=128), + mx.gluon.nn.Activation('sigmoid'), + FullyConnected(in_units=128, units=8), + mx.gluon.nn.Activation('sigmoid'), + FullyConnected(in_units=8, units=1), + mx.gluon.nn.Activation('sigmoid'), +) +net.initialize(ctx=ctx) +trainer = mx.gluon.Trainer(net.collect_params(), 'sgd') +loss_fn = mx.gluon.loss.SigmoidBinaryCrossEntropyLoss() +``` + + +```python +%%timeit +data_iter.reset() +for batch in data_iter: + data = batch.data[0] + data = data.as_in_context(ctx) + label = batch.label[0].as_in_context(ctx) + with mx.autograd.record(): + pred = net(data) + loss = loss_fn(pred, label) + loss.backward() + trainer.step(data.shape[0]) + loss.wait_to_read() +``` + + 528 ms +- 22.7 ms per loop (mean +- std. dev. of 7 runs, 1 loop each) + + +![fully connected sparse](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/doc/tutorials/ndarray/sparse/fully_connected_sparse.png) + +We see the forward pass of `dot` and `add` (equivalent to [`FullyConnected`](https://mxnet.incubator.apache.org/versions/master/api/python/ndarray/ndarray.html?highlight=fullyconnected#mxnet.ndarray.FullyConnected) operator) is much faster now: 1.54ms vs 0.26ms. And this explains the reduction in overall time for the epoch. We didn't gain any benefit on the backward pass or parameter updates though. + +![fully connected sparse backward](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/doc/tutorials/ndarray/sparse/fully_connected_sparse_backward.png) + +Our first weight matrix and its gradients still take up the same amount of memory as before. + + +```python +print_memory_allocation(net, block_idxs=[0, 2, 4]) +``` + + Memory Allocation for Weight: + 512.000 MBs ( 99.999%) for fullyconnectedsparse0 + 0.004 MBs ( 0.001%) for fullyconnected0 + 0.000 MBs ( 0.000%) for fullyconnected1 + Memory Allocation for Weight Gradient: + 512.000 MBs ( 99.999%) for fullyconnectedsparse0 + 0.004 MBs ( 0.001%) for fullyconnected0 + 0.000 MBs ( 0.000%) for fullyconnected1 + + +### Benchmark: `FullyConnectedSparse` with `grad_stype=row_sparse` + +One useful outcome of sparsity in our [`CSRNDArray`](https://mxnet.incubator.apache.org/api/python/ndarray/sparse.html?highlight=csrndarray#mxnet.ndarray.sparse.CSRNDArray) input is that our gradients will be row sparse. We can exploit this fact to give us potentially huge memory savings and speed improvements. Creating our `weight` parameter with shape `(units, in_units)` and not transposing in the forward pass are important pre-requisite for obtaining row sparse gradients. Using [`nn.Dense`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/nn.html?highlight=dense#mxnet.gluon.nn.Dense) would have led to column sparse gradients which are not supported in MXNet. We previously had `grad_stype` of the `weight` parameter in the first layer set to `'default'` so we were handling the gradient as a dense array. Switching this to `'row_sparse'` can give us these potential improvements. + + +```python +net = mx.gluon.nn.Sequential() +net.add( + FullyConnectedSparse(in_units=num_features, units=128, weight_grad_stype='row_sparse'), + mx.gluon.nn.Activation('sigmoid'), + FullyConnected(in_units=128, units=8), + mx.gluon.nn.Activation('sigmoid'), + FullyConnected(in_units=8, units=1), + mx.gluon.nn.Activation('sigmoid'), +) +net.initialize(ctx=ctx) +trainer = mx.gluon.Trainer(net.collect_params(), 'sgd') +loss_fn = mx.gluon.loss.SigmoidBinaryCrossEntropyLoss() +``` + + +```python +%%timeit +data_iter.reset() +for batch in data_iter: + data = batch.data[0] + data = data.as_in_context(ctx) + label = batch.label[0].as_in_context(ctx) + with mx.autograd.record(): + pred = net(data) + loss = loss_fn(pred, label) + loss.backward() + trainer.step(data.shape[0]) + loss.wait_to_read() +``` + + 334 ms +- 16.9 ms per loop (mean +- std. dev. of 7 runs, 1 loop each) + + +![fully connected sparse grad backward](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/doc/tutorials/ndarray/sparse/fully_connected_sparse_grad_backward.png) + +We can see a huge reduction in the time taken for the backward pass and parameter update step: 3.99ms vs 0.18ms. And this reduces the overall time of the epoch significantly. Our gradient consumes a much smaller amount of memory and means only a subset of parameters need updating as part of the `sgd_update` step. Some optimizers don't support sparse gradients however, so reference the specific optimizer's documentation for more details. + + +```python +print_memory_allocation(net, block_idxs=[0, 2, 4]) +``` + + Memory Allocation for Weight: + 512.000 MBs ( 99.999%) for fullyconnectedsparse1 + 0.004 MBs ( 0.001%) for fullyconnected2 + 0.000 MBs ( 0.000%) for fullyconnected3 + Memory Allocation for Weight Gradient: + 0.059 MBs ( 93.490%) for fullyconnectedsparse1 + 0.004 MBs ( 6.460%) for fullyconnected2 + 0.000 MBs ( 0.050%) for fullyconnected3 + + +### Advanced: Sparse `weight` + +You can optimize this example further by setting the weight's `stype` to `'row_sparse'`, but whether `'row_sparse'` weights make sense or not will depends on your specific task. See [`contrib.SparseEmbedding `](https://github.com/apache/incubator-mxnet/blob/master/python/mxnet/gluon/contrib/nn/basic_layers.py#L118) for an example of this. + +# Conclusion + +As part of this tutorial, we learned how to write sparse data to disk in LibSVM format and load it back in sparse batches with the [`LibSVMIter`](https://mxnet.incubator.apache.org/api/python/io/io.html?highlight=libsvmiter). We learned how to improve the performance of Gluon's [`nn.Dense`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/nn.html?highlight=dense#mxnet.gluon.nn.Dense) on sparse arrays using `mx.nd.sparse`. And lastly, we set `grad_stype` to `'row_sparse'` to reduce the size of the gradient and speed up the parameter update step. + +## Recommended Next Steps + +* More detail on the [`CSRNDArray`](https://mxnet.incubator.apache.org/api/python/ndarray/sparse.html?highlight=csrndarray#mxnet.ndarray.sparse.CSRNDArray) sparse array format can be found in [this tutorial](https://mxnet.incubator.apache.org/versions/master/tutorials/sparse/csr.html). +* More detail on the [`RowSparseNDArray`](https://mxnet.incubator.apache.org/api/python/ndarray/sparse.html?highlight=rowsparsendarray#mxnet.ndarray.sparse.RowSparseNDArray) sparse array format can be found in [this tutorial](https://mxnet.incubator.apache.org/versions/master/tutorials/sparse/row_sparse.html). +* Users of the Module API can see a symbolic only example in [this tutorial](https://mxnet.incubator.apache.org/versions/master/tutorials/sparse/train.html). + + diff --git a/tests/tutorials/test_tutorials.py b/tests/tutorials/test_tutorials.py index 829d56f419af..0c4954acbd8b 100644 --- a/tests/tutorials/test_tutorials.py +++ b/tests/tutorials/test_tutorials.py @@ -193,6 +193,9 @@ def test_sparse_csr(): def test_sparse_train(): assert _test_tutorial_nb('sparse/train') +def test_sparse_train_gluon(): + assert _test_tutorial_nb('sparse/train_gluon') + def test_speech_recognition_ctc(): assert _test_tutorial_nb('speech_recognition/ctc') From 8ebaa5c0384ecbef244150859b3e24ea2f02095d Mon Sep 17 00:00:00 2001 From: sunrongda <38203631+sunrongda@users.noreply.github.com> Date: Thu, 4 Jul 2019 20:02:17 +0800 Subject: [PATCH 037/813] REAME MTCNN Link URL Error in original website (#15020) * eval_metric.py * Update eval_metric.py * Update eval_metric.py * faster cnn * use_global_stats=False use_global_stats=False * readme * Delete symbol_resnet.py * resnet * Update README.md * Update README.md * update the link of MTCNN --- example/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/README.md b/example/README.md index 16b556dcda08..be9903099e19 100644 --- a/example/README.md +++ b/example/README.md @@ -171,7 +171,7 @@ If your tutorial depends on specific packages, simply add them to this provision * [MultiGPU enabled image generative models (GAN and DCGAN)](https://github.com/tqchen/mxnet-gan) by [Tianqi Chen](https://github.com/tqchen) * [Deep reinforcement learning for playing flappybird by mxnet](https://github.com/li-haoran/DRL-FlappyBird) by LIHaoran * [Neural Style in Markov Random Field (MRF) and Perceptual Losses Realtime transfer](https://github.com/zhaw/neural_style) by [zhaw](https://github.com/zhaw) -* [MTCNN Face keypoints detection and alignment](https://pangyupo.github.io/2016/10/22/mxnet-mtcnn/) ([github](https://github.com/pangyupo/mxnet_mtcnn_face_detection)) in Chinese by [pangyupo](https://github.com/pangyupo) +* [MTCNN Face keypoints detection and alignment](https://github.com/YYuanAnyVision/mxnet_mtcnn_face_detection) by [yuanyang](https://github.com/YYuanAnyVision), source code for [paper](https://kpzhang93.github.io/papers/spl.pdf) "Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Neural Networks", [Kaipeng Zhang](https://github.com/kpzhang93), Zhanpeng Zhang, Zhifeng Li and Yu Qiao, IEEE Signal Processing Letters, 23(10), 2016 * [SSD: Single Shot MultiBox Object Detector](https://github.com/zhreshold/mxnet-ssd) by [zhreshold](https://github.com/zhreshold) * [Fast Neural Style in Scala](https://github.com/Ldpe2G/DeepLearningForFun/tree/master/Mxnet-Scala/FastNeuralStyle) by [Ldpe2G](https://github.com/Ldpe2G) * [LSTM Human Activity Recognition](https://github.com/Ldpe2G/DeepLearningForFun/tree/master/Mxnet-Scala/HumanActivityRecognition) by [Ldpe2G](https://github.com/Ldpe2G) From 0ec48864f6edc2b57c0002ff75c94ddfe472e405 Mon Sep 17 00:00:00 2001 From: Pedro Larroy Date: Thu, 4 Jul 2019 14:53:51 -0700 Subject: [PATCH 038/813] =?UTF-8?q?Expose=20get=5Fall=5Fregistered=5Fopera?= =?UTF-8?q?tors=20and=20get=5Foperator=5Farguments=20in=20the=E2=80=A6=20(?= =?UTF-8?q?#15364)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Expose get_all_registered_operators and get_operator_arguments in the Python API. * Code review * CR --- benchmark/opperf/utils/op_registry_utils.py | 81 ++------------------- python/mxnet/operator.py | 61 +++++++++++++++- tests/python/unittest/test_operator.py | 19 ++++- 3 files changed, 84 insertions(+), 77 deletions(-) diff --git a/benchmark/opperf/utils/op_registry_utils.py b/benchmark/opperf/utils/op_registry_utils.py index 5da0e322eccc..6509be37f39d 100644 --- a/benchmark/opperf/utils/op_registry_utils.py +++ b/benchmark/opperf/utils/op_registry_utils.py @@ -20,6 +20,7 @@ from operator import itemgetter from mxnet import runtime from mxnet.base import _LIB, check_call, py_str, OpHandle, c_str, mx_uint +import mxnet as mx from benchmark.opperf.rules.default_params import DEFAULTS_INPUTS, MX_OP_MODULE @@ -75,89 +76,19 @@ def _select_ops(operator_names, filters=("_contrib", "_"), merge_op_forward_back return mx_operators -def _get_all_registered_ops(): - """Get all registered MXNet operator names. - - - Returns - ------- - ["operator_name"] - """ - plist = ctypes.POINTER(ctypes.c_char_p)() - size = ctypes.c_uint() - - check_call(_LIB.MXListAllOpNames(ctypes.byref(size), - ctypes.byref(plist))) - - mx_registered_operator_names = [py_str(plist[i]) for i in range(size.value)] - return mx_registered_operator_names - - -def _get_op_handles(op_name): - """Get handle for an operator with given name - op_name. - - Parameters - ---------- - op_name: str - Name of operator to get handle for. - """ - op_handle = OpHandle() - check_call(_LIB.NNGetOpHandle(c_str(op_name), ctypes.byref(op_handle))) - return op_handle - - -def _get_op_arguments(op_handle): - """Given operator name and handle, fetch operator arguments - number of arguments, - argument names, argument types. - - Parameters - ---------- - op_handle: OpHandle - Handle for the operator - - Returns - ------- - (narg, arg_names, arg_types) - """ - real_name = ctypes.c_char_p() - desc = ctypes.c_char_p() - num_args = mx_uint() - arg_names = ctypes.POINTER(ctypes.c_char_p)() - arg_types = ctypes.POINTER(ctypes.c_char_p)() - arg_descs = ctypes.POINTER(ctypes.c_char_p)() - key_var_num_args = ctypes.c_char_p() - ret_type = ctypes.c_char_p() - - check_call(_LIB.MXSymbolGetAtomicSymbolInfo( - op_handle, ctypes.byref(real_name), ctypes.byref(desc), - ctypes.byref(num_args), - ctypes.byref(arg_names), - ctypes.byref(arg_types), - ctypes.byref(arg_descs), - ctypes.byref(key_var_num_args), - ctypes.byref(ret_type))) - - narg = int(num_args.value) - arg_names = [py_str(arg_names[i]) for i in range(narg)] - arg_types = [py_str(arg_types[i]) for i in range(narg)] - - return narg, arg_names, arg_types - - def _set_op_arguments(mx_operators): """Fetch and set operator arguments - nargs, arg_names, arg_types """ for op_name in mx_operators: - op_handle = _get_op_handles(op_name) - narg, arg_names, arg_types = _get_op_arguments(op_handle) - mx_operators[op_name]["params"] = {"narg": narg, - "arg_names": arg_names, - "arg_types": arg_types} + operator_arguments = mx.operator.get_operator_arguments(op_name) + mx_operators[op_name]["params"] = {"narg": operator_arguments.narg, + "arg_names": operator_arguments.names, + "arg_types": operator_arguments.types} def _get_all_mxnet_operators(): # Step 1 - Get all registered op names and filter it - operator_names = _get_all_registered_ops() + operator_names = mx.operator.get_all_registered_operators() mx_operators = _select_ops(operator_names) # Step 2 - Get all parameters for the operators diff --git a/python/mxnet/operator.py b/python/mxnet/operator.py index 33e9b89a032c..8d14a805ef56 100644 --- a/python/mxnet/operator.py +++ b/python/mxnet/operator.py @@ -22,13 +22,15 @@ import traceback import warnings +import collections from array import array from threading import Lock +import ctypes from ctypes import CFUNCTYPE, POINTER, Structure, pointer from ctypes import c_void_p, c_int, c_char, c_char_p, cast, c_bool -from .base import _LIB, check_call, MXCallbackList, c_array, c_array_buf, mx_int +from .base import _LIB, check_call, MXCallbackList, c_array, c_array_buf, mx_int, OpHandle from .base import c_str, mx_uint, mx_float, ctypes2numpy_shared, NDArrayHandle, py_str from . import symbol, context from .ndarray import NDArray, _DTYPE_NP_TO_MX, _DTYPE_MX_TO_NP @@ -1099,3 +1101,60 @@ def delete_entry(_): return do_register register("custom_op")(CustomOpProp) + + +def get_all_registered_operators(): + """Get all registered MXNet operator names. + + Returns + ------- + operator_names : list of string + """ + plist = ctypes.POINTER(ctypes.c_char_p)() + size = ctypes.c_uint() + + check_call(_LIB.MXListAllOpNames(ctypes.byref(size), + ctypes.byref(plist))) + + mx_registered_operator_names = [py_str(plist[i]) for i in range(size.value)] + return mx_registered_operator_names + +OperatorArguments = collections.namedtuple('OperatorArguments', ['narg', 'names', 'types']) + +def get_operator_arguments(op_name): + """Given operator name, fetch operator arguments - number of arguments, + argument names, argument types. + + Parameters + ---------- + op_name: str + Handle for the operator + + Returns + ------- + operator_arguments : OperatorArguments, namedtuple with number of arguments, names and types + """ + op_handle = OpHandle() + check_call(_LIB.NNGetOpHandle(c_str(op_name), ctypes.byref(op_handle))) + real_name = ctypes.c_char_p() + desc = ctypes.c_char_p() + num_args = mx_uint() + arg_names = ctypes.POINTER(ctypes.c_char_p)() + arg_types = ctypes.POINTER(ctypes.c_char_p)() + arg_descs = ctypes.POINTER(ctypes.c_char_p)() + key_var_num_args = ctypes.c_char_p() + ret_type = ctypes.c_char_p() + + check_call(_LIB.MXSymbolGetAtomicSymbolInfo( + op_handle, ctypes.byref(real_name), ctypes.byref(desc), + ctypes.byref(num_args), + ctypes.byref(arg_names), + ctypes.byref(arg_types), + ctypes.byref(arg_descs), + ctypes.byref(key_var_num_args), + ctypes.byref(ret_type))) + + narg = int(num_args.value) + arg_names = [py_str(arg_names[i]) for i in range(narg)] + arg_types = [py_str(arg_types[i]) for i in range(narg)] + return OperatorArguments(narg, arg_names, arg_types) diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index d50304712196..ca9ecc45621b 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -27,10 +27,11 @@ from distutils.version import LooseVersion from numpy.testing import assert_allclose, assert_array_equal from mxnet.test_utils import * +from mxnet.operator import * from mxnet.base import py_str, MXNetError, _as_list from common import setup_module, with_seed, teardown, assert_raises_cudnn_not_satisfied, assertRaises from common import run_in_spawned_process -from nose.tools import assert_raises +from nose.tools import assert_raises, ok_ import unittest import os @@ -8655,6 +8656,22 @@ def test_add_n(): assert_almost_equal(rslt.asnumpy(), add_n_rslt.asnumpy(), atol=1e-5) +def test_get_all_registered_operators(): + ops = get_all_registered_operators() + ok_(isinstance(ops, list)) + ok_(len(ops) > 0) + ok_('Activation' in ops) + + +def test_get_operator_arguments(): + operator_arguments = get_operator_arguments('Activation') + ok_(isinstance(operator_arguments, OperatorArguments)) + ok_(operator_arguments.names == ['data', 'act_type']) + ok_(operator_arguments.types + == ['NDArray-or-Symbol', "{'relu', 'sigmoid', 'softrelu', 'softsign', 'tanh'}, required"]) + ok_(operator_arguments.narg == 2) + + if __name__ == '__main__': import nose nose.runmodule() From 612b9d1ed441e5af239cab6064b648beba3b99bb Mon Sep 17 00:00:00 2001 From: Serge Panev Date: Fri, 5 Jul 2019 10:09:31 +0900 Subject: [PATCH 039/813] Update Horovod docs links in README (#15366) * Update Horovod docs links in README Signed-off-by: Serge Panev * retrigger CI * retrigger ci * Retrigger * Update README.md * Retrigger CI --- example/distributed_training-horovod/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/distributed_training-horovod/README.md b/example/distributed_training-horovod/README.md index bfaf9d97dbd9..8c939eadfe55 100644 --- a/example/distributed_training-horovod/README.md +++ b/example/distributed_training-horovod/README.md @@ -43,8 +43,8 @@ $ pip install horovod ``` This basic installation is good for laptops and for getting to know Horovod. -If you're installing Horovod on a server with GPUs, read the [Horovod on GPU](https://github.com/horovod/horovod/blob/master/docs/gpus.md) page. -If you want to use Docker, read the [Horovod in Docker](https://github.com/horovod/horovod/blob/master/docs/docker.md) page. +If you're installing Horovod on a server with GPUs, read the [Horovod on GPU](https://github.com/horovod/horovod/blob/master/docs/gpus.rst) page. +If you want to use Docker, read the [Horovod in Docker](https://github.com/horovod/horovod/blob/master/docs/docker.rst) page. ## Install MPI MPI is required to run distributed training with Horovod. Install [Open MPI](https://www.open-mpi.org/) or another MPI implementation. @@ -177,7 +177,7 @@ model.fit(train_data, # Running Horovod The example commands below show how to run distributed training. See the -[Running Horovod](https://github.com/horovod/horovod/blob/master/docs/running.md) +[Running Horovod](https://github.com/horovod/horovod/blob/master/docs/running.rst) page for more instructions. 1. To run on a machine with 4 CPUs: @@ -198,4 +198,4 @@ $ mpirun -np 8 \ -x NCCL_DEBUG=INFO \ -mca pml ob1 -mca btl ^openib \ python train.py -``` \ No newline at end of file +``` From 74dbadbc3fc56e8a9e91f9724d92c32afea1bb54 Mon Sep 17 00:00:00 2001 From: Sergey Sokolov Date: Fri, 5 Jul 2019 03:43:16 -0700 Subject: [PATCH 040/813] [TUTORIAL] Revise Naming tutorial (#15365) * Revise Naming tutorial * Code review fixes * Force build --- docs/tutorials/gluon/naming.md | 148 ++++++++++----------------------- 1 file changed, 43 insertions(+), 105 deletions(-) diff --git a/docs/tutorials/gluon/naming.md b/docs/tutorials/gluon/naming.md index e667ad3cb79e..c2293a9644e1 100644 --- a/docs/tutorials/gluon/naming.md +++ b/docs/tutorials/gluon/naming.md @@ -15,14 +15,12 @@ - # Naming of Gluon Parameter and Blocks -In gluon, each Parameter or Block has a name (and prefix). Parameter names are specified by users and Block names can be either specified by users or automatically created. +In Gluon, each [`Parameter`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/gluon.html#mxnet.gluon.Parameter) has a name and each [`Block`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/gluon.html#mxnet.gluon.Block) has a prefix. `Parameter` names are specified by users, and `Block` names can be either specified by users or automatically created. In this tutorial we talk about the best practices on naming. First, let's import MXNet and Gluon: - ```python from __future__ import print_function import mxnet as mx @@ -31,60 +29,49 @@ from mxnet import gluon ## Naming Blocks -When creating a block, you can assign a prefix to it: - +When you create a `Block`, you can assign it a prefix: ```python mydense = gluon.nn.Dense(100, prefix='mydense_') print(mydense.prefix) +print(mydense.name) ``` - mydense_ - - -When no prefix is given, Gluon will automatically generate one: +The prefix of the `Block` acts like its name. As you can see from the example above, the only difference is that the last `_` is removed from the `Block`'s name. +When no prefix is given, `Gluon` will automatically generate one: ```python dense0 = gluon.nn.Dense(100) -print(dense0.prefix) +dense0.prefix ``` - dense0_ - - -When you create more Blocks of the same kind, they will be named with incrementing suffixes to avoid collision: - +When you create more Blocks of the same kind, they will be named with an incrementing indices to avoid naming collision. To illustrate that, let's create a new `Dense` layer without specifying the prefix: ```python dense1 = gluon.nn.Dense(100) -print(dense1.prefix) +dense1.prefix ``` - dense1_ - +As we can see prefixes of `dense0` and `dense1` blocks are different. ## Naming Parameters -Parameters within a Block will be named by prepending the prefix of the Block to the name of the Parameter: - +Parameters within a `Block` will be named by prepending the prefix of the `Block` to the name of the `Parameter`: ```python -print(dense0.collect_params()) +dense0.collect_params() ``` - dense0_ ( - Parameter dense0_weight (shape=(100, 0), dtype=) - Parameter dense0_bias (shape=(100,), dtype=) - ) +As we can see, both `weight` and `bias` parameters of the `Dense` block have the same prefix `dense0_`. +If you create a new `Parameter`, for example, when you [create a custom block](https://mxnet.incubator.apache.org/versions/master/tutorials/gluon/custom_layer.html#parameters-of-a-custom-layer), you have to specify its name as Gluon cannot set a default one. ## Name scopes -To manage the names of nested Blocks, each Block has a `name_scope` attached to it. All Blocks created within a name scope will have its parent Block's prefix prepended to its name. - -Let's demonstrate this by first defining a simple neural net: +To manage the names of nested Blocks, each `Block` has a `name_scope` attached to it. All Blocks created within a name scope will have its parent `Block`'s prefix prepended to its name. We can retrieve the `name_scope` by using [`.name_scope()`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/gluon.html#mxnet.gluon.Block.name_scope) method of the `Block`. +Let's demonstrate this by first defining a simple neural network: ```python class Model(gluon.Block): @@ -101,12 +88,7 @@ class Model(gluon.Block): return mx.nd.relu(self.mydense(x)) ``` -Now let's instantiate our neural net. - -- Note that `model0.dense0` is named as `model0_dense0_` instead of `dense0_`. - -- Also note that although we specified `mydense_` as prefix for `model.mydense`, its parent's prefix is automatically prepended to generate the prefix `model0_mydense_`. - +Here we defined three `Dense` layers, out of which only the last one has a prefix. Now, let's create and initialize our network. ```python model0 = Model() @@ -118,16 +100,13 @@ print(model0.dense1.prefix) print(model0.mydense.prefix) ``` - model0_ - model0_dense0_ - model0_dense1_ - model0_mydense_ - +- Note that `model0.dense0` is named as `model0_dense0_` instead of `dense0_`. -If we instantiate `Model` again, it will be given a different name like shown before for `Dense`. +- As before, to avoid naming collision, the next `Dense` layer got automatic prefix `dense1_`, and its full prefix is `model0_dense1_`. -- Note that `model1.dense0` is still named as `dense0_` instead of `dense2_`, following dense layers in previously created `model0`. This is because each instance of model's name scope is independent of each other. +- Also note that although we specified `mydense_` as prefix for `model.mydense`, its parent's prefix is automatically prepended to generate the prefix `model0_mydense_`. +If we create another instance of `Model`, it will be given a different name like shown before in the example with the `Dense` layer. ```python model1 = Model() @@ -137,16 +116,11 @@ print(model1.dense1.prefix) print(model1.mydense.prefix) ``` - model1_ - model1_dense0_ - model1_dense1_ - model1_mydense_ - - -**It is recommended that you manually specify a prefix for the top level Block, i.e. `model = Model(prefix='mymodel_')`, to avoid potential confusions in naming.** +Note that `model1.dense0` is still named as `dense0_` instead of `dense2_`, following `Dense` layers in previously created `model0`. This is because each instance of model's name scope is independent of each other. -The same principle also applies to container blocks like Sequential. `name_scope` can be used inside `__init__` as well as out side of `__init__`: +**It is recommended that you manually specify a prefix for the top level `Block`, i.e. `model = Model(prefix='mymodel_')`, to avoid potential confusion in naming.** +The same principle applies to container blocks like [`Sequential`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/gluon.html#mxnet.gluon.nn.Sequential). `name_scope` can be used inside as well as outside of `__init__`. It is recommended to use `name_scope` with container blocks as the naming of the parameters inside of it tends to be more clear. ```python net = gluon.nn.Sequential() @@ -158,13 +132,7 @@ print(net[0].prefix) print(net[1].prefix) ``` - sequential0_ - sequential0_dense0_ - sequential0_dense1_ - - -`gluon.model_zoo` also behaves similarly: - +Models loaded from [`gluon.model_zoo`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/model_zoo.html#module-mxnet.gluon.model_zoo) behave in a similar way: ```python net = gluon.nn.Sequential() @@ -174,40 +142,16 @@ with net.name_scope(): print(net.prefix, net[0].prefix, net[1].prefix) ``` - sequential1_ sequential1_alexnet0_ sequential1_alexnet1_ - - -## Saving and loading - -Because model0 and model1 have different prefixes, their parameters also have different names: +## Saving and loading of parameters +Because `model0` and `model1` have different prefixes, their parameters also have different names: ```python print(model0.collect_params(), '\n') print(model1.collect_params()) ``` - model0_ ( - Parameter model0_dense0_weight (shape=(20L, 20L), dtype=) - Parameter model0_dense0_bias (shape=(20L,), dtype=) - Parameter model0_dense1_weight (shape=(20L, 20L), dtype=) - Parameter model0_dense1_bias (shape=(20L,), dtype=) - Parameter model0_mydense_weight (shape=(20L, 20L), dtype=) - Parameter model0_mydense_bias (shape=(20L,), dtype=) - ) - - model1_ ( - Parameter model1_dense0_weight (shape=(20, 0), dtype=) - Parameter model1_dense0_bias (shape=(20,), dtype=) - Parameter model1_dense1_weight (shape=(20, 0), dtype=) - Parameter model1_dense1_bias (shape=(20,), dtype=) - Parameter model1_mydense_weight (shape=(20, 0), dtype=) - Parameter model1_mydense_bias (shape=(20,), dtype=) - ) - - -As a result, if you try to save parameters from model0 and load it with model1, you'll get an error due to unmatching names: - +As a result, if you try to save parameters of `model0` and then load it into `model1`, you'll get an error due to unmatching names: ```python model0.collect_params().save('model.params') @@ -217,11 +161,7 @@ except Exception as e: print(e) ``` - Parameter 'model1_dense0_weight' is missing in file 'model.params', which contains parameters: 'model0_mydense_weight', 'model0_dense1_bias', 'model0_dense1_weight', 'model0_dense0_weight', 'model0_dense0_bias', 'model0_mydense_bias'. Please make sure source and target networks have the same prefix. - - -To solve this problem, we use `save_parameters`/`load_parameters` instead of `collect_params` and `save`/`load`. `save_parameters` uses model structure, instead of parameter name, to match parameters. - +To solve this problem, we use `save_parameters`/`load_parameters` instead of `collect_params` and `save`/`load`. The `save_parameters` method uses model structure instead of parameter names to match parameters. ```python model0.save_parameters('model.params') @@ -229,20 +169,11 @@ model1.load_parameters('model.params') print(mx.nd.load('model.params').keys()) ``` - ['dense0.bias', 'mydense.bias', 'dense1.bias', 'dense1.weight', 'dense0.weight', 'mydense.weight'] - - -## Replacing Blocks from networks and fine-tuning - -Sometimes you may want to load a pretrained model, and replace certain Blocks in it for fine-tuning. +## Replacing Blocks in networks and fine-tuning -For example, the alexnet in model zoo has 1000 output dimensions, but maybe you only have 100 classes in your application. - -To see how to do this, we first load a pretrained AlexNet. - -- In Gluon model zoo, all image classification models follow the format where the feature extraction layers are named `features` while the output layer is named `output`. -- Note that the output layer is a dense block with 1000 dimension outputs. +Sometimes you may want to load a pretrained model, and replace certain Blocks in it for fine-tuning. For example, the [`AlexNet`](https://mxnet.incubator.apache.org/versions/master/api/python/gluon/model_zoo.html#vision) model in the model zoo has 1000 output dimensions, but maybe you have only 100 classes in your application. Let's see how to change the number of output dimensions from 1000 to 100. +The first step is to load a pretrained `AlexNet`: ```python alexnet = gluon.model_zoo.vision.alexnet(pretrained=True) @@ -250,12 +181,13 @@ print(alexnet.output) print(alexnet.output.prefix) ``` - Dense(4096 -> 1000, linear) - alexnet0_dense2_ +- In Gluon Model Zoo, all image classification models follow the format where the feature extraction layers are named `features` while the output layer is named `output`. +- Note that the output layer is a `Dense` block with 1000 dimension outputs. -To change the output to 100 dimension, we replace it with a new block. +- Some networks, including `AlexNet`, provide a way to directly specify number of output classes by using `classes` argument. We will still show how to do it in a replacing block manner for demonstration purposes. +To change the output to 100 dimension, we replace it with a new `Block` which has only 100 units. ```python with alexnet.name_scope(): @@ -265,8 +197,14 @@ print(alexnet.output) print(alexnet.output.prefix) ``` - Dense(None -> 100, linear) - alexnet0_dense3_ +## Conclusion + +Each `Block` has a `prefix` and each `Parameter` has a `name`. Gluon can automatically generate unique prefixes for blocks, but it is up to you to specify Parameter's name if you create a `Parameter` yourself. Prefixes act like name spaces and create a hierarchy following the nesting of blocks in your model. To save and load model parameters, it is better to use `save_parameters`/`load_parameters` methods on the parent `Block`. + +## Recommended Next Steps + +- Learn more about [model serialization](https://mxnet.incubator.apache.org/versions/master/tutorials/gluon/save_load_params.html) in Gluon. +- [Create custom blocks](https://mxnet.incubator.apache.org/versions/master/tutorials/gluon/custom_layer.html) with your own parameters. From 728b8db572e3652ea76e89a0d8f91be8dc895e41 Mon Sep 17 00:00:00 2001 From: Sergey Sokolov Date: Fri, 5 Jul 2019 03:44:30 -0700 Subject: [PATCH 041/813] Revise Symbol tutorial (#15343) * Revise Symbol tutorial * Force flaky build * Address code review comments * Split tie weight example into blocks * Force build --- docs/tutorials/basic/symbol.md | 370 +++++++++++++++++---------------- 1 file changed, 192 insertions(+), 178 deletions(-) diff --git a/docs/tutorials/basic/symbol.md b/docs/tutorials/basic/symbol.md index cff12aca3c95..57e014f36f54 100644 --- a/docs/tutorials/basic/symbol.md +++ b/docs/tutorials/basic/symbol.md @@ -17,50 +17,21 @@ # Symbol - Neural network graphs -In a [previous tutorial](http://mxnet.io/tutorials/basic/ndarray.html), we introduced `NDArray`, -the basic data structure for manipulating data in MXNet. -And just using NDArray by itself, we can execute a wide range of mathematical operations. -In fact, we could define and update a full neural network just by using `NDArray`. -`NDArray` allows you to write programs for scientific computation -in an imperative fashion, making full use of the native control of any front-end language. Gluon uses this approach under the hood (before hybridization) to allow for flexible and debugable networks. -So you might wonder, why don't we just use `NDArray` for all computation? - -MXNet also provides the Symbol API, an interface for symbolic programming. -With symbolic programming, rather than executing operations step by step, -we first define a *computation graph*. -This graph contains placeholders for inputs and designated outputs. -We can then compile the graph, yielding a function -that can be bound to `NDArray`s and run. -MXNet's Symbol API is similar to the network configurations -used by [Caffe](http://caffe.berkeleyvision.org/) -and the symbolic programming in [Theano](http://deeplearning.net/software/theano/). And Gluon takes advantage of this approach under the hood after the network has been hybridized. - -Another advantage conferred by symbolic approach is that -we can optimize our functions before using them. -For example, when we execute mathematical computations in imperative fashion, -we don't know at the time that we run each operation, -which values will be needed later on. -But with symbolic programming, we declare the required outputs in advance. -This means that we can recycle memory allocated in intermediate steps, -as by performing operations in place. Symbolic API also uses less memory for the -same network. Refer to [How To](http://mxnet.io/faq/index.html) and -[Architecture](http://mxnet.io/architecture/index.html) section to know more. - -In our design notes, we present [a more thorough discussion on the comparative strengths -of imperative and symbolic programing](http://mxnet.io/architecture/program_model.html). -But in this document, we'll focus on teaching you how to use MXNet's Symbol API. -In MXNet, we can compose Symbols from other Symbols, using operators, -such as simple matrix operations (e.g. "+"), -or whole neural network layers (e.g. convolution layer). -Operator can take multiple input variables, -can produce multiple output symbols -and can maintain internal state symbols. - -For a visual explanation of these concepts, see -[Symbolic Configuration and Execution in Pictures](http://mxnet.io/api/python/symbol_in_pictures/symbol_in_pictures.html). - -To make things concrete, let's take a hands-on look at the Symbol API. -There are a few different ways to compose a `Symbol`. +In the [previous tutorial](http://mxnet.io/tutorials/basic/ndarray.html), we introduced [`NDArray`](https://mxnet.incubator.apache.org/api/python/ndarray/ndarray.html), the basic data structure for manipulating data in MXNet. +Just using `NDArray` by itself, we can execute a wide range of mathematical operations. In fact, we could define and update a full neural network just by using `NDArray`. + +`NDArray` allows you to write programs for scientific computation in an imperative fashion, making full use of the native control of any front-end language. Gluon API uses this approach under the hood (before hybridization) to allow for flexible and debugable networks. So you might wonder, why don't we just use `NDArray` for all computation? + +MXNet also provides the `Symbol API`, an interface for symbolic programming. With symbolic programming, rather than executing operations step by step, we first define a [computation graph](https://mxnet.incubator.apache.org/versions/master/faq/visualize_graph.html). This graph contains placeholders for inputs and designated outputs. We can then compile the graph, yielding a function that can be bound to `NDArray`s and run. MXNet's `Symbol API` is similar to the network configurations used by [Caffe](http://caffe.berkeleyvision.org/) and the symbolic programming in [Theano](http://deeplearning.net/software/theano/). Gluon API takes advantage of this approach under the hood after the network has been hybridized. + +Another advantage conferred by symbolic approach is that we can optimize our functions before using them. For example, when we execute mathematical computations in imperative fashion, we don't know at the time that we run each operation, which values will be needed later on. But with symbolic programming, we declare the required outputs in advance. This means that we can recycle memory allocated in intermediate steps, as by performing operations in place. Symbolic API also uses less memory for the +same network. Refer to [How To](http://mxnet.io/faq/index.html) and [Architecture](http://mxnet.io/architecture/index.html) section to know more. + +In our design notes, we present [a more thorough discussion on the comparative strengths of imperative and symbolic programing](http://mxnet.io/architecture/program_model.html). In this document, however, we'll focus on explaining how to use MXNet's `Symbol API`. + +In MXNet, we can compose Symbols from other Symbols, using operators, such as simple matrix operations (e.g. `+`), or whole neural network layers (e.g. convolution layer). Operator can take multiple input variables, can produce multiple output symbols and can maintain internal state symbols. For a visual explanation of these concepts, see [Symbolic Configuration and Execution in Pictures](http://mxnet.io/api/python/symbol_in_pictures/symbol_in_pictures.html). + +To make things concrete, let's take a hands-on look at the `Symbol API`. There are a few different ways to compose a [`Symbol`](http://mxnet.incubator.apache.org/api/python/symbol/symbol.html). ## Prerequisites @@ -71,20 +42,14 @@ To complete this tutorial, we need: ``` pip install jupyter ``` -- GPUs - A section of this tutorial uses GPUs. If you don't have GPUs on your machine, simply -set the variable gpu_device to mx.cpu(). +- GPUs (optional). A section of this tutorial uses GPUs, if one is available. If not, the code will automatically switch to CPU. ## Basic Symbol Composition ### Basic Operators -The following example builds a simple expression: `a + b`. -First, we create two placeholders with `mx.sym.Variable`, -giving them the names `a` and `b`. -We then construct the desired symbol by using the operator `+`. -We don't need to name our variables while creating them, -MXNet will automatically generate a unique name for each. -In the example below, `c` is assigned a unique name automatically. +The following example builds a simple expression: `a + b`. First, we create two placeholders with `mx.sym.Variable`, +giving them the names `a` and `b`. We then construct the desired symbol by using the operator `+`. We don't need to name our variables while creating them, MXNet will automatically generate a unique name for each. In the example below, `c` is assigned a unique name automatically. ```python import mxnet as mx @@ -97,7 +62,7 @@ c = a + b Most operators supported by `NDArray` are also supported by `Symbol`, for example: ```python -# elemental wise multiplication +# element-wise multiplication d = a * b # matrix multiplication e = mx.sym.dot(a, b) @@ -106,12 +71,11 @@ f = mx.sym.reshape(d+e, shape=(1,4)) # broadcast g = mx.sym.broadcast_to(f, shape=(2,4)) # plot -mx.viz.plot_network(symbol=g, node_attrs={"shape":"oval","fixedsize":"false"}) +mx.viz.plot_network(symbol=g, + node_attrs={"shape": "oval", "fixedsize": "false"}) ``` -The computations declared in the above examples can be bound to the input data -for evaluation by using `bind` method. We discuss this further in the -[Symbol Manipulation](#symbol-manipulation) section. +The computations declared in the above examples can be bound to the input data for evaluation by using `bind` method. We discuss this further in the [Symbol Manipulation](#symbol-manipulation) section. ### Basic Neural Networks @@ -125,23 +89,20 @@ net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=128) net = mx.sym.Activation(data=net, name='relu1', act_type="relu") net = mx.sym.FullyConnected(data=net, name='fc2', num_hidden=10) net = mx.sym.SoftmaxOutput(data=net, name='out') -mx.viz.plot_network(net, shape={'data':(100,200)}, node_attrs={"shape":"oval","fixedsize":"false"}) +mx.viz.plot_network(net, + shape={'data':(100, 200)}, + node_attrs={"shape": "oval", "fixedsize": "false"}) ``` -Each symbol takes a (unique) string name. NDArray and Symbol both represent -a single tensor. *Operators* represent the computation between tensors. -Operators take symbol (or NDArray) as inputs and might also additionally accept -other hyperparameters such as the number of hidden neurons (*num_hidden*) or the -activation type (*act_type*) and produce the output. +Each `Symbol` takes a unique string name. `NDArray` and `Symbol` both represent a single tensor. *Operators* represent the computation between tensors. Operators take `Symbol` or `NDArray` as inputs and might also additionally accept other hyperparameters such as the number of hidden neurons (`num_hidden`) or the activation type (`act_type`) and produce the output. -We can view a symbol simply as a function taking several arguments. -And we can retrieve those arguments with the following method call: +We can view a `Symbol` simply as a function taking several arguments. And we can retrieve those arguments with the following method call: ```python net.list_arguments() ``` -These arguments are the parameters and inputs needed by each symbol: +These arguments are the parameters and inputs needed by each `Symbol`: - *data*: Input data needed by the variable *data*. - *fc1_weight* and *fc1_bias*: The weight and bias for the first fully connected layer *fc1*. @@ -157,16 +118,12 @@ net = mx.symbol.FullyConnected(data=net, weight=w, name='fc1', num_hidden=128) net.list_arguments() ``` -In the above example, `FullyConnected` layer has 3 inputs: data, weight, bias. -When any input is not specified, a variable will be automatically generated for it. +In the above example, `FullyConnected` layer has 3 inputs: data, weight, bias. When any input is not specified, a variable will be automatically generated for it. ## More Complicated Composition -MXNet provides well-optimized symbols for layers commonly used in deep learning -(see [src/operator](https://github.com/dmlc/mxnet/tree/master/src/operator)). -We can also define new operators in Python. The following example first -performs an element-wise add between two symbols, then feeds them to the fully -connected operator: +MXNet provides well-optimized Symbols for layers commonly used in deep learning (see [src/operator](https://github.com/dmlc/mxnet/tree/master/src/operator)). We can also define new operators in Python. The following example first +performs an element-wise add between two Symbols, then feeds them to the fully connected operator: ```python lhs = mx.symbol.Variable('data1') @@ -175,8 +132,7 @@ net = mx.symbol.FullyConnected(data=lhs + rhs, name='fc1', num_hidden=128) net.list_arguments() ``` -We can also construct a symbol in a more flexible way than the single forward -composition depicted in the preceding example: +We can also construct a `Symbol` in a more flexible way than the single forward composition depicted in the preceding example: ```python data = mx.symbol.Variable('data') @@ -188,14 +144,9 @@ composed = net2(data2=net1, name='composed') composed.list_arguments() ``` -In this example, *net2* is used as a function to apply to an existing symbol *net1*, -and the resulting *composed* symbol will have all the attributes of *net1* and *net2*. +In this example, `net2` is used as a function to apply to an existing `Symbol` `net1`, and the resulting *composed* `Symbol` will have all the attributes of `net1` and `net2`. -Once you start building some bigger networks, you might want to name some -symbols with a common prefix to outline the structure of your network. -You can use the -[Prefix](https://github.com/dmlc/mxnet/blob/master/python/mxnet/name.py) -NameManager as follows: +Once you start building some bigger networks, you might want to name some symbols with a common prefix to outline the structure of your network. You can use the [Prefix class](https://github.com/apache/incubator-mxnet/blob/master/python/mxnet/name.py#L93) as follows: ```python data = mx.sym.Variable("data") @@ -209,63 +160,76 @@ net.list_arguments() ### Modularized Construction for Deep Networks -Constructing a *deep* network layer by layer, (like the Google Inception network), -can be tedious owing to the large number of layers. -So, for such networks, we often modularize the construction. +Constructing a *deep* network layer by layer, (like the Google Inception network), can be tedious owing to the large number of layers. So, for such networks, we often modularize the construction. -For example, in Google Inception network, -we can first define a factory function which chains the convolution, -batch normalization and rectified linear unit (ReLU) activation layers together. +For example, in Google Inception network, we can first define a factory function which chains the convolution, batch normalization and rectified linear unit (ReLU) activation layers together. ```python -def ConvFactory(data, num_filter, kernel, stride=(1,1), pad=(0, 0),name=None, suffix=''): - conv = mx.sym.Convolution(data=data, num_filter=num_filter, kernel=kernel, - stride=stride, pad=pad, name='conv_%s%s' %(name, suffix)) - bn = mx.sym.BatchNorm(data=conv, name='bn_%s%s' %(name, suffix)) - act = mx.sym.Activation(data=bn, act_type='relu', name='relu_%s%s' - %(name, suffix)) +def ConvFactory(data, num_filter, kernel, + stride=(1, 1), pad=(0, 0), name=None, suffix=''): + conv = mx.sym.Convolution(data=data, num_filter=num_filter, + kernel=kernel, stride=stride, pad=pad, + name='conv_%s%s' % (name, suffix)) + + bn = mx.sym.BatchNorm(data=conv, name='bn_%s%s' % (name, suffix)) + + act = mx.sym.Activation(data=bn, act_type='relu', + name='relu_%s%s' % (name, suffix)) return act + prev = mx.sym.Variable(name="Previous Output") -conv_comp = ConvFactory(data=prev, num_filter=64, kernel=(7,7), stride=(2, 2)) +conv_comp = ConvFactory(data=prev, num_filter=64, kernel=(7, 7), stride=(2, 2)) shape = {"Previous Output" : (128, 3, 28, 28)} -mx.viz.plot_network(symbol=conv_comp, shape=shape, node_attrs={"shape":"oval","fixedsize":"false"}) +mx.viz.plot_network(symbol=conv_comp, shape=shape, + node_attrs={"shape": "oval", "fixedsize": "false"}) ``` -Then we can define a function that constructs an inception module based on -factory function `ConvFactory`. +Then we can define a function that constructs an inception module based on factory function `ConvFactory`. ```python -def InceptionFactoryA(data, num_1x1, num_3x3red, num_3x3, num_d3x3red, num_d3x3, - pool, proj, name): +def InceptionFactoryA(data, num_1x1, num_3x3red, num_3x3, num_d3x3red, + num_d3x3, pool, proj, name): # 1x1 - c1x1 = ConvFactory(data=data, num_filter=num_1x1, kernel=(1, 1), name=('%s_1x1' % name)) + c1x1 = ConvFactory(data=data, num_filter=num_1x1, kernel=(1, 1), + name=('%s_1x1' % name)) + # 3x3 reduce + 3x3 - c3x3r = ConvFactory(data=data, num_filter=num_3x3red, kernel=(1, 1), name=('%s_3x3' % name), suffix='_reduce') - c3x3 = ConvFactory(data=c3x3r, num_filter=num_3x3, kernel=(3, 3), pad=(1, 1), name=('%s_3x3' % name)) + c3x3r = ConvFactory(data=data, num_filter=num_3x3red, kernel=(1, 1), + name=('%s_3x3' % name), suffix='_reduce') + c3x3 = ConvFactory(data=c3x3r, num_filter=num_3x3, kernel=(3, 3), + pad=(1, 1), name=('%s_3x3' % name)) + # double 3x3 reduce + double 3x3 - cd3x3r = ConvFactory(data=data, num_filter=num_d3x3red, kernel=(1, 1), name=('%s_double_3x3' % name), suffix='_reduce') - cd3x3 = ConvFactory(data=cd3x3r, num_filter=num_d3x3, kernel=(3, 3), pad=(1, 1), name=('%s_double_3x3_0' % name)) - cd3x3 = ConvFactory(data=cd3x3, num_filter=num_d3x3, kernel=(3, 3), pad=(1, 1), name=('%s_double_3x3_1' % name)) + cd3x3r = ConvFactory(data=data, num_filter=num_d3x3red, kernel=(1, 1), + name=('%s_double_3x3' % name), suffix='_reduce') + cd3x3 = ConvFactory(data=cd3x3r, num_filter=num_d3x3, kernel=(3, 3), + pad=(1, 1), name=('%s_double_3x3_0' % name)) + cd3x3 = ConvFactory(data=cd3x3, num_filter=num_d3x3, kernel=(3, 3), + pad=(1, 1), name=('%s_double_3x3_1' % name)) + # pool + proj - pooling = mx.sym.Pooling(data=data, kernel=(3, 3), stride=(1, 1), pad=(1, 1), pool_type=pool, name=('%s_pool_%s_pool' % (pool, name))) - cproj = ConvFactory(data=pooling, num_filter=proj, kernel=(1, 1), name=('%s_proj' % name)) + pooling = mx.sym.Pooling(data=data, kernel=(3, 3), stride=(1, 1), + pad=(1, 1), pool_type=pool, + name=('%s_pool_%s_pool' % (pool, name))) + cproj = ConvFactory(data=pooling, num_filter=proj, kernel=(1, 1), + name=('%s_proj' % name)) + # concat - concat = mx.sym.Concat(*[c1x1, c3x3, cd3x3, cproj], name='ch_concat_%s_chconcat' % name) + concat = mx.sym.Concat(*[c1x1, c3x3, cd3x3, cproj], + name='ch_concat_%s_chconcat' % name) return concat + prev = mx.sym.Variable(name="Previous Output") in3a = InceptionFactoryA(prev, 64, 64, 64, 64, 96, "avg", 32, name="in3a") -mx.viz.plot_network(symbol=in3a, shape=shape, node_attrs={"shape":"oval","fixedsize":"false"}) +mx.viz.plot_network(symbol=in3a, shape=shape, + node_attrs={"shape": "oval", "fixedsize": "false"}) ``` -Finally, we can obtain the whole network by chaining multiple inception -modules. See a complete example -[here](https://github.com/dmlc/mxnet/blob/master/example/image-classification/symbols/inception-bn.py). +Finally, we can obtain the whole network by chaining multiple inception modules. See a complete example [here](https://github.com/dmlc/mxnet/blob/master/example/image-classification/symbols/inception-bn.py). ### Group Multiple Symbols -To construct neural networks with multiple loss layers, we can use -`mxnet.sym.Group` to group multiple symbols together. The following example -groups two outputs: +To construct neural networks with multiple loss layers, we can use `mxnet.sym.Group` to group multiple Symbols together. The following example groups two outputs: ```python net = mx.sym.Variable('data') @@ -279,48 +243,35 @@ group.list_outputs() ## Relations to NDArray -As you can see now, both `Symbol` and `NDArray` provide multi-dimensional array -operations, such as `c = a + b` in MXNet. We briefly clarify the differences here. +As you can see now, both `Symbol` and `NDArray` provide multi-dimensional array operations, such as `c = a + b` in MXNet. We briefly clarify the differences here. -The `NDArray` provides an imperative programming alike interface, in which the -computations are evaluated sentence by sentence. While `Symbol` is closer to -declarative programming, in which we first declare the computation and then -evaluate with data. Examples in this category include regular expressions and -SQL. +The `NDArray` provides an imperative programming alike interface, in which the computations are evaluated sentence by sentence. While `Symbol` is closer to declarative programming, in which we first declare the computation and then evaluate with data. Examples in this category include regular expressions and SQL. The pros for `NDArray`: - Straightforward. -- Easy to work with native language features (for loop, if-else condition, ..) - and libraries (numpy, ..). +- Easy to work with native language features (for loop, if-else condition, ..) and libraries (numpy, ..). - Easy step-by-step code debugging. The pros for `Symbol`: -- Provides almost all functionalities of NDArray, such as `+`, `*`, `sin`, - `reshape` etc. +- Provides almost all functionalities of NDArray, such as `+`, `*`, `sin`, `reshape` etc. - Easy to save, load and visualize. - Easy for the backend to optimize the computation and memory usage. ## Symbol Manipulation -One important difference of `Symbol` compared to `NDArray` is that we first -declare the computation and then bind the computation with data to run. - -In this section, we introduce the functions to manipulate a symbol directly. But -note that, most of them are wrapped by the high-level packages: `Module` and `Gluon`. +One important difference of `Symbol` compared to `NDArray` is that we first declare the computation and then bind the computation with data to run. In this section, we introduce the functions to manipulate a `Symbol` directly. But note that, most of them are wrapped by the high-level packages: [`Module`](https://mxnet.incubator.apache.org/api/python/module/module.html) and [`Gluon`](https://mxnet.incubator.apache.org/api/python/gluon/gluon.html). ### Shape and Type Inference -For each symbol, we can query its arguments, auxiliary states and outputs. -We can also infer the output shape and type of the symbol given the known input -shape or type of some arguments, which facilitates memory allocation. +For each `Symbol`, we can query its arguments, auxiliary states and outputs. We can also infer the output shape and type of the `Symbol` given the known input shape or type of some arguments, which facilitates memory allocation. ```python arg_name = c.list_arguments() # get the names of the inputs out_name = c.list_outputs() # get the names of the outputs # infers output shape given the shape of input arguments -arg_shape, out_shape, _ = c.infer_shape(a=(2,3), b=(2,3)) +arg_shape, out_shape, _ = c.infer_shape(a=(2, 3), b=(2, 3)) # infers output type given the type of input arguments arg_type, out_type, _ = c.infer_type(a='float32', b='float32') {'input' : dict(zip(arg_name, arg_shape)), @@ -331,13 +282,9 @@ arg_type, out_type, _ = c.infer_type(a='float32', b='float32') ### Bind with Data and Evaluate -The symbol `c` constructed above declares what computation should be run. To -evaluate it, we first need to feed the arguments, namely free variables, with data. +The `Symbol` `c` constructed above declares what computation should be run. To evaluate it, we first need to feed the arguments, namely free variables, with data. -We can do it by using the `bind` method, which accepts device context and -a `dict` mapping free variable names to `NDArray`s as arguments and returns an -executor. The executor provides `forward` method for evaluation and an attribute -`outputs` to get all the results. +We can do it by using the [`bind`](https://mxnet.incubator.apache.org/api/python/symbol/symbol.html#mxnet.symbol.Symbol.bind) method, which accepts device context and a `dict` mapping free variable names to `NDArray`s as arguments and returns an [`Executor`](https://mxnet.incubator.apache.org/api/python/executor/executor.html#mxnet.executor.Executor). The `Executor` provides [`forward`](https://mxnet.incubator.apache.org/api/python/executor/executor.html#mxnet.executor.Executor.forward) method for evaluation and an attribute `outputs` to get all the results. ```python ex = c.bind(ctx=mx.cpu(), args={'a' : mx.nd.ones([2,3]), @@ -347,11 +294,10 @@ print('number of outputs = %d\nthe first output = \n%s' % ( len(ex.outputs), ex.outputs[0].asnumpy())) ``` -We can evaluate the same symbol on GPU with different data. +We can evaluate the same `Symbol` on GPU with different data. -**Note** In order to execute the following section on a cpu set gpu_device to mx.cpu(). ```python -gpu_device=mx.gpu() # Change this to mx.cpu() in absence of GPUs. +gpu_device = mx.gpu() if mx.test_utils.list_gpus() else mx.cpu() ex_gpu = c.bind(ctx=gpu_device, args={'a' : mx.nd.ones([3,4], gpu_device)*2, 'b' : mx.nd.ones([3,4], gpu_device)*3}) @@ -359,8 +305,7 @@ ex_gpu.forward() ex_gpu.outputs[0].asnumpy() ``` -We can also use `eval` method to evaluate the symbol. It combines calls to `bind` -and `forward` methods. +We can also use [eval](https://mxnet.incubator.apache.org/api/python/symbol/symbol.html#mxnet.symbol.Symbol.eval) method to evaluate the `Symbol`. It combines calls to `bind` and `forward` methods. ```python ex = c.eval(ctx = mx.cpu(), a = mx.nd.ones([2,3]), b = mx.nd.ones([2,3])) @@ -368,24 +313,13 @@ print('number of outputs = %d\nthe first output = \n%s' % ( len(ex), ex[0].asnumpy())) ``` -For neural nets, a more commonly used pattern is ```simple_bind```, which -creates all of the argument arrays for you. Then you can call ```forward```, -and ```backward``` (if the gradient is needed) to get the gradient. +For neural nets, a more commonly used pattern is [simple_bind](https://mxnet.incubator.apache.org/api/python/symbol/symbol.html#mxnet.symbol.Symbol.simple_bind), which creates all of the argument arrays for you. Then you can call `forward`, and [`backward`](https://mxnet.incubator.apache.org/api/python/executor/executor.html#mxnet.executor.Executor.backward) to get gradients if needed. ### Load and Save -Logically symbols correspond to ndarrays. They both represent a tensor. They both -are inputs/outputs of operators. We can either serialize a `Symbol` object by -using `pickle`, or by using `save` and `load` methods directly as we discussed in -[NDArray tutorial](http://mxnet.io/tutorials/basic/ndarray.html#serialize-from-to-distributed-filesystems). +Logically Symbols correspond to NDArrays. They both represent a tensor. They both are inputs/outputs of operators. We can either serialize a `Symbol` object by using `pickle`, or by using `save` and `load` methods directly as it is explained in [this NDArray tutorial](http://mxnet.io/tutorials/basic/ndarray.html#serialize-from-to-distributed-filesystems). -When serializing `NDArray`, we serialize the tensor data in it and directly dump to -disk in binary format. -But symbol uses a concept of graph. Graphs are composed by chaining operators. They are -implicitly represented by output symbols. So, when serializing a `Symbol`, we -serialize the graph of which the symbol is an output. While serialization, Symbol -uses more readable `json` format for serialization. To convert symbol to `json` -string, use `tojson` method. +When serializing `NDArray`, we serialize the tensor data in it and directly dump to disk in binary format. But `Symbol` uses a concept of graph. Graphs are composed by chaining operators. They are implicitly represented by output Symbols. So, when serializing a `Symbol`, we serialize the graph of which the `Symbol` is an output. While serialization, `Symbol` uses more readable `json` format for serialization. To convert `Symbol` to `json` string, use [`tojson`](https://mxnet.incubator.apache.org/api/python/symbol/symbol.html#mxnet.symbol.Symbol.tojson) method. ```python print(c.tojson()) @@ -396,25 +330,16 @@ c.tojson() == c2.tojson() ## Customized Symbol -Most operators such as `mx.sym.Convolution` and `mx.sym.Reshape` are implemented -in C++ for better performance. MXNet also allows users to write new operators -using any front-end language such as Python. It often makes the developing and -debugging much easier. To implement an operator in Python, refer to -[How to create new operators](http://mxnet.io/faq/new_op.html). +Most operators such as [`mx.sym.Convolution`](https://mxnet.incubator.apache.org/api/python/symbol/symbol.html#mxnet.symbol.Convolution) and [`mx.sym.Reshape`](https://mxnet.incubator.apache.org/api/python/symbol/symbol.html#mxnet.symbol.reshape) are implemented in C++ for better performance. MXNet also allows users to write new operators using any front-end language such as Python. It often makes the developing and debugging much easier. To implement an operator in Python, refer to [How to create new operators](http://mxnet.io/faq/new_op.html). ## Advanced Usages ### Type Cast -By default, MXNet uses 32-bit floats. -But for better accuracy-performance, -we can also use a lower precision data type. -For example, The Nvidia Tesla Pascal GPUs -(e.g. P100) have improved 16-bit float performance, -while GTX Pascal GPUs (e.g. GTX 1080) are fast on 8-bit integers. +By default, MXNet uses 32-bit floats. But for better accuracy-performance, we can also use a lower precision data type. +For example, The NVIDIA Tesla Pascal GPUs (e.g. P100) have improved 16-bit float performance, while GTX Pascal GPUs (e.g. GTX 1080) are fast on 8-bit integers. -To convert the data type as per the requirements, -we can use `mx.sym.cast` operator as follows: +To convert the data type as per the requirements, we can use [`mx.sym.cast`](https://mxnet.incubator.apache.org/api/python/symbol/symbol.html#mxnet.symbol.cast) operator as follows: ```python a = mx.sym.Variable('data') @@ -429,18 +354,107 @@ print({'input':arg, 'output':out}) ### Variable Sharing -To share the contents between several symbols, -we can bind these symbols with the same array as follows: +To share the contents between several Symbols, we can bind these Symbols with the same array as follows: ```python a = mx.sym.Variable('a') b = mx.sym.Variable('b') b = a + a * a -data = mx.nd.ones((2,3))*2 +data = mx.nd.ones((2,3)) * 2 ex = b.bind(ctx=mx.cpu(), args={'a':data, 'b':data}) ex.forward() ex.outputs[0].asnumpy() ``` +### Weight tying + +You can use same principle to tie weights of different layers. In the example below two `FullyConnected` layers share same weights and biases, but process different data. Let's demonstrate how we can do it. + +In this example we first create training and evaluation datasets. Both of them consist of two individual `NDArray`s. We are using `NDArrayIter` to iterate over all of them. + +```python +import numpy as np +import mxnet as mx + +# Training data +train_data_size = 4 +train_data1 = mx.random.uniform(shape=(train_data_size, 2)) +train_data2 = mx.random.uniform(shape=(train_data_size, 2)) +train_label = mx.nd.array([i % 2 for i in range(train_data_size)]) +batch_size = 3 + +# Evaluation Data +eval_data_size = 3 +eval_data1 = mx.random.uniform(shape=(eval_data_size, 2)) +eval_data2 = mx.random.uniform(shape=(eval_data_size, 2)) +eval_label = np.array([i % 2 for i in range(eval_data_size)]) + +train_iter = mx.io.NDArrayIter({'inputs_left': train_data1, + 'inputs_right': train_data2}, + train_label, batch_size, shuffle=True, + label_name='labels', last_batch_handle='pad') + +eval_iter = mx.io.NDArrayIter({'inputs_left': eval_data1, + 'inputs_right': eval_data2}, + eval_label, batch_size, shuffle=False, + label_name='labels', + last_batch_handle='pad') +``` + +We define a `Symbol` for both `inputs_left` and `inputs_right` variables, and separate symbols for `shared_weight` and `shared_bias`. We use `shared_weight` and `shared_bias` symbols in both `FullyConnected` layers, making sure that they are using the same data underlying the symbols. This is where weight tying is happening. + +```python +num_hidden_nodes = 2 + +# Assume the left and right inputs have the same shape as each other +inputs_left = mx.sym.var('inputs_left') +inputs_right = mx.sym.var('inputs_right') +labels = mx.symbol.Variable('labels') + +shared_weight = mx.symbol.Variable('shared_weight') +shared_bias = mx.symbol.Variable('shared_bias') + +fc_left_sym = mx.sym.FullyConnected(data=inputs_left, weight=shared_weight, + bias=shared_bias, + num_hidden=num_hidden_nodes, name='fc_left') + +fc_right_sym = mx.sym.FullyConnected(data=inputs_right, weight=shared_weight, + bias=shared_bias, + num_hidden=num_hidden_nodes, name='fc_right') + +combined = mx.sym.concat(fc_left_sym, fc_right_sym) +output = mx.sym.SoftmaxOutput(data=combined, label=labels, name='softmax') +``` + +In the next lines of the code, we use `Module API` to start the training. We first create a `Module` object and then call `fit` providing data iterators. To use trained model for prediction, we use `predict` method, providing evaluation data iterator. + +```python +model = mx.mod.Module( + symbol=output, + data_names=['inputs_left', 'inputs_right'], + label_names=['labels'] +) + +model.fit(train_iter, eval_iter, + optimizer_params={'learning_rate': 0.01, 'momentum': 0.9}, + num_epoch=1, + eval_metric='acc') + +result = model.predict(eval_iter).asnumpy() +print(result) +``` + +## Recommended Next Steps + +- Learn how to [use Module API to train neural network](https://mxnet.incubator.apache.org/versions/master/tutorials/basic/module.html). + +- Explore ways you can [load data using Data Iterators](https://mxnet.incubator.apache.org/versions/master/tutorials/basic/data.html). + +- [Use pretrained models](https://mxnet.incubator.apache.org/versions/master/tutorials/python/predict_image.html) for image object detection. + +- [Hybridize your models](https://mxnet.incubator.apache.org/versions/master/tutorials/gluon/hybrid.html) to get the best from both `Gluon` and `Symbol API`. + +- Convert your existing `Module API` code to `Gluon` as it is explained [here](https://mxnet.incubator.apache.org/versions/master/tutorials/python/module_to_gluon.html). + From dfe923ac254be93ae0a580bfd26b52fb6ea065e8 Mon Sep 17 00:00:00 2001 From: Serge Panev Date: Sat, 6 Jul 2019 03:08:33 +0900 Subject: [PATCH 042/813] Update fp16 docs: Block.cast is inplace (#15458) Signed-off-by: Serge Panev --- docs/faq/float16.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq/float16.md b/docs/faq/float16.md index 465668610413..22d4ef48c2f6 100644 --- a/docs/faq/float16.md +++ b/docs/faq/float16.md @@ -42,7 +42,7 @@ With Gluon API, you need to take care of three things to convert a model to supp 1. Cast Gluon `Block`'s parameters and expected input type to float16 by calling the [cast](https://mxnet.incubator.apache.org/api/python/gluon/gluon.html#mxnet.gluon.Block.cast) method of the `Block` representing the network. ```python -net = net.cast('float16') +net.cast('float16') ``` 2. Ensure the data input to the network is of float16 type. If your `DataLoader` or `Iterator` produces output in another datatype, then you would have to cast your data. There are different ways you can do this. The easiest would be to use the [astype](https://mxnet.incubator.apache.org/api/python/ndarray/ndarray.html#mxnet.ndarray.NDArray.astype) method of NDArrays. From bc570dbd948f23873388bbe9fb84de50679dbef9 Mon Sep 17 00:00:00 2001 From: Tao Lv Date: Sat, 6 Jul 2019 14:14:04 +0800 Subject: [PATCH 043/813] [OP] Add a new arange_like operator to contrib (#15400) * add contrib.arange_like operator * add parameter to template * fix lint complains * inum -> num_in * add unit tests --- src/operator/tensor/init_op.cc | 38 ++++++++++++++- src/operator/tensor/init_op.cu | 5 +- src/operator/tensor/init_op.h | 64 ++++++++++++++++++++++++-- tests/python/unittest/test_operator.py | 22 +++++++++ 4 files changed, 123 insertions(+), 6 deletions(-) diff --git a/src/operator/tensor/init_op.cc b/src/operator/tensor/init_op.cc index a58498b85aa5..0cbdaa43c198 100644 --- a/src/operator/tensor/init_op.cc +++ b/src/operator/tensor/init_op.cc @@ -32,6 +32,7 @@ DMLC_REGISTER_PARAMETER(InitOpParam); DMLC_REGISTER_PARAMETER(InitOpWithScalarParam); DMLC_REGISTER_PARAMETER(InitOpWithoutDTypeParam); DMLC_REGISTER_PARAMETER(RangeParam); +DMLC_REGISTER_PARAMETER(RangeLikeParam); DMLC_REGISTER_PARAMETER(EyeParam); DMLC_REGISTER_PARAMETER(LinspaceParam); @@ -97,9 +98,44 @@ NNVM_REGISTER_OP(_arange) .set_attr_parser(RangeParamParser) .set_attr("FInferShape", RangeShape) .set_attr("FInferType", InitType) -.set_attr("FCompute", RangeCompute) +.set_attr("FCompute", RangeCompute) .add_arguments(RangeParam::__FIELDS__()); +NNVM_REGISTER_OP(_contrib_arange_like) +.describe(R"code(Return an array with evenly spaced values. If axis is not given, the output will +have the same shape as the input array. Otherwise, the output will be a 1-D array with size of +the specified axis in input shape. + +Examples:: + + x = [[0.14883883 0.7772398 0.94865847 0.7225052 ] + [0.23729339 0.6112595 0.66538996 0.5132841 ] + [0.30822644 0.9912457 0.15502319 0.7043658 ]] + + + out = mx.nd.contrib.arange_like(x, start=0) + + [[ 0. 1. 2. 3.] + [ 4. 5. 6. 7.] + [ 8. 9. 10. 11.]] + + + out = mx.nd.contrib.arange_like(x, start=0, axis=-1) + + [0. 1. 2. 3.] + +)code") +.set_num_inputs(1) +.set_num_outputs(1) +.set_attr_parser(ParamParser) +.set_attr("FInferShape", RangeLikeShape) +.set_attr("FInferType", InitType) +.set_attr("FIgnoreInputs", + [](const NodeAttrs& attrs) { return std::vector(1, 0); }) +.set_attr("FCompute", RangeCompute) +.set_attr("FGradient", MakeZeroGradNodes) +.add_argument("data", "NDArray-or-Symbol", "The input"); + NNVM_REGISTER_OP(_linspace) .describe("Return evenly spaced numbers over a specified interval. Similar to Numpy") .set_num_inputs(0) diff --git a/src/operator/tensor/init_op.cu b/src/operator/tensor/init_op.cu index 5829ff2237e8..ee6963bd87c0 100644 --- a/src/operator/tensor/init_op.cu +++ b/src/operator/tensor/init_op.cu @@ -62,7 +62,10 @@ NNVM_REGISTER_OP(_full) .set_attr("FCompute", InitFillWithScalarCompute); NNVM_REGISTER_OP(_arange) -.set_attr("FCompute", RangeCompute); +.set_attr("FCompute", RangeCompute); + +NNVM_REGISTER_OP(_contrib_arange_like) +.set_attr("FCompute", RangeCompute); NNVM_REGISTER_OP(_linspace) .set_attr("FCompute", LinspaceCompute); diff --git a/src/operator/tensor/init_op.h b/src/operator/tensor/init_op.h index c7a10541adbd..51c84363489a 100644 --- a/src/operator/tensor/init_op.h +++ b/src/operator/tensor/init_op.h @@ -174,6 +174,40 @@ struct RangeParam : public dmlc::Parameter { } }; +struct RangeLikeParam : public dmlc::Parameter { + double start; + double step; + int repeat; + std::string ctx; + int dtype; + dmlc::optional axis; + + DMLC_DECLARE_PARAMETER(RangeLikeParam) { + DMLC_DECLARE_FIELD(start) + .set_default(0) + .describe("Start of interval. The interval includes this value. The default start value is 0."); + DMLC_DECLARE_FIELD(step) + .set_default(1) + .describe("Spacing between values."); + DMLC_DECLARE_FIELD(repeat) + .set_default(1) + .describe("The repeating time of all elements." + " E.g repeat=3, the element a will be repeated three times --> a, a, a."); + DMLC_DECLARE_FIELD(ctx) + .set_default("") + .describe("Context of output, in format [cpu|gpu|cpu_pinned](n)." + "Only used for imperative calls."); + DMLC_DECLARE_FIELD(dtype).set_default(mshadow::kFloat32) + MXNET_ADD_ALL_TYPES + .describe("Target data type."); + DMLC_DECLARE_FIELD(axis) + .set_default(dmlc::optional()) + .describe("Arange elements according to the size of a certain axis of input array." + " The negative numbers are interpreted counting from the backward." + " If not provided, will arange elements according to the input shape."); + } +}; + /*! \brief Initialize and fill output with an arbitrary value */ struct InitOpWithScalarParam : dmlc::Parameter { mxnet::TShape shape; @@ -250,12 +284,12 @@ inline bool InitShape(const nnvm::NodeAttrs& attrs, return shape_is_known(out_attrs->at(0)); } -template +template inline bool InitType(const nnvm::NodeAttrs& attrs, std::vector *in_attrs, std::vector *out_attrs) { const ParamType& param = nnvm::get(attrs.parsed); - CHECK_EQ(in_attrs->size(), 0U); + CHECK_EQ(in_attrs->size(), num_in); CHECK_EQ(out_attrs->size(), 1U); TYPE_ASSIGN_CHECK(*out_attrs, 0, param.dtype); return true; @@ -493,7 +527,7 @@ struct range_fwd { } }; -template +template void RangeCompute(const nnvm::NodeAttrs& attrs, const OpContext& ctx, const std::vector& inputs, @@ -501,7 +535,7 @@ void RangeCompute(const nnvm::NodeAttrs& attrs, const std::vector& outputs) { using namespace mxnet_op; Stream *s = ctx.get_stream(); - const RangeParam& param = nnvm::get(attrs.parsed); + const ParamType& param = nnvm::get(attrs.parsed); MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, DType, { // Force unsigned params to take two's complement form on ARM to ensure consistency with x86 // results. Casting negative floats to unsigned types is undefined in the CPP standard. @@ -588,6 +622,28 @@ inline bool LinspaceShape(const nnvm::NodeAttrs& attrs, return true; } +inline bool RangeLikeShape(const nnvm::NodeAttrs& attrs, + mxnet::ShapeVector *in_attrs, + mxnet::ShapeVector *out_attrs) { + const RangeLikeParam& param = nnvm::get(attrs.parsed); + CHECK_EQ(in_attrs->size(), 1U); + CHECK_EQ(out_attrs->size(), 1U); + int real_axis = -1; + if (param.axis.has_value()) { + real_axis = param.axis.value() < 0 ? + (param.axis.value() + (*in_attrs)[0].ndim()) : param.axis.value(); + CHECK(real_axis >=0 && real_axis < (*in_attrs)[0].ndim()) + << "cannot handle param.axis " << param.axis.value() << "."; + } + if (real_axis == -1) { + SHAPE_ASSIGN_CHECK(*out_attrs, 0, (*in_attrs)[0]); + } else { + const index_t out_size = (*in_attrs)[0][real_axis]; + SHAPE_ASSIGN_CHECK(*out_attrs, 0, mxnet::TShape({static_cast(out_size)})); + } + return true; +} + } // namespace op } // namespace mxnet diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index ca9ecc45621b..b550139d341b 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -4042,11 +4042,33 @@ def test_arange_inferstop(): exe.forward() assert_almost_equal(exe.outputs[0].asnumpy(), np.array([0,1,2,3,4])) + def test_arange_like(): + shape_list = [(10,), (10, 20), (10, 20, 30), (10, 20, 30, 40)] + axis_list = [0, -1] + for sh in shape_list: + for axis in axis_list: + val = np.random.rand(*sh) + data = mx.nd.array(val) + nd_out = mx.nd.contrib.arange_like(data, start=0, axis=axis) + np_out = np.arange(start=0, stop=sh[axis]) + assert_almost_equal(nd_out.asnumpy(), np_out) + + def test_arange_like_without_axis(): + shape_list = [(10,), (10, 20), (10, 20, 30), (10, 20, 30, 40)] + for sh in shape_list: + val = np.random.rand(*sh) + data = mx.nd.array(val) + nd_out = mx.nd.contrib.arange_like(data, start=0) + np_out = np.arange(start=0, stop=val.size) + assert_almost_equal(nd_out.asnumpy(), np_out.reshape(sh)) + test_basic_val_init(mx.sym.zeros, np.zeros, (3, 4), np.float32) test_basic_val_init(mx.sym.ones, np.ones, 3, np.int32) test_basic_val_init(mx.sym.ones, np.ones, (2, 2, 3), np.float16) test_arange() test_arange_inferstop() + test_arange_like() + test_arange_like_without_axis() @with_seed() From a6ed12fe4f49ffceb12b25244a16fde42cfa11b6 Mon Sep 17 00:00:00 2001 From: Disi A Date: Sun, 7 Jul 2019 07:07:20 -0400 Subject: [PATCH 044/813] Had a few PRs merged. Hope to become an official contributor and potentially a commiter. (#15451) --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 43d988037de9..750a22a806d1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -244,6 +244,7 @@ List of Contributors * [Shoubhik Bhattacharya](https://github.com/shoubhik) * [Rohit Srivastava](https://github.com/access2rohit) * [Caner Turkmen](https://github.com/canerturkmen) +* [Disi A](https://github.com/adis300) Label Bot --------- From a3ae30979989f488cd933c2fbb6416a4e187de9d Mon Sep 17 00:00:00 2001 From: kshitij12345 Date: Mon, 8 Jul 2019 02:35:09 +0530 Subject: [PATCH 045/813] [MXNET-978] Higher Order Gradient Support `reciprocal`, `abs`. (#15413) * add higher order support for reciprocal and abs * add relevant tests * address comments * fix extra line in tests. * fix missing space. * fix incorrect comment. --- .../tensor/elemwise_unary_op_basic.cc | 54 ++++++++++++++++++- .../python/unittest/test_higher_order_grad.py | 27 ++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/operator/tensor/elemwise_unary_op_basic.cc b/src/operator/tensor/elemwise_unary_op_basic.cc index 26c74085dbe6..6da384d3679b 100644 --- a/src/operator/tensor/elemwise_unary_op_basic.cc +++ b/src/operator/tensor/elemwise_unary_op_basic.cc @@ -717,7 +717,38 @@ Example:: MXNET_OPERATOR_REGISTER_BINARY(_backward_reciprocal) .set_attr("FCompute", - ElemwiseBinaryOp::Compute >); + ElemwiseBinaryOp::Compute >) +.set_attr("FGradient", + [](const nnvm::NodePtr& n, const std::vector& ograds) { + // ograds[0]: dL/dxgrad + // inputs[0]: dL/dy + // inputs[1]: x + // f(x) = y = 1/x + // f'(x) = -1/x^2 + // f''(x) = 2/x^3 = -2 * (f'(x) * f(x)) + + const std::unordered_map args = {{"scalar", "-2.0"}}; + + auto dydx_mul_dldy = nnvm::NodeEntry{n}; // f'(x) * head_grads + auto dydx = MakeNode("elemwise_div", n->attrs.name + "_dydx", + {dydx_mul_dldy, n->inputs[0]}, nullptr, &n); + auto fx = MakeNode("reciprocal", n->attrs.name + "_fx", + {n->inputs[1]}, nullptr, &n); + + auto d2ydx2_mid = MakeNode("elemwise_mul", n->attrs.name + "_d2ydx2_mid", + {dydx_mul_dldy, nnvm::NodeEntry{fx}}, nullptr, &n); + + auto d2ydx2 = MakeNode("_mul_scalar", n->attrs.name + "_d2ydx2", + {nnvm::NodeEntry{d2ydx2_mid}}, &args, &n); + + std::vector ret; + + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad", + {ograds[0], nnvm::NodeEntry{dydx}}, nullptr, &n)); + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad_inp", + {ograds[0], nnvm::NodeEntry{d2ydx2}}, nullptr, &n)); + return ret; +}); // abs MXNET_OPERATOR_REGISTER_UNARY_WITH_RSP_CSR(abs, cpu, mshadow_op::abs) @@ -736,7 +767,26 @@ The storage type of ``abs`` output depends upon the input storage type: )code" ADD_FILELINE) .set_attr("FGradient", ElemwiseGradUseIn{"_backward_abs"}); -MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU(_backward_abs, unary_bwd); +MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU(_backward_abs, unary_bwd) +.set_attr("FGradient", + [](const nnvm::NodePtr& n, const std::vector& ograds) { + // ograds[0]: dL/dxgrad + // inputs[0]: dL/dy + // inputs[1]: x + // f(x) -> abs(x) + // f'(x) = 1 if x > 0 else -1 + // f''(x) = 0 + auto dydx = MakeNode("elemwise_div", n->attrs.name + "_dydx", + {nnvm::NodeEntry{n}, n->inputs[0]}, nullptr, &n); + + std::vector ret; + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad", + {ograds[0], nnvm::NodeEntry(dydx)}, nullptr, &n)); + ret.emplace_back(MakeNode("zeros_like", n->attrs.name + "_backward_grad_grad_in", + {n->inputs[1]}, nullptr, &n)); + return ret; + }); + // sign MXNET_OPERATOR_REGISTER_UNARY_WITH_RSP_CSR(sign, cpu, mshadow_op::sign) diff --git a/tests/python/unittest/test_higher_order_grad.py b/tests/python/unittest/test_higher_order_grad.py index ad14c5050c1b..0f07d014d435 100644 --- a/tests/python/unittest/test_higher_order_grad.py +++ b/tests/python/unittest/test_higher_order_grad.py @@ -107,6 +107,33 @@ def grad_grad_op(x): @with_seed() +def test_reciprocal(): + def reciprocal(x): + return nd.reciprocal(x) + + def grad_grad_op(x): + return 2 / x**3 + + for dim in range(1, 5): + shape = rand_shape_nd(dim) + array = random_arrays(shape) + check_second_order_unary(array, reciprocal, grad_grad_op) + + +@with_seed() +def test_abs(): + def abs(x): + return nd.abs(x) + + def grad_grad_op(x): + return nd.zeros_like(x) + + for dim in range(1, 5): + shape = rand_shape_nd(dim) + array = random_arrays(shape) + check_second_order_unary(array, abs, grad_grad_op) + + def test_sigmoid(): def sigmoid(x): return nd.sigmoid(x) From 091fece5431b43c931568d279c1dd9d664318c36 Mon Sep 17 00:00:00 2001 From: Wuxun Zhang Date: Mon, 8 Jul 2019 10:07:37 +0800 Subject: [PATCH 046/813] fix fp32 flatten issue (#15351) * Fix flatten issue before slice op * fix cpplint * address comments * retrigger CI * trigger CI * retrigger CI * use SupportMKLDNNReshape and update operator list --- docs/tutorials/mkldnn/operator_list.md | 2 + src/operator/nn/mkldnn/mkldnn_base-inl.h | 2 + src/operator/nn/mkldnn/mkldnn_flatten.cc | 87 +++++++++ src/operator/nn/mkldnn/mkldnn_ops-inl.h | 9 +- src/operator/nn/mkldnn/mkldnn_reshape-inl.h | 68 +++++++ src/operator/nn/mkldnn/mkldnn_reshape.cc | 185 +++++++++----------- src/operator/tensor/matrix_op.cc | 20 +-- tests/python/gpu/test_operator_gpu.py | 14 ++ tests/python/mkl/test_mkldnn.py | 20 +++ 9 files changed, 296 insertions(+), 111 deletions(-) create mode 100644 src/operator/nn/mkldnn/mkldnn_flatten.cc create mode 100644 src/operator/nn/mkldnn/mkldnn_reshape-inl.h diff --git a/docs/tutorials/mkldnn/operator_list.md b/docs/tutorials/mkldnn/operator_list.md index 4958f8d9b602..0ef0f29f4cdc 100644 --- a/docs/tutorials/mkldnn/operator_list.md +++ b/docs/tutorials/mkldnn/operator_list.md @@ -44,6 +44,8 @@ To help users understanding MKL-DNN backend better, the following table summariz | **elemwise_add** | 1D-4D input | Y | Y | Y | | **Concat** | 1D-4D input | Y | Y | Y | | **slice** | 1D-4D input | N | Y | N | +| **Reshape** | 1D-4D input | N | Y | N | +| **Flatten** | 1D-4D input | N | Y | N | | **Quantization** | 1D-4D input | N | N | Y | | **Dequantization** | 1D-4D input | N | N | Y | | **Requantization** | 1D-4D input | N | N | Y | diff --git a/src/operator/nn/mkldnn/mkldnn_base-inl.h b/src/operator/nn/mkldnn/mkldnn_base-inl.h index 5670983e6aa3..e01b7b14082b 100644 --- a/src/operator/nn/mkldnn/mkldnn_base-inl.h +++ b/src/operator/nn/mkldnn/mkldnn_base-inl.h @@ -176,6 +176,7 @@ struct DeconvolutionParam; struct SoftmaxParam; struct SoftmaxOutputParam; struct TransposeParam; +struct ReshapeParam; bool SupportMKLDNNAct(const ActivationParam& param); bool SupportMKLDNNAct(const ActivationParam& param, const NDArray &input); bool SupportQuantizedMKLDNNAct(const ActivationParam ¶m); @@ -184,6 +185,7 @@ bool SupportMKLDNNDeconv(const DeconvolutionParam& params, const NDArray &input) bool SupportMKLDNNSoftmax(const SoftmaxParam& param, const NDArray &input, const NDArray &output); bool SupportMKLDNNSoftmaxOutput(const SoftmaxOutputParam ¶m); bool SupportMKLDNNTranspose(const TransposeParam& param, const NDArray &data); +bool SupportMKLDNNReshape(const ReshapeParam ¶m, const NDArray &data); } // namespace op static int GetTypeSize(int dtype) { diff --git a/src/operator/nn/mkldnn/mkldnn_flatten.cc b/src/operator/nn/mkldnn/mkldnn_flatten.cc new file mode 100644 index 000000000000..fdc02f960009 --- /dev/null +++ b/src/operator/nn/mkldnn/mkldnn_flatten.cc @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \file mkldnn_flatten.cc + * \brief Implement flatten operator by using mkldnn reorder primitive + * \author Wuxun Zhang +*/ + +#if MXNET_USE_MKLDNN == 1 + +#include "mkldnn_reshape-inl.h" + +namespace mxnet { +namespace op { + +class MKLDNNFlattenFwd : public MKLDNNReshapeFwd { + public: + explicit MKLDNNFlattenFwd(const OpReqType &req, + const NDArray &input, + const NDArray &output) + : MKLDNNReshapeFwd(req, input, output) {} +}; + +static MKLDNNFlattenFwd &GetFlattenForward(const OpReqType &req, + const NDArray &input, + const NDArray &output) { +#if DMLC_CXX11_THREAD_LOCAL + static thread_local std::unordered_map fwds; +#else + static MX_THREAD_LOCAL std::unordered_map fwds; +#endif + OpSignature key; + key.AddSign(req); + key.AddSign(input); + + auto it = fwds.find(key); + if (it == fwds.end()) { + MKLDNNFlattenFwd fwd(req, input, output); + it = AddToCache(&fwds, key, fwd); + } + return it->second; +} + +void MKLDNNFlattenForward(const nnvm::NodeAttrs &attrs, + const OpContext &ctx, + const NDArray &input, + const OpReqType &req, + const NDArray &output) { + if (req == kNullOp) return; + CHECK_NE(req, kAddTo) << "kAddTo is not supported yet"; + + auto fwd = GetFlattenForward(req, input, output); + auto ws_size = fwd.GetWorkspaceSize(); + void* ws_ptr = nullptr; + if (ws_size) { + mshadow::Stream *s = ctx.get_stream(); + mshadow::Tensor ws = ctx.requested[0] + .get_space_typed(mshadow::Shape1(ws_size), s); + ws_ptr = reinterpret_cast(ws.dptr_); + } + + fwd.Execute(input, output, ws_ptr); +} + +} // namespace op +} // namespace mxnet + +#endif diff --git a/src/operator/nn/mkldnn/mkldnn_ops-inl.h b/src/operator/nn/mkldnn/mkldnn_ops-inl.h index 2699a02f9b9b..502abff6231b 100644 --- a/src/operator/nn/mkldnn/mkldnn_ops-inl.h +++ b/src/operator/nn/mkldnn/mkldnn_ops-inl.h @@ -119,12 +119,17 @@ void MKLDNNTransposeForward(const nnvm::NodeAttrs& attrs, const OpReqType &req, const NDArray &output); -void MKLDNNReshapeForward(const nnvm::NodeAttrs &attrs, +void MKLDNNReshapeForward(const nnvm::NodeAttrs& attrs, const OpContext &ctx, - const NDArray &data, + const NDArray &input, const OpReqType &req, const NDArray &output); +void MKLDNNFlattenForward(const nnvm::NodeAttrs &attrs, + const OpContext &ctx, + const NDArray &input, + const OpReqType &req, + const NDArray &output); } // namespace op } // namespace mxnet #endif // MXNET_USE_MKLDNN == 1 diff --git a/src/operator/nn/mkldnn/mkldnn_reshape-inl.h b/src/operator/nn/mkldnn/mkldnn_reshape-inl.h new file mode 100644 index 000000000000..63e367b4dc7f --- /dev/null +++ b/src/operator/nn/mkldnn/mkldnn_reshape-inl.h @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file mkldnn_reshape-inl.h + * \brief Function definition of mkldnn reshape operator + */ + +#ifndef MXNET_OPERATOR_NN_MKLDNN_MKLDNN_RESHAPE_INL_H_ +#define MXNET_OPERATOR_NN_MKLDNN_MKLDNN_RESHAPE_INL_H_ + +#if MXNET_USE_MKLDNN == 1 +#include +#include "mkldnn_base-inl.h" +#include "../../tensor/matrix_op-inl.h" + +namespace mxnet { +namespace op { + +class MKLDNNReshapeFwd { + protected: + std::shared_ptr data_; + std::shared_ptr out_; + std::shared_ptr temp_; + std::vector prims_; + bool needInvalidateInput = false; + + public: + MKLDNNReshapeFwd(const OpReqType &req, + const NDArray &input, + const NDArray &output); + int GetWorkspaceSize(); + void SetNewMem(const NDArray &input, + const NDArray &output, + void* workspace = nullptr); + void Execute(const NDArray &input, + const NDArray &output, + void* workspace = nullptr); +}; + +typedef ParamOpSign MKLDNNReshapeSignature; +MKLDNNReshapeFwd &GetReshapeForward(const ReshapeParam& param, + const OpReqType &req, + const NDArray &input, + const NDArray &output); + +} // namespace op +} // namespace mxnet + +#endif // MXNET_USE_MKLDNN == 1 +#endif // MXNET_OPERATOR_NN_MKLDNN_MKLDNN_RESHAPE_INL_H_ diff --git a/src/operator/nn/mkldnn/mkldnn_reshape.cc b/src/operator/nn/mkldnn/mkldnn_reshape.cc index 4f1d67a8ff9e..063c85dae39a 100644 --- a/src/operator/nn/mkldnn/mkldnn_reshape.cc +++ b/src/operator/nn/mkldnn/mkldnn_reshape.cc @@ -26,7 +26,7 @@ #if MXNET_USE_MKLDNN == 1 #include -#include "../../tensor/matrix_op-inl.h" +#include "mkldnn_reshape-inl.h" namespace mxnet { namespace op { @@ -43,117 +43,106 @@ bool SupportMKLDNNReshape(const ReshapeParam ¶m, return true; } -typedef ParamOpSign MKLDNNReshapeSignature; - -class MKLDNNReshapeForward { - std::shared_ptr data_; - std::shared_ptr out_; - std::shared_ptr temp_; - std::vector prims_; - - bool needInvalidateInput = false; - - public: - MKLDNNReshapeForward(const ReshapeParam ¶m, - const OpReqType &req, - const NDArray &input, - const NDArray &output) { - auto engine = CpuEngine::Get()->get_engine(); - - // data_ - auto in_mem = input.GetMKLDNNData(); - auto in_pd = in_mem->get_primitive_desc(); - data_ = std::make_shared(in_pd, nullptr); - - // temp_ - auto temp_dims = mkldnn::memory::dims(input.shape().begin(), input.shape().end()); - auto temp_type = static_cast(in_pd.desc().data.data_type); - auto temp_fmt = static_cast(GetDefaultFormat(in_pd.desc())); - auto temp_desc = mkldnn::memory::desc(temp_dims, temp_type, temp_fmt); - auto temp_pd = mkldnn::memory::primitive_desc(temp_desc, engine); - temp_ = std::make_shared(temp_pd, nullptr); - - // destination - out_ = std::make_shared(temp_pd, nullptr); - - if (req == kWriteInplace) { - // If the input has MKL-DNN internal layout, we need reorder it to a temporal buffer with - // default layout and copy from the temporal buffer back to output buffer which has the same - // address with input buffer. - // If the input has default layout, then nothing need to do. - if (input.IsMKLDNNData()) { - prims_.push_back(mkldnn::reorder(*data_, *temp_)); // reorder to default - prims_.push_back(mkldnn::reorder(*temp_, *out_)); // copy back - needInvalidateInput = true; - } - } else if (req == kWriteTo) { - if (input.IsMKLDNNData()) { - prims_.push_back(mkldnn::reorder(*data_, *temp_)); // reorder to default - prims_.push_back(mkldnn::reorder(*temp_, *out_)); // copy to the output buffer - needInvalidateInput = false; - } else { - prims_.push_back(mkldnn::reorder(*data_, *out_)); // copy directly from input to output - needInvalidateInput = false; - } +MKLDNNReshapeFwd::MKLDNNReshapeFwd(const OpReqType &req, + const NDArray &input, + const NDArray &output) { + auto engine = CpuEngine::Get()->get_engine(); + + // data_ + auto in_mem = input.GetMKLDNNData(); + auto in_pd = in_mem->get_primitive_desc(); + data_ = std::make_shared(in_pd, nullptr); + + // temp_ + auto temp_dims = mkldnn::memory::dims(input.shape().begin(), input.shape().end()); + auto temp_type = static_cast(in_pd.desc().data.data_type); + auto temp_fmt = static_cast(GetDefaultFormat(in_pd.desc())); + auto temp_desc = mkldnn::memory::desc(temp_dims, temp_type, temp_fmt); + auto temp_pd = mkldnn::memory::primitive_desc(temp_desc, engine); + temp_ = std::make_shared(temp_pd, nullptr); + + // destination + out_ = std::make_shared(temp_pd, nullptr); + + if (req == kWriteInplace) { + // If the input has MKL-DNN internal layout, we need reorder it to a temporal buffer with + // default layout and copy from the temporal buffer back to output buffer which has the same + // address with input buffer. + // If the input has default layout, then nothing need to do. + if (input.IsMKLDNNData()) { + prims_.push_back(mkldnn::reorder(*data_, *temp_)); // reorder to default + prims_.push_back(mkldnn::reorder(*temp_, *out_)); // copy back + needInvalidateInput = true; + } + } else if (req == kWriteTo) { + if (input.IsMKLDNNData()) { + prims_.push_back(mkldnn::reorder(*data_, *temp_)); // reorder to default + prims_.push_back(mkldnn::reorder(*temp_, *out_)); // copy to the output buffer + needInvalidateInput = false; } else { - LOG(FATAL) << "not supported req type: " << req; + prims_.push_back(mkldnn::reorder(*data_, *out_)); // copy directly from input to output + needInvalidateInput = false; } + } else { + LOG(FATAL) << "not supported req type: " << req; } +} - int GetWorkspaceSize() { - return temp_ ? temp_->get_primitive_desc().get_size() : 0; - } +int MKLDNNReshapeFwd::GetWorkspaceSize() { + return temp_ ? temp_->get_primitive_desc().get_size() : 0; +} - void SetNewMem(const NDArray &input, const NDArray &output, void* workspace = nullptr) { - if (input.IsMKLDNNData()) { - this->data_->set_data_handle(input.GetMKLDNNData()->get_data_handle()); - } else { - MSHADOW_TYPE_SWITCH(input.dtype(), DTYPE, { - this->data_->set_data_handle(input.data().dptr()); - }) - } +void MKLDNNReshapeFwd::SetNewMem(const NDArray &input, + const NDArray &output, + void* workspace) { + if (input.IsMKLDNNData()) { + this->data_->set_data_handle(input.GetMKLDNNData()->get_data_handle()); + } else { + MSHADOW_TYPE_SWITCH(input.dtype(), DTYPE, { + this->data_->set_data_handle(input.data().dptr()); + }) + } - if (output.IsMKLDNNData()) { - this->out_->set_data_handle(output.GetMKLDNNData()->get_data_handle()); - } else { - MSHADOW_TYPE_SWITCH(output.dtype(), DTYPE, { - this->out_->set_data_handle(output.data().dptr()); - }) - } + if (output.IsMKLDNNData()) { + this->out_->set_data_handle(output.GetMKLDNNData()->get_data_handle()); + } else { + MSHADOW_TYPE_SWITCH(output.dtype(), DTYPE, { + this->out_->set_data_handle(output.data().dptr()); + }) + } - if (workspace) { - this->temp_->set_data_handle(workspace); - } + if (workspace) { + this->temp_->set_data_handle(workspace); } +} - void Execute(const NDArray &input, - const NDArray &output, - void* workspace = nullptr) { - // set memory handles - SetNewMem(input, output, workspace); - // register primitives - auto stream = MKLDNNStream::Get(); - for (auto &v : this->prims_) { - stream->RegisterPrim(v); - } - stream->Submit(); - // invalidate mkldnn memory in input - if (needInvalidateInput) { - const_cast(input).InvalidateMKLDNNData(); - } +void MKLDNNReshapeFwd::Execute(const NDArray &input, + const NDArray &output, + void* workspace) { + // set memory handles + SetNewMem(input, output, workspace); + // register primitives + auto stream = MKLDNNStream::Get(); + for (auto &v : this->prims_) { + stream->RegisterPrim(v); } -}; + stream->Submit(); + // invalidate mkldnn memory in input + if (needInvalidateInput) { + const_cast(input).InvalidateMKLDNNData(); + } +} -static MKLDNNReshapeForward &GetReshapeForward(const ReshapeParam& param, - const OpReqType &req, - const NDArray &input, - const NDArray &output) { +MKLDNNReshapeFwd &GetReshapeForward(const ReshapeParam& param, + const OpReqType &req, + const NDArray &input, + const NDArray &output) { #if DMLC_CXX11_THREAD_LOCAL static thread_local std::unordered_map fwds; + MKLDNNReshapeFwd, OpHash> fwds; #else static MX_THREAD_LOCAL std::unordered_map fwds; + MKLDNNReshapeFwd, OpHash> fwds; #endif MKLDNNReshapeSignature key(param); key.AddSign(req); @@ -162,7 +151,7 @@ static MKLDNNReshapeForward &GetReshapeForward(const ReshapeParam& param, auto it = fwds.find(key); if (it == fwds.end()) { - MKLDNNReshapeForward fwd(param, req, input, output); + MKLDNNReshapeFwd fwd(req, input, output); it = AddToCache(&fwds, key, fwd); } return it->second; diff --git a/src/operator/tensor/matrix_op.cc b/src/operator/tensor/matrix_op.cc index b4abc9f5974a..c2bcb29193a7 100644 --- a/src/operator/tensor/matrix_op.cc +++ b/src/operator/tensor/matrix_op.cc @@ -111,12 +111,13 @@ static void ReshapeComputeExCPU(const nnvm::NodeAttrs& attrs, const std::vector& inputs, const std::vector& req, const std::vector& outputs) { + const ReshapeParam& param = nnvm::get(attrs.parsed); CHECK_EQ(inputs.size(), 1U); CHECK_EQ(outputs.size(), 1U); // If inputs are supposed to be in MKLDNN format and // MKLDNNsupport the data type or the shape. Then convert // it to the output format and shape - if (SupportMKLDNNArray(inputs[0].dtype(), inputs[0].shape())) { + if (SupportMKLDNNReshape(param, inputs[0])) { MKLDNNReshapeForward(attrs, ctx, inputs[0], req[0], outputs[0]); return; } @@ -233,12 +234,9 @@ static void FlattenEx(const nnvm::NodeAttrs& attrs, CHECK_EQ(inputs.size(), 1U); CHECK_EQ(outputs.size(), 1U); #if MXNET_USE_MKLDNN == 1 - if (inputs[0].IsMKLDNNData()) { - MKLDNNCopy(attrs, ctx, inputs[0], req[0], outputs[0]); - // If the output is a special MKLDNN layout and the number of dimensions - // is larger than 2, we should use the default layout. - if (outputs[0].IsMKLDNNData() && inputs[0].shape().ndim() > 2) - const_cast(outputs[0]).Reorder2Default(); + auto data_ndim = inputs[0].shape().ndim(); + if (data_ndim <= 4 && inputs[0].dtype() == mshadow::kFloat32) { + MKLDNNFlattenForward(attrs, ctx, inputs[0], req[0], outputs[0]); return; } else { // This happens if inputs are supposed to be in MKLDNN format @@ -252,10 +250,10 @@ static void FlattenEx(const nnvm::NodeAttrs& attrs, #if MXNET_USE_MKLDNN == 1 static inline bool FlattenStorageType(const nnvm::NodeAttrs& attrs, - const int dev_mask, - DispatchMode* dispatch_mode, - std::vector *in_attrs, - std::vector *out_attrs) { + const int dev_mask, + DispatchMode* dispatch_mode, + std::vector *in_attrs, + std::vector *out_attrs) { CHECK_EQ(in_attrs->size(), 1); CHECK_EQ(out_attrs->size(), 1); return MKLDNNStorageType(attrs, dev_mask, true, dispatch_mode, in_attrs, diff --git a/tests/python/gpu/test_operator_gpu.py b/tests/python/gpu/test_operator_gpu.py index 064f783ec6c8..5b4f81d96065 100644 --- a/tests/python/gpu/test_operator_gpu.py +++ b/tests/python/gpu/test_operator_gpu.py @@ -1129,6 +1129,20 @@ def test_pooling_full_2d_type(pool_type): test_pooling_full_2d_type('sum') +@with_seed() +def test_flatten_slice_after_conv(): + ctx_list = [] + + data = mx.sym.Variable('conv_data') + conv = mx.symbol.Convolution(data=data, name='conv', num_filter=16, kernel=(3,3), stride=(1,1)) + flatten = mx.symbol.flatten(data=conv) + slice_sym = mx.symbol.slice(data=flatten, begin=0, end=1) + + ctx_list = [{'ctx': mx.gpu(0), 'conv_data': (2, 16, 16, 16), 'type_dict': {'conv_data': np.float32}}, + {'ctx': mx.cpu(0), 'conv_data': (2, 16, 16, 16), 'type_dict': {'conv_data': np.float32}}] + check_consistency(slice_sym, ctx_list) + + @with_seed() def test_global_pooling(): def test_1d_pooling(pool_type, p_value=2): diff --git a/tests/python/mkl/test_mkldnn.py b/tests/python/mkl/test_mkldnn.py index 662edcfeb739..3e623b59977d 100644 --- a/tests/python/mkl/test_mkldnn.py +++ b/tests/python/mkl/test_mkldnn.py @@ -233,6 +233,26 @@ def hybrid_forward(self, F, x, *args, **kwargs): mx.test_utils.assert_almost_equal(out1.asnumpy(), out2.asnumpy(), rtol=1e-5, atol=1e-6) +@with_seed() +def test_flatten_slice_after_conv(): + data = mx.symbol.Variable('data') + weight = mx.symbol.Variable('weight') + bias = mx.symbol.Variable('bias') + conv1= mx.symbol.Convolution(data = data, weight=weight, bias=bias, name='conv1', num_filter=64, kernel=(3,3), stride=(1,1)) + flatten1 = mx.symbol.flatten(data = conv1) + slice1 = mx.symbol.slice(data = flatten1, begin=0, end=1) + + shape = (2, 16, 16, 16) + val = np.random.rand(2, 16, 16, 16).astype(np.float32) + exe = slice1.simple_bind(Context.default_ctx, data=shape) + exe.arg_arrays[0][:] = val + exe.arg_arrays[1][:] = np.random.normal(size=exe.arg_arrays[1].shape) + exe.arg_arrays[2][:] = np.random.normal(size=exe.arg_arrays[2].shape) + p = exe.forward(is_train=False) + p[0].wait_to_read() + print(p[0]) + + def test_mkldnn_sum_inplace_with_cpu_layout(): x_shape = (32, 3, 224, 224) From 1ae73de6915119c2d453d1e0fe163aa6b353f30f Mon Sep 17 00:00:00 2001 From: suyz526 <35927539+suyz526@users.noreply.github.com> Date: Mon, 8 Jul 2019 20:30:34 +0200 Subject: [PATCH 047/813] fix doc for sort and argsort (#15317) --- src/operator/tensor/ordering_op.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/operator/tensor/ordering_op.cc b/src/operator/tensor/ordering_op.cc index e2f014d1ad41..b0ade2076a2c 100644 --- a/src/operator/tensor/ordering_op.cc +++ b/src/operator/tensor/ordering_op.cc @@ -114,7 +114,7 @@ Examples:: [ 1., 3.]] // flattens and then sorts - sort(x) = [ 1., 1., 3., 4.] + sort(x, axis=None) = [ 1., 1., 3., 4.] // sorts along the first axis sort(x, axis=0) = [[ 1., 1.], @@ -173,7 +173,7 @@ Examples:: [ 0., 1., 0.]] // flatten and then sort - argsort(x) = [ 3., 1., 5., 0., 4., 2.] + argsort(x, axis=None) = [ 3., 1., 5., 0., 4., 2.] )code" ADD_FILELINE) .set_num_inputs(1) .set_num_outputs(1) From 09c71bf3144b09a28b9d09d33703a3dcbf4ca9a5 Mon Sep 17 00:00:00 2001 From: Lanking Date: Mon, 8 Jul 2019 11:48:30 -0700 Subject: [PATCH 048/813] Add Sparse NDArray support for Scala (#15378) * add Sparse Support * add imperative invoke sparse support * add retain method and comments * add getData method * add Sparse NDIter test * remove debug line --- .../main/scala/org/apache/mxnet/DType.scala | 17 +- .../scala/org/apache/mxnet/Executor.scala | 9 +- .../main/scala/org/apache/mxnet/LibInfo.scala | 27 ++- .../main/scala/org/apache/mxnet/NDArray.scala | 65 +++++- .../scala/org/apache/mxnet/SparseFormat.scala | 25 +++ .../org/apache/mxnet/SparseNDArray.scala | 196 ++++++++++++++++++ .../scala/org/apache/mxnet/NDArraySuite.scala | 16 ++ .../org/apache/mxnet/SparseNDArraySuite.scala | 93 +++++++++ .../native/org_apache_mxnet_native_c_api.cc | 75 ++++++- .../native/org_apache_mxnet_native_c_api.h | 48 ++++- 10 files changed, 543 insertions(+), 28 deletions(-) create mode 100644 scala-package/core/src/main/scala/org/apache/mxnet/SparseFormat.scala create mode 100644 scala-package/core/src/main/scala/org/apache/mxnet/SparseNDArray.scala create mode 100644 scala-package/core/src/test/scala/org/apache/mxnet/SparseNDArraySuite.scala diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/DType.scala b/scala-package/core/src/main/scala/org/apache/mxnet/DType.scala index f3a8e8e9a4a5..1d5cc2847ac0 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/DType.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/DType.scala @@ -24,26 +24,17 @@ object DType extends Enumeration { val Float16 = Value(2, "float16") val UInt8 = Value(3, "uint8") val Int32 = Value(4, "int32") + val Int8 = Value(5, "int8") + val Int64 = Value(6, "int64") val Unknown = Value(-1, "unknown") private[mxnet] def numOfBytes(dtype: DType): Int = { dtype match { - case DType.UInt8 => 1 + case DType.UInt8 | DType.Int8 => 1 case DType.Int32 => 4 case DType.Float16 => 2 case DType.Float32 => 4 - case DType.Float64 => 8 + case DType.Float64 | DType.Int64 => 8 case DType.Unknown => 0 } } - private[mxnet] def getType(dtypeStr: String): DType = { - dtypeStr match { - case "UInt8" => DType.UInt8 - case "Int32" => DType.Int32 - case "Float16" => DType.Float16 - case "Float32" => DType.Float32 - case "Float64" => DType.Float64 - case _ => throw new IllegalArgumentException( - s"DType: $dtypeStr not found! please set it in DType.scala") - } - } } diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/Executor.scala b/scala-package/core/src/main/scala/org/apache/mxnet/Executor.scala index b0fae0f9d58d..6365f9cb4645 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/Executor.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/Executor.scala @@ -159,7 +159,14 @@ class Executor private[mxnet](private[mxnet] val handle: ExecutorHandle, private def getOutputs: Array[NDArray] = { val ndHandles = ArrayBuffer[NDArrayHandle]() checkCall(_LIB.mxExecutorOutputs(handle, ndHandles)) - ndHandles.toArray.map(new NDArray(_, addToCollector = false)) + ndHandles.toArray.map(ele => { + val nd = new NDArray(ele, addToCollector = false) + if (nd.isSparse) { + nd.asInstanceOf[SparseNDArray] + } + nd + } + ) } /** diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/LibInfo.scala b/scala-package/core/src/main/scala/org/apache/mxnet/LibInfo.scala index 640ecf5d5978..0ee6476be365 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/LibInfo.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/LibInfo.scala @@ -31,13 +31,14 @@ private[mxnet] class LibInfo { @native def mxListAllOpNames(names: ListBuffer[String]): Int @native def nnGetOpHandle(opName: String, opHandle: RefLong): Int // NDArray - @native def mxImperativeInvoke(creator: FunctionHandle, + @native def mxImperativeInvokeEx(creator: FunctionHandle, inputs: Array[NDArrayHandle], outputsGiven: Array[NDArrayHandle], outputs: ArrayBuffer[NDArrayHandle], numParams: Int, paramKeys: Array[String], - paramVals: Array[String]): Int + paramVals: Array[String], + outStype: ArrayBuffer[Int]): Int @native def mxNDArrayFree(handle: NDArrayHandle): Int @native def mxNDArrayCreateNone(out: NDArrayHandleRef): Int @native def mxNDArrayCreateEx(shape: Array[Int], @@ -47,6 +48,20 @@ private[mxnet] class LibInfo { delayAlloc: Int, dtype: Int, out: NDArrayHandleRef): Int + // scalastyle:off parameterNum + @native def mxNDArrayCreateSparseEx(storageType: Int, + shape: Array[Int], + ndim: Int, + devType: Int, + devId: Int, + delayAlloc: Int, + dtype: Int, + numAux: Int, + auxTypes: Array[Int], + auxNdims: Array[Int], + auxShapes: Array[Int], + out: NDArrayHandleRef): Int + // scalastyle:on parameterNum @native def mxNDArrayWaitAll(): Int @native def mxNDArrayWaitToRead(handle: NDArrayHandle): Int @native def mxListFunctions(functions: ListBuffer[FunctionHandle]): Int @@ -76,6 +91,9 @@ private[mxnet] class LibInfo { @native def mxNDArrayGetShape(handle: NDArrayHandle, ndim: MXUintRef, data: ArrayBuffer[Int]): Int + @native def mxNDArraySyncCopyFromNDArray(handleDst: NDArrayHandle, + handleSrc: NDArrayHandle, + locator: Int): Int @native def mxNDArraySyncCopyToCPU(handle: NDArrayHandle, data: Array[Byte], size: Int): Int @@ -105,10 +123,15 @@ private[mxnet] class LibInfo { @native def mxNDArraySave(fname: String, handles: Array[NDArrayHandle], keys: Array[String]): Int + @native def mxNDArrayGetDataNDArray(handle: NDArrayHandle, out: NDArrayHandleRef): Int + @native def mxNDArrayGetAuxNDArray(handle: NDArrayHandle, + location: Int, + out: NDArrayHandleRef): Int @native def mxNDArrayGetContext(handle: NDArrayHandle, devTypeId: RefInt, devId: RefInt): Int @native def mxNDArraySaveRawBytes(handle: NDArrayHandle, buf: ArrayBuffer[Byte]): Int @native def mxNDArrayLoadFromRawBytes(bytes: Array[Byte], handle: NDArrayHandleRef): Int @native def mxNDArrayGetDType(handle: NDArrayHandle, dtype: RefInt): Int + @native def mxNDArrayGetStorageType(handle: NDArrayHandle, stype: RefInt): Int // KVStore Server @native def mxInitPSEnv(keys: Array[String], values: Array[String]): Int diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala b/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala index 40888012fc5a..1b7b31b32cce 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala @@ -21,7 +21,8 @@ import java.nio.{ByteBuffer, ByteOrder} import org.apache.mxnet.Base._ import org.apache.mxnet.DType.DType -import org.apache.mxnet.MX_PRIMITIVES.{MX_PRIMITIVE_TYPE} +import org.apache.mxnet.MX_PRIMITIVES.MX_PRIMITIVE_TYPE +import org.apache.mxnet.SparseFormat.SparseFormat import org.slf4j.LoggerFactory import scala.collection.mutable @@ -113,10 +114,22 @@ object NDArray extends NDArrayBase { } val outputs = ArrayBuffer.empty[NDArrayHandle] - checkCall(_LIB.mxImperativeInvoke(function.handle, ndArgs.map(_.handle).toArray, outputVars, - outputs, updatedKwargs.size, updatedKwargs.keys.toArray, updatedKwargs.values.toArray)) + val outStypes = ArrayBuffer.empty[Int] + checkCall(_LIB.mxImperativeInvokeEx(function.handle, + ndArgs.map(_.handle).toArray, + outputVars, + outputs, + updatedKwargs.size, + updatedKwargs.keys.toArray, + updatedKwargs.values.toArray, + outStypes)) new NDArrayFuncReturn(Option(oriOutputs).getOrElse { - val outputArrs = outputs.map(new NDArray(_)).toArray + val outputArrs = (outputs zip outStypes).map( + ele => ele._2 match { + case 0 => new NDArray(ele._1) + case _ => new SparseNDArray(ele._1) + } + ).toArray addDependency(ndArgs.toArray, outputArrs) outputArrs }) @@ -943,6 +956,12 @@ class NDArray private[mxnet](private[mxnet] val handle: NDArrayHandle, DType(mxDtype.value) } + val sparseFormat: SparseFormat = { + val mxSF = new RefInt + checkCall(_LIB.mxNDArrayGetStorageType(handle, mxSF)) + SparseFormat(mxSF.value) + } + /** * Return a copied numpy array of current array with specified type. * @param dtype Desired type of result array. @@ -1309,6 +1328,30 @@ class NDArray private[mxnet](private[mxnet] val handle: NDArrayHandle, if (this.context == context) this else this.copyTo(context) } + /** + * check if NDArray is SparseNDArray + * @return Boolean + */ + def isSparse: Boolean = { + this.sparseFormat.id != 0 + } + + /** + * Convert a NDArray to SparseNDArray + * + * @param sfOption the target sparse type + * @return SparseNDArray + */ + def toSparse(sfOption : Option[SparseFormat] = None): SparseNDArray = { + val sf = sfOption.getOrElse(SparseFormat.ROW_SPARSE) + if (sf.id == 0) throw new IllegalArgumentException("Require Sparse") + if (isSparse && sfOption.isEmpty) { + this.asInstanceOf[SparseNDArray] + } else { + NDArray.api.cast_storage(this, sf.toString).head.asInstanceOf[SparseNDArray] + } + } + override def equals(o: Any): Boolean = o match { case that: NDArray => that != null && that.shape == this.shape && that.toArray.sameElements(this.toArray) @@ -1479,6 +1522,7 @@ private[mxnet] class NDArrayInternal (private val internal: Array[Byte], private case DType.Float32 => units.map(wrapBytes(_).getFloat.toDouble) case DType.Float64 => units.map(wrapBytes(_).getDouble) case DType.Int32 => units.map(wrapBytes(_).getInt.toDouble) + case DType.Int64 => units.map(wrapBytes(_).getLong.toDouble) case DType.UInt8 => internal.map(_.toDouble) } } @@ -1488,6 +1532,7 @@ private[mxnet] class NDArrayInternal (private val internal: Array[Byte], private case DType.Float32 => units.map(wrapBytes(_).getFloat) case DType.Float64 => units.map(wrapBytes(_).getDouble.toFloat) case DType.Int32 => units.map(wrapBytes(_).getInt.toFloat) + case DType.Int64 => units.map(wrapBytes(_).getLong.toFloat) case DType.UInt8 => internal.map(_.toFloat) } } @@ -1497,15 +1542,27 @@ private[mxnet] class NDArrayInternal (private val internal: Array[Byte], private case DType.Float32 => units.map(wrapBytes(_).getFloat.toInt) case DType.Float64 => units.map(wrapBytes(_).getDouble.toInt) case DType.Int32 => units.map(wrapBytes(_).getInt) + case DType.Int64 => units.map(wrapBytes(_).getLong.toInt) case DType.UInt8 => internal.map(_.toInt) } } + def toLongArray: Array[Long] = { + require(dtype != DType.Float16, "Currently cannot convert float16 to native numerical types") + dtype match { + case DType.Float32 => units.map(wrapBytes(_).getFloat.toLong) + case DType.Float64 => units.map(wrapBytes(_).getDouble.toLong) + case DType.Int32 => units.map(wrapBytes(_).getInt.toLong) + case DType.Int64 => units.map(wrapBytes(_).getLong) + case DType.UInt8 => internal.map(_.toLong) + } + } def toByteArray: Array[Byte] = { require(dtype != DType.Float16, "Currently cannot convert float16 to native numerical types") dtype match { case DType.Float16 | DType.Float32 => units.map(wrapBytes(_).getFloat.toByte) case DType.Float64 => units.map(wrapBytes(_).getDouble.toByte) case DType.Int32 => units.map(wrapBytes(_).getInt.toByte) + case DType.Int64 => units.map(wrapBytes(_).getLong.toByte) case DType.UInt8 => internal.clone() } } diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/SparseFormat.scala b/scala-package/core/src/main/scala/org/apache/mxnet/SparseFormat.scala new file mode 100644 index 000000000000..acb0c0f24070 --- /dev/null +++ b/scala-package/core/src/main/scala/org/apache/mxnet/SparseFormat.scala @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.mxnet + +object SparseFormat extends Enumeration { + type SparseFormat = Value + val DEFAULT = Value(0, "default") + val ROW_SPARSE = Value(1, "row_sparse") + val CSR = Value(2, "csr") +} diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/SparseNDArray.scala b/scala-package/core/src/main/scala/org/apache/mxnet/SparseNDArray.scala new file mode 100644 index 000000000000..f3fe638e41ed --- /dev/null +++ b/scala-package/core/src/main/scala/org/apache/mxnet/SparseNDArray.scala @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.mxnet + +import org.apache.mxnet.Base.{NDArrayHandle, NDArrayHandleRef, checkCall, _LIB} +import org.apache.mxnet.DType.DType +import org.apache.mxnet.SparseFormat.SparseFormat + +object SparseNDArray { + /** + * Create a Compressed Sparse Row Storage (CSR) Format Matrix + * @param data the data to feed + * @param indices The indices array stores the column index for each non-zero element in data + * @param indptr The indptr array is what will help identify the rows where the data appears + * @param shape the shape of CSR NDArray to be created + * @param ctx the context of this NDArray + * @return SparseNDArray + */ + def csrMatrix(data: Array[Float], indices: Array[Float], + indptr: Array[Float], shape: Shape, ctx: Context): SparseNDArray = { + val fmt = SparseFormat.CSR + val dataND = NDArray.array(data, Shape(data.length), ctx) + val indicesND = NDArray.array(indices, Shape(indices.length), ctx).asType(DType.Int64) + val indptrND = NDArray.array(indptr, Shape(indptr.length), ctx).asType(DType.Int64) + val dTypes = Array(indptrND.dtype, indicesND.dtype) + val shapes = Array(indptrND.shape, indicesND.shape) + val handle = + newAllocHandle(fmt, shape, ctx, false, DType.Float32, dTypes, shapes) + checkCall(_LIB.mxNDArraySyncCopyFromNDArray(handle, dataND.handle, -1)) + checkCall(_LIB.mxNDArraySyncCopyFromNDArray(handle, indptrND.handle, 0)) + checkCall(_LIB.mxNDArraySyncCopyFromNDArray(handle, indicesND.handle, 1)) + new SparseNDArray(handle) + } + + /** + * RowSparseNDArray stores the matrix in row sparse format, + * which is designed for arrays of which most row slices are all zeros + * @param data Any Array(Array(... Array(Float))) + * @param indices the indices to store the data + * @param shape shape of the NDArray + * @param ctx Context + * @return SparseNDArray + */ + def rowSparseArray(data: Array[_], indices: Array[Float], + shape: Shape, ctx: Context): SparseNDArray = { + val dataND = NDArray.toNDArray(data) + val indicesND = NDArray.array(indices, Shape(indices.length), ctx).asType(DType.Int64) + rowSparseArray(dataND, indicesND, shape, ctx) + } + + /** + * RowSparseNDArray stores the matrix in row sparse format, + * which is designed for arrays of which most row slices are all zeros + * @param data NDArray input + * @param indices in NDArray. Only DType.Int64 supported + * @param shape shape of the NDArray + * @param ctx Context + * @return + */ + def rowSparseArray(data: NDArray, indices: NDArray, + shape: Shape, ctx: Context): SparseNDArray = { + val fmt = SparseFormat.ROW_SPARSE + val handle = newAllocHandle(fmt, shape, ctx, false, + DType.Float32, Array(indices.dtype), Array(indices.shape)) + checkCall(_LIB.mxNDArraySyncCopyFromNDArray(handle, data.handle, -1)) + checkCall(_LIB.mxNDArraySyncCopyFromNDArray(handle, indices.handle, 0)) + new SparseNDArray(handle) + } + + def retain(sparseNDArray: SparseNDArray, indices: Array[Float]): SparseNDArray = { + if (sparseNDArray.sparseFormat == SparseFormat.CSR) { + throw new IllegalArgumentException("CSR not supported") + } + NDArray.genericNDArrayFunctionInvoke("_sparse_retain", + Seq(sparseNDArray, NDArray.toNDArray(indices))).head.toSparse() + } + + private def newAllocHandle(stype : SparseFormat, + shape: Shape, + ctx: Context, + delayAlloc: Boolean, + dtype: DType = DType.Float32, + auxDTypes: Array[DType], + auxShapes: Array[Shape]) : NDArrayHandle = { + val hdl = new NDArrayHandleRef + checkCall(_LIB.mxNDArrayCreateSparseEx( + stype.id, + shape.toArray, + shape.length, + ctx.deviceTypeid, + ctx.deviceId, + if (delayAlloc) 1 else 0, + dtype.id, + auxDTypes.length, + auxDTypes.map(_.id), + auxShapes.map(_.length), + auxShapes.map(_.get(0)), + hdl) + ) + hdl.value + } +} + +/** + * Sparse NDArray is the child class of NDArray designed to hold the Sparse format + * + *

Currently, Rowsparse and CSR typed NDArray is supported. Most of the Operators + * will convert Sparse NDArray to dense. Basic operators like add will + * have optimization for sparse operattions

+ * @param handle The pointer that SparseNDArray holds + * @param writable whether the NDArray is writable + */ +class SparseNDArray private[mxnet] (override private[mxnet] val handle: NDArrayHandle, + override val writable: Boolean = true) + extends NDArray(handle, writable) { + + private lazy val dense: NDArray = toDense + + override def toString: String = { + dense.toString + } + + /** + * Convert a SparseNDArray to dense NDArray + * @return NDArray + */ + def toDense: NDArray = { + NDArray.api.cast_storage(this, SparseFormat.DEFAULT.toString).head + } + + override def toArray: Array[Float] = { + dense.toArray + } + + override def at(idx: Int): NDArray = { + dense.at(idx) + } + + override def slice(start: Int, end: Int): NDArray = { + NDArray.api.slice(this, Shape(start), Shape(end)) + } + + /** + * Get the Data portion from a Row Sparse NDArray + * @return NDArray + */ + def getData: NDArray = { + require(this.sparseFormat == SparseFormat.ROW_SPARSE, "Not Supported for CSR") + val handle = new NDArrayHandleRef + _LIB.mxNDArrayGetDataNDArray(this.handle, handle) + new NDArray(handle.value, false) + } + + /** + * Get the indptr Array from a CSR NDArray + * @return NDArray + */ + def getIndptr: NDArray = { + require(this.sparseFormat == SparseFormat.CSR, "Not Supported for row sparse") + getAuxNDArray(0) + } + + /** + * Get the indice Array + * @return NDArray + */ + def getIndices: NDArray = { + if (this.sparseFormat == SparseFormat.ROW_SPARSE) { + getAuxNDArray(0) + } else { + getAuxNDArray(1) + } + } + + private def getAuxNDArray(idx: Int): NDArray = { + val handle = new NDArrayHandleRef + checkCall(_LIB.mxNDArrayGetAuxNDArray(this.handle, idx, handle)) + new NDArray(handle.value, false) + } + +} diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala index c2ef641f9c9a..82b9edc8f4bb 100644 --- a/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala +++ b/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala @@ -45,6 +45,22 @@ class NDArraySuite extends FunSuite with BeforeAndAfterAll with Matchers { assert(ndones.toScalar === 1f) } + test("to sparse") { + val arr = Array( + Array(1f, 0f, 0f), + Array(0f, 3f, 0f), + Array(0f, 0f, 1f) + ) + val nd = NDArray.toNDArray(arr) + assert(!nd.isSparse) + // row sparse + var ndSparse = nd.toSparse() + assert(ndSparse.getIndices.toArray sameElements Array(0f, 1f, 2f)) + // csr + ndSparse = nd.toSparse(Some(SparseFormat.CSR)) + assert(ndSparse.getIndptr.toArray sameElements Array(0f, 1f, 2f, 3f)) + } + test("to float 64 scalar") { val ndzeros = NDArray.zeros(Shape(1), dtype = DType.Float64) assert(ndzeros.toFloat64Scalar === 0d) diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/SparseNDArraySuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/SparseNDArraySuite.scala new file mode 100644 index 000000000000..f9968efd80c5 --- /dev/null +++ b/scala-package/core/src/test/scala/org/apache/mxnet/SparseNDArraySuite.scala @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.mxnet + +import org.apache.mxnet.io.NDArrayIter +import org.scalatest.FunSuite +import org.slf4j.LoggerFactory + +class SparseNDArraySuite extends FunSuite { + + private val logger = LoggerFactory.getLogger(classOf[SparseNDArraySuite]) + + test("create CSR NDArray") { + val data = Array(7f, 8f, 9f) + val indices = Array(0f, 2f, 1f) + val indptr = Array(0f, 2f, 2f, 3f) + val shape = Shape(3, 4) + val sparseND = SparseNDArray.csrMatrix(data, indices, indptr, shape, Context.cpu()) + assert(sparseND.shape == Shape(3, 4)) + assert(sparseND.toArray + sameElements Array(7.0f, 0.0f, 8.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 9.0f, 0.0f, 0.0f)) + assert(sparseND.sparseFormat == SparseFormat.CSR) + assert(sparseND.getIndptr.toArray sameElements indptr) + assert(sparseND.getIndices.toArray sameElements indices) + } + + test("create Row Sparse NDArray") { + val data = Array( + Array(1f, 2f), + Array(3f, 4f) + ) + val indices = Array(1f, 4f) + val shape = Shape(6, 2) + val sparseND = SparseNDArray.rowSparseArray(data, indices, shape, Context.cpu()) + assert(sparseND.sparseFormat == SparseFormat.ROW_SPARSE) + assert(sparseND.shape == Shape(6, 2)) + assert(sparseND.at(1).toArray sameElements Array(1f, 2f)) + assert(sparseND.getIndices.toArray sameElements indices) + } + + test("Test retain") { + val arr = Array( + Array(1f, 2f), + Array(3f, 4f), + Array(5f, 6f) + ) + val indices = Array(0f, 1f, 3f) + val rspIn = SparseNDArray.rowSparseArray(arr, indices, Shape(4, 2), Context.cpu()) + val toRetain = Array(0f, 3f) + val rspOut = SparseNDArray.retain(rspIn, toRetain) + assert(rspOut.getData.toArray sameElements Array(1f, 2f, 5f, 6f)) + assert(rspOut.getIndices.toArray sameElements Array(0f, 3f)) + } + + test("Test add") { + val nd = NDArray.array(Array(1f, 2f, 3f), Shape(3)).toSparse(Some(SparseFormat.ROW_SPARSE)) + val nd2 = nd + nd + assert(nd2.isInstanceOf[SparseNDArray]) + assert(nd2.toArray sameElements Array(2f, 4f, 6f)) + } + + test("Test DataIter") { + val nd = NDArray.array(Array(1f, 2f, 3f), Shape(1, 3)).toSparse(Some(SparseFormat.CSR)) + val arr = IndexedSeq(nd, nd, nd, nd) + val iter = new NDArrayIter(arr) + while (iter.hasNext) { + val tempArr = iter.next().data + tempArr.foreach(ele => { + assert(ele.sparseFormat == SparseFormat.CSR) + assert(ele.shape == Shape(1, 3)) + }) + } + } + + +} diff --git a/scala-package/native/src/main/native/org_apache_mxnet_native_c_api.cc b/scala-package/native/src/main/native/org_apache_mxnet_native_c_api.cc index 9b19fd360fc4..387a0b17e252 100644 --- a/scala-package/native/src/main/native/org_apache_mxnet_native_c_api.cc +++ b/scala-package/native/src/main/native/org_apache_mxnet_native_c_api.cc @@ -93,6 +93,31 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayCreateEx return ret; } +JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayCreateSparseEx + (JNIEnv *env, jobject obj, jint storageType, jintArray shape, jint ndim, jint devType, + jint devId, jint delayAlloc, jint dtype, jint numAux, jintArray auxTypes, + jintArray auxNdims, jintArray auxShapes, jobject ndArrayHandle) { + jint *shapeArr = env->GetIntArrayElements(shape, NULL); + jint *auxTypesArr = env->GetIntArrayElements(auxTypes, NULL); + jint *auxNdimsArr = env->GetIntArrayElements(auxNdims, NULL); + jint *auxShapesArr = env->GetIntArrayElements(auxShapes, NULL); + NDArrayHandle out; + int ret = MXNDArrayCreateSparseEx(storageType, + reinterpret_cast(shapeArr), + static_cast(ndim), + devType, devId, delayAlloc, dtype, + static_cast(numAux), + reinterpret_cast(auxTypesArr), + reinterpret_cast(auxNdimsArr), + reinterpret_cast(auxShapesArr), &out); + env->ReleaseIntArrayElements(shape, shapeArr, 0); + env->ReleaseIntArrayElements(auxTypes, auxTypesArr, 0); + env->ReleaseIntArrayElements(auxNdims, auxNdimsArr, 0); + env->ReleaseIntArrayElements(auxShapes, auxShapesArr, 0); + SetLongField(env, ndArrayHandle, reinterpret_cast(out)); + return ret; +} + JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayWaitAll(JNIEnv *env, jobject obj) { return MXNDArrayWaitAll(); } @@ -179,10 +204,10 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxFuncGetInfo return ret; } -JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxImperativeInvoke +JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxImperativeInvokeEx (JNIEnv *env, jobject obj, jlong funcPtr, jlongArray inputs, jlongArray outputsGiven, jobject outputs, jint numParams, - jobjectArray paramKeys, jobjectArray paramVals) { + jobjectArray paramKeys, jobjectArray paramVals, jobject outStypes) { const char **cParamKeys = NULL; const char **cParamVals = NULL; @@ -204,6 +229,7 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxImperativeInvoke int numOutputs = 0; jlong *cOutputsGiven = NULL; NDArrayHandle *cOutputs = NULL; + const int *cOutStypes; if (outputsGiven) { cOutputsGiven = env->GetLongArrayElements(outputsGiven, NULL); cOutputs = reinterpret_cast(cOutputsGiven); @@ -211,14 +237,15 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxImperativeInvoke } jlong *cInputs = env->GetLongArrayElements(inputs, NULL); jsize numInputs = env->GetArrayLength(inputs); - int ret = MXImperativeInvoke(reinterpret_cast(funcPtr), + int ret = MXImperativeInvokeEx(reinterpret_cast(funcPtr), static_cast(numInputs), reinterpret_cast(cInputs), &numOutputs, &cOutputs, static_cast(numParams), cParamKeys, - cParamVals); + cParamVals, + &cOutStypes); env->ReleaseLongArrayElements(inputs, cInputs, 0); if (cOutputsGiven) { env->ReleaseLongArrayElements(outputsGiven, cOutputsGiven, 0); @@ -240,7 +267,9 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxImperativeInvoke if (cOutputs) { jclass longCls = env->FindClass("java/lang/Long"); + jclass intCls = env->FindClass("java/lang/Integer"); jmethodID longConst = env->GetMethodID(longCls, "", "(J)V"); + jmethodID intConst = env->GetMethodID(intCls, "", "(I)V"); // scala.collection.mutable.ListBuffer append method jclass listClass = env->FindClass("scala/collection/mutable/ArrayBuffer"); jmethodID listAppend = env->GetMethodID(listClass, "$plus$eq", @@ -249,6 +278,9 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxImperativeInvoke env->CallObjectMethod(outputs, listAppend, env->NewObject(longCls, longConst, reinterpret_cast(cOutputs[i]))); + env->CallObjectMethod(outStypes, listAppend, + env->NewObject(intCls, intConst, + cOutStypes[i])); } } @@ -379,6 +411,14 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayGetShape return ret; } +JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArraySyncCopyFromNDArray + (JNIEnv *env, jobject obj, jlong dstPtr, jlong srcPtr, jint locator) { + int ret = MXNDArraySyncCopyFromNDArray(reinterpret_cast(dstPtr), + reinterpret_cast(srcPtr), + locator); + return ret; +} + JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArraySyncCopyToCPU (JNIEnv *env, jobject obj, jlong ndArrayPtr, jbyteArray data, jint size) { jbyte *pdata = env->GetByteArrayElements(data, NULL); @@ -434,6 +474,25 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxFloat64NDArraySyncCopyFro return ret; } +JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayGetDataNDArray + (JNIEnv *env, jobject obj, jlong arrayPtr, jobject ndArrayHandle) { + NDArrayHandle out; + int ret = MXNDArrayGetDataNDArray(reinterpret_cast(arrayPtr), + &out); + SetLongField(env, ndArrayHandle, reinterpret_cast(out)); + return ret; +} + +JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayGetAuxNDArray + (JNIEnv *env, jobject obj, jlong arrayPtr, jint location, jobject ndArrayHandle) { + NDArrayHandle out; + int ret = MXNDArrayGetAuxNDArray(reinterpret_cast(arrayPtr), + static_cast(location), + &out); + SetLongField(env, ndArrayHandle, reinterpret_cast(out)); + return ret; +} + JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayGetContext (JNIEnv *env, jobject obj, jlong arrayPtr, jobject devTypeId, jobject devId) { int outDevType; @@ -540,6 +599,14 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayGetDType return ret; } +JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayGetStorageType + (JNIEnv * env, jobject obj, jlong jhandle, jobject jstype) { + int stype; + int ret = MXNDArrayGetStorageType(reinterpret_cast(jhandle), &stype); + SetIntField(env, jstype, stype); + return ret; +} + JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxInitPSEnv (JNIEnv *env, jobject obj, jobjectArray jkeys, jobjectArray jvals) { // keys and values diff --git a/scala-package/native/src/main/native/org_apache_mxnet_native_c_api.h b/scala-package/native/src/main/native/org_apache_mxnet_native_c_api.h index fac32bb0a410..c8ee0ce8d22b 100644 --- a/scala-package/native/src/main/native/org_apache_mxnet_native_c_api.h +++ b/scala-package/native/src/main/native/org_apache_mxnet_native_c_api.h @@ -41,11 +41,11 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_nnGetOpHandle /* * Class: org_apache_mxnet_LibInfo - * Method: mxImperativeInvoke - * Signature: (J[J[JLscala/collection/mutable/ArrayBuffer;I[Ljava/lang/String;[Ljava/lang/String;)I + * Method: mxImperativeInvokeEx + * Signature: (J[J[JLscala/collection/mutable/ArrayBuffer;I[Ljava/lang/String;[Ljava/lang/String;Lscala/collection/mutable/ArrayBuffer;)I */ -JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxImperativeInvoke - (JNIEnv *, jobject, jlong, jlongArray, jlongArray, jobject, jint, jobjectArray, jobjectArray); +JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxImperativeInvokeEx + (JNIEnv *, jobject, jlong, jlongArray, jlongArray, jobject, jint, jobjectArray, jobjectArray, jobject); /* * Class: org_apache_mxnet_LibInfo @@ -71,6 +71,14 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayCreateNone JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayCreateEx (JNIEnv *, jobject, jintArray, jint, jint, jint, jint, jint, jobject); +/* + * Class: org_apache_mxnet_LibInfo + * Method: mxNDArrayCreateSparseEx + * Signature: (I[IIIIIII[I[I[ILorg/apache/mxnet/Base/RefLong;)I + */ +JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayCreateSparseEx + (JNIEnv *, jobject, jint, jintArray, jint, jint, jint, jint, jint, jint, jintArray, jintArray, jintArray, jobject); + /* * Class: org_apache_mxnet_LibInfo * Method: mxNDArrayWaitAll @@ -135,6 +143,14 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxFuncInvokeEx JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayGetShape (JNIEnv *, jobject, jlong, jobject, jobject); +/* + * Class: org_apache_mxnet_LibInfo + * Method: mxNDArraySyncCopyFromNDArray + * Signature: (JJI)I + */ +JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArraySyncCopyFromNDArray + (JNIEnv *, jobject, jlong, jlong, jint); + /* * Class: org_apache_mxnet_LibInfo * Method: mxNDArraySyncCopyToCPU @@ -199,6 +215,22 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayLoad JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArraySave (JNIEnv *, jobject, jstring, jlongArray, jobjectArray); +/* + * Class: org_apache_mxnet_LibInfo + * Method: mxNDArrayGetDataNDArray + * Signature: (JLorg/apache/mxnet/Base/RefLong;)I + */ +JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayGetDataNDArray + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: org_apache_mxnet_LibInfo + * Method: mxNDArrayGetAuxNDArray + * Signature: (JILorg/apache/mxnet/Base/RefLong;)I + */ +JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayGetAuxNDArray + (JNIEnv *, jobject, jlong, jint, jobject); + /* * Class: org_apache_mxnet_LibInfo * Method: mxNDArrayGetContext @@ -231,6 +263,14 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayLoadFromRawBytes JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayGetDType (JNIEnv *, jobject, jlong, jobject); +/* + * Class: org_apache_mxnet_LibInfo + * Method: mxNDArrayGetStorageType + * Signature: (JLorg/apache/mxnet/Base/RefInt;)I + */ +JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxNDArrayGetStorageType + (JNIEnv *, jobject, jlong, jobject); + /* * Class: org_apache_mxnet_LibInfo * Method: mxInitPSEnv From b62aaf3d984c647a2fbab4a292ad1f11e6147abe Mon Sep 17 00:00:00 2001 From: kshitij12345 Date: Tue, 9 Jul 2019 02:48:25 +0530 Subject: [PATCH 049/813] fix comment (#15481) --- src/operator/tensor/elemwise_binary_op_extended.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operator/tensor/elemwise_binary_op_extended.cu b/src/operator/tensor/elemwise_binary_op_extended.cu index 585d379b0bef..0ae6ac966a2b 100644 --- a/src/operator/tensor/elemwise_binary_op_extended.cu +++ b/src/operator/tensor/elemwise_binary_op_extended.cu @@ -20,7 +20,7 @@ /*! * Copyright (c) 2016 by Contributors * \file elemwise_binary_op_extended.cu - * \brief GPU Implementation of unary function. + * \brief GPU Implementation of binary function. */ #include "./elemwise_unary_op.h" #include "./elemwise_binary_op.h" From e02c0a290eeb761d6c0690420ab420ac00c98f26 Mon Sep 17 00:00:00 2001 From: Tao Lv Date: Tue, 9 Jul 2019 10:07:25 +0800 Subject: [PATCH 050/813] Upgrade MKL-DNN submodule to v0.20 release (#15422) * update mkldnn to v0.20 release * update mkldnn format numbers * retrigger ci --- 3rdparty/mkldnn | 2 +- tests/cpp/operator/mkldnn_test.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/3rdparty/mkldnn b/3rdparty/mkldnn index 41bee20d7eb4..d89bf4babd7c 160000 --- a/3rdparty/mkldnn +++ b/3rdparty/mkldnn @@ -1 +1 @@ -Subproject commit 41bee20d7eb4a67feeeeb8d597b3598994eb1959 +Subproject commit d89bf4babd7cce7efa6613387dca79c123164084 diff --git a/tests/cpp/operator/mkldnn_test.cc b/tests/cpp/operator/mkldnn_test.cc index 3e7b62d8b33f..ab624e3a3c44 100644 --- a/tests/cpp/operator/mkldnn_test.cc +++ b/tests/cpp/operator/mkldnn_test.cc @@ -100,9 +100,9 @@ static void VerifyDefMem(const mkldnn::memory &mem) { TEST(MKLDNN_UTIL_FUNC, MemFormat) { // Check whether the number of format is correct. - CHECK_EQ(mkldnn_format_last, 145); + CHECK_EQ(mkldnn_format_last, 158); CHECK_EQ(mkldnn_nchw, 7); - CHECK_EQ(mkldnn_oihw, 16); + CHECK_EQ(mkldnn_oihw, 17); } static void VerifyMem(const mkldnn::memory &mem) { From 6b2b927871acaba1d7bd124437e94da661118d6d Mon Sep 17 00:00:00 2001 From: Robert Stone Date: Tue, 9 Jul 2019 17:38:04 -0700 Subject: [PATCH 051/813] [Perl] - simplify aliasing strategy (#15395) * alternate support for emulation of Python "import mxnet.someclass as sc" moving responsibility to the imported class instead of the importer. * simpler dispatch for constructor aliases to have fewer anonymous subroutines in stack traces on errors. --- perl-package/AI-MXNet/lib/AI/MXNet.pm | 116 +++++------------- .../AI-MXNet/lib/AI/MXNet/AutoGrad.pm | 19 +-- perl-package/AI-MXNet/lib/AI/MXNet/Base.pm | 16 +-- .../AI-MXNet/lib/AI/MXNet/Callback.pm | 1 + perl-package/AI-MXNet/lib/AI/MXNet/Context.pm | 8 +- perl-package/AI-MXNet/lib/AI/MXNet/Contrib.pm | 10 +- .../AI-MXNet/lib/AI/MXNet/Contrib/NDArray.pm | 1 + .../AI-MXNet/lib/AI/MXNet/Contrib/Symbol.pm | 1 + .../AI-MXNet/lib/AI/MXNet/CudaModule.pm | 5 +- perl-package/AI-MXNet/lib/AI/MXNet/Engine.pm | 3 +- perl-package/AI-MXNet/lib/AI/MXNet/Gluon.pm | 38 +----- .../AI-MXNet/lib/AI/MXNet/Gluon/Block.pm | 7 +- .../AI-MXNet/lib/AI/MXNet/Gluon/Data.pm | 4 +- .../lib/AI/MXNet/Gluon/Data/Vision.pm | 3 + .../AI-MXNet/lib/AI/MXNet/Gluon/Loss.pm | 3 +- .../AI-MXNet/lib/AI/MXNet/Gluon/NN.pm | 21 +--- .../lib/AI/MXNet/Gluon/NN/Activation.pm | 2 + .../lib/AI/MXNet/Gluon/NN/BasicLayers.pm | 4 +- .../lib/AI/MXNet/Gluon/NN/ConvLayers.pm | 4 +- .../AI-MXNet/lib/AI/MXNet/Gluon/Parameter.pm | 5 + .../AI-MXNet/lib/AI/MXNet/Gluon/RNN.pm | 21 +--- .../AI-MXNet/lib/AI/MXNet/Gluon/Trainer.pm | 3 + .../AI-MXNet/lib/AI/MXNet/Gluon/Utils.pm | 2 +- perl-package/AI-MXNet/lib/AI/MXNet/IO.pm | 1 + perl-package/AI-MXNet/lib/AI/MXNet/Image.pm | 1 + .../AI-MXNet/lib/AI/MXNet/Initializer.pm | 1 + perl-package/AI-MXNet/lib/AI/MXNet/KVStore.pm | 1 + perl-package/AI-MXNet/lib/AI/MXNet/LinAlg.pm | 1 + perl-package/AI-MXNet/lib/AI/MXNet/Metric.pm | 1 + perl-package/AI-MXNet/lib/AI/MXNet/Module.pm | 1 + perl-package/AI-MXNet/lib/AI/MXNet/Monitor.pm | 1 + perl-package/AI-MXNet/lib/AI/MXNet/NDArray.pm | 1 + perl-package/AI-MXNet/lib/AI/MXNet/NS.pm | 78 ++++++++++++ .../AI-MXNet/lib/AI/MXNet/Optimizer.pm | 1 + perl-package/AI-MXNet/lib/AI/MXNet/RNN.pm | 1 + perl-package/AI-MXNet/lib/AI/MXNet/Random.pm | 1 + .../AI-MXNet/lib/AI/MXNet/RecordIO.pm | 1 + perl-package/AI-MXNet/lib/AI/MXNet/Symbol.pm | 1 + .../AI-MXNet/lib/AI/MXNet/Symbol/AttrScope.pm | 12 +- .../lib/AI/MXNet/Symbol/NameManager.pm | 6 +- .../AI-MXNet/lib/AI/MXNet/TestUtils.pm | 9 +- .../AI-MXNet/lib/AI/MXNet/Visualization.pm | 1 + perl-package/AI-MXNet/t/AI-MXNet.t | 5 +- .../AI-MXNet/t/test_multi_device_exec.t | 4 +- 44 files changed, 210 insertions(+), 216 deletions(-) create mode 100644 perl-package/AI-MXNet/lib/AI/MXNet/NS.pm diff --git a/perl-package/AI-MXNet/lib/AI/MXNet.pm b/perl-package/AI-MXNet/lib/AI/MXNet.pm index ffc72f9513d3..b5665c88eddf 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet.pm @@ -19,105 +19,51 @@ package AI::MXNet; use v5.14.0; use strict; use warnings; +use AI::MXNet::NS 'global'; use AI::MXNet::Base; -use AI::MXNet::Callback; -use AI::MXNet::NDArray; -use AI::MXNet::Symbol; +use AI::MXNet::Callback 'callback'; +use AI::MXNet::NDArray qw(nd ndarray); +use AI::MXNet::Context 'context'; +use AI::MXNet::Symbol qw(sym symbol); use AI::MXNet::Executor; use AI::MXNet::Executor::Group; use AI::MXNet::CudaModule; -use AI::MXNet::Random; -use AI::MXNet::Initializer; -use AI::MXNet::Optimizer; -use AI::MXNet::KVStore; +use AI::MXNet::Random qw(rnd random); +use AI::MXNet::Initializer qw(init initializer); +use AI::MXNet::Optimizer qw(optimizer opt); +use AI::MXNet::KVStore 'kv'; use AI::MXNet::KVStoreServer; -use AI::MXNet::IO; -use AI::MXNet::Metric; +use AI::MXNet::IO 'io'; +use AI::MXNet::Metric 'metric'; use AI::MXNet::LRScheduler; -use AI::MXNet::Monitor; +use AI::MXNet::Monitor 'mon'; use AI::MXNet::Profiler; use AI::MXNet::Module::Base; -use AI::MXNet::Module; +use AI::MXNet::Module qw(mod module); use AI::MXNet::Module::Bucketing; -use AI::MXNet::RNN; -use AI::MXNet::Visualization; -use AI::MXNet::RecordIO; -use AI::MXNet::Image; -use AI::MXNet::Contrib; -use AI::MXNet::LinAlg; +use AI::MXNet::RNN 'rnn'; +use AI::MXNet::Visualization 'viz'; +use AI::MXNet::RecordIO 'recordio'; +use AI::MXNet::Image qw(img image); +use AI::MXNet::Contrib 'contrib'; +use AI::MXNet::LinAlg 'linalg'; use AI::MXNet::CachedOp; -use AI::MXNet::AutoGrad; -use AI::MXNet::Gluon; +use AI::MXNet::AutoGrad 'autograd'; +use AI::MXNet::Gluon 'gluon'; use AI::MXNet::NDArray::Sparse; use AI::MXNet::Symbol::Sparse; -use AI::MXNet::Engine; +use AI::MXNet::Engine 'engine'; our $VERSION = '1.4'; -sub import -{ - my ($class, $short_name) = @_; - if($short_name) - { - $short_name =~ s/[^\w:]//g; - if(length $short_name) - { - my $short_name_package =<<"EOP"; - package $short_name; - no warnings 'redefine'; - sub nd { 'AI::MXNet::NDArray' } - sub ndarray { 'AI::MXNet::NDArray' } - sub sym { 'AI::MXNet::Symbol' } - sub symbol { 'AI::MXNet::Symbol' } - sub init { 'AI::MXNet::Initializer' } - sub initializer { 'AI::MXNet::Initializer' } - sub optimizer { 'AI::MXNet::Optimizer' } - sub opt { 'AI::MXNet::Optimizer' } - sub rnd { 'AI::MXNet::Random' } - sub random { 'AI::MXNet::Random' } - sub Context { shift; AI::MXNet::Context->new(\@_) } - sub context { 'AI::MXNet::Context' } - sub cpu { AI::MXNet::Context->cpu(\$_[1]//0) } - sub cpu_pinned { AI::MXNet::Context->cpu_pinned(\$_[1]//0) } - sub gpu { AI::MXNet::Context->gpu(\$_[1]//0) } - sub kv { 'AI::MXNet::KVStore' } - sub recordio { 'AI::MXNet::RecordIO' } - sub io { 'AI::MXNet::IO' } - sub metric { 'AI::MXNet::Metric' } - sub mod { 'AI::MXNet::Module' } - sub module { 'AI::MXNet::Module' } - sub mon { 'AI::MXNet::Monitor' } - sub viz { 'AI::MXNet::Visualization' } - sub rnn { 'AI::MXNet::RNN' } - sub callback { 'AI::MXNet::Callback' } - sub img { 'AI::MXNet::Image' } - sub image { 'AI::MXNet::Image' } - sub contrib { 'AI::MXNet::Contrib' } - sub linalg { 'AI::MXNet::LinAlg' } - sub autograd { 'AI::MXNet::AutoGrad' } - sub engine { 'AI::MXNet::Engine' } - sub name { '$short_name' } - sub rtc { '$short_name' } - sub gluon { 'AI::MXNet::Gluon' } - sub CudaModule { shift; AI::MXNet::CudaModule->new(\@_) } - sub AttrScope { shift; AI::MXNet::Symbol::AttrScope->new(\@_) } - *AI::MXNet::Symbol::AttrScope::current = sub { \$${short_name}::AttrScope; }; - \$${short_name}::AttrScope = AI::MXNet::Symbol::AttrScope->new; - sub Prefix { AI::MXNet::Symbol::Prefix->new(prefix => \$_[1]) } - *AI::MXNet::Symbol::NameManager::current = sub { \$${short_name}::NameManager; }; - *AI::MXNet::Symbol::NameManager::set_current = sub { \$${short_name}::NameManager = \$_[1]; }; - \$${short_name}::NameManager = AI::MXNet::Symbol::NameManager->new; - *AI::MXNet::Context::current_ctx = sub { \$${short_name}::Context; }; - *AI::MXNet::Context::current_context = sub { \$${short_name}::Context; }; - *AI::MXNet::Context::set_current = sub { \$${short_name}::Context = \$_[1]; }; - \$${short_name}::Context = AI::MXNet::Context->new(device_type => 'cpu', device_id => 0); - package nd; - \@nd::ISA = ('AI::MXNet::NDArray'); - 1; -EOP - eval $short_name_package; - } - } -} +sub cpu { AI::MXNet::Context->cpu($_[1]//0) } +sub cpu_pinned { AI::MXNet::Context->cpu_pinned($_[1]//0) } +sub gpu { AI::MXNet::Context->gpu($_[1]//0) } +sub name { __PACKAGE__ } +sub rtc { __PACKAGE__ } +sub Prefix { AI::MXNet::Symbol::Prefix->new(prefix => $_[1]) } +our $AttrScope = AI::MXNet::Symbol::AttrScope->new; +our $NameManager = AI::MXNet::Symbol::NameManager->new; +our $Context = AI::MXNet::Context->new(device_type => 'cpu', device_id => 0); 1; __END__ diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/AutoGrad.pm b/perl-package/AI-MXNet/lib/AI/MXNet/AutoGrad.pm index c1e5f06e12bd..d6272b5a1def 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/AutoGrad.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/AutoGrad.pm @@ -18,29 +18,12 @@ package AI::MXNet::AutoGrad; use strict; use warnings; +use AI::MXNet::NS 'global'; use AI::MXNet::Base; use AI::MXNet::Function::Parameters; use Scalar::Util qw(blessed); use Carp qw(confess); -sub import -{ - my ($class, $short_name) = @_; - if($short_name) - { - $short_name =~ s/[^\w:]//g; - if(length $short_name) - { - my $short_name_package =<<"EOP"; - package $short_name; - use parent 'AI::MXNet::AutoGrad'; - 1; -EOP - eval $short_name_package; - } - } -} - =head1 NAME AI::MXNet::AutoGrad - Autograd for NDArray. diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Base.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Base.pm index 3f6bd8341325..13513c577175 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Base.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Base.pm @@ -32,14 +32,14 @@ use base qw(Exporter); use List::Util qw(shuffle); use Data::Dumper; -@AI::MXNet::Base::EXPORT = qw(product enumerate assert zip check_call build_param_doc - pdl cat dog svd bisect_left pdl_shuffle as_array ascsr rand_sparse - DTYPE_STR_TO_MX DTYPE_MX_TO_STR DTYPE_MX_TO_PDL - DTYPE_PDL_TO_MX DTYPE_MX_TO_PERL GRAD_REQ_MAP - STORAGE_TYPE_UNDEFINED STORAGE_TYPE_DEFAULT - STORAGE_TYPE_ROW_SPARSE STORAGE_TYPE_CSR - STORAGE_TYPE_STR_TO_ID STORAGE_TYPE_ID_TO_STR STORAGE_AUX_TYPES); -@AI::MXNet::Base::EXPORT_OK = qw(pzeros pceil pones digitize hash array_index range); +our @EXPORT = qw(product enumerate assert zip check_call build_param_doc + pdl cat dog svd bisect_left pdl_shuffle as_array ascsr rand_sparse + DTYPE_STR_TO_MX DTYPE_MX_TO_STR DTYPE_MX_TO_PDL + DTYPE_PDL_TO_MX DTYPE_MX_TO_PERL GRAD_REQ_MAP + STORAGE_TYPE_UNDEFINED STORAGE_TYPE_DEFAULT + STORAGE_TYPE_ROW_SPARSE STORAGE_TYPE_CSR + STORAGE_TYPE_STR_TO_ID STORAGE_TYPE_ID_TO_STR STORAGE_AUX_TYPES); +our @EXPORT_OK = qw(pzeros pceil pones digitize hash array_index range); use constant DTYPE_STR_TO_MX => { float32 => 0, diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Callback.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Callback.pm index b2a0b2948154..72ea1bd4502c 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Callback.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Callback.pm @@ -19,6 +19,7 @@ package AI::MXNet::Callback; use strict; use warnings; use List::Util qw/max/; +use AI::MXNet::NS; use AI::MXNet::Function::Parameters; use Mouse; use overload "&{}" => sub { my $self = shift; sub { $self->call(@_) } }; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Context.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Context.pm index 7ae99be7b99e..2cca47f9ab4d 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Context.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Context.pm @@ -19,6 +19,7 @@ package AI::MXNet::Context; use strict; use warnings; use Mouse; +use AI::MXNet::NS; use AI::MXNet::Base; use AI::MXNet::Types; use AI::MXNet::Function::Parameters; @@ -216,12 +217,12 @@ method gpu_memory_info($device_id=0) method current_ctx() { - return $AI::MXNet::current_ctx; + return $AI::MXNet::Context; } method set_current(AI::MXNet::Context $current) { - $AI::MXNet::current_ctx = $current; + $AI::MXNet::Context = $current; } *current_context = \¤t_ctx; @@ -234,5 +235,6 @@ method deepcopy() ); } -$AI::MXNet::current_ctx = __PACKAGE__->new(device_type => 'cpu', device_id => 0); +__PACKAGE__->AI::MXNet::NS::register('AI::MXNet'); +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib.pm index c470acab60e7..f9a99f706302 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib.pm @@ -18,8 +18,9 @@ package AI::MXNet::Contrib; use strict; use warnings; -use AI::MXNet::Contrib::Symbol; -use AI::MXNet::Contrib::NDArray; +use AI::MXNet::NS; +use AI::MXNet::Contrib::Symbol qw(sym symbol); +use AI::MXNet::Contrib::NDArray qw(nd ndarray); =head1 NAME @@ -47,9 +48,4 @@ use AI::MXNet::Contrib::NDArray; } =cut -sub sym { 'AI::MXNet::Contrib::Symbol' } -sub symbol { 'AI::MXNet::Contrib::Symbol' } -sub nd { 'AI::MXNet::Contrib::NDArray' } -sub ndarray { 'AI::MXNet::Contrib::NDArray' } - 1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/NDArray.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/NDArray.pm index 574ecc443f79..83e7cbabce5a 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/NDArray.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/NDArray.pm @@ -18,6 +18,7 @@ package AI::MXNet::Contrib::NDArray; use strict; use warnings; +use AI::MXNet::NS; use parent 'AI::MXNet::AutoLoad'; sub config { ('contrib', 'AI::MXNet::NDArray') } diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/Symbol.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/Symbol.pm index d5a041a085fe..03f4b90e3373 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/Symbol.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Contrib/Symbol.pm @@ -18,6 +18,7 @@ package AI::MXNet::Contrib::Symbol; use strict; use warnings; +use AI::MXNet::NS; use parent 'AI::MXNet::AutoLoad'; sub config { ('contrib', 'AI::MXNet::Symbol') } diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/CudaModule.pm b/perl-package/AI-MXNet/lib/AI/MXNet/CudaModule.pm index b3272fe8b048..67e6b60a0190 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/CudaModule.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/CudaModule.pm @@ -18,6 +18,7 @@ package AI::MXNet::CudaModule; use strict; use warnings; +use AI::MXNet::NS; use AI::MXNet::Base; use Mouse; use AI::MXNet::Function::Parameters; @@ -195,6 +196,8 @@ method get_kernel(Str $name, Str $signature) return AI::MXNet::CudaKernel->new($handle, $name, \@is_ndarray, \@dtypes); } +__PACKAGE__->AI::MXNet::NS::register('AI::MXNet'); + package AI::MXNet::CudaKernel; use Mouse; use AI::MXNet::Base; @@ -296,4 +299,4 @@ method launch( ); } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Engine.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Engine.pm index 1d73e5584268..e74be0230d7b 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Engine.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Engine.pm @@ -20,6 +20,7 @@ use strict; use warnings; use AI::MXNet::Function::Parameters; use AI::MXNet::Base; +use AI::MXNet::NS; =head1 NAME @@ -100,4 +101,4 @@ method bulk(Int $size, CodeRef $sub) Carp::confess($err) if $err; } -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon.pm index fde2f6ac5a63..657be74c5a6d 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon.pm @@ -18,42 +18,14 @@ package AI::MXNet::Gluon; use strict; use warnings; -use AI::MXNet::Gluon::Loss; +use AI::MXNet::NS 'global'; +use AI::MXNet::Gluon::Loss 'loss'; use AI::MXNet::Gluon::Trainer; use AI::MXNet::Gluon::Utils; -use AI::MXNet::Gluon::Data; -use AI::MXNet::Gluon::NN; -use AI::MXNet::Gluon::RNN; - -sub import -{ - my ($class, $short_name) = @_; - if($short_name) - { - $short_name =~ s/[^\w:]//g; - if(length $short_name) - { - my $short_name_package =<<"EOP"; - package $short_name; - no warnings 'redefine'; - sub data { 'AI::MXNet::Gluon::Data' } - sub nn { 'AI::MXNet::Gluon::NN_' } - sub rnn { 'AI::MXNet::Gluon::RNN_' } - sub loss { 'AI::MXNet::Gluon::Loss_' } - sub utils { 'AI::MXNet::Gluon::Utils' } - sub model_zoo { require AI::MXNet::Gluon::ModelZoo; 'AI::MXNet::Gluon::ModelZoo' } - sub Trainer { shift; AI::MXNet::Gluon::Trainer->new(\@_); } - sub Parameter { shift; AI::MXNet::Gluon::Parameter->new(\@_); } - sub ParameterDict { shift; AI::MXNet::Gluon::ParameterDict->new(\@_); } - \@${short_name}::ISA = ('AI::MXNet::Gluon_'); - 1; -EOP - eval $short_name_package; - } - } -} +use AI::MXNet::Gluon::Data 'data'; +use AI::MXNet::Gluon::NN 'nn'; +use AI::MXNet::Gluon::RNN 'rnn'; -sub data { 'AI::MXNet::Gluon::Data' } sub utils { 'AI::MXNet::Gluon::Utils' } sub model_zoo { require AI::MXNet::Gluon::ModelZoo; 'AI::MXNet::Gluon::ModelZoo' } diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Block.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Block.pm index 599c3c3bef6e..b58704b5d8a1 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Block.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Block.pm @@ -731,8 +731,13 @@ method forward(@args) method register(Str $container) { my $sub_name = $self->_class_name; + my $dest = $self->can('new'); + my $func = sub { + splice @_, 0, 1, $self; + goto $dest; + }; no strict 'refs'; - *{$container.'_::'.$sub_name} = sub { shift; $self->new(@_) }; + *{"$container\::$sub_name"} = $func; } =head2 summary diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Data.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Data.pm index e2287c23dafe..95612d9d8cf3 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Data.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Data.pm @@ -18,11 +18,11 @@ package AI::MXNet::Gluon::Data; use strict; use warnings; +use AI::MXNet::NS; use AI::MXNet::Gluon::Data::Set; use AI::MXNet::Gluon::Data::Sampler; use AI::MXNet::Gluon::Data::Loader; -use AI::MXNet::Gluon::Data::Vision; -sub vision { 'AI::MXNet::Gluon::Data::Vision' } +use AI::MXNet::Gluon::Data::Vision 'vision'; 1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Data/Vision.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Data/Vision.pm index 5711af350e5f..1c9d551b3d1e 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Data/Vision.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Data/Vision.pm @@ -15,6 +15,9 @@ # specific language governing permissions and limitations # under the License. +package AI::MXNet::Gluon::Data::Vision; +use AI::MXNet::NS; + package AI::MXNet::Gluon::Data::Vision::DownloadedDataSet; use strict; use warnings; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Loss.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Loss.pm index 3eb62eb5a2ef..a5938595df6f 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Loss.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Loss.pm @@ -18,6 +18,7 @@ use strict; use warnings; package AI::MXNet::Gluon::Loss; +use AI::MXNet::NS; use AI::MXNet::Gluon::Block; use AI::MXNet::Function::Parameters; @@ -995,4 +996,4 @@ method _cosine_similarity($F, $x, $y, $axis=-1) __PACKAGE__->register('AI::MXNet::Gluon::Loss'); -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN.pm index 673ee5de3686..c8b4c7f4ef60 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN.pm @@ -18,27 +18,10 @@ package AI::MXNet::Gluon::NN; use strict; use warnings; +use AI::MXNet::NS 'global'; use AI::MXNet::Gluon::Block; use AI::MXNet::Gluon::NN::Activation; use AI::MXNet::Gluon::NN::BasicLayers; use AI::MXNet::Gluon::NN::ConvLayers; -sub import -{ - my ($class, $short_name) = @_; - if($short_name) - { - $short_name =~ s/[^\w:]//g; - if(length $short_name) - { - my $short_name_package =<<"EOP"; - package $short_name; - \@${short_name}::ISA = ('AI::MXNet::Gluon::NN_'); - 1; -EOP - eval $short_name_package; - } - } -} - -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN/Activation.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN/Activation.pm index 1d6342f4955b..63fd80d705b8 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN/Activation.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN/Activation.pm @@ -240,3 +240,5 @@ method hybrid_forward(GluonClass $F, GluonInput $x) } __PACKAGE__->register('AI::MXNet::Gluon::NN'); + +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN/BasicLayers.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN/BasicLayers.pm index 954179095cdf..6c554bfd0626 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN/BasicLayers.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN/BasicLayers.pm @@ -272,7 +272,7 @@ sub BUILD if(defined $self->activation) { $self->act( - AI::MXNet::Gluon::NN::Activation->new( + AI::MXNet::Gluon::NN->Activation( activation => $self->activation, prefix => $self->activation.'_' ) @@ -920,4 +920,4 @@ use overload '""' => sub { __PACKAGE__->register('AI::MXNet::Gluon::NN'); -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN/ConvLayers.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN/ConvLayers.pm index a4bb89b2b2a2..0e11714d4a41 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN/ConvLayers.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/NN/ConvLayers.pm @@ -156,7 +156,7 @@ sub BUILD if(defined $self->activation) { $self->act( - AI::MXNet::Gluon::NN::Activation->new( + AI::MXNet::Gluon::NN->Activation( activation => $self->activation, prefix => $self->activation.'_' ) @@ -1415,4 +1415,4 @@ method hybrid_forward(GluonClass $F, GluonInput $x) __PACKAGE__->register('AI::MXNet::Gluon::NN'); -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Parameter.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Parameter.pm index 89cd0cac6229..a914c1ff68ca 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Parameter.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Parameter.pm @@ -19,6 +19,7 @@ use strict; use warnings; use Hash::Ordered; package AI::MXNet::Gluon::Parameter; +use AI::MXNet::NS; use AI::MXNet::Function::Parameters; =head1 NAME @@ -799,6 +800,8 @@ method cast(Dtype $dtype) }); } +__PACKAGE__->AI::MXNet::NS::register('AI::MXNet::Gluon'); + package AI::MXNet::Gluon::Constant; use strict; use warnings; @@ -1323,4 +1326,6 @@ method load( } } +__PACKAGE__->AI::MXNet::NS::register('AI::MXNet::Gluon'); + 1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/RNN.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/RNN.pm index cdd9468f228c..bf5736ccbb9e 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/RNN.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/RNN.pm @@ -18,25 +18,8 @@ package AI::MXNet::Gluon::RNN; use strict; use warnings; +use AI::MXNet::NS 'global'; use AI::MXNet::Gluon::RNN::Layer; use AI::MXNet::Gluon::RNN::Cell; -sub import -{ - my ($class, $short_name) = @_; - if($short_name) - { - $short_name =~ s/[^\w:]//g; - if(length $short_name) - { - my $short_name_package =<<"EOP"; - package $short_name; - \@${short_name}::ISA = ('AI::MXNet::Gluon::RNN_'); - 1; -EOP - eval $short_name_package; - } - } -} - -1; \ No newline at end of file +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Trainer.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Trainer.pm index 6117777eed8f..12cd7c2391c3 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Trainer.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Trainer.pm @@ -18,6 +18,7 @@ use strict; use warnings; package AI::MXNet::Gluon::Trainer; +use AI::MXNet::NS; use AI::MXNet::Base; use AI::MXNet::Function::Parameters; use IO::File; @@ -554,4 +555,6 @@ method load_states(Str $fname) } } +__PACKAGE__->AI::MXNet::NS::register('AI::MXNet::Gluon'); + 1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Utils.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Utils.pm index 497356da89a8..66d8acc6c5b1 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Utils.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon/Utils.pm @@ -25,7 +25,7 @@ use File::Path qw(make_path); use HTTP::Tiny; use Exporter; use base qw(Exporter); -@AI::MXNet::Gluon::Utils::EXPORT_OK = qw(download check_sha1); +our @EXPORT_OK = qw(download check_sha1); =head1 NAME diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/IO.pm b/perl-package/AI-MXNet/lib/AI/MXNet/IO.pm index 19e7cfdb8fe3..5dd3955b2438 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/IO.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/IO.pm @@ -18,6 +18,7 @@ package AI::MXNet::IO; use strict; use warnings; +use AI::MXNet::NS; use AI::MXNet::Base; use AI::MXNet::Function::Parameters; use Scalar::Util qw/blessed/; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Image.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Image.pm index 9c7fa120f343..ab4e7964918b 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Image.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Image.pm @@ -19,6 +19,7 @@ package AI::MXNet::Image; use strict; use warnings; use Scalar::Util qw(blessed); +use AI::MXNet::NS; use AI::MXNet::Base; use AI::MXNet::Function::Parameters; use AI::MXNet::Image::NDArray; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Initializer.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Initializer.pm index 75c8b1e3dad1..089731f16eee 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Initializer.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Initializer.pm @@ -45,6 +45,7 @@ around BUILDARGS => sub { # Base class for Initializers package AI::MXNet::Initializer; use Mouse; +use AI::MXNet::NS; use AI::MXNet::Base qw(:DEFAULT pzeros pceil); use AI::MXNet::NDArray; use JSON::PP; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/KVStore.pm b/perl-package/AI-MXNet/lib/AI/MXNet/KVStore.pm index 15aad76c7b4a..259ad0df5191 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/KVStore.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/KVStore.pm @@ -18,6 +18,7 @@ package AI::MXNet::KVStore; use strict; use warnings; +use AI::MXNet::NS; use AI::MXNet::Base; use AI::MXNet::NDArray; use AI::MXNet::Optimizer; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/LinAlg.pm b/perl-package/AI-MXNet/lib/AI/MXNet/LinAlg.pm index be1262fb6a87..3fef1b873cab 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/LinAlg.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/LinAlg.pm @@ -18,6 +18,7 @@ package AI::MXNet::LinAlg; use strict; use warnings; +use AI::MXNet::NS; use AI::MXNet::LinAlg::Symbol; use AI::MXNet::LinAlg::NDArray; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Metric.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Metric.pm index 0941316960a9..4c2baa43d1fd 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Metric.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Metric.pm @@ -18,6 +18,7 @@ package AI::MXNet::Metric; use strict; use warnings; +use AI::MXNet::NS; use AI::MXNet::Function::Parameters; use Scalar::Util qw/blessed/; use JSON::PP; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Module.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Module.pm index 38c2ae645969..097f038ed11a 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Module.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Module.pm @@ -32,6 +32,7 @@ has [qw/_param_names _fixed_param_names ] => (is => 'rw', init_arg => undef); package AI::MXNet::Module; +use AI::MXNet::NS; use AI::MXNet::Base; use AI::MXNet::Function::Parameters; use List::Util qw(max); diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Monitor.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Monitor.pm index 76fdfd24e7e8..9e4a96849a00 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Monitor.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Monitor.pm @@ -17,6 +17,7 @@ package AI::MXNet::Monitor; use Mouse; +use AI::MXNet::NS; use AI::MXNet::Function::Parameters; use AI::MXNet::Base; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/NDArray.pm b/perl-package/AI-MXNet/lib/AI/MXNet/NDArray.pm index f466aaa11a3d..f75cc84b2a8f 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/NDArray.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/NDArray.pm @@ -59,6 +59,7 @@ package AI::MXNet::NDArray; use strict; use warnings; +use AI::MXNet::NS; use AI::MXNet::Base; use AI::MXNet::NDArray::Slice; use AI::MXNet::Context; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/NS.pm b/perl-package/AI-MXNet/lib/AI/MXNet/NS.pm new file mode 100644 index 000000000000..03cd5f195182 --- /dev/null +++ b/perl-package/AI-MXNet/lib/AI/MXNet/NS.pm @@ -0,0 +1,78 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +package AI::MXNet::NS; +# this class is similar to Exporter, in that it will add an "import" +# method to the calling package. It is to allow a package to emulate +# the python "import mxnet as mx" style aliasing as "use AI::MXNet 'mx'" +use strict; +use warnings; + +sub _sym : lvalue +{ + my ($pkg, $name) = @_; + no strict 'refs'; + *{"$pkg\::$name"}; +} + +sub import +{ + my (undef, $opt) = @_; + my $class = caller(); + my $func = sub { $class }; + _sym($class, 'import') = sub { + my (undef, @names) = @_; + @names = map { s/[^\w:]//sgr } @names; + my $target = caller(); + + _sym($names[0], '') = _sym($class, '') if + @names == 1 and $opt and $opt eq 'global'; + + _sym($target, $_) = $func for @names; + }; +} + +my $autoload_template = q( + sub AUTOLOAD + { + our ($AUTOLOAD, %AUTOLOAD); + my $name = $AUTOLOAD =~ s/.*:://sr; + my $func = $AUTOLOAD{$name}; + Carp::carp(qq(Can't locate object method "$name" via package "${\ __PACKAGE__ }")) + unless $func; + goto $func; + } +); + +# using AUTOLOAD here allows for the addition of an AI::MXNet::SomeClass +# class to coexist with an AI::MXNet->SomeClass() shorthand constructor. +sub register +{ + my ($class, $target) = @_; + my $name = $class =~ s/.*:://sr; + my $dest = $class->can('new'); + ${_sym($target, 'AUTOLOAD')}{$name} = sub { + splice @_, 0, 1, $class; + goto $dest; + }; + return if $target->can('AUTOLOAD'); + eval sprintf 'package %s { %s }', $target, $autoload_template; + die if $@; + return; +} + +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Optimizer.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Optimizer.pm index ad0e45503220..7e78cd384220 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Optimizer.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Optimizer.pm @@ -18,6 +18,7 @@ package AI::MXNet::Optimizer; use strict; use warnings; +use AI::MXNet::NS; use AI::MXNet::Base; use AI::MXNet::NDArray; use AI::MXNet::Random; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/RNN.pm b/perl-package/AI-MXNet/lib/AI/MXNet/RNN.pm index 07e72a755723..6ba9bf431e08 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/RNN.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/RNN.pm @@ -18,6 +18,7 @@ package AI::MXNet::RNN; use strict; use warnings; +use AI::MXNet::NS; use AI::MXNet::Function::Parameters; use AI::MXNet::RNN::IO; use AI::MXNet::RNN::Cell; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Random.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Random.pm index 7a99b1dc7ea9..ddc0edca1ba8 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Random.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Random.pm @@ -19,6 +19,7 @@ package AI::MXNet::Random; use strict; use warnings; use Scalar::Util qw/blessed/; +use AI::MXNet::NS; use AI::MXNet::Base; use AI::MXNet::NDArray::Base; use AI::MXNet::Function::Parameters; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/RecordIO.pm b/perl-package/AI-MXNet/lib/AI/MXNet/RecordIO.pm index f22e2ce92789..5637260a6b08 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/RecordIO.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/RecordIO.pm @@ -18,6 +18,7 @@ package AI::MXNet::RecordIO; use strict; use warnings; +use AI::MXNet::NS; use AI::MXNet::Function::Parameters; use AI::MXNet::Types; use AI::MXNet::Base; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol.pm index e4953f17031a..32e1105210d5 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol.pm @@ -24,6 +24,7 @@ package AI::MXNet::Symbol; use strict; use warnings; +use AI::MXNet::NS; use AI::MXNet::Base; use AI::MXNet::Symbol::Base; use AI::MXNet::Symbol::Random; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/AttrScope.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/AttrScope.pm index c728ed1b6ce8..549939f006aa 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/AttrScope.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/AttrScope.pm @@ -19,6 +19,7 @@ package AI::MXNet::Symbol::AttrScope; use strict; use warnings; use Mouse; +use AI::MXNet::NS; use AI::MXNet::Function::Parameters; around BUILDARGS => sub { my $orig = shift; @@ -58,7 +59,12 @@ has 'attr' => ( method current() { - $AI::MXNet::curr_attr_scope; + $AI::MXNet::AttrScope; +} + +method set_current(AI::MXNet::Symbol::AttrScope $new) +{ + $AI::MXNet::AttrScope = $new; } =head2 get @@ -83,4 +89,6 @@ method get(Maybe[HashRef[Str]] $attr=) return bless (\%ret, 'AI::MXNet::Util::Printable'); } -$AI::MXNet::curr_attr_scope = __PACKAGE__->new; +__PACKAGE__->AI::MXNet::NS::register('AI::MXNet'); + +1; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/NameManager.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/NameManager.pm index 0126655186fd..2238a4366789 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/NameManager.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Symbol/NameManager.pm @@ -79,16 +79,14 @@ method get(Maybe[Str] $name, Str $hint) method current() { - $AI::MXNet::Symbol::NameManager; + $AI::MXNet::NameManager; } method set_current(AI::MXNet::Symbol::NameManager $new) { - $AI::MXNet::Symbol::NameManager = $new; + $AI::MXNet::NameManager = $new; } -$AI::MXNet::Symbol::NameManager = __PACKAGE__->new; - package AI::MXNet::Symbol::Prefix; use Mouse; diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/TestUtils.pm b/perl-package/AI-MXNet/lib/AI/MXNet/TestUtils.pm index c04b79c5e4d0..3131692fc981 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/TestUtils.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/TestUtils.pm @@ -18,6 +18,7 @@ package AI::MXNet::TestUtils; use strict; use warnings; +use AI::MXNet 'mx'; use PDL; use Carp qw(confess); use Scalar::Util qw(blessed); @@ -26,10 +27,10 @@ use AI::MXNet::Function::Parameters; use AI::MXNet::Base; use Exporter; use base qw(Exporter); -@AI::MXNet::TestUtils::EXPORT_OK = qw(same reldiff almost_equal GetMNIST_ubyte - GetCifar10 pdl_maximum pdl_minimum mlp2 conv dies_ok - check_consistency zip assert enumerate same_array dies_like allclose rand_shape_2d - rand_shape_3d rand_sparse_ndarray random_arrays rand_ndarray randint pdl); +our @EXPORT_OK = qw(same reldiff almost_equal GetMNIST_ubyte + GetCifar10 pdl_maximum pdl_minimum mlp2 conv dies_ok + check_consistency zip assert enumerate same_array dies_like allclose rand_shape_2d + rand_shape_3d rand_sparse_ndarray random_arrays rand_ndarray randint pdl); use constant default_numerical_threshold => 1e-6; =head1 NAME diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Visualization.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Visualization.pm index 1574ea58307f..90ec1da4e289 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Visualization.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Visualization.pm @@ -18,6 +18,7 @@ package AI::MXNet::Visualization; use strict; use warnings; +use AI::MXNet::NS; use AI::MXNet::Base; use AI::MXNet::Function::Parameters; use JSON::PP; diff --git a/perl-package/AI-MXNet/t/AI-MXNet.t b/perl-package/AI-MXNet/t/AI-MXNet.t index d876fe3ee12b..ad5a0266e06f 100644 --- a/perl-package/AI-MXNet/t/AI-MXNet.t +++ b/perl-package/AI-MXNet/t/AI-MXNet.t @@ -17,5 +17,8 @@ use strict; use warnings; -use Test::More tests => 1; +use Test::More tests => 3; BEGIN { use_ok('AI::MXNet') }; + +isa_ok(AI::MXNet->Context(), 'AI::MXNet::Context'); +isa_ok(AI::MXNet::Context->new(), 'AI::MXNet::Context'); diff --git a/perl-package/AI-MXNet/t/test_multi_device_exec.t b/perl-package/AI-MXNet/t/test_multi_device_exec.t index 6a33a2375b7d..1b37e6ee9981 100644 --- a/perl-package/AI-MXNet/t/test_multi_device_exec.t +++ b/perl-package/AI-MXNet/t/test_multi_device_exec.t @@ -62,11 +62,11 @@ sub test_ctx_group my ($arr, $name) = @$_; if(exists $set_stage1{ $name }) { - ok($arr->context == $group2ctx->{stage1}); + cmp_ok($arr->context, '==', $group2ctx->{stage1}); } else { - ok($arr->context == $group2ctx->{stage2}); + cmp_ok($arr->context, '==', $group2ctx->{stage2}); } } } From d82c89a9df74ba7c8f0e42da187ceb7c62bcb355 Mon Sep 17 00:00:00 2001 From: kshitij12345 Date: Wed, 10 Jul 2019 07:58:04 +0530 Subject: [PATCH 052/813] Opperf: Support Python<3.6 (#15487) * support python<3.6 * fix: use kwargs Co-Authored-By: Lin Yuan --- benchmark/opperf/opperf.py | 7 ++++--- benchmark/opperf/utils/benchmark_utils.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/benchmark/opperf/opperf.py b/benchmark/opperf/opperf.py index 68b404185bf8..a73db4fdae89 100755 --- a/benchmark/opperf/opperf.py +++ b/benchmark/opperf/opperf.py @@ -125,8 +125,9 @@ def main(): 'output file.') args = parser.parse_args() - logging.info(f"Running MXNet operator benchmarks with the following options: {args}") - assert not os.path.isfile(args.output_file), f"Output file {args.output_file} already exists." + logging.info("Running MXNet operator benchmarks with the following options: {args}".format(args=args)) + assert not os.path.isfile(args.output_file),\ + "Output file {output_file} already exists.".format(output_file=args.output_file) # 2. RUN BENCHMARKS ctx = _parse_mxnet_context(args.ctx) @@ -140,7 +141,7 @@ def main(): # 4. Generate list of MXNet operators not covered in benchmarks ops_not_covered = get_operators_with_no_benchmark(final_benchmark_results.keys()) for idx, op in enumerate(ops_not_covered): - print(f"{idx}. {op}") + print("{idx}. {op}".format(idx=idx, op=op)) return 0 diff --git a/benchmark/opperf/utils/benchmark_utils.py b/benchmark/opperf/utils/benchmark_utils.py index dc4890b3df0f..adf5d533ff52 100644 --- a/benchmark/opperf/utils/benchmark_utils.py +++ b/benchmark/opperf/utils/benchmark_utils.py @@ -55,14 +55,14 @@ def _run_nd_operator_performance_test(op, inputs, run_backward, warmup, runs, kw # Run Benchmarks op_benchmark_result = {op.__name__: []} - logging.info(f"Begin Benchmark - {op.__name__}") + logging.info("Begin Benchmark - {name}".format(name=op.__name__)) for idx, kwargs in enumerate(kwargs_list): _, profiler_output = benchmark_helper_func(op, runs, **kwargs) # Add inputs used for profiling this operator into result profiler_output["inputs"] = inputs[idx] op_benchmark_result[op.__name__].append(profiler_output) - logging.info(f"Complete Benchmark - {op.__name__}") + logging.info("Complete Benchmark - {name}".format(name=op.__name__)) return op_benchmark_result From 7d4d1bc26d52cd87eb97536ff154c7f127b68a55 Mon Sep 17 00:00:00 2001 From: Konrad Heidler Date: Wed, 10 Jul 2019 04:32:39 +0200 Subject: [PATCH 053/813] Two fixes for info_gan.md example Code (#15323) * Two fixes for info_gan.md example Code * retrigger CI --- docs/tutorials/gluon/info_gan.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/gluon/info_gan.md b/docs/tutorials/gluon/info_gan.md index 91adf6c75108..c35df69fb6d3 100644 --- a/docs/tutorials/gluon/info_gan.md +++ b/docs/tutorials/gluon/info_gan.md @@ -339,9 +339,11 @@ with SummaryWriter(logdir='./logs/') as sw: fake_image = generator(g_input) sw.add_scalar(tag='Loss_D', value={'test':d_error_epoch.asscalar()/count}, global_step=counter) - sw.add_scalar(tag='Loss_G', value={'test':d_error_epoch.asscalar()/count}, global_step=counter) + sw.add_scalar(tag='Loss_G', value={'test':g_error_epoch.asscalar()/count}, global_step=counter) sw.add_image(tag='data_image', image=((fake_image[0]+ 1.0) * 127.5).astype(np.uint8) , global_step=counter) sw.flush() + + counter += 1 discriminator.save_parameters("infogan_d_latest.params") generator.save_parameters("infogan_g_latest.params") From 6191dd7083cac7574f8172a4a062bb9408cc7621 Mon Sep 17 00:00:00 2001 From: Lanking Date: Wed, 10 Jul 2019 15:22:07 -0700 Subject: [PATCH 054/813] fix the bug on Scala Sparse (#15500) * fix the bug on sparse tensor * update jenkins --- ci/docker/runtime_functions.sh | 16 ++++++++-------- ci/jenkins/Jenkins_steps.groovy | 4 ++-- .../main/scala/org/apache/mxnet/NDArray.scala | 4 +++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index 0fef2a049125..424ab2cf7e92 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -952,13 +952,6 @@ unittest_ubuntu_python3_quantization_gpu() { nosetests-3.4 $NOSE_COVERAGE_ARGUMENTS $NOSE_TIMER_ARGUMENTS --with-xunit --xunit-file nosetests_quantization_gpu.xml --verbose tests/python/quantization_gpu } -unittest_ubuntu_cpu_scala() { - set -ex - scala_prepare - cd scala-package - mvn -B integration-test -} - unittest_centos7_cpu_scala() { set -ex cd /work/mxnet @@ -1156,12 +1149,19 @@ integrationtest_ubuntu_cpu_dist_kvstore() { ../../tools/launch.py -n 3 --launcher local python test_server_profiling.py } +integrationtest_ubuntu_cpu_scala() { + set -ex + scala_prepare + cd scala-package + mvn -B verify -DskipTests=false +} + integrationtest_ubuntu_gpu_scala() { set -ex scala_prepare cd scala-package export SCALA_TEST_ON_GPU=1 - mvn -B integration-test -DskipTests=false + mvn -B verify -DskipTests=false } integrationtest_ubuntu_gpu_dist_kvstore() { diff --git a/ci/jenkins/Jenkins_steps.groovy b/ci/jenkins/Jenkins_steps.groovy index 668d2f7c7dca..4eb84b64fa91 100644 --- a/ci/jenkins/Jenkins_steps.groovy +++ b/ci/jenkins/Jenkins_steps.groovy @@ -941,7 +941,7 @@ def test_unix_scala_cpu() { ws('workspace/ut-scala-cpu') { timeout(time: max_time, unit: 'MINUTES') { utils.unpack_and_init('cpu', mx_lib, true) - utils.docker_run('ubuntu_cpu', 'unittest_ubuntu_cpu_scala', false) + utils.docker_run('ubuntu_cpu', 'integrationtest_ubuntu_cpu_scala', false) utils.publish_test_coverage() } } @@ -955,7 +955,7 @@ def test_unix_scala_mkldnn_cpu(){ ws('workspace/ut-scala-mkldnn-cpu') { timeout(time: max_time, unit: 'MINUTES') { utils.unpack_and_init('mkldnn_cpu', mx_mkldnn_lib, true) - utils.docker_run('ubuntu_cpu', 'unittest_ubuntu_cpu_scala', false) + utils.docker_run('ubuntu_cpu', 'integrationtest_ubuntu_cpu_scala', false) utils.publish_test_coverage() } } diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala b/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala index 1b7b31b32cce..717120bcf984 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala @@ -956,7 +956,9 @@ class NDArray private[mxnet](private[mxnet] val handle: NDArrayHandle, DType(mxDtype.value) } - val sparseFormat: SparseFormat = { + // This is a optimization on the SparseFormat checking + // TODO: In some cases, the checking on Sparse is invalid (-1) + lazy val sparseFormat: SparseFormat = { val mxSF = new RefInt checkCall(_LIB.mxNDArrayGetStorageType(handle, mxSF)) SparseFormat(mxSF.value) From 7a83883ec3aa2fe8b84dcf26f5f96ee83b4592e2 Mon Sep 17 00:00:00 2001 From: Anirudh Subramanian Date: Wed, 10 Jul 2019 20:15:29 -0700 Subject: [PATCH 055/813] Improve docs for AMP (#15455) --- docs/tutorials/amp/amp_tutorial.md | 49 ++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/amp/amp_tutorial.md b/docs/tutorials/amp/amp_tutorial.md index 31cd23b19ae7..b75ac97b9cc8 100644 --- a/docs/tutorials/amp/amp_tutorial.md +++ b/docs/tutorials/amp/amp_tutorial.md @@ -17,7 +17,7 @@ # Using AMP (Automatic Mixed Precision) in MXNet -Training Deep Learning networks is a very computationally intensive task. Novel model architectures tend to have increasing number of layers and parameters, which slows down training. Fortunately, new generations of training hardware as well as software optimizations, make it a feasible task. +Training Deep Learning networks is a very computationally intensive task. Novel model architectures tend to have increasing number of layers and parameters, which slows down training. Fortunately, new generations of training hardware as well as software optimizations, make it a feasible task. However, where most of the (both hardware and software) optimization opportunities exists is in exploiting lower precision (like FP16) to, for example, utilize Tensor Cores available on new Volta and Turing GPUs. While training in FP16 showed great success in image classification tasks, other more complicated neural networks typically stayed in FP32 due to difficulties in applying the FP16 training guidelines. @@ -253,7 +253,10 @@ We got 60% speed increase from 3 additional lines of code! ## Inference with AMP -To do inference with mixed precision for a trained model in FP32, you can use the conversion APIs: `amp.convert_model` for symbolic model and `amp.convert_hybrid_block` for gluon models. The conversion APIs will take the FP32 model as input and will return a mixed precision model, which can be used to run inference. Below, we demonstrate for a gluon model and a symbolic model: 1. Conversion from FP32 model to mixed precision model 2. Run inference on the mixed precision model. +To do inference with mixed precision for a trained model in FP32, you can use the conversion APIs: `amp.convert_model` for symbolic model and `amp.convert_hybrid_block` for gluon models. The conversion APIs will take the FP32 model as input and will return a mixed precision model, which can be used to run inference. +Below, we demonstrate for a gluon model and a symbolic model: +- Conversion from FP32 model to mixed precision model. +- Run inference on the mixed precision model. ```python with mx.Context(mx.gpu(0)): @@ -289,6 +292,48 @@ with mx.Context(mx.gpu(0)): print("Conversion and Inference completed successfully") ``` +You can also customize the operators to run in FP16 versus the operator to run in FP32 or to conditionally run in FP32. +Also, you can force cast the params wherever possible to FP16. Below is an example which demonstrates both these use cases +for symbolic model. You can do the same for gluon hybrid block with `amp.convert_hybrid_block` API, `cast_optional_params` flag. + +```python +with mx.Context(mx.gpu(0)): + # Below is an example of converting a symbolic model to a mixed precision model + # with only Convolution op being force casted to FP16. + dir_path = os.path.dirname(os.path.realpath(__file__)) + model_path = os.path.join(dir_path, 'model') + if not os.path.isdir(model_path): + os.mkdir(model_path) + prefix, epoch = mx.test_utils.download_model("imagenet1k-resnet-18", dst_dir=model_path) + sym, arg_params, aux_params = mx.model.load_checkpoint(prefix, epoch) + + # All Convolution ops should run in FP16, SoftmaxOutput and FullyConnected should run in FP32 + # cast_optional_params=True: Force cast params to FP16 wherever possible + result_sym, result_arg_params, result_aux_params = amp.convert_model(sym, + arg_params, + aux_params, + target_dtype_ops=["Convolution"], + fp32_ops=["SoftmaxOutput", "FullyConnected"], + cast_optional_params=True) + + # Run dummy inference with the converted symbolic model + mod = mx.mod.Module(result_sym, data_names=["data"], label_names=["softmax_label"], context=mx.current_context()) + mod.bind(data_shapes=[['data', (1, 3, 224, 224)]], label_shapes=[['softmax_label', (1,)]]) + mod.set_params(result_arg_params, result_aux_params) + mod.forward(mx.io.DataBatch(data=[mx.nd.ones((1, 3, 224, 224))], + label=[mx.nd.ones((1,))])) + mod.get_outputs()[0].wait_to_read() + + # Assert that the params for conv are in FP16, this is because cast_optional_params is set to True + assert mod._arg_params["conv0_weight"].dtype == np.float16 + # FullyConnected params stay in FP32 + assert mod._arg_params["fc1_bias"].dtype == np.float32 + + print("Conversion and Inference completed successfully") + + # Serialize AMP model and save to disk + mod.save_checkpoint("amp_tutorial_model", 0, remove_amp_cast=False) +``` ## Current limitations of AMP From 68460a927dceef5dfbd5d8db0550df1530ef57fd Mon Sep 17 00:00:00 2001 From: Xinyu Chen Date: Thu, 11 Jul 2019 11:29:36 +0800 Subject: [PATCH 056/813] [Doc] Add MKL install method apt/yum into tutorial (#15491) * add mkl install method * update in build instruction * trigger --- docs/install/build_from_source.md | 5 +++-- docs/tutorials/mkldnn/MKLDNN_README.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/install/build_from_source.md b/docs/install/build_from_source.md index b47267db8ef5..6fbfa247c037 100644 --- a/docs/install/build_from_source.md +++ b/docs/install/build_from_source.md @@ -123,8 +123,7 @@ You can set the BLAS library explicitly by setting the BLAS variable to: See the [cmake/ChooseBLAS.cmake](https://github.com/apache/incubator-mxnet/blob/master/cmake/ChooseBlas.cmake) file for the options. -Intel's MKL (Math Kernel Library) is one of the most powerful math libraries -https://software.intel.com/en-us/mkl +[Intel's MKL (Math Kernel Library)](https://software.intel.com/en-us/mkl) is one of the most powerful math libraries It has following flavors: @@ -144,6 +143,8 @@ shipped as a subrepo with MXNet source code (see 3rdparty/mkldnn or the [MKL-DNN Since the full MKL library is almost always faster than any other BLAS library it's turned on by default, however it needs to be downloaded and installed manually before doing `cmake` configuration. Register and download on the [Intel performance libraries website](https://software.intel.com/en-us/performance-libraries). +You can also install MKL through [YUM](https://software.intel.com/en-us/articles/installing-intel-free-libs-and-python-yum-repo) +or [APT](https://software.intel.com/en-us/articles/installing-intel-free-libs-and-python-apt-repo) Repository. Note: MKL is supported only for desktop builds and the framework itself supports the following hardware: diff --git a/docs/tutorials/mkldnn/MKLDNN_README.md b/docs/tutorials/mkldnn/MKLDNN_README.md index 460fa200cc46..516b2b3e796a 100644 --- a/docs/tutorials/mkldnn/MKLDNN_README.md +++ b/docs/tutorials/mkldnn/MKLDNN_README.md @@ -214,7 +214,7 @@ With MKL BLAS, the performace is expected to furtherly improved with variable ra You can redistribute not only dynamic libraries but also headers, examples and static libraries on accepting the license [Intel Simplified license](https://software.intel.com/en-us/license/intel-simplified-software-license). Installing the full MKL installation enables MKL support for all operators under the linalg namespace. - 1. Download and install the latest full MKL version following instructions on the [intel website.](https://software.intel.com/en-us/mkl) + 1. Download and install the latest full MKL version following instructions on the [intel website.](https://software.intel.com/en-us/mkl) You can also install MKL through [YUM](https://software.intel.com/en-us/articles/installing-intel-free-libs-and-python-yum-repo) or [APT](https://software.intel.com/en-us/articles/installing-intel-free-libs-and-python-apt-repo) Repository. 2. Run `make -j ${nproc} USE_BLAS=mkl` From 1f3195f2bdcc972d236fdd40a958b9f7d6052635 Mon Sep 17 00:00:00 2001 From: Aaron Markham Date: Thu, 11 Jul 2019 14:23:20 -0700 Subject: [PATCH 057/813] Julia docs (#15454) * add julia docs generation option * add julia docs to website build * update links to point to local julia site * fix mxnet build setting bug * fix link for site * update ubuntu guide for julia * turn building mxnet on by default * readd env var since CI uses it * fold julia docs into main website jenkins routine * fix ubuntu julia setup steps * cleanup mentions of the old julia docs pipeline --- ci/docker/runtime_functions.sh | 20 +++----- ci/jenkins/Jenkins_steps.groovy | 17 +------ ci/jenkins/Jenkinsfile_website | 1 - docs/api/julia/index.md | 11 +++-- docs/install/ubuntu_setup.md | 84 +++++++++++++++++++++++++++++---- docs/install/windows_setup.md | 2 +- docs/mxdoc.py | 15 +++++- docs/settings.ini | 19 +++++++- 8 files changed, 123 insertions(+), 46 deletions(-) diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index 424ab2cf7e92..b00fa3220d17 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -58,7 +58,7 @@ EOF else echo "NOTE: cython is used." return 0 - fi + fi } build_ccache_wrappers() { @@ -1415,15 +1415,7 @@ deploy_docs() { set -ex pushd . - export CC="ccache gcc" - export CXX="ccache g++" - make docs SPHINXOPTS=-W USE_MKLDNN=0 - - popd -} - -deploy_jl_docs() { - set -ex + # Setup for Julia docs export PATH="/work/julia10/bin:$PATH" export MXNET_HOME='/work/mxnet' export JULIA_DEPOT_PATH='/work/julia-depot' @@ -1433,11 +1425,13 @@ deploy_jl_docs() { # FIXME export LD_PRELOAD='/usr/lib/x86_64-linux-gnu/libjemalloc.so' export LD_LIBRARY_PATH=/work/mxnet/lib:$LD_LIBRARY_PATH + # End Julia setup - make -C julia/docs + export CC="ccache gcc" + export CXX="ccache g++" + make docs SPHINXOPTS=-W USE_MKLDNN=0 - # TODO: make Jenkins worker push to MXNet.jl ph-pages branch if master build - # ... + popd } build_static_scala_mkl() { diff --git a/ci/jenkins/Jenkins_steps.groovy b/ci/jenkins/Jenkins_steps.groovy index 4eb84b64fa91..40700ad22bb1 100644 --- a/ci/jenkins/Jenkins_steps.groovy +++ b/ci/jenkins/Jenkins_steps.groovy @@ -17,7 +17,7 @@ // specific language governing permissions and limitations // under the License. // -// This file contains the steps that will be used in the +// This file contains the steps that will be used in the // Jenkins pipelines utils = load('ci/Jenkinsfile_utils.groovy') @@ -1381,7 +1381,7 @@ def docs_website() { if ( master_url == 'jenkins.mxnet-ci.amazon-ml.com') { sh "ci/other/ci_deploy_doc.sh ${env.BRANCH_NAME} ${env.BUILD_NUMBER}" } else { - print "Skipping staging documentation publishing since we are not running in prod. Host: {$master_url}" + print "Skipping staging documentation publishing since we are not running in prod. Host: {$master_url}" } } } @@ -1389,19 +1389,6 @@ def docs_website() { }] } -def docs_julia() { - return ['Julia docs': { - node(NODE_LINUX_CPU) { - ws('workspace/julia-docs') { - timeout(time: max_time, unit: 'MINUTES') { - utils.unpack_and_init('cpu', mx_lib) - utils.docker_run('ubuntu_cpu', 'deploy_jl_docs', false) - } - } - } - }] -} - def misc_asan_cpu() { return ['CPU ASAN': { node(NODE_LINUX_CPU) { diff --git a/ci/jenkins/Jenkinsfile_website b/ci/jenkins/Jenkinsfile_website index acdd2be4d00e..5e97696a2953 100644 --- a/ci/jenkins/Jenkinsfile_website +++ b/ci/jenkins/Jenkinsfile_website @@ -39,7 +39,6 @@ core_logic: { utils.parallel_stage('Deploy', [ custom_steps.docs_website(), - custom_steps.docs_julia() ]) } , diff --git a/docs/api/julia/index.md b/docs/api/julia/index.md index 8aa884eb2772..3eb7df68b4e1 100644 --- a/docs/api/julia/index.md +++ b/docs/api/julia/index.md @@ -17,7 +17,7 @@ # MXNet - Julia API -See the [MXNet Julia Reference Manual](https://media.readthedocs.org/pdf/mxnet-test/latest/mxnet-test.pdf). +See the [MXNet Julia Site](site/index.html) for examples and API reference docs. MXNet supports the Julia programming language. The MXNet Julia package brings flexible and efficient GPU computing and the state-of-art deep learning to Julia. @@ -26,8 +26,9 @@ computing and the state-of-art deep learning to Julia. - It also enables you to construct and customize the state-of-art deep learning models in Julia, and apply them to tasks such as image classification and data science challenges. +## Installation +* [Ubuntu installation guide](../../install/ubuntu_setup.html) +* Mac / Windows guides are not available (contributions welcome!) -  - -## Julia API Reference -Julia documents are available at [http://dmlc.ml/MXNet.jl/latest/](http://dmlc.ml/MXNet.jl/latest/). +## Docs +To build your own copy of the [MXNet Julia Site](site/index.html), run `make -C julia/docs` from the MXNet source root directory. You can also generate it with Docker by using `dev_menu.py` from the root directory and choosing to build the entire website. The Julia site will be located in `api/julia/site/`. diff --git a/docs/install/ubuntu_setup.md b/docs/install/ubuntu_setup.md index ef700b4f353b..db8a0bb48067 100644 --- a/docs/install/ubuntu_setup.md +++ b/docs/install/ubuntu_setup.md @@ -310,25 +310,93 @@ Refer to the [Clojure setup guide](https://github.com/apache/incubator-mxnet/tre ### Install the MXNet Package for Julia -The MXNet package for Julia is hosted in a separate repository, MXNet.jl, which is available on [GitHub](https://github.com/dmlc/MXNet.jl). To use Julia binding it with an existing libmxnet installation, set the ```MXNET_HOME``` environment variable by running the following command: +#### Install Julia +The package available through `apt-get` is old and not compatible with the latest version of MXNet. +Fetch the latest version (1.0.3 at the time of this writing). ```bash - export MXNET_HOME=//libmxnet +wget -qO julia-10.tar.gz https://julialang-s3.julialang.org/bin/linux/x64/1.0/julia-1.0.3-linux-x86_64.tar.gz ``` -The path to the existing libmxnet installation should be the root directory of libmxnet. In other words, you should be able to find the ```libmxnet.so``` file at ```$MXNET_HOME/lib```. For example, if the root directory of libmxnet is ```~```, you would run the following command: +Place the extracted files somewhere like a julia folder in your home dir. ```bash - export MXNET_HOME=/~/libmxnet +mkdir ~/julia +mv julia-10.tar.gz ~/julia +cd ~/julia +tar xvf julia-10.tar.gz ``` -You might want to add this command to your ```~/.bashrc``` file. If you do, you can install the Julia package in the Julia console using the following command: +Test Julia. +```bash +cd julia-1.0.3/bin +julia -e 'using InteractiveUtils; versioninfo()' +``` + +If you're still getting the old version, remove it. +```bash +sudo apt remove julia +``` + +Update your PATH to have Julia's new location. Add this to your `.zshrc`, `.bashrc`, `.profile` or `.bash_profile`. +```bash +export PATH=~/julia/julia-1.0.3/bin:$PATH +``` + +Validate your PATH. +```bash +echo $PATH +``` + +Validate Julia works and is the expected version. +```bash +julia -e 'using InteractiveUtils; versioninfo()' +``` + +#### Setup Your MXNet-Julia Environment + +**For each of the following environment variables, add the commands to your `.zshrc`, `.bashrc`, `.profile` or `.bash_profile` to make them persist.** + +Create a `julia-depot` folder and environment variable. +```bash +mkdir julia-depot +export JULIA_DEPOT_PATH=$HOME/julia/julia-depot +``` + +To use the Julia binding with an existing `libmxnet` installation, set the `MXNET_HOME` environment variable to the MXNet source root. For example: +```bash +export MXNET_HOME=$HOME/incubator-mxnet +``` -```julia - Pkg.add("MXNet") +Now set the `LD_LIBRARY_PATH` environment variable to where `libmxnet.so` is found. If you can't find it, you might have skipped the building MXNet step. Go back and [build MXNet](#build-the-shared-library) first. For example: +```bash +export LD_LIBRARY_PATH=$HOME/incubator-mxnet/lib:$LD_LIBRARY_PATH +``` + +Verify the location of `libjemalloc.so` and set the `LD_PRELOAD` environment variable. +```bash +export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so:$LD_PRELOAD +``` + +With all of these updates, here's an example of what you might want to have in your `.zshrc`, `.bashrc`, `.profile` or `.bash_profile`. + +``` +export PATH=$HOME/bin:$HOME/.local/bin:$HOME/julia/julia-1.0.3/bin:$PATH +export JULIA_DEPOT_PATH=$HOME/julia/julia-depot +export MXNET_HOME=$HOME/incubator-mxnet +export LD_LIBRARY_PATH=$HOME/incubator-mxnet/lib:$LD_LIBRARY_PATH +export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so:$LD_PRELOAD +``` + +Install MXNet with Julia: + +```bash +julia --color=yes --project=./ -e \ + 'using Pkg; \ + Pkg.develop(PackageSpec(name="MXNet", path = joinpath(ENV["MXNET_HOME"], "julia")))' ``` -For more details about installing and using MXNet with Julia, see the [MXNet Julia documentation](http://dmlc.ml/MXNet.jl/latest/user-guide/install/). +For more details about installing and using MXNet with Julia, see the [MXNet Julia documentation](../api/julia/site/).
diff --git a/docs/install/windows_setup.md b/docs/install/windows_setup.md index f2561488be4d..4fb4e565bf8c 100644 --- a/docs/install/windows_setup.md +++ b/docs/install/windows_setup.md @@ -494,7 +494,7 @@ You might want to add this command to your ```~/.bashrc``` file. If you do, you Pkg.add("MXNet") ``` -For more details about installing and using MXNet with Julia, see the [MXNet Julia documentation](http://dmlc.ml/MXNet.jl/latest/user-guide/install/). +For more details about installing and using MXNet with Julia, see the [MXNet Julia documentation](/api/julia/site/). ## Installing the MXNet Package for Scala diff --git a/docs/mxdoc.py b/docs/mxdoc.py index 280f2a6ce282..f70dcfad1d0e 100644 --- a/docs/mxdoc.py +++ b/docs/mxdoc.py @@ -41,7 +41,7 @@ for section in [ _DOC_SET ]: print("Document sets to generate:") - for candidate in [ 'scala_docs', 'java_docs', 'clojure_docs', 'doxygen_docs', 'r_docs' ]: + for candidate in [ 'scala_docs', 'java_docs', 'clojure_docs', 'doxygen_docs', 'julia_docs', 'r_docs' ]: print('%-12s : %s' % (candidate, parser.get(section, candidate))) _MXNET_DOCS_BUILD_MXNET = parser.getboolean('mxnet', 'build_mxnet') @@ -50,6 +50,7 @@ _CLOJURE_DOCS = parser.getboolean(_DOC_SET, 'clojure_docs') _DOXYGEN_DOCS = parser.getboolean(_DOC_SET, 'doxygen_docs') _R_DOCS = parser.getboolean(_DOC_SET, 'r_docs') +_JULIA_DOCS = parser.getboolean(_DOC_SET, 'julia_docs') _ARTIFACTS = parser.getboolean(_DOC_SET, 'artifacts') # white list to evaluate the code block output, such as ['tutorials/gluon'] @@ -95,6 +96,13 @@ def build_mxnet(app): _run_cmd("cd %s/.. && make -j$(nproc) USE_MKLDNN=0 USE_CPP_PACKAGE=1 " % app.builder.srcdir) +def build_julia_docs(app): + """build Julia docs""" + dest_path = app.builder.outdir + '/api/julia/site' + _run_cmd('cd {}/.. && make -C julia/docs'.format(app.builder.srcdir)) + _run_cmd('mkdir -p {}'.format(dest_path)) + _run_cmd('cd {}/.. && cp -a julia/docs/site/* {}'.format(app.builder.srcdir, dest_path)) + def build_r_docs(app): """build r pdf""" r_root = app.builder.srcdir + '/../R-package' @@ -458,7 +466,7 @@ def copy_artifacts(app): def setup(app): # If MXNET_DOCS_BUILD_MXNET is set something different than 1 # Skip the build step - if os.getenv('MXNET_DOCS_BUILD_MXNET', '1') == '1' or _MXNET_DOCS_BUILD_MXNET: + if os.getenv('MXNET_DOCS_BUILD_MXNET') == '1'or _MXNET_DOCS_BUILD_MXNET: print("Building MXNet!") app.connect("builder-inited", build_mxnet) if _DOXYGEN_DOCS: @@ -476,6 +484,9 @@ def setup(app): if _CLOJURE_DOCS: print("Building Clojure Docs!") app.connect("builder-inited", build_clojure_docs) + if _JULIA_DOCS: + print("Building Julia Docs!") + app.connect("builder-inited", build_julia_docs) if _R_DOCS: print("Building R Docs!") app.connect("builder-inited", build_r_docs) diff --git a/docs/settings.ini b/docs/settings.ini index ec64dd784cdb..22887f7a576b 100644 --- a/docs/settings.ini +++ b/docs/settings.ini @@ -16,7 +16,7 @@ # under the License. [mxnet] -build_mxnet = 0 +build_mxnet = 1 [document_sets_tutorial] artifacts = 0 @@ -24,6 +24,7 @@ clojure_docs = 0 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 0 [document_sets_default] @@ -32,6 +33,7 @@ clojure_docs = 1 doxygen_docs = 1 java_docs = 1 r_docs = 0 +julia_docs = 1 scala_docs = 1 [document_sets_1.4.0] @@ -40,6 +42,7 @@ clojure_docs = 1 doxygen_docs = 1 java_docs = 1 r_docs = 0 +julia_docs = 0 scala_docs = 1 [document_sets_v1.4.x] @@ -48,6 +51,7 @@ clojure_docs = 1 doxygen_docs = 1 java_docs = 1 r_docs = 0 +julia_docs = 0 scala_docs = 1 [document_sets_1.3.1] @@ -56,6 +60,7 @@ clojure_docs = 1 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 1 [document_sets_1.3.0] @@ -64,6 +69,7 @@ clojure_docs = 1 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 1 [document_sets_v1.3.x] @@ -72,6 +78,7 @@ clojure_docs = 0 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 0 [document_sets_1.2.0] @@ -80,6 +87,7 @@ clojure_docs = 0 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 1 [document_sets_v1.2.0] @@ -88,6 +96,7 @@ clojure_docs = 0 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 1 [document_sets_1.1.0] @@ -96,6 +105,7 @@ clojure_docs = 0 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 0 [document_sets_v1.1.0] @@ -104,6 +114,7 @@ clojure_docs = 0 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 0 [document_sets_1.0.0] @@ -112,6 +123,7 @@ clojure_docs = 0 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 0 [document_sets_v1.0.0] @@ -120,6 +132,7 @@ clojure_docs = 0 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 0 [document_sets_0.12.0] @@ -128,6 +141,7 @@ clojure_docs = 0 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 0 [document_sets_v0.12.0] @@ -136,6 +150,7 @@ clojure_docs = 0 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 0 [document_sets_0.11.0] @@ -144,6 +159,7 @@ clojure_docs = 0 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 0 [document_sets_v0.11.0] @@ -152,4 +168,5 @@ clojure_docs = 0 doxygen_docs = 1 java_docs = 0 r_docs = 0 +julia_docs = 0 scala_docs = 0 From 5ffd598499ee23372b446a4303504c72cace57f4 Mon Sep 17 00:00:00 2001 From: Iblis Lin Date: Fri, 12 Jul 2019 05:23:35 +0800 Subject: [PATCH 058/813] CI: upgrade Julia version from 1.0.3 to 1.0.4 (#15502) This is a bugfix release. No breaking changes. see: https://julialang.org/downloads/ --- ci/docker/install/ubuntu_julia.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/docker/install/ubuntu_julia.sh b/ci/docker/install/ubuntu_julia.sh index 6849fe228502..435ec46db6c7 100755 --- a/ci/docker/install/ubuntu_julia.sh +++ b/ci/docker/install/ubuntu_julia.sh @@ -40,4 +40,4 @@ function install_julia() { } install_julia 0.7 0.7.0 -install_julia 1.0 1.0.3 +install_julia 1.0 1.0.4 From 554b1965595fbac10052ce23987773c185ef5e04 Mon Sep 17 00:00:00 2001 From: Yimin Jiang Date: Fri, 12 Jul 2019 06:18:35 +0800 Subject: [PATCH 059/813] Rebase #13757 to master (#15189) * Update .gitmodules * Set ImageNet data augmentation by default https://github.com/apache/incubator-mxnet/blob/a38278ddebfcc9459d64237086cd7977ec20c70e/example/image-classification/train_imagenet.py#L42 When I try to train imagenet with this line commented, the train-accuracy reaches 99% while the validation-accuracy is only less than 50% (single machine, 8 GPUs, global batchsize=2048, Resnet50). Absolutely this is overfitting. Then I uncomment this line and try again with the same experiment settings. This time both train and validation accuracy converge to about 70%. Thus, it seems that this data augmentation is pretty important for ImageNet training. Perhaps it will be better to uncomment this as default, so that future developers won't get confused by the over-fit issue. * Add argument for imagenet data augmentation * Enable data-aug with argument * Update .gitmodules --- example/image-classification/common/fit.py | 4 +++- example/image-classification/train_imagenet.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/example/image-classification/common/fit.py b/example/image-classification/common/fit.py index 5775f30bd885..8e8b0197960a 100755 --- a/example/image-classification/common/fit.py +++ b/example/image-classification/common/fit.py @@ -142,6 +142,8 @@ def add_fit_args(parser): train.add_argument('--profile-server-suffix', type=str, default='', help='profile server actions into a file with name like rank1_ followed by this suffix \ during distributed training') + train.add_argument('--use-imagenet-data-augmentation', type=int, default=0, + help='enable data augmentation of ImageNet data, default disabled') return train @@ -335,4 +337,4 @@ def fit(args, network, data_loader, **kwargs): if args.profile_server_suffix: mx.profiler.set_state(state='run', profile_process='server') if args.profile_worker_suffix: - mx.profiler.set_state(state='run', profile_process='worker') \ No newline at end of file + mx.profiler.set_state(state='run', profile_process='worker') diff --git a/example/image-classification/train_imagenet.py b/example/image-classification/train_imagenet.py index 0835f5d3ba9b..421c15db73ad 100644 --- a/example/image-classification/train_imagenet.py +++ b/example/image-classification/train_imagenet.py @@ -38,8 +38,6 @@ def set_imagenet_aug(aug): fit.add_fit_args(parser) data.add_data_args(parser) data.add_data_aug_args(parser) - # uncomment to set standard augmentations for imagenet training - # set_imagenet_aug(parser) parser.set_defaults( # network network = 'resnet', @@ -56,6 +54,8 @@ def set_imagenet_aug(aug): dtype = 'float32' ) args = parser.parse_args() + if args.use_imagenet_data_augmentation: + set_imagenet_aug(parser) # load network from importlib import import_module From 6a564bec3e2c0d8388da1e89d39851e54f055a31 Mon Sep 17 00:00:00 2001 From: Dick Carter Date: Fri, 12 Jul 2019 09:53:58 -0700 Subject: [PATCH 060/813] cuda/cuDNN lib version checking. Force cuDNN v7 usage. (#15449) * Add STATIC_ASSERT_{CUDA,CUDNN}_VERSION_GE macros. Protect rnn.cc against CUDNN_VERSION < 7000. * Omit cuda/cudnn lib version checks when no visible gpu devices. * Move STATIC_ASSERT_... to resource.cc. * Remove function names in cuda/cudnn version check impl. * Remove runtime cuda lib check- major.minor already needed for program load. --- docs/faq/env_var.md | 10 ++++ include/mxnet/resource.h | 8 +-- src/common/cuda_utils.cc | 105 +++++++++++++++++++++++++++++++++++++++ src/common/cuda_utils.h | 27 ++++++++++ src/resource.cc | 22 ++++---- 5 files changed, 158 insertions(+), 14 deletions(-) create mode 100644 src/common/cuda_utils.cc diff --git a/docs/faq/env_var.md b/docs/faq/env_var.md index cdd528cd8c8f..a5ba07098005 100644 --- a/docs/faq/env_var.md +++ b/docs/faq/env_var.md @@ -242,6 +242,16 @@ If ctypes is used, it must be `mxnet._ctypes.ndarray.NDArrayBase`. - If set to '0', disallows implicit type conversions to Float16 to use Tensor Cores - If set to '1', allows CUDA ops like RNN and Convolution to use TensorCores even with Float32 input data by using implicit type casting to Float16. Only has an effect if `MXNET_CUDA_ALLOW_TENSOR_CORE` is `1`. +* MXNET_CUDA_VERSION_CHECKING + - 0(false) or 1(true) ```(default=1)``` + - If set to '0', disallows various runtime checks of the cuda library version and associated warning messages. + - If set to '1', permits these checks (e.g. compile vs. link mismatch, old version no longer CI-tested) + +* MXNET_CUDNN_VERSION_CHECKING + - 0(false) or 1(true) ```(default=1)``` + - If set to '0', disallows various runtime checks of the cuDNN library version and associated warning messages. + - If set to '1', permits these checks (e.g. compile vs. link mismatch, old version no longer CI-tested) + * MXNET_GLUON_REPO - Values: String ```(default='https://apache-mxnet.s3-accelerate.dualstack.amazonaws.com/'``` - The repository url to be used for Gluon datasets and pre-trained models. diff --git a/include/mxnet/resource.h b/include/mxnet/resource.h index 34c8f88d1ca9..f8ee6364807c 100644 --- a/include/mxnet/resource.h +++ b/include/mxnet/resource.h @@ -44,11 +44,11 @@ struct ResourceRequest { kTempSpace, /*! \brief common::RandGenerator object, which can be used in GPU kernel functions */ kParallelRandom -#if MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#if MXNET_USE_CUDNN == 1 , /*! \brief cudnnDropoutDescriptor_t object for GPU dropout kernel functions */ kCuDNNDropoutDesc -#endif // MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#endif // MXNET_USE_CUDNN == 1 }; /*! \brief type of resources */ Type type; @@ -162,7 +162,7 @@ struct Resource { reinterpret_cast(get_space_internal(shape.Size() * sizeof(DType))), shape, shape[ndim - 1], stream); } -#if MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#if MXNET_USE_CUDNN == 1 /*! * \brief Get cudnn dropout descriptor from shared state space. * @@ -175,7 +175,7 @@ struct Resource { mshadow::Stream *stream, const float dropout, uint64_t seed) const; -#endif // MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#endif // MXNET_USE_CUDNN == 1 /*! * \brief Get CPU space as mshadow Tensor in specified type. diff --git a/src/common/cuda_utils.cc b/src/common/cuda_utils.cc new file mode 100644 index 000000000000..728d1e6681e6 --- /dev/null +++ b/src/common/cuda_utils.cc @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file cuda_utils.cc + * \brief CUDA debugging utilities. + */ + +#include +#include "cuda_utils.h" + +#if MXNET_USE_CUDA == 1 + +namespace mxnet { +namespace common { +namespace cuda { + +// The oldest version of cuda used in upstream MXNet CI testing, both for unix and windows. +// Users that have rebuilt MXNet against older versions will we advised with a warning to upgrade +// their systems to match the CI level. Minimally, users should rerun the CI locally. +#if defined(_MSC_VER) +#define MXNET_CI_OLDEST_CUDA_VERSION 9020 +#else +#define MXNET_CI_OLDEST_CUDA_VERSION 10000 +#endif + +// Dynamic init here will emit a warning if runtime and compile-time cuda lib versions mismatch. +// Also if the user has recompiled their source to a version no longer tested by upstream CI. +bool cuda_version_check_performed = []() { + // Don't bother with checks if there are no GPUs visible (e.g. with CUDA_VISIBLE_DEVICES="") + if (dmlc::GetEnv("MXNET_CUDA_VERSION_CHECKING", true) && Context::GetGPUCount() > 0) { + // Not currently performing a runtime check of linked-against vs. compiled-against + // cuda runtime library, as major.minor must match for libmxnet.so to even load, per: + // https://docs.nvidia.com/deploy/cuda-compatibility/#binary-compatibility + if (CUDA_VERSION < MXNET_CI_OLDEST_CUDA_VERSION) + LOG(WARNING) << "Upgrade advisory: this mxnet has been built against cuda library version " + << CUDA_VERSION << ", which is older than the oldest version tested by CI (" + << MXNET_CI_OLDEST_CUDA_VERSION << "). " + << "Set MXNET_CUDA_VERSION_CHECKING=0 to quiet this warning."; + } + return true; +}(); + +} // namespace cuda +} // namespace common +} // namespace mxnet + +#endif // MXNET_USE_CUDA + +#if MXNET_USE_CUDNN == 1 + +namespace mxnet { +namespace common { +namespace cudnn { + +// The oldest version of CUDNN used in upstream MXNet CI testing, both for unix and windows. +// Users that have rebuilt MXNet against older versions will we advised with a warning to upgrade +// their systems to match the CI level. Minimally, users should rerun the CI locally. +#if defined(_MSC_VER) +#define MXNET_CI_OLDEST_CUDNN_VERSION 7600 +#else +#define MXNET_CI_OLDEST_CUDNN_VERSION 7600 +#endif + +// Dynamic init here will emit a warning if runtime and compile-time cudnn lib versions mismatch. +// Also if the user has recompiled their source to a version no longer tested by upstream CI. +bool cudnn_version_check_performed = []() { + // Don't bother with checks if there are no GPUs visible (e.g. with CUDA_VISIBLE_DEVICES="") + if (dmlc::GetEnv("MXNET_CUDNN_VERSION_CHECKING", true) && Context::GetGPUCount() > 0) { + size_t linkedAgainstCudnnVersion = cudnnGetVersion(); + if (linkedAgainstCudnnVersion != CUDNN_VERSION) + LOG(WARNING) << "cuDNN library mismatch: linked-against version " << linkedAgainstCudnnVersion + << " != compiled-against version " << CUDNN_VERSION << ". " + << "Set MXNET_CUDNN_VERSION_CHECKING=0 to quiet this warning."; + if (CUDNN_VERSION < MXNET_CI_OLDEST_CUDNN_VERSION) + LOG(WARNING) << "Upgrade advisory: this mxnet has been built against cuDNN library version " + << CUDNN_VERSION << ", which is older than the oldest version tested by CI (" + << MXNET_CI_OLDEST_CUDNN_VERSION << "). " + << "Set MXNET_CUDNN_VERSION_CHECKING=0 to quiet this warning."; + } + return true; +}(); + +} // namespace cudnn +} // namespace common +} // namespace mxnet + +#endif // MXNET_USE_CUDNN diff --git a/src/common/cuda_utils.h b/src/common/cuda_utils.h index 0dd9d2db3722..acc8d5fac6df 100644 --- a/src/common/cuda_utils.h +++ b/src/common/cuda_utils.h @@ -47,12 +47,20 @@ extern __cuda_fake_struct threadIdx; extern __cuda_fake_struct blockIdx; #endif +#define QUOTE(x) #x +#define QUOTEVALUE(x) QUOTE(x) + #if MXNET_USE_CUDA #include #include #include +#define STATIC_ASSERT_CUDA_VERSION_GE(min_version) \ + static_assert(CUDA_VERSION >= min_version, "Compiled-against CUDA version " \ + QUOTEVALUE(CUDA_VERSION) " is too old, please upgrade system to version " \ + QUOTEVALUE(min_version) " or later.") + /*! * \brief When compiling a __device__ function, check that the architecture is >= Kepler (3.0) * Note that __CUDA_ARCH__ is not defined outside of a __device__ function @@ -441,6 +449,25 @@ inline cublasMath_t SetCublasMathMode(cublasHandle_t blas_handle, cublasMath_t n #include +// Creating CUDNN_VERSION_AS_STRING as follows avoids a static_assert error message that shows +// the formula for CUDNN_VERSION, i.e. "1000 * 7 + 100 * 6 + 0" rather than number "7600". +static_assert(CUDNN_PATCHLEVEL < 100 && CUDNN_MINOR < 10, + "CUDNN_VERSION_AS_STRING macro assumptions violated."); +#if CUDNN_PATCHLEVEL >= 10 +#define CUDNN_VERSION_AS_STRING QUOTEVALUE(CUDNN_MAJOR) \ + QUOTEVALUE(CUDNN_MINOR) \ + QUOTEVALUE(CUDNN_PATCHLEVEL) +#else +#define CUDNN_VERSION_AS_STRING QUOTEVALUE(CUDNN_MAJOR) \ + QUOTEVALUE(CUDNN_MINOR) \ + "0" QUOTEVALUE(CUDNN_PATCHLEVEL) +#endif + +#define STATIC_ASSERT_CUDNN_VERSION_GE(min_version) \ + static_assert(CUDNN_VERSION >= min_version, "Compiled-against cuDNN version " \ + CUDNN_VERSION_AS_STRING " is too old, please upgrade system to version " \ + QUOTEVALUE(min_version) " or later.") + #define CUDNN_CALL(func) \ { \ cudnnStatus_t e = (func); \ diff --git a/src/resource.cc b/src/resource.cc index cd6320d393b1..3f461243e499 100644 --- a/src/resource.cc +++ b/src/resource.cc @@ -92,9 +92,9 @@ class ResourceManagerImpl : public ResourceManager { gpu_temp_space_copy_ = dmlc::GetEnv("MXNET_GPU_TEMP_COPY", 1); cpu_native_rand_copy_ = dmlc::GetEnv("MXNET_CPU_PARALLEL_RAND_COPY", 1); gpu_native_rand_copy_ = dmlc::GetEnv("MXNET_GPU_PARALLEL_RAND_COPY", 4); -#if MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#if MXNET_USE_CUDNN == 1 gpu_cudnn_dropout_state_copy_ = dmlc::GetEnv("MXNET_GPU_CUDNN_DROPOUT_STATE_COPY", 4); -#endif // MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#endif // MXNET_USE_CUDNN == 1 engine_ref_ = Engine::_GetSharedRef(); storage_ref_ = Storage::_GetSharedRef(); cpu_rand_.reset(new ResourceRandom( @@ -113,9 +113,9 @@ class ResourceManagerImpl : public ResourceManager { gpu_rand_.Clear(); gpu_space_.Clear(); gpu_parallel_rand_.Clear(); -#if MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#if MXNET_USE_CUDNN == 1 gpu_cudnn_dropout_state_.Clear(); -#endif // MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#endif // MXNET_USE_CUDNN == 1 #endif if (engine_ref_ != nullptr) { engine_ref_ = nullptr; @@ -153,14 +153,14 @@ class ResourceManagerImpl : public ResourceManager { return new ResourceParallelRandom(ctx, gpu_native_rand_copy_, global_seed_); })->GetNext(); } -#if MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#if MXNET_USE_CUDNN == 1 case ResourceRequest::kCuDNNDropoutDesc: { return gpu_cudnn_dropout_state_.Get(ctx.dev_id, [ctx, this]() { return new ResourceTempSpace( ctx, gpu_cudnn_dropout_state_copy_); })->GetNext(); } -#endif // MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#endif // MXNET_USE_CUDNN == 1 default: LOG(FATAL) << "Unknown supported type " << req.type; } #else @@ -399,13 +399,13 @@ class ResourceManagerImpl : public ResourceManager { common::LazyAllocArray> gpu_space_; /*! \brief GPU parallel (on device) random number resources */ common::LazyAllocArray > gpu_parallel_rand_; -#if MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#if MXNET_USE_CUDNN == 1 /*! \brief number of copies in GPU cudnn dropout descriptor resources */ int gpu_cudnn_dropout_state_copy_; /*! \brief GPU parallel (on device) random number resources */ common::LazyAllocArray> gpu_cudnn_dropout_state_; -#endif // MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#endif // MXNET_USE_CUDNN == 1 #endif }; } // namespace resource @@ -418,7 +418,7 @@ void* Resource::get_host_space_internal(size_t size) const { return static_cast(ptr_)->GetHostSpace(size); } -#if MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#if MXNET_USE_CUDNN == 1 void Resource::get_cudnn_dropout_desc( cudnnDropoutDescriptor_t* dropout_desc, mshadow::Stream *stream, @@ -442,6 +442,8 @@ void Resource::get_cudnn_dropout_desc( dropout_state_size, seed)); } else { + // cudnnRestoreDropoutDescriptor() introduced with cuDNN v7 + STATIC_ASSERT_CUDNN_VERSION_GE(7000); CUDNN_CALL(cudnnRestoreDropoutDescriptor(*dropout_desc, stream->dnn_handle_, dropout, state_space->handle.dptr, @@ -449,7 +451,7 @@ void Resource::get_cudnn_dropout_desc( seed)); } } -#endif // MXNET_USE_CUDNN == 1 && CUDNN_MAJOR >= 7 +#endif // MXNET_USE_CUDNN == 1 ResourceManager* ResourceManager::Get() { typedef dmlc::ThreadLocalStore inst; From 9ff6c46edaa09cb468f9083ef9644dace5cce7b8 Mon Sep 17 00:00:00 2001 From: Pedro Larroy Date: Fri, 12 Jul 2019 11:04:47 -0700 Subject: [PATCH 061/813] Add -R option to ci/build.py to avoid rebuilding containers (#15426) There are a couple of problems which trigger continuous rebuild of the containers which have difficult solutions, this option enables a workaround. https://github.com/docker/docker.github.io/issues/8886 --- ci/build.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ci/build.py b/ci/build.py index 3d6fb9b4d893..e6a183fefa34 100755 --- a/ci/build.py +++ b/ci/build.py @@ -416,6 +416,10 @@ def main() -> int: help="Only build the container, don't build the project", action='store_true') + parser.add_argument("-R", "--run-only", + help="Only run the container, don't rebuild the container", + action='store_true') + parser.add_argument("-a", "--all", help="build for all platforms", action='store_true') @@ -492,8 +496,12 @@ def signal_handler(signum, _): tag = get_docker_tag(platform=platform, registry=args.docker_registry) if args.docker_registry: load_docker_cache(tag=tag, docker_registry=args.docker_registry) - build_docker(platform=platform, docker_binary=docker_binary, registry=args.docker_registry, + if not args.run_only: + build_docker(platform=platform, docker_binary=docker_binary, registry=args.docker_registry, num_retries=args.docker_build_retries, no_cache=args.no_cache) + else: + logging.info("Skipping docker build step.") + if args.build_only: logging.warning("Container was just built. Exiting due to build-only.") return 0 From 2565fa2460495b0524b62460f27eb2d0c2f70648 Mon Sep 17 00:00:00 2001 From: Lai Wei Date: Fri, 12 Jul 2019 13:17:59 -0700 Subject: [PATCH 062/813] fix nightly CI failure (#15452) * fix nightly * update with nosetest and fixed seed * remove line --- ci/docker/runtime_functions.sh | 6 +- docs/tutorials/amp/amp_tutorial.md | 1 + tests/nightly/estimator/test_estimator_cnn.py | 32 ++++++--- tests/nightly/estimator/test_sentiment_rnn.py | 69 +++++++++---------- 4 files changed, 55 insertions(+), 53 deletions(-) diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index b00fa3220d17..596b956efc84 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -1403,10 +1403,8 @@ nightly_estimator() { set -ex cd /work/mxnet/tests/nightly/estimator export PYTHONPATH=/work/mxnet/python/ - python test_estimator_cnn.py --type gpu - python test_sentiment_rnn.py --type gpu - python test_estimator_cnn.py --type cpu - python test_sentiment_rnn.py --type cpu + nosetests test_estimator_cnn.py + nosetests test_sentiment_rnn.py } # Deploy diff --git a/docs/tutorials/amp/amp_tutorial.md b/docs/tutorials/amp/amp_tutorial.md index b75ac97b9cc8..5f9bd1195a1c 100644 --- a/docs/tutorials/amp/amp_tutorial.md +++ b/docs/tutorials/amp/amp_tutorial.md @@ -35,6 +35,7 @@ import os import logging import warnings import time +import numpy as np import mxnet as mx import mxnet.gluon as gluon from mxnet import autograd diff --git a/tests/nightly/estimator/test_estimator_cnn.py b/tests/nightly/estimator/test_estimator_cnn.py index c60dc544b347..4a3bb2076524 100644 --- a/tests/nightly/estimator/test_estimator_cnn.py +++ b/tests/nightly/estimator/test_estimator_cnn.py @@ -17,14 +17,22 @@ # Test gluon estimator on CNN models -import argparse -import numpy as np +import os +import sys + import mxnet as mx +import numpy as np from mxnet import gluon, init, nd from mxnet.gluon import data from mxnet.gluon.contrib.estimator import estimator from mxnet.gluon.model_zoo import vision +# use with_seed decorator in python/unittest/common.py +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'python', 'unittest')) +from common import with_seed +import unittest + + def load_data_mnist(batch_size, resize=None, num_workers=4): ''' Load MNIST dataset @@ -44,6 +52,7 @@ def load_data_mnist(batch_size, resize=None, num_workers=4): num_workers=num_workers) return train_iter, test_iter + def bilinear_kernel(in_channels, out_channels, kernel_size): ''' Bilinear interpolation using transposed convolution @@ -60,6 +69,7 @@ def bilinear_kernel(in_channels, out_channels, kernel_size): weight[range(in_channels), range(out_channels), :, :] = filt return nd.array(weight) + def get_net(model_name, context): if model_name == 'FCN': num_classes = 21 @@ -82,6 +92,8 @@ def get_net(model_name, context): loss_axis = -1 return net, input_shape, label_shape, loss_axis + +@with_seed() def test_estimator_cpu(): ''' Test estimator by doing one pass over each model with synthetic data @@ -112,6 +124,10 @@ def test_estimator_cpu(): val_data=val_data, epochs=1) + +# using fixed seed to reduce flakiness in accuracy assertion +@with_seed(7) +@unittest.skipIf(mx.context.num_gpus() < 1, "skip if no GPU") def test_estimator_gpu(): ''' Test estimator by training resnet18_v1 for 5 epochs on MNIST and verify accuracy @@ -139,13 +155,7 @@ def test_estimator_gpu(): assert acc.get()[1] > 0.80 + if __name__ == '__main__': - parser = argparse.ArgumentParser(description='test gluon estimator') - parser.add_argument('--type', type=str, default='cpu') - opt = parser.parse_args() - if opt.type == 'cpu': - test_estimator_cpu() - elif opt.type == 'gpu': - test_estimator_gpu() - else: - raise RuntimeError("Unknown test type") + import nose + nose.runmodule() diff --git a/tests/nightly/estimator/test_sentiment_rnn.py b/tests/nightly/estimator/test_sentiment_rnn.py index d54af491f325..d2eef9b6733c 100644 --- a/tests/nightly/estimator/test_sentiment_rnn.py +++ b/tests/nightly/estimator/test_sentiment_rnn.py @@ -20,17 +20,23 @@ https://github.com/d2l-ai/d2l-en/blob/master/chapter_natural-language-processing/sentiment-analysis-rnn.md https://github.com/d2l-ai/d2l-en/blob/master/chapter_natural-language-processing/sentiment-analysis-cnn.md""" -import argparse +import collections import os -import tarfile import random -import collections +import sys +import tarfile + import mxnet as mx from mxnet import nd, gluon from mxnet.contrib import text from mxnet.gluon import nn, rnn from mxnet.gluon.contrib.estimator import estimator +# use with_seed decorator in python/unittest/common.py +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'python', 'unittest')) +from common import with_seed +import unittest + class TextCNN(nn.Block): def __init__(self, vocab, embed_size, kernel_sizes, num_channels, @@ -175,14 +181,10 @@ def pad(x): return features, labels -def run(net, train_dataloader, test_dataloader, **kwargs): +def run(net, train_dataloader, test_dataloader, num_epochs, ctx, lr): ''' Train a test sentiment model ''' - num_epochs = kwargs['epochs'] - ctx = kwargs['ctx'] - batch_size = kwargs['batch_size'] - lr = kwargs['lr'] # Define trainer trainer = mx.gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': lr}) @@ -199,14 +201,17 @@ def run(net, train_dataloader, test_dataloader, **kwargs): return acc -def test_estimator_cpu(**kwargs): +@with_seed() +def test_estimator_cpu(): ''' Test estimator by doing one pass over each model with synthetic data ''' models = ['TextCNN', 'BiRNN'] - ctx = kwargs['ctx'] - batch_size = kwargs['batch_size'] - embed_size = kwargs['embed_size'] + ctx = mx.cpu() + batch_size = 64 + embed_size = 100 + lr = 1 + num_epochs = 1 train_data = mx.nd.random.randint(low=0, high=100, shape=(2 * batch_size, 500)) train_label = mx.nd.random.randint(low=0, high=2, shape=(2 * batch_size,)) @@ -229,18 +234,22 @@ def test_estimator_cpu(**kwargs): net = BiRNN(vocab_list, embed_size, num_hiddens, num_layers) net.initialize(mx.init.Xavier(), ctx=ctx) - run(net, train_dataloader, val_dataloader, **kwargs) + run(net, train_dataloader, val_dataloader, num_epochs=num_epochs, ctx=ctx, lr=lr) -def test_estimator_gpu(**kwargs): +# using fixed seed to reduce flakiness in accuracy assertion +@with_seed(7) +@unittest.skipIf(mx.context.num_gpus() < 1, "skip if no GPU") +def test_estimator_gpu(): ''' Test estimator by training Bidirectional RNN for 5 epochs on the IMDB dataset and verify accuracy ''' - ctx = kwargs['ctx'] - batch_size = kwargs['batch_size'] - num_epochs = kwargs['epochs'] - embed_size = kwargs['embed_size'] + ctx = mx.gpu(0) + batch_size = 64 + num_epochs = 5 + embed_size = 100 + lr = 0.01 # data download_imdb() @@ -263,27 +272,11 @@ def test_estimator_gpu(**kwargs): net.embedding.weight.set_data(glove_embedding.idx_to_vec) net.embedding.collect_params().setattr('grad_req', 'null') - acc = run(net, train_dataloader, test_dataloader, **kwargs) + acc = run(net, train_dataloader, test_dataloader, num_epochs=num_epochs, ctx=ctx, lr=lr) assert acc.get()[1] > 0.70 -parser = argparse.ArgumentParser(description='test gluon estimator') -parser.add_argument('--type', type=str, default='cpu') -opt = parser.parse_args() -kwargs = { - 'batch_size': 64, - 'lr': 0.01, - 'embed_size': 100 -} - -if opt.type == 'cpu': - kwargs['ctx'] = mx.cpu() - kwargs['epochs'] = 1 - test_estimator_cpu(**kwargs) -elif opt.type == 'gpu': - kwargs['ctx'] = mx.gpu() - kwargs['epochs'] = 5 - test_estimator_gpu(**kwargs) -else: - raise RuntimeError("Unknown test type") +if __name__ == '__main__': + import nose + nose.runmodule() From 9c5acb452c35cbf7eb8259bd18be5bd88e01bc30 Mon Sep 17 00:00:00 2001 From: Wang Jiajun Date: Fri, 12 Jul 2019 18:17:28 -0500 Subject: [PATCH 063/813] Accelerate ROIPooling layer (#14894) * refactor roi_pooling backward * update max_idx * refactor gpu part * update * update max_idx as int * remove unused arguments * trigger CI * fix index type as index_t * remove assert, fix invalid box * update * fix * trigger CI * retrigger CI --- src/operator/roi_pooling-inl.h | 11 ++- src/operator/roi_pooling.cc | 128 ++++++++------------------------- src/operator/roi_pooling.cu | 127 ++++++++------------------------ 3 files changed, 66 insertions(+), 200 deletions(-) diff --git a/src/operator/roi_pooling-inl.h b/src/operator/roi_pooling-inl.h index ce0efe9b07c9..a189fe231826 100644 --- a/src/operator/roi_pooling-inl.h +++ b/src/operator/roi_pooling-inl.h @@ -83,7 +83,7 @@ class ROIPoolingOp : public Operator { Tensor data = in_data[roipool::kData].get(s); Tensor bbox = in_data[roipool::kBox].get(s); Tensor out = out_data[roipool::kOut].get(s); - Tensor max_idx = out_data[roipool::kMaxIdx].get(s); + Tensor max_idx = out_data[roipool::kMaxIdx].get(s); CHECK_EQ(data.CheckContiguous(), true); CHECK_EQ(bbox.CheckContiguous(), true); CHECK_EQ(out.CheckContiguous(), true); @@ -114,7 +114,7 @@ class ROIPoolingOp : public Operator { Tensor grad_out = out_grad[roipool::kOut].get(s); Tensor bbox = in_data[roipool::kBox].get(s); - Tensor max_idx = out_data[roipool::kMaxIdx].get(s); + Tensor max_idx = out_data[roipool::kMaxIdx].get(s); Tensor grad_in = in_grad[roipool::kData].get(s); Tensor grad_roi = in_grad[roipool::kBox].get(s); CHECK_EQ(grad_out.CheckContiguous(), true); @@ -195,6 +195,7 @@ class ROIPoolingProp : public OperatorProperty { bool InferType(std::vector *in_type, std::vector *out_type, std::vector *aux_type) const override { + using namespace mshadow; CHECK_EQ(in_type->size(), 2U); int dtype = (*in_type)[0]; CHECK_EQ(dtype, (*in_type)[1]); @@ -202,7 +203,11 @@ class ROIPoolingProp : public OperatorProperty { out_type->clear(); out_type->push_back(dtype); - out_type->push_back(dtype); +# if MXNET_USE_INT64_TENSOR_SIZE == 1 + out_type->push_back(kInt64); +# else + out_type->push_back(kInt32); +# endif return true; } diff --git a/src/operator/roi_pooling.cc b/src/operator/roi_pooling.cc index 8862d0db1401..bba3bea5ce6a 100644 --- a/src/operator/roi_pooling.cc +++ b/src/operator/roi_pooling.cc @@ -40,12 +40,13 @@ template inline void ROIPoolForward(const Tensor &out, const Tensor &data, const Tensor &bbox, - const Tensor &max_idx, + const Tensor &max_idx, const float spatial_scale_) { const Dtype *bottom_data = data.dptr_; const Dtype *bottom_rois = bbox.dptr_; Dtype *top_data = out.dptr_; - Dtype *argmax_data = max_idx.dptr_; + index_t *argmax_data = max_idx.dptr_; + const int batch_size = data.size(0); const int channels_ = data.size(1); const int height_ = data.size(2); const int width_ = data.size(3); @@ -53,25 +54,25 @@ inline void ROIPoolForward(const Tensor &out, const int pooled_width_ = out.size(3); const int num_rois = bbox.size(0); - const int data_size = data.size(1) * data.size(2) * data.size(3); - const int data_size_c = data.size(2) * data.size(3); - const int out_size_c = out.size(2) * out.size(3); - const int out_size = channels_ * out_size_c; - const int max_idx_size_c = max_idx.size(2) * max_idx.size(3); - const int max_idx_size = channels_ * max_idx_size_c; + const index_t data_size = data.size(1) * data.size(2) * data.size(3); + const index_t data_size_c = data.size(2) * data.size(3); + const index_t out_size_c = out.size(2) * out.size(3); + const index_t out_size = channels_ * out_size_c; + const index_t max_idx_size_c = max_idx.size(2) * max_idx.size(3); + const index_t max_idx_size = channels_ * max_idx_size_c; // For each ROI R = [batch_index x1 y1 x2 y2]: max pool over R for (int n = 0; n < num_rois; ++n) { // Increment ROI data pointer const Dtype *bottom_rois_n = bottom_rois + n * bbox.size(1); Dtype *top_data_n = top_data + n * out_size; - Dtype *argmax_data_n = argmax_data + n * max_idx_size; - int roi_batch_ind = bottom_rois_n[0]; + index_t *argmax_data_n = argmax_data + n * max_idx_size; int roi_start_w = std::round(bottom_rois_n[1] * spatial_scale_); int roi_start_h = std::round(bottom_rois_n[2] * spatial_scale_); int roi_end_w = std::round(bottom_rois_n[3] * spatial_scale_); int roi_end_h = std::round(bottom_rois_n[4] * spatial_scale_); - assert(roi_batch_ind >= 0); - assert(static_cast(roi_batch_ind) < data.size(0) /* batch size */); + + int roi_batch_ind = static_cast(bottom_rois_n[0]); + bool is_ind_invalid = (roi_batch_ind < 0) || (roi_batch_ind >= batch_size); // force malformed ROIs to be 1 * 1 int roi_height = max(roi_end_h - roi_start_h + 1, 1); @@ -81,14 +82,15 @@ inline void ROIPoolForward(const Tensor &out, const Dtype bin_size_w = static_cast(roi_width) / static_cast(pooled_width_); - const Dtype* batch_data = bottom_data + data_size * roi_batch_ind; + index_t offset_batch_data = data_size * roi_batch_ind; #pragma omp parallel for for (int c = 0; c < channels_; ++c) { // Increment all data pointers - const Dtype* batch_data_c = batch_data + c * data_size_c; + index_t offset_batch_data_c = offset_batch_data + c * data_size_c; + const Dtype* batch_data_c = bottom_data + offset_batch_data_c; Dtype* top_data_c = top_data_n + c * out_size_c; - Dtype* argmax_data_c = argmax_data_n + c * max_idx_size_c; + index_t* argmax_data_c = argmax_data_n + c * max_idx_size_c; for (int ph = 0; ph < pooled_height_; ++ph) { for (int pw = 0; pw < pooled_width_; ++pw) { @@ -111,18 +113,19 @@ inline void ROIPoolForward(const Tensor &out, bool is_empty = (hend <= hstart) || (wend <= wstart); - const int pool_index = ph * pooled_width_ + pw; - if (is_empty) { + const index_t pool_index = ph * pooled_width_ + pw; + if (is_empty || is_ind_invalid) { top_data_c[pool_index] = 0; argmax_data_c[pool_index] = -1; + continue; } for (int h = hstart; h < hend; ++h) { for (int w = wstart; w < wend; ++w) { - const int index = h * width_ + w; + const index_t index = h * width_ + w; if (batch_data_c[index] > top_data_c[pool_index]) { top_data_c[pool_index] = batch_data_c[index]; - argmax_data_c[pool_index] = index; + argmax_data_c[pool_index] = offset_batch_data_c + index; } } } @@ -137,91 +140,18 @@ template inline void ROIPoolBackwardAcc(const Tensor &in_grad, const Tensor &out_grad, const Tensor &bbox, - const Tensor &max_idx, + const Tensor &max_idx, const float spatial_scale_) { const Dtype *top_diff = out_grad.dptr_; - const Dtype *bottom_rois = bbox.dptr_; Dtype *bottom_diff = in_grad.dptr_; - Dtype *argmax_data = max_idx.dptr_; - - const int batch_size_ = in_grad.size(0); - const int channels_ = in_grad.size(1); - const int height_ = in_grad.size(2); - const int width_ = in_grad.size(3); - const int pooled_height_ = out_grad.size(2); - const int pooled_width_ = out_grad.size(3); - - const int num_rois = bbox.size(0); - - for (int b = 0; b < batch_size_; ++b) { - for (int c = 0; c < channels_; ++c) { - for (int h = 0; h < height_; ++h) { - for (int w = 0; w < width_; ++w) { - int offset_bottom_diff = (b * channels_ + c) * height_ * width_; - offset_bottom_diff += h * width_ + w; - - Dtype gradient = 0; - // Accumulate gradient over all ROIs that pooled this element - for (int roi_n = 0; roi_n < num_rois; ++roi_n) { - const Dtype* offset_bottom_rois = bottom_rois + roi_n * 5; - int roi_batch_ind = offset_bottom_rois[0]; - assert(roi_batch_ind >= 0); - assert(roi_batch_ind < batch_size_); - if (b != roi_batch_ind) { - continue; - } + index_t *argmax_data = max_idx.dptr_; - int roi_start_w = std::round(offset_bottom_rois[1] * spatial_scale_); - int roi_start_h = std::round(offset_bottom_rois[2] * spatial_scale_); - int roi_end_w = std::round(offset_bottom_rois[3] * spatial_scale_); - int roi_end_h = std::round(offset_bottom_rois[4] * spatial_scale_); + const index_t count = out_grad.shape_.Size(); - bool in_roi = (w >= roi_start_w && w <= roi_end_w && - h >= roi_start_h && h <= roi_end_h); - if (!in_roi) { - continue; - } - - // force malformed ROIs to be 1 * 1 - int roi_height = max(roi_end_h - roi_start_h + 1, 1); - int roi_width = max(roi_end_w - roi_start_w + 1, 1); - const Dtype bin_size_h = static_cast(roi_height) - / static_cast(pooled_height_); - const Dtype bin_size_w = static_cast(roi_width) - / static_cast(pooled_width_); - - // compute pooled regions correspond to original (h, w) point - int phstart = static_cast(floor(static_cast(h - roi_start_h) - / bin_size_h)); - int pwstart = static_cast(floor(static_cast(w - roi_start_w) - / bin_size_w)); - int phend = static_cast(ceil(static_cast(h - roi_start_h + 1) - / bin_size_h)); - int pwend = static_cast(ceil(static_cast(w - roi_start_w + 1) - / bin_size_w)); - - // clip to boundaries of pooled region - phstart = min(max(phstart, 0), pooled_height_); - phend = min(max(phend, 0), pooled_height_); - pwstart = min(max(pwstart, 0), pooled_width_); - pwend = min(max(pwend, 0), pooled_width_); - - // accumulate over gradients in pooled regions - int offset = (roi_n * channels_ + c) * pooled_height_ * pooled_width_; - const Dtype* offset_top_diff = top_diff + offset; - const Dtype* offset_argmax_data = argmax_data + offset; - for (int ph = phstart; ph < phend; ++ph) { - for (int pw = pwstart; pw < pwend; ++pw) { - const int pooled_index = ph * pooled_width_ + pw; - if (static_cast(offset_argmax_data[pooled_index]) == h * width_ + w) { - gradient += offset_top_diff[pooled_index]; - } - } - } - } - bottom_diff[offset_bottom_diff] += gradient; - } - } + for (int index = 0; index < count; ++index) { + index_t max_idx = argmax_data[index]; + if (max_idx >= 0) { + bottom_diff[max_idx] += top_diff[index]; } } diff --git a/src/operator/roi_pooling.cu b/src/operator/roi_pooling.cu index 9ea99b309aaf..b4013a672711 100644 --- a/src/operator/roi_pooling.cu +++ b/src/operator/roi_pooling.cu @@ -34,12 +34,12 @@ namespace cuda { template __global__ void ROIPoolForwardKernel(const int count, const Dtype* bottom_data, - const float spatial_scale, const int channels, - const int height, const int width, + const float spatial_scale, const int batch_size, + const int channels, const int height, const int width, const int pooled_height, const int pooled_width, const Dtype* bottom_rois, Dtype* top_data, - Dtype* argmax_data) { - for (int index = (blockIdx.x + blockIdx.y * gridDim.x) * blockDim.x + threadIdx.x; + index_t* argmax_data) { + for (index_t index = (blockIdx.x + blockIdx.y * gridDim.x) * blockDim.x + threadIdx.x; index < count; index += blockDim.x * gridDim.x * gridDim.y) { // (n, c, ph, pw) is an element in the pooled output @@ -49,11 +49,11 @@ __global__ void ROIPoolForwardKernel(const int count, const Dtype* bottom_data, int n = index / pooled_width / pooled_height / channels; bottom_rois += n * 5; - int roi_batch_ind = bottom_rois[0]; + int roi_batch_ind = static_cast(bottom_rois[0]); - if (roi_batch_ind < 0) { + if (roi_batch_ind < 0 || roi_batch_ind >= batch_size) { top_data[index] = 0; - argmax_data[index] = 0; + argmax_data[index] = -1; continue; } @@ -89,19 +89,20 @@ __global__ void ROIPoolForwardKernel(const int count, const Dtype* bottom_data, // Define an empty pooling region to be zero Dtype maxval = is_empty ? 0 : -FLT_MAX; // If nothing is pooled, argmax = -1 causes nothing to be backprop'd - int maxidx = -1; - bottom_data += (roi_batch_ind * channels + c) * height * width; + index_t maxidx = -1; + index_t offset_bottom_data = (roi_batch_ind * channels + c) * height * width; + bottom_data += offset_bottom_data; for (int h = hstart; h < hend; ++h) { for (int w = wstart; w < wend; ++w) { - int bottom_index = h * width + w; + index_t bottom_index = h * width + w; if (bottom_data[bottom_index] > maxval) { maxval = bottom_data[bottom_index]; - maxidx = bottom_index; + maxidx = offset_bottom_data + bottom_index; } } } top_data[index] = maxval; - argmax_data[index] = (Dtype)maxidx; + argmax_data[index] = maxidx; } } @@ -109,13 +110,14 @@ template inline void ROIPoolForward(const Tensor &out, const Tensor &data, const Tensor &bbox, - const Tensor &max_idx, + const Tensor &max_idx, const float spatial_scale) { const Dtype *bottom_data = data.dptr_; const Dtype *bottom_rois = bbox.dptr_; Dtype *top_data = out.dptr_; - Dtype *argmax_data = max_idx.dptr_; - const int count = out.shape_.Size(); + index_t *argmax_data = max_idx.dptr_; + const index_t count = out.shape_.Size(); + const int batch_size = data.size(0); const int channels = data.size(1); const int height = data.size(2); const int width = data.size(3); @@ -127,84 +129,21 @@ inline void ROIPoolForward(const Tensor &out, CheckLaunchParam(dimGrid, dimBlock, "ROIPooling Forward"); cudaStream_t stream = Stream::GetStream(out.stream_); ROIPoolForwardKernel<<>>( - count, bottom_data, spatial_scale, channels, height, width, + count, bottom_data, spatial_scale, batch_size, channels, height, width, pooled_height, pooled_width, bottom_rois, top_data, argmax_data); MSHADOW_CUDA_POST_KERNEL_CHECK(ROIPoolForwardKernel); } template __global__ void ROIPoolBackwardAccKernel(const int count, const Dtype* top_diff, - const Dtype* argmax_data, const int num_rois, - const float spatial_scale, const int channels, - const int height, const int width, - const int pooled_height, const int pooled_width, - Dtype* bottom_diff, const Dtype* bottom_rois) { - for (int index = (blockIdx.x + blockIdx.y * gridDim.x) * blockDim.x + threadIdx.x; + const index_t* argmax_data, Dtype* bottom_diff) { + for (index_t index = (blockIdx.x + blockIdx.y * gridDim.x) * blockDim.x + threadIdx.x; index < count; index += blockDim.x * gridDim.x * gridDim.y) { - // (n, c, h, w) coords in bottom data - int w = index % width; - int h = (index / width) % height; - int c = (index / width / height) % channels; - int n = index / width / height / channels; - - Dtype gradient = 0; - // Accumulate gradient over all ROIs that pooled this element - for (int roi_n = 0; roi_n < num_rois; ++roi_n) { - const Dtype* offset_bottom_rois = bottom_rois + roi_n * 5; - int roi_batch_ind = offset_bottom_rois[0]; - // Skip if ROI's batch index doesn't match n - if (n != roi_batch_ind) { - continue; - } - - int roi_start_w = round(offset_bottom_rois[1] * spatial_scale); - int roi_start_h = round(offset_bottom_rois[2] * spatial_scale); - int roi_end_w = round(offset_bottom_rois[3] * spatial_scale); - int roi_end_h = round(offset_bottom_rois[4] * spatial_scale); - - // Skip if ROI doesn't include (h, w) - const bool in_roi = (w >= roi_start_w && w <= roi_end_w && - h >= roi_start_h && h <= roi_end_h); - if (!in_roi) { - continue; - } - - int offset = (roi_n * channels + c) * pooled_height * pooled_width; - const Dtype* offset_top_diff = top_diff + offset; - const Dtype* offset_argmax_data = argmax_data + offset; - - // Compute feasible set of pooled units that could have pooled - // this bottom unit - - // Force malformed ROIs to be 1x1 - int roi_width = max(roi_end_w - roi_start_w + 1, 1); - int roi_height = max(roi_end_h - roi_start_h + 1, 1); - - Dtype bin_size_h = static_cast(roi_height) - / static_cast(pooled_height); - Dtype bin_size_w = static_cast(roi_width) - / static_cast(pooled_width); - - int phstart = floor(static_cast(h - roi_start_h) / bin_size_h); - int phend = ceil(static_cast(h - roi_start_h + 1) / bin_size_h); - int pwstart = floor(static_cast(w - roi_start_w) / bin_size_w); - int pwend = ceil(static_cast(w - roi_start_w + 1) / bin_size_w); - - phstart = min(max(phstart, 0), pooled_height); - phend = min(max(phend, 0), pooled_height); - pwstart = min(max(pwstart, 0), pooled_width); - pwend = min(max(pwend, 0), pooled_width); - - for (int ph = phstart; ph < phend; ++ph) { - for (int pw = pwstart; pw < pwend; ++pw) { - if (static_cast(offset_argmax_data[ph * pooled_width + pw]) == (h * width + w)) { - gradient += offset_top_diff[ph * pooled_width + pw]; - } - } - } + index_t max_idx = argmax_data[index]; + if (max_idx >= 0) { + atomicAdd(&bottom_diff[max_idx], top_diff[index]); } - bottom_diff[index] += gradient; } } @@ -212,27 +151,19 @@ template inline void ROIPoolBackwardAcc(const Tensor &in_grad, const Tensor &out_grad, const Tensor &bbox, - const Tensor &max_idx, + const Tensor &max_idx, const float spatial_scale) { const Dtype *top_diff = out_grad.dptr_; - const Dtype *bottom_rois = bbox.dptr_; Dtype *bottom_diff = in_grad.dptr_; - Dtype *argmax_data = max_idx.dptr_; - const int count = in_grad.shape_.Size(); - const int num_rois = bbox.size(0); - const int channels = in_grad.size(1); - const int height = in_grad.size(2); - const int width = in_grad.size(3); - const int pooled_height = out_grad.size(2); - const int pooled_width = out_grad.size(3); + index_t *argmax_data = max_idx.dptr_; + const index_t count = out_grad.shape_.Size(); const int gridSize = (count + kMaxThreadsPerBlock - 1) / kMaxThreadsPerBlock; dim3 dimGrid(kMaxGridDim, (gridSize + kMaxGridDim - 1) / kMaxGridDim); dim3 dimBlock(kMaxThreadsPerBlock); CheckLaunchParam(dimGrid, dimBlock, "ROIPooling Backward"); cudaStream_t stream = Stream::GetStream(in_grad.stream_); ROIPoolBackwardAccKernel<<>>( - count, top_diff, argmax_data, num_rois, spatial_scale, channels, height, width, - pooled_height, pooled_width, bottom_diff, bottom_rois); + count, top_diff, argmax_data, bottom_diff); MSHADOW_CUDA_POST_KERNEL_CHECK(ROIPoolBackwardAccKernel); } @@ -242,7 +173,7 @@ template inline void ROIPoolForward(const Tensor &out, const Tensor &data, const Tensor &bbox, - const Tensor &max_idx, + const Tensor &max_idx, const float spatial_scale) { cuda::ROIPoolForward(out, data, bbox, max_idx, spatial_scale); } @@ -251,7 +182,7 @@ template inline void ROIPoolBackwardAcc(const Tensor &in_grad, const Tensor &out_grad, const Tensor &bbox, - const Tensor &max_idx, + const Tensor &max_idx, const float spatial_scale) { cuda::ROIPoolBackwardAcc(in_grad, out_grad, bbox, max_idx, spatial_scale); } From cbb6f7fd6e297c17fd267b29174a4ed29100c757 Mon Sep 17 00:00:00 2001 From: Ruslan Baratov Date: Sat, 13 Jul 2019 03:26:01 +0300 Subject: [PATCH 064/813] Docs: Fix misprints (#15505) * Docs: Fix 'bahavior' -> 'behavior' * Docs: Fix 'the the' -> 'the' * retrigger CI * retrigger CI --- NEWS.md | 4 ++-- R-package/R/viz.graph.R | 4 ++-- contrib/clojure-package/README.md | 2 +- docs/api/python/gluon/gluon.md | 2 +- docs/install/windows_setup.md | 2 +- docs/tutorials/mkldnn/MKLDNN_README.md | 2 +- example/gan/CGAN_mnist_R/README.md | 2 +- include/mxnet/ndarray.h | 2 +- perl-package/AI-MXNet/lib/AI/MXNet/Gluon.pm | 2 +- perl-package/AI-MXNet/lib/AI/MXNet/Module/Base.pm | 2 +- python/mxnet/contrib/onnx/onnx2mx/_op_translations.py | 2 +- python/mxnet/gluon/data/dataloader.py | 2 +- python/mxnet/module/base_module.py | 2 +- python/mxnet/module/python_module.py | 2 +- .../src/main/scala/org/apache/mxnet/module/BaseModule.scala | 2 +- .../mxnetexamples/javaapi/infer/objectdetector/README.md | 2 +- .../apache/mxnetexamples/javaapi/infer/predictor/README.md | 2 +- .../org/apache/mxnetexamples/infer/objectdetector/README.md | 2 +- src/operator/tensor/diag_op-inl.h | 2 +- src/operator/tensor/matrix_op.cc | 2 +- tools/staticbuild/README.md | 4 ++-- 21 files changed, 24 insertions(+), 24 deletions(-) diff --git a/NEWS.md b/NEWS.md index 59f8de831c50..ee8a73cc4bc8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -678,8 +678,8 @@ This fixes an buffer overflow detected by ASAN. This PR adds or updates the docs for the infer_range feature. Clarifies the param in the C op docs - Clarifies the param in the the Scala symbol docs - Adds the param for the the Scala ndarray docs + Clarifies the param in the Scala symbol docs + Adds the param for the Scala ndarray docs Adds the param for the Python symbol docs Adds the param for the Python ndarray docs diff --git a/R-package/R/viz.graph.R b/R-package/R/viz.graph.R index 58043721feb6..ab876afdfa1e 100644 --- a/R-package/R/viz.graph.R +++ b/R-package/R/viz.graph.R @@ -34,7 +34,7 @@ #' @param symbol a \code{string} representing the symbol of a model. #' @param shape a \code{numeric} representing the input dimensions to the symbol. #' @param direction a \code{string} representing the direction of the graph, either TD or LR. -#' @param type a \code{string} representing the rendering engine of the the graph, either graph or vis. +#' @param type a \code{string} representing the rendering engine of the graph, either graph or vis. #' @param graph.width.px a \code{numeric} representing the size (width) of the graph. In pixels #' @param graph.height.px a \code{numeric} representing the size (height) of the graph. In pixels #' @@ -169,4 +169,4 @@ graph.viz <- function(symbol, shape=NULL, direction="TD", type="graph", graph.wi return(graph_render) } -globalVariables(c("color", "shape", "label", "id", ".", "op")) \ No newline at end of file +globalVariables(c("color", "shape", "label", "id", ".", "op")) diff --git a/contrib/clojure-package/README.md b/contrib/clojure-package/README.md index 7566ade66ce8..7bb417edf3d3 100644 --- a/contrib/clojure-package/README.md +++ b/contrib/clojure-package/README.md @@ -237,7 +237,7 @@ If you are having trouble getting started or have a question, feel free to reach There are quite a few examples in the examples directory. To use. `lein install` in the main project -`cd` in the the example project of interest +`cd` in the example project of interest There are README is every directory outlining instructions. diff --git a/docs/api/python/gluon/gluon.md b/docs/api/python/gluon/gluon.md index c063a71d43ea..19e462e8a773 100644 --- a/docs/api/python/gluon/gluon.md +++ b/docs/api/python/gluon/gluon.md @@ -28,7 +28,7 @@ The Gluon package is a high-level interface for MXNet designed to be easy to use, while keeping most of the flexibility of a low level API. Gluon supports both imperative and symbolic programming, making it easy to train complex models imperatively in Python and then deploy with a symbolic graph in C++ and Scala. -Based on the the [Gluon API specification](https://github.com/gluon-api/gluon-api), the Gluon API in Apache MXNet provides a clear, concise, and simple API for deep learning. It makes it easy to prototype, build, and train deep learning models without sacrificing training speed. +Based on the [Gluon API specification](https://github.com/gluon-api/gluon-api), the Gluon API in Apache MXNet provides a clear, concise, and simple API for deep learning. It makes it easy to prototype, build, and train deep learning models without sacrificing training speed. **Advantages** diff --git a/docs/install/windows_setup.md b/docs/install/windows_setup.md index 4fb4e565bf8c..8a3b1f3b099e 100644 --- a/docs/install/windows_setup.md +++ b/docs/install/windows_setup.md @@ -183,7 +183,7 @@ cd C:\incubator-mxnet\build cmake -G "Visual Studio 15 2017 Win64" -T cuda=9.2,host=x64 -DUSE_CUDA=1 -DUSE_CUDNN=1 -DUSE_NVRTC=1 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_BLAS=open -DUSE_LAPACK=1 -DUSE_DIST_KVSTORE=0 -DCUDA_ARCH_LIST=Common -DCUDA_TOOLSET=9.2 -DCUDNN_INCLUDE=C:\cuda\include -DCUDNN_LIBRARY=C:\cuda\lib\x64\cudnn.lib "C:\incubator-mxnet" ``` * Make sure you set the environment variables correctly (OpenBLAS_HOME, OpenCV_DIR) and change the version of the Visual studio 2017 to v14.11 before enter above command. -6. After the CMake successfully completed, compile the the MXNet source code by using following command: +6. After the CMake successfully completed, compile the MXNet source code by using following command: ``` msbuild mxnet.sln /p:Configuration=Release;Platform=x64 /maxcpucount ``` diff --git a/docs/tutorials/mkldnn/MKLDNN_README.md b/docs/tutorials/mkldnn/MKLDNN_README.md index 516b2b3e796a..c9e940fdeb3e 100644 --- a/docs/tutorials/mkldnn/MKLDNN_README.md +++ b/docs/tutorials/mkldnn/MKLDNN_README.md @@ -135,7 +135,7 @@ command: >"C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl\bin\mklvars.bat" intel64 >cmake -G "Visual Studio 14 Win64" .. -DUSE_CUDA=0 -DUSE_CUDNN=0 -DUSE_NVRTC=0 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_PROFILER=1 -DUSE_BLAS=mkl -DUSE_LAPACK=1 -DUSE_DIST_KVSTORE=0 -DCUDA_ARCH_NAME=All -DUSE_MKLDNN=1 -DCMAKE_BUILD_TYPE=Release -DMKL_ROOT="C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl" ``` -4. After the CMake successfully completed, in Visual Studio, open the solution file ```.sln``` and compile it, or compile the the MXNet source code by using following command: +4. After the CMake successfully completed, in Visual Studio, open the solution file ```.sln``` and compile it, or compile the MXNet source code by using following command: ```r msbuild mxnet.sln /p:Configuration=Release;Platform=x64 /maxcpucount ``` diff --git a/example/gan/CGAN_mnist_R/README.md b/example/gan/CGAN_mnist_R/README.md index bf0bb08b1147..99d2e1c1f63b 100644 --- a/example/gan/CGAN_mnist_R/README.md +++ b/example/gan/CGAN_mnist_R/README.md @@ -94,7 +94,7 @@ update_args_D<- updater_D(weight = exec_D$ref.arg.arrays, grad = exec_D$ref.grad mx.exec.update.arg.arrays(exec_D, update_args_D, skip.null=TRUE) ``` -The generator loss comes from the backpropagation of the the discriminator loss into its generated output. By faking the generator labels to be real samples into the discriminator, the discriminator back-propagated loss provides the generator with the information on how to best adapt its parameters to trick the discriminator into believing the fake samples are real. +The generator loss comes from the backpropagation of the discriminator loss into its generated output. By faking the generator labels to be real samples into the discriminator, the discriminator back-propagated loss provides the generator with the information on how to best adapt its parameters to trick the discriminator into believing the fake samples are real. This requires to backpropagate the gradients up to the input data of the discriminator (whereas this input gradient is typically ignored in vanilla feedforward network). diff --git a/include/mxnet/ndarray.h b/include/mxnet/ndarray.h index 34e891e0f336..428245b56d0e 100644 --- a/include/mxnet/ndarray.h +++ b/include/mxnet/ndarray.h @@ -197,7 +197,7 @@ class NDArray { } /* * This indicates whether an array is a view of another array (created by - * reshape or slice). If an array is a view and the the data is stored in + * reshape or slice). If an array is a view and the data is stored in * MKLDNN format, we need to convert the data to the default format when * data in the view is accessed. */ diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon.pm index 657be74c5a6d..1badacb7ece1 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Gluon.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Gluon.pm @@ -41,7 +41,7 @@ sub model_zoo { require AI::MXNet::Gluon::ModelZoo; 'AI::MXNet::Gluon::ModelZoo' AI::MXNet::Gluon supports both imperative and symbolic programming, making it easy to train complex models imperatively in Perl. - Based on the the Gluon API specification, + Based on the Gluon API specification, the Gluon API in Apache MXNet provides a clear, concise, and simple API for deep learning. It makes it easy to prototype, build, and train deep learning models without sacrificing training speed. diff --git a/perl-package/AI-MXNet/lib/AI/MXNet/Module/Base.pm b/perl-package/AI-MXNet/lib/AI/MXNet/Module/Base.pm index 542cf498f495..6b572f4cceb5 100644 --- a/perl-package/AI-MXNet/lib/AI/MXNet/Module/Base.pm +++ b/perl-package/AI-MXNet/lib/AI/MXNet/Module/Base.pm @@ -684,7 +684,7 @@ method output_shapes() { confess("NotImplemented") } =head2 get_params - The parameters, these are potentially a copies of the the actual parameters used + The parameters, these are potentially a copies of the actual parameters used to do computation on the device. Returns diff --git a/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py b/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py index 24bb727e6401..734b438581a5 100644 --- a/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py +++ b/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py @@ -24,7 +24,7 @@ # Method definitions for the callable objects mapped in the import_helper module def identity(attrs, inputs, proto_obj): - """Returns the identity function of the the input.""" + """Returns the identity function of the input.""" return 'identity', attrs, inputs def random_uniform(attrs, inputs, proto_obj): diff --git a/python/mxnet/gluon/data/dataloader.py b/python/mxnet/gluon/data/dataloader.py index accd968cc9df..65fd7d84b5bb 100644 --- a/python/mxnet/gluon/data/dataloader.py +++ b/python/mxnet/gluon/data/dataloader.py @@ -268,7 +268,7 @@ def shutdown(self): if not self._shutdown: # send shutdown signal to the fetcher and join data queue first # Remark: loop_fetcher need to be joined prior to the workers. - # otherwise, the the fetcher may fail at getting data + # otherwise, the fetcher may fail at getting data self._data_queue.put((None, None)) self._fetcher.join() # send shutdown signal to all worker processes diff --git a/python/mxnet/module/base_module.py b/python/mxnet/module/base_module.py index 754e369b4e63..0d5515f172b8 100644 --- a/python/mxnet/module/base_module.py +++ b/python/mxnet/module/base_module.py @@ -619,7 +619,7 @@ def output_shapes(self): # Parameters of a module ################################################################################ def get_params(self): - """Gets parameters, those are potentially copies of the the actual parameters used + """Gets parameters, those are potentially copies of the actual parameters used to do computation on the device. Returns diff --git a/python/mxnet/module/python_module.py b/python/mxnet/module/python_module.py index df1648e82694..a5d6f157e6a5 100644 --- a/python/mxnet/module/python_module.py +++ b/python/mxnet/module/python_module.py @@ -94,7 +94,7 @@ def output_shapes(self): # Parameters of a module ################################################################################ def get_params(self): - """Gets parameters, those are potentially copies of the the actual parameters used + """Gets parameters, those are potentially copies of the actual parameters used to do computation on the device. Subclass should override this method if contains parameters. diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/module/BaseModule.scala b/scala-package/core/src/main/scala/org/apache/mxnet/module/BaseModule.scala index 7fbdae5b3e21..f2f4c20b8833 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/module/BaseModule.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/module/BaseModule.scala @@ -306,7 +306,7 @@ abstract class BaseModule { // Parameters of a module /** - * Get parameters, those are potentially copies of the the actual parameters used + * Get parameters, those are potentially copies of the actual parameters used * to do computation on the device. * @return `(argParams, auxParams)`, a pair of dictionary of name to value mapping. */ diff --git a/scala-package/examples/src/main/java/org/apache/mxnetexamples/javaapi/infer/objectdetector/README.md b/scala-package/examples/src/main/java/org/apache/mxnetexamples/javaapi/infer/objectdetector/README.md index 4c4512f152c8..f6f8b674c08e 100644 --- a/scala-package/examples/src/main/java/org/apache/mxnetexamples/javaapi/infer/objectdetector/README.md +++ b/scala-package/examples/src/main/java/org/apache/mxnetexamples/javaapi/infer/objectdetector/README.md @@ -103,7 +103,7 @@ Class: dog Probabilties: 0.8226818 (Coord:,83.82353,179.13998,206.63783,476.7875) ``` -the outputs come from the the input image, with top3 predictions picked. +the outputs come from the input image, with top3 predictions picked. ## Infer API Details diff --git a/scala-package/examples/src/main/java/org/apache/mxnetexamples/javaapi/infer/predictor/README.md b/scala-package/examples/src/main/java/org/apache/mxnetexamples/javaapi/infer/predictor/README.md index 09189cb83268..02e09f39cdcf 100644 --- a/scala-package/examples/src/main/java/org/apache/mxnetexamples/javaapi/infer/predictor/README.md +++ b/scala-package/examples/src/main/java/org/apache/mxnetexamples/javaapi/infer/predictor/README.md @@ -75,4 +75,4 @@ Probability : 0.30337515 Class : n02123159 tiger cat Predict with NDArray Probability : 0.30337515 Class : n02123159 tiger cat ``` -the outputs come from the the input image, with top1 predictions picked. \ No newline at end of file +the outputs come from the input image, with top1 predictions picked. diff --git a/scala-package/examples/src/main/scala/org/apache/mxnetexamples/infer/objectdetector/README.md b/scala-package/examples/src/main/scala/org/apache/mxnetexamples/infer/objectdetector/README.md index e5d3bbee0490..25f040e36e37 100644 --- a/scala-package/examples/src/main/scala/org/apache/mxnetexamples/infer/objectdetector/README.md +++ b/scala-package/examples/src/main/scala/org/apache/mxnetexamples/infer/objectdetector/README.md @@ -103,7 +103,7 @@ Class: dog Probabilties: 0.8226818 (Coord:,83.82353,179.13998,206.63783,476.7875) ``` -the outputs come from the the input image, with top3 predictions picked. +the outputs come from the input image, with top3 predictions picked. ## Infer API Details diff --git a/src/operator/tensor/diag_op-inl.h b/src/operator/tensor/diag_op-inl.h index c95c1ce414f2..73eb4e1daf54 100644 --- a/src/operator/tensor/diag_op-inl.h +++ b/src/operator/tensor/diag_op-inl.h @@ -71,7 +71,7 @@ inline mxnet::TShape DiagShapeImpl(const mxnet::TShape& ishape, const int k, int32_t x1 = CheckAxis(axis1, ishape.ndim()); int32_t x2 = CheckAxis(axis2, ishape.ndim()); - CHECK_NE(x1, x2) << "axis1 and axis2 cannot refer to the the same axis " << x1; + CHECK_NE(x1, x2) << "axis1 and axis2 cannot refer to the same axis " << x1; auto h = ishape[x1]; auto w = ishape[x2]; diff --git a/src/operator/tensor/matrix_op.cc b/src/operator/tensor/matrix_op.cc index c2bcb29193a7..e78050aec009 100644 --- a/src/operator/tensor/matrix_op.cc +++ b/src/operator/tensor/matrix_op.cc @@ -270,7 +270,7 @@ NNVM_REGISTER_OP(Flatten) For an input array with shape ``(d1, d2, ..., dk)``, `flatten` operation reshapes the input array into an output array of shape ``(d1, d2*...*dk)``. -Note that the bahavior of this function is different from numpy.ndarray.flatten, +Note that the behavior of this function is different from numpy.ndarray.flatten, which behaves similar to mxnet.ndarray.reshape((-1,)). Example:: diff --git a/tools/staticbuild/README.md b/tools/staticbuild/README.md index bfccbab184bd..b861cc3308c6 100644 --- a/tools/staticbuild/README.md +++ b/tools/staticbuild/README.md @@ -34,7 +34,7 @@ This would build the mxnet package based on MKLDNN and and pypi configuration se As the result, users would have a complete static dependencies in `/staticdeps` in the root folder as well as a static-linked `libmxnet.so` file lives in `lib`. You can build your language binding by using the `libmxnet.so`. ## `build_lib.sh` -This script clones the most up-to-date master and builds the MXNet backend with a static library. In order to run the static library, you must set the the following environment variables: +This script clones the most up-to-date master and builds the MXNet backend with a static library. In order to run the static library, you must set the following environment variables: - `DEPS_PATH` Path to your static dependencies - `STATIC_BUILD_TARGET` Either `pip` or `maven` as your publish platform @@ -46,4 +46,4 @@ It is not recommended to run this file alone since there are a bunch of variable After running this script, you would have everything you need ready in the `/lib` folder. ## `build_wheel.sh` -This script builds the python package. It also runs a sanity test. \ No newline at end of file +This script builds the python package. It also runs a sanity test. From d677d1aefd30e58ff9265abd6f66617d10e2771b Mon Sep 17 00:00:00 2001 From: Anirudh Subramanian Date: Fri, 12 Jul 2019 19:21:42 -0700 Subject: [PATCH 065/813] FP16 Support for C Predict API (#15245) * Upgrade archive utility and add back FC improvement This reverts commit 65434886f6caa7210ed3ff39cd4e950c023d8328. * Change permissions for Ubuntu AR * Extract and cd into binutils dir * Allow AR path to be chosen by user * Add AR path to build * Fix AR paths * Revert AR flag in makefile * Build from source doc updated * Commit for C Predict API * Add FP16 predict support * Add Test Predictor fixes * Add test for predictor * Cleanup fixes * Fixes * Add support for forward pass only for gpu * Fix Reshape, move test to gpu * Add monitor callback for C Predict API * Add tests, default dtype and set_monitor_callback * Improve error * Fix c_str_array --- amalgamation/python/mxnet_predict.py | 131 ++++++++++++++++++++++++- include/mxnet/c_predict_api.h | 65 +++++++++++++ src/c_api/c_predict_api.cc | 140 ++++++++++++++++++++++++++- tests/python/gpu/test_predictor.py | 128 ++++++++++++++++++++++++ 4 files changed, 454 insertions(+), 10 deletions(-) create mode 100644 tests/python/gpu/test_predictor.py diff --git a/amalgamation/python/mxnet_predict.py b/amalgamation/python/mxnet_predict.py index a91d3849b0d2..48e3cd4a5145 100644 --- a/amalgamation/python/mxnet_predict.py +++ b/amalgamation/python/mxnet_predict.py @@ -25,17 +25,77 @@ import os import sys +from array import array import ctypes import logging import numpy as np +# pylint: disable= no-member +_DTYPE_NP_TO_MX = { + None: -1, + np.float32: 0, + np.float64: 1, + np.float16: 2, + np.uint8: 3, + np.int32: 4, + np.int8: 5, + np.int64: 6, +} + +_DTYPE_MX_TO_NP = { + -1: None, + 0: np.float32, + 1: np.float64, + 2: np.float16, + 3: np.uint8, + 4: np.int32, + 5: np.int8, + 6: np.int64, +} + __all__ = ["Predictor", "load_ndarray_file"] if sys.version_info[0] == 3: py_str = lambda x: x.decode('utf-8') + + def c_str_array(strings): + """Create ctypes const char ** from a list of Python strings. + + Parameters + ---------- + strings : list of string + Python strings. + + Returns + ------- + (ctypes.c_char_p * len(strings)) + A const char ** pointer that can be passed to C API. + """ + arr = (ctypes.c_char_p * len(strings))() + arr[:] = [s.encode('utf-8') for s in strings] + return arr + + else: py_str = lambda x: x + def c_str_array(strings): + """Create ctypes const char ** from a list of Python strings. + + Parameters + ---------- + strings : list of strings + Python strings. + + Returns + ------- + (ctypes.c_char_p * len(strings)) + A const char ** pointer that can be passed to C API. + """ + arr = (ctypes.c_char_p * len(strings))() + arr[:] = strings + return arr + def c_str(string): """"Convert a python string to C string.""" @@ -48,6 +108,11 @@ def c_array(ctype, values): """Create ctypes array from a python array.""" return (ctype * len(values))(*values) +def c_array_buf(ctype, buf): + """Create ctypes array from a Python buffer.""" + return (ctype * len(buf)).from_buffer(buf) + + def _find_lib_path(): """Find mxnet library.""" @@ -87,9 +152,18 @@ def _check_call(ret): if ret != 0: raise RuntimeError(py_str(_LIB.MXGetLastError())) + +def _monitor_callback_wrapper(callback): + """A wrapper for the user-defined handle.""" + def callback_handle(name, array, _): + """ ctypes function """ + callback(name, array) + return callback_handle + _LIB = _load_lib() # type definitions mx_uint = ctypes.c_uint +mx_int = ctypes.c_int mx_float = ctypes.c_float mx_float_p = ctypes.POINTER(mx_float) PredictorHandle = ctypes.c_void_p @@ -116,10 +190,13 @@ class Predictor(object): dev_id : int, optional The device id of the predictor. + + type_dict : Dict of str->numpy.dtype + Input type dictionary, name->dtype """ def __init__(self, symbol_file, param_raw_bytes, input_shapes, - dev_type="cpu", dev_id=0): + dev_type="cpu", dev_id=0, type_dict=None): dev_type = devstr2type[dev_type] indptr = [0] sdata = [] @@ -133,7 +210,26 @@ def __init__(self, symbol_file, handle = PredictorHandle() param_raw_bytes = bytearray(param_raw_bytes) ptr = (ctypes.c_char * len(param_raw_bytes)).from_buffer(param_raw_bytes) - _check_call(_LIB.MXPredCreate( + + # data types + num_provided_arg_types = 0 + # provided type argument names + provided_arg_type_names = ctypes.POINTER(ctypes.c_char_p)() + # provided types + provided_arg_type_data = ctypes.POINTER(mx_uint)() + if type_dict is not None: + provided_arg_type_names = [] + provided_arg_type_data = [] + for k, v in type_dict.items(): + v = np.dtype(v).type + if v in _DTYPE_NP_TO_MX: + provided_arg_type_names.append(k) + provided_arg_type_data.append(_DTYPE_NP_TO_MX[v]) + num_provided_arg_types = mx_uint(len(provided_arg_type_names)) + provided_arg_type_names = c_str_array(provided_arg_type_names) + provided_arg_type_data = c_array_buf(ctypes.c_int, array('i', provided_arg_type_data)) + + _check_call(_LIB.MXPredCreateEx( c_str(symbol_file), ptr, len(param_raw_bytes), ctypes.c_int(dev_type), ctypes.c_int(dev_id), @@ -141,7 +237,11 @@ def __init__(self, symbol_file, c_array(ctypes.c_char_p, keys), c_array(mx_uint, indptr), c_array(mx_uint, sdata), + num_provided_arg_types, + provided_arg_type_names, + provided_arg_type_data, ctypes.byref(handle))) + self.type_dict = type_dict self.handle = handle def __del__(self): @@ -160,10 +260,18 @@ def forward(self, **kwargs): >>> predictor.forward(data=mydata) >>> out = predictor.get_output(0) """ + if self.type_dict and len(self.type_dict) != len(kwargs.items()): + raise ValueError("number of kwargs should be same as len of type_dict" \ + "Please check your forward pass inputs" \ + "or type_dict passed to Predictor instantiation") + for k, v in kwargs.items(): if not isinstance(v, np.ndarray): raise ValueError("Expect numpy ndarray as input") - v = np.asarray(v, dtype=np.float32, order='C') + if self.type_dict and k in self.type_dict: + v = np.asarray(v, dtype=self.type_dict[k], order='C') + else: + v = np.asarray(v, dtype=np.float32, order='C') _check_call(_LIB.MXPredSetInput( self.handle, c_str(k), v.ctypes.data_as(mx_float_p), @@ -218,18 +326,30 @@ def get_output(self, index): """ pdata = ctypes.POINTER(mx_uint)() ndim = mx_uint() + out_type = mx_int() _check_call(_LIB.MXPredGetOutputShape( self.handle, index, ctypes.byref(pdata), ctypes.byref(ndim))) + _check_call(_LIB.MXPredGetOutputType( + self.handle, index, + ctypes.byref(out_type))) shape = tuple(pdata[:ndim.value]) - data = np.empty(shape, dtype=np.float32) + data = np.empty(shape, dtype=_DTYPE_MX_TO_NP[out_type.value]) _check_call(_LIB.MXPredGetOutput( self.handle, mx_uint(index), data.ctypes.data_as(mx_float_p), mx_uint(data.size))) return data + def set_monitor_callback(self, callback, monitor_all=False): + cb_type = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_void_p, ctypes.c_void_p) + self._monitor_callback = cb_type(_monitor_callback_wrapper(callback)) + _check_call(_LIB.MXPredSetMonitorCallback(self.handle, + self._monitor_callback, + None, + ctypes.c_int(monitor_all))) + def load_ndarray_file(nd_bytes): """Load ndarray file and return as list of numpy array. @@ -273,4 +393,5 @@ def load_ndarray_file(nd_bytes): if len(keys) == 0 or len(keys[0]) == 0: return arrs else: - return {keys[i] : arrs[i] for i in range(len(keys))} + return {keys[i] : arrs[i] for i in range(len(keys)) + } diff --git a/include/mxnet/c_predict_api.h b/include/mxnet/c_predict_api.h index ecbbf8dfc819..18bec625f05f 100644 --- a/include/mxnet/c_predict_api.h +++ b/include/mxnet/c_predict_api.h @@ -49,6 +49,12 @@ typedef float mx_float; typedef void *PredictorHandle; /*! \brief handle to NDArray list */ typedef void *NDListHandle; +/*! \brief handle to NDArray */ +typedef void *NDArrayHandle; +/*! \brief callback used for add monitoring to nodes in the graph */ +typedef void (*PredMonitorCallback)(const char*, + NDArrayHandle, + void*); /*! * \brief Get the last error happeneed. @@ -85,6 +91,44 @@ MXNET_DLL int MXPredCreate(const char* symbol_json_str, const mx_uint* input_shape_data, PredictorHandle* out); +/*! + * \brief create a predictor + * \param symbol_json_str The JSON string of the symbol. + * \param param_bytes The in-memory raw bytes of parameter ndarray file. + * \param param_size The size of parameter ndarray file. + * \param dev_type The device type, 1: cpu, 2: gpu + * \param dev_id The device id of the predictor. + * \param num_input_nodes Number of input nodes to the net. + * For feedforward net, this is 1. + * \param input_keys The name of the input argument. + * For feedforward net, this is {"data"} + * \param input_shape_indptr Index pointer of shapes of each input node. + * The length of this array = num_input_nodes + 1. + * For feedforward net that takes 4 dimensional input, this is {0, 4}. + * \param input_shape_data A flattened data of shapes of each input node. + * For feedforward net that takes 4 dimensional input, this is the shape data. + * \param num_provided_arg_dtypes + * The length of provided_arg_dtypes. + * \param provided_arg_dtype_names + * The provided_arg_dtype_names the names of args for which dtypes are provided. + * \param provided_arg_dtypes + * The provided_arg_dtypes the dtype provided + * \param out The created predictor handle. + * \return 0 when success, -1 when failure. + */ +MXNET_DLL int MXPredCreateEx(const char* symbol_json_str, + const void* param_bytes, + int param_size, + int dev_type, int dev_id, + const mx_uint num_input_nodes, + const char** input_keys, + const mx_uint* input_shape_indptr, + const mx_uint* input_shape_data, + const mx_uint num_provided_arg_dtypes, + const char** provided_arg_dtype_names, + const int* provided_arg_dtypes, + PredictorHandle* out); + /*! * \brief create a predictor wich customized outputs * \param symbol_json_str The JSON string of the symbol. @@ -186,6 +230,18 @@ MXNET_DLL int MXPredGetOutputShape(PredictorHandle handle, mx_uint index, mx_uint** shape_data, mx_uint* shape_ndim); + +/*! + * \brief Get the dtype of output node. + * The returned data type is only valid before next call to MXPred function. + * \param handle The handle of the predictor. + * \param out_index The index of the output node, set to 0 if there is only one output. + * \param out_dtype The dtype of the output node + */ +MXNET_DLL int MXPredGetOutputType(PredictorHandle handle, + mx_uint out_index, + int* out_dtype); + /*! * \brief Set the input data of predictor. * \param handle The predictor handle. @@ -269,6 +325,15 @@ MXNET_DLL int MXNDListGet(NDListHandle handle, const mx_float** out_data, const mx_uint** out_shape, mx_uint* out_ndim); + +/*! + * \brief set a call back to notify the completion of operation and allow for + * additional monitoring + */ +MXNET_DLL int MXPredSetMonitorCallback(PredictorHandle handle, + PredMonitorCallback callback, + void* callback_handle, + bool monitor_all); /*! * \brief Free a MXAPINDList * \param handle The handle of the MXAPINDList. diff --git a/src/c_api/c_predict_api.cc b/src/c_api/c_predict_api.cc index 7de23ef935ef..b371fd044dc5 100644 --- a/src/c_api/c_predict_api.cc +++ b/src/c_api/c_predict_api.cc @@ -47,6 +47,9 @@ struct MXAPIPredictor { std::vector aux_arrays; // output shapes mxnet::ShapeVector out_shapes; + // output types + nnvm::DTypeVector out_dtypes; + // uint32_t buffer for output shapes std::vector out_shapes_buffer; // key to arguments @@ -88,7 +91,7 @@ int _CreatePartialOut(const char* symbol_json_str, const void* param_bytes, int param_size, int dev_type, int dev_id, - mx_uint num_input_nodes, + const mx_uint num_input_nodes, const char** input_keys, const mx_uint* input_shape_indptr, const mx_uint* input_shape_data, @@ -97,6 +100,9 @@ int _CreatePartialOut(const char* symbol_json_str, // This is used for parallel inference. int num_threads, bool lazy, + const mx_uint num_provided_arg_dtypes, + const char** provided_arg_dtype_names, + const int* provided_arg_dtypes, PredictorHandle* out) { using nnvm::Symbol; @@ -135,6 +141,7 @@ int _CreatePartialOut(const char* symbol_json_str, // load the parameters std::unordered_map arg_params, aux_params; + std::unordered_map arg_types, aux_types; { std::unordered_set arg_names, aux_names; std::vector arg_names_vec = sym.ListInputNames(Symbol::kReadOnlyArgs); @@ -156,12 +163,23 @@ int _CreatePartialOut(const char* symbol_json_str, std::string name(names[i].c_str() + 4); if (aux_names.count(name) != 0) { aux_params[name] = data[i]; + aux_types[name] = data[i].dtype(); } } if (!strncmp(names[i].c_str(), "arg:", 4)) { std::string name(names[i].c_str() + 4); if (arg_names.count(name) != 0) { arg_params[name] = data[i]; + arg_types[name] = data[i].dtype(); + } + } + } + + if (num_provided_arg_dtypes > 0) { + for (mx_uint i = 0; i < num_provided_arg_dtypes; ++i) { + if (aux_types.count(provided_arg_dtype_names[i]) == 0 && + arg_types.count(provided_arg_dtype_names[i]) == 0) { + arg_types[provided_arg_dtype_names[i]] = provided_arg_dtypes[i]; } } } @@ -179,6 +197,7 @@ int _CreatePartialOut(const char* symbol_json_str, mxnet::ShapeVector out_shapes(sym.ListOutputNames().size()); mxnet::ShapeVector aux_shapes(aux_names.size()); mxnet::ShapeVector arg_shapes; + nnvm::DTypeVector result_arg_types, result_out_types, result_aux_types; std::unordered_map key2arg; for (size_t i = 0; i < arg_names.size(); ++i) { std::string key = arg_names[i]; @@ -187,6 +206,7 @@ int _CreatePartialOut(const char* symbol_json_str, try { mxnet::ShapeVector in_shapes; + nnvm::DTypeVector in_types; for (std::string key : sym.ListInputNames(Symbol::kAll)) { if (known_shape.count(key) != 0) { in_shapes.push_back(known_shape[key]); @@ -194,14 +214,38 @@ int _CreatePartialOut(const char* symbol_json_str, in_shapes.emplace_back(); } } + + for (std::string key : sym.ListInputNames(Symbol::kAll)) { + if (arg_types.count(key) != 0) { + in_types.push_back(arg_types[key]); + } else if (aux_types.count(key) != 0) { + in_types.push_back(aux_types[key]); + } else { + // if key not in arg_types or aux_types set to FP32 + in_types.push_back(0); + } + } nnvm::Graph g; g.outputs = sym.outputs; g = mxnet::exec::InferShape(std::move(g), std::move(in_shapes), "__shape__"); + g = mxnet::exec::InferType(std::move(g), std::move(in_types), "__dtype__"); bool infer_complete = (g.GetAttr("shape_num_unknown_nodes") == 0); + // This is tricky for AMP Use case, for example, with only weights input types + // cannot be inferred in AMP. Thus for AMP converted model type_dict will be + // required + bool infer_type_complete = (g.GetAttr("dtype_num_unknown_nodes") == 0); CHECK(infer_complete) << "The shape information of is not enough to get the shapes"; + CHECK(infer_type_complete) + << "The type information is not enough, please provide input arg_types " + "with provided_arg_dtype_names and provided_arg_dtypes." + "If using amalgamation python frontend you can use type_dict in Predictor API" + "to provide this information"; CopyAttr(g.indexed_graph(), g.GetAttr("shape"), &arg_shapes, &out_shapes, &aux_shapes); + CopyAttr(g.indexed_graph(), + g.GetAttr("dtype"), + &result_arg_types, &result_out_types, &result_aux_types); } catch (const mxnet::op::InferShapeError &err) { throw dmlc::Error(err.msg); } @@ -210,19 +254,31 @@ int _CreatePartialOut(const char* symbol_json_str, std::vector arg_arrays, aux_arrays; for (size_t i = 0; i < arg_shapes.size(); ++i) { - NDArray nd = NDArray(arg_shapes[i], ctx); + NDArray nd; + if (result_arg_types[i] != -1) { + nd = NDArray(arg_shapes[i], ctx, false, result_arg_types[i]); + } else { + nd = NDArray(arg_shapes[i], ctx); + } if (arg_params.count(arg_names[i]) != 0) { CopyFromTo(arg_params[arg_names[i]], &nd); } arg_arrays.push_back(nd); } + for (size_t i = 0; i < aux_shapes.size(); ++i) { - NDArray nd = NDArray(aux_shapes[i], ctx); + NDArray nd; + if (result_aux_types[i] != -1) { + nd = NDArray(aux_shapes[i], ctx, false, result_aux_types[i]); + } else { + nd = NDArray(aux_shapes[i], ctx); + } if (aux_params.count(aux_names[i]) != 0) { CopyFromTo(aux_params[aux_names[i]], &nd); } aux_arrays.push_back(nd); } + // bind for (int i = 0; i < num_threads; i++) { std::unique_ptr ret(new MXAPIPredictor()); @@ -232,6 +288,7 @@ int _CreatePartialOut(const char* symbol_json_str, ret->arg_arrays = arg_arrays; ret->aux_arrays = aux_arrays; ret->out_shapes = out_shapes; + ret->out_dtypes = result_out_types; if (!lazy) { std::map ctx_map; @@ -272,6 +329,9 @@ int MXPredCreatePartialOut(const char* symbol_json_str, output_keys, 1, false, + 0, + nullptr, + nullptr, out); } @@ -295,9 +355,44 @@ int MXPredCreate(const char* symbol_json_str, input_shape_indptr, input_shape_data, 0, - NULL, + nullptr, + 1, + false, + 0, + nullptr, + nullptr, + out); +} + +int MXPredCreateEx(const char* symbol_json_str, + const void* param_bytes, + int param_size, + int dev_type, int dev_id, + mx_uint num_input_nodes, + const char** input_keys, + const mx_uint* input_shape_indptr, + const mx_uint* input_shape_data, + const mx_uint num_provided_arg_dtypes, + const char** provided_arg_dtype_names, + const int* provided_arg_dtypes, + PredictorHandle* out) { + return _CreatePartialOut( + symbol_json_str, + param_bytes, + param_size, + dev_type, + dev_id, + num_input_nodes, + input_keys, + input_shape_indptr, + input_shape_data, + 0, + nullptr, 1, false, + num_provided_arg_dtypes, + provided_arg_dtype_names, + provided_arg_dtypes, out); } @@ -330,9 +425,12 @@ int MXPredCreateMultiThread(const char* symbol_json_str, input_shape_indptr, input_shape_data, 0, - NULL, + nullptr, num_threads, true, + 0, + nullptr, + nullptr, out); } @@ -421,6 +519,7 @@ int MXPredReshape(mx_uint num_input_nodes, p->exec.get())); ret->out_shapes = out_shapes; ret->out_arrays = ret->exec->outputs(); + ret->out_dtypes = p->out_dtypes; } *out = ret.release(); API_END(); @@ -444,6 +543,21 @@ int MXPredGetOutputShape(PredictorHandle handle, API_END(); } +int MXPredGetOutputType(PredictorHandle handle, + mx_uint out_index, + int* out_dtype) { + MXAPIPredictor* p = static_cast(handle); + API_BEGIN(); + CHECK_LT(out_index, p->out_arrays.size()) + << "Index exceed number of outputs, provided out_index should be less than " + << p->out_arrays.size(); + + const int s = p->out_dtypes[out_index]; + CHECK_GE(s, 0); + out_dtype[out_index] = s; + API_END(); +} + int MXPredSetInput(PredictorHandle handle, const char* key, const mx_float* data, @@ -543,6 +657,22 @@ int MXNDListGet(NDListHandle handle, API_END(); } +int MXPredSetMonitorCallback(PredictorHandle handle, + PredMonitorCallback callback, + void* callback_handle, + bool monitor_all) { + MXAPIPredictor* p = static_cast(handle); + API_BEGIN(); + PredMonitorCallback callback_temp = callback; + void* callback_handle_temp = callback_handle; + std::function clbk + = [callback_temp, callback_handle_temp](const char* name, void* handle) { + callback_temp(name, handle, callback_handle_temp); + }; + p->exec->SetMonitorCallback(clbk, monitor_all); + API_END(); +} + int MXNDListFree(NDListHandle handle) { API_BEGIN(); delete static_cast(handle); diff --git a/tests/python/gpu/test_predictor.py b/tests/python/gpu/test_predictor.py new file mode 100644 index 000000000000..4838a76c7cb1 --- /dev/null +++ b/tests/python/gpu/test_predictor.py @@ -0,0 +1,128 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import print_function +import sys, os +curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) +sys.path.append(os.path.join(curr_path, "../../../amalgamation/python/")) +from mxnet_predict import Predictor, load_ndarray_file + +import ctypes +import numpy as np +import mxnet as mx +import mxnet.ndarray as nd +from mxnet.ndarray import NDArray +from mxnet import gluon +from mxnet.test_utils import assert_almost_equal, download_model +from mxnet.contrib.amp import amp +from mxnet.base import NDArrayHandle, py_str +sys.path.insert(0, os.path.join(curr_path, '../unittest')) +from common import setup_module, with_seed, teardown + +@with_seed() +def test_predictor_with_dtype(): + prefix = 'test_predictor_simple_dense' + symbol_file = "%s-symbol.json" % prefix + param_file = "%s-0000.params" % prefix + + input1 = np.random.uniform(size=(1, 3)) + input1 = input1.astype(np.float16) + + block = mx.gluon.nn.HybridSequential() + block.add(mx.gluon.nn.Dense(7)) + block.add(mx.gluon.nn.Dense(3)) + block.cast(np.float16) + block.hybridize() + block.initialize(ctx=mx.gpu(0)) + tmp = mx.nd.array(input1, dtype=np.float16, ctx=mx.gpu(0)) + out1 = block.forward(tmp) + block.export(prefix) + + predictor = Predictor(open(symbol_file, "r").read(), + open(param_file, "rb").read(), + {"data": input1.shape}, + dev_type="gpu", + dev_id=0, + type_dict={"data": input1.dtype}) + predictor.forward(data=input1) + predictor_out1 = predictor.get_output(0) + + assert_almost_equal(out1.asnumpy(), predictor_out1, rtol=1e-5, atol=1e-6) + +def compare_module_cpredict(result_sym, result_arg_params, result_aux_params, monitor_callback=False): + # Dummmy inputs + input1 = np.ones((1, 3, 224, 224)) + input1 = input1.astype(np.float32) + nd_dict = {} + def pred_mon_callback(name, arr): + nd_dict[name] = arr + mod = mx.mod.Module(result_sym, data_names=["data"], label_names=["softmax_label"], context=mx.gpu()) + mod.bind(data_shapes=[['data', (1, 3, 224, 224)]], label_shapes=[['softmax_label', (1,)]], for_training=False) + mod.set_params(result_arg_params, result_aux_params) + mod.forward(mx.io.DataBatch(data=[mx.nd.array(input1, ctx=mx.gpu())], + label=[mx.nd.ones((1,), ctx=mx.gpu())])) + prefix = "test_predictor_amp" + mod.save_checkpoint(prefix, 0, remove_amp_cast=False) + sym_file = "{}-symbol.json".format(prefix) + params_file = "{}-0000.params".format(prefix) + predictor = Predictor(open(sym_file, "r").read(), + open(params_file, "rb").read(), + {'data': (1, 3, 224, 224), + 'softmax_label': (1,)}, + dev_type="gpu", + dev_id=0) + if monitor_callback: + predictor.set_monitor_callback(pred_mon_callback, monitor_all=True) + predictor.forward(data=input1, softmax_label=mx.nd.ones((1,)).asnumpy()) + predictor_out1 = predictor.get_output(0) + if monitor_callback: + assert len(nd_dict) > 0, "Callback not called" + assert_almost_equal(mod.get_outputs()[0].asnumpy(), predictor_out1, atol=1e-1, rtol=1e-1) + + +@with_seed() +def test_predictor_amp(): + dir_path = os.path.dirname(os.path.realpath(__file__)) + model_path = os.path.join(dir_path, 'model') + if not os.path.isdir(model_path): + os.mkdir(model_path) + prefix, epoch = download_model("imagenet1k-resnet-18", dst_dir=model_path) + + sym, arg_params, aux_params = mx.model.load_checkpoint(prefix, epoch) + + + # Convert model to mixed precision model, params in FP32 + result_sym, result_arg_params, result_aux_params = amp.convert_model(sym, + arg_params, + aux_params, + target_dtype="float16", + target_dtype_ops=["Convolution"]) + compare_module_cpredict(result_sym, result_arg_params, result_aux_params) + + # Convert model to mixed precision model, params in FP16 + result_sym, result_arg_params, result_aux_params = amp.convert_model(sym, + arg_params, + aux_params, + target_dtype="float16", + target_dtype_ops=["Convolution"], + cast_optional_params=True) + compare_module_cpredict(result_sym, result_arg_params, result_aux_params, monitor_callback=True) + + +if __name__ == '__main__': + import nose + nose.runmodule() From b25ec8efb77462bb7b033ff98dc6558c74b68790 Mon Sep 17 00:00:00 2001 From: Pedro Larroy Date: Sat, 13 Jul 2019 22:23:50 -0700 Subject: [PATCH 066/813] Improve diagnose.py, adding build features info and binary library path. (#15499) --- tools/diagnose.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) mode change 100644 => 100755 tools/diagnose.py diff --git a/tools/diagnose.py b/tools/diagnose.py old mode 100644 new mode 100755 index 0f878f918e3c..f3026e635844 --- a/tools/diagnose.py +++ b/tools/diagnose.py @@ -97,6 +97,11 @@ def check_pip(): except ImportError: print('No corresponding pip install for current python.') +def get_build_features_str(): + import mxnet.runtime + features = mxnet.runtime.Features() + return '\n'.join(map(str, list(features.values()))) + def check_mxnet(): print('----------MXNet Info-----------') try: @@ -105,13 +110,20 @@ def check_mxnet(): mx_dir = os.path.dirname(mxnet.__file__) print('Directory :', mx_dir) commit_hash = os.path.join(mx_dir, 'COMMIT_HASH') - with open(commit_hash, 'r') as f: - ch = f.read().strip() - print('Commit Hash :', ch) + if os.path.exists(commit_hash): + with open(commit_hash, 'r') as f: + ch = f.read().strip() + print('Commit Hash :', ch) + else: + print('Commit hash file "{}" not found. Not installed from pre-built package or built from source.'.format(commit_hash)) + print('Library :', mxnet.libinfo.find_lib_path()) + try: + print('Build features:') + print(get_build_features_str()) + except Exception: + print('No runtime build feature info available') except ImportError: print('No MXNet installed.') - except IOError: - print('Hashtag not found. Not installed from pre-built package.') except Exception as e: import traceback if not isinstance(e, IOError): From 38a44db4d752c6dade436438fc53d374f66c49b2 Mon Sep 17 00:00:00 2001 From: Sheng Zha Date: Sat, 13 Jul 2019 23:31:45 -0700 Subject: [PATCH 067/813] update ratcheck for apache-rat 0.13 release (#15417) * update ratcheck for apache-rat 0.13 release * remove minpy folder --- Makefile | 8 ++++---- python/minpy/README.md | 21 --------------------- 2 files changed, 4 insertions(+), 25 deletions(-) delete mode 100644 python/minpy/README.md diff --git a/Makefile b/Makefile index 02a74a067031..a7cf8a56a433 100644 --- a/Makefile +++ b/Makefile @@ -699,15 +699,15 @@ rclean: $(RM) -r R-package/src/image_recordio.h R-package/NAMESPACE R-package/man R-package/R/mxnet_generated.R \ R-package/inst R-package/src/*.o R-package/src/*.so mxnet_*.tar.gz -build/rat/apache-rat/target/apache-rat-0.13-SNAPSHOT.jar: +build/rat/apache-rat/target/apache-rat-0.13.jar: mkdir -p build - svn co http://svn.apache.org/repos/asf/creadur/rat/branches/0.12-release/ build/rat; \ + svn co http://svn.apache.org/repos/asf/creadur/rat/tags/apache-rat-project-0.13/ build/rat; \ cd build/rat; \ mvn -Dmaven.test.skip=true install; -ratcheck: build/rat/apache-rat/target/apache-rat-0.13-SNAPSHOT.jar +ratcheck: build/rat/apache-rat/target/apache-rat-0.13.jar exec 5>&1; \ - RAT_JAR=build/rat/apache-rat/target/apache-rat-0.13-SNAPSHOT.jar; \ + RAT_JAR=build/rat/apache-rat/target/apache-rat-0.13.jar; \ OUTPUT=$(java -jar $(RAT_JAR) -E tests/nightly/apache_rat_license_check/rat-excludes -d .|tee >(cat - >&5)); \ ERROR_MESSAGE="Printing headers for text files without a valid license header"; \ echo "-------Process The Output-------"; \ diff --git a/python/minpy/README.md b/python/minpy/README.md deleted file mode 100644 index 0278ca1c00fb..000000000000 --- a/python/minpy/README.md +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - -MXNet Python Package -==================== - -This is the WIP directory for MinPy project. From 6acf7e6a051e75d9f1cca0ec3c198c38c0f6a3fe Mon Sep 17 00:00:00 2001 From: Kellen Sunderland Date: Sun, 14 Jul 2019 20:02:54 -0700 Subject: [PATCH 068/813] Small typo fixes in batch_norm-inl.h (#15527) * Small typo fixes in batch_norm-inl.h --- src/operator/nn/batch_norm-inl.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/operator/nn/batch_norm-inl.h b/src/operator/nn/batch_norm-inl.h index 70e91c164090..a82771cd4401 100644 --- a/src/operator/nn/batch_norm-inl.h +++ b/src/operator/nn/batch_norm-inl.h @@ -57,7 +57,7 @@ enum BatchNormOpAuxiliary {kMovingMean, kMovingVar}; // aux_states constexpr int DEFAULT_AXIS = 1; } // namespace batchnorm -/*! \brief Parameters for BatchNoram operator */ +/*! \brief Parameters for BatchNorm operator */ struct BatchNormParam : public dmlc::Parameter { double eps; float momentum; @@ -167,7 +167,7 @@ void BatchNormBackwardImpl(mshadow::Stream *stream, * \param out_data array of output data, pointer is used to indicate that this is holder * the space of TBlob in out_data must be pre-allocated with InferShape * \param aux_states Auxiliary states of operator. Normally operator doesn't - * need, epecial case like Batch Norm requires. + * need, special case like Batch Norm requires. * \sa OpReqType, OpContext */ template @@ -413,4 +413,3 @@ extern volatile bool disable_mkl; #endif #endif // MXNET_OPERATOR_NN_BATCH_NORM_INL_H_ - From b88705e7744be1cc22122eb2c5b492e495ab45bb Mon Sep 17 00:00:00 2001 From: Wang Jiajun Date: Mon, 15 Jul 2019 13:21:25 -0500 Subject: [PATCH 069/813] fix heap-use-after-free in scala (#15503) * fix heap-use-after-free * trigger CI --- .../src/main/native/org_apache_mxnet_native_c_api.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scala-package/native/src/main/native/org_apache_mxnet_native_c_api.cc b/scala-package/native/src/main/native/org_apache_mxnet_native_c_api.cc index 387a0b17e252..5c704c9646a2 100644 --- a/scala-package/native/src/main/native/org_apache_mxnet_native_c_api.cc +++ b/scala-package/native/src/main/native/org_apache_mxnet_native_c_api.cc @@ -247,9 +247,6 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxImperativeInvokeEx cParamVals, &cOutStypes); env->ReleaseLongArrayElements(inputs, cInputs, 0); - if (cOutputsGiven) { - env->ReleaseLongArrayElements(outputsGiven, cOutputsGiven, 0); - } // release allocated memory if (numParams > 0) { @@ -284,6 +281,10 @@ JNIEXPORT jint JNICALL Java_org_apache_mxnet_LibInfo_mxImperativeInvokeEx } } + if (cOutputsGiven) { + env->ReleaseLongArrayElements(outputsGiven, cOutputsGiven, 0); + } + return ret; } From 9724ce6821075ff90dc5cca8edc596601dd667db Mon Sep 17 00:00:00 2001 From: Tao Lv Date: Tue, 16 Jul 2019 06:50:44 +0800 Subject: [PATCH 070/813] Avoid memory copy for dropout inference (#15521) * fix dropout for inference --- src/operator/nn/dropout-inl.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/operator/nn/dropout-inl.h b/src/operator/nn/dropout-inl.h index 2a643a266b2b..cb02be6e0338 100644 --- a/src/operator/nn/dropout-inl.h +++ b/src/operator/nn/dropout-inl.h @@ -398,6 +398,8 @@ class DropoutOp { } } } else { + if (req[dropout::kOut] == kWriteInplace) return; + MXNET_ASSIGN_REQ_SWITCH(req[dropout::kOut], Req, { mxnet_op::Kernel, xpu>::Launch( s, out.Size(), out.dptr(), in.dptr()); From 41ecf583bb551c2b43670c0b6db2ef87560b9f5c Mon Sep 17 00:00:00 2001 From: Aaron Markham Date: Mon, 15 Jul 2019 16:04:19 -0700 Subject: [PATCH 071/813] add julia env settings (#15523) --- docs/build_version_doc/build_all_version.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/build_version_doc/build_all_version.sh b/docs/build_version_doc/build_all_version.sh index 3c432bbedfc9..ee467f99e0a4 100755 --- a/docs/build_version_doc/build_all_version.sh +++ b/docs/build_version_doc/build_all_version.sh @@ -46,6 +46,17 @@ set -x # Set OPTS to any Sphinx build options, like -W for "warnings as errors" OPTS= +# Setup environment for Julia docs +export PATH="$1/bin:$PATH" +export MXNET_HOME='/work/mxnet' +export JULIA_DEPOT_PATH='/work/julia-depot' +export INTEGRATION_TEST=1 + +julia -e 'using InteractiveUtils; versioninfo()' +export LD_PRELOAD='/usr/lib/x86_64-linux-gnu/libjemalloc.so' +export LD_LIBRARY_PATH=/work/mxnet/lib:$LD_LIBRARY_PATH + + # $1 is the list of branches/tags to build if [ -z "$1" ] then From 1b725c3c91246f916ec0e626d94b78dda451e631 Mon Sep 17 00:00:00 2001 From: Zixuan Wei Date: Tue, 16 Jul 2019 10:51:17 +0800 Subject: [PATCH 072/813] [MKLDNN] Independent gradients requests check with respect to weights and bias of convolution (#15497) * Independent req[kBias] and req[kWeight] check * Add UT for independent conv gradient requests * Update conv independent grad UT with no_bias enabled * Check req[kWeight] for avoiding unnecessary prim registration * Check `OpReqTpye` in CommitOutput automatically * Lock cudnn autotune for accurate conv output * Ignore independent gradients test on GPU * Trigger CI * Sets a low bar for autotuned cudnn convolution --- src/operator/nn/mkldnn/mkldnn_convolution.cc | 15 ++-- tests/python/unittest/test_operator.py | 82 ++++++++++++++++++++ 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/src/operator/nn/mkldnn/mkldnn_convolution.cc b/src/operator/nn/mkldnn/mkldnn_convolution.cc index 6a91ae0d92a1..9cab2dd0e2b3 100644 --- a/src/operator/nn/mkldnn/mkldnn_convolution.cc +++ b/src/operator/nn/mkldnn/mkldnn_convolution.cc @@ -507,9 +507,9 @@ class MKLDNNConvBackward { mkldnn::primitive::at(*this->weight), *this->in_grad)); } -void SetWeightNewMem(const mkldnn::memory &data, - const mkldnn::memory &out_grad, - const mkldnn::memory &in_grad_weight) { + void SetWeightNewMem(const mkldnn::memory &data, + const mkldnn::memory &out_grad, + const mkldnn::memory &in_grad_weight) { if (this->data == nullptr) this->data = std::shared_ptr(new mkldnn::memory( bwdWeights_pd.src_primitive_desc(), data.get_data_handle())); @@ -649,7 +649,7 @@ void MKLDNNConvolutionBackward(const nnvm::NodeAttrs& attrs, const OpContext &ct MKLDNNStream::Get()->RegisterPrim(convBwd.GetBwdData()); CommitOutput(in_grad[conv::kData], in_grad_mem); } - if (req[conv::kWeight]) { + if (req[conv::kWeight] || req[conv::kBias]) { MKLDNNConvBackward &convBwdWeight = GetConvBwd(attrs, data, weight, bias, out_grad, fwd_pd); if (convBwdWeight.bwdData_pd.diff_dst_primitive_desc() != @@ -662,17 +662,16 @@ void MKLDNNConvolutionBackward(const nnvm::NodeAttrs& attrs, const OpContext &ct in_grad[conv::kWeight], convBwdWeight.bwdWeights_pd.diff_weights_primitive_desc(), req[conv::kWeight]); - mkldnn_output_t in_grad_bias; if (param.no_bias) { convBwdWeight.SetWeightNewMem(*data_mem, *out_grad_mem, - *in_grad_weight.second); + *in_grad_weight.second); MKLDNNStream::Get()->RegisterPrim(convBwdWeight.GetBwdWeights()); } else { - in_grad_bias = CreateMKLDNNMem( + auto in_grad_bias = CreateMKLDNNMem( in_grad[conv::kBias], convBwdWeight.bwdWeights_pd.diff_bias_primitive_desc(), req[conv::kBias]); convBwdWeight.SetWeightNewMem(*data_mem, *out_grad_mem, - *in_grad_weight.second, *in_grad_bias.second); + *in_grad_weight.second, *in_grad_bias.second); MKLDNNStream::Get()->RegisterPrim(convBwdWeight.GetBwdWeights()); CommitOutput(in_grad[conv::kBias], in_grad_bias); } diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index b550139d341b..aeddc7a893df 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -1907,6 +1907,88 @@ def test_depthwise_convolution(): for arr1, arr2 in zip(exe1.outputs + exe1.grad_arrays, exe2.outputs + exe2.grad_arrays): np.testing.assert_allclose(arr1.asnumpy(), arr2.asnumpy(), rtol=1e-3, atol=1e-3) + +@with_seed() +def test_convolution_independent_gradients(): + ctx = default_context() + # set a low bar for autotuned cudnn conv + atol = 1.0e-1 if ctx.device_type == "gpu" else 1.0e-3 + rtol = 1.0e-2 if ctx.device_type == "gpu" else 1.0e-3 + reqs = ["null", "write", "add"] + var_names = ["x", "w", "b"] + dims = [1, 2] + num_bases = [1, 16, 64] + kernel_xs = [3, 5] + stride_xs = [1, 2] + pad_xs = [0, 1] + in_sizes = [7, 32] + no_biases = [True, False] + for dim, num_base, kernel_x, stride_x, pad_x , in_size, no_bias in \ + itertools.product(dims, num_bases, kernel_xs, stride_xs, pad_xs, in_sizes, no_biases): + # Prepare params shape + kernel = (kernel_x,) * dim + stride = (stride_x,) * dim + pad = (pad_x,) * dim + num_filter = num_base + x_shape = (2, num_base) + (in_size,) * dim + w_shape = (num_filter, num_base) + kernel + + # Symbols definition + x = mx.sym.Variable('x') + w = mx.sym.Variable('w') + b = mx.sym.Variable('b') if not no_bias else None + conv = mx.sym.Convolution(x, w, b, num_filter=num_filter, + kernel=kernel, stride=stride, pad=pad, no_bias=no_bias) + + for req_kind in reqs: + # Binding args for conv with possible dependent gradients + base_args = { + 'x': mx.nd.random.normal(shape=x_shape), + 'w': mx.nd.random.normal(shape=w_shape), + 'b': mx.nd.random.normal(shape=(num_filter, )) if not no_bias else None} + args1 = copy.deepcopy(base_args) + grad1 = { + 'x': mx.nd.zeros(shape=x_shape), + 'w': mx.nd.zeros(shape=w_shape), + 'b': mx.nd.zeros(shape=(num_filter, )) if not no_bias else None} + + grad_req1 = [req_kind] * 3 + grad_req1 = dict(zip(var_names, grad_req1)) + + exe1 = conv.bind(ctx, args1, args_grad=grad1, grad_req=grad_req1) + exe1.forward(is_train=True) + exe1.backward(exe1.outputs[0]) + + for x_req, w_req, b_req in itertools.product(reqs, repeat=3): + # Binding args for conv with independent gradients + args2 = copy.deepcopy(base_args) # Deepcopy the same params of `exe1` + grad2 = { + 'x': mx.nd.zeros(shape=x_shape), + 'w': mx.nd.zeros(shape=w_shape), + 'b': mx.nd.zeros(shape=(num_filter, )) if not no_bias else None} + grad_req2 = {"x": x_req, "w": w_req, "b": b_req} + exe2 = conv.bind(ctx, args2, args_grad=grad2, grad_req=grad_req2) + + exe2.forward(is_train=True) + np.testing.assert_allclose(exe1.outputs[0].asnumpy(), + exe2.outputs[0].asnumpy(), rtol=rtol, atol=atol) + + exe2.backward(exe2.outputs[0]) + for var_name in var_names: + if var_name == "b" and no_bias: + continue + if grad_req2[var_name] == "null": + exe2_var_grad = grad2[var_name].asnumpy() + np.testing.assert_allclose(exe2_var_grad, + np.zeros_like(exe2_var_grad), rtol=rtol, atol=atol) + if grad_req2[var_name] != grad_req1[var_name]: + continue + np.testing.assert_allclose(args1[var_name].asnumpy(), + args2[var_name].asnumpy(), rtol=rtol, atol=atol) + np.testing.assert_allclose(grad1[var_name].asnumpy(), + grad2[var_name].asnumpy(), rtol=rtol, atol=atol) + + def gen_broadcast_data(idx): # Manually set test cases binary_op_data_shape = np.array( From 9777717ec0e55d63d6306df21fc92c1635281733 Mon Sep 17 00:00:00 2001 From: Aaron Markham Date: Tue, 16 Jul 2019 06:08:52 -0700 Subject: [PATCH 073/813] website build for julia: fix path to be static (#15554) --- ci/docker/runtime_functions.sh | 10 ++++++++++ docs/build_version_doc/build_all_version.sh | 11 ----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index 596b956efc84..d2b0d6b0e274 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -1210,6 +1210,16 @@ test_ubuntu_cpu_python3() { build_docs() { set -ex pushd . + + # Setup environment for Julia docs + export PATH="/work/julia10/bin:$PATH" + export MXNET_HOME='/work/mxnet' + export JULIA_DEPOT_PATH='/work/julia-depot' + + julia -e 'using InteractiveUtils; versioninfo()' + export LD_PRELOAD='/usr/lib/x86_64-linux-gnu/libjemalloc.so' + export LD_LIBRARY_PATH=/work/mxnet/lib:$LD_LIBRARY_PATH + cd /work/mxnet/docs/build_version_doc # Parameters are set in the Jenkins pipeline: restricted-website-build # $1: the list of branches/tags to build diff --git a/docs/build_version_doc/build_all_version.sh b/docs/build_version_doc/build_all_version.sh index ee467f99e0a4..3c432bbedfc9 100755 --- a/docs/build_version_doc/build_all_version.sh +++ b/docs/build_version_doc/build_all_version.sh @@ -46,17 +46,6 @@ set -x # Set OPTS to any Sphinx build options, like -W for "warnings as errors" OPTS= -# Setup environment for Julia docs -export PATH="$1/bin:$PATH" -export MXNET_HOME='/work/mxnet' -export JULIA_DEPOT_PATH='/work/julia-depot' -export INTEGRATION_TEST=1 - -julia -e 'using InteractiveUtils; versioninfo()' -export LD_PRELOAD='/usr/lib/x86_64-linux-gnu/libjemalloc.so' -export LD_LIBRARY_PATH=/work/mxnet/lib:$LD_LIBRARY_PATH - - # $1 is the list of branches/tags to build if [ -z "$1" ] then From 300cb69fc9dac5e69636fe280f340967d30ed53d Mon Sep 17 00:00:00 2001 From: Paul King Date: Wed, 17 Jul 2019 06:14:45 +1000 Subject: [PATCH 074/813] some minor typos/clarifications (#15538) --- scala-package/README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/scala-package/README.md b/scala-package/README.md index c7d0cecf15ac..c0f32e47f080 100644 --- a/scala-package/README.md +++ b/scala-package/README.md @@ -18,24 +18,24 @@ MXNet Package for Scala/Java ===== -The MXNet Scala/Java Package brings flexible and efficient GPU/CPU computing and state-of-art deep learning to JVM. +The MXNet Scala/Java Package brings flexible and efficient GPU/CPU computing and state-of-art deep learning to the JVM. - It enables you to write seamless tensor/matrix computation with multiple GPUs - in Scala, Java and other languages built on JVM. + in Scala, Java and other JVM languages. - It also enables you to construct and customize the state-of-art deep learning models in JVM languages, and apply them to tasks such as image classification and data science challenges. -- The Scala/Java Inferece APIs provides an easy out of the box solution for loading pre-trained MXNet models and running inference on them. - +- The Scala/Java _Inference API_ provides an easy out of the box solution for performing inference tasks using pre-trained MXNet models. + Pre-Built Maven Packages ------------------------ ### Stable ### The MXNet Scala/Java packages can be easily included in your Maven managed project. -The stable jar files for the packages are available on the [MXNet Maven Package Repository](https://search.maven.org/search?q=g:org.apache.mxnet) +The stable jar files for the packages are available on the [MXNet Maven Package Repository](https://search.maven.org/search?q=g:org.apache.mxnet). Currently we provide packages for Linux (Ubuntu 16.04) (CPU and GPU) and macOS (CPU only). Stable packages for Windows and CentOS will come soon. For now, if you have a CentOS machine, follow the ```Build From Source``` section below. -To add MXNet Scala/Java package to your project, add the dependency as shown below corresponding to your platform, under the ```dependencies``` tag in your project's ```pom.xml``` : +To add the MXNet Scala/Java packages to your project, add the dependency as shown below corresponding to your platform, under the ```dependencies``` tag in your project's ```pom.xml``` : **Linux GPU** @@ -77,7 +77,7 @@ To add MXNet Scala/Java package to your project, add the dependency as shown bel ### Nightly ### -Apart from these, the nightly builds representing the bleeding edge development on Scala/Java packages are also available on the [MXNet Maven Nexus Package Repository](https://repository.apache.org/#nexus-search;gav~org.apache.mxnet~~~~). +Apart from these, the nightly builds representing the bleeding edge development on Scala/Java packages are also available on the [MXNet Maven Nexus Package Repository](https://repository.apache.org/#nexus-search;gav~org.apache.mxnet~~~~). Currently we provide nightly packages for Linux (CPU and GPU) and MacOS (CPU only). The Linux nightly jar files also work on CentOS. Nightly packages for Windows will come soon. Add the following ```repository``` to your project's ```pom.xml``` file : @@ -133,8 +133,8 @@ Also, add the dependency which corresponds to your platform to the ```dependenci Build From Source ----------------- -Checkout the [Installation Guide](http://mxnet.incubator.apache.org/install/index.html) contains instructions to install mxnet package and build it from source. Scala maven build assume you already have a ``lib/libmxnet.so`` file. -If you have built MXNet from source and are looking to setup Scala from that point, you may simply run the following from the MXNet source root, Scala build will detect your platform (OSX/Linux) and libmxnet.so flavor (CPU/GPU): +The [Installation Guide](http://mxnet.incubator.apache.org/install/index.html) contains instructions to install mxnet or build it from source. The Scala/Java package is built from source using Maven. The maven build assumes you already have a ``lib/libmxnet.so`` file. +If you have built MXNet from source and are looking to set up Scala\Java from that point, you may simply run the following from the MXNet source root, the build will detect your platform (OSX/Linux) and libmxnet.so flavor (CPU/GPU): ```bash cd scala-package @@ -156,21 +156,21 @@ mvn -Dsuites=org.apache.mxnet.NDArraySuite integration-test ``` If everything goes well, you will find jars for `assembly`, `core` and `example` modules. -Also it produces the native library in `native/target`, which you can use to cooperate with the `core` module. +Also it produces the native library in `native/target`, which you can use in conjunction with the `core` module. Deploy to repository -------------------- -By default, `maven deploy` will deploy artifacts to local file system, you can file then in: ``scala-package/deploy/target/repo`` folder. +By default, `maven deploy` will deploy artifacts to local file system, you can find them in the ``scala-package/deploy/target/repo`` folder. -For nightly build in CI, a snapshot build will be uploaded to apache repository with follow command: +For nightly builds (typically done by CI), a snapshot build will be uploaded to an apache snapshot repository with the following command: ```bash cd scala-package mvn deploy -Pnightly ``` -Use following command to deploy release build (push artifacts to apache staging repository): +Use the following command when performing a release (pushes artifacts to an apache staging repository): ```bash cd scala-package @@ -208,7 +208,7 @@ Caused by: java.lang.ClassNotFoundException: org.apache.mxnet.NDArray at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ``` -Please make sure your $CLASSPATH is able to find `mxnet-full_scala_version-INTERNAL.jar`. +Please make sure your $CLASSPATH contains `mxnet-full_scala_version-INTERNAL.jar`. - To set up the Scala Project using IntelliJ IDE on macOS follow the instructions [here](https://mxnet.incubator.apache.org/tutorials/scala/mxnet_scala_on_intellij.html). - Several examples on using the Scala APIs are provided in the [Scala Examples Folder](https://github.com/apache/incubator-mxnet/tree/master/scala-package/examples/) From 4d07d78e1aff36f603231568bfff88242d073756 Mon Sep 17 00:00:00 2001 From: Chaitanya Prakash Bapat Date: Tue, 16 Jul 2019 15:54:54 -0700 Subject: [PATCH 075/813] broadcast axis is alias to broadcast axes; doc fix (#15546) --- src/operator/tensor/broadcast_reduce_op_value.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/operator/tensor/broadcast_reduce_op_value.cc b/src/operator/tensor/broadcast_reduce_op_value.cc index 861060514181..7e8a7f05659b 100644 --- a/src/operator/tensor/broadcast_reduce_op_value.cc +++ b/src/operator/tensor/broadcast_reduce_op_value.cc @@ -221,6 +221,8 @@ MXNET_OPERATOR_REGISTER_BROADCAST(broadcast_axis) Broadcasting is allowed on axes with size 1, such as from `(2,1,3,1)` to `(2,8,3,9)`. Elements will be duplicated on the broadcasted axes. +`broadcast_axes` is an alias to the function `broadcast_axis`. + Example:: // given x of shape (1,2,1) From 57d097b18e0c117e2f0be8287bb94858266a9798 Mon Sep 17 00:00:00 2001 From: Sandeep Krishnamurthy Date: Wed, 17 Jul 2019 12:14:29 -0700 Subject: [PATCH 076/813] Add transpose_conv, sorting and searching operator benchmarks to Opperf (#15475) * Add Transpose Convolution op benchmarks * Add sorting and searching ops for opperf * Remove redudant logic, make it uniform * Dummy commit for CI retrigger * Address code review comments --- benchmark/opperf/nd_operations/README.md | 6 -- .../opperf/nd_operations/nn_conv_operators.py | 49 +++++++++++++++- .../sorting_searching_operators.py | 56 +++++++++++++++++++ benchmark/opperf/opperf.py | 12 +++- benchmark/opperf/rules/default_params.py | 7 ++- benchmark/opperf/utils/op_registry_utils.py | 26 ++++++++- 6 files changed, 143 insertions(+), 13 deletions(-) create mode 100644 benchmark/opperf/nd_operations/sorting_searching_operators.py diff --git a/benchmark/opperf/nd_operations/README.md b/benchmark/opperf/nd_operations/README.md index 7aa220c4368a..b98a0d3d0c48 100644 --- a/benchmark/opperf/nd_operations/README.md +++ b/benchmark/opperf/nd_operations/README.md @@ -62,12 +62,10 @@ 40. crop 41. rmsprop_update 43. RNN -44. argmin 45. SoftmaxOutput 46. linalg_extractdiag 47. sgd_mom_update 48. SequenceLast -49. Deconvolution 50. flip 51. SequenceReverse 52. swapaxes @@ -86,18 +84,15 @@ 65. tile 66. space_to_depth 67. gather_nd -68. argsort 69. SequenceMask 70. reshape_like 71. slice_axis 72. stack -73. topk 74. khatri_rao 75. multi_mp_sgd_update 76. linalg_sumlogdiag 77. broadcast_to 78. IdentityAttachKLSparseReg -79. sort 80. SpatialTransformer 81. Concat 82. uniform @@ -129,7 +124,6 @@ 110. split 111. MAERegressionOutput 112. Correlation -113. argmax 114. batch_take 115. L2Normalization 116. broadcast_axis diff --git a/benchmark/opperf/nd_operations/nn_conv_operators.py b/benchmark/opperf/nd_operations/nn_conv_operators.py index e42205f8f56c..973058929b2f 100644 --- a/benchmark/opperf/nd_operations/nn_conv_operators.py +++ b/benchmark/opperf/nd_operations/nn_conv_operators.py @@ -102,7 +102,7 @@ def run_convolution_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=2 dtype=dtype, ctx=ctx, inputs=[{"data": conv_data, - "weight": (64, 3, 3,), + "weight": (64, 3, 3), "bias": (64,), "kernel": (3,), "stride": (1,), @@ -135,3 +135,50 @@ def run_convolution_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=2 # Prepare combined results mx_conv_op_results = merge_map_list(conv1d_benchmark_res + conv2d_benchmark_res) return mx_conv_op_results + + +def run_transpose_convolution_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=10, runs=50): + # Conv1DTranspose Benchmarks + conv1d_transpose_benchmark_res = [] + for conv_data in [(32, 3, 256), (32, 3, 64)]: + conv1d_transpose_benchmark_res += run_performance_test([getattr(MX_OP_MODULE, "Deconvolution")], + run_backward=True, + dtype=dtype, + ctx=ctx, + inputs=[{"data": conv_data, + "weight": (3, 64, 3), + "bias": (64,), + "kernel": (3,), + "stride": (1,), + "dilate": (1,), + "pad": (0,), + "adj": (0,), + "num_filter": 64, + "no_bias": False, + "layout": 'NCW'} + ], + warmup=warmup, + runs=runs) + # Conv2DTranspose Benchmarks + conv2d_transpose_benchmark_res = [] + for conv_data in [(32, 3, 256, 256), (32, 3, 64, 64)]: + conv2d_transpose_benchmark_res += run_performance_test([getattr(MX_OP_MODULE, "Deconvolution")], + run_backward=True, + dtype=dtype, + ctx=ctx, + inputs=[{"data": conv_data, + "weight": (3, 64, 3, 3), + "bias": (64,), + "kernel": (3, 3), + "stride": (1, 1), + "dilate": (1, 1), + "pad": (0, 0), + "num_filter": 64, + "no_bias": False, + "layout": 'NCHW'} + ], + warmup=warmup, + runs=runs) + # Prepare combined results + mx_transpose_conv_op_results = merge_map_list(conv1d_transpose_benchmark_res + conv2d_transpose_benchmark_res) + return mx_transpose_conv_op_results diff --git a/benchmark/opperf/nd_operations/sorting_searching_operators.py b/benchmark/opperf/nd_operations/sorting_searching_operators.py new file mode 100644 index 000000000000..ab98b3feee06 --- /dev/null +++ b/benchmark/opperf/nd_operations/sorting_searching_operators.py @@ -0,0 +1,56 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import mxnet as mx +from benchmark.opperf.utils.benchmark_utils import run_op_benchmarks +from benchmark.opperf.utils.op_registry_utils import get_all_sorting_searching_operators + + +""" Performance benchmark tests for MXNet NDArray Sorting and Searching Operations +1. sort +2. argsort +3. topk +4. argmax +5. argmin +""" + + +def run_sorting_searching_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): + """Runs benchmarks with the given context and precision (dtype)for all the sorting and searching + operators in MXNet. + + Parameters + ---------- + ctx: mx.ctx + Context to run benchmarks + dtype: str, default 'float32' + Precision to use for benchmarks + warmup: int, default 25 + Number of times to run for warmup + runs: int, default 100 + Number of runs to capture benchmark results + + Returns + ------- + Dictionary of results. Key -> Name of the operator, Value -> Benchmark results. + + """ + # Fetch all Random Sampling Operators + mx_sort_search_ops = get_all_sorting_searching_operators() + # Run benchmarks + mx_sort_search_op_results = run_op_benchmarks(mx_sort_search_ops, dtype, ctx, warmup, runs) + return mx_sort_search_op_results diff --git a/benchmark/opperf/opperf.py b/benchmark/opperf/opperf.py index a73db4fdae89..b2258afaaaf7 100755 --- a/benchmark/opperf/opperf.py +++ b/benchmark/opperf/opperf.py @@ -34,13 +34,14 @@ from benchmark.opperf.nd_operations.gemm_operators import run_gemm_operators_benchmarks from benchmark.opperf.nd_operations.random_sampling_operators import run_mx_random_sampling_operators_benchmarks from benchmark.opperf.nd_operations.reduction_operators import run_mx_reduction_operators_benchmarks +from benchmark.opperf.nd_operations.sorting_searching_operators import run_sorting_searching_operators_benchmarks from benchmark.opperf.nd_operations.nn_activation_operators import run_activation_operators_benchmarks from benchmark.opperf.nd_operations.nn_conv_operators import run_pooling_operators_benchmarks, \ - run_convolution_operators_benchmarks + run_convolution_operators_benchmarks, run_transpose_convolution_operators_benchmarks from benchmark.opperf.nd_operations.nn_basic_operators import run_nn_basic_operators_benchmarks from benchmark.opperf.utils.common_utils import merge_map_list, save_to_file -from benchmark.opperf.utils.op_registry_utils import get_operators_with_no_benchmark,\ +from benchmark.opperf.utils.op_registry_utils import get_operators_with_no_benchmark, \ get_current_runtime_features @@ -74,6 +75,9 @@ def run_all_mxnet_operator_benchmarks(ctx=mx.cpu(), dtype='float32'): # Run all Reduction operations benchmarks with default input values mxnet_operator_benchmark_results.append(run_mx_reduction_operators_benchmarks(ctx=ctx, dtype=dtype)) + # Run all Sorting and Searching operations benchmarks with default input values + mxnet_operator_benchmark_results.append(run_sorting_searching_operators_benchmarks(ctx=ctx, dtype=dtype)) + # ************************ MXNET NN OPERATOR BENCHMARKS **************************** # Run all basic NN operations benchmarks with default input values @@ -88,6 +92,9 @@ def run_all_mxnet_operator_benchmarks(ctx=mx.cpu(), dtype='float32'): # Run all Convolution operations benchmarks with default input values mxnet_operator_benchmark_results.append(run_convolution_operators_benchmarks(ctx=ctx, dtype=dtype)) + # Run all Transpose Convolution operations benchmarks with default input values + mxnet_operator_benchmark_results.append(run_transpose_convolution_operators_benchmarks(ctx=ctx, dtype=dtype)) + # ****************************** PREPARE FINAL RESULTS ******************************** final_benchmark_result_map = merge_map_list(mxnet_operator_benchmark_results) return final_benchmark_result_map @@ -148,4 +155,3 @@ def main(): if __name__ == '__main__': sys.exit(main()) - diff --git a/benchmark/opperf/rules/default_params.py b/benchmark/opperf/rules/default_params.py index df6cdae0c48d..2c8f3d436e0d 100644 --- a/benchmark/opperf/rules/default_params.py +++ b/benchmark/opperf/rules/default_params.py @@ -56,7 +56,11 @@ # For reduction operators # NOTE: Data used is DEFAULT_DATA -DEFAULT_AXIS = [(), 0, (0, 1)] +DEFAULT_AXIS_SHAPE = [(), 0, (0, 1)] + +# For sorting and searching operators +# NOTE: Data used is DEFAULT_DATA +DEFAULT_AXIS = [0] # Default Inputs. MXNet Op Param Name to Default Input mapping DEFAULTS_INPUTS = {"data": DEFAULT_DATA, @@ -76,6 +80,7 @@ "p": DEFAULT_P, "k_nd": DEFAULT_K_ND, "p_nd": DEFAULT_P_ND, + "axis_shape": DEFAULT_AXIS_SHAPE, "axis": DEFAULT_AXIS} # These are names of MXNet operator parameters that is of type NDArray. diff --git a/benchmark/opperf/utils/op_registry_utils.py b/benchmark/opperf/utils/op_registry_utils.py index 6509be37f39d..88ea7a16cfe6 100644 --- a/benchmark/opperf/utils/op_registry_utils.py +++ b/benchmark/opperf/utils/op_registry_utils.py @@ -16,10 +16,8 @@ # under the License. """Utilities to interact with MXNet operator registry.""" -import ctypes from operator import itemgetter from mxnet import runtime -from mxnet.base import _LIB, check_call, py_str, OpHandle, c_str, mx_uint import mxnet as mx from benchmark.opperf.rules.default_params import DEFAULTS_INPUTS, MX_OP_MODULE @@ -121,6 +119,10 @@ def prepare_op_inputs(arg_params): arg_values[arg_name] = DEFAULTS_INPUTS[arg_name] elif "float" in arg_type and arg_name + "_float" in DEFAULTS_INPUTS: arg_values[arg_name] = DEFAULTS_INPUTS[arg_name + "_float"] + elif "Shape" in arg_type and arg_name + "_shape" in DEFAULTS_INPUTS: + # This is for cases where in some ops 'axis' is Int in some ops a shape tuple. + # Ex: axis in sum is shape, axis in sort is int. + arg_values[arg_name] = DEFAULTS_INPUTS[arg_name + "_shape"] # Number of different inputs we want to use to test # the operator @@ -240,6 +242,26 @@ def get_all_reduction_operators(): return reduction_mx_operators +def get_all_sorting_searching_operators(): + """Gets all Sorting and Searching operators registered with MXNet. + + Returns + ------- + {"operator_name": {"has_backward", "nd_op_handle", "params"}} + """ + sort_search_ops = ['sort', 'argsort', 'argmax', 'argmin', 'topk'] + + # Get all mxnet operators + mx_operators = _get_all_mxnet_operators() + + # Filter for Sort and search operators + sort_search_mx_operators = {} + for op_name, op_params in mx_operators.items(): + if op_name in sort_search_ops and op_name not in unique_ops: + sort_search_mx_operators[op_name] = mx_operators[op_name] + return sort_search_mx_operators + + def get_operators_with_no_benchmark(operators_with_benchmark): """Gets all MXNet operators with not benchmark. From cb0697fc35abeaac43b0089baa5f0fe6044f8d26 Mon Sep 17 00:00:00 2001 From: Dick Carter Date: Wed, 17 Jul 2019 13:41:58 -0700 Subject: [PATCH 077/813] Bypass cuda/cudnn checks if no driver. (#15551) * Bypass cuda/cudnn checks if no driver. * Perform cuda/cudnn checks only when a gpu context is created. * Fix cpplint. --- docs/faq/env_var.md | 4 +- include/mxnet/base.h | 40 +++++++++++++-- src/base.cc | 95 +++++++++++++++++++++++++++++++++++ src/common/cuda_utils.cc | 105 --------------------------------------- 4 files changed, 132 insertions(+), 112 deletions(-) create mode 100644 src/base.cc delete mode 100644 src/common/cuda_utils.cc diff --git a/docs/faq/env_var.md b/docs/faq/env_var.md index a5ba07098005..f6d48af70089 100644 --- a/docs/faq/env_var.md +++ b/docs/faq/env_var.md @@ -242,12 +242,12 @@ If ctypes is used, it must be `mxnet._ctypes.ndarray.NDArrayBase`. - If set to '0', disallows implicit type conversions to Float16 to use Tensor Cores - If set to '1', allows CUDA ops like RNN and Convolution to use TensorCores even with Float32 input data by using implicit type casting to Float16. Only has an effect if `MXNET_CUDA_ALLOW_TENSOR_CORE` is `1`. -* MXNET_CUDA_VERSION_CHECKING +* MXNET_CUDA_LIB_CHECKING - 0(false) or 1(true) ```(default=1)``` - If set to '0', disallows various runtime checks of the cuda library version and associated warning messages. - If set to '1', permits these checks (e.g. compile vs. link mismatch, old version no longer CI-tested) -* MXNET_CUDNN_VERSION_CHECKING +* MXNET_CUDNN_LIB_CHECKING - 0(false) or 1(true) ```(default=1)``` - If set to '0', disallows various runtime checks of the cuDNN library version and associated warning messages. - If set to '1', permits these checks (e.g. compile vs. link mismatch, old version no longer CI-tested) diff --git a/include/mxnet/base.h b/include/mxnet/base.h index b239cb1f7302..c1e2da7a9db3 100644 --- a/include/mxnet/base.h +++ b/include/mxnet/base.h @@ -191,6 +191,11 @@ struct Context { * \return The number of GPUs that are available. */ inline static int32_t GetGPUCount(); + /*! + * Is the cuda driver installed and visible to the system. + * \return Whether the driver is present. + */ + inline static bool GPUDriverPresent(); /*! * Get the number of streams that a GPU Worker has available to operations. * \return The number of streams that are available. @@ -222,6 +227,14 @@ struct Context { * \return Context */ inline static Context FromString(const std::string& str); + + private: +#if MXNET_USE_CUDA + static void CudaLibChecks(); +#endif +#if MXNET_USE_CUDNN + static void CuDNNLibChecks(); +#endif }; #if MXNET_USE_CUDA @@ -387,17 +400,21 @@ inline bool Context::operator<(const Context &b) const { inline Context Context::Create(DeviceType dev_type, int32_t dev_id) { Context ctx; ctx.dev_type = dev_type; - if (dev_id < 0) { - ctx.dev_id = 0; - if (dev_type & kGPU) { + ctx.dev_id = dev_id < 0 ? 0 : dev_id; + if (dev_type & kGPU) { +#if MXNET_USE_CUDA + CudaLibChecks(); +#endif +#if MXNET_USE_CUDNN + CuDNNLibChecks(); +#endif + if (dev_id < 0) { #if MXNET_USE_CUDA CHECK_EQ(cudaGetDevice(&ctx.dev_id), cudaSuccess); #else LOG(FATAL) << "Please compile with CUDA enabled for cuda features"; #endif } - } else { - ctx.dev_id = dev_id; } return ctx; } @@ -417,8 +434,21 @@ inline Context Context::GPU(int32_t dev_id) { return Create(kGPU, dev_id); } +inline bool Context::GPUDriverPresent() { +#if MXNET_USE_CUDA + int cuda_driver_version = 0; + CHECK_EQ(cudaDriverGetVersion(&cuda_driver_version), cudaSuccess); + return cuda_driver_version > 0; +#else + return false; +#endif +} + inline int32_t Context::GetGPUCount() { #if MXNET_USE_CUDA + if (!GPUDriverPresent()) { + return 0; + } int32_t count; cudaError_t e = cudaGetDeviceCount(&count); if (e == cudaErrorNoDevice) { diff --git a/src/base.cc b/src/base.cc new file mode 100644 index 000000000000..96d66ad1d379 --- /dev/null +++ b/src/base.cc @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file base.cc + * \brief Implementation of base declarations, e.g. context + */ +#include + +namespace mxnet { + +#define UNUSED(x) (void)(x) + +#if MXNET_USE_CUDA == 1 +// The oldest version of cuda used in upstream MXNet CI testing, both for unix and windows. +// Users that have rebuilt MXNet against older versions will we advised with a warning to upgrade +// their systems to match the CI level. Minimally, users should rerun the CI locally. +#if defined(_MSC_VER) +#define MXNET_CI_OLDEST_CUDA_VERSION 9020 +#else +#define MXNET_CI_OLDEST_CUDA_VERSION 10000 +#endif + +void Context::CudaLibChecks() { + // One-time init here will emit a warning if no gpus or gpu driver is seen. + // Also if the user has recompiled their source to a version no longer tested by upstream CI. + static bool cuda_lib_checks_performed = []() { + if (dmlc::GetEnv("MXNET_CUDA_LIB_CHECKING", true)) { + if (!GPUDriverPresent()) + LOG(WARNING) << "Please install cuda driver for GPU use. No cuda driver detected."; + else if (GetGPUCount() == 0) + LOG(WARNING) << "GPU context requested, but no GPUs found."; + else if (CUDA_VERSION < MXNET_CI_OLDEST_CUDA_VERSION) + LOG(WARNING) << "Upgrade advisory: this mxnet has been built against cuda library version " + << CUDA_VERSION << ", which is older than the oldest version tested by CI (" + << MXNET_CI_OLDEST_CUDA_VERSION << "). " + << "Set MXNET_CUDA_LIB_CHECKING=0 to quiet this warning."; + } + return true; + }(); + UNUSED(cuda_lib_checks_performed); +} +#endif // MXNET_USE_CUDA + +#if MXNET_USE_CUDNN == 1 +// The oldest version of CUDNN used in upstream MXNet CI testing, both for unix and windows. +// Users that have rebuilt MXNet against older versions will we advised with a warning to upgrade +// their systems to match the CI level. Minimally, users should rerun the CI locally. +#if defined(_MSC_VER) +#define MXNET_CI_OLDEST_CUDNN_VERSION 7600 +#else +#define MXNET_CI_OLDEST_CUDNN_VERSION 7600 +#endif + +void Context::CuDNNLibChecks() { + // One-time init here will emit a warning if runtime and compile-time cudnn lib versions mismatch. + // Also if the user has recompiled their source to a version no longer tested by upstream CI. + static bool cudnn_lib_checks_performed = []() { + // Don't bother with checks if there are no GPUs visible (e.g. with CUDA_VISIBLE_DEVICES="") + if (dmlc::GetEnv("MXNET_CUDNN_LIB_CHECKING", true) && GetGPUCount() > 0) { + size_t linkedAgainstCudnnVersion = cudnnGetVersion(); + if (linkedAgainstCudnnVersion != CUDNN_VERSION) + LOG(WARNING) << "cuDNN lib mismatch: linked-against version " << linkedAgainstCudnnVersion + << " != compiled-against version " << CUDNN_VERSION << ". " + << "Set MXNET_CUDNN_LIB_CHECKING=0 to quiet this warning."; + if (CUDNN_VERSION < MXNET_CI_OLDEST_CUDNN_VERSION) + LOG(WARNING) << "Upgrade advisory: this mxnet has been built against cuDNN lib version " + << CUDNN_VERSION << ", which is older than the oldest version tested by CI (" + << MXNET_CI_OLDEST_CUDNN_VERSION << "). " + << "Set MXNET_CUDNN_LIB_CHECKING=0 to quiet this warning."; + } + return true; + }(); + UNUSED(cudnn_lib_checks_performed); +} +#endif // MXNET_USE_CUDNN + +} // namespace mxnet diff --git a/src/common/cuda_utils.cc b/src/common/cuda_utils.cc deleted file mode 100644 index 728d1e6681e6..000000000000 --- a/src/common/cuda_utils.cc +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/*! - * Copyright (c) 2019 by Contributors - * \file cuda_utils.cc - * \brief CUDA debugging utilities. - */ - -#include -#include "cuda_utils.h" - -#if MXNET_USE_CUDA == 1 - -namespace mxnet { -namespace common { -namespace cuda { - -// The oldest version of cuda used in upstream MXNet CI testing, both for unix and windows. -// Users that have rebuilt MXNet against older versions will we advised with a warning to upgrade -// their systems to match the CI level. Minimally, users should rerun the CI locally. -#if defined(_MSC_VER) -#define MXNET_CI_OLDEST_CUDA_VERSION 9020 -#else -#define MXNET_CI_OLDEST_CUDA_VERSION 10000 -#endif - -// Dynamic init here will emit a warning if runtime and compile-time cuda lib versions mismatch. -// Also if the user has recompiled their source to a version no longer tested by upstream CI. -bool cuda_version_check_performed = []() { - // Don't bother with checks if there are no GPUs visible (e.g. with CUDA_VISIBLE_DEVICES="") - if (dmlc::GetEnv("MXNET_CUDA_VERSION_CHECKING", true) && Context::GetGPUCount() > 0) { - // Not currently performing a runtime check of linked-against vs. compiled-against - // cuda runtime library, as major.minor must match for libmxnet.so to even load, per: - // https://docs.nvidia.com/deploy/cuda-compatibility/#binary-compatibility - if (CUDA_VERSION < MXNET_CI_OLDEST_CUDA_VERSION) - LOG(WARNING) << "Upgrade advisory: this mxnet has been built against cuda library version " - << CUDA_VERSION << ", which is older than the oldest version tested by CI (" - << MXNET_CI_OLDEST_CUDA_VERSION << "). " - << "Set MXNET_CUDA_VERSION_CHECKING=0 to quiet this warning."; - } - return true; -}(); - -} // namespace cuda -} // namespace common -} // namespace mxnet - -#endif // MXNET_USE_CUDA - -#if MXNET_USE_CUDNN == 1 - -namespace mxnet { -namespace common { -namespace cudnn { - -// The oldest version of CUDNN used in upstream MXNet CI testing, both for unix and windows. -// Users that have rebuilt MXNet against older versions will we advised with a warning to upgrade -// their systems to match the CI level. Minimally, users should rerun the CI locally. -#if defined(_MSC_VER) -#define MXNET_CI_OLDEST_CUDNN_VERSION 7600 -#else -#define MXNET_CI_OLDEST_CUDNN_VERSION 7600 -#endif - -// Dynamic init here will emit a warning if runtime and compile-time cudnn lib versions mismatch. -// Also if the user has recompiled their source to a version no longer tested by upstream CI. -bool cudnn_version_check_performed = []() { - // Don't bother with checks if there are no GPUs visible (e.g. with CUDA_VISIBLE_DEVICES="") - if (dmlc::GetEnv("MXNET_CUDNN_VERSION_CHECKING", true) && Context::GetGPUCount() > 0) { - size_t linkedAgainstCudnnVersion = cudnnGetVersion(); - if (linkedAgainstCudnnVersion != CUDNN_VERSION) - LOG(WARNING) << "cuDNN library mismatch: linked-against version " << linkedAgainstCudnnVersion - << " != compiled-against version " << CUDNN_VERSION << ". " - << "Set MXNET_CUDNN_VERSION_CHECKING=0 to quiet this warning."; - if (CUDNN_VERSION < MXNET_CI_OLDEST_CUDNN_VERSION) - LOG(WARNING) << "Upgrade advisory: this mxnet has been built against cuDNN library version " - << CUDNN_VERSION << ", which is older than the oldest version tested by CI (" - << MXNET_CI_OLDEST_CUDNN_VERSION << "). " - << "Set MXNET_CUDNN_VERSION_CHECKING=0 to quiet this warning."; - } - return true; -}(); - -} // namespace cudnn -} // namespace common -} // namespace mxnet - -#endif // MXNET_USE_CUDNN From 35b54804413624115cec854e14fc35ecf3034412 Mon Sep 17 00:00:00 2001 From: Zhaoqi Zhu Date: Wed, 17 Jul 2019 15:14:57 -0700 Subject: [PATCH 078/813] Utility to help developers debug operators: Tensor Inspector (#15490) * initial. Supports Tensor, TBlob, NDArray * add GPU tensor support * move run context reference to constructors * sanity fix * sanity fix * fix checker bug & add new checker type * add more checker types * fix gpu tensor constructor call * add value dumping funtionality * sanity fix * sanity fix * add dumping support to interactive print * sanity fix * Re-Trigger build * add namespace before Tensor * add more checker types * bug fix * fix comments * change int to size_t * miscellaneous * sanity fix * remove unnecessary inlines * change size_t to index_t * bug fixes and add print value options in value_check() * fix warnings * add negative check * Re-Trigger build * Re-Trigger build * change int to size_t --- src/common/tensor_inspector.h | 815 ++++++++++++++++++++++++++++++++++ 1 file changed, 815 insertions(+) create mode 100644 src/common/tensor_inspector.h diff --git a/src/common/tensor_inspector.h b/src/common/tensor_inspector.h new file mode 100644 index 000000000000..2df94b7fc04f --- /dev/null +++ b/src/common/tensor_inspector.h @@ -0,0 +1,815 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file tensor_inspector.h + * \brief utility to inspect tensor objects + * \author Zhaoqi Zhu + */ + +#ifndef MXNET_COMMON_TENSOR_INSPECTOR_H_ +#define MXNET_COMMON_TENSOR_INSPECTOR_H_ + +#include +#include +#include +#include +#include +#include "../../3rdparty/mshadow/mshadow/base.h" +#include "../../tests/cpp/include/test_util.h" + +namespace mxnet { + +/*! + * \brief this singleton struct mediates individual TensorInspector objects + * so that we can control the global behavior from each of them + */ +struct InspectorManager { + static InspectorManager* get() { + static std::mutex mtx; + static std::unique_ptr im = nullptr; + if (!im) { + std::unique_lock lk(mtx); + if (!im) + im = std::make_unique(); + } + return im.get(); + } + /* !\brief mutex used to lock interactive_print() and check_value() */ + std::mutex mutex_; + /* !\brief skip all interactive prints */ + bool interactive_print_skip_all_ = false; + /* !\brief skip all value checks */ + bool check_value_skip_all_ = false; + /* !\brief visit count for interactive print tags */ + std::unordered_map interactive_print_tag_counter_; + /* !\brief visit count for check value tags */ + std::unordered_map check_value_tag_counter_; + /* !\brief visit count for dump value tags */ + std::unordered_map dump_to_file_tag_counter_; +}; + +/*! + * \brief Enum for building value checkers for TensorInspector::check_value() + */ +enum CheckerType { + NegativeChecker, // check if is negative + PositiveChecker, // check if is positive + ZeroChecker, // check if is zero + NaNChecker, // check if is NaN, will always return false if DType is not a float type + InfChecker, // check if is infinity, will always return false if DType is not a float type + PositiveInfChecker, // check if is positive infinity, + // will always return false if DType is not a float type + NegativeInfChecker, // check if is nagative infinity, + // will always return false if DType is not a float type + FiniteChecker, // check if is finite, will always return false if DType is not a float type + NormalChecker, // check if is neither infinity nor NaN + AbnormalChecker, // chekck if is infinity or nan +}; + +/** + * _______ _____ _ + * |__ __| |_ _| | | + * | | ___ _ __ ___ ___ _ __| | _ __ ___ _ __ ___ ___| |_ ___ _ __ + * | |/ _ \ '_ \/ __|/ _ \| '__| | | '_ \/ __| '_ \ / _ \/ __| __/ _ \| '__| + * | | __/ | | \__ \ (_) | | _| |_| | | \__ \ |_) | __/ (__| || (_) | | + * |_|\___|_| |_|___/\___/|_||_____|_| |_|___/ .__/ \___|\___|\__\___/|_| + * | | + * |_| + */ + +/*! + * \brief This class provides a unified interface to inspect the value of all data types + * including Tensor, TBlob, and NDArray. If the tensor resides on GPU, then it will be + * copied from GPU memory back to CPU memory to be operated on. Internally, all data types + * are stored as a TBlob object tb_. + */ +class TensorInspector { + private: + /*! + * \brief generate the tensor info, including data type and shape + * \tparam DType the data type + * \tparam StreamType the type of the stream object + * \param os stream object to output to + */ + template + void tensor_info_to_string(StreamType* os) { + const int dimension = tb_.ndim(); + *os << "<" << infer_type_string(typeid(DType)) << " Tensor "; + *os << tb_.shape_[0]; + for (int i = 1; i < dimension; ++i) { + *os << 'x' << tb_.shape_[i]; + } + *os << ">" << std::endl; + } + + /*! + * \brief output the tensor info, including data type and shape + * \tparam DType the data type + * \tparam StreamType the type of the stream object + * \param os stream object to output to + * \param shape the shape of the tensor + */ + template + void tensor_info_to_string(StreamType* os, const std::vector& shape) { + const int dimension = shape.size(); + *os << "<" << infer_type_string(typeid(DType)) << " Tensor "; + *os << shape[0]; + for (int i = 1; i < dimension; ++i) { + *os << 'x' << shape[i]; + } + *os << ">" << std::endl; + } + + /*! + * \brief output the tensor in a structured format + * \tparam DType the data type + * \tparam StreamType the type of the stream object + * \param os stream object to output to + */ + template + void to_string_helper(StreamType* os) { +#if MXNET_USE_CUDA + if (tb_.dev_mask() == gpu::kDevMask) { + TensorInspector(test::CAccessAsCPU(ctx_, tb_, false)(), ctx_) + .to_string_helper(os); + return; + } +#endif // MXNET_USE_CUDA + const int dimension = tb_.ndim(); + std::vector offsets; + index_t multiple = 1; + for (int i = dimension - 1; i >= 0; --i) { + multiple *= tb_.shape_[i]; + offsets.push_back(multiple); + } + *os << std::string(dimension, '['); + *os << tb_.dptr()[0]; + for (index_t i = 1; i < static_cast(tb_.shape_.Size()); ++i) { + int n = 0; + for (auto off : offsets) { + n += (i % off == 0); + } + if (n) { + *os << std::string(n, ']') << ", " << std::string(n, '['); + } else { + *os << ", "; + } + *os << tb_.dptr()[i]; + } + *os << std::string(dimension, ']') << std::endl; + tensor_info_to_string(os); + } + + /*! + * \brief output the tensor in a structured format + * \tparam DType the data type + * \tparam StreamType the type of the stream object + * \param os stream object to output to + * \param dptr the data pointer + */ + template + void to_string_helper(StreamType* os, const DType* dptr) { +#if MXNET_USE_CUDA + if (tb_.dev_mask() == gpu::kDevMask) { + TensorInspector(test::CAccessAsCPU(ctx_, tb_, false)(), ctx_) + .to_string_helper(os, dptr); + return; + } +#endif // MXNET_USE_CUDA + *os << *dptr << std::endl; + *os << "<" << typeid(*dptr).name() << ">" << std::endl; + } + + /*! + * \brief output a part of the tensor in a structed format + * \tparam DType the data type + * \tparam StreamType the type of the stream object + * \param os stream object to output to + * \param sub_shape the sub-shape of the desired part of the tensor + * \param offset the position of the first value of the desired part of the tensor + */ + template + void to_string_helper(StreamType* os, const std::vector& sub_shape, index_t offset) { +#if MXNET_USE_CUDA + if (tb_.dev_mask() == gpu::kDevMask) { + TensorInspector(test::CAccessAsCPU(ctx_, tb_, false)(), ctx_) + .to_string_helper(os, sub_shape, offset); + return; + } +#endif // MXNET_USE_CUDA + DType* dptr = tb_.dptr() + offset; + if (sub_shape.size() == 0) { + to_string_helper(os, dptr); + return; + } + const int dimension = sub_shape.size(); + std::vector offsets; + index_t multiple = 1; + for (int i = dimension - 1; i >= 0; --i) { + multiple *= sub_shape[i]; + offsets.push_back(multiple); + } + std::stringstream ss; + *os << std::string(dimension, '['); + *os << dptr[0]; + for (index_t i = 1; i < multiple; ++i) { + int n = 0; + for (auto off : offsets) { + n += (i % off == 0); + } + if (n) { + *os << std::string(n, ']') << ", " << std::string(n, '['); + } else { + *os << ", "; + } + *os << dptr[i]; + } + *os << std::string(dimension, ']') << std::endl; + tensor_info_to_string(os, sub_shape); + } + + /*! + * \brief helper function to calculate the sub_shape and offset for the desired part of the tensor, + * given its coordinates in the original tensor + * \param pos the coordinates of the desired part of the tensor + * \param sub_shape the sub-shape of the desired part of the tensor; calculated here + * \param offset the position of the first value of the desired part of the tensor; calculated here + */ + void print_locator(const std::vector& pos, std::vector* sub_shape, + index_t* offset) { + const int dimension = tb_.ndim(); + const int sub_dim = dimension - pos.size(); + sub_shape->resize(sub_dim); + index_t multiple = 1; + for (size_t i = pos.size(), j = 0; i < static_cast(dimension); ++i, ++j) { + (*sub_shape)[j] = tb_.shape_[i]; + multiple *= tb_.shape_[i]; + } + index_t sum = 0; + index_t m = 1; + for (index_t i = pos.size() - 1; i >= 0; --i) { + sum += pos[i] * m; + m *= tb_.shape_[i]; + } + *offset = sum * multiple; + } + + /*! + * \brief parse the coordinate of the desired part of the tensor, given a string that represents that + * coordinate + * \param pos the coordinates of the desired part of the tensor, calculated here + * \param str the string that represents the coordinate + */ + bool parse_position(std::vector* pos, const std::string& str) { + const int dimension = tb_.ndim(); + std::istringstream ss(str); + index_t n; + while (ss >> n) { + pos->push_back(n); + if (ss.peek() == ',') { + ss.ignore(); + } + } + if (pos->size() > static_cast(dimension)) { + return false; + } + for (size_t i = 0; i < pos->size(); ++i) { + if ((*pos)[i] > (tb_.shape_[i] - 1) || (*pos)[i] < 0) { + return false; + } + } + return !pos->empty(); + } + + /*! + * \brief interactive print the tensor value + * \tparam DType the data type + * \param tag the name given to this call + */ + template + void interactive_print_helper(std::string tag) { +#if MXNET_USE_CUDA + if (tb_.dev_mask() == gpu::kDevMask) { + TensorInspector(test::CAccessAsCPU(ctx_, tb_, false)(), ctx_) + .interactive_print_helper(tag); + return; + } +#endif // MXNET_USE_CUDA + std::lock_guard lock(InspectorManager::get()->mutex_); + InspectorManager::get()->interactive_print_tag_counter_[tag] += 1; + while (!InspectorManager::get()->interactive_print_skip_all_) { + std::cout << "----------Interactive Print----------" << std::endl; + if (tag != "") { + std::cout << "Tag: " << tag << " Visit: " << + InspectorManager::get()->interactive_print_tag_counter_[tag] << std::endl; + } + tensor_info_to_string(&std::cout); + std::cout << "To print a part of the tensor, " << + "please specify a position, seperated by \",\"" << std::endl; + std::cout << "\"e\" for the entire tensor, " << + "\"d\" to dump value to file, " << + "\"b\" to break, " << + "\"s\" to skip all: "; + std::string str; + std::cin >> str; + if (str == "b") { + break; + } else if (str == "e") { + to_string_helper(&std::cout); + continue; + } else if (str == "s") { + InspectorManager::get()->interactive_print_skip_all_ = true; + break; + } else if (str == "d") { + while (true) { + std::cout << "Please enter a tag: "; + std::cin >> str; + if (str.find(' ') != std::string::npos) { + std::cout << "Invalid tag name. No space allowed."; + continue; + } + dump_to_file_helper(str); + break; + } + continue; + } + std::vector pos; + if (parse_position(&pos, str)) { + std::vector sub_shape; + index_t offset; + print_locator(pos, &sub_shape, &offset); + to_string_helper(&std::cout, sub_shape, offset); + } else { + std::cout << "invalid command/indices" << std::endl; + } + } + } + + /*! + * \brief build the lambda function, aka the checker, given its type + * \tparam DType the data type + * \param ct the type of the checker + */ + template + std::function get_checker(CheckerType ct) { + switch (ct) { + case NegativeChecker: + return [] (DType x) { + return x < 0; + }; + case PositiveChecker: + return [] (DType x) { + return x > 0; + }; + case ZeroChecker: + return [] (DType x) { + return x == 0; + }; + case NaNChecker: + if (std::is_same::value || std::is_same::value || + std::is_same::value) { + return [] (DType x) { + return x != x; + }; + } else { + LOG(WARNING) << "NaNChecker only applies to float types. " << + "Lambda will always return false."; + } + break; + case InfChecker: + if (std::is_same::value || std::is_same::value || + std::is_same::value) { + return [] (DType x) { + return x == (DType)1.0 / 0.0f || x == -(DType)1.0 / 0.0f; + }; + } else { + LOG(WARNING) << "InfChecker only applies to float types. " << + "Lambda will always return false."; + } + break; + case PositiveInfChecker: + if (std::is_same::value || std::is_same::value || + std::is_same::value) { + return [] (DType x) { + return x == (DType)1.0 / 0.0f; + }; + } else { + LOG(WARNING) << "PositiveInfChecker only applies to float types. " << + "Lambda will always return false."; + } + break; + case NegativeInfChecker: + if (std::is_same::value || std::is_same::value || + std::is_same::value) { + return [] (DType x) { + return x == -(DType)1.0 / 0.0f; + }; + } else { + LOG(WARNING) << "NegativeInfChecker only applies to float types. " << + "Lambda will always return false."; + } + break; + case FiniteChecker: + if (std::is_same::value || std::is_same::value || + std::is_same::value) { + return [] (DType x) { + return x != (DType)1.0 / 0.0f && x != -(DType)1.0 / 0.0f; + }; + } else { + LOG(WARNING) << "FiniteChecker only applies to float types. " << + "Lambda will always return false."; + } + break; + case NormalChecker: + if (std::is_same::value || std::is_same::value || + std::is_same::value) { + return [] (DType x) { + return x != (DType)1.0 / 0.0f && x != -(DType)1.0 / 0.0f && + x == x; + }; + } else { + LOG(WARNING) << "NormalChecker only applies to float types. " << + "Lambda will always return false."; + } + break; + case AbnormalChecker: + if (std::is_same::value || std::is_same::value || + std::is_same::value) { + return [] (DType x) { + return x == (DType)1.0 / 0.0f || x == -(DType)1.0 / 0.0f || + x != x; + }; + } else { + LOG(WARNING) << "AbnormalChecker only applies to float types. " << + "Lambda will always return false."; + } + break; + default: + return [] (DType x) { + return false; + }; + } + return [] (DType x) {return false;}; + } + + /*! + * \brief calculate the coordinate of a value in the tensor, given its index + * \param idx the index of the value in the tensor + */ + std::vector index_to_coordinates(index_t idx) { + const int dimension = tb_.ndim(); + std::vector ret; + for (int i = dimension - 1; i >= 0; --i) { + ret.push_back(idx % tb_.shape_[i]); + idx /= tb_.shape_[i]; + } + std::reverse(ret.begin(), ret.end()); + return ret; + } + + /*! + * \brief check/validate the values within the tensor, find the coordinates + * where the value checker evaluates to true + * \tparam DType the data type + * \param ret a vector of coordinates which itself is a vector of int; calculated here + * \param checker the lambda function to check each value of within the tensor + * \param interactive wherether to allow the user to interactively check the coordinates + * \param tag the name given to this call + */ + template + void check_value_helper(std::vector>* ret, + const std::function& checker, bool interactive, std::string tag) { +#if MXNET_USE_CUDA + if (tb_.dev_mask() == gpu::kDevMask) { + return TensorInspector(test::CAccessAsCPU(ctx_, tb_, false)(), ctx_) + .check_value_helper(ret, checker, interactive, tag); + } +#endif // MXNET_USE_CUDA + index_t count = 0; + std::stringstream ss; + ss << "["; + bool first_pass = true; + for (index_t i = 0; i (tb_.shape_.Size()); ++i) { + if (checker(tb_.dptr()[i])) { + ++count; + if (!first_pass) { + ss << ", "; + } + first_pass = false; + std::vector coords = index_to_coordinates(i); + ss << "(" << coords[0]; + for (size_t i = 1; i < coords.size(); ++i) { + ss << ", " << coords[i]; + } + ss << ")"; + ret->push_back(coords); + } + } + ss << "]" << std::endl; + if (interactive) { + std::lock_guard lock(InspectorManager::get()->mutex_); + InspectorManager::get()->check_value_tag_counter_[tag] += 1; + while (!InspectorManager::get()->check_value_skip_all_) { + std::cout << "----------Value Check----------" << std::endl; + tensor_info_to_string(&std::cout); + if (tag != "") { + std::cout << "Tag: " << tag << " Visit: " << + InspectorManager::get()->check_value_tag_counter_[tag] << std::endl; + } + std::cout << count << " value(s) found." << std::endl; + std::cout << "To print a part of the tensor," << + " please specify a position, seperated by \",\"" << std::endl; + std::cout << "\"e\" for the entire tensor, " << + "\"p\" to print the coordinates of the values found, " << + "\"b\" to break, " << + "\"s\" to skip all: "; + std::string str; + std::cin >> str; + if (str == "b") { + break; + } else if (str == "e") { + to_string_helper(&std::cout); + continue; + } else if (str == "p") { + std::cout << ss.str() << std::endl; + continue; + } else if (str == "s") { + InspectorManager::get()->check_value_skip_all_ = true; + break; + } + std::vector pos; + if (parse_position(&pos, str)) { + std::vector sub_shape; + index_t offset; + print_locator(pos, &sub_shape, &offset); + to_string_helper(&std::cout, sub_shape, offset); + } else { + std::cout << "invalid command/indices" << std::endl; + } + } + } + } + + /*! + * \brief infer the python type, given the c++ type + * \tparam ti the type info + */ + inline char infer_type(const std::type_info& ti) { + if (ti == typeid(float)) return 'f'; + else if (ti == typeid(double)) return 'f'; + else if (ti == typeid(mshadow::half::half_t) ) return 'f'; + else if (ti == typeid(uint8_t)) return 'u'; + else if (ti == typeid(int32_t)) return 'i'; + else if (ti == typeid(int64_t)) return 'i'; + else + return '?'; + } + + /*! + * \brief infer the python type, given the c++ type + * \tparam ti the type info + */ + inline std::string infer_type_string(const std::type_info& ti) { + if (ti == typeid(float)) return "float"; + else if (ti == typeid(double)) return "double"; + else if (ti == typeid(mshadow::half::half_t) ) return "mshasow::half::half_t"; + else if (ti == typeid(uint8_t)) return "uint8_t"; + else if (ti == typeid(int32_t)) return "int32_t"; + else if (ti == typeid(int64_t)) return "int64_t"; + else + return "unknown tyoe"; + } + + /*! + * \brief check if the host machine is big or small endian + */ + inline char endian_test() { + int x = 1; + return (reinterpret_cast(&x)[0]) ? '<' : '>'; + } + + /*! + * \brief generate the header following npy 1.0 format + * \tparam DType the data type + */ + template + std::string get_header() { + const int dimension = tb_.ndim(); + std::string dict; + dict += "{'descr':'"; + dict += endian_test(); + dict += infer_type(typeid(DType)); + dict += std::to_string(sizeof(DType)); + dict += "','fortran_order':False,'shape':("; + dict += std::to_string(tb_.shape_[0]); + for (int i = 1; i < dimension; ++i) { + dict += ','; + dict += std::to_string(tb_.shape_[i]); + } + if (dimension == 1) { + dict += ","; + } + dict += ")} "; + int padding_size = 64 - ((10 + dict.size()) % 64); + dict += std::string(padding_size, ' '); + dict.back() = '\n'; + std::string header; + header += static_cast(0x93); + header += "NUMPY"; + header += static_cast(0x01); + header += static_cast(0x00); + header += static_cast((uint16_t)dict.size() & 0x00ff); + header += static_cast(((uint16_t)dict.size() >> 8) & 0x00ff); + header += dict; + return header; + } + + /*! + * \brief write the header and the date to an npy file + * \tparam DType the data type + * \param header the header of the file + * \param filename the file name + */ + template + void write_npy(const std::string& header, const std::string& filename) { + std::ofstream file; + file.exceptions(std::ofstream::failbit | std::ofstream::badbit); + try { + file.open(filename, std::ios::out | std::ios::binary); + file.write(header.c_str(), header.size()); + file.write(reinterpret_cast(tb_.dptr()), sizeof(DType) * tb_.shape_.Size()); + file.close(); + std::cout << "Tensor dumped to file: " << filename << std::endl; + } catch (std::ofstream::failure e) { + std::cerr << "Exception opening/writing/closing file " << filename << std::endl; + } + } + + /*! + * \brief dump the value of the tensor to a file with name "[tag]_[visit count].npy" in npy format + * the dump file follows npy 1.0 stantand + * \tparam DType the data type + * \param tag the name given to this call + */ + template + void dump_to_file_helper(const std::string& tag) { +#if MXNET_USE_CUDA + if (tb_.dev_mask() == gpu::kDevMask) { + TensorInspector(test::CAccessAsCPU(ctx_, tb_, false)(), ctx_) + .dump_to_file_helper(tag); + return; + } +#endif // MXNET_USE_CUDA + std::string header = get_header(); + InspectorManager::get()->dump_to_file_tag_counter_[tag] += 1; + const int visit = InspectorManager::get()->dump_to_file_tag_counter_[tag]; + std::string filename = tag + "_" + std::to_string(visit) + ".npy"; + write_npy(header, filename); + } + + /*! + * \brief validate that the shape + */ + inline void validate_shape() { + const int dimension = tb_.ndim(); + CHECK(dimension > 0) << "Tensor Inspector does not support empty tensors " << + "or tensors of unknow shape."; + for (int i = 0; i < dimension; ++i) { + CHECK(tb_.shape_[i] != 0) << "Invalid tensor shape: shape_[" << i << "] is 0"; + } + } + + /* !\brief the tensor blob */ + const TBlob tb_; + /* !\brief the run context of the tensor */ + const RunContext& ctx_; + + public: + /*! + * \brief construct from Tensor object + * \tparam Device the device the tensor resides in + * \tparam dimension the dimension of the tensor + * \tparam DType the data type + * \param ts the source tensor object + * \param ctx the run context of the tensor + */ + template + TensorInspector(const mshadow::Tensor& ts, const RunContext& ctx): + tb_(ts), ctx_(ctx) { + validate_shape(); + } + + /*! + * \brief construct from TBlob object + * \param tb the source tblob object + * \param ctx the run context of the tensor + */ + TensorInspector(const TBlob& tb, const RunContext& ctx): + tb_(tb), ctx_(ctx) { + validate_shape(); + } + + /*! + * \brief construct from NDArray object. Currently this only works with kDefaultStorage + * \param arr the source ndarray object + * \param ctx the run context of the tensor + */ + TensorInspector(const NDArray& arr, const RunContext& ctx): + tb_(arr.data()), ctx_(ctx) { + validate_shape(); + } + + /*! + * \brief print the tensor to std::cout + */ + void print_string() { + std::cout << to_string() << std::endl; + } + + /*! + * \brief return a string which contains the values and other info of the tensor + */ + std::string to_string() { + std::stringstream ss; + MSHADOW_TYPE_SWITCH(tb_.type_flag_, DType, { + to_string_helper(&ss); + }); + return ss.str(); + } + + /*! + * \brief interactively print the tensor value + * \param tag the name given to this call + */ + void interactive_print(std::string tag = "") { + MSHADOW_TYPE_SWITCH(tb_.type_flag_, DType, { + interactive_print_helper(tag); + }); + } + + /*! + * \brief check/validate the values within the tensor, return the coordinates + * where the value checker evaluates to true + * \tparam ValueChecker the type of the lambda + * \param checker the lambda function to check each value of within the tensor + * \param interactive wherether to allow the user to interactively check the coordinates + * \param tag the name given to this call + */ + template + std::vector> check_value(const ValueChecker& checker, + bool interactive = false, std::string tag = "") { + std::vector> ret; + MSHADOW_TYPE_SWITCH(tb_.type_flag_, DType, { + check_value_helper(&ret, checker, ret, interactive, tag); + }); + return ret; + } + + /*! + * \brief check/validate the values within the tensor, return the coordinates + * where the lambda evaluates to true + * \param ct the type of the checker + * \param interactive wherether to allow the user to interactively check the coordinates + * \param tag the name given to this call + */ + std::vector> check_value(CheckerType ct, bool interactive = false, + std::string tag = "") { + std::vector> ret; + MSHADOW_TYPE_SWITCH(tb_.type_flag_, DType, { + check_value_helper(&ret, get_checker(ct), interactive, tag); + }); + return ret; + } + + /*! + * \brief dump the value of the tensor to a file with name "tag_[visit count].npy" in npy format + * \param tag the name given to this call + */ + void dump_to_file(std::string tag) { + MSHADOW_TYPE_SWITCH(tb_.type_flag_, DType, { + dump_to_file_helper(tag); + }); + } +}; + +} // namespace mxnet + +#endif // MXNET_COMMON_TENSOR_INSPECTOR_H_ From 9983adbaa8993c038acb65d7d6195d012c418967 Mon Sep 17 00:00:00 2001 From: Carin Meier Date: Wed, 17 Jul 2019 21:45:52 -0400 Subject: [PATCH 079/813] make clojure api generator tests less brittle (#15579) --- .../test/dev/generator_test.clj | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/contrib/clojure-package/test/dev/generator_test.clj b/contrib/clojure-package/test/dev/generator_test.clj index acc81afcbcd5..3d7d41f79d18 100644 --- a/contrib/clojure-package/test/dev/generator_test.clj +++ b/contrib/clojure-package/test/dev/generator_test.clj @@ -17,7 +17,14 @@ (ns dev.generator-test (:require [clojure.test :refer :all] - [dev.generator :as gen])) + [dev.generator :as gen] + [clojure.string :as string])) + +(defn file-function-name [f] + (->> (string/split (slurp f) #"\n") + (take 33) + last + (string/trim))) (deftest test-clojure-case (is (= "foo-bar" (gen/clojure-case "FooBar"))) @@ -334,20 +341,20 @@ fns (gen/all-symbol-api-functions gen/op-names) _ (gen/write-to-file [(first fns) (second fns)] (gen/symbol-api-gen-ns false) - fname) - good-contents (slurp "test/good-test-symbol-api.clj") - contents (slurp fname)] - (is (= good-contents contents)))) + fname)] + (is (= "activation" + (file-function-name "test/good-test-symbol-api.clj") + (file-function-name fname))))) (testing "symbol-random-api" (let [fname "test/test-symbol-random-api.clj" fns (gen/all-symbol-random-api-functions gen/op-names) _ (gen/write-to-file [(first fns) (second fns)] (gen/symbol-api-gen-ns true) - fname) - good-contents (slurp "test/good-test-symbol-random-api.clj") - contents (slurp fname)] - (is (= good-contents contents)))) + fname)] + (is (= "exponential" + (file-function-name "test/good-test-symbol-random-api.clj") + (file-function-name fname))))) (testing "symbol" @@ -364,20 +371,20 @@ fns (gen/all-ndarray-api-functions gen/op-names) _ (gen/write-to-file [(first fns) (second fns)] (gen/ndarray-api-gen-ns false) - fname) - good-contents (slurp "test/good-test-ndarray-api.clj") - contents (slurp fname)] - (is (= good-contents contents)))) + fname)] + (is (= "activation" + (file-function-name "test/good-test-ndarray-api.clj") + (file-function-name fname))))) (testing "ndarray-random-api" (let [fname "test/test-ndarray-random-api.clj" fns (gen/all-ndarray-random-api-functions gen/op-names) _ (gen/write-to-file [(first fns) (second fns)] (gen/ndarray-api-gen-ns true) - fname) - good-contents (slurp "test/good-test-ndarray-random-api.clj") - contents (slurp fname)] - (is (= good-contents contents)))) + fname)] + (is (= "exponential" + (file-function-name "test/good-test-ndarray-random-api.clj") + (file-function-name fname))))) (testing "ndarray" (let [fname "test/test-ndarray.clj" From dd14c813d095abe7195076b7460975ccec62a388 Mon Sep 17 00:00:00 2001 From: Aaron Markham Date: Wed, 17 Jul 2019 21:15:06 -0700 Subject: [PATCH 080/813] refine Nano setup directions (#15524) * refine directions * resolving feedback on instructions --- docs/install/install-jetson.md | 66 ++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/docs/install/install-jetson.md b/docs/install/install-jetson.md index 6b0d65060342..0b50ceb3fcc5 100644 --- a/docs/install/install-jetson.md +++ b/docs/install/install-jetson.md @@ -19,13 +19,12 @@ MXNet supports the Ubuntu Arch64 based operating system so you can run MXNet on NVIDIA Jetson Devices, such as the [TX2](http://www.nvidia.com/object/embedded-systems-dev-kits-modules.html) or [Nano](https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit). -These instructions will walk through how to build MXNet and install MXNet's Python language binding. +These instructions will walk through how to build MXNet and install MXNet's Python language binding. For the purposes of this install guide we will assume that CUDA is already installed on your Jetson device. The disk image provided by NVIDIA's getting started guides will have the Jetson toolkit preinstalled, and this also includes CUDA. You should double check what versions are installed and which version you plan to use. -You have several options for installing MXNet: -1. Use a Jetson MXNet pip wheel for Python development. -2. Use precompiled Jetson MXNet binaries. +After installing the prerequisites, you have several options for installing MXNet: +1. Use a Jetson MXNet pip wheel for Python development and use a precompiled Jetson MXNet binary. 3. Build MXNet from source * On a faster Linux computer using cross-compilation * On the Jetson itself (very slow and not recommended) @@ -35,7 +34,7 @@ You have several options for installing MXNet: To build from source or to use the Python wheel, you must install the following dependencies on your Jetson. Cross-compiling will require dependencies installed on that machine as well. -### Python API +### Python Dependencies To use the Python API you need the following dependencies: @@ -60,49 +59,54 @@ sudo pip install \ ``` If you plan to cross-compile you will need to install these dependencies on that computer as well. +If you get an error about something being busy, you can restart the Nano and this error will go away. You can then continue installation of the prerequisites. -### Configure CUDA +### Download the source & setup some environment variables: -You can check to see what version of CUDA is running with `nvcc`. +These steps are optional, but some of the following instructions expect MXNet source files and the `MXNET_HOME` environment variable. Also, CUDA commands will not work out of the box without updating your path. + +Clone the MXNet source code repository using the following `git` command in your home directory: ```bash -nvcc --version +git clone --recursive https://github.com/apache/incubator-mxnet.git mxnet ``` -To switch CUDA versions on a device or computer that has more than one version installed, use the following and replace the version as appropriate. +Setup your environment variables for MXNet and CUDA in your `.profile` file in your home directory. +Add the following to the file. ```bash -sudo rm /usr/local/cuda -sudo ln -s /usr/local/cuda-10.0 /usr/local/cuda +export PATH=/usr/local/cuda/bin:$PATH +export MXNET_HOME=$HOME/mxnet/ +export PYTHONPATH=$MXNET_HOME/python:$PYTHONPATH ``` -**Note:** When cross-compiling, change the CUDA version on the host computer you're using to match the version you're running on your Jetson device. -**Note:** CUDA 10.1 is recommended but doesn't ship with the Nano's SD card image. You may want to go through CUDA upgrade steps first. +You can then apply this change immediately with the following: +```bash +source .profile +``` -### Download the source & setup some environment variables: +**Note:** Change the `~/.profile` steps according to how you prefer to use your shell. Otherwise, your environment variables will be gone after you logout. -These steps are optional, but some of the following instructions expect MXNet source files and the `MXNET_HOME` environment variable. +### Configure CUDA -Clone the MXNet source code repository using the following `git` command in your home directory: +You can check to see what version of CUDA is running with `nvcc`. ```bash -git clone --recursive https://github.com/apache/incubator-mxnet.git mxnet -cd mxnet +nvcc --version ``` -Setup your environment variables for MXNet. +To switch CUDA versions on a device or computer that has more than one version installed, use the following and replace the symbolic link to the version you want. This one uses CUDA 10.0, which is preinstalled on the Nano. ```bash -cd .. -export MXNET_HOME=$(pwd) -echo "export PYTHONPATH=$MXNET_HOME/python:$PYTHONPATH" >> ~/.rc -source ~/.rc +sudo rm /usr/local/cuda +sudo ln -s /usr/local/cuda-10.0 /usr/local/cuda ``` -**Note:** Change the `~/.rc` steps according to how you prefer to use your shell. Otherwise, your environment variables will be gone after you logout. +**Note:** When cross-compiling, change the CUDA version on the host computer you're using to match the version you're running on your Jetson device. +**Note:** CUDA 10.1 is recommended but doesn't ship with the Nano's SD card image. You may want to go through CUDA upgrade steps first. -## Install MXNet for Python +## Option 1. Install MXNet for Python To use a prepared Python wheel, download it to your Jetson, and run it. * [MXNet 1.4.0 - Python 3](https://s3.us-east-2.amazonaws.com/mxnet-public/install/jetson/1.4.0/mxnet-1.4.0-cp36-cp36m-linux_aarch64.whl) @@ -113,12 +117,10 @@ It should download the required dependencies, but if you have issues, install the dependencies in the prerequisites section, then run the pip wheel. ```bash -sudo pip install mxnet-1.4.0-cp36-cp36m-linux_aarch64.whl +sudo pip install mxnet-1.4.0-cp27-cp27mu-linux_aarch64.whl ``` -## Use a Pre-compiled MXNet Binary - -If you want to just use a pre-compiled binary you can download it from S3: +Now use a pre-compiled binary you can download it from S3 which is a patch v1.4.1: * https://s3.us-east-2.amazonaws.com/mxnet-public/install/jetson/1.4.1/libmxnet.so Place this file in `$MXNET_HOME/lib`. @@ -128,12 +130,14 @@ To use this with the MXNet Python binding, you must match the source directory's ```bash cd $MXNET_HOME git checkout v1.4.x -git submodule update --init +git submodule update --init --recursive cd python sudo pip install -e . ``` -## Build MXNet from Source +Refer to the following Conclusion and Next Steps section to test your installation. + +## Option 2. Build MXNet from Source Installing MXNet from source is a two-step process: From c8a1de429b4ba81c14348db0c04f6d3ea3ec4a17 Mon Sep 17 00:00:00 2001 From: Aaron Markham Date: Thu, 18 Jul 2019 00:12:45 -0700 Subject: [PATCH 081/813] Julia path patch (#15561) * fix path to be static * add MXNET_HOME setting for Julia docs * Update docs/mxdoc.py Co-Authored-By: Iblis Lin --- docs/mxdoc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/mxdoc.py b/docs/mxdoc.py index f70dcfad1d0e..f2bb9714de0c 100644 --- a/docs/mxdoc.py +++ b/docs/mxdoc.py @@ -98,6 +98,8 @@ def build_mxnet(app): def build_julia_docs(app): """build Julia docs""" + os.environ['MXNET_HOME'] = os.path.abspath(os.path.join(app.builder.srcdir, '..')) + print("Julia will check for MXNet in {}/lib".format(os.environ.get('MXNET_HOME'))) dest_path = app.builder.outdir + '/api/julia/site' _run_cmd('cd {}/.. && make -C julia/docs'.format(app.builder.srcdir)) _run_cmd('mkdir -p {}'.format(dest_path)) From f44fdd683112d593d239847b1fa6932f5ab5c6c0 Mon Sep 17 00:00:00 2001 From: Anirudh Subramanian Date: Thu, 18 Jul 2019 13:54:11 -0700 Subject: [PATCH 082/813] Fix AMP Tutorial failures (#15526) --- docs/tutorials/amp/amp_tutorial.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/tutorials/amp/amp_tutorial.md b/docs/tutorials/amp/amp_tutorial.md index 5f9bd1195a1c..9da0505e9ff6 100644 --- a/docs/tutorials/amp/amp_tutorial.md +++ b/docs/tutorials/amp/amp_tutorial.md @@ -262,19 +262,20 @@ Below, we demonstrate for a gluon model and a symbolic model: ```python with mx.Context(mx.gpu(0)): # Below is an example of converting a gluon hybrid block to a mixed precision block - model = get_model("resnet50_v1") - model.collect_params().initialize(ctx=mx.current_context()) - model.hybridize() - model(mx.nd.zeros((1, 3, 224, 224))) - converted_model = amp.convert_hybrid_block(model) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("ignore") + model = get_model("resnet50_v1") + model.collect_params().initialize(ctx=mx.current_context()) + model.hybridize() + model(mx.nd.zeros((1, 3, 224, 224))) + converted_model = amp.convert_hybrid_block(model) # Run dummy inference with the converted gluon model result = converted_model.forward(mx.nd.random.uniform(shape=(1, 3, 224, 224), dtype=np.float32)) # Below is an example of converting a symbolic model to a mixed precision model - dir_path = os.path.dirname(os.path.realpath(__file__)) - model_path = os.path.join(dir_path, 'model') + model_path = "model" if not os.path.isdir(model_path): os.mkdir(model_path) prefix, epoch = mx.test_utils.download_model("imagenet1k-resnet-18", dst_dir=model_path) @@ -301,8 +302,7 @@ for symbolic model. You can do the same for gluon hybrid block with `amp.convert with mx.Context(mx.gpu(0)): # Below is an example of converting a symbolic model to a mixed precision model # with only Convolution op being force casted to FP16. - dir_path = os.path.dirname(os.path.realpath(__file__)) - model_path = os.path.join(dir_path, 'model') + model_path = "model" if not os.path.isdir(model_path): os.mkdir(model_path) prefix, epoch = mx.test_utils.download_model("imagenet1k-resnet-18", dst_dir=model_path) From 67bbf5a7b35a6e6e57061b81fee9899a9ddde87b Mon Sep 17 00:00:00 2001 From: Jake Lee Date: Thu, 18 Jul 2019 17:29:12 -0700 Subject: [PATCH 083/813] [Dependency Update] Bump up the CI Nvidia docker to CUDA 10.1 (#14986) * bump up the nvidia docker to CUDA 10.1 * change the func name * change the groovy file * clean up code & create cu101 docker file * add cuda 10.1 * update the miss groovy * change description * move up the cudnn installation * update label & func for nightly build * update to cuda 10.1 * upgrade to build cu101mkl * do not use nvidia docker in build static test * fix cuda_patch_typo * use nvidia docker * fix the right config * use nvidia docker and install latest cuDNN * fix the typo * use default cudnn --- ci/docker/Dockerfile.build.centos7_gpu | 2 +- ci/docker/Dockerfile.build.ubuntu_base_gpu | 2 +- ci/docker/Dockerfile.build.ubuntu_build_cuda | 2 +- ci/docker/Dockerfile.build.ubuntu_gpu_cu101 | 82 +++++++++++++++++++ ci/docker/Dockerfile.build.ubuntu_nightly_gpu | 2 +- ci/docker/Dockerfile.publish.ubuntu1404_gpu | 2 +- ci/docker/install/ubuntu_cudnn.sh | 4 + ci/docker/install/ubuntu_nvidia.sh | 2 +- ci/docker/runtime_functions.sh | 8 +- ci/jenkins/Jenkins_steps.groovy | 44 +++++----- tests/nightly/JenkinsfileForBinaries | 4 +- tools/setup_gpu_build_tools.sh | 2 +- 12 files changed, 121 insertions(+), 35 deletions(-) create mode 100644 ci/docker/Dockerfile.build.ubuntu_gpu_cu101 diff --git a/ci/docker/Dockerfile.build.centos7_gpu b/ci/docker/Dockerfile.build.centos7_gpu index 1a927c4d5832..7e49e88b3a52 100644 --- a/ci/docker/Dockerfile.build.centos7_gpu +++ b/ci/docker/Dockerfile.build.centos7_gpu @@ -18,7 +18,7 @@ # # Dockerfile to build and run MXNet on CentOS 7 for GPU -FROM nvidia/cuda:10.0-devel-centos7 +FROM nvidia/cuda:10.1-devel-centos7 WORKDIR /work/deps diff --git a/ci/docker/Dockerfile.build.ubuntu_base_gpu b/ci/docker/Dockerfile.build.ubuntu_base_gpu index 40e1da657203..94e6437e578b 100644 --- a/ci/docker/Dockerfile.build.ubuntu_base_gpu +++ b/ci/docker/Dockerfile.build.ubuntu_base_gpu @@ -19,7 +19,7 @@ # Dockerfile to run the MXNet Installation Tests on Ubuntu 16.04 # This should run in an empty docker with ubuntu and cuda. -FROM nvidia/cuda:10.0-devel-ubuntu16.04 +FROM nvidia/cuda:10.1-devel-ubuntu16.04 WORKDIR /work/deps diff --git a/ci/docker/Dockerfile.build.ubuntu_build_cuda b/ci/docker/Dockerfile.build.ubuntu_build_cuda index f568fbc386eb..47f1d1f9ca58 100644 --- a/ci/docker/Dockerfile.build.ubuntu_build_cuda +++ b/ci/docker/Dockerfile.build.ubuntu_build_cuda @@ -21,7 +21,7 @@ # package generation, requiring the actual CUDA library to be # present -FROM nvidia/cuda:10.0-devel-ubuntu16.04 +FROM nvidia/cuda:10.1-devel-ubuntu16.04 WORKDIR /work/deps diff --git a/ci/docker/Dockerfile.build.ubuntu_gpu_cu101 b/ci/docker/Dockerfile.build.ubuntu_gpu_cu101 new file mode 100644 index 000000000000..32f0a0a8d862 --- /dev/null +++ b/ci/docker/Dockerfile.build.ubuntu_gpu_cu101 @@ -0,0 +1,82 @@ +# -*- mode: dockerfile -*- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# Dockerfile to run MXNet on Ubuntu 16.04 for GPU + +FROM nvidia/cuda:10.1-devel-ubuntu16.04 + +WORKDIR /work/deps + +COPY install/ubuntu_core.sh /work/ +RUN /work/ubuntu_core.sh + +COPY install/deb_ubuntu_ccache.sh /work/ +RUN /work/deb_ubuntu_ccache.sh + +COPY install/ubuntu_python.sh /work/ +RUN /work/ubuntu_python.sh + +COPY install/ubuntu_scala.sh /work/ +COPY install/sbt.gpg /work/ +RUN /work/ubuntu_scala.sh + +COPY install/ubuntu_r.sh /work/ +COPY install/r.gpg /work/ +RUN /work/ubuntu_r.sh + +COPY install/ubuntu_perl.sh /work/ +RUN /work/ubuntu_perl.sh + +COPY install/ubuntu_clang.sh /work/ +RUN /work/ubuntu_clang.sh + +COPY install/ubuntu_mklml.sh /work/ +RUN /work/ubuntu_mklml.sh + +COPY install/ubuntu_tvm.sh /work/ +RUN /work/ubuntu_tvm.sh + +COPY install/ubuntu_llvm.sh /work/ +RUN /work/ubuntu_llvm.sh + +COPY install/ubuntu_caffe.sh /work/ +RUN /work/ubuntu_caffe.sh + +COPY install/ubuntu_onnx.sh /work/ +RUN /work/ubuntu_onnx.sh + +COPY install/ubuntu_docs.sh /work/ +COPY install/docs_requirements /work/ +RUN /work/ubuntu_docs.sh + +COPY install/ubuntu_tutorials.sh /work/ +RUN /work/ubuntu_tutorials.sh + +ENV CUDNN_VERSION=7.5.1.10 +COPY install/ubuntu_cudnn.sh /work/ +RUN /work/ubuntu_cudnn.sh + +# Always last +ARG USER_ID=0 +ARG GROUP_ID=0 +COPY install/ubuntu_adduser.sh /work/ +RUN /work/ubuntu_adduser.sh + +COPY runtime_functions.sh /work/ + +WORKDIR /work/mxnet diff --git a/ci/docker/Dockerfile.build.ubuntu_nightly_gpu b/ci/docker/Dockerfile.build.ubuntu_nightly_gpu index a667f7b7a94f..275a5a54fc66 100644 --- a/ci/docker/Dockerfile.build.ubuntu_nightly_gpu +++ b/ci/docker/Dockerfile.build.ubuntu_nightly_gpu @@ -18,7 +18,7 @@ # # Dockerfile to run MXNet on Ubuntu 16.04 for CPU -FROM nvidia/cuda:10.0-devel-ubuntu16.04 +FROM nvidia/cuda:10.1-devel-ubuntu16.04 WORKDIR /work/deps diff --git a/ci/docker/Dockerfile.publish.ubuntu1404_gpu b/ci/docker/Dockerfile.publish.ubuntu1404_gpu index 9855986a2891..4d9fa819a39e 100644 --- a/ci/docker/Dockerfile.publish.ubuntu1404_gpu +++ b/ci/docker/Dockerfile.publish.ubuntu1404_gpu @@ -18,7 +18,7 @@ # # Dockerfile to run MXNet on Ubuntu 14.04 for GPU -FROM ubuntu:14.04 +FROM nvidia/cuda:10.1-cudnn7-devel-ubuntu14.04 WORKDIR /work/deps diff --git a/ci/docker/install/ubuntu_cudnn.sh b/ci/docker/install/ubuntu_cudnn.sh index 3d260046b5e7..b773fea0f82f 100755 --- a/ci/docker/install/ubuntu_cudnn.sh +++ b/ci/docker/install/ubuntu_cudnn.sh @@ -32,6 +32,10 @@ fi apt-get update || true case ${CUDA_VERSION} in + 10\.1*) + export libcudnn7_version="${CUDNN_VERSION}-1+cuda10.1" + export libcudnn7_dev_version="${CUDNN_VERSION}-1+cuda10.1" + ;; 10\.0*) export libcudnn7_version="${CUDNN_VERSION}-1+cuda10.0" export libcudnn7_dev_version="${CUDNN_VERSION}-1+cuda10.0" diff --git a/ci/docker/install/ubuntu_nvidia.sh b/ci/docker/install/ubuntu_nvidia.sh index 36eb21b8a03e..41f68a21858b 100755 --- a/ci/docker/install/ubuntu_nvidia.sh +++ b/ci/docker/install/ubuntu_nvidia.sh @@ -22,4 +22,4 @@ set -ex # Retrieve ppa:graphics-drivers and install nvidia-drivers. # Note: DEBIAN_FRONTEND required to skip the interactive setup steps apt update -DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends cuda-10-0 +DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends cuda-10-1 diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index d2b0d6b0e274..2518f4c7c64f 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -579,7 +579,7 @@ build_ubuntu_cpu_mkldnn_mkl() { } build_ubuntu_gpu() { - build_ubuntu_gpu_cuda100_cudnn7 + build_ubuntu_gpu_cuda101_cudnn7 } build_ubuntu_gpu_tensorrt() { @@ -679,7 +679,7 @@ build_ubuntu_gpu_mkldnn_nocudnn() { -j$(nproc) } -build_ubuntu_gpu_cuda100_cudnn7() { +build_ubuntu_gpu_cuda101_cudnn7() { set -ex build_ccache_wrappers make \ @@ -1460,10 +1460,10 @@ build_static_python_mkl() { popd } -build_static_python_cu100mkl() { +build_static_python_cu101mkl() { set -ex pushd . - export mxnet_variant=cu100mkl + export mxnet_variant=cu101mkl ./ci/publish/python/build.sh popd } diff --git a/ci/jenkins/Jenkins_steps.groovy b/ci/jenkins/Jenkins_steps.groovy index 40700ad22bb1..c27a61383e46 100644 --- a/ci/jenkins/Jenkins_steps.groovy +++ b/ci/jenkins/Jenkins_steps.groovy @@ -153,7 +153,7 @@ def compile_unix_int64_gpu() { ws('workspace/build-gpu-int64') { timeout(time: max_time, unit: 'MINUTES') { utils.init_git() - utils.docker_run('ubuntu_gpu_cu100', 'build_ubuntu_gpu_large_tensor', false) + utils.docker_run('ubuntu_gpu_cu101', 'build_ubuntu_gpu_large_tensor', false) utils.pack_lib('ubuntu_gpu_int64', mx_cmake_lib, true) } } @@ -232,12 +232,12 @@ def compile_unix_mkldnn_nocudnn_gpu() { } def compile_unix_full_gpu() { - return ['GPU: CUDA10.0+cuDNN7': { + return ['GPU: CUDA10.1+cuDNN7': { node(NODE_LINUX_CPU) { ws('workspace/build-gpu') { timeout(time: max_time, unit: 'MINUTES') { utils.init_git() - utils.docker_run('ubuntu_build_cuda', 'build_ubuntu_gpu_cuda100_cudnn7', false) + utils.docker_run('ubuntu_build_cuda', 'build_ubuntu_gpu_cuda101_cudnn7', false) utils.pack_lib('gpu', mx_lib_cpp_examples, true) } } @@ -251,7 +251,7 @@ def compile_unix_cmake_mkldnn_gpu() { ws('workspace/build-cmake-mkldnn-gpu') { timeout(time: max_time, unit: 'MINUTES') { utils.init_git() - utils.docker_run('ubuntu_gpu_cu100', 'build_ubuntu_gpu_cmake_mkldnn', false) + utils.docker_run('ubuntu_gpu_cu101', 'build_ubuntu_gpu_cmake_mkldnn', false) utils.pack_lib('cmake_mkldnn_gpu', mx_cmake_mkldnn_lib, true) } } @@ -265,7 +265,7 @@ def compile_unix_cmake_gpu() { ws('workspace/build-cmake-gpu') { timeout(time: max_time, unit: 'MINUTES') { utils.init_git() - utils.docker_run('ubuntu_gpu_cu100', 'build_ubuntu_gpu_cmake', false) + utils.docker_run('ubuntu_gpu_cu101', 'build_ubuntu_gpu_cmake', false) utils.pack_lib('cmake_gpu', mx_cmake_lib_cython, true) } } @@ -631,7 +631,7 @@ def test_static_python_gpu() { ws('workspace/ut-publish-python-gpu') { timeout(time: max_time, unit: 'MINUTES') { utils.init_git() - utils.docker_run("publish.ubuntu1404_gpu", 'build_static_python_cu100mkl', true) + utils.docker_run("publish.ubuntu1404_gpu", 'build_static_python_cu101mkl', true) } } } @@ -662,7 +662,7 @@ def test_unix_python2_gpu() { ws('workspace/ut-python2-gpu') { try { utils.unpack_and_init('gpu', mx_lib, true) - python2_gpu_ut('ubuntu_gpu_cu100') + python2_gpu_ut('ubuntu_gpu_cu101') utils.publish_test_coverage() } finally { utils.collect_test_results_unix('nosetests_gpu.xml', 'nosetests_python2_gpu.xml') @@ -679,7 +679,7 @@ def test_unix_python2_quantize_gpu() { timeout(time: max_time, unit: 'MINUTES') { try { utils.unpack_and_init('gpu', mx_lib, true) - utils.docker_run('ubuntu_gpu_cu100', 'unittest_ubuntu_python2_quantization_gpu', true) + utils.docker_run('ubuntu_gpu_cu101', 'unittest_ubuntu_python2_quantization_gpu', true) utils.publish_test_coverage() } finally { utils.collect_test_results_unix('nosetests_quantization_gpu.xml', 'nosetests_python2_quantize_gpu.xml') @@ -696,7 +696,7 @@ def test_unix_python2_mkldnn_gpu() { ws('workspace/ut-python2-mkldnn-gpu') { try { utils.unpack_and_init('mkldnn_gpu', mx_mkldnn_lib, true) - python2_gpu_ut('ubuntu_gpu_cu100') + python2_gpu_ut('ubuntu_gpu_cu101') utils.publish_test_coverage() } finally { utils.collect_test_results_unix('nosetests_gpu.xml', 'nosetests_python2_mkldnn_gpu.xml') @@ -746,7 +746,7 @@ def test_unix_python3_gpu() { ws('workspace/ut-python3-gpu') { try { utils.unpack_and_init('gpu', mx_lib_cython, true) - python3_gpu_ut_cython('ubuntu_gpu_cu100') + python3_gpu_ut_cython('ubuntu_gpu_cu101') utils.publish_test_coverage() } finally { utils.collect_test_results_unix('nosetests_gpu.xml', 'nosetests_python3_gpu.xml') @@ -763,7 +763,7 @@ def test_unix_python3_quantize_gpu() { timeout(time: max_time, unit: 'MINUTES') { try { utils.unpack_and_init('gpu', mx_lib, true) - utils.docker_run('ubuntu_gpu_cu100', 'unittest_ubuntu_python3_quantization_gpu', true) + utils.docker_run('ubuntu_gpu_cu101', 'unittest_ubuntu_python3_quantization_gpu', true) utils.publish_test_coverage() } finally { utils.collect_test_results_unix('nosetests_quantization_gpu.xml', 'nosetests_python3_quantize_gpu.xml') @@ -848,7 +848,7 @@ def test_unix_python3_mkldnn_gpu() { ws('workspace/ut-python3-mkldnn-gpu') { try { utils.unpack_and_init('mkldnn_gpu', mx_mkldnn_lib, true) - python3_gpu_ut('ubuntu_gpu_cu100') + python3_gpu_ut('ubuntu_gpu_cu101') utils.publish_test_coverage() } finally { utils.collect_test_results_unix('nosetests_gpu.xml', 'nosetests_python3_mkldnn_gpu.xml') @@ -864,7 +864,7 @@ def test_unix_python3_mkldnn_nocudnn_gpu() { ws('workspace/ut-python3-mkldnn-gpu-nocudnn') { try { utils.unpack_and_init('mkldnn_gpu_nocudnn', mx_mkldnn_lib, true) - python3_gpu_ut_nocudnn('ubuntu_gpu_cu100') + python3_gpu_ut_nocudnn('ubuntu_gpu_cu101') utils.publish_test_coverage() } finally { utils.collect_test_results_unix('nosetests_gpu.xml', 'nosetests_python3_mkldnn_gpu_nocudnn.xml') @@ -898,7 +898,7 @@ def test_unix_python3_integration_gpu() { ws('workspace/it-python-gpu') { timeout(time: max_time, unit: 'MINUTES') { utils.unpack_and_init('gpu', mx_lib, true) - utils.docker_run('ubuntu_gpu_cu100', 'integrationtest_ubuntu_gpu_python', true) + utils.docker_run('ubuntu_gpu_cu101', 'integrationtest_ubuntu_gpu_python', true) utils.publish_test_coverage() } } @@ -913,7 +913,7 @@ def test_unix_caffe_gpu() { timeout(time: max_time, unit: 'MINUTES') { utils.init_git() utils.unpack_lib('gpu', mx_lib) - utils.docker_run('ubuntu_gpu_cu100', 'integrationtest_ubuntu_gpu_caffe', true) + utils.docker_run('ubuntu_gpu_cu101', 'integrationtest_ubuntu_gpu_caffe', true) utils.publish_test_coverage() } } @@ -927,7 +927,7 @@ def test_unix_cpp_package_gpu() { ws('workspace/it-cpp-package') { timeout(time: max_time, unit: 'MINUTES') { utils.unpack_and_init('gpu', mx_lib_cpp_examples, true) - utils.docker_run('ubuntu_gpu_cu100', 'integrationtest_ubuntu_gpu_cpp_package', true) + utils.docker_run('ubuntu_gpu_cu101', 'integrationtest_ubuntu_gpu_cpp_package', true) utils.publish_test_coverage() } } @@ -969,7 +969,7 @@ def test_unix_scala_gpu() { ws('workspace/ut-scala-gpu') { timeout(time: max_time, unit: 'MINUTES') { utils.unpack_and_init('gpu', mx_lib, true) - utils.docker_run('ubuntu_gpu_cu100', 'integrationtest_ubuntu_gpu_scala', true) + utils.docker_run('ubuntu_gpu_cu101', 'integrationtest_ubuntu_gpu_scala', true) utils.publish_test_coverage() } } @@ -1052,7 +1052,7 @@ def test_unix_cpp_gpu() { ws('workspace/ut-cpp-gpu') { timeout(time: max_time, unit: 'MINUTES') { utils.unpack_and_init('cmake_gpu', mx_cmake_lib, true) - utils.docker_run('ubuntu_gpu_cu100', 'unittest_cpp', true) + utils.docker_run('ubuntu_gpu_cu101', 'unittest_cpp', true) utils.publish_test_coverage() } } @@ -1066,7 +1066,7 @@ def test_unix_cpp_mkldnn_gpu() { ws('workspace/ut-cpp-mkldnn-gpu') { timeout(time: max_time, unit: 'MINUTES') { utils.unpack_and_init('cmake_mkldnn_gpu', mx_cmake_mkldnn_lib, true) - utils.docker_run('ubuntu_gpu_cu100', 'unittest_cpp', true) + utils.docker_run('ubuntu_gpu_cu101', 'unittest_cpp', true) utils.publish_test_coverage() } } @@ -1094,7 +1094,7 @@ def test_unix_perl_gpu() { ws('workspace/ut-perl-gpu') { timeout(time: max_time, unit: 'MINUTES') { utils.unpack_and_init('gpu', mx_lib, true) - utils.docker_run('ubuntu_gpu_cu100', 'unittest_ubuntu_cpugpu_perl', true) + utils.docker_run('ubuntu_gpu_cu101', 'unittest_ubuntu_cpugpu_perl', true) utils.publish_test_coverage() } } @@ -1108,7 +1108,7 @@ def test_unix_r_gpu() { ws('workspace/ut-r-gpu') { timeout(time: max_time, unit: 'MINUTES') { utils.unpack_and_init('gpu', mx_lib, true) - utils.docker_run('ubuntu_gpu_cu100', 'unittest_ubuntu_gpu_R', true) + utils.docker_run('ubuntu_gpu_cu101', 'unittest_ubuntu_gpu_R', true) utils.publish_test_coverage() } } @@ -1176,7 +1176,7 @@ def test_unix_distributed_kvstore_gpu() { ws('workspace/it-dist-kvstore') { timeout(time: max_time, unit: 'MINUTES') { utils.unpack_and_init('gpu', mx_lib, true) - utils.docker_run('ubuntu_gpu_cu100', 'integrationtest_ubuntu_gpu_dist_kvstore', true) + utils.docker_run('ubuntu_gpu_cu101', 'integrationtest_ubuntu_gpu_dist_kvstore', true) utils.publish_test_coverage() } } diff --git a/tests/nightly/JenkinsfileForBinaries b/tests/nightly/JenkinsfileForBinaries index d5f1ebdd6fef..725c76969e23 100755 --- a/tests/nightly/JenkinsfileForBinaries +++ b/tests/nightly/JenkinsfileForBinaries @@ -31,11 +31,11 @@ utils.assign_node_labels(utility: 'utility', linux_cpu: 'mxnetlinux-cpu', linux_ utils.main_wrapper( core_logic: { stage('Build') { - parallel 'GPU: CUDA9.1+cuDNN7': { + parallel 'GPU: CUDA10.1+cuDNN7': { node(NODE_LINUX_CPU) { ws('workspace/build-gpu') { utils.init_git() - utils.docker_run('ubuntu_build_cuda', 'build_ubuntu_gpu_cuda100_cudnn7', false) + utils.docker_run('ubuntu_build_cuda', 'build_ubuntu_gpu_cuda101_cudnn7', false) utils.pack_lib('gpu', mx_lib) } } diff --git a/tools/setup_gpu_build_tools.sh b/tools/setup_gpu_build_tools.sh index 724af3c90ec1..b25be6b697e1 100755 --- a/tools/setup_gpu_build_tools.sh +++ b/tools/setup_gpu_build_tools.sh @@ -29,7 +29,7 @@ DEPS_PATH=$2 >&2 echo "Setting CUDA versions for $VARIANT" if [[ $VARIANT == cu101* ]]; then CUDA_VERSION='10.1.105-1' - CUDA_PATCH_VERSION='10.1.105-1' + CUDA_PATCH_VERSION='10.1.0.105-1' LIBCUDA_VERSION='418.39-0ubuntu1' LIBCUDNN_VERSION='7.6.0.64-1+cuda10.1' LIBNCCL_VERSION='2.4.7-1+cuda10.1' From 45c360cee9ac9d1c7008652fcabb0dceac1df38f Mon Sep 17 00:00:00 2001 From: nuslq <46871447+nuslq@users.noreply.github.com> Date: Thu, 18 Jul 2019 21:37:56 -0700 Subject: [PATCH 084/813] fixed config.mk and Makefile bugs for installing mkl (#15424) * fixed config.mk and Makefile bugs for installing mkl * remove comments from the previous changes --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index a7cf8a56a433..c5ac4ebe01fa 100644 --- a/Makefile +++ b/Makefile @@ -244,13 +244,13 @@ ifeq ($(USE_CUDNN), 1) LDFLAGS += -lcudnn endif -ifeq ($(use_blas), open) +ifeq ($(USE_BLAS), open) CFLAGS += -DMXNET_USE_BLAS_OPEN=1 -else ifeq ($(use_blas), atlas) +else ifeq ($(USE_BLAS), atlas) CFLAGS += -DMXNET_USE_BLAS_ATLAS=1 -else ifeq ($(use_blas), mkl) +else ifeq ($(USE_BLAS), mkl) CFLAGS += -DMXNET_USE_BLAS_MKL=1 -else ifeq ($(use_blas), apple) +else ifeq ($(USE_BLAS), apple) CFLAGS += -DMXNET_USE_BLAS_APPLE=1 endif From b887c06f6c74e64fca668dbf2a69b67ba2a197d3 Mon Sep 17 00:00:00 2001 From: david-seiler <1927983+david-seiler@users.noreply.github.com> Date: Fri, 19 Jul 2019 07:41:58 +0200 Subject: [PATCH 085/813] PDF operators for each distribution for which we have a random sampler (plus also the PDF of the Dirichlet). Supports probabilities and log-probabilities, as well as gradients. (#14617) --- python/mxnet/contrib/amp/lists/symbol.py | 17 + src/operator/random/pdf_op.cc | 319 ++++++++++++ src/operator/random/pdf_op.cu | 48 ++ src/operator/random/pdf_op.h | 622 +++++++++++++++++++++++ tests/python/unittest/test_random.py | 111 +++- 5 files changed, 1110 insertions(+), 7 deletions(-) create mode 100644 src/operator/random/pdf_op.cc create mode 100644 src/operator/random/pdf_op.cu create mode 100644 src/operator/random/pdf_op.h diff --git a/python/mxnet/contrib/amp/lists/symbol.py b/python/mxnet/contrib/amp/lists/symbol.py index 066618b4073d..9a587dfa73c1 100644 --- a/python/mxnet/contrib/amp/lists/symbol.py +++ b/python/mxnet/contrib/amp/lists/symbol.py @@ -600,6 +600,23 @@ '_sparse_elemwise_mul', '_sparse_elemwise_sub', '_sparse_sum', + + 'random_pdf_gamma', + 'random_pdf_exponential', + 'random_pdf_uniform', + 'random_pdf_negative_binomial', + 'random_pdf_generalized_negative_binomial', + 'random_pdf_dirichlet', + 'random_pdf_normal', + 'random_pdf_poisson', + '_random_pdf_gamma', + '_random_pdf_exponential', + '_random_pdf_uniform', + '_random_pdf_negative_binomial', + '_random_pdf_generalized_negative_binomial', + '_random_pdf_dirichlet', + '_random_pdf_normal', + '_random_pdf_poisson', ] LOSS_OUTPUT_FUNCTIONS = [ diff --git a/src/operator/random/pdf_op.cc b/src/operator/random/pdf_op.cc new file mode 100644 index 000000000000..070ca81d61ff --- /dev/null +++ b/src/operator/random/pdf_op.cc @@ -0,0 +1,319 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2018 by Contributors + * \file pdf_op.cc + * \brief CPU-operators for computing the pdf of random distributions. + */ + +#include "./pdf_op.h" + +namespace mxnet { +namespace op { + +DMLC_REGISTER_PARAMETER(PdfParam); + +#define MXNET_OPERATOR_REGISTER_PDF(distr, pdffunc, num_parms, \ + parm_name_1, parm_name_2, \ + parm_desc_1, parm_desc_2, \ + description, vectorparms) \ + NNVM_REGISTER_OP(_random_pdf_##distr) \ + .add_alias("random_pdf_" #distr) \ + .describe(description()+std::string(ADD_FILELINE)) \ + .set_num_inputs(num_parms+1) \ + .set_num_outputs(1) \ + .set_attr_parser(ParamParser) \ + .set_attr("FListInputNames", \ + [](const NodeAttrs& attrs) { \ + std::vector v = {"sample", parm_name_1, parm_name_2}; \ + v.resize(num_parms+1); \ + return v; \ + }) \ + .set_attr("FInferShape", PdfOpShape) \ + .set_attr("FInferType", ElemwiseType) \ + .set_attr("FCompute", PdfOpForward) \ + .set_attr("FGradient", ElemwiseGradUseInOut{"_backward_pdf_" #distr}) \ + .add_argument("sample", "NDArray-or-Symbol", "Samples from the distributions.") \ + .add_argument(parm_name_1, "NDArray-or-Symbol", parm_desc_1) \ + .add_arguments(PdfParam::__FIELDS__()) + +#define MXNET_OPERATOR_REGISTER_PDF_GRAD(distr, pdffunc, num_parms, vectorparms) \ + NNVM_REGISTER_OP(_backward_pdf_##distr) \ + .set_num_inputs(num_parms+3) \ + .set_num_outputs(num_parms+1) \ + .set_attr_parser(ParamParser) \ + .set_attr("FInplaceOption", [](const NodeAttrs& attrs) \ + { std::vector > v = {{1, 0}, {2, 1}, {3, 2}}; \ + v.resize(num_parms+1); \ + return v; }) \ + .set_attr("FResourceRequest", [](const NodeAttrs& attrs) \ + { return std::vector{ResourceRequest::kTempSpace}; }) \ + .set_attr("TIsBackward", true) \ + .set_attr("FCompute", PdfOpBackward); + + +#define MXNET_OPERATOR_REGISTER_PDF1(distr, pdffunc, parm_name, parm_desc, \ + description, vectorparms) \ + MXNET_OPERATOR_REGISTER_PDF(distr, pdffunc, 1, parm_name, parm_name, \ + parm_desc, parm_desc, description, vectorparms); \ + MXNET_OPERATOR_REGISTER_PDF_GRAD(distr, pdffunc, 1, vectorparms) + +#define MXNET_OPERATOR_REGISTER_PDF2(distr, pdffunc, parm_name_1, parm_name_2, \ + parm_desc_1, parm_desc_2, description) \ + MXNET_OPERATOR_REGISTER_PDF(distr, pdffunc, 2, parm_name_1, parm_name_2, \ + parm_desc_1, parm_desc_2, description, false) \ + .add_argument(parm_name_2, "NDArray-or-Symbol", parm_desc_2); \ + MXNET_OPERATOR_REGISTER_PDF_GRAD(distr, pdffunc, 2, false) + +inline std::string uniform_desc() { + return std::string(R"code(Computes the value of the PDF of *sample* of +uniform distributions on the intervals given by *[low,high)*. + +*low* and *high* must have the same shape, which must match the leftmost subshape +of *sample*. That is, *sample* can have the same shape as *low* and *high*, in which +case the output contains one density per distribution, or *sample* can be a tensor +of tensors with that shape, in which case the output is a tensor of densities such that +the densities at index *i* in the output are given by the samples at index *i* in *sample* +parameterized by the values of *low* and *high* at index *i*. + +Examples:: + + random_pdf_uniform(sample=[[1,2,3,4]], low=[0], high=[10]) = [0.1, 0.1, 0.1, 0.1] + + sample = [[[1, 2, 3], + [1, 2, 3]], + [[1, 2, 3], + [1, 2, 3]]] + low = [[0, 0], + [0, 0]] + high = [[ 5, 10], + [15, 20]] + random_pdf_uniform(sample=sample, low=low, high=high) = + [[[0.2, 0.2, 0.2 ], + [0.1, 0.1, 0.1 ]], + [[0.06667, 0.06667, 0.06667], + [0.05, 0.05, 0.05 ]]] + +)code"); +} + +inline std::string normal_desc() { + return std::string(R"code(Computes the value of the PDF of *sample* of +normal distributions with parameters *mu* (mean) and *sigma* (standard deviation). + +*mu* and *sigma* must have the same shape, which must match the leftmost subshape +of *sample*. That is, *sample* can have the same shape as *mu* and *sigma*, in which +case the output contains one density per distribution, or *sample* can be a tensor +of tensors with that shape, in which case the output is a tensor of densities such that +the densities at index *i* in the output are given by the samples at index *i* in *sample* +parameterized by the values of *mu* and *sigma* at index *i*. + +Examples:: + + sample = [[-2, -1, 0, 1, 2]] + random_pdf_normal(sample=sample, mu=[0], sigma=[1]) = + [[0.05399097, 0.24197073, 0.3989423, 0.24197073, 0.05399097]] + + random_pdf_normal(sample=sample*2, mu=[0,0], sigma=[1,2]) = + [[0.05399097, 0.24197073, 0.3989423, 0.24197073, 0.05399097], + [0.12098537, 0.17603266, 0.19947115, 0.17603266, 0.12098537]] +)code"); +} + +inline std::string gamma_desc() { + return std::string(R"code(Computes the value of the PDF of *sample* of +gamma distributions with parameters *alpha* (shape) and *beta* (rate). + +*alpha* and *beta* must have the same shape, which must match the leftmost subshape +of *sample*. That is, *sample* can have the same shape as *alpha* and *beta*, in which +case the output contains one density per distribution, or *sample* can be a tensor +of tensors with that shape, in which case the output is a tensor of densities such that +the densities at index *i* in the output are given by the samples at index *i* in *sample* +parameterized by the values of *alpha* and *beta* at index *i*. + +Examples:: + + random_pdf_gamma(sample=[[1,2,3,4,5]], alpha=[5], beta=[1]) = + [[0.01532831, 0.09022352, 0.16803136, 0.19536681, 0.17546739]] + + sample = [[1, 2, 3, 4, 5], + [2, 3, 4, 5, 6], + [3, 4, 5, 6, 7]] + + random_pdf_gamma(sample=sample, alpha=[5,6,7], beta=[1,1,1]) = + [[0.01532831, 0.09022352, 0.16803136, 0.19536681, 0.17546739], + [0.03608941, 0.10081882, 0.15629345, 0.17546739, 0.16062315], + [0.05040941, 0.10419563, 0.14622283, 0.16062315, 0.14900276]] +)code"); +} + +inline std::string exponential_desc() { + return std::string(R"code(Computes the value of the PDF of *sample* of +exponential distributions with parameters *lam* (rate). + +The shape of *lam* must match the leftmost subshape of *sample*. That is, *sample* +can have the same shape as *lam*, in which case the output contains one density per +distribution, or *sample* can be a tensor of tensors with that shape, in which case +the output is a tensor of densities such that the densities at index *i* in the output +are given by the samples at index *i* in *sample* parameterized by the value of *lam* +at index *i*. + +Examples:: + + random_pdf_exponential(sample=[[1, 2, 3]], lam=[1]) = + [[0.36787945, 0.13533528, 0.04978707]] + + sample = [[1,2,3], + [1,2,3], + [1,2,3]] + + random_pdf_exponential(sample=sample, lam=[1,0.5,0.25]) = + [[0.36787945, 0.13533528, 0.04978707], + [0.30326533, 0.18393973, 0.11156508], + [0.1947002, 0.15163267, 0.11809164]] +)code"); +} + +inline std::string poisson_desc() { + return std::string(R"code(Computes the value of the PDF of *sample* of +Poisson distributions with parameters *lam* (rate). + +The shape of *lam* must match the leftmost subshape of *sample*. That is, *sample* +can have the same shape as *lam*, in which case the output contains one density per +distribution, or *sample* can be a tensor of tensors with that shape, in which case +the output is a tensor of densities such that the densities at index *i* in the output +are given by the samples at index *i* in *sample* parameterized by the value of *lam* +at index *i*. + +Examples:: + + random_pdf_poisson(sample=[[0,1,2,3]], lam=[1]) = + [[0.36787945, 0.36787945, 0.18393973, 0.06131324]] + + sample = [[0,1,2,3], + [0,1,2,3], + [0,1,2,3]] + + random_pdf_poisson(sample=sample, lam=[1,2,3]) = + [[0.36787945, 0.36787945, 0.18393973, 0.06131324], + [0.13533528, 0.27067056, 0.27067056, 0.18044704], + [0.04978707, 0.14936121, 0.22404182, 0.22404182]] +)code"); +} + +inline std::string negative_binomial_desc() { + return std::string(R"code(Computes the value of the PDF of samples of +negative binomial distributions with parameters *k* (failure limit) and *p* (failure probability). + +*k* and *p* must have the same shape, which must match the leftmost subshape +of *sample*. That is, *sample* can have the same shape as *k* and *p*, in which +case the output contains one density per distribution, or *sample* can be a tensor +of tensors with that shape, in which case the output is a tensor of densities such that +the densities at index *i* in the output are given by the samples at index *i* in *sample* +parameterized by the values of *k* and *p* at index *i*. + +Examples:: + + random_pdf_negative_binomial(sample=[[1,2,3,4]], k=[1], p=a[0.5]) = + [[0.25, 0.125, 0.0625, 0.03125]] + + # Note that k may be real-valued + sample = [[1,2,3,4], + [1,2,3,4]] + random_pdf_negative_binomial(sample=sample, k=[1, 1.5], p=[0.5, 0.5]) = + [[0.25, 0.125, 0.0625, 0.03125 ], + [0.26516506, 0.16572815, 0.09667476, 0.05437956]] +)code"); +} + +inline std::string generalized_negative_binomial_desc() { + return std::string(R"code(Computes the value of the PDF of *sample* of +generalized negative binomial distributions with parameters *mu* (mean) +and *alpha* (dispersion). This can be understood as a reparameterization of +the negative binomial, where *k* = *1 / alpha* and *p* = *1 / (mu \* alpha + 1)*. + +*mu* and *alpha* must have the same shape, which must match the leftmost subshape +of *sample*. That is, *sample* can have the same shape as *mu* and *alpha*, in which +case the output contains one density per distribution, or *sample* can be a tensor +of tensors with that shape, in which case the output is a tensor of densities such that +the densities at index *i* in the output are given by the samples at index *i* in *sample* +parameterized by the values of *mu* and *alpha* at index *i*. + +Examples:: + + random_pdf_generalized_negative_binomial(sample=[[1, 2, 3, 4]], alpha=[1], mu=[1]) = + [[0.25, 0.125, 0.0625, 0.03125]] + + sample = [[1,2,3,4], + [1,2,3,4]] + random_pdf_generalized_negative_binomial(sample=sample, alpha=[1, 0.6666], mu=[1, 1.5]) = + [[0.25, 0.125, 0.0625, 0.03125 ], + [0.26517063, 0.16573331, 0.09667706, 0.05437994]] +)code"); +} + +inline std::string dirichlet_desc() { + return std::string(R"code(Computes the value of the PDF of *sample* of +Dirichlet distributions with parameter *alpha*. + +The shape of *alpha* must match the leftmost subshape of *sample*. That is, *sample* +can have the same shape as *alpha*, in which case the output contains one density per +distribution, or *sample* can be a tensor of tensors with that shape, in which case +the output is a tensor of densities such that the densities at index *i* in the output +are given by the samples at index *i* in *sample* parameterized by the value of *alpha* +at index *i*. + +Examples:: + + random_pdf_dirichlet(sample=[[1,2],[2,3],[3,4]], alpha=[2.5, 2.5]) = + [38.413498, 199.60245, 564.56085] + + sample = [[[1, 2, 3], [10, 20, 30], [100, 200, 300]], + [[0.1, 0.2, 0.3], [0.01, 0.02, 0.03], [0.001, 0.002, 0.003]]] + + random_pdf_dirichlet(sample=sample, alpha=[0.1, 0.4, 0.9]) = + [[2.3257459e-02, 5.8420084e-04, 1.4674458e-05], + [9.2589635e-01, 3.6860607e+01, 1.4674468e+03]] +)code"); +} + +MXNET_OPERATOR_REGISTER_PDF2(uniform, PDF_Uniform, "low", "high", + "Lower bounds of the distributions.", "Upper bounds of the distributions.", uniform_desc) +MXNET_OPERATOR_REGISTER_PDF2(normal, PDF_Normal, "mu", "sigma", + "Means of the distributions.", "Standard deviations of the distributions.", normal_desc) +MXNET_OPERATOR_REGISTER_PDF2(gamma, PDF_Gamma, "alpha", "beta", + "Alpha (shape) parameters of the distributions.", "Beta (scale) parameters of the distributions.", + gamma_desc) +MXNET_OPERATOR_REGISTER_PDF1(exponential, PDF_Exponential, "lam", + "Lambda (rate) parameters of the distributions.", exponential_desc, false) +MXNET_OPERATOR_REGISTER_PDF1(poisson, PDF_Poisson, "lam", + "Lambda (rate) parameters of the distributions.", poisson_desc, false) +MXNET_OPERATOR_REGISTER_PDF2(negative_binomial, PDF_NegativeBinomial, "k", "p", + "Limits of unsuccessful experiments.", "Failure probabilities in each experiment.", + negative_binomial_desc) +MXNET_OPERATOR_REGISTER_PDF2(generalized_negative_binomial, + PDF_GeneralizedNegativeBinomial, "mu", "alpha", + "Means of the distributions.", "Alpha (dispersion) parameters of the distributions.", + generalized_negative_binomial_desc) +MXNET_OPERATOR_REGISTER_PDF1(dirichlet, PDF_Dirichlet, "alpha", + "Concentration parameters of the distributions.", dirichlet_desc, true) + +} // namespace op +} // namespace mxnet diff --git a/src/operator/random/pdf_op.cu b/src/operator/random/pdf_op.cu new file mode 100644 index 000000000000..e77720b2c329 --- /dev/null +++ b/src/operator/random/pdf_op.cu @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2018 by Contributors + * \file pdf_op.cu + * \brief GPU-operators for computing the pdf of random distributions. + */ + +#include "./pdf_op.h" + +namespace mxnet { +namespace op { + +#define MXNET_OPERATOR_REGISTER_PDF(distr, pdffunc, num_parms, vector_parms) \ + NNVM_REGISTER_OP(_random_pdf_##distr) \ + .set_attr("FCompute", PdfOpForward); \ + NNVM_REGISTER_OP(_backward_pdf_##distr) \ + .set_attr("FCompute", PdfOpBackward); + +MXNET_OPERATOR_REGISTER_PDF(uniform, PDF_Uniform, 2, false) +MXNET_OPERATOR_REGISTER_PDF(normal, PDF_Normal, 2, false) +MXNET_OPERATOR_REGISTER_PDF(gamma, PDF_Gamma, 2, false) +MXNET_OPERATOR_REGISTER_PDF(exponential, PDF_Exponential, 1, false) +MXNET_OPERATOR_REGISTER_PDF(poisson, PDF_Poisson, 1, false) +MXNET_OPERATOR_REGISTER_PDF(negative_binomial, PDF_NegativeBinomial, 2, false) +MXNET_OPERATOR_REGISTER_PDF(generalized_negative_binomial, + PDF_GeneralizedNegativeBinomial, 2, false) +MXNET_OPERATOR_REGISTER_PDF(dirichlet, PDF_Dirichlet, 1, true) + +} // namespace op +} // namespace mxnet diff --git a/src/operator/random/pdf_op.h b/src/operator/random/pdf_op.h new file mode 100644 index 000000000000..62d473927fb0 --- /dev/null +++ b/src/operator/random/pdf_op.h @@ -0,0 +1,622 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2018 by Contributors + * \file pdf_op.h + * \brief Operators for computing the pdf of random distributions. + */ +#ifndef MXNET_OPERATOR_RANDOM_PDF_OP_H_ +#define MXNET_OPERATOR_RANDOM_PDF_OP_H_ + +#include +#include +#include +#include "../mshadow_op.h" +#include "../mxnet_op.h" +#include "../operator_common.h" +#include "../elemwise_op_common.h" +#include "../special_functions-inl.h" +#include "../tensor/broadcast_reduce_op.h" + +namespace mxnet { +namespace op { + +template +MSHADOW_XINLINE static DType ceph_psi(DType val) { return special_functions::cephes::psi(val); } +template<> +MSHADOW_XINLINE mshadow::half::half_t ceph_psi(mshadow::half::half_t val) { + return special_functions::cephes::psi(val); +} + +template +struct PDF_Uniform { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, + DType *out, IType1 *sample, IType2 *lower, IType2 *upper) { + const int index(start / sample_size); + const DType l(lower[index]), h(upper[index]); + const int end = start + length; + for (int i = start; i < end; ++i) { + // No check whether sample is in the support. + out[i] = logpdf ? -DType(log(h - l)) : DType(1.0) / (h - l); + } + } +}; + +template +struct PDF_Uniform_Grad { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, OpReqType req, + DType *out, IType1 *sample, IType2 *lower, IType2 *upper, + DType *grad_out, IType1 *grad_sample, IType2 *grad_lower, IType2 *grad_upper) { + const int index(start / sample_size); + const DType l(lower[index]), h(upper[index]); + + const int end = start + length; + for (int i = start; i < end; ++i) { + const DType scaling(grad_out[i]*(logpdf ? DType(1) : out[i])); + grad_lower[i] = scaling / (h - l); + grad_upper[i] = scaling / (l - h); + KERNEL_ASSIGN(grad_sample[i], req, 0); + } + } +}; + +template +struct PDF_Normal { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, + DType *out, IType1 *sample, IType2 *loc, IType2 *scale) { + const int index(start / sample_size); + const DType u(loc[index]), s(scale[index]), sq(s * s); + const DType normalizer(sqrt(2.0 * mxnet_op::PI) * s); + + const int end = start + length; + for (int i = start; i < end; ++i) { + const DType x(sample[i]); + const DType exponent((DType(-0.5) * (x - u) * (x - u)) / (sq)); + out[i] = logpdf ? exponent - log(normalizer) : exp(exponent) / normalizer; + } + } +}; + +template +struct PDF_Normal_Grad { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, OpReqType req, + DType *out, IType1 *sample, IType2 *loc, IType2 *scale, + DType *grad_out, IType1 *grad_sample, IType2 *grad_loc, IType2 *grad_scale) { + const int index(start / sample_size); + const DType u(loc[index]), s(scale[index]), s_squared(s * s), s_cubed(s_squared * s); + + const int end = start + length; + for (int i = start; i < end; ++i) { + const DType x(sample[i]); + const DType scaling(grad_out[i]*(logpdf ? DType(1) : out[i])); + grad_loc[i] = scaling * (x - u) / s_squared; + grad_scale[i] = scaling * ((x - u) * (x - u) - s_squared) / s_cubed; + KERNEL_ASSIGN(grad_sample[i], req, scaling * (u - x) / s_squared); + } + } +}; + +template +struct PDF_Gamma { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, + DType *out, IType1 *sample, IType2 *alpha, IType2 *beta) { + const int index(start / sample_size); + const DType a(alpha[index]), b(beta[index]), lgamma_a(lgamma(a)), a_log_b(a * log(b)); + + const int end = start + length; + for (int i = start; i < end; ++i) { + const DType x(sample[i]); + const DType lpdf(a_log_b + (a - 1) * log(x) - b * x - lgamma_a); + out[i] = logpdf ? lpdf : DType(exp(lpdf)); + } + } +}; + +template +struct PDF_Gamma_Grad { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, OpReqType req, + DType *out, IType1 *sample, IType2 *alpha, IType2 *beta, + DType *grad_out, IType1 *grad_sample, IType2 *grad_alpha, IType2 *grad_beta) { + const int index(start / sample_size); + const DType a(alpha[index]), b(beta[index]), log_b(log(b)), ceph_psi_a(ceph_psi(a)); + + const int end = start + length; + for (int i = start; i < end; ++i) { + const DType x(sample[i]); + const DType scaling(grad_out[i]*(logpdf ? DType(1) : out[i])); + grad_alpha[i] = scaling * (log_b + log(x) - ceph_psi_a); + grad_beta[i] = scaling * (a / b - x); + KERNEL_ASSIGN(grad_sample[i], req, scaling * ((a - 1) / x - b)); + } + } +}; + +template +struct PDF_Exponential { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, + DType *out, IType1 *sample, IType2 *lambda) { + const int index(start / sample_size); + const DType l(lambda[index]), log_l(log(l)); + + const int end = start + length; + for (int i = start; i < end; ++i) { + const DType x(sample[i]); + out[i] = logpdf ? log_l - l * x : l * exp(-l * x); + } + } +}; + +template +struct PDF_Exponential_Grad { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, OpReqType req, + DType *out, IType1 *sample, IType2 *lambda, + DType *grad_out, IType1 *grad_sample, IType2 *grad_lambda) { + const int index(start / sample_size); + const DType l(lambda[index]); + + const int end = start + length; + for (int i = start; i < end; ++i) { + const DType x(sample[i]); + const DType scaling(grad_out[i]*(logpdf ? DType(1) : out[i])); + grad_lambda[i] = scaling * (DType(1) / l - x); + KERNEL_ASSIGN(grad_sample[i], req, -scaling * l); + } + } +}; + +template +struct PDF_Poisson { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, + DType *out, IType1 *sample, IType2 *lambda) { + const int index(start / sample_size); + const DType l(lambda[index]), log_l(log(l)); + + const int end = start + length; + for (int i = start; i < end; ++i) { + const DType x(sample[i]); + const DType lpdf((x * log_l - lgamma(x + 1)) - l); + out[i] = logpdf ? lpdf : DType(exp(lpdf)); + } + } +}; + +template +struct PDF_Poisson_Grad { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, OpReqType req, + DType *out, IType1 *sample, IType2 *lambda, + DType *grad_out, IType1 *grad_sample, IType2 *grad_lambda) { + const int index(start / sample_size); + const DType l(lambda[index]); + + const int end = start + length; + for (int i = start; i < end; ++i) { + const DType x(sample[i]); + const DType scaling(grad_out[i]*(logpdf ? DType(1) : out[i])); + grad_lambda[i] = scaling * (x / l - DType(1)); + KERNEL_ASSIGN(grad_sample[i], req, 0); + } + } +}; + + +template +struct PDF_NegativeBinomial { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, + DType *out, IType1 *sample, IType2 *limit, IType2 *prob) { + const int index(start / sample_size); + const DType l(limit[index]), p(prob[index]), lgamma_l(lgamma(l)); + + const int end = start + length; + for (int i = start; i < end; ++i) { + const DType x(sample[i]); + const DType lpdf((lgamma(x + l) - lgamma(x + 1) - lgamma_l) + l * log(p) + x * log(1 - p)); + out[i] = logpdf ? lpdf : DType(exp(lpdf)); + } + } + + template + MSHADOW_XINLINE static DType LPDF(DType l, DType p, DType x) { + // Note that "p" is the failure and not the success probability. + return (lgamma(x + l) - lgamma(x + 1) - lgamma(l)) + l * log(p) + x * log(1 - p); + } +}; + +template +struct PDF_NegativeBinomial_Grad { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, OpReqType req, + DType *out, IType1 *sample, IType2 *limit, IType2 *prob, + DType *grad_out, IType1 *grad_sample, IType2 *grad_limit, IType2 *grad_prob) { + const int index(start / sample_size); + const int end = start + length; + for (int i = start; i < end; ++i) { + DType grad_l(0), grad_p(0); + LPDF_GRAD(DType(limit[index]), DType(prob[index]), + DType(sample[i]), out[i], + grad_out[i], &grad_l, &grad_p); + grad_limit[i] = grad_l; + grad_prob[i] = grad_p; + KERNEL_ASSIGN(grad_sample[i], req, 0); + } + } + + template + MSHADOW_XINLINE static void LPDF_GRAD(DType l, DType p, DType x, DType o, DType grad_o, + DType* grad_l, DType* grad_p) { + const DType scaling(grad_o * (logpdf ? DType(1) : o)); + *grad_l = scaling * ((ceph_psi(x + l) - ceph_psi(l)) + log(p)); + *grad_p = scaling * (l / p - x / (1 - p)); + } +}; + +template +struct PDF_GeneralizedNegativeBinomial { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, + DType *out, IType1 *sample, IType2 *mu, IType2 *alpha) { + const int index(start / sample_size); + + // Reparameterize with limit = 1 / alpha, prob = 1 / (mu * alpha + 1) + const DType limit(1.0 / alpha[index]), prob(1.0 / (mu[index]*alpha[index]+1.0)); + + const int end = start + length; + for (int i = start; i < end; ++i) { + const DType lpdf(PDF_NegativeBinomial::LPDF(limit, prob, DType(sample[i]))); + out[i] = logpdf ? lpdf : DType(exp(lpdf)); + } + } +}; + +template +struct PDF_GeneralizedNegativeBinomial_Grad { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, OpReqType req, + DType *out, IType1 *sample, IType2 *mu, IType2 *alpha, + DType *grad_out, IType1 *grad_sample, IType2 *grad_mu, IType2 *grad_alpha) { + const int index(start / sample_size); + const DType fmu(mu[index]), falpha(alpha[index]), den(fmu * falpha + 1.0); + + // Reparameterize with limit = 1 / alpha, prob = 1 / (mu * alpha + 1) + const DType limit(1.0 / falpha), prob(1.0 / (fmu * falpha + 1.0)); + + const int end = start + length; + for (int i = start; i < end; ++i) { + // Grad returned as d_limit, d_prob + DType grad_l(0), grad_p(0); + PDF_NegativeBinomial_Grad::LPDF_GRAD(limit, prob, + DType(sample[i]), out[i], + grad_out[i], &grad_l, &grad_p); + grad_mu[i] = -grad_p * falpha / (den * den); + grad_alpha[i] = -grad_l / (falpha * falpha) - grad_p * fmu / (den * den); + KERNEL_ASSIGN(grad_sample[i], req, 0); + } + } +}; + +template +struct PDF_Dirichlet { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, int k, + DType *out, IType1 *sample, IType2 *alpha) { + const int index(start / sample_size); + const int end = start + length; + for (int i = start; i < end; ++i) { + const IType1 *cur_sample = sample + i * k; + const IType2 *cur_alpha = alpha + index * k; + DType sum_alpha(0), sum_lgamma(0), sum_sample(0); + for (int j = 0; j < k; ++j) { + sum_alpha += cur_alpha[j]; + sum_lgamma += lgamma(cur_alpha[j]); + sum_sample += (cur_alpha[j]-1) * log(cur_sample[j]); + } + DType lpdf(sum_sample + (lgamma(sum_alpha) - sum_lgamma)); + out[i] = logpdf ? lpdf : DType(exp(lpdf)); + } + } +}; + + +template +struct PDF_Dirichlet_Grad { + template + MSHADOW_XINLINE static void Map(int start, int length, int sample_size, + OpReqType req, int k, + DType *out, IType1 *sample, IType2 *alpha, + DType *grad_out, IType1 *grad_sample, IType2 *grad_alpha + ) { + const int index(start / sample_size); + const int end = start + length; + + for (int i = start; i < end; ++i) { + // Digamma function + const IType1 *cur_sample = sample + i * k; + const IType2 *cur_alpha = alpha + index * k; + + const DType scaling(grad_out[i]*(logpdf ? DType(1) : out[i])); + DType sum_alpha(0); + for (int j = 0; j < k; ++j) { + sum_alpha += cur_alpha[j]; + } + const DType psi_sum(ceph_psi(sum_alpha)); + + for (int j = 0; j < k; ++j) { + size_t grad_alpha_index = i%sample_size + sample_size * (j + k * index); + size_t grad_sample_index = i * k + j; + + // order grad_alpha differently to allow efficient reduction at the end. + grad_alpha[grad_alpha_index] = + scaling * (log(cur_sample[j]) + (psi_sum - ceph_psi(cur_alpha[j]))); + KERNEL_ASSIGN(grad_sample[grad_sample_index], + req, scaling * (cur_alpha[j]-1) / cur_sample[j]); + } + } + } +}; + +struct PdfParam : public dmlc::Parameter { + bool is_log; + DMLC_DECLARE_PARAMETER(PdfParam) { + DMLC_DECLARE_FIELD(is_log).set_default(false) + .describe("If set, compute the density of the log-probability instead of the probability."); + } +}; + +template +inline bool PdfOpShape(const nnvm::NodeAttrs& attrs, + std::vector* in_attrs, + std::vector* out_attrs) { + CHECK_GT(in_attrs->size(), 1) + << "pdf operator takes at least 2 arguments (" << in_attrs->size() << " given)"; + CHECK_EQ(out_attrs->size(), 1); + // All inputs must be defined in order to infer output shape. + if ( std::all_of((*in_attrs).begin(), + (*in_attrs).end(), + [](const TShape& s){ return s.ndim() > 0; }) ) { + // Tensors of distribution parameters must have same shape. + for (size_t i = 2; i < in_attrs->size(); ++i) { + SHAPE_ASSIGN_CHECK(*in_attrs, i, (*in_attrs)[i - 1]); + } + // Tensors of distribution parameters must match leftmost subshape of samples. + CHECK_LE((*in_attrs)[1].ndim(), (*in_attrs)[0].ndim()) + << "dimension of input samples (" << (*in_attrs)[0].ndim() + << ") must be at least dimension of distribution parameters ("<< (*in_attrs)[1].ndim() << ")"; + TShape tshape((*in_attrs)[0].begin(), (*in_attrs)[0].begin() + (*in_attrs)[1].ndim()); + if (vparm) { + *(tshape.end() - 1) = *((*in_attrs)[0].end() - 1); + } + for (size_t i = 1; i < in_attrs->size(); ++i) { + SHAPE_ASSIGN_CHECK(*in_attrs, i, tshape); + } + // Output shape must equal input tensor of samples except for last dimension if we are + // dealing with samples that are itself vectors. Be aware of the special case where we + // are dealing with a single vector sample. + if (vparm && ((*in_attrs)[0].ndim() == 1)) { + // Special case where we are dealing with a single vector sample. + SHAPE_ASSIGN_CHECK(*out_attrs, 0, mshadow::Shape1(1)); + } else { + TShape oshape((*in_attrs)[0].begin(), (*in_attrs)[0].end() - (vparm ? 1 : 0)); + SHAPE_ASSIGN_CHECK(*out_attrs, 0, oshape); + } + return true; + } + return false; +} + +template +struct LaunchExWrapper { + template + MSHADOW_XINLINE static void Map(const int start, const int length, const int sample_size, + Args... args) { + // Apply the operator to the sample in strides of sample_size, so that + // the operators can assume that their distribution parameters are constant. + int i = start; + + // Get aligned + const int align_step = sample_size - (i % sample_size); + const int first_stride = length > align_step ? align_step : length; + OP::Map(i, first_stride, sample_size, args...); + i += first_stride; + + const int end = start + length - sample_size; + for (; i < end; i += sample_size) { + OP::Map(i, sample_size, sample_size, args...); + } + + // Last stride might not be aligned either + const int last_stride = start + length - i; + if (last_stride > 0) { // Don't overstep even if length <= sample_size + OP::Map(i, last_stride, sample_size, args...); + } + } +}; + +template +struct PdfCaller; + +template +struct PdfCaller { + static void op(const std::vector& inputs, + const std::vector& outputs, + mshadow::Stream *s) { + CHECK_EQ(inputs[0].Size()%inputs[1].Size(), 0); + CHECK_EQ(inputs[0].Size()%outputs[0].Size(), 0); + index_t num_samples(inputs[0].Size() / inputs[1].Size()); + mxnet_op::Kernel, xpu>::LaunchEx(s, outputs[0].Size(), num_samples, + outputs[0].dptr(), inputs[0].dptr(), inputs[1].dptr()); + } +}; + +template +struct PdfCaller { + static void op(const std::vector& inputs, + const std::vector& outputs, + mshadow::Stream *s) { + CHECK_EQ(inputs[0].Size()%inputs[1].Size(), 0); + CHECK_EQ(inputs[0].Size()%outputs[0].Size(), 0); + index_t num_samples(inputs[0].Size() / inputs[1].Size()); + index_t sample_size(inputs[0].Size() / outputs[0].Size()); + + // Covers distributions parametrized by a vector of parameters (Dirichlet distribution). + mxnet_op::Kernel, xpu>::LaunchEx(s, outputs[0].Size(), + num_samples, sample_size, + outputs[0].dptr(), inputs[0].dptr(), inputs[1].dptr()); + } +}; + +template +struct PdfCaller { + static void op(const std::vector& inputs, + const std::vector& outputs, + mshadow::Stream *s) { + CHECK_EQ(inputs[0].Size()%inputs[1].Size(), 0); + CHECK_EQ(inputs[0].Size(), outputs[0].Size()); + index_t num_samples(inputs[0].Size() / inputs[1].Size()); + mxnet_op::Kernel, xpu>::LaunchEx(s, outputs[0].Size(), num_samples, + outputs[0].dptr(), inputs[0].dptr(), + inputs[1].dptr(), inputs[2].dptr()); + } +}; + +template class pdf, int pnum, bool vparm> +void PdfOpForward(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + CHECK_NE(req[0], kAddTo); + CHECK_EQ(inputs.size(), pnum + 1); + CHECK_EQ(outputs.size(), 1); + mshadow::Stream *s = ctx.get_stream(); + const PdfParam& param = nnvm::get(attrs.parsed); + MSHADOW_REAL_TYPE_SWITCH(outputs[0].type_flag_, DType, { + if ( param.is_log ) { + PdfCaller, pnum, vparm>::op(inputs, outputs, s); + } else { + PdfCaller, pnum, vparm>::op(inputs, outputs, s); + } + }); +} + + +template +struct PdfGradCaller; + +template +struct PdfGradCaller { + static void op(const std::vector& inputs, + const std::vector& req, + const std::vector& grads, + mshadow::Stream *s) { + index_t num_samples(inputs[1].Size() / inputs[2].Size()); + mxnet_op::Kernel, xpu>::LaunchEx(s, inputs[0].Size(), + num_samples, req[0], + inputs[3].dptr(), inputs[1].dptr(), inputs[2].dptr(), + inputs[0].dptr(), grads[0].dptr(), grads[1].dptr()); + } +}; + +template +struct PdfGradCaller { + static void op(const std::vector& inputs, + const std::vector& req, + const std::vector& grads, + mshadow::Stream *s) { + index_t num_samples(inputs[1].Size() / inputs[2].Size()); + index_t sample_size(inputs[1].Size() / inputs[0].Size()); + mxnet_op::Kernel, xpu>::LaunchEx(s, inputs[0].Size(), num_samples, + req[0], sample_size, + inputs[3].dptr(), inputs[1].dptr(), inputs[2].dptr(), + inputs[0].dptr(), grads[0].dptr(), grads[1].dptr()); + } +}; + +template +struct PdfGradCaller { + static void op(const std::vector& inputs, + const std::vector& req, + const std::vector& grads, + mshadow::Stream *s) { + index_t num_samples(inputs[1].Size() / inputs[2].Size()); + mxnet_op::Kernel, xpu>::LaunchEx(s, inputs[0].Size(), + num_samples, req[0], + inputs[4].dptr(), inputs[1].dptr(), inputs[2].dptr(), + inputs[3].dptr(), inputs[0].dptr(), + grads[0].dptr(), grads[1].dptr(), grads[2].dptr()); + } +}; + +template class pdfgrad, int pnum, bool vparm> +void PdfOpBackward(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + using namespace mshadow; + CHECK_EQ(inputs.size(), pnum + 3); + CHECK_EQ(outputs.size(), pnum + 1); + mshadow::Stream *s = ctx.get_stream(); + const PdfParam& param = nnvm::get(attrs.parsed); + const size_t N(outputs[1].Size()); + const TShape src_shape(Shape2(N, outputs[0].Size() / N)), dst_shape(Shape2(N, 1)); + // Inputs to PdfOpBackward: grad, samples, parm1, parm2, pdf. + MSHADOW_REAL_TYPE_SWITCH(outputs[0].type_flag_, DType, { + const size_t red_work_size(broadcast::ReduceWorkspaceSize<2, DType>( + s, dst_shape, kAddTo, src_shape)); + const size_t tmp_size(outputs[0].Size() * pnum * sizeof(DType) + red_work_size); + Tensor tmp_space = + ctx.requested[0].get_space_typed(Shape1(tmp_size), s); + std::vector grads = {outputs[0]}; + grads.push_back(TBlob(tmp_space.dptr_, outputs[0].shape_, + outputs[1].dev_mask(), outputs[1].type_flag_, outputs[1].dev_id())); + if (pnum == 2) { + grads.push_back(TBlob(tmp_space.dptr_ + outputs[0].Size() * sizeof(DType), outputs[0].shape_, + outputs[2].dev_mask(), outputs[2].type_flag_, outputs[2].dev_id())); + } + if (param.is_log) { + PdfGradCaller, pnum, vparm>::op(inputs, req, grads, s); + } else { + PdfGradCaller, pnum, vparm>::op(inputs, req, grads, s); + } + Tensor red_work( + tmp_space.dptr_ + pnum * outputs[0].Size() * sizeof(DType), Shape1(red_work_size), s); + broadcast::Reduce( + s, outputs[1].reshape(dst_shape), req[1], red_work, grads[1].reshape(src_shape)); + if (pnum == 2) { + broadcast::Reduce( + s, outputs[2].reshape(dst_shape), req[2], red_work, grads[2].reshape(src_shape)); + } + }); +} + +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_RANDOM_PDF_OP_H_ diff --git a/tests/python/unittest/test_random.py b/tests/python/unittest/test_random.py index 4d147197a150..8fdca619a5c8 100644 --- a/tests/python/unittest/test_random.py +++ b/tests/python/unittest/test_random.py @@ -25,6 +25,7 @@ from common import setup_module, with_seed, random_seed, teardown import scipy.stats as ss import unittest +from mxnet.test_utils import * def same(a, b): return np.sum(a != b) == 0 @@ -38,6 +39,9 @@ def check_with_device(device, dtype): 'name': 'normal', 'symbol': mx.sym.random.normal, 'ndop': mx.nd.random.normal, + 'pdfsymbol': mx.sym.random_pdf_normal, + 'pdffunc': ss.norm.pdf, + 'discrete': False, 'params': { 'loc': 10.0, 'scale': 0.5 }, 'inputs': [ ('loc',[ [ 0.0, 2.5 ], [ -9.75, -7.0 ] ]) , ('scale',[ [ 1.0, 3.7 ], [ 4.2, 1.5 ] ]) ], 'checks': [ @@ -68,6 +72,9 @@ def check_with_device(device, dtype): 'name': 'uniform', 'symbol': mx.sym.random.uniform, 'ndop': mx.nd.random.uniform, + 'pdfsymbol': mx.sym.random_pdf_uniform, + 'pdffunc': lambda x, low, high: ss.uniform.pdf(x, low, high-low), + 'discrete': False, 'params': { 'low': -1.5, 'high': 3.0 }, 'inputs': [ ('low', [ [ 0.0, 2.5 ], [ -9.75, -1.0 ] ]) , ('high', [ [ 1.0, 3.7 ], [ 4.2, 10.5 ] ]) ], 'checks': [ @@ -89,8 +96,11 @@ def check_with_device(device, dtype): 'name': 'gamma', 'symbol': mx.sym.random.gamma, 'ndop': mx.nd.random.gamma, + 'pdfsymbol': mx.sym.random_pdf_gamma, + 'pdffunc': lambda x, alpha, beta: ss.gamma.pdf(x, alpha, 0, 1/beta), + 'discrete': False, 'params': { 'alpha': 9.0, 'beta': 0.5 }, - 'inputs': [ ('alpha', [ [ 0.0, 2.5 ], [ 9.75, 11.0 ] ]) , ('beta', [ [ 1.0, 0.7 ], [ 0.5, 0.3 ] ]) ], + 'inputs': [ ('alpha', [ [ 0.1, 2.5 ], [ 9.75, 11.0 ] ]) , ('beta', [ [ 1.0, 0.7 ], [ 0.5, 0.3 ] ]) ], 'checks': [ ('mean', lambda x, params: np.mean(x.astype(np.float64)) - params['alpha'] * params['beta'], tol), ('std', lambda x, params: np.std(x.astype(np.float64)) - np.sqrt(params['alpha'] * params['beta'] ** 2), tol) @@ -110,6 +120,9 @@ def check_with_device(device, dtype): 'name': 'exponential', 'symbol': mx.sym.random.exponential, 'ndop': mx.nd.random.exponential, + 'pdfsymbol': mx.sym.random_pdf_exponential, + 'pdffunc': lambda x, lam: ss.expon.pdf(x, 0, 1/lam), + 'discrete': False, 'params': { 'scale': 1.0/4.0 }, 'inputs': [ ('scale', [ [ 1.0/1.0, 1.0/8.5 ], [ 1.0/2.7 , 1.0/0.5 ] ]) ], 'checks': [ @@ -131,6 +144,9 @@ def check_with_device(device, dtype): 'name': 'poisson', 'symbol': mx.sym.random.poisson, 'ndop': mx.nd.random.poisson, + 'pdfsymbol': mx.sym.random_pdf_poisson, + 'pdffunc': ss.poisson.pmf, + 'discrete': True, 'params': { 'lam': 4.0 }, 'inputs': [ ('lam', [ [ 25.0, 8.5 ], [ 2.7 , 0.5 ] ]) ], 'checks': [ @@ -152,6 +168,9 @@ def check_with_device(device, dtype): 'name': 'neg_binomial', 'symbol': mx.sym.random.negative_binomial, 'ndop': mx.nd.random.negative_binomial, + 'pdfsymbol': mx.sym.random_pdf_negative_binomial, + 'pdffunc': ss.nbinom.pmf, + 'discrete': True, 'params': { 'k': 3, 'p': 0.4 }, 'inputs': [ ('k', [ [ 3, 4 ], [ 5 , 6 ] ]) , ('p', [ [ 0.4 , 0.77 ], [ 0.5, 0.84 ] ]) ], 'checks': [ @@ -173,6 +192,9 @@ def check_with_device(device, dtype): 'name': 'gen_neg_binomial', 'symbol': mx.sym.random.generalized_negative_binomial, 'ndop': mx.nd.random.generalized_negative_binomial, + 'pdfsymbol': mx.sym.random_pdf_generalized_negative_binomial, + 'pdffunc': lambda x, mu, alpha: ss.nbinom.pmf(x, 1.0/alpha, 1.0/(mu*alpha+1.0)), + 'discrete': True, 'params': { 'mu': 2.0, 'alpha': 0.3 }, 'inputs': [ ('mu', [ [ 2.0, 2.5 ], [ 1.3, 1.9 ] ]) , ('alpha', [ [ 1.0, 0.1 ], [ 0.2, 0.5 ] ]) ], 'checks': [ @@ -195,6 +217,9 @@ def check_with_device(device, dtype): # Create enough samples such that we get a meaningful distribution. shape = (500, 500) + # Test pdf on smaller shapes as backward checks will take too long otherwise. + # This must be a subshape of the former one. + pdfshape = (30, 30) for symbdic in symbols: name = symbdic['name'] ndop = symbdic['ndop'] @@ -270,7 +295,7 @@ def check_with_device(device, dtype): # check multi-distribution sampling symbol = symbdic['symbol'] params = { 'shape' : shape, 'dtype' : dtype } - single_param = len(symbdic['inputs']) == 1; + single_param = len(symbdic['inputs']) == 1 v1 = mx.sym.Variable('v1') v2 = mx.sym.Variable('v2') Y = symbol(v1,**params) if single_param else symbol(v1,v2,**params) @@ -290,12 +315,84 @@ def check_with_device(device, dtype): for check_name, check_func, tol in symbdic['checks']: assert np.abs(check_func(samples, params)) < tol, "symbolic test: %s check for `%s` did not pass" % (check_name, name) -@with_seed() -def test_random(): - check_with_device(mx.context.current_context(), 'float16') - check_with_device(mx.context.current_context(), 'float32') - check_with_device(mx.context.current_context(), 'float64') + # check pdfs with only a subset of the generated samples + un1 = np.resize(un1, (un1.shape[0], un1.shape[1], pdfshape[0], pdfshape[1])) + print(name) + symbol = symbdic['pdfsymbol'] + pdffunc = symbdic['pdffunc'] + v0 = mx.sym.Variable('v0') + v1 = mx.sym.Variable('v1') + v2 = mx.sym.Variable('v2') + p1 = np.array(symbdic['inputs'][0][1]) + p2 = None if single_param else np.array(symbdic['inputs'][1][1]) + # Move samples away from boundaries of support + if name == 'gamma' or name == 'exponential': + un1 = np.maximum(un1, 1e-1) + if name == 'uniform': + un1 = np.minimum(np.maximum(un1.reshape((un1.shape[0],un1.shape[1],-1)), p1.reshape((p1.shape[0],p1.shape[1],-1))+1e-4), + p2.reshape((p2.shape[0],p2.shape[1],-1))-1e-4).reshape(un1.shape) + for use_log in [False, True]: + test_pdf = symbol(v0, v1, is_log=use_log) if single_param else symbol(v0, v1, v2, is_log=use_log) + forw_atol = 1e-7 if dtype != np.float16 else 1e-3 + forw_rtol = 1e-4 if dtype != np.float16 else 5e-2 + backw_atol = 1e-3 + backw_rtol = 5e-2 + if single_param: + res = pdffunc(un1.reshape((un1.shape[0],un1.shape[1],-1)), + p1.reshape((p1.shape[0],p1.shape[1],-1))).reshape(un1.shape) + if use_log: + res = np.log(res) + check_symbolic_forward(test_pdf, [un1, p1], [res], atol=forw_atol, rtol=forw_rtol, dtype=dtype) + if dtype == np.float64: + grad_nodes = ['v1'] if symbdic['discrete'] else ['v0', 'v1'] + check_numeric_gradient(test_pdf, [un1, p1], grad_nodes=grad_nodes, atol=backw_atol, rtol=backw_rtol, dtype=dtype) + else: + res = pdffunc(un1.reshape((un1.shape[0],un1.shape[1],-1)), + p1.reshape((p1.shape[0],p1.shape[1],-1)), + p2.reshape((p2.shape[0],p2.shape[1],-1))).reshape(un1.shape) + if use_log: + res = np.log(res) + check_symbolic_forward(test_pdf, [un1, p1, p2], [res], atol=forw_atol, rtol=forw_rtol, dtype=dtype) + if dtype == np.float64: + grad_nodes = ['v1', 'v2'] if symbdic['discrete'] else ['v0', 'v1', 'v2'] + print(backw_rtol) + check_numeric_gradient(test_pdf, [un1, p1, p2], grad_nodes=grad_nodes, atol=backw_atol, rtol=backw_rtol, dtype=dtype) + +@with_seed(1000) +def test_dirichlet(): + num_classes = 2 + num = 100 + alpha = np.random.uniform(low=0.5, high=2, size=(4, num_classes)) + + samples = [] + results = [] + for a in alpha: + v = ss.dirichlet.rvs(a, size=num) + samples.append(v) + results.append(ss.dirichlet.logpdf(v.transpose(), a)) + samples = np.concatenate(samples, axis=0).reshape((2, 2, num, num_classes)) + results = np.concatenate(results, axis=0).reshape((2, 2, num)) + + alpha = alpha.reshape((2, 2, num_classes)) + + for dtype in [np.float32, np.float64]: + forw_atol = 1e-5 + forw_rtol = 1e-4 + for use_log in [False, True]: + v0 = mx.sym.Variable('v0') + v1 = mx.sym.Variable('v1') + test_pdf = mx.sym.random_pdf_dirichlet(v0, v1, is_log=use_log) + res = results if use_log else np.exp(results) + check_symbolic_forward(test_pdf, [samples, alpha], [res], atol=forw_atol, rtol=forw_rtol, dtype=dtype) + if dtype == np.float64: + backw_atol = 1e-2 + backw_rtol = 1e-2 + eps = 1e-5 + check_numeric_gradient(test_pdf, [samples, alpha], numeric_eps=eps, atol=backw_atol, rtol=backw_rtol, dtype=dtype) +def test_random(): + for dtype in [np.float16, np.float32, np.float64]: + check_with_device(mx.context.current_context(), dtype) # Set seed variously based on `start_seed` and `num_init_seeds`, then set seed finally to `final_seed` def set_seed_variously(init_seed, num_init_seeds, final_seed): From eec0fb4eda40f4fb8222a8d93d8face454aead09 Mon Sep 17 00:00:00 2001 From: Hao Jin Date: Thu, 18 Jul 2019 22:44:34 -0700 Subject: [PATCH 086/813] Group Normalization (#14959) * GroupNorm * add to amp list * re-write forward --- python/mxnet/contrib/amp/lists/symbol.py | 1 + python/mxnet/gluon/nn/basic_layers.py | 91 +++++- src/operator/nn/group_norm-inl.h | 347 +++++++++++++++++++++++ src/operator/nn/group_norm.cc | 131 +++++++++ src/operator/nn/group_norm.cu | 37 +++ tests/python/unittest/test_gluon.py | 9 + tests/python/unittest/test_operator.py | 91 ++++++ 7 files changed, 706 insertions(+), 1 deletion(-) create mode 100644 src/operator/nn/group_norm-inl.h create mode 100644 src/operator/nn/group_norm.cc create mode 100644 src/operator/nn/group_norm.cu diff --git a/python/mxnet/contrib/amp/lists/symbol.py b/python/mxnet/contrib/amp/lists/symbol.py index 9a587dfa73c1..c6cc3d1b1f00 100644 --- a/python/mxnet/contrib/amp/lists/symbol.py +++ b/python/mxnet/contrib/amp/lists/symbol.py @@ -471,6 +471,7 @@ 'log_softmax', 'InstanceNorm', 'LayerNorm', + 'GroupNorm', 'L2Normalization', 'LRN', 'SoftmaxActivation', diff --git a/python/mxnet/gluon/nn/basic_layers.py b/python/mxnet/gluon/nn/basic_layers.py index 3d6976c32740..b1482ce6dd82 100644 --- a/python/mxnet/gluon/nn/basic_layers.py +++ b/python/mxnet/gluon/nn/basic_layers.py @@ -19,7 +19,8 @@ # pylint: disable= arguments-differ """Basic neural network layers.""" __all__ = ['Sequential', 'HybridSequential', 'Dense', 'Dropout', 'Embedding', - 'BatchNorm', 'InstanceNorm', 'LayerNorm', 'Flatten', 'Lambda', 'HybridLambda'] + 'BatchNorm', 'InstanceNorm', 'LayerNorm', 'GroupNorm', + 'Flatten', 'Lambda', 'HybridLambda'] import warnings import numpy as np @@ -616,6 +617,94 @@ def __repr__(self): for k, v in self._kwargs.items()])) +class GroupNorm(HybridBlock): + r""" + Applies group normalization to the n-dimensional input array. + This operator takes an n-dimensional input array where the leftmost 2 axis are + `batch` and `channel` respectively: + + .. math:: + + x = x.reshape((N, num_groups, C // num_groups, ...)) + axis = (2, ...) + out = \frac{x - mean[x, axis]}{ \sqrt{Var[x, axis] + \epsilon}} * gamma + beta + + Parameters + ---------- + num_groups: int, default 1 + Number of groups to separate the channel axis into. + epsilon: float, default 1e-5 + Small float added to variance to avoid dividing by zero. + center: bool, default True + If True, add offset of `beta` to normalized tensor. + If False, `beta` is ignored. + scale: bool, default True + If True, multiply by `gamma`. If False, `gamma` is not used. + beta_initializer: str or `Initializer`, default 'zeros' + Initializer for the beta weight. + gamma_initializer: str or `Initializer`, default 'ones' + Initializer for the gamma weight. + + + Inputs: + - **data**: input tensor with shape (N, C, ...). + + Outputs: + - **out**: output tensor with the same shape as `data`. + + References + ---------- + `Group Normalization + `_ + + Examples + -------- + >>> # Input of shape (2, 3, 4) + >>> x = mx.nd.array([[[ 0, 1, 2, 3], + [ 4, 5, 6, 7], + [ 8, 9, 10, 11]], + [[12, 13, 14, 15], + [16, 17, 18, 19], + [20, 21, 22, 23]]]) + >>> # Group normalization is calculated with the above formula + >>> layer = GroupNorm() + >>> layer.initialize(ctx=mx.cpu(0)) + >>> layer(x) + [[[-1.5932543 -1.3035717 -1.0138891 -0.7242065] + [-0.4345239 -0.1448413 0.1448413 0.4345239] + [ 0.7242065 1.0138891 1.3035717 1.5932543]] + [[-1.5932543 -1.3035717 -1.0138891 -0.7242065] + [-0.4345239 -0.1448413 0.1448413 0.4345239] + [ 0.7242065 1.0138891 1.3035717 1.5932543]]] + + """ + def __init__(self, num_groups=1, epsilon=1e-5, center=True, scale=True, + beta_initializer='zeros', gamma_initializer='ones', + prefix=None, params=None): + super(GroupNorm, self).__init__(prefix=prefix, params=params) + self._kwargs = {'eps': epsilon, 'num_groups': num_groups, 'center': center, 'scale': scale} + self._num_groups = num_groups + self._epsilon = epsilon + self._center = center + self._scale = scale + self.gamma = self.params.get('gamma', grad_req='write' if scale else 'null', + shape=(num_groups,), init=gamma_initializer, + allow_deferred_init=True) + self.beta = self.params.get('beta', grad_req='write' if center else 'null', + shape=(num_groups,), init=beta_initializer, + allow_deferred_init=True) + + def hybrid_forward(self, F, data, gamma, beta): + norm_data = F.GroupNorm(data, gamma=gamma, beta=beta, num_groups=self._num_groups, eps=self._epsilon) + return norm_data + + def __repr__(self): + s = '{name}({content})' + return s.format(name=self.__class__.__name__, + content=', '.join(['='.join([k, v.__repr__()]) + for k, v in self._kwargs.items()])) + + class Lambda(Block): r"""Wraps an operator or an expression as a Block object. diff --git a/src/operator/nn/group_norm-inl.h b/src/operator/nn/group_norm-inl.h new file mode 100644 index 000000000000..69d5a304dc2c --- /dev/null +++ b/src/operator/nn/group_norm-inl.h @@ -0,0 +1,347 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file group_norm-inl.h + * \brief Implements Group Normalization (https://arxiv.org/abs/1803.08494). + * \author Hao Jin +*/ + +#ifndef MXNET_OPERATOR_NN_GROUP_NORM_INL_H_ +#define MXNET_OPERATOR_NN_GROUP_NORM_INL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "./moments-inl.h" +#include "../mshadow_op.h" +#include "../operator_common.h" +#include "../mxnet_op.h" +#include "../tensor/broadcast_reduce_op.h" + +namespace mxnet { +namespace op { + +namespace groupnorm { +enum GroupNormOpInputs {kData, kGamma, kBeta}; // kGamma: scaling parameters, kBeta: shift biases +enum GroupNormOpOutputs {kOut, kMean, kStd}; // req, out_data +} // namespace groupnorm + +struct GroupNormParam : public dmlc::Parameter { + int num_groups; + float eps; + bool output_mean_var; + DMLC_DECLARE_PARAMETER(GroupNormParam) { + DMLC_DECLARE_FIELD(num_groups).set_default(1) + .describe("Total number of groups."); + DMLC_DECLARE_FIELD(eps).set_default(1e-5f) + .describe("An `epsilon` parameter to prevent division by 0."); + DMLC_DECLARE_FIELD(output_mean_var).set_default(false) + .describe("Output the mean and std calculated along the given axis."); + } +}; + + +template +void GroupNormCompute(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + using namespace mshadow; + using namespace mshadow::expr; + using namespace mxnet_op; + const GroupNormParam& param = nnvm::get(attrs.parsed); + const int num_groups = param.num_groups; + if (req[0] == kNullOp) return; + CHECK_NE(req[0], kAddTo); + + Stream *s = ctx.get_stream(); + const TBlob& data = inputs[groupnorm::kData]; + const TBlob& mean = outputs[groupnorm::kMean]; + const TBlob& std = outputs[groupnorm::kStd]; + const mxnet::TShape& data_shape = data.shape_; + CHECK_GE(data_shape.ndim(), 3U) + << "input should have at least 3 dims and " + << "the first 2 dims should be batch and channel respectively"; + CHECK_EQ(data_shape[1] % num_groups, 0) + << "number of channel should be divisible by num_groups."; + + mxnet::TShape temp_data_shape(data_shape.ndim() + 1, 1); + temp_data_shape[0] = data_shape[0]; + temp_data_shape[1] = num_groups; + temp_data_shape[2] = data_shape[1] / num_groups; + for (int i = 2; i < data_shape.ndim(); ++i) { + temp_data_shape[i+1] = data_shape[i]; + } + + mxnet::TShape moments_shape(temp_data_shape.ndim(), 1); + for (int i = 0; i < data.shape_.ndim(); ++i) { + moments_shape[i] = (i < mean.shape_.ndim()) ? mean.shape_[i] : 1; + } + + mxnet::TShape red_src_shape, red_dst_shape; + BroadcastReduceShapeCompact(temp_data_shape, moments_shape, &red_src_shape, &red_dst_shape); + int channel_size = red_src_shape.Size() / red_dst_shape.Size(); + + TBlob data_ = data.reshape(red_src_shape); + const TBlob& mean_ = mean.reshape(red_dst_shape); + const TBlob& std_ = std.reshape(red_dst_shape); + + Tensor workspace; + + size_t workspace_size = 0; + MSHADOW_REAL_TYPE_SWITCH(data.type_flag_, DType, { + BROADCAST_NDIM_SWITCH(red_dst_shape.ndim(), NDim, { + workspace_size = + broadcast::ReduceWorkspaceSize(s, red_dst_shape, req[0], red_src_shape); + }); + }); + + workspace = ctx.requested[0].get_space_typed(Shape1(workspace_size), s); + + // Calculate mean + MSHADOW_REAL_TYPE_SWITCH(data.type_flag_, DType, { + BROADCAST_NDIM_SWITCH(red_dst_shape.ndim(), NDim, { + broadcast::Reduce( + s, mean_, req[0], workspace, data_); + Tensor mean_data_tensor = mean_.FlatTo1D(s); + mean_data_tensor /= scalar(channel_size); + }); + }); + + TBlob data_grp = data.reshape(temp_data_shape); + const TBlob& mean_grp = mean.reshape(moments_shape); + const TBlob& std_grp = std.reshape(moments_shape); + const TBlob& output = outputs[groupnorm::kOut].reshape(temp_data_shape); + + // Calculate data = data - mean + BinaryBroadcastCompute(attrs, ctx, + {data_grp, mean_grp}, + {kWriteTo}, {output}); + + // Calculate std + const TBlob centered_out = outputs[groupnorm::kOut].reshape(red_src_shape); + MSHADOW_REAL_TYPE_SWITCH(output.type_flag_, DType, { + BROADCAST_NDIM_SWITCH(red_dst_shape.ndim(), NDim, { + broadcast::Reduce( + s, std_, req[0], workspace, centered_out); + Tensor std_data_tensor = std_.FlatTo1D(s); + std_data_tensor = F(std_data_tensor / scalar(channel_size) + + scalar(param.eps)); + }); + }); + + // Calculate data = data / std + BinaryBroadcastCompute(attrs, ctx, + {output, std_grp}, + {kWriteTo}, {output}); + + mxnet::TShape new_param_shape(data_shape.ndim() + 1, 1); + new_param_shape[1] = num_groups; + + const TBlob& gamma = inputs[groupnorm::kGamma].reshape(new_param_shape); + const TBlob& beta = inputs[groupnorm::kBeta].reshape(new_param_shape); + + // Calculate data = data * gamma + BinaryBroadcastCompute(attrs, ctx, + {output, gamma}, + {kWriteTo}, {output}); + // Calculate data = data + beta + BinaryBroadcastCompute(attrs, ctx, + {output, beta}, + {kWriteTo}, {output}); +} + +/* +Calculate the gradient of group normalization. +We have the following gradient for gamma, beta and x: + +\bar{x} = (x - mean) / std +w = og * r / std +grad_gamma = sum(\bar{x} og, exclude_axis) +grad_beta = sum(og, exclude_axis) +grad_x = w - mean(w, axis) - \bar{x} * mean(w * \bar{x}, axis) +*/ +template +void GroupNormGradCompute(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + using namespace mshadow; + using namespace mshadow::expr; + using namespace mxnet_op; + CHECK_EQ(inputs.size(), 5U); + CHECK_EQ(outputs.size(), 3U); + const GroupNormParam& param = nnvm::get(attrs.parsed); + const int num_groups = param.num_groups; + + const TBlob& data = inputs[1]; + const mxnet::TShape& dshape = data.shape_; + + mxnet::TShape temp_dshape(dshape.ndim() + 1, 1); + temp_dshape[0] = dshape[0]; + temp_dshape[1] = num_groups; + temp_dshape[2] = dshape[1] / num_groups; + for (int i = 2; i < dshape.ndim(); ++i) { + temp_dshape[i+1] = dshape[i]; + } + const TBlob& data_ = data.reshape(temp_dshape); + const TBlob& ograd = inputs[0].reshape(temp_dshape); + + Stream *s = ctx.get_stream(); + // Reshape gamma to be broadcastable + mxnet::TShape new_param_shape(dshape.ndim() + 1, 1); + new_param_shape[1] = num_groups; + + const TBlob& gamma = inputs[2].reshape(new_param_shape); + + const TBlob& mean = inputs[3]; + const TBlob& std = inputs[4]; + + mxnet::TShape moments_shape(temp_dshape.ndim(), 1); + for (int i = 0; i < dshape.ndim(); ++i) { + moments_shape[i] = (i < mean.shape_.ndim()) ? mean.shape_[i] : 1; + } + const TBlob& mean_ = mean.reshape(moments_shape); + const TBlob& std_ = std.reshape(moments_shape); + + // Prepare the necessary shapes for reduction + mxnet::TShape red_src_shape, red_dst_shape, red_exclude_src_shape, red_exclude_dst_shape; + BroadcastReduceShapeCompact(temp_dshape, mean_.shape_, &red_src_shape, &red_dst_shape); + BroadcastReduceShapeCompact(temp_dshape, gamma.shape_, + &red_exclude_src_shape, &red_exclude_dst_shape); + + int N = red_src_shape.Size() / red_dst_shape.Size(); + + // Initialize the workspace + Construct the temporary TBlobs + Tensor workspace; + size_t reduce_workspace_size = 0; + size_t data_size = 0; + size_t red_out_size = 0; + MSHADOW_REAL_TYPE_SWITCH(outputs[0].type_flag_, DType, { + data_size = sizeof(DType) * data.Size(); + red_out_size = sizeof(DType) * mean.Size(); + // There are two types of reduction workloads: reduce over axis and reduce exclude axis + // We take the maximum of the workspace sizes required by these workloads. + // Also, we explicitly set the req_type=kAddto in case we want to use it. + BROADCAST_NDIM_SWITCH(red_dst_shape.ndim(), NDim, { + reduce_workspace_size = + std::max(reduce_workspace_size, + broadcast::ReduceWorkspaceSize(s, red_dst_shape, + kAddTo, red_src_shape)); + }); + BROADCAST_NDIM_SWITCH(red_exclude_dst_shape.ndim(), NDim, { + reduce_workspace_size = + std::max(reduce_workspace_size, + broadcast::ReduceWorkspaceSize(s, red_exclude_dst_shape, kAddTo, + red_exclude_src_shape)); + }); + }); + workspace = ctx.requested[0].get_space_typed( + Shape1(reduce_workspace_size + data_size * 2 + red_out_size), s); + const TBlob normalized_data = + TBlob(workspace.dptr_ + reduce_workspace_size, + data_.shape_, data.dev_mask(), data.type_flag_, data.dev_id()); + const TBlob ograd_mult = TBlob(workspace.dptr_ + reduce_workspace_size + data_size, + data_.shape_, ograd.dev_mask(), ograd.type_flag_, ograd.dev_id()); + const TBlob red_out = TBlob(workspace.dptr_ + reduce_workspace_size + data_size * 2, + mean_.shape_, mean.dev_mask(), mean.type_flag_, mean.dev_id()); + // Compute normalized_data = (data - mean) / std + BinaryBroadcastCompute(attrs, ctx, + {data_, mean_}, + {kWriteTo}, {normalized_data}); + BinaryBroadcastCompute(attrs, ctx, + {normalized_data, std_}, + {kWriteTo}, {normalized_data}); + // Calculate grad_beta + if (req[2] != kNullOp) { + MSHADOW_REAL_TYPE_SWITCH(outputs[2].type_flag_, DType, { + BROADCAST_NDIM_SWITCH(red_exclude_dst_shape.ndim(), NDim, { + broadcast::Reduce( + s, outputs[2].reshape(red_exclude_dst_shape), req[2], workspace, + ograd.reshape(red_exclude_src_shape)); + }); + }); + } + // Calculate grad_gamma, it will be sum(ograd * normalized_data, exclude_axis) + ElemwiseBinaryOp::Compute(attrs, ctx, {normalized_data, ograd}, + {kWriteTo}, {ograd_mult}); + if (req[1] != kNullOp) { + MSHADOW_REAL_TYPE_SWITCH(outputs[1].type_flag_, DType, { + BROADCAST_NDIM_SWITCH(red_exclude_dst_shape.ndim(), NDim, { + broadcast::Reduce( + s, outputs[1].reshape(red_exclude_dst_shape), req[1], workspace, + ograd_mult.reshape(red_exclude_src_shape)); + }); + }); + } + + // Calculate grad_data: + // ograd_mult = ograd * gamma / std + // grad_data = ograd_mult - mean(ograd_mult, axis) + // + normalized_data * (-mean(normalized_data * ograd_mult, axis)) + if (req[0] != kNullOp) { + const TBlob output_ = outputs[0].reshape(data_.shape_); + BinaryBroadcastCompute(attrs, ctx, + {ograd, gamma}, + {kWriteTo}, {ograd_mult}); + BinaryBroadcastCompute(attrs, ctx, + {ograd_mult, std_}, + {kWriteTo}, {ograd_mult}); + MSHADOW_REAL_TYPE_SWITCH(outputs[0].type_flag_, DType, { + BROADCAST_NDIM_SWITCH(red_dst_shape.ndim(), NDim, { + broadcast::Reduce( + s, red_out.reshape(red_dst_shape), kWriteTo, workspace, + ograd_mult.reshape(red_src_shape)); + }); + Tensor red_out_tensor = red_out.FlatTo1D(s); + red_out_tensor /= scalar(N); + }); + BinaryBroadcastCompute(attrs, ctx, + {ograd_mult, red_out}, + {req[0]}, {output_}); + ElemwiseBinaryOp::Compute(attrs, ctx, {ograd_mult, normalized_data}, + {kWriteTo}, {ograd_mult}); + MSHADOW_REAL_TYPE_SWITCH(outputs[0].type_flag_, DType, { + BROADCAST_NDIM_SWITCH(red_dst_shape.ndim(), NDim, { + broadcast::Reduce( + s, red_out.reshape(red_dst_shape), kWriteTo, workspace, + ograd_mult.reshape(red_src_shape)); + }); + Tensor red_out_tensor = red_out.FlatTo1D(s); + red_out_tensor /= scalar(-N); + }); + BinaryBroadcastCompute(attrs, ctx, + {normalized_data, red_out}, + {kAddTo}, {output_}); + } +} + +} // namespace op +} // namespace mxnet +#endif // MXNET_OPERATOR_NN_GROUP_NORM_INL_H_ diff --git a/src/operator/nn/group_norm.cc b/src/operator/nn/group_norm.cc new file mode 100644 index 000000000000..b4698abeff83 --- /dev/null +++ b/src/operator/nn/group_norm.cc @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file group_norm.cc + * \brief Implements Group Normalization (https://arxiv.org/abs/1803.08494). +*/ + +#include "group_norm-inl.h" +#include +#include "../elemwise_op_common.h" + +namespace mxnet { +namespace op { + +DMLC_REGISTER_PARAMETER(GroupNormParam); + +static bool GroupNormShape(const nnvm::NodeAttrs& attrs, + mxnet::ShapeVector *in_shape, + mxnet::ShapeVector *out_shape) { + const GroupNormParam& param = nnvm::get(attrs.parsed); + using namespace mshadow; + CHECK_EQ(in_shape->size(), 3U) << "Input:[data, gamma, beta]"; + const mxnet::TShape &dshape = in_shape->at(groupnorm::kData); + CHECK_GE(dshape.ndim(), 3U); + const int num_groups = param.num_groups; + CHECK_EQ(dshape[1] % num_groups, 0) << "# of channels must be divisible by # of groups"; + + if (!mxnet::ndim_is_known(dshape)) { + return false; + } + + in_shape->at(groupnorm::kGamma) = mxnet::TShape(Shape1(num_groups)); + in_shape->at(groupnorm::kBeta) = mxnet::TShape(Shape1(num_groups)); + + out_shape->clear(); + out_shape->push_back(dshape); + + mxnet::TShape moments_shape(2, 1); + moments_shape[0] = dshape[0]; + moments_shape[1] = num_groups; + out_shape->push_back(moments_shape); + out_shape->push_back(moments_shape); + return true; +} + +NNVM_REGISTER_OP(GroupNorm) +.describe(R"code(Group normalization. + +The input channels are separated into ``num_groups`` groups, each containing ``num_channels / num_groups`` channels. +The mean and standard-deviation are calculated separately over the each group. + +.. math:: + + data = data.reshape((N, num_groups, C // num_groups, ...)) + out = \frac{data - mean(data, axis)}{\sqrt{var(data, axis) + \epsilon}} * gamma + beta + +Both ``gamma`` and ``beta`` are learnable parameters. + +)code" ADD_FILELINE) +.set_num_inputs(3) +.set_num_outputs(3) +.set_attr_parser(ParamParser) +.set_attr("FListInputNames", + [](const NodeAttrs& attrs) { + return std::vector{"data", "gamma", "beta"}; +}) +.set_attr("FListOutputNames", + [](const NodeAttrs& attrs) { + return std::vector{"output", "mean", "std"}; +}) +.set_attr("FNumVisibleOutputs", + [](const NodeAttrs& attrs) { + const GroupNormParam& param = nnvm::get(attrs.parsed); + return param.output_mean_var ? 3 : 1; +}) +.set_attr("FInferShape", GroupNormShape) +.set_attr("FInferType", ElemwiseType<3, 3>) +.set_attr("FCompute", GroupNormCompute) +.set_attr("FGradient", [](const nnvm::NodePtr& n, + const std::vector& ograds) { + std::vector heads; + heads.push_back(ograds[0]); // ograd + heads.push_back(n->inputs[0]); // data + heads.push_back(n->inputs[1]); // gamma + heads.emplace_back(nnvm::NodeEntry{n, 1, 0}); // mean + heads.emplace_back(nnvm::NodeEntry{ n, 2, 0 }); // std + return MakeGradNode("_backward_GroupNorm", n, heads, n->attrs.dict); +}) +.set_attr("FInplaceOption", + [](const NodeAttrs& attrs) { + return std::vector >{{0, 0}}; +}) +.set_attr("FResourceRequest", [](const NodeAttrs& n) { + return std::vector{ResourceRequest::kTempSpace}; +}) +.add_argument("data", "NDArray-or-Symbol", "Input data") +.add_argument("gamma", "NDArray-or-Symbol", "gamma array") +.add_argument("beta", "NDArray-or-Symbol", "beta array") +.add_arguments(GroupNormParam::__FIELDS__()); + + +NNVM_REGISTER_OP(_backward_GroupNorm) +.set_num_inputs(5) +.set_num_outputs(3) +.set_attr("TIsBackward", true) +.set_attr_parser(ParamParser) +.set_attr("FCompute", GroupNormGradCompute) +.set_attr("FResourceRequest", [](const NodeAttrs& n) { + return std::vector{ResourceRequest::kTempSpace}; +}); + +} // namespace op +} // namespace mxnet diff --git a/src/operator/nn/group_norm.cu b/src/operator/nn/group_norm.cu new file mode 100644 index 000000000000..136c3337468c --- /dev/null +++ b/src/operator/nn/group_norm.cu @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file group_norm.cu + * \brief Implements Group Normalization (https://arxiv.org/abs/1803.08494). +*/ +#include "./group_norm-inl.h" + +namespace mxnet { +namespace op { + +NNVM_REGISTER_OP(GroupNorm) +.set_attr("FCompute", GroupNormCompute); + +NNVM_REGISTER_OP(_backward_GroupNorm) +.set_attr("FCompute", GroupNormGradCompute); + +} // namespace op +} // namespace mxnet diff --git a/tests/python/unittest/test_gluon.py b/tests/python/unittest/test_gluon.py index d52e7f8bc832..b59ce2d0864c 100644 --- a/tests/python/unittest/test_gluon.py +++ b/tests/python/unittest/test_gluon.py @@ -743,6 +743,15 @@ def test_layernorm(): check_layer_forward(layer, (2, 10, 10, 10)) +@with_seed() +def test_groupnorm(): + layer = nn.GroupNorm() + check_layer_forward(layer, (2, 10, 10, 10)) + layer = nn.GroupNorm(num_groups=2) + check_layer_forward(layer, (2, 10, 10, 10)) + layer = nn.GroupNorm(num_groups=5) + check_layer_forward(layer, (2, 10, 10, 10)) + @with_seed() def test_reflectionpad(): layer = nn.ReflectionPad2D(3) diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index aeddc7a893df..749f0f2bed23 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -1830,6 +1830,97 @@ def _test_batchnorm_impl(op, shape, axis, cudnn_off, output_mean_var): cudnn_off, output_mean_var) +@with_seed() +def test_groupnorm(): + acc_types = {'float16': 'float32', 'float32': 'float64', 'float64': 'float64'} + def x_hat_helper(x, num_groups, eps): + dtype = x.dtype + dshape = x.shape + assert len(dshape) == 4 + acc_type = acc_types[str(dtype)] + new_shape = (dshape[0], num_groups, int(dshape[1] / num_groups), dshape[2], dshape[3]) + new_moments_shape = (dshape[0], num_groups, 1, 1, 1) + data = x.reshape(new_shape) + mean = np.mean(data, axis=(2, 3, 4), keepdims=False, dtype=acc_type).astype(dtype) + std = np.sqrt(np.var(data, axis=(2, 3, 4), dtype=acc_type, keepdims=False).astype(dtype) + eps) + x_hat = (data - mean.reshape(new_moments_shape)) / std.reshape(new_moments_shape) + return x_hat, mean, std + + def np_groupnorm(data, gamma, beta, num_groups, eps): + new_param_shape = (1, num_groups, 1, 1, 1) + x_hat, mean, std = x_hat_helper(data, num_groups, eps) + out = x_hat * gamma.reshape(new_param_shape) + beta.reshape(new_param_shape) + return out.reshape(dshape), mean, std + + def np_groupnorm_grad(ograd, data, gamma, beta, mean, std, num_groups, eps): + x_hat, mean, std = x_hat_helper(data, num_groups, eps) + new_shape = x_hat.shape + dshape = data.shape + dtype = data.dtype + new_moments_shape = (new_shape[0], num_groups, 1, 1, 1) + new_param_shape = (1, num_groups, 1, 1, 1) + acc_type = acc_types[str(dtype)] + ograd = ograd.reshape(new_shape) + data = data.reshape(new_shape) + gamma = gamma.reshape(new_param_shape) + beta = beta.reshape(new_param_shape) + mean = mean.reshape(new_moments_shape) + std = std.reshape(new_moments_shape) + beta_grad = np.sum(ograd, axis=(0, 2, 3, 4), dtype=acc_type, keepdims=False).astype(dtype) + gamma_grad = np.sum(x_hat * ograd, axis=(0, 2, 3, 4), dtype=acc_type, keepdims=False).astype(dtype) + x_hat_grad = ograd * gamma + ograd_mult = x_hat_grad / std + red_out = np.mean(ograd_mult, axis=(2, 3, 4), dtype=acc_type, keepdims=True).astype(dtype) + data_grad = ograd_mult - red_out + red_out = np.mean(ograd_mult * x_hat, axis=(2, 3, 4), dtype=acc_type, keepdims=True).astype(dtype) + data_grad = data_grad - x_hat * red_out + return data_grad.reshape(dshape), gamma_grad, beta_grad + + + batch_size = random.randint(1, 8) + num_groups = random.randint(2, 3) + num_channels = random.randint(2, 3) * num_groups + height = random.randint(1, 5) + width = random.randint(1, 5) + dshape = (batch_size, num_channels, height, width) + param_shape = (num_groups,) + temp_shape = (batch_size, num_groups, int(num_channels / num_groups), height, width) + np_data = np.random.uniform(0.2, 1.0, dshape) + np_gamma = np.random.uniform(-1.0, 1.0, param_shape) + np_beta = np.random.uniform(-1.0, 1.0, param_shape) + data_sym = mx.sym.Variable("data") + gamma_sym = mx.sym.Variable("gamma") + beta_sym = mx.sym.Variable("beta") + for dtype in [np.float16, np.float32, np.float64]: + eps = 1e-2 if dtype == np.float16 else 1e-5 + mx_data = mx.nd.array(np_data, dtype=dtype) + mx_gamma = mx.nd.array(np_gamma, dtype=dtype) + mx_beta = mx.nd.array(np_beta, dtype=dtype) + np_out, np_mean, np_std = np_groupnorm(np_data.astype(dtype), + np_gamma.astype(dtype), + np_beta.astype(dtype), + num_groups=num_groups, + eps=eps) + mx_sym = mx.sym.GroupNorm(data=data_sym, gamma=gamma_sym, beta=beta_sym, + num_groups=num_groups, eps=eps, output_mean_var=True) + check_symbolic_forward(mx_sym, [mx_data, mx_gamma, mx_beta], [np_out, np_mean, np_std], + rtol=1e-2 if dtype == np.float16 else 1e-3, + atol=5e-3 if dtype == np.float16 else 1e-5, dtype=dtype) + mx_sym = mx.sym.GroupNorm(data=data_sym, gamma=gamma_sym, beta=beta_sym, + num_groups=num_groups, eps=eps, output_mean_var=False) + np_ograd = np.random.uniform(-1.0, 1.0, dshape).astype(dtype) + np_data_grad, np_gamma_grad, np_beta_grad = np_groupnorm_grad(np_ograd, + np_data.astype(dtype), + np_gamma.astype(dtype), + np_beta.astype(dtype), + np_mean, np_std, + num_groups, eps) + check_symbolic_backward(mx_sym, [mx_data, mx_gamma, mx_beta], [mx.nd.array(np_ograd)], + [np_data_grad, np_gamma_grad, np_beta_grad], + rtol=1e-2 if dtype == np.float16 else 1e-3, + atol=5e-2 if dtype == np.float16 else 1e-5, dtype=dtype) + + @with_seed() def test_convolution_grouping(): for dim in [1, 2, 3]: From 6bdcef25b18f572ad528c54cc993a434c9932610 Mon Sep 17 00:00:00 2001 From: Chaitanya Prakash Bapat Date: Fri, 19 Jul 2019 10:03:11 -0700 Subject: [PATCH 087/813] Deprecate USE_PROFILER flag (#15595) * deprecate USE_PROFILER * Trigger notification --- CMakeLists.txt | 1 - benchmark/opperf/README.md | 2 -- docs/faq/env_var.md | 2 +- docs/faq/perf.md | 4 +--- docs/install/osx_setup.md | 1 - docs/tutorials/mkldnn/MKLDNN_README.md | 2 +- example/profiler/README.md | 3 +-- make/maven/maven_darwin_mkl.mk | 2 -- make/maven/maven_linux_cu90mkl.mk | 3 --- make/maven/maven_linux_cu92mkl.mk | 3 --- make/maven/maven_linux_mkl.mk | 3 --- make/pip/pip_darwin_cpu.mk | 3 --- make/pip/pip_darwin_mkl.mk | 3 --- make/pip/pip_linux_cpu.mk | 3 --- make/pip/pip_linux_cu100.mk | 3 --- make/pip/pip_linux_cu100mkl.mk | 3 --- make/pip/pip_linux_cu101.mk | 3 --- make/pip/pip_linux_cu101mkl.mk | 3 --- make/pip/pip_linux_cu75.mk | 3 --- make/pip/pip_linux_cu75mkl.mk | 3 --- make/pip/pip_linux_cu80.mk | 3 --- make/pip/pip_linux_cu80mkl.mk | 3 --- make/pip/pip_linux_cu90.mk | 3 --- make/pip/pip_linux_cu90mkl.mk | 3 --- make/pip/pip_linux_cu91.mk | 3 --- make/pip/pip_linux_cu91mkl.mk | 3 --- make/pip/pip_linux_cu92.mk | 3 --- make/pip/pip_linux_cu92mkl.mk | 3 --- make/pip/pip_linux_mkl.mk | 3 --- tests/jenkins/run_test.sh | 1 - tests/jenkins/run_test_ubuntu.sh | 1 - 31 files changed, 4 insertions(+), 78 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0148ac302d54..19a93c731e00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,6 @@ mxnet_option(USE_MKLDNN "Use MKLDNN variant of MKL (if MKL found)" ON mxnet_option(USE_OPERATOR_TUNING "Enable auto-tuning of operators" ON IF NOT MSVC) mxnet_option(USE_GPERFTOOLS "Build with GPerfTools support" OFF) mxnet_option(USE_JEMALLOC "Build with Jemalloc support" ON) -mxnet_option(USE_PROFILER "Build with Profiler support" ON) mxnet_option(USE_DIST_KVSTORE "Build with DIST_KVSTORE support" OFF) mxnet_option(USE_PLUGINS_WARPCTC "Use WARPCTC Plugins" OFF) mxnet_option(USE_PLUGIN_CAFFE "Use Caffe Plugin" OFF) diff --git a/benchmark/opperf/README.md b/benchmark/opperf/README.md index 132eb84bf650..e6a367d38f28 100644 --- a/benchmark/opperf/README.md +++ b/benchmark/opperf/README.md @@ -47,8 +47,6 @@ Hence, in this utility, we will build the functionality to allow users and devel ## Prerequisites -This utility uses MXNet profiler under the hood to fetch compute and memory metrics. Hence, you need to build MXNet with `USE_PROFILER=1` flag. - Make sure to build the flavor of MXNet, for example - with/without MKL, with CUDA 9 or 10.1 etc., on which you would like to measure operator performance. Finally, you need to add path to your cloned MXNet repository to the PYTHONPATH. ``` diff --git a/docs/faq/env_var.md b/docs/faq/env_var.md index f6d48af70089..05f70b01a69b 100644 --- a/docs/faq/env_var.md +++ b/docs/faq/env_var.md @@ -188,7 +188,7 @@ $env:MXNET_STORAGE_FALLBACK_LOG_VERBOSE=0 ## Control the profiler -When USE_PROFILER is enabled in Makefile or CMake, the following environments can be used to profile the application without changing code. Execution options may affect the granularity of profiling result. If you need profiling result of every operator, please set `MXNET_EXEC_BULK_EXEC_INFERENCE`, `MXNET_EXEC_BULK_EXEC_MAX_NODE_TRAIN` and `MXNET_EXEC_BULK_EXEC_TRAIN` to 0. +The following environments can be used to profile the application without changing code. Execution options may affect the granularity of profiling result. If you need profiling result of every operator, please set `MXNET_EXEC_BULK_EXEC_INFERENCE`, `MXNET_EXEC_BULK_EXEC_MAX_NODE_TRAIN` and `MXNET_EXEC_BULK_EXEC_TRAIN` to 0. * MXNET_PROFILER_AUTOSTART - Values: 0(false) or 1(true) ```(default=0)``` diff --git a/docs/faq/perf.md b/docs/faq/perf.md index 62b40247081c..03a8dbbb20a1 100644 --- a/docs/faq/perf.md +++ b/docs/faq/perf.md @@ -276,9 +276,7 @@ that gives detailed information about execution time at the symbol level. This feature complements general profiling tools like _nvprof_ and _gprof_ by summarizing at the operator level, instead of a function, kernel, or instruction level. -In order to be able to use the profiler, you must compile _MXNet_ with the `USE_PROFILER=1` flag in `config.mk`. - -The profiler can then be turned on with an [environment variable](http://mxnet.io/faq/env_var.html#control-the-profiler) +The profiler can be turned on with an [environment variable](http://mxnet.io/faq/env_var.html#control-the-profiler) for an entire program run, or programmatically for just part of a run. See [example/profiler](https://github.com/dmlc/mxnet/tree/master/example/profiler) for complete examples of how to use the profiler in code, but briefly, the Python code looks like: diff --git a/docs/install/osx_setup.md b/docs/install/osx_setup.md index 6d38c466001b..525b61bc2488 100644 --- a/docs/install/osx_setup.md +++ b/docs/install/osx_setup.md @@ -119,7 +119,6 @@ echo "USE_OPENCV=1" >> ./config.mk echo "USE_OPENMP=1" >> ./config.mk echo "USE_MKLDNN=1" >> ./config.mk echo "USE_BLAS=apple" >> ./config.mk -echo "USE_PROFILER=1" >> ./config.mk LIBRARY_PATH=$(brew --prefix llvm)/lib/ make -j $(sysctl -n hw.ncpu) ``` diff --git a/docs/tutorials/mkldnn/MKLDNN_README.md b/docs/tutorials/mkldnn/MKLDNN_README.md index c9e940fdeb3e..0b8c6b04517b 100644 --- a/docs/tutorials/mkldnn/MKLDNN_README.md +++ b/docs/tutorials/mkldnn/MKLDNN_README.md @@ -97,7 +97,7 @@ cd incubator-mxnet ### Build MXNet with MKL-DNN ``` -LIBRARY_PATH=$(brew --prefix llvm)/lib/ make -j $(sysctl -n hw.ncpu) CC=$(brew --prefix llvm)/bin/clang CXX=$(brew --prefix llvm)/bin/clang++ USE_OPENCV=1 USE_OPENMP=1 USE_MKLDNN=1 USE_BLAS=apple USE_PROFILER=1 +LIBRARY_PATH=$(brew --prefix llvm)/lib/ make -j $(sysctl -n hw.ncpu) CC=$(brew --prefix llvm)/bin/clang CXX=$(brew --prefix llvm)/bin/clang++ USE_OPENCV=1 USE_OPENMP=1 USE_MKLDNN=1 USE_BLAS=apple ```

Windows

diff --git a/example/profiler/README.md b/example/profiler/README.md index 255ad8f65579..933e04db67d3 100644 --- a/example/profiler/README.md +++ b/example/profiler/README.md @@ -19,8 +19,7 @@ This folder contains examples of using MXNet profiler to generate profiling results in json files. Please refer to [this link](http://mxnet.incubator.apache.org/faq/perf.html?highlight=profiler#profiler) -for visualizing profiling results and make sure that you have installed a version of MXNet compiled -with `USE_PROFILER=1`. +for visualizing profiling results. - profiler_executor.py. To run this example, - clone mxnet-memonger (git clone https://github.com/dmlc/mxnet-memonger.git). diff --git a/make/maven/maven_darwin_mkl.mk b/make/maven/maven_darwin_mkl.mk index f68f1565f990..a7f2bdb027d4 100644 --- a/make/maven/maven_darwin_mkl.mk +++ b/make/maven/maven_darwin_mkl.mk @@ -33,8 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/maven/maven_linux_cu90mkl.mk b/make/maven/maven_linux_cu90mkl.mk index 3d3b2c48c58b..8da56d803973 100644 --- a/make/maven/maven_linux_cu90mkl.mk +++ b/make/maven/maven_linux_cu90mkl.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/maven/maven_linux_cu92mkl.mk b/make/maven/maven_linux_cu92mkl.mk index ab93801b0b56..dd3e86b8a64f 100644 --- a/make/maven/maven_linux_cu92mkl.mk +++ b/make/maven/maven_linux_cu92mkl.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/maven/maven_linux_mkl.mk b/make/maven/maven_linux_mkl.mk index dfe5065b9a9c..3c8534a7e2aa 100644 --- a/make/maven/maven_linux_mkl.mk +++ b/make/maven/maven_linux_mkl.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_darwin_cpu.mk b/make/pip/pip_darwin_cpu.mk index 2c80c42387ad..2497aadc9d3d 100644 --- a/make/pip/pip_darwin_cpu.mk +++ b/make/pip/pip_darwin_cpu.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_darwin_mkl.mk b/make/pip/pip_darwin_mkl.mk index 2fac512140d0..1859936f180e 100644 --- a/make/pip/pip_darwin_mkl.mk +++ b/make/pip/pip_darwin_mkl.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cpu.mk b/make/pip/pip_linux_cpu.mk index 9ed88bb5bd1b..4d3b95f4dbd8 100644 --- a/make/pip/pip_linux_cpu.mk +++ b/make/pip/pip_linux_cpu.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu100.mk b/make/pip/pip_linux_cu100.mk index f3bea65a7260..a972c538e512 100644 --- a/make/pip/pip_linux_cu100.mk +++ b/make/pip/pip_linux_cu100.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu100mkl.mk b/make/pip/pip_linux_cu100mkl.mk index 4dfcb213feb1..1f5d99642f91 100644 --- a/make/pip/pip_linux_cu100mkl.mk +++ b/make/pip/pip_linux_cu100mkl.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu101.mk b/make/pip/pip_linux_cu101.mk index 6aef44237f29..b180d048557f 100644 --- a/make/pip/pip_linux_cu101.mk +++ b/make/pip/pip_linux_cu101.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu101mkl.mk b/make/pip/pip_linux_cu101mkl.mk index 1bb939d0a04c..d893fd611bad 100644 --- a/make/pip/pip_linux_cu101mkl.mk +++ b/make/pip/pip_linux_cu101mkl.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu75.mk b/make/pip/pip_linux_cu75.mk index c686c20db456..3070a907825a 100644 --- a/make/pip/pip_linux_cu75.mk +++ b/make/pip/pip_linux_cu75.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu75mkl.mk b/make/pip/pip_linux_cu75mkl.mk index ec87676c6c96..e263794600df 100644 --- a/make/pip/pip_linux_cu75mkl.mk +++ b/make/pip/pip_linux_cu75mkl.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu80.mk b/make/pip/pip_linux_cu80.mk index 72a04deeb400..969229e22608 100644 --- a/make/pip/pip_linux_cu80.mk +++ b/make/pip/pip_linux_cu80.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu80mkl.mk b/make/pip/pip_linux_cu80mkl.mk index 1bd295138936..a42220d3d467 100644 --- a/make/pip/pip_linux_cu80mkl.mk +++ b/make/pip/pip_linux_cu80mkl.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu90.mk b/make/pip/pip_linux_cu90.mk index b831d6586795..1bca27b2b80c 100644 --- a/make/pip/pip_linux_cu90.mk +++ b/make/pip/pip_linux_cu90.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu90mkl.mk b/make/pip/pip_linux_cu90mkl.mk index 8ab13075b3d0..8a8c273f3a09 100644 --- a/make/pip/pip_linux_cu90mkl.mk +++ b/make/pip/pip_linux_cu90mkl.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu91.mk b/make/pip/pip_linux_cu91.mk index cb66394683a9..fb6be493cf15 100644 --- a/make/pip/pip_linux_cu91.mk +++ b/make/pip/pip_linux_cu91.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu91mkl.mk b/make/pip/pip_linux_cu91mkl.mk index a94874503c92..c29fcef0ce0b 100644 --- a/make/pip/pip_linux_cu91mkl.mk +++ b/make/pip/pip_linux_cu91mkl.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu92.mk b/make/pip/pip_linux_cu92.mk index 8796a3bbdaa7..616ee14ccf9c 100644 --- a/make/pip/pip_linux_cu92.mk +++ b/make/pip/pip_linux_cu92.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_cu92mkl.mk b/make/pip/pip_linux_cu92mkl.mk index b9766ec36dc8..b36f97d67fe7 100644 --- a/make/pip/pip_linux_cu92mkl.mk +++ b/make/pip/pip_linux_cu92mkl.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/make/pip/pip_linux_mkl.mk b/make/pip/pip_linux_mkl.mk index 95146ee225ba..1cf389ae4a57 100644 --- a/make/pip/pip_linux_mkl.mk +++ b/make/pip/pip_linux_mkl.mk @@ -33,9 +33,6 @@ DEV = 0 # whether compile with debug DEBUG = 0 -# whether compiler with profiler -USE_PROFILER = 1 - # whether to turn on signal handler (e.g. segfault logger) USE_SIGNAL_HANDLER = 1 diff --git a/tests/jenkins/run_test.sh b/tests/jenkins/run_test.sh index 5ef8c1ac01ef..5ded74291f25 100755 --- a/tests/jenkins/run_test.sh +++ b/tests/jenkins/run_test.sh @@ -25,7 +25,6 @@ cp make/config.mk . echo "USE_CUDA=1" >> config.mk echo "USE_CUDA_PATH=/usr/local/cuda" >> config.mk echo "USE_CUDNN=1" >> config.mk -echo "USE_PROFILER=1" >> config.mk echo "DEV=1" >> config.mk echo "EXTRA_OPERATORS=example/ssd/operator" >> config.mk echo "USE_CPP_PACKAGE=1" >> config.mk diff --git a/tests/jenkins/run_test_ubuntu.sh b/tests/jenkins/run_test_ubuntu.sh index 28e00331b47e..0459d2cc8ec5 100755 --- a/tests/jenkins/run_test_ubuntu.sh +++ b/tests/jenkins/run_test_ubuntu.sh @@ -38,7 +38,6 @@ cp make/config.mk . echo "USE_CUDA=1" >> config.mk echo "USE_CUDA_PATH=/usr/local/cuda" >> config.mk echo "USE_CUDNN=1" >> config.mk -echo "USE_PROFILER=1" >> config.mk echo "DEV=1" >> config.mk echo "EXTRA_OPERATORS=example/ssd/operator" >> config.mk echo "USE_CPP_PACKAGE=1" >> config.mk From 076b2f330c60f05cb939beea28dd04cd571a34c0 Mon Sep 17 00:00:00 2001 From: Hao Jin Date: Fri, 19 Jul 2019 10:43:10 -0700 Subject: [PATCH 088/813] Softmax with length (#15169) * softmax with length forward * softmax with length backward * new macro to reduce compile-time heap usage and limit length to integers only * address comments --- src/operator/mxnet_op.h | 51 +++ src/operator/nn/softmax-inl.h | 428 +++++++++++++++++++++---- src/operator/nn/softmax.cc | 33 +- tests/python/unittest/test_operator.py | 39 ++- 4 files changed, 487 insertions(+), 64 deletions(-) diff --git a/src/operator/mxnet_op.h b/src/operator/mxnet_op.h index f17b708a7687..52788f697f11 100644 --- a/src/operator/mxnet_op.h +++ b/src/operator/mxnet_op.h @@ -363,6 +363,57 @@ inline int get_num_threads(const int N) { LOG(FATAL) << "Unknown type enum " << type; \ } +#define MXNET_INT_TYPE_SWITCH(type, DType, ...)\ + switch (type) { \ + case mshadow::kFloat32: \ + { \ + typedef float DType; \ + LOG(FATAL) << "This operation only support " \ + "integer types, not float32"; \ + } \ + break; \ + case mshadow::kFloat64: \ + { \ + typedef double DType; \ + LOG(FATAL) << "This operation only support " \ + "integer types, not float64"; \ + } \ + break; \ + case mshadow::kFloat16: \ + { \ + typedef mshadow::half::half_t DType; \ + LOG(FATAL) << "This operation only support " \ + "integer types, not float16"; \ + } \ + break; \ + case mshadow::kUint8: \ + { \ + typedef uint8_t DType; \ + {__VA_ARGS__} \ + } \ + break; \ + case mshadow::kInt8: \ + { \ + typedef int8_t DType; \ + {__VA_ARGS__} \ + } \ + break; \ + case mshadow::kInt32: \ + { \ + typedef int32_t DType; \ + {__VA_ARGS__} \ + } \ + break; \ + case mshadow::kInt64: \ + { \ + typedef int64_t DType; \ + {__VA_ARGS__} \ + } \ + break; \ + default: \ + LOG(FATAL) << "Unknown type enum " << type; \ + } + /*! * \brief assign the val to out according * to request in Kernel::Launch diff --git a/src/operator/nn/softmax-inl.h b/src/operator/nn/softmax-inl.h index d6113b05dbb9..2c82d839e5ed 100644 --- a/src/operator/nn/softmax-inl.h +++ b/src/operator/nn/softmax-inl.h @@ -75,7 +75,7 @@ inline void Softmax(Stream *s, DType *in, OType *out, index_t sa = stride[axis]; #pragma omp parallel for - for (int i = 0; i < static_cast(N); ++i) { + for (index_t i = 0; i < N; ++i) { index_t base = unravel_dot(i, sshape, stride); DType mmax = negate ? -in[base] : in[base]; @@ -113,6 +113,60 @@ inline void Softmax(Stream *s, DType *in, OType *out, } } +template +inline void SoftmaxWithLength(Stream *s, DType *in, OType *out, IType *length, + Shape shape, int axis, const DType temperature) { + index_t M = shape[axis]; + index_t N = shape.Size()/M; + Shape stride = calc_stride(shape); + Shape sshape = shape; + sshape[axis] = 1; + index_t sa = stride[axis]; + + #pragma omp parallel for + for (index_t i = 0; i < N; ++i) { + index_t len = static_cast(length[i]); + index_t base = unravel_dot(i, sshape, stride); + + DType mmax = negate ? -in[base] : in[base]; + DType val; + for (index_t j = 1; j < len; ++j) { + val = negate ? -in[base + j*sa] : in[base + j*sa]; + if (mmax < val) mmax = val; + } + for (index_t j = len; j < M; ++j) { + out[base + j*sa] = OType(0.0f); + } + + AType sum = AType(0); + DType in_val; + // By default temperature is 1.0. + // Adding a branch here to save the CPU 'divide-by-1' computation at runtime + if (temperature == 1.0) { + for (index_t j = 0; j < len; ++j) { + in_val = negate ? -in[base + j*sa] : in[base + j*sa]; + sum += std::exp(in_val - mmax); + } + + for (index_t j = 0; j < len; ++j) { + in_val = negate ? -in[base + j*sa] : in[base + j*sa]; + out[base + j*sa] = OP::Map(in_val - mmax, sum); + } + } else { + for (index_t j = 0; j < len; ++j) { + in_val = negate ? -in[base + j*sa] : in[base + j*sa]; + sum += std::exp((in_val - mmax)/temperature); + } + + for (index_t j = 0; j < len; ++j) { + in_val = negate ? -in[base + j*sa] : in[base + j*sa]; + out[base + j*sa] = OP::Map((in_val - mmax)/temperature, sum); + } + } + } +} + struct softmax_bwd { template @@ -136,7 +190,7 @@ struct log_softmax_bwd { template + typename AType, typename DType, typename OType, int ndim> inline void SoftmaxGrad(Stream *s, OType *out, OType *ograd, DType *igrad, Shape shape, int axis, const DType temperature) { @@ -148,7 +202,7 @@ inline void SoftmaxGrad(Stream *s, OType *out, OType *ograd, index_t sa = stride[axis]; #pragma omp parallel for - for (int i = 0; i < static_cast(N); ++i) { + for (index_t i = 0; i < N; ++i) { index_t base = unravel_dot(i, sshape, stride); AType sum = AType(0); @@ -177,10 +231,55 @@ inline void SoftmaxGrad(Stream *s, OType *out, OType *ograd, } } +template +inline void SoftmaxWithLengthGrad(Stream *s, OType *out, OType *ograd, + DType *igrad, IType *length, Shape shape, + int axis, const DType temperature) { + index_t M = shape[axis]; + index_t N = shape.Size()/M; + Shape stride = calc_stride(shape); + Shape sshape = shape; + sshape[axis] = 1; + index_t sa = stride[axis]; + + #pragma omp parallel for + for (index_t i = 0; i < N; ++i) { + index_t base = unravel_dot(i, sshape, stride); + index_t len = static_cast(length[i]); + + AType sum = AType(0); + for (index_t j = 0; j < len; ++j) { + sum += OP1::Map(ograd[base + j*sa], out[base + j*sa]); + } + + // By default temperature is 1.0. + // Adding a branch here to save the CPU 'divide-by-1' computation at runtime + DType final_result; + if (temperature == 1.0) { + for (index_t j = 0; j < M; ++j) { + final_result = negate ? + -OP2::Map(ograd[base + j*sa], out[base + j*sa], sum) : + OP2::Map(ograd[base + j*sa], out[base + j*sa], sum); + final_result = (j < len) ? final_result : DType(0.0f); + KERNEL_ASSIGN(igrad[base + j*sa], Req, final_result); + } + } else { + for (index_t j = 0; j < M; ++j) { + final_result = negate ? + -OP2::Map(ograd[base + j*sa], out[base + j*sa], sum) / temperature : + OP2::Map(ograd[base + j*sa], out[base + j*sa], sum) / temperature; + final_result = (j < len) ? final_result : DType(0.0f); + KERNEL_ASSIGN(igrad[base + j*sa], Req, final_result); + } + } + } +} + #ifdef __CUDACC__ template + typename DType, typename OType> __global__ void softmax_compute_kernel(DType *in, OType *out, index_t M, int axis, Shape sshape, Shape stride, const double temperature) { @@ -235,9 +334,68 @@ inline void Softmax(Stream *s, DType *in, OType *out, MSHADOW_CUDA_POST_KERNEL_CHECK(softmax_compute_kernel); } +template +__global__ void softmax_with_length_kernel(DType *in, OType *out, IType *length, + index_t M, int axis, Shape sshape, + Shape stride, const double temperature) { + const unsigned x_size = 1 << x_bits; + __shared__ AType smem[x_size]; + index_t sa = stride[axis]; + index_t base = unravel_dot(blockIdx.x, sshape, stride); + index_t x = threadIdx.x; + index_t len = static_cast(length[blockIdx.x]); + + red::maximum::SetInitValue(smem[x]); + for (index_t i = x; i < len; i += x_size) { + smem[x] = ::max(smem[x], negate ? -in[base + i*sa] : in[base + i*sa]); + } + __syncthreads(); + cuda::Reduce1D(smem); + __syncthreads(); + DType smax = smem[0]; + __syncthreads(); + + red::sum::SetInitValue(smem[x]); + DType val; + for (index_t i = x; i < len; i += x_size) { + val = negate ? -in[base + i*sa]:in[base + i*sa]; + smem[x] += static_cast(expf((val - smax) / static_cast(temperature))); + } + __syncthreads(); + cuda::Reduce1D(smem); + __syncthreads(); + AType ssum = smem[0]; + __syncthreads(); + + for (index_t i = x; i < M; i += x_size) { + val = negate ? -in[base + i*sa] : in[base + i*sa]; + out[base + i*sa] = + (i < len) ? OType(OP::Map((val - smax)/static_cast(temperature), ssum)) : OType(0.0f); + } +} + +template +inline void SoftmaxWithLength(Stream *s, DType *in, OType *out, IType *length, + Shape shape, int axis, const double temperature) { + const int x_bits = 7; + const int x_size = 1 << x_bits; + index_t M = shape[axis]; + index_t N = shape.Size()/M; + Shape stride = calc_stride(shape); + Shape sshape = shape; + sshape[axis] = 1; + + softmax_with_length_kernel + <<::GetStream(s)>>>( + in, out, length, M, axis, sshape, stride, temperature); + MSHADOW_CUDA_POST_KERNEL_CHECK(softmax_compute_kernel); +} + template + typename DType, typename OType> __global__ void softmax_gradient_kernel(OType *out, OType *ograd, DType *igrad, index_t M, int axis, Shape sshape, Shape stride, const double temperature) { @@ -269,7 +427,7 @@ __global__ void softmax_gradient_kernel(OType *out, OType *ograd, DType *igrad, template + typename DType, typename OType> inline void SoftmaxGrad(Stream *s, OType *out, OType *ograd, DType *igrad, Shape shape, int axis, const double temperature) { @@ -286,6 +444,60 @@ inline void SoftmaxGrad(Stream *s, OType *out, OType *ograd, out, ograd, igrad, M, axis, sshape, stride, temperature); MSHADOW_CUDA_POST_KERNEL_CHECK(softmax_gradient_kernel); } + +template +__global__ void softmax_with_length_grad_kernel(OType *out, OType *ograd, DType *igrad, + IType *length, index_t M, int axis, + Shape sshape, Shape stride, + const double temperature) { + const unsigned x_size = 1 << x_bits; + __shared__ AType smem[x_size]; + index_t sa = stride[axis]; + index_t base = unravel_dot(blockIdx.x, sshape, stride); + index_t x = threadIdx.x; + index_t len = static_cast(length[blockIdx.x]); + + red::sum::SetInitValue(smem[x]); + for (index_t i = x; i < len; i += x_size) { + smem[x] += OP1::Map(ograd[base + i*sa], out[base + i*sa]); + } + __syncthreads(); + cuda::Reduce1D(smem); + __syncthreads(); + AType ssum = smem[0]; + __syncthreads(); + + DType final_result; + for (index_t i = x; i < M; i += x_size) { + final_result = + negate ? + -OP2::Map(ograd[base + i*sa], out[base + i*sa], ssum) : + OP2::Map(ograd[base + i*sa], out[base + i*sa], ssum); + final_result = (i < len) ? final_result : DType(0.0f); + KERNEL_ASSIGN(igrad[base + i*sa], Req, final_result / static_cast(temperature)); + } +} + + +template +inline void SoftmaxWithLengthGrad(Stream *s, OType *out, OType *ograd, + DType *igrad, IType *length, Shape shape, int axis, + const double temperature) { + const int x_bits = 7; + const int x_size = 1 << x_bits; + index_t M = shape[axis]; + index_t N = shape.Size()/M; + Shape stride = calc_stride(shape); + Shape sshape = shape; + sshape[axis] = 1; + + softmax_with_length_grad_kernel + <<::GetStream(s)>>>( + out, ograd, igrad, length, M, axis, sshape, stride, temperature); + MSHADOW_CUDA_POST_KERNEL_CHECK(softmax_with_length_grad_kernel); +} #endif } // namespace mxnet_op @@ -295,6 +507,7 @@ struct SoftmaxParam : public dmlc::Parameter { int axis; dmlc::optional temperature; dmlc::optional dtype; + dmlc::optional use_length; DMLC_DECLARE_PARAMETER(SoftmaxParam) { DMLC_DECLARE_FIELD(axis).set_default(-1) .describe("The axis along which to compute softmax."); @@ -307,6 +520,9 @@ struct SoftmaxParam : public dmlc::Parameter { .set_default(dmlc::optional()) .describe("DType of the output in case this can't be inferred. " "Defaults to the same as input's dtype if not defined (dtype=None)."); + DMLC_DECLARE_FIELD(use_length) + .set_default(dmlc::optional(false)) + .describe("Whether to use the length input as a mask over the data input."); } }; @@ -315,27 +531,71 @@ static inline bool softmax_has_dtype_override(const nnvm::NodeAttrs& attrs) { return param.dtype.has_value() && param.dtype.value() != -1; } +static inline bool softmax_use_length(const nnvm::NodeAttrs& attrs) { + const SoftmaxParam& param = nnvm::get(attrs.parsed); + return param.use_length.value(); +} + static inline bool SoftmaxOpType(const nnvm::NodeAttrs& attrs, std::vector* in_attrs, std::vector* out_attrs) { - CHECK_EQ(in_attrs->size(), 1); CHECK_EQ(out_attrs->size(), 1); const SoftmaxParam& param = nnvm::get(attrs.parsed); + CHECK_EQ(in_attrs->size(), softmax_use_length(attrs) ? 2U : 1U); if (softmax_has_dtype_override(attrs)) { TYPE_ASSIGN_CHECK(*out_attrs, 0, param.dtype.value()); type_assign(&(*in_attrs)[0], (*out_attrs)[0]); return true; } else { - return ElemwiseType<1, 1>(attrs, in_attrs, out_attrs); + std::vector tmp = {in_attrs->at(0)}; + return ElemwiseType<1, 1>(attrs, &tmp, out_attrs); + } +} + +static inline bool SoftmaxOpShape(const nnvm::NodeAttrs& attrs, + mxnet::ShapeVector *in_attrs, + mxnet::ShapeVector *out_attrs) { + CHECK_EQ(out_attrs->size(), 1U); + const SoftmaxParam& param = nnvm::get(attrs.parsed); + CHECK_EQ(in_attrs->size(), param.use_length.value() ? 2U : 1U); + + if (param.use_length.value()) { + mxnet::TShape& dshape = in_attrs->at(0); + mxnet::TShape tmp_shape((dshape.ndim() == 1) ? 1U : dshape.ndim() - 1, 1); + int j = 0; + for (int i = 0; i < dshape.ndim(); ++i) { + if (i != param.axis) { + tmp_shape[j++] = dshape[i]; + } + } + SHAPE_ASSIGN_CHECK(*in_attrs, 1, tmp_shape); } + mxnet::ShapeVector tmp = {in_attrs->at(0)}; + return ElemwiseShape<1, 1>(attrs, &tmp, out_attrs); } static inline bool SoftmaxGradOpShape(const nnvm::NodeAttrs& attrs, mxnet::ShapeVector *in_attrs, mxnet::ShapeVector *out_attrs) { - if (softmax_has_dtype_override(attrs)) { - return ElemwiseShape<3, 1>(attrs, in_attrs, out_attrs); + if (softmax_has_dtype_override(attrs) || softmax_use_length(attrs)) { + if (softmax_use_length(attrs)) { + mxnet::ShapeVector ins = {in_attrs->at(0), in_attrs->at(1), in_attrs->at(3)}; + mxnet::ShapeVector dgrad = {out_attrs->at(0)}; + bool res = ElemwiseShape<3, 1>(attrs, &ins, &dgrad); + SHAPE_ASSIGN_CHECK(*in_attrs, 0, ins[0]); + SHAPE_ASSIGN_CHECK(*in_attrs, 1, ins[1]); + SHAPE_ASSIGN_CHECK(*in_attrs, 3, ins[2]); + SHAPE_ASSIGN_CHECK(*out_attrs, 0, dgrad[0]); + mxnet::ShapeVector length = {in_attrs->at(2)}; + mxnet::ShapeVector lgrad = {out_attrs->at(1)}; + res = (res && ElemwiseShape<1, 1>(attrs, &length, &lgrad)); + SHAPE_ASSIGN_CHECK(*in_attrs, 2, length[0]); + SHAPE_ASSIGN_CHECK(*out_attrs, 1, lgrad[0]); + return res; + } else { + return ElemwiseShape<3, 1>(attrs, in_attrs, out_attrs); + } } else { return ElemwiseShape<2, 1>(attrs, in_attrs, out_attrs); } @@ -344,17 +604,21 @@ static inline bool SoftmaxGradOpShape(const nnvm::NodeAttrs& attrs, static inline bool SoftmaxGradOpType(const nnvm::NodeAttrs& attrs, std::vector* in_attrs, std::vector* out_attrs) { - CHECK_EQ(out_attrs->size(), 1); - if (softmax_has_dtype_override(attrs)) { - CHECK_EQ(in_attrs->size(), 3); + CHECK_EQ(out_attrs->size(), softmax_use_length(attrs) ? 2U : 1U); + if (softmax_has_dtype_override(attrs) || softmax_use_length(attrs)) { + CHECK_EQ(in_attrs->size(), softmax_use_length(attrs) ? 4U : 3U); int in_dtype = (*in_attrs)[1]; - int out_dtype = (*in_attrs)[2]; + int out_dtype = (*in_attrs)[softmax_use_length(attrs) ? 3 : 2]; TYPE_ASSIGN_CHECK(*in_attrs, 0, out_dtype); TYPE_ASSIGN_CHECK(*out_attrs, 0, in_dtype); + if (softmax_use_length(attrs)) { + TYPE_ASSIGN_CHECK(*out_attrs, 1, in_attrs->at(2)); + } - return (*out_attrs)[0] != -1 && (*in_attrs)[0] != -1; + return (*out_attrs)[0] != -1 && (*in_attrs)[0] != -1 && + (*out_attrs)[1] != -1 && (*in_attrs)[1] != -1; } else { - CHECK_EQ(in_attrs->size(), 2); + CHECK_EQ(in_attrs->size(), 2U); int out_dtype = (*in_attrs)[1]; TYPE_ASSIGN_CHECK(*out_attrs, 0, out_dtype); TYPE_ASSIGN_CHECK(*in_attrs, 0, out_dtype); @@ -365,20 +629,31 @@ static inline bool SoftmaxGradOpType(const nnvm::NodeAttrs& attrs, static inline std::vector > SoftmaxGradOpInplaceOption(const nnvm::NodeAttrs& attrs) { - if (softmax_has_dtype_override(attrs)) { - return std::vector >{{0, 0}, {1, 0}, {2, 0}}; + if (softmax_has_dtype_override(attrs) || softmax_use_length(attrs)) { + if (softmax_use_length(attrs)) { + return std::vector >{{0, 0}, {1, 0}, {2, 1}, {3, 0}}; + } else { + return std::vector >{{0, 0}, {1, 0}, {2, 0}}; + } } else { return std::vector >{{0, 0}, {1, 0}}; } } static inline uint32_t SoftmaxGradOpNumInputs(const nnvm::NodeAttrs& attrs) { - return softmax_has_dtype_override(attrs) ? 3 : 2; + if (softmax_has_dtype_override(attrs) || softmax_use_length(attrs)) { + return softmax_use_length(attrs) ? 4 : 3; + } + return 2; } static inline std::vector SoftmaxGradOpInputNames(const nnvm::NodeAttrs& attrs) { - if (softmax_has_dtype_override(attrs)) { - return std::vector{"ograd", "data", "output"}; + if (softmax_has_dtype_override(attrs) || softmax_use_length(attrs)) { + if (softmax_use_length(attrs)) { + return std::vector{"ograd", "data", "length", "output"}; + } else { + return std::vector{"ograd", "data", "output"}; + } } else { return std::vector{"ograd", "output"}; } @@ -388,7 +663,7 @@ struct SoftmaxFGradient { const char *op_name; std::vector operator()(const nnvm::NodePtr& n, const std::vector& ograds) const { - if (softmax_has_dtype_override(n->attrs)) { + if (softmax_has_dtype_override(n->attrs) || softmax_use_length(n->attrs)) { return ElemwiseGradUseInOut {op_name}(n, ograds); } else { return ElemwiseGradUseOut {op_name}(n, ograds); @@ -419,30 +694,46 @@ void SoftmaxCompute(const nnvm::NodeAttrs& attrs, MXNET_REAL_ACC_TYPE_SWITCH(inputs[0].type_flag_, DType, AType, { MSHADOW_REAL_TYPE_SWITCH(outputs[0].type_flag_, OType, { - if (safe_acc) { - if (shape.ndim() == 2) { - Softmax( - ctx.get_stream(), inputs[0].dptr(), - outputs[0].dptr(), shape.get<2>(), axis, - static_cast(temperature)); + if (!param.use_length.value()) { + if (safe_acc) { + if (shape.ndim() == 2) { + Softmax( + ctx.get_stream(), inputs[0].dptr(), + outputs[0].dptr(), shape.get<2>(), axis, + static_cast(temperature)); + } else { + Softmax( + ctx.get_stream(), inputs[0].dptr(), + outputs[0].dptr(), shape.get<3>(), axis, + static_cast(temperature)); + } } else { - Softmax( - ctx.get_stream(), inputs[0].dptr(), - outputs[0].dptr(), shape.get<3>(), axis, - static_cast(temperature)); + if (shape.ndim() == 2) { + Softmax( + ctx.get_stream(), inputs[0].dptr(), + outputs[0].dptr(), shape.get<2>(), axis, + static_cast(temperature)); + } else { + Softmax( + ctx.get_stream(), inputs[0].dptr(), + outputs[0].dptr(), shape.get<3>(), axis, + static_cast(temperature)); + } } } else { - if (shape.ndim() == 2) { - Softmax( + MXNET_INT_TYPE_SWITCH(inputs[1].type_flag_, IType, { + if (shape.ndim() == 2) { + SoftmaxWithLength( ctx.get_stream(), inputs[0].dptr(), - outputs[0].dptr(), shape.get<2>(), axis, - static_cast(temperature)); - } else { - Softmax( + outputs[0].dptr(), inputs[1].dptr(), + shape.get<2>(), axis, static_cast(temperature)); + } else { + SoftmaxWithLength( ctx.get_stream(), inputs[0].dptr(), - outputs[0].dptr(), shape.get<3>(), axis, - static_cast(temperature)); - } + outputs[0].dptr(), inputs[1].dptr(), + shape.get<3>(), axis, static_cast(temperature)); + } + }); } }); }); @@ -464,35 +755,56 @@ void SoftmaxGradCompute(const nnvm::NodeAttrs& attrs, mxnet::TShape shape = AxisShapeCompact(inputs[0].shape_, &axis, true); int out_idx = softmax_has_dtype_override(attrs) ? 2 : 1; + out_idx = softmax_use_length(attrs) ? 3 : out_idx; bool safe_acc = dmlc::GetEnv("MXNET_SAFE_ACCUMULATION", false); MXNET_REAL_ACC_TYPE_SWITCH(inputs[0].type_flag_, OType, AType, { MSHADOW_REAL_TYPE_SWITCH(outputs[0].type_flag_, DType, { MXNET_ASSIGN_REQ_SWITCH(req[0], Req, { - if (safe_acc) { - if (shape.ndim() == 2) { - SoftmaxGrad( - ctx.get_stream(), inputs[out_idx].dptr(), - inputs[0].dptr(), outputs[0].dptr(), - shape.get<2>(), axis, static_cast(temperature)); + if (!softmax_use_length(attrs)) { + if (safe_acc) { + if (shape.ndim() == 2) { + SoftmaxGrad( + ctx.get_stream(), inputs[out_idx].dptr(), + inputs[0].dptr(), outputs[0].dptr(), + shape.get<2>(), axis, static_cast(temperature)); + } else { + SoftmaxGrad( + ctx.get_stream(), inputs[out_idx].dptr(), + inputs[0].dptr(), outputs[0].dptr(), + shape.get<3>(), axis, static_cast(temperature)); + } } else { - SoftmaxGrad( - ctx.get_stream(), inputs[out_idx].dptr(), - inputs[0].dptr(), outputs[0].dptr(), - shape.get<3>(), axis, static_cast(temperature)); + if (shape.ndim() == 2) { + SoftmaxGrad( + ctx.get_stream(), inputs[out_idx].dptr(), + inputs[0].dptr(), outputs[0].dptr(), + shape.get<2>(), axis, static_cast(temperature)); + } else { + SoftmaxGrad( + ctx.get_stream(), inputs[out_idx].dptr(), + inputs[0].dptr(), outputs[0].dptr(), + shape.get<3>(), axis, static_cast(temperature)); + } } } else { - if (shape.ndim() == 2) { - SoftmaxGrad( + MXNET_INT_TYPE_SWITCH(inputs[2].type_flag_, IType, { + if (req[1] != kNullOp) { + mxnet_op::Kernel::Launch( + ctx.get_stream(), outputs[1].Size(), outputs[1].dptr()); + } + if (shape.ndim() == 2) { + SoftmaxWithLengthGrad( ctx.get_stream(), inputs[out_idx].dptr(), inputs[0].dptr(), outputs[0].dptr(), - shape.get<2>(), axis, static_cast(temperature)); - } else { - SoftmaxGrad( + inputs[2].dptr(), shape.get<2>(), axis, static_cast(temperature)); + } else { + SoftmaxWithLengthGrad( ctx.get_stream(), inputs[out_idx].dptr(), inputs[0].dptr(), outputs[0].dptr(), - shape.get<3>(), axis, static_cast(temperature)); - } + inputs[2].dptr(), shape.get<3>(), axis, static_cast(temperature)); + } + }); } }); }); diff --git a/src/operator/nn/softmax.cc b/src/operator/nn/softmax.cc index e44bbbb6b8f6..5a581e4ea5ef 100644 --- a/src/operator/nn/softmax.cc +++ b/src/operator/nn/softmax.cc @@ -59,14 +59,23 @@ inline static bool SoftmaxStorageType(const nnvm::NodeAttrs& attrs, DispatchMode* dispatch_mode, std::vector *in_attrs, std::vector *out_attrs) { - CHECK_EQ(in_attrs->size(), 1); - CHECK_EQ(out_attrs->size(), 1); + const SoftmaxParam& param = nnvm::get(attrs.parsed); + CHECK_EQ(in_attrs->size(), (param.use_length.value()) ? 2U : 1U); + CHECK_EQ(out_attrs->size(), 1U); + + if (param.use_length.value()) { + auto& out_stype = out_attrs->at(0); + return storage_type_assign(&out_stype, kDefaultStorage, + dispatch_mode, DispatchMode::kFCompute); + } return MKLDNNStorageType(attrs, dev_mask, true, dispatch_mode, in_attrs, out_attrs); } #endif + + NNVM_REGISTER_OP(softmax) .describe(R"code(Applies the softmax function. @@ -92,6 +101,13 @@ Example:: )code" ADD_FILELINE) .set_attr_parser(ParamParser) +.set_attr("FListInputNames", + [](const NodeAttrs& attrs){ + const SoftmaxParam& param = nnvm::get(attrs.parsed); + return (param.use_length.value()) ? + std::vector{"data", "length"} : + std::vector{"data"}; +}) .set_attr("FListOutputNames", [](const NodeAttrs& attrs) { return std::vector{"output"}; @@ -103,20 +119,27 @@ Example:: .set_attr("FInferStorageType", SoftmaxStorageType) #endif .set_attr("FGradient", SoftmaxFGradient{"_backward_softmax"}) +// .set_attr("FGradient", MakeZeroGradNodes) .set_attr("FInferType", SoftmaxOpType) -.set_num_inputs(1) +.set_num_inputs([](const nnvm::NodeAttrs& attrs) { + const SoftmaxParam& param = nnvm::get(attrs.parsed); + return (param.use_length.value()) ? 2 : 1; + }) .set_num_outputs(1) -.set_attr("FInferShape", ElemwiseShape<1, 1>) +.set_attr("FInferShape", SoftmaxOpShape) .set_attr("FInplaceOption", [](const NodeAttrs& attrs){ return std::vector >{{0, 0}}; }) .add_argument("data", "NDArray-or-Symbol", "The input array.") +.add_argument("length", "NDArray-or-Symbol", "The length array.") .add_arguments(SoftmaxParam::__FIELDS__()); NNVM_REGISTER_OP(_backward_softmax) .set_num_inputs(SoftmaxGradOpNumInputs) -.set_num_outputs(1) +.set_num_outputs([](const nnvm::NodeAttrs& attrs) { + return (softmax_use_length(attrs) ? 2 : 1); + }) .set_attr("FListInputNames", SoftmaxGradOpInputNames) .set_attr("FInferShape", SoftmaxGradOpShape) .set_attr("FInferType", SoftmaxGradOpType) diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index 749f0f2bed23..fea07f540624 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -5196,6 +5196,39 @@ def check_dtypes_almost_equal(op_name, check_dtypes_almost_equal('log_softmax', 1e-3, 1e-3, 1e-3, 1e-3, 'float32', 'float64', 'float64') + +@with_seed() +def test_softmax_with_length(): + def np_softmax_with_length(data, length): + res = np.zeros(data.shape) + for i in range(length.shape[0]): + for j in range(length.shape[1]): + leng = int(length[i, j]) + res[i, 0:leng, j] = np_softmax(data[i, 0:leng, j]) + return res + + ndim = 3 + shape = rand_shape_nd(ndim, dim=10) + len_shape = list(shape) + del len_shape[1] + len_shape = tuple(len_shape) + for dtype in [np.float16, np.float32, np.float64]: + mx_data = rand_ndarray(shape, dtype=dtype) + np_data = mx_data.asnumpy() + np_length = np.random.randint(1, shape[1] + 1, len_shape) + mx_length = mx.nd.array(np_length, dtype=np.int32) + np_out = np_softmax_with_length(np_data, np_length) + data = mx.sym.Variable("data") + length = mx.sym.Variable("length") + mx_sym = mx.sym.softmax(data=data, length=length, use_length=True, axis=1) + location = {"data": mx_data, "length": mx_length} + rtol = 1e-2 if dtype == np.float16 else 1e-3 + atol = 1e-4 if dtype == np.float16 else 1e-5 + check_symbolic_forward(mx_sym, location, [np_out], rtol=rtol, atol=atol, dtype="asnumpy") + check_symbolic_backward(mx_sym, location, [np.ones(shape, dtype=dtype)], + [np.zeros(shape), np.zeros(len_shape, dtype=np.int32)], rtol=1e-2, atol=1e-3, dtype="asnumpy") + + @with_seed() def test_pick(): def test_pick_helper(index_type=np.int32): @@ -8034,7 +8067,11 @@ def get_output_names_callback(name, arr): check_name(cc_sym, ['data', 'concat_arg0', 'data', 'concat_arg1', 'concat_output']) sm_sym = mx.sym.softmax(data, name='softmax') - check_name(sm_sym, ['data', 'softmax_input0', 'softmax_output']) + check_name(sm_sym, ['data', 'softmax_data', 'softmax_output']) + + length = mx.sym.Variable("length", shape=(10, 10, 10)) + sm_sym = mx.sym.softmax(data, length, axis=1, use_length=True, name='softmax') + check_name(sm_sym, ['data', 'softmax_data', 'length', 'softmax_length', 'softmax_output']) sa_sym = mx.sym.SoftmaxActivation(data, name='softmax') check_name(sa_sym, ['data', 'softmax_input0', 'softmax_output']) From eab6da6208fdc7b68437dadc6d7d58ee11c2f528 Mon Sep 17 00:00:00 2001 From: Pedro Larroy Date: Fri, 19 Jul 2019 15:14:21 -0700 Subject: [PATCH 089/813] Fix warnings in CLang: (#15270) In file included from ../src/kvstore/kvstore.cc:28: ../src/kvstore/./kvstore_local.h:281:23: warning: lambda capture 'this' is not used [-Wunused-lambda-capture] auto validator = [this](const int key, const NDArray& nd, bool ignore_sparse) -> bool { ^ ../src/kvstore/./kvstore_local.h:326:23: warning: lambda capture 'this' is not used [-Wunused-lambda-capture] auto validator = [this](const int key, const RSPVal& val_rowid, bool ignore_sparse) -> bool { ^ In file included from ../src/c_api/c_api_profile.cc:35: ../src/c_api/../profiler/./profiler.h:1160:8: warning: 'mxnet::profiler::ProfileOperator::start' hides overloaded virtual function [-Woverloaded-virtual] void start(mxnet::Context::DeviceType dev_type, uint32_t dev_id) { ^ ../src/c_api/../profiler/./profiler.h:870:8: note: hidden overloaded virtual function 'mxnet::profiler::ProfileEvent::start' declared here: different number of parameters (0 vs 2) void start() override { ^ ../src/c_api/../profiler/./profiler.h:1212:8: warning: lambda capture 'this' is not used [-Wunused-lambda-capture] [this](OprExecStat *stat) {}, name_.c_str(), dev_type_, dev_id_, ^ See also #14940 --- src/engine/naive_engine.cc | 6 +++--- src/engine/threaded_engine.h | 2 +- src/kvstore/kvstore_local.h | 4 ++-- src/profiler/profiler.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/engine/naive_engine.cc b/src/engine/naive_engine.cc index 9cfe9b28f862..92b38a8ea368 100644 --- a/src/engine/naive_engine.cc +++ b/src/engine/naive_engine.cc @@ -128,7 +128,7 @@ class NaiveEngine final : public Engine { attrs.reset(new profiler::ProfileOperator::Attributes()); } opr->opr_profile.reset(new profiler::ProfileOperator(opr->opr_name, attrs.release())); - opr->opr_profile->start(exec_ctx.dev_type, exec_ctx.dev_id); + opr->opr_profile->startForDevice(exec_ctx.dev_type, exec_ctx.dev_id); } opr->fn(ctx, on_complete); if (opr->profiling) { @@ -176,8 +176,8 @@ class NaiveEngine final : public Engine { if (profiler->AggregateEnabled()) { attrs.reset(new profiler::ProfileOperator::Attributes()); } - opr->opr_profile.reset(new profiler::ProfileOperator(display_name, attrs.release())); - opr->opr_profile->start(exec_ctx.dev_type, exec_ctx.dev_id); + opr->opr_profile.reset(new profiler::ProfileOperator(opr->opr_name, attrs.release())); + opr->opr_profile->startForDevice(exec_ctx.dev_type, exec_ctx.dev_id); } if (exec_ctx.dev_mask() == gpu::kDevMask) { #if MXNET_USE_CUDA diff --git a/src/engine/threaded_engine.h b/src/engine/threaded_engine.h index 3d56b7c79b90..bf74485ba442 100644 --- a/src/engine/threaded_engine.h +++ b/src/engine/threaded_engine.h @@ -359,7 +359,7 @@ class ThreadedEngine : public Engine { const Context& ctx = opr_block->ctx; opr_block->opr_profile.reset(new profiler::ProfileOperator(threaded_opr->opr_name, attrs.release())); - opr_block->opr_profile->start(ctx.dev_type, ctx.dev_id); + opr_block->opr_profile->startForDevice(ctx.dev_type, ctx.dev_id); } CallbackOnComplete callback = this->CreateCallback(ThreadedEngine::OnCompleteStatic, opr_block); diff --git a/src/kvstore/kvstore_local.h b/src/kvstore/kvstore_local.h index 4e004a3a3008..5c3c80838a36 100644 --- a/src/kvstore/kvstore_local.h +++ b/src/kvstore/kvstore_local.h @@ -278,7 +278,7 @@ class KVStoreLocal : public KVStore { std::vector> *grouped_vals, bool ignore_sparse) { // check if the storage type of a value is valid - auto validator = [this](const int key, const NDArray& nd, bool ignore_sparse) -> bool { + auto validator = [](const int key, const NDArray& nd, bool ignore_sparse) -> bool { CHECK(!ignore_sparse) << "Cannot ignore sparse arrays for push"; auto stype = nd.storage_type(); // valid NDArray @@ -323,7 +323,7 @@ class KVStoreLocal : public KVStore { std::vector> *grouped_vals, bool ignore_sparse) { // check if the storage type of a value is valid - auto validator = [this](const int key, const RSPVal& val_rowid, bool ignore_sparse) -> bool { + auto validator = [](const int key, const RSPVal& val_rowid, bool ignore_sparse) -> bool { CHECK(!ignore_sparse) << "Cannot ignore sparse arrays in row_sparse_pull"; auto val_stype = val_rowid.first->storage_type(); auto rowid_stype = val_rowid.second.storage_type(); diff --git a/src/profiler/profiler.h b/src/profiler/profiler.h index 49ef99da5bba..6df7ce4339da 100644 --- a/src/profiler/profiler.h +++ b/src/profiler/profiler.h @@ -1187,7 +1187,7 @@ struct ProfileOperator : public ProfileEvent { * \param dev_type Device type that the profiling will occur on * \param dev_id Device id associated with this opr */ - void start(mxnet::Context::DeviceType dev_type, uint32_t dev_id) { + void startForDevice(mxnet::Context::DeviceType dev_type, uint32_t dev_id) { dev_type_ = dev_type; dev_id_ = dev_id; if (profiling_) { @@ -1247,7 +1247,7 @@ struct ProfileOperator : public ProfileEvent { */ void SendStat() override { Profiler::Get()->AddNewProfileStat( - [this](OprExecStat *stat) {}, name_.c_str(), dev_type_, dev_id_, + [](OprExecStat *stat) {}, name_.c_str(), dev_type_, dev_id_, start_time_, ProfileStat::NowInMicrosec(), attributes_.get()); } From da71324882808f6e6b1bc520076f65113636bc54 Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Fri, 19 Jul 2019 15:33:11 -0700 Subject: [PATCH 090/813] Fix dumps for Constant initializer (#15150) * update dumps for const init * add test * fix for numpy input * randomize test array shape and dim * fix test * replace type with isinstance --- python/mxnet/initializer.py | 6 ++++++ tests/python/unittest/test_init.py | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/python/mxnet/initializer.py b/python/mxnet/initializer.py index aca7c58707e2..277bfd0f4fa5 100755 --- a/python/mxnet/initializer.py +++ b/python/mxnet/initializer.py @@ -464,6 +464,12 @@ def __init__(self, value): def _init_weight(self, _, arr): arr[:] = self.value + def dumps(self): + val = self._kwargs['value'] + if not np.isscalar(val): + self._kwargs['value'] = val.tolist() if isinstance(val, np.ndarray) else val.asnumpy().tolist() + return json.dumps([self.__class__.__name__.lower(), self._kwargs]) + @register class Uniform(Initializer): """Initializes weights with random values uniformly sampled from a given range. diff --git a/tests/python/unittest/test_init.py b/tests/python/unittest/test_init.py index c8bf01f48ca3..6d8830c1d089 100644 --- a/tests/python/unittest/test_init.py +++ b/tests/python/unittest/test_init.py @@ -17,6 +17,7 @@ import mxnet as mx import numpy as np +import json def test_default_init(): data = mx.sym.Variable('data') @@ -67,10 +68,26 @@ def test_bilinear_init(): bili_1d = np.array([[1/float(4), 3/float(4), 3/float(4), 1/float(4)]]) bili_2d = bili_1d * np.transpose(bili_1d) assert (bili_2d == bili_weight.asnumpy()).all() - + +def test_const_init_dumps(): + shape = tuple(np.random.randint(1, 10, size=np.random.randint(1, 5))) + # test NDArray input + init = mx.init.Constant(mx.nd.ones(shape)) + val = init.dumps() + assert val == json.dumps([init.__class__.__name__.lower(), init._kwargs]) + # test scalar input + init = mx.init.Constant(1) + assert init.dumps() == '["constant", {"value": 1}]' + # test numpy input + init = mx.init.Constant(np.ones(shape)) + val = init.dumps() + assert val == json.dumps([init.__class__.__name__.lower(), init._kwargs]) + + if __name__ == '__main__': test_variable_init() test_default_init() test_aux_init() test_rsp_const_init() test_bilinear_init() + test_const_init_dumps() From 7e632c961ef7d94aaff6c5ddbd023b343082f70d Mon Sep 17 00:00:00 2001 From: Lin Yuan Date: Fri, 19 Jul 2019 15:34:23 -0700 Subject: [PATCH 091/813] add myself to interested modules (#15590) --- CODEOWNERS | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 9d4145c6b0f2..7007e80b2035 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -23,17 +23,17 @@ /julia/ @iblis17 # C++ base -/src/kvstore/ @rahul003 @anirudh2290 @eric-haibin-lin +/src/kvstore/ @rahul003 @anirudh2290 @eric-haibin-lin @apeforest /include/ @anirudh2290 @pllarroy @eric-haibin-lin -/src/c_api/ @anirudh2290 @eric-haibin-lin +/src/c_api/ @anirudh2290 @eric-haibin-lin @apeforest /src/common/ @anirudh2290 -/src/engine/ @anirudh2290 @eric-haibin-lin -/src/executor/ @anirudh2290 @eric-haibin-lin -/src/imperative/ @anirudh2290 @eric-haibin-lin -/src/io/ @anirudh2290 @eric-haibin-lin -/src/ndarray/ @anirudh2290 @eric-haibin-lin -/src/nnvm/ @anirudh2290 @eric-haibin-lin -/src/operator/ @anirudh2290 @eric-haibin-lin +/src/engine/ @anirudh2290 @eric-haibin-lin @apeforest +/src/executor/ @anirudh2290 @eric-haibin-lin @apeforest +/src/imperative/ @anirudh2290 @eric-haibin-lin @apeforest +/src/io/ @anirudh2290 @eric-haibin-lin @apeforest +/src/ndarray/ @anirudh2290 @eric-haibin-lin @apeforest +/src/nnvm/ @anirudh2290 @eric-haibin-lin @apeforest +/src/operator/ @anirudh2290 @eric-haibin-lin @apeforest /src/profiler/ @anirudh2290 @eric-haibin-lin /src/storage/ @anirudh2290 @eric-haibin-lin /tests/cpp/ @anirudh2290 From d14fa692b075bc70c58d8e5ad2bd8d442f7449b9 Mon Sep 17 00:00:00 2001 From: yifeim Date: Fri, 19 Jul 2019 15:57:16 -0700 Subject: [PATCH 092/813] Update profiler.md (#15477) Add one line "dump all results to log file before download" to the main example. Otherwise, it is likely that a user may download partially-written and broken files. --- docs/tutorials/python/profiler.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tutorials/python/profiler.md b/docs/tutorials/python/profiler.md index f2da62833fcf..5be6bf83ac5d 100644 --- a/docs/tutorials/python/profiler.md +++ b/docs/tutorials/python/profiler.md @@ -154,6 +154,8 @@ run_training_iteration(*next(itr)) mx.nd.waitall() # Ask the profiler to stop recording profiler.set_state('stop') +# Dump all results to log file before download +profiler.dump() ``` Between running and stopping the profiler, you can also pause and resume the profiler using `profiler.pause()` and `profiler.resume()` respectively to profile only parts of the code you want to profile. From cc861f0a3fecea8fb94c9687396e27966e5ddcde Mon Sep 17 00:00:00 2001 From: Zhaoqi Zhu Date: Fri, 19 Jul 2019 22:41:09 -0700 Subject: [PATCH 093/813] Tensor Inspector Tutorial (#15517) * add tensor inspector tutorial * link docs * link docs * add license * Revert "add license" This reverts commit 32881e5acff6a0dc833e52f9c82c4967df40f006. * Revert "link docs" This reverts commit f93ae219262513e8ce52c0d68abd8eb3f40b2ed5. * Revert "link docs" This reverts commit 160b8912e14746f2de1b660c6b24a24197ffcb46. * Revert "add tensor inspector tutorial" This reverts commit 3b53981a4fe932e8ae80e4ea1ab5cd0260a12574. * add tensor inspector doc * fix api name * add new test and limitations section * fix * update urls and other fixes * fix urls * fix --- docs/faq/add_op_in_backend.md | 3 + docs/faq/develop_and_hack.md | 1 + docs/faq/tensor_inspector_tutorial.md | 168 ++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 docs/faq/tensor_inspector_tutorial.md diff --git a/docs/faq/add_op_in_backend.md b/docs/faq/add_op_in_backend.md index 15f4ed9fbab4..248a0b058370 100644 --- a/docs/faq/add_op_in_backend.md +++ b/docs/faq/add_op_in_backend.md @@ -674,3 +674,6 @@ We welcome your contributions to MXNet. [quadratic_op.cu](https://github.com/apache/incubator-mxnet/blob/master/src/operator/contrib/quadratic_op.cu), and [test_operator.py](https://github.com/apache/incubator-mxnet/blob/master/tests/python/unittest/test_operator.py#L6514). + +## Additional Resources +- [Use TensorInspector to Help Debug Operators](tensor_inspector_tutorial.md) diff --git a/docs/faq/develop_and_hack.md b/docs/faq/develop_and_hack.md index 0e7d221f7dc3..d2710e923be4 100644 --- a/docs/faq/develop_and_hack.md +++ b/docs/faq/develop_and_hack.md @@ -19,6 +19,7 @@ - [Create new operators](new_op.md) - [Use Torch from MXNet](torch.md) - [Set environment variables of MXNet](env_var.md) +- [Use TensorInspector to Help Debug Operators](tensor_inspector_tutorial.md) # Other Resources - [MXNet System Architecture Overview](/architecture/overview.html) diff --git a/docs/faq/tensor_inspector_tutorial.md b/docs/faq/tensor_inspector_tutorial.md new file mode 100644 index 000000000000..e77c7447fd17 --- /dev/null +++ b/docs/faq/tensor_inspector_tutorial.md @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + +# Use TensorInspector to Help Debug Operators + +## Introduction + +When developing new operators, developers need to deal with tensor objects extensively. This new utility, Tensor Inspector, mainly aims to help developers debug by providing unified interfaces to print, check, and dump the tensor value. To developers' convenience, this utility works for all the three data types: Tensors, TBlobs, and NDArrays. Also, it supports both CPU and GPU tensors. + + +## Usage + +This utility is located in `src/common/tensor_inspector.h`. To use it in any operator code, just include it using `#include "{path}/tensor_inspector.h"`, construct an `TensorInspector` object, and call the APIs on that object. You can run any script that uses the operator you just modified then. + +The screenshot below shows a sample usage in `src/operator/nn/convolution-inl.h`. + +![tensor_inspector_example_usage](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/doc/faq/tensor_inspector_tutorial/tensor_inspector_example_usage.png) + + +## Functionalities/APIs + +### Create a TensorInspector Object from Tensor, TBlob, and NDArray Objects + +You can create a `TensorInspector` object by passing in two things: 1) an object of type `Tensor`, `Tbob`, or `NDArray`, and 2) an `RunContext` object. + +Essentially, `TensorInspector` can be understood as a wrapper class around `TBlob`. Internally, the `Tensor`, `Tbob`, or `NDArray` object that you passed in will be converted to a `TBlob` object. The `RunContext` object is used when the tensor is a GPU tensor; in such a case, we need to use the context information to copy the data from GPU memory to CPU/main memory. + +Following are the three constructors: + +```c++ +// Construct from Tensor object +template +TensorInspector(const mshadow::Tensor& ts, const RunContext& ctx); + +// Construct from TBlob object +TensorInspector(const TBlob& tb, const RunContext& ctx); + +// Construct from NDArray object +TensorInspector(const NDArray& arr, const RunContext& ctx): +``` + +### Print Tensor Value (Static) + +To print out the tensor value in a nicely structured way, you can use this API: + +```c++ +void print_string(); +``` + +This API will print the entire tensor to `std::cout` and preserve the shape (it supports all dimensions from 1 and up). You can copy the output and interpret it with any `JSON` loader. You can find some useful information about the tensor on the last line of the output. Refer to the case below, we are able to know that this is a float-typed tensor with shape 20x1x5x5. + +![tensor_inspector_to_string](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/doc/faq/tensor_inspector_tutorial/tensor_inspector_to_string.png) + +If instead of printing the tensor to `std::cout`, you just need a `string`, you can use this API: +```c++ +std::string void to_string(); +``` + +### Interactively Print Tensor Value (Dynamic) + +Sometimes at compilation time, you may not know which part of a tensor to inspect. Also, it may be nice to pause the operator control flow to “zoom into” a specific, erroneous part of a tensor multiple times until you are satisfied. In this regard, you can use this API to interactively inspect the tensor: + +```c++ +void interactive_print(std::string tag = "") { +``` + +This API will set a "break point" in your code. When that "break point" is reached, you will enter a loop that will keep asking you for further command input. In the API call, `tag` is an optional parameter to give the call a name, so that you can identify it when you have multiple `interactive_print()` calls in different parts of your code. A visit count will tell you how many times you stepped into this particular "break point", should this operator be called more than once. Note that all `interactive_print()` calls are properly locked, so you can use it in many different places without issues. + +![tensor_inspector_interactive_print](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/doc/faq/tensor_inspector_tutorial/tensor_inspector_interactive_print.png) + +There are many useful commands available, as described in the previous screenshot: you can type "e" to print out the entire tensor, "d" to dump the tensor to file (see below), "b" to break from this command loop, and "s" to skip all future `interactive_print()`. Most importantly, in this screen, you can specify a part of the tensor that you are particularly interested in and want to print out. For example, for this 64x20x24x24 tensor, you can type in "0, 0" and presss enter to check the sub-tensor with shape 24x24 at coordinate (0, 0). + +### Check Tensor Value + +Sometimes, developers might want to check if the tensor contains unexpected values which could be negative values, NaNs, infinities or others. To facilitate that, you can use these APIs: + +```c++ +template +std::vector> check_value(const ValueChecker& checker, + bool interactive = false, std::string tag = ""); +// OR +std::vector> check_value(CheckerType ct, + bool interactive = false, std::string tag = ""); +``` + +In the first API, `ValueChecker checker` is a bool lambda function that takes in a single parameter which is of the same data type as the tensor. For example: + +```c++ +// use the same DType as in the tensor object +[] (DType x) {return x == 0}; +``` + +This checker is called on every value within the tensor. The return of the API is a `vector` of all the coordinates where the checker evaluates to `true`. The coordinates are themselves represented by `vector`. If you set `interactive` to true, you will set a "break point" and enter a loop that asks for commands. This is similar to `interactive_print()`. You can type "p" to print the coordinates, "b" to break from the loop, and "s" to skip all future "break points" in `interactive_print()`. You can also specify a coordinate to print only a part of the tensor or type "e" to print out the entire tensor. Just like `interactive_print()`, this this interactive screen is also properly locked. + +![tensor_inspector_value_check](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/doc/faq/tensor_inspector_tutorial/tensor_inspector_value_check.png) + +Also, there are a bunch of built-int value checkers. Refer to the Enum below: + +```c++ +enum CheckerType { + NegativeChecker, // check if negative + PositiveChecker, // check if positive + ZeroChecker, // check for zero + NaNChecker, // check if for NaN, will always return false if DType is not a float type + InfChecker, // check for infinity, will always return false if DType is not a float type + PositiveInfChecker, // check for positive infinity, + // will always return false if DType is not a float type + NegativeInfChecker, // check for nagative infinity, + // will always return false if DType is not a float type + FiniteChecker, // check if finite, will always return false if DType is not a float type + NormalChecker, // check if it is neither infinity nor NaN + AbnormalChecker, // chekck if it is infinity or nan +}; +``` + +Remember the second API? + +```c++ +std::vector> check_value(CheckerType ct, + bool interactive = false, std::string tag = ""); +``` + +You can simply pass in a value from `CheckerType` where you would have passed in your own lambda if you were using the first API. Note that it's the developer's responsibility to pass in a valid value checker. + +### Dump Tensor Value + +Sometimes, you might want to dump the tensor to a file in binary mode. Then, you might want to use a python script to further analyze the tensor value. Or, you might do that simply because a binary dump has better precision and is faster to load than the output copy-pasted from `print_string()` and loaded as a `JSON` string. Either way, you can use this API: + +```c++ +void dump_to_file(std::string tag); +``` + +This API will create a file with name "{tag}_{visit_count}.npy", where tag is the name that we give to the call, and visit is the visit count, should the operated be called more than once. + +The output format is `.npy`, version 1.0. This is the Numpy format and we can easily load it with the following code: + +``` +import numpy as np +a = np.load('abc_1.npy') +print(a) +``` + +Let's see how it runs: + +![tensor_inspector_dump_to_file](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/doc/faq/tensor_inspector_tutorial/tensor_inspector_dump_to_file.png) + +Notice: in `interactive_print()`, you could also do value dumping with command "d". You will be prompted to enter the `tag` value: + +![tensor_inspector_interactive_print](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/doc/faq/tensor_inspector_tutorial/tensor_inspector_interactive_print.png) + +### Test Coverage and Limitations + +This utility has been tested on Mac and Ubuntu with and without CUDNN and MKLDNN. Supports for `Tensor`, `TBlob`, and `NDArray`, as well as for CPU and GPU have been manually tested. + +Currently, this utility only supports non-empty tensors and tensors with known shapes i.e. `tb_.ndim() > 0`. Also, this utility only supports dense `NDArray` objects, i.e. when the type is `kDefaultStorage`. + From d5df2473563b69cdd9ddb0b47966e6d66637f1d2 Mon Sep 17 00:00:00 2001 From: Iblis Lin Date: Sat, 20 Jul 2019 22:17:43 +0800 Subject: [PATCH 094/813] julia: bump window prebult binary version to v1.5.0 (#15608) - remove file REQUIRE, it's stale for Pkg3.jl --- julia/REQUIRE | 6 ------ julia/deps/build.jl | 16 ++++++++++------ 2 files changed, 10 insertions(+), 12 deletions(-) delete mode 100644 julia/REQUIRE diff --git a/julia/REQUIRE b/julia/REQUIRE deleted file mode 100644 index 8008da3d2aaa..000000000000 --- a/julia/REQUIRE +++ /dev/null @@ -1,6 +0,0 @@ -julia 0.7 -Formatting -BinDeps -JSON -MacroTools -Reexport diff --git a/julia/deps/build.jl b/julia/deps/build.jl index badca65be577..a87343d9dab5 100644 --- a/julia/deps/build.jl +++ b/julia/deps/build.jl @@ -24,7 +24,7 @@ using LinearAlgebra ################################################################################ libmxnet_detected = false libmxnet_curr_ver = get(ENV, "MXNET_COMMIT", "master") -curr_win = "20180211" # v1.1.0 +curr_win = "20190608" # v1.5.0 if haskey(ENV, "MXNET_HOME") MXNET_HOME = ENV["MXNET_HOME"] @@ -119,21 +119,25 @@ if !libmxnet_detected base_url = "https://github.com/yajiedesign/mxnet/releases/download/weekly_binary_build_v2/prebuildbase_win10_x64_vc14_v2.7z" if libmxnet_curr_ver == "master" + _cmd = "{ + [System.Net.ServicePointManager]::SecurityProtocol='tls12'; + Invoke-WebRequest -Uri 'https://api.github.com/repos/yajiedesign/mxnet/releases/latest' + -OutFile 'mxnet.json'}" # download_cmd uses powershell 2, but we need powershell 3 to do this - run(`powershell -NoProfile -Command Invoke-WebRequest -Uri "https://api.github.com/repos/yajiedesign/mxnet/releases/latest" -OutFile "mxnet.json"`) + run(`powershell -NoProfile -Command $_cmd`) curr_win = JSON.parsefile("mxnet.json")["tag_name"] @info("Can't use MXNet master on Windows, using latest binaries from $curr_win.") end # TODO: Get url from JSON. - name = "mxnet_x64_vc14_$(HAS_CUDA ? "gpu" : "cpu").7z" + # TODO: detect cuda version and select corresponding url. + name = "mxnet_x64_$(HAS_CUDA ? "vc141_gpu_cu101" : "vc14_cpu").7z" package_url = "https://github.com/yajiedesign/mxnet/releases/download/$(curr_win)/$(curr_win)_$(name)" - exe7z = joinpath(JULIA_HOME, "7z.exe") + exe7z = joinpath(Sys.BINDIR, "7z.exe") run(download_cmd(package_url, "mxnet.7z")) # this command will create the dir "usr\\lib" - run(`$exe7z x mxnet.7z build lib -y -ousr`) - run(`cmd /c copy "usr\\build\\*.dll" "usr\\lib"`) + run(`$exe7z e mxnet.7z *\\build\\* *\\lib\\* -y -ousr\\lib`) run(download_cmd(base_url, "mxnet_base.7z")) run(`$exe7z x mxnet_base.7z -y -ousr`) From 8f5930b2c95a6b7594ff6535a097e35b3315bc6d Mon Sep 17 00:00:00 2001 From: nicklhy Date: Sun, 21 Jul 2019 06:10:36 +0800 Subject: [PATCH 095/813] fix normalize mean error bug (#15539) * fix normalize mean error bug * add scalar mean/std tests for image_normalize --- src/operator/image/image_random-inl.h | 2 +- tests/python/unittest/test_operator.py | 53 +++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/operator/image/image_random-inl.h b/src/operator/image/image_random-inl.h index aeb189f35b78..e00b255bfd30 100644 --- a/src/operator/image/image_random-inl.h +++ b/src/operator/image/image_random-inl.h @@ -339,7 +339,7 @@ void NormalizeOpForward(const nnvm::NodeAttrs &attrs, std::vector mean(3); std::vector std(3); if (param.mean.ndim() == 1) { - mean[0] = mean[1] = mean[3] = param.mean[0]; + mean[0] = mean[1] = mean[2] = param.mean[0]; } else { mean[0] = param.mean[0]; mean[1] = param.mean[1]; diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index fea07f540624..915a83f2f342 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -8678,7 +8678,7 @@ def test_invalid_max_pooling_pad_type_same(): @with_seed() def test_image_normalize(): - # Part 1 - Test 3D Input + # Part 1 - Test 3D input with 3D mean/std shape_3d = (3, 28, 28) mean = (0, 1, 2) std = (3, 2, 1) @@ -8709,7 +8709,7 @@ def test_image_normalize(): # check backward using finite difference check_numeric_gradient(img_norm_sym, [data_in_3d], atol=0.001) - # Part 2 - Test 4D Input + # Part 2 - Test 4D input with 3D mean/std shape_4d = (2, 3, 28, 28) data_in_4d = mx.nd.random.uniform(0, 1, shape_4d) @@ -8741,6 +8741,55 @@ def test_image_normalize(): # check backward using finite difference check_numeric_gradient(img_norm_sym, [data_in_4d], atol=0.001) + # Part 3 - Test 3D input with scalar mean/std + shape_3d = (3, 28, 28) + mean = 1.0 + std = 2.0 + + data_in_3d = mx.nd.random.uniform(0, 1, shape_3d) + data_expected_3d = data_in_3d.asnumpy() + data_expected_3d[:][:][:] = (data_expected_3d[:][:][:] - 1.0) / 2.0 + + data = mx.symbol.Variable('data') + img_norm_sym = mx.sym.image.normalize(data=data, mean=mean, std=std) + + # check forward + check_symbolic_forward(img_norm_sym, [data_in_3d], [data_expected_3d], + rtol=1e-5, atol=1e-5) + + # Gradient is 1/std_dev + grad_expected_3d = np.ones(shape_3d) + grad_expected_3d[:][:][:] = 1 / 2.0 + + # check backward + check_symbolic_backward(img_norm_sym, location=[data_in_3d], out_grads=[mx.nd.ones(shape_3d)], + expected=[grad_expected_3d], rtol=1e-5, atol=1e-5) + + # check backward using finite difference + check_numeric_gradient(img_norm_sym, [data_in_3d], atol=0.001) + + # Part 4 - Test 4D input with scalar mean/std + shape_4d = (2, 3, 28, 28) + + data_in_4d = mx.nd.random.uniform(0, 1, shape_4d) + data_expected_4d = data_in_4d.asnumpy() + data_expected_4d[:][:][:][:] = (data_expected_4d[:][:][:][:] - 1.0) / 2.0 + + # check forward + check_symbolic_forward(img_norm_sym, [data_in_4d], [data_expected_4d], + rtol=1e-5, atol=1e-5) + + # Gradient is 1/std_dev + grad_expected_4d = np.ones(shape_4d) + grad_expected_4d[:][:][:][:] = 1 / 2.0 + + # check backward + check_symbolic_backward(img_norm_sym, location=[data_in_4d], out_grads=[mx.nd.ones(shape_4d)], + expected=[grad_expected_4d], rtol=1e-5, atol=1e-5) + + # check backward using finite difference + check_numeric_gradient(img_norm_sym, [data_in_4d], atol=0.001) + @with_seed() def test_index_array(): def test_index_array_default(): From f553b3134b6601c3588b2a0461bc6d4eb3d2dbf8 Mon Sep 17 00:00:00 2001 From: Iblis Lin Date: Sun, 21 Jul 2019 06:22:36 +0800 Subject: [PATCH 096/813] julia: remove Travis CI related files (#15616) We do not use travis since Julia package got merged into main repo. --- julia/.travis.yml | 59 ------------------------------- julia/test/travis/run_coverage.sh | 18 ---------- julia/test/travis/run_test.sh | 28 --------------- julia/test/travis/setup_env.sh | 38 -------------------- 4 files changed, 143 deletions(-) delete mode 100644 julia/.travis.yml delete mode 100644 julia/test/travis/run_coverage.sh delete mode 100755 julia/test/travis/run_test.sh delete mode 100755 julia/test/travis/setup_env.sh diff --git a/julia/.travis.yml b/julia/.travis.yml deleted file mode 100644 index 680df7af481e..000000000000 --- a/julia/.travis.yml +++ /dev/null @@ -1,59 +0,0 @@ -# Documentation: http://docs.travis-ci.com/user/languages/julia/ -sudo: false - -language: julia - -os: - - linux - - osx -osx_image: xcode8 - -julia: - - 0.6 -# - nightly 0.6 supports depends on #170 - -branches: - only: - - master - - stable - - /^v\d+\.\d+(\.\d+)?(-\S*)?$/ # for tagging - -cache: - directories: - - $TRAVIS_BUILD_DIR/deps/src - -# dependent apt packages -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - doxygen - - wget - - git - - libcurl4-openssl-dev - - unzip - - libatlas-base-dev - - libatlas-dev - - libopencv-dev - - gcc-4.8 - - g++-4.8 - -before_install: - - export TRAVIS_DIR=test/travis - - source ${TRAVIS_DIR}/setup_env.sh - -notifications: - email: false - -script: - - ${TRAVIS_DIR}/run_test.sh - -after_success: - # See https://github.com/dmlc/MXNet.jl/pull/303#issuecomment-341171774 - - julia -e 'using MXNet; mx._sig_checker()' - - - source ${TRAVIS_DIR}/run_coverage.sh - - echo $TRAVIS_JULIA_VERSION - - julia -e 'Pkg.add("Documenter")' - - julia -e 'cd(Pkg.dir("MXNet")); include(joinpath("docs", "make.jl"))' diff --git a/julia/test/travis/run_coverage.sh b/julia/test/travis/run_coverage.sh deleted file mode 100644 index ee22b258b549..000000000000 --- a/julia/test/travis/run_coverage.sh +++ /dev/null @@ -1,18 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -julia -e 'cd(Pkg.dir("MXNet")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' diff --git a/julia/test/travis/run_test.sh b/julia/test/travis/run_test.sh deleted file mode 100755 index 414b1450b554..000000000000 --- a/julia/test/travis/run_test.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -set -e - -if [[ -a .git/shallow ]]; then git fetch --unshallow; fi -julia -e 'Pkg.clone(pwd())' -( - cd `julia -e 'println(Pkg.dir("MXNet", "deps"))'` && - ln -fs $TRAVIS_BUILD_DIR/deps/src -) -julia -e 'Pkg.build("MXNet"); Pkg.test("MXNet"; coverage=true)' diff --git a/julia/test/travis/setup_env.sh b/julia/test/travis/setup_env.sh deleted file mode 100755 index 9d2f7341998d..000000000000 --- a/julia/test/travis/setup_env.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - - -echo "##########################" -echo $TRAVIS_OS_NAME - -if [ ${TRAVIS_OS_NAME} == "osx" ]; then - brew update >/dev/null 2>&1 - brew tap homebrew/science - brew info opencv - brew install graphviz - brew install opencv -fi - -if [ ${TRAVIS_OS_NAME} == "linux" ]; then - mkdir shadow_bin - ln -s `which gcc-4.8` shadow_bin/gcc - ln -s `which g++-4.8` shadow_bin/g++ - - export PATH=$PWD/shadow_bin:$PATH -fi From 5086ff0b3eb68b55b7b92e1232a79ffeadbe752d Mon Sep 17 00:00:00 2001 From: Sam Skalicky Date: Sat, 20 Jul 2019 16:53:42 -0700 Subject: [PATCH 097/813] changed constructor args (#15601) --- plugin/warpctc/warpctc-inl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/warpctc/warpctc-inl.h b/plugin/warpctc/warpctc-inl.h index 37677d21fd14..9fcbedce74f1 100644 --- a/plugin/warpctc/warpctc-inl.h +++ b/plugin/warpctc/warpctc-inl.h @@ -254,7 +254,7 @@ class WarpCTCProp : public OperatorProperty { CHECK_EQ(in_shape->size(), 2) << "Input:[data, label]"; const mxnet::TShape &dshape = in_shape->at(0); if (dshape.ndim() == 0) return false; - mxnet::TShape label_shape(dshape.ndim() - 1); + mxnet::TShape label_shape(dshape.ndim() - 1, 1); label_shape[0] = param_.label_length * (dshape[0] / param_.input_length); SHAPE_ASSIGN_CHECK(*in_shape, warpctc_enum::kLabel, label_shape); From 2b4c9c07c5f0039b93c51f7d9fb7123a0847c679 Mon Sep 17 00:00:00 2001 From: kshitij12345 Date: Sun, 21 Jul 2019 07:48:29 +0530 Subject: [PATCH 098/813] [fix] print `self` in warning. (#15614) * use format * make pylint happy * Update block.py --- python/mxnet/gluon/block.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/mxnet/gluon/block.py b/python/mxnet/gluon/block.py index 3bac3c023bf8..bd22cf85d561 100644 --- a/python/mxnet/gluon/block.py +++ b/python/mxnet/gluon/block.py @@ -842,8 +842,9 @@ def hybridize(self, active=True, **kwargs): self._flags = list(kwargs.items()) self._clear_cached_op() if active and self._forward_hooks or self._forward_pre_hooks: - warnings.warn('"{}" is being hybridized while still having forward hook/pre-hook. ' - 'If "{}" is a child of HybridBlock, the hooks will not take effect.') + warnings.warn('"{block}" is being hybridized while still having forward hook/pre-hook. ' + 'If "{block}" is a child of HybridBlock, the hooks will not take effect.' + .format(block=self)) super(HybridBlock, self).hybridize(active, **kwargs) def cast(self, dtype): From 399d3a0dc64386e106527257721a51c6152aff4e Mon Sep 17 00:00:00 2001 From: Iblis Lin Date: Sun, 21 Jul 2019 13:45:26 +0800 Subject: [PATCH 099/813] julia: bump binding version to v1.6.0 (#15607) --- julia/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/julia/Project.toml b/julia/Project.toml index 82a94c5a0767..994a696c1399 100644 --- a/julia/Project.toml +++ b/julia/Project.toml @@ -1,7 +1,7 @@ name = "MXNet" uuid = "a7949054-b901-59c6-b8e3-7238c29bf7f0" authors = ["Chiyuan Zhang ", "Valentin Churavy ", "Iblis Lin "] -version = "1.5.0" +version = "1.6.0" [deps] BinDeps = "9e28174c-4ba2-5203-b857-d8d62c4213ee" From d599bc3c1356b3471d27026711f67467122992f9 Mon Sep 17 00:00:00 2001 From: Neo Chien Date: Sun, 21 Jul 2019 16:07:39 +0800 Subject: [PATCH 100/813] [MXNET-1411] solve pylint error issue#14851 (#15113) * fix pylint error: no-else-raise in _export_helper.py * fix pylint error: no-else-raise in _translation_utils.py * fix pylint error: Bad option value 'no-else-raise' (bad-option-value) in vocab.py * fix pylint error: Bad option value 'no-else-raise' (bad-option-value) in trainer.py * fix pylint error: Bad option value 'no-else-raise' (bad-option-value) in utils.py * fix pylint error: Bad option value 'no-else-raise' (bad-option-value) in detection.py * fix pylint error: Bad option value 'no-else-raise' (bad-option-value) in image.py * fix pylint error: Bad option value 'no-else-raise' (bad-option-value) in model.py * fix pylint error: Bad option value 'no-else-raise' (bad-option-value) in sparse.py * fix pylint error: Bad option value 'no-else-raise' (bad-option-value) in test_utils.py * fix pylint error: R1720: Unnecessary else after raise (no-else-raise) for vocab.py * fix pylint error: R1720: Unnecessary else after raise (no-else-raise) for model.py * fix pylint error: R1720: Unnecessary else after raise (no-else-raise) for _translation_utils.py * fix pylint error: R1720: Unnecessary else after raise (no-else-raise) for _export_helper.py * fix pylint error: R1720: Unnecessary else after raise (no-else-raise) for test_utils.py * fix pylint error: R1720: Unnecessary else after raise (no-else-raise) for image.py * fix pylint error: R1720: Unnecessary else after raise (no-else-raise) for trainer.py * fix pylint error: R1720: Unnecessary else after raise (no-else-raise) for detection.py * fix pylint error: R1720: Unnecessary else after raise (no-else-raise) for utils.py * fix pylint error: R1720: Unnecessary else after raise (no-else-raise) for sparse.py * fix pylint error:R1719: The if expression can be replaced with 'bool(test)' (simplifiable-if-expression) --- .../contrib/onnx/mx2onnx/_export_helper.py | 38 +++++++++---------- .../onnx/onnx2mx/_translation_utils.py | 30 +++++++-------- python/mxnet/contrib/text/vocab.py | 5 +-- python/mxnet/gluon/trainer.py | 12 +++--- python/mxnet/gluon/utils.py | 8 ++-- python/mxnet/image/detection.py | 18 ++++----- python/mxnet/image/image.py | 24 ++++++------ python/mxnet/model.py | 5 +-- python/mxnet/ndarray/sparse.py | 10 ++--- python/mxnet/test_utils.py | 28 +++++++------- 10 files changed, 88 insertions(+), 90 deletions(-) diff --git a/python/mxnet/contrib/onnx/mx2onnx/_export_helper.py b/python/mxnet/contrib/onnx/mx2onnx/_export_helper.py index e73ff70fa5b0..fdb6689579be 100644 --- a/python/mxnet/contrib/onnx/mx2onnx/_export_helper.py +++ b/python/mxnet/contrib/onnx/mx2onnx/_export_helper.py @@ -40,26 +40,26 @@ def load_module(sym_filepath, params_filepath): params : params object Model weights including both arg and aux params. """ - if not (os.path.isfile(sym_filepath) and os.path.isfile(params_filepath)): # pylint: disable=no-else-raise + if not (os.path.isfile(sym_filepath) and os.path.isfile(params_filepath)): raise ValueError("Symbol and params files provided are invalid") - else: - try: - # reads symbol.json file from given path and - # retrieves model prefix and number of epochs - model_name = sym_filepath.rsplit('.', 1)[0].rsplit('-', 1)[0] - params_file_list = params_filepath.rsplit('.', 1)[0].rsplit('-', 1) - # Setting num_epochs to 0 if not present in filename - num_epochs = 0 if len(params_file_list) == 1 else int(params_file_list[1]) - except IndexError: - logging.info("Model and params name should be in format: " - "prefix-symbol.json, prefix-epoch.params") - raise - sym, arg_params, aux_params = mx.model.load_checkpoint(model_name, num_epochs) + try: + # reads symbol.json file from given path and + # retrieves model prefix and number of epochs + model_name = sym_filepath.rsplit('.', 1)[0].rsplit('-', 1)[0] + params_file_list = params_filepath.rsplit('.', 1)[0].rsplit('-', 1) + # Setting num_epochs to 0 if not present in filename + num_epochs = 0 if len(params_file_list) == 1 else int(params_file_list[1]) + except IndexError: + logging.info("Model and params name should be in format: " + "prefix-symbol.json, prefix-epoch.params") + raise - # Merging arg and aux parameters - params = {} - params.update(arg_params) - params.update(aux_params) + sym, arg_params, aux_params = mx.model.load_checkpoint(model_name, num_epochs) - return sym, params + # Merging arg and aux parameters + params = {} + params.update(arg_params) + params.update(aux_params) + + return sym, params diff --git a/python/mxnet/contrib/onnx/onnx2mx/_translation_utils.py b/python/mxnet/contrib/onnx/onnx2mx/_translation_utils.py index 48ede28ab022..0160c5c61974 100644 --- a/python/mxnet/contrib/onnx/onnx2mx/_translation_utils.py +++ b/python/mxnet/contrib/onnx/onnx2mx/_translation_utils.py @@ -178,23 +178,23 @@ def _fix_channels(op_name, attrs, inputs, proto_obj): these attributes. We check the shape of weights provided to get the number. """ weight_name = inputs[1].name - if not weight_name in proto_obj._params: # pylint: disable=no-else-raise + if not weight_name in proto_obj._params: raise ValueError("Unable to get channels/units attr from onnx graph.") - else: - wshape = proto_obj._params[weight_name].shape - assert len(wshape) >= 2, "Weights shape is invalid: {}".format(wshape) - if op_name == 'FullyConnected': - attrs['num_hidden'] = wshape[0] - else: - if op_name == 'Convolution': - # Weight shape for Conv and FC: (M x C x kH x kW) : M is number of - # feature maps/hidden and C is number of channels - attrs['num_filter'] = wshape[0] - elif op_name == 'Deconvolution': - # Weight shape for DeConv : (C x M x kH x kW) : M is number of - # feature maps/filters and C is number of channels - attrs['num_filter'] = wshape[1] + wshape = proto_obj._params[weight_name].shape + assert len(wshape) >= 2, "Weights shape is invalid: {}".format(wshape) + + if op_name == 'FullyConnected': + attrs['num_hidden'] = wshape[0] + else: + if op_name == 'Convolution': + # Weight shape for Conv and FC: (M x C x kH x kW) : M is number of + # feature maps/hidden and C is number of channels + attrs['num_filter'] = wshape[0] + elif op_name == 'Deconvolution': + # Weight shape for DeConv : (C x M x kH x kW) : M is number of + # feature maps/filters and C is number of channels + attrs['num_filter'] = wshape[1] return attrs diff --git a/python/mxnet/contrib/text/vocab.py b/python/mxnet/contrib/text/vocab.py index 6e9920d601b6..7db3ee844bb5 100644 --- a/python/mxnet/contrib/text/vocab.py +++ b/python/mxnet/contrib/text/vocab.py @@ -210,9 +210,8 @@ def to_tokens(self, indices): tokens = [] for idx in indices: - if not isinstance(idx, int) or idx > max_idx: # pylint: disable=no-else-raise + if not isinstance(idx, int) or idx > max_idx: raise ValueError('Token index %d in the provided `indices` is invalid.' % idx) - else: - tokens.append(self.idx_to_token[idx]) + tokens.append(self.idx_to_token[idx]) return tokens[0] if to_reduce else tokens diff --git a/python/mxnet/gluon/trainer.py b/python/mxnet/gluon/trainer.py index 0939490a8307..c8aeb614160a 100644 --- a/python/mxnet/gluon/trainer.py +++ b/python/mxnet/gluon/trainer.py @@ -249,11 +249,11 @@ def _init_kvstore(self): @property def learning_rate(self): - if not isinstance(self._optimizer, opt.Optimizer): # pylint: disable=no-else-raise + if not isinstance(self._optimizer, opt.Optimizer): raise UserWarning("Optimizer has to be defined before its learning " "rate can be accessed.") - else: - return self._optimizer.learning_rate + + return self._optimizer.learning_rate @property def optimizer(self): @@ -270,11 +270,11 @@ def set_learning_rate(self, lr): lr : float The new learning rate of the optimizer. """ - if not isinstance(self._optimizer, opt.Optimizer): # pylint: disable=no-else-raise + if not isinstance(self._optimizer, opt.Optimizer): raise UserWarning("Optimizer has to be defined before its learning " "rate is mutated.") - else: - self._optimizer.set_learning_rate(lr) + + self._optimizer.set_learning_rate(lr) def _row_sparse_pull(self, parameter, out, row_id, full_idx=False): """Internal method to invoke pull operations on KVStore. If `full_idx` is set to True, diff --git a/python/mxnet/gluon/utils.py b/python/mxnet/gluon/utils.py index 3957b7402688..2060f61a0212 100644 --- a/python/mxnet/gluon/utils.py +++ b/python/mxnet/gluon/utils.py @@ -341,11 +341,11 @@ def download(url, path=None, overwrite=False, sha1_hash=None, retries=5, verify_ break except Exception as e: retries -= 1 - if retries <= 0: # pylint: disable=no-else-raise + if retries <= 0: raise e - else: - print('download failed due to {}, retrying, {} attempt{} left' - .format(repr(e), retries, 's' if retries > 1 else '')) + + print('download failed due to {}, retrying, {} attempt{} left' + .format(repr(e), retries, 's' if retries > 1 else '')) return fname diff --git a/python/mxnet/image/detection.py b/python/mxnet/image/detection.py index a70e5723072f..48cf5bc1cf67 100644 --- a/python/mxnet/image/detection.py +++ b/python/mxnet/image/detection.py @@ -809,23 +809,23 @@ def next(self): pad = batch_size - i # handle padding for the last batch if pad != 0: - if self.last_batch_handle == 'discard': # pylint: disable=no-else-raise + if self.last_batch_handle == 'discard': raise StopIteration # if the option is 'roll_over', throw StopIteration and cache the data - elif self.last_batch_handle == 'roll_over' and \ + if self.last_batch_handle == 'roll_over' and \ self._cache_data is None: self._cache_data = batch_data self._cache_label = batch_label self._cache_idx = i raise StopIteration + + _ = self._batchify(batch_data, batch_label, i) + if self.last_batch_handle == 'pad': + self._allow_read = False else: - _ = self._batchify(batch_data, batch_label, i) - if self.last_batch_handle == 'pad': - self._allow_read = False - else: - self._cache_data = None - self._cache_label = None - self._cache_idx = None + self._cache_data = None + self._cache_label = None + self._cache_idx = None return io.DataBatch([batch_data], [batch_label], pad=pad) diff --git a/python/mxnet/image/image.py b/python/mxnet/image/image.py index a142282c83a6..bffd286cb538 100644 --- a/python/mxnet/image/image.py +++ b/python/mxnet/image/image.py @@ -1198,10 +1198,10 @@ def __init__(self, batch_size, data_shape, label_width=1, logging.info('%s: loading recordio %s...', class_name, path_imgrec) if path_imgidx: - self.imgrec = recordio.MXIndexedRecordIO(path_imgidx, path_imgrec, 'r') # pylint: disable=redefined-variable-type + self.imgrec = recordio.MXIndexedRecordIO(path_imgidx, path_imgrec, 'r') self.imgidx = list(self.imgrec.keys) else: - self.imgrec = recordio.MXRecordIO(path_imgrec, 'r') # pylint: disable=redefined-variable-type + self.imgrec = recordio.MXRecordIO(path_imgrec, 'r') self.imgidx = None else: self.imgrec = None @@ -1224,7 +1224,7 @@ def __init__(self, batch_size, data_shape, label_width=1, imgkeys = [] index = 1 for img in imglist: - key = str(index) # pylint: disable=redefined-variable-type + key = str(index) index += 1 if len(img) > 2: label = nd.array(img[:-1], dtype=dtype) @@ -1374,23 +1374,23 @@ def next(self): pad = batch_size - i # handle padding for the last batch if pad != 0: - if self.last_batch_handle == 'discard': # pylint: disable=no-else-raise + if self.last_batch_handle == 'discard': raise StopIteration # if the option is 'roll_over', throw StopIteration and cache the data - elif self.last_batch_handle == 'roll_over' and \ + if self.last_batch_handle == 'roll_over' and \ self._cache_data is None: self._cache_data = batch_data self._cache_label = batch_label self._cache_idx = i raise StopIteration + + _ = self._batchify(batch_data, batch_label, i) + if self.last_batch_handle == 'pad': + self._allow_read = False else: - _ = self._batchify(batch_data, batch_label, i) - if self.last_batch_handle == 'pad': - self._allow_read = False - else: - self._cache_data = None - self._cache_label = None - self._cache_idx = None + self._cache_data = None + self._cache_label = None + self._cache_idx = None return io.DataBatch([batch_data], [batch_label], pad=pad) diff --git a/python/mxnet/model.py b/python/mxnet/model.py index 7e324a1d6b7e..aee4a8ce2b45 100644 --- a/python/mxnet/model.py +++ b/python/mxnet/model.py @@ -642,10 +642,9 @@ def _init_iter(self, X, y, is_train): """Initialize the iterator given input.""" if isinstance(X, (np.ndarray, nd.NDArray)): if y is None: - if is_train: # pylint: disable=no-else-raise + if is_train: raise ValueError('y must be specified when X is numpy.ndarray') - else: - y = np.zeros(X.shape[0]) + y = np.zeros(X.shape[0]) if not isinstance(y, (np.ndarray, nd.NDArray)): raise TypeError('y must be ndarray when X is numpy.ndarray') if X.shape[0] != y.shape[0]: diff --git a/python/mxnet/ndarray/sparse.py b/python/mxnet/ndarray/sparse.py index 4987cb57b6ea..52c5de78ddd4 100644 --- a/python/mxnet/ndarray/sparse.py +++ b/python/mxnet/ndarray/sparse.py @@ -639,10 +639,10 @@ def __getitem__(self, key): if isinstance(key, int): raise Exception("__getitem__ with int key is not implemented for RowSparseNDArray yet") if isinstance(key, py_slice): - if key.step is not None or key.start is not None or key.stop is not None: # pylint: disable=no-else-raise + if key.step is not None or key.start is not None or key.stop is not None: raise Exception('RowSparseNDArray only supports [:] for __getitem__') - else: - return self + + return self if isinstance(key, tuple): raise ValueError('Multi-dimension indexing is not supported') raise ValueError('Undefined behaviour for {}'.format(key)) @@ -1102,9 +1102,9 @@ def row_sparse_array(arg1, shape=None, ctx=None, dtype=None): # construct a row sparse array from (D0, D1 ..) or (data, indices) if isinstance(arg1, tuple): arg_len = len(arg1) - if arg_len < 2: # pylint: disable=no-else-raise + if arg_len < 2: raise ValueError("Unexpected length of input tuple: " + str(arg_len)) - elif arg_len > 2: + if arg_len > 2: # empty ndarray with shape _check_shape(arg1, shape) return empty('row_sparse', arg1, ctx=ctx, dtype=dtype) diff --git a/python/mxnet/test_utils.py b/python/mxnet/test_utils.py index aa46a9628b81..0e260ceb7676 100644 --- a/python/mxnet/test_utils.py +++ b/python/mxnet/test_utils.py @@ -212,11 +212,11 @@ def _get_powerlaw_dataset_csr(num_rows, num_cols, density=0.1, dtype=None): return mx.nd.array(output_arr).tostype("csr") col_max = col_max * 2 - if unused_nnz > 0: # pylint: disable=no-else-raise + if unused_nnz > 0: raise ValueError("not supported for this density: %s" " for this shape (%s,%s)" % (density, num_rows, num_cols)) - else: - return mx.nd.array(output_arr).tostype("csr") + + return mx.nd.array(output_arr).tostype("csr") def assign_each(the_input, function): @@ -1407,10 +1407,10 @@ def check_consistency(sym, ctx_list, scale=1.0, grad_req='write', except AssertionError as e: print('Predict Err: ctx %d vs ctx %d at %s'%(i, max_idx, name)) traceback.print_exc() - if raise_on_err: # pylint: disable=no-else-raise + if raise_on_err: raise e - else: - print(str(e)) + + print(str(e)) # train if grad_req != 'null': @@ -1434,10 +1434,10 @@ def check_consistency(sym, ctx_list, scale=1.0, grad_req='write', except AssertionError as e: print('Train Err: ctx %d vs ctx %d at %s'%(i, max_idx, name)) traceback.print_exc() - if raise_on_err: # pylint: disable=no-else-raise + if raise_on_err: raise e - else: - print(str(e)) + + print(str(e)) return gt @@ -1514,11 +1514,11 @@ def download(url, fname=None, dirname=None, overwrite=False, retries=5): break except Exception as e: retries -= 1 - if retries <= 0: # pylint: disable=no-else-raise + if retries <= 0: raise e - else: - print("download failed, retrying, {} attempt{} left" - .format(retries, 's' if retries > 1 else '')) + + print("download failed, retrying, {} attempt{} left" + .format(retries, 's' if retries > 1 else '')) logging.info("downloaded %s into %s successfully", url, fname) return fname @@ -1661,7 +1661,7 @@ def get_mnist_iterator(batch_size, input_shape, num_parts=1, part_index=0): """ get_mnist_ubyte() - flat = False if len(input_shape) == 3 else True # pylint: disable=simplifiable-if-expression + flat = not bool(len(input_shape) == 3) train_dataiter = mx.io.MNISTIter( image="data/train-images-idx3-ubyte", From 9d859c8585fe7924bf904938b844a721b5cf98cf Mon Sep 17 00:00:00 2001 From: William Tambellini Date: Sun, 21 Jul 2019 11:59:22 -0700 Subject: [PATCH 101/813] logging (#15106) --- example/named_entity_recognition/src/metrics.py | 13 ++++++++----- example/named_entity_recognition/src/ner.py | 8 ++++++++ example/named_entity_recognition/src/preprocess.py | 7 +++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/example/named_entity_recognition/src/metrics.py b/example/named_entity_recognition/src/metrics.py index ef5f64fb1af3..a1d270af6863 100644 --- a/example/named_entity_recognition/src/metrics.py +++ b/example/named_entity_recognition/src/metrics.py @@ -19,6 +19,7 @@ # -*- coding: utf-8 -*- +import logging import mxnet as mx import numpy as np import pickle @@ -43,17 +44,17 @@ def classifer_metrics(label, pred): corr_pred = (prediction == label) == (pred_is_entity == True) #how many entities are there? - num_entities = np.sum(label_is_entity) - entity_preds = np.sum(pred_is_entity) - + # better to cast to float for safer further ratio computations + num_entities = float(np.sum(label_is_entity)) + entity_preds = float(np.sum(pred_is_entity)) #how many times did we correctly predict an entity? - correct_entitites = np.sum(corr_pred[pred_is_entity]) + correct_entitites = float(np.sum(corr_pred[pred_is_entity])) #precision: when we predict entity, how often are we right? if entity_preds == 0: precision = np.nan else: - precision = correct_entitites/entity_preds + precision = correct_entitites / entity_preds #recall: of the things that were an entity, how many did we catch? recall = correct_entitites / num_entities @@ -64,6 +65,8 @@ def classifer_metrics(label, pred): f1 = 0 else: f1 = 2 * precision * recall / (precision + recall) + + logging.debug("Metrics results: precision=%f recall=%f f1=%f", precision, recall, f1) return precision, recall, f1 def entity_precision(label, pred): diff --git a/example/named_entity_recognition/src/ner.py b/example/named_entity_recognition/src/ner.py index 7f5dd84527cc..6accb2826f2d 100644 --- a/example/named_entity_recognition/src/ner.py +++ b/example/named_entity_recognition/src/ner.py @@ -93,6 +93,7 @@ def build_vocab(nested_list): """ # Build vocabulary word_counts = Counter(itertools.chain(*nested_list)) + logging.info("build_vocab: word_counts=%d" % (len(word_counts))) # Mapping from index to label vocabulary_inv = [x[0] for x in word_counts.most_common()] @@ -114,6 +115,7 @@ def build_iters(data_dir, max_records, train_fraction, batch_size, buckets=None) :param buckets: size of each bucket in the iterators :return: train_iter, val_iter, word_to_index, index_to_word, pos_to_index, index_to_pos """ + # Read in data as numpy array df = pd.read_pickle(os.path.join(data_dir, "ner_data.pkl"))[:max_records] @@ -135,12 +137,14 @@ def build_iters(data_dir, max_records, train_fraction, batch_size, buckets=None) # Split into training and testing data idx=int(len(indexed_tokens)*train_fraction) + logging.info("Preparing train/test datasets splitting at idx %d on total %d sentences using a batchsize of %d", idx, len(indexed_tokens), batch_size) X_token_train, X_char_train, Y_train = indexed_tokens[:idx], indexed_chars[:idx], indexed_entities[:idx] X_token_test, X_char_test, Y_test = indexed_tokens[idx:], indexed_chars[idx:], indexed_entities[idx:] # build iterators to feed batches to network train_iter = iterators.BucketNerIter(sentences=X_token_train, characters=X_char_train, label=Y_train, max_token_chars=5, batch_size=batch_size, buckets=buckets) + logging.info("Creating the val_iter using %d sentences", len(X_token_test)) val_iter = iterators.BucketNerIter(sentences=X_token_test, characters=X_char_test, label=Y_test, max_token_chars=train_iter.max_token_chars, batch_size=batch_size, buckets=train_iter.buckets) return train_iter, val_iter, word_to_index, char_to_index, entity_to_index @@ -205,6 +209,8 @@ def sym_gen(seq_len): def train(train_iter, val_iter): import metrics devs = mx.cpu() if args.gpus is None or args.gpus is '' else [mx.gpu(int(i)) for i in args.gpus.split(',')] + logging.info("train on device %s using optimizer %s at learningrate %f for %d epochs using %d records: lstm_state_size=%d ...", + devs, args.optimizer, args.lr, args.num_epochs, args.max_records, args.lstm_state_size) module = mx.mod.BucketingModule(sym_gen, train_iter.default_bucket_key, context=devs) module.fit(train_data=train_iter, eval_data=val_iter, @@ -225,6 +231,8 @@ def train(train_iter, val_iter): train_iter, val_iter, word_to_index, char_to_index, entity_to_index = build_iters(args.data_dir, args.max_records, args.train_fraction, args.batch_size, args.buckets) + logging.info("validation iterator: %s", val_iter) + # Define the recurrent layer bi_cell = mx.rnn.SequentialRNNCell() for layer_num in range(args.lstm_layers): diff --git a/example/named_entity_recognition/src/preprocess.py b/example/named_entity_recognition/src/preprocess.py index 6ae348ad8bae..22c1c360c62d 100644 --- a/example/named_entity_recognition/src/preprocess.py +++ b/example/named_entity_recognition/src/preprocess.py @@ -19,6 +19,7 @@ # -*- coding: utf-8 -*- +import logging import pandas as pd import numpy as np @@ -45,6 +46,12 @@ #join the results on utterance id df = df1.merge(df2.merge(df3, how = "left", on = "utterance_id"), how = "left", on = "utterance_id") +pd.option_context('display.max_colwidth', None) +pd.option_context('display.max_rowwidth', None) + +logging.info("preprocess: 1st sentence:") +logging.info(df['token'].iloc[0].tolist()) +logging.info(df['BILOU_tag'].iloc[0].tolist()) #save the dataframe to a csv file df.to_pickle("../data/ner_data.pkl") \ No newline at end of file From 9a1a1028529c91c83f275edb74b272f8ad3dcc3d Mon Sep 17 00:00:00 2001 From: Wuxun Zhang Date: Mon, 22 Jul 2019 07:25:30 +0800 Subject: [PATCH 102/813] Add omp parallel optimization for _contrib_BilinearReisze2D (#15584) * Add omp parallel optimization for bilinear_resize op * retrigger CI * retrigger CI * trigger CI --- src/operator/contrib/bilinear_resize.cc | 146 ++++++++++++++---------- tests/python/gpu/test_operator_gpu.py | 16 +++ tests/python/unittest/test_operator.py | 2 +- 3 files changed, 101 insertions(+), 63 deletions(-) diff --git a/src/operator/contrib/bilinear_resize.cc b/src/operator/contrib/bilinear_resize.cc index 441ea53ad9c6..346324753a26 100644 --- a/src/operator/contrib/bilinear_resize.cc +++ b/src/operator/contrib/bilinear_resize.cc @@ -23,7 +23,6 @@ * \author Hang Zhang */ #include "bilinear_resize-inl.h" -// #include "elemwise_op_common.h" #include "../elemwise_op_common.h" namespace mxnet { @@ -44,56 +43,66 @@ void SpatialUpSamplingBilinearUpdateOutput(mshadow::Stream *s, int inputHeight = itensor.size(2); int inputWidth = itensor.size(3); + const auto nthreads = engine::OpenMP::Get()->GetRecommendedOMPThreadCount(); + DType *idata = itensor.dptr_; DType *odata = otensor.dptr_; channels = nbatch * channels; + const int input_elems_per_channel = inputWidth * inputHeight; + const int output_elems_per_channel = outputWidth * outputHeight; + // special case: just copy if (inputHeight == outputHeight && inputWidth == outputWidth) { - for (int h2 = 0; h2 < outputHeight; ++h2) { +#pragma omp parallel for num_threads(nthreads) + for (int index = 0; index < output_elems_per_channel; index++) { + const int h2 = index / outputWidth; const int h1 = h2; - for (int w2 = 0; w2 < outputWidth; ++w2) { - const int w1 = w2; - const DType* pos1 = &idata[h1 * inputWidth + w1]; - DType* pos2 = &odata[h2 * outputWidth + w2]; - for (int c = 0; c < channels; ++c) { - pos2[0] = pos1[0]; - pos1 += inputWidth * inputHeight; - pos2 += outputWidth * outputHeight; - } + const int w2 = index % outputWidth; + const int w1 = w2; + const DType* pos1 = &idata[h1 * inputWidth + w1]; + DType* pos2 = &odata[index]; + for (int c = 0; c < channels; ++c) { + *pos2 = *pos1; + pos1 += input_elems_per_channel; + pos2 += output_elems_per_channel; } } return; } + const float rheight =(outputHeight > 1) ? static_cast(inputHeight - 1)/ (outputHeight - 1) : 0.f; const float rwidth = (outputWidth > 1) ? static_cast(inputWidth - 1) / (outputWidth - 1) : 0.f; - for (int h2 = 0; h2 < outputHeight; ++h2) { +#pragma omp parallel for num_threads(nthreads) + for (int index = 0; index < output_elems_per_channel; index++) { + const int h2 = index / outputWidth; + const int w2 = index % outputWidth; + const float h1r = rheight * h2; const int h1 = h1r; const int h1p = (h1 < inputHeight - 1) ? 1 : 0; const DType h1lambda = h1r - h1; const DType h0lambda = (DType)1. - h1lambda; - for (int w2 = 0; w2 < outputWidth; ++w2) { - const float w1r = rwidth * w2; - const int w1 = w1r; - const int w1p = (w1 < inputWidth - 1) ? 1 : 0; - const DType w1lambda = w1r - w1; - const DType w0lambda = (DType)1. - w1lambda; - const DType* pos1 = &idata[h1 * inputWidth + w1]; - DType* pos2 = &odata[h2 * outputWidth + w2]; - for (int c = 0; c < channels; ++c) { - pos2[0] = h0lambda * (w0lambda * pos1[0]+ w1lambda * pos1[w1p]) - + h1lambda * (w0lambda * pos1[h1p * inputWidth] - + w1lambda * pos1[h1p * inputWidth + w1p]); - pos1 += inputWidth * inputHeight; - pos2 += outputWidth * outputHeight; - } + + const float w1r = rwidth * w2; + const int w1 = w1r; + const int w1p = (w1 < inputWidth - 1) ? 1 : 0; + const DType w1lambda = w1r - w1; + const DType w0lambda = (DType)1. - w1lambda; + const DType* pos1 = &idata[h1 * inputWidth + w1]; + DType* pos2 = &odata[index]; + + for (int c = 0; c < channels; ++c) { + *pos2 = h0lambda * (w0lambda * (*pos1) + w1lambda * *(pos1 + w1p)) + + h1lambda * (w0lambda * *(pos1 + h1p * inputWidth) + + w1lambda * *(pos1 + h1p * inputWidth + w1p)); + pos1 += input_elems_per_channel; + pos2 += output_elems_per_channel; } } } - template void SpatialUpSamplingBilinearUpdateGradInput(mshadow::Stream *s, const std::vector &input, @@ -109,23 +118,28 @@ void SpatialUpSamplingBilinearUpdateGradInput(mshadow::Stream *s, int inputHeight = gradInput.size(2); int inputWidth = gradInput.size(3); + const auto nthreads = engine::OpenMP::Get()->GetRecommendedOMPThreadCount(); + DType *dataInput = gradInput.dptr_; DType *dataOutput = gradOutput.dptr_; channels = nbatch * channels; + const int input_elems_per_channel = inputWidth * inputHeight; + const int output_elems_per_channel = outputWidth * outputHeight; // special case: same-size matching grids if (inputHeight == outputHeight && inputWidth == outputWidth) { - for (int h2 = 0; h2 < outputHeight; ++h2) { +#pragma omp parallel for num_threads(nthreads) + for (int index = 0; index < output_elems_per_channel; index++) { + const int h2 = index / outputWidth; const int h1 = h2; - for (int w2 = 0; w2 < outputWidth; ++w2) { - const int w1 = w2; - DType* pos1 = &dataInput[h1 * inputWidth + w1]; - const DType* pos2 = &dataOutput[h2 * outputWidth + w2]; - for (int c = 0; c < channels; ++c) { - pos1[0] += pos2[0]; - pos1 += inputWidth * inputHeight; - pos2 += outputWidth * outputHeight; - } + const int w2 = index % outputWidth; + const int w1 = w2; + DType* pos1 = &dataInput[h1 * inputWidth + w1]; + const DType* pos2 = &dataOutput[index]; + for (int c = 0; c < channels; ++c) { + *pos1 += *pos2; + pos1 += input_elems_per_channel; + pos2 += output_elems_per_channel; } } return; @@ -134,28 +148,36 @@ void SpatialUpSamplingBilinearUpdateGradInput(mshadow::Stream *s, (outputHeight - 1) : 0.f; const float rwidth = (outputWidth > 1) ? static_cast(inputWidth - 1)/ (outputWidth - 1) : 0.f; - for (int h2 = 0; h2 < outputHeight; ++h2) { + +#pragma omp parallel for num_threads(nthreads) + for (int index = 0; index < output_elems_per_channel; index++) { + const int h2 = index / outputWidth; + const int w2 = index % outputWidth; + const float h1r = rheight * h2; const int h1 = h1r; const int h1p = (h1 < inputHeight - 1) ? 1 : 0; const DType h1lambda = h1r - h1; const DType h0lambda = (DType)1. - h1lambda; - for (int w2 = 0; w2 < outputWidth; ++w2) { - const float w1r = rwidth * w2; - const int w1 = w1r; - const int w1p = (w1 < inputWidth - 1) ? 1 : 0; - const DType w1lambda = w1r - w1; - const DType w0lambda = (DType)1. - w1lambda; - DType* posInput = &dataInput[h1 * inputWidth + w1]; - const DType* posOutput = &dataOutput[h2 * outputWidth + w2]; - for (int c = 0; c < channels; ++c) { - posInput[0] += h0lambda * w0lambda * posOutput[0]; - posInput[w1p] += h0lambda * w1lambda * posOutput[0]; - posInput[h1p * inputWidth] += h1lambda * w0lambda * posOutput[0]; - posInput[h1p * inputWidth + w1p] += h1lambda * w1lambda * posOutput[0]; - posInput += inputWidth * inputHeight; - posOutput += outputWidth * outputHeight; + + const float w1r = rwidth * w2; + const int w1 = w1r; + const int w1p = (w1 < inputWidth - 1) ? 1 : 0; + const DType w1lambda = w1r - w1; + const DType w0lambda = (DType)1. - w1lambda; + + DType* posInput = &dataInput[h1 * inputWidth + w1]; + const DType* posOutput = &dataOutput[index]; + for (int c = 0; c < channels; ++c) { + #pragma omp critical + { + *posInput += h0lambda * w0lambda * (*posOutput); + *(posInput + w1p) += h0lambda * w1lambda * (*posOutput); + *(posInput + h1p * inputWidth) += h1lambda * w0lambda * (*posOutput); + *(posInput + h1p * inputWidth + w1p) += h1lambda * w1lambda * (*posOutput); } + posInput += input_elems_per_channel; + posOutput += output_elems_per_channel; } } @@ -165,19 +187,19 @@ void SpatialUpSamplingBilinearUpdateGradInput(mshadow::Stream *s, int inputWidthLike = gradInputLike.size(3); DType *dataInputLike = gradInputLike.dptr_; int channelsLike = nbatch * gradInputLike.size(1); - for (int h_like = 0; h_like < inputHeightLike; ++h_like) { - for (int w_like = 0; w_like < inputWidthLike; ++w_like) { - DType *posInput = &dataInputLike[h_like * inputWidthLike + w_like]; - for (int c = 0; c < channelsLike; ++c) { - posInput[0] = 0; - posInput += inputWidthLike * inputHeightLike; - } + + const int inputLike_elems_per_channel = inputHeightLike * inputWidthLike; +#pragma omp parallel for num_threads(nthreads) + for (int index = 0; index < inputLike_elems_per_channel; index++) { + DType *posInput = &dataInputLike[index]; + for (int c = 0; c < channelsLike; ++c) { + *posInput = 0; + posInput += inputLike_elems_per_channel; } } } } - DMLC_REGISTER_PARAMETER(BilinearSampleParam); NNVM_REGISTER_OP(_contrib_BilinearResize2D) diff --git a/tests/python/gpu/test_operator_gpu.py b/tests/python/gpu/test_operator_gpu.py index 5b4f81d96065..f9814abc9c0b 100644 --- a/tests/python/gpu/test_operator_gpu.py +++ b/tests/python/gpu/test_operator_gpu.py @@ -1143,6 +1143,22 @@ def test_flatten_slice_after_conv(): check_consistency(slice_sym, ctx_list) +@with_seed() +def test_bilinear_resize_op(): + ctx_list = [{'ctx': mx.cpu(0), 'data': (2, 2, 20, 20), 'type_dict': {'data': np.float32}}, + {'ctx': mx.gpu(0), 'data': (2, 2, 20, 20), 'type_dict': {'data': np.float32}}] + + data = mx.sym.Variable('data') + sym = mx.sym.contrib.BilinearResize2D(data, height=10, width=5) + check_consistency(sym, ctx_list) + + sym = mx.sym.contrib.BilinearResize2D(data, None, scale_height=2, scale_width=0.5, mode='odd_scale') + check_consistency(sym, ctx_list) + + sym = mx.sym.contrib.BilinearResize2D(data, None, scale_height=0.5, scale_width=2, mode='to_even_up') + check_consistency(sym, ctx_list) + + @with_seed() def test_global_pooling(): def test_1d_pooling(pool_type, p_value=2): diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index 915a83f2f342..d195ea9ef2f3 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -7649,7 +7649,7 @@ def py_bilinear_resize(x, outputHeight, outputWidth): w1r = 1.0 * w2 * rwidth w1 = int(np.floor(w1r)) w1lambda = w1r - w1 - w1p = 1 if w1 < (inputHeight - 1) else 0 + w1p = 1 if w1 < (inputWidth - 1) else 0 for b in range(batch): for c in range(channel): y[b][c][h2][w2] = (1-h1lambda)*((1-w1lambda)*x[b][c][h1][w1] + \ From 77254f2a829d481d61e015e900923f1edb4af39f Mon Sep 17 00:00:00 2001 From: Yizhi Liu Date: Sun, 21 Jul 2019 20:58:28 -0700 Subject: [PATCH 103/813] Infra to use tvm write op kernels (#15550) * intra to use tvm write op kernels * add cmake support for tvm op * fix header lint * cleanup USE_TVM_OP logic in Makefile * add doc, cmake def, etc. * fix doc * test rand shape * add with_seed to test * improve err msg. add #if --- 3rdparty/dlpack | 2 +- 3rdparty/dmlc-core | 2 +- 3rdparty/tvm | 2 +- CMakeLists.txt | 23 +++++ Makefile | 38 ++++++++ cmake/BuildTVM.cmake | 135 ++++++++++++++++++++++++++ contrib/tvmop/__init__.py | 22 +++++ contrib/tvmop/basic/__init__.py | 19 ++++ contrib/tvmop/basic/ufunc.py | 50 ++++++++++ contrib/tvmop/compile.py | 59 +++++++++++ contrib/tvmop/opdef.py | 111 +++++++++++++++++++++ contrib/tvmop/prepare_tvm.sh | 63 ++++++++++++ contrib/tvmop/utils.py | 20 ++++ include/mxnet/c_api.h | 9 ++ include/mxnet/libinfo.h | 7 ++ make/config.mk | 3 + make/osx.mk | 3 + python/mxnet/base.py | 7 +- python/mxnet/libinfo.py | 10 +- src/c_api/c_api.cc | 9 ++ src/libinfo.cc | 4 + src/operator/contrib/tvmop/ufunc.cc | 66 +++++++++++++ src/operator/tvmop/op_module.cc | 117 ++++++++++++++++++++++ src/operator/tvmop/op_module.h | 63 ++++++++++++ tests/python/gpu/test_operator_gpu.py | 1 + tests/python/unittest/test_tvm_op.py | 38 ++++++++ 26 files changed, 874 insertions(+), 9 deletions(-) create mode 100644 cmake/BuildTVM.cmake create mode 100644 contrib/tvmop/__init__.py create mode 100644 contrib/tvmop/basic/__init__.py create mode 100644 contrib/tvmop/basic/ufunc.py create mode 100644 contrib/tvmop/compile.py create mode 100644 contrib/tvmop/opdef.py create mode 100644 contrib/tvmop/prepare_tvm.sh create mode 100644 contrib/tvmop/utils.py create mode 100644 src/operator/contrib/tvmop/ufunc.cc create mode 100644 src/operator/tvmop/op_module.cc create mode 100644 src/operator/tvmop/op_module.h create mode 100644 tests/python/unittest/test_tvm_op.py diff --git a/3rdparty/dlpack b/3rdparty/dlpack index 10892ac964f1..b90e93907206 160000 --- a/3rdparty/dlpack +++ b/3rdparty/dlpack @@ -1 +1 @@ -Subproject commit 10892ac964f1af7c81aae145cd3fab78bbccd297 +Subproject commit b90e939072066c160b18ea1e7156537b8d3710f6 diff --git a/3rdparty/dmlc-core b/3rdparty/dmlc-core index 3943914eed66..f1ff6cc117f4 160000 --- a/3rdparty/dmlc-core +++ b/3rdparty/dmlc-core @@ -1 +1 @@ -Subproject commit 3943914eed66470bd010df581e29e4dca4f7df6f +Subproject commit f1ff6cc117f4e95169a9f62be549c8fe3e15c20f diff --git a/3rdparty/tvm b/3rdparty/tvm index 21935dcbf56a..afd4b3e44509 160000 --- a/3rdparty/tvm +++ b/3rdparty/tvm @@ -1 +1 @@ -Subproject commit 21935dcbf56ad3bd66ebff9891a6bc3865b8106d +Subproject commit afd4b3e4450984358e9d79a7e8e578483cb7b017 diff --git a/CMakeLists.txt b/CMakeLists.txt index 19a93c731e00..7c479f756295 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,7 @@ mxnet_option(USE_MXNET_LIB_NAMING "Use MXNet library naming conventions." ON) mxnet_option(USE_GPROF "Compile with gprof (profiling) flag" OFF) mxnet_option(USE_CXX14_IF_AVAILABLE "Build with C++14 if the compiler supports it" OFF) mxnet_option(USE_VTUNE "Enable use of Intel Amplifier XE (VTune)" OFF) # one could set VTUNE_ROOT for search path +mxnet_option(USE_TVM_OP "Enable use of TVM operator build system." OFF) mxnet_option(ENABLE_CUDA_RTC "Build with CUDA runtime compilation support" ON) mxnet_option(BUILD_CPP_EXAMPLES "Build cpp examples" ON) mxnet_option(INSTALL_EXAMPLES "Install the example source files." OFF) @@ -733,6 +734,28 @@ if(USE_DIST_KVSTORE) list(APPEND mxnet_LINKER_LIBS ${pslite_LINKER_LIBS}) endif() +if(USE_TVM_OP) + add_definitions(-DMXNET_USE_TVM_OP=1) + list(APPEND mxnet_LINKER_LIBS ${CMAKE_CURRENT_BINARY_DIR}/3rdparty/tvm/libtvm_runtime.so) + include(cmake/BuildTVM.cmake) + add_subdirectory("3rdparty/tvm") + + if(NOT Python3_EXECUTABLE) + find_package(PythonInterp 3 REQUIRED) + set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE} CACHE FILEPATH "Path to the python3 executable") + if(NOT Python3_EXECUTABLE) + message(FATAL_ERROR "No python3 interpreter found to build TVM operators") + endif() + endif() + + add_custom_command(TARGET mxnet POST_BUILD + COMMAND ${CMAKE_COMMAND} -E env + PYTHONPATH="${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/tvm/python:${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/tvm/topi/python:${CMAKE_CURRENT_SOURCE_DIR}/contrib" + LD_LIBRARY_PATH="${CMAKE_CURRENT_BINARY_DIR}/3rdparty/tvm/build" + ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/contrib/tvmop/compile.py -o${CMAKE_CURRENT_BINARY_DIR}/libtvmop.so + ) +endif() + target_link_libraries(mxnet PUBLIC ${mxnet_LINKER_LIBS}) if(USE_PLUGINS_WARPCTC) diff --git a/Makefile b/Makefile index c5ac4ebe01fa..ce840a3d30aa 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,14 @@ ifndef AMALGAMATION_PATH AMALGAMATION_PATH = $(ROOTDIR)/amalgamation endif +ifndef TVM_PATH + TVM_PATH = $(TPARTYDIR)/tvm +endif + +ifndef LLVM_PATH + LLVM_PATH = $(TVM_PATH)/build/llvm +endif + ifneq ($(USE_OPENMP), 1) export NO_OPENMP = 1 endif @@ -589,6 +597,35 @@ $(DMLC_CORE)/libdmlc.a: DMLCCORE DMLCCORE: + cd $(DMLC_CORE); $(MAKE) libdmlc.a USE_SSE=$(USE_SSE) config=$(ROOTDIR)/$(config); cd $(ROOTDIR) +ifeq ($(USE_TVM_OP), 1) +LIB_DEP += lib/libtvm_runtime.so lib/libtvmop.so +CFLAGS += -I$(TVM_PATH)/include -DMXNET_USE_TVM_OP=1 +LDFLAGS += -L$(TVM_PATH)/build -ltvm_runtime + +TVM_USE_CUDA := OFF +ifeq ($(USE_CUDA), 1) + TVM_USE_CUDA := ON + ifneq ($(USE_CUDA_PATH), NONE) + TVM_USE_CUDA := $(USE_CUDA_PATH) + endif +endif +lib/libtvm_runtime.so: + echo "Compile TVM" + [ -e $(LLVM_PATH)/bin/llvm-config ] || sh $(ROOTDIR)/contrib/tvmop/prepare_tvm.sh; \ + cd $(TVM_PATH)/build; \ + cmake -DUSE_LLVM="$(LLVM_PATH)/bin/llvm-config" \ + -DUSE_SORT=OFF -DUSE_CUDA=$(TVM_USE_CUDA) -DUSE_CUDNN=OFF ..; \ + $(MAKE) VERBOSE=1; \ + cp $(TVM_PATH)/build/libtvm_runtime.so $(ROOTDIR)/lib/libtvm_runtime.so; \ + cd $(ROOTDIR) + +lib/libtvmop.so: lib/libtvm_runtime.so $(wildcard contrib/tvmop/*/*.py contrib/tvmop/*.py) + echo "Compile TVM operators" + PYTHONPATH=$(TVM_PATH)/python:$(TVM_PATH)/topi/python:$(ROOTDIR)/contrib:$PYTHONPATH \ + LD_LIBRARY_PATH=lib \ + python3 $(ROOTDIR)/contrib/tvmop/compile.py -o $(ROOTDIR)/lib/libtvmop.so +endif + NNVM_INC = $(wildcard $(NNVM_PATH)/include/*/*.h) NNVM_SRC = $(wildcard $(NNVM_PATH)/src/*/*/*.cc $(NNVM_PATH)/src/*/*.cc $(NNVM_PATH)/src/*.cc) $(NNVM_PATH)/lib/libnnvm.a: $(NNVM_INC) $(NNVM_SRC) @@ -726,6 +763,7 @@ clean: rclean cyclean $(EXTRA_PACKAGES_CLEAN) cd $(DMLC_CORE); $(MAKE) clean; cd - cd $(PS_PATH); $(MAKE) clean; cd - cd $(NNVM_PATH); $(MAKE) clean; cd - + cd $(TVM_PATH); $(MAKE) clean; cd - cd $(AMALGAMATION_PATH); $(MAKE) clean; cd - $(RM) -r $(patsubst %, %/*.d, $(EXTRA_OPERATORS)) $(patsubst %, %/*/*.d, $(EXTRA_OPERATORS)) $(RM) -r $(patsubst %, %/*.o, $(EXTRA_OPERATORS)) $(patsubst %, %/*/*.o, $(EXTRA_OPERATORS)) diff --git a/cmake/BuildTVM.cmake b/cmake/BuildTVM.cmake new file mode 100644 index 000000000000..ad8517cafe26 --- /dev/null +++ b/cmake/BuildTVM.cmake @@ -0,0 +1,135 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +message(STATUS "Prepare external packages for TVM...") +execute_process(COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/contrib/tvmop/prepare_tvm.sh") + +# Whether enable ROCM runtime +# +# Possible values: +# - ON: enable ROCM with cmake's auto search +# - OFF: disable ROCM +# - /path/to/rocm: use specific path to rocm +set(USE_ROCM OFF) + +# Whether enable SDAccel runtime +set(USE_SDACCEL OFF) + +# Whether enable Intel FPGA SDK for OpenCL (AOCL) runtime +set(USE_AOCL OFF) + +# Whether enable OpenCL runtime +set(USE_OPENCL OFF) + +# Whether enable Metal runtime +set(USE_METAL OFF) + +# Whether enable Vulkan runtime +# +# Possible values: +# - ON: enable Vulkan with cmake's auto search +# - OFF: disable vulkan +# - /path/to/vulkan-sdk: use specific path to vulkan-sdk +set(USE_VULKAN OFF) + +# Whether enable OpenGL runtime +set(USE_OPENGL OFF) + +# Whether to enable SGX runtime +# +# Possible values for USE_SGX: +# - /path/to/sgxsdk: path to Intel SGX SDK +# - OFF: disable SGX +# +# SGX_MODE := HW|SIM +set(USE_SGX OFF) +set(SGX_MODE "SIM") +set(RUST_SGX_SDK "/path/to/rust-sgx-sdk") + +# Whether enable RPC runtime +set(USE_RPC ON) + +# Whether embed stackvm into the runtime +set(USE_STACKVM_RUNTIME OFF) + +# Whether enable tiny embedded graph runtime. +set(USE_GRAPH_RUNTIME ON) + +# Whether enable additional graph debug functions +set(USE_GRAPH_RUNTIME_DEBUG OFF) + +# Whether build with LLVM support +# Requires LLVM version >= 4.0 +# +# Possible values: +# - ON: enable llvm with cmake's find search +# - OFF: disable llvm +# - /path/to/llvm-config: enable specific LLVM when multiple llvm-dev is available. +set(USE_LLVM "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/tvm/build/llvm/bin/llvm-config") + +#--------------------------------------------- +# Contrib libraries +#--------------------------------------------- +# Whether use BLAS, choices: openblas, mkl, atlas, apple +set(USE_BLAS none) + +# /path/to/mkl: mkl root path when use mkl blas library +# set(USE_MKL_PATH /opt/intel/mkl) for UNIX +# set(USE_MKL_PATH ../IntelSWTools/compilers_and_libraries_2018/windows/mkl) for WIN32 +set(USE_MKL_PATH none) + +# Whether use contrib.random in runtime +set(USE_RANDOM OFF) + +# Whether use NNPack +set(USE_NNPACK OFF) + +# Whether use CuDNN +if(USE_CUDNN AND USE_CUDA) + detect_cuDNN() + if(HAVE_CUDNN) + set(USE_CUDNN ON) + else() + set(USE_CUDNN OFF) + endif() +else() + set(USE_CUDNN OFF) +endif() + +# Whether use cuBLAS +set(USE_CUBLAS OFF) + +# Whether use MIOpen +set(USE_MIOPEN OFF) + +# Whether use MPS +set(USE_MPS OFF) + +# Whether use rocBlas +set(USE_ROCBLAS OFF) + +# Whether use contrib sort +set(USE_SORT OFF) + +# Build ANTLR parser for Relay text format +set(USE_ANTLR OFF) + +# Build TSIM for VTA +set(USE_VTA_TSIM OFF) + +# Whether use Relay debug mode +set(USE_RELAY_DEBUG OFF) diff --git a/contrib/tvmop/__init__.py b/contrib/tvmop/__init__.py new file mode 100644 index 000000000000..31189d499b5a --- /dev/null +++ b/contrib/tvmop/__init__.py @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 +from .opdef import defop +from .utils import AllTypes, RealTypes + +from . import basic diff --git a/contrib/tvmop/basic/__init__.py b/contrib/tvmop/basic/__init__.py new file mode 100644 index 000000000000..fc0fa720009f --- /dev/null +++ b/contrib/tvmop/basic/__init__.py @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 +from . import ufunc diff --git a/contrib/tvmop/basic/ufunc.py b/contrib/tvmop/basic/ufunc.py new file mode 100644 index 000000000000..0419e5fd2ca9 --- /dev/null +++ b/contrib/tvmop/basic/ufunc.py @@ -0,0 +1,50 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 +import tvm +from .. import defop, AllTypes + +def compute_add(dtype, ndim): + A = tvm.placeholder([tvm.var() for _ in range(ndim)], name='A', dtype=dtype) + B = tvm.placeholder([tvm.var() for _ in range(ndim)], name='B', dtype=dtype) + C = tvm.compute([tvm.var() for _ in range(ndim)], + lambda *index: A[index] + B[index], name='C') + s = tvm.create_schedule(C.op) + return s, A, B, C + +@defop(name="vadd", target="cpu", auto_broadcast=True, + dtype=AllTypes, ndim=list(range(1, 6))) +def vadd(dtype, ndim): + s, A, B, C = compute_add(dtype, ndim) + axes = [axis for axis in C.op.axis] + fused = s[C].fuse(*axes) + s[C].parallel(fused) + + return s, [A, B, C] + +@defop(name="cuda_vadd", target="cuda", auto_broadcast=True, + dtype=["float32", "float64"], ndim=list(range(1, 6))) +def vadd_gpu(dtype, ndim): + s, A, B, C = compute_add(dtype, ndim) + s = tvm.create_schedule(C.op) + axes = [axis for axis in C.op.axis] + fused = s[C].fuse(*axes) + bx, tx = s[C].split(fused, factor=64) + s[C].bind(bx, tvm.thread_axis("blockIdx.x")) + s[C].bind(tx, tvm.thread_axis("threadIdx.x")) + return s, [A, B, C] diff --git a/contrib/tvmop/compile.py b/contrib/tvmop/compile.py new file mode 100644 index 000000000000..94274fe4142a --- /dev/null +++ b/contrib/tvmop/compile.py @@ -0,0 +1,59 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 +"""TVM Operator compile entry point""" +import tvm + +import os +import argparse +from tvmop.opdef import __OP_DEF__ + +def get_target(device): + if device == "cpu": + return "llvm" + elif device == "cuda" or device == "gpu": + return "cuda" + assert False, "Unknown device " + device + + +if __name__ == "__main__": + import sys + sys.path.append(os.path.dirname(sys.path[0])) + parser = argparse.ArgumentParser(description="Generate tvm operators") + parser.add_argument("-o", action="store", required=True, dest="target_path", + help="Target path which stores compiled library") + arguments = parser.parse_args() + + func_list_llvm = [] + func_list_cuda = [] + + # TODO: attach instruction features to the library, e.g., avx-512, etc. + for operator_def in __OP_DEF__: + for sch, args in operator_def.invoke_all(): + if tvm.module.enabled(get_target(operator_def.target)): + func_list = func_list_llvm if operator_def.target == "cpu" else func_list_cuda + func_lower = tvm.lower(sch, args, + name=operator_def.get_op_name(args), + binds=operator_def.get_binds(args)) + func_list.append(func_lower) + + lowered_funcs = {get_target("cpu") : func_list_llvm} + if len(func_list_cuda) > 0: + lowered_funcs[get_target("cuda")] = func_list_cuda + func_binary = tvm.build(lowered_funcs, name="tvmop") + func_binary.export_library(arguments.target_path) diff --git a/contrib/tvmop/opdef.py b/contrib/tvmop/opdef.py new file mode 100644 index 000000000000..c65824588047 --- /dev/null +++ b/contrib/tvmop/opdef.py @@ -0,0 +1,111 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 +import tvm +from itertools import product + +__OP_DEF__ = [] + +class OpDef: + """Specify the properties of an operator and + construct the value combination of the arguments + e.g., ldtype=["float32", "int32"], rdtype=["float16", "int16"], + then the argument combination is + [ + {"ldtype": "float32", "rdtype": "float16"}, + {"ldtype": "float32", "rdtype": "int16"}, + {"ldtype": "int32", "rdtype": "float16"}, + {"ldtype": "int32", "rdtype": "int16"}, + ] + + Parameters + ---------- + func : function + The function to define the operator (in tvm compute and schedule). + It will get the argument combination extracted by this class. + name : str + function name. + target : str + {"cpu", "gpu", "cuda"} + auto_broadcast : bool + auto_broadcast=True allows one to implement broadcast computation + without considering whether dimension size equals to one. + TVM maps buffer[i][j][k] -> buffer[i][0][k] if dimension i's shape equals 1. + """ + def __init__(self, func, name, target, auto_broadcast, **kwargs): + # construct the value combination of the arguments + # e.g., ldtype=["float32", "int32"], rdtype=["float16", "int16"] + # arg_combination = [ + # {"ldtype": "float32", "rdtype": "float16"}, + # {"ldtype": "float32", "rdtype": "int16"}, + # {"ldtype": "int32", "rdtype": "float16"}, + # {"ldtype": "int32", "rdtype": "int16"}, + # ] + args = [k for k in kwargs] + values = [kwargs[k] if isinstance(kwargs[k], (list, tuple)) else [kwargs[k]] + for k in args] + cart_product = product(*values) + self.arg_combination = [{k: v for k, v in zip(args, comb_values)} + for comb_values in cart_product] + self.func = func + self.name = name + self.target = target + self.auto_broadcast = auto_broadcast + + def __call__(self, *args, **kwargs): + return self.func(*args, **kwargs) + + def invoke_all(self): + for each_kwargs in self.arg_combination: + yield self.func(**each_kwargs) + + def get_op_name(self, args): + return self.name + ''.join(["%s_%d" % (arg.dtype, len(arg.shape)) for arg in args]) + + def get_binds(self, args): + if self.auto_broadcast: + return {arg: tvm.decl_buffer(arg.shape, arg.dtype, buffer_type="auto_broadcast") + for arg in args} + return None + + +def defop(name, target=None, auto_broadcast=False, **kwargs): + """Decorator to define a tvm operator. + Parameters + ---------- + name : str + function name + target : str + {"cpu", "gpu", "cuda"} + auto_broadcast : bool + auto_broadcast=True allows one to implement broadcast computation + without considering whether dimension size equals to one. + TVM maps buffer[i][j][k] -> buffer[i][0][k] if dimension i's shape equals 1. + Returns + ------- + fdef : function + A wrapped operator definition function, which returns (schedule, [tensors]) + """ + assert name is not None and len(name) > 0 + target = "cpu" if target is None else target + def _defop(func): + opdef = OpDef(func, name, target, auto_broadcast, **kwargs) + __OP_DEF__.append(opdef) + return opdef + return _defop + diff --git a/contrib/tvmop/prepare_tvm.sh b/contrib/tvmop/prepare_tvm.sh new file mode 100644 index 000000000000..7ebe256446a4 --- /dev/null +++ b/contrib/tvmop/prepare_tvm.sh @@ -0,0 +1,63 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +#!/bin/sh + +LLVM_VERSION="8.0.0" +LLVM_ROOT="http://releases.llvm.org/${LLVM_VERSION}/" +LLVM_PKG="clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu" + +os=`uname` +if [ "$os" = "Linux" ] && [ "$(arch)" = "x86_64" ]; then + DISTRIB_ID=$(cat /etc/*-release | grep DISTRIB_ID | sed 's/DISTRIB_ID=//g' | tr '[:upper:]' '[:lower:]') + DISTRIB_RELEASE=$(cat /etc/*-release | grep DISTRIB_RELEASE | sed 's/DISTRIB_RELEASE=//g' | tr '[:upper:]' '[:lower:]') + if [ "$DISTRIB_ID" = "ubuntu" ]; then + LLVM_PKG=${LLVM_PKG}-${DISTRIB_ID}-${DISTRIB_RELEASE} + else + echo "Downloading LLVM only supports Ubuntu. Please download manually." + exit 1 + fi +else + echo "Cannot identify operating system. Try downloading LLVM manually." + exit 1 +fi + +LLVM_URL=${LLVM_ROOT}${LLVM_PKG}.tar.xz + +TVM_PATH=`dirname $0`/../../3rdparty/tvm +DST=${TVM_PATH}/build +rm -rf $DST +mkdir -p $DST +DST=`cd $DST; pwd` + +if [ -x "$(command -v curl)" ]; then + curl -L -o "${DST}/${LLVM_PKG}.tar.xz" "$LLVM_URL" +elif [ -x "$(command -v wget)" ]; then + wget -O "${DST}/${LLVM_PKG}.tar.xz" "$LLVM_URL" +else + echo "curl or wget not available" + exit 1 +fi + +if [ \! $? ]; then + echo "Download from $LLVM_URL to $DST failed" + exit 1 +fi + +tar -xf "$DST/${LLVM_PKG}.tar.xz" -C $DST +mv $DST/${LLVM_PKG} $DST/llvm +echo "Downloaded and unpacked LLVM libraries to $DST" diff --git a/contrib/tvmop/utils.py b/contrib/tvmop/utils.py new file mode 100644 index 000000000000..0b2416b4f3ae --- /dev/null +++ b/contrib/tvmop/utils.py @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 +AllTypes = ["float32", "float64", "float16", "uint8", "int8", "int32", "int64"] +RealTypes = ["float32", "float64", "float16"] diff --git a/include/mxnet/c_api.h b/include/mxnet/c_api.h index bd30e44f910c..058f859ae7bc 100644 --- a/include/mxnet/c_api.h +++ b/include/mxnet/c_api.h @@ -506,6 +506,15 @@ MXNET_DLL int MXGetGPUMemoryInformation64(int dev, uint64_t *free_mem, uint64_t */ MXNET_DLL int MXGetVersion(int *out); +/*! + * \brief Load TVM operator from the binary library + * \param libpath TVM operators lib file + * \return 0 when success, -1 when failure happens + */ +#if MXNET_USE_TVM_OP +MXNET_DLL int MXLoadTVMOp(const char *libpath); +#endif // MXNET_USE_TVM_OP + //------------------------------------- // Part 1: NDArray creation and deletion diff --git a/include/mxnet/libinfo.h b/include/mxnet/libinfo.h index 8b58a398c673..1972688c7739 100644 --- a/include/mxnet/libinfo.h +++ b/include/mxnet/libinfo.h @@ -127,6 +127,10 @@ #define MXNET_USE_INT64_TENSOR_SIZE MSHADOW_INT64_TENSOR_SIZE #endif +#ifndef MXNET_USE_TVM_OP +#define MXNET_USE_TVM_OP 0 +#endif + namespace mxnet { namespace features { // Check compile flags such as CMakeLists.txt @@ -185,6 +189,9 @@ enum : unsigned { SIGNAL_HANDLER, DEBUG, + // TVM operator + TVM_OP, + // size indicator MAX_FEATURES }; diff --git a/make/config.mk b/make/config.mk index 4bddb8ba461d..982d15b19656 100644 --- a/make/config.mk +++ b/make/config.mk @@ -62,6 +62,9 @@ ADD_LDFLAGS = # the additional compile flags you want to add ADD_CFLAGS = +# whether to build operators written in TVM +USE_TVM_OP = 0 + #--------------------------------------------- # matrix computation libraries for CPU/GPU #--------------------------------------------- diff --git a/make/osx.mk b/make/osx.mk index 0b5842e59524..25f3ba6df55b 100644 --- a/make/osx.mk +++ b/make/osx.mk @@ -53,6 +53,9 @@ ADD_LDFLAGS = # the additional compile flags you want to add ADD_CFLAGS = +# whether to build operators written in TVM +USE_TVM_OP = 0 + #--------------------------------------------- # matrix computation libraries for CPU/GPU #--------------------------------------------- diff --git a/python/mxnet/base.py b/python/mxnet/base.py index 73fae4876873..bf8026359d02 100644 --- a/python/mxnet/base.py +++ b/python/mxnet/base.py @@ -16,7 +16,7 @@ # under the License. # coding: utf-8 -# pylint: disable=invalid-name, no-member, trailing-comma-tuple, bad-mcs-classmethod-argument, unnecessary-pass +# pylint: disable=invalid-name, no-member, trailing-comma-tuple, bad-mcs-classmethod-argument, unnecessary-pass, wrong-import-position """ctypes library of mxnet and helper functions.""" from __future__ import absolute_import @@ -734,3 +734,8 @@ def write_all_str(module_file, module_all_list): ctypes.pythonapi.PyCapsule_New.restype = ctypes.py_object ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p + +from .runtime import Features +if Features().is_enabled("TVM_OP"): + _LIB_TVM_OP = libinfo.find_lib_path("libtvmop") + check_call(_LIB.MXLoadTVMOp(c_str(_LIB_TVM_OP[0]))) diff --git a/python/mxnet/libinfo.py b/python/mxnet/libinfo.py index ff795f914a4b..fb0859b9954a 100644 --- a/python/mxnet/libinfo.py +++ b/python/mxnet/libinfo.py @@ -23,7 +23,7 @@ import logging -def find_lib_path(): +def find_lib_path(prefix='libmxnet'): """Find MXNet dynamic library files. Returns @@ -61,13 +61,13 @@ def find_lib_path(): dll_path[0:0] = [p.strip() for p in os.environ['LD_LIBRARY_PATH'].split(":")] if os.name == 'nt': os.environ['PATH'] = os.path.dirname(__file__) + ';' + os.environ['PATH'] - dll_path = [os.path.join(p, 'libmxnet.dll') for p in dll_path] + dll_path = [os.path.join(p, prefix + '.dll') for p in dll_path] elif platform.system() == 'Darwin': - dll_path = [os.path.join(p, 'libmxnet.dylib') for p in dll_path] + \ - [os.path.join(p, 'libmxnet.so') for p in dll_path] + dll_path = [os.path.join(p, prefix + '.dylib') for p in dll_path] + \ + [os.path.join(p, prefix + '.so') for p in dll_path] else: dll_path.append('../../../') - dll_path = [os.path.join(p, 'libmxnet.so') for p in dll_path] + dll_path = [os.path.join(p, prefix + '.so') for p in dll_path] lib_path = [p for p in dll_path if os.path.exists(p) and os.path.isfile(p)] if len(lib_path) == 0: raise RuntimeError('Cannot find the MXNet library.\n' + diff --git a/src/c_api/c_api.cc b/src/c_api/c_api.cc index 35bd3eeb477a..5207bdfa444b 100644 --- a/src/c_api/c_api.cc +++ b/src/c_api/c_api.cc @@ -48,6 +48,7 @@ #include "./c_api_common.h" #include "../operator/custom/custom-inl.h" #include "../operator/tensor/matrix_op-inl.h" +#include "../operator/tvmop/op_module.h" #include "../common/utils.h" using namespace mxnet; @@ -159,6 +160,14 @@ int MXGetVersion(int *out) { API_END(); } +#if MXNET_USE_TVM_OP +int MXLoadTVMOp(const char *libpath) { + API_BEGIN(); + tvm::runtime::TVMOpModule::Get()->Load(libpath); + API_END(); +} +#endif // MXNET_USE_TVM_OP + int MXNDArrayCreateNone(NDArrayHandle *out) { API_BEGIN(); *out = new NDArray(); diff --git a/src/libinfo.cc b/src/libinfo.cc index f67b45ed1c14..b31d7e4301e0 100644 --- a/src/libinfo.cc +++ b/src/libinfo.cc @@ -89,6 +89,9 @@ class FeatureSet { feature_bits.set(INT64_TENSOR_SIZE, MXNET_USE_INT64_TENSOR_SIZE); feature_bits.set(SIGNAL_HANDLER, MXNET_USE_SIGNAL_HANDLER); + // TVM operators + feature_bits.set(TVM_OP, MXNET_USE_TVM_OP); + #ifndef NDEBUG feature_bits.set(DEBUG); #endif @@ -159,6 +162,7 @@ const std::vector EnumNames::names = { "INT64_TENSOR_SIZE", "SIGNAL_HANDLER", "DEBUG", + "TVM_OP", }; } // namespace features diff --git a/src/operator/contrib/tvmop/ufunc.cc b/src/operator/contrib/tvmop/ufunc.cc new file mode 100644 index 000000000000..faba671322e2 --- /dev/null +++ b/src/operator/contrib/tvmop/ufunc.cc @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file ufunc.cc + * \brief + * \author Yizhi Liu + */ +#ifdef MXNET_USE_TVM_OP +#include +#include +#include +#include +#include "../../tensor/elemwise_binary_broadcast_op.h" +#include "../../tvmop/op_module.h" +#include "../../tensor/elemwise_binary_op.h" + +namespace mxnet { +namespace op { + +static constexpr char func_vadd_cpu[] = "vadd"; +static constexpr char func_vadd_gpu[] = "cuda_vadd"; + +template +void TVMBroadcastCompute(const nnvm::NodeAttrs& attrs, + const mxnet::OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + CHECK_EQ(inputs.size(), 2U); + CHECK_EQ(outputs.size(), 1U); + tvm::runtime::TVMOpModule::Get()->Call(func, ctx, {inputs[0], inputs[1], outputs[0]}); +} + +NNVM_REGISTER_OP(_contrib_tvm_vadd) + .set_num_inputs(2) + .set_num_outputs(1) + .add_argument("a", "NDArray-or-Symbol", "first input") + .add_argument("b", "NDArray-or-Symbol", "second input") + .set_attr("FInferShape", BinaryBroadcastShape) + .set_attr("FInferType", mxnet::op::ElemwiseType<2, 1>) + .set_attr("FCompute", mxnet::op::TVMBroadcastCompute) +#if MXNET_USE_CUDA + .set_attr("FCompute", mxnet::op::TVMBroadcastCompute); +#endif // MXNET_USE_CUDA + +} // namespace op +} // namespace mxnet +#endif // MXNET_USE_TVM_OP diff --git a/src/operator/tvmop/op_module.cc b/src/operator/tvmop/op_module.cc new file mode 100644 index 000000000000..d1d1c1d45d53 --- /dev/null +++ b/src/operator/tvmop/op_module.cc @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file op_module.cc + * \brief Invoke registered TVM operators. + * \author Yizhi Liu + */ +#if MXNET_USE_TVM_OP +#include +#include +#include +#include +#include +#include "op_module.h" + +using namespace tvm::runtime; + +namespace tvm { +namespace runtime { + +void TVMOpModule::Load(const std::string &filepath) { + static const PackedFunc *f_load = Registry::Get("module._LoadFromFile"); + std::lock_guard lock(mutex_); + Module module = (*f_load)(filepath, ""); + module_ptr_ = std::make_shared(); + *module_ptr_ = module; +} + +PackedFunc GetFunction(const std::shared_ptr &module, + const std::string &op_name, + const std::vector &args) { + std::ostringstream func_name; + func_name << op_name; + for (const auto &arg : args) { + switch (arg.type_flag_) { + case mshadow::kFloat32: + func_name << "float32"; + break; + case mshadow::kFloat64: + func_name << "float64"; + break; + case mshadow::kFloat16: + func_name << "float16"; + break; + case mshadow::kUint8: + func_name << "uint8"; + break; + case mshadow::kInt32: + func_name << "int32"; + break; + case mshadow::kInt8: + func_name << "int8"; + break; + case mshadow::kInt64: + func_name << "int64"; + break; + default: + LOG(FATAL) << "Unknown dtype " << arg.type_flag_; + } + func_name << "_" << arg.shape_.ndim(); + } + return module->GetFunction(func_name.str(), false); +} + +void TVMOpModule::Call(const std::string &func_name, + const mxnet::OpContext& ctx, + const std::vector &args) { + std::vector type_codes; + std::vector values; + + type_codes.resize(args.size()); + values.resize(args.size()); + for (size_t i = 0; i < args.size(); ++i) { + type_codes[i] = kArrayHandle; + values[i].v_handle = const_cast(&(args[i].dltensor())); + } + + TVMArgs tvm_args(&values[0], &type_codes[0], args.size()); + TVMRetValue rv; + +#if MXNET_USE_CUDA + int dev_type = (ctx.run_ctx.ctx.dev_type == mxnet::Context::DeviceType::kGPU) ? kDLGPU : kDLCPU; + int dev_id = ctx.run_ctx.ctx.dev_id; + if (dev_type == kDLGPU) { + void *stream = static_cast(ctx.run_ctx.get_stream()->stream_); + TVMSetStream(dev_type, dev_id, stream); + } +#endif + GetFunction(module_ptr_, func_name, args).CallPacked(tvm_args, &rv); +#if MXNET_USE_CUDA + if (dev_type == kDLGPU) { + TVMSetStream(dev_type, dev_id, nullptr); + } +#endif +} + +} // namespace runtime +} // namespace tvm +#endif // MXNET_USE_TVM_OP diff --git a/src/operator/tvmop/op_module.h b/src/operator/tvmop/op_module.h new file mode 100644 index 000000000000..04e97ef51f4e --- /dev/null +++ b/src/operator/tvmop/op_module.h @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file op_module.h + * \brief Invoke registered TVM operators. + * \author Yizhi Liu + */ +#ifndef MXNET_OPERATOR_TVMOP_OP_MODULE_H_ +#define MXNET_OPERATOR_TVMOP_OP_MODULE_H_ + +#if MXNET_USE_TVM_OP +#include +#include +#include +#include +#include + +namespace tvm { +namespace runtime { + +class Module; +class TVMOpModule { + public: + // Load TVM operators binary + void Load(const std::string& filepath); + + void Call(const std::string& func_name, + const mxnet::OpContext& ctx, + const std::vector& args); + + static TVMOpModule *Get() { + static TVMOpModule inst; + return &inst; + } + + private: + std::mutex mutex_; + std::shared_ptr module_ptr_; +}; + +} // namespace runtime +} // namespace tvm + +#endif // MXNET_USE_TVM_OP +#endif // MXNET_OPERATOR_TVMOP_OP_MODULE_H_ diff --git a/tests/python/gpu/test_operator_gpu.py b/tests/python/gpu/test_operator_gpu.py index f9814abc9c0b..91ba9fb68750 100644 --- a/tests/python/gpu/test_operator_gpu.py +++ b/tests/python/gpu/test_operator_gpu.py @@ -44,6 +44,7 @@ from test_ndarray import * from test_subgraph_op import * from test_contrib_operator import test_multibox_target_op +from test_tvm_op import * set_default_context(mx.gpu(0)) del test_support_vector_machine_l1_svm # noqa diff --git a/tests/python/unittest/test_tvm_op.py b/tests/python/unittest/test_tvm_op.py new file mode 100644 index 000000000000..3ab2a25bc20e --- /dev/null +++ b/tests/python/unittest/test_tvm_op.py @@ -0,0 +1,38 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import mxnet as mx +from mxnet.test_utils import same, rand_shape_nd +from mxnet.runtime import Features +from common import with_seed + +_features = Features() + +@with_seed() +def test_tvm_broadcast_add(): + if _features.is_enabled("TVM_OP"): + a_shape = rand_shape_nd(4) + b_shape = (1,) + a_shape[1:2] + (1, 1) + a = mx.nd.normal(shape=a_shape) + b = mx.nd.normal(shape=b_shape) + c = mx.nd.contrib.tvm_vadd(a, b) + c_np = a.asnumpy() + b.asnumpy() + assert same(c.asnumpy(), c_np) + +if __name__ == '__main__': + import nose + nose.runmodule() From 858e17115e5240553f5e7fe932643c3850433b72 Mon Sep 17 00:00:00 2001 From: Lai Wei Date: Tue, 23 Jul 2019 09:05:31 -0700 Subject: [PATCH 104/813] 1.5.0 news (#15137) * 1.5.0 news * update news * add news * update news * trigger ci: * fix * address comments * Update NEWS.md Co-Authored-By: Aaron Markham * Update NEWS.md Co-Authored-By: Aaron Markham * Update NEWS.md Co-Authored-By: Aaron Markham * Update NEWS.md Co-Authored-By: Aaron Markham * Update NEWS.md Co-Authored-By: Aaron Markham --- NEWS.md | 1031 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1031 insertions(+) diff --git a/NEWS.md b/NEWS.md index ee8a73cc4bc8..8d8ef2c4ffd5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,6 +17,1037 @@ MXNet Change Log ================ +- [MXNet Change Log](#mxnet-change-log) + * [1.5.0](#150) + + [New Features](#new-features) + - [Automatic Mixed Precision(experimental)](#automatic-mixed-precision-experimental-) + - [MKL-DNN Reduced precision inference and RNN API support](#mkl-dnn-reduced-precision-inference-and-rnn-api-support) + - [Dynamic Shape(experimental)](#dynamic-shape-experimental-) + - [Large Tensor Support](#large-tensor-support) + - [Dependency Update](#dependency-update) + - [Gluon Fit API(experimental)](#gluon-fit-api-experimental-) + - [New Operators](#new-operators) + + [Feature Improvements](#feature-improvements) + - [Operators](#operators) + - [MKLDNN](#mkldnn) + - [ONNX](#onnx) + - [TensorRT](#tensorrt) + - [FP16 Support](#fp16-support) + - [Deep Graph Library(DGL) support](#deep-graph-library-dgl--support) + - [Horovod Integration](#horovod-integration) + - [Dynamic Shape](#dynamic-shape) + - [Backend Engine](#backend-engine) + - [Large Tensor Support](#large-tensor-support-1) + - [Quantization](#quantization) + - [Profiler](#profiler) + - [CoreML](#coreml) + + [Front End API](#front-end-api) + - [Gluon](#gluon) + - [Python](#python) + + [Language Bindings](#language-bindings) + - [Scala](#scala) + - [Java](#java) + - [C++](#c--) + - [Clojure](#clojure) + - [Julia](#julia) + - [Perl:](#perl-) + - [R](#r) + + [Performance Improvements](#performance-improvements) + + [Example and Tutorials](#example-and-tutorials) + + [Website](#website) + + [Documentation](#documentation) + + [Build and Test](#build-and-test) + + [Bug-fixes](#bug-fixes) + + [License](#license) + + [Depreciations](#depreciations) + + [Known Issues](#known-issues) + * [1.4.1](#141) + + [Bug-fixes](#bug-fixes-1) + * [1.4.0](#140) + + [New Features](#new-features-1) + - [Java Inference API](#java-inference-api) + - [Julia API](#julia-api) + - [Control Flow Operators (experimental)](#control-flow-operators--experimental-) + - [SVRG Optimization](#svrg-optimization) + - [Subgraph API (experimental)](#subgraph-api--experimental-) + - [JVM Memory Management](#jvm-memory-management) + - [Topology-aware AllReduce (experimental)](#topology-aware-allreduce--experimental-) + - [MKLDNN backend: Graph optimization and Quantization (experimental)](#mkldnn-backend--graph-optimization-and-quantization--experimental-) + * [Graph Optimization](#graph-optimization) + * [Quantization](#quantization-1) + + [New Operators](#new-operators-1) + + [Feature improvements](#feature-improvements) + - [Operator](#operator) + - [Optimizer](#optimizer) + - [Sparse](#sparse) + - [ONNX](#onnx-1) + - [MKLDNN](#mkldnn-1) + - [Inference](#inference) + - [Other](#other) + + [Frontend API updates](#frontend-api-updates) + - [Gluon](#gluon-1) + - [Symbol](#symbol) + + [Language API updates](#language-api-updates) + - [Java](#java-1) + - [R](#r-1) + - [Scala](#scala-1) + - [Clojure](#clojure-1) + - [Perl](#perl) + - [Julia](#julia-1) + + [Performance benchmarks and improvements](#performance-benchmarks-and-improvements) + + [Bug fixes](#bug-fixes) + + [Licensing updates](#licensing-updates) + + [Improvements](#improvements) + - [Tutorial](#tutorial) + - [Example](#example) + - [Documentation](#documentation-1) + - [Website](#website-1) + - [MXNet Distributions](#mxnet-distributions) + - [Installation](#installation) + - [Build and CI](#build-and-ci) + - [3rd party](#3rd-party) + * [TVM:](#tvm-) + * [CUDNN:](#cudnn-) + * [Horovod:](#horovod-) + + [Deprications](#deprications) + + [Other](#other-1) + + [How to build MXNet](#how-to-build-mxnet) + + [List of submodules used by Apache MXNet (Incubating) and when they were updated last](#list-of-submodules-used-by-apache-mxnet--incubating--and-when-they-were-updated-last) + * [1.3.1](#131) + + [Bug fixes](#bug-fixes-1) + + [Documentation fixes](#documentation-fixes) + + [Other Improvements](#other-improvements) + + [Submodule updates](#submodule-updates) + + [Known issues](#known-issues) + * [1.3.0](#130) + + [New Features - Gluon RNN layers are now HybridBlocks](#new-features---gluon-rnn-layers-are-now-hybridblocks) + + [MKL-DNN improvements](#mkl-dnn-improvements) + + [New Features - Gluon Model Zoo Pre-trained Models](#new-features---gluon-model-zoo-pre-trained-models) + + [New Features - Clojure package (experimental)](#new-features---clojure-package--experimental-) + + [New Features - Synchronized Cross-GPU Batch Norm (experimental)](#new-features---synchronized-cross-gpu-batch-norm--experimental-) + + [New Features - Sparse Tensor Support for Gluon (experimental)](#new-features---sparse-tensor-support-for-gluon--experimental-) + + [New Features - Control flow operators (experimental)](#new-features---control-flow-operators--experimental-) + + [New Features - Scala API Improvements (experimental)](#new-features---scala-api-improvements--experimental-) + + [New Features - Rounding GPU Memory Pool for dynamic networks with variable-length inputs and outputs (experimental)](#new-features---rounding-gpu-memory-pool-for-dynamic-networks-with-variable-length-inputs-and-outputs--experimental-) + + [New Features - Topology-aware AllReduce (experimental)](#new-features---topology-aware-allreduce--experimental-) + + [New Features - Export MXNet models to ONNX format (experimental)](#new-features---export-mxnet-models-to-onnx-format--experimental-) + + [New Features - TensorRT Runtime Integration (experimental)](#new-features---tensorrt-runtime-integration--experimental-) + + [New Examples - Scala](#new-examples---scala) + + [Maintenance - Flaky Tests improvement effort](#maintenance---flaky-tests-improvement-effort) + + [Maintenance - MXNet Model Backwards Compatibility Checker](#maintenance---mxnet-model-backwards-compatibility-checker) + + [Maintenance - Integrated testing for "the Straight Dope"](#maintenance---integrated-testing-for--the-straight-dope-) + + [Bug-fixes](#bug-fixes-2) + + [Performance Improvements](#performance-improvements-1) + + [API Changes](#api-changes) + + [Other features](#other-features) + + [Usability Improvements](#usability-improvements) + * [1.2.0](#120) + + [New Features - Added Scala Inference APIs](#new-features---added-scala-inference-apis) + + [New Features - Added a Module to Import ONNX models into MXNet](#new-features---added-a-module-to-import-onnx-models-into-mxnet) + + [New Features - Added Support for Model Quantization with Calibration](#new-features---added-support-for-model-quantization-with-calibration) + + [New Features - MKL-DNN Integration](#new-features---mkl-dnn-integration) + + [New Features - Added Exception Handling Support for Operators](#new-features---added-exception-handling-support-for-operators) + + [New Features - Enhanced FP16 support](#new-features---enhanced-fp16-support) + + [New Features - Added Profiling Enhancements](#new-features---added-profiling-enhancements) + + [Breaking Changes](#breaking-changes) + + [Bug Fixes](#bug-fixes) + + [Performance Improvements](#performance-improvements-2) + + [API Changes](#api-changes-1) + + [Sparse Support](#sparse-support) + + [Deprecations](#deprecations) + + [Other Features](#other-features) + + [Usability Improvements](#usability-improvements-1) + + [Known Issues](#known-issues-1) + * [1.1.0](#110) + + [Usability Improvements](#usability-improvements-2) + + [Bug-fixes](#bug-fixes-3) + + [New Features](#new-features-2) + + [API Changes](#api-changes-2) + + [Deprecations](#deprecations-1) + + [Performance Improvements](#performance-improvements-3) + + [Known Issues](#known-issues-2) + * [1.0.0](#100) + + [Performance](#performance) + + [New Features - Gradient Compression [Experimental]](#new-features---gradient-compression--experimental-) + + [New Features - Support of NVIDIA Collective Communication Library (NCCL) [Experimental]](#new-features---support-of-nvidia-collective-communication-library--nccl---experimental-) + + [New Features - Advanced Indexing [General Availability]](#new-features---advanced-indexing--general-availability-) + + [New Features - Gluon [General Availability]](#new-features---gluon--general-availability-) + + [New Features - ARM / Raspberry Pi support [Experimental]](#new-features---arm---raspberry-pi-support--experimental-) + + [New Features - NVIDIA Jetson support [Experimental]](#new-features---nvidia-jetson-support--experimental-) + + [New Features - Sparse Tensor Support [General Availability]](#new-features---sparse-tensor-support--general-availability-) + + [Bug-fixes](#bug-fixes-4) + + [Doc Updates](#doc-updates) + * [0.12.1](#0121) + + [Bug-fixes](#bug-fixes-5) + * [0.12.0](#0120) + + [Performance](#performance-1) + + [New Features - Gluon](#new-features---gluon) + + [New Features - Autograd](#new-features---autograd) + + [New Features - Sparse Tensor Support](#new-features---sparse-tensor-support) + + [Other New Features](#other-new-features) + + [API Changes](#api-changes-3) + + [Bug-fixes](#bug-fixes-6) + * [0.11.0](#0110) + + [Major Features](#major-features) + + [API Changes](#api-changes-4) + + [Performance Improvements](#performance-improvements-4) + + [Bugfixes](#bugfixes) + + [Refactors](#refactors) + * [0.10.0](#0100) + * [0.9.3](#093) + * [v0.8](#v08) + * [v0.7](#v07) + * [v0.5 (initial release)](#v05--initial-release-) + +## 1.5.0 + +### New Features + +#### Automatic Mixed Precision(experimental) +Training Deep Learning networks is a very computationally intensive task. Novel model architectures tend to have increasing numbers of layers and parameters, which slow down training. Fortunately, software optimizations and new generations of training hardware make it a feasible task. +However, most of the hardware and software optimization opportunities exist in exploiting lower precision (e.g. FP16) to, for example, utilize Tensor Cores available on new Volta and Turing GPUs. While training in FP16 showed great success in image classification tasks, other more complicated neural networks typically stayed in FP32 due to difficulties in applying the FP16 training guidelines. +That is where AMP (Automatic Mixed Precision) comes into play. It automatically applies the guidelines of FP16 training, using FP16 precision where it provides the most benefit, while conservatively keeping in full FP32 precision operations unsafe to do in FP16. To learn more about AMP, check out this [tutorial](https://github.com/apache/incubator-mxnet/blob/master/docs/tutorials/amp/amp_tutorial.md). + +#### MKL-DNN Reduced precision inference and RNN API support +Two advanced features, fused computation and reduced-precision kernels, are introduced by MKL-DNN in the recent version. These features can significantly speed up the inference performance on CPU for a broad range of deep learning topologies. MXNet MKL-DNN backend provides optimized implementations for various operators covering a broad range of applications including image classification, object detection, and natural language processing. Refer to the [MKL-DNN operator documentation](https://github.com/apache/incubator-mxnet/blob/v1.5.x/docs/tutorials/mkldnn/operator_list.md) for more information. + +#### Dynamic Shape(experimental) +MXNet now supports Dynamic Shape in both imperative and symbolic mode. MXNet used to require that operators statically infer the output shapes from the input shapes. However, there exist some operators that don't meet this requirement. Examples are: +* while_loop: its output size depends on the number of iterations in the loop. +* boolean indexing: its output size depends on the value of the input data. +* many operators can be extended to take a shape symbol as input and the shape symbol can determine the output shape of these operators (with this extension, the symbol interface of MXNet can fully support shape). +To support dynamic shape and such operators, we have modified MXNet backend. Now MXNet supports operators with dynamic shape such as [`contrib.while_loop`](https://mxnet.incubator.apache.org/api/python/ndarray/contrib.html#mxnet.ndarray.contrib.while_loop), [`contrib.cond`](https://mxnet.incubator.apache.org/api/python/ndarray/contrib.html#mxnet.ndarray.contrib.cond), and [`mxnet.ndarray.contrib.boolean_mask`](https://mxnet.incubator.apache.org/api/python/ndarray/contrib.html#contrib) +Note: Currently dynamic shape does not work with Gluon deferred initialization. + +#### Large Tensor Support +Currently, MXNet supports maximal tensor size of around 4 billon (2^32). This is due to uint32_t being used as the default data type for tensor size, as well as variable indexing. +This limitation has created many problems when larger tensors are used in the model. +A naive solution to this problem is to replace all uint32_t in the MXNet backend source code to int64_t. +This solution is not viable, however, because many data structures use uint32_t as the data type for its members. +Unnecessarily replacing these variables to int64_t will increase the memory consumption causing another limitation. Second, MXNet has many submodule dependencies. +Updating the variable types in the MXNet repository is not enough. We also need to make sure different libraries, such as MKLDNN, MShadow etc. supports the int64_t integer data type. +Third, many front end APIs assume unsigned 32-bit integer interface. Only updating the interface in C/C++ will cause all the language bindings to fail. +Therefore, we need a systematic approach to enhance MXNet to support large tensors. +Now you can enable large tensor support by changing the following build flag to 1: `USE_INT64_TENSOR_SIZE = 1`. Note this is set to 0 by default. +For more details please refer to the [design document](https://cwiki.apache.org/confluence/display/MXNET/Large+Tensor+Support). + +#### Dependency Update +MXNet has added support for CUDA 10, CUDA 10.1, cudnn7.5, NCCL 2.4.2, and numpy 1.16.0. +These updates are available through PyPI packages and build from source, refer to [installation guid](https://mxnet.incubator.apache.org/versions/master/install/index.html) for more details. + +#### Gluon Fit API(experimental) +Training a model in Gluon requires users to write the training loop. This is useful because of its imperative nature, however repeating the same code across multiple models can become tedious and repetitive with boilerplate code. +The training loop can also be overwhelming to some users new to deep learning. We have introduced an Estimator and Fit API to help facilitate training loop. +Note: this feature is still experimental, for more details, refer to [design document](https://cwiki.apache.org/confluence/display/MXNET/Gluon+Fit+API+-+Tech+Design). + +#### New Operators +* split_v2 (#13687) +* Gradient multiplier (contrib) operator (#13632) +* Image normalize operator - GPU support, 3D/4D inputs (#13802) +* Image ToTensor operator - GPU support, 3D/4D inputs (#13837) +* Add Gluon Transformer Crop (#14259) +* GELU (#14449) +* AdamW operator (Fixing Weight Decay Regularization in Adam) (#13728) +* [MXNET-1382] Add the index_array operator (#14638) +* add an operator for computing the likelihood of a Hawkes self-exciting process (#14683) +* Add numpy linspace (#14927) + + +### Feature Improvements + +#### Operators +* make ROIAlign support position-sensitive pooling (#13088) +* Add erfinv operator for calculating inverse error function (#13811) +* Added optional parameters to BilinearResize2D to do relative scaling (#13985) +* MXNET-1295 Adding integer index support to Sequence* family of operators. (#13880) +* Export resize and support batch size (#14014) +* CUDNN dropout (#13896) +* Relaxing type requirements for slice_like op (#14097) +* Relaxing type requirements for reshape_like op (#14325) +* Parallelize CPU version and add GPU version of boolean_mask op (#14090) +* Add NHWC layout support to Pooling (cpu, gpu cuda, gpu cuDNN) (#13749) +* Multi-precision AdamW update op (#14171) +* [op] add back support for scalar type rescale_grad argument for adamw_update/mp_adamw_update (#14221) +* move choose_element_0index to operator (#14273) +* Optimize NMS (#14290) +* Optimize NMS part 2 (#14352) +* add background class in box_nms (#14058) +* Use cudnn for dropout by default (#14278) +* In-place updates for Nadam, Adadelta, Adamax and SGLD (#13960) +* Aggregate SGD (#13346) +* Add proper exception message for negative shape in array creation routines (#14362) +* Support multi-threading for Custom Operator (#14363) +* moveaxis operator now accepts negative indices and sequence of ints as well. (#14321) +* Support SyncBatchNorm5D (#14542) +* Add nd.power and sym.pow (#14606) +* Change RNN OP to stateful (#14476) +* Add imresize and copyMakeBorder to mx.image (#13357) +* add ctx for rand_ndarray and rand_sparse_ndarray (#14966) +* Add cpu implementation for Deformable PSROIPooling (#14886) +* Add warning for fp16 inputs with MXNET_SAFE_ACCUMULATION=0 (#15046) +* Safe LayerNorm (#15002) +* use MXNET_SAFE_ACCUMULATION for softmax accumulator (#15037) +* LayerNorm acceleration on GPU (#14935) +* Add matrix inversion operator in linalg (#14963) +* implementation for equivalence of tf.moments (#14842) +* Use env var to enforce safe accumulation in ReduceAxesCompute (#14830) +* [MXNet-1211] Factor and "Like" modes in BilinearResize2D operator (#13226) +* added extraction/generation of diagonal and triangonal matrices to linalg (#14501) +* [Mxnet-1397] Support symbolic api for requantize and dequantize (#14749) +* [MXNET-978] Support higher order gradient for `log`. (#14992) +* Add cpu implementation for Deformable Convolution (#14879) + +#### MKLDNN +* Feature/mkldnn static (#13628) +* Feature/mkldnn static 2 (#13503) +* support mkl log when dtype is fp32 or fp64 (#13150) +* Add reshape op supported by MKL-DNN (#12980) +* Move the debug output message into MXNET_MKLDNN_DEBUG (#13662) +* Integrate MKLDNN Conv1d and support 3d layout (#13530) +* Making MKL-DNN default on MXNet master (#13681) +* Add mkldnn OP for slice (#13730) +* mkldnn s8 conv API change for master (#13903) +* [MKLDNN] Enable signed int8 support for convolution. (#13697) +* add mkldnn softmax_output (#13699) +* MKLDNN based Quantized FullyConnected Operator and its fusion (#14128) +* Fix entropy for uint8 (#14150) +* Update MKL-DNN to v0.18 release (was: fix the Dense layer issue) (#13668) +* [MKL-DNN] Enable s8 support for inner product and 3d input with flatten=false (#14466) +* Optimize transpose operator with MKL-DNN (#14545) +* [MKLDNN] Remove repeat parts in MKLDNN.md (#14995) +* [MKLDNN] Enable more convolution + activation fusion (#14819) +* Update MKL-DNN submodule to v0.19 (#14783) +* Add mkldnn_version.h to pip package (#14899) +* [MKLDNN] add quantized sum (#14614) +* [MKLDNN]Refactor requantize to speed up execution (#14608) +* [MKLDNN]Add quantized relu (#14604) +* Add MKLDNN headers to pip package (#14339) +* add symbolic link to mkldnn header files in include (#14300) +* disable default MKLDNN for cross compilation (#13893) +* Update MKLDNN_README.md (#13653) +* [Quantization] Support zero-size tensor input for quantization flow (#15031) +* Support 3D input for MKL-DNN softmax operator (#14818) +* Add primitive cache for MKL-DNN sum(elemwise_add operator (#14914) +* Fix reshape to add in-place back (#14903) +* [int8] Add MobileNetV2_1.0 & ResNet18 Quantization (#14823) +* [MKLDNN]Improve quantizeV2 and dequantize latency (#14641) +* added mkldnn dependency for plugin compile target (#14274) +* Support Quantized Fully Connected by INT8 GEMM (#12922) + +#### ONNX +* ONNX export: Instance normalization, Shape (#12920) +* ONNX export: Logical operators (#12852) +* ONNX import/export: Size (#13112) +* ONNX export: Add Flatten before Gemm (#13356) +* ONNX import/export: Add missing tests, ONNX export: LogSoftMax (#13654) +* ONNX import: Hardmax (#13717) +* [MXNET-898] ONNX import/export: Sample_multinomial, ONNX export: GlobalLpPool, LpPool (#13500) +* ONNX ops: norm exported and lpnormalization imported (#13806) +* [MXNET-880] ONNX export: Random uniform, Random normal, MaxRoiPool (#13676) +* ONNX export: Add Crop, Deconvolution and fix the default stride of Pooling to 1 (#12399) +* onnx export ops (#13821) +* ONNX export: broadcast_to, tile ops (#13981) +* ONNX export: Support equal length splits (#14121) + +#### TensorRT +* [MXNET-1252][1 of 2] Decouple NNVM to ONNX from NNVM to TenosrRT conversion (#13659) +* [MXNET-703] Update to TensorRT 5, ONNX IR 3. Fix inference bugs. (#13310) +* [MXNET-703] Minor refactor of TensorRT code (#13311) +* reformat trt to use subgraph API, add fp16 support (#14040) + +#### FP16 Support +* Update mshadow to support batch_dot with fp16. (#13716) +* float32 → float16 cast consistency across implementations (#13857) +* modifying SyncBN doc for FP16 use case (#14041) +* support dot(vector, vector) for fp16 inputs on GPU (#14102) +* softmax for fp16 with fp32 accumulator (#14098) +* [MXNET-1327] Allow RNN Layers to be initialized to fp16 (#14219) +* fp16 safe norm operator (#14616) +* NAG Optimizer with multi-precision support (#14568) + +#### Deep Graph Library(DGL) support +* Add graph_compact operator. (#13436) +* Accelerate DGL csr neighbor sampling (#13588) + +#### Horovod Integration +* Add extra header file to export for error checking (#13795) +* whitelist symbols for using MXNet error handling externally (#13812) +* Use CPUPinned context in ImageRecordIOParser2 (#13980) +* Add pin_device_id option to Gluon DataLoader (#14136) + +#### Dynamic Shape +* [MXNET-1315] Add checks for dynamic-shaped operators in CachedOp (#14018) +* [MXNET-1325] Make InferShapeAttr a standalone pass (#14193) +* [MXNET-1324] Add NaiveRunGraph to imperative utils (#14192) +* [MXNET-1352] Allow dynamic shape in while_loop and if conditionals (#14393) + +#### Backend Engine +* Add infer_type_partial (#14214) +* Tidy up storage allocation and deallocation (#14480) +* Add MXEnginePushAsync and MXEnginePushSync C APIs (#14615) +* Enhance subgraph API (#14113) +* Enhance PartitionGraph (#14277) +* Allow clearing gpu cache (#14252) +* Fix warning / static function in header. (#14900) +* Simplify creation of NodeEntry instances and use emplace_back (#14095) +* Add unpooled gpu memory type (#14716) +* [MXNET-1398] Enable zero-copy from numpy to MXNet NDArray (#14733) +* Use DEFAULT macro in C APIs (#14767) +* Avoid unnecessary vector copies in imperative_utils.cc (#14665) +* Support populating errors back to MXNet engine in callback (#13922) +* Restore save/load ndarray to 1.4.1 (#15073) +* Enable serializing/deserializing ndarrays in np_shape semantics (#15090) +* [numpy] Support zero-dim and zero-size tensors in MXNet (#14661) +* Rename np_compat to np_shape (#15063) +* [MXNET-1330] Bring nnvm::Tuple to mxnet::Tuple (#14270) + +#### Large Tensor Support +* Large array support for randint (#14242) +* [MXNET-1185] Support large array in several operators (part 1) (#13418) +* [MXNET-1401] adding more operators to test support for Large Tensor (#14944) +* [MXNET-1410]Adding Large Tensor Support for tensor transpose (#15059) + +#### Quantization +* Exclude concat layer for gpu quantization (#14060) +* Enhance gpu quantization (#14094) +* Register fake grad to subgraph and quantized operators (#14275) +* Add int8 data loader (#14123) + +#### Profiler +* [MXNET-857] Add initial NVTX profiler implementation (#12328) + +#### CoreML +* Add more support for mxnet_to_coreml (#14222) + + +### Front End API + +#### Gluon +* Add pixelshuffle layers (#13571) +* [MXNET-766] add dynamic_unroll RNN for HybridBlock (#11948) +* add pos_weight for SigmoidBinaryCrossEntropyLoss (#13612) +* Rewrite dataloader with process pool, improves responsiveness and reliability (#13447) +* Complimentary gluon DataLoader improvements (#13606) +* [Fit-API] Adress PR comments (#14885) +* [Fit API] update estimator (#14849) +* [MXNET-1396][Fit-API] Update default handler logic (#14765) +* [Fit API] improve event handlers (#14685) +* move to gluon contrib (#14635) +* move estimator to contrib (#14633) +* [MXNet-1340][Fit API]Update train stats (#14494) +* [MXNet-1334][Fit API]base class for estimator and eventhandler (#14346) +* [MXNET-1333] Estimator and Fit API (#14629) +* Add support for fast variable-length LSTM (#14208) +* Add the Gluon Implementation of Deformable Convolution (#14810) +* hybridize rnn and add model graph (#13244) + +#### Python +* Python BucketingModule bind() with grad_req = 'add' (#13984) +* Refine runtime feature discovery python API and add documentation to … (#14130) +* Runtime feature detection (#13549) +* Add dtype visualization to plot_network (#14066) +* [MXNET-1359] Adds a multiclass-MCC metric derived from Pearson (#14461) +* support long for mx.random.seed (#14314) +* Optimization of metric evaluation (#13471) +* [MXNET-1403] Disable numpy's writability of NDArray once it is zero-copied to MXNet (#14948) +* Refactor ImageRecordIter (#14824) + + +### Language Bindings + +#### Scala +* [MXNET-1260] Float64 DType computation support in Scala/Java (#13678) +* [MXNET-1000] get Ndarray real value and form it from a NDArray (#12690) +* Now passing DType of Label downstream to Label's DataDesc object (#14038) +* Scala interpreter instructions (#14169) +* Add default parameters for Scala NDArray.arange (#13816) +* [MXNET-1287] Up scala comp (#14667) +* [MXNET-1385] Improved Scala Init and Macros warning messages (#14656) +* Remove all usages of makefile for scala (#14013) +* Update scala-package gitignore configuration. (#13962) +* [MXNET-1177]Adding Scala Demo to be run as a part of Nightly CI (#13823) +* [MXNET-1287] Miscellaneous Scala warning fixes (#14658) +* Fix jar path and add missing ones for spark jobs (#14020) +* [MXNET-1155] Add scala packageTest utility (#13046) +* [MXNET-1195] Cleanup Scala README file (#13582) +* Add scalaclean to make clean (#14322) +* Add maven wraper to scala project. (#13702) +* Add new Maven build for Scala package (#13819) +* [MXNET-1287] Feat dep (#14668) +* add Apache header on all XML (#14138) +* update the version name (#14076) +* change to compile time (#13835) +* [MXNET-918] Random module (#13039) +* Avoid secondary deployment of package to local (#14647) + +#### Java +* [MXNET-1180] Java Image API (#13807) +* [MXNET-1285] Draw bounding box with Scala/Java Image API (#14474) +* Add BERT QA Scala/Java example (#14592) +* [MXNET-1232] fix demo and add Eclipse support (#13979) +* [MXNET-1331] Removal of non-MXNET classes from JAR (#14303) +* Java install info update (#13912) +* [MXNET-1226] add Docs update for MXNet Java (#14395) +* [MXNET-1383] Java new use of ParamObject (#14645) +* MXNET-1302 Exclude commons-codec and commons-io from assembled JAR (#14000) + +#### C++ +* print error message for mxnet::cpp::Operator::Invoke when failed (#14318) +* build docs with CPP package (#13983) +* Update inception_inference.cpp (#14674) +* Optimize C++ API (#13496) + +#### Clojure +* [Clojure] - Add Spec Validations to the Optimizer namespace (#13499) +* [Clojure] Add Spec Validations for the Random namespace (#13523) +* [Clojure] Correct the versions in the README so they correspond to the latest maven.org release ([#13507) +* Port of scala infer package to clojure (#13595) +* Clojure example for fixed label-width captcha recognition (#13769) +* Update project.clj file to use the snapshots repo to be able to pull (#13935) +* [Clojure] Add resource scope to clojure package (#13993) +* [clojure-package] improve docstrings in image.clj (#14307) +* [Clojure] Helper function for n-dim vector to ndarray (#14305) +* [clojure]: add comp-metric based on CompositeEvalMetric (#14553) +* [Clojure] enhance draw bounding box (#14567) +* [Clojure] Add methods based on NDArrayAPI/SymbolAPI (#14195) +* [Clojure] Clojure BERT QA example (#14691) +* [clojure-package][wip] add ->nd-vec function in ndarray.clj (#14308) +* [Clojure] Correct the versions in the README so they correspond to the latest maven.org release (#13507) +* Update version to v1.5.0 including clojure package (#13566) +* [clojure][generator] ndarray/symbol api random merged (#14800) +* upgrade codox to work with lein 2.9.0 (#14133) +* [clojure] fix: image test does not rely on s3 to run (#15122) + +#### Julia +* Julia v0.7/1.0 support and drop v0.6 support (#12845) +* Julia: split ndarray.jl into several snippets (#14001) +* Julia: split symbolic-node.jl into several snippets (#14024) +* Julia: rename mx.clip to clamp for NDArray (#14027) +* Julia: add binding for runtime feature detection (#13992) + +#### Perl: +* Two more gluon loss classes. (#14194) + +#### R +* add NAG optimizer to r api (#14023) +* R-Package Makefile (#14068) + + +### Performance Improvements + +* Less cudaGet/SetDevice calls in Gluon execution (#13764) +* Improve bulking in Gluon (#13890) +* Increase perfomance of BulkAppend and BulkFlush (#14067) +* Performance improvement in ToTensor GPU Kernel (#14099) +* Performance improvement in Normalize GPU Kernel (#14139) +* Bulked op segments to allow Variable nodes (#14200) +* Performance improving for MKL-DNN Quantized FullyConnected (#14528) +* speedup SequenceMask on GPU (#14445) +* Dual stream cudnn Convolution backward() with MXNET_GPU_WORKER_NSTREAMS=2. (#14006) +* Speedup `_contrib_index_copy` (#14359) +* use mkl sparse matrix to improve performance (#14492) +* Re-enable static cached_op optimization (#14931) +* Speed up SequenceReverse (#14627) +* Improve FC perf when no_bias=False (#15033) +* Improve cached_op performance for static mode (#14785) + + +### Example and Tutorials + +* [MXNET-949] Module API to Gluon API tutorial (#12542) +* Support SSD f32/int8 evaluation on COCO dataset (#14646) +* [MXNET-1209] Tutorial transpose reshape (#13208) +* [Clojure] Add Fine Tuning Sentence Pair Classification BERT Example (#14769) +* example/ssd/evaluate/eval_metric.py (#14561) +* Add examples of running MXNet with Horovod (#14286) +* Added link to landing page for Java examples (#14481) +* Update lip reading example (#13647) +* [MXNET-1121] Example to demonstrate the inference workflow using RNN (#13680) +* [MXNET-1301] Remove the unnecessary WaitAll statements from inception_inference example (#13972) +* Modifying clojure CNN text classification example (#13865) +* [MXNET-1210 ] Gluon Audio - Example (#13325) +* add examples and fix the dependency problem (#13620) +* add quantization example to readme (#14186) +* Add an inference script providing both accuracy and benchmark result for original wide_n_deep example (#13895) +* Update autoencoder example (#12933) +* #13813 examples with opencv4/origami (#13813) +* [MXNET-1083] Add the example to demonstrate the inference workflow using C++ API (#13294) +* Add tutorial on how to use build from source jar (#14197) +* Gluon end to end tutorial (#13411) +* Update MXNetTutorialTemplate.ipynb (#13568) +* Simplifications and some fun stuff for the MNIST Gluon tutorial (#13094) +* Clarify dependency on OpenCV in CNN Visualization tutorial. (#13495) +* Update row_sparse tutorial (#13414) +* add clojure tutorials to index (#14814) +* Update lstm_crf.py (#14865) + + +### Website + +* Version switching user experience improvements (#13921) +* fix toctree Sphinx errors (#13489) +* fix link (#15036) +* fix website build (#14148) +* Fixed mailing list addresses (#13766) +* website publish updates (#14015) +* use relative links; update links (#13741) +* update social media section (#13705) +* [MXNET] Updated http://data.dmlc.ml/ links to http://data.mxnet.io/ (#15065) + +### Documentation +* [MXNET-1402] MXNet docs change for 1.4.1 release (#14949) +* Add API documentation for upsampling operator with examples (#14919) +* Make docblocks for Gluon BatchNorm and SyncBatchNorm consistent with the code (#14840) +* [DOC] Update ubuntu install instructions from source (#14534) +* [Clojure] Better api docstrings by replacing newlines (#14752) +* Fix documentation for bilinear upsampling and add unit test (#14035) +* Updated docs for R-package installation (#14269) +* [docstring] improve docstring and indentation in `module.clj` (#14705) +* The folder python-howto was removed in an earlier commit. The reference to that folder was not removed. Making a PR to remove the reference to this folder to keep documents consistent (#14573) +* Updated documentation about nightly tests (#14493) +* [Doc] Start the tutorials for MKL-DNN backend (#14202) +* [DOC] fix sym.arange doc (#14237) +* fix render issue in NDArray linalg docs (#14258) +* [clojure-package] fix docstrings in `normal.clj` (#14295) +* [DOC] Refine documentation of runtime feature detection (#14238) +* [MXNET-1178] updating scala docs (#14070) +* Fix website scala doc (#14065) +* Return value docs for nd.random.* and sym.random.* (#13994) +* Fixing the doc for symbolic version of rand_zipfian (#13978) +* fix doc of take operator (#13947) +* beta doc fixes (#13860) +* [MXNET-1255] update hybridize documentation (#13597) +* Update Adam optimizer documentation (#13754) +* local docs build feature (#13682) +* gluon docfix (#13631) +* Added javadocs and improved example instructions (#13711) +* [MXNET-1164] Generate the document for cpp-package using Doxygen (#12977) +* Fix warning in waitall doc (#13618) +* Updated docs for randint operator (#13541) +* Update java setup docs for 1.4.0 (#13536) +* clarify ops faq regarding docs strings (#13492) +* [MXNET-1158] JVM Memory Management Documentation (#13105) +* Fixing a 404 in the ubuntu setup doc (#13542) +* Fix READMEs for examples (#14179) +* [Doc] Add MKL-DNN operator list (#14891) +* Fixed some typos in AvgPooling Docs (#14324) +* doc fix (#13465) +* Change Straight Dope to Dive into Deep Learning (#14465) +* [DEV] update code owner (#14862) +* Add notes about debug with libstdc++ symbols (#13533) +* Mention additional language bindings and add links (#14798) +* add contributors from intel (#14455) +* what's new - add 1.4.0 release (#14435) +* added note about cuda9.2 requirement (#14140) +* Remove unnecessary "also" in README.md (#14543) +* Updated news.md with the latest mkldnn submodule version (#14298) +* add new cloud providers to install page (#14039) +* Update NOTICE (#14043) +* Update README.md (#13973) +* Update profiler doc (#13901) +* Add CODEOWNERS for Julia package (#13872) +* update code owner (#13737) +* Update git clone location to apache github (#13706) +* NEWS.md backport from v1.4.x to master (#13693) +* Update CODEOWNERS, add Pedro Larroy. (#13579) +* [MXNET-1225] Always use config.mk in make install instructions (#13364) +* Docs & website sphinx errors squished 🌦 (#13488) +* add Qing's Key to master (#14180) +* add KEY for zachgk (#14965) +* corrected a spellign (#14247) +* 1.4 release (#14297) + + +### Build and Test + +* Fix scala doc build break for v1.3.1 (#13820) +* Adds additional CUDA build environments (#14909) +* Pins version of scikit-learn for python2 due to drop in support (#14928) +* upgrade the libpng to 1.6.35 (#14620) +* Updates to cudnn package installation (#14923) +* Improve order of execution of install scripts. (#14867) +* Installs qemu pip requirements from qemu requirements file (#14355) +* update raspberry pi install instructions (#14172) +* update the scala installation tutorial on intellij (#14033) +* Removes unneeded nvidia driver ppa installation (#13814) +* script for installing gpu libraries and build tools (#13646) +* Set install path for libmxnet.so dynamic lib on Mac OS (#13629) +* compatibility with opencv4 (#14313) +* Flaky test #14189 (#14190) +* Enforce determinism for backwards compatibility checker (#14463) +* Change CUB submodule to track Nvidia CUB project. (#13322) +* Updates gpu tests to use CUDNN_VERSION supplied by the environment but default to 7.0.3 if not set (#14595) +* upgrade the version to 2.0.2 (#14621) +* [Dependency Update] Upgrade the libtiff to 4.0.10 (#14623) +* [Dependency Update] Upgrade cuDNN & NCCL (#14884) +* [Dependency Update] Upgrade openssl to 1.1.1b (#14837) +* [Dependency Update] Upgrade CI to use latest cuDNN (#14950) +* GPU RNN to use TempSpace resource for workspace. (#15056) +* Add vim-nox to ci/docker/install/ubuntu_core.sh (#14632) +* Fix dockerized GPU builds in dev_menu (#14603) +* [MXNET-1093] Add python3 Docker images for each MXNet release (#12791) +* increased docker shared memory (#14119) +* Fix permissions of ci/docker/install/ubuntu_publish.sh (#13840) +* Dockerfiles for Publish Testing (#13707) +* Fix test randint (#14990) +* Silence excessive mkldnn logging output on tests. (#14947) +* Fix test memory with ResourceScope (#14666) +* Sync Horovod distributed training examples with latest changes (#14748) +* use mx.context.num_gpus instead of mx.test_utils.list_gpus in MF recommender example (#14926) +* [MXNET-1400] adding tests cases to verify large tensor support for depth_to_space and space_to_depth (#14797) +* rewrite test_custom_op_exc (#14878) +* [Clojure] Remove unneeded test files (#14813) +* Use correct stash name when running nightly tests (#14809) +* julia/ndarray: fix flaky test cases for `clamp` (#14776) +* Updates tolerances for test_layer_bidirectional (#14682) +* Adds context parameter to check_rnn_layer_forward calls in test_lstmp (#14529) +* reenable the test (#14483) +* temporarily disable integ tests with a dependency on origami repo (#14448) +* Bypass ThreadedEngine in test_operator_gpu.py:test_convolution_multiple_streams. (#14338) +* Updated the MLP test to accept the number of epochs. Reduced the epochs in ci_test.sh to shorten the CI build time (#14149) +* follow up on fix nightly test (#14134) +* Julia: enable integration test (#14025) +* fix test_depthwise_convoltuion for occasional CI failures (#14016) +* fix test_stn (#14063) +* Add a test for SGLD optimizer with comparisons for set noise seeds. (#13762) +* Code modification for testcases of various network models in directory example (#12498) +* Remove MXNET_STORAGE_FALLBACK_LOG_VERBOSE from test_autograd.py (#13830) +* [MXNET-1263] Unit Tests for Java Predictor and Object Detector APIs (#13794) +* ONNX test code cleanup (#13553) +* #13385 [Clojure] - Turn examples into integration tests (#13554) +* add cpp example inception to nightly test (#13534) +* Fix flaky test test_random:test_randint_generator (#13498) +* Adding test for softmaxoutput (#13116) +* [MXNET-1235] Add a test for AdaMax optimizer (#13467) +* [MXNET-545] Fix broken cython build (#10951) +* Update mkldnn window build instructions in MKLDNN_README.md (#14952) +* Added USE_SIGNAL_HANDLER to other Linux builds which didn't had it (#14122) +* Static build for Python (#13916) +* Julia: add windows-cpu build (#13937) +* Static build instruction for MXNet in general (#13914) +* Jenkins nightly maven with static build script and gpu (#13767) +* Re-organize Scala maven build (#13626) +* disable error checking when building old versions (#13725) +* scripts for building libmxnet binary and wheel (#13648) +* Improve dev_menu usability, local build and virtualenv (#13529) +* Scripts for building dependency libraries of MXNet (#13282) +* [MXNET-1224]: improve scala maven jni build and packing. (#13493) +* fix compile error in debug mode (#13873) +* add ccache to docs build (#13832) +* Decreases test sensitivity (#15014) +* bump up atol for test_bilinear_resize_op (#15011) +* Add STL checks via -D_GLIBCXX_ASSERTIONS in debug mode (#14896) +* clean up duplicate cudnn installation (#14996) +* fix custom op fork test (#14753) +* fix pi instructions (#14746) +* Reenable TensorRT step (#14654) +* Fixes for CI downloads (#14504) +* Fixed tutorial warnings (#14472) +* Fixes static build script for cub directory rename (#14578) +* add a compiler flag to use int64 as tensor size (#14570) +* Upgrade Pylint version to 2.3.1 (#14807) +* Fixes installation nightly test by filtering out the git commands (#14144) +* fix nightly test on tutorials (#14036) +* Fix MXNet R package build (#13952) +* re-enable test after issue fixed https://github.com/apache/incubator-mxnet/issues/10973 (#14032) +* Add back R tests and fix typo around R and perl tests (#13940) +* Fix document build (#13927) +* Temporarily disables windows pipeline to unblock PRs (#14261) +* Fix USE_MKLDNN check in Makefile (#13775) +* Fix spelling in threaded_engine_test (#14709) +* Fix cmake options parsing in dev_menu (#13458) +* Add Local test stage and option to jump directly to menu item from commandline (#13809) +* Add CPU test coverage and refine cmake builds (#13338) +* ONNX test code cleanup - part 2 (#13738) +* Rearrange tests written only for update_on_kvstore = True (#13514) +* add batch norm test (#13625) +* Adadelta optimizer test (#13443) +* Skip flaky test https://github.com/apache/incubator-mxnet/issues/13446 (#13480) +* Comment out test_unix_python3_tensorrt_gpu step (#14642) +* Enable bulking test on windows (#14392) +* rewrote the concat test to avoid flaky failures (#14049) +* #13624 clojure nightly tests (#13624) +* Temporarily disable website testing (#13887) +* adding tolerance to flaky test (#13850) +* Add publish test of PyPi cu100mkl (#14637) +* CMake: Enable installation of cpp-package headers (#13339) +* Use USE_SIGNAL_HANDLER by default set to ON in CMakeLists.txt (#14599) +* Improve CMake handling of sse2 and sse3 (#14757) +* Update base CUDA image for CI to v10.0 cuDNN 7.3.1 (#14513) +* Updates build_lib.sh to copy the cub library license (#14347) +* Add license check to dev_menu, docs build with docker (#14166) +* Print reproduction command on CI failure (#14815) +* change mxnet_option behavior (#14743) +* [DEP] upgrade dmlc-core (#14510) +* Use ubuntu_rat container for rat check (#14678) +* Added repeats for github status updates (#14530) +* add filter to warnings (#14532) +* CI Changes for Codified Windows AMIs (#14336) +* Refactors USE_NVRTC setting to ENABLE_CUDA_RTC in pip make config files (#14250) +* pypi package description. manifest/setup.py update (#14255) +* make rat-excludes compliant with apache release policy (#14142) +* Add libhdf5-dev to ubuntu_core.sh (#14079) +* Added logging to GitHub commit status publishing (#13615) +* [CI] Prevent timeouts when rebuilding containers with docker. (#13818) +* [MXNET-862] Basic maven jenkins pipeline (#13450) +* Scope requests so it's not needed for dev_menu (#13771) +* Add timeout/retry logic to docker cache download (#13573) +* turn on Sphinx warnings as errors (#13544) +* [MXNET-1251] Basic configuration to do static-linking (#13621) +* Improve CCache handling (#13456) +* build config for maven and pip (#13556) +* Add Intel MKL blas to Jenkins (#13607) +* Add workspace cleaning after job finished (#13490) +* Add a retry to qemu_provision (#13551) +* Deprecate Jenkinsfile (#13474) +* [MXNET-1408] Adding test to verify Large Tensor Support for ravel and unravel (#15048) +* move amp test and change op support to warning (#15085) +* Fixes call to build ubuntu gpu in nightly tests (#14964) +* rat check make target (#15127) +* add epsilon for tolerance level (#15098) +* Change mx.test_utils.list_gpus to mx.context.num_gpus where possible (#14946) +* bump up cudnn to 7.5.1 & nccl 2.4.2 (#14988) +* Disables TensorRT build step (#14958) +* disable flaky integration test (#14151) +* Disables large tensor size cpu test step (#14982) +* Disable Flaky Test test_poisson_generator (#14540) +* Disabled flaky test test_negative_binomial_generator (#13784) +* Disabled flaky test test_gluon_data.test_recordimage_dataset_with_data_loader_multiworker (#13527) + + +### Bug-fixes + +* Improve dev_menu virtualenv handling (#14788) +* Fallback to dense version for grad(reshape), grad(expand_dims) (#13599) +* Fix the bug of BidirectionalCell (#13575) +* set _scale in Trainer using optimizer rescale_grad (#14593) +* [MXNET-1379] update reshape operator (#14600) +* Add repr for SymbolBlock (#14423) +* Cudnn conv dgrad algo filtering (#14310) +* Fix memory leak for size-zero ndarray (#14365) +* Fixes the test_sgld (#14473) +* Revert "Fix memory leak for size-zero ndarray (#14365)" (#14477) +* fix custom operation in fork (#14451) +* Fixes test_operator_gpu.test_multinomial_generator (#14475) +* support leading dimension of -1 in ravel/unravel (#14356) +* begin=end not a valid input (#14403) +* Fix NaN value comparisons in relu, max and min ops (#14262) +* fix engine crash in shutdown phase (#14382) +* fix OOM error during resource allocation (#14444) +* Fix relative difference scala (#14417) +* Correct update count with Gluon trainer and update_on_kvstore=False (#14377) +* Fix crashes on visualization (#14425) +* Reorder module import orders for dist-kvstore (#13742) +* Fixes for trainer with update_on_kvstore=False (#13721) +* Fix errors in docstrings for subgraph op; use code directive (#13463) +* Add resiliency to onnx export code (#13426) +* update github location for sampled_block.py (#13508) +* Revert "Manually track num_max_thread (#12380)" (#13501) +* Revert "Feature/mkldnn static 2 (#13503)" (#13540) +* [MXNET-1110] Add header files required by horovod (#13062) +* [MXAPPS-1020] Clean up some Sphinx warnings. (#13539) +* [MXNET-1249] Fix Object Detector Performance with GPU (#13522) +* [MXNET-769] Use MXNET_HOME in a tempdir in windows to prevent access denied due t… (#13531) +* Chi_square_check for discrete distribution fix (#13543) +* Fix use-before-assignment in convert_dot (#13511) +* fix the situation where idx didn't align with rec (#13550) +* fix link for gluon model zoo (#13583) +* Fix exception handling api doc (#13519) +* [MXNET-1253] fix control_flow_op (#13555) +* fix the Float not showing correctly problem (#13617) +* fix quantize pass error when the quantization supported Op are excluded in the model (#13596) +* Fix for import mxnet taking long time if multiple process launched (#13602) +* Revert "Feature/mkldnn static (#13628)" (#13638) +* updated reference to Apache MXNet (#13645) +* Fix incorrect delete in MXExecutorReshape exception handling (#13376) +* add build fix for Scala/Java build (#13655) +* remove omp which can cause ssd accuracy variance (#13622) +* Fix Jetson compilation (#13532) +* Revert "Fix Jetson compilation" (#13665) +* Fix Jetson compilation (#13666) +* Revert "Revert "[MXNET-43] Fix Jetson compilation" (#13665)" (#13672) +* fix unpicklable transform_first on windows (#13686) +* Fix NDArray ToDLPack Bug (#13698) +* Fix the quantization script to support Python2 (#13700) +* Update basic_layers.py (#13732) +* [MXNET-1231] Allow not using Some in the Scala operators (#13619) +* [MXNET-244] Work around likely compiler bug on nested inlines and temporary acces… (#13535) +* Use curl to download sample data instead of wget. (#13761) +* fix bipartite match memory corruption (#13727) +* remove attributes clear on TRT nodes for GetOptimizedSymbol (#13703) +* fix redirection issues; set default version to master (#13796) +* fix for params with no dims in onnx (#13413) +* Remove semicolon in libmxnet.sym file (#13822) +* remove useless code (#13777) +* Fixing a symlink issue with R install (#13708) +* fix minor indentation (#13827) +* Fix Tree Reduction on new instance type p3dn.24xlarge (#13852) +* [Clojure] package infer tweaks (#13864) +* Fix cpp examples build on Mac. (#13826) +* Fix launch bounds in spatial transformer (#13188) +* Update example scripts classpath. (#13849) +* fix ssd quantization script error (#13843) +* Avoid adding SegfaultLogger if process already has sig handler. (#13842) +* fix the fetching GPU problem (#13889) +* Fix SN-GAN example doc (#13877) +* update Spectral Normalization Code (#13868) +* Fixed java benchmark failing error by fixing the classpath (#13891) +* Fix the order of error term's operands (#13745) +* fix bug in nag optimizer (#13683) +* Fix BatchNorm converter for CoreML when fix_gamma=True (#13557) +* Fix for test always returning true (#13911) +* Add error checking for cpp examples. (#13828) +* julia: fix `argmax` for NDArray (#13871) +* test_ImageRecordIter_seed_augmentation flaky test fix (#12485) +* Julia: fix filename quoting in docstring (#13894) +* Flaky maven binary download (#13974) +* [MXNET-1293] Adding Iterables instead of List to method signature for infer APIs in Java (#13977) +* Sample python bilinear initializer at integral points in y-direction (#12983) +* Fix inconsistent handling for FResourceRequestEx for imperative and symbolic executor (#14007) +* [MXNET-1258] fix unittest for ROIAlign Operator (#13609) +* Fix performance regression in normalize operator (#14055) +* Remove inplace support for ToTensor operator (#14083) +* Addresses comments in runtime feature discovery API (#13964) +* The latest version of leiningen has a dependency problem with codox (#14132) +* Fix quote on LBSGD docs (#13975) +* Fixes spelling (#14168) +* Fix broken amalgamation (#12792) +* Fix nd.pick large array issue (#14082) +* Fix req=null in SliceLikeBackward (#14209) +* onnx broadcast ops fixes (#13604) +* fix update params (#14218) +* MXNet Java bug fixes and experience improvement (#14213) +* reverting broadcasting fixes (#14299) +* fix memory-related issues to enable ASAN tests (#14223) +* FIX: flaky test exponential generator (#14287) +* fix SoftmaxOutput resource bug (#14302) +* Fix shape inference pass (#14153) +* Limit workspace for cudnnGet results (#14326) +* #14199: catch subprocess.CalledProcessError in get_gpus() (#14212) +* Fixes #14181, validate model output shape for ObjectDetector. (#14215) +* Optimizer MXKVStoreUpdater bug fix in serializeState method (#14337) +* Add proper exception message for negative shape in array creation routines (#14362) +* Fix NaN value comparisons in relu, max and min ops (#14262) +* fix engine crash in shutdown phase (#14382) +* Flaky test #14189 (#14190) +* Correct update count with Gluon trainer and update_on_kvstore=False (#14377) +* Fix relative difference scala (#14417) +* fix OOM error during resource allocation (#14444) +* Fix crashes on visualization (#14425) +* begin=end not a valid input (#14403) +* Fix memory leak for size-zero ndarray (#14365) +* Fixes the test_sgld (#14473) +* Revert "Fix memory leak for size-zero ndarray (#14365)" (#14477) +* fix custom operation in fork (#14451) +* Fixes test_operator_gpu.test_multinomial_generator (#14475) +* Fix script retrieval (#14519) +* Memory fixes. Resolves #10867, and resolves #14080 (#14372) +* Chouffe/clojure fix tests (#14531) +* [clojure][image] add draw-bounding-box interop (#14533) +* fix tests (#14565) +* Do not touch GPU 0 during ReleaseAll (#14550) +* [MXNET-1357] Fix the cpp-examples to add exception handling (#14441) +* fix build cpp examples option (#14562) +* Fix flaky test poisson generator & test_negative_binomial_generator (#14571) +* Fixing unintentional variable overloading (#14438) +* fix quantize graph pass (#14605) +* replace std::random_shuffle to std::shuffle (#14523) +* Add exception handling support for waitall (#14397) +* split_and_load can now handle num_ctx > num_data. Issue #13909 (#14607) +* Fix aspect ratio sampling for RandomResizedCrop (#14585) +* [MXNET-400] support string type for kvstore key in cpp-package (#10792) +* Fix warning on macro expansion using defined. (#14598) +* Fix scaladoc scalastyle violations in Infer package (#14671) +* Fix profiler check (#14677) +* Tweak the copy for the cudnn autotuning warning. (#14680) +* Properly handling custom op exception by modify engine (#14693) +* Disable USE_GPERFTOOLS (#14711) +* Reference engine from chunk via weak pointer (#14591) +* [C++] fix type inconsistent issue when loading quantized parameters (#15038) +* Fix crash in random.shuffle operator (#15041) +* [MXNET-1406] [BUG] Fix DLManagedTensor deleter (#15016) +* Fixes lint issue in AMP (#15015) +* Fixed issue where the estimator was printing beyond the dataset size … (#14464) +* Fixes cuDNN version for CUDA 9.0 build environment (#15001) +* Fix the incorrect MKLDNN/MKL logic in cmake (#14877) +* Fixed and re-enables TensorRT steps (#14960) +* Fix the return type of sparse.clip operator (#14856) +* Fix sample_multinomial number of outputs bug (#14873) +* [MXNET-13578] Fix cmake installation failed (#14692) +* Fix iterator over symbol when multiple children have the same name (#14597) +* Fixes for wine detection tutorial (#13886) +* Scala/Java Predict API fix #14756 (#14804) +* Fix GELU backward possible NaN (#14782) +* fix shape index bug (#14518) +* [BUGFIX] fix ELU function will appear nan when calculating the gradient (#14673) +* Change size_t to int within for loop to fix windows build error (#14740) +* [contrib][op] fix MultiBoxPrior confusing results if first ratio is not 1.0 (#13763) +* Fix scalastyle (#14669) +* fix Makefile (#14424) +* [v1.4.x] Update MKL-DNN to fix the OSX build issue (#14141) (#14182) +* add preprocessed data and pretrained model info; minor format/spelling fixes (#14170) +* Fixes libjpeg-turbo dependency under Ubuntu 16.04 (#14127) +* Fix website error pages (#13963) +* fix Makefile for rpkg (#13590) +* fix c complier to clang (#13778) +* Fix #13521 (#13537) +* [MXNET-1234] Fix shape inference problems in Activation backward (#13409) +* Revert the change broadcast_to param shape (#14998) +* Fix infer shape partial after unknown shape changed to -1 (#14869) +* fix add_n bug: when input mem overlap with output mem, results is wrong (#14889) +* [Bugfix] Fix layer norm for large input shape (#14870) +* Fix Clojure BERT example's context argument (#14843) +* fix min max on zero-sized ndarray (#14745) +* fix acc_type_switch macro with extra tests (#14773) +* fix bug in profiler tutorial when using cpu (#13695) +* [MXNET-1291] solve pylint errors in examples with issue no.12205 (#13815) +* data preparation file moved in example (#14781) +* [MXNET-1291] solve pylint errors in examples with issue no.12205 (#13848) +* Prevent crashes for opencv exception and std::exception (#14433) +* Set idx2name for Optimizer object (#14703) +* Revert "Bumped minor version from 1.4.0 to 1.5.0 on master, updated License file" (#13558) +* [BUGFIX] fix unknown parameter shapes when np_shape is turned on. (#15097) +* Add gluonCV to fix AMP Tutorial (#15039) +* fix the if condition for LayerNorm (#15094) +* [MKLDNN]Fix mkldnn deconvolution forward with bias (#15088) +* NER example: fix divisions by zero (#15068) +* remove warning in tutorial: (#15135) +* [MXNET-1291] solve pylint errors in examples with issue no.12205 (#13938) +* Revert "Improve cached_op performance for static mode (#14785)" (#14868) +* Fix mkldnn backend when using naive engine (#15089) +* fix gluon rnn cell single step unroll (#15081) +* Revert "Improve FC perf when no_bias=False (#15033)" (#15099) + + +### License + +* Updates python setup.py for recent license changes (#14778) +* [MXNET-1377] Add static-dependencies licenses (#14726) +* add license (#13793) +* License update (#13565) +* Bumped minor version from 1.4.0 to 1.5.0 on master, updated License file (#13478) +* License Googletest and Appendix (#14687) +* Add copyrights for third party licenses to license file (#13851) +* Improve license_header tool by only traversing files under revision c… (#13803) +* Update LICENSE File with subcomponents (#13808) + +### Depreciations + +* Julia: deprecate `mx.empty`, replace it with `UndefInitializer` (#13934) + * Deprecate NDArrayCollector and instead use ResourceScope (#14780) + +### Known Issues +* Amalgamation compile problems(#14808) +* Dynamic Shape does not support reverse shape inference and deferred initialization. (#14983) +* Disables flaky test_random_size_crop (#15019) +* Disables flaky test_l2_normalization (#15006) +* Disables flaky TestStochasticTiming_2D test (#14412) +* Disables flaky test_operator.test_sgld test (#14410) +* Disables test_bulking due to flakyness (#14971) +* Disabled flaky test (#13758) +* Disables flaky test_droupout (#15003) +* Disables flaky test_operator_gpu.test_activation (#14969) + ## 1.4.1 From 0e0c7f394e5686ea4b3fa9ab4c41e3cd12cb214a Mon Sep 17 00:00:00 2001 From: Haohuan Wang Date: Tue, 23 Jul 2019 22:17:42 -0700 Subject: [PATCH 105/813] fix LinearRegressionOutput with empty label (#15620) --- cpp-package/example/CMakeLists.txt | 4 ++ cpp-package/example/test_regress_label.cpp | 56 ++++++++++++++++++++++ cpp-package/tests/ci_test.sh | 3 ++ src/operator/regression_output-inl.h | 3 +- 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 cpp-package/example/test_regress_label.cpp diff --git a/cpp-package/example/CMakeLists.txt b/cpp-package/example/CMakeLists.txt index b4cea68fbd05..643a92d9a3bc 100644 --- a/cpp-package/example/CMakeLists.txt +++ b/cpp-package/example/CMakeLists.txt @@ -27,6 +27,10 @@ endif() include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include) +add_executable(test_regress_label test_regress_label.cpp ${CPP_PACKAGE_HAEDERS}) +target_link_libraries(test_regress_label ${CPP_EXAMPLE_LIBS}) +add_dependencies(test_regress_label ${CPPEX_DEPS}) + add_executable(lenet lenet.cpp ${CPP_PACKAGE_HEADERS}) target_link_libraries(lenet ${CPP_EXAMPLE_LIBS}) add_dependencies(lenet ${CPPEX_DEPS}) diff --git a/cpp-package/example/test_regress_label.cpp b/cpp-package/example/test_regress_label.cpp new file mode 100644 index 000000000000..8d1d6444b138 --- /dev/null +++ b/cpp-package/example/test_regress_label.cpp @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * This file is used for testing LinearRegressionOutput can + * still bind if label is not provided + */ + +#include +#include +#include +#include "dmlc/logging.h" +#include "mxnet-cpp/MxNetCpp.h" + +using namespace mxnet::cpp; + +int main() { + LOG(INFO) << "Running LinearRegressionOutput symbol testing, " + "executor should be able to bind without label."; + Symbol data = Symbol::Variable("data"); + Symbol label = Symbol::Variable("regress_label"); + Symbol symbol = LinearRegressionOutput(data, label); + std::map opReqMap; + for (const auto& iter : symbol.ListArguments()) { + opReqMap[iter] = mxnet::cpp::OpReqType::kNullOp; + } + std::map argMap({ + {"data", NDArray(Shape{1, 3}, Context::cpu(), true)} + }); + + try { + symbol.SimpleBind(Context::cpu(), + argMap, + std::map(), + opReqMap, + std::map()); + } catch (const std::exception& e) { + LOG(ERROR) << "Error binding the symbol: " << MXGetLastError() << " " << e.what(); + throw; + } + return 0; +} diff --git a/cpp-package/tests/ci_test.sh b/cpp-package/tests/ci_test.sh index e90493442246..2ac9d6fcd741 100755 --- a/cpp-package/tests/ci_test.sh +++ b/cpp-package/tests/ci_test.sh @@ -60,6 +60,9 @@ cp ../../build/cpp-package/example/test_score . cp ../../build/cpp-package/example/test_ndarray_copy . ./test_ndarray_copy +cp ../../build/cpp-package/example/test_regress_label . +./test_regress_label + sh unittests/unit_test_mlp_csv.sh cd inference diff --git a/src/operator/regression_output-inl.h b/src/operator/regression_output-inl.h index ba59937a7152..dcee8027dff0 100644 --- a/src/operator/regression_output-inl.h +++ b/src/operator/regression_output-inl.h @@ -59,7 +59,8 @@ inline bool RegressionOpShape(const nnvm::NodeAttrs& attrs, const mxnet::TShape &dshape = in_attrs->at(0); if (!shape_is_known(dshape)) return false; auto &lshape = (*in_attrs)[1]; - if (lshape.ndim() == 0) { + // if label is not defined, manually build the shape based on dshape + if (lshape.ndim() == -1) { // special treatment for 1D output, to allow 1D label by default. // Think about change convention later if (dshape.ndim() == 2 && dshape[1] == 1) { From 01fae12e58d73d5989a23cfc8b7519f4772fdf1f Mon Sep 17 00:00:00 2001 From: Lai Wei Date: Wed, 24 Jul 2019 13:38:42 -0700 Subject: [PATCH 106/813] bump up version from 1.5.0 to 1.6.0 on master (#15072) * bump up version * address comments * trigger * trigger --- R-package/DESCRIPTION | 2 +- contrib/clojure-package/README.md | 4 ++-- .../clojure-package/examples/bert/project.clj | 2 +- .../examples/captcha/project.clj | 2 +- .../cnn-text-classification/project.clj | 2 +- .../clojure-package/examples/gan/project.clj | 2 +- .../examples/imclassification/project.clj | 2 +- .../examples/infer/imageclassifier/project.clj | 2 +- .../examples/infer/objectdetector/project.clj | 2 +- .../examples/infer/predictor/project.clj | 2 +- .../examples/module/project.clj | 2 +- .../examples/multi-label/project.clj | 2 +- .../examples/neural-style/project.clj | 2 +- .../examples/pre-trained-models/project.clj | 2 +- .../examples/profiler/project.clj | 2 +- .../clojure-package/examples/rnn/project.clj | 2 +- .../examples/tutorial/project.clj | 2 +- .../examples/visualization/project.clj | 2 +- contrib/clojure-package/project.clj | 2 +- .../clojure-package/scripts/update_versions.sh | 2 +- .../tutorials/scala/mxnet_scala_on_intellij.md | 6 +++--- julia/NEWS.md | 5 ++++- python/mxnet/libinfo.py | 2 +- scala-package/README.md | 18 +++++++++--------- .../main/scala/org/apache/mxnet/NDArray.scala | 2 +- .../org/apache/mxnet/NDArrayCollector.scala | 12 ++++++------ scala-package/mxnet-demo/java-demo/README.md | 4 ++-- scala-package/mxnet-demo/java-demo/pom.xml | 4 ++-- scala-package/mxnet-demo/scala-demo/pom.xml | 4 ++-- scala-package/packageTest/README.md | 2 +- snapcraft.yaml | 2 +- 31 files changed, 53 insertions(+), 50 deletions(-) diff --git a/R-package/DESCRIPTION b/R-package/DESCRIPTION index 70aa66e36b7e..b124877c8d3c 100644 --- a/R-package/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -1,7 +1,7 @@ Package: mxnet Type: Package Title: MXNet: A Flexible and Efficient Machine Learning Library for Heterogeneous Distributed Systems -Version: 1.5.0 +Version: 1.6.0 Date: 2017-06-27 Author: Tianqi Chen, Qiang Kou, Tong He, Anirudh Acharya Maintainer: Qiang Kou diff --git a/contrib/clojure-package/README.md b/contrib/clojure-package/README.md index 7bb417edf3d3..c8971d327805 100644 --- a/contrib/clojure-package/README.md +++ b/contrib/clojure-package/README.md @@ -137,7 +137,7 @@ With this option, you will install a Git revision of the Clojure package source - Install additional dependencies as described in [the corresponding section for Option 1](#installing-additional-dependencies), -- Recursively clone the MXNet repository and checkout the desired version, (example 1.3.1). You should use the latest [version](https://search.maven.org/search?q=clojure-mxnet)), and a clone into the `~/mxnet` directory: +- Recursively clone the MXNet repository and checkout the desired version, (example 1.4.1). You should use the latest [version](https://search.maven.org/search?q=clojure-mxnet)), and a clone into the `~/mxnet` directory: ```bash git clone --recursive https://github.com/apache/incubator-mxnet.git ~/mxnet @@ -174,7 +174,7 @@ In that case, however, breakage can happen at any point, for instance when the S With this option, you will compile the core MXNet C++ package and jars for both Scala and Clojure language bindings from source. If you intend to make changes to the code in any of the parts, or if you simply want the latest and greatest features, this choice is for you. -The first step is to recursively clone the MXNet repository and checkout the desired version, (example 1.3.1). You should use the latest [version](https://search.maven.org/search?q=clojure-mxnet)), and clone into the `~/mxnet` directory: +The first step is to recursively clone the MXNet repository and checkout the desired version, (example 1.4.1). You should use the latest [version](https://search.maven.org/search?q=clojure-mxnet)), and clone into the `~/mxnet` directory: ```bash git clone --recursive https://github.com/apache/incubator-mxnet.git ~/mxnet diff --git a/contrib/clojure-package/examples/bert/project.clj b/contrib/clojure-package/examples/bert/project.clj index 05061b476241..44ae4d536d26 100644 --- a/contrib/clojure-package/examples/bert/project.clj +++ b/contrib/clojure-package/examples/bert/project.clj @@ -23,7 +23,7 @@ ;;; so if you run into trouble please delete the `lein-juptyter` plugin [lein-jupyter "0.1.16" :exclusions [org.clojure/tools.nrepl org.clojure/clojure org.codehaus.plexus/plexus-utils org.clojure/tools.reader]]] :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"] [cheshire "5.8.1"] [clojure-csv/clojure-csv "2.0.1"]] :pedantic? :skip diff --git a/contrib/clojure-package/examples/captcha/project.clj b/contrib/clojure-package/examples/captcha/project.clj index fa37fecbe035..795f3fea875a 100644 --- a/contrib/clojure-package/examples/captcha/project.clj +++ b/contrib/clojure-package/examples/captcha/project.clj @@ -19,7 +19,7 @@ :description "Captcha recognition via multi-label classification" :plugins [[lein-cljfmt "0.5.7"]] :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"]] :main ^:skip-aot captcha.train-ocr :profiles {:train {:main captcha.train-ocr} :infer {:main captcha.infer-ocr} diff --git a/contrib/clojure-package/examples/cnn-text-classification/project.clj b/contrib/clojure-package/examples/cnn-text-classification/project.clj index 29ebefe5d200..1b8859fd732c 100644 --- a/contrib/clojure-package/examples/cnn-text-classification/project.clj +++ b/contrib/clojure-package/examples/cnn-text-classification/project.clj @@ -19,6 +19,6 @@ :description "CNN text classification with MXNet" :plugins [[lein-cljfmt "0.5.7"]] :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"]] :pedantic? :skip :main cnn-text-classification.classifier) diff --git a/contrib/clojure-package/examples/gan/project.clj b/contrib/clojure-package/examples/gan/project.clj index 439398783312..4048b0c1cb2e 100644 --- a/contrib/clojure-package/examples/gan/project.clj +++ b/contrib/clojure-package/examples/gan/project.clj @@ -20,7 +20,7 @@ :plugins [[lein-cljfmt "0.5.7"]] :repositories [["vendredi" {:url "https://repository.hellonico.info/repository/hellonico/"}]] :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"] [origami "4.0.0-3"] ] :main gan.gan-mnist) \ No newline at end of file diff --git a/contrib/clojure-package/examples/imclassification/project.clj b/contrib/clojure-package/examples/imclassification/project.clj index 5f77cf55cf35..702a33d67ee8 100644 --- a/contrib/clojure-package/examples/imclassification/project.clj +++ b/contrib/clojure-package/examples/imclassification/project.clj @@ -19,6 +19,6 @@ :description "Clojure examples for image classification" :plugins [[lein-cljfmt "0.5.7"]] :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"]] :pedantic? :skip :main imclassification.train-mnist) diff --git a/contrib/clojure-package/examples/infer/imageclassifier/project.clj b/contrib/clojure-package/examples/infer/imageclassifier/project.clj index 2d5b171d9ab7..dcca5982fd28 100644 --- a/contrib/clojure-package/examples/infer/imageclassifier/project.clj +++ b/contrib/clojure-package/examples/infer/imageclassifier/project.clj @@ -20,6 +20,6 @@ :plugins [[lein-cljfmt "0.5.7"]] :dependencies [[org.clojure/clojure "1.9.0"] [org.clojure/tools.cli "0.4.1"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"]] :main ^:skip-aot infer.imageclassifier-example :profiles {:uberjar {:aot :all}}) diff --git a/contrib/clojure-package/examples/infer/objectdetector/project.clj b/contrib/clojure-package/examples/infer/objectdetector/project.clj index da01797f5a21..1d29be2d8e94 100644 --- a/contrib/clojure-package/examples/infer/objectdetector/project.clj +++ b/contrib/clojure-package/examples/infer/objectdetector/project.clj @@ -22,6 +22,6 @@ :aliases {"run-detector" ["run" "--" "-m" "models/resnet50_ssd/resnet50_ssd_model" "-i" "images/dog.jpg" "-d" "images/"]} :dependencies [[org.clojure/clojure "1.9.0"] [org.clojure/tools.cli "0.4.1"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"]] :main ^:skip-aot infer.objectdetector-example :profiles {:uberjar {:aot :all}}) diff --git a/contrib/clojure-package/examples/infer/predictor/project.clj b/contrib/clojure-package/examples/infer/predictor/project.clj index 0bd1eaee671d..936d9179b76e 100644 --- a/contrib/clojure-package/examples/infer/predictor/project.clj +++ b/contrib/clojure-package/examples/infer/predictor/project.clj @@ -20,6 +20,6 @@ :plugins [[lein-cljfmt "0.5.7"]] :dependencies [[org.clojure/clojure "1.9.0"] [org.clojure/tools.cli "0.4.1"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"]] :main ^:skip-aot infer.predictor-example :profiles {:uberjar {:aot :all}}) diff --git a/contrib/clojure-package/examples/module/project.clj b/contrib/clojure-package/examples/module/project.clj index b667a2a4e122..83519e8e6886 100644 --- a/contrib/clojure-package/examples/module/project.clj +++ b/contrib/clojure-package/examples/module/project.clj @@ -19,7 +19,7 @@ :description "Clojure examples for module" :plugins [[lein-cljfmt "0.5.7"]] :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"]] :pedantic? :skip :main mnist-mlp) diff --git a/contrib/clojure-package/examples/multi-label/project.clj b/contrib/clojure-package/examples/multi-label/project.clj index 6e6a14340d36..d9ec86eb84f2 100644 --- a/contrib/clojure-package/examples/multi-label/project.clj +++ b/contrib/clojure-package/examples/multi-label/project.clj @@ -19,5 +19,5 @@ :description "Example of multi-label classification" :plugins [[lein-cljfmt "0.5.7"]] :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"]] :main multi-label.core) diff --git a/contrib/clojure-package/examples/neural-style/project.clj b/contrib/clojure-package/examples/neural-style/project.clj index 2614a6987dc9..32a640be1490 100644 --- a/contrib/clojure-package/examples/neural-style/project.clj +++ b/contrib/clojure-package/examples/neural-style/project.clj @@ -20,6 +20,6 @@ :plugins [[lein-cljfmt "0.5.7"]] :repositories [["vendredi" {:url "https://repository.hellonico.info/repository/hellonico/"}]] :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"] [origami "4.0.0-3"]] :main neural-style.core) \ No newline at end of file diff --git a/contrib/clojure-package/examples/pre-trained-models/project.clj b/contrib/clojure-package/examples/pre-trained-models/project.clj index e4f6939cb1af..07e0f77b5933 100644 --- a/contrib/clojure-package/examples/pre-trained-models/project.clj +++ b/contrib/clojure-package/examples/pre-trained-models/project.clj @@ -21,6 +21,6 @@ :repositories [["vendredi" {:url "https://repository.hellonico.info/repository/hellonico/"}]] :aliases {"predict-image" ["run" "-m" "pre-trained-models.predict-image" ]} :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"] [origami "4.0.0-3"]] :main pre-trained-models.fine-tune) diff --git a/contrib/clojure-package/examples/profiler/project.clj b/contrib/clojure-package/examples/profiler/project.clj index cc50482d0418..b5c737b521e2 100644 --- a/contrib/clojure-package/examples/profiler/project.clj +++ b/contrib/clojure-package/examples/profiler/project.clj @@ -18,5 +18,5 @@ (defproject profiler "0.1.0-SNAPSHOT" :plugins [[lein-cljfmt "0.5.7"]] :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"]] :main profiler.core) diff --git a/contrib/clojure-package/examples/rnn/project.clj b/contrib/clojure-package/examples/rnn/project.clj index 64f4c290741c..ffbae5da3dd4 100644 --- a/contrib/clojure-package/examples/rnn/project.clj +++ b/contrib/clojure-package/examples/rnn/project.clj @@ -19,5 +19,5 @@ :description "RNN example" :plugins [[lein-cljfmt "0.5.7"]] :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"]] :main rnn.train-char-rnn) diff --git a/contrib/clojure-package/examples/tutorial/project.clj b/contrib/clojure-package/examples/tutorial/project.clj index 58a10f04f28b..6bb77bd753d1 100644 --- a/contrib/clojure-package/examples/tutorial/project.clj +++ b/contrib/clojure-package/examples/tutorial/project.clj @@ -19,7 +19,7 @@ :description "MXNET tutorials" :plugins [[lein-cljfmt "0.5.7"]] :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"] ;; Uncomment the one appropriate for your machine & configuration: #_[org.apache.mxnet.contrib.clojure/clojure-mxnet-linux-cpu "1.4.0"] diff --git a/contrib/clojure-package/examples/visualization/project.clj b/contrib/clojure-package/examples/visualization/project.clj index d91ace3188e6..dae61919716e 100644 --- a/contrib/clojure-package/examples/visualization/project.clj +++ b/contrib/clojure-package/examples/visualization/project.clj @@ -19,5 +19,5 @@ :description "Visualization example" :plugins [[lein-cljfmt "0.5.7"]] :dependencies [[org.clojure/clojure "1.9.0"] - [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] + [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT"]] :main visualization.core) diff --git a/contrib/clojure-package/project.clj b/contrib/clojure-package/project.clj index e2b999df84d6..3ad0cf6ead46 100644 --- a/contrib/clojure-package/project.clj +++ b/contrib/clojure-package/project.clj @@ -15,7 +15,7 @@ ;; limitations under the License. ;; -(defproject org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT" +(defproject org.apache.mxnet.contrib.clojure/clojure-mxnet "1.6.0-SNAPSHOT" :description "Clojure package for MXNet" :url "https://github.com/apache/incubator-mxnet" :license {:name "Apache License" diff --git a/contrib/clojure-package/scripts/update_versions.sh b/contrib/clojure-package/scripts/update_versions.sh index 607e3f357bc9..73bbf14fd942 100755 --- a/contrib/clojure-package/scripts/update_versions.sh +++ b/contrib/clojure-package/scripts/update_versions.sh @@ -19,7 +19,7 @@ # Run this from the main Clojure project directory with 2 arguments # old-version and new-version -# Ex: scripts/update_version 1.5.0-SNAPSHOT 1.5.0-SNAPSHOT +# Ex: scripts/update_version 1.6.0-SNAPSHOT 1.6.0-SNAPSHOT set -evx echo "Replacing $2 with $2 in the directory $PWD " diff --git a/docs/tutorials/scala/mxnet_scala_on_intellij.md b/docs/tutorials/scala/mxnet_scala_on_intellij.md index f1d15cb3c5b5..b9dc7e6d5152 100644 --- a/docs/tutorials/scala/mxnet_scala_on_intellij.md +++ b/docs/tutorials/scala/mxnet_scala_on_intellij.md @@ -228,7 +228,7 @@ The project's `pom.xml` will be open for editing. org.apache.mxnet mxnet-full_2.11-osx-x86_64-cpu - [1.3.1,) + [1.4.0,) org.scala-lang @@ -403,14 +403,14 @@ If you chose to "Build from Source" when following the [install instructions](ht org.apache.mxnet mxnet-core_${scala.version}-${platform}-sources system - /PathToMXNetSource/incubator-mxnet/scala-package/assembly/osx-x86_64-cpu/target/mxnet-full_${scala.version}-osx-x86_64-cpu-1.5.0-SNAPSHOT-sources.jar + /PathToMXNetSource/incubator-mxnet/scala-package/assembly/osx-x86_64-cpu/target/mxnet-full_${scala.version}-osx-x86_64-cpu-1.6.0-SNAPSHOT-sources.jar org.apache.mxnet mxnet-full_${scala.version}-${platform} system - /PathToMXNetSource/incubator-mxnet/scala-package/assembly/osx-x86_64-cpu/target/mxnet-full_${scala.version}-osx-x86_64-cpu-1.5.0-SNAPSHOT.jar + /PathToMXNetSource/incubator-mxnet/scala-package/assembly/osx-x86_64-cpu/target/mxnet-full_${scala.version}-osx-x86_64-cpu-1.6.0-SNAPSHOT.jar ``` diff --git a/julia/NEWS.md b/julia/NEWS.md index 3cd616288bf8..e678b97176d1 100644 --- a/julia/NEWS.md +++ b/julia/NEWS.md @@ -15,7 +15,10 @@ -# v1.5.0 (#TBD) +# v1.6.0 + + +# v1.5.0 * Following material from `mx` module got exported (#TBD): * `NDArray` diff --git a/python/mxnet/libinfo.py b/python/mxnet/libinfo.py index fb0859b9954a..8e0ae05f3378 100644 --- a/python/mxnet/libinfo.py +++ b/python/mxnet/libinfo.py @@ -111,4 +111,4 @@ def find_include_path(): # current version -__version__ = "1.5.0" +__version__ = "1.6.0" diff --git a/scala-package/README.md b/scala-package/README.md index c0f32e47f080..c3ef4b0c84d0 100644 --- a/scala-package/README.md +++ b/scala-package/README.md @@ -45,7 +45,7 @@ To add the MXNet Scala/Java packages to your project, add the dependency as show org.apache.mxnet mxnet-full_2.11-linux-x86_64-gpu - [1.3.1,) + [1.4.0,) ``` @@ -57,7 +57,7 @@ To add the MXNet Scala/Java packages to your project, add the dependency as show org.apache.mxnet mxnet-full_2.11-linux-x86_64-cpu - [1.3.1,) + [1.4.0,) ``` @@ -69,11 +69,11 @@ To add the MXNet Scala/Java packages to your project, add the dependency as show org.apache.mxnet mxnet-full_2.11-osx-x86_64-cpu - [1.3.1,) + [1.4.0,) ``` -**Note:** ```[1.3.1,)<\version>``` indicates that we will fetch packages with version 1.3.1 or higher. This will always ensure that the pom.xml is able to fetch the latest and greatest jar files from Maven. +**Note:** ```[1.4.0,)<\version>``` indicates that we will fetch packages with version 1.4.0 or higher. This will always ensure that the pom.xml is able to fetch the latest and greatest jar files from Maven. ### Nightly ### @@ -101,7 +101,7 @@ Also, add the dependency which corresponds to your platform to the ```dependenci org.apache.mxnet mxnet-full_2.11-linux-x86_64-gpu - [1.5.0-SNAPSHOT,) + [1.6.0-SNAPSHOT,) ``` @@ -113,7 +113,7 @@ Also, add the dependency which corresponds to your platform to the ```dependenci org.apache.mxnet mxnet-full_2.11-linux-x86_64-cpu - [1.5.0-SNAPSHOT,) + [1.6.0-SNAPSHOT,) ``` @@ -124,11 +124,11 @@ Also, add the dependency which corresponds to your platform to the ```dependenci org.apache.mxnet mxnet-full_2.11-osx-x86_64-cpu - [1.5.0-SNAPSHOT,) + [1.6.0-SNAPSHOT,) ``` -**Note:** ```[1.5.0-SNAPSHOT,)``` indicates that we will fetch packages with version 1.5.0 or higher. This will always ensure that the pom.xml is able to fetch the latest and greatest jar files from Maven Snapshot repository. +**Note:** ```[1.6.0-SNAPSHOT,)``` indicates that we will fetch packages with version 1.6.0 or higher. This will always ensure that the pom.xml is able to fetch the latest and greatest jar files from Maven Snapshot repository. Build From Source ----------------- @@ -186,7 +186,7 @@ Adding the following configuration in `pom.xml` org.apache.mxnet mxnet-full_2.11-INTERNAL - 1.5.0 + 1.6.0 system path_to_jar/mxnet-full_2.11-INTERNAL.jar diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala b/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala index 717120bcf984..d55ed961d7ea 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala @@ -756,7 +756,7 @@ object NDArray extends NDArrayBase { class NDArray private[mxnet](private[mxnet] val handle: NDArrayHandle, val writable: Boolean) extends NativeResource { - @deprecated("Please use ResourceScope instead", "1.5.0") + @deprecated("Please use ResourceScope instead", "1.6.0") def this(handle: NDArrayHandle, writable: Boolean = true, addToCollector: Boolean = true) { diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/NDArrayCollector.scala b/scala-package/core/src/main/scala/org/apache/mxnet/NDArrayCollector.scala index 0761481cdfe8..5fa0aa5303b6 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/NDArrayCollector.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/NDArrayCollector.scala @@ -64,7 +64,7 @@ import scala.collection.mutable * }); * */ -@deprecated("Please use ResourceScope instead", "1.5.0") +@deprecated("Please use ResourceScope instead", "1.6.0") object NDArrayCollector { private val logger = LoggerFactory.getLogger(classOf[NDArrayCollector]) @@ -76,14 +76,14 @@ object NDArrayCollector { * Create a collector which will dispose the collected NDArrays automatically. * @return an auto-disposable collector. */ - @deprecated("Please use ResourceScope instead", "1.5.0") + @deprecated("Please use ResourceScope instead", "1.6.0") def auto(): NDArrayCollector = new NDArrayCollector(true) /** * Create a collector allows users to later dispose the collected NDArray manually. * @return a manually-disposable collector. */ - @deprecated("Please use ResourceScope instead", "1.5.0") + @deprecated("Please use ResourceScope instead", "1.6.0") @Experimental def manual(): NDArrayCollector = new NDArrayCollector(false) @@ -91,13 +91,13 @@ object NDArrayCollector { * Collect the NDArrays into the collector of the current thread. * @param ndArray NDArrays need to be collected. */ - @deprecated("Please use ResourceScope instead", "1.5.0") + @deprecated("Please use ResourceScope instead", "1.6.0") @varargs def collect(ndArray: NDArray*): Unit = { currCollector.get().add(ndArray: _*) } } -@deprecated("Please use ResourceScope instead", "1.5.0") +@deprecated("Please use ResourceScope instead", "1.6.0") class NDArrayCollector private(private val autoDispose: Boolean = true, private val doCollect: Boolean = true) { // native ptr (handle) of the NDArray -> NDArray @@ -147,7 +147,7 @@ class NDArrayCollector private(private val autoDispose: Boolean = true, * @return The result of function codeBlock. */ @Experimental - @deprecated("Please use ResourceScope instead", "1.5.0") + @deprecated("Please use ResourceScope instead", "1.6.0") def withScope[T](codeBlock: => T): T = { val old = NDArrayCollector.currCollector.get() NDArrayCollector.currCollector.set(this) diff --git a/scala-package/mxnet-demo/java-demo/README.md b/scala-package/mxnet-demo/java-demo/README.md index 33cd000b3e6b..40a9a4e829fe 100644 --- a/scala-package/mxnet-demo/java-demo/README.md +++ b/scala-package/mxnet-demo/java-demo/README.md @@ -120,5 +120,5 @@ sudo apt install libopencv-imgcodecs3.4 Is there any other version available? -You can find nightly release version from [here](https://repository.apache.org/#nexus-search;gav~org.apache.mxnet~~1.5.0-SNAPSHOT~~). -Please keep the same version in the pom file or [other versions in here](https://repository.apache.org/#nexus-search;gav~org.apache.mxnet~~~~) to run this demo. +You can find nightly release version from [here](https://repository.apache.org/#nexus-search;gav~org.apache.mxnet~~1.6.0-SNAPSHOT~~). +Please keep the same version in the pom file or [other versions in here](https://repository.apache.org/#nexus-search;gav~org.apache.mxnet~~~~) to run this demo. \ No newline at end of file diff --git a/scala-package/mxnet-demo/java-demo/pom.xml b/scala-package/mxnet-demo/java-demo/pom.xml index eb5e043a0dda..a8337b1f3fc6 100644 --- a/scala-package/mxnet-demo/java-demo/pom.xml +++ b/scala-package/mxnet-demo/java-demo/pom.xml @@ -27,7 +27,7 @@ 1.8 1.8 - [1.5.0-SNAPSHOT, ) + [1.6.0-SNAPSHOT, ) 2.11 @@ -41,7 +41,7 @@ - [1.5.0-SNAPSHOT, ) + [1.6.0-SNAPSHOT, ) diff --git a/scala-package/mxnet-demo/scala-demo/pom.xml b/scala-package/mxnet-demo/scala-demo/pom.xml index b83ba518aa3c..8fab50e8cb9c 100644 --- a/scala-package/mxnet-demo/scala-demo/pom.xml +++ b/scala-package/mxnet-demo/scala-demo/pom.xml @@ -35,7 +35,7 @@ - [1.5.0-SNAPSHOT, ) + [1.6.0-SNAPSHOT, ) @@ -65,7 +65,7 @@ 2.11 - [1.3.1, ) + [1.4.0, ) 2.11.8 diff --git a/scala-package/packageTest/README.md b/scala-package/packageTest/README.md index ccbafd46a121..edd5344b4aaa 100644 --- a/scala-package/packageTest/README.md +++ b/scala-package/packageTest/README.md @@ -55,7 +55,7 @@ You are able to run unit tests, integration tests, or both using this utility. T For running on GPU, add the flag `USE_CUDA=1`. -An additional option, you can specify the mxnet version with `MXNET_VERSION=1.3.1-SNAPSHOT`. +An additional option, you can specify the mxnet version with `MXNET_VERSION=1.4.0-SNAPSHOT`. ## Cleaning Up diff --git a/snapcraft.yaml b/snapcraft.yaml index 9791cd86fc0e..5e03a0935430 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -16,7 +16,7 @@ # under the License. name: mxnet -version: '1.5.0' +version: '1.6.0' summary: MXNet is a deep learning framework designed for efficiency and flexibility. description: | MXNet is a deep learning framework designed for both efficiency and From 81ae341f74667dfa9474e3ace2595f9b076d28d9 Mon Sep 17 00:00:00 2001 From: Chaitanya Prakash Bapat Date: Wed, 24 Jul 2019 16:50:39 -0700 Subject: [PATCH 107/813] [Doc] add squeeze to Array change shape (#15549) * add squeeze to Array change shape * Trigger notification * Trigger notification * Trigger notification * Trigger notification --- docs/api/python/ndarray/ndarray.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api/python/ndarray/ndarray.md b/docs/api/python/ndarray/ndarray.md index 3cebffccaaee..ac18b02b6c9b 100644 --- a/docs/api/python/ndarray/ndarray.md +++ b/docs/api/python/ndarray/ndarray.md @@ -149,6 +149,7 @@ The `ndarray` package provides several classes: NDArray.expand_dims NDArray.split NDArray.diag + NDArray.squeeze ``` ### Array expand elements From 8158ba4b0f1ebd696ec09a0b1aa6031bacb60740 Mon Sep 17 00:00:00 2001 From: kshitij12345 Date: Thu, 25 Jul 2019 06:21:02 +0530 Subject: [PATCH 108/813] fix typo (#15648) --- python/mxnet/initializer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mxnet/initializer.py b/python/mxnet/initializer.py index 277bfd0f4fa5..d0282471ceaa 100755 --- a/python/mxnet/initializer.py +++ b/python/mxnet/initializer.py @@ -648,7 +648,7 @@ class MSRAPrelu(Xavier): https://arxiv.org/abs/1502.01852. This initializer is proposed for initialization related to ReLu activation, - it maked some changes on top of Xavier method. + it makes some changes on top of Xavier method. Parameters ---------- From b00bb81fd4346196d7e059e3b051d1c74ba6e572 Mon Sep 17 00:00:00 2001 From: Chaitanya Prakash Bapat Date: Thu, 25 Jul 2019 15:12:42 -0700 Subject: [PATCH 109/813] [Opperf] Add array rearrange operators to opperf (#15606) * add array rearrange operators to opperf * Trigger notification * 4d tensor, param support * new line * add alias logic --- benchmark/opperf/nd_operations/README.md | 5 -- .../opperf/nd_operations/array_rearrange.py | 57 +++++++++++++++++++ .../opperf/nd_operations/binary_operators.py | 4 +- benchmark/opperf/opperf.py | 4 ++ benchmark/opperf/rules/default_params.py | 15 ++++- benchmark/opperf/utils/op_registry_utils.py | 22 +++++++ benchmark/opperf/utils/profiler_utils.py | 20 ++++++- 7 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 benchmark/opperf/nd_operations/array_rearrange.py diff --git a/benchmark/opperf/nd_operations/README.md b/benchmark/opperf/nd_operations/README.md index b98a0d3d0c48..321158c48399 100644 --- a/benchmark/opperf/nd_operations/README.md +++ b/benchmark/opperf/nd_operations/README.md @@ -66,9 +66,7 @@ 46. linalg_extractdiag 47. sgd_mom_update 48. SequenceLast -50. flip 51. SequenceReverse -52. swapaxes 53. SVMOutput 54. linalg_trsm 55. where @@ -82,7 +80,6 @@ 63. mp_sgd_mom_update 64. choose_element_0index 65. tile -66. space_to_depth 67. gather_nd 69. SequenceMask 70. reshape_like @@ -110,14 +107,12 @@ 94. broadcast_like 95. Embedding 96. linalg_makediag -97. transpose 98. linalg_syrk 99. squeeze 101. ROIPooling 102. ftrl_update 103. SliceChannel 104. slice_like -105. depth_to_space 106. linalg_maketrian 108. pad 109. LayerNorm diff --git a/benchmark/opperf/nd_operations/array_rearrange.py b/benchmark/opperf/nd_operations/array_rearrange.py new file mode 100644 index 000000000000..151127c7b49a --- /dev/null +++ b/benchmark/opperf/nd_operations/array_rearrange.py @@ -0,0 +1,57 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import mxnet as mx +from benchmark.opperf.utils.benchmark_utils import run_op_benchmarks +from benchmark.opperf.utils.op_registry_utils import get_all_rearrange_operators + +"""Performance benchmark tests for MXNet NDArray Rearrange Operators. + +1. transpose +2. swapaxes +3. flip +4. depth_to_space +5. space_to_depth +""" + + +def run_rearrange_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): + """Runs benchmarks with the given context and precision (dtype) for all the + rearrange operators in MXNet. + + Parameters + ---------- + ctx: mx.ctx + Context to run benchmarks + dtype: str, default 'float32' + Precision to use for benchmarks + warmup: int, default 25 + Number of times to run for warmup + runs: int, default 100 + Number of runs to capture benchmark results + + Returns + ------- + Dictionary of results. Key -> Name of the operator, Value -> Benchmark results. + + """ + # Fetch all optimizer operators + mx_rearrange_ops = get_all_rearrange_operators() + + # Run benchmarks + mx_rearrange_op_results = run_op_benchmarks(mx_rearrange_ops, dtype, ctx, warmup, runs) + return mx_rearrange_op_results diff --git a/benchmark/opperf/nd_operations/binary_operators.py b/benchmark/opperf/nd_operations/binary_operators.py index cca8f9d1f2ad..1898f5dcbe04 100644 --- a/benchmark/opperf/nd_operations/binary_operators.py +++ b/benchmark/opperf/nd_operations/binary_operators.py @@ -39,7 +39,7 @@ def run_mx_binary_broadcast_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): - """Runs benchmarks with the given context and precision (dtype)for all the binary + """Runs benchmarks with the given context and precision (dtype) for all the binary broadcast operators in MXNet. Parameters @@ -66,7 +66,7 @@ def run_mx_binary_broadcast_operators_benchmarks(ctx=mx.cpu(), dtype='float32', def run_mx_binary_element_wise_operators_benchmarks(ctx=mx.cpu(), dtype='float32', warmup=25, runs=100): - """Runs benchmarks with the given context and precision (dtype)for all the binary + """Runs benchmarks with the given context and precision (dtype) for all the binary element_wise operators in MXNet. Parameters diff --git a/benchmark/opperf/opperf.py b/benchmark/opperf/opperf.py index b2258afaaaf7..77b16670b443 100755 --- a/benchmark/opperf/opperf.py +++ b/benchmark/opperf/opperf.py @@ -39,6 +39,7 @@ from benchmark.opperf.nd_operations.nn_conv_operators import run_pooling_operators_benchmarks, \ run_convolution_operators_benchmarks, run_transpose_convolution_operators_benchmarks from benchmark.opperf.nd_operations.nn_basic_operators import run_nn_basic_operators_benchmarks +from benchmark.opperf.nd_operations.array_rearrange import run_rearrange_operators_benchmarks from benchmark.opperf.utils.common_utils import merge_map_list, save_to_file from benchmark.opperf.utils.op_registry_utils import get_operators_with_no_benchmark, \ @@ -78,6 +79,9 @@ def run_all_mxnet_operator_benchmarks(ctx=mx.cpu(), dtype='float32'): # Run all Sorting and Searching operations benchmarks with default input values mxnet_operator_benchmark_results.append(run_sorting_searching_operators_benchmarks(ctx=ctx, dtype=dtype)) + # Run all Array Rearrange operations benchmarks with default input values + mxnet_operator_benchmark_results.append(run_rearrange_operators_benchmarks(ctx=ctx, dtype=dtype)) + # ************************ MXNET NN OPERATOR BENCHMARKS **************************** # Run all basic NN operations benchmarks with default input values diff --git a/benchmark/opperf/rules/default_params.py b/benchmark/opperf/rules/default_params.py index 2c8f3d436e0d..b16cd5429f59 100644 --- a/benchmark/opperf/rules/default_params.py +++ b/benchmark/opperf/rules/default_params.py @@ -62,6 +62,15 @@ # NOTE: Data used is DEFAULT_DATA DEFAULT_AXIS = [0] +# For rearrange operators +# NOTE: Data needs to be a 4D tensor for operators like space_to_depth and depth_to_space +# Hence below we append 4d to mark the difference. +# For depth_to_space, dimension 3 needs to be a multiple of 'block' and 1 should be a multiple of `block^2` +DEFAULT_DATA_4d = [(1, 4, 2, 4), (10,25,10,100)] +DEFAULT_DIM_1 = [0, 1, 2, 3] +DEFAULT_DIM_2 = [1, 2, 3, 0] +DEFAULT_BLOCK_SIZE = [2, 5] + # Default Inputs. MXNet Op Param Name to Default Input mapping DEFAULTS_INPUTS = {"data": DEFAULT_DATA, "lhs": DEFAULT_LHS, @@ -81,7 +90,11 @@ "k_nd": DEFAULT_K_ND, "p_nd": DEFAULT_P_ND, "axis_shape": DEFAULT_AXIS_SHAPE, - "axis": DEFAULT_AXIS} + "axis": DEFAULT_AXIS, + "data_4d": DEFAULT_DATA_4d, + "dim1": DEFAULT_DIM_1, + "dim2": DEFAULT_DIM_2, + "block_size": DEFAULT_BLOCK_SIZE} # These are names of MXNet operator parameters that is of type NDArray. # We maintain this list to automatically recognize these parameters are to be diff --git a/benchmark/opperf/utils/op_registry_utils.py b/benchmark/opperf/utils/op_registry_utils.py index 88ea7a16cfe6..f5e75066cafc 100644 --- a/benchmark/opperf/utils/op_registry_utils.py +++ b/benchmark/opperf/utils/op_registry_utils.py @@ -115,6 +115,8 @@ def prepare_op_inputs(arg_params): arg_params["params"]["arg_types"]): if "NDArray" in arg_type and arg_name + "_nd" in DEFAULTS_INPUTS: arg_values[arg_name] = DEFAULTS_INPUTS[arg_name + "_nd"] + elif "NDArray" in arg_type and arg_name + "_4d" in DEFAULTS_INPUTS: + arg_values[arg_name] = DEFAULTS_INPUTS[arg_name + "_4d"] elif arg_name in DEFAULTS_INPUTS: arg_values[arg_name] = DEFAULTS_INPUTS[arg_name] elif "float" in arg_type and arg_name + "_float" in DEFAULTS_INPUTS: @@ -262,6 +264,26 @@ def get_all_sorting_searching_operators(): return sort_search_mx_operators +def get_all_rearrange_operators(): + """Gets all array rearrange operators registered with MXNet. + + Returns + ------- + {"operator_name": {"has_backward", "nd_op_handle", "params"}} + """ + rearrange_ops = ['transpose','swapaxes','flip','depth_to_space','space_to_depth'] + + # Get all mxnet operators + mx_operators = _get_all_mxnet_operators() + + # Filter for Array Rearrange operators + rearrange_mx_operators = {} + for op_name, op_params in mx_operators.items(): + if op_name in rearrange_ops and op_name not in unique_ops: + rearrange_mx_operators[op_name] = mx_operators[op_name] + return rearrange_mx_operators + + def get_operators_with_no_benchmark(operators_with_benchmark): """Gets all MXNet operators with not benchmark. diff --git a/benchmark/opperf/utils/profiler_utils.py b/benchmark/opperf/utils/profiler_utils.py index a434d3be1e5c..0df67a00ff26 100644 --- a/benchmark/opperf/utils/profiler_utils.py +++ b/benchmark/opperf/utils/profiler_utils.py @@ -42,14 +42,28 @@ def _get_memory_profile(memory_profile_results): def _get_operator_profile(operator_name, operator_profile_results): operator_profile = {} + + # alias map : dictionary of the form {"alias" : "registered_name"} + # allows to retrieve alias operator profile from the profiler results + # TODO handling - "identity" : "_copy" + alias_map = {"broadcast_plus" : "broadcast_add", "broadcast_minus" : "broadcast_sub", "flatten" : "Flatten", "max_axis" : "max", + "swapaxes" : "SwapAxis", "flip" : "reverse", "reshape" : "Reshape", "crop" : "slice", "sum_axis" : "sum", "min_axis" : "min"} + + op_name = None + + if operator_name in alias_map: + op_name = alias_map[operator_name] + else: + op_name = operator_name + for line in operator_profile_results: - if operator_name in line or operator_name[:3] + " " in line: + if op_name in line or op_name[:3] + " " in line: operation = line.split()[0] operation_avg_time = float(line.split()[-1]) if "_backward" in operation: - operator_profile["avg_time" + operation] = operation_avg_time + operator_profile["avg_time_backward_" + operator_name] = operation_avg_time else: - operator_profile["avg_time_forward_" + operation] = operation_avg_time + operator_profile["avg_time_forward_" + operator_name] = operation_avg_time return operator_profile From e98fea3165670157090f2a2f644890452443803c Mon Sep 17 00:00:00 2001 From: Zhennan Qin Date: Thu, 25 Jul 2019 22:42:04 -0500 Subject: [PATCH 110/813] [MKLDNN] Enable subgraph backend mkldnn by default. (#15518) * Enable subgraph backend mkldnn by default * Fix lint * fix ut * fix scala test * Fix UT * Fix lint * Run CI * Run CI * Support MXNET_MKLDNN_ENABLED * Fix merge * Run CI --- cpp-package/example/inference/README.md | 3 - docs/faq/env_var.md | 3 +- docs/tutorials/c++/subgraphAPI.md | 11 +- docs/tutorials/mkldnn/MKLDNN_README.md | 18 +- example/quantization/README.md | 29 +- example/ssd/README.md | 2 - .../org/apache/mxnet/OperatorSuite.scala | 8 +- src/c_api/c_api_symbolic.cc | 8 +- src/c_api/c_api_test.cc | 8 +- src/executor/graph_executor.cc | 399 +++++++++++------- src/operator/subgraph/build_subgraph.cc | 25 +- .../subgraph/default_subgraph_property.cc | 1 + .../subgraph/default_subgraph_property_v2.cc | 2 + .../subgraph/mkldnn/mkldnn_conv_property.h | 14 +- .../mkldnn/mkldnn_subgraph_property.cc | 8 + src/operator/subgraph/subgraph_property.h | 115 ++++- src/operator/subgraph/tensorrt/tensorrt.cc | 2 + tests/python/mkl/test_quantization_mkldnn.py | 4 + tests/python/unittest/test_operator.py | 135 +++--- 19 files changed, 504 insertions(+), 291 deletions(-) diff --git a/cpp-package/example/inference/README.md b/cpp-package/example/inference/README.md index 81cf9d856c23..90047e5fe14f 100644 --- a/cpp-package/example/inference/README.md +++ b/cpp-package/example/inference/README.md @@ -41,7 +41,6 @@ The following performance numbers are collected via using C++ inference API on A ``` export KMP_AFFINITY=granularity=fine,noduplicates,compact,1,0 export OMP_NUM_THREADS=$(vCPUs/2) -export MXNET_SUBGRAPH_BACKEND=MKLDNN export MXNET_ENGINE_TYPE=NaiveEngine ``` Also users are recommended to use ```numactl``` or ```taskset``` to bind a running process to the specified cores. @@ -87,8 +86,6 @@ Follow the below steps to do inference with more models. The below command lines show how to run inference with FP32/INT8 resnet50_v1 model. Because the C++ inference script provides the almost same command line as this [Python script](https://github.com/apache/incubator-mxnet/blob/master/example/quantization/imagenet_inference.py) and then users can easily go from Python to C++. ``` -# set MKLDNN as subgraph backend -export MXNET_SUBGRAPH_BACKEND=MKLDNN # FP32 inference ./imagenet_inference --symbol_file "./model/resnet50_v1-symbol.json" --params_file "./model/resnet50_v1-0000.params" --dataset "./data/val_256_q90.rec" --rgb_mean "123.68 116.779 103.939" --rgb_std "58.393 57.12 57.375" --batch_size 64 --num_skipped_batches 50 --num_inference_batches 500 diff --git a/docs/faq/env_var.md b/docs/faq/env_var.md index 05f70b01a69b..24d62f342983 100644 --- a/docs/faq/env_var.md +++ b/docs/faq/env_var.md @@ -307,9 +307,10 @@ If ctypes is used, it must be `mxnet._ctypes.ndarray.NDArrayBase`. - This variable controls how many CuDNN dropout state resources to create for each GPU context for use in operator. * MXNET_SUBGRAPH_BACKEND - - Values: String ```(default="")``` + - Values: String ```(default="MKLDNN")``` if MKLDNN is avaliable, otherwise ```(default="")``` - This variable controls the subgraph partitioning in MXNet. - This variable is used to perform MKL-DNN FP32 operator fusion and quantization. Please refer to the [MKL-DNN operator list](../tutorials/mkldnn/operator_list.md) for how this variable is used and the list of fusion passes. + - Set ```MXNET_SUBGRAPH_BACKEND=NONE``` to disable subgraph backend. * MXNET_SAFE_ACCUMULATION - Values: Values: 0(false) or 1(true) ```(default=0)``` diff --git a/docs/tutorials/c++/subgraphAPI.md b/docs/tutorials/c++/subgraphAPI.md index 6b1b477b8021..7403e2654423 100644 --- a/docs/tutorials/c++/subgraphAPI.md +++ b/docs/tutorials/c++/subgraphAPI.md @@ -111,7 +111,15 @@ There're 2 built-in attributes that used by MXNet executor. `inference_only` : bool, apply this property only for inference. Property will be skiped when need_grad=True. Default `false` if this attribute isn't defined. -After defining the subgraph property, we need to register it in .cc file. +After defining the subgraph property, we need to register it under a backend in .cc file. + +Firstly, we need to register the backend + +```C++ +MXNET_REGISTER_SUBGRAPH_BACKEND(SgTest); +``` + +Then register the property under it. ```C++ MXNET_REGISTER_SUBGRAPH_PROPERTY(SgTest, SgProperty); @@ -124,6 +132,7 @@ It's possible to register multiple properties for same backend. In practice, we #include "SgProperty2.h" // Define SgProperty2 class #include "SgProperty3.h" // Define SgProperty3 class +MXNET_REGISTER_SUBGRAPH_BACKEND(SgTest); MXNET_REGISTER_SUBGRAPH_PROPERTY(SgTest, SgProperty); // Execution order 1. MXNET_REGISTER_SUBGRAPH_PROPERTY(SgTest, SgProperty2); // Execution order 2. MXNET_REGISTER_SUBGRAPH_PROPERTY(SgTest, SgProperty3); // Execution order 3. diff --git a/docs/tutorials/mkldnn/MKLDNN_README.md b/docs/tutorials/mkldnn/MKLDNN_README.md index 0b8c6b04517b..d8f78ecf71a7 100644 --- a/docs/tutorials/mkldnn/MKLDNN_README.md +++ b/docs/tutorials/mkldnn/MKLDNN_README.md @@ -103,7 +103,7 @@ LIBRARY_PATH=$(brew --prefix llvm)/lib/ make -j $(sysctl -n hw.ncpu) CC=$(brew -

Windows

On Windows, you can use [Micrsoft Visual Studio 2015](https://www.visualstudio.com/vs/older-downloads/) and [Microsoft Visual Studio 2017](https://www.visualstudio.com/downloads/) to compile MXNet with Intel MKL-DNN. -[Micrsoft Visual Studio 2015](https://www.visualstudio.com/vs/older-downloads/) is recommended. +[Micrsoft Visual Studio 2015](https://www.visualstudio.com/vs/older-downloads/) is recommended. **Visual Studio 2015** @@ -113,8 +113,8 @@ To build and install MXNet yourself, you need the following dependencies. Instal 2. Download and Install [CMake 3](https://cmake.org/files/v3.14/cmake-3.14.0-win64-x64.msi) if it is not already installed. 3. Download [OpenCV 3](https://sourceforge.net/projects/opencvlibrary/files/3.4.5/opencv-3.4.5-vc14_vc15.exe/download), and unzip the OpenCV package, set the environment variable ```OpenCV_DIR``` to point to the ```OpenCV build directory``` (e.g.,```OpenCV_DIR = C:\opencv\build ```). Also, add the OpenCV bin directory (```C:\opencv\build\x64\vc14\bin``` for example) to the ``PATH`` variable. 4. If you have Intel Math Kernel Library (Intel MKL) installed, set ```MKL_ROOT``` to point to ```MKL``` directory that contains the ```include``` and ```lib```. If you want to use MKL blas, you should set ```-DUSE_BLAS=mkl``` when cmake. Typically, you can find the directory in ```C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl```. -5. If you don't have the Intel Math Kernel Library (MKL) installed, download and install [OpenBLAS](http://sourceforge.net/projects/openblas/files/v0.2.14/), or build the latest version of OpenBLAS from source. Note that you should also download ```mingw64.dll.zip``` along with openBLAS and add them to PATH. -6. Set the environment variable ```OpenBLAS_HOME``` to point to the ```OpenBLAS``` directory that contains the ```include``` and ```lib``` directories. Typically, you can find the directory in ```C:\Downloads\OpenBLAS\```. +5. If you don't have the Intel Math Kernel Library (MKL) installed, download and install [OpenBLAS](http://sourceforge.net/projects/openblas/files/v0.2.14/), or build the latest version of OpenBLAS from source. Note that you should also download ```mingw64.dll.zip``` along with openBLAS and add them to PATH. +6. Set the environment variable ```OpenBLAS_HOME``` to point to the ```OpenBLAS``` directory that contains the ```include``` and ```lib``` directories. Typically, you can find the directory in ```C:\Downloads\OpenBLAS\```. After you have installed all of the required dependencies, build the MXNet source code: @@ -123,17 +123,17 @@ After you have installed all of the required dependencies, build the MXNet sourc git clone --recursive https://github.com/apache/incubator-mxnet.git cd C:\incubator-mxent ``` -2. Enable Intel MKL-DNN by -DUSE_MKLDNN=1. Use [CMake 3](https://cmake.org/) to create a Visual Studio solution in ```./build```. Make sure to specify the architecture in the +2. Enable Intel MKL-DNN by -DUSE_MKLDNN=1. Use [CMake 3](https://cmake.org/) to create a Visual Studio solution in ```./build```. Make sure to specify the architecture in the command: ``` >mkdir build >cd build >cmake -G "Visual Studio 14 Win64" .. -DUSE_CUDA=0 -DUSE_CUDNN=0 -DUSE_NVRTC=0 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_PROFILER=1 -DUSE_BLAS=open -DUSE_LAPACK=1 -DUSE_DIST_KVSTORE=0 -DCUDA_ARCH_NAME=All -DUSE_MKLDNN=1 -DCMAKE_BUILD_TYPE=Release ``` -3. Enable Intel MKL-DNN and Intel MKL as BLAS library by the command: +3. Enable Intel MKL-DNN and Intel MKL as BLAS library by the command: ``` >"C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl\bin\mklvars.bat" intel64 ->cmake -G "Visual Studio 14 Win64" .. -DUSE_CUDA=0 -DUSE_CUDNN=0 -DUSE_NVRTC=0 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_PROFILER=1 -DUSE_BLAS=mkl -DUSE_LAPACK=1 -DUSE_DIST_KVSTORE=0 -DCUDA_ARCH_NAME=All -DUSE_MKLDNN=1 -DCMAKE_BUILD_TYPE=Release -DMKL_ROOT="C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl" +>cmake -G "Visual Studio 14 Win64" .. -DUSE_CUDA=0 -DUSE_CUDNN=0 -DUSE_NVRTC=0 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_PROFILER=1 -DUSE_BLAS=mkl -DUSE_LAPACK=1 -DUSE_DIST_KVSTORE=0 -DCUDA_ARCH_NAME=All -DUSE_MKLDNN=1 -DCMAKE_BUILD_TYPE=Release -DMKL_ROOT="C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl" ``` 4. After the CMake successfully completed, in Visual Studio, open the solution file ```.sln``` and compile it, or compile the MXNet source code by using following command: ```r @@ -154,7 +154,7 @@ User can follow the same steps of Visual Studio 2015 to build MXNET with MKL-DNN

Verify MXNet with python

-Preinstall python and some dependent modules: +Preinstall python and some dependent modules: ``` pip install numpy graphviz set PYTHONPATH=[workdir]\incubator-mxnet\python @@ -261,7 +261,7 @@ MKL_VERBOSE SGEMM(T,N,12,10,8,0x7f7f927b1378,0x1bc2140,8,0x1ba8040,8,0x7f7f927b1

Enable graph optimization

-Graph optimization by subgraph feature are available in master branch. You can build from source and then use below command to enable this *experimental* feature for better performance: +Graph optimization with subgraph is available and enabled by default in master branch. For MXNet release v1.5, you can manually enable it by: ``` export MXNET_SUBGRAPH_BACKEND=MKLDNN @@ -271,7 +271,7 @@ This limitations of this experimental feature are: - Use this feature only for inference. When training, be sure to turn the feature off by unsetting the `MXNET_SUBGRAPH_BACKEND` environment variable. -- This feature will only run on the CPU, even if you're using a GPU-enabled build of MXNet. +- This feature will only run on the CPU, even if you're using a GPU-enabled build of MXNet.

Quantization and Inference with INT8

diff --git a/example/quantization/README.md b/example/quantization/README.md index 40f1371c33cc..09321beb7997 100644 --- a/example/quantization/README.md +++ b/example/quantization/README.md @@ -50,10 +50,7 @@ python imagenet_gen_qsym_mkldnn.py --model=resnet50_v1 --num-calib-batches=5 --c The model would be automatically replaced in fusion and quantization format. It is then saved as the quantized symbol and parameter files in the `./model` directory. The following command is to launch inference. ``` -# USE MKLDNN AS SUBGRAPH BACKEND -export MXNET_SUBGRAPH_BACKEND=MKLDNN - -# Launch FP32 Inference +# Launch FP32 Inference python imagenet_inference.py --symbol-file=./model/resnet50_v1-symbol.json --param-file=./model/resnet50_v1-0000.params --rgb-mean=123.68,116.779,103.939 --rgb-std=58.393,57.12,57.375 --num-skipped-batches=50 --batch-size=64 --num-inference-batches=500 --dataset=./data/val_256_q90.rec --ctx=cpu # Launch INT8 Inference @@ -74,8 +71,6 @@ python imagenet_gen_qsym_mkldnn.py --model=squeezenet1.0 --num-calib-batches=5 - The model would be automatically replaced in fusion and quantization format. It is then saved as the quantized symbol and parameter files in the `./model` directory. The following command is to launch inference. ``` -# USE MKLDNN AS SUBGRAPH BACKEND -export MXNET_SUBGRAPH_BACKEND=MKLDNN # Launch FP32 Inference python imagenet_inference.py --symbol-file=./model/squeezenet1.0-symbol.json --param-file=./model/squeezenet1.0-0000.params --rgb-mean=123.68,116.779,103.939 --rgb-std=58.393,57.12,57.375 --num-skipped-batches=50 --batch-size=64 --num-inference-batches=500 --dataset=./data/val_256_q90.rec --ctx=cpu @@ -98,8 +93,6 @@ python imagenet_gen_qsym_mkldnn.py --model=mobilenet1.0 --num-calib-batches=5 -- The model would be automatically replaced in fusion and quantization format. It is then saved as the quantized symbol and parameter files in the `./model` directory. The following command is to launch inference. ``` -# USE MKLDNN AS SUBGRAPH BACKEND -export MXNET_SUBGRAPH_BACKEND=MKLDNN # Launch FP32 Inference python imagenet_inference.py --symbol-file=./model/mobilenet1.0-symbol.json --param-file=./model/mobilenet1.0-0000.params --rgb-mean=123.68,116.779,103.939 --rgb-std=58.393,57.12,57.375 --num-skipped-batches=50 --batch-size=64 --num-inference-batches=500 --dataset=./data/val_256_q90.rec --ctx=cpu @@ -122,8 +115,6 @@ python imagenet_gen_qsym_mkldnn.py --model=mobilenetv2_1.0 --num-calib-batches=5 The model would be automatically replaced in fusion and quantization format. It is then saved as the quantized symbol and parameter files in the `./model` directory. The following command is to launch inference. ``` -# USE MKLDNN AS SUBGRAPH BACKEND -export MXNET_SUBGRAPH_BACKEND=MKLDNN # Launch FP32 Inference python imagenet_inference.py --symbol-file=./model/mobilenetv2_1.0-symbol.json --param-file=./model/mobilenetv2_1.0-0000.params --rgb-mean=123.68,116.779,103.939 --rgb-std=58.393,57.12,57.375 --num-skipped-batches=50 --batch-size=64 --num-inference-batches=500 --dataset=./data/val_256_q90.rec --ctx=cpu @@ -146,8 +137,6 @@ python imagenet_gen_qsym_mkldnn.py --model=inceptionv3 --image-shape=3,299,299 - The model would be automatically replaced in fusion and quantization format. It is then saved as the quantized symbol and parameter files in the `./model` directory. The following command is to launch inference. ``` -# USE MKLDNN AS SUBGRAPH BACKEND -export MXNET_SUBGRAPH_BACKEND=MKLDNN # Launch FP32 Inference python imagenet_inference.py --symbol-file=./model/inceptionv3-symbol.json --param-file=./model/inceptionv3-0000.params --image-shape=3,299,299 --rgb-mean=123.68,116.779,103.939 --rgb-std=58.393,57.12,57.375 --num-skipped-batches=50 --batch-size=64 --num-inference-batches=500 --dataset=./data/val_256_q90.rec --ctx=cpu @@ -171,10 +160,8 @@ python imagenet_gen_qsym_mkldnn.py --model=imagenet1k-resnet-152 --num-calib-bat The model would be automatically replaced in fusion and quantization format. It is then saved as the quantized symbol and parameter files in the `./model` directory. The following command is to launch inference. ``` -# USE MKLDNN AS SUBGRAPH BACKEND -export MXNET_SUBGRAPH_BACKEND=MKLDNN -# Launch FP32 Inference +# Launch FP32 Inference python imagenet_inference.py --symbol-file=./model/imagenet1k-resnet-152-symbol.json --param-file=./model/imagenet1k-resnet-152-0000.params --num-skipped-batches=50 --batch-size=64 --num-inference-batches=500 --dataset=./data/val_256_q90.rec --ctx=cpu # Launch INT8 Inference @@ -196,10 +183,8 @@ python imagenet_gen_qsym_mkldnn.py --model=imagenet1k-inception-bn --num-calib-b The model would be automatically replaced in fusion and quantization format. It is then saved as the quantized symbol and parameter files in the `./model` directory. The following command is to launch inference. ``` -# USE MKLDNN AS SUBGRAPH BACKEND -export MXNET_SUBGRAPH_BACKEND=MKLDNN -# Launch FP32 Inference +# Launch FP32 Inference python imagenet_inference.py --symbol-file=./model/imagenet1k-inception-bn-symbol.json --param-file=./model/imagenet1k-inception-bn-0000.params --rgb-mean=123.68,116.779,103.939 --num-skipped-batches=50 --batch-size=64 --num-inference-batches=500 --dataset=./data/val_256_q90.rec --ctx=cpu # Launch INT8 Inference @@ -240,10 +225,8 @@ Some tips on quantization configs: 2. Then, you should run the following command and verify that your fp32 symbolic model runs inference as expected. ``` -# USE MKLDNN AS SUBGRAPH BACKEND -export MXNET_SUBGRAPH_BACKEND=MKLDNN -# Launch FP32 Inference +# Launch FP32 Inference python imagenet_inference.py --symbol-file=./model/custom-symbol.json --param-file=./model/custom-0000.params --rgb-mean=* --rgb-std=* --num-skipped-batches=* --batch-size=* --num-inference-batches=*--dataset=./data/* --ctx=cpu ``` @@ -260,7 +243,7 @@ python imagenet_gen_qsym_mkldnn.py --model=custom --num-calib-batches=5 --calib- 6. Finally, you can run INT8 inference: ``` -# Launch INT8 Inference +# Launch INT8 Inference python imagenet_inference.py --symbol-file=./model/*.json --param-file=./model/*.params --rgb-mean=* --rgb-std=* --num-skipped-batches=* --batch-size=* --num-inference-batches=*--dataset=./data/* --ctx=cpu # Launch dummy data Inference @@ -289,6 +272,6 @@ the console to run model quantization for a specific configuration. - `launch_inference.sh` This is a shell script that calculate the accuracies of all the quantized models generated by invoking `launch_quantize.sh`. -**NOTE**: +**NOTE**: - This example has only been tested on Linux systems. - Performance is expected to decrease with GPU, however the memory footprint of a quantized model is smaller. The purpose of the quantization implementation is to minimize accuracy loss when converting FP32 models to INT8. MXNet community is working on improving the performance. diff --git a/example/ssd/README.md b/example/ssd/README.md index 92a125f1892d..dcb15f4e47a8 100644 --- a/example/ssd/README.md +++ b/example/ssd/README.md @@ -234,8 +234,6 @@ python quantization.py After quantization, INT8 models will be saved in `model/` dictionary. Use the following command to launch inference. ``` -# USE MKLDNN AS SUBGRAPH BACKEND -export MXNET_SUBGRAPH_BACKEND=MKLDNN # Launch FP32 Inference on VOC dataset python evaluate.py --cpu --num-batch 10 --batch-size 224 --deploy --prefix=./model/ssd_ diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/OperatorSuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/OperatorSuite.scala index deb149985ce8..3bb1b2b5b3bc 100644 --- a/scala-package/core/src/test/scala/org/apache/mxnet/OperatorSuite.scala +++ b/scala-package/core/src/test/scala/org/apache/mxnet/OperatorSuite.scala @@ -694,8 +694,8 @@ class OperatorSuite extends FunSuite with BeforeAndAfterAll } test("maximum") { - val data1 = Symbol.Variable("data") - val data2 = Symbol.Variable("data") + val data1 = Symbol.Variable("data1") + val data2 = Symbol.Variable("data2") val shape = Shape(3, 4) val dataTmp1 = Random.uniform(0, 100, shape) val dataTmp2 = Random.uniform(0, 100, shape) @@ -712,8 +712,8 @@ class OperatorSuite extends FunSuite with BeforeAndAfterAll } test("minimum") { - val data1 = Symbol.Variable("data") - val data2 = Symbol.Variable("data") + val data1 = Symbol.Variable("data1") + val data2 = Symbol.Variable("data2") val shape = Shape(3, 4) val dataTmp1 = Random.uniform(0, 100, shape) val dataTmp2 = Random.uniform(0, 100, shape) diff --git a/src/c_api/c_api_symbolic.cc b/src/c_api/c_api_symbolic.cc index 80ae5438c20d..df839b4d296e 100644 --- a/src/c_api/c_api_symbolic.cc +++ b/src/c_api/c_api_symbolic.cc @@ -1035,15 +1035,15 @@ int MXSetCalibTableToQuantizedSymbol(SymbolHandle qsym_handle, API_END_HANDLE_ERROR(delete s); } -int MXGenBackendSubgraph(SymbolHandle sym_handle, const char *backend, +int MXGenBackendSubgraph(SymbolHandle sym_handle, const char *backend_name, SymbolHandle *ret_sym_handle) { nnvm::Symbol *s = new nnvm::Symbol(); API_BEGIN(); nnvm::Symbol *sym = static_cast(sym_handle); *s = sym->Copy(); - std::vector properties = - mxnet::op::SubgraphPropertyRegistry::Get()->CreateSubgraphProperty(backend); - for (auto property : properties) { + auto backend = mxnet::op::SubgraphBackendRegistry::Get()->GetSubgraphBackend(backend_name); + const auto& subgraph_prop_list = backend->GetSubgraphProperties(); + for (auto property : subgraph_prop_list) { nnvm::Graph g = Symbol2Graph(*s); property->SetAttr("graph", g); g.attrs["subgraph_property"] = std::make_shared(std::move(property)); diff --git a/src/c_api/c_api_test.cc b/src/c_api/c_api_test.cc index ae36b7af2829..fff92692971d 100644 --- a/src/c_api/c_api_test.cc +++ b/src/c_api/c_api_test.cc @@ -41,9 +41,11 @@ int MXBuildSubgraphByOpNames(SymbolHandle sym_handle, nnvm::Symbol* sym = static_cast(sym_handle); *s = sym->Copy(); if (!op_name_set.empty()) { - std::vector properties = - mxnet::op::SubgraphPropertyRegistry::Get()->CreateSubgraphProperty(prop_name); - for (auto property : properties) { + auto& backend = + mxnet::op::SubgraphBackendRegistry::Get()->GetSubgraphBackend(prop_name); + LOG(INFO) << "Subgraph backend " << backend->GetName() << " is activated."; + const auto& subgraph_prop_list = backend->GetSubgraphProperties(); + for (auto property : subgraph_prop_list) { nnvm::Graph g; g.outputs = s->outputs; property->SetAttr("graph", g); diff --git a/src/executor/graph_executor.cc b/src/executor/graph_executor.cc index 82e591e8f17d..0ba85a9cea55 100644 --- a/src/executor/graph_executor.cc +++ b/src/executor/graph_executor.cc @@ -41,11 +41,23 @@ namespace exec { using namespace mxnet::common; +static const std::string GetDefaultSubgraphBackend() { +#if MXNET_USE_MKLDNN == 1 + return std::string("MKLDNN"); +#else + return std::string(); +#endif +} + GraphExecutor::GraphExecutor() { log_verbose_ = dmlc::GetEnv("MXNET_EXEC_VERBOSE_LOGGING", false); need_grad_ = false; is_dynamic_ = false; - subgraph_property_ = dmlc::GetEnv("MXNET_SUBGRAPH_BACKEND", std::string()); + subgraph_property_ = dmlc::GetEnv("MXNET_SUBGRAPH_BACKEND", GetDefaultSubgraphBackend()); + if (subgraph_property_ == "NONE") { + subgraph_property_ = std::string(); + LOG(INFO) << "MXNET_SUBGRAPH_BACKEND=NONE is detected, subgraph backend is not in use"; + } engine_ref_ = Engine::_GetSharedRef(); } @@ -1598,15 +1610,18 @@ static nnvm::Graph InferForwardAttrs(nnvm::Graph g, const Context& default_ctx, const std::map& ctx_map, const std::vector& in_arg_ctxes, - const std::vector& aux_state_ctxes) { + const std::vector& aux_state_ctxes, + bool partial_shape = false) { const auto& indexed_graph = g.indexed_graph(); const auto num_forward_inputs = indexed_graph.input_nodes().size(); g = AssignContext(g, default_ctx, ctx_map, in_arg_ctxes, {}, aux_state_ctxes, {}, num_forward_inputs, g.outputs.size()); g = InferShape(std::move(g), std::move(arg_shapes), "__shape__"); if (g.GetAttr("shape_num_unknown_nodes") != 0U) { - HandleInferShapeError(num_forward_inputs, indexed_graph, - g.GetAttr("shape")); + if (!partial_shape) { + HandleInferShapeError(num_forward_inputs, indexed_graph, + g.GetAttr("shape")); + } } g = InferType(std::move(g), std::move(arg_dtypes), "__dtype__"); if (g.GetAttr("dtype_num_unknown_nodes") != 0U) { @@ -1621,10 +1636,46 @@ static nnvm::Graph InferForwardAttrs(nnvm::Graph g, return g; } +static bool SubgraphBackendCheck(const op::SubgraphBackendPtr& backend, + const Context& default_ctx, + bool verbose = false) { + if (backend->HasAttr("enable") && (backend->GetAttr("enable") != true)) { + if (verbose) { + LOG(INFO) << "Subgraph backend " << backend->GetName() + << " isn't activated."; + } + return false; + } + if (backend->HasAttr("context") && backend->GetAttr("context") != default_ctx) { + if (verbose) { + LOG(INFO) << "Subgraph backend " << backend->GetName() + << " isn't activated as context mismatch."; + } + return false; + } + return true; +} + +static bool SubgraphPropertyCheck(const std::string& backend_name, + const op::SubgraphPropertyPtr& prop, bool need_grad, + bool verbose = false) { + if (prop->HasAttr("inference_only") && prop->GetAttr("inference_only") == true) { + if (need_grad) { + auto full_name = prop->HasAttr("property_name") ? prop->GetAttr("property_name") + : std::string(); + if (verbose) { + LOG(INFO) << "skip partitioning graph with subgraph property " << full_name + << " from backend " << backend_name << " as it requires `grad_req=null`."; + } + return false; + } + } + return true; +} + // Given input attr arrays, partition the graph using the backend name equal to prop_name. // This is a common function for bind and simple_bind flows. -static nnvm::Symbol BuildSubgraph(const nnvm::Symbol& src, - mxnet::op::SubgraphPropertyPtr subgraph_prop, +static nnvm::Symbol BuildSubgraph(const nnvm::Symbol& src, op::SubgraphPropertyPtr subgraph_prop, const mxnet::ShapeVector& arg_shapes, const nnvm::DTypeVector& arg_dtypes, const StorageTypeVector& arg_stypes, const Context& default_ctx, @@ -1635,7 +1686,7 @@ static nnvm::Symbol BuildSubgraph(const nnvm::Symbol& src, nnvm::Graph g; g.outputs = ret.outputs; g = InferForwardAttrs(g, arg_shapes, arg_dtypes, arg_stypes, default_ctx, ctx_map, in_arg_ctxes, - aux_state_ctxes); + aux_state_ctxes, true); subgraph_prop->SetAttr("graph", g); g.attrs["subgraph_property"] = std::make_shared(std::move(subgraph_prop)); g = ApplyPass(std::move(g), "BuildSubgraph"); @@ -1643,20 +1694,16 @@ static nnvm::Symbol BuildSubgraph(const nnvm::Symbol& src, return ret; } -// Given input attr dicts, partition the graph using the backend name equal to prop_name. +// Given input attr dicts, partition the graph using the backend. // This is for simple_bind flow. -static nnvm::Symbol BuildSubgraph(const nnvm::Symbol& src, - const std::string& prop_name, - const std::unordered_map - & arg_shape_map, - const std::unordered_map& arg_dtype_map, - const std::unordered_map& arg_stype_map, - const Context& default_ctx, - const std::map& ctx_map, - std::vector* in_arg_ctxes, - std::vector* arg_grad_ctxes, - std::vector* grad_req_types, - std::vector* aux_state_ctxes) { +static nnvm::Symbol BuildSubgraph( + const nnvm::Symbol& src, const op::SubgraphBackendPtr backend, + const std::unordered_map& arg_shape_map, + const std::unordered_map& arg_dtype_map, + const std::unordered_map& arg_stype_map, const Context& default_ctx, + const std::map& ctx_map, std::vector* in_arg_ctxes, + std::vector* arg_grad_ctxes, std::vector* grad_req_types, + std::vector* aux_state_ctxes, bool verbose = false) { // setup map for in_arg_ctxes, arg_grad_ctxes, aux_state_ctxes and grad_req_types std::unordered_map in_arg_ctx_map; std::unordered_map arg_grad_ctx_map; @@ -1666,7 +1713,7 @@ static nnvm::Symbol BuildSubgraph(const nnvm::Symbol& src, auto arg_names = src.ListInputNames(nnvm::Symbol::kReadOnlyArgs); auto aux_names = src.ListInputNames(nnvm::Symbol::kAuxiliaryStates); for (size_t i = 0; i < arg_names.size(); ++i) { - auto name = arg_names[i]; + const auto& name = arg_names[i]; in_arg_ctx_map[name] = in_arg_ctxes->at(i); arg_grad_ctx_map[name] = arg_grad_ctxes->at(i); grad_req_type_map[name] = grad_req_types->at(i); @@ -1685,81 +1732,73 @@ static nnvm::Symbol BuildSubgraph(const nnvm::Symbol& src, } nnvm::Symbol ret = src.Copy(); std::unordered_set op_names_set; - auto it = op::SubgraphPropertyOpNameSet::Get()->find(prop_name); + const auto& backend_name = backend->GetName(); + const auto it = op::SubgraphPropertyOpNameSet::Get()->find(backend_name); // assign a op name set to the subgraph property if it has been provided by users if (it != op::SubgraphPropertyOpNameSet::Get()->end()) { - LOG(INFO) << "SubgraphPropertyOpNameSet for subgraph property " << prop_name + LOG(INFO) << "SubgraphPropertyOpNameSet for subgraph property " << backend_name << " has been assigned a value. Please make sure it is initialized" " only for the testing purpose."; op_names_set = it->second; } - std::vector properties = - op::SubgraphPropertyRegistry::Get()->CreateSubgraphProperty(prop_name); - for (auto subgraph_prop : properties) { - if (subgraph_prop->HasAttr("inference_only") && - subgraph_prop->GetAttr("inference_only") == true) { - if (need_grad) { - auto full_name = subgraph_prop->HasAttr("property_name") - ? subgraph_prop->GetAttr("property_name") - : prop_name; - LOG(INFO) << "skip partitioning graph with subgraph property " << full_name - << " as it requires `grad_req=null`."; - continue; - } - } - subgraph_prop->SetAttr("op_names", op_names_set); - const std::vector input_names = ret.ListInputNames(Symbol::kAll); - mxnet::ShapeVector arg_shapes(input_names.size(), mxnet::TShape()); - nnvm::DTypeVector arg_dtypes(input_names.size(), -1); - StorageTypeVector arg_stypes(input_names.size(), kUndefinedStorage); - for (size_t i = 0; i < input_names.size(); ++i) { - const auto& input_name = input_names[i]; - auto it1 = arg_shape_map.find(input_name); - if (arg_shape_map.end() != it1) { - arg_shapes[i] = it1->second; + + const auto& subgraph_prop_list = backend->GetSubgraphProperties(); + for (auto& subgraph_prop : subgraph_prop_list) { + if (SubgraphPropertyCheck(backend_name, subgraph_prop, need_grad, verbose)) { + subgraph_prop->SetAttr("op_names", op_names_set); + const std::vector input_names = ret.ListInputNames(Symbol::kAll); + mxnet::ShapeVector arg_shapes(input_names.size(), mxnet::TShape()); + nnvm::DTypeVector arg_dtypes(input_names.size(), -1); + StorageTypeVector arg_stypes(input_names.size(), kUndefinedStorage); + for (size_t i = 0; i < input_names.size(); ++i) { + const auto& input_name = input_names[i]; + const auto it1 = arg_shape_map.find(input_name); + if (arg_shape_map.end() != it1) { + arg_shapes[i] = it1->second; + } + const auto it2 = arg_dtype_map.find(input_name); + if (arg_dtype_map.end() != it2) { + arg_dtypes[i] = it2->second; + } + const auto it3 = arg_stype_map.find(input_name); + if (arg_stype_map.end() != it3) { + arg_stypes[i] = it3->second; + } } - auto it2 = arg_dtype_map.find(input_name); - if (arg_dtype_map.end() != it2) { - arg_dtypes[i] = it2->second; + ret = BuildSubgraph(ret, subgraph_prop, arg_shapes, arg_dtypes, arg_stypes, default_ctx, + ctx_map, *in_arg_ctxes, *aux_state_ctxes); + // Reorder in_arg_ctxes, arg_grad_ctxes, aux_state_ctxes and grad_req_types according to + // partitioned symbol input sequence + in_arg_ctxes->clear(); + arg_grad_ctxes->clear(); + aux_state_ctxes->clear(); + grad_req_types->clear(); + auto new_arg_names = ret.ListInputNames(nnvm::Symbol::kReadOnlyArgs); + auto new_aux_names = ret.ListInputNames(nnvm::Symbol::kAuxiliaryStates); + for (const auto& arg_name : new_arg_names) { + CHECK(in_arg_ctx_map.count(arg_name)); + in_arg_ctxes->push_back(in_arg_ctx_map[arg_name]); + arg_grad_ctxes->push_back(arg_grad_ctx_map[arg_name]); + grad_req_types->push_back(grad_req_type_map[arg_name]); } - auto it3 = arg_stype_map.find(input_name); - if (arg_stype_map.end() != it3) { - arg_stypes[i] = it3->second; + for (const auto& arg_name : new_aux_names) { + CHECK(aux_state_ctx_map.count(arg_name)); + aux_state_ctxes->push_back(aux_state_ctx_map[arg_name]); } } - ret = BuildSubgraph(ret, subgraph_prop, arg_shapes, arg_dtypes, arg_stypes, default_ctx, - ctx_map, *in_arg_ctxes, *aux_state_ctxes); - // Reorder in_arg_ctxes, arg_grad_ctxes, aux_state_ctxes and grad_req_types according to - // partitioned symbol input sequence - in_arg_ctxes->clear(); - arg_grad_ctxes->clear(); - aux_state_ctxes->clear(); - grad_req_types->clear(); - auto new_arg_names = ret.ListInputNames(nnvm::Symbol::kReadOnlyArgs); - auto new_aux_names = ret.ListInputNames(nnvm::Symbol::kAuxiliaryStates); - for (auto arg_name : new_arg_names) { - CHECK(in_arg_ctx_map.count(arg_name)); - in_arg_ctxes->push_back(in_arg_ctx_map[arg_name]); - arg_grad_ctxes->push_back(arg_grad_ctx_map[arg_name]); - grad_req_types->push_back(grad_req_type_map[arg_name]); - } - for (auto arg_name : new_aux_names) { - CHECK(aux_state_ctx_map.count(arg_name)); - aux_state_ctxes->push_back(aux_state_ctx_map[arg_name]); - } } return ret; } -// Given input ndarrays, partition the graph using the backend name equal to prop_name. +// Given input ndarrays, partition the graph using backend. // This is for bind flow. -static nnvm::Symbol BuildSubgraph(const nnvm::Symbol& src, const std::string& prop_name, +static nnvm::Symbol BuildSubgraph(const nnvm::Symbol& src, const op::SubgraphBackendPtr backend, const Context& default_ctx, const std::map& ctx_map, std::vector* in_args, std::vector* arg_grad_store, std::vector* grad_req_type, - std::vector* aux_states) { + std::vector* aux_states, bool verbose = false) { // setup map for in_args, arg_grad_store, grad_req_type and aux_states std::unordered_map in_args_map; std::unordered_map arg_grad_store_map; @@ -1768,16 +1807,21 @@ static nnvm::Symbol BuildSubgraph(const nnvm::Symbol& src, const std::string& pr const std::vector arg_names = src.ListInputNames(nnvm::Symbol::kReadOnlyArgs); const std::vector aux_names = src.ListInputNames(nnvm::Symbol::kAuxiliaryStates); for (size_t i = 0; i < arg_names.size(); ++i) { - auto name = arg_names[i]; - in_args_map[name] = in_args->at(i); - arg_grad_store_map[name] = arg_grad_store->at(i); - grad_req_type_map[name] = grad_req_type->at(i); + in_args_map[arg_names[i]] = in_args->at(i); } for (size_t i = 0; i < aux_names.size(); ++i) { aux_states_map[aux_names[i]] = aux_states->at(i); } + if (arg_grad_store->size()) { + for (size_t i = 0; i < arg_names.size(); ++i) { + const auto& name = arg_names[i]; + arg_grad_store_map[name] = arg_grad_store->at(i); + grad_req_type_map[name] = grad_req_type->at(i); + } + } + bool need_grad = false; for (OpReqType req : *grad_req_type) { if (req != kNullOp) { @@ -1787,65 +1831,58 @@ static nnvm::Symbol BuildSubgraph(const nnvm::Symbol& src, const std::string& pr } nnvm::Symbol ret = src.Copy(); std::unordered_set op_names_set; - auto it = op::SubgraphPropertyOpNameSet::Get()->find(prop_name); + const auto& backend_name = backend->GetName(); + auto it = op::SubgraphPropertyOpNameSet::Get()->find(backend_name); // assign a op name set to the subgraph property if it has been provided by users if (it != op::SubgraphPropertyOpNameSet::Get()->end()) { - LOG(INFO) << "SubgraphPropertyOpNameSet for subgraph property " << prop_name + LOG(INFO) << "SubgraphPropertyOpNameSet for subgraph property " << backend_name << " has been assigned a value. Please make sure it is initialized" " only for the testing purpose."; op_names_set = it->second; } - std::vector properties = - op::SubgraphPropertyRegistry::Get()->CreateSubgraphProperty(prop_name); - for (auto subgraph_prop : properties) { - if (subgraph_prop->HasAttr("inference_only") && - subgraph_prop->GetAttr("inference_only") == true) { - if (need_grad) { - auto full_name = subgraph_prop->HasAttr("property_name") - ? subgraph_prop->GetAttr("property_name") - : prop_name; - LOG(INFO) << "Skip subgraph " << full_name << " as it requires `grad_req=null`."; - continue; - } - } - subgraph_prop->SetAttr("op_names", op_names_set); - const std::vector input_names = ret.ListInputNames(Symbol::kAll); - const std::vector arg_names = ret.ListInputNames(nnvm::Symbol::kReadOnlyArgs); - const std::vector aux_names = ret.ListInputNames(nnvm::Symbol::kAuxiliaryStates); - CHECK_EQ(arg_names.size(), in_args_map.size()); - CHECK_EQ(aux_names.size(), aux_states_map.size()); - mxnet::ShapeVector arg_shapes; // all input shapes - arg_shapes.reserve(input_names.size()); - nnvm::DTypeVector arg_dtypes; // all input dtypes - arg_dtypes.reserve(input_names.size()); - StorageTypeVector arg_stypes; // all input stypes - arg_stypes.reserve(input_names.size()); - std::vector in_arg_ctxes(in_args_map.size()); - std::vector aux_state_ctxes(aux_states_map.size()); - - size_t i1 = 0, i2 = 0; - for (const auto& input_name : input_names) { - if (i2 < aux_names.size() && aux_names[i2] == input_name) { - const auto &aux_st = aux_states_map[input_name]; - arg_shapes.push_back(aux_st.shape()); - arg_dtypes.push_back(aux_st.dtype()); - arg_stypes.push_back(aux_st.storage_type()); - aux_state_ctxes[i2] = aux_st.ctx(); - ++i2; - } else { - CHECK(i1 < arg_names.size()); - CHECK_EQ(arg_names[i1], input_name); - const auto &in_arg = in_args_map[input_name]; - arg_shapes.push_back(in_arg.shape()); - arg_dtypes.push_back(in_arg.dtype()); - arg_stypes.push_back(in_arg.storage_type()); - in_arg_ctxes[i1] = in_arg.ctx(); - ++i1; + const auto& subgraph_prop_list = backend->GetSubgraphProperties(); + + for (auto subgraph_prop : subgraph_prop_list) { + if (SubgraphPropertyCheck(backend_name, subgraph_prop, need_grad, verbose)) { + subgraph_prop->SetAttr("op_names", op_names_set); + const std::vector input_names = ret.ListInputNames(Symbol::kAll); + const std::vector arg_names = ret.ListInputNames(nnvm::Symbol::kReadOnlyArgs); + const std::vector aux_names = ret.ListInputNames(nnvm::Symbol::kAuxiliaryStates); + CHECK_EQ(arg_names.size(), in_args_map.size()); + CHECK_EQ(aux_names.size(), aux_states_map.size()); + mxnet::ShapeVector arg_shapes; // all input shapes + arg_shapes.reserve(input_names.size()); + nnvm::DTypeVector arg_dtypes; // all input dtypes + arg_dtypes.reserve(input_names.size()); + StorageTypeVector arg_stypes; // all input stypes + arg_stypes.reserve(input_names.size()); + std::vector in_arg_ctxes(in_args_map.size()); + std::vector aux_state_ctxes(aux_states_map.size()); + + size_t i1 = 0, i2 = 0; + for (const auto& input_name : input_names) { + if (i2 < aux_names.size() && aux_names[i2] == input_name) { + const auto &aux_st = aux_states_map[input_name]; + arg_shapes.push_back(aux_st.shape()); + arg_dtypes.push_back(aux_st.dtype()); + arg_stypes.push_back(aux_st.storage_type()); + aux_state_ctxes[i2] = aux_st.ctx(); + ++i2; + } else { + CHECK(i1 < arg_names.size()); + CHECK_EQ(arg_names[i1], input_name); + const auto &in_arg = in_args_map[input_name]; + arg_shapes.push_back(in_arg.shape()); + arg_dtypes.push_back(in_arg.dtype()); + arg_stypes.push_back(in_arg.storage_type()); + in_arg_ctxes[i1] = in_arg.ctx(); + ++i1; + } } - } - ret = BuildSubgraph(ret, subgraph_prop, arg_shapes, arg_dtypes, arg_stypes, default_ctx, - ctx_map, in_arg_ctxes, aux_state_ctxes); + ret = BuildSubgraph(ret, subgraph_prop, arg_shapes, arg_dtypes, arg_stypes, default_ctx, + ctx_map, in_arg_ctxes, aux_state_ctxes); + } } // Reorder in_args, arg_grad_store, grad_req_type and aux_states according to partitioned symbol // input sequence @@ -1854,19 +1891,25 @@ static nnvm::Symbol BuildSubgraph(const nnvm::Symbol& src, const std::string& pr CHECK_EQ(arg_names.size(), new_arg_names.size()); CHECK_EQ(arg_names.size(), new_arg_names.size()); in_args->clear(); - arg_grad_store->clear(); - grad_req_type->clear(); aux_states->clear(); - for (auto arg_name : new_arg_names) { + for (const auto& arg_name : new_arg_names) { CHECK(in_args_map.count(arg_name)); in_args->push_back(in_args_map[arg_name]); - arg_grad_store->push_back(arg_grad_store_map[arg_name]); - grad_req_type->push_back(grad_req_type_map[arg_name]); } - for (auto arg_name : new_aux_names) { + + for (const auto& arg_name : new_aux_names) { CHECK(aux_states_map.count(arg_name)); aux_states->push_back(aux_states_map[arg_name]); } + + if (arg_grad_store->size()) { + arg_grad_store->clear(); + grad_req_type->clear(); + for (const auto& arg_name : new_arg_names) { + arg_grad_store->push_back(arg_grad_store_map[arg_name]); + grad_req_type->push_back(grad_req_type_map[arg_name]); + } + } return ret; } } // namespace exec @@ -1888,18 +1931,68 @@ Executor *Executor::SimpleBind(nnvm::Symbol symbol, std::unordered_map* shared_buffer, Executor* shared_exec) { auto exec = new exec::GraphExecutor(); - std::vector tmp_in_arg_ctxes = in_arg_ctxes; - std::vector tmp_arg_grad_ctxes = arg_grad_ctxes; - std::vector tmp_aux_state_ctxes = aux_state_ctxes; - std::vector tmp_grad_req_types = grad_req_types; + bool init = false; if (!exec->subgraph_property().empty()) { - symbol = exec::BuildSubgraph(symbol, exec->subgraph_property(), arg_shape_map, arg_dtype_map, - arg_stype_map, default_ctx, group2ctx, &tmp_in_arg_ctxes, - &tmp_arg_grad_ctxes, &tmp_grad_req_types, &tmp_aux_state_ctxes); + static bool verbose = dmlc::GetEnv("MXNET_SUBGRAPH_VERBOSE", false); + const auto& backend_name = exec->subgraph_property(); + const auto& backend = op::SubgraphBackendRegistry::Get()->GetSubgraphBackend(backend_name); + if (exec::SubgraphBackendCheck(backend, default_ctx, verbose)) { + LOG(INFO) << "Subgraph backend " << backend_name << " is activated."; + std::vector tmp_in_arg_ctxes = in_arg_ctxes; + std::vector tmp_arg_grad_ctxes = arg_grad_ctxes; + std::vector tmp_aux_state_ctxes = aux_state_ctxes; + std::vector tmp_grad_req_types = grad_req_types; + std::vector tmp_in_args; + std::vector tmp_arg_grads; + std::vector tmp_aux_states; + const auto arg_names = symbol.ListInputNames(nnvm::Symbol::kReadOnlyArgs); + const auto aux_names = symbol.ListInputNames(nnvm::Symbol::kAuxiliaryStates); + symbol = exec::BuildSubgraph(symbol, backend, arg_shape_map, arg_dtype_map, arg_stype_map, + default_ctx, group2ctx, &tmp_in_arg_ctxes, &tmp_arg_grad_ctxes, + &tmp_grad_req_types, &tmp_aux_state_ctxes, verbose); + exec->Init(symbol, default_ctx, group2ctx, tmp_in_arg_ctxes, tmp_arg_grad_ctxes, + tmp_aux_state_ctxes, arg_shape_map, arg_dtype_map, arg_stype_map, + tmp_grad_req_types, shared_arg_names, &tmp_in_args, &tmp_arg_grads, + &tmp_aux_states, shared_buffer, shared_exec); + init = true; + const auto new_arg_names = symbol.ListInputNames(nnvm::Symbol::kReadOnlyArgs); + const auto new_aux_names = symbol.ListInputNames(nnvm::Symbol::kAuxiliaryStates); + std::unordered_map new_arg_names_idx_map; + std::unordered_map new_aux_names_idx_map; + for (size_t i = 0; i != new_arg_names.size(); ++i) { + new_arg_names_idx_map[new_arg_names[i]] = i; + } + for (size_t i = 0; i != new_aux_names.size(); ++i) { + new_aux_names_idx_map[new_aux_names[i]] = i; + } + + in_args->reserve(arg_names.size()); + arg_grads->reserve(arg_names.size()); + for (size_t i = 0; i != arg_names.size(); ++i) { + const auto& arg_name = arg_names[i]; + const auto& it = new_arg_names_idx_map.find(arg_name); + CHECK(it != new_arg_names_idx_map.end()) + << "Subgraph doesn't support remove any input node for now."; + in_args->emplace_back(std::move(tmp_in_args[it->second])); + arg_grads->emplace_back(std::move(tmp_arg_grads[it->second])); + } + + aux_states->reserve(aux_names.size()); + for (size_t i = 0; i != aux_names.size(); ++i) { + const auto& aux_name = aux_names[i]; + const auto& it = new_aux_names_idx_map.find(aux_name); + CHECK(it != new_aux_names_idx_map.end()) + << "Subgraph doesn't support remove any input node for now."; + aux_states->emplace_back(std::move(tmp_aux_states[it->second])); + } + } + } + if (!init) { + // init without subgraph + exec->Init(symbol, default_ctx, group2ctx, in_arg_ctxes, arg_grad_ctxes, aux_state_ctxes, + arg_shape_map, arg_dtype_map, arg_stype_map, grad_req_types, shared_arg_names, + in_args, arg_grads, aux_states, shared_buffer, shared_exec); } - exec->Init(symbol, default_ctx, group2ctx, tmp_in_arg_ctxes, tmp_arg_grad_ctxes, - tmp_aux_state_ctxes, arg_shape_map, arg_dtype_map, arg_stype_map, tmp_grad_req_types, - shared_arg_names, in_args, arg_grads, aux_states, shared_buffer, shared_exec); return exec; } @@ -1912,15 +2005,21 @@ Executor *Executor::Bind(nnvm::Symbol symbol, const std::vector &aux_states, Executor* shared_exec) { auto exec = new exec::GraphExecutor(); + static bool verbose = dmlc::GetEnv("MXNET_SUBGRAPH_VERBOSE", false); std::vector tmp_in_args = in_args; std::vector tmp_arg_grad_store = arg_grad_store; std::vector tmp_grad_req_type = grad_req_type; std::vector tmp_aux_states = aux_states; if (!exec->subgraph_property().empty()) { - symbol = - exec::BuildSubgraph(symbol, exec->subgraph_property(), default_ctx, group2ctx, &tmp_in_args, - &tmp_arg_grad_store, &tmp_grad_req_type, &tmp_aux_states); + const auto& backend_name = exec->subgraph_property(); + const auto& backend = op::SubgraphBackendRegistry::Get()->GetSubgraphBackend(backend_name); + if (exec::SubgraphBackendCheck(backend, default_ctx, verbose)) { + LOG(INFO) << "Subgraph backend " << backend_name << " is activated."; + symbol = exec::BuildSubgraph(symbol, backend, default_ctx, group2ctx, &tmp_in_args, + &tmp_arg_grad_store, &tmp_grad_req_type, &tmp_aux_states, + verbose); + } } exec->Init(symbol, default_ctx, group2ctx, tmp_in_args, tmp_arg_grad_store, tmp_grad_req_type, tmp_aux_states, reinterpret_cast(shared_exec)); diff --git a/src/operator/subgraph/build_subgraph.cc b/src/operator/subgraph/build_subgraph.cc index e0fb615a7ac0..0420e2248077 100644 --- a/src/operator/subgraph/build_subgraph.cc +++ b/src/operator/subgraph/build_subgraph.cc @@ -297,8 +297,11 @@ void PreSelectSubgraphNodes(const nnvm::Graph& g, SubgraphSelectorV2Ptr subgraph for (auto node : excluded_nodes) { excluded_node_names += node->node->attrs.name + ", "; } - LOG(INFO) << "Found a cycle when BFS from node " << simple_nodes[snid]->node->attrs.name - << ". Excluding nodes " << excluded_node_names << "and retrying"; + static bool verbose = dmlc::GetEnv("MXNET_SUBGRAPH_VERBOSE", false); + if (verbose) { + LOG(INFO) << "Found a cycle when BFS from node " << simple_nodes[snid]->node->attrs.name + << ". Excluding nodes " << excluded_node_names << "and retrying"; + } subgraph_selector->Reset(); } ++count; @@ -673,17 +676,23 @@ void TopSortEntries(const nnvm::Graph& g, } nnvm::Graph BuildSubgraph(nnvm::Graph&& g) { + static bool verbose = dmlc::GetEnv("MXNET_SUBGRAPH_VERBOSE", false); if (!g.HasAttr("subgraph_property")) { // treat the whole graph as a subgraph - LOG(INFO) << "The graph has no attribute of subgraph_property attached. " - "The original graph is returned."; + if (verbose) { + LOG(INFO) << "The graph has no attribute of subgraph_property attached. " + "The original graph is returned."; + } return g; } using namespace sg; + const SubgraphPropertyPtr& subg_prop = g.GetAttr("subgraph_property"); - const std::string& prop_name = subg_prop->HasAttr("property_name") - ? subg_prop->GetAttr("property_name") - : "partition graph"; - LOG(INFO) << "start to execute " << prop_name << "."; + if (verbose) { + const std::string& prop_name = subg_prop->HasAttr("property_name") + ? subg_prop->GetAttr("property_name") + : "partition graph"; + LOG(INFO) << "start to execute " << prop_name << "."; + } // top sort NodeEntry of all the nodes' inputs std::unordered_map entry_top_order_map; TopSortEntries(g, &entry_top_order_map); diff --git a/src/operator/subgraph/default_subgraph_property.cc b/src/operator/subgraph/default_subgraph_property.cc index 5a2c52e61729..246b2945ecec 100644 --- a/src/operator/subgraph/default_subgraph_property.cc +++ b/src/operator/subgraph/default_subgraph_property.cc @@ -73,6 +73,7 @@ class DefaultSubgraphProperty: public SubgraphProperty { } }; +MXNET_REGISTER_SUBGRAPH_BACKEND(default); MXNET_REGISTER_SUBGRAPH_PROPERTY(default, DefaultSubgraphProperty); } // namespace op diff --git a/src/operator/subgraph/default_subgraph_property_v2.cc b/src/operator/subgraph/default_subgraph_property_v2.cc index bf8ccfe5ba6f..c8cc3b144890 100644 --- a/src/operator/subgraph/default_subgraph_property_v2.cc +++ b/src/operator/subgraph/default_subgraph_property_v2.cc @@ -78,6 +78,8 @@ class DefaultSubgraphProperty: public SubgraphProperty { } }; +MXNET_REGISTER_SUBGRAPH_BACKEND(default_v2); + MXNET_REGISTER_SUBGRAPH_PROPERTY(default_v2, DefaultSubgraphProperty); } // namespace op diff --git a/src/operator/subgraph/mkldnn/mkldnn_conv_property.h b/src/operator/subgraph/mkldnn/mkldnn_conv_property.h index d7a237e08c87..bf278ab75718 100644 --- a/src/operator/subgraph/mkldnn/mkldnn_conv_property.h +++ b/src/operator/subgraph/mkldnn/mkldnn_conv_property.h @@ -24,6 +24,7 @@ #include #include #include "../../nn/activation-inl.h" +#include "../../nn/convolution-inl.h" #include "../../nn/mkldnn/mkldnn_ops-inl.h" #include "../../tensor/matrix_op-inl.h" #include "../common.h" @@ -62,10 +63,13 @@ class SgMKLDNNConvSelector : public SubgraphSelector { bool Select(const nnvm::Node &n) override { if (n.op() && n.op()->name == "Convolution") { - status_ = disable_all_ ? kSuccess : kStart; - matched_list_.clear(); - matched_list_.push_back(&n); - return true; + const auto ¶m = nnvm::get(n.attrs.parsed); + if (param.kernel.ndim() == 2) { + status_ = disable_all_ ? kSuccess : kStart; + matched_list_.clear(); + matched_list_.push_back(&n); + return true; + } } return false; } @@ -224,7 +228,7 @@ class SgMKLDNNConvProperty : public SubgraphProperty { } SubgraphSelectorPtr CreateSubgraphSelector() const override { - int quantize = HasAttr("quantize") ? GetAttr("quantize") : 0; + bool quantize = HasAttr("quantize") ? GetAttr("quantize") : false; auto selector = std::make_shared( disable_all_, disable_conv_bn_, disable_conv_act_, disable_conv_sum_, quantize); return selector; diff --git a/src/operator/subgraph/mkldnn/mkldnn_subgraph_property.cc b/src/operator/subgraph/mkldnn/mkldnn_subgraph_property.cc index 7fbc859cc8d1..d0d2b51918b1 100644 --- a/src/operator/subgraph/mkldnn/mkldnn_subgraph_property.cc +++ b/src/operator/subgraph/mkldnn/mkldnn_subgraph_property.cc @@ -28,10 +28,18 @@ namespace mxnet { namespace op { +MXNET_REGISTER_SUBGRAPH_BACKEND(MKLDNN) +.set_attr("enable", MKLDNNEnvSet()) +.set_attr("context", Context::CPU()); + MXNET_REGISTER_SUBGRAPH_PROPERTY(MKLDNN, SgMKLDNNConvProperty); MXNET_REGISTER_SUBGRAPH_PROPERTY(MKLDNN, SgMKLDNNFCProperty); + +MXNET_REGISTER_SUBGRAPH_BACKEND(MKLDNN_QUANTIZE) +.set_attr("context", Context::CPU()); + MXNET_REGISTER_SUBGRAPH_PROPERTY(MKLDNN_QUANTIZE, SgMKLDNNConvProperty) .set_attr("quantize", true); diff --git a/src/operator/subgraph/subgraph_property.h b/src/operator/subgraph/subgraph_property.h index 460055f9ed86..0683e5ed65f1 100644 --- a/src/operator/subgraph/subgraph_property.h +++ b/src/operator/subgraph/subgraph_property.h @@ -345,8 +345,10 @@ using SubgraphPropertyPtr = std::shared_ptr; class SubgraphPropertyEntry { public: explicit SubgraphPropertyEntry(std::shared_ptr entry) : entry_(entry) {} - SubgraphPropertyEntry set_attr(const std::string& name, const int value) const { - entry_->SetAttr(name, value); + + template + SubgraphPropertyEntry set_attr(const std::string& name, const T value) const { + entry_->SetAttr(name, value); return *this; } @@ -354,31 +356,102 @@ class SubgraphPropertyEntry { std::shared_ptr entry_; }; -class SubgraphPropertyRegistry { +class SubgraphBackend { + public: + explicit SubgraphBackend(std::string name) : name_(name) {} + /*! + * \brief Set an attr with name in the attr map. + */ + template + SubgraphBackend& SetAttr(const std::string& name, const T& value) { + attrs_[name] = std::make_shared(value); + return *this; + } + /*! + * \brief Get the attr with the name. + */ + template + const T& GetAttr(const std::string& name) const { + auto it = attrs_.find(name); + CHECK(it != attrs_.end()) << "Cannot find attribute " << name << " in SubgraphProperty"; + return nnvm::get(*it->second); + } + /*! + * \brief Check if the attr exists. + */ + bool HasAttr(const std::string& name) const { + auto it = attrs_.find(name); + return it != attrs_.end(); + } + + SubgraphPropertyPtr& RegisterSubgraphProperty(const SubgraphPropertyPtr prop) { + prop_ptr_.push_back(prop); + return prop_ptr_.back(); + } + + const std::string& GetName() const { return name_; } + + const std::vector& GetSubgraphProperties() const { return prop_ptr_; } + + private: + const std::string name_; + std::unordered_map> attrs_; + std::vector prop_ptr_; +}; + +using SubgraphBackendPtr = std::shared_ptr; + +class SubgraphBackendEntry { public: + explicit SubgraphBackendEntry(SubgraphBackendPtr entry) : entry_(entry) {} + + template + SubgraphBackendEntry set_attr(const std::string& name, const T value) const { + entry_->SetAttr(name, value); + return *this; + } + + private: + SubgraphBackendPtr entry_; +}; + +class SubgraphBackendRegistry { typedef SubgraphPropertyPtr (*SubgraphPropertyCreateFn)(void); - static SubgraphPropertyRegistry* Get() { - static SubgraphPropertyRegistry inst; + + public: + static SubgraphBackendRegistry* Get() { + static SubgraphBackendRegistry inst; return &inst; } - std::vector CreateSubgraphProperty(const std::string& name) { - auto it = prop_ptr_map_.find(name); - CHECK(it != prop_ptr_map_.end()) << "SubgraphProperty " << name - << " is not found in SubgraphPropertyRegistry"; + SubgraphBackendPtr& GetSubgraphBackend(const std::string& name) { + auto it = backend_map_.find(name); + CHECK(it != backend_map_.end()) << "SubgraphProperty " << name + << " is not found in SubgraphBackendRegistry"; return it->second; } - SubgraphPropertyEntry __REGISTER__(const std::string& name, SubgraphPropertyCreateFn fn) { - prop_ptr_map_[name].emplace_back(fn()); - return SubgraphPropertyEntry(prop_ptr_map_[name].back()); + SubgraphBackendEntry __REGISTER_BACKEND__(const std::string& name) { + auto it = backend_map_.find(name); + CHECK(it == backend_map_.end()) << "Subgraph backend " << name << " is already registered"; + backend_map_[name] = std::make_shared(name); + return SubgraphBackendEntry(backend_map_[name]); + } + + SubgraphPropertyEntry __REGISTER_PROPERTY__(const std::string& name, + SubgraphPropertyCreateFn fn) { + auto it = backend_map_.find(name); + CHECK(it != backend_map_.end()) + << "Subgraph backend " << name << " is not found in SubgraphBackendRegistry"; + auto prop = it->second->RegisterSubgraphProperty(fn()); + return SubgraphPropertyEntry(prop); } - SubgraphPropertyRegistry() = default; - SubgraphPropertyRegistry(const SubgraphPropertyRegistry&) = delete; - SubgraphPropertyRegistry(SubgraphPropertyRegistry&&) = delete; - SubgraphPropertyRegistry& operator=(const SubgraphPropertyRegistry&) = delete; - std::unordered_map> prop_ptr_map_; + SubgraphBackendRegistry() = default; + SubgraphBackendRegistry(const SubgraphBackendRegistry&) = delete; + SubgraphBackendRegistry(SubgraphBackendRegistry&&) = delete; + SubgraphBackendRegistry& operator=(const SubgraphBackendRegistry&) = delete; + std::unordered_map backend_map_; }; // This op name set is for setting the names of operators that should be grouped into @@ -395,7 +468,13 @@ typedef dmlc::ThreadLocalStore__REGISTER__(#Name, &SubgraphPropertyType::Create) + SubgraphBackendRegistry::Get()->__REGISTER_PROPERTY__(#Name, &SubgraphPropertyType::Create) + +#define DECLARE_BACKEND(Name) \ + static const DMLC_ATTRIBUTE_UNUSED auto __make_##Name##__ + +#define MXNET_REGISTER_SUBGRAPH_BACKEND(Name) \ + DECLARE_BACKEND(Name) = SubgraphBackendRegistry::Get()->__REGISTER_BACKEND__(#Name) } // namespace op } // namespace mxnet diff --git a/src/operator/subgraph/tensorrt/tensorrt.cc b/src/operator/subgraph/tensorrt/tensorrt.cc index 30fcee007cfc..eac4ba7fc6fc 100644 --- a/src/operator/subgraph/tensorrt/tensorrt.cc +++ b/src/operator/subgraph/tensorrt/tensorrt.cc @@ -329,6 +329,8 @@ NNVM_REGISTER_OP(_TensorRT) .set_attr("FCreateOpState", TRTCreateState) .set_attr("FInferStorageType", TRTInferStorageType); +MXNET_REGISTER_SUBGRAPH_BACKEND(TensorRT); + MXNET_REGISTER_SUBGRAPH_PROPERTY(TensorRT, TensorrtProperty); } // namespace op } // namespace mxnet diff --git a/tests/python/mkl/test_quantization_mkldnn.py b/tests/python/mkl/test_quantization_mkldnn.py index 290f1a195c24..3c8cee465ec5 100644 --- a/tests/python/mkl/test_quantization_mkldnn.py +++ b/tests/python/mkl/test_quantization_mkldnn.py @@ -19,6 +19,7 @@ import mxnet as mx os.environ['ENABLE_MKLDNN_QUANTIZATION_TEST'] = '1' +os.environ['MXNET_SUBGRAPH_BACKEND'] = 'NONE' curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) sys.path.insert(0, os.path.join(curr_path, '../quantization')) from test_quantization import * @@ -26,3 +27,6 @@ if __name__ == '__main__': import nose nose.runmodule() + +del os.environ['ENABLE_MKLDNN_QUANTIZATION_TEST'] +del os.environ['MXNET_SUBGRAPH_BACKEND'] diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index d195ea9ef2f3..49a3475638c0 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -1227,8 +1227,8 @@ def test_rsqrt_cos_sin(): @with_seed() def test_maximum_minimum(): - data1 = mx.symbol.Variable('data') - data2 = mx.symbol.Variable('data') + data1 = mx.symbol.Variable('data1') + data2 = mx.symbol.Variable('data2') shape = (3, 4) data_tmp1 = np.random.rand(3,4) data_tmp2 = np.random.rand(3,4) @@ -1241,7 +1241,7 @@ def test_maximum_minimum(): arr_grad1 = mx.nd.empty(shape) arr_grad2 = mx.nd.empty(shape) - test = mx.sym.maximum(data1,data2) + mx.sym.minimum(data1,data2); + test = mx.sym.maximum(data1,data2) + mx.sym.minimum(data1,data2) exe_test = test.bind(default_context(), args=[arr_data1,arr_data2], args_grad=[arr_grad1,arr_grad2]) exe_test.forward(is_train=True) out = exe_test.outputs[0].asnumpy() @@ -2028,9 +2028,9 @@ def test_convolution_independent_gradients(): x = mx.sym.Variable('x') w = mx.sym.Variable('w') b = mx.sym.Variable('b') if not no_bias else None - conv = mx.sym.Convolution(x, w, b, num_filter=num_filter, + conv = mx.sym.Convolution(x, w, b, num_filter=num_filter, kernel=kernel, stride=stride, pad=pad, no_bias=no_bias) - + for req_kind in reqs: # Binding args for conv with possible dependent gradients base_args = { @@ -2049,7 +2049,7 @@ def test_convolution_independent_gradients(): exe1 = conv.bind(ctx, args1, args_grad=grad1, grad_req=grad_req1) exe1.forward(is_train=True) exe1.backward(exe1.outputs[0]) - + for x_req, w_req, b_req in itertools.product(reqs, repeat=3): # Binding args for conv with independent gradients args2 = copy.deepcopy(base_args) # Deepcopy the same params of `exe1` @@ -2059,11 +2059,11 @@ def test_convolution_independent_gradients(): 'b': mx.nd.zeros(shape=(num_filter, )) if not no_bias else None} grad_req2 = {"x": x_req, "w": w_req, "b": b_req} exe2 = conv.bind(ctx, args2, args_grad=grad2, grad_req=grad_req2) - + exe2.forward(is_train=True) np.testing.assert_allclose(exe1.outputs[0].asnumpy(), exe2.outputs[0].asnumpy(), rtol=rtol, atol=atol) - + exe2.backward(exe2.outputs[0]) for var_name in var_names: if var_name == "b" and no_bias: @@ -3960,8 +3960,8 @@ def mathematical_core_binary(name, data1_init=2., data2_init=3., grad_init=2.): - data1 = mx.symbol.Variable('data') - data2 = mx.symbol.Variable('data') + data1 = mx.symbol.Variable('data1') + data2 = mx.symbol.Variable('data2') shape = (3, 4) data_tmp1 = np.random.rand(3, 4) data_tmp2 = np.random.rand(3, 4) @@ -7995,39 +7995,46 @@ def get_output_names_callback(name, arr): pass for output_name, expected_name in zip(output_names, expected_names): assert output_name == expected_name + is_windows = sys.platform.startswith('win') + if (is_windows): + # Windows doesn't support set environment variable on the fly, so disable it for now + pass + else: + # Disable subgraph in case subgraph will replace symbol + os.environ['MXNET_SUBGRAPH_BACKEND'] = "NONE" + data = mx.sym.Variable('data', shape=(10, 3, 10, 10)) + conv_sym = mx.sym.Convolution(data, kernel=(2, 2), num_filter=1, name='conv') + check_name(conv_sym, ['conv_output']) - data = mx.sym.Variable('data', shape=(10, 3, 10, 10)) - conv_sym = mx.sym.Convolution(data, kernel=(2, 2), num_filter=1, name='conv') - check_name(conv_sym, ['conv_output']) - - deconv_sym = mx.sym.Deconvolution(data, kernel=(2, 2), num_filter=1, name='deconv') - check_name(deconv_sym, ['deconv_output']) + deconv_sym = mx.sym.Deconvolution(data, kernel=(2, 2), num_filter=1, name='deconv') + check_name(deconv_sym, ['deconv_output']) - fc_sym = mx.sym.FullyConnected(data, num_hidden=10, name='fc') - check_name(fc_sym, ['fc_output']) + fc_sym = mx.sym.FullyConnected(data, num_hidden=10, name='fc') + check_name(fc_sym, ['fc_output']) - lrn_sym = mx.sym.LRN(data, nsize=1, name='lrn') - check_name(lrn_sym, ['lrn_output', 'lrn_tmp_norm']) + lrn_sym = mx.sym.LRN(data, nsize=1, name='lrn') + check_name(lrn_sym, ['lrn_output', 'lrn_tmp_norm']) - act_sym = mx.sym.Activation(data, act_type='relu', name='act') - check_name(act_sym, ['act_output']) + act_sym = mx.sym.Activation(data, act_type='relu', name='act') + check_name(act_sym, ['act_output']) - cc_sym = mx.sym.concat(data, data, dim=0, name='concat') - check_name(cc_sym, ['concat_output']) + cc_sym = mx.sym.concat(data, data, dim=0, name='concat') + check_name(cc_sym, ['concat_output']) - sm_sym = mx.sym.softmax(data, name='softmax') - check_name(sm_sym, ['softmax_output']) + sm_sym = mx.sym.softmax(data, name='softmax') + check_name(sm_sym, ['softmax_output']) - sa_sym = mx.sym.SoftmaxActivation(data, name='softmax') - check_name(sa_sym, ['softmax_output']) + sa_sym = mx.sym.SoftmaxActivation(data, name='softmax') + check_name(sa_sym, ['softmax_output']) - us_sym = mx.sym.UpSampling(data, scale=2, sample_type='nearest', - name='upsampling') - check_name(us_sym, ['upsampling_output']) + us_sym = mx.sym.UpSampling(data, scale=2, sample_type='nearest', + name='upsampling') + check_name(us_sym, ['upsampling_output']) - us_sym = mx.sym.Pooling(data, kernel=(2, 2), pool_type='avg', - name='pooling') - check_name(us_sym, ['pooling_output']) + us_sym = mx.sym.Pooling(data, kernel=(2, 2), pool_type='avg', + name='pooling') + check_name(us_sym, ['pooling_output']) + del os.environ['MXNET_SUBGRAPH_BACKEND'] def test_op_all_names_monitor(): def check_name(op_sym, expected_names): @@ -8046,43 +8053,51 @@ def get_output_names_callback(name, arr): pass for output_name, expected_name in zip(output_names, expected_names): assert output_name == expected_name + is_windows = sys.platform.startswith('win') + if (is_windows): + # Windows doesn't support set environment variable on the fly, so disable it for now + pass + else: + # Disable subgraph in case subgraph will replace symbol + os.environ['MXNET_SUBGRAPH_BACKEND'] = "NONE" - data = mx.sym.Variable('data', shape=(10, 3, 10, 10)) - conv_sym = mx.sym.Convolution(data, kernel=(2, 2), num_filter=1, name='conv') - check_name(conv_sym, ['data', 'conv_data', 'conv_weight', 'conv_weight', 'conv_bias', 'conv_bias', 'conv_output']) + data = mx.sym.Variable('data', shape=(10, 3, 10, 10)) + conv_sym = mx.sym.Convolution(data, kernel=(2, 2), num_filter=1, name='conv') + check_name(conv_sym, ['data', 'conv_data', 'conv_weight', 'conv_weight', 'conv_bias', 'conv_bias', 'conv_output']) - deconv_sym = mx.sym.Deconvolution(data, kernel=(2, 2), num_filter=1, name='deconv') - check_name(deconv_sym, ['data', 'deconv_data', 'deconv_weight', 'deconv_weight', 'deconv_output']) + deconv_sym = mx.sym.Deconvolution(data, kernel=(2, 2), num_filter=1, name='deconv') + check_name(deconv_sym, ['data', 'deconv_data', 'deconv_weight', 'deconv_weight', 'deconv_output']) - fc_sym = mx.sym.FullyConnected(data, num_hidden=10, name='fc') - check_name(fc_sym, ['data', 'fc_data', 'fc_weight', 'fc_weight', 'fc_bias', 'fc_bias', 'fc_output']) + fc_sym = mx.sym.FullyConnected(data, num_hidden=10, name='fc') + check_name(fc_sym, ['data', 'fc_data', 'fc_weight', 'fc_weight', 'fc_bias', 'fc_bias', 'fc_output']) - lrn_sym = mx.sym.LRN(data, nsize=1, name='lrn') - check_name(lrn_sym, ['data', 'lrn_data', 'lrn_output', 'lrn_tmp_norm']) + lrn_sym = mx.sym.LRN(data, nsize=1, name='lrn') + check_name(lrn_sym, ['data', 'lrn_data', 'lrn_output', 'lrn_tmp_norm']) - act_sym = mx.sym.Activation(data, act_type='relu', name='act') - check_name(act_sym, ['data', 'act_input0', 'act_output']) + act_sym = mx.sym.Activation(data, act_type='relu', name='act') + check_name(act_sym, ['data', 'act_input0', 'act_output']) - cc_sym = mx.sym.concat(data, data, dim=0, name='concat') - check_name(cc_sym, ['data', 'concat_arg0', 'data', 'concat_arg1', 'concat_output']) + cc_sym = mx.sym.concat(data, data, dim=0, name='concat') + check_name(cc_sym, ['data', 'concat_arg0', 'data', 'concat_arg1', 'concat_output']) - sm_sym = mx.sym.softmax(data, name='softmax') - check_name(sm_sym, ['data', 'softmax_data', 'softmax_output']) + sm_sym = mx.sym.softmax(data, name='softmax') + check_name(sm_sym, ['data', 'softmax_data', 'softmax_output']) - length = mx.sym.Variable("length", shape=(10, 10, 10)) - sm_sym = mx.sym.softmax(data, length, axis=1, use_length=True, name='softmax') - check_name(sm_sym, ['data', 'softmax_data', 'length', 'softmax_length', 'softmax_output']) + length = mx.sym.Variable("length", shape=(10, 10, 10)) + sm_sym = mx.sym.softmax(data, length, axis=1, use_length=True, name='softmax') + check_name(sm_sym, ['data', 'softmax_data', 'length', 'softmax_length', 'softmax_output']) - sa_sym = mx.sym.SoftmaxActivation(data, name='softmax') - check_name(sa_sym, ['data', 'softmax_input0', 'softmax_output']) + sa_sym = mx.sym.SoftmaxActivation(data, name='softmax') + check_name(sa_sym, ['data', 'softmax_input0', 'softmax_output']) - us_sym = mx.sym.UpSampling(data, scale=2, sample_type='nearest', - name='upsampling') - check_name(us_sym, ['data', 'upsampling_arg0', 'upsampling_output']) + us_sym = mx.sym.UpSampling(data, scale=2, sample_type='nearest', + name='upsampling') + check_name(us_sym, ['data', 'upsampling_arg0', 'upsampling_output']) - us_sym = mx.sym.Pooling(data, kernel=(2, 2), pool_type='avg', - name='pooling') - check_name(us_sym, ['data', 'pooling_data', 'pooling_output']) + us_sym = mx.sym.Pooling(data, kernel=(2, 2), pool_type='avg', + name='pooling') + check_name(us_sym, ['data', 'pooling_data', 'pooling_output']) + del os.environ['MXNET_SUBGRAPH_BACKEND'] @with_seed() @unittest.skip("test fails intermittently. temporarily disabled till it gets fixed. tracked at https://github.com/apache/incubator-mxnet/issues/13915") From 5e6ba7b9812d1cf76b82c291264f794365c9563b Mon Sep 17 00:00:00 2001 From: Zixuan Wei Date: Fri, 26 Jul 2019 16:34:24 +0800 Subject: [PATCH 111/813] [Flaky test] Skip test_operator_gpu.test_convolution_independent_gradients (#15631) * Skip test_convolution_independent_gradirents * Add an issue link * Fix inconsistent context of input array and binding op * Trigger CI * Retrigger CI --- tests/python/unittest/test_operator.py | 28 ++++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index 49a3475638c0..e8c9d6cbd061 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -2001,10 +2001,12 @@ def test_depthwise_convolution(): @with_seed() def test_convolution_independent_gradients(): - ctx = default_context() - # set a low bar for autotuned cudnn conv - atol = 1.0e-1 if ctx.device_type == "gpu" else 1.0e-3 - rtol = 1.0e-2 if ctx.device_type == "gpu" else 1.0e-3 + # NOTE(zixuanweeei): Flaky test tracked by https://github.com/apache/incubator-mxnet/issues/15603. + # GPU context will be enabled after figuring out the possible issue tracked at + # https://github.com/apache/incubator-mxnet/issues/15638. + ctx = mx.cpu() + atol = 1.0e-3 + rtol = 1.0e-3 reqs = ["null", "write", "add"] var_names = ["x", "w", "b"] dims = [1, 2] @@ -2034,14 +2036,14 @@ def test_convolution_independent_gradients(): for req_kind in reqs: # Binding args for conv with possible dependent gradients base_args = { - 'x': mx.nd.random.normal(shape=x_shape), - 'w': mx.nd.random.normal(shape=w_shape), - 'b': mx.nd.random.normal(shape=(num_filter, )) if not no_bias else None} + 'x': mx.nd.random.normal(shape=x_shape, ctx=ctx), + 'w': mx.nd.random.normal(shape=w_shape, ctx=ctx), + 'b': mx.nd.random.normal(shape=(num_filter, ), ctx=ctx) if not no_bias else None} args1 = copy.deepcopy(base_args) grad1 = { - 'x': mx.nd.zeros(shape=x_shape), - 'w': mx.nd.zeros(shape=w_shape), - 'b': mx.nd.zeros(shape=(num_filter, )) if not no_bias else None} + 'x': mx.nd.zeros(shape=x_shape, ctx=ctx), + 'w': mx.nd.zeros(shape=w_shape, ctx=ctx), + 'b': mx.nd.zeros(shape=(num_filter, ), ctx=ctx) if not no_bias else None} grad_req1 = [req_kind] * 3 grad_req1 = dict(zip(var_names, grad_req1)) @@ -2054,9 +2056,9 @@ def test_convolution_independent_gradients(): # Binding args for conv with independent gradients args2 = copy.deepcopy(base_args) # Deepcopy the same params of `exe1` grad2 = { - 'x': mx.nd.zeros(shape=x_shape), - 'w': mx.nd.zeros(shape=w_shape), - 'b': mx.nd.zeros(shape=(num_filter, )) if not no_bias else None} + 'x': mx.nd.zeros(shape=x_shape, ctx=ctx), + 'w': mx.nd.zeros(shape=w_shape, ctx=ctx), + 'b': mx.nd.zeros(shape=(num_filter, ), ctx=ctx) if not no_bias else None} grad_req2 = {"x": x_req, "w": w_req, "b": b_req} exe2 = conv.bind(ctx, args2, args_grad=grad2, grad_req=grad_req2) From c310763e0c54180279cda48a89f5ab4488ad4514 Mon Sep 17 00:00:00 2001 From: Zhaoqi Zhu Date: Fri, 26 Jul 2019 08:53:36 -0700 Subject: [PATCH 112/813] update profiler tutorial (#15580) * update profiler tutorial * Update profiler.md * Update profiler.md * Update profiler.md * Re-Trigger build * Re-Trigger build * Re-Trigger build * Update profiler.md --- docs/tutorials/python/profiler.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/tutorials/python/profiler.md b/docs/tutorials/python/profiler.md index 5be6bf83ac5d..91a74e4f49cf 100644 --- a/docs/tutorials/python/profiler.md +++ b/docs/tutorials/python/profiler.md @@ -195,10 +195,10 @@ print(profiler.dumps()) You can also dump the information collected by the profiler into a `json` file using the `profiler.dump()` function and view it in a browser. ```python -profiler.dump() +profiler.dump(finished=False) ``` -`dump()` creates a `json` file which can be viewed using a trace consumer like `chrome://tracing` in the Chrome browser. Here is a snapshot that shows the output of the profiling we did above. +`dump()` creates a `json` file which can be viewed using a trace consumer like `chrome://tracing` in the Chrome browser. Here is a snapshot that shows the output of the profiling we did above. Note that setting the `finished` parameter to `False` will prevent the profiler from finishing dumping to file. If you just use `profiler.dump()`, you will no longer be able to profile the remaining sections of your model. ![Tracing Screenshot](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/tutorials/python/profiler/profiler_output_chrome.png) @@ -214,11 +214,6 @@ Should the existing NDArray operators fail to meet all your model's needs, MXNet Let's try profiling custom operators with the following code example: ```python - -import mxnet as mx -from mxnet import nd -from mxnet import profiler - class MyAddOne(mx.operator.CustomOp): def forward(self, is_train, req, in_data, out_data, aux): self.assign(out_data[0], req[0], in_data[0]+1) @@ -246,7 +241,8 @@ class CustomAddOneProp(mx.operator.CustomOpProp): inp = mx.nd.zeros(shape=(500, 500)) -profiler.set_config(profile_all=True, continuous_dump = True) +profiler.set_config(profile_all=True, continuous_dump=True, \ + aggregate_stats=True) profiler.set_state('run') w = nd.Custom(inp, op_type="MyAddOne") @@ -254,7 +250,8 @@ w = nd.Custom(inp, op_type="MyAddOne") mx.nd.waitall() profiler.set_state('stop') -profiler.dump() +print(profiler.dumps()) +profiler.dump(finished=False) ``` Here, we have created a custom operator called `MyAddOne`, and within its `forward()` function, we simply add one to the input. We can visualize the dump file in `chrome://tracing/`: @@ -267,10 +264,10 @@ Please note that: to be able to see the previously described information, you ne ```python # Set profile_all to True -profiler.set_config(profile_all=True, aggregate_stats=True, continuous_dump = True) +profiler.set_config(profile_all=True, aggregate_stats=True, continuous_dump=True) # OR, Explicitly Set profile_symbolic and profile_imperative to True -profiler.set_config(profile_symbolic = True, profile_imperative = True, \ - aggregate_stats=True, continuous_dump = True) +profiler.set_config(profile_symbolic=True, profile_imperative=True, \ + aggregate_stats=True, continuous_dump=True) profiler.set_state('run') # Use Symbolic Mode @@ -280,9 +277,15 @@ c = b.bind(mx.cpu(), {'a': inp}) y = c.forward() mx.nd.waitall() profiler.set_state('stop') +print(profiler.dumps()) profiler.dump() ``` +### Some Rules to Pay Attention to +1. Always use `profiler.dump(finished=False)` if you do not intend to finish dumping to file. Otherwise, calling `profiler.dump()` in the middle of your model may lead to unexpected behaviors; and if you subsequently call `profiler.set_config()`, the program will error out. + +2. You can only dump to one file. Do not change the target file by calling `profiler.set_config(filename='new_name.json')` in the middle of your model. This will lead to incomplete dump outputs. + ## Advanced: Using NVIDIA Profiling Tools MXNet's Profiler is the recommended starting point for profiling MXNet code, but NVIDIA also provides a couple of tools for low-level profiling of CUDA code: [NVProf](https://devblogs.nvidia.com/cuda-pro-tip-nvprof-your-handy-universal-gpu-profiler/), [Visual Profiler](https://developer.nvidia.com/nvidia-visual-profiler) and [Nsight Compute](https://developer.nvidia.com/nsight-compute). You can use these tools to profile all kinds of executables, so they can be used for profiling Python scripts running MXNet. And you can use these in conjunction with the MXNet Profiler to see high-level information from MXNet alongside the low-level CUDA kernel information. From 7bc3db8e8e2daa2647fb2229a788b935e2a6fe17 Mon Sep 17 00:00:00 2001 From: Zach Kimberg Date: Fri, 26 Jul 2019 14:06:24 -0700 Subject: [PATCH 113/813] Bump Scala version to 1.6 (#15660) Fix API deprecation versions --- .../src/main/scala/org/apache/mxnet/NDArray.scala | 2 +- .../scala/org/apache/mxnet/NDArrayCollector.scala | 12 ++++++------ scala-package/pom.xml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala b/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala index d55ed961d7ea..717120bcf984 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala @@ -756,7 +756,7 @@ object NDArray extends NDArrayBase { class NDArray private[mxnet](private[mxnet] val handle: NDArrayHandle, val writable: Boolean) extends NativeResource { - @deprecated("Please use ResourceScope instead", "1.6.0") + @deprecated("Please use ResourceScope instead", "1.5.0") def this(handle: NDArrayHandle, writable: Boolean = true, addToCollector: Boolean = true) { diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/NDArrayCollector.scala b/scala-package/core/src/main/scala/org/apache/mxnet/NDArrayCollector.scala index 5fa0aa5303b6..0761481cdfe8 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/NDArrayCollector.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/NDArrayCollector.scala @@ -64,7 +64,7 @@ import scala.collection.mutable * }); * */ -@deprecated("Please use ResourceScope instead", "1.6.0") +@deprecated("Please use ResourceScope instead", "1.5.0") object NDArrayCollector { private val logger = LoggerFactory.getLogger(classOf[NDArrayCollector]) @@ -76,14 +76,14 @@ object NDArrayCollector { * Create a collector which will dispose the collected NDArrays automatically. * @return an auto-disposable collector. */ - @deprecated("Please use ResourceScope instead", "1.6.0") + @deprecated("Please use ResourceScope instead", "1.5.0") def auto(): NDArrayCollector = new NDArrayCollector(true) /** * Create a collector allows users to later dispose the collected NDArray manually. * @return a manually-disposable collector. */ - @deprecated("Please use ResourceScope instead", "1.6.0") + @deprecated("Please use ResourceScope instead", "1.5.0") @Experimental def manual(): NDArrayCollector = new NDArrayCollector(false) @@ -91,13 +91,13 @@ object NDArrayCollector { * Collect the NDArrays into the collector of the current thread. * @param ndArray NDArrays need to be collected. */ - @deprecated("Please use ResourceScope instead", "1.6.0") + @deprecated("Please use ResourceScope instead", "1.5.0") @varargs def collect(ndArray: NDArray*): Unit = { currCollector.get().add(ndArray: _*) } } -@deprecated("Please use ResourceScope instead", "1.6.0") +@deprecated("Please use ResourceScope instead", "1.5.0") class NDArrayCollector private(private val autoDispose: Boolean = true, private val doCollect: Boolean = true) { // native ptr (handle) of the NDArray -> NDArray @@ -147,7 +147,7 @@ class NDArrayCollector private(private val autoDispose: Boolean = true, * @return The result of function codeBlock. */ @Experimental - @deprecated("Please use ResourceScope instead", "1.6.0") + @deprecated("Please use ResourceScope instead", "1.5.0") def withScope[T](codeBlock: => T): T = { val old = NDArrayCollector.currCollector.get() NDArrayCollector.currCollector.set(this) diff --git a/scala-package/pom.xml b/scala-package/pom.xml index 147d7615a380..e1887d7c343b 100644 --- a/scala-package/pom.xml +++ b/scala-package/pom.xml @@ -57,7 +57,7 @@ 2.11.8 2.11 - 1.5.0 + 1.6.0 g++ $ From 6b22aa455e562db0e2744e94dc6a583191081b05 Mon Sep 17 00:00:00 2001 From: Haohuan Wang Date: Fri, 26 Jul 2019 18:07:15 -0700 Subject: [PATCH 114/813] handle fix_gamma in tensorrt subgraph conversion correctly (#15645) --- .../subgraph/tensorrt/nnvm_to_onnx-inl.h | 21 ++++-- .../subgraph/tensorrt/nnvm_to_onnx.cc | 29 ++++++++- src/operator/subgraph/tensorrt/tensorrt.cc | 2 +- .../tensorrt/test_tensorrt_batchnorm.py | 65 +++++++++++++++++++ 4 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 tests/python/tensorrt/test_tensorrt_batchnorm.py diff --git a/src/operator/subgraph/tensorrt/nnvm_to_onnx-inl.h b/src/operator/subgraph/tensorrt/nnvm_to_onnx-inl.h index edf4d357e922..55b3d938df0a 100644 --- a/src/operator/subgraph/tensorrt/nnvm_to_onnx-inl.h +++ b/src/operator/subgraph/tensorrt/nnvm_to_onnx-inl.h @@ -33,6 +33,8 @@ #include +#include +#include #include namespace mxnet { @@ -72,15 +74,12 @@ typedef void (*ConverterFunction)(NodeProto *node_proto, const nnvm::IndexedGraph &ig, const array_view &inputs); - // Forward declarations -void ConvertConvolution( - NodeProto *node_proto, +void ConvertConvolution(NodeProto *node_proto, const NodeAttrs &attrs, const nnvm::IndexedGraph &ig, const array_view &inputs); - void ConvertPooling(NodeProto *node_proto, const NodeAttrs &attrs, const nnvm::IndexedGraph &ig, @@ -152,7 +151,7 @@ void ConvertPad(NodeProto* node_proto, const array_view &inputs); std::string ConvertNnvmGraphToOnnx(const nnvm::Graph &g, - const std::unordered_map* const params_map); + std::unordered_map* params_map); static const std::unordered_map converter_map = { {"Activation", ConvertActivation}, @@ -172,6 +171,18 @@ static const std::unordered_map converter_map = {"SoftmaxOutput", ConvertSoftmaxOutput} }; +typedef void (*PreprocessFunction)(const NodeAttrs &attrs, + const std::vector &inputs, + std::unordered_map *params_map); + +void PreprocessBatchNorm(const NodeAttrs &attrs, + const std::vector &inputs, + std::unordered_map *params_map); + +static const std::unordered_map preprocess_map = { + {"BatchNorm", PreprocessBatchNorm} +}; + } // namespace nnvm_to_onnx } // namespace op } // namespace mxnet diff --git a/src/operator/subgraph/tensorrt/nnvm_to_onnx.cc b/src/operator/subgraph/tensorrt/nnvm_to_onnx.cc index 9d98a48c2ec2..6116f296e300 100644 --- a/src/operator/subgraph/tensorrt/nnvm_to_onnx.cc +++ b/src/operator/subgraph/tensorrt/nnvm_to_onnx.cc @@ -54,7 +54,7 @@ namespace nnvm_to_onnx { std::string ConvertNnvmGraphToOnnx( const nnvm::Graph& g, - const std::unordered_map* const params_map) { + std::unordered_map* params_map) { static std::atomic_ulong subgraph_count = { 0 }; @@ -88,8 +88,21 @@ std::string ConvertNnvmGraphToOnnx( auto placeholder_shapes = GetPlaceholderShapes(shape_inputs, ig); auto placeholder_dtypes = GetPlaceholderDTypes(dtype_inputs, ig); auto output_lookup = GetOutputLookup(ig); - uint32_t current_input = 0; + for (uint32_t node_idx = 0; node_idx < ig.num_nodes(); ++node_idx) { + const IndexedGraph::Node& node = ig[node_idx]; + const nnvm::Node* source = node.source; + // If this is a op + if (!source->is_variable()) { + auto mightNeedPreprocessNode = preprocess_map.find(source->op()->name); + // if this op is defined in preprocess_map + if (mightNeedPreprocessNode != preprocess_map.end()) { + mightNeedPreprocessNode->second(source->attrs, source->inputs, params_map); + } + } + } + + uint32_t current_input = 0; // Can't do a foreach over IndexedGraph since it doesn't implement begin(), etc. for (uint32_t node_idx = 0; node_idx < ig.num_nodes(); ++node_idx) { const IndexedGraph::Node& node = ig[node_idx]; @@ -642,6 +655,18 @@ void ConvertDropout(NodeProto* node_proto, const NodeAttrs& attrs, node_proto->set_op_type("Dropout"); } +void PreprocessBatchNorm(const NodeAttrs &attrs, + const std::vector &inputs, + std::unordered_map *params_map) { + const auto& param = nnvm::get(attrs.parsed); + if (param.fix_gamma) { + // if mxnet is specify fix_gamma, we will need to preprocess the params map + // to convert the gamma associate with this batch norm layer to 1. + std::string gammaNodeName = inputs[batchnorm::kGamma].node->attrs.name; + (*params_map)[gammaNodeName] = 1.0f; + } +} + } // namespace nnvm_to_onnx } // namespace op } // namespace mxnet diff --git a/src/operator/subgraph/tensorrt/tensorrt.cc b/src/operator/subgraph/tensorrt/tensorrt.cc index eac4ba7fc6fc..8b64c2a6b6ac 100644 --- a/src/operator/subgraph/tensorrt/tensorrt.cc +++ b/src/operator/subgraph/tensorrt/tensorrt.cc @@ -272,7 +272,7 @@ OpStatePtr TRTCreateState(const nnvm::NodeAttrs& attrs, Context ctx, << " instead of: " << max_batch_size; max_batch_size = in_shape[0][0]; } - const auto& params_map = node_param.params_map; + std::unordered_map params_map = node_param.params_map; const auto& inputs_to_idx = node_param.inputs_to_idx; const auto& outputs_to_idx = node_param.outputs_to_idx; const auto& idx_g = graph.indexed_graph(); diff --git a/tests/python/tensorrt/test_tensorrt_batchnorm.py b/tests/python/tensorrt/test_tensorrt_batchnorm.py new file mode 100644 index 000000000000..62af3bbf329b --- /dev/null +++ b/tests/python/tensorrt/test_tensorrt_batchnorm.py @@ -0,0 +1,65 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import mxnet as mx +from mxnet.test_utils import assert_almost_equal + +def get_params(): + arg_params = {} + aux_params = {} + arg_params["trt_bn_test_conv_weight"] = mx.nd.ones((1, 1, 3, 3)) + arg_params["trt_bn_test_bn_gamma"] = mx.nd.zeros((1,)) + arg_params["trt_bn_test_bn_beta"] = mx.nd.zeros((1,)) + aux_params["trt_bn_test_bn_moving_mean"] = mx.nd.ones(1) + aux_params["trt_bn_test_bn_moving_var"] = mx.nd.ones(1) + return arg_params, aux_params + +def get_symbol(): + data = mx.sym.Variable("data") + conv = mx.sym.Convolution(data=data, kernel=(3,3), no_bias=True, num_filter=1, num_group=1, + name="trt_bn_test_conv") + bn = mx.sym.BatchNorm(data=conv, fix_gamma=True, use_global_stats=False, name="trt_bn_test_bn") + return bn + +def test_batch_norm_runs_correctly_with_fix_gamma(): + arg_params, aux_params = get_params() + arg_params_trt, aux_params_trt = get_params() + + sym = get_symbol() + sym_trt = get_symbol().get_backend_symbol("TensorRT") + + mx.contrib.tensorrt.init_tensorrt_params(sym_trt, arg_params_trt, aux_params_trt) + + executor = sym.simple_bind(ctx=mx.gpu(), data=(1, 1, 3, 3), grad_req='null', force_rebind=True) + executor.copy_params_from(arg_params, aux_params) + + executor_trt = sym_trt.simple_bind(ctx=mx.gpu(), data=(1, 1, 3, 3), grad_req='null', + force_rebind=True) + executor_trt.copy_params_from(arg_params_trt, aux_params_trt) + + input_data = mx.nd.random.uniform(low=0, high=1, shape=(1, 1, 3, 3)) + + y = executor.forward(is_train=False, data=input_data) + y_trt = executor_trt.forward(is_train=False, data=input_data) + + print(y[0].asnumpy()) + print(y_trt[0].asnumpy()) + assert_almost_equal(y[0].asnumpy(), y_trt[0].asnumpy(), 1e-4, 1e-4) + +if __name__ == '__main__': + import nose + nose.runmodule() From 08fd98d54bf342a1293ba17444b22118dc323099 Mon Sep 17 00:00:00 2001 From: Lai Wei Date: Sat, 27 Jul 2019 11:33:49 -0700 Subject: [PATCH 115/813] update website for 1.5.0 (#15641) * update website * update description * update cuda --- docs/_static/js/options.js | 2 +- docs/_static/mxnet-theme/index.html | 6 +- docs/install/download.md | 1 + docs/install/index.md | 104 +++++++++++++++++++--------- 4 files changed, 76 insertions(+), 37 deletions(-) diff --git a/docs/_static/js/options.js b/docs/_static/js/options.js index ca4ac363dc41..d400d1ce2c9f 100644 --- a/docs/_static/js/options.js +++ b/docs/_static/js/options.js @@ -19,7 +19,7 @@ */ /* Installation page display functions for install selector */ -var versionSelect = defaultVersion = 'v1.4.1'; +var versionSelect = defaultVersion = 'v1.5.0'; var platformSelect = 'Linux'; var languageSelect = 'Python'; var processorSelect = 'CPU'; diff --git a/docs/_static/mxnet-theme/index.html b/docs/_static/mxnet-theme/index.html index a5f0bed636e9..d9311d7ae39f 100644 --- a/docs/_static/mxnet-theme/index.html +++ b/docs/_static/mxnet-theme/index.html @@ -23,9 +23,9 @@
-

MXNet 1.4.1 Released

-

This patch release features bug fixes and performance improvements.

- Learn More +

MXNet 1.5.0 Released

+

This release features Automatic Mixed Precision, MKL-DNN updates, CUDA10.1 support and more.

+ Learn More

A 60-minute Gluon Crash Course

diff --git a/docs/install/download.md b/docs/install/download.md index 808b4b8a72e5..ead73e5a18c2 100644 --- a/docs/install/download.md +++ b/docs/install/download.md @@ -21,6 +21,7 @@ These source archives are generated from tagged releases. Updates and patches wi | Version | Source | PGP | SHA | |---------|-------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------| +| 1.5.0 | [Download](https://apache.org/dist/incubator/mxnet/1.5.0/apache-mxnet-src-1.5.0-incubating.tar.gz) | [Download](https://apache.org/dist/incubator/mxnet/1.5.0/apache-mxnet-src-1.5.0-incubating.tar.gz.asc) | [Download](https://apache.org/dist/incubator/mxnet/1.5.0/apache-mxnet-src-1.5.0-incubating.tar.gz.sha512) | | 1.4.1 | [Download](https://www.apache.org/dyn/closer.cgi/incubator/mxnet/1.4.1/apache-mxnet-src-1.4.1-incubating.tar.gz) | [Download](https://apache.org/dist/incubator/mxnet/1.4.1/apache-mxnet-src-1.4.1-incubating.tar.gz.asc) | [Download](https://apache.org/dist/incubator/mxnet/1.4.1/apache-mxnet-src-1.4.1-incubating.tar.gz.sha512) | | 1.4.0 | [Download](https://www.apache.org/dyn/closer.cgi/incubator/mxnet/1.4.0/apache-mxnet-src-1.4.0-incubating.tar.gz) | [Download](https://apache.org/dist/incubator/mxnet/1.4.0/apache-mxnet-src-1.4.0-incubating.tar.gz.asc) | [Download](https://apache.org/dist/incubator/mxnet/1.4.0/apache-mxnet-src-1.4.0-incubating.tar.gz.sha512) | | 1.3.1 | [Download](https://www.apache.org/dyn/closer.cgi/incubator/mxnet/1.3.1/apache-mxnet-src-1.3.1-incubating.tar.gz) | [Download](https://apache.org/dist/incubator/mxnet/1.3.1/apache-mxnet-src-1.3.1-incubating.tar.gz.asc) | [Download](https://apache.org/dist/incubator/mxnet/1.3.1/apache-mxnet-src-1.3.1-incubating.tar.gz.sha512) | diff --git a/docs/install/index.md b/docs/install/index.md index ac7bd048a588..4217780c4b1c 100644 --- a/docs/install/index.md +++ b/docs/install/index.md @@ -38,10 +38,11 @@ Indicate your preferred configuration. Then, follow the customized commands to install MXNet. +
+ {{ content }} +
+
diff --git a/docs/static_site/src/_layouts/page_category.html b/docs/static_site/src/_layouts/page_category.html new file mode 100644 index 000000000000..9033d9a21399 --- /dev/null +++ b/docs/static_site/src/_layouts/page_category.html @@ -0,0 +1,18 @@ +--- +layout: page +--- +
+
+

{{page.category}}

+
    + {% for p in site.pages %} + {% if p.category == page.category %} +
  • {{ p.title }}
  • + {% endif %} + {% endfor %} +
+
+
+ {{ content }} +
+
diff --git a/docs/static_site/src/_layouts/page_landing_tutorials.html b/docs/static_site/src/_layouts/page_landing_tutorials.html new file mode 100644 index 000000000000..53c3dc399e07 --- /dev/null +++ b/docs/static_site/src/_layouts/page_landing_tutorials.html @@ -0,0 +1,13 @@ +--- +layout: page_api +--- +

List of available tutorials

+
    + {% for p in site.pages %} + {% if p.is_tutorial == true %} + {% if page.tag == p.tag %} +
  • {{ p.title }}
  • + {% endif %} + {% endif %} + {% endfor %} +
diff --git a/docs/static_site/src/_layouts/post.html b/docs/static_site/src/_layouts/post.html new file mode 100644 index 000000000000..b36f5af590b0 --- /dev/null +++ b/docs/static_site/src/_layouts/post.html @@ -0,0 +1,29 @@ +--- +layout: default +--- +
+ +
+

{{ page.title | escape }}

+ +
+ +
+ {{ content }} +
+ + {%- if site.disqus.shortname -%} + {%- include disqus_comments.html -%} + {%- endif -%} + + +
diff --git a/docs/static_site/src/_plugins/markdowner.rb b/docs/static_site/src/_plugins/markdowner.rb new file mode 100644 index 000000000000..ef4ccb0a701b --- /dev/null +++ b/docs/static_site/src/_plugins/markdowner.rb @@ -0,0 +1,30 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +module Jekyll + class MarkdownBlock < Liquid::Block + def initialize(tag_name, text, tokens) + super + end + require "kramdown" + def render(context) + content = super + "#{Kramdown::Document.new(content).to_html}" + end + end +end +Liquid::Template.register_tag('markdown', Jekyll::MarkdownBlock) diff --git a/docs/static_site/src/_sass/minima.scss b/docs/static_site/src/_sass/minima.scss new file mode 100644 index 000000000000..b8c46d0aecaa --- /dev/null +++ b/docs/static_site/src/_sass/minima.scss @@ -0,0 +1,64 @@ +@charset "utf-8"; + +// import grid system +@import "minima/simple-grid"; + +// Define defaults for each variable. + +$base-font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol" !default; +$base-font-size: 17px !default; +$base-font-weight: 300 !default; +$small-font-size: $base-font-size * 0.875 !default; +$base-line-height: 1.5 !default; + +$spacing-unit: 30px !default; + +$text-color: white !default; +$background-color: #fdfdfd !default; +$brand-color: #2a7ae2 !default; + +$color-mxnet: rgb(4,140,204); +$color-mxnet-dark: rgb(4,60,110); +$grey-color: #828282 !default; +$grey-color-light: lighten($grey-color, 45%) !default; +$grey-color-dark: darken($grey-color, 25%) !default; + +$table-text-align: left !default; + +// Width of the content area +$content-width: 1150px !default; + +$on-palm: 600px !default; +$on-palm: 900px !default; +$on-laptop: 1024px !default; + +// Use media queries like this: +// @include media-query($on-palm) { +// .wrapper { +// padding-right: $spacing-unit / 2; +// padding-left: $spacing-unit / 2; +// } +// } +@mixin media-query($device) { + @media screen and (max-width: $device) { + @content; + } +} + +@mixin relative-font-size($ratio) { + font-size: $base-font-size * $ratio; +} + +// Import partials. +@import + "minima/base", + "minima/layout", + "minima/syntax-highlighting", + "minima/home", + "minima/blog", + "minima/features", + "minima/ecosystem", + "minima/docs", + "minima/getting_started", + "minima/colorful" +; diff --git a/docs/static_site/src/_sass/minima/_base.scss b/docs/static_site/src/_sass/minima/_base.scss new file mode 100644 index 000000000000..79f5959663e1 --- /dev/null +++ b/docs/static_site/src/_sass/minima/_base.scss @@ -0,0 +1,278 @@ +/** +* Reset some basic elements +*/ +body, h1, h2, h3, h4, h5, h6, +p, blockquote, pre, hr, +dl, dd, ol, ul, figure { + margin: 0; + padding: 0; +} + +body { + background-repeat: no-repeat; + background-size: contain; + min-height: 100%; + background-color: $grey-color-light; + @media screen and (min-width: $on-palm) and (max-width: $on-laptop) { + background-size: inherit; + } + @media screen and (max-width: $on-palm) { + background-size: cover; + } +} + +table { + color: $grey-color-dark !important; +} + +/** + * Basic styling + */ +body { + font-family: $base-font-family; + font-weight: $base-font-weight; + font-size: $base-font-size; + line-height: $base-line-height; + color: $text-color; + background-color: $color-mxnet; + + display: flex; + min-height: 100vh; + flex-direction: column; +} + + +/** + * Set `margin-bottom` to maintain vertical rhythm + */ +h1, h2, h3, h4, h5, h6, +p, blockquote, pre, +ul, ol, dl, figure, +%vertical-rhythm { + margin-bottom: $spacing-unit / 2; +} + + +/** + * `main` element + */ +main { + display: block; /* Default value of `display` of `main` element is 'inline' in IE 11. */ +} + +header { + z-index: 10; +} + +/** + * Images + */ +img { + max-width: 100%; + vertical-align: middle; +} + + +/** + * Figures + */ +figure > img { + display: block; +} + +figcaption { + font-size: $small-font-size; +} + + +/** + * Lists + */ +ul, ol { + margin-left: $spacing-unit; +} + +li { + > ul, + > ol { + margin-bottom: 0; + } +} + + +/** + * Headings + */ +h1, h2, h3, h4, h5, h6 { + font-weight: $base-font-weight; +} + + +h1 { + font-weight: 200; + @include relative-font-size(3.5); + line-height: 120%; +} + + +/** + * Links + */ +a { + color: $color-mxnet; + text-decoration: none; + font-weight: 300; + + &:visited { + color: $color-mxnet; + } + + &:hover { + color: $color-mxnet-dark; + text-decoration: none; + } + + .social-media-list &:hover { + + } +} + +.clickable { + &:hover { + cursor: pointer; + } +} + +/** + * Blockquotes + */ +blockquote { + color: $grey-color; + border-left: 4px solid $grey-color-light; + padding-left: $spacing-unit / 2; + @include relative-font-size(1.125); + letter-spacing: -1px; + font-style: italic; + + > :last-child { + margin-bottom: 0; + } +} + + +/** + * Code formatting + */ +pre, +code { + @include relative-font-size(0.9375); + border: 1px solid $grey-color-light; + border-radius: 3px; + background-color: #eef; +} + +code { + padding: 1px 5px; +} + +pre { + padding: 8px 12px; + overflow-x: auto; + + > code { + border: 0; + padding-right: 0; + padding-left: 0; + } +} + +.span-accented { + color: orangered; + float: none !important; + margin: 0 !important; + font-size: 120%; +} + +/** + * Wrapper + */ +.wrapper { + max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit} * 2)); + max-width: calc(#{$content-width} - (#{$spacing-unit} * 2)); + margin-right: auto; + margin-left: auto; + padding-right: $spacing-unit; + padding-left: $spacing-unit; + @extend %clearfix; + + @include media-query($on-laptop) { + max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit})); + max-width: calc(#{$content-width} - (#{$spacing-unit})); + padding-right: $spacing-unit / 2; + padding-left: $spacing-unit / 2; + } +} + + +/** + * Clearfix + */ +%clearfix:after { + content: ""; + display: table; + clear: both; +} + + +/** + * Icons + */ + +.svg-icon { + width: 16px; + height: 16px; + display: inline-block; + fill: $grey-color-light; + padding-right: 5px; + padding-top: 4px; + vertical-align: text-top; +} + +.social-media-list { + li + li { + padding-top: 5px; + } +} + + +/** + * Tables + */ +table { + margin-bottom: $spacing-unit; + width: 100%; + text-align: $table-text-align; + color: lighten($text-color, 18%); + border-collapse: collapse; + border: 1px solid $grey-color-light; + + tr { + &:nth-child(even) { + background-color: lighten($grey-color-light, 6%); + } + } + + th, td { + padding: ($spacing-unit / 3) ($spacing-unit / 2); + } + + th { + background-color: lighten($grey-color-light, 3%); + border: 1px solid darken($grey-color-light, 4%); + border-bottom-color: darken($grey-color-light, 12%); + } + + td { + border: 1px solid $grey-color-light; + } +} diff --git a/docs/static_site/src/_sass/minima/_blog.scss b/docs/static_site/src/_sass/minima/_blog.scss new file mode 100644 index 000000000000..745f70408ee9 --- /dev/null +++ b/docs/static_site/src/_sass/minima/_blog.scss @@ -0,0 +1,41 @@ +.medium-widget-article__item { + transition: box-shadow 0.3s linear; + + &:hover { + box-shadow: inset 0 -2px 0 0 $color-mxnet; + filter: none; + border-bottom: 1px white solid; + + a.medium-widget-article__title { + color: orangered; + } + } + + margin: 20px; + background-color: $grey-color-light; + padding: 0px; + border: 1px solid $grey-color-light +} + +.medium-widget-article__title { + font-weight: 300; +} + +.medium-widget-article__content { + padding: 15px; +} + +@include media-query($on-palm) { + .medium-widget-article__row { + flex-direction: column; + } +} + +.blog-more { + margin-top: 40px; + margin-bottom: 40px; + height: 60px; + .btn { + background-color: $grey-color-light; + } +} \ No newline at end of file diff --git a/docs/static_site/src/_sass/minima/_docs.scss b/docs/static_site/src/_sass/minima/_docs.scss new file mode 100644 index 000000000000..09924f3166e7 --- /dev/null +++ b/docs/static_site/src/_sass/minima/_docs.scss @@ -0,0 +1,79 @@ +.docs-logo-docs { + width: 25px; + padding-bottom: 4px; + margin-right: 10px; +} + +.docs-logo-container { + margin: auto; + text-align: center; + margin-bottom: 20px; + + img.docs-logo-image { + height: 75px; + margin: auto; + } +} + +.docs-side-bar { + margin-left: 0px !important; + padding-right: 10px; + background-color: $grey-color-light; +} + +.docs-hero { + margin-bottom: 20px; +} + +.docs-hero-left { + +} + +.docs-hero-right { + +} + +.docs-card { + background-color: $grey-color-light; + padding: 20px; + height: 100%; +} + +.docs-card.docs-side { + padding: 5px; + + ul { + margin-left: 0; + } + + li { + margin-left: 20px; + } +} + +.docs-action-btn { + a { + color: $grey-color-dark; + + &:visited { + color: $color-mxnet-dark; + } + + &:hover { + color: orangered; + } + + } +} + +.docs-faq { + background-color: white; +} + +.docs-architecture { + background-color: $grey-color-light; + margin-top: 20px; + margin-bottom: 20px; + padding-top: 20px; + padding-bottom: 20px; +} \ No newline at end of file diff --git a/docs/static_site/src/_sass/minima/_ecosystem.scss b/docs/static_site/src/_sass/minima/_ecosystem.scss new file mode 100644 index 000000000000..91d545bf8598 --- /dev/null +++ b/docs/static_site/src/_sass/minima/_ecosystem.scss @@ -0,0 +1,9 @@ +.ecosystem-page { + .card { + background-color: $grey-color-light; + } + + h4 { + float: left; + } +} \ No newline at end of file diff --git a/docs/static_site/src/_sass/minima/_features.scss b/docs/static_site/src/_sass/minima/_features.scss new file mode 100644 index 000000000000..dd04a39f7953 --- /dev/null +++ b/docs/static_site/src/_sass/minima/_features.scss @@ -0,0 +1,23 @@ +.feature-image { + max-width: 100px; + filter: grayscale(1); + margin: auto; +} + +.feature-paragraph { + margin: auto; +} + +.feature-title { + float: left; + margin-bottom: 0px; +} + +.highlight pre { + box-shadow: inset 0 -2px 0 0 $color-mxnet; + margin-bottom: 0 !important; +} + +figure { + margin-bottom: 0 !important; +} \ No newline at end of file diff --git a/docs/static_site/src/_sass/minima/_getting_started.scss b/docs/static_site/src/_sass/minima/_getting_started.scss new file mode 100644 index 000000000000..550ddaaf30db --- /dev/null +++ b/docs/static_site/src/_sass/minima/_getting_started.scss @@ -0,0 +1,178 @@ +.install-selector { + max-width: 800px; + margin: auto; + margin-bottom: 40px; + + .highlight { + margin-left: 20px; + margin-top: 20px; + margin-bottom: 20px !important; + } +} + + +.install-content, .install-widget { + visibility: hidden; +} + +.get-started-from-source { + background-color: $grey-color-light; + padding-top: 50px; + padding-bottom: 50px; +} + +#lang-demo ul { + margin-top: 20px; + margin-bottom: 15px; +} + +.option-title { + width: 100px; + float: left; + clear: none; + text-align: right; + font-size: 15px; + padding-top: 7px; + padding-bottom: 8px; + padding-right: 10px; + font-weight: bold; +} + +.option-row { + padding-bottom: 8px; +} + +.install-inst { +} + +#setup-options { + margin-top: 15px; + margin-bottom: 15px; + margin-left: 30px; +} + +/* + * Drop down + */ + +.dropbtn { + background-color: $color-mxnet; + color: white; + font-size: $base-font-size; + margin: 0px; + border: none; + min-width: 100%; + padding: 10px; +} + +li.opt.versions { + padding: 10px; +} + + +.dropdown { + position: relative; + display: inline-block; + margin: 5px; + font-size: $base-font-size; + +} + +ul.dropdown-content { + display: none; + position: absolute; + left: 40%; + text-align: center; + background-color: whitesmoke; + box-shadow: none; + z-index: 1; + margin: 0px; + padding: 0px; + list-style-type: none; +} + +.dropdown-content a { + color: $grey-color-dark; + text-decoration: none; + display: block; + padding-left: 5px; + padding-right: 5px; + font-size: $base-font-size; + + &:hover { + color: orangered; + } +} + +.dropdown-content .active a { + color: $grey-color-light; +} + +.dropdown:hover .dropdown-content { + display: block; +} + +.dropdown:hover .dropbtn { + background-color: $color-mxnet +} + +/* + * selector + */ + +.col-3.install-left { + margin: auto; + + span { + padding-left: 20px; + border-left: 2px $color-mxnet dashed; + @media screen and (max-width: $on-palm) { + border: none; + } + } + +} + +.col-9.install-right { + margin: auto; +} + +.install-selector { + .active { + background-color: $color-mxnet !important; + color: $grey-color-light !important; + } +} + +.btn-group.opt-group { + margin-top: 5px; + margin-bottom: 5px; + display: flex; + justify-content: space-between; + + + .opt { + padding-bottom: 10px; + padding-top: 10px; + font-size: $base-font-size; + width: 100%; + border: none; + color: $dark-gray; + margin-left: 5px; + margin-right: 5px; + background-color: $grey-color-light; + + @media screen and (max-width: $on-palm) { + font-size: 11px; + margin-left: 2px; + margin-right: 2px; + } + + &:hover { + background-color: $color-mxnet; + color: $grey-color-light; + } + } + + +} diff --git a/docs/static_site/src/_sass/minima/_home.scss b/docs/static_site/src/_sass/minima/_home.scss new file mode 100644 index 000000000000..4bcb7fa8ff9c --- /dev/null +++ b/docs/static_site/src/_sass/minima/_home.scss @@ -0,0 +1,181 @@ +.home { + margin-bottom: 40px; +} + + +.home .btn { + border: 0; +} + +a.btn { + padding: 10px; + padding-left: 20px; + padding-right: 20px; + margin-top: 20px; + background-color: white; + transition: box-shadow 0.3s linear, border 0.3s linear; + + color: $grey-color-dark; + + &:hover { + box-shadow: inset 0 -2px 0 0 $color-mxnet; + color: black; + } +} + +.btn-action { + float: right; +} + +.section { + padding-top: 20px; + padding-bottom: 30px; + + h2 { + text-transform: uppercase; + } +} + +// ===== +// Cards +// ===== + + +.card { + color: $grey-color-dark; + padding: 10px; + background-color: white; + transition: box-shadow 0.3s linear; + height: 100%; + + + &:hover { + box-shadow: inset 0 -2px 0 0 $color-mxnet; + filter: none; + + a h3, h4 { + color: orangered; + } + } + + h3 { + color: black; + } + + p { + font-size: 90%; + } + + a { + color: $grey-color-dark; + + &:hover { + color: black; + } + } +} + + +.card-header-title { + height: 50px; +} + +.card-header-title h3 { + float: left +} + +.card-header-title img { + float: right; + width: 30px; +} + + +// ============== +// Key features +// ============== + +.key-features-section { + background-color: white; + color: $grey-color-dark; + + h3 { + color: orangered; + } + + .btn { + background-color: $grey-color-light; + } + + .card-text { + @media screen and (min-width: $on-laptop) { + min-height: 180px !important; + } + } + + .card { + &:hover { + box-shadow: 0 0 0 0 !important; + } + } + +} + +.key-feature-image { + width: 100%; + text-align: center; + margin-top: 30px; + + img { + width: 70px; + filter: grayscale(1); + } +} + +// ==== +// ecosystem +// ==== + +.ecosystem-section { + background-color: $grey-color-light; + color: black; +} + +// === +// community +// === + +.community-section { + background-color: $grey-color-light; + color: $grey-color-dark; +} + +.community-section .card { + background-color: white; +} + +// === +// trusted-by +// === + +.trusted-by-section { + background-color: white; + color: $grey-color-dark; +} + +.trusted-by { + margin-top: 30px; + +} + +// === +// news +// === +.news-section { + color: $grey-color-dark; + min-height: 500px; + background-color: white; + + .btn { + background-color: $grey-color-light; + } +} \ No newline at end of file diff --git a/docs/static_site/src/_sass/minima/_layout.scss b/docs/static_site/src/_sass/minima/_layout.scss new file mode 100644 index 000000000000..2097210d0dea --- /dev/null +++ b/docs/static_site/src/_sass/minima/_layout.scss @@ -0,0 +1,350 @@ +/** + * Site header + */ +.site-header { + min-height: $spacing-unit * 1.865; + + position: fixed; + top: 0; + width: 100%; + padding-top: 10px; + padding-bottom: 10px; +} + +.site-header-logo { + width: 120px; +} + +.site-title { + @include relative-font-size(1.625); + font-weight: 300; + line-height: $base-line-height * $base-font-size * 2.25; + letter-spacing: -1px; + margin-bottom: 0; + float: left; + color: white; + + &, + &:visited { + color: $grey-color-dark; + } +} + +.site-nav { + float: right; + line-height: $base-line-height * $base-font-size * 2.25; + + .nav-trigger { + display: none; + } + + .menu-icon { + display: none; + } + + .page-link { + color: $text-color; + line-height: $base-line-height; + //text-transform: uppercase; + //text-shadow: 1px 1px rgba(50,50,50,0.2); + + // Gaps between nav items, but not on the last one + &:not(:last-child) { + margin-right: 40px; + } + + &:hover { + color: white; + text-shadow: -0.06ex 0 white, 0.06ex 0 white; + } + } + + .page-link.page-current { + color: white; + text-decoration: underline; + } + + @media screen and (max-width: $on-laptop) { + position: absolute; + top: 9px; + right: $spacing-unit / 2; + background-color: $color-mxnet; + border-radius: 2px; + text-align: right; + + label[for="nav-trigger"] { + display: block; + float: right; + width: 36px; + height: 36px; + z-index: 2; + cursor: pointer; + } + + .menu-icon { + display: block; + float: right; + width: 36px; + height: 26px; + line-height: 0; + padding-top: 10px; + text-align: center; + + > svg { + fill: $grey-color-light; + } + } + + input ~ .trigger { + clear: both; + display: none; + } + + input:checked ~ .trigger { + display: block; + padding-bottom: 5px; + } + + .page-link { + padding: 5px 10px; + display: block; + + &:not(:last-child) { + margin-right: 0; + } + + margin-left: 20px; + } + } +} + + +/** + * Site footer + */ +.site-footer { + border-top: 1px solid $grey-color-light; + padding: $spacing-unit 0; + background-color: #424242; + .footer-category-title { + color: $color-mxnet; + } + a { + color: $grey-color-light !important; + + &:visited { + color: $grey-color-light !important; + } + } + +} + +.site-footer2 { + background-color: #424242; + padding-top: 40px; + padding-bottom: 10px; +} + +.footer-heading { + @include relative-font-size(1.125); + margin-bottom: $spacing-unit / 2; +} + +.contact-list, +.social-media-list { + list-style: none; + margin-left: 0; +} + +.footer-col-wrapper { + @include relative-font-size(0.9375); + + margin-left: -$spacing-unit / 2; + @extend %clearfix; +} + +.footer-bottom-warning { + font-size: 80%; + color: white; + float: left; +} + +.footer-logo { + width: 200px; + margin-bottom: 30px; + margin-top: 30px; +} + +.footer-col { + float: left; + margin-bottom: $spacing-unit / 2; + padding-left: $spacing-unit / 2; +} + +.footer-text { + color: $grey-color-light; +} + +.footer-col-1 { + width: -webkit-calc(25% - (#{$spacing-unit} / 2)); + width: calc(25% - (#{$spacing-unit} / 2)); +} + +.footer-col-2 { + width: -webkit-calc(30% - (#{$spacing-unit} / 2)); + width: calc(30% - (#{$spacing-unit} / 2)); +} + +.footer-col-3 { + width: -webkit-calc(45% - (#{$spacing-unit} / 2)); + width: calc(45% - (#{$spacing-unit} / 2)); +} + +@include media-query($on-laptop) { + .footer-col-1, + .footer-col-2 { + width: -webkit-calc(50% - (#{$spacing-unit} / 2)); + width: calc(50% - (#{$spacing-unit} / 2)); + } + + .footer-col-3 { + width: -webkit-calc(100% - (#{$spacing-unit} / 2)); + width: calc(100% - (#{$spacing-unit} / 2)); + } +} + +@include media-query($on-palm) { + .footer-col { + float: none; + width: -webkit-calc(100% - (#{$spacing-unit} / 2)); + width: calc(100% - (#{$spacing-unit} / 2)); + } +} + + +/** + * Page content + */ + +.header-section { + .btn { + background-color: $grey-color-light; + } +} + +.page-content { + height: 100%; + padding: $spacing-unit 0, 0; + flex: 1; + margin-top: 100px; +} + +.page-content-home { + height: 100%; + padding: $spacing-unit 0, 0; + margin-top: 100px; +} + +.page-heading { + @include relative-font-size(2); +} + +.post-list-heading { + @include relative-font-size(1.75); +} + +.post-list { + margin-left: 0; + list-style: none; + + > li { + margin-bottom: $spacing-unit; + } +} + +.post-meta { + font-size: $small-font-size; + color: $grey-color; +} + +.post-link { + display: block; + @include relative-font-size(1.5); +} + + +/** + * Posts + */ +.post-header { + margin-bottom: 20px; + margin-top: 50px; +} + +.post-title { + @include relative-font-size(2.625); + letter-spacing: -1px; + line-height: 1; + + @include media-query($on-laptop) { + @include relative-font-size(2.25); + } +} + +.post-content { + min-height: 700px; + background-color: white; + padding-top: 30px; + color: $grey-color-dark; + + h2 { + @include relative-font-size(2); + + @include media-query($on-laptop) { + @include relative-font-size(1.75); + } + } + + h3 { + @include relative-font-size(1.625); + + @include media-query($on-laptop) { + @include relative-font-size(1.375); + } + } + + h4 { + @include relative-font-size(1.25); + + @include media-query($on-laptop) { + @include relative-font-size(1.125); + } + } +} + +.copy-btn { + display: none; + position: absolute; + right: 0; + width: 60px; + text-align: center; + padding: 2px; + padding-bottom: 5px; + font-family: monospace; + font-size: 15px; + background-color: $color-mxnet; + color: white; + border: none; + + &:hover { + background-color: #eef; + color: $color-mxnet; + cursor: pointer; + border: 1px solid $color-mxnet; + } + + &:active { + background-color: $color-mxnet; + color: $grey-color-light; + } +} diff --git a/docs/static_site/src/_sass/minima/_syntax-highlighting.scss b/docs/static_site/src/_sass/minima/_syntax-highlighting.scss new file mode 100644 index 000000000000..e1ebf1087113 --- /dev/null +++ b/docs/static_site/src/_sass/minima/_syntax-highlighting.scss @@ -0,0 +1,322 @@ +/** + * Syntax highlighting styles + */ +.highlight { + background: #fff; + @extend %vertical-rhythm; + + .highlighter-rouge & { + background: #eef; + } + + .c { + color: #998; + font-style: italic + } + + // Comment + .err { + color: #a61717; + background-color: #e3d2d2 + } + + // Error + .k { + font-weight: bold + } + + // Keyword + .o { + font-weight: bold + } + + // Operator + .cm { + color: #998; + font-style: italic + } + + // Comment.Multiline + .cp { + color: #999; + font-weight: bold + } + + // Comment.Preproc + .c1 { + color: #998; + font-style: italic + } + + // Comment.Single + .cs { + color: #999; + font-weight: bold; + font-style: italic + } + + // Comment.Special + .gd { + color: #000; + background-color: #fdd + } + + // Generic.Deleted + .gd .x { + color: #000; + background-color: #faa + } + + // Generic.Deleted.Specific + .ge { + font-style: italic + } + + // Generic.Emph + .gr { + color: #a00 + } + + // Generic.Error + .gh { + color: #999 + } + + // Generic.Heading + .gi { + color: #000; + background-color: #dfd + } + + // Generic.Inserted + .gi .x { + color: #000; + background-color: #afa + } + + // Generic.Inserted.Specific + .go { + color: #888 + } + + // Generic.Output + .gp { + color: #555 + } + + // Generic.Prompt + .gs { + font-weight: bold + } + + // Generic.Strong + .gu { + color: #aaa + } + + // Generic.Subheading + .gt { + color: #a00 + } + + // Generic.Traceback + .kc { + font-weight: bold + } + + // Keyword.Constant + .kd { + font-weight: bold + } + + // Keyword.Declaration + .kp { + font-weight: bold + } + + // Keyword.Pseudo + .kr { + font-weight: bold + } + + // Keyword.Reserved + .kt { + color: #458; + font-weight: bold + } + + // Keyword.Type + .m { + color: #099 + } + + // Literal.Number + .s { + color: #d14 + } + + // Literal.String + .na { + color: #008080 + } + + // Name.Attribute + .nb { + color: #0086B3 + } + + // Name.Builtin + .nc { + color: #458; + font-weight: bold + } + + // Name.Class + .no { + color: #008080 + } + + // Name.Constant + .ni { + color: #800080 + } + + // Name.Entity + .ne { + color: #900; + font-weight: bold + } + + // Name.Exception + .nf { + color: #900; + font-weight: bold + } + + // Name.Function + .nn { + color: #555 + } + + // Name.Namespace + .nt { + color: #000080 + } + + // Name.Tag + .nv { + color: #008080 + } + + // Name.Variable + .ow { + font-weight: bold + } + + // Operator.Word + .w { + color: #bbb + } + + // Text.Whitespace + .mf { + color: #099 + } + + // Literal.Number.Float + .mh { + color: #099 + } + + // Literal.Number.Hex + .mi { + color: #099 + } + + // Literal.Number.Integer + .mo { + color: #099 + } + + // Literal.Number.Oct + .sb { + color: #d14 + } + + // Literal.String.Backtick + .sc { + color: #d14 + } + + // Literal.String.Char + .sd { + color: #d14 + } + + // Literal.String.Doc + .s2 { + color: #d14 + } + + // Literal.String.Double + .se { + color: #d14 + } + + // Literal.String.Escape + .sh { + color: #d14 + } + + // Literal.String.Heredoc + .si { + color: #d14 + } + + // Literal.String.Interpol + .sx { + color: #d14 + } + + // Literal.String.Other + .sr { + color: #009926 + } + + // Literal.String.Regex + .s1 { + color: #d14 + } + + // Literal.String.Single + .ss { + color: #990073 + } + + // Literal.String.Symbol + .bp { + color: #999 + } + + // Name.Builtin.Pseudo + .vc { + color: #008080 + } + + // Name.Variable.Class + .vg { + color: #008080 + } + + // Name.Variable.Global + .vi { + color: #008080 + } + + // Name.Variable.Instance + .il { + color: #099 + } + + // Literal.Number.Integer.Long +} diff --git a/docs/static_site/src/_sass/minima/colorful.scss b/docs/static_site/src/_sass/minima/colorful.scss new file mode 100644 index 000000000000..fce0dfcb1783 --- /dev/null +++ b/docs/static_site/src/_sass/minima/colorful.scss @@ -0,0 +1,370 @@ +.highlight .hll { + background-color: #ffffcc +} + +.highlight { + background: #f0f3f3; +} + +.highlight .c { + color: #0099FF; + font-style: italic +} + +/* Comment */ +.highlight .err { + color: #AA0000; + background-color: #FFAAAA +} + +/* Error */ +.highlight .k { + color: #006699; + font-weight: bold +} + +/* Keyword */ +.highlight .o { + color: #555555 +} + +/* Operator */ +.highlight .ch { + color: #0099FF; + font-style: italic +} + +/* Comment.Hashbang */ +.highlight .cm { + color: #0099FF; + font-style: italic +} + +/* Comment.Multiline */ +.highlight .cp { + color: #009999 +} + +/* Comment.Preproc */ +.highlight .cpf { + color: #0099FF; + font-style: italic +} + +/* Comment.PreprocFile */ +.highlight .c1 { + color: #0099FF; + font-style: italic +} + +/* Comment.Single */ +.highlight .cs { + color: #0099FF; + font-weight: bold; + font-style: italic +} + +/* Comment.Special */ +.highlight .gd { + background-color: #FFCCCC; + border: 1px solid #CC0000 +} + +/* Generic.Deleted */ +.highlight .ge { + font-style: italic +} + +/* Generic.Emph */ +.highlight .gr { + color: #FF0000 +} + +/* Generic.Error */ +.highlight .gh { + color: #003300; + font-weight: bold +} + +/* Generic.Heading */ +.highlight .gi { + background-color: #CCFFCC; + border: 1px solid #00CC00 +} + +/* Generic.Inserted */ +.highlight .go { + color: #AAAAAA +} + +/* Generic.Output */ +.highlight .gp { + color: #000099; + font-weight: bold +} + +/* Generic.Prompt */ +.highlight .gs { + font-weight: bold +} + +/* Generic.Strong */ +.highlight .gu { + color: #003300; + font-weight: bold +} + +/* Generic.Subheading */ +.highlight .gt { + color: #99CC66 +} + +/* Generic.Traceback */ +.highlight .kc { + color: #006699; + font-weight: bold +} + +/* Keyword.Constant */ +.highlight .kd { + color: #006699; + font-weight: bold +} + +/* Keyword.Declaration */ +.highlight .kn { + color: #006699; + font-weight: bold +} + +/* Keyword.Namespace */ +.highlight .kp { + color: #006699 +} + +/* Keyword.Pseudo */ +.highlight .kr { + color: #006699; + font-weight: bold +} + +/* Keyword.Reserved */ +.highlight .kt { + color: #007788; + font-weight: bold +} + +/* Keyword.Type */ +.highlight .m { + color: #FF6600 +} + +/* Literal.Number */ +.highlight .s { + color: #CC3300 +} + +/* Literal.String */ +.highlight .na { + color: #330099 +} + +/* Name.Attribute */ +.highlight .nb { + color: #336666 +} + +/* Name.Builtin */ +.highlight .nc { + color: #00AA88; + font-weight: bold +} + +/* Name.Class */ +.highlight .no { + color: #336600 +} + +/* Name.Constant */ +.highlight .nd { + color: #9999FF +} + +/* Name.Decorator */ +.highlight .ni { + color: #999999; + font-weight: bold +} + +/* Name.Entity */ +.highlight .ne { + color: #CC0000; + font-weight: bold +} + +/* Name.Exception */ +.highlight .nf { + color: #CC00FF +} + +/* Name.Function */ +.highlight .nl { + color: #9999FF +} + +/* Name.Label */ +.highlight .nn { + color: #00CCFF; + font-weight: bold +} + +/* Name.Namespace */ +.highlight .nt { + color: #330099; + font-weight: bold +} + +/* Name.Tag */ +.highlight .nv { + color: #003333 +} + +/* Name.Variable */ +.highlight .ow { + color: #000000; + font-weight: bold +} + +/* Operator.Word */ +.highlight .w { + color: #bbbbbb +} + +/* Text.Whitespace */ +.highlight .mb { + color: #FF6600 +} + +/* Literal.Number.Bin */ +.highlight .mf { + color: #FF6600 +} + +/* Literal.Number.Float */ +.highlight .mh { + color: #FF6600 +} + +/* Literal.Number.Hex */ +.highlight .mi { + color: #FF6600 +} + +/* Literal.Number.Integer */ +.highlight .mo { + color: #FF6600 +} + +/* Literal.Number.Oct */ +.highlight .sa { + color: #CC3300 +} + +/* Literal.String.Affix */ +.highlight .sb { + color: #CC3300 +} + +/* Literal.String.Backtick */ +.highlight .sc { + color: #CC3300 +} + +/* Literal.String.Char */ +.highlight .dl { + color: #CC3300 +} + +/* Literal.String.Delimiter */ +.highlight .sd { + color: #CC3300; + font-style: italic +} + +/* Literal.String.Doc */ +.highlight .s2 { + color: #CC3300 +} + +/* Literal.String.Double */ +.highlight .se { + color: #CC3300; + font-weight: bold +} + +/* Literal.String.Escape */ +.highlight .sh { + color: #CC3300 +} + +/* Literal.String.Heredoc */ +.highlight .si { + color: #AA0000 +} + +/* Literal.String.Interpol */ +.highlight .sx { + color: #CC3300 +} + +/* Literal.String.Other */ +.highlight .sr { + color: #33AAAA +} + +/* Literal.String.Regex */ +.highlight .s1 { + color: #CC3300 +} + +/* Literal.String.Single */ +.highlight .ss { + color: #FFCC33 +} + +/* Literal.String.Symbol */ +.highlight .bp { + color: #336666 +} + +/* Name.Builtin.Pseudo */ +.highlight .fm { + color: #CC00FF +} + +/* Name.Function.Magic */ +.highlight .vc { + color: #003333 +} + +/* Name.Variable.Class */ +.highlight .vg { + color: #003333 +} + +/* Name.Variable.Global */ +.highlight .vi { + color: #003333 +} + +/* Name.Variable.Instance */ +.highlight .vm { + color: #003333 +} + +/* Name.Variable.Magic */ +.highlight .il { + color: #FF6600 +} + +/* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/static_site/src/_sass/minima/simple-grid.scss b/docs/static_site/src/_sass/minima/simple-grid.scss new file mode 100644 index 000000000000..c4155950c031 --- /dev/null +++ b/docs/static_site/src/_sass/minima/simple-grid.scss @@ -0,0 +1,207 @@ +// SIMPLE GRID - SASS/SCSS + + +// fonts +$font-weight-light: 300; +$font-weight-regular: 400; +$font-weight-heavy: 700; + +// colors +$dark-grey: #333447; +$dark-gray: #333447; // for the Americans + + +.font-light { + font-weight: $font-weight-light; +} + +.font-regular { + font-weight: $font-weight-regular; +} + +.font-heavy { + font-weight: $font-weight-heavy; +} + +// utility + +.left { + text-align: left; +} + +.right { + text-align: right; +} + +.center { + text-align: center; + margin-left: auto; + margin-right: auto; +} + +.justify { + text-align: justify; +} + +.hidden-sm { + display: none; +} + +// grid + +$width: 98%; +$gutter: 2%; +$breakpoint-small: 33.75em; // 540px +$breakpoint-med: 45em; // 720px +$breakpoint-large: 60em; // 960px + +.container { + width: 100%; + margin-left: auto; + margin-right: auto; + + @media only screen and (min-width: $breakpoint-small) { + width: 80%; + } + + @media only screen and (min-width: $breakpoint-large) { + width: 75%; + max-width: 60rem; + } +} + +.row { + position: relative; + width: 100%; +} + +.row [class^="col"] { + float: left; + margin: 0.5rem 1%; + min-height: 0.125rem; +} + +.row::after { + content: ""; + display: table; + clear: both; +} + +.col-1, +.col-2, +.col-3, +.col-4, +.col-5, +.col-6, +.col-7, +.col-8, +.col-9, +.col-10, +.col-11, +.col-12 { + width: $width; +} + +.col-1-sm { + width: ($width / 12) - ($gutter * 11 / 12); +} + +.col-2-sm { + width: ($width / 6) - ($gutter * 10 / 12); +} + +.col-3-sm { + width: ($width / 4) - ($gutter * 9 / 12); +} + +.col-4-sm { + width: ($width / 3) - ($gutter * 8 / 12); +} + +.col-5-sm { + width: ($width / (12 / 5)) - ($gutter * 7 / 12); +} + +.col-6-sm { + width: ($width / 2) - ($gutter * 6 / 12); +} + +.col-7-sm { + width: ($width / (12 / 7)) - ($gutter * 5 / 12); +} + +.col-8-sm { + width: ($width / (12 / 8)) - ($gutter * 4 / 12); +} + +.col-9-sm { + width: ($width / (12 / 9)) - ($gutter * 3 / 12); +} + +.col-10-sm { + width: ($width / (12 / 10)) - ($gutter * 2 / 12); +} + +.col-11-sm { + width: ($width / (12 / 11)) - ($gutter * 1 / 12); +} + +.col-12-sm { + width: $width; +} + +@media only screen and (min-width: $breakpoint-med) { + .col-1 { + width: ($width / 12) - ($gutter * 11 / 12); + } + .col-2 { + width: ($width / 6) - ($gutter * 10 / 12); + } + .col-3 { + width: ($width / 4) - ($gutter * 9 / 12); + } + .col-4 { + width: ($width / 3) - ($gutter * 8 / 12); + } + .col-5 { + width: ($width / (12 / 5)) - ($gutter * 7 / 12); + } + .col-6 { + width: ($width / 2) - ($gutter * 6 / 12); + } + .col-7 { + width: ($width / (12 / 7)) - ($gutter * 5 / 12); + } + .col-8 { + width: ($width / (12 / 8)) - ($gutter * 4 / 12); + } + .col-9 { + width: ($width / (12 / 9)) - ($gutter * 3 / 12); + } + .col-10 { + width: ($width / (12 / 10)) - ($gutter * 2 / 12); + } + .col-11 { + width: ($width / (12 / 11)) - ($gutter * 1 / 12); + } + .col-12 { + width: $width; + } + + .hidden-sm { + display: block; + } +} + +.row { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + flex-wrap: wrap; +} + +.row > [class*='col-'] { + display: flex; + flex-direction: column; +} diff --git a/docs/static_site/src/assets/img/Github.svg b/docs/static_site/src/assets/img/Github.svg new file mode 100644 index 000000000000..17414ee24685 --- /dev/null +++ b/docs/static_site/src/assets/img/Github.svg @@ -0,0 +1 @@ +Roundicons.com \ No newline at end of file diff --git a/docs/static_site/src/assets/img/R_logo.svg b/docs/static_site/src/assets/img/R_logo.svg new file mode 100644 index 000000000000..78281f78ff48 --- /dev/null +++ b/docs/static_site/src/assets/img/R_logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/algorithm.svg b/docs/static_site/src/assets/img/algorithm.svg new file mode 100644 index 000000000000..1647cfa53ef3 --- /dev/null +++ b/docs/static_site/src/assets/img/algorithm.svg @@ -0,0 +1 @@ +Layer 1 \ No newline at end of file diff --git a/docs/static_site/src/assets/img/algorithmv1.svg b/docs/static_site/src/assets/img/algorithmv1.svg new file mode 100644 index 000000000000..4c92d39c868f --- /dev/null +++ b/docs/static_site/src/assets/img/algorithmv1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static_site/src/assets/img/apache_incubator_logo.png b/docs/static_site/src/assets/img/apache_incubator_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..96804c4a55a72bec853b3ae85bca0635207cb79f GIT binary patch literal 16552 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 \ No newline at end of file diff --git a/docs/static_site/src/assets/img/artificial-intelligence.svg b/docs/static_site/src/assets/img/artificial-intelligence.svg new file mode 100644 index 000000000000..819c98b6feb8 --- /dev/null +++ b/docs/static_site/src/assets/img/artificial-intelligence.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/book.svg b/docs/static_site/src/assets/img/book.svg new file mode 100644 index 000000000000..15969fee7d52 --- /dev/null +++ b/docs/static_site/src/assets/img/book.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static_site/src/assets/img/bottom-min.jpg b/docs/static_site/src/assets/img/bottom-min.jpg new file mode 100644 index 0000000000000000000000000000000000000000..25ce6dcd37c741e4d3f3ef902efa0e216d2951b7 GIT binary patch literal 952968 zcmbrkWm6ms6D*1Zcemi~?h@SHStK~@;_d`@cMpr(;->V# zJ@aX*rsmU3SJ(Wv`QHyXTvbICML2kPI5_zK5$?YoxF|T({|{*B=xCT&|06yw4h}9p z1p(3jNWn@*MfSg7W#C|-{a?!Pb8_;_sC|+6qGqb2W9sSbnVmfa|9{)yzhO8$RFqYe zJ4ARoI0QU+L_GNaM&Ulg!66_Z{=cLCA0Qzkeu6_lfk*vs8xHmVFBo;nzz+QFyXfj;U;LAlXCYB<_37!S znAP^5Eepq#gvnE&DopOclj%0x%;O)8``>=A>Mk^tec1N>3^;T-^$9GQYAuc4SRVPZQhB$@LDQ7Gp}Ct%2TLt?LE zxg7!dTv`!sW$S4(EEQ*9w-sKsn{3kXK&4%PYz`mGJvpVhxno155xNSE&064agV{4K z1n(Z0z)ju$SqeFBb{twm@TS1##{1<_j>a_KTE}uLThvu|;ipp_Lin{7^?CVlxn4A zxfl=r9OkutZmewkz5@;zB8~F)YaTbi(RdOLNLj_#)N?1V*^R83E>OKt{%yFmOq{8j z=-Mr4ROwY5GEn-GfY7--L%^|FM*9O~*ruiDMMyUZv$-T_e3Th)mi)~JN!TbP9yh1H z?hLu!oyQ)S`jmZM6k0`!*Me>*KvI}>$tH|dBsEz$wjJQ&rMct$L zxz@T^5=GrG{ZeL%$S>ku+ZUhKE#`tmbekZV$VD@{fW3U0s9u)Z&kERpfBm{#kGI`C zJcrJF%ZmrQMr0!{sU|Pg_-n_%AAZ^X!$Q}4HxF1yT$={$2>MjB>QzG;s)6=y-Dm(c zc)PX~6ujUfM(MB2eHlNb^}I1Yg??@KyyVUaXwxV_rzkPsZ7%lxrdFy{+2rkSAcU6YdGDcW(w&ThcNeN}1c6q1ixlP61D zMxz<57FYi5wCi@UI>WrYx|3GyJw#ez30=>aL#SdM$;#Sh)Xkb;HKE}-B`7*D7v4|z zyhe80z?X$tuN`%t;j-!xut1(uUW@SAHHa<_y93XC4{n+zk4raW&%AunITz6vQZ8$M zVI=bGOO-v+)x&dBwWjhz2oh4{QlWQv{(Dg>KO5LuFDjVWn^)C7*yO9>^962s_zxXv z8*rcLk5P8eDi#wG@A9ozAf5EKlzezbp#2(SgzmFwK30!wTqAlqHKDU+JbLDaNS?RXMK=GKOAk zFW~3oqo*T|qwiIbWj3T2)UOeDi=ZSh(qZ&#+}5Zo?|>6sVxuA)R79DKQaZQb?uWx5 zdv=`&)EUIm^(s%$>)>A5G+{W+QDBVc)_2kBrl^;bRE=5vHCBLVWy7k7cAFWhn!;S2 zSOXsV#41x}fJ9?^MTLI%4vkp7$cX6*X3ph+DN%G*JHcl}^_6nUIt1Ly53K3OmbJtn zgsV-;>Xf^@jC2y{4(t8i6ki-taGJ>&zafdOS^w3d%cFHV?)d|AeXAmz&wDicn63tI z^hnNMq#3bPVyTJ;=K%~FE-f3W^FD+^zGR5=N}`&bQ7RQOvSD^NBhzTdYNUw-MeYGL zd;$!Wlc;cZ1k%OTXqdY+#u+VuJsP$4IIb`blojRzBzu|-Bx6A6;MUF;Ik=uGR5@=> z0%$*OHjY1G7`NwZljMPR$o;Pi3Mo}b7QS<~&=LPx7kqab7L0~GSXI}d4e0v_EK=Z= zB#I~ba>=6V7PJ3LUcmfO93ntJ$Ejx*%*M)tg=6>Hjd@bx%fNOSB;C?Qkt3f8W+2E2 zJ)uNGs(laD=Q`T9D2d@``r-9eEVyU^>X|Y!9uMy);uY;e`KxmUq0wDtT-I=PfUVm`AT{FnMMpq0!$aYOhXkQCBrmUV&&5raQYJ!LJgRvLTv;agptWTj<1>05E;ct>#9`gCJe|0 zR&MKDk+*93;#@k`T#d7C)W3@^3*`Rqouf{KY#mBU6bN#J6kckOzL|<#b&FYnpM==)UcQdP?=R{{KN59sMj~7#k;jnJw58phJEIO^bAyN;(%epy%n&ZI$@`PV}LAjb{F7V)ScSQOM9jiHI%gF~QKgJ(?U)3Ib0H zmH%GmT)b!(SKSH>SB#{M%zbTw5CSQl8mkp#O;Ue?_k{ZB#Qc1gJL;A<0^O(++1ojA z!P*u2oiNKiv}|AT6ZC4s&ioGl&*1D==w+yv)Ru~gCRH}3k&-NlrYvu}rVPlS z7IdnA{F}P8QJYHY;c4_b_NQuU$Xm@7@k2${1cFK#<(v*uVOTA78GNTXR>Xshf>KMaHWYc!~>|7ke5h z(5xN;bR>J9_pRXE_BeHW$&;0dS~)IGN@RZJ^2vUm&7F);z?18$;?$P35S1Tmp~Juv zXs}E85N8LfkH4PSuLKWR%NjoYNM(5t8Kft#Wna$ti-L+WW0BfGTd+#f`izE_$Rv=Y zQM(l#OIF5?1@|q^PuT)9bEOdJQRXE!J0h|9rv~jcBs{lrtIKUHyZxdr;4DWuic=RV z(y^Ep>10a4B?LS05qVO?07E|$elq0Y3KB#zX2&f&>Uy1cBoS@S>EP^o=-hWX zl*j@;{HjpaGhIxs0Nj9XRH{Jng~~nO+0yg;dy64ob3b?$kIkD6T_F8&`!!*lCkLAD zJ=^nTe1&`sMnBdG2onsb8O5_P41uimQo5Nka?Mbumy^hF^EN~R{D_YX=n3!6(v2)Eqh>jmcHbnv%14 zlla8WqONQq#OMMdZQJAg3MtnX=ZWYs`jp*a7E>2(s#}#NPhoP|=MOu~A}g4F02P?| zo%sXy9877CT~8A?Czh+VG0&8#ey%wO33x6_AuY*L{*eqxiW!EX;vlyql7t3!-1@Oe za64p=V6npIMawWsq7xH{2fj+5*=F?)k|p7st#p zu)P+EWtVDwTi^=>k-S?Lj*~CsjZ}mMdZT2_Hg;`I*@RBm*W%P}*Fj007PN$$@or~g1@dg#i6;=^bFbsU?CI7|H7ajqM9lS%XS9kcH6QmBx!81s!Cts(-+`{N=>c^{$JX4c8>ff;?aAvn(YLUMn=}4WWteBJ5JYi`u6p*tWUZ zpdM_E!aS|E&9!BRV^;c=Bfwv&#Lwdo2%noJcR!%eI$Ft!Cb1A-bgqn)_6$Zz-r0+~ z@O9CJA3*>`^}5uuXRu9xF*)uU!9liRh?%QHZJ3yo>u@i|TP5?N?eXF1-EgB5%GV@X zo5Vh*XdJbu_=ATM-%Af6rzqmQ+legDm`c`$P=F} za&7ojWMDbso+|Ed*P1dr%xLidpl$Tn;$-AuPTv|HXGRhAN1wNktkdnQ?qfymfI~ z*F83pLT-g?m>%-Wm@H?Knjm!g^cvl3@%-pYvLJDiy>_srnPbMZJ(>jZKe#WiJm5-% z*uiArc^lVbrxBNgk7mrqj0|a5kSS2pVspdBcrn9E<*MYfypN|C zHlLMyO!kQn6BE<$Zabc#l$~p3fh{SB(}do|Jbg-?s zkXEWD+phch9Cd%1>#A_#0LLf^U}aFuZu)LStB645JFlg$j-*~g=ym3MqbTt5ibXDZ zURMP?ll)rV*J2@T?pThmUhn}mIbXG*cBsFazvH_4#7sD+z1+(w^Jr+()N{M%{8e(< zvFbvsPTQgCRYFcXCr(Myn(j8;NmnQU$LX zdW4F@#KpR&ZZa4;7Z`4uxc{{n>Wul~`Xf#?9*S6I{|tL!ZUK*z*qM0O`0h~|RZjH# z5&O$V{d+z^LceZwR(7o*6g^};z%nw`H)? z)l+&>0YJ_k3LV`JL4dqBTTzbBoorpgysmK5Yh{Dl*>75^qC<}G0$o@F8kfmU+rI9B zJ>%6pQ)v@wCpSmNnJZV`QhE+)*ZZz!f?rlB)W{?I5RrIrKKEglW1+bkp%9=RW&}>C zGfnWg?pRPdailhRK+Cp&vYcOeUQVO8q`0QLAoLo|#N?O9*%Xg6^415B-}{<5AqXRqLOB~ynpkW3~3 zRJ5a^uuZ0@L}(Wn9Vc5?npNKJixW&-T%2#rqdjLOW)Qgbhu$eA5v(Yh~^V6-49y4o1(=%MrYApe?Tk)o9N?h8! zJMmd$ZXFji+_w%tERNHqZ5-E{^%*%>vg zpChWv3B(U`vs;#oY3#>)<@?mMl)=j2I zjO`1eC>qH3FK>p1Jn0@|Ul`I@5{WM2XW?khk{#l4IJIt8OfrE%6#M12a@L8|^3{{6 zku5RXLui2ryhVhFEF$gTE>3Jg+GBjeL0-7?xQ6ihKv^e5`Uhm)8@PXndyZqoWKzN16C)0cpMisGif- zag9#kz2SX7OsEn+|G0LQcZJwm&$zeAW;*T9vlPJDL?)i@4M*F>Nr2t2Mb7%S)QQE7 zMkk~0S{4$NfSIDr3)GwwgM`-@v4{#RCZ{CZ7=;qPU?Ig(n6=zdlz266k^50N7G@5J z1035D*&($bBY>2?+eA=O`QG_ncP8bHARS#pGRFH9>aw^BPPiaj`AKTzR7PqjL0vV$ zcEP?}FQ>%4=e`TKn_H1((LXtlPa#Jo*Hkh#m$%-Pt0X+{a9tOeL1N8{g|JyEmQ$y7 zJIZZ<1x_^Zk_}<85$lnie=xZ3Br8b24u|BI%v&P&?{!L>e=#?DDmRp13?(IX%0PsL zy&rJ27=BT?{ld;iPV$NNX-dpLo6@&wU6dijiiV(~9`As2R=MzRY*E=pF%Vh1vb}mY8t3;$7j)-6Vx9hC6y}dhd`DA=Nr4R-J>< z^T#C#vM0y$3G3!sg@AQZ0XsC3YNV7EFdZ&Q%kxKBgZ&mOy~;@?R)!OmU!!I2#nMV^ zG*QS$;U<^nw9iqRut(WDVhEaj5$#qOp$L0G9rmwLC%Zrb5?ZXoI;Aejjg@{B8G( z%SgUCSZTmhrr5MNyrt@y^lpHS*`(xsDYP>98~tJsmW+a@(`JV!-_J@S-e);5hjFDB zfN@jr*3JZI*EphBWgCPReL?_Eea)P|~4>p}E!-lg{VdK2@wsvg# zVlJeOBR2rAGe|LaYGg@nZs28EO&=Tk?z(GUXUEAl70|Xgdz`U}Yb7F$^fx5ZzG?F!=z`t$O=BM`Q^he62k_1rnOx4~*e^zklOe`mU|LM`gcC z(@T2m2II&azrR0j04wN(Wz&O|V>47ON-jt0+c_V4dbE1|wE-uWf%u#j3iUTC1-Pw3 zI3DqSyBka~hh?5l_(G#L`@h7q%fv2bkEXZ|7#fX9TJM%9f8Qo%_7v=eFuDBL{+!wR zz}D9GUa+-@1F2R3pun3B#~xnD8nwWj@&rnT7DjpVPI$u;SvSwwE<>B)w@(l-=gbb2xe><0kU6m^Niqm()Ui$_ z39B;8z+2Usx-p3MBcGi4UCcp3KDDO*+q_EFH0Ary!Q)u{E>(wWqQ}<>&u}Tci$UEw zhbr6cyt36R+f!C^N;T%05iEnSXf^dqLgSNd@+_94I0VFn^|ipyFMsRRl77_Vk*ybs zco;w+&mGxed{fA37MfS%$h-W4Hc9FhUPK4T=1$Mn6vrf7>pdDQuP3MwqBt2 z^3z?s#jQ~@+e3G7=YqDx>cLu61{>THLsp=toeKl$x~!mS1jt(;K^Qo3xF~6xQDccx zW-%dO?|hl|0S#NnN$rr2f}iZ5RaCs;T|eZ%eB*QaOIw~r-7=qWg|!8%vu_XL)U<$B zQV>p+T+bZC6CNinH~TPnD|5Adm096U_gi3oz`9MxK}i;Ybc^o9f#^&C<1(m1$3|zRf9(}cWF%;-&7?J4?9OiHgf-9lO zA-tTyku|1JGh2du>)DXigdAmr7UKaT889qd&Ad@=I0$PU#S^L&EK5UN_fmc?QI^J5 zHGB4kL+&sloJSoe%$6`~YJk2sh%wl^Ia6!cdLb?KB`z$@3#x)4tdab4@#q<#PV^gG zYxfh!kGX9U(M4R}#vsK=X+^=wYhn!3Ehf8U`CsOfM+GfmG}W2CZew!0DWoUMXF$y} zB}bm|&gW$xC7FtIbQoQU|7ti0?fSx1T>58L)J*49H`}b1bO8m1)Q9j>C=rn-mddP= z-bsjhMu+{>vt$fW=;0JGySi(_&G4qLG1JdawWd z02Kv_pp)({PNv*h1q6O-J{l6zA%5P^O3DhnbWrOWU9CnGJIk@{d`SEBqiiMqf3CYS zb;*mOyr!*^N6*|R*ii{2C&$`s@;^B94!wlHA1scMMo~OLcKlzKgTG6EHxrL~1HH^-AdZV!|6F$Wpc|KDc^+G)WJLGSdt{MZ*% z?p+*#H{0!vYz>pIohlxAl;W6{>;PeD;ns8Iy7oEZrpGz4gh8wme9t`>+24x4nX)gF zMtMZ8_@nvs9^=1)gh83Wlxv^;Blb@?wO`J zK})iyZZ4YAg>lU31KfrqFNy34XtU{P?=)|JR!*DCg~duZU}<@g5WPHv2+|_bRRtWu zp+OSHrPHPdMWWHEE=1{YhdW200wJ*Bu~MvH$xGO0=@*3#Cxf2kFiM0j+NNV8ftw;O zU)!t3xbfmU+oBvjX_VYVJvd7!h38xlv7?)Pae+Y>AMtFk>r7*C1s&TX9AYo!5u*owszae~ixiF}XPJFfuZ(% z)XmDdM-2U2iY7G996NDpejoNT%y1$regZF8o91#s$n3qM{BRusD-9fBXv3l#jM1Om;E*mQ748w*^>!2h@qy^?Yy|Twnk|)LX6=<05HI)hR4ao|H450 zG2lc&6T9w>%d1?mF?wP0h|c$ zR73;e1c#{DDQa94J%_|8BqCtTa$V;tGq-dyxG2>MF%lf51EX07>Cq>6411Eu#Uzvo3>fEV-It8GKDE{S*9NzpE-smTIrlGa-E7qV}VHc%5B&K(>o zzAQ$uv8+!f7!avI3@(V$2o&sf8Y!0O=e*7D%#?rm2}>btw!|8|bm-5@*|7{qid#1S za!8$wZZJBRC}b?Dcn7)yTqD+n#9acND<);()87Gh?3h^$hOlg{F+gSjmycs|3`!Ek zxWKP?nWNJpD>m|(&<@Ezo23C|f;S=#74k-{>7Rc#`snSUwI`aqhBhRLMdgIh%EPk$ zgR^n381~B3hk339rAi4qhk8NYley$l*qnpvCDA~WStc5u54dZ7MmV6&WqOSS0e({x zk|ep)zqcNh-PRa`ewiFHPb_rG9{# z^_=NAJ3`hk765rbAAs+60NJKhM<+@Q!Lj?&won0DKq;JctRXDD_<~Wwr`lrtw=|Ej ziYyu!NLzvBX6{@%_09nS1sApVs3MHMSSD2Mn^p=Y6ztS8SB2sB5X(ji&|4svF0Yka zCwf*+hdyPcz5MJ5jWt7AaEo4oDV!vJN9hBgbV3E>3Wq)KOmk(AD>Q0}Hf?@DL=zFu z=(d@^c$=lF#h}q4KCqp9$*ewO z?ufqB6yLm2V9iAI8|x8kPPJ_kfo|-`pobW=sG>}pOOhevM%P||f5=Cx5t~5nS`OLB ztyq{SKonU*G+&IOLBe(vM{HBDvO*0BMl@VWMh)ZTQ(+d85JiaDP_Cziyx5}mN7BUa zXr`xSH}*6A_hdTdTnf>SH5+WR*_z;_HDFx;$C8{Yt-~3<7LuV2nfyhJ*KSti)I87! zW7z|-jkCqHqmfs*u4BJs&RBx-xFPg{Xt1iry?awJKxMK#nradXao<-U; z)oiSZkkEPBIR!gZ_?#Ma!*ngDS`!nens>b*%WyV+?Lgz3_Qiu z%?L^rC?QwiVNVjavX_K09$m2G(osOVNZu=(+Dk%!cK<$9xubu`MAq%%DILtma0wl$ z+rg6B++*=bG<49YU(N@5El*XOza5@%wE$(u4ExoNsETp&-$L}rj8Y<6vj13OV2?Vr ze5&>q_rDCG@P&hy3tHh(qT1@U+uJoGCPb^2X!lBxw9oB&E)43m+k|b;!i!8);?yN? zu9tMO#B-`v_h;4Gx|N7A5tM%e56yLU(}lG9t@4MlSqZg!9@Xt`Oy^GBo;Q>q1zW0W zqP;Qo5on9eUyUa7lR(Fu9aW1tfM{A~`{q_V=6}}5yX9>~FUYR+yL3jiF$@3a`Ln_m zsBnkRJQ4#+{*tR-K@v!s_RYipjWl}54RMSM8wvHx{?W^5(=1;VEkF83ey)PU%Z17D zlq8m((1pgew5X*7a+32k%5f$O1${1BY6nY169UQnh?jGtvuQQk`D#4~E7rtk9|?4==2Gzpiqbc;+dEekq`~^H<~z{H}(JnmB3 zJAqL})H_4Qjc8Oav~v61nBEC4aFYEV+&1>k`Qp~fX>W5=5X31;lFED=xyZVJ6gaC_ zH4b#4zqw?bWk#$dvF~%7zBr0b9aag&rgG!5Vv2%fGu;jP7O%DCn>aUv%i@izb?5fq zWza=4_05m7FSXw{k8SK4YME!-yR44giJ)SO&5Ei7`6e zge~rCZHb)jRdq415}=w2PuQJCndXc={S63-KzJVzaXO17*K>dF`SVXv^{1(Ix=f|} zqV*7m!Z*l$h9qO?y-n!U_+VPOLd)O8L{9$bykDKl@GV&9Mfx+<65nKEW(L{pY9+l& zx~%t$4vpg?)U;yX4CQ=7&M!XowlkeUuM(~cvA0+58E2TP@Ycmd7ftZ+ufBGYF6P9_i_5m(kS!cHHeR zX%GKwDX#8&f@IgD7?vf5fH-q6#1@cCZuqxjQ$DjjiMVt*vHyTndhVIs9iM2GlRjVR zsxNBu4zO6o)wW{Pu3s=SJSIsKF`Ik)ltL8zR_(+$A`n@nWN1Mfn9nr{ncZ4t`S9)& zL$|#wnp|B6mH+k^mJ_OzpUfw{{+&4M+GR&W0TN>ULtA+mxah6E;I~vG|HL*|PqFMe zgEKwyv*XY%J~6>biCr=+*RCKEKO}j(mN2H7H(4iYJ3fSVKi59qxsaM;tZ(F~W-8gK z)hzE*K<9?yM{uZ1; zYxj+TZqRW5O&Km`SoLZqvdG12$qurQpc7pMkJujBng>P1Ar~HPMXk%t0MnhDZnA08Ub2tZ7VRZp_t%(yq^Y9aZBy+Xol?enfbX$G8e2~U zF>Vr17+pL687k_$tS#eM<~XvPlZoVF!dTav5gEkUQqHqTKF=IL_A$Kb3lHKs9ecNk zyTV0n^|!YH%S+5SYlI^;u1LB_t;G-@q5G*`O|Lg4+aPgbcz21AkI@hIy|~o&aNRgc zw2be=-|&!;dL-PUpsIm7ekB(RuVRmmk&-(8g0tl$UXlZ|wcn($G;yw7coHxF5#E(l zDalw0k1nlMQlEph=-OQ6Tw@$KPLAl5Qnf$syrk@viV(16zlVOec#)+OdbJsOmy4z{ zcJlJf+-8U@fmZnUUGc&38sZP}sMsA9@$VA{Mbso(nG`B}PM`3uJfm zE9I7yqEl6$!8eT%HHEv#zM2zPEBj%4rSoxQ9#)E)iI<*(>Wm@#NB6J|@3vau!Y*Qs zgs^rMwYTFyG>tvd>F42NIV7nFxcH<7>|7tS`?baSHWE+;XjDX>R~5yOeA{pW#th%` z{l&Djn0XC9UgRHH$|w1q&PVT8YE$K9nM;P$5<{r$-pTDqPQB{JOkZ$?=7E}bDXes) zi@)4od8xrrsv5TM&pA+vdv${Uc9#{n!PRtQedtvu7^!G6r8dYe`!^n5%a1*A@nFdl zW_ZpCT*7!Tn3xb#jsKM-fLCd;6{oa`zYfdx&0*dot`%b)!Z~rPNyJxG`bCpI*pTfi-QS^v79So?grgAQ>7rVh&_E($yc|a)`lpF5T@%YfWgRi(hJXPqaTo}l z!{S7wf|<_P$~bj)0=II_ljG|1Ev6Ede}6j4*2rLWD=b2B2pPY;Ew?b%3Y$U6>B|+1 zmo0-H@t`A?a$90BXC0egI1wre)d)jI=i64dY9H*SV&|9DRY{?`++5J>)TMTm&BGK#s?HcA{^PDnkK?v&T?#X>& z4*M85Y*b8(z2y9YYcb~6)p4#1KAHTAoAT(|33<+RZS+zXx>~M>Iz0R7g zuS?r-m-Jcr9~hn!Be9~2uaPhMte~PN`nIv~w=B zd}=C?ze|W9Q>f%~c5lq|?1em~l?om4>)A`3n~(_jX6< zq%T&lbnvr+Ze=LWCDCST1*fwUf z8JK@jcd+DWDC%^~zlqh%JVw%}Fssx2i|&5qpbXeH39$U_XoV#V;eF=FwC_zlD`N5fZ%Fn?NVClWkYk@%o9 zEn9fw1&XC1S*9$sA3PL6v0`<_o=U>Tb=?XUu-3X_;X+eE+is+buF{Ehhr=6bq)zst z9_Gq>Gfr^ySI%^cj9EO5HLx_^>1$b)bg|XF5a)V=&0>@@k^W`+IFdgN8}SMt9naUP zNTggPdJ3s+Kig|pRkbEYO@Mfn{H$BuZo(o2(t5Uf zH5V+wTl7mmu(K|}VaA5+1N;70a7(u*eMm4>6$qJzB=B#)dUZy-seH=rjaN6PcIYYF zgN&ueTO;@6BsR5mJv$t#mE~%aaYc9kZYn6D{Vw?78SPK3gJ#yiLav~5^W8Uva3Kg@ zTHt1mnn6wd9(ZZL`gpZw3#i}jj>jv9jLU>Ga%h?Al=Uv-s^xAUbl5^rjHPCSM0_WX2TVRkw87VO=;IS!t}i>7@8VTk zaMgQtYG;hxebu$zJZXX}d`!sN5}c({2{!`3OtWqrtbIQ4us}3)rY!x6$uJB2JXtrN z-IjR<4B6cLw|QrMu-}+LNmc56{Wjz%t%^w9#p2T?Ois>*W^Fx_qKV@?IB>){L6)oA ztBkOni(9Ptp{T`kwEWR%SUs^VlSi>q?raiQ(L`^~6{3 zyU&iF?=6p!Oi)0~`K4hEjDa%*#PlPQOFugtbkM?^0Cvw#dx!<46f==0V->yIahH2) zh$e)WBK2L|*K^9i`EQ1`w)0y|z7@#G0CrkgvZ(Z5$Hfgd*k*!tXtq**lBi_ZNGCcR z1G;m-1vU7J$=6DGFHiG!i11Z@O5`0!VQ&2%llbDS0TWZ2UwXG#g4AFcC}Sm zOR=pbLdaGuk0b@S4A>yNq^_!Z!y=d0)nD`HhjX zad2vWDu5ut$eMaSaP$w=SAoT@GSgu^^d%3W%@>yquvO35!qkce(_5D$br9I|=2oT_ zFGQW5Zdyu>q~GUB+vo4YeY%etb(MQ~-EfNBEQw0dde2INpTesShVD+|XyHK5OS43e zn)eKm8RU@7#S$gxF0&;wo=ftG7m%Y>dD*GR$K|Tc%h#e?8LNWB>GP{9!hbCsJ1Fdm z_0Iyoi<*R2@dZD^VlRnO$6W`JQUwOTVr{svEdz1(bX1HL1tGiOqV4y>TzBcTzCD(( ze&xq`OCRGKMz=bTy795x;4{fD*`l5Ib<1jkFWMN8Uy2?^B#_>rje`z6FNV2?|49wxE)$C0>F`+WtY# z?s(qd8Yu_@gj zjhf}pm1RViL2chxKZYSX%@3`BXlMXJq-u!LGz^qp3;iO(!DrFokcE1^8UW*Vp84jE zZwk^5@lfmc%0@eV|tWXTB~?yIR4dGQH&)qIyzy-hHC@2vWiOVg2Kk-29W_%K_ewtlf=1JA)R99-w&JD)Vn zwtg7_osc_iblN~|Bp92|o&Ec(8&cSFhyT6eUW4A6!gl%h%3|CU-S<|O%~i6F&U3sq(E>1OIj^Z{PYai7N3)}QLE;;9Suw@01nk=*!y z>53d)PBJ=g{sKIXE{~E-Epu($x7Jk$%V>Jbi8km?$h4?jFy+P|2CeOiO!lu#A!iM8 zROpx8M+X{riA;&pfk&_QIE=)D1$k&?vk|>NsY~g}yqjx@{V2g8&$gX>+?{P-9h~H)4!I; zaX|vTIlso)S?)9tV|&$ssf*Rchlx{u)^_0x9N>CKOqy9^^GwY946~woiV;@rG{DzR zh-=w9Y|~FjW!Wa%gv6Iu%@LXh1npCz&G{|Wx|ww4_bHE;+I*TcNx@E^%iJcI16tq< z_}PghuqL6w7yA!LY2EGG*B&kr<{Pry!^B)H-T}Q1*yknt+;i*lPr~?oWvmdvS{ueZ zN>Sk23rVO_XHtFU#_NLDT(Z9F9&zW}KnASjSjdr(BHy#A-@AgmP?$~2Q7g8io1M#S^77&vZ4OFxpU>_#whruf-N3OztwO4sYR^z zPOGZFvDqW0vzJ&Zl#bbSfm_THgY_s@+n!e0F|>Y;mpr znqeeB7>u ze39CBS0w+lR9}S~`AOf8@zOk>y1bMLH%T+iuC7t+^O^9)jihE1(%O#Mk4Z8s5K_8e z4svy8N{D!&;6r0C%IDeVo`=u*jL;fGde5EdiqzN|B!Qy~vHhl|LK~h-V=3{8=nk+lpAQ0r~nJ1-V zlY{x|4N1!`Z&2edF2yXxc|$#`^jtD&QZr0S9xCuEk=L>x{>>7E7J`+Ok;+#TN;J+Q zo!c$x2%U}-t+no|JAPk5SYqoDL*2l$e4?c_F;X2jSl@0XIJ>K~1F41RA@^wuBDORg$*qI!X42WhMEouz_ zP0@Sjb)wt0)aH!u+)^rrLuq8gI?nY8yi(Z-jJGq&@e!kDN*7*hs8ZR|!abcI`Q7Nr z24hXXG4g8w^e55iA@$^AWsyQ`J6^HOwA7L6AR{ZKY&vucaQaOExGI&~+Bxz9BTf2H%&{{FiT<}i|R+No6_S0A|yNe;{aBKAn#DJ)usR$6w z+>O~oe=duetxC-{W!P@qXiB_siUY27(UBfz>EH-q+My+}k(5~fql+`5gG%BvG%Jomk9=3eF=U%Fziq3-L{ zoKlu*FBbvlnbL&T15wmDEnw?ZIY?~HJ!A(QUS<&1q0mwu4?MzS^rJ9U>>;jF!dBeF zZ?zu)5Ag6)pa^XViGw`X2F!|TPQr)EHeP4+`)1WHDX?uHTox<^PT)Z-8h$0 zsm-j&qYSb6n_8)iB?Yf_*4(1G$w;#*QZ6eUQVs-uRTnp`P;U3lgk;S}fGA6y^({97 z%3nu*V|<$b09HsmlT4u_2=VK!s@rcTMh3B8=#C#s8|=zBKQPZgs*K_xS`U13>VjCN z;UFS_bS!r&kmjO#M>d6B9HH78r<^DmCthqow2 zv7XJ(5WMzN!C<)m0L-zGXQ6a*g1EKel7*Kw$bB*s8gv@P(zF6~cavSmG}(kp(RsTf z{{U4*3FoH#9oG3i-pdmZ<*#?@54>Wy`a~yn=7fmOPIanU6$D|GUs%r$UH92sQ*HLX zHAsGRWZIRy_Sa43P7KMwDCzG*d*ofF;vjpRyJ;D!FKsrEu+XLD=9L%Di+~nFjK5GG z-g({mQ$og!>y<5?=xgc9^s-lEOS?f{Y=+_GPgt)f`{%e>&24)169BVgk=t9wKoOh; z>k{#5jBrH4v=6L_GJJbw!rl7Ztj&^e{HMzYrJZy_1&JgFVx=a>4@%#kO5@vB ze7eqNdv%h+HM&ko*<#C5y)0>0$(X{5DH!7Nh3G@HbOx|z(O0k^AR`g=Tx1E=8Eh(0O3nrIz!3 zX4WDavlHj7fktf>XO?T+H!nx#1NS1bBM$EB+NIXb1sw0`a+tn5*KE-OT_Aad8Rw`E;t)q+J_Qar=GE`HJrt% znIz@qDClRW+DO@2h5ot7=rC$O3av+;>)B7uq=616F516&O}-@~<_Qhu<6puvIr9SX zC}CAEbKf`DTQ&##yE>n_leXxi=91f|r0k-HFod=zV&p?p(6tnVx2Ew0ZA`pZQJN1cUFiWT+4v>jgXD1#PdsctVQ}SDMNC4)Ei1hMaglogp=x zHe+DAwQAC3w!8$GyLGU&2tCb9boDNfsJth!@KQt@(;Rt!ROiDiQOPdSWq#&De4eLV zvBY2D^FZVI3pKA)Br80h$^i z91SSKD95(O0_o5X^LjOj_I@c-*ym{m-jzo@X;){b*8`YFj6Ky82EFP$A^2MB(x0o| z$D8QRukXE~L^B(KWJ>kq9z24p6>Yi&O6vm~4zdL>L!5dhyehkC?)v$*U)NfX3e+=y z4@4#mG8f#PtPM-!qia{g6$fs1n9cgC!f~LNSn3AqjlBN=nCF;8c4xkAp6i)sSP8xJ zSeOBM<%;FK$_`9?i!;p3zCW+??zh9F&8ckFX(P7;-W0G>XyE6u7S)7qVNxdmrzRU(G zo3=lW9IuM?_V+c(V`11#4Jq@_Q$`@3L*Ze0t(HKrX0qO)?V9y0npWO^grp|*A6WcU z?*@6FJoDcShRn?0Uv=I{ZB*FfXE)W2cHLb{!+ZcfzR_Re4uCUOd1R@b2s<(}$XRuvnxS?*y+wS7Bqi`>;X#o|qAi>$o&%e5KTYn$2~ z8x*>fiAkC5+f*HrYkPX0=7Dy>e3w?mpBlzFr1gc0@h0@XVAz=^z7x{?PEO-EdG+_M zo9`xHqvK#Q&|L)WaZD*d2h345(lHw?D<)=37rt1o%CPRH(3>>XwQB(cTNwwow(i&+ z>m330EZ8JCL1A#etXu_ZrkB}~{)n5MbB(m{knGIQcJpl0$zk42-ucy{BkN!HDDTl2 zx$4YW?qdz!p+kx2R;dyyuT@oLquS9nG95Q=j-D2F3%w7MfXpI0%h_8@&9RZ+nb&^m z8P5{Vr}QR;CDx8?P92Y{(J39#j0_K`=~h=V{2p|UXQY=0+^^TIuXA0Ah%r-FzH`+I z^U~@rinPuve+IoQarH_@7TVJbU^YrelX?e3$})4b9G6`) zE}9x8%{|E|g&m`39YPyZj?6kX*P@BjBsaqm)U9a9^5Cn_sOhdUDE$YgIY8Gk-8|2H zmPmB>&wb3XSgC~*bgC1SB(QeH)XN}qGYLI8b5ze|(?V3Al|@N2CqrLXst+DgYHUNU zsHcz2CNkHt#hIZvwCY@FiF@;-N&(e8L z6(2Oc4xP<8cE*FWtQlqJ{WSjoFesMMZBkRpXPX;&(${M-^QD+!BiBwN^7a_SYIg3P z)4sjs-EI%RO^NE{_=(Llv0pPTo~9Cb8J0?k(^r6k;3b26W3dHmC+JkF!RQK>YTT6g z7_^oTI9M- z2`8kwG{~Pa^T!2RwXit%Wb52aeSE`!NYT1K-PnQF*`|n1doy;`R{6E}8Y`qw^TUI0 z)EdgWJ>svUktoE-t?aLSx@Vmj5LH>n$AafH5b!Np$q8-bw|#j-uKUj<$lj@}N78raRf`Q_USoRY z57beaZ>Tw=XT8Ukujj43We1#RnWtO251!X>0Yc>m;8FB$RV4PJ8(GqltJ9&(Lg#+f z+gaf`asYSCK(n5hnw2;=nUEwgFo=$(?3MRhH(YB83DTJA#ZDQ?_-%>kS0czpUlr_G znVV~wndIBo%=UhQluFuvyP04B2&S43_d4?WkCZcbkA92g*>fK$sA0qSz)uydIF-(9~617 zmG6yi{Bt*avbeHPQ!p}JSua*zQdqKMLcVlRO1|d(6Qc4(TKVq1U5}n5$r<5fM6pmY zM(XPrNGZtTt5LKcdC2(U&fa9u%IY8_R*J4|tFor`;>U$bOhX_ksGCIZQ(V7zIeG4^ zlnB_hR`jxT2P;clk13TnqgUMbZ1X(#GbGnvefL9C0DMRDN68gw*3pPy?!3juM3jtW z`c~`TX+R{@5s8PI&)3#f)253YO><1l%yA4J)Eefj%YNKeAo3LYjh6 zuzpu1>n&!)(O%{tj}$dCNL)OGy`*?!C+2X1A`ls%qz|et2L_%2&>+G1mf0NT;%CKSB`?=`WCR3Oa4B zcwj%IC>#Us{S%)1;qPvy`cjr(gtxT)z9J~#Hhv1xji`H3G?{1Xk|H97(!?HRlbh{8 zR32x(;Q`r~jm@(8gq2c zW3Qg)nTEp62L6*5?%wF^iSKzM%Rrhqw^N|GF7m)r`?YUx+zdycb2^Vnvo;9?Wst%$ z^tNh8BW>svN?96xTGwZ?9cl}K2aM}{L*F*?$7QX~0Ojv#>CqR~^6gEgta8j1q@})1 zNwd`td}A8=t)6C%sbs4_a1P$Rpq*h<7RCcERLfScfvWmzm&>t37StS7cL94{ zo3P8|-0c)rlf~zr=bDitKKW05m;lX`fomqF`mYRYuc1{~qc_w;k4SXqzU{79i%U)7 zR`$(}vUBO~ROzog)1BDqyr!=eB+-|_DDC{ z-4Y#RjYQy^XrcP&UpNJLwje)Q819dhd}UUe;p*}!&7y5)(`l(0?qN=}Kbi}k=(}1u zFNNKuQ+eySR#zb&mnXCW%VHMAnaA$b(l?OMhcga}iz7G-ZytP!W}Zr5J&H#)yX_i8 z;Vv&P24Y*@#eipl`cj3S;cJ_y1Sfuu+c?yu^0qVm#nY!4q}tk`8T=mnY^b*4)bq3*E4S%RUqwGc+t?E zGo6g9-wNzAi0Mn*^srm~(}q#$YG4fS)kkg2MeXAv=e{sZwbGYVt{iT@C8M3ZRbR*) zILDcpt4CQ<=Maz?zNMJVCJMX`ZQwU~X2YVXK{q1^(jNV3w_q35D{|G0_ZWg#-_2MT zS(=M+yAoE`R+ZeQW`j+{S$u?@;HJSfER`HxaiBd~*F5co`Er#FN{QPuGi{>xGtA5) zJ2Tz1!(Bj+E!(Yennijdf+l$8&&oCPGsZBmAMh8JZ&-*U5NNu;COg*GfTCo+d9-`7 zfUTL0D!%8w+F}XO%kvPI`U0x;E}r%>xz2gcvhI_MV(#BG%->-@e$QPU9yw(3RNNLQK~utlJV*#k`w&TaFLFztIG~Ad!E@Z(m1cIYn#ajuPD-6trJ~E!rQW^ zkkE?l{-Sr7qvm+xo4$Fg1aQHFfKwfoQl*bN3x6XWBz{<)gv^2lZ@Ft@ zMH(3c{{TO-TeWhfSTxMZBV`9)2-dDxU!HrvMOApbg1KX+E3DP%M69K>bP$}_ezj?` zcJH1jI;8da6Yv*WN>8Vjt_s!BiJHDK`OY))*U8d9)ekV_K(grOpJ$=6hhv z-yz%E6|7dBjb>A}REk|0WO3k?{AB>*Rn%t}t?oCc@2QE%t*XMJ=>P)$)S=}_i*yHPI_K0%SVcPnYX%n!W&&;&u&%QefEAq>6cN! z*6W<{epjm`0VKZknTIO?NonjHjXlc4pElIeXH_}QRN~&~`|i7Qd!2)l4e_#>PLMq^ zCQUhZzUF4Et39&s4k9y8GVRWV$~@56*>s7`bZl+vd1!>dwr&Y~(G{-sV|?K>bnF1{ zT?bT_v{;>a^iWf6xa_dwaP~`S{@ov}>gxcLCKd~~}9Ebr{k9VrC&ht+r z_a-7EGi`3t%*^PDFR&ueJouKaW`3_*n;8OUqIhQV0QR7Y{Y(!cDdD;$i#l=u7A?Uk z+X{O-bN%&Z7{v1cun*iiznPwUt9qAjzsJCx1;0Q=vC+9TsAsD-y4h|?&6ovOYF(i_ z2ubKP(gJ_1)Z}(rsw<4iW1g?1x^F41a-WvUOS1wie%+GXZTF-MV*39?D);L81sy(@rm~O<+Tb#Hk?A>4^QP#*d zkDjW7Kc&|6NgU+nMD40b`x4~j)uTObp`%Tx1%~r{&pppi39r~s{Q64Rlglh2I&Unn zST47y%j+Vx)0v<&t3rH4?t_fvWv6OBm(5FVyXHt#WH~EK7cM#%Y})MxYtzAJO$XoH zK)AFlVt{+JvZEs8Gxphe5U4X#A@eiBWBv17_svc{IA}J4vuc{{EGf}gc^bjO$+?sC zZf&N$1D|Zh4_5L><$-x^sXyq_adE~~{{R@5S0L;BGPY)UjEkA>d47i4?}xh5%E+{v66<8@-qMbR*R7E;k1Mdl= z=4NI!DiP}pzdC4agCb5fGIB_xWDUI>I5wtPBdsjC;Yw4jW$O&}8CwAIvth;Nc+9sZ zl94`~b$?8pkY^>?X+mzw>%0pSQrlLb#B7cgOv#a_*e~*7+U5}-ufA=3c5S9?*6w;r zT*j%9H|whHvylG7$QE=eMQ3A-2{2(Zz(-xs!%s7@2J^Mw?0QBllF(O5e2S6doR{3p zn4nzuJiANW_QYL~*BGr{Td&-RxRU)dlhrDGcuV!-`WlQhx!TO~Q|mkPP0lRVb46kX z;RQv%UzYT`9iE?Gtg*^--!nYfsr3VG-wrNl55c z^%CUP8Fn&w`T@Q_rewsz`;(qq@qh=CZ)j_Z`7jbNNkElavw$I3h2cktt_CR(rq`dAJ48}7@#h8H8=}sJAc(d%Y zrc&=!ct@7>z8hv$X+6zN&|zP(C7Y@jXTqcy<86p<8jDXZ`K?TunMY<(5Ll*0o04`) zPsZhO%tL}{1>1wI?tNByVyI)IUgJJhj$70u_Oz4fUGbt-YD0=E&M(;?sHAO=<&@uQ zCad}BSXve#-i?~?sB`PPvL1~}hx&Ule|7gG^nU^vadp|#i0zqf$iH(lEa?VM6OT&c zx`*=5V&n71JGXL*8x|J{0=Bwx0s$`L$}7q}kT|Su=kr=)RneniBO!6cZQ9oxos02> zjL&?`Wgc7?M|R-T35~J9QeRG@3ZlM`DiL~hdu?pAt+Uap71El&mTF(R#v85Kqr`{h zh&?F|w#zBaae*qt1dRHnbiFIokEauY;bCLRg)v0n_3LakAnmbm`04GjF&^t`daUhD zbr67r$qS9GnDG*Rk5x8O?HrS&@|kORZTFeL#Y{|h*%d05HmPHviRzw+Z=2lnUu*MD zQfwA>zI3(kmuDH7neVtpoKiLPj#n!0v%4$hDY{P{DngA4{<1%=?awvquF711rw#&jdnO$YFb^M-HF!R?2K8w!m469_upEU zV>3mS>Yognp1D1I^H&EG;!#I}^KGq^10SCBG>pTdjSB4iY=1~W5r$eRCK?eQS!Dy3 zuCM#q8COHG8ZDdVc(=v?91AHfsl>C(+SAW{)QIewYGeuZzDYW+9xI8>id}gi3qZJs zO6k%#>q<4Y z?$w)LjdM>&DQ$A#ok{yzeS9U^66Y&&sr8G@YOhC zsc4O`ZYl)oi<;>SUOVNuDGn-2rW{@4R)_29A&{}Z5~hp>wd#Ivu9j^HjFU<(?JH5X zRO@`vGoX9Ved(#rN?b=Y*W;$EJ`*V;4p(Hb34+5U9Gy}X6M*&M1$O(e6#||oY zIwBN!zv*_adA9!DXBHFMn`_Z|{(0sxfNdgyEOu(|B|eQ8J~5!I%Rbh(rjQgF)1Em+ zK?DT%eK@bNKCW)6cq;>2k0@wfQ1hjxwJN+OyuA#e!{piq#lER^F(O7bh2;x9UARvQ z%sZ$y;Tck)3oRupflkXFT%dHSQSdm-uf4esE=O_-ww)5rf_YXrJ#u|7UM?`xX`1{d zu4^{sg5*2&MCy={DbRV&4;Iq2bUoTEK$t}sQ6*Ap{Wq8%EWqbPL2WP+6zdXYok)k% zE!!O#u|Ba49KaS0WGZnk{{U;`JA-CuLNJ@2l0KoHS|r`gty0e)P~_8Z8&vuj1+9rg zS}muy+rE7*AU)?cv+au3vgyY%oN~E^-0W=p1KSr{`OI@M8=&XQx{o$nBzld?-4mTH z^>Qqnb!SMoVsj+D%_L_p5>b~I)lNO;W6-TU4HlD~)xKm3e(76#B}*j`ic_HAqk7#34O7H~h7*;DYOq34zx7W(4Hz4*Y0PwW;3gF#9^{}Y;8Sfh{t6@ zN~M@YZuh8rE~om!rz~O?Tq_xU6m_}RdEjAgiRU`k9@?ZMcWR>9(mr{keh#!{S@x=G z4SwCo$c>S2q1vE*sY8@|o)?(F)y>Kcc%~z$@%W9<@?)y~ADqZPEj6a;c2WQ&gz(A^ z0HV2esGGyLo+Dz5z(vuP&jcSC8nAnn~2L4CF!{q=Ip%2P+J?=&BFCp3ma z{T6~}KA!MD7?-z=$UQGRk(03?^I}77lP)dCl@RHm(6rtC z6vtfQy1~&o$#--He!cU&CEz&WKRukcNElrP0E;^_HIwDtqn~r1a{YlFIRtg+A?j$m z^pPv2IsTrP9Quu9W>{ig(%mzaDpeu4mW7)wH4CZ5Q^Hg_*F1pqjHd{!au|;U+CA+_ z(s^;tkHyW{A6M9oUk&c?Nia707DCV}QlZ9n(7fO+a+Jj%UfaW<$-$)2k-+ydm5Q?5 zF1MG5N$h5Z^lo-SdBJs_db=KbmbynR)#-4G+-&=-`yOL(UX988J^<*5E=O8??_5B3 zO+75VTQt=F0B+`d`uSDZ`j6Yq15~m4! zp*`(#lJFX4N^)N9&5@wdnR7=0VxyPdxUr4!CoB>HGZ4;=%4e;TpIDk*EH|UuqDJfi z{VLKeS+#QQu}*3C#+PCw!FP-RJPX+D#}{zpu9TCRC~7Bck;!K5O64jJJ6eJ8#&)UF zOF^n^>iDT83>c?Ra&Xo0F-~QI9H|4b;o=2s7V2>5Hn( z$}TCpHp06HzWCfB+nM<^!~x50hTCZP(Z(e6eKolbn(u$;9v-zJpxV5#@5~B2rLE3L z>PshA`SwYTQGxwB-(TX>RtoFV`SQg|SmS-w7pwvVBcntukqtJDm_r(j?ZxYGxq<=G z7`VV`83ITjr*nIeR+Wuf`1vM0wPRT;91#g(eyZx{LdqN zCPv5YzKhNJKR@=DK%r#^T!VHvOAH5U*hbrFk!EA!e?{ezZ$GtlzYf&=Wa#|tE>!ag zYX_MZ(L8-yb|ptfg`+?gUIr^If>?=8i8`nad|lq=1r z2MAOx)A@}Ot}1A(0jM;oX)C1xjLj*C1nI#wha?=iO7x=%kM?=!XkcbprRhoRlco`f zSSGr4U>@ppxl2iW{8>S`M{b!_2AK&?idM<>h2eg!J($91?iGBD%$+)Y7rE$*y9D-= zbW|*_TBJfN)&|2==*rD1`Qx&F@fV>>+3$6(BiIubBy&;d+}fT@TwTUWs})+*pR`k_ znd7Jqg~|5Z)p{^{m&aMRL%H#>dux{9i+!~EBaKs_M74c%e^N;4V;1X3bZOU~Id?)E zfqtPNW@I*Wx%!H+U;${j1MB?#L#NQ{Jn1i`xAKG%bd0&!<@#NkBNm0l>*a1*NPfN^ zK8dWFump^_S%?bT%eYTXriAikUA^}0suNxdx19u;+trtO%meR1(Omc~-z}N&NIhrr z&rT4hOuoyc?$z+0LbhWu4uW|QH_eM6u>(r2pEQhw@K(EIe0GLrk1Nz`-72}Z=6W|e z={(>>ZsBpanrSmCp*KJRq9u)c^DT2cavIyr{lcWxvuw3Yg)cgkT)O373kuBIf1!CY zy+(@S*u{0ge6971lj}zP#*+MxZ%e^7xkuOReAbLlf(BnscUiU{tS9C4a}pG-3(LB5P3AT}x^fhC7+4}s4uxFqQ;d8rPFlynpx zooZwEeFkzWG8pU0i-F4yH`VJ=%C-;1TFjb5LY)P$RWT|f`*|H}z@x&!sx1qFLMqcdboYLJ#guAi!1f;2I z-Riu98x_uUltljkJeA5Y+>D*pAmaAKi!0&wZ zfEC$>ys&bcJB!T19k+{7@*@=3)cP}r7I$vp#ZM!hcbl12z2#zoAEH?7X+akHyO#>F zIF@AzdfIedMOuoMp7#X~co?GuK3>?1d2G)YQtdnO@Sfg^o31ANqY#*4)cq0did%YU zCT(GrLu1g1k}Pux)i@LufE*m&P&8V z5Eb-1mJ$LnNG=eX$!~GpV3e z(gtXkydd7{kb~^H-)QMPCb4X*Or&g}xXdefsSE+M2n>GuRsR`x9YcnQqnWg=^hTLtu zt2F8e%UT%_^Jks=a^-4};ZfLXsLUuYHrU<5PrSujUGye1yHKpauSuN5z?AwYv{gkQ zl(l=P^J~jCgnC;7zvvd=&FrigfF+8bM?=e!NfKRWkR^KTYq}Jnx!WuBSOWW5m>C$g zYDctSEj{Ig+3V-|pQ4{oBG!3(Cny^U!m5yY*Q|4>g84taa)Of0y53#9A{im{xO|r0 zg}WCGB)z5*A=fx3o|Lo@){q$VoyFxFXU25Z z@z5Uh1si1CINiOOQ}^>-?EBg5@K$jqy37=gjtZz=UQvgKPev_F3(p-=Fz_t*!Y2BG z#BhA~Fu+G1m@fXZ20l(P)+VdX0)i{!v#%Lr7iE04aadQepoAixU?ZwQAwGA>d3xco zLk>K;C zo#sWAp|?lB^6Xd3H7mw&`>znm%GJXa12hw@8-F8!u~KlS zMppRxyT74qVbmIxH!uR;S7oJNtsmxSkz@L4aotCg*4dqIw7TnZZo^eVlA(^+mCc|u zc~Q%wt-^;B!1Pzicn_=UQIn+ME{n>EN9zmN{w-eBP=zJ+Y?Ul73@2 zJ&cZ_dq*nV>CB8<2J$e3taU5A%s#?c)%7YR-G0qlTi7-SOBY1bIdlwJt9oWLY_qQ3 zwX8?kxY5?Oe<1DURt5a7V6RCQ%M%Q5Cep{^;zOV8^D}!ydY2{- zcEGNP!OTQ&E%Oo4py8xd^B3bJT#^y9=oTO))%dAT8<{yxha^fYq34=zoO_*fUvRDb zUGDr+gzQT)N11HTOjo{`vRAgNa-5q6)G3Ww}1X|0A_ zP~6DPii@x76cr+22ru34olo?H)gF%B#(-vJw6)CFd1_M;mF9*HH| zxCt1}w5No80YIvo06wVU&Q9_lJl*6d9`D3yvM*_^m^HZag zT1{d{f*KJZr+Ts-M+-bmMA>;Ii%ko;N%NfB(Z&!M;3-ALb+tpcP(@sE=N|agi@mRj z=+LD;o>|jgpwG$cxga(%%g1wUje*aW<2t#W1#0T6WN9m>%}7pi(BxI>!)oZMCZ1cP zEhqEeTBfQtZl`me)OOc}ld8o9Vk`-AIjIR@z-~%b-Fe>n&(YXcG)F!Z7nh+jx4hU` zRSRC}A)&UR7nxz9GcHs0nP$~9gS75~IUiC+tEio<*pz>3r_6%3nqOJB69-X4S~jI- z10Z)gAAKh;t!p8mx#eB8?%aGEzf9wcAwm4Cd_d{?vZ}o< zq_na`@Nh3GO?!1dBeLH}P8ElyAKZ97HuWg>^9a)F%{*9FSmXVSO`chsYq8FW#_^?K zwHp33Dw7^UtR@ET-jZdMJ75t0_7t!dm~I!Vzb+Z0HseL!>cyeS5xI5$01iSMrz|JU z)VZ{mx1{Wzd)k_Ban9c%apoi%)halr6o_duLa&0`Z7`^8w_e^+bV;?9u=WGkfZscM z*1=jizk~8^t%`Q*9B4@kLqXdq=V(e*m|Ys=_aHk;k#8GXgA(@lcaEK>XYLEpUHM9m z+uJB{74Ct=etiSByLyK#oKUG50FIM}Fyp1;u|x4(wvmK^jS9BjOoHB?)hG(v6Q?M-g*!ka6Q!UDhg!V~^17__Y`S2zMdKd5YaEr!J zdaom?ql%5wLxk($fhAzmYIbxj2Ts53u0tx$*if8$nJv!x)wO`>o7(8=m8y=o%D)4* zIk{2J$e3Ce7T*B%Qd|}r;wL6?EW2RNvBV(09=ExcyzPYfa(7%(8!2=>GcFc&3buMmDD;>UtOs^akZMi0lJ!!XpL~)H{wNc?`eZ4j~ z72#IyldIsJ3xHBXqdPr%qo>j)TTkf&=~D#xV^a?LMc< zds63%>z?fux~$cEEW`Lqy1i@7rWTSMOAHhcolW3l6fgb{yxy1;&m=2$DSCQ}R9U)K^a`k#57Nu6p3NTbu73yz4d`?3NlOL>g9`#8Hiy`Qf znq0v4hHa(l^}T6WBqJoAqu5YB6X|@Xxd&LFpFV9RHf`OQ28lw75>E;Aft6ukPF^QN z$ljMk+Z=E&j5%C2{pimrKGe0yB!PW~5TL|`Vikz_^ZWSNI=XGb({xySc`iG?pl zFqO~rh>({xt&o-x*3ck&=sXPC!9ZJiw)XltK?@H&!jTtFWv)p+c5W8pr;GrOFI=gb zoPxIARMzwoct{r6j3G+&BS73M$x<4dqa+pWJS(d*S>bksbtGNHQNGHw$)r^R#L8Jx zEq9Wq&q1*!}Aqzq)u44Qlq4 zWQ?~&UnF%*f)&n%&OEIa6JWspu4%7O=8X!z6&^!zCn(KFU47rA8{)pod?EGY?^JXv zw=C(rl*Ywb(fnM?Fq*jO$hMrjDg!{;j_#zCR97^oCZ72>PC#zye?BlR+7<5J&1uEJ zN;gU3cjs>By6G*deaDy$8R6h;s}g3uS}IzizD_`9v$BdAW+oLg{bDsI6^&05gnE6e zEWD(rOE$g7#`GnMv`Uo;LghCtHV)Asao4w88}t&#D%Z~$S;nH097yW4C8w_(MC#Ow zY3HLA-pS7v>F+Ia1Bvu5X{SK1(33hZ6kpybriae#pa-+_bk~wLxU(wN+VHw7b#7)a zJnQ_FxOj?<@rX18uy(nj7QV5}k$th_a?^#nX>JWXb^QxS?evjAG+ywIkoJC4aJloyE%N7Hr^-`WRc}WVf>qw7$o%u6bMH?tu!WS!ivEy{Z2Rtet8c*Iw`irY zmnI4`t#h81fh7ukB9}7V*c9TX=_xBWZFMd|*Lf#Wf2*pYUTZ)cbJo2?&q6p;k=Fn~ zH@oIqfw!q48RM;suL!Vh<*I#NpE#FG!y6R#%Mryr-v>#&1#6gGGy`n#pJ?K(NS2x6 zSATDYX)gGdx7O`j<7rO%1wLP%Ld#cMON6fzT=uO`GUsqe=NV<=o?ktliISate7|ye zEdU#YbntflgQarrN41Ln-KCyB8bT;U2T`rZpv<*a*GOcQQchIl%}7;6E4o25I;obf zc?(jp-PVVKC{r6n4FA{Z&X!U@VOOEp3g05;Fp|dPrGSbkHE}r4b)x?kSh|&yDgmM zflgV7H$9;9)22WOIlQ#$6}L-xVxt81iqorZgqa@hx^tYGJ+4yM&AP>2TK13F;N=kJ zZ=oK5{22|uk#yt=8g$T$Lv_=mqUkqEMs~J3^m4mfg@qsWU{YV1geq*Htc~%I6~~3%w5@ zUbspcEHc_&<2A@odAbF1?OBYg-x=6BdorUM)k58sId~;Tk>MKdCT>!lSV-Hif^J7N zUb8Dhe`?R1^J!q-)oA_CdzUX~=ABlzH;+7?3;j;g^5;$GofnPPB-)zc=RDTaYCpDm zS|DAsbE09gP%IfPj4M}rPcK@z+EkT?vaEC^<(s_?8=!M0LE`&re>UFav~Pr;qw|f9 zQ0_H7=G|zQy!V+^CPhkJFI^<6`O!u3Z43h6Oz@z)P5mTS5j{_!a~Yu4QX7EE+owJ3U9uvs(56Hk|0dnYxd;texFW?7)&cIwm4m3bT{UWQv5 z6jbQ^?%=ulTaPzOfQGUPGEgmDZRV20F&HA>ad0sKueO-_hia~4BJa88U+b~)LhYRQ zGs{{*-8p;wwGLGSOcOd)LY+Y!F|}^C#vS=znrYOz=eqlBWu59mYHrocL7Io8@xYBy zcy$9`pgA+QI6g8i+s~0R%B~8QBX3UBmCnnr5x(x_TB=elC$ujw$7f1xotCc>p>mXd zdEp9Mu=Rjkb&-3cb8V)IdoHo0!om_WpsVSzVIH1@K1%_?6YWY%C6^>U*BVl|nZ`PH zQcv_X%~g8$wq=SAOJb*k-|62_FRIghWriRnDq69f1kWr{fb#8{?3;%kTz+qsDO{Ls zxcn^`Ut{xk{(EM&*?Oj#th%uE&NvadLp{lI4)@Bs(B(&{5Jz2PSk!QKN1k?V6V^*N z(6-mI@(kX)O1YY`dn>cZE_4L5TSp^+_;YH_U~> z*T?!^F?pL=vr1ZXKyPu7v9@$>8qAw=@M7*F`S#D-wh;okzc0RSi^{dCG)S+$2LSO) zMab!v?v*+E45q_JSoPtfOa+I=K(LVVjQ-RkM)srpk=Yhl^F7w}9M5&=JEwGxd0H&g z)$XB8w0E|wu#mRyf<$G?=Uar)Q)q&j^ujI3uv)HL(b{RRym9p)1z_1EB-$uLt-iDJ#_`O)N!W6=dwq) zp7v|}kUpJDmG2%i2PbV!m<5X1R9qg|*U}ME(oIPYwWV!!###ffdGsy7oCXUd$d2gX z5eq*wWE{hzaPw9|IHfk}IL*Yi)fstYvfQn`Y84JoKDWkt)V=4ERMQDTEgT^@k1yBW ztd}J!x8xqn9;HR+zGc49l=Pi^$-M;35y2#(vFig}&eP_Bf$sr;uMhlHm2R1PMpuSK zys#P%JzFhq^k||=CL=w+kZh{hy-2ZvJkZh`Le5R6o@JKqp~#gy>fEO`*zg8TmYLRUNAB>!?xzM583(x` zwd+=)rRHO*sRIt1mgj2sXfaT@*RL~q=)!JtzPt0W%Ho&=kptG(ONdS){x@++xzwGb zQRQCY5qmIDt9Q{GWxa2R+9m)VS>I|t$i-NFZ8h7?ZqzJHxmFwZy-rLC0o5*@MB3Y9 z8fsk%CwhqpQI&(07mOV)9%LIDZYGnVta}k3qYNTDnu_J+TGuZ0R!%BBC2<_S zOjkbr^~#<@P;^HMEdOWjW{DuCEf#oz4IisSlG>Kd**G_X`=P0nY83iG~Y*Z7CTGs zZa@G;?Z(8~pyR3S2!NcLMexK{NgufGcABRuk0-6^`cw!*PtE1uYP&*9!M8IiWya7` z)V+MYqPO9xL{hvZ`Xd7G8p+Bm^o9<0H0NLB$z=uKbw_b-Dw3oJ+&kG)d8CA%Ii>O2 z)aq=Pi%A^JJmUUjtRX)j~=;Ag7UeZo~q@~0NjVVkK<6@ zDD}xYuONiIY!p_UT??f}#F$fyE$xzd=D6bEGnvQNc^1}81&rA=)&cogxB@#;vjhw) zp69#VfQO-TGsc_SBlYO|MI9!$&v}Hzm=ulm#68A2@k;JTlAhG*;2xffCR&|3e>Uu_ zbXEB_-VqtS`GsE6v_9pj_p><|?BCMm;+kE-mw~S6GQ|Kg={y+WOSX&RFv)N)SGmps z^zMwVabrJY;|tC!<4cOP_Q}K# z?SA4HOXt{R8^1x~6mFAUIzf-3LC#Rl4(>L2l7wxo+?S;D;eaoPKX^@X>+V3zVcFg*_C6p^ECeeZIA?D zH$mzA_ugXE%glV5FD%!;+f6s(dr7a_rfHnz_KP z3!>(Pu(@m9*z&fPai3+W+Z2Xns+oF@_A1GQVl@T6%qM`AvU{iUq@3y!&{T+1a9Ap6 z=mhW)H@dkymiXq%Z`&T`9@nhcrJi@2+<{}xgo_T>>S@srElAbLdq)$3%@*h0Sq&bR zN1M{QE~TkHb64edFd?kXbVo_&z$<#-o^BSdlgSNpOMM|pb3FHL+(-2ON*>Qm<)uN` zt?d23is@3>Ae!9Pwt0=JJ7)9V9bETc=1Jr#SghKl#MRQSZyH4_jX(y>K$sq0SE=!U zdRc?lrLwThIl`R2EQP{-*U&smq0m*TUrM*97{IX+x&IKPEXyh5Bi(PTl# z^oZ_qhqDx`TC#dKPS_b;{SA+rP$Czvflg!S0Qp7R9vu+g44qrbonT@OTc#J<>?Z`m z!WwtC()e@FH^)uU^D}$)LH2|dQg7*CJ)S9|>s-o?U)XMV#=ov?#I>2U&I&GVEnh#` zBqr11!eb@gx<;LesYgCkuT+g*X|GV{NSR_QrG*()A>%MjSb~g+Rji?c+KLdg4) zQl>&d(3GbhjR&r%F3AZBI6f;s-m^@&T<-)8D3``UH<@w8&hIk zXH+gfVnC$E5LMZ_&2xNvC@!>BW-EPYD#1UZ)j8UCvUF~GM06yIY1euCVnH5#XWu(& zs|_woKeaU2^=OF=dy|wtbS?(Veza7<Znvmdd^Wdg4z2!xDJrUhS`s33~Kf+^COQ zK~u@&*KX&>Mf-lpZ+vB0+ud2#Gks*}H_bBD`Qg>$lyyl^(t0B+AsIE7LE$pLx9;T~ z^OuMfJf!d`urt{4v?P=%*mdY)phWa&;VhKtF^{W80^!b?_5m<3CR(qPMd24ddmdSW zT=IIpZFa=q99J4TNL8f{>Boe>w3f9DmSl_qzWd5<*j?=LZ*r`KrwuUa;Dyq03-)E~w-nCvSr?dv z41z-*;0vZmiKZqyQ{P^>Qt!03xYn5=D}buqAGE$}YL3LLHK}x#j!Y}LacV|-LiaY2 z)Karrmeyc2v+nsXNan4Zcx8-7E3@rRkIfF#xJ$LDuk(l}X@moUoig_5eF4abbLh_X zvkPHR#b(gDu6cOy+trr4-xJ>P&}ye>vA8LvTJo%}c+by4Iwg8^!d7@(Me3|wAD4*i zd|Tb6uZP)@=}38ZX<%wya_u}cf(3^H>a;LZh#{|lK`{0unz1mm!cJ%gjOK}(@736? zt-yDd)VDIV+sno3P%Tuk1?`8v^q}F3u7F&5Ox75gLb(YHAn0=9uEl3jc50as@2vW( z=4w)e@M*aLSLxxJT~PvFq?hMG<2Cv%7b&B&QsI8shDeSxqSx@~4dvSGySQ_fWzMvW zs$0tRnRT#4ct+;NAuPFWO?<64ffgwtbS7JB3^MS$zK-44Bx-RRT9VZ((bJGYg z$$59CB3M>36xrnHcCuU_Pgn>q$JIvunaY!)eoL<8WyL3)W`>{G`SP&;09*jm`WK6L zZdmus1HU`*rF(i23oYUc&GED)+UY5aB`9=GO*Nx+O`zPN6MZjTc{3}DI4y7Vj!yQ6 zw*H)c2u+WI?P4uFVClSmyetdyX@7g-sqfN8t(TxS-D)SglDfs-q*CrhVj1mg0FG8) zrtJ_8E!rm*Q*J&W(&-5*GJ!G(_|qPzG`Kver^J4e&?3YF7$Oc4wyN5kb?O=usa=pg zeLUA36(RS1CL>a;u={%3S9^*-jN4nPyQz-!<8dzgbZJmG$XIti- z84?be%JwD!vv&={$Lb;aN+4yBJj!+}p+{TXqR>2`*5{?DyUbE@pL1+SL{y zWG^th@JnX58KpKAV{Tn@$yTh8v4TtZKZcKg=~8dVf%RL|eA9;2WM5)lwpnQScQwn@ zFA42slGAIl1)X80>O7aL$R-2()uW}GRHfGJLVfpNIszA->ypEMmiqajz?j~)U@vYJ z8-FdKO_9@tXQWw~xtPmUPuCHS{;bq%s3L}Ver1;)nwF8(IQ-qZzbFZ|UM$^F9DhV} zNV#Fm)qT}a`V&tB1t*(T@>T`LD{ma>dAt>_jm@9=%@!q6y>%TM&pMi19dm|T>O(pF zI&?0X^UZ&Jm7#)_g8Y|F;q;)NrTF5EMuP8ZmOHip#5+}Q8vt&ABEk$7n*}mS(@cq` zdlUH`D%}IB*b(hJYI>IC0tm(70s7J-?Z9UQG{@R1^UoqXQUEnDM)swha}5mg_@aTY z?0U*>zIo)m2b|}*?%Le1=0&*?lUlVxw@Y%G=gti>9wEs5&l?!IymG=cWDROs^Zfs{Py+{ulN1_@>Ew*jq?tjKQcHTVGd$WzkEk0f4Z={%%> z7lc!cCi#x`mZ$1e`IozOK5r*m;(GL=3Coc(bcJLc^fcCZeiq|ZZB~?s-=z3*)?eP1 zWDTSdS-^XDO$pnwl*qbVcBa5K0M5L{cHX?Vp4Y2w>#LyCIDI`%1#-cQ)qU5S#cgqfFhgVLC&ig%XRyN&ovW5B!fJKYID&5*TG32v8v@Jp(nJ^@b)3}#j`#IcqB zgW&aN>d8Q731`PHeU9}Nn)+7{gKpN5L0QSWnv$R|&V@R2<1a2*lUNB=?OZc$r)9UW z0EYvy$`*#VV6b4yPMB4VPI`_&*6i5rOuH-ciU$)}`X?x5cRr#CEyDF}+f^=696;6v zGPMs_-9wKx11)iQ+TGXndtrhYx)_Al_Eob%nRNQrnYWBpLhU8UG>_aoz3$q1c1l}r zO||TQAB~Jt0HtXQF@po8$(|BvL6RQ^S%q*fnVYkW6X5~9h02n8j)RA5CrjzHu5ut< z9gmi!n9w!M>*;9ax=WP=bsE+ppklcYvv+<-5-z2CjoQ8{4x!0?dS4Xq!kF1;VF>MxG$*kdN;j1;xL@i zdv;()7Ps@vY+27D!b9Wm$o(~r-AlK|R|d1 zWCUh!>so)eDLgf{bG~P`OBMdWaZjF5yVz(8PhOstsrvr_9iwJ?P7duUpcF-u9@4JS z)cI@`{O$wOBH?p5tPlxt{NTN*Aj~?zZguJKT+} z`b+!_%OZ&IeZ2v7=PvzCyQ+fZqE6D7t%UcFgMh>+QF9RzB>H4n{T3WY2Uf*eUXooPkNK7 z0Io?DSEAM~thH&WeUEkYw;RNgP>8JCD}i^Ty{j|Psc!qGi}Xw-=eFGfWUvM&EgQX_ z+Q%$)Q1-`CJf?GiMnE^6DGY!%G5HN!_{9EP_;`)63H z7Lq#WsSLA*l(pMz+c$z(GJP`Dz}Q`a!GX_Qd7_GGhg3k@u+D8Jda%U6xi7vkfbJY* zZKyL(9K>PeIvr&93G}Y#dg%ImC)_tj=uU8p^e4|W2v#xCc~u#!-L6Kl9mv7cu6xLr zy5bV;rmm&HwCx;-cUt4^Qz_?iBgaP#)yt^zzQC7rjC-2*RO8MGOuWjiw#0CR4uBmmgZc#w8#pW-65HM#MCCOXt37L|?e<2qfFo?OyRuSvt(zxP-jt zF^4;kC`9ahrj|Yqdgei{(jh}v2vttOF#fW`V-SPQq z7KCJux2RDL!w4uGdT+FK;yT-DTN~EUg58LS6CzEqDl&BuylP8e%K9eTT~$^HsnLl1 zbl~2x&^PzbL+>8vtpv^G@jQ;>V%)dWk=IQQdIe(38Q*-NrtJ<@32hnp& zVqShsKh*PhFHDexUduV|a@$r-+N$%J=~IHbGTG2+@n~;?(cNkj9sx9QJ}X#I7TRHJ!lJiremvZqz`U9)+j9eBo{s+ET2N?2 z`R;1(*SO1adS|CfwDK1S#tXMj#e22yRXny+1Mj927wc78`UyAO;|7bOtWq=Ll$zgU zk|fsF{zWTQ)7*`ynt?Dk>_JJz9fT7g4IOISs4>5&sFt3wdLcROj)yqTB2JcT(mE;v zc+*R*-lz1S{Y@ypD-G%AE{;qWt|&B=wO<`Vyjg**EJ>+LV|rX zZ65SnST-D9TdSV=Zk}#KLGwX!osNRI&CR;&z!ek^DRd?NhJ{*c;b*9DZb5MR>~5-~ z@@R0tgXJt4$ALAuIF=>-c5T&4NaS*=SD!T%s26rKJj}rI%Q0H=&0}jU%T24b9x2Qn zw>p;v)1hBBUz_sAM!N(!tlR4w$5KH@tlEpGO-*-R>jKorBjH9qiOgvQ_G4>~i2Ke? zdV)&ctyuPomY9ND3Sz5*i8aWbL7RXj3EYx3#t>HfuMrzHPYXXHi(_o&PiSQJ3nyb? zqr#d5%4pJ^{>&SR)HgK&c5RF&v;WtjQ|Ps z$Pqc6rqKb&KP^$JBGfi3%d8BG*!GR&TWjX+JuLM5Z-^fApoIl_eTQ`SZjsKqK8oC( z!$uW4T?cM;Bv$8Kv#x}t3ed35uQycY^&mZ`Ye+$RuC?6paX{KHN8}aSg;P7&)VWE0 zyyF-b=S4Q2S*SF-nBT7 z8x{SuNpgPW$yZ906H&2$dhGQcYr#!+_ffJXD)68l^k#7W1TB|RZNKW47`Y_iU1=+*&#HNFe+*XbCf`baq+?S~Aze{;5=zIjO3GuTX zI$zp5XsDq5)A%XQd$B>7Y?N)iW6axbSg1%wF)v5YvQo{yl7%og2EaE?ev+)tuJ*3Qts6;@urewzm4$2y>{nA+_1x z5HxID+Qq8Z2Cz)H#3&uwc=}Byi0zo_GcAN<0N59L&ph%T(!0(r=9{xBLclY!KW5gW z*}HuX6JE&fhAdN+$3<>RZuX0;JtK@o)K`H-Cvu}LR z54^i^6!UvDU8@5((b~9;)q-JZ&j(5tFX+Ufod#+=%WLU*(gxe(l$i#@AqtrVds#*O zAEKs%XQjIT0AY^*06vK8mn${Ua-#rr>cfE(?f!9aGDJYa<1>qDKPdC5r|BPG;I@cJ z9ou`$3-*TQZ+?r2tJ;BNSlNYJX1q@-H!zW5fxJ9$RUa<_-IuBG+GWRG7k-uVShjcv zzMkzv5E5!Q=;rW8&4==#W3*^a^9GP10ZG%Qu|_PG`-%ias9A>$wTuNBsjUG`KVy0#C^=-Yz*~vFdW>@!oaB1XS!r2oS zlawxo=Y14cVq4=8l!_RqF1L0sTUUw?YEW}pUg~`3uuZ2i-IUiF^SO_!h)*sS^hk8q ziI25rxTD3LT2l}gy7D-~y-R`TtjZ?AT<8>+gvafcdP`4rq6c)MkzjVsF(hvUI}zw= zAT%j$PjWr4jf^ANdCK%-4Gl0WTXB0WdV2LY>Ig}LUE=Ha>nfOF=De=a9q!ypti=sV z4B}!cz2w?mZbjDJDLjtteJ98=Llk|VG!|c|o98_NkBK(O1>LcymXihK7FZ(%RHFiR_6{xSztCX8^3Cqe0Z@DUWcsx` zomG;u2mtM>9b`K)fH|p;*1MXwjkOy&*!^!;p6Dr~!$;A5vs%Vkhucxm*F3dXZ7$M0 zWs$vMpP3=~a;90R%=MDdQCy7S6@0ukna`Yy_rvtqI%7B<9oZ^y=Cw;iL+j&3^sgTJ zGlpqXg3<88m(z-zMq**6jD&#al9Pdj&DuGB_IA;WsUnMG0{zQZUUwTeMdi!IHQs)^ za-!n$xylkv)b2;lRovHzLiNQbgqtdbZJY)!l4_$vQBvI|f1I1V612K8xbqpeEUjJk zx}0_@J|tzjl0ng1IOyvTwJ#YN?9|Jy`g5nvUztItnMkctOkjj%2)YUnr5Y`VUH978 ztS-x^taOCr&{>8^FX>Y8Ttz4ZEoc`8J@X_ia%s>p?qeWN5D6P=SCVTk%9 zb-e8^Tu10_Q#toV2gch|?^zSJjAW9tuJp@0#~&C*yi+@f-=AuD$2be&^4 zIyQ4(WMOlo5vUTfmy9v0PZ`$UXl-_O^#=U_4xdIfJUTB=4E9i5=p*Mpv}I_Yrx$5w zMd)C)NdsqLuvsZmKn$X(O5md#D$_{j)Y^5=!yfd!Xitkb^W87i)5}!}M0rbp4K*QG z*pG40pETz8Xyac-3N&hiC6#*HYWRz8l;+N$+N+G+`-}H&bOdsrGh0u)cs`TP*q`rb ze3RI=5UPwjp}O$m=o0O%+wlVQ#t7}4o8FbrgG)=XuyP~fXt*|TY2TDOJXHCM+I$Rb zXtoOUfOmtm-&kr3KLarMzN*M!3dOpgM0?G7$F7K*V?x74D^8rwXncEAkYpZogtMxC z8KT!kUTEsETun3*Z+7esTsSd79+eH5(7BJkc7vRP#Z}|7Q6CpkRTXel+j>H*R>K*g zT)`S;5`6RBdiNsD?N-Y<1;neWgBX zdz3Ptwzg`!&(fMz4_~44QPtX;5FM7fJQOZ;JEnZkY4OoLtX}|qK+KX;G{P5{7e+bD zn2m%^IBp)f$BEFL%ojX5L1VY4dYA{GrNP&EU$%NM1*_h)I1m#r&!wLILNwZ>Cb9nj zpf#wY#~gY5w(G!J+;$)zye+BoH=}#hm1+e)p)g$|j$ZQcb9aVF8)IFlr_VVcf<8_s zIu|$Cmt)t5l#0zFl|{tzC2;7twlfvW3ecGyTIS83qWhZf)OmHOD)|liJ{KJv_5Iv~ z63(=9-+VvXt*4`Os=R`Yy3XK&m2*lLIaU&l9Z&}raT*)w z1;v%otz;4%mTs+@cSC0ZOz&0{Pr-qZn2hw#nvQ|B*B$EeM61i2vMfn$paks;0f--5 zp)2Z#R8gi8g1E*f{%RJz%5@l4%hbH})F9!O*X#&^m;n=UpEgB%nnFiWEl`1ll7Oa_ z+XKkdYgUUwm^Q;Iyw7Rk4QTt!A95bwTwbiixgk3xM;98u?b%?8CTqSe491|8J6|yoCGe$BBxZM+ z**5v&Me;N)O+|!c9g9+7c}-)Zpg&SOEK30`#Y)A|O?Cwfnue@F>*{=2<+iL(_iLm8 z8l?<-N3_k0;=P^>%<|t)=yZ8%ZC!~_ht|+T>{)6}ti;LAF4ZNj%}MZ2Ru;9Z?LAb_ zHRV?*MZL&rnq)BD#jjLf=^XObonxRy^Heu=A~u|sGQg`ku`N4vgw=JaVClUdl3=@C z8;v{j>vVpEuggJQ^fR%Q1-~x#o_st#zMeI>e;S?TG}H05WNV_kVYq&3iv3F0Avm^J zz4nAr;EHP~&vE|hfQeaDC+f({5=>Eg%VL{nMA?%#ENlHwus=+jj~GmrDrYt(eROT)bZ1U8=UIncv%B#EjeqBJtyPcV;0C=uc=Kqi-_ z1xJlRDA*u1;RNjKTWZp)d!o}%6Pq;wGP(4${9R(`FFnzW11ncE=v?W{Kwgc^?q|?f zhw$OnC^;&@9nxSnvslw{a6E`0WkE~qYglv|AbZ}hu z)?T3#cR;^;Lc4s`0m>N4uy~R$M`*OO$oJQ!E;XPRt!Q79U=MsCO11}OTi;T`XRkGU z^SGxbQ<8lqqn`PFokp~#!D~J?wN^$4?{TBda9@`vp%Z{Qa4;{r*9SnzxSNzox^K@b zVN0T!6F!-rjRYX4=JB+P=={F1Q)JbHbS`~zPoU_pbJf3OWQl81R|M&inqsEa$=cRn z^Qv?%f66)@g_OXWTF%+O9;KVOO15&51)hY1JtBo{^w1n}{{VCvt)v#LxHg1h3_MbJ zViio+7iPhxW_~s>`4!EfsQA^+7wubTP3Q|-ElJSTYB{{*Omj=~pazw#ndsDX2hp!$ zmoDA|fs8aHxFlU7EXv21$}ce16j(lZN`>kcD{E`0qlPa~IgvW&hwUr-lavnF&rMZ( zZ!>O8{Bmt08%1T85G9!B)dDnuz=c;@WBuOF-yM~<+o#JV{~9yU3ha<@@q;KZk6TO}+R1SZ&n75xl9v&xH`xX__H=-VMHRXrFfmGbUvpwv`XW*uG-BPTpToBD zsEW8IKPCyDq-@r(QZ^k-Zk0DBOuCUcDerN( zBK_;ZOl|>_-jGA*{<`~9j$T|Mzt9+X#(Tfe9JwbN8`02pj?#y2{+MZ|# zfX9g5Y|~Un4Z9N2Dy~!%nIqmPcLrQBpzT2D*nyO79rcHIfEIvW4po^9qX>cpr4=&W z^~;p~+g!}|tAb32XzyiB7{R)w*dIFsZz{D>hOly-2~k(w`q~h~7P~~>FT2%Ud4Wch z=a$IQ>RhC_hb;kh)C;KcH>O8)d!R_8t&EC>1(~?7azj?OUh6!Apmh-Z6~POjqV);* z_StK<@TmRs9m(^{i~|kyX+`n<+S9AIRsmg&V*UAg0Ob%eJEP57)Ymm-Y~&Y)6qsc* z?a?IrR_UKZ7EL9jQJUz2xz(l~m&Wv}@~`6kzNl=5E(Yg?tglzMS)TBw1am7U#(R3L z(QLHGQ1WBp>u>FTqMdJh(AzFlC~(wAY3^#qy*wG2o@RBW^pSrlm};VINH#a8+tZ_R;x*r>seKdkt5UR0$$ML*c6c=m z)Uh4fjsrm6v*R6}c->*W%ohUBT)@BY%q!*Y%fsW%tu#lf&1RmPCD1(3b0>;#BEb52 zQt23HI>IW@LhEbnAaS0HzbIwWsHR;sd4W8ATZZdb_N{^Qje*5#oU-;#PHr$}jIKf1 z31xKfKrPBL+#{fNPV|m~jjvCTHHtI_I-Y98E~K$(-WdR{)3UoD`IC0fB-1rwUzRNQ zUvoU|3Ci_jnlk})a8uNkp)4y8A=htJ+`J83_jybeI~v(p&Opz?om`~3-!EYfT2Up{M_ zvJnml+#ov{`G)Z7#GmiW)HsIIV4@yTu*(Qr;6dnD1q4W59ql(s2ZTpAx@t zHQm#V?$C{)+llbqVzO75ZG8D+Ez|8qMKCS$B4wyda~KPRmm)L^YgQ=|fL;1o;gK4V zp}%lJuAhu@EbQVSCVB2=90fc%@0ppMy;Tmj($+K|Rwe!AGQP~PQRkk+70acFpzUj> za?5un?i17HsEms~`c}3O2dUnP%I{h*knPe^`Hb@HU2aPHz9Mw;YDyE_4bV_GKNUy8 zX_0n9r#KKjvtG0w1aA)XwqKuVW7QO3R zVGe1}jNb}#p>j!D6(~9^G>C8KGMQ&vs4Vpk?OToR7K1?g(_W{2c3JoA$8*S*QLU{_ zM)i^N76eYUD(4nGvksQB39D>yzOxB3W+V%O8rEw0?eg^kX4-8nq5J8c`JQ{XeCEm! z)>oCwh-3+Zfim5x|_P zds5t9(>-BdYwEmKjWc0cMo=dx-nfvXr*6DQgAL5a_q6m~Pr5tsYCiiHVn|xs&3jG4 z0}@08I?@(^I!$4qW=$+IJJUsv=gFk6Z(kbH1~w}Y(#r~C>a8CgDGCu=tICrG;J3Nl zdEzYf%x19!?dSZtQ9qq&P0dmbOKEDL=9Pahi1n#XS?U)JFGwh^tUuQ#MG7Q+RLsn? zS&mhSV$GSBbFs!Pr7VORRBIH$4H}Q&MO|NNXCexnPi@$11=s&~wyk%Xl}?97IK)#&avn^cSk%ArVC)qNcZ{Q-)Sw+Ho9Q&Ai}I zVUb=wI%(_aO__SkAsZ%9Fw4*6khN-4SE^B^KHqKfc!qP(L*_0QdZ#Jn@Gh&u*g#sZ z5n%X#859Oso|lb{{Lzp=9M5xabn&t!oXj>C%=6sL%RSSYs@IkbEGWH7n)+JsCiKgt z#Ed>QwUd}tW2hasuc5OoR_9gB3D0nd=LMO`x}}v-=*j*2kSSDx#dh?$Jd<*;A4+GG zO?GwdsN37Cah-~~zEa^8eaz0GK@w{2eIy4T)=0WawPD&D>ARGv#x*R~eHIlfg{~|$ zCKIScOx|wDs>m^|fi#O$_To(mFOx{6)DV3vH^}TRI4sy?RzK7EIlv3%2HV+~M{i!P zT8}BR7{P@NA-{6Gb{kUYu?nT3ZnTTFC5Zj=c|EtC9Z)3*b2Hhi?U+HopKsw|v1V=N zc_|Lf%+w~{zD+%`k>`zT`m!h%6#k8BlVe{eyjqBk+!cMCNN^R%3E#{pCDACB{SjvX zJMGi>9WjziRqYCBu4=`g!M^k*p#8IjnWR*qo@2hU zN=rM5k-V-6N-p%&3M|6ydD0p{iaNBF9b0-|gdMxuR*dCAw}S8IPv>+PYX{rgI!|OP znf_gRyk7b4O%7&du7S;R)>O}PGdFW~=BswK@-GSBUf)fdly)#W;D=O`L0Yb+5V3cD zlgAkA^bS}kr-Ap=b3q$a2^uqTR_{S8C(rTwwS5|E3$8#@Vg740O9&SWr<-*FQAueFR_f%12{=cqA>25k3G!HbyLuYjLo(44fYj&casd$H?+W~ zmLPLZ$5di8NSYcud8*pp>-v&gC@%HMS4jr39D5!B)}~zDycpK~bC@HF!Z1rj$Zvem z;e?wL8CrPhTq?9pJp~|80v+p=c1{ihJct}hLJS*VHYJlK-+x-Lp*r>Px{3`L79 zuZXB#dTIjdyB(oqR=#fHvt9|V=Sj+$tprx+=mog^WQ> z`(~;}my_#8!naP@l@@@eYWsk(=>r)9Q_R0`WE+|C%KrdSm;1UeGumr3bk7-pWD;}^e-ny%fm(DWK@t(71m7Mm8n|9(_WF#@F?gfoE+zYFN!!X9CCAIYQRvwp^-1e$(^kbLH#Vp5cz0#w|@b zaaCBdM6RvMcHH-2=EgNo5iE`m-2VWdZR0Hi3X!rewkS#Ld$NJ6HrDHO+uX-i)-+S# zb|j-8Q;}1vB7_>-429L;EWFdn{hrjFF_&dVbXAG1fWW0@gJT2C+7l3qQceJXyU}XS ztFc=(MRZjleqO5{W@h^Np5|eYjN7Yiwwa!i?wr9QXtM26wOAB4(DbW#Y$-ZXK7?Gx znW|X<+qf1tDg6?Q%Z!KiXiMK%Iyl9aN^Za(I7}6bjjaZjn3Lv&-hu8ybHP`hRsh=` z*ryKf?;k(+WTXuDFwaoq)$YcM$R9n=AeS|dGbM{NJ@eeZLd1rZdLd>cF3+?ZYp-7}8!s_E@GN8(-xGN&C{HxWj*b)3z)JXKF0WPj`lTXt zwbm_Xt7R!>F2F5scyXWVqpftEN<4FFnQENTdzmCz z@0p(XbQIARzPqMmj~yF_WDwf=BBurz$P36Ac3rh#cG<0_rD{zJ7w<4UaI7;+ysYvT zwMp*NhsPSN$b)<2yq&qr=N-#WC~DCW6z;gln;oxWK4<_wlR}I!6-C=Vh_Ul;(VqP0 zue)cR%aa7#%TiD&Q0m815R6~5F7wCfIdMtb`NwGH0qB>1T&fDt&opVO2i;gg*R^_= zr5cH@vGRFHh@GBcLb{JKu2O}c1KpkjmV@-=RSF#qnTowA4|73FnNh7+&C&IU1#_;W zW+Na;T#HPE;N|x9ua;NC0nHH+_)|v+X)082KWP zap#~b>203c-3sw#xhUkevW`uETa7|=UFh*8gYx65Io_Mc;k|Gaw0y#(x+2M8;M?YF z)Lb;qa(vpHP!siB#fl@EhlgZ)MP8O-%Q5WP7_k}9x<>+n9KAwKJ@ei4^WQTw7}n&s zS4~^S1S8Y)ljpi$>+PQ~vF}V70*hARD^{MCYqu{~Vpl4_{JsFT8~$03%#L`?YZVI%Inh=F(46!4 z8lfx7o<1!sLF0&#^}6>mldbGuK+rMAG*IkQnlOegQoVZDeh)&*d zo{!H3lf$)@EW$`zSdz*fyQ7h}Mg~PZzhO{z>)n5+6kkZWRy($-T3R|jZx(WDbPQ^ypHJiAj}TP+H_ zEM}iTa1A?XwHREG8k#vYF+pyV;%238>anqa`|D|yJA3fV;H$7lWZWf7a;-xano_UQ zIdNjOk~8$Tt<|`lHde)Mv*rlpt?4x{2cLH!wTc06(62D{sCd5`6qr|{JMGm>=-l%( z+E{bU%-dYd%*-W5CIDXfu6yTb5zi@Q&a}{SFkDOx!XtO5ZundA!4=v)5u1#6h4qmj zNNh&C#U6XKUY7|rIp8?DpFQjo{O=x4NOi#ktZA>iN*4+^t_rE?hZQ|es+U%8(D{0& zlsUh?XPt|V<%mfP_YPSw7ShkXzk_Yr6~w=WRdRCR3`_NG({unR;9KVuT@-YZCbg~l zasEtwFWT;jo)i_e(#Gi+ORh|UZ(2YOO{M6G{(h6P{{Thj-9>{$C8jk&*2ZsT{{UdY zcP17V78Eg-iqI)oHmn3veAlOQO{1O-v&_x4^E2NwGkGVpA2U7AIutvA0i9(*w84%- zuQ_0w%@G*JWZv7R=KP`35s@-~?IVJI>&vyUDOvlxei?9A3V4?n5+Is{5X1Z>DBuXSw0M zfo5iV<~-I;E@4woF2+P&%zCVN*I1Ha5x7e$vY+TNBccq~a;&L+DRnz`k$aOB2ud1n z>tPy?gKXro_fBjMF0!CIqH)=5RkAe2VY8MMnAV(53Ol)_`P)h)bA0uTOiasqS+P~n zhc>KPr2B9}LRXqvXW%Q>uiW;2p%*cDrw2bT}RZG?=Anq%%Na#g_+E(zyJ($1V27A>Qb4c9yPZP~}u+qce2?J3FOH*r;*Y z0-HhQv@Fj%7b1@>7St}e)&wQIP`Bbn-NTOr*^j0m^bRjgt#Y>QRY3V>l+oH`Q?YD! zMGT$ke$mE55R^}cz?>BimwFNdlIMrIG1!FCY~Mb726Tn+mAiy|@b@l*FNKyOkXRSc zl?RurLh#ubVj$tp9>ok#@?M|HrH z`&-g^0^~!PJ#E% zMJwe-4n;f;K}AbqmzQ#$#s-%rs79qjJ)fQ-ZPK~lm%_DIeqGz6po&Y(>s+HkS0Wjv zAJYc8CA5=4ZR-4Kd)S*DSa@)}c1ZP-fZaSSGD|cal6L4_RWx@p-=}lql144YCY8&kzvUlR=NVDyW81gh*4+N`hd^MX zEx9R4wx2a&E3|}d*v~Jcr%MrlmnO=#!2+a;9W16=}Ek# zsFibeP}1%)!du7BDm1KmS3wI?&wR$d<|m&YX+aY{C-S<-KwhypDjv7KR(w=7n-P}nlDD$M;k(g=shfG5XS+bgy_ueR$I_L#=JMJXVjX&q zFw3OCYrcoqKMO17~w0)A%ZG6$=ZGOfXcdj9|_d*uBZiuceQx1ML6Fan`^UX)&T(W{f5 zeP`oq#sueDXXEc2n%2}V9WHvZ?3g0`8KMYHd3M4Rn5;YN^X;EUrl6c$Sgx% z5_@R3q*-I#ty!tg(rH-Ulhk@mhe-k~0O&=5V(JC`!(++pW44T~>7k~lX086OQh~HbyDLUhth1TIS zu4&aTHmGrOeN68e1)1YA)O{HW_f-MTS(*8phg;!cy|G{4Jt0kzVqF=l!B|w0+@8;9 z;jR4t08I0tbL{tCxhdW%VYN%4i{##T-u1{UTdQ*m_2piA{xHB`6b781vhmsyb^tUR z8TZ?5e7BV(n^-lMP9%}hImPKl90z)py6*iMrj+#W`|KJ?HAt+<#F~;XK%JQ3((_OX z{-&RyO_@&_FD%PfNu0exu@{nCIIpvberLQ4aD&@S$u#pbGcz~S35?9oe7(~-l`Xu< z;MCQ{ylW4&7(8-L_SsmDE@2Qj=EQd5HAYtM8oTGZx}|LFl2@#cT}W$BeCoeC9$DXg zCftygg_&%zC)PXze3;lr;AVOLndd|2p8MhcxFel}In?*@IG9rfxrIBywk|uYfujJ& ze>p+HH|h7@R=zE}xLDgXl1OLMeAW%--9M8I9NwJA2tuN)!FoN$x^-jzc~*VvX_ZQQ z?*zE9WPmz$oY z`L?-wj%H>W=v*Fb=RVIl&pG#X-PiTHuIu%3ic?ph za`L~nyemRVt^&^*+GHg5xLz8!tYW1NYa`~vV~U@KHF|htXG%ohtS)|&N%f%5J$2`19#)Z=`rq!@KA!dMt&PgRZK7mOiBt$KKxCx&oT< zXH&giKYbQG8^(*Y1LL@w0v}9bA^vM=FTF%%L6M&07v#JJR9ihhrf`Z1!gKoqiu?*s za;je^@+t+ULA^e`V6>z}St;G%+rEOmVkf{>{mNt(W5c+E@S4Z}bN=tr^{AyHRl}GT z#pg31U0#RqE}!HBh%!0qp^7gP^K~Aj^-SD$uW(U7)uc>9$+ zTx;jH)o0vr7TM8lm`RIWC7+z)(fwQUtHP(}YJV1TeR!%HRPa{Zqk)pzbMyEVE9*kr zE8eo}ehfL=WPSwvY_%{~d(kE{pyB_<;;i2Lsyd?`-=i`<2dc6pIjF&f9c|`**A%sK9Vz~XxnQVkn)^QPk!CU8Qn~ zIM#eG8+dkG>Dt#+>JPLI>&1+?)S|{G-N|K?U$f4*eTJIFiTkiYbbB;CU=^OMwr*Y<<#_3T(X9M*j6O{nSLJyR-X^f0M>^vk;F#G=*yHgv1vXT%Vpt{2ZDXCE-?3q3m^vYlL|CP&l2m z{L+)m1#ktw@3#22BEtL_cePJ;@ZWA-MEF`sbA#B{H=Us7hx^1gVOI@&*<&VwCceFa zcf~&I&kU^G8Ldtdeq)s|Ii?@tlxmt>-{rPkdHw@`T3z%*XV%M$?VCK${ z=e+4tHmX&3?9v?~-lung(KFk)TG?6ZhhpYZdt^_` zl+AX`&f}c&50`!^x5I_mI2`#!n*4WF1jg&Z?Ni5EGea_)`>BIT?4`~AG@ihoYYK8R zlv7N;YrEgTScfNNA>{%dU&c;zprJob?X_H?2#cGAtu&S$rKrXK`XW7Z?y0B?D{`6n z-P_?udgE(Ub*?n{zLJUZ(v=D9>aV7`>1Hh2q7`yU!R1qAz~(8Q@cHMU#YXvwh`-g7 zCIt~*u9$4UEXM9Rj4wAYGGB3(TkFj*Df++0(1+%t)<4r82$g%lwgoh!ca8}ER27(s zbFpQ1YtJm@RWNbZmP|_gO2o}PtAF0SXZ}5sw0LOT|C;9r@f<`&9~I|QMo0IR+U|O{ zMOYq$os^f$>nr?JT_z_wpXOHue7=!lwr^n{{PHOE=TKz6%-i{*r&LWg{2$2REuGPY z2+~r}M4kj;esd#5lo=I^mi&5oQ^VZ>KPA_hJFjbdoI-DHqnm!dt%-#sm58bg{eINi zAT<6yu>W!69kSErYjDkN#!jZA*L86v-A)sq&UI{qg6oSXn_=Ft-dhXk-6GH=IPouD z?%JJ3ovChVW%EW?skhewI*$}=zTV{uyqo&D_HM=b60Jkn@n__NL+ca$A1&*ea!P0C>7^@`u;4S z7&=o%`7x1pBUFv4GIq&f!!QiCm03 z)_ToDniibD_oJ2?DK!4a^Qmt+bt0C%OS;5c6=aTi+H5Sc{HkSYktt0-X#tEGjZw1G z2sG*aA}07vF9M;~Hv_IzI^z9J3VM2tzejNathAhJunex~a-2T|qz!-Nt7xl={Lyej zXlq-+&`GcPjS0+QwXG~&5uPg*CUSO6_svW}rs>K~T{I1lO*_0TG<#iaTMd+9u_67_ zU*Swp!&tHw?SNm;pRMZkTz60uCDa|9U}e7E$tr?nm!YUK2c2>-U>5zg z(LXu{u$xe9>yf{=lnyz)%FgxO&KVhP-ZOgb-(1IU?*%$w8-ti1?zf#+i~MaDMbW*2 zIocO>EJ*p*UV_=3|09w}@tR%Qz%ZlMv2($h0lCu+o+>TOlQ`e- z{*kEoX?e!xwFNs#Tg_{@4fxb0L{b|fenRXN8*n8euDYDqt=v2;6BKjJyg`*c-!0!{HPN+-fC_W1k z5l=4;C3onH&2~<=FC`j%K6eXij3q6uI*KwxuT;{_fZOqczvvLgf6C>FpZ`jp-QMUg zCa&D)`t=tqU!PxerKHoTV-@m~8XQS$VK~=M{%UKdRKFAP92eO6K;&L^IxLD>8z#DK z@cHrdh$miOdh^bF=co`2Vnjv%oAy1B%n`rv5FvYeey=+!VMJyxiX3djerpu;6(f)6 zG$t=?D5!eY14fua+=gC$X3`su9UEibx11UXY`Yz@%4u2W8?|Pgb*Ho@xh^5O-QRYx zS^F#~9W|LPua*^VI9R)#x0_x}i-gKvGRN-oCWw)Ixdm*;8ET zJQc*&(Bg2IMb8cVbMcJ*zjv*(o$e-0J}iRV2NzjsZQvE`cyk>RP_6$iy*4=g?%m%w zN@q{OQz@?Ao*s|ItOoZ-Us`X-bbRD+qvwupoUgp8YBhcjKLd1&Ol$h&ilLmGsa3(D zKigs1-I!iqD^~e01rmBd{ z?X+)>#al7L{8~BOd0M*T^K02p!x&cyzcd|%L!9F<>XE|<`CERe%D8-M7b&Kpl<1~G zRLc1srM0;Usa4Me+_`pTwvOiAhZV$a&teTkb5LMcjmm~j z)p22WnndbLEefgPJ73hht4?yC1KfTLv0tkIsmSP&|d}AkaM*Pj4H{SDcMzX`aW;D`S<< zf=8FnKGgq_;{t=`_Q;g|_>cYJQINxoMb5%i%qYB-qoOyBXB2avau(XlVy|1*4OM#A za=S=mo-j)7YvEDKq$BjLtL;W-2GyI78l3hj#}s}s=l`sE)^swdWyYw^jIiE}?jmN< zCR|xe7FgZv&YApIZ#bKfy@R?Qm40A4B#^7j?k@;yn@5u zXbH%rEVHU>Y9&!UZYti^sFD51bxXjZ@mPsFJ$g)KIVG4!e4e4L4hbpZO$tUakv3j4 z$b255zq8p5m6}dQ|CpH(fdwQt7L%HC?)YvzOXr9h2089)E&Bu1tk36B@$%Iq2g3*> zpXa#jl+>ayO9+1%#?6x~j$z`<$w1%^1W^7E)LIUXe}LAOrd0+U`hCqml_lxoy0C;^2#C0m*`MIx?Ys&2S8Npe zl&zATaaN2vr}S><8AnuG?4O1rSYCcUGKZhyWANZxy+uIrVWK|ii`(~aV>(Xqd2LTG zkOsrS7xZP`*!)LblXD0BwSrL-3O_stT!^9AMNk=FMfnoHgG*HG9NM_}OUc5)3}4{! zhe-QND%G%`&JD@!!$}M!uGSMiq^t-?IAFnb#Z~F9%7U?SvWxKtzPoQH{~HwfX|()` z)?Fm_{31P|I6pQh;`bl*=dbIxKcUnPwx=0JCtb6rE)0j7D2iyP6J`FmU2<`@XnFu1 z*LwfX>PJJtkZ@r%Rq2n^}|~zq8l7 zH{0>4dz<3Lbtc{P$Ev?;Gc3iVq3=0o@VS_v#qQeB%8R*F8f`baGf-zl@`UMrUhYX~-pw^doBwtYDoP$anU--5HN=^W_emHm4_NmPn)> zzq@W4z1*mak_`z1Sw9MkpDEo~ta*su`8WP;kxyLg2;bj`S?+kZ$@cbVS>#4J<}*no~(;bY&mX`=`r~icbbd~5J^)i z8Cj9M|GAcqcJ4F>P@1<3SxK&UKmYI4b$9cz+qGi>XrRTt9uVeXUk(BVjh-8Lr~CHx~Kv8w8=D|Smx)kGspBXaZ!VxM~7;zD| z0jmoj?SI(iBr9ON@EUY!{ckR{|55n@0oWK2_N8RVAV^Qd?;t1!;b_}=FOo{tjKbf~?nk8jgiY0p|HtR6pH zKV7Q)VQ@jHt02QmOZb$$RFfFa3s+!y^FIkJpxdnXH>!j=*WfpHXYTZ`$#XaNG+WZ= zSqAHkzPq3}o^E$MXq%oT%NtP$J*U_ zBB+*C)unHBISKxE{J8!>uy^vrf$*a<2mbs7?E}6bSdUtVV#Z}SuUuH>w0x0-Ss`*P zoc>&QMgt@`F!<7S^Bz<~{pr@Pxrk484&^o+F6@+p($ulg?=SWqw>qqjX!@V;oWJxh zn_%hWWCZ5i0Clc7IT^@L#{HxBb~t?gfFERJo;ddipkYBiVXwR7`la{dkWvt_$VodA z!aGTb?k!QXnLcziQqW9y_UP|)y@1c{Hpa_=d21~uPRe+CuwC@CpuM8)Z3v^V=uSNE zUiNHbZ_nn#9KnYD3&IkO>%qED?qk2DiC$R!s=6aNZMi$Mg|CXQ8lU_xd3!RDbMD=j z^b%W8&RMlI!0A@$J@h@N%_WhiUl&H%E1~Ql9lA(rEalY*Nj)lNh)jJ{@-w)sG}YJt zrAN9|`S)@E={b$15H8r2y->ye)2)}d#xeB{>)%4|_B||ypJt`%YNSNP4=2Z0X-iH| zyby`?V~t+}h6F!vA3UFMyZKoS&6GV*(|fCth?=_opJnCLG%YB1bJ)FL-1kU)qvv|O zfon$vVd%wwE0BD}V~xd5~eEMMjYvlx@y1piOW@rB$ zQwWpLA1wOsQ)<`#h%|eWGZ#RT`|~aP09fVef;P@Su9t9~DV`9X$Y=~eY<_*uH~>#z zth-xy$}*!`TN2nCG8z~WpU#}8Z-LOT#MIvtYbg@^{3s1wh1rgLGgiZe66`7S3iI{v zg^j&`&|!}Zm6s77?|QTRwh}3g#QiH>kf5nftHXqme}7*45zszCQTlc8vpszti1|F_H018`1qIeGomH@!`*s5TLOSc`$&F|M~6m z#BDxrQ+YR+<)YEUV_g-QIjNNgeM9ws3hO8Bp_j#6d&V)|$r44lX1DeAxlGW=&SSyL z4W6^}v3+4jr)q`tcNdAhv&A^1nq6&;TTi4tuFJ%|n8z4=xnc}aT5 zJm=wl7FxIOPTzj-x|>gDKcDin@0t1BZ{44ND+!6_mnIB-KBf(QIv5Mq?6<#qnIBg?J8Oq z_9n5%>>yw|T+pHj?i2}U(>%Sen9y!-66sF|%U;!zGDqm|6ozT$GThkWt< zR#L=8U__eFJXlu%)4s4PbiO-D`++3EN55H(BIB1%r-Z4&jRe)1Cyc_^APJXRo4-zd z!#Vf$X;sW`|M*+7IhsxObe%fLX~jt3wR-Sx)sZf#Z=Eieb6@2CaC%52;`{31ySdl_ zpFH=W?&~5m0SB|=NkcD=jpcX4?N20E4@*NK1rJd!Tr%b_#RM}@&)9KuC&2T~@@~gD z;XID|!^mOhOpgAk;4AQ<3SIl>PglmmGt?`LMtZ$5PnW9$6$*fCu2#2Wp2=rgHa+}-?lro4eK zB4W)j&UP}z^sI-UUrEQ$qM{uBwjjyNTRKsT3kE{Kk-k=nZ5iL|-tEG*Ew9$c8$++t zUYU!0T2n9rz2y_yLrMYttb*;&RyiH)Sy)%TExWzGmwuyjsYpYo{LGgY-UkiQsqZeL zhLYTFD?Gt9p1A+u8Gv=|l`MZUwv^ZiRcBw^Sfx%J4;iF7a{u8;p`nynD=itJ( zlEqpTt1*R_WkF_*zy+38Eme?vcUiFNq<;D6`lLn3t+Vf}V@J(?3sgvoZ@Ye+v4F2t z-U4J3=;}WA56zs9y$o0Y1^X53&(Qh$O>!@0v|ij#-S1L+PCdzXROBF=g#3i3>4WcMzY2wFu7ff5&Y#+~b}E z)`zCUgr{B!XLUuh~+G=8i*j7% zrKZBTRCyLphfz}!KXy!SK{y}JXy zt2LP@Wk{<1HgBW*jEkGC-~Ud%GQan%YuEGlOm`&m+3>ZVyV|dmM7jLl$?OcGF3rOY z8yD}aJ$L%GxTgQ&k;k?M9uc+LkohTO0m^nQ;p8j)*fypX7Yt)vXW>lku5=c%IyTC7 zr;q2dYNLDX-nN8&$6cF}Cs{!ENiN_2T4%`pc4?>>ob6{!&EGK6C3XR`*efOndB23qBV- zZEbY(4~wuh`7HurHkIo@($LV^F#Yl8Y*V8N?9$_mf9@ZNzKe6IF-sq8BFz?=$&us^ zu{eu2%N(bwL))5~Rvsc9A59)m{dE01`B%Vm{3U--i^UfCrmL9|b``eYrwlA2cpews zgJqjDio>pP1b^jlBm>14NxGLbg3DWXAeC|TK~Dp^vKj)_)U=dxWDCp zsv-2QJN=%Xv!h#n0JF=Xbl+IMX}D z;G&zZgy)?lQH9ZyQyM=Va@~Icay@rz6zf8?%#(Ud3VPrVu3B5{`biDH2y8g#_r2Qa z+D7erMw+Pn1*sGj-OFYCU|8qwhS^;x;jL6&sqOipWRY4x<= zlvKmVZr@TXd3gnD*uZ76j)O;QmxaTx)6d4Z&Md5-p79P`$Fz4q$&#({r~JJ{;h9gb z56_%kb**r5l-prz0j`p+I49RWuuzRWih2uru-v@hi{nDRLK9!|8;7bX#pb@Lt)*se z`%t3)lxWokRnPfU7k@b<=|Dt2|H(Z%A;ur_TybSTt43!>JVF`Hf~U59Z!#X{NTTP2;9`@lq`j7#STMh9 zzQin_c2)G41bVG*1)@5P=$ql9r_+TOUX`wBwu%p|Y3{4?NpKLoGN~xwBs{L)m$qmx zMx_AFW?A@ky7&jCs>5DUsa6SPt3Qku>@tvS=(;c@K7u;aSEHDlTw8Bukk;l+V?%~6 zBJrOBpWfME7}ei<+0I?WbWG|u{?7Oj%6tWmM+|(SEz)xseo57y!}H8(mv>a->h}x9 z>$b<85JcDQJyCpdq!eP6qKEwg?76m>@c@e2cG@`-*n8 zUwJMjJ;8O$QwDwEV8GcmZ?ThM1O%>gmOp~x#1fMxtdc;SxDY4tSJfEoMB#L1Yjzq7 zp~MgvbHd%;lNKuLXR8Q7$xb4PV#%2aaT#|A@e6oR1@7^x&htptYYU@hY}>Bd3Y% zTlUJzn_plVsjLqpYdWc~wo@e#;_!EHQ1N{23ebBrFSV$Wc5rIl1youv4m3qh+2e%z z<0HM2fbhX}hD||xK~bZI=J6PaGCgxDB~CVDsTXcZFLKksrU}ET!l7TOeCc82!jCLB z3{FGPp_8^_Rv9y$L(XqMuL2BG&(5gqhW4ghoznTaG%H4e_l_v-SaQ15ni= zimLIgZW!Czq|N?Wdk*2Rkbo@2FCf%78RT77$q;W;fDO|NtyVwQT?8-L z5}?hG=b1DsDG&i=Ej2zDJAvbGsgbrBAgNL4dFA;SpkN^b=|Aq9iH%F$t(=Hm)@W|o zlR|$uWEnRlu8wyYvw~_MC=IBIrC3WWsG7F3Fi>l!3<-gXE2wu4wUNC;l6{l}%i9E) zpezsp9_$0&BUqjE_TpC{L~j!S0nn6LqfJ~>#irPNaI)tEBFDWqhc#hj#H9TGm+fo- z!rb&dddeX)=nSOKx2(e2u^s|S360I7XG&>hE@hIsrgMjUY~fU-xGn~=qO*}&J@h1X zJCY&I@CvanD;G)&;;<%($=+Lrj(87@xT9RRaK3$VA+tz^xgQ9-JseNi8ubjV;tmmc z1-*uMC#V?L53NsWZ0fv*)*iP!2SIJMb;g&Qr-+Guaze1RQ z1;9C=paM2!p>u!&)nMJ!@}Fv?$GciNKAr}we9m}@v-UXlBr{@@ z4)ePzXD|O{;6uQjS2m~`OB4Ath6JqM5!Eh+Pg>-#%5tcHi6kR^8>8sOWgA^LI~LuW;j(O#4%BVFSx>CIwu5FNlwOGG8@1Okkdq(@fYm%&;u`m z=Vl&IemruXrf7fd4|1)E$SQ7@A(R1g9bTQTkJ!*T^odY)$dm?nm-&CBfoAerK+d|5 z^&%TH3cMfiLwrTrvsM))rVe*De(`O%-Z9*B5?oxQ^fJ@bs;xtUxh3m0@<`cjXXup+X?;X0fI>8tB5oxl)pS*S6yIRFvQuG*m*)Hu|p+LsOHsK@DmP4Z#g)OtuN--utrR|}F6PCKs&du2NQ9B>;R*w`L+cH&H z>uf!2Az!Z6Rf9vG4IgOvIE7S|SIY7Zv>i)>a?w=0wRNq2MvgAWag=m1tWY zye)c`N)5^2uB&ZBxqgaJWSB3@mf-^mvlQ;l%@tJ*F?JpDb{v18l+gV?u)2wog7z`1 zoM~)vl>^kmrE_C*_(YZ!1)R2}Z1W+qh=tWF8qGLV4!V=}%Y}FTqhl(a;9FVT!W3*% zbI66F#4U#`Gt~v;k`ggcb1>Hf$1k67kkz?wW{9rAP8XWZc&w3S&Ib` z5}p|$Wr`+~v#OdYZK2+?j{81{X5G%&iUzJ_S<#oi4CLkzj!lboovsI2+Je+vK5CyY zK`*I6p&(ASYEBn>A}yzTz#Jl4nMFpGBp~$1%jyN?E^H0i*%M^9<>cVXB~F$ghiT*Q zdBxVhAT&}CU+OfvlR1?`)$dD*SQZIEiF5M4vxD>#Hk4smH83?SxdWM5+~B$e%E2Q+ z3Dw*ZvsC)URGNoxel&JP)yDqjMN3s|PTJzcL`gBBh<>gQsQ~a|xOsWG9Dyn!sS` zT<=11{HS$P5kd5b3r0SH^S;NSK)3xy2WHP&7OHwP4AAS;@>XvzG42X@KM5oY3X;u)eB8TH zHa??^s0bw$2Z4N398tE!JG8!<+mLhE5MlYm3L|ZntwD{TO-@qahl_qx`DDHhhCd71 ztt;u!FK4blPa_10?2CDxH;Sv{=PYvepMsFgcysY{1+#oVtgKNry57N7V%z-?_uxt7 zDE-75vE<^73}P(M*F+_kA_as39W+~XF--KG@M5~PEyJZo2}xj_7;{YtIWIJgrbS?u zsV|HQ;|*ZuNIamRWu7ak`1J!Q-w!ksX-Ou~_3)2i<+LPu%)CwVrW@D{S+9Frgj5+q zNU{QWXpKcU0W(H*@60Esoz=j$zg&9hC!j^!OiUtsgS$857Gijy1UI!b18!#0@JO#s zf~CE%Ll#O}wXm#;3TA{7>}zmzFgXQt6?!gYl6wVWjMt*owVs=ujp=&NbM{86uQAo- z^mHb<5R^&oRqu(Q7Qu>}kkf?^!tVhnRKrA6#0XT{F&vA7vp7Gb!8|RD2$O)$dFg_d z!R|FYt=Tkt7-`H+dMT-B;ytFT z58+jix))7HqHd zq)t}abAv>Rq2-J#AUS&q2O2y%;gID|BZ(8Jpvy>!pbRQKC(Bo=Iv74gl}1z{ z@ktYMsLMH7j>}NTodJB8Cn%G!yP_I51C}nRv!ij732se+#p62+4zBV_ykg`dMLD5L zl-TlVYzPh{Cg><|lIpd7*?9z<;vO|B*1fjQ_}*C$!IF9->-5d>^{`?(Pa)pwOW6pr zUy;s-UJXI%stYfG6)zK0u=S$Lgu{VfcEI@xRgq*_eBEW=U^3L0FmtbIQfR}<({p?5 z-u*xiGpGdxDE0+y#S|kK!N@uJTou3}sL6vsue`);Kzni~)n0%Fo6D0d#=)vc1|kJz z)5SX=_svIknqeZv+?sYt@i*N(c~$hGfftbSN|j_pt#wuIjM}o2@0^2FdCS(o&IZT` zoD(;iBcmVWdJ+}jL$ZKAVZ>5{XR70g&v8yKOq#%*b7{E)@y+rdGBCPt zk)Bi=U+M8Ao-TKLj@%mx^&j2(xTP{bp{2{h47AFJ5}@`5scr#2dWP9T*=n3g%0@m$ zQpp>o>kKcV@sEa(dZo~Hmk%~u6S2tPPOmG#?JJPw3YZuEf*gliNF(ly^}p2Gq;DCmJAL4xZNqf(Z5 zd7al99X@a&q?)s(^aE^aennIfXloocO^73u_tnclJ}iC{=k@K6>z@R*D9P7qq!S+$ z|F~JGj+KGo<4Vzv@MRNNeaXCg5kyYVIW#lvi)+^jY*6g6gOf6H!4nFGfsooV9?;}jh3Bkqc^&CTS zMszV6EEbRCmCY$$+SalTBcU}4iG>V%m-n}~FRZf|stc@S5bx!F$7DQ_1%gw{tvu3C?IfJVO6}GG4W+HGs|rlK<#yYs{39mF8$`kcd@3 zq`z!gz0>>^)X1ouOn}-N7qm>8^?Y~s-@?&}jn%Jlq!wRBq$15yM zamaO;CuN%fU1-fg0*>v)siyQa3GlY&j=IPqx=Gm55$9ySKjteEw-}e<~&t;D0fH=IL}a@AB;l>6puI)&mye8nw8hi1j>t}Gv$9El|T)l zwQ|(50XCE}>9`y@@d9fc>D9EMq%je&FE<)ro+e}lm7{Z>4~~QISf6*M$0CbVVNSfj zY!qLMP_{kA*8esG^2u}%sbr29z~j9%0QIWkqiA)xo?uVU2?tAidqNN(6qaGEvYCXZ zinUcM%3zW!@#d98uY##u3Cut;$o32)6pBESam-mbb5EGpRz?sdV!A7yij}g-@Wj_8 zG0an}ZRe8Bttohr8LuWHj`orf@FheRsp95~56J>d*PJ~8G-S(_TJ2|Yxx}Pfzs}h) zoGYG}mk#C+D3JIv zfUMgj47%e>u+)j^F)$s6sE@dc&%z~}2)q}i-^qWEzFTMQBR9~BI=gOwrN3ayMzq_VPJ z&>dCZO8mD$e4QBy>eA8~&ztMW&I_rB@$%t8r$feEsuI4!ZN-{kY>QD$b z5B1RtS>Tc_?XMfG9(BV6V%j8Pt6aAX>}LgGBbv>W3qEqzMMjP!0~xTqC#XX8tfj-W zJ}P?1da6cy_gHNGO;+LhMTq-3=$>*1D7Q{ zf-@Z)*&ZS2}eJ@6BxKoY}i z9ZKAjo>wH@nT`X`KjyvSCJ!{TguNnRn;~OmSY$ey<-8p$ z;kBacJsgrzyGw58uxvgbT4SzMQG^-}!W_PfQvsRJu2=@iqje1;f0jsMuu0{^$t6v-Q(Q^0O zaOs4VEvWt^Iph_x0*Iu|P7vm)6lz{FzCDYuU9Dl>uh~hbxM+X_Bi{=`8OfP=eH1ji zTKigAGB$V`d2L%c0fP<+Ppl?>Rd%w*z?Xu*A+OhrHjXriP%of{`p7D@;HH ze-(-I0A*<;+=tbd3 zrcZT@(={$W>&RQfMswck8KE%k(hyT(A-v;FXjX5O(Dq;otT5R=dJFjc{{zQQojrla{~t7d z@^Je9f#YZYFF3y1erL(h@3oVH8yjWFK4=cPZ(|XjXFHV*=QOx)DAb%A5>ijwQqs>0 z)$l__taVoU@mkR`KscZK)^`cb4fyr#}U(Es~F2xh@~{o<26k zu;jcE7hY+_WPaah@kXGVbsz!*k=E8ps@!uR0Y=vBclc>!OZ8M+FlP zPCV&m=Zq@SB+WWrJWc+6;=LdIcu&9E`E>w$zmQPEmok(qeSzRzrD$)8Azql_8FLdN zmp84sz2q?+X!Z6H1BIwj?ESnOgxj?Y?N2AE%ByPeW0tHu5Bnl*JvgIOI)85$9(-J# zR+cy(E!Ws6=%$H%st#SBA8j28t0=0|Hg;V9dSEz>AF(_roEQRS6LyPd)gOV|rUEOK zvPL@=HOB2>)2lboKKIq%s64cO(K2n}=fI?@Z%Cs*gYNO|N%q6#58y&L(+a?P($sVv2BbkY*y>c9%SN%5)I zy`ouUt>@4>;;6E#F{2yc1NbBb6wa>V&{cz|D|Vz+=lQP_>jAGI5sq%Re+2>3^OXY3 z%+Cy%&tMPgLil2U#o@vif^>xxs>N;Z?4?@o>k3q^nTk1;XlGkuW4=t*Epb@}Z__!` zmBlnB>r6unKb{ ze*!A&vU+g2@JxSG0@^V|Ze=&qA$=b;aa6 zf5!>qlxQqd0=3#Dly`{7wWQf^@(hsn%?m7H91pKG?$kXUd}y@(#@Iy)XH4@8tcw0mL$#xp>}8)H*Iy5Xs;PJuEGH*pshjDA)-&=? z=~Q2)~74E?%Zr0r{T7Ba4gdIYv(Op_w&Z^ zbv%ohJsdW?2eGXKG&7Lggw`TXZk226O%{R)0RsC2-d_A5#JCm(vv8? zW~ff$>T84qv&#cSx2;ujjf}2-yVHCl<`+WMCBh_kmjddqUP%V0*y9-s5g;hv9!x7*pw$>g#}4*L=;aqG+w|KX{) z029rq!fvc@*2~onCN#OaS|sKZ#({|*V&UME@11?@wp8!(b*J3AIbvnkS4(IJEvPqk z(xUAdyuox#fPkhf6ynYtvN|gyKtsY-sMgByNdP#%(v@_(ZM{XmvdTjBRo1c91A+aX zi(wU}I1bHGC6?~5-##jk#lA2H*CC>Vp^{>Mw!M3vi@8|f%_n2T`P`DU5rMHt@F7D4dhh|E2UivK5WWk6SiSJ214KGGezc89!pPk^?3mGDu!xt zJVh4Qv?cV%^FU&&?MhS5o8-<577}z9^RXvD+t}UgTDnR&M;UN^yiM&Rl3qS;ROwR& zQMiK3z3NX~dxDvVE2ehM$3d^)j6QCsck$?uYXkhis;+k7xtqdQ&i`ZoN;3Uf;Q)R$ zyEHkw^gWBeJ2`C9p+BuF_ldm+HQ#FS4k7QO>ws1d2=`$^1?b{k?UHh5v98Y?+`Ktc zWRWH=jcWjqD~D%vG`ljD&q4@C71EfMkX|FqbjpGU!hb1U}2vU9^82C_uRP&&ii&%3#PWvbeH*g zmh>$No3C*ahIb;m+q8sco9~Co+NR5H6ZHujb#+q;yy%PJdcqtv+|`Nnq6$650BLB3 zE?n-*!TNI63~Xqv)mH0t(SkO(Ee)y^T(5eQxuVKrCGzA^Lm?V_(+PZFb$aZLdk9*7 zqNJtvmLifg8RK$F4^y9q2fq8%hG0)t=Gr!=i@ar^o3;!tThvzM5dZ)%}%k)(?T;cttffe z3QMJvt4VU{p`#9K*E?4kZk6U__j5v1eD>{`+{SmcEVc=P#g^pS%H0V}Q%%qcUBf7< z!ZjMMeR-cl2y;|rs;PO~NR1htwz1zURxBOqId3Hb2|2E`Z!zas^jpV9h|cyo^$U<- znH4oW-K_$e6$5x(0y9~3#;HZ&wHa(myxS6s(23Ek?le>xO}uVxw{sRoIu50{r7KM) zff8BGt;Nj=$|(xgTeJzl#x>2Wwk-MjuPdoflYI!(mPswFwKPgt^4ZH|K|0!{_k z9JIK*mzJ@W>PE;y&tokfc7mdbi>WIPlmcdSHca8ZfU!CZC!#Uv&9a(JtkGA^X0(o8 zn#Wd$HO|)Nc^w54H_I=fq?69-begbEaNYCtDpN*nm7b0o&!GSTH5Nh8XNl8kaB6@3^&%W77Hb(PZdXnvLuX zb(Y>*E6vIthIY}MR_Fi#Q%K_MrgC+N$5WqbFE>t81Q2PO(NmIVWC4SEI8n7{raDet z51nr0jV5a=j)APz=e*;*+))OykXnrEX|OzQn)dO5FkJl%?0iO*M43u=&? z#_8uo+9AdUE9MhMb*d~~g%Ut!C*5xrGXLLq$)i}oZzL4bT(JpSZ zoXurxOrJkhs#!dGrQ=H5Nm(}X%b|@XFas?z&wDufEFh(NMrmjQV3sRT`lQd*Uj@0BvSdXTZ&@wshUV&~~#O9rAHJZnoa)N31YzL7DO zwg70# zWuBQ^nkpP?NDT7Q=SX*&ortC?=H1;fPEA@&1re*uT=a`F)+~ocS36_0;iilu2faLW z1mu|=wHa?@&o5+L<Zjnl9X$}dp(=w}gYVxZ> zRsbxs>7G99^o!1~Jau(Nb6&iCQ=aoeBM!nc)q)3{TG6aysgXM7%(SG*qy)K(xsq zdOWF&g__qy)Y7F%25&8esKu7VQ3~dojX?)Ct;M*xm!T1~W32?&9_K+3vd!b%S7wkZ(M-E-A}SDv*aWMC>G9dGr|7p@w>)JWW(BD2dum zZL0CIv2m_?P^%iwS7Lcv32s`8W^{8cuS`~$YgA)HM9HfdsvOa3X&K|xOSGHDdQ>eY zfLU)eur`WS8s!}cXRbOySB{v|)|3#h9Q#8ZlR(p$w)0iV(JyXoo;pp+RV}h^+L@(O zQJ^9Wx+RX2GJ2UBA-6D9EGtmMLgyWrlxc?P<)(Hq4Lmo~x6s2r|~W_o9 zEm{^&Om#$P6sSXrq9xS2Ax1q6Y6m8`YG`Gw!wA*Rh*D-FqT}u} zX{&hX+`QG&Fy7V=W`&Ze){cIY4WueWod$&SJB6P0HB%v;)05~WT1=Npgwim+e#VK@ zEmRdsXlV{FpQO&^Jtj(W(G^f>p1I}F27JAXpQ6rgwU(k(r#$qb&XJamE;SI~s8?8R zLTSlKrE^5lj2$AZY1FkFB-(N#7rK{~EsUyw&Z;XdsZMxyHnZaHm0CP?acC-3RXoVr zuy>lON`h}Iqn~Sr(Bk6eo_fniI$c0>-l(~oCV+6v-m*+Y_0Bp_ITbOEXPdE7jxM6- zRfj{1nvfE*Xq$PF&DH{>rPV$z#}lHtkyL4}7M$gkEwv{jY_}0N6apC;?t(Mbu0HZr z3N~T`iu#jNfw4N7Y9=N%F{2DED@t^sCm|Pe!|2P+trHYoX^jEQx994jdFxo!q&T)n z<-?mnntEZm9o!SbRsO3W2lSENl{eTpr(b%(?Zb7=tfn~ z*3rwO)eMa$g)|k*zGji6O_^%t&0(!=FsO{Tphp)|R%58F2(lw-s(C;(;_X9IO^Ys= zr7h>6s%(mA7e1O%IK5b$l)()u=Dl*!UP`K45_xLMQ$%B~qDLQ1&XX1cXlX``O{XM|OL^#-G{9MtYGBgF#}wkz5zDTo8eA%BlQR_Rm9!brszwid^nj$s z0#l%~T?81ovqubLmPdzB?l=rkEjTqe`N*9CAH@ zG}58-tuEzSOsQ2iAYqOw>W4!ZMds`bXP{IreaJK%=a)dT*i_U~sL))yy#qZ2#->Kq zjnG2E?g3MagA<{GYKdxy&=3wVt4l*Ikxb6THqK+IWvf^_Y)&6O=$p8vV|aDrS}t|2 zpqgN!pdD%qS?Y1@(h8MlHkj>VbdyS(%RmMwo=(fhbv8|>D&^&(=IWv<2rFq!n!{PB zdP}1nxq4zZT$iqaG>Vc^n+jE_k~E3Sbv7m$IC?p>nF~f|sDK3U)QYs(3>qg)Om(!h z&a{|XHiI1p3u#nizb4iyzvu;N$R5eO5+|B5+(#WQSqUT&oq~8})!E36|8raV0qG6LoXE&yEwqdq$ zs!Ec+eH)y2ZYdU-HYC6pPd`eb60Hiv7_Ai*F{2`wns5ndnI*n-UFDYh=Z8Rq#~J7n zuFUQ#kesWRYZ{@&p{fYc#ee{eb;3Ig=wO{oCUwDVDoS(|SS4a6V_2)lN?AP!nA2(H z>H?NFWN6H{Rn5_!gie=@su`AzX!FXP(}wCa6F`|2%Y9OTg9H&4o{BWl5f43BrGsZa zfaTiGH*=HJF}f&Oi5pF0du1BuptIQwLNVOZmML7*lE|?zLrBft2pSTKre|eL-l2@m zTL~&DRw}aJEofP4Nryu@`g65SV)4^t;ndEstj|Sr7FK-SZ&1o+(8kt!pkSJK=5+u= zlBo%-^l8o29Q8Kfxe-lcZZ*czOJJDMR$E29s5>|vr$pOtKvDXcv)v3nyOAXaV%7~>3FG#HFmRmV)DWG+q zHk#Vb+RteQ+G11y#T?piAl1XEWfo3!lwPb@*(@I}l}2}#*tfBUm}{K9($8~K8(E}V zNvWM&=!G>ljymbNyzNT1N~bmhLnVu!KBmDrU@|nk(+*v9E*laVimMTps!fQji;Q%t z(UkDAG99#wob^lU478q}J*=L+YHy#`1#JqG1b8VQi%=`#&n z(y6GXa&=*)%^}66o_01!oYE$=c*d8>)bXq4THR-FnB6(7u<2B%MLI<-6=6#Ao_@M* z$x@b#L6webfiO8bYH2y7nvFU2WL1Ef4wWQLlHNMw)j8+6&(J|w$3SPZ%Ai+K&$Dr! z@XA_E!J_7&ZBhdmF$)XaNq`BQJ(LpA4gz(GV5akR29aYza?Cn8dTDd?9KA%+jQ49j zNVgeb)=bus49{zu} z0-BpS`WcJYnzaWU9lK(;Lj^T4&#mTF;k&X`F=SNevAskT(+ur^13=d~xJ#&Qq@`(5 zO*AaI`-yBC&6gTPJxtY$4c&5Q)uu-3ED#foIM+8Cl=F=)XD6$5om8~S6Ef5Q00DCA ztvDJ(PaQJoT>Svj%Pn+hrh;5+7lyH^5wajO!fi$B)d&#bVW}Hoti3aKPI7dl#vu;G zMX&$>1;^D|C;~0ZA3s5rQevKNn?U7&Lo~|cP{VOeOibBxtd5Y;GZ^&TtU6q3r9xR< z+nr{lX=nx;fB+0nJ|6nCMATIY8E$7ACY~AfQkE(unbD*;)H2l*NWpJ8og|HbjQRFv zrWdT#mCr=Rwg{-ul7Z)-FhBqtole@!dDs!1>ad=cXL(M{O(tclglh?E5p!8FnQ182 zrK$v)=jaC!Wwf;BvofxpbS9HwdDt)z02t{_lO$?DW6&7SKqEQbh$753tZsBsRLt~3 z!)$fSeAM&<1en~DPcWrvX)`MwHU!l}RGV=?38M*|R(-klP5@2)m0$^ugVOq5on6T)F zZb1s6#*=1kYG(Dtv{MNvnwJinGgVmcG)Fe~%h>ssOG&ntf`|&X;+PzhG^=q5I}%}@qd>$7h9XQOcqgfjybNa zTAG>(y!05r5X5tIF)B^V&Y+g=gw_{Lf?}I!nYszbNz0=e)~7Wa)N^);+W}^yNv1{H z5MoW|&DA-Z+9k0(?wAY^(D?emLkNqwiqTCpNK~gp-WxOvEyF5KC!?9I;~FmGpRy6s z#LP*io|01l!W_1u19wR}C;IqHR@h9V0M9I#|AdZ@hXXnDM~=jl>b)&NQq z0&(0B!4slz!dA15Yco!ga;%n2>l90zH_;jC!_!YKveI-)fr85DcEAaUX*-O1Ry$)l z=e~L)YCr=}fLZ7c>ODC9G@7 z-4j-rOCp+jGAPbtS(?vWZ!IX#M~6;j)K;RQ0h(rZ)0#*@WY8y$dR-Fekyw}s6o#&7Wdw zX6_=iLjhr}3XWZcrW?F8S>84ldaGGlskGRwtpEiNmv%A3 z1i)Y{jS8Hi_9nyvYBop=4WnGC<*a3?mF+HcN{|@p!CZ56wv)?6EHLOB=IA-@iY4k; z00QUHiB#5f!B7Okby!z5c9PX51jd7!T;|#B4HY=-VOXiF`v z7;d6d~I9P=&?RB;|obSOGH_?^3m?tgX4JWaG17 zJc5eNJIh6utYs5O4qmhw>6KXIaW<(&T`)?aQl_aCWvxwEv(HwLB-Q6>w;(KLI+L^} zJnRJ&#+p#-jHa$N=Z=Wc#-=+VtTz+SUTv%(saG`-8z~hCHI`hBx`Z-$`h*~gpmRW& zj(F2)G*3GmP!kr$F=Y|hv9!zK=+m2eS{UqxvD`v@y-XC`h$W-etN`LjXg;ZgcCRBSe!;Ii$2qT-6;l$JaNVWNpLIsbPcK*?;_hHgfB(b) zI}iW@0s#a81ONmD1pxp70SN*F0R#XA5fT$25ELRY6(cf11|TFeQ6)4&VR1D>Fj5vo zCPh;=Vq^c>00;pA009UA9R=IZBG8M341NG#bNEk9U&}oT(-|@)qUt}V14g(k4$o>- z?<(GV2C>XDzgSpfAi6erfhz5DsJKQIHw11~3e_h=MuOVyMb?=otl82tbhH zVhun;5j$nrjt3K|Zw3LMSTWbRPKK}Q=15t_n`USGgfOqwIU9N@M`56_m=ubZI)w7L z>57VBRxO?6%a@EVKS#)9CoCHE7+WCDCelng)?exryP-R^;lY%rn$Klhd3Ws}c; zy#D~X_0@Q)$%xSLPPAp+iq}-=tQD-$Jv(Z!_@C5KDH)0{oYTxb@a?YdE3bp%eLwF3 za!NCa2&5dcNAlpYoH(qb3V)|i;YS~iM9g^4gyhE4lsveiCVwU;9}{mzDHY5TQH7NP z30J2ys%FGJPwZa@5~y1aaDvq*a zB!7L!V@~PCfAfk#{f*hAPBze}d0e7Fqnf31b1TeV1Xcj7Dzc&YjPa9^4HP6R1Wl^r zYV+Z9P}4KsY#H2EugJC>(3s(fmeG`Ir8gzH*+{G~7~)NC;1&c~ z(!I*-Ojvr^nx%P{MWO!ytVLC_pAfLKQ#ucAqH}Ce@?>rjZBv#hj!s-*Pc}57OL&OQ zGG!-dr3E|j#*YJ-$%u~_s2JqJ($8m<9mPT{up{elO)f?i0CVNkw|l4%fiZDE9kcIkm8Vyx0R2M#rt3w*3L;@!OPV8) z^&gNdl8Et;TW`IJ33k#YQIfu_uTs+C`@^jZU>m0~mTXIyc1=yNIG#`)!rSe6ZrO7S z6=8tM-nbHKC1x=ma2S z^ZxQT=LhNLc8so%YNH$t%FT7-CP<9%f)S=vWc|)<`((s%$4tvgWpg#9Y?7HNnUs;P zsBkt3Lj=kuFd`)43TJXDkNeLYXBGy+jurISYiG6u5j;dg-HBtSodTJcTYJNZ-D@03 zh0c`3^~;~Chrx9tR@8p6_Sl+4Fg#fx%R z_byiBBdY8(U2#^MZOriIia^>+=kna*26ozi@Aky6#Ju%Q{r!lV@%=qmi|&BdD(*2g z9Sn#lXG;aqDYI*fCSsB!DDO5=5v&Y4tmkDIE#s8}-+1=Caqe2{rcwImd5;`~q`x6? zk(?}9r|vWFKlXAb&e5i_M(XBmd}&yS(OL$BOjeDM1Cu}9jHnPGF_fOtZTXtC?A$NP zT`Kf;le=%ARB>ZcHLWB%b85c&34N7&7+E7Mugvz}dCHJcUi)AMOX^aZB%*Db=I!7@ zB2FVKHGGI3`o@k7nE4lgRaB2F7aqdXD|5>sC0d)YT1~5G1Yu)M(n>*hhHf7;Pmi$k z2N>w*T@Hwz1{er7`CQ6tahOvk2%&{pir9`CRE&5TcMs*na`BKLHHB~zCW}cL<@%L1 z8(_{W#275W5hN zQ&g(->@PWY)riQS6B!W_TTamn-zqc|=#zbNt!aT9$N`K_`BgH->J61|$aXm7P*MtG zPk=s0!osw`*jD z+1z^>@wa1?EDyDj5jnXWk8OzTMLCMkAhfZ>^j>!fvs$%MuMD+78!&117j~H$-+iW> zZB#=NT%*LmwhePhbNGsb64DDC@k|S7j%uL2i{QNJ4nKU_qOEL+oGx_lo zJ2$L{lox~jrz^dvFM^VtQ^Jg^Xko=nVZEJ=XZIPLpk^{)V~*bwln6fi*7=c}V6+3K zrI4`(b2m`2*hf`FlnHzA21=oC>=mbQiEPGi1=1YuY(Tj9I#y@E?^caaD ziJ#svNo%LzI%LM&f&!+(si;~>W3Hrz_iuW;Q=56uCp!*Sh&w0}we08fjn`~8 zIN+45#NqN1(jm_{97w#c^PYgq)4j_%Tl*CATRAJ|6L9|kJH8Raia>8DhFnx05s+aOoNu|N32$>KWh_kn| zrImF1H$1Pdrk7j+nwF(yv%q2_&gx{OreG`t2|>=BU=m?8?go@#YgIbC&8d4NI_Jj@ zIo47_F{hA}K9R((d6BBr)$*Tk7pyD0R`GOzsW>2=yv*|0E2h4dK-bWhyHI}}RwYWX zcs5$9U0?~{O>McUVTxo4N93)9b}ZK=3D_(3q@npbr5?SZE~NG_OHpg<4M-Lkzzbz7 z;*;3yAL1@_{OiA%Rw!y}b(l)eiD~i4=bP~3h_bV)rWd*z25DJ}_!MN;T8&b<9gK!! zeabNW_|5hijI_xn*F2saM>fqf%E-*ssJztTRyEb1`#6{aT;z`5ZJ3#iiJvnK9t%!R-?Z-6FJos)l{tDlI_yP4 zn6j=mV`sK9cVJ|#h9DzTNMn9)HNHeE)4xQn7?A!5_%A<#{G*g;i;vW;Ca_NOC(87* zDk3afO6O9LV~38A5?0PIT?19Pt=MV-d+fsxD$5GGmJPbrZ8%!2t^xuza6gMH*-Mos zwNCMy?VtPPW^rTIGoA~O=H4Pb9&kQOsE6~g%W}$N<}*``lTBHv@c{+KM935nwt&A= zEsEa`Yp!AodF}n3J0^TaI$ORnAWC93@xBav;}<+pV8;eIm<~2D{zOxgG5aQ~+))8K zU?>7j{jxsWHn{NsnRN<#V|Azb>3iK|xwd0$(`@?+?y->e+#619pSAjgN&GW;8Ht7_ zq>pK_!5j2rcH*!dqr_kwgv`W5oWu<+FJXgVb*2I`%T-!C9d6kkn=Vo!I#}|;nA$Nc zkOPR+xlE>UQbWAtkn!GNe=15e{g!WOe%s`DpA+{vrhuqT=Xm!%KBqC(y{G)=?UGv$ z7^SzptCmllhn^x}Ytxz4zu&tidlFHaEtxQ2mae_hd`vqUaymlB3fqF6v1R2M>ov|S ziNJ{wK*w2pq^%NJSsP}K`^l-q)#6)lOdrefB*RJbKg{gW~P%hgR^}iie8a%_E zA15QSV|e<{A9$X@@*{ZJLc(%CSDE|Cz%8KXDKaZPFk&3`-J~SXiGENJ6ZqPomt|@E z-R7c76)uS=-RUZwrmFS>R8-gsxyzgREEo&}16^)!ObG{iigM2vJoZ|+97cOq?op8{ zUm=@hUC5=4S^Q>nyCph%W>aAMxk|6uZ^4U%Qc%F!sH?*zfby*?lw1z_skxdq&#Pet zWJWF6fU~dS4G-qX?UgU`E3bP*cB|P;%oeysoJ!RPR18eq{riQy?`5tlp~n zV^3wvY#TQTos2O@)^J#m0C2$HR23#8(!YmtEVd@#tn9B*wp%=A@}cC>P0JUpR#e!v z4P%NMyF^R|N=$O>waSH6uWn3H<-O&L zA!p2cy)^KXv=I*d5-=K_aTtRM?f_`I$;b?+?{2PWG!BYxHKXvzQ!4eZ?`H z(cKIS5tFfItdo6MArR~^3^D9|SUZwTVS0GVXS6UP#7MScOKvMHRC6H=Y;JsVH0!22 z!nB*;0+7;AtiZ!etJlL9r_o-=lUAur%)lgaBzVNhHVR{uGc=#qDnHw1$k^5s-dl6IBNqKr$!VI5rJHeyCeft zc^MSdXd;cd%I|$}lM0pz$PzfS9mc5m%x{Ipa%+tJb(f5RE&SQqR8uxt@*s)vnldHL znahC(YD+J$oZ$~jbxq|ZmK)y$r8&xkzy$b26xo4+tgKk>z=<81Vdp!{GH1~{$@m$K ziYF-qHqypkGqmzmLpYWcSEUp1U9%|#BiWF3cWMFb)*K&k&e{m3N_U^yeOd7lPFC6~ zTbX|tkcjc0wnR@7C`bu2o)L30FHP~|vrlN9qkO5Sw!}$gYH6#-eY{DPold$$x-Pi& z3L^YKOEiO4FO2E$T*hti8N}@>v$+vkqdSitzA-=k%$ZL@g8=^kqIi0)x!U2wb^DMM z>Ji4$}LWp=oVz@FFvvc-cdCpALldDE2C=XS!W zbs^eeVJui_k}=tg9HlUg9@}wc60wQYf-X=?huku_a9I9f#7#|}>vZsdmSzRJ|hEL>2V=F|;d3m6#mcF&fZiS5tkPDqMg0iZXo0 zOpGdbGuuxH%}?_ziv%N}cP%Nv^;}LcR5cbDxZy_TFK?+?d@Su+dN{EBiEWe5oR&<< zOw55#=*BFUGC5`jLb2p;I{@jrYZcAbRdoQttxHj~=A5QPI0P_@#?yf_DQHA^8No{r z=z3Gjken2hu^t-(lf+ADkr_GKV{P)hM4azkZNA%50VDqaD&n$c<+k!$q~Qm?DzG3u z!8Y5CVvJCPYHARt!`$3B)erh-L>f&cF`Q4tW}e1Pt)o$B)}^*>GrZvb`!dT~5fdI} zBQ-I4wFko+V6QKn=>xqsZhBRbcFC`A+C;5p(rp$rwA-IJ5wjdEx)rBdQv0jg(&?2; zPB}UWZO8U-Umc;c3uM3{wcH&nxQ3+uOX22B%$fJ2jc&(x>Qw!sz6wW>CLwjK+KGam zMpKJ90_7X*Q!(Q-?Uc!Jg4}FlFa=weBm^2?GuV}K1iZh~>YP<)mCp7tA^UhZ7Ob8v9kd2 zBotXe0;%QqDipA#cvGLPHyh2_9AEFGZHDDJlK@pIf$&?G4>CeSE5yc{KV6w zjg5Sw3zSJ5&OprVkbB95!GvxG#msNC8ux-VL>DGUeNx=#-BBT#k9SAoBX-jSSt0J3 zyktv_t;X1V%pNMkNXA*_Jmak%70Eq+_uf6LU08!C-NyG zk=&*MvhcI`gba%NY^enHZsROe&uZo>b#z=j zDfvwLC+WBmGd?R&b(7l}rj;`msA|iIVm*X;-IWkC2LAxXwEE*BmfSmHXEN(s#K}i3 z$}{(!x5XjkJ-NZ(EJoxoytgCTF`N1-rG~Cf7()Xo2*~^N_RVaJg-t0T@^DB1j0}Vi zBW|audzqB0{q zX4T*7rgKn|_fsSzZoLvHyZLs$G2(Wcwb;8(=U@ml`e_wb6*rBoB75t$ae*pngrX)~ zy`z|v7~K5VcaIXBP>{~~*V_OC}x3#)i5h4>g zr?M4hH*ABMJb6b)hM#W|jrW|LqipWvz7tnWwPiUE()696ZeOu>NQ+DcG3NNNeC#qt z%*31wO!>;HYk6?@GgIMNmYrZWKN+{eXVU7bnyQkVmn{DPF#aNA~mU!)ojexI14H)uTE*zAepu7xEx%93oR&{Qe_ zQJs0#S0_#x8C-u8s~}bE3qoc$-z=-#RyE>L9aS>Un)^-dHEDH5cOltZY*rF;N#fnO7$rCD_G`oDR z9kxR*m%4B-ZR`7R)k=5%+D^Wu(n9H(X~BeVV$e1oD0ToxX3-C zMo30uangn-QB;lc{{T~?7^2{DIpFvf8#?vDJTH%K(upcniyK`^(S}4M$P0Zln>4LX zCrbYS=BF(9rrE+iTkbO{k>N9n{aa`M0E$N-hA|p#n|vlFzL}&9cG}l1H(^LDEtloK zXOs--zm;9)V;@B=8ScfTo@65CgfFREenez_?UUm;NiH_x6x3VJ`_AjVX1QXHmaOnm z>ISZGwMX1WEX^$D0(7+oeshr2G8SdVBa9(t)^P#2Y3hhiuxB)f zz{g_04;B(uuA?F0stj3^OeM@j@>Qt^2w4Fi)hj~H&05D&Xgd2y5-620VZ~=o(wE3@ zm7x6Hmic3k@(j;(k`!SwiLT0R?dc_M$!V0# z?Z$Ue!kQ(Q&1!cAGidlgdD13w>yjZ-Bwti#o?BwV&m4_Ifo=-aZuPx^ZqwL`@aAv7 zkPcJG#AT)<4YEh^g(wO~h)p{Kd9t^n9vv0eV&R-vWYkHtB3wLXA$($RQP0)(0-it%?5tyxY>bNg3s0?WWrD z6_ZO&4|{Io?#Po$L}y@z5SJ-3j?$Uq<9NL@D5WxvD+M^xTB-taDqI-JrBMxfS z8p!dtRZ2%>!sW%rIYCt}0oGslmLua6f^sv;0%NjvvK-qRu`ot5Pb$d>+@@}C9+CGO zgvcr9AKRrd39i?d5MGZZ5i%7kS4;>(mU27n)8n0R9JAdhN9N6B5Lm57#fJ0CQ@hI| zc8@En$MWFPP>)h}`pERH*9vi&-*^82ihNFOpIC*LIb$hzp3 zwg(^6G-gw5W>7G_7)ECsr@G}}a0dK`U@n?=o0?=m(5D#igKZhhWiEE}DVZKa*3PA# zY5+GVgH#l)gn{BxuyPK~_&(t>#7h*OOJpcqJG@4{bPP#v=O!b68TFKr0am4f)i#T| zY~;rVlw~sBnkO|;>eO8^C)km8SS{#LjMJuHR{dEY&43Ki8xmK*wBP<^SjJBzv?df? zu&k1ga}o}T2QX%5#?fC*w`4v6S4yK0Z+^FBjGmd?_cB<^b_jPE_=4ZqhXseO7o=6m zMX3RtR-bXxz7u~Y6uH73t1mdtEnUtPfjMfkJzRi{rho2EW_aZigoU@_xsV(Ttv?3A z42v*nWDy8Q+_lO8%(i35v?b&?siH{XxN|nHkRKTuGE{c2Z*IqKr zGS!yh2=e&X8DSc+?iW}tERHPPys$}!*d%coUYi~xyO;ybNYl}>-ZMe)rozU!SogMf zeJgzBnK`9t<=D49uVd{TD0uNAmdKB8-}zIC(6XG=Bw7{U8C-rqb864GD&#=}= zr?G&_&j@Kqtx-y;n(oSlmy8+6d6AFS?n$Y{;U6O#lyeyzg;^^f5LBpVIlz}ZCLy1x z+G1b6`_*g#!WU_KrCyOs%mzW^o6y zV4^Z7&iOfAAJD+fO)YC3&xo&&7wQrPyDn-^wej?P5ml(kA0R=Iy!~}fbK+6Mb8~_U zxkQ(XE|IiBJwE2yg6vw8-6+k;a;eKJ6}aGQu&d0B*;+})NUspe_a<;FfCpuQyTvku zX+g=2n0gxe#6knSOw2frz&20h!sLUfyteo?uk02>ug4OjOG4x%HwMCmQWxe0y^V`Ctfnr!d^zL9QoAan(S^$JgyV%DumecLWWS< zt(6JBJ+!qkq&J47jp>Xwl8%~lvKywy9EM_4op0$n?L~amtx!39xhf@+0E0|=7N$o+kr%t%nS*R1Vxje|l zU}qVtd|ZLa`i#^XV9Eps3AxZm8g-u7RvzQ10-}r8QJwj%&<{3MPp9AeL3#^Ikui7= z#KI?m#Kle0EewzQ@wVAoV07Ejk&zQQ^Y;0~vYj?uJ@TCNIM%&Lre6V@PEOgTh?V7U zYyoV9uX&C0N0{ehcKKlxX|vYXzas@wTz5+lg6m3bLAG(lMN5%z*hfHwgXsuK{EmYxyvSezzM5ihqp3Z_-b~aj3oV9w=YdG&3k{mZa?YCWiM4F3f|BmlFjjR7 zgl3XM4q%h%{{W++Hi(Fkd`7kDna_vqWkRNqq>baCcO(0vpI>+a8*R{;$%6n@17?k z$WDHkoZ!N%C5VY4R6%dek&FHE$j;-r+pH_?#_E;2pu{!R{6xPMs%m2)dbE-n=^bg( zehXDl@x}$nvk>Er#F*#VqwDFq)BFV_DPO8POn{hm` zt(S4#Nt+X}Zi>KQNVdXf0f^rsBLR$5a#-888_1o@1`-Ehbjqbz$J8`zSRzL|1uY;3 zIoOEmD9&Z|I)$;VUPC5(f+y2&sKDIlG+`wW!JGg&4X~Ni zju~3V_3cQMa_o$s)?4k1F1G<@jAEGSFn|Asxc# zr7;%JSaA{JB7BDs(_PNU$hSz-1~K=~QPVA!Epxm~rJFiqHXte6YL`(kDhUSrGyy5|)BvIJST8lWu5pNN=FKHb(zoOKRON5uo zwWd2!W@;k#CVEwR-AU?xq&>`2!om!iQx#?bzS=a(=C11uk}qg##fJPdUGu1(%?S=8 zI-6F&)A;qZwMC9a=k_iJ&Tnci|(aB6l`NJZ)AaQofelM-bE1Rwqo zn5Kc)`5T+@0y-_LL=1K_jG!eH>>eMtu4ckh%q+ep*7dZ06ZNCzJ|>$BBL0obn6i$k z6BGc*5o5?w>dHHnj-7)^dtU816~d);)kuU#-gunfY-Bfh;^`J!1ZOxQd}D6P7eM7r z6Nr$Pf}62O^4dkqO|j!lKXJa?ZJiUb|$3Q}B!2a1*vn;UI zC%9A*%A*BkQ8o{{!OkgCZv|z6gOhTV112zVvP)cWgM^1UMDtJ-M1RXUWF8A04(1T z@J)lwzsN}mL5-Dhaq#0iZ;|ymKlHHeXaruK+Lbz~>CTbd)mpV^nIF_6wYw0FhAnC& z;@mT~ddLmn@Q*nVjnq zQsiAfq%*Uu60m4+oGsFc4AQMdD?HB3N}9ll%{2td7Uvpp1UuEO&~~FWD|{cpMQ+Dj zPEjguTp*q`sSt#@5)$LYSdh}5CP&B=wo)4GF1Xhivb_Wm*wdaRS$^89O67rnSjYDF z+DvWH5DS7~(FQO{(x|jpuYS@T)yTi6wgcAYM=Qha#Y=^d4D44_+c0c_h||>!of+J1 z>~0_>SwY3qq|swYy7%)M64Gep&uwK08kJA{V7<4D_7P1L13V@iV)) zu7weai2c5UIu1c|oskPrD z^^-npaVcuGDsDal z2NRH4*9GKf!A|EfJaG0cv9)cy*iwb1R|w~sV|zFV$(?FaJgnSd%sYuGd=mWa`cck(1{l^}ZuDz~xN`l_&Zd+iEqU)p_EL7443BR*iZf*o#{C*jD)+ z6wKD0>cQr?JCYd!TX@8TpK!x??-2v_GnED&UfAQ2$N-N`2nJYv%T~*ipgEX`e-ZEQ z6EXh)peyM)l#b^a9u+>7jA3@hYrau4mgus$i+v4p69GJD#AW5pD(a^t@<>dXa@r#) z$Vj*iQOU}JY4$aR3Jzm3JB$xGoy+c4HmV7S?UW=^ij~U8knpJDvYFIJi-qGHp!o)x zzIP%92rX4=V^6XyGf5RGjX0#=Kc*pjt)N-0)8(_l&R+1eT4@<9CPHB3a&Q=Rt14G! zN*dv9hO%i=PI6!sYoVxcv!yi35X>ef$27iwZUFpdxHBl@awxJY{G2&xGbFT~`+Dc0zJY?UfFBITv zh4{9|Y|UDenUiux($MQjn(mR!8VjPqWXHG(fGCT}l{VUCT5Vic7-IL?vQ_}1=}3$< z7Q=*bOGy6!PSL*jbJ#Kmt5CLb2cP;A5xilDjQ9_e6%y}*SUD_%)1h;>DV?%E<+N`T zl*q!+a8l)QY6}It6AOazjnl6QP91h!r(D3aYoNYGS4ch*%N3PfJbK;7pJ6fZ)DVSx z2u#Sa)a%=9%~_}wMV+X&06ZOQQ9BLdyRdOiReKmIk>rj z0dKxRXM+1V`DKrirX^nGL)R6|m;~3iQl_{$is$Y&bZQJ^EEhGU4d^q}Ec?qdGD9Oi zRxq&bnJ!Z$Ivfy_&RJhiH?ZEX!+A)FSY(e##0T#)8EQEKoi*vU&hs4^%0 zl>D6==(7vDX)6H$_5^LVb2#x9+I$j^N!w_`k^|%;tGrUEJ+!NGM?Z!zz`8V`Vo)G& z_-dD|ngrs6lc8WnUcDN0|?pC9?ZOCAcjFjzK zHzs7yC?7<~2r_*u>K~9*U(jf8%vQ@LMHS|nxJCypPqUWWETVlj>)3E3vHO4rJ-3@& z2t`aGpD_?c`fIeSI7=+l2EkdZ$I0K*iWZn>2`3j2d1Q56h9V|Regu+jSqmA>Yq(Oj zb+1yXVP5U;v0<{#31E`x1m3u^v1jELmRfu!Ei14K+$G_hrUq6$XC(s`RE8qGG}}mA zCRncuWx6hQBIguMd8EKC(J}J|CS(eqYy>%P@b!p+3&UK13-iDhQbQOby0pF8e~1oc zh~3BeYE%sIoHfYKFt!*>CxjOxV4!!{NpM@?Px@B*xn#){?KhvpRY1k1Lp5VwUvxh0 zgT;PG_Dg18vCND}FJwW)H8~M3a@U%bfMq@@jlX#xj`*(0V!{obJiu*-eH5&2Oh$=N zGJ&78n2$0ejd>#V35{aBs@XC=$9zbcQ?bU5l1$j#05K7UV6_3)vj;B^lVivn0|QWaO1>@vH7gT?Zt&ZyvBV;pW1Rh zGrm%9P1LelQ^N0}_gbZcKL(JHYG{$2lWKRBXX3`5K`zwV-MHDgX>$vX_j@_zF(Q#E zt6{&IxdJelAPZw+A$A;xq=*i%j0-tMH{CW)PW4R1L%bgNHyonjAet+V_NK&et{A!h z01V_JQYmLJg2w)-oUl;3#(3>b=4C1}Hb)}=06X(4+r%(;w7HRNdsedoB)8dZ>Y3#V z7m#t|Q%)!;t*JcisB-7`xWX08SjYgIBq9ydVG$B%r_|*Trot9G<18Cvgn!mn<=1TR zW=HG)06jc(ZIx-V$;KfmroIYgkV0XG{XpeR2)br>iFqJEQxl)#A3xg+`^ox`RnML{ z?xShUxL^oZ7{7#eVKB^yq_{@PGq2s!WOk{Gh~>AHFtS)&G8U9yk9$ooM&2=jL>2zB1yJlkZ0j?w8FjMnaIf0pt^Orf?zeM>a z8F-xxcdK}^KJt8I#EzC5(T=s+enf9Lm%`>d3U(tLT*Yl(Afk52o&Nw-#@SiDS1Po1 zgmhkRc-tw~CySYdGQwf{&(O|o<3E2cR3WThX|ltpqb*h(_H-dy1E++#14h$4l5P#P zwA6kFEeCd)YBN%4MW#$rDGO+kg_N=tnt_NzqbfF8M^g!_Gwm-#BFn4K<)LV>)irZe zQ6z1PNC=)2Y0mdBaWF{XCS2e%ei7qL-|-sTB;syeL5P;bUX-9vt4%za`o}M=k`d?Y z1W5c113~`)M|Q_>G|)$AOUGPTv_p!URBMW+%@siu!xARBSSpK00U2 zY=n=V1C1D+VLPS&0EktZjUh0Z3DdOVLS)hBmi$`fKNPniHjEOXk*+%50oDRikwOEC7r8TRMc9_#^iIQ#VT7%FB6@XvC zQudXL)(omlO;DLF%XtBJpOnCt-28`&Ea|-WI^$f_W4mMeh6sx1^G6G5<2gU1bLt}} z{Ni~-*T&A{1T(hyup>bhIJYhEH;I`VB{5`2CBST%X(VNX0NFmnDIYZ$QRmM3azNPV zW>1`X@>OWcC;dZA3*1|^BbHW6IEk%Qu2(RRXI$n@T~@6wFy9e+LRLex9)FF{G64gO z{iihv+KrjXI<1I|fc&QI{v!*jQUt|EBj=Y+0RoYUnEFy2_CnUg4Izm>CR3EuXKf-k z2o3F+ugbECq;5FppMPRLH^#J=#AEUvKN*d@O1XP`K9T*XA*-&L?Pt z8C7-mmYJK66Qx=%CfWyG7A!I^unqmwpYfQ=Kn$0OA&yMUsnr;5Ifm7EEGG$M3G+F3 z^&&AK3`Bc_zUdhi(@(gv13Ecz24#;AbY(&PvH$|1mQR_@;eVrPC@Ie$wv+MEu1v-V zk&uAd`BaR6_&syWXpxGm?zn}LS>8^Z)ED{sbD*~7cpyirx z^djSHnaDP=3SrCjoK1JxE-D=%&tl5WbR`=tceQD%=~ArRvuLJSx87(?D03B>fk4!3 z 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%0ABr5NfW;x zAc13z@bE&;qYf_D3SchDxJ80+410t6VMfKEx1rib?KeBAg|5eD+jR?v%@Z`)a7IPf zUWk7Ysn&I85$c(`%4Vz{0k%$uX4a;c4l7cf_x5o7vYosm%|H!kYd=$rTcA#W;uec1 zYNH)PE_SVN1)76|Ij`n8CXg{XIzH}4s6C7FOgxVvRO)cRzPnR`>pXyDSHkh)s_&6!? zV*qhaZ73%Bl*d;z+DUWI4oTvjCmc9g^<7j*0+nHyg)#Y|MhA+0)+QI|NI17%FNb+$brG{aqaO?78`xilWCnE}rO zKZ0O!Wr5)cn45)>7eNHk;S=hq_fFNo;tKdkGs|WkUPyabR7NaBDY{`U;g(%c9ppq# zym}^^tnq{CbTfC!2@en(`=-Ho5JsCOOMnQ1*_J1HbX-n|X8ihW(t`m` zb6HH{9LE5Fq|eH-&D{bwlLN^do_e0%HU9v3%MCNl1b3r83g=(nW~K4{yEAbB zd95oJG;(N#A9N3?D~^2>?^F`4g0ty*A(eaw)=@0hDw5!o^^gIsTHdAxD1X{q#ZDtnl6NEwzAZp^NZv(tssu=fl8!|1$=)J;) zstEKDj&#}bS`d%%apK?(@;k)Ce9_LERQLe(J01(qjo?R;nRC7iiaF)|%fc*t)>qkm z@I7p^ZBl&-{5qd_!^AmQPUAIq_)&6>E*&->Yh!S`#PhF3N$z$da3wvIT>4Ee$DHe6 zyF;p55Cd|2mtv&XS}||=-FCAi=G=$D5MFK^Ns^r2CQba+cE>`A@Zy-zbzPOj7Z&0! z;G6yx5KeMR#>?O&U8}W-=v8(Wjz8r@Mr(ri6}S}k62rv^rBLE&>W{nfLe8<^n=Nfz zbmJf#QQW35A^{ibq8iBu^&m@e&LrcCqj5_|!8n;WKULt6B^mz!dLg_C#EiUG@bVW^ zQMC`=F#Ad5oOTfq(4)N!(}x6HYBk%#Mf^tMCx;{f_hjG611(|CnN|emRPMEztlGm# z-_erzAaQDjU7hKq`6_OZB(4P=((CjKH#;qY4NtfFL``-hrHIRz+bi(w`61jBb}GSyRI1{ZVUORhXLdq$80ak;zT$ ztn4)D51CTx&V9U-7;44e(Rh&j5(gt+d(gJ zaBCn$s^T-=dsl8Dqbx84uG1$?;8$%S)W#SAI;~trV*dc)b{4t@ZU{Aj&Mg2Zmoq5$ zRlep399QdaP`Uh*txevp;Isu29PuJ}uNiy;*NVE;NIX!7A8370#KUk}rzIP%`JjyK zbR0tJAVSqMc6Z6)!EwnQ5R-@^dMcKEr-I{ftoqyS1d9x; zzxc18HF>1p+hhS3bFykCtFZ}pvK34XC&J&|RB`I$(%{xaj(k&LguxtBNojBg7|L~2 zD9cZZFKZ*++F=!KZMLf^=OTSbL$HIftn8oc4lCq9AGz zy%U4`cDUd!`^IM@%>mt10rdJgc2Yss3@D6pTHD8(q{(ITFDi>L>4;_MpEYYgyQdmq z8T?a;F2|o#&kPgjhO;{|A=BumOuD9qoH=k#Xqt0=R$ZVpzNQe-b=qR6a{*_)g;v1$ z#3vrjdPXH%3m|dqEi-zqWU2dv{GkM2UmswRIu^C|aC*p8!7QM(l&S--LG($`R zV3M9#!sxdjB<)v)rzAzp2)fS*Yu&^Fl6k=W2ff7t2+S=gv~)vww;BGVAT(c@;6!tTuDBV6n!qO8x-OchL##9!p6?_An{Q}S79%%*_jP%_$zWBj3VRf)(OO5bt( zM|0p8f{4cMd;-nJV`bQ>w1OJiVBZ6{!X&txqZ_MKCABP7u(Y*Wqd~y&?R8uwdrx&o zeG`1q@Qi{ujt5jfI7H&B+M3hkB^&|F5>FM`+V|vZRLxa;Tm7Lq#l{?e%4ANW(1su> z96eTe%%&F#VALRT$q|mD$@;0o@=#n2ip^jU(B-n3tsLOPY6%W_dhsq`fR1W%$jiQAlnW?=e+G)4i#>bU}) zYflo1kWqY`l(7?6EXWe(8M7=VU9uQc3r z*zqrI#gQ^fEOsWG+9W6I9Kw$3t8klTITU6Mj~P_l{{Ye@Qou{ug~v`RsH*tgB;7Ue z_gtSsvmOV2=tmugBDd){cpY$o7^FewAzKQmh?K((rbB$n2YX-z)fN0` zYk+9V07PMNPRWoitcA*SGMzB2e`GS)%yhDN;tCs;H|hS8hwP!O>H8~^KJS8c;F6<# z!^t{BOh-fvO1p`G5yiWNyNn3fEeqYkJob#xx+ez)UEXP!7{n|B%J77~FtUDX=U+54 zztLs>o)1z}eS$he{{YISTHNutLr0q4VIaNn;vq1=Il?%Hv_SBn9^CD!7#WaY=%;X$ zb13HWN&(onOC74+VL18X} zjkEcVB;ifDLj)t0J#|(?ezUQW$XG1LX@l13;DJtj$ysWnd$QFBq*)u%tplUg+8zjn zy@bJ!uG21cH)DU4z(Y5_eoBko2i;F)F+6b}f~;&O5#3B-do06-K=X2D8OZp_=(|hU z#}f?({(%fE_@1kQPmn20a4xxA9rG%25*Q11ynYv@X{Iit4S?cQvtqf*fZ#-M5nyD z!#RQIq?SFkF#efY+>{z%5<`T7ofv8fr@*UWaq_y%o0Wtyjs{$FglzY?k5y~fCV?$% z`XinJK1iRusfDC_qSCU(W$y*=6{8wH3tG*Uk#y|Oy>G;e|1@b#ilcL zlZ#75LVSYWn7;-1E;k%VRJG5KRZJPyB@@g-Xn-KEW!6rbxN$p|BnfM##V`nf8W6zd z*q&+4GUDrcY&juwk{gtoDL?f-#bzc^B5k_w**T=PmM4hRnN%5un@NBmAprNJ`liGP z2kM=OjGd@hiFS$d=K%V{pSrUnRAMGn+Wv@r;~pvj@J|Po%CO5NNYr=cu=pUxFgRI) z7qY^AR!wW1eHFEo(`m+=4*jQ12@jm$s0|9wi^bq21)vuD$%Qbd7%f;SQk;NH)ui?=QO#(3=s_|2>P+C|V zV^?a0>AZkHR!f6gPdwI!SBEZY@N(jsTGo+t?8T226Olm=CHIu#?JIHr0Lv+)ESDQ2 zF7FFTCh?}h2OcR|BX<$-UaLx(PK68N$ryX3tXQ!OuAb~yi^L%JI-8}3@~Hwhi8(5S z2$r66a;f)>GX`(*O~Wh}kcG*{LZMUpN~*x-AG;GZIwIjtZXMz!JK66WCoCE4tl?k< z^Cak+ahi|_F+9=C?i3K4ETst;gmNjmhLVP9_=c?+S$1kJ=q|e}i@nlq@LiV$gNtOw zOR`e|){~+*2{xRFMgu9h{SdP&nHs^m7dZstohIl2YyjaK3iUxZ@P)O`_fOuK{{V%G zU7jS-Qi%{-lyeA|(l!qI9%yNV6mOrZH&0HUFS?e?VaWSd~q zZi(j%fOi|(uMK#dXh$ysWkVsYlGyB8SwCK|NeXaY)U z49sHDJO|Ww9QdsHWFZ-Ihz$cWfG$6ZM;;00#>%at6Dg3+C^4kfXu#2AaXO!8bX!?l zm25JzxvvF1)&r>sOPg86-18{=Ek2@9&1pc7Rhg&V)BgbVj>#SJ>VQuXb?+G*LTpKx z3X$P$Q`yW)Ex^MTGaBlt?+<97qORf6-y}@UC%=a#Lu;crosg%;a?23DBXz028 z(;VwJA<0&!PSXVMdMa-395CZPFC=l*nS0K2!3?*SpqF(E+NX$h7uf)RrDeaRe-5oy zeO7Qnj4pjhT3Kprrgjq;sgAkr@e4cmRE#y#Ccv)Misr9$!I>+wviRDsE`s=duQ9iHE1rhX8PO&_||z1OYh;I(SI@@sYKRYQtq0JXTL zcU2rry~e~0l*rFjJI2FB$(xM~3jM*3&I#R8~?D7%@pFf;ku-NL*5M zbs+^P2&TXR;7+NS>Am>49;&_}8^PJgLMe83Ip>R<0=69xQVSyU4c{HsLsV@J`g< zVTEEi1la?|24x-h0s#uOoY@i)f;CQtdu}-nsA6Mm(&gR@UK7}9&74N?RXi_|Yg?i* ziRP$3ML&6Gfy63zE*CetV-4`h94wwgV<1$^8pi(sc$JCUy4g@A-ussCy9h`k{F8|j zVh}fIE%Ul2R!^3^bzD)gfBmEUtn0MlZ>7hN6Nl-y4|I}Bj#?{N8THwIA5i36G=6>TIQGAa?WqPJ} zjpL|J#y#Ca1*aI9UMz3N)eNpyWRg%u;DH-8V{vsrIELrtqJZ0LnuG_cyUiC{PACB; z@Tj&3hY_3}3B}Z3k@_BK*j?Sm5uKCY4R?o%?GFGS1Dz1kos+W(;+*GgQ@-u?abd_h z+Jz)y+&o5#B8@%bJ?zjU4F>v~t464k1YQ&MnCiisx=-xdQAoqcCc&FP&wt$fn?AX7dZQ<38;$Z{}4R zY>iIyzA5wOoJ~xyfs&+gaVn#?srng%5R9N3Hwfu}CE?T@ls4CD@zFRV@_?9kb97iH zyA4L3grT}TE!33UHuiX+4n5Y-G-e=rEJwTn&itp6z8Y-|>CJCOK%vbI=Nkon&`p{$ zXy&R13`ZLxFcb7z>TDAQdV?YC1%KgDWAuoADtLGRvxVfWqGA?BP8hzq zm|fr*F*q@d@j2Qc=F=IIo6i&W1Yw!#g^7d=u(9o4&hj27mwK&Dg36V8W6_cJS&or9 zW;!OB0ARE_Iqdv4IA^Kg6QSg8x>GHT-gY`fTpWNFkY!?3jOk4fL4S^3d=F91{k;W{5kwiH8 z=#ENeQOQrWR+R0WqhnfdFp=aY>KnmKIph4G zCsD~6K;aXM%tC%3GU!qAK;V5rVk8oX+;{!Tp~b`_4}u(HIJBp>@zUw+c$>6e;8h|W z?G#C?+4o({DkBkS#Rqm%%N^bZGJH5yKp;#v+Cthu$pj{Be3LM>&cp{ik?^(w;*3AL zC;N&cDJu>PVVT}yP~I*Oh{cS^S!#iUr;_$)k`s>czs+UERd791TKp%+5bspuAlan| zDc3iS04BM>Ivv7Lwu3;L1bu-bn3(2?#N%}PP1Dh!O^H7X%3>L#8QS@T1gtom;ciP@x+WYAE-gJp)D-*y=1d!g4r@2Ok``PRW}J^B zC|V zVHgyvHU3#AyA^7V{?TY!h8X=PW^${mSY5*i#W??#ER){HQD z{{YG;;$|^p!CmwVq;i=~f-+ZZ6B$pUqlLH{AelT<_gfF%wfG~wGMaGs$WMGb*=bmV zj$IKAVK_*cMAdwF)}>{kRGkgWnyB7PZPDB@l8jiai5V(`D+%~@O@X010*%Y7k`!hL zQ8g(RIPw~yyH1PnbQ?dK-;HWmE=K!`Mk9}PDC<9^olj0k=!DfLv1?={EVD(oGEnxDp z%1Q?XX1mG8$)|!22C;9WN)P_8@Sq8sBPdQS*MD;N;D1e%WwM457@7|Wg`PN2cS(Ys zgceB+PPQ%ep;_V)*)RmbS0Xjkf)dsG~9}$!Zwd{jKPOd7ieqz`%Kv8Kv!GqAkbEM9y zZK@4^I!5GVMeL05l}n<=Zkj!e?`$fdbon7*?H8hkh{!E->#$s43pe($i=$g8ZVa{( zF}O6IM5jl3($fdrvTt_bt1>GK3*KSww(LZv4$%3{_GdsfSP3kth&I8SvjI9?CVf|q zvs+Ab)ffiLG+;R4{_7_G8=(2{Q7ewjmR3HaXSKcQW$%@#cg=nn3n6BX1BT4%)@8pW zrx>f-YWhMO)T$3hC^+g#AFAplm4TjI7ZsIW&g*jmLh#dQU>y|)7P^saozd}C?lWaE zi2CHaBY#uwekuFB5oj&ZQ>llDPVZRpIEIIs&)}PP0d8p7l|d(q%HlpqVQAE+5=Rl3 zO&!f~h(?D{oHA|E^I4xQp3|I9%jmKj&OCzDm-{{V1Z2a%aseE!AoQhE4nC%x?(qC8A#D9jy=J=O@Gw^b>=liV>f zWmpq%Mf{ht6}g<1GAcZrb=~4TaHmN+l?mT-P7INmZh=d@PM2}b7?P(2Ie9#QK=)Xq zJGk;+4L-%#(Bsi@D<~=Zt*3b6<0u@ldMoa44ynM}-84`^95Pls5YKcJ2sImwPbB@; zx(Gi-7|6R%(5DgxZ8?QwE6DKKc8kx^HvkRv!lqhrN0J}GsXT%L<30(*z|A~@3V`QJ;A4}YK(HsX>6;up*=ph!H#f{T*ru;o&JBn3IO^r0$mk#|_axdJK+=mwAQL(Y%o)H#VHmEO!kPLr4%u)nrJM!Hk;i7(k$uDRz;EKywUte96cI|020`gH}SjVyj|$|Bj&HeV}oSLkeP;)eY2S; zLqM<_9e-&9%Whbt`60Sa$yp|}wZjPqC{Nu21_5s+C8fi6L)BBl>Xhc$6(F3Ydn{=9 zN^CVnR}7h?rZu7Yz*Y$FX=^IG;^d7N3+!=3!2~Q(+_Oz#G1h1#b0adT4E2jbdmmEJ z)?|lv){G+wznXKEX3K41yp_fS#(ScwmfNVCS*K zUnQ#5bsG)qR`@G-l_H=20AsS(SyPvJjF;V(8Q=F^(KPmot;oN_=#CdTpvF*)y~B4E z^VK_ewxIQFN%dA`EBWe$z?St?-)>2c#L*774OU=;NpOdHS!#a9PNXl)TVRX~4Oxc* zK1-SSt|V-*Ox>eJ?}PanWgC@V{+cK!42ctU?XiQ;C96u}6axBKVCZs{*&S8$<8j?i zwWI@=$vb=0pEXJDJj^GCx4v%ZviHgLSs!Pb{1=<1{oBm2jG&?oSBxwk=;Eq8RuvhE zkNJg>dBM!5Ov(zO0fba$vBf#B4WIWLBx(_~--lH=JJ7*DlxT>C(k-5DQEiP5rBFfS zbSP!uIyzCXbDJnw-gQLb0$fgk3>4c;7S3jKCz_W57|`%F&*_9Ca>;K{i(2MXo7)~Q zs%dCpmk*-%k^Uf)&Q+$s_01XX;(C-MVvi*dG8se+lSl&mPyh^qZRa)kVT}{5nu}c& z#1`quqtZrw6ykGadRa6Y^ad9uwZi0rIO?L2;WH~Kbz*#=1i%Sk1MMyX6KR;!tv&WIrRxWHW*p|0H^OHaVE%5l5iKq4-S%xo!$-NZ*j!{ z(Khl~Y0*8z8$z>1N5ZknP%bg(gF)>T3Gxq+cGi3+A{(`-_4{2I`g8+aQ zZ+LI2nQcM@ep@3FW2$Ce_0x=D7U5(8wtNsGMoY<~GF0I1og+rLA*s36b~uI$z!!4x z3P6F7x*oyU1io*|F1!@P0rfzq9)UCjNluA-THP+KyjHFWyQG*2;|sxHo@nBRwtZHO z8$3hB=7#GVAJwTxafC#0UfT=DJIXg#_guGXPGaEoO@a@qu|6P#U~!VO?uiGjm5X2E z;@)c`*nmMDoCgb_#!g(7NH&6C$UM`rPvTMFdT>~p zIcT2ZIwm>_(!zNwkjH#N0JFr?sFZSUM^!B$*8<*1xvn4|E_o&r+4h4(#KucRUIr;f=B9kW(`?B$a8GN^HwkO6@Kw9I?QjmF z-OKy%oJ;XylY8ekOl9{mG)5xcJkzFM5z!wmX~YS)DOZZVB-_S(r4tWvF{cfZve0UI zpw{k-f$;k=fV7M8^G=dUkb*yZUoXnv^S6rPg{5V%l3M6J2)e@!xRpwoJYfmsgMc{z zsp1j4ctm9o6G`CKi=o{&3=CP4O6?5>Vg~ogR;?gtEP5tYCVi;-F3!=Q6b&vrXKPMi*nVrU@hyG=uQc*JBCQQ6LDhcubW$+$dh z!^O@20F=_w21G28Lcm<;69cKW#NyWc6T8hzW)H;;>Aa!ps!q;K9%z5~ zj&JuBA9!jHMWb-6EEGvM>WsNSf}oL${*=bYs(@H2VFQ`EbhZp4sypfk5st_Hz*7QJ3?>KysY?@eI zZdNR-2gy6yvIiL$DxP2`ZiC?-BB&GhnYr0>M+m2PVLF`?vvYn3 zqpT;u$PCBD2A4VmPX$@#pz+uSo-mx!aOK4?hP>{(fmt_nDBUOE)BqJhC}Lq5i`(eG z4s#q8h8P1#O{XJ}nB=K_-8ZunxGmPXz2kM47bfKDZXQU)ocJq?j;IWEoJl4eY$_#ulC zf)s9j+kI6#VN~}CgboqeYBYaUk!;k&*gq8Ry9mluEX=vfAW3m`g@mVl+uq{bCdlML z+iGpzsPmA7AR7X6fVdK~@5?8V2--JFJE9vWQ^{&TL>95ZmeFeFjev(d7Y=}Ius>jv zybmCRW$;>DESV%PdO9Ou#=3uWaEu_2MB|mLon`=V1PnE>!g#FJ_ply?aZ#QW?yGpj z;WqSA>VFWedH5p-{X37I%s9gf9(wJPvc%GSuDzm4ae&OY@>_)EITe`Eak>PQ?*mDW zD^|l+BMlaH;XU2sEohD@ZlJ~h`f?>1PSD*MLbu2aJ5%fRf`^Ip6$)cbYz`~lX6U* zhjYBgADGz?>h2Kl%Azq&$?-J(6k3ZL=$*O=vMb(zIFv>p+>x`LBZ)kAl{*C9LEuw^ ztvnI-ygd*jhR!R^MtbZSW^@RLhPV(0sNw=Z=rq_=%8MfdQ^T6H=JF(!RnCxnMtSV> zN7;q#I0-~Do2y{N@23{A1&xxz>6x0vjT@SNB4-GurfX!8rShcl(Z?V-1@0Lk+x22#(N z>#}CeZ_Pt{Q*3kIsFlGElDtk~ z^fp=uO^S7>h#gx9VkhCzIzY^1LU!#YNgXc=V^hf(!qQp>kR>;IaJ}45H5gmGcyw7+ z-+`l>b;jsJsZw)*=Ce0n$|uo8-DQhTF|gi?H+sIv_>IcVuD3#+lqStlcXUVEAN2&? z_PkPI=c*^Xo4Qelz8E0!K>q;xH|h#CL+%I>#a3_nN=@$_7B8ECP`Q0pjWx7-p=Yo( z`L#wGt;Ev2&(TcMsM$Z2!~893Hh5~NQiGm{;Dj7ot>6O6(de5e5x_vd1WA2Xxq*$o z5veM*H#BvSoq>&I{{S~c*Smf|1_JD@qiNOI>vZB~txu-Rz`;-TJ!ValS};_N)aeYS z=#}{!@m}}MGZZ-G$yn&Bh_gt`%?b_{&6258U^}J<%aWu+u!7^sGrVaY6QXN?#e@T& z{3w!%WgQvqyzopEdp$AWZ`Ekk?9&pMW?J^DMtPzdX>VcVznIiRTUKv2oaK12lnxgE z0I-NR#Ne`5*ib&Ga}zHqRS^% zaS9XcTr7pyT#TtTIjJlm=_wCo(ZQH4X1cAeGbh<_E*Z$ zn*1kZMv-2l8fV2qDJLZ8#EePQlONr}} zW2zI90ksQa*O1;R#b%;@@8+|^k>IM}97-p;Yc~hMFuXK?KXm6G%Dt9=R}WR0P9#QC zlI*UoTEm7i^G?A@!hhjWhdeCX93smmumOc`x})6h>E^#c^UZs13_Qb@MfXD!4sAqW;!H&GPi;qc zgqZSQ&$p6^BQ0FF;+vfu3~IGIFKLct5s%tDIU(P&ATjrjik1L0P5ZG8#R{cYkTh(3 z*JTs$KIVMU)>Fe;M(S& ZaGhQWRBS2bv$wba3I{VO2j2=1Mjqq)RS2QVy#NZ&;4 zsx*`4gg894j^y1Cz9uvksFeb!@VxR0nFk@&4*A-%LF=l1Do_6ar;>KzxJ70I90#6j z+8`*gf#tIn5G59o05%R$uoC{uQh%ZA`9gZi(4z*uHr0PA(H+ry>Wi4%2T8hl#?T%c z$+^n$7<2mYR~WGR*<>7H9O+cqttZR0Z!(+rOnf(*%-ne(kBP(1HhW?ZUP~nBV?HjN z6G$(0IFDzMZlKN7g4XYT zWnmav-5LgnL6pL9_i%jEaLZ$j<269u_=22R+?~BJ(VlA-HW4v9xQ2^;wL47TGsO?N z#em`Fo4Q@QjWBnNb;>QmU29z%5cBbc;h2?n(`m#;t|;Zb9TN!^CO86%0m@0Y6zey& z35{YbsmI>8JL-v!_1`pSW=O>fS9#;fFg32QZvdHaZ_Fxh4{@{<{{Zz}uw=}`dZ~TC zw4iZnVF7H)!XnKuo?O=qIC`RS0#jr#A>d=((#th(xe_ApAv3$DG8sP-tqmld0=X>2 zeAMmsoeMF95Wklah{O%2otE?Dh||e&vNtcmA5{k0b3)|V32ib+=)ae5U5BAw93YOc zj(M+wU~f#*uF*3n6DKr4cM;7tTI0AvgWXT5vY5aDJ3Udh3m-k21)AWAA(fBG4S-sv zXvuSLR}UTM1j0I;4Iz+huB2nvrjHF+a|^}MGzQ*k6)FcAP6g8G&0i=uHbTcY5pwb(A)NCq=4(2SYS zifP>u39j^%2t4*nLk>3;+1=zUjuGB6!sZhpj5t^|o+W!0^VF=n=?jL_v}RF&!2|q# zgNuynsE}Qp8I)RTObd)aCl?2gj|3ovm@=*CcrJk&X*I>yBethXuu z0MvQ}(DM}Ia=MjoxY!NOr-2|R!{{Z<;9<2*qxss3F(x4mEjLOVX4L)~P8K7*) z9!b}8oZ>zR&3RAFwa(OD4%=-d30^1L<3#*Ck?FC{{ZPnp+Z2r z95ZSCkpz|kQP@o4x0I?(ser*Z4)`XTa>oZw@Q6>xN&XsxU-G=wHj{@5E)s)cqg{}V zIU)$+5r{IU5v&JMD7JgoT44({%s(^?;*}M3pp}VgpOPj_v>2^E+uw$$v8=yD#8bdYY0o5 zTLJwsWn@n4SK+psvGWGdfWuL;+2BNkZr1`3hB~1j^-l270^UkY=!W($yuM`q%fV6P z4)jO*%p??o5Y{l8=Vl{ZCRg1QTPjsNESY7q0dw^ zzW|;v)fOXyjC9Ed=PASQDAPamr?t&53@jR#Al=hu44@{HPNQVNkT|Cd@(GrYygy5) z%PLZ_98=s2jzd&#Wa*TTzuZiAot&660`(QKIKq9Y>U35%dXs^H_pDO&cwq5@l0 zx7RfiTuE0u4hkel1i^9woi-l>c=OoYyiU=*QN>x-soFk_UMg*KujrZGREw6}IZc~? zBzK)ztn$9e7nN}FCNwF;2WGC^7ZVwyEGKfCb9xuVZS+}{9o+AcuT%ttc0kiOPqPj9 zZi!9+Iry*Ug^Ol-tuj*%Xp{MdYSQornr)={LJ;VKxj#}9uKP`Y4x}Ap!L8p5BN}|RLoh=HSv_3G5$28JU1Pg5ehchXGpqU((s%+sddBv7$ zn_W6MS*h1Lpm9~zFcOm{TF5cmq00{?I!m`)Tv81X>$n8vstyuFuC}jNy8H@ z)lzFix0>H{IaPcZBKfNB%5&nXPf!ZOJYf+JF>XqupxCq(BcC2hT3RIXk#;fZ07&+T&s_vs1lJ=c18mEBI~0@stEJvsH2a*!rU*{UCq$l2QCBk753! zI|sI6>GL{s8$G9wsYFA(UTA9vTRB0YQTy#VUsX6}-od&=ZTywDSl2Okct>atntkKguoV`<{hvug=NODc!6&crLo zQ)dj2Gm8!{(Q=|k#ay-0KV>T^i)y9@eHN=bIC&u1bG+0#lj1|K9Mx8X4I-Br_fsXr z2Ej%SZ|1)nvyWDPgf}USMt(~Bq@IXpNxv|sy*?kxIHMcF>DO&Zm65n!)90e#j1@_x z$H4Jz?<8T&;)A=Uk5wU#?Eq7sQnhd~?*(Sfkj;zca`uIn-TyXsNWc9L{Om)s8p)0x~m>VP1|BT6hvQQ~v-c?uVG@n9|^J zw3K92`@zxo*>>+d`I%2Hbw?8Ba3}+b*dBjXtc|f7@m30Lu-Y zH;eLw&@k)8FiTr5j!MmLsClU{u6v>m#d{PtvpwsyKgq?3n?s$}%KX_K0vQNgp5S7)dJI<$mAc+-JIOrvSdIsh00cmd=*h+W5kUct|r)Xu<6H2sb! ztzkQLtsfnSVIh~8%>WzkM)J)7>)kdLGEXEytoHu^Y^O;WqB!$TBZ&*O;x1*z9FyCE zGq||wtI{TXAl+}j6b5A-P$o0v5gc}Fv)U|Z`LA%oyfOgmv*K5Q%;GK0Ii}hb1w^)l z+mYGA(1Cz+S$i(ktJMqPw^>j|G7gtV3nLaCW14FQ180GkNlyFRTin6bTyRE2EX$fU zW@|jZsJGa$E(|cT4R^_Ssn;x?M&z#u-D*A{V6Hauw#?r`nLdbaxY*~K3>(e%dFqXW zub@B%0|4x?Ik%eljaGx_uy?c%ij(d=cu>`chI zFz90%ILW`sY1ruxs?)Rs@=iS)bimd&q(J7f4J92=!@ZwHmPxpQHXP6($>@Z!r*f#x zzu`PqW^&y8!fo;S^ipIt9mjMghf)W2*YZtq4Xhqa@G1S}8ir?4)qf$Rd8@Um-zJ&tjj9;>G#t3u~rwL8cFQR-qgd$x|pv8_gb7x_&@g5}X zC&gg1mXB4aTv5AS17<=1~1`=UfsNckRh_X zmV_;Cy_aCO2CUlY>#^QR&_U0FWLCI-A8VQ>!+jtB03^@$r@8NMvl#_Ycdc&oV%FfM z!v~N2x@g)it6-k_K?U`B(HQj&=%eKL}l(t^WWi2-2nc-WPAB zW9)+L)2mkKFooG!M-@Cw#BmZwRO#S24qH6ewS;_Se=U*duID(y{u={)^C^u0Fp{zh z)o1MvX~%ODq~DN1!PO@7YZXoN$;e@Iu1>MTVHc2kD>NGor+Tee1M;;`iNvg%QvFWN zE`}Ge*k6T3gpyG%d&k~Y;yuyHTXAKI5V5B0USUoeZY}iKZb4}DlybS`nJ&<}=;xlu zbK>8XKYB3_C30~%Mw7)zdm$SW;#N3G!x^21%z@~pRzYyS#({f1HSKZMZRDVXOgX^n zO5EK9@s(1bF*x*Z_+NU*WabH!CCAwc4R?U!s2sL%2uUOlH2(m-1bAl1%cdM>8Tz+v?l zPPC8*1Z)~Fwk@AjCH8GG#T-=#mz?jxNHhN9W~uQO`zwZw*t zUybaYo^qKdMNBf$>zct4o?$!^cz=}BmcjUyiJlllT?Re@IQnp3$slvvZPPxBEe`~6 zfudvZx|v};ZnFb?eR^yqTuj2HyR;l7KfJ8*;2B*w;TZ%(%m#u1M#jW-LU5fn zG={v$p0Tsrs`KV8vMz^SX!xb)%?mb}_^h9dk42%MMA;2tGr?ws)^w>;Z;@&n(Yw`q z_vjJ1BmtzU@dPg06<{waV}j0hobu|tO)hC!G=?|*`6C4}ue)yS#PCNMr`f58H&%$v zALFp>yV~pXF41ZTTqOxEX@t@bHKUM?=|;yy@;UtqVQqSC-i)2iKkm&iIzj{q7UY|S zvYbXRZ8?Af8PI6LHV{MtTRHVZenm#N=7-(5!R5_p><6es&NiqYnQ>ovMSwK1#@4d! zEu?3_{R+6p;?SoEx%JpG%?oSfhltr58=xIZki>srsZ*q6p1Ff_j9Edypeok z5@|c*6F7f_FuJ6)aLpSw(ZLTH#7|8=J4Pp90s?bd9A^O_yU;8B*^ROr#UBgHq#^g% zWsh&QY>LmlaU)&{;8v>L^X>zp)E_WH7{wfc8F`?4vgtR179!e!5IwIoY;~OA(`kVV z7k0s%YObW&x}!B|c!AZNQDMZ!kOjmGLT(WIfx%`3by0dyJk>zb*@N#Py4r1Q`7GH$ zG2@sj33fmuO!lg$6Qqy21Forw*eu;t z4~O|jc9_qgM0Y^r5BC5F;;mho;8a zro{0=Pb3PpmXvXxuQHloQvqam$5XZoJA&)+M8mviB2PV;n-?Q+Rle&8;~dexBPd+x zj_`X+&YahTpMv8d2`40RIBTHU?KcmqY*jMoJT#PVQS<1tPJjpVcM9FK{B zown+Vb{ecdcJi7&>CT^=CgLX8Mra8J?Kyd>g`%;s8aX+&92*Th^+S93mdZ6-%{kA_ zV>hzhgCS@K$qm=os-e^=@XYdrPJ4rbI^n7^x{{(fBXg12#R0r+#>=^?0KGZMQXBr$ zeV(b5gKR_Sh>`iB8`{Wj_eMgU(T+X~j=>;Yrs8{zfIte=h7jKHmnV?!CC z-aY}qm??+w&*rK)x#C`D;j!iu%X>v86HJe6d%aWKV~Igh>IswsL|k!Capbc{VZFD3F~XaSIhHdbz^Ng*v84#_az9%BJB=(hqrOic0MbdoV@!NR-GSw(GRJ z3w&j|1i%^%j=pG~^kw;izj)~ALd^k9he<$z%@hl1d(rYuH+xI!aR8&d96{Jj@m{NR zjl&$ZMZ&C{2TQNRX&@2mj7DIelK%kovHii(OU5d_WuA*2-iET+VVXUMJk+HEPBc}Ud7REI>zynTsa^< z`Hguah!;^i(xWaaVn@M0@dDG(YoA4s#4$!KfTS-gA|5 z(>_5susWrf6R`%_txX%k&x^FDWrdADRN_c+6VW;=%z>lF1VoBW4?<|oqjAH1#G6?g!m^#XK^I35ho@iq24+7)Zl`agkpZSy9-L{O9K|99w zgmD%QdfH&2k9z!5q~gO0d#v0iHBq=lEMdOrIwwaI4h(wgtPf}ccr*!=*9t5k1Mi(& z?UG4Np3ralKg!JJuLKpViP+mUp$*m&EMn-KTP^`+0 z4jWz_A`c`rL=epnH4Pa{tSp%7u&pLx5)nOB%iIFU93#rg+n{_D0viM5v_}gYcf91# zgi;Qt1m<{>oeXg#4=C6!cb0kt_RD}5;1S((rei5phj~8G`6F^c`JynA3a||eH)idb zp=KLSM5hAxG|V6fZ9F#jqDrv}T^r=1e469+Ku&LO)dPx^SSv_ENmz^h#^ySr3WZIs zX0AL?7-ZZ})lJw#IhC0y_=5yb6U9kwP+M*qsVBQGW-CmKLa9x^tiov^dqX7{u(o5S z%fne@2IGIi{xf%fSjh{$|9TnLp%c{@28!8-C2J{KS zs=NsC;nh}=#E4kj_sFIOLNzJ%JcsV6InZRjbW{TY-V!d0gk~o+ayDKjvA9U$5#2Q} z#HU*nL$137{${+4)A7-F_cxJ83vWE*XR3xk!~r>{pUeQ21*Ax6-M<9L!gr*qjKQ7& zNIrH$yQAR^8n1W%0I*qNJ7r(IxAka_67uJd=t40|y6hj!_!aD!^4MYa>8g7Fn-0@YpjG=)aWH=0ekBB-Cyn zjQtbh51vSw29XENP@z*o;@g?%pjKruG`PYTd++=?n5W-pgl5;-Tn7x;`zmB}Kz-Yw z`RoP6=?bG6fHk?_cY<~{yL69qgQ|keO^|u4u^Ai|VuMGJS<>wW+=!G2k%>2P{?4n2 z^g`6Zo|&h8;{kVP)2K~88w6zJh=#O?*7lV>)EqAJTysO9RK-el9dYi?p>H(1bMpnd z8)BN^0fv8DQqjVWf*EpIidZYbAkaPBF#BXsCiGe(Uv>^ey}P!uY~h8sBO6w9eO zvH6q(H*yhBumJ7~plb$LE?vWuy4E<&6; zHC%QyK~1*nOp8pTY-ixKN5wWm!!yA=hBdp5(#mj#yw5S3&1qn!9@h_5?}S?;CviL# zvE2D_TeQY*c0gvIgh|HpnH&cM*3^09kPC`{F=9ST5M!M&DCzT9ySF+B;NYGcpNr8E zA=c)pmIjDuE$cSzOgM`~miy5t&o!1unBboGm*Kox zGf}$WZS|BK1w&)OUQ=c%nAmf04Ic3(7Dlmi+CMU;U^dE+b=pgDnu6VKiK&Jsitk$I zc-V64wQ2aK9s5rqbXHOFUxwWLvIzTleHXYTXg+t?$-GKVm-N1@4oF~{~tH@wF~2wZBckAc{fl3EBV z#-(3_5IAZNi*wn;EkP&xZ?yjavb`qj+A-^8oP3;Cra|h7ME)~AYg(@Pqm?F@=w%<{ z7oX`z+9f*AW2m3$aat4eos1+*+V)WPM}_wRF_e48M7F?@$#y0Wocrj4|iUcBX@h9L5k`@G0$kaEu~%pbb`SY=zOU;niosOp=Zr z!W^ZfTyahmt9T^j_)X|^Jr-^OpHwIAs!#V7;qe|z$RTF751Ofn_I>(imb*B7b{G!@ zr1eg|C%wam`-Q2p&;3Jk44HByUC@vM-}|Pk`~zG(MOBT z5lyGDvI*lW!Sm*$yMBL{m|8b9*{@Rgi10T^>a~`Vd8|0h$&90dV0BX%24CEaoVf%5 zhJcJ-SpNWY@I!s|20o0*W*6Rf#!M=w#&A)m;*MuUNq{&^8<+(L7S;gFWyu(T$)000 zbdMlciL@Uhh$1#knJ}!(Q#$Uba~*t538p*21#0qD+V=@rNrXHuX<2vJq}Tz(qG0Cz zPfkc_^VoKOC5YampnQ!=wA`GV z(KwPkBZ51?hSYAPa!nW-Tb_M36mv!eIs}8v*dfo->rzT_V+*jhGkoymofMgq1%^w> z9P0fS8IFhu76V}zJJ>Dhb(x=v2d533LNZwS0cWwd&&e|bMuPI1>CS!FUe+*wyJ3J@ zKnTRzR+WCM-X&NIW8$4GI!%*s(-vz7)etFD-6h5(!eAGX)l)BTc5xPo%~37adv!b) zP1@c_cXXfZ#;PPjamLoR^T|y{jGbafs%PBa2s$b)b4j@(Q^;^K0UB~oA}F>`8!pNo z{{VOZXVrEr6O)6mqUW*H>~o0dgd8#PPIfWuM=OwuHo`u84~5P3lqADIRe56>`mWM< z*jncYTu)WoSyK58;!{C28!Z}%S&Y#G14-C7s`{f3Cq5=W$~SqQxV2Y3;b;Suk!uDK zd6?vYbNSwXWY(|_8vw-TWyEHpMw>`zC(qFx+eg^VG~mWC_19rTI2#<|29snS$r+1; z=TxHNdfP>2wbn)ou_N&Z)ijoS!z9@h+a&=e`znl7<*)U!YnoVMV%XbHcm=pdC*X?xAlu1~=r4;u9YQ?$rMPyJlJ_-f0=H<+fJ!z9DBys3ZFG z9}R;lbMZvnkr9|$9fKhX#W#31UHh+bsk_=9TR)kL`);+U=u!Uwv~qNob~8}&#NYC< zo{*ec4OiiC6NHPR41sa6<*siHP>P=1Yt4HKp){SnPOhz=q67fP9AhKRI!FYQ(?r-j zWj=R+JZ2Q&XfA(7r$p2#t!-DcJc4NiXn@4oY38#I2wtQ|s=SNxAv^}Lwz}V@4V|l0 z5cj#j@(Izn=0^x9_>sJ#B5YAOF&smSPKoWQOE{ahd2H2L2sPR;eu(<7Ac-!JArfNG z90^b`(ta+n0PXfm0P-6zAw&zONyCv*1-J4?#KP3q968(Oskm^F8wf5lAQ^;z{CYrq zOsZ~i%Q7skT+T5Hxt6ziq|R&Z-8RFbd3SkS8=1&L@Kt6Yy|(B2ZS+L_<8eXJc?5P1 z;WW>dNGa&I=+n-1E3r;L7WQ2gd`yto_OEDB-IHfJ$&e4xRG7JLQPp6PO~tcwJy&HI z%RFZ!>=TKn4V6bb^%+@X5}r&stsX+?M5)NGH$q!aBsXW!D#F6%GSbTtaet2_>>_d& z+H9sMvDaCWXO_VUyToB(`IV~0faG*NHZjMdlI;M=cplp{WC&bW72z%?>=S|28>~Uf zR$=GPcD#w;Q-Bo+c0LE1&yO9DSy`YsAIp&)6B~drt$Pje#%Z`%YDjMYN@&3tt*Une z>Lo$(d~B@gLPa*06l9f}7SyfK>wv2L%W42>9GZilM&M72^(ye}uv8ziAOm=5M21a< z!Qlv8lnLUgyA*JefN0dX!tDX>E&zZy^HhXTpy_sG;9rMU?2S2W5gnSC_)5(HMJ&P$ zE~zke0eFkY&3GQk9Z~T*m&62hf2Ja=JDgwKPTnYQz0_OkB?$%r9Z}qH1a+O0;b`@8 zYpDUo+{zkn^18QfGnwY4jgA-xkLCkeA$}! zZm$RDs=2jKqU{WBI(7o&UegMziHkt}(@9?U%VGA6FHM7Ww4i>Xjf+RBsNs#5jv~sl z+)jkz`KnZ^zTwX!;30N=APaz1Kj8vDWLnaEL4bN}j83PI)jYIC#)*yZ9cnFzkIYqb zN})Q54)b4mO~-ZZx}{LG4d4Q#LtaJPJ}bi61@pUZYNj_ezk6HZ4CI}cikZwTQ=(vV zp3@HCL0h=v02UTPFxaOPc>p&gH!A7Fk1-q-ove<+Vqx+8_Sqo8O;#M`0+ zdw10lV{#E$IKk0)aAJ<730W7(L>MZj9owpHad)>H!Bt_z%!td8RjKEpRbFE#5V3iq z_|;3lw#lYmKw~mC;+-e`n{1ke#oYhc?94mKyDQ`JW~L<^)1N;n#Skd3DV+Zoq}+; zGr@iv&g^`Ub3o9pEm3F!HaEdeeB7=9#K+CYAc1Qbz}B@y(|eeB?_6y+Gk$_orqVxD zti_L#BNgeic_Re`>=dez4)ftQE(e2CEk3n{XHv;-{8*DwAv3LN&EP@zb;bA)UfPxhwB8?R|sVadze?QCiN zAS{c_bX*~NFIDX{JJk*$W+=4$5#Jd*k5yv7G5-Lhp&xkY9&0CV`lAL39-mt($3I#Mz-d&61>r~+ixO;o-foR?EI02&U&Hv#!vMB0Lp6sG>?k+3X@&{x3WTg zSTD0jB_-H#iR7S3(PwXqkTE1r7(|1`FgQ(%uVmxivw6%Z_?=OK)6-8R1U;bJj|F1_ z+*W{m6JhOeb@*ig54;YBKbJJuK#?ONoLbjrC1;ePKJCIfWRCc)mncRQZZwb&6gTNt z5`Qbl_)*DTUf-%BUPD?sX1%iC`gUsI5cZyb>e(%GFsw)D)5dur`2(v3uLeSPL&QUoKF7GyGLm4 zRX?$+n<%m38U}=3N+S`mfgKl(;xTF!E4OsLg61_(bvjJ59hvi0`{&2$LLA4#<5G1_ zEmYd~9~`Q4y(fuKZ*Mg>LIPy(nP1Cq#HhzR{7(eq(r$VyND+riAq~LdRlfEu&ott} zB)~q1NgP6?+`XG3_WV^=Yng<}rP_BaWOcIQK_gjO)dhH}ebapWTL_FoSG;y+Ob;{< z-8%tV4EpRbjic3Q)3RZ&1r5jVx1n4UNprFYHPzb5FZXe9PLgg;%M5)N^GP0IT6{8q znJ8KYsTZl<`d=gyD7M`JnewhPI!&aOkPC9s=+L=-E}!;B0ASYFEGag~vaql8ey9(G2;8|b5-*c-*tfEtpx zY&clZcaBN;*uyM*VK+urdm=_lHK~PC&2ACQ3n0^nl4B{mIz@wC9%vACoY0P_qEmyc z8;8j^T~na^Iu+q}jicBY(E=LLFFX}SieuQ+^C}||4do;%c&`o_%4udQM{}PIfIgFj z+HNOBLX{K5Hva$>RnK$#Jlx2+`xz|jK0KO-j;0w$b@IdJ5QhqwPpZE9&Bg*BcFT2* zN*S9DOUFb{Q*<}|N-|A?Gg3R|i2Xw(2qaDZh}_fy5HVm6026(pN$P@WJMHZQO1eGbzwb$7dcg zIq*^0sGnkxzpiqw*CFJax@v}xJlEZvTaU{LB<}MF(ql8m_kaqnYd=um5BXJbF^_Hd zD}Cq#&0=*dcP-9mMP4r$UzlB?rmjMsW)(6XFLZZlyg)o1r@<3=jf}RZqB6(2n|%zb zZ6W!z_=8d0c*jrllzoQJqSe?>M_Fgy2YeHFkUA_dU4BrGnK@>K`?2vu@Y)(W@me8v zu*SKpBvJ*l#oCogzZ45vQFYFjRd2wAT6?|!07WEK2Nx7h^~l+W3tNEBt0;HFV{8iX z_E^QuxhDp(w|z%s(3x`R@4csVPH}=8Cq3INgGSCP@iB%G<^C3JWS=$pzG=k0?acB( zxy{L%N-^E|K+5)o1MeMV+56BZhRZZ-&w`J`3&P=%M%>nAGF=xcteslh{8cM4n5FlX zQfhbe3qYDnj|4;eQ^x-QBxXo=pvetN3ssSL^D%Yo)5F0Rw|KZYxyMCMycWBMAF6R< zae4ZokGie8e^69#way#XRNsMv9ma5h6QXg!7@5REaRA-As-B9p-yy0c%zFcx-9)1d zrZGRFE^yG5>=5YdWsp{v01=+-U>yQ6O~VhFK;d@tBKI1OMI5MLA0-?m5=|P`o(@7H z;ary@C8ofv>YdNi#ZThIt^}!ulF{akn@9YqzJF%T0uhlmQ#_;p zh#qLlGcaAtlAP3dU49l(!(q!JuI5L1Z~C_REX_7M*K3Gtj5;_cchq={$6c{fk@j7u zMcP@}nhv|!@B`+mV-XE~jm?S#IMi`*Co+h8oX^70`KE?_+y?0bhRz7_M|51E(Aby2 z%y@2HmnU4f zVJVT?8C`+KEcVL&Yrbe4a76If>@4ovjoe@+#sFCz6nJk4_=A28NBD040J;{8w~;8^ zgFRCSJky&b<*k+Reb5}A{{Rcy#83?t0MjxGaXp{g1n7NRze(Zhv)yR(KjCEC0mvf+XXKqXl-At%qW(wiUom1E#lav*MtlramDPmPlV2*fj) zTvkHJSZ@xiJxY@9&io(Hz0emBuYmjtHgP_gBMogs0SqpTqSC0ko3l849Z((HTgH4V zR5xibF@yCdLl_{1;C05$2u%QT*nBN(BlVx~j$$l;aA>ry&uP%Ys$&$18FE!7X`Ay} zrkT+gHQBt)VK)UKlN*!)8 zZufhLpUM#t-a0SM5GCdLqSJmhUD%+<<_r&1<_u0JIr$(5Igd10=W(BsZ7}D$Z*U=Y zh90F=r|OGwK3maX@eM+6##3gg6CWUqxyDKmY3g!KI5p+7*)oC7Pnyh>H)@##X{;mS zsj3qsqh}Ix*P?N|@u{irgXV(5E? zu{Jgs#L|DZ=v>V4kc42qU%Fw3=0LQsX~g{ylSpp6gxVB?saC|`C#o2Ofd)Bhn=_e> zIFu#h+d26JA+ro`0K62Ngb$kL?^G_)AsbFFHzRtiX?L0jmeQi)0+1XKD7BzjlkId| zE}x4Fcdcne*lZF?dl=?HYe|xCz%DXT%%-uzyD;aH>$jtT)D#_Of3UkF$&Ab4QgNi; z7bN!wV6kWgu&TDLq=90DDBd24_*}P3kBogn_Dpr=p2mo~iA<+evvBr<=u^A65CaSV zIV@$8{7%nUL5~B%=XjgYp(DC!cg<(9uEVRM!>Xe2&N1RDn~ogdWT`%SC;lVqk~l`{ z7n`P>^T}2&Er3IEvSE7x!p8#<$r9R!$B8ryas% zgTs#FZ)9S@169Bd7F$Crqv5oyar2;G26{t#=vh{qH+Y0JK-elKeDw689S z@N`0FB8A-CGu2KZQl2l05f)R5&GHBWHEGELz}@E$**HI3?y{8Qhc!v2oMFOv52|qd zd$kBInLy&Gz#MpgxY@xoc9lz?)PQqqZV+^wiWdew73Q7xX(!N1L#umk*}&dQM%Al_v|zW3-8 zfeobAz|h6eBrr>z%`nST&L^9yET&1qOL+6-sYDDk$GqdgJTq_ynac|8edx?(sh*Bv!ClpAv9Z$W z?1lgx&Do!lp9w8lf{HLX?5aE3l`#f>n_UdEQ;nbycx)Od-Dxo>65?zEJ@0B7dkjBR z`E^ZtBJ2}nd_9@)dFZ2d_e7#mD1-M=<`$%sZGgwDFKLB?XN$UYABN1hD<fT*3r{pRW=}w}#4!2nWwG3$?K9@G0TZ;g&NorP z3ALHTpvwadizwZcJW`C5{pTOT8OJoiOmfW`JW!S4Y0F!yCp33#kv}cI5xnj|B1>@? zJduuTA+Hq>(0|xoMF!OOL++b~4*NKB9JaPiM#wyLpMuM4-+)qUoXNH`%V3uO0H$kR z-ZHyE!7-Lcf}~$lS34&3RbLzr%~p5>-s-6rlB++CY7OeoR~t&qILf-!#aZpllkr|A zLuEL-8OxG+7;=<+_C3L|fwAuu+A2mN?G)&iShU2-bEb18KPsY1%CW#l2L+I@*-&L* zE>sfoPsGYF`VJ$>8Hz20KMs>m_mvU=%VwnlODWW3jYNFYUkzw%9xl4gtZpb#A;9@7 zGM4#`12RwEtzGq9rLvUBB-~|*3=JA45L*X!>A}57_@dGZXp$hQ566UGMDX?xVa*V` zFoW*@0F=hVXtz&-A(g3sU=26~;$M*_>hSCz8BgyS7HGS?)=ceRHp2F)xBiH+4uJS6Qavcwo5t zrZ^L3+eLUH08Ihq&vEC3rh!uwKA8E0BX^IB%7tRZ)>JI$!vJvFWGX$Ko|Q@YX= z7Mn*-DXn2|W_c0B+v=^6MG}0~>RiFesL3ZZEZQw-UOJa|?X)oU5{)21{!>#J_RoZF zkAk4qvLIl2G3vWNcZ+wuH+NN9r{5jtyNF%7i@3-wPN!G&xGEp@1w`A)Mj`BoU7TE8 z$oi%`#dk%N?za!s1Ka-P-zbB;f8-o&80zqbSe&wH%36TFT|#A%T&x% z9k}jJ;-2lz=D&szrwE842o1MhY0%iRH{_ZKaEArF+*q6Cp9BZQ4tx_@NqD}wFKVl4{Z_CL+BdM7<|9Rt_YciU01`;z zQ+Z5TQzPyH8jJHA0y!al2t)2#?dVOxMTX7V>E6i^_)h7X z_JAtjTH2w7rO>526*u^s*MdFeCkubRDVxCGmm-<>&Z_I{Z4@#y#$k4lIws>71aX8Q zTFc%f`6HPrz0hl7efU;^4k+BCX8hB)MdO;P+HN*(JlqEbl}eQz{h=8WVQ9VKDkSU~ zst>W8xF)unKRJu2d(Nj$h?gE{-wJN?%mc`509$t!y%D+Du4u+97~ry-blI-JGtD+C z2=qH5`z;eEs*Ewa#BxMAza=53#UAdd&NTF!9*ZQ>dyGWKy6wKxfb~K*u>SyUmU;!5 zqfIb9uNX^yy;bTzSkpT!=I3ov*E`w6Dcj8Z%UH%DKBzmsrf}7p$T<~#hy0)J0`>G6U8Tp_i4HsRV_#N%8(pcv(>rc?|4^*vg6^g_nDZ^1l+#1v27cBn3B{IimKYKEL_EzUI%vtVb*8HdlPS7YhH z2O?U$1IEo;{uF17dMw3J9%RmYs!zhuM4M)1HZTqDb7bJ=mdm2$hngS}O}{O6ds5YtD-r0tFQ&&gkSaH8 zJ*$C>tlGx5KU0vABFlNMJES8uBY69~f-|zrV;Lm!3oF7fNw8cHk`o*pGb2>*2^Qvb zM*jetbOiSDXt_R_D0lnjsgFd!fdnD0XcrVR$_lqtPM$6}0$?pUfS*;Cu<-z^%YW2& zu69~Nq~~HJ;?LxSV(jD*)P(0Xp~o!MQs$adCsb-Vl>l_NGi%uvhnk)P!4LW5nq~p= zLCzSikBC4$QHK^`2SrE#(gAiH(|jWfxh^Z*`JtH~Bw@8dY5FYmm8N{tw~VsdlCvtz z@2$=TYu_hh-GE%SY@8`l8M*dMhBr6+PUq8MM1AXI9N@KkBV(|3ag^rsz#te6IcORL z;^I&Hlt)dGhYb253@>pu)eRLu+ z#;Bg{!+8BwHipmQe#!E9ruw1)5xCwwP;5c7IHw9@At}*`((RvxTAoKNO|egOlZc7S z3c}j{-8~$5gu(_ZPZZx~{F6y#F^SR3Md6l!h2*BrHV~;2BNiP1=B(U+U&Lq*{aXg( zP1VUT7qS2zO0Rc?gtT=sh8j$S+(5PbGdDDv|}x}fnoen?y< zv_~w}Fk0poIlPYqTn7XRRt+V`mK&}6$Z@haak$`x+ev`+gao)42)O&M7jrpZMBHtO z#MD3z7$f#vQwb+P6XLaI;cEf79Mw0#wkWpC=_Oo~9%Ww+ z@1>EJv>87Icyah9v%NG$5cu|CTBBR{j^kwPBsRyKpm5d&-5sb$V6#}k@YrIL-G56I z+dmV0p&tkzEuPC{^(yg7W0oN+SZMW^7UCU}Q8^CfQ1

|f4#Wlc+cpWAT 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 j-_~TbQ&i#Xp)& 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

x88Cz|)xMyvg0 z373l~662D2mUeR-z|m!i3rHmhZ-t|f*~Bcv$ST;3lsGL(`#e;Cu#R-`YyjZ1XIB(t z+>(idpOUZ!;bn@&Ww2$lRtEn7(!KSQnhMHxM7pCuN5R>^_yjL2U#k3{_iTN&ZilLh zRqzLQdOq57O>gx+||UBvo=*Ok3>%9BO^(&5mk=D@HHOsTK35J zY;?B(*d*w;oZQ`{rx_fPOd`8mVm+&Bj_wz;5E&Ht%n_kaZ7%Y_ag9@&?qNF#BgC-0 ziiO7xvZ-OFbH|rehOl#T36TtT=(>}K5guu#N*O$~j*9Il^eE$lBNDzCp0cxP2h9T& zskHg^Qt_8WijYj9F$KgNn^baEA}w0xNq=(DMs1nrVG1hV2=H#3s3+ zzV>Rxr48q@{%8z1h-o*>D{7ifCcCYgkIrv1D3&xm0j15s>sxIdZTezx8M_j6scd4b;wzQns655 zYHaZ2R;7D18V{Dn!5-A+owFtb1pCB9ZPB<7^r{84#0}%0s%Wh95&@Rmiga)dupKs7 z;|uWzh;dP6a&k`l(@~8lhefV8ly}}|qIeNS&ut0baD+o?w8JSj&1`+Fr|dVr?DYoo z579}a$6bc+XI@`tFu_!Ez;E_={gY!+v+B6V!5wo}eus+8EXk9$aZv}M2%$fE)9H0G z<37J&NqrZz1^g-v4;&tDSGy}QNEzuVzy#B&mmUZMN3?0R;{{Sgz zM=T(DpYWnPu}-Hh2~?%7%p;o!**mnDJ|AcK)d#`5EPvNq`w)j}&)Il=ldBKxoE+yn zyr@Q5SSiBlSU~*Q zBW|N(gwv;({{VcJq!S6ChdNuxpl4l~ih>q>$oT|E-c&C2SG;;7nP zaK%NraS6Dpdo@LzI;*v;1RgMCqZph5$+d_5I|E(T>XWIGK*fsF=!~}mo&ByxH%Kf0 z0Fdp2CN;i+32(_f5mAA_jt6Cog=c)%h*<(Vy5gtwxIzT42%yMSEx_C&1G57|3R>p7 ze+sPU&l8BM4SCA;Ov6a_X1{c0ctjECpSvS=bwk5;#g#)d;Buvlp$d>7Uc?m*Yih)6 z=$VbBtsvxb%~U^f{{U{@B*WK40jWe-@=a_LGso39MBTpWPu^psbXO+jInwGPM_m_b zWlr**BZoE~1vW6TqNM8WO3W_K-q6Rg?LP8m1~`%%!;-@xm^~F%y|{zz?8wO*#vesO zYCLY#8IDShFjxrWkKKlsKcGy++OCkoeoCKm(oXDUum-)LTx9R^skly7S-e3fL&-kV zC5G)jN&U}hbFWS2Sn1$ZBFp(_#bu)Lz2I~jltW9Kt&tmnqtO6ePjrK64!Xn-1mXc0 znqw(MIyfl%wim%bHQ?u*Cr$JEuN0ZsU^Ii*k~4|8g2>lr?5b|#xz$vu1;AvfSX092 z;h+c{9?EHfns_CL35%FKRI$1=ZL>g^6QESwFf$Z3*k_8XUmqxN3Z-|k$CMRBQ^4>@ znXyhWIem>B5x8<2#N-!@_}}ykF8zbxNzC{D!ZG6_GJu779) z1MIr`@NnQ$V2{P4!1DVJj`5Fa^9bhem;Q!m#`5R;ASIUvVBYpm?!A_V`fbZm$vSC~ zEi2h$&;2DCwtrw*tde*jciu{w00`)WW5B`ZGqb`04Ic43KNgd_nx2`V)2cbRBh@?Z z>e4ueHr)XXhzHu>u1dgZDsO?;W^en?*$_jVOONP6?V;M-f2pN%D0nEm zA0iY80^LF^r!$#=nqZqdL(ypH^tR4BI3wZh2mYpy*>O#~>s5I_rrRv|5_BoI#{s^^JSlDsp8y# zZ~a?1gd+=LK07nQae1(vn%iI`-~{=nK$$*jPx@+wue4eBmEpqa=me~Zw?VMQ?69-8 z(>du_52!~4R#}dB{iQbk6Uj`PB)pwU$m*JLRCjpe%ODYlH_)&CE6<|v(rlD$LEG-1 zk`au0!s5NUn3wYo!?YtO78u~OrDNug{$i%{a~w*)W}nk7G#Xq-;09 z==4Onu>>lC)Z-R+PbZN!Oc3UpR`ozY%T(rv6P`mP^MnbBmZ}2W3Sk0zs=0fmL2xo0 zb5+YK;r52_!8xuKnPM`;+l1#1AvQs#eh8Mkfyq?gsMy01k``$ExM=m*EZRs%|FEQ<8;Mz*KZMc3RWLI-}h-!(j=J9UYRBP4tULB@v3w;8%p?RabX@ z>L8K>PeQX%F*M23h*X^2CM*&?UBDsSUmy= z%pEp+;a>}g{{Ya;g;0JHB$m6rL%c5+AV%K>QNvpV$Uomq95Q8V<}kYj}NAr`z0S8lP@XUxKLaTo7j zK&lCOmw9A}-t6FdCsGWNJK&#!8_(TmCE;Ty&DSb7Kk(Y*^(D<&RNN?k7C*R^o-(zv zxWzaQR!#fI;&En8Ss~>CL2!lcmlwO4DX?nqm2`<`psyMgU`tQ>sAEgTP`j5vd% zs&$(8&p=h0#%HLeQ_o!;YBa8I7wv;Tg1Ca&T0D7^J&dPwvllZ^bt{m_g&s8@Z7la;zrn zGn(Rzyt?tGii2vflNhtN1ozUY2slm0k`f(r3F2}M*Ub!^SMdJ;c~a2&f=8NVtN~8@ z$~3@p95jU1xvw)BK}^Ik%bUa|;g;_N$9IrMR_c!Lb_n!Mrsfmqnp*lzl0RkPAv-PL z5LGa}#~3|yLJBwHt5jVhCuguuN$55Kviw;k(G}LjBRdkE(lc#soBODc)l51!jntSkW6+c zm(HcvO>ex=P5P#X;%v3&F>I`k^j+39$oVf((SqycX00iP!9pJ_CtP zcXyE{<=oQ=c{XcApvt|Zi%|Ai&FY~fmlm)OcR?8}@{R*$t|f5$SQ=tgALC@6?cW6$Y7X(fXn|*$;bNqoDaKmU zbWU&isQp@mWv*}&$z?c4l8xTX9sR{N9^vpS!AjnGtxBEB3}^RLlBUlG(5$KoH9l+F zy2;M@ESXL2CxkT{rorK&;eEdKCl6R!AtAlC{H2>SYXBeAVQ_c-r2&Lgd(Qy~W!cOf zL!NyH89gZMOqo9z8#Oid~Kd9Uxosj$1J9enl4G-?7MsS+Y z+(g@1;-@I}R!0MBVs10-NXQ^?hVHvk<$y&(JR)sm-R6mXFROWM$coF|fPyPPw_6_*v^ z?qG4Y-9Sbm%Eqlw2a>pC4v-Pfvt*-k{9c3nrpI{3)Evp=(YN z-m4!J(Y;Qk^|429{{Scd0IItlej2a-Q@&d>4g`5Bn8TV6sFY$V3Fy7)Z$hIy_6b*B zHUjD|;mJ2r*ABK?6R_qbGRDpvmO15|D5UDAKAb{umQS3bEm-Gd=^zgZq`)dub6oHx zz(S0)=9!2BCiyK1SF`?mD!H{OlFqu`R&Ms7W`#3Q7&zljWp-}Oac9%L_#n;NfS3d5 zy{NarNvI!ybBiv`h0gIX9Fq%bOx-sNkI_{OA(*dqQbiWKo?H=^#_MJ$imAFp7L$9T z?aYqO>Z@t6;1vYGJQg7GCk$CX3&aN1{{Z=r^UNM$2x!xfGK>&p6;B0H+}o46mEkvQ z+m!z4_&ZNO4NvlzaN_xFcm^#i6t-B`wClj~LI|b=BjR_-7$>v67P++>INQg9y>9kn zQ!5Ql@csLSoPsh4o2Le_ykaF(#e~C1%E(PeRL2|>h&+=++-C{!pN5KK74`Y2TD3CJ zV^X(p(RB3(CE=@HoiC|CNPDBnc&6$Mam%8!>CDpGCahnQBym4tU>nAxf{5`qgcSzb z9iC-WZ{E*&#>yE;XHoV76v+_!?(rYfz$14UmB0p*sXKklU);UX_Ou`R7Vi5y@A;p) z6Fc6{{{S5`^BmP&eUlX9)JBe0#NvL%zQdq`h>{5lkXmfXpan}9(1Q{7f0`NO2l-9} z@=(#hY!c%DF9i?YKE&+Q>((DHg=U;J9I zjq7%iPsj;U4ao#&DlR}iYl>^#Rq=?825FCLs&;CO0Ep6lhyvQLY)`W6r3)A^AO<|~cnOl{ z_M64*0%4Z&$fo!(umkyuaDz$L(RLa*1@SUkOL&8-?ETv~V#KM_cyquZ?U8hF(_{A< zGHXJ0+gaD+3tF%Frxv(s3@^scC&$y8kq$=XpFM`{acb4p`%!lU+)nSXM;t?CN5=_7 z5DdW+E3&ux4isy&;?@tzc8YG^$e4GGmqBmp9KC9U{yqB~H^$33i{LuC#9qMl)d5set zf$gdT#p3$x8$4g?jC6$JFj6>mxw6mwOReOA1ZZ|ZZEnv^$M@I)t{gyixHf2xn+XOi zL0OH;y+)`Qa8Qo0tkL3fs`j{J;_F6=rXcC#tF; zrv_QVsf?abtFsYxrbW+^s>1qjjO=Ar?BK_PyuHwZTZy{w1x=?pWZqM7yBUz3X_(Pb z&oRXLpj%OfVcM?fxK+j>+RUmiYmS9gVB~EJvpSKAIOR^H8z##Ki+VU76cs#C!`xTQVWL4!Fe!R^cgn^$ZDk+SV9t3pl-%xuWyvuJ{v zA4&cuJ0*(|z7KcJ5J25$9-AX0qMSkf=UB4J0?w2=KTr}-)>sMziwXlDb_Yc;bH$l0wKX?SWpHP@x?9v%e5{6{i zXv5}EB=8EWMt^qvkjeN(Yh9yWVS{{SCiKE>{| zYR?h$8!~8}z7gScZ#VoVIn%%X+?%8aJpBZ#Srm0wZ)ESxKep)F2D}uPvsoF5a!nmoH&HZ6j03_Yw}WDnT)khpPGUMD-MVxwJ9 zh&?`OwQ1Cx*>Ukz@e1=;&5l-7-4kO46D_JF9-m|0XU-H_%^cOqH$%c&K;|qLvg7el z4FUfEy7z>ld_j1sKK%kG9ZbPS&Z_TOParn78r1lKX@m!7s@pcLF}Uub0QyHNjJVh? z!v6p~MSl`pe5{ULv;P39CMNt*CAt%3nC#67*g-ZYMCV{*iw8ixRi?{5ufi*MPX`51 zG1M_f&34Yq(;faPqq}G)9Pr?UoAKM5n*n7P8X($b(FASJ({x)V(mRbcih#FiY+4^MN_AH7zhD50iwOGo4Q{h{>jb--V#q9 zXu7Q`L}SD!=Bc&;9uTTouQ+T1adVg^>XTkH(NbduC;Ve? zso5(dpPJ&0g%;2I^Fnb`cs`3Dor$k&ZNl?qP_e>fJKE@&KrSRB0JsTW7QBJvQIZBp z*bE%~Qw=CHj2lloAr8b`IfAZ-ih!Q2vN2iEz{dNdIa#7$3r^1f1iCsd%JDAVIF3uf zL8L;_9oJOC0M6G9zNfBJ`;fon!~S*+VkyZg+c7_`_F z$fMnl{{V#A!QDa6f2So@ zljNTZvbQyn?Rd=c3otBsD;Hhme+rvjbFE8~w1oJm_mc;PYFjNLd61o|ZZ2GCC>d^JPQI5Ldy;{NOXR&J_KpiM4wj0EY!dc+$!{{Z7?BXAEy?b0E= zRN|%D1Mlv*N6lnu7Zdu7p@oxw&DBQjz&fM+Clm5aFyP_;0NPRembAUK5AeUi;F?;= z#PBogikdiF6!7O#bb<2t#_C;GReq(|w&vd{VI&dtIE}!>1&09{`;tK^VmO zZ5g;B%?n*y{{Sux`9k3T0Q*Y69;aoD=7}#XX@SgKr`3te2>Hs#j~?{IC*rH!jBo4i zterCLP%zskcw9w3F~g!^%EduFp0IRlfaZ;=w-aGg1kKzNi5%>UZOu4uuxF5Iz0^|d z!di5h)S-@b7m>wwd^Nzf2y=SGuF^x_RMZ8`BbJh$DzM~R^-Ybj9o{ePDjERYF35gm zMaSNGF^N!XdK2|jT0K)8iPWk80NR0boIdc*Wgigw&fjIE>_2Bg{{W*n#b%fCY!Eha z17)f@8V@Z{V>?D=g3W*F=A>Nw6}IT$yg)Y*U(cT=iB0hv;n5K4Pw+fse`2qOz^wQo zyyAewk(U8V84@7Pa!q1M+;?{fr0Jn8UBI8l<@Y$3g zU<0#6Gdl#}s2%807%G79daDWb+5F0b*Jfu3A2q9^)qW3We(#mseAl?s_1pZyKeosZ zyPUX`Y~IYn=n(dPXh|jl5ZY9l(Xay3q53CCb5o3rwi_?IsNch{z&h~tU9>|uZ#n+} zq#!`2P%*Qgh<@qBliC_D`C3)W918ZS;zOD8RYX<86TqfnrqWtGti6)&fLfA-fs!%1 zj04W-j{CH?%u@dVDhFty&GG!kjaR&*IrTvav4Q2V&woS*{ME zCxRkR9iG!>7LOyF>wz@Hf1>|0|mUo_i!IY#uVYF zOb>$8N2pp7Hsm&FSGLB$K;ox~+@Z%|8$X!en)lAg!-yX>d8pr-;yzn9gIQvDa4PrS zrTZX38Yk3m93~Y^ZWbq+?M$Uydld{yq+{L}9|Ut~1Ic(C$FuwQR&6U3Rc+_qZICAs z{{UrLsT=O#y_$fb?E~tvCVf^x;G#hp6v0|Wi@aW{f!w?~(>%vC`FVNAMcc0jl&=&t z$LY;9mc_tan9(ML4yqd0+W!Cr>`al7oriJbcqWeSsNEpXsxZNK z9!a?9KE{zcazW3ecbHvc3Yc*c2r7;OMZi&o&Bi>?-ix^Bon&-h?UcE?C!D)gf-Rh8az{2>S#705MU3eulb=Ot|@>n~y|WQHJK8lg3kGkvWf%LPtx4 z=I&C4x7u-W&>=myvk4r8aJVQQc2<-iwV(SuC zWd)ERBb7GW%VFMi->_$rJK7K3c>5z79@Cixx$Mj>J5e9>?C~1pc{Cv~?}@Nq!)_lp zFKSvge?6ZxEsWHEsuz3m>c9Bx#(HS2xv%6fQ)VA0_fDJJV7X1bH)EbhKd?zSVan0} z0EU8prb70+bbrvj;_o6R}9vL5Fi4D zqvVMJe-7vmyQbFtQ8I)bd8YvShZqxb)}g%)ZNDx`?x$6?U(w^}nlLu7zPAaz=VpJ6 zoH@6eel}JC)Q+~j_GC7D`K+7TOGlCb_f-jZMwONneVJbM_F11kh(OS7zB&TvXSzmO zLh%E)`Hf!r+2h@hRs7a`)wlK7d{l_@PQv8FjLH1BKn>B2On@@J|MB8~0Qk&anvf*n`d=Oe}%Mg|CljL?vn2XO8Hy>hn9xL9@ zh~k9u*{O))nZ8W64S*`C!-gR8OaalM&(%M7bVu%q6sdp*_)WA!5b)-x(=s=7zXa?r zp99ayHv_Rn`K~(~4-N!*EQrE-t=Dl0pL$P-7?lt$DscCPW0EyTkW9u4VB=Ly2bphY^P#Lw`HYSB4VVUe(VG8Tj{YLo*S0G+#{7j@CycZhLDT5?o_sZ**ZND_D~ zCap;B!4rX;mCKk~2VH=MzNEDAz^J~V70Y)7g@+UAVt{7H{x)RlfF7Exv6^$s{f(M5 zt|5DLoA3x}EgVs~+3XGo%KR$Lbr(tyT2y5Si4}ox{Xm_A`1$_;N;j!h&+--vH&rhE zf;T4-8#uW71Oz$lr$6--{N{YuvrVJwqwaBw{$w}m&I9>Ew(;6|A2cI+-43b@TVT|p zfA;U8=Q0+(zDM?KGsPdfqd~8KI2KK_D?0}^sNHTN2j<;g)q8m_D7rMYHid79J^vEGDrhCS939{HNs1LN>1qh<<(&y70BSnI=`yI_Tf9O%gG446L)I3mX+@P zQPqDSe9F9TJA}6+?aH&3K%?MnxN!wp#oN5W7ArI&Dn-)+jV{sN4v$a)2Ti`q9DWG3 zuk4%b;#y3V7Z@rtjk8W74)YF2kqCEB#zCe{r;$O;aO1Z?uF%HQas0r2k%iRalfcm% z@lBV9Tpan#{ftzlrhPokBYeZO|@}G!n3j72n zcgk;ktpmHe2FAVWi$ihx1n>0r?y;;_MbMj`-=Cym< zEkcmRQ2h|YNuCBQo*J?Z3x?CFKELt*0PY6=0O?Q9Y;My(AF@O^Z#3LHs;Jr(3H%qE zc@;_=GsUQ0*$(wiKZZkpx)!Ay35}NrMO+*~&lHAi20m%HyEtSQob94A{NsQZLklmy za#dJcQ)^IKZh7qOIz$ngLvno)0-(|hpXl~TvKtl7o)nwZA*bnFgY{+(oN0=y%@3K;0FFiF`N;mFC z&&~e;X-C9rIFC3$&wSbSP9eCO2?Ah1Sj9J1X$By@qpAU|r%MAz2wYaA?fhB|6y43b zD9v%%{N_jXXYxkuswb%gqOGDbO)*KmV>N#6nLc@x+1_ltoQ7pIG!bFR9~Kslvnq?v zMv|!(+W4*WO)W0%Q^wd-`K{Mw#rk;U20))So7V&ia9M+roG%o!v>WeUnIgO8!V$u!^nu6&8Gt*%;hvG6hi5 z2=-1zHvpSj>Pvf&VEfRrtxe7I&R4z`jyos78|qhu?!e*6*1B;4m6_(*vW#;D*-uqc z&t$ee_S_+0cWVz5dhrN{(szQRaywo{I>32283k#xL9lLCxI7#Jwh114a!e-3~b+Zg<=yA3)=i$ZfBNDpT_`^&|r zmD3M-OLnjBnn=TdyM`rEuB1$p7add_Lm1`J5aB4u;I25JM|w>E08Yn{V?|CM@3UU3 zw>7PadmItRN#qM?M^qkL1~6t16ejAIa@ezY_1RITqfMhhlq#4<6xUU$5r~_76+%dE z7J_Ugeq8*~mPd&DSauAG_d?Jkd{Tr!Ya^+;BpR=<;u^BVBqO@|kAJ0<5OW6T9?tla zt9=$1QUi73{zY2(s~EX9Rw5IaMqt1>tx<|J>z6x;AP5Puj0b_q-4u`YCwg?l!*B9| z9(xamE-G`yOz?O4O)}RD^+q@iRYL`s{EkCwNx{8jd_X(YEY7D+Y2R?q5gyyPtrQX) z@+*Sn8-3CH{k<*!0HdG!Y*@A2GahS>&*Ho^bZyVrFq!>FG-WfqV6!TFZ2tfXrZ$F% zcNG{(-ubNzY|RW!B$7G_Oy6@a9)Wnh-_-2W%<({gaXC?z;X3Q2D&@5s_}!YQ2M#j_ z3KBh`#3{l_EkOO@Z6JOnKksP?G|#uKwv=R=Y?;yq+txOwICI!zns)6$}l z2opl3c4Ka=GKO8V8+`4txW4k4XyzqGH@kT8C1nBg@LtPUJ{v!p8~xT=GC%4iX;pyv zBR_b43GR1*vTq9VO_p+^NxW^1>R9LWY%?p0Xf1DOrhj$d?t`1p7OAs>o1Okvc!ihY zQ){X}$y$c{V~vsVp7N;~Uch`7O@I^2MG!Lg*J-SOtf+AzJMNxD6O&)X2yi!EH$H!) zEQ5y5DZn(@&UQ@)RA;h}3x=-Z?r3<6TlGLiH@JGn!v&3V=k(oP+9P0fTDmJUBHGV{Q2Ou>Mh49Wn=EnPmhT@blHB^Y~$ zVe@LrWpGAag;ixY8e$~Grek3^NsAEWZt3!WrzQBE*mHiWWz-B0%-f=S(coEMO7F>4Eer54@!8f#ivOA8EeT zKD#tb-Kfy0u!+FR6%orCO36eT*_7V9g6o`)QwC*{{XtoEb8`YD8jS#XY7r^ zSv8=K>Rqw53xJlYPIFtC0(L*e;%`LUZDVD@LyLvm!FF=W)MR4!wgoUj9;o3BF@#x8 zcD8G9*6_1ZIKmd*o+inREC(0ny{x(+AqA{QRU~(UWJYSJ?;TuXIw6a?Z?k;UNVqSXK7F#ELieu_fB;jc4jwXZ~r`=q9SY;7o@c{hp{o~Uk%Pd$X%m^&N=2pFe{Hdn}l%4KHb0UMH>1%!2RUdU@#z499( zd*+`+asV&pV)VMpAIDT@d8;qoPHQPUOktq&@Qs}omqJ`#ehp3pp2{0<%A5F7Beq0M_sNb9g- zd)pv&P7b`(euVPm7WF51+nNIlsuDbvH$z?bqlh`G&8tY=r(5V#PVF0YaRFHDb1@qh zP5P#9hWCs=RsR6RdIUA2)!LP1;&A|Zqc{kr-z3X2kRT4XR%-)Aw8Dw+S{wfWaye(@ zhIJkDKus>u1ECuq_l(}`=jO!1!fLUHsFMk^Q}Ev$bonet0fHH=lLJFu9KqFQAF9IYpS80}Wcq!9B=S#(V{A3IDECYbnPy~>H%%3U*t^6!6vAkhvwO=`?=NY= zp;{OK`E1~{CQq97XodHUn+IMxC$uuoaURox{#rYZijC;xKn-|s9Evs#T0Ecw>Qe+f zWjy@Q$;G=YV4aZc40Jb79IU72Hbl5#C!2OV_HgEb5%{!qXEDoT8Y$CbW8N0XBO-QY zb{*Q3%meRegb-kacSM*}x67cvmjoKugCPFuBpmuI`|UDbiNuDPji1hw*J(ON!lQDS zV2(-u0E>u_jJlpp_khu2V0g2I{%WI$YeQymMlV$r8<^ YOjkZY~akY}F!IPGdld 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%0A99K| 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

&ziBddrl1HW#wX|Zt z5b!@B@B0K^wVZL>qSqyM%}3ufwaC(Uc2@weQj8x|Pm3FjZNbto;wsK6p2lw{ev_{t z%DH)8E37C)C^=Q)5ZHyggghhG=L%M?!d$rEPe@ZYJxHv^%Kev5A3oEElW5P8j~y0m z78YwaAO8S>48I`T_(|i-=3Lq%;VC6|!z<5VJi>GYtvCUVqPDs?_eqdgw|Qe(nYZz- z$VRL+ZN1gQPO@8SU%(f-`D-Fu2WkkU#cTbwX(gt6oIs_ zc+^2uza$FR?7*m0-1J9Ub*|$~mxFLcSRhL_EcZ&;!aYrzmVne?`TFEuD%e@#g<+zG z!Yfz{DEk4f2}qb_D@y+WSn!}noX|J78*YQsL8r+$g^CF1DyR(C_O`J52pl{@3n9-f zrjB}-)D0&4S28)ZK;h1jD6G)>L2n85)G$qTDy)Rs0=LOz3GIe*WB1TXVDIu^BwW}~ z>KmYwe{x|B0{rQgiZnh6htY&$*EJt|Rp2pnD7$p-CiQLw3tJpQ1slrU^9%Yd1|t|< zU1*vog@Vx3C^)vI?bk!qhBy%w@>BIepbP|lL2(ot^ihjMsdDuIg8+_>#vQRgfT}5$ zRm~Td$sL1ca`w~err_l_NPVCX`Umks>lmBaY3LGAZ9O3}WZ;*u;{pZUxzmM1^_S|N zYWMYv?x;=q%3^i52GgAL@;DMZHQ&TJTa}w%Bs!_|K&wvh$yF#FxTntOz2$#H_pROAUZ+JBI>aie9?BXjGR()wZnj73Qy+mS~A3 z_$6HNcNKVHDY*Qq^$~mv+XUDIVSIokA~cFoi_B6hN5{oZDZ#+^9^;Cm<#YwysLFJ7 zFwKUohsT%$2CIiTJl`m!NHk10P*&424sY-8{^f)-?qZSTWb$v%sfcdt< z?iPVnmX8)-BEdW)ZBX}FeoE$b*1}YClUMO6(;@=QHHF*!n_nD9C>}_wh3+`7e-mvC zMLa+*ZciC%`AYnXkdJl6%6E6uhqJqlPU5V&cOq}t=*Mz#DRTE{#;F)=Y+Z7#f#4+u z)V)fFSdzZ?UkrKVbPiOzcqfpK*0&yr-?mCP6lGVdV-l*wH1cJl z_*JKdy~U-jQ5x0c!Ah?}yN$GUtz_!>$0@fi#bI@1@F2*dt7jT)n*{t0><}NQxJ`7 zE)(QoVW-gR-Kz$?qkDVklP{M;2g40KUKQIK3c0ldp?hj$uwYDEdQ*@60(+5W2B__N z7QG^qO(}s(>#jSN=}>PAP2~P`G-b$vCtYq#mhEZ^fzzPEuEl z7%Q|wGq!Eocj5JLAd8Ni7|rXzHUi{sfnc-5vM#*b%^kHaxk{tc*bQrun@`=Q4AkxLfrkb%5xHV)xm@llMVBJ&zziuHPGP$0EagkUzK$@{`CW`p@ zfLZDIH*~>ww-$+GNq*I09P*(WQnz}qSpX>39Irt*R>Y1anO?H}19ux)k@@z;)(Xs7 zJHKNc_vYKJQv5LD^?~LVgnm3_oC*{_reCV)VpaRNXaRmG=zhbSfO2ZYA1=zMrKbC+ zM}(AB5{<^w(5`-=DG>6(BYX{|nmUh31(S^sCfS7>WN8yfu3ZT3q8(q9#oYz5OGr^d z#W~dsDSFI7Dif{}iiy>6D^9P^<&Gdmu6<5_u(7|n9}M%|Fl~7VBq>m-L&_QhUk+6O zpi$_({{Yys5M#3<0rLDX9yvcqB)xP%`CB`f4eNgt0Y+eWAtutPtHo{H`?d=Xq$6}oJ)3-;W+ z6s01SwBHAmHPOZ3LU0rbXg`+^9qw>ZTTwXJsgjFChw-ja)Y+h#T4oJiX6^G8i=wyr z5*?`H_B}y>A3k8Yf2=npG}%`hYt#emQDW*Q17mOEp-@ZBm&>-S! z*aY~r0)ndhwh%m%2JZ_RKAPh+ zC2p5s^$Y0q`Uq^}_t2G4VibYJ+O)0!XudgA8z7tGgsxV4@LMihv?D z96K%_`wC9JE-or!eEWdl%oM7y%^L_?(_0%((rJXKqNuehFt7^wVNw!WT7zvgK1Vkk zro=qfU-kt+ep}!2zT%Tc*F;hFNb* z8yJ_V`=kp3?aCeOFTOeYhdFrZ&zSh3a_!<(O2L#IT)eUPj<|}padzyXrN<{T2vwHd zYOBO$Iwh)k-Ssr4Tc(6APme!<8GpL+#^qk*9n?a(mqpP411sO$UEbdm2&fLxqnbg^ z(b`#1&;YwuF^d$Mq~yMlLCU0n9!+roEz?Wn=plu|$U^mm23ojJ#(R_(R{VvkWRu?o zp(?bi-zy7>?qgLG6jpb8{!TX*gzX>uQG68+zSG{VimIW3@TVfRr992)n$@{}iFEk6 zl$&wrw+uE2*UVEO+>ieNN=sZXxK*5Q<`TAA<(CW|QlVBh6k}1a(2lX88! zAgc!VhzOQjmF`s3gVDM_a=`(5f?Qd}wjwp_Y9v1ruvov4OWMG109=d8#F^g3o$Mm0 zjSFmN04zJ&gHwQFG4Tkgy0=iuN^9elm(tn>i%XuUx137x8}o_TVVT6?FaSIA4Yn#e zG_{qbQ@ZT{{W|}kuu~T^<^|1 z`22<8oXwQ206yDgZHBdGRr3-`T}ydK6KBDA=2lSU{X=83Y0ZRnpYE0hnYZO~qx~5| zz&p_d9{@gLU{PDFtvxc)Agh!?Rm7(a{Sa{iY|~=YwBSPc_YmS#H5}V0Ta|wo z5E#F&%y%vWdgqmicH?OQ4W+1iQ3wRLw-NA1m^O+u1moOww-r+85pv^mc&u9f(f0fw z0AfCge&Su=)kL&BCEhKubO~2C=bK{td?;1Rxy_x(ZH~%0n`rd ze2CSlyi)HqSrgkAAhz17G{@0t(#9QCi`6RxyAB&@D;<|FNmzXuaq(<77;cB{2uw!p ztLCDdJI{t8lsk5ePGoFngiC*M)G1%6D*#An!-|O7d#PBYt>G@H*g%QWhVm=S1mHBl zVkk=8gTRE>fZWS!0K~0DJ41m4@kBV;>L*JUiD|9}Z}OuO3bKL02t5y6KoT;XsHFQN z_@obw5a*l9@#ZGjqfI74Kxe}Yf^R6E#jt!LX=pw5?5trV9czp^X z2ab_O1YdwIj|4B8tWjtA71GgCmNzU0ri*=~t)sYW=kTe22X*+y8h!IM=xRO6h^S~V zA20%By=oj{Tet^ERm&^OLih+a)tz@I)GS4QcqOrH*Mbm8 z+A^O#2c-z&P`$Ao5#IM38uqohWs{48yYk(_xHnUS=DjfN>f(9{p6(Xt zhv0J_FXG#8{fN$WZ_e`?I9E?1zu0`$Y@w^-x$!8VtYW0@phPWR{b?-J1Y6_OO?U`& zOCU>GdYnw>HqIQQwN_H}^j@}EE}|NZwmPiR7gGflVMtZ7!er&-lx|;el0t=hXpA>$ z=$IOnfptL}wB)FBTP~5nH4`z~_2_$r-v=i-=4X1(T+n5^<4o8Df-$H0QPtv7)}WN~ z*2UfS7lg_^tTP`ptq7#!C4F5i@XjV^t1q%NLUJ{P^DKIyPV1=jls&`u6@lX6s$Wph zC=e_b1;)JEh5!o|Dn?(+GghdHDY43bNDR9{zT$>6k`jc8x^X2))2dleEJY*intb85 z^>_oDy@mv9U>49(ZAC)3rwEmCx$(o$!9-GW?RWnGnMQmVrPI$imeERyb)0ncafnvo z#{>m`&vN69?`4768BQOoTb0D3ySp1$xkqC2*EHj2(R>W9o!>eiYGt8=373Z6j z5h+fbN>$$amDfh_^sD~>Qr!9R0G)`w1g-`%Wzo5+PeS+atYFsSQCfKzjw59$9}UZ5 zThAT7*wck?4jEualo+*}_zQg7FW5|UH03y4HHM(`p`Bg8=Y}eturR=z!rvw{W2^#A zq1WOVFVS7Cv7n$l3;c}+F9fsXj@yO8B1w+G8OpeiiB~-R;*ik>%K25ih|AO4bQnM{ z7FKqHs-#{FTYF;LRY%PeL4l{ANRR;rsuj@%6%yE@qh95gd@Hv!U`!r~W=ga^KtGV& z7gm~I5AEL&G7MWZKtW&@3tUqVGWBL+X{45PV93t4huyOnjIX9>fvW!iVkx@vICxwU zB~WIc=oL&BzO}sqi0QP<;)c@wN4c=N$}5O3pn4=G!aag#9}yc18n+4QV6PGu@o;P(5Z~A1o_eQfaVH zxn#6uvJ(b_AyuaeB1dH%M5q<^mI9*|ZAP|u{{Y6_cGH7J>tkNkE~q?0%xqfkEZTu* z3iSl86fOYp>*VOZn1_1@K%r%@7hrg~z+x2VNpGX2l|t0`m`Q|~PSc7cBim>whZ0;K ztlNC07==LcY_M~u$H>tJq=&g0qTMj``!^hN+s`MYk(aDC56nO;2};FB3e7cmR$(C; z0Z@EdR~5keGSd!!ml5M*E4`{yRzGh;@DvqC6)TUdh02&+oVd|Au$nm$4g-9?7tKLYi-&i=H7-%`!(#-wXKhi^Y#w0b zYWWR~K#c+TH7V%;71n23A0Q3?0AnO+ctvdRo;!?`;U5Dk;$){a8RQ3&?@-Z!X9rTE z;b-$4E%K7!dW!}0h|#?~btCCX%f`^uvlKofy>{OOGW@DBWx#ud{gN0zE!NfDrR9Mw zKv}03G5Q=_u)b@@e{i;1B`Blqtp5Pb z+_=ffQmfxmfR`5fc)&Pn9lL+{OVUj(Ykp23?}&d9S5z(-vMQP_{!{_ljltdG zEjTZlY_dmMqoVMP=6piF0ql&fuBxo(#+5W4SE%Y1y;`pbbs~;*8k!fLcu|Wq-k1Ew z)xbPgsYDa2YVf%OH{l!lTV<3*29d=Xl@S`{0Ew1B@*%`3c8Y>wos!NTSJBFWC~X!L zyi5x@ekLw0&_eO+7EJ~Blp9%LLx6~!E40qpHqQ_)_YZ5a8)H*QgLD!N*(Ns;+?f#)H}BQYO`x^r0Ilyw&&~*+b6IurH=v z^6FdW`S&;g*D4F!`Wgc0_o8h9 zKcuOJi=5m1mglW9K7+jX20{c4B6wl8JXcmz0ws(+bK`?4NiJxDuxrDqS;%;ib45ZZ zKv9L3>wPGG8FD@+XShH`#?BG|(LI2W3@~c*{Ub3J_7g&ZJRZ)l9su#;av%V=s^+6w9S4-KjYIrI_#B%ul&WjGfB7ly z0SmvP7joHl-B0x@W35Qs1cU@%29d9z0u+$t^)hl$SM21|XUxX-pbSt-+g90L(7q4s zoJkSz>1?toHLjuq4IEPi;+hQt5S~g;E?elPB{#NY2yBCj)XB3}4a4IzcnsP0O+AXIJzl1gOjI<)QKps4}goS25x(ofW97W9iAD5wMx`qB98jbc0jl=TF!x(s`KyZ5oFxZ51Sa7?1~8p}(|d2ev|yC|{`IDwM5v$}*ch4Wc9kmzH7-)R}P^;R6A)$UMz7dz>izWxM)xuJu5X@RPx)LW5% zU{>aY2G$d|s#ftCucFv|@MDAGvoTm`StqD@I_)WR_R23*0e`#TB^$-w?&+bI8CBpM zwGqClR_}ggK4)Qf@OcU0``Fvq2ddw_!s`=4sKyOm<0(^)74t1`HdWna9rqDc0Q%j8 ztOhtO9KKmb)>g-09<~l30=~5|VX~S$$-6at<$<-nNmLf~UCo6&v>LvBOLN?!F{iCf zYkE0I(>k%xD(*t;`{(9)FD4_D=EZZYJH^IyRFl^(91HVP&jP#10wEfe`F3$UENs6N8^o>KBB6KWaW3xSt)wM+?j++tf?CLw z*c`Rn8W*P)q8DExthFd;(hZ}HfFGeRM3{vv06nMqH^U|71COhXogF$IJ&;>jAksn( ztV^gdW!P|~zC6b+&S4D{ZyUD(klu?FfVy;HF2z#3m^6qS)-W_sgYarj8u1-!a{W;N zuEQ(JsQmTrfp&`A9IY_Bmm~(MLIQLbPA8w_+qyT2Q-)nbgGOyPKB*Q85ACS(kR72# z;*!PXfH^!*>0mcT0T;wq8`T?ez%%-OC|Fe1s^FYGb2QPS)t@N?*7JD{*qZ)yHkc#f zSp9}g=WGUtnB{~o(xJ~ZC4SrZxSSY=o$KOWgXr6Oxf~r4u_Ejb&fv^PQh-!Y;+lYT z!|a>{a!$$GX(!xIaC)+C_>1D64iJqnKW%pQ^4>t$N}&u?>_0{e1B% z8*5F252kD^9SAIIZ98tlyJe?9S51Z_B%&yL!x-Qhck_!R;t zV138zP;wXKV5JMRJE&}BCl!YNqk{PwU^ozN~)GT^}u>hV?+kKNb(R-)NZ$IFRZ4^op{22;&v${s^Fzoc29C#uk zh!#PD*|2kRp!bPp6cz#!)xXE{a_$vPG$_Lw%Ifxs2m!4Vm?tX2FKH`6SA%lEz8uR# z=t4MZVg|!X+`?CYFrL`RRh4@l*{X#U;T=@LPm7qVNFOyGYuguytRn$@5=2TUpLPft zS5nQyILSx=OWU=1ooa5=xn=--dzyGBIo>|)z_&hHER&hTf>o2dv5~9w)yys$l!@bD zMRyO5TW1BNTJ+vhjC|gNXbTbrDj5Q?oYH_f#mD8v<7ENDxs<&+F~=BX6`MyqY)8q{ z1LGGlKdxPStPDg{Sox|p6Jea66f4cHzRqH}ORK&=F$&lf2z z#EcSuoooK3#g@GD0=S?nbm3($-zrwySt`#>Oz~0Vrg4JXlg|b7P(O9&jHXdAG`#L| zX_u0y$)}Go1G!inO7jyYIc1XkDmE!e)8C|RH2^qzwGannO)XvAy;Xzqk0U__UcKU3 zT-;$Ws!KV|-reaDZuvs8AaI#(0e*)u!6WHcezH*&WMxKy&QwaI(MxNJZ;}^_OBZZu zZUvVqV)@=>uL|fH$^t#`WKWazUZ4iR4D15F@Eo~;-8fT&Gv!A3WN_(jaB%Syli6Uxh0o}DgYrK z%F*SO^imuShs>)&T5Hnbv2i&x;vn-Ba8ioTeKve0G)^nMO=-DMeJC&8DQAh;VRtxZ zD-girg-{&;ZzNmPYC1qF>Ty*!>Mb3)T7re@BhKDqhxEAQdL^3G8z~4xu5Y4CNU8x{ zOX8I*KtOql8(tZ{3R-1|&SFr2pmi%XZ8bjG$i#_>PKg|$qt4Vj1Hy4Fcm@%**(C$! z$ilCb77mIOyG0*~?4|o99IE5@c%rDf2?sA0=x zl_3wT`UaN}Li~kTcuw&_Ud)F;cMq9uu=XAk*u)IWz1t?Smok7S@rDL{OKIk!D#q3Y zsCj|?X=%pT2p(!R;`wm^pwbSNDT|ZVxQn4>mZOVT51GQjPoISAPGrFFvY$| zytj>?{0cxKb>6F>n6<5%@~l@d6FNlHh^YlC_%{eg%R|{GK!O7BK~T%W5_k{`nDw)E z3Q7yM&EuH7Z_^g+v1L%RBNqImN(cjCX4P|d<*$X92WzmPXPI2h9JF0NB?Aiv04)71 z1S(u~1PWDl@Cj6IizrhumH{lI7##syv%zn#i1pJ#_JQ`5v(I^?B@2;KK|0s*{8JO< zgc?~AdAw1|c6)~k>6VprzbOG>KpIy=Od6xaP7%vsRD#JXp+H;*LdP_s0riRziU3L& zgL;+b0q)Msxk6IxBGft;arau{2tp7AgsyhM=@9)cD=@MqEEuxv@`+r7mxy@bj%rKM zw7mhuos@s#xvyxg3$5b~{oLj@8+O6LJCn<>V6+us%Kn5WH5>4KJ10HAl$#kzg8t3d zFv8jGyX}4CvRW$T5ki3THt{dR{FSm<(d*(gL_s~>qj-{FeMXAZKNnPvU4ac7gpTu9 zGq7~W$=@`~ZkqavfEG8pTou0)%&bK>-Mq=#Q-+EhtJEjZ@&d8P2imYQ;PvdiX{s+! zC0L9Ja-38=b?IRNL3^o50}p^;4>_?g;a-Y`;COoxNa5n{GsKX;1)TxyL5zMlwcsUL za(g|c%LI~le0i3&K^&cRhQeY=1o^vp_8`-3Wr^Teu9AGv6?;!`j}-<|3xpfbt`kgJxC zN=3G!+S#$&SK0Hza2o#r5{@Z;A`4oV>^##BCAD@`rG&K`UR#H-ZPe*LWdXY12s(** zRa~+W&H!rw^$useyXOEA{zIL3o+`2gp`6`kA9EvNfod0cf}(jPJ2sCXJl8PeFWkx# zJV(UrUzjf|H2p(wd50-fgk|*#X85nUP8NK}WgEw+8JiH=8x}#GDw|=;XHRp~%Aw?d zpwMXm(G&uVj}$|Wj;!KkGPf%?&2qjNhT0f=$AAN6kU%e|AvXQo1=M>m)!4C!kL48Y zB7_Na56p=w&=jI>)DxnZ-PW-8p`sZ}53o+d0ytf;{pA@k;s}(MUTe2+oTMdUOtX5d zHrTqHvLu_gWy*6EHLXl-52g+9*8t$Vs3-S~^I|13WouP7=EBwZPET?9FDh?=2-bv; z4Pwm;X0OhYvePDC7EiuB$AD^ji$TU3?E?^{EJypWA#=8mYTiJTsXQ0@2~y;7zI#}bJiIbW; zkuks)bgE-6uUCZKd#K|B;lpI26}s;`u(IUj8JWwUnC!#sxgMjUF+)nYi$c;kiOC27 zr2~|B^(&(2$2}yXl7+esS(4QF=TJewep>DPmvka2&x(lK@pziz zEkQ_BxyTVxbVk5h`u4{&6_&0gu6W@trJHqZPndo8h*ovtiF!77LX2aCwwzTmdy*svRF_)xAiusO8^eYdgRl$V=R zh0uR<-sBA6OQmhqPI7QToLnov94b`-3XL_P#ZEU9^AH(Yv%=*BQn(_thtRd*gv8k3 zr~d$8HB)`FQTuvrc(N}UT=w0}t4L72MmDSn)6(F-RF9$40)#KFYXfe-lmdJ#U(*(l zrkr6s+^7^<79h6ZhA(Rnw!T=QY}_}#;2~ALaq$9TvH@}3szGgmh$3)q=oRxji4w1b zq^||BL2bua6&KBMXiW;o?)TxSN~YPs9V?P>s>5}lsB@!1+wL&}$2QB<;kbvjS!E?+ z0^6uKyZarY7wD}@s%OnY7RiR?xrNj;eu~4|BJAAy7Gcfa#z3!9J{zn#1%;}3^xWT6 zPfb7mlad}zk<@tQ^V~Y+KwX}Cig`fws`D}4%r@`I9E1hF4HBWPa73yLc@&buuz9-X zpo&B7?pr5h?DA6OBu1%c7^ufJ8{)131-5s}n(h$2f6xB_LL!A8Zd-PGmA!M%cEzHl z977fl{4$pna|j9X$o)Vag;UtXHBoH70iM7vE*%q)V3u&M`n=imKAXrYT++bp*TJ}TPQUd0h7%7h|?7(HFD~j<-F860=-U0 zA@oMKjP`}&3cb3SW!C-2aRvdw%rwyIbJXg@h>(Qu0XRnalo&3#$BsP9vf`*bL90k3 zkQIL73bzjx)L7_fiHXj{#>NY~@ZJ#Zey2!$%NQEDO6DC>&xc_ZgCtlr^E_9~L(Wty zuMmYsg71gKJZqnFfom6>0P#)<{Fze2I!*R<3ih&He`hu&PD$_2nTnj$z@)(7+!sS( z=@0n|6a(!X3F#KDd`gOA--@!Y!vVWJPCA&c2v4#tiCL=^w#F<7$H^#+-mhF#4`UAJ zMrx7&0LWmmZuU;Czq5FHuQXs|n_<9`#Hl!R`?8V%b+Yb_?q0 zVc8bx867Q197GL7R}B~TNbS{~z`^>sg{PY}k212nRX8o_52T5Pu6lG+FYWbJb#qS% z5!w9x0SbT}s;XRF1^AnVZYC7wn=ZD-(+Pism3y1x;e1RxhuFRW?R77VNgBxSYUz)G z5{J2TRZ@p0n;gwHl{bDZAcDix5sE{nyZ%dP1vc0&7vd+vDb*fhf{A_uyMZyXb|Xgo**q|>D(ab7DQ!JZi3D#P~-*OmU@epPI3rY zQP79gv;}{Q2j)K9@X98yka1u)C?QMc$cQ`2s8=M^KUVPlkPZL|m4><@^;2o_l7T!W z!C#&o=HjBo6yFu%Raj7(4nis`KztMEbC}6z&>{YaLeXbgMcg)8to$z-T?OA5j8U?H zd1+r)1NyJ(3qt-f2{X?)3sE(hwLj*!74;^=#Y@gFCBoUXozg1vaMA_)zL4+AqmoMR_O5_q4LF<2QYs{sp0!X*8}%ha%pAw#Cu$DBvV; zwr(9Z^A=WSAIP0E9P;e=Yv-umF@;p&FkNMMxgoW+0G<3hcd4MK_6S^H3`%8NT*|1B zI`}DTDX-HVgZ{*{m1^{*w*utd$dyo9<*c)qU`3^R6-*wf^l(h@5LtJq#8WzrbaNX+Ubom1_9HjYXF!-El-!^j%ZQ<_rytB({x-+!)JE^3eA<))RIgV<$$~9 z-CVi<0Q_hCN+BLLf(W8sa|LvKB+VzFD6k)rnjNUL>znfwR}{@cGIG4Q3ZzR^rRh#c ze$G@H^gB3tKcqO!SLWQF;99c&BtH)sDorY#U615dFK_(K$laa5yy!6X;vp>J?Y?4G z?s6&`Cy)W+;j0kcb-u~9s6v7=$75|*SmY>-ZwitI2C(>}iNUe|0ChRmfwUkhi-Agp zug^H3sZu1Xv|9NmBj9AwS6lIMTRT+z%2rKnsc)0 zanjHWXwavf`i)t2?3<;)S{?=HZUK(r##h1+f)l(p`UEhG=2%vy{jS?8K&z)vH!GSP`=SC+FTrsVFBzVRE0;(o;NgKK z4*uLGQNJZbusdPW04j%QWnK-`Y@8TiBrZOQL3NiY!3tLr@mP??@72&%iF#Br=)T9)|4tTc^b0618gIfd|?WJ%w9iwe1bHbsZO3L{%!}iKs zA{EjIWfGWSas;;9TL;<|x*V29f}!aKT1 z+9)oy)sAx0_p$azTObOcRa^@QRM^scoEaw9K64<-&#u?#qwXk7mrbrIK#c-6*2z+ODdRQF1HFA zPmU#$uhB={qHvHe3u4jHQt>e=Hi|qQO_^1#>A^Eg&qVP`{{SjVwFM|Z)7|85^(1=; zbRnf<+7b%NU|vUQ0T?OXjtYz6LY<)=hMEq6M8Y#OJxYZEqF>y!(3|cFII!k4no&$@IU7~orz}(e# zxtB*+6qJiFuYc}yf^S6?paDgcqSP;{Mf^hf0;&OOxt=41=NnP%D;l>yg>*Vj#u1hv zUknQCEcOe;eBg_a@<3ikgXD(?_G;S6t~3GXQ5r-m*s=i5&(4<=R6idU?DizsgWdA0 z8u++dSD{lI^$9ANJ0W>EgS`&}+^9O|D6jUhpv*t)e_ic&fJ3d%Yq8N>7`oqy&rck$ zP}D8T)!}=DY9ZyQ##vLrovbVX4-OxTloFm7k8CBU9)4#HNcLpE)g%qT4I0p<44_yC zmlWI|a5x~nu+jqK^%miMwKaS|y3!F?6c&|aq9weqidLm(5m?xA`#4rb;*>$(bcdfk?7TPd%@cjRdguH1}*G75uIC~$|E^>Vy&vmvnxT# z9iGRT&o4~8(jF&fO)ykzNvaf_{0dMg)>>?o;9}192gE1|x#bB)0JxmNp;rYaw(jE@ zfbv$0r^H0(g|nIis4J8R*s8tV;h&x*m4}(&-r}Kk8{+O`eEdtdnS-BRp;^NznXne+ ziNH_`(UBySr$V0Ko5X2Id-DyOAn{VJ>xrH5+@`Koy1~{fM=Cb9)I-qm7Xc@fTbP<@ zlaV8r-Io=uud5Q-tdz`ejv;Y(ub$8v3kK`V@dX(H!_%?S3cb-rw=8%Sf+)d_YO|zP zoO2eOejEIhi3Ts4d~{gJ)J2P8vKywak^ zUuNX1^@(jfjw3ie?g(%x7diGOTX))U-I1m=IJVoUN9~N}FhitknTxQg6m%jH<#RYn z;%2!smV*EZpmgIg55OIWaKu!JTX2eLJcarcWaW6*M$w4y*MO!KayE1<-X*Uko>)Odvhnb`_1kKH&*NmDVz~cJdlN zf+ zfe3mx!w(u$4_W?5&N+YJW1X;3MNLNO{{Wi=)Vu8M2s?E=mWD1uk1)e7h4HAXm2W_l z&%rgai`8h$JOgSFnm3e>Vm>%xE#PGgT`CF}$8wt#TluIrW~}2m1jR0P{qSKBJl5ZI zu_h3zp(TW-yebu!%76Vg6?tLt=rp^vGUB9BbquYz;=Ibi{{Toi8rn|)VtM5Xi9LALZ4J{;Y)hujkE!U`|0mWwV8m}ViFkM=J2C52KJ@I@+*6x(Z9nmy4w}27h zzzVm8l|V~w166#%BVfm19H%gPeRo_b5&cBI#jsbqkTf3Va&jR%RY$#u0N_VEQ5%=D zpS5roa5pMcHn;R3*(%gAZGuYy4S&kfXnK4~dWshLfq0q%Wk*y{{dXO%Cv4q(-!{?- zEvsFCMT7U19R&)D(m4sLTQ^3+-qiM6zwsk}N)#xiU!*-;vBZT`l>0u&>J6z+n`7o_ z(;}hCvIyXC8ndz%ASz{fe&U(}+ZPHPXP9qjSgyrtGoA7ms*UpMoSma6cPh-kOfW-* zS{0|P6wMAVl)z_DuxE?v7N+7hK0GtS;d|tWI^6chwIi=SCmmN;!Z?OF=eV>HWja)4 z7sB?E-~J$u@v{1S!9rSmuiUde_iica5Pk!$#nFX{ue0t41RY*fV3p_eTUVTogo1hY zDdr8({Ohk{_&R(NP=&ZBDiUrz6^Dw$R-8~rgTUt{M?$HI7bI_yNM8!ulgDIK3Bo^6 z?~DQt!+%QcX;S^9=Y0=(5y#Y=Xc-M9ob5nrKZg}{r7}pHR-y~KY@6Xaow-j~Gu}V@ z6->fX<@tyq%HrJlc#JS^-pWeyRtIfc0+)}GiO^24sY-`;oZG3S5h<*w!w~o@h;gSo z?qgW{_d0XAM?H9#x^o*L(eo>f&^}5d08v#(vluLm0CF42*3k+!GEuF(!2C-n-I%He zzYz^UQ)TXz4TUx3g+W&%R{FYxJZd7_&HHC3GJ!o1-A z6-Rf8EpAYUYeNCSAXR6$sA_7fZx(>TdaBhBL&c8oBHtx+#`Gi8hfhsRr%}YqEWzW+ zX(jSfq3e2=muo?+lV!QOgvmPG*fAKDvqJSiVvwWAthjqr{B3yr%Rg=kfYE+s@pZ6^ zTT6M#XDG4Ahr(6p^A^U-C2^Lp6mioap-}J$L3HJJE{}N@h~V2DihL5(wq8pGd`i7t z9$>}|hZQtadM0+N&6)%C7opHm&@yqz?}Rx0g%?2dnSP^cwV<}{plOfvj-XCjI)lZ# z&IS%SJy~_vh}21Y!YHG6YfkHGQo2o6le!b~zGEv}FT`5%D&FPrNnKIgdSNpdhx}!V zl6tjIY&zLUw^f#oOvg`%O>KN{l}6-Pe<26NZJctUNK;=%LIo%w+~pMN9>p+LcBl*T z2*ar=U5DnPE!Wd8`v@sW6AK?@Lv|vwN930>X>UqaV1-O2k#=g*+KBeWX%xY}=q$b5 z?ud$^Eq$~6gn$;hu0q%CV)q){cp(yLU0N<_fl*b?B145y^hBkNo4Q}V3D6H~JTyOc zs}S6Z#?G^Ni~<_GK%jU4eD5%t8iLRMcrzw)+R#iz7W(RD(PTt;FY7FYdTPJvl}$)m zIMgO~JNomqBA^RtKbfcu_vj|q9ZMIOTO*ff{T!YyL>DGUVum;a0$r>_a}JJH>TEhw zdYsZ)tul-iwi?^=vAD-g6ew=u*aaC?U;04TgpEO0=5hzlxGD%EYN4jnvVeDvA!Voy z>SgsT0#p@~2R|?W040b#0jHXYpNdxGA26Yaw?oV3HFTga4wQ#$7wslA+-=nqSA}NZ zHas|eO7GadJycs-+7;L{^bi&UM%Ygwq*wW2p}6Ts?Zs8VQL=pRnC;vd%f+GE26sZE z`mnz-Eoq#Qi&ooM5aJBAujo`HtB_i=N6ZYzP&H7Ec({2&r-+C$k04F8eirS`D z$)-!Xz2a7X6?X9%HRJQHlz=f|DaJerI=X<(l4e|VR8T`Q_>kDB#My`#|rmcia4cXN+!tRUs0HVQ7)<>0*Kqp?Qj z8@NSBYwTEKm2-%u9PDYfMCuS(# z1L=7VQf3=Mz*V^fH29!0pe}HCz{Xt;Ws-CrvR}bf3a~A=jjD$UvXJa<4WfV7%gh+s zMdk4MAQYm3ZaOoF?8kxY$jEq6(PB?_H*g^YTA@c^fxrXISgWliAldgORn^OoXgX<7 zjMnhWzM<>lFjNB@s%&b?y@nSyb#M&Yu5oS)U)3^+Ob?xe95(TdvwTZ*bVJn(?q+RA zPB?%TXT~8&Z`Z{3IDZkxHRoVmXEMqxys1IYjF@$K^#h%+9^(4Fq9PM(oxBLfpf9~* zsDgUAs;a8$>Al*Cv$~8y;jsb}4W3T6LdX$ee`QvsU&A9r725tn$Z>tMZ4J}Che=G7 zFrE@3mThQ|b<+ACVwWSU?2jYI64G&a10xDMsNWUw6GSB8>Hh#C%ers}aii_2L%HT4 zD3x0*0hBO#=nG;A8McjBi^3%Iob$8NT^;4Iz=iDV*v&HK{TphB5N*0y7 zo~9axlB`AkE(E!;8tk*YQq)x2EHI=rXzmgMUZ*t|m>O~u!U7oEJ-E*b5{08zvd;VI zu(lc*Lt>?@kl?up9SBEIr>g~e5n55VXN!!y>HtFBNF!_+bh%xuLFa{LzyZ{@D;^0O z>Ifo&xmGcf@qzucvx}RS2vr8*@g_17147R~9x zc9Lq_I&IWlVJDSl$~SHKl}$x7YQ75kh-^jCvL4}YOmOSx#4VKM3abmqcPIup;hv%v znjwO(AO;Q&YCOdkG#?AL#!w05J_neFV~1OQYFi*ZhiJ=`j3-x5DLFUF?6urq-Twgl zoHcZ-24A2M*>(t#T{&KGTrbc3smq_fVjW~nSH)jTO7N7dim1N2j^fJRF^5DB5{3J? zEfIP+5SiAjH<5YOfY&1zH5W>(OTOUSi-_~fEHjEmts>2V@E}-4j_SCsa$|}W;;tQO z3QjSPzft3pZ8x0Cm34XI5V-^KBNzt$wp$gjG9|+xeG|&Y!nKw2_m_KN1C5?$FBn;{ zd1XdiXhv<{59_X0Cxv!J>Vf13U}zPuOA5i=n8RNf4S<3|LhWACH;>=4+vT~dj6ib} zVq829*Mb)tka^pPC>zeaKr`x3#KDVaWvlv&jAXsNVSP_`68cJ93VR09Kd^iZytp0&^AU(>s+P+jOvP^MyurtE98(sdK0_+1%LV>`rc!{L ztrKuF7YYi<^$N*!B}uU_)Tjszl9yVRSQhce7{eC$(`($)!XUDAaS+`){I%2W3x(Zl zp$dU&j5T~imv78amj|Jg-OCyoZ$e)~%j7WjL(nFQPj3h`G!w3TG36`Sl-^!MS0L)B z#55D38IE5}{LUwMX0o2Fewo^~Er)0armZml>q+?w}w%tM;sUjSDr9CVeP8 zv>eV|`hjJU=IG~+z9uUSe1$}H=&n3Ugy3(NY0>U^&Lws{=fw4KV6}Vc45Gn8%r+X*&YzH(R>pK0h0Sjvff@W_q{?3YJhU`CwZ{Ru|l$y#9)o6g);Kl~FX8xP}=8#t*D@I!7o)aDlcl$qFt6XX7MLw0j$~i{xh$Cit># zbU(|C@=Y6O62ZBm@%)5)SGS+JKzjhw@~nUXzJLWNy7w}`=A(b+#kJ_D=+|Y+v2dKB zmzo<5ZJjiLzxXo&R0EWGuE>yZ=t{Wr{k3}+h6^@y&|cO7I0~~i6k_1^4WNa*D`Uu% zi?Dn^kix%X4LaePIANdaywhGV?ufqq%xP#!~JS4O#&0!YDyx~}t)Ot^rbgw62*6>XdZ zk@;-k(a-mZZZVwrtrVE5ZK>_lRm{44m;Oho0(eD=Sc?=zXcN(KuP18x^*O7*h%>+` zVBqMD7i6%7iTf?}1Se+~@62YdPf9%ck7FPG)s16 z38wTNQXXoy_1O^Dgw=1bHYA@Xt4?dL65kpxVhsrK-v!OK8q&^=Gh!!~%g4ZGCeJ0onv!M(;Wm5a{nyTeS+3VXHt>V$@eMP4sdV~YCbkVrDhq+0O>0-}y`>dITs zHO=rtF<49os1JEj8@p>Q0pxgs{XZ~R!*E}rqh$&}#nClo!pnMK^`nL6Wv?wENS!jb zL)Cz1+_IyAq8!`HE{8P+w^Q+~zYq%Bm7Rpt)D+q;a&h4*-$^BV+l@HmiFj2l!`RV& z$bxDvX;C7Aiks&AKsBiyT?>;i(CxbQEDF?hUOhosNdUsXG zqpIpJlvJZNY_`CzKrg>6C9pI!Q(hs{%hC;4@)rQ&K})injDwX;cZ{-w3rwsK9>ILoWKZRNH+$8KLa;Ih zf;eBmj4-?#WT7c*Pm;KE=fk<~qCFHZU)IJ+%{|y;mrB{D(@*TNj;WisC#r zK!OZ8!(?Q!)_L-t*s+WDJ{WM8@69Y9SFYLSPi?6l(9=N zE0)<0M9zRFJZh;HQ{q}bk?HXZVo~WDr|KJ9+SGY@n()p(=V96#^$W`ixxqMmL|GOu zl!szYmAw;z?2ALe1)2o3NGTnm2Fk@`^OVExZFEyJ4J__g%%HZR<+hJ89srQLHE<_M zkAYYVa_Um){aA--D)3NjVvxXPnqVHIaSW~CcI};?fC|@%VMC?`J&`EdboVao{PPeR zb}G$Caox)%8$E3<4MlvgEf8_b=H)7{VDJ(bNqm|_s+P0^o4Bf-G@@L+h)M{XRBYZS z%`ow+cSBbsWdKD=q)BpU>f5G(MR}}4OU$maf-oys1yM+Nw11@Ypk||3%e^SCff?p^ z{{RRm`INTtEf7G3?849e{2imNQRVgCSLh}r`e=AIdFwR+tt`D7u&!(+aCXrWj18?) zBb3~&R+GoE1o=Ef3fI8Ow24=Vwux}8s<-ZIZlf1WdW?2YAKcA{cw`OAfq-7{n0M`* zSPYJfqrM{@rtu}OTlV*Iv+4_a<}=a&9{f&7WN=;TWT0NpXhV9QJj#3S+#c99QU0Kv z5p_n2i1qsg82}Z=uMvo$6jsz0`l}U7jxTit$qGG<$#9CNjnWaSv80Q4`*Q3zq|CON zbe}Qqt!xm+(5j^rOMz&1?bvOM3YN6xc%SfyX+=f+33fXIlPUZzLpat$jR74n%^_nf zsagv7>TU%=r4N|u7;@bLDnX%eVeu(STBl>OI3A_Lhbkus8#8U5V3wd414&AtboX)1 z+yKJ9prJ%A)G*bWG2M)}z46geWTD>h*w5RMbf3rF7ep!@Qz^Id?4yR}ttDLssI0npwhjhANKKIiP>x zHWfHkv%#W4{TDAB{KZ2nM$60okrcP4psFAby4iQ3;*@!jN&c!GB&)LD7)>h2yf|fK zD@<3&fzY&`T39gB_B_CaZPz|yv$~*Z;td*Hc;rusz{O4>uOCJx0=!#W0)V`{FQ|f@ zm7h10lz^pao^BF1v`OK{N}hD9p+<30u1lb&FK(e?mW^E}B(ZgSd@ul>Yte|Ztu5YH zxO%zh1k)9`TEIn=oT{`F?1bWL<%sl!DR!WE&}DgN@JHXM-W4jIeNDFBVOi>V&Zka7 zh@#6w*~Dz%2i2sRs=MZU#C}Rb5Mf-Z4E`#=j=pTH-Lh0plcy%BW~1UdBK>%rxrJw* zCksA~p=>Ft-9~tPGKJfa1bHm)&Pwgl9T7)PPL~QOrR(KNm6=#47!n<7rryp#?MIbU1a7EFvP>;32FBqs_z@ZDG6x^$bJXBFeLDbr| z*aQW!z@*tKtP@U7Z3$;LL65B$Vu`^yiiFU@059TNg1}kiAR}nIUWB=jLa0|{r^%zD zx+0)O$AKVgBGu9_*eo9La@)EQtyZ^G=WqZkK=WXB1Hs-EZ-^nDG&}@VxSjkz5Nri@ zk=Wc5v$4kz^I>}ZzMdvI9v6u1&t)OF=%&mB3l*TYPmB*DD<+)x^0<> zF=yEK64+o+nMi4Zhv`3PF6zr^c|+a9rU6v2Mo@7BiklRdLqp-mH|FP{26lwCcm=v3 zGy%RTW!hyyJhhWhg%-HQ*zpsN9_E-6xUzZ2ICV9}ssjR!2}5$!vb06UaZ5dlGUa;n zl=Kmg5wJ6L2669$$Hl@l9_|U0f%qPTZmkSn^gON*LhBFJ?TvjB=pO{Str?6$USGlS z3978QaoFrd>^;W^e;5uHI9)9eJxd-}Gs*=J3e|Vc*wcJtlC7*V6jprunNgJ9uP$-a zG4}z<41Cfa0r3z}N6Q3DG1^NrN!=%-$t@hzA(Wi>0o8L?3}O=<~R8$}Pzp{g5O z6XRTNU(IntG$5gT)IEiqtp$zW{{Wo)LEgGK;-V`S1D@sz{ZQOh@F`NZmH<|?zZ_@W zMCYk`h>m%FBi2>8zI>TiSKQ?5Cs_BM=Z;N^Sh1KYdzCODyEGd9&4i@ttS&Wy*-ceJ zst%69fE0k{Va`S8=Hi3JIycbjLh9h{i}l1zz~7bqFfWBHZDVmn?7R(K^yvlm-A42o zU^)&MaX5X}*2CD}uL9s`rvc0c?U2jSj`h0fL2*)#R8dhl_$Of&M=p_}bwVmDWnCME zPBH-f8FH=GDAA-kEpc(8bwR;)E|J6TOt$YJmC&hbJihXUA${l{h&22gehA6Mk$8a{ za9B;CQXe(hDMSHMs_Pg*uUVoRoFUJRq-xO045iQDBEaHoAVqn;2^B{rS~?W-QPV>U z3;4RYIxA)(#a|stp&|-}1CU8|i6tL4<13n{-B`frID7xC?nJBBfT4_PYX%@K8Hy<&>)S5a?Z$bc)@gnw&^L_J#g8i`Z^ zj>1Z-FI7w1aeUE&hzxFoct86ifqIX0Kk-8ENa6ngEd9Uysyt^hxbsrbEs6JXDbS1A zwg7p^>=SZyQJv~0BebcPV146z{H9RcWjTV2CskEm9tl^jJVK~rXDlSz}0;MmSYq^DE)su%l6MTLH)r1^s6ae3x`zWHG=;DgD#vZ zWqD7i-kNK)gWw`jLaVlNEl0kmKc#%LO8}=J)I1XzP9}kfIUWB12YkwBPo>PS%Yoel zs3Yk^3qT*aBjZUhFDHl)Ddesb>NC;c5G3h6%vTWOnx?kk!z>6|l1qoHFna}7SvXp# zyrgYroOr_uX66{)aDA<6zO0?0!==6BBJs^H%$%dmu-VXd+pnyBKt7*h( zR7e5zLs4nfivwN$i`fWf264LnlLX!E&N!dM-7v3se!!N+o3JDmYQR+3Yi0nVZ6Y)10mkjk)K z8$3M6I(IKK!cA-r&T4Qck@>s`tgZ()eES%@ z%e?bKC+$Kk6xYnEBmw%%e9mXhpRV;Ifj^9AwaGZ4;eA!-wW ztE&hU31-`Q(!cXC+*G;GAVLHL5o2JHByzKW5HY73ftq3hKt4|-b;xVE_(B8;^#G#9 z3rEWx2^reACKcv4w)ogk_S8W_p?Kl`A<0wC*#a}*Yt zK$Mo3D?9f&1O7zTxADxVyB;^i^pTznecvPZC_nq{3IXD)={?#`YAiUkl=o}MmIRu=L8hC4hqD(D|C425&st&2OD+mkT zkt>*#7j@z_vS{;l+bHa=e4>t76i#buJBE=@48d{3DCI)Qn0O9PXWUiQ#gBBW{r4qdDuFnsm*|!%Jmie5wOnRiGtZK(B%? zgx`<@&dR4+~i`6FDk_yR5|KtsecR@1*tDQ&Z?rsu@%Pa zOF|wp>VXF4Dxmdd9|ySAFNmh?s$bM25q00HF(+SK!nlis4%us#E-Y82wDzRP<14q{ z(H9H|>E}mWwCdKu1uqB=3l;JiI6h+D1L85rYmXmu+z*EqMZdX=ryjrXwwkNP_FsaR zPM9?-z(*X(gBS98fQ-B3IsrF~Y7Hf1r0fn+K?^J0uttgmZ$$AcAeB0N(fy7#rDAb4 zv?_z9qP76FY8P?^1+kn{U?w!=A9{lL7k#p}#zRDCr605f)Xd()F^T+8uGcoYs+V>*GyDo3Qx=3bn*rqws&?QEr-O^kqeldgi=@H|kYrYo_ZWg;rBuE)Nm7s;kbQ zMGJCJLM}b6vC=(CboPK3G}wz*ONrEcwB*)v*)GmPovgUbx&$1yKkVLTt>Mv>8)8C- zh5W)vd(wwvq!?UDNnjiUFN9qB)>rX>dxLuh1*vqD^NKywCRUnuPG?Jg_r>{mhf0*=#i*CSm5DOZ1)WTo&O*eXQ2C8IoEc0LTXEUU*&EA^3+i)%vO^B~HUb$-AAR{jgs;Rmb2HAb&x$-!|#kjx&#Rf~O!nog29_wc8 z<<9wu0=cdoM%bl{0$~B89%SROe}H(nuN820DTekUU+Atub)2U zgLsMw_+>l@b9yOsm_4?=0a+9W z`F-e&|7>`#v~gx^&z z`x508ND8;{tXR3*v!7KvTEO6s_78ZI&4518k4q8rcUvf6Bi+LB1QO9MEg4C7-P8_? zW#DvN_$n++Wz$PYwQN+{W$RTQMJE(6H?7LTnMMoYw!%(y{F9&1gSBOr69P7}%}eP_ z%2dw)*~(RMa+n=ONBBqu-kNXmR~!$tRtOEkf(DdWtQCx~X%MxhLugQdseds+KMBkm zeh@#yED=Bs0lx*XtT-*Z+kLF3n&o8&me^v4D%V5ELJYwuz88T$Zr{n>W|voYanKa2 zM-2!2I$g1)bUqjy+OLFh5;(ZN2a$#yW&Z%70K~rfK`qER7&OPO1Ui1)`kB7_k>kb^FXYqt!E697qH+_UiK+gR+w^4mmx;-P`VUA z7HI{6S-#_gK$L@@wgreSH;0IED|cn%j0&#PDpv9{QZSLegRa z-w(Kpx3qS@F9g97D%fcQoFdv;b-E($>wncbsg$<-Bxp}H3zsJ5h5$!y(z&QuvLr+h^EjhfT)Lo$A=7x>HWiRte=Kl60V!_?gd3zPh&{j-}C@*7in*(3VJl9%&b|EqU?xyj=42<`u3MsuZrBiHe55Kz0$|A z6-D1LwP<-(0Ii}cm9%W-)kj0(JrE#jxS|jcVT$P;!$6UDHWa6yKa-|SB9;#$(nmKz zLQLBlhYVJ%x!!ge5CgYZ)Si5#~}o;LA0IC=JDEjj-Fkf!*pu zMztJn*h-Gqo7#neUXK^nrAv3m!_GTiJAwBV><#KmporDJxQUrB--`~iK6$K>k% zO+>iMj72G67}J;uc&iW=Th91VRo1`iMmv%yINpZN)Jk5*39VGVaPq8f?@zjh7)fKT zA@y-(btDF!Uwjd<+(C4qVqkji73u=YEngi(Y%dW@=2;ymv=tvK2qTAQUPzIv+4Phi zejr}sSJ80h!Y3^ckyu*2*|!oZz~j!`Awj;X2)F8?f&;w?d)-evam%*5xmDRJa_eSl ziKJUE%0t+0eTdeyLhSzlp5P!fd?nnCR#4NXr4g^Pu9bs=jh*AQ9;1W^3Z5aGxM{41 zyK@FM<@9LwM+ zk-i79NYhVhv=7F_5gLtGIBiY44Lwpkio|N1L1KvN{e!R|4-UTW1BdR?HHf?w;l{}w z2mWQk&lqg>mAkQhSxU@|LIiLc1({ zx??e^+kC_;d3S^ap;fw}hAZk`I`Jp}02RKk%t!+}%W?`>mNOOvwVauikp(fj zMi~15LtO1Sm=~N%HOm=XF{**8TH?YhIR>HOQoSm`)sv}kzR*y!A=()u3Gg7~E}-4J zKCWRnsdsl%yp^UB-N}OPl~SfOwS24#Ots+6wxQ@CSb_65(FPMQWIh#bTt#)h==6Fb#2Z|u zR>*wTYPqBE-?(%Ls!+Ak0Wmm)hYItb`y%xTpyp$^qt2s|U|{Uz3D{M>T&dq-Thkd$ z^KqL~sZ?!3Xw#R$vc{wNVkoXqbz0=ut<2 zBL;w!3y_?c^j}doDWBgmif$_q+glYvOfd&nwkwgqvA+72hXsIj7SD-xGk2n#&Inw5 zsI93;T~SLM+rsoEeHeTK@cw&!$=li>lJvwqz^+i&MeOEHuqG{2Rl}VD6wXHt6!i}# za+P?YxC>T*TW1lBc1<%M>xpr$VLG=1=O;*+Qq90T1^Zrb42d`Igl*K+8?Gf#wN zz1Aw^@p!Oc(4q<+Pt-mQoo(h14unPmfNL3M#*2E&GxL$foz5v)DeT_`NVxBtPdt*v z>|S%B{{Yp`u=?f%by!TG18|CafvOFnA%^o_M8l&VsVO|1Rz{dLGE(Mslb4x%hlnx6 zzH+4Npg;2(ab>5CpZtg1;Y#0A&MsgxetG(+hJ*+-w4aTfr*R1izzt)dP077B4^}0W zL9lgMeO%q~YQY$?qnS>uaSz=#G1jM|9wY^`W713b?V7u4P3Qmz2JsiZT2!Av6iZjQ zJ;ciOmewUioRE{}l?h9So5)MJ2C}MQ0q2+72w>Q0V<8lke;~*;P*lmwt8p`f(7rfsX{;twTSW!F`!6qMCI0u>d3L?WiZv;x9$vW2zKHWX0Rdx*NO z;c*oZ7OThdHm<6qpv#pL$VLYwuO=_Zlz)kN#61k**n3oQ+HnS7ARo>+nD>yo!Q7@z z4c8;jrf%|-w|hdR7ttOQV^i0`q%|lPYiZ#aFb4Ds!kosHV8lGwtVKm<{Y4n&A!>*# z)HkwtjliU(kOFDhQrQ#-WK3mf0s)1Pc!R!zwszW^&Y^ zm9CKJXJ=oYVLj_r)!1pBXHIn)2#+c6M(+)X>l@w%$a*0)!F-Mt6%dU%%5!_c_>;X1 zub4#wk9ZH-j+Fe1{9$iPxbm1W5O#>J^8SeH+0x3r3*cGDbG?S*z;*gOa|8}*hpYO4 zReIJklI8t0*O{LVtnhAd)WB(qB}KA`+ED?0k30VWkPdubV6{Qzd)Wh<7+~to3K`Ivc+p&FB zT%)#`^|9opm2*$*N)+|axG+_BumoB?aw$aArMK0ax|wQ&tZ2_N>%jxt$5jQFf@EIH zh{6X4BUTm-AY0Bmkmz5`54>z>7Bh_M7_Y!9!?B8-7k^D5#a9Q zfTL;y%gvT~hj=a3BdgE>^kb7s&LA&fjJIWBhZ5GG%J?QY5%M`>`ScG_6cj9Qkxh9M zc4e7`+EPk#5u>rf0W4HyxJp65K~=Mw4cQ7oK6 zLZs}!Mjc5-`Y0>|RpJ#_;VlF9)&{M!zOE|o!dY{mYl9x3)DCP5vem1%f)YyK>xzG1 zb8f^s14cC<3^*HXSf=5vIi^sns({i|8A~#PfomTj7(l_g_!9FdsNBMp+y4NJ?BzN? z)9{M6D0yYFrs-(B_dLPT0R)nqvfs#6T%df`Tz1QTZUFb5rtgj+)^QtC-;JMo)Jd$h z4l#%3LZX0Ca4@mfiHGUq2sthK%SPj5fz8Oy#hO~Th9P<`Eh}{f7b2FggV8HfmiyVh zxNuZYwAG8tQug7gmu38$5e-M+tEhJy+ykO77^;04lG>;zAqqqy`oUO&R$UbK7Ft60 z_qhC_Ea=#no1elu4sZRUElw;6bttW?QZ=Pvb|HBJL@SqiqWHMeg`wwN{{XCB!AARG z$*awkK}}_0+;|2tmxQh;Q*WnHth*lAwo}2>P)yPI5%zQoi;ybsR3AyBvS?Z=osy!# z{{ZWp=Hzn*-7wu%&@|Ix)J6iT-tt)pYAk;*9cwJ%O(K^P&7pWsys=MF)wHf+c+vGm zF?fm$RpN>nuQgAyPJRhR0+>|i}KbHt^irfocrPwY(HQi zdV;Nf7q}x7IHy;oj4^GvH47Gqrvbsr_QqxORBpA*TDuMyNX|$ITJch_7TS=%aS()4 zjr;PAdI`(46LMA(kL&1;>`)bH-*5yH)LJ(Y=Bien;i#%d%@$?g_$VOYwrctFFtYE% zIYQ0ua)XtV9_zF|#0HJ7py&8c6BR9Aa1zL`H3H~oCgY#3CCd1>Ek*j+ z0pg8*!=gcVN10l&u{@mNYA;9pWqGat0AR7Y1JPp&t2t&p4FjLR{{XBb6qGA(mJXo< zY$o?|-5;PGBir1qOjW9@=5H^K(_;b56zEzof=4Y;N7P&lrZt;F6BF?X#UycTn<{4e zw6u)dXt@}<$k&T^kdE*F0PmDpq;3I+^HAw0hntkz=3ra^4o0*LMsFb(iXzve)h}Q- zbI&n#8a_#PfOA0IZdE0UoKVp^f~R%oYUA^~MZUIm-Cq9y*nG6{9{kJri9?P8X8!=m z-i?C7>wz?NQBFE8Se|J33Xg+wlq4Qma#=}SfJWqA0M_vQ;uOT~NYB|!0EY-OWI z*@i_a8sr=r!zZ5NjZrs@Sbrcy(Qx1$sL{o5-9$$K15k}Ku6skD$C+Hv**fi=P9oI7 z``DO5iZRz05ZWv@UZUZlDIr>__ZCeHmG@KShaEYqi0m_7u&)J|briO&E)~9_Vd8N7 z{{SgnK!98VRIfVw7yCFw>b=dX^WIdqli_tc4}llVFpg)9UiyXRs})|Tb*bOj*#!kQ zQq{taquU=cJwmxS_$$S(Z{`AZyZbp3%+JXNYS;eddSBu!x{CJpyAk}K;DbHYaH~oR z=mIywFav=>vsDUywG#m8V`AU~QGIC#z_<}DG0)iPbqQhyCbIms+W2hBoV3e}M!=Nu|Z(j3-zg{@$> ztneOJ`bDnq!EV^y)Btw*$2>(3z7P)%t_(5itQw%4?m_Fqqml93g>T92$iSWjV6 zHYHQd_fdDq84FZ+70h;PBPE_zTp`U%A5{p_uGG>8`=scC(638%v)COLx5Y>!rrdCFOIA4W1Qqsz`PwA1Dxr5GqX^))h%t$?noYIsoTU)DvSe0ZJjYNi zyx|M`e7oO7r)4s!8)z2bv{ul7fpS%9u9 zULBm{!Q|WsxCQVi1EL}2Khi2XQNAm0`%~k&$>HvyI3m-{ka`Iwm6GM17xHJ+c|_Uc zHZ@HMwUjGq^Y9Rgk(L*&2o#nfwfEv}WaV9=R^PT$_Bj@vjrb`=^*qq3xYok?U3nom zNkN4V2&@BtI(3{@k1nDqyy!OP(n6P<=Qm~vb-Fkz{jeAXHf{O}BB@7{FaDyrvXv3t z?j#Lqan2nYg6PdyU2`jdL1iBe6E|9mD^kPIDoWE|#BsedK(N9k1&7H%oIa_lC%Wbx zXV^Lh9z;@%?#?*Ft)~(LSt}mFV|C>t_>4f&rwK_9V7qK;Ermvjiptb16df>PHuFR; zhRhRL#}?Jg08HIDLMqR5@-vo#i`jU?j)tSK8JZw ziWRwF!bU}M43-uPX&@L02*(wLY^iOcr72;3c^md8B0MSFSCMg=Tl!Wj2#+gl5Rq1% zOCz;L`hXG5%cdc~KvA8L8YsG|Crx;P97-2}OVs4mRkaNzGRDB>)Mt9G?Jw(zRW@>- zd6;wxrrHT%SSMfr7^V&)&4k2(Gqf@ml~*WQM4;4)D4mG04l6jO9Bf5Dvl5^i_(>cP z4ZkV(JmgBNrd-Q@-xP?2S3BHxC)l9dT8*kx<~Na_7Gz6rwym(ZP>Ym2GV8@YiwljJ zU!jz{rz~$y0BSmBmv%fsf*g#lZuonc_jNMQ?;3D{XT3~dv%Ii2Y z4lRI&qyGT$$7)xuGULg)Nua5pWMNg%P6f$c?m7)B^--sAIdJA3sX6XBGWe&iI*YkF zwili5aG6Jh&ijHnLj8rhM&J$I6Q8RBC{jAG`J}%WA8{p;5|0ATOC(;=io@lm1EgxJ z+QbjKnjb&~1DKMsn%GjRrO#sAU*RJ~mB6oLBvf@OtLfC)uO|SwM$$0 ziJ;YZYkO``aF5i?tY#@E$GDeI{3_}^u84%7!jrF8JXF*`Y&x@vdjlZ~X;z0>c@wtH zu04s5Kd>!fXyY8OGul-#lax7Nfx9?Vb?8|nZKFJEv& zJs?OOqfDleP;rPFWvt>_0`z|lRZ$=aEE*@K9!Xe3wg+q3Y}&eXk~&_Cu%%ol7c5&9 z(u5th9ZEb@9_O6Mj1#ekpONzzTa&`Iu6a=TKr8?@FVq(MQZO_M6Byjm5d#C5K@V#l zR`=#5SgHFSKtze}^$^XXUzVeA1t*2AOVx>2bR-t};vv4J5^HVyu$^PK#BAVx%fA)l z{{RbXPkhaEaVQ0jM~b+u_d(n^MdY9V0Ebe`qGnEPu|xV=f@nlVGKDI!VYuLBPH`hd z&1D4fkRD>J#()rPi(ecESqV@8ry*Fauq9x;@P&C8w`(@6hs41Z3xi4{JK;Cf?8?w$ zAbrhRi>Cwa2tv}Pi0Fc^mBN={+HW8eo0rFOyUEYzP!Hr!@^?Xn&RAo^P5PK=$@q#P zO-IzF0>%Z2R#RJRRF_IhkB}Wyy_W$*N-mYfYtDMbAua)_9|;pyrVG!Q8p#VFXd{(v z*S&xY6q%kvCfqq5emkC$zB~prX)&m4VFilEQb;MF`1=wg)+61~7~YH@a3Vcv+ouh& z^Ms+GF=1Ou9i#q5>$wvxM&Afmgji1=l(6*y+3kQ05w%%M`$T88BpUNqG~n-cd8Kp> zwuAla!X=NmqYy^ge#v{8+e*F@*y3zqx=Z1^ohpY&8!BWlfa$B4xMEB2u4FoZnLeZwa0?|E(z%rD1@2OH)^yi%c!!3~rD|H^F8G4%9pzdx=m+?I?^KIAuJK)MTkw44@d7T>0#K&0o%hVZbJMmN>LaEG{Wgak0erj`up(| z%OL=fKyJSoea(q_sgZqmJ^iUOM2^v#xHKm0CuFQCd?BN9_HBbrA*4uCgr(W!5#VJI zaai?_5B~r$h$~lr2Z>76IAdNS2BU!2>|HJ1EgymlMoJJSi60VF>0>c%lYF^{p)J-l z&+nPQ26Z30fVjzce?cx+R|hM=*AWDyA9PT`QEX#MsFn~=+p)f~Y-V54GV@48`C*8W zFroxTkx$bSu%rI~6c~heTCMw7)E<{>37t(fBVV{KBomc=u&9c##+5|kJJ0F+c? z(4(LO#Wkd`lT@`c(D(p5mkkLWPrKS<+gA%nc*Vz#7G3?z1YTGtn;7LSDDmNe!b_-X z+V5`=-AWs4-HdFy%-y6SxH0gwvBo9(!Px6Dun9xrVv7o%@XlNnZ3TZDj)Cwd6r(sw zmAw-#G$~XcD04aIzSz9PYrYNP_3m>JY!^t|WoN8_I8d9QHSofi0xfVRRjjdADZENP zq2ML{0CMRWpGxL8FXE_&q`6A-d-X4nqOW`jz;`w7jMY1zc}0&Y4l82oo-2E2Xu~HzwM21mdWigKF zn!Q~#L%C5Xxn{oixl;zJ){nMd1maUTF<~Ntf{P=RA;Q7-8@qN?q1nTQB8g%Khw+62 zMBfw+Zg37V4}d~V^Zh}O0EGY)sl}ysy@F{bUtjc2z*ny0 z$yDS+%&Xyr9&Ds-`06WXd^{umvoP)L!fg|+N=}Mm>6M`NRKnnQ+R_n0Yshx(5r{2U z6)+m@M{TO=s#RxP%f7fiCT-+RKIO=-FgOgU*~bpD`YvPPYBwzmdzrMPB;GJ3cnV+! z$ZLV03~m0gi%C zNLLC3rxKjO=9vI#EN=Kj#e*ebo4mWV0@|cLG5-J{UGe4~&bWn` zjt986MTQM5sC8gZVj%G~hMBNmh}uHrs*0{%bZO1NE>i{WJWpipW8U}FVMa)Rb~&aU z`o|I!EJSFL8P~|&>>nHmri{cq!qHZT-%T4V+v^5qGmkZ3u1rR?&R3u!N@#LI`3bZ` zVspvC!hFN{^8i#5%Z89+g{VrZmo?XiCcQ?xif@H?YE=TN=+YQak*UT<)g zZu>+XdbMT+MhdKKA8cyR1IcTec&SyqJ;f;1OKON~ivVeD@f0rK3A2K{yhf10D|yM) zjo-8!NVoyS$M*+Uj2HnMFJJsh93Vc_9X`kwOFGC=e0rB1O)nk{8y^?R;vK}ZNKTMm z0YR}DqPb`x{xP#&uyuUo(Zxlncc;v0zqRNx=y4QyJ5uau4Ov%6*Xo*uHA#prt$67Z zjj7_Gb^*GjW^eoMtAfK#tZvVx33Uw{nOJ1-tBi2BvYbzefktRmw#LHmDF|`Kj2;0L zJ^>ZsgP(UzeS}-MAyI8HdtFp;7lP?1Ea7T9psa{01!vVkRi}5J+K2!L383K`<;msa zI)z7=g4tEzjJjq~lkVqtOAli>>it8gQFdQd60fu0GN!4*o~zvJ3&~U`dCpV|XoY!V zSfdN!o%@RDskO={{7g@eED1%d;3P%FuN@9-kLi&1NAc!YbT|pATI=7kH%8Mj5`v%t z+l=w+y+Sqg@dWVi9k2~RAYSoJ=(X5(0I;$FW5|1II71%_B0v^AU7rjna1i={S$EV! zxYlQLHznJSyN2FzvDM-`IH12}{{SNMfqhIJTluUlu*o`mS;Gdotf~-#)t|kX3bL2h zoJOEPt1sZ(YU?T9K>jHS?(3-E`M z6b&?cgk93T&=EswbSf?joN9)dNx0L^yudzrB9kfAyFkcGinq{cNwa=ikQF7;^RvP#c$z}Rwc@{Rf$n6TN_9P-z5j2 zoCuWn{sFA{JxUqg0I_%>)OUl)groWx8&X0 zyaMh~ik+_Gb!1LMd|wQzfXeolMs{%v7EWSC`i@GzhysSVl>Y!(G>J5pSIxgRoqL>1 zFc`TZh6)G46`@Bjo*=L^gRl?sC709&>QMP?Y<2Dk^ICUX3@OtQK}FWx1W8@%9WT577BY=&sRmt zTwor8pf2jJnN+M1fDV@JtjANeHY+ZbgO^?!jwHTmj4SRd&b5!blp)6=u$=gT7Gj@E z*;`E?J$%NDzqn)DE9``m$TjcWv>=bL2%yPV@4*vNP+Lc*nu*I(8%AzaN5fvBe}%YO z@`b^KwHg$^>1LN3BO;#VN>%ghYw+zc83n)!>UbiD$C=L*1cSBqk? zN&FY`vWueZDi?+pG3~8<3JJal4%e{G*}pUgDt3n(ZVyx5;N@U(j+LTNXb^Mp+J~S~*|Y14Ahl#rU~wJ+|5)^GrP;%Z(fs63^AKfblGMi*5QNWH=ZqTN4Nmk3WRoP|yxBXzO zP<>?SqW#Uo2rj#FD-I%jhcLYKm14GT-E3*_@dzL>j_y6x&lXH8IOp7WYR#u}#6sxB z&xkjt{JB&eR+B5^`weVdn1AyKPR0H87K{d?3tYh1Qj*Vt^$K+zo=vZjl(WhW4lPC_ zkvSOEL=YdoHJ@=^Je%3JM*_-i#ZJU87w$X{I~JO$dZ{h_w z%JVD?_Dl1)Diz`MN>yGS*MeOt*0+_KCXtU<@I-A7&r**KWMx_JVA~w1Cw&4z!Fevg z#k`G#a?Eue#>?JZDa3eTs1-q5SjSLv!T!q_8~$NaORWOAN4jzhQA$#(Z|GuUL?Tde z_V^%Ot`kL|1#ua%E?}pF@Fl)%bOkZ1mGhpPO@j`O+8?|8hSBu(If&`&Jh!okA4rX5 zUI%dAif6UB+s-i#T*K&+kFrsB0IOOfGh`4dhTWK6Xuaab)6NAfaVJdVK+Icmpokuc zci`86tACr9-4es6pDd4fT>B^PQI?$ZOE3zxU6!TmO#3|S z6thZ7PPOwoRx8JGd=>{S2f4Vfk_t0=h;oj! zXy?-hob0Y7&j+3>wf%z+e6OuxuXyMF*adC^t-P_wwVH~;V>vMLu0ncy!v$u1#PE2U zDe;S_Mrakt{gJ$AlC&jcDqf9782u%ot%Q`qd*(mF&rC@uW8iB~sru?um)63ry*uiY(xA+0J&RV4-&Y}d_`+O z73gDNC-FHcmB$jUot%{`To3b|qGhJW`iu-Awt{oLa!Tk_9_+{<_fnf516fLd00@K+Mw-V{{VkLJO9B@enP)016dRkh53FD7)%@192QZ0Gd0IVQ)zu*N4dzZSnrhX)=|e=8{5R?UMVQHyGa<8-KsvNckVA%KIz1I zubeBpmlc;TmfV(ESb27I)2~saO0u%b69`kUm;6(5UXioz3nPG3hzrD2w%#Yq@9bDR zI6GC-=3MkX^UR1~f#=901>Y0HNiQMtD|wbi8x9fD1C1I!bQKIrzLVIy6naJ|XdhI{ zM6UPK?gE#wfs#HhSw|&p3!-M*TRBk#ylLBuB2vhYIBNz3+dLyYL^EED+y&7xyre6r zSD1ix#(Rulq5Bx5%D<1V{9kF|s;oeq@_48NsE~NMx#~M&}+zmhfE%;U?Vc!sHGUjktbov`otl<^UPnxPsziOSMJ{MFPWAN=jH&a^OU-nDgsdbM{1Dc2t5X zNEKG0$JmYr+I*!17V0khWL&iF{h}(>)y@0}B{wo%Si|e( zToK#sKPtigL$<;QiTIy2#)!O5Aj)5u!P|JUF0>Gy&E0I+W12oa4vru@dc zhm4KwwfRf%WGw-_oN(#i5T5KNH0*_#7w2SyqzbOJ1hzE4qy>&s$J|1OU&xSBx@xPL z*5eQ=TjPXTQq|v){n$M+kB^kUWF|r*7Po^3I-p zL!l}e!%&2zVBmOpVLEj?YNu~BP<4Lk!Wyw5u`v92eB63>DdrQr`_qWxrERTf+!mX0 zwF=1hYKVX+x_Zn_ybAuz$fqDW{^q(R5h>RUUXIvb;x4!a!OZ&!URA6e=>SpAf-6c8 zwi$BhGeYXjZ5-1o!I5Bd^2lvnBce*jP27sV5Li|UEh~$Z*Fo{Imf1UxztS`uzOR9BmMh{lpEI`c*EVZF$he-9F8x_z4PBC$choL}H4I`% zgS*;M;-d0rj>B68;Y7EMhSJ&eRYOZ|UELf)3q&M*BG8s7Q4YGh;$^Y#LHcC_O3Uam z{{TAf1x)M5an;TBk*+w%Py)wc0-2W0ghC!`+0J{AcL~m?ZO!12pctSA`uGkb#MeMCGvl9 zfPb5kUQPEYHOrAd@XoQjHE!j0lbFUku1I#HK-=btTz3o{#LBmuozolVSox`$W9|mp zhel+mn15E_N;pBcWwc36SY1n1z;#8-+5%zMBKPqcu|@bSoD;dmItiL9(T-ep)m9l$ zPeF*G2w6-@S$%}0e~5?-B<9cu8jE06a@-B%`DDzs2d3rP0bHRGhf5N+%VCtbRqE*;jvoBA za*e#m)=^b5+wa6&9Sk?22F+hZN;#?R}`#4Y#i0 zeH+siXl}zx%ZirC>P(UHhsx9c0GURovG6bs15(}mGT_f=I+D>YeRI5)X%*NM*Qh~2 zA=AoidvRXZ1xly^YG_7MKuV9zw8~)|{g{4175qDr-btOPz4FU0poNQFL?o zE-i$Q$zZb8Nx&QuC0;E?0+t^wp=`Ub@o#di0Y+>+!?2u$1yyK5hs9Rgx5OOj1s1{R zL^X&~FDL0oulxb9)m>9^tOx*tv{Eg5Aol%B4uwKyz=D+cjEPBJm9Y-^REeVG*NK|Y z))oL33j2x34C}~cnL`8NaAn0*9$!+ruA{(KPF`}l3bR&}@Ecn=$Zc>A)KiA?z6Nr2 z29}g1Zx3^Updp-D1eH9tpYV$a^a;P3PGZ~SYrBK3YQaax)NxvG^(C@UrZbyOzkB-1 zK(ngKU|IJY0A2baFMIrpAd8E3%34c5w9^vJ!OnglwmtjuEeI279jaRfZnZ%}&j%DI(@P~IAh79T9MO*QQe0>E=!2*mV#3);dfR^@|S?L^#G zgE!YdcQbA|nSX+iPkS057~Rb|#yvW))#WjP*3_x}I~^2XwslLRLe@BN^} z7j;sV%PM5ijdgn{pllBT!3lYRaXc3G=7f)gARu0%q0Mc87;3Ld=H@%HuPfBizOkCs zn35E&0Z!8UbzUaY@H={gXs&`#ZtxDSUD}^nEe>&+^yYjCSAV_GSdm51!zI{B8^Rjo zuP~OGL=7>qJ*un}UUAq51P*fxAEk1Ay?*Ef<{i`sz@ zF7|K>TVx;WFXPqf@VBRnn3>QTdgya5q&J}d02DpPcoz@SmIe5xlB}+6fR$;dVce)5 z<<{4i#3U|-r#tZ%xiihRIR`F>oPbuY@^=N$0ag{M#M`}Va6YE_7H8jZYEsV9!AyI~ zX87a8a4ucb<}2j!$Eel&mra~pfAfmo$J_>-M$y=X*F!>HcHJX&5J0lz5riU6cF;P#Us$K_>K zQh{&_T1a1=qU8XiDuGGJ%y>boXGZ6Vl*~JFR?XKOkQQKHu`=L`e+{dwYZWTJv!jX3 zAd1qxjo+zi4!CB3jX+f&9$|J6L8MOZJELM%TJ#XsE4YgmAVQ{zWjNz;%VCL5doC?4 zYs)m0YCJs2QmgaeRsCffifQzkP^zy~@&5pLjFTJRdk`_OEuE0oN{PiRzj-W9AFLz@ zeTIP0mU6^7qxl#nsn@7MKMX5QE+%We;)t4ghprGCz~kqcfcNz(y?sY)@+V4*1qI*9 zV<1o}it=Qz(x20C@pwK;-;AvKifv;f!nKvmT5Z@X9ITc7Q~^OBOl8nrs%9=RuQKG6 zWEc*T=<)TJH^OWUwetrV(luPrg!V9uAx#nAH#0ME(bf7RuP0O} z*90mpww3l=RwJ!oyYPq<&{;N8$wjTFB{hOMiG3j1e~w{wVdLB_7hl~X_dAOQjTl z*cx4NS0u2!pwpYE%&O$>`(mzKW=9IRLaSbTig3Kq#j)^I*a}znvNVg)A6}esNk&{1MhV5NS|hk~#A_|=;`tC_xE-<< z;*|pP7@SBX*Wt&4qS02TmrMK@YcGYG_PS*)rKPj-c0!RVs4aMTOvA$Fo&>pOZ2-Zq zK^C%s*tKEn*)|1S+Q-aL6_Xs&lG)4D$hbp06lKONx-AVbKPWy=jP z4%L$iOc){jO+La_80sZ(gaAh#7^>>_bW6vHxIKfj8z2Kgt3-hzT}HPQlb(?A%gj!! zqN8U4Zhex4aGq0s;;T)EbD}q&*<}_3`>|8Mb1JrS;NlBaZ~|B-k1_uMkb|Rvr{Wb# zqcm~z3}afWoza&w>I$hrXL#Qp2AG4576`Q#K4NIjgh1d}rPv=oj));>>F{bA<;D3| zQl^$Zu98qlp8!Kka`TLK>o%vke*^^pqtL+=qE1|r1^&UWBBK6@n&fP(;R<|1lIigd z63JM9&f?Zu4!)T!t|@^yd_izUba0eQRUa?Z>%q>(?hUM|xIq_9t(=Oc@FH$ySqDyF z5af04C0|DIP`8?K6%rTaMFA_$-NkzLLmt+s`Ok_DIchE$8Y1HM17Q|7WvI(yVqPci zFv6hUa0WI5uOGq|0Y)V@DsZP?z62Hw<*zZ22GMPAhe%6Y3G+fddXE_2n0%<9FaZAm z4l@4ZXl=LM>dOFn4-xNuvafId01g@eUXU>>LFdQ(#!zc({{U8HPF{S$Zwc#9DtjPu z@ac_~72O+=7F`ZGws7v6tF%M-e_I<8?~94}LR)CEU6ZwXj;@$xta`Ow}w%}S1z7&6k19YM?~4Jyb*Djo-Z zB{3-_`-a3;+yO=SxN?B>=BNS_db@--!uTbm%PRl`tSrjnz^>v(s7G$*B&P~d3SD<8 ztK-Y5`y;%eTC-vTR}~5qAZbqqPQQM9iUh^~0RHAG&3?llbKVay#cX;;?F0Tp6V>aRV{l*FCmM*=4L z6(eFj%~5D{1iLeikTfXumkdFw2AvZb@hF9_Sab+a2nACa2%yIzokH#8qzeZ6^o0T| zU=Sl(X$~tU))q#T`6l>4jzi9IC}WJiYakz$Xvdb8JD3ksxMCt*e+mBpg)}QQT;fW^ z;M2rbSG}XCtblh*1WiL`t^16alE}8a6L`Dv9*U%u&u>Jjp}3%nq;9Bp9PJP;^~Ant zBjwiTxGbZHF5BWHA0*l1>E;pPV)l6v5#Vy1%DStJNQ|#oeya{tc4732g4a)jFKTQu z62E1{4W0hWo(vjt*vIIdbSl+YN&(OwQ9J5qzaBp{N@N&H4?Vd zAH$r_pgfUk6(|P-V*QY7Vmi)_TIXf3QUrLvBjQn-ydY9KN)&n;D~dvTJ)3ucO~#J! zH8l8$K|9bn7a6=l%TaK=;|BmFZ-r_-CFjDnifLvWUjqFkdN-A4y5S-}uZ2Ikqc<(|sT0W0F>V#}#muHs>`_mO@o z>a_1KW4}ad!B}T3DJA;64pCg(Zx>bO9CrvBLf^(Mz`MNJW77g)Q0ZJ%tP6b^MC$!# z9&iOdK`a#@3iQcIK<}JW>qK;wRBIXtH|pZ0S?e{sj<;emfQmI+lcT8kL~M3&z zV5Lk0vbQ?vo`|%|Ddilps5Jfa1#cF3 zY>qH_H^>nnWpAMfQ+clfTZk7^sjfJ${jjRxzNehFRz4+in8FBVQDTgDfPm3L5+>6a zVRkCGmes{7t4A0<*PIIGEGeL*)bSqES5(%9uw_pyf210db~PI+jJv2#!FL;ZAuI_^ zRi}AsRdJw;6eELTcKjpdyvJmA;`#RqIT*w!CxD8H8lVOFDVw`mIq^5(g$|k2sI6Tn zp+sGUex0kPE`pvGKuW8r+sgw(*s9htxtEc}J;i@kMqB2B22hF^^;l%Ub+~$ij>Y!F zjjXS!#20~s!j4>E&aAgJisEvCR@&0If$X+S5ily{=Ah75zb zZ)e#~SEAo78pdghwG{c4v%=3P*X>+Xh&>22-mYUb?;1~RxB;mNwHR*8^OzrHtN}vW5d2 z-5A|+I_fEqzb3z8yauQ;Eb?D?5#g9-84E;ej6jrD4+aT)F@5tjpSWwwU-@T2N+$}} z#`PE`g4yo2Qc+v5xj2Vgru{5-a^XHDSX6q=)KHaRQh^d_>b@C*bAKLEtf&%(bX^!Y zgX!b@5mE@9@jeq;$3u@X9~W!mU?3p<&)O!EyIIyRiKkn20!_4hiEIiS;&yzTk;zkI zsOarf#!@azWkRUBD5Or33M#$`eF{Vq!09PUcj7vtWy~g?(a1t5|x7=3~7N1rB z0LiO;pcDT9;LUQ*FT@wf5d_HY7fjf4X|HgvGvU7cTuWa>++y}Pg)g)1EHDG|GScaX zv}H$1!u-PHu*X`O1qn^C)JPzeNiAO^mb%-|OVt)cHcLU5-GP{k@<(fnk~-6NaP z1?KucLKOj@dS%zR=yDKL(Cpwkt}Tgiw(EwVDr0=>pATu2xnZ`vLs#|yHCq*^FHKL9 zP$aHBDlR{r2>Y%*VVC8z6&!tv7YB5rY54pTJf8v(l=X+g-M-kI9|3GUuYF%13@NCL zsP-Ld5z*1W9w>?d&mM}$SwNMRY#_aNkJ!|5QklOhQ`B~D8*tRhu7IuqAV3vDiy@io zT-m>p72h1CKHEmU{y-I@!2C5cqs*&_3WKU@1-%wtm%g}_1ZF-r!C5Ewu z=;c#@<#|lXVgL&#Dnbqnn+`fh;|FGH8H;7fv#R}NQV+yn* zc`?V{fN@rK%8mvb-hDGIU{Gi{{Glj4S;AFq5O5xrw*`JB(W(9$%tLJ(?On{&W%ir$ zmZxMReuh$(tx&mAI{yIYP)tiegU!_9nPb{u=FgZ)vwo&UVpx4euL}d%!He?2XQH{{VWIM)~Q!BU`;gc@h5r^iKjj)(Y=s_M5b?30m8bm&e!sb|txbAW{q$ zQ;MgJE6kuZts&H#qzJ~T92_<+gH52zT^1-EL!sM5d{*yDdu$B&zqdNnKu!V%Dg2a(XC!2#zZa(!$y#zkYTT} zRO|}7uxC#Yq_qiG7*A$KZE1!<9#iidL#` zZB)eJTR3zV4IvK_2Lh&phN8RqL%Fzfm&4QQ90e+irC?dx{{HI2kt{{$kmtl=h zDNo?cknoH>7c2W#j|Vy$F&&!-#6aWXCGBCVXxikoE(Ll4zy;@fP9cQd&=?>;As|tt zeL=a%Ted6YRUk0ATGh!O12pQy1RDqr{EB|XEuuf^HW$D&*+lnbUcW4O0%BhLGUG8fC^Y6r%}zwHyVc3O&ycf zRS*w`@Imrd(jR3oPlOhzaC0ips+9Qbg=pM`d8vx81q8RiY`EB}(-kIsPV{RRGoif8iG)$EyU|Dy4LP$?`1bY zT7Kh06NhP<_H_lry}^bBwf9XKMax@QMX`2e;BAbcWeG)8G4VtzM-ghJAaS}#rwzha zx{H96fe{qPr96_RvV=%=_-_e;qP=SIl-V^@;Aa8EzLmVR6m14m;G+uImSZJZ!nGiZ z8<)NyZ4-l;Js1ZJp}s`Hi!Jc3%wChQX)l_Tpfc($%Fq{4*W4wbjRQ+P0)dNw<&{`f0+;9zsLYl|a0mUpBa)AgXR*X!Z%dD?PpNRP z6UCu_QJ@_i%)ejwtOsgub$SuQFN4b;b0ny}u+5D2Is`u)7M?6nKlYA?$;D@poNrDYPR1)S!=ky!tAP^+idx z@7%eN8m~z7j8KmBXVkWu5>0@kYB{128S%Z=V3ibrpvWB-VwDQ^J|TNzk#-jVBg6ZO zU!uKYiuc1b5g;o@95n{T6rL^5XG$duv4W%B@G;7To6Ib?K+#EnfHbUQ+`B-sDTvGl z3fWja-SaI%<;#t-?(GzmiO8O!wj5pb#<6E4F9E^xQ7mhbaFQLb49wcYF4S_z09Cl# z>j#Q`Rhgbh6Pb)3>MC;w+|ksd6$(jQ-M19uSaZp!?l>BI5;?r6;#2rTme0aL&MkWI+j`$ zqP@dFEydajeNBSv7QM?|775CU^*JF)LhUd^Cq=$s8>rchH5}Xl(dbK_<*#>0rjcd3 zU#WEM!3yeCKwg>v8u=r;33CDJQV6W4fWh!#M=@?;^2EAv%0L3ohAI1Pg;?FXs;N;- zq_&j!V9i^6!2bZaq$oJ6CCw(w%s4#?kA-YQT~u3kx3SHHpoha6%W6=H7G=GE;`99V z6s;!2; zc1u<0AwC)6iq;!gc)_9afTSMjI?@wzq7v%6h`gQv z-*T$3ODz`#UbW491oC(O!l5WUh+XJU5Ll;Rr9P-2T?{-ygl#{hNT`y!Y3z^5?)&NX zMBLUmwNP~!4QY!HAuLT#E#lc4ZEG}BFF~kjMy(z)b)cmx+sTS-{{T5yV6A6w;z_-v z>o~GAmdeUtPwlQ)#`3hNZi7y-`-#(zrKrR!&N69Tf#j99QYm(mbV23k2~tH)N7Pus z(4+qV$IJzqYG;DJ2>5Z!xYO0Mst!jlm|drCVl)7B9r>2c1?I8&fr7%bK#kX?1xS~{ z(K{69k{0z~sq+kUJOx=oTG7?~2~GV}i=ZHObU<|ZB7HybSckR#Gqy1oYWFq3t!lcK zt5o=AUlowW-I_}7AvweLP>NQXg+O$Md=5+Hjx^ym=!O)hX^laP=jAhSeP4ubYDT~h zGOR9lg7jAA;-vJ0OC7z&G`A5}s*UvxC@MP~<58$S*$wQ>pt!o7nkP6~ez2xk9Z@hBsdoXcde4pF7Xqu#j2MHbhG1tb@)WMXsBiz!;|@fZm8_{4UsCi33&utQ&Awyq*{cp-ooo`ey@Ly=SY~@(;495Y4U*T#Oyex(_A<_R+$it#MKggh6F zK}{28Hol|Tz?mz3`&T6}Be*;Ygx`>ZXcP=HY^w|hPl;#7qri1N%UUk?#k0G_ez%#n zR4dGT5lMc#VO%DuiqbpTC(y+=P_Nm2M9ABOp<)&n zbje(@l+5!m7|%4jF{@hf0ByYnu3D%)92?YZfF1oFx||~45fwsUl7siJ7^a0o{;QGQ zK|D?`MXZ`wwje9BOGH+G3wH1U0bIi4Sdbwq1Ra4nwTn$IB%0O;7J;-h->8c13i7GTl&Dkt5T!1#HJx$L zihNuzx`9N%%AAH+xCDq{GYXFb!*H64OS!LTOO{|TT$_7*MS@aDiozLg5FB)se|H@r z(E=^^5ayFT5X307UEFY6?jwL`7TK}%mYs+Xnw59za@~vL)LUNSHEp>@V`A}QAzxyu zHS$Y<#}&J_s~kp)zx-lM9`ez`$DqVSQ51%LW{O_>grp#ga-$vKTUo{l9%ucIZ6F}; z%w7;h04e4!sIH#d^2$44Qn>T(S=(ukncSE!IHY4l;jkuD9F8%P^gSALouukRkG}-6 zXo2q7RY|Hwbou}UfsF*fD zm*j|0&k8Tr)V6S79A8rwmT`alcNK71v^jF9y71am6ti^T;kG7bymG?TV-s2_)m&;` z8^Y9BqFz?M_6KbZnh+JC9k(wPe}r^aqyGT(0#iB_FJM13s+DtM1zU5J7o{&TfRHOi68*q^A7RgIX5Thd-9P3icv^mLeD&1nAbh#aQjn?#s2l$P8EJ$S&5Mrj=-Lb%HPMdSVr*-{c!3yC#(ErK zD9cRGhA7iO{@jAAPU$_wkhjq|_=&l5mt8LXjJvYhIGt5-zf%Ag$8jsGD&x#p2@@JZ zrB!POBFh|-oJ})mTLF|>l;8-RtM=yd`zX$@vM3T3h0$jD1iobK;K>l%0QXq3O3j3O z0#!zkh5Ik`h{Km~Hxhy73eSg|6|p1XMn;-)7C{sTmxC}O-rNwluVyjNh>WQ2qju2> zshb;X{GZLAP})Juk=Dnu(?QnV@LDlKs$O2uv+Fj{`*q6)hff3G$@}s!LJ%;v52n{UDvhB#s=U z+eJiLs0}ohRzBd`>_WldL^TUT&};V?8+@ECN?YHU)5-ZO&v1GO!&J2F8Kfmb)7o!W z(K2CoI4&p?N;wi>WD6}^^BxQF1aQGAX_ETnq!C3+;cID<*1NC5K>)PpZtbSc?dnm! zJj`=12p+*!x0f;ko1`Qx*io(;ga#ntLhQGRQQn#z4a8z1 zz)YGegp^Qf(hpX~=Npk|t(ijgYdKVVk0!gorM)a>0dSTLOL~nK1O7u~6)TS^iwHZ> zeO34)ZMR3B%s}^`@unhb_KluJDZe}8mK-W-`=_PiSDmnj_EC*@OJT8aq|VbP^AUIP zuGE^rg5Qrn1oK0sUcX2|nN0b$h_2M+9J}p}d^RiQnT|S&a)x<~o2C4@YQdztLIUaY zH%g(ucBn7*=4o>6`8-ap%zhc=`?v`(JEj5yz*i?smEA>$-~WOPOw#_AJKRuTwB!egYAw9f^1_bt}`6 z4N@rPjj&vckgf@`hfW2HNNUOJ)Y-WaU86l$)Exq&tn_gdo7oO8%FccLYySY!J%16S z=!`0jF~bNnLo)KD_rH3r8CT+Raq!AfM3ao`A0JEmM zls4(`^D3C;#4PNi=OS+CLxD>mMAg=Zl~S2p-w@q5^>sr1ig%e_E)vgCZUn0T0Ae9z z9}h|SjQFm=(`XtCu5SQV5JwOAU=JFWK0zA&WmIas%3hV)c&uCaYvz&-0f9Q5Hc@kb z(p?JGz$6pAiZtG5z?^|+e&DPfU0kY-G-?~f)}y{l4D=R{mr=hAbJR`B;YK%;?uc7r z6dqYmS2VMPyn2*S7xZz*9ML>&;=Dweu6UU$$}nKwE1>fLa=6H9L9u9O{U@ZUm2N3r zPp!WPOtP;V)Wja`N~>6D^z#m+0IG%dA_zjD;Gmp~?$>|Frxl1*I4SUecfaf*@&f0I zjWXgZSVdAHFz8lpkx_k0Yhh6*m(5FMm<~7OD66c(Dz`+_8kJ49WEKF5;vS$q9rC9Q zH=}%rs73;7Rz-2yu&U_(MPXuAz)+1uAPqz>Y_9uR(VibIW}HP{Rv!?zZKuTCXKqxt zZubTG1ZWg*$x{hbxÿ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%0A8p_3+(i7 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%0DL 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,Gc#c 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 AVE_OC literal 0 HcmV?d00001 diff --git a/docs/static_site/src/assets/img/chip.svg b/docs/static_site/src/assets/img/chip.svg new file mode 100644 index 000000000000..c21a6a5e1271 --- /dev/null +++ b/docs/static_site/src/assets/img/chip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static_site/src/assets/img/circuit.svg b/docs/static_site/src/assets/img/circuit.svg new file mode 100644 index 000000000000..4ce59a3ea024 --- /dev/null +++ b/docs/static_site/src/assets/img/circuit.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/clipboard.svg b/docs/static_site/src/assets/img/clipboard.svg new file mode 100644 index 000000000000..0a4abf1377e9 --- /dev/null +++ b/docs/static_site/src/assets/img/clipboard.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/clojure_logo.svg b/docs/static_site/src/assets/img/clojure_logo.svg new file mode 100644 index 000000000000..152078cc7e84 --- /dev/null +++ b/docs/static_site/src/assets/img/clojure_logo.svg @@ -0,0 +1,50 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/coach_logo.png b/docs/static_site/src/assets/img/coach_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1591da5afa1262fad59ba8390da88d81128dc7f5 GIT binary patch literal 41911 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/cpp_logo.svg b/docs/static_site/src/assets/img/cpp_logo.svg new file mode 100644 index 000000000000..4c4b9645c261 --- /dev/null +++ b/docs/static_site/src/assets/img/cpp_logo.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/front.jpg b/docs/static_site/src/assets/img/front.jpg new file mode 100644 index 0000000000000000000000000000000000000000..87fc8989f6bb4f5f473e8d7e3f1fdefd7be2bc7c GIT binary patch literal 280416 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 z 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

Uii1$)UH;ksy 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 X61#|IR?g>t2l>fLDM?m}8~Xnr?lh + + + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/julia_logo.svg b/docs/static_site/src/assets/img/julia_logo.svg new file mode 100644 index 000000000000..c608baf05159 --- /dev/null +++ b/docs/static_site/src/assets/img/julia_logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/keras.png b/docs/static_site/src/assets/img/keras.png new file mode 100644 index 0000000000000000000000000000000000000000..5e4ff02984037924b367fc9eb5df7563c2564188 GIT binary patch literal 13784 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 literal 0 HcmV?d00001 diff --git a/docs/static_site/src/assets/img/line-graph.svg b/docs/static_site/src/assets/img/line-graph.svg new file mode 100644 index 000000000000..f7ab812ee4be --- /dev/null +++ b/docs/static_site/src/assets/img/line-graph.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/logos.png b/docs/static_site/src/assets/img/logos.png new file mode 100644 index 0000000000000000000000000000000000000000..e2d58a0ef3d3fa8d10181d29feb2c27a3e1c0fc9 GIT binary patch literal 493103 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�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 z zKc=di$guCLh5K<6(lYX5iR;?z+x$zur+d?b#SJldl@X)@ zH87Cq{q+)&J%Vs)`c@Xn%5bRf>T>!8k3$KQ))7ns8gn$rK@U)#mANo9B#Gt&Xn-PL))%uam=fzhURk z!`aRXyhzZXmM)>LUGh~?RmJU72>f3qa`yK7@|NH6^VEhX-0H+h1;}^TcbY#-N9TF` zm{@(u&NT|Fd%jbr|KYW&A_mI5&(6;p2_tQe z{vW{NU$K|37ffwDisW3MMMk2N&*py&OXk+}Ui5AAU??0o)Wxq3pF76XD!1K^LR=bp zT~jA}Vz`H28Cn)uME2Zs3AHO@ZjIuEk#;{^CsI-UmJ%C0^k09N^ndBW@S3&1JP86( zKQbRbLNQPUluU8?$5a#Hu(7VQy%@D1Nix>usZmPV93|!V$2sLPi4PrQ;n0w#x@;xZ zsS#m$T5TO~K;~E!R-yb(4Xm{znzrEskYrs4bFj4$EFH#@qKwU)grCk$RhY(GeBR$F zJIVK}y8bHob){AWK{Vot0WDbtnR6^cpaR?TxI zXE|8#*k-eJD@loJgglgC?M?y?f*z$*=hi1cNaSseJtyv15Q@AryK?$eAAeqbtWxy8l$3l$)nWs65ba6c3Tna1nk50Bg{M;n zje#L+U0Dj$b?Tb)^n8kHOR$)>C0{h{2||0uZrRSt!FS_=rN^(ggHYe#S`Cyb>nd7P zcj2wYcu&=+{MTB(mp5^R8db&ft2jErt!V)F?78Y<6&d5b_SU;kk25*c!#8xDKF8`9 zvIuC|n5C1p#E*IB#n?ITF1@4@>}5Ab0(hZVhII-n5(cFn@uYf8Z+Qrav`Sq48G2!- z{_s63b$t|I)mm_4_x2N0J+k-_`CXR(^gTLczW@AxNXWVSr&-m^>jTGb+8s7PZF-VS z)*AIA7mad!bRgTjeWT}sqG11X`v9lA#s8t2_e1d>v=O-8Ee}TcUu*&NI~J zjOJ9ZtDUXtUTDL=89EI%vXN#Av#H8Zl=CMAR|=QY!NFDD##wm7uiieXs^VgFU}@{b zw%8}j9Pu4n;zUaV<6uzrv@PVD%)^wpe*n_|`f*Quu5+x&BN$P8Tuw4k=_IlvV~OBR z4lvT0d&<8VOZ0Vk^=?9fQ&mi&qfYj0KjApVE*zx9EU9X9#WQ1Ob2wPvg2PWX(_r2JQTilG_!tDOgAAUcnpsU~&)?>RAtvGOmW1-C z^X}B=aQ2k1=Op`Nb3x5<)>O)OpaS~aXK%9NSKQ_6WU9)L4M9Hewb>U1&#(lElXbU` zPJo*>8m=1-=e4toXFB8e(LuK#EjY*66-e^>_w{MMj6}CNj>W5LFGLO1@H>T09n*9p zl1W&Aa?=cOZTvd15;j<$*clydorC&E4Dr8xoM&d2oXW%^eEm#b$mSKC7N{2f&MbU# zlLyb%%=gFB&~+t`=!#^J@bVKEjE&uyQegOP*`vF6u8> zUPOS2f4Q7moO}SNQk3Kb4emPx9?V@Kz+>w0*;`1zBzWF0^CVv4tz`C+pV{sAjyqX| zE05bfcu^;GFTm4Jc^s2W`6r?DpEvV=X(zSfEpzaPvMN1pdcbV|->LSM!nfil{&uzz z+>e}f0a>KdQf}D*JC$6a8!{$i$p)nl;Zs&|BdrAw7HEp@l|(vuqlh z3l$p;ej-}N6MkvLET@$k{)uZ@`>v##nIr6;cC)cACOk&m-rClMOHV@k!{#qQ5G6D? z{4nU*kiX!!B%U3Z#TgB&-E6F2FT8pp`1=RF9!32jQpuNv{{eXZ`!#Yam|xllS;nrO z*K$U&f5pf@i>s?r*CI;avsuu+HKrtSzv!mqXIzI_(gk;811|8_WNR-+fePEiP3N^6 z1JE)(GNpzhlg(5zNF2OfoYAItSKeDNEw(%nDAn2nF@%d{-VQs_bO)%fT)y-Ikd!7R=_4 zwVs*1qV{X8<986;Vv1iJKt7sSp9)4`#{4n1!Mv3 z&;1e9x8TtFJ6s)x4d;v9$nqGvGxJ=vJmahMZ0_i4vSQxQ`a)RnaHw0B3REK`g}RxY z=3uXq^sm6^f3;r!R>G?h@DUD+e@LZvd*2%nac8^dz15_V4Q2xtGPP#U3X#!lJtnUq zhkX4Un}OCn+pKmKxZBFo$|L*B>&F)qt|pI<%T^6(PLK8TxmKLRlhb-qs=K8G({CF= zV*Zrad8*nBCd%~>42Q1Oq3m4szX;{!?n10nywFC9Z%D?17}dUXUS!lygSF!eyNxf%xn+Z4c>A8GMbgq_S$fI z@w$%76dvFfiJqsV<$JHN+DRst&+OMW0<5bBa293vU^+#eonz}1=$Fsde8<6AAiX)n ziaeXn7J0p-0z*QwR$Hf9;8Y)R#48=Q7y(`;sjnAJh)Z^c`Z?NW#3X#}uBZsIE$Ypb zIc`}vQ1`^dxJJybBJ#{e{A$m?{|0pb$136ud;Meg z<{kN)f6FGd;4L$PBmlnYc~w!;j4WL&ay+bM~RmVmNWkoA6d{l8`7KUU{Nm|3b9TvTBxr+>6+ zy@YQau6SE7BXG|~*WOvTRYR+>CG7!yDy7#$Jm?CNcv%9=qC2JxBK%}_x%g1*X1$X! z<6Fd(qV-~XRPm}>wUbjtd&8Ys9*f`VGiy<`Em5kw{|SHp%Wh5TMx5j|n(7|h{M&5J zJ>}VK_c7PW;vyx6KdnwlmBCAMse2lIeFKaGe$3Q0YB&17ZTjD*k<0tHaby0Vcl=H5 zoDVHm!||NNwF91f!aH4xsHuySxJT<+e7c)A2Bh~2npc&QnSSx>s7AOSyw^`;x4j{u zDC@#KjUZh{Y@4RCdZ(JkjAKTTH=*kx*yz^o?4?TU$PkZL2%xuT{V9MW=2`jx?&)*4 zSVN%i#pz>d{g18v-(v2s|F%7#aQRh@8GG2Ts=m$ zrbyYUJY~PxskGp(onMhNCkZ&IHSdun4zEhzx*HQqPbM@wL9UY@6eyC?*?>nao}?-x z4tJ?i^`Oo>s?VRCU9b&}&f~v}wX+fvGo~fckB#|s*f4V(B>*1iRsfWSHaO1A zLYAtBd;|0flC37}Xt9Ifek9V=MY*CLVOAVu2Og>y9+T=Eyp)W>xg-V=OFe679|`g? zHw8auxEAm|ITTGlX3Jms3>-VPypH%8a<>`#MLs?tiL2MBuC1v|dvm|iHA;YWACo$P zrS_h}na;fY1h>GN3h-t`|G)N{_(^N|nhIUkEj#M?F@Lk%+Po1-0uO1(7iihD8pfKD zq_Wrl6tG@7UZ}{l))gI!vq^I1Znb$?lF$#!tdC!|#<){cBGF_;$(F|2jxQF!Taai| zWzsK?1fj1k8$kTUucsja1B0U9GRxY8R?vlYGa0>}-mp6@F*CGQf0N7&DV?^B5uu?xdOifcJo2IT_rCVx8SD=LC^k8z6a|MWa{XTo z%HK#M|3n1gPUJo1<2vjUOcPk2rz!O>a;P{ZMVr_B<RvE0G5;%r6y0?B_%~6?76DXnO*wwjo#Y-ow}-l)DUb5CqyP&mXx({tQRH#{7hOeFU9r#JDzHup~8gS~cx@+~sWftr$c8(R0Rls95n}ra` zPK`3(6tVKJq2@2XAZr~L(H{TvPyMI*9#=FH?fhLuF8(k-R+9+#2;sCcN%JgEc0FL9 zhf-O1fc2g1%fu>C)rr@9hDK+)%6%NLvr7{yMNVV8xc(1X2;R{6P#%yE$WB>#c76{)Hu+~Dr=3UimigLAQO*F6+( z$P9d;y#nJ17;h-1dYh2_NZm%8&>HJ^4uvcm*k3FzQwxq3TT^aG@qY8G*+=9f==^`T zBLDW4&I!!1uaLUJ!VgbGoRsz(eI5e$>~^*Dnu)BN%~qaHUed-z7e6iYgUm{FHsjJq zui-BAL)*q+`cG2=KX!fB2r|rGERN9-Kt=|>wr!SeOsbC^q3*nt46ZLp-vp z@TSB|3Vz9{?QYv83i|6A_dQ+p0jLGN2ysk5XsuNzWrx@StzTHd@WT8sTvS`Dp90wk zpg^S-a~O<*jRF+1gmZ9x1u|H;(boEf3|QW@@ue?bGs%0#|Ia;FkKz|@qnLfI*uZCklQb>~o0VtNj9=_A#O^gj0uaEgaSk78|5 zF&AM+JyK)go0#K*5|n*IL-03n$Fa+u2g4J zW8GlrOIMrnuETXhawTC%$j1GRQVMQxO(pKxOar>mnc)lt&Q7<=2N%G#kEYcKC1!uJ zJURh^*ymGL_NHz9=1H1Tl|K7~{2kYkFvi3i(=Aw93 zJYUbUO~|DDZHNC^ZvDR<;(!11ocP}$1t++>YtX@+!3TGDhs*B0ckkJA z&U^O#1NO(~nd#~NR8?11SJzY#Qsbx3OZOMNf{6D!Ze_tGlA|#AHaQLSM=l`&K{x^(3ZOji`<$6E;{|8U{|G@Yt zo4UcL$i-0}{;zSBec;kwW4nR>?@9C@=pBas;T1U7{nq&(-Le0TOY`Fh20TjnZS=2M zn+fy5+C;>08?ygTN%_AY)Y5(6(riX}75kU0-IDlVZKj=-=Etq@f57nH>8LP`4_viv z<_FmSnzjEH*?)`d-|n;jEwcaCvj5hyANe-_*0TS04F2sHko?=175TSg@NdW9BUbYN z=lB))J_&aJOTxKwt)7I0#6-ORXN1Xl=YrK&P7PBZ=Aw?&9m?~2O7BDOqWk{-ehhSU zy(p>ZK`=lvBI0|=cNmzNmAC!&(LZ$zH5iysPk}dqS`nEc-9ot_?52K6+aq9D>oteLGIf zr-A=zkPRwCdP|igx4fsu|C#MSec>OEPPHgu@-hPo%x^J_V)}^`Rl00il`SgEZFtz0 zB-F(6@1E#Y)BTNsabO$Be+Q@?&!d%$*%@;^-WcWR;i4Uw#; z_`T%6K%^{=!h@9&wTCFkYOk7uH9tp5d;?^3w}kabsAn>%J=ca$VQuNOU--bdst>LI~gMhS1p^UQvV|Gc_fm%q`; zrR7ldU-5VpFYrWweMnFIz*t(%+pM1awxGabb6h6_MV5QloHCUUYuZ9FyQzfvUYX1QFzeSD-4Z>ouV4`TCzP|~p{P;?hZFnzdA#{tFxJs(O zv<`N^?r3Cq92jAMdVqEidGIT#t}ZX8Ua}!AK3UH~$WQe@Ve=r#4L@FX&T(Vr{1+mO zP56nla+JL42l>|JX!+IN9P7V+v_y*s>_A?bOr)e~$^)H{|m3cG1vRqa-H2Cg2e+8T~uezIi5(uY3B6C|ZEV(r_oD>pa!=L9~u*2A$ zAu4fWw_GznnZY#?Mldx-Ig`=r>FMKZA5{LweH*wOC6cwg`k20W-!X)fFe3Zc8lH0z zQer#)7_JTrkRskpQN-VbZF0A#G?{so3hBFA{?ifL*#2yMs};G6=TDYN?_c`If9C$6 zD|SV1%oTk4>aK&l#=e^qBkeE*>wx}kVtBK+2X;bmrKi;!y=9#>`}PT=m#o0UU!7nq zymUx7^^5C>_fBzvYsQxv2D4kzvus_?kmpmanoge(^T^Yk4C9G02%Lp5pI(O@>Y65Y1h67||b1sSfwZM;af3PEnoQxi$|= zKNEg2UOS^vJ_@<_2Zt!-5XP1)y26C>L=pumOqUoNNB|3SldC0vT2#**g_YY^8~T0y zhd}A zsG}o_hW*xN69$p12F4U6O98SJZHMRKnVzBnE?$)j)o;xp zvbgxP(4kqENrj7h*B_z$m{@Lf*nx`$b`pbT<(^fQIsCr(w&pP|qO-XFJ<)Te$VfCl zT6de`lF_ax5(yE-0>FlWZf*BfN(EfR*qtbS0WXC~H1pg4XL0mtAY&v3Hk#er zSpw!YSK0hovRk*);pda8v_9BVBlf`s&2Q@F+}t#1OQW8-isygrOpoXc8E~3no1_l! z=rnuXI(QE$@fdkAaPvSE`8a&HMV;0VamL=Gt=;Y&5a%!maYfzie-MrMMkVUdK$$Yf z0*(2mY1Ul9WkzY7)lo8=E0#Eq2SZZMH(k=9u`;fwQWyMn$O#|wf#A!=(ip}vYW#;5;iuW!5{GJ*aWBPd>}?} z)|eP!7rgs}B4mgq*`G;Falm|NdHLWtk;Buk|F~`cMvDzq5|oUz=;oN2wnSG?(^C3! zGnFZaT}|wFyLGgxyoSoej}d<<`9O}LfBpjJU$GWqX|}sHjjXk8_Z6M$@z zGnB5}_UTb-Y)QRhoj5u}+MNM=pk@e?qhO);wTJcB<&ccP4G7NcLeTH2>63QTHjggn zotU1HWBaXHBvAbS?LcJ(m`uUcpl%O>-qqxl8h<02iP`ouc%`pB*Z2BH3XBIfA953T zg)-Ie?hV81lMR}u^3>XyrQ}&?!y2_{yVL|t+#PMeEzLzGI@AtXjI0qm)LJy9HaD2+ z8)wY2F<9y-`Lh`W0x5f24O~ZA-!!piq`UzIjhUu`-o^vlP-*p6oas6eLHHVr>$=)@ ziK+ZoF`4RqY~C|2`o+3GzB7oBo7UH!%kWJUDaQw%ZCqb7$YFbXyVvkjpaYf6e_Q5K zFLMaExK%H4kZKA_>T<~GQwNb1@6LvfqlJW6qc-9~!?;G)=d{r8UWzl{ zgzgP!?rZh4%Zna&-pZSDQH=({#Sc9R z_CqubP)`Ik9(Emd84|%*Q5CkG<<~ZGhV4LLZx(7)*I~CnanDk_cAHk|#rx7CXNod_q! zHq>3@84L09I{jP0VDV;A_}WPdJ5mwc@SjF6!TY=^z#wmDr60@=6tT!zCUgAkB6gJ; z@prYAx@01ob<86o+YiT-^Hbp0gqd}wk0$Khm3`T!(=6-)P)Aew4hGyK!?cTq+{?vh zEpYFEttOYwpL4;-(|%Jh`VJr7^b^{eiX;LY?wHx02rOxv{gPO|qZko>aEtSp2fg{g zR{Nik+)(?S|62=yUzj`+YXi$umad#7c21QsUb~r>S3w70S-qwuqmMlbY|3hH5(Q7I zRpb-UyEr0q{=ufN4w$2XtUE+HzX<>8pEMhvb{f2zDL_XvC70cqB)m-E z;7_m5S`Bnr?`ULGQ!9kDxJyaIJH3&$8Ezb)~YO=cCE6(~9v$Gtux@EG~QA0gl(CTjYYA~q}EtB@<(qLj%0 z=!K?=?wNxXM@@{2a?&+q&YQpW46sxG=^y(w?Yn1An}IiVB`0x*BU!uf!bUkU8hBoN=6DV+XA%w8;!(2d!j=|1?$v1ds3QS*)~YhbNT(DZ zgRFP}3BhA6tdk80=P~Nuyy)3g@>jj1BBNhi02|R(Oo#fY)8UVCvVJh-p=s)uPqo>? zScxH7(la=~<%)`O%9zHf@hfUshu^i;NQ(sQ6Ook8nwoqruE*b`W%yzwEOV8BK33P< zQ2}d|kp5*&t9UX5h#9d&o0b zC)}IaK)yCv@`!}Bp4itDbF~Df>u6061&t`y5&Il?@X7vSaokU3VZ7G;=8$5f^uTalFrc6q3xV3Z)g+*Zjr0Sm&|~SAHM5*PG_r~RCl`fvL66SjA3>X zk%xyz#E1}~%-0#V!!MY2gfn@to%5u1Ea`e%T3Xp=YzbRbEmr=FqTe3+-8DM;t7TiC zcX~4j`R_6DyGeW z`|r)h%{I$^j>1=N3Qd_&UaH}Sq#h0*791uAi37fu6?Mr zPBWN(s@-q4+H}3L`uv#vwM~8;2&%e#)+hLS$EKj?y`YB$SnrgFh052}DgGLwZKHQT zeYbEx!%7kszMztGJx`n-IKKIWlx%O z6H79QK=(0#^%xrI_OZqFXdJWr>pti|A8YkVASDi(%}24uUXE;e+FQyvOQzNJ?HCLJ zukHxA0x-8n&aVY((bBmJi9Y&!d)xXpqE>v+0D(-}3^Pw)w~m+E#z@W&rG4ybNnE4smXq@1cLxaw-xS=~7i?+fZ*t@;zE`Z&+Ms%5maK zK&Z{z)k1tqHI=&SCEmnyhO~wP(-wL5x;Bx{S^;_a1Nm5RNJ?~@b!m61Z{`ILmws4{6`h&#hGwCwO>NXt=O8 zOD=y|>)%mdyK0v~P5;Q^5|q!7bm~X4tDSB>Jr_`$A;zC-qKH^PaBg-=9o zR>V>ts%AnK*UO%hP72#w3(~w>AzTNyB1sM$>2dkZr-aNWa@V36#h%>s@5m+~x zSl{wE`L6YBa%bUt*P7RLx2wfHmH%0bZCpun5#7fqF`c% zR2j4zzJ1gGqO~{zgr{N>oS-VYvPsuGh=6#--xSp~nxyi6ommsVTwTbRvUZ0N-`h2j zwqo%2zqEO&V}R*w=jgjItfW_OHdB#m=3Udfa;H%Nh5YXxd>&e0jwh=1(K7v!3iYnttj+WnKX#*xQhrD^lmRT6^}W zGwd*0^rxQL&X$blx+-A2Yc0h{&bt#T8;!Jkur?0_CFSX~+U6acoYV0@zP>smjZt*y!^!m)_pyHTp5)mk&Q z?)_OfmHlfZ?Pl`yyo(a8SSAQ8_3*hqIF>QCZ?nC0%OO?od7Zis^K*N?8juSnnn%G@ zUW;OC?YYc8)al&aJl3cQrgFs{rQPq{=T#%b_*GuC@Z+Uy9g`$JYeW>eHZQNARmptCFNeWmacG(@YdN_af zacc^1$;3ExGIU}hNwLM_U!qdjnWOv@;YBU2HL_b1BC8SQ6#Mp-Ip!!B5UXH>58rUM zciH!s1II(&eimPT!`J?`gtE}jX0743bo4IPA61EWQ|58YFasHMcZo^D+t>vV57B_> zkz!9$D)x&RRl{qXF?#2zAlhSr!7g8s=!b>hQ2~VBe5h+HfZY6UXlRN2yBV8`S0H{1 z?{-m4b_dGitSq^*AIu3qN^rG|Vk(xZ*w9I}<@=b{Y82osQ{NBqIQ{QoAQ31O9#<}0 zX&!0!Ce@~-Evh$%y=}zz6a@mb5#AXyt`PKRBxzF3B!2Ul5nik9pSM%Z(t0gg60U2C zrajUnjR8#2YI}LDksOzlhNWbfB#Z>9=d;od2z!XeIL$dd!{%aWXtOF{)6{`~o6Dn0<4eV88=*AMH}r*V6xJgtX ztBZBEeY|#l3O(H2zj`^l2>aGq5f9$khs5UK{qVLGbH3g93oSUB-jG$kpW1Tcrqzw3 z`dl<`I$zcOg%D1bVWBHaZ+kREFValhSGt+a?3;K0?fLP&=?W=c^Uihq$YZ3DDKdwH za;FP&Vmba1SP-%G?R2xl?$fFjLHxGp)nSaZRkHT0=4AM=wb$k~rS?~`>?rK&=qpvu z;+@%VT%;LnjcS=nVbb3l`NddkKFZ768gca)P4CnE7!*793}xLa7(cz!!(NpG-6^uOL3%l+(mb#OOEQ$hX$;pwy);)@teX^ix;YqUw_Fv6s zve7CTb@OBB1jUoP9QNMP#(H}6)PPTAbW?@)nZ^tQvrIu=yvmAaD}-CqH;$RX=R@4H z=G^O*5iQ_}(Qyrm+f5ByC$nqK{tzNcm8{;HD5eXCy-2LJwm;2k&=2k>-=!+`ddscV zytwQ{W|r0B=iOx{hIQw-XtlP)Jgz&1ooFx;-}%w^Co+NkJqFIjT3i!f2&KL*vJ$M- zA?{U=l`Is%rhA@7DdtP%j$-C-EVSm^Cnvu#x(JZUt#ICN@~9^qv(kMmMdhqKdff+p zF5mWEZ}0|a%Z}YE;h>PDfmN~6qfALo*ASE0?Nf|r#6VsP+ib*JFk;)B?h*hxhm|tu zf=)`O0aNob61E4$Uh9-~Sw4sv+8)w}!!I3~Yd5D=g^wM+$VA3PItPew%OT8(Z>0y^ z3K}cQHVcr3EGQ9w|-0|Am*CyKsBZplYDrt6_wHPDKWU?y-6jREN~#}FaZ1lr9zF) zYv@|3JkmcjJPhTz&IU87&^}?+rMi-+PS$Z=%A9Pwu&2<;dOu1%)gb#SH*b!yJm}tg zT|HT>p-7bSdLy9QTtYDYZO*du)q5K2rOu4I?aPzN8Mac=mst0BlV>sYfa_VlmzrwgZW!M9}%biFiKK@0g^BSjJEa+a%-ZWBK)F!)~W zbyk#*LY+2O>60rH!obi9{;K+O4B-Cbt;?yCIA@}>lrN3^@cgVLt=1M3`CSCv(X*kb z{bFONU#=Mx2b!97IE*|`A?RPza_i}hmPT1@f-N#!!W=(P?5I05W2iKvjkI0rB4RW# zpw}BWH=4+jnEdWwZ=I}I&M^YTJFEin`^VOJZB$Zbx@g)2?nttgS6^!+*=K;eNT*Gc zkfy=LecQwVy|K-3t?{saOH}I@#qspi>GqKF#cNfS#&dn|At=+nw9SOk@=JF(N4Q0YLs#xkr~H6=zskHRZ%fpkic}3!{rYVe z!*M&baOEQ4UWuWRR%J--ao*J|&m6aH^M~6{X2&+8umF1~Ark=SA z?C^`P7A>7?fR5?04~u!2bW*X@52K>&y(#%b8&Qbts>}}WWn8}o4`{l@TA~N-hG->Q zjVNTLTwTzsBIN>i$7~QI2=5Ps)zz}3qVDSy{oHUgjYuekAlfeax+&eeT<^RsC+KOC zb-H&J751y%>!MzgXM%Hf-V}Oce}P!q?~aj9<1=*b`VPLVNvtN;nnqCe&PziE(0HcAU!!bK410pIJ5I9Bh{zm!;1U;p{2p*P#iBg zr?XQ4W%#~Iw(rRb^y}IMHe5wsb=+&j23FhLeb(Fwdk%0d#QDhX-6sB)iKoJ{BYKsZ zjz}BZjK|j?6n`w1ZO?GJ!C*`LptNPDew4fH@c(e2A z1k>H|47pMA-IIA*d!1r^^0!-WtqO~meb6}0dpg#z0{-DZ0jHwR`}q$MHtO}#IS1S6 z4xJFDHl@ik8_|s6aFfyeqnv2$hjt?Aa^JK%?u`S1ddj>a7{k+B6BKJqj z5S7-$jGArGL3t>$Vh`i7shic&qF#?__|rwnN|G5WrloVt@Gio->0#2ik6AMGCy;G2 z2CbwxdY*&>!0PqzPn|1TdHxeUG`{81sYND~4vtb%6tW4Ut!0Ir89!t;Tq=8POt#c& zt)*cc+I+sv)0tbVU=3l97T74JI1)esW4>~nHoW$AL_gfLyEi;d0_RPAE*`BagUv*Z zi!Z1R_^Os%Fg8)^FZ!rsHQ~EF+LXO$k|6>WR>Vtblw(;>JT%f;nqYp=!+tfczKN0A z*dpDKXqE2n^1IAAoV}bGWhSTp5%EG0z4MB6vItw<^J;y%ilDAW>4YF~V#YsAvI8@82-QgSy_d*IbSy%I6>q`tM|pnd)qK&Sll7yGtOM$Q9r zwC`$JrQ&gfP)0LYOcx$^lA^$reMIiv8^{xNxUPb_kZ1QxCO=1<;!evRezT+gNnlL- zzHm`!UyJde@ZiiAI+YEZ40qID(<58GqLR%Y=956`Sc*`c%8ZkKlnT-Ol?C?Cay0mY_twZ=iLBg@<%09AIPHdU(qW$kAGFE!#FH8kV5D^qUC@N#23a4mK#N8CbAZ9=bL4C zT-3dTZms1Vp|#e4-LMTlXOR0gS0FU!Cj^Iw1!$fcW(2LcmzT?V>xhPw6|$7&Xx*7n z)b}=}4hWr6>#)T}=x6`u&!o1X&5ej;7@g)%Z+DAF#$0~}DooS+v4+_dGSsEo2sd)o}7r`ov>jMWl` zc$??5X9Qn-Z+<6lj=@Ji!DN`NsI-u<70YQAKRy&T1O@xdsT}zx1igOJz9|5 zv`Fk+oIHP9lo_bio`+`j?+iUxde$r)O*Hk2?Rv3~R=peE*Y*PT|HhebW(Q|>;`s43 zsT^;8{*8)&>Y`TbVRME2%$}IOx{bHYttRL>mnBd%2j-pSG*Hw>GYSq0)!jkbqRPZt zAo;CdYQ_@#6G7ZxJ}cqDgqC|fBHQc>ZbjbKrq3(NPFzb$3+r7%3&Z3RZW#k`U|Dw@ z;h~0qTCQ(wQFISsw12`-hqq`|7sro=F;U2{5{9C9-}vLA;9NL9P(R#Mp;x<0dC=5& z<=3h=uVp5;Kfd0uvEI7hxt*D93u?U~CSLg^!aU`1c?;p6?bWlSU}$af+WCX(zs%2$x=lXKj1EvgE2 zd`gNGqLbDGr)`8o7XNS4Z{O)0v(|ltNm*fg+(a?`1<+(=_h}}FLp}YN8;XugtK48U zvbRJTI9+FFON%Ci@A?2$;t#k;c|7r0m%|sNLova)z)(N1J~w zl^>0uipIOlZW8+z>S487vZWarWH}d}SO#ukzZ@DH3xa#}jz;&s~R9+ZekD<+I?%P!zpc`xOthBKi;P=B(*b9b{4kuvR zg;4Qex)7@Cmoss-IM_rN)@#PHH&!tKyulglcQY}m5O&Zd}} zQQC_O3Fuj_tJJ>SR*!qwdgiowynY+?{I(kAFhJLGyBw%)qa7rxb+^0dxj9@Xyr5H) zbJ>d_AoR)~EImk?8|_#tq;Ufb`O}f-H}%K~fhj5inV}^7%HeZ=7BbMyEXu}M#P{Wk z$j3JQvHe3=^<@B5#rGyjJu(2R8wtj1v7qjJAa2frn?1K|QHqLrVY5GDNDXWt7p zQ^QFwUxe_UtNqTqZ!j@3%AxI09JQXh#PlM69-5HOP`^h%lh3K7IZCKK_@`eta3QFe zFrOmx!^%N=%b7(}wOk9`9&b4^+cpxlD;Ky!V4?<4(&|}d$5zXE0sy4hUL3VKwMzSE5(_)892}L^x!hi~@?<20=V+a%oB8k3m1e6Nc0eP8 zc{)}{gtFUH*igI`8Ktzq9Ap_j=I<_}8uDL+#1~=?0>d?Ks?-?$N;i{XNFUHE(^ujz zUe=rCon9me-c2s|rP@x4Px{i1>FygaI7TYP%@aHh)*1iO67m;*x;cks_K7a3QkT)4 z<@22TNLhM*;@~J~UY$v3Av)*q34V&?$BG$We+%n`$09cYGD(~Z2Rl$SS9@<7SCr2F@mV3I^j3WaNGhm4Rido}LykSR!-vsF+~t|;O1*78E@lU7f1I|ef;r85 zziEt@orYeVVZ$>li29$D{qDqYW?UangJ1YYj7ik;%`-Pg=SJlC2a)>3gZVBOfe^+7 zo5cy<3b84)8~r?`x7d~U%X7UEs%OH8HyL)ps@#@px7$0SCJDy6K|MW8wf_)6>9M3k z-g9cV6}hsac%2_s?=G~osb8$~6Jw37JRQ?vC1Yn*$ScQuaSAr^Sf0eDz~`?0;(}fs zNKa641l!WOsi4I)mnnjnPJKX+|B=N{Jn6*`~p+RV-7Zra@O@zs%56s$&7rvy{cL>0sA%%4NIu%Q1RX zfP%uQ!|CcY<)0tKXynZ*rt{6bFD&r5CK>uXL-^^hVnKxk1#vq(lb1#&3Nt>Dc$&9I z^W>YGP{YSoBin$a?Ch$exY%Fu1&Y>_%beNHOvy~w71wyc!l>e6%Th5tT5^d@ij^xd z7jJD;KdJ1fD1@#fu5&&mIl~_~iPtmKJ>pmLYcq~LeHp37$Pm7Y8^ zh%kRX0~)b0L5CZiB$%U3JXs%S-*V{wT0Xsx$sq)+zq)7>DuKh^N5WG6J# zm0!#J8!aD!Y3u7Tfv4dMkY91QBhK^uM8Ucy?{7eMUMdXVX80qNnWye|ZzaJD6vJbEj3kJ+)6j2PT*<#-Ao%tsS_EaHqL9(el_aK!FuzD=JqFR z^EkAc2Ryg4?Y*`72)=1|;Yr63Z3{9gi?Gzye;kKakPB^J#LMLN@>3h#X1FlNy2 zKmZ&FeU-6nwfr#Uhdr!|Ic7H-pZr*Cc9a`zXWLlhX=Hs{kvo0d23lDAE@sNlH(D)= zj6ZRf9(#lO@R@~;ercWa#C-P(kSSJP$^Slu2~RAnYrQ7y&H=I$`Z~j%Ra-i#5!T}W zK59-;spMK~kvDK>-;$t-2$Eg4l7q*zCa*DI7kIr=;*nhV?U=~$e$d!nS}9LJ+C1JaHOX4Jv;3>FZ_l5#a)fot{PV^l<*&Bk`+*M1mCFDnLr3OzAx(Ldp~Twgd#+SHEq{4Ip0h=NPn+RF{W*sb zg1rd(D&+v|M&t59GoiS+xLlC-+1WfX{X$vksE?;e>Ympvb(>tYVu88-u(y*Ab^sX@ zl%sDmCU-27gd!GWwA7>8@3qq#Av57J; z;&xckJs?v2GoXQT;s#Vwm9mX@i5wY0pWFiOdkofvVeKBm;a7z%#~fZ=PLH|JWyIRS zg4{_bYID|xomswnVgh+FUt2|D=SOrQHVIZOJJz0M)Z0OywOKed0@<(F#Cvv z4VOehsu5;h*hQfyA)gAQ1Wo2VV=$IhtcGKi3obY3ktPYZ&9Jo_L>5^cXz(rR>pBKE zbRD)LJAan|*YpTpj%Fz3);4QdFUb?!+Y^7!0Q=&0R&~O4cVAH{5+e-u6w*6gDWf>} z?ISi66Kty%s^ihWJYk1xJ9kgiD#-Q?5!ws7K|aT3D{D5J-=0dG=m^|_>=;%W7E}!x zB4|xn<8yB*iRY4d!V%7VeWnJyqdxs@#+s3vRClra~o&KLx5Kp_V`&H zF08Q7^O_3r^v)TXF@JLC_QFuE(_*X3+K2|&I+NVNeS%mU_m*^zcmy)k@s{5eGgQL! zi^uRjF;v(^w%KaCTEt`?hL%La%2#5#+7!7=ss)KK zjak)`Bh!Rfxwk!uni%ZQLO~{#e*ppCuPOaG6I;xbe4ca*0IYxBkpHQ35 zE-8GZb5SutX6mXQ$7$9-q5<_x+(-Wu&QgktI9MxI;R>J1NJyZi5?m34LFH-0nynv! z^uDwPUAwIERco*|>Vdt7&9Z=PjK_W}KD*77Z|+~7ji3QBX4_`ISc8aGBBE}$LTcgR z;V{U^T4t%!$fSAr>~FHb7(!HR>`=90MOui(6m3iwNk}|@e}5}*N2@plEvfxSQP>h8 zk}Fi!gpZq0k5f69x`$Ait9~GG`{r;q?k$qu$ zPq)b|3MWI`If0si;VbGf$X@o1$)K&I3zmqLl{Mmf6Xi*vU@Ow0Qn_X&9Oq52nSl2d z#Y84=zKkgbjr067#Pu%P8{Y zAAACr-~j2`h{-A894{Fih!u;q7VmblgXChw@j;YDyMi#OV zkjdl^e#w)`TGMS8jl11?tJW*$Th}MXKA`E*4n7YP_BUqwaSDX9x9 zx=WI?=(4SUMBrqI^+h0nRSE$&41f!5h(TV}ZUJ$3VF-5FZdCVmQh~hp6rNDSx~t0t zmHMEfT=mv%pr>xdYy%j`u;34E(wcER*90mct?gGvH-GSLUp^0-4qA@Z`rG1ER>zjN z%r!lZJR2_xlclh)Ad>E1PtV{zIUuEhjycr$Tr%jb`?KYcp zb6Ne-6HTc=3SCvnEI#>abV?|lGmKUJC=A~n3^K+3?X~wim2rhue-`kg32xCEKhbR` zhqVXmnW)A}Twxpgd-x6?05IbXSy;3Ux&PPx;k=yTJ0C_0r-!q+9(WRrk$zW{tf=NM zU6wBKc=2!vGhyH4TvbavtwJ3qwX3bdmFzCt>|nrZwIR~j#ge2{zFeeYI9O1h>1e;! zgvsxDmbZ|cFSF$<_Cx_!TB$c1ZJ8s4!)8`fm(Dek+=#ggdhfV^5EBzC36}PS!-~wC z;8KEEm2Dpat2Pj$y#Xgz*TA8h5Y+Kq0@|+>>j9ux=&FdIs@X6?JN#cxLGazMW~Bu^Qp z>0T#+acgUhK_nOobEu-_<==7GMQO>ldUW=tODF>-)L~YSprWtcVYv$nH%B9 z7x%u7cfrFN&Ki6BPd}hHdU)42Qj4?27k`fPVEf?-3*1ngWj>{Tug=jK_`bGq9vSE@R+R%J^uG4`fA?%U4Pv=&c0R1m`gt z)7KRq$ISXS`xE??x~n1H9}lcYsV)goLU6+g!`&?uO1egtq&2r8s9QP#t7b+OHRNPu z7}kuhs4nyAW67oo{aX+H;ph=Vr&GUw7(`mjPviwimXHrOjpq?a0>qTz)94GTcetNO zp2g^7R}|7YXs4^Rxuqo~OJAe_s^xK0Vmy%|qlRoxW_Hm6s_U2AKul}<27CDySwqw) z*g(by*h^-tb?I0ljqZ>?O{^x;=WICTxi%t2r?Uz5FWoYSKw1fn83U?e%AV z02BHE%a@8VD`ca|pF6^`=><)HN5?-EvQAq;S|a;TXpVnnwkhDXrmF8ijUoKt!!RP} z_+4Q6%2w!61Kerg)zM1W56=AN)1!CqRF2rIOwZp)1YTc8y&gj>r}OnJkBjCWjH?CH z`l++z2W=f$EoOhCgaB%TUQ`NL;mk|ljxvZv7=AIH*E3Ev6*8J;t1H>yn5&rc;Tcqrb2sU4;4ZEQ(EAI0>4K*f{XvBwbD62!OV`FXhhiaWFOog)O zLca^>{cLPh`O(oQ%((g$DDshc)Pbs-ycIJu4 z3(v(~<%$QggDb;uHqhPG*|P#wnLO89Q4VSId-c(BT}-x zWbQIjViU%R1+&LEyc0Gvl*kz|Y3c+F@>L&Gfhn#oW}=|HQXCecZybQ8*r63oT@&67 zPpF;`HFGrx@2E{Hn6ROko%)vV7y#8(ck4u`n_IK|kWY0)06OgLJTt$Sb1sXygG zv}y$8$Azp3bcdxF_^U_!+E_tJ%;1#ThM-3+uh>1?Qx$3UIK}h5y=AalY(cdw-LOHC z3kiW|7{=qUiTdaY^wGwaxOZO5K*P#j^;dNe|J9nH0u(5X-O}WYxx1`Ul_(iBKQgV% zWn z)z*jf7Dz%NeH`ixVy83~?WYna)_g-hpP<8a;#k2e7THT#8$Sxy^uC_Zc)3ZAAnW_H zHI{%LUxC}5#NDP!_9Tfv#lNY4PJK6dY})?i;dh>ZdqNjRz5VltA&}13WDUOij!r6vUue5AAWcPh2#3%A0Opl8 zV1#nIA$R_C_&sluT~L;zB<^O0uwh8}I-MBo(fnQWhH5k41WP);p(~uV@}BL@B>*k# z+VbVIf1!3eUqmLh;IYEvf&EyfFZTq2DGnQ++E~Dyu$Ng+ES*qO{;l??1p%We^Lp%P zv+|h*?!tK*PZSufc@bIVfP8h@y~$r|4U?Jv z!nJm$8CipKOa$lx(@m|_F2C)Es^rWnPCog&c33s==563Q6{MG<|J(=_=F&l6rXe@# za7KRfn`~5Xsb{tw-{OR8Rq)XATfv(ydw+`eBeX7~L4H0|h-L2c@nqoB9L&~sZ7u*@ zTm3}CsvbO+Q8XHV*lvQsK zror>2KcJ+3ySY-+9wCXUi1%vrGx8oL9y@veM^5 zOFv!bOzt?+e%)Gj!lp}PJC~V@1aW z&=Gk9m8JuPkCWL1_9arI5`u>oG#B9_BCnLVpqqyZ7!vS&J>YbcD%+B|1mpJAh@!s` z<*n!^0r{#sw|5B>Ek17U_f==DVJ9Bg*rqw3EbMkWYMHSXt#P3uh+XpV5<_0%QW#`m zK&~dqr3?~`An!rml}b@7d9W!khPDobIy2d%U>azt*ZqR!xopx((qHQ?gn9v^!T=hx zUv8k)RIli#tQK|C%Sq~J0?mN-?VqWn77oyQi(D z%}n&KNp&VVhTZ;tl7o6V-I@2PudR6JejN3`3L8&v;kdT89<;q6+uUovj@Vx>J`1Mo zG{&_!PF?ZU|EX;6^mX|vqwCzqXtv%EdcJ8X23{SZtmX2LuS&X)m%)T+tCJlX973nI zmhBL2Jz***l77la%uqXEYOmN|Y$F_|p1go(p3G%o*AOg@NlEsRuyUmmLKRL>_q!vO z1c?>iYF(h+uZU4;6%bm>pZ!L;V(xxp5~5h5W6h0Lw*fHJXM@&F?Co%!qtPgvw^Uyx zb>E?O+fb%4NXdH*DL+f2}>t;Y9pf$6b>8F z4L+w*3O?c8 zTQD$P#oH5`A-LC(I@@8lrP@Nz%~w4E*&P$cR#3o6OI23@48w zl~b5HOc$U{CwsPc&6@#+`aVlkq`}?K?&SNdZsJN>2g%Vo484tl(Bv2Y3ur)>zv=8% z98ZeoD!F{#2ls7{L4B$wJYBJNKax{(&@HfOp+)#rV|(M9BHWeGUhcec6?Q|Dr zdryqt?dVbJiTkdgW%Hl3gpX&8^I7QHu_byk4(I5hUC_NtYj|G-Yv*LHwboHN;Qg2H zEd1uy^K`@YjI;RjZ(DG~!~w-f8>9bvTXPT)+x=KtT1Rtz&Y)aylMj`eJ{6xsSyuG& z5z9jy=gHqjWME5T3Lc|{%g~07jaBlkijJn@*MDEeuk+?1Gd&H?RLFDTe7m$Ho}QL3 zTZT1j*W#9&Z^j$1zoym{rR6W{xXjgmU$O)*yznAILxSnGjWO}K4smfSouEicO2W@` z=3vG06?o$D$MNj5&oays!=javBp#m#po^_j_^lAd+tQMJ`wt#E#Jc~5HEY(WY;{;q zS^biA$vR@PdCL~ue8UZx@%9X5jV+T57s^H&E2Ur;yX&aZ`k{}PL}1d808Ia8GtQn% zr|_GuNJ~lbWy{FS!6%Gq?e67DZ)OcJd2}a=jf%fVQrwM(7&i^;06r;Ly!9x(GP$Ei zKvOwYZdyVTomyHXzuT9aS7#_fK&+S1NM`RlLNc2QhvBE=v3|!P%0ftH%UX_Pwh)C~+O|-Zsv)#kZ9(hT&uGcplGfhU zk%YV+_1J^>^&iip4(D%Y=`Cl&m*3)s*I#tFdU3s-L=U>M#w;y~R9_U2O7mP67Ihk{ zcODkYCMtZ_!F7z?-N?HhrW3mau1}&EEBTMj`*6=SBT6(e^S8CwNMclx*1Ru0JNjes zdrvz|?3dQ35)D=0fXtEeI8HlTDL$4}d`OQ#T4}eyAwCCUbsS8O$U7O^dsN>pc8l!_ zIgSH#pDuV&IEe8ByQ^5#b~Bf8P`*_%Z*>F%aY^65b2~(zqCzLXLmL?ge6DgAu zR$L%N6BjzpTzu!yTBR5{&B;JM;}a@YRETaP$wI{Xw{cu=s|^7|C@*)~ERPCrSHY%u z8+?vr@Vl|EHv)aBWT9@YJT&Desjo6g`X(Qj6Y<$txjUI2=K0KVvTK`+{>m9gA9ve2 zD)U=DCPorriA;bb$#D_f*b*Hg01=W_Txpl)y4$F)GFx9?Usbo16%x%zvM*$>oHydI ziI7U%dE1iKMqF+mS_)P)Njt^|R)k6Np%n}a!r8VyIWE@Vl8kbKV_D^-K6x&Lh!K@2-eDKLvinO**K*|+}cL%=R#!JoNM&ADt81TGkly;+=Z6T$qn#A3>SSlIq zSe`Mvm*c-naO(iFo&;FY(@6FBKR5+GZ~JT^?y|T-(Q=&%$B4B)3OetNw8wAnl8Fs3==V zzonl>d82tbx0!C+pwLqYK61wTuyXC4P3z985ZBqX6kV}qKl|u}Yp&>1h*=vd4fN^W2HW;V zV#&%qBvAXQF{oGvYWa_QCXb}TL>L6j`kv04~&Z{N3x7(aTj0VMXIuS(wPYMyTea z$y5dl&BWsrklsk^>#~x3Tg4st0}nog{d@ON*y&a#F>BN%-)0wDkH~lT=bz>vC@2Wu zelrWbdzZA>Fv{55$~X1X&pyYCFTSW0RLdfTt;@^z)``Tnd}rT$^9@=w2jlb4KjU|^ z;%gD>dX0TqZ>;+3FFf+dqli8cWkl{`w1!Q2BWFH{U4t;bBLPDso-c zF}L1wtI?F+Rsw-52eif0Uu?kYJyDoKE9cVENyfDPXx=Vb3N<7V?TH&k1QyP{?3EGn z`g)Sr@?OkdPNjv(1JI&r*`7h7Vv@1x@F|9RX^dgrTiL5tA1eic0o~d#90Fw^a-MUZ zxP%l$olZda4#i37-4fqgCfH0e_mT5HlqFt>bT~W)Z+*X(-LVkLCWbq?VMOO@C$8Jk zLreD#&9R5`Ff)xo<0i}A_7rxa7Xyb#w}IBfrfjbHzN42LV{K?6!8NpJJI1JObE&$d+pPcsF`;3I+Phzoy?K~LD09wd>yJ1i1MqhfHFf2%g{FYMWADjDAV z?~iDSO$FZqH{;^!I)Ij~KJ++SnG`UvA3PZ%GLv&7nWF`-UWf6|Ji%iY?@MLXDRGjY z|MVBU`t>3^QVvT=;d8wB{w@FHd9?PmFQ=HdnqlP5omV{3u%6;|poNmvP8`w`bCzyY zW4X8szk`<8*5MwK>A6>kYaUdx6E|{QA3b}Q!=!at*LE?_iPx2zM-RZEkVtwMu7fiFat^X?}ms&0%O?yLG2fx>R4~BFPpdyxMu{vw(562}qwe<=w zK);RP>t$6fKIonOiUyPXbE%f>IV8>uc`Ngn>d^%Q1$<^&(V9fLv7$o!V);oB6~urv zapEt3g>4y~rOOBvBbzQCve)ees8y*_!DLiP_>BI^32Lu8QTQTmA`0b?l2_`cwRnm8 z<(yHMrDpx_1wue>lll|Lxi2p4tdldfY?^sHp<4@Uty#%=p_2&#es$s7ARVU@osgT8 zr5co$jV{X8_-!1!(7iJ17qVIe>p}=iyf#VV(pqA$i6BOVKt_^$@>|?<3+Ynpy#1KWyz41S4snXJ?toDRtm3Y8% z&!2eg{xSteB^u&^m4B^QF{-VfKkIxF@iQ7K002M$NklKNhBsFc?gszWVk;D)gsdS5PA=gWeR8lsTll()6LokMl1mz{8Ep_jixl@M#^yuD2 z#rhUfTjF^irq#2Yw~&0rYm|E^*6x4N)%aCcjxB!D?yr)4mTXeLT&oYqv>m(l;ZNRM zd+iBn?9-$%uD^ObdiCrEFOHXqw6c=;(|_Ik|cKYl!_*f^-J ziGA(Yw-+9``!*^WHC1DfJb(KS1YzTr9XN6{Tste zHwM1+3?wngh0qD3FmlPty_mCbJN6xl#D}llMK{U~%WYliULD;f890!A=5kiH4E_4-oIVE{s?!09f#*OHP-L$mdzWXTl z2Swrdi8DAAn~V=;{Y7PgQ~2KtQb`byiNA>MDksyWy{)2zC=LwmABbNTZ`H(g zbpv06>Z`-|ES<_DABLY)F|x0xm5;q7^C@wCefu4EB8-;Kj*`aKb;Mk!taZJqf@zDd_Q|OyuB-~ zjA>oJ%o#Rq+Kl_|yI(2yRt1-Rd-mYYyYIrhUw*+o+)9Wh@c?Bg1zWeP_Z>&TznM42 z^lpt=e+HFFT<-`vMeDZn@NMXU2PgG$r~y~Xm~I-?NeKwkTcVsW6QZ_Sp)pl|?r1?0P zn#@?umU$XTR1d+=e=^)HuXEFWzB5N6(T`TYM%r~a97iK#EM>|4j2YMqBl_@Le2J6D z5FE9(HoAJ#*#3Cur)5YL<4k@(mTf(N*UqP*fmd-7H<^~nOE&FO*DUe0MRCD+W{kQV z-HKs^fwd!LhWF_R4|@0$*ZAkzx6QmKw#2ovoEK#Ux_TFv?Ex*CvX6e)7aWNr(PtFl z-KSHV!sb$VJ-cZ^E@ZfPdLG-qd*Qj-zN3lj%G^PjJn_LSHF2;Jm)|C<*m34e#yF)l zwYV9lUm8VFi&lrY+s=zVTWVEyPZ$=>v>pNDc_?X~WaMPCkW`oM*~ZmYc`QP|I=$Li zi0iUG3VcaS61$HJ2s7gyD9NEwhs5P@%|@Y ztNUb-lm-UHw+gW;l!r>>9Witu zO#k-}#xnj;S<95wt`MIKep{wUlBcGTyk;L9Nokw}&HnKh#XH-}FZGNVHW2rdG;G(_ zzgQM=alLfeD*QZeF_jv`0zDs(Ja89oxNc&xd{zA+c(!iY91juG-n5{tOH1~2DoTj^ z>My?fUb)&X3mdtHzP)?kUXr8%ZQIbhP8~(KuUfMK-_N1zPJYAfj#eDh#MQea#I;N9plL?!(AogNqRxGVW}p#quX~D-{zHSAv|C@)WCJ;qS{ZZY(VZ#r0+dk|-BQ z4Pr^$zI|I-F^|NXbnPwE09!s#@aRk>gjZg827b+a3tI_*Kl+m(7GnCPSKmh5nX@cg zG>Xgqc|I@Vb>Ly%iwL^ODmrVaM($M`|5kYP!Mh6UZA4AvmM#1+oX>OeJyWr6<5q0g zv>jbL2V&fq5rz3qos6-o-zOTw;!PmH>iLuS>0(VSv9WjWJ%|;It?uM(!s8F$TUejo zxiAR0aNckqn+eqd;_J7~ef;t}WeX$I0zc`hiPs$#LL|`edo=?Rd#{R=m z6fl|{p4+6H&rr{|7i03-l8EJ9xxA~bTQtPIQ%0(1Jc(g^X8p8|Zcjlg=3aRIari-=qlq-wQc8;7!?7GJ3TqATz8<57#BfM0)-+H^(QMkAHUP;`f?+# z4G{epTl!jBo6DcLU)!&F(l1VE6)v{IZowt!;6Xgjc-HgjhFv^a=~O)kJaSDB-1zbmghnNx6Rn+X)7-y; z@dDk6FMNFx+BYvg7qHE1`&;I8H;?H=8;M+evt$qcaqB?4bA>D_h|FFUOj5uTcTebQ zTaEr*4g#K3IPTr4H6mlrSv)ZE9p4`sjVTlQ6_z}ca30~&@%)yF)P|%I1x9bh3?I6~z->s~c#m%^%PXh!pW_H(hEzzfA8xq>B;7=n(pN0(G zYIL5RJ#{dcWOWk1R}y4~>s2*pa*!M0v)rUneRwiFnv}Jd#NxUhV?NihGiQ_hYjy`A zR9s85PtEA@=jx%o3ac5}yE8*PwB{I3MS-kr{Ip_AG2(h}SR{=p=~<7Yw~Y4_26dNN zUtxJ;CxKpt1Uu{NmUA*U6NR8Af{Bs2s#{K`6ByEN?#itki)pZBTOZr3OT4Ze(u3hf zipHcaZCheQuZ~#0aUV=PA4&J<8~26MZF=8gHEi4)g5!*tuCAG~z8gmMBfjp%@;LN^ zYi^0Z{>!Ru>f2$jpYH6(_`y9;m)-^?b!h2J*Y(Y@`|xps2gb4v#(md~ENt!^jbpY4 z9aHn^uI-vLj<}DK-45qvTa8kh^O984VRaG6g%f=Z$<9Ys*Jm5$nlJYtr`u~uju*Wc%mz@~-#)JIB`89rnn{yY6?y!zG$NTY>tS?vpX zz3BJl%#)9QJpQ0VmdfD%OnKOt@< zhV`@m_bM&c<4W91(Re?4EP@Im;dHg`m=>(GEAOUQH&3`?w6cizqLuUB{f7`75>|pB zm;4RrBI_k8L|Bv7)*bHJxg(x_>JdzT>8--?$v7{4T(@B>?!9xe1#{bhp3ih57j;7T zw-0D*6f4|%d)LRLYp+y)g$E0E(&!f7-fBq2K|T~*jJP(+ig$>?1N*8!W88D=aJ=;HVys~Znlp4u>1RW3+m;dGox~|#wkAkf&L)z0^`Rwqc`211 zh4vqgRM`gfZeL#Z66fVTkiOk|-JrsA65o3M(!E%<{vhrm>Dkh+YQ&>hGF5kqC#IWs zgeuFS6EWxT#>dNTnxHsD=J5Omtl4xJkK8$q;VMd!YBq&x^-mF`3>(l9UFhEQw#Poj z$(R&uV2tkzL0tdw*FLyY=<&;)UIoI2~9Z?O8WRdy|{WzH;ff*w73piAe@%8FUm z35VzsTogp*9kwN~ooJ=BdE-WW{>2yg_w&z})-84X3>9Nv`tQp~J)g?>hm^9dt#>TJWz&WYm^pJMp8MCo z9O;J6;{XEV`un3jmAgLweLp^aa%`~@OST-tTR-i<0+QCf+EKV-?sc|lP4DT*n%_AD6N zLLy>tE|nU4GH;-8z7>_iIx>vl<&vJeXFTHQGI+t714ZODEw8_!f>}Dp?>8U6{(^XC zDGNVYZy!AohdclCt^I7gH`DK*NEg7(?4Pfuqq4U0@^r(4Hx0+Mw|=EyFBc(^XH*RG z8^`vprdFy}%^te3-?Bek&3P>~(NotA!>FuUj-0z{&Dt$(Hs|4^pVwmEs@)}I1~O~t z(#}uaH@k)Z#s_^nw1*o#tL1WDByy$XxL9nQ^&PH#;khcKaw5$>3BrH_-7a=`++&z^qo^@@C# zx=x)*DyoumyZ{ob_R?ej`xfhW{y%$H0UpKGMQ@xyumlna1WSP6?(PmPE-fuB)TldE z>il);UZ4tXDYTRd?(Xgcf`kwvgb>ev&f82fyF0ttKoV^3m&we&c{X?6JUdr*!ZmF! zP^{vi{^2l7{@=%wpj=TfFe8N}4*+x?{me&3Rl$Tj6LZxvvS-#?c#hoqa1B2a+UE1A zO%0-T<5@7vZ3|q_TCh&t6AxHp1$H;GXYQKcxIp7j;%a*_*B>?M14J-b7oeVjzz28y zG)0Q}msXij0fpx6H^Lk^HRRVr zvH@s?KL|NN(Rh_vyC>)wiKW;yAJ*pOpCe>7*U{)z>bC=s|E~X(9c9ipqY(O zWuXDOW8=aNonB@%Itjb>jR1uf+tdt=FyrWHN#B25Nj)pI2`vheSOFHru@KdFdIYn} zLW3IQSuIH1XaYRj|ACf;)fs*bJOa_tCZtKR+j>j#jDI8x6Sy4k+A1=Buq;k;QI1K4 z%K@@lJ6MupJsvfqunctI&uKx_ub0*u2dp;F@KYg*Nl^Pd_Xzn_F)( zEQMCH(RfaN_V3dJ8qmR7k%s4Xty@T=hIN(sa(>?$vCW(~bxIDwq<2Z0__pczc|R8B z;~#zMC1r7-TbB+(ApuuNS%$ED7FN;OKYsYh*YIa~SO)d)CAGpr(XMR@wn}0CxKbP_ z4&PjC18K0_lg!1H3q;WI{V)EOaucvG=n4rYm*%R&N6a=YGp&hwlE`@H! zo)E^Z^BvF@Sp`ju?&T~Si0uC&BO~Rr&%eO;X}=Xip+%bep8Vnqu&&ZAM|+y-2wwmB z=RA4!_1EPv{5R8wqMp()SDZe5hKw0K+M!@8*A}y9&z6nr*W0D%bGo6NQKLu6q^F;@ zGCR)lqhM-#`n2it2DGYaMM)2y^IM`@ppQTPSh{xYqSgZXy5)7vc?A7^|NQezW&YeY zekS;RQCl8dm)$BE~^HU1J>)ZM@mKp!- zkgw)#m40o)q+)<&FDn&ui2u#nD1If2$)iJ><|kV-u8srEV9~}0{tc2dxK9E<&yi=9 zx!NG?6OuOVIi|E&35HOcmGP;meZ3F`+unM3h?)=lws^Y^yiS9b-)6W)N|E=U7+$m* z#^`I~6H{c-1^^mHgueD}Xo?pFQR}*IyZTb6+7bYGQL4xxuWa6K;`q9y)ComETT0tgo76&w-$@8WPsNt?c;gAMwUM zqNlfytomk#l&e-n+T1eIK7orNClzS@K0xa|QUM_BFH;_p=5=d0OL%d`EG1+?XxH2;y0nOHx>vzdYXlD!RO#G)rMvC}Xs3(DG$r7A)L5?T; z%Bq*YmM@>aLut%9EgbEq6hKS=(v~sKXB~Jzy_aZt30DF4nN_tz^oK zc@hhYWRcJ;pSygMj33lR#Y>|?BuHENvTrn^(-8DIb0QkZe>PBzaT=Y0@~K=7V7`{# zDD5SOIoi{~x6l0^qcVHr{Z+15ULpy|T-a=mG@R_NNip1k$lUy_A%M@~kg?>H{48`M-eEJEEfVCtL#0$p3w z%gMDx-B1ZGUlwclSd7m{A$S!hLH?HY#j?$K|Kt@w?alzjJsrHmoXoDF3;Wg5HB|Yl zwyJ0#nI~f;D?Ukq!x`ul(zhPQ2c!G=(J@JB3-FT#e<8Mw7_%qi$75s10vRK;nq0c+ zF)MrwSuycB=`233b@||faLcyq?*=Iyk7^YpZ9M>WIIuUOEcmc?uToC3ptX{|4HG9v z9R?F?^<^9YVkQ+n2M7~rZx&TkHj!uYxULQ@Lo|7H(neO zH(mVw{e_wkdf=LyDO#8#px05Ju7+2vSW)?MV_EvLZ2@cMN3fbEw=Y+F*%>dWNx{Qt zQ`aa=drsbSl+IasTKa44OhEGk0A_qo&Yn->3(Xl1A7X4`HtoOPC2iVN$wXcnXsz*s ztyY28fBh_}Xv5yO+=mZI`?4&t+O}#A)6S2}`pM1~`v`&Xq?9Sc$AfayGA5x;TH#DCbq%a(1J^xF}98pG6AI5f-W{k_oc zF546RWq9jWzi&fU6>fB zC4$y1n@V*od`&8P4%l>0IThH!O;B_mTugb}N^9rDvc3p~u;q|`E#;@V8)Vw771Fp~ zHR;+ucM)eDTVxIV8h`oyQj`NX2CZw!rD$B+=E>hxDwUHFgIdWKKdg}7{@N%v^==_` zV3P1ssi1``C=Boi%zsm3fxziw_ZkEIoCY0&HqZ7wC*f~-jr8i=5MzLY#daPKcd6;} z$o7(M?}SCFnSZR4C-3Wre%;qF4{G9b>?#I#O6->ll4zFQ5B~8wwQ;^_I;dAu`E2?E zS-yIY^zQ0>>?+9mnR#6r2RKT417IG7@BgMif2oLdGuuu)HoBGpkp3p(ltjUYfR}^z zl!p1%B53e*1#r!;jC1_I|NcjgMI03`Zg%R3bF;=xn#fN-d@r?X)zaPJl+R)v>Nfal zZr-A~j2(NMvT&sLP0jgt1T>@n{^uXLYr+Jp^x6-)f0;Gw7rQwm@4KUOc;LYYXjUPWAbIl8C&adT7?U*&|*2%_=8>L;lc3QAYXAMwKh>x?= zw&pyqcb`7;&DUQUTEC*pq*0?rO5M73;WPXe*|T>K!0X)o4f5sK(WCMg%$eVF_uVel z$BdLW2S#+REpPq2L8kn)UOsuG2j<8E-;1Z@n)3 zP3xzo%G!;4q&!S|&wS&-96&HX`7ka`P={5nppO3SEkaTkq0AQv<-^0LV`RZ!OJv^M z|0E`(n8bN{%W-#KIqq3fHhBO{q*No!Rd{;nDx_3LPIYxe-gY~2@ z^fx9Bx8z|^PnrlXoy}t{?`CzXOQj%xi3A9p2K-^g)_tm-(qh+A2wd=GREAqAy*Y52 zr*Bm+OnyJLPND$5Z-A-s{vDdinRD^7Vee72IZwPBQQF*EIoHFv2uI)Rfn6>>1293Y3a!?qxXvpHN1Z^5>Xw)MIi>S)V9Y9ZT##atZFY#%pO z9BCTc=9Ayw1P-fJt0sH*?sb}!8%L}U(>BY!vv%e;H>4x4bng6|EJp?K9f&Joq=|2ylQh=I-c3~{IG~% z0{kr?r~AptSS83VB+iwag((T1QB-c#TacvoTO9hiR7#ga0g$ycWDpswwX;1 z7_On6bmmMn!tHxu@{^|T^Yo#OHgNLPX$V>vpvcJiz`?^ZcFb@o?mHdU7Lx4R5c@F- z0#H+G`O0Wa4E%=gHh}d7NtXOKDDCyU zOjMRF<0qY<5k#v1CU9MlZ=rDV^1_&SAN(SYfCi490Wx0?2-T?-7YXe`jPm zoCehP!~9<~HaddjR1OUsna|8;<9tE(y4St_Ju(_!xZd7*b(E}RIlkGrjeLQMe59r?? z8rhTN=}D8U%I2!%!ubSQ3e(*A1+Hn8Y5DRMN~=>}9>3RWSo6I1-n)jNwHB8=XxOlU zy!+1EGJM2Hj6V=kVP}_36c|{xY`FsT_59NAo9FyDdRiH;1}k~SooN3T{l>s z9cQoQmH(WrQv~_@$xA3ld!OeLF3NM?tdbhl%gbZKTUbXnf8~n<)S7Kx2m22ZXO-Di zf*IQnL;~~(6WcbGswxz#fw2*8p)@# zmdar&TwxCX1*{+SfhM+5{anoQvZKxTbFCz&aSaNNpxx3M!;mXz!kogg{UB6J6YjPk zH8Ja|m+4;UvKe+2XHcPbO&RvqtMcOmPe>{R;yf_F;^Ju0ho8xTRcjS^UAJ3j1(xRZ z7$*TJ4}1CpS-WAk1i*UiPp{pt{2k|&E>2ggiXDpYjErpYrmGOB2&GVG5fTf4O!% z1I3fUPr&UU7h*c1*6%TBkJJ5Z7OvOAv@}grw{2JpX3qopXJ#eFIL| zSI0h9a4;=3T!tVq=BWXtipzj}*0WrhlG3_fsQh|jE$}%HSq7imPu$vHnWoj9)T=Un~E0$~iaf;{x zY&rm=Qr$3-PyKhjr2MrO9Rq*`8wovT;hycAAPZoaHW4?kq4w{ zGEVHY)ZZv0Ci8(*2R30c<1e6>Mo*1}A-vAp7H9r0AP$h_`h(f>x9#g;7^iYqiX7EZ zU(f8R=9Q|VVY!-lY<`p6L?ZFbO%87O6edyIn`XFUp@~S4+=y7e^5x5`%`Kz*W?12K zAUQcj_U=0%AAR<1o}e{rLt%&e?z&9|59(`Jxy%E*ByIYKhMLy43D_c&F~Rcv=T}J& zt|Fe@2iZn}S?ngzynakxoibHvwQB9yE+PU5^3D5ukq{6x(gZ&jiwTe+LGvcCJba^; zF}z!pfz(>eYZKDh7|XP7c0H)|6L9#wT*|NJZ#9?^TVvEGJ_|4hDeyV|IP*b7lL~ zG}$R6v0(9XZ0awC_H{$K9e%Q#z|^}&1l8}p<5qd}>B*>P3ep+($y#5XjP|`{+fJnw z{=?6|$;jb1$-urnU7K@hg_`Glae(iWfc5>8z6JP!g)!O!ft0iGO`V>}-}D{rCr>{x z5E^l=FGFbN`SZ8#lvm#S9RT=l`Doe_nE`X%l^}^+9A+zNvj5bXI5``ejQVCt(*_~( z>QlqfPZew*6@Yq-8Qf0Z{_G!FylRiE-nd`dwXm0Jcg8eYJRoBht=OZU#eN3I)R=?7 zpWu(5-Y-osUw4J(LfhKS!%ISfOG}H!HJm2t)Fxa$`1)Tte<5AwE!=Vmz%|?;;Tax! ze!7#5TzBcywSm0&_zCr-u6 z?)@jQ>E$gwI@FPy`xGqLu?>C}j~qKA@4bAhOOJEQkoI!R{U6KDy^&Hs9DTiu@SgM4Kf*H!G@C{4zjJCx2!#RvsCop#;aLqaX?%jLr z09y75-FtMGZe6?BW?}c6V`1}V&1BG^K}rkR9X_Zv;$|fMJ)eThj^N;6Em)l?Xt@hz z)BTa{yvYZ1)w?H5P;P{*5*hrU<)_hOM$0sqCEvSmua(L049rn??%Ji+OtxhhWTk@G zuA`%)vAG+8y*q1xOm~0*Lx&8Onqgs%lIMhzFEk_G4JOv-{QkR@DRi1*+z<2W6qa-5 zHYz7G^TH(#^luX?1ECQ;@%@Fe`lHc>qLpephkt4MYB?BjPUgKe6dSt6)-_zJyctPx zpbh2=-~WwGd_`HZm0H%IgOSm4Iwk@06^ohhT2%t2N)ZF5sv<@7kD&BR_w-j9#vlH? zRB0V@K1uMJX2UB0On%}v<8@U*R*?W`C0OWq0{*wJ7V^V9o4%u;`(&Qt#|G>DH)#HO_|sEm4M6KM?ky_h-t=b|ljK4`r7T1rl_8Vd;5KgzOW9{d^#udn4DA$@95G3-z zH=bW=4bZo7?W(eA*AazhE!(mWf&|gXFH@F7yIEm$5Gozmz6s22m(FkYR`FR+?mxLY z@6{4_ZBbW#{SUx3+R(UI`3@w8+rz~-Mj zwN&DuCCxHz38u*1p&{WOQXcgK5bcOqZQRdTTA2RK z@GC|7dhl91v^s0QuT=dUmFsA*;oz=;A&$~sZaB-J`EQnEyGUo=4CBCbxhBFdx1Kk8 z`b21j{Ig)Gn)KwgiOlxYrd2bCpf!1!v1gkU6MOUEj`+-P2Q5L;cWrSLMVW>k1(43^ zAdsD!&tPos8QnX_Cj;Wx;){vw)nq2tpqf`t5MEk-tu^>SJDcy?h`9OesGneb_7y=8 zEC>s&W|Ul+Ds|R&HO=Kb&*-a+DJ#dP{VgMhKqz7WCh{;Th3~-`u}&R&4~2xlIFhDY7nmBkk(tz98%n=f3xLwWUK{qhnDsD#bu z4z0q~GcH`d6P7h#{mDhr-ZS4dO>f7aPcl5tUr13x3@&D?qLDw$*Aed><>d)2^8r1Z z$v5xcB^_GUP+IZQg^tw@fuk;jk2jQBP!DCf+! zdh+B6$)F`T9Wk6BceCcrbfL~K10ZbMs#Qy@`?+bEO9alNVbO)cM6MF~LQ679Nf+#% zpCEaM4jo<1N`dH|I(4#(&#xvLWyC{6-J}BK02urJ16^P?HBnv$pj_DW=cpYh9zTi@PE6Z&Eu1#uG6n;@jn3-d^}JMwg&8ue0DcSP}O5S~skLy>;jF_-mbE z9A78h(OVwBwL7*p`K*@lmV$&I0L)&v4(3~nfGi-0qY+Ha`*{KzEP`p0~Ui8!7O)(I>w#mR_CAIK{BGA<2Y z%QIe?D81TTh8Ar$7gHh7e{%O;Sv})N`TF*I=W?W(~ts8e%!al#P!bLx{aVNlls-#?oHli%#S`8^Z^1{G2{ zP(q-9XmnJm5~NIPE8%}=w$E9%NlwGebc4_krTt5jWp3a%f$(qsSeng+tE(@{u2h~U znh_t?wWU(7WZ#|;o1jdD(`5MhI14cB34V>}**ed>3M)d#CUvBENeJAaerMqJmwv04 zL-QIN%~Aq_I2{a3zSpV}EcI$sRshRhxGUOqI8uS#mcChp8rjzyxvuJPy*>HmJP$QQ z(6FqCU@bSeY{QMdaVAB_;#s2-ROc|@V#9}^vp@rb=C~D5Ovx3iB9IaD!p2_~HiAeV z!2$50T~e~nU$m6X`=~?O%AHbdXlKcc#>ePj(~arfVILnmL%g)3EKEW?>sAMN4apPR zXR?cp07znb)U1qe1?DLrsbf;%b3kias#%~otLB2=4AQy7GCDrzLM zxs>(=_+VR@K>=DWJoC_!@T|XQko1ZMar1_UEitUHHw#(D>C>mdXLn^v&`}*2r&K9? zzb0J~4wQg@ar*ZCXTcJSu#Q1{(JIExxHW4x%B7(VN zKWpERXCq9+9MHxs+vVq9=i+0tZ_9Q`P%h0f8xc1QjfmMMB|+<#W0{S1j8Xg>J`697 z0uTA2qzf0(Z&F0Tp6DN#8|p@V6=PFczX~+AeSP&(vbp(i!`oSZ=ImZ>E{?Cb*~2nO z?9cAM=MI?c4wvtK{8gC{=ZL@slcV}G9l_?*luY^L^XUNZipfm_dNWv2mrm`}^L-6X z?p13yD9ukLIOn9jtMV|lTC!}l(*B|;4~)bcgYm&XB`Uv2^OlDVUNZhmYB6`}on zxxy}N`!_+WqG#tuasVbQx9vPCahOM!g5+_7I#s21Xa(hGIJe~dU%mG92uX%erXzob z^ke+>J9kME=kREAUJxpEm016`C*P{tw=3Tn%~rZXWlQ_Xj1TWq-&+s>epl#)M{kw~ z?(BuSz={VpS6rnoFmwIa_m3gEQSYSB#SNP_#$$*+mj3!)n1sJ|SVw5)H3 zl;`C(MW06X=@onMm?;OvX^#CmxuOH-~3A+cV85Zp5&nrWw1 z8IuZ@1C`5{lFuIPA;TvBAuYly%1s>##}D$}V=*%RfB#6&meu9eJ3AEYL)^f*RffP- zEw~5V3accXOCN`6+Y@JEp?Mt%u4MNW2yfRo?1tgeqX4cGp^f<2uLOh$&O+-Cial>n zUmzWu)`B~wf)jqbR+vgZvSSCdme;>optP=+!{uZH%&WXGpGl)7p(3IfSo!?vpY?bz zmNMPo(Dml7O{5acLYs8$bHG2Ol8pK29cgmY09pR!w{isgXz38P^TeJM_ZJf2#%|-R z-)z0(V_eZ1H#%_|t1%ljwr#tyZM(6Xrm@l3wrx*rv*9EY`{umoJ@8nTYE^pJko5**s_c$hV zIFi{s?PK0G=)0W#w^!gg@3h)#FN7swMD^aoQO~r;4iNRchQeO1R$=I1U;Y_ru6eB! z@H@Y{=Ryju5!i)~kM-k@&03TAZ;)Dl;RrH;e0J3U<&Au&*&H+~RmkO6$U(=`H<#TZ z>x4S4{$Xklj~LwC2Ik0bZE(VngvbK+H-d|`*4bHqy|KZQqWblF5Wm)gr`y}x`gl5Z zow*B2tR4OM?*+Z6azyMIp7L*~jU1%p2NaQsS~GKU%n13(1jYFJ`?_p1?L3~+AsAf) z9Yf$!AJ&Zew%0fq7X`xGM4_vM^s%81y@Q)m6rhfZ4oj-9ySuKdLFfON4&@HtVw#>$ z&TjYslO*-`#&Y|LPCso@%=VPRtKFUWIGSiD&&GJS`ib15RzGEt3p z?OkrceZAsL`Q&F(%SZx>)_$`A81d%vN&Gk!+*8TJQ5q|1q&A25yw^h#Sl*y0(f=WJ z9<`_;B_>p6iw!o2DRp$>);iyCdS5TLBuC;|-kIoSn4NEzWZr_-Lxe6$?N;YXTV^$7 z$)eaWFfl78Mb;_Vq0YIT9-Tt<(;5N?m=lxdznqchGZoiZ$ORPM6+gqbN;>g;uyO{q z38i;)vmaq%>|`z-yWYrP$glxcU}R=%F}NRxgm&^rHH(Gj;f}JV>?ecwm|)6umTIVUQL{u-@MbDx zQt!|Ze!N%lq=qwB3dz@VK}&m+&$7*QZn^)X2+R^gpr8!wf_nLT3$fMdut*U3l?TXM zuZj5d^PbkqDm%i&qFw&Qv`H+Go>~RM@U_`0N_;3k_BfmJ+1t?36K}o89U|4l!n24+ zxy8rw%LuRE$M41Z>(uSTopFu?T%v-lHn?8XOJj42?9@$N&#{^ajKjv79+@ws=-OWimL2-!>vB2EueOHum*HB~qfU zza#`ZW#}J$Rz*QMsIYw8hkI~5gv>W=cBYj5Q0edR2!8_islr@NF=i@jV6o?rEB>9j z$;_XVE&`h`7)-@}R5x3pih0#@U;VA~TNf)>`?+PQ%LO^RtDL`qjKlRKH|u&Cq0Q8Y z`jF0h?v_Y%!$@`An;Jmx#e>EE8dsFCaxFgHkE#;F)#Q(&smLG+s&$vFkFiM5j@5T%fxZ z%=It}a-AR}7X)&f*RGMqjpNcp48CSew}(oCet{ z>Tr)*;fIE)a4LL!>Yf)+#OoR4)}44=4t}xQt)S`usRq_X+r2aXK-u80jm?Jy4(@P5Fjx3;p>o-f`bXX7N8cKjq)baRaN+$Y4(uBrG2 zkTaWUXFknw==NVonlbl6bKd<){H_=~WRg-UO~OcN)Y_8~G z4oiZ(1tu$f+gqBaMz2969O=|n)6UPz9PN6eh6n%Q#oUsfmK0R7T^Ii1c`sDQ;Xnz6k@ zdvfVumwOU!($Bka3M?}HnfC~f{uAFpl+Y2y)(XG;)E3r~O>+2lW{aIlcSRGk<+0mj z9+&-A=ve68M!4GFxinw~c14ym2z=Sa{VA*YBO4b4oQw|xp``LIB1q0qUHaA|c^fkx z&6I&jUUf9#uR1z^XOvxV&orx)`a%fLi8_Ff>+N0WPqUt5tW@H2-E(_P6+}9aYC-@A zQ3|*l1{xTx)BeO6iqvU-1(3^!q*`8x!h4?GhLw%grftV|>&w`C9X)}5SD>S+F>N~4 z^>(H8pW`ONWZ1SAeqr3bUFBEPL<=HS$9@wU<6D#ylrAHRGJ*u;GTB>TsdQzY3w2rk zhPfi8ewH%N>q>D-<-oUA8|suJ89K){)VvEZ*UP>sxa=~3bhJ&jKrPN>mm%aLbnS`3 z5N5x-IUS4~d}H~U#~Hoxg^t`9SW?*5f#<2s1xonoK#J{<$BO5y+$IO+h+92)Vxqjp zK-p=M-s&IiIlPb5MaFXcGdtN|xUVDrlRMK><`3QQc8t%X&zliRWhXHJ*^h_b?}wQ$ zncFxOzCfrAzoU?x!})D&IXEluQV=$?-#WEO=?(YruSLH55X)M~e?~oFoeDt`;VM8s zNI2bpj7X;`FnyncjdygJY0KVSGo3y{(z~R?K!STaS-@Phtb$y4qlFmXG=PoI%&mr# z)p870z4`EO0kKz$`3{1WbZQF9eA~~EyAA*Es=Vbt@k>kuI$9h^YufCMd;XUNuqiao zC>pDQe6UIKj@`Z{BVGT!OqHfd3UTD-0sBgZnDBrpUZoBL+0)RjV?*o`AuikK@lJAi z8}n-)fr}K>byq_yMb|?n7<3Lh_V<=3g5d~OOVCGxG-!O8EeOi(C;K{08~|LZBjO9& zm~2wep=~n`LN%(j?6OIyd-+A-_sGZA>sj`~e66BABkI#%q3GPiP;ddR&{0(E5yJ z|KSuTy^keQCv$Rel;0J~n4_;O{mSH}$UrJl;!Uh3gtO{S+aBUm6VlXSYPOfEz#HHZ zc|M53OM|u?ON$R_(&e$N$p$peF3t7$DYeDS*c#kq(nz{Cc0uUGc9HZnGOX~(2Bbp=ze3OhToWhxD>)|(C zToGqWWi%1wF~-#-lMT~PxOq}1SC6)ZGTE{vu5-`bhM6s994PnWali+areNwOY%yT@?V!SrM=>u(HhZMWD@&W!dka$!9r+0dESIxLk}%GwH5XpJ9Y! zh5=I%;YoL#IO1YOI(PWqN5)e!H=6$3y>~O3v3E)6Odm7?B$<>q+k^-`=}!&NCc0aA zRvIWi&M3kQH(*pSVvP>7QL8-<*E0y#IJxl07m`S&!t@WM!wH@c1hz+qmFrDui~c+N zyq`rbwcpRE%Gi5E3x_&1hfjjA8~%%5!V9xj0{^ia5P_l3OF*-67=Ga43~ERH){ zG{;g1Gnj)aH*BvHPf09i`_Rz=+?EizNBnU&=%`GN*EA>8MZu7ir52^3>^7vy&OeZ9 zD2M8}G?bG>E?5HFwTV;ieIdN2VdS{>TjBXNzgYd4zrFALs%r*(H^tt;-!*-1Y{(Ge znf~N@|EN0WcI90bqzH7`-odZ-bT8%E$7P)t2yz_ zc7djJA@Va_=6+>5Wu4-LLQn>lQDu-`prsj1kzAYzB8kGra9%51whxA3PV zztDr>=dV*UXYLX1D$&gN1FT{HREf5+x*$04CYjJ@3!$Z%&hswXxsE*%DSzsGdij1A zMmlw}740BEtTQs49u2l-GgeyOF3%@^1vfm#Tp;I6lO;SKeP`k34QwG~=etZxL`0AatWHS1Uy(3^$Z^U`;GN>naWt70R6%Uaj_ za$bRFGv_){O6F15*^VlT+l>ojAl8p*{rILluk-u|4WtWd*l3Yyv)GjMeu{p`O+SUS zLM|K4glHA={Rri!ZXwkCy!kQ_a5}copua(Lvd0icCp`#0ie~?`%by4BBfY}BJ---! z2PzB$S{WjTdZQ)naOS%|B>F`w21>jzBVv^p4wQPbP~Ph!8mViQGrk7Ol%vF9y}Q1P zc0jJQOuksgyM>WF{B(CchV(#hfcn?LcRHzE77kP7k7*F4)ay^aru0|GSQW~@^v|zg z)W5uq5NQT-!`m-ugIQ5cI=D_wYW_U&X~BFG8rAAYbGl8;N91RHod!QIon?XwYr!G* z14{QdCwk)PZzZ#IECc^JGcxlyZMp`!R!GkDBL|)ZH83|=9^_;YIM2sQ$xuN5g%!JL z=dDb2c5s8uv_mY|KU`K*ae=US3$(|@SpLxzSFG3E^HWF6db_);QojNJ+I9JB7K!~@ ztIU@FF_HJ-VJh;?93+j~-Ifn%EM-bJsYilAFW@M{coqEBWjXZA^G?NmJH4+ur}cu? zz3`cyHLCe?QsyWDr$Z9gR@%4El(3Awu7U{Zz%gK|;ZN-tsLuO(ymf)zZ4Cxp?!Ohh zwNSml>%r zV?m^?(q*~@`IVc_ST`V~^P%Hv5qN|q2dysBs16G-FQzN8sKiFwo^f76*a5A?#D|TL zW*~=R2GU39dyo4?VeQ%9-*u1cZG>D5py0J7qReoa`77Em{l@eU_2D2hkO`~zDltzQ z!enSG&Ak>UHXXI@#&0=^So)Ls-1bJ~FL8xwt}f^nz4MGzafZwX7c1&c0k)93NQ2I{MFR%H()WR#?(D@;GvV+-a3TZr%wh{2`d zPb$q@1@_)Ul(}cA6qdOPch8dXyvs6I&^S3ObC?lPF2wT|^j&Dm@QVS|fw@Q;c`V6_ zHr?DV&u9<(mI|e_6faXH9{puE8WE5LcIrV~>u>GXB=+$?cSsVx(bHD&ub&|nfIlei`|tW_mHF&dett2FR8AL_ln2hh4h4Z-k@;=No_X1E z2x}|az~aW$paY4^zQaLKl@G<8VH$MN7bjl{hq!T%ngb@{CI+JxzA3O%-GnwDulCyh z@+jutdQcZvlx0-Lo^}bXurpvROFpmRx(h^A1e4v4MKyRW;_YrlN=&vy!lCfzrOTy+ z;-gjB-9MY7;umV5H84C+2fbX7QpiXAX_D%@lGJ?>FPFOcf%qN_uade{h477(v2!3% z^{JaXOm5gg=<3$I1Y4(>FQZDDl%gOHPD_5UlTV1+uOW7d=s_a8@SG@G}m9^y- zM`z#QU~ez-sQUiHG`n8m8-C}g7W#PRTbv$_!L!f|)_e#m4{*=C4tgvu(!!p6lZt%! z=Yy6?Qp7P^cst?PNf#N}*lfJ%QDw@ii;9QjI)SRtsiq)n4I|U7D7BK zKp^Mof=Z;}@hNpX$=Os&cH3YR(0v5?h+`y~m9cbtObF+1v)!IeePB-{{j!8qRgKzR zXlwH$B1|J$?5fLt@i;GgAu#)OjgR#t5{}TufNct_es+*~A|&E^&ffbtjAqnon4Yps zo0R4ylKu12oyYeUm+EyGhK2sjVMH|XbspeBZs}+kte~AE7>15YqKB!%^%6c> z=J>^2gqYD^H1TP{k#2i#`xs7P=J@V8i%p$*JsSDmRJ9ylcy6m5(Kor+0S9$B9k(hg z*;FQ8Y^cG9#hGwpm4+5&O?Hf$wSq8)TRtCtlM!qOt1^j&@_4ov_)XzZT5lT-z_D1N zH;U|)!Ff!Eu^BAykN3B`z9V-s=Um+0vvlA?Dz%XQH>m7vhMLVq6=LGGNhijmQiK}~ zk|e&8S17~PuV%xA-Ky1<)~jRaH3;9b-jwOi>8dxqkya0Frc{_aSh1Y@^9EfC&|i`r zcL#W)=A>2+!Z+)mXB3$bzO{NZ&8u%+yD}n6JNMuDG@|Dxv&k zKddGYjmpPYoM_EB-D}KWzETLT7@+bam0P~V;!0~Pz7m(jZw!Zc|LhYK8Ad+M7w$1> z2S$to25m}(`|r!|wd+dqCqoSTQaKO%X2GX}@lOr|l|87n#bchR$zu;%_ZR|K{4uX& z1YHiEw-<&2Tq3G@xMN>vAE^E80Jpyn$H&DAo=f-L)Hd8jRSBKTl}KNcPtQ6;8i z4_}j;YpIj%+-?t6-oc}3-8`;_n+zRt9%RqAhWQ4Su@!@sz^LpV*INDYTBlW zoBS84+k}n?51@=hccRu&OCo5hSu0bMF~vdk3xVVIy&l|#56b%UBo%nzp}@oNw{(9Qnj)*CwTk!RY$8rYp?>$YK>J} zFov8QaRcqGuJT;-P;JP;mviD?xt_If1r3;==NB*T0=ktGzKW$gk68WI_nGWiW4jO? zJ__GM!u~ncqoWZt21f=Gg_jKM1TZm%%gw^LrGJbx-Nfu$FiJ_dzMWm=`9_;s`YD}C zekf#ZnYDu2rsUcZ^zT+<)oQ)IC+1lpoHI-!BKsnRsh5TPqha}zxuUOuL$(<#C8!S3 zrQTjglX#CE$ic8@(J+uU{H+<&Z$C{{Lu7VzL4FBvUeS)2Z0wLpSzf9)8I}Ux?iWm# z>SS@XwuxS_0}^64_s{D*%N08hsToVq0{ReD5nCHd#I2~gO$y#Tc=&qCe9!l~5fe!* zLaO$&oCrO!cLZD$#SH6(8d}I@235Zyp-rd%svTvN*-s7izz#ZWHH&5YoCJVR5tU@W z;JgZ~r+6}aP@z6n3%=Xo%lp26L2EC56#GuaD5!XSO2OW8+sXQKX*UFp8`?U0j}M`s z=cKM6f+pu*vvX@NpZoJ4e!&y-lh3w}#sQTw6By~GpR@tk909T8CM;}mBC`7t-mD!2jzJ_sQNdLQpm;$L;%ek&RrXrwLCRC% zTk9txZZV4`|GvAGsic(7YVxjVOS*ei*gOzIGR13MBs_XuJJEBW{U)6_99`t|;J|wP zu)22%m3L*grx)k?@}ZFaEee|RK4AaDqXa-UHKm?(d}_BorQvIx0x{97UUpZNeGIH7ndVbCu$w^-M)A!# zl7ocVuvq9AqgK|rYt~I`nnHQL!JX)uNx%Q}T2WN`WD|tlj|GY$MATZivNxa>$6v0t z``W_n@03IOWj`L>Yl;J-51FJj%#9NkOR}qxzoIwzc*jBEU|nb#io5Yo?ck@a)uaoE>ME>x)+cM%**Kk=|y`X=K{n|x>r+E>j9A~j-_N3+zNXCzn>f$BbY z$bj&6A;quO{^TX{Agk&k>9kjHkb{~ptkSx=zX~Rt@mzuIt_`QU&O7^h`RP;w(c#$* zr!_*E?p+uoxJmAYb`jJAc^p1@t~Yv1)6QzDvB+aJ_#HZhVVst_!9=y(L`2A2{y(GG z|G0kd+u&|XIOA-GrBJ_MYW8|Eyf^uB2A$bHU9@R=KGzq+dSxF37s_UPr=kB2g~H#F zb4r`%dT*BBcd7Zp%C@0C7ih8W+qgX$LF(ZTHq)#6eC*HdU;`CQuFz@In}`V5n5)UY z{v~nlGf6;tE=SW1LI(UJk-*O%3WZJWwMY(}*Pe^z>gw@?N##GvzVUgS*Z!Wkr8rXQ z@qhc49#;W56@bDs)1s+)$IyNEUSe~j@?lVgvHJ$T@Z-3UD&oM`Dacr~f4_o9#@XEs z)TlOqn|b!Ly6z`QN6c6nh(L;a0pWSdT!d!SpK;p7QzAfzx-}u!F@RSZKmgwh!p4oE zk?EhS;J}Rfb&vBD$DYix$>eo2_stIqA*=9Msz#vr$bs9QWak`Ph(*??*|b>R=4^oJ zxLvNqqyFcnNe@!JSjQM9oK^2P4vMUtKq9(?8XKknM{pr86YXeUZ4{1#o zkGi1Ov3g@z2%!99943182>lqcaSfjHX6KjaE)0U|%mMe!(lbYL+4C{1(887~X761l z$o~(&ixkRdpt45`&P7b1k8`_wFD)~kMISut%5elVZd_@VeZqe~b9t1F6~djLG)ToL zBO2ADYN!$>rfTc>EysxAw01r=boehX&uB3}4vm9dL>EbEEEad9 z6ptN#J5MW}*_DgR>&(Q0u=Q~83# zUe4vXS{eG<$~phb_xkEKL1E_GHuU^1`;>HTCUz<7{Jaq(R%W5#eP(N;g*qF(eD=>% znWpYJFdI-}Ajrqj=G&Al+*oJ}j&#(Q?Xijd^@$6=Q-a{x22H}a<8G~L=)+@qJo?QH z4TcQajH2%-3y8?!)^ivb?w9;HPCRK|HP>%AhNlb>*m+{pOgZ=iO0FT1fdOn|GuSM;02nUpF7(s(|8f&c`Ar0VzyAo zq8CtpJfeRX_*Esayfe&c%1AUB^4U_9S zFCv&9c4&f>g#(Ni6ebegeJ&MmiYHHscaqQoLZW>b=A{)crX z61}%DSp86{VV|9*S{I2mlb-&(6}v=SmFl%^#?e#5u9LMV@6~+(*z7|(NvUQReNDER z^3_sX81Y7u=eElJKT#ev(fBHP+d>qYtnaX|*6i`Ah> zYeoO|XD3(b3KB$)a-jjlM0f;S00))|35T8E9mpDR|#7gauZgsw`n02FqO zBqF+1OD6lf1Am$7gyThR-D_zJ2MArOPu4v!-uLw&9_~_Al(V|fnG)rtH-cJG8StE9 z-vL0-4OyNPgNk5SPBGA(!Xd=U?ONWCr;f|C-~ZQ zdaNOzi^ZE}V>i2rX=w;m12F}bvWnv=jbtS6GnZ*hj!7XUg2Y>VDgw8dI%As3c`Cn3 zmk?qpo*$)dE+8QW@6G?z<4vN`DR$ z*PiB6_fSqvOOjMbr}SNG?48Dq3O7I&wy?3FvnVJFpf&)MxZ?&!MWnF5AO>^;d>}~a z0v6Mp4gfRXN2>CB9u?9_4Uh*aWvBl9PCnqFqp)98kytBO4E}QTxV0HlaAi{02JT;a8$%>KsT=o<^lT z2`bKfyKJ~AuvTY!FtPIYT4s#XkXlN-+s*5-z}6u&z=3hT3CSdYF8K1un>naDpW$r# zxkG>VSJr;7GaWgX4{dO$v^*S7zR<$J!1(rAa9eJG?^G%Pb(2rx@D?7U4{ISD@0{xy z%lOYMQv4> zT2}JKd3HCUP#Wo>&l}0wDMH#uchbW+arRv$CNUL||0&MK);^bLt6ITT8$xJ>0(oXB zwW|8*{Tz}3g7{HVv_doJ<5mS@$57)5Hs}3~NtSdlAxA~F8EzlxiP)FrI1g<&`p9%M zjohO2f4g%W)KFcDU+GUunc=mE?Rl2Hfv^cmv=I^#cs&8YOgpu&F5fqVGDTb=;w8=L zQ3A)#yd@|-sFegNGh2e{wEX!NLo=fF--hkFF`7;7(nm!Tgxq+3ip1WQ;h|wa z;yg)u6m%AQ01%W!b#no`Rnu3tCRgd2FSPnam~dXQIjCUO6j#s3NWmSwg( zYVJ&h-4_|_OY83lxnZxNyU2n_#fF*ltT(p=LN}kw%+veMCh(;p%`L55Mz=v%)*l?F z_v!$_UY_@NH>8g+ZXWa(e+jn(S@(1;K18}SM#r5KV2v6-Jv)lY6$mw4 zuvTYg-nqJ%4$Ov^3ksYOp($%zcKS7Tvfa5p`Sg9e4LT2T;fS^0Z{Ph3ESFk7EIsZiuT1ED+q8lwh-C|(K;PAj&MB@5CjCJ5x5J3`DQC*6Erz9m0 zstA~4g}&*rcAA8Zdusz9gCc}(Eq1k;$2sQhwE-w=^aN&oI!rwztfcn-`GPy`ZZ{9+ z>eFst_d@YjM!R}C&cXC7T>j-6+q&XfZ*g~O3)^b3Nn>;mmSd860tbQDY-?oB+i+8# z`gueob;?7-7NM`~gO_x4TXm-I0P;Y;JF3S{K}I!ytrGg1*S8+Bx$3;G-up$>V}deb za-Sw;on<|BCk-NawT0{-$~50>7HivIZ=YMU{1}$y4xuH`au>;Wm6cX5)X-+elr zMFB<6kZ@)ob9p)O>(IQBvD@xc4#izf(;n;%OZay}>_02&>P>kTZWemMSpkpa*FJ|R z*OwdOrS^ZjxkNW)`vVyF*DyFwH$%q1Po3TVl^UF?!EJ1Xz$O@KQmFWtdZhJvh3ZD$ z{5~y7fw{Je7iZ!MwQ`|dQqIbj{bkDjobgPp!uB%CG8@QYEjOb-G!H&1OxbCZk?4nL zrX}{RO2M|!uk_gFU3g-$fsmwZpU^xrDH3sFIlY18}N14a5R%DERB73whk} z;4tVzpuLjcz2AWbzYDB`830uvM&TJ9x`?R5criGc<9mCvPpEA7;b$@-AF*{jwiLx@ z&YrelBt5(ywA_eCRbjw3F@`FQR!3FuVkX>uJSgBDxtqXvPh0L!RPD^^T&c0ZSFT#$ zKqTQc${9|jkY22P7Dpv3Ja?=4IYmsKlECw*=evH0V9wy9*}g26^b^0`;gCZ$-7!4( zvkj4@>TSrxwOJnE$gTN!Qt(*0!*s&Y#y*#v>n>36F4xU!Th#)X&cx>dN?yQD7kfDPfO45+;PMyuyLlvzip6oOV zcJfPFb0Yvgnx3KqoYMuZvjhKi_e-IAg$DTf%yBe043UxsUC@Fhd$#iX@io5lpMwov z#4`?^3+uVUI{t^r2A6q-5HkT^lb-vZ&N7Pmfj*bKf{14~Fo-x~URIuo+&E-K#){?W z=5QP$%nbB3Y-c8Q?@8uyI7c1f74w}gQ(a2k{I4@1bIta;SBD(htybvS$PTktc3D!1 zOlxaW*Dz-!HgeB!Nr_});k_G=iD<&r@tJwv?&{9DY6WP!#&e59L98o@b}BWr-INQ6 zZ;;2=5;Ep;*g%FH7#tTOc#5TJi&0TcW!c=+CpO zj-DS1LuAGAc?R^q`P7XshoCJRc2s@$Aq(i`T`&WEIBV0DE6Z}CQCIv5r$0<@2EufJ01i-+25 z+=R8qtW{sPAZGq-(ct-fLx3hNzIVTt3YQtKhL?G=tnv~YXT%gVAUwBv1CiUwGgt+Lhpeji|t#Xfx z-9z%-_ba(|LB|_KOD40?v#0_}N@M^G2lcE&SKJona95@VIHh`~w_2GsAaSNfGdppB z1uyPri?orA`}}m}XJlZFTh#^mh3(hM!S5m`A#kWVPccV@$-PK^<0o&O=xK|b&R#ja zbuv_N2pLdx8a0VZJ!y=VrY1$kZ@m9u`&=5O^=WK(F@l%wGu9=o+eSOxZca6ddmt%beZI&dqvf?I^$N+%w02rbn#rG;`q z`Fp?*GFz=cD#u@j({96ry0P!L^(=;EhLfaR;uCHm6e5d2C4nLJ$L=1V`}72%1@BH| zyq2T=5Tvedh=PXS))Ll51mUKxO%Fi;3{mPF`SH;sJqgH46m#MS2gUF7hJNpMiN@vX zETVO;)0S7hCW#4Rv2w2O6H2lO-1-pfosoCKp)n{}*@oga>mOlHBW#57PytDDX`4>LC;0^5VKEh zq0~^lr^0)n!5cJKRK0TgG*^bF+^T3Z~2^M5mBxO`+3AjrDv z3XLjAqizw1^Aw6SGO`*{_$Fi$PQtsp(6}oeJZK}o#q!1chuMqpyQ3~l>hTv)cc!H^ zz3u01lhyK)q~3qvr&bj&)iGIZ#Mg=pxaR?N3HF6`H$%D ze-wxf$^mL*dpSvw6(2(t4869rZ#X5> z;ZLB5JQX5RyohfQzI@(9N!5x|Sh2Js3Elg(L-gD?MMRc1Vu!}VL(K@MTup6xi^*T6 zY`LVdimO(Py-}!j=Eo1bJS;BN>&mKScCT5=ob`J#Ji_ldp^}YAw5uKUJC_{Qlm%4- zw(-^*J6j=yUBo&RLVYM!s!D$nSJj89stKlRQti3IC1TF6-r2k|yB+g!BjGUe8$^8SQO{%BAoYwmu) zRV64WVnYn0aE@aeuC6I1a-={^YYYp|js+bb`;`v5ynqzUcEil0oJN>i_Nu2`#w^Er z^M8h!Do<~+B4U~`ks6P3_b+bBOUW~D{Ug1Y7cntV{Aph*k`5bR@YrXCj2c`O-Df7b z;&fJ|#!_r2AUu^pP4Y8L5_uC><R>G99aTfAQ$=GpWnOaIx2Ih zgcA@%$cfHJ4Hdj<`` zUgC>!f9xX|fF^0Zq*veM|D51}0s9Tpy6h!LCYd5IlMv0p=P5>M4Glh$Um73Rw1Asni& zN15!`>Nia+^tQLxEP|1RZJ?tG_*5@@jsIV2Ko1|zWIxet?o6mpG+e?b=LmP8Tr;lv z;;byBE&KQHL`#DY?N1MXczBOFCuT5@cL}Gvxo|9%?7T9a0OM}(WLlFb3UMT)@_zJy z9M#5}06B4sX24G*8`;`Q$ zp&yXMF=fUDx%N{z^Uye80vTWGl9^gEahG_}7=sNDf9>*@9D8iW#H22{-7j+pB%mzx zcfEcoLFEF9h=FU2r?ss1^1+25KG^xKDhj_F5$A$t_CR#51v)xL zmKH)dP3^K3w7I~~44bZYE(rlO!a>du#NSZ(D;0m!nQEG_3-r%G9jctVG2M4oPt1l6 z{AR@zGIs3|lk2JUw&aZczFuF_&>zrA9;LXxRr%WJSG2Q`W;S-Zi>xu+*!|}pBg6eW z)4ppW#Ay^QjB(jabaNd19SA2>mM^%BMv z5IW zEk)|l7gdN$tlR&Cu}p6^?KoA(z_^Idk4od7h+kW4HS?*xHwZ&(U|Y8+_*@`BB;jg` zARVbh`}#=3i`f!WkF1>?(q!n_J+pFfL~6@IxpqVEI4c|L|L>T2 z-V0>ebMq50?Gr*4HuMazyT^rqzv>egUVC`YEPN~&7;GR0M6fejyNjPgkT&{I1~`Xc zM_BGVU6BH#Sk;uVxzJuoCqfxlTxhufKJ!B%+Cf} zi25v2HsG@uNwNNM2^xbTL)C@jGp<}1tOu5_32-C(J4yAQWP9p7`@vt$gB~%=7s?g` z_5<~xbt;a4%ulUo+(eLt0{*zb{)mNbg%y$GrC)|&7PCX+7oFq!!Os6KNUynbFOGv- z>0jL%4|Dh1^95mI!foAB7`gn~IBMtz8`I0`A!{va@nXn7TU|`5CJn~}hyc|RW1_3c zkPr%7QZHd`Av~7SX!deV_3oUmIb$LL(!tZ9?!V$fm$PV`l$56SwdNx#kH3UU{Lenn znGHKm0tbal)eG1x_bF+4MaBkH`S_8ISjzC&vntF&j#Or^MQgdB&VGp(iJ%xu=u!98 zV@xdlgIt1hzQ$PqCOVN}=EN0(y8xfHH0=!I#ae^L0*a95p5oSkTWts0N*Q-3EB$7- z>SEdnGe1=)B_(<#Iw1(`?6fWPWLjRC6$;09sfsUl+yE{Y{4{8G=l{Ji7d_`>auu3POUYz&j4OINuWJPyg

4 zMtc2M)vLcS{}9mAyXN1`3h&Tm^e|a{RY}i91Ji=<844}qoYufelL}?`@gGfwlF%EL zU@gOiR>uS=sJoqj!=?}>-SU6m?tiTi|MyfRkD}!)93iR`mMH|@2?UN#C$!@mv2dva z%xr}nu>%kHfSa^iL=1>^mAHZ9nGbYDfaC9xB2pe{JKnUm9kwXQ8b zIqRE)|IkpEuQj3hkBx-@u5#CDA;?*NHv&1H2SPn%L_KRKD0{bv6K7BQ(M8p)RW!3> z)FW$Q2ZJ^RDmnolu}Bpa(8AMMkydU|HmuB4F615n=Vk5Q=+Yyf(zP3bK_2RlK7$5S z1)kwV$nrD)Q)W_jc5#0!wbumK*?y6SQNnHMAvMQ@c4LU@JR2n=jhKD#>5Xb+Q*pB2 z0NSkv`c%Mw$TurOT!B3c_otK8d38^qe_-#Im(vbVtAqoQz>g@K>>5%y_NZ$S+#0T6fYIp!UBbL6d7juz?bCj#iAbuo$5b5>)1i#}1Oh`FyWZ%%sX?-#NEdw>~}^UGv#zPNg6V`AR#!fn#G&L_6{x3P|{iF3Lk5`u-pG-ZQGnbPF4{ zAu0kkKtPH*DlMQWpman;L5lQVL^`2|UIYeF1W{=sQj}f;0TLjDfCwnPCWH_I(n&&3 z0tAvbXU>^%=Dc&(tnbJ7@BKm6v*1~E5hg&6XVexP3kD zx?(6@%i6o+t6g~WH&UQdUP0QrU^V~Qh%EvuXl3cFfBThdN8By#zE}Pn+%eUPcx0LP=4MTxUbs z)+pn)UYy`9BV@zA4`OZOi*TFrlQw0Xi`<=)f&;@5=r7PjzHr6@)s_BPCXJLcV8(B= zT$Y1Wt%i;^jblu&l6VhF@kTsk*o1`4a97l-Tz(tv_Y^5g&9}kHR6oHc@`O5-WrYH= zUH4%~TnWLezSYya1mj6udc&>(+7P|wKGo7^*ogEKIvtX9;LtJQXTN=@E}R|XjwObL z(8g^jZt1W_v#PtSNxB92!a zKI+?~miy{N(Av1vQ^q_s@QAtJW47F|5$Ot*?I<>Q@qhQewoyLD-Pu4C-c!*Y1!;>9UO^XA_+m z$jKT-m@AV#dT<#VNm;T>%?32JcZ^cz52Cx`&nWF~@$XX7r5;DXv$;sTqm#D=M~sVD9PWb6i~;?!l1m3QR>mvnJQE=7PP68UgFi?WrtJQB_HOtQn8Fn;C~V| z1=yV>M-Ck1E~X64XSeRWAaxGL3FYGV7tg941x?Fuf|U%z5xCo7NjQB%Bz3pis@^|* ztr~xCibly)VPJQIHQulwTMrGSNA51jdvB?-3yfI~J4+7fHHSN1XAE)iL!E-*?SYXY zVBH$KJr-wy(_wTWTUs*Re<7uX)ZpqD1Ki(@RQ{~RPuQkLhHFJubq0Dijp%lNkrfQd z^Q^A!RsnV})~jm{Ehwx+prXm*{@pOJezh^8u@^;Ff|pG3;1Tj`6-1NI$*-04a~d~l zr>JYmwsb@x68b<>??NixI-3f4mcv7aI{3Y10ehL(TkTky5-DoNnBbi&HkX>FK1N6d z7PVD2S61puA-3KzJ1Ht?LSg!P$~*Z(6lYlrvx?r3Rk>~QGDaJ16`oq%Ajy94$SKtW zzkiI`JZm9SuEJ&7Yd(cbESFoitjFb4g}mvzna370zza82{`SoiZqohZTgXFC_lNwK zC3lwZ!UwV*%Zog8&KPh^V*y_IqK?x_g-X2K+M5&GpWSKjsm4~8A*c}r9ds|)&ci3& zZM5YXA6mJMC+cQV;25!H>&NKO_qbI%Zc8wHfKsYzE>WHwm$ulD+G{asR%n!*7``GW zT3b71HtybkaC{J~8ZpT{`Z3*h3FW=V$@yZRye6(_ht$8}zwpxptfz%r>A`YcB* z7eMlH|BsXo3b-fGAxSBe^41xCP8NClU6<)`k-cZ=WB!+-w562s9Y-#F(qt%UT)QCm zkU8`o9yIwfVA>itnq&iN*iRNhxr6)1E#}dI+dad&gD*RU$d!|U0US|*$*SXGok2%8 z?{_OwrZx$FX-j>0t0J|-rBISnH&0O&PX9f0FnrhI0DSyVkC$1=nqkOXrw4R(3y4$% zTDEy2Ho#lTlVs6Op>szn!aseA3Bj7KJUwN$;)%(=NL%e-(BU14%itPaC5dkvHq z{536)eI8pON*j0bb9c-T5n@xl3+tM$u8Ja#*_j_Qvm;6U%8~Vs&Wghm>7t^qfuvzW z!-e7w&(~xLKhBNarV=9)stSEMAc_#A$hXybcrRSSg+!AZPPnFXo|h>@Xn4=&rS8&* zFVS0q^^{c~knG41gVqxYOBKeE}=0DQZkd=Ol`mDyeA$0S=r1;JB z#V%@2v6%S9)n2a)X7EYR=g@gxx@Jb_;8|a_XTsB99*N?!&cUddn#Q89E{v;WV(C}6mVnwPSKX|`ULQC_(`@mI5SNld@u>Ku z?PBUjCM&mzQsiY~LB%1oNt?*A^4GPwF3rK-8!8g7f{5Sf%U<0kW9-G7+~7gAG<&Nt zMUkb0eOmB`pui7QQ5EI7{?({U4DpVlP}NPC%HDhWeh+`^Hrl3=Af0WPE{_sHh8kC*)y>px+g#DeB{ zmzp^uDNqyy7HR~goOd=Kh-_GBnDXP47789)%VMnn>cRvRbK{yRI}@k)i?V~>H1Fb& zt~HLILNh>8h?N4UA+*^_#c;QZB`G|?ca_oK>}RasN8{>lqA&7zj>eT6?Oa(9Jg3wQ-C6vmjVERy9QghkXBZgj8hnitrFYxK ztV)W0*-=h2#jLJ0#sj`H-?4wtMG=F^Mz%r82dzx>AU!EF7+FsYN7ME2!JLS z`%8*SRSHB>Imj}SbPq5)gV}kH-f_CJEiIGAj@^~lUonJForaXVd71NrnLmC6JAP=v zsb~KrW;gFLt3}4@eSRryj1RX5bZ+k}Mmq&-LA2+2FX3>p{#~l21aXCt zF3Fy4>FKcf7gC;cO%^gaPOZy!k39HIdueYlRpvSjYRVT}x5V(op#u1^E|`Uo7o}J= zl_gBY_kmp2t$H&?)d9-Zs7V_3P)$bK1}!arEg?g!L4Y&2F1QZ4HZiXq0L(({_s>=~4sSMgyD6nVcjHRAyS;X$1 zxyTSSgnP(cHziX6I z&hnVXZA4Q5;Xt*u@Ona2G~uo}yd|0J>ltwR7oO7|8J!C?ErG?i(*ZmbVsx5EjKI2M zrlA&iQbb4{{^K>j5q+k=cyC^>WDhQ6x+4cR!I)O+FBM5vwbWG%!_vJ1T17*~M7V6j z3*%L8n``jo6pzgwk5AyuUwjc2pCIrt6Q3@+7+MwN&C;oRD;Xg4qz0dL6;~=k4A2*h8%$$R(1(1#uYC>5vVm#Y;o834X7i4wCLTlTJ$yrO z*2=*#%M^?|mq2cES(3;3Z{pH!Sn?*U6F03x=r>H1;c6dEIK8;(tN*2r|CjeYlS&?G%0y^`{7bl5}Wa{n(8Q|r8qILJ%vYg2f;=K#4H5}slB zTF8rb76qnn)dS$|9q~3E8Oz9lGA{{sC}xtg|3W{k`ZDWcDw-R+jjM?P?V8~j?iiF{ z0(Bny0E+l7i}Y^wTN(4P1V?T)B;{1Z<%H=d3vQoIyFzkAh3~lvY0?_f=a-_(B1BXo zntRKXsbzMNxFKq5Oz*ncqk;gxwUTQW^1UYalBR;BY@mJ8kVyUnM^eYIt!??F$z)he z>hYXAp&1duSO*K&m4=!3>>Z1`taAKb1mf7~{TO`g)9wZoDKh8G_Ra;hQ@Du!6Pf(A z8AjVTz|_yra=1C`e0N!Qbyz5&5gx{|PqL%+uqCR}u4qLfQXJf*71=P;&!-qPpu4c3 z{%6{(+QRa!J_3$p&GxY#Q_O3ToH)YU4;XHfTJ)3l>t3m-1#jF}_#_cOcPX`EVWaF`gJ|r+T);)Zv2T8mNfUP@g4*Uc^6ZJg z__a#S^jpiV8s?+L_r800ULfO^%#_E7o4dOfwqn=_i)lq=uW9fLB-O{p!Gya>@k8}V z!V>|NawCnwFAW}7|FF}6s3VHUmS!YFWj*X^vvGQ(LaSl&-=0!DFlzf{TZkMSPXhS@ zWc4e(Zu?u?{Y8G;#@pvbtjXhtco2xnq~y* zYyvo@ue2b7y+anKCe@~HsYJd_G!K84SVycT#SpKK@K-9yi5Tv~8R2)Cy=Mc7XzMpQ z#&PDs%gBAd($wx|OVjYFAOYD3v+HS~4E&4nAzhW7Q{eY_Lvp`Ak;<5er!beB{x z$1`>gb5}{BSSJI^D`|_o+}jm>QfHnft1vbc!^xq6%WOHfJG=ZcJ55z`h3Lx%#nb7| zjuj~sW1-s*&vFVhgEGGGWV5Ukz&9~dlQ~{tOP6rI1ocK5-IaT9GXE7m)c?&6v75Yt zsYq3uZaiVj)9%n3!d8?B;}1y6zHPus4}I=vp@#zCcdME=lOvbk7TLglJpN?oK{))J za-+Z8vxyRhqf?#9G^C*~hi*5=}j8q?*kIB4^JBAB8$+ z+eS?L?CuB&4hsaTxVHrxv4rQq?Lhh;CTc47&!?pWZ&K;uQ_&7FKCMXlKC>g=i{zaE>FVt`4Wk8cl1w!euD;&1cjl?`#+Ax9E1cDbv-j6!rJA zgzc|H!XZ=UV;T#BWgA|R`QDxdra*nCCBBlej<99Rf~u^wqmJ#C^KQF@nNr52ttAV> zvZ7`3Y33QR{U0=AUyYR~_p$x^;UVqvqgG^VM|X(_o;~DOJTLB`9Bfn)lIEM#^O~5! zB)7lSpae8qZdzo1INNI01?)UaNtk^?Q(nb`_$pGM&g~)3y+%B>$ac`Aw@rB_hz8wk z9~}Nr_uI79C4~>@o8IBr9r8|q;tO*#lZm1_QjW9HLb|9Gj9QKA7?G@g7AV1eyu+QM z%+p7}G=XGK>Nl{w*&IfDmDkzPpl8sYhUXuV9mA}=q~6-p>nXoxP9Fp^#wp)#<|w_l zt+$;-8_Q0DVk>H@YN2|Z&5rZPAnwZYsXFTS?!fdhA2APQ#P`0UE-gPU@LtVc5IPpy zFI987+wP7fa@7)>$v+RUIGe&JAWrpr-PwdPj=kzfd=o+@c7GqF`2b+O~5(Nt1W$gvy<&>7sTdqGN6TEtcf)}!Q z5(HFS22=%n2U_J9KKl80iC!y%Kp+nOwQSVS9dysgt=QOB+bJLZ6MY;R`&q!qILvC5 zN)Zvy5t)L;n_km9&$qXq290zG<7L1lQz86=NwxT3y@0Vq)YC{!dg%C*D<6jWR3NbX zoJ$Qj1A5@3xSt7OM-J@*0wMT_76y310(tsWl)Tg4vnl-ZFR@K3oGM7+ZCGLbZMQbx zUq~G}P6c~-ZTOvt#TGFE+0rB}w%vNW;5wT+vMIBThT(}GLo&swZQzDV+z ztf9dfL-PDl9p&W%AXsFi%kL#hW)@^>hs{`{lQfIQL!A67hQ!erv*EOt_B>8cIuqDB zAi4-?J&Z+vfG6(K;gIh_3LdSo?*$@0&{}l4Aq2MoGySkv>6 zo?nOfB@OpstNLzz?{fWvs=OY_%X4mG^6s?ZU#k?deE?#CAa2)iNK%CHc^VLK9)7#I`;)-bD$pKs&K+3WUBVFU6^CPmM2zm%?k5w zE{oAq^N}qRdrsE2Ff^7uc%(4+`FiCCYKf9_j`Bb>4VbBi&zcW+^Fk;in5i>i+*~E_ zDX)a-sYjB;;l;zdT+PO=Y9^tfo#HBft2WrA9V{iVWq~ZL0%RQD_Oe3;S;dV7EgY$U z39y0)iSHf-9|XfyX3lF`IBZt5AT>zD2^@|OF&a)`fe zd-;!F88)V9Spo~&C7R1o@cwky)GFUAQ!KlMsP%qcr;)=!oPwvksnAE_jwIYOP*GYt zXRkGHcb7G3=pNPtOUY$<#aN%UO5__+pSiy^*vh&!#LaeBFVe40ncUVHt#w|`%BNP@ ze|)>*)vn6H8~o8kIVz$wG&>92R>44Lhra({DB5dQZ^OAqF(+4*a^o3W-{onGhok3y zP`s9!<}&?<8H%6tjSp|H7W11BDY1COPR?U1USgB=}vD1_BP44Wx&zjWb!y zF7v9#{-$#~eIJryNCmByEFZ$71gxw?txWy*yp8vkLJG=fZGFct6cqMzO4r{^6ZrhA zV@l{|4G#k^Rah6O))n{(vD8qogNb6EikIhST(V^F zNGWL9()Mc_e^^opv2rF$Y3}&5MzA&eE4nHn_0P9f3qt{!w0d>crR7b|Ua$$9i4g%| zyI-WdMxv32q$JT}_J~{gwgt_;y*}d}qhb+jNU37m9M1|!hY~hy<8d2#zDuXeLgrPb za>y4HdHvAOyRUg4m&j4`u)SX-u_6FJpY&z(LUPs%aBbGJAc3Vo~1>|g|m6f9H zD${5BP$LV`{geWQ2`N+Q2{|D#n8Lfw!7l>j&a`z9vDQ4}m;M7%C&g&THBX(#FGV+S zs@o{9)yG6Lubh*r@eNc?%Sr`yStym1C&JS(MtLJ_HGq00G(o%0ri=xz4(!^f%Z>^Z zFmI`;2u$%IA+sZ@J1psSDzq%D4R_O*j%sBb^UL1lrIrIQLMpmoJL*5t@ZXr{ZxiLK zz{nawfUZRM!R5A^%8nbE-G^Meygk4I%uHWu-Y8#B+Z<^8(K+ZqTUtmvw*G@S8bT~+ zm#2+oIsd{S-oQg)PxejKR53MQ5tp8W;Z=zjj0%Lpe%#BVw!=ZIBe7~z@{Aq!bRn)swDF4m z#oFWQx2pP~Vmt&nnZ=wm;C?0}FeD-%`}7|baQSD<@*d3R6og%eyQxOFLW=*X7Pf4U z$C>syr=-%jEos$YF_2SaqD|MM;#M;I$#V&ES#>uy_asqoaH&?lz2;){si&cc3gVP5 z+<`FWy4e~(o+7~6XiA}UEWSLsbS-k$5?XojygL^5wrUFG0&l77HP^p;19_o8OTC5J zS;Wb}_2oR-ttHud@ptb2dhtfq9mpeumSxUtz-vTd`p@XXd}^s=xfCF1i@I6`BNV(p z9QFF*8=zlBah2&~8X|%b$|3RbT+<2`@k{+mvQ7cHCbHSBpjOi(T_el37zL?dtW6Qb zDnuTCvAG7QzJ&7ab@}8d!zgkzV2}u_IQs#4q+leSBGXCO!7wR4vMJv$N#Ijn$DA17 z%2S^5*&r{Ct{DsG*2OO-j;(8};lv}>K{HCbyFf#7oK#K&c_x!V)v)&eA(D8$e*YAg zrSlJ#KMy?#->1L40J5JqN0d-CVJe3ANzVoWxufu)$ zzy`gRoTk>Sfl1|0Evc~!cSt4~T9}L1&CWdWLYGHo*Xa;G9@@#RqfXi`WHOxQ@ifAl zLxGxQpmJGs!Jd4}ZSf@~`$x98 z^|1>|)bSHp-eXe%)R}%x#3wer^96J^6w9Uz_If};dxJX*5*}F?VxdZ#6_CIN#A?VY z;!FXCyX8`o1^98uP74UTa{_-2_-4gltSrW(hX^G_pS@3bce8_##MxBYUH;3CYw~mV z7Y>SgtPUYNd0K_+xu7p~}xWn^5~yYQ6IF=uA75Yr85w0l(W zdeQ5khwYo^e*uVp9o`Dqw8VS?VmVho77`@0i--2exoNy-(qyb%AM(msDL)KUQnH0j zY-+)y2!H^<@-z?kvR>L_r3JKJ2`0@Q(ozRYvCa#z8$Tl;-w2K`CvLAmnb}$SoqkQ+3u3gDS2Qpun8G! zIU7(y;-_AHXKd7sgTLeQSpOk7qryieu=X-=vNOMrh3GM{)Yy^hZr=TXvL4-35N@>? ztr~E>n#7qYV@cp0ip@I#%PQ#&mAcbhlO}~D;nrR_Xk+Q(;q>GQKd0WS1w3Gr;?ky3 z--?CtcDP+;nh!}W+@k1gu^0y-KBjSLa)*69MZ);1Jud~5mMQFUf#BU_hRbu!x(T^g zbh1vGn&5z2dVNFS{vRiGzaiG*mqP(FJNr_SsPW#!*4`y$ldTc7soM&~Eqn*^5Zii7 zwy3<+#%Sg_8ReMFIYQz@e&XFkFRwigx9lZgP-itG{aR@h>$NjGshk+qZ)W=BQz1FAOgWIhx!wCcea#a;)5;I14ymjdvD0+nRLOKd*Y zX)(4@!3q)@$`fhGrBVkFsXu$*q3t$YIk>Mi?O`*}rOBEc*Z&h~DOm>|cfjlg#9%k_ zHUk~!Z|bt;we74|1~#swEkdrj%b6btHI4GLG!=nvzU!G5oI4SWJQ0mDcTD+!w5rbQ zAXUj>mia{ArcJtD9}+L-N%ldgFLLaujd(WR-h3wA{aF%)g!_t9BF#jVoTiH%0E}Y|7EFFS*qUOyDDviry2&O^Oamk=`}XiwHLoRd4fNT zJMot_rYG`~NPawml?sB{j^^l_TSWv|;#q44Lde>VMkBdnqIOcb17xTr!~WgwPXD;> zwVMIq(|})qN3^vr)BFuPw4TxAH+pa8JNMXvZTG@IG%0SkfhEyHbuu3)1Wvb+H>G(H_G=phQg+Hf#uiCP-)Z~ zd4ZU{{3oVr_EPW>C^4DkYfUC0pmBF=-ogaMK_zn#i=q&X zEStt@=AYvn-Mn0TtQX?3pwbsgCKdP{P>J2NtjZJae2Ql9cPQbs;8$h8A^rWu#_z2Y z{br);-(|TerV-R~`i?En*d8|hZa)}B^-`ru1B+dazQ%pc`?;C^52CC`vRB@OPp)R2 zu$+>&GS2Asm^9y6`J(ELwXsyaehwxKc5HOwujtvX{19HkL6*QSSqa|aIMf--i|0O% zH{My%clD8UvED}D4x_xTFwaPrd9mvPIlmBVH^at09eEy*wDjDeks#i=HL#}Td4Ex# zHLmYJy(xb{-kR)@cR=YJalrNcx0@Ws#aTXia$cVLj8Y)_lXB$r5gE%L?RpHa`&Dxv zo;xXB2Ysr%LmjA45``H|L);B?08xalq5AvevH|T))KSMaSYJKARGL@0sS2C}RTJu3 z*1#rZoaOyZc)yc&ZCQ9?v;L!rn)os?Cz^S zbn*L9lfs$X^SNsAu;dcrjG-y0CO!s0I@VpTOs#+0xc?SYIyqE~RF?1TfUo3T3>V+1 zDUM6$hDj_n7EYW;-+&8(;#tzs731Zdwl)w@vo+#@<+J^{JMR9JUq6YBx64$3$G0aO zfKAKbJ-T_Xtqr2gc=M+XtFr#aIym02yuHl4^&;@CC#*gRF{9@CiX1E)SOoKdri}KD ztjf>GyI;wIWCqw+h#upk-5vva2>@x$z@og+;wMe4gut|@$pCnh=3k-sH#{C&J=FWs zsBAr7Zsb;RX(!afS%{Uk$)lsMW1ZYgiE{?6)@+hrvT=Bgt_r5vlhnsXQ45FA^yxS~ zp`!O!0>W1N(v6V{>T?)rsUGu+_A=OF@`4b`ZJ&fFk&r?qsrbBebN{dvYpdiq6Nn_Q z*J3TDhJV)LC`td~u1kU{pf8jtck#G$N@4j%Pi;p?D+3*&TxCaLPY($)q*FK!UV8+~nl zI->visAv(lxFy4)#RtT(7$g7b;|){(0 zoYi1YE#efl8@#QSC&n!pwHT%I&BOD~msp}U_L?x(5eIR&R-o&eXxRXJuAS-TZnA5Ec4c_yU!J3VUhUNoyJ)(e|o^kQ< zMq%rs@d7vGMZmZ)yb+sCk>5+S;8%*&cJ|w>NHhIA$~)fe971*K=^hSudjs_zL;F_W z)Pc4`&OD5zQ49u`G>P7K`cLWY5w5U(u@v)wO4QZ3%uC|7?)%OyY zvT??Yg8^n`mpO@G$dC6=WLL;6vdRHSo4MW1yc%**9Dbk+RObdII9ir}rG6~n-)gC0 z^QWHm1NE(629d64&WfFYdQ3A4dq#UB!yg`&u*-hvxYa~%Xsrws@Ks%{*i5y!vr%)E z9qq>U4ZrjC0>J9AKmrnneJ5jMS+8r01ZnAiY4W0;QMwaeq6Nqd+p7Zd;wl23%l%9i zX*$Bi+$p_Io#vcQB$$o=2D>xLSbTl!(UqL6lC3W?T^{34S{=G8q4hrAuZprnzV^!- zEETu|-dLA@rV3V!y>&Rp3nghsY&r~&>?Jv;SpTbM=L|MI1oKeso0gYzroORkKj(sxe(0YvyU+&aGG(VU=NSVWSR8kIv4mz$rUcYi}m3hsQ@cLS}me-g|E8(bP znR_Z3qWe7s5^!lMa4tkP0Clq)nA4*L_axdsff()1UF|XJ~0@ z=BXltr{N~@pYR9DdtPY@|0vJtOBezG@|1n{^kB*Npx(=_*_|wRV4Tu=kzDxGN}o%$_!-w)rE-h@D$lZ%n4r zH*__9nU91}F-`N?FBMR}`Ug#+88dtB1m~$OULm8;t=0nl5RgRf15l}@_3vi^=O^;L z`{RY%LNqhk54BU~6wcLDGq3Mrv5B5I^zEXMLMRWQ#^;r^I7(bGAKWX{UD1iIyOx=L z#|s-~pk}@W71|X18<_s9gN(iXS1Z%^rrxF2pdn?GTIXxntU^qAlD2?B@x;m@GTeM$ zcQ~NqQ=eDg>T|LLX{r*v7baK_{r(Ae?4)xzmcFK71l%zpeD|8oQVw+bUp@%B_vG<1fkH52{&B>Ys z0Ily9>GBCCsE{07kk>;C067MVo^3>oexPO&|A1CRTiShqg}Fe2w)GPSBcVG1s`}ps zI+r5sQDT|dBQN9FtG~q91FR7mH|HxKk5OdMcQ?9zJ!jBSZ-C@fN_*>cT5Ids)nIlzP5@g={wAuBhAe5aXx{u;&?d@N z=Vfg~2-=_j7b)~5C?ee%Y?8(D0+D*Ied|}2N@hMce)uadn+F;-2c5=RyAhC^C31lC zlR8wz^+LLRQrM>IPMehE$Up>6Cd6A$i9T_+qO6ilnG&7>f0`#48u9iLK%IbqdS`WM z{b-eq4D3H?oYHQ5F3D~E1E5t}c>~AT+tH9QpPFRP4iAr=i2GT5V4i1e2cUdjvnZ+k z)dl`-6?RF-gS_h_`=!aHBACX$7c4-{lPjN^jU6qE1e6QGV&bp-_ocE84E-dl6g-WD z$PmX(fRp+#d#wi{n}s01Q`O2hS(NDn7(dlx;*~L;iqn?pZm?aOJp45Vm~kVf?AF8~ z^g`CH`{AJh-CzTC5A#+0wIdzmiB$b`gkDK%*=_u^KKkQF6^oud>wW`vcIcjWvG(efvQVT;7sxBuW73H-f z!?zQ2M*<_@%~`4Z%UeInxk7-0A=Zk)9`ws>F6oxn5swxkmOR5((mJE3>Ycq8EC+Zj z1Dk^zyuEA$aVnb&P?E!fP@!}Az3t7k*a+tQVW7&q(jPY462UF(jmY6VkomVDVCQur zXdYBH@7RWY$&RF--}=)NQ7$S9 zAzprj3iT;LTn2%9OGRFLk&=_nk{l6;aMFhvBlMdAKw$IJR>C4GcrUhpp`e2;lQmoK zEjzk@O%gOHNWOM**a;iZ9K6?*&eD|x^PSW*hf+D|y9V?; z;bYBi>J>-HB?FrMckT(nr}P1~NMH|e)vLMRk_W=0CIrmHvJfntHhMfbyZ(ga^4Y2c zuMYyurk!JdA66y{nVsUCChCS-Fw2Y;(@CoeS|PLZ3TSpX)T=up8|hnDrpvpkd0@9!RQ9Jt@e$o{z?(VmM zlYC0#W$Ex$$^vY=8k#CXdp1JjsjT0Sa$p|;(IhqsKX1~AYHN3UFSN8$# zACnl2%Wt!QG$2&BGt=e}-a3hV0K=v#KphEShR|@AMRDnJ*c6rD+l7!pAK;%u;NfGQ zPFH_=2GK7nS43YnQ^46WyRIMBcl2lR>ZfNhb06@DpcYD9EMa8iy}qNy%MMNg*OB5) zerwOxNwQ2LpHgvLm$q-l7&~_@<$#a*L&=!&!)QN%nU%#kc1TfMwxsny8W{7rbzh9F zaJD1U#!k%#d?&kBIN)pa;7lBOqLCEbT?P=&8aO6?WirpQdf6jW8>qxOuGma(=YD2m zXK1&oodt;3<%)+Uqb$|%EjAeVv%apdF!lAltxxyzA5#7Ox|k(;P3AUq-r?Rl^3VEu$J7^m z*50}3?|tJ>;zqr`tUA>=bAc@+`k(dnkf|^0jjEdAf8K`9)68x7pP~Iv0vtTjn9tPL z|1;bFXSR#~&ukAI`u`1WPm$h*_WmQzw7~BRXX=XAl~{b|5>GLMYh9#Cjf0Ut4zr4m z=V^UT#B)5rm+a0VST;PTZnY5b~l&gMMC3cMkVw2ebbC<{-X%+Kk%(3;E;}=Q|VG_ ziJKMNVhNC$0)*y*#7f?b9-~T6Pbvmgcz=adzq-O^$3rIOnK9JF?)blpf1cRW`?a6r z@~I1Q3NAC{nlCNJL37Ml`dZb^uMS1u%8ZvAEy3KZ+~h?8P7~f}fB_5RXTkf7 z+*rUzf#b1UN%vAj%8c;t48ymcsig9(hcs>BCZudnz(0dOOiX`s>#&&Ch!~faICuJ! z8?VeXAtomjD<1@BTuSANH}N;DFE{&SX7;#DO(2PRW|L;@CtKpuc6Ibo@M48$+71Rbec2X9vp5-!&EQOosrPqRfi zNpLD;L2iHvsU?{5*$x@4a+^Y8=@l)YRoPdzq+()K9+%K=n&2yN;$F09G(8B()iy;{}#z3?+B*FK9l}3 zfFR=%ayOTjtSH}cQFn%UHfZg_*E~{PXMW8IQIGzIuO7=4LE?=m24QPvsR?(6Aka>6 zD8R3}_+lMs*|Kw^xmWO(~=S!#OG)L+eW!j~W7u4_;5bZCUvl2Fi>(Y<* zD=o-QAM}WP8qGrqKJ}GD)Ty9#c&Yr7@nIw1fpsyZ*483N9H$|mf|CpK^W6>ODG3U>c>$8*S{=<3ox(oCv*He z3KbR3^oBh|eBtuQdtI?7t;<0YpMPb|rg0dTGhDdH%z0hl*7y_?v2#+#J75%H5UQiG zPZlk1T%-FPq&?E~S)kI1;$*&~%_zbEh+@TQ_FACuS^2Gn8YCOzt_#5!i0j2n*$ z{3m-FP&;x)e4)gZa;`f~wHu@P8i%{zXF7Yp1{E*U>9iOI`LAWqy5S6tK-Jrm*E7t1X^V2{sx2 z;i@GOY1~Ul7s1wQTx9bMU0Ms<{8Vu^?XiHUm|^~B@Q-lKvY_o>+F1q9rSN&#(4V{6 z?x_@*_L)eWd+aoIaTzq7F0lwK=y0W3F;jBoYtAu$`TKwP*-uZe5|4UrZkQ7E!^AP} z!sm?{Dbw-=)hn_RCNn*8BenjGmzLy(N-atrT|K`rZp$juvRUn(Iptta!#=p};b0Lr z^07y3(!E}bL(#avLGQ818N)XIq_Tiz>v(hX^>;Tb4ZklwZ2hoZ?ZqVLtCn8SpV`DX0Lfma;Vn=QNP*ey&s9=Jckaz;HsK*vNk+aizM~A9i41zQm>Sny zR+X5lgM}T65Fyz)c4CIh!{M&~EZec#(^b#>NZkyB+H$y7M^^Ft+)ZQJgb-J$B~Hvv z;PtOuNt!XwK++DM7JqogpEDJ?05}mnnsE;yts`@yfbU#gZos7s1F=-7FjS+O#0dK3 z$!4svbO5ALz%LfUeG zEziYyp==i|^ccq$Xm-`J=AEE7hx6P)!jI4I4P;k9P52-#_B&s?UL+kmgjQCrOJ}97 zSH!5gls+sO2WeGi6%@`%iCfl)9n%jzg*`m>$>YJ5!Qv6W1Tj5?u030rSB5V!5z=Wef&u@JH1CF8se_0!INOIlTmk?IKDQz^PKo{wcyx5lUA z%^Fr81bb3f-;UJ|F*^_n<#XEC9LOC{I{p$SPS&1_HtwA{!Kju?&nS?b{f%$tA1*PH;(r;C2ls}uGE(j zpHW&8V>1gh9R6}G)r=B->AOYum)9aV=}Hj&(cl>{SGADxEXYrqd2Pwtcp>l7C*k+8 zW*COaOnIbOvWt=5mqWOvgm~s%4!-rJeLJO`Z-5yXQH@4p(D~%RP|agjNNmz|THIfZ zqKS=VIrGf!HXzWQ4godJ)vOLz&pdBtqrS7g`!CR5)H)=z^G$OrPG!%YAlW(Nqx0d` z!*rXFl0Jo1MRs24rv&Gw|C}V?BnO&)umeLDXrI45DqV`vi znNIjJmqNAUuS}fFCt#zjoPsu26@}{i?l24C(xvK@7v>!Ss6!)z5v*m~Z>ZviwiV+g zNXP=&4YK)&(HZ1gV-J4x?$OvT+EMJ<2=S=+&1`dJ z9r8_~THl!?75M4tFzr`o>m#(SdJ_J!Ny1e~78I~@SZXCx3=_+S(Rg7@kaZpH_^^)y zK>zLTEjDM1D=ST;U9UWH{m&u~Mm3t!OT72p*rc&z!`$B`L|=*rw0sv+*bXlZU5A~h z2>!0jX0ESr;YhxcowZxkvdhGw5k~~q@*S;D%B6mtu z;qxpDmU1{XHsTR7zbna!2X>tAC8*ILzYx1rKNsYWzkvc~mo>Qy0;VzngI&k8y&_O7!PSGPNYQD+7^skqijHA5Y@8!YVI`)2BN0y;* zoofN#f}-b@F8~*TuJQdY?yhoqRoqo;*Qi;fNYgZX_X{Wl&mG2rK_+Q%C-glB*x?xZ zsYZ#%3%_^3`wBlqdn~z>PaGQ6=#mARJRS9K=R>nxX$7oQJnhhWZuzV+VtjRk^i`^a z_=2;{b6F}C0xU9gO?6r}rY<9vBZjVdgdZ0-^_0^arj#-<4AIry^e87T-Cv;|9%Y9;)lDp&Kx5Js;+n-S zBj#FOHsxbO&M8_Uv+|B=ZfvjP3`!$+l1gvygvfBp8%%RaVNMD3rln??1Wg+ia46uT zQSAl}iI;F8B=>icbxNM21cSKDtiLied8F#~>^kRc#~Lw7z|i#NqyF@)C!D|enf%9= z4>InxzH?n1Xes$$f3wpTxIs(b>JT}T$yr%sif*bwK|YU-sy}IpTMXhmGMlw|&8skd zEb6i={tq9%y^D(zm)-DiL_IQeX+N80j2P^S%d?)~*Zpl|Zn`sSRYXQZwREh)3rT=rD9%?bQ zO8_%3BR=c@ueleW;sgz>t#R3tM+m<+Rh)g`S+7x7)y`JjYhc%LRP&a)Hd(t}Fmb0i zmCegym#@=9idi6?d{tlmU)5Z@{u5)M!y1>ffj~l?T<`Z<@!vMmDz~}W zhJdOVCc*HPHT}w#$yarT*ySd2mxhbP9eAq$10%v~eJs*CAUBI!O2;0^0YXR5l#-YO zsgtnY`qeg3A^|IT+Mg2GC0>-GWI62E_7Vg=m#rg0NhXk$onbweD?sHNQqMOu18^s@Ah2aWM zH<|zHwl`bIr3JJp*nT$X_I_Re`XpHa?}r-5X1XH9=>2~Y!~uJj=UYps6EvJ2y8}zS zhQ@X31fXF5-#WB%H&6V!=UEah>)dxCoJldz)t!Q6$-43nRcM(0+Na;&Mc)Z}`5)Z% zGR|EmU$Z#j#yrQ}{~@eT3=|73S^3<0R>H2Srz36B1%rY*(O?bVAQkFLogyi22Ick* zAKs;92^^`&i|+tR5L~P?zI_rzJV z#d%i#4_n_DUfH^I+Z}Xl+qTV)jgFHQ+ji2iZ9C~W>DcMmwyhQ0x!Gr*eeU`0cYm(u znLpO7w`SF-QRA%=vWPL~sCLtsKL1|0gCDL*z&p%?U)x!kR&F-=M@qF?=?zD|V}9M} zeI2!iMC2d$`zNsIeZUl2;ntZurU278&@JD~;L1uZm3Yw=KeExVxpGtBKks}&tn8{S zcFLz@IsH2K)o;X4yDv6j&?@g0hhO{P5&ZkRZVeP9j+^@Q103GYt0-Hu>%HOUZivs- zWb*tPDk>^EJwLPlPL6u=kcE~UWj4>>Pka_i60Mi=f2@n)AdUPksg!9(TU2V=FiceE zSp8jHU{U{ouiZ%5VBkHdMd;p2jBa*MYvn6qs3;mfLI$H6!VK4#?IB8=n?(mCDN08x z*R3>S*I#dp-!4sz(&-qM3SMRWvHnd9>pDnPir2cRuQGom8BMi;w^V(eG3ZkiBvZ%> zu-P9=DVtlsIG&cjV|Y_0D~MJqtZs%pU=f7Ya@_QQC0=SqbmQEo~%~t zC0*B@)h;pFty2HOKsrv-qTJ6%1!zqb4F70xPStbI`+ccqYc%3U_@x%rXfmwoBh6s6 zJ*N}Y!+MI=lS(y~nU#J}Qlex>CrhbP^D1{OjJU6s79>GDbT<^4X-&1}2#4LV>kfb{ z`eu~y_+TY(I1uqKKodE_}L}8;F4bWJ8=I8Wn7|z&#=oaH=Vo5 zY}Sge*p@c6?HLmo;x#{}ocN4%v$%nEiNBDgxU@KS)F`Y_gDbtwlL9ekOw$-RDNWQJ)FcmEYz?Ea)|j zKO0lPQjC>MmC>E(&rLPfap;Dub2M^!tPRylaM&qlUSe}ansX?hn;x63dqEfDW~gc8 zZEjXrd8}2226pbzsVWtSlN4_v2UR?_pu+F*QDqJXvp>5m9j3S%F#nTH>|RA|-hMzh zF@ez>{as3TVugWrI`TPkUu6l{3|<=$0mYu&aGLM0Q&bb8lXdA{InP0HI~MumVe-t2 zmV{wBmdl^cXpuXh#dYPQvi`#&%?8*wcRX$kbXsI#X|Ql^;P<==d!DA4Xn1U{jQBhF zbpsGDGt>Y+Py1U}Sr=a76xnY1^eubHS5-LwQsI@!fxzz>+}L1(<7##AikAL#!0c&?f#-j{-6;1tXKF6=hUe0{{WNFusZcJ5 zQ}YmC_Fn7DAi`gP)vtqEL07jr1mAr8zSi4Ja!7pT8RaoZCr@#T0JkG3b)|c0g4Zo@m`7bZmuM6r-`3p98Os=@JvuX9$ zOuZgURchof{Y3i1>eKG%w;=wKRUO0T#Qb#<;C8$p^!)6*UX;e3yymGUH>F{UYvU_H z7vc9m)8i?bU+aL0_wHn}`56X+rvJjA2G#{n*F6#3sWI6G^a@e|1TY>2zE77AZ6 zmp2<9TDxeb)E?TE4FlBC8)XrC#?bBLR=M<>TNap0B2GI^wU(I6`4F>ym+8cbL zB-X}n5aGOWXGnpUWFYa#%Y<0rHS&8FCEE7-uka5vgrBXkD4GU!ycV0n_^c)?HEvi{ zj4jlSEfDP4Ld^Es<7lmFwwAIJyfm8PysVT5Oy8$|`X!&(@{{V^TH;)#uZOTe+nqie z8`<3YK#1qvUB70%QKcCERM*WJBHw=lNk_ zQxKHl&*OeHP;Z8d=2LWRclV2tmpA)fP`V(aVmXoLOkfcGX+W9Z*F+y@ad z$q8m+(SNqlsAnMko~eiiEgW5=rj_YapO_9zlAWWOsjU8;aiCGL46=w$djG{4aVT0% z-R`qYGWFlGpObP8K}MgWKe3#gXJ41{)cV|42%|Z8da%D@j84!**aBYZtrHiTgv3CQ zdwa0&0)PDVM0<-HCt5?-<5#JPGh4rspht-{_2Z!ZI zBGsRONMMuckkY>lpCwPYQ4phq|Joov=HF9#q7oj%rMbOhGotBSRiCsQ9YUAT>QQRY zAzto2Jj-4Uo=zu$2WOnPQvYKbV2HNPr~@ZCI?;%{1%+cmjjw6CJhP5UUGDhb@+8vO^@Y2qar?mONA1jp6s^w+$cifZ+ChVQv#+Wmp%1QtydYY znkIJ^Do%B6YS8H)T%Nb398Tj9@=CntYO(({p5gOlyR$J;Ey=q0P`J%p@^&gn20DwP z5?98Xq^(YYU26~RRTqG&UNP7yYu(NtrnpR+re?QVniwp9cLOg~!JluB3trCKaSAPSQ?}-{J)_#^SV5;s_S48tcv~^sGkjRV~){wuB(y_wvyh_s-Cw~jN z6eJ-&*q3~lVx>TpwXMx4u&ktP&apMAByCY6me-zjIc=M$z<*N)pLy5~?N@s6!5SJg(32B?o^mwYhi3f3CWqA|6gB48vOgF@C$qf# z^3o#f-#Gt1=qE(l8#Se526l_BM`QVqcZ=GtLfe+-M z<*Xi``%!)zB>o}bx%d^azrkhlcTkbqS0TmIj(&7Q1}f~?NT%Y4ja6xPci-z7Pnnyd z^i)j4$Xe2SWX}sUAw<;U4tf}vk0%oyFtEbUO-1FS6BA=}j>YSqlMh)4LG~6hWm!Yt zUKRVCLA!Z!ejn^>T&}b^9-H$i*U{P+b2P}Ht0X~RSE|>#=&EGQaR>br79`3=RDh%e zLj{5U`mdi;&5(9_2CSG{!U!j=#pn7$V?7#ha+o!Uq=oakWeru%)K0Ik^WV>I^@s|#XR@SBhLGJub|gFMU7Cwk z1NFu&TT+ee?JaOSlI6R}DhQyZqj2|+SrN=Zp)90tJyLme$dQUA=u^yyN}wggNZbje z91^zXo|y;A*?TlXt&!)jC3Un#cG3xaRu|F!gkA4qo1d*9KOcbVM`_9!@Z{WuHZ{Oa zd?U*DoC#RL0avsn%wAs;>{)|4KC_vejkE|ic$vvSM8*aa)5VCV`%!~jW%4b_b?8cE+JVU^ZrqX`f1yIAY)Q0I z>A%hRk5%D>1>H>Io)^SkUTKJ;M2O0&O4Cws#@FQGES2kSPBa5obpLY#QPDq}nmEX1 zQh)0DJc*Lj3cTGa!xHdk>OcKh8x@0S3qx(TID@ayuImWHjpBy=*Kyf#gFLmd7d)DQ zIiZg@+$mr#2rWrQ)J*911)aUx1*Kg-=@9&tA z$2hCCLiAvrN9$%krb{^<7Sj3F@Y57ia$l)g;e7R1K_UkXVvW{I#@bofw^Lk5@dv7Thb!nL+e&(J{NT`|<5<|LGCEnRL zgVwfnEN5<)MpQ;+Ux&DgqMSxp7fsWI z@xDgIUZ=c!TbM|u)W6+37m+gs5fTQbmn4SQKZ!=o!eNJ@^O2a__1ue|O|fAgSFn40 zzW-c@a;1)%>aVZDuQ>Qi+HoeFuQ_RrpJAi__qKWxr?V-D1!N?c!>L%nqI(GY6weCu z1HRf6Ogc>vD|-A=yx*Em-PVF3Ncfmvx@mo10s8Sp56H;RoF$QqV`Iv@b4bGwOMS}) zd6HUb9T0OSJlG73%{DqQ!A%!htEV(dUdD**6{;G>>ewxA4+G0U*Cya&1{inPHON)t z0gDEtleZv+RFZ2jnFCAaHM72DrNK^0P1uaU9PMh98EN8`yH2*gY{NumSejY@O zt@55=j|uJQ@}EV{y$IP{OXlS+Rk}=OavYdTZj5-=r~=l1;>V>c;Z`5id8hZwP87#R zcQTz;joCEu+ULwCNa*+)3~HStNZ6xQaFP%m5i%L$K-WB7n3G2|5Lx7}ouF9(-;WC_ zX4{kKkUSL)hC^!IEmMWLW5E^@a!n$S$3#5TZ1p0Kvl+o22!&)9kCE~E>RKs1*o+67 zdJLxmDA_z1>@lm;M;9_HSu7F9_0y02hr>4d*-ig@ZqL9Ma98ChfXWWk6-6t1c+#8Agbt# zujL>cxV4W-DmO6wUM_IqNDTW+3;$=({oC3n{y(G8FWXA@>R%*y4H8()DgYzRhH_@d z7eV(#<94E((U>pmj!UkrNYYKX!uS*#P^RK*?9bBj#E;{k z+3s{t1gFp%{~&&l<+7IwE10Kjc4K~+#6to=Pa&I;1wP;bLv!%JH2bYS)) zEl-j(6^o;)ih;tND^%MJl_D+QFn+q$`;yD8uuSql;pacew!00{4l;Im@POEF%r^C;F<bJF}w`Mb8m)~SEJL+@fdF)a?Pl}W*x?ja9 zFYoIugyzF9wV3;(g%-G#25Y~cCj54;1y0Q}Gt6(;Ng+-5M!l4O060`hM{v!JMNrlE zH*_>iCBMK3F2)D)chTwOfWs%Fjf*aMiG8vV@^~hZ(1m?fZn zMY&~F3!+X|)#FWw(>_(?YjXqOuFm(QVLHxLvIYSDEMb5 z(TE(N#lNFgm7x^{Y$>v(eOyjSqJwAT|XxJ=i&p7~OZwc~sUd^{X9skiAP*dwFjB!emQ8K?2MnnktQ;DkhYI8)OwCg=y9 z`#j1P78{#;4OVghf;}&q%d9+gFb){BL#UKZd&OoHYgp4@AGe|grwX)IThWW|tD(Ne z_lX{xlRAKIt+mqKj%$I0F|9=9)ET2`w<74B9!12WAbwLmQIxV?-HOkWx%R;44Os(w z=tNPdIWt9_`8haXnwUV3!DY!GJV=JnZTMJ6x%7mRnQ$LhcyJ>EX}$pzHR_6}^kU3( znap8D-)5tmB)|YQ9y$&?Tie&f{O&>Gxr;#=wi@m=67m`4NK34%{4Q~vaICmWH8Z4+ z&}@9oj<=>ipgFN*hf129C50;vByEV%&KFE$Rb3^0@Ew}0{0~E?*@y}oRs4PuSCxO+ z_tH@r%Xp3<AzCBF4ECV5zOArUV^;AXGbTM%aI(N0T8*gr0(IXF|pXIbXN7v zFLKQ4_ITM3;Oi2z)f+b$8=oAeN(VY!q?{^-F127^seQFj1$#=x$OLJ%j%ra>n*PSZ z=dll|Zz}IjYDi8m-7|xFzfKvWWhzf8E_5x+{A)lxRJK83~-9TUCIU0`AI0I2YFNuNOtva(gJ;iMWnFjUE2SkryY<8S9$RyMi zR4JN84LaIxt*JoYt7yiOi$|jBlI912|3&cli#;VVdiC4O1~fQcRM=rHF2$tyOikR( zdjOMqy8%mQ&NFf_U`CooCEEKVufTXPJ2qAIVkNn9&3`ca1ruCYAzS96<2{zR5>JnH z$Mw%2&>=uA?AI6psH|zohxhMsJRg`Hg%R6cywJ(LZCf)%4DG>yh)6C1^9Sv`2CzmQ zX?=@jxbu!`VS5YOOaqRo*uvBp!IrAJxDFTST^(JB6rYD&N*@kdqzL^H*+3LHg1~Nc zHd`hM$`3oliP51LK(-=Tp;*R4u;<|wCEbYlz3xRGD_wkHLy~=+^dn}$$QMcLYW;`a z;dz{6n2iu+A@<>8C4PRmDRAdN%R|4l#o$b5m_OoQZ!VV))?p9m?=2UQy}_Pt4zD>i z|EyJcljN`5Mkw_;qi_vgZ)G>x(Ey$=Xg5+}zf9uPvk&V#5x2g&ETg^vW%jV4EBhZE zhw;}q^%3bx%Tho~W%(=!{&&H&)g!p&CWTI+!nJ*T-V-J)r#Gz}1PTrR$|X(60?7m$gFLF4@r$Z>>AwTKEdJNJ<;c(BcosSJ^{lc|+D3ilH?LOLKUt?i zt?*WG<@p?Tv0^3$m!WdvGx6#u!O6bGFV5JPWny}w^`GK!#ZRXN!o5h&lTm=aHOZd| z+zGNLlRJL&XnVq!iWk}e6Vw|kZ+|Q+^)bcwOxnnm9M;`JmFI9#CCO*PV*=HZo*Hwa|TUw%Xt=(%BXatU^DlSv37_WApX> z<=$2OarMQ{rBRoL4bPJSEG z_-!DJq1UtTZAd*HCZ&%L+g-=1$jMFeE3KzVFLqao!!F9P_Zx0_Jc(Y^suogQzBQt+g~kcrZwkUWphZ;o2-ZZMSS^2R3i(V!zjlf-ln46jS)pt2OYDn2 z^tu1(xTAuX6UDC8>~33P&ptQFtJvDmtJvPzI;0jYu*j=94Y>X-%73FjDMgjafuo|p zss&Et=Aid>$ zJoS>{u+$p8@xOR1(}QBezc##4?H>|WX0`C%T|pg9=^tM>Htr2p9l zUJ)nnQQ`hTioHpVO9N$)cO4;KjNZe++VMyD=4PNq4TWcaMw3z)cv+!jC2`;(wRch~ z!NMU&2XhMq-m z3`lADpiV_7FkX=|VqV(ZvNtZf^rHgNQ<*H&I^HzBxuvXKbJH!%IyT)6XzL!jk;Z2= zBmgbACSw3qzh-`CGw#R7=H1_MpeNQwB=+S9_|bo=_~I(c1D(aqb9}tHbIOXpO0T*G z?RJW93c0{<_VQv9E$4V!G2=J4<*#{wmMLhA62K+(mqwkgv-6={7qKN?S1&*qBcc$f|ZAUnN zjV*yqmp$=sW)@(QF8RpjlNoeXmfQD^>&3+5H;zqCxW)AQ>h?k$V z$~PH!@4(nAw6yj+Z<1-hFP`(2tre?MgMoeBxA%V6g%H;KxL9p@+^W-n%r%ht6cw4R zmvL!=aeP-V*8>B8CT8qL;NLh;CyA?*#CO;)zGv+n0( zcHvEZVU9IY89G8OY{bqN5`<8qJnKvco9b;&IBzp@HrL&+LLs3J1C-2HNN?D>7QDT! z&dHM#%Y*xVNTKJ8{u0hk(?|xooQQDGT8l7TA5>eZ5vQ7h0o3GVhva*;xOa*ir5ng~ zzv9fE^dY|q2YopCY@MyC_^9Q;o2uInzls#1ai7qW+UvZ%n>tDxC~+J2#UPvTOn|<) z3{=nRhDd|r&GZfJoJdY?BNm+oJ#IFVh1#%TYVNFbcGULb{iDj9`hT%hi<$tedEs7`72$?kf~;|YNy+N zO4#Dbr|S_tByo1dMvX5&iH6oYzfMp@&nTx=rIvq?9?R@-k-}~mUI-s##^jZ3hXp;f zavf9tmH>DH-XXo{eWAHBOmf{ei8qQPvt;+Tg{7GYXt+7a7$=-(gp7dWjL-EyH&`&` zY<#9Nw9R9b0+C0&QVz%pD0ex-fIm{*EHUDK&@Tv+)Nl@@yXHX)hlw-te%pjO+?v2P z&uTMiMc2|2fz_7qAgPDorN^%(if!>?cRHr}VV(=Z410<1&1T%cUT>;%|GR%G;u=%J z2>~CL;$bBS=@(hOtWT2z%U9WV^^CQz;iQkhlRPnJpD{+NGq39xaFNXHmHINOVw0&l zJMoVe=rKt`oP?{-t*0ub9bJ6nG<+l@GYjW<`v!~^Y#73BMCx5wPDWgd>dgbwA|95t?b3wecvmYRfRMSz0-U>X;t*T_I7sv5>{K*2}N3Oqp@Lj!im+>B7 zR?vfdT}@7o)XroWkjRcyQqWX@L$1Amz#xNP+CNZLir*khG?j>0qvUYq*LH5kdfm!B z(@K=emRO(MYCQ9%E6-mQ=d`FTP)jsk8rzgt*noR>5a6anc?f#A*VwlGMI`T`6Mm5$O))# zWvq6C-#jH1Ff?M-1z)ejCAC+Fk7Y)lERv@G(DbQW z%BKx~uP0*S(rHue-^zE0w_FyR$ZwPaNxB)ewXXoqmXky~SP@E|M+6s>DB3sQ_;DOR zjx;Y(NCGp+qkCu=HfE>tRo<}?8J}MMI6g9}ziSWK>p(H1kBP^k16ROuXefy9fC!S* zvnXk=KMr0ArcSF4^=wu_&q>HhOrM-uikidr?!q9A#=LPh)OaCdgqnIV=b*T>e1fHI zK#~xC(bIylXR+ten21o11ZW`?u(HLEa@$q%)zad?po%LjcS%4W`Vv*hvPL#84+S;g zKsCK{D4ndLEaUi!ZY;Q?y6A=K_>95&DV*RyciBIzkoCA$1{e=&ax0~D%Lr?2c2a12 z+%KN|#^wGpy1|!@%Du)L`sXqsNFd$ zl&w<6OF~t5rVf-IXBH>mrHSMUHy2OHMGhZcoLtes5U24hQb^kX-Qjm#Tw-F_81A?1 zLjd<#46Oox5JQHK_sUk&1wld5W}5ExA*M_j*Z*6we+}y9itIk=P;@)m-DIjT)zWsh z$KbWw7v%Nbh0K%~p9wt$WeN+Gk`ecC>0~bj9v+8(wu>;A+wiypS$RTYWY}bh?CufG zXsL0z8|Az&yXN%+1{-Gu~E2mv!bN@9;T*Mcd|*x{6^-ArOe2AIREu z88I67^7MKi8P`;3M1q`OU=swu#xHg*ZbCfx7-FgrLOcO|7l|=ZW7}Qi`221+H}W}d zNt^F6WlPU(ZbJdEt9fXQ=!FG|g{pCwpwkKi)B}_8ax46+zL(W5P^$$1oTGO0!kH8m zNMP>y<*73oUuRn$CkqnRXqU+Jv`*xg?rw~>Kk*Y3rqZ{@b(-;UX`Y#Om`YQ5?5hQ?mWy1O#%Du; ztvhqHyx-xI(IIhre#$YEG}wnLUZj;8GA{W3La5ErDKMbBf|HjwUO$`_dIaY6_6A*{ z?Tg8j`4|=@T{5p-YQjaqB%gVtVx0?~-ZXgJSKoe`e{@!Jp@92*XX8lUo4->052yKe zWAZN)lxYg-1O+Fe3Ha)x(}~wbzZ_mJrW^QFy%voqWjb;=6TzKnDqfggj`-+_NJ&y% z^7fOcH+Zo{3BugER-v^aZsyP13l1p)K!pW6qTNPjW@TDV6u=YvIqvCekf1dZCpJp8 zyB5dILokkUa#ognPDG`(MpR3Vo5-r9ng_l^)e^99&&6PMbQ?$5w97yM=PFmh_9IUJf6mfM(sF$^2w zAD|N^BSX2F55s$>+P>ezNj*X~@3t~RIg!=GG!Y19V3aq!uOq3yML*BG;vsF_xjw$+ zOc~rly49P*1(ZN|Rb$6Qnr~XR+Yh|DGrFEa2KchLMPnfVYt2$)f=P1lXX!ng2uFJ( z9JV?BSROgsF{j7YJ*z48vR~`6V_LNg0j}_%qpb?GA4HlTI^b>i=tIanU<_4l{pp?{ z_gii-HAe6!0lTI92)0UR{QJ5sD#PN+V!U4lrZz6k1Tc%c?@==;h0mh}V!Hx1wn@k7 z@;e1*R{HfsN_A--wO=Tqjs(?FVu-x&e~d)W)}*iAu?Ajt9-w1pfSU^%(`kYA^_!Er zODZ7pio?=?GP+EFgo~(8-Gzm)Ds@=}XmY~&1$U9j(I{1NW&v>|PR0}6^FQg{gN+lBVC z>2mgpD>IF>A{zNHM&JL4m7``CWd1mpb@R14J8bwGeS?2*_hQ_;Dfu zu-&Ku+|=7x%{(_sE-vyEJIhiO=k47s6ISA#sq8g?LfuvV<;+li1+`F^99J5Jg(f}g z<6~3N*_RJ@Go*jC0>Ky?8wfzsp9lkMtKQz)S!bpbwsCZ@eEPea%Zc4zTuJR^*e&V% zqSP{2MsXIyg$gDuo;@#1y&(M;Q+m_QnHqyw?_J3aUlGV4YEOT?d!5Ob6d4<=R@qIs zF~PHaEO&I^i94UX$T;koA8TmJ06QS^xjJjEZjYv$7PYOqY7zK3iXIRsx6w#8m|J?M?d1PAHc zO0(0>Lt4g6FUDnd#^X=$4{2PXzy^^fh#q7FqsZ01#Gzxrr`*47t$3qqb1S0fwe#N?? zq|RUC>nO^4oD|6(FLkz+O57Z82h@<`eqR}rZ`S3G;*i>|VCi<=bMyH5Fv~~_DG<7b zHhNS_H$d|3k!dX|A>vJQlnXg3aa_w>&p`BYTjJ40>DvmMj$3$h6;zg^ofe4kb+)@X z_l_KCZxmjNPsi%+A5Ss;zNxCj2kM?3G|$VMi;P-Jaa4z+7QOttv}v=zk~YaY^0@EF z%&m=*fG4^3+C}T=?t&-uaqOB#_j#XxCt3voFssw-K{O2Fuq+yv+E~*BH{~M6^l?fm z>K=sx2qi1gB`pIX#!{fF2a5LcvXbO~gaU^+AUkkMimg{UX->SGcFJcekerCWXx!-v z?>d^+r9kfn?&|rq3LADgfzp7kkA;lK>l=Bz{>&s>Q9urwx=J3jA9+ak#+x{!nV@Uv zbCJxrl$RwGIMrkm>A+=D^B)hFNYdm6x76^aij)){FK^x{-XQ|Naj&Pd5~MX#I9#UW z5{PX+OQkJ~8c*_thAJgwJ_Kin-q2X(K0R_ivYeIFVC`hMc)2^-BG#=g-lfns9?!X^ z(7O0)Ejk0UoTTu{Y2p4M;*%q&_B+2m%Br^~^n4u`gqYC03fKGt=feZB_uYK@61-B0 zypLM94%L`7>7)ddi=v@lyPSylqYe^36Y9@rO3^ysPQl033$Nc#Tz83L@lpf_aR`lH z=nK!6;66G#*r3i#5^Vdznl^)!ZQyHc1%4;f`adM8{n-K{Y-XH)U#X{J+&hd#Y$US1 z%b}bY^_y(Ykd)B{De#5-o%{ir@(8JJ9;l$8M{=kQV);nEhrjgY@Ic?rW)^26Z~d5W z{p-O3hKjFXJ0QF+AM;+3oZ zslL(JJ91GXZ_|wN>Zws+>7<@Vly2p`N@jL&WR5SX1^^7Dn&VKvSJKRNwlQhyiq!;t zzxQcu(bK)hONqPvS*(mMyqQ0y$}e*bg!k?ur`Fk&RSfRD7-^w5#O1q(34Q0I2bND- z6O^KO{EZ4GHcaii%-N4HW1B4Cm_hZO=vjPO@;qKi`PP zO`j)USqpiR^3fvA#A&(-U1I$uBo1o>vd(`58V#h6*TAQ?F`e`4;gPo%@@7 z{K6TJi+@bDZPXUVtf1rJz4iRLvKYY8;3XYuQLQb<@Vhl{W&c!{t8QCLB#Exd=9RsD zD34D?nd}O;C_oJ+YUN*$KxFI0{ySmLGbz!5kq9XY&bXm-Beiccc~Kns5gxBqTI(2ky>r>wA@ZhrqdoJe>?Z znUCwubR>xethrz@q1^e@ZO%>?5~=tEq^Fze%abK|F$9FvbcjEP!9XgkcTgw;90zo| z{6ohtYY?!3j=;vtjwc_Z*V`hU;*9J?xzzDQN39_P%MY5i>lAdPU={Zs5uD-}k*Nz-IPT}Fy+ zKaK5c^dkz^qy9+%rl$r$=a=7C=ZWBt&gbZT6otK3J|W)0|uPbYlg z%{H)%Dp>yti$5VH7TrAo-#k+J>|3ww#NZYLW=3-0ay^@F{bUntd(FiZi9rlYUG$o!96J)Q@B-8LVX{20|t z#J#*c-%Y^LO3Pb|@0UBPJXog>^?H!2E6I|isd6&p5zLhFDKINcPAezZ5d(! zf%jjH9pu-o?AZ&#n!fMju9!?7G7WcJ?0ZJ0EIto#OHHOthV$l|;qClz$PL1u!JQD~ z*hwFNZYuP18Onz~GHB#+8bFS>hB>R)!@5Ww_HeYBK0=;kECLwpYtL-_6}wk56my7S z|NDEWrD9|OgyAx&OHha9qoU|C+v!`AwdQXF7I<9liDxI+Me~-eFiW0GhqE8vZ=p?2 z{UXmbYoY}@{G^p_t~OB^*`1X;Rr|h@SFL5hnhTRna^s9&Mq542*VI|wV3~Yt)B6iJ zHNb$$t;AG+5`jsv!ibS3L_jn#tI^K2BFxWZLWpNYze~z&3M*x1FN>-czCH4VlfbW%4PX_l#^ib7B5%9b z4%%5wc*#C)L^)>AiNo`}P~<_)<%R-pwn))udu2-o4g@)W|BS|*|%+3n-w(NLALEaJwAEpFNt^dbL-wa%n@0opqp-OfTVrXy)& zIs>zs1=d6gVld!SLZ@uLuOaod7Caz6dX}C+sre|071p47P&sz zfR~r=s+XZ_A-z5OljCKDQP^N*%LHkVB(f7CBy1nL(VDq(#^rmjfSp$O0a;fCW|O#m z&jdu;D+?hoBpxj(3PBo|w0gOjmFa9*PXKG;S+>TQ7)^hS<65c9QiuEYm@u^Mj|nr+ zmUJRL-4m63w;!JnD;d3sdl=kuJT;I22zRFKnp5zn-#{C6L4A?;;c=uFGoXYKHDRW1 z6`crbN`b)JYE^3v-lHBG9v?jS7H8V9@kZ8nFBGiIJfY@ADc>|dG3e@XrEeBaJFnD+nlac2lhbPE*$YKKV5G9%`NCb49JF@~a5A?Y zU##tHkn&YDGIL^vE{Y3MiQFPE-)k4o67ourf=J^KVpcs7^SnLGy5@7jA%9Vri}Fqk z>w554FknqVy}c|ko|Vi$UWatq?TBD&>qOnw@OuV2V_~)zV0GdTMi=QFZWLfL^;M1Q zHtNv*TWl;?iL@l6k-msbGMyE8$9X~c8k_Lb30_2+Ht2M#2B%LbI!rahIwF(ti_OvA z^rtS`Dm5buj3uW(V9m=e6|fAt=3P4wA67sa{8_CDODKiZ1fa^uTd8?yuR;WKyHrf% zkOp)hOTGO#(!RzsBBYn)`tmnScymRy zAAlWwjvXypxyH+0tnBTXDxWOn3^x6wwOECRfET6{J$MNeS<^Sq2f*t788#q|TP)Xz z;$H=5cThn0bS5#bsotR?fV9#)S4Y!g?ua|@G;l6i9@?;h{01dkbS8R|A9NU%C@Sme zN{>@Q0C{Pr=uA51EcsjvtcBBj&m}ZNh@=6}Oezgdj?__mi7tbU`wQI~jxFxQcVrxI zPv&%cQ*qPwwZ)P@<0{~p8ITgE_i}i`S1*xGa+bM8Q{jgnH{d?m9q}n`S!Sq)zaId; zkEBrEn#t5^M{bpJ?IxrEuY*SFrqPjIig7eEE_V5foeNTB-lw8>+Qa$>h894 zLE?G0Tk(7aVZ-3t>aOCZyED79b%^4v%HRQ)vD5I`H9b&#eVl z$hDW4z&~AFyqLZ#-ree`C4Ee)clxLUqWP>*zu&lyZv*K`zv_H$>?wVh&DW?N4hD{$ zPrZP3BO*dSOv$C=HgAmEj1j(IoNh+9%Z@6b;*`3Z!kE)tRYey+gz}Ay_pRFG>}&ZX)`c*Yj)x`{Po`2n2_~%tOi%jx!>?AvbGlT z?<_=`#x_URY>bWB99DbASbtUh9y4`6*^+UbGCos)aTdY5nV*B9ic@W#-4Df{ zapDN4r(}-$1~MlgVV*jPF+EW$d!`wa+1Zti3@u#k^&m;Be zRLMo_cPJ0U_wGbg=lc7>FzUjxJ;XFwfZHRR*{pGnCE|@?z+1VwvqRY*3hI%kyf6a& zl=bLLk6uU+wdi~M8Z9~a6N$xW*M-b+bZ=;3CBK~)6^)Dzx(BsnDYL$(gj1a0Ks2@I_pDR>0(u(m zz$82^ZGS>GQ3;&Yr2W#5n}3VnbHhjwPY=U30i9?u-rN|K-(N51?t0T7k;;3OAzAT3 zDp%`;WLRGU=1GaFgNFE&xs|z0M|9`!pFK3y=*!8kpUFM;+piB+jr(eFfgk)?N-d$# zUHt{p62Le^`pTNlDgg^=z+!`TL)Yi)PSqnm-aRC7BIQ5}+`GPdbxe)LYP1bzvp2=lB~9wy6hkdn?;XGuXo>f2#GaE!=O#V#`(&^ zr_m5b{EimF9=C5(ktm+8apSYwPlP-=@Qrd8N}qRsog&V*)?j(W8avNxOM&*>XOHgn zao%_9;&5JlycpKq(d(njcel7rS7;3?4z5Z6PkxjWbfy%;h4@etYkaMELBr!K?XN@Q zUZh$Z7?%m1Nc45?JY(B7gKF1|g(Htex}}$=Ma_0#K#ot?xx4^KLK6op>o-`RTOv~L z8@!{+FarQWTGy+8J_^N_^BLID`WmLK2jtjU8=>dCFsk*cAaa_vD$yZ#<&)h?9UOQY zNAK_;LGhG}9Xt@s@J5xkI!-L``kxR*e{?+W>vTVO`H}-(;+SqI5(^9cK+>j^MSFk;8Cx3B^>W$` z-)g(Y3raZASh?#(7kO-E3j6m@!$lrjtBZT%Kd}Sk5R*_v%46=L1KWohY9*u@j-)uv?3|_iJ(wsibQg5cx|T0 z93(-0>5(`Zb1RZ(R?^KIFHXyFexH#oAdO>*>R|72VQs-Y-zNS~zUlncu$XR$&V zE~OlaJMajOHmi4kEz?g3 zjx=6(XQBRrPafS9f3oYv$`l^!h;O!2CmdgEqPpH`q^%`83HR@3{CrOZ(p*vsKJVOI z#Yq_%V+YIBgO7fkYSpd*FW_x9J4oUMZqYo2pv*g^=B(uE1P?2+Dv^%dbdOz3%m@wK zpyLlhzf41@)5C1-?GC5r7DSaG+P&xK-&u6aDmX6%$kgs2S z`vS@sSQIIDHWy5)w!uAODQo~~^z!z2droiNOmK2)k8V8|k3WqsA2G(6(Exg+EJp^Z zx03?POrk-`c@)#LD;`rit617p#hGPgf@$@}-nzgchrMXjyd{C^W!sGrv-LV-b?6K4 zGa~d!=0pv$F|VPg{*UZ|+hLJq=8 zez^$i!}64IvId$!;-wW_4?qUIbJ~VHZVP7=|(E0ZL#H5C*|-UpNBhZb3GJ!a=SQjno6a% z$$yHL{K9#Sr;wz53oH^5>YoHJEmH4dT`Foe_2B6%r}e_X37EQlP(5 zb}qBQ9tWMDh2>i}Zmr=)s72}XrkNf53@k;$b zX2l;zZ#z3Cz2}FC(b3T=N8mj7>9_GV%p!?69$)2lPP~{G@arvegR%-t4+mlbF4%p` z*SwvuS%0I&7cN#7d9c9zoL#lPtm*nF6WE&>Oe(IU4)F_0y_*m(npCTMbBhf*9S4p) zMQ_oF(YHOCS!qT3nUR86_QcTl4@IddXJz5Cnjt-EjR)v{TdU;3k+L4CNANs9 zvr!vthWd8ztM)B{HyVY2Yr8DZPt?SOGL1pA-&6FmQ7N6S$?;o4`#)Z!rF_|ur7~Lg ziAK57aE@jUzoSEJQhu5mn<=Sm$&d_8NjNgF4iA80vs_dvYPkg`(vBJ3>qnrU3)A(| z(wLWi(&jF&mwW~Rb?el?^sH-^F}?r(9IU$7-V|P2UDpyZY{*f)ZVXu`tMn*{JR-%* zH+c|37$<3)Y8XE0=giwx;+{b5lZ#7v=1MF;?qEakh}^Uz0pkGx?3bDP8-borjt z9fFD3SXaCWbeF~UwiH?4q||W7-256bxygH>YleWMTyP|7bjdfQbQ=@Ss^k&+VJwJA zAEL#j7wyrL+ERgV?MrWzb!jc6f-b*#q0hUb@HMY?IDGp1(^-fuKd^vZ=Lcu8vV2R* zt%Wi$2N1MD1Q6zGudis&pE%+7Lq?}4boAN#TJ^fGt@AX8!7?Wzsq|7=6Tr!hI)cL> zy0r5>X<0viV&I{FbsE*{0>fpQNDB3<1o#z31&=9+3CyD;^agDw7S;>8sh_#+Hb+F6 z-yisgF;Zx-jGc0}4xv4VfcwiyEBB%$R3B&zn=(>x*7zJ|Gbdo z7)8(cuWLQDB;`LemE{hh@VoPT;O%G-d{$|__S|gphK!5nPq5-Q5V-S(m2tjX|HRO= z*qHr^0dbJW7xVY8fX^Lp=<%jA_0Ejb`1PQh#YzM9?(!eL?HWhz)oxOpf{hN{r+}yb z)HU3JgDbPxm7Jen?K6oyLX8B5l72%oj->kT`OYSgt6FS(Vk#yPT1LT+4E-^PfeE}+ zEGS3)QnGz`Dkoc|uy!;CH)1q79Q=6jAg=-BMITZdk8}nSc^f6>45 zmvcM5fodCR^Y*q3cD?>}ph!te?-+O#ad{*`B+0}Ei%=T3F~})M*0}h2ObU!2)fH2s zFWJ*b7#Ry0H_eDhgLoZ%5xOe@vRx9qUOz~_%243m@@Y$1;+?Qb|A9X-@lC=;LaI(9 zL4XgI4J1ZFV&@S#G)NC|*I7HWd-lwz4M4uZ@Fb*UknJ zP|Pu^6+o6dx_MyC@#aa{)lA=nj$)nmGz){&X=`a%Y$n2)(f!yi;1sw~hJ~Kb(~k;y zVztAq86?%;dZjm8L!lsdvgq8#lpeXJ{$CWY&J6rQc$Ztk(h-J(yYLk$pX*7~kL=zQ z*6d=SGV!ZVzfAM}tE9+tXs7$-@P7{OJ#P=m1O#;W|IEzHG=M%{g~v<1p+!l74_=d2 zY+oVQSzFxm72jJE^4ac|mmGg&133D#`1!#YMIKNi1qUN;_Fu1t!hXtZBZ?_64|*Ze zCcoUYAbZS0R1j0GSW}i`{OmbRw9CD-XcBG=hv_tsWoZpl7@lQ6JV_O*ff6wO^ z7>ECwbk>Ym1uk6-%DZ<5)#$dNg8taA_0SV`K9J0g&s=YSkJl8EJ8SJd4{o0nO}9Il z^#&&&x}tpH;ueEBKGKD+7vWn=b(k@mPad#G zI64j=y2~n?x^FcQ5S{?jGV_7}XRf|#Z3}Nc$9Jp0P_2e)I*{mKNrQWJVWWj2_w>ns zE|~&>T=@GBbtDlk9qWjNqxwh1zQ@FgAnd86&#X-JCbY*`*qWEDvW=uN z$0KAK&O3{vp~Sdk)(>Ax`b9Tskn&J9d_tOQGtkrk+PxaQb9IhjcQL5ab-h>U>A zu(}$p*FD_b*@t!4!Vm8HTZj}NFUl4_^*ll{QF*0EZI|Au&*zDv=(Gqar(u%r&&m!@m z7P~3Z?Q3vKkNl|BFDEAYYxkJCxmqIndi~0ul(6iwGpwHJn9@w!#~x@)_V4*Av}wci zbnh%sMT?%8ktrlDWJ^M}g_@|wb+<_n{|F!-Lv4Jy8H{y7 zKqfBe0R&$zM@wieqQ;mMio`jdKQl%MVvi&cXf_oy%)+bM+XI@AEo++pLzOl~JycbA zQBs!~9M!y;o~N_T&kfFX%&`Xn;|U`T|CYJ_P7Erli^lsHo=={WG?(ISGSdYb(t|Hh zkJ$OrU}ZA#(dbVRdjv?x`sS7t+B|y2&zv7~3YwI&LghBQOv}Kp&T@nkBHDn?;j1NIB(Ii7q36qI4m$AjEz{((v)R(cX1jRjRL)tGn2Qpea>xf>M3 z1vLiSdy|&!dKe-{OcsaPzJZH{zg50oeQ^|UzrEcPq+pvcEvqCuXGbKx>xXksO1;_D z%%CyRCg&8l_o>!q$*RoJCmYvpg6|ja?l2y#i?$B+A6lb-N2jQh*LE{}$bE~5i839H zbD`Uh2+Wu6w9~rcJKZQJ3e%Y*nGA-cGpo6yXA~4`JWhA-h@De6!;j*bir(v@MiXoA zfrO&}+E0g7;`cvC`MsV|BD$lXEPJ5XZIciQ>U~my*u?L*9e#g(IZ%VlV$gpiY2S#5 zV#TAecW{OuH#$<2%2J0TV@(&l(opw;n1lq#u`dzPhH;m6r7&myO5R3+3r3neUVkRFwNF=YyH4q5+tJG^!Y*LWIQkoa9+Rll&Rj8@BbKQHC~t z`TAgZ-c~WDb+kATe%Dt_1lS~m$v|G(5sD$ZXh5qEq^nc(92Q@3h(4Il?5X*2}%Vy&`R35|Nt@ zURC-WL%Vh97b!DsH|(f+Wy1-n9qv8Ak)zOvs4<=p{r;)zu1|DwG=Z_Tx3b>`i3ISA zm(xp9@1Z~?#kE)6!BGLrF3SzKt;lBJ8_P@v+$&Ea*sTyEbGdoBw^>_V^6N!j{2vnk zA9HIc`_1JwbUG7zsQt;#`Ee;zPXnfxfY8v>(SJ1^nBBv%ZZ>m0+|0Vc!aaGxhqj`h z_t)rnBb`N%zfMQC!UG1Ab6X8MvDKe*)M*s`cw>^&S2fx^XukfW+^(4DFdW^!$iABF zn|9{fS#BqAyt6>0v6D4C9PB8|id=G=p*D<_pwZzhC9(U>0}sVywPk_odi{*IQY-j; zZy7DGO>s4gBU@+o4?%I)Ftd*$(Yf5O2ZQ}(pR-ixW#>&9-~ z^m0Cs=X}Qofr=#Ktw!>)v#Z8hmoo}}$6j9}fSsqEGsJ0AtF>EccRTQ_PCNM0m%@R{ ztNo#GSq~kp;$bDwk`_KqjieYatBRhDzVvs9_k&rHiW=%kmNeAT0l8^BxCor>{n1ds zbI*<1jNr!c$e1>bD1~YCN-Kg~6suY^^$8@lnPArX7EC4ba554b%7d?sf(S!uA-Jan z9KYK2-m%f0FJ6Z-q|3969b-Hx>y1~iJ(*Gfw3HOeE2|ql&dg@-9bX2*?na1O6^dOQ z{>A2-y|SUou9VX!2A+;l?(eNkNG?RRZHmu5KKTO=My)=m$<|6;zxRARLnVc)%H9(e zy6&6n`%JsS(CRamu!kt?@Akf6sFux#OKW$b$IP+!Gm~ju?@l5p~z2Y(}d{ZQJg6`7%jhE zY6qAbaV&mbnHq-~&E4;DifWwxp`}WC^nXfy@VMap@*PT~Y)Iz?Jn@jwz>cL4cnQrV zlQc-SPX0LlnC^)>XfkHd2bh_0t+SIf9x+LfqmVT5{8ZJEMW_-ETwhi;IvL6HAIVS@ zbGXb&Mq6WR^KmR=G&eEZNnIA6i$NJTHZ1=Ams`IpIv9@PdJJd&8!=jmZHuv61uof~ ze_kuJVYSw7&ANTBs=A)mQBRsj9J59nVWZhc;Bm&M5amKe&BFUTXR)}MVNW*_y+a70ioZUQ5pPT&IFe)Z z{d{MqB5{SBhH-96)B)~>!m-BJ^2@pG+&UQ&Q&hU03Jb;oEneqB*jZ0|H(b>Cu)lej zYRs8sq|L|ZMar$+FQb3djWIER5;r}GuBB->fX$?#V0Qd=Ho}1*;QUt}m1QGWSi^{( z8>V5h@p4OjRFcuiDZ3w%aLIwOAHmrCT50aT^_|FYnw@T1VfKl!Yxh-+_GhI*?^Tnz zJdTQ$>Hy3T7Jjo7xd`Vy%V*`P$nw_;G3 z@!yDdEH|_=f$)Oh79B(DbH64lybw!e{r>$!iA#0@;JbA(bw-;@iUb#82AJ%7p&_a3rKl$8G|vEBVWq_i8wmM?DeODkL0gX zH(pET!n<)t7xbO5uVP{>Y5Vs<+(La*TR38Xqnto)zzj(@6Pq!Jg{ z)rVOe3*Ske5IxSc@kEvUqP>8R;z$S37LF!mU3KS!=yX;iB_~vi=|p0a5sZLDT*wjD zp4890I}fuHui>mTRIS$Pho|o{(h^ITzygm;;)3#mB4}P(d#fAoKHtB%pW==FjOCPM zo=m4A<8Su^$onjA?yLvHy8JC-58)S~*1SgvRgXUzypP!Tlh5pj+Gh%~0d#prsFKD@ zH1o3s_Uc4vmTpdIvsWOSHynlwQ9C8eda z&d#ma)~|Q{(_Lnt4#s!=f9ozso!^#EIWt&xI@rxsytB~)hwfV9sK#EkG~hehIt=4T^P*PlKeL`th zD97&Jr&Og4#EKX2;IAjvRx?#anzQRGW@AzYz9*`kyd2-l1&7?6GpR@HsSw2q#-)F} zKrkMoZnu|_m;kE|S4`u`-7Yrc(OBMnVCAc3(rT-$%Osh0(gA+Ra|0fys6ZiRAw>8|E-|&BHJbE`tx6l z>297yDXS%Qy)@@}>QBG_CWI_(YV^D{dwqYo%m6SqWMpJeyT{^wzGV{Hv8?~O%yFJ0r%JEgMN&?q~rL&b+XI`J~07!#h4pF5nXKXry? z5gWo}s~a?GHN3e>PYd~Bh-yO{4A)NZZsjQ%tF?cn^mESBBXXR!-K)2@hc2Dg5j9;T zY-v7R8gHJ>oZCm;@+KPb!zu|YQTpV;omM{jX+E+(rBLYo=W?b?D;jqD$EmQ`c5R2_ zWFA)+)&TbHuZVZzvct=1rL75NO6$rjC^&C7moExUcg=iam0s9IP4RLHo zWi|%X*HX-a4+*w6=~ULIsThzC!%0Nn1A|9*UtOhtQdW`mU3I%~=ybT$h>)gZZS=wz zH-35C^nyb?w#*SLQI1SK=XykML&Zae+Oy$TLcmhoO|KrT_e4_s>-|?$ulOmV;MirI za`;|IytIsjKd{8!KPWpy1zK-`$e6fCnc{}%z@)^~U@c3(_pkZ0?eqo|1`4N21I4da znmW4Fw!5Xuk4Py1i<{WxHzSwECYh`2nJ-6d1ZQ$|u75`(NT%T*n;jV~$*F@sn ziz&8gO>n(N)VH~Q)M-Jk+?NOIrk0#?(n#ljjozUDc;n?s!n<5CaRyMsfwc>-<0bbQa10?alZ4DPvEcsy_>&hYFecL`q&X z_rePK*cPS)=GmFF0&t&riIS|89VHcws+RXNp#CeNB;fATsmsy$K*5ui%XV#Wh6n+^ z)W+U6N$M|?eowz1_Vny$2nk4BM^@7FV#BknV&6WJfQdM;CZ$HH{2e|b{e<~?3>QFu zqRs2z>0?dUVv1SVUKyEVk8XS?AlScqDd+(G;QTUQt-VXG&fJwl80*{N!|%z6>O)Ng zS1YP>$vFBW_nQ^XvNb_75fAi^Yktw98-ldGu#EBnepI4Z`kRj?oL}zTw8Epx|GWzyKTYp^(`|n7E?<=P4Z4vy{SnZmg^xQq!N(ys#nlq?1k{oCt_LaH7ZZwhvIbus=gYTl ziLs{-*P4~Bnp;yVkj@f-AJ@yjTQB_eyt38a7BL#ij8!Y72R_X$TT;~1n_QmoUmqrE ztAtw>&Rdp$WP1WjlyXlOxl1k3?@|BxYd2rUR;)2zw{j~kZET*gxBheVamHbo@1ooE zxYtym%lS=N{;8gwcs_psig3|xYt{|^vHktDpSZhWeONy$j zQ5|lV|2P$Xh`;I$gnHbI7iRg4=r~)c57zBwe-NC0D^g;_gdVhh{_QBna{U`E7BRxN z%8aJD*))wQY~esq-qeEu(PSzcgXKm!&qdkWT`oW6;bgDmImL{`csygUSlIW>PZMf4A0i*N_=r{H3x~9H^xIVBI<0!LJEQ#O)b$oo|iQypWt9r*Zcl+IZq|tzQC%OFj`qKrd(r3 z4P$?#s3kyO%(2T3n3)}*KTG%cj!rN1O5${4Ni+ww z9NF?F?wNcg%v#_^`}1sBl+O!-9~I*kVEdZq+m0+Ntzmw&U!-5l(sjEwc|4f0FmGPn z$4~MeDPXnVsr*$3eG5%%mt7^f9+$HLPS5v(Z#bq3NR{0E^Zr3pQe@XqPala{J2BC3 zf0oAKc&31k45PLt_+xt0^~<4)+YU{`gJ?toX+-zKrZ?6y)ShP+61bpZd-~#g?+~^(Sr`Ul(Pylmp*trde{9{bJ@#6RD?T%x3OKqfs7a;WXG!K~Mhg;6AawAxU@5;{Y# zf3ilmWn(rT@f=}NZHQQ-Pllkgr{V}~`+SffRWo1pg3M+a&jn}0qO-NYQx3(Z;Pzq< zEsfjIusaRGGr?bR3)`~E?Gk=+y(pyg%o^lph{Q zeYBFFT*i4Y>RTNJ#hm!1rs9q$13#$v!HJtXravZx57Y+Twd#$qeJ!TbSHJ&wJ`@t< z->fC0JAW5)M;m#vcH3$PS`H{29ISizEw!=NyA8M<3|-wKkYHx~5c9t1@J$|;RZOuv zT{`zZ=@v*e;78$#**8!9gg~k&O!paEmnti$b``iR=jG1@Jc^O)+wswjW(e{tqpZL=;1aCtE1-#<$03*E54+uf*}%?Dcx zRKqm+InPW;9h4SDAnFOqcblo>d{4{{=R5A=BsKU{;HMC) zq8TjfI4pPZntihV-x)4v6_z~HSrr$5HrQ#mK>t0c%{Qt&Xx5$SSUVU3b?1|dw4|!` zfeseqXXYlh-moFIr7zS?$R%)Z%9tC{Q%Edo^=W{YqNp`8JDj9P`$LIPXyQv_!ZaU{ za6yxc1dCIabux{xbp-`O9p#OVUjMi#6Rv9~X98u;tZDa;RUuKf2;%bZwH+*1=``jt z=`!+mUhu{nyvCUFmXRP)1N$ZiovfMzew=NrU*u>%n{&mv9HPFe#W5pVnRu$ zR#~j8D_9DPX#ZYVQ9FbGjV{B!v{v*Z9XK-X8lLz&EsTp1^6KSp>$ZLmrbN7cKs^Qy ztDtzn!_0+LJ=<<+@l4Q%fNLG1mNmVkV9zDK#lj!5Qr`y@913bR3LOkYc^-9;%n6WBaKt*9ZKF-Y;QaUfaAQm{qI{d%-;7TVc6&UPl!EG3 zL95#=XIHmcSLQM$?2+T=j&G@y7igQW1H$Pt-vFo}&ibHB{E&Ee>gt!02 zlbU<(D<$O)uaa0bN^~Eb9@e#dX2litjLnbfha2c>EYja1?7^C|jlYNGe99TL|6;$v zt9@)ML(#ty7;%8w(Tm@+@*i&1;|-6Sl_PeY_Z^%V-ldQwsd~PkxH6z@x#)Gq;5n0q zI@Zd6$>7~%cOtD1AMpoQOV4QevH#wZcT_S?-z8TOcx;?&Jf4Ey2Q3sdCoX+>OOHrh z`bZpCHbVTEgf|t+y|6o;$KhJx2?kqEsxNFhfDY3JKK5rBs$uI9A{5!=3F zU$6OVnsQbj`O$gJ)6-)*tu@3N9L5Vz@2p~KsK2|250jt62hgTCjU(kdh$`{GfIO)rPvxr=tvEN4gn z*BH$v-&%!n3~!hIt>MA&sjVGmoIffRX!M$c#x*F|TE%76=RKt2cZEBz9PvHnGWjMb zEvhq%1OSv_Id|omc}>;KP8UP`K45!)kcs6~ZE0MSc87~}sA!kWK@>;sl}e37j?5{J zDpUom%I915sNy8uZXWc&p3%DX@%V-j2aiONoL*3W(S7&%xc^?39jt~>mo^JoFd8Qw z9l=qA8z0=5dbTLe{2g6KGua=zsi3#eZGPRWhF~4n0s0BTqTq)---C>=w)-?1&0-O? zgg47{b8;#m{`0|HDU=l_lD$|~F~zx7WHWy-1th|-m8>WJqVWCL_Z1H;$*aIU4c2#p zjUFe?vIv+5F*=vakW!3yxMf&ZG{8t3^l<3c=W}N0*+xy|Cz2L;PLG-0gNOoW5ik4! z7AO00|7|1-o@XMW87+$=RA!w(6y#7GfLdRL;9AkVlv03o;L=I~$a)(!C4MXbyy-{Azci%I2eD zwM8lU!z~^Yh!@uV{-7Mv->g1;!Q*h1p9p714gU0Dy1If-VaVhT7A+LT=n2J`^0h$n ztzSTe|6|lbuJqKa+isMqz%OIU)tTd1BZ*96<)&@br-M@L%!(z?SCrP&1a2S~f32B- zuE?C8WCM89e=C)M#QFXlUGsMVGsIlOXyM7=tPJOtCe??vEIV41NN;f%BGIzEEjuMZ z#_g-c@qS<&6V*3^qYxFZ4A{BG=kP1iv*`H#dleOKaZ%~q$kLdKa``h$=P8jg+Rw_zc>aRQYrg0(2_?zpK6g^*2pn{2z zFS;IV29d4wSPOI6VL$)Hr8r{808VN~s4?O@BK!TWnS4(1gNd-6@>_4Ujr-js;#zzi zgv%n;5fQVaJtrO-zQr}i&8+vnRp-Ze=U+i^x=@j3`r124sVA%c7}V&q=w-7?ZG%zH z*OBOLjOY(YE_)w*JW<^iA3$&xFREqYs5)M(7Q}-X|9;VY`}Zj7=HI`G$G(_ zK9w6DaHUG8I&(r7xB0BEurnbOG!tyCFMOlD15GPWuxR={68uM%T_jSM(Q(N>EM!*s z`3slL=Fl&qovO+g?c|nsy+`J?>os&f2ed6Gp~v&Gu`VXTl*=su9;)OV7WjBB*LQX) z)XRRgt3Ijhy~s*rG2lNB77z#9;c|K1OXGcTL)FFULF)Hz9A_DP1Q~Ox_rqFV9sf$J z2PgEXIbewyd?~rjr#K(_{1jp(vb0X#Ad!4!`5AJexT_|M%c=UGO7owoL1eKvIUhnD zRUJ9&@v9&1Eu$YHHc*(6+kz#2YIZ0(IS0bGIgvO_re-RIpA5K7)NZd&8M@ zFcKT64_AO){+jo*Bb8GN#Sh!HZL5f}d}H7_JpaB2rE~UZ@@S?7AbD<+VyYtjB#x%i4fSeBKw72&&FXYbJ|Ac2m7F zDm)A=tUdfsV`6q{-zO||3}kNZ{{De__e-I>llcjcgR=!=$@oO-*RCI#w(Bi>i+#b! zbMjQ##XEhG$uzNMFOMm#ziX%uYat>7_lIMV)N#y_*9c-7BVQF~KzKTJn61sC==R{T zt~~r?IY_kzTy0FO=sn5l-Q*}!*0D-9l(az$S`6Wo%E0j!NHz0aja?$S4p)egq+JI= z#>kYN;$KE`)-XDzF_`sgO)YgHM1lLz!qdZH#@)RO zA*o0$O2YVX*5Ie*hXs|;yG8L2Uu6WgLv)?4_$LP*z7~7S3S96Re+&`yl&Nn4eN3>K zF-)ZL+c?r&?A-6v!c@D)())Vb5j~&xMVF9JN&FZ=N)AzotS$c|lz)fvzI$z< z?z7#p{5eX6m0|bRd*=@{2Cc*%j9wiRoSUZjX2(#=6Y6%ona=4k*#t;Jk~!Bqe_B`6 zy{TF<-NJ=9Y%DhFzUKOu#zXqpuh;9F`$108+?pOuw?``;WmyRalBZJdEFJ1BjLwl7k>X5Jc9h= zA2y0p>BYEUMUU~;@Ws=FgUJ)0lI3)tYr_I3RlvF08hx2g`LpY6Rta~l!MK@7Rp3hhs~5#o`zeNA0YGD zC(EFlii`yj)?{8Qi9`#LFGmD1`p?7bK!de-kbx=#MoaOj+#byN;b9DIUN^KhzjYVC z*;nwlCO0=0=<0T1X+Q;sz%mf;M8dVZCCVhhlY^X~A-yJKH}r>azH*-Q@$*8u$3bD8)!(g_CNi+|yY1XX`-90uY<@859aunLM%FlV$v z2=Ixa_i%TxX$5?p>^bS63B;K&eDtvK5$Knksoz?&N;s9^uY6r@37_?NLlRr33>W`d zY~*uuLcy_5aSB%73$$0Sra|F&To?PMiL3C@jwP>L0xjzw>B$IPtFvcjr()01S-6$4 znYD`srxux+NKc#r4})#96tO`|YX>A$Gg`y8XFAer?Y**XeiT2wt@uXLVyHIDed!-l z#$Yg95iNWZWe$R4DM%!+`YEr`rj)owj?w$SiFFx-|zcRT>Gvyt0_F%9edcpiTT^0--YAm zqW!wBomNos_kN&-dJ2erW|}xPi$kioOxo+ii7CoqyjsnoD5HpcU4^O1K(P^rr*Y16 zlY5Q-?pyzvYz4ldq~zB-N!MEx;$;9qsr zHSHV*EiGCtaA@RVN}yWhe6+v@1qIEtIT|Aq@nNC^wZO6$;u{+qqxs6OdcS{xD*mUH z1ylrvmqI|)ZatksdAz*rmR)ZdADuGt8P`*rx~;3{i2)#2xTpltnl5p%{U3Q1X0AS% z%9}Zjq+ic~&|22RC2zDZY&$r$2Is|>b>}Zz-!RZ%^x5|A^X`P9X7~=>F#X;==)Ph! z`5m6CtITL=CsN0$Zm)$HMUiXvmNW(~vwEyB*lhWR>1nu)M@Rn}(qhv)c%1dctDQwM zrePRd$~oGm%x~5k39>DoL;Cz*xz!l+{5=BlTUO6eM5rT*K55k@PAIDwb9%S%yp67pbb2k4oB5~{> zX{XYq#_nSYNgGKE%gw@xU6?YkrEhk7;oAoZgLC-Z$>`|DJ%p60zMh4}W_c9*mwhod zrNX`6=UU1bT>p$Y@1AuFf$cFIcpk1Ow=}aLjg!Ntx7+B$G4IFnav*osZ1!MgG^8P3 zK53)RU0mxIKnn8RP1Q|ete{>tQ^cJ0OLiDu831p~^E(VQ0`i#RgC6qj9^SLa$wOcKWD7m?%y9!`j z7Qt6(I+0KR>UEI>WSJeDON5N6P`FsZR}oT#{El27r92K0S1pXQ{&EpJ56U`Zh z$*o0&uINzH`u#()ec449?PzOHWiR)nFy6*FH>Euha37%HT+XZUG-u*z^Zt5_GC5V% z`Two!pOcsGd_bAG<6rHrv!1}^`BEQSJvr;^rX|Q~)BfNs4~(J_-=w1rBqTlh{QH3S z(dl_D5l}l4&QYg-@)7CcUrA9Y^u1T``T7Hg8bZl@{tqahPi-WR%kej_PZwNs2SE3q z)c@;a!LtGUVxVsMY0%nTOD^|4Dnm~57t6t_rV>QWyWAHq!tqwyDE?XZx`)eyHow=| zwXFof>Pkt4?bAqS=$+`ITR5><&;3e=1;k4l=da`#TTKr=y$8=fZyT>Lpk-w6>1^ zc)Nt6%C%Col;pxJEGoLa@4ztVoth#BgWbdzT^0DHrN<&74L4Q=em=JvRv}4BjtIz@ zn!28?{D^$*c*9?fqJcl2HBKB_Q-E$0{G(T!bTl z8S=hv?{RQ*8!rMEWl{AW2My69QQo7bP?G8+f4$bNBzf|1Y^ zWEg{4p4+leH=tosp#EizMw1I?4ZB?{(=VHX!LRA<2UJ5mcC>rj2@lcJiIFX%?K@OPq{J~VcjM7-D08wL6gkG@>%+USj;>=w>oC`y;c-w2b!rtvM6W_w%+SZ=U8S*7tGE+?FM?)MCCX~L*j{8JFHRCAZDD!wS9hyUkS z0pu32bN8eyPvSFoK1C3QHg8Ldua;|3;D3>uUz}I^GbsBf2LI~?5D=V9vH9x3llI#1 z4TMwEC*Lq6_%TI_$($1{%j?}y1X`~xkO)_EFyY}u&71oOEyxmty@t1cw9$WL*U69V z?uIa6%o6opwjC)XIqFMvtlxS%q)@3kw0C;}c>Ta?>p@i)Suz95Bl}*WhKTGla_=JJxv+YFBFeH6t76i&o^kYTNm2G z;eM*-uwTLizTY?}f|*e>LZh*b_YNod3VG`0S{?x1=vEa3~Rj1H6e3u1NS=ZCAwq`X>>Xza`l1UIa-gwKBJ1 z1yZ%z?HM`IMa>3mQ{i}1F@X#C9aqq@0t~OISpP2Ls8*d{n?7P~kjfF}WvCrGjQDc8|Ylc1JHGH;(EKq$<;OBeHLPaZl_56l5!16M3^1O)BBN-A-!A3Hz z1yOzU+K5YMVVf_+Nu~`&OiXOCTtftlj2rM-TiLp%-@NsIrPlmkD^)4%;N!sW3 zP=f}GFH?{A2YuJwW+nU|bzO4}(@Gph?@EO|)a~d_ciLb*rFQks(|GZf4i^l%#oD-Z z$!^34bYj?p1?YMFUxC*19keQOHn`y%QT^V=AN`EOG|d$GWoWCNa^`{DdUi#{{;aO4 zqR9r85G|ZA2g#xH2r|nRYS_&8++Sg31U~&7rnYlfZyor_i9ftMadJH?5CjAp^k9vR z(Ce%(dH=p!6O`xJ_ifH1UP}mMH+4>eG`OVhx#e%^Y7Yf}m|SnMRa2d~;Vqkl@=ma9pRqW4I2rkwc^HnfSB+INI5dRzcP!6zTWypv zTi74M^>ksX)w=*mADG+Q94NzH?+W6I;BKj`tW@CN&1v%sUSR$6)8Q`^(OT;~t^H~A zuSENmx-kp>W~(KUYAsIr&sk)0M#M&j4;wCQ+hm2vXQ7IXtNCH%HUGO@_csJ?0$ED5 zKlGCxOmDKD1|sFV-!Bcqlc9@8^x%hwNBJX?=IIS`e|aU(bvLxakt@w%R$Mh1WoCG> z6CQam`e?Qbbl2vva_vJ-X;IQP?eHzb{$N!f+QUawZ9Cj#+|KdvY;CBMw1)#!MIngG z(f~Z7%sqtA$G6gZq`cp;d`PtItut#ov*=NjCNI5hbOg8op}ATgxd~$)53DQoAQ3-#+EXYNg4|yHsW5`p#2np9HBodFlTBsaxbl@wF6faOM>o6odx>T^lTw{5dZJQA1Mi? zlo$vjm2b55&92lLoRu2GHfzlf4uNGBXC28Bh0^7zQ{EEqz->tPa<%^k)cy;pGqV(d z(gV%_nV~m0smQqPN%u?3b#CV-VX8T+Gm-S*as?~Pm5u=@7U!jqiJ?n7ds>Rf$XQ&r zQGRdF1(NZ^-vHMCAB?Ur@GwWK&?gl!Vux4_!M>KpXe|x;Jj>NOdX15wNQX*)aW(&& zdAA1H=^xdb84>jEeW9#ZW`*o_Sa1I7Np(9tv$?%Vps3~D#pag@HKM`HMs?@z`unw% z?kOW^Xg$vwDnde(lX%nQo8E$G5AQpjIb4i8k6IcD;5?GwflUAEn8Fi1XfN^zwyC#E zQ*5@$@r$7Od;-(;FI7d(gmBnTZ|}-4a1GKmPxBCi8tT0jT((-q?_KRHJ-)ZT5dRMV z13~=0t@9Y7(X7Zw#xf<9_QMkSP43yV*R1Y4v~N$(Oi!a_#k>8$!9xalM~)md9$TE} zqE)Mw@b)&3PG!fAT?h#YF=$_01G{(cmP1cwq=1@ah6bD&qq=9MQdn~-6s_m)#rW-~ z(A~=(yV_Skhte*nPO6*@4Qt>XKX06*ckA=V1JV1)8T@nm4n3~ZbK_RCFDrVl`a9So zh;eMwNyRT%CVq&LB|H`HS5w}*S@%p* zvs3R^uieC~U*==Th%pEaHNA;jSiOm1Pa;`WjEmgAv&K=7PvdRz(qoKu@wm=U#4|WQ zuWV@}nIRYqgqNup2^3c3I^^YTAZh&Fr1H0AS}Y)O1+A@%&X!Zo%UxxTS-(B~z6)|r zG-ZvV;A~hty^Ty;zf1MIssU94ss>aIs2aGh8W8Y${*vV=QL<=_h_wf;(1Xu5jA^Z1 z#@Mld*B_4k*jQGPrKzBGk*V^rYA7He0At3C;Xc70Y}>XCBS(%@LF>or46Bn-HK1xh z)qtu2b2Z@YTL#tY*MKLTc%B52y|Gedy%lP3l_9TiXT0ck=?d5XV^u4S8@vCmIfT_4 zkHDGnsCAsOxT~1P*w#<=Y)A@b3;ex!4{Qi<7~j&QT3Fe=xEtEE)}>C+C@efY7XSNf z5hA1GNzqyrZw&6J$w(T-JR%hW~&pcSjyd|hoJ!cPEPj9rugY| z*RgZ=IRsseRw&e4wWx&F&8pLj%Qz?!pO8dqbX#teXKmZc=K8gp(Fh5<4HtTSRbgCi z8&c_-5*g>dLl?0BP!JO1Qs7tK1D)H~BJHzzAfTysALaB470RP>HJFIC0`6 zru{IT@ikB3xo4k7MQ&C+RN=+Gefth{>C*Y(41?G6j-9(OY}k-|`!7|>3vJr8!jh%S zmEXE{1^Hd9C0%|HK@g$872sNxd((gw z??vU^9MRsd1Xct`eCf*Yw{gIXsB1{z!YGq~rU0uhme%yP zCg8{(EC|fWS?^)xijMZ~_>pn4e@VKHK?J?6NzrVM$Y!B2NhbY`Lix)&#unO}*qB}gNEYT5Q(_8SH&xzL zy0jyUY)$wGQ{@VhZpzRhiLuuUE8^vB>^#W$i&;L{Yg5LN7tmjn=SJ~R=NwhJ{~CzD zn}U=A8h|?U9&W9-hP}hJtAqk_cHp{LFHel%lDT%ap&^Bp-gTQK*ErV!_C}bR;<%1a z=GdIb=wWAL{9diEp;*?5O-xemVjc_yWUO8NI(dDM;F>y_Mk2B%cXzgjeIDy??RHYR zc8}AG5wD%EeQZphntain=r*r1nzHgKeM;XhWIoZ?wRSn}V|fzGJX5mgDS9(k-zn@X z-pqLxWK7!Q%j;PBm+Qox?_8Sqro75)=Oxf^Es58qlf4aWi_pkNyASpF@z8(5)M-7;_vvD5>@VGQBm_k_i9n6@ufg8Mr8>9j_d3rjdbK9CYdNLSi zFNVOuj_S|FB63tAZKb4VqHVJ(@NhF+DWHYi_?bhIVhV5`L7W(-c!Ti(1LgTH?trs!kx_p7o0 z;3WtpvWRS;&G~0Lnl%W(H=jRGk5YHVyFC1MEDjyHh%LJU5yv>5fBrN~@$g!-Vm}ry z+lSNVZ*lXQu{gOESGT$^zMA+P!3JF*S^)LA3wGeQIn-lNC^rxpACkb1ot-si{sI(6!dqDN!B%BFmDbTkgpGx*TK!}#^r-|_zY?_%hX zx0G{Xtj>K;ob7>gaTR{0j!n&c;7Bg@Sy~$nFHf^PvnNY zwg@;K%X`-Q7lLt&_rY-lumkL!F!9z!1lbbAVtsege<$&GL8t>(1gHBDv~FhSf^R}E zVQyR)ESOiDv9-+=cQ<2YGjaV!2#u6d6`E*s+q&0s8a)@b9f=970i6rdhQx0kJ9;>2 zprdi@#2IWPE%?#nrx6-{TQM#$2ApPOq+{FmeWdQ*k4_!Td!CvzCZX+C;w?uTmUghW zhci8pwPQ#dmNG9z?mB+HK6x5}nb%ToDWkO9ylg|mA{6ksFy~i1+=W#@f6k&Ky9fMC zaLDO1%djDL=HNQR7A}kn&xvX$7xLJG272YWZZmG&SI9=yY9=)B?yQwqx#J`(Y;#|q zpt8NF@3wXD)vLNjW;e;$(Rs|T^zu)^nn|xAARnXC-DiR^Yt>%t zJQaipK3|04$=kyjT^m%vh`uf1?`@nBMrWYeUlQ5pFY69s;kIKqPlMG2GEd2SL}|9y zrX~%xx-~|VfO5vF=A`X7c?E;MUVu!3`1(4`DI^DcH~Qz}7tyAcaYM;-*TT__dZ=5; z2mg$DAzvVV*!1Ptb|?@FKOKZ-)qHij(*GiW^XxSTuxQ6|T;Mzt`5s}-d3y6awdHtZ zo((Je=vUMEzWL}yy!p*SWD@wzbDn%>TsHm{G^_5XQ~zGSjQ({EmaIR7@o)CTYh4=^ zsJStJtjE#~hcJ2MQ+U2z-2&BB3qN@pxF6v4y@J-7vmoF#IcaE2__-$OoLgSR+)oW$x^hEdsj41OUS}Us-l@-=3!)>H z7hIejb%t<*q0=I6$Dmr(@(g2T+DS>L(tY?M`yYpo2GW=@6-^t}&@oU?;+pkrREiQR zRNfV2KC*7~b+UxJZSH&9_4qX0N~F=JmJPDJB0BI@qnf)79S|&VIx-n4TpMcIv12>_ zu2!(O(#0Ll$D|-OMatz)meo+w-V%P!x$iELc8X)W#=fms*x$txmE5%0VzaAfjV(=i zPmm;O*4xn%rDbqh5^*;jVToy|;hFayY+!UUT)4j_ZRpFbgf!eq$v^<-tS|AY8Mt^S z6}3EU6emFa{P4SJ2#!lffSWbj*VRcX&x6eMMnXDlEGF!7JrY9b6 zmFdM>y0iyo{Ye0d8`<2UF|H4mx8_5rMPuV2(w*45ShMjcH)83H!Hw&hHGGY?qm}AT zuje^GzX3r~SAdmh}Ga(7Gy$bEE0Np-TuOI2=!U+6iASQ8rfwz0g)u z&FE(|y$>dSwG2yE9zYt$-?e>BF4l|V%$ch=a_kE3-p#MEaj7PQ*WfR(ic zCVeyj$GCC!{wE6%P5RqhwmKJJOVdi5Zc{aIz2LPskx zmv7z-K`a3>L(o&=sYc?Xk3LpF+lil!FEaIrm!HI})?P&9)wrBHq1rA=?{5K*Jv`i% z#b#c8OP(x9FI>1_7I59NWpj9!Da|-Gy9mbU_SCnI&fB0K! z*RCCk7dMY)Wz(k3h+=G6V2^#Ns^ZqpLTsXRT zyI^kZ${2L=BChiqCy%i~rxwL5Q8D{|efe$@pBcQd>I*@V3|<5C2wGf7ynrcH%43dS zZF~`W85=Vb;cRJzyL|eHuk`?HH#D+w#>AWFvHWh7GA7qd3skntGLF!1+SqsBa)ui) za?Rho8H&IQmr<`y4P({rOPUquW=!siuFQ}-j5AEt={~DjPksYp?3JH*db*=SyB5k{ z$gME!*>@O=m#*P6gy>dMQ_Vn7VlDtMyo`=* z>!K3Z-ljx6Zam-Q>9fUV+ne2#PYVcsVEz2z9D4ex^&%m62&H+EsOq#cNsS=}53!i~~%~ zQ!<2=t0+Z`8|G{}jOg$f{Iq%>UhY`mz}r8s{%-T#=_bb>Yo^C|UTvv)Plg5ryq>ps z8D1RlRGu-f&Cy0tf!3P7-WWPuIV=kBS_Q2&T75D*^n9j9V^edKWHTN9Vqy@czhW@$Rr^bJFGAXUM2;82T;( zyEgm;E|_xi9^`?HW5bpM@GVyw<$X&l>}1;g2gjwM)ry-)paX+A6llc9p%}*3al~uY zvey37PTs-PK<*dOkxJQ|kb-2*t7@NuQ_uJih)ze>wV}9`%$E)gOCI~K>28IeTDYTm zsob!l!*)etOPIW0YrTxv*yClN49sblEmrN&2)*PexxqM||1H zjevNrKBt|G!PIjcn_w66(nBHci+5`{VqASU?#XARtqe}Yh=Xz1+}Q)ow8o=U$o?lV zhT#6H+jzf*1IE^K*UnOohwQqI-JwZ1^Ry4V^uXdX1fH9(ion>~j`*~phe6#XR}wJn z;9YF$=zp7uJiub=X zJ{Ouatfa&uJW1d+Pf9wLcO>;(?OMJDIuRPY3m0zSdT2B^9IO;da<7txH-7z!@=S4a z%>^hV-s7BqcH+Aq*TIV(fKxty0qt7V#>%ya z2`t;v>o*?5KAej-O{?I`2~VT4e;LHmdt||)otQOe8@x8y`z{&<*kB5E)0pS13OA^pG-=tg1>{d7 zcaxG378=U9Ogr$y^dE8Q(j`rn)@LTZ|9%FVG;V}$-MVU}7yht(c|WXOv&w8wKUqL( zyp#*yt5!|^aGoU%`X?Wc)k-vdDE&J)=vsuDD(&In0f}`z_pf=CCdV1z+R4 z3%zqmrJgGme6H9Rn6bf|tOo<0>W-e>I$-Vkt@v*GuQbGp)zOjs?oOQvL?R8s9JCC` za{Dg$kx)ce_AgJ5?=w1;1>6xsf;)F&QKPzX12~-u58+GAngNQJaH`_@o;3iurozG_ zv2oK5418%ofkt)w#95p;6=?W7;WbgWR<#1PRaAxR)U1l#dk*O|CvyzCc7xaHZ8U9C zPbW|P^556M*YtG$>Q&wK&7RZ2X#MtdG^$b#TPDA5FbVNy)|cwOrO<<6aF!9&UA*%o z-g&BJP7x{nX7?rpt`B40mZJ)A9U2{nfA^dw6>WKR)brjJ(0Sh0qsqqJkRDBqdLl^U za?5^i)+)@HzZqr9d13OKJLjQT%WIpw`QKL=!w2+;y~>#436`fr$J#e5*BPfuDmlp8D_YTlErfeQZq_Y7VaHE69lBSisTSFK*1`zedz=AQpvjHX}p^U>9S81rkS zT)A=#vDKaX1ka*Tqef)YG5>u^(}wz~YCzQh(5Y=BvX+toXXg%-XbO=VV^i+J+&;_1$0&vqin}43RPRg1~*6-rIUC+Y`!)uWJj^D+@ z_}kNocxg*Gj`sI~n{5^_E?(KCY%?&aVJ_I3NzlBqb5{Nu0=fOS#y}j7KdtBII#E)j=sM__%|5~xDYWf5g;8L9ZPfh$VTYg^i19+Zd)r&Cbfoo!Xj!WU#2 zeb>SkqxME(Yp>Gu7SHNWX(YRy_xIjLjD>^Ib>!LT6l}PWgd{5mEF}m%v_`H$Z%!Ko z_CG(7h{~=uc%gDGt|9rNBW>dVN&D-3EC%k51E+||4V=)#%Z`kP=~#E|E@qxj;vQKv z=6A?)kV+=jU)!}9-mc}6)t0o$Ni`~N=>08itD7>+FVfd>dMIadvp1udHOiKDg_A?U zZm81!g-f?EmB6emY0W#ft*!iN`YKCrhtVT@;-$BKL@I&hxYz`&Tz3dVU+tnPlJl8N zAoz#b8?b51ae~A)82CcV9MD<{3HA0fPc=s%>AUAI-mQ3v2VaXMz;8p(loEJm&<{wa z^}c^aZ@e+Eg92JhJ=u6_N{^Jbt!rU9DZm9tUb*flULV+w-cz1gaXmQ?d6*7*u_ea7 z*H@}3;^tzH;cs@s)*WYXAt(&RSmw~N%jnXvE_Uq;!~)WZyExl%TuljDSJG5tngiZ? zwH-EWJH~49z`fSUNXpAWEEoC$T5m}vn9BAP;cu&sy^FS8 zX}*&afL1(t4<0$0{f3ZLMPjaQ*t`oJ+qXpfwz&o``8IeD1xnK+S0Bt5pd~2y2F_o& zf?5O(O-Wo_0=;u36oT%xF3J#3Y9(f{smghz3kCF3eYz^Y=T9e1Qw-HK#iggaNy!AX z?-UtuEp4}M)dUL`uhevt^O?Z-=~KS@8Plgu%%Y{v$@y;_1XQUAUtXh^uUykRkD2)M zuf=H9yb&r?@HK0&aSW^Q!^}S*p-=Qh;siHqQeQv$fqZY-ydh@&I-m4$V)>pc=^W>j zX+LB3k6)pM@b-*<3C5SD2XvN)hIUyw7VlvJ z-7RU9_4VsLbUqE`IzZ!UR_z1l4%0Q!?xYGEvD5Nt%xF*e-8*R!lJ9LrJ z%zP3Byxy>L58`8E^C@XO+lFf^PfycK62@zpmnz_O@2C3HnM`;6aO1{x3>iEScVc4l zY^a>CADr$XcJa4A&O{2=hQ_2gVB^q>>h*iLw$8xmGna8LFc>wn0pi_zPmmEb16@0sw+D8= zG$QZzvPYel$X<1$lC~HWkQ@H9O&NRCS{9Dg1dBD^$(EUE7|1du?8N23P|~^A;_qUG zK~-|w5!&lfN0T9-ucjRtQ*kau2W$1GX)T;|-f!-Tf)~N@4P6LMYf*A{^sz^0A3O9R zs6AqL1lD#d4XbSKa?RL;Zd{}fy_L)TeG_Z070Usxm5kJ69JphNALv2;KRu7?xdf5d z+~Vjg3qV~tyL~3uAO#! z8O*yF98G3fl8>`LS8k+ObAzTJA{62C7wtwY>AkJ^GT5_gU7hN(F;|6g+T2NxEQ5nss&_xJ-}k z3pjo<7}oScY|}gduH59(7E4-cHL6`s(Us^mmSDq4dbZ?@frag8WoIk~w}nL}1i%&@ ziO9o9=Kc5LJt#(S)q(&|+txLeJk3ASN6F%DC_x&)44F5<^JAy4Q`foHwVJX|@@E-u zZ_A}nM8ZWUqMO`0@Dc|Sj+EndESg>>y_ zaN_t01O){dDPw_-9Xk+|G8T8(u=v`w>j;G3HOyd2M8s|U^6T&T<(J=-c;&zHSo1gY zr&-vsbEnaIYGU3RXiZj!08i4&(!04DnJum{_D{TkYXq6kC1#-iu3$J?SmRfDVWaVY zh0unJ@MdJmGJcGcdzHou$wK3#wHVJTr1dY$TQHur1@Bod?CT2aMDc!SNzXLqU{=Ic zY|SKTI6bp9V!=gvJ%@sAcG{4Nt7rEPM#hq<+$TT%;s@;BYuq?O({E9IRt>1cdyw7| zhB^{(c=m69D>1iC^|ff}YFrMwt_PU1G#?iMm5c)%J9Ztw_|K*)F_=xYt4Xg#FK5w} z&Vqn#i(!w16)8$-Sict8S*o!sU0uja*pXvrFy@oV4837IJb?B%A|fI&@r&B?1ueEo#D_ykP&e465QE_2ovp-gYms4nlZWwnzZ z*l{KQ3TRNTmXUKqDPuT_ZYIfq<^qJPRwTmgoe{4LA41%kJJ2ixZ z34l0}z~yIuZ&u2GGo+URXsz9-^zjxwz@tbD{mo+2&wc@PV@Q>4!%aJ(2R9`GVCUy? zZK`fz(q+w9l>qHK{aPWB2B-t4EJQF_stPMI$2;*!u%*GRfYhc$@@xzEK4z+|qE6p> z{1VOwURMkWTG4C2HNEy%(}Vvq&#@|xh6bFu)-TO?xyJs?WqQhQrqYhF_9e~5)zJGX zzxK5AoeA!mYCFI5`vqES#^+V4l)=~L@mRHLl@h~R4C_@;&K&P*3G(^sNhnGG7Yd6AZe0`Je zakptSAENvvYn%geMyff;+K<+Ji%#PED))A5b?^WS+?9Bn6jOVT0)lHqJhzWTq@Y+@ z3fycgG|9?m_V%}Otlic!dNSWkx5roXp4JAfHI=*iI$~I5TWku;WZSuWMgkdY#+J^P zp8a8rw_VJJv7hxocJ6byTcqHN6Y)4rYHy9)U>xnShhpJsnFb4bZ|h4m-JR!NOh7kp zYkX423CH5BurtK)oMaZW!jJ&2Q`qnG+oN!mRP3f?&V@MKAw~PNCT=>QwWfR>qWj*C zx)_SKezoE=R|z1C#g5)o_+1_zcC^BGcXLE& zL<~1EbssL;H6%yc^D1t9F)Ym|ODr~m%W^DU1$nin-L~dfVmxo$aay6#rV$%#?d%a1 zoxsiFI6lzt@Bti+ZS?Ypp=X$4X~j6GiS(=x09SkDvQK{gQaElhKAe?2d~M`U_W>Y% zim_D5;x3GzO)3;V5v;hG9Lk6MnvKWV9=)cSX32PvnlZ>{WK=w3UEac0QhaOrluiFT z1lu*nS2BM$XIr>98Cq>gzd~QD5%J)bINXPi1u0{+x3xqj`;~sAKlux1T)dsT&mf$O z8V!|u8W$)<4K!%R^eBJUt5*-MCN?qdk_uP6dsWF_1G16RsWXAVoU1&yjx54AZrFr% z>o+P!F}XQeM`Bx7bupx{PAKo)uM{tfx|syFv#o+H7{}R_ z_<2QJU1Oo_k>&Z(sY7d2B0aSsP@1X4Paie}@X`St5@ca|-a{I3R5Rd1ZA)z{BkpBOey9hI9&s9KVolJg_-&FIa4fK<7gE73n z{`My{X=+JHzS0Coat-?#7FhhguG!{TLYG3?!svGm`y%6-7p{E8~QSGSHlhglR|+HJ}` zDDeChyfJh%$Fh#kXG4!CVWSWuiMK|Kp|}4|j#u-;%5BM-MjS7_(630?KXO|&|457% zYF z05&{Vnz-eIXcTdmp_YV-UKLdXss@Z}Kw?*$175FNwUY5yhZGvTHr~6Ms%k*hfU1Ef zM+2QZG~wPr26pZ_rc8SM=KXxf2SyHg8jTuM!>YBrd5_IN#O-M8J$M@JTGxj&nd!uV z?1OPLFy!5-ICK1A&ypFo;yZ@F*l(WmgYgP4}XE={Bb?C6cPoWIg0Kxy;ABphf zOpLF~JhA`^@UJb)38;r<*yB}tiN8|C1+E0YXPq^|(86)mU`*=2+j(+-3wUvB1O&j> z7ID}R*&CXSGFHj-L^nNG?Rui$EupDd`WM|T4kdkhf_4%0Sk-{4frqF8LzM9$4(3tp zTEJ9!u*pM9eEfI=?&+D6M?xNN>*$)Umfs?YfXUX{KXK&(>u$CVA9>Y zsp$L4boQ5Bo8#x?xZp@*e*(6%T|RBgc^4<$dG2CBe_l`Ock!Y(>FLkaig@sZ=@OQC){{h8Y z+n&EcRSKp7iE};s=oNUAk>X$&VB#uZ?+hOd0jQG{ARzbJl;?v` zRR2tRWTCIFPFisSH3P@WS)0e$%BNlg9W7Xx5iUZi|{O3{GL z^s`;3;{tqX-?}+EcWjjf;A*W8qzM5?uMkKdH{lzUE9;G_Rs2!L+e?Y#E#UjP^Owl% za6wtWNZD9|`{O_Rjt%&<{b+YC zbG${5Yw@NQgNmvF{wQ0uRYxs zWe7BzlD*>5O*V?Q^zs(KJU^*e+7s2vm%`!GK{y>8#(GW$Rh;P|^IDh2_~hr6_~f^> z*hX-@ALrA&x}QQD?!^2837>6oG}JmBtZQ(j-G+?Ip7E>m6EW@(@8?maj`)`# zaf7-6DCgsaUR_(@-7zy1ujk-vx0L_Mqy;`GI4S&T`JB=IRGEP$>K=RSssfq`(yw6$kdv#A+d|J8lwx=W5zmlT(_B;0ki zY#Q)PvW#lz$7546;LP@&>1=08qz^FOc1&{C+Ey7G*VF-SRyY@(ilB5y^eB^tY0W%P zk#yZU!f^{_`S`jPc<)3y=F{uh%f1-q-vHjL=78?>=pJ=Y*BDm7?OEqY>205ZUgfhw z6Ah~5fS=CO5sGx*q7!Yo%L)xk+F@p6Ck#H2gps==@q616)Pe4tVj1q#jdQW*e9aNj zujoa9aURnDgH9fFbW=sufU1H2h6eQRHvf%5JxZexiy_&lPa!pkDNg~p;R^sPAnjcO zw|NT7O04KoCG{+rv?__mvWVQD#j8s;(`A#^9Fg&!zf9|-ge1jNxm;Nfd^_o7(sVoW zK_|CqEy=#mOUqvL@<>Td*M-lRZz+S2df8QX?m2_59cpVeBJ`-FQwfR2t%w9T5~Oyd z=R)mjWwCSD8HJ8pn)qyVFSO(aodD|w!hHTuK;MS6Fiy0_(*-FE%IhUaT?>NnS3~ab zT&2Q_57{b}N@L1r&nt0B^rx+K#`%baS#f%b8_?0}wf`i3y%ib?p)WNh2G^3Qf@(S% zG-!azl`G-gxj+MLTnP#?NK+E{qv_hU3-1-?0gwN#Sb0w#uHr#|;lf4Ku3gLcc+^yr z8mL*)72W(x!;LRem*VI-DmNr;;Y-!IL>oM_I|z$fmdBi0{&<1lbty3p;fvHof>oJf z#6#~<-i%}5>k%hoq#t7B5=Mf{s`2i0s{6i;pKp_qHzx@8j#d-G*{`@^p}_x{|vSzh}wyg%%v zx2Q&B+Bx?H-gL$5xdCae11eY46e;j$|EIcR<(kb3-EvOb*{ujD7;!rao44;#QZ)VR zm&?keaTE3X`rzmIoX%Yq3HUMVxA|n^5O2b)XH!=FOj=xrm znDs^_&+LZvYh&13uju7GoUg;)di@Wb4VSj2eI?~&eGTZH0UMAp&5e55TY zG_?Gap4yfKR$rs%c3zU@HEoe6xUF4Z*eA*o2tUj|Z$+72>wWZWTgBM+$K|`RZQogi zt%>f*@MEmPhLy{rXOpV@s|GhGGuA46=IuYn7>2J-(H=_Hv0V5x)H*nQBpNdAwmFhY z<1V4^o;&(Ebo_uZ<(KY4wQ?o#cHfrfRM#o<@e3UkAo*t+5o{*K_GZQlmw8qokS_D= zMNqgq=jzJ$n|y`y+;#Gb>3K5$*vxlOr=oYhO_@_*4#DXZK9^qYl6#G~^L)s4H+9ZN ztUDNpr;Sk1o71*hM%BQRpn>W&YnlaKuU)x9QHKg%KM7aG!=GbHitd`=;g3iifvSPR zYrxgTiC)wV_}+d9+jbtIVKoi!Ck{~>Y}=~7vIbkZc9-Ju>_W!P#`MA#S+ijuJX{^f zdhx6RT1#5BDt-j3{b|5{O1>&G^!0vNPHOOZ|E$NT5zpbbzgDAeZGW~?LsKN@QykJ{ z4?r8VmP{FA*)m?Zb}fwTb7d7rth9VW8ZxXj-md9{|J7wEO0H#vC9gQ+Sj)bEM&xbR zieT}acBKjQ8V5^L2-vnOV}peqz3};wXncG!1$Vh8u1hyg&u#Wmj*bhIix}TcCuAaVBw`!)vsHAAHi+Kd7YJ;Bbpfb*>JA(=0Cl!ry&SBtmkuK_1nULB&QC{yVOP3)JF&RUxYGqJ{z)Vbh zJV6#Ktln@O?HN0|Lz|j8twlw};nz8vQH-6YrWeBpL%T5UV+DF>$0-~90)ChA_JCix zEGw&=ibf6;`c)(8`6bDwUpx&A#G6~T&he(k9FSsIx4*WWanF!92~ zYpi6+{LiJT%Pb8@tXBb{&z(EZ1m%~NuWE#HxL3~}`IeIciHV6=vt}KS4BHmKEucz3 zHqd6eIk|5fJ$g(9u1%}RWR8Sec)gw;wLFb}I(fRGF9GXH1W7N%rr`Utx3TzYG=jw-imV>WO`Vp- z1N=P@#~4CeZ^!Unp9z5-KD-!=c=q$DPZc`y=;fyskJxz&{>=uY zb0(Bkir0(=FqH)DTuF~5z3kNQf58t^KcxYYdCxDc?v=w*^yVHl@->VZKZTyrDLVZr zeX<_~>C@|9w;lSo@fkGmrT(NM)1}%C55J9N%hxLT(v-Gj%`Gc2iGX#I+z+!0Y0q2g zh+&zZuMfI+X{+5tksg;|c#*F^{~zA@U?M@@8wMb-bSC4{8JpgmvOIl7YRagy{IPznWL;V#q-=;!zI10)UW?B4Giww5I_C98-FrZwNP}6r?AMm>)}{K zW6*%gKDf@c{>;_;(SsncMs}YL#xef2ZCp(OPc`YrJ{1E@{%(Y2$y|}JMZEdZJH+&c zkA&6va6lU+zV@~gm$CWSMS{=QaE_aL!Iy4g@rFaF;_r>`hW19+hNeAIlrgbCKgOnh zuC1=|tCGjD3z+WhY1*jQ)Vu@${&~~@JUd|yKAOE6ReinCsa|DM#f_#5Q`6Yz+LPM7 zF=U=wa^@M1D;I8H;f8~#=3fTWhWA0o{MzO`E0!sar&}2@1mrx$^T*5G+2}Y5ni$Ra z+;cY_A(Mz7dJ|mNme;#A#*fQ(;TO`p58zm`-chwnsK=@XR1Ih}AmH_1iXn)# zlbCO?~91RxIK*q?ey4hbl!JaPQ0$WbZCq@^tvul z!kymRH}KQc_l$a53!|JV#xXZHXJt=W>D-Kes}-qPHlmyH7;lFH~`}#*8M; zcy@Op-Xi7sy!IsxJe>t!u;F_B`(~bKzA}tVgLYWi)>-iuzZz#avfl!^OU3t$v(YIy zMjCh#@lv<4u*AHJ$#}1}tD-++dYRbBgWz>IMjc2%yHecK=f0Wdymu|@fYsqvSavxc zgY_xRjWxzJ8M`VEPy=Z^_sKVCZNdFfQsu+eho1#~|KYo8LJ-?vTiSa?G7W{sm+!EG zomq0P(SrDz6@M$1{3~V#29&{ZC9>@dGG$tO(z}%7(+2!x&+6_~UUzg%B8@$)iGSFY zGGBq|Y(H7*CuEJ!u52OZZ+>@=ddaNakw<-wQsZ_206+jqL_t&*4|)7T`C*J=L^CYz z`zZ9-X=xdJux8P2X!4AG%I1N1T^kc=%hkn>zeC1_=8y5}tvmE8i-T`j^EUqFek^pR zvKb}ur4zZ~E>!0di?Kk$hSEuCQigPPV?OAE(G&im*LkX9B{g#N9Q5hY5ETeSN5|d8 z`i;kM{Pb1Evh={aL%Lwl3vF;YD4fqIq4}mo6=PFG#gGoy43Y6GLOlo)lEEjq{Fkq= zyyy$`PLz2|?8pRC61wWqkL26V6J8*m1`4xg{eoY9{f(M1u6KKaYT`j$6e7Qc0$-a& z(Xmz=0ubD9>CvM*rceKoUeQ_JRr&Q-y?QmXLFB#r_T%#9%Z5!-x81vS!|d5VWdn1@ z&!?thhA}n-O{V&prv}99I+j+ko}?VAL5rW%G%Lj-HkB)637x=b3`nIl>*aV0ys|eK zbDR62UO6Y6qs6ayRf~6ZYfo3+D=n}&G!~{cEpG8z6lqO{eFuW&`cONN};}Z>6&fR}We0)k{#&;7j z`MaO7cKvo`XoZ_2J@-4d#)t2{b>FY2|C0V5=dskP;EfKXZeB-^>+L75pmT#PP*niO zM8=o?X2Q$(d+Tv5+I$2juiQed3Z)fw%;9&r|G&|#QNBzLei9p->);DFZ=0pcWN!Xk zBn3HnxeelK8*89AJ(~smls^dtbTQ;M)*m{LA6M=n%ToxR|6)G&em??L>G5n*XjoOn zr-Txl+GLqRX7nSq_k>rwVcg7rF?jMqZ2NWyDiJ&`sECLBGxScDzZ9A0jR>qe6v%n* z#aV`7dT!Eu?E7Iv-feT9)d-$T4DS0aD;WR$7ROkHam{D1J)n3HY5Ej{UJn`qY$phR z{7MMwRxG2*Qa@D>+@Q*_?#Uu0KI$nMs;3`lBSBP zfyYGyjT=?|g?Zk?KHlF_hURnm(e!p1EJ(7khW zC0%|u5j5TxgGX%{{zgCaer7a2{_Iy&@GZsnsoZw7%Q=zBD4O9tjY$~yS1Ob**m_C4 zh|Li@GK88V0_^_WsyNQ93difa8S|U;))idx-^=ezFt@qu9ejEslN8)G7*H`6@J{AF z-;@)v_?KYs@XD4-zOk`9$Unvyd7_g3zVL7hop-ME3x z)wonFxtfe;eQoe%W3T_ScNYLwmFXYHAEt+4=#cIf0SSvzR7~vd?yg;Hbywa0+U?ql zwYDybi6R)F0@4Bk(lyio6HNYp&$(QVZQXYS0s=Xaj>-4r@|x8FX# z^i{uR+Nbl%_u#*;MksJEae*^Tq_t(*ls3|=u(S2^h1w>}qHp3b+gn`2i0FonQH z=U_!{QQBOEzY{!c+cKI6znuK#=dZzF8a?RYb-V)Rsx7tt63)$~YPnHM^jTXy1+4pB zUGWaod=Z~xbM!l6^%y<2@wJzxX{xJttEw%E+|&XO=4+X&f7J4Iv4(G#7oo1x@@nl@ zU^4D(79hCZY_UzZ{0(EAs&=DwHU1V?d4!kf<0$KgbA!UtWL4fMzxI5g*vY+HYp-zd zKXQe~Yw+rZ9{V)0x898jez|8UbRMmfCe54n39qfh?^^A~F`IqaQMO>wnjkc;QKRTT z2$2gb>dkSC_WQ(Rdt1W>HSAwcerl7a&bMrZEie7&8^xMhoA#q~`-XPi6-U|GryO9d zT7<4bsipwGR?Q?`v8FCT6h8ZS*JW>I`{N(EJb&X-Q{BjT;P1B=EL>yD6!@2Azay8k zC*B^d;B%U~v7WB4??#SNm!~ttP~0)7V!d>gd8<~fb_ms?MT_m+^DfXa3%#Kz>q7l+ zQ}5or?CPtoDC;amPp1Wa_W76Uig?LZRVw+yTDELyci(kKnYO*_K3#0@z4x}?bd`ST zEfhG%m$6FqqYpm{x*6SKxiUKO#1nL3z*f9D*kU0(P*#CtjaU8Beob|$j{jQpK(UH< zTA>So)MJOf`MB#@?NlqJ{cWL(RJyPf(?&cYWo}m%qKu|4v<|(0T99?G^66W|JoC z4V#wh!&j8l{BMbWRZH>@Oa*_J=L?pv-EZ2splQbDI;fVCwyIi+4yO}EwT7CjPV2O4DiKpqYK*BRF@&TjPOIpI zmZGYr1Tlw1q^gFRYe)zZ^OVFC2@>I#bMC$G`n~tP|75LiWXZ~Yp69ba`?L46_qTqd zz7=4bgR(-f?zTCs!y^&;prjdduL?u;#=^-9D2`dVf;DgJGRKUO|@ zrN<)FV7oz{_%=QH9>0gUdj~ur>xG$*PV`=UIX0)bHMD7RA0sTAaaVeZ_l0E+Kv2)) zt5m!*b+J6r`Bu;bYlpKHBH`DzQf?SXxLEjJ79y*y(a+~BmY*k4r8Ou{HbN{3(BYa` zX#fu?O~V04T43u4S(CA!JJKWCxNsm)q^ClCvAL<$Pq`=&D7h++yM?Oz4hGm^^P3lg z3=2HOqE#>o`V1A7#mK1$-gx{y*xbkX)Y`%oTEVePl$J{b2i7T+D-t}Gp3NH{;S3t6 zST0Wq?UH=Ez7Rz4mHPdgKX$c9QZ56RU03!sl?Ykj8Y&voD2 zvVhOU1TZS-ay3CcABtIf;KOOo$>s9MkNG`YLPkYYJ?-H$Krk{#)SyS7f6(M1FvL>H z^6W#UOqpHxQ8iZBcfXzQ?%DwjB(CO?9HQ_?PZGDVSWWHXO>(7o+&TYEtzBQ>Mdxks zOQ6+X(#PEwDxFOe>$Zmsq3c@Lf|D*gK4B(!*{V~V799G;`A@4eHC^~#4`e9LB(NZg(eRSns&$2~X+ShTdu>A#I>)S`5LRmH! zx=X3}IGnP|Nt4VG{b9KeWu&Z6pWF(t!yl~v=)^y=N>d-T03=c>ksuJT) z^5Yfo-a58t+!~x@w3F-nn`VVBEk=M^_Evr+WymWsZE6%s`1QFW$28TEjmN#T+cU(g zvd~|i-PCFF9ds0Gu?Iof&+=TemF!dVJIGIgpHA)Xkh3;EK$d$3zW`g3z9HY;!TM&M z*|Bl0zN&q-+N#PId}fKaoIzKiNIPtV!4sGz(}3gKVwOKcYII>e`wkKn6fc?)51vQn z#)D=J{t~&Y>tbeVN` zrgW^!!UVhkbwq1tUQWZCavRTCYbrKP;M$N7mqAM`HfXM- zY-sZ&Z=zrU#>_L`&vOV|5L({4H}~i4!?iaqv$q_GqLk`Vw7>JQY3B>`lkS}=H`umK z(TbNRoh+q}99Ol=i=0nTX{c(IL*6GYUpV!``G*{Diw(ilDa^N2{Z)YF=2SzVGql!h zUv`9kQ@|pH8_nwJ&*c;>Ba@OOjOYgIv?an^7s8LPEkZe_X+7SBrd55%a+32wIyQI+1 z^NqpxO43?75DUY|;kM=0c@*7}!@@}T^0`%}XDd~DRG!_lhGOD#yM z7vP+{+jOGG!9#N_7}Y$#VM9|^Dsf1&;gB?^7&|QC;dp6JE$D&*eqmrIl>zcj>%*b$ z^IV{h^c;a>J>s%{*z6~NmIrP2tP!dF?x&Jj&Vivl6^SE`oqg#8Xm4XwdIWc%t`Q-5 zW7sTgNM%2Qi<V!M}oy*ndd*vlmGfnojj2 zJFDZ!=-+f4q>e6+R1;*wyi_7df_A1$6ax(DA&7Pl=*FC$5*Jtb(zeVOU-UmOXv>m~ zSas`F+6tHS$fJ+8AB|rk78L*uva>!hq$Z@sBDNPeKY;_Lp+Dc4oCog5qO$@dKpwvO zZUsw;^k(=$CZX78bjkh+$=c1@xCW7Uj9Knv#~FHcI#HO=t-S1bzOcSj7Q-@VuBuU= z^8KO8W|rTBFvqkOIQ`SrhIvomO1mHBp1>^Qr}s{IXj-fO;i)GjWhu6_XVZkgIOKia zW;j=ZJhVsbSaH%?EbN|)MzT8i2bb`ro9j$QvPz|&LdjG zPMMkB$kGnWkco!vh!W#V8KTwVlm-xwTrPvOD_{p88t~DJ@<}BD$q;#i^o&QjX(eI^ zISQ(SWsGkf&Xc9@A0&CBM3%pz*md4p8b0N5^P<@Sy&F}k-`p=tVtS4s<@kq}MIaiFo3MLrRZ!k2QRE z<(LLC*|A1nhNsoFx8$W0xmxXN=gXtoG}H{btoc|KV-V+6>yI>bUSZ?BshxS0o{y9h zrt~g@R%6Xy2u;A)9POOY{T(Iy>8rXYuZ$CibRGSNt7^LVL6*~28UDPUP1Jx|1~D{I z{g-WbHL6ue@7(d7VL;DoOh-}0x-ofQ!Ub*BLB|x)iCGq7@N+7g-ruNynwtRZ2XIx0 zJCB+gP6xEg4WHMwdqd+t9*Dl9$Yk#(9gaRq*j8H#ZXn!z{M_s%%%;#GSST)@gdyJO zqg46YK88kTSM5IOJPco7P$>p&*ueVLV-g&Z(=l@U83j*Dk*9R}2UhD$GaGvKcf+Hp zua6S|a-5z%+u!N^8n#utlfy$1+3U!nE=2&P%Ey-tJ=SHa*S57RoGV$g@zBB|xNHFk zbw=p?6kqDMyu?XpIENXK1VtY@AgA3chrHbgEs}DyE>~0`jvf%xju2jko)Ql{3URFU zdZR!=fP_MS8Cc!veE8kSfhZ>D#ouOO+(k+wn~i0mU-i~k&Sw~eAm1g08gObPk|= zH_Y4B7k#J*wRP@c^%H|VF%Hr)d@IxN)!< z;Gt%_+zE_jDAA?S)uH0*ov{zRJ1*sh*B0DN$wgo*!x<8hzbe9V(!(8acJiw?Yux=lx57nA@e?+&g%f@8Ss0i(M&*nd8ZL_gp>WG@@-*5BA17L`Z$_F$BcTwdxj z{ZW$=+~4|K5TxZYYTd-K7fe!xr*5lQEX&YIrR2FpNARonUl*sijkcsN;8~}KvCs+j z-|29hOByYBpM^r6K|FjJwbnQEB~MSbgMIk)4jDE0aG=CqC9D|0ZxWWmfxr z28KAIh9T_-Dvs@kl|75^HTmdNY4P6(m;R!x-$h$MhQGWatTC81t~1i$vaEuj znH&*2K;6{&xF*NXFBb%pXQ^gdh+9!B_}w3$! zQ>x(NSDxCV>_aAhCdQ1{tpvT<2q4|;)-0S4>PW>L(F$IgZIzIg+}h$Q02Q4_Esc7% z>dsVy2B9$TlG59Py+*FU!w0I~caEN^^?!X5IK39Yw6U6cK)!m!k+GWOA~ntZh=~vY?*aSL>v-30nNd`EJ!Z^UDvciI z`61Yq??U~Oimb&M%F`~NtsT-_+ls?o9(U%EYls`|SjmrJjppY2gy!DRA3SvDmF032 zu3KI9=syQ)WR^${mFZgNon9*H-scX8B$zC11Epye*#+kmGdghF`!holNOM)v;HuL? z1Jv!`X1(k5ouDujssQSwYfRrrONk$HDw5SSr^)y`1fE9I)CbvN;mn&QMi@g(y;&~@ zlabS^Ba~q=p6v+ySdcUb^B&WW{DkP4S(r?tj77SMK4{O3-s`Y)=kliRM*ErZ6m4OS zzr=pbA?>ZI)#a9aEk65gk%36%Y@Y2#WICjHPFus1`jr3a1XMB(mpJ%hczyjU&^n-O zI4Di(U}Ctzt zz(&Bk)z#dlB~VRQcD8F*`qt2sX5!}LagIFvu{oUG^~SJpB4^&nJkpgZNvaAU-*gq@ zW`1#2$VGm9WUkMO)mt5}TU#~g%yExbY!&Ua_#x|z_=5N-8xm{||AStXR~81`gw(g| zI{Jph^B{MR5lE~Gjgg|^6P7enm=M?>O7_GS z`=i-9kok%fuB2V_0q+QggPOP%{vj@Q>urdnCfI<0(G`M}_)#6lct?W(NRX z^C8*F?)Wyh{!aJ$iIir!%Up7jk3Yz%#J>7>dT)d2O7(ftM9Uj_F6_s3QI#eYWiLvV zkNLdUDt?(bp1ze~NC)Ber8x~#>+gyCbTqN8rCN=p5$k$7Qw^F7 z-W4Qe3suPRc|@!YTFW+J+=J%oS)cXT2dq3?H=~BmrDnc_Wb@bX1l~_C9&O$m#(m~* z7&7cM3Oeq@+5>*hd==qQ41X9~2i^L*xDxQS2pt6vD8!;sx2sbil8Lb6>xJ;4NT@Er^J z5euwrrlQus>zP&I)P`(2u-b}(AX%}4N?esMH}I-4zUc~U&cHi}3bXtZ{tc$`yM;ZW zINOZ}vKenF&Bp6}n~M_63s56ujz1i|31SFf?{!#1#dN(qy@6Njs6E!5 zyIG$o3M@8W3ULhFxw3ZDKP}@H#h35_s@x))M5xeq8aD5a?ya)`vN8;aN{!Vjiubc$ zxQgz`4OPj%K)k8`KD}P&0XZ%=2iaFIJu<>Iu-+O~tJ2FcZr=$QB#Hcxj58J#r2Q26 z4qW#Rvyyay=rV5h{Ls+Y4U~D6QR3fzLp?D&xt$C$l^njaTzBW~qpZG$v*uyj-8wR%lY2xy&?&{kI7)8eyM2o`5lwNY9dZ@%((1*2 z3CBfvFVfr4EI#1zT#_ksyPrEEp@Mj%KEP*J9ESp)Hg&*wrmez;kZJh{;(bQP#5=VE zYzs8{%v4^1GN2{rlRmsCm3VuswHft=E1cMv>6i8?q>MROR+`mQu_`e;bVCJztzv)V z%qD<@Cj5J47h8xq<>9RHOZfZ;VBgAFl}c_kn!?aBVb1rB-T?VB}O~xy;9(=ssrZo5kVDIXl=Z<&`=M7LkmN zHMPSVj$JOPaJKn6ylU!0aY(SbYRICFpMWBY&8U2#^Q}obix(&oxuad zhY>pV5!6;9NF_xd0%7$UB44qJNMV2xTE7But1gd~H#}8eEL-G4b*QjDn<6=Z2=_UsaqH zsgK`YHZ~~)DxEYKF_8H>Um3sPO41f-%&zA=Ki1mb%@%#gX7~TlA}xK7uOjurx0P%# zpSKW+t9)`lV0iKh@>|Sf`Gl*L@7iM*Rx0^{{(zAxz5lw($pI6o@>z<6 zd{PCd3WJH*j=6veua7$&q_+Grk!RQeQi(+>m{KP%;9}^t`s;+){!* zIUHZA^~+zo$l0=oQuq_O_&sKrvkgT&noezGBZC{i`GAWf-YO%!N*1|-6hRIZ2R$#g zs(Rz(KK@@$%!E(69q6-(hl*d=J}m* zc>XQx%(&Y__+Vu=ZT;$1ZM#Nhg9sc%&vg!8(Q2wl(VCvW@%X{2)T`s>!0oxZUfp(; zdd0QVv8@a5s`Vq4Sv{{TQS05N{o2)f4kNnLsGQraDU|q|kv3~AEW(3~Ka2?}OeK`x z>T6|Fuc=h&JIBX$RPPsBSQV>*CBmoI_mg$T%iy;thQRM*8~%@S33U4HK|I$|{PxIC zkxLPWn-Ib^)d9w);&QB@t8coHOZ_)0{Fc6}FD6xGntrJ5O_Hmk`k)hr+fHDPaMBf% z%3?b}6k{VKa3;9EWZ+%_WOpr{u-0;r-d9wITk5GE7^E}OXtTrHv5I~xpG!FyVs_qA zh0&C+0c$)A#x5~pm;<38lb4knJDM%}+rFQgQKBY*bSqE^sM<#qKC@sF!zQvC0W}c| z_Y;7ZC+9-%^4IYS*p2d1o?7+3@S2)(kq9&}0f5t%(|3KzfllT-NuNu=M2OUTg(%VfBoWR{N!7sK_*=~L)9%R ztQnS4xa~5!@{8DAg%F1Jxn&?fkxM$tX3%81t6bFL?oX7$E|{eb3aK;Srq#jNen!6B zSFy8W2N1lm$1NiCsvT<>(w(kTCCAFSk7NBTX^q~#cMC6&#fE_)8uv%{4Ng~f=j1FeMa6^Uj9 z`}1y2+hE3-G4NX%KNT~($GacZdbk=TZgUG8%QPJ__LBlz-5*w^ZYu+kXU{)Ys+m%+ zg8*%H{sx&R`|I~^+5#tG-d5YZR~1WLgE4@rnJbXuchNumxeBAodOij`x1FaDN?EGx zSCK_OZztKvK&%F*0VgBBGa_GOy_`Z2F4Tzh8BrAuuE~{jT9b|3xHov?+WD z#AUEbaA$WT8=sm0HwLKas1SLCOREPg?mKKbz5Xky(2V`9*W7gzUg4Ud%wR8-BtV>U zA{fs~N!F6oH&U1r>iof0*~f~K#mN~Om>)|7G@UirRj`g_39|uFxDo|`kQ8v!;PwJZvDea#Nt z)^Cu-s0EBcx%uXVEAi68IqC@X?6+3uC`&N2%#m6(*SFN=6!u(ywev4aRV0KRB>~n=w^%{I$j}>Sn zvm-#}o&EgeVv$G+)O9qbViUqdFDxJ zVI9v*|J@UgS7kqu2R&61P@bud`g%sGVR*&f=h_buX1dOn#D%tBHNbDJ{hJK>Ds09S zWBV3#O3DWjeeS+w$}VF4HX1}sl|{A~83xLpe%9vA#{KN-{N&mw-^sv^}Pdum`CewQdzK_yLxxAN<^Z(VZ-n6MZwyTmd#j#Tz2 zmnnii4o##d1F=eUFo{oc(5Z=DF4r_i%M9!a0C zkL1DrhJ!KZcOWNdqG^_H-Z#jg=bAqqQ@ed#XO2;M-|_C|PfR@0P41Hvj14`l>h-#% z+V5!A`(?b~R`Q^?2nLA-cqmuKE$Vo8-Vq+jq?uKdJ+JmRfsUQ^14WkVs2y*1 zS zEhK(YkAhJA7FayK0gX za_Qyx9~RTso3MIB~FPdTq|*4olT+i98_JRr7#Di_qtS6=K& z#n__n_pnoQN|&Bmn`RU#w@F=f@^ibST;>c+d4tpFnv{IzdF879UEbC}jMM@8J>a| zm;U1nZu9=~@^bAZRqfeEOfpl$mWYV#h4Nu4g&%~hF2Q1Bw&&A@zC6@#WO>f*u52_IpQ_y5E5C7F&A{o7Qmdz_S?P(>t=F%{-GhC3 z781l=s*2@w58UBQs`4m_&gK<>xsvkD;=_Mm_K5gQU8ube~Ws!f19r(76m^0Z7v#|)2)5bxc z?cHvX^+H*{UlDm3qu_raeK zCOdM>5q)I9CP_qy+WtNIrN4Zuy`Im3t|njiD>haW;jXMM`lL6dkkbYiiO z+aD584KUdHxfD8no)_3wO)4?z=+nGy6-w@Ka$guE>ExC?)QkxrvU0&i{zMZWx%SXo z2rLIa`qd!Dk5Tl*o)$h6Cwn3~wF;4CB#6%yqHm#E-aHt%b8~sO;o5vrZUPELM_o$h z4jzlnx~lM9zbx@zY*>)0%Tm4-NGRCd&%p67)l#9H;!}Ue_kw@oci?R&&7u0UZkG9v~e)Y)PEBok{h@$vS|nOd6< zj@8YxGXW-n%%C~1kBYj_dj|ZJMw%7_$LD|(Pwb7moHEEwCD#vK-YH;S-hTy9t5VrRxuKk(~(mct3vQUi62Fy+W1xWW@OWz5Jy2P(atAO}T8*s? zvDBG&ggk%(1D4S<-UjE4=%0<9k^b4TI7N8-_k*P&D{qoO8Cop5e*9a|DOB5w zc$2(qSD1s|MbQK9*Y7y}LH}Ox9jTzuC0=>{KfC2WQPe-7)ENt{{e5$V3BG{hY8|=p zi$#6fs9uY7xzm?44(TmRwwJ$D=vQ+Nt6CoxX{&Z|7pQ2|e32DdEo$)Kti69%Z^Lse z&4}p3WN(U#v^8~%88t{arZq*C=B<9+UC!z+fp&0Hi(ZKq2>*f85^rtsEZ+09PS-LQ1nC=%EP|lPQI=<}CVV_uxmBgZkG@e8Ltb z!-7*ZtPXRD8EdfsRS`9?Xo_DT&qhSVIyT- zvm-RRXFI_@ii0zC$x-}PMl)wp<~(CvG+P|iDIYd|RmVtj?*GVs|7d^zdExAj5|#`7 zmZUX%QfKn7^S)b8*?-y=y1O&$B}VHO6$Xj_wkhLU)Zy6L@^-u~>!JDE6VB!|h$LaN zmXcoM@1>ose^<*s?mN9gEn6RSC+nla*G}6b-1dPGc^&xJvIqKZhSKiCje;wu$xrqE z9`5MAl&C&Y__f^SF8_vUbBGXf|@1zA1?nK1jhrSZ2HOroKQCsn@-W`l%5`o7+) zD@~3)|BtS5WiRfef@$LuryqALfev1C38F99p@XTY2TrH_ug@lGeZZe6&`0NR6f=4PcTURywvVy{~UK&#e3KtVRm+NvD`je0S_EHC9P@8 z8y_reQ2B^EspOgZoyWEJ6o@-1hoB#BT{hSw|1OxA<;R%0q+CN6=nP60wwZ-e@rcLJ z@_X+IETSsx$HW*TDI>XZRbr<~68&&+201UZfWZu~y*W%kKl6)#%YQb)Q4|9ZkQXnW zaGv3&ORaQ>QF4`U4aK;l4csFivL-!&k;h`(~8zY{R%sLd+OFC zOaQ)my=>Idha~>^>EDrYDaTQ9#o-u&52BkVVN32yf^pa^wYseoC z==P=h_ZGL939m@uK9?&^oIkfpw(zcfcUs64$P77)YxA;0dRs|DJ7b}z_LFBK0_)ds z##kFpwxv%Y7H`}sGH@93}0w{o~aAWA0ov?rPG-ZrZ&S_M}9Wyu-a zuwquQ#LFwqblL<%TRi#2o@mdQtN59vU7w>s#$_RzPG#79gYSY5uY(W(sb?O>PgXR= z48k|&2=E%Q2G}e|(BJq!Uhm&d5T?0jw?1q666)`>ee3zHm;+*r{0#q>p+9{Nz%Ux0*(crn~EG#}xr-h^mSk9UHBZb50=&Z``L}2HF~ftePutulzWXUehvr z54({XPxlrnCCEGtIMlxFDy?`jKps`*v|2(Ec;7iU>o5WrRH@B>FB-0XvqUYbE!Un> zrAtiQyAxGyN{{2T-$)ywEg@-fC==$7=e7i`R~4(ou5wSOo%T}v_MbZJ=XM;fPHu5{ z_2xKRvP97!|ADht|N3C5sgX|D%8o$VrrE0N5e+4~G)H%EdBuNLv$`3?M28w^MFv!b-Bn#tb^Ao58Mt*lDiJ5phc@>Y96G7me{D<-2c1agja z&%K*i0i~1d1anIKR>m7i^S2>;s-);%l+s&m?RBZJbl~sL7YTIB4k3 zKIQr8sI?~Vfj$#M-0|`D_J&cz=7dIOl4D!bovOSnuO;s8cnzX}M|^k@5*Htond@); z*xn)tg7hTq#X=Y2cHMVB!JHpXvy`*fhwrgAXd5M&n}IH6U^#1y;5M^uT+bAJ9f~)4 z`m{pbzgS}F->#(Q&KZX(Ezc`oyV(QwN+wZ$H6b%4FQ@zLYxYuCgtDEWVE6J_hk|dK zb(>oJ@92|4zg~3RW*rE?rvQP!rsQmmFU*Gt%{k4~=Qd4_KB!b*JU3c3=EP=Bgk_OH zL2iJb<<*^UH*9`0uzYv9s@obgc}N6D?irYpeM!5NHT?UL(`JNn7I(8KLvH47-J!<| zrc5*j=?O64e{}cswEKi8%*}OUevI$IE`c3}w9@n-R{7nPo0~&t>bt40-9S!HpO1Yh zFOx#|Z4I#G$QwYzw8=W@XZk9{mziPhQLuiX@j{o^A(d(7=y_-I%JRjL64bg=(JD@c zgz*j_M5wy~0}2!?|3U75ajAbHlVErFWzy<~i|?#Rj$KFRz&-X~ut6YbxZm8s?^0&)+c40?#4Mc!b&g~Yat5r-W94W679<5f z+u_n{)N5Ew^zh->Dr)`s6G-XA`h9Oe6affiKmDYT24P6x$7i(he zN}MJg*A^M{D=*A%aJgC95ns@n=e^nSag4xk7 zU)7nHu58lZ*0hhUy>H#Pera~07Wnm9m6wjy88eWpQv~&v%-ZM-sVLIQfv9gX*e}Bf z*xiVtKJysa7n13J4~Rxxig84Lk%ByMtcui#h|ViDt>i)>L#6^EHmtRyz|rioTr{hH z$)D@8gtgo;A0dmRcH|1`n<@*OHv38mY@h?)pFlm`x?ghI&tCm%@?iOc{|4B9@V5Q; z@kVrfzu>&XEE*$bKYCJ#yZit=t6L}L$ydE=92lQX7S5>-A@e5_R1N^iazmwHyvD)z zg!_~Xm1O~E5Wh`d8p8_%M~S;Tcs*9<4eewsRi8V5KI7&SSBpEc;^~nM8#JWB!NkaV zA3!^yZ+Ikx7Ky!bReZzo!|T^a_D>*pwhGvc;UKHDF*)0fh5k#MYrWjD5L)#}08wu! zUsyikTH#IuZwVh0wp=?UE~D!DN16C;>%LWE`AD^cC6UFx092%ocJRE50B1x|^?DH- zsx^eL>QKQL?`S9!zueI1wjJ{i8h*18oxbwHG+Rn~gyP9@ivMzaHheHB_*6rLfE|uH zya7Q9dVurjnbZN@)70TEEF^}b9yC?ctoW*wQtINx9GM1EtHTQ??R$jXd?60G);Y`+ z%9aeeFN9MA`pjOqbUD=MZZli?rk7yu0MXXAQKhRknqNe6Z{a_;_$KoQjNT>zWvE(xT84r)S`E#jWI<*0p@VCJ6 z@>NLQco(;3DRrasb%kpp=|-ayGUR>Nr)q1d@*h63z^f+vsI(^?tFYC&cwhSDN5FrD zlm9$MF;8qVu{6fBec}g2jOxD(H;n|cN~;N9!dqUQf{jNB`xO2>Epz?xUtzVU{pHre z$rNfAHolWJ+a-$I^ zXizMQQz9RA@7PeTh;H>2=4d%tebTADrE|Ek*O75x?_h zz^U-+4Li?F_kjA|BRx4}Q>9A0{hlI=l990DrB-OH2hz~;l2Rj4mbz<~=3p-@;-xX8UN zn|^f2QT7g`{Yx!uloFy0GM>zkT`MX0H=EdghE-*WPaw4v z2}IxeYA>gro$#qu4I{(V{vM?FPw5^CYI+zhzw6s7KW3sQ^2ab3`^m+sK&dP?0c0ED z_~3|BE4t&!Zrv_7;{~Uy?FvcI+*oH~NZ@n!!1^yjJJt^jw3az#RkXC2a{C)(*myB{ zt-(|k=MoXRm&yHsl(Rk!N%rb!`)oc`@{mAL{V#SUc#HyH9?z+aub12F9KMhzRP(#8 ze`UxZ6YLGIu1r0*yy{Bq zYW*r6Puh7c+)S;mO~x|m$|%9kDgDlwBSh;sHZ!N}&C}Jc%;#_akTAb`4UcJ!p=Tp^ zw1&jx-A0Tc*<`#jAfBZEF2JbvjxtT21&t?5WyLN3D@1+|z7=K3{nYiY^-5{4PL9u!P;rel0VvjCqn77&=Sa8KB>$y5)Hca^XYDvD-l$v<#!oq@~1!JN{(-}7LO)G+y zEt+g1>y&CR>-@$rc(d+pgR%0ZwVHs+5GE6x36F(67F{j?S@AZhR{0MOWto|oQC%r* zTidW{?Q;QASxLNquaQETAtC%R#Mg`Mb!@t0SOf~Gx(uy3cj?cE7oN5=dY{(gSG<%o zO*)blH$(7kVOXAFtn-CxD3J~*thqFLr`EYPb-->U|0TW8KGB~_D9`We-?_ZWQ7$D) zUHMNhfNt`u{tvS0n)&wLLCbG?BpjD7#_MTm{VF90_;av}6r7zBz)U;AX6)e2YGxG4 zLz{Tg^YAHAm`jN4~xczt&rot{=KD>s`S$ZQA5O52CoGKj|0i5VhG*)f8P3#n|05BYS#Y0T z@x1PipRF&`;!oKWX_Ogf#^b7p-iZeo$BundUa`c*s3Fw*>QDH>6iLK`oOG3bbW!Ak zMFpI!zZSiVWY!-pnG}`>1xmta$(knq@>yS|mn|s(OsA9HL*AErk-p=<3(e3a<9Jh5 zO8d{PV?&dKVHMAk`&mCcJzrVm<3dbI!hgs1YZc%Ro1WvC3Ar(IhkwA2pX;1Sl_8n znOCe1u@-CTQTEpUN09!1aPD)xP(?+BFF!of!X_axQK|P1EObonCj1*+sRFXR!qRuX z*~vjc00>O|+I{?77MwwaFN@*C0{Zh!uTj|mgY6^x+w@zYVc4pwDi=w)ZrYPp**m4( z8(sT0{@h2C|99qA=gv2D4q0VD{*g-Yzz|7&12{g7#pY~9sN)oWm&45DDUJ`)Dxd%5 z16j6*mnaXs><_LJNq*U z$4_bQ3!|YW_J+;XpoDnrjr!{}Y`$rRKgEDH8yGS>;k}vk4~4r`cj$MoKkWdR$irTk zt5mAOe64nSEa2Cg+KDQVp79;WSCR!QwMhnZV{hqoZK!$Xm?5BfDzfkh5C~f3TiD9_ ztBOq1#RtNH3(QB*_qSPo-oTWt3u?bURZy~Kd^>Q8L%RFP_^mxG1xBeLHS8&`MN`Jl zUf``|+|yX?3~M|&)SAuyKrNdc&J-m2FoR|`q5RaY^vatjCEyTc&SlZ4_z7xE<5|A; z;^Khpu#k}*sax%TLd5aoEB*}*RbG#RW;+j|l5X6%p$RRL&FX9N0w4)t?57h?3JLaF zr@b_NPb!c;2hn8Z6{{7`csXN`_V(cqM*~Tv?_VIwt#@k%XE?G z&A+bcKjn{7e?RhM_*;H!ZF$C_KhU&oemJ(gKu*uu&7yTE*Bt5L2V_TZ4+#%h378C- z&xS%qYTiU{&nCjwR(THL9!V{sJy$G%1U~+Kz@WN#NcKK$-`y{^d;b-L{g)|5u8ijd z_hjkC>?O@GjyN45#z0C;%C>IN~-tLH}ttbyYUPyhl+@5y<*jJ=ZHyHNdK3d5(nbh zlRWK7z2zEaNr*&NT0n^d)15hT90{GPQTkN(zYm5+lo3Er?ZYp{M1#M>XwlU(n&;0q zCFyg$Btxys1fruJeOgeD9yPlYhwU)G7@stYDoO6G2dH)}N; zUX}Q#U(nH{b!)M2>3j~smt}TAUilt?P zXwZn(+$V_#Q;(LPACl-qeUVnAw^D$cL{sOT`?a#Lxv37>8e-x=|d-<1|(@Y)*=1sCmB?6o0oLlaTQH z_2mwyMJ8bALYm{T&KrT_x6pzU*bBx5wElP@5z`tu5}^+mi_8?hE=^6L_5^azQQ-E$vB5`rz7nxqj#A=I^}84BAya zx0o>=2Ea(bToM`_lFAG!%Zyw!Eh>a$An+)cV*-dg!zhwWdFj6e1Okl6z9L}%u`mG9y}5p2W{D`^YI`j15il8hGL znd@sEECmR}pK(=;2j@ORV9PNx1OB=xOBOcth$A?C`}fp>f(6@9@1zb_us$^l{RJb) zT^{YsD~=DNxVYFwALFY$8qw(&Fg!dg)DSPfAuNahWuK25#T_p1(dS*=eEH&q>hkyP zc16r#(#B{|&#>TIPellc_8Cx3aq-;~FoE@iC#pfu4c2>&8bO;{Gh$i}#Q~QxAFTEUDrOLFGa<=B_C@RMW(7 zXEEA*s2W|fU6deZG zBTu6!z9Ha-@CP?UCI!NO-vNN!Y3sW@n{akz2%afP(T4@oNhQqZ5@y!`uqHpoP`Tl2 z#@XeSiKdwHlBS-pL6&OkX^G;deSeFml!mQYnT+f1FT>as@pBVTP=rYep2-R0413DU zTyuFADX^U(6eifV(Vs_~JOijHSmA!TSMZa}NBIAby|)ajGfTRL1A(By-Q5YU!Gc3@ zcXxN!;O_43?jGFT-QC^cJJU0r?w+UTo$LEQf4J^rH+$FKr)uq0tLg-7;h4BSzg&-> z0t{Zo{r+8gfgEPcg&(Q8e>ojePhK0Y>7zj&>%kO39H*!4)xY&%A;+R8CX&rmZ%?NqK~Rzudi zSaqaPG}%xZ(~TD^v05DMO{I<%g`A2yPNCntYX7LZ;D0pAddTu z^zm|orBq&PF9^r)0|sOVMuF{++ehtM#zU))uAX~3pBE#z$7?0q(M&~xF2Bv%>HH{P zk^|<{?*qnkT+g^WU=W$hGS~guC<%64?+A4~lNEL5xa{uWOa1My$+dsgETBT>=I4Jh zy+1NI7v-q_YEhB>b~~@>v)^`<^hchkQWvzaajh!7#mrbkyUl7)^y_w4f!Sm>^3hVk z1`~uwrmgL@kIB)+B`nxF+pC*K1glrvJnM>A*V^Kyb>~NnV^^&J>iF)Df9s6oNW9fPXWd_P!!GMa zaH9Cm`rVrYTIj%}<1!jaZ5I5q`VO#gy^v&9L&Ne9NM+=Z`ieM8&Uo^@8M4Ep6WkQ# zp)jArFgfBg${uTOlynZ*ra3ew(bM}$hvu)`l{>fUs6OXmQqAL5$W$&&lQz6yz1CPK zi!aw&tG-+U58vHB-l#amx_r%#OX0D4xVpanYW!Jt*qoZmsQ^OHaDVCeJhZk2kiRLG z4_hw2b;)ciI%G=BfoqZ7H@GYRaqR0`raG6uYxVc1a!8U&t4&@^F1N^mGgR zqnRr2k6}!>IZEGI-3=V6Mcok>eS0&Oi!(@5n z+iDh*HId*d6rIW=3?^o!8+@z=`}s&_zT;ep&IKxf&@4AXn|4137pV-FDw{+y^d|a>p$7O z%V-c(2{XC$NE1y;)=WjP@LT(EYQYEb))Li(4~%dwVzU^5LhOItj`MJ8jJ zIF&O``mvYXHmX2wrop1VS;Koy1-cSNEp%TpBel@lFjq5RRt9{z^ieJ^shoy0fle~w zy@+S&<9T_OJ#op8<%|$nrUn-hnEI-@Z;%yC5f$87S@qwuoMY!Qa(7+ua0IdpiJrsT z%QBEu^bv&vBkDTVXcxj+?Qm5y+O<<0#%a`oWD-6D8Ur+nUo#;7t#kTg*L(B0se^W# z%5%06A<4PUUi;^{h_NY%iLYvuXR3Uw;!dwrB%>Nrh&;`5tnJb{&t7ghzdJg0^Hch! zxDi#4V^)N~UY}fCmSW+?8__EVD{r`OqPal*jfF(( zDvTAD>g-J9*JfhB${7B-a=*{!Ckil~hI*Okk(mAUY1~Xzb4C>&5>G!F)g4_Wp(<_r zY2O5^5YT;b1u~XlGI7cCg_hXKKu~uBvXwKGBi)I&ZD?^NgPeC-N%2wf=xy!~L(K}X zrEhp`*6CV(fu29CR?aC!M`a4r^S=zcv(?%l<28O!TvKnkc)B%N$hprjrB*r7Kg7{U zxFm?&ATGWv1NeA7CX<69q+denKb-wvu9hj_Nl-SDIV;58vv;i-F+@vUuzB_AZ z%VyWytY(2tzqVVZEOa+JDrA3>X~ZrWIzbyRLo%|It}b%!l3(~HUoV-1Qs7)8uYIZ> z>#BsEM{%sSdVg8bU;>HQ6m*T;_q^Gf2=~R^w0j<@X5rfNywbG zuT~Yl3g2p-Gf%0D@|vMEv5L(kuu&nibdSxU%aPeh9Tg_p_&Xbl>)RlgK3>#KkIWrY zn&#!tcU2<^S3Ik0>ejh|u1>0G!YP=dXhwi(0fYf?IXtTCK6|^VI4;LcYGLN5*AM+;lQ<*q;>#o1RN`j2JS%bJ zmHLfFPA*)wj!J-+`E13vsTGhHaU@&^u*+^AUYv`eE0$?)8o~D3)6lQ$)Q5SmFD#oN z4hs0^;dk~Ory7cO>rqfzhe5Aq%DT=OP?mRD+H`E+)yzcXw^{5jaht5yo_Q@S%5TqR z0VM0DXh`tZlD{yn#s7Ql%zV5eq)%r@aS*^(y(z8kzO=TbSXn+68;s5Bu6=f@1vae6 zzWdVcj&shuk`W?PSId3AS&dUq@zmdfdH%l5;GR-! zlmuW+DpW^t?}w~tAYk|I?Td!3JUJ3+0u_Bk9u4~;g%j&Z!JT&PN#!*8K@X9^Sy}32VRf1qA#ZcKzcBS16Y7T=p+Pr<(hfM$HJ!Jd@FVbG6YW%B zBJYG@&(!S%YYO-so&52=__wRrm=B%yY@KIgA!V8s{+yTN*C#(1z!sir&4x#Zq0Gza z%{o@I)Co=3T`E?cQ5TqhKOO(tk^e+;zaAFy{|1A!EJn+TNEPc`Ex8xpoX-zYFfmP& zFk3A>K<6PVz_1oNx3-wWNh_J4^i}@b9eK1c0W)p@Dytqrz{E7)PhFaSyFS^S%eMz~ zMT$3`Eg@dQvnQ1JhsySkRrtFrhj<>^AB}x%XSv7{tICx6oGlqVL&0@-6Nnj0Pcve! zPnXx!6v3t#4jnl?D8Eqq@1FdxmXu-x1uv)Ew_e?A6BQS)!C;E6clI#g|F2`HSMaJ{ zDnl;WnO2ll-28RY{g0p1GK7RvI~O~JtSmn;@*tx4sjY+f|K5Lag>t~Tv2jOM)eL_> z^gp?Io?b+}#&W0rP-?;dID+3_pojx(lsxv-6X^dEz5%s4mw*MMT%*$s`7c8SB-ccV z0c=wd4C&W@W}g3QmxYGo$&>*EamC{9!Lfha?tk+TSrXu# zx3C%N(f->8`iE%&82^8!{hw+7w`)QE=MVrQ{r@?H|M^+~*7#o-1Hgp;IfVZ?gnvM& z|9{{MFMrFNUiEKz*gu28H~}p{y2@eQ{a%@umsfu=FRdC-A6;LI&*y&gTn&T#XiJ{|m6C=-?3(3zaXD$-6oyb5+U)Ao3s% zj-Wr=>7SE8!jH6!FQ`!S6kUHE?ojq9-#wgETfN@rySKeT1E}2IGTP2RRF;q}dsxt@ z)`&2-eZwFEfs4`r1^Jeod2_A^1mzZ(jf)rf1GI8lE;bwwIEz9(`U8F>oD8_{P5Nr= z>FM1w_jsx+vm;B&s(7_;9gLs5AcWt$w!*iZ>1~5Buzs6B+lkg4>zBzabZ7 z&r}j?H9BsPDP+H}o~rpV4)B_w?!PSg`#hKNX)RV*`Ew>uDH;OTi;F3!G~2NVD&4to zku15;seZV0{s0Z^)$wmdS>$jyQ9?pSHfOMIce>P>nVrpQY(xXpCM@mIGqSMg0YdBK5 z%I(x?X=j-*=7`8Glrbih%2%r=+U3 z$}79D*in5JS+2pg;zS7Mj0MZ*DXVWPhmLmRql%8WCFR0IKjxpEH0+^Nr;l{T~DmC<+WyC=Qm9oMGQ$(fC5!#$O zIU>y0!z1@Hm#iG^A6j?v8OYZPJpHwsL_+b4rn$IZbZU_;>3iFvM{NXz7!pFTon0I3 z2?8l8DDY&L3DjB$Jdq6ms%_O76OA^PI=a(Uc@Bag*&?K;+v9+)t}f;W7Dh&ZIvj_V z;iiv53#(bdP@Vw615&O(6 zi`Hc}@x{|e;a*utF-i`irlM$dIVE3+?qKT$Z!oPBoLw@QhbW2sFoSwY1I6j@?V3K+ZOl(>udB>ZN8=fj?k$|zyad7QzKp1 zHXsva*=-er_9D8pEi6lY{m*;U$^njCYCgmzbGoPg7(@qcZoL>bD?obF)(8t+fnbsh z-omUum?(XeG!}gcVbH?1VKE}2Gf}?MYea=SP5WB0*iSid0oPBDnU+OCv|o!Cg^X&iRg#HozGpK-p$}neya;pNRD(=!9;t`uQ#w=!1n@JZU9UYmdxt`WV zqBzl_nQb?ep$cu0C)bZ=O`_)oA!IzyD~G4bs#Qm3|~@ znqGQN#+W}e!x)rn|Kv~jbHzQfhImwnC!Z!SNmf%cA_A4Fr%M2;%4SzoPC9aFVZk@H z)}HJ`7(W3SHTAcSc6U0r9&j-+e-D_ zc7ubJ!@+_g|53GGpD43Q&mqrx~jbu`ITfnW5buL;bmi$>w4-+ILq}4EW z1Dfr^(s2`M;97XGdhU(WSV}oSZavy++5TwY29O}GPCc!iXuIkL5CUKsHq4_TWv64o zXy=u_sns1DX%$~pNjJA@ouRGi@A1Jq9A3Cg+0Xy0ry&Y1s>i7t$S2rS*37m)-k^>E zbSg#$5XW&{Nm@2HUrp9(DLms$MLGI|n+0n8#A=_I*8N;4OAS0+tTEg!Ygq3Sy583vj3%Brv0=RY z3}@oVVLV%DY))!IWx}8VeV`b=*)6m&;RD;}4d#KPD;VLwZYki>BF|UtSNG2y;a$e# zwOvYMRPQ8W9J#w7I)tu~^ht6eT*CHq^>Kct#oIDjv8r#E7!#?7M$eC3;n*;N%|t|6 z?Heg*94<(|O=uifeG-|Zz)c}~@+Fx0faH2_9=Me$j7}pUjFcl<{XVe9jnHj6GI=c& zCKHvxwAwO4;OFxWZjYC3-0kM$a)yu_7Yw8%@cFd`f`Q?op(XxzfDUcr?fLwjf66>2 zvh9Elo8$8=Hut5cNgO)ZJ0?^h4WC@6X~T*G?d1F*v@(%cmyPNx%xV>$of zX@2|CZZJgfM1L$4R209>4M81Xj;bu@tGC9cN5;+3p6v%Oa|UwvNqu)lFtF6-7WZ?l zsk=~UgKcdNE6R*d?IJUMxB_m|^^72}?`#_{E22xF5#D6ajC zw7HG4va<1j%_-sOw#FXkVeV#`U5gFDj4CRb1DujApYrac#$uYE?Au50)f{T&t!5v8 z9ZR4Z!_odWPuJ)YukF1&ldEHfg~5F5H-Un?2iTRZKf$V>;e2?^ujYC-;;J-41FbW}?5`*#i$CyWS?tQL%*AFq0TcHdR-E4-Qqlufj4SxiLg;RKNCql--{uG44g0T9&b%k6I`1BX|7eo>0}!2`+BH%3dk zv!-W=a+IKy6@CKk@(1CKQS$Q?+;Px%Ta^fVim%_S%~5`xA1aseB6rtQY?ZghU+$#qA=A?&U5(J$6tpIOu7dlSkI z{iFqd^QlY2)1g0BL|=sWiYhcAzFE8iFjB$YK>KO^S6cI>uZ<6?>s_6i4?mozJbr^j z-jcvT)K>(OhI8vq&Sws)Nww1=LAhDCJ9A%3YB&-#qMm_MggNxk(#TSU?8At}sd zJf#D#B@qbIcd={WYKdti9d;|(phzH(LD&xPGNLiDKwiy9HzSvT-TH)>-C1D!9U3(C zaUvT=zO*R^%6dmhjqT&59}QmJox0Q3EI9qCUM-fq_a!3~*9zQ3eroA^DG#kUo$gYI z;1cU`c4a-J?sub8Jsxz0d3l1fps1+3&J>s_j3zNZMAA8Nsp8cl=qD6@sXkh18A^2i z39K-@=De;G|NH(>bltnm_NyUES6|&|5_@UsDExrB1c!D&6)*vg>q(X52>-*R5N=wA zTEPBPZu@rlS1DQ9>8uWmJ3y@hpy=id<@K5q$7#d+Wmx@g1#pHio3&8wtj)Mxe}7_C zN!~VicsQDu1h~iZps(*Yu;q|N{i^=0pZGPNUorGbYko&90N^J?2$yk-1{f(=&Vf}}HKMpS)Q$<4`v0??Ui#u^w##bv-oEq(4 zK=Go80FTZ%HJO>FEHxAzbAP*(vm>-dDAl)flM7sCn7#J{0>U$SM{Lp_6=(y3hKeEl z0s?T%y3@z|HAmdNv9y5D;lxI7eHx9hF~INkZTKYuLoC|IGVHTUJKggNc`R|zc>2YB zs^7-{e6m=xzp-N59{!!CeF5AGxr_a`qs~MOxRDrip`^?_QNdfddvYM}elm_ocxHQ~ z?dl*)fGR>_)4nhdNt1kD{pLkfB62`!`PA(x5eA}QPaMiED?jC+Sg*Jt$`fsfM^qb8 z8FGf$CHqn}B>rN(Dw?|GVw1CUYPBdufz!ww{tduk!)K&5*%kXe60RQE8l!6$lIrdD zS}R9S9j$-H6%2!0*80IDKF9eIb0#?*1~=}CBWFyI ziXMqhlkFoaUS`0kr~oyVRLW%bCjnkd!7Q?mi$pGYbA&4NSb=w)UO+zZ>Qi3vAm6`L ztHgARHTRl1Vp9bZh!Fqeih>(*MNMLzW+Xq$2|BQd3pzZNI0Hb0o?MZbi<@alOO_%k z^W)=+4xEhVmHj22Mx9>B6|3&gWg&=5PG>)%;OCE6T)K+hm()|2`Od!V*6p5vco&D& zH9n6=WMyS7Lf?>jJX)pNT}AB$4Og^&@N9pTmQ$S9K1!)!3!x`|p{ zHL9TSU_4;M+DV>#ibyfqSMy@tOpGqK)lP%rOlheIy_o9J26;p8X&T@92+J7?%as_A zq2Y*qw_*$HSN%^e?FJ3_#9`r*m8eQ_X=3VAe&_-vf)p-Yp9fu;dJN?ubdXApb4jm` z8^^|kYZ+88n!00P2qROG`;5xk4qD2S=r$4qx7^JwS;q;@M-tDJLVoz3vQAq`dpoK7 zQ-{qrh41=ZU4vE4rZEbrQIEm~_2Gy!tdcR6_n`7JDjxW@l=K{YShk`k{x@=mfM3C| zkJLvao)(aSo~(4uKYCJsrMs;FM(#Dmv6z zd0g*<1TVcj$-uR@UW*q!h~55m+G1{DS7ZyL= zZfn6b0*DOec@tJ>6M`SjUtdALIC8Fz0Iz1gvJwb0yK)_E)*)V?T(5V7)#;RDnn1(t-QZapksRznoK8T-;k*WE+}6T%n3zP5KK# zEog%~g@i<{R>A~ztozr&z93H`N(fF0B;eWXR9%>96R6tuTf(ciKzc8q2Q(-un`+o= zxQ&JhYg~SwS$V^}X}V-7^LQjxEhe+Xt=7mvOAD41(_s&{wu1pqeGV`gE&?0Q;zK0n9vP^22qkx=*6H}#QTro%}|X9^AcfVzvH|J-?IIo-Be@;R{m%BA`4mHsBn^g znM35Jv5{eR|32HRx>W}W4ht8FHCe{xvS-2TcI}rOhrhb+s{UUA4?p=kS_{oAG*+yZT^LtramOOz z6zFk~gWo?X#>LDRS;z$jEL|6OgrdLmZ`I)VQEWp;(!$(t>AW0N4fwJ~=(dOwwe@97 z1U`T^S#@sjW5H0z8b+$+cbJl_6@eDm4TT&L#O2pB2N_D@g-8Wnb5l!g*Ds8t;It>u;+t^@tH1|^-aDij^X`l(fAr4quB6!e+X?m%kAZd$xCFcQs8bb$H-utWooGw6>*?myV9H93LD+-m;1o`C`o3^Yh?^CJ{zdGu@X8U72 z#0?5r!_B?XlmH%{=tlMRMa_|N&Hh!2O-hFTu*2aqkxs+8?bK z@5c$9U%>~D*;}vgu&#C4-?L?>KmQqEb_sBH>hrFXTUn2x>tyA;*73cR#x^B7atB z+uw0&8=5j+mUlN5$K47!|MJk!>YXh7PtMO9=bP7R_@ul+LLO)Kthk7*yg^6W+Ng+r z%;d3z46^O_eQgd2?gC+ZAC;=m(Zfw>Mc2pRAu(NGeO^FLm3CH zE*tz;@*+Bg&@&Br?uu;5FZEi|^1n&!vTX;b77%!+VaS-2fmE1dql7r>`gZP{nt6v2TJR{Nb4%T@qEkw@7{#wBau%=IWRky4e0_7%l~^Gz*Ej5_uLruVV;r8G@KvG;cjE9y8ciQq3Hs!) zYueSWOKW7+5pH8}{wLOV0uLk>xC*fG^Y2F<_ic;a>R*!_b9fB-uLeHSV74t9zWh9I3sq3c8%P2JaiE_ zs%_9^hzdi>n{mFi?=aa)o-k^TY^j;AnDWjW47HGj-`4+*(0{@R@hmaPB-6QST-7Q6 z5TNl()JTquZG0I;w$xs0hP4`+*uI>VF3^b>&r3n)vg_C_0r}FH z(fqJr=U@rc+QQ+Ytd>aDkY>!#FA&^}6ybJQJx+adwB`k6vJBcvQ0MA+5J7cs6s}c` zmh^hn#(&7rBhFG5jHGpFre2NYLczniki7JXk=RO$VLRkN$tS33UuHoS{n4N^4zfy? zO^Hz*!c#~y`93j|r6E(hM(El?k8{cfl{xr(RHM?xfG5o^5x&akDHKD z8ox7*`IJmGNj?cXB2kV(v_8+N(3x(J#+iNF`G7RqOasWI zRV2xwGD?h4U%`oTg8)b^>@A>Km=7R(;g}K|H=9!7aa%H>vH1AqThqLx#}y)=m^n+9 zp6xy9lnvGz))xLO4Ss$#NYg!d3%;SA$z9WN>#fwk3h)J}+hZSS2;6M3cm|`OED=Wd zy2rHA+2C&ViuMWiOTWCLCsfSa^OwQhTy(pLuWwS&*j$zF7tHen74HeI8r&~GZCP(a zb*0xTSn@l*qS_j`hwcu~W!C#AD-%t``Fj|hdYXAS91t{9Td#gnaMk|B!deh+P#&Al zaRiKWfCVL;6FfffkvUsa1b19wGjhDaI=xxbM0>sY4 zVB5fPl56CDkRve77*SEt-Z3mYYM|_(-*np7d=Ey|aG9({p0=6!G-E}Cbw1E^Z*S+v zXvR8rx?*c0Y{6l^HBcw9Efk@q=3ncu&9sTV7*cBm{DzmDoRTOP&wIzdGQdf6{xFTp zJS}Vz6WaDKHG?;gN)Mq3+ocV-J6dC=k zyEO+Irz7o~*T4%;{-pw?_k-6nEG^zQJFS>yWG)&I&;h|M`UCRWEw^-L0xpy?2HpS;I z2G41-8E1_eLR2?9^~=?Ygk5u(45t{rCXZR;%rj$ip3miN=dcl=BI@}Vi8xofoeK6e zjf%z3IaZrJ7+m_Hk28=(=vj54_GL<1C+?JF=<6sq=6B8z+RiPlr3Ril;s67x!+$+vAzB0!koUEv9Q5t>K%T{2oqsi7A9uhy--+mE}!gdU@TX)dxcFHoGD!Hhs*bk z*L$uW8qYgJIXSbPhw}6cPOb=g)y&E66Bh0Gs^j`kY7CHHwHqQ?T$g{r5ESn*_=~BL z$P8Tzt8rVGkqllX#JeiJNfydS48r?PQ?0UKRdsY+lGH7 z2-4ty1~ZI#=_!G%BxHlP!~z1x{D~g+5n#JT z8GCP=n8NyX+{3@+ ztnuS65{&9Ur3v-OK*nE4je(nj6<87FeoVoUS zVYK35@O~re;S6-&G8SJKy0k}?Xv+&U1=ZEcKgHK3>#EKpmgp^J)v1k+(hi`AvLnLc zJ)a_=7}>ijI7^5vYEO&UI$U5IUJ(}CpRy5|WZXxnW?jOHu;td1nb*1cYE98VyZy}& zJb42rH?W|V<@tu31du#I6k%lSl^V9$2^O4M3h-!ygeT$A5SVh7N!!thQH+K(u(z*G zUW(S@w9()%Wt<88K}tD5Z(qU1(%ZOO6P*bw&?_+Gdxt;f=MyGw%~&_#LUk-vm&vGq zpKn@#Wdem-haX_YvUH}_gV)vQbrp?JPaM%1cp42|CpPev!w=k$llrO^JFQiDN;F`T ze#8=5sUr&rVienbqzFYU7a3}M>sEvClh#Qly|1yy$~kP}gZQ$Jk4P2pa^8$RykiYq zzpZKvpm`(+K>p%dK+?^0KDCdk7R%yvvB69>n$C&Y@p@|6QdYORr|nubjSonlrIm}; zbMm=JTMr&>ZIRt&u~}RL&wh)gm!E9VL{}&;-O$VN-;RXMcz$7xwYoS9U#`r;h~T^e z-jf+t=}wyaoY%BfsQJ!?MsN4w(`O2yb1r>G0i18Xh!Jq33FS)dD6nhyc8=K%+P{by zE%*ayp!UE#;BQ0;)ZZ%JTYyFxH&0)?X^atG*l*e|6`VvEQB&QT9~obtjc3%{U`)!N zfqqD~0c%!N!|0C9%w-;tDt4iQXU?y`2mO%kSRzrorKu2lNFyJ3*23vZFTWH=tJi!geM?p;GIyg;-(W&CY3;5;()N=~|Du3PpGrZY#|brdrrh>1#Hi#afy&50@-Y#0t-W4cgDq5C`#`=V&Tbx!*PM>J~@LgGvL&2@A5NTzj!hs?UW zZkYCp6jlkU({{adQ>=}&Wbe%gHGo7B@0R>?e1o0&0`B_TbG}Q-s0DTz0lln6l6x1c zB!Y^tBU*8xnW^T^S-VY}*I_9j7Gok5mhW$Y>4Ek=Ie&OnHpJ?Q|4wi0W-b*J=8j_acz3wk;>8Q76K zN$44UE3n(mCIW}mxh$Yv9*b^75ceu z`N3_QfCq6xn2S_Pf=A9mfp7(`x{~(Pj5%KXh zYjT4Ttd2PDB}DOccu2Y!fCiDs!FSyk!}ZVxsgQ?a@_1shRTg<}^9#$mSqrWBxRaT8 z`HUaY3!ENHK}v&vSPKUTIU?uLVAI~)QHwJvZVS%a_-8Vb?oD}|=7RU=(5*NupB7eB zFxVgKO_%kmca5>GO7_GRI%+{=2ZLNTR?}4^QV>{;EFO60=^ZP9MBxD{1=JteA4Osv^t;fTkV0lL+F zN$nx9lFMf>65Yw-x)UnI&;)*4_Fy9|&^I~<&l7H1e=Y4mU6;sb#IJe{f)=><5-3?o zcS56Tc#&E449_YxLiAr{E}QHxsn%2#f($j1!_jLJGIh)mWllnvOr^v$YG8w=t6J2Z zxsY%WW;B+H?p$LXO)71ZCt?ROy{vILTt36BJHqDjC&@jp4Nw#D2VOExP0E{dzXZ>E+Q_NnX0UIi|iPBJrYI zz)eIGDjEl&BXz>aVmiro`hY65o_Wmf=~#ZlgPC|DG@wjWEa@7MSvgdMY#U^iZP6QVQX{uG1fyb%kfunSPnH)|YGWO_s z=`tI<)q1VN*vg`sIWq9mq;Xul+K-Rz>`IPaOEUb($XHkp1#PG+oCS&%#XW*1a_9F= zZ7i-QX$^+kn_w9^&d}wzIxf0>F#Sn}8D5-1FsoE%&zMV4L-uEQ8yd?wzoZaUz^g($ zC5!F?y~5KsgyQLFW&7=pYXP4%*^!tO-JsgRNC`$bOVsC&hf1of4+%hUnoyZ?`m*Qa z-uGRSbVtoQ{N6L}@!ii@Cho!JYcE5@GYqzFZVsXHHaClBs75pOo2F*Dwct1&X0yx9Ohq`wKxlNYJFL$C=;+^#neWskl-{yQrO$hn< zfUjhJ*nFCzCUqd?Qa>bbg7%w33cE^U>$MUMvRu82=1EOnAt{6?a(!WqXmUKUCgtO) z4h_I{2D#>X8UXKQnZ;H0ifU;@XO0_TAFT^o$-A^azvSHsgwQ_Q1-{%Eu(V6tNAfqW z?IWkj6VX2NdV94dt_x|m>$)uKT5W!ES-C0E)P8eQ?jL5G?h|gT#ByA@H9P^dMn+@T z3p_TB_-$U9%iukT)>KN>AeXF6emxq2w{8WOPpg|POCP|6jJh=p^>fd5fj({yL;q(t>u zyEn}?`1|AhSh97rExEFNR+9xeJZYeaIf4ILkFuzNeA4Q{;auf~6Hs+N-^zyV5 zbXGGlGs?=gh3^5f`4?PYG$uyG4Cab@V7My;1WZk3sUH~>tY;J&52U^bkDk~?#~U)` z5sVcR&dPhe_!GpUKP%;^qIH#(kH2lUip=eX=al)yYW<6~Zd5x~2b zUCJ_E>fyo%0d&p4wuo+Xb7V}d!>xas*ox*oqm$OMnVXpK?RdY1sPVM-P$+2S!oFX> zM!Ii}T&$%Ef1lERc$0shrk+x{G-9eera##z`*Zet8U6y~@yAF4R~w*rf&aPpmg>20 zfeYcBwcpcHOgPW#`Z=%7qs+FZ!?n55;{CNz)9t5PG57A3>4dQq%SZApL>Qh$>V}l( z!QEjg$MFnl>nmxc@kXgSAc4@Y077G%CMHJN;!5vI>uli<(fNrRxD%^}^C+vxW)(P3 zQOkwAoZ&YU>41l|lLELIK5>Gm1$s;i-!TjoTU>yG%VWMTq5(OqN@LKLl%%)FQ5~W% z4n`o0kk&jDr};)1=1wrMVJW@VL;t>gl+c)%Wy`tUz1_fFFM8g z@{R8!Q;Z1DFhvqdk;w7Na6|yc?9_|{cXPa^AjZ*%Yx2^OM*DO?>I`B;HO7q)8OpS! ztNb@%JVcOF`SPa7@kA~|(4Dxvoa^%A)Kg-Sn2l0W*=XI1@}Uwl%>i1nGl)BvO|Z9T9q5{>-?GgK*^O4=}Re zkIv4OhhSMVBEYxDCft6r>7`Ew@4zbQphbx@^hBqAcm9;eUC?wMtx?!~!;w2zDrj@_^K61NqK<58OeO>bW})0l6}%4PUR zb&rDrNN!tf{)O@nJ+`U-ml!(9BY5d};V9;~%){{zM$jZ>6Gn7mQ*xDkuBkp0=Tn6F z8{=c5M>$}G@b6NCrpAj-MZ`qcIfqoBi8nVgi6&@0snD`f2Xe~8`jQQ$%MkF5_ixYA z>aw%(Crro*C2eln0m}<=C3_y_U7sI+0wnEYqe6c6W5_X=rihAfHD-#CUE>h_xB(q+ zk6%t&&Otb5j|{9O2VBqE=7W~oN*=DZ^_t&ZUaYB9S{FWR{|c3bUm>hohh>%-`OvFe zNMON4(T89r?QCXH`LY744U!hlPWWWkjV)T>A;Fpy?XZ)NS!6<({h|Y5;Z4e(*l+-q zMjfx07-H{M#1KLz`uzYdrZmH-QQ|14UGBAxhPOROTC|CFJkH7v}Yj)<7M;Lw5r3=Wb=bat}1!hY&X1`5N;dP$p4Ul|1!=V($XnV~bd)KMDSEi2+N6=hEozpW! zbMzmK>0($&zKmdAW6he8BW|k|UP~6-rkO%%e?buLNlCJ-$3+njHs3;=TCz;G-__p= zB4R2WG>-Ha1X3jObu%6-Doh)XNZ14(F=R~5Vp2+G9-n~3+P-nFGm3^nC7nd&oZo+q3iPQ!eA_zcjW#2`&clBaKb?wGOO6L~uwGzWngq zz$2wAv)ISsjACdHug0_x98QUebbit_ay}J!(DeNOsCozP%DQN4x1x&8if!ArQ58F> z*fuM+lO5Z(ZQHh;9p~mf=icvY_ZRH7TU%|eF=p@m>Bw@O<5c>RMA~Xmk80XhS+}Yc z;uQsO=L}q!5ID<3801pkdukb+(YzRo96AxfzWipYvF&8i^tr3{@kjnd{W%?Ouo-;l zI=-F_|3@U7^7DJ@PE0U~>S|PIOc8YekoV%mFRpa~j-;sEj@ud03#lFsJnNAE0){oa zMYUyW=yr?1cdavIz3zhlO~CbVx*3a_XT6nhyL>hH_IL`yKqg;LBgH?pV>ipwUK(1W zHg-NgAgecez;9)|x>V_uM5j@(p>Z`rEESjORyE?KmUjTytjU3qea9K*)ZC_usB2+8 zx7N#3PE`J<5XYGzGUmc6po?B=QVA)(&-2+6S@l&&TL%mRNF+*cRpsTwG7R03v-#qO zZ)=YK>t!PYHBs1YfD(<1Bvo3q{VUm#96x}w(ToN*9xl0Kub`*5&R^y|aCW6kO7(#& zV#&UwzxZlVQE5?>0azQmxE8$_`R7UkWbxT;Y_>aTae$*EN-wj|{8TPe?k+`6{h6P6 zQVQ|8rd7`r<4Mj@Y{ttgio!ICL@HdrMv%VW!qDT{E8HO@_HBXQh8&#jeGhdj4(4PV zmg|-x{-e1fE)?Nju&dO;{^&iSQ4={(h|yMi!OS)3od%W$F5PnN;9gDn`a~saV839d zm=1S1wreckA2k2vDBjx|iqQfw5@o9X^D_NH_qc2-g}V!$$B{! zkp+$A6`3gRZ%z@zLDN=jBHZfUt$a@-eRHIsg|g%**U4pOZSb60M#1~Tv>%wbGiC6W z0c^tP@zsFefgiFUhz1ofB|gU`9%wZIyFZ9-&5Sd{nh znoLKgWowQ7QWEF8>EfcEekM-A%4gD-WWF&Oy8(X^`-@zol_OZCArHio&RYeO&CaW= zb?lQCcSmPo^SRolh?Cdy_pdXK1WQ+<)1%)(gxwVW9GrXNH$Q4zNzVt9L1iG z8$M#64&3}-@Av98`a+g#ZOz{XyifKv1>JJngtWU5e2bKa5O+Y=+izWtakW7g*4u3LWJ zhK^~*?UrX*uiYKFau2o2ik|(L&d}b#w^OD6z3u$p{O&V2l4O;3^aD&6W%tKVWdd)% zqf@jT8}$&V;~Bd7E#ZTk{LJ56_Ux#ZU5YQyds=R58b>6TXWya;;p0Jm=($Gll)MKQ z5RPIDjU61gikMsP8#QMn@8>(CKdi8em9m$pA-z1fHuCk)tr?gC1ezzaLmPZP-LidGqr3Pu0U2-pdnLsU~H+( z8kW|Z{y5KAd?%98xvXaQx=+cw_Sol~giB$v_tjD0JX_n5Et>ysL~g5< z#hm=(HCc-h80bEQ8w1Q-U>|Hgm4)SGcDpMrwmYLL)_=a)O&>J1{hx!@e+mgn-W3P)suQ!A2d{(x7 zM+yK;raJx6Kk;Vv%aC+@o|Lwc#f2N)bWO-*oU~A?Zf>UWSKzspSBveQ?`#kLj7<8T zcK*;*ED6i#$`3YYG4i2Ry`QB)TH|sN+1}pdV{b_YvWcEzkn;|{p28Dg&|{o(niQ5b$9{zLypK?DR19L~_Dp$^B!YgdF*ipe&i6i36;L!u zJR&H3r$ld#oD^?CjkPCvvfExc*4u-Q}Wnp+zZeSY*C zS5)B>i8`k}{x6`(8YDN^INKkxi=!uiEg~&FLn9g90vR9iL?!l*`jB~@zQ0oQpt!Ta zyFw!EZE8;TwM!(g<=~;8F;5T5pb5-KH`7`U`wBmq=Kz}cw7fXYgFY5YJdUXhNW2*+ zSrg?|;|2EVHr-uAt`SPf_KefAqf6e8Sy8fse+Z_eFn@&%!_q+J?rFN_h+joj1l;O# z_WBmopMqG^bweDkXngj1J?lFW2SNLk z2|%x|Ph7j-bYhS?!WA^9%Bqi!OtgJJ9*e5(98X`*vwT9nm7zBib4c?mU(1G`zgJ$X zb$?sl@5b*Fm~Sop^R%Yev)WICJ{B(w(FODc^;C#EK}WrSStOY$d(q*gDK^U;&C`)X z>B3HE&c=wXY7FFt=Tp*8QTC%p3#HgsV@qQ)LFbOHax|3>04lM8j#i3my}(XXXFhh5C~ zf8uw7F~owoIYU(8kW##sa0_o#WEJl~vvKOJ%8a;+(b|A>(R z@Q-XNvDzX+4zknA8}g3DJ}@}*UQ$os;7{~H=1FB zN_7SnWHl(?o37@Ca&mJ)zPk(90=%H_GsE|k;L8_JHR+G1?b2cKHccHQ?j`BHY<&La zjl?X4cHlX@)rcVUIxp*r1o_Ls!pc2wEZ=X`d!PUt+1>TGWLe2|6Jk-FEk85$=h}Q+ zhv47{_&1)fORCYN@*mHaadvA`thGSB(<12@7>eseYsRfY3#v;hSh3otziWiLyx*`v zoKldj{{Cq6U(>XS6QPum4u4s-zh8Yk=g>c`D|}8})8STIg@wt(V$>%bipHNDg!_3%S^@%g$YdC%k9|ixdj7*Y% zECk>CVqG;l{$7vE=~l?**NSccBQP(s=ypvDQFZ5V4c~TtIZZG#0<3MjSk5~cy;Tsb z_RsP*L;Z}SoI)TTAWx4uU%#u3|ZN-iGT)ua|xh?$mDC&)L2T6U2ncx8U}GNWwcv?@5ot|KKx z7E;;7K#4I|fmonYNRgUKhU2g-PCPy!s_iqP90rcTw)nEn=lx}lx)0Z@^?cTbolIiT z(+J~^zJ{Ix6XmlcFp2A=zPFda3+;TO9Tv3EPWcDhGF;m*DaL;9{z?9uU;L0WSp+tu zZVZQQnB`ZD@S7TE)6ZIonNtie@ct<^15)0@x8340cQxYjazGDKvy=2}c@34iaBX<25zfK3n( zjZ7P#(e@xZcGlz7M?=^&B@}-+76xnbr^FQgr;brSqj$^5KOa~l|L27tERu)gkHwNT zA}C(k`Bv8RL&aGqPf?pEwY9zRCEc-=?Jt+X_LdP z47!m#^DCicX1^ZV_WAz__{KtKZ?%AolEEUUv%o@J%ZXfk{0ilJBJ7CSiilz* zI=M^jd#fxg&4JSC3B_viplv9F(WuJERkeuo^UL+R2L#L+cLaIjD#ywhIFl-l z7^vEFkcjOzNm?m{2mA#T$ttbL7ZSlRa6Kv6eQWAgvBgw5cW=V#1nYjD{BBq(%bg*x z!PC_U*`kH93yTAB9UxD4}P7f`p zru2N@)S&cMH!#yI9}j$~6O)4ko-y8|ziM9FR$7=l*Ak@iyvV~}x-MMu zFLzQpg5{>!8EvB#Y)BNM?CxhSZ7kJ=G0z!S>8@irp9&s32t~zo$cvYQO#iZxU zX?%g1D|bGAMu$(SdRwj$p}UmV?%8tuC)0`jL_2LwBX=5G`6Dp4GB%akjPY{?PNdc;@y>M&7hE=IJTARv!o zRFU}XXpq+&DE*LVIcmsKmh^^9&RI0=$o)vd`zy&#Wot~TE)IbA=;Dw6Y`bm3IdAzM zYD^kmnV|mufn$@O3+T9!m=k)z@HiGNp!F%E>u(#~M*BmWraQ(VfP!D~Lqp^Ua3-lM zv|k2So*$Jm)53oTzrhabU1{A8bWa zo3X^iAX+|;(Ol~D>FJ!wbJN8v{&#EmkG!PMAx6mRL2L1Xq1)00Dn}wt^19BW#B0a+ z9qqfRT=af3hn0W!V<3D@(CfLcC&+!};K;q8;7I#%$yT+_pVWT#Z(|D4R=1AR_GTth zd2&)f;vq0?)8Q~cAtx_28=fU|E7IU z%)vp$Po(0C3f8?k*$Nn9uB)=6hEw<>q;QIF*&%WkDEERj(9Bc}rIqrK1Ep1EpgqDJ zOdjH66Z=ljSoa=TrKIkPdi4Im#&9AYU%jCxDsy@-KHC~(Z{^!f=Se8dYKK^nu-%ls zEDEy}FhfY?)+-4cb)cx8$pqMqvWlSHcs}!Y!nYNab zLOezHX#MjD-uE45`5|VtNfGAPuq}$;)L=Io5LZ%e&Q)+-ZVpf(hLT&V&* z5|z+BRm`D(xz-r1JshPMcCsNyf!Wzw zS)Gx<{*pHsQOgn5jdvIy1}&fSHo0`wFC@8Ijqdaf~Xtb0BJzzYT z7ec`m-~XN#YD2;AWU4#*T5#aIs=J=Bqu=&0Zx0jXFLo0%P6{r8NWc}ZliU!cKRSOI znMJLuayem%7R)l5IIsH=Z+9i`Z>uS?SPQ-ot2^ex%y7CEry5HL#hHhbkX3(I^o+V2m^U2+IeH_IJgndd6l zD=ArK-3Pk3R5djiE=t$K`)TP?@UAEMF#I%uJdfTBYg#wz%4cl9;CrGYR$C+pSAVS8 zbTDS|waCZT|>mO!#sO#D3SC1Nt-MJ z&d$VIH2uP|PqvSlM|zfq=)JHR=gfBekn{I<+Tsx(ccY%S<}l{*ld5;b24-%$a#m^= zF|fvlccQ#vxS+K5g3Nv64|Z7u+-1EZbap8Yy&fa%Rx5TdEi?2>qk62pBT;s!*)HXW zF<;7|H=XK@;<_e`?Hu*A1&CK{1XQctOzaVO-|+j55;N-rs}^{Asd?i`#qBm2edBX}|l7QGRRDD1~MN$(yYS(@jf;a4x{iQd}kFRK#U zp>viL{n1y=Diuk?>)uZLA>{p#Kk&EA^|Ht0M$P}Z zn^w5NxwG7@ba2@p=oWsnc>6ZL$$_-LHRmbme|-^?ib8YOYgR?q*@cHa0| zr(0xy#Xea72x-$u-af3s#^V)w;OZIvogyRP?%IV2Ykxgo@+^6kWmm5eZd4>%m}~rq zj{2(BYcTwpF7+5<@q=Nm>?EGDQd3qwd|v#=FO zbEdY}OE$f;ZK)?F(-ucALLx3`(a={yR>a42NXm#IM7!Gx6-yg*Xl~)<4*9IqLqyX` z6qt?c^Glw@ymDEq(@_FUjMMRXbfw-7yvps!Vxh8H93bq4xLr_kfyOzc-dW;nS@?J<`!EXpC9ogDb7jYz9L{ z@~0%e-iLtY7?oxvu*im7`lB_Bb4417ZvOARNZ5VKuks)OM;tL-GKxXrW!R-|d4=hD zaN2gDM2TcwJlmCQ?-JNV{R+^g!|nq-d4F?=x=<`A4;2m6UIFhdj=C1sV? zTvbxSdQb(!5ao#6`Dbx)JqhPgt;&i~kN~u_y`3@(-^+>)~^qmp=52hSnrXfVUfU{Q1mTji<6$MM` zG{Pk-vmS}@a>fE*g2Xvom(YQdl}XaZF)_2(I_L)qA{}E{w*(8aUW*B>gj~$Vj*v~5cJ9rN;ezWHFSYfk&d?v&$AkFpo2V#Xs# zG7s%a?d``&`;Klqc-1?+u2jH-Y@Lckp>ctiJ!_cX5&r_wlq)EySG~4SEO|6!6oK)? zm_1pGwbMKEp8T+Xi|kXd zc}5`TzlGt?81F3SLOKZ&0;^gLF`$uAQylrQkrW($>5CI^F`r8@*uZ9Gb6*sVvAr!J zYUOhr3OSl@?!fGFn%m`k9|4;KaD2dRyFX7J%LVLYSnBCp=Ri?w_cqWIPeT5QEUN_O{f0HvVAE+L8vm zpe|;~3=J87`?W8?p;BGtYcbKZEd@@8L{YQUKM7>DZ*h~^{t${*W|f2#b^a0;=Qp2E zw=IvCQQjFxg~o|`LYx-UA2;8P_&pS#sE5zFONiSJoJF&`bC2JQCy9{6Fz3v5f{Q+L zh5Bs#)DSH^?dKHJqzMf+V8JN=nhlxLO-SY{_DjOn)-dtQ`+b#;HS9>E*XN02vcEt? zv71+Gnv;0YB5}$@V6EHqE6F*V7nFFkptn8JxXj)HyR4l(DOWr{oww{V)ls+oxCj|b zk5AIJkxMMm@#VNp%y(2-p8r-<+h-NXXtqPhVH`4Sn(d2k+wqbd*KP%)&zv?cbESW` zAFTKaXB5CBKnk5gr?=~IwUciwMrvc`qpB`556nDe;U=rv&SqOT+k+{llv9e(4o%oF zQ=#Mg9S#6s@4Xe3DvXXN7Hz!Mn;M$t9&LU50c@6&|IL5aut1^6gf?bj@{%5392r*8}q3)Xtq>zgg$JSb>^T%Z?ao23G;t@w*Gl ze|)PEC!=Vx$7INp4z4-Jp0uOm0%4d+6sUp|X82Q$&9XrWq+wB@?o$&tAM_LdwT>7y zN)`*u_*@L30J5)-F4}*2PBo*(wx1qG6f5?;1iOSqD@9j-tZpMql zO088H!wIQ`v1ZE~a?{-b#GngMGwv0ssua+sx4y}K4!wRFxI0IaRKzMS8Nn4p#!Q&Z z75!uJgpB|x8pn#6yl&bUAs^+2U{u&zZ##^`d%I7uZgP5dt;Q;L71&5D}mqV;EW2C6My)_Z))}|1vGyw;Cu`4 zih|Mf33MW@(a+d{YKr$wrsXTEc!GK<X#4eYp>YJIRP1t#0&Spp)CoJO#x47u6-r0r{fk8VQeh7GX zuj#6TK@_;bHJN_JI@WnkQguASD>VAQv0Z&vVM0b@tXE!6WSlM!(6diq`T`bSs3hi_ zFeDHwtPjUVY!gVIH%i+z0XzuT9YWuUaH+EZ3%~L}>gAJg*1)4<*%IOl16pYiJp9!F z?GJW+iG;85B#O1xjDC2(?w!9Q(gX9(S)o4 z?!pXCy0ai`o)+X)eFwHt??!A=?K>SAs04^i#7H%cKL~rYZZILGb$#k{DM8Gq>W>|= zqTpLJl8h)01HGLs^duy>pCVIzwrAFW*`v^lA*ABay7ktzd<9mv5z}^ZPlQVJmi$V_mjSD2lN|V>0AH1QT<>vOo8i#{IfXQ z2YbuYwus5OOJfJW<#w*T+VJSD9!muyUm}`$;I(YAyd+l1Qg#GLYTs$`XtD8<(w*vE zxJdn50aMqGpZ;yim76qT*v|Km;NFqE*7KXv<<8qV{Iuz^mOp*Lq_f;piL@fVmCXkU zA)*mIoGR zT_VAVcZ`MG;}3Zm(m5O>YVQ7P42J6o1I^%i*Jq)6WQNzBExgrCTNT!%T0P1j;?;12 zGJd2mNc$mqH&mgVh8F*qx`h_l@I^O8+1Z>M0u&AM|HlG2?sp~~GMZqBkc^*~*wY0p zh9#o~XbbK-bw02f2_W_bA8W{q0u1TY$9#-$u?J=Hmq;67IT1OnzT%$O%tMU2Yr&Ls zaha~~*J3nMEs!IcaN>%ZE4oHXVq+jybkzANhL>(1C~JS%DOKMbSIaQg^nybZI4$>AWK^D99+f0k!BA zNRqME8voRPBoGc#W9R?XrR|k_AMYtC*%I72F@u_g&j|BL3&Yi2TKq$5!!wmCc^3NI|>n zTS}5$EytvmBi&8_je-FWO^ZwevjwZWLwP0aFW3mRxqdx zZ;GxIr46rM+D-)Z4zZ<=GziViEt6 z+IKQ*S%yiTG29m6;=kCxGv!p@v{hQ9e4!H-RU{)us?Rfu;)s`h=It#WM~tm{q?S^F z@=UZw`;Hp)OGQmsn658iXl}}a>9OQ!5NT@>EF%7%s-P~qGU*a;h7RL$j%n!ja^0j5 zZ~PWycniL~pW7uY_HJK+Ni2tHI7{@3yzdw8N}fT<^G78u(ia6X024z1Q(v^QxwZ&~^5N ztydIJ$0E+}?owQrpWAwq>2xeuvs`)Y<>>$Y6}Vb4VzQjVodB?nU+8>%t-c}rC@nkx zuzj^M!s%qt@sI!)D=1Y*VslYKmAhafpCvs?$s>O$PAwvDQm=7U0*RXQjQc+3#;^LC zPVc|E-b|7=|GH}E3z0b*JT+=fcV2JZPq|KWjVCjdqqOLbeyTUSRG!91M#4qrVBAe; z)K?^j4=qKY+P+BP73%P~-x<45QH^o!j@25~hM0`$;eSz&hY90A(zpMsa>UNU&90P| zj^?mjva*OL`1U^74>C(c;rjDjx8zIDw0ZTf9UTaEx4~fe6__)I%P`uUT|DSAarpVf~R{uZ@{lBfS=Pv0R%4S6=uKL)+FLyj&OwYi6v)_CX@ zDTHF33U0?|^Y`XY9(G>2%%wVqQneW!E}q9E6lAPR-6xBBl-^rgW9;{b^E0qomYw~<=KpMlY z@v`>E@wTzuDj#-8F*wBbxyE>glBSq1VAF}3d1&C#Sepx>6I5Ai$L$^hXOHoZz1wS> z2Cgg6{9KbWMO{-sYEHb&YD zqP}+K%z7Opb?*0!Yg6JF_hfL#H&{4$;U(`13wg5kVdQv z0W+}Raa5`0NFph;qX@`KuHbEkK)Q*7j34Q^H$gZ0g^>YXCc}j=dL4IMKhNsU{5GK{41|_i7%CFhVdfxiu-F-j?aG|V z6+;EfD0Z9=U_B3;Nr<-MrP(F&BiLP8P1lR8Ua}f)i^8#&&&J*GJ``*)UxF%SCA-dX zX&mmC!_gGgP|Js4s6P&i%@GU$b(Z%hsTa*O%^bP;tsaDK}W?A6oYo7OlkElHnzZS z3J3+kErpOnS|@khT>w{BTtW#SAvKLP59kP0Trc5NkHm;xI+?Gb_*=ffS;wNcBxqL{ zC@hE57*e3d+OYXUd*y}Lf^xLPoC@BTMGuA%Y(hq?-uShI!pA*9BWR^JxRuY_^L~*HN^-h>nX$f;u4-8t_<36)HDw8W z5nkI4nC{hT`}B|HHb?HjA6Os*cM2QfKBiF>;Xv@@F;bgDM(-n98yAu*%`N*ASoQli)JF4ph7ueyHJKcv31o%bqjR zyVZPg0{P$?h_NEH*{7;&}>Pp$&Y4b_ZRysGX;&xv0a#0zmvC2sZ#RQN;QYKvpa`K7dFY(Ewa2O40 z&=edR*ZhmL16|c_aUE!rRaG23C**Cw4^tSS11Hcqso!_b820<>wTSBoXuC-qCOd-W zXk<;woMv(hR}TSIk+sH`^s-s4_*yW~isYtD-GrLWmVQ&2*gL^Y$N8S?!L0ps_qz*A zMdwH_=~vr8S|`ippYmg6ItvpncKhWMZnmqnh>pqD19y|2yZat^N8W~jrHs*gkAYnY z#F5@=TfBgFhI4=VsQ#PaQ;D=6uBo_wG5g(#JFS^rjhh)|R;<->xJf=q&@OZTxZV#0 zgb@Nt7EX$O*%%akZ-{RtSzeNLj%r4-dUcpF0*~Cumn$*xskEDZ%U+O;N(%ft-=BDQ z#i9piT}-01dqut{)%b?8(n?%-6fYCKV5_u)5ak^a_G}BA*ugYxp9M}tMN2Me{!w{; zNuSP5@Dhlwl$kn-L{&6aQ?(Gef$R16kWmAOSZS+mJUX)JKX5$050P^GG=vm1tS@*u z$^w-O|6sVr%&f9{x>5k2y009~ZztmbR+k`YtY+|4U4berAB5gxS>uA)cTNy%^QZJ6 z5ggLzdxur%BqWO%a)ZHOoz!+?^jDx|gr^NZ0bPAsb)X;T`az-d58(q7jm9IHC;x_R zR3agNaEP-E&~l)i z-Jn;?sF~hlrqV4X8@|YcRGt0eZ@@=%s!>9+PV4N2I?vIFo1MK*gFac8W(PLp({V17 z>tIy-B@F32sXMPzj>}^pc=fis5sJx_x|M9Qz}H0kLpCa-PxbyS$}l(t3Yw%M;eU_y znL-uI{p#?~7wGy-&;|K6tP><|>kTTAe5gX#V$UZ{;r(w9xFu-ZD*H>5D84Udx1B)L z`1>X8yyqsijgheN!05n?6kbE$_6KI%>raz*_aJV$A*o*)z3;_riJhh-#QnC54FGu! z#CF5qh}64#K&?R)Wbl3>I66Mrg59F25*j<9*|AveHAbZx;c#}U)Pe?HfBG-T3=h>S z;G&|{3_%H<&y2O}Gc*cX`rI!|A(`RX4+@>%pD0Ad(~_8%C0wAUk$xZHJHL7UqoTVqU5BrtF`c2E0r7TmHuMrc!9h4&_dCmSv+MpfdhCN`MB^nafg(X(0`)2Dl1z1FU}nl#mI!joPvAIW|M!=ns_qXi!ER8oF%8^m}`>2=ow-E zK@(n1B3xTwyFLC#Nhs({G0BlWOJS|+4~ET-9d$G!fYwE@s0Mm9mNmS0t_Y%Cvj>HH zF_R1S`8uEn2c@NG2L&oyUtd4duEtPjE)jFgvF#DE*6KLHV=!i*ihi$ZP^*8IH+EAT z7>2`hrd{j>DGeQbJw7S+_H@^eoyYSDzmbrqMHsLI0Hf_Oo4P8<0orE-b+dXu?`v925acQ;G8TszQ+U&qB}Elvi& zX9VoH|VHe;{vIwTX)tN&)Q9w}u)5bbPIknZRMegL}r)%Gx=)0!6s5!2|dm<9gco6K9EfW}Ib4~!qM6kB~J$%@$a zy-pcNjXqsY4$3fi`9u zVpAL?AsDjuC{J)52~Z6$Rg&Kz_OPb0nSr z%$&P~P1|$e=+2`4R7fm?PYD5)cqIzti67Sxe$CDi%8PTB@PAmp7RP5Rvi@e8IrutJ z#51&l4qkV_d@Xztrxn!*Pim38OcCU|ogLd%>x6m4@cBR3H2R^VQ-izy;l&yNNCx_N{0`h@OybIA{NlpNd1z0r31#T={$?&>LHc4D;z zN+!^O=P$1l$LmDumE94uV@oh}wf%vTbstP}2uN3CHV4&?IPUwL*CW|~11{l#pm1d( ze@~>>f^LW3oB>Pk?d9HhV?oQ;_<$yxb|M`m8azG4>4;4c}`&y$d ztWh24xR~+j>U`iA5u}Fvm{cs9yUx26k`l^eZMVQgQxlfl0bv>SrDU|i?VBn9aEV6K|OXm_!HFdNC=cqq<0+SxYa%#yX&BUdZ0S+K8T z3EAV;lZJF!6m43Onf~= zI&HX4Z~ko9sy9XS`5f0XB>S-7G2e6AnB>@tzV@jGWh9b2FwOE@G6N|Kb>m-hzp(B2 zzPUe4#iUa0Dh(OPe_f2Rp<_B(n5XBx~^tX4b1e#lc{&};q% z`8LJa;5AvV%a5&^8`L1Txm-Cok(`yBr9+ceZ@2V5Gz#AC_J^eT#3~$sfV~=h_5~Fc zB!Q=!PN%7SKR2MP$$J=K#MjjeRW!zC)2uAzUIpC%eRvp_q08)DV?8w4?k7v_Liykz z8LJ*YcE7psM?t_4v@i20P0z*CwKryj`OwD?4#bo8UA-@#pJ3DcIM(Y8rA4VLZskYj zmA3V?)`TykB8dI_hvI|)t7=^1A-0#Icb|GrPW^nBlA)sw{e*w23?4FSR+zvYBPg6v@sFz3+Rm|XB_2f` z(P1sMvn|`U-S50%k#-ZJ&LV4A@1Cb=2!PQ8UTjj6!fm*skSWO(CqL|nG!v)~!*=XC z^Y3IXDW2O>t`(SiPXjY7f!92Ogj*rJujBN&?hkXqFGEFC7m~;sQVpWW4n}*4=vZ61 zOd!UV7w0-~@oKS)X>|lId2KLC0S{ycxiIFzS~!3O$I%7Gt3`S+4(%mGjRp1MGM+aP zO6$j7qkI#d%56!l4I!UlnS|3j1|8v=HJM3Za5Qnt#sgoaJ2?hx%&#{RD=pj!c@n>k z3L%US1%kX}-gPO>V97VAwZVKV5~vfo^~- z5wW!s$(1@ff(-jLl#7uRv#Z&4jEHuz>;3Oyy1)m4(&M~2|B!e@IrF;e+ZlyEzLXxO zne)g8+z$PD$XZFGmLu1WvCz?l@#S#`kA|M4KS#lOD|PfuDUnEK8a|vqz*`B2rzzg) zxpFp9E6g$%-mbZmK|8na8>0AI!*}eqy zos0()v(Z7=c60qIeBir%eU5p##P{#9*I(4J%=TJUS}34x`08>m~XRQfdl-l z&Gl}>1G=~x$;yADMz%YaGBnuMWC#R|D3WF}rjd0Od2?Mrmq^^!xaOk`CNoJd=)DgV?;nmnn&GE|>Y)Ji_P~CtCxu!9~-;kU<_hV;=Ng#fpYrYlsUlKWo zYlwc0KptHm#FKn8k8An&d494?m!t~A4Cv9V7K&nvd6hZ6PUgQR{YTVqt&msxZQtP$ z4}FzMozhF5Iy$WKfvFt>4boR4$ZW-(jrN}g2~PMibh3cjf< z^N<`LdB&2BpOLG_A0?TYmB2}%AlzU#8jukpe53uoAoRPFipB-)CanNZ3hG7SZGL6z zsDCqh4X4fBVNL@~pvB(t49G=M$#_{lN)2X|b;a|`i>6ysMJ9_Y96z|UbbVmKne+a@ zdFJz4mH--FEfprmCSt3iHc$5)rx&Z-lf#CL%A_dwTok&fr#gFDu7{+ynPStMJ2Tl3 z`FSgy=RUiAV7f&RK4sn5EU`_d5?wCVVA@2L^O0Fr(SPk45VAD{4kz9X;blvcGghXUlDwa=|x3e?1 zvdKF=2}@u+svDn84R8(8X?NYf-TuNKWSzHye)_XD>s&U;SH6WlkDXm!R51Tg9I1_= zNYtwE#qUjcX%xiJ^^Bqx(}|+{%9W)02>0q2Jzt~K?oy)H&Hr?{u2xlNF+B=7T3aNW zZ<*m`qp6QqU-Ge4FA?+2;X(KnkfMtp(;pN9U-CNTd2Mu)BZSV5l_*wqD8)2+JQUbN z1@gQI5f%8W-UOQ_PSOj&{^hnRzWUiz^(6XnkK_9xByts(FR$W$NrF zB#Gpwge&Goi7lp{1b-U$l3np??BfYKgVCS&%E?ln77fsj z34YSq299k>5=)+vY|APTOr+#6yF*aONrsrw4PF&=TUwy3KPv);4qQehZboj4*g7fA z`YH6LgkoHx;xY!|TJTp!)nvGOhh9Cb&;peCR?`y$XaPkCU&3;Rsnu8`SAWsoVpfHLDdWf?Gz9MIWi1ZY(CWE78iJT|}N#{?{mHhV>O9HccEc z1&>&lhWFv;3RZkvb)O58dzu1H2{BK~utOpHOzv=mtoN#lx`K<;i`ze|!phNAHP ziJLQj(ec>|&mANj9CMG6w4;tch1^~r>lOdSwa|xj%YJa4v*mm|KrTpo3fSl`8UD0b z^z(C1#w*=It5@oi#l5h_l=HEap8Y9qGJ*PPc0BacI($mrfM04OLkRW9)Y&QHsbh>h+;ze0wG_!XB;H`EnVF!dG za8ZJNyRv~M${?CCe<%4#pRNAguSWnA{KC?adYbL+q}l2PMU%o}UoA>1OozdHh;ak>0I6g*|eVv(6GP^5swz2trE8)KM zwV$xkZUG_fMReu=?I&T~u3 zS^hr&zCc00hd*W>obzK{1WVIep!EX;tuHXznAVDS;<-ojsP(jX0wQZ?Ae_666L^WW zmTDE1#z0CY>@B;)(fcwuJDp1g=;$>xID0(oi$5@?03DQu*vC8qXNPl+S*g(6*I`@u zo|TTpIj{URY;F3Lr3-Cef$+P(l)OqZmTE&pLK~Vcj%GH4i*>Z0bv|rKG4saNrmFfu zX#G5HPI=gX1~T;*%^3jq4S%V!-BslfSvwv0>~GY1p^!>GhtK~$oc+dB74?MgMfPt( z@cBEq(E1Dj7|3h|&+X4az<*UCT2cVtOZUQFLZ5h2vpvorzkZRF2Jnpg8|)kp3eo*r z5P1D3#-yV7u;U3%<#*wa;OsX1c#{23#FeRV1@W^o;hjo>XS<9RXpz?p8xQY9fDzpG z5+lfz2+}*-2Trw9{COYde-eWhq`<_!?eaH36mE+~wuw5q%Qd zf-a;vE&tku)bf@YQ?C)85?ZH#H$&`tUh|!+aUIQJ$Aj^;sdTY1AZ=FxwhDzsMflBQk73*P z?MCim;*^!aG$vWBY$b^0qbaTo+$f75ucIDTkyhzXR}ej7)lrdGU3n!wqWNuKX^GK1 zpEhGUE*v!q8#iypr=Namz#upMVUM5&w3AwIae%{WZ{1Y-;K76Va>^7#LrFN!8E`h5 zmE&mznvWbY9PfSbfr%qu*PAzQK|2D*CmICKmJemV%gf)aa9)mR>Oz8QWn;^LK@}n& z_eB|9%cZ<++&IjiHxEnX_nZH|oH7+Xd-cZ6H(XyXmQGEDtYf7o$8{MWuNop8C#sXp z+>2#xNT5Gfd8*T?2xafjsmT0WohP-A>1IQ4qBi*$byb18y!O!dbXrmdGU%K*8j}~- z)Kn+UfTyKNlbAv*uBRF4E)4F|9=QkeY3-ztATwQrua1f-Fujnw% zP&?TM>f++C--*SW4`Ri}J!sOqB@S&`P8mDj z;}Csr=N3EZ^P5`UE{Swyxl;WVNc3}glZt_6VR8CGY?_(@^)d#kfH6&X` zXqUX3m@S(v70E0+%%6+DhK)QID24QX`pu3hRLFE?N9yHHPhnp4` z9P}-%DIiKZhaKz>n(1!Suo(Hn&c<8QH_-hkTf*X;jKq;=tli}Li}3Hym*F8Q{57th zV*18GD>>4v7ImWR%OLxT@H%aT^9HrTb*^eFFRhVL0{7bJY95=LtfLYf4#;JWT7Sk3_gYLFr^#%ZL`l!6y&x(-?~vIs#p#(7ub4koRev<) za_*);E~9=+6dc?MKNmqer3B^8@#tE9D3X)2O*{v8)s+te&hAc7eFIH&*y$(9kFOT3 zsx@#uclHu2m`f&@jDmJdUU%aq)YPs)TcUiyiSyG&f&HaXJUF|QroqXIaQ2WOM;g5C z>By#pjKk902yz)mBkJ+R0W_m|mN9L##GiZ+5IALL2=fV?tK0#N92nU`X7^4$?=vk2 z(Io!M^h#I^l(K&WqDu2xrgf(63gd2WKzMaTGBUaAQ9-8Pm$MH^UJRhw_xdzbbSlHv z!x2p05i*+`nsTgM)6Rt~*7r;a@jf_}V^n70=j^qFTHUklXr8K1mP9zyC=jNZ@clQB zg=5J=r2PE_)Vt}nvL=Rh?LYxdcq2U%*-!q5KniyfA0rff_zrASCnG%SG85BIjp@P< z-ZuWPYf7rTv!D784qiSC1rOW-_s7#96XMS1Es*wu`w>k^L+SnB#lc&yLW4Jt_~Nc9 z&&0Dcn*!02UG!;v4}~jV5d@+QHp6?Ke47nuO>N6fqwXhYJ&b}8&4VYAIr-1vytfga z@*G@6`>5?e1Ak&R7-TultW3<_&P|@qg53zN`T~*e7s7Sn{jg;;HSywqqJ;2pffj<-c>Pj(qf8eGi3G4wJ~% z_)v^!(1-Beb(Dii@OpHbn(oL~UX*l7geoXtK(&}4kl!6NIEgEBF2Om?`r_lw|HXS- zKgXWZT}I$6e~D2FpE((DCXk4O9YM_*i5276oZe)ak96=OI6%Rui-PM>H1T~?=PPhl zS_^zYf7P!pdK>EsHc*&K;U$5wFoo4o&H_;iNd=mODL4*FC{CfX`0Ef&O1e0INCSwG zsJ&yu7T-?%a@9XEaL?zst-}rY?YZ~Us#q}v`6+NxlgS9c6<;%p#tUCse$klkUp>bkRLC>Jc!-bIY+$t z8y{!pdHjz_#6Pjb)tRP_M?WP;wWr($qSmcjp?}{#)F_^1Os~o$w}9*UG?8A+cSv<@ z)UY8g7&`Rm>Zqyuon(-<_1d-TO!p;G+k4QUb10wE`)OsaaWQo0`Nq$)0K5D{1@yf= z#rUK?QIONa{x*SSglRH-_ny7gwxW5nW;lPy5Hm*PGd4Fj57VYkElXc*!dgDiaESlP zCy|--hko)C+;P`kI83w7qQ>}FUo|G)TeoTzqYv+z-4f+fp4_)*(B#?{IPjB~l+B1=!VMz2noJ5=(2yfpPpIU>zDhoV&3<=QRqH%t)KUqYVAmW8>?sj2+c2AUS%k3&U%t<13V zod^poriQke3OSgK(Q7gStQPqQ3dFcp+i2m4%p2um;5{WO-F6%4~mZOd2^4%N`C=Zp*_>3#h z)AB1W`m>1mv~J!@y)%Ng9fly{i5P=CkO<_U#<|uBTiDMwODkPflNP*Wx{SyF`sQLP z&Vx?+sV4l-emHiB$$(8%w!2awW^ejC$^uoA2q=Hp#(y+5zoq>gC8+HoaGpXMdFfkP z-w~=)RHoCZHSeOIa&0QS@ch2mPLu01mu;n{JvR%G27b2BL7}HMsIK365EG`Zq_v!E zw51oCTg?VYv`jgX#Sq{HgkTg>J|;-jfmZhUzQ!KyI&R|agv}wf&wQ% zfn%>CCy;3($*G}0qsH~|!yk{sQ~!9K{$=tA46ng6Prr%VZod*@s%r`}r!T^*FHhjE zE$%-fK;5^07hG}W@Yv*RrF6Ny$mui~&2oBS>$*Ao&8A5bnmXZPs}C$UeShS19E8># z&*6DR!MG;!KK;6yov+V7^EN-u{rK>`FSO3#Qu?^ADa@a{3@`uZL&I=tH`lc{j5cP% zYf6)N_VrNU{mntF&5t69Glv9oKk4Ph_d2_g7W13DjwZw(nB^mo7D7jY*hN*wdFX4_~f}>+|Z#m9}>q?LL%!p%yAesxLgT+aV|G{X9^3;V-t+e94_C|9)g?9e zJzcsZ^>@^u{K1XLz3+B3dg~KzPPXO>0qQ{l>G;#HO`RK!f1jG3GO3ja|BDoe6z@i0 z)xB`}7S}OoO>mGtZ8wd&54oL2oIGevASy8V@4(ELX=*NjVgiz4U*r={3KV97gBbuq<5jHe$(Fea~Z4{JD$rz79ZRF;6LTy#$K z$}dpkFVzWEb`ulq9`?a!$kwc6_lwTQ#3r*lIp8_u9Z z63sIQmwXI&ErDyb-l7GC@kV{2j@Kw|BnT{_n*!}_WRNV*P9b7=F#-23L#q|8NxRKO zA(as(nw0!Qdg_}emX*0HK1$y}<%w)~Tc+pTvY>PInzU%uKZ@g4;#vfaF^#K4sEPul zq4mG)p#+IdoQ-jNr<*XW$q-D~{wY4#IT5+RBBas8sGdugM*y|a^wAYN#7-Kshxi>q zts{O&oL#>ku4;E7hBh3GJ-&nZ(U*_n^PO`T&rZOae6jl2N#T65TY%-U$oB8Ul zgr$k_&7puc(`Tfo6QtutmC8W^ZcRAsHFGq5&ubmy!3e>5FBZ*;M-4lgnt$Nl`;eWT z9gk^_IXO9a>Yx9_RDz%lV&JU&vd$ui`||klW=xh9pt4OJtb-||LTxRZ+`afC zbIZyrJ-zk{5lVKlK}|5#;9MLJ0&x2YxSl=BkaFe9Rn)pXh#50yS&Z?Kg}cyY@0QE zw$Y%^%&6Hca&=V@7;lXyb(IY(SFSWZuccL9Idtya8GrtN|F7)!nzd^&b=p)j9IQWO zk52ByjNgh`bnMvCSP6LkKQ9_<0Q>jv!^sDp9j(f;dNFnW za-844D+YFMOn#93zm&JkVqb1pFk_QH$BLcvf??@cuHI#mhajodL9roih7_j_`fYZi z_;7&j3}D^f68e`;;k-iaUTul*fXrl`HGu zPJN${#+8H0S1@#`{4jBypg@8Gr;P$fb3&)B=tMy$LV@Nj8XLdSPyPKh`t&)7rC+VZ zlk_Wn+Z|W&SO8L zx47_X@vx(7-^&fCeRPT=cY5^d#9f%z;@PL)GXAOsUc0%=Zq!BR9*I=$Y{9(cc@0SAL_pFaOVtlkGUn&9AX);~~!cFby=v@yY`{7?zXlMRok5F^^GuVInE2Ld>V})l`T||98 z?7RJX*r>ss_0DH-HnKE%vY&Ys`B#m^em-mZ;g`n*o9lh&2IOs~=I5V&gSqG`eCCGi=wBhf}6r41h}{yBZQ_t56pOx<_9ITvA|#nprXyY zBHFqqqRBKzNT1i(xfYHcOW>wyVIKu8CH?@?4y-|F?Tkvm^)U*pgU6ctZK>IaWVfg& zP+|S$Cp&s*4?I5l-=q*rruW>3a}oOJ_wd~LN||}S` z8sTA zUPIKtX_M9tSLIxeF|973>2YrSrX+V${$_l!cL6uU5`q>pB|)#iWHSyVJmoEZgxEN? zBnc03vkP^v%KBD9Y01Xp!3aB<$ipASry@>!W~+0`q3nZr#3PhgAtJ5DfZjx=I6s_oP`@c5R+2PV8P;WF@lh-GA@B z=-2Ov8wlkY4>{Ca&AIi~_$6k~os0kb?V|=LwTiduU`O&V{?*q=A{G#%mGwvw=3PQ{g1 zTycB_#$tD9-yW^l_uFX-UPRfnWec`!-AaGe?T(pWh?`ig`W-WBts{um6&FY=sX(M# zZob)=3E#ANv)SzR!ABp_|MeB9pUrhK9!!6jT;g|5{74#yVkYxXJd+bi$wtK6QAbTV zky?C{(-_%<$i$aG|Foi&5(PeZ*pFQYifFBZ;4n3>j}@Za&?Nv<1TW#{ksSwoSh#Ex zw(dGe{*D4b@$=f0NF}Tk0@)4frC`I(Tzs`T7X`(o#{9I*IY(_=(}TsxPIaMK{WMF# zMF29_Z();W^QRbSU9&hlfnbWj2+_JwOpWUe+jHTghO@wCJKLl+V0T^#zFM;t-P>eS zc1D1epBw>xTlo27Qns0`CLf{t3*eMiu%-qTNG`;@N4nG$VyrMRUqAU2UrCVK!KK)^ zTPDJ{VKcRYBP>|fTx8Ze!nV4o9h{Mt%sl{1%>SB#WO!WfyR?&|E<09l+>J)mOqWk@ z7lCkXc2bBPgxMPzn|w?M$sB3E)i;@g50nS7dSf-FQHD0;iLbHNDYZ*=j#8uB$4{-@ zc|`<^X|`OzI`5xsD5eSbR%sq?5a#$`XGCP;T|cw&fNhAQ{VYi3Nz+_-fi@PD=eLM# z!%c1R-t8J;*TA+MKc&cDdjLLaWd}-qW>o4svxFwrC(l`p4$U)=+>>(ZEc&mOFqHkK zjS3ol>OV7=R%%>T_bNq6TqY=xpuj1lK-H3-Q&?ak>yxKI(`F5E-;Zv_zn**@yLRly z`n6l}#2;V8&9{s}$Fp)O6i`y)LqS0yf#bnw)HoZR&T7LQyjd0ERCm#|bq{RYxBw-G zb|5*u1$14T(5-t1eD4QW8}Qmkb3X6A{W(%nk}>$aJ{6KL;O@1TUU=Uu`~t?Wx&9(t zFd_k3S0K_i=t7HmA$Jvv*Avs!pc<3T-LoCguP(!igJA=fd&tB56C;Vwte1 z%TmtFyv#R1rKa7~!Hw59P>?WG)-bt_{kpG8Y!~XKW5x(|TG|Z=u_N>Yi*1uT^dw#=4$b9(MRc4d8d1g{TAO8FH%%@IrON2Z*++9xbxFiq+MSA1eF$%ugTR;17)@*O zHUh39SuNnDX>D7_!8AE2KXuGO4=Lb^Y+HuV^cUbESS?N2Kn|@CopXKl1)QQMUe+^o z)=+rHK5|qEHVS?s1-lUbY62Y8BG=p?FxR_pJ;E!$gnhtON2RHJBT8+L(2|Lg6Ph62 zPeQ&B|9A_wBTSRw&VDB*ps!rkiCzmdhx1!N=Ned zo@RGin=#|%M=5v=%zh27FaJri<7_8s96fg&TxWlWeIFx4EV2Y>@K&vzC{627^Dc0Y zdorGNjYlH@bR1i-TVp2wYY$_ZF?zct8t$B)gU9>+1T*%}!D|~Q;H!LsTpSeIq^(!C zBvgtFcRDU|crRw7&u)n+~FToOBAzCGMAowhCoLm6HhH z2nqL zIQ@TRcDieqE|@lB27Q*6VB*9{tOVb7zJ#66ItyoY>Qq)KwZ5!ptc$W|$9p1$kJsLK z!vdt^y-MPUr!hXHb+7zd5F??f0n2{x{SPqavdav(e!OL;Q?q~c#TVh<&pt=ci{{7k z^DzFk*YL>Cer7ZSYs&8ks1h3sHASxFGmVKO`BqGnnwpAx?z$5{{n^hc$D=P=*7py8 z`lGQRpt@T=c14^r0#(f5TXZFgmc!4!MKwQNRpsg>lvj)wxv?OBTj-P8M)Tbpx9>Bq z1*0CVfHeiaU)*r%;2Inyin?XnUhFwoi0%9HQ7lbj`Z^BDBsAB@Wb*YL8YI)ab|&7M zJP-MFPZ^=R4haS2($Yl$Gs_#qIh~r}lJonZL3S#<^lzAy;-#V#*LDKw$>g_;f-UD0 zJ3-FDJd>M16pnQJZ2Ew4N)6fV6&6Y#VyE{k@71zT!IfKO~sb&?YVIz zi!a9p3iEsnp zmi>n@VcHUY6VV-tE7@9aOztQChn}Tz<++`a)1n#MBrZr}NM-8CGt$-2&RnS-JkdB4 z$ZSP1^-vSsw8G`2JC_Rwbu*^g_ZBX|+O0+0G?9*wHmQ;>5vLEE_5d%uJ)7gwiF10i zMN^~r#|FFPf*`?%$4H$xBq;EWqd)?<{>F7l?J6V?`u+!R#&iFA2OHLJqku3MfBVbJ z7;)hsj2Lwe($cIC4{2Cman*?0Wm+kgJ`Or`AC9H7#+#%0_^uSEy2~CtJK2o^~YNkLv zYOJRaB+jFMWC?l$=3}GgG$-OKveG-+z(qTbXmOs-Sr!PD87`Xxew z0tpHnp+Lb?e}$hK&9-3|8_+tiWD$x!{Sd_;y#vQ40#J+^Y1$mN-8*q`>^fwyhOV<6}C}7M5Lq$o`r8wTOy`Wu!$}QY5`nF zHZKJBt~Z*@PSVBE>k@?5&qAaqpG8FBU-S{2eJ-PPiwxn{6r%e!AhdBF0cZ-~B(&@_ z1mOc)f!wX8-qEin!ZqX;T98PuSVKwWk~1sxt0JhM?Um>mbraG|t=)#-iiNN~bg`5O zr-lMH`93sR^RG0usGa1b@O*gV^U*)659aP&gz=kS$CA9wNU|qmY>NwUMXS;1)u223 zSstq?UKcPeO)TX@Fe*%;XP5$7g%Bm74sMb$((Smp2m#g{I`WsOIVGz5F%a7%s`3w4 zYNiP>Upn1_mzqVJ{5$aI%0FSk)=9Xz{Z$y*Y>=so#sA*nIuwDT0 z2OoZf7R_7WySLs_T3>RRq;_#PDI5 zG-)#D#3(h+SDJBV`ipaGwF#@pt2Vc)GMQeRbSD$X45jKxa57n+A!%tSobQITc7-pK z%j8<@!IlH1)R^AR=_CD7 z2YEjkZeKBm_w0b}1yM{}Nfa{d;{;#u^U=mcWxy@*J2gJqhr%!IO( z5VU(Npqn2zVQw;YI$iLjW}txJcrsm;4D8kt4YR%2pD!(J@!RA*SZD2@ktVy7v^r35 z7?bDG!Vs_Hyo0sTMnq?PmZkePT8fc5bZJmKcuywPWg=bfO9r0R976{7Wt}WQTokp= z=n7r}6K1YL2|;&x(>8N$O?_I6XZ7rd(Vzp$kfZ*G*Z z2iQu`TE2CE*u#xcG#j20^K*SF>K11k#GZ)3Dj!P8DgSh?4~zGQjd}9F4)T_NoH(z7 z0{cp$2I!ZJUM#Tp6lpqtyfuWM&-2lgqaB}Jnu7XOXz8D#qN`|i;#PtJ-yRASzVkW? z|L;LW>SZFSaZ~KSnjrF$h2|5@-r+2G@BJx~FT50LO5H$iNh+k?D&Zl!tLrfB%Dm&<%om#2vQR5#+Y1Ka?YckgX*PMAt_ zYcz#6e>O_~I}xQbzC?0KF+aokvD~c}JojFWq`^b^8v@_5CGdUr2_lmxAoy2mZS#j- z{b1|T1Ah9_4zVpVOP+ewb>)jpoS(@Qkl=wlo{=up)K12ckVm05f&pbKX@(9ae;sQI zVjQXo;Vp|1*}9m58iBfu#P`H;>Ca%7|7KHA6$vS*wkM~pmG;%O4cuY&>vk|=x?QYcLwJI#D*Oh+jsPNRS7QO$>>$El7f{*>+tZ>Kj8BnXXC~;SD;J%PQ1^;jwR%;uBgQQ zI;Vh_J~eyv=z)6m>Y=cxh~rBWB7d4cb!58gISzHg@dZ&DV_YCF`2Oei%7?xJB=Z(c>8%b+@)Rg5-B6 zfA$$QoeNA|WN!Dqd+tI1zI|)VLZ-YQ`uWdI9|?&1&&w}!%ycJ+#X00SL%X(ZaqF$O z;NQU(;xmYKEDmtuRzaDzhj3E=6$X7 z_=GM+NmCZ{PK2r`Kv0nzD9xJ6?k5z?4@AP%T*u^pS$wpULPI;Xplb|Vh@;7AC=~w7 zIjE4}btvScKjLJX2B#VDV5xDt;qsNDSDOYHOd0bFQfGaf4;q$qi;_ zg>cEm=c7UW05`Z4Beh-@>*YjR1~+r+TZ2Hm_J~EuKiF6|$)Q5bH$o7%K~^$Gj~Ix2 znqt{KeK|FBsijL_%6_V6hq%f1{moBtd}&v8J97%*)L4G z*=S{^sPGU%+L$W3=%3QDeG&eQNpkW?MT{3<9USaWLrW{?6aHIB3pd398+_DwH?&b% z%8hl6ke)6owWzK-*Za+bMwp4CcB;?R1=`3Mq8W6D%YkH9nuZ{*8a4oJnl{4U{__!5 zZ=nr!X9+d!)9AX2KsGIDEn1%kA6;4PKd=|qjXe)3nGFq(R^8m9YE|`2_Y)yOfdmCk zJq1o2(LD8aOO#Lx3M8i_;k)#2oztc{KK^hDii>^7rCF5;A5O(56goF*T;G7#+1Z(7 z;!@%9a%UE2^U6DY)6>(?tw)EWN|cs$^PJvC-H+c`v|uHcE?L7}Y(AP{ahrPd>eCtZ z>s#6T)Yb|gkpNS+Aa4hme*+=&}HIPu~d znu+D?v60%e_ssC2YnC0C=D2WKYbSk`h^08aAONs0GV*> zZfBZxZ;^UxDR*jfFWJ&O*UM_n42gFinXSzwsiREpw1`=Z}mgv!_>I z*eFbmhm;W)LBHrPA4hQGdX!H23^r;;mrVNtq0c@>fV;6B_0nN4$V1B0ufWwRC!X}E zqqAjeq(1&v9J=!+?7j4SglVSC?JGejBin%BuD%12F>DlE?K<#7*7_(`CP`-xGQW(6 zeg!`@!;7UEKItQbS$7E>*%;*i^lmtt(FF6r!AF&wxN#;@fKmk$;>YUScwB45v9qhb zph{0d%ReLB6{bE0H*isa73n+zPMQa=7PKy(o<80TXk8{1D*y1CbMBzFaDZkJZ4`o= zK#dC{6|%%bRaP{ze+we(=TeYF@Kz|(Kk4g@pff@+I>gA*SdyZ&U8YSb@TE+Y$!K1b`6cn9#4ar# z3WBw4@f%ovHw8x8DDaipaE<+Fm}yyt(fYPgqfGaBLqb&Lt@~C4J!2YPhY5oD7}vov zq~#sqx%9|%{@>hLSdhB}_jVnJt6N`g^7~eG#u;2$X?kT6aF*}km8(`+E#;hVz-e9a z+>F5(4KzM1fxiYq?4~)`@WcD=MaPaE%ipYUu5mZ{v(NFTzx)NcbhV=EcqT^#=DMfb z6*4!s%(yLoTj$Ed+~Xf}xm5WQ;?CRkG(@DIlQWA;J5#_0X|71 z`EE9P#RNCQB9^*es3JjMyB5?Mf}({=3CxmL4O0u&<>7jqM9`Rdg}7nT#kD%sO__9t z3pZTaA4LRLznHUt=AY{``xFBP3xLj`C5FzeveB_+D>TeVp?_+E*#vb10-jmEfDfC^ zMWrrk8nc1|diBn^l9`x*vMfq=Zrcof&T55K8@3Vf+>fv;gYDAV@4(`K=MuHmM1hS*lJhzcC(S{h6X`|M(0$FV4 ztqr^uFqcFCZA4CVS5^L;1*7O zZ^r}k)#?EY+o`4rFZaygHCJ4KSZDK#I+H(k{bJruS zVFQ|#ZwN2%Yt7fMawt#7%Fhzl2?``AaOx?L0Ip9xjZb+Ann;IncUYIM?eN*8Sy;Sq zC4FxM%-XewzBl&LZ-yp#P4rp_Oo$zHN4vn%GgI)`?|(>T6E3#L5$4WWiq$lm5~PV0 z)nn0uRrvJd>9}mn1-M}N*(Rs4SC7Kl)tj-G=FDf!T#WOE_QQP-++;MbhYqvi97mCf zs9z%m#9aNhzZXdkKVDoLG|ZFyPOr#|VCg|Wo?R8dm^Lol*~Mkdiq}eff5uF+u zsNa;ce&zd3Cd1<;p26flQ-@WeU8j%&erhfsxOps+30V91lJHdly7WZ$Bagw=wFjJy zn^ar6KHl6NI>C{V319sNkZJGW>}l}&OA(^kwn$b3B=x9%BU(ITZ}(ovr)h5+cOeTN z*GICab2lVkbQ!!uh96~uJf2QR0qO##1#G8WFcP_s|B{;G3(OqtWt-HthVOm`Pmf;Z zlO@g*6gd495b(NVd0Zw*Aqica5#T&7)7N4?zTqKqcpsvEpW*%`7>Xp(#5w`FnnGw9 zO>9#up_K1_hyq9_&4g=yv9}w5NM=j8^0w2|BEKM(O@?#m_h3t_UsHCc&@fevR5!Rren7(@|Qg{Lf*1bUv^t!T`Pp?GdoOSrHumHs5Hnp?)AB0d}q?kcZ4=7 zZ5&SmvhD5&{QT~91U-n#k`O^6f!vO`%$UO9Fo9_cOJ|-E{!(ZyP&-~>V+zw09fp^h z;D-XmSaN7J#zrrp06Za{F{Jqh{1Lz)t?Tyf+GEw~RhHnKv#A+5@xc?XC%-AngxhhU zJ_n-iUHhG@ap@)1TYm8S{dn&A=kdaeFA+b{W(PA^tekbKvUpEgb!EP@<8iFPZk28B zF@f&I!6P&K$MZ88Sdj2vVwJwhq5Mj|QUc1WqpLg!L_~g@1d7ClV zw(i}#LT0fm2a}hHqiEITRykG0>lw3VVdJJvCcXN7;DEEKh;i(-P1^WZTz(mzeEMmV zHjlokWjR zk2P1yCaQNTeekw!-MTE!@tzp^mC;aOQO)!`f5;G=bN1PoGk2~TX{J1;Iaa!3)yt$e z4OE{-aER?UFM@fuG>#QCF03<+d8N_2R98Wop4Pj>qs|kdA_YiuI~AfrG?gs_pSFmf z+QVGasHrTCU}@|6{gg>_EY}n?pQh2jvow{X%qTz*ncq&goAJe@EVHnxYA z&7$x4el*!VqF-B@(DoubGa2>LQ>o$2^rUB$8|dQN+hIR}T>|h*8(~8nVN?ElC&C0uJVm%$_4(Pc@By~g-D{CDL?tGF#TO0 zCOuc}^x>tq7NK6)gG)!!l?=yp9JtoG+M#12Bq)%ez}HKG#Fx?6OaD48y>XKU7l@w+%xZ(Mdq`KGx7bquSF~RA4!C79|fe9 z^c#ZL!>E1z-0C1^?+w96|Iz}0c^C7|Fl~333~9u~>p^Yd+03JeBM$4J35367HpmZRMX&XP7GQ zZ2H^oQQv{3^s$}Gdzl_9zGCb1W+DB6A7(w8(KmQ~B0!&P3z_z|qv`DPn>mbzmz6W) z($x8q1AG@*uIk#IZBzay8dPO{V?9JO>Z-rK0rXS71r`_c{Nk1ljO$df{WX=R{ATP9 zVNJeF2La8gsXdD3;|uv6u>7E?Ue|-i;Q$B}%+ZF_99z48q2}-ZI2z%WGh1<4=vv+@Zd-o&A))(%aw#E#2XwNQ` zPNk{v_uoQr=^~_3OS+WtQ!ct3u6_ei`0PL6*|!@9AG!~X-k4x~hE+w4D76}L8OM`% z0KSGzsO9|t0t@D#_{+&~&6$Q=njm-e=!2B8*C2W5NI07|N0htcgWI>lzj!_Z%ac8`{!ZpwOmKJGeRyG%0MU*>Lu_ z5cX-$qJ-KyD0C`W- zM`S`Z=5=Xq&`f8P_Z;CQ3WFomj;480MktZ+HsHd5R^3sW8YZ8+#t4#~)D%+&(zZ4} zusJsE0uC8s2XlV(A8UX}812+nH7m){(GqvQHVU|C@_FdcA^3FCrv`*M0tlC{5=V~t zSQKDqb)&|TK!v`2`r!V1?=|2_`4sW<^)$2m$G<#*1&bCLA-TD4GKmY#<1u5yBUX;? zs*EE;CRPWf<&8CBr;$kyzy!XNT7&&qT8Z~TKU6LB_YqP}9AASe|?x#+hM(}4q zaaLD3pBedNqsUITSCURgzvnaV6>agjaxLzLcfk#NKYg9$_*e6 z0_NJ#!PS{TGD(%dy0DTOTh@J^@R=-E*{tssnH^A*?Exfp4I>1U?Cc$ z(-ShsSeMrIk(TU6yEKC5#U+%3)66%)WETNwnXFg225e?=aS&HyNbX2r4*PoY8=7PK1H-7(}v~I^fduy`Kp%u zi`z?C2Nc2~3&!R;X>DG9P1 zlo1;ZYg0OD7~7oX5@^k~+qCJ>^s`k(59h{g3i3wK8f@lc%`tz$LLA7YUvkn@rrphK zPa3+}=^GE^8utw9BLkAh=9u!JwJ9TZzfK5{$Rt651O;kM0lnRca0XFe{kpAKL(tmA zT~pWHFq(^<*MQbOuDJSX*PFm=F{IiN_v$MXkb8i_J5JJ7F=Sd?7tUXSF5S4Oq$VFl zDNW`|n?}>$_dam5F#)axPQlJaAwI$P?%M^8Xv%#5zC5g2xd8?Fhsk_U>mw1qwG#@(R;iv>oi zta=2*KViJ?1icUXB6wuJ9}@|(9wyKpXE)P`yk4H;#A5@z2DCJlIDtP!+!&xdRjzx7Ua+JhpgY0Ls7Eg#XWFPix zXoqOCn41Lz>av=+rd@~{=q}SAYq+sOxoaG0Bg8Z3dn=4flW`sY;f7^AzSxIXNXtVU z3zU7EI&>oNeranLUgiD0r4jss{r2OY6?ZSI{@N_?&ZZLlcLz=1>Z6GD6a5F1rhlRD z_ZF$*{1f4uOaYnF)-UOjD@-*b)ZFl|TaEDARY;yW4Ux4gQTRGdhrjj$IPGvU$$+Ct zGnxorhYSjdBH0b#C7|qK`j+PrByP$LVjK&FFFa-ZW_hMeM!~=Sj?DXhTqPOw%+5H{ zSCG%^jC&c6>wIW8f$PUjdr%sP2HbwToo zi{Uv4`Af|D=-?Q)r#FNxIpcKJ;H1jZ z>u;liU-=kXJrnla zO@>!UreH1HbvR8fq?JW;_8bl0>{sctu-MEkkwqUM+WS(@7e_>ZWsy(ONody^*tRY- zb@XzL3wJyZj_g)trMWtvkCMz5)C}KY_!8Ul&k-5=J=jw$H%4Uoh&ucYO^^6$z^b+1ZAjCRbFXUOe}c({#8}LWp8!<`7D8m6ew9% zPux(N_SPZrE^rRuwte^|qU&x3UX&SoEd!`NkNsto2;n5WAfZXfVIaFvkZgH(3!w+=Y!pbh(RkNw&2 zGNWmTOs%MvIc`N`z&m>N`FS(4OH4!#fNr$}0#&%Ro0hq&boP@lzeHk`K-DwBB=^~@ z6|!xS9JhM{40K%{)PA@oheObs+1Z6k+P`Fy(c| zyJ$5O%f+om<1Ou`mE`mj+k+sLVv;$g$X*L@pIWZ&GP6dBVY#dpVa?V!G~x2^y$t@T z^>R?%UOJCz|Jf68u7t-x$F^#QEGicgi8e~I<9*z8tZb8v3qfsiG|D+2bujYtrki`| zDQE8`p8y<3Mjd_kIUxy!5xtP_^{;Dd;GwBClP6!9Z%ca~qMq(JSzFGTdAwi?N37Rq zgSpEFIr6z1l3;9b=1tq8>d(xAmskS)*bUjDdcp7A=c9Ok!@7$|@1yVvGc$i`AAsWR zqOiSuo$EmbqKPf}qk3@E)6sF$Ta5_;$jA#wBEzw`!OibGSu?lNW_V|=&&oV3xMtvr zRg{pB$F!WMxn(r>j-C#NY4jg~9cvQ97m{jBM}JT0%T+45;p9t`#VCq!Fh z)s#(C#f&i2%ZPY-PEo4C8vNU!R>hUsik4=}V!X;?J z#xz7vz{117MKC1-zrFDYbQOG_=EO3U+%`yo4PKAS6om9v1fh=GOXatuoDQA01Niu) z9DApM#^_O_l~i9NuWVmFA_hyGu(jH!>9GXr4*2?7m_~iv9T9*%)ad0Q5spJzHmQR? zUPYs9#LuSuQw5zE|2iqK({Us0F%3W@%nHHxN){(9cKNb2oUm5hbRNMyFQPDot?hV5 zOc9&pQ|x3yalL0ndFvKsoAjUq>Urh)B1RyJC@Crywy>p3=IAP);B!0pNz36|A*3r*p;r}?@Acwm!ci~Kwd=q` z4Nc|X#~jQ)#wcoE&l_@rMF=Yy-4$2B$HLwvEZrnWiY5q3ykZZkDSy-1MSKA<^z0w0 zK|RwOp&YuQFh(4#^&cX9*SjP?#FbnD5ATkICMHdX+}0^7-xd&tfl83p@^}2tkX(jv zRgH}mMs0`smcmy*PWjv$pkSe$4@BrBbJcw;}#L1N6%PXQxDa@*oM50R->8)Tdp zKSWnmOsZyY|MZZJ)?;Hpeea~+i^;bKAD&kgxZ1@~qgTd%k(#|4@MAN{(a`HrfEuQ@ zd%le~NYUbl)?`*f@iAiDi0>lXP3}-7f63qozuE!4k$chKkO>%`>QuDtdXqWV>q(-? zYDZ@|yqK6m7SKPx(DOD_-lX7W9Uzdu=*7NzSIpw>pcV$(U5| z%lYU4hmteV%TRVtH90x?7A{pdz5p7J}hh-iwcBP!)HgH4X{ppxIh zM91r-;>iqv;Ck*a%1th=$NR&KMwzZq0O^)fUL&X_?l%m&Pc0pU*N4d>npR&XN|P6<%B>07^jccx zQw<)GmaJ3@lN7AE0O~ZZpcF)=5M}HehMC03mW{K7zutlvLo?5xgMT`ekwUFth01I6 zyOn$VDC|kh4?nSVu2r0uss#{|8ZtVN znAupbs^oG*79xi&2bQt=oNWwlbSZ34*;L2uGz`h|g2jl%h062^eh>0FiAk>rz9BJ% z^RB}GN^VgL#v2=C%(RRFwb%8NqJxiUEa?z3QOXQ`T<8T2G!DA?P!^mKS#HWD0}Zo+ z?XIjU8&TzK0Zt+`_3d|r8`;jtU5-pGGLNhFe?Z_JnJN3^&>*gQ7mv6%h*i!5cgDMbm8RUR6-K$C$liEKluWf}GY=%<9&TsYxDI4sx8cX;O`Ait$GDwkNOW z?d$&r@h_5J|s)zo|Vm6lB1A&+KSsl1Djj5RniRaB&)~py{KOYfdIEow>lY1O%YYDbFNPxvkqLh~M|^#Qq!J9!&UH?8>t0o^rWAuK zd|J{HL0%mGmKwX=L}t6Je*~q$E@UY=f#Mua`NeF^*wA)f)?z@x01vPPe#D8G36?_!ZYlTGD`VrS#o4P1);? zN9J4TOF7Q9r`g}SFV+7NM z3Fc=*l=G85GNsmd;JJlH#I&;sx4;DHEC0O~wWlahJ-T{aF?;6=H)G~4c*>Q{t| z9{*ZQ`Dlcsps6C_jY{7lt)adiDIM?<7li-mL@kJL^O4BU_yVg0OiT5BG~wGS@keZH zxu}{e*H$SES|pEOa!;jwAR?wL!X;-1KpA?Me{$QJ${qX#K*Zc;Uv&m^m0F=7WP=xi|gmhFhK#V#Zpcx-!x>2jmZWQNze zEHj8{&SbLlzROB-_o13+D*5JFq6;xQ=9CF?r%Hqd>gIVl#ah5SIgR8Aw7%C*Ot+<7 z2L^hph(oNjWFdH|3|qT8#|Tm*gQUqUA$Ju|+0y|ISpRxLV9yZq`*RI5aAUdPSr3c< zY5;TL^b}8^W54tZ#yY~wgrCROM_Of|({D5m!g@1avT5qqiw2bC4%dH~m&U zcCEgo#LsQ0)ZPacw4sr1V4V1_8=NhktUD{(3^L(T8Wc97#gs zrE39fkg@>kdky>&vOa8#p7KF<3`J6Pt%{YIJln31e}a#3g~+pTH+tm(-ot zyZ-!p`wW-I@M5 zngcve3ju0qp}VNYmUUxpF0av2YR9kjZ&kR>t7`U-fC;h}F84Gz*2Slm2Pyo-F*Y&= zss3$fJDVTr<;8Jk2(JFX%S2?NktxCLMYhAP6T{^5u+uZkAXwat0o7-{DlU*_NfJ{w z@TpuNo*6DwVCE+96x_+75s1^1V~ZljKOKT-`HCixCFrstrdKhS2*qW($3XX|k5JAJ z*jmzN|3lu^0Guxwtl%j~?}Wb3Mm#@#ITT#wHJl=7QZUBOn&A!)lVNtd5nJ+L9*SeQ zs+B=r>?Gs3RPGBpT!TS5HT;YNrtrmK{Y4Gi9A`Wuw%vZP$=HrQ&kqmV@?RHt<9S)= zjNtE9m2x$T0Td9kPOyVc7Q$HEJCx^?PsVT?>s_EMV-1s4iOvgAiCd6BurI>HzMm02 zu52)G(o%rZ!~Enkd#iPL0$#;APM68YbzFmCR4~g|YTK z;$Q@vpz%L}?4UXOq;> zk06B0K4&m%vd|iFrslxRk70TCu@P7O*5sH(BYY3lsaRl9Nm^g(aEe`tB@9f$u$0XG zz);9;rR<6Z4ZYzvRzDx{flVeZuY;ZEai_bgSU<4v)3C8Nyo%PGF8Do&>D~mSG_G^T=jn8$PBH5 zBfwL9$QzS>B5FHwurybE*1&Ss>-*xku2KJbLQ%I*-urV(x!A-ywut$wl4AA6d;rTD zW0xKr!)0f>B1CM5N;K>)1cbf1!}XGk)nbO3V$@5@OK<8eaHKUklgt5jeyTuxv+y!T z(TUS;i%#L!N3`um2qxbjLu{&$EHS3ie#gOntuQZyH$Jxc8$9qC7Xi8|Cz^BI)70hE z^O4`?mIw%-PF2C;f~2{~RIFg0sZ{OQ+6`oUB7`dCmA%1{e)>{<@?c3ouE~K2q4R6* zqE|^g>T2`_iV2djae|DoRKP+N_Pgh2y#jwj@7yW@${fF?FYe)s=VTot*b_IJ;e2VD z?T}&Sj6dR9@4+89zKJ;G1%mE{eHUaxY6B39{ zcf$%%P8Lf&zh#$;7)TwJ9P|()OV{?LvP&D?#Gg=Tx|9p!F^;{Fb{Q=N)74ka|GaVl zL}U&!_1CK#{4p}P;062ScopFbK-ldq z1`e3m0qcR`eh49UjqyL}wE)R%HIssS$wr)#tMo*H+_UXi+SL?RcA4zmK7qLvLGRxr z!$&|CFCz$|1e+@l2glP^Jha#({oXWVF7^|okY>tn{^=0{Y40BOyI%w-SQyQMKuKho z3L0h~kc_V*E5eq{pI;!813bYZeT%tvXweUd4vQo1ErV!baN|qi;k>8V@G=_gt8vV5 z2|$c$)IoosIAY1Xv}(Jchi}ZoQ*e}IbCFL~PCc^#^q15#M_o zc1bhUUKH!*S*04{RwGB40roKm3cVY9#Sl8p+3@8BzDF7De;gh5L$i-^kdii6X27Bg zgHM(E81ityM0>kw^l^7rmWm-r+K%I;U)f&F<8h4v&6z|LwvQ8nJ`Zrov`x=$PLvgg zg&ts!3%njLTLqSdDl#b^q#Q>us_8I&5Gv=rH>d+Ba>tCm$;ii>&&%d72aQI&!@RQO<6 zWp8yW$opNsbmOH*m5ihr1{4G72#M{?O$!clurUm{_T;j16>r^!V3-;in3Cen)emD)0AVo5f0Uh4<4q;k$YB>)wTPL;?Bal&>rFL#WhG z;l-E&0@WbZAPaN{rpxjXkpnOBA6J}Y#)>c^%``06X31kBtXXU9Y{%ldZb^;yZWtIg zkmd8Z-wbdk03OtuT%r-XkReB2oNa75{7wmxnL7xyfy5*s`{jdgSbj&U1dZTXyl>C( zhbSd#K0{I>{E{s1U2>YBNUr=4QQXDZgPz<(_Oaxmi zq+d2^Xpf4Q^d`>-7?0HHUhYg`^w}#U**?GeAjBQZ>P|Kl!=G4>gs895On6Hp1dm;? zXk5n%)y)q*_`q>#)b|>u8As3E*yV>?njN;C?frwif+FL`6 zbcnoBOUvSHVe*=OGEO<88VFGDpDyBLjS_u@E!eZFu~veO0}`yhJ)*Gpl#`EQu+gy# z;l@10C1rNmCEMrCns7*~ro7@&vh!FtgU7V3pcsdHt<3Bpv+zkJ9Fm5uq12+fVa3!J z{v2Aq8w6zcrxkqKJ0xK8YbrW=MTkZwZC#(;h<7}K`F?X?ZY@b~GQm>Js_1*kurOSZW4CPQ zUc%Z4|5o*Ru-k1@hFXHTEBAl4UJxQhiU`C960&vK1*_XT^(pkYdO~a4X~NH}MxlUF zGI#RJn(w?29x7o^R(UV?ip*3~xcx4P#+Oayn)eAeJ0@|f#8{<&QGeeQ4~L}4o*~mz z`DaY5Dg?8&X=CJC`@AzVfim+MBi$D`;}Rg-sR^_}ON)s5>YQqqnY^Y9whE7VE8!gE zIrdxRe9i0268ONcD|tebF}~?0`GtEd4D@Q5Gc^wyTnc;l(!D?<$AUeJ_7BzU(2x%_ ze}b;v9I|nfpe|of(zld-bFWg2T2GB&0f3Ct?qL@?s#Pjn>YNh|R*FQ>_S8ZjZBZOd|U7#aFnV|>Tn%X*<8cQ-P63&H2-WEk4rwQb)RxVU5 z-o?zY1!tRe@zX81GQq!E@Z-mn&vLQySANEW1CGbDV9$iM&%o{UMIy8@epwlwO=ET_>XLIL#Mn|_3&_sW<%qgDiM(TIP$7iYB$FV-F6b&W}a(3 zurNY7Sb>l~F=_DMz?!gV;@e7NyfKYIhr?J{G!uVt)*T?vq-FH-PjG^Vvd^bFm9$Nb zM5Pc?jKR>q7tu8Qi zSShXEcz?7&R8h>>%f7lbIS*Md>$z8vLdgLKxMb1<5>8Bk8mM z_zi*{DEC-YvJZ)#3PMA#iL<3b?;41T`w5oAyQ`miD|wq1Ne2)t%}`s)TRHwtOB6Fs zF^AP3V&`jNF`eyy$OBI`af5u|UQP=g_wj$twf`itutv%HrTHzkKM;s z*$j;RS3`zO{Uat;C|(4*sF^hc&}B6&p&*((Rw0@Ki98VoxuHys3Kf|6Yegc5!064( zD;i?l>4^_3{^Ah}9uT7J@jSx!?u_n8WUEDlE>OP8i7}eUjE>jUJbF%y@ zj`-8xacT+2Z6BV+V~cOK+mH8^^ZOLrLbS_u?6%t&dE;U*fEjIf``sQi%#15rTH%&C z3SaC{z-&;`fN{on7pGyPa~tBNq8|O@+gH=xU$vFp$bbyBU6ixL7%uOZmPqn%dq~bG zvYIc3jJVJKpX3Y{%+&KE^cZX)o1+iP;0E%I?$4p{v;yu7Eh%ZTSbhz}Zd3{+?wi!2 z&Ac(h1nV)XSD=OQE~Va05{6guVg1O63w}Vcm!0&%!)_P&zC7*v^f67&*fW(fPJM-J zcz%y*(@G2@x425`+6p4xU3`MCE~Z#NjWNzXa`#MWd#YGc3=a_$5Aoo>3AGn8FJ^%@ zH*#UPD+87~3X#B8m#Pny&5n0Ei?XGQK>WL{ovu5Bt@vhX@RH?%o5Xr$>vXwQX)2XC zta%UCh3*1ZGjU;$deR>Xxcjt(!&^A^l$1f>zGLAvh^NIi43jhI$3zGg! z>)PyN{=f?W1eWkjH4eKSO#4)aUY#W}!Qd1v0-*?AV_2T1P$zxfxn#|Dy|Uqedqps@ zy$9`>K;8Qy`t()?cw2Omsa|h{XV_^CGRCon5zIsCseijx2ymIAFSY(YcHPmzW0nBd zRjequuUjoeFUr$MDGDO492_T{Qj6t(ADxJarABZl zCttiFd{O=(t|@O_o-a1;0G&7&5sHx%E% zej+U3%CY|%DEH!wk_s`r7@9um-O9Bu{Z25ojfYAf67i+=R;P^hc2RD0qlkT_lAI?p zB-)$dBwFcX_}lfwu@e{bgLhP?Nw<;Agowt3jhe6xZ@dD#JyVU!Oa8PXnCrE;oNL++V9tg@N`+?Ee zmnU3f%N-e%oip8s^!E)>O={c1W7xZ!9rC`s7()9CwT1h33y=pNsKW=E3({QoVI2R~ zPWN(dBkMF1fKd}YsMHay41EqBs-VcHyk3f(XXb|f(~fl{>^M>TYnYBPJ2ZLF*xc|( z+bvQX2K+Soh(A=YN?KE5N7#G7X8L~Ls4KZ;EBP84-$akkrcL03((t=@x4YZgmFXZx zHRZVtz__wIMY3i>hjl5Fhr!RFJ%mR(+kJaE6=v#kI!MUZxiGAR)u^kbj~c9h38~}h zQ4-pJ2xAcFM>_u{9GFZTiaOO!g`MiZV&4W();-VX?UBT%r_*?BdWG>lOtNZD#-)fr zFeH|16%_08I-4q9W3!r%8|Zrq#o}@2ZzcSud`>(Ti9+4`OQ3Jg*YZpyJP^$~kK2Mf z-QeuoYQ{8!D-jKy$%%T4#1O)8PSF#w7tEU~cV!8|28I)ag2dFeK5H6jCGQF`l|{ojxHJX`=6e1W~ST1#j24NZ~;Jn`HGfH*bq69Ay4j)wvvbm52zYU z!^kh3j$A8Idokp$YRR>fy~|jCe}<3*zdBfA9Zz}wg%JR6n#M+psCk6z6mD9OK5Hj`B!UhJ@2nZtVb<2#_f?u^;@NtgH=Y z+gl>_-iP8^&8Ag&Q(QqQ*84-HE-f6+^QE+8{KT&^E}P!$!D5%IeuB;}H_mbTp*Q#2 z2SczuFOC+%cP|dl&dSU*0vc*J1tNz+LT10F6|T@KV!&$^KYuY+s}C%rlj-?Cc6 zn5elc%S752xP0PD)QLD5ZBF79IFD&v#yQzI{c#gdhs_bv>emY>QY1KHK-;i*3Xz}D zo5z{rgTC~0YJ3g1S$;{_|E6|IN6UdndD=ePWIC=I<}>R~CH*T8h=FSOxb467uhIHptx|Ipr8McZs;Md9=ILQFCtdR&nfOA{09=lp+EQ& zkBVR_um_U$?dWb6jT5Q|$ZUrn*?RQxLbxG=`|TY$KJLp6LU<)orx{c{E42{NHC2ES z!lckBX!kEk6V7?Q(Y6gTiZR_VZ?r#HKM|%McbOgm7Ri3#ivHOcsR{{SqYMe!T5$x8 zV%f5r*J6on4Ckr)K=wWT%5+a4Id5<8hi?9w6xce7&HV#K+Bec2Z?`MS?GT=Ci|M8y zrVM6?M^(&#$kt=>&8IC9maap=+u^f;XNuJQT2P258Um1z1)!B(19mT%5t=X^n^~ zf1OkW1+*w$4Zbjs$biMTJ*}5u&0F~~srdeg42vDk$NIro&*TR3KU}lT&7?JmgX|`^ zOt7d8M#Tqyjy)^hx|EOJc1)Vi5dz1Fp3U?aGo1>#c3pB-es|be$I+M;VR;F1w2O7= znpOz&J6V6CY;G_CQ?c0f$MUZNPI+6F=Jj2)6cONSXQIHLFpt#(L)yGuE3b(Pts!J% z!Aor)&!A%x28RZmDtIH05k=OUrC!XkXsaFid_=izAbF*^m)IF9cvBrBSSH9R7K?1p zqynXlRY__093MklwfK=t6p#5yVji@BHIQe2vgxi0q1s^k?Ix6Ev!)ds%sL`UZ6UL0 z(m&mkCVQ*(y`$KxFA|BzBOgXGcF;L1@FdeG1I`kHAEw-C6D3F#PRyl^H25}4x=f_D zLlA5+8aT%VQlW210_^X;d2V){sJ-DOJV)L_TVNcTi6^>UO>8p$n&zMNirp)pRh9hh zQno}ji-nxsw)~xMXsWnXB={PjNUf%`L|R?uG12%>Wpj-K=zp&Mq1Kz;ph*2op+cpU z1AM#nf!o8~f|&&Hzk_jt<;ZjrgMC0A%SG$Cv0^mJi?J9a<^pbM741cr3}fTHu|_`g z%wuqFcdi1UDm*N%e(`GNZ2kSNJAGOpg+HzkBYTU3uByTOjWojJT@|c_#Mo!m{>1?9 zMFKNCYQh?zq*IJ|WE4Pyk5y^I>BNh+Y=^cn8&0H#_G0<^lDp-_cwc?{PZsq zjjk>eMJJv)LL75lj5~b65F{-BW)>9*QvGOKxnloXCCxbwF^gx{Bn0xhB?he8FXAVa zMh@{MRHaJ(h5uB-kin1@DqK`6s*5*}b5F~2iYI{~A)ZbHQ0Dgb1Z;&3Ldt|GCK3uX za#pyF1d>!PC`{DM@_+Px2{QbfFB*jEp?)7f#6`23T9lVd=ZK#fkpq11bEdsbNAw!b zAmiB1z>?48QVZYUm_E5059~Fx#1#lIihlQ!;+Ng*VL((0=ygEl&zZ5o$9alnREy2R>M z*Ef<>USeRG9j5n^-EIy$ecC)1=DvAWbn~Z|I3%>)9bSuVAEDsN-FtMGe%cSan^maN zQ%IMk)Y2+Q)~zwD*-NOul3=`SiDVk2@pSpIOB(f3R~hey&9YlkUmXL zCw2AS;v;Rcnarq_*m8bk5`t1FvccI9ni=B{lEW_>-4N%z`!Y%B5vE6wcP`W)ByE-KU2Wo_C^{!L5AjoRmLep2&_zA^>*}79_y#1QR34b$seU!mp=4dP z!&+h1q`pKk{1e8QKw+ZJFGh5#qOpD`i<1G?LPL4`SzR0O)E^G1nqXG=qZU%tTz4!s-d8ix~9}pBdzDu+{@ap>PA)U zZ6gd{S1iKnI~I_)8Uv}!Eiu$!9m(~&fx(U$VS?LKub~j>y%l}1!m^x3*1YXUzeK~H zmm1ma`0(_H(be#PqHfzC`iCW6L=uw`WW!7|LP~_$wzb>(pdjl;gRgNL%5o&pc8~nV z)utUs*@_(<1^1m#EXMK9Ta;XhLNYkm33yxQ9}kqCI(0Tfu!T%QPC|}Z={+(=!~(vO z#AK|!vLN$AFg($fJh3Vi6N4$GegDI~4N(aITtxs3-GwV~ro}%_tb^55W}3lu#J5_M z8n2E=l@H>whK-U^;`4D%=60-P-}G$BPTKJ9QfXOjlb3ioWuFlb zBi&Jh6Sg62!!}ywv<*I!)t*L8Va3m+ct0)Px013=8o>8-&I;Iizkf>+4D~MfE{pwF zz6tuxNdK!R>H*Pj@iEcGqbKA=YFNE?=ssO(=1I7RtCXwWd6JsW_`?bwIH`tGB(u!H z;^>I@u%~)*SOy)$Dj3LU&@NxaQk|VnXBBfc7IHIBzCS93G#|=tIB86kvT2>t+4-Kk ziumnPqmBS!Z+)ps(Jk;>{My(O!lJj4S&G%#6~NU|U#C|cbY(*z0ec^{38&q3WjC5) zMzkt#B%A_^ic7I}Ky!c}bjNH8BLXJX(f4ox01-QtR2h-|HaI8y)GomSrV?ufcYH&O z7-%d(tWq1+y{v4CIw=qjZ>{$CQ1W{z+Q$q0H3;)tWLi@B ze~w6*73Tjn{q5e_Q$drqwdCj)BJs<`9R71OX7Fzp0BfUl*fYz<;j0ZRJF0`8yfAu+ zmzOXTVQ(E&A~c`+q|qqIXZf4|R~W_cz&sP8Srsm^4LqIfPM1WQ%EXQhA86}S@FSnp zl_MD}8R_2~{U*U!0;Z_Xmt0LRmnz&I_|*QPerWw3;_JU#girKM&25H-)c%O(J6&To z;xG2#3~cP5%+*7nbWFL;*z7fBL4MTZv%AZofj-CX?5p+_>w+5{?xSOITS=2M0?8r4 zZ-Y8r#e<)pPGvl|6iyrtc{rD=?ON{vKEg#6&I&uV0ifL<2zkHkEU?Gg|C#Rl20?8?12D+#61iOC(5ydis)0 zsv>00T*8vHARncj6=wL?a8H!OdjpEXlntEq?WqO?p$-2#326-EzDBH(l}@0aGaV1U zLK2TL{%b0i_7f{lb3BHQAU?^it#Ws->sq^~foC^k4`lOfJ{;v6g zaw+8B(dk}G?+}8@+2!GO{|57iq)8oGW6Q;_Y7L6c0<+>Vd$VTVwK2g=Ob{JgNQ$kG z4vh$pV;Wrqfp^4UU55qi;+6R*YU20AF1LWOte_!2tr0TBkMhc_AI4wybrj183Z58o zt)9y%7IX|WWtx;~u1)CW07IIbyVkHNC2hwz-aaIELkjq8_)8+KYs`vC?>$*28%K#> zybB#1{%?!6V=To(a?Vy4QKtRXW%bX`JanRy10C{xU4(Q7SE^hPd$l0f2~dQI17Qdg zd*m`Vd{0sc7-;sJ&A+a7M;iVhdb+*5m=T!dJmFsk)%NZSvbc5c5YeU4K)tL6VBK+; zlLu9`m!u6vb4I$g)hfPv^Z;Qy+@h!y2(ACaX#aPF`A@*1p#Z}paM@Y^nc;@6B1*5O zg_L?bpqqAmA)9*f2T@aY?*{Cy=#MrP0u)=#-aZ*Ow^G`p{aR;2VF@RIhAUgZqs{l@ zP_^y0yxZBSrZ1%paV)ir*9=;!iqQ7GlFyNW@yE(sp zD9@g=DJL;8kS~}lo!cczD?sfhL~O8V!7w|_gyyv|8OwxlJ&QnujWxzHgq8IM@!|6Q zhZeu3gbp(|u0GTuQJ*F(OCp+OJ2xpNuq3`soO&)rRsZCrfGWk5eC%EoX{4_$Y*rNA z@C!3OKQp%)fh2{ZV}kYCN?7IJf}ld+)tw~$=WXSiZ}OB+^b=0RSs@D@f9~W$BY*S| zI>$zVZglQI4Qf!qi~zY;4IkwvfghQ}i*dxv7>370Gr;iUf?Z_~=is)Epa z--6g(Z8M_}8Z+Hq-(sPYRJ`@hzPH+=fS|j9Z0@g8I2~O?RD;rg>l2HYTM}ed8d>(C@G;O5s?L_E3P{Be^^?|2_ycN z`NyAU6cN8#f52)874B$1OMIXncm^bmyxn0MXI!&w$$-T$)EE9nU3i~v9G2Q3^jE}L ze#sG_m}cJ&y5?Zh=pr@1fO=O=;o0Fii1pFbJ);ytNIh&`AN+MqoEp*7rHI@%zlFR! z*%Jk2qGEiG{SH;xw- z{~h>qg-Nf+D04 z$H1!{i^r035pz}|8l&HvK`D;!;v)Er!GPP;F(jz9&#g=QuIyb((-zN-kY9LhrI{7@ zx4s`mzGun^T>`H#uu>i-%Qu|MKnVffg- z)5%M}$g&O=sDXNMuV5fvmVk^dCSv?^hd-^8A55%{lo`N9CW?zS#e~OyiA1E z8vgpxUKpV{Tw|0y`^fH?+o1Dd&fwC8c4C{L?;B^hDZbiKO;z2^o?Io2!4&?`(z|e- z;{0kXOBD1Hj@2ISeJ+XciNmr|*ja28jC#k)k8R5CbU9T&f_rNBbmr+Xw7jMOr1j=l zCMyI$+D0UObL7guWJPmPHw=krt}s5TtzQ+_kG+r$Oypp8F07jWT2Qxpw}Y#wKT`X^ z_lD5HZ(|Py^=-5%Zx$>oBk%M0b)vS+Z;ICQotQuDF&*?e355_C*+}gcaYU!iLJaI` zk`fU|MVqN2v7arm?Pj-P+rXX>s^pO;Ss?UfdKKg+SVBE2{{MBTXJGIq~dv2D3BWi*jJ=hED(Un~hT{PCSSS76Mh&m+@1ZL*y? z-HcnHDN+x_vx)Q+@mO(TGq(P>$&3kj&-2ZK7~f^8W1JwcyB(>y6Ib>skx&X|5aR{y zhBD0xPPDhsrRB#644bflb=le;k9U8;a-w&rslhKff0@zP3|%f15)O1Xzq&&;_> ze%k(!Mj+%XXKUm2u%ho`yu{F8=RcUPaRdsMZkEKfuVUE{J^2hA6V}=Xg}9 zP#}rZXDBob?L~me-Z}ggd3k!HA9iK1g2Lls^bVKhSIWNVe=(9+4|K~WqSVR9-a&4( zOYuGMuWoTVMgUo@QgE%{$G=CT3l zM#Ri88Mpkn7ijTK!@n_*;`BEpimrlW%EfX7QlMv#AAoURQt50~bUD$uly6Ujfo?pS ztvmBP&!n7V<}SEgL2@S!fl(RJNGs`KzSB(C=0=%bcw7qg>C~<7MuI)v&V-XT9P%*L^<#aXLWz_w_p8jpZq!V(yJO?`%acs~V_;O%VOuVx^&*_gp;j>pQ z*Q_Ie(#=s-#csIXp-1*I2t>G!{kNxylH^iQPDpD*{d8)+AbMOM4nMBaAcOkg4mO$i z;k0oVKe)4T8j?Q=%#qjPi4)HygD6cZ_SNP!A)|-kuL6H^l6txK`aT=qgS3KxX|jK+ z0T@I(ldW@-R>^&kE9TezJin8o$ibZaHDwJA@qGj6spBo#4ydVsUL=RNFTO#pLn+wl zjI#RX!PsCGo{Aim+=gONkojwt{!4|Gbbcv*#!KThR~MsBNbi$q0pTS_vuFzyIxfCPINx_xPwj`oD#8Jzl8|9WR$r zmLb09@s-b+eb$idJcWZ}6y&LD+UAltchAIxxH~2GXE(Am#W-1ga9L`P3|q~$Yq~y2 z#R!Ut^b(y?p+|7UjgX#k+9d>su@<_7H3O8{wP1APY0}>fJx^KjXFvf4hc!_g_R^9_CI#A#ZAsVXQ6;S=>#BunJ(aon3}Yd#iMldl z)gQixM@M6O=oNzzPzR6~YsR=#kb4Q^(=#)`!wC%;b@$|jnr*>!b~zEgi3oyJpR28| zdZT^u)ABy;@sBC z-5QW4sjfXUWvL#qKE9Krz&CLUSpiDSL|r3#2F_4@-Erd?E^C?yzFVP8QPS1%j8?CA z`Ad!IEc4~Q)xY5rsqk-ny5Eci?yoe~p(c$K4*Pt$e6)+6tHv{pDpV-yZ)t0JzwJw2~37V(iq?hNT zP2`b-3@URyn#oDrSnItMnI}L-oq@r24>v2vrKFZYp;y^5%%Ge`2d|n&)TJ5Oi~Sa{*@m*QAd6 zi4kJGp{u~U^sVh?IGQaMt3?UUS}St4RcBvj(|HQ+7T(D}a%P~eHeHBoY)R5q=N(Gd zy;6(yt3{%-mnXAG?Z(n&mlu zV(VL^cL}l|4EGocH2F?GX>d}QMKMwYl8%3}-iPp-5ISN488qLAuY41B-fQK!5NShb zYNnK8i#we;GxgE{U;@)#;M`G{&!K#QT3z^bp@Mj2B_qH78IZ@#ap*X~&S39$#r7@U zE!(qY|Ex@rx?1PO9@x`<8OF!R@JqD%hlJyYk;p5FNOxwtbV&y5Eq*1)s68;TU%Mq! z8{I?~pSg)fVtLR9z%O_$kM|h$Zn>N7$cIYmUWHxlkNlWPs06p^VLzH9KHGj*EYb9R zG_O40%b6@uGpg<}7yPR0;jD>NZ#qxEVRW@i!)Vn3rc!NOVZK_a60WcRIlIQi!=}Wh z0=Cp;on79zXe9Spu`ZzYRj7py#j=QhN%eQp%x{YFpXYpg89eB$d0Q;qS1}*^kfIuL zVp-IDwF}&|UIfXz6xb_ynAU@3FY_-Ln2ZQPO#mzV`dnF<_{0gyve1G}r_%}s8HlR+ zXB!~#k1NdM^Wp<+WOv8j zRLI`wg>Fw)>C)UU3zi-3j0@t_Nzc`FIWnYCj+3@C3Ny}u{CG(&!O;UZLxX+(l|JAVJrOphtaK3l9tM=q7@16eOu zYq5q6G&mrhIBDnlAE(LfAS2T&aIPkH`by!SP~j~cC` zSRU~c)3u76Wsfxn6mTyc(1;dp@ti~5e>w5l*Py#^5A9lo#&692PVuzA!GV!&n4vUr z@3y+Ae?l_kjnZgx9FV3zJMk_!fAyt}!?vo&%cc?2$jyMP(uyu3>5m+O6x$LG+t#9A zX|WwKjW6&VAJiZ0|CMoJD3wYVITmX0jd*pQX=^6V5}8`l z;50%1`TmTogZo~;*=mX36amQp@9{ zObi#+;*~um%YB*>xwZy)NGkuSA zqsevpJJ-Et!gjZyG>G_L(t!~GE8G2GmOndtXqdm9z3rkbgQ-75;{VMm@a2f4mp7PY ziOI~XICQ3G@_+IDek&#=RcgM}pte4%%2{JYr+|XOuPVp1KoASRI#1v(#wTZ2V=<%{ zot#W05`h8qMQ&$A2`#9pFqNW1yO`0=Kq6(gzz5$v$mV z>&J*;m|}w$%O8R6nSa8Fwm1ok{z7dnO+&m6zCcnE+e`CN05ucBSAPsN$fRdTG5Et` zT?%J)oF`AyP_&S^!A&B|eX(LU%TN%>X4a?ttbft=zuO7gNAC>5fNwxrjceuFP$g0_ zvfNM(Do0z89FC8&a(>H!iU7>+&TTv8=HZgB>GOdsaU*4GC{7wluW}D2W17PIB->u1 zmTDsDTf@hl96W~2N}&`Tt}Rac_x}P^zs2{~1RdG!(6nOvd~ajZIcyt(PV?#WH{wMc z&qOr`f7L}iv2UPF&xHy*=NnzoQ8wM|S!iie;0nkK zGwZ!ge|*y>a8&+&x5o~GsS?&A{XN5>;|054(#O0Ww9y%Yv@nt91jAWOv)-m*!nB%= z>Y%ynWHqKUk+7T&p8|#`Jsvek%vg(K4yp{hFl?3LCCW1UpEifMUS{``|McClyf66( zI5;pvq+^D%j5Q2N1=f|M3gk1tWC;C+*l%C8?7FFMS-6_?oZ8rf_)d*G4OnDCluRrkNL#S_`!nKBYgAQ zm#2mYDDKM@ggWP76T1cR8IC_a>P%ujcw^scQe-pud+S^oYo<@f2@ILfqD#b+{>Tme zUu_o=7!!E?G)k3&!a`!%m2h|~*oD>2&D-ChkhwE^?<@$8XJ9_M37OTM(j}acTSI6zZqR zvBU^CboHE?roE|4H=@Nram&CV0gvEGkwFP*QUfz-aZTjMql1Wy;Ab zLgZf<+&40-YQstdCY@Zkw(MGJXL32`J96VmTQe?P+kH;-dAPc~q$f;4Oan^YpH&xG z7-QL`ftH$CNp)6n+pS6HStgXSS7mS;4a&(b_Om8Ae6<{@Wz;lPsdh1@8;eC*faj zC*47ecC$G4hDJWPBCY78Rb8i%I!Xw!lreezsUA<;T@ipN2B$UBf`Sziu_ZUrasl}==V*8 zqRLh&)@+iC1%LUP#{I4jMf)5UXlK-{dr!L)joOYPP_z zcQZ&8DNW$$1zWWl9fF3Rtw9I(Jx z!pzAq=pn$|ojWt%d1phT{OO0!m`z}xuJ@8Er~Z>z(zJAWq1#z2M}sZJ5_t@51f3&8 z7YDk6k`e+m4!84nP4^2S1KVFcdD+?ddZzjeIPBJZsji2V_Z!19Bh{uOBPM9jrj-%v7#o!?DlJ1Kv1V9;$iF4v%fk_IV_ zS}j~$Ps&eNjKL(UR?OoeWvwR1^EFJ;=8InJO7n$E?ZtkQV>>8BJnAgruxPL6EqqYK zGMR58X31tU`StPU2o&_+1XGhaOEjQUE2j@?FI?ALT~A4i`$EF|m=HM`4#yMXyPtap z8{+BS?Po>!lP;C(_&%RldC8$ZaU)}3kpJFNjnr4({rC_6mWm#XAp4@24IuO}_&te+ zbYD-e80f?GXBHhQodf#?l*}(M54+I+&@1s`BU5aoxqB9;Rt9!$un4e4d zP0X{-=an&@1?Ie|tF^ls!Sp1439ijvT-f+io-u|kN)8Cr<>Q}hJQiIWN|Ki7cs$Ih zo%J`uC+(a#tpO+MmPu;N=4hwiWKCi9FFNz?I)4Jhr7TnHH&G2veI6LiUb(9WBZP`s z;{^eQ5(Q`TH6pAQt3)ZB=%Fz3m>HRW8)Ej4`}rrfe&(b5g@!b3rLqGWxmet~E*$+o=%a5fncS3P!B@4+P%J z&hgkij$L+jyD-`-<73k2yA@m=`0ZhM>$xOHA|HHAo1A8r^7FquYWY%&{ywiobokXl zg*Uf>+SaSd=fmf`5HgmMh~38>d7Qj7`XHma{;d?a$z%JWVu^>qy)2ARnh@5di+nUs zT&h2{L`>E3`(9L%AzvG!rRU^Yp(900LF4;|(*RnM)mQQQ4yvk&h0ZgD3Z01L8)D~D zDV!{!^_oqNCFZNmDk;*Ek_w4>!}XwrBQCoMM*3r^Jcr2z9=*1VjEwqor5cx0(trU_ z;$=J0tQ3Ydx|O%id?6Fvmf>=`K(50siVHNJ4Ne@pU`5zPt;RH`QYMC`dU?G1SJ3vw z-I^C&-I{09vddmdlmd*{y;bXjuv98{!D1H#wb$-&g6-7^8^$*);bQd$*^RBPZtkOH zD4ObtED>=B1IQWC9!6G+#ZTr;Qxz7wVDGA^{|SsB3im12=X*JRXy1H@2XwyL0fg6R z)$V9aiCSHO=SA?BQu@k5#z>&rHLPNY!Y$stQ0vmn-4cizUkuL&OntCDlZxf|w=qN;`Aci7r6Nv)o*a8;Ttv)rEt zlrinjTAb@7q06YoOH&B4G3ZpNrsqdThoeoZ4EY?T4$?;m)=O|gb$84?rCK`+3|#G~ z&uuY1E-;7iX`}x=H2;nDJ3oJrl@7r!e3UH2^0L4P+ditICX#o13hT9KV&*R!t^8BJjKS;`rEnq1>~GtW@$l zRY$FrMpJXuDh;S{Ar^}7oz~6)Tw8FN4II{m_iC z+c(oZcn#&bl5xbNko<0Znlap@6Vib3BWN@7)Hrtts@+BR*sME$x|f zhleU(n$_n0pJTLlxv)P{0NEHv8A_#bo8*Ih+EiW-Ea&iU22bTYO=Qrr@fZ6XFR>g2 zmN3s_fDcg3n~;zg{Qf`d74x_~Nu{Yp-61uf(rj9=$W(I#9g;46d9f|jA-tBE2SzVd zb3X4(bh9m;WxhB)k1d=<(dHL<@ibjFy`V9_1nsa$;!M8VVJOFmp7F4jMhWSnS4;Cf zVnsF_D^MAI37;v^akc^+#3ZNHyt1XJPs6q7Q$k@u(Db zDw2um^0_w*TY*zfs@A0^7>&_-oi8?2b-#O>drWk&qvstcbY=;e{6~Hx{|B@+T%NG0 zH|{>8p1O!`eLIWa2fr%Jdv~LCrZjL{%OqAyij_wD=!5d{ia{2ar&$ubEuyhHK^8zEa#^y{Lse6e+AE3(3x9yy^;&#)?0o=NVnGiOtJMs!gfz}x6)EXq8UZ`bB2O|`M; zC5id5%M)_R{M4%l4Ja+bl7fbb_p8k=z-cRG@zaL0$0^$Q&&u?^-O}6>rLP8!$R&hO&U!PLaKxqJEiK zH%rtZ6CDiL-5cbMOj76|#2-O>P(ll7p8cZO=nXOI4SxuDNzP+#Bu5PE`xHgdd68?v zacL^8L};O=V2rF4&lb~5(CCGj`QmS~aJjvyE@Zw6*O2wo9I3o5VC}T5wuux*pG$1t ztGKqEnq}D6H~%eNtJuAHfQEb})7=9=&nD8^N}`#n`iq>zf*c`8&PCD4d8dxO?lfOZ zHa*mXQEQtep4zLT>5C|Dga|HOSwGMf2Hn7s2leZ*+R^hRWc7TRC7dIM&ZB0;INU9A zWS@ff9(jHe)|gwBJL65T^8p{%o)L$8;{wf@uD8I7^WXd0)_VcZ zhaC)|dE3fnueaCBdA$f76ke4C*t}gv7_;bF``C+7t}}}0*$w(rr>f>jOBJZ0Tro?O ziwr_rfwVp`F|o_Ew`UQH1;a$-G-$0fb3}toM@JGHNuR_MBhTpL?w>9osG*wXJcGbb zj`D^bR94ha=dh2dCriypTsKrjnFEoKe{+ubNcn$k(-S_X?mxJ{89;9>tMExg-1O|^>_6qvvMP%acgK9y%%U#xm z7NfLc)e6}#ngE0bd6Tq&X}nsu>wB04?gx|-3W65j?@lIX$$2Y*ymrR_GT$-0>UDr*{HOFDHJE+W*nn;GgBKYCbBMP}4r#K358 zkPZrA=t7UYOL{$!?%@K)1_eB9A5;k6f3cNEg2-Dj%r~{fRePoo5P*mq%TmNPgdW+& z!J&NxwsEdQ?D=KWPVn;5o^`RS@jxWh`^I9e7U~rcQMjdrfD^@1#F{aj5??*4Xfx3& z`b{o;<_p_EYfEo;YZz-3vU_~HcEfc0=*U1<1;znuVRavv#cIpZkK;8b3e}1D>mP;( zxhb|_7Hoq|WMr9Svr%a1@1^QOO;3NN`S%WJHTF_4o=^O2xP%Z@W22>H*K$GkBO6$& z0M|3vEq8W#?ffhx-v+xGv%wdqg$KbfT%ATdB2AzL@JZX>x)+NaCc_+T3U4iVU;rl% z#bIZyNf^1GG%w)SU>g0n$s&~~==(VF)8(>9F%bbVrIPU*7Od|5Rf(k#PR!?22%q_$x^#LA|8b;-zfmovrsavak?zJ6X-ky8=v!78$dbTpsF-XxmRfUtRv(ypn_rql{uPKr>z;b+9^^O>y7rbmLEIO^#b|efz zGC+70JCQ>l{S;;uFcNKX2{dpy^jz2T4_@RjoD2Yt60+(F%evWf(@;E{aOn@jM-pSLF-VyadW;Yx&pD%c#KI zXG8j$ff2phsNG3S<*_ifRSL-@R)r_-R7Qi~1ZHz_pER;w71kYXkL5gcE-@=eQ?(ds zqx5C!2$One0>GN{Xi?0g`2WpxfaaBa=EWo|_s#g!8?*n$r&*@nAkg|~*zEo?;&gd4 z_w4>fOW?}+LbfY9C?R&~D|KVzQTWpK;fB&TBJ}8!)hZ>=cy1wHtS8LNP@nH^*L* zQT+qP8am9jxsW=}$o+iNdpt|T2}D``+7Sm1%vz{@SIDQP3VQvwWZG{)1es2WjYk}< zWN*~g<>3&1e`JOSU;AlP>=0XfTAYaNhWJq-GI)+o*WzW5QH()XqlM{Y4T3}*JT@<1 zVx1^#%n!etIjz_1eod)f)%H#M=JZ&&HbW%ltj?E*=;M_86SxPL4ys>4K*eVkY3Uf` zIfTw^R~7|Yv30h!dwJybH&;CziEs`b!=@TW>`@3kK;k>E39fCQ(GvY0#zguZ@e=96 zJhCB{In7Mb{Q^1k&lmCZWg2;n>VQLAlZna-Dp35@bNNzNWzdh-vUB<6(FX@W`F7Xo zCQHg8GdWTF;Yu+dZIky`LUJ+zP-8;m@fx0OufcJ|aAvo~4IvnQcXS!AuKFyJc7b3_ zOmm5WL(=Clt<1~E@zIaurfu~e=fbf+g_dp25p6U~A^UO4t5v#CL0YjgLF;Nym*uq& zAe#27F@8EUY}`_k4Lu!H*QsP58v5g4m&<5}BGZAnUp!)r2wNw*i4(dTUkB3tk~Xsb zyF=Lfq5ADeh9FR#d`?~3{jU8IavPxLB`Ti;tSJNudAz<%KP9EzB6eFpuJoSPn`|Z1zNHx= z=-L;ucn2p)Md)!hT_$ZFJggZ74aEldJ7x2)5n>BsiE^-9h;tci=veldo&^i+r)h!WCSDT(Of{jBERH|_F$+KiVPFxRMpb(`QWZKvv&D5_ z4DePwc|qc#Gc|pG068c&Vl=1&IXGUf(!lKOlwFpmCw{X!zauD!1i_tkMyhK41Cru(-%&0N} z`j)M~u}u=`Cvz@`;~(H5)fRKX+~tdO>}IDAIpB|+(we->&eaOAdBaQ@ONNrzLpeaW zIR7xL{MVV|sqf9m^JA=qbU*|=3I>}BS+tOj&;5B%7o(kbtK$5cOZ1RDi8k^ooHlwD zaZ@ItbaF*Xz>fZCa4dmZwp+g?fHdnZ*P@nFk+$tD9}$-l?4RB!3yd}sva|q(~3{3YGy}At4eRT zEpsEaWsWvlsb6Lq6=)P`)`JP4E6;Rb-bY7zr0K^!d#y3qk+F3_fI-Cjop}$m+$FCq z@ZOOz*|pu>ag|Zin;B>E_>tl>ImQ=j)kzlKmdK-L8BE7#OnzU>2797PC=zhVy#=@o zvd0hQhAr)Q172pFWyUz&5$2K1B1HR8>t1NHQhG1b zvlzmn&nnycF%DQHauQ%byO1Z4^GKV|R=!(=rIyY*9bWHCTEs@=SRO8%FcmX3?|gwoQrWbOHh5KI4EPIunc|9GQVgt>?hFFATuV zcfP$By;FzfkJEXzR@)f1_mJ@L)MlZJ_v-jOHzsxnA7Q-}QEW9A_>T4=f0?*$9~?}N zrI|}lpV=~ZZ`dK7=S9e6X=z(f`K)sXq)usx5sN@vr=44uKRiF8 zZZ!s$?b2;jY^=dzO`jR4bRmScu^$g_5`D|e*f?^@s;xyl$rwVXJ;h8&ZOO{xNX^hv z*$xXPld<>F7yp@$krC`D*wj-^Es3wbmhDIy@M6Ek;jw6vHy|g?W3Y=jW(Pg0q;B)m zWaza$IkFvTbst&=VFRlV>4dOQuf?hwe(Y?Bu5G#6moqFZZ1?>S>rB94g6Izj&_xvR z?zNP7W1|3;mi;XqyvvAMtv`*I>sCPUm%52DQ2nFw(~=wxH?Kvxlp) z2wH?r9MeR?2lj^xDGK7Y4IDqJEA*LvgUnJupc!rupF-&xcxvi(Mul4*)h*k?6YSTEo1M_@J!#|WdlDXR;%phlf(@pSEF*T!YX>a3s59(P|E%^~0unrO2B zG7f9=_qmYM!;N1Bn@qi)lig95Cry?gJumRm@utq6?JC0U;@$V$v=DX?r*ASgbXq_Y z9fDuF0zV z+1fmOB~lMDs&u#SWbM?m+q5o3v zTft9l{qeR#;Pe^>3`v)efG8y{Nw>MMBBCZa!z#C+J_(4ELs?>`#?<0FmZ7tFdp99jx;vg4bDWA+&nsDYN9WZdK68#u{`!7<^=K); z0;pHhn{W7edjfCFev(LZ8P8>FVVtjLOs6j5-SP4a%W3N4n1=%;#SxYVa5X44B z7iqKfR?9dUHtiA~{vo=>&)cwy4G#|Hj@p_UJIYTudf{_gUM&){^u?nP84IqNSuL=% z_3fX!)Ed*%2|jW}EQW00fu1|h=9 z*+6jFA3B128nMo?*3NyI{5=Z6dldF?h;vr-TS0jv{yjY2^p4N`rA=gSO&I|EGi*`z z;Hk3=)`dG%1wRW#wxv-#U&to|9f;3k9g81p)4B4u@RPg%kvmA$O~XTY@vRqKDs#04 zVb!2|3abdNI*o#bTF#*ckv|$CA6S$``qh#KNknBt=tz-`*@lJroMCMjsP2?SE8Avk zA;CY4;#2XLFhBBcU=i?w6qrIhFc9Q=RWBzgCP%jmOPfl1*4893WR!G^?DI6NG8Jlk zQZ2LwEUJIm7oEsCSmGw#F;^xl086K>x_a0-Ug5MT7Pk?Pm3ulRljU%j z*UI1hzQdMM!OA459s1?xs=0_uz0JIkhfe+QeA;5yW+v{azyCj%e#3K040O{f23D!kYk(P96Sm^b75y-s_@D)f#)<-iq z29UyBJ;I=UCDiO1S|sx;^q+Ux6DhGhx_6d?+^C|-#)TfACB||%VR-CJoQFo{Rhn~< znr=IP#fQ=;{qZkPJTUU=UriJq1TAt^(R3y_57 z8v2|xbM#oI#=X1Y8{!YST*vZ8RX*<7+Ey>w%GzNx+NUMIE^KV24of_#Pz-x*vef5@ z+?>r)`~>eR^>JDde~W6md|fo-HlwfHTj{L^tMj?-8tXB-7WuFN0Xw%Jif!!XPk zy3zT~8!6=bx^o*2gRUR4%7(@Bq&QtdGbjNFpqSL1Wo>4iO%TR?!h@V(Qy2Jb?(ScH zrnll^7*K>wOUJ>XV%e~R{$f7)(B#7ahs|mZuq)PqKRbsP*BcW3&H4hh!BVs+1tAwy#vA5t+^VVs^1{A#KE9&RIsn&ZShFq9 z>Fcw0=bi8Z@Ej~Ej%4Osc?wMmkJpUu90Z^CIlXyLO z8S_*870EzDJXuAPHC7$Ey~mqax@wsgRX zH<5P;4hG;L?cnCqhP#34?9f{=#umWc0`uBlf;e+^_Txh39w@^?F506|MZw)O@aHu@ z4`4#wxjU>t%(d?B^+mW#P9!0ok;C5PS?udV9%bgor)}rlm$v9^{GrQzeKzjU6=iEb z`r*X$%pJ#vRwj5(3_|b$k%unXT}TM4&rLXG- zwUqYKS`PEe2vK$8*k0_T>sw+%hQ;(8+oM9mxDS0FRHKYXdL_Li!xsS=3g(}QflER=4d>CdMcqwL?p1MQ2vU`JQW#XO-#pY+AUhN3PceuFa#dFC;50VkRs0#i}fZSwBgO5 z`y7XBPuZk`fplNMmw34k@H?KtMg*N(&xT2u!p9o{$%*M;tri7#ZYTZh#LK`!AnzeT zHG8)`2@b%(S1t~q9FLhhrDPwz0zIQLf@GuR^O1}bM&j}Ut2qMzdQKVm z_!C1D+7;N{l__E+d^5{XqHgN zaF{)hS&lqBd-*OA1jUFWs)QMEwdMU9wr0e|a|Xt96AJs{Iv#Dl!eWgo6)RS@Z^FWP zPWP!RXq7GX)#9J0z~N~G=N7}m!x=K>`a*GMd#VcyOVxB*LkJNcA>KQ~U^r+}>Ga`H z83!;4h@OCxibk*0#AJJMAd*dYHenMzbf5^>29PaN@0N={jhea#T zjou*P^K}6ym?H(FjxcTspL{%8LN@!;a@Z86MMp9}elKl{Bp5-@t$->KLR}cie zg6=giXEhm_jDAYcbEAGWsZ*1jZ-Fb;UGSCG8v>(|)d&I17b&kt9U8qNt_F*u>-f%elP8rYrgoqjOF`LtiYFr95th@{?#LJ`w< z!4h_DG)QhdmP^xpiT+PW({4jPt`Nq4+I6r5{=*2_GWC|ZW7d^;L-UL@4WP#B{wDVz zI_Kz^TUe;-vN-`X=(}C%IOf1e&Nvc0i<{Hb(C&;_^dW;sew{Pg8@Mz$GLOr~mSIz^ zMLYYryMXKLLVROC@925E%N%}D*=Ia-s;0KRp#Stfr@I^CVfle6C#OIXZ;GYC2^g1T zy}u-1?)e-A>|Oy)l2Q?NG+QdS(B*P`hQC3Vk;6N zH+zduscDe@?WwnjtaHE%wlyx2*M&(#Khg7@-E_x=lfVCxxXFV^*DNBL#;dWmgyP$k z$mvkn-Pf07YX-hC^RbE^r&OoQ>v1X#D+ zsSLLZRL3EyG2M3*Bh4EHq9mr{m!Rggu}@zXYC(fJ(Vs@`Zx@W9*6s-Nv+$j{=(Jjw zxAhRpDXFNUkB^UO>70$07_uax*P*e)kq%f@tn6_F(|LD($-$|GV#D;Yc06H;C(d(} zXf(~D+lc=;^7wyjz&I4l_*NrOO4YZNyhTr~z#n17tHrQvu4wb}HPtf+>(*^27`_qD zp(s0>Y+xX!Z;(Z+Sv1w_R8r-7!zfyd{yWxTl0E#0>* zMQQAv>j~S5k5}G$k7q9GgtUsy;Hp%%NC8xL3}20mn0l0 z^mZ9Mb6Lw;R*}YO;gjsOfhpTqB*9@G?e=;#CaiDuy#wU; zrzu>7MCu6+R5a#!kzv0DqR@PKmX{HVaWxko^)7s?!30wbzYec3+Ai`$x0*~hw^u1o9y-aP#e4;@JYla zzur7-9A6-gxI-Mf));kTums86B4r7DK_C>LQ9rQNYc_#Zu|(BivAc0W@mK44Cv3{d zhr2w91l?Vq$bHcyRtrWt==IOoD1IJBoOjE1JD}1cW(&R6{WY@l9@wKmVjb!^b-OT{ z-MHT*)?Z)g5y2@2O42aCp-G-W#m8><{|&~JQRzY3jI_4r@=m$&IpP(7IUxV9j#ru%rLCi6VW z->8NF=QLG}zur$7CR2l|@ngJNUMhwg_Tdt&Cn=1oIy?fW&ROY}nxqrR22AoKe{~4? zZiltCk>`!ZQ&f#68d`dexovqYlp;bbKc3C)k6dnRdz}7u$=aiktc&bSW10lZ$(}ko z2k^xJ#ct0qeAL3mrp*C_{LpyJ#*r8A9K#X7tmNe6=*`yKy)~LEeLr~P1~#5+3*$o9 zAoUS`67)lmL>ehE##S|q*mxh#TU1zhtwReO@CPz3!q1VxA!RO?F2OG?FE7(xQPI({ zrB0R$acG{LoUk)nWLj+h6pxLK?HJ#q2nY;sP`$wQ&{<{eAjVE64AyG~ZDkANKJPR_ zAt_!6vnBh@j{NoI-75B@(_xL<3Wwz%QCGVS**h7u%>v0$q;*!rPFC|`-0m507p^SH zW`gZbgxi%F{-j!ur8GOUCw>pJxfmOqVOIOq{l40e*CuSb!S5I_xA{=WST75oN=wml zb+)JxQ`&Y6mNx$}BZ%PJoevrgkg^jlH^XoM$+5bo+QQfXHS(y zk)TB)_bwG>GB>B)ojywlOD7Y@?x-msg-3rz3nbLu`ng3@w8kL70CD=9DORaBMT8m8 zCJVV6!Jw~$ii`RMp2e;E=xTqcNOKtvE%~m$bTfxa8!;++bBEDj7bC1i?MWCnB2SXl zY{>-NHpjm28)hMqYtQM8@Yk{uY8jaq`vtYiZy#;(t5Y0Qyie}9!tu8c?y6uNja|;)#RI%jjus1L zi9dDua&oW?O=G)Enh%U(d*@CM8~GVe`|iV!9hjrlZtN`2E@m&0p#2KKQrphS-AlA} zHm@`5KS$m&64&fthi7ZmY-v{0-}YPZv^qg|HdFd$Z=MO)Pb>T0Ei(8tAsS#`>1s}F z6?n1eU*|N^n&RorNvkr+1WQWG+cRv@-+$1UrvJE$_}^Kz_m;?y&YKH8uVuH>?-!W5 z)=5PZ&O&vy%n-fVoA#k=h?s<}bylugUEUZW;(}&<n5$6l;;i zGQBH=PW^%na1*4uvy9HjGKz1lEA~{Q8mj#q#qBmO3a_(j?sw>yEgv+6w5>KfjG-j% zLI2Fw%Va%HF(ndErXefKM8e8S%MRd@vK0F_0QpOt3CDk5KVBF)&%n=i6tPA>e|16h zgQZ9VOBOqmNx)vO1!O5afcFx z@VHMmakuqqGh_?SCVEmxeM#6w%ybiUWJe4rvt`1{XSt8x#6-VMA+3W;@Om;&$0o2w zS4Z?VekA79qozfhRZB9{-;3*^(jWUZoSZ>r4J<&yE%QK}zFn*-`-Uoov$}D?X&873 z6J%n@`x)vnC`^(bZ~nR#foKomztUbDB@p2k@x{~8lLCT3xvp+HuU$mVd2Glx%aI3@GjD>N zcR7syi?@2eN`XGr3(%E}|6koI$X36y{|=x%eb~AGi~s+(BW?c-fU)B0wWB;#!>wi(}{~P!Pl}X@5 z%nf;gH2oaBIvw$olVoOC$MrHMK9GbYo-$;8of2c%G1jg+*3LiJX#>GCDSv z^x@%wQLh&+0mQd`LPvL8LlXZ>Pe(L84G9zwN=uRC@&A$dLRx^}3U}Xt)r#agtrrxIz*~`|8z%c}fwqeO4#Ihya5(9mhYJioma6=hd_{G_lxL z%`)h8n&y57;&d(Yo-apQL!gp;QeNKHzApcD4Gj&N-)!x6>(ePJaESE7hY!D7dIowh zw3fFVjYa=_Q-A*!Q4$24(NKLAe-;U!7Rh&z!aA*a)PagzWIRCav_pc3JR~4B<06^B zvI_vR%ytOZU$~vMD9OsoN>E&+y5;HwLue z=yzAKy2FeDn72Dfg(|f>d^Amx!v5L|Xulvqa^vPQTKl^!Jz{>09mVV}8WzwL#b9~N z926DgE^Id>F8v?2zA`S#HvEzbDeYUzRjz*O03ImQBbptzIX(gVfmQ3@;8LhBBC`H2_-CB z5`!m%r5)~gc60y!q79TUw)^vCnrYGN29JB1roZ|^KdQFzNJ>Z`Mq!%Kad4=1+^-yu z+#h$cpvXK8QAG_MKrO%c_-^e|jYWa7p572L4f395!4P*S472qOZ0bfnXyv56@RDS;{MZo0tN zS^V-ribRxry$))-k*Fo3Vt=2zD}bQOoHUlkqqK%AU~;iL?J%prg9>VD_+uw-#pnWI z0z(xOVY=Rz^Jh>yfRUL3In(psEbDZpU zt6zzRmO}eX^7rr}hvaDfWCpbagUR5)Ibb9-`fpUmD0JMGO`YQM=glx#y6Jcm&A+J=7(%mt>0t^L&VGhg8faNzH=9VO!g#g1Vq$D$6s{~} z)Q3Wq$fDo&`WnNubnKu>wzq9Ipy{tGTbo7dG)8PFFALYeBaH)VWk&o+rDBGz@ zXb5sa-#sKKs}{n4Pj7;OVHlb-JD9yFpq@c#?HWBh;- ztc0!(6jq?s;Kf%Nqbws+?bFa{dHDyRT?FgK_VzZkR9PaNXU-L&|M#IpuMt2Rnj<o@9Lo?wJVMiC@kNk9+BZVknMhXW}5G8dDJ07?FN6(r2S zmM{;klg0b9;nC5)m0zK6yZN1jv8IJcZ9*-7#7;g{YlE0|SvaaPn0HpVb&$4915(eE z|2{gB7`9wUPgBz@V1H}NL`Fg3FrhkIZCF2P3s5~yAnj1F)r zq$-6mwP(n3KC;JxA>X7f=LYIO?>Nhamu?Cl(#k%w{h#&f23k0OC;S6^&|v{n+w0H3 zw!eo<8yAx422M>}4dk$XuIvHz+KVKAKkeGqJhewSY)6T(Xd$L}0~knH{(1YNB2+M6 z`B+#c-4x~*7KmAPSCexud(ax0pu1yCyY6#v*zfjfI>_Im48#rX0<~zDSOZXHhb|A9 zH__YcysBQy1`S@u(0M6-(O02mOe3OZ|Ss$l&h2YneTyLf_S?qHFXxsIn@J?M$pl+PlJUE;%vb<0%{B{CZz^$k#Yw z&wF^#!6Zd0ppmvCIN)R6QJV#S2ZF++R--5Fj5TgAQu5u`nSxfeR`pUi$gj8qtvcyUSC#^<9J8+g#LF*;vM4XK zR`jZT{fC5hAqKZEvPjSJ^q^h>na$P*tqO~XF}76scxJ?xuh2YSotJIrI}Tjmy4(K^ zPVXcbP&zX`Ju6S1rIl3yv`K}Mz@4G}B2U?;Mg;|h8pj<)M-E0hx}t?oapb3dOaGm< z3ktX&BQB$=X*?m+2$V_18e1CJt-bpRJXeXbPV(|qDe_DSCHRmChx3yfixA4AYAri1TS zJhFB$A&Dcq@$!V}(V&87<&V=#NsenvGEH7vfeqW`j@j%*q*uN5w)+qt>kRm%+(?B6 zvWA98z%1C%>O7sBn7qLR7i6L>c3b7X#P8L>H zaW7UrP*W7wz6?m(#|b9Tif*G##v1~Hmkw1Py6JzH{y+38C#7ugJOlaB3KI?bWo$+= zy~Qc&*+8Vx>F)hrcsVc@pTJfyTeE4jpfXRPFds7-)-tb5*>%l4N-mdLajkZ8f+e1! z#G2{huehI6uGVKFSBJrr+~67-iH;WGZm8 z)I~Bi-cEQD(PAP2#}pLm&m`TeKUmSn6-K&*MusIWMt(W{v(p8>;(l*y#nw{~m^G*` zQ{MaFcmz3JIs}fCN34K-(wz@FGLlxnUabrY3tfr z1tF`zhKYvqjPcsRi3-wJX9vO!8+*Ki){vhr4P#3|_0<4@sKbtmg^UOQi*9pmC15fV zd2r$kzTSA>!P6S`okxMb#mt5!fq_+;_#c0*yC7;68C1$s`xPly-iBTiTWV-GwZR|q zUEn1_PCcX4H8zZncDm^$c3hO4)(jvgJSD#yXl%UQ8W(A!QZL|g7@w?+zM-x>Um~C6 z+7W*Vm>7AOo=d2{%oN%x)i`X+x<5Z&BU1Eou(M-$E?&_p?}9c@hrT1eLeO9MBIS8? zuoS$4{2y(lD^%3b)-i6m(Iaqf&woPLn>@nO;!PF=K=#n`b&j{$ZujI}kdQ*e=i|$v z1gb&ln6NP^s(u7A-d@KAMOYMXCYA2HXYQ(`$Qw5f&}GH=S+z&d8rQAb`-5$+#;Aei zJI^$Y=BgQ1)uF<#t=8n?dGSc2k*gX<%^;+1 z6uc!fwo8G-`akMF@aM{8gsmtaMf?~{AA(QW*DRg5XLAAiS_^*&k{zTJ6%PQFGb zPiA~F3$;4&8)3z+jiWmEvRmV&I3;IwvTedA*$#0fmg%?`{6G(M zTiJwK=av1VDlJtRU#CA*b}LKl))|hq_Y?ezItS#%ALc-0!DYtxHQYInqmc|iAeiJ} z5d;lO-a1=Pub*e|OQhwMr2c?SLx_L9Yw>o@)r9c@xI|i) zm&6`2h$fZVy#_WFS;PbpXX&)bq+SYXskdfoKqf22$R!y8=FzPpxAb3-V(7HR!lI-V z6RhTONlKL4JA9jVg_icN!sG%E^B0@4gH0U1^nj}ArIt?6SGenlxxgOY*A`-YMtni=37Dv#ux)dKg-uPHLJ z^tr4jv-cQ__;;T?8$`??+?d?lOM(#YGe+&@&(ph!-W4_$&i5wT`mt{c>5w3G&W6M` zYfDA5NDUKPb+x802cQY?qR#;+k=&fv(&}E6w2m0u02tMp1XVZuwExWq0Hn-<8kXXi z_Li>Fb8(i0ic86xMEDPCTyHz0tSQ68lAs*3m8%JnTYZ zM%ggMQ?BMR?cg~bPc!cf?3^EUq`rGtjc6ePX~a&}WE=w~cqP{*t}eko1JYA`hRpJV zNq%@cKb>jQj>l?yGG;;GKpPdzt9>F%Q=32Ev{sc1_+x_*7S3(j%T>qgrrnenYaMbl!*%)kbuY85}BZnj&pp-(m8Ar z+lROu;6G~Vqy+;OJtXl6wDhJwV6s%jO0=e#xc*RDX2_-)aNp`>iAwlXVFhur5xdUt z4m=WozGu=XCb5o{K@e@4S=1rO!WdEmZGsvb&~B)#MYa4Hkf}xDykJ5SO3z z!wo<{)6*wN?cgHi693g=gVkvI6$oU}`?yNAu2ri7Cim7CJZ4GwQuVBD4*cQFgPO!! zc_yh8tX6lIuM%jLg}DGUr)GI1)!-r?Je|-dpID2sNj2j} zy&!^4yzN1;=r5_e>y*xqt0L=hLO}$!^Ot*r0ei@jIAUuZca)Y&#pt zTl6$RsQ*mXJydN^3~!j>EzPb!nu;Xrh0aFNh^k%q94X2okiaIjIKxs?NcO2iU@Oo$ z;cUe>A(UT;7+b)jQHQ*kK1Q>WltxRY`eO{cZk|SVovDlP6)%(?(Nx%#l zQV`vLUI125`qa2%;*!Urw!l9vp2cSQ!b(Sl8N?pERAUDSN?f{|c(4OFYaZ2YKW$*2 zaB;~&HXZxVABb`GEoav#vfMCoFE%n6o`luvJPN>wK6T$k2{9`#IWprx zl4MGMm?P(Rfw()?gd-XEypm|+=axEIa|dz9<$4U5hYX1f=%`s(q-Eb@y`?`&P`A^; zsi3Y9VOrrO+A;moKt`a?B67P71ekyFy^oj;7m*ca03;xVd;zNmd+2%?bJ<48xy&^f z<>x3k6AFOq98p(}G^b;#OWU+M9yLPW@VNEoLB-xHwppnZEWTfpz5e9O*B;rp9v<>w zO0@>pSV2VMp*@G0clhuyoJ-DT-5_*I7%lX-eFY~e3#Dl@^UWJ z;a-t>D!t4ALCDv^f&8HJld7UEf|3Z)3i6;l<6IhHHpS0Kh0@=iU`pPX#SM%|KEiB0 zQ*PT_4-MDS4VgaMqjtEHx((e8{}~tQWO~8jngTMbb^^Jox@n5&b$)w2IwBwW7yOq* zbGl?dF)bb zHlt$;a&f8qoW*4gDe$c1Q>+}IkNem6IsAtgRinOJKI<%#7BU-ReQ#wG>vU;IR$;_? zb}iRrUzLJcuAh_Jutr{?-@j@8$n2Ttp`BF);8pdnbbee6n*Thb?0a(DP+V-_OQ9iY zv9^o67WB9lt3n@}6-z0?XBuQ(Qjy?+S9PxX*;IT0#+SlZ5&<^ERk4~yZLAjVtERy7 z5=MQ7L4)Q~6QI4$;>*cFGQ#;g#7_a6SyBmqxC0P1ucek{L+5?+xX3BFp?e=bUiMb3 z$_#?oM6mqe;XDBdx&LxPWRy;cv#NKM#Mw+~*upkh@DaER*m?W&n6cU*<=!b7Jt( zb^iDiKX1e30e5;t{VN_?vv#}fm3p&tOpUeS8IZkAhBw%|)fsI6yo+V-dkk;Uc`Y1T zVx`=zo%N-=YV2c6kFl0>OOSJlhqmVhW`GtUe8S1rkK6l=XHTQd@6#geH?6p)Ptm)X zQ2#Xj1#f5*jy{f)mib*)p4x5>igpR_g)G6ooHY_}IAY;NYTHNC4j+qCvd4)}9w+V> zagjdjGV(IfTd)kum?O~#-CFGCCYf82LkY_{@TQ{rLHXZ`MrPNZ%e)?b(MgU;a$vQp z)_yiw+{%sC=p`>SElT0vkZYYpBsh6bqWKPYf^eao;=dJTHUVnizyMLyZc&yG*iDB; zt44g)`&v({!wZURaY!+@9C9dB)Itw)qMG;o(xRwgDeYNk%D+qRts#U~^X zR_%BDBc~~E@GRSjMFkw|r(>l3MgM?49}Sy=kOs@>P*=-QRw6i+*UCghOq$XKn)a_3 z`0)~-b2kQETl8=WR+l7EuJ_GXTyxfP+JP>u96c=%+*)K8&9MiRcQDvfXx=%!)utJr zD45ak0!V(9;XTlPGaWvjQCwg>nQjTglU2ZQm6gk*%NS@SbJEZy>DUwX`V?OAj-lDmtx<%osK2K(0 z@F*HKl+EEf#oxqS^80(&r=Nh-3X4}b1mL|l@f8szW_N!+^o|KLS|$40Sg9Q=hfBmw z92bTz#l{`@X3y5`u&{|tlf`cD9=y6O)V*IIB|(a-=jfeV4sy|()fZa&8irsch!7pO zOR!tf{s$R=_s17RRKRPJ_0u^hGPpNiQZEF4)mRGp*C!Ns$g}$!(EVS z5O&H-LTfpnBp3w~(c5phPcxdKnoOUqF)@UJu${_ulx^Fb(LYy!)mfphtiS~HhJ_DX0U76s)(A%L-gbV!UgMr42qp(mK>s6L%ubjs;!Nrb z9H6S5_UN8G9s?cyD2&Um7tF^&A-2%qEbX4ApXVW*s~&JP^@FF5yOPWCas88qfFSRt zEB_3R(8^&#{Y*zyVoyCT^L5m}L}M1>m(3|KPbpiYJ=x&lwhZpr)N6`2akz{;=+yxJ z5P^?Z^A^Tr%p1JQIF8sB((jL=2b{d|SgXNCkOItYIN|k+j830fH(!C#f0Z-P1SBQBaV`F2`84a!+GiPn8tlp)aCGc_?8)xig?O z{b+x#2H*ByamEi!tC4;XDjGuxVMaH82*OTzeX}^J`F_WzW6{(G7dT&SEWZMq#)XKXxFk7- z{NX$mWM@IBRlJ5`i`VnkMz`oOlSjwuvflewS97v5j%j?pSeR`1>C5!$NSl{H&5-M& z*U||;f=otvFRh*b2sN%%t*4$m5w*4L1;)3K;3fabYJSQZXus_!7Mo0B^QBbtMl*Tg3~UvbSnH@QxipQWDH zU;+?$hHZLze#jLnO6;TIRbW@b#t>bE8|lHG2mNk?n$p0?+8;g8JgsRxS1WZ!-JK}I z;i8gIy--H6&0YRklS%o*1T1U%Ubf5~RV+-BN)1AYybZlA^Lf3|=DdPXW4^XL1X+t@+#oHrvrp z!P_&$1|Gc(5@ON#czDB52_T>+Q3-0$Wk*%osk|fj9~L%*nqRD(fkW(L5oM`a<^etW z1$(dplx$i@$(-XMAfZDpE^&lo3n4J6FWgP|H{7cVzn$e{?MN!GiP@|vpgh`9GUP*s zRKxPB&oqu|u}HSQC6x?Yb!;J~bhyIg=gCJ(3%kbQN)SGt62!#gTjuR+i$N2eb2*JC z{q*m6)T47ZUs8m>P%W2KPm>4~nU(De(cyh#yAj=9*L_qd)hrA%z7e$A%T-_LuJ%0% zirM?w;?q!>0odO?4Y_#xUiR8kD6JGJ*2St6`L%uVZDEDqXPYtpAO4#c5JU|BpLr%m zia~t@NyDoejlN`H?T8?yC-HXaDk5&_)>#n1vqj?mZNPR@eU1coF*1j^;g)gB=1iQ4 zxpuCH(*4{%SPiRDSQjSBY|$_mEAIK{*Cekm)7kro_W~j8KVIFlWgAGZma>dw;zyxP zBA)BOV9S63yS!R3&*DBc%bBKTYy~mD#k&TtU??h_iR%~UepmNsKkWmcK)rg-h zFc!c`ZpM>!ZsmEWdtcRrFK%JNPYHK;%z`zU2eo$75Ntv6o=oj20Oc=S3nzK(?*pHC zaLiE6giW@@o#!VoazvxAfFfW%+l|1 zol^WfxWucu%W#&W5$8p)YV_#}BDl;ySBia4pqQwBTpp8PTaoxhaEVa5nn=I6iaKf% z;0A<*mnj}H_WtJmcW-hB3%h*r{figWl%9x!DEG3)baV@f?Iq64eBQ)ffe!1Tp`nuH zFZ!i)hmrRV1B7NnP4=7eK1!fIq*51j(p#tp#V|akpdn~5XpsAp!Y@fOed)K8(EH#N zo-u`QvUT0J6TUGWh2qksIz0XFonflq>fm6-LlJkSdes{>vBSyYh%(wE2(ghbHKF$!5o?}C==2l29uR{V8*ahTrp&E9v*_0gUe8a z9hFVsES(qhR5@kWsoqjCI|)GdK&M!)(;;sPmVQ$P_w_qk6bfHPDJBWVR4~cONTgh} zQ#;6rQ=hZ#Jq7hshi5eWg(WhTC>{f#UHGd$lI zxEPrAR$%Obu{KW`m%R=UVm#Gj=+6q0WbkiK6(tiB@Js_2}gUYLUOxUPB?RhX;Z7KHVqLnREM%JjqPz6kP@J=hUJY4>%zD;Fb=o<4mE zhTA7AdY3~x3x!%QAM@Z!MjLH5?=&F}jDy?R*byXL2jZM|HmZ=pA`_a>kx`(pOMF96 zR=;Y$=XU#<2Pu&xFf}>2kK*0hq_inND%$lDqE2~4DSGK+i6 z4=cR&K-rJ7UMVActacd4mggijDac4Qe*PlhyZ5JB^>w_XV=;oD4tEH&k5>mVpF)Rj z8t-fuJN}cgj|E|%r%r(;4`p0}$lGyFo#$f$9|0Ax*6Q|`eyax?>L|v~8`|V{hWv#H z(530P_jl+zZ+IA0kkdh4%I<-GU%;~W)VMG6<@mm%M4dafigv$lHx)L#xPn<>ZlwMb zmseYv^}g1YU#=Iplg8}grC}u4mHxQI0yohny*h}@YDet)`|a=)3zPicvC-}WDwO5( z9%a#ZAUr09N@|2_LDzUy2b+>)bqa+kGd|v=Eq8-3f?)50lVa=!YHRk&{cM;{VbR{r zMhE*vSRI>|W_1Upp2XlWY79X&zy-OvpI)vdfnIDEbBfmaD~!_wGkHLfz~r$QAns}~$PW*1nppe~p87mPENG+w8bKdh@~0Onf5i%a zDc+tkbngE;y&da3zWFwEA$&2}Z?Sm$6TwU%-G9rv^`1iI!0W*EUZjB zziE|fpCJ`GL+V^{8>1UX=?Zb{b#ScsW}$QvTQLxAUyeK1)V1lgM|iWpXVmZDppp`c zysx_TX86LgH2Z~FCSxzUHte&hSLO3Z%!I`4S|NqA z?n+uMm1$Z$rWVDjND-yHoGg{(51QfQ@wr_N&RlyHZ88$O<6O*3pSNS#Bc${Tdi26^ zo*vx;+4%*upf^>HX4WuRSWi2r<9Rt41)E6n$Z5%?y(yqY4E!P>3@^98VC8mV%AiYy z;G!n_K}i|*{@^2)=Z4gO2g*J zE~hHhieh31zAeokB-(3v=+8QRm%Qws&#Cz1-kbH#@eObA?yJoY7BfSbX|k6TC{^y! z-DuI~JalXtLHWOIrNTWn@AK6b+AAI0FT0W3wWlJv7mMGK&0y_GSn@z6RF9|}yXm-2 zHsv)d#Q?#drj2h(50$qApV-JbG-k6a4yzyu#@QA&9`aoa`IvHoURC0a^Kxlgvn^)o z_D2k9-rpBf>{?u*(N{)qcV<6v@*{hJYoe*$#qKJ7knK)K&8}C1Xq7yQjg^!U?gzg2 z2bRu<8m+xo*T~E*yPs;E7OVH3-LAzp?%c9qul`xp2g?{fA#ek3l+Gg7~?<-c~~Qt zlfqwLCs1>`k6ZScHv#A@`#n9MZlOHU4+eKLNhZ5fIUaEg10n0X$ogbhqe&mMD5C_= zLQv^CzV;$DKVCr;o}g@4-{d?)uD^&)h&)^q_H$Ki1`%1KcRgzv31sqytJmqN@`aoR57O>!g;EEOn_RD4|)sA(lSq@%Nzomom#Q{JEmR^JMr ze{jR|duKf$Iatb*N48mDWCYj1pgmaf8RhuPL+#g``9=dUr-@UAhqCm0GlrvmY}{Ue#9eqZmX7I34*XbYA@|Noja*4jIjb!zxnPKt18qTb>RN` zT8?46dmE3TrdQzYLT#k@;Bt;}a`HYwKBRt?JV|4SJV{+vfkQCa`RT5##?3RXT$Sx% z-b%8>55&yVS*@y3R(z;&kKy*CEGA~yX@dOFGNC|#%ZoR*biubJw+m$fvIpK;jT$vs zZICPhTv$HeWrqaGUY8OD2aD@N<~}G@l_Z%}5!sab97X_YazJ#<=uzRGn!-ZJ!sde_JP!SjGE%c@#qLW(;>#dh28fYF&xmj?h&i0T%}R)4SQD~HW@hx0!h7&F zvlCJ;;q7oTh{)gDxis1SekK1&LR8`tkBb53N{fiARA8o5)ta$x)Ys7Z@@K%VXmcIC z(plWWEPa(Y2lH>)~*`>1BJL$0$N`n!;YnsjHDVoJO;cPmHgM) z-m}@RsOz2{-rntQoRr8BqP`R-8=0ToznWFMLBMlmPJXDD?M;e^=@I+e#B@r3 z*cFz4NfG6^1ilBon$B{i7pXqF6x-JrLDKWRi7HDP&G67I)~}Ja*AuK0&C^$oG@@=~Bg#=x>bAwqgC1^)n>u<=rb!Rr~gcE5KF9lAAA zWFMFlv~64YTxA#-#FDvl>pj$nW3>BesC4%)4H2!SBd|~@$ZrjNV5>=uxW(;5LKv)D z)W%kpzzjO6X^el)LgrG7X8e3Qq3CW8i0JDZ3?WRr?GMF? zLOUn7woQz3<@s|-#>B*M7daA!hKLM4)(z@oy?@$qLr+T>ft zrd!Uj(_L78zQdEXox^xj&kHgl5USK?|1D~_2G1?32=klU?&bmGC4;v&C7b?*jdg`8 zTDN0-ea(SMmlpmr<1Mpc%4rLyR^;Y{3u1;IpQpp75n_+lT|I*;>odx%zNDS|QyP;n z1rDD&^iK%_NSKV=wEb$j22Ac=eQ&iYCE8l4(a!MiRJsUsu_a5PTR

*}+!0sR}f| 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 MSeX#fWPEdA}zPH{F2L+EkG52%Rio)S;YaS>s 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 BJ6`|* literal 0 HcmV?d00001 diff --git a/docs/static_site/src/assets/img/mxfusion.png b/docs/static_site/src/assets/img/mxfusion.png new file mode 100644 index 0000000000000000000000000000000000000000..00de0eeae510aa02fe5d1dda3d2cf95e23a9e467 GIT binary patch literal 21013 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 LNkvXXu0mjfubX)X literal 0 HcmV?d00001 diff --git a/docs/static_site/src/assets/img/mxnet-background-compressed.jpeg b/docs/static_site/src/assets/img/mxnet-background-compressed.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..78d664228490e20906a0320c2392e2a91b078409 GIT binary patch literal 86747 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 F2>|JE`mF!} literal 0 HcmV?d00001 diff --git a/docs/static_site/src/assets/img/mxnet_logo.png b/docs/static_site/src/assets/img/mxnet_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..be8ad9ab9ccdfcc03b8f32e1d66e661fd97ae5fb GIT binary patch literal 22390 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/static_site/src/assets/img/mxnet_m.png b/docs/static_site/src/assets/img/mxnet_m.png new file mode 100644 index 0000000000000000000000000000000000000000..d25535659f1a923696f8fdadec0b805e3960a9f1 GIT binary patch literal 3463 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 literal 0 HcmV?d00001 diff --git a/docs/static_site/src/assets/img/octocat.png b/docs/static_site/src/assets/img/octocat.png new file mode 100644 index 0000000000000000000000000000000000000000..9c9e6f17915e23049489646f54e63db7a1344dbe GIT binary patch literal 32590 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 literal 0 HcmV?d00001 diff --git a/docs/static_site/src/assets/img/perl_logo.svg b/docs/static_site/src/assets/img/perl_logo.svg new file mode 100644 index 000000000000..2ac676ee4bb7 --- /dev/null +++ b/docs/static_site/src/assets/img/perl_logo.svg @@ -0,0 +1 @@ + diff --git a/docs/static_site/src/assets/img/programming.svg b/docs/static_site/src/assets/img/programming.svg new file mode 100644 index 000000000000..63fbe369a57a --- /dev/null +++ b/docs/static_site/src/assets/img/programming.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + +Layer 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/static_site/src/assets/img/programmingv1.svg b/docs/static_site/src/assets/img/programmingv1.svg new file mode 100644 index 000000000000..5204941ad335 --- /dev/null +++ b/docs/static_site/src/assets/img/programmingv1.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/python_logo.svg b/docs/static_site/src/assets/img/python_logo.svg new file mode 100644 index 000000000000..116eaac31119 --- /dev/null +++ b/docs/static_site/src/assets/img/python_logo.svg @@ -0,0 +1,269 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/scala_logo.svg b/docs/static_site/src/assets/img/scala_logo.svg new file mode 100644 index 000000000000..9c075293ebea --- /dev/null +++ b/docs/static_site/src/assets/img/scala_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static_site/src/assets/img/search.svg b/docs/static_site/src/assets/img/search.svg new file mode 100644 index 000000000000..afbf046f954a --- /dev/null +++ b/docs/static_site/src/assets/img/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static_site/src/assets/img/slack-logo-icon.svg b/docs/static_site/src/assets/img/slack-logo-icon.svg new file mode 100755 index 000000000000..c37dc5eb49e3 --- /dev/null +++ b/docs/static_site/src/assets/img/slack-logo-icon.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static_site/src/assets/img/tensorly_logo.png b/docs/static_site/src/assets/img/tensorly_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3b912a1908eb3e25c5ff28b9ae481e15e373511f GIT binary patch literal 100093 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 \ No newline at end of file diff --git a/docs/static_site/src/assets/img/tvm.png b/docs/static_site/src/assets/img/tvm.png new file mode 100644 index 0000000000000000000000000000000000000000..4fb0bacd6a837610e3521cdb0da3e2c8ad008db7 GIT binary patch literal 4808 zcmZ`-XH-*Lw@r}VdoKZzP6$m}LY0JiPb5`@Q$ZcxQ}#_TF>PHRoDupI_&Wm4z`AJwH7F0AMmTxngrZd!LV+ zwAAPCxV85a=MyDH&s+}xXvknVaiKcjgWODP%mILK2>>9L0011FL$Rv>01g5GtUCh$ zn)d(zo`9lOYv}m_U4V&wFaW^7@p}LPg+;so0C3FH4uwUTn`yfF`+{9=`n$S=Lwy6z z*#H1ERP%i4>yC8+h5GvV1#5=F#QtJvp09ty3SywYC|GZp7|PrVWZ)m<4pIfHfFWYq z^dJxj8gvt_X>-NspYZb?Ow0p|4bW6j2nh)RhbV*ngD?t;8X6i35G4gACHZrPd~lc_ z)+JQlFZkl$BLA&(#XZ<9$TI-z>F)>nt?T0IkHf;m#C{w7`~00J))W07C%@o-$~rGl z;rEGxA{e6Zul9K;^f#($;O`p{bh{&9@BlbU)AZcRv5}|0ni; z`BonOSpV~`1$nxe`eEIJ&dvUbR{F>7|7ZN81y%T6`2T3c-(CI-J#Vu%JyhY}E7PW@ z7>_;wLRcM5ujtu@0=FD}QymTYqK8^;w0&`erz8zqQPA&Qc#bl)vNcD*N67{pt-Kt` zcZ}FoJYK~1#ZF5|QLpOj>XTjY^E4F*@!fbzYh;%QZ5lQoTxG{^D|OGyU@dy4ZsEtE zM)1MrOhm`_OkHp*Nh4%=upn%u_@E#R^S-DcgekDC9SKiJZA|75^G&lG6eV;@C0rNF zBi=6Ucb^{$z<5WEQ&zytfX8%*vz#hQ_N#`7cBc(T4A4J`MYc%U^ibt!F5B?kRRJr@EBydY zgzCd9?=KnO&$zSPuD1+Qe*+9smZ8PGS^7Rk8^azbYd&I^TWcrtqcIu9?5Tb@@i?6` zrN47;Z%-wQ5)5#VYeAaQ=+{V}Ekjcb$radZvnCt#V@`QE7EUNLqb1^J zngn!z5h5?9%eT9GQ7-Ah9tX`Bk%ODl3BbWMdm56Gl{N?#wQ=*77c(|UocqGn98RoT zi;AStx((kUf!~uS>In1*>aOb*(9FkBTxD|! zO~71ih_HxR057AOt@9|W0lt~>Z=wloje$L+#`TsT3$#^~F#y>5N-d^_ zI@z!xn^lV9Iora7xy4}-HdNC<_-LiYMoJJrxnb~frTIfm^$>|Y)GEuz(g9AI8YNdE z+ANuA;Xh@_v$~CvbHUWN`OK~fy{uJvb4tTMh|Wu`$9z+&gs6$PUjQTz4zNohjOTqC z-g45otF6Z-}(tHNHip|it01zK`A6-W6$ar0?RdUwiJ zuQiKYaB!^s{>SK74F5f+9Ob#3$_1}viN^fL2T~S83mc5bun(w<{T$v1)$j98%4N~6 zw%*KDD|7=M>Xuj9Ixa5?hEpo{HdPRnxjF^*`(J1aQ(#Dq`I%oCkw4vq$fzYUmW##e z&xc#d2-%HXxx?L}+qejMjw$+b`k8O;Dl#?5N=t5(w*a zo(`Du?Zpg3*^jZb5GhgT!20#miD{eraQ&{JzA~Q7JegeZyHdIOR=Zd4TxI#31ABol zi3H0pQ@amp?3v!)Psh`R%0Jcf=fe$U6UrB9HTk?CYex$9&hiJu?Tq7o2YP-O}d{U)hl*lfx)(JRL)BQ(bTM)9fyLN=8#nm=+-plB2T3l9ULx%a0

^M4RMj*&yj6 z%pa>Wq+)3JQ#e>Tp6$}JOz$;wboDP< z<|~mR+Co1LTW=DXCJ{{l&i80LF(G2?hfwvno(QVb@TNU2$XYYC_-C=&$$M;Q=~l$} zrB8=7`R|1im8vq++(x6CPj%Uo3?SqMPkfhtZ-vi@#0#+$vs7w&WMwZ4WhHQd=)*$@ z(Xx5lCF2oMwR-^Aehz7%0t|IrVnwqF?43 zr6`XU#)!mrBduz6D$S_5&}U2&f2PXs*6Ep@>62j|T;tI)L-6jtq8snTH$JOnKdV5Y z#*=tn?v_|Q8aY5DukmTLoRTRR{6sf#cBG5W%DTb0JPjZ&l-goDK5*)Jrj|=;Z{(rU zrDEkv@v2U>L3XVD2H%?Y(Sb&IOO6`51h1F%M|u)59GlLL6w-@vb4W;C zsoF1(W9-!(XYd**#02kNO}RBRygH+{ue$f_ku$^V%n4H!ttIufGN9Jd1LgWY53c+_+5fm74GvHCNQr*+^r?)zOuspIW#B-K zL=dm#)r#DPd&7e@N`J07yos$NSL^s*lvdm65mi*)w`=ZkJd#9w;HD#B(+Z$GQEET$( zt+%-G{fQ}CKVjr$KAS=fkt~Na(oHCs-T(_znp$ z80I0j5%&@OcqMqN;0t40xJ?J|oHv@A`wRUy6HBk+NdJY3a&~WIj+d;D0J7q?rL6?$ z;eAH_3ZV?}iNs1M*;&V776I61{!_qlB> z^bTO3}UL^nez*rN#9xRmbt_gWcGUlqSL^ z1bTAQAKYGcTTpKMI$EN;?DGd&I4W6-94?ZS&;9^W7bZsM3H>5XYZM zET}@yFQi*eM`}dfO=Bu+L}6Ka18LT40gz@;8Z!7q3u_r9x&FkVy#P8jDCa z989H|ds1%2=P=pAA_gM2avE%1`6W&R8zM)Pxy!oZbm<4Qq4eQtZ@s>@ zC(6_6q`5)7PFcFcGrgq3nchqKUU1Inodr%-AM~))4Kqf@A)tn@Mp&QLyyu(-uH%=Q z0)*pRct4kdpr_d!;2+~6VnQ8kiN;02Ql$;>SDH?Sa^cH2$AoX78}j^}Xu8k<-UE>J zrK2W=;<4Q^$-b(~WrkEuho3Nm-;14fS1&SKO`G?;u*`B5#<58!2;I2tc(AQ9hS98B zAT{iWJjwy`W+eLoWdnrudS9u zy-NN@_6=#JNi214D(**e@lKDg$5pnh5xO+3t7`ode>tme({ZEGzL=Skx~BH=an4mb zq@-7@;?prVH_7U;CZ#rdV&#~OsIGBAtAe?6@t_b?HzcbKozp%|DirNU5APJnh|?06 zO2!5+lgwmODJ?P-WmCn!7E9=PL1uLAT6ACoL@ykN0BYO*3-#9bdt<&y7YAf^h8MVv_dt|kQ zFGo3-lCy!;I*V3O3FB3OUBcaS`foj*(e|wjksS)<Jtx?+!iDCV1k|BHM@l@#6yHy?NL;?t|9O zIM`lW*=a|tQmExl4j2#>6S9K5)^dO@xn0oeR-e+xR6avLTk7zr6d5p`2ZdG=tCn)* zGx^({LXMl31@R-WP0-izUSzKh4UgV)`rBb7Kk_#UhV9)yYuz-VkmcoNbWqDQ8=&$Y zg>QA|P7ji%C-8c%k+#u*Fy42%&HX#&7>4}X)RB2g*uML2WvW>zLH^1DkHs2JfXIIz zKkk--DKzO}6kCVV_>NuFZL7LOl{F_Gf9R1gw|exeKCOX{x~F-pjUz(%!bDAWEDj7m zTbJ8ZV|u$Y_v_;zaO}b3au5n;vG%y~NRXjCei4kguh1H^sV~)iwGz`WQ&2qcntk5y zt5w=Z^V&3*2z5TPm61e-4)a?NtukU)ooh?28T2JF+|0JVnVex!h#PaScA6*&RJqYN za;!x&i)S0QbTPtRIYO=%w?{6O!Cf>|oabVm=x3@(6-RI9mh`WwOn!z+J#|?%e_}>2 z5vvrmPFC-;eSv0+ttYX+T8(KUgeK!-f35j{5S1ss zLU?}}PQ$OPSmP%+`A~823|f_3ZW{dJb-5-~oJJ05abaMm{CYGBc=?R(Ra{U +VIDEO TUTORIAL diff --git a/docs/static_site/src/assets/img/visual.svg b/docs/static_site/src/assets/img/visual.svg new file mode 100644 index 000000000000..6b9c0946b4df --- /dev/null +++ b/docs/static_site/src/assets/img/visual.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/static_site/src/assets/img/xfer.png b/docs/static_site/src/assets/img/xfer.png new file mode 100644 index 0000000000000000000000000000000000000000..3c93662dad6f8accafc8b3ef66c2543c1f2b966e GIT binary patch literal 8932 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 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.action = options.action; + this.emitter = options.emitter; + this.target = options.target; + this.text = options.text; + this.trigger = options.trigger; + + this.selectedText = ''; + } + }, { + key: 'initSelection', + value: function initSelection() { + if (this.text) { + this.selectFake(); + } else if (this.target) { + this.selectTarget(); + } + } + }, { + key: 'selectFake', + value: function selectFake() { + var _this = this; + + var isRTL = document.documentElement.getAttribute('dir') == 'rtl'; + + this.removeFake(); + + this.fakeHandlerCallback = function () { + return _this.removeFake(); + }; + this.fakeHandler = document.body.addEventListener('click', this.fakeHandlerCallback) || true; + + this.fakeElem = document.createElement('textarea'); + // Prevent zooming on iOS + this.fakeElem.style.fontSize = '12pt'; + // Reset box model + this.fakeElem.style.border = '0'; + this.fakeElem.style.padding = '0'; + this.fakeElem.style.margin = '0'; + // Move element out of screen horizontally + this.fakeElem.style.position = 'absolute'; + this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; + // Move element to the same position vertically + var yPosition = window.pageYOffset || document.documentElement.scrollTop; + this.fakeElem.style.top = yPosition + 'px'; + + this.fakeElem.setAttribute('readonly', ''); + this.fakeElem.value = this.text; + + document.body.appendChild(this.fakeElem); + + this.selectedText = (0, _select2.default)(this.fakeElem); + this.copyText(); + } + }, { + key: 'removeFake', + value: function removeFake() { + if (this.fakeHandler) { + document.body.removeEventListener('click', this.fakeHandlerCallback); + this.fakeHandler = null; + this.fakeHandlerCallback = null; + } + + if (this.fakeElem) { + document.body.removeChild(this.fakeElem); + this.fakeElem = null; + } + } + }, { + key: 'selectTarget', + value: function selectTarget() { + this.selectedText = (0, _select2.default)(this.target); + this.copyText(); + } + }, { + key: 'copyText', + value: function copyText() { + var succeeded = void 0; + + try { + succeeded = document.execCommand(this.action); + } catch (err) { + succeeded = false; + } + + this.handleResult(succeeded); + } + }, { + key: 'handleResult', + value: function handleResult(succeeded) { + this.emitter.emit(succeeded ? 'success' : 'error', { + action: this.action, + text: this.selectedText, + trigger: this.trigger, + clearSelection: this.clearSelection.bind(this) + }); + } + }, { + key: 'clearSelection', + value: function clearSelection() { + if (this.target) { + this.target.blur(); + } + + window.getSelection().removeAllRanges(); + } + }, { + key: 'destroy', + value: function destroy() { + this.removeFake(); + } + }, { + key: 'action', + set: function set() { + var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy'; + + this._action = action; + + if (this._action !== 'copy' && this._action !== 'cut') { + throw new Error('Invalid "action" value, use either "copy" or "cut"'); + } + }, + get: function get() { + return this._action; + } + }, { + key: 'target', + set: function set(target) { + if (target !== undefined) { + if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) { + if (this.action === 'copy' && target.hasAttribute('disabled')) { + throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'); + } + + if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) { + throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes'); + } + + this._target = target; + } else { + throw new Error('Invalid "target" value, use a valid Element'); + } + } + }, + get: function get() { + return this._target; + } + }]); + + return ClipboardAction; + }(); + + module.exports = ClipboardAction; + }); + + }, {"select": 5}], 8: [function (require, module, exports) { + (function (global, factory) { + if (typeof define === "function" && define.amd) { + define(['module', './clipboard-action', 'tiny-emitter', 'good-listener'], factory); + } else if (typeof exports !== "undefined") { + factory(module, require('./clipboard-action'), require('tiny-emitter'), require('good-listener')); + } else { + var mod = { + exports: {} + }; + factory(mod, global.clipboardAction, global.tinyEmitter, global.goodListener); + global.clipboard = mod.exports; + } + })(this, function (module, _clipboardAction, _tinyEmitter, _goodListener) { + 'use strict'; + + var _clipboardAction2 = _interopRequireDefault(_clipboardAction); + + var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter); + + var _goodListener2 = _interopRequireDefault(_goodListener); + + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { + default: obj + }; + } + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + var _createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; + }(); + + function _possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + } + + var Clipboard = function (_Emitter) { + _inherits(Clipboard, _Emitter); + + /** + * @param {String|HTMLElement|HTMLCollection|NodeList} trigger + * @param {Object} options + */ + function Clipboard(trigger, options) { + _classCallCheck(this, Clipboard); + + var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this)); + + _this.resolveOptions(options); + _this.listenClick(trigger); + return _this; + } + + /** + * Defines if attributes would be resolved using internal setter functions + * or custom functions that were passed in the constructor. + * @param {Object} options + */ + + + _createClass(Clipboard, [{ + key: 'resolveOptions', + value: function resolveOptions() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.action = typeof options.action === 'function' ? options.action : this.defaultAction; + this.target = typeof options.target === 'function' ? options.target : this.defaultTarget; + this.text = typeof options.text === 'function' ? options.text : this.defaultText; + } + }, { + key: 'listenClick', + value: function listenClick(trigger) { + var _this2 = this; + + this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) { + return _this2.onClick(e); + }); + } + }, { + key: 'onClick', + value: function onClick(e) { + var trigger = e.delegateTarget || e.currentTarget; + + if (this.clipboardAction) { + this.clipboardAction = null; + } + + this.clipboardAction = new _clipboardAction2.default({ + action: this.action(trigger), + target: this.target(trigger), + text: this.text(trigger), + trigger: trigger, + emitter: this + }); + } + }, { + key: 'defaultAction', + value: function defaultAction(trigger) { + return getAttributeValue('action', trigger); + } + }, { + key: 'defaultTarget', + value: function defaultTarget(trigger) { + var selector = getAttributeValue('target', trigger); + + if (selector) { + return document.querySelector(selector); + } + } + }, { + key: 'defaultText', + value: function defaultText(trigger) { + return getAttributeValue('text', trigger); + } + }, { + key: 'destroy', + value: function destroy() { + this.listener.destroy(); + + if (this.clipboardAction) { + this.clipboardAction.destroy(); + this.clipboardAction = null; + } + } + }], [{ + key: 'isSupported', + value: function isSupported() { + var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut']; + + var actions = typeof action === 'string' ? [action] : action; + var support = !!document.queryCommandSupported; + + actions.forEach(function (action) { + support = support && !!document.queryCommandSupported(action); + }); + + return support; + } + }]); + + return Clipboard; + }(_tinyEmitter2.default); + + /** + * Helper function to retrieve attribute value. + * @param {String} suffix + * @param {Element} element + */ + function getAttributeValue(suffix, element) { + var attribute = 'data-clipboard-' + suffix; + + if (!element.hasAttribute(attribute)) { + return; + } + + return element.getAttribute(attribute); + } + + module.exports = Clipboard; + }); + + }, {"./clipboard-action": 7, "good-listener": 4, "tiny-emitter": 6}] + }, {}, [8])(8) +}); \ No newline at end of file diff --git a/docs/static_site/src/assets/js/copycode.js b/docs/static_site/src/assets/js/copycode.js new file mode 100644 index 000000000000..3e7ce5d4b0f8 --- /dev/null +++ b/docs/static_site/src/assets/js/copycode.js @@ -0,0 +1,97 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* Copy code to clipboard */ + + +function addBtn() { + copyBtn = '' + codeBlock = $('figure,highlight, div.highlight'); + codeBlock.css('position', 'relative') + codeBlock.prepend(copyBtn); + codeBlock.hover( + function () { + $(this).children().first().show(); + }, function () { + $(this).children().first().hide(); + } + ); + +}; + +function html2clipboard(content) { + var tmpEl = document.createElement("div"); + tmpEl.style.opacity = 0; + tmpEl.style.position = "absolute"; + tmpEl.style.pointerEvents = "none"; + tmpEl.style.zIndex = -1; + + tmpEl.innerHTML = content; + document.body.appendChild(tmpEl); + + var range = document.createRange(); + range.selectNode(tmpEl); + window.getSelection().addRange(range); + document.execCommand("copy"); + document.body.removeChild(tmpEl); +} + +$(document).ready(function () { + addBtn() + + clipboard = new Clipboard('.copy-btn', { + target: function (trigger) { + return trigger.parentNode.querySelector('code'); + } + }); + + clipboard.on('success', function (e) { + //Deal with codes with leading gap + var btnClass = e.trigger.classList; + var lang = btnClass[btnClass.length - 1]; + var lines = e.text.split('\n'); + var hasGap = false; + var continueSign = '...'; + + e.clearSelection(); + + for (var i = 0; i < lines.length; ++i) { + lines[i] = lines[i].replace(/^\s+|\s+$/g, ""); + if (!hasGap && lines[i].startsWith(LANG_GP[lang])) hasGap = true; + } + + if (hasGap) { + var content = ''; + for (var i = 0; i < lines.length; ++i) { + if (lines[i].startsWith(LANG_GP[lang]) || ((lang == 'python' || lang == 'default') && + lines[i].startsWith(continueSign))) { + content = content.concat(lines[i].substring(LANG_GP[lang].length, lines[i].length) + '
'); + } else if (lines[i].length == 0) content = content.concat('
'); + } + content = content.substring(0, content.length - 6); + html2clipboard(content); + } + }); + + clipboard.on('error', function (e) { + $(e.trigger).attr('title', 'Copy failed. Try again.') + .tooltip('fixTitle') + .tooltip('show'); + }); +}); diff --git a/docs/static_site/src/assets/js/options.js b/docs/static_site/src/assets/js/options.js new file mode 100644 index 000000000000..59913a0c7ec7 --- /dev/null +++ b/docs/static_site/src/assets/js/options.js @@ -0,0 +1,120 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* Installation page display functions for install selector */ + +$(document).ready(function () { + function label(lbl) { + lbl = lbl.replace(/[ .]/g, '-').toLowerCase(); + + return lbl; + } + + function urlSearchParams(searchString) { + let urlDict = new Map(); + let searchParams = searchString.substring(1).split("&"); + searchParams.forEach(function (element) { + kvPair = element.split("="); + urlDict.set(kvPair[0], kvPair[1]); + }); + return urlDict; + } + + function is_a_match(elem, text) { + + if (label(elem.text()).includes(label(text))) { + elem.addClass(('active')) + } + } + + function setSelects() { + let urlParams = urlSearchParams(window.location.search); + if (urlParams.get('version')) + versionSelect = urlParams.get('version'); + $('.current-version').html( versionSelect + ' ' ); + if (urlParams.get('platform')) + platformSelect = label(urlParams.get('platform')); + if (urlParams.get('language')) + languageSelect = label(urlParams.get('language')); + if (urlParams.get('processor')) + processorSelect = label(urlParams.get('processor')); + if (urlParams.get('environ')) + environSelect = label(urlParams.get('environ')); + + $('li.versions').removeClass('active'); + $('li.versions').each(function(){is_a_match($(this), versionSelect)}); + $('button.opt').removeClass('active'); + $('button.opt').each(function(){is_a_match($(this), platformSelect)}); + $('button.opt').each(function(){is_a_match($(this), languageSelect)}); + $('button.opt').each(function(){is_a_match($(this), processorSelect)}); + $('button.opt').each(function(){is_a_match($(this), environSelect)}); + + showContent(); + if (window.location.href.indexOf("/get_started/") >= 0) { + history.pushState(null, null, '?version=' + versionSelect + '&platform=' + platformSelect + '&language=' + languageSelect + '&environ=' + environSelect + '&processor=' + processorSelect); + } + } + + function showContent() { + $('.opt-group .opt').each(function () { + $('.' + label($(this).text())).hide(); + }); + $('.opt-group .active').each(function () { + $('.' + label($(this).text())).show(); + }); + } + + showContent(); + setSelects(); + + function setContent() { + var el = $(this); + let urlParams = urlSearchParams(window.location.search); + el.siblings().removeClass('active'); + el.addClass('active'); + if ($(this).hasClass("versions")) { + $('.current-version').html($(this).text()); + if (window.location.search.indexOf("version") < 0) { + if (window.location.search.length > 0) { + var url = 'index.html' + window.location.search.concat('&version=' + $(this).text()); + } else { + var url = 'index.html?version=' + $(this).text(); + } + history.pushState(null, null, url); + } else { + history.pushState(null, null, 'index.html' + window.location.search.replace(urlParams.get('version'), $(this).text())); + } + } else if ($(this).hasClass("platforms")) { + history.pushState(null, null, 'index.html' + window.location.search.replace('='+urlParams.get('platform'), '='+label($(this).text()))); + } else if ($(this).hasClass("languages")) { + history.pushState(null, null, 'index.html' + window.location.search.replace('='+urlParams.get('language'), '='+label($(this).text()))); + } else if ($(this).hasClass("processors")) { + history.pushState(null, null, 'index.html' + window.location.search.replace('='+urlParams.get('processor'), '='+label($(this).text()))); + } else if ($(this).hasClass("environs")) { + history.pushState(null, null, 'index.html' + window.location.search.replace('='+urlParams.get('environ'), '='+label($(this).text()))); + } + + showContent(); + } + + $('.opt-group').on('click', '.opt', setContent); + $('.install-widget').css("visibility", "visible"); + $('.install-content').css("visibility", "visible"); + +}); diff --git a/docs/static_site/src/assets/main.scss b/docs/static_site/src/assets/main.scss new file mode 100644 index 000000000000..3ab5c7091e68 --- /dev/null +++ b/docs/static_site/src/assets/main.scss @@ -0,0 +1,8 @@ +--- +--- +body { + background-image: url("{{'/assets/img/mxnet-background-compressed.jpeg' | relative_url}}"); +} + + +@import "minima"; diff --git a/docs/static_site/src/assets/minima-social-icons.svg b/docs/static_site/src/assets/minima-social-icons.svg new file mode 100644 index 000000000000..fa7399fe2509 --- /dev/null +++ b/docs/static_site/src/assets/minima-social-icons.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static_site/src/index.html b/docs/static_site/src/index.html new file mode 100644 index 000000000000..9f5460c6ff9a --- /dev/null +++ b/docs/static_site/src/index.html @@ -0,0 +1,54 @@ +--- +# Feel free to add content and custom Front Matter to this file. +# To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults + +layout: home + +key_features: +- title: Hybrid Front-End + text: A hybrid front-end seamlessly transitions between Gluon eager imperative mode and symbolic mode to provide both flexibility and speed. + icon: /assets/img/circuit.svg +- title: Distributed Training + text: Scalable distributed training and performance optimization in research and production is enabled by the dual Parameter Server and Horovod support. + icon: /assets/img/algorithm.svg +- title: 8 Language Bindings + text: Deep integration into Python and support for Scala, Julia, Clojure, Java, C++, R and Perl. + icon: /assets/img/programming.svg +- title: Tools & Libraries + text: A thriving ecosystem of tools and libraries extends MXNet and enable use-cases in computer vision, NLP, time series and more. + icon: /assets/img/chip.svg + +ecosystem: +- title: D2L.ai + text: An interactive deep learning book with code, math, and discussions. Used at Berkeley, University of Washington and more. + icon: /assets/img/textbook.svg + link: https://d2l.ai +- title: GluonCV + text: GluonCV is a computer vision toolkit with rich model zoo. From object detection to pose estimation. + icon: /assets/img/visual.svg + link: https://gluon-cv.mxnet.io +- title: GluonNLP + text: GluonNLP provides state-of-the-art deep learning models in NLP. For engineers and researchers to fast prototype research ideas and products. + icon: /assets/img/artificial-intelligence.svg + link: https://gluon-nlp.mxnet.io/ +- title: GluonTS + text: Gluon Time Series (GluonTS) is the Gluon toolkit for probabilistic time series modeling, focusing on deep learning-based models. + icon: /assets/img/line-graph.svg + link: https://gluon-ts.mxnet.io/ + + +community: +- title: GitHub + text: Report bugs, request features, discuss issues, and more. + icon: /assets/img/octocat.png + link: https://github.com/apache/incubator-mxnet +- title: Discuss Forum + text: Browse and join discussions on deep learning with MXNet and Gluon. + icon: /assets/img/mxnet_m.png + link: https://discuss.mxnet.io/ +- title: Slack + text: Discuss advanced topics. Request access by mail dev@mxnet.apache.org + icon: /assets/img/slack-logo-icon.svg + link: mailto:dev@mxnet.apache.org + +--- \ No newline at end of file diff --git a/docs/static_site/src/pages/api/api.html b/docs/static_site/src/pages/api/api.html new file mode 100644 index 000000000000..098efda5e605 --- /dev/null +++ b/docs/static_site/src/pages/api/api.html @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: Docs +subtitle: Documentation for the supported language bindings +action: Get Started +action_url: /get_started +permalink: /api/ +tag: main_docs + +faq_categories: +- Deployment Environments +- Model +- Speed +- Security +- Extend and Contribute to MXNet + +docs: +- title: Python + guide_link: /api/python.html + api_link: /api/python/docs/api + tutorial_link: /api/python/docs/tutorials + icon: /assets/img/python_logo.svg + tag: python +- title: Scala + guide_link: /api/scala.html + api_link: /api/scala/docs/api + tutorial_link: /api/scala/docs/tutorials + description: + icon: /assets/img/scala_logo.svg + tag: scala +- title: Java + guide_link: /api/java.html + api_link: /api/java/docs/api + tutorial_link: /api/java/docs/tutorials + description: + icon: /assets/img/java_logo.svg + tag: java +- title: Clojure + guide_link: /api/clojure + api_link: /api/clojure/docs/api + tutorial_link: /api/clojure/docs/tutorials + description: + icon: /assets/img/clojure_logo.svg + tag: clojure +- title: C/C++ + guide_link: /api/cpp + api_link: /api/cpp/docs/api + tutorial_link: /api/cpp/docs/tutorials + description: + icon: /assets/img/cpp_logo.svg + tag: cpp +- title: Julia + guide_link: /api/julia + api_link: /api/julia/docs/api + tutorial_link: https://github.com/apache/incubator-mxnet/tree/master/julia/examples + description: + icon: /assets/img/julia_logo.svg + tag: julia +- title: R + guide_link: /api/r + api_link: https://s3.amazonaws.com/mxnet-prod/docs/R/mxnet-r-reference-manual.pdf + tutorial_link: /api/r/docs/tutorials + description: + icon: /assets/img/R_logo.svg + tag: r +- title: Perl + guide_link: /api/perl + api_link: https://metacpan.org/release/AI-MXNet + tutorial_link: /api/perl/docs/tutorials + description: + icon: /assets/img/perl_logo.svg + tag: perl +--- +{%- for doc in page.docs -%} + {%- if doc.tag == 'python' -%} +

Python API

+
+ + +
+

Python-first API

+

MXNet provides a comprehensive and flexible Python API to serve a broad community of developers with different levels of experience and wide ranging requirements. Current efforts are focused on the + Gluon API. Gluon provides a clear, concise, and simple API for deep learning. It makes it easy to prototype, build, and train deep learning models without sacrificing training speed.

+

You can checkout the rich ecosystem built around Apache MXNet Gluon, including D2L.ai, GluonCV, + GluonNLP and GluonTS.

+

While most of the usability improvement around training are focused on the python API, the performance of MXNet is accessible through a variety of different language bindings, checkout their respective API and guides below!

+
+ +
+ {%- endif -%} +{%- endfor -%} +

Other Bindings

+
+{%- for doc in page.docs -%} + {%- if doc.tag != 'python' -%} + + {%- endif -%} +{%- endfor -%} +
+

^l8 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

+
+
+

MXNet Architecture

+

+ Building a high-performance deep learning library + requires many systems-level design decisions. + In this design note, we share the rationale + for the specific choices made when designing _MXNet_. + We imagine that these insights may be useful + to both deep learning practitioners + and builders of other deep learning systems. +

+

Deep Learning System Design Concepts

+

+ The following pages address general design concepts for deep learning systems. + Mainly, they focus on the following 3 areas: + abstraction, optimization, and trade-offs between efficiency and flexibility. + Additionally, we provide an overview of the complete MXNet system. +

+
    + {%- for p in site.pages -%} + {%- if p.category == 'architecture' -%} +
  • {{p.title}}
  • + {%- endif -%} + {%- endfor -%} +
+
+
+
+
+

FAQ

+
    + {%- for faq_c in page.faq_categories -%} +

    {{faq_c}}

    + {%- for p in site.pages -%} + {%- if p.faq_c == faq_c -%} +
  • {{p.question}}
  • + {%- endif -%} + {%- endfor -%} +
    + {%- endfor -%} +
+
+
+
diff --git a/docs/static_site/src/pages/api/architecture/exception_handling.md b/docs/static_site/src/pages/api/architecture/exception_handling.md new file mode 100644 index 000000000000..b985b27b285e --- /dev/null +++ b/docs/static_site/src/pages/api/architecture/exception_handling.md @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + +--- +layout: page_category +title: Exception Handling in MXNet +category: architecture +permalink: /api/architecture/exception_handling +--- + +# Exception Handling in MXNet + +This tutorial explains the exception handling support in MXNet, +and provides examples on how to throw and handle exceptions when in a multithreaded context. +Although, the examples are in Python, they can be easily extended to MXNet +language bindings. + +MXNet exceptions can be thrown from two areas: +- MXNet main thread. For eg. Infershape and InferType. +- Spawned threads: + * By dependency engine for operator execution in parallel + * By the iterators, during the data loading, text parsing phase etc. + +In the first case, the exception is thrown and can be handled in the main thread. +In the second case, the exception is thrown in a spawned thread, caught and transported to the +main thread, where it is rethrown. This tutorial will give more explanation and examples on how +to handle exceptions for the second case. + +## Prerequisites + +To complete this tutorial, we need: +- MXNet [7b24137](https://github.com/apache/incubator-mxnet/commit/7b24137ed45df605defa4ce72ec91554f6e445f0). See Instructions in [Setup and Installation](http://mxnet.io/install/index.html). + +## Exception Handling for Iterators + +The below example shows how to handle exceptions for iterators. In this example, +we populate files for data and labels with fewer number of labels compared to the +number of samples. This should throw an exception. + +CSVIter uses PrefetcherIter for loading and parsing data. +The PrefetcherIter spawns a producer thread in the background which prefetches +the data while the main thread consumes the data. The exception is thrown in the spawned +producer thread during the prefetching, when the label is not found corresponding to a specific sample. + +The exception is transported to the main thread, where it is rethrown when Next is +called as part of the following line: `for batch in iter(data_train)`. + +In general, Exception may be rethrown as part of `Next` and `BeforeFirst` calls which correspond to `reset()` and `next()` methods in `MXDataIter` for Python language bindings. + +```python +import os +import mxnet as mx + +cwd = os.getcwd() +data_path = os.path.join(cwd, "data.csv") +label_path = os.path.join(cwd, "label.csv") + +with open(data_path, "w") as fout: + for i in range(8): + fout.write("1,2,3,4,5,6,7,8,9,10\n") + +with open(label_path, "w") as fout: + for i in range(7): + fout.write("label"+str(i)) + +try: + data_train = mx.io.CSVIter(data_csv=data_path, label_csv=label_path, data_shape=(1, 10), + batch_size=4) + + for batch in iter(data_train): + print(data_train.getdata().asnumpy()) +except mx.base.MXNetError as ex: + print("Exception handled") + print(ex) +``` + +### Limitation + +There is a race condition when your last `next()` call doesnt reach the batch in your dataset where exception occurs. Exception may or may not be thrown in this case depending on which thread wins the race. To avoid this situation, you should try and iterate through your full dataset if you think it can throw exceptions which need to be handled. + + +## Exception Handling for Operators + +The below example shows how to handle exceptions for operators in the imperative mode. + +For the operator case, the dependency engine spawns a number of threads if it is running in the `ThreadedEnginePool` or `ThreadedEnginePerDevice` mode. The final operator is executed in one of the spawned threads. + +If an operator throws an exception during execution, this exception is propagated +down the dependency chain. Once there is a synchronizing call i.e. WaitToRead for a variable in the dependency chain, the propagated exception is rethrown. + +In the below example, I illustrate how an exception that occured in the first line is propagated down the dependency chain, and finally is rethrown when we make a synchronizing call to WaitToRead. + +```python +import mxnet as mx +a = mx.nd.random.normal(0, 1, (2, 2)) +b = mx.nd.random.normal(0, 2, (2, 2)) +c = mx.nd.dot(a, b) +d = mx.nd.random.normal(0, -1, (2, 2)) +e = mx.nd.dot(c, d) +e.wait_to_read() +``` + +Although the above exception occurs when executing the operation which writes to the variable d in one of the child threads, it is thrown only when the synchronization happens as part of the line: `e.wait_to_read()`. + +Let us take another example. In the following case, we write to two variables and then `wait_to_read` for both. This example shows that any particular exception will not be thrown more than once. + +```python +import mxnet as mx +a = mx.nd.random.normal(0, 1, (2, 2)) +b = mx.nd.random.normal(0, -1, (2, 2)) +c, d = mx.nd.dot(a, b) +try: + c.asnumpy() +except mx.base.MXNetError as ex: + print("Exception handled") +d.asnumpy() +``` diff --git a/docs/static_site/src/pages/api/architecture/note_data_loading.md b/docs/static_site/src/pages/api/architecture/note_data_loading.md new file mode 100644 index 000000000000..63d90024a80c --- /dev/null +++ b/docs/static_site/src/pages/api/architecture/note_data_loading.md @@ -0,0 +1,267 @@ + + + + + + + + + + + + + + + + +--- +layout: page_category +title: Efficient Data Loaders +category: architecture +permalink: /api/architecture/note_data_loading +--- + +# Designing Efficient Data Loaders for Deep Learning + +Data loading is an important component of any machine learning system. +When we work with tiny datasets, we can get away with loading an entire dataset into GPU memory. +With larger datasets, we must store examples in main memory. +And when datasets grow too large to fit into main memory, +data loading can become performance-critical. +In designing a data loader, +we aim to achieve more efficient data loading, +to spend less effort on data preparation, +and to present a clean and flexible interface. + +We organize this design note as follows: + +* **IO Design Insight:** Guiding principles in data loading design. +* **Data Format:** Our solution using dmlc-core's binary recordIO implementation. +* **Data Loading:** Our method to reduce IO cost by utilizing the threaded iterator provided by dmlc-core. +* **Interface Design:** Our approach to facilitate writing MXNet data iterators in just a few lines of Python. +* **Future Extension:** Prospective ideas for making data loading more flexible. + +Our analysis will motivate several requirements that an effective IO system should fulfill. + +***List of Key Requirements*** +- Small file size. +- Parallel (distributed) packing of data. +- Fast data loading and online augmentation. +- Quick reads from arbitrary parts of the dataset in the distributed setting. + +## Design Insight +To design an IO system, we must address two kinds of tasks: +data preparation and data loading. +Data preparation is usually performed offline, +whereas data loading influences the online performance. +In this section, we will introduce our insight of IO design involving the two phases. + +### Data Preparation +Data preparation describes the process of packing data +into a desired format for later processing. +When working with large datasets like ImageNet, this process can be time-consuming. +In these cases, there are several heuristics we ought to follow: + +- Pack the dataset into small numbers of files. A dataset may contain millions of data instances. Packed data distributes easily from machine to machine. +- Do the packing once. We don't want to repack data every time run-time settings, like the number of machines, are changed. +- Process the packing in parallel to save time. +- Be able to access arbitrary parts of the data easily. This is crucial for distributed machine learning when data parallelism is introduced. Things may get tricky when the data has been packed into several physical data files. The desired behavior could be: the packed data can be logically separated into arbitrary numbers of partitions, no matter how many physical data files there are. For example, if we pack 1000 images into 4 physical files, then each file contains 250 images. If we then use 10 machines to train a DNN, we should be able to load approximately 100 images per machine. Some machines may need images from different physical files. + +### Data Loading +The next step to consider is how to load the packed data into RAM. +Our goal is to load the data as quickly as possible. +There are several heuristics we try to follow: +- **Read continuously:** We can read faster when reading from contiguous locations on disk. +- **Reduce the bytes to be loaded:** We can achieve this by storing data in a compact way, e.g. saving images in JPEG format. +- **Load and train in different threads:** This avoids computational bottlenecks while loading data. +- **Save RAM:** Judiciously decide whether to load entire files into RAM. + +## Data Format + +Since the training of deep neural network often involves large amounts of data, +the format we choose should be both efficient and convenient. +To achieve our goals, we need to pack binary data into a splittable format. +In MXNet, we rely on the binary recordIO format implemented in dmlc-core. + +### Binary Record + +![baserecordio](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/io/baserecordio.jpg) +In MXNet's binary RecordIO, we store each data instance as a record. +**kMagic** is a *magic number* indicating the start of a record. +**Lrecord** encodes length and a continue flag. +In lrecord, +- cflag == 0: this is a complete record +- cflag == 1: start of a multiple-records +- cflag == 2: middle of multiple-records +- cflag == 3: end of multiple-records + +**Data** is the space to save data content. +**Pad** is simply a padding space to make record align to 4 bytes. + +After we pack the data, each file contains multiple records. +Then, loading can be continuous. +This avoids the low performance that can result +from reading random locations on disk. + +One advantage of storing data via records +is that each record can vary in length. +This allows us to save data compactly +when good compression algorithms are available for our data. +For example, we can use JPEG format to save image data. +The packed data will be much smaller +compared with storing uncompressed RGB values for each pixel. + +Take ImageNet_1K dataset as an example. +If we store the data as 3 * 256 * 256 array of raw RGB values, +the dataset would occupy more than **200G**. +But after compressing the images using JPEG, +they only occupy about **35G** of disk space. +This significantly reduces the cost owing to reading from disk. + +Here's an example of binary recordIO: +![baserecordio](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/io/ImageRecordIO.jpg) +We first resize the image into 256 * 256, +then compress into JPEG format. +After that, we save a header that indicates the index and label +for that image to be used when constructing the *Data* field for that record. +We then pack several images together into a file. +You may want to also review the [example using im2rec.py to create a RecordIO dataset](https://mxnet.incubator.apache.org/tutorials/basic/data.html#loading-data-using-image-iterators). + +### Access Arbitrary Parts Of Data + +One desirable property for a data loader might be: +The packed data can be logically sliced into an arbitrary number of partitions, +no matter how many physical packed data files there are. +Since binary recordIO can easily locate +the start and end of a record using the Magic Number, +we can achieve the above goal using the InputSplit +functionality provided by dmlc-core. + +InputSplit takes the following parameters: +- FileSystem *filesys*: dmlc-core wrapper around the IO operations for different file systems, like hdfs, s3, local. User shouldn't need to worry about the difference between file systems anymore. +- Char *uri*: The URI of files. Note that it could be a list of files because we may pack the data into several physical parts. File URIs are separated by ';'. +- Unsigned *nsplit*: The number of logical splits. *nsplit* could be different from the number of physical files. +- Unsigned *rank*: Which split to load in this process. + +The splitting process is demonstrated below: +- Determine the size of each partition. + +![beforepartition](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/io/beforepartition.jpg) + +- Approximately partition the records according to file size. Note that the boundary of each part may be located in the middle of a record. + +![approxipartition](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/io/approximatepartition.jpg) + +- Set the beginning of partitions in such a way as to avoid splitting records across partitions. + +![afterpartition](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/io/afterpartition.jpg) + +By conducting the above operations, +we now identify the records belong to each part, +and the physical data files needed by each logical part. +InputSplit greatly simplifies data parallelism, +where each process only reads part of the data. + +Since our partitioning scheme does not depend on the number of physical data files, +we can process a huge dataset like ImageNet_22K in parallel fashion as illustrated below. +We don't need to consider distributed loading issue at the preparation time, +just select the most efficient physical file number +according to the dataset size and computing resources available. +![parallelprepare](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/io/parallelprepare.jpg) + +## Data Loading and Preprocessing + +When the speed of loading and preprocessing can't keep up +with the speed of training or evaluation, +IO can bottleneck the speed of the whole system. +In this section, we will introduce a few tricks +to achieve greater efficiency when loading +and preprocessing data packed in binary recordIO format. +When applied to the ImageNet dataset, our approach achieves +the IO speed of **3000** images/sec **with a normal HDD**. + +### Loading and preprocessing on the fly + +When training deep neural networks, +we sometimes must load and preprocess the data +while simultaneously training for the following reasons: +- When the whole size of the dataset exceeds available RAM size, we can't load it in advance; +- Sometimes, to make models robust to things like translations, rotations, and small amounts of color shift of noise, we introduce randomness into the training process. In these cases we must re-preprocess the data each time we revisit an example. + +In service of efficiency, we also address multi-threading techniques. Taking Imagenet training as an example, after loading a bunch of image records, we can start multiple threads to simultaneously perform image decoding and image augmentation. We depict this process in the following illustration: +![process](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/io/process.jpg) + +### Hide IO Cost Using Threadediter + +One way to lower IO cost is to pre-fetch the data for next batch on one thread, +while the main thread performs the forward and backward passes for training. +To support more complicated training schemes, +MXNet provides a more general IO processing pipeline +using *threadediter* provided by dmlc-core. +The key of *threadediter* is to start a stand-alone thread that acts as a data provider, +while the main thread acts as a data consumer as illustrated below. + +The threadediter maintains a buffer of a certain size +and automatically fills the buffer when it's not full. +And after the consumer finishes consuming part of the data in the buffer, +threadediter will reuse the space to save the next part of data. +![threadediter](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/io/threadediter.png) + +## MXNet IO Python Interface +We make the IO object as an iterator in numpy. +By achieving that, the user can easily access the data +using a for-loop or calling next() function. +Defining a data iterator is very similar to defining a symbolic operator in MXNet. + +The following example code demonstrates a Cifar data iterator. + +```python +dataiter = mx.io.ImageRecordIter( + # Dataset Parameter, indicating the data file, please check the data is already there + path_imgrec="data/cifar/train.rec", + # Dataset Parameter, indicating the image size after preprocessing + data_shape=(3,28,28), + # Batch Parameter, tells how many images in a batch + batch_size=100, + # Augmentation Parameter, when offers mean_img, each image will subtract the mean value at each pixel + mean_img="data/cifar/cifar10_mean.bin", + # Augmentation Parameter, randomly crop a patch of the data_shape from the original image + rand_crop=True, + # Augmentation Parameter, randomly mirror the image horizontally + rand_mirror=True, + # Augmentation Parameter, randomly shuffle the data + shuffle=False, + # Backend Parameter, preprocessing thread number + preprocess_threads=4, + # Backend Parameter, prefetch buffer size + prefetch_buffer=1, + # Optional, the device context which data loader optimized for, could be 'gpu' or 'cpu' + ctx="gpu", + # The out data type, could be 'float32' 'int8' or 'uint8' + dtype="float32") +``` + +Generally, to create a data iterator, you need to provide five kinds of parameters: + +* **Dataset Param:** Information needed to access the dataset, e.g. file path, input shape. +* **Batch Param:** Specifies how to form a batch, e.g. batch size. +* **Augmentation Param:** Which augmentation operations (e.g. crop, mirror) should be taken on an input image. +* **Backend Param:** Controls the behavior of the backend threads to hide data loading cost. +* **Auxiliary Param:** Provides options to help with debugging. + +Usually, **Dataset Param** and **Batch Param** MUST be given, +otherwise the data batch can't be created. +Other parameters can be given as needed. +Ideally, we should separate the MX Data IO into modules, +some of which might be useful to expose to users, for example: + +* **Efficient prefetcher:** allows the user to write a data loader that reads their customized binary format that automatically gets multi-threaded prefetcher support. +* **Data transformer:** image random cropping, mirroring, etc. Allows the users to use those tools, or plug in their own customized transformers (maybe they want to add some specific kind of coherent random noise to data, etc.) + +## Future Extensions + +In the future, there are some extensions to our data IO +that we might consider adding. +Specifically, we might add specialized support +for applications including image segmentation, object localization, and speech recognition. +More detail will be provided when such applications have been running on MXNet. diff --git a/docs/static_site/src/pages/api/architecture/note_engine.md b/docs/static_site/src/pages/api/architecture/note_engine.md new file mode 100644 index 000000000000..cb626649e5ff --- /dev/null +++ b/docs/static_site/src/pages/api/architecture/note_engine.md @@ -0,0 +1,391 @@ + + + + + + + + + + + + + + + + +--- +layout: page_category +title: Dependency Engine +category: architecture +permalink: /api/architecture/note_engine +--- + +# Dependency Engine for Deep Learning + +We always want deep learning libraries +to run faster and scale to larger datasets. +One natural approach is to see if we can benefit +from throwing more hardware at the problem, +as by using multiple GPUs simultaneously. + +Library designers then ask: +How can we *parallelize* computation across devices? +And, more importantly, how can we *synchronize* computation +when we introduce multi-threading? +A runtime dependency engine is a generic solution to these problems. + +In this document, we examine approaches for using +runtime dependency scheduling to accelerate deep learning. +We aim to explain how runtime dependency scheduling +can both speed up and simplify multi-device deep learning. +We also explore potential designs for a generic dependency engine +that could be both library- and operation-independent. + +Most of the discussion of on this page draws inspiration +from the MXNet dependency engine. +The dependency tracking algorithm we discuss +was primarily developed by [Yutian Li](https://github.com/hotpxl) +and [Mingjie Wang](https://github.com/jermainewang). + +## Dependency Scheduling + +Although most users want to take advantage of parallel computation, +most of us are more familiar with serial programs. +So one natural question is: how can we write serial programs +and build a library to automatically parallelize our programs +in an asynchronous way? + +For example, in the following code, we can run `B = A + 1` +and `C = A + 2` in any order, or in parallel: + +```python + A = 2 + B = A + 1 + C = A + 2 + D = B * C +``` + +However, it's quite hard to code the sequence manually +because the last operation, `D = B * C`, needs to wait +for both of the preceding operations to complete before it starts. +The following dependency graph/data flow graph illustrates this. + +![Dep Simple](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/engine/dep_simple.png) + + +A dependency engine is a library that takes a sequence of operations +and schedules them according to the dependency pattern, potentially in parallel. +So in this example, a dependency library +could run ```B = A + 1``` and ```C = A + 2``` in parallel, +and run ```D = B * C``` after those operations complete. + +## Problems in Dependency Scheduling + +A dependency engine relieves the burden of writing concurrent programs. +However, as operations become parallelized, +new dependency tracking problems arise. +In this section, we discuss those problems. + +### Data Flow Dependency +Data flow dependency describes how the outcome of one computation +can be used in other computations. +Every dependency engine has to solve the data flow dependency problem. + +![Dep Simple](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/engine/dep_simple.png) + +Because we discussed this issue in the preceding section, +we include the same figure here. Libraries that have +data flow tracking engines include Minerva and Purine2. + +### Memory Recycling +When should we recycle the memory that we allocated to the arrays? +In serial processing, this is easy to determine. +We simply recycle the memory after the variable goes out of scope. +However, as the following figure shows, this is a bit harder in parallel processing. + +![Dep Del](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/engine/dep_del.png) + +In this example, because both computations need to use values from A, +we can't recycle the memory until both complete. +The engine must schedule the memory recycling operations according to the dependencies, +and ensure that they are executed after both ```B = A + 1``` and ```C = A + 2``` complete. + + +### Random Number Generation +Random number generators, which are commonly used in machine learning, +pose interesting challenges for dependency engines. +Consider the following example: + +![Dep Rand](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/engine/dep_rand.png) + +In this example, we are generating random numbers in a sequence. +Although it seems that the two random number generations can be parallelized, +this is usually not the case. A pseudo-random number generator (PRNG) +is not thread-safe because it might cause some internal state +to mutate when generating a new number. +Even if the PRNG is thread-safe, +it is preferable to serialize number generation, +so we can get reproducible random numbers. + +## Case Study: A Dependency Engine for a Multi-GPU Neural Network + +In the last section, we discussed the problems +we might face in designing a dependency engine. +Before thinking about how to design a generic engine to solve those problems, +let's consider how a dependency engine can help in multi-GPU training of a neural network. +The following pseudocode Python program illustrates +training one batch on a two-layer neural network. + +```python + # Example of one iteration Two GPU neural Net + data = next_batch() + data[gpu0].copyfrom(data[0:50]) + data[gpu1].copyfrom(data[50:100]) + # forward, backprop on GPU 0 + fc1[gpu0] = FullcForward(data[gpu0], fc1_weight[gpu0]) + fc2[gpu0] = FullcForward(fc1[gpu0], fc2_weight[gpu0]) + fc2_ograd[gpu0] = LossGrad(fc2[gpu0], label[0:50]) + fc1_ograd[gpu0], fc2_wgrad[gpu0] = + FullcBackward(fc2_ograd[gpu0] , fc2_weight[gpu0]) + _, fc1_wgrad[gpu0] = FullcBackward(fc1_ograd[gpu0] , fc1_weight[gpu0]) + # forward, backprop on GPU 1 + fc1[gpu1] = FullcForward(data[gpu1], fc1_weight[gpu1]) + fc2[gpu1] = FullcForward(fc1[gpu1], fc2_weight[gpu1]) + fc2_ograd[gpu1] = LossGrad(fc2[gpu1], label[50:100]) + fc1_ograd[gpu1], fc2_wgrad[gpu1] = + FullcBackward(fc2_ograd[gpu1] , fc2_weight[gpu1]) + _, fc1_wgrad[gpu1] = FullcBackward(fc1_ograd[gpu1] , fc1_weight[gpu1]) + # aggregate gradient and update + fc1_wgrad[cpu] = fc1_wgrad[gpu0] + fc1_wgrad[gpu1] + fc2_wgrad[cpu] = fc2_wgrad[gpu0] + fc2_wgrad[gpu1] + fc1_weight[cpu] -= lr * fc1_wgrad[cpu] + fc2_weight[cpu] -= lr * fc2_wgrad[cpu] + fc1_weight[cpu].copyto(fc1_weight[gpu0] , fc1_weight[gpu1]) + fc2_weight[cpu].copyto(fc2_weight[gpu0] , fc2_weight[gpu1]) +``` +In this program, the data 0 to 50 is copied to GPU 0, +and the data 50 to 100 is copied to GPU 1. +The calculated gradients are aggregated in the CPU, +which then performs a simple SGD update, +and copies the updated weight back to each GPU. +This is a common way to write a parallel program in a serial manner. +The following dependency graph shows how it can be parallelized: + +![Dep Net](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/engine/dep_net.png) + +***Notes:*** + +- The gradient can be copied to the CPU as soon as we get the gradient of a layer. +- The weight can be copied back soon as the weight is updated. +- In the forward pass, we have a dependency on ```fc1_weight[cpu].copyto(fc1_weight[gpu0] , fc1_weight[gpu1])``` + from the previous iteration. +- There is a delay in computation between the last backward pass to layer k and the next forward call to layer k. We can synchronize the weight of layer k *in parallel* with other computation during this delay. + +This approach to optimization is used by multi-GPU deep learning libraries, such as CXXNet. +The point is to overlap weight synchronization (communication) with computation. +However, it's not easy to do that, because the copy operation needs to be triggered +as soon as the backward pass of the layer completes, +which then triggers the reduction, updates, etc. + +A dependency engine can schedule these operations and perform multi-threading +and dependency tracking. + +## Designing a Generic Dependency Engine + +We hope that you're convinced that a dependency engine is useful +for scaling deep learning programs to multiple devices. +Now let's discuss how we can design and implement +a generic interface for a dependency engine. +This solution isn't the only possible design for a dependency engine. +It's an example that we think is useful in most cases. + +Our goal is to create a dependency engine that is *generic* and *lightweight*. +Ideally, we'd like the engine that easily plugs into existing deep learning code, +and that can scale up to multiple machines with minor modifications. +To do that, we need to focus only on dependency tracking, +not on assumptions about what users can or can't do. + +Here's a summary of goals for the engine: + +- The engine should not be aware of what operations it performs, so that users can perform any operations they define. +- It should not be restricted in what type of objects it can schedule. + - We should be able to schedule dependencies on GPU and CPU memory. + - We should be able to track dependencies on the random number generator, etc. +- The engine should not allocate resources. It should only track dependencies. Users can allocate their own memory, PRNG, etc. + +The following Python snippet provides an engine interface that might help us reach our goal. Note that a real implementation will be closer to the metal, typically in C++. + +```python + class DepEngine(object): + def new_variable(): + """Return a new variable tag + Returns + ------- + vtag : Variable Tag + The token of the engine to represent dependencies. + """ + pass + + def push(exec_func, read_vars, mutate_vars): + """Push the operation to the engine. + + Parameters + ---------- + exec_func : callable + The real operation to be performed. + + read_vars : list of Variable Tags + The list of variables this operation will read from. + + mutate_vars : list of Variable Tags + The list of variables this operation will mutate. + """ + pass +``` + +Because we can't make assumptions about what objects we are scheduling, we ask the user to allocate a +_virtual tag_ that is associated with each object to represent what we need to schedule. +So, at the beginning, the user can allocate the variable tag, +and attach it to each of the objects that we want to schedule. + +![Dep Net](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/engine/tag_var.png) + +The user then calls `push` to tell the engine about the function to execute. +The user also needs to specify the dependencies of the operation, +using `read_vars` and `write_vars`: + +- `read_vars` are variable tags for objects that the operation will _read from_, without changing their internal state. +- `mutate_vars` are variable tags for objects whose internal states the operation will mutate. + +![Push Op](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/engine/push_var.png) + +The preceding figure shows how to push operation `B = A + 1` to the dependency engine. `B.data` and +`A.data` are the allocated space. Note that the engine is *only aware of variable tags*. +Any execution function can be processed. +This interface is generic for the operations and resources we want to schedule. + +For fun, let's look at how the engine internals work with the tags by considering the following code snippet: + +``` + B = A + 1 + C = A + 2 + A = C * 2 + D = A + 3 +``` + +The first line reads variable `A` and mutates variable `B`. The second line reads variable `A` and mutates variable `C`. And so on. + +The engine maintains a queue for each variable, as the following animation shows for each of the four lines. Green blocks represents a read action, while red blocks represent mutations. + +![Dependency Queue](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/engine/dep_queue.gif) + +Upon building this queue, the engine sees that the first two green blocks at the beginning of `A`'s queue could actually be run in parallel because they are both read actions and won't conflict with each other. The following graph illustrates this point. + +![Dependency Parallelism](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/engine/dep_parallel.png) + +One cool thing about all this scheduling is that it's not confined to numerical calculations. +Because everything that is scheduled is only a tag, the engine could schedule everything! + +The following figure gives a complete push sequence of the programs we mentioned in previous sections. + +![Push Seq](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/engine/push_seq.png) + +### Porting Existing Code to the Dependency Engine +Because the generic interface doesn't control things like memory allocation and which operation to execute, +most existing code can be scheduled by the dependency engine in two steps: + + +1. Allocate the variable tags associated with resources like memory blob, PRNGS. +2. Call `push` with the execution function as the original code to execute, and put the variable tags of + corresponding resources correctly in `read_vars` and `mutate_vars`. + +## Implementing the Generic Dependency Engine + +We have described the generic engine interface and +how it can be used to schedule various operations. +In this section, we provide a high-level discussion +of how to implement such an engine. + +The general idea is as follows: + +- Use a queue to track all of the pending dependencies on each variable tag. +- Use a counter on each operation to track how many dependencies are yet to be fulfilled. +- When operations are completed, update the state of the queue and dependency counters to schedule new operations. + +The following figure illustrates the scheduling algorithm +and might give you a better sense of what is going on in the engine. + +![Dep Tracking](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/engine/engine_queue_step.png) + +Below, we show another example involving random number generators. + +![Dep Rand](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/engine/engine_queue_rand.png) + +As you can see, the purpose of the algorithm is to update pending queues +of operations and to make the right state transition when an operation has completed. +More care should be taken to make sure the state transitions +are done in a way that's safe for threads. + +### Separate Dependency Tracking with Running Policy +If you're reading carefully, you might have noticed +that the preceding section shows only the algorithm +for deciding when an operation can be executed. +We didn't show how to actually run an operation. +In practice, there can be many different policies. +For example, we can either use a global thread-pool to run all operations, +or use a specific thread to run operations on each device. + +This running policy is usually independent of dependency tracking, +and can be separated out as either an independent module +or a virtual interface of base-dependency tracking modules. +Developing an elegant runtime policy that is fair +to all operations and schedules is an interesting systems problem itself. + +## Discussion + +The design that we discussed in this article +isn't the only solution to the dependency tracking problem. +It's just one example of how we might approach this. +To be sure, some of these design choices are debatable. +We'll discuss some of them in this section. + +### Dynamic vs. Static +The dependency engine interface discussed in this topic is somewhat dynamic +in the sense that the user can push operations one by one, +instead of declaring the entire dependency graph (static). +Dynamic scheduling can require more overhead +than static declarations, in terms of data structure. +However, it also enables more flexibility, such as supporting auto parallelism +for imperative programs or a mixture of imperative and symbolic programs. +You can also add some level of predeclared operations +to the interface to enable data structure reuse. + +### Mutation vs. Immutable +The generic engine interface presented in this page +supports explicit scheduling for mutation. +In a typical data flow engine, the data are usually immutable. +Working with immutable data has a lot of benefits. +For example, immutable data is generally more suitable for parallelization, +and facilitates better fault tolerance in a distributed setting (by way of re-computation). + +However, immutability presents several challenges: + +- It's harder to schedule resource contention problems, as arise when dealing with random numbers and deletion. +- The engine usually needs to manage resources (memory, random number) to avoid conflicts. It's harder to plug in user-allocated space, etc. +- Preallocated static memory isn't available, again because the usual pattern is to write to a preallocated layer space, which is not supported if data is immutable. + +Allowing mutation mitigates these issues. + + +## Source Code of the Generic Dependency Engine +[MXNet](https://github.com/dmlc/mxnet) provides an implementation +of the generic dependency engine described in this page. +You can find more details in [this topic](http://mxnet.io/architecture/note_engine.html). +We welcome your contributions. + +## Next Steps + +* [Squeeze the Memory Consumption of Deep Learning](http://mxnet.io/architecture/note_memory.html) +* [Efficient Data Loading Module for Deep Learning](http://mxnet.io/architecture/note_data_loading.html) +* [Survey of RNN Interface](http://mxnet.io/architecture/rnn_interface.html) diff --git a/docs/static_site/src/pages/api/architecture/note_memory.md b/docs/static_site/src/pages/api/architecture/note_memory.md new file mode 100644 index 000000000000..771d6a5ff012 --- /dev/null +++ b/docs/static_site/src/pages/api/architecture/note_memory.md @@ -0,0 +1,351 @@ + + + + + + + + + + + + + + + + +--- +layout: page_category +title: Memory Consumption +category: architecture +permalink: /api/architecture/note_memory +--- + +# Optimizing Memory Consumption in Deep Learning + +Over the last ten years, a constant trend in deep learning +is towards deeper and larger networks. +Despite rapid advances in hardware performance, +cutting-edge deep learning models continue to push the limits of GPU RAM. +So even today, it's always desirable to find ways +to train larger models while consuming less memory. +Doing so enables us to train faster, using larger batch sizes, +and consequently achieving a higher GPU utilization rate. + +In this document, we explore techniques for optimizing +memory allocation for deep neural networks. +We discuss a few candidate solutions. +While our proposals are by no means exhaustive, +these solutions are instructive and allow us to +introduce the major design issues at play. + +## Computation Graph + +First, let's revisit the idea of the computation graph. +A computation graph describes the (data flow) dependencies +between the operations in the deep network. +The operations performed in the graph +can be either fine-grained or coarse-grained. +The following figure shows two examples of computation graphs. + +![Comp Graph Example](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/comp_graph_example.png) + +The concept of a computation graph is explicitly encoded in packages like Theano and CGT. +In other libraries, computation graphs appear implicitly as network configuration files. +The major difference in these libraries comes down to how they calculate gradients. +There are mainly two ways: performing back-propagation on the _same_ graph +or explicitly representing a _backwards path_ to calculate the required gradients. + +![Backward Graph](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/back_graph.png) + +Libraries like Caffe, CXXNet, and Torch take the former approach, +performing back-prop on the original graph. +Libraries like Theano and CGT take the latter approach, +explicitly representing the backward path. +In this discussion, we adopt the *explicit backward path* approach +because it has several advantages for optimization. + +However, we should emphasize that choosing the explicit backward path approach doesn't restrict us +to symbolic libraries, such as Theano and CGT. We can also use the explicit backward path for gradient calculation of +layer-based (which ties forward and backward together) libraries. The following graph shows how to do this. +Basically, we introduce a backward node that links to the forward node of the graph and calls the ```layer.backward``` +in the backward operations. + +![Backward Layer](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/explicit_back_layer.png) + +This discussion applies to almost all existing deep learning libraries. +(There are differences between libraries, e.g., higher-order differentiation, which is beyond the scope of this topic.) + +Why is the explicit backward path better? Let's explain it with two examples. +The first reason is that the explicit backward path +clearly describes the dependency between computations. +Consider the following case, where we want to get +the gradient of A and B. As we can see clearly from the graph, +the computation of the ```d(C)``` gradient doesn't depend on F. +This means that we can free the memory of ```F``` +right after the forward computation is done. +Similarly, the memory of ```C``` can be recycled. + +![Backward Prune](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/back_dep_prune.png) + +Another advantage of the explicit backward path +is the ability to have a different backward path, +instead of a mirror of forward one. +A common example is the split connection case, +as shown in the following figure. + +![Backward Agg](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/back_agg_grad.png) + +In this example, the output of B is referenced by two operations. +If we want to do the gradient calculation in the same +network, we need to introduce an explicit split layer. +This means we need to do the split for the forward pass, too. +In this figure, the forward pass doesn't contain a split layer, +but the graph will automatically insert a gradient +aggregation node before passing the gradient back to B. +This helps us to save the memory cost of allocating the output of the split layer, +and the operation cost of replicating the data in the forward pass. + +If we adopt the explicit backward approach, +there's no difference between the forward pass and the backward pass. +We simply step through the computation graph in chronological order +and carry out computations. +This makes the explicit backward approach easy to analyze. +We just need to answer the question: +how do we allocate memory for each output node of a computation graph? + + +## What Can Be Optimized? + +As you can see, the computation graph is a useful way +to discuss memory allocation optimization techniques. +Already, we've shown how you can save some memory +by using the explicit backward graph. +Now let's explore further optimizations, +and see how we might determine reasonable baselines for benchmarking. + +Assume that we want to build a neural network with `n` layers. +Typically, when implementing a neural network, +we need to allocate node space for both the output of each layer +and the gradient values used during back-propagation. +This means we need roughly `2 n` memory cells. +We face the same requirement when using the explicit backward graph approach +because the number of nodes in a backward pass +is roughly the same as in a forward pass. + +### In-place Operations +One of the simplest techniques we can employ +is _in-place memory sharing_ across operations. +For neural networks, we can usually apply this technique +for the operations corresponding to activation functions. +Consider the following case, where we want +to compute the value of three chained sigmoid functions. + +![Inplace op](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/alloc_inline.png) + +Because we can compute sigmoid ```in-place```, +using the same memory for input and output, +we can compute an arbitrary-length chain +of sigmoid functions using constant memory. + +Note: it's easy to make mistakes when implementing in-place optimization. +Consider the following case, where the value of B is used not only by C, but also by F. + +![In-place trap](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/alloc_inline_trap.png) + +We can't perform in-place optimization because the value of B +is still needed after ```C=sigmoid(B)``` is computed. +An algorithm that simply does in-place optimization +for every sigmoid operation might fall into such trap, +so we need to be careful about when we can use it. + +### Standard Memory Sharing +In-place operations are not the only places where we can share memory. +In the following example, because the value of B is no longer needed +after we compute E, we can reuse B's memory to hold the result of E. + +![Normal Sharing](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/alloc_normal.png) + +*Memory sharing doesn't necessarily require the same data shape*. +Note that in the preceding example, the shapes of `B` and `E` can differ. +To handle such a situation, we can allocate a memory region +of size equal to the maximum of that required by `B` and `E` and share it between them. + +### Example of Real Neural Network Allocation +Of course, these are only toy examples and they address only the computation of the forward pass. +But the same ideas apply to real neural networks. +The following figure shows an allocation plan for a two-layer perceptron. + +![Net Alloc](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/alloc_mlp.png) + +In this example: + +- In-place optimization is applied when computing ```act1```, ```d(fc1)```, ```out``` and ```d(fc2)```. +- Memory is shared between ```d(act1)``` and ```d(A)```. + +## Memory Allocation Algorithm + +So far, we've discussed general techniques for optimizing memory allocation. +We've seen that there are traps to avoid, +as demonstrated in the case of in-place memory optimization. +So, how can we allocate memory correctly? +This is not a new problem. +For example, it is very similar +to the problem with register allocation in compilers. +There might be techniques that we can borrow. +We're not attempting to give a comprehensive review of techniques here, +but rather to introduce some simple +but useful tricks to attack the problem. + +The key problem is that we need to place resources +so that they don't conflict with each other. +More specifically, each variable has a *life time* +between the time it gets computed until the last time it is used. +In the case of the multi-layer perceptron, +the *life time* of ```fc1``` ends after ```act1``` get computed. + +![Net Alloc](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/alloc_mlp.png) + +The principle is *to allow memory sharing only between variables whose lifetimes don't overlap*. +There are multiple ways to do this. +You can construct the conflicting graph +with each variable as a node and link the edge +between variables with overlapping lifespans, +and then run a graph-coloring algorithm. +This likely has ```$O(n^2)$``` complexity, +where ```n``` is the number of nodes in the graph. +This might be too costly. + +Let's consider another simple heuristic. +The idea is to simulate the procedure of traversing the graph, +and keep a count of future operations that depends on the node. + +![Alloc](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/alloc_step.png) + +- An in-place optimization can be performed when only the current operation depends on the source (i.e., ```count==1```). +- Memory can be recycled into the box on the upper right corner when the ```count``` goes to 0. +- When we need new memory, we can either get it from the box or allocate a new one. + +***Note:*** During the simulation, no memory is allocated. +Instead, we keep a record of how much memory each node needs, +and allocate the maximum of the shared parts in the final memory plan. + +## Static vs. Dynamic Allocation + +The preceding strategy exactly simulates +the dynamic memory allocation procedure +in imperative languages, such as Python. +The ```count``` is the reference counter for each memory object, +and the object gets garbage collected +when the reference counter goes to 0. +In that sense, +we are simulating dynamic memory allocation once +to create a static allocation plan. +Can we simply use an imperative language +that dynamically allocates and deallocates memory? + +The major difference is that static allocation is only done once, +so we can afford to use more complicated algorithms. +For example, we can search for memory sizes +that are similar to the required memory block. +The Allocation can also be made graph aware. +We'll talk about that in the next section. +Dynamic allocation puts more pressure +on fast memory allocation and garbage collection. + +There is also one takeaway for users +who want to rely on dynamic memory allocations: +*do not unnecessarily reference objects*. +For example, if we organize all of the nodes in a list +and store then in a Net object, +these nodes will never get dereferenced, and we gain no space. +Unfortunately, this is a common way to organize code. + + +## Memory Allocation for Parallel Operations + +In the previous section, we discussed +how we can *simulate* running the procedure +for a computation graph to get a static allocation plan. +However, optimizing for parallel computation presents other challenges +because resource sharing and parallelization are on the two ends of a balance. +Let's look at the following two allocation plans for the same graph: + +![Parallel Alloc](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/parallel_alloc.png) + +Both allocation plans are valid +if we run the computation serially, +from ```A[1]``` to ```A[8]```. +However, the allocation plan on the left +introduces additional dependencies, +which means we can't run computation of ```A[2]``` and ```A[5]``` in parallel. +The plan on the right can. +To parallelize computation, we need to take greater care. + +### Be Correct and Safe First +Being correct is our first principle. +This means to execute in a way that takes implicit dependency +memory sharing into consideration. +You can do this by adding the implicit dependency edge to the execution graph. +Or, even simpler, if the execution engine is mutation aware, +as described in [our discussion of dependency engine design](note_engine), +push the operation in sequence +and write to the same variable tag +that represents the same memory region. + +Always produce a safe memory allocation plan. +This means never allocate the same memory +to nodes that can be parallelized. +This might not be ideal when memory reduction is more desirable, +and we don't gain too much when we can get benefit +from multiple computing streams simultaneously executing on the same GPU. + +### Try to Allow More Parallelization +Now we can safely perform some optimizations. +The general idea is to try and encourage memory sharing between nodes that can't be parallelized. +You can do this by creating an ancestor relationship +graph and querying it during allocation, +which costs approximately ```$O(n^2)$``` in time to construct. +We can also use a heuristic here, +for example, color the path in the graph. +As shown in the following figure, +when you try to find the longest paths in the graph, +color them the same color and continue. + +![Path Color](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/memory/graph_color.png) + +After you get the color of the node, +you allow sharing (or encourage sharing) +only between nodes of the same color. +This is a stricter version of the ancestor relationship, +but it costs only `$O(n)$` of time +if you search for only the first `k` path. + +This is by no means the only solution. +More sophisticated approaches might exist: + +## How Much Can you Save? + +We've discussed the techniques and algorithms you can use +to squeeze memory usage for deep learning. +How much can you really save by using these techniques? + +On coarse-grained operation graphs +that are already optimized for big operations, +you can reduce memory consumption roughly *by half*. +You can reduce memory usage even more +if you are optimizing a fine-grained computation network +used by symbolic libraries, such as Theano. Most of the ideas in this article inspired the design of _MXNet_. + +Also, you will notice that memory cost, for forward pass only execution, is extremely low compared to running both forward and backward pass. This is simply because there's more memory reuse if you run only the forward pass. + +So here are two takeaways: + +- Use a computation graph to allocate memory. +- For deep learning models, prediction consumes much less memory than training. + + +## Next Steps + +* [Efficient Data Loading Module for Deep Learning](http://mxnet.io/architecture/note_data_loading.html) +* [Survey of RNN Interface](http://mxnet.io/architecture/rnn_interface.html) diff --git a/docs/static_site/src/pages/api/architecture/overview.md b/docs/static_site/src/pages/api/architecture/overview.md new file mode 100644 index 000000000000..d2ad51c87776 --- /dev/null +++ b/docs/static_site/src/pages/api/architecture/overview.md @@ -0,0 +1,874 @@ + + + + + + + + + + + + + + + + +--- +layout: page_category +title: MXNet System Architecture +category: architecture +permalink: /api/architecture/overview +--- + +# MXNet System Architecture + +![System Overview](https://raw.githubusercontent.com/dmlc/dmlc.github.io/master/img/mxnet/system/overview.png) + +This figure shows the major modules and components of the MXNet system and their interaction. The modules are: + +- Runtime Dependency Engine: Schedules and executes the + operations according to their read/write dependency. +- Storage Allocator: Efficiently allocates and recycles memory blocks + on host (CPU) and devices (GPUs). +- Resource Manager: Manages global resources, such as the random number generator + and temporal space. +- NDArray: Dynamic, asynchronous n-dimensional arrays, + which provide flexible imperative programs for MXNet. +- Symbolic Execution: Static symbolic graph executor, + which provides efficient symbolic graph execution and optimization. +- Operator: Operators that define static forward and gradient + calculation (backprop). +- SimpleOp: Operators that extend NDArray operators and symbolic operators + in a unified fashion. +- Symbol Construction: Symbolic construction, which provides a way to construct + a computation graph (net configuration). +- KVStore: Key-value store interface for efficient parameter synchronization. +- Data Loading(IO): Efficient distributed data loading and augmentation. + +# MXNet System Components + +## Execution Engine + +You can use MXNet's engine not only for deep learning, +but for any domain-specific problem. +It's designed to solve a general problem: +execute a bunch of functions following their dependencies. +Execution of any two functions with dependencies should be serialized. +To boost performance, functions with no dependencies *can* be executed in parallel. +For a general discussion of this topic, +see our [notes on the dependency engine](note_engine.md). + +### Interface + +The following API is the core interface for the execution engine: + +```c++ + virtual void PushSync(Fn exec_fun, Context exec_ctx, + std::vector const& const_vars, + std::vector const& mutate_vars) = 0; +``` +This API allows you to push a function (`exec_fun`), +along with its context information and dependencies, to the engine. +`exec_ctx` is the context information in which the `exec_fun` should be executed, +`const_vars` denotes the variables that the function reads from, +and `mutate_vars` are the variables to be modified. +The engine provides the following guarantee: + +>*The execution of any two functions +that modify a common variable +is serialized in their push order.* + +### Function + +The function type of the engine is: + +```c++ + using Fn = std::function; +``` +`RunContext` contains runtime information, which is determined by the engine: + +```c++ + struct RunContext { + // stream pointer which could be safely cast to + // cudaStream_t* type + void *stream; + }; +``` +Alternatively, you could use `mxnet::engine::DAGEngine::Fn`, which has the same type definition. + +All of the functions are executed by the engine's internal threads. +In such a model, it's usually not a good idea to push *blocking* functions +to the engine (usually for dealing with I/O tasks like disk, web service, UI, etc.) +because it will occupy the execution thread and reduce total throughput. +In that case, we provide another *asynchronous* function type: + +```c++ + using Callback = std::function; + using AsyncFn = std::function; +``` +In the `AsyncFn` function, you can pass the heavy part to your own threads +and safely exit the body of the function. +The engine doesn't consider the function finished +until the `Callback` function is called. + +### Context + +You can specify the `Context` of the function to be executed within. +This usually includes whether the function should be run on a CPU or a GPU, +and if you specify a GPU, which GPU to use. +`Context` is different from `RunContext`. +`Context` contains device type (GPU/CPU) and device id, + while `RunContext` contains information that can be decided only during runtime, + for example, on which stream the function should be executed. + +### VarHandle + +`VarHandle` is used to specify the dependencies of functions. +The MXNet engine is designed to be decoupled from other MXNet modules. +So `VarHandle` is like an engine-provided token you use +to represent the external resources the functions can use or modify. +It's designed to be lightweight, so creating, +deleting, or copying a variable incurs little overhead. +Upon pushing the functions, you need to specify the variables +that will be used (immutable) in the `const_vars` vector, +and the variables that will be modified (mutable) in the `mutate_vars` vector. +The engine uses one rule for resolving the dependencies among functions: + +>*The execution of any two functions when one of them modifies at least one common variable is serialized in their push order.* + +For example, if `Fn1` and `Fn2` both mutate `V2` then `Fn2` +is guaranteed to be executed after `Fn1` +if `Fn2` is pushed after `Fn1`. +On the other hand, if `Fn1` and `Fn2` both use `V2`, +their actual execution order could be random. + +This design allows the engine to schedule *state-mutating* operations in a manner +that minimizes calls to allocate new memory. +For example, the weight update function in DNN +can now use the `+=` operator +to update the weights in place, +rather than generating a new weight array each time. + +To create a variable, use the `NewVar()` API. +To delete a variable, use the `PushDelete` API. + +### Push and Wait + +*All `Push` APIs are asynchronous.* The API call returns immediately +regardless of whether the pushed `Fn` is finished or not. +This allows the engine to start computing at the same time +as the user thread is pushing functions. +`Push` APIs are not thread-safe. +To be specific, only one thread should make engine API calls at a time. + +If you want to wait for a specific `Fn` to finish, +include a callback function in the closure, +and call the function at the end of your `Fn`. + +If you want to wait for all `Fn`s +that involve (use or mutate) a certain variable to finish, +use the `WaitForVar(var)` API. + +If you want to wait for all pushed `Fn`s to finish, +use the `WaitForAll()` API. + +### Save Object Creation Cost + +In some cases, you need to push several functions to the engine for a long period of time. +If the computation of these functions is light, +the overhead of copying lambdas and creating use/mutate variable lists becomes relatively high. +We provide an API to create an `OprHandle` beforehand: + +```c++ + virtual OprHandle NewOperator(AsyncFn fn, + std::vector const& const_vars, + std::vector const& mutate_vars) = 0; +``` +You can keep pushing the `OprHandle` without repeatedly creating them: + +```c++ + virtual void Push(OprHandle op, Context exec_ctx) = 0; +``` +To delete it, call the `DeleteOperator(OprHandle op)` API. +Ensure that the operator has finished computing before calling this API. + + +## Operators in MXNet + +In MXNet, an operator is a class that contains both actual computation logic +and auxiliary information that can aid the system in performing optimizations, +like in-place updates and auto-derivatives. +To understand the remainder of the document, +we recommend that you familiarize yourself with the `mshadow` library, +because all operators compute on the tensor-like structure `mshadow::TBlob` +provided by the system during runtime. + +MXNet's operator interface allows you to: + +* Reduce memory allocation cost by specifying in-place updates. +* Hide some internal arguments from Python to make it cleaner. +* Define the relationships among input tensors and output tensors, +which allows the system to perform shape checking for you. +* Acquire additional temporary spaces from the system +to perform computation (e.g., calling `cudnn` routines). + +### Operator Interface + +`Forward` is the core operator interface: + +```c++ + virtual void Forward(const OpContext &ctx, + const std::vector &in_data, + const std::vector &req, + const std::vector &out_data, + const std::vector &aux_states) = 0; +``` +The `OpContext` structure is: + +```c++ + struct OpContext { + int is_train; + RunContext run_ctx; + std::vector requested; + } +``` +It describes whether the operator is in the train or test phase, +which device the operator should be run on (in `run_ctx`), +and requested resources (covered in the following sections). + +- `in_data` and `out_data` represent the input and output tensors, respectively. +All of the tensor spaces have been allocated by the system. +- `req` denotes how the computation results are written into the `out_data`. +In other words, `req.size() == out_data.size()` and `req[i]` +correspond to the write type of `out_data[i]`. + +- The `OpReqType` is defined as: + +```c++ + enum OpReqType { + kNullOp, + kWriteTo, + kWriteInplace, + kAddTo + }; +``` + Normally, the types of all `out_data` should be `kWriteTo`, + meaning that the provided `out_data` tensor is a *raw* memory block, + so the operator should write results directly into it. + In some cases, for example when calculating the `gradient` tensor, + it would be great if we could accumulate the result, + rather than directly overwrite the tensor contents + so that no extra space needs to be created each time. + In such a case, the corresponding `req` type is set as `kAddTo`, + indicating that a `+=` should be called. + +- `aux_states` is intentionally designed for auxiliary tensors used to help computation. Currently, it is useless. + +Aside from the `Forward` operator, you could optionally implement the `Backward` interface: + +```c++ + virtual void Backward(const OpContext &ctx, + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &req, + const std::vector &in_grad, + const std::vector &aux_states); +``` +This interface follows the same design principle as the `Forward` interface, +except that `out_grad`, `in_data`, and `out_data` are given, +and the operator computes `in_grad` as the results. + The naming strategy is similar to Torch's convention, + and can be summarized in following figure: + +[input/output semantics figure] + +Some operators might not require all of the following: +`out_grad`, `in_data` and `out_data`. +You can specify these dependencies with the `DeclareBackwardDependency` interface in `OperatorProperty`. + +### Operator Property + +One convolution might have several implementations, +and you might want to switch among them to achieve the best performance. +Therefore, we separate the operator *semantic* interfaces +from the implementation interface (`Operator` class) +into the `OperatorProperty` class. +The `OperatorProperty` interface consists of: + +* **InferShape:** + +```c++ + virtual bool InferShape(mxnet::ShapeVector *in_shape, + mxnet::ShapeVector *out_shape, + mxnet::ShapeVector *aux_shape) const = 0; +``` + +This interface has two purposes: +* Tell the system the size of each input and output tensor, + so it can allocate space for them before the `Forward` and `Backward` call. +* Perform a size check to make sure that there isn't an obvious error before running. + The shape in `in_shape` is set by the system + (from the `out_shape` of the previous operators). + It returns `false` when there is not enough information + to infer shapes or throws an error when the shape is inconsistent. + +* **Request Resources:** Operations like `cudnnConvolutionForward` need a work space for computation. +If the system can manage that, it could then perform optimizations, +like reuse the space, and so on. +MXNet defines two interfaces to achieve this: + +```c++ + virtual std::vector ForwardResource( + const mxnet::ShapeVector &in_shape) const; + virtual std::vector BackwardResource( + const mxnet::ShapeVector &in_shape) const; +``` + The `ResourceRequest` structure (in `resource.h`) currently contains only a type flag: + +```c++ + struct ResourceRequest { + enum Type { + kRandom, // get a mshadow::Random object + kTempSpace, // request temporary space + }; + Type type; + }; +``` + If `ForwardResource` and `BackwardResource` return non-empty arrays, + the system offers the corresponding resources through the `ctx` parameter + in the `Forward` and `Backward` interface of `Operator`. + Basically, to access those resources, simply write: + +```c++ + auto tmp_space_res = ctx.requested[kTempSpace].get_space(some_shape, some_stream); + auto rand_res = ctx.requested[kRandom].get_random(some_stream); +``` + For an example, see `src/operator/cudnn_convolution-inl.h`. + +* **Backward dependency:** Let's look at two different operator signatures +(we name all of the arguments for demonstration purposes): + +```c++ + void FullyConnectedForward(TBlob weight, TBlob in_data, TBlob out_data); + void FullyConnectedBackward(TBlob weight, TBlob in_data, TBlob out_grad, TBlob in_grad); + + void PoolingForward(TBlob in_data, TBlob out_data); + void PoolingBackward(TBlob in_data, TBlob out_data, TBlob out_grad, TBlob in_grad); +``` + Note that `out_data` in `FullyConnectedForward` + is not used by `FullyConnectedBackward`, + while `PoolingBackward` requires all of the arguments of `PoolingForward`. + Therefore, for `FullyConnectedForward`, + the `out_data` tensor once consumed could be safely freed + because the backward function will not need it. + This provides a chance for the system to collect some tensors + as garbage as soon as possible. + To specify this situation, we provide an interface: + +```c++ + virtual std::vector DeclareBackwardDependency( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data) const; +``` + The `int` element of the argument vector is an ID + to distinguish different arrays. + Let's see how this interface specifies different dependencies + for `FullyConnected` and `Pooling`: + + ```c++ + std::vector FullyConnectedProperty::DeclareBackwardDependency( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data) const { + return {out_grad[0], in_data[0]}; // NOTE: out_data[0] is NOT included + } + std::vector PoolingProperty::DeclareBackwardDependency( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data) const { + return {out_grad[0], in_data[0], out_data[0]}; + } +``` + +* **In place Option:** To further save the cost of memory allocation, +you can use in-place updates. +They are appropriate for element-wise operations +when the input tensor and output tensor have the same shape. +You specify and in-place update with the following interface: + +```c++ + virtual std::vector> ElewiseOpProperty::ForwardInplaceOption( + const std::vector &in_data, + const std::vector &out_data) const { + return { {in_data[0], out_data[0]} }; + } + virtual std::vector> ElewiseOpProperty::BackwardInplaceOption( + const std::vector &out_grad, + const std::vector &in_data, + const std::vector &out_data, + const std::vector &in_grad) const { + return { {out_grad[0], in_grad[0]} } + } +``` + This tells the system that the `in_data[0]` and `out_data[0]` tensors could share the same memory spaces during `Forward`, and so do `out_grad[0]` and `in_grad[0]` during `Backward`. + + >**Important:** Even if you use the preceding specification, it's *not* guaranteed that the input and output tensors will share the same space. In fact, this is only a suggestion for the system, which makes the final decision. However, in either case, the decision is completely transparent to you, so the actual `Forward` and `Backward` implementation does not need to consider that. + +* **Expose Operator to Python:** Because of the restrictions of C++, you need user to implement following interfaces: + +```c++ + // initial the property class from a list of key-value string pairs + virtual void Init(const vector> &kwargs) = 0; + // return the parameters in a key-value string map + virtual map GetParams() const = 0; + // return the name of arguments (for generating signature in python) + virtual vector ListArguments() const; + // return the name of output values + virtual vector ListOutputs() const; + // return the name of auxiliary states + virtual vector ListAuxiliaryStates() const; + // return the number of output values + virtual int NumOutputs() const; + // return the number of visible outputs + virtual int NumVisibleOutputs() const; +``` + +### Create an Operator from the Operator Property + + `OperatorProperty` includes all *semantic* attributes of an operation. It's also responsible for creating the `Operator` pointer for actual computation. + +#### Create Operator +Implement the following interface in `OperatorProperty`: + +```c++ + virtual Operator* CreateOperator(Context ctx) const = 0; +``` +For example: + +```c++ + class ConvolutionOp { + public: + void Forward( ... ) { ... } + void Backward( ... ) { ... } + }; + class ConvolutionOpProperty : public OperatorProperty { + public: + Operator* CreateOperator(Context ctx) const { + return new ConvolutionOp; + } + }; +``` + +#### Parametrize Operator +When implementing a convolution operator, you need to know the kernel size, +the stride size, padding size, and so on. +These parameters should be passed to the operator +before any `Forward` or `Backward` interface is called. +To do so, you could define a `ConvolutionParam` structure, as follows: + +```c++ + #include + struct ConvolutionParam : public dmlc::Parameter { + mxnet::TShape kernel, stride, pad; + uint32_t num_filter, num_group, workspace; + bool no_bias; + }; +``` +Put it in `ConvolutionOpProperty`, and pass it to the operator class during construction: + +```c++ + class ConvolutionOp { + public: + ConvolutionOp(ConvolutionParam p): param_(p) {} + void Forward( ... ) { ... } + void Backward( ... ) { ... } + private: + ConvolutionParam param_; + }; + class ConvolutionOpProperty : public OperatorProperty { + public: + void Init(const vector& kwargs) { + // initialize param_ using kwargs + } + Operator* CreateOperator(Context ctx) const { + return new ConvolutionOp(param_); + } + private: + ConvolutionParam param_; + }; +``` + +#### Register the Operator Property Class and the Parameter Class to MXNet +Use the following macros to register the parameter structure and the operator property class to MXNet: + +```c++ + DMLC_REGISTER_PARAMETER(ConvolutionParam); + MXNET_REGISTER_OP_PROPERTY(Convolution, ConvolutionOpProperty); +``` +The first argument is the name string, the second is the property class name. + +### Interface Summary + +We've almost covered the entire interface required to define a new operator. Let's do a recap: + +* Use the `Operator` interface to write your computation logic (`Forward` and `Backward`). +* Use the `OperatorProperty` interface to: + - Pass the parameter to the operator class (you can use the `Init` interface). + - Create an operator using the `CreateOperator` interface. + - Correctly implement the operator description interface, such as the names of arguments, etc. + - Correctly implement the `InferShape` interface to set the output tensor shape. + - [Optional] If additional resources are needed, check `ForwardResource` and `BackwardResource`. + - [Optional] If `Backward` doesn't need all of the input and output of `Forward`, check `DeclareBackwardDependency`. + - [Optional] If in-place update is supported, check `ForwardInplaceOption` and `BackwardInplaceOption`. +* Register the `OperatorProperty` class and the parameter class. + +## Unifying the NDArray Operator and Symbolic Operator +NDArray operations are similar to symbolic operations, +except that sometimes you can't write in place to the operands +without a complete dependency graph. +However, the logic underlying NDArray and symbolic operations are almost identical. +*SimpleOp*, a new unified operator API, +unifies different invoking processes +and returns to the fundamental elements of operators. +Because most mathematical operators attend to one or two operands, +and more operands make dependency-related optimization useful, +the unified operator is specifically designed for unary and binary operations. + +Consider the elements of an operation. +Ideally, you need only functions and derivatives +to describe an operation. +Let's restrict that to the space of unary and binary operations. +How do we classify all operations to maximize the possibility +of in-place write optimization? +Note that you can separate functions by the number of operands. +Derivatives are a bit more complex. +To construct a dependency graph, you need to know whether output value, +input data, or neither are needed alongside head gradient. +Gradient functions in the unified API are differentiated +by the types of operands it takes for calculation. + +Before you learn more about the SimpleOp interface, + we recommend that you review the + [mshadow library guide](https://github.com/dmlc/mshadow/tree/master/guide) + because calculations will be done in the `mshadow::TBlob` structure. + +In the following example, we'll create an operator +functioning as a smooth l1 loss, +which is a mixture of l1 loss and l2 loss. The loss itself can be written as: + +``` + loss = outside_weight .* f(inside_weight .* (data - label)) + grad = outside_weight .* inside_weight .* f'(inside_weight .* (data - label)) +``` + `.*` stands for element-wise multiplication, and `f`, `f'` is the smooth l1 loss function, +which we are assuming is in `mshadow` for now. +At first glance, it's impossible to implement +this particular loss as a unary or binary operator. +But we have automatic differentiation in symbolic execution. +That simplifies the loss to `f` and `f'` directly. +This loss is no more complex than a `sin` or an `abs` function, +and can certainly be implemented as a unary operator. + +## SimpleOp: The Unified Operator API +### Define Shapes +The `mshadow` library requires explicit memory allocation. +As a consequence, all data shapes +must be provided before any calculation occurs. + Before we proceed with defining functions and gradient, +let's check input data shape consistency and provide output shape. + +```cpp + typedef mxnet::TShape (*UnaryShapeFunction)(const mxnet::TShape& src, + const EnvArguments& env); + typedef mxnet::TShape (*BinaryShapeFunction)(const mxnet::TShape& lhs, + const mxnet::TShape& rhs, + const EnvArguments& env); +``` +You can use `mshadow::TShape` to check input data shape and designate output data shape. +If you don't define this function, the default output shape is the same as the input shape. +In the case of a binary operator, the shape of `lhs` and `rhs` is checked as the same by default. + +You can also use shape functions to check if any additional arguments and resources are present. +Refer to the additional usages of `EnvArguments` to accomplish this. + +Before we start on our smooth l1 loss example, we define a `XPU` to `cpu` or `gpu` in the header +`smooth_l1_unary-inl.h` implementation so that we reuse the same code in `smooth_l1_unary.cc` and +`smooth_l1_unary.cu`. + +```cpp + #include + #if defined(__CUDACC__) + #define XPU gpu + #else + #define XPU cpu + #endif +``` +In our smooth l1 loss example, it's okay to use the default behavior whereby the output has the same shape as the source. +Written explicitly, it is: + +```cpp + inline mxnet::TShape SmoothL1Shape_(const mxnet::TShape& src, + const EnvArguments& env) { + return mxnet::TShape(src); + } +``` + +### Define Functions +Create a unary or binary function with one output: `mshadow::TBlob`. + +```cpp + typedef void (*UnaryFunction)(const TBlob& src, + const EnvArguments& env, + TBlob* ret, + OpReqType req, + RunContext ctx); + typedef void (*BinaryFunction)(const TBlob& lhs, + const TBlob& rhs, + const EnvArguments& env, + TBlob* ret, + OpReqType req, + RunContext ctx); +``` +* Functions are differentiated by the types of input arguments. +* `RunContext ctx` contains information needed during runtime for execution. + +```cpp + struct RunContext { + void *stream; // the stream of the device, can be NULL or Stream* in GPU mode + template inline mshadow::Stream* get_stream() // get mshadow stream from Context + } // namespace mxnet +``` + `mshadow::stream *s = ctx.get_stream();` is an example of obtaining a stream from `ctx`. +* `OpReqType req` denotes how computation results are written into `ret`. + +```cpp + enum OpReqType { + kNullOp, // no operation, do not write anything + kWriteTo, // write gradient to provided space + kWriteInplace, // perform an in-place write + kAddTo // add to the provided space + }; +``` + A macro is defined in `operator_util.h` for a simplified use of `OpReqType`. + `ASSIGN_DISPATCH(out, req, exp)` checks `req` and performs an assignment. + +In our smooth l1 loss example, we use `UnaryFunction` to define the function of this operator. + +```cpp + template + void SmoothL1Forward_(const TBlob& src, + const EnvArguments& env, + TBlob *ret, + OpReqType req, + RunContext ctx) { + using namespace mshadow; + using namespace mshadow::expr; + mshadow::Stream *s = ctx.get_stream(); + real_t sigma2 = env.scalar * env.scalar; + MSHADOW_TYPE_SWITCH(ret->type_flag_, DType, { + mshadow::Tensor out = ret->get(s); + mshadow::Tensor in = src.get(s); + ASSIGN_DISPATCH(out, req, + F(in, ScalarExp(sigma2))); + }); + } +``` +After obtaining `mshadow::Stream` from `RunContext`, we get `mshadow::Tensor` from `mshadow::TBlob`. +`mshadow::F` is a shortcut to initiate a `mshadow` expression. The macro `MSHADOW_TYPE_SWITCH(type, DType, ...)` +handles details on different types, and the macro `ASSIGN_DISPATCH(out, req, exp)` checks `OpReqType` and +performs actions accordingly. `sigma2` is a special parameter in this loss, which we will cover later. + +### Define Gradients (Optional) +Create a gradient function with various types of inputs. + +```cpp + // depending only on out_grad + typedef void (*UnaryGradFunctionT0)(const OutputGrad& out_grad, + const EnvArguments& env, + TBlob* in_grad, + OpReqType req, + RunContext ctx); + // depending only on out_value + typedef void (*UnaryGradFunctionT1)(const OutputGrad& out_grad, + const OutputValue& out_value, + const EnvArguments& env, + TBlob* in_grad, + OpReqType req, + RunContext ctx); + // depending only on in_data + typedef void (*UnaryGradFunctionT2)(const OutputGrad& out_grad, + const Input0& in_data0, + const EnvArguments& env, + TBlob* in_grad, + OpReqType req, + RunContext ctx); +``` +Gradient functions of binary operators have similar structures, except that `Input`, `TBlob`, and `OpReqType` +are doubled. + +`GradFunctionArgument` + + `Input0`, `Input`, `OutputValue`, and `OutputGrad` all share the structure of `GradFunctionArgument`, + which is defined as: + + ```cpp + struct GradFunctionArgument { + TBlob data; + } + ``` + +In our smooth l1 loss example, note that it's an `f'(x)`, +which utilizes input for the gradient calculation, +so the `UnaryGradFunctionT2` is suitable. +To enable the chain rule of the gradient, +we also need to multiply `out_grad` from the top to the result of `in_grad`. + +```cpp + template + void SmoothL1BackwardUseIn_(const OutputGrad& out_grad, + const Input0& in_data0, + const EnvArguments& env, + TBlob *in_grad, + OpReqType req, + RunContext ctx) { + using namespace mshadow; + using namespace mshadow::expr; + mshadow::Stream *s = ctx.get_stream(); + real_t sigma2 = env.scalar * env.scalar; + MSHADOW_TYPE_SWITCH(in_grad->type_flag_, DType, { + mshadow::Tensor src = in_data0.data.get(s); + mshadow::Tensor ograd = out_grad.data.get(s); + mshadow::Tensor igrad = in_grad->get(s); + ASSIGN_DISPATCH(igrad, req, + ograd * F(src, ScalarExp(sigma2))); + }); + } +``` + +### Register SimpleOp to MXNet +After creating the shape, function, and gradient, restore them into both an NDArray operator and +a symbolic operator. To simplify this process, use the registration macro defined in `operator_util.h`. + +```cpp + MXNET_REGISTER_SIMPLE_OP(Name, DEV) + .set_shape_function(Shape) + .set_function(DEV::kDevMask, Function, SimpleOpInplaceOption) + .set_gradient(DEV::kDevMask, Gradient, SimpleOpInplaceOption) + .describe("description"); +``` +`SimpleOpInplaceOption` is defined as: + +```cpp + enum SimpleOpInplaceOption { + kNoInplace, // do not allow inplace in arguments + kInplaceInOut, // allow inplace in with out (unary) + kInplaceOutIn, // allow inplace out_grad with in_grad (unary) + kInplaceLhsOut, // allow inplace left operand with out (binary) + kInplaceOutLhs // allow inplace out_grad with lhs_grad (binary) + }; +``` + +In our example, we have a gradient function that relies on input data, so the function can't be written in +place. The output gradient has no purpose after gradient computation, so the gradient can be written in place. + +```cpp + MXNET_REGISTER_SIMPLE_OP(smooth_l1, XPU) + .set_function(XPU::kDevMask, SmoothL1Forward_, kNoInplace) + .set_gradient(XPU::kDevMask, SmoothL1BackwardUseIn_, kInplaceOutIn) + .set_enable_scalar(true) + .describe("Calculate Smooth L1 Loss(lhs, scalar)"); +``` +Remember from the discussion of shape functions that a default behavior without `set_shape_function` forces the inputs +(if they're binary) to be the same shape and yield the same shape for output. We'll discuss `set_enable_scalar` later. + +### NDArray Operator Summary +* Create a shape function for determining the output shape. +* Create a function as the forward routine by choosing a suitable function type. +* Create a gradient as the backward routine by choosing a suitable gradient type. +* Register the operator using the registration process. + +## Additional Information on SimpleOp +### Using SimpleOp on EnvArguments +Some operations might need a scalar as input, such as a gradient scale, a set of keyword arguments +controlling behavior, or a temporary space to speed up calculations.`EnvArguments` provides additional arguments and resources to make calculations more scalable +and efficient. + +```cpp + struct EnvArguments { + real_t scalar; // scalar argument, if enabled + std::vector > kwargs; // keyword arguments + std::vector resource; // pointer to the resources requested + }; +``` + +More registration parameters are required to enable these additional features. To prevent confusion on parameters, `scalar` and `kwargs` +can't be present at the same time. To enable `scalar`, use +`set_enable_scalar(bool enable_scalar)` in registration. Then, in forward functions and gradients, the `scalar` can be accessed from `env.scalar` as in the function parameter `EnvArguments env`. + +To enable `kwargs`, use `set_enable_kwargs(bool enable_kwargs)` in registration. Then, in forward +functions and gradients, additional arguments are contained in `env.kwarg`, which is defined as +`std::vector >`. Use the DMLC parameter structure to +simplify parsing keyword arguments. For more details, see the [guide on parameter structure](https://github.com/dmlc/dmlc-core/blob/master/doc/parameter.md). + +Additional resources like `mshadow::Random` and temporary memory space can also be requested and +accessed from `EnvArguments.resource`. The registration routine is `set_resource_request(ResourceRequest req)` +or `set_resource_request(const std::vector)`, where `mxnet::ResourceRequest` is defined as: + +```cpp + struct ResourceRequest { + enum Type { // Resource type, indicating what the pointer type is + kRandom, // mshadow::Random object + kTempSpace // A dynamic temp space that can be arbitrary size + }; + Type type; // type of resources + }; +``` +Registration will request the declared resource requests from `mxnet::ResourceManager`, and place resources +in `std::vector resource` in `EnvArguments`. To access resources, use the following: + +```cpp + auto tmp_space_res = env.resources[0].get_space(some_shape, some_stream); + auto rand_res = env.resources[0].get_random(some_stream); +``` +For an example, see `src/operator/loss_binary_op-inl.h`. + +In our smooth l1 loss example, a scalar input is needed to mark the turning point of a loss function. Therefore, +in the registration process, we use `set_enable_scalar(true)`, and use `env.scalar` in function and gradient +declarations. + +### Crafting a Tensor Operation +Because computation utilizes the `mshadow` library and we sometimes don't have functions readily available, we +can craft tensor operations in operator implementations. If you define such functions as element-wise, you +can implement them as a `mxnet::op::mshadow_op`. `src/operator/mshadow_op.h` that contains a lot of `mshadow_op`, +for example. `mshadow_op` are expression mappers. They deal with the scalar case of desired functions. For details, see +[mshadow expression API guide](https://github.com/dmlc/mshadow/tree/master/doc). + +If an operation can't be done in an element-wise way, like the softmax loss and gradient, then you need to create a new tensor operation. You need to create as `mshadow` function and as `mshadow::cuda` +function directly. For details, see the `mshadow` library. For an example, see `src/operator/roi_pooling.cc`. + +In our smooth l1 loss example, we create two mappers, namely the scalar cases of smooth l1 loss and gradient. + +```cpp + namespace mshadow_op { + struct smooth_l1_loss { + // a is x, b is sigma2 + MSHADOW_XINLINE static real_t Map(real_t a, real_t b) { + if (a > 1.0f / b) { + return a - 0.5f / b; + } else if (a < -1.0f / b) { + return -a - 0.5f / b; + } else { + return 0.5f * a * a * b; + } + } + }; + } +``` +The gradient, which can be found in `src/operator/smooth_l1_unary-inl.h`, is similar. + +### Beyond Two Operands +The new unified API is designed to fulfill the fundamentals of an operation. For operators with more than two inputs, +more than one output, or that need more features, see the original [Operator API](http://mxnet.io/architecture/overview.html#operators-in-mxnet). diff --git a/docs/static_site/src/pages/api/architecture/program_model.md b/docs/static_site/src/pages/api/architecture/program_model.md new file mode 100644 index 000000000000..be291d24b19e --- /dev/null +++ b/docs/static_site/src/pages/api/architecture/program_model.md @@ -0,0 +1,629 @@ + + + + + + + + + + + + + + + + +--- +layout: page_category +title: Deep Learning Programming Paradigm +category: architecture +permalink: /api/architecture/program_model +--- + +# Deep Learning Programming Paradigm + +However much we might ultimately care about performance, +we first need working code before we can start worrying about optimization. +Writing clear, intuitive deep learning code can be challenging, +and the first thing any practitioner must deal with is the language syntax itself. +Complicating matters, of the many deep learning libraries out there, +each has its own approach to programming style. + +In this document, we focus on two of the most important high-level design decisions: +1. Whether to embrace the _symbolic_ or _imperative_ paradigm for mathematical computation. +2. Whether to build networks with bigger (more abstract) or more atomic operations. + +Throughout, we'll focus on the programming models themselves. +When programming style decisions may impact performance, we point this out, +but we don't dwell on specific implementation details. + + +## Symbolic vs. Imperative Programs + +If you are a Python or C++ programmer, then you're already familiar with imperative programs. +Imperative-style programs perform computation as you run them. +Most code you write in Python is imperative, as is the following NumPy snippet. + +```python + import numpy as np + a = np.ones(10) + b = np.ones(10) * 2 + c = b * a + d = c + 1 +``` +When the program executes ```c = b * a```, it runs the actual numerical computation. + +Symbolic programs are a bit different. With symbolic-style programs, +we first define a (potentially complex) function abstractly. +When defining the function, no actual numerical computation takes place. +We define the abstract function in terms of placeholder values. +Then we can compile the function, and evaluate it given real inputs. +In the following example, we rewrite the imperative program from above +as a symbolic-style program: + +```python + A = Variable('A') + B = Variable('B') + C = B * A + D = C + Constant(1) + # compiles the function + f = compile(D) + d = f(A=np.ones(10), B=np.ones(10)*2) +``` +As you can see, in the symbolic version, when ```C = B * A``` is executed, no computation occurs. +Instead, this operation generates a _computation graph_ (also called a _symbolic graph_) +that represents the computation. +The following figure shows a computation graph to compute ```D```. + +![Comp Graph](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/prog_model/comp_graph.png) + +Most symbolic-style programs contain, either explicitly or implicitly, a *compile* step. +This converts the computation graph into a function that we can later call. +In the above example, numerical computation only occurs in the last line of code. +The defining characteristic of symbolic programs is their clear separation +between building the computation graph and executing it. +For neural networks, we typically define the entire model as a single compute graph. + +Among other popular deep learning libraries, Torch, Chainer, and Minerva embrace the imperative style. +Examples of symbolic-style deep learning libraries include Theano, CGT, and TensorFlow. +We might also view libraries like CXXNet and Caffe, which rely on configuration files, as symbolic-style libraries. +In this interpretation, we'd consider the content of the configuration file as defining the computation graph. + +Now that you understand the difference between these two programming models, let's compare the advantages of each. + + +### Imperative Programs Tend to be More Flexible + +When you're using an imperative-style library from Python, you are writing in Python. +Nearly anything that would be intuitive to write in Python, you could accelerate by calling down in the appropriate places to the imperative deep learning library. +On the other hand, when you write a symbolic program, you may not have access to all the familiar Python constructs, like iteration. +Consider the following imperative program, and think about how you can translate this into a symbolic program. + +```python + a = 2 + b = a + 1 + d = np.zeros(10) + for i in range(d): + d += np.zeros(10) +``` +This wouldn't be so easy if the Python for-loop weren't supported by the symbolic API. +When you write a symbolic program in Python, you're *not* writing in Python. +Instead, you're writing in a domain-specific language (DSL) defined by the symbolic API. +The symbolic APIs found in deep learning libraries +are powerful DSLs that generate callable computation graphs for neural networks. + + +Intuitively, you might say that imperative programs +are more *native* than symbolic programs. +It's easier to use native language features. +For example, it's straightforward to print out the values +in the middle of computation or to use native control flow and loops +at any point in the flow of computation. + +### Symbolic Programs Tend to be More Efficient + +As we've seen, imperative programs tend to be flexible +and fit nicely into the programming flow of a host language. +So you might wonder, why do so many deep learning libraries +embrace the symbolic paradigm? +The main reason is efficiency, both in terms of memory and speed. +Let's revisit our toy example from before. + +```python + import numpy as np + a = np.ones(10) + b = np.ones(10) * 2 + c = b * a + d = c + 1 + ... +``` + +![Comp Graph](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/prog_model/comp_graph.png) + +Assume that each cell in the array occupies 8 bytes of memory. +How much memory do you need to execute this program in the Python console? + +As an imperative program we need to allocate memory at each line. +That leaves us allocating 4 arrays of size 10. +So we'll need `4 * 10 * 8 = 320` bytes. +On the other hand, if we built a computation graph, +and knew in advance that we only needed `d`, +we could reuse the memory originally allocated for intermediate values. +For example, by performing computations in-place, +we might recycle the bits allocated for ```b``` to store `c`. +And we might recycle the bits allocated for `c` to store `d`. +In the end we could cut our memory requirement in half, +requiring just `2 * 10 * 8 = 160` bytes. + +Symbolic programs are more *restricted*. +When we call `compile` on D, we tell the system +that only the value of `d` is needed. +The intermediate values of the computation, +in this case ```c```, is then invisible to us. + +We benefit because the symbolic programs +can then safely reuse the memory for in-place computation. +But on the other hand, if we later decide that we need to access `c`, we're out of luck. +So imperative programs are better prepared to encounter all possible demands. +If we ran the imperative version of the code in a Python console, +we could inspect any of the intermediate variables in the future. + + + +Symbolic programs can also perform another kind of optimization, called operation folding. +Returning to our toy example, the multiplication and addition operations +can be folded into one operation, as shown in the following graph. +If the computation runs on a GPU processor, +one GPU kernel will be executed, instead of two. +In fact, this is one way we hand-craft operations +in optimized libraries, such as CXXNet and Caffe. +Operation folding improves computation efficiency. + +![Comp Graph Folded](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/prog_model/comp_graph_fold.png) + +Note, you can't perform operation folding in imperative programs, +because the intermediate values might be referenced in the future. +Operation folding is possible in symbolic programs +because you get the entire computation graph, +and a clear specification of which values will be needed and which are not. + + +### Case Study: Backprop and AutoDiff + +In this section, we compare the two programming models +on the problem of auto differentiation, or backpropagation. +Differentiation is of vital importance in deep learning +because it's the mechanism by which we train our models. +In any deep learning model, we define a _loss function_. +A _loss function_ measures how far the model is from the desired output. +We then typically pass over training examples (pairs of inputs and ground-truth outputs). +At each step we update the model's _parameters_ to minimize the loss. +To determine the direction in which to update the parameters, +we need to take the derivative of the loss function with respect to the parameters. + +In the past, whenever someone defined a new model, +they had to work out the derivative calculations by hand. +While the math is reasonably straightforward, +for complex models, it can be time-consuming and tedious work. +All modern deep learning libraries make the practitioner/researcher's job +much easier, by automatically solving the problem of gradient calculation. + +Both imperative and symbolic programs can perform gradient calculation. +So let's take a look at how you might perform automatic differentiation with each. + +Let's start with imperative programs. +The following example Python code performs automatic differentiation using our toy example: + +```python + class array(object) : + """Simple Array object that support autodiff.""" + def __init__(self, value, name=None): + self.value = value + if name: + self.grad = lambda g : {name : g} + + def __add__(self, other): + assert isinstance(other, int) + ret = array(self.value + other) + ret.grad = lambda g : self.grad(g) + return ret + + def __mul__(self, other): + assert isinstance(other, array) + ret = array(self.value * other.value) + def grad(g): + x = self.grad(g * other.value) + x.update(other.grad(g * self.value)) + return x + ret.grad = grad + return ret + + # some examples + a = array(1, 'a') + b = array(2, 'b') + c = b * a + d = c + 1 + print d.value + print d.grad(1) + # Results + # 3 + # {'a': 2, 'b': 1} +``` + +In this code, each array object contains a grad function (it is actually a closure). +When you run ```d.grad```, it recursively invokes the grad function of its inputs, +backprops the gradient value back, and +returns the gradient value of each input. + +This might look a bit complicated, so let's consider +the gradient calculation for symbolic programs. +The following program performs symbolic gradient calculation for the same task. + +```python + A = Variable('A') + B = Variable('B') + C = B * A + D = C + Constant(1) + # get gradient node. + gA, gB = D.grad(wrt=[A, B]) + # compiles the gradient function. + f = compile([gA, gB]) + grad_a, grad_b = f(A=np.ones(10), B=np.ones(10)*2) +``` + +The grad function of ```D``` generates a backward computation graph, +and returns a gradient node, ```gA, gB```, +which correspond to the red nodes in the following figure. + +![Comp Graph Folded](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/prog_model/comp_graph_backward.png) + +The imperative program actually does the same thing as the symbolic program. +It implicitly saves a backward computation graph in the grad closure. +When you invoked ```d.grad```, you start from ```d(D)```, +backtrack through the graph to compute the gradient, and collect the results. + +The gradient calculations in both symbolic +and imperative programming follow the same pattern. +What's the difference then? +Recall the *be prepared to encounter all possible demands* requirement of imperative programs. +If you are creating an array library that supports automatic differentiation, +you have to keep the grad closure along with the computation. +This means that none of the history variables can be +garbage-collected because they are referenced by variable `d` by way of function closure. + +What if you want to compute only the value of `d`, +and don't want the gradient value? +In symbolic programming, you declare this with `f=compiled([D])`. +This also declares the boundary of computation, +telling the system that you want to compute only the forward pass. +As a result, the system can free the memory of previous results, +and share the memory between inputs and outputs. + +Imagine running a deep neural network with ```n``` layers. +If you are running only the forward pass, +not the backward(gradient) pass, +you need to allocate only two copies of +temporal space to store the values of the intermediate layers, +instead of ```n``` copies of them. +However, because imperative programs need to be prepared +to encounter all possible demands of getting the gradient, +they have to store the intermediate values, +which requires ```n``` copies of temporal space. + +As you can see, the level of optimization depends +on the restrictions on what you can do. +Symbolic programs ask you to clearly specify +these restrictions when you compile the graph. +One the other hand, imperative programs +must be prepared for a wider range of demands. +Symbolic programs have a natural advantage +because they know more about what you do and don't want. + +There are ways in which we can modify imperative programs +to incorporate similar restrictions. +For example, one solution to the preceding +problem is to introduce a context variable. +You can introduce a no-gradient context variable +to turn gradient calculation off. + +```python + with context.NoGradient(): + a = array(1, 'a') + b = array(2, 'b') + c = b * a + d = c + 1 +``` + + + +However, this example still must be prepared to encounter all possible demands, +which means that you can't perform the in-place calculation +to reuse memory in the forward pass (a trick commonly used to reduce GPU memory usage). +The techniques we've discussed generate an explicit backward pass. +Some of the libraries such as Caffe and CXXNet perform backprop implicitly on the same graph. +The approach we've discussed in this section also applies to them. + +Most configuration-file-based libraries, +such as CXXNet and Caffe are designed +to meet one or two generic requirements: +get the activation of each layer, +or get the gradient of all of the weights. +These libraries have the same problem: +the more generic operations the library has to support, +the less optimization (memory sharing) you can do, +based on the same data structure. + +As you can see, the trade-off between restriction +and flexibility is the same for most cases. + +### Model Checkpoint + +It's important to able to save a model and load it back later. +There are different ways to *save* your work. +Normally, to save a neural network, +you need to save two things: a net configuration +for the structure of the neural network and the weights of the neural network. + +The ability to check the configuration is a plus for symbolic programs. +Because the symbolic construction phase does not perform computation, +you can directly serialize the computation graph, and load it back later. +This solves the problem of saving the configuration +without introducing an additional layer. + +```python + A = Variable('A') + B = Variable('B') + C = B * A + D = C + Constant(1) + D.save('mygraph') + ... + D2 = load('mygraph') + f = compile([D2]) + # more operations + ... +``` + +Because an imperative program executes as it describes the computation, +you have to save the code itself as the ```configuration```, +or build another configuration layer on top of the imperative language. + +### Parameter Updates + +Most symbolic programs are data flow (computation) graphs. +Data flow graphs describe computation. +But it's not obvious how to use graphs to describe parameter updates. +That's because parameter updates introduce mutation, +which is not a data flow concept. +Most symbolic programs introduce a special update statement +to update persistent state in the programs. + +It's usually easier to write parameter updates in an imperative style, +especially when you need multiple updates that relate to each other. +For symbolic programs, the update statement is also executed as you call it. +So in that sense, most symbolic deep learning libraries +fall back on the imperative approach to perform updates, +while using the symbolic approach to perform gradient calculation. + +### There Is No Strict Boundary + +In comparing the two programming styles, +some of our arguments might not be strictly true, +i.e., it's possible to make an imperative program +more like a traditional symbolic program or vice versa. +However, the two archetypes are useful abstractions, +especially for understanding the differences between deep learning libraries. +We might reasonably conclude that there is no clear boundary between programming styles. +For example, you can create a just-in-time (JIT) compiler in Python +to compile imperative Python programs, +which provides some of the advantages of global +information held in symbolic programs. + + +## Big vs. Small Operations + +When designing a deep learning library, another important programming model decision +is precisely what operations to support. +In general, there are two families of operations supported by most deep learning libraries: + +- Big operations - typically for computing neural network layers (e.g. FullyConnected and BatchNormalize). +- Small operations - mathematical functions like matrix multiplication and element-wise addition. + +Libraries like CXXNet and Caffe support layer-level operations. +Libraries like Theano and Minerva support fine-grained operations. + +### Smaller Operations Can Be More Flexible +It's quite natural to use smaller operations to compose bigger operations. +For example, the sigmoid unit can simply be composed of division, addition and an exponentiation: + +```python + sigmoid(x) = 1.0 / (1.0 + exp(-x)) +``` +Using smaller operations as building blocks, you can express nearly anything you want. +If you're more familiar with CXXNet- or Caffe-style layers, +note that these operations don't differ from a layer, except that they are smaller. + +```python + SigmoidLayer(x) = EWiseDivisionLayer(1.0, AddScalarLayer(ExpLayer(-x), 1.0)) +``` +This expression composes three layers, +with each defining its forward and backward (gradient) function. +Using smaller operations gives you the advantage of building new layers quickly, +because you only need to compose the components. + +### Big Operations Are More Efficient +Directly composing sigmoid layers requires three layers of operation, instead of one. + +```python + SigmoidLayer(x) = EWiseDivisionLayer(1.0, AddScalarLayer(ExpLayer(-x), 1.0)) +``` +This code creates overhead for computation and memory (which could be optimized, with cost). + +Libraries like CXXNet and Caffe take a different approach. +To support coarse-grained operations, +such as BatchNormalization and the SigmoidLayer directly, +in each layer, the calculation kernel is hand crafted +with one or only some CUDA kernel launches. +This makes these implementations more efficient. + +### Compilation and Optimization + +Can small operations be optimized? Of course, they can. +Let's look at the system optimization part of the compilation engine. +Two types of optimization can be performed on the computation graph: + +- Memory allocation optimization, to reuse the memory of the intermediate computations. +- Operator fusion, to detect sub-graph patterns, such as the sigmoid, and fuse them into a bigger operation kernel. + +Memory allocation optimization isn't restricted to small operations graphs. +You can use it with bigger operations graph, too. +However, optimization might not be essential +for bigger operation libraries like CXXNet and Caffe, +because you can't find the compilation step in them. +However, there's a (dumb) ```compilation step``` in these libraries, +that basically translates the layers into a fixed forward, +backprop execution plan, by running each operation one by one. + +For computation graphs with smaller operations, +these optimizations are crucial to performance. +Because the operations are small, +there are many sub-graph patterns that can be matched. +Also, because the final, generated operations +might not be enumerable, +an explicit recompilation of the kernels is required, +as opposed to the fixed amount of precompiled kernels +in the big operation libraries. +This creates compilation overhead for the symbolic libraries +that support small operations. +Requiring compilation optimization also creates engineering overhead +for the libraries that solely support smaller operations. + +As in the case of symbolic vs. imperative, +the bigger operation libraries "cheat" +by asking you to provide restrictions (to the common layer), +so that you actually perform the sub-graph matching. +This moves the compilation overhead to the real brain, which is usually not too bad. + +### Expression Template and Statically Typed Language +You always have a need to write small operations and compose them. +Libraries like Caffe use hand-crafted kernels to build these bigger blocks. +Otherwise, you would have to compose smaller operations using Python. + +There's a third choice that works pretty well. +This is called the expression template. +Basically, you use template programming to +generate generic kernels from an expression tree at compile time. +For details, see [Expression Template Tutorial](https://github.com/dmlc/mshadow/blob/master/guide/exp-template/README.md). +CXXNet makes extensive use of an expression template, +which enables creating much shorter and more readable code that matches +the performance of hand-crafted kernels. + +The difference between using an expression template and Python kernel generation +is that expression evaluation is done at compile time for C++ with an existing type, +so there is no additional runtime overhead. +In principle, this is also possible with other statically typed languages that support templates, +but we've seen this trick used only in C++. + +Expression template libraries create a middle ground between Python operations +and hand-crafted big kernels by allowing C++ users to craft efficient big +operations by composing smaller operations. It's an option worth considering. + +## Mix the Approaches + +Now that we've compared the programming models, which one should you choose? +Before delving into that, we should emphasize that depending on the problems you're trying to solve, +our comparison might not necessarily have a big impact. + +Remember [Amdahl's law](https://en.wikipedia.org/wiki/Amdahl%27s_law): +If you are optimizing a non-performance-critical part of your problem, +you won't get much of a performance gain. + +As you've seen, there usually is a trade-off between efficiency, +flexibility, and engineering complexity. +The more suitable programming style depends on the problem you are trying to solve. +For example, imperative programs are better for parameter updates, +and symbolic programs for gradient calculation. + +We advocate *mixing* the approaches. +Sometimes the part that we want to be flexible +isn't crucial to performance. +In these cases, it's okay to leave some efficiency on the table +to support more flexible interfaces. +In machine learning, combining methods usually works better than using just one. + +If you can combine the programming models correctly, +you can get better results than when using a single programming model. +In this section, we discuss how to do so. + +### Symbolic and Imperative Programs +There are two ways to mix symbolic and imperative programs: + +- Use imperative programs within symbolic programs as callbacks +- Use symbolic programs as part of imperative programs + +We've observed that it's usually helpful to write parameter updates imperatively, +and perform gradient calculations in symbolic programs. + +Symbolic libraries already mix programs because Python itself is imperative. +For example, the following program mixes the symbolic approach with NumPy, which is imperative. + +```python + A = Variable('A') + B = Variable('B') + C = B * A + D = C + Constant(1) + # compiles the function + f = compile(D) + d = f(A=np.ones(10), B=np.ones(10)*2) + d = d + 1.0 +``` +The symbolic graphs are compiled into a function that can be executed imperatively. +The internals are a black box to the user. +This is exactly like writing C++ programs and exposing them to Python, which we commonly do. + +Because parameter memory resides on the GPU, +you might not want to use NumPy as an imperative component. +Supporting a GPU-compatible imperative library +that interacts with symbolic compiled functions +or provides a limited amount of updating syntax +in the update statement in symbolic program execution +might be a better choice. + +### Small and Big Operations + +There might be a good reason to combine small and big operations. +Consider applications that perform tasks such as changing +a loss function or adding a few customized layers to an existing structure. +Usually, you can use big operations to compose existing +components, and use smaller operations to build the new parts. + +Recall Amdahl's law. Often, the new components +are not the cause of the computation bottleneck. +Because the performance-critical part is already optimized by +the bigger operations, it's okay to forego optimizing the additional small operations, +or to do a limited amount of memory optimization instead +of operation fusion and directly running them. + +### Choose Your Own Approach + +In this document, we compared multiple approaches +to developing programming environments for deep learning. +We compared both the usability and efficiency implications of each, +finding that many of these trade-offs (like imperative vs symbolic aren't necessarily black and white). +You can choose your approach, or combine the approaches +to create more interesting and intelligent deep learning libraries. + +## Contribute to MXNet + +This document is part of our effort to provide [open-source system design notes](index.md) +for deep learning libraries. If you're interested in contributing to _MXNet_ or its +documentation, [fork us on GitHub](http://github.com/dmlc/mxnet). + +## Next Steps + +* [Dependency Engine for Deep Learning](http://mxnet.io/architecture/note_engine.html) +* [Squeeze the Memory Consumption of Deep Learning](http://mxnet.io/architecture/note_memory.html) +* [Efficient Data Loading Module for Deep Learning](http://mxnet.io/architecture/note_data_loading.html) +* [Survey of RNN Interface](http://mxnet.io/architecture/rnn_interface.html) diff --git a/docs/static_site/src/pages/api/clojure/docs/tutorials/index.md b/docs/static_site/src/pages/api/clojure/docs/tutorials/index.md new file mode 100644 index 000000000000..a424c87b335a --- /dev/null +++ b/docs/static_site/src/pages/api/clojure/docs/tutorials/index.md @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + +--- +layout: page_landing_tutorials +title: Clojure Tutorials +action: Get Started +tag: clojure +permalink: /api/clojure/docs/tutorials +--- diff --git a/docs/static_site/src/pages/api/clojure/docs/tutorials/kvstore.md b/docs/static_site/src/pages/api/clojure/docs/tutorials/kvstore.md new file mode 100644 index 000000000000..96eb03abdeb7 --- /dev/null +++ b/docs/static_site/src/pages/api/clojure/docs/tutorials/kvstore.md @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + +--- +layout: page_api +title: KVStore API +is_tutorial: true +permalink: /api/clojure/docs/tutorials/kvstore +tag: clojure +--- +# KVStore API + +Topics: + +* [Basic Push and Pull](#basic-push-and-pull) +* [List Key-Value Pairs](#list-key-value-pairs) +* [API Reference](http://mxnet.incubator.apache.org/api/clojure/docs/org.apache.clojure-mxnet.kvstore.html) + +To follow along with this documentation, you can use this namespace to with the needed requires: + +```clojure +(ns docs.kvstore + (:require [org.apache.clojure-mxnet.kvstore :as kvstore] + [org.apache.clojure-mxnet.ndarray :as ndarray] + [org.apache.clojure-mxnet.context :as context])) +``` + +## Basic Push and Pull + +Provides basic operation over multiple devices (GPUs) on a single device. + +### Initialization + +Let's consider a simple example. It initializes +a (`int`, `NDArray`) pair into the store, and then pulls the value out. + +```clojure +(def kv (kvstore/create "local")) ;; create a local kvstore +(def shape [2 3]) +;;; init the kvstore with a vector of keys (strings) and ndarrays +(kvstore/init kv ["3"] [(ndarray/* (ndarray/ones shape) 2)]) +(def a (ndarray/zeros shape)) +(kvstore/pull kv ["3"] [a]) +(ndarray/->vec a) ;=> [2.0 2.0 2.0 2.0 2.0 2.0] +``` + +### Push, Aggregation, and Updater + +For any key that's been initialized, you can push a new value with the same shape to the key, as follows: + +```clojure +(kvstore/push kv ["3"] [(ndarray/* (ndarray/ones shape) 8)]) +(kvstore/pull kv ["3"] [a]) +(ndarray/->vec a);=>[8.0 8.0 8.0 8.0 8.0 8.0] +``` + +The data that you want to push can be stored on any device. Furthermore, you can push multiple +values into the same key, where KVStore first sums all of these +values, and then pushes the aggregated value, as follows (Here we use multiple cpus): + +```clojure +(def cpus [(context/cpu 0) (context/cpu 1) (context/cpu 2)]) +(def b [(ndarray/ones shape {:ctx (nth cpus 0)}) + (ndarray/ones shape {:ctx (nth cpus 1)}) + (ndarray/ones shape {:ctx (nth cpus 2)})]) +(kvstore/push kv ["3" "3" "3"] b) +(kvstore/pull kv "3" a) +(ndarray/->vec a) ;=> [3.0 3.0 3.0 3.0 3.0 3.0] +``` + + +### Pull + +You've already seen how to pull a single key-value pair. Similar to the way that you use the push command, you can +pull the value into several devices with a single call. + +```clojure +(def b [(ndarray/ones shape {:ctx (context/cpu 0)}) + (ndarray/ones shape {:ctx (context/cpu 1)})]) +(kvstore/pull kv ["3" "3"] b) +(map ndarray/->vec b) ;=> ([3.0 3.0 3.0 3.0 3.0 3.0] [3.0 3.0 3.0 3.0 3.0 3.0]) +``` + +## List Key-Value Pairs + +All of the operations that we've discussed so far are performed on a single key. KVStore also provides +the interface for generating a list of key-value pairs. For a single device, use the following: + +```clojure +(def ks ["5" "7" "9"]) +(kvstore/init kv ks [(ndarray/ones shape) (ndarray/ones shape) (ndarray/ones shape)]) +(kvstore/push kv ks [(ndarray/ones shape) (ndarray/ones shape) (ndarray/ones shape)]) +(def b [(ndarray/zeros shape) (ndarray/zeros shape) (ndarray/zeros shape)]) +(kvstore/pull kv ks b) +(map ndarray/->vec b);=> ([1.0 1.0 1.0 1.0 1.0 1.0] [1.0 1.0 1.0 1.0 1.0 1.0] [1.0 1.0 1.0 1.0 1.0 1.0]) +``` diff --git a/docs/static_site/src/pages/api/clojure/docs/tutorials/module.md b/docs/static_site/src/pages/api/clojure/docs/tutorials/module.md new file mode 100644 index 000000000000..013e6ec44820 --- /dev/null +++ b/docs/static_site/src/pages/api/clojure/docs/tutorials/module.md @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + +--- +layout: page_api +title: Module API +is_tutorial: true +tag: clojure +permalink: /api/clojure/docs/tutorials/module +--- + +# Module API +The module API provides an intermediate and high-level interface for performing computation with neural networks in MXNet. Module wraps a Symbol and one or more Executors. It has both a high level and intermediate level API. + + +Topics: + +* [Prepare the Data](#prepare-the-data) +* [List Key-Value Pairs](#list-key-value-pairs) +* [Preparing a Module for Computation](#preparing-a-module-for-computation) +* [Training and Predicting](#training-and-predicting) +* [Saving and Loading](#saving-and-loading) +* [API Reference](http://mxnet.incubator.apache.org/api/clojure/docs/org.apache.clojure-mxnet.module.html) + + +To follow along with this documentation, you can use this namespace to with the needed requires: + +```clojure +(ns docs.module + (:require [clojure.java.io :as io] + [clojure.java.shell :refer [sh]] + [org.apache.clojure-mxnet.eval-metric :as eval-metric] + [org.apache.clojure-mxnet.io :as mx-io] + [org.apache.clojure-mxnet.module :as m] + [org.apache.clojure-mxnet.symbol :as sym] + [org.apache.clojure-mxnet.ndarray :as ndarray])) +``` + +## Prepare the Data + +In this example, we are going to use the MNIST data set. If you have cloned the MXNet repo and `cd contrib/clojure-package`, we can run some helper scripts to download the data for us. + +```clojure +(def data-dir "data/") + +(when-not (.exists (io/file (str data-dir "train-images-idx3-ubyte"))) + (sh "../../scripts/get_mnist_data.sh")) +``` + +MXNet provides function in the `io` namespace to load the MNIST datasets into training and test data iterators that we can use with our module. + +```clojure +(def train-data (mx-io/mnist-iter {:image (str data-dir "train-images-idx3-ubyte") + :label (str data-dir "train-labels-idx1-ubyte") + :label-name "softmax_label" + :input-shape [784] + :batch-size 10 + :shuffle true + :flat true + :silent false + :seed 10})) + +(def test-data (mx-io/mnist-iter {:image (str data-dir "t10k-images-idx3-ubyte") + :label (str data-dir "t10k-labels-idx1-ubyte") + :input-shape [784] + :batch-size 10 + :flat true + :silent false})) +``` + + +## Preparing a Module for Computation + +To construct a module, we need to have a symbol as input. This symbol takes input data in the first layer and then has subsequent layers of fully connected and relu activation layers, ending up in a softmax layer for output. + +```clojure +(let [data (sym/variable "data") + fc1 (sym/fully-connected "fc1" {:data data :num-hidden 128}) + act1 (sym/activation "relu1" {:data fc1 :act-type "relu"}) + fc2 (sym/fully-connected "fc2" {:data act1 :num-hidden 64}) + act2 (sym/activation "relu2" {:data fc2 :act-type "relu"}) + fc3 (sym/fully-connected "fc3" {:data act2 :num-hidden 10}) + out (sym/softmax-output "softmax" {:data fc3})] + out) + ;=>#object[org.apache.mxnet.Symbol 0x1f43a406 "org.apache.mxnet.Symbol@1f43a406"] +``` + +You can also write this with the `as->` threading macro. + +```clojure +(def out (as-> (sym/variable "data") data + (sym/fully-connected "fc1" {:data data :num-hidden 128}) + (sym/activation "relu1" {:data data :act-type "relu"}) + (sym/fully-connected "fc2" {:data data :num-hidden 64}) + (sym/activation "relu2" {:data data :act-type "relu"}) + (sym/fully-connected "fc3" {:data data :num-hidden 10}) + (sym/softmax-output "softmax" {:data data}))) +;=> #'tutorial.module/out +``` + + +By default, `context` is the CPU. If you need data parallelization, you can specify a GPU context or an array of GPU contexts like this `(m/module out {:contexts [(context/gpu)]})` + +Before you can compute with a module, you need to call `bind` to allocate the device memory and `init-params` or `set-params` to initialize the parameters. If you simply want to fit a module, you don’t need to call `bind` and `init-params` explicitly, because the `fit` function automatically calls them if they are needed. + +```clojure +(let [mod (m/module out)] + (-> mod + (m/bind {:data-shapes (mx-io/provide-data train-data) + :label-shapes (mx-io/provide-label train-data)}) + (m/init-params))) +``` + +Now you can compute with the module using functions like `forward`, `backward`, etc. + +## Training and Predicting + +Modules provide high-level APIs for training, predicting, and evaluating. To fit a module, call the `fit` function with some data iterators: + +```clojure +(def mod (m/fit (m/module out) {:train-data train-data :eval-data test-data :num-epoch 1})) +;; Epoch 0 Train- [accuracy 0.12521666] +;; Epoch 0 Time cost- 8392 +;; Epoch 0 Validation- [accuracy 0.2227] +``` + +You can pass in batch-end callbacks using batch-end-callback and epoch-end callbacks using epoch-end-callback in the `fit-params`. You can also set parameters using functions like in the fit-params like optimizer and eval-metric. To learn more about the fit-params, see the fit-param function options. To predict with a module, call `predict` with a DataIter: + +```clojure +(def results (m/predict mod {:eval-data test-data})) +(first results) ;=>#object[org.apache.mxnet.NDArray 0x3540b6d3 "org.apache.mxnet.NDArray@a48686ec"] + +(first (ndarray/->vec (first results))) ;=>0.08261358 +``` + +The module collects and returns all of the prediction results. For more details about the format of the return values, see the documentation for the [`predict`](docs/org.apache.clojure-mxnet.module.html#var-fit-params) function. + +When prediction results might be too large to fit in memory, use the [`predict-every-batch`](docs/org.apache.clojure-mxnet.module.html#predict-every-batch) API. + +```clojure +(let [preds (m/predict-every-batch mod {:eval-data test-data})] + (mx-io/reduce-batches test-data + (fn [i batch] + (println (str "pred is " (first (get preds i)))) + (println (str "label is " (mx-io/batch-label batch))) + ;;; do something + (inc i)))) +``` + +If you need to evaluate on a test set and don’t need the prediction output, call the `score` function with a data iterator and an eval metric: + +```clojure +(m/score mod {:eval-data test-data :eval-metric (eval-metric/accuracy)}) ;=>["accuracy" 0.2227] +``` + +This runs predictions on each batch in the provided data iterator and computes the evaluation score using the provided eval metric. The evaluation results are stored in `eval-metric` object itself so that you can query later. + + +## Saving and Loading + +To save the module parameters in each training epoch, use the `save-checkpoint` function: + +```clojure +(let [save-prefix "my-model"] + (doseq [epoch-num (range 3)] + (mx-io/do-batches train-data (fn [batch + ;; do something +])) + (m/save-checkpoint mod {:prefix save-prefix :epoch epoch-num :save-opt-states true}))) + +;; INFO org.apache.mxnet.module.Module: Saved checkpoint to my-model-0000.params +;; INFO org.apache.mxnet.module.Module: Saved optimizer state to my-model-0000.states +;; INFO org.apache.mxnet.module.Module: Saved checkpoint to my-model-0001.params +;; INFO org.apache.mxnet.module.Module: Saved optimizer state to my-model-0001.states +;; INFO org.apache.mxnet.module.Module: Saved checkpoint to my-model-0002.params +;; INFO org.apache.mxnet.module.Module: Saved optimizer state to my-model-0002.states + +``` + +To load the saved module parameters, call the `load-checkpoint` function: + +```clojure +(def new-mod (m/load-checkpoint {:prefix "my-model" :epoch 1 :load-optimizer-states true})) + +new-mod ;=> #object[org.apache.mxnet.module.Module 0x5304d0f4 "org.apache.mxnet.module.Module@5304d0f4"] +``` + +To initialize parameters, Bind the symbols to construct executors first with `bind` function. Then, initialize the parameters and auxiliary states by calling `init-params` function. + +```clojure +(-> new-mod + (m/bind {:data-shapes (mx-io/provide-data train-data) :label-shapes (mx-io/provide-label train-data)}) + (m/init-params)) +``` + +To get current parameters, use `params` + +```clojure + +(let [[arg-params aux-params] (m/params new-mod)] + {:arg-params arg-params + :aux-params aux-params}) + +;; {:arg-params +;; {"fc3_bias" +;; #object[org.apache.mxnet.NDArray 0x39adc3b0 "org.apache.mxnet.NDArray@49caf426"], +;; "fc2_weight" +;; #object[org.apache.mxnet.NDArray 0x25baf623 "org.apache.mxnet.NDArray@a6c8f9ac"], +;; "fc1_bias" +;; #object[org.apache.mxnet.NDArray 0x6e089973 "org.apache.mxnet.NDArray@9f91d6eb"], +;; "fc3_weight" +;; #object[org.apache.mxnet.NDArray 0x756fd109 "org.apache.mxnet.NDArray@2dd0fe3c"], +;; "fc2_bias" +;; #object[org.apache.mxnet.NDArray 0x1dc69c8b "org.apache.mxnet.NDArray@d128f73d"], +;; "fc1_weight" +;; #object[org.apache.mxnet.NDArray 0x20abc769 "org.apache.mxnet.NDArray@b8e1c5e8"]}, +;; :aux-params {}} + +``` + +To assign parameter and aux state values, use the `set-params` function. + +```clojure +(m/set-params new-mod {:arg-params (m/arg-params new-mod) :aux-params (m/aux-params new-mod)}) +;=> #object[org.apache.mxnet.module.Module 0x5304d0f4 "org.apache.mxnet.module.Module@5304d0f4"] +``` + +To resume training from a saved checkpoint, pass the loaded parameters to the `fit` function. This will prevent `fit` from initialzing randomly. + +Create fit-params and then use it to set `begin-epoch` so that `fit` knows to resume from a saved epoch. + +```clojure +;; reset the training data before calling fit or you will get an error +(mx-io/reset train-data) +(mx-io/reset test-data) + +(m/fit new-mod {:train-data train-data :eval-data test-data :num-epoch 2 + :fit-params (-> (m/fit-params {:begin-epoch 1}))}) + +``` + + +## Next Steps +* See [Symbolic API](symbol.md) for operations on NDArrays that assemble neural networks from layers. +* See [NDArray API](ndarray.md) for vector/matrix/tensor operations. +* See [KVStore API](kvstore.md) for multi-GPU and multi-host distributed training. diff --git a/docs/static_site/src/pages/api/clojure/docs/tutorials/ndarray.md b/docs/static_site/src/pages/api/clojure/docs/tutorials/ndarray.md new file mode 100644 index 000000000000..a989d7482ecf --- /dev/null +++ b/docs/static_site/src/pages/api/clojure/docs/tutorials/ndarray.md @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + +--- +layout: page_api +title: NDArray +is_tutorial: true +tag: clojure +permalink: /api/clojure/docs/tutorials/ndarray +--- + +# NDArray API + + +The NDArray API contains tensor operations similar to `numpy.ndarray`. The syntax is also similar, except for some additional calls for dealing with I/O and multiple devices. + +Topics: + +* [Create NDArray](#create-ndarray) +* [NDArray Operations](#ndarray-operations) +* [NDArray API Reference](http://mxnet.incubator.apache.org/api/clojure/docs/org.apache.clojure-mxnet.ndarray.html) + +To follow along with this documentation, you can use this namespace with the needed requires: + +```clojure +(ns docs.ndarray + (:require [org.apache.clojure-mxnet.ndarray :as ndarray] + [org.apache.clojure-mxnet.context :as context])) +``` + + +## Create NDArray + +Create `mxnet.ndarray` as follows: + +```clojure + +(def a (ndarray/zeros [100 50])) ;;all zero arrray of dimension 100 x 50 +(def b (ndarray/ones [256 32 128 1])) ;; all one array of dimension +(def c (ndarray/array [1 2 3 4 5 6] [2 3])) ;; array with contents of a shape 2 x 3 +``` + +There are also ways to convert a NDArray to a vec or get the shape or the NDArray as an object or vec as follows: + +```clojure +(ndarray/->vec c) ;=> [1.0 2.0 3.0 4.0 5.0 6.0] +(ndarray/shape c) ;=> #object[org.apache.mxnet.Shape 0x583c865 "(2,3)"] +(ndarray/shape-vec c) ;=> [2 3] +``` + + +## NDArray Operations + +There are some basic NDArray operations, like arithmetic and slice operations. + +### Arithmetic Operations + +```clojure +(def a (ndarray/ones [1 5])) +(def b (ndarray/ones [1 5])) +(-> (ndarray/+ a b) (ndarray/->vec)) ;=> [2.0 2.0 2.0 2.0 2.0] + +;; original ndarrays are unchanged +(ndarray/->vec a) ;=> [1.0 1.0 1.0 1.0 1.0] +(ndarray/->vec b) ;=> [1.0 1.0 1.0 1.0 1.0] + +;;inplace operators +(ndarray/+= a b) +(ndarray/->vec a) ;=> [2.0 2.0 2.0 2.0 2.0] +``` + +Other arithmetic operations are similar. + + +### Slice Operations + +```clojure +(def a (ndarray/array [1 2 3 4 5 6] [3 2])) +(def a1 (ndarray/slice a 1)) +(ndarray/shape-vec a1) ;=> [1 2] +(ndarray/->vec a1) ;=> [3.0 4.0] + +(def a2 (ndarray/slice a 1 3)) +(ndarray/shape-vec a2) ;=>[2 2] +(ndarray/->vec a2) ;=> [3.0 4.0 5.0 6.0] +``` + +### Dot Product + +```clojure +(def arr1 (ndarray/array [1 2] [1 2])) +(def arr2 (ndarray/array [3 4] [2 1])) +(def res (ndarray/dot arr1 arr2)) +(ndarray/shape-vec res) ;=> [1 1] +(ndarray/->vec res) ;=> [11.0] +``` + +### Save and Load NDArray + +You can use MXNet functions to save and load a list or dictionary of NDArrays from file systems, as follows: + +```clojure +(ndarray/save "filename" {"arr1" arr1 "arr2" arr2}) +;; you can also do "s3://path" or "hdfs" +``` + +To load: + +```clojure +(def from-file (ndarray/load "filename")) +from-file +;=>{"arr1" #object["org.apache.mxnet.NDArray@43d85753"], "arr2" #object["org.apache.mxnet.NDArray@5c93def4"]} +``` + +The good thing about using the `save` and `load` interface is that you can use the format across all `mxnet` language bindings. They also already support Amazon S3 and HDFS. + +### Multi-Device Support + +Device information is stored in the `mxnet.Context` structure. When creating NDArray in MXNet, you can use the context argument (the default is the CPU context) to create arrays on specific devices as follows: + +```clojure +(def cpu-a (ndarray/zeros [100 200])) +(ndarray/context cpu-a) ;=> #object[org.apache.mxnet.Context 0x3f376123 "cpu(0)"] + +(def gpu-b (ndarray/zeros [100 200] {:ctx (context/gpu 0)})) ;; to use with gpu + +``` + +## Next Steps +* See [KVStore API](kvstore.md) for multi-GPU and multi-host distributed training. diff --git a/docs/static_site/src/pages/api/clojure/docs/tutorials/symbol.md b/docs/static_site/src/pages/api/clojure/docs/tutorials/symbol.md new file mode 100644 index 000000000000..352b0406bd60 --- /dev/null +++ b/docs/static_site/src/pages/api/clojure/docs/tutorials/symbol.md @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + +--- +layout: page_api +title: Symbolic API +is_tutorial: true +tag: clojure +permalink: /api/clojure/docs/tutorials/symbol +--- + +# MXNet Clojure Symbolic API + +Topics: + +* [How to Compose Symbols](#how-to-compose-symbols) +* [More Complicated Compositions](#more-complicated-compositions) +* [Group Multiple Symbols](#group-multiple-symbols) +* [Serialization](#serialization) +* [Executing Symbols](#executing-symbols) +* [Symbol API Reference](http://mxnet.incubator.apache.org/api/clojure/docs/org.apache.clojure-mxnet.symbol.html) + + +We also highly encourage you to read [Symbolic Configuration and Execution in Pictures](symbol_in_pictures.md). + +To follow along with this documentation, you can use this namespace to with the following requirements: + +```clojure +(ns docs.symbol + (:require [org.apache.clojure-mxnet.executor :as executor] + [org.apache.clojure-mxnet.ndarray :as ndarray] + [org.apache.clojure-mxnet.symbol :as sym] + [org.apache.clojure-mxnet.context :as context])) +``` + + +## How to Compose Symbols + +The Symbolic API provides a way to configure computation graphs. +You can configure the graphs either at the level of neural network layer operations or as fine-grained operations. + +The following example configures a two-layer neural network. + +```clojure +(def data (sym/variable "data")) +(def fc1 (sym/fully-connected "fc1" {:data data :num-hidden 128})) +(def act1 (sym/activation "act1" {:data fc1 :act-type "relu"})) +(def fc2 (sym/fully-connected "fc2" {:data act1 :num-hidden 64})) +(def net (sym/softmax-output "out" {:data fc2})) +``` + +This can also be combined more dynamically with the `as->` Clojure threading form. + +```clojure +(as-> (sym/variable "data") data + (sym/fully-connected "fc1" {:data data :num-hidden 128}) + (sym/activation "act1" {:data data :act-type "relu"}) + (sym/fully-connected "fc2" {:data data :num-hidden 64}) + (sym/softmax-output "out" {:data data})) + +net ;=> #object[org.apache.mxnet.Symbol 0x5c78c8c2 "org.apache.mxnet.Symbol@5c78c8c2"] +``` + +The basic arithmetic operators (plus, minus, div, multiplication) work as expected. + +The following example creates a computation graph that adds two inputs together. + +```clojure +(def a (sym/variable "a")) +(def b (sym/variable "b")) +(def c (sym/+ a b)) +``` + +## More Complicated Compositions + +MXNet provides well-optimized symbols for layers commonly used in deep learning (see src/operator). We can also define new operators in Python. The following example first performs an element-wise add between two symbols, then feeds them to the fully connected operator: + +```clojure +(def lhs (sym/variable "data1")) +(def rhs (sym/variable "data2")) +(def net (sym/fully-connected "fc1" {:data (sym/+ lhs rhs) :num-hidden 128})) +(sym/list-arguments net) ;=> ["data1" "data2" "fc1_weight" "fc1_bias"] +``` + +## Group Multiple Symbols + +To construct neural networks with multiple loss layers, we can use `group` to group multiple symbols together. The following example groups two outputs: + +```clojure +(def net (sym/variable "data")) +(def fc1 (sym/fully-connected {:data net :num-hidden 128})) +(def net2 (sym/activation {:data fc1 :act-type "relu"})) +(def out1 (sym/softmax-output {:data net2})) +(def out2 (sym/linear-regression-output {:data net2})) +(def group (sym/group [out1 out2])) +(sym/list-outputs group) +;=> ["softmaxoutput0_output" "linearregressionoutput0_output"] +``` + +## Serialization +You can use the [`save`](docs/org.apache.clojure-mxnet.symbol.html#var-save) and [`load`](docs/org.apache.clojure-mxnet.symbol.html#var-load) functions to serialize the Symbol objects. The advantage of using save and load functions is that it is language agnostic and cloud friendly. The symbol is saved in JSON format. You can also get a JSON string directly using mxnet.Symbol.toJson. Refer to API documentation for more details. + + The following example shows how to save a symbol to a file, load it back, and compare two symbols using a JSON string. You can also save to S3 as well + +```clojure +(def a (sym/variable "a")) +(def b (sym/variable "b")) +(def c (sym/+ a b)) +(sym/save c "symbol-c.json") +(def c2 (sym/load "symbol-c.json")) +(= (sym/to-json c) (sym/to-json c2)) ;=>true +``` + + +## Executing Symbols + +To execute symbols, first we need to define the data that they should run on. We can do it by using the bind method, which accepts device context and a dict mapping free variable names to NDArrays as arguments and returns an executor. The executor provides forward method for evaluation and an attribute outputs to get all the results. + +```clojure +(def a (sym/variable "a")) +(def b (sym/variable "b")) +(def c (sym/+ a b)) + +(def ex (sym/bind c {"a" (ndarray/ones [2 2]) "b" (ndarray/ones [2 2])})) +(-> (executor/forward ex) + (executor/outputs) + (first) + (ndarray/->vec));=> [2.0 2.0 2.0 2.0] +``` + +We can evaluate the same symbol on GPU with different data. +_To do this you must have the correct native library jar defined as a dependency_ + +**Note In order to execute the following section on a cpu set gpu_device to (cpu)** + + +```clojure +(def ex (sym/bind c (context/gpu 0) {"a" (ndarray/ones [2 2]) "b" (ndarray/ones [2 2])})) +``` + +## Next Steps +* See [NDArray API](ndarray.md) for vector/matrix/tensor operations. +* See [KVStore API](kvstore.md) for multi-GPU and multi-host distributed training. diff --git a/docs/static_site/src/pages/api/clojure/docs/tutorials/symbol_in_pictures.md b/docs/static_site/src/pages/api/clojure/docs/tutorials/symbol_in_pictures.md new file mode 100644 index 000000000000..09c9cfce02f7 --- /dev/null +++ b/docs/static_site/src/pages/api/clojure/docs/tutorials/symbol_in_pictures.md @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + +--- +layout: page_api +title: Symbolic API with Pictures +is_tutorial: true +tag: clojure +permalink: /api/clojure/docs/tutorials/symbol_in_pictures +--- + +# Symbolic Configuration and Execution in Pictures + +This topic explains symbolic construction and execution in pictures. + +We recommend that you read the [Symbolic API](symbol.md) as another useful reference. + +## Compose Symbols + +Symbols are a description of the computation that you want to perform. The symbolic construction API generates the computation +graph that describes the computation. The following picture shows how you compose symbols to describe basic computations. + +![Symbol Compose](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/symbol/compose_basic.png) + +- The ```mxnet.Symbol.Variable``` function creates argument nodes that represent input to the computation. +- The symbol is overloaded with basic element-wise mathematical operations. + +## Configure Neural Networks + +In addition to supporting fine-grained operations, MXNet provides a way to perform big operations that are analogous to layers in neural networks. +You can use operators to describe the configuration of a neural network. + +![Net Compose](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/symbol/compose_net.png) + + +## Example of a Multi-Input Network + +The following example shows how to configure multiple input neural networks. + +![Multi Input](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/symbol/compose_multi_in.png) + + +## Bind and Execute Symbol + +When you need to execute a symbol graph, you call the bind function to bind ```NDArrays``` to the argument nodes +in order to obtain an ```Executor```. + +![Bind](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/symbol/bind_basic.png) + +To get the output results, given the bound NDArrays as input, you can call ```Executor.Forward```. + +![Forward](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/symbol/executor_forward.png) + + +## Bind Multiple Outputs + +To group symbols, then bind them to get outputs of both, use ```mx.symbol.Group```. + +![MultiOut](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/symbol/executor_multi_out.png) + +Remember: Bind only what you need, so that the system can perform more optimizations. + + +## Calculate the Gradient + +In the bind function, you can specify NDArrays that will hold gradients. Calling ```Executor.backward``` after ```Executor.forward``` gives you the corresponding gradients. + +![Gradient](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/symbol/executor_backward.png) + + +## Simple Bind Interface for Neural Networks + +It can be tedious to pass the argument NDArrays to the bind function, especially when you are binding a big +graph. ```Symbol.simple_bind``` provides a way to simplify +the procedure. You need to specify only input data shapes. The function allocates the arguments, and binds +the Executor for you. + +![SimpleBind](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/symbol/executor_simple_bind.png) + +## Auxiliary States + +Auxiliary states are just like arguments, except that you can't take the gradient of them. Although auxiliary states might not be part of the computation, they can be helpful for tracking. You can pass auxiliary states in the same way that you pass arguments. + +![SimpleBind](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/symbol/executor_aux_state.png) + +## Next Steps + +See [Symbolic API](symbol.md) and [Python Documentation](index.md). diff --git a/docs/static_site/src/pages/api/clojure/index.md b/docs/static_site/src/pages/api/clojure/index.md new file mode 100644 index 000000000000..af535c8614fa --- /dev/null +++ b/docs/static_site/src/pages/api/clojure/index.md @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + +--- +layout: page_api +title: Clojure Guide +action: Get Started +action_url: /get_started +permalink: /api/clojure +tag: clojure +--- + +# MXNet - Clojure API + +MXNet supports the Clojure programming language. The MXNet Clojure package brings flexible and efficient GPU +computing and state-of-art deep learning to Clojure. It enables you to write seamless tensor/matrix computation with multiple GPUs in Clojure. It also lets you construct and customize the state-of-art deep learning models in Clojure, and apply them to tasks, such as image classification and data science challenges. + + +## Tensor and Matrix Computations +You can perform tensor or matrix computation in pure Clojure: + +```clojure +(def arr (ndarray/ones [2 3])) + +arr ;=> #object[org.apache.mxnet.NDArray 0x597d72e "org.apache.mxnet.NDArray@e35c3ba9"] + +(ndarray/shape-vec arr) ;=> [2 3] + +(-> (ndarray/* arr 2) + (ndarray/->vec)) ;=> [2.0 2.0 2.0 2.0 2.0 2.0] + +(ndarray/shape-vec (ndarray/* arr 2)) ;=> [2 3] + +``` diff --git a/docs/static_site/src/pages/api/cpp/docs/tutorials/basics.md b/docs/static_site/src/pages/api/cpp/docs/tutorials/basics.md new file mode 100644 index 000000000000..aec71d013ee6 --- /dev/null +++ b/docs/static_site/src/pages/api/cpp/docs/tutorials/basics.md @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + +--- +layout: page_api +title: Basics +action: Get Started +action_url: /get_started +permalink: /api/cpp/docs/tutorials/basics +is_tutorial: true +tag: cpp +--- + +Basics +====== + +This tutorial provides basic usages of the C++ package through the classical handwritten digits +identification database--[MNIST](http://yann.lecun.com/exdb/mnist/). + +The following contents assume that the working directory is `/path/to/mxnet/cpp-package/example`. + +Load Data +-------- +Before going into codes, we need to fetch MNIST data. You can either use the script `/path/to/mxnet/cpp-package/example/get_data.sh`, +or download mnist data by yourself from Lecun's [website](http://yann.lecun.com/exdb/mnist/) +and decompress them into `data/mnist_data` folder. + +Except linking the MXNet shared library, the C++ package itself is a header-only package, +which means all you need to do is to include the header files. Among the header files, +`op.h` is special since it is generated dynamically. The generation should be done when +[building the C++ package](http://mxnet.incubator.apache.org/versions/master/api/c++/index.html). +It is important to note that you need to **copy the shared library** (`libmxnet.so` in Linux and MacOS, +`libmxnet.dll` in Windows) from `/path/to/mxnet/lib` to the working directory. +We do not recommend you to use pre-built binaries because MXNet is under heavy development, +the operator definitions in `op.h` may be incompatible with the pre-built version. + +In order to use functionalities provides by the C++ package, first we include the general +header file `MxNetCpp.h` and specify the namespaces. + +```c++ +#include "mxnet-cpp/MxNetCpp.h" + +using namespace std; +using namespace mxnet::cpp; +``` + +Next we can use the data iter to load MNIST data (separated to training sets and validation sets). +The digits in MNIST are 2-dimension arrays, so we should set `flat` to true to flatten the data. + +```c++ +auto train_iter = MXDataIter("MNISTIter") + .SetParam("image", "./data/mnist_data/train-images-idx3-ubyte") + .SetParam("label", "./data/mnist_data/train-labels-idx1-ubyte") + .SetParam("batch_size", batch_size) + .SetParam("flat", 1) + .CreateDataIter(); +auto val_iter = MXDataIter("MNISTIter") + .SetParam("image", "./data/mnist_data/t10k-images-idx3-ubyte") + .SetParam("label", "./data/mnist_data/t10k-labels-idx1-ubyte") + .SetParam("batch_size", batch_size) + .SetParam("flat", 1) + .CreateDataIter(); +``` + +The data have been successfully loaded. We can now easily construct various models to identify +the digits with the help of C++ package. + + +Multilayer Perceptron +--------------------- +If you are not familiar with multilayer perceptron, you can get some basic information +[here](http://mxnet.io/tutorials/python/mnist.html#multilayer-perceptron). We only focus on +the implementation in this tutorial. + +Constructing multilayer perceptron model is straightforward, assume we store the hidden size +for each layer in `layers`, and each layer uses +[ReLu](https://en.wikipedia.org/wiki/Rectifier_(neural_networks)) function as activation. + +```c++ +Symbol mlp(const vector &layers) { + auto x = Symbol::Variable("X"); + auto label = Symbol::Variable("label"); + + vector weights(layers.size()); + vector biases(layers.size()); + vector outputs(layers.size()); + + for (int i=0; i
\ No newline at end of file diff --git a/docs/static_site/src/pages/get_started/build_from_source.md b/docs/static_site/src/pages/get_started/build_from_source.md new file mode 100644 index 000000000000..af07c38e105a --- /dev/null +++ b/docs/static_site/src/pages/get_started/build_from_source.md @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: Building From Source +action: Get Started +action_url: /get_started +permalink: /get_started/build_from_source +--- + + +# Build MXNet from Source + +This document explains how to build MXNet from source code. + +**For Java/Scala/Clojure, please follow [this guide instead](./scala_setup.md)** + +## Overview + +Building from source follows this general two-step flow of building the shared library, then installing your preferred language binding. Use the following links to jump to the different sections of this guide. + +1. Build the MXNet shared library, `libmxnet.so`. + * [Clone the repository](#clone-the-mxnet-project) + * [Prerequisites](#prerequisites) + * [Math library selection](#math-library-selection) + * [Install GPU software](#install-gpu-software) + * [Install optional software](#install-optional-software) + * [Adjust your build configuration](#build-configurations) + * [Build MXNet](#build-mxnet) + * [with NCCL](#build-mxnet-with-nccl) (optional) + * [for C++](#build-mxnet-with-c++) (optional) + * [Usage Examples](#usage-examples) + * [systems with GPUs and Intel CPUs](#recommended-for-Systems-with-NVIDIA-GPUs-and-Intel-CPUs) + * [GPUs with non-Intel CPUs](#recommended-for-Systems-with-Intel-CPUs) + * [Intel CPUs](#recommended-for-Systems-with-Intel-CPUs) + * [non-Intel CPUs](#recommended-for-Systems-with-non-Intel-CPUs) +2. [Install the language API binding(s)](#installing-mxnet-language-bindings) you would like to use for MXNet. +MXNet's newest and most popular API is Gluon. Gluon is built into the Python binding. If Python isn't your preference, you still have more options. MXNet supports several other language APIs: + - [Python (includes Gluon)](../api/python/index.md) + - [C++](../api/cpp/index.md) + - [Clojure](../api/clojure/index.md) + - [Java](../api/java/index.md) + - [Julia](../api/julia/index.md) + - [Perl](../api/perl/index.md) + - [R](../api/r/index.md) + - [Scala](../api/scala/index.md) + +
+ +## Build Instructions by Operating System + +Detailed instructions are provided per operating system. Each of these guides also covers how to install the specific [Language Bindings](#installing-mxnet-language-bindings) you require. +You may jump to those, but it is recommended that you continue reading to understand more general "build from source" options. + +* [Amazon Linux / CentOS / RHEL](centos_setup.md) +* [macOS](osx_setup.md) +* [Devices](https://mxnet.incubator.apache.org/versions/master/install/index.html?platform=Devices&language=Python&processor=CPU) +* [Ubuntu](ubuntu_setup.md) +* [Windows](windows_setup.md) + + +
+ +## Clone the MXNet Project + +1. Clone or fork the MXNet project. +```bash +git clone --recursive https://github.com/apache/incubator-mxnet mxnet +cd mxnet +``` + +
+ +## Prerequisites + +The following sections will help you decide which specific prerequisites you need to install. + +#### Math Library Selection +It is useful to consider your math library selection prior to your other prerequisites. +MXNet relies on the +[BLAS](https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms) (Basic +Linear Algebra Subprograms) library for numerical computations. +Those can be extended with [LAPACK (Linear Algebra Package)](https://github.com/Reference-LAPACK/lapack), an additional set of mathematical functions. + +MXNet supports multiple mathematical backends for computations on the CPU: +* [Apple Accelerate](https://developer.apple.com/documentation/accelerate) +* [ATLAS](http://math-atlas.sourceforge.net/) +* [MKL](https://software.intel.com/en-us/intel-mkl) (MKL, MKLML) +* [MKL-DNN](https://github.com/intel/mkl-dnn) +* [OpenBLAS](http://www.openblas.net/) + +The default order of choice for the libraries if found follows the path from the most +(recommended) to less performant backends. +The following lists show this order by library and `cmake` switch. + +For desktop platforms (x86_64): + +1. MKL-DNN (submodule) | `USE_MKLDNN` +2. MKL | `USE_MKL_IF_AVAILABLE` +3. MKLML (downloaded) | `USE_MKLML` +4. Apple Accelerate | `USE_APPLE_ACCELERATE_IF_AVAILABLE` | Mac only +5. OpenBLAS | `BLAS` | Options: Atlas, Open, MKL, Apple + +Note: If `USE_MKL_IF_AVAILABLE` is set to False then MKLML and MKL-DNN will be disabled as well for configuration +backwards compatibility. + +For embedded platforms (all other and if cross compiled): + +1. OpenBLAS | `BLAS` | Options: Atlas, Open, MKL, Apple + +You can set the BLAS library explicitly by setting the BLAS variable to: + +* Atlas +* Open +* MKL +* Apple + +See the [cmake/ChooseBLAS.cmake](https://github.com/apache/incubator-mxnet/blob/master/cmake/ChooseBlas.cmake) file for the options. + +[Intel's MKL (Math Kernel Library)](https://software.intel.com/en-us/mkl) is one of the most powerful math libraries + +It has following flavors: + +* MKL is a complete math library, containing all the functionality found in ATLAS, OpenBlas and LAPACK. It is free under +community support licensing (https://software.intel.com/en-us/articles/free-mkl), +but needs to be downloaded and installed manually. + +* MKLML is a subset of MKL. It contains a smaller number of functions to reduce the +size of the download and reduce the number of dynamic libraries user needs. + + + +* MKL-DNN is a separate open-source library, it can be used separately from MKL or MKLML. It is +shipped as a subrepo with MXNet source code (see 3rdparty/mkldnn or the [MKL-DNN project](https://github.com/intel/mkl-dnn)) + +Since the full MKL library is almost always faster than any other BLAS library it's turned on by default, +however it needs to be downloaded and installed manually before doing `cmake` configuration. +Register and download on the [Intel performance libraries website](https://software.intel.com/en-us/performance-libraries). +You can also install MKL through [YUM](https://software.intel.com/en-us/articles/installing-intel-free-libs-and-python-yum-repo) +or [APT](https://software.intel.com/en-us/articles/installing-intel-free-libs-and-python-apt-repo) Repository. + +Note: MKL is supported only for desktop builds and the framework itself supports the following +hardware: + +* Intel® Xeon Phi™ processor +* Intel® Xeon® processor +* Intel® Core™ processor family +* Intel Atom® processor + +If you have a different processor you can still try to use MKL, but performance results are +unpredictable. + + +#### Install GPU Software + +If you want to run MXNet with GPUs, you must install [NVDIA CUDA and cuDNN](https://developer.nvidia.com/cuda-downloads). + + +#### Install Optional Software + +These might be optional, but they're typically desirable as the extend or enhance MXNet's functionality. + +* [OpenCV](http://opencv.org/) - Image Loading and Augmentation. Each operating system has different packages and build from source options for OpenCV. Refer to your OS's link in the [Build Instructions by Operating System](#build-instructions-by-operating-system) section for further instructions. +* [NCCL](https://developer.nvidia.com/nccl) - NVIDIA's Collective Communications Library. Instructions for installing NCCL are found in the following [Build MXNet with NCCL](#build-mxnet-with-nccl) section. + +More information on turning these features on or off are found in the following [build configurations](#build-configurations) section. + + +
+ +## Build Configurations + +There is a configuration file for make, +[`make/config.mk`](https://github.com/apache/incubator-mxnet/blob/master/make/config.mk), that contains all the compilation options. You can edit it and then run `make` or `cmake`. `cmake` is recommended for building MXNet (and is required to build with MKLDNN), however you may use `make` instead. For building with Java/Scala/Clojure, only `make` is supported. + +**NOTE:** When certain set of build flags are set, MXNet archive increases to more than 4 GB. Since MXNet uses archive internally archive runs into a bug ("File Truncated": [bugreport](https://sourceware.org/bugzilla/show_bug.cgi?id=14625)) for archives greater than 4 GB. Please use ar version 2.27 or greater to overcome this bug. Please see https://github.com/apache/incubator-mxnet/issues/15084 for more details. + +
+ +## Build MXNet + +### Build MXNet with NCCL +- Download and install the latest NCCL library from NVIDIA. +- Note the directory path in which NCCL libraries and header files are installed. +- Ensure that the installation directory contains ```lib``` and ```include``` folders. +- Ensure that the prerequisites for using NCCL such as Cuda libraries are met. +- Append the ```config.mk``` file with following, in addition to the CUDA related options. +- USE_NCCL=1 +- USE_NCCL_PATH=path-to-nccl-installation-folder + +``` bash +echo "USE_NCCL=1" >> make/config.mk +echo "USE_NCCP_PATH=path-to-nccl-installation-folder" >> make/config.mk +cp make/config.mk . +``` +- Run make command +``` bash +make -j"$(nproc)" +``` + +#### Validating NCCL +- Follow the steps to install MXNet Python binding. +- Comment the following line in ```test_nccl.py``` file at ```incubator-mxnet/tests/python/gpu/test_nccl.py``` +``` bash +@unittest.skip("Test requires NCCL library installed and enabled during build") +``` +- Run test_nccl.py script as follows. The test should complete. It does not produce any output. +``` bash +nosetests --verbose tests/python/gpu/test_nccl.py +``` + +**Recommendation to get the best performance out of NCCL:** +It is recommended to set environment variable NCCL_LAUNCH_MODE to PARALLEL when using NCCL version 2.1 or newer. + +
+ +### Build MXNet with C++ + +* To enable C++ package, just add `USE_CPP_PACKAGE=1` when you run `make` or `cmake` (see examples). + +
+ +### Usage Examples + +For example, you can specify using all cores on Linux as follows: + +```bash +mkdir build && cd build +cmake -GNinja .. +ninja -v +``` + + +#### Recommended for Systems with NVIDIA GPUs and Intel CPUs +* Build MXNet with `cmake` and install with MKL DNN, GPU, and OpenCV support: + +```bash +mkdir build && cd build +cmake -DUSE_CUDA=1 -DUSE_CUDA_PATH=/usr/local/cuda -DUSE_CUDNN=1 -DUSE_MKLDNN=1 -GNinja .. +ninja -v +``` + +#### Recommended for Systems with NVIDIA GPUs +* Build with both OpenBLAS, GPU, and OpenCV support: + +```bash +mkdir build && cd build +cmake -DBLAS=open -DUSE_CUDA=1 -DUSE_CUDA_PATH=/usr/local/cuda -DUSE_CUDNN=1 -GNinja .. +ninja -v +``` + +#### Recommended for Systems with Intel CPUs +* Build MXNet with `cmake` and install with MKL DNN, and OpenCV support: + +```bash +mkdir build && cd build +cmake -DUSE_CUDA=0 -DUSE_MKLDNN=1 -GNinja .. +ninja -v +``` + +#### Recommended for Systems with non-Intel CPUs +* Build MXNet with `cmake` and install with OpenBLAS and OpenCV support: + +```bash +mkdir build && cd build +cmake -DUSE_CUDA=0 -DBLAS=open -GNinja .. +ninja -v +``` + +#### Other Examples + +* Build without using OpenCV: + +```bash +mkdir build && cd build +cmake -DUSE_OPENCV=0 -GNinja .. +ninja -v +``` + +* Build on **macOS** with the default BLAS library (Apple Accelerate) and Clang installed with `xcode` (OPENMP is disabled because it is not supported by the Apple version of Clang): + +```bash +mkdir build && cd build +cmake -DBLAS=apple -DUSE_OPENCV=0 -DUSE_OPENMP=0 -GNinja .. +ninja -v +``` + +* To use OpenMP on **macOS** you need to install the Clang compiler, `llvm` (the one provided by Apple does not support OpenMP): + +```bash +brew install llvm +mkdir build && cd build +cmake -DBLAS=apple -DUSE_OPENMP=1 -GNinja .. +ninja -v +``` + +
+ +## Installing MXNet Language Bindings +After building MXNet's shared library, you can install other language bindings. + +**NOTE:** The C++ API binding must be built when you build MXNet from source. See [Build MXNet with C++](#build-mxnet-with-c++). + +The following table provides links to each language binding by operating system: +| | [Ubuntu](ubuntu_setup.html) | [macOS](osx_setup.html) | [Windows](windows_setup.html) | +| --- | ---- | --- | ------- | +| Python | [Ubuntu guide](ubuntu_setup.html#install-mxnet-for-python) | [OSX guide](osx_setup.html) | [Windows guide](windows_setup.html#install-mxnet-for-python) | +| C++ | [C++ guide](c_plus_plus.html) | [C++ guide](c_plus_plus.html) | [C++ guide](c_plus_plus.html) | +| Clojure | [Clojure guide](https://github.com/apache/incubator-mxnet/tree/master/contrib/clojure-package) | [Clojure guide](https://github.com/apache/incubator-mxnet/tree/master/contrib/clojure-package) | n/a | +| Julia | [Ubuntu guide](ubuntu_setup.html#install-the-mxnet-package-for-julia) | [OSX guide](osx_setup.html#install-the-mxnet-package-for-julia) | [Windows guide](windows_setup.html#install-the-mxnet-package-for-julia) | +| Perl | [Ubuntu guide](ubuntu_setup.html#install-the-mxnet-package-for-perl) | [OSX guide](osx_setup.html#install-the-mxnet-package-for-perl) | n/a | +| R | [Ubuntu guide](ubuntu_setup.html#install-the-mxnet-package-for-r) | [OSX guide](osx_setup.html#install-the-mxnet-package-for-r) | [Windows guide](windows_setup.html#install-the-mxnet-package-for-r) | +| Scala | [Scala guide](scala_setup.html) | [Scala guide](scala_setup.html) | n/a | +| Java | [Java guide](java_setup.html) | [Java Guide](java_setup.html) | n/a | diff --git a/docs/static_site/src/pages/get_started/c_plus_plus.md b/docs/static_site/src/pages/get_started/c_plus_plus.md new file mode 100644 index 000000000000..edfb51b4ecf4 --- /dev/null +++ b/docs/static_site/src/pages/get_started/c_plus_plus.md @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: C++ Setup +action: Get Started +action_url: /get_started +permalink: /get_started/cpp_setup +--- + + +## Build the C++ package +The C++ package has the same prerequisites as the MXNet library. + +To enable C++ package, just add `USE_CPP_PACKAGE=1` in the [build from source](build_from_source.md) options when building the MXNet shared library. + +For example to build MXNet with GPU support and the C++ package, OpenCV, and OpenBLAS, from the project root you would run: + +```bash +cmake -DUSE_CUDA=1 -DUSE_CUDA_PATH=/usr/local/cuda -DUSE_CUDNN=1 -DUSE_MKLDNN=1 -DUSE_CPP_PACKAGE=1 -GNinja .. +ninja -v +``` + +You may also want to add the MXNet shared library to your `LD_LIBRARY_PATH`: + +```bash +export LD_LIBRARY_PATH=~/incubator-mxnet/lib +``` + +Setting the `LD_LIBRARY_PATH` is required to run the examples mentioned in the following section. + +## C++ Example Code +You can find C++ code examples in the `cpp-package/example` folder of the MXNet project. Refer to the [cpp-package's README](https://github.com/apache/incubator-mxnet/tree/master/cpp-package) for instructions on building the examples. + +## Tutorials + +* [MXNet C++ API Basics](../api/cpp/docs/tutorials/basics) + +## Related Topics + +* [Image Classification using MXNet's C Predict API](https://github.com/apache/incubator-mxnet/tree/master/example/image-classification/predict-cpp) diff --git a/docs/static_site/src/pages/get_started/centos_setup.md b/docs/static_site/src/pages/get_started/centos_setup.md new file mode 100644 index 000000000000..d3d203ffb7e6 --- /dev/null +++ b/docs/static_site/src/pages/get_started/centos_setup.md @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: CentOS setup +action: Get Started +action_url: /get_started +permalink: /get_started/centos_setup +--- + + +# Installing MXNet on CentOS and other non-Ubuntu Linux systems + +Step 1. Install build tools and git on `CentOS >= 7` and `Fedora >= 19`: + +```bash +sudo yum groupinstall -y "Development Tools" && sudo yum install -y git +``` + +Step 2. Install Atlas: + +```bash +sudo yum install atlas-devel +``` + +Installing both `git` and `cmake` or `make` by following instructions on the websites is +straightforward. Here we provide the instructions to build `gcc-4.8` from source codes. + +Step 3. Install the 32-bit `libc` with one of the following system-specific commands: + +```bash +sudo apt-get install libc6-dev-i386 # In Ubuntu +sudo yum install glibc-devel.i686 # In RHEL (Red Hat Linux) +sudo yum install glibc-devel.i386 # In CentOS 5.8 +sudo yum install glibc-devel.i686 # In CentOS 6/7 +``` + +Step 4. Download and extract the `gcc` source code with the prerequisites: + +```bash +wget http://mirrors.concertpass.com/gcc/releases/gcc-4.8.5/gcc-4.8.5.tar.gz +tar -zxf gcc-4.8.5.tar.gz +cd gcc-4.8.5 +./contrib/download_prerequisites +``` + +Step 5. Build `gcc` by using 10 threads and then install to `/usr/local` + +```bash +mkdir release && cd release +../configure --prefix=/usr/local --enable-languages=c,c++ +make -j10 +sudo make install +``` + +Step 6. Add the lib path to your configure file such as `~/.bashrc`: + +```bash +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib64 +``` + +Step 7. Build [OpenBLAS from source](https://github.com/xianyi/OpenBLAS#installation-from-source). + +Step 8. Build OpenCV + +To build OpenCV from source code, you need the [cmake](https://cmake.org) library. + +* If you don't have cmake or if your version of cmake is earlier than 3.6.1, run the following commands to install a newer version of cmake: + +```bash +wget https://cmake.org/files/v3.6/cmake-3.6.1-Linux-x86_64.tar.gz +tar -zxvf cmake-3.6.1-Linux-x86_64.tar.gz +alias cmake="cmake-3.6.1-Linux-x86_64/bin/cmake" +``` + +* To download and extract the OpenCV source code, run the following commands: + +```bash +wget https://codeload.github.com/opencv/opencv/zip/2.4.13 +unzip 2.4.13 +cd opencv-2.4.13 +mkdir release +cd release/ +``` + +* Build OpenCV. The following commands build OpenCV with 10 threads. We +disabled GPU support, which might significantly slow down an MXNet program +running on a GPU processor. It also disables 1394 which might generate a +warning. Then install it on `/usr/local`. + +```bash +cmake -D BUILD_opencv_gpu=OFF -D WITH_CUDA=OFF -D WITH_1394=OFF -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local .. +make -j10 +sudo make install +``` + +* Add the lib path to your configuration such as `~/.bashrc`. + +```bash +export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig/ +``` diff --git a/docs/static_site/src/pages/get_started/download.md b/docs/static_site/src/pages/get_started/download.md new file mode 100644 index 000000000000..ec26e2227ef3 --- /dev/null +++ b/docs/static_site/src/pages/get_started/download.md @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: Download Source Files +action: Get Started +action_url: /get_started +permalink: /get_started/download +--- + + +# Source Download + +These source archives are generated from tagged releases. Updates and patches will not have been applied. For any updates refer to the corresponding branches in the [GitHub repository](https://github.com/apache/incubator-mxnet). Choose your flavor of download from the following links: + +| Version | Source | PGP | SHA | +|---------|-------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------| +| 1.5.0 | [Download](https://apache.org/dist/incubator/mxnet/1.5.0/apache-mxnet-src-1.5.0-incubating.tar.gz) | [Download](https://apache.org/dist/incubator/mxnet/1.5.0/apache-mxnet-src-1.5.0-incubating.tar.gz.asc) | [Download](https://apache.org/dist/incubator/mxnet/1.5.0/apache-mxnet-src-1.5.0-incubating.tar.gz.sha512) | +| 1.4.1 | [Download](https://www.apache.org/dyn/closer.cgi/incubator/mxnet/1.4.1/apache-mxnet-src-1.4.1-incubating.tar.gz) | [Download](https://apache.org/dist/incubator/mxnet/1.4.1/apache-mxnet-src-1.4.1-incubating.tar.gz.asc) | [Download](https://apache.org/dist/incubator/mxnet/1.4.1/apache-mxnet-src-1.4.1-incubating.tar.gz.sha512) | +| 1.4.0 | [Download](https://www.apache.org/dyn/closer.cgi/incubator/mxnet/1.4.0/apache-mxnet-src-1.4.0-incubating.tar.gz) | [Download](https://apache.org/dist/incubator/mxnet/1.4.0/apache-mxnet-src-1.4.0-incubating.tar.gz.asc) | [Download](https://apache.org/dist/incubator/mxnet/1.4.0/apache-mxnet-src-1.4.0-incubating.tar.gz.sha512) | +| 1.3.1 | [Download](https://www.apache.org/dyn/closer.cgi/incubator/mxnet/1.3.1/apache-mxnet-src-1.3.1-incubating.tar.gz) | [Download](https://apache.org/dist/incubator/mxnet/1.3.1/apache-mxnet-src-1.3.1-incubating.tar.gz.asc) | [Download](https://apache.org/dist/incubator/mxnet/1.3.1/apache-mxnet-src-1.3.1-incubating.tar.gz.sha512) | +| 1.3.0 | [Download](https://archive.apache.org/dist/incubator/mxnet/1.3.0/apache-mxnet-src-1.3.0-incubating.tar.gz) | [Download](https://archive.apache.org/dist/incubator/mxnet/1.3.0/apache-mxnet-src-1.3.0-incubating.tar.gz.asc) | [Download](https://archive.apache.org/dist/incubator/mxnet/1.3.0/apache-mxnet-src-1.3.0-incubating.tar.gz.sha512) | +| 1.2.1 | [Download](https://archive.apache.org/dist/incubator/mxnet/1.2.1/apache-mxnet-src-1.2.1-incubating.tar.gz) | [Download](https://archive.apache.org/dist/incubator/mxnet/1.2.1/apache-mxnet-src-1.2.1-incubating.tar.gz.asc) | [Download](https://archive.apache.org/dist/incubator/mxnet/1.2.1/apache-mxnet-src-1.2.1-incubating.tar.gz.sha512) | +| 1.2.0 | [Download](https://archive.apache.org/dist/incubator/mxnet/1.2.0/apache-mxnet-src-1.2.0-incubating.tar.gz) | [Download](https://archive.apache.org/dist/incubator/mxnet/1.2.0/apache-mxnet-src-1.2.0-incubating.tar.gz.asc) | [Download](https://archive.apache.org/dist/incubator/mxnet/1.2.0/apache-mxnet-src-1.2.0-incubating.tar.gz.sha512) | +| 1.1.0 | [Download](https://archive.apache.org/dist/incubator/mxnet/1.1.0/apache-mxnet-src-1.1.0-incubating.tar.gz) | [Download](https://archive.apache.org/dist/incubator/mxnet/1.1.0/apache-mxnet-src-1.1.0-incubating.tar.gz.asc) | [Download](https://archive.apache.org/dist/incubator/mxnet/1.1.0/apache-mxnet-src-1.1.0-incubating.tar.gz.sha512) | +| 1.0.0 | [Download](https://archive.apache.org/dist/incubator/mxnet/1.0.0/apache-mxnet-src-1.0.0-incubating.tar.gz) | [Download](https://archive.apache.org/dist/incubator/mxnet/1.0.0/apache-mxnet-src-1.0.0-incubating.tar.gz.asc) | [Download](https://archive.apache.org/dist/incubator/mxnet/1.0.0/apache-mxnet-src-1.0.0-incubating.tar.gz.sha512) | +| 0.12.1 | [Download](https://archive.apache.org/dist/incubator/mxnet/0.12.1/apache-mxnet-src-0.12.1-incubating.tar.gz) | [Download](https://archive.apache.org/dist/incubator/mxnet/0.12.1/apache-mxnet-src-0.12.1-incubating.tar.gz.asc) | [Download](https://archive.apache.org/dist/incubator/mxnet/0.12.1/apache-mxnet-src-0.12.1-incubating.tar.gz.sha512) | +| 0.12.0 | [Download](https://archive.apache.org/dist/incubator/mxnet/0.12.0/apache-mxnet-src-0.12.0-incubating.tar.gz) | [Download](https://archive.apache.org/dist/incubator/mxnet/0.12.0/apache-mxnet-src-0.12.0-incubating.tar.gz.asc) | [Download](https://archive.apache.org/dist/incubator/mxnet/0.12.0/apache-mxnet-src-0.12.0-incubating.tar.gz.sha512) | +| 0.11.0 | [Download](https://archive.apache.org/dist/incubator/mxnet/0.11.0/apache-mxnet-src-0.11.0-incubating.tar.gz) | [Download](https://archive.apache.org/dist/incubator/mxnet/0.11.0/apache-mxnet-src-0.11.0-incubating.tar.gz.asc) | [Download](https://archive.apache.org/dist/incubator/mxnet/0.11.0/apache-mxnet-src-0.11.0-incubating.tar.gz.sha512) | + +## Verify the Integrity of the Files +It is essential that you verify the integrity of the downloaded file using the PGP signature (.asc file) or a hash (.md5 or .sha* file). Please read [Verifying Apache Software Foundation Releases](https://www.apache.org/info/verification.html) for more information on why you should verify our releases. + +The PGP signature can be verified using PGP or GPG. First download the KEYS as well as the .asc signature file for the relevant distribution. Make sure you get these files from the main distribution site, rather than from a mirror. Then verify the signatures using one of the following alternatives: + +```bash +% gpg --import KEYS +% gpg --verify downloaded_file.asc downloaded_file +``` + +```bash +% pgpk -a KEYS +% pgpv downloaded_file.asc +``` + +```bash +% pgp -ka KEYS +% pgp downloaded_file.asc +``` + +Alternatively, you can verify the hash on the file. + +Hashes can be calculated using GPG: + +```bash +% gpg --print-md SHA1 downloaded_file +``` + +The output should be compared with the contents of the SHA1 file. Similarly for other hashes (SHA256 MD5 etc) which may be provided. + +Windows 7 and later systems should all now have `certUtil`: + +```bash +% certUtil -hashfile pathToFileToCheck +``` + +HashAlgorithm choices: MD2 MD4 MD5 SHA1 SHA256 SHA384 SHA512 + +Unix-like systems (and macOS) will have a utility called `md5`, `md5sum` or `shasum`. diff --git a/docs/static_site/src/pages/get_started/index.html b/docs/static_site/src/pages/get_started/index.html new file mode 100644 index 000000000000..1abf8b9e06d4 --- /dev/null +++ b/docs/static_site/src/pages/get_started/index.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: Get Started +action: MXNet Tutorials +action_url: /api/python/docs/tutorials +permalink: /get_started/index.html +--- + +{% include /get_started/get_started.html %} + +
+
+
+

Download from source

+

The signed source code for Apache MXNet (incubating) is available for download here

+
+
diff --git a/docs/static_site/src/pages/get_started/java_setup.md b/docs/static_site/src/pages/get_started/java_setup.md new file mode 100644 index 000000000000..52edde8cb383 --- /dev/null +++ b/docs/static_site/src/pages/get_started/java_setup.md @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: Java Setup +action: Get Started +action_url: /get_started +permalink: /get_started/java_setup +--- + +# Setup the MXNet Package for Java + +The following instructions are provided for macOS and Ubuntu. Windows is not yet available. + +**Note:** If you use IntelliJ or a similar IDE, you may want to follow the [MXNet-Java on IntelliJ tutorial](../tutorials/java/mxnet_java_on_intellij.md) instead of these instructions. + +
+ +## Maven + +### Setup Instructions + +**Step 1.** Install dependencies: + +**macOS Steps** + +```bash +brew update +brew tap caskroom/versions +brew cask install java8 +brew install maven +``` + +**Ubuntu Steps** + +Please run the following lines: + +```bash +sudo apt-get install openjdk-8-jdk maven +``` + +**Step 2.** Run the demo MXNet-Java project. + +Go to the [MXNet-Java demo project's README](https://github.com/apache/incubator-mxnet/tree/master/scala-package/mxnet-demo/java-demo) and follow the directions to test the MXNet-Java package installation. + +#### Maven Repository + +MXNet-Java can be easily included in your Maven managed project. The Java packages are currently available on Maven. Add the dependency which corresponds to your platform to the `dependencies` tag : + +**Linux CPU** +```html + + org.apache.mxnet + mxnet-full_2.11-linux-x86_64-cpu + 1.4.0 + +``` + +**Linux GPU** +```html + + org.apache.mxnet + mxnet-full_2.11-linux-x86_64-gpu + 1.4.0 + +``` + +**macOS CPU** +```html + + org.apache.mxnet + mxnet-full_2.11-osx-x86_64-cpu + 1.4.0 + +``` + +The official Java Packages have been released as part of MXNet 1.4 and are available on the [MXNet Maven package repository](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.mxnet%22). +
+ +### Eclipse IDE Support +You can convert your existing Maven project to a project that can run in Eclipse by: +``` +mvn eclipse:eclipse +``` +This can be done once you have your maven project properly configured. + +## Source + +The previously mentioned setup with Maven is recommended. Otherwise, the following instructions for macOS and Ubuntu are provided for reference only: + +**If you have already built mxnet from source using `cmake`, run `make clean` and then follow the appropriate guide below*** + +| OS | Step 1 | Step 2 | +|---|---|---| +|macOS | [Shared Library for macOS](osx_setup.html#build-the-shared-library) | [Scala Package for macOS](osx_setup.html#install-the-mxnet-package-for-scala) | +| Ubuntu | [Shared Library for Ubuntu](ubuntu_setup.html#installing-mxnet-on-ubuntu) | [Scala Package for Ubuntu](ubuntu_setup.html#install-the-mxnet-package-for-scala) | +| Windows | | Call for Contribution | + + +#### Build Java from an Existing MXNet Installation +If you have already built MXNet **from source** and are looking to setup Java from that point, you may simply run the following from the MXNet `scala-package` folder: + +``` +mvn install +``` +This will install both the Java Inference API and the required MXNet-Scala package. +
+ +## Documentation + +Javadocs are generated as part of the docs build pipeline. You can find them published in the [Java API](../api/java/index.md) section of the website or by going to the [scaladocs output](https://mxnet.incubator.apache.org/api/scala/docs/index.html#org.apache.mxnet.package) directly. + +To build the docs yourself, follow the [developer build docs instructions](https://github.com/apache/incubator-mxnet/tree/master/docs/build_version_doc#developer-instructions). + +
+ +## Resources + +* [Java API](../api/java/) +* [javadocs](https://mxnet.incubator.apache.org/api/scala/docs/index.html#org.apache.mxnet.package) +* [MXNet-Java Tutorials](../tutorials/java/) diff --git a/docs/static_site/src/pages/get_started/jetson_setup.md b/docs/static_site/src/pages/get_started/jetson_setup.md new file mode 100644 index 000000000000..1bb52d30e137 --- /dev/null +++ b/docs/static_site/src/pages/get_started/jetson_setup.md @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: Jetson Setup +action: Get Started +action_url: /get_started +permalink: /get_started/jetson_setup +--- + + +# Install MXNet on a Jetson + +MXNet supports the Ubuntu Arch64 based operating system so you can run MXNet on NVIDIA Jetson Devices, such as the [TX2](http://www.nvidia.com/object/embedded-systems-dev-kits-modules.html) or [Nano](https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit). + +These instructions will walk through how to build MXNet and install MXNet's Python language binding. + +For the purposes of this install guide we will assume that CUDA is already installed on your Jetson device. The disk image provided by NVIDIA's getting started guides will have the Jetson toolkit preinstalled, and this also includes CUDA. You should double check what versions are installed and which version you plan to use. + +After installing the prerequisites, you have several options for installing MXNet: +1. Use a Jetson MXNet pip wheel for Python development and use a precompiled Jetson MXNet binary. +3. Build MXNet from source + * On a faster Linux computer using cross-compilation + * On the Jetson itself (very slow and not recommended) + + +## Prerequisites +To build from source or to use the Python wheel, you must install the following dependencies on your Jetson. +Cross-compiling will require dependencies installed on that machine as well. + +### Python Dependencies + +To use the Python API you need the following dependencies: + +```bash +sudo apt update +sudo apt -y install \ + build-essential \ + git \ + graphviz \ + libatlas-base-dev \ + libopencv-dev \ + python-pip + +sudo pip install --upgrade \ + pip \ + setuptools + +sudo pip install \ + graphviz==0.8.4 \ + jupyter \ + numpy==1.15.2 +``` + +If you plan to cross-compile you will need to install these dependencies on that computer as well. +If you get an error about something being busy, you can restart the Nano and this error will go away. You can then continue installation of the prerequisites. + +### Download the source & setup some environment variables: + +These steps are optional, but some of the following instructions expect MXNet source files and the `MXNET_HOME` environment variable. Also, CUDA commands will not work out of the box without updating your path. + +Clone the MXNet source code repository using the following `git` command in your home directory: + +```bash +git clone --recursive https://github.com/apache/incubator-mxnet.git mxnet +``` + +Setup your environment variables for MXNet and CUDA in your `.profile` file in your home directory. +Add the following to the file. + +```bash +export PATH=/usr/local/cuda/bin:$PATH +export MXNET_HOME=$HOME/mxnet/ +export PYTHONPATH=$MXNET_HOME/python:$PYTHONPATH +``` + +You can then apply this change immediately with the following: +```bash +source .profile +``` + +**Note:** Change the `~/.profile` steps according to how you prefer to use your shell. Otherwise, your environment variables will be gone after you logout. + +### Configure CUDA + +You can check to see what version of CUDA is running with `nvcc`. + +```bash +nvcc --version +``` + +To switch CUDA versions on a device or computer that has more than one version installed, use the following and replace the symbolic link to the version you want. This one uses CUDA 10.0, which is preinstalled on the Nano. + +```bash +sudo rm /usr/local/cuda +sudo ln -s /usr/local/cuda-10.0 /usr/local/cuda +``` + +**Note:** When cross-compiling, change the CUDA version on the host computer you're using to match the version you're running on your Jetson device. +**Note:** CUDA 10.1 is recommended but doesn't ship with the Nano's SD card image. You may want to go through CUDA upgrade steps first. + + +## Option 1. Install MXNet for Python + +To use a prepared Python wheel, download it to your Jetson, and run it. +* [MXNet 1.4.0 - Python 3](https://s3.us-east-2.amazonaws.com/mxnet-public/install/jetson/1.4.0/mxnet-1.4.0-cp36-cp36m-linux_aarch64.whl) +* [MXNet 1.4.0 - Python 2](https://s3.us-east-2.amazonaws.com/mxnet-public/install/jetson/1.4.0/mxnet-1.4.0-cp27-cp27mu-linux_aarch64.whl) + + +It should download the required dependencies, but if you have issues, +install the dependencies in the prerequisites section, then run the pip wheel. + +```bash +sudo pip install mxnet-1.4.0-cp27-cp27mu-linux_aarch64.whl +``` + +Now use a pre-compiled binary you can download it from S3 which is a patch v1.4.1: +* https://s3.us-east-2.amazonaws.com/mxnet-public/install/jetson/1.4.1/libmxnet.so + +Place this file in `$MXNET_HOME/lib`. + +To use this with the MXNet Python binding, you must match the source directory's checked out version with the binary's source version, then install it with pip. + +```bash +cd $MXNET_HOME +git checkout v1.4.x +git submodule update --init --recursive +cd python +sudo pip install -e . +``` + +Refer to the following Conclusion and Next Steps section to test your installation. + +## Option 2. Build MXNet from Source + +Installing MXNet from source is a two-step process: + +1. Build the shared library from the MXNet C++ source code. +2. Install the supported language-specific packages for MXNet. + +You can use a Docker method or you can build from source manually. + +### Docker + +You must have installed Docker and be able to run `docker` without `sudo`. +Follow these [setup instructions to get to this point](https://docs.docker.com/install/linux/#manage-docker-as-a-non-root-user). +Then run the following to execute cross-compilation via Docker. + +```bash +$MXNET_HOME/ci/build.py -p jetson +``` + +### Manual + +**Step 1** Build the Shared Library + +(Skip this sub-step for compiling on the Jetson device directly.) +Edit the Makefile to install the MXNet with CUDA bindings to leverage the GPU on the Jetson: + +```bash +cp $MXNET_HOME/make/crosscompile.jetson.mk config.mk +``` + +Now edit `config.mk` to make some additional changes for the Nano. Update the following settings: + +1. Update the CUDA path. `USE_CUDA_PATH = /usr/local/cuda` +2. Add `-gencode arch=compute-63, code=sm_62` to the `CUDA_ARCH` setting. +3. Update the NVCC settings. `NVCCFLAGS := -m64` +4. (optional, but recommended) Turn on OpenCV. `USE_OPENCV = 1` + +Now edit the Mshadow Makefile to ensure MXNet builds with Pascal's hardware level low precision acceleration by editing `3rdparty/mshadow/make/mshadow.mk`. +The last line has `MSHADOW_USE_PASCAL` set to `0`. Change this to `1` to enable it. + +```bash +MSHADOW_CFLAGS += -DMSHADOW_USE_PASCAL=1 +``` + +Now you can build the complete MXNet library with the following command: + +```bash +cd $MXNET_HOME +make -j $(nproc) +``` + +Executing this command creates a file called `libmxnet.so` in the `mxnet/lib` directory. + +**Step 2** Install MXNet Python Bindings (optional) + +To install Python bindings run the following commands in the MXNet directory: + +```bash +cd $MXNET_HOME/python +sudo pip install -e . +``` + +Note that the `-e` flag is optional. It is equivalent to `--editable` and means that if you edit the source files, these changes will be reflected in the package installed. + +**Step 3** Install the MXNet Java & Scala Bindings (optional) + +Change directories to `scala-package` and run `mvn install`. + +```bash +cd $MXNET_HOME/scala-package +mvn install +``` + +This creates the required `.jar` file to use in your Java or Scala projects. + +## Conclusion and Next Steps + +You are now ready to run MXNet on your NVIDIA Jetson TX2 or Nano device. +You can verify your MXNet Python installation with the following: + +```python +import mxnet +mxnet.__version__ +``` + +You can also verify MXNet can use your GPU with the following test: + +```python +import mxnet as mx +a = mx.nd.ones((2, 3), mx.gpu()) +b = a * 2 + 1 +b.asnumpy() +``` + +If everything is working, it will report the version number. +For assistance, head over to the [MXNet Forum](https://discuss.mxnet.io/). diff --git a/docs/static_site/src/pages/get_started/osx_setup.md b/docs/static_site/src/pages/get_started/osx_setup.md new file mode 100644 index 000000000000..26a63a1d13a3 --- /dev/null +++ b/docs/static_site/src/pages/get_started/osx_setup.md @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: OSX Setup +action: Get Started +action_url: /get_started +permalink: /get_started/osx_setup +--- + +# Installing MXNet from source on OS X (Mac) + +**NOTE:** For pre-built MXNet with Python, please refer to the [new install guide]({{'/get_started'|relative_url}}). + +Installing MXNet is a two-step process: + +1. Build the shared library from the MXNet C++ source code. +2. Install the supported language-specific packages for MXNet. + +**Note:** To change the compilation options for your build, edit the ```make/config.mk``` file and submit a build request with the ```make``` command. + +## Prepare Environment for GPU Installation + +This section is optional. Skip to next section if you don't plan to use GPUs. If you plan to build with GPU, you need to set up the environment for CUDA and cuDNN. + +First, download and install [CUDA 8 toolkit](https://developer.nvidia.com/cuda-toolkit). + +Once you have the CUDA Toolkit installed you will need to set up the required environment variables by adding the following to your ~/.bash_profile file: + +```bash + export CUDA_HOME=/usr/local/cuda + export DYLD_LIBRARY_PATH="$CUDA_HOME/lib:$DYLD_LIBRARY_PATH" + export PATH="$CUDA_HOME/bin:$PATH" +``` + +Reload ~/.bash_profile file and install dependencies: +```bash + . ~/.bash_profile + brew install coreutils + brew tap caskroom/cask +``` + +Then download [cuDNN 5](https://developer.nvidia.com/cudnn). + +Unzip the file and change to the cudnn root directory. Move the header files and libraries to your local CUDA Toolkit folder: + +```bash + $ sudo mv include/cudnn.h /Developer/NVIDIA/CUDA-8.0/include/ + $ sudo mv lib/libcudnn* /Developer/NVIDIA/CUDA-8.0/lib + $ sudo ln -s /Developer/NVIDIA/CUDA-8.0/lib/libcudnn* /usr/local/cuda/lib/ +``` + +Now we can start to build MXNet. + +## Build the Shared Library + +### Install MXNet dependencies +Install the dependencies, required for MXNet, with the following commands: +- [Homebrew](http://brew.sh/) +- OpenBLAS and homebrew/core (for linear algebraic operations) +- OpenCV (for computer vision operations) + +```bash + # Paste this command in Mac terminal to install Homebrew + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + + # Insert the Homebrew directory at the top of your PATH environment variable + export PATH=/usr/local/bin:/usr/local/sbin:$PATH +``` + +```bash + brew update + brew install pkg-config + brew install graphviz + brew install openblas + brew tap homebrew/core + brew install opencv@3 + + # If building with MKLDNN + brew install llvm + + # Get pip + easy_install pip + # For visualization of network graphs + pip install graphviz==0.8.4 + # Jupyter notebook + pip install jupyter +``` + +### Build MXNet Shared Library +After you have installed the dependencies, pull the MXNet source code from Git and build MXNet to produce an MXNet library called ```libmxnet.so```. You can clone the repository as described in the following code block, or you may try the [download links](download.md) for your desired MXNet version. + +The file called ```osx.mk``` has the configuration required for building MXNet on OS X. First copy ```make/osx.mk``` into ```config.mk```, which is used by the ```make``` command: + +```bash + git clone --recursive https://github.com/apache/incubator-mxnet ~/mxnet + cd ~/mxnet + cp make/osx.mk ./config.mk + echo "USE_BLAS = openblas" >> ./config.mk + echo "ADD_CFLAGS += -I/usr/local/opt/openblas/include" >> ./config.mk + echo "ADD_LDFLAGS += -L/usr/local/opt/openblas/lib" >> ./config.mk + echo "ADD_LDFLAGS += -L/usr/local/lib/graphviz/" >> ./config.mk + make -j$(sysctl -n hw.ncpu) +``` + +To build with MKLDNN + +```bash +echo "CC=$(brew --prefix llvm)/bin/clang" >> ./config.mk +echo "CXX=$(brew --prefix llvm)/bin/clang++" >> ./config.mk +echo "USE_OPENCV=1" >> ./config.mk +echo "USE_OPENMP=1" >> ./config.mk +echo "USE_MKLDNN=1" >> ./config.mk +echo "USE_BLAS=apple" >> ./config.mk +LIBRARY_PATH=$(brew --prefix llvm)/lib/ make -j $(sysctl -n hw.ncpu) +``` + +If building with ```GPU``` support, add the following configuration to config.mk and build: +```bash + echo "USE_CUDA = 1" >> ./config.mk + echo "USE_CUDA_PATH = /usr/local/cuda" >> ./config.mk + echo "USE_CUDNN = 1" >> ./config.mk + make -j$(sysctl -n hw.ncpu) +``` +**Note:** To change build parameters, edit ```config.mk```. + + +  + +We have installed MXNet core library. Next, we will install MXNet interface package for the programming language of your choice: +- [Python](#install-mxnet-for-python) +- [R](#install-the-mxnet-package-for-r) +- [Julia](#install-the-mxnet-package-for-julia) +- [Scala](#install-the-mxnet-package-for-scala) +- [Perl](#install-the-mxnet-package-for-perl) + +## Install MXNet for Python +To install the MXNet Python binding navigate to the root of the MXNet folder then run the following: + +```bash +$ cd python +$ pip install -e . +``` + +Note that the `-e` flag is optional. It is equivalent to `--editable` and means that if you edit the source files, these changes will be reflected in the package installed. + +## Install the MXNet Package for R +You have 2 options: +1. Building MXNet with the Prebuilt Binary Package +2. Building MXNet from Source Code + +### Building MXNet with the Prebuilt Binary Package +Install OpenCV and OpenBLAS. + +```bash +brew install opencv +brew install openblas@0.3.1 +``` + +Add a soft link to the OpenBLAS installation. This example links the 0.3.1 version: + +```bash +ln -sf /usr/local/opt/openblas/lib/libopenblasp-r0.3.* /usr/local/opt/openblas/lib/libopenblasp-r0.3.1.dylib +``` + +Note: packages for 3.6.x are not yet available. + +Install 3.5.x of R from [CRAN](https://cran.r-project.org/bin/macosx/). The latest is [v3.5.3](https://cran.r-project.org/bin/macosx/R-3.5.3.pkg). + +For OS X (Mac) users, MXNet provides a prebuilt binary package for CPUs. The prebuilt package is updated weekly. You can install the package directly in the R console using the following commands: + +```r + cran <- getOption("repos") + cran["dmlc"] <- "https://apache-mxnet.s3-accelerate.dualstack.amazonaws.com/R/CRAN/" + options(repos = cran) + install.packages("mxnet") +``` + +### Building MXNet from Source Code + +Run the following commands to install the MXNet dependencies and build the MXNet R package. + +```r + Rscript -e "install.packages('devtools', repo = 'https://cran.rstudio.com')" +``` +```bash + cd R-package + Rscript -e "library(devtools); library(methods); options(repos=c(CRAN='https://cran.rstudio.com')); install_deps(dependencies = TRUE)" + cd .. + make rpkg +``` + +## Install the MXNet Package for Julia +The MXNet package for Julia is hosted in a separate repository, MXNet.jl, which is available on [GitHub](https://github.com/dmlc/MXNet.jl). To use Julia binding it with an existing libmxnet installation, set the ```MXNET_HOME``` environment variable by running the following command: + +```bash + export MXNET_HOME=//libmxnet +``` + +The path to the existing libmxnet installation should be the root directory of libmxnet. In other words, you should be able to find the ```libmxnet.so``` file at ```$MXNET_HOME/lib```. For example, if the root directory of libmxnet is ```~```, you would run the following command: + +```bash + export MXNET_HOME=/~/libmxnet +``` + +You might want to add this command to your ```~/.bashrc``` file. If you do, you can install the Julia package in the Julia console using the following command: + +```julia + Pkg.add("MXNet") +``` + +For more details about installing and using MXNet with Julia, see the [MXNet Julia documentation](http://dmlc.ml/MXNet.jl/latest/user-guide/install/). + + +## Install the MXNet Package for Scala + +To use the MXNet-Scala package, you can acquire the Maven package as a dependency. + +Further information is in the [MXNet-Scala Setup Instructions](./scala_setup.md). + +If you use IntelliJ or a similar IDE, you may want to follow the [MXNet-Scala on IntelliJ tutorial](../tutorials/scala/mxnet_scala_on_intellij.md) instead. + + +## Install the MXNet Package for Perl +Before you build MXNet for Perl from source code, you must complete [building the shared library](#build-the-shared-library). +After you build the shared library, run the following command from the MXNet source root directory to build the MXNet Perl package: + +```bash + brew install swig + sudo sh -c 'curl -L https://cpanmin.us | perl - App::cpanminus' + sudo cpanm -q -n PDL Mouse Function::Parameters Hash::Ordered PDL::CCS + + MXNET_HOME=${PWD} + export PERL5LIB=${HOME}/perl5/lib/perl5 + + cd ${MXNET_HOME}/perl-package/AI-MXNetCAPI/ + perl Makefile.PL INSTALL_BASE=${HOME}/perl5 + make + install_name_tool -change lib/libmxnet.so \ + ${MXNET_HOME}/lib/libmxnet.so \ + blib/arch/auto/AI/MXNetCAPI/MXNetCAPI.bundle + make install + + cd ${MXNET_HOME}/perl-package/AI-NNVMCAPI/ + perl Makefile.PL INSTALL_BASE=${HOME}/perl5 + make + install_name_tool -change lib/libmxnet.so \ + ${MXNET_HOME}/lib/libmxnet.so \ + blib/arch/auto/AI/NNVMCAPI/NNVMCAPI.bundle + make install + + cd ${MXNET_HOME}/perl-package/AI-MXNet/ + perl Makefile.PL INSTALL_BASE=${HOME}/perl5 + make install +``` + +## Next Steps + +* [Tutorials](../tutorials/index.md) +* [How To](../faq/index.md) +* [Architecture](../architecture/index.md) diff --git a/docs/static_site/src/pages/get_started/scala_setup.md b/docs/static_site/src/pages/get_started/scala_setup.md new file mode 100644 index 000000000000..0dc47aad4db6 --- /dev/null +++ b/docs/static_site/src/pages/get_started/scala_setup.md @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: Scala Setup +action: Get Started +action_url: /get_started +permalink: /get_started/scala_setup +--- + + +# Setup the MXNet Package for Scala + +The following instructions are provided for macOS and Ubuntu. Windows is not yet available. + +**Note:** If you use IntelliJ or a similar IDE, you may want to follow the [MXNet-Scala on IntelliJ tutorial](../tutorials/scala/mxnet_scala_on_intellij.md) instead of these instructions. +**Note:** Currently, we only support scala 2.11 + +
+ +## Maven + +### Setup Instructions + +**Step 1.** Install dependencies: + +**macOS Steps** + +```bash +brew update +brew tap caskroom/versions +brew cask install java8 +brew install opencv +brew install maven +``` + +**Ubuntu Steps** + +These scripts will install Maven and its dependencies. + +```bash +wget https://raw.githubusercontent.com/apache/incubator-mxnet/master/ci/docker/install/ubuntu_core.sh +wget https://raw.githubusercontent.com/apache/incubator-mxnet/master/ci/docker/install/ubuntu_scala.sh +chmod +x ubuntu_core.sh +chmod +x ubuntu_scala.sh +sudo ./ubuntu_core.sh +sudo ./ubuntu_scala.sh +``` + +**Step 2.** Run the demo MXNet-Scala project. + +Go to the [MXNet-Scala demo project's README](https://github.com/apache/incubator-mxnet/tree/master/scala-package/mxnet-demo) and follow the directions to test the MXNet-Scala package installation. + +#### Maven Repository + +Package information can be found in the Maven Repository: +https://mvnrepository.com/artifact/org.apache.mxnet + +**Linux CPU** +```html + + + org.apache.mxnet + mxnet-full_2.11-linux-x86_64-cpu + +``` + +**Linux GPU** +```html + + + org.apache.mxnet + mxnet-full_2.11-linux-x86_64-gpu + +``` + +**macOS CPU** +```html + + + org.apache.mxnet + mxnet-full_2.11-osx-x86_64-cpu + +``` + +**NOTE:** You may specify the version you wish to use by adding the version number to the `dependency` block. For example, to use v1.2.0 you would add `1.2.0`. Otherwise Maven will use the latest version available. + +
+ +## Source + +The previously mentioned setup with Maven is recommended. Otherwise, the following instructions for macOS, Ubuntu, and Windows are provided for reference only: + +**If you have already built mxnet from source using `cmake`, run `make clean` and then follow the appropriate guide below*** + +| OS | Step 1 | Step 2 | +|---|---|---| +|macOS | [Shared Library for macOS](osx_setup.html#build-the-shared-library) | [Scala Package for macOS](osx_setup.html#install-the-mxnet-package-for-scala) | +| Ubuntu | [Shared Library for Ubuntu](ubuntu_setup.html#installing-mxnet-on-ubuntu) | [Scala Package for Ubuntu](ubuntu_setup.html#install-the-mxnet-package-for-scala) | +| Windows | | Call for Contribution | + + +#### Build Scala from an Existing MXNet Installation +If you have already built MXNet **from source** and are looking to setup Scala from that point, you may simply run the following from the MXNet `scala-package` folder: + +``` +mvn install +``` + +
+ +## Interpreter + +To run the scala interpreter, first download and install scala 2.11.x (run `scala -version` to make sure you have the right version installed.** + +### Installing the Interpreter + +**Ubuntu*** + +``` +sudo apt-get install scala +``` + +**macOS*** + +``` +brew install scala@2.11 +``` + +Then, add scala to your path by following the instructions output by homebrew. + +### Running the Interpreter + +To run the interpreter, download the appropriate mxnet jar from [the maven repository](https://search.maven.org/search?q=g:org.apache.mxnet) or build from source following the instructions above. + +Then, run `scala -cp {path/to/mxnet-full_2.11-os-version.jar}` to start it. +If you receive a "NumberFormatException" when running the interpreter, run `export TERM=xterm-color` before starting the interpreter. + +## Documentation + +Scaladocs are generated as part of the docs build pipeline. You can find them published in the [Scala API](../api/scala/index.md) section of the website or by going to the [scaladocs output](https://mxnet.incubator.apache.org/api/scala/docs/index.html#org.apache.mxnet.package) directly. + +To build the docs yourself, follow the [developer build docs instructions](https://github.com/apache/incubator-mxnet/tree/master/docs/build_version_doc#developer-instructions). + +
+ +## Resources + +* [Scala API](../api/scala/) +* [scaladocs](https://mxnet.incubator.apache.org/api/scala/docs/index.html#org.apache.mxnet.package) +* [MXNet-Scala Tutorials](../tutorials/scala/) diff --git a/docs/static_site/src/pages/get_started/ubuntu_setup.md b/docs/static_site/src/pages/get_started/ubuntu_setup.md new file mode 100644 index 000000000000..5fc0e237d086 --- /dev/null +++ b/docs/static_site/src/pages/get_started/ubuntu_setup.md @@ -0,0 +1,561 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: Ubuntu Setup +action: Get Started +action_url: /get_started +permalink: /get_started/ubuntu_setup +--- + +# Installing MXNet on Ubuntu + +The following installation instructions are for installing MXNet on computers running **Ubuntu 16.04**. Support for later versions of Ubuntu is [not yet available](#contributions). +
+ +## Contents + +* [CUDA Dependencies](#cuda-dependencies) +* [Quick Installation](#quick-installation) + * [Python](#install-mxnet-for-python) + * [pip Packages](#pip-package-availability) +* [Build from Source](#build-mxnet-from-source) +* [Installing Language Packages](#installing-language-packages-for-mxnet) + * [R](#install-the-mxnet-package-for-r) + * [Julia](#install-the-mxnet-package-for-julia) + * [Scala](#install-the-mxnet-package-for-scala) + * [Java](#install-the-mxnet-package-for-java) + * [Perl](#install-the-mxnet-package-for-perl) + * [Contributions](#contributions) + * [Next Steps](#next-steps) + +
+ +## CUDA Dependencies + +If you plan to build with GPU, you need to set up the environment for CUDA and cuDNN. + +First, download and install [CUDA toolkit](https://developer.nvidia.com/cuda-toolkit). CUDA 9.2 is recommended. + +Then download [cuDNN 7.1.4](https://developer.nvidia.com/cudnn). + +Unzip the file and change to the cuDNN root directory. Move the header and libraries to your local CUDA Toolkit folder: + +```bash + tar xvzf cudnn-9.2-linux-x64-v7.1 + sudo cp -P cuda/include/cudnn.h /usr/local/cuda/include + sudo cp -P cuda/lib64/libcudnn* /usr/local/cuda/lib64 + sudo chmod a+r /usr/local/cuda/include/cudnn.h /usr/local/cuda/lib64/libcudnn* + sudo ldconfig +``` + +
+ + +## Quick Installation + +### Install MXNet for Python + +#### Dependencies + +The following scripts will install Ubuntu 16.04 dependencies for MXNet Python development. + +```bash +wget https://raw.githubusercontent.com/apache/incubator-mxnet/master/ci/docker/install/ubuntu_core.sh +wget https://raw.githubusercontent.com/apache/incubator-mxnet/master/ci/docker/install/ubuntu_python.sh +sudo ./ubuntu_core.sh +sudo ./ubuntu_python.sh +``` + +Using the latest MXNet with CUDA 9.2 package is recommended for the fastest training speeds with MXNet. + +**Recommended for training:** +```bash +pip install mxnet-cu92 +``` + +**Recommended for inference:** +```bash +pip install mxnet-cu92mkl +``` + +Alternatively, you can use the table below to select the package that suits your purpose. + +| MXNet Version | Basic | CUDA | MKL-DNN | CUDA/MKL-DNN | +|-|-|-|-|-| +| Latest | mxnet | mxnet-cu92 | mxnet-mkl | mxnet-cu92mkl | + + +#### pip Package Availability + +The following table presents the pip packages that are recommended for each version of MXNet. + +![pip package table](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/install/pip-packages-1.4.0.png) + +To install an older version of MXNet with one of the packages in the previous table add `==` with the version you require. For example for version 1.1.0 of MXNet with CUDA 8, you would use `pip install mxnet-cu80==1.1.0`. + +
+ + +## Build MXNet from Source + +You can build MXNet from source, and then you have the option of installing language-specific bindings, such as Scala, Java, Julia, R or Perl. This is a two-step process: + +1. Build the shared library from the MXNet C++ source code. +2. (optional) Install the supported language-specific packages for MXNet. Be sure to check that section first, as some scripts may be available to handle all of the dependencies, MXNet build, and language bindings for you. Here they are again for quick access: + +* [R](#install-the-mxnet-package-for-r) +* [Julia](#install-the-mxnet-package-for-julia) +* [Scala](#install-the-mxnet-package-for-scala) +* [Java](#install-the-mxnet-package-for-java) +* [Perl](#install-the-mxnet-package-for-perl) + +**Note:** To change the compilation options for your build, edit the ```make/config.mk``` file prior to building MXNet. More information on this is mentioned in the different language package instructions. + +### Build the Shared Library + +#### Quick MXNet Build +You can quickly build MXNet from source with the following script found in the `/docs/install` folder: + +```bash +cd docs/install +./install_mxnet_ubuntu_python.sh +``` + +Or you can go through a manual process described next. + +#### Manual MXNet Installation + +It is recommended that you review the general [build from source](build_from_source) instructions before continuing. + +On Ubuntu versions 16.04 or later, you need the following dependencies: + +**Step 1:** Install prerequisite packages. +```bash + sudo apt-get update + sudo apt-get install -y build-essential git ninja-build ccache +``` + +**For Ubuntu 18.04 and CUDA builds you need to update CMake** + +```bash +#!/usr/bin/env bash +set -exuo pipefail +sudo apt remove --purge --auto-remove cmake + +# Update CMAKE for correct cuda autotedetection: https://github.com/clab/dynet/issues/1457 +version=3.14 +build=0 +mkdir -p ~/tmp +cd ~/tmp +wget https://cmake.org/files/v$version/cmake-$version.$build.tar.gz +tar -xzvf cmake-$version.$build.tar.gz +cd cmake-$version.$build/ +./bootstrap +make -j$(nproc) +sudo make install +``` + + +**Step 2:** Install a Math Library. + +Details on the different math libraries are found in the build from source guide's [Math Library Selection](build_from_source#math-library-selection) section. + +For OpenBLAS use: + +```bash + sudo apt-get install -y libopenblas-dev +``` + +For other libraries, visit the [Math Library Selection](build_from_source#math-library-selection) section. + +**Step 3:** Install OpenCV. + +*MXNet* uses [OpenCV](http://opencv.org/) for efficient image loading and augmentation operations. + +```bash + sudo apt-get install -y libopencv-dev +``` + +**Step 4:** Download MXNet sources and build MXNet core shared library. + +If building on CPU and using OpenBLAS: + +Clone the repository: + +```bash + git clone --recursive https://github.com/apache/incubator-mxnet.git + cd incubator-mxnet +``` + +Build with CMake and ninja, without GPU and without MKL. + +```bash + rm -rf build + mkdir -p build && cd build + cmake -GNinja \ + -DUSE_CUDA=OFF \ + -DUSE_MKL_IF_AVAILABLE=OFF \ + -DCMAKE_CUDA_COMPILER_LAUNCHER=ccache \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + .. + ninja +``` + +If building on CPU and using MKL and MKL-DNN (make sure MKL is installed according to [Math Library Selection](build_from_source#math-library-selection) and [MKL-DNN README](https://github.com/apache/incubator-mxnet/blob/master/docs/tutorials/mkldnn/MKLDNN_README.md)): + +```bash + rm -rf build + mkdir -p build && cd build + cmake -GNinja \ + -DUSE_CUDA=OFF \ + -DUSE_MKL_IF_AVAILABLE=ON \ + -DCMAKE_CUDA_COMPILER_LAUNCHER=ccache \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + .. + ninja +``` + +If building on GPU (make sure you have installed the [CUDA dependencies first](#cuda-dependencies)): +Cuda 10.1 in Ubuntu 18.04 builds fine but is not currently tested in CI. + +```bash + rm -rf build + mkdir -p build && cd build + cmake -GNinja \ + -DUSE_CUDA=ON \ + -DUSE_MKL_IF_AVAILABLE=OFF \ + -DCMAKE_CUDA_COMPILER_LAUNCHER=ccache \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + .. + ninja +``` + +*Note* - You can explore and use more compilation options as they are delcared in the top of `CMakeLists.txt` and also review common [usage examples](build_from_source#usage-examples). +Optionally, you can also use a higher level, scripted version of the above with an editable CMake options file by doing the +following: + +```bash +cp cmake/cmake_options.yml . +# Edit cmake_options.yml in the MXNet root to your taste +$EDITOR cmake_options.yml +# Launch a local CMake build +./dev_menu.py build +``` + +Building from source creates a library called ```libmxnet.so``` in the `build` folder in your MXNet project root. + +After building the MXNet library, you may install language bindings. + +
+ + +## Installing Language Packages for MXNet + +After you have installed the MXNet core library. You may install MXNet interface packages for the programming language of your choice: +- [Python](#install-mxnet-for-python) +- [C++](#install-the-mxnet-package-for-c++) +- [Clojure](#install-the-mxnet-package-for-clojure) +- [Julia](#install-the-mxnet-package-for-julia) +- [Perl](#install-the-mxnet-package-for-perl) +- [R](#install-the-mxnet-package-for-r) +- [Scala](#install-the-mxnet-package-for-scala) +- [Java](#install-the-mxnet-package-for-java) + +
+ +### Install MXNet for Python + +To install the MXNet Python binding navigate to the root of the MXNet folder then run the following: + +```bash +$ cd python +$ pip install -e . +``` + +Note that the `-e` flag is optional. It is equivalent to `--editable` and means that if you edit the source files, these changes will be reflected in the package installed. + +#### Optional Python Packages + +You may optionally install ```graphviz``` library that is used for visualizing network graphs you build on MXNet. You may also install [Jupyter Notebook](http://jupyter.readthedocs.io/) which is used for running MXNet tutorials and examples. + +```bash +sudo pip install graphviz==0.8.4 \ + jupyter +``` +
+ + +### Install the MXNet Package for C++ + +Refer to the [C++ Package setup guide](c_plus_plus). +
+ + +### Install the MXNet Package for Clojure + +Refer to the [Clojure setup guide](https://github.com/apache/incubator-mxnet/tree/master/contrib/clojure-package). +
+ + +### Install the MXNet Package for Julia + +#### Install Julia +The package available through `apt-get` is old and not compatible with the latest version of MXNet. +Fetch the latest version (1.0.3 at the time of this writing). + +```bash +wget -qO julia-10.tar.gz https://julialang-s3.julialang.org/bin/linux/x64/1.0/julia-1.0.3-linux-x86_64.tar.gz +``` + +Place the extracted files somewhere like a julia folder in your home dir. + +```bash +mkdir ~/julia +mv julia-10.tar.gz ~/julia +cd ~/julia +tar xvf julia-10.tar.gz +``` + +Test Julia. +```bash +cd julia-1.0.3/bin +julia -e 'using InteractiveUtils; versioninfo()' +``` + +If you're still getting the old version, remove it. +```bash +sudo apt remove julia +``` + +Update your PATH to have Julia's new location. Add this to your `.zshrc`, `.bashrc`, `.profile` or `.bash_profile`. +```bash +export PATH=~/julia/julia-1.0.3/bin:$PATH +``` + +Validate your PATH. +```bash +echo $PATH +``` + +Validate Julia works and is the expected version. +```bash +julia -e 'using InteractiveUtils; versioninfo()' +``` + +#### Setup Your MXNet-Julia Environment + +**For each of the following environment variables, add the commands to your `.zshrc`, `.bashrc`, `.profile` or `.bash_profile` to make them persist.** + +Create a `julia-depot` folder and environment variable. +```bash +mkdir julia-depot +export JULIA_DEPOT_PATH=$HOME/julia/julia-depot +``` + +To use the Julia binding with an existing `libmxnet` installation, set the `MXNET_HOME` environment variable to the MXNet source root. For example: +```bash +export MXNET_HOME=$HOME/incubator-mxnet +``` + +Now set the `LD_LIBRARY_PATH` environment variable to where `libmxnet.so` is found. If you can't find it, you might have skipped the building MXNet step. Go back and [build MXNet](#build-the-shared-library) first. For example: +```bash +export LD_LIBRARY_PATH=$HOME/incubator-mxnet/lib:$LD_LIBRARY_PATH +``` + +Verify the location of `libjemalloc.so` and set the `LD_PRELOAD` environment variable. +```bash +export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so:$LD_PRELOAD +``` + +With all of these updates, here's an example of what you might want to have in your `.zshrc`, `.bashrc`, `.profile` or `.bash_profile`. + +``` +export PATH=$HOME/bin:$HOME/.local/bin:$HOME/julia/julia-1.0.3/bin:$PATH +export JULIA_DEPOT_PATH=$HOME/julia/julia-depot +export MXNET_HOME=$HOME/incubator-mxnet +export LD_LIBRARY_PATH=$HOME/incubator-mxnet/lib:$LD_LIBRARY_PATH +export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so:$LD_PRELOAD +``` + +Install MXNet with Julia: + +```bash +julia --color=yes --project=./ -e \ + 'using Pkg; \ + Pkg.develop(PackageSpec(name="MXNet", path = joinpath(ENV["MXNET_HOME"], "julia")))' +``` + +For more details about installing and using MXNet with Julia, see the [MXNet Julia documentation](../api/julia/site/). +
+ + +### Install the MXNet Package for Perl + +Before you build MXNet for Perl from source code, you must complete [building the shared library](#build-the-shared-library). After you build the shared library, run the following command from the MXNet source root directory to build the MXNet Perl package: + +```bash + sudo apt-get install libmouse-perl pdl cpanminus swig libgraphviz-perl + cpanm -q -L "${HOME}/perl5" Function::Parameters Hash::Ordered PDL::CCS + + MXNET_HOME=${PWD} + export LD_LIBRARY_PATH=${MXNET_HOME}/lib + export PERL5LIB=${HOME}/perl5/lib/perl5 + + cd ${MXNET_HOME}/perl-package/AI-MXNetCAPI/ + perl Makefile.PL INSTALL_BASE=${HOME}/perl5 + make install + + cd ${MXNET_HOME}/perl-package/AI-NNVMCAPI/ + perl Makefile.PL INSTALL_BASE=${HOME}/perl5 + make install + + cd ${MXNET_HOME}/perl-package/AI-MXNet/ + perl Makefile.PL INSTALL_BASE=${HOME}/perl5 + make install +``` +
+ + +### Install the MXNet Package for R + +Building *MXNet* from source is a 2 step process. +1. Build the *MXNet* core shared library, `libmxnet.so`, from source. +2. Build the R bindings. + +#### Quick MXNet-R Installation +You can quickly build MXNet-R with the following two scripts found in the `/docs/install` folder: + +```bash +git clone --recursive https://github.com/apache/incubator-mxnet.git mxnet +cd mxnet/docs/install +./install_mxnet_ubuntu_python.sh +./install_mxnet_ubuntu_r.sh +``` + +Or you can go through a manual process described next. + +#### Manual MXNet-R Installation + +**Minimum Requirements** +1. [GCC 4.8](https://gcc.gnu.org/gcc-4.8/) or later to compile C++ 11. +2. [GNU Make](https://www.gnu.org/software/make/) + +
+ +**Build the MXNet core shared library** + +**Step 1** Install build tools and git. +```bash +$ sudo apt-get update +$ sudo apt-get install -y build-essential git +``` + +**Step 2** Install OpenBLAS. + +*MXNet* uses [BLAS](https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms) and [LAPACK](https://en.wikipedia.org/wiki/LAPACK) libraries for accelerated numerical computations on CPU machine. There are several flavors of BLAS/LAPACK libraries - [OpenBLAS](http://www.openblas.net/), [ATLAS](http://math-atlas.sourceforge.net/) and [MKL](https://software.intel.com/en-us/intel-mkl). In this step we install OpenBLAS. You can choose to install ATLAS or MKL. +```bash +$ sudo apt-get install -y libopenblas-dev liblapack-dev +``` + +**Step 3** Install OpenCV. + +*MXNet* uses [OpenCV](http://opencv.org/) for efficient image loading and augmentation operations. +```bash +$ sudo apt-get install -y libopencv-dev +``` + +**Step 4** Download MXNet sources and build MXNet core shared library. You can clone the repository as described in the following code block, or you may try the [download links](download) for your desired MXNet version. + +```bash +$ git clone --recursive https://github.com/apache/incubator-mxnet +$ cd incubator-mxnet +$ echo "USE_OPENCV = 1" >> ./config.mk +$ echo "USE_BLAS = openblas" >> ./config.mk +$ make -j $(nproc) +``` + +*Note* - USE_OPENCV and USE_BLAS are make file flags to set compilation options to use OpenCV and BLAS library. You can explore and use more compilation options in `make/config.mk`. + +
+ +**Step 5** Make and install the MXNet-R bindings. + +```bash +$ make rpkg +``` +#### Verify MXNet-R Installation + +You can verify your MXNet-R installation as follows: + +```bash +sudo -i R +``` + +At the R prompt enter the following: + +```r +library(mxnet) +a <- mx.nd.ones(c(2,3), ctx = mx.cpu()) +b <- a * 2 + 1 +b +``` + +You should see the following output: + +``` + [,1] [,2] [,3] +[1,] 3 3 3 +[2,] 3 3 3 +> quit() +``` +
+ + +### Install the MXNet Package for Scala + +To use the MXNet-Scala package, you can acquire the Maven package as a dependency. + +Further information is in the [MXNet-Scala Setup Instructions](scala_setup). + +If you use IntelliJ or a similar IDE, you may want to follow the [MXNet-Scala on IntelliJ tutorial](../api/scala/docs/tutorials/mxnet_scala_on_intellij) instead. +
+ +### Install the MXNet Package for Java + +To use the MXNet-Java package, you can acquire the Maven package as a dependency. + +Further information is in the [MXNet-Java Setup Instructions](java_setup). + +If you use IntelliJ or a similar IDE, you may want to follow the [MXNet-Java on IntelliJ tutorial](../api/java/docs/tutorials/mxnet_java_on_intellij) instead. +
+ +## Contributions + +You are more than welcome to contribute easy installation scripts for other operating systems and programming languages. See the [community contributions page](../community/contribute) for further information. + +## Next Steps + +* [Tutorials](../api/python/docs/tutorials) +* [How To](../api) +* [Architecture](../api) + + + diff --git a/docs/static_site/src/pages/get_started/validate_mxnet.md b/docs/static_site/src/pages/get_started/validate_mxnet.md new file mode 100644 index 000000000000..8cdb05170903 --- /dev/null +++ b/docs/static_site/src/pages/get_started/validate_mxnet.md @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: Validate MXNet +action: Get Started +action_url: /get_started +permalink: /get_started/validate_mxnet +--- + +# Validate Your MXNet Installation + +- [Python](#python) +- [Python with GPU](#python-with-gpu) +- [Verify GPU training](#verify-gpu-training) +- [Virtualenv](#virtualenv) +- [Docker with CPU](#docker-with-cpu) +- [Docker with GPU](#docker-with-gpu) +- [Cloud](#cloud) +- [C++](#alternative-language-bindings) +- [Clojure](#clojure) +- [Julia](#julia) +- [Perl](#perl) +- [R](#r) +- [Scala](#scala) + + +## Python + +Start the python terminal. + +```bash +$ python +``` + +Run a short *MXNet* python program to create a 2X3 matrix of ones, multiply each element in the matrix by 2 followed by adding 1. We expect the output to be a 2X3 matrix with all elements being 3. + +```python +>>> import mxnet as mx +>>> a = mx.nd.ones((2, 3)) +>>> b = a * 2 + 1 +>>> b.asnumpy() +array([[ 3., 3., 3.], + [ 3., 3., 3.]], dtype=float32) +``` + + +## Python with GPU + +This is similar to the previous example, but this time we use *mx.gpu()*, to set *MXNet* context to be GPUs. + +```python +>>> import mxnet as mx +>>> a = mx.nd.ones((2, 3), mx.gpu()) +>>> b = a * 2 + 1 +>>> b.asnumpy() +array([[ 3., 3., 3.], + [ 3., 3., 3.]], dtype=float32) +``` + + +## Verify GPU Training + +From the MXNet root directory run: `python example/image-classification/train_mnist.py --network lenet --gpus 0` to test GPU training. + + +## Virtualenv + +Activate the virtualenv environment created for *MXNet*. + +```bash +$ source ~/mxnet/bin/activate +``` + +After activating the environment, you should see the prompt as below. + +```bash +(mxnet)$ +``` + +Start the python terminal. + +```bash +$ python +``` + +Run the previous Python example. + + +## Docker with CPU + +Launch a Docker container with `mxnet/python` image and run example *MXNet* python program on the terminal. + +```bash +$ docker run -it mxnet/python bash # Use sudo if you skip Step 2 in the installation instruction + +# Start a python terminal +root@4919c4f58cac:/# python +``` + +Run the previous Python example. + + +## Docker with GPU + +Launch a NVIDIA Docker container with `mxnet/python:gpu` image and run example *MXNet* python program on the terminal. + +```bash +$ nvidia-docker run -it mxnet/python:gpu bash # Use sudo if you skip Step 2 in the installation instruction + +# Start a python terminal +root@4919c4f58cac:/# python +``` + +Run the previous Python example and run the previous GPU examples. + + +## Cloud + +Login to the cloud instance you launched, with pre-installed *MXNet*, following the guide by corresponding cloud provider. + +Start the python terminal. + +```bash +$ python +``` + +Run the previous Python example, and for GPU instances run the previous GPU example. + + +## Alternative Language Bindings + +### C++ + +Please contribute an example! + + +### Clojure + +Please contribute an example! + + +### Julia + +Please contribute an example! + + +### Perl + +Start the pdl2 terminal. + +```bash +$ pdl2 +``` + +Run a short *MXNet* Perl program to create a 2X3 matrix of ones, multiply each element in the matrix by 2 followed by adding 1. We expect the output to be a 2X3 matrix with all elements being 3. + +```perl +pdl> use AI::MXNet qw(mx) +pdl> $a = mx->nd->ones([2, 3]) +pdl> $b = $a * 2 + 1 +pdl> print $b->aspdl + +[ + [3 3 3] + [3 3 3] +] +``` + +### R + +Run a short *MXNet* R program to create a 2X3 matrix of ones, multiply each element in the matrix by 2 followed by adding 1. We expect the output to be a 2X3 matrix with all elements being 3. + +```r +library(mxnet) +a <- mx.nd.ones(c(2,3), ctx = mx.cpu()) +b <- a * 2 + 1 +b +``` + +You should see the following output: + +```r +[,1] [,2] [,3] +[1,] 3 3 3 +[2,] 3 3 3 +``` + + +#### R with GPU + +This is similar to the previous example, but this time we use *mx.gpu()*, to set *MXNet* context to be GPUs. + +```r +library(mxnet) +a <- mx.nd.ones(c(2,3), ctx = mx.gpu()) +b <- a * 2 + 1 +b +``` + +You should see the following output: + +```r +[,1] [,2] [,3] +[1,] 3 3 3 +[2,] 3 3 3 +``` + + +### Scala + +Run the MXNet-Scala demo project to validate your Maven package installation. diff --git a/docs/static_site/src/pages/get_started/windows_setup.md b/docs/static_site/src/pages/get_started/windows_setup.md new file mode 100644 index 000000000000..a7a3aabfa75b --- /dev/null +++ b/docs/static_site/src/pages/get_started/windows_setup.md @@ -0,0 +1,490 @@ + + + + + + + + + + + + + + + + +--- +layout: page +title: Windows Setup +action: Get Started +action_url: /get_started +permalink: /get_started/windows_setup +--- + +# Installing MXNet on Windows + +The following describes how to install with pip for computers with CPUs, Intel CPUs, and NVIDIA GPUs. Further along in the document you can learn how to build MXNet from source on Windows, or how to install packages that support different language APIs to MXNet. + +- [Prerequisites](#prerequisites) +- [Install MXNet with Python](#install-mxnet-with-python) + - [Install with CPUs](#install-with-cpus) + - [Install with Intel CPUs](#install-with-intel-cpus) + - [Install with GPUs](#install-with-gpus) + - [Notes on the Python Packages](#notes-on-the-python-packages) +- [Build from Source](#build-from-source) +- Install MXNet with a Programming Language API + - [Python](#install-the-mxnet-package-for-python) + - [R](#install-the-mxnet-package-for-r) + - [Julia](#install-the-mxnet-package-for-julia) + + +## Prerequisites + +### Minimum System Requirements + +* Windows 71, 10, Server 2012 R2, or Server 2016 +* Visual Studio 2015 or 2017 (any type) +* Python 2.7 or 3.6 +* pip + +1. There are [known issues](https://github.com/apache/incubator-mxnet/issues?utf8=%E2%9C%93&q=is%3Aissue+windows7+label%3AWindows+) with Windows 7. + +### Recommended System Requirements + +* Windows 10, Server 2012 R2, or Server 2016 +* Visual Studio 2017 (any type) +* At least one [NVIDIA CUDA-enabled GPU](https://developer.nvidia.com/cuda-gpus) +* MKL-enabled CPU: Intel® Xeon® processor, Intel® Core™ processor family, Intel Atom® processor, or Intel® Xeon Phi™ processor +* Python 2.7 or 3.6 +* pip + + +## Install MXNet with Python + +The easiest way to install MXNet on Windows is by using a [Python pip package](https://pip.pypa.io/en/stable/installing/). + +**Note**: Windows pip packages typically release a few days after a new version MXNet is released. Make sure you verify which version gets installed. + +### Install with CPUs + +Install MXNet with CPU support with Python: + +```bash +pip install mxnet +``` + +Now [validate your MXNet installation with Python](validate_mxnet.md). + +### Install with Intel CPUs + +MXNet has experimental support for Intel [MKL](https://software.intel.com/en-us/mkl) and [MKL-DNN](https://github.com/intel/mkl-dnn). When using supported Intel hardware, inference and training can be vastly faster when using MXNet with [MKL](https://software.intel.com/en-us/mkl) or [MKL-DNN](https://github.com/intel/mkl-dnn). + +The following steps will setup MXNet with MKL. MKL-DNN can be enabled only when building from source. +1. Download and install [Intel MKL](https://software.intel.com/en-us/mkl/choose-download/windows) (registration required). +1. Install MXNet with MKL support with Python: + +```bash +pip install mxnet-mkl +``` + +Now [validate your MXNet installation with Python](validate_mxnet.md). + +### Install with NVIDIA GPUs + +When using supported NVIDIA GPU hardware, inference and training can be vastly faster with [NVIDIA CUDA](https://developer.nvidia.com/cuda-toolkit) and [cuDNN](https://developer.nvidia.com/cudnn). You have two options for installing MXNet with CUDA support with a Python package. +- [Install with CUDA support](#install-with-cuda-support) +- [Install with CUDA and MKL support](#install-with-cuda-and-mkl-support) + +#### Install with CUDA Support + +The following steps will setup MXNet with CUDA. cuDNN can be enabled only when building from source. +1. Install [Microsoft Visual Studio 2017](https://www.visualstudio.com/downloads/) or [Microsoft Visual Studio 2015](https://www.visualstudio.com/vs/older-downloads/). +1. Download and install [NVIDIA CUDA](https://developer.nvidia.com/cuda-downloads?target_os=Windows&target_arch=x86_64&target_version=10&target_type=exelocal). CUDA versions 9.2 or 9.0 are recommended. Some [issues with CUDA 9.1](https://github.com/apache/incubator-mxnet/labels/CUDA) have been identified in the past. +1. Download and install [NVIDIA_CUDA_DNN](https://docs.nvidia.com/deeplearning/sdk/cudnn-install/index.html#install-windows) +1. Install MXNet with CUDA support with pip: + +```bash +pip install mxnet-cu92 +``` + +Once you have installed a version of MXNet, [validate your MXNet installation with Python](validate_mxnet.md). + +#### Install with CUDA and MKL Support + +You can also use a combination of CPU/GPU enhancements provided by Intel and NVIDIA. + +The following steps will setup MXNet with CUDA and MKL. +1. Install [Microsoft Visual Studio 2017](https://www.visualstudio.com/downloads/) or [Microsoft Visual Studio 2015](https://www.visualstudio.com/vs/older-downloads/). +1. Download and install [Intel MKL](https://software.intel.com/en-us/mkl/choose-download/windows) (registration required). +1. Download and install [NVIDIA CUDA](https://developer.nvidia.com/cuda-downloads?target_os=Windows&target_arch=x86_64&target_version=10&target_type=exelocal). +1. Download and install [NVIDIA_CUDA_DNN](https://docs.nvidia.com/deeplearning/sdk/cudnn-install/index.html#install-windows) +1. Install MXNet with MKL support with pip: + +```bash +pip install mxnet-cu92mkl +``` + +Once you have installed a version of MXNet, [validate your MXNet installation with Python](validate_mxnet.md). + +### Notes on the Python Packages +To get further enhancements for deep neural networks, you may want to enable MKL-DNN and/or cuDNN. Each of these require you to [build from source](#build-from-source) and to enable the build flags for each. + +Check the chart below for other options or refer to [PyPI for other MXNet pip packages](https://pypi.org/project/mxnet/). + +![pip packages](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/install/pip-packages.png) + + +## Build from Source + +**IMPORTANT: It is recommended that you review the [build from source guide](build_from_source.md) first.** It describes many of the build options that come with MXNet in more detail. You may decide to install additional dependencies and modify your build flags after reviewing this material. + +We provide two primary options to build and install MXNet yourself using [Microsoft Visual Studio 2017](https://www.visualstudio.com/downloads/) or [Microsoft Visual Studio 2015](https://www.visualstudio.com/vs/older-downloads/). + +**NOTE:** Visual Studio 2017's compiler is `vc15`. This is not to be confused with Visual Studio 2015's compiler, `vc14`. + +You also have the option to install MXNet with MKL or MKL-DNN. In this case it is recommended that you refer to the [MKLDNN_README](https://github.com/apache/incubator-mxnet/blob/master/docs/tutorials/mkldnn/MKLDNN_README.md). + +**Option 1: Build with Microsoft Visual Studio 2017 (VS2017)** + +To build and install MXNet yourself using [VS2017](https://www.visualstudio.com/downloads/), you need the following dependencies. You may try a newer version of a particular dependency, but please open a pull request or [issue](https://github.com/apache/incubator-mxnet/issues/new) to update this guide if a newer version is validated. + +1. Install or update VS2017. + - If [VS2017](https://www.visualstudio.com/downloads/) is not already installed, download and install it. You can download and install the free community edition. + - When prompted about installing Git, go ahead and install it. + - If VS2017 is already installed you will want to update it. Proceed to the next step to modify your installation. You will be given the opportunity to update VS2017 as well +1. Follow the [instructions for opening the Visual Studio Installer](https://docs.microsoft.com/en-us/visualstudio/install/modify-visual-studio) to modify `Individual components`. +1. Once in the Visual Studio Installer application, update as needed, then look for and check `VC++ 2017 version 15.4 v14.11 toolset`, and click `Modify`. +1. Change the version of the Visual studio 2017 to v14.11 using the following command (by default the VS2017 is installed in the following path): +``` +"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" -vcvars_ver=14.11 +``` +1. Download and install [CMake](https://cmake.org/download) if it is not already installed. [CMake v3.12.2](https://cmake.org/files/v3.12/cmake-3.12.2-win64-x64.msi) has been tested with MXNet. +1. Download and run the [OpenCV](https://sourceforge.net/projects/opencvlibrary/files/opencv-win/3.4.1/opencv-3.4.1-vc14_vc15.exe/download) package. There are more recent versions of OpenCV, so please create an issue/PR to update this info if you validate one of these later versions. +1. This will unzip several files. You can place them in another directory if you wish. We will use `C:\utils`(```mkdir C:\utils```) as our default path. +1. Set the environment variable `OpenCV_DIR` to point to the OpenCV build directory that you just unzipped. Start ```cmd``` and type `set OpenCV_DIR=C:\utils\opencv\build`. +1. If you don’t have the Intel Math Kernel Library (MKL) installed, you can install it and follow the [MKLDNN_README](https://github.com/apache/incubator-mxnet/blob/master/docs/tutorials/mkldnn/MKLDNN_README.md) from here, or you can use OpenBLAS. These instructions will assume you're using OpenBLAS. +1. Download the [OpenBlas](https://sourceforge.net/projects/openblas/files/v0.2.19/OpenBLAS-v0.2.19-Win64-int32.zip/download) package. Later versions of OpenBLAS are available, but you would need to build from source. v0.2.19 is the most recent version that ships with binaries. Contributions of more recent binaries would be appreciated. +1. Unzip the file, rename it to ```OpenBLAS``` and put it under `C:\utils`. You can place the unzipped files and folders in another directory if you wish. +1. Set the environment variable `OpenBLAS_HOME` to point to the OpenBLAS directory that contains the `include` and `lib` directories and type `set OpenBLAS_HOME=C:\utils\OpenBLAS` on the command prompt(```cmd```). +1. Download and install [CUDA](https://developer.nvidia.com/cuda-downloads?target_os=Windows&target_arch=x86_64&target_version=10&target_type=exelocal). If you already had CUDA, then installed VS2017, you should reinstall CUDA now so that you get the CUDA toolkit components for VS2017 integration. Note that the latest CUDA version supported by MXNet is [9.2](https://developer.nvidia.com/cuda-92-download-archive). You might also want to find other CUDA verion on the [Legacy Releases](https://developer.nvidia.com/cuda-toolkit-archive). +1. Download and install cuDNN. To get access to the download link, register as an NVIDIA community user. Then follow the [link](http://docs.nvidia.com/deeplearning/sdk/cudnn-install/index.html#install-windows) to install the cuDNN and put those libraries into ```C:\cuda```. +1. Download and install [git](https://git-for-windows.github.io/) if you haven't already. + +After you have installed all of the required dependencies, build the MXNet source code: + +1. Start ```cmd``` in windows. +2. Download the MXNet source code from GitHub by using following command: +``` +cd C:\ +git clone https://github.com/apache/incubator-mxnet.git --recursive +``` +3. Verify that the `DCUDNN_INCLUDE` and `DCUDNN_LIBRARY` environment variables are pointing to the `include` folder and `cudnn.lib` file of your CUDA installed location, and `C:\incubator-mxnet` is the location of the source code you just cloned in the previous step. +4. Create a build dir using the following command and go to the directory, for example: +``` +mkdir C:\incubator-mxnet\build +cd C:\incubator-mxnet\build +``` +5. Compile the MXNet source code with `cmake` by using following command: +``` +cmake -G "Visual Studio 15 2017 Win64" -T cuda=9.2,host=x64 -DUSE_CUDA=1 -DUSE_CUDNN=1 -DUSE_NVRTC=1 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_BLAS=open -DUSE_LAPACK=1 -DUSE_DIST_KVSTORE=0 -DCUDA_ARCH_LIST=Common -DCUDA_TOOLSET=9.2 -DCUDNN_INCLUDE=C:\cuda\include -DCUDNN_LIBRARY=C:\cuda\lib\x64\cudnn.lib "C:\incubator-mxnet" +``` +* Make sure you set the environment variables correctly (OpenBLAS_HOME, OpenCV_DIR) and change the version of the Visual studio 2017 to v14.11 before enter above command. +6. After the CMake successfully completed, compile the MXNet source code by using following command: +``` +msbuild mxnet.sln /p:Configuration=Release;Platform=x64 /maxcpucount +``` + + +**Option 2: Build with Visual Studio 2015** + +To build and install MXNet yourself using [Microsoft Visual Studio 2015](https://www.visualstudio.com/vs/older-downloads/), you need the following dependencies. You may try a newer version of a particular dependency, but please open a pull request or [issue](https://github.com/apache/incubator-mxnet/issues/new) to update this guide if a newer version is validated. + +1. If [Microsoft Visual Studio 2015](https://www.visualstudio.com/vs/older-downloads/) is not already installed, download and install it. You can download and install the free community edition. At least Update 3 of Microsoft Visual Studio 2015 is required to build MXNet from source. Upgrade via it's ```Tools -> Extensions and Updates... | Product Updates``` menu. +2. Download and install [CMake](https://cmake.org/) if it is not already installed. +3. Download and install [OpenCV](http://sourceforge.net/projects/opencvlibrary/files/opencv-win/3.0.0/opencv-3.0.0.exe/download). +4. Unzip the OpenCV package. +5. Set the environment variable ```OpenCV_DIR``` to point to the ```OpenCV build directory``` (```C:\opencv\build\x64\vc14``` for example). Also, you need to add the OpenCV bin directory (```C:\opencv\build\x64\vc14\bin``` for example) to the ``PATH`` variable. +6. If you don't have the Intel Math Kernel Library (MKL) installed, download and install [OpenBlas](http://sourceforge.net/projects/openblas/files/v0.2.14/). +7. Set the environment variable ```OpenBLAS_HOME``` to point to the ```OpenBLAS``` directory that contains the ```include``` and ```lib``` directories. Typically, you can find the directory in ```C:\Program files (x86)\OpenBLAS\```. +8. Download and install [CUDA](https://developer.nvidia.com/cuda-downloads?target_os=Windows&target_arch=x86_64) and [cuDNN](https://developer.nvidia.com/cudnn). To get access to the download link, register as an NVIDIA community user. +9. Set the environment variable ```CUDACXX``` to point to the ```CUDA Compiler```(```C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.1\bin\nvcc.exe``` for example). +10. Set the environment variable ```CUDNN_ROOT``` to point to the ```cuDNN``` directory that contains the ```include```, ```lib``` and ```bin``` directories (```C:\Downloads\cudnn-9.1-windows7-x64-v7\cuda``` for example). + +After you have installed all of the required dependencies, build the MXNet source code: + +1. Download the MXNet source code from [GitHub](https://github.com/apache/incubator-mxnet) (make sure you also download third parties submodules e.g. ```git clone --recurse-submodules```). +2. Use [CMake](https://cmake.org/) to create a Visual Studio solution in ```./build```. +3. In Visual Studio, open the solution file,```.sln```, and compile it. +These commands produce a library called ```mxnet.dll``` in the ```./build/Release/``` or ```./build/Debug``` folder. + +  +Next, we install ```graphviz``` library that we use for visualizing network graphs you build on MXNet. We will also install [Jupyter Notebook](http://jupyter.readthedocs.io/) used for running MXNet tutorials and examples. +- Install ```graphviz``` by downloading MSI installer from [Graphviz Download Page](https://graphviz.gitlab.io/_pages/Download/Download_windows.html). +**Note** Make sure to add graphviz executable path to PATH environment variable. Refer [here for more details](http://stackoverflow.com/questions/35064304/runtimeerror-make-sure-the-graphviz-executables-are-on-your-systems-path-aft) +- Install ```Jupyter``` by installing [Anaconda for Python 2.7](https://www.anaconda.com/download/) +**Note** Do not install Anaconda for Python 3.5. MXNet has a few compatibility issues with Python 3.5. + +We have installed MXNet core library. Next, we will install MXNet interface package for programming language of your choice: +- [Python](#install-the-mxnet-package-for-python) +- [R](#install-mxnet-package-for-r) +- [Julia](#install-the-mxnet-package-for-julia) +- **Scala** is not yet available for Windows + +## Install the MXNet Package for Python + +These steps are required after building from source. If you already installed MXNet by using pip, you do not need to do these steps to use MXNet with Python. + +1. Install ```Python``` using windows installer available [here](https://www.python.org/downloads/release/python-2712/). +2. Install ```Numpy``` using windows installer available [here](https://scipy.org/index.html). +3. Start ```cmd``` and create a folder named ```common```(```mkdir C:\common```) +4. Download the [mingw64_dll.zip](https://sourceforge.net/projects/openblas/files/v0.2.12/mingw64_dll.zip/download), unzip and copy three libraries (.dll files) that openblas.dll depends on to ```C:\common```. +5. Copy the required .dll file to ```C:\common``` and make sure following libraries (.dll files) in the folder. +``` +libgcc_s_seh-1.dll (in mingw64_dll) +libgfortran-3.dll (in mingw64_dll) +libquadmath-0.dll (in mingw64_dll) +libopenblas.dll (in OpenBlas folder you download) +opencv_world341.dll (in OpenCV folder you download) +``` +6. Add ```C:\common``` to Environment Variables. + * Type ```control sysdm.cpl``` on ```cmp``` + * Select the **Advanced tab** and click **Environment Variables** + * Double click the **Path** and click **New** + * Add ```C:\common``` and click OK +7. Use setup.py to install the package. +```bash + # Assuming you are in root mxnet source code folder + cd python + python setup.py install +``` + +Done! We have installed MXNet with Python interface. + +You can continue with using MXNet-Python, or if you want to try a different language API for MXNet, keep reading. Otherwise, jump ahead to [next steps](#next-steps). + +## Install the MXNet Package for R +MXNet for R is available for both CPUs and GPUs. + +### Installing MXNet-R on a Computer with a CPU Processor + +To install MXNet on a computer with a CPU processor, choose from two options: + +* Use the prebuilt binary package +* Build the library from source code + +#### Installing MXNet-R with the Prebuilt Binary Package(CPU) +For Windows users, MXNet provides prebuilt binary packages. +You can install the package directly in the R console. + +Note: packages for 3.6.x are not yet available. +Install 3.5.x of R from [CRAN](https://cran.r-project.org/bin/windows/base/old/). + +For CPU-only package: + +```r + cran <- getOption("repos") + cran["dmlc"] <- "https://apache-mxnet.s3-accelerate.dualstack.amazonaws.com/R/CRAN/" + options(repos = cran) + install.packages("mxnet") +``` + +#### Building MXNet-R from Source Code(CPU) +1. Clone the MXNet github repo. +```sh +git clone --recursive https://github.com/apache/incubator-mxnet +``` +The `--recursive` is to clone all the submodules used by MXNet. You will be editing the ```"/mxnet/R-package"``` folder. +2. Download prebuilt GPU-enabled MXNet libraries for Windows from [Windows release](https://github.com/yajiedesign/mxnet/releases). You will need `mxnet_x64_vc14_cpu.7z` and `prebuildbase_win10_x64_vc14.7z` where X stands for your CUDA toolkit version +3. Create a folder called ```R-package/inst/libs/x64```. MXNet supports only 64-bit operating systems, so you need the x64 folder. +4. Copy the following shared libraries (.dll files) into the ```R-package/inst/libs/x64``` folder. +``` +libgcc_s_seh-1.dll +libgfortran-3.dll +libmxnet.dll +libmxnet.lib +libopenblas.dll +libquadmath-0.dll +mxnet.dll +unzip.exe +unzip32.dll +vcomp140.dll +wget.exe +``` +These dlls can be found in `prebuildbase_win10_x64_vc14/3rdparty`, `mxnet_x64_vc14_cpu/build`, `mxnet_x64_vc14_cpu/lib`. +5. Copy the header files from `dmlc`, `mxnet`, `mxshadow` and `nnvm` from `mxnet_x64_vc14_cpu/include and mxnet_x64_vc14_cpu/nvnm/include` into `./R-package/inst/include`. It should look like +``` +./R-package/inst +└── include + ├── dmlc + ├── mxnet + ├── mshadow + └── nnvm +``` + +6. Make sure that R executable is added to your ```PATH``` in the environment variables. Running the ```where R``` command at the command prompt should return the location. +7. Also make sure that Rtools is installed and the executable is added to your ```PATH``` in the environment variables. +8. Temporary patch - im2rec currently results in crashes during the build. Remove the im2rec.h and im2rec.cc files in R-package/src/ from cloned repository and comment out the two im2rec lines in [R-package/src/mxnet.cc](https://github.com/apache/incubator-mxnet/blob/master/R-package/src/mxnet.cc) as shown below. +``` +#include "./kvstore.h" +#include "./export.h" +//#include "./im2rec.h" +...... +...... + DataIterCreateFunction::InitRcppModule(); + KVStore::InitRcppModule(); + Exporter::InitRcppModule(); +//IM2REC::InitRcppModule(); +} +``` + +9. Now open the Windows CMD with admin rights and change the directory to the `mxnet` folder(cloned repository). Then use the following commands to build R package: + +```bash +echo import(Rcpp) > R-package\NAMESPACE +echo import(methods) >> R-package\NAMESPACE +Rscript -e "install.packages('devtools', repos = 'https://cloud.r-project.org')" +cd R-package +Rscript -e "library(devtools); library(methods); options(repos=c(CRAN='https://cloud.r-project.org')); install_deps(dependencies = TRUE)" +cd .. + +R CMD INSTALL --no-multiarch R-package + +Rscript -e "require(mxnet); mxnet:::mxnet.export('R-package')" +rm R-package/NAMESPACE +Rscript -e "require(devtools); install_version('roxygen2', version = '5.0.1', repos = 'https://cloud.r-project.org/', quiet = TRUE)" +Rscript -e "require(roxygen2); roxygen2::roxygenise('R-package')" + +R CMD INSTALL --build --no-multiarch R-package +``` + +### Installing MXNet-R on a Computer with a GPU Processor +To install MXNet on a computer with a GPU processor, choose from two options: + +* Use the prebuilt binary package +* Build the library from source code + +However, a few dependencies remain for both options. You will need the following: +* Install [Nvidia-drivers](http://www.nvidia.com/Download/index.aspx?lang=en-us) if not installed. Latest driver based on your system configuration is recommended. + +* Install [Microsoft Visual Studio](https://visualstudio.microsoft.com/downloads/) (VS2015 or VS2017 is required by CUDA) + +* Install [NVidia CUDA Toolkit](https://developer.nvidia.com/cuda-toolkit)(cu92 is recommended though we support cu80, cu90, cu91 and cu92) + +* Download and install [CuDNN](https://developer.nvidia.com/cudnn) (to provide a Deep Neural Network library). Latest version recommended. + +Note: A pre-requisite to above softwares is [Nvidia-drivers](http://www.nvidia.com/Download/index.aspx?lang=en-us) which we assume is installed. + +#### Installing MXNet-R with the Prebuilt Binary Package(GPU) +For Windows users, MXNet provides prebuilt binary packages. +You can install the package directly in the R console after you have the above software installed. + +Note: packages for 3.6.x are not yet available. +Install 3.5.x of R from [CRAN](https://cran.r-project.org/bin/windows/base/old/). + +For GPU package: + +```r + cran <- getOption("repos") + cran["dmlc"] <- "https://apache-mxnet.s3-accelerate.dualstack.amazonaws.com/R/CRAN/GPU/cu92" + options(repos = cran) + install.packages("mxnet") +``` +Change cu92 to cu80, cu90 or cu91 based on your CUDA toolkit version. Currently, MXNet supports these versions of CUDA. + +#### Building MXNet-R from Source Code(GPU) +After you have installed above software, continue with the following steps to build MXNet-R: +1. Clone the MXNet github repo. +```sh +git clone --recursive https://github.com/apache/incubator-mxnet +``` +The `--recursive` is to clone all the submodules used by MXNet. You will be editing the ```"/mxnet/R-package"``` folder. +2. Download prebuilt GPU-enabled MXNet libraries for Windows from https://github.com/yajiedesign/mxnet/releases. You will need `mxnet_x64_vc14_gpu_cuX.7z` and `prebuildbase_win10_x64_vc14.7z` where X stands for your CUDA toolkit version +3. Create a folder called ```R-package/inst/libs/x64```. MXNet supports only 64-bit operating systems, so you need the x64 folder. +4. Copy the following shared libraries (.dll files) into the ```R-package/inst/libs/x64``` folder: +``` +libgcc_s_seh-1.dll +libgfortran-3.dll +libmxnet.dll +libmxnet.lib +libopenblas.dll +libquadmath-0.dll +mxnet.dll +unzip.exe +unzip32.dll +vcomp140.dll +wget.exe +``` +These dlls can be found in `prebuildbase_win10_x64_vc14/3rdparty`, `mxnet_x64_vc14_gpu_cuX/build`, `mxnet_x64_vc14_gpu_cuX/lib`. +5. Copy the header files from `dmlc`, `mxnet`, `mxshadow` and `nnvm` from `mxnet_x64_vc14_gpuX/include` and `mxnet_x64_vc14_gpuX/nvnm/include` into `./R-package/inst/include`. It should look like: +``` +./R-package/inst +└── include + ├── dmlc + ├── mxnet + ├── mshadow + └── nnvm +``` +6. Make sure that R executable is added to your ```PATH``` in the environment variables. Running the ```where R``` command at the command prompt should return the location. +7. Also make sure that Rtools is installed and the executable is added to your ```PATH``` in the environment variables. +8. Temporary patch - im2rec currently results in crashes during the build. Remove the im2rec.h and im2rec.cc files in R-package/src/ from cloned repository and comment out the two im2rec lines in [R-package/src/mxnet.cc](https://github.com/apache/incubator-mxnet/blob/master/R-package/src/mxnet.cc) as shown below. +```bash +#include "./kvstore.h" +#include "./export.h" +//#include "./im2rec.h" +...... +...... + DataIterCreateFunction::InitRcppModule(); + KVStore::InitRcppModule(); + Exporter::InitRcppModule(); +//IM2REC::InitRcppModule(); +} +``` +9. Now open the Windows CMD with admin rights and change the directory to the `mxnet` folder(cloned repository). Then use the following commands +to build R package: + +```bat +echo import(Rcpp) > R-package\NAMESPACE +echo import(methods) >> R-package\NAMESPACE +Rscript -e "install.packages('devtools', repos = 'https://cloud.r-project.org')" +cd R-package +Rscript -e "library(devtools); library(methods); options(repos=c(CRAN='https://cloud.r-project.org')); install_deps(dependencies = TRUE)" +cd .. + +R CMD INSTALL --no-multiarch R-package + +Rscript -e "require(mxnet); mxnet:::mxnet.export('R-package')" +rm R-package/NAMESPACE +Rscript -e "require(devtools); install_version('roxygen2', version = '5.0.1', repos = 'https://cloud.r-project.org/', quiet = TRUE)" +Rscript -e "require(roxygen2); roxygen2::roxygenise('R-package')" + +R CMD INSTALL --build --no-multiarch R-package +``` + +**Note:** To maximize its portability, the MXNet library is built with the Rcpp end. Computers running Windows need [MSVC](https://en.wikipedia.org/wiki/Visual_C%2B%2B) (Microsoft Visual C++) to handle CUDA toolchain compatibilities. + +## Install the MXNet Package for Julia +The MXNet package for Julia is hosted in a separate repository, MXNet.jl, which is available on [GitHub](https://github.com/dmlc/MXNet.jl). To use Julia binding it with an existing libmxnet installation, set the ```MXNET_HOME``` environment variable by running the following command: + +```bash +export MXNET_HOME=//libmxnet +``` + +The path to the existing libmxnet installation should be the root directory of libmxnet. In other words, you should be able to find the ```libmxnet.so``` file at ```$MXNET_HOME/lib```. For example, if the root directory of libmxnet is ```~```, you would run the following command: + +```bash +export MXNET_HOME=/~/libmxnet +``` + +You might want to add this command to your ```~/.bashrc``` file. If you do, you can install the Julia package in the Julia console using the following command: + +```julia +Pkg.add("MXNet") +``` + +For more details about installing and using MXNet with Julia, see the [MXNet Julia documentation](/api/julia/site/). + + +## Installing the MXNet Package for Scala + +MXNet-Scala is not yet available for Windows. diff --git a/tests/nightly/apache_rat_license_check/rat-excludes b/tests/nightly/apache_rat_license_check/rat-excludes index c08a2816045f..4297fa6bdec4 100755 --- a/tests/nightly/apache_rat_license_check/rat-excludes +++ b/tests/nightly/apache_rat_license_check/rat-excludes @@ -1,8 +1,34 @@ +# Generated files +.*html +.buildinfo +Gemfile.lock +Manifest.toml +searchindex.js +_build/* +_static/* +_api/* +build/* +latex/* +target/* +site/* +xml/* + +# Separate licenses, refer to LICENSE in each folder or header + +# Sphinx themes +themes/* + +# Jekyll & other licenses in static_site/* +clipboard.js + +# Includes +_includes/* + +# Other \..* .*css \\.* .*ipynb -.*html .*json .*txt 3rdparty/* @@ -15,7 +41,6 @@ trunk/* .*cfg .*config __init__.py -build/* .*\\.t MANIFEST Changes diff --git a/tools/license_header.py b/tools/license_header.py index c8add72288e7..2e3a03451369 100755 --- a/tools/license_header.py +++ b/tools/license_header.py @@ -71,6 +71,12 @@ # 3rdparty headerfiles under different licenses 'include/mkldnn', + # Docs Sphinx themes under different licenses + 'docs/python_docs/themes', + + # Docs Jekyll website under different licenses + 'docs/static_site', + # Code shared with project by author - see file for details 'src/operator/special_functions-inl.h', @@ -91,6 +97,7 @@ 'docs/_static/searchtools_custom.js', 'docs/_static/js/clipboard.js', 'docs/_static/js/clipboard.min.js', + 'docs/static_site/src/assets/js/clipboard.js', # Licensed under 2-Clause BSD in header 'example/ssd/dataset/pycocotools/coco.py', From e98dbe776201424cff93f87e67ff3bddb87d45e5 Mon Sep 17 00:00:00 2001 From: Doron Singer <48903991+doronsinger@users.noreply.github.com> Date: Fri, 6 Sep 2019 17:53:04 +0300 Subject: [PATCH 290/813] Speed up group executor (#16069) * Speed up group executor Current implementation is O(n^2), this implementation is O(n) * Speed up group executor Current implementation is O(n^2), this implementation is O(n) * CI --- python/mxnet/module/executor_group.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/mxnet/module/executor_group.py b/python/mxnet/module/executor_group.py index 637acce317cc..d47665d6d509 100755 --- a/python/mxnet/module/executor_group.py +++ b/python/mxnet/module/executor_group.py @@ -273,9 +273,9 @@ def __init__(self, symbol, contexts, workload, data_shapes, label_shapes, param_ self.data_layouts = None self.label_layouts = None self.output_names = self.symbol.list_outputs() - self.output_layouts = [DataDesc.get_batch_axis(self.symbol[name].attr('__layout__')) - for name in self.output_names] - self.num_outputs = len(self.symbol.list_outputs()) + self.num_outputs = len(self.output_names) + self.output_layouts = [DataDesc.get_batch_axis(self.symbol[index].attr('__layout__')) + for index in range(self.num_outputs)] self.bind_exec(data_shapes, label_shapes, shared_group) From d85a2d08ad4419a15b521d4653cdd30957257d76 Mon Sep 17 00:00:00 2001 From: Haibin Lin Date: Fri, 6 Sep 2019 10:39:01 -0700 Subject: [PATCH 291/813] [DOC] Fix doc for nn.Embedding, nn.Dense and nd.Embedding (#15869) * Update basic_layers.py * Update indexing_op.cc --- python/mxnet/gluon/nn/basic_layers.py | 17 ++++++++++------- src/operator/tensor/indexing_op.cc | 5 +++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/python/mxnet/gluon/nn/basic_layers.py b/python/mxnet/gluon/nn/basic_layers.py index fb0b62e8a74f..61f9ea07fbec 100644 --- a/python/mxnet/gluon/nn/basic_layers.py +++ b/python/mxnet/gluon/nn/basic_layers.py @@ -151,8 +151,9 @@ class Dense(HybridBlock): created by the layer, and `bias` is a bias vector created by the layer (only applicable if `use_bias` is `True`). - Note: the input must be a tensor with rank 2. Use `flatten` to convert it - to rank 2 manually if necessary. + .. note:: + the input must be a tensor with rank 2. Use `flatten` to convert it + to rank 2 manually if necessary. Parameters ---------- @@ -379,11 +380,13 @@ class Embedding(HybridBlock): r"""Turns non-negative integers (indexes/tokens) into dense vectors of fixed size. eg. [4, 20] -> [[0.25, 0.1], [0.6, -0.2]] - Note: if `sparse_grad` is set to True, the gradient w.r.t weight will be - sparse. Only a subset of optimizers support sparse gradients, including SGD, AdaGrad - and Adam. By default lazy updates is turned on, which may perform differently - from standard updates. For more details, please check the Optimization API at: - https://mxnet.incubator.apache.org/api/python/optimization/optimization.html + .. note:: + if `sparse_grad` is set to True, the gradient w.r.t weight will be + sparse. Only a subset of optimizers support sparse gradients, including SGD, + AdaGrad and Adam. By default lazy updates is turned on, which may perform + differently from standard updates. For more details, please check the + Optimization API at: + https://mxnet.incubator.apache.org/api/python/optimization/optimization.html Parameters ---------- diff --git a/src/operator/tensor/indexing_op.cc b/src/operator/tensor/indexing_op.cc index 147205505e24..7a770163da17 100644 --- a/src/operator/tensor/indexing_op.cc +++ b/src/operator/tensor/indexing_op.cc @@ -490,8 +490,9 @@ All the input values should be integers in the range [0, input_dim). If the input_dim is ip0 and output_dim is op0, then shape of the embedding weight matrix must be (ip0, op0). -By default, if any index mentioned is too large, it is replaced by the index that addresses -the last vector in an embedding matrix. +When "sparse_grad" is False, if any index mentioned is too large, it is replaced by the index that +addresses the last vector in an embedding matrix. +When "sparse_grad" is True, an error will be raised if invalid indices are found. Examples:: From 255dff0ea56007780ef5d4e96123f9525f9a723b Mon Sep 17 00:00:00 2001 From: kshitij12345 Date: Fri, 6 Sep 2019 23:49:58 +0530 Subject: [PATCH 292/813] [MXNET-978] Higher Order Gradient Support `arctan`, `arctanh`, `radians`. (#15531) * support arc{tan/tanh} for higher order grad * add relevant tests * add new abstraction for Node operations * support radians for higher order grad * add test for radians * changes * use NodeOp for arctan. * update few comments. * update few variable names. * rename grad_x to x_grad * update comments * move node_op_util.h to src/nnvm * address comments * rename NodeOp to NodeOpGen. * rename Op to op. * fix file description --- src/nnvm/node_op_util.h | 76 +++++++++++++++++++ src/operator/tensor/elemwise_unary_op_trig.cc | 63 ++++++++++++++- .../python/unittest/test_higher_order_grad.py | 46 +++++++++++ 3 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 src/nnvm/node_op_util.h diff --git a/src/nnvm/node_op_util.h b/src/nnvm/node_op_util.h new file mode 100644 index 000000000000..8d5916aafff9 --- /dev/null +++ b/src/nnvm/node_op_util.h @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file node_op_util.h + * \brief abstraction for commonly used nnvm::Node operations. + */ +#ifndef MXNET_NNVM_NODE_OP_UTIL_H_ +#define MXNET_NNVM_NODE_OP_UTIL_H_ +#include +#include +#include +#include "../operator/elemwise_op_common.h" + +namespace mxnet { +namespace util { + +class NodeOpGen { + private: + const nnvm::NodePtr &dependent_node; + + public: + explicit NodeOpGen(const nnvm::NodePtr &dependent_node) : dependent_node{dependent_node} {} + + nnvm::NodeEntry mul(const nnvm::NodeEntry &lhs, const nnvm::NodeEntry &rhs) { + return nnvm::NodeEntry{mxnet::op::MakeNode("elemwise_mul", + dependent_node->attrs.name + "_mul", + {lhs, rhs}, nullptr, &dependent_node)}; + } + + nnvm::NodeEntry mul(const nnvm::NodeEntry &x, double scalar) { + const std::unordered_map scalar_dict = + {{"scalar", std::to_string(scalar)}}; + return nnvm::NodeEntry{mxnet::op::MakeNode("_mul_scalar", + dependent_node->attrs.name + "_mul_scalar", + {x}, &scalar_dict, &dependent_node)}; + } + + nnvm::NodeEntry mul(double scalar, const nnvm::NodeEntry &x) { + return NodeOpGen::mul(x, scalar); + } + + nnvm::NodeEntry div(const nnvm::NodeEntry &lhs, const nnvm::NodeEntry &rhs) { + return nnvm::NodeEntry{mxnet::op::MakeNode("elemwise_div", + dependent_node->attrs.name + "_div", + {lhs, rhs}, nullptr, &dependent_node)}; + } + + nnvm::NodeEntry square(const nnvm::NodeEntry &x) { + return nnvm::NodeEntry{mxnet::op::MakeNode("square", + dependent_node->attrs.name + "_square", + {x}, nullptr, &dependent_node)}; + } +}; + +} // namespace util +} // namespace mxnet + +#endif // MXNET_NNVM_NODE_OP_UTIL_H_ diff --git a/src/operator/tensor/elemwise_unary_op_trig.cc b/src/operator/tensor/elemwise_unary_op_trig.cc index ff210040d552..bd0c8973ae07 100644 --- a/src/operator/tensor/elemwise_unary_op_trig.cc +++ b/src/operator/tensor/elemwise_unary_op_trig.cc @@ -24,6 +24,7 @@ #include #include "elemwise_unary_op.h" #include "./elemwise_binary_op-inl.h" +#include "../../nnvm/node_op_util.h" namespace mxnet { namespace op { @@ -227,7 +228,35 @@ The storage type of ``arctan`` output depends upon the input storage type: .set_attr("FGradient", ElemwiseGradUseIn{ "_backward_arctan" }); MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU_DR(_backward_arctan, - unary_bwd); + unary_bwd) +.set_attr("FGradient", + [](const nnvm::NodePtr& n, const std::vector& ograds) { + // ograds[0]: head_grad_grads (dL/dxgrad) + // inputs[0]: dL/dy + // inputs[1]: x (ElemwiseGradUseIn) + // n: dL/dy * f'(x) + // f(x) = arctanh(x) + // dydx = f'(x) = 1/(1+x^2) + // f''(x) = f'(x) * f'(x) * -2 * x = (-2 * x) / (1 + x^2)^2 + // return: + // 0: dL/dy_grad * dy/dx + // 1: dL/dy_grad * dL/dy * f''(x) + auto dldy = n->inputs[0]; + auto x = n->inputs[1]; + auto dldy_mul_dydx = nnvm::NodeEntry{n}; + auto op = mxnet::util::NodeOpGen{n}; + + auto x_grad = op.div(dldy_mul_dydx, dldy); + auto x_grad_square = op.square(x_grad); + auto x_grad_square_mul_x = op.mul(x_grad_square, x); + auto x_grad_square_mul_2_x = op.mul(-2.0, x_grad_square_mul_x); + auto grad_grad_x = op.mul(dldy, x_grad_square_mul_2_x); + + std::vector ret; + ret.emplace_back(op.mul(ograds[0], x_grad)); + ret.emplace_back(op.mul(ograds[0], grad_grad_x)); + return ret; + }); // degrees MXNET_OPERATOR_REGISTER_UNARY_WITH_RSP_CSR(degrees, cpu, mshadow_op::degrees) @@ -265,7 +294,8 @@ The storage type of ``radians`` output depends upon the input storage type: .set_attr("FGradient", ElemwiseGradUseIn{ "_backward_radians" }); MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU_DR(_backward_radians, - unary_bwd); + unary_bwd) +.set_attr("FGradient", MakeZeroGradNodes); // sinh MXNET_OPERATOR_REGISTER_UNARY_WITH_RSP_CSR(sinh, cpu, mshadow_op::sinh) @@ -391,8 +421,35 @@ The storage type of ``arctanh`` output depends upon the input storage type: .set_attr("FGradient", ElemwiseGradUseIn{ "_backward_arctanh" }); MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU_DR(_backward_arctanh, - unary_bwd); + unary_bwd) +.set_attr("FGradient", + [](const nnvm::NodePtr& n, const std::vector& ograds) { + // ograds[0]: head_grad_grads (dL/dxgrad) + // inputs[0]: dL/dy + // inputs[1]: x (ElemwiseGradUseIn) + // n: dL/dy * dy/dx + // f(x) = arctanh(x) + // dy/dx = f'(x) = 1/(1-x^2) + // f''(x) = f'(x) * f'(x) * 2 * x = (2 * x) / (1 - x^2)^2 + // return: + // 0: dL/dy_grad * dy/dx + // 1: dL/dy_grad * dL/dy * f''(x) + auto dldy = n->inputs[0]; + auto x = n->inputs[1]; + auto dldy_mul_dydx = nnvm::NodeEntry{n}; + auto op = mxnet::util::NodeOpGen{n}; + + auto x_grad = op.div(dldy_mul_dydx, dldy); + auto x_grad_square = op.square(x_grad); + auto x_grad_square_mul_x = op.mul(x_grad_square, x); + auto x_grad_square_mul_2_x = op.mul(2.0, x_grad_square_mul_x); + auto grad_grad_x = op.mul(dldy, x_grad_square_mul_2_x); + std::vector ret; + ret.emplace_back(op.mul(ograds[0], x_grad)); + ret.emplace_back(op.mul(ograds[0], grad_grad_x)); + return ret; + }); } // namespace op } // namespace mxnet diff --git a/tests/python/unittest/test_higher_order_grad.py b/tests/python/unittest/test_higher_order_grad.py index 429070de5896..c70c747411b8 100644 --- a/tests/python/unittest/test_higher_order_grad.py +++ b/tests/python/unittest/test_higher_order_grad.py @@ -17,6 +17,7 @@ import math +import random from mxnet import nd, autograd from mxnet.test_utils import assert_almost_equal, random_arrays, rand_shape_nd from common import with_seed @@ -85,6 +86,51 @@ def grad_grad_op(x): array, tanh, grad_grad_op, rtol=1e-6, atol=1e-6) +@with_seed() +def test_arctan(): + def arctan(x): + return nd.arctan(x) + + def grad_grad_op(x): + return (-2 * x)/((1 + x**2)**2) + + for dim in range(1, 5): + shape = rand_shape_nd(dim) + array = random_arrays(shape) + # Domain of arctan is all real numbers. + # Scale std_dev + array *= random.randint(500, 10000) + check_second_order_unary(array, arctan, grad_grad_op) + + +@with_seed() +def test_arctanh(): + def arctanh(x): + return nd.arctanh(x) + + def grad_grad_op(x): + return (2 * x)/((1 - x**2)**2) + + for dim in range(1, 5): + shape = rand_shape_nd(dim) + array = random_arrays(shape) + check_second_order_unary(array, arctanh, grad_grad_op) + + +@with_seed() +def test_radians(): + def radians(x): + return nd.radians(x) + + def grad_grad_op(x): + return nd.zeros_like(x) + + for dim in range(1, 5): + shape = rand_shape_nd(dim) + array = random_arrays(shape) + check_second_order_unary(array, radians, grad_grad_op) + + @with_seed() def test_relu(): def relu(x): From 259f6bbe8b3b742fd561b377356be5562571072d Mon Sep 17 00:00:00 2001 From: Pedro Larroy Date: Fri, 6 Sep 2019 12:56:34 -0700 Subject: [PATCH 293/813] Revert accidental change to CMakelists (#16040) --- CMakeLists.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b6faf639a70e..f441e9b0bd3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,6 @@ mxnet_option(USE_OLDCMAKECUDA "Build with old cmake cuda" OFF) mxnet_option(USE_NCCL "Use NVidia NCCL with CUDA" OFF) mxnet_option(USE_OPENCV "Build with OpenCV support" ON) mxnet_option(USE_OPENMP "Build with Openmp support" ON) -mxnet_option(USE_OPENMP_BUNDLED_LLVM "Build with bundled llvm openmp from 3rdparty" OFF) mxnet_option(USE_CUDNN "Build with cudnn support" ON) # one could set CUDNN_ROOT for search path mxnet_option(USE_SSE "Build with x86 SSE instruction support" ON IF NOT ARM) mxnet_option(USE_F16C "Build with x86 F16C instruction support" ON) # autodetects support if ON @@ -436,11 +435,11 @@ if(USE_OPENMP) find_package(OpenMP REQUIRED) # This should build on Windows, but there's some problem and I don't have a Windows box, so # could a Windows user please fix? - if(USE_OPENMP_BUNDLED_LLVM AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/openmp/CMakeLists.txt - AND SYSTEM_ARCHITECTURE STREQUAL "x86_64" - AND NOT MSVC - AND NOT CMAKE_CROSSCOMPILING) - message("Using bundlded LLVM OpenMP") + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/openmp/CMakeLists.txt + AND SYSTEM_ARCHITECTURE STREQUAL "x86_64" + AND NOT MSVC + AND NOT CMAKE_CROSSCOMPILING) + # Intel/llvm OpenMP: https://github.com/llvm-mirror/openmp set(OPENMP_STANDALONE_BUILD TRUE) set(LIBOMP_ENABLE_SHARED TRUE) @@ -454,7 +453,6 @@ if(USE_OPENMP) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") add_definitions(-DMXNET_USE_OPENMP=1) else() - message("Using platform provided OpenMP") if(OPENMP_FOUND) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") From 6de684825130c28dfa75f2a707aeaed64a4340e5 Mon Sep 17 00:00:00 2001 From: Anirudh Subramanian Date: Fri, 6 Sep 2019 13:14:25 -0700 Subject: [PATCH 294/813] Not to search for coverage files when none exist (#16107) --- tests/nightly/JenkinsfileForBinaries | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/nightly/JenkinsfileForBinaries b/tests/nightly/JenkinsfileForBinaries index 725c76969e23..8582643e0163 100755 --- a/tests/nightly/JenkinsfileForBinaries +++ b/tests/nightly/JenkinsfileForBinaries @@ -45,7 +45,7 @@ core_logic: { ws('workspace/build-cpu-int64') { utils.init_git() utils.docker_run('ubuntu_nightly_cpu', 'build_ubuntu_cpu_large_tensor', false) - utils.pack_lib('cpu_int64', mx_cmake_lib, true) + utils.pack_lib('cpu_int64', mx_cmake_lib) } } }, @@ -54,7 +54,7 @@ core_logic: { ws('workspace/build-gpu-int64') { utils.init_git() utils.docker_run('ubuntu_nightly_gpu', 'build_ubuntu_gpu_large_tensor', true) - utils.pack_lib('gpu_int64', mx_cmake_lib, true) + utils.pack_lib('gpu_int64', mx_cmake_lib) } } } From c928392a2d865dd3d452f0a3b9d7a320476c02fa Mon Sep 17 00:00:00 2001 From: kshitij12345 Date: Sat, 7 Sep 2019 04:40:44 +0530 Subject: [PATCH 295/813] [MXNET-978] Higher Order Gradient Support `sqrt`, `cbrt`. (#15474) * support sqrt, cbrt for higher order grad * add relevant tests * remove unnecessary variable --- src/operator/tensor/elemwise_unary_op_pow.cc | 71 ++++++++++++++++++- .../python/unittest/test_higher_order_grad.py | 40 +++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/src/operator/tensor/elemwise_unary_op_pow.cc b/src/operator/tensor/elemwise_unary_op_pow.cc index f22dabc7201a..486fe268b0cf 100644 --- a/src/operator/tensor/elemwise_unary_op_pow.cc +++ b/src/operator/tensor/elemwise_unary_op_pow.cc @@ -143,7 +143,38 @@ The storage type of ``sqrt`` output depends upon the input storage type: .set_attr("FGradient", ElemwiseGradUseOut{"_backward_sqrt"}); MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU_DR(_backward_sqrt, - unary_bwd); + unary_bwd) +.set_attr("FGradient", + [](const nnvm::NodePtr& n, const std::vector& ograds) { + // NodeEntry{n} : y_grad * f'(x) + // n->inputs[0] : y_grad + // n->inputs[1] : f(x) = x^1/2 + // ograds[0] : head_grads + // f'(x) = 1/(2*x^1/2) + // f''(x) = f'(x) * -1/(2*x) = -1/(4 * x^3/2) + const std::unordered_map mul_args = {{"scalar", "0.5"}}; + auto x = MakeNode("square", n->attrs.name + "_cube_x", {n->inputs[1]}, nullptr, &n); + auto r_x = MakeNode("reciprocal", n->attrs.name + "_reciprocal_x", + {nnvm::NodeEntry{x}}, nullptr, &n); + auto neg_r_x = MakeNode("negative", n->attrs.name + "_neg_reciprocal_x", + {nnvm::NodeEntry{r_x}}, nullptr, &n); + auto half_neg_r_cube_x = MakeNode("_mul_scalar", n->attrs.name + "_half_neg_reciprocal_x", + {nnvm::NodeEntry{neg_r_x}}, &mul_args, &n); + auto grad_grad_mid = MakeNode("elemwise_mul", n->attrs.name + "_grad_grad_mid", + {nnvm::NodeEntry{half_neg_r_cube_x}, n->inputs[0]}, + nullptr, &n); + auto dydx = MakeNode("elemwise_div", n->attrs.name + "_grad_div", + {nnvm::NodeEntry{n}, n->inputs[0]}, nullptr, &n); + + // when building gradient graph, the backward node of n->inputs[1] will be + // added to the graph again, therefore f`(x) will be multiplied + std::vector ret; + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "backward_grad_grad", + {ograds[0], nnvm::NodeEntry{dydx}}, nullptr, &n)); + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "backward_grad_grad_in", + {ograds[0], nnvm::NodeEntry{grad_grad_mid}}, nullptr, &n)); + return ret; + }); // rsqrt MXNET_OPERATOR_REGISTER_UNARY_WITH_SPARSE_DR(rsqrt, cpu, mshadow_op::reciprocal_square_root) @@ -186,7 +217,43 @@ The storage type of ``cbrt`` output depends upon the input storage type: .set_attr("FGradient", ElemwiseGradUseOut{"_backward_cbrt"}); MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU_DR(_backward_cbrt, - unary_bwd); + unary_bwd) +.set_attr("FGradient", + [](const nnvm::NodePtr& n, const std::vector& ograds) { + // NodeEntry{n} : y_grad * f'(x) + // n->inputs[0] : y_grad + // n->inputs[1] : f(x) = x^1/3 + // ograds[0] : head_grads + // f'(x) = 1/(3*x^2/3) + // f''(x) = f'(x) * -2/(3*x) = -2/(9 * x^5/3) + const std::unordered_map three = {{"scalar", "3.0"}}; + const std::unordered_map two = {{"scalar", "2.0"}}; + auto x = MakeNode("_power_scalar", n->attrs.name + "_x", {n->inputs[1]}, &three, &n); + auto three_x = MakeNode("_mul_scalar", n->attrs.name + "_three_x", + {nnvm::NodeEntry{x}}, &three, &n); + auto r_three_x = MakeNode("reciprocal", n->attrs.name + "_reciprocal_three_x", + {nnvm::NodeEntry{three_x}}, nullptr, &n); + auto neg_r_three_x = MakeNode("negative", n->attrs.name + "_neg_reciprocal_three_x", + {nnvm::NodeEntry{r_three_x}}, nullptr, &n); + auto two_third_neg_r_x = MakeNode("_mul_scalar", + n->attrs.name + "_two_third_neg_reciprocal_x", + {nnvm::NodeEntry{neg_r_three_x}}, &two, &n); + auto grad_grad_mid = MakeNode("elemwise_mul", n->attrs.name + "_grad_grad_mid", + {nnvm::NodeEntry{two_third_neg_r_x}, n->inputs[0]}, + nullptr, &n); + auto dydx = MakeNode("elemwise_div", n->attrs.name + "_grad_div", + {nnvm::NodeEntry{n}, n->inputs[0]}, nullptr, &n); + + // when building gradient graph, the backward node of n->inputs[1] will be + // added to the graph again, therefore f`(x) will be multiplied + std::vector ret; + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "backward_grad_grad", + {ograds[0], nnvm::NodeEntry{dydx}}, nullptr, &n)); + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "backward_grad_grad_in", + {ograds[0], nnvm::NodeEntry{grad_grad_mid}}, nullptr, &n)); + return ret; + }); + // rcbrt MXNET_OPERATOR_REGISTER_UNARY(rcbrt) diff --git a/tests/python/unittest/test_higher_order_grad.py b/tests/python/unittest/test_higher_order_grad.py index c70c747411b8..64c429a94c49 100644 --- a/tests/python/unittest/test_higher_order_grad.py +++ b/tests/python/unittest/test_higher_order_grad.py @@ -231,6 +231,46 @@ def grad_grad_op(x): check_second_order_unary(array, sigmoid, grad_grad_op) +@with_seed() +def test_sqrt(): + def sqrt(x): + return nd.sqrt(x) + + def grad_grad_op(x): + return -1/(4 * sqrt(x**3)) + + sigma = random.randint(25, 100) + mu = random.randint(500, 1000) + + for dim in range(1, 5): + shape = rand_shape_nd(dim) + array = random_arrays(shape) + array = sigma * array + mu + # Only positive numbers + assert((array > 0).all()) + check_second_order_unary(array, sqrt, grad_grad_op) + + +@with_seed() +def test_cbrt(): + def cbrt(x): + return nd.cbrt(x) + + def grad_grad_op(x): + return -2/(9 * cbrt(x**5)) + + sigma = random.randint(25, 100) + mu = random.randint(500, 1000) + + for dim in range(1, 5): + shape = rand_shape_nd(dim) + array = random_arrays(shape) + array = sigma * array + mu + # Only positive numbers + assert((array > 0).all()) + check_second_order_unary(array, cbrt, grad_grad_op) + + def check_second_order_unary(x, op, grad_grad_op, rtol=None, atol=None): x = nd.array(x) grad_grad_x = grad_grad_op(x) From 24f0a10e2c41dc43b99089f8312986e6e241c390 Mon Sep 17 00:00:00 2001 From: kshitij12345 Date: Sat, 7 Sep 2019 04:46:22 +0530 Subject: [PATCH 296/813] [MXNET-978] Higher Order Gradient Support `clip`, `dropout`. (#15746) * support clip, dropout for higher order grad * add relevant tests * retrigger CI --- src/operator/nn/dropout.cc | 4 ++- src/operator/tensor/matrix_op.cc | 3 +- .../python/unittest/test_higher_order_grad.py | 30 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/operator/nn/dropout.cc b/src/operator/nn/dropout.cc index c4ad4250853e..745bba142b6e 100644 --- a/src/operator/nn/dropout.cc +++ b/src/operator/nn/dropout.cc @@ -171,6 +171,7 @@ Example:: .add_arguments(DropoutParam::__FIELDS__()); NNVM_REGISTER_OP(_backward_Dropout) +.set_num_inputs(2) .set_num_outputs(1) .set_attr("TIsLayerOpBackward", true) .set_attr("TIsBackward", true) @@ -178,7 +179,8 @@ NNVM_REGISTER_OP(_backward_Dropout) .set_attr("FInplaceOption", [](const NodeAttrs& attrs){ return std::vector >{{0, 0}}; }) -.set_attr("FStatefulCompute", DropoutGradCompute); +.set_attr("FStatefulCompute", DropoutGradCompute) +.set_attr("FGradient", MakeZeroGradNodes); } // namespace op } // namespace mxnet diff --git a/src/operator/tensor/matrix_op.cc b/src/operator/tensor/matrix_op.cc index 9697744d03d3..c60402488b65 100644 --- a/src/operator/tensor/matrix_op.cc +++ b/src/operator/tensor/matrix_op.cc @@ -779,7 +779,8 @@ NNVM_REGISTER_OP(_backward_clip) .set_num_outputs(1) .set_attr_parser(ParamParser) .set_attr("TIsBackward", true) -.set_attr("FCompute", ClipGrad_); +.set_attr("FCompute", ClipGrad_) +.set_attr("FGradient", MakeZeroGradNodes); NNVM_REGISTER_OP(repeat) .add_alias("_np_repeat") diff --git a/tests/python/unittest/test_higher_order_grad.py b/tests/python/unittest/test_higher_order_grad.py index 64c429a94c49..a758775a09ba 100644 --- a/tests/python/unittest/test_higher_order_grad.py +++ b/tests/python/unittest/test_higher_order_grad.py @@ -215,6 +215,36 @@ def grad_grad_op(x): check_second_order_unary(array, abs, grad_grad_op) +@with_seed() +def test_clip(): + def clip(x): + a_min, a_max = sorted([random.random(), random.random()]) + + return nd.clip(x, a_min, a_max) + + def grad_grad_op(x): + return nd.zeros_like(x) + + for dim in range(1, 5): + shape = rand_shape_nd(dim) + array = random_arrays(shape) + check_second_order_unary(array, clip, grad_grad_op) + + +@with_seed() +def test_dropout(): + def dropout(x): + return nd.Dropout(x) + + def grad_grad_op(x): + return nd.zeros_like(x) + + for dim in range(1, 5): + shape = rand_shape_nd(dim) + array = random_arrays(shape) + check_second_order_unary(array, dropout, grad_grad_op) + + def test_sigmoid(): def sigmoid(x): return nd.sigmoid(x) From 4f8bc3a5c27c54e71f71d517998feddb2ae376e3 Mon Sep 17 00:00:00 2001 From: Lin Yuan Date: Fri, 6 Sep 2019 22:33:51 -0700 Subject: [PATCH 297/813] Fix unary operator ceil/floor/trunc when data type is integer (#14251) * fix integer precision loss due to casting to float * add test * update comment --- src/operator/math_functions-inl.h | 15 ++++++++++----- tests/python/unittest/test_ndarray.py | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/operator/math_functions-inl.h b/src/operator/math_functions-inl.h index be5bbe20d036..45d74a62d8bd 100644 --- a/src/operator/math_functions-inl.h +++ b/src/operator/math_functions-inl.h @@ -35,19 +35,24 @@ namespace op { namespace math { // Wrappers for math.h unary and binary functions -// - For DType != double: math::name(a) does computation in float +// - For DType == float: math::name(a) does computation in float // and returns float -// - For DType == double: math::name(a) does computation in double +// - For DType == double or DType == integer: math::name(a) does computation in double // and returns double #define MXNET_UNARY_MATH_FUNC(name) \ -template MSHADOW_XINLINE \ -float name(DType a) { \ - return ::name##f(static_cast(a)); \ +MSHADOW_XINLINE \ +float name(float a) { \ + return ::name##f(a); \ } \ MSHADOW_XINLINE \ double name(double a) { \ return ::name(a); \ +} \ +template MSHADOW_XINLINE \ +typename std::enable_if::value, double>::type \ +name(DType a) { \ + return ::name(static_cast(a)); \ } #define MXNET_BINARY_MATH_FUNC(name) \ diff --git a/tests/python/unittest/test_ndarray.py b/tests/python/unittest/test_ndarray.py index a05b3eae0e44..7091abf7308a 100644 --- a/tests/python/unittest/test_ndarray.py +++ b/tests/python/unittest/test_ndarray.py @@ -1952,6 +1952,25 @@ def test_op(op, num_inputs, mutated_inputs, **kwargs): {'rescale_grad': 0.1, 'lr': 0.01, 'wd': 1e-3}) +def test_large_int_rounding(): + large_integer = 50000001 + + a = mx.nd.array([large_integer], dtype='int32') + assert np.all(a == large_integer) + + a = mx.nd.array([large_integer], dtype='int32').floor() + assert np.all(a == large_integer) + + a = mx.nd.array([large_integer], dtype='int32').round() + assert np.all(a == large_integer) + + a = mx.nd.array([large_integer], dtype='int32').ceil() + assert np.all(a == large_integer) + + a = mx.nd.array([large_integer], dtype='int32').trunc() + assert np.all(a == large_integer) + + if __name__ == '__main__': import nose nose.runmodule() From 3ff1d8794c432142999199df77963ace161ff493 Mon Sep 17 00:00:00 2001 From: Per Goncalves da Silva Date: Sun, 1 Sep 2019 18:58:35 +0200 Subject: [PATCH 298/813] Fixes openblas installation for static build --- tools/dependencies/openblas.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tools/dependencies/openblas.sh b/tools/dependencies/openblas.sh index 1201c8947568..05a500da3147 100755 --- a/tools/dependencies/openblas.sh +++ b/tools/dependencies/openblas.sh @@ -18,9 +18,8 @@ # under the License. # This script builds the static library of openblas that can be used as dependency of mxnet. -set +e # This script throws an error but otherwise works -set -x -OPENBLAS_VERSION=0.3.5 +set -ex +OPENBLAS_VERSION=0.3.7 if [[ ! -e $DEPS_PATH/lib/libopenblas.a ]]; then # download and build openblas >&2 echo "Building openblas..." @@ -32,8 +31,13 @@ if [[ ! -e $DEPS_PATH/lib/libopenblas.a ]]; then pushd . cd $DEPS_PATH/OpenBLAS-$OPENBLAS_VERSION - $MAKE DYNAMIC_ARCH=1 NO_SHARED=1 USE_OPENMP=1 + # Adding NO_DYNAMIC=1 flag causes make install to fail + $MAKE DYNAMIC_ARCH=1 USE_OPENMP=1 $MAKE PREFIX=$DEPS_PATH install + + # Manually removing .so to avoid linking against it + rm $DEPS_PATH/lib/libopenblasp-r${OPENBLAS_VERSION}.so + popd ln -s libopenblas.a $DEPS_PATH/lib/libcblas.a ln -s libopenblas.a $DEPS_PATH/lib/liblapack.a From 27a9782e7afee7ed1be3ee3b5fe3061b0c299d98 Mon Sep 17 00:00:00 2001 From: Haibin Lin Date: Sat, 7 Sep 2019 20:53:22 -0700 Subject: [PATCH 299/813] [Dataset] Add take, filter, sample API to dataset (#16078) * first commit * fix lint * add more test for transformed data * address CR * Update dataset.py * use sampler to implement * Update dataset.py --- python/mxnet/gluon/data/dataset.py | 89 ++++++++++++++++++++++++ python/mxnet/gluon/data/sampler.py | 23 +++++- tests/python/unittest/test_gluon_data.py | 39 +++++++++++ 3 files changed, 150 insertions(+), 1 deletion(-) diff --git a/python/mxnet/gluon/data/dataset.py b/python/mxnet/gluon/data/dataset.py index 28d19c9fe37c..a5399f259435 100644 --- a/python/mxnet/gluon/data/dataset.py +++ b/python/mxnet/gluon/data/dataset.py @@ -40,6 +40,70 @@ def __getitem__(self, idx): def __len__(self): raise NotImplementedError + def filter(self, fn): + """Returns a new dataset with samples filtered by the + filter function `fn`. + + Note that if the Dataset is the result of a lazily transformed one with + transform(lazy=False), the filter is eagerly applied to the transformed + samples without materializing the transformed result. That is, the + transformation will be applied again whenever a sample is retrieved after + filter(). + + Parameters + ---------- + fn : callable + A filter function that takes a sample as input and + returns a boolean. Samples that return False are discarded. + + Returns + ------- + Dataset + The filtered dataset. + """ + from . import FilterSampler + return _SampledDataset(self, FilterSampler(fn, self)) + + def take(self, count): + """Returns a new dataset with at most `count` number of samples in it. + + Parameters + ---------- + count : int or None + A integer representing the number of elements of this dataset that + should be taken to form the new dataset. If count is None, or if count + is greater than the size of this dataset, the new dataset will contain + all elements of this dataset. + + Returns + ------- + Dataset + The result dataset. + """ + if count is None or count > len(self): + count = len(self) + from . import SequentialSampler + return _SampledDataset(self, SequentialSampler(count)) + + def sample(self, sampler): + """Returns a new dataset with elements sampled by the sampler. + + Parameters + ---------- + sampler : Sampler + A Sampler that returns the indices of sampled elements. + + Returns + ------- + Dataset + The result dataset. + """ + from . import Sampler + if not isinstance(sampler, Sampler): + raise TypeError('Invalid sampler type: %s. Expected gluon.data.Sampler instead.'% + type(sampler)) + return _SampledDataset(self, sampler) + def transform(self, fn, lazy=True): """Returns a new dataset with each sample transformed by the transformer function `fn`. @@ -135,6 +199,31 @@ def __call__(self, x, *args): return (self._fn(x),) + args return self._fn(x) +class _FilteredDataset(Dataset): + """Dataset with a filter applied""" + def __init__(self, dataset, fn): + self._dataset = dataset + self._indices = [i for i, sample in enumerate(dataset) if fn(sample)] + + def __len__(self): + return len(self._indices) + + def __getitem__(self, idx): + return self._dataset[self._indices[idx]] + +class _SampledDataset(Dataset): + """Dataset with elements chosen by a sampler""" + def __init__(self, dataset, sampler): + self._dataset = dataset + self._sampler = sampler + self._indices = list(iter(sampler)) + + def __len__(self): + return len(self._sampler) + + def __getitem__(self, idx): + return self._dataset[self._indices[idx]] + class ArrayDataset(Dataset): """A dataset that combines multiple dataset-like objects, e.g. Datasets, lists, arrays, etc. diff --git a/python/mxnet/gluon/data/sampler.py b/python/mxnet/gluon/data/sampler.py index 2f827c83bc4d..980c63134fac 100644 --- a/python/mxnet/gluon/data/sampler.py +++ b/python/mxnet/gluon/data/sampler.py @@ -18,7 +18,7 @@ # coding: utf-8 # pylint: disable= """Dataset sampler.""" -__all__ = ['Sampler', 'SequentialSampler', 'RandomSampler', 'BatchSampler'] +__all__ = ['Sampler', 'SequentialSampler', 'RandomSampler', 'FilterSampler', 'BatchSampler'] import numpy as np @@ -72,6 +72,27 @@ def __iter__(self): def __len__(self): return self._length +class FilterSampler(Sampler): + """Samples elements from a Dataset for which `fn` returns True. + + Parameters + ---------- + fn : callable + A callable function that takes a sample and returns a boolean + dataset : Dataset + The dataset to filter. + """ + def __init__(self, fn, dataset): + self._fn = fn + self._dataset = dataset + self._indices = [i for i, sample in enumerate(dataset) if fn(sample)] + + def __iter__(self): + return iter(self._indices) + + def __len__(self): + return len(self._indices) + class BatchSampler(Sampler): """Wraps over another `Sampler` and return mini-batches of samples. diff --git a/tests/python/unittest/test_gluon_data.py b/tests/python/unittest/test_gluon_data.py index 58e241b528ea..62bf7c3db869 100644 --- a/tests/python/unittest/test_gluon_data.py +++ b/tests/python/unittest/test_gluon_data.py @@ -283,6 +283,45 @@ def test_dataloader_context(): def batchify(a): return a +def test_dataset_filter(): + length = 100 + a = mx.gluon.data.SimpleDataset([i for i in range(length)]) + a_filtered = a.filter(lambda x: x % 10 == 0) + assert(len(a_filtered) == 10) + for idx, sample in enumerate(a_filtered): + assert sample % 10 == 0 + a_xform_filtered = a.transform(lambda x: x + 1).filter(lambda x: x % 10 == 0) + assert(len(a_xform_filtered) == 10) + # the filtered data is already transformed + for idx, sample in enumerate(a_xform_filtered): + assert sample % 10 == 0 + +def test_dataset_take(): + length = 100 + a = mx.gluon.data.SimpleDataset([i for i in range(length)]) + a_take_full = a.take(1000) + assert len(a_take_full) == length + a_take_full = a.take(None) + assert len(a_take_full) == length + count = 10 + a_take_10 = a.take(count) + assert len(a_take_10) == count + expected_total = sum([i for i in range(count)]) + total = 0 + for idx, sample in enumerate(a_take_10): + assert sample < count + total += sample + assert total == expected_total + + a_xform_take_10 = a.transform(lambda x: x * 10).take(count) + assert len(a_xform_take_10) == count + expected_total = sum([i * 10 for i in range(count)]) + total = 0 + for idx, sample in enumerate(a_xform_take_10): + assert sample < count * 10 + total += sample + assert total == expected_total + def test_dataloader_scope(): """ Bug: Gluon DataLoader terminates the process pool early while From 75ee1e9f434383eff9b250f32b10c89e721f66d3 Mon Sep 17 00:00:00 2001 From: Iblis Lin Date: Sun, 8 Sep 2019 12:30:57 +0800 Subject: [PATCH 300/813] julia: rename build env var `MXNET_HOME` to `MXNET_ROOT` (#15568) * julia: rename build env var `MXNET_HOME` to `MXNET_ROOT` - Add MXNET_LIBRARY_PATH support Ref: https://github.com/apache/incubator-mxnet/pull/15561 * backward compatibility --- ci/docker/runtime_functions.sh | 8 +++---- ci/windows/test_jl07_cpu.ps1 | 2 +- ci/windows/test_jl10_cpu.ps1 | 2 +- julia/NEWS.md | 7 ++++++ julia/deps/build.jl | 33 +++++++++++++++++++++++----- julia/docs/src/user-guide/install.md | 20 ++++++++++------- julia/src/base.jl | 30 ++++++++++++++++++++----- 7 files changed, 77 insertions(+), 25 deletions(-) diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index 689d525a5cce..ac962527d7f7 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -1088,7 +1088,7 @@ unittest_ubuntu_gpu_R() { unittest_ubuntu_cpu_julia() { set -ex export PATH="$1/bin:$PATH" - export MXNET_HOME='/work/mxnet' + export MXNET_ROOT='/work/mxnet' export JULIA_DEPOT_PATH='/work/julia-depot' export INTEGRATION_TEST=1 @@ -1098,7 +1098,7 @@ unittest_ubuntu_cpu_julia() { export LD_PRELOAD='/usr/lib/x86_64-linux-gnu/libjemalloc.so' export LD_LIBRARY_PATH=/work/mxnet/lib:$LD_LIBRARY_PATH - # use the prebuilt binary from $MXNET_HOME/lib + # use the prebuilt binary from $MXNET_ROOT/lib julia --project=./julia -e 'using Pkg; Pkg.build("MXNet")' # run the script `julia/test/runtests.jl` @@ -1253,7 +1253,7 @@ build_docs() { # Setup environment for Julia docs export PATH="/work/julia10/bin:$PATH" - export MXNET_HOME='/work/mxnet' + export MXNET_ROOT='/work/mxnet' export JULIA_DEPOT_PATH='/work/julia-depot' julia -e 'using InteractiveUtils; versioninfo()' @@ -1465,7 +1465,7 @@ deploy_docs() { # Setup for Julia docs export PATH="/work/julia10/bin:$PATH" - export MXNET_HOME='/work/mxnet' + export MXNET_ROOT='/work/mxnet' export JULIA_DEPOT_PATH='/work/julia-depot' julia -e 'using InteractiveUtils; versioninfo()' diff --git a/ci/windows/test_jl07_cpu.ps1 b/ci/windows/test_jl07_cpu.ps1 index 6cd34ef209de..d3b77b1cbc1d 100644 --- a/ci/windows/test_jl07_cpu.ps1 +++ b/ci/windows/test_jl07_cpu.ps1 @@ -20,7 +20,7 @@ # set default output encoding to utf8 $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8' -$env:MXNET_HOME = [System.IO.Path]::GetFullPath('.\windows_package') +$env:MXNET_ROOT = [System.IO.Path]::GetFullPath('.\windows_package') $env:JULIA_URL = "https://julialang-s3.julialang.org/bin/winnt/x64/0.7/julia-0.7.0-win64.exe" $env:JULIA_DEPOT_PATH = [System.IO.Path]::GetFullPath('.\julia-depot') diff --git a/ci/windows/test_jl10_cpu.ps1 b/ci/windows/test_jl10_cpu.ps1 index 96c419066354..2dbda4886c42 100644 --- a/ci/windows/test_jl10_cpu.ps1 +++ b/ci/windows/test_jl10_cpu.ps1 @@ -20,7 +20,7 @@ # set default output encoding to utf8 $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8' -$env:MXNET_HOME = [System.IO.Path]::GetFullPath('.\windows_package') +$env:MXNET_ROOT = [System.IO.Path]::GetFullPath('.\windows_package') $env:JULIA_URL = "https://julialang-s3.julialang.org/bin/winnt/x64/1.0/julia-1.0.3-win64.exe" $env:JULIA_DEPOT_PATH = [System.IO.Path]::GetFullPath('.\julia-depot') diff --git a/julia/NEWS.md b/julia/NEWS.md index e678b97176d1..9935d6885ca5 100644 --- a/julia/NEWS.md +++ b/julia/NEWS.md @@ -17,6 +17,13 @@ # v1.6.0 +* Rename environment variable `MXNET_HOME` to `MXNET_ROOT` (#15568). + +* Environment variable `MXNET_LIBRARY_PATH` supports (#15568). + + ```shell + $ MXNET_LIBRARY_PATH=/path/to/libmxnet.so julia + ``` # v1.5.0 diff --git a/julia/deps/build.jl b/julia/deps/build.jl index a87343d9dab5..dfd3f78379b0 100644 --- a/julia/deps/build.jl +++ b/julia/deps/build.jl @@ -26,14 +26,35 @@ libmxnet_detected = false libmxnet_curr_ver = get(ENV, "MXNET_COMMIT", "master") curr_win = "20190608" # v1.5.0 +# TODO: remove MXNET_HOME backward compatibility in v1.7 if haskey(ENV, "MXNET_HOME") - MXNET_HOME = ENV["MXNET_HOME"] - @info("MXNET_HOME environment detected: $MXNET_HOME") + @warn "The environment variable `MXNET_HOME` has been renamed, please use `MXNET_ROOT` instead." +end + +# TODO: remove MXNET_HOME backward compatibility in v2.0 +MXNET_ROOT = get(ENV, "MXNET_ROOT", get(ENV, "MXNET_HOME", "")) +search_locations = if !isempty(MXNET_ROOT) + !isabspath(MXNET_ROOT) && error("MXNET_ROOT should be a absolute path") + @info "env var: MXNET_ROOT -> $MXNET_ROOT" + [joinpath(MXNET_ROOT, "lib"), MXNET_ROOT] +else + [] +end + +MXNET_LIBRARY_PATH = get(ENV, "MXNET_LIBRARY_PATH", "") +println(typeof(MXNET_LIBRARY_PATH)) +# In case of macOS, if user build libmxnet from source and set the MXNET_ROOT, +# the output is still named as `libmxnet.so`. +search_names = ["libmxnet.$(Libdl.dlext)", "libmxnet.so"] +if !isempty(MXNET_LIBRARY_PATH) + !isabspath(MXNET_LIBRARY_PATH) && error("MXNET_LIBRARY_PATH should be a absolute path") + @info "env var: MXNET_LIBRARY_PATH -> $MXNET_LIBRARY_PATH" + pushfirst!(search_names, MXNET_LIBRARY_PATH) +end + +if (!isempty(MXNET_ROOT)) || (!isempty(MXNET_LIBRARY_PATH)) @info("Trying to load existing libmxnet...") - # In case of macOS, if user build libmxnet from source and set the MXNET_HOME, - # the output is still named as `libmxnet.so`. - lib = Libdl.find_library(["libmxnet.$(Libdl.dlext)", "libmxnet.so"], - [joinpath(MXNET_HOME, "lib"), MXNET_HOME]) + lib = Libdl.find_library(search_names, search_locations) if !isempty(lib) @info("Existing libmxnet detected at $lib, skip building...") libmxnet_detected = true diff --git a/julia/docs/src/user-guide/install.md b/julia/docs/src/user-guide/install.md index d1a90a808507..d9d5574b025d 100644 --- a/julia/docs/src/user-guide/install.md +++ b/julia/docs/src/user-guide/install.md @@ -40,9 +40,13 @@ libmxnet. There are several environment variables that change this behaviour. -- `MXNET_HOME`: If you already have a pre-installed version of mxnet - you can use `MXNET_HOME` to point the build-process in the right direction. -- `CUDA_HOME`: If the automatic cuda detection fails you can also set `CUDA_HOME` +- `MXNET_ROOT`: If you already have a pre-installed version of mxnet + you can use `MXNET_ROOT` to point the build-process in the right direction. + Note that you should set this variable as a absolute path. +- `MXNET_HOME`: This variable is replaced by `MXNET_ROOT`. It has been renamed + as of v1.6. +- `MXNET_LIBRARY_PATH`: The absolute path of the libmxnet share object. +- `CUDA_HOME`: If the automatic CUDA detection fails you can also set `CUDA_HOME` to override the process. - `MXNET_COMMIT`: To control which version of libmxnet will be compiled, you can use the`MXNET_COMMIT` variable to point to either a version tag @@ -80,18 +84,18 @@ to work with a separate, maybe customized libmxnet. To build libmxnet, please refer to [the installation guide of libmxnet](https://mxnet.incubator.apache.org/install/index.html). After -successfully installing libmxnet, set the `MXNET_HOME` *environment +successfully installing libmxnet, set the `MXNET_ROOT` *environment variable* to the location of libmxnet. In other words, the compiled -`libmxnet.so` should be found in `$MXNET_HOME/lib`. +`libmxnet.so` should be found in `$MXNET_ROOT/lib`. > **note** > -> The constant `MXNET_HOME` is pre-compiled in MXNet.jl package cache. +> The constant `MXNET_ROOT` is pre-compiled in MXNet.jl package cache. > If you updated the environment variable after installing MXNet.jl, > make sure to update the pre-compilation cache by > `Base.compilecache("MXNet")`. -When the `MXNET_HOME` environment variable is detected and the +When the `MXNET_ROOT` environment variable is detected and the corresponding `libmxnet.so` could be loaded successfully, MXNet.jl will skip automatic building during installation and use the specified libmxnet instead. @@ -99,7 +103,7 @@ libmxnet instead. Basically, MXNet.jl will search `libmxnet.so` or `libmxnet.dll` in the following paths (and in that order): -- `$MXNET_HOME/lib`: customized libmxnet builds +- `$MXNET_ROOT/lib`: customized libmxnet builds - `Pkg.dir("MXNet", "deps", "usr", "lib")`: automatic builds - Any system wide library search path diff --git a/julia/src/base.jl b/julia/src/base.jl index 683146402620..7a2bde0907c1 100644 --- a/julia/src/base.jl +++ b/julia/src/base.jl @@ -47,11 +47,31 @@ const grad_req_map = Dict{Symbol,GRAD_REQ}( ################################################################################ # Initialization and library API entrance ################################################################################ -const MXNET_LIB = Libdl.find_library(["libmxnet.$(Libdl.dlext)", "libmxnet.so"], # see build.jl - [joinpath(get(ENV, "MXNET_HOME", ""), "lib"), - get(ENV, "MXNET_HOME", ""), - joinpath(@__DIR__, "..", - "deps", "usr", "lib")]) +function _get_search_names() + MXNET_LIBRARY_PATH = get(ENV, "MXNET_LIBRARY_PATH", "") + A = ["libmxnet.$(Libdl.dlext)", "libmxnet.so"] # see build.jl + if !isempty(MXNET_LIBRARY_PATH) + !isabspath(MXNET_LIBRARY_PATH) && error("MXNET_LIBRARY_PATH should be a absolute path") + pushfirst!(A, MXNET_LIBRARY_PATH) + end + A +end + +function _get_search_dirs() + # TODO: remove MXNET_HOME backward compatibility in v1.7 + if haskey(ENV, "MXNET_HOME") + @warn "The environment variable `MXNET_HOME` has been renamed, please use `MXNET_ROOT` instead." + end + A = [joinpath(@__DIR__, "..", "deps", "usr", "lib")] + MXNET_ROOT = get(ENV, "MXNET_ROOT", get(ENV, "MXNET_HOME", "")) + if !isempty(MXNET_ROOT) + !isabspath(MXNET_ROOT) && error("MXNET_ROOT should be a absolute path") + prepend!(A, [joinpath(MXNET_ROOT, "lib"), MXNET_ROOT]) + end + A +end + +const MXNET_LIB = Libdl.find_library(_get_search_names(), _get_search_dirs()) const LIB_VERSION = Ref{Cint}(0) if isempty(MXNET_LIB) From 5806200e949beb8fd48c2fed13cf5bbd4baef351 Mon Sep 17 00:00:00 2001 From: Yuan Tang Date: Sun, 8 Sep 2019 05:44:15 -0400 Subject: [PATCH 301/813] [DOC] Consistent capitalization: mxnet -> MXNet, scala -> Scala (#16041) --- CONTRIBUTORS.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 86c20cbc3d87..8eb540d8b759 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -26,37 +26,37 @@ Committers are people who have made substantial contribution to the project and The committers are the granted write access to the project. * [Bing Xu](https://github.com/antinucleon) - - Bing is the initiator and major contributor of operators and ndarray modules of mxnet. + - Bing is the initiator and major contributor of operators and ndarray modules of MXNet. * [Tianjun Xiao](https://github.com/sneakerkg) - Tianqjun is the master behind the fast data loading and preprocessing. * [Yutian Li](https://github.com/hotpxl) - - Yutian is the ninja behind the dependency and storage engine of mxnet. + - Yutian is the ninja behind the dependency and storage engine of MXNet. * [Mu Li](https://github.com/mli) - - Mu is the contributor of the distributed key-value store in mxnet. + - Mu is the contributor of the distributed key-value store in MXNet. * [Tianqi Chen](https://github.com/tqchen) - - Tianqi is one of the initiator of the mxnet project. + - Tianqi is one of the initiator of the MXNet project. * [Min Lin](https://github.com/mavenlin) - - Min is the guy behind the symbolic magics of mxnet. + - Min is the guy behind the symbolic magics of MXNet. * [Naiyan Wang](https://github.com/winstywang) - - Naiyan is the creator of static symbolic graph module of mxnet. + - Naiyan is the creator of static symbolic graph module of MXNet. * [Mingjie Wang](https://github.com/jermainewang) - Mingjie is the initiator, and contributes the design of the dependency engine. * [Chuntao Hong](https://github.com/hjk41) - Chuntao is the initiator and provides the initial design of engine. * [Chiyuan Zhang](https://github.com/pluskid) - - Chiyuan is the creator of MXNet Julia Package. + - Chiyuan is the creator of MXNet Julia package. * [Junyuan Xie](https://github.com/piiswrong) * [Haibin Lin](https://github.com/eric-haibin-lin) * [Qiang Kou](https://github.com/thirdwing) - KK is a R ninja, he makes mxnet available for R users. * [Tong He](https://github.com/hetong007) - - Tong is the major maintainer of MXNetR, he designs the mxnet interface and wrote many of the tutorials on R. + - Tong is the major maintainer of MXNet R package, he designs the MXNet interface and wrote many of the tutorials on R. * [Yizhi Liu](https://github.com/yzhliu) - Yizhi is the main creator on mxnet scala project to make deep learning available for JVM stacks. * [Zixuan Huang](https://github.com/yanqingmen) - - Zixuan is one of major maintainers of mxnet scala package. + - Zixuan is one of major maintainers of MXNet Scala package. * [Yuan Tang](https://github.com/terrytangyuan) - - Yuan is one of major maintainers of mxnet scala package. + - Yuan is one of major maintainers of MXNet Scala package. * [Chris Olivier](https://github.com/cjolivier01) * [Sergey Kolychev](https://github.com/sergeykolychev) - Sergey is original author and current maintainer of Perl5 interface. @@ -89,9 +89,9 @@ List of Contributors * [Full List of Contributors](https://github.com/apache/incubator-mxnet/graphs/contributors) - To contributors: please add your name to the list when you submit a patch to the project:) * [Feng Wang](https://github.com/happynear) - - Feng makes mxnet compatible with Windows Visual Studio. + - Feng makes MXNet compatible with Windows Visual Studio. * [Jack Deng](https://github.com/jdeng) - - Jack created the amalgamation script and Go bind for mxnet. + - Jack created the amalgamation script and Go bind for MXNet. * [Li Dong](https://github.com/donglixp) * [Piji Li](https://github.com/lipiji) * [Hu Shiwen](https://github.com/yajiedesign) @@ -103,7 +103,7 @@ List of Contributors * [Nan Xiao](https://github.com/road2stat) * [Wei Wu](https://github.com/tornadomeet) * [Michaël Benesty](https://github.com/pommedeterresautee) - -Michaël contributes the R visualization module of mxnet + -Michaël contributes the R visualization module of MXNet * [Kublai Jing](https://github.com/Kublai-Jing) * [chenjx1005](https://github.com/chenjx1005) * [ry](https://github.com/ry) From 1c67928ad387c09731e5d062827439414b6d4851 Mon Sep 17 00:00:00 2001 From: Anand J Date: Sun, 8 Sep 2019 18:32:49 -0400 Subject: [PATCH 302/813] [MXNET-1294] Add KVSTORE PushPull API (#15559) * Switch to latest ps-lite * Add PushPull API * Add PushPull test cases --- 3rdparty/ps-lite | 2 +- include/mxnet/c_api.h | 42 ++++++++++++ include/mxnet/kvstore.h | 27 ++++++++ python/mxnet/kvstore.py | 81 +++++++++++++++++++++++ src/c_api/c_api.cc | 52 +++++++++++++++ src/kvstore/kvstore_dist.h | 75 +++++++++++++++++++++ src/kvstore/kvstore_dist_server.h | 22 ++++-- src/kvstore/kvstore_local.h | 31 +++++++++ tests/nightly/dist_device_sync_kvstore.py | 30 ++++++--- 9 files changed, 345 insertions(+), 17 deletions(-) diff --git a/3rdparty/ps-lite b/3rdparty/ps-lite index 2c8ed25384fc..60b826e4422f 160000 --- a/3rdparty/ps-lite +++ b/3rdparty/ps-lite @@ -1 +1 @@ -Subproject commit 2c8ed25384fc0ea00a70f081f8308170146d8f25 +Subproject commit 60b826e4422fee0df00b892c66ffffea11e5da3f diff --git a/include/mxnet/c_api.h b/include/mxnet/c_api.h index e5719cb36447..2fa2af5ebcf2 100644 --- a/include/mxnet/c_api.h +++ b/include/mxnet/c_api.h @@ -2649,6 +2649,48 @@ MXNET_DLL int MXKVStorePullRowSparseEx(KVStoreHandle handle, const NDArrayHandle* row_ids, int priority); +/*! + * \brief push and pull a list of (key, value) pairs from the kvstore + * \param handle handle to the kvstore + * \param vnum the number of key-value pairs corresponding to vkeys + * \param vkeys the list of keys for the values to be pushed + * \param onum the number of key-value pairs corresponding to okeys + * \param okeys the list of keys for the values to be pulled + * \param vals the list of values + * \param outs the list of outputs + * \param priority the priority of the action + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXKVStorePushPull(KVStoreHandle handle, + mx_uint vnum, + const int* vkeys, + mx_uint onum, + const int* okeys, + NDArrayHandle* vals, + NDArrayHandle* outs, + int priority); +/*! + * \brief push and pull a list of (key, value) pairs from the kvstore, + * where each key is a string + * \param handle handle to the kvstore + * \param vnum the number of key-value pairs corresponding to vkeys + * \param vkeys the list of keys for the values to be pushed + * \param onum the number of key-value pairs corresponding to okeys + * \param okeys the list of keys for the values to be pulled + * \param vals the list of values + * \param outs the list of outputs + * \param priority the priority of the action + * \return 0 when success, -1 when failure happens + */ +MXNET_DLL int MXKVStorePushPullEx(KVStoreHandle handle, + mx_uint vnum, + const char** vkeys, + mx_uint onum, + const char** okeys, + NDArrayHandle* vals, + NDArrayHandle* outs, + int priority); + /*! * \brief user-defined updater for the kvstore * It's this updater's responsibility to delete \a recv and \a local diff --git a/include/mxnet/kvstore.h b/include/mxnet/kvstore.h index a73d96356132..c5e8a8219989 100644 --- a/include/mxnet/kvstore.h +++ b/include/mxnet/kvstore.h @@ -198,6 +198,33 @@ class KVStore { const std::vector& values, int priority = 0, bool ignore_sparse = true) = 0; + /*! + * \brief push and pull a list of key-value pairs from the store + * \param vkeys the list of keys to be pushed + * \param okeys the list of keys to be pulled. Should be the same set of keys in vkeys. + * \param values the list of values to be pushed + * \param outs the list of buffers for the pulled data, they should be preallocated + * \param priority Priority of the action. + */ + virtual void PushPull(const std::vector& vkeys, + const std::vector& okeys, + const std::vector& values, + const std::vector& outs, + int priority = 0) = 0; + + /*! + * \brief push and pull a list of key-value pairs from the store + * \param vkeys the list of keys to be pushed in string format + * \param okeys the list of keys to be pulled in string format. Should be the same set of keys in vkeys. + * \param values the list of values to be pushed + * \param outs the list of buffers for the pulled data, they should be preallocated + * \param priority Priority of the action. + */ + virtual void PushPull(const std::vector& str_vkeys, + const std::vector& str_okeys, + const std::vector& values, + const std::vector& outs, + int priority = 0) = 0; /*! * \brief pull a list of key-value pairs from the store. * The NDArray pulled back will be in row_sparse storage with only the diff --git a/python/mxnet/kvstore.py b/python/mxnet/kvstore.py index a54817501391..5d332ff45ecb 100644 --- a/python/mxnet/kvstore.py +++ b/python/mxnet/kvstore.py @@ -311,6 +311,87 @@ def pull(self, key, out=None, priority=0, ignore_sparse=True): cvals, ctypes.c_int(priority), ctypes.c_bool(ignore_sparse))) + def pushpull(self, key, value, out=None, priority=0): + """ Performs push and pull a single value or a sequence of values from the store. + + This function is coalesced form of push and pull operations. This function returns + immediately after adding an operator to the engine. Subsequent attempts to read + from the `out` variable will be blocked until the pull operation completes. + + `value` is pushed to the kvstore server for the specified keys and the updated + values are pulled from the server to `out`. If `out` is not specified the pulled + values are written to `value`. The returned values are guaranteed to be the latest + values in the store. + + pushpull with `RowSparseNDArray` is not supported for dist kvstore. + + Parameters + ---------- + key : str, int, or sequence of str or int + Keys. + + value : NDArray, RowSparseNDArray, list of NDArray or RowSparseNDArray, + or list of list of NDArray or RowSparseNDArray + Values corresponding to the keys. + + out: NDArray or list of NDArray or list of list of NDArray + Values corresponding to the keys. + + priority : int, optional + The priority of the pull operation. + Higher priority pull operations are likely to be executed before + other pull actions. + + Examples + -------- + >>> # push a single key-value pair + >>> kv.pushpull('3', mx.nd.ones(shape)*8, out=a) + >>> print a.asnumpy() + [[ 8. 8. 8.] + [ 8. 8. 8.]] + + >>> # aggregate the value and the push + >>> gpus = [mx.gpu(i) for i in range(4)] + >>> b = [mx.nd.ones(shape, gpu) for gpu in gpus] + >>> kv.pushpull('3', b, out=a) + >>> print a.asnumpy() + [[ 4. 4. 4.] + [ 4. 4. 4.]] + + >>> # push a list of keys. + >>> # single device + >>> keys = ['4', '5', '6'] + >>> b = [mx.nd.zeros(shape)]*len(keys) + >>> kv.push(keys, [mx.nd.ones(shape)]*len(keys), out=b) + >>> print b[1].asnumpy() + [[ 1. 1. 1.] + [ 1. 1. 1.]] + + >>> # multiple devices: + >>> keys = ['7', '8', '9'] + >>> b = [[mx.nd.ones(shape, gpu) for gpu in gpus]] * len(keys) + >>> kv.pushpull(keys, b) + >>> print b[1][1].asnumpy() + [[ 4. 4. 4.] + [ 4. 4. 4.]] + """ + + cvkeys, cvals, use_str_keys = _ctype_key_value(key, value) + if out is not None: + cokeys, couts, _ = _ctype_key_value(key, out) + else: + cokeys = cvkeys + couts = cvals + + if use_str_keys: + check_call(_LIB.MXKVStorePushPullEx( + self.handle, mx_uint(len(cvkeys)), cvkeys, mx_uint(len(cokeys)), cokeys, + cvals, couts, ctypes.c_int(priority))) + else: + check_call(_LIB.MXKVStorePushPull( + self.handle, mx_uint(len(cvkeys)), cvkeys, mx_uint(len(cokeys)), cokeys, + cvals, couts, ctypes.c_int(priority))) + def row_sparse_pull(self, key, out=None, priority=0, row_ids=None): """ Pulls a single RowSparseNDArray value or a sequence of RowSparseNDArray values \ from the store with specified row_ids. When there is only one row_id, KVStoreRowSparsePull \ diff --git a/src/c_api/c_api.cc b/src/c_api/c_api.cc index f3e0ba8f5c26..70ce869b0ef7 100644 --- a/src/c_api/c_api.cc +++ b/src/c_api/c_api.cc @@ -1064,6 +1064,58 @@ int MXKVStorePullEx(KVStoreHandle handle, API_END(); } +int MXKVStorePushPull(KVStoreHandle handle, + mx_uint vnum, + const int* vkeys, + mx_uint onum, + const int* okeys, + NDArrayHandle* vals, + NDArrayHandle* outs, + int priority) { + API_BEGIN(); + std::vector v_vkeys(vnum); + std::vector v_okeys(onum); + std::vector v_vals(vnum); + std::vector v_outs(onum); + for (mx_uint i = 0; i < vnum; ++i) { + v_vkeys[i] = vkeys[i]; + v_vals[i] = *static_cast(vals[i]); + } + for (mx_uint i = 0; i < onum; ++i) { + v_okeys[i] = okeys[i]; + v_outs[i] = static_cast(outs[i]); + } + static_cast(handle)->PushPull(v_vkeys, v_okeys, v_vals, v_outs, + priority); + API_END(); +} + +int MXKVStorePushPullEx(KVStoreHandle handle, + mx_uint vnum, + const char** vkeys, + mx_uint onum, + const char** okeys, + NDArrayHandle* vals, + NDArrayHandle* outs, + int priority) { + API_BEGIN(); + std::vector v_vkeys(vnum); + std::vector v_okeys(onum); + std::vector v_vals(vnum); + std::vector v_outs(onum); + for (mx_uint i = 0; i < vnum; ++i) { + v_vkeys[i] = vkeys[i]; + v_vals[i] = *static_cast(vals[i]); + } + for (mx_uint i = 0; i < onum; ++i) { + v_okeys[i] = okeys[i]; + v_outs[i] = static_cast(outs[i]); + } + static_cast(handle)->PushPull(v_vkeys, v_okeys, v_vals, v_outs, + priority); + API_END(); +} + int MXKVStorePullWithSparse(KVStoreHandle handle, uint32_t num, const int* keys, diff --git a/src/kvstore/kvstore_dist.h b/src/kvstore/kvstore_dist.h index 5ac28cb20c8d..f2df582db3b1 100644 --- a/src/kvstore/kvstore_dist.h +++ b/src/kvstore/kvstore_dist.h @@ -206,6 +206,81 @@ class KVStoreDist : public KVStoreLocal { } } + void PushPullImpl(const std::vector& vkeys, + const std::vector& okeys, + const std::vector& values, + const std::vector& outputs, + int priority) override { + std::vector uniq_vkeys; + std::vector uniq_okeys; + std::vector> grouped_vals; + std::vector> grouped_outs; + + GroupKVPairsPush(vkeys, values, &uniq_vkeys, &grouped_vals, false); + GroupKVPairsPull(okeys, outputs, &uniq_okeys, &grouped_outs, true); + CHECK_EQ(uniq_vkeys.size(), uniq_okeys.size()) + << "List of push and pull keys are different"; + + for (size_t i = 0; i < uniq_vkeys.size(); ++i) { + CHECK_EQ(uniq_vkeys[i], uniq_okeys[i]) + << "Mismatch in push and pull key"; + int key = uniq_vkeys[i]; + const auto& vals = grouped_vals[i]; + const auto& outs = grouped_outs[i]; + + NDArray merged = comm_->Reduce(key, vals, priority); + + const auto push_stype = merged.storage_type(); + const auto pull_stype = outs[0]->storage_type(); + CHECK_EQ(push_stype, kDefaultStorage) + << "Expected push_stype of value to be kDefaultStorage"; + CHECK_EQ(pull_stype, kDefaultStorage) + << "Expected pull_stype of value to be kDefaultStorage"; + + const int push_dtype = merged.dtype(); + const int pull_dtype = outs[0]->dtype(); + CHECK_EQ(push_dtype, pull_dtype) << "Output buffer dtype is different"; + + auto &comm_buf = comm_buf_[key]; + if (merged.ctx().dev_mask() == cpu::kDevMask) { + comm_buf = merged; // avoid memory copy + } else { + if (comm_buf.is_none()) { + comm_buf = NDArray(outs[0]->shape(), pinned_ctx_, true, pull_dtype); + } + CopyFromTo(merged, &comm_buf); + } + + CHECK(gradient_compression_->get_type() == CompressionType::kNone) + << "Compression not supported with PushPull"; + auto pushpull = [this, key, comm_buf]( + RunContext rctx, Engine::CallbackOnComplete cb) { + size_t size = comm_buf.shape().Size(); + const int dtype = comm_buf.dtype(); + const int num_bytes = mshadow::mshadow_sizeof(dtype); + const int cmd = GetCommandType(RequestType::kDefaultPushPull, dtype); + + PSKV& pskv = EncodeDefaultKey(key, size, num_bytes); + char* data = static_cast(comm_buf.data().dptr_); + auto vals = new ps::SArray(data, size * num_bytes, false); + + CHECK_NOTNULL(ps_worker_)->ZPushPull( + pskv.keys, *vals, vals, &pskv.lens, cmd, [vals, cb](){ delete vals; cb(); }); + }; + + CHECK_NOTNULL(Engine::Get())->PushAsync( + pushpull, + pinned_ctx_, + {}, + {comm_buf.var()}, + FnProperty::kNormal, + priority, + "KVStoreDistDefaultStoragePushPull"); + + comm_->Broadcast(key, comm_buf, outs, priority); + } + } + void PushImpl(const std::vector& keys, const std::vector& values, int priority) override { diff --git a/src/kvstore/kvstore_dist_server.h b/src/kvstore/kvstore_dist_server.h index 0cb1a11e3fcc..65ded79743e4 100644 --- a/src/kvstore/kvstore_dist_server.h +++ b/src/kvstore/kvstore_dist_server.h @@ -344,7 +344,8 @@ class KVStoreDistServer { } inline void ApplyUpdates(const DataHandleType type, const int key, - UpdateBuf *update_buf, ps::KVServer* server) { + const ps::KVPairs& req_data, UpdateBuf *update_buf, + ps::KVServer* server) { if (!sync_mode_ || update_buf->request.size() == (size_t) ps::NumWorkers()) { // let the main thread to execute updater_, which is necessary for python auto& stored = has_multi_precision_copy(type) ? store_realt_[key] : store_[key]; @@ -364,7 +365,16 @@ class KVStoreDistServer { LOG(INFO) << "sent response to " << update_buf->request.size() << " workers"; } for (const auto& req : update_buf->request) { - server->Response(req); + /** + * Request can be for either push, pull or pushpull + * If pull flag is set, respond immediately with the updated values + * Otherwise, only send the notification + */ + if (req.pull) { + DefaultStorageResponse(type, key, req, req_data, server); + } else { + server->Response(req); + } } update_buf->request.clear(); if (has_multi_precision_copy(type)) CopyFromTo(stored, store_[key]); @@ -532,7 +542,7 @@ class KVStoreDistServer { true, merged_dtype); } // else nothing to aggregate updates.request.push_back(req_meta); - ApplyUpdates(type, master_key, &updates, server); + ApplyUpdates(type, master_key, req_data, &updates, server); } else { server->Response(req_meta); } @@ -570,7 +580,7 @@ class KVStoreDistServer { AccumulateRowSparseGrads(type, recved, &updates); } updates.request.push_back(req_meta); - ApplyUpdates(type, master_key, &updates, server); + ApplyUpdates(type, master_key, req_data, &updates, server); } } } else { @@ -649,7 +659,7 @@ class KVStoreDistServer { merged.merged += decomp_buf; } merged.request.push_back(req_meta); - ApplyUpdates(type, key, &merged, server); + ApplyUpdates(type, key, req_data, &merged, server); } else { // async push gradient_compression_->Dequantize(recved, &decomp_buf, 0); @@ -732,7 +742,7 @@ class KVStoreDistServer { } } updates.request.push_back(req_meta); - ApplyUpdates(type, key, &updates, server); + ApplyUpdates(type, key, req_data, &updates, server); } } else { DefaultStorageResponse(type, key, req_meta, req_data, server); diff --git a/src/kvstore/kvstore_local.h b/src/kvstore/kvstore_local.h index 5c3c80838a36..ad70bc15ea0a 100644 --- a/src/kvstore/kvstore_local.h +++ b/src/kvstore/kvstore_local.h @@ -129,6 +129,15 @@ class KVStoreLocal : public KVStore { PullImpl(keys, values, priority, ignore_sparse); } + void PushPull(const std::vector& vkeys, + const std::vector& okeys, + const std::vector& values, + const std::vector& outs, + int priority) override { + SetKeyType(kIntKey); + PushPullImpl(vkeys, okeys, values, outs, priority); + } + void PullRowSparse(const std::vector& keys, const std::vector>& val_rowids, int priority = 0) override { @@ -155,6 +164,19 @@ class KVStoreLocal : public KVStore { PullImpl(keys, values, priority, ignore_sparse); } + void PushPull(const std::vector& str_vkeys, + const std::vector& str_okeys, + const std::vector& values, + const std::vector& outs, + int priority) override { + SetKeyType(kStringKey); + std::vector vkeys(str_vkeys.size()); + std::vector okeys(str_okeys.size()); + LookupKeys(str_vkeys, &vkeys); + LookupKeys(str_okeys, &okeys); + PushPullImpl(vkeys, okeys, values, outs, priority); + } + void PullRowSparse(const std::vector& str_keys, const std::vector>& val_rowids, int priority = 0) override { @@ -269,6 +291,15 @@ class KVStoreLocal : public KVStore { CHECK_EQ(key_type_, key_type) << "Mixed key types are not allowed"; } + virtual void PushPullImpl(const std::vector& vkeys, + const std::vector& okeys, + const std::vector& values, + const std::vector& outs, + int priority) { + PushImpl(vkeys, values, priority); + PullImpl(okeys, outs, priority, true); + } + /** * \brief group values on keys for push */ diff --git a/tests/nightly/dist_device_sync_kvstore.py b/tests/nightly/dist_device_sync_kvstore.py index 7fd0333aea79..dc2c7bc35747 100644 --- a/tests/nightly/dist_device_sync_kvstore.py +++ b/tests/nightly/dist_device_sync_kvstore.py @@ -55,23 +55,33 @@ def init_kv(): def test_sync_push_pull(): kv, my_rank, nworker = init_kv() num_gpus = 2 - def check_default_keys(kv, my_rank, nworker): - nrepeat = 3 + def check_default_keys(kv, my_rank, nworker, nrepeat=3, offset=0, use_pushpull=False): # checks pull after push in loop, because behavior during # consecutive pushes doesn't offer any guarantees - for i in range(nrepeat): + for i in range(offset, nrepeat): scale = my_rank + 1 - kv.push('3', [mx.nd.ones(shape, ctx=mx.gpu(j)) * scale for j in range(num_gpus)]) - kv.push('99', [mx.nd.ones(big_shape, ctx=mx.gpu(j)) * scale for j in range(num_gpus)]) num = (nworker + 1) * nworker * rate * num_gpus / 2 * (i + 1) + 1 + + arr = [mx.nd.ones(shape, ctx=mx.gpu(j)) * scale for j in range(num_gpus)] val = mx.nd.zeros(shape) - kv.pull('3', out=val) + if use_pushpull: + kv.pushpull('3', arr, out=val) + else: + kv.push('3', arr) + kv.pull('3', out=val) check_diff_to_scalar(val, num) - val2 = mx.nd.zeros(big_shape) - kv.pull('99', out=val2) - check_diff_to_scalar(val2, num) - check_default_keys(kv, my_rank, nworker) + big_arr = [mx.nd.ones(big_shape, ctx=mx.gpu(j)) * scale for j in range(num_gpus)] + big_val = mx.nd.zeros(big_shape) + if use_pushpull: + kv.pushpull('99', big_arr, out=big_val) + else: + kv.push('99', big_arr) + kv.pull('99', out=big_val) + check_diff_to_scalar(big_val, num) + + check_default_keys(kv, my_rank, nworker, nrepeat=3, offset=0, use_pushpull=False) + check_default_keys(kv, my_rank, nworker, nrepeat=3, offset=3, use_pushpull=True) print('worker ' + str(my_rank) + ' is done') def test_sync_init(): From 9a9c5f8fcce4c8875e9b6aabef11bcec57fceb96 Mon Sep 17 00:00:00 2001 From: Alexander Chalk Date: Mon, 9 Sep 2019 08:59:22 -0400 Subject: [PATCH 303/813] Fix failing tests (#16117) --- .../test/org/apache/clojure_mxnet/operator_test.clj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/clojure-package/test/org/apache/clojure_mxnet/operator_test.clj b/contrib/clojure-package/test/org/apache/clojure_mxnet/operator_test.clj index 5e1b127d18bd..c4edb19d7e3d 100644 --- a/contrib/clojure-package/test/org/apache/clojure_mxnet/operator_test.clj +++ b/contrib/clojure-package/test/org/apache/clojure_mxnet/operator_test.clj @@ -407,8 +407,8 @@ (-> (executor/forward exec-test) (executor/outputs) (first)))))) (deftest test-maximum - (let [data1 (sym/variable "data") - data2 (sym/variable "data") + (let [data1 (sym/variable "data1") + data2 (sym/variable "data2") shape-vec [3 4] data-tmp1 (random/uniform 0 100 shape-vec) data-tmp2 (random/uniform 0 100 shape-vec) @@ -424,8 +424,8 @@ out)))) (deftest test-minimun - (let [data1 (sym/variable "data") - data2 (sym/variable "data") + (let [data1 (sym/variable "data1") + data2 (sym/variable "data2") shape-vec [3 4] data-tmp1 (random/uniform 0 100 shape-vec) data-tmp2 (random/uniform 0 100 shape-vec) From e260f13499a6c84737a97aae09e5921ad8fdd4fd Mon Sep 17 00:00:00 2001 From: Xi Wang Date: Mon, 9 Sep 2019 22:58:29 +0800 Subject: [PATCH 304/813] [Numpy] Random.choice implemented (#16089) * imperative choice done * unit test done * expose take to np internal * style fixed * style fixed * style problems fixed * remove out parameter and fix style * fix syntax error --- python/mxnet/ndarray/numpy/random.py | 78 ++++++- python/mxnet/numpy/random.py | 57 ++++- python/mxnet/symbol/numpy/random.py | 77 +++++++ src/operator/numpy/random/np_choice_op.cc | 81 +++++++ src/operator/numpy/random/np_choice_op.cu | 46 ++++ src/operator/numpy/random/np_choice_op.h | 244 ++++++++++++++++++++++ src/operator/tensor/indexing_op.cc | 1 + tests/python/unittest/test_numpy_op.py | 97 +++++++++ 8 files changed, 679 insertions(+), 2 deletions(-) create mode 100644 src/operator/numpy/random/np_choice_op.cc create mode 100644 src/operator/numpy/random/np_choice_op.cu create mode 100644 src/operator/numpy/random/np_choice_op.h diff --git a/python/mxnet/ndarray/numpy/random.py b/python/mxnet/ndarray/numpy/random.py index 9ea2ef0f5405..9372beaf1e92 100644 --- a/python/mxnet/ndarray/numpy/random.py +++ b/python/mxnet/ndarray/numpy/random.py @@ -24,7 +24,7 @@ from ...base import numeric_types -__all__ = ['randint', 'uniform', 'normal'] +__all__ = ['randint', 'uniform', 'normal', "choice"] def randint(low, high=None, size=None, dtype=None, **kwargs): @@ -243,3 +243,79 @@ def multinomial(n, pvals, size=None): if any(isinstance(i, list) for i in pvals): raise ValueError('object too deep for desired array') return _npi.multinomial(n=n, pvals=pvals, size=size) + + +def choice(a, size=None, replace=True, p=None, **kwargs): + """Generates a random sample from a given 1-D array + + Parameters + ----------- + a : 1-D array-like or int + If an ndarray, a random sample is generated from its elements. + If an int, the random sample is generated as if a were np.arange(a) + size : int or tuple of ints, optional + Output shape. If the given shape is, e.g., ``(m, n, k)``, then + ``m * n * k`` samples are drawn. Default is None, in which case a + single value is returned. + replace : boolean, optional + Whether the sample is with or without replacement + p : 1-D array-like, optional + The probabilities associated with each entry in a. + If not given the sample assumes a uniform distribution over all + entries in a. + ctx : Context, optional + Device context of output. Default is current context. + + Returns + -------- + samples : ndarray + The generated random samples + + Examples + --------- + Generate a uniform random sample from np.arange(5) of size 3: + + >>> np.random.choice(5, 3) + array([0, 3, 4]) + >>> #This is equivalent to np.random.randint(0,5,3) + + Generate a non-uniform random sample from np.arange(5) of size 3: + + >>> np.random.choice(5, 3, p=[0.1, 0, 0.3, 0.6, 0]) + array([3, 3, 0]) + + Generate a uniform random sample from np.arange(5) of size 3 without + replacement: + + >>> np.random.choice(5, 3, replace=False) + array([3,1,0]) + >>> #This is equivalent to np.random.permutation(np.arange(5))[:3] + + Generate a non-uniform random sample from np.arange(5) of size + 3 without replacement: + + >>> np.random.choice(5, 3, replace=False, p=[0.1, 0, 0.3, 0.6, 0]) + array([2, 3, 0]) + """ + from ...numpy import ndarray as np_ndarray + ctx = kwargs.pop('ctx', None) + if ctx is None: + ctx = current_context() + out = kwargs.pop('out', None) + if size == (): + size = None + if isinstance(a, np_ndarray): + ctx = None + if p is None: + indices = _npi.choice(a, a=None, size=size, + replace=replace, ctx=ctx, weighted=False) + return _npi.take(a, indices) + else: + indices = _npi.choice(a, p, a=None, size=size, + replace=replace, ctx=ctx, weighted=True) + return _npi.take(a, indices) + else: + if p is None: + return _npi.choice(a=a, size=size, replace=replace, ctx=ctx, weighted=False, out=out) + else: + return _npi.choice(p, a=a, size=size, replace=replace, ctx=ctx, weighted=True, out=out) diff --git a/python/mxnet/numpy/random.py b/python/mxnet/numpy/random.py index bd534f0b6d97..aace767c8d55 100644 --- a/python/mxnet/numpy/random.py +++ b/python/mxnet/numpy/random.py @@ -21,7 +21,7 @@ from ..ndarray import numpy as _mx_nd_np -__all__ = ["randint", "uniform", "normal"] +__all__ = ["randint", "uniform", "normal", "choice"] def randint(low, high=None, size=None, dtype=None, **kwargs): @@ -180,3 +180,58 @@ def multinomial(n, pvals, size=None, **kwargs): array([32, 68]) """ return _mx_nd_np.random.multinomial(n, pvals, size, **kwargs) + + +def choice(a, size=None, replace=True, p=None, **kwargs): + """Generates a random sample from a given 1-D array + + Parameters + ----------- + a : 1-D array-like or int + If an ndarray, a random sample is generated from its elements. + If an int, the random sample is generated as if a were np.arange(a) + size : int or tuple of ints, optional + Output shape. If the given shape is, e.g., ``(m, n, k)``, then + ``m * n * k`` samples are drawn. Default is None, in which case a + single value is returned. + replace : boolean, optional + Whether the sample is with or without replacement + p : 1-D array-like, optional + The probabilities associated with each entry in a. + If not given the sample assumes a uniform distribution over all + entries in a. + ctx : Context, optional + Device context of output. Default is current context. + + Returns + -------- + samples : ndarray + The generated random samples + + Examples + --------- + Generate a uniform random sample from np.arange(5) of size 3: + + >>> np.random.choice(5, 3) + array([0, 3, 4]) + >>> #This is equivalent to np.random.randint(0,5,3) + + Generate a non-uniform random sample from np.arange(5) of size 3: + + >>> np.random.choice(5, 3, p=[0.1, 0, 0.3, 0.6, 0]) + array([3, 3, 0]) + + Generate a uniform random sample from np.arange(5) of size 3 without + replacement: + + >>> np.random.choice(5, 3, replace=False) + array([3,1,0]) + >>> #This is equivalent to np.random.permutation(np.arange(5))[:3] + + Generate a non-uniform random sample from np.arange(5) of size + 3 without replacement: + + >>> np.random.choice(5, 3, replace=False, p=[0.1, 0, 0.3, 0.6, 0]) + array([2, 3, 0]) + """ + return _mx_nd_np.random.choice(a, size, replace, p, **kwargs) diff --git a/python/mxnet/symbol/numpy/random.py b/python/mxnet/symbol/numpy/random.py index c5b8e1dc4906..523983bac20a 100644 --- a/python/mxnet/symbol/numpy/random.py +++ b/python/mxnet/symbol/numpy/random.py @@ -190,3 +190,80 @@ def normal(loc=0.0, scale=1.0, size=None, **kwargs): raise NotImplementedError('np.random.normal only supports loc and scale of ' 'numeric types for now') return _npi.random_normal(loc, scale, shape=size, dtype=dtype, ctx=ctx, out=out, **kwargs) + + +def choice(a, size=None, replace=True, p=None, **kwargs): + """Generates a random sample from a given 1-D array + + Parameters + ----------- + a : 1-D array-like or int + If an ndarray, a random sample is generated from its elements. + If an int, the random sample is generated as if a were np.arange(a) + size : int or tuple of ints, optional + Output shape. If the given shape is, e.g., ``(m, n, k)``, then + ``m * n * k`` samples are drawn. Default is None, in which case a + single value is returned. + replace : boolean, optional + Whether the sample is with or without replacement + p : 1-D array-like, optional + The probabilities associated with each entry in a. + If not given the sample assumes a uniform distribution over all + entries in a. + ctx : Context, optional + Device context of output. Default is current context. + + Returns + -------- + samples : _Symbol + The generated random samples + + Examples + --------- + Generate a uniform random sample from np.arange(5) of size 3: + + >>> np.random.choice(5, 3) + array([0, 3, 4]) + >>> #This is equivalent to np.random.randint(0,5,3) + + Generate a non-uniform random sample from np.arange(5) of size 3: + + >>> np.random.choice(5, 3, p=[0.1, 0, 0.3, 0.6, 0]) + array([3, 3, 0]) + + Generate a uniform random sample from np.arange(5) of size 3 without + replacement: + + >>> np.random.choice(5, 3, replace=False) + array([3,1,0]) + >>> #This is equivalent to np.random.permutation(np.arange(5))[:3] + + Generate a non-uniform random sample from np.arange(5) of size + 3 without replacement: + + >>> np.random.choice(5, 3, replace=False, p=[0.1, 0, 0.3, 0.6, 0]) + array([2, 3, 0]) + """ + from ._symbol import _Symbol as np_symbol + ctx = kwargs.pop('ctx', None) + if ctx is None: + ctx = current_context() + out = kwargs.pop('out', None) + if size == (): + size = None + + if isinstance(a, np_symbol): + ctx = None + if p is None: + indices = _npi.choice(a, a=None, size=size, + replace=replace, ctx=ctx, weighted=False) + return _npi.take(a, indices) + else: + indices = _npi.choice(a, p, a=None, size=size, + replace=replace, ctx=ctx, weighted=True) + return _npi.take(a, indices) + else: + if p is None: + return _npi.choice(a=a, size=size, replace=replace, ctx=ctx, weighted=False, out=out) + else: + return _npi.choice(p, a=a, size=size, replace=replace, ctx=ctx, weighted=True, out=out) diff --git a/src/operator/numpy/random/np_choice_op.cc b/src/operator/numpy/random/np_choice_op.cc new file mode 100644 index 000000000000..328d7e264861 --- /dev/null +++ b/src/operator/numpy/random/np_choice_op.cc @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file np_choice_op.cc + * \brief Operator for random subset sampling + */ + +#include "./np_choice_op.h" +#include + +namespace mxnet { +namespace op { + +template <> +void _sort(float* key, int64_t* data, index_t length) { + std::sort(data, data + length, + [key](int64_t const& i, int64_t const& j) -> bool { + return key[i] > key[j]; + }); +} + +DMLC_REGISTER_PARAMETER(NumpyChoiceParam); + +NNVM_REGISTER_OP(_npi_choice) +.describe("random choice") +.set_num_inputs( + [](const nnvm::NodeAttrs& attrs) { + int num_input = 0; + const NumpyChoiceParam& param = nnvm::get(attrs.parsed); + if (param.weighted) num_input += 1; + if (!param.a.has_value()) num_input += 1; + return num_input; +}) +.set_num_outputs(1) +.set_attr( + "FListInputNames", + [](const NodeAttrs& attrs) { + int num_input = 0; + const NumpyChoiceParam& param = + nnvm::get(attrs.parsed); + if (param.weighted) num_input += 1; + if (!param.a.has_value()) num_input += 1; + if (num_input == 0) return std::vector(); + if (num_input == 1) return std::vector{"input1"}; + return std::vector{"input1", "input2"}; +}) +.set_attr_parser(ParamParser) +.set_attr("FInferShape", NumpyChoiceOpShape) +.set_attr("FInferType", NumpyChoiceOpType) +.set_attr("FResourceRequest", + [](const nnvm::NodeAttrs& attrs) { + return std::vector{ + ResourceRequest::kRandom, + ResourceRequest::kTempSpace}; + }) +.set_attr("FCompute", NumpyChoiceForward) +.set_attr("FGradient", MakeZeroGradNodes) +.add_argument("input1", "NDArray-or-Symbol", "Source input") +.add_argument("input2", "NDArray-or-Symbol", "Source input") +.add_arguments(NumpyChoiceParam::__FIELDS__()); + +} // namespace op +} // namespace mxnet diff --git a/src/operator/numpy/random/np_choice_op.cu b/src/operator/numpy/random/np_choice_op.cu new file mode 100644 index 000000000000..0f42a2e76df0 --- /dev/null +++ b/src/operator/numpy/random/np_choice_op.cu @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file np_choice_op.cu + * \brief Operator for random subset sampling + */ + +#include +#include +#include +#include "./np_choice_op.h" + +namespace mxnet { +namespace op { + +template <> +void _sort(float* key, int64_t* data, index_t length) { + thrust::device_ptr dev_key(key); + thrust::device_ptr dev_data(data); + thrust::sort_by_key(dev_key, dev_key + length, dev_data, + thrust::greater()); +} + +NNVM_REGISTER_OP(_npi_choice) +.set_attr("FCompute", NumpyChoiceForward); + +} // namespace op +} // namespace mxnet diff --git a/src/operator/numpy/random/np_choice_op.h b/src/operator/numpy/random/np_choice_op.h new file mode 100644 index 000000000000..335cc2741759 --- /dev/null +++ b/src/operator/numpy/random/np_choice_op.h @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file np_choice_op.h + * \brief Operator for random subset sampling + */ + +#ifndef MXNET_OPERATOR_NUMPY_RANDOM_NP_CHOICE_OP_H_ +#define MXNET_OPERATOR_NUMPY_RANDOM_NP_CHOICE_OP_H_ + +#include +#include +#include +#include +#include +#include "../../elemwise_op_common.h" +#include "../../mshadow_op.h" +#include "../../mxnet_op.h" +#include "../../operator_common.h" +#include "../../tensor/elemwise_binary_broadcast_op.h" + +namespace mxnet { +namespace op { + +struct NumpyChoiceParam : public dmlc::Parameter { + dmlc::optional a; + std::string ctx; + dmlc::optional> size; + bool replace; + bool weighted; + DMLC_DECLARE_PARAMETER(NumpyChoiceParam) { + DMLC_DECLARE_FIELD(a); + DMLC_DECLARE_FIELD(size); + DMLC_DECLARE_FIELD(ctx).set_default("cpu"); + DMLC_DECLARE_FIELD(replace).set_default(true); + DMLC_DECLARE_FIELD(weighted).set_default(false); + } +}; + +inline bool NumpyChoiceOpType(const nnvm::NodeAttrs &attrs, + std::vector *in_attrs, + std::vector *out_attrs) { + (*out_attrs)[0] = mshadow::kInt64; + return true; +} + +inline bool NumpyChoiceOpShape(const nnvm::NodeAttrs &attrs, + std::vector *in_attrs, + std::vector *out_attrs) { + const NumpyChoiceParam ¶m = nnvm::get(attrs.parsed); + if (param.size.has_value()) { + // Size declared. + std::vector oshape_vec; + const mxnet::Tuple &size = param.size.value(); + for (int i = 0; i < size.ndim(); ++i) { + oshape_vec.emplace_back(size[i]); + } + SHAPE_ASSIGN_CHECK(*out_attrs, 0, TShape(oshape_vec)); + } else { + SHAPE_ASSIGN_CHECK(*out_attrs, 0, TShape(0, -1)) + } + return true; +} + +template +void _sort(float *key, int64_t *data, index_t length); + +namespace mxnet_op { + +// Uniform sample without replacement. +struct generate_samples { + MSHADOW_XINLINE static void Map(index_t i, int64_t k, unsigned *rands) { + rands[i] = rands[i] % (i + k + 1); + } +}; + +template +struct generate_reservoir { + MSHADOW_XINLINE static void Map(index_t dummy_index, int64_t *indices, + unsigned *samples, int64_t nb_iterations, + int64_t k) { + for (int64_t i = 0; i < nb_iterations; i++) { + int64_t z = samples[i]; + if (z < k) { + int64_t t = indices[z]; + indices[z] = indices[i + k]; + indices[i + k] = t; + } + } + } +}; + +// Uniform sample with replacement. +struct random_indices { + MSHADOW_XINLINE static void Map(index_t i, unsigned *samples, int64_t *outs, + int64_t k) { + outs[i] = samples[i] % k; + } +}; + +// Weighted sample without replacement. +// Use perturbed Gumbel variates as keys. +struct generate_keys { + MSHADOW_XINLINE static void Map(index_t i, float *uniforms, float *weights) { + uniforms[i] = -logf(-logf(uniforms[i])) + logf(weights[i]); + } +}; + +// Weighted sample with replacement. +struct categorical_sampling { + MSHADOW_XINLINE static void Map(index_t i, float *weights, size_t length, + float *uniforms, int64_t *outs) { + outs[i] = 0; + float acc = 0.0; + float threshold = uniforms[i]; + for (size_t k = 0; k < length; k++) { + acc += weights[k]; + if (acc < threshold) { + outs[i] += 1; + } + } + } +}; + +} // namespace mxnet_op + +template +void NumpyChoiceForward(const nnvm::NodeAttrs &attrs, const OpContext &ctx, + const std::vector &inputs, + const std::vector &req, + const std::vector &outputs) { + using namespace mshadow; + using namespace mxnet_op; + const NumpyChoiceParam ¶m = nnvm::get(attrs.parsed); + Stream *s = ctx.get_stream(); + bool replace = param.replace; + bool weighted = param.weighted; + int64_t input_size = 0; + int weight_index = 0; + if (param.a.has_value()) { + input_size = param.a.value(); + } else { + input_size = inputs[0].Size(); + weight_index += 1; + } + int64_t output_size = outputs[0].Size(); + if (weighted) { + Random *prnd = ctx.requested[0].get_random(s); + int64_t random_tensor_size = replace ? output_size : input_size; + int64_t indices_size = replace ? 0 : input_size; + Tensor workspace = + ctx.requested[1].get_space_typed( + Shape1(indices_size * sizeof(int64_t) + + (random_tensor_size * sizeof(float) / 7 + 1) * 8), + s); + // slice workspace + char *workspace_ptr = workspace.dptr_; + Tensor random_numbers = + Tensor(reinterpret_cast(workspace_ptr), + Shape1(random_tensor_size), s); + prnd->SampleUniform(&random_numbers, 0, 1); + workspace_ptr += ((random_tensor_size * sizeof(float) / 7 + 1) * 8); + if (replace) { + Kernel::Launch( + s, output_size, inputs[weight_index].dptr(), input_size, + random_numbers.dptr_, outputs[0].dptr()); + } else { + Tensor indices = Tensor( + reinterpret_cast(workspace_ptr), Shape1(indices_size), s); + indices = expr::range((int64_t)0, input_size); + Kernel::Launch(s, input_size, random_numbers.dptr_, + inputs[weight_index].dptr()); + _sort(random_numbers.dptr_, indices.dptr_, input_size); + Copy(outputs[0].FlatTo1D(s), indices.Slice(0, output_size), s); + } + } else { + Random *prnd = ctx.requested[0].get_random(s); + int64_t random_tensor_size = + (replace ? output_size + : std::min(output_size, input_size - output_size)); + int64_t indices_size = replace ? 0 : input_size; + Tensor workspace = + ctx.requested[1].get_space_typed( + Shape1(indices_size * sizeof(int64_t) + + (random_tensor_size * sizeof(unsigned) / 7 + 1) * 8), + s); + // slice workspace + char *workspace_ptr = workspace.dptr_; + Tensor random_numbers = + Tensor(reinterpret_cast(workspace_ptr), + Shape1(random_tensor_size), s); + prnd->GetRandInt(random_numbers); + workspace_ptr += ((random_tensor_size * sizeof(unsigned) / 7 + 1) * 8); + if (replace) { + Kernel::Launch(s, output_size, random_numbers.dptr_, + outputs[0].dptr(), + input_size); + } else { + Tensor indices = Tensor( + reinterpret_cast(workspace_ptr), Shape1(indices_size), s); + indices = expr::range((int64_t)0, input_size); + int64_t nb_iterations = random_tensor_size; + int64_t split = input_size - nb_iterations; + Kernel::Launch(s, random_tensor_size, split, + random_numbers.dptr_); + // Reservoir sampling. + Kernel, xpu>::Launch( + s, 1, indices.dptr_, random_numbers.dptr_, nb_iterations, split); + index_t begin; + index_t end; + if (2 * output_size < input_size) { + begin = input_size - output_size; + end = input_size; + } else { + begin = 0; + end = output_size; + } + Copy(outputs[0].FlatTo1D(s), indices.Slice(begin, end), s); + } + } +} + +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_NUMPY_RANDOM_NP_CHOICE_OP_H_ diff --git a/src/operator/tensor/indexing_op.cc b/src/operator/tensor/indexing_op.cc index 7a770163da17..463e9f98820e 100644 --- a/src/operator/tensor/indexing_op.cc +++ b/src/operator/tensor/indexing_op.cc @@ -654,6 +654,7 @@ NNVM_REGISTER_OP(_backward_SparseEmbedding) .set_attr("FComputeEx", SparseEmbeddingOpBackwardEx); NNVM_REGISTER_OP(take) +.add_alias("_npi_take") .describe(R"code(Takes elements from an input array along the given axis. This function slices the input array along a particular axis with the provided indices. diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index 51aa9054451c..1c9ebbb32866 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -1647,6 +1647,103 @@ def hybrid_forward(self, F, a): assert_almost_equal(mx_out.asnumpy(), np_out, rtol=1e-3, atol=1e-5) +@with_seed() +@use_np +def test_np_choice(): + class TestUniformChoice(HybridBlock): + def __init__(self, sample_size, replace): + super(TestUniformChoice, self).__init__() + self.sample_size = sample_size + self.replace = replace + + def hybrid_forward(self, F, a): + return F.np.random.choice(a=a, size=self.sample_size, replace=self.replace, p=None) + + class TestWeightedChoice(HybridBlock): + def __init__(self, sample_size, replace): + super(TestWeightedChoice, self).__init__() + self.sample_size = sample_size + self.replace = replace + + def hybrid_forward(self, F, a, p): + op = getattr(F.np.random, "choice", None) + return F.np.random.choice(a, self.sample_size, self.replace, p) + + def test_sample_with_replacement(sampler, num_classes, shape, weight=None): + samples = sampler(num_classes, shape, replace=True, p=weight).asnumpy() + generated_density = _np.histogram(samples, _np.arange(num_classes + 1), density=True)[0] + expected_density = (weight.asnumpy() if weight is not None else + _np.array([1 / num_classes] * num_classes)) + # test almost equal + assert_almost_equal(generated_density, expected_density, rtol=1e-1, atol=1e-1) + # test shape + assert (samples.shape == shape) + + def test_sample_without_replacement(sampler, num_classes, shape, num_trials, weight=None): + samples = sampler(num_classes, shape, replace=False, p=weight).asnumpy() + # Check shape and uniqueness + assert samples.shape == shape + assert len(_np.unique(samples)) == samples.size + # Check distribution + bins = _np.zeros((num_classes)) + expected_freq = (weight.asnumpy() if weight is not None else + _np.array([1 / num_classes] * num_classes)) + for i in range(num_trials): + out = sampler(num_classes, 1, replace=False, p=weight).item() + bins[out] += 1 + bins /= num_trials + assert_almost_equal(bins, expected_freq, rtol=1e-1, atol=1e-1) + + def test_indexing_mode(sampler, set_size, samples_size, replace, weight=None): + a = np.arange(set_size) + if weight is not None: + samples = sampler(a, weight) + else: + samples = sampler(a) + assert len(samples) == samples_size + if not replace: + assert len(_np.unique(samples)) == samples_size + + num_classes = 10 + num_samples = 10 ** 8 + # Density tests are commented out due to their huge time comsumption. + # Tests passed locally. + # shape_list1 = [ + # (10 ** 8, 1), + # (10 ** 5, 10 ** 3), + # (10 ** 2, 10 ** 3, 10 ** 3) + # ] + # for shape in shape_list1: + # test_sample_with_replacement(np.random.choice, num_classes, shape) + # weight = np.array(_np.random.dirichlet([1.0] * num_classes)) + # test_sample_with_replacement(np.random.choice, num_classes, shape, weight) + + # Tests passed locally, + # commented out for the same reason as above. + # shape_list2 = [ + # (6, 1), + # (2, 3), + # (1, 2, 3), + # (2, 2), + # ] + # for shape in shape_list2: + # test_sample_without_replacement(np.random.choice, num_classes, shape, 10 ** 5) + # weight = np.array(_np.random.dirichlet([1.0] * num_classes)) + # test_sample_without_replacement(np.random.choice, num_classes, shape, 10 ** 5, weight) + + # Test hypridize mode: + for hybridize in [True, False]: + for replace in [True, False]: + test_choice = TestUniformChoice(num_classes // 2, replace) + test_choice_weighted = TestWeightedChoice(num_classes // 2, replace) + if hybridize: + test_choice.hybridize() + test_choice_weighted.hybridize() + weight = np.array(_np.random.dirichlet([1.0] * num_classes)) + test_indexing_mode(test_choice, num_classes, num_classes // 2, replace, None) + test_indexing_mode(test_choice_weighted, num_classes, num_classes // 2, replace, weight) + + if __name__ == '__main__': import nose nose.runmodule() From 9675a2d5dbefc5fb8faf59126f74a0d31bfcbd02 Mon Sep 17 00:00:00 2001 From: Zach Kimberg Date: Mon, 9 Sep 2019 14:09:32 -0700 Subject: [PATCH 305/813] Update python dependencies (#16105) --- tools/pip/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/pip/setup.py b/tools/pip/setup.py index 3905a69fb94d..f0a3e37fd3c3 100644 --- a/tools/pip/setup.py +++ b/tools/pip/setup.py @@ -52,8 +52,8 @@ def has_ext_modules(self): DEPENDENCIES = [ - 'numpy>1.16.0,<2.0.0', - 'requests>=2.20.0', + 'numpy<2.0.0,>1.16.0', + 'requests>=2.20.0,<3', 'graphviz<0.9.0,>=0.8.1' ] From e87995db6f8ac7e8d4424001e1db5296398562f6 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Srivastava Date: Tue, 10 Sep 2019 21:25:33 -0700 Subject: [PATCH 306/813] Reducing memory footprint of one_hot for Large Array Testing (#16136) * reducing memory footprint of one_hot for Large Tensor * removing one_hot from large_vector and changing large_array test to incorporate large vector part of the test --- tests/nightly/test_large_array.py | 10 +++++----- tests/nightly/test_large_vector.py | 9 --------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/tests/nightly/test_large_array.py b/tests/nightly/test_large_array.py index 7622b76a3120..99856f770d5c 100644 --- a/tests/nightly/test_large_array.py +++ b/tests/nightly/test_large_array.py @@ -25,6 +25,7 @@ # dimension constants MEDIUM_X = 10000 +VLARGE_X = 4300000000 LARGE_X = 100000000 SMALL_X = 100 SMALL_Y = 50 @@ -1197,12 +1198,11 @@ def test_slice_axis(): def test_one_hot(): - a = nd.array(np.zeros(SMALL_Y)) - a[0] = 1 - a[-1] = 1 - b = nd.one_hot(a, LARGE_X) + #default dtype of ndarray is float32 which cannot index elements over 2^32 + a = nd.array([1, (VLARGE_X - 1)], dtype=np.int64) + b = nd.one_hot(a, VLARGE_X) b[0][1] == 1 - b[-1][1] == 1 + b[1][-1] == 1 def test_full(): diff --git a/tests/nightly/test_large_vector.py b/tests/nightly/test_large_vector.py index b59a4462d922..e3407792f2da 100644 --- a/tests/nightly/test_large_vector.py +++ b/tests/nightly/test_large_vector.py @@ -714,15 +714,6 @@ def test_full(): assert a[-1] == 3 -def test_one_hot(): - a = nd.zeros(10) - a[0] = 1 - a[-1] = 1 - b = nd.one_hot(a, LARGE_X) - assert b[0][1] == 1 - assert b[-1][1] == 1 - - if __name__ == '__main__': import nose nose.runmodule() From c12c978aa0b1096e0153fbdc2355628ed6271c07 Mon Sep 17 00:00:00 2001 From: reminisce Date: Wed, 11 Sep 2019 00:27:34 -0700 Subject: [PATCH 307/813] Fix sample.normal shape inference --- src/operator/random/multisample_op.h | 2 +- tests/python/unittest/test_operator.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/operator/random/multisample_op.h b/src/operator/random/multisample_op.h index 7d5e256297ad..bbd58e23c210 100644 --- a/src/operator/random/multisample_op.h +++ b/src/operator/random/multisample_op.h @@ -42,7 +42,7 @@ struct MultiSampleParam : public dmlc::Parameter { int dtype; DMLC_DECLARE_PARAMETER(MultiSampleParam) { DMLC_DECLARE_FIELD(shape) - .set_default(mxnet::TShape()) + .set_default(mxnet::TShape(0, 1)) .describe("Shape to be sampled from each random distribution."); DMLC_DECLARE_FIELD(dtype) .add_enum("None", -1) diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index bcf618f2784a..40763fabef19 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -9239,6 +9239,19 @@ def test_transpose_infer_shape_mixed(): assert(y[0].shape == (2,3)) +@with_seed() +def test_sample_normal_default_shape(): + # Test case from https://github.com/apache/incubator-mxnet/issues/16135 + s = mx.nd.sample_normal(mu=mx.nd.array([10.0]), sigma=mx.nd.array([0.5])) + assert s.shape == (1,) + s = mx.nd.sample_normal(mu=mx.nd.array([10.0]), sigma=mx.nd.array([0.5]), shape=()) + assert s.shape == (1,) + s = mx.nd.sample_normal(mu=mx.nd.array([10.0]), sigma=mx.nd.array([0.5]), shape=1) + assert s.shape == (1, 1) + s = mx.nd.sample_normal(mu=mx.nd.array([10.0]), sigma=mx.nd.array([0.5]), shape=(1,)) + assert s.shape == (1, 1) + + if __name__ == '__main__': import nose nose.runmodule() From 85fe0b439adf587024457a5346e8e84929195985 Mon Sep 17 00:00:00 2001 From: Anirudh Date: Wed, 11 Sep 2019 15:06:47 -0700 Subject: [PATCH 308/813] Fixes NAG optimizer #15543 (#16053) * fix update rules * readable updates in unit test * mom update --- src/operator/optimizer_op-inl.h | 50 ++++++++++++------------- tests/python/unittest/test_optimizer.py | 14 ++----- 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/src/operator/optimizer_op-inl.h b/src/operator/optimizer_op-inl.h index 3c03f8061d87..d22bd644d21f 100644 --- a/src/operator/optimizer_op-inl.h +++ b/src/operator/optimizer_op-inl.h @@ -1066,21 +1066,18 @@ struct NAGMomKernel { const DType param_lr, const DType param_wd, const DType param_rescale_grad, const OpReqType req) { if (param_clip_gradient >= 0.0f) { - mom_data[i] = param_momentum*mom_data[i] - + mshadow_op::clip::Map(param_rescale_grad*grad_data[i], - param_clip_gradient) - + (param_wd*weight_data[i]); - KERNEL_ASSIGN(out_data[i], req, weight_data[i] - - param_lr*(param_momentum*mom_data[i] - + mshadow_op::clip::Map(param_rescale_grad*grad_data[i], - param_clip_gradient))); + mom_data[i] = param_momentum*mom_data[i]; + KERNEL_ASSIGN(out_data[i], req, weight_data[i]-mom_data[i]+(param_momentum+1) + *(mom_data[i]-(param_lr*(mshadow_op::clip::Map(param_rescale_grad + *grad_data[i], param_clip_gradient)+(param_wd*weight_data[i]))))); + mom_data[i] = mom_data[i] - (param_lr*((mshadow_op::clip::Map(param_rescale_grad*grad_data[i], + param_clip_gradient))+(param_wd*weight_data[i]))); } else { - mom_data[i] = param_momentum*mom_data[i] - + param_rescale_grad*grad_data[i] - + (param_wd*weight_data[i]); - KERNEL_ASSIGN(out_data[i], req, weight_data[i] - - param_lr*(param_momentum*mom_data[i] - + param_rescale_grad*grad_data[i])); + mom_data[i] = param_momentum*mom_data[i]; + KERNEL_ASSIGN(out_data[i], req, weight_data[i]-mom_data[i]+(param_momentum+1) + *(mom_data[i]-(param_lr*(param_rescale_grad*grad_data[i]+param_wd*weight_data[i])))); + mom_data[i] = mom_data[i] - param_lr*((param_rescale_grad*grad_data[i]) + +(param_wd*weight_data[i])); } } }; @@ -1119,22 +1116,21 @@ struct MP_NAGMomKernel { const OpReqType req) { float w = weight32[i]; if (param_clip_gradient >= 0.0f) { - mom_data[i] = param_momentum*mom_data[i] - + mshadow_op::clip::Map(param_rescale_grad - *static_cast(grad_data[i]), param_clip_gradient) - + (param_wd*w); - w = w - param_lr*(param_momentum*mom_data[i] - + mshadow_op::clip::Map(param_rescale_grad - *static_cast(grad_data[i]), - param_clip_gradient)); + mom_data[i] = param_momentum*mom_data[i]; + w = w-mom_data[i]+(param_momentum+1)*(mom_data[i]-param_lr + *(mshadow_op::clip::Map(param_rescale_grad*static_cast(grad_data[i]), + param_clip_gradient)+(param_wd*w))); + mom_data[i] = mom_data[i] - param_lr + *((mshadow_op::clip::Map(param_rescale_grad*static_cast(grad_data[i]), + param_clip_gradient))+(param_wd*w)); weight32[i] = w; KERNEL_ASSIGN(out_data[i], req, w); } else { - mom_data[i] = param_momentum*mom_data[i] - + param_rescale_grad*static_cast(grad_data[i]) - + (param_wd*w); - w = w - param_lr*(param_momentum*mom_data[i] - + param_rescale_grad*static_cast(grad_data[i])); + mom_data[i] = param_momentum*mom_data[i]; + w = w-mom_data[i]+(param_momentum+1)*(mom_data[i]-param_lr + *(param_rescale_grad*static_cast(grad_data[i])+(param_wd*w))); + mom_data[i] = mom_data[i] - param_lr + *((param_rescale_grad*static_cast(grad_data[i]))+(param_wd*w)); weight32[i] = w; KERNEL_ASSIGN(out_data[i], req, w); } diff --git a/tests/python/unittest/test_optimizer.py b/tests/python/unittest/test_optimizer.py index 3e6cdd0997ce..cea469960f64 100644 --- a/tests/python/unittest/test_optimizer.py +++ b/tests/python/unittest/test_optimizer.py @@ -384,11 +384,8 @@ def update(self, index, weight, grad, state): weight[:] += -lr * (grad + wd * weight) else: mom = state - mom[:] *= self.momentum - mom[:] += grad - mom[:] += wd * weight - grad[:] += self.momentum * mom - weight[:] -= lr * grad + weight[:] += (self.momentum**2 * mom) - lr*(self.momentum + 1)*(grad + wd*weight) + mom[:] = (self.momentum*mom) - lr*(grad + wd*weight) else: grad32 = array(grad, ctx=grad.context, dtype=np.float32) grad32 = grad32 * self.rescale_grad @@ -399,11 +396,8 @@ def update(self, index, weight, grad, state): if self.momentum == 0.0: weight32[:] += -lr * (grad32 + wd * weight32) else: - mom[:] *= self.momentum - mom[:] += grad32 - mom[:] += wd * weight32 - grad32[:] += self.momentum * mom - weight32[:] -= lr * grad32 + weight32[:] += (self.momentum**2 * mom) - lr*(self.momentum+1)*(grad32 + wd*weight32) + mom[:] = (self.momentum*mom) - lr*(grad32 + wd*weight32) tmp = weight32.astype(weight.dtype) tmp.copyto(weight) From eb037a85c9bd05a55ff37813758b8ced7ea11733 Mon Sep 17 00:00:00 2001 From: kshitij12345 Date: Thu, 12 Sep 2019 04:05:20 +0530 Subject: [PATCH 309/813] [MXNET-978] Higher Order Gradient Support `sinh`, `cosh`. (#15412) * add higher order support for sinh cosh * add relevant tests * update comments * retrigger CI --- src/operator/tensor/elemwise_unary_op_trig.cc | 51 ++++++++++++++++++- .../python/unittest/test_higher_order_grad.py | 29 +++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/operator/tensor/elemwise_unary_op_trig.cc b/src/operator/tensor/elemwise_unary_op_trig.cc index bd0c8973ae07..7546f5096ccd 100644 --- a/src/operator/tensor/elemwise_unary_op_trig.cc +++ b/src/operator/tensor/elemwise_unary_op_trig.cc @@ -313,7 +313,30 @@ The storage type of ``sinh`` output depends upon the input storage type: )code" ADD_FILELINE) .set_attr("FGradient", ElemwiseGradUseIn{ "_backward_sinh" }); -MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU_DR(_backward_sinh, unary_bwd); +MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU_DR(_backward_sinh, unary_bwd) +.set_attr("FGradient", + [](const nnvm::NodePtr& n, const std::vector& ograds) { + // ograds[0]: head_grad_grads (dL/dxgrad) + // inputs[0]: dL/dy + // inputs[1]: x (ElemwiseUseIn) + // f(x) = sinh(x) + // f'(x) = cosh(x) + // f''(x) = sinh(x) + auto dydx = MakeNode("cosh", n->attrs.name + "_dydx", + {n->inputs[1]}, nullptr, &n); + auto d2ydx2 = MakeNode("sinh", n->attrs.name + "_grad_grad_mid", {n->inputs[1]}, nullptr, &n); + + auto grad_grad_mid = MakeNode("elemwise_mul", n->attrs.name + "backward_grad_grad_mid", + {n->inputs[0], nnvm::NodeEntry{d2ydx2}}, nullptr, &n); + + std::vector ret; + + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad", + {ograds[0], nnvm::NodeEntry{dydx}}, nullptr, &n)); + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad_in", + {ograds[0], nnvm::NodeEntry{grad_grad_mid}}, nullptr, &n)); + return ret; + }); // cosh MXNET_OPERATOR_REGISTER_UNARY_WITH_SPARSE_DR(cosh, cpu, mshadow_op::cosh) @@ -328,7 +351,31 @@ The storage type of ``cosh`` output is always dense )code" ADD_FILELINE) .set_attr("FGradient", ElemwiseGradUseIn{ "_backward_cosh" }); -MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU(_backward_cosh, unary_bwd); +MXNET_OPERATOR_REGISTER_BINARY_WITH_SPARSE_CPU(_backward_cosh, unary_bwd) +.set_attr("FGradient", + [](const nnvm::NodePtr& n, const std::vector& ograds) { + // ograds[0]: head_grad_grads (dL/dxgrad) + // inputs[0]: dL/dy + // inputs[1]: x (ElemwiseUseIn) + // f(x) = cosh(x) + // f'(x) = sinh(x) + // f''(x) = cosh(x) + auto dydx = MakeNode("sinh", n->attrs.name + "_dydx", + {n->inputs[1]}, nullptr, &n); + auto d2ydx2 = MakeNode("cosh", n->attrs.name + "_grad_grad_mid", {n->inputs[1]}, nullptr, &n); + + auto grad_grad_mid = MakeNode("elemwise_mul", n->attrs.name + "backward_grad_grad_mid", + {n->inputs[0], nnvm::NodeEntry{d2ydx2}}, nullptr, &n); + + std::vector ret; + + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad", + {ograds[0], nnvm::NodeEntry{dydx}}, nullptr, &n)); + ret.emplace_back(MakeNode("elemwise_mul", n->attrs.name + "_backward_grad_grad_in", + {ograds[0], nnvm::NodeEntry{grad_grad_mid}}, nullptr, &n)); + return ret; + }); + // tanh MXNET_OPERATOR_REGISTER_UNARY_WITH_RSP_CSR(tanh, cpu, mshadow_op::tanh) diff --git a/tests/python/unittest/test_higher_order_grad.py b/tests/python/unittest/test_higher_order_grad.py index a758775a09ba..8e6ac3d0cf52 100644 --- a/tests/python/unittest/test_higher_order_grad.py +++ b/tests/python/unittest/test_higher_order_grad.py @@ -68,6 +68,34 @@ def grad_grad_op(x): check_second_order_unary(array, tan, grad_grad_op) +@with_seed() +def test_sinh(): + def sinh(x): + return nd.sinh(x) + + def grad_grad_op(x): + return sinh(x) + + for dim in range(1, 5): + shape = rand_shape_nd(dim) + array = random_arrays(shape) + check_second_order_unary(array, sinh, grad_grad_op) + + +@with_seed() +def test_cosh(): + def cosh(x): + return nd.cosh(x) + + def grad_grad_op(x): + return cosh(x) + + for dim in range(1, 5): + shape = rand_shape_nd(dim) + array = random_arrays(shape) + check_second_order_unary(array, cosh, grad_grad_op) + + @with_seed() def test_tanh(): def tanh(x): @@ -245,6 +273,7 @@ def grad_grad_op(x): check_second_order_unary(array, dropout, grad_grad_op) +@with_seed() def test_sigmoid(): def sigmoid(x): return nd.sigmoid(x) From 61e8347a88565c7660c8e28535cc230457df45d9 Mon Sep 17 00:00:00 2001 From: Aaron Markham Date: Wed, 11 Sep 2019 20:33:53 -0700 Subject: [PATCH 310/813] Revert "julia: rename build env var `MXNET_HOME` to `MXNET_ROOT` (#15568)" (#16147) This reverts commit 75ee1e9f434383eff9b250f32b10c89e721f66d3. --- ci/docker/runtime_functions.sh | 8 +++---- ci/windows/test_jl07_cpu.ps1 | 2 +- ci/windows/test_jl10_cpu.ps1 | 2 +- julia/NEWS.md | 7 ------ julia/deps/build.jl | 33 +++++----------------------- julia/docs/src/user-guide/install.md | 20 +++++++---------- julia/src/base.jl | 30 +++++-------------------- 7 files changed, 25 insertions(+), 77 deletions(-) diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index ac962527d7f7..689d525a5cce 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -1088,7 +1088,7 @@ unittest_ubuntu_gpu_R() { unittest_ubuntu_cpu_julia() { set -ex export PATH="$1/bin:$PATH" - export MXNET_ROOT='/work/mxnet' + export MXNET_HOME='/work/mxnet' export JULIA_DEPOT_PATH='/work/julia-depot' export INTEGRATION_TEST=1 @@ -1098,7 +1098,7 @@ unittest_ubuntu_cpu_julia() { export LD_PRELOAD='/usr/lib/x86_64-linux-gnu/libjemalloc.so' export LD_LIBRARY_PATH=/work/mxnet/lib:$LD_LIBRARY_PATH - # use the prebuilt binary from $MXNET_ROOT/lib + # use the prebuilt binary from $MXNET_HOME/lib julia --project=./julia -e 'using Pkg; Pkg.build("MXNet")' # run the script `julia/test/runtests.jl` @@ -1253,7 +1253,7 @@ build_docs() { # Setup environment for Julia docs export PATH="/work/julia10/bin:$PATH" - export MXNET_ROOT='/work/mxnet' + export MXNET_HOME='/work/mxnet' export JULIA_DEPOT_PATH='/work/julia-depot' julia -e 'using InteractiveUtils; versioninfo()' @@ -1465,7 +1465,7 @@ deploy_docs() { # Setup for Julia docs export PATH="/work/julia10/bin:$PATH" - export MXNET_ROOT='/work/mxnet' + export MXNET_HOME='/work/mxnet' export JULIA_DEPOT_PATH='/work/julia-depot' julia -e 'using InteractiveUtils; versioninfo()' diff --git a/ci/windows/test_jl07_cpu.ps1 b/ci/windows/test_jl07_cpu.ps1 index d3b77b1cbc1d..6cd34ef209de 100644 --- a/ci/windows/test_jl07_cpu.ps1 +++ b/ci/windows/test_jl07_cpu.ps1 @@ -20,7 +20,7 @@ # set default output encoding to utf8 $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8' -$env:MXNET_ROOT = [System.IO.Path]::GetFullPath('.\windows_package') +$env:MXNET_HOME = [System.IO.Path]::GetFullPath('.\windows_package') $env:JULIA_URL = "https://julialang-s3.julialang.org/bin/winnt/x64/0.7/julia-0.7.0-win64.exe" $env:JULIA_DEPOT_PATH = [System.IO.Path]::GetFullPath('.\julia-depot') diff --git a/ci/windows/test_jl10_cpu.ps1 b/ci/windows/test_jl10_cpu.ps1 index 2dbda4886c42..96c419066354 100644 --- a/ci/windows/test_jl10_cpu.ps1 +++ b/ci/windows/test_jl10_cpu.ps1 @@ -20,7 +20,7 @@ # set default output encoding to utf8 $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8' -$env:MXNET_ROOT = [System.IO.Path]::GetFullPath('.\windows_package') +$env:MXNET_HOME = [System.IO.Path]::GetFullPath('.\windows_package') $env:JULIA_URL = "https://julialang-s3.julialang.org/bin/winnt/x64/1.0/julia-1.0.3-win64.exe" $env:JULIA_DEPOT_PATH = [System.IO.Path]::GetFullPath('.\julia-depot') diff --git a/julia/NEWS.md b/julia/NEWS.md index 9935d6885ca5..e678b97176d1 100644 --- a/julia/NEWS.md +++ b/julia/NEWS.md @@ -17,13 +17,6 @@ # v1.6.0 -* Rename environment variable `MXNET_HOME` to `MXNET_ROOT` (#15568). - -* Environment variable `MXNET_LIBRARY_PATH` supports (#15568). - - ```shell - $ MXNET_LIBRARY_PATH=/path/to/libmxnet.so julia - ``` # v1.5.0 diff --git a/julia/deps/build.jl b/julia/deps/build.jl index dfd3f78379b0..a87343d9dab5 100644 --- a/julia/deps/build.jl +++ b/julia/deps/build.jl @@ -26,35 +26,14 @@ libmxnet_detected = false libmxnet_curr_ver = get(ENV, "MXNET_COMMIT", "master") curr_win = "20190608" # v1.5.0 -# TODO: remove MXNET_HOME backward compatibility in v1.7 if haskey(ENV, "MXNET_HOME") - @warn "The environment variable `MXNET_HOME` has been renamed, please use `MXNET_ROOT` instead." -end - -# TODO: remove MXNET_HOME backward compatibility in v2.0 -MXNET_ROOT = get(ENV, "MXNET_ROOT", get(ENV, "MXNET_HOME", "")) -search_locations = if !isempty(MXNET_ROOT) - !isabspath(MXNET_ROOT) && error("MXNET_ROOT should be a absolute path") - @info "env var: MXNET_ROOT -> $MXNET_ROOT" - [joinpath(MXNET_ROOT, "lib"), MXNET_ROOT] -else - [] -end - -MXNET_LIBRARY_PATH = get(ENV, "MXNET_LIBRARY_PATH", "") -println(typeof(MXNET_LIBRARY_PATH)) -# In case of macOS, if user build libmxnet from source and set the MXNET_ROOT, -# the output is still named as `libmxnet.so`. -search_names = ["libmxnet.$(Libdl.dlext)", "libmxnet.so"] -if !isempty(MXNET_LIBRARY_PATH) - !isabspath(MXNET_LIBRARY_PATH) && error("MXNET_LIBRARY_PATH should be a absolute path") - @info "env var: MXNET_LIBRARY_PATH -> $MXNET_LIBRARY_PATH" - pushfirst!(search_names, MXNET_LIBRARY_PATH) -end - -if (!isempty(MXNET_ROOT)) || (!isempty(MXNET_LIBRARY_PATH)) + MXNET_HOME = ENV["MXNET_HOME"] + @info("MXNET_HOME environment detected: $MXNET_HOME") @info("Trying to load existing libmxnet...") - lib = Libdl.find_library(search_names, search_locations) + # In case of macOS, if user build libmxnet from source and set the MXNET_HOME, + # the output is still named as `libmxnet.so`. + lib = Libdl.find_library(["libmxnet.$(Libdl.dlext)", "libmxnet.so"], + [joinpath(MXNET_HOME, "lib"), MXNET_HOME]) if !isempty(lib) @info("Existing libmxnet detected at $lib, skip building...") libmxnet_detected = true diff --git a/julia/docs/src/user-guide/install.md b/julia/docs/src/user-guide/install.md index d9d5574b025d..d1a90a808507 100644 --- a/julia/docs/src/user-guide/install.md +++ b/julia/docs/src/user-guide/install.md @@ -40,13 +40,9 @@ libmxnet. There are several environment variables that change this behaviour. -- `MXNET_ROOT`: If you already have a pre-installed version of mxnet - you can use `MXNET_ROOT` to point the build-process in the right direction. - Note that you should set this variable as a absolute path. -- `MXNET_HOME`: This variable is replaced by `MXNET_ROOT`. It has been renamed - as of v1.6. -- `MXNET_LIBRARY_PATH`: The absolute path of the libmxnet share object. -- `CUDA_HOME`: If the automatic CUDA detection fails you can also set `CUDA_HOME` +- `MXNET_HOME`: If you already have a pre-installed version of mxnet + you can use `MXNET_HOME` to point the build-process in the right direction. +- `CUDA_HOME`: If the automatic cuda detection fails you can also set `CUDA_HOME` to override the process. - `MXNET_COMMIT`: To control which version of libmxnet will be compiled, you can use the`MXNET_COMMIT` variable to point to either a version tag @@ -84,18 +80,18 @@ to work with a separate, maybe customized libmxnet. To build libmxnet, please refer to [the installation guide of libmxnet](https://mxnet.incubator.apache.org/install/index.html). After -successfully installing libmxnet, set the `MXNET_ROOT` *environment +successfully installing libmxnet, set the `MXNET_HOME` *environment variable* to the location of libmxnet. In other words, the compiled -`libmxnet.so` should be found in `$MXNET_ROOT/lib`. +`libmxnet.so` should be found in `$MXNET_HOME/lib`. > **note** > -> The constant `MXNET_ROOT` is pre-compiled in MXNet.jl package cache. +> The constant `MXNET_HOME` is pre-compiled in MXNet.jl package cache. > If you updated the environment variable after installing MXNet.jl, > make sure to update the pre-compilation cache by > `Base.compilecache("MXNet")`. -When the `MXNET_ROOT` environment variable is detected and the +When the `MXNET_HOME` environment variable is detected and the corresponding `libmxnet.so` could be loaded successfully, MXNet.jl will skip automatic building during installation and use the specified libmxnet instead. @@ -103,7 +99,7 @@ libmxnet instead. Basically, MXNet.jl will search `libmxnet.so` or `libmxnet.dll` in the following paths (and in that order): -- `$MXNET_ROOT/lib`: customized libmxnet builds +- `$MXNET_HOME/lib`: customized libmxnet builds - `Pkg.dir("MXNet", "deps", "usr", "lib")`: automatic builds - Any system wide library search path diff --git a/julia/src/base.jl b/julia/src/base.jl index 7a2bde0907c1..683146402620 100644 --- a/julia/src/base.jl +++ b/julia/src/base.jl @@ -47,31 +47,11 @@ const grad_req_map = Dict{Symbol,GRAD_REQ}( ################################################################################ # Initialization and library API entrance ################################################################################ -function _get_search_names() - MXNET_LIBRARY_PATH = get(ENV, "MXNET_LIBRARY_PATH", "") - A = ["libmxnet.$(Libdl.dlext)", "libmxnet.so"] # see build.jl - if !isempty(MXNET_LIBRARY_PATH) - !isabspath(MXNET_LIBRARY_PATH) && error("MXNET_LIBRARY_PATH should be a absolute path") - pushfirst!(A, MXNET_LIBRARY_PATH) - end - A -end - -function _get_search_dirs() - # TODO: remove MXNET_HOME backward compatibility in v1.7 - if haskey(ENV, "MXNET_HOME") - @warn "The environment variable `MXNET_HOME` has been renamed, please use `MXNET_ROOT` instead." - end - A = [joinpath(@__DIR__, "..", "deps", "usr", "lib")] - MXNET_ROOT = get(ENV, "MXNET_ROOT", get(ENV, "MXNET_HOME", "")) - if !isempty(MXNET_ROOT) - !isabspath(MXNET_ROOT) && error("MXNET_ROOT should be a absolute path") - prepend!(A, [joinpath(MXNET_ROOT, "lib"), MXNET_ROOT]) - end - A -end - -const MXNET_LIB = Libdl.find_library(_get_search_names(), _get_search_dirs()) +const MXNET_LIB = Libdl.find_library(["libmxnet.$(Libdl.dlext)", "libmxnet.so"], # see build.jl + [joinpath(get(ENV, "MXNET_HOME", ""), "lib"), + get(ENV, "MXNET_HOME", ""), + joinpath(@__DIR__, "..", + "deps", "usr", "lib")]) const LIB_VERSION = Ref{Cint}(0) if isempty(MXNET_LIB) From 5b42065c129e5ce80ad41a7839f21d25821f5667 Mon Sep 17 00:00:00 2001 From: Vikas-kum Date: Wed, 11 Sep 2019 21:24:26 -0700 Subject: [PATCH 311/813] Fixing build for gluon estimator test, including libtvm in pack libs (#16148) * Fixing build for gluon estimator test, including libtvm in pack libs * Fixing html build for nightly --- docs/mxdoc.py | 8 +++++++- tests/nightly/JenkinsfileForBinaries | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/mxdoc.py b/docs/mxdoc.py index f2bb9714de0c..237b36a96606 100644 --- a/docs/mxdoc.py +++ b/docs/mxdoc.py @@ -468,7 +468,13 @@ def copy_artifacts(app): def setup(app): # If MXNET_DOCS_BUILD_MXNET is set something different than 1 # Skip the build step - if os.getenv('MXNET_DOCS_BUILD_MXNET') == '1'or _MXNET_DOCS_BUILD_MXNET: + env_build_mxnet = os.getenv('MXNET_DOCS_BUILD_MXNET', default=None) + should_build_mxnet = _MXNET_DOCS_BUILD_MXNET + if env_build_mxnet is not None: + print("env MXNET_DOCS_BUILD_MXNET is set to " + str(env_build_mxnet)) + should_build_mxnet = (env_build_mxnet == '1') + + if should_build_mxnet: print("Building MXNet!") app.connect("builder-inited", build_mxnet) if _DOXYGEN_DOCS: diff --git a/tests/nightly/JenkinsfileForBinaries b/tests/nightly/JenkinsfileForBinaries index 8582643e0163..2bd78ac2afb6 100755 --- a/tests/nightly/JenkinsfileForBinaries +++ b/tests/nightly/JenkinsfileForBinaries @@ -18,8 +18,8 @@ // //This is a Jenkinsfile for nightly tests. The format and some functions have been picked up from the top-level Jenkinsfile -mx_lib = 'lib/libmxnet.so, lib/libmxnet.a, 3rdparty/dmlc-core/libdmlc.a, 3rdparty/tvm/nnvm/lib/libnnvm.a' -mx_cmake_lib = 'build/libmxnet.so, build/libmxnet.a, build/3rdparty/dmlc-core/libdmlc.a, build/tests/mxnet_unit_tests, build/3rdparty/openmp/runtime/src/libomp.so' +mx_lib = 'lib/libmxnet.so, lib/libmxnet.a, lib/libtvm_runtime.so, lib/libtvmop.so, 3rdparty/dmlc-core/libdmlc.a, 3rdparty/tvm/nnvm/lib/libnnvm.a' +mx_cmake_lib = 'build/libmxnet.so, build/libmxnet.a, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/3rdparty/dmlc-core/libdmlc.a, build/tests/mxnet_unit_tests, build/3rdparty/openmp/runtime/src/libomp.so' node('utility') { // Loading the utilities requires a node context unfortunately From 287e3b52524ef791a0429a741084fe2db28b0c44 Mon Sep 17 00:00:00 2001 From: "Shi, guangyong" <35016185+gyshi@users.noreply.github.com> Date: Thu, 12 Sep 2019 14:02:24 +0800 Subject: [PATCH 312/813] Numpy add numpy op indices (#15837) * add numpy op indices * re-trigger ci * fix cpplint --- python/mxnet/ndarray/numpy/_op.py | 73 ++++++++++++++++- python/mxnet/numpy/multiarray.py | 67 +++++++++++++++- python/mxnet/symbol/numpy/_symbol.py | 72 ++++++++++++++++- src/operator/numpy/np_init_op.cc | 33 ++++++++ src/operator/numpy/np_init_op.cu | 4 + src/operator/numpy/np_init_op.h | 107 +++++++++++++++++++++++++ tests/python/unittest/test_numpy_op.py | 54 ++++++++++++- 7 files changed, 402 insertions(+), 8 deletions(-) create mode 100644 src/operator/numpy/np_init_op.h diff --git a/python/mxnet/ndarray/numpy/_op.py b/python/mxnet/ndarray/numpy/_op.py index 964d533b2387..671345c9a546 100644 --- a/python/mxnet/ndarray/numpy/_op.py +++ b/python/mxnet/ndarray/numpy/_op.py @@ -33,7 +33,7 @@ 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'linspace', 'expand_dims', 'tile', 'arange', 'split', 'concatenate', 'stack', 'mean', - 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var'] + 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices'] @set_module('mxnet.ndarray.numpy') @@ -1888,7 +1888,7 @@ def tile(A, reps): """ return _unary_func_helper(A, _npi.tile, _np.tile, reps=reps) - +# pylint: disable=redefined-outer-name @set_module('mxnet.ndarray.numpy') def split(ary, indices_or_sections, axis=0): """Split an array into multiple sub-arrays. @@ -1936,6 +1936,7 @@ def split(ary, indices_or_sections, axis=0): if not isinstance(ret, list): return [ret] return ret +# pylint: enable=redefined-outer-name @set_module('mxnet.ndarray.numpy') @@ -2363,3 +2364,71 @@ def var(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): # pylint: 0.2025 """ return _npi.var(a, axis=axis, dtype=dtype, ddof=ddof, keepdims=keepdims, out=out) + + +# pylint: disable=redefined-outer-name +@set_module('mxnet.ndarray.numpy') +def indices(dimensions, dtype=_np.int32, ctx=None): + """Return an array representing the indices of a grid. + + Compute an array where the subarrays contain index values 0,1,... + varying only along the corresponding axis. + + Parameters + ---------- + dimensions : sequence of ints + The shape of the grid. + dtype : data-type, optional + The desired data-type for the array. Default is `float32`. + ctx : device context, optional + Device context on which the memory is allocated. Default is + `mxnet.context.current_context()`. + + Returns + ------- + grid : ndarray + The array of grid indices, + ``grid.shape = (len(dimensions),) + tuple(dimensions)``. + + Notes + ----- + The output shape is obtained by prepending the number of dimensions + in front of the tuple of dimensions, i.e. if `dimensions` is a tuple + ``(r0, ..., rN-1)`` of length ``N``, the output shape is + ``(N,r0,...,rN-1)``. + + The subarrays ``grid[k]`` contains the N-D array of indices along the + ``k-th`` axis. Explicitly:: + + grid[k,i0,i1,...,iN-1] = ik + + Examples + -------- + >>> grid = np.indices((2, 3)) + >>> grid.shape + (2, 2, 3) + >>> grid[0] # row indices + array([[0, 0, 0], + [1, 1, 1]]) + >>> grid[1] # column indices + array([[0, 0, 0], + [1, 1, 1]], dtype=int32) + + The indices can be used as an index into an array. + + >>> x = np.arange(20).reshape(5, 4) + >>> row, col = np.indices((2, 3)) + >>> x[row, col] + array([[0., 1., 2.], + [4., 5., 6.]]) + + Note that it would be more straightforward in the above example to + extract the required elements directly with ``x[:2, :3]``. + """ + if isinstance(dimensions, (tuple, list)): + if ctx is None: + ctx = current_context() + return _npi.indices(dimensions=dimensions, dtype=dtype, ctx=ctx) + else: + raise ValueError("The dimensions must be sequence of ints") +# pylint: enable=redefined-outer-name diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index 7d6e81a2d4a5..4eddb0380c09 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -52,7 +52,7 @@ 'degrees', 'log2', 'log1p', 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'linspace', 'expand_dims', 'tile', 'arange', 'split', 'concatenate', - 'stack', 'mean', 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var'] + 'stack', 'mean', 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices'] # Return code for dispatching indexing function call _NDARRAY_UNSUPPORTED_INDEXING = -1 @@ -1520,6 +1520,7 @@ def _full(self, value): """ return _mx_nd_np.full(self.shape, value, ctx=self.context, dtype=self.dtype, out=self) + # pylint: disable=redefined-outer-name def _scatter_set_nd(self, value_nd, indices): """ This is added as an ndarray class method in order to support polymorphism in NDArray and numpy.ndarray indexing @@ -1527,6 +1528,7 @@ def _scatter_set_nd(self, value_nd, indices): return _npi.scatter_set_nd( lhs=self, rhs=value_nd, indices=indices, shape=self.shape, out=self ) + # pylint: enable=redefined-outer-name @property def shape(self): @@ -3808,3 +3810,66 @@ def var(a, axis=None, dtype=None, out=None, ddof=0, keepdims=None): 0.2025 """ return _npi.var(a, axis=axis, dtype=dtype, ddof=ddof, keepdims=keepdims, out=out) + + +# pylint: disable=redefined-outer-name +@set_module('mxnet.numpy') +def indices(dimensions, dtype=_np.int32, ctx=None): + """Return an array representing the indices of a grid. + + Compute an array where the subarrays contain index values 0,1,... + varying only along the corresponding axis. + + Parameters + ---------- + dimensions : sequence of ints + The shape of the grid. + dtype : data-type, optional + The desired data-type for the array. Default is `float32`. + ctx : device context, optional + Device context on which the memory is allocated. Default is + `mxnet.context.current_context()`. + + Returns + ------- + grid : ndarray + The array of grid indices, + ``grid.shape = (len(dimensions),) + tuple(dimensions)``. + + Notes + ----- + The output shape is obtained by prepending the number of dimensions + in front of the tuple of dimensions, i.e. if `dimensions` is a tuple + ``(r0, ..., rN-1)`` of length ``N``, the output shape is + ``(N,r0,...,rN-1)``. + + The subarrays ``grid[k]`` contains the N-D array of indices along the + ``k-th`` axis. Explicitly:: + + grid[k,i0,i1,...,iN-1] = ik + + Examples + -------- + >>> grid = np.indices((2, 3)) + >>> grid.shape + (2, 2, 3) + >>> grid[0] # row indices + array([[0, 0, 0], + [1, 1, 1]]) + >>> grid[1] # column indices + array([[0, 0, 0], + [1, 1, 1]], dtype=int32) + + The indices can be used as an index into an array. + + >>> x = np.arange(20).reshape(5, 4) + >>> row, col = np.indices((2, 3)) + >>> x[row, col] + array([[0., 1., 2.], + [4., 5., 6.]]) + + Note that it would be more straightforward in the above example to + extract the required elements directly with ``x[:2, :3]``. + """ + return _mx_nd_np.indices(dimensions=dimensions, dtype=dtype, ctx=ctx) +# pylint: enable=redefined-outer-name diff --git a/python/mxnet/symbol/numpy/_symbol.py b/python/mxnet/symbol/numpy/_symbol.py index ec4f6a4dd741..0841c0e4d2cc 100644 --- a/python/mxnet/symbol/numpy/_symbol.py +++ b/python/mxnet/symbol/numpy/_symbol.py @@ -35,7 +35,7 @@ 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'linspace', 'expand_dims', 'tile', 'arange', 'split', 'concatenate', 'stack', 'mean', - 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var'] + 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices'] def _num_outputs(sym): @@ -2305,6 +2305,7 @@ def arange(start, stop=None, step=1, dtype=None, ctx=None): return _npi.arange(start=start, stop=stop, step=step, dtype=dtype, ctx=ctx) +# pylint: disable=redefined-outer-name @set_module('mxnet.symbol.numpy') def split(ary, indices_or_sections, axis=0): """Split an array into multiple sub-arrays. @@ -2345,6 +2346,7 @@ def split(ary, indices_or_sections, axis=0): raise ValueError('indices_or_sections must either int or tuple of ints') ret = _npi.split(ary, indices, axis, False, sections) return ret +# pylint: enable=redefined-outer-name @set_module('mxnet.symbol.numpy') @@ -2678,4 +2680,72 @@ def var(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): # pylint: return _npi.var(a, axis=axis, dtype=dtype, ddof=ddof, keepdims=keepdims, out=out) +# pylint: disable=redefined-outer-name +@set_module('mxnet.symbol.numpy') +def indices(dimensions, dtype=_np.int32, ctx=None): + """Return an array representing the indices of a grid. + + Compute an array where the subarrays contain index values 0,1,... + varying only along the corresponding axis. + + Parameters + ---------- + dimensions : sequence of ints + The shape of the grid. + dtype : data-type, optional + The desired data-type for the array. Default is `float32`. + ctx : device context, optional + Device context on which the memory is allocated. Default is + `mxnet.context.current_context()`. + + Returns + ------- + grid : _Symbol + The array of grid indices, + ``grid.shape = (len(dimensions),) + tuple(dimensions)``. + + Notes + ----- + The output shape is obtained by prepending the number of dimensions + in front of the tuple of dimensions, i.e. if `dimensions` is a tuple + ``(r0, ..., rN-1)`` of length ``N``, the output shape is + ``(N,r0,...,rN-1)``. + + The subarrays ``grid[k]`` contains the N-D array of indices along the + ``k-th`` axis. Explicitly:: + + grid[k,i0,i1,...,iN-1] = ik + + Examples + -------- + >>> grid = np.indices((2, 3)) + >>> grid.shape + (2, 2, 3) + >>> grid[0] # row indices + array([[0, 0, 0], + [1, 1, 1]]) + >>> grid[1] # column indices + array([[0, 0, 0], + [1, 1, 1]], dtype=int32) + + The indices can be used as an index into an array. + + >>> x = np.arange(20).reshape(5, 4) + >>> row, col = np.indices((2, 3)) + >>> x[row, col] + array([[0., 1., 2.], + [4., 5., 6.]]) + + Note that it would be more straightforward in the above example to + extract the required elements directly with ``x[:2, :3]``. + """ + if isinstance(dimensions, (tuple, list)): + if ctx is None: + ctx = current_context() + return _npi.indices(dimensions=dimensions, dtype=dtype, ctx=ctx) + else: + raise ValueError("The dimensions must be sequence of ints") +# pylint: enable=redefined-outer-name + + _set_np_symbol_class(_Symbol) diff --git a/src/operator/numpy/np_init_op.cc b/src/operator/numpy/np_init_op.cc index b85a92f97683..4f031bdaa050 100644 --- a/src/operator/numpy/np_init_op.cc +++ b/src/operator/numpy/np_init_op.cc @@ -24,10 +24,33 @@ */ #include "../tensor/init_op.h" #include "../tensor/elemwise_unary_op.h" +#include "./np_init_op.h" namespace mxnet { namespace op { +DMLC_REGISTER_PARAMETER(IndicesOpParam); + +inline bool NumpyIndicesShape(const nnvm::NodeAttrs& attrs, + mxnet::ShapeVector* in_shapes, + mxnet::ShapeVector* out_shapes) { + const IndicesOpParam& param = nnvm::get(attrs.parsed); + CHECK_EQ(in_shapes->size(), 0U); + CHECK_EQ(out_shapes->size(), 1U); + CHECK_GE(param.dimensions.ndim(), 0) + << "_npi_indices dimensions the number of dim must not be less than 0"; + mxnet::TShape param_dim = param.dimensions; + if (!shape_is_known(param_dim)) return false; + const int indim = param.dimensions.ndim(); + mxnet::TShape ret(indim + 1, -1); + ret[0] = indim; + for (int i = 1; i < indim + 1; ++i) { + ret[i] = param.dimensions[i-1]; + } + SHAPE_ASSIGN_CHECK(*out_shapes, 0, ret); + return shape_is_known(out_shapes->at(0)); +} + NNVM_REGISTER_OP(_npi_zeros) .set_num_inputs(0) .set_num_outputs(1) @@ -110,5 +133,15 @@ NNVM_REGISTER_OP(_npi_arange) .set_attr("FCompute", RangeCompute) .add_arguments(RangeParam::__FIELDS__()); +NNVM_REGISTER_OP(_npi_indices) +.describe("Return an array representing the indices of a grid.") +.set_num_inputs(0) +.set_num_outputs(1) +.set_attr_parser(ParamParser) +.set_attr("FInferShape", NumpyIndicesShape) +.set_attr("FInferType", InitType) +.set_attr("FCompute", IndicesCompute) +.add_arguments(IndicesOpParam::__FIELDS__()); + } // namespace op } // namespace mxnet diff --git a/src/operator/numpy/np_init_op.cu b/src/operator/numpy/np_init_op.cu index fe631f388a19..49f1051735d8 100644 --- a/src/operator/numpy/np_init_op.cu +++ b/src/operator/numpy/np_init_op.cu @@ -24,6 +24,7 @@ */ #include "../tensor/init_op.h" +#include "./np_init_op.h" namespace mxnet { namespace op { @@ -43,5 +44,8 @@ NNVM_REGISTER_OP(_np_ones_like) NNVM_REGISTER_OP(_npi_arange) .set_attr("FCompute", RangeCompute); +NNVM_REGISTER_OP(_npi_indices) +.set_attr("FCompute", IndicesCompute); + } // namespace op } // namespace mxnet diff --git a/src/operator/numpy/np_init_op.h b/src/operator/numpy/np_init_op.h new file mode 100644 index 000000000000..5c41820b57f8 --- /dev/null +++ b/src/operator/numpy/np_init_op.h @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file np_init_op.h + * \brief CPU Implementation of numpy init op + */ +#ifndef MXNET_OPERATOR_NUMPY_NP_INIT_OP_H_ +#define MXNET_OPERATOR_NUMPY_NP_INIT_OP_H_ + +#include +#include +#include "../tensor/init_op.h" +#include "../tensor/elemwise_unary_op.h" + + +namespace mxnet { +namespace op { + +struct IndicesOpParam : public dmlc::Parameter { + mxnet::TShape dimensions; + int dtype; + std::string ctx; + DMLC_DECLARE_PARAMETER(IndicesOpParam) { + DMLC_DECLARE_FIELD(dimensions) + .describe("The shape of the grid."); + DMLC_DECLARE_FIELD(dtype).set_default(mshadow::kInt32) + MXNET_ADD_ALL_TYPES + .describe("Target data type."); + DMLC_DECLARE_FIELD(ctx) + .set_default("") + .describe("Context of output, in format [cpu|gpu|cpu_pinned](n)." + "Only used for imperative calls."); + } +}; + +template +struct indices_fwd { + template + MSHADOW_XINLINE static void Map(index_t i, DType* out, + const nnvm::dim_t value, + const nnvm::dim_t N, + const nnvm::dim_t dim_i, + const nnvm::dim_t j, + const nnvm::dim_t k, + const nnvm::dim_t t) { + KERNEL_ASSIGN(out[dim_i*N+N/(t*value)*j+i+k*N/t], req, static_cast(j)); + } +}; + +template +void IndicesCompute(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + using namespace mxnet_op; + CHECK_EQ(inputs.size(), 0U); + CHECK_EQ(outputs.size(), 1U); + CHECK_EQ(req.size(), 1U); + const IndicesOpParam& param = nnvm::get(attrs.parsed); + const TBlob& out_data = outputs[0]; + mshadow::Stream *s = ctx.get_stream(); + dim_t indim = param.dimensions.ndim(); + dim_t t = 1; + dim_t N = out_data.Size()/indim; + dim_t value = 0; + if (out_data.Size() == 0) return; + if (req[0] != kNullOp) { + MSHADOW_TYPE_SWITCH(out_data.type_flag_, DType, { + MXNET_ASSIGN_REQ_SWITCH(req[0], req_type, { + for (int i = 0; i < indim; ++i) { + value = param.dimensions[i]; + for (int k = 0; k < t; ++k) { + for (int j = 0; j < param.dimensions[i]; ++j) { + Kernel, xpu>::Launch(s, N/(param.dimensions[i] * t), + out_data.dptr(), value, N, i, j, k, t); + } + } + t = t * param.dimensions[i]; + } + }); + }); + } +} + +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_NUMPY_NP_INIT_OP_H_ diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index 1c9ebbb32866..0f7355b7ef13 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -30,6 +30,7 @@ import collections import scipy.stats as ss from mxnet.test_utils import verify_generator, gen_buckets_probs_with_ppf, retry +import platform @with_seed() @@ -1664,7 +1665,7 @@ def __init__(self, sample_size, replace): super(TestWeightedChoice, self).__init__() self.sample_size = sample_size self.replace = replace - + def hybrid_forward(self, F, a, p): op = getattr(F.np.random, "choice", None) return F.np.random.choice(a, self.sample_size, self.replace, p) @@ -1703,7 +1704,7 @@ def test_indexing_mode(sampler, set_size, samples_size, replace, weight=None): assert len(samples) == samples_size if not replace: assert len(_np.unique(samples)) == samples_size - + num_classes = 10 num_samples = 10 ** 8 # Density tests are commented out due to their huge time comsumption. @@ -1717,7 +1718,7 @@ def test_indexing_mode(sampler, set_size, samples_size, replace, weight=None): # test_sample_with_replacement(np.random.choice, num_classes, shape) # weight = np.array(_np.random.dirichlet([1.0] * num_classes)) # test_sample_with_replacement(np.random.choice, num_classes, shape, weight) - + # Tests passed locally, # commented out for the same reason as above. # shape_list2 = [ @@ -1730,7 +1731,7 @@ def test_indexing_mode(sampler, set_size, samples_size, replace, weight=None): # test_sample_without_replacement(np.random.choice, num_classes, shape, 10 ** 5) # weight = np.array(_np.random.dirichlet([1.0] * num_classes)) # test_sample_without_replacement(np.random.choice, num_classes, shape, 10 ** 5, weight) - + # Test hypridize mode: for hybridize in [True, False]: for replace in [True, False]: @@ -1744,6 +1745,51 @@ def test_indexing_mode(sampler, set_size, samples_size, replace, weight=None): test_indexing_mode(test_choice_weighted, num_classes, num_classes // 2, replace, weight) +@with_seed() +@use_np +def test_np_indices(): + dtypes = ['int32', 'int64', 'float16', 'float32', 'float64'] + shapes = [ + (0,), + (3,), + (2, 3, 4), + (2, 0, 4), + (1, 1, 1, 1), + (1, 0, 0, 1), + (2, 3, 4, 5, 6, 7) + ] + if platform.system() == 'Windows': + shapes = shapes[1:] #beacuse in numpy windows version, indces not support dimensions is empty tuple. + for dtype in dtypes: + for shape in shapes: + np_out = _np.indices(dimensions=shape, dtype=dtype) + mx_out = np.indices(dimensions=shape, dtype=dtype) + same(mx_out.asnumpy(), np_out) + assert mx_out.shape == np_out.shape + + @use_np + class TestIndices(HybridBlock): + def __init__(self, dimensions=None, dtype=None): + super(TestIndices, self).__init__() + self._dimensions = dimensions + self._dtype = dtype + + def hybrid_forward(self, F, x): + return x + F.np.indices(dimensions=self._dimensions, dtype=self._dtype) + + for dtype in dtypes: + for shape in shapes: + x = np.zeros(shape=(), dtype=dtype) + for hybridize in [False, True]: + net = TestIndices(dimensions=shape, dtype=dtype) + np_out = _np.indices(dimensions=shape, dtype=dtype) + if hybridize: + net.hybridize() + mx_out = net(x) + same(mx_out.asnumpy(), np_out) + assert mx_out.shape == np_out.shape + + if __name__ == '__main__': import nose nose.runmodule() From d9364ba20f6dfe0c0049b91daece3032ddc2c53e Mon Sep 17 00:00:00 2001 From: Iblis Lin Date: Thu, 12 Sep 2019 21:41:53 +0800 Subject: [PATCH 313/813] julia: fix `mx.forward` kwargs checking (#16138) close https://github.com/dmlc/MXNet.jl/issues/431 --- julia/src/executor.jl | 2 +- julia/test/unittest/bind.jl | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/julia/src/executor.jl b/julia/src/executor.jl index e565617976ce..37f2dde615b8 100644 --- a/julia/src/executor.jl +++ b/julia/src/executor.jl @@ -176,7 +176,7 @@ end function forward(self::Executor; is_train::Bool = false, kwargs...) for (k,v) in kwargs - @assert(k ∈ self.arg_dict, "Unknown argument $k") + @assert(k ∈ keys(self.arg_dict), "Unknown argument $k") @assert(isa(v, NDArray), "Keyword argument $k must be an NDArray") copy!(self.arg_dict[k], v) end diff --git a/julia/test/unittest/bind.jl b/julia/test/unittest/bind.jl index 0ae0ab427b99..a221733cded1 100644 --- a/julia/test/unittest/bind.jl +++ b/julia/test/unittest/bind.jl @@ -84,11 +84,26 @@ function test_arithmetic() end end +function test_forward() + # forward with data keyword argument + x = @var x + y = x .+ 42 + + A = 1:5 + B = A .+ 42 + + e = bind(y, args = Dict(:x => NDArray(24:28))) + z = forward(e, x = NDArray(A))[1] + + @test copy(z) == collect(B) +end + ################################################################################ # Run tests ################################################################################ @testset "Bind Test" begin test_arithmetic() + test_forward() end end From b4b7bfb20705c8ac5e86caf7b69e709c77b2cc06 Mon Sep 17 00:00:00 2001 From: Haibin Lin Date: Thu, 12 Sep 2019 07:31:32 -0700 Subject: [PATCH 314/813] Update env_var.md (#16145) --- docs/faq/env_var.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/faq/env_var.md b/docs/faq/env_var.md index b33a104fd315..ca5292c3a294 100644 --- a/docs/faq/env_var.md +++ b/docs/faq/env_var.md @@ -127,10 +127,10 @@ $env:MXNET_STORAGE_FALLBACK_LOG_VERBOSE=0 - Values: Int ```(default=15)``` - The maximum number of nodes in the subgraph executed in bulk during training (not inference). Setting this to a larger number may reduce the degree of parallelism for multi-GPU training. * MXNET_EXEC_BULK_EXEC_MAX_NODE_TRAIN_FWD - - Values: Int ```(default=)``` + - Values: Int ```(default=)``` - The maximum number of nodes in the subgraph executed in bulk during training (not inference) in the forward pass. * MXNET_EXEC_BULK_EXEC_MAX_NODE_TRAIN_BWD - - Values: Int ```(default=)``` + - Values: Int ```(default=)``` - The maximum number of nodes in the subgraph executed in bulk during training (not inference) in the backward pass. ## Control the Data Communication From c5383f788c2f2eec21ff97261b2ac0f5cc902684 Mon Sep 17 00:00:00 2001 From: perdasilva Date: Thu, 12 Sep 2019 23:00:46 +0200 Subject: [PATCH 315/813] CD Fixes (#16127) * Ignore load lib test in CD jobs * Removes cu80 and adds cu101 support to CD builds * Disable cython in CD python tests * Updates CD documentation to reflect variant changes --- cd/Jenkinsfile_cd_pipeline | 2 +- cd/README.md | 4 ++-- ci/docker/runtime_functions.sh | 2 ++ python/mxnet/test_utils.py | 5 +++++ tests/python/unittest/test_library_loading.py | 3 ++- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cd/Jenkinsfile_cd_pipeline b/cd/Jenkinsfile_cd_pipeline index 85b06cb09bb0..d7872204fcb7 100644 --- a/cd/Jenkinsfile_cd_pipeline +++ b/cd/Jenkinsfile_cd_pipeline @@ -30,7 +30,7 @@ pipeline { parameters { // Release parameters - string(defaultValue: "cpu,mkl,cu80,cu80mkl,cu90,cu90mkl,cu92,cu92mkl,cu100,cu100mkl", description: "Comma separated list of variants", name: "MXNET_VARIANTS") + string(defaultValue: "cpu,mkl,cu90,cu90mkl,cu92,cu92mkl,cu100,cu100mkl,cu101,cu101mkl", description: "Comma separated list of variants", name: "MXNET_VARIANTS") booleanParam(defaultValue: false, description: 'Whether this is a release build or not', name: "RELEASE_BUILD") } diff --git a/cd/README.md b/cd/README.md index fd978c1d819e..a66fd0ce5533 100644 --- a/cd/README.md +++ b/cd/README.md @@ -31,14 +31,14 @@ Currently, 10 variants are supported: * *cpu*: CPU * *mkl*: CPU w/ MKL -* *cu80*: CUDA 8.0 -* *cu80mkl*: CUDA 8.0 w/ MKL-DNN * *cu90*: CUDA 9.0 * *cu90mkl*: CUDA 9.0 w/ MKL-DNN * *cu92*: CUDA 9.2 * *cu92mkl*: CUDA 9.2 w/ MKL-DNN * *cu100*: CUDA 10 * *cu100mkl*: CUDA 10 w/ MKL-DNN +* *cu101*: CUDA 10 +* *cu101mkl*: CUDA 10.1 w/ MKL-DNN *For more on variants, see [here](https://github.com/apache/incubator-mxnet/issues/8671)* diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index 689d525a5cce..67cc98c73620 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -834,6 +834,8 @@ cd_unittest_ubuntu() { export PYTHONPATH=./python/ export MXNET_MKLDNN_DEBUG=1 # Ignored if not present export MXNET_STORAGE_FALLBACK_LOG_VERBOSE=0 + export MXNET_ENABLE_CYTHON=0 + export CD_JOB=1 # signal this is a CD run so any unecessary tests can be skipped local mxnet_variant=${1:?"This function requires a mxnet variant as the first argument"} local python_cmd=${2:?"This function requires a python command as the first argument"} diff --git a/python/mxnet/test_utils.py b/python/mxnet/test_utils.py index 4533729e2c15..abdf57039c3a 100644 --- a/python/mxnet/test_utils.py +++ b/python/mxnet/test_utils.py @@ -2220,3 +2220,8 @@ def collapse_sum_like(a, shape): assert s == 1 axes.append(i+ndim_diff) return np.sum(a, axis=tuple(axes)).reshape(shape) + + +def is_cd_run(): + """Checks if the test is running as part of a Continuous Delivery run""" + return os.environ.get("CD_JOB", 0) == "1" diff --git a/tests/python/unittest/test_library_loading.py b/tests/python/unittest/test_library_loading.py index 596d124c89d6..29b99dacdbe1 100644 --- a/tests/python/unittest/test_library_loading.py +++ b/tests/python/unittest/test_library_loading.py @@ -22,12 +22,13 @@ import unittest import mxnet as mx from mxnet.base import MXNetError -from mxnet.test_utils import download +from mxnet.test_utils import download, is_cd_run def check_platform(): return platform.machine() not in ['x86_64', 'AMD64'] @unittest.skipIf(check_platform(), "not all machine types supported") +@unittest.skipIf(is_cd_run(), "continuous delivery run - ignoring test") def test_library_loading(): if (os.name=='posix'): lib = 'libsample_lib.so' From ccd24a8a55275fe3522bd203dab25f08bc24e24a Mon Sep 17 00:00:00 2001 From: YixinBao Date: Fri, 13 Sep 2019 07:03:48 +0800 Subject: [PATCH 316/813] avoid test relu at the origin due to discontinuous gradient (#16133) * avoid test relu at the origin due to discontinuous gradient * retrigger CI --- tests/python/mkl/test_mkldnn.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/python/mkl/test_mkldnn.py b/tests/python/mkl/test_mkldnn.py index 3e623b59977d..2ffe3eaa233d 100644 --- a/tests/python/mkl/test_mkldnn.py +++ b/tests/python/mkl/test_mkldnn.py @@ -337,13 +337,17 @@ def check_pooling_training(stype): def test_activation(): def check_activation_training(stype): for shape in [(2, 3, 3), (2, 3, 2, 2)]: + eps = 1e-5 data_tmp = np.random.normal(-0.1, 1, size=shape) + # Avoid finite difference method inaccuracies due to discontinuous gradient at the origin. + # Here we replace small problematic inputs with 1.0. Repro issue with seed 851486559. + data_tmp[abs(data_tmp) < eps] = 1.0 data = mx.symbol.Variable('data', stype=stype) in_location = [mx.nd.array(data_tmp).tostype(stype)] test = mx.symbol.Activation(data, act_type="relu") - check_numeric_gradient(test, in_location, numeric_eps=1e-5, rtol=0.16, atol=1e-4) + check_numeric_gradient(test, in_location, numeric_eps=eps, rtol=0.16, atol=1e-4) stypes = ['row_sparse', 'default'] for stype in stypes: From e9e267ef711261f20528d443f38eb7b9e991057c Mon Sep 17 00:00:00 2001 From: reminisce Date: Sat, 14 Sep 2019 09:33:08 -0700 Subject: [PATCH 317/813] Fix remaining errors reported by D2L (#16157) * Fix * Fix ssd * Add test for np.empty * Add np.linalg.norm * Fix indexing bug * Improve doc --- python/mxnet/_numpy_op_doc.py | 353 +++++++++++++++++++- python/mxnet/gluon/loss.py | 10 +- python/mxnet/ndarray/numpy/linalg.py | 48 ++- python/mxnet/numpy/linalg.py | 42 ++- python/mxnet/numpy/multiarray.py | 102 ++++-- python/mxnet/symbol/numpy/_symbol.py | 20 +- python/mxnet/symbol/numpy/linalg.py | 48 ++- tests/python/unittest/test_numpy_gluon.py | 38 ++- tests/python/unittest/test_numpy_ndarray.py | 71 +++- tests/python/unittest/test_numpy_op.py | 81 ++++- 10 files changed, 751 insertions(+), 62 deletions(-) diff --git a/python/mxnet/_numpy_op_doc.py b/python/mxnet/_numpy_op_doc.py index 4e5ea74c2771..c95752a00cb6 100644 --- a/python/mxnet/_numpy_op_doc.py +++ b/python/mxnet/_numpy_op_doc.py @@ -21,7 +21,10 @@ def _np_ones_like(a): - """Return an array of ones with the same shape and type as a given array. + """ + ones_like(a) + + Return an array of ones with the same shape and type as a given array. Parameters ---------- @@ -38,7 +41,10 @@ def _np_ones_like(a): def _np_zeros_like(a): - """Return an array of zeros with the same shape and type as a given array. + """ + zeros_like(a) + + Return an array of zeros with the same shape and type as a given array. Parameters ---------- @@ -55,7 +61,10 @@ def _np_zeros_like(a): def _np_cumsum(a, axis=None, dtype=None, out=None): - """Return the cumulative sum of the elements along a given axis. + """ + cumsum(a, axis=None, dtype=None, out=None) + + Return the cumulative sum of the elements along a given axis. Parameters ---------- @@ -103,3 +112,341 @@ def _np_cumsum(a, axis=None, dtype=None, out=None): """ pass + + +def _np_repeat(a, repeats, axis=None): + """ + repeat(a, repeats, axis=None) + + Repeat elements of an array. + + Parameters + ---------- + a : ndarray + Input array. + repeats : int + The number of repetitions for each element. + axis : int, optional + The axis along which to repeat values. By default, use the + flattened input array, and return a flat output array. + + Returns + ------- + repeated_array : ndarray + Output array which has the same shape as `a`, except along + the given axis. + + Notes + ----- + Unlike the official NumPy ``repeat`` operator, this operator currently + does not support array of ints for the parameter `repeats`. + + Examples + -------- + >>> x = np.arange(4).reshape(2, 2) + >>> x + array([[0., 1.], + [2., 3.]]) + >>> np.repeat(x, repeats=3) + array([0., 0., 0., 1., 1., 1., 2., 2., 2., 3., 3., 3.]) + >>> np.repeat(x, repeats=3, axis=0) + array([[0., 1.], + [0., 1.], + [0., 1.], + [2., 3.], + [2., 3.], + [2., 3.]]) + >>> np.repeat(x, repeats=3, axis=1) + array([[0., 0., 0., 1., 1., 1.], + [2., 2., 2., 3., 3., 3.]]) + """ + pass + + +def _np_transpose(a, axes=None): + """ + transpose(a, axes=None) + + Permute the dimensions of an array. + + Parameters + ---------- + a : ndarray + Input array. + axes : list of ints, optional + By default, reverse the dimensions, + otherwise permute the axes according to the values given. + + Returns + ------- + p : ndarray + a with its axes permuted. + + Notes + ----- + This function differs from the original `numpy.transpose + `_ in + the following way(s): + + - only ndarray is accepted as valid input, python iterables are not supported + - the operator always returns an `ndarray` that does not share the memory with the input + + Examples + -------- + >>> x = np.arange(4).reshape((2,2)) + >>> x + array([[0., 1.], + [2., 3.]]) + >>> np.transpose(x) + array([[0., 2.], + [1., 3.]]) + >>> x = np.ones((1, 2, 3)) + >>> np.transpose(x, (1, 0, 2)).shape + (2, 1, 3) + """ + pass + + +def _np_dot(a, b, out=None): + """dot(a, b, out=None) + + Dot product of two arrays. Specifically, + + - If both `a` and `b` are 1-D arrays, it is inner product of vectors + + - If both `a` and `b` are 2-D arrays, it is matrix multiplication, + + - If either `a` or `b` is 0-D (scalar), it is equivalent to :func:`multiply` + and using ``np.multiply(a, b)`` or ``a * b`` is preferred. + + - If `a` is an N-D array and `b` is a 1-D array, it is a sum product over + the last axis of `a` and `b`. + + - If `a` is an N-D array and `b` is a 2-D array, it is a + sum product over the last axis of `a` and the second-to-last axis of `b`:: + + dot(a, b)[i,j,k] = sum(a[i,j,:] * b[:,k]) + + Parameters + ---------- + a : ndarray + First argument. + b : ndarray + Second argument. + + out : ndarray, optional + Output argument. It must have the same shape and type as the expected output. + + Returns + ------- + output : ndarray + Returns the dot product of `a` and `b`. If `a` and `b` are both + scalars or both 1-D arrays then a scalar is returned; otherwise + an array is returned. + If `out` is given, then it is returned + + Examples + -------- + >>> a = np.array(3) + >>> b = np.array(4) + >>> np.dot(a, b) + array(12.) + + For 2-D arrays it is the matrix product: + + >>> a = np.array([[1, 0], [0, 1]]) + >>> b = np.array([[4, 1], [2, 2]]) + >>> np.dot(a, b) + array([[4., 1.], + [2., 2.]]) + + >>> a = np.arange(3*4*5*6).reshape((3,4,5,6)) + >>> b = np.arange(5*6)[::-1].reshape((6,5)) + >>> np.dot(a, b)[2,3,2,2] + array(29884.) + >>> np.sum(a[2,3,2,:] * b[:,2]) + array(29884.) + """ + pass + + +def _np_sum(a, axis=0, dtype=None, keepdims=None, initial=None, out=None): + r""" + sum(a, axis=None, dtype=None, keepdims=_Null, initial=_Null, out=None) + + Sum of array elements over a given axis. + + Parameters + ---------- + a : ndarray + Input data. + axis : None or int, optional + Axis or axes along which a sum is performed. The default, + axis=None, will sum all of the elements of the input array. If + axis is negative it counts from the last to the first axis. + dtype : dtype, optional + The type of the returned array and of the accumulator in which the + elements are summed. The default type is float32. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + + If the default value is passed, then `keepdims` will not be + passed through to the `sum` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-classes `sum` method does not implement `keepdims` any + exceptions will be raised. + initial: Currently only supports None as input, optional + Starting value for the sum. + Currently not implemented. Please use ``None`` as input or skip this argument. + out : ndarray or None, optional + Alternative output array in which to place the result. It must have + the same shape and dtype as the expected output. + + Returns + ------- + sum_along_axis : ndarray + An ndarray with the same shape as `a`, with the specified + axis removed. If an output array is specified, a reference to + `out` is returned. + + Notes + ----- + - Input type does not support Python native iterables. + - "out" param: cannot perform auto type change. out ndarray's dtype must be the same as the expected output. + - "initial" param is not supported yet. Please use None as input. + - Arithmetic is modular when using integer types, and no error is raised on overflow. + - The sum of an empty array is the neutral element 0: + + >>> a = np.empty(1) + >>> np.sum(a) + array(0.) + + This function differs from the original `numpy.sum + `_ in + the following aspects: + + - Input type does not support Python native iterables(list, tuple, ...). + - "out" param: cannot perform auto type cast. out ndarray's dtype must be the same as the expected output. + - "initial" param is not supported yet. Please use ``None`` as input or skip it. + + Examples + -------- + >>> a = np.array([0.5, 1.5]) + >>> np.sum(a) + array(2.) + >>> a = np.array([0.5, 0.7, 0.2, 1.5]) + >>> np.sum(a, dtype=np.int32) + array(2, dtype=int32) + >>> a = np.array([[0, 1], [0, 5]]) + >>> np.sum(a) + array(6.) + >>> np.sum(a, axis=0) + array([0., 6.]) + >>> np.sum(a, axis=1) + array([1., 5.]) + + With output ndarray: + + >>> a = np.array([[0, 1], [0, 5]]) + >>> b = np.ones((2,), dtype=np.float32) + >>> np.sum(a, axis = 0, out=b) + array([0., 6.]) + >>> b + array([0., 6.]) + + If the accumulator is too small, overflow occurs: + + >>> np.ones(128, dtype=np.int8).sum(dtype=np.int8) + array(-128, dtype=int8) + """ + pass + + +def _np_copy(a, out=None): + """ + copy(a, out=None) + + Return an array copy of the given object. + + Parameters + ---------- + a : ndarray + Input data. + out : ndarray or None, optional + Alternative output array in which to place the result. It must have + the same shape and dtype as the expected output. + + Returns + ------- + arr : ndarray + Array interpretation of `a`. + + Notes + ------- + This function differs from the original `numpy.copy + `_ in + the following aspects: + + - Input type does not support Python native iterables(list, tuple, ...). + - ``out`` param: cannot perform auto broadcasting. ``out`` ndarray's shape must be the same as the expected output. + - ``out`` param: cannot perform auto type cast. ``out`` ndarray's dtype must be the same as the expected output. + - Does not support "order" parameter. + + Examples + -------- + Create an array x, with a reference y and a copy z: + + >>> x = np.array([1, 2, 3]) + >>> y = x + >>> z = np.copy(x) + + Note that, when ``x`` is modified, ``y`` is also modified, but not ``z``: + + >>> x[0] = 10 + >>> x[0] == y[0] + array([1.]) + >>> x[0] == z[0] + array([0.]) + """ + pass + + +def _np_reshape(a, newshape, order='C', out=None): + """ + reshape(a, newshape, order='C') + + Gives a new shape to an array without changing its data. + This function always returns a copy of the input array if + ``out`` is not provided. + + Parameters + ---------- + a : ndarray + Array to be reshaped. + newshape : int or tuple of ints + The new shape should be compatible with the original shape. If + an integer, then the result will be a 1-D array of that length. + One shape dimension can be -1. In this case, the value is + inferred from the length of the array and remaining dimensions. + order : {'C'}, optional + Read the elements of `a` using this index order, and place the + elements into the reshaped array using this index order. 'C' + means to read / write the elements using C-like index order, + with the last axis index changing fastest, back to the first + axis index changing slowest. Other order types such as 'F'/'A' + may be added in the future. + + Returns + ------- + reshaped_array : ndarray + It will be always a copy of the original array. This behavior is different + from the official NumPy ``reshape`` operator where views of the original array may be + generated. + + See Also + -------- + ndarray.reshape : Equivalent method. + """ + pass diff --git a/python/mxnet/gluon/loss.py b/python/mxnet/gluon/loss.py index 4cdb361eb146..45c3dee08139 100644 --- a/python/mxnet/gluon/loss.py +++ b/python/mxnet/gluon/loss.py @@ -189,9 +189,15 @@ def __init__(self, weight=None, batch_axis=0, **kwargs): def hybrid_forward(self, F, pred, label, sample_weight=None): label = _reshape_like(F, label, pred) - loss = F.abs(label - pred) + loss = F.np.abs(label - pred) if is_np_array() else F.abs(label - pred) loss = _apply_weighting(F, loss, self._weight, sample_weight) - return F.mean(loss, axis=self._batch_axis, exclude=True) + if is_np_array(): + if F is ndarray: + return F.np.mean(loss, axis=tuple(range(1, loss.ndim))) + else: + return F.npx.batch_flatten(loss).mean(axis=1) + else: + return F.mean(loss, axis=self._batch_axis, exclude=True) class SigmoidBinaryCrossEntropyLoss(Loss): diff --git a/python/mxnet/ndarray/numpy/linalg.py b/python/mxnet/ndarray/numpy/linalg.py index 0222bb45d148..36f3f21a7588 100644 --- a/python/mxnet/ndarray/numpy/linalg.py +++ b/python/mxnet/ndarray/numpy/linalg.py @@ -18,5 +18,51 @@ """Namespace for operators used in Gluon dispatched by F=ndarray.""" from __future__ import absolute_import +from . import _op as _mx_nd_np -__all__ = [] +__all__ = ['norm'] + + +def norm(x, ord=None, axis=None, keepdims=False): + r"""Matrix or vector norm. + + This function can only support Frobenius norm for now. + The Frobenius norm is given by [1]_: + + :math:`||A||_F = [\sum_{i,j} abs(a_{i,j})^2]^{1/2}` + + Parameters + ---------- + x : ndarray + Input array. + ord : {'fro'}, optional + Order of the norm. + axis : {int, 2-tuple of ints, None}, optional + If `axis` is an integer, it specifies the axis of `x` along which to + compute the vector norms. If `axis` is a 2-tuple, it specifies the + axes that hold 2-D matrices, and the matrix norms of these matrices + are computed. If `axis` is None, the norm of the whole ndarray is + returned. + + keepdims : bool, optional + If this is set to True, the axes which are normed over are left in the + result as dimensions with size one. With this option the result will + broadcast correctly against the original `x`. + + Returns + ------- + n : float or ndarray + Norm of the matrix or vector(s). + + References + ---------- + .. [1] G. H. Golub and C. F. Van Loan, *Matrix Computations*, + Baltimore, MD, Johns Hopkins University Press, 1985, pg. 15 + """ + if ord is not None and ord != 'fro': + raise ValueError('only support Frobenius norm for now, received ord={}'.format(str(ord))) + if isinstance(axis, tuple) and len(axis) > 2: + raise ValueError('Improper number of dimensions to norm') + if ord == 'fro' and x.ndim > 2 and axis is None: + raise ValueError('Improper number of dimensions to norm') + return _mx_nd_np.sqrt(_mx_nd_np.sum(x * x, axis=axis, keepdims=keepdims)) diff --git a/python/mxnet/numpy/linalg.py b/python/mxnet/numpy/linalg.py index c4109378e146..9758af47233d 100644 --- a/python/mxnet/numpy/linalg.py +++ b/python/mxnet/numpy/linalg.py @@ -18,5 +18,45 @@ """Namespace for ops used in imperative programming.""" from __future__ import absolute_import +from ..ndarray import numpy as _mx_nd_np -__all__ = [] +__all__ = ['norm'] + + +def norm(x, ord=None, axis=None, keepdims=False): + r"""Matrix or vector norm. + + This function can only support Frobenius norm for now. + The Frobenius norm is given by [1]_: + + :math:`||A||_F = [\sum_{i,j} abs(a_{i,j})^2]^{1/2}` + + Parameters + ---------- + x : ndarray + Input array. + ord : {'fro'}, optional + Order of the norm. + axis : {int, 2-tuple of ints, None}, optional + If `axis` is an integer, it specifies the axis of `x` along which to + compute the vector norms. If `axis` is a 2-tuple, it specifies the + axes that hold 2-D matrices, and the matrix norms of these matrices + are computed. If `axis` is None, the norm of the whole ndarray is + returned. + + keepdims : bool, optional + If this is set to True, the axes which are normed over are left in the + result as dimensions with size one. With this option the result will + broadcast correctly against the original `x`. + + Returns + ------- + n : float or ndarray + Norm of the matrix or vector(s). + + References + ---------- + .. [1] G. H. Golub and C. F. Van Loan, *Matrix Computations*, + Baltimore, MD, Johns Hopkins University Press, 1985, pg. 15 + """ + return _mx_nd_np.linalg.norm(x, ord, axis, keepdims) diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index 4eddb0380c09..1f8aa92f9851 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -59,6 +59,7 @@ _NDARRAY_BASIC_INDEXING = 0 _NDARRAY_ADVANCED_INDEXING = 1 + # This function is copied from ndarray.py since pylint # keeps giving false alarm error of undefined-all-variable def _new_alloc_handle(shape, ctx, delay_alloc, dtype=mx_real_t): @@ -311,14 +312,14 @@ def _prepare_value_nd(self, value, bcast_shape, squeeze_axes=None): Note: mxnet.numpy.ndarray not support NDArray as assigned value. """ if isinstance(value, numeric_types): - value_nd = full(bcast_shape, value, ctx=self.context, dtype=self.dtype) + value_nd = full(bcast_shape, value, ctx=self.ctx, dtype=self.dtype) elif isinstance(value, self.__class__): - value_nd = value.as_in_context(self.context) + value_nd = value.as_in_ctx(self.ctx) if value_nd.dtype != self.dtype: value_nd = value_nd.astype(self.dtype) else: try: - value_nd = array(value, ctx=self.context, dtype=self.dtype) + value_nd = array(value, ctx=self.ctx, dtype=self.dtype) except: raise TypeError('mxnet.np.ndarray does not support assignment with non-array-like ' 'object {} of type {}'.format(value, type(value))) @@ -329,6 +330,19 @@ def _prepare_value_nd(self, value, bcast_shape, squeeze_axes=None): squeeze_axes = tuple([ax for ax in squeeze_axes if ax < len(value_nd.shape)]) value_nd = value_nd.squeeze(axis=tuple(squeeze_axes)) + # handle the cases like the following + # a = np.zeros((3, 3)), b = np.ones((1, 1, 1, 1, 3)), a[0] = b + # b cannot broadcast directly to a[0].shape unless its leading 1-size axes are trimmed + if value_nd.ndim > len(bcast_shape): + squeeze_axes = [] + for i in range(value_nd.ndim - len(bcast_shape)): + if value_nd.shape[i] == 1: + squeeze_axes.append(i) + else: + break + if squeeze_axes: + value_nd = value_nd.squeeze(squeeze_axes) + if value_nd.shape != bcast_shape: if value_nd.size == 0: value_nd = value_nd.reshape(bcast_shape) @@ -336,7 +350,6 @@ def _prepare_value_nd(self, value, bcast_shape, squeeze_axes=None): value_nd = value_nd.broadcast_to(bcast_shape) return value_nd - def __add__(self, other): """x.__add__(y) <=> x + y""" return add(self, other) @@ -727,14 +740,14 @@ def copyto(self, other): Examples -------- - >>> x = np.ones((2,3)) - >>> y = np.zeros((2,3), ctx=mx.gpu(0)) + >>> x = np.ones((2, 3)) + >>> y = np.zeros((2, 3), ctx=npx.gpu(0)) >>> z = x.copyto(y) >>> z is y True - >>> y.asnumpy() + >>> y array([[ 1., 1., 1.], - [ 1., 1., 1.]], dtype=float32) + [ 1., 1., 1.]]) """ if isinstance(other, ndarray): if other.handle is self.handle: @@ -756,6 +769,11 @@ def argmax(self, axis=None, out=None): # pylint: disable=arguments-differ return argmax(self, axis, out) def as_in_context(self, context): + """This function has been deprecated. Please refer to ``ndarray.as_in_ctx``.""" + warnings.warn('ndarray.context has been renamed to ndarray.ctx', DeprecationWarning) + return self.as_nd_ndarray().as_in_context(context).as_np_ndarray() + + def as_in_ctx(self, ctx): """Returns an array on the target device with the same value as this array. If the target context is the same as ``self.context``, then ``self`` is @@ -771,15 +789,58 @@ def as_in_context(self, context): ndarray The target array. """ - if self.context == context: + if self.ctx == ctx: return self - return self.copyto(context) + return self.copyto(ctx) + + @property + def ctx(self): + """Device context of the array. + + Examples + -------- + >>> x = np.array([1, 2, 3, 4]) + >>> x.ctx + cpu(0) + >>> type(x.ctx) + + >>> y = np.zeros((2, 3), npx.gpu(0)) + >>> y.ctx + gpu(0) + """ + dev_typeid = ctypes.c_int() + dev_id = ctypes.c_int() + check_call(_LIB.MXNDArrayGetContext( + self.handle, ctypes.byref(dev_typeid), ctypes.byref(dev_id))) + return Context(Context.devtype2str[dev_typeid.value], dev_id.value) + + @property + def context(self): + """This function has been deprecated. Please refer to ``ndarray.ctx``.""" + warnings.warn('ndarray.context has been renamed to ndarray.ctx', DeprecationWarning) + return self.as_nd_ndarray().context def copy(self, order='C'): # pylint: disable=arguments-differ + """Return a coyp of the array, keeping the same context. + + Parameters + ---------- + order : str + The memory layout of the copy. Currently, only c-contiguous memory + layout is supported. + + Examples + -------- + >>> x = np.ones((2, 3)) + >>> y = x.copy() + >>> y + array([[ 1., 1., 1.], + [ 1., 1., 1.]]) + """ if order != 'C': raise NotImplementedError('ndarray.copy only supports order=\'C\', while ' 'received {}'.format(str(order))) - return super(ndarray, self).copy().as_np_ndarray() + return self.copyto(self.ctx) def dot(self, b, out=None): """Dot product of two arrays. @@ -787,7 +848,7 @@ def dot(self, b, out=None): return _mx_np_op.dot(self, b, out=out) def reshape(self, *args, **kwargs): # pylint: disable=arguments-differ - """Returns an array containing the same data with a new shape. + """Returns a copy of the array with a new shape. Notes ----- @@ -854,7 +915,7 @@ def broadcast_axes(self, *args, **kwargs): def repeat(self, repeats, axis=None): # pylint: disable=arguments-differ """Repeat elements of an array.""" - raise NotImplementedError + return _mx_np_op.repeat(self, repeats=repeats, axis=axis) def pad(self, *args, **kwargs): """Convenience fluent method for :py:func:`pad`. @@ -1182,22 +1243,22 @@ def var(self, axis=None, dtype=None, out=None, ddof=0, keepdims=None): # pylint def cumsum(self, axis=None, dtype=None, out=None): """Return the cumulative sum of the elements along the given axis.""" - raise NotImplementedError + return _mx_np_op.cumsum(self, axis=axis, dtype=dtype, out=out) def tolist(self): return self.asnumpy().tolist() def max(self, axis=None, out=None, keepdims=False): # pylint: disable=arguments-differ """Return the maximum along a given axis.""" - raise NotImplementedError + return _mx_np_op.max(self, axis=axis, keepdims=keepdims, out=out) - def min(self, *args, **kwargs): + def min(self, axis=None, out=None, keepdims=False): # pylint: disable=arguments-differ """Convenience fluent method for :py:func:`min`. The arguments are the same as for :py:func:`min`, with this array as data. """ - raise NotImplementedError + return _mx_np_op.min(self, axis=axis, keepdims=keepdims, out=out) def norm(self, *args, **kwargs): """Convenience fluent method for :py:func:`norm`. @@ -1549,7 +1610,7 @@ def tostype(self, stype): @set_module('mxnet.numpy') -def empty(shape, dtype=float, order='C', ctx=None): +def empty(shape, dtype=_np.float32, order='C', ctx=None): """Return a new array of given shape and type, without initializing entries. Parameters @@ -1573,7 +1634,8 @@ def empty(shape, dtype=float, order='C', ctx=None): Array of uninitialized (arbitrary) data of the given shape, dtype, and order. """ if order != 'C': - raise NotImplementedError + raise NotImplementedError('`empty` only supports order equal to `C`, while received {}' + .format(str(order))) if ctx is None: ctx = current_context() if dtype is None: @@ -1609,7 +1671,7 @@ def array(object, dtype=None, ctx=None): if isinstance(object, ndarray): dtype = object.dtype if dtype is None else dtype else: - dtype = mx_real_t if dtype is None else dtype + dtype = _np.float32 if dtype is None else dtype if not isinstance(object, (ndarray, _np.ndarray)): try: object = _np.array(object, dtype=dtype) diff --git a/python/mxnet/symbol/numpy/_symbol.py b/python/mxnet/symbol/numpy/_symbol.py index 0841c0e4d2cc..077008aba119 100644 --- a/python/mxnet/symbol/numpy/_symbol.py +++ b/python/mxnet/symbol/numpy/_symbol.py @@ -223,11 +223,11 @@ def dot(self, b, out=None): return _mx_np_op.dot(self, b, out=out) def reshape(self, *args, **kwargs): # pylint: disable=arguments-differ - """Returns an array containing the same data with a new shape. + """Returns a copy of the array with a new shape. Notes ----- - Unlike the free function `numpy.reshape`, this method on `ndarray` allows + Unlike the free function `mxnet.numpy.reshape`, this method on `ndarray` allows the elements of the shape parameter to be passed in as separate arguments. For example, ``a.reshape(10, 11)`` is equivalent to ``a.reshape((10, 11))``. @@ -289,7 +289,7 @@ def broadcast_axes(self, *args, **kwargs): def repeat(self, repeats, axis=None): # pylint: disable=arguments-differ """Repeat elements of an array.""" - raise NotImplementedError + return _mx_np_op.repeat(self, repeats=repeats, axis=axis) def pad(self, *args, **kwargs): """Convenience fluent method for :py:func:`pad`. @@ -543,19 +543,15 @@ def var(self, axis=None, dtype=None, out=None, ddof=0, keepdims=None): # pylint def cumsum(self, axis=None, dtype=None, out=None): """Return the cumulative sum of the elements along the given axis.""" - raise NotImplementedError + return _mx_np_op.cumsum(self, axis=axis, dtype=dtype, out=out) def max(self, axis=None, out=None, keepdims=False): # pylint: disable=arguments-differ """Return the maximum along a given axis.""" - raise NotImplementedError - - def min(self, *args, **kwargs): - """Convenience fluent method for :py:func:`min`. + return _mx_np_op.max(self, axis=axis, keepdims=keepdims, out=out) - The arguments are the same as for :py:func:`min`, with - this array as data. - """ - raise NotImplementedError + def min(self, axis=None, out=None, keepdims=False): # pylint: disable=arguments-differ + """Return the minimum along a given axis.""" + return _mx_np_op.min(self, axis=axis, keepdims=keepdims, out=out) def norm(self, *args, **kwargs): """Convenience fluent method for :py:func:`norm`. diff --git a/python/mxnet/symbol/numpy/linalg.py b/python/mxnet/symbol/numpy/linalg.py index 28cfd0f3806a..d1918ef8b903 100644 --- a/python/mxnet/symbol/numpy/linalg.py +++ b/python/mxnet/symbol/numpy/linalg.py @@ -18,5 +18,51 @@ """Namespace for operators used in Gluon dispatched by F=symbol.""" from __future__ import absolute_import +from . import _symbol +from . import _op as _mx_sym_np -__all__ = [] +__all__ = ['norm'] + + +def norm(x, ord=None, axis=None, keepdims=False): + r"""Matrix or vector norm. + + This function can only support Frobenius norm for now. + The Frobenius norm is given by [1]_: + + :math:`||A||_F = [\sum_{i,j} abs(a_{i,j})^2]^{1/2}` + + Parameters + ---------- + x : ndarray + Input array. + ord : {'fro'}, optional + Order of the norm. + axis : {int, 2-tuple of ints, None}, optional + If `axis` is an integer, it specifies the axis of `x` along which to + compute the vector norms. If `axis` is a 2-tuple, it specifies the + axes that hold 2-D matrices, and the matrix norms of these matrices + are computed. If `axis` is None, the norm of the whole ndarray is + returned. + + keepdims : bool, optional + If this is set to True, the axes which are normed over are left in the + result as dimensions with size one. With this option the result will + broadcast correctly against the original `x`. + + Returns + ------- + n : float or ndarray + Norm of the matrix or vector(s). + + References + ---------- + .. [1] G. H. Golub and C. F. Van Loan, *Matrix Computations*, + Baltimore, MD, Johns Hopkins University Press, 1985, pg. 15 + """ + if ord is not None and ord != 'fro': + raise ValueError('only support Frobenius norm for now, received ord={}'.format(str(ord))) + if isinstance(axis, tuple) and len(axis) > 2: + raise ValueError('Improper number of dimensions to norm') + # TODO(junwu): When ord = 'fro', axis = None, and x.ndim > 2, raise exception + return _symbol.sqrt(_mx_sym_np.sum(x * x, axis=axis, keepdims=keepdims)) diff --git a/tests/python/unittest/test_numpy_gluon.py b/tests/python/unittest/test_numpy_gluon.py index e96f829a8580..62ea38fc0c13 100644 --- a/tests/python/unittest/test_numpy_gluon.py +++ b/tests/python/unittest/test_numpy_gluon.py @@ -19,9 +19,11 @@ from __future__ import absolute_import from __future__ import division +import numpy as _np import mxnet as mx from mxnet import gluon, autograd, np -from mxnet.test_utils import use_np +from mxnet.test_utils import use_np, assert_almost_equal +from common import with_seed def test_create_np_param(): @@ -108,6 +110,40 @@ def hybrid_forward(self, F, pred, label): trainer.step(1) +@with_seed() +@use_np +def test_np_loss_ndarray(): + # Ported from test_loss.test_loss_ndarray + output = np.array([1, 2, 3, 4]) + label = np.array([1, 3, 5, 7]) + weighting = np.array([0.5, 1, 0.5, 1]) + + loss = gluon.loss.L1Loss() + assert np.sum(loss(output, label)) == 6. + loss = gluon.loss.L1Loss(weight=0.5) + assert np.sum(loss(output, label)) == 3. + loss = gluon.loss.L1Loss() + assert np.sum(loss(output, label, weighting)) == 5. + + loss = gluon.loss.L2Loss() + assert np.sum(loss(output, label)) == 7. + loss = gluon.loss.L2Loss(weight=0.25) + assert np.sum(loss(output, label)) == 1.75 + loss = gluon.loss.L2Loss() + assert np.sum(loss(output, label, weighting)) == 6 + + output = np.array([[0, 2], [1, 4]]) + label = np.array([0, 1]) + weighting = np.array([[0.5], [1.0]]) + + loss = gluon.loss.SoftmaxCrossEntropyLoss() + L = loss(output, label).asnumpy() + assert_almost_equal(L, _np.array([2.12692809, 0.04858733]), use_broadcast=False) + + L = loss(output, label, weighting).asnumpy() + assert_almost_equal(L, _np.array([1.06346405, 0.04858733]), use_broadcast=False) + + if __name__ == '__main__': import nose nose.runmodule() diff --git a/tests/python/unittest/test_numpy_ndarray.py b/tests/python/unittest/test_numpy_ndarray.py index 883060466836..bffa7a00dccb 100644 --- a/tests/python/unittest/test_numpy_ndarray.py +++ b/tests/python/unittest/test_numpy_ndarray.py @@ -26,12 +26,45 @@ from mxnet.test_utils import same, assert_almost_equal, rand_shape_nd, rand_ndarray, retry, assert_exception, use_np from common import with_seed, TemporaryDirectory from mxnet.test_utils import verify_generator, gen_buckets_probs_with_ppf +from mxnet.ndarray.ndarray import py_slice +from mxnet.base import integer_types import scipy.stats as ss @with_seed() @use_np -def test_array_creation(): +def test_np_empty(): + dtypes = [np.int8, np.int32, np.float16, np.float32, np.float64, None] + expected_dtypes = [np.int8, np.int32, np.float16, np.float32, np.float64, np.float32] + orders = ['C', 'F', 'A'] + shapes = [ + (), + 0, + (0,), + (0, 0), + 2, + (2,), + (3, 0), + (4, 5), + (1, 1, 1, 1), + ] + ctxes = [npx.current_context(), None] + for dtype, expected_dtype in zip(dtypes, expected_dtypes): + for shape in shapes: + for order in orders: + for ctx in ctxes: + if order == 'C': + ret = np.empty(shape, dtype, order, ctx) + assert ret.dtype == expected_dtype + assert ret.shape == shape if isinstance(shape, tuple) else (shape,) + assert ret.ctx == npx.current_context() + else: + assert_exception(np.empty, NotImplementedError, shape, dtype, order, ctx) + + +@with_seed() +@use_np +def test_np_array_creation(): dtypes = [_np.int8, _np.int32, _np.float16, _np.float32, _np.float64, None] objects = [ [], @@ -54,7 +87,7 @@ def test_array_creation(): @with_seed() @use_np -def test_zeros(): +def test_np_zeros(): # test np.zeros in Gluon class TestZeros(HybridBlock): def __init__(self, shape, dtype=None): @@ -102,7 +135,7 @@ def check_zero_array_creation(shape, dtype): @with_seed() @use_np -def test_ones(): +def test_np_ones(): # test np.ones in Gluon class TestOnes(HybridBlock): def __init__(self, shape, dtype=None): @@ -149,7 +182,7 @@ def check_ones_array_creation(shape, dtype): @with_seed() -def test_ndarray_binary_element_wise_ops(): +def test_np_ndarray_binary_element_wise_ops(): np_op_map = { '+': _np.add, '*': _np.multiply, @@ -304,7 +337,7 @@ def check_binary_op_result(shape1, shape2, op, dtype=None): @with_seed() -def test_hybrid_block_multiple_outputs(): +def test_np_hybrid_block_multiple_outputs(): @use_np class TestAllNumpyOutputs(HybridBlock): def hybrid_forward(self, F, x, *args, **kwargs): @@ -338,7 +371,7 @@ def hybrid_forward(self, F, x, *args, **kwargs): @with_seed() @use_np -def test_grad_ndarray_type(): +def test_np_grad_ndarray_type(): data = np.array(2, dtype=_np.float32) data.attach_grad() assert type(data.grad) == np.ndarray @@ -379,7 +412,7 @@ def test_np_ndarray_copy(): def test_np_ndarray_indexing(): def np_int(index, int_type=np.int32): """ - Helper function for testing indexing that converts slices to slices of ints or None, and tuples to + Helper function for testing indexing that converts slices to slices of ints or None, and tuples to tuples of ints or None. """ def convert(num): @@ -401,7 +434,7 @@ def convert(num): else: assert False - # Copied from test_ndarray.py. Under construction. + # Copied from test_ndarray.py. Under construction. def test_getitem(np_array, index): np_index = index if type(index) == mx.nd.NDArray: # use of NDArray is prohibited @@ -439,6 +472,13 @@ def assert_same(np_array, np_index, mx_array, mx_index, mx_value, np_value=None) assert same(np_array, mx_array.asnumpy()) + def _is_basic_index(index): + if isinstance(index, (integer_types, py_slice)): + return True + if isinstance(index, tuple) and all(isinstance(i, (integer_types, py_slice)) for i in index): + return True + return False + np_index = index # keep this native numpy type if isinstance(index, np.ndarray): np_index = index.asnumpy() @@ -467,6 +507,13 @@ def assert_same(np_array, np_index, mx_array, mx_index, mx_value, np_value=None) assert_same(np_array, np_index, mx_array, index, np.array(np_value)) # test native numpy array with broadcast assert_same(np_array, np_index, mx_array, index, np_value) + + # test value shape are expanded to be longer than index array's shape + # this is currently only supported in basic indexing + if _is_basic_index(index): + expanded_value_shape = (1, 1, 1) + np_value.shape + assert_same(np_array, np_index, mx_array, index, np.array(np_value.reshape(expanded_value_shape))) + assert_same(np_array, np_index, mx_array, index, np_value.reshape(expanded_value_shape)) # test list with broadcast assert_same(np_array, np_index, mx_array, index, [_np.random.randint(low=-10000, high=0)] * indexed_array_shape[-1]) @@ -664,18 +711,18 @@ def test_setitem_autograd(np_array, index): test_setitem(np_array, index) test_getitem_autograd(np_array, index) test_setitem_autograd(np_array, index) - + # Test indexing to zero-size tensors index_list = [ - (slice(0, 0), slice(0, 0), 1, 2), - (slice(0, 0), slice(0, 0), slice(0, 0), slice(0, 0)), + (slice(0, 0), slice(0, 0), 1, 2), + (slice(0, 0), slice(0, 0), slice(0, 0), slice(0, 0)), ] for index in index_list: test_getitem(np_array, index) test_setitem(np_array, index) test_getitem_autograd(np_array, index) test_setitem_autograd(np_array, index) - + # test zero-size tensors get and setitem shapes_indices = [ ((0), [slice(None, None, None)]), diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index 0f7355b7ef13..c5b0907fb7a8 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -288,7 +288,7 @@ def __init__(self, axis=None, keepdims=False): self._keepdims = keepdims def hybrid_forward(self, F, a, *args, **kwargs): - return F.np.max(a, axis=self._axis, keepdims=self._keepdims) + return a.max(axis=self._axis, keepdims=self._keepdims) class TestMin(HybridBlock): def __init__(self, axis=None, keepdims=False): @@ -297,7 +297,7 @@ def __init__(self, axis=None, keepdims=False): self._keepdims = keepdims def hybrid_forward(self, F, a, *args, **kwargs): - return F.np.min(a, axis=self._axis, keepdims=self._keepdims) + return a.min(axis=self._axis, keepdims=self._keepdims) def is_int(dtype): return 'int' == dtype @@ -326,12 +326,8 @@ def get_grad(axis, func_name): raise ValueError('axis should be int or None or ()') def _test_np_exception(func, shape, dim): - x = _np.random.uniform(-1.0, 1.0, shape) - x = mx.nd.array(x).as_np_ndarray() - if func == 'max': - out = mx.np.max(x) - else: - out = mx.np.min(x) + x = np.random.uniform(-1.0, 1.0, shape) + out = getattr(x, func)() assert out.ndim == dim, 'dimension mismatch, output.ndim={}, dim={}'.format(output.ndim, dim) in_data_dim = random.choice([2, 3, 4]) @@ -1620,7 +1616,7 @@ def __init__(self, axis=None, dtype=None): self._dtype = dtype def hybrid_forward(self, F, a): - return F.np.cumsum(a, axis=self._axis, dtype=self._dtype) + return a.cumsum(axis=self._axis, dtype=self._dtype) shapes = [(2, 3, 4), (2, 0, 3), ()] for hybridize in [True, False]: @@ -1790,6 +1786,73 @@ def hybrid_forward(self, F, x): assert mx_out.shape == np_out.shape +@with_seed() +@use_np +def test_np_repeat(): + config = [ + ((), 2, None), + ((), 0, None), + ((4, 2), 2, None), + ((4, 2), 2, 0), + ((4, 2), 2, 1), + ((4, 2), 2, -1), + ] + + class TestRepeat(HybridBlock): + def __init__(self, repeats, axis=None): + super(TestRepeat, self).__init__() + self._repeats = repeats + self._axis = axis + + def hybrid_forward(self, F, x): + return x.repeat(self._repeats, self._axis) + + for shape, repeats, axis in config: + data_np = _np.random.randint(low=0, high=1000, size=shape) + data_mx = np.array(data_np, dtype=data_np.dtype) + ret_np = data_np.repeat(repeats, axis) + ret_mx = data_mx.repeat(repeats, axis) + assert same(ret_mx.asnumpy(), ret_np) + + net = TestRepeat(repeats, axis) + for hybrid in [False, True]: + if hybrid: + net.hybridize() + ret_mx = net(data_mx) + assert same(ret_mx.asnumpy(), ret_np) + + +@with_seed() +@use_np +def test_np_linalg_norm(): + @use_np + class TestLinalgNorm(HybridBlock): + def __init__(self, ord=None, axis=None, keepdims=False): + super(TestLinalgNorm, self).__init__() + self._ord = ord + self._axis = axis + self._keepdims = keepdims + + def hybrid_forward(self, F, x): + return F.np.linalg.norm(x, ord=self._ord, axis=self._axis, keepdims=self._keepdims) + + a = np.arange(5 * 6 * 7 * 8).reshape((5, 6, 7, 8)) + ords = [None, 'fro'] + axes = [None, (0, 2), (1, 0), (1, 2)] + for ord in ords: + for axis in axes: + if ord == 'fro' and axis is None and a.ndim > 2: + continue + for keepdims in [False, True]: + for hybridize in [False, True]: + net = TestLinalgNorm(ord, axis, keepdims) + if hybridize: + net.hybridize() + mx_ret = net(a) + np_ret = _np.linalg.norm(a.asnumpy(), ord=ord, axis=axis, keepdims=keepdims) + assert_almost_equal(mx_ret.asnumpy(), np_ret, atol=1e-5, rtol=1e-4) + + if __name__ == '__main__': import nose nose.runmodule() From 90091b155d6f53c070e3c406f9edc69f38d02e96 Mon Sep 17 00:00:00 2001 From: Haozheng Fan Date: Mon, 16 Sep 2019 02:57:51 +0800 Subject: [PATCH 318/813] [Numpy] Numpy copysign (#15851) * add numpy compatible copysign * fix scalar op registration error * add test --- python/mxnet/ndarray/numpy/_op.py | 53 ++++++++- python/mxnet/numpy/multiarray.py | 53 ++++++++- python/mxnet/symbol/numpy/_symbol.py | 36 +++++- src/operator/mshadow_op.h | 10 ++ .../numpy/np_elemwise_broadcast_op.cc | 36 ++++++ .../numpy/np_elemwise_broadcast_op.cu | 21 ++++ src/operator/operator_tune.cc | 5 + tests/python/unittest/test_numpy_op.py | 105 ++++++++++++++++++ 8 files changed, 316 insertions(+), 3 deletions(-) diff --git a/python/mxnet/ndarray/numpy/_op.py b/python/mxnet/ndarray/numpy/_op.py index 671345c9a546..b8e4f3f28b86 100644 --- a/python/mxnet/ndarray/numpy/_op.py +++ b/python/mxnet/ndarray/numpy/_op.py @@ -33,7 +33,7 @@ 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'linspace', 'expand_dims', 'tile', 'arange', 'split', 'concatenate', 'stack', 'mean', - 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices'] + 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices', 'copysign'] @set_module('mxnet.ndarray.numpy') @@ -2432,3 +2432,54 @@ def indices(dimensions, dtype=_np.int32, ctx=None): else: raise ValueError("The dimensions must be sequence of ints") # pylint: enable=redefined-outer-name + + +@set_module('mxnet.ndarray.numpy') +def copysign(x1, x2, out=None): + r"""copysign(x1, x2, out=None) + + Change the sign of x1 to that of x2, element-wise. + + If `x2` is a scalar, its sign will be copied to all elements of `x1`. + + Parameters + ---------- + x1 : ndarray or scalar + Values to change the sign of. + x2 : ndarray or scalar + The sign of `x2` is copied to `x1`. + out : ndarray or None, optional + A location into which the result is stored. It must be of the + right shape and right type to hold the output. If not provided + or `None`,a freshly-allocated array is returned. + + Returns + ------- + out : ndarray or scalar + The values of `x1` with the sign of `x2`. + This is a scalar if both `x1` and `x2` are scalars. + + Notes + ------- + This function differs from the original `numpy.copysign + `_ in + the following aspects: + + - ``where`` param is not supported. + + Examples + -------- + >>> np.copysign(1.3, -1) + -1.3 + >>> 1/np.copysign(0, 1) + inf + >>> 1/np.copysign(0, -1) + -inf + + >>> a = np.array([-1, 0, 1]) + >>> np.copysign(a, -1.1) + array([-1., -0., -1.]) + >>> np.copysign(a, np.arange(3)-1) + array([-1., 0., 1.]) + """ + return _ufunc_helper(x1, x2, _npi.copysign, _np.copysign, _npi.copysign_scalar, _npi.rcopysign_scalar, out) diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index 1f8aa92f9851..632cfadf86a6 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -52,7 +52,7 @@ 'degrees', 'log2', 'log1p', 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'linspace', 'expand_dims', 'tile', 'arange', 'split', 'concatenate', - 'stack', 'mean', 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices'] + 'stack', 'mean', 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices', 'copysign'] # Return code for dispatching indexing function call _NDARRAY_UNSUPPORTED_INDEXING = -1 @@ -3935,3 +3935,54 @@ def indices(dimensions, dtype=_np.int32, ctx=None): """ return _mx_nd_np.indices(dimensions=dimensions, dtype=dtype, ctx=ctx) # pylint: enable=redefined-outer-name + + +@set_module('mxnet.numpy') +def copysign(x1, x2, out=None): + r"""copysign(x1, x2, out=None) + + Change the sign of x1 to that of x2, element-wise. + + If `x2` is a scalar, its sign will be copied to all elements of `x1`. + + Parameters + ---------- + x1 : ndarray or scalar + Values to change the sign of. + x2 : ndarray or scalar + The sign of `x2` is copied to `x1`. + out : ndarray or None, optional + A location into which the result is stored. It must be of the + right shape and right type to hold the output. If not provided + or `None`,a freshly-allocated array is returned. + + Returns + ------- + out : ndarray or scalar + The values of `x1` with the sign of `x2`. + This is a scalar if both `x1` and `x2` are scalars. + + Notes + ------- + This function differs from the original `numpy.copysign + `_ in + the following aspects: + + - ``where`` param is not supported. + + Examples + -------- + >>> np.copysign(1.3, -1) + -1.3 + >>> 1/np.copysign(0, 1) + inf + >>> 1/np.copysign(0, -1) + -inf + + >>> a = np.array([-1, 0, 1]) + >>> np.copysign(a, -1.1) + array([-1., -0., -1.]) + >>> np.copysign(a, np.arange(3)-1) + array([-1., 0., 1.]) + """ + return _mx_nd_np.copysign(x1, x2, out=out) diff --git a/python/mxnet/symbol/numpy/_symbol.py b/python/mxnet/symbol/numpy/_symbol.py index 077008aba119..5a38f81f9102 100644 --- a/python/mxnet/symbol/numpy/_symbol.py +++ b/python/mxnet/symbol/numpy/_symbol.py @@ -35,7 +35,7 @@ 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'linspace', 'expand_dims', 'tile', 'arange', 'split', 'concatenate', 'stack', 'mean', - 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices'] + 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices', 'copysign'] def _num_outputs(sym): @@ -2744,4 +2744,38 @@ def indices(dimensions, dtype=_np.int32, ctx=None): # pylint: enable=redefined-outer-name +@set_module('mxnet.symbol.numpy') +def copysign(x1, x2, out=None): + r"""copysign(x1, x2, out=None) + + Change the sign of x1 to that of x2, element-wise. + + If `x2` is a scalar, its sign will be copied to all elements of `x1`. + + Parameters + ---------- + x1 : _Symbol or scalar + Values to change the sign of. + x2 : _Symbol or scalar + The sign of `x2` is copied to `x1`. + out : _Symbol or None + Dummy parameter to keep the consistency with the ndarray counterpart. + + Returns + ------- + out : _Symbol + The values of `x1` with the sign of `x2`. + This is a scalar if both `x1` and `x2` are scalars. + + Notes + ------- + This function differs from the original `numpy.copysign + `_ in + the following aspects: + + - ``where`` param is not supported. + """ + return _ufunc_helper(x1, x2, _npi.copysign, _np.copysign, _npi.copysign_scalar, _npi.rcopysign_scalar, out) + + _set_np_symbol_class(_Symbol) diff --git a/src/operator/mshadow_op.h b/src/operator/mshadow_op.h index 616192e4af57..f3d24b2c119e 100644 --- a/src/operator/mshadow_op.h +++ b/src/operator/mshadow_op.h @@ -417,6 +417,16 @@ MXNET_BINARY_MATH_OP(rdiv, math::id(b) / math::id(a)); MXNET_BINARY_MATH_OP(rdiv_grad, -math::id(b) / math::sqr(a)); +MXNET_BINARY_MATH_OP(copysign, (a >= 0 && b >= 0) || (a < 0 && b < 0) ? a : -a); + +MXNET_BINARY_MATH_OP(copysign_grad, (a >= 0 && b >= 0) || (a < 0 && b < 0) ? 1: -1); + +MXNET_BINARY_MATH_OP(copysign_rgrad, 0); + +MXNET_BINARY_MATH_OP(rcopysign, (b >= 0 && a >= 0) || (b < 0 && a < 0) ? b : -b); + +MXNET_BINARY_MATH_OP(rcopysign_grad, 0); + struct mod : public mxnet_op::tunable { template MSHADOW_XINLINE static typename enable_if::value, DType>::type diff --git a/src/operator/numpy/np_elemwise_broadcast_op.cc b/src/operator/numpy/np_elemwise_broadcast_op.cc index 697657d84dd5..a9254e8a7d02 100644 --- a/src/operator/numpy/np_elemwise_broadcast_op.cc +++ b/src/operator/numpy/np_elemwise_broadcast_op.cc @@ -76,6 +76,26 @@ MXNET_OPERATOR_REGISTER_BINARY_BROADCAST(_npi_power) .set_attr("FCompute", BinaryBroadcastCompute) .set_attr("FGradient", ElemwiseGradUseIn{"_backward_broadcast_power"}); +MXNET_OPERATOR_REGISTER_BINARY_BROADCAST(_npi_copysign) +.describe(R"code()code" ADD_FILELINE) +.set_attr("FCompute", BinaryBroadcastCompute) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_npi_copysign"}); + +NNVM_REGISTER_OP(_backward_npi_copysign) +.set_num_inputs(3) +.set_num_outputs(2) +.set_attr("TIsBackward", true) +.set_attr("FInplaceOption", + [](const NodeAttrs& attrs){ + return std::vector >{{0, 1}}; + }) +.set_attr("FResourceRequest", + [](const NodeAttrs& attrs) { + return std::vector{ResourceRequest::kTempSpace}; + }) +.set_attr("FCompute", BinaryBroadcastBackwardUseIn); + MXNET_OPERATOR_REGISTER_NP_BINARY_SCALAR(_npi_add_scalar) .set_attr("FCompute", BinaryScalarOp::Compute) .set_attr("FGradient", ElemwiseGradUseNone{"_copy"}); @@ -108,5 +128,21 @@ MXNET_OPERATOR_REGISTER_NP_BINARY_SCALAR(_npi_rpower_scalar) .set_attr("FCompute", BinaryScalarOp::Compute) .set_attr("FGradient", ElemwiseGradUseOut{"_backward_rpower_scalar"}); +MXNET_OPERATOR_REGISTER_NP_BINARY_SCALAR(_npi_copysign_scalar) +.set_attr("FCompute", BinaryScalarOp::Compute) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_npi_copysign_scalar"}); + +MXNET_OPERATOR_REGISTER_NP_BINARY_SCALAR(_npi_rcopysign_scalar) +.set_attr("FCompute", BinaryScalarOp::Compute) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_npi_rcopysign_scalar"}); + +MXNET_OPERATOR_REGISTER_NP_BINARY_SCALAR(_backward_npi_copysign_scalar) +.set_attr("FCompute", + BinaryScalarOp::Backward); + +MXNET_OPERATOR_REGISTER_NP_BINARY_SCALAR(_backward_npi_rcopysign_scalar) +.set_attr("FCompute", + BinaryScalarOp::Backward); + } // namespace op } // namespace mxnet diff --git a/src/operator/numpy/np_elemwise_broadcast_op.cu b/src/operator/numpy/np_elemwise_broadcast_op.cu index ac8def2af2c2..ecf8e8531334 100644 --- a/src/operator/numpy/np_elemwise_broadcast_op.cu +++ b/src/operator/numpy/np_elemwise_broadcast_op.cu @@ -42,6 +42,13 @@ NNVM_REGISTER_OP(_npi_mod) NNVM_REGISTER_OP(_npi_power) .set_attr("FCompute", BinaryBroadcastCompute); +NNVM_REGISTER_OP(_npi_copysign) +.set_attr("FCompute", BinaryBroadcastCompute); + +NNVM_REGISTER_OP(_backward_npi_copysign) +.set_attr("FCompute", BinaryBroadcastBackwardUseIn); + NNVM_REGISTER_OP(_npi_add_scalar) .set_attr("FCompute", BinaryScalarOp::Compute); @@ -66,5 +73,19 @@ NNVM_REGISTER_OP(_npi_power_scalar) NNVM_REGISTER_OP(_npi_rpower_scalar) .set_attr("FCompute", BinaryScalarOp::Compute); +NNVM_REGISTER_OP(_npi_copysign_scalar) +.set_attr("FCompute", BinaryScalarOp::Compute); + +NNVM_REGISTER_OP(_npi_rcopysign_scalar) +.set_attr("FCompute", BinaryScalarOp::Compute); + +NNVM_REGISTER_OP(_backward_npi_copysign_scalar) +.set_attr("FCompute", + BinaryScalarOp::Backward); + +NNVM_REGISTER_OP(_backward_npi_rcopysign_scalar) +.set_attr("FCompute", + BinaryScalarOp::Backward); + } // namespace op } // namespace mxnet diff --git a/src/operator/operator_tune.cc b/src/operator/operator_tune.cc index 98ce14e7bf05..51595254cafc 100644 --- a/src/operator/operator_tune.cc +++ b/src/operator/operator_tune.cc @@ -328,6 +328,11 @@ IMPLEMENT_BINARY_WORKLOAD_FWD(mxnet::op::mshadow_op::elu); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::power_grad); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::rpower_grad); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::power_rgrad); // NOLINT() +IMPLEMENT_BINARY_WORKLOAD_FWD(mxnet::op::mshadow_op::copysign); // NOLINT() +IMPLEMENT_BINARY_WORKLOAD_FWD(mxnet::op::mshadow_op::rcopysign); // NOLINT() +IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::copysign_grad); // NOLINT() +IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::copysign_rgrad); // NOLINT() +IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::rcopysign_grad); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::xelu_grad); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::gelu_grad); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::prelu_grad); // NOLINT() diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index c5b0907fb7a8..1f2af8dfa4a9 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -1853,6 +1853,111 @@ def hybrid_forward(self, F, x): assert_almost_equal(mx_ret.asnumpy(), np_ret, atol=1e-5, rtol=1e-4) +@with_seed() +@use_np +def test_np_copysign(): + class TestCopysign(HybridBlock): + def __init__(self): + super(TestCopysign, self).__init__() + + def hybrid_forward(self, F, a1, a2): + return F.np.copysign(a1, a2) + + def get_grad(a1, a2): + sign = _np.logical_or(_np.logical_and(a1 < 0, a2 < 0), + _np.logical_and(a1 >= 0, a2 >= 0)) + sign = 2 * sign.astype(int) - 1 + sign = sign.reshape(-1, *a1.shape) + sign = _np.sum(sign, axis=0) + return sign, _np.zeros_like(a2) + + def get_grad_left(a1, a2): + sign = _np.logical_or(_np.logical_and(a1 < 0, a2 < 0), + _np.logical_and(a1 >= 0, a2 >= 0)) + sign = 2 * sign.astype(int) - 1 + sign = sign.reshape(a1.shape) + return sign + + def get_grad_right(a1, a2): + return _np.zeros_like(a2) + + shapes = [ + (), + (1), + (2, 1), + (3, 2, 1), + (4, 3, 2, 1), + (2, 4, 3, 2, 1) + ] + types = ['float16', 'float32', 'float64', 'int8', 'int32', 'int64'] + for a1shape in shapes: + for a2shape in shapes: + for hybridize in [True, False]: + for dtype in types: + test_copysign = TestCopysign() + if hybridize: + test_copysign.hybridize() + rtol = 1e-3 + atol = 1e-5 + a1_np = _np.array(_np.random.uniform(-1.0, 1.0, a1shape), dtype=dtype) + a2_np = _np.array(_np.random.uniform(-1.0, 1.0, a2shape), dtype=dtype) + a1 = np.array(a1_np, dtype=dtype) + a2 = np.array(a2_np, dtype=dtype) + a1.attach_grad() + a2.attach_grad() + expected_np = _np.copysign(a1_np, a2_np) + with mx.autograd.record(): + mx_out = test_copysign(a1, a2) + assert mx_out.shape == expected_np.shape + assert_almost_equal(mx_out.asnumpy(), expected_np, rtol=rtol, atol=atol) + + # Test gradient + mx_out.backward() + a1_grad, a2_grad = get_grad(a1_np, a2_np) + assert_almost_equal(a1.grad.asnumpy(), a1_grad, rtol=rtol, atol=atol) + assert_almost_equal(a2.grad.asnumpy(), a2_grad, rtol=rtol, atol=atol) + + # Test imperative once again + mx_out = np.copysign(a1, a2) + expected_np = _np.copysign(a1_np, a2_np) + assert_almost_equal(mx_out.asnumpy(), expected_np, rtol=rtol, atol=atol) + + types = ['float16', 'float32', 'float64'] + for x_shape in shapes: + for dtype in types: + # Test left + x_np = _np.array(_np.random.uniform(-2.0, 2.0, x_shape), dtype=dtype) + scalar = _np.random.uniform(-2.0, 2.0) + x = np.array(x_np, dtype=dtype) + x.attach_grad() + expected_np = _np.copysign(x_np, scalar) + with mx.autograd.record(): + mx_out = np.copysign(x, scalar) + assert mx_out.shape == expected_np.shape + assert_almost_equal(mx_out.asnumpy(), expected_np, rtol=rtol, atol=atol) + + # Test gradient + mx_out.backward() + x_grad = get_grad_left(x_np, scalar) + assert_almost_equal(x.grad.asnumpy(), x_grad, rtol=rtol, atol=atol) + + # Test right + x_np = _np.array(_np.random.uniform(-2.0, 2.0, x_shape), dtype=dtype) + scalar = _np.random.uniform(-2.0, 2.0) + x = np.array(x_np, dtype=dtype) + x.attach_grad() + expected_np = _np.copysign(scalar, x_np) + with mx.autograd.record(): + mx_out = np.copysign(scalar, x) + assert mx_out.shape == expected_np.shape + assert_almost_equal(mx_out.asnumpy(), expected_np, rtol=rtol, atol=atol) + + # Test gradient + mx_out.backward() + x_grad = get_grad_right(scalar, x_np) + assert_almost_equal(x.grad.asnumpy(), x_grad, rtol=rtol, atol=atol) + + if __name__ == '__main__': import nose nose.runmodule() From 5c136c9a312c40d642c13c36367ab4157ad91f81 Mon Sep 17 00:00:00 2001 From: Zhennan Qin Date: Mon, 16 Sep 2019 00:56:17 -0500 Subject: [PATCH 319/813] Update dmlc-core (#16149) Change-Id: I472fb7bbffc16ed8c36494ab49838b08c59b2f12 --- 3rdparty/dmlc-core | 2 +- amalgamation/amalgamation.py | 3 ++- .../src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/3rdparty/dmlc-core b/3rdparty/dmlc-core index f1ff6cc117f4..9088d2ee02cd 160000 --- a/3rdparty/dmlc-core +++ b/3rdparty/dmlc-core @@ -1 +1 @@ -Subproject commit f1ff6cc117f4e95169a9f62be549c8fe3e15c20f +Subproject commit 9088d2ee02cdfe393fd0569af4aefebe94f8b105 diff --git a/amalgamation/amalgamation.py b/amalgamation/amalgamation.py index fef54aaecba4..5f825de77483 100644 --- a/amalgamation/amalgamation.py +++ b/amalgamation/amalgamation.py @@ -30,7 +30,8 @@ 'opencv2/opencv.hpp', 'sys/stat.h', 'sys/types.h', 'cuda.h', 'cuda_fp16.h', 'omp.h', 'onnx/onnx.pb.h', 'execinfo.h', 'packet/sse-inl.h', 'emmintrin.h', 'thrust/device_vector.h', 'cusolverDn.h', 'internal/concurrentqueue_internal_debug.h', 'relacy/relacy_std.hpp', - 'relacy_shims.h', 'ittnotify.h', 'shared_mutex', 'nvToolsExt.h' + 'relacy_shims.h', 'ittnotify.h', 'shared_mutex', 'nvToolsExt.h', 'dmlc/build_config.h', + 'sys/isa_defs.h' ] minimum = int(sys.argv[6]) if len(sys.argv) > 5 else 0 diff --git a/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala b/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala index c984d07143ef..8fb95bfeb83a 100644 --- a/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala +++ b/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala @@ -55,8 +55,8 @@ private[mxnet] object CToScalaUtils { case "double" | "doubleorNone" => types("double") case "string" => "String" case "boolean" | "booleanorNone" => types("bool") - case "tupleof" | "tupleof" | "tupleof" | "tupleof" | - "tupleof<>" | "ptr" | "" => "Any" + case "tupleof"| "tupleof" | "tupleof" | "tupleof" | + "tupleof" | "tupleof<>" | "ptr" | "" => "Any" case default => throw new IllegalArgumentException( s"Invalid type for args: $default\nString argType: $argType\nargName: $argName") } From 18d145c7f1c0f4af1470483fcdd85d5f761c894d Mon Sep 17 00:00:00 2001 From: Xingjian Shi Date: Sun, 15 Sep 2019 23:52:35 -0700 Subject: [PATCH 320/813] use 1E-4 in groupnorm test(#16169) --- tests/python/unittest/test_operator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index 40763fabef19..2e7cc3ce7504 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -1943,7 +1943,7 @@ def np_groupnorm_grad(ograd, data, gamma, beta, mean, std, num_groups, eps): num_groups=num_groups, eps=eps, output_mean_var=True) check_symbolic_forward(mx_sym, [mx_data, mx_gamma, mx_beta], [np_out, np_mean, np_std], rtol=1e-2 if dtype == np.float16 else 1e-3, - atol=5e-3 if dtype == np.float16 else 1e-5, dtype=dtype) + atol=5e-3 if dtype == np.float16 else 1e-4, dtype=dtype) mx_sym = mx.sym.GroupNorm(data=data_sym, gamma=gamma_sym, beta=beta_sym, num_groups=num_groups, eps=eps, output_mean_var=False) np_ograd = np.random.uniform(-1.0, 1.0, dshape).astype(dtype) @@ -1956,7 +1956,7 @@ def np_groupnorm_grad(ograd, data, gamma, beta, mean, std, num_groups, eps): check_symbolic_backward(mx_sym, [mx_data, mx_gamma, mx_beta], [mx.nd.array(np_ograd)], [np_data_grad, np_gamma_grad, np_beta_grad], rtol=1e-2 if dtype == np.float16 else 1e-3, - atol=5e-2 if dtype == np.float16 else 1e-5, dtype=dtype) + atol=5e-2 if dtype == np.float16 else 1e-4, dtype=dtype) @with_seed() From 5ed5689b6a769708090cd4df47f8faf995cf4f4e Mon Sep 17 00:00:00 2001 From: tingying <18640449+tingying2020@users.noreply.github.com> Date: Mon, 16 Sep 2019 21:29:55 +0800 Subject: [PATCH 321/813] numpy operator ravel, derive from reshape (#16016) * it is the same as reshape(x, -1) * register reshape with prefix _npi_ * fix format error * edit examples in doc * fix error in review * add out in wrapper * remove out * test data type and add order * change order check * remove redundant test and add docstring about order --- python/mxnet/ndarray/numpy/_op.py | 56 +++++++++++++++++++++++++- python/mxnet/numpy/multiarray.py | 49 +++++++++++++++++++++- python/mxnet/symbol/numpy/_symbol.py | 42 ++++++++++++++++++- src/operator/numpy/np_matrix_op.cc | 1 + tests/python/unittest/test_numpy_op.py | 33 +++++++++++++++ 5 files changed, 178 insertions(+), 3 deletions(-) diff --git a/python/mxnet/ndarray/numpy/_op.py b/python/mxnet/ndarray/numpy/_op.py index b8e4f3f28b86..163d90835e2a 100644 --- a/python/mxnet/ndarray/numpy/_op.py +++ b/python/mxnet/ndarray/numpy/_op.py @@ -33,7 +33,8 @@ 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'linspace', 'expand_dims', 'tile', 'arange', 'split', 'concatenate', 'stack', 'mean', - 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices', 'copysign'] + 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices', 'copysign', + 'ravel'] @set_module('mxnet.ndarray.numpy') @@ -2483,3 +2484,56 @@ def copysign(x1, x2, out=None): array([-1., 0., 1.]) """ return _ufunc_helper(x1, x2, _npi.copysign, _np.copysign, _npi.copysign_scalar, _npi.rcopysign_scalar, out) + + +@set_module('mxnet.ndarray.numpy') +def ravel(x, order='C'): + r""" + ravel(x) + + Return a contiguous flattened array. + A 1-D array, containing the elements of the input, is returned. A copy is + made only if needed. + + Parameters + ---------- + x : ndarray + Input array. The elements in `x` are read in row-major, C-style order and + packed as a 1-D array. + order : `C`, optional + Only support row-major, C-style order. + + Returns + ------- + y : ndarray + y is an array of the same subtype as `x`, with shape ``(x.size,)``. + Note that matrices are special cased for backward compatibility, if `x` + is a matrix, then y is a 1-D ndarray. + + Notes + ----- + This function differs from the original numpy.arange in the following aspects: + - Only support row-major, C-style order. + + Examples + -------- + It is equivalent to ``reshape(x, -1)``. + + >>> x = np.array([[1, 2, 3], [4, 5, 6]]) + >>> print(np.ravel(x)) + [1. 2. 3. 4. 5. 6.] + + >>> print(x.reshape(-1)) + [1. 2. 3. 4. 5. 6.] + + >>> print(np.ravel(x.T)) + [1. 4. 2. 5. 3. 6.] + """ + if order != 'C': + raise NotImplementedError('order {} is not supported'.format(order)) + if isinstance(x, numeric_types): + return _np.reshape(x, -1) + elif isinstance(x, NDArray): + return _npi.reshape(x, -1) + else: + raise TypeError('type {} not supported'.format(str(type(x)))) diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index 632cfadf86a6..a43045235503 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -52,7 +52,8 @@ 'degrees', 'log2', 'log1p', 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'linspace', 'expand_dims', 'tile', 'arange', 'split', 'concatenate', - 'stack', 'mean', 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices', 'copysign'] + 'stack', 'mean', 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices', 'copysign', + 'ravel'] # Return code for dispatching indexing function call _NDARRAY_UNSUPPORTED_INDEXING = -1 @@ -3986,3 +3987,49 @@ def copysign(x1, x2, out=None): array([-1., 0., 1.]) """ return _mx_nd_np.copysign(x1, x2, out=out) + + +@set_module('mxnet.numpy') +def ravel(x, order='C'): + r""" + ravel(x) + + Return a contiguous flattened array. + A 1-D array, containing the elements of the input, is returned. A copy is + made only if needed. + + Parameters + ---------- + x : ndarray + Input array. The elements in `x` are read in row-major, C-style order and + packed as a 1-D array. + order : `C`, optional + Only support row-major, C-style order. + + Returns + ------- + y : ndarray + y is an array of the same subtype as `x`, with shape ``(x.size,)``. + Note that matrices are special cased for backward compatibility, if `x` + is a matrix, then y is a 1-D ndarray. + + Notes + ----- + This function differs from the original numpy.arange in the following aspects: + - Only support row-major, C-style order. + + Examples + -------- + It is equivalent to ``reshape(x, -1)``. + + >>> x = np.array([[1, 2, 3], [4, 5, 6]]) + >>> print(np.ravel(x)) + [1. 2. 3. 4. 5. 6.] + + >>> print(x.reshape(-1)) + [1. 2. 3. 4. 5. 6.] + + >>> print(np.ravel(x.T)) + [1. 4. 2. 5. 3. 6.] + """ + return _mx_nd_np.ravel(x, order) diff --git a/python/mxnet/symbol/numpy/_symbol.py b/python/mxnet/symbol/numpy/_symbol.py index 5a38f81f9102..12c333b253ba 100644 --- a/python/mxnet/symbol/numpy/_symbol.py +++ b/python/mxnet/symbol/numpy/_symbol.py @@ -35,7 +35,8 @@ 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'linspace', 'expand_dims', 'tile', 'arange', 'split', 'concatenate', 'stack', 'mean', - 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices', 'copysign'] + 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices', 'copysign', + 'ravel'] def _num_outputs(sym): @@ -2778,4 +2779,43 @@ def copysign(x1, x2, out=None): return _ufunc_helper(x1, x2, _npi.copysign, _np.copysign, _npi.copysign_scalar, _npi.rcopysign_scalar, out) +@set_module('mxnet.symbol.numpy') +def ravel(x, order='C'): + r""" + ravel(x) + + Return a contiguous flattened array. + A 1-D array, containing the elements of the input, is returned. A copy is + made only if needed. + + Parameters + ---------- + x : ndarray + Input array. The elements in `x` are read in row-major, C-style order and + packed as a 1-D array. + order : `C`, optional + Only support row-major, C-style order. + + Returns + ------- + y : ndarray + y is an array of the same subtype as `x`, with shape ``(x.size,)``. + Note that matrices are special cased for backward compatibility, if `x` + is a matrix, then y is a 1-D ndarray. + + Notes + ----- + This function differs from the original numpy.arange in the following aspects: + - Only support row-major, C-style order. + """ + if order != 'C': + raise NotImplementedError('order {} is not supported'.format(order)) + if isinstance(x, numeric_types): + return _np.reshape(x, -1) + elif isinstance(x, _Symbol): + return _npi.reshape(x, -1) + else: + raise TypeError('type {} not supported'.format(str(type(x)))) + + _set_np_symbol_class(_Symbol) diff --git a/src/operator/numpy/np_matrix_op.cc b/src/operator/numpy/np_matrix_op.cc index 5ad6c8908017..f88dd56065f4 100644 --- a/src/operator/numpy/np_matrix_op.cc +++ b/src/operator/numpy/np_matrix_op.cc @@ -163,6 +163,7 @@ bool NumpyReshapeShape(const nnvm::NodeAttrs& attrs, NNVM_REGISTER_OP(_np_reshape) .describe(R"code()code" ADD_FILELINE) +.add_alias("_npi_reshape") .set_num_inputs(1) .set_num_outputs(1) .set_attr_parser(ParamParser) diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index 1f2af8dfa4a9..ed4375939688 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -1320,6 +1320,39 @@ def hybrid_forward(self, F, a, *args): assert same(mx_out.asnumpy(), np_out) +@with_seed() +@use_np +def test_np_ravel(): + class TestRavel(HybridBlock): + def __init__(self): + super(TestRavel, self).__init__() + + def hybrid_forward(self, F, a): + return F.np.ravel(a) + + types = ['float64', 'float32', 'float16', 'int64', 'int32', 'int8'] + for oneType in types: + for hybridize in [True, False]: + for shape in [(), (2,), (2, 2), (1, 2, 3), (3, 0), (1, 0, 2)]: + test_ravel = TestRavel() + if hybridize: + test_ravel.hybridize() + x = rand_ndarray(shape, dtype=oneType).as_np_ndarray() + x.attach_grad() + np_out = _np.ravel(x.asnumpy()) + with mx.autograd.record(): + mx_out = test_ravel(x) + assert mx_out.shape == np_out.shape + assert_almost_equal(mx_out.asnumpy(), np_out, rtol=1e-3, atol=1e-5) + mx_out.backward() + np_backward = _np.ones(shape) + assert_almost_equal(x.grad.asnumpy(), np_backward, rtol=1e-3, atol=1e-5) + + mx_out = np.ravel(x) + np_out = _np.ravel(x.asnumpy()) + assert_almost_equal(mx_out.asnumpy(), np_out, rtol=1e-3, atol=1e-5) + + @with_seed() @use_np def test_np_randint(): From 7fc1d84747f6787a43ac21937d55ec7dad3117d3 Mon Sep 17 00:00:00 2001 From: perdasilva Date: Mon, 16 Sep 2019 22:11:19 +0200 Subject: [PATCH 322/813] Adds dynamic libmxnet to CD pipeline (#16163) * Adds dynamic libmxnet release pipeline * Updates default variants on CD pipeline --- cd/Jenkinsfile_cd_pipeline | 8 +- cd/Jenkinsfile_release_job | 4 +- cd/mxnet_lib/dynamic/Jenkins_pipeline.groovy | 57 +++++++++++ ci/docker/runtime_functions.sh | 101 +++++++++++++++++++ 4 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 cd/mxnet_lib/dynamic/Jenkins_pipeline.groovy diff --git a/cd/Jenkinsfile_cd_pipeline b/cd/Jenkinsfile_cd_pipeline index d7872204fcb7..4e4e6a7af3d4 100644 --- a/cd/Jenkinsfile_cd_pipeline +++ b/cd/Jenkinsfile_cd_pipeline @@ -48,10 +48,16 @@ pipeline { script { cd_utils.error_checked_parallel([ - "Static libmxnet based Release": { + "Static libmxnet based release": { stage("Build") { cd_utils.trigger_release_job("Build static libmxnet", "mxnet_lib/static", params.MXNET_VARIANTS) } + }, + + "Dynamic libmxnet based release": { + stage("Build") { + cd_utils.trigger_release_job("Build dynamic libmxnet", "mxnet_lib/dynamic", params.MXNET_VARIANTS) + } } ]) diff --git a/cd/Jenkinsfile_release_job b/cd/Jenkinsfile_release_job index 92ea72fca9c4..6a8864f4dbb1 100644 --- a/cd/Jenkinsfile_release_job +++ b/cd/Jenkinsfile_release_job @@ -37,8 +37,8 @@ pipeline { parameters { // Release parameters string(defaultValue: "Generic release job", description: "Optional Job name", name: "RELEASE_JOB_NAME") - choice(choices: ["mxnet_lib/static"], description: "Pipeline to build", name: "RELEASE_JOB_TYPE") - string(defaultValue: "cpu,mkl,cu80,cu80mkl,cu90,cu90mkl,cu92,cu92mkl,cu100,cu100mkl", description: "Comma separated list of variants", name: "MXNET_VARIANTS") + choice(choices: ["mxnet_lib/static", "mxnet_lib/dynamic"], description: "Pipeline to build", name: "RELEASE_JOB_TYPE") + string(defaultValue: "cpu,mkl,cu90,cu90mkl,cu92,cu92mkl,cu100,cu100mkl,cu101,cu101mkl", description: "Comma separated list of variants", name: "MXNET_VARIANTS") booleanParam(defaultValue: false, description: 'Whether this is a release build or not', name: "RELEASE_BUILD") } diff --git a/cd/mxnet_lib/dynamic/Jenkins_pipeline.groovy b/cd/mxnet_lib/dynamic/Jenkins_pipeline.groovy new file mode 100644 index 000000000000..57812d2c3690 --- /dev/null +++ b/cd/mxnet_lib/dynamic/Jenkins_pipeline.groovy @@ -0,0 +1,57 @@ +// -*- mode: groovy -*- + +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +// Jenkins pipeline +// See documents at https://jenkins.io/doc/book/pipeline/jenkinsfile/ + +// NOTE: ci_utils is loaded by the originating Jenkins job, e.g. jenkins/Jenkinsfile_release_job + +// libmxnet location +libmxnet = 'lib/libmxnet.so' + +// licenses +licenses = 'licenses/*' + +// libmxnet dependencies +mx_deps = '' +mx_mkldnn_deps = 'lib/libiomp5.so, lib/libmkldnn.so.0, lib/libmklml_intel.so' + +// library type +// either static or dynamic - depending on how it links to its dependencies +libtype = 'dynamic' + +libmxnet_pipeline = load('cd/mxnet_lib/mxnet_lib_pipeline.groovy') + +// Builds the dynamic binary for the specified mxnet variant +def build(mxnet_variant) { + node(NODE_LINUX_CPU) { + ws("workspace/mxnet_${libtype}/${mxnet_variant}/${env.BUILD_NUMBER}") { + def image = libmxnet_pipeline.get_environment(mxnet_variant) + ci_utils.init_git() + ci_utils.docker_run(image, "build_dynamic_libmxnet ${mxnet_variant}", false) + ci_utils.pack_lib("mxnet_${mxnet_variant}", libmxnet_pipeline.get_stash(mxnet_variant)) + } + } +} + +def get_pipeline(mxnet_variant) { + return libmxnet_pipeline.get_pipeline(mxnet_variant, this.&build) +} + +return this diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index 67cc98c73620..de753a56be7f 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -146,6 +146,107 @@ build_wheel() { # Build commands: Every platform in docker/Dockerfile.build. should have a corresponding # function here with the same suffix: +gather_licenses() { + mkdir -p licenses + + cp tools/dependencies/LICENSE.binary.dependencies licenses/ + cp NOTICE licenses/ + cp LICENSE licenses/ + cp DISCLAIMER licenses/ +} + +build_ubuntu_cpu_release() { + set -ex + + build_ccache_wrappers + + make \ + DEV=0 \ + ENABLE_TESTCOVERAGE=0 \ + USE_CPP_PACKAGE=0 \ + USE_MKLDNN=0 \ + USE_BLAS=openblas \ + USE_SIGNAL_HANDLER=1 \ + -j$(nproc) +} + +build_ubuntu_cpu_mkldnn_release() { + set -ex + + build_ccache_wrappers + + make \ + DEV=0 \ + ENABLE_TESTCOVERAGE=0 \ + USE_CPP_PACKAGE=0 \ + USE_MKLDNN=1 \ + USE_BLAS=openblas \ + USE_SIGNAL_HANDLER=1 \ + -j$(nproc) +} + +build_ubuntu_gpu_release() { + set -ex + # unfortunately this build has problems in 3rdparty dependencies with ccache and make + # build_ccache_wrappers + + make \ + DEV=0 \ + ENABLE_TESTCOVERAGE=0 \ + USE_BLAS=openblas \ + USE_MKLDNN=0 \ + USE_CUDA=1 \ + USE_CUDA_PATH=/usr/local/cuda \ + USE_CUDNN=1 \ + USE_CPP_PACKAGE=0 \ + USE_DIST_KVSTORE=1 \ + USE_SIGNAL_HANDLER=1 \ + -j$(nproc) +} + +build_ubuntu_gpu_mkldnn_release() { + set -ex + # unfortunately this build has problems in 3rdparty dependencies with ccache and make + # build_ccache_wrappers + + make \ + DEV=0 \ + ENABLE_TESTCOVERAGE=0 \ + USE_BLAS=openblas \ + USE_MKLDNN=1 \ + USE_CUDA=1 \ + USE_CUDA_PATH=/usr/local/cuda \ + USE_CUDNN=1 \ + USE_CPP_PACKAGE=0 \ + USE_DIST_KVSTORE=1 \ + USE_SIGNAL_HANDLER=1 \ + -j$(nproc) +} + +# Compiles the dynamic mxnet library +# Parameters: +# $1 -> mxnet_variant: the mxnet variant to build, e.g. cpu, cu100, cu92mkl, etc. +build_dynamic_libmxnet() { + set -ex + + local mxnet_variant=${1:?"This function requires a mxnet variant as the first argument"} + + # relevant licenses will be placed in the licenses directory + gather_licenses + + if [[ ${mxnet_variant} = "cpu" ]]; then + build_ubuntu_cpu_release + elif [[ ${mxnet_variant} = "mkl" ]]; then + build_ubuntu_cpu_mkldnn_release + elif [[ ${mxnet_variant} =~ cu[0-9]+$ ]]; then + build_ubuntu_gpu_release + elif [[ ${mxnet_variant} =~ cu[0-9]+mkl$ ]]; then + build_ubuntu_gpu_mkldnn_release + else + echo "Error: Unrecognized mxnet variant '${mxnet_variant}'" + fi +} + build_jetson() { set -ex pushd . From 692f49f2b1b9df1bb226c586405226291c6095cf Mon Sep 17 00:00:00 2001 From: Chaitanya Prakash Bapat Date: Mon, 16 Sep 2019 13:33:57 -0700 Subject: [PATCH 323/813] Sequence last fix (#16156) * seq last fix * index tensor to have int64 * fix dtypes * revert unnecessary changes * if seq len not passed, pass int64 dtype * dtype comment * use int32 or int64 as index dtype based on build flag * Trigger notification * Trigger notification * lint fix --- src/operator/sequence_last-inl.h | 12 ++++++------ src/operator/sequence_last.cc | 10 ++++++++-- tests/nightly/test_large_vector.py | 4 +++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/operator/sequence_last-inl.h b/src/operator/sequence_last-inl.h index 3c3c8b0cd49e..78ade5e9de06 100644 --- a/src/operator/sequence_last-inl.h +++ b/src/operator/sequence_last-inl.h @@ -101,8 +101,8 @@ class SequenceLastOp : public Operator { using namespace mshadow::expr; int axis = param_.axis; - int out_size = out.size(0) * out.size(1); - int max_seq_len = data.size(axis); + index_t out_size = out.size(0) * out.size(1); + index_t max_seq_len = data.size(axis); index_t offset1 = axis ? out.size(1) : out_size; index_t offset2 = axis ? (max_seq_len * out.size(1)) : out.size(1); @@ -121,11 +121,11 @@ class SequenceLastOp : public Operator { using namespace mshadow::expr; auto axis = param_.axis; - int batch = out_grad.size(0); - int rest = out_grad.size(1); - int out_size = batch * rest; + index_t batch = out_grad.size(0); + index_t rest = out_grad.size(1); + index_t out_size = batch * rest; - int max_seq_len = in_grad.size(axis); + index_t max_seq_len = in_grad.size(axis); index_t offset1 = axis ? rest : out_size; index_t offset2 = axis ? (max_seq_len * rest) : rest; diff --git a/src/operator/sequence_last.cc b/src/operator/sequence_last.cc index 44869c518504..3a6cdbad6149 100644 --- a/src/operator/sequence_last.cc +++ b/src/operator/sequence_last.cc @@ -46,8 +46,14 @@ Operator *SequenceLastProp::CreateOperatorEx(Context ctx, DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0], (*in_type)[1]); } - // sequence_length not passed in, so fall back to using input array dtype for second argument - DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0], (*in_type)[0]); + // sequence_length not passed in, so fall back to using int32/int64 dtype for second argument + // second argument is the dtype of the sequence_length NDArray + // use int32 or int64 as index dtype based on build flag + #if MXNET_USE_INT64_TENSOR_SIZE == 1 + DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0], mshadow::kInt64); + #else + DO_BIND_DISPATCH(CreateOp, param_, (*in_type)[0], mshadow::kInt32); + #endif } DMLC_REGISTER_PARAMETER(SequenceLastParam); diff --git a/tests/nightly/test_large_vector.py b/tests/nightly/test_large_vector.py index e3407792f2da..169f5244d784 100644 --- a/tests/nightly/test_large_vector.py +++ b/tests/nightly/test_large_vector.py @@ -356,7 +356,9 @@ def test_sequence_last(): # test with sequence length # parameter sequence_length - NDArray with shape (batch_size) # (2,3) indicates 2nd sequence from batch 1 and 3rd sequence from batch 2 - b = nd.SequenceLast(a, sequence_length=mx.nd.array([2, 3]), + # need to mention dtype = int64 for sequence_length ndarray to support large indices + # else it defaults to float32 and errors + b = nd.SequenceLast(a, sequence_length=mx.nd.array([2, 3], dtype="int64"), use_sequence_length=True) # check if it takes 2nd sequence from the first batch assert b[0] == a[1][0] From 2a55cd7d1801e6ed801057cac9018b707fe3e27f Mon Sep 17 00:00:00 2001 From: Vikas-kum Date: Mon, 16 Sep 2019 14:52:10 -0700 Subject: [PATCH 324/813] fixing test for model compatibility checker (#16159) * fixing test for model compatibility checker * fake change --- .../model_backwards_compatibility_check/JenkinsfileForMBCC | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/nightly/model_backwards_compatibility_check/JenkinsfileForMBCC b/tests/nightly/model_backwards_compatibility_check/JenkinsfileForMBCC index 08f562189593..725261d904f2 100644 --- a/tests/nightly/model_backwards_compatibility_check/JenkinsfileForMBCC +++ b/tests/nightly/model_backwards_compatibility_check/JenkinsfileForMBCC @@ -18,7 +18,7 @@ // //This is a Jenkinsfile for the model backwards compatibility checker. The format and some functions have been picked up from the top-level Jenkinsfile. -mx_lib = 'lib/libmxnet.so, lib/libmxnet.a, 3rdparty/dmlc-core/libdmlc.a, 3rdparty/tvm/nnvm/lib/libnnvm.a' +mx_lib = 'lib/libmxnet.so, lib/libmxnet.a, lib/libtvm_runtime.so,lib/libtvmop.so, 3rdparty/dmlc-core/libdmlc.a, 3rdparty/tvm/nnvm/lib/libnnvm.a' node('restricted-utility') { // Loading the utilities requires a node context unfortunately From 3dacabeaa4dea080f91363701a4a928cc807ed54 Mon Sep 17 00:00:00 2001 From: Vikas-kum Date: Mon, 16 Sep 2019 17:26:13 -0700 Subject: [PATCH 325/813] Tutorials nighly fix (#16179) * Fixing tutorials test failures * profier set state * modifying Jenkins for fast run * chanign comments * more test comment for faster run * changing comments from * more comments * profiler tutorial test fixes * uncommenting all tutorial tests * enabling all tests for nightly * removing parallel keyword from tutorials test * updating comments * Added github issue link in the comment --- docs/tutorials/gluon/custom_layer.md | 2 +- docs/tutorials/python/profiler.md | 3 +++ docs/tutorials/sparse/train_gluon.md | 2 +- tests/nightly/JenkinsfileForBinaries | 6 +++--- tests/tutorials/test_tutorials.py | 17 ++++++++++------- tests/utils/notebook_test/__init__.py | 2 +- 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/docs/tutorials/gluon/custom_layer.md b/docs/tutorials/gluon/custom_layer.md index 40ba0823a9d5..d2d1f56160b3 100644 --- a/docs/tutorials/gluon/custom_layer.md +++ b/docs/tutorials/gluon/custom_layer.md @@ -156,7 +156,7 @@ class NormalizationHybridLayer(gluon.HybridBlock): self.scales = self.params.get('scales', shape=scales.shape, - init=mx.init.Constant(scales.asnumpy().tolist()), # Convert to regular list to make this object serializable + init=mx.init.Constant(scales.asnumpy()), # Convert to regular list to make this object serializable differentiable=False) def hybrid_forward(self, F, x, weights, scales): diff --git a/docs/tutorials/python/profiler.md b/docs/tutorials/python/profiler.md index 91a74e4f49cf..49d50f494042 100644 --- a/docs/tutorials/python/profiler.md +++ b/docs/tutorials/python/profiler.md @@ -57,6 +57,7 @@ from mxnet import profiler profiler.set_config(profile_all=True, aggregate_stats=True, + continuous_dump=True, filename='profile_output.json') ``` @@ -195,6 +196,8 @@ print(profiler.dumps()) You can also dump the information collected by the profiler into a `json` file using the `profiler.dump()` function and view it in a browser. ```python +profiler.set_state('run') +profiler.set_state('stop') profiler.dump(finished=False) ``` diff --git a/docs/tutorials/sparse/train_gluon.md b/docs/tutorials/sparse/train_gluon.md index e9c5301aed84..f9f1a50f0925 100644 --- a/docs/tutorials/sparse/train_gluon.md +++ b/docs/tutorials/sparse/train_gluon.md @@ -151,7 +151,7 @@ Similar to using a [`DataLoader`](https://mxnet.incubator.apache.org/versions/ma ```python -data_iter = mx.io.LibSVMIter(data_libsvm='test.libsvm', data_shape=(num_features,), label_shape=(1,), batch_size=10) +data_iter = mx.io.LibSVMIter(data_libsvm=filepath, data_shape=(num_features,), label_shape=(1,), batch_size=10) for batch in data_iter: data = batch.data[0] print('data.stype: {}'.format(data.stype)) diff --git a/tests/nightly/JenkinsfileForBinaries b/tests/nightly/JenkinsfileForBinaries index 2bd78ac2afb6..8be95adb9194 100755 --- a/tests/nightly/JenkinsfileForBinaries +++ b/tests/nightly/JenkinsfileForBinaries @@ -39,7 +39,7 @@ core_logic: { utils.pack_lib('gpu', mx_lib) } } - }, + }/*, 'CPU: USE_INT64_TENSOR_SIZE': { node(NODE_LINUX_CPU) { ws('workspace/build-cpu-int64') { @@ -57,7 +57,7 @@ core_logic: { utils.pack_lib('gpu_int64', mx_cmake_lib) } } - } + }*/ } stage('NightlyTests'){ @@ -73,7 +73,7 @@ core_logic: { node('mxnetlinux-gpu-p3-8xlarge') { ws('workspace/nt-KVStoreTest') { utils.unpack_and_init('gpu', mx_lib) - utils.docker_run('ubuntu_nightly_gpu', 'nightly_test_KVStore_singleNode', true) + utils.docker_run('ubuntu_nightly_gpu', 'nightly_test_KVStore_singleNode', true) } } }, diff --git a/tests/tutorials/test_tutorials.py b/tests/tutorials/test_tutorials.py index c2173a7dc071..1d2da0c1eedd 100644 --- a/tests/tutorials/test_tutorials.py +++ b/tests/tutorials/test_tutorials.py @@ -57,7 +57,6 @@ def _test_tutorial_nb(tutorial): """ return run_notebook(tutorial, TUTORIAL_DIR, kernel=KERNEL, no_cache=NO_CACHE) - def test_basic_ndarray(): assert _test_tutorial_nb('basic/ndarray') @@ -114,10 +113,11 @@ def test_gluon_save_load_params(): def test_gluon_hybrid(): assert _test_tutorial_nb('gluon/hybrid') - +# https://github.com/apache/incubator-mxnet/issues/16181 +""" def test_gluon_performance(): assert _test_tutorial_nb('gluon/performance') - +""" def test_gluon_pretrained_models(): assert _test_tutorial_nb('gluon/pretrained_models') @@ -183,10 +183,11 @@ def test_module_to_gluon(): def test_python_types_of_data_augmentation(): assert _test_tutorial_nb('python/types_of_data_augmentation') - +#https://github.com/apache/incubator-mxnet/issues/16181 +""" def test_python_profiler(): assert _test_tutorial_nb('python/profiler') - +""" def test_sparse_row_sparse(): assert _test_tutorial_nb('sparse/row_sparse') @@ -216,6 +217,8 @@ def test_control_flow(): def test_amp(): assert _test_tutorial_nb('amp/amp_tutorial') - +# https://github.com/apache/incubator-mxnet/issues/16181 +""" def test_mkldnn_quantization(): - assert _test_tutorial_nb('mkldnn/mkldnn_quantization') \ No newline at end of file + assert _test_tutorial_nb('mkldnn/mkldnn_quantization') +""" diff --git a/tests/utils/notebook_test/__init__.py b/tests/utils/notebook_test/__init__.py index a32c5269b812..0a794cd13619 100644 --- a/tests/utils/notebook_test/__init__.py +++ b/tests/utils/notebook_test/__init__.py @@ -31,7 +31,7 @@ IPYTHON_VERSION = 4 # Pin to ipython version 4. -TIME_OUT = 10*60 # Maximum 10 mins/test. Reaching timeout causes test failure. +TIME_OUT = 15*60 # Maximum 10 mins/test. Reaching timeout causes test failure. ATTEMPTS = 8 KERNEL_ERROR_MSG = 'Kernel died before replying to kernel_info' From b777a69c9a2b1a2557b2cbcb4acc7bb4d13d0007 Mon Sep 17 00:00:00 2001 From: Anirudh Subramanian Date: Wed, 18 Sep 2019 07:47:26 -0700 Subject: [PATCH 326/813] Add register_op_hook for gluon (#15839) * Set monitor callback basic support * Trigger CI * Add base.pyi and ndarray.pyx * Change not supported to experimental and check for both static_shape and static_alloc --- include/mxnet/c_api.h | 12 +++++ python/mxnet/_ctypes/ndarray.py | 28 +++++++++++- python/mxnet/cython/base.pyi | 8 ++++ python/mxnet/cython/ndarray.pyx | 16 ++++++- python/mxnet/gluon/block.py | 37 ++++++++++++++++ src/c_api/c_api_ndarray.cc | 20 +++++++++ src/common/utils.cc | 57 ++++++++++++++++++++++++ src/common/utils.h | 9 ++++ src/imperative/cached_op.cc | 23 ++++++++-- src/imperative/cached_op.h | 8 ++++ src/imperative/imperative_utils.cc | 20 ++++++++- src/imperative/imperative_utils.h | 9 +++- tests/python/unittest/test_gluon.py | 69 +++++++++++++++++++++++++++++ 13 files changed, 307 insertions(+), 9 deletions(-) diff --git a/include/mxnet/c_api.h b/include/mxnet/c_api.h index 2fa2af5ebcf2..46953b11714b 100644 --- a/include/mxnet/c_api.h +++ b/include/mxnet/c_api.h @@ -111,6 +111,11 @@ typedef void (*EngineFuncParamDeleter)(void*); typedef void (*ExecutorMonitorCallback)(const char*, NDArrayHandle, void*); +/*! \brief Monitor callback called at operator level for cached op */ +typedef void (*CachedOpMonitorCallback)(const char*, + const char*, + NDArrayHandle); + struct NativeOpInfo { void (*forward)(int, float**, int*, unsigned**, int*, void*); @@ -1284,6 +1289,13 @@ MXNET_DLL int MXInvokeCachedOpEx(CachedOpHandle handle, NDArrayHandle **outputs, const int** out_stypes); +/*! + * \brief cached op set monitor callback + */ +MXNET_DLL int MXCachedOpRegisterOpHook(NDArrayHandle handle, + CachedOpMonitorCallback callback, + bool monitor_all); + //-------------------------------------------- // Part 3: symbolic configuration generation //-------------------------------------------- diff --git a/python/mxnet/_ctypes/ndarray.py b/python/mxnet/_ctypes/ndarray.py index b1a38c1d2621..0d5dade2f163 100644 --- a/python/mxnet/_ctypes/ndarray.py +++ b/python/mxnet/_ctypes/ndarray.py @@ -29,6 +29,13 @@ from ..base import check_call +def _monitor_callback_wrapper(callback): + """A wrapper for the user-defined handle.""" + def callback_handle(name, opr_name, array, _): + """ ctypes function """ + callback(name, opr_name, array) + return callback_handle + class NDArrayBase(object): """Base data structure for ndarray""" __slots__ = ["handle", "writable"] @@ -112,10 +119,11 @@ def _imperative_invoke(handle, ndargs, keys, vals, out, is_np_op): class CachedOp(object): """Cached operator handle.""" - __slots__ = ["handle", "is_np_sym"] + __slots__ = ["handle", "is_np_sym", "_monitor_callback"] def __init__(self, sym, flags=()): self.handle = CachedOpHandle() + self._monitor_callback = None from ..symbol.numpy._symbol import _Symbol self.is_np_sym = bool(isinstance(sym, _Symbol)) @@ -170,3 +178,21 @@ def __call__(self, *args, **kwargs): else: return [create_ndarray_fn(ctypes.cast(output_vars[i], NDArrayHandle), stype=out_stypes[i]) for i in range(num_output.value)] + + def _register_op_hook(self, callback, monitor_all=False): + """Install callback for monitor. + + Parameters + ---------- + callback : function + Takes a string for node_name, string for op_name and a NDArrayHandle. + monitor_all : bool, default False + If true, monitor both input _imperative_invoked output, otherwise monitor output only. + """ + cb_type = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_char_p, NDArrayHandle, ctypes.c_void_p) + if callback: + self._monitor_callback = cb_type(_monitor_callback_wrapper(callback)) + check_call(_LIB.MXCachedOpRegisterOpHook( + self.handle, + self._monitor_callback, + ctypes.c_int(monitor_all))) diff --git a/python/mxnet/cython/base.pyi b/python/mxnet/cython/base.pyi index 548afc782763..0a35555aabb3 100644 --- a/python/mxnet/cython/base.pyi +++ b/python/mxnet/cython/base.pyi @@ -2,13 +2,18 @@ from ..base import MXNetError from libcpp.vector cimport vector from libcpp.string cimport string +from libcpp cimport bool as _bool from cpython.version cimport PY_MAJOR_VERSION ctypedef void* SymbolHandle ctypedef void* NDArrayHandle ctypedef void* OpHandle ctypedef void* CachedOpHandle +ctypedef void* MonitorCallbackHandle ctypedef unsigned nn_uint +ctypedef void (*CachedOpMonitorCallback)(const char*, + const char*, + NDArrayHandle) cdef py_str(const char* x): if PY_MAJOR_VERSION < 3: @@ -112,3 +117,6 @@ cdef extern from "mxnet/c_api.h": int *num_outputs, NDArrayHandle **outputs, const int **out_stypes); + int MXCachedOpRegisterOpHook(NDArrayHandle handle, + CachedOpMonitorCallback callback, + _bool monitor_all); diff --git a/python/mxnet/cython/ndarray.pyx b/python/mxnet/cython/ndarray.pyx index 50791e9b9a86..74ad1c4c3d49 100644 --- a/python/mxnet/cython/ndarray.pyx +++ b/python/mxnet/cython/ndarray.pyx @@ -22,6 +22,7 @@ import ctypes as _ctypes import numpy as np from ..ndarray_doc import _build_doc from libc.stdint cimport uint32_t, int64_t +from ..base import _LIB include "./base.pyi" @@ -47,7 +48,6 @@ cdef class NDArrayBase: return _ctypes.cast(self.chandle, _ctypes.c_void_p) def __set__(self, value): self._set_handle(value) - property writable: def __get__(self): return bool(self.cwritable) @@ -75,6 +75,10 @@ def _set_np_ndarray_class(cls): global _np_ndarray_cls _np_ndarray_cls = cls +def _monitor_callback_wrapper(callback): + def callback_handle(name, opr_name, arr, _): + callback(name, opr_name, arr) + return callback_handle cdef NewArray(NDArrayHandle handle, int stype=-1, int is_np_array=0): """Create a new array given handle""" @@ -103,6 +107,7 @@ cdef class CachedOp: self._set_handle(value) cdef int is_np_sym + cdef readonly object mhandle def __init__(self, sym, flags=()): cdef vector[string] s_flag_keys @@ -169,6 +174,15 @@ cdef class CachedOp: else: return [NewArray(p_output_vars[i], p_output_stypes[i], self.is_np_sym) for i in range(num_output)] + def _register_op_hook(self, callback, monitor_all=False): + cb_type = _ctypes.CFUNCTYPE(None, _ctypes.c_char_p, _ctypes.c_char_p, _ctypes.c_void_p, _ctypes.c_void_p) + if callback: + self.mhandle = cb_type(_monitor_callback_wrapper(callback)) + chandle = _ctypes.cast(self.chandle, _ctypes.c_void_p) + CALL(_LIB.MXCachedOpRegisterOpHook(chandle, + self.mhandle, + _ctypes.c_int(monitor_all))) + def _imperative_invoke(handle, ndargs, keys, vals, out, is_np_op=0): """cython implementation of imperative invoke wrapper""" diff --git a/python/mxnet/gluon/block.py b/python/mxnet/gluon/block.py index 97e6e8b68453..fc08b4c6bd32 100644 --- a/python/mxnet/gluon/block.py +++ b/python/mxnet/gluon/block.py @@ -590,6 +590,19 @@ def forward(self, *args): # pylint: disable= invalid-name raise NotImplementedError + def register_op_hook(self, callback, monitor_all=False): + """Install callback monitor. + + Parameters + ---------- + callback : function + Takes a string and a NDArrayHandle. + monitor_all : bool, default False + If true, monitor both input and output, otherwise monitor output only. + """ + for cld in self._children.values(): + cld.register_op_hook(callback, monitor_all) + def summary(self, *inputs): """Print the summary of the model's output and parameters. @@ -754,6 +767,8 @@ def __init__(self, prefix=None, params=None): self._in_format = None self._active = False self._flags = [] + self._callback = None + self._monitor_all = False def __setattr__(self, name, value): """Registers parameters.""" @@ -833,6 +848,12 @@ def _deferred_infer_shape(self, *args): def _call_cached_op(self, *args): if self._cached_op is None: self._build_cache(*args) + assert self._cached_op, "cached op is not None" + if self._callback: + self._cached_op._register_op_hook(self._callback, self._monitor_all) + if len(self._flags) >= 2 and (self._flags[1] or self._flags[0]): + warnings.warn("register_op_hook is experimental when static_alloc=True / static_shape=True " + " and may not work correctly") args, fmt = _flatten(args, "input") assert fmt == self._in_format, "Invalid input format" @@ -938,6 +959,22 @@ def export(self, path, epoch=0, remove_amp_cast=True): save_fn = _mx_npx.save if is_np_array() else ndarray.save save_fn('%s-%04d.params'%(path, epoch), arg_dict) + def register_op_hook(self, callback, monitor_all=False): + """Install op hook for block recursively. + + Parameters + ---------- + callback : function + Takes a string and a NDArrayHandle. + monitor_all : bool, default False + If true, monitor both input and output, otherwise monitor output only. + """ + self._callback = callback + self._monitor_all = monitor_all + for cld in self._children.values(): + cld._callback = callback + cld._monitor_all = monitor_all + def forward(self, x, *args): """Defines the forward computation. Arguments can be either :py:class:`NDArray` or :py:class:`Symbol`.""" diff --git a/src/c_api/c_api_ndarray.cc b/src/c_api/c_api_ndarray.cc index a80e171c8d36..5863f08762e6 100644 --- a/src/c_api/c_api_ndarray.cc +++ b/src/c_api/c_api_ndarray.cc @@ -378,3 +378,23 @@ int MXAutogradGetSymbol(NDArrayHandle handle, SymbolHandle *out) { *out = reinterpret_cast(sym); API_END(); } + +int MXCachedOpRegisterOpHook(NDArrayHandle handle, + CachedOpMonitorCallback callback, + bool monitor_all) { + API_BEGIN(); + CachedOpMonitorCallback callback_temp = nullptr; + std::function clbk; + if (callback) { + callback_temp = callback; + clbk = [callback_temp](const char *name, const char *opr_name, + void *handle) { + callback_temp(name, opr_name, handle); + }; + } else { + clbk = nullptr; + } + CachedOpPtr op = *static_cast(handle); + op->RegisterOpHook(clbk, monitor_all); + API_END(); +} diff --git a/src/common/utils.cc b/src/common/utils.cc index 9fe46d94d036..032a324c96b0 100644 --- a/src/common/utils.cc +++ b/src/common/utils.cc @@ -51,5 +51,62 @@ void CastStorageDispatch(const OpContext& ctx, mxnet::op::CastStorageComputeImpl(ctx, input, output); } +void ExecuteMonInputCallback( + const nnvm::IndexedGraph &idx, const std::vector &state_arrays, + size_t nid, const std::function + &monitor_callback) { + static const auto &flist_inputs = + nnvm::Op::GetAttr("FListInputNames"); + std::vector input_names; + const nnvm::IndexedGraph::Node &inode = idx[nid]; + const nnvm::Node *node = inode.source; + if (flist_inputs.count(node->op())) { + input_names = flist_inputs[node->op()](node->attrs); + } else { + for (size_t i = 0; i < node->num_inputs(); ++i) { + input_names.emplace_back("input" + std::to_string(i)); + } + } + + for (size_t i = 0; i < node->num_inputs(); ++i) { + const nnvm::NodeEntry &input = node->inputs[i]; + if (state_arrays[idx.entry_id(input)]->is_none()) { + continue; + } + NDArray *cpy = new NDArray(*state_arrays[idx.entry_id(input)]); + std::string name = inode.source->attrs.name + "_" + input_names[i]; + monitor_callback(name.c_str(), inode.source->op()->name.c_str(), + reinterpret_cast(cpy)); + } +} + +void ExecuteMonOutputCallback( + const nnvm::IndexedGraph &idx, const std::vector &state_arrays, + size_t nid, const std::function + &monitor_callback) { + static const auto &flist_outputs = + nnvm::Op::GetAttr("FListOutputNames"); + std::vector output_names; + const nnvm::IndexedGraph::Node &inode = idx[nid]; + const nnvm::Node *node = inode.source; + if (flist_outputs.count(node->op())) { + output_names = flist_outputs[node->op()](node->attrs); + } else { + for (size_t i = 0; i < node->num_outputs(); ++i) { + output_names.emplace_back(std::to_string(i)); + } + } + + for (size_t i = 0; i < node->num_outputs(); ++i) { + if (state_arrays[idx.entry_id(nid, i)]->is_none()) { + continue; + } + NDArray *cpy = new NDArray(*state_arrays[idx.entry_id(nid, i)]); + std::string name = inode.source->attrs.name + "_" + output_names[i]; + monitor_callback(name.c_str(), inode.source->op()->name.c_str(), + reinterpret_cast(cpy)); + } +} + } // namespace common } // namespace mxnet diff --git a/src/common/utils.h b/src/common/utils.h index 6edfa3c6bb56..2bd6aac6f5d9 100644 --- a/src/common/utils.h +++ b/src/common/utils.h @@ -804,6 +804,15 @@ inline void ConvertToLegacyShape(mxnet::ShapeVector* shapes) { ConvertToLegacyShape(&(shapes->at(i))); } } +void ExecuteMonInputCallback( + const nnvm::IndexedGraph &idx, const std::vector &state_arrays, + size_t nid, const std::function + &monitor_callback); + +void ExecuteMonOutputCallback( + const nnvm::IndexedGraph &idx, const std::vector &state_arrays, + size_t nid, const std::function + &monitor_callback); /*! * \brief This is function can return the output names of a NodeEntry. diff --git a/src/imperative/cached_op.cc b/src/imperative/cached_op.cc index efe38019cfda..6818d757ab79 100644 --- a/src/imperative/cached_op.cc +++ b/src/imperative/cached_op.cc @@ -697,6 +697,9 @@ void CachedOp::StaticRunOps( ndinputs.emplace_back(state_arrays[idx.entry_id(j)]); CHECK(!ndinputs.back()->is_none()); } + if (monitor_callback_ && monitor_all_) { + mxnet::common::ExecuteMonInputCallback(idx, state_arrays, i, monitor_callback_); + } ndoutputs.clear(); ndoutputs.reserve(num_outputs); req.clear(); @@ -708,6 +711,7 @@ void CachedOp::StaticRunOps( CHECK(req.back() == kNullOp || !ndoutputs.back()->is_none()); } const DispatchMode dispatch_mode = dispatch_modes[i]; + if (createop.count(node.source->op())) { arg_shapes.clear(); arg_dtypes.clear(); @@ -735,6 +739,9 @@ void CachedOp::StaticRunOps( default_ctx, node.source->attrs, ndinputs, ndoutputs, req, dispatch_mode); } + if (monitor_callback_) { + mxnet::common::ExecuteMonOutputCallback(idx, state_arrays, i, monitor_callback_); + } } } } @@ -883,12 +890,12 @@ OpStatePtr CachedOp::DynamicForward( // So if it's not the inline mode, we disable recording. RunGraph(false, idx, arrays, 0, idx.num_nodes(), std::move(array_reqs), std::move(ref_count), &states, dispatch_modes, - recording && inlining_); + recording && inlining_, nullptr, monitor_callback_, monitor_all_); } else { mxnet::ShapeVector shapes = g.GetAttr("shape"); NaiveRunGraph(false, default_ctx, idx, arrays, 0, idx.num_nodes(), std::move(array_reqs), std::move(ref_count), &states, - dispatch_modes, recording && inlining_, &shapes); + dispatch_modes, recording && inlining_, &shapes, monitor_callback_, monitor_all_); { auto state_ptr = GetCachedOpState(default_ctx); auto& state = state_ptr.get_state(); @@ -1028,7 +1035,7 @@ void CachedOp::DynamicBackward( RunGraph(retain_graph, idx, arrays, num_forward_nodes, idx.num_nodes(), std::move(array_reqs), std::move(ref_count), &states, dispatch_modes, - Imperative::Get()->is_recording()); + Imperative::Get()->is_recording(), nullptr, monitor_callback_); if (retain_graph) { buff.resize(num_forward_entries); @@ -1295,6 +1302,16 @@ void CachedOpBackward(const OpStatePtr& state_ptr, CopyFromTo(out_bufs[i], outputs[i]); } +/* + * Register the callback to be called when the operator is executed + */ +void CachedOp::RegisterOpHook(const CachedOp::CachedOpMonCallback& callback, + bool monitor_all) { + CHECK(callback) << "invalid callback"; + monitor_callback_ = callback; + monitor_all_ = monitor_all; +} + OpStatePtr CreateCachedOpState(const NodeAttrs& attrs, Context ctx, const mxnet::ShapeVector& in_shapes, diff --git a/src/imperative/cached_op.h b/src/imperative/cached_op.h index c45f137b2d63..db049d59ed80 100644 --- a/src/imperative/cached_op.h +++ b/src/imperative/cached_op.h @@ -74,6 +74,9 @@ struct CachedOpConfig : public dmlc::Parameter { }; class CachedOp { + using CachedOpMonCallback = + std::function; + public: CachedOp( const nnvm::Symbol& sym, @@ -134,6 +137,8 @@ class CachedOp { sym.outputs = fwd_graph_.outputs; return sym; } + void RegisterOpHook(const CachedOp::CachedOpMonCallback& callback, + bool monitor_all = false); private: struct GraphInfo; @@ -203,6 +208,9 @@ class CachedOp { std::vector save_inputs_, save_outputs_; std::vector bwd_output_reqs_; + std::function monitor_callback_{nullptr}; + bool monitor_all_{false}; + std::mutex mutex_; std::unordered_map > cached_op_states_; }; diff --git a/src/imperative/imperative_utils.cc b/src/imperative/imperative_utils.cc index 568d39fc8043..5491457b188f 100644 --- a/src/imperative/imperative_utils.cc +++ b/src/imperative/imperative_utils.cc @@ -137,7 +137,9 @@ void RunGraph( std::vector *p_states, const DispatchModeVector &dispatch_modes, bool recording, - mxnet::ShapeVector *shapes) { + mxnet::ShapeVector *shapes, + const imperative::CachedOpMonCallback& callback, + const bool monitor_all) { CHECK(shapes == nullptr); for (size_t i = node_start; i < node_end; ++i) { const nnvm::IndexedGraph::Node& node = idx[i]; @@ -148,6 +150,9 @@ void RunGraph( std::vector ndoutputs = NodeOutputs(idx, i, arrays); std::vector req = NodeReq(idx, i, array_reqs); Context ctx = ndoutputs[0]->ctx(); + if (callback && monitor_all) { + mxnet::common::ExecuteMonInputCallback(idx, arrays, i, callback); + } auto invoke = [&](const OpStatePtr &state) { const nnvm::IndexedGraph::Node& node = idx[i]; DispatchMode dispatch_mode = dispatch_modes[i]; @@ -159,6 +164,9 @@ void RunGraph( }; InvokeOperator(idx, i, retain_graph, arrays, ctx, p_states, ndinputs, ndoutputs, &req, &ref_count, invoke); + if (callback) { + mxnet::common::ExecuteMonOutputCallback(idx, arrays, i, callback); + } } } @@ -173,7 +181,9 @@ void NaiveRunGraph( std::vector *p_states, const DispatchModeVector &dispatch_modes, bool recording, - mxnet::ShapeVector *shapes) { + mxnet::ShapeVector *shapes, + const imperative::CachedOpMonCallback& callback, + const bool monitor_all) { for (size_t i = node_start; i < node_end; ++i) { const nnvm::IndexedGraph::Node& node = idx[i]; if (node.source->op() == nullptr) { @@ -183,6 +193,9 @@ void NaiveRunGraph( std::vector ndoutputs = NodeOutputs(idx, i, arrays); std::vector req; Context ctx = GetContext(node.source->attrs, ndinputs, ndoutputs, default_ctx); + if (callback && monitor_all) { + mxnet::common::ExecuteMonInputCallback(idx, arrays, i, callback); + } auto invoke = [&](const OpStatePtr &state) { const nnvm::IndexedGraph::Node& node = idx[i]; DispatchMode dispatch_mode = DispatchMode::kUndefined; @@ -205,6 +218,9 @@ void NaiveRunGraph( }; InvokeOperator(idx, i, retain_graph, arrays, ctx, p_states, ndinputs, ndoutputs, &req, &ref_count, invoke); + if (callback) { + mxnet::common::ExecuteMonOutputCallback(idx, arrays, i, callback); + } } } diff --git a/src/imperative/imperative_utils.h b/src/imperative/imperative_utils.h index 21caafa124f9..c5932bb3bbfe 100644 --- a/src/imperative/imperative_utils.h +++ b/src/imperative/imperative_utils.h @@ -59,6 +59,7 @@ struct EngineOprSeg { }; using MemoryPlanVector = std::vector; +using CachedOpMonCallback = std::function; inline Context GetContext(const nnvm::NodeAttrs& attrs, const std::vector& inputs, @@ -1056,7 +1057,9 @@ void RunGraph(const bool retain_graph, std::vector *p_states, const DispatchModeVector &dispatch_modes, bool recording, - mxnet::ShapeVector *shapes = nullptr); + mxnet::ShapeVector *shapes = nullptr, + const CachedOpMonCallback& callback = nullptr, + const bool monitor_all_ = false); void NaiveRunGraph(const bool retain_graph, const Context& default_ctx, @@ -1068,7 +1071,9 @@ void NaiveRunGraph(const bool retain_graph, std::vector *p_states, const DispatchModeVector &dispatch_modes, bool recording, - mxnet::ShapeVector *shapes); + mxnet::ShapeVector *shapes, + const CachedOpMonCallback& callback = nullptr, + const bool monitor_all_ = false); } // namespace imperative } // namespace mxnet diff --git a/tests/python/unittest/test_gluon.py b/tests/python/unittest/test_gluon.py index af30980b10ea..46e976432fa8 100644 --- a/tests/python/unittest/test_gluon.py +++ b/tests/python/unittest/test_gluon.py @@ -21,6 +21,7 @@ import mxnet as mx from mxnet import gluon from mxnet.gluon import nn +from mxnet.base import py_str from mxnet.test_utils import assert_almost_equal from mxnet.ndarray.ndarray import _STORAGE_TYPE_STR_TO_ID from common import (setup_module, with_seed, assertRaises, teardown, @@ -1503,6 +1504,74 @@ def call_pre_hook(block, x): assert hook_call_count == 1 assert pre_hook_call_count == 2 +@with_seed() +def test_op_hook_output_names(): + def check_name(block, expected_names, inputs=None, expected_opr_names=None, monitor_all=False): + opr_names = [] + output_names = [] + + def mon_callback(node_name, opr_name, arr): + output_names.append(py_str(node_name)) + opr_names.append(py_str(opr_name)) + + block.register_op_hook(mon_callback, monitor_all) + if not inputs: + block(mx.nd.ones((2, 3, 4))) + else: + block(inputs) + + for output_name, expected_name in zip(output_names, expected_names): + print(output_name) + assert output_name == expected_name + + if expected_opr_names: + for opr_name, expected_opr_name in zip(opr_names, expected_opr_names): + assert opr_name == expected_opr_name + + # Test with Dense layer + model = mx.gluon.nn.HybridSequential(prefix="dense_") + with model.name_scope(): + model.add(mx.gluon.nn.Dense(2)) + model.initialize() + model.hybridize() + check_name(model, ["dense_dense0_fwd_output"]) + + # Test with Activation, FListInputNames not registered, input name will have _input appended + model = mx.gluon.nn.HybridSequential(prefix="relu_") + with model.name_scope(): + model.add(mx.gluon.nn.Activation("relu")) + model.initialize() + model.hybridize() + check_name(model, ["relu_relu0_fwd_output"]) + + # Test with Pooling, monitor_all is set to True + model = mx.gluon.nn.HybridSequential("pool_") + with model.name_scope(): + model.add(mx.gluon.nn.AvgPool1D()) + model.initialize() + model.hybridize() + check_name(model, ['pool_pool0_fwd_data', 'pool_pool0_fwd_output'], expected_opr_names=["Pooling"], + monitor_all=True) + + # stack two layers and test + model = mx.gluon.nn.HybridSequential("dense_") + with model.name_scope(): + model.add(mx.gluon.nn.Dense(2)) + model.add(mx.gluon.nn.Activation("relu")) + model.initialize() + model.hybridize() + check_name(model, + ['dense_dense0_fwd_data', 'dense_dense0_fwd_weight', + 'dense_dense0_fwd_bias', 'dense_dense0_fwd_output', + 'dense_relu0_fwd_input0', 'dense_relu0_fwd_output'], monitor_all=True) + + # check with different hybridize modes + model.hybridize(static_alloc=True) + check_name(model, + ['dense_dense0_fwd_data', 'dense_dense0_fwd_weight', + 'dense_dense0_fwd_bias', 'dense_dense0_fwd_output', + 'dense_relu0_fwd_input0', 'dense_relu0_fwd_output'], monitor_all=True) + @with_seed() def test_apply(): From 8cc344305ed9409f5364aa4cefdc7628e064b0ff Mon Sep 17 00:00:00 2001 From: Aaron Markham Date: Wed, 18 Sep 2019 08:40:23 -0700 Subject: [PATCH 327/813] adding codeowners (#16165) * add aaron to codeowners * retrigger CI * ci --- CODEOWNERS | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 90744501ec71..6bf30b90602d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,7 +3,7 @@ # https://help.github.com/articles/about-codeowners/ and # https://github.com/blog/2392-introducing-code-owners # -# Anybody can add themselves or a team as additional watcher or contributor +# Anybody can add themselves or a team as additional watcher or contributor # to get notified about changes in a specific package. # See https://help.github.com/articles/about-teams how to setup teams. @@ -47,7 +47,7 @@ CMakeLists.txt @szha @pllarroy # MXNet CI dev_menu.py @pllarroy -/ci/ @pllarroy @marcoabreu +/ci/ @pllarroy @marcoabreu @aaronmarkham /docker/ @marcoabreu /tests/ci_build/ @marcoabreu Jenkinsfile @marcoabreu @@ -59,13 +59,13 @@ Makefile @szha prepare_mkl.sh @szha # Docs -/docs/ @szha @pllarroy +/docs/ @szha @pllarroy @aaronmarkham # Submodules .gitmodules @szha # Examples -/example/ @szha @pllarroy +/example/ @szha @pllarroy @aaronmarkham # Tools /tools/ @szha @pllarroy From 956cfa3122e31a6957e964657422d625c1ef245d Mon Sep 17 00:00:00 2001 From: kshitij12345 Date: Wed, 18 Sep 2019 23:05:28 +0530 Subject: [PATCH 328/813] assert_allclose -> rtol=1e-10 (#16198) --- tests/python/unittest/test_ndarray.py | 33 ++++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/tests/python/unittest/test_ndarray.py b/tests/python/unittest/test_ndarray.py index 7091abf7308a..7be59df6efda 100644 --- a/tests/python/unittest/test_ndarray.py +++ b/tests/python/unittest/test_ndarray.py @@ -21,6 +21,7 @@ from itertools import permutations, combinations_with_replacement import os import pickle as pkl +import functools from nose.tools import assert_raises, raises from common import with_seed, assertRaises, TemporaryDirectory from mxnet.test_utils import almost_equal @@ -1887,14 +1888,16 @@ def check_save_load(save_is_np_shape, load_is_np_shape, shapes, save_throw_excep check_save_load(True, True, [(2, 0, 1), (0,), (), (), (0, 4), (), (3, 0, 0, 0), (2, 1), (0, 5, 0)], False, False) -@with_seed() -def test_update_ops_mutation(): - def assert_mutate(x, y, op): +def _test_update_ops_mutation_impl(): + assert_allclose = functools.partial( + np.testing.assert_allclose, rtol=1e-10) + + def assert_mutate(x, y): np.testing.assert_raises( - AssertionError, np.testing.assert_allclose, x, y) + AssertionError, assert_allclose, x, y) - def assert_unchanged(x, y, op): - np.testing.assert_allclose(x, y) + def assert_unchanged(x, y): + assert_allclose(x, y) def test_op(op, num_inputs, mutated_inputs, **kwargs): for dim in range(1, 7): @@ -1919,9 +1922,9 @@ def test_op(op, num_inputs, mutated_inputs, **kwargs): for idx, (pre_array, post_array) in \ enumerate(zip(pre_arrays, post_arrays)): if idx in mutated_inputs: - assert_mutate(pre_array, post_array, op) + assert_mutate(pre_array, post_array) else: - assert_unchanged(pre_array, post_array, op) + assert_unchanged(pre_array, post_array) test_op(mx.nd.signsgd_update, 2, [0], ** {'rescale_grad': 0.1, 'lr': 0.01, 'wd': 1e-3, @@ -1952,6 +1955,20 @@ def test_op(op, num_inputs, mutated_inputs, **kwargs): {'rescale_grad': 0.1, 'lr': 0.01, 'wd': 1e-3}) +@with_seed() +def test_update_ops_mutation(): + _test_update_ops_mutation_impl() + + +# Problem : +# https://github.com/apache/incubator-mxnet/pull/15768#issuecomment-532046408 +@with_seed(412298777) +def test_update_ops_mutation_failed_seed(): + # The difference was -5.9604645e-08 which was + # lower than then `rtol` of 1e-07 + _test_update_ops_mutation_impl() + + def test_large_int_rounding(): large_integer = 50000001 From f75d0933efc566d98b083113fe58969a0ab4efbf Mon Sep 17 00:00:00 2001 From: Haibin Lin Date: Wed, 18 Sep 2019 11:05:10 -0700 Subject: [PATCH 329/813] [Dataset] add shard API (#16175) * add shard API * more balanced * fix a bug * Update test_gluon_data.py --- python/mxnet/gluon/data/dataset.py | 31 ++++++++++++++++++++++++ python/mxnet/gluon/data/sampler.py | 10 +++++--- tests/python/unittest/test_gluon_data.py | 18 ++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/python/mxnet/gluon/data/dataset.py b/python/mxnet/gluon/data/dataset.py index a5399f259435..7f2c6342d595 100644 --- a/python/mxnet/gluon/data/dataset.py +++ b/python/mxnet/gluon/data/dataset.py @@ -64,6 +64,37 @@ def filter(self, fn): from . import FilterSampler return _SampledDataset(self, FilterSampler(fn, self)) + def shard(self, num_shards, index): + """Returns a new dataset includes only 1/num_shards of this dataset. + + For distributed training, be sure to shard before you randomize the dataset + (such as shuffle), if you want each worker to reach a unique subset. + + Parameters + ---------- + num_shards : int + A integer representing the number of data shards. + index : int + A integer representing the index of the current shard. + + Returns + ------- + Dataset + The result dataset. + """ + assert index < num_shards, 'Shard index of out bound: %d out of %d'%(index, num_shards) + assert num_shards > 0, 'Number of shards must be greater than 0' + assert index >= 0, 'Index must be non-negative' + length = len(self) + shard_len = length // num_shards + rest = length % num_shards + # Compute the start index for this partition + start = shard_len * index + min(index, rest) + # Compute the end index for this partition + end = start + shard_len + (index < rest) + from . import SequentialSampler + return _SampledDataset(self, SequentialSampler(end - start, start)) + def take(self, count): """Returns a new dataset with at most `count` number of samples in it. diff --git a/python/mxnet/gluon/data/sampler.py b/python/mxnet/gluon/data/sampler.py index 980c63134fac..f53e636317df 100644 --- a/python/mxnet/gluon/data/sampler.py +++ b/python/mxnet/gluon/data/sampler.py @@ -36,23 +36,25 @@ def __len__(self): class SequentialSampler(Sampler): - """Samples elements from [0, length) sequentially. + """Samples elements from [start, start+length) sequentially. Parameters ---------- length : int Length of the sequence. + start : int, default is 0 + The start of the sequence index. """ - def __init__(self, length): + def __init__(self, length, start=0): self._length = length + self._start = start def __iter__(self): - return iter(range(self._length)) + return iter(range(self._start, self._start + self._length)) def __len__(self): return self._length - class RandomSampler(Sampler): """Samples elements from [0, length) randomly without replacement. diff --git a/tests/python/unittest/test_gluon_data.py b/tests/python/unittest/test_gluon_data.py index 62bf7c3db869..80bea2a511f2 100644 --- a/tests/python/unittest/test_gluon_data.py +++ b/tests/python/unittest/test_gluon_data.py @@ -296,6 +296,24 @@ def test_dataset_filter(): for idx, sample in enumerate(a_xform_filtered): assert sample % 10 == 0 +def test_dataset_shard(): + length = 9 + a = mx.gluon.data.SimpleDataset([i for i in range(length)]) + shard_0 = a.shard(4, 0) + shard_1 = a.shard(4, 1) + shard_2 = a.shard(4, 2) + shard_3 = a.shard(4, 3) + assert len(shard_0) + len(shard_1) + len(shard_2) + len(shard_3) == length + assert len(shard_0) == 3 + assert len(shard_1) == 2 + assert len(shard_2) == 2 + assert len(shard_3) == 2 + total = 0 + for shard in [shard_0, shard_1, shard_2, shard_3]: + for idx, sample in enumerate(shard): + total += sample + assert total == sum(a) + def test_dataset_take(): length = 100 a = mx.gluon.data.SimpleDataset([i for i in range(length)]) From 2a201ba29d568bb0e87fc027350d5b52b8593c12 Mon Sep 17 00:00:00 2001 From: reminisce Date: Wed, 4 Sep 2019 16:36:50 -0700 Subject: [PATCH 330/813] Add __array_function__ Add test Fix Fix sanity Fix build failure Use array_function protocol only when np.version >= 1.17 Add unit tests for array ufunc protocol Fix pylint Reformat Fix build Fix Refactor test suite for numpy interoperability --- python/mxnet/__init__.py | 4 + python/mxnet/numpy/multiarray.py | 58 ++++- python/mxnet/numpy_dispatch_protocol.py | 214 ++++++++++++++++++ python/mxnet/symbol/numpy/_symbol.py | 2 +- tests/python/gpu/test_operator_gpu.py | 1 + .../unittest/test_numpy_interoperability.py | 211 +++++++++++++++++ tests/python/unittest/test_numpy_ndarray.py | 2 +- tests/python/unittest/test_numpy_op.py | 11 +- 8 files changed, 488 insertions(+), 15 deletions(-) create mode 100644 python/mxnet/numpy_dispatch_protocol.py create mode 100644 tests/python/unittest/test_numpy_interoperability.py diff --git a/python/mxnet/__init__.py b/python/mxnet/__init__.py index e9c1229d7f2f..e1b1a95838c5 100644 --- a/python/mxnet/__init__.py +++ b/python/mxnet/__init__.py @@ -96,3 +96,7 @@ # checks the __version__ attr of MXNet, which is not set on kvstore server due to the # fact that kvstore-server module is imported before the __version__ attr is set. from . import kvstore_server + +from .numpy_dispatch_protocol import _register_array_function, _register_array_ufunc +_register_array_function() +_register_array_ufunc() diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index a43045235503..d48ccf1f4b9c 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -108,6 +108,10 @@ def _get_index(idx): return idx +_NUMPY_ARRAY_FUNCTION_DICT = {} +_NUMPY_ARRAY_UFUNC_DICT = {} + + @set_module('mxnet.numpy') # pylint: disable=invalid-name class ndarray(NDArray): """ @@ -117,6 +121,54 @@ class ndarray(NDArray): floating point number, or something else, etc.). Arrays should be constructed using `array`, `zeros` or `empty`. Currently, only c-contiguous arrays are supported. """ + + @staticmethod + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): # pylint: disable=bad-staticmethod-argument + """ + Dispatch official NumPy unary/binary operator calls on mxnet.numpy.ndarray + to this function. The operators must comply with the ufunc definition in NumPy. + The following code is adapted from CuPy. + """ + if 'out' in kwargs: + # need to unfold tuple argument in kwargs + out = kwargs['out'] + if len(out) != 1: + raise ValueError('The `out` parameter must have exactly one ndarray') + kwargs['out'] = out[0] + + if method == '__call__': + if ufunc.signature is not None: + # we don't support generalised-ufuncs (gufuncs) + return NotImplemented + name = ufunc.__name__ + mx_ufunc = _NUMPY_ARRAY_UFUNC_DICT.get(name, None) + if mx_ufunc is None: + raise ValueError('mxnet.numpy operator `{}` has not been registered in ' + 'the _NUMPY_ARRAY_UFUNC_LIST. Please make sure the implementation' + ' is compatible with NumPy and then add it to the list.' + .format(name)) + return mx_ufunc(*inputs, **kwargs) + else: + return NotImplemented + + @staticmethod + def __array_function__(self, func, types, args, kwargs): # pylint: disable=bad-staticmethod-argument + """ + Dispatch official NumPy operators that comply with the array function protocol to + this function. + """ + mx_np_func = _NUMPY_ARRAY_FUNCTION_DICT.get(func, None) + if mx_np_func is None: + raise ValueError('mxnet.numpy operator `{}` has not been registered in ' + 'the _NUMPY_ARRAY_FUNCTION_LIST. Please make sure the ' + 'implementation is compatible with NumPy and then add ' + 'it to the list.'.format(func)) + # Note: this allows subclasses that don't override + # __array_function__ to handle mxnet.numpy.ndarray objects + if not all(issubclass(t, ndarray) for t in types): + return NotImplemented + return mx_np_func(*args, **kwargs) + def _get_np_basic_indexing(self, key): """ This function indexes ``self`` with a tuple of `slice` objects only. @@ -1238,7 +1290,7 @@ def std(self, axis=None, dtype=None, out=None, ddof=0, keepdims=False): # pylin """Returns the standard deviation of the array elements along given axis.""" return std(self, axis=axis, dtype=dtype, ddof=ddof, keepdims=keepdims, out=out) - def var(self, axis=None, dtype=None, out=None, ddof=0, keepdims=None): # pylint: disable=arguments-differ + def var(self, axis=None, dtype=None, out=None, ddof=0, keepdims=False): # pylint: disable=arguments-differ """Returns the variance of the array elements, along given axis.""" return var(self, axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims) @@ -3739,7 +3791,7 @@ def mean(a, axis=None, dtype=None, out=None, keepdims=False): # pylint: disable @set_module('mxnet.numpy') -def std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=None): +def std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): """ Compute the standard deviation along the specified axis. Returns the standard deviation, a measure of the spread of a distribution, @@ -3806,7 +3858,7 @@ def std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=None): @set_module('mxnet.numpy') -def var(a, axis=None, dtype=None, out=None, ddof=0, keepdims=None): +def var(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): """ Compute the variance along the specified axis. Returns the variance of the array elements, a measure of the spread of a diff --git a/python/mxnet/numpy_dispatch_protocol.py b/python/mxnet/numpy_dispatch_protocol.py new file mode 100644 index 000000000000..ab010fbd593f --- /dev/null +++ b/python/mxnet/numpy_dispatch_protocol.py @@ -0,0 +1,214 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Utils for registering NumPy array function protocol for mxnet.numpy ops.""" + +from __future__ import absolute_import +import functools +import numpy as _np +from . import numpy as mx_np # pylint: disable=reimported +from .numpy.multiarray import _NUMPY_ARRAY_FUNCTION_DICT, _NUMPY_ARRAY_UFUNC_DICT + + +def _find_duplicate(strs): + str_set = set() + for s in strs: + if s in str_set: + return s + else: + str_set.add(s) + return None + + +def _implements(numpy_function): + """Register an __array_function__ implementation for MyArray objects.""" + def decorator(func): + _NUMPY_ARRAY_FUNCTION_DICT[numpy_function] = func + return func + return decorator + + +def with_array_function_protocol(func): + """A decorator for functions that expect array function protocol. + The decorated function only runs when NumPy version >= 1.17.""" + from distutils.version import LooseVersion + cur_np_ver = LooseVersion(_np.__version__) + np_1_17_ver = LooseVersion('1.17') + + @functools.wraps(func) + def _run_with_array_func_proto(*args, **kwargs): + if cur_np_ver >= np_1_17_ver: + try: + func(*args, **kwargs) + except: + raise RuntimeError('Running function {} with NumPy array function protocol failed' + .format(func.__name__)) + + return _run_with_array_func_proto + + +def with_array_ufunc_protocol(func): + """A decorator for functions that expect array ufunc protocol. + The decorated function only runs when NumPy version >= 1.15.""" + from distutils.version import LooseVersion + cur_np_ver = LooseVersion(_np.__version__) + np_1_15_ver = LooseVersion('1.15') + + @functools.wraps(func) + def _run_with_array_ufunc_proto(*args, **kwargs): + if cur_np_ver >= np_1_15_ver: + try: + func(*args, **kwargs) + except: + raise RuntimeError('Running function {} with NumPy array ufunc protocol failed' + .format(func.__name__)) + + return _run_with_array_ufunc_proto + + +_NUMPY_ARRAY_FUNCTION_LIST = [ + 'argmax', + 'broadcast_arrays', + 'broadcast_to', + 'clip', + 'concatenate', + 'copy', + 'cumsum', + 'dot', + 'expand_dims', + 'fix', + 'max', + 'mean', + 'min', + 'ones_like', + 'prod', + 'repeat', + 'reshape', + 'split', + 'squeeze', + 'stack', + 'std', + 'sum', + 'swapaxes', + 'tensordot', + 'tile', + 'transpose', + 'var', + 'zeros_like', +] + + +@with_array_function_protocol +def _register_array_function(): + """Register __array_function__ protocol for mxnet.numpy operators so that + ``mxnet.numpy.ndarray`` can be fed into the official NumPy operators and + dispatched to MXNet implementation. + + Notes + ----- + According the __array_function__ protocol (see the following reference), + there are three kinds of operators that cannot be dispatched using this + protocol: + 1. Universal functions, which already have their own protocol in the official + NumPy package. + 2. Array creation functions. + 3. Dispatch for methods of any kind, e.g., methods on np.random.RandomState objects. + + References + ---------- + https://numpy.org/neps/nep-0018-array-function-protocol.html + """ + dup = _find_duplicate(_NUMPY_ARRAY_FUNCTION_LIST) + if dup is not None: + raise ValueError('Duplicate operator name {} in _NUMPY_ARRAY_FUNCTION_LIST'.format(dup)) + for op_name in _NUMPY_ARRAY_FUNCTION_LIST: + strs = op_name.split('.') + if len(strs) == 1: + mx_np_op = getattr(mx_np, op_name) + onp_op = getattr(_np, op_name) + setattr(mx_np, op_name, _implements(onp_op)(mx_np_op)) + elif len(strs) == 2: + mx_np_submodule = getattr(mx_np, strs[0]) + mx_np_op = getattr(mx_np_submodule, strs[1]) + onp_submodule = getattr(_np, strs[0]) + onp_op = getattr(onp_submodule, strs[1]) + setattr(mx_np_submodule, strs[1], _implements(onp_op)(mx_np_op)) + else: + raise ValueError('Does not support registering __array_function__ protocol ' + 'for operator {}'.format(op_name)) + + +# https://docs.scipy.org/doc/numpy/reference/ufuncs.html#available-ufuncs +_NUMPY_ARRAY_UFUNC_LIST = [ + 'add', + 'subtract', + 'multiply', + # Uncomment divide when mxnet.numpy.true_divide is added + # 'divide', + 'negative', + 'power', + 'mod', + 'absolute', + 'rint', + 'sign', + 'exp', + 'log', + 'log2', + 'log10', + 'expm1', + 'sqrt', + 'square', + 'cbrt', + 'reciprocal', + 'remainder', + 'sin', + 'cos', + 'tan', + 'sinh', + 'cosh', + 'tanh', + 'arcsin', + 'arccos', + 'arctan', + 'arcsinh', + 'arccosh', + 'arctanh', + 'maximum', + 'minimum', + 'ceil', + 'trunc', + 'floor', +] + + +@with_array_ufunc_protocol +def _register_array_ufunc(): + """Register NumPy array ufunc protocol. + + References + ---------- + https://numpy.org/neps/nep-0013-ufunc-overrides.html + """ + dup = _find_duplicate(_NUMPY_ARRAY_UFUNC_LIST) + if dup is not None: + raise ValueError('Duplicate operator name {} in _NUMPY_ARRAY_UFUNC_LIST'.format(dup)) + for op_name in _NUMPY_ARRAY_UFUNC_LIST: + try: + mx_np_op = getattr(mx_np, op_name) + _NUMPY_ARRAY_UFUNC_DICT[op_name] = mx_np_op + except AttributeError: + raise AttributeError('mxnet.numpy does not have operator named {}'.format(op_name)) diff --git a/python/mxnet/symbol/numpy/_symbol.py b/python/mxnet/symbol/numpy/_symbol.py index 12c333b253ba..962fee2d9375 100644 --- a/python/mxnet/symbol/numpy/_symbol.py +++ b/python/mxnet/symbol/numpy/_symbol.py @@ -538,7 +538,7 @@ def std(self, axis=None, dtype=None, out=None, ddof=0, keepdims=False): # pylin """Returns the standard deviation of the array elements along given axis.""" return std(self, axis=axis, dtype=dtype, ddof=ddof, keepdims=keepdims, out=out) - def var(self, axis=None, dtype=None, out=None, ddof=0, keepdims=None): # pylint: disable=arguments-differ,too-many-arguments + def var(self, axis=None, dtype=None, out=None, ddof=0, keepdims=False): # pylint: disable=arguments-differ,too-many-arguments """Returns the variance of the array elements, along given axis.""" return var(self, axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims) diff --git a/tests/python/gpu/test_operator_gpu.py b/tests/python/gpu/test_operator_gpu.py index db550e4254b7..c4d96fb550c0 100644 --- a/tests/python/gpu/test_operator_gpu.py +++ b/tests/python/gpu/test_operator_gpu.py @@ -37,6 +37,7 @@ from test_operator import * from test_numpy_ndarray import * from test_numpy_op import * +from test_numpy_interoperability import * from test_optimizer import * from test_random import * from test_exc_handling import * diff --git a/tests/python/unittest/test_numpy_interoperability.py b/tests/python/unittest/test_numpy_interoperability.py new file mode 100644 index 000000000000..f0c205f72dc4 --- /dev/null +++ b/tests/python/unittest/test_numpy_interoperability.py @@ -0,0 +1,211 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# pylint: skip-file +from __future__ import absolute_import +import numpy as _np +from mxnet import np +from mxnet.test_utils import assert_almost_equal +from mxnet.test_utils import use_np +from common import assertRaises, with_seed +from mxnet.numpy_dispatch_protocol import with_array_function_protocol, with_array_ufunc_protocol +from mxnet.numpy_dispatch_protocol import _NUMPY_ARRAY_FUNCTION_LIST, _NUMPY_ARRAY_UFUNC_LIST + + +class OpArgMngr(object): + """Operator argument manager for storing operator workloads.""" + _args = {} + + @staticmethod + def add_workload(name, *args, **kwargs): + if name not in OpArgMngr._args: + OpArgMngr._args[name] = [] + OpArgMngr._args[name].append({'args': args, 'kwargs': kwargs}) + + @staticmethod + def get_workloads(name): + return OpArgMngr._args.get(name, None) + + +@use_np +def _prepare_workloads(): + array_pool = { + '4x1': np.random.uniform(size=(4, 1)) + 2, + '1x2': np.random.uniform(size=(1, 2)) + 2, + '1x1x0': np.array([[[]]]) + } + + # workloads for array function protocol + OpArgMngr.add_workload('argmax', array_pool['4x1']) + OpArgMngr.add_workload('broadcast_arrays', array_pool['4x1'], array_pool['1x2']) + OpArgMngr.add_workload('broadcast_to', array_pool['4x1'], (4, 2)) + OpArgMngr.add_workload('clip', array_pool['4x1'], 0.2, 0.8) + OpArgMngr.add_workload('concatenate', [array_pool['4x1'], array_pool['4x1']]) + OpArgMngr.add_workload('concatenate', [array_pool['4x1'], array_pool['4x1']], axis=1) + OpArgMngr.add_workload('copy', array_pool['4x1']) + OpArgMngr.add_workload('cumsum', array_pool['4x1']) + OpArgMngr.add_workload('cumsum', array_pool['4x1'], axis=1) + OpArgMngr.add_workload('dot', array_pool['4x1'], array_pool['4x1'].T) + OpArgMngr.add_workload('expand_dims', array_pool['4x1'], -1) + OpArgMngr.add_workload('fix', array_pool['4x1']) + OpArgMngr.add_workload('max', array_pool['4x1']) + OpArgMngr.add_workload('min', array_pool['4x1']) + OpArgMngr.add_workload('mean', array_pool['4x1']) + OpArgMngr.add_workload('mean', array_pool['4x1'], axis=0, keepdims=True) + OpArgMngr.add_workload('ones_like', array_pool['4x1']) + OpArgMngr.add_workload('prod', array_pool['4x1']) + OpArgMngr.add_workload('repeat', array_pool['4x1'], 3) + OpArgMngr.add_workload('reshape', array_pool['4x1'], -1) + OpArgMngr.add_workload('split', array_pool['4x1'], 2) + OpArgMngr.add_workload('squeeze', array_pool['4x1']) + OpArgMngr.add_workload('stack', [array_pool['4x1']] * 2) + OpArgMngr.add_workload('std', array_pool['4x1']) + OpArgMngr.add_workload('sum', array_pool['4x1']) + OpArgMngr.add_workload('swapaxes', array_pool['4x1'], 0, 1) + OpArgMngr.add_workload('tensordot', array_pool['4x1'], array_pool['4x1']) + OpArgMngr.add_workload('tile', array_pool['4x1'], 2) + OpArgMngr.add_workload('tile', np.array([[[]]]), (3, 2, 5)) + OpArgMngr.add_workload('transpose', array_pool['4x1']) + OpArgMngr.add_workload('var', array_pool['4x1']) + OpArgMngr.add_workload('zeros_like', array_pool['4x1']) + + # workloads for array ufunc protocol + OpArgMngr.add_workload('add', array_pool['4x1'], array_pool['1x2']) + OpArgMngr.add_workload('add', array_pool['4x1'], 2) + OpArgMngr.add_workload('add', 2, array_pool['4x1']) + OpArgMngr.add_workload('add', array_pool['4x1'], array_pool['1x1x0']) + OpArgMngr.add_workload('subtract', array_pool['4x1'], array_pool['1x2']) + OpArgMngr.add_workload('subtract', array_pool['4x1'], 2) + OpArgMngr.add_workload('subtract', 2, array_pool['4x1']) + OpArgMngr.add_workload('subtract', array_pool['4x1'], array_pool['1x1x0']) + OpArgMngr.add_workload('multiply', array_pool['4x1'], array_pool['1x2']) + OpArgMngr.add_workload('multiply', array_pool['4x1'], 2) + OpArgMngr.add_workload('multiply', 2, array_pool['4x1']) + OpArgMngr.add_workload('multiply', array_pool['4x1'], array_pool['1x1x0']) + OpArgMngr.add_workload('power', array_pool['4x1'], array_pool['1x2']) + OpArgMngr.add_workload('power', array_pool['4x1'], 2) + OpArgMngr.add_workload('power', 2, array_pool['4x1']) + OpArgMngr.add_workload('power', array_pool['4x1'], array_pool['1x1x0']) + OpArgMngr.add_workload('mod', array_pool['4x1'], array_pool['1x2']) + OpArgMngr.add_workload('mod', array_pool['4x1'], 2) + OpArgMngr.add_workload('mod', 2, array_pool['4x1']) + OpArgMngr.add_workload('mod', array_pool['4x1'], array_pool['1x1x0']) + OpArgMngr.add_workload('remainder', array_pool['4x1'], array_pool['1x2']) + OpArgMngr.add_workload('remainder', array_pool['4x1'], 2) + OpArgMngr.add_workload('remainder', 2, array_pool['4x1']) + OpArgMngr.add_workload('remainder', array_pool['4x1'], array_pool['1x1x0']) + OpArgMngr.add_workload('maximum', array_pool['4x1'], array_pool['1x2']) + OpArgMngr.add_workload('maximum', array_pool['4x1'], 2) + OpArgMngr.add_workload('maximum', 2, array_pool['4x1']) + OpArgMngr.add_workload('maximum', array_pool['4x1'], array_pool['1x1x0']) + OpArgMngr.add_workload('minimum', array_pool['4x1'], array_pool['1x2']) + OpArgMngr.add_workload('minimum', array_pool['4x1'], 2) + OpArgMngr.add_workload('minimum', 2, array_pool['4x1']) + OpArgMngr.add_workload('minimum', array_pool['4x1'], array_pool['1x1x0']) + OpArgMngr.add_workload('negative', array_pool['4x1']) + OpArgMngr.add_workload('absolute', array_pool['4x1']) + OpArgMngr.add_workload('rint', array_pool['4x1']) + OpArgMngr.add_workload('sign', array_pool['4x1']) + OpArgMngr.add_workload('exp', array_pool['4x1']) + OpArgMngr.add_workload('log', array_pool['4x1']) + OpArgMngr.add_workload('log2', array_pool['4x1']) + OpArgMngr.add_workload('log10', array_pool['4x1']) + OpArgMngr.add_workload('expm1', array_pool['4x1']) + OpArgMngr.add_workload('sqrt', array_pool['4x1']) + OpArgMngr.add_workload('square', array_pool['4x1']) + OpArgMngr.add_workload('cbrt', array_pool['4x1']) + OpArgMngr.add_workload('reciprocal', array_pool['4x1']) + OpArgMngr.add_workload('sin', array_pool['4x1']) + OpArgMngr.add_workload('cos', array_pool['4x1']) + OpArgMngr.add_workload('tan', array_pool['4x1']) + OpArgMngr.add_workload('sinh', array_pool['4x1']) + OpArgMngr.add_workload('cosh', array_pool['4x1']) + OpArgMngr.add_workload('tanh', array_pool['4x1']) + OpArgMngr.add_workload('arcsin', array_pool['4x1'] - 2) + OpArgMngr.add_workload('arccos', array_pool['4x1'] - 2) + OpArgMngr.add_workload('arctan', array_pool['4x1']) + OpArgMngr.add_workload('arcsinh', array_pool['4x1']) + OpArgMngr.add_workload('arccosh', array_pool['4x1']) + OpArgMngr.add_workload('arctanh', array_pool['4x1'] - 2) + OpArgMngr.add_workload('ceil', array_pool['4x1']) + OpArgMngr.add_workload('trunc', array_pool['4x1']) + OpArgMngr.add_workload('floor', array_pool['4x1']) + + +_prepare_workloads() + + +def _get_numpy_op_output(onp_op, *args, **kwargs): + onp_args = [arg.asnumpy() if isinstance(arg, np.ndarray) else arg for arg in args] + onp_kwargs = {k: v.asnumpy() if isinstance(v, np.ndarray) else v for k, v in kwargs.items()} + for i, v in enumerate(onp_args): + if isinstance(v, (list, tuple)): + new_arrs = [a.asnumpy() if isinstance(a, np.ndarray) else a for a in v] + onp_args[i] = new_arrs + + return onp_op(*onp_args, **onp_kwargs) + + +def _check_interoperability_helper(op_name, *args, **kwargs): + strs = op_name.split('.') + if len(strs) == 1: + onp_op = getattr(_np, op_name) + elif len(strs) == 2: + onp_op = getattr(getattr(_np, strs[0]), strs[1]) + else: + assert False + out = onp_op(*args, **kwargs) + expected_out = _get_numpy_op_output(onp_op, *args, **kwargs) + if isinstance(out, (tuple, list)): + assert type(out) == type(expected_out) + for arr in out: + assert isinstance(arr, np.ndarray) + for arr, expected_arr in zip(out, expected_out): + assert isinstance(arr, np.ndarray) + assert_almost_equal(arr.asnumpy(), expected_arr, rtol=1e-3, atol=1e-4, use_broadcast=False) + else: + assert isinstance(out, np.ndarray) + assert_almost_equal(out.asnumpy(), expected_out, rtol=1e-3, atol=1e-4, use_broadcast=False) + + +def check_interoperability(op_list): + for name in op_list: + workloads = OpArgMngr.get_workloads(name) + assert workloads is not None, 'Workloads for operator `{}` has not been ' \ + 'added for checking interoperability with ' \ + 'the official NumPy.'.format(name) + for workload in workloads: + _check_interoperability_helper(name, *workload['args'], **workload['kwargs']) + + +@with_seed() +@use_np +@with_array_function_protocol +def test_np_array_function_protocol(): + check_interoperability(_NUMPY_ARRAY_FUNCTION_LIST) + + +@with_seed() +@use_np +@with_array_ufunc_protocol +def test_np_array_ufunc_protocol(): + check_interoperability(_NUMPY_ARRAY_UFUNC_LIST) + + +if __name__ == '__main__': + import nose + nose.runmodule() diff --git a/tests/python/unittest/test_numpy_ndarray.py b/tests/python/unittest/test_numpy_ndarray.py index bffa7a00dccb..b60316724808 100644 --- a/tests/python/unittest/test_numpy_ndarray.py +++ b/tests/python/unittest/test_numpy_ndarray.py @@ -729,8 +729,8 @@ def test_setitem_autograd(np_array, index): ((3, 0), [2, (slice(None, None, None)), (slice(None, None, None), None)]), ] for shape, indices in shapes_indices: + np_array = _np.zeros(shape) for index in indices: - np_array = np.zeros(shape) test_getitem(np_array, index) test_setitem(np_array, index) test_getitem_autograd(np_array, index) diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index ed4375939688..fde88f8f7dec 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -20,16 +20,14 @@ import numpy as _np import mxnet as mx from mxnet import np, npx -from mxnet.base import MXNetError from mxnet.gluon import HybridBlock from mxnet.base import MXNetError from mxnet.test_utils import same, assert_almost_equal, rand_shape_nd, rand_ndarray from mxnet.test_utils import check_numeric_gradient, use_np, collapse_sum_like from common import assertRaises, with_seed import random -import collections import scipy.stats as ss -from mxnet.test_utils import verify_generator, gen_buckets_probs_with_ppf, retry +from mxnet.test_utils import verify_generator, gen_buckets_probs_with_ppf import platform @@ -657,7 +655,6 @@ def hybrid_forward(self, F, a): for hybridize in [True, False]: for shape_pair in shape_pairs: shape1, shape2 = shape_pair - print(shape1, shape2) test_reshape = TestReshape(shape2) if hybridize: test_reshape.hybridize() @@ -742,7 +739,6 @@ def hybrid_forward(self, F, a, *args, **kwargs): test_prod.hybridize() x = np.array(_np.random.uniform(-2.0, 2.0, size=shape), dtype=itype) x.attach_grad() - print(x.grad.dtype) expected_ret = _np.prod(x.asnumpy(), axis=axis, keepdims=keepdims) expected_ret = expected_ret.astype(dtype) with mx.autograd.record(): @@ -963,7 +959,6 @@ def __init__(self, func): self._func = func def hybrid_forward(self, F, a, *args, **kwargs): - print(self._func) return getattr(F.np, self._func)(a) np_func = getattr(_np, func) @@ -1588,10 +1583,6 @@ def test_np_random(): for shape in shapes: for dtype in dtypes: for op_name in op_names: - print('-------------------------------') - print(op_name) - print(shape) - print(dtype) op = getattr(np.random, op_name, None) assert op is not None out = op(size=shape, dtype=dtype) From 681d9a7c611fd7b701615c45e857ae712ea10676 Mon Sep 17 00:00:00 2001 From: reminisce Date: Tue, 17 Sep 2019 13:21:28 -0700 Subject: [PATCH 331/813] Improved error mesages --- python/mxnet/numpy/multiarray.py | 13 ++++++++----- python/mxnet/numpy_dispatch_protocol.py | 10 ++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index d48ccf1f4b9c..52dc9fb7a685 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -144,8 +144,10 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): # pylint: disable= mx_ufunc = _NUMPY_ARRAY_UFUNC_DICT.get(name, None) if mx_ufunc is None: raise ValueError('mxnet.numpy operator `{}` has not been registered in ' - 'the _NUMPY_ARRAY_UFUNC_LIST. Please make sure the implementation' - ' is compatible with NumPy and then add it to the list.' + 'the _NUMPY_ARRAY_UFUNC_LIST. Please make sure you are ' + 'using NumPy >= 1.15.0 and the operator implementation ' + 'is compatible with NumPy. Then add the operator name ' + 'to the list.' .format(name)) return mx_ufunc(*inputs, **kwargs) else: @@ -160,9 +162,10 @@ def __array_function__(self, func, types, args, kwargs): # pylint: disable=bad- mx_np_func = _NUMPY_ARRAY_FUNCTION_DICT.get(func, None) if mx_np_func is None: raise ValueError('mxnet.numpy operator `{}` has not been registered in ' - 'the _NUMPY_ARRAY_FUNCTION_LIST. Please make sure the ' - 'implementation is compatible with NumPy and then add ' - 'it to the list.'.format(func)) + 'the _NUMPY_ARRAY_FUNCTION_LIST. Please make sure you are ' + 'using NumPy >= 1.17.0 and the operator ' + 'implementation is compatible with NumPy. Then add ' + 'the operator name to the list.'.format(func)) # Note: this allows subclasses that don't override # __array_function__ to handle mxnet.numpy.ndarray objects if not all(issubclass(t, ndarray) for t in types): diff --git a/python/mxnet/numpy_dispatch_protocol.py b/python/mxnet/numpy_dispatch_protocol.py index ab010fbd593f..93b81b1a868d 100644 --- a/python/mxnet/numpy_dispatch_protocol.py +++ b/python/mxnet/numpy_dispatch_protocol.py @@ -54,9 +54,10 @@ def _run_with_array_func_proto(*args, **kwargs): if cur_np_ver >= np_1_17_ver: try: func(*args, **kwargs) - except: + except Exception as e: raise RuntimeError('Running function {} with NumPy array function protocol failed' - .format(func.__name__)) + ' with exception {}' + .format(func.__name__, str(e))) return _run_with_array_func_proto @@ -73,9 +74,10 @@ def _run_with_array_ufunc_proto(*args, **kwargs): if cur_np_ver >= np_1_15_ver: try: func(*args, **kwargs) - except: + except Exception as e: raise RuntimeError('Running function {} with NumPy array ufunc protocol failed' - .format(func.__name__)) + ' with exception {}' + .format(func.__name__, str(e))) return _run_with_array_ufunc_proto From 477e6f7a00426ddb4d0e6bfb160a2ab9c036029a Mon Sep 17 00:00:00 2001 From: reminisce Date: Tue, 17 Sep 2019 22:30:49 -0700 Subject: [PATCH 332/813] Fix np.choice --- tests/python/unittest/test_numpy_op.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index fde88f8f7dec..1656716b4a35 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -1723,7 +1723,7 @@ def test_indexing_mode(sampler, set_size, samples_size, replace, weight=None): samples = sampler(a) assert len(samples) == samples_size if not replace: - assert len(_np.unique(samples)) == samples_size + assert len(_np.unique(samples.asnumpy())) == samples_size num_classes = 10 num_samples = 10 ** 8 From 479ab46af4d0f92e199c910be7e04b1924680543 Mon Sep 17 00:00:00 2001 From: Sheng Zha Date: Wed, 18 Sep 2019 13:30:21 -0700 Subject: [PATCH 333/813] [MEMORY] retry GPU memory allocation if fragmented (#16194) --- src/storage/pooled_storage_manager.h | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/storage/pooled_storage_manager.h b/src/storage/pooled_storage_manager.h index 91eb536ec7bd..6e54ddd7e52a 100644 --- a/src/storage/pooled_storage_manager.h +++ b/src/storage/pooled_storage_manager.h @@ -153,8 +153,16 @@ void GPUPooledStorageManager::Alloc(Storage::Handle* handle) { void* ret = nullptr; cudaError_t e = cudaMalloc(&ret, size); - if (e != cudaSuccess && e != cudaErrorCudartUnloading) { - LOG(FATAL) << "cudaMalloc failed: " << cudaGetErrorString(e); + if (e != cudaSuccess) { + if (e == cudaErrorMemoryAllocation) { + ReleaseAll(); + e = cudaMalloc(&ret, size); + if (e != cudaSuccess && e != cudaErrorCudartUnloading) { + LOG(FATAL) << "cudaMalloc retry failed: " << cudaGetErrorString(e); + } + } else if (e != cudaErrorCudartUnloading) { + LOG(FATAL) << "cudaMalloc failed: " << cudaGetErrorString(e); + } } used_memory_ += size; handle->dptr = ret; @@ -328,8 +336,16 @@ void GPUPooledRoundedStorageManager::Alloc(Storage::Handle* handle) { void* ret = nullptr; cudaError_t e = cudaMalloc(&ret, size); - if (e != cudaSuccess && e != cudaErrorCudartUnloading) { - LOG(FATAL) << "cudaMalloc failed: " << cudaGetErrorString(e); + if (e != cudaSuccess) { + if (e == cudaErrorMemoryAllocation) { + ReleaseAll(); + e = cudaMalloc(&ret, size); + if (e != cudaSuccess && e != cudaErrorCudartUnloading) { + LOG(FATAL) << "cudaMalloc retry failed: " << cudaGetErrorString(e); + } + } else if (e != cudaErrorCudartUnloading) { + LOG(FATAL) << "cudaMalloc failed: " << cudaGetErrorString(e); + } } used_memory_ += size; handle->dptr = ret; From 995b477d940bcba9927b34cd7bda8cabc85628a5 Mon Sep 17 00:00:00 2001 From: Zach Kimberg Date: Wed, 18 Sep 2019 13:33:28 -0700 Subject: [PATCH 334/813] Fix README Build Status (#16183) * Fix README Build Status * Add labels to Jenkins Badges * Add more line breaks --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 849a54f3c60a..04b7bcf9e7fa 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Apache MXNet (incubating) for Deep Learning ===== | Master | Docs | License | | :-------------:|:-------------:|:--------:| -| [![Build Status](http://jenkins.mxnet-ci.amazon-ml.com/job/incubator-mxnet/job/master/badge/icon)](http://jenkins.mxnet-ci.amazon-ml.com/job/incubator-mxnet/job/master/) | [![Documentation Status](http://jenkins.mxnet-ci.amazon-ml.com/job/restricted-website-build/badge/icon)](https://mxnet.incubator.apache.org/) | [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE) | +[![CentOS CPU Build Status](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/centos-cpu/job/master/badge/icon?subject=build%20centos%20cpu)](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/centos-cpu/job/master/) [![CentOS GPU Build Status](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/centos-gpu/job/master/badge/icon?subject=build%20centos%20gpu)](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/centos-gpu/job/master/) [![Clang Build Status](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/clang/job/master/badge/icon?subject=build%20clang)](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/clang/job/master/)
[![Edge Build Status](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/edge/job/master/badge/icon?subject=build%20edge)](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/edge/job/master/) [![Miscellaneous Build Status](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/miscellaneous/job/master/badge/icon?subject=build%20miscellaneous)](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/miscellaneous/job/master/) [![Sanity Build Status](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/sanity/job/master/badge/icon?subject=build%20sanity)](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/sanity/job/master/)
[![Unix CPU Build Status](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/unix-cpu/job/master/badge/icon?subject=build%20unix%20cpu)](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/unix-cpu/job/master/) [![Unix GPU Build Status](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/unix-gpu/job/master/badge/icon?subject=build%20unix%20gpu)](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/unix-gpu/job/master/) [![Website Build Status](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/website/job/master/badge/icon?subject=build%20website)](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/website/job/master/)
[![Windows CPU Build Status](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/windows-cpu/job/master/badge/icon?subject=build%20windows%20cpu)](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/windows-cpu/job/master/) [![Windows GPU Build Status](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/windows-gpu/job/master/badge/icon?subject=build%20windows%20gpu)](http://jenkins.mxnet-ci.amazon-ml.com/job/mxnet-validation/job/windows-gpu/job/master/) | [![Documentation Status](http://jenkins.mxnet-ci.amazon-ml.com/job/restricted-website-build/badge/icon)](https://mxnet.incubator.apache.org/) | [![GitHub license](http://dmlc.github.io/img/apache2.svg)](./LICENSE) | ![banner](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/image/banner.png) From 1e058a31bcf565a2a7c868e6726ab77527513ee7 Mon Sep 17 00:00:00 2001 From: Xingjian Shi Date: Wed, 18 Sep 2019 14:43:38 -0700 Subject: [PATCH 335/813] add exception check for numpy reshape (#16180) --- tests/python/unittest/test_exc_handling.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/python/unittest/test_exc_handling.py b/tests/python/unittest/test_exc_handling.py index 960a4e8d1364..f029d09b5f54 100644 --- a/tests/python/unittest/test_exc_handling.py +++ b/tests/python/unittest/test_exc_handling.py @@ -190,6 +190,15 @@ def check_resize(): assert_raises(MXNetError, check_resize) +@with_seed() +def test_np_reshape_exception(): + a = mx.np.ones((10, 10)) + a.reshape((-1,)).asnumpy() # Check no-raise + assert_raises(MXNetError, lambda: a.reshape((1,))) + assert_raises(MXNetError, lambda: mx.np.reshape(a, (1,))) + assert_raises(MXNetError, lambda: mx.np.reshape(a, (-1, 3))) + + if __name__ == '__main__': import nose nose.runmodule() From 53b2b40273dbd7659a75470ddcb2a495862b3141 Mon Sep 17 00:00:00 2001 From: "Joshua Z. Zhang" Date: Wed, 18 Sep 2019 18:23:06 -0700 Subject: [PATCH 336/813] improve dataloader signals and messages (#16114) * improve dataloader signals and messages * address comments * fix spawn tests on windows --- python/mxnet/gluon/data/dataloader.py | 50 +++++++++++++++++++----- tests/python/unittest/test_gluon_data.py | 5 ++- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/python/mxnet/gluon/data/dataloader.py b/python/mxnet/gluon/data/dataloader.py index 9f0939ec7f37..68412267da6b 100644 --- a/python/mxnet/gluon/data/dataloader.py +++ b/python/mxnet/gluon/data/dataloader.py @@ -24,6 +24,7 @@ import pickle import io import sys +import signal import multiprocessing import multiprocessing.queues from multiprocessing.reduction import ForkingPickler @@ -426,7 +427,8 @@ def _thread_worker_fn(samples, batchify_fn, dataset): class _MultiWorkerIter(object): """Internal multi-worker iterator for DataLoader.""" def __init__(self, worker_pool, batchify_fn, batch_sampler, pin_memory=False, - pin_device_id=0, worker_fn=_worker_fn, prefetch=0, dataset=None, data_loader=None): + pin_device_id=0, worker_fn=_worker_fn, prefetch=0, dataset=None, + data_loader=None, timeout=120): self._worker_pool = worker_pool self._batchify_fn = batchify_fn self._batch_sampler = batch_sampler @@ -439,6 +441,7 @@ def __init__(self, worker_pool, batchify_fn, batch_sampler, pin_memory=False, self._pin_device_id = pin_device_id self._dataset = dataset self._data_loader = data_loader + self._timeout = timeout # pre-fetch for _ in range(prefetch): self._push_next() @@ -465,12 +468,29 @@ def __next__(self): assert self._rcvd_idx < self._sent_idx, "rcvd_idx must be smaller than sent_idx" assert self._rcvd_idx in self._data_buffer, "fatal error with _push_next, rcvd_idx missing" ret = self._data_buffer.pop(self._rcvd_idx) - batch = pickle.loads(ret.get()) if self._dataset is None else ret.get() - if self._pin_memory: - batch = _as_in_context(batch, context.cpu_pinned(self._pin_device_id)) - batch = batch[0] if len(batch) == 1 else batch - self._rcvd_idx += 1 - return batch + try: + if self._dataset is None: + batch = pickle.loads(ret.get(self._timeout)) + else: + batch = ret.get(self._timeout) + if self._pin_memory: + batch = _as_in_context(batch, context.cpu_pinned(self._pin_device_id)) + batch = batch[0] if len(batch) == 1 else batch + self._rcvd_idx += 1 + return batch + except multiprocessing.context.TimeoutError: + msg = '''Worker timed out after {} seconds. This might be caused by \n + - Slow transform. Please increase timeout to allow slower data loading in each worker. + '''.format(self._timeout) + if not isinstance(self._worker_pool, multiprocessing.pool.ThreadPool): + msg += '''- Insufficient shared_memory if `timeout` is large enough. + Please consider reduce `num_workers` or increase shared_memory in system. + ''' + print(msg) + raise + except Exception: + self._worker_pool.terminate() + raise def next(self): return self.__next__() @@ -537,16 +557,22 @@ def default_batchify_fn(data): If ``True``, use threading pool instead of multiprocessing pool. Using threadpool can avoid shared memory usage. If `DataLoader` is more IO bounded or GIL is not a killing problem, threadpool version may achieve better performance than multiprocessing. - + timeout : int, default is 120 + The timeout in seconds for each worker to fetch a batch data. Only modify this number + unless you are experiencing timeout and you know it's due to slow data loading. + Sometimes full `shared_memory` will cause all workers to hang and causes timeout. In these + cases please reduce `num_workers` or increase system `shared_memory` size instead. """ def __init__(self, dataset, batch_size=None, shuffle=False, sampler=None, last_batch=None, batch_sampler=None, batchify_fn=None, num_workers=0, pin_memory=False, pin_device_id=0, - prefetch=None, thread_pool=False): + prefetch=None, thread_pool=False, timeout=120): self._dataset = dataset self._pin_memory = pin_memory self._pin_device_id = pin_device_id self._thread_pool = thread_pool + self._timeout = timeout + assert timeout > 0, "timeout must be positive, given {}".format(timeout) if batch_sampler is None: if batch_size is None: @@ -577,9 +603,13 @@ def __init__(self, dataset, batch_size=None, shuffle=False, sampler=None, initializer=_thread_worker_initializer, initargs=(is_np_shape(), is_np_array())) else: + # set ignore keyboard interupt signal before forking processes + original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) self._worker_pool = multiprocessing.Pool( self._num_workers, initializer=_worker_initializer, initargs=[self._dataset, is_np_shape(), is_np_array()]) + # resume keyboard interupt signal in main process + signal.signal(signal.SIGINT, original_sigint_handler) if batchify_fn is None: if num_workers > 0: self._batchify_fn = default_mp_batchify_fn @@ -604,7 +634,7 @@ def same_process_iter(): worker_fn=_thread_worker_fn if self._thread_pool else _worker_fn, prefetch=self._prefetch, dataset=self._dataset if self._thread_pool else None, - data_loader=self) + data_loader=self, timeout=self._timeout) def __len__(self): return len(self._batch_sampler) diff --git a/tests/python/unittest/test_gluon_data.py b/tests/python/unittest/test_gluon_data.py index 80bea2a511f2..f87228739c36 100644 --- a/tests/python/unittest/test_gluon_data.py +++ b/tests/python/unittest/test_gluon_data.py @@ -249,7 +249,10 @@ def test_multi_worker_forked_data_loader(): @with_seed() def test_multi_worker_dataloader_release_pool(): # will trigger too many open file if pool is not released properly - for _ in range(100): + if os.name == 'nt': + print('Skip for windows since spawn on windows is too expensive.') + return + for _ in range(10): A = np.random.rand(999, 2000) D = mx.gluon.data.DataLoader(A, batch_size=8, num_workers=8) the_iter = iter(D) From 66c4207faec92b33550381da31ebe4e1dcda6c6c Mon Sep 17 00:00:00 2001 From: Xi Wang Date: Thu, 19 Sep 2019 12:00:15 +0800 Subject: [PATCH 337/813] [Numpy] Numpy behavior normal distribution (#16109) * normal implemented * numpy behavior normal imlemented * retrigger CI * retrigger CI * regrigger ci * add normal parameter check * add raise for normal * remove dead code --- python/mxnet/ndarray/numpy/random.py | 38 ++-- python/mxnet/numpy/random.py | 10 +- python/mxnet/symbol/numpy/random.py | 39 ++-- src/operator/numpy/random/dist_common.cc | 43 ++++ src/operator/numpy/random/dist_common.cu | 43 ++++ src/operator/numpy/random/dist_common.h | 7 + src/operator/numpy/random/np_normal_op.cc | 69 ++++++ src/operator/numpy/random/np_normal_op.cu | 35 ++++ src/operator/numpy/random/np_normal_op.h | 233 +++++++++++++++++++++ src/operator/numpy/random/np_uniform_op.cc | 6 + tests/python/unittest/test_numpy_op.py | 1 - 11 files changed, 482 insertions(+), 42 deletions(-) create mode 100644 src/operator/numpy/random/dist_common.cc create mode 100644 src/operator/numpy/random/dist_common.cu create mode 100644 src/operator/numpy/random/np_normal_op.cc create mode 100644 src/operator/numpy/random/np_normal_op.cu create mode 100644 src/operator/numpy/random/np_normal_op.h diff --git a/python/mxnet/ndarray/numpy/random.py b/python/mxnet/ndarray/numpy/random.py index 9372beaf1e92..883c56e12393 100644 --- a/python/mxnet/ndarray/numpy/random.py +++ b/python/mxnet/ndarray/numpy/random.py @@ -21,7 +21,6 @@ from ...context import current_context from . import _internal as _npi from ..ndarray import NDArray -from ...base import numeric_types __all__ = ['randint', 'uniform', 'normal', "choice"] @@ -145,7 +144,7 @@ def uniform(low=0.0, high=1.0, size=None, dtype=None, ctx=None, out=None): ctx=ctx, dtype=dtype, out=out) -def normal(loc=0.0, scale=1.0, size=None, **kwargs): +def normal(loc=0.0, scale=1.0, size=None, dtype=None, **kwargs): """Draw random samples from a normal (Gaussian) distribution. Samples are distributed according to a normal distribution parametrized @@ -166,31 +165,36 @@ def normal(loc=0.0, scale=1.0, size=None, **kwargs): Data type of output samples. Default is 'float32' ctx : Context, optional Device context of output. Default is current context. - out : ``ndarray``, optional - Store output to an existing ``ndarray``. Returns ------- out : ndarray Drawn samples from the parameterized normal distribution. - - Notes - ----- - This function currently does not support ``loc`` and ``scale`` as ndarrays. """ - dtype = kwargs.pop('dtype', None) + from ...numpy import ndarray as np_ndarray + input_type = (isinstance(loc, np_ndarray), isinstance(scale, np_ndarray)) + ctx = kwargs.pop('ctx', None) + out = kwargs.pop('out', None) if dtype is None: dtype = 'float32' - ctx = kwargs.pop('ctx', None) if ctx is None: ctx = current_context() - out = kwargs.pop('out', None) - if size is None and out is None: - size = () - if (not isinstance(loc, numeric_types)) or (not isinstance(scale, numeric_types)): - raise NotImplementedError('np.random.normal only supports loc and scale of ' - 'numeric types for now') - return _npi.random_normal(loc, scale, shape=size, dtype=dtype, ctx=ctx, out=out, **kwargs) + if out is not None: + size = out.shape + if size == (): + size = None + if input_type == (True, True): + return _npi.normal(loc, scale, loc=None, scale=None, size=size, + ctx=ctx, dtype=dtype, out=out) + elif input_type == (False, True): + return _npi.normal(scale, loc=loc, scale=None, size=size, + ctx=ctx, dtype=dtype, out=out) + elif input_type == (True, False): + return _npi.normal(loc, loc=None, scale=scale, size=size, + ctx=ctx, dtype=dtype, out=out) + else: + return _npi.normal(loc=loc, scale=scale, size=size, + ctx=ctx, dtype=dtype, out=out) def multinomial(n, pvals, size=None): diff --git a/python/mxnet/numpy/random.py b/python/mxnet/numpy/random.py index aace767c8d55..f89b8cac9871 100644 --- a/python/mxnet/numpy/random.py +++ b/python/mxnet/numpy/random.py @@ -110,7 +110,7 @@ def uniform(low=0.0, high=1.0, size=None, dtype=None, ctx=None, out=None): return _mx_nd_np.random.uniform(low, high, size=size, ctx=ctx, dtype=dtype, out=out) -def normal(loc=0.0, scale=1.0, size=None, **kwargs): +def normal(loc=0.0, scale=1.0, size=None, dtype=None, **kwargs): """Draw random samples from a normal (Gaussian) distribution. Samples are distributed according to a normal distribution parametrized @@ -130,7 +130,7 @@ def normal(loc=0.0, scale=1.0, size=None, **kwargs): dtype : {'float16', 'float32', 'float64'}, optional Data type of output samples. Default is 'float32' ctx : Context, optional - Device context of output. Default is current context. + Device context of output, default is current context. out : ``ndarray``, optional Store output to an existing ``ndarray``. @@ -138,12 +138,8 @@ def normal(loc=0.0, scale=1.0, size=None, **kwargs): ------- out : ndarray Drawn samples from the parameterized normal distribution. - - Notes - ----- - This function currently does not support ``loc`` and ``scale`` as ndarrays. """ - return _mx_nd_np.random.normal(loc, scale, size, **kwargs) + return _mx_nd_np.random.normal(loc, scale, size, dtype, **kwargs) def multinomial(n, pvals, size=None, **kwargs): diff --git a/python/mxnet/symbol/numpy/random.py b/python/mxnet/symbol/numpy/random.py index 523983bac20a..a5ccbc1d1995 100644 --- a/python/mxnet/symbol/numpy/random.py +++ b/python/mxnet/symbol/numpy/random.py @@ -20,7 +20,6 @@ from __future__ import absolute_import from ...context import current_context from . import _internal as _npi -from ...base import numeric_types __all__ = ['randint', 'uniform', 'normal'] @@ -144,7 +143,7 @@ def uniform(low=0.0, high=1.0, size=None, dtype=None, ctx=None, out=None): ctx=ctx, dtype=dtype, out=out) -def normal(loc=0.0, scale=1.0, size=None, **kwargs): +def normal(loc=0.0, scale=1.0, size=None, dtype=None, **kwargs): """Draw random samples from a normal (Gaussian) distribution. Samples are distributed according to a normal distribution parametrized @@ -162,34 +161,40 @@ def normal(loc=0.0, scale=1.0, size=None, **kwargs): samples are drawn. If size is `None` (default), a scalar tensor containing a single value is returned if loc and scale are both scalars. dtype : {'float16', 'float32', 'float64'}, optional - Data type of output samples. Default is 'float32' + Data type of output samples. Default is 'float32'. ctx : Context, optional Device context of output. Default is current context. - out : ``ndarray``, optional - Store output to an existing ``ndarray``. Returns ------- out : _Symbol (symbol representing `mxnet.numpy.ndarray` in computational graphs) Drawn samples from the parameterized normal distribution. - - Notes - ----- - This function currently does not support ``loc`` and ``scale`` as `_Symbol`s. """ - dtype = kwargs.pop('dtype', None) + from ._symbol import _Symbol as np_symbol + input_type = (isinstance(loc, np_symbol), isinstance(scale, np_symbol)) + ctx = kwargs.pop('ctx', None) + out = kwargs.pop('out', None) if dtype is None: dtype = 'float32' - ctx = kwargs.pop('ctx', None) if ctx is None: ctx = current_context() out = kwargs.pop('out', None) - if size is None and out is None: - size = () - if (not isinstance(loc, numeric_types)) or (not isinstance(scale, numeric_types)): - raise NotImplementedError('np.random.normal only supports loc and scale of ' - 'numeric types for now') - return _npi.random_normal(loc, scale, shape=size, dtype=dtype, ctx=ctx, out=out, **kwargs) + if out is not None: + size = out.shape + if size == (): + size = None + if input_type == (True, True): + return _npi.normal(loc, scale, loc=None, scale=None, size=size, + ctx=ctx, dtype=dtype, out=out) + elif input_type == (False, True): + return _npi.normal(scale, loc=loc, scale=None, size=size, + ctx=ctx, dtype=dtype, out=out) + elif input_type == (True, False): + return _npi.normal(loc, loc=None, scale=scale, size=size, + ctx=ctx, dtype=dtype, out=out) + else: + return _npi.normal(loc=loc, scale=scale, size=size, + ctx=ctx, dtype=dtype, out=out) def choice(a, size=None, replace=True, p=None, **kwargs): diff --git a/src/operator/numpy/random/dist_common.cc b/src/operator/numpy/random/dist_common.cc new file mode 100644 index 000000000000..925565654a11 --- /dev/null +++ b/src/operator/numpy/random/dist_common.cc @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2015 by Contributors + * \file dist_common.cc + * \brief Function definition of common functions for distributions + * \with two parameters. + */ + +#include "./dist_common.h" + +namespace mxnet { +namespace op { + +template <> +void _copy(float *dst, float *src) { + *dst = *src; +} + +template <> +void _copy(double *dst, double *src) { + *dst = *src; +} + +} // namespace op +} // namespace mxnet diff --git a/src/operator/numpy/random/dist_common.cu b/src/operator/numpy/random/dist_common.cu new file mode 100644 index 000000000000..7dde0123e099 --- /dev/null +++ b/src/operator/numpy/random/dist_common.cu @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2015 by Contributors + * \file dist_common.cuh + * \brief Function definition of common functions for distributions + * \with two parameters. + */ + +#include "./dist_common.h" + +namespace mxnet { +namespace op { + +template <> +void _copy(float *dst, float *src) { +CUDA_CALL(cudaMemcpy(dst, src, sizeof(float), cudaMemcpyDeviceToHost)); +} + +template <> +void _copy(double *dst, double *src) { +CUDA_CALL(cudaMemcpy(dst, src, sizeof(double), cudaMemcpyDeviceToHost)); +} + +} // namespace op +} // namespace mxnet diff --git a/src/operator/numpy/random/dist_common.h b/src/operator/numpy/random/dist_common.h index 42d7507f7e74..a3c48f5b4d5c 100644 --- a/src/operator/numpy/random/dist_common.h +++ b/src/operator/numpy/random/dist_common.h @@ -41,6 +41,13 @@ namespace mxnet { namespace op { +template +void _copy(float *dst, float*src); + +template +void _copy(double *dst, double*src); + + inline int FillShape(const mxnet::TShape &lshape, const mxnet::TShape &rshape, const mxnet::TShape &oshape, mxnet::TShape *new_lshape, mxnet::TShape *new_rshape, mxnet::TShape *new_oshape) { diff --git a/src/operator/numpy/random/np_normal_op.cc b/src/operator/numpy/random/np_normal_op.cc new file mode 100644 index 000000000000..1a5df95ff9e8 --- /dev/null +++ b/src/operator/numpy/random/np_normal_op.cc @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file np_random_op.cc + * \brief Operator for numpy sampling from normal distributions. + */ +#include "./np_normal_op.h" + +namespace mxnet { +namespace op { + +DMLC_REGISTER_PARAMETER(NumpyNormalParam); + +NNVM_REGISTER_OP(_npi_normal) +.describe("Numpy behavior normal") +.set_num_inputs( + [](const nnvm::NodeAttrs& attrs) { + const NumpyNormalParam& param = nnvm::get(attrs.parsed); + int num_inputs = 2; + if (param.loc.has_value()) num_inputs -= 1; + if (param.scale.has_value()) num_inputs -= 1; + return num_inputs; + } +) +.set_num_outputs(1) +.set_attr("FListInputNames", + [](const NodeAttrs& attrs) { + const NumpyNormalParam& param = nnvm::get(attrs.parsed); + int num_inputs = 2; + if (param.loc.has_value()) num_inputs -= 1; + if (param.scale.has_value()) num_inputs -= 1; + if (num_inputs == 0) return std::vector(); + if (num_inputs == 1) return std::vector{"input1"}; + return std::vector{"input1", "input2"}; + }) +.set_attr_parser(ParamParser) +.set_attr("FInferShape", TwoparamsDistOpShape) +.set_attr("FInferType", NumpyNormalOpType) +.set_attr("FResourceRequest", + [](const nnvm::NodeAttrs& attrs) { + return std::vector{ + ResourceRequest::kRandom, ResourceRequest::kTempSpace}; + }) +.set_attr("FCompute", NumpyNormalForward) +.set_attr("FGradient", MakeZeroGradNodes) +.add_argument("input1", "NDArray-or-Symbol", "Source input") +.add_argument("input2", "NDArray-or-Symbol", "Source input") +.add_arguments(NumpyNormalParam::__FIELDS__()); + +} // namespace op +} // namespace mxnet diff --git a/src/operator/numpy/random/np_normal_op.cu b/src/operator/numpy/random/np_normal_op.cu new file mode 100644 index 000000000000..6cdf9f9c4eae --- /dev/null +++ b/src/operator/numpy/random/np_normal_op.cu @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file np_normal_op.cu + * \brief Operator for numpy sampling from normal distributions + */ + +#include "./np_normal_op.h" + +namespace mxnet { +namespace op { + +NNVM_REGISTER_OP(_npi_normal) + .set_attr("FCompute", NumpyNormalForward); + +} // namespace op +} // namespace mxnet diff --git a/src/operator/numpy/random/np_normal_op.h b/src/operator/numpy/random/np_normal_op.h new file mode 100644 index 000000000000..678f0ec2fb47 --- /dev/null +++ b/src/operator/numpy/random/np_normal_op.h @@ -0,0 +1,233 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * Copyright (c) 2019 by Contributors + * \file np_normal_op.h + * \brief Operator for numpy sampling from normal distributions + */ +#ifndef MXNET_OPERATOR_NUMPY_RANDOM_NP_NORMAL_OP_H_ +#define MXNET_OPERATOR_NUMPY_RANDOM_NP_NORMAL_OP_H_ + +#include +#include +#include +#include +#include "../../elemwise_op_common.h" +#include "../../mshadow_op.h" +#include "../../mxnet_op.h" +#include "../../operator_common.h" +#include "../../tensor/elemwise_binary_broadcast_op.h" +#include "./dist_common.h" + +namespace mxnet { +namespace op { + +struct NumpyNormalParam : public dmlc::Parameter { + dmlc::optional loc; + dmlc::optional scale; + std::string ctx; + int dtype; + dmlc::optional> size; + DMLC_DECLARE_PARAMETER(NumpyNormalParam) { + DMLC_DECLARE_FIELD(loc); + DMLC_DECLARE_FIELD(scale); + DMLC_DECLARE_FIELD(size) + .set_default(dmlc::optional>()) + .describe( + "Output shape. If the given shape is, " + "e.g., (m, n, k), then m * n * k samples are drawn. " + "Default is None, in which case a single value is returned."); + DMLC_DECLARE_FIELD(ctx).set_default("cpu").describe( + "Context of output, in format [cpu|gpu|cpu_pinned](n)." + " Only used for imperative calls."); + DMLC_DECLARE_FIELD(dtype) + .add_enum("float32", mshadow::kFloat32) + .add_enum("float64", mshadow::kFloat64) + .add_enum("float16", mshadow::kFloat16) + .set_default(mshadow::kFloat32) + .describe( + "DType of the output in case this can't be inferred. " + "Defaults to float32 if not defined (dtype=None)."); + } +}; + +inline bool NumpyNormalOpType(const nnvm::NodeAttrs &attrs, + std::vector *in_attrs, + std::vector *out_attrs) { + const NumpyNormalParam ¶m = nnvm::get(attrs.parsed); + int otype = param.dtype; + if (otype != -1) { + (*out_attrs)[0] = otype; + } else { + (*out_attrs)[0] = mshadow::kFloat32; + } + return true; +} + +namespace mxnet_op { +template +struct normal_kernel { + MSHADOW_XINLINE static void Map(index_t i, const Shape &lstride, + const Shape &hstride, + const Shape &oshape, IType *loc, + IType *scale, float *normals, OType *out) { + Shape coord = unravel(i, oshape); + auto lidx = static_cast(dot(coord, lstride)); + auto hidx = static_cast(dot(coord, hstride)); + IType loc_value = loc[lidx]; + IType scale_value = scale[hidx]; + out[i] = loc_value + normals[i] * scale_value; + } +}; + +template +struct normal_one_scalar_kernel { + MSHADOW_XINLINE static void Map(index_t i, int scalar_pos, + const Shape &stride, + const Shape &oshape, IType *array, + float scalar, float *normals, OType *out) { + Shape coord = unravel(i, oshape); + auto idx = static_cast(dot(coord, stride)); + IType loc_value; + IType scale_value; + if (scalar_pos == 0) { + loc_value = scalar; + scale_value = array[idx]; + } else { + loc_value = array[idx]; + scale_value = scalar; + } + out[i] = loc_value + normals[i] * scale_value; + } +}; + +template +struct normal_two_scalar_kernel { + MSHADOW_XINLINE static void Map(index_t i, float loc, float scale, + float *normals, OType *out) { + out[i] = loc + normals[i] * scale; + } +}; + +template +struct check_legal_scale_kernel { + MSHADOW_XINLINE static void Map(index_t i, IType *scalar, float* flag) { + if (scalar[i] < 0) { + flag[0] = -1.0; + } + } +}; + +} // namespace mxnet_op + +template +void NumpyNormalForward(const nnvm::NodeAttrs &attrs, + const OpContext &ctx, + const std::vector &inputs, + const std::vector &req, + const std::vector &outputs) { + using namespace mshadow; + using namespace mxnet_op; + const NumpyNormalParam ¶m = nnvm::get(attrs.parsed); + CHECK_EQ(outputs.size(), 1); + Stream *s = ctx.get_stream(); + + // Generate base random number. + Random *prnd = ctx.requested[0].get_random(s); + index_t output_len = outputs[0].Size(); + Tensor workspace = + ctx.requested[1].get_space_typed(Shape1(output_len + 1), s); + Tensor normal_tensor = workspace.Slice(0, output_len); + Tensor indicator_device = workspace.Slice(output_len, output_len + 1); + float indicator_host = 1.0; + float *indicator_device_ptr = indicator_device.dptr_; + prnd->SampleGaussian(&normal_tensor, 0.0, 1.0); + mxnet::TShape new_lshape, new_hshape, new_oshape; + + // [scalar scalar] case + if (inputs.size() == 0U) { + CHECK_GE(param.scale.value(), 0.0) << "ValueError: scale < 0"; + MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, OType, { + Kernel, xpu>::Launch( + s, outputs[0].Size(), param.loc.value(), param.scale.value(), + normal_tensor.dptr_, outputs[0].dptr()); + }); + } else if (inputs.size() == 1U) { + // [scalar tensor], [tensor scalar] case + int ndim = FillShape(inputs[0].shape_, inputs[0].shape_, outputs[0].shape_, + &new_lshape, &new_lshape, &new_oshape); + int scalar_pos; + float scalar_value; + if (param.loc.has_value()) { + scalar_pos = 0; + scalar_value = param.loc.value(); + MSHADOW_TYPE_SWITCH(inputs[0].type_flag_, IType, { + Kernel, xpu>::Launch( + s, inputs[0].Size(), inputs[0].dptr(), indicator_device_ptr); + }); + _copy(&indicator_host, indicator_device_ptr); + CHECK_GE(indicator_host, 0.0) << "ValueError: scale < 0"; + } else { + scalar_pos = 1; + scalar_value = param.scale.value(); + CHECK_GE(scalar_value, 0.0) << "ValueError: scale < 0"; + } + MSHADOW_TYPE_SWITCH(inputs[0].type_flag_, IType, { + MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, OType, { + BROADCAST_NDIM_SWITCH(ndim, NDim, { + Shape oshape = new_oshape.get(); + Shape stride = calc_stride(new_lshape.get()); + Kernel, xpu>::Launch( + s, outputs[0].Size(), scalar_pos, stride, oshape, + inputs[0].dptr(), scalar_value, normal_tensor.dptr_, + outputs[0].dptr()); + }); + }); + }); + } else if (inputs.size() == 2U) { + // [tensor tensor] case + MSHADOW_TYPE_SWITCH(inputs[0].type_flag_, IType, { + Kernel, xpu>::Launch( + s, inputs[1].Size(), inputs[1].dptr(), indicator_device_ptr); + }); + _copy(&indicator_host, indicator_device_ptr); + CHECK_GE(indicator_host, 0.0) << "ValueError: scale < 0"; + int ndim = FillShape(inputs[0].shape_, inputs[1].shape_, outputs[0].shape_, + &new_lshape, &new_hshape, &new_oshape); + MSHADOW_TYPE_SWITCH(inputs[0].type_flag_, IType, { + MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, OType, { + BROADCAST_NDIM_SWITCH(ndim, NDim, { + Shape oshape = new_oshape.get(); + Shape lstride = calc_stride(new_lshape.get()); + Shape hstride = calc_stride(new_hshape.get()); + Kernel, xpu>::Launch( + s, outputs[0].Size(), lstride, hstride, oshape, + inputs[0].dptr(), inputs[1].dptr(), + normal_tensor.dptr_, outputs[0].dptr()); + }); + }); + }); + } +} + +} // namespace op +} // namespace mxnet + +#endif // MXNET_OPERATOR_NUMPY_RANDOM_NP_NORMAL_OP_H_ diff --git a/src/operator/numpy/random/np_uniform_op.cc b/src/operator/numpy/random/np_uniform_op.cc index 394626d07596..7307b7744d5e 100644 --- a/src/operator/numpy/random/np_uniform_op.cc +++ b/src/operator/numpy/random/np_uniform_op.cc @@ -43,6 +43,12 @@ NNVM_REGISTER_OP(_npi_uniform) .set_num_outputs(1) .set_attr("FListInputNames", [](const NodeAttrs& attrs) { + const NumpyUniformParam& param = nnvm::get(attrs.parsed); + int num_inputs = 2; + if (param.low.has_value()) num_inputs -= 1; + if (param.high.has_value()) num_inputs -= 1; + if (num_inputs == 0) return std::vector(); + if (num_inputs == 1) return std::vector{"input1"}; return std::vector{"input1", "input2"}; }) .set_attr_parser(ParamParser) diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index 1656716b4a35..07bd2864cfb8 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -1579,7 +1579,6 @@ def test_np_random(): shapes = [(), (1,), (2, 3), (4, 0, 5), 6, (7, 8), None] dtypes = ['float16', 'float32', 'float64'] op_names = ['uniform', 'normal'] - op_names = ['normal'] for shape in shapes: for dtype in dtypes: for op_name in op_names: From b3da7d2334a7d6fddd46c9c737a63c289198e816 Mon Sep 17 00:00:00 2001 From: Jake Lee Date: Wed, 18 Sep 2019 21:02:02 -0700 Subject: [PATCH 338/813] fix multinomial bug on gpu (#16204) --- .../numpy/random/np_multinomial_op.cu | 13 +++ src/operator/numpy/random/np_multinomial_op.h | 26 ++++-- tests/python/unittest/test_numpy_ndarray.py | 90 ++++++++++++------- 3 files changed, 88 insertions(+), 41 deletions(-) diff --git a/src/operator/numpy/random/np_multinomial_op.cu b/src/operator/numpy/random/np_multinomial_op.cu index a80926024735..bf05ede55ac7 100644 --- a/src/operator/numpy/random/np_multinomial_op.cu +++ b/src/operator/numpy/random/np_multinomial_op.cu @@ -27,6 +27,19 @@ namespace mxnet { namespace op { +template +void CheckPvalGPU(DType* input, int prob_length) { + std::vector pvals_(prob_length); + CUDA_CALL(cudaMemcpy(&pvals_[0], input, sizeof(DType) * prob_length, + cudaMemcpyDeviceToHost)); + DType sum = DType(0.0); + for (int i = 0; i < prob_length; ++i) { + sum += pvals_[i]; + CHECK(sum <= DType(1.0)) + << "sum(pvals[:-1]) > 1.0"; + } +} + NNVM_REGISTER_OP(_npi_multinomial) .set_attr("FCompute", NumpyMultinomialForward); diff --git a/src/operator/numpy/random/np_multinomial_op.h b/src/operator/numpy/random/np_multinomial_op.h index 7115f2761202..2a54fba9b1cc 100644 --- a/src/operator/numpy/random/np_multinomial_op.h +++ b/src/operator/numpy/random/np_multinomial_op.h @@ -99,6 +99,19 @@ inline bool NumpyMultinomialOpType(const nnvm::NodeAttrs& attrs, return true; } +template +void CheckPvalGPU(DType* input, int prob_length); + +template +void CheckPval(DType* input, int prob_length) { + DType sum = DType(0.0); + for (int i = 0; i < prob_length; ++i) { + sum += input[i]; + CHECK_LE(sum, 1.0) + << "sum(pvals[:-1]) > 1.0"; + } +} + struct multinomial_kernel { template MSHADOW_XINLINE static void Map(int i, @@ -172,14 +185,11 @@ void NumpyMultinomialForward(const nnvm::NodeAttrs& attrs, s, num_output, num_exp, prob_length, pvals_, temp_tensor.dptr_, outputs[0].dptr()); } else { MSHADOW_TYPE_SWITCH(inputs[0].type_flag_, DType, { - // check if sum of input(pvals) > 1.0 - DType sum = DType(0); - DType* input = inputs[0].dptr(); - for (int i = 0; i < prob_length; ++i) { - sum += input[i]; - CHECK_LE(sum, 1.0) - << "sum(pvals[:-1]) > 1.0"; - } + if (std::is_same::value) { + CheckPval(inputs[0].dptr(), prob_length); + } else { + CheckPvalGPU(inputs[0].dptr(), prob_length); + } Kernel::Launch( s, num_output, num_exp, prob_length, inputs[0].dptr(), temp_tensor.dptr_, outputs[0].dptr()); diff --git a/tests/python/unittest/test_numpy_ndarray.py b/tests/python/unittest/test_numpy_ndarray.py index b60316724808..923ee53fc400 100644 --- a/tests/python/unittest/test_numpy_ndarray.py +++ b/tests/python/unittest/test_numpy_ndarray.py @@ -821,47 +821,71 @@ def test_np_multinomial(): pvals_list = [[0.0, 0.1, 0.2, 0.3, 0.4], [0.4, 0.3, 0.2, 0.1, 0.0]] sizes = [None, (), (3,), (2, 5, 7), (4, 9)] experiements = 10000 - for have_size in [False, True]: - for pvals in pvals_list: - if have_size: - for size in sizes: - freq = mx.np.random.multinomial(experiements, pvals, size=size).asnumpy() / _np.float32(experiements) - # for those cases that didn't need reshape - if size in [None, ()]: - mx.test_utils.assert_almost_equal(freq, pvals, rtol=0.20, atol=1e-1) + for pvals_mx_np_array in [False, True]: + for have_size in [False, True]: + for pvals in pvals_list: + if pvals_mx_np_array: + pvals = mx.np.array(pvals) + if have_size: + for size in sizes: + freq = mx.np.random.multinomial(experiements, pvals, size=size).asnumpy() / _np.float32(experiements) + # for those cases that didn't need reshape + if size in [None, ()]: + if type(pvals) == np.ndarray: + mx.test_utils.assert_almost_equal(freq, pvals.asnumpy(), rtol=0.20, atol=1e-1) + else: + mx.test_utils.assert_almost_equal(freq, pvals, rtol=0.20, atol=1e-1) + else: + # check the shape + assert freq.shape == size + (len(pvals),), 'freq.shape={}, size + (len(pvals))={}'.format(freq.shape, size + (len(pvals))) + freq = freq.reshape((-1, len(pvals))) + # check the value for each row + for i in range(freq.shape[0]): + if type(pvals) == np.ndarray: + mx.test_utils.assert_almost_equal(freq[i, :], pvals.asnumpy(), rtol=0.20, atol=1e-1) + else: + mx.test_utils.assert_almost_equal(freq[i, :], pvals, rtol=0.20, atol=1e-1) + else: + freq = mx.np.random.multinomial(experiements, pvals).asnumpy() / _np.float32(experiements) + if type(pvals) == np.ndarray: + mx.test_utils.assert_almost_equal(freq, pvals.asnumpy(), rtol=0.20, atol=1e-1) else: - # check the shape - assert freq.shape == size + (len(pvals),), 'freq.shape={}, size + (len(pvals))={}'.format(freq.shape, size + (len(pvals))) - freq = freq.reshape((-1, len(pvals))) - # check the value for each row - for i in range(freq.shape[0]): - mx.test_utils.assert_almost_equal(freq[i, :], pvals, rtol=0.20, atol=1e-1) - else: - freq = mx.np.random.multinomial(experiements, pvals).asnumpy() / _np.float32(experiements) - mx.test_utils.assert_almost_equal(freq, pvals, rtol=0.20, atol=1e-1) + mx.test_utils.assert_almost_equal(freq, pvals, rtol=0.20, atol=1e-1) # check the zero dimension sizes = [(0), (0, 2), (4, 0, 2), (3, 0, 1, 2, 0)] - for pvals in pvals_list: - for size in sizes: - freq = mx.np.random.multinomial(experiements, pvals, size=size).asnumpy() - assert freq.size == 0 + for pvals_mx_np_array in [False, True]: + for pvals in pvals_list: + for size in sizes: + if pvals_mx_np_array: + pvals = mx.np.array(pvals) + freq = mx.np.random.multinomial(experiements, pvals, size=size).asnumpy() + assert freq.size == 0 # check [] as pvals - for pvals in [[], ()]: - freq = mx.np.random.multinomial(experiements, pvals).asnumpy() - assert freq.size == 0 - for size in sizes: - freq = mx.np.random.multinomial(experiements, pvals, size=size).asnumpy() + for pvals_mx_np_array in [False, True]: + for pvals in [[], ()]: + if pvals_mx_np_array: + pvals = mx.np.array(pvals) + freq = mx.np.random.multinomial(experiements, pvals).asnumpy() assert freq.size == 0 + for size in sizes: + freq = mx.np.random.multinomial(experiements, pvals, size=size).asnumpy() + assert freq.size == 0 # test small experiment for github issue # https://github.com/apache/incubator-mxnet/issues/15383 small_exp, total_exp = 20, 10000 - for pvals in pvals_list: - x = np.random.multinomial(small_exp, pvals) - for i in range(total_exp // small_exp): - x = x + np.random.multinomial(20, pvals) - freq = (x.asnumpy() / _np.float32(total_exp)).reshape((-1, len(pvals))) - for i in range(freq.shape[0]): - mx.test_utils.assert_almost_equal(freq[i, :], pvals, rtol=0.20, atol=1e-1) + for pvals_mx_np_array in [False, True]: + for pvals in pvals_list: + if pvals_mx_np_array: + pvals = mx.np.array(pvals) + x = np.random.multinomial(small_exp, pvals) + for i in range(total_exp // small_exp): + x = x + np.random.multinomial(20, pvals) + freq = (x.asnumpy() / _np.float32(total_exp)).reshape((-1, len(pvals))) + for i in range(freq.shape[0]): + if type(pvals) == np.ndarray: + mx.test_utils.assert_almost_equal(freq[i, :], pvals.asnumpy(), rtol=0.20, atol=1e-1) + else: + mx.test_utils.assert_almost_equal(freq[i, :], pvals, rtol=0.20, atol=1e-1) if __name__ == '__main__': From 5fe4d2ab61a25387415a6df30ec7f11f103bf041 Mon Sep 17 00:00:00 2001 From: Haibin Lin Date: Wed, 18 Sep 2019 21:02:45 -0700 Subject: [PATCH 339/813] Update ndarray.py (#16205) --- python/mxnet/ndarray/ndarray.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mxnet/ndarray/ndarray.py b/python/mxnet/ndarray/ndarray.py index 59306e21e6f5..0b72865dc17a 100644 --- a/python/mxnet/ndarray/ndarray.py +++ b/python/mxnet/ndarray/ndarray.py @@ -3302,8 +3302,8 @@ def arange(start, stop=None, step=1.0, repeat=1, infer_range=None, ctx=None, dty repeat : int, optional Number of times to repeat each element. The default repeat count is 1. infer_range : boolean, optional - When set to True, infer the stop position from the start, step, - repeat, and output tensor size. + Infer the stop position from the start, step, repeat, and output tensor size. + Deprecated. Only False is supported. ctx : Context, optional Device context. Default context is the current default context. dtype : str or numpy.dtype, optional From 5f9a68033f0cad7b777cc3988b819997c6f4c9eb Mon Sep 17 00:00:00 2001 From: Sheng Zha Date: Wed, 18 Sep 2019 21:08:09 -0700 Subject: [PATCH 340/813] subscribe to build and CD changes (#16192) --- CODEOWNERS | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 6bf30b90602d..f548c2ad30cc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -41,19 +41,24 @@ /src/ @pllarroy /plugin/ @pllarroy -# CMake +# Build system CMakeLists.txt @szha @pllarroy /cmake/ @szha @pllarroy +/make/ @szha # MXNet CI dev_menu.py @pllarroy /ci/ @pllarroy @marcoabreu @aaronmarkham +/ci/publish/ @szha /docker/ @marcoabreu /tests/ci_build/ @marcoabreu Jenkinsfile @marcoabreu .travis.yml @marcoabreu appveyor.yml @marcoabreu +# MXNet CD +/cd/ @szha + # Build logic Makefile @szha prepare_mkl.sh @szha From a37a76c42bf790f288a06adfc81e2df325fe0c24 Mon Sep 17 00:00:00 2001 From: Zhennan Qin Date: Thu, 19 Sep 2019 03:22:51 -0500 Subject: [PATCH 341/813] Float64 fallback for mkldnn subgraph and rnn op (#15853) * Fix mkldnn subgraph with float64 * Fix ci Change-Id: I0bb4e8d7a0a534aa661601887cc633cb9c4fcadf * Fix test Change-Id: I96c529abe7adb6def90a22f03b3432263ef12fda * Update dmlc-core Change-Id: I472fb7bbffc16ed8c36494ab49838b08c59b2f12 * pin to offical dmlc Change-Id: I5a27dc83b892bf8fcb34bb089449d1d3b6e9beed * Fix GPU CI Change-Id: I285947e01bdb0651c2c7830ed4eb76931a09b754 * Fix GPU CI Change-Id: I6f23b51d6bda44f6ae18766ebe390118740bb9c7 --- src/operator/nn/mkldnn/mkldnn_base-inl.h | 17 ++- src/operator/nn/mkldnn/mkldnn_base.cc | 23 +++- src/operator/rnn.cc | 16 ++- src/operator/subgraph/build_subgraph.cc | 33 +++++- .../subgraph/mkldnn/mkldnn_conv_property.h | 10 +- .../mkldnn/mkldnn_fc_post_quantize_property.h | 2 +- .../subgraph/mkldnn/mkldnn_fc_property.h | 8 +- ...kldnn_post_quantize_align_scale_property.h | 2 +- .../mkldnn/mkldnn_post_quantize_property.h | 8 +- .../mkldnn/mkldnn_subgraph_base-inl.h | 42 +++++++ src/operator/subgraph/subgraph_property.h | 110 ++++++++++++------ src/operator/tensor/matrix_op-inl.h | 2 +- tests/python/mkl/test_subgraph.py | 21 ++++ tests/python/unittest/test_operator.py | 34 ++++++ 14 files changed, 266 insertions(+), 62 deletions(-) create mode 100644 src/operator/subgraph/mkldnn/mkldnn_subgraph_base-inl.h diff --git a/src/operator/nn/mkldnn/mkldnn_base-inl.h b/src/operator/nn/mkldnn/mkldnn_base-inl.h index 5db9d6e5defc..34f4a0b0b062 100644 --- a/src/operator/nn/mkldnn/mkldnn_base-inl.h +++ b/src/operator/nn/mkldnn/mkldnn_base-inl.h @@ -47,17 +47,18 @@ #define MXNET_OPERATOR_NN_MKLDNN_MKLDNN_BASE_INL_H_ #if MXNET_USE_MKLDNN == 1 +#include #include +#include #include #include -#include #include -#include -#include +#include #include "mkldnn.hpp" +#include "mxnet/graph_attr_types.h" #include "mxnet/ndarray.h" -#include "mxnet/resource.h" #include "mxnet/op_attr_types.h" +#include "mxnet/resource.h" using namespace mkldnn; namespace mxnet { @@ -132,6 +133,11 @@ static inline bool SupportMKLDNN(int dtype, const mxnet::TShape &shape) { return dtype == mshadow::kFloat32 && (ndim == 1 || ndim == 2 || ndim == 4); } +static inline bool SupportMKLDNNRNN(const NDArray &input) { + int ndim = input.shape().ndim(); + return (input.dtype() == mshadow::kFloat32) && (ndim == 3); +} + static inline bool SupportMKLDNNQuantize(int dtype) { return dtype == mshadow::kFloat32 || dtype == mshadow::kInt8 || dtype == mshadow::kUint8; @@ -569,7 +575,8 @@ class MKLDNNMemory { } }; -void FallBackCompute(FCompute fn, const nnvm::NodeAttrs &attrs, +template +void FallBackCompute(Compute fn, const AttrState &attrs, const OpContext &ctx, const std::vector &inputs, const std::vector &req, diff --git a/src/operator/nn/mkldnn/mkldnn_base.cc b/src/operator/nn/mkldnn/mkldnn_base.cc index a13337b122c3..862947eb726a 100644 --- a/src/operator/nn/mkldnn/mkldnn_base.cc +++ b/src/operator/nn/mkldnn/mkldnn_base.cc @@ -420,7 +420,8 @@ mkldnn::memory::primitive_desc GetPrimitiveDesc(mkldnn::memory::primitive_desc p return mkldnn::memory::primitive_desc(data_md, pd.get_engine()); } -void FallBackCompute(FCompute fn, const nnvm::NodeAttrs &attrs, +template +void FallBackCompute(Compute fn, const AttrState &attrs_states, const OpContext &ctx, const std::vector &inputs, const std::vector &req, @@ -461,7 +462,7 @@ void FallBackCompute(FCompute fn, const nnvm::NodeAttrs &attrs, out_blobs[i] = output.data(); } - fn(attrs, ctx, in_blobs, req, out_blobs); + fn(attrs_states, ctx, in_blobs, req, out_blobs); for (size_t i = 0; i < out_blobs.size(); i++) { if (req[i] == kAddTo && outputs[i].IsMKLDNNData()) mxnet::common::CastNonDefaultStorage(temp_src, temp_dst, ctx, false); @@ -518,6 +519,24 @@ static bool SimilarArray(const mxnet::NDArray &arr1, const mxnet::NDArray &arr2, return success.load(); } +template void FallBackCompute(void (*)(nnvm::NodeAttrs const &, OpContext const &, + std::vector > const &, + std::vector > const &, + std::vector > const &), + nnvm::NodeAttrs const &, OpContext const &, + std::vector > const &, + std::vector > const &, + std::vector > const &); + +template void FallBackCompute(void (*)(OpStatePtr const &, OpContext const &, + std::vector > const &, + std::vector > const &, + std::vector > const &), + OpStatePtr const &, OpContext const &, + std::vector > const &, + std::vector > const &, + std::vector > const &); + void OpCheck::Init(const std::vector &inputs_, const std::vector &outputs_) { auto ctx = inputs_[0].ctx(); diff --git a/src/operator/rnn.cc b/src/operator/rnn.cc index fd016d6819b9..b2ac2f0cb615 100644 --- a/src/operator/rnn.cc +++ b/src/operator/rnn.cc @@ -633,6 +633,20 @@ static void RNNStatefulComputeCPU(const OpStatePtr& state_ptr, }); }); } + +static void RNNStatefulComputeExCPU(const OpStatePtr& state_ptr, const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + if (SupportMKLDNNRNN(inputs[0])) { + RNNStatefulComputeCPU(state_ptr, ctx, inputs, req, outputs); + return; + } + int use_mkldnn_rnn = dmlc::GetEnv("MXNET_USE_MKLDNN_RNN", 1); + dmlc::SetEnv("MXNET_USE_MKLDNN_RNN", 0); + FallBackCompute(RNNStatefulCompute, state_ptr, ctx, inputs, req, outputs); + dmlc::SetEnv("MXNET_USE_MKLDNN_RNN", use_mkldnn_rnn); +} #endif NNVM_REGISTER_OP(RNN) @@ -719,7 +733,7 @@ The definition of GRU here is slightly different from paper but compatible with .set_attr("FStatefulCompute", RNNStatefulCompute) #if MXNET_USE_MKLDNN == 1 .set_attr("TIsMKLDNN", true) -.set_attr("FStatefulComputeEx", RNNStatefulComputeCPU) +.set_attr("FStatefulComputeEx", RNNStatefulComputeExCPU) #endif .set_attr("FGradient", RNNGrad{"_backward_RNN"}) .set_attr("FResourceRequestEx", RNNResourceEx) diff --git a/src/operator/subgraph/build_subgraph.cc b/src/operator/subgraph/build_subgraph.cc index 8a33115feb8b..d43647ac83b9 100644 --- a/src/operator/subgraph/build_subgraph.cc +++ b/src/operator/subgraph/build_subgraph.cc @@ -24,7 +24,6 @@ */ #include #include -#include #include #include #include @@ -105,6 +104,28 @@ void ResetNodeLabels(const nnvm::Graph& g, subgraph_nodes->clear(); } +/* + * \brief Prepare NodeAttr for node. NodeAttr will be used in SubgraphSelectorV2. + */ +static const std::shared_ptr PrepareNodeAttr(const nnvm::Graph& g, + const BiDirectedNode& node) { + const auto& indexed_graph = g.indexed_graph(); + if (g.HasAttr("dtype") && g.HasAttr("shape") && g.HasAttr("dispatch_mode")) { + const auto& vdtype = g.GetAttr("dtype"); + const auto& vshape = g.GetAttr("shape"); + const auto& dispatch_modes = g.GetAttr("dispatch_mode"); + auto ret = std::make_shared(); + ret->dispatch_mode = dispatch_modes[indexed_graph.node_id(node.node)]; + for (const auto& e : node.node->inputs) { + ret->ishape.emplace_back(vshape[indexed_graph.entry_id(e)]); + ret->itype.emplace_back(vdtype[indexed_graph.entry_id(e)]); + } + return ret; + } else { + return nullptr; + } +} + /*! * \brief This function traverses the nodes in a computation graph from a starting * node following the input edges and output edges, and marks all nodes that @@ -153,7 +174,7 @@ bool LabelSubgraph(const nnvm::Graph& g, SubgraphSelectorV2Ptr subgraph_selector CHECK_LT(nid, simple_nodes.size()); const bool select_input = (snode->label == -1) && (!excluded_nodes || !excluded_nodes->count(snode)) && - subgraph_selector->SelectInput(*cur_node, *snode); + subgraph_selector->SelectInput(*cur_node, *snode, PrepareNodeAttr(g, *snode)); if (select_input) { // e.node is a subgraph node snode->label = label; @@ -170,7 +191,7 @@ bool LabelSubgraph(const nnvm::Graph& g, SubgraphSelectorV2Ptr subgraph_selector CHECK_LT(nid, simple_nodes.size()); const bool select_output = (snode->label == -1) && (!excluded_nodes || !excluded_nodes->count(snode)) && - subgraph_selector->SelectOutput(*cur_node, *snode); + subgraph_selector->SelectOutput(*cur_node, *snode, PrepareNodeAttr(g, *snode)); if (select_output) { // it->first is a subgraph node snode->label = label; @@ -325,14 +346,16 @@ void SelectSubgraphNodes(nnvm::Graph* g, SubgraphSelectorV2Ptr subgraph_selector std::vector* subgraph_selectors, const BiDirectedNode* node, const size_t snid, size_t* subgraph_id) { const auto& indexed_graph = g->indexed_graph(); + auto node_cmp = [&] (const BiDirectedNode* node1, const BiDirectedNode* node2) { return indexed_graph.node_id(node1->node) < indexed_graph.node_id(node2->node); }; - if (simple_nodes[snid]->label == -1 && subgraph_selector->Select(*node)) { + if ((simple_nodes[snid]->label == -1) && + subgraph_selector->Select(*node, PrepareNodeAttr(*g, *node))) { // pre-select nodes that can be grouped in a subgraph std::vector preselected_nodes; PreSelectSubgraphNodes(*g, subgraph_selector, *subgraph_id, snid, simple_nodes, - &preselected_nodes); + &preselected_nodes); // filter out unqualified pre-selected nodes std::vector filtered_nodes = subgraph_selector->Filter(preselected_nodes); diff --git a/src/operator/subgraph/mkldnn/mkldnn_conv_property.h b/src/operator/subgraph/mkldnn/mkldnn_conv_property.h index 42ea9ea67fcf..40b3f7c1d010 100644 --- a/src/operator/subgraph/mkldnn/mkldnn_conv_property.h +++ b/src/operator/subgraph/mkldnn/mkldnn_conv_property.h @@ -28,7 +28,7 @@ #include "../../nn/mkldnn/mkldnn_ops-inl.h" #include "../../tensor/matrix_op-inl.h" #include "../common.h" -#include "../subgraph_property.h" +#include "mkldnn_subgraph_base-inl.h" namespace mxnet { namespace op { @@ -61,15 +61,15 @@ class SgMKLDNNConvSelector : public SubgraphSelector { disable_conv_sum_(dis_conv_sum), quantize_(quantize) {} - bool Select(const nnvm::Node &n) override { + bool Select(const nnvm::Node& n, const std::shared_ptr& node_attr) override { if (n.op() && n.op()->name == "Convolution") { const auto ¶m = nnvm::get(n.attrs.parsed); - if (param.kernel.ndim() == 2) { + if (param.kernel.ndim() == 2 && SupportMKLDNNAttr(node_attr)) { status_ = disable_all_ ? kSuccess : kStart; matched_list_.clear(); matched_list_.push_back(&n); return true; - } + } } return false; } @@ -161,7 +161,7 @@ class SgMKLDNNConvSelector : public SubgraphSelector { CHECK_GE(matched_list_.size(), 1); auto new_selector = SgMKLDNNConvSelector(disable_all_, disable_conv_bn_, disable_conv_act_, disable_conv_sum_, quantize_); - new_selector.Select(*matched_list_[0]); + new_selector.Select(*matched_list_[0], nullptr); *this = new_selector; } }; diff --git a/src/operator/subgraph/mkldnn/mkldnn_fc_post_quantize_property.h b/src/operator/subgraph/mkldnn/mkldnn_fc_post_quantize_property.h index 8b5c08802986..f4f252bc92e9 100644 --- a/src/operator/subgraph/mkldnn/mkldnn_fc_post_quantize_property.h +++ b/src/operator/subgraph/mkldnn/mkldnn_fc_post_quantize_property.h @@ -33,7 +33,7 @@ #include "../../nn/fully_connected-inl.h" #include "../../quantization/requantize-inl.h" #include "../common.h" -#include "../subgraph_property.h" +#include "mkldnn_subgraph_base-inl.h" namespace mxnet { namespace op { diff --git a/src/operator/subgraph/mkldnn/mkldnn_fc_property.h b/src/operator/subgraph/mkldnn/mkldnn_fc_property.h index 17d30d272ad1..6dcd114d9ec4 100644 --- a/src/operator/subgraph/mkldnn/mkldnn_fc_property.h +++ b/src/operator/subgraph/mkldnn/mkldnn_fc_property.h @@ -32,7 +32,7 @@ #include #include "../common.h" #include "../../tensor/matrix_op-inl.h" -#include "../subgraph_property.h" +#include "mkldnn_subgraph_base-inl.h" #include "mkldnn_fc-inl.h" namespace mxnet { @@ -58,8 +58,8 @@ class SgMKLDNNFCSelector : public SubgraphSelector { disable_fc_eltwise_(dis_fc_eltwise), quantized_(quantized) {} - bool Select(const nnvm::Node &n) override { - if (n.op() == Op::Get("FullyConnected")) { + bool Select(const nnvm::Node &n, const std::shared_ptr& node_attr) override { + if (n.op() == Op::Get("FullyConnected") && SupportMKLDNNAttr(node_attr)) { status_ = disable_fc_eltwise_ ? kSuccess : kStart; matched_list_.clear(); matched_list_.push_back(&n); @@ -150,7 +150,7 @@ class SgMKLDNNFCSelector : public SubgraphSelector { void Reset() override { CHECK_GE(matched_list_.size(), 1); auto new_selector = SgMKLDNNFCSelector(disable_fc_eltwise_, quantized_); - new_selector.Select(*matched_list_[0]); + new_selector.Select(*matched_list_[0], nullptr); *this = new_selector; } }; diff --git a/src/operator/subgraph/mkldnn/mkldnn_post_quantize_align_scale_property.h b/src/operator/subgraph/mkldnn/mkldnn_post_quantize_align_scale_property.h index 5c5037e7a116..c05c2a8e4a6a 100644 --- a/src/operator/subgraph/mkldnn/mkldnn_post_quantize_align_scale_property.h +++ b/src/operator/subgraph/mkldnn/mkldnn_post_quantize_align_scale_property.h @@ -24,7 +24,7 @@ #include #include #include "../common.h" -#include "../subgraph_property.h" +#include "mkldnn_subgraph_base-inl.h" namespace mxnet { namespace op { diff --git a/src/operator/subgraph/mkldnn/mkldnn_post_quantize_property.h b/src/operator/subgraph/mkldnn/mkldnn_post_quantize_property.h index e78b8d1bfa42..38b08968d8a5 100644 --- a/src/operator/subgraph/mkldnn/mkldnn_post_quantize_property.h +++ b/src/operator/subgraph/mkldnn/mkldnn_post_quantize_property.h @@ -20,14 +20,14 @@ #define MXNET_OPERATOR_SUBGRAPH_MKLDNN_MKLDNN_POST_QUANTIZE_PROPERTY_H_ #if MXNET_USE_MKLDNN == 1 +#include #include #include -#include -#include "../common.h" -#include "../subgraph_property.h" #include "../../nn/mkldnn/mkldnn_convolution-inl.h" -#include "mkldnn_conv-inl.h" #include "../../quantization/requantize-inl.h" +#include "../common.h" +#include "mkldnn_conv-inl.h" +#include "mkldnn_subgraph_base-inl.h" namespace mxnet { namespace op { diff --git a/src/operator/subgraph/mkldnn/mkldnn_subgraph_base-inl.h b/src/operator/subgraph/mkldnn/mkldnn_subgraph_base-inl.h new file mode 100644 index 000000000000..4c8a7ab285b3 --- /dev/null +++ b/src/operator/subgraph/mkldnn/mkldnn_subgraph_base-inl.h @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#ifndef MXNET_OPERATOR_SUBGRAPH_MKLDNN_MKLDNN_SUBGRAPH_BASE_INL_H_ +#define MXNET_OPERATOR_SUBGRAPH_MKLDNN_MKLDNN_SUBGRAPH_BASE_INL_H_ +#if MXNET_USE_MKLDNN == 1 + +#include "../subgraph_property.h" + +namespace mxnet { +namespace op { + +static inline bool SupportMKLDNNAttr(const std::shared_ptr& node_attr) { + if (node_attr) { + int ndim = node_attr->ishape[0].ndim(); + return (node_attr->dispatch_mode == DispatchMode::kFComputeEx) && + (node_attr->itype[0] == mshadow::kFloat32) && (ndim == 1 || ndim == 2 || ndim == 4); + } else { + return true; + } +} + +} // namespace op +} // namespace mxnet + +#endif // MXNET_USE_MKLDNN == 1 +#endif // MXNET_OPERATOR_SUBGRAPH_MKLDNN_MKLDNN_SUBGRAPH_BASE_INL_H_ diff --git a/src/operator/subgraph/subgraph_property.h b/src/operator/subgraph/subgraph_property.h index fd1dc2827ffd..bbc8f4076899 100644 --- a/src/operator/subgraph/subgraph_property.h +++ b/src/operator/subgraph/subgraph_property.h @@ -20,9 +20,11 @@ #ifndef MXNET_OPERATOR_SUBGRAPH_SUBGRAPH_PROPERTY_H_ #define MXNET_OPERATOR_SUBGRAPH_SUBGRAPH_PROPERTY_H_ -#include #include #include +#include +#include +#include #include #include #include @@ -54,6 +56,12 @@ struct BiDirectedNode { std::unordered_map> outputs; }; // struct BiDirectedNode +struct NodeAttr { + DispatchMode dispatch_mode; + ShapeVector ishape; + std::vector itype; +}; + /* * This provides criteria for the graph partitioning algorithm to select * nodes to subgraphs. @@ -81,21 +89,41 @@ class SubgraphSelector { /*! * \brief Determines if to search for other nodes to form a subgraph from the seed_node. */ - virtual bool Select(const nnvm::Node &seed_node) = 0; + virtual bool Select(const nnvm::Node& seed_node) { + LOG(FATAL) << "No Select is implemented."; + return false; + } + virtual bool Select(const nnvm::Node& seed_node, const std::shared_ptr& node_attr) { + return Select(seed_node); + } /*! * \brief Determines if to select input_node when traverse to the cur_node. * \param cur_node the node for determining whether its input_node should be selected * \param input_node the input node of the cur_node * \return true if input_node is selected */ - virtual bool SelectInput(const nnvm::Node &cur_node, const nnvm::Node &input_node) = 0; + virtual bool SelectInput(const nnvm::Node& cur_node, const nnvm::Node& input_node) { + LOG(FATAL) << "No SelectInput is implemented."; + return false; + } + virtual bool SelectInput(const nnvm::Node& cur_node, const nnvm::Node& input_node, + const std::shared_ptr& input_node_attr) { + return SelectInput(cur_node, input_node); + } /*! * \brief Determines if to select output_node when traverse to the cur_node. * \param cur_node the node for determining whether its output_node should be selected * \param output_node the output node of the cur_node * \return true if output_node is selected */ - virtual bool SelectOutput(const nnvm::Node &cur_node, const nnvm::Node &output_node) = 0; + virtual bool SelectOutput(const nnvm::Node& cur_node, const nnvm::Node& output_node) { + LOG(FATAL) << "No SelectOutput is implemented."; + return false; + } + virtual bool SelectOutput(const nnvm::Node& cur_node, const nnvm::Node& output_node, + const std::shared_ptr& output_node_attr) { + return SelectOutput(cur_node, output_node); + } /*! * \brief Post processes pre-selected subgraph nodes. Return a list of nodes that * users want to keep in subgraph(s). @@ -120,31 +148,48 @@ class SubgraphSelectorV2 { /*! * \brief Determines if to search for other nodes to form a subgraph from the seed_node. */ - virtual bool Select(const BiDirectedNode& seed_node) = 0; + virtual bool Select(const BiDirectedNode& seed_node) { + LOG(FATAL) << "No Select is implemented."; + return false; + } + virtual bool Select(const BiDirectedNode& seed_node, const std::shared_ptr& node_attr) { + return Select(seed_node); + } /*! * \brief Determines if to select input_node when traverse to the cur_node. * \param cur_node the node for determining whether its input_node should be selected * \param input_node the input node of the cur_node * \return true if input_node is selected */ - virtual bool SelectInput(const BiDirectedNode& cur_node, - const BiDirectedNode& input_node) = 0; + virtual bool SelectInput(const BiDirectedNode& cur_node, const BiDirectedNode& input_node) { + LOG(FATAL) << "No SelectInput is implemented."; + return false; + } + virtual bool SelectInput(const BiDirectedNode& cur_node, const BiDirectedNode& input_node, + const std::shared_ptr& input_node_attr) { + return SelectInput(cur_node, input_node); + } /*! * \brief Determines if to select output_node when traverse to the cur_node. * \param cur_node the node for determining whether its output_node should be selected * \param output_node the output node of the cur_node * \return true if output_node is selected */ - virtual bool SelectOutput(const BiDirectedNode& cur_node, - const BiDirectedNode& output_node) = 0; + virtual bool SelectOutput(const BiDirectedNode& cur_node, const BiDirectedNode& output_node) { + LOG(FATAL) << "No SelectOutput is implemented."; + return false; + } + virtual bool SelectOutput(const BiDirectedNode& cur_node, const BiDirectedNode& output_node, + const std::shared_ptr& output_node_attr) { + return SelectOutput(cur_node, output_node); + } /*! * \brief Post processes pre-selected subgraph nodes. Return a list of nodes that * users want to keep in subgraph(s). * \param candidates re-selected subgraph nodes to filt * \return a list of nodes to keep */ - virtual std::vector Filter( - const std::vector& candidates) { + virtual std::vector Filter(const std::vector& candidates) { return candidates; } @@ -163,22 +208,22 @@ class SubgraphSelectorV2Bridge : public SubgraphSelectorV2 { virtual ~SubgraphSelectorV2Bridge() {} - bool Select(const BiDirectedNode& seed_node) override { - return ss_ptr_->Select(*seed_node.node); + bool Select(const BiDirectedNode& seed_node, + const std::shared_ptr& node_attr) override { + return ss_ptr_->Select(*seed_node.node, node_attr); } - bool SelectInput(const BiDirectedNode& cur_node, - const BiDirectedNode& input_node) override { - return ss_ptr_->SelectInput(*cur_node.node, *input_node.node); + bool SelectInput(const BiDirectedNode& cur_node, const BiDirectedNode& input_node, + const std::shared_ptr& node_attr) override { + return ss_ptr_->SelectInput(*cur_node.node, *input_node.node, node_attr); } - bool SelectOutput(const BiDirectedNode& cur_node, - const BiDirectedNode& output_node) override { - return ss_ptr_->SelectOutput(*cur_node.node, *output_node.node); + bool SelectOutput(const BiDirectedNode& cur_node, const BiDirectedNode& output_node, + const std::shared_ptr& node_attr) override { + return ss_ptr_->SelectOutput(*cur_node.node, *output_node.node, node_attr); } - std::vector Filter( - const std::vector& candidates) override { + std::vector Filter(const std::vector& candidates) override { std::unordered_map node_2_snode_map; std::vector n_candidates; for (auto i : candidates) { @@ -282,7 +327,7 @@ class SubgraphProperty { * \param subgraph_id subgraph id */ virtual void AdjustSubgraphNode(const std::vector& subgraph_nodes, - const SubgraphSelectorV2Ptr &subgraph_selector, + const SubgraphSelectorV2Ptr& subgraph_selector, const int subgraph_id = 0) const { CHECK_EQ(GetPropertyType(), kAdjust); LOG(FATAL) << "Not implement AdjustSubgraphNode() for this subgraph property."; @@ -315,7 +360,7 @@ class SubgraphProperty { /*! * \brief Set an attr with name in the attr map. */ - template + template SubgraphProperty& SetAttr(const std::string& name, const T& value) { attrs_[name] = std::make_shared(value); return *this; @@ -323,7 +368,7 @@ class SubgraphProperty { /*! * \brief Get the attr with the name. */ - template + template const T& GetAttr(const std::string& name) const { auto it = attrs_.find(name); CHECK(it != attrs_.end()) << "Cannot find attribute " << name << " in SubgraphProperty"; @@ -361,7 +406,7 @@ class SubgraphPropertyEntry { public: explicit SubgraphPropertyEntry(std::shared_ptr entry) : entry_(entry) {} - template + template SubgraphPropertyEntry set_attr(const std::string& name, const T value) const { if (entry_) entry_->SetAttr(name, value); return *this; @@ -377,7 +422,7 @@ class SubgraphBackend { /*! * \brief Set an attr with name in the attr map. */ - template + template SubgraphBackend& SetAttr(const std::string& name, const T& value) { attrs_[name] = std::make_shared(value); return *this; @@ -385,7 +430,7 @@ class SubgraphBackend { /*! * \brief Get the attr with the name. */ - template + template const T& GetAttr(const std::string& name) const { auto it = attrs_.find(name); CHECK(it != attrs_.end()) << "Cannot find attribute " << name << " in SubgraphProperty"; @@ -433,7 +478,7 @@ class SubgraphBackendEntry { public: explicit SubgraphBackendEntry(SubgraphBackendPtr entry) : entry_(entry) {} - template + template SubgraphBackendEntry set_attr(const std::string& name, const T value) const { entry_->SetAttr(name, value); return *this; @@ -454,8 +499,8 @@ class SubgraphBackendRegistry { SubgraphBackendPtr& GetSubgraphBackend(const std::string& name) { auto it = backend_map_.find(name); - CHECK(it != backend_map_.end()) << "SubgraphProperty " << name - << " is not found in SubgraphBackendRegistry"; + CHECK(it != backend_map_.end()) + << "SubgraphProperty " << name << " is not found in SubgraphBackendRegistry"; return it->second; } @@ -487,7 +532,7 @@ class SubgraphBackendRegistry { // This set is only used for the testing purpose. // key: property name, value: op name set typedef dmlc::ThreadLocalStore>> - SubgraphPropertyOpNameSet; + SubgraphPropertyOpNameSet; #define DECLARE_PROPERTY_EX(NAME, SubgraphPropertyType, X) \ static const DMLC_ATTRIBUTE_UNUSED auto __make_##SubgraphPropertyType##_##Name##_##X##__ @@ -498,8 +543,7 @@ typedef dmlc::ThreadLocalStore__REGISTER_PROPERTY__(#Name, &SubgraphPropertyType::Create) -#define DECLARE_BACKEND(Name) \ - static const DMLC_ATTRIBUTE_UNUSED auto __make_##Name##__ +#define DECLARE_BACKEND(Name) static const DMLC_ATTRIBUTE_UNUSED auto __make_##Name##__ #define MXNET_REGISTER_SUBGRAPH_BACKEND(Name) \ DECLARE_BACKEND(Name) = SubgraphBackendRegistry::Get()->__REGISTER_BACKEND__(#Name) diff --git a/src/operator/tensor/matrix_op-inl.h b/src/operator/tensor/matrix_op-inl.h index 5fcb718eab71..5a2bd036c22b 100644 --- a/src/operator/tensor/matrix_op-inl.h +++ b/src/operator/tensor/matrix_op-inl.h @@ -2656,7 +2656,7 @@ inline bool SplitOpShape(const nnvm::NodeAttrs& attrs, mxnet::TShape ishape = in_attrs->at(split_enum::kData); if (!mxnet::ndim_is_known(dshape)) return false; if (param.axis >= 0) { - CHECK_LT(static_cast(param.axis), dshape.ndim()); + CHECK_LT(param.axis, dshape.ndim()); } else { CHECK_LT(param.axis + dshape.ndim(), dshape.ndim()); } diff --git a/tests/python/mkl/test_subgraph.py b/tests/python/mkl/test_subgraph.py index 00e2e824c6f5..8ba6d6183c86 100644 --- a/tests/python/mkl/test_subgraph.py +++ b/tests/python/mkl/test_subgraph.py @@ -844,6 +844,27 @@ def test_neg_fc_relu(): syms, attrs, excluded_attrs = neg_fc_relu(no_bias, dshape, flatten) check_neg_fusion(syms, attrs, excluded_attrs, dshape, name='fc') +def test_float64_fallback(): + sym = mx.sym.FullyConnected( + mx.sym.Variable('in'), + mx.sym.Variable('w'), + mx.sym.Variable('b'), + num_hidden=2 + ) + + dtype = 'float64' + ex = sym.bind(mx.cpu(), + { + 'in': mx.nd.array([[2, 3, 4]], dtype=dtype), + 'w': mx.nd.array([[1, 2, 3], [4, 5, 6]], dtype=dtype), + 'b': mx.nd.array([7, 8], dtype=dtype) + }, + args_grad=None, + grad_req='write' + ) + ex.forward() + ex.outputs[0].wait_to_read() + if __name__ == "__main__": import nose nose.runmodule() diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index 2e7cc3ce7504..77b8080422d9 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -310,6 +310,40 @@ def test_rnnrelu_dropout(): out = exe.forward(is_train=True) out[0].wait_to_read() +def test_RNN_float64(): + if default_context().device_type == 'gpu': + return + sym = mx.sym.RNN( + mx.sym.Variable('in'), + mx.sym.Variable('par'), + mx.sym.Variable('s'), + state_size = (2), + num_layers = 1, + mode = 'rnn_tanh' + ) + + dtype = 'float64' + explicit_grad = { + 'in': mx.nd.ones([2, 1, 2], dtype=dtype), + 'par': mx.nd.ones([12], dtype=dtype), + 's': mx.nd.ones([1, 1, 2], dtype=dtype) + } + + args_grad = explicit_grad + grad_req = 'write' + + ex = sym.bind(default_context(), + { + 'in': mx.nd.ones([2, 1, 2], dtype=dtype), + 'par': mx.nd.ones([12], dtype=dtype), + 's': mx.nd.ones([1, 1, 2], dtype=dtype) + }, + args_grad = args_grad, + grad_req = grad_req + ) + ex.forward() + ex.outputs[0].wait_to_read() + def np_softmax(x, axis=-1, temperature=1.0): x = x - np.max(x, axis=axis, keepdims=True) x = np.exp(x/temperature) From f5d8fbfeec8e476872a02ac580afebc0e56c7147 Mon Sep 17 00:00:00 2001 From: Thomas Delteil Date: Thu, 19 Sep 2019 11:25:19 -0700 Subject: [PATCH 342/813] New Website: Remove Old Content [2/3] (#15885) * Adding new website files Co-authored-by: Thomas Delteil Co-authored-by: Aaron Markham * removing old content Co-authored-by: Thomas Delteil Co-authored-by: Aaron Markham * add license header * fixing links and url + css * fixing tutorials * exclude python_docs folder when building prod site This allows things to coexist until a following PR deletes this file. * license header fixes * exclude static site from license header check * exclude static site and sphinx theme * exclude static_site and cpp_docs from sphinx * add licenses and excludes * fix headers * restore file for legacy builds * empty commit to trigger CI * update submodule to correct one * delete website check * add placeholder website check * add placeholder website check, for reals this time * trigger CI * trigger PR --- ci/jenkins/Jenkinsfile_website | 7 +- docs/Dockerfile | 63 - docs/Doxyfile | 2370 ----------------- docs/Makefile | 214 -- docs/README.md | 160 +- docs/_static/cn.svg | 20 - docs/_static/js/auto_module_index.js | 46 - docs/_static/js/clipboard.js | 778 ------ docs/_static/js/clipboard.min.js | 7 - docs/_static/js/copycode.js | 110 - docs/_static/js/docversion.js | 47 - docs/_static/js/navbar.js | 117 - docs/_static/js/options.js | 118 - docs/_static/js/page.js | 93 - docs/_static/js/search.js | 39 - docs/_static/js/sidebar.js | 277 -- docs/_static/mxnet-theme/footer.html | 16 - docs/_static/mxnet-theme/index.html | 73 - docs/_static/mxnet-theme/layout.html | 201 -- docs/_static/mxnet-theme/navbar.html | 141 - docs/_static/mxnet-theme/theme.conf | 2 - docs/_static/mxnet.css | 1474 ---------- docs/_static/searchtools_custom.js | 853 ------ docs/_static/selectlang.js | 44 - .../tutorials/tensorrt/wavenet_optimized.png | Bin 275440 -> 0 bytes .../tensorrt/wavenet_unoptimized.png | Bin 614819 -> 0 bytes docs/_static/us.svg | 117 - docs/api/c++/index.md | 72 - docs/api/clojure/index.md | 61 - docs/api/clojure/kvstore.md | 103 - docs/api/clojure/module.md | 252 -- docs/api/clojure/ndarray.md | 136 - docs/api/clojure/symbol.md | 149 -- docs/api/clojure/symbol_in_pictures.md | 94 - docs/api/index.md | 33 - docs/api/java/index.md | 32 - docs/api/julia/index.md | 34 - docs/api/perl/index.md | 91 - docs/api/perl/io.md | 129 - docs/api/perl/kvstore.md | 126 - docs/api/perl/module.md | 62 - docs/api/perl/ndarray.md | 59 - docs/api/perl/symbol.md | 144 - docs/api/python/autograd/autograd.md | 176 -- docs/api/python/callback/callback.md | 44 - docs/api/python/contrib/contrib.md | 29 - docs/api/python/contrib/onnx.md | 78 - docs/api/python/contrib/svrg_optimization.md | 103 - docs/api/python/contrib/text.md | 418 --- docs/api/python/executor/executor.md | 59 - docs/api/python/gluon/contrib.md | 179 -- docs/api/python/gluon/data.md | 155 -- docs/api/python/gluon/gluon.md | 189 -- docs/api/python/gluon/loss.md | 59 - docs/api/python/gluon/model_zoo.md | 257 -- docs/api/python/gluon/nn.md | 114 - docs/api/python/gluon/rnn.md | 99 - docs/api/python/image/image.md | 232 -- docs/api/python/index.md | 219 -- docs/api/python/io/io.md | 203 -- docs/api/python/kvstore/kvstore.md | 146 - docs/api/python/libinfo/libinfo.md | 110 - docs/api/python/metric/metric.md | 45 - docs/api/python/model.md | 175 -- docs/api/python/module/module.md | 227 -- docs/api/python/ndarray/contrib.md | 100 - docs/api/python/ndarray/linalg.md | 75 - docs/api/python/ndarray/ndarray.md | 745 ------ docs/api/python/ndarray/random.md | 77 - docs/api/python/ndarray/sparse.md | 618 ----- docs/api/python/optimization/contrib.md | 69 - docs/api/python/optimization/optimization.md | 183 -- docs/api/python/profiler/profiler.md | 82 - docs/api/python/rtc/rtc.md | 46 - docs/api/python/symbol/contrib.md | 97 - docs/api/python/symbol/linalg.md | 78 - docs/api/python/symbol/random.md | 76 - docs/api/python/symbol/rnn.md | 378 --- docs/api/python/symbol/sparse.md | 241 -- docs/api/python/symbol/symbol.md | 739 ----- .../symbol_in_pictures/symbol_in_pictures.md | 94 - docs/api/python/tools/test_utils.md | 44 - docs/api/python/tools/visualization.md | 44 - docs/api/r/index.md | 44 - docs/api/scala/index.md | 82 - docs/api/scala/infer.md | 59 - docs/api/scala/io.md | 182 -- docs/api/scala/kvstore.md | 122 - docs/api/scala/model.md | 135 - docs/api/scala/module.md | 155 -- docs/api/scala/ndarray.md | 174 -- docs/api/scala/symbol.md | 148 - docs/api/scala/symbol_in_pictures.md | 93 - docs/architecture/exception_handling.md | 125 - docs/architecture/index.md | 46 - docs/architecture/note_data_loading.md | 261 -- docs/architecture/note_engine.md | 385 --- docs/architecture/note_memory.md | 345 --- docs/architecture/overview.md | 875 ------ docs/architecture/program_model.md | 623 ----- docs/architecture/rnn_interface.md | 117 - docs/build_version_doc/AddPackageLink.py | 58 - docs/build_version_doc/AddVersion.py | 91 - docs/build_version_doc/Dockerfile | 65 - docs/build_version_doc/README.md | 334 --- docs/build_version_doc/artifacts/.htaccess | 24 - docs/build_version_doc/artifacts/404.html | 50 - docs/build_version_doc/artifacts/api.html | 50 - docs/build_version_doc/artifacts/mxnet.css | 1479 ---------- docs/build_version_doc/build_all_version.sh | 185 -- docs/build_version_doc/build_doc.sh | 148 - docs/build_version_doc/build_site_tag.sh | 65 - docs/build_version_doc/setup_docs_ubuntu.sh | 151 -- docs/build_version_doc/update_all_version.sh | 106 - docs/community/contribute.md | 188 -- docs/community/ecosystem.md | 88 - docs/community/index.md | 28 - docs/community/mxnet_channels.md | 27 - docs/community/powered_by.md | 83 - docs/conf.py | 217 -- docs/faq/add_op_in_backend.md | 679 ----- docs/faq/bucketing.md | 113 - docs/faq/caffe.md | 201 -- docs/faq/cloud.md | 200 -- docs/faq/develop_and_hack.md | 26 - docs/faq/distributed_training.md | 324 --- docs/faq/env_var.md | 352 --- docs/faq/faq.md | 86 - docs/faq/finetune.md | 228 -- docs/faq/float16.md | 248 -- docs/faq/gradient_compression.md | 124 - docs/faq/index.md | 103 - docs/faq/model_parallel_lstm.md | 92 - docs/faq/multi_devices.md | 104 - docs/faq/new_op.md | 388 --- docs/faq/nnpack.md | 155 -- docs/faq/perf.md | 302 --- docs/faq/recordio.md | 100 - docs/faq/s3_integration.md | 116 - docs/faq/security.md | 41 - docs/faq/smart_device.md | 112 - docs/faq/tensor_inspector_tutorial.md | 168 -- docs/faq/visualize_graph.md | 81 - docs/faq/why_mxnet.md | 216 -- docs/gluon/index.md | 127 - docs/index.md | 32 - docs/install/amazonlinux_setup.md | 25 - docs/install/build_from_source.md | 321 --- docs/install/c_plus_plus.md | 47 - docs/install/centos_setup.md | 107 - docs/install/download.md | 75 - docs/install/index.md | 1567 ----------- docs/install/install-jetson.md | 235 -- docs/install/install_mxnet_ubuntu_python.sh | 74 - docs/install/install_mxnet_ubuntu_r.sh | 55 - docs/install/java_setup.md | 128 - docs/install/osx_setup.md | 268 -- docs/install/requirements.txt | 8 - docs/install/scala_setup.md | 157 -- docs/install/ubuntu_setup.md | 554 ---- docs/install/validate_mxnet.md | 219 -- docs/install/windows_setup.md | 508 ---- docs/model_zoo/index.md | 120 - docs/mxdoc.py | 510 ---- .../tutorials/performance/backend/amp.md} | 32 +- .../tutorials/performance/backend/index.rst | 6 + docs/settings.ini | 12 +- docs/static_site/src/pages/api/api.html | 33 +- .../api/architecture/exception_handling.md | 12 +- .../api/architecture/note_data_loading.md | 12 +- .../src/pages/api/architecture/note_engine.md | 12 +- .../src/pages/api/architecture/note_memory.md | 12 +- .../src/pages/api/architecture/overview.md | 12 +- .../pages/api/architecture/program_model.md | 12 +- .../pages/api/clojure/docs/tutorials/index.md | 14 +- .../api/clojure/docs/tutorials/kvstore.md | 14 +- .../api/clojure/docs/tutorials/module.md | 14 +- .../api/clojure/docs/tutorials/ndarray.md | 14 +- .../api/clojure/docs/tutorials/symbol.md | 14 +- .../docs/tutorials/symbol_in_pictures.md | 14 +- .../src/pages/api/clojure/index.md | 16 +- .../pages/api/cpp/docs/tutorials/basics.md | 18 +- .../src/pages/api/cpp/docs/tutorials/index.md | 12 +- .../tutorials/mxnet_cpp_inference_tutorial.md | 18 +- .../api/cpp/docs/tutorials/subgraphAPI.md | 24 +- docs/static_site/src/pages/api/cpp/index.md | 16 +- .../src/pages/api/faq/add_op_in_backend.md | 16 +- docs/static_site/src/pages/api/faq/caffe.md | 16 +- docs/static_site/src/pages/api/faq/cloud.md | 16 +- .../src/pages/api/faq/distributed_training.md | 16 +- docs/static_site/src/pages/api/faq/env_var.md | 20 +- docs/static_site/src/pages/api/faq/float16.md | 16 +- .../src/pages/api/faq/gradient_compression.md | 16 +- .../src/pages/api/faq/model_parallel_lstm.md | 16 +- docs/static_site/src/pages/api/faq/new_op.md | 16 +- docs/static_site/src/pages/api/faq/nnpack.md | 16 +- docs/static_site/src/pages/api/faq/perf.md | 16 +- .../static_site/src/pages/api/faq/recordio.md | 16 +- .../src/pages/api/faq/s3_integration.md | 16 +- .../static_site/src/pages/api/faq/security.md | 16 +- .../src/pages/api/faq/smart_device.md | 16 +- .../src/pages/api/faq/visualize_graph.md | 16 +- .../src/pages/api/faq/why_mxnet.md | 16 +- .../pages/api/java/docs/tutorials/index.md | 12 +- .../docs/tutorials/mxnet_java_on_intellij.md | 14 +- .../api/java/docs/tutorials/ssd_inference.md | 14 +- docs/static_site/src/pages/api/java/index.md | 16 +- docs/static_site/src/pages/api/julia/index.md | 16 +- .../pages/api/perl/docs/tutorials/index.md | 16 +- .../src/pages/api/perl/docs/tutorials/io.md | 14 +- .../pages/api/perl/docs/tutorials/kvstore.md | 14 +- .../pages/api/perl/docs/tutorials/module.md | 14 +- .../pages/api/perl/docs/tutorials/ndarray.md | 14 +- .../pages/api/perl/docs/tutorials/symbol.md | 14 +- docs/static_site/src/pages/api/perl/index.md | 16 +- .../static_site/src/pages/api/python/index.md | 16 +- .../api/r/docs/tutorials/callback_function.md | 14 +- .../api/r/docs/tutorials/char_rnn_model.md | 14 +- ...assify_real_image_with_pretrained_model.md | 14 +- .../api/r/docs/tutorials/custom_iterator.md | 14 +- .../r/docs/tutorials/custom_loss_function.md | 14 +- .../tutorials/five_minutes_neural_network.md | 14 +- .../src/pages/api/r/docs/tutorials/index.md | 14 +- .../api/r/docs/tutorials/mnist_competition.md | 14 +- .../api/r/docs/tutorials/multi_dim_lstm.md | 14 +- .../src/pages/api/r/docs/tutorials/ndarray.md | 14 +- .../src/pages/api/r/docs/tutorials/symbol.md | 14 +- docs/static_site/src/pages/api/r/index.md | 16 +- .../api/scala/docs/tutorials/char_lstm.md | 14 +- .../pages/api/scala/docs/tutorials/index.md | 12 +- .../pages/api/scala/docs/tutorials/infer.md | 14 +- .../src/pages/api/scala/docs/tutorials/io.md | 14 +- .../pages/api/scala/docs/tutorials/kvstore.md | 14 +- .../pages/api/scala/docs/tutorials/mnist.md | 14 +- .../pages/api/scala/docs/tutorials/model.md | 14 +- .../pages/api/scala/docs/tutorials/module.md | 14 +- .../docs/tutorials/mxnet_scala_on_intellij.md | 14 +- .../pages/api/scala/docs/tutorials/ndarray.md | 14 +- .../pages/api/scala/docs/tutorials/symbol.md | 14 +- .../docs/tutorials/symbol_in_pictures.md | 14 +- docs/static_site/src/pages/api/scala/index.md | 16 +- .../src/pages/community/contribute.md | 16 +- .../pages/get_started/build_from_source.md | 16 +- .../src/pages/get_started/c_plus_plus.md | 14 +- .../src/pages/get_started/centos_setup.md | 14 +- .../src/pages/get_started/download.md | 14 +- .../src/pages/get_started/index.html | 14 +- .../src/pages/get_started/java_setup.md | 14 +- .../src/pages/get_started/jetson_setup.md | 14 +- .../src/pages/get_started/osx_setup.md | 14 +- .../src/pages/get_started/scala_setup.md | 14 +- .../src/pages/get_started/ubuntu_setup.md | 14 +- .../src/pages/get_started/validate_mxnet.md | 14 +- .../src/pages/get_started/windows_setup.md | 14 +- docs/tutorials/amp/index.md | 25 - docs/tutorials/basic/data.md | 474 ---- docs/tutorials/basic/index.md | 25 - docs/tutorials/basic/module.md | 364 --- docs/tutorials/basic/ndarray.md | 491 ---- docs/tutorials/basic/ndarray_indexing.md | 394 --- docs/tutorials/basic/reshape_transpose.md | 214 -- docs/tutorials/basic/symbol.md | 460 ---- docs/tutorials/c++/basics.md | 213 -- docs/tutorials/c++/index.md | 25 - .../c++/mxnet_cpp_inference_tutorial.md | 284 -- docs/tutorials/c++/subgraphAPI.md | 166 -- .../control_flow/ControlFlowTutorial.md | 405 --- docs/tutorials/control_flow/index.md | 25 - docs/tutorials/embedded/index.md | 25 - docs/tutorials/embedded/wine_detector.md | 405 --- docs/tutorials/gluon/autograd.md | 72 - docs/tutorials/gluon/custom_layer.md | 265 -- docs/tutorials/gluon/customop.md | 238 -- docs/tutorials/gluon/data_augmentation.md | 235 -- docs/tutorials/gluon/datasets.md | 333 --- docs/tutorials/gluon/fit_api_tutorial.md | 271 -- docs/tutorials/gluon/gluon.md | 159 -- .../gluon_from_experiment_to_deployment.md | 330 --- .../tutorials/gluon/gotchas_numpy_in_mxnet.md | 185 -- docs/tutorials/gluon/hybrid.md | 266 -- docs/tutorials/gluon/index.md | 25 - docs/tutorials/gluon/info_gan.md | 456 ---- docs/tutorials/gluon/learning_rate_finder.md | 332 --- .../gluon/learning_rate_schedules.md | 345 --- .../gluon/learning_rate_schedules_advanced.md | 325 --- .../gluon/logistic_regression_explained.md | 255 -- docs/tutorials/gluon/mnist.md | 460 ---- docs/tutorials/gluon/multi_gpu.md | 193 -- docs/tutorials/gluon/naming.md | 210 -- docs/tutorials/gluon/ndarray.md | 162 -- docs/tutorials/gluon/performance.md | 483 ---- docs/tutorials/gluon/pretrained_models.md | 392 --- docs/tutorials/gluon/save_load_params.md | 281 -- docs/tutorials/gluon/transforms.md | 173 -- docs/tutorials/index.md | 257 -- docs/tutorials/java/index.md | 25 - docs/tutorials/java/mxnet_java_on_intellij.md | 180 -- docs/tutorials/java/ssd_inference.md | 203 -- docs/tutorials/mkldnn/MKLDNN_README.md | 291 -- docs/tutorials/mkldnn/index.md | 25 - docs/tutorials/mkldnn/mkldnn_quantization.md | 259 -- docs/tutorials/mkldnn/operator_list.md | 90 - docs/tutorials/nlp/cnn.md | 468 ---- docs/tutorials/nlp/index.md | 25 - docs/tutorials/onnx/export_mxnet_to_onnx.md | 151 -- docs/tutorials/onnx/fine_tuning_gluon.md | 458 ---- docs/tutorials/onnx/index.md | 25 - .../tutorials/onnx/inference_on_onnx_model.md | 256 -- docs/tutorials/onnx/super_resolution.md | 140 - docs/tutorials/python/data_augmentation.md | 180 -- .../python/data_augmentation_with_masks.md | 261 -- docs/tutorials/python/index.md | 25 - docs/tutorials/python/kvstore.md | 171 -- docs/tutorials/python/linear-regression.md | 215 -- docs/tutorials/python/mnist.md | 222 -- docs/tutorials/python/module_to_gluon.md | 365 --- docs/tutorials/python/predict_image.md | 171 -- .../python/types_of_data_augmentation.md | 395 --- docs/tutorials/r/CallbackFunction.md | 271 -- docs/tutorials/r/CustomIterator.md | 219 -- docs/tutorials/r/CustomLossFunction.md | 223 -- docs/tutorials/r/MultidimLstm.md | 319 --- docs/tutorials/r/charRnnModel.md | 310 --- .../r/classifyRealImageWithPretrainedModel.md | 201 -- docs/tutorials/r/fiveMinutesNeuralNetwork.md | 334 --- docs/tutorials/r/index.md | 31 - docs/tutorials/r/mnistCompetition.md | 356 --- docs/tutorials/r/ndarray.md | 223 -- docs/tutorials/r/symbol.md | 147 - docs/tutorials/scala/char_lstm.md | 526 ---- docs/tutorials/scala/index.md | 34 - docs/tutorials/scala/mnist.md | 134 - .../scala/mxnet_scala_on_intellij.md | 449 ---- docs/tutorials/sparse/csr.md | 552 ---- docs/tutorials/sparse/index.md | 25 - docs/tutorials/sparse/row_sparse.md | 581 ---- docs/tutorials/sparse/train.md | 338 --- docs/tutorials/speech_recognition/ctc.md | 43 - docs/tutorials/speech_recognition/index.md | 25 - docs/tutorials/tensorrt/index.md | 25 - docs/tutorials/tensorrt/inference_with_trt.md | 143 - docs/tutorials/tensorrt/wavenet_optimized.svg | 1 - .../tensorrt/wavenet_unoptimized.svg | 17 - docs/tutorials/unsupervised_learning/gan.md | 418 --- docs/tutorials/unsupervised_learning/index.md | 25 - docs/tutorials/vision/cnn_visualization.md | 275 -- docs/tutorials/vision/index.md | 25 - .../vision/large_scale_classification.md | 282 -- 348 files changed, 803 insertions(+), 55565 deletions(-) delete mode 100644 docs/Dockerfile delete mode 100644 docs/Doxyfile delete mode 100644 docs/Makefile delete mode 100644 docs/_static/cn.svg delete mode 100644 docs/_static/js/auto_module_index.js delete mode 100644 docs/_static/js/clipboard.js delete mode 100755 docs/_static/js/clipboard.min.js delete mode 100644 docs/_static/js/copycode.js delete mode 100644 docs/_static/js/docversion.js delete mode 100644 docs/_static/js/navbar.js delete mode 100644 docs/_static/js/options.js delete mode 100644 docs/_static/js/page.js delete mode 100644 docs/_static/js/search.js delete mode 100644 docs/_static/js/sidebar.js delete mode 100644 docs/_static/mxnet-theme/footer.html delete mode 100644 docs/_static/mxnet-theme/index.html delete mode 100644 docs/_static/mxnet-theme/layout.html delete mode 100644 docs/_static/mxnet-theme/navbar.html delete mode 100644 docs/_static/mxnet-theme/theme.conf delete mode 100644 docs/_static/mxnet.css delete mode 100644 docs/_static/searchtools_custom.js delete mode 100644 docs/_static/selectlang.js delete mode 100644 docs/_static/tutorials/tensorrt/wavenet_optimized.png delete mode 100644 docs/_static/tutorials/tensorrt/wavenet_unoptimized.png delete mode 100644 docs/_static/us.svg delete mode 100644 docs/api/c++/index.md delete mode 100644 docs/api/clojure/index.md delete mode 100644 docs/api/clojure/kvstore.md delete mode 100644 docs/api/clojure/module.md delete mode 100644 docs/api/clojure/ndarray.md delete mode 100644 docs/api/clojure/symbol.md delete mode 100644 docs/api/clojure/symbol_in_pictures.md delete mode 100644 docs/api/index.md delete mode 100644 docs/api/java/index.md delete mode 100644 docs/api/julia/index.md delete mode 100644 docs/api/perl/index.md delete mode 100644 docs/api/perl/io.md delete mode 100644 docs/api/perl/kvstore.md delete mode 100644 docs/api/perl/module.md delete mode 100644 docs/api/perl/ndarray.md delete mode 100644 docs/api/perl/symbol.md delete mode 100644 docs/api/python/autograd/autograd.md delete mode 100644 docs/api/python/callback/callback.md delete mode 100644 docs/api/python/contrib/contrib.md delete mode 100644 docs/api/python/contrib/onnx.md delete mode 100644 docs/api/python/contrib/svrg_optimization.md delete mode 100644 docs/api/python/contrib/text.md delete mode 100644 docs/api/python/executor/executor.md delete mode 100644 docs/api/python/gluon/contrib.md delete mode 100644 docs/api/python/gluon/data.md delete mode 100644 docs/api/python/gluon/gluon.md delete mode 100644 docs/api/python/gluon/loss.md delete mode 100644 docs/api/python/gluon/model_zoo.md delete mode 100644 docs/api/python/gluon/nn.md delete mode 100644 docs/api/python/gluon/rnn.md delete mode 100644 docs/api/python/image/image.md delete mode 100644 docs/api/python/index.md delete mode 100644 docs/api/python/io/io.md delete mode 100644 docs/api/python/kvstore/kvstore.md delete mode 100644 docs/api/python/libinfo/libinfo.md delete mode 100644 docs/api/python/metric/metric.md delete mode 100644 docs/api/python/model.md delete mode 100644 docs/api/python/module/module.md delete mode 100644 docs/api/python/ndarray/contrib.md delete mode 100644 docs/api/python/ndarray/linalg.md delete mode 100644 docs/api/python/ndarray/ndarray.md delete mode 100644 docs/api/python/ndarray/random.md delete mode 100644 docs/api/python/ndarray/sparse.md delete mode 100644 docs/api/python/optimization/contrib.md delete mode 100644 docs/api/python/optimization/optimization.md delete mode 100644 docs/api/python/profiler/profiler.md delete mode 100644 docs/api/python/rtc/rtc.md delete mode 100644 docs/api/python/symbol/contrib.md delete mode 100644 docs/api/python/symbol/linalg.md delete mode 100644 docs/api/python/symbol/random.md delete mode 100644 docs/api/python/symbol/rnn.md delete mode 100644 docs/api/python/symbol/sparse.md delete mode 100644 docs/api/python/symbol/symbol.md delete mode 100644 docs/api/python/symbol_in_pictures/symbol_in_pictures.md delete mode 100644 docs/api/python/tools/test_utils.md delete mode 100644 docs/api/python/tools/visualization.md delete mode 100644 docs/api/r/index.md delete mode 100644 docs/api/scala/index.md delete mode 100644 docs/api/scala/infer.md delete mode 100644 docs/api/scala/io.md delete mode 100644 docs/api/scala/kvstore.md delete mode 100644 docs/api/scala/model.md delete mode 100644 docs/api/scala/module.md delete mode 100644 docs/api/scala/ndarray.md delete mode 100644 docs/api/scala/symbol.md delete mode 100644 docs/api/scala/symbol_in_pictures.md delete mode 100644 docs/architecture/exception_handling.md delete mode 100644 docs/architecture/index.md delete mode 100644 docs/architecture/note_data_loading.md delete mode 100644 docs/architecture/note_engine.md delete mode 100644 docs/architecture/note_memory.md delete mode 100644 docs/architecture/overview.md delete mode 100644 docs/architecture/program_model.md delete mode 100644 docs/architecture/rnn_interface.md delete mode 100644 docs/build_version_doc/AddPackageLink.py delete mode 100755 docs/build_version_doc/AddVersion.py delete mode 100755 docs/build_version_doc/Dockerfile delete mode 100644 docs/build_version_doc/README.md delete mode 100644 docs/build_version_doc/artifacts/.htaccess delete mode 100644 docs/build_version_doc/artifacts/404.html delete mode 100644 docs/build_version_doc/artifacts/api.html delete mode 100644 docs/build_version_doc/artifacts/mxnet.css delete mode 100755 docs/build_version_doc/build_all_version.sh delete mode 100755 docs/build_version_doc/build_doc.sh delete mode 100755 docs/build_version_doc/build_site_tag.sh delete mode 100755 docs/build_version_doc/setup_docs_ubuntu.sh delete mode 100755 docs/build_version_doc/update_all_version.sh delete mode 100644 docs/community/contribute.md delete mode 100644 docs/community/ecosystem.md delete mode 100644 docs/community/index.md delete mode 100644 docs/community/mxnet_channels.md delete mode 100644 docs/community/powered_by.md delete mode 100644 docs/conf.py delete mode 100644 docs/faq/add_op_in_backend.md delete mode 100644 docs/faq/bucketing.md delete mode 100644 docs/faq/caffe.md delete mode 100644 docs/faq/cloud.md delete mode 100644 docs/faq/develop_and_hack.md delete mode 100644 docs/faq/distributed_training.md delete mode 100644 docs/faq/env_var.md delete mode 100644 docs/faq/faq.md delete mode 100644 docs/faq/finetune.md delete mode 100644 docs/faq/float16.md delete mode 100644 docs/faq/gradient_compression.md delete mode 100644 docs/faq/index.md delete mode 100644 docs/faq/model_parallel_lstm.md delete mode 100644 docs/faq/multi_devices.md delete mode 100644 docs/faq/new_op.md delete mode 100644 docs/faq/nnpack.md delete mode 100644 docs/faq/perf.md delete mode 100644 docs/faq/recordio.md delete mode 100644 docs/faq/s3_integration.md delete mode 100644 docs/faq/security.md delete mode 100644 docs/faq/smart_device.md delete mode 100644 docs/faq/tensor_inspector_tutorial.md delete mode 100644 docs/faq/visualize_graph.md delete mode 100644 docs/faq/why_mxnet.md delete mode 100644 docs/gluon/index.md delete mode 100644 docs/index.md delete mode 100644 docs/install/amazonlinux_setup.md delete mode 100644 docs/install/build_from_source.md delete mode 100644 docs/install/c_plus_plus.md delete mode 100644 docs/install/centos_setup.md delete mode 100644 docs/install/download.md delete mode 100644 docs/install/index.md delete mode 100644 docs/install/install-jetson.md delete mode 100755 docs/install/install_mxnet_ubuntu_python.sh delete mode 100755 docs/install/install_mxnet_ubuntu_r.sh delete mode 100644 docs/install/java_setup.md delete mode 100644 docs/install/osx_setup.md delete mode 100644 docs/install/requirements.txt delete mode 100644 docs/install/scala_setup.md delete mode 100644 docs/install/ubuntu_setup.md delete mode 100644 docs/install/validate_mxnet.md delete mode 100644 docs/install/windows_setup.md delete mode 100644 docs/model_zoo/index.md delete mode 100644 docs/mxdoc.py rename docs/{tutorials/amp/amp_tutorial.md => python_docs/python/tutorials/performance/backend/amp.md} (94%) delete mode 100644 docs/tutorials/amp/index.md delete mode 100644 docs/tutorials/basic/data.md delete mode 100644 docs/tutorials/basic/index.md delete mode 100644 docs/tutorials/basic/module.md delete mode 100644 docs/tutorials/basic/ndarray.md delete mode 100644 docs/tutorials/basic/ndarray_indexing.md delete mode 100644 docs/tutorials/basic/reshape_transpose.md delete mode 100644 docs/tutorials/basic/symbol.md delete mode 100644 docs/tutorials/c++/basics.md delete mode 100644 docs/tutorials/c++/index.md delete mode 100644 docs/tutorials/c++/mxnet_cpp_inference_tutorial.md delete mode 100644 docs/tutorials/c++/subgraphAPI.md delete mode 100644 docs/tutorials/control_flow/ControlFlowTutorial.md delete mode 100644 docs/tutorials/control_flow/index.md delete mode 100644 docs/tutorials/embedded/index.md delete mode 100644 docs/tutorials/embedded/wine_detector.md delete mode 100644 docs/tutorials/gluon/autograd.md delete mode 100644 docs/tutorials/gluon/custom_layer.md delete mode 100644 docs/tutorials/gluon/customop.md delete mode 100644 docs/tutorials/gluon/data_augmentation.md delete mode 100644 docs/tutorials/gluon/datasets.md delete mode 100644 docs/tutorials/gluon/fit_api_tutorial.md delete mode 100644 docs/tutorials/gluon/gluon.md delete mode 100644 docs/tutorials/gluon/gluon_from_experiment_to_deployment.md delete mode 100644 docs/tutorials/gluon/gotchas_numpy_in_mxnet.md delete mode 100644 docs/tutorials/gluon/hybrid.md delete mode 100644 docs/tutorials/gluon/index.md delete mode 100644 docs/tutorials/gluon/info_gan.md delete mode 100644 docs/tutorials/gluon/learning_rate_finder.md delete mode 100644 docs/tutorials/gluon/learning_rate_schedules.md delete mode 100644 docs/tutorials/gluon/learning_rate_schedules_advanced.md delete mode 100644 docs/tutorials/gluon/logistic_regression_explained.md delete mode 100644 docs/tutorials/gluon/mnist.md delete mode 100644 docs/tutorials/gluon/multi_gpu.md delete mode 100644 docs/tutorials/gluon/naming.md delete mode 100644 docs/tutorials/gluon/ndarray.md delete mode 100644 docs/tutorials/gluon/performance.md delete mode 100644 docs/tutorials/gluon/pretrained_models.md delete mode 100644 docs/tutorials/gluon/save_load_params.md delete mode 100644 docs/tutorials/gluon/transforms.md delete mode 100644 docs/tutorials/index.md delete mode 100644 docs/tutorials/java/index.md delete mode 100644 docs/tutorials/java/mxnet_java_on_intellij.md delete mode 100644 docs/tutorials/java/ssd_inference.md delete mode 100644 docs/tutorials/mkldnn/MKLDNN_README.md delete mode 100644 docs/tutorials/mkldnn/index.md delete mode 100644 docs/tutorials/mkldnn/mkldnn_quantization.md delete mode 100644 docs/tutorials/mkldnn/operator_list.md delete mode 100644 docs/tutorials/nlp/cnn.md delete mode 100644 docs/tutorials/nlp/index.md delete mode 100644 docs/tutorials/onnx/export_mxnet_to_onnx.md delete mode 100644 docs/tutorials/onnx/fine_tuning_gluon.md delete mode 100644 docs/tutorials/onnx/index.md delete mode 100644 docs/tutorials/onnx/inference_on_onnx_model.md delete mode 100644 docs/tutorials/onnx/super_resolution.md delete mode 100644 docs/tutorials/python/data_augmentation.md delete mode 100644 docs/tutorials/python/data_augmentation_with_masks.md delete mode 100644 docs/tutorials/python/index.md delete mode 100644 docs/tutorials/python/kvstore.md delete mode 100644 docs/tutorials/python/linear-regression.md delete mode 100644 docs/tutorials/python/mnist.md delete mode 100644 docs/tutorials/python/module_to_gluon.md delete mode 100644 docs/tutorials/python/predict_image.md delete mode 100644 docs/tutorials/python/types_of_data_augmentation.md delete mode 100644 docs/tutorials/r/CallbackFunction.md delete mode 100644 docs/tutorials/r/CustomIterator.md delete mode 100644 docs/tutorials/r/CustomLossFunction.md delete mode 100644 docs/tutorials/r/MultidimLstm.md delete mode 100644 docs/tutorials/r/charRnnModel.md delete mode 100644 docs/tutorials/r/classifyRealImageWithPretrainedModel.md delete mode 100644 docs/tutorials/r/fiveMinutesNeuralNetwork.md delete mode 100644 docs/tutorials/r/index.md delete mode 100644 docs/tutorials/r/mnistCompetition.md delete mode 100644 docs/tutorials/r/ndarray.md delete mode 100644 docs/tutorials/r/symbol.md delete mode 100644 docs/tutorials/scala/char_lstm.md delete mode 100644 docs/tutorials/scala/index.md delete mode 100644 docs/tutorials/scala/mnist.md delete mode 100644 docs/tutorials/scala/mxnet_scala_on_intellij.md delete mode 100644 docs/tutorials/sparse/csr.md delete mode 100644 docs/tutorials/sparse/index.md delete mode 100644 docs/tutorials/sparse/row_sparse.md delete mode 100644 docs/tutorials/sparse/train.md delete mode 100644 docs/tutorials/speech_recognition/ctc.md delete mode 100644 docs/tutorials/speech_recognition/index.md delete mode 100644 docs/tutorials/tensorrt/index.md delete mode 100644 docs/tutorials/tensorrt/inference_with_trt.md delete mode 100644 docs/tutorials/tensorrt/wavenet_optimized.svg delete mode 100644 docs/tutorials/tensorrt/wavenet_unoptimized.svg delete mode 100644 docs/tutorials/unsupervised_learning/gan.md delete mode 100644 docs/tutorials/unsupervised_learning/index.md delete mode 100644 docs/tutorials/vision/cnn_visualization.md delete mode 100644 docs/tutorials/vision/index.md delete mode 100644 docs/tutorials/vision/large_scale_classification.md diff --git a/ci/jenkins/Jenkinsfile_website b/ci/jenkins/Jenkinsfile_website index 5e97696a2953..f6c853046b1c 100644 --- a/ci/jenkins/Jenkinsfile_website +++ b/ci/jenkins/Jenkinsfile_website @@ -33,13 +33,10 @@ utils.assign_node_labels(utility: 'utility', linux_cpu: 'mxnetlinux-cpu') utils.main_wrapper( core_logic: { - utils.parallel_stage('Build', [ - custom_steps.compile_unix_cpu_openblas() + utils.parallel_stage('Placeholder', [ + // Do nothing ]) - utils.parallel_stage('Deploy', [ - custom_steps.docs_website(), - ]) } , failure_handler: { diff --git a/docs/Dockerfile b/docs/Dockerfile deleted file mode 100644 index 61a1e5f915e2..000000000000 --- a/docs/Dockerfile +++ /dev/null @@ -1,63 +0,0 @@ -# -*- mode: dockerfile -*- -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -# -FROM ubuntu:14.04 -MAINTAINER Mu Li - -# -# First, build MXNet binaries (ref mxnet/docker/cpu/Dockerfile) -# - -RUN apt-get update && apt-get install -y build-essential git libopenblas-dev liblapack-dev libopencv-dev -RUN git clone --recursive https://github.com/dmlc/mxnet/ && cd mxnet && \ - cp make/config.mk . && \ - echo "USE_BLAS=openblas" >>config.mk && \ - make -j$(nproc) - -# python pakcage -RUN apt-get install -y python-numpy wget unzip -ENV PYTHONPATH /mxnet/python - -# -# Now set up tools for doc build -# - -RUN apt-get update && apt-get install -y \ - doxygen \ - build-essential \ - git \ - python-pip - -RUN pip install sphinx==1.3.5 CommonMark==0.5.4 breathe mock==1.0.1 recommonmark - -WORKDIR /opt/mxnet/docs - -# Fool it into thinking it's on a READTHEDOCS server, so it builds the -# API reference -ENV READTHEDOCS true - -ENTRYPOINT /opt/mxnet/docs/build-preview.sh - -EXPOSE 8008 - -# Put this at the end so that you don't have to rebuild the earlier -# layers when iterating on the docs themselves. -ADD . /opt/mxnet/docs - diff --git a/docs/Doxyfile b/docs/Doxyfile deleted file mode 100644 index bf6344d4f74a..000000000000 --- a/docs/Doxyfile +++ /dev/null @@ -1,2370 +0,0 @@ -# Doxyfile 1.8.8 - -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = "mxnet" - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = - -# With the PROJECT_LOGO tag one can specify an logo or icon that is included in -# the documentation. The maximum height of the logo should not exceed 55 pixels -# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo -# to the output directory. - -PROJECT_LOGO = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = docs/doxygen - -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -#ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = NO - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a -# new page for each member. If set to NO, the documentation of a member will be -# part of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 8 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. - -ALIASES = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = NO - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. -# -# Note For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -#MARKDOWN_SUPPORT = YES - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by by putting a % sign in front of the word -# or globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -#AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = NO - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -#EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. When set to YES local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO these classes will be included in the various overviews. This option has -# no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO these declarations will be -# included in the documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. -# The default value is: system dependent. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -#SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the -# todo list. This list is created by putting \todo commands in the -# documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the -# test list. This list is created by putting \test commands in the -# documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if ... \endif and \cond -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES the list -# will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO doxygen will only warn about wrong or incomplete parameter -# documentation, but not about the absence of documentation. -# The default value is: NO. - -WARN_NO_PARAMDOC = YES - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. -# Note: If this tag is empty the current directory is searched. - -INPUT = include src/common cpp-package/include/mxnet-cpp - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank the -# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, -# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, -# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, -# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, -# *.qsf, *.as and *.js. - -FILE_PATTERNS = *.h - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = 3rdparty - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = */test/* \ - logging.h - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# -# -# where is the value of the INPUT_FILTER tag, and is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER ) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -#USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES, then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -#SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -# If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. -# Note: The availability of this option depends on whether or not doxygen was -# compiled with the --with-libclang option. -# The default value is: NO. - -#CLANG_ASSISTED_PARSING = NO - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -#CLANG_OPTIONS = - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefor more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra stylesheet files is of importance (e.g. the last -# stylesheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -#HTML_EXTRA_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the stylesheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -#HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler ( hhc.exe). If non-empty -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated ( -# YES) or that it should be included in the master .chm file ( NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated ( -# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering -# instead of using prerendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -#MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://www.mathjax.org/mathjax - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -#MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use + S -# (what the is depends on the OS and browser, but it is typically -# , /