From 012de7695dfd8a2d490095852f2e96c08f7802b2 Mon Sep 17 00:00:00 2001 From: Megvii Engine Team Date: Tue, 28 Sep 2021 13:58:48 +0800 Subject: [PATCH] feat(mgb/gopt): add profiler cache In order to improve performance of the profiling procedure. Make layout transform testcase stable. The profiling result in ci environment will be cached in files. GitOrigin-RevId: ba2743f35fcdbd554b7cd82e70f433ccdcc66fa4 --- .gitattributes | 1 + sdk/load-and-run/BUILD | 4 - sdk/load-and-run/src/mgblar.cpp | 2 +- .../impl/utils}/infile_persistent_cache.cpp | 4 +- .../megbrain/utils}/infile_persistent_cache.h | 3 +- .../include/megbrain/utils/persistent_cache.h | 2 + .../global_layout_transform/opr_safe_dump.cpp | 96 +++++++ .../global_layout_transform/opr_safe_dump.h | 30 +++ .../profiler_cache.cpp | 184 ++++++++++++++ .../global_layout_transform/profiler_impl.cpp | 232 ++++++++--------- src/gopt/include/megbrain/gopt/profiler.h | 239 +++++++++++++++++- src/gopt/test/cache_data.h | Bin 0 -> 570073 bytes src/gopt/test/embed_cache.py | 93 +++++++ src/gopt/test/layout_transform_pass.cpp | 207 ++++++++++----- 14 files changed, 905 insertions(+), 192 deletions(-) rename {sdk/load-and-run/src => src/core/impl/utils}/infile_persistent_cache.cpp (98%) rename {sdk/load-and-run/src => src/core/include/megbrain/utils}/infile_persistent_cache.h (95%) create mode 100644 src/gopt/impl/global_layout_transform/opr_safe_dump.cpp create mode 100644 src/gopt/impl/global_layout_transform/opr_safe_dump.h create mode 100644 src/gopt/impl/global_layout_transform/profiler_cache.cpp create mode 100644 src/gopt/test/cache_data.h create mode 100644 src/gopt/test/embed_cache.py diff --git a/.gitattributes b/.gitattributes index 082d51c09..0f1dbf43e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,6 +9,7 @@ dnn/src/cuda/matrix_mul/fp32_simt/kimpl/* binary dnn/src/cuda/sass/prebuilt/map_defs.cpp binary dnn/src/cuda/convolution/backward_data/int8/kimpl/* binary dnn/src/cuda/elemwise_multi_type/kimpl/* binary +src/gopt/test/cache_data.h binary tools/mlir/mlir-tblgen filter=lfs diff=lfs merge=lfs -text imperative/python/test/integration/data/*.mge filter=lfs diff=lfs merge=lfs -text ci/resource/models/float/mobilenet_v2.pkl filter=lfs diff=lfs merge=lfs -text diff --git a/sdk/load-and-run/BUILD b/sdk/load-and-run/BUILD index 54b98e680..29c79b220 100644 --- a/sdk/load-and-run/BUILD +++ b/sdk/load-and-run/BUILD @@ -2,13 +2,11 @@ cc_library( name = "mgblar", copts = ["-std=c++14"], srcs = [ - "src/infile_persistent_cache.cpp", "src/mgblar.cpp", "src/json_loader.cpp", "src/text_table.cpp", ], hdrs = [ - "src/infile_persistent_cache.h", "src/mgblar.h", "src/json_loader.h", "src/text_table.h", @@ -57,11 +55,9 @@ cc_megvii_binary( cc_library( name = "megbrain_ios_lar_lib", srcs = [ - "src/infile_persistent_cache.cpp", "src/mgblar.cpp", ], hdrs = [ - "src/infile_persistent_cache.h", "src/mgblar.h", ], copts = ["-DMGB_NO_MAIN=1"], diff --git a/sdk/load-and-run/src/mgblar.cpp b/sdk/load-and-run/src/mgblar.cpp index b3d901b74..da41f919e 100644 --- a/sdk/load-and-run/src/mgblar.cpp +++ b/sdk/load-and-run/src/mgblar.cpp @@ -10,7 +10,6 @@ */ #include "./mgblar.h" -#include "./infile_persistent_cache.h" #include "./json_loader.h" #include "./npy.h" #include "./text_table.h" @@ -30,6 +29,7 @@ #include "megbrain/serialization/extern_c_opr.h" #include "megbrain/serialization/serializer.h" #include "megbrain/utils/debug.h" +#include "megbrain/utils/infile_persistent_cache.h" #include "megbrain/system.h" #include "megbrain/version.h" diff --git a/sdk/load-and-run/src/infile_persistent_cache.cpp b/src/core/impl/utils/infile_persistent_cache.cpp similarity index 98% rename from sdk/load-and-run/src/infile_persistent_cache.cpp rename to src/core/impl/utils/infile_persistent_cache.cpp index 81444b7ae..5171ff789 100644 --- a/sdk/load-and-run/src/infile_persistent_cache.cpp +++ b/src/core/impl/utils/infile_persistent_cache.cpp @@ -1,5 +1,5 @@ /** - * \file sdk/load-and-run/src/infile_persistent_cache.cpp + * \file src/core/impl/utils/infile_persistent_cache.cpp * MegEngine is Licensed under the Apache License, Version 2.0 (the "License") * * Copyright (c) 2014-2021 Megvii Inc. All rights reserved. @@ -9,7 +9,7 @@ * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ -#include "./infile_persistent_cache.h" +#include "megbrain/utils/infile_persistent_cache.h" #if defined(_WIN32) #include diff --git a/sdk/load-and-run/src/infile_persistent_cache.h b/src/core/include/megbrain/utils/infile_persistent_cache.h similarity index 95% rename from sdk/load-and-run/src/infile_persistent_cache.h rename to src/core/include/megbrain/utils/infile_persistent_cache.h index d9dc5bf09..28b28bef0 100644 --- a/sdk/load-and-run/src/infile_persistent_cache.h +++ b/src/core/include/megbrain/utils/infile_persistent_cache.h @@ -1,5 +1,5 @@ /** - * \file sdk/load-and-run/src/infile_persistent_cache.h + * \file src/core/include/megbrain/utils/infile_persistent_cache.h * MegEngine is Licensed under the Apache License, Version 2.0 (the "License") * * Copyright (c) 2014-2021 Megvii Inc. All rights reserved. @@ -70,6 +70,7 @@ public: Maybe get(const std::string& category, const Blob& key) override; void put(const std::string& category, const Blob& key, const Blob& value) override; + bool support_dump_cache() override { return true; } }; } // namespace mgb diff --git a/src/core/include/megbrain/utils/persistent_cache.h b/src/core/include/megbrain/utils/persistent_cache.h index ce25c59d6..8c881aea7 100644 --- a/src/core/include/megbrain/utils/persistent_cache.h +++ b/src/core/include/megbrain/utils/persistent_cache.h @@ -39,6 +39,8 @@ public: virtual void put( const std::string& category, const Blob& key, const Blob& value) = 0; + virtual bool support_dump_cache() { return false; } + //! set an implementation; return the original implementation static std::shared_ptr set_impl( std::shared_ptr impl); diff --git a/src/gopt/impl/global_layout_transform/opr_safe_dump.cpp b/src/gopt/impl/global_layout_transform/opr_safe_dump.cpp new file mode 100644 index 000000000..28c187918 --- /dev/null +++ b/src/gopt/impl/global_layout_transform/opr_safe_dump.cpp @@ -0,0 +1,96 @@ +/** + * \file src/gopt/impl/global_layout_transform/opr_safe_dump.cpp + * MegEngine is Licensed under the Apache License, Version 2.0 (the "License") + * + * Copyright (c) 2014-2021 Megvii Inc. All rights reserved. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + */ + +#include "./opr_safe_dump.h" +#include "megbrain/opr/basic_arith.h" +#include "megbrain/opr/dnn/convolution.h" +#include "megbrain/opr/dnn/pooling.h" +#include "megbrain/opr/imgproc.h" +#include "megbrain/opr/nn_int.h" +#include "megbrain/opr/tensor_manip.h" + +#include "midout.h" +MIDOUT_DECL(megbrain_opr_safe_dump) +#define MIDOUT_B(...) MIDOUT_BEGIN(megbrain_opr_safe_dump, __VA_ARGS__) { +#define MIDOUT_E \ + } \ + MIDOUT_END(); + +using namespace mgb; +using namespace opr; + +namespace { +template +void write_param(std::string& data, const Param& param) { + megdnn::Algorithm::serialize_write_pod(param, data); +} + +template <> +void write_param(std::string& /* data */, const DType& /* dtype */) {} + +template +struct OprDumpImpl { + static std::string dump(const cg::OperatorNodeBase* opr_) { + MIDOUT_B(Opr) + auto&& opr = opr_->cast_final_safe(); + std::string data; + write_param(data, opr.param()); + return data; + MIDOUT_E + } +}; + +#define INST(_Opr) \ + template <> \ + struct OprDumpImpl<_Opr> { \ + static std::string dump(const cg::OperatorNodeBase* opr_) { \ + MIDOUT_B(_Opr) \ + auto&& opr = opr_->cast_final_safe<_Opr>(); \ + std::string data; \ + write_param(data, opr.param()); \ + using ExecutionPolicy = megdnn::param::ExecutionPolicy; \ + ExecutionPolicy policy{ \ + opr.execution_policy_transient().strategy, \ + opr.execution_policy_transient().workspace_limit}; \ + write_param(data, policy); \ + return data; \ + MIDOUT_E \ + } \ + }; +INST(Convolution); +INST(ConvBiasForward); +INST(ConvolutionBackwardData); +INST(PoolingForward); +#undef INST +} // namespace + +namespace mgb { +namespace gopt { +namespace intl { + +std::string opr_safe_dump(const cg::OperatorNodeBase* opr) { +#define cb(_Opr) \ + if (opr->dyn_typeinfo() == _Opr::typeinfo()) { \ + return OprDumpImpl<_Opr>::dump(opr); \ + } else + FOREACH_SUPPORTED_OPR(cb) { + mgb_throw(InternalError, "unsupported operator(got:%s)", + opr->dyn_typeinfo()->name); + } +#undef cb +} + +} // namespace intl +} // namespace gopt +} // namespace mgb + +// vim: syntax=cpp.doxygen diff --git a/src/gopt/impl/global_layout_transform/opr_safe_dump.h b/src/gopt/impl/global_layout_transform/opr_safe_dump.h new file mode 100644 index 000000000..d25be51d2 --- /dev/null +++ b/src/gopt/impl/global_layout_transform/opr_safe_dump.h @@ -0,0 +1,30 @@ +/** + * \file src/gopt/impl/global_layout_transform/opr_safe_dump.h + * MegEngine is Licensed under the Apache License, Version 2.0 (the "License") + * + * Copyright (c) 2014-2021 Megvii Inc. All rights reserved. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + */ + +#pragma once +#include "megbrain/graph.h" + +namespace mgb { +namespace gopt { +namespace intl { +#define FOREACH_SUPPORTED_OPR(cb) \ + cb(Convolution) cb(ConvBiasForward) cb(ConvolutionBackwardData) \ + cb(PoolingForward) cb(WarpPerspective) cb(Resize) cb(Elemwise) \ + cb(ElemwiseMultiType) cb(Concat) cb(PowC) cb(TypeCvt) + +std::string opr_safe_dump(const cg::OperatorNodeBase* opr); + +} // namespace intl +} // namespace gopt +} // namespace mgb + +// vim: syntax=cpp.doxygen diff --git a/src/gopt/impl/global_layout_transform/profiler_cache.cpp b/src/gopt/impl/global_layout_transform/profiler_cache.cpp new file mode 100644 index 000000000..cfa703400 --- /dev/null +++ b/src/gopt/impl/global_layout_transform/profiler_cache.cpp @@ -0,0 +1,184 @@ +/** + * \file src/gopt/impl/profiler_cache.cpp + * MegEngine is Licensed under the Apache License, Version 2.0 (the "License") + * + * Copyright (c) 2014-2021 Megvii Inc. All rights reserved. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + */ + +#include "./opr_safe_dump.h" +#include "megbrain/gopt/profiler.h" +#include "megbrain/comp_node_env.h" + +using namespace mgb; +using namespace gopt; +using ReformatKey = ReformatManager::ReformatKey; + +// =================== ProfilerCache ====================== +void ProfilerCache::Key::build_blob_from_opr() { + auto&& opr = m_key_impl.opr_key.opr; + // process opr type + auto type = opr->dyn_typeinfo()->name; + size_t type_size = strlen(type); + + // process opr param + auto data = intl::opr_safe_dump(opr); + size_t param_size = data.size(); + + size_t nr_inputs = opr->input().size(); + size_t nr_outputs = opr->usable_output().size(); + size_t nr_layouts = nr_inputs + nr_outputs; + m_blob_storage.reserve(sizeof(TensorLayout) * 3 * nr_layouts + type_size + + param_size); + + // serialize opr type + m_blob_storage.append(type, type_size); + + // serialize param + const char* data_ptr = reinterpret_cast(data.data()); + m_blob_storage.append(data_ptr, param_size); + + // serialize layouts + auto append_layout = [this](const VarNode* v) { + TensorLayout ly{v->shape(), v->dtype(), v->format()}; + for (size_t i = 0; i < ly.ndim; ++i) { + if (i) + m_blob_storage.push_back(','); + m_blob_storage.append(std::to_string(ly.shape[i])); + } + if (!ly.is_contiguous()) { + m_blob_storage.push_back(';'); + for (size_t i = 0; i < ly.ndim; ++i) { + if (i) + m_blob_storage.push_back(','); + m_blob_storage.append(std::to_string(ly.stride[i])); + } + } + m_blob_storage.push_back(';'); + m_blob_storage.append(ly.dtype.name()); + m_blob_storage.push_back('|'); + }; + for (size_t i = 0; i < nr_inputs; ++i) { + append_layout(opr->input(i)); + } + for (size_t i = 0; i < nr_outputs; ++i) { + append_layout(opr->output(i)); + } + + // serialize opr_format + m_blob_storage.append(std::to_string( + static_cast(m_key_impl.opr_key.opr_format))); + + // serialize extra_attribute + m_blob_storage.append(std::to_string( + static_cast(m_key_impl.opr_key.extra_attribute))); +} + +void ProfilerCache::Key::build_category(CompNode cn) { + m_category = "layout_transform_profile:"; + auto&& env = CompNodeEnv::from_comp_node(cn); + switch (env.property().type) { +#if MGB_CUDA + case CompNode::DeviceType::CUDA: { + auto&& prop = env.cuda_env().device_prop; + m_category += ssprintf("plat=cuda;dev=%s;cap=%d.%d", prop.name, + prop.major, prop.minor); + break; + } +#endif + case CompNode::DeviceType::CPU: + m_category += "plat=cpu"; + break; + default: + mgb_throw(MegBrainError, + "unsupported comp node for global layout transform " + "profiler cache category"); + } +} + +void ProfilerCache::Key::build_blob_from_var() { + auto v = m_key_impl.var_key.var; + + // serialize layouts + auto append_layout = [this](const VarNode* v) { + TensorLayout ly{v->shape(), v->dtype(), v->format()}; + for (size_t i = 0; i < ly.ndim; ++i) { + if (i) + m_blob_storage.push_back(','); + m_blob_storage.append(std::to_string(ly.shape[i])); + } + if (!ly.is_contiguous()) { + m_blob_storage.push_back(';'); + for (size_t i = 0; i < ly.ndim; ++i) { + if (i) + m_blob_storage.push_back(','); + m_blob_storage.append(std::to_string(ly.stride[i])); + } + } + m_blob_storage.push_back(';'); + m_blob_storage.append(ly.dtype.name()); + m_blob_storage.push_back('|'); + }; + append_layout(v); + + // serialze reformat key + m_blob_storage.append(m_key_impl.var_key.key.to_string()); +} + +const std::string& ProfilerCache::Key::category() const { + mgb_assert(!m_category.empty()); + return m_category; +} + +PersistentCache::Blob ProfilerCache::Key::blob() const { + mgb_assert(!m_blob_storage.empty()); + return {m_blob_storage.data(), m_blob_storage.size()}; +} + +ProfilerCache& ProfilerCache::inst() { + static ProfilerCache inst; + return inst; +} + +ProfilerCache& ProfilerCache::set_impl(std::unique_ptr impl) { + mgb_assert(impl != nullptr); + m_impl.swap(impl); + return *this; +} + +void ProfilerCache::dump_cache(const char* path) { + mgb_assert(m_impl->support_dump_cache(), + "current impl of ProfilerCache does not support dump cache to " + "file."); + auto cache = static_cast(m_impl.get()); + cache->dump_cache(path); +} + +Maybe ProfilerCache::get(const Key& key) { + auto raw_buf = m_impl->get(key.category(), key.blob()); + if (!raw_buf.valid()) + return None; + // data type of cost is float + auto buf = static_cast(raw_buf->ptr); + auto size = raw_buf->size; + mgb_assert(buf && size == sizeof(float), + "ProfileCache invalid value: ptr=%p, size=%zu", buf, size); + auto read_f32 = [&]() { + auto ret = *reinterpret_cast(buf); + return ret; + }; + auto cost = read_f32(); + return cost; +} + +void ProfilerCache::put(const Key& key, Result& result) { + std::string val; + megdnn::Algorithm::serialize_write_pod(result, val); + m_impl->put(key.category(), key.blob(), {val.data(), val.size()}); +} + +// vim: syntax=cpp.doxygen diff --git a/src/gopt/impl/global_layout_transform/profiler_impl.cpp b/src/gopt/impl/global_layout_transform/profiler_impl.cpp index 43a463e71..99a585070 100644 --- a/src/gopt/impl/global_layout_transform/profiler_impl.cpp +++ b/src/gopt/impl/global_layout_transform/profiler_impl.cpp @@ -154,69 +154,61 @@ void MarkInputContiguous::init_output_static_infer_desc() { } // namespace /* ================== ProfilerImpl =================*/ -class ProfilerImpl final : public ProfilerBase { -public: - ProfilerImpl(int runs = 10) : m_runs{runs} {}; - ~ProfilerImpl() = default; - ProfilingResult profile(const Problem& problem) const override; - -private: - static constexpr float PROFILE_TIME_OUT = 1e7; - using ReformatAttribute = ReformatKey::Attribute; - /*! - * \brief profile opr format agnostic operators (like elemwise, elemwise - * multi type, typecvt etc.) - * - * \param opr pointer to the operator node to be profiled - * \param base_format the original tensor format of the operator node. - * \param available_tensor_formats the available tensor formats - * \return the operator node record - */ - OperatorNodeRecord profile_operator( - const OperatorNodeBase* opr, TensorFormats base_format, - const SmallVector& available_tensor_formats, - ReformatAttribute extra_attribute = ReformatAttribute::DEFAULT) const; - float profile_operator( - const OperatorNodeBase* opr, TensorFormats base_format, - TensorFormats tensor_format, - ReformatAttribute extra_attribute = ReformatAttribute::DEFAULT) const; - /*! - * \brief profile opr format aware operators (like conv, deconv, conv_bias, - * etc.) - * - * \param opr pointer to the operator node to be profiled - * \param base_config the tensor formats configuration of base opr format - * \param config all the available configuration - * \return the operator node record - */ - OperatorNodeRecord profile_operator( - const OperatorNodeBase* opr, - const OprTensorFormatsConfiguration& base_config, - const SmallVector& available_configs, - ReformatAttribute extra_attribute = ReformatAttribute::DEFAULT) const; - float profile_operator( - const OperatorNodeBase* opr, - const OprTensorFormatsConfiguration& base_config, - const OprTensorFormatsConfiguration& config, - ReformatAttribute extra_attribute = ReformatAttribute::DEFAULT) const; - /*! - * \brief profile layout transform of the var node - * - * \param var pointer to the var node to be profiled - * \param base_format the original tensor formats in which the var node is - * stored \param available_tensor_formats the available tensor formats - * \param extra_attribute the extra attributes (options) of the problem - * \return the var node record - */ - VarNodeRecord profile_var_node( - const VarNode* var, TensorFormats base_format, - const SmallVector& available_tensor_formats, - ReformatAttribute extra_attribute = ReformatAttribute::DEFAULT) const; - float profile_var_node( - const VarNode* var, TensorFormats base_format, - const ReformatKey& key) const; - int m_runs; /// sample times of the profiler -}; +ProfilerImpl::ProfilerImpl(int runs, float opr_threshold, + float var_node_threshold) + : m_opr_threshold{opr_threshold}, + m_var_node_threshold{var_node_threshold}, + m_runs{runs} { + m_opr_filter = [this](const OperatorNodeBase* opr, + OperatorNodeBase* new_opr) { + /// \note: for the considerations of performance, we skip nchw(naive) + /// kernels for conv bias on CUDA platform. to remove this later + if (auto conv = try_cast_as_op(new_opr)) { + if (conv->output(0)->comp_node().device_type() == + CompNode::DeviceType::CUDA && + conv->input(0)->dtype().category() == + DTypeCategory::QUANTIZED && + conv->param().format == OprFormat::NCHW) { + return false; + } + } + float comp1 = m_opr_footprint.get_computation( + const_cast(opr)); + float comp2 = m_opr_footprint.get_computation(new_opr); + if (comp2 > m_opr_threshold * comp1) + return false; + return true; + }; + m_var_node_filter = [this](const VarNode* var, TensorShape from, + TensorShape to, ReformatKey key) { + /// \note: due to the alignment requirement of low-bit tensor, we skip + /// some layout transform for low-bit tensors. The skipped layout + /// transforms do not have corresponding dnn kernel and cannot be + /// implemented by tensor manip operators (like reshape, dimshuffle, + /// subtensor, etc.). + if (var->dtype().enumv() == DTypeEnum::QuantizedS4 || + var->dtype().enumv() == DTypeEnum::Quantized4Asymm) { + if (key.input_format == TensorFormats::NCHW && + key.output_format != TensorFormats::NHWC && + key.output_format != TensorFormats::NCHWc64) { + return false; + } + if (key.output_format == TensorFormats::NCHW && + key.input_format != TensorFormats::NHWC && + key.input_format != TensorFormats::NCHWc64) { + return false; + } + } + TensorLayout orig_ly = {var->shape(), var->dtype()}, + from_ly = {from, var->dtype()}, to_ly = {to, var->dtype()}; + float orig_memory = orig_ly.span().dist_byte() * 2.f; + float reformat_memory = + from_ly.span().dist_byte() + to_ly.span().dist_byte(); + if (reformat_memory > orig_memory * m_var_node_threshold) + return false; + return true; + }; +} ProfilerImpl::OperatorNodeRecord ProfilerImpl::profile_operator( const OperatorNodeBase* opr, TensorFormats base_format, @@ -507,56 +499,6 @@ ProfilerImpl::ProfilingResult ProfilerImpl::profile(const Problem& problem) cons } /* ================== ProfilerBase =================*/ -ProfilerBase::ProfilerBase(float opr_threshold, float var_node_threshold) - : m_opr_threshold{opr_threshold}, m_var_node_threshold{var_node_threshold} { - m_opr_filter = [this](const OperatorNodeBase* opr, OperatorNodeBase* new_opr) { - /// \note: for the considerations of performance, we skip nchw(naive) - /// kernels for conv bias on CUDA platform. to remove this later - if (auto conv = try_cast_as_op(new_opr)) { - if (conv->output(0)->comp_node().device_type() == - CompNode::DeviceType::CUDA && - conv->input(0)->dtype().category() == DTypeCategory::QUANTIZED && - conv->param().format == OprFormat::NCHW) { - return false; - } - } - float comp1 = - m_opr_footprint.get_computation(const_cast(opr)); - float comp2 = m_opr_footprint.get_computation(new_opr); - if (comp2 > m_opr_threshold * comp1) - return false; - return true; - }; - m_var_node_filter = [this](const VarNode* var, TensorShape from, TensorShape to, - ReformatKey key) { - /// \note: due to the alignment requirement of low-bit tensor, we skip - /// some layout transform for low-bit tensors. The skipped layout - /// transforms do not have corresponding dnn kernel and cannot be - /// implemented by tensor manip operators (like reshape, dimshuffle, - /// subtensor, etc.). - if (var->dtype().enumv() == DTypeEnum::QuantizedS4 || - var->dtype().enumv() == DTypeEnum::Quantized4Asymm) { - if (key.input_format == TensorFormats::NCHW && - key.output_format != TensorFormats::NHWC && - key.output_format != TensorFormats::NCHWc64) { - return false; - } - if (key.output_format == TensorFormats::NCHW && - key.input_format != TensorFormats::NHWC && - key.input_format != TensorFormats::NCHWc64) { - return false; - } - } - TensorLayout orig_ly = {var->shape(), var->dtype()}, - from_ly = {from, var->dtype()}, to_ly = {to, var->dtype()}; - float orig_memory = orig_ly.span().dist_byte() * 2.f; - float reformat_memory = from_ly.span().dist_byte() + to_ly.span().dist_byte(); - if (reformat_memory > orig_memory * m_var_node_threshold) - return false; - return true; - }; -} - std::string ProfilerBase::OperatorNodeRecord::to_string() const { auto str = ssprintf( "\nopr type: %s\nopr name: %s\ninputs:\n", opr->dyn_typeinfo()->name, @@ -595,4 +537,68 @@ std::unique_ptr ProfilerBase::make_profiler() { return std::make_unique(); } +std::unique_ptr ProfilerBase::make_cached_profiler( + const char* path) { + return std::make_unique(path); +} + +/* ================== CachedProfiler =================*/ +CachedProfiler::CachedProfiler(const char* path, int runs, float opr_threshold, + float var_node_threshold) + : ProfilerImpl(runs, opr_threshold, var_node_threshold), m_path{path} { + if (m_path != nullptr) { // file cache + ProfilerCache::inst().set_impl( + std::make_unique(m_path)); + } +} + +CachedProfiler::ProfilingResult CachedProfiler::profile( + const Problem& problem) const { + auto ret = ProfilerImpl::profile(problem); + if (m_path != nullptr) + ProfilerCache::inst().dump_cache(m_path); + return ret; +} + +float CachedProfiler::profile_operator( + const OperatorNodeBase* opr, TensorFormats base_format, + TensorFormats tensor_format, ReformatAttribute extra_attribute) const { + ProfilerCache::Key key{opr, tensor_formats_to_opr_format(tensor_format), + extra_attribute}; + auto ret = ProfilerCache::inst().get(key); + if (ret.valid()) + return ret.val(); + auto rst = ProfilerImpl::profile_operator(opr, base_format, tensor_format, + extra_attribute); + ProfilerCache::inst().put(key, rst); + return rst; +} + +float CachedProfiler::profile_operator( + const OperatorNodeBase* opr, + const OprTensorFormatsConfiguration& base_config, + const OprTensorFormatsConfiguration& config, + ReformatAttribute extra_attribute) const { + ProfilerCache::Key key{opr, config.opr_format, extra_attribute}; + auto ret = ProfilerCache::inst().get(key); + if (ret.valid()) + return ret.val(); + auto rst = ProfilerImpl::profile_operator(opr, base_config, config, + extra_attribute); + ProfilerCache::inst().put(key, rst); + return rst; +} + +float CachedProfiler::profile_var_node(const VarNode* var, + TensorFormats base_format, + const ReformatKey& key) const { + ProfilerCache::Key pf_key{var, key}; + auto ret = ProfilerCache::inst().get(pf_key); + if (ret.valid()) + return ret.val(); + auto rst = ProfilerImpl::profile_var_node(var, base_format, key); + ProfilerCache::inst().put(pf_key, rst); + return rst; +} + // vim: syntax=cpp.doxygen diff --git a/src/gopt/include/megbrain/gopt/profiler.h b/src/gopt/include/megbrain/gopt/profiler.h index 3d4d61ef1..299312c34 100644 --- a/src/gopt/include/megbrain/gopt/profiler.h +++ b/src/gopt/include/megbrain/gopt/profiler.h @@ -18,11 +18,13 @@ #include "megbrain/gopt/subgraph_extractor.h" #include "megbrain/opr/dnn/convolution.h" #include "megbrain/plugin/opr_footprint.h" +#include "megbrain/utils/infile_persistent_cache.h" namespace mgb { namespace gopt { class Problem; +class CachedProfiler; /*! * \brief A profiler that collects all the performance data to describe the @@ -75,22 +77,245 @@ public: using VarNodeFilter = thin_function; - ProfilerBase(float opr_threshold = 2.f, float var_node_threshold = 2.f); - ProfilerBase(OprFilter opr_filter, VarNodeFilter var_node_filter = {}) - : m_opr_filter{std::move(opr_filter)}, - m_var_node_filter{std::move(var_node_filter)} {} + ProfilerBase() = default; + virtual ~ProfilerBase() = default; + virtual ProfilingResult profile(const Problem& problem) const = 0; + + ProfilerBase& set_opr_filter(const OprFilter& opr_filter) { + m_opr_filter = opr_filter; + return *this; + } + + ProfilerBase& set_var_node_filter(const VarNodeFilter& var_node_filter) { + m_var_node_filter = var_node_filter; + return *this; + } + static std::unique_ptr make_profiler(); + static std::unique_ptr make_cached_profiler( + const char* path = nullptr); protected: OprFilter m_opr_filter; VarNodeFilter m_var_node_filter; - float m_opr_threshold; - float m_var_node_threshold; +}; -private: + +/*! \brief A default profiler impl + */ +class ProfilerImpl : public ProfilerBase { +public: + ProfilerImpl(int runs = 10, float opr_threshold = 2.f, + float var_node_threshold = 2.f); + ~ProfilerImpl() = default; + ProfilingResult profile(const Problem& problem) const override; + +protected: + static constexpr float PROFILE_TIME_OUT = 1e7; + using ReformatKey = ReformatManager::ReformatKey; + using ReformatAttribute = ReformatKey::Attribute; + /*! + * \brief profile opr format agnostic operators (like elemwise, elemwise + * multi type, typecvt etc.) + * + * \param opr pointer to the operator node to be profiled + * \param base_format the original tensor format of the operator node. + * \param available_tensor_formats the available tensor formats + * \return the operator node record + */ + OperatorNodeRecord profile_operator( + const OperatorNodeBase* opr, TensorFormats base_format, + const SmallVector& available_tensor_formats, + ReformatAttribute extra_attribute = + ReformatAttribute::DEFAULT) const; + /*! + * \brief prfile opr format agnostic operators (like elemwise, elemwise multi type, typecvt etc.) + * + * \param opr pointer to the operator to be profiled + * \param base_format the original tensor format of the operator node. + * \param tensor_format the tensor format to be profiled + * \param extra_attribute identify whether to use image object for OpenCL or automatically padding nhwc layout + * \return elapsed time of operator in the given tensor format configuration + */ + virtual float profile_operator( + const OperatorNodeBase* opr, TensorFormats base_format, + TensorFormats tensor_format, + ReformatAttribute extra_attribute = + ReformatAttribute::DEFAULT) const; + /*! + * \brief profile opr format aware operators (like conv, deconv, conv_bias, + * etc.) + * + * \param opr pointer to the operator node to be profiled + * \param base_config the tensor formats configuration of base opr format + * \param config all the available configuration + * \return the operator node record + */ + OperatorNodeRecord profile_operator( + const OperatorNodeBase* opr, + const OprTensorFormatsConfiguration& base_config, + const SmallVector& available_configs, + ReformatAttribute extra_attribute = + ReformatAttribute::DEFAULT) const; + /*! + * \brief prfile opr format aware operators (like conv, deconv, conv_bias, resize, warp etc.) + * + * \param opr pointer to the operator to be profiled + * \param base_config the original opr format configuration of the operator node, + * \param config the opr format configuration to be profiled + * \param extra_attribute identify whether to use image object for OpenCL or automatically padding nhwc layout + * \return elapsed time of operator in the given opr format configuration + */ + virtual float profile_operator(const OperatorNodeBase* opr, + const OprTensorFormatsConfiguration& base_config, + const OprTensorFormatsConfiguration& config, + ReformatAttribute extra_attribute = + ReformatAttribute::DEFAULT) const; + /*! + * \brief profile layout transform of the var node + * + * \param var pointer to the var node to be profiled + * \param base_format the original tensor formats in which the var node is + * stored + * \param available_tensor_formats the available tensor formats + * \param extra_attribute the extra attributes (options) of the problem + * \return the var node record + */ + VarNodeRecord profile_var_node( + const VarNode* var, TensorFormats base_format, + const SmallVector& available_tensor_formats, + ReformatAttribute extra_attribute = + ReformatAttribute::DEFAULT) const; + /*! + * \brief profile layout transform of the var node + * + * \param var pointer to the var node to be profiled + * \param base_format the original tensor formats in which the var node is + * stored + * \param key type of ReformatKey, identify the information/attributes of the layout transoform + * \return elapsed time of the layout transform + */ + virtual float profile_var_node(const VarNode* var, + TensorFormats base_format, + const ReformatKey& key) const; OprFootprint m_opr_footprint; + float m_opr_threshold; /// a threshold, when the computation of the newly + /// created operator that is built in some opr + /// format configuration is as greater as + /// m_opr_threshold times of the original operator, + /// the opr format configuration will be skipped + /// (i.e. the cost is infinite) + float m_var_node_threshold; /// a threshold, when the memory footprint of + /// the layout transform of the var node is as + /// larger as m_var_node_threshold as the var + /// node itself, the layout transform will be + /// skipped (i.e. the cost is infinite) + int m_runs; /// sample times of the profiler +}; + +/*! + * \brief a ProfilerCache that manages the profiling results of operator in + * different layouts and of layout transform of var nodes. + */ +class ProfilerCache : public NonCopyableObj { + ProfilerCache() : m_impl{std::make_unique()} {}; + +public: + using ReformatKey = ReformatManager::ReformatKey; + using ReformatAttribute = ReformatKey::Attribute; + using OprFormat = ProfilerBase::OprFormat; + class Key final : public NonCopyableObj { + std::string m_blob_storage; + std::string m_category; + + struct OprKey { + const OperatorNodeBase* opr; + OprFormat opr_format; + ReformatAttribute extra_attribute; + }; + + struct VarKey { + const VarNode* var; + ReformatKey key; + }; + + union KeyImpl { + OprKey opr_key; + VarKey var_key; + + KeyImpl() { std::memset(this, 0, sizeof(KeyImpl)); } + }; + + KeyImpl m_key_impl; + + void build_blob_from_opr(); + void build_blob_from_var(); + void build_category(CompNode cn); + + public: + Key(const OperatorNodeBase* opr, OprFormat opr_format, + ReformatAttribute extra_attribute = ReformatAttribute::DEFAULT) { + m_key_impl.opr_key = {opr, opr_format, extra_attribute}; + build_blob_from_opr(); + mgb_assert( + opr->node_prop().contain( + cg::OperatorNodeProp::Flag::SINGLE_COMP_NODE), + "operator with multiple comp node is not supported(opr:%s)", + opr->cname()); + // here, we assume that the operator to be profiled has only one + // comp node + build_category(opr->output(0)->comp_node()); + } + + Key(const VarNode* var, ReformatKey key) { + m_key_impl.var_key = {var, key}; + build_blob_from_var(); + build_category(var->comp_node()); + } + + const std::string& category() const; + PersistentCache::Blob blob() const; + }; + + using Result = float; + +public: + static ProfilerCache& inst(); + + ProfilerCache& set_impl(std::unique_ptr impl); + + void dump_cache(const char* path); + + Maybe get(const Key& key); + + void put(const Key& key, Result& result); + +private: + std::unique_ptr m_impl; +}; + +class CachedProfiler final : public ProfilerImpl { +public: + CachedProfiler(const char* path = nullptr, int runs = 10, + float opr_threshold = 2.f, float var_node_threshold = 2.f); + ProfilingResult profile(const Problem& problem) const override; + +private: + float profile_operator(const OperatorNodeBase* opr, + TensorFormats base_format, + TensorFormats tensor_format, + ReformatAttribute extra_attribute = + ReformatAttribute::DEFAULT) const override; + float profile_operator(const OperatorNodeBase* opr, + const OprTensorFormatsConfiguration& base_config, + const OprTensorFormatsConfiguration& config, + ReformatAttribute extra_attribute = + ReformatAttribute::DEFAULT) const override; + float profile_var_node(const VarNode* var, TensorFormats base_format, + const ReformatKey& key) const override; + const char* m_path; }; } // namespace gopt diff --git a/src/gopt/test/cache_data.h b/src/gopt/test/cache_data.h new file mode 100644 index 0000000000000000000000000000000000000000..f049589e93605153451273ca3e70c0c71be857e3 GIT binary patch literal 570073 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

v~=-0 zwcAOpJJrb=G`SB>;9EqEE|@$Rhmm;XE>TUlhdkxJ1_YZ?tNdK3*^(rsRV!z%QM;OI z7+z3>2H7JFfnS-QO$Ysz{{7LN{5e&iBa3)8tmM9XsJG^Fo zN!7%b}Mcm)G+1I6v-pP&GGr1Z{!sqWaqZ z79kAkX-||UuvRdHx-zDx+=^U8hc~@ZGGO+%oGA>_zdKoxx*Csq_@*}Q5swra_|zk} z^hN}8op|t-LJ|sjr<;+%4VnuM3L@ih5kF)&<+Y>({akP|^1J01zi(QL?(iR)Z z*(rm=fxA5^Eej)e`g+NWQkIeW80$yjZfftcAkI5aH>uafBq1D$xG{^0W@{ekD`gcR zE-m=BF0vQy+t$SpI`RS_ zrb01^iqN0-?Syb(@jyO5cJ=hl>4DCTOT+j)lHMXRX|01eYin4nabW)OGnP|?C4o0i z;iR$#1EJ6?nzj>>jYU0y0wFNA#=h7<(!@+#!;~#{tH9zNXph{)>#)t-&1$EkcUf$b zp5DABf9k$uv{Tc|=X*@C#{XcbB##`e?Dli9O@;AIyXmo)C<9*!c;(f89%5!@mwaJ4GCm!*@JJ*j zOWN_uvddoL^VL^b(Qk#qnDK3CB!j7iRm#r$JK$E`uetXnM(&W@eGb6RGM)lpC>a2*t|s zB(hturib_>S}R(?C9YJSc!X+7C$vso@~URqq(p9_!J<400#?#Vd>u-Pe{+99SbB}O zplIQM^vdq*wcI}*c|&&>27Q0r%cF~Elxi+69SHY!yO)Yxcgc_fB~QwC&qEz4cfZ?W z=SZ)}&TSTZSu<@w8#V!Z;@&=rXHyyBnvjJfj4E)Ez$487Nw|78WXTG>tt| zp%+#Ve`i^$a9$AN?6QlOsza8%nP&>U0X@A~Nd8aV)|L5VY+XTxxdZpghCtc1(rUKQ zYQVCRRl+uTEk752%IEqw0%Z+~g=+b$I_=ZmCwVgoC-O^zB;+S&eA5D=UM;dquDMR# znr|040lUIGBTAW3$w1vW4;5%6zcYJPVmLD@;Y#93rYSJiXl2uZmBKKOs~RcMBER6d zggd)Z!4pr7s3wYO+Er3Q_3tNkdx*18x)$$oeQZHzB^nYOJ=eb@dWLk z9<~`8RA*_)3+{uN?rtUO?s;{&4Y`mQlK$N(z}4;o=AC89M9HADa2U=taWSjaxih(7 zq4176o?Z#9P=DsT%w$cRUMaF3JGKI+mXb4O`Hs! z_jMjg#Q60NZmgh|z|W{bPKrL51)LI>oZv=Y+O1=V)VlClZUE?{Coa*Zgxc8cfnT5^kp;%(4O~2XxCRT`f5P+U7H`c4g6T>yp(#dto%Z$u$IoRYj zg|cL-_dRJTz$b*7z9q|3UdS-cUn7HdctIEM(Lvr>WfD41yK#f9F`ab<*r4u?yaZl~xDL?%kfN|-AtCGe=dMS~n#6<3-AM+~&A2RWtmvdcToC)O`; zPqM zJ#7;D)J@iGNjtU63<8lP20}`%bZTvZ#KQ%4EPPdng!#3wN)Z9mx~BWIuxEi&dA`K- z&_)oU6O=C8+JX!wiZ@D!u(DVMxInWy`IxmHrj-k_M5xQ+hei`OxOTt@XrFD$J;AnS zc)k*yOSDa{xElh5_D@0k;RmkT%M$3 zaiZWJEz+x{KfT$)Byr0U2hLm<`^o7WHW*{D*|<;=kV_HQdkj|>b`hKm( zk{l#1SX-WZ{IqoIf&koVb>Y3D+inSyH=4Q~BQ!Z|y7DR9vW($kS}FgV7%jTlU67nX zK@TG&Zzz9NwYvp-6CATbV;+I zE!J$G@59ZTD3H`4Bvi&M$kv3<4N)o`5#>7errCxmF1F(DR@C$`XWehBywkuFogpQw zhz0gid?4wtf$+7kse?cpfc_+|>;#JoO%?{xn8Uh4YU_YUp&I^_F_OzzReRBs{DCr} zam@rq;mPJoP>XD(ZUupS2<0Z908-mx;|mk)Eh{1;3Nh`QM-vVR?IRQL*z=}zDQr2M zLz3MOVGsH%(>|hKqmn87v*2Mzk5_`nbqSi|F4U<%Wl(%^B4cgaTNrhL2?%13A8-gR z9^w6);52~g`$?V+OnDfQlIPq2^W%kKsiXT&Fab|z0)kOe9e5}+cjl~O;+BI? z%nT!p=tiY!9Rvx4PAvM6ME~iYINFv?%7p*U)C>R`&2ir;e~{ zC9Ht1t&_)FZBh@K#@URvZvk+c2O8+I{g66MY<8zrlgX%u5@^VmV@17hpfm23S+}~A z3!$4Bs3VOn_%+P^GL%B8m^LAx(EK`##LaXeN~&0NokT2{ZY5&m0_s4l$D}qp7Ks#Q zOl?`9ap#7}#VV1eAfa!0k8vFR9KnieEPAu$R&T|OBpyt{d={-(rIhcaY_&(S4nCd1 zr4jtcnn%4IINX?k79DuDjNle1mC>4|47NQxf`j} zeHX|TViz@TX|Yuy^rISI(s^v!5pKT!6js0L20rVX%sgmIF)9PnJ`b%G5Snp=6sTwL zzZjKqie{xS(OuHgrHd5~`sCGGO(S8Bu`CVYa5bpq9VIL5Cl)a=vK3Wp>>kJ*G`U=J zo)?B;W*+Cl2%{L})YG%*#TjK9bwazGQP}eX%#(^KUwKLK{c)IfIVR7| zV2e?5=20h5noAhRVPH>4hOn-!(D+!yk^#Lc5L1+3w4hBNBoc6Zr}#b*i6b?b;;r~l z%!Pa>-QRGeCuyMF04y@mfAfBMR}W|I{eR`IA9VzNrjv8~jFx=6O%|zR{|@b}${Iw>&A--c#gcbRASK7x60=vYhs0Vx z+P?3JF(R?AONSM=mKNHVg^5DC`%Ma*2CV0^q42$x3S^rg1Y)p_wv%Iovo~oO7GZP zG;W_!nUGxuw3(}=u`Uc^U_ws)hUu?7E5=1_9zLZBG6BThoE0}Trdo8ZE}nl3Q57B! zO%c{;p-oK|R_K<@-epHi;s`24gE4!_p^_)>t0B<=Z0S@`#y%{V%fJU|>1hCw6W0~L zpG)R!jwj)U+itK^5|Dv1n;dWXMCJC?FlZjxc^W}pD zi|^T>T$!PJV2ToPB5);=h0g{uHnraso;iR^ZccCbe_}f}x`Xog%2cbMQ$yX&iTeR& zr~8x(4?vR~J?Cn~JAAYeBIG7I6=S9xa#9v9j5d6wELKX%jD`Z{20Di z!Ymd0c0-{%kAwE|6IN=srSK31nMH1o)t3;bfV!YVqgZ?6bo|SxWo89xgkK_Ev7DRm418W zU3p)PJ{!#7CO5kzX zNW_DqX+xwD0Vc?C3;^IVS2hfyD>-5n2U4$4aGs3=U{B<1D1ZR7I(&Ko;S@G?V*mhU zgkt-jLg-(C5UC*TT#H53K!!M##ZIyynDPi|jsTCs1|I?cNs-jfK=G)e%{cfzEP|GCY((bn~b29Q|7%>=ZLGF1VhQQ;m z4>XxfvnnZQ98o&QV6PRwy!2uiX(=oE2=*3q`%VC)-ZUpaQygYXa=Fi3khWh8#rbdtNS@;tkB8$0lPKvHrvQmhlX#1eoDqJ1E*ao!3W-O8wG>+&2kty~7pvNr) z{pLcN8r(Q8rN?$d&S;@$aDTW|NR0#z?Q<0C%wX)4r*a_?XoN@achUdeM~MpISvd5N zNDLPKUw4>HxEG1{F5m01WQs*^hyxL+qG~`4>IrZBaSUpKx1-;MvJPrZr{@g9sz)vg zg!m=z5025p((%k?HJztaIbj+Tg%Pe4m{loBZY3ZL+PIyP6qd4in<9grS=n{TTw}!% z!UYUQGblW;_A5Lzg3EMuO2R-9VnPuA)909oaTChdZmCD zjzp*jF{VOGXLE!Enl$(-;-P046jrOLL?_$?^M=fYk8X`0!!&2qah;IBW4K?IjTF7M zu}};Mi*%(pr4+;q31*E(_=P)uD3uGsi#vK>leHC{wm7lEH|@|8G54Fd;}qlvjXLm4 z;8Q(i%szkyS-k-tY$#c>r_2gj!qq=?qQ`V<#}val`ulB><`ttUTJJe!Saw?snt5Ge zpdejoY1Rvf^a1piWZhC5M&=ew5m7iPmds=uI!z1fvHCQK_pQ4uP-&Xt1dM6HYdVPV zef0)OTqx&_Bx%z>#smBg53&pkp0B%`OqLNmgutV z7JB)_j4(+vzmG{`#o8osC(@cEt|xR^BDGdn`cjvcC;WA>=;X~AuwF1TiK<6umQqC! zz<+bbR-qQRupXEL;EG5+4Xo@c4wA9Wy@bVx=v8ZN!f-%8d}IQGwRmU+NU=Ivlu7^L z)^QkJy4{xx+Z=4-n0DZz8A^KgJ8joM@{RvOv4|%xi>(Ao0L{-&*wBAQmla^Z0E;4 zEZIWK%Yq z;}NKeBmDyY!dC?v)ZggA=ZHDT+&Ed!Gd>fO=Y|)K7k<#{ltuwS3f9*Q0&0s?_3`A* zPO8v-g+g+B*@2xR ziA2>p-&1+Qbz4{zKt;|eryQl2K?G1inS}rl7TQ`z9&UL}4wBD8v{De3U?It@Mq&vz zu)K39l@US)5^f}WB#r(eWk91pL7>YB;C>+zZ}y#p+cu=kL!_+YpG&pE22bZLV75Xe z#$oEShFp5dy>%u;g4i;dluFQ}(&+=P5CBl~{3}vwJF$JYHdyFSTc~?Kp4tW9S1eXh zA+|STRytf-&JDb1=?tk;V@=KmIF~IZZOoHAKj0D8V8#>0HS%Hts<I{ow_-dMEZ;kSZD`*4It{39*ZKxp+Q z=$3;pOt7Cf=%MKc+> zSjN|4LyKH(!E3~F54P|M!flCl=WLT0V4`Xu!nD!qo0+0%yml{G&+&7i=j8P$8zAxd zi0yil*oC`Y>GAZp)#g$N(TSjlevMS{v%TDA~K9E<*L*;Y*+q*m^(O>x%4w4!t({Q#NN zFsHffR-9xeg7w6$d2Q>(d+$DJu>d{O05Lq!Lq(J6J9%0J-QUSyOyb*5nK3%Oq*W*~ z{{}0v_ea=b5UVF58WE;dZm4C$DA45>Pur6Erp1od;dcZZXMk~~Uf(FaSOr4k3y1K; z8WJc!$Wf;)I{3wb+opGGNK}MLz!j!%Ol70BiUYF$C>?i5vpZJOB#M{`$0l z)K!~-ZIv)4)1sQu^CPl7zMrttC5$4412V&#A9pEKS#pa*>;R|Hc!D*7=Zq;vylOPD z+no|TsRx(k;QFZ$2DVjRNrkk9z!q#yXE}ncfP$LnhAnXpu)q`YzC-n`_(rI5ml%%S zKX=XKRw7*mx!+>@OAc2OO$}6Syj5AO;&;hR`{I*<6It6TxI&mEt=C6E*ktEon*^l2 zP_*I)sKZ(gt?4e{xIoGZn?w^AY(z*E7LTFD2(BnRtwJm%RdKNJQ$653vLrRFo6T{d zHv$|jwkP+A2SHG5QRUVJUhtp%&caVw;M#VT>@GoMfqlbG3t$?kqN8Cz2>EqUePBFH zRq!N41i>goZA0jlrkIj044`l&K7E=C#`U_oJjPlL7BXR?Bmmv-@c>NaPs@tArBIEZ z7av}-HH9aujtD+7;et6~vL$(2O=uj8j;2wlZwZh}sueL{!eQ4ng57Hp!0CM?oi5xM zdwG;$onZK8G?O=Tf&&f=oEe^Ri_k7Ny*AS>W|HJ#y|_Ft7~FPC-Eun96(N62xD|YZ z>$uZ^%TR+^E6v{dAer-QzWowTb-_vPm=tyq{x5u2tnP_o?-SF#kW*5iN~6j8@{}Yi z4KMtfIP}r2Ot~cWtnPM=j+T{gXcZ=?kiEsHtq!se2|r2#O$Sck@o17=67XUdRtpcB zob9O_tGu~tUrQhlLZ+T%0bww! z@ja`UDh4ux^y~uJR^^GMN@t3?5Mm`gGm*gDvuxmIhf8)d?SM|8=ft~|NH`Vhh zx+X@~MFlxbFhM0~fl{@BUR&GWIv|e-hFgL$g@?3<1gsf0oQ1GE{Rx%HHCvOCD^opR z!nF~xQ}jT99Uq4~KB80SEp$1a2(zLkVV!1ew0|lhN!+GiRc*r(t0!gj&$zWpReAzq zgj-&Ck>iEmlX$hn!Ra_N3=0##W_3b;_CbNNGYVPU#cN+K-h*wxvTX;6Nzv(6fLn!e zDDd-n#Ag}Qx@EXCEZQx`4o)L#iN2F}rTq<;RBd5*py2tWM~vY4cu@T#Zjb>Nu)ffd zyfUv>j2=Z6Ddw-`f<{@uD6i$`a;|)?f8+LqM{kiMy{}a`bC5f*aJ(oYD&Tt6C{Q57 zXt@Tc1=p&3my#ag3b{w%Kk2fg>*SXS{EJs&70}jcM8XH|OU4;lx(*z2oCcuDlLK$j zO+RpQ*yWH1;S@z=9P3wMz-u>H)iRMVBt17~0X;JsixkO+mJ^ml327Y&Q!-?z2{lEu zQFI`E)#Jsk(xR`7C)P(v3|`)}l9QeZyZEao1T~$S`*AAry^$lY%9l-VwNkh^8R{g2 z01AdmadH53(Tbh;APA6)(%mW#N!k|Jt#op+sJmFT?kOQb7;p)$%1<r0YC0>4*)>;AfFWQJ*b~ zs$wA_mFF}8wJ6$doY>nh*oIW;LBvsjQ~F!z5qO>A-LO;EWZQN)z%NB_q1)4+LKEfTHQyh8M7~DaG3`|9 zQyaf}h0ZA%Yu|EF8<{o2OBmKN&yI{W$(SPa;`vRLj)7PdBjoXd950MVk7IjJ53eEa zSz-&P4H<*yvmWvKFtmq|D~s7(#CtPrNc<36W6e7ZkJh_?k^~&^9hfGetx`z$DBCz_ zBWOgT5@0m_r^7kqI?7>&^O6tWoUA6`w&3^-4&@UjFpxZ<&sC{NTd^K)rzjvB=h_BZ zGUN1t7xN%P@VuN#K(1udC7iGSc4CKS zAeONXwj4AZcdX&?N8z1A-c!6Pu{ImpA|sj4X$ukxRW>i!GAnfT18r`F&S5DM|Ie6E zmErNH+zU?~Hfg&KVorx_{Q2{OC>z;sC;VxKiOE9D!iKsUnLrnh!vpLd&X$d2W`S&= zxX0!^5ChKF6$0^N$0x)oxFdpALV^{j&MGV7s$=CxqPQ<5OWDh*Sa6^dDSR+>@Qb~F z>W6^##C9e$6A)Kw_hX|t!GaLUO`Mhi*;0PN6fPAd?~-)6_+rPlVAaS5t}KxpPr54< zeB$TA$Yiw=Ne{e?YUIu}r84WPj4N?S%(=j(1aXH#K6w-l#U-InJiHNhhX$(ML3>E{ zhk0u6{<#_!;g0X#G#`0-zYdc3KGJU6G9z;%FT^gBqtv$2fQpgc;zry-sAEC584x{O ziClFhFv)NvIEbOwhRh_}aK>Rij@&= z3f1znq9Gf;n`$_a3VJvYhpPyvN*IJ4i7!{V?wO9>5#TI&QRNDvcv^+|{J>#vW=k=^ zxjWVyC{AT@Fh&!~5EtDBvh-EOrzObQ#MoGmeLk=DO0~5lRY`&*^eya<5DFhH=y6uU zapRe?1zRkw^*DjFDmG9jK_^AC!;I+`cv6fzMJGeD&7pdkIKeZ_EFH_&TM3oKHn=!L zMv3!qhld1SWqO*g8Uwt?{>ZEaJp3-@UuZeyUzt2{9C$Bwm4y+4QG{|6YguQ7U_6gT zYPrs}s9kV=^gldy>G*)nRgjN0!6Vk~FAGVgsoaPw7Ax3t)Hr@9()&cbndjU9Z^JPH zz$ZPcGOQKqM43s|q|F>hOeG?ziD+91;FLRvA5f{%tz2qu%(%*B63ZAIpI(feb`Y)(Y`;}3CZE(pTi*U>-Sg;mMnCU7FH+QlqjO>caZm@mO2BT51CfU!PU{SfQr&z&& z!2F`~Wh|OdA|hp9ccSC~2|$7n)#K77p1{+GvDqu%b9PTS(Fe)9nTAElX`L4;e!)eB z4wgnwCOF;)U8>{!WMM*wEg?+ZFW3}G5}aQxM4%G~x94?eG2CK1qgo}oL)s>YWV>O2 zYIhgmlR%IQ7Krujud|4Eg+*H^9iO01hzWR0;RZ(1le8`QVpzrQK9Q)cH1_i09sbz&C z00pk4l-;IO3ShbYjrhTWIq2YW5xp5n)g&Q!lEw$iGxuM0k}6 z8r=|Up7uVS@8{jc56!%lOjO-&8n8}OwAzVlB96vA;b7%PIV&h06qr*^!5QHL48hsc z>Alg8I{8wNiUXJE7B{DZ7_pQqqN!fIZ6a20YezPMX91lClH!zMI_NmY6;WS(N>V)XdjikV1w?8fdV@eamnIxQoLbykwG@?+%S(1{(p#gp+ z@m7#4jBYsCu(cTJ=wut9AZ9fqptIp`OT%$Tni^9U3k|@zh+ORjV6~bvLbRF*yRtuN zpEI6u!(Y81kph4mt8nb*Sc#It!{KxWk|KdvVP(tK6*_IDSyZD&<4^{|IO2eS0)jZPjU*My$U3|46qJ=nDI2qyYY{q3p1pbrvg6`-A3>{73;8hcAmQ3F2*0x zG5D#FeP#P*sYE2a!hi+{!_lISwdk|#u@Zh&aX>+TS=s2IS|esU;*5SHB?&myob%U? z%AmV1Q3GT&Rx+gY5DOVQZ{1Nu)}t&c1=e^@7TtAEldg9x2|))i)btzULO`XnGx>(hfX(>!Bt#Nf}6qlH=D;$9*0p;nFD|0@{9SV{ht zAVPF>$CA>SN{QeQXk)0^Z5X6iD7KZ5U^4oEG$C{jv#~KHxicf_is4bE(&uK|95io-_{x zK?-+0PMDIVBAoGeIHQt4}iaR=lK~DGs{u)O8o3O<^wK z)`ToBZ>UL&H<^*Wz{VPbE$k|ctg7G^fT6Mc&NgxlUXhu6pE`Ze5qlwAXK-QI_-?i=@Q4K9Pq;kg>7 zvOsm=hinspomWYZvRxujAyFHu4_rT0%*}e(QNgq9{MH!dPo|QFqyBgxI|)<%^?aie zDy6<51au+BlUA96$M8BgqsWcKox;Ez+^L<$LhFP&Pb|)S((Eh3FXB!e_DGC_)}K+G zWeOx{_|#D1pBm#DaW(+M#0NbVgk3+3L8HWSLl&hFA1wS-#vFj2fSV;D6{qObOphc< zxgDb??z}<_`87<__6l)mSpm_C8DEMK7X&=qT500Kh>JBMFo6yrzlAB{U*;B-nCO1B zu!0B2u3OqL)@jGXYC6*-G0SVZxFV&4w|}wVS}wa9$!@%uxs{FZ!uA3O!AglRqw0Im zQ%IR#G}8nHmZtHmd#354d5|=sk}u$$Xr^Iq2xvGSWJac$PLu#7zmDTFLF?j`iXg`0 zl2Ns6-GwC=3``NDJ7`l{7%T19(M;#k4+<^}xZA*)mByjTeufL0o*Y4?R2r0-$ZJLC z8`WKmHnQ{vj`;Z~O?c#Qy!Y`_dfbN6NDmT#CxR#tC?T+ND_c722HYm~xZZj9gzP{(@#*SFll z!q7w$!w6PPC%@Bc0q_!i_cd|BI)qiWT<4GEtDoxnk_#=7_~wMW#~X1k zL8z{XSb(^%X^BdfFfaRo7KQq~?hb0*WVSt8aY^MV%y#nbIa#G4#lx!eyIONu5`CS3jE$?!jHKepa5HpV#`mJw zp$QKQnL2GgsHAe>n5}yGL7Mu<9d-91DaJ($GH5*pZoMS-$lFGnLha;?y%pq%^AMSI zC*D+$sH&IodNn{7x;-qEr@|$z496ts-M|cbkB>~hlc`;>bIDeQ2QcWqcfLop3YRp2 z32Rhds5iqvA;k&CpyrFg7dR%CLuGOVleE(tpc>>VGrb`?59)`B35h;V(QTbt*(w`C*YT%ImJ!9w5B=l&?1;EvEmw1b_kn3gHs5s<9%)Hz2sa0sQvQupbu>Mi z*n2_d$`=d7&#s1dL0$UHd*2-A!RfrkA`Wf=rv9asx@(kcSTk8BvzrTei=j) z<&K;lelAfOXqb9RlLIZN5ydP+BIL7JnS?`Ou(Q`QCdJu2R^)e1xN}hi*-62XERwnt zn>Fjy?cnGZGu#}6vnjoKjdL5?;iL*Y<=_fMmIO@$0>q#Q?y*K8?ikz8nprgY-v!?G z>~Dx0lrm#le6B>BiYpN985}a2C)E_V3MKxZ+(Gax5UKcRDiu~faD$YJzbYzg(*#N6 zyBPUrkhge*Nn^TkDr`+ur!@%}8Fg0ngwVX)Av{`^9S1n69a-_9l^VlX7QQP+_QW=f zP$ODlH%^U-Q;_eV+Iqb3gM`sEjZ1h8pu|VJe$A`hanZbhbi4;b@v&GGXnz*@MpJg; z>@qx-Fj-#Zb679GCGoSorZ31T_IvW~D8_beb`kc!_eQpyd1@pHIaZ>OG;_=eD_grB zu0dyKp##4|*VZe|Y&6(5QIO7JY0NIjtoD)v<^o|)7sDSy6f96h98N0|$;r#R_)VEf zy5L7!HX_nhNGzF2?78l}+%v2YP{|`fw(V=ri(%;#N=~#+7qB5UWcqscHO*;pZWIfS ztzRsrhhgnd1#`%@%B74(_pT5TtkY;x+a z#6D=V+O)zuY`++EFWIVeWfvqi*VNHu1cG|ebuxhPQ5Ndu0nl9=R&Ci&ycg;~uYz}C z^8tjnyC?po(i_p=VF2X=bw_Vg22#KDNSh1fLVTxVnixS;XX}@;W-cem$uT9bbYSVs zfhRT6)Y_-UYPK~Ib**E|lJkxQ(Q-eh@vWp$k|8Y4=0kuFtcC|a$Z=PRrYaE~fI6q5 z;3z}naFY&A)9sdci7Yn6D-tWX{T=-19*Wtvm%eEHGwy$&oVKJ;Z zx(n3U$is8WBT(U9O{Ag+N&;&DD@0vne!+3P3BV&4m@8ByU3l%_@=iSRmB_%z z3a+E~u&?{$v7(9RF_YyMiDY^Bz7Zosu@UgmZADV?*ez-&!U^vS?^)Jhyb2fBUb(?b z;eOl>5__&RV_A*5*+5D2Let&9#15jU<+4&aF*t#|7e3e-#a#=X5)UB{z6w+4R|jax zKY%!L6LDp$%1Tp*OQ;nTyF2{QY8KE;_u?U;?A7V2#}A$69OIVkU(_Acl?kpL65B-+ zpHSGv!sV)XJBSRX{A$ehC&^eI_;jk%(mdvpHF4UVw01$vMV|dZ%io|kXd$yAjn#0i zNpGvWOrUL|EF)joK)+yDe9;(7@7IAt?<4JZbn6dz5~zeTl3V~-5J+RbLl@B)Cu|w z=`3`u+%HRX2ccsHFlcMM*V2-rEyoQup7c&>Vdr81tEqkS92mg?FJa2zgh1S zI{u=?!VoWf73)lR;3*^tf=Sz?Krrjc*Q@bSS`6X2Yw~bQHKnril*99a=q?6Elyl26g&3=1c4YFVKnQY~$Y$X8 zoP1>8?~8?&EK-X#c30v04s}ElmjpvTGZei(!Kq#_gi-B%q?9o=_#zMpF`*1wjIZ$L zj-90vU9uZ7+=f7fkD6R6B24lnD~#FlbhL@$lh%`mLJmC^hr=jz#l6Utt(cbV*EfOj z)%yR+;#PT;)kE?sXY%JU#-lggt6Wd3DeD5MvnY&S5^uts=pz-GnWX8};)!z2#-%2D z6^X>ZuY#9d56z4}(on&{9n>U))F(aNdri;XsbI7iXm~ste&~pItl1Fad$drSK?b7?GrYuOH7*qYS6ZTp=i-%;wG}$5V!DAC2qwRO`zp|Vs0|AiCv^X>xnG@p4i!p zHcZDtTxc50iIUsp4q+)_N3WELS8)Q&ObZ}Y?~_+>0>|v>xi{cYeVA4;uRn&pC5dY-E5#_1 zxk{V)p!%(DRC5V{J^Mt%v=TZs(m@le?)2#((4llkPu#_?5^&d)5{jifVxG&rZ`rIB zwiCK`qWnVUio3PGb~c^T=zd1F-so_M-z>ifeJ7Gd$SuRY>`9%Hm;&|#MXc~}XMWm6 zJ@=r2RdKLDY+)N4RA=M1+wobpanB8_3?JnX`hhoTj<<;mmLe`df_+((QiUJJ z81t|{954K$watWthelM*`rgcz$rtc@&&u|o`!imWE)jZlHk4du!No|Xr3DpR>5`&> zA`nbb@5tzqNUKCJ72NaeBYIhDpP`Ysc5MK$4-xWbAcRF=x&fCWWY!uiFH}x4HO!l5 zOu1dN>E1$Qg|s>VJi-5q8Uvyy3fQ4BnEVPP(PQwWE@5!AWhJI<1gYy|TC^T`+56o= zlSF6ZIwN{e8h-cO98@<6*f$K12&0!Pyubt)&hT2a;3WQt&Ep(Sy+A0zN zaN7Y-0uEy_-6VK6LzP5|V=XCR9UO%?-tzW)pZ`%GmKLa0tEL<&|C zeN!AsGPGp&jEPo_HFXwz^5)-rhrC#3j$sCkwp1!jFIh)--^~CecmhDVFtbFU1zx4?9<%I?ufy-r znt*s$oAM6JQuG-)cZUazT|t$|5W*)5AGn?_;1;Kp*q$`cKB9^eHtwL?Y~bg^bLE>^ zeV)wQJZvvKC_0_tl+3SUP=avV|2c_7%g6BZG1I`Wk-&t7D}#zrwy}!jA{zoW=)an$ z>3+-j#NQM!hdd>GcAa#sDAzQ{zb1AVKv;^91+NZRTh=%5c<%8|{XdZJT!KL9zJh!pdq=j72npHC$BhnIY=RJtpjbjO4?b{@S|2&Na-l91 zriPEQ@V*3H+JaN7Rj!eFui5=dwt|yL#u6)j zD&odXTo_}Nb;qmL6R|4I>Kki1$?03zzt)qkalJa&SAP~AVPsp8h`;2{zqj3F)Guct z4}L?O5*@@oRh+UV>$y6mgj~Og&Z`=sMo5SoS*!8RbltWFe(F@wS?Swp4|DNcN-c4<6+@;A;vt zG#&KBW*y)M`3srUP}pL059*{lFY$*z=%ss5Iu&;bJ=6rjP=He^J|Wk`j0{+|2gZ36 zVQ(PUm=Nkuz6rDNJilWzOz2KZnBEE@Ve7|SmiFYGln_9(a*&MQ3y-lAXeUMdEwd{W4X z@;1`3zKPCzuc{3s&=4|&IXQYr31;=Zn=@`M>F z`@qN3CZreJ4nvXwF-(yMHX5(jrLEd79A~`-m4AC7j_EmaHa;2WM%)xWnnXaICN8Lg zda3wONQ}-jc#L8n8aVwaG;#L)bted0prfrTO`Lu=^+u8VLXTjFE+MqVEsRxgl=FQF zx%5+wg)Vg*cpE&a9aD6&!B%+8UU1{hOiVc|BzV(bl*Xk1TayS~@Xn~W79`|0mV9Gj zj1wDdsl;BG4Wu3iFhDFsnMhR^y?{v)VlznXjwT=|5HE$Zdsru!{&MSrV6F(K=R$1#AyDG!o~R49nxx%Bif@wS5#FO;k-;N>2O6d5Qt z7M}+3779KX>%^|&6Y;^Kr*TZ%lRMvga@g>hVO`w#m3Z-)|aq`r6)a!@fyx)8yl4Evo%j`zuhZzH$2liJW0Jo z5^G+?7M2l@i!B?-QOyt~Gpv+oZv7|x*Kj8`Z{e4Ew318L5ODM`h=ls+#d^rA(4$8i z;n6JzUdi?Oq-*2Wm88)g3h`i7_!U4;FoIUMDmM!QFWQCM#-21)i|uTo@3d%zi+niP zDLG1ouqxW85A2bY7JIMq?NP}w6zj`PyHoUbfeUY53~w&h3O~U|@H{{~QL5{U*9q1c zuLM5j7h9h@=O86tBuW)uekMYk)2UZ5K?U%uCOQ(B-6Sk48f_D3SeNWbocS^|28=!9 z`6tm)J28Ih zR=XKM$Bkqgo&y^`u$S2MS#r9#8hP#BN+`ZaUgZMvDi@H?_3y!ibDAcjr8Vkr=m5eR z)-dU@n^95PMT7LJQN8iLH)G~2;R32@v6->4-s{w!>$F0~PMQU{2hth3iJSuBcG*KP zIj=7#45+Nw?^=*Ic~b+z{TpRi$CYXP~QGC8|!vSecV9@Y9`KLb)Oj zJ%o6Xua@^~vRuLy83E?Xqr0@4ywI}cF zcf;7?N}tAJ(OS=JaqIab zukST>S%Q;imkAr22EZU=M4Fo;p^_sc#{)5|nN;`9_TA%*(!BW@eZ@m#X&Mfh3&soo z?*%4Hz9tl1Y>#|BUQ(CsZd19%tRcCo-*%u2iEIX4Jka8)a0b~$*9WsYZ1=nDb{$$I zqSuB$yNQF{C5`&n^3g$j!30%_Ds9YGR1qaVx8-M(Zm{VC&tc=LEG+*LJ81DCMOYaj zmJvJ81OzV!jQUAWX$Z6N)MHpiqQJ1?*dw8IdAUap4$#2%es}PZL<|VCKE0zy%VQVW z)&QRhv>w^5EGPP&PR%r7E(tE5NKM=@jf2)s!MOQWmU~PC-IkRt(+n&Tg}|!)$_1wJ z4n`3w@d(VxC5A8{8HTug{P=o+lw?w~(ncq2Co3;eFd|lOH>w$1jG$FvcF1Usj7bpx=j}xL|0|~gVs~txSlvk$LlZaHtq~IF= zd%|R5ge*679cU-j|3q_>(VX%o+Uy8}M;yspw5U;G0=TY1g9pFhM727F%$2kwvk*Pr zFc1$I9V}Y$w(M^eJ~MW5otaci1C%Fp09-=&smEwNJRAxGCsyK4hdpuSbP&4hX}L=L ze7c2oi#FnETZ12MAs_pM%RpByv>{Oxlc6=Iex2umGL_TCw)Fb7=G zy7}rX_lCH|z~c!XJdxL-9twt>zh$j8iQEWbMN<}3HX-gYKCs0gGATng;@U8C)$Wdh z_akuR&2g@dU{Y@@h&p@jTl1Qw0FAKr8h8Cb-gnOmeS)M`jnwUt8;*U$m==$7cc@C- z^AIl-smdAx6M^UwuTCi9ibNKxprh{k6r(}<-g1;w`NHEmKZM71w&(5Ju)t!y3lNf6 z3{0T!V4`un5jb(XCxAeiZWXn{=;XF&(Xhsx;CSw#PJ2f-kudZ}9sYXg)?$2{?QGnR z>0FMKh@@;8%144q#-<<<1vB9zljQVG%}l6m*;)Zrl^Bi#xPHqdristdt|_(TJ(2<^ z2rH+Bk4!M=b zOYps8$IYtQRSQ-KsTG5@RY_~D*&z~;Ik30}cSeU00K-@m?UYLQmv;+IGE(Fg#tjhS z15kMz!#!i#smHXtONw)+F>Tgku)+k%)JKq{<@}W-bg->AU|1tgbf%M5scSR5*p#3- z)y<0Uuulb-d@FCJ0C8@@1qwoHJ#p0oU^1$lu!YIb4&=?qhg*DGEB5y31I9}3jyGU`^Je@rjpU5=(k}>LPrf#)w6M4z}(p7oiPKD z!6V}E5H4a^KWG{dY$s$`2BL*wyy;B+)#XC7@v%sd#@npztEObI)S(rIiW3FR2ngv`tgflZ}h(n4jM<)*{ zGTU^cS)y0SQ|#syFeC}zKmq-J9C$ncI@t{4xdf!asU9r+R2@&$!)9qi%fnG=WC*SXwy`J)#iEzO+DPM+EBmOQ$34TZJO%(2f(bG7u6h+ z>;H;<)Zl(Q^9Tyez-hS}C%j6IyP^f`@9ujqCZ$W8j0r|kkRk!Xv>aMlxT_a~1dS_W z#6%?6r&*}~DxpTabKrSmq2NL{@i0VaXM5`q#ImSd;T&36L|fHi)n>L`u_TG!SD7m9 zz0!5UsL3iwxp28vNcp!8?M2SGz8>DefQAynTJ?!f)C;CRy_bn88WKsQwpluwGbhxcI?vJLSdw*v=yJZF!dQzy8bP@%y)*zWw#* zAAbJr*WbVW@cXyF{NWG(_RG)Te*4=${Qa-Le*5z0Z-4yZPrv;B?Vo@8_uu~h?N5LE z>DS-?<+s26>!1JWmv8viUw`}c&;Rwmefi-ZfB27ws0>DhsZ6WLh@G@SUJx{ry-)P4 z_0US_n{pR#y+_C?vWGjW!?)-yQbAmRKBj)r$~0`lhZQcg1CRU!E{pB+@wi{|y5#$D zWtb-&1@A?VQ-ciCb?^e!lb@nf@D`@wu!$H-m#8)281@G1q<|p&iTCNZaY1}Nv>AIr zp$(XBz!=a5T4+t^^hM8hh^|!RIDZwC5D{hKD#N?p(YRy(>-{1dNwv}GmsQu@IB=$<$rlrYzG@X@;J@N(ImV>t-_6IJ( z;{a`8CX(fb`X&)A{K6;@C85;BvSQ z>ctJlMvjUuC?lZP#G8vls~4M-Waf8nVM*3o4KbwKiYXs3fW9gxA!wrv+T4~z5aS5q z-SWI(3=>eszi~!N>)+c1kPDIlafbq7Y z6M%E2yMDwnfS>OvOvYFR3d^0KgGcNICyzT6IT)Zwfu)-KcnC%)aGSs?HbIlIiN6$SVGVUCb>NsWiRi`x9mQ1?DB+t(P$RtG6S}ghbJw7+`uuqr5r(%V-E6x+Kxw zm8N%%l4q-Otx{sl4wUxE7X`8j2X7=>7Q?@uY*aGksAG{Lb*SQJiDYLBWE)mKz9X`s zog9eBqZz8cTE59n4w&M)Id~V5$tDgnm2ppHB>R&KCMH~Yy=`Jr15NPrq<97;*aK9* z9OGcTufk;4Rx^ix<)CFD5Y2!baQaVv{^>>xq=qw%;IHfgA?m%Jwyx{36B+_(VKz!SF)e?J4Y%Z_JCio)>(&Ynrl@sN2{d*yZ zC+JY&(`C?doOr%k3*gNIf9?>GT8XUwwowTZ`AptC`_=v4Pf2 z+m$MgSr%yBnF6|uyu8ZD%d5OEuf&KzBKg8S6!8u%4+ES8u`c`@#>+Xj7g2XEdavcm8dbvHo>1lQvOZzp9Z(W zBd}UabN_S)!%U2n8d6i+8S9xDv<$U_w#7yF#datZH zWRE(WG9ZlQB67_Eh?mzfn7JAq1UgZgbA=A+#ZqrWx4WI}83E49=7dlm!YzVNEVK6pT(By} z)>gj47r=_>Azo0uWxvvp4kQ0Eyz(l;?tPW>O6l;Y%op(^T_%jq5mz7xlsiOp#wHuk*gIPUL*&hX0+pp?TDVZ3G#_YJR$CAJZIse8*$Rj^I%@w z5j2GCjlC|ALSgSkQZ(EztGm!)MD)Am4fhC*9nS_?086Ga=%LnBm!^lq0iVHkVY7ij z6G0EM#-bF@q_JIH-VS5{ssh;F@G3)Y(8LYsibWRbUZ!&un4xPDHKA8g6nQO`NWLqd z>)$b=?YbwG9y++D1XI*X@~eXO^gEjOG(y{WTa9ea8HIxsEGu&0B92S(9VC3Eh6-iZ z+bNnr=(sod*S2owYH~3vXxP&Bc*1DGb`rE|+bc7Ap&k6rs=sI%tau>RDfAMSNro4} zEM%?tVkv8xZHjtSzGA5aCbEY0a~FQjNk&oGXnS;X zBMrwxbINT%JvzdJXg~YnT$j(yK!D~ktiAj==JdqNgd38q~J};#q+A-giS!ZPP-xB zqEAJvyVx}9OiC~)w}8shPU2zTCLRIpe^yr2Uk?Y1KiZO8iHQppCAkeBRZ(Q|#rQeh zXui;zg|CX-USvfW>ia9dG=8e#J@b+Gb(qb;Sq*Yo(Ng_asE1~rmRoI|*NPf0TSAv) zMs!t_fBD|5I?YJGC0*fWERd0YvtdPzjlW~a(n-TOH<4x(CRK(y7nxQALbWYa&BG(? z`F5bm#>6W15QsHFZa}AlGTXtkb?$2cq@=S*6BNLF2dB}-&WZS;?HA$)64AtfdGLK( z!9mSfJa|2RmWWOiq88vIt84ho0-XhLGO^jQIhqVYE$hGz zbYycCJ{RyuvRY$8QJ@9=qW>*zTISvYwWx_3=uzPpkG`b2u_k!n6ofP{u<7x_uTe|} zbIGgR99}K(D}i^HOLWeLV|7cEb(`;emW^tYWnKne#EzneWgHUj1w|K3G|l|DMm_EY z(#B~Ly5>?Jj$OaVkKyUW{xBnr25j`u(6NNC)3O(gbiqc+p4<4Q@M}HrDi|UQ zTMEaK$IE!eqb@L^r1zC_zu*VxiK@NeOAV9{E+ANi5gTeBqOiaFn?$InVL`$n$dG;9 zz}LViSRD=W^*CjQXtE$TGOPR|?hiimb=QsByF)TrKSH zK^7H0%aBt>dDnW3EU|`Tp*PGml(exW_W@ytDHvmk}5`X;n!@iP$1REC#?xA zPANU*%`jo&9RBT~Z~-f7a-8m}J1|j-UiX1gRu^XgC*4HkHPr_+;E6)V0p+F)QSSX$ z`>PN3{ob;4&xOx~8LE3!8@ZoirsBM0iC5HatOID{02nNUwM!kr)NpwV8Y13NrdIK| zO0q*{U ze;tb)X*9JI6?2vBvhkg@xi2sd=M`5STnXgD5EbhjV}y`pPx%EYtH)0))#|9LY7Bc9 zE6jkdhTKZZScqNl6Y^XNWUU5?A_cfq$0}I!`dq^(O_BebLffu;*y2YE zGpo)VG*YdSFw52Xes0k%#Q5RkYY?IFw4P1cU{q^Q+@P(Tz|%(6jDG z4^P|ze!T8k=hTx&N2DZs7BJ-&rw_OEtc6-(nx%s)l&cwVEyYse{AK?g7$YfHHEEpc zlwuJ~|0%PINXt?ylcJ12NwF&CXnD!_!wcxsK~mtsv*SQkenPN^KI;Lk1;&;v*VdWU zpk9DaE3!`$a!+I^aC~512#@M%l)K^uA!A`Sr8*P1Gyw+RHy{^2x|&uK*k}a+0&~vu zu_T~~E0aELW=SnlRW?<2JDpUZit5H|N^J3IEYE<;`u>8*0^$P}k;KuBd@-++LQ456 zpL<{Bd@2dVA>+4Xd35hp$CD1S@%_Bj-7k(q+ko$Kz1~9!8sdDEgYCi9( zdcQkpl6WGTHlqiHhIzfvnTPxndEt`P|IDukw%T7;mvhuettoE!o! z*EyeAtOst;Oc*i(AcWb&Qc_wR+BUC5A#u#4HD-1~`%Yt5e_y|@SiUBT(W&X@M`$o@ z@wF@ z6Y&uSl06!6ZPCsd3hba(_}+;lqs?6ie*o63>8QRH2d7LgV~gaAEs&z3aD-}*r zn23}clkLqoNLA?)Q7SBG5JU&VQk%2WfoF}bpwAeeXDm1)S0R8pvK_Z8KQG&UgqEoT zt)YPTF4?|h=e44ekcTPr0H+Lq38ts;x65+M!n!g6(pzEhePP2(4p24vp-^ zD7nYTR=T2TWQlSe{*&74xel#wY=k;;v2&?1&JH(I$2>uvQ^?Ozf!kp8uZGr&YNooC zwuGhiwxC=X+Z7IdNn23#3^4I*116cv%IE9Vw?@!-pm~h%CSod^vkPJmMDu#g6htAr zE1OI;QmnrBD(WDwUKJTC$Q>#|rh_zxlM%65p8z02;_Z$|0XPs&C(1R`JNsIaw~>yk z0_Vg&tkR{^vo@wTfq}QB+Gm@SJu#t$+RqUak7~M4y8zH)TDJI;NuLU_sq`sD-6taF zYtu4X07m;{eVVQDU5k6NK~C_b?hCaGz)E#)oq7VfJTNNEWnsuAs1_Fzgg%;*JY^JgH z3y)LSPZ-f8#TAUKIydA;v5_XPsdHvUsuO%8nEOU2!q;PgrVMuixm@ELrBgwUmYidf zV;T=AfZwBn2eup*4vWOI89Kj7nLU&6&=^1}Rvn&jC1G$26>Ppp42|~cz zkEisT{Ke=HFCtf7*WheKFtS$psLis#2Cd_FE&xgRT6CQ-3LjNL0d157M9_x(6vh4y zvm79Y><%Dc)*s}OiAVT7jPh;@-OHBg{W3#t{T)(p`9LlvDYpKNT3cRsE01-+uc(9I z8ewy!qMUTLUT@Q&Xt_RFfG~jnr1pBtJPmV_i=8WOQzM}EoK9eC zEbGpdF;elukFhDI<@B;Z0e$H$dlNHCw%~Ly(Tt*FkH#k*ciB;|NrTo(r>)zD1TYf` zF_v1)F0vAw8-k=2MUOJyTndmMgO$Z^ZaSvSlrjRu1f;|$rlI;#fJA5wYn;A?0>YpY zhl)oYAr^Bk;4Wh#6=Gi#H^6%q>%$w^G1I2rvf&tN$g+@bK&>gHip!+r%|Wn%#hJ#& z>NVdIUK?JrKp9CkK=V_1-d6@ETr^L1r@EA$G&bReJ+}t-1V-bAt5PFXrF{fAV?A{G z9$k4&WE_3HcxO z_UOe0*~!qG5c}R`+svku%o072g0@)*VRCIxiS-(x@WJBGCZi~!NnP+7L%i0 zXwAY`)!XAn1KMw#@(Fmd?YI~ccmG`}WL+9;mnKyH%@oSx&vWrgK@z%fH!cPmu$ks+ z%4nH+yU@~!8I^pmttholsO5AHMID$>V#CI%Dc~%$ zsSNvWc33=-@BwZC?SLTb%#4LF(D^*U)FrLeu^RXRmM~oyyk7fOvfGifp1v;pT(+B3 z37dp<6Y8{JvsJUVTzhYp=i#m?_APkC_koA@@?shs!!z>wi36EFEp(JGvJZjCf|c|y zj2!tM^Hh$ZyuIS5DEIsuLjB&4P)s%al`nY|4WU98zVLlwHu8xM<5IPfvT+^Uw8-J) z)eVMKOsSjwY-__RB7hUlc8-EIk(g}hvre5J6Nout$}+QC3MsRI0Y64&#P_P!>l4*- zii5>Cr3GD;g8&~mv}V9ih}YftNXQMP!7?f|ikzLinVj8NP4>4h<9MNiPof8b+T>KE zp?U%e6YeLK;B&wr=>dLEXe?!IE#Z>eq(nqGTG%F4n;w>QwW@ifDXVfUv`(R5>hP0? z2F6887vYwcY?z!9^)TDVr|zPKU?~pupp#PQDL5IBijLX#u7(FvjYbeKx6>rMC#W*J zw=$!+Sc59jFe9`gRxk@Cz7QW0lYI`!Hh^K&UZv1Btgd&M%)NgSZiaBm*+g$KHswOh zNrae%0FmI9-anmML#(!T+tjJuLmN2|N6*j7@-Xo@NbTbwvEJJ!1 zr$i8d(Q{G`s&65=s~7c~v~JU*>VRH)u}hMBZW^&6LJ0SXO+248#}anP>6!G%eZrha z_kv%ldwV8*n?`b8p`^zuUJ)QNoFK1 zo<#VKDUu$1Re;8MBl;YJ4h!7C+upYzhZ=*#2QVOXqs_CT=-si}L?^cSH|XEq6pg$u z-<(a=^ryzdu-fUjKSnLRlG=D^EZCUnn)_eKIMj#SCx;|5Y z93xE7yVS(^bXzv8=O&cI2oxncxU`@OA~#YhJFi~Aay=StFv1g&skUV&au^G;YenTu zZ6f77OI&EtNEpm`*+Ogh>}AN=s)fuVDqaaf6{Csot4p~(*XOjQgx`o=PFY+kN{Ow} z{@@hJ<6-w?YJ2g)BxK#tfu$yWby2w!L@Q+RmToX>!l=#6FBwhheP(Z@iDv3JcV6k-k->Q<1Qxuq zpu^$#gLZuep_=A)hi6+-^&)!c@T;WbvN3v57U)8m0c@vfR%{1W?*Q!21N#d%6G58f z@^K5N5(|}LFY|bx@uSB(Y_tu4n$tNP-a@wGybcT40*}|UToC{o0A&k@mHT{@Dp)^6 z`2pd8rO&L|G<#YsLf9!}Wt&ql;be+zQ_hkPkp&I_Yd4k~Tx{dPFm|mymGnTcgH~k$ z+16}fnLP#R^|DJA@mUkB%_x0-1--njqY(*2LKaU!0o6PMzW_-$D%hk%>LH0x8-XNG z*7FD9sn-ajoA(PeWwl6mgZ$vu_O97+1hH5^&h1WejB{aEGM3i$LMhm}{uJbpbRVjN ztFPX9nVq8Tu+tLKIqFchFPACgHGkwBRSwPe&4iHL`TJXU(K7A!u3}8`VlM?yGf?wra^$w9- zg>>an%;f0S4wzR22?(k|C2&;m!oew&mU5LuKt+o@Akco9`cMrMy9ANN(ez%wNT9IA z^$i;+^}K%9loCFJQ}Iz*Bv^^M7y2R8`T)$3rabYeR>DkT1w}{?62ppEa=v)LigoI~ z)4T||bdn9Sx^NqtoHua;IkHJw*pEzxY_eHF;21>uIS{T+DaRtGhGp#PJUk2g3C@ue zV$pNfpIAeKJ>Z-0TojpEv%-EE3R09E`-ShLgaYLjq2i6FMlt%?)L7ZK=ovuCeAmK; z5G-Esrt3+zYI9^*CR%)a;JqBr8RuG&DibI>=hB#MSF#$5HrlmdoYSw0ZDL)}6EQkm z4NUV~fb{;aSfq%1L4t8o1z1UZe1H*68w9DiBMB)DHXo*Sd7mY{=>ZjDst%7_5HTwFS0%}Y3NdYo zWfiMNW))(O&>Hj^);-9!qSz-T=6aG%2K4Aa7y6%{T+aeYkBU05VyP^^yF_WIT(vn zw^*E&C{C6%pu zi;fd(!bc~C#9=TpDUKChPt3|4v{NXgr0J~Ob4zVQmXxdnq3><}KlZ+D*K*v*^8I{8 zeSjZQyUKNOU%sGUX2!rg8iuofCKBz0S9hRwiX6Y7s9@(0N-e&LLG@n9@~$$CNLE z(n%M|D7znVDzhd=5ur)5lOr@4+Nw}ZFCN%1R@jB*K>i5)hH8|pke)Vt%C{LnhOhdz z>K?Z0JmLmquizHGP=5ioy1dP1Tyh!c&=Ti%v>({f>Wb}XpWD%{v+w=yWSh)r-GJV2a711LZz#66| zNKvQEcTshly_MBhM1x)U$B6_z1kwaX9dl+Ny`Vz}UI5FGOKgjZW6ZgX(ymCvQ3Y6zuwV) zYlQB9gRq;ygt2$VCHJscugRcmBT6j%k9UL?du(k{PfFJ)FMdHN`OxH^Dz0!`+FG@z zRAy&W6=`kc7zvBIm8eu8tWtr-PqU#vRe~%?zoXJSS-4m3J*SXRPj%T@4umpOl_bK? z^RVoBcpxuad87sE^(xxb`GNa_5sWGB*%k9SJZ(9M@U-FE0Rm+F!|zUWcSf{Ui+*6> zX`PJnYWcWPk0d*gXa(>EKADNR#V%b`EAXRL!8=^6D13?C4Bd3yi$Moj!e(w<`-rpccQMYqicu)gY^ohCY_Ow!ZbuI% zw(~(uz-t=FJXR&cMeokZ)foXN7;^qO3M}%R9d!)u1h2EF(LeFudHx8*BTb>5w6*l< z$OvnXmaiR?C>=%~Z)G6kurNc$F!P7*iQHN$QfXJqFLI5e_GuOfm8)+HEijG4{SbA^ zf=fMBUh$;EY$}+TYqYU<4R%Hl{}iWiuu7>n>F*s)YwT#(+tIH3upih57KHbX4-9c? zpV)jNIA+|}gcXfY@)NVCpRn=SmKvIrv{(2UbJ0Xl@t~Le)~0+THb#|>xmj*_gzT&) z&>&|zqUfL3+}n8M}0H} zvZHC39qkJH-v92*TXQMS9mV}`wJyq z6E1gN4@3kR=#g<_6-e}tIK)Y_Du3Ok$|V!EsCS*;|bG-`G z1|i4|l48SIAK}dEhbAe;H+Hno?PyKd_x^W>m&_rkH4NivjcFgAF7kp`KJP^h);(UXru$3rmx!%n2_S|n_ z+;=(HzlV88O%ObLMj*+=MkoZ2@HI0Ya)2X>rRPSFg z=DbNYQD-3H1RJwPiZhp&qd{vNx9h1Er{Yp zmF9Vnn@tO><%Z_73(%xs#Ewr;5WfHG(~}b--th+Kw0qt{x&jT&UVy7BWeumr;L;*J zA^6(^6tmmuF>@%*;P;Mp;l3jv_LAmW_)lU@IqO1Y?o)0W)jtnrUjiPeVX)>qUVyjP zZf2PDu3Z3cE&3=sos7(qqvr5cpy4^ew4b1C&9}Q1-UK4)7nYH|zTPcW1T}QG;QZ1^ zu8~-US_`ydOO@P6E|(n5xe(4_wcC_Z7#Z%mWAOlHfQ88};eF6bj#;B7U{Ul zcfL%PxQ1=gb;y(NQ__{i#V-p@R^?tXzXsC8I{FTJ;_&e_CA( zjaf$>DD#)HS%2I`1<1kZoh;y<3rry%K`MY=`m!yZaGXT$VQz`k@`2zBlh|(nDGI`Z zR>l1*QXBRz+F_dDg(wST#B700E2Z*N2Eh8=wj^zVGKG>%gT%#04GXrmYRvBVLp2($dN5(h{!cQwUfKg24+f3a{ZBP9}BdbBv_k z1JZ!E!z&YdoNhvcTqxg1${L8%ZTDckL~JNha=xu{af@D6l%`GZ6q=NR@*r)8DARH% zFU(l?I0+sRy5h=q8zuGi1g1{PuUgvmwOBknV^IuPxCJ-GoGlFc$R)sy-tp!X|6Urm zRr}07(XYf(@&dF%%3SWY)p0;prMBfri`z|OckXxJ=63T5W?lp6DDCg??-I~7@=9Hq zaPZj%V0wGmMIf-14CTTo8_8!&A2C?TWPIAdorRsM2-(DKC_qknbE{y(-o7jYA@8}wG6AQ;?EZPWig+E8>&0>~= zE)*V(0%`5&pM2g?J88;8t_&@?j6=6g<$_; z1s`dnn_q9ozNh|I;%;aM=EOfk}MVm$g{r9LVB9&bpr1Jbz7ry)xD&qG4OnL*La2ORX~MV|F8QZs4p;`~q^O zr47*KtBVPV`91wG%$S~7NQ6>RXL+hU#VoI$ngk$P%>t>508OkN3xlHu|K+CDhhhaC&#g0QSBzy~_Q9I#bous}Pp2RQz>2 zdEUp?rKgPaeC7rAlFQ)_bb#@+iVJLWYDJpnL`=(yvz!YNg(Q)SBo1`q3ZDKXZ@Kf? zAa6Je+)Co&)-E>HNxU!>b2EE4_slL-DMgF}rfJPolUh=C1AxS@a19q|)wL8(8ItWx zDK~RBO)Xtu@Op2YE&Sp|m43QxmQmv@8hpgr-!j8zt}Rp$&Mh`2e*`$($+WX_xGD^S z)P1QbZsfy0O$EZ@UUf`qR2xJ)~u@QFSx43a_T!eJhlH1tJvw#Rw%9@p`SwU0m2 z|8Gt|XNDpTH{^U9#ldS+9p>3g(8w}eGz`J(0z2}>*Dq`zOd+%CYsV(amP?zQY``;5 zPNSsssZu|PnYtKQJtQVYml0Q4Fo&)<*+4AHOWWTVAR5VVm+v>}p1U~7l-QW=FD^Hs zvoPqr1DADv&BF*!_OAnK z$ZyceY=)?^m6bsYfw$FXwHOEE`GT%l|7F8!+ZT1j%K&!k@nyrDacec@*Ug!Jt#uI{U2iaV-Pj%<2Zq~10-bx~sz5N^xHvMDv}?yQgh$B4pujCb>daOldy(YX z;{26_f+6ue<9ax$;^C*ObHZ?%jKy&k;oZX_loJhs7*IRD%B<;Qob+Mn_$t!yB9tn_ zQ=3FLk9!aS8zRv5wp5flMJJD;;b34ZQ7);atU$cIP!!MG*eOA|81w!lp{<<~dh>+6 z&%@Rh@cx39+h`dR_l{0pJV{Kms(^mGIGSZkcs6DvgopIM>kZ<%qS^3C%K9Tts@b|| z4;`k8ZuPjBP6U;3bBq@jOoCw{xH=fxbU|;#TDch`D%-N zv++E!y{1vGPe>qhftETwH=@)7$+;yjc4?tx%-=vM2$JoZm34h2fJ<&JK@vCXV1|tw z9O?e4Pa7!+CsERvWE|_$;6~^Qpsq{N7)lc&dN$x!%?lTnj4O_@P#o*KHsU5js=lCwcW{UTc&_(DXa!IAoD1_*+#3>0)f&4?RN1#Q)aXs}=hQvM z1>ct3Ls;TPrmMkufE0Of+X&2hj|771edSE>0N`Oq)v>a6-;t1vXV4jbxP| zm^tA+)~Rj|NdyEs9HAF{=_}v5^OL?!&j`t7?`>icU;o+SYUkq+znF{`u<=s)D#K^C zm}^SZ%O(6w8F9lZ-DL~I)RYbvh%Mo04}PgHD@BQ_HPeDq%S~Qf*N9zhgdjjv4fcD- z5VQ?7T7x!HAtrV;?x{^K7NQ$BE`mX}?uEBh)@18Q@WfG)lc4}MCKE~tfO5FLHZ&uc zFt3HCSSdpr&7RQA)nLork8l^@-p@7# zk`51wz0%jIv}{pOHV5ilvhtzG?NS8E8bsftpNy|d&V3_F`GXrsFGOYBeddD;^iYzi zggjlox*)X^P`O5xNPQJKww%R_1j)l*VIogmkT4ceoT4ZiT~%`UKCciZtF-!IZ+hs2 zN*SK92;`v2*v6h8XOY}uAXm-}+5PNTe(%AX{lJu>LlgEDZ~BUD_tj!zZ&X1uJ+K$nC94&1Z}JopOl0;0QF#nOiYOTTZ56 zDN?t!dbnEM=Oy(wP3k^!BMI(IjXF{({FdAhvVyhme3v!rp+jk$fv4jqq1i@;DEBNk zFslRCWa=LESdjX#0d&FH=uOnu4VlU_vpvuX%>Ku09fr4{qb?+bx|MHP&`LL-GoHTc z`2=ROTq!fr$-RMyyA5ICaF#yTnPZ+iN?ld0#m+Sq(i#@ zd5`oQx9bI(23G8F1=E~zO6+T7+5jt%eloM$d!)hBTBmcb32@Z3<^2zPAvrLE<_!$* zF&go%G!sHgq}1y$MX&dr^%&I-2WNzkBmz~}QAaxLS`M<5YU}qr*-$S!f({A*!t>l8 zedX%nARzWa`W*{LFyub!@#VSKR6s|)-L=^Dfui86hAHZ9gfubg2l5wO#lS6g(dqkB zTVi}g<29RuTMXp<-Rf=Iz}fga*ZB#jxj=ebvW~v4C10WtsF1{j>AMsFTf=j!B}iGS z01=Jc1c;w+#4wPyT>1M7ungE~jdo zSN=c_21KC~6!*K#5tLa|Yfj99lMV}PBvm$C7fKdR?!Og$f1iHDaG z$+A35KwDJt|MzAB&AEr6C(1p*aN?EW6a!ub9SDMTKvr#^3Tyy_4QN&19zj}kSp4E< zAC|fxnFTfj@xQsrdml3c8xa#KMh+xOB(+#5x1$pCbi%Yc#DwR39e%Fq1z)|-F76lR zzEFoEiv%z!!dRkq9r9R$uEQAxRt&gq=Au<-W4g$B!>Wl7?l5i|nhHhQ*Yv!i)YyD3 z50T0zt$XaYUMk77Qxke87{sE=$JY6j=pVcf4AxYfc?Ln{!p2PZc?M<@T|BarloaJW zQb=QOu%e=%Co-qQ9))G3Bil~(pbW>9#TG9z7Xk8kT8WauXG+6EZubiZgnNqSok~PT zsYCWmWZa;lQ|l4CJAxr`l^`<#48e=kG>Oc%9Q7yNq8O!KxV|1!rp@t?#T@>drtrh0 zz0KvTrWk!XKZzgBiy=)6v z=D(5R2)oPA*eUN9Q0XQadknm$Rp@9`s!4mFkq!tKQ=$}kD-Fg&_e@w@cd&l(sel4V ze)z5|S4LEr&&TDWc(fXmbocU10D}Nfp#^GT!UaJ9H|iWtEKVi{&}9mmEK!SsQn9a7 zdlutIThB`^`34IZOkO5PJ96~GD-t;AjHzYpVuu%8ECvFz=AqWLx-)ra^Q2m3_&t%N z5X?Tu0!z9~pg8{kI$R`-e2K;GZq}87qi0OfPC}*GC)-76o^qH{o_(5ypGKTgbABzy z0d3*V4j5!?4_b+2YX5u~^km_=33zt3nbhn<3}y>8DJrb1H7>Q;u6?&AGmFSK(tw>_ z=mK*b=BW6SbAzu`4%72yfBN^1TG<5B~2&;g*UFVgCAxp)h)? zJTlw@ZSqDf{_OKjyiS^of3})w(y*v`_FQ}leL(Bt;zQ-}$e|lShTUs>9s!_DJm*&20PV;u!BXpNyyd=`hEPcfC)}=Z_e>(?|@q zi`{bYu95Q~rsM&99kwfv@NXaYsjxb8Yxt>x+|x!7y|j zm#}AF+U0rKT2jn&!=ECT%<2sW+mVB(9%@JEGEF4@8;W@<$p)SSq}my`$4yZT;*}B< zeFg`fL3$E0^1Qk7^rmpI;h7qExniOuPmQh%nM0MD)D4;Q{?()wD-Sy=8DV>m%yp8z z*;Xx(Ic|0+X_bM^jLD|UCNrW?rSw8Bobbta%_)RL-Y$?@qM99;=y+KID%+og6AJH_ zuC}usJ$S+@(1fJS`>@esP~FdcMucKg<_7(%WC$PtioOUH^wxn1bd3xla=&etc;Ug@ zGK9ATlp?g48)!P&c-}0m8|c~@PVv%BvU(cAYt9$P_5{L+z@(LHC?ilV^TO*c#JMMH zLIyU6d>`NG?qn7hMV{PF#5p#St+^;z;yj2M$d=FdS$qM*-$|?m44g&^HW3vTy zufUmdkr>-Y>G=ZSkc{{M@eQ@P`zg~n&q&V{Kp8$Rc%C~1V%1zk>a^bEf2IjO_47^f za`&^j8InL~-V;U3&e6Bt#G*-4^g3(rXHITi-Z|SP3u%8&wpiZF)g?PY zwnsw}3sH`-5f=Cwv?Ms3om z%OTcFD`^C}S;RX6&nPr`#LM7;3<_sD)51MMD(0BZ7&uB;1&?343{sr>=wj3mMpH)a zYY4fb69=xs1NK1jDTeM{Y}Dcd)pFI=zp!GjO-M6>5^uTibOwQKQpoN6I3-&N3O;8z zN^@+?peGAAn}x#VV69FNAgvJN!h;)1+`xeQjmX{^uxb@br|L94Uy)7GaN>hf{N(;A`H@~RX$Qu<71deIgdTthzv)_VkNIB~p`7Un%q=e@4 zvau%AW?s!xwWB#C_Pzf-n3d&>tt&ue2{WJ}alW8-)B!eVcFEhshae(;wdO3w>#WK6 ziMMo?Lu z`R1Tq4CKg9wR+|1r{0p8m4fTa1m%sIX^I)B&R8A^FTR^pN|cb3>Nx`(t8|Fud{H1< zI3#Y-|HO%s7dA=auK!FRf!bC>n;QT$!cjrWF$~IEgvV_meL&KRTnO$UnLC)sk0MHr zG$4H72^>ASgi>1_$Bsl$rb7*vt8FNYAGYZ1%za*3 z;$ou2moRZ3C9c?nPf;yma8Yw~x=5YOw&rnnr1oW~;-#i)1$Y!T`Km;5j&c@=&QQ$f zUe>Ha(j8ayWJVYs`)JC9V%?p-LNVbpx~v^H>p7Pxt9$35q`v^ zc*M*2)q8Td+1SlVVwg8No&!B+8%(fjVVT0E%tm7O0KPs9*FHFz0#uFK2EuW=t0=F1 z3rKk)%~k>;Ho!kVrFt7KG}(znPr(b@jid%3wKXUT(wBNo{R?k?#00L)H~H(3(T~Pn zB^twF>W-zoT*+J|gv^_pJ(`Q(J7Te!HkJGSE3UtJd+uxN$&}8iIOR^UwFlX;xO>)T zS7pM5LCVa{4S&uyQ@j2y$IVX81&-_`FHc-`a6Pz%>jIgGsEYRet~|M-J>XzS(sdqI z$EmVi5auPTW%EBe`YI@M7@FgW%=E!st>F%loGYsjoH6EWE>mqb3^iqz4u5RHdS=&K z2z6rL{HOk!UhZS~g4_g%-Y}mwL(O@nFLHCd_JUV_(f&u%CX{GOHWUeX-2=lBLM!3LltfReh;9Mu8+)Q?xh(&J-9N{o!*}&f2fkW>r zqo%yun_DbNhz^1q?q`zG+h%s(P0V^@MeMEwh`&Z&essFQ5q!&grI8JZ!qXHGd6`=py%k!C_C9;9q8Wz7D zpcML3P*MFK=%L|d2kD?9uL(zRm96!7?0MJrx&3laIOqlIj0I23K# zHVpEtcieNbSDp1$FMG;RSts*tk6^yJ$rPyn4)G{Q)kfEj_I>N9u0;?}`+;4!Cms|E z%Lnp&$r2<*4c#OWZo1E~Q+Gap-&+r|Wn0}|w1d|SgJiVg5Z)#LmmV|o^r(RF_QFQ|ldj&m zF?X#ob?O~^YG|L^b*7H~cPOFy=&7ZTh8y26Zp^KgACDF^*p$0e=;hh+*jpiSw72E4 zXccvQi)xtN4x4DbZEH0hWXO<7LGS3&P?&+9P}t`YuCaZaCRWw)Yv#A->P-pwaTK0#BEu z7kIw4^H33HFFdkkB($g=XferLP0>Ir@oX7BCV^A;N<7GjPej3#zd(%n;vf}`n((FC z2y|@tpw|J+Q#yygM|IIbZ2N~3cB#iT_W26uWY#Hap$$1jVbiH@Hii$;B)?krGcBHc zz#?7fVHQ|^V3h^yy^$QIZdH{V?Dsux7gZ}N)g-mZ)Vs6J@}ka#AzS#VvC`5nr&O!+a5do=E9JIo9bBcvH?9Q+(ttR)bXFk>i6C|TE*Vy@pCz?mCW3fN> z_QqBO*dA3y{rWJ&Mz@U_&fHvw*~@=+`8hQugwQXMO_q0f*-C<#_=1az?`# z$(X33IQzlu(HcrAjh~eaW`Da68ATf-C{Bqw^)v?J-7k)5OpGM$g~pcsM_fk>rO$;Y z4cs={PKZt*VsPSA(5$hGoyr#KY;IQP z)w;+YV-)XmA{uJBKn@E(_4wj@Bs@_6gh?5h^fL)w@gp_arn5SxA*K=K(Z08%-OrBp zxgG6#{XXGvQ=#6`hny$(M3<>frLBVKoPAdsN+4x;AS@G>FSIg|BJFKQ&AZJ@Mu0*;V198U4DhUIPqGzg~b0`{*2)31jZV zg7*TaFy^V4%J~)ifP}p1b`pvfil@|e13e*xX+>I&-*=EY%sQhy{K~AeB^66rF?z9M zmE|yAvZFO%N4wCz_rGU%3P_10PT#A1EDnD`B+hy%KjkEF>h3q`Ngi`nvqk)FxBv7l zV>0CR37finj}0YsUsERU#ZtJIxr2MnCvEA-vU(|%#eHA0iptsHS&$6ZB*d*)xWvUe zV4`%()8MyTPZX?JPPK3^jx#}m_t0Z+PqcP?q=M;B(CM(p!Xq0z2;@0idSJK3ZAVCE ze@j0o@mY) zKYsqp=U@N!kAMH`FQ0$>)8~Kw=8r%B_W57_{GY%6{qrCH_UB)I`?p{J_V0iC-#`EM z%g>*WAOG~f{?B*c{L?r8^7O6K^cFBxgc!fiQVe;rLKAlrcv?gYse69f)W$7jM3zos554e@I5Au z_!mSB!3-Y=Prx5x_sRCJUAQ^e6b;}@|mI09Nh*IC>{;2cEp2BB8%ct)m^$!BBE zYoI*!n9;>DFC6g0>Orm*pJEacc9CLF;q^yy9|E87L)j19&!-8YusUcpycoxuQZdt8 zOg9_lG)r7_fiCwCLwyXa1SoX9yNt*6VYVKu?l1X8W^^vJs@;t`B>KW$$~ppMb#{CE zSZ5`zu_-g5w=jUIAk)%9M!C`W=Fp~{jl(=Gb59N}Wg~VLLH`Q|<>SziebbiVsw@1j z(F8Vo(GN|L>}Wr=ANt=({G5i(cFt}V^P13K{X|ZNgCWM&s)8Z|I2T(~$8i>o8@48X zTPPOx#=>>hV(!j`*shy+cZYF+nMhLC#Xq)1O!acC-ucl{}fWpUsW9u*AW9m*gR=ApQz| zSy3gT_f<&V*@A(HpaK`nG?PJE3MBK-I@I_3gwY3~L7gR^CS8Q9ZTRnQFL^2ZIXm_q zFtsZowa=0(CP6Iab{XT3YAO!MsN-`tCY$Z)4i*V3-}HcdoP<8Z)b8=EMmB2z!@kW& zkT*R)Es3hY%zzE~C)H}$(VHHyn4Ch?$oCfUZR#Bf28vA6Fz>=d5K}r^A^J_v-vd~t zg!Z-iK7jdvQGyXbap&d05FhtZ#DfeG;_`kbx#2~fe~#lQSq-CdB#4>2>__GR!XTiJa5abaO$M6%hPvy3m$+^0BN+Ejzw zm=L)t(a;&DS0HwDSHlh1?;G_pOAgRHuZ$i1Y8V<+VD+g7@Rdp$Hp36Vt8v`$x;1&>i#@<*w*;E?g>k0pq@0%AC`d zh@H?^M=?R(oWs*%+XIcPpgDD(tpuI(FE7UYEN3yp-FnX~-hmuq;SIZpE zecwtQ#i(*AORLMMvJzLxbgW`VT(=6@QtmI=cn_K=p`XY`?(G-RZ*`inywJ_EO9OQ(P zsV|lUfXS`C+=YrK$m&1}0bzb}dN%}8x*20%v}{}ox#=@h@xu+>E9C1$77Zs(D08N-fHx+4fN2uk9WeAmMI<&1B(crdw;S^z2UfDuv4 z$y~o(OGJ!R@}fqDT%66p13yMJeKNZq47wooLPCH?UF2*3;_|NYs!1h&5IK!!K929X zSO!I8??-M118zdHWjh{64wLPyIK~2j4~QHHZZd7dJ+P%{35%mNy0l=8ae12TtLZ5J zUNc-n6qC0QDImSO1LodWM&~c=Bf%O1=o&LFwxrC>9#l&$A1DGR`ro0!%d&=%#!Fq9 z40D&k0U{?fj|Xf`;0!yJyB7`~jG@2i3+UPB7tpgCB`dyK5S%(pt&1ihpH;H(h#R;a zm~wbw0mBQ@1*vOn8({K(joF)d_C@GweVxTe=T)ZQ2~e8+6z)Sk3u^FCBliR5GV|h| z7J3LpS4S(4mR-48@?}qzdT7CJ%2y|P*;32-_%KU2u(SfC1~-eoyv^W6wX`R_hF^=^ z3^ur2iK7s*HU%&ncoaVSxjRbx#rF`tIHOKC4|HFVJYWcKpvNy(gPS_GY08oMpl$`! znMxq?Z+c#6n5*E0-8`t+-u}vfE%XjNId^El;4!2&9d%*iiNu3^z^??nM1S4)2XK|+ zFjqae1>1||c)<-NZY`lJ&;ouz45vsl&Rbs~BVM{Dud6%gZJ}oqVOZuu@K!80wX~r-yjn1f(fKJ*lH1Ju z6!w7BCSOe&hQiaHMijlZxCRL?mRlh`H5M~ROHXERrG!(4KocR(En+q!Wl3sowo{ar zfFCUpcLDaaJN@Z2;h5Zo_jHMa3-eG_;J8n6i%SCf3eH#0H^Xg$qo2BFE%0ul zJ`0mQ##)Aui4ne24qXEE9q{lp)q{TAxxm-U3M~k+qm>QX(LT4MK22N*?0-*{7n(2H zH*&MCpR7Mgl0d2H1ou(EFy^!VhT2SsC$Ga|0~J*Kp&#+j*`|V|Y2g(9D-C*kyVHJb zKxH(@0}KR`JYl(=NQ#Zb&5~lxngHZS$Wi`5{M#-xN%Fs^B!#$XhXp{KE0X-MH58rf zS-wok8@whU*371GfcR7Cu+}zxnP5#S0f7-az>t#5n&#p!66cg|=+J-Q6HfY}6&cTR zi@6`Pfotxhv5*2h(okzh4mlcG?+nes#hxsmiSDtcTAk}hTx(}piaCds0nB} zn?I-G#NNjEco`^OTBpR89V7@8fIM9-nE;io9faU3DXQ|Olt*d-;_nSgL>6~ziyE@=XvB~@Rrbz13LvW z-%yF1s^VUe%@x=Z)BsJc$X|oVk-admAY}*>ko$Nybmlu1R8?lIg39Iq_NDc0a>p5X zlpG+)qt#?KRg~Dx!Pg;o;0m{wgb;bxcOkSsNW5#3?Pl9!WPJtDSR1HEOoN z0nWv<_ql08#y#+mQa?3+3*56gA%jJic%Li8>bA^6JF$R4wt~oc;d(#qz~<@9s1l|L zmrM?Tb+~PJ@FmI8Zp>7&?hRoYFj)!hlXA2ma`>qOghFlZJ~NWJgNcEPpV|PZ){ZRqO*4WoOSTj1l*Wc`-TB z70YV5Q75EexEiG#oeD|MM)vzG`=P|FNbIy)_etXI-50~s`^q|c=-V(k+sG2@L?7;5 zoLvt5K>ka?@2X312_3Zes*6QcnxT}qz2w?32|!DJW16kjhb}@|95n)HdTM$>#a*`Y zJa=6uCgM$K@Gn1)cROD_-q$Wj(jZuvRIa%g_9(wj8;z1mABmxwdAGhfsBVn-^$Od^QcK9xI$Ks}2hjl_s$xRyCF@fNWI} z+AExR#BiobSEKG+rveR$t_=qu^({*@C^%}HCY|2sPsdBKfGL$eoa4J~_TED;Xll@o_=3#W3r|B1a7%asMoy znpO{JSa1&WJR+93mp-ErX#vvIt+q3WHHa(XvQP-EK`7ed^dw`RlMNPb zz$bz#Bb35Y=xGK_biYp?G3(CU+Oy-Q@A;xf{6nPw!a4)}AD(o-$~H^@SX-l`)kNB{ ztf91P?R)%5nFO?ICd=@Y;;mC1Hkk)QihM07C$VB zZmx`QT0NqiS+0s%KIC?{D-O?+F}KLs2}o0=a4VsQDPyRWhA3f8ZLNyQyxckI)xKaZ zL)4bF1WObmUivQ2OWdhIR>x0BRzdiftAY>>m5ux)W?oMgL^j8108^;r>MPqjaY{H{ zs^DbjH=y)UFMldb-pw$rIjjN0HA%!1FO28HPfZbl8+;9HuTB8Rnz%eej)$)$tk$Mn z+rBcZm&ZWhbaf1=YVfOIc@nDnxPnmBzDbw81zU9~TBXu*AjC^ptO(c;D19XiY`9Ks zCX-w>U=6(Ht)2%m4T9dnW=+?2tX?5ZwQG>LP}f;KsXa>d_5=Hxpp1Yxp_qym&Vx3kZg-Tlkt@|pD?q<`Ul~E?lg8nNObC4M ze@hT0&O*)NWPt|=sIy~O)_F%z>}}rrn257ELlVRY?iQm+beF<)TP;G=ql%!N#WI=> zQe`efZ8KHTda^SkR#@+YR6MK#dWv>RH5xL}^h9>8U3FLHG^m~Ls9pa`QfF#y;cKG^ z1+$#M0#mpp7RH;Nl+lywR{bS1hC?j~9O6;sKt!j$)-MgbbY)ob=#x%%% zj)o2Tf?Pcisizr=UuI$d6@AHTcd7+TfE>d?+QdIOmx2`eV+vrF4#Ww}!C(&=Lygqr ztg(T&A}%*f0MGO4M{mIT`s6xs2GebF^1*En;3@>#V0j56cf43ePMUMc4a1)?6xDFRm!TI_+>*b-l+q zq1wmnRTWq_;>6Po_mCon)r<{6Sc7HTS1BEcWR}G6;WkNF&VckFeHHsOqBiu~W*=;b zoq8)VxExT6hEfS3>9ucgSFBV&%n)LVAGD06AZ^f+91fY-bn8aAoRcD2Odxzcq82C5 zOr{|7AIJ;-Uf(fUd&M2ldL(EvrAQR?k`U6Q=fgzSUA`T-OX7Ch&=Kl`&Xb5hhX`)ET%6p=3KNt~7{VlrLbTe#01{D<-g1Fr&`a zJ38gNg!xC7LH*532#o<^n)QP@Rc16WLiLR628oqp*XOVqBFY_xwgp|m>4s3gsO4r@ z;;!A-hB=YigDPx#jYY^%dn9+tykf#iu5%pZt1eVj!WuCZklq~z zH#a6hF&yj&HHCZY%e?VSzXmOvQ5xxgYoJ>?)31!W&}EnmsT{-96RE+Fp#N8esy>ia zhs9yAtig@u#OAA*gG$C5=a9OEM1|xl`@(0vwhhVk&AgoL)!_#fXdx%pO2gRD93fUc zKB0)&zCy3@q7(_;aq2ao_}VOaIO;Szf;XFQxA4P`de8X1|BbP-%FNa=j+t2|4%pbK z$H1`zAn&G$rTEs(UYje>_Mm75>Rcp?_cr7X&W)@aJ;cZ%lR%U^PmI`7F zK8yxJSzSF^pa(sf%&C777z{NyX3UTx<6E4+LlqCKc^zur16;!#eFWex*du$kRKEi3^3_G6 zIj{e7@k6}^vl>X2wbvtvLGnx^hThMIyq2y`jk4#WVh~Q^kn3hbOB*;9!Zxy`NC-Jj zd1^8D9f^wddN7$473>;^WI(<(dlFI(XU#QrDd zUnM(T(|u>omM--r2~tH`=K4aIPKc_LcwTsLBdQnd;XxJCH7~UTZIrM^RE;?X^R%4Q zJIZ#-WcWeDshXAR45Ojz(2FvP{Of&e-cY*CsctdSns2ct+!<6O!W3zwc9L@7toAHy`T!An#rO3O=6Kk(HS&(=(X)A6m zkxJR%d*3tX0B7@RU)>93@j`zD)Y#6LZtf!7hMh)+T2MPG|%LxU5Jf z@%gdTlk)G6@&mhkq)2lq?oB;1(f0r$o~&vSq$ z9w01MjD1hvO$USAD{~v1ag{*P&QxV~I4Ek6m<1*p(F_+Cw@qU6h}=<7ZP zxJ)WVLhm7>`VP5|2sDCsyg7wi3bZyaa&TmdBPYrQl{c1xX7>_D?g?h@TuxHMxK!R) zBUa{Z1cn-g18Jlewm&Du8&9|#Kp_-B=p0UnA^%=+a9V7RX2xVb#EJOUFu~=5^dUsT z3shx#5TUwQ%fZu{uI3YxjlzvvEFGc9UAV@jNVHJCHOq052g-ObCT+1L`oU#STq_T=U^ap9K(Qdx ziMqAtcgAr`E?@;lNxYS0!5)rQ$ZJV(DtsZ$fV|JT&EtF1_{urXAOQ)hiW$JbuU0I- zbb^`;Ly?eAsc)0K+)Jqp9w{o4bnrk@jkAmv6(!kaL!IOOjNPIDo{nm6wjg*e1sfjW ziHXYRs2r;BS{S~CeqyHL>RSaay3+}u1At^o`k|oz79R-Qhv=``)z+-}MUXap={oz| zdBka}7U@^sY=$x_sANmPR8QRALudSac(cOex(T~E0qYg^H8ij*i_uA>Aw*XXY?MbQ z(TXD@F$@2(8CG2@X;eYGp0yq`4jSfK~8SHo~P@x?+_EPV;o&2{}|79(Cd# z171#9c}F2~tT;%#m;rnvH8~$F^?`?6mzwTKtOs(8A9zEOsPH}c#%8cU(7{mK#R`cu z`uJUKvk^F4Z8CXG=8+=K-1P=oTU{u&xqb6W7#}6gs3^LHkjtQ~@?9dIE`+^wWw?$a z|5OQZvEd8#2b)@iOl_6M_Tp3J?bnpAfKy=hSzyEOx>2JFc<-Y!=YUbexk+no7Wj?{Xt* z{M2LWZf^nZ?|E_7)fe7;E*>2hV`3`@TNrj}cBy82NyC`}TvN+93ir2n+w7gBpA2Iu zg#gU@a>fhkw~&2<>rVm29AD+VB%a-F+Bf2Rr7_Ln$H71KIcV2z~HlK zFSn~Ld0+!(UQ6TV=Ax&oJ$=koRSS>7U23SqFuyPZO!kEsFyCm|Cx@6PSNy5y`waWS z7$yFoux5`Mv1IU(@<(?M-9^RAJQWHd8vp@mk;xE#Dke=&7ChowBQqF%6>D0L%C!MU)dJyP1OnRc|fMR~o$o5 zB-tT^OJ|tQwNF)wVAX*L&#QF7oC~A`D5($^fiedvHp+X}+ivL1N0>i`~M7grCrXg2pRI`lG?06^39Ppf% z0Bz{nqzX2z$+=xCdYPaQRjUHc*(l%k(Fqop~bHuOB;L&)UiHQEm=S?fJ;aBw?s$iqipeb!F^+e=6YkAag z=qK7o4=oAA;!wCFFVsMCvcyOxnajSx`-xOKppt3+4eIp<=Q&JXLjma+*7kd7pf+hZ zB4ne!L^G4dP4#2?5vOG=kt|rQ1Cc8wa>;z<+SZ-mRKJ>%6%iB7P2qC-&CF9SjHOY7H za;mv)7M&pt%5YW)mL-3oe&R;Zn%g;Ou$Q?rTrQANN?d3r10=a^o)sKCl93I#20IOd zg`4#;=-Yi6vQP-z?#WlAH8RLyT^?xpp8v&MH%Lg?0O?MUl>{=@Z&o; zLo{3BNsklMj?nCVlyKUKjs-t4+P(WJijO$k9sJZ_dm()TYC>k3#BMo;=s~vRYT-7C zSTXMq0hmH<@N<{LENEH7pz6mUjxxN$Yu(^k(?5DTS@dBpy&Y|U>}W|MJF1@+SNZ$j z5P3@swT^MXWnf^MWLr`DL%qBTubx4FW3p%_q4>y1dxq(YQJqS@<`r_-i;Y`00<2Ga{mBZtDUs>VvFUkGz@O z>wfD@=hK)zT1n#0#%H=-{ zsf@Q7+Yk%jK*=)PYEB`)Nb>IkhlP=Nz}{9=9CXf^crjnK&>O=C11TnF60;1l^X;r} zz-(EKGgUl)#0|^<0$;cRCk3c^q02zhGStLa#?Y;i*v(OMg@WJ$j7s4K0F~mDK~p?O zUHP3MGaGK|zU7!DyaAf}kqnu-&tw}d={+O&Hl7!t8OMI6Xnc#E&vW&{0Sz%BE=p3_ z2#jxI$awG96BWtcOLO7r+o+vF#qw2DZbPgNEXh~ziRvH)hE%8fst+wsY)5m}coeH^ zU)WKf=1M}@%3cML2$&h+Q-|xw4r6%B+en2{A4!w5xnTIQxUG!%l#Silj&wP z`sIUYCxblDL+9%=cgRI`Gk z+#BJ+BiZ0{bADF1%TfT$N!8il3&UT~bnIf=8HwKy*aBZ=as3T4fyadWSKB=qM7Bxf zw^}`5R!?adQX0ruD4rkQoiCp*$BFp0@x1}6K2qW#aBFg`C6yQMLYQi(l5ibR1+&A?l!U)2ro}8MMRP& zgWnZIf7ymM+vJC$5_+;=_&9LP`9`2IVjuEmT=6Aq4L0{0x8mLICOaq7R9ge*%dOMN zLD%9h;VW<-ii5TRW&s-tn4kg_zI&8*;RZd3lsN%evJgYXcl7h@C#fnwe17E|^O3gs+Hn8<62jLXrzj(C>S`0yR3BMN2@ zDG2*+jH>WORT{4ODTQE^4&U0O?Z60#=IA&z-&`94sILGk7nhLdw_%KBs!(f)+ZDHF z3q}Sa$id1c!PpI_T0D5y`{?@;XHcZt)j*;vtc{8>q8L)y%LPzdD2jAL9yyy06!T;h zAJD^tuRs=SRy&)GdtRAYdiN8IAHXwvKzb9u=OYbBh@ZynvvV30?)EB|1-GHb3{T+` zn8XjiXqbXIk80lO#7G!EzI7w3@{s_qRF?q4?~>Uise4LRmCri4xL=Ll$a{!DO`_|v zHRt&+Jh%asd)x=aOkE2+>fUsuqzo;?aDFsb#*Q`(X-8|pzSqBTlh&e^^fT8G4s$h8 z#&4GdivyjiTG47p?J#WM_GTiQ4XVDT&FUw7x>*elRIX|-l#sSJpQvQL$=D!t16Obl1U;$$XG!R+8kQsSF+3@s@#7ZkjoYt@g%Tpd* zBnFwMRhpm5?<_ocvgsZJ0$Uz35F@8bJz2O{Z3;D}>|h}8VGQIYerOaUM;0j44!vn( zIplm_d_gE(Ew~JWg?#6_Hjd~Fstc;77{v&E1EeiNLf0{2!^=a<&ZuTs^!`{2P$ zXz*|uJ)09wb*h{&l8<;p)A&XMwyZFr>zTcKGb;mu1I7qOfcjKJBZHVZhzd{OqIG9+ z7bL|ck1zX~I_PMy@+bHx)LxbUHmy z5zSHNH`aUHXtfY$+mnQ|B(Wd zx{sGhI2VcDOjB6odWD3~yqb|ZEsXx=6l~aDncmgef{=?K^o>d`CpVBr>2aJWMy?kg zMCI?8s9n2Yagt0T2$2rT0)0nY9mSvzfCN#zq==v*t4z8$3v#vgQ<92S5lSyODMh)e zNhLQ%YQnc|QQ^=H@Hr5qNV!vSNt<3C%vc|JrYFtxH%gB#s@~XDDa~<99do&gacJo~ zuSv_ofV;IqU+kp|fUsw-B*)*1xO#58#pv^Ft!lb!uJT0(0HXI`-tjDE81LkDU0f1_ zeCobYULpfkKX+oA_W361`=sQXFP-2XL@!D|oDTz%v^|WndLxmGNbGS-1kC+UU6ymb zItj&l4>ln^`Fl`VtXz6|w-WLg9DG~yZH7(eDQslAmdw40MeYyQ);^tPVg2t#lNAxl zskJKt8A7KkB)#5kdCq?eycc+%^E+JXI5}^Th49mcAZEgxRxl85%m;Nrz}7uSNqbvw8SC9gIPge1@CDK2!(&$|Fs@o_{bidfZm2QTB~mWWlC&cIgY zHGHyh3pQJjf&~MQ#VC?5ya-q=hb@sRv55hBcZ^eeSiJtVU@arFQyVwy*_zFpf#y6q zX<5o3PpP4N05*LW0O8T-g!A-#?<;3E5TB?qL}1{)!G}h|a+R`J^sP56 z-B?1d!lfDO5dX3P)4vcjxZhA z(uQWPDl>xQrmUTwXLp;No1c@LsGE~GF6bAh^s*-(&vY99pIMs zR1g!hW{fvatWh;8KgPk!5L!@;Of$}_feM6J4Apu;$t;5yK&@)C5+4jUy=@OI243?0 z43fcgvT+Z0Lk!AMFvw*=n#8FClgbe{R@3oG)#qM8Iz`9Uck{Rp zvrINb&xn`qO-43zp6C|yzTnt!C3^@QY8h(fJoXn8r9u%YHMc*C-3?k+#eG|GyS+D@ zORnh9)DSK5b*g4b)OWMRisTc~k=11>yAMjyw(P<@%$+78B6UFTKNrYe!kwUr7N?VKnweM$-cBQV@lH96(l&;Nc zoku%>S^AsSYb7ml;>23Th*7dIZrIjkF5J*mC7zKNCQa!leD+zn4AP<%yh;gqdm2!( z$<&uP%P05)^70@PI764!6RQ^v-AY8&eydwdbXzb~8=CnjE>6$oHt=cx;R=M2rz&!D zp3f*QldUB7NyD}1;;3-XB+yQ!sxd%4w14fj%|2wLH4(tlYoc*^Pb63h#`poqQ8?=sMM1 zGIf4FEw5#gt5BQ7mr7-bb};?rbs^fC^sy{pShS2MbYo|C7-cI-m|g$-K`p}0WQdAC=~NGG|v z^x~p|nDQ3vU3N3lQ1QbcS!d#r#h_dAQQ%Y?YwO8vF3sCP%0ufW*PL)QJQ;4940{H) znVDKsu$s4;iJ1zBO1B?h_XR0ldn-w&(Pk%{0*Ie}mlt!(8^Mc7Q(qire z>^KBalhfFNu76ZFqE!2jXX-W~N$ue^-}vUIn0_Y;fYVJ?WPF_g5a#2 zxrY^OTYa$_04#xH=b}6(mi1ARLH0kX(iAAhLSQkjH|! z#KBp{OO@$@34fu0OLn!yi!o!5=gV(Ak-EocvBt_$<&sf!7Qd_N34+O1=pOy*U# zVTKasVc7HCyfXbo9)N^%^fB^J&l7v!gOgf;1KIx~EAN&)I;jpRT)LZuXa=mxtPsE+ zxZYrP)phpJ<#xv>HMPc}(etplNA|I>KbW#y$WabR5np8;YB9=8kVr-uKhi+itk=0n z@FihNEO^N28Yl7k$`Yrf97$W$|Jtmd9qoR0G(XUecCCHyf1}pMN}Est9sjWNdN_3i z=|N#AZ8Yafk~Ex)E|?0%^6Qr6UCx@;N*p>mxH?04p{@z-$wOW9mbIT~Ln;8#ph*Kf zWRzQmjq&T;CeSpml(^fIsC3RjTsrDefpT5QGXwJ7bzfxm5us4CKXsFJSk@aEH_kg6 zrUVMZZUg5+SqSKyuM*J`>v6W11t< zi~c@(L$OS=MZfQm0b&=0z=WzH2l7>-F=}LsA7)9W8@m005(sRH*{Ez!iYp)l=mbS! zUdwWiJp!;c@Hu#=8ni}h3%xOF2rtaVJIqi}N*xhn`Ve8PL4^@2WW{pbH*>=+JV_6( zCnC!_5~XWWLe3QjC5p%I#C?K8uLBVnG55Rc?1XcIH8&OOa+PM7k1p1FDPsmq(a;h#dw@PB<)v4$1 zjU~%ArZm-N-tlPRp^|3t2_JdZtP2tH>Ab_`V+sND+ofbOo3yt_8_j}$@kZ>kOaSkn zJErJNPbtmd%&Uf(&|Eh(sWxez@3y{Hrkt#%D@0{~3}QD6^HRB`5yCzUZ{BSWvdkCE z!Xi%wb8$D+TV-Z|RmUKe##=?^zNiKcj8a)qo{d42**;XlM}?mD`#Te`IX!e*8)+!2 zM5xl?kzW8u^!6v+Lop9b$!M)xtW4DyE4WV-vYvgxR)??}=eEx1V793m^n&b4R+B!> z#StPP5Oy5ERQJ7OJzS}yiJ?=;8I3hfTzZq2{Eq#7;hTc#ukZg&y~-!&w+r>z4M=~Rsf^o5l_QaJ)9 zstc1ud+4%>*Cts-`(x04PSAz&RV++hO~T{wy{hF#=R8~dLhOq5=Ewk1>uiW`wW#Z& zN(d=sW1m;$FcDXKRUq~PhrAwGd!zmVu)8+5WD!#lj9L4$M{CAV_P-l}m`y85K-Sba zTtq)X>OR|cxHiL-xzw}B&R0V)ARqV!&vEfx#Xz>R9^hW=8 zn}Aq@dThysh*0Z`^wJdpp`dKY$H2|agd%Z6VH+rL7^>O8Iu($#RXne2__^rHB7o-v zB$_+gCMI(5ia(HHy||b#gV>Wah;jWwd4yuZTanT`g)Hv0XQnLL&bGBsYfuB)8S8~% zG+W5z7MC<)F)gCe8_mUBwOmuqQ(@{^LSvS~FK0O@Uo9&!7`Me6(Sbgq>>sVx1SSS+ zaJrehRH57Frsj4mX~DC9_5*9ij6?4jae}xec`@2bwXDl|tWb}8(hMnltXs$kU-EP}*&tbhgasp?;8SzffI}-b;Y~KkM9GeLN0LgF37^YYPzL`S z0QBT7w?Wrs&XT*Sbg0mU3WhIaiC9ve%ABn+y-7@>C1Ni6ci&>wA_`=%{kV?7hJ?sm zRXMUYqP?Suh8^vCJKA;jeWa;b5ut+RhLltwpDY+9ab^o)vsN2iGHqhQvn0G3NORWh zNkt?GZ1I>Q9!6FGqmCtKZTCHr(?Xl)O-9)Z#Q?|mQVwwVyG0Z6lf(sO6Hh7j?}}A4 zv9hCyl^yMK9s^gY1lkW=9>1XYS;T7cchOhQhxR`4v&S6C-?^+nSEIOuaLFDW0xImH{CH z;$MD#k_0I`EPFgj3q_2O^*Fz(hEWmedDDYO@(!Z~#!=jj8$1z61nQfx2(zD0kw{7! zbkRsm%p;#>ISbOF zltp7ZLW)fWQ zW}SUXS`x|u&|nFd2S05oJtx1@Mj`BTZ&sorrjk!WxnMz)2MkX)+tPfj$)w8q144BP zS@4ojt-`5oSq%wsfrF?2pgLOUyl=^gE^COr1{C_R;@ zs1kPqas8r0lEM*;NB8K zDoQeHZJc2zCv+|s>6h(giFzea;A7h#>AVm8ntCym=rB6ia7u%E`{q8Ip4ixCM^VJk zOjpgAu-e^VoCuAHfUu7i}3jodj)JrU2 zTWJPBqm(Z*WYqX!Y%_Zw3w7p2148Hm<^Frm!4Hs8eC47wE3%;LCQpIs=#Id@P`28l zJtI0Z6Lz!_u%j&vwqtmJD?W$G9%)K)-H<&$U%(^=%V;3*meyLh?C{fBXF)lLPZUjh zsJlZObe_!=@HZ-*YhCF^Do}tBlF!TOKujkovn8y9N1O%{S)U-8Eb9E0uhc~@3-o6n zNBqR!q`QcJ`8n=ZCkx<3juw$k_B$iYHO+255+B~5762C67VC8$w9C7$s7z}+Zqs=z z>d$JaOk9D&sNm@)UI&~jR=>3;8|##1$fM=O;((v4i(cPKAtqlhtK0^OQ{S3i&ppZ1 z#LF}OMB^sxoumEI7}fvA*o=I|s3jyd)eZ}VG-#XFNToKXNI$_XlzSIj((CGG=Cl97 zbra$*xYPsEtxl$>^+22U;-Vt3sW86Cp~%Zdu^RaI|NRdiAHRM6^XI?*<(t3!`pa*h zzxnO+zy9G5|MBx*KL7f+fBgGjfBF35pFaQdH-G&3x6l9b=l}fm@1Otpw?F^#+rRz# zw}1cB|Ni;6Uw;04{P?H;^?!c+=AXX#uMZO=7#fuj#)^!TWp91v6DZH$bK3_Aobe1E zN8=;zSZ0yrClucBSNw=c2*#Z_XWS|AC%|V`m>=ma&@4*u>5B0th+)w(M1p6sQi1A& zo&DJk4ooYDTk#Tn1dfO6242Drd^m?jazO{|fc#0m!oATTelE`pXAjSS*b=2-pqHRR zV0Z0#L2#nTd|))_mN#LgREj5uRt@fHYdyNBTu?frP#i3kLPya1_m;Ka(G!eC>EQ;*s!*bRdt+= zz8y_E>}bu{_x|^SC&caw`Nr}x`XRGHo{wt;eqKs;gk;hn-76)P6z9f@^Fa_^^@$Oc z1u}R{Omv)03d*BWuWn)+H&vSv>yWk*;R86_?U&mJx3mPOUb zB_-G#-Pv<;0*pp2xQFl)mukRG0%ngc8IY9BqD`Bf`+!SZO3y*jBrIkwmPzVM7@wIG zLDY@R34Xm0Js>rSj*9)UVZ4o4vN8UIjUrS4>}Ynyj&^@Lrs87fJdUKLSYoEu2O5Td z`T64Ldhi^YSzfQ^V?gQh3;`rB2+gdp)t1K}jg5(~qD=;=o^4nkFy#{rUy%g!8Fj{y zoEoJEhI4u`GEK^m6(J`>Uq+@H1x_Ww+^jF=ObQQ+oz8Q;*B#Vb+WUZ$Yx#-j0v_Sb zE-Z}aB*Fd5JX6In6~w?kse^f-kgFX;GuzW+&V&ZpbQ#e$uj-(xGPMRH47n*a1=NZg zQ?83><7pktq6Ac(;xXhs+ISi-Cj?1{v<~g}J^50~iV21tjhyXhP%#>q!4L-x7Kicj)NTe{`HmG_u z*|4Jxy&X+P>}Xfp_x5+TL08MFv!BYo6^mSuksxr3B2pU~N~%#3p0vXv+cJV4YLi3( z>QK^Cr{T7tfoQxC7QB%3q_2mxt`5R&zMhR~dQ61KhTe`Q8g{hH?0f%vnni3R+}_JJ z%=mN?x5PdJ>^vj2KDw@_K`N}tMoVXlMQSn$0-?`nEmK4mmTvn4s)C(1IUi@_NiHVZ zIVD1Ff+c-I@DedIv57|$C_9?K+0lM!-}~Puf)|5%=Kz;HJ^3mGPn<0gl!oNV}(H!{-ac z5h#dxwUvXj!UlF-%DkEI*wKW@j-}A0zsuw}6|C0z-NMH9T*+-D3OSiKHSt1gGk!8_ z_G^*Lp3xd^m{&Y!pFEp1cNfcLzW;%RW-OyQ5KEU#hew2t%46OaBAPJSu@pA@`^FH_ zB;}&cN;9mvbyiuI7a!Pb&D3J&&!nA{$CPB)h9hyOeV{r-vx>PWPFnsbIpYr`CsYdV zFN>Si!gD2zuG)QVvLylez9v8N5`s|6iX6Qp1~0oWrmgLx>8wu@nBPfuVI2tFRjLV5|fh1N^ z`Fm7QP9Cjc2(TYjl;;bA5?LGTf>1Dn#9%2SyZ%AuM4Il~(PY4mCL4CN&+TZ}+4uJM zLVoa08Hv~@t&T+mu~0otka-GYGTD)l_?Sx~l2l9NnHt?r@?c#yo*WfyIt&jkSAh_j zS+k=JeJKj532B`ZjNIwLCytPx4+*0k&=$>-ef36iGHfu7Y@LA;Uy4aFRG>07Hi`IP zwXz{+N@Yhw(vF6-9ql^%-v3S#U`<851M(W7y44lL8$*~2IhKfVLJh^#0=Z3SKP2RV zB@JpjI>bT6{O<(vyM=trP`3&>hcNe1zSVjGwLiU>0q)A>G6LQjqU~4!)msMFVI6%QN54+PNkh7daXRps1D4Nt&D9v5*87lJfC1b$EQZ zD<3W|%SJOy3{z{jd^j7+#AiCi`9smr&fgQ6I z{g3<8ez*hgts(kSV8e}?Ah@V-h>T#3Niy}e)+jj_8pCr&Xo00A@ISCt=*z4#o{%(0 z)=ZQ^dl3war7jP8%4V<~tU1712`Q}nN?}bR@mgZP8dzBgAH~(O(s0$k7HOS|Y`zX@ zEk-=TdSE9Pr1_;h3)1RtyT(l#CFA_j0BK{vgSsTDx=#jOY1=!GnKACccUr;8=POQO z)_j|bTVf>lTFpI)jNzq*?v_LVNcMi;ZkNnhd;e8ZH~vBz3|8 zn;sE$4vJCbpPGnS6&73;+_-aD*_ejAO<3~O;tL(-&L#(6*@c6%_{S~G5{0C9W`to! z6(%r}NtIn_Kk&aB%+idod&06VD^ize5lY%D&KK!mL0FcFrFzVLW??7Q*}Yx{YE-#B< z%x|qX^+pj4i39?Aja8$&RxPKrN$hR;>q?qRlF8NVu`PeNNlWO`&xq39)+6DTln(oq z4(D`m?yoJI;PlaEWu>z}Oi5NSlKRG2nGm@wD>k^TBZhcZM(lUijB%O&J!>u%xm(mM z9fa)sHrJjNPm7M<(_9sK<%ONk@M2-?wI!~7G;6o#XHsxyrM+05oP^qUQ^uVvls;yb zpLwxb0E8{+nO?EL4XLmJ)H^w*o7s5=>ea{0JX<% z`dnyI%P=40;Th?WB+cq4eD({;#-u%%V&0)s#@!6^JY5k)dAhY>nL+N_V3!EVT;djz zwX{*LrA{7Ee6l-VduY zaee#pLG+fz!-v<}?a4v!bGy!1%m41UzCH~?%~nI448pDQLD}0E50S3q(H@hJX?_do zF!<7DvM_p_jikl8X)_TUzSglN0wO=HFVY5PWy6fuhG-1<+xep8qJ7yYT3QngnOSZG zM2W2JssVw^*n{hpB+smsVxR{dm)g1{>@lcZw~SZi1Vp*_rU`$^%-|)o z`d&N%8;|heZ}`C*!Qn0tB zbz8P%%^s{og08CAv^DHLKgn=Z;&qFlipu(zo;3~T(8;}p@}zpN!It#~DW&%KYfZVq z7Sbv#^r8Mflrs%Zi#v{TVj0RMgO=68tBDWJXx?~+#Tbf{Cbu6mPfhDmWMoa5z4Fxt zkd@u#>K+nUC@?x&9ML=4wZ=34H;Bwa3(rW{2RuegXu$xO$sJdmEDi*(&@jbLaEBm1 z^U7-5W08sX+X)V>`i^Qgtg&mIbkI1F8M=E7wJexDu#*j$Ulz9bE2I6UbEe~DV!TCa zrw&_w(Z08$6sD3jmpV7PW3{>Mpojb0TIoe>T4psa)rq}Sr}qPIZG<3F)z#bQ4+i$b zUBS%RyYc}^jAzfhdT&@d(2 zsYP|hwRjUBCS_zg<_BH~^lgG;6 zMsX@aLNnqjY=hL6hAj>2u}No=Vd0TYhF%O{K_Yyq;rAuyrrzDELxx98mySmn>BAFc zEYF1&SCpx(9!Tv;+zg}181BH69Z=tg^PRdgv z^flJ4E&By(9ViO(NdbnB7j&3Q?m;9BiiYvx4h*v*(qm$Bum@2Y6F-hk4h=kECAr4- z8R`zmejD$7kycaz;!Hy7XK^GIGs{=Sn&vwfB zg4X9!NH4k8wWzE7UOk}PAHYRSi@#S2aW@Qx?G$Tzi^u4vRWFonm_468s!Cwgwhh0I z>s={s*fpu#NL&55Q2T`;Pn*Jd_+(a*Qp2yxnYKswL@P}o`Z!w@X*LBBb(4mK*In7O zG#Jl;?NHEzz$IQ4$J&5-# zF-&p8VSstVzf$0C3R*>ipv;B;&EB?!tE_Fx@?}?|RCv(`W?0#ACXlDY4i}WUxek+* z`J@~yfP552;n>Fm-92MY6E)68#216(M!b0QZi2^o|o0o88Mk87))3%P=g5< zB@43_SWDf^5K-G8Ugn&pC*7NMR&Ynz>MPn`taow7~@p>5R%5SApr_K3~#)|5x_j^*ew9$0++!ho@R)V`X;g}a_W zPr;K}%*1@npqKpoJroZo(aPgF%SxcsM@$Qsb0+}(aOa>oFzs2FT+I4<7{yS)2+_)W z&;S^qZ1o=J6poK~i^frA1m^;ojB+B6MO8bqk)XskO|wFN5`}}yP(~YxxeEcWSi6-{ z)Le=p3RBPxe(qA2;q5Fn7ZogMgV{iCs4n#!-(mI@`d0@mL*+|CC9Q);0w-{@R&;2; z3YiKLu_H~HFeP`F(j>b%aAnPiMrAB!_#`zi?_?iN1ppiLs?ZLOc%W{v*IV*+lWsM_C_|2r z@&DnK#rJN5Y_4ZKau!Hf&!&THmdB3vxgG6N``-VCo?FRB>qtJ3B)k%XN1Kv`R(W5Z z9HE$ z*pAQ}TyLjA&-PGmOSU?hf@V(KCDhm;8UX%5t`20lx#1WSZp{+e8MiAK8G zrM}j^3bxjc`X@t z5Ut|}nO!%B_TWgok1)X(xgtn|C&3~KO+u+0(|aUHQ12`2bh2RZgNVE(#h!8}c(v)sRsh6x z@0ksMoMnLu^J!QD1N+k`$29xH!M&1TUmp8hjTEm$*zh>nQVzbQ@O>Ezsr|)DOL>nx z7tvYd%ke&o$f3cECVtX=RdI?rpwg(Aaj~O)Z%4bI9qn^F+I26@x!RwcY?{oTZgFT* z^cx~_(hj^lt^F~>g7i4W3B$R*!yqn9+r)YJWY(SpR?=qh-@Lu}u=3Q=O3gXyc^v)1 z{M8E~0&BP0v%Okb&X$v#W9!CBU%rxhu6fNdvYl>4>`XlSig-C%cf!K;8g zW$FlFM!+rbs%z0~cG1JEO(zsRr&PW0n%g#RiDa$oi27Wuz7Hul2o6INAk`VD-Z1R*w-N?^_tIn zyfVCbmnS@-lD~V8GyvnndYyRCa21z!sA&#B4nunkTOBsRsDg|_3pV;{T@5>PiZ2YW z%p)f}X$(JM%jPU3sB+6 zI9yN*Zmden3GK%{3;<=fcqTWPe85!Hdn0AU`3y{5ltwjJ1N&iOB5Q~tLAq{n0K*~*(OVBPQw@B( zN0gam1eGm%12aG&v7)S=!T)szzYz!~97-7xwbm1~KSzJjX`ks~uTHk$p3JS9eU61k zc0m!Rg-RoVAbbs)yBQaA_MvW2pawS7kJ6fqjmZW$R$G23Hs%OWKF+-Fwi|o{76Tc> zR~b={^=HH|CLP??BfP|5HQSbWW^#J!m@&EzR^E>0aoN#6x1$Y+eeZv-CEJ2E^)Y1% zZcX_q5nz&jwrxdO(3rBaH>sa2z3d(fd$rsXq4jdf{DQ6kpq?2rRS5|-O{Y~eV*~K1hb!W-WlCG1Oqy=6$jT!w z^v0lrbNfO~$AQcclS37Fr_scGxbp?TQ$Xe(aRWSCd>G!%gV9p;1E>d*l$?>>xkNGQ_Y7L&SAE5yE#xn<31-{h1(6-1p6WgJAzp-%;pRJU$TJJkI~sa+G)se! zI={qzV4qIs0@&r8eL?p6$-?LpJXMQi>i}yOw+IGgjal@%LfaNws_jnonEO69u~UnIJ{9ZCLI-4V-31G z_{vZKGhssw3VnQEG?E9L(2%`Kan5%$!0;T)8%2s~Rq_IL3kN!d>B)j9QfQ*Y-PxYR zLiaV88+Tf5gl4k29f+l>=w_G9h!rOQXX%}Y7vw0vv{uG5`Yj6D7KYX2P^!7{Y7|`t zaC>no>$$ zJB^dlm)9^I)y^!ZfJM8ecrmAvz5e0h8I#0ZokCFoc$vPr3PN^R5BsoODEoorfv2hg zFvZcqOotcUO*-#`y_O|EW0p#NrYX_XCz(`&_DpXuvLJW{k&eG?gEXlNIk|MEq|j`p zb<2>|sg}-I8)Ud0%oZcgyOoC!)#fD<^9p#El?Q1TmPf)7q|a|*ZRFJ?;u2Rz29;kI zQ@Z0Myh%q?kTw7#1zWh|?hcQzyqFI;4wy)&NzEhXAff++z{_vcf0>WE-4aJUmUEq| zXdoKkl8Vz!HfJTMGnHYf*YRSC2Fq(OgHSnoniGh5>1)E2EI?XD?{EJ6Ye@S3*h0}MPqY9Z0^aqgNI;Vb7E$>`>y zc#6V%fvHjLU_XsZ{Cq`;O;fz5y~N`a;*eOB1$l6X2UACU7n@Y@w_a7J$b|B!Ji$ME zJ1TJwQJWWX<(X^@G$NLG*ZC~-I`#a5Hkm*n@>iWdd+~4 ziAdDBwobM%g8RVy5XQZ7x@ReYd=mKh_=$VDV2vR{2t0nnok>j6ysMYcqJiT|vXXi<`Ifa*9Rz+4HU9L2@1tRS2wD- zC0^|i_bW>Ub23fY3B$OuIBl*3TAL30wdx->!D+`>kY+#5e`lXg*Q3$D&r7KSf2@fl zhd&dcyjk}FXUB@|(T~bo)lVp=)8zn-g0veXosXbdhSSDQ^fLelv`tfGH+!y&L*+dz zVy&vYdG19KCi!H6J56CB$((3fm_W$MtaB-v&5XpFI92@^*bBqWwlrW87Ix)nRSj;6 zDIAvS1!}P4bE+COH0agx9Nb$})gE)!1^Gg%Iz5Rls)~n9R23xf;u}wNqkLbw9O)rX zodi9Fst!xl64iuSu1o{BR8wHZolgw#_>rKHy%`u!3*>9oYu?I)?((iHbflDf`W*}t zTZkk>Ib$o3+4HpJWaTF-G#?D58o!`O=D5I< zS+3D+0vh9Wi4E6Jc$2f1JU9^XtJ-k7o<}V%-J?`7QEDQLuoV7#mR=iL^>9H z(+L5#~R0bbNi&s@HO7|!i)X(f&E7{Xcm!0u}9qWwLeS4y0! z-F#xQW+rKkoTARwnp=~5RCp%YDCQ?kvRMBnDeT(J-iX7ZNZz2fjgppGW0=yt)z!<2 z5j>!R6`>k>RK2i~_7BgOzTf--TzYp0485;BFw5iF2l>iefswD6BPHHaW6yXD&Xar< z;{*fA)^ok|9aY8ReP)0(FkuuTX%Bv4=L@o!Lll*y;_VWF25xHg@E|Y@e8Ox4uMo|> zXSb?C!djXo5E@{&f!Efw0jwi)IGsRS7;lrWZ(-U%Ke%!x@YNWV1|o@j=R(a~!1^wO z196&sT6V4yZ(su{dlecB|KNl^;az>iZTVViwV;b_d~fou8dlH5iJZ^bbW9FVp85f? z%Met~&lg4|7pGLQsL)0CGs-5i>Fet!s>+|bA8Q)3A*ruV-@`^SYb@^S$4};jJ+Qjv zHttT6EVs281?ILUxlW80DyS(JDsz{n7xyiNLuE+EJKrs;15TWp;7Gi_ci9%+2j%(l z{#w)#!1>kxIY_F*?&_o`3*uiZbKTc7+k>H9$*C7`)1cDX0M&oB*g%qFHqVYH_hn=lt zCF&2Y{Uy!C;=H*6!xK4Q5VW2|Oxm<47bB)QbHDDH2~Cr~n6P2>0n`UEUo5H{uh;xQ z2{s)8-QZPglQCPE?nv2pEO~zpT-1W`q^M%o(fJxZ*m7)h`LT{$^#n8)VfM5a&4;`G9d1w~r z_x!`59s%_3(=CO)SbsP*GyBGXc8g#uxHEuz#rkAS&Rft+1#b=Nz$6&*7%6=XQC7KG*Px??tWa? zKGNNs!CeLYK7mg`E(tvDe*-xE6<<#se(*RH$W}s}U}otmR{O>@k&uA-0xi!#u{594 z0%@Qn3;u_bbJ#V7c}FN;42T)D{H7f(xnoEB zy?yV0vkn~p|L9FvI496kvyh1&3Ww^+0$${V)Ojgdg@m)oQ3YRvgmDRdfsRi!$D?As z_;kEK{+qYAQ>qBZ;KtUhF~AFe?EA+Yw_+jn6?2L z>rTP5dlva@t{>zR#x|OiFXFPSl+VfCY(CHgH8Vj=iv&&S~Ge~_|}+n z_Gk_D7|-xG4!{=htdXcZp{r ztU~B1B?CLsFMctsHo1Sg18cZ3VHS2tsow5F_zbJy{5pI$pf)L8x&7@Ed%kS!agoz4 zpJ98TYGCe0GOM!Nf*}B1SPl>USC_O9mxi=NoYLPyrm~P~Y|P<>!V`gKFRr}#5~fa4 z6$uG_;gRU_`wjrS$%SyhY)pCda>zFrD|L=^d7MP%9Bce?K}vzjz6 ziw!TroC)IS8_&jYZnZlCbgFZe!BhM$h?T!m5S0m}T+8dlp{v5yPLFS_5I)5rV_zrV zF%08a7S&lVmTV#{0SwO#d@Enk%JoDxB^ZfeUWnd@e4?8paju>HwRou*_L!l-BXq_+ zOAVOyh>~8py=V!35&jznG4(H&dz}j%B%vn*=3wL{N4}6V_B(Mf@F5x$wwdIuF)Q$|b5va6CXN1?l<1-Es+OcQrx*kZga}uf;_zB#85K9T(rN zsGe+t1P}nWLsfYk{C=ork_t`r>_J)5go^D+4xe>EFU}}rK>q_c^|Uw_p3{Dp;Y*Y& z2NO3&Gv}l#OxS<0Hof3|Zwkjl^x}dMQVEr0jJkPSk|p|Qh60EJo;coXfU>J#O0ytt zNF=LcFO3x)jiDKYX=2nzg#rU{^!_rrNil+3&Pp@;JP!M%9>*1SV>NGyd(@E%C%4fV zS{ojvy+P4Zz1gCfXI!abyeq-iZnrlN#&=hHtVt0Y<^vRGTLE$SCw%DWSPjI>*lu|Y zqr?v`9m0V8d@<=WcuvQ`UPJ*Z2PCZ+^`;ONiX{lzHWkcJss))y3P-*oz^SI+tBn8Uaro za11g|^uLht*=I7{R1!`6{7E-8s%V?ap7GGqR6g>o*v{s_s=H=!bPCXJnNI9vL&ZKF4Xe@16PniGXPLTk;fzu+L$cGTx+%s8`^$Y$}g!@Se^G5`YA$ zAR}5#~!~8KZ?8a6JC(ewm zroStz@&?Wo1>w{L|-u{^pNA|MvM`{`{Z6{{8bG|Mur!e*3py|Mu^H`oDku*Pp-nr*Hnt>l6WW z$gHCxms4(71)w#y6dfd6ptr1?fiLdmd-#auNK%Q5)jy~XkYDj5{K=1{T8Te{?7=*u zVuH)A`Dd8bL$L`~Z#;Z=i6JXVT{Hb8WD-FQLft@PO0(1J;ZgA7(<|{izl`Q6{C#>| zEQLk`NgLYO*?oWHaGTwHVZdLXq#4PX8gRwHe(+2DRHR~N0jaf99&NzvXaiw- z0Qm%AS5DKo-GS9?wj{*^uhg0SVBQlo@-}86XV)V? zht;RDn-M6estStDi?s^Ee99P$&Rp%N;}M(MrlK?W$_x+py?=31nwx^+pH0kTRFz*P zIY3W`t_yuBdPUe_)!4*)?kCd-8@umc8TDe}rpd6v-2+J9^O1=Np`i2Gs&*wKtqAti|!L>mFwe`cvnIz!pX1rDjZ z7`9ZlM93YMs&S9Rl)6bcDTshtacVzVzH_Wqzp8}}Q2jxvyXbeMYQ`=4aQ)4swy3O literal 0 HcmV?d00001 diff --git a/src/gopt/test/embed_cache.py b/src/gopt/test/embed_cache.py new file mode 100644 index 000000000..db50a777a --- /dev/null +++ b/src/gopt/test/embed_cache.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# MegEngine is Licensed under the Apache License, Version 2.0 (the "License") +# +# Copyright (c) 2014-2021 Megvii Inc. All rights reserved. +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +# 为了保证全局图优化里的 profiling 结果不受到 ci 环境的影响,所以把写死的 profiling 结果存到了 cache 里去, +# 每次跑测试会从内存里读取 cache 里的 profiling 结果,然后根据 profiling 结果去做全局图优化。 +# 这个脚本用来把 dump 出去的 cache 文件转化成 cache 的头文件,用于测试时读取数据。 +# 如果在 src/gopt/test/layout_transform_pass.cpp 里添加了全局图优化相关的测试,则需要考虑用这个脚本来 +# 处理一下 profiling 数据。 +# 1. 首先将 src/gopt/test/layout_transform_pass.cpp 中的 `#define MGB_WITH_CACHED_TEST 1` 修改为 +# `#define MGB_WITH_CACHED_TEST 0` +# 2. 编译megbrain_test,并运行所有全局图优化相关测试: +# ./megbrain_test --gtest_filter="*LayoutTransform*" +# 3. 用这个脚本把所有的cache文件打包在一起 +# python3 embed_cache.py -o cache_data.h $(ls /path/to/cache/*.cache) +# 4. 将步骤1中的 define 改回去,这样 profile 过程用到的是 cache 下来的数据。随后可以重新构建 megbrain_test , +# 验证测试是否正确。 +import os.path +import logging +import hashlib +import argparse +import struct +import itertools +import sys +import subprocess + +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.WARNING, format='%(asctime)-15s %(message)s') + +CHAR_MAP = {i: r'{}'.format(i) for i in range(256)} + +def _u32(data): + return struct.unpack('\n') + fout.write('#include \n') + for k, v in sorted(src_map.items()): + fout.write(""" +static const std::vector {} = {{ +""".format(k.replace('.', '_'))) + fout.write('{}'.format(v)) + fout.write('};\n') + + def invoke(self, output): + logger.info('generate cache_data.h ...') + fname2cache_data = {} + for fname in self._cache_files: + base, ext = os.path.splitext(os.path.basename(fname)) + assert ext == ".cache", "ext: {}, fname {}".format(ext, fname) + assert base not in fname2cache_data, "duplicated kernel: " + base + fname2cache_data[base] = self.gen_cache_data(fname) + with open(output, 'w') as fout: + self.gen_cache_data_header(fout, fname2cache_data) + logger.info('done') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='embed cache into cache header file', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('-o', '--output', help='output source file', + required=True) + parser.add_argument('cache', help='cache files to be embedded', nargs='+') + args = parser.parse_args() + cache_generator = CacheDataGenerator(args.cache) + cache_generator.invoke(args.output) diff --git a/src/gopt/test/layout_transform_pass.cpp b/src/gopt/test/layout_transform_pass.cpp index e9cfb7e1f..8cba8b16d 100644 --- a/src/gopt/test/layout_transform_pass.cpp +++ b/src/gopt/test/layout_transform_pass.cpp @@ -23,6 +23,12 @@ #include "megbrain/plugin/profiler.h" #include "megbrain/serialization/serializer.h" +#define MGB_WITH_CACHED_TEST 1 + +#if MGB_WITH_CACHED_TEST +#include "./cache_data.h" +#endif + using namespace mgb; using namespace gopt; using namespace serialization; @@ -53,6 +59,78 @@ size_t find_opr_num(SymbolVar endpoint) { cg::DepOprIter{cb}.add(endpoint.node()->owner_opr()); return opr_num; } + +using OprFormat = Problem::OprFormat; +OprFormat tensor_formats_to_opr_format(TensorFormats tensor_format) { + switch (tensor_format) { + case TensorFormats::NCHW: + return OprFormat::NCHW; + case TensorFormats::NCHWc4: + return OprFormat::NCHW4; + case TensorFormats::NCHWc8: + return OprFormat::NCHW8; + case TensorFormats::NCHWc32: + return OprFormat::NCHW32; + case TensorFormats::NCHWc64: + return OprFormat::NCHW64; + case TensorFormats::NHWC: + return OprFormat::NHWC; + case TensorFormats::CHWNc4: + return OprFormat::CHWN4; + default: + mgb_throw(MegBrainError, "tensor format(%u) is not supported", + static_cast(tensor_format)); + } +} + +class ProfilerMock : public ProfilerImpl { +public: + ProfilerMock(const uint8_t* bin, size_t size) { + mgb_assert(bin != nullptr); + ProfilerCache::inst().set_impl( + std::make_unique(bin, size)); + } + ~ProfilerMock() { + // reset in memory cache + ProfilerCache::inst().set_impl( + std::make_unique()); + } + +private: + float profile_operator(const OperatorNodeBase* opr, + TensorFormats base_format, + TensorFormats tensor_format, + ReformatAttribute extra_attribute = + ReformatAttribute::DEFAULT) const override { + ProfilerCache::Key key{opr, tensor_formats_to_opr_format(tensor_format), + extra_attribute}; + auto ret = ProfilerCache::inst().get(key); + if (ret.valid()) + return ret.val(); + mgb_assert(false); + } + float profile_operator(const OperatorNodeBase* opr, + const OprTensorFormatsConfiguration& base_config, + const OprTensorFormatsConfiguration& config, + ReformatAttribute extra_attribute = + ReformatAttribute::DEFAULT) const override { + ProfilerCache::Key key{opr, config.opr_format, extra_attribute}; + std::string tmp; + tmp.reserve(key.blob().size); + auto ret = ProfilerCache::inst().get(key); + if (ret.valid()) + return ret.val(); + mgb_assert(false); + } + float profile_var_node(const VarNode* var, TensorFormats base_format, + const ReformatKey& key) const override { + ProfilerCache::Key pf_key{var, key}; + auto ret = ProfilerCache::inst().get(pf_key); + if (ret.valid()) + return ret.val(); + mgb_assert(false); + } +}; } // namespace #if MGB_CUDA @@ -96,15 +174,23 @@ TEST(TestLayoutTransform, Resnet18_QS8) { OprFormat::NCHW, TensorFormats::NCHW, Target::UNSPEC, ReformatAttribute::AUTO_PADDING_NHWC}; auto ctx = std::make_unique( - std::move(opr_list), std::move(available_tensor_formats), attribute); - ctx->add_opr_config( - opr::ConvBiasForward::typeinfo(), - {OprFormat::NCHW4, OprFormat::NCHW32, OprFormat::CHWN4, OprFormat::NHWC}) - .add_opr_config( - opr::PoolingForward::typeinfo(), - {OprFormat::NCHW4, OprFormat::NCHW32, OprFormat::NHWC, - OprFormat::CHWN4}); - auto profiler = ProfilerBase::make_profiler(); + std::move(opr_list), std::move(available_tensor_formats), + attribute); + ctx->add_opr_config(opr::ConvBiasForward::typeinfo(), + {OprFormat::NCHW4, OprFormat::NCHW32, OprFormat::CHWN4, + OprFormat::NHWC}) + .add_opr_config(opr::PoolingForward::typeinfo(), + {OprFormat::NCHW4, OprFormat::NCHW32, + OprFormat::NHWC, OprFormat::CHWN4}); +#if MGB_WITH_CACHED_TEST + auto profiler = std::make_unique( + static_cast( + TestLayoutTransform_Resnet18_QS8.data()), + TestLayoutTransform_Resnet18_QS8.size()); +#else + auto profiler = ProfilerBase::make_cached_profiler( + "TestLayoutTransform.Resnet18_QS8.cache"); +#endif std::unique_ptr solver{ new DynamicProgrammingSolver(std::move(profiler))}; auto new_output = @@ -190,7 +276,15 @@ TEST(TestLayoutTransform, Resnet18_QS4) { opr::PoolingForward::typeinfo(), {OprFormat::NCHW4, OprFormat::NCHW32, OprFormat::NCHW64, OprFormat::NHWC, OprFormat::CHWN4}); - auto profiler = ProfilerBase::make_profiler(); +#if MGB_WITH_CACHED_TEST + auto profiler = std::make_unique( + static_cast( + TestLayoutTransform_Resnet18_QS4.data()), + TestLayoutTransform_Resnet18_QS4.size()); +#else + auto profiler = ProfilerBase::make_cached_profiler( + "TestLayoutTransform.Resnet18_QS4.cache"); +#endif std::unique_ptr solver{ new DynamicProgrammingSolver(std::move(profiler))}; auto new_output = @@ -305,7 +399,15 @@ TEST(TestLayoutTransform, Detection_QS8) { opr::PoolingForward::typeinfo(), {OprFormat::NCHW4, OprFormat::NCHW32, OprFormat::NCHW64, OprFormat::NHWC, OprFormat::CHWN4}); - auto profiler = ProfilerBase::make_profiler(); +#if MGB_WITH_CACHED_TEST + auto profiler = std::make_unique( + static_cast( + TestLayoutTransform_Detection_QS8.data()), + TestLayoutTransform_Detection_QS8.size()); +#else + auto profiler = ProfilerBase::make_cached_profiler( + "TestLayoutTransform.Detection_QS8.cache"); +#endif std::unique_ptr solver{ new DynamicProgrammingSolver(std::move(profiler))}; auto new_outputs = @@ -375,7 +477,15 @@ TEST(TestLayoutTransform, Detection_QS4) { opr::PoolingForward::typeinfo(), {OprFormat::NCHW4, OprFormat::NCHW32, OprFormat::NCHW64, OprFormat::NHWC, OprFormat::CHWN4}); - auto profiler = ProfilerBase::make_profiler(); +#if MGB_WITH_CACHED_TEST + auto profiler = std::make_unique( + static_cast( + TestLayoutTransform_Detection_QS4.data()), + TestLayoutTransform_Detection_QS4.size()); +#else + auto profiler = ProfilerBase::make_cached_profiler( + "TestLayoutTransform.Detection_QS4.cache"); +#endif std::unique_ptr solver{ new DynamicProgrammingSolver(std::move(profiler))}; auto new_outputs = @@ -443,10 +553,18 @@ TEST(TestLayoutTransform, Wide) { OprFormat::NCHW, TensorFormats::NCHW, Target::UNSPEC, ReformatAttribute::DEFAULT}; auto ctx = std::make_unique( - std::move(opr_list), std::move(available_tensor_formats), attribute); - ctx->add_opr_config( - opr::ConvBiasForward::typeinfo(), {OprFormat::NCHW, OprFormat::NHWC}); - auto profiler = ProfilerBase::make_profiler(); + std::move(opr_list), std::move(available_tensor_formats), + attribute); + ctx->add_opr_config(opr::ConvBiasForward::typeinfo(), + {OprFormat::NCHW, OprFormat::NHWC}); +#if MGB_WITH_CACHED_TEST + auto profiler = std::make_unique( + static_cast(TestLayoutTransform_Wide.data()), + TestLayoutTransform_Wide.size()); +#else + auto profiler = ProfilerBase::make_cached_profiler( + "TestLayoutTransform.Wide.cache"); +#endif std::unique_ptr solver{ new DynamicProgrammingSolver(std::move(profiler))}; auto v = gopt::GraphOptimizer{} @@ -463,12 +581,8 @@ TEST(TestLayoutTransform, Wide) { auto func = network.graph->compile({{sym_o, {}}}); func->execute(); gprof.to_json_full(func.get())->writeto_fpath(output_file("wide.json")); - /// check global layout transform pass, no dimshuffle - /// disable the following check, to make ci stable. -#if 0 auto nr_dimshuffle = find_opr_num(sym_o); ASSERT_EQ(nr_dimshuffle, 0u); -#endif auto nr_param_merge = find_opr_num(sym_o); ASSERT_EQ(nr_param_merge, 1u); /// check first conv format @@ -477,48 +591,6 @@ TEST(TestLayoutTransform, Wide) { ASSERT_EQ(cast.param().format, opr::ConvBias::Param::Format::NCHW); } -TEST(TestLayoutTransform, ElemwiseMultiType) { - REQUIRE_GPU(1); - auto cn = CompNode::load("gpu0"); - Network network(cn); - auto x = network.add_var("x", {64, 64, 1, 2}); - auto y = network.add_var("y", {64, 64, 1, 2}); - x = network.add_type_cvt(x, dtype::QuantizedS4{1.f}); - y = network.add_type_cvt(y, dtype::QuantizedS4{1.f}); - auto x_ = network.add_type_cvt(x, dtype::Float32()); - auto y_ = network.add_type_cvt(y, dtype::Float32()); - auto z = network.add_elemwise( - {x_, y_}, dtype::Float32(), opr::Elemwise::Mode::FUSE_ADD_RELU); - z = network.add_type_cvt(z, dtype::QuantizedS4{1.f}); - z = network.add_type_cvt(z, dtype::Float32()); - auto z2 = network.add_elemwise( - {x, y}, dtype::QuantizedS4{1.f}, opr::Elemwise::Mode::FUSE_ADD_RELU); - z2 = network.add_type_cvt(z2, dtype::Float32()); - HostTensorND t1; - auto func1 = network.graph->compile({make_callback_copy(z, t1)}); - func1->execute(); - - HostTensorND t3; - auto func3 = network.graph->compile({make_callback_copy(z2, t3)}); - func3->execute(); - - auto alter_x = opr::RelayoutFormat::make( - x, megdnn::param::RelayoutFormat::Mode::NCHW_NCHW64); - auto alter_y = opr::RelayoutFormat::make( - y, megdnn::param::RelayoutFormat::Mode::NCHW_NCHW64); - auto alter_z = network.add_elemwise( - {alter_x, alter_y}, dtype::QuantizedS4{1.f}, - opr::Elemwise::Mode::FUSE_ADD_RELU); - alter_z = opr::RelayoutFormat::make( - alter_z, megdnn::param::RelayoutFormat::Mode::NCHW64_NCHW); - alter_z = network.add_type_cvt(alter_z, dtype::Float32()); - HostTensorND t2; - auto func2 = network.graph->compile({make_callback_copy(alter_z, t2)}); - func2->execute(); - // MGB_ASSERT_TENSOR_EQ(t1, t3); - MGB_ASSERT_TENSOR_EQ(t2, t3); -} - #if CUDA_VERSION >= 10020 TEST(TestLayoutTransform, DetectionHead) { REQUIRE_GPU(1); @@ -600,8 +672,15 @@ TEST(TestLayoutTransform, DetectionHead) { .add_opr_config( opr::WarpPerspectiveForward::typeinfo(), {OprFormat::NHWC, OprFormat::NCHW4, OprFormat::NCHW64}); - - auto profiler = ProfilerBase::make_profiler(); +#if MGB_WITH_CACHED_TEST + auto profiler = std::make_unique( + static_cast( + TestLayoutTransform_DetectionHead.data()), + TestLayoutTransform_DetectionHead.size()); +#else + auto profiler = ProfilerBase::make_cached_profiler( + "TestLayoutTransform.DetectionHead.cache"); +#endif std::unique_ptr solver{ new DynamicProgrammingSolver(std::move(profiler))}; auto new_out_vars = -- GitLab