From abb63121fedeb015ee36b69e255465e8e4415ea6 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Tue, 28 Nov 2023 15:39:20 +0800 Subject: [PATCH 01/22] update onnxrt woq example Signed-off-by: yuwenzho --- .../llama/quantization/ptq_static/README.md | 3 +++ .../llama/quantization/ptq_static/main.py | 8 +++++++- .../llama/quantization/ptq_static/run_quant.sh | 13 +++++++++++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md index 938a777ddf6..cacb457c464 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md @@ -44,6 +44,9 @@ bash run_quant.sh --input_model=/path/to/model \ # folder path of onnx model --quant_format="QOperator" # or QDQ, optional ``` +Additionally set `--layer-wise=True` to use layer-wise quantization to save your memory. More details please refer to [](). + + ## 2. Benchmark Accuracy: diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py index 46b09a25f86..c1c940ef803 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py @@ -116,6 +116,11 @@ type=int, default=4 ) +parser.add_argument( + '--layer_wise', + action='store_true', \ + default=False, +) args = parser.parse_args() # load model @@ -262,7 +267,8 @@ def __iter__(self): calibration_sampling_size=[8], recipes={'optypes_to_exclude_output_quant': ['MatMul'], 'smooth_quant': True, - 'smooth_quant_args': {'alpha': args.smooth_quant_alpha}}, + 'smooth_quant_args': {'alpha': args.smooth_quant_alpha}, + 'layer_wise_quant': True if args.layer_wise else False}, op_type_dict={'^((?!(MatMul|Gather|Conv)).)*$': {'weight': {'dtype': ['fp32']}, 'activation': {'dtype': ['fp32']}}}) for model in ['decoder_model.onnx', 'decoder_with_past_model.onnx']: q_model = quantization.fit( diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/run_quant.sh b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/run_quant.sh index 9c7d2ff8c2a..384f43a84aa 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/run_quant.sh +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/run_quant.sh @@ -32,6 +32,9 @@ function init_params { --tokenizer=*) tokenizer=$(echo $var |cut -f2 -d=) ;; + --layer_wise=*) + layer_wise=$(echo $var |cut -f2 -d=) + ;; esac done @@ -59,15 +62,21 @@ function run_tuning { echo "Created directory $output_model" fi + # check if layer_wise option is set to true (case insensitive) + if [ "${layer_wise,,}" = "true" ]; then + extra_cmd="--layer_wise" + fi + python main.py \ --quant_format ${quant_format-QOperator} \ --model_path ${input_model} \ - --tokenizer ${tokenizer-meta-llama/Llama-2-7b-hf} \ + --tokenizer ${tokenizer-meta-llama/Llama-2-7b-hf} \ --output_model ${output_model} \ --batch_size ${batch_size-1} \ --smooth_quant_alpha ${alpha-0.6} \ --dataset ${dataset-NeelNanda/pile-10k} \ - --tune + --tune \ + ${extra_cmd} } main "$@" From 0655119081f6e4298317871c7ab156485ba9f798 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Thu, 30 Nov 2023 17:08:21 +0800 Subject: [PATCH 02/22] add layer-wise quantization example Signed-off-by: yuwenzho --- examples/.config/model_params_onnxrt.json | 7 +++ .../llama/quantization/ptq_static/README.md | 14 +++++- .../llama/quantization/ptq_static/main.py | 47 ++++++++++++++----- neural_compressor/adaptor/onnxrt.py | 2 +- .../adaptor/ox_utils/calibration.py | 4 +- neural_compressor/adaptor/ox_utils/util.py | 39 +++++++++++++++ neural_compressor/model/onnx_model.py | 13 +++-- 7 files changed, 106 insertions(+), 20 deletions(-) diff --git a/examples/.config/model_params_onnxrt.json b/examples/.config/model_params_onnxrt.json index 8695baa3dc1..238c744ad27 100644 --- a/examples/.config/model_params_onnxrt.json +++ b/examples/.config/model_params_onnxrt.json @@ -763,6 +763,13 @@ "main_script": "main.py", "batch_size": 1 }, + "llama-2-7b-lwq": { + "model_src_dir": "nlp/huggingface_model/text_generation/llama/quantization/ptq_static", + "dataset_location": "", + "input_model": "/tf_dataset2/models/onnx/llama-2-7b", + "main_script": "main.py", + "batch_size": 1 + }, "llama-2-7b-rtn": { "model_src_dir": "nlp/huggingface_model/text_generation/llama/quantization/weight_only", "dataset_location": "", diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md index cacb457c464..5c2bc11ab89 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md @@ -34,6 +34,8 @@ optimum-cli export onnx --model meta-llama/Llama-2-7b-hf --task text-generation- ## 1. Quantization +Run SmoothQuant + ```bash bash run_quant.sh --input_model=/path/to/model \ # folder path of onnx model --output_model=/path/to/model_tune \ # folder path to save onnx model @@ -44,7 +46,17 @@ bash run_quant.sh --input_model=/path/to/model \ # folder path of onnx model --quant_format="QOperator" # or QDQ, optional ``` -Additionally set `--layer-wise=True` to use layer-wise quantization to save your memory. More details please refer to [](). +Additionally set `--layer-wise=True` to use layer-wise quantization to save your memory. Please note that layer-wise quantization for ONNX models is still under development and only support W8A8 quantization now. More details please refer to [layer wise quantiation](https://github.com/intel/neural-compressor/blob/master/docs/source/quantization_layer_wise.md). + +```bash +bash run_quant.sh --input_model=/path/to/model \ # folder path of onnx model + --output_model=/path/to/model_tune \ # folder path to save onnx model + --batch_size=batch_size # optional \ + --dataset NeelNanda/pile-10k \ + --tokenizer=meta-llama/Llama-2-7b-hf \ # model name or folder path containing all relevant files for model's tokenizer + --quant_format="QOperator" \ # or QDQ, optional + --layer-wise=True +``` ## 2. Benchmark diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py index c1c940ef803..cb26cc585a4 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py @@ -263,16 +263,37 @@ def __iter__(self): if args.tune: from neural_compressor import quantization, PostTrainingQuantConfig - config = PostTrainingQuantConfig( - calibration_sampling_size=[8], - recipes={'optypes_to_exclude_output_quant': ['MatMul'], - 'smooth_quant': True, - 'smooth_quant_args': {'alpha': args.smooth_quant_alpha}, - 'layer_wise_quant': True if args.layer_wise else False}, - op_type_dict={'^((?!(MatMul|Gather|Conv)).)*$': {'weight': {'dtype': ['fp32']}, 'activation': {'dtype': ['fp32']}}}) - for model in ['decoder_model.onnx', 'decoder_with_past_model.onnx']: - q_model = quantization.fit( - os.path.join(args.model_path, model), - config, - calib_dataloader=KVDataloader(os.path.join(args.model_path, model), pad_max=args.pad_max, batch_size=1)) - q_model.save(os.path.join(args.output_model, model)) + if args.layer_wise: + # layer-wise quantization for ONNX models is still under development and only support W8A8 quantization now + config = PostTrainingQuantConfig( + calibration_sampling_size=[8], + recipes={'optypes_to_exclude_output_quant': ['MatMul'], + 'layer_wise_quant': True}, + op_type_dict={'^((?!(MatMul|Gather|Conv)).)*$': {'weight': {'dtype': ['fp32']}, 'activation': {'dtype': ['fp32']}}}) + for model in ['decoder_model.onnx']: + # only test decoder_model + q_model = quantization.fit( + os.path.join(args.model_path, model), + config, + calib_dataloader=KVDataloader(os.path.join(args.model_path, model), pad_max=args.pad_max, batch_size=1)) + q_model.save(os.path.join(args.output_model, model)) + + tokenizer.save_pretrained(args.output_model) + + else: + config = PostTrainingQuantConfig( + calibration_sampling_size=[8], + recipes={'optypes_to_exclude_output_quant': ['MatMul'], + # 'smooth_quant': True, + # 'smooth_quant_args': {'alpha': args.smooth_quant_alpha}, + }, + op_type_dict={'^((?!(MatMul|Gather|Conv)).)*$': {'weight': {'dtype': ['fp32']}, 'activation': {'dtype': ['fp32']}}}) + for model in ['decoder_model.onnx', 'decoder_with_past_model.onnx']: + q_model = quantization.fit( + os.path.join(args.model_path, model), + config, + calib_dataloader=KVDataloader(os.path.join(args.model_path, model), pad_max=args.pad_max, batch_size=1)) + q_model.save(os.path.join(args.output_model, model)) + + tokenizer.save_pretrained(args.output_model) + \ No newline at end of file diff --git a/neural_compressor/adaptor/onnxrt.py b/neural_compressor/adaptor/onnxrt.py index 5e8deff9933..e24dbc23f3b 100644 --- a/neural_compressor/adaptor/onnxrt.py +++ b/neural_compressor/adaptor/onnxrt.py @@ -1021,7 +1021,7 @@ def _pre_optimize(self, model, level=1): from onnx.external_data_helper import load_external_data_for_model load_external_data_for_model(tmp_model, os.path.split(model.model_path)[0]) - model.model_path = sess_options.optimized_model_filepath + model.model_path = sess_options.optimized_model_filepath else: model.model_path = sess_options.optimized_model_filepath diff --git a/neural_compressor/adaptor/ox_utils/calibration.py b/neural_compressor/adaptor/ox_utils/calibration.py index 2e15acd0d1e..26ea0739a9b 100644 --- a/neural_compressor/adaptor/ox_utils/calibration.py +++ b/neural_compressor/adaptor/ox_utils/calibration.py @@ -264,9 +264,9 @@ def get_intermediate_outputs(self, q_config=None): for output in session.get_outputs() ] augment_model_wrapper = ( - ONNXModel(self.augmented_model) + ONNXModel(self.augmented_model, load_external_data=False) if not self.model_wrapper.is_large_model - else ONNXModel(self.model_wrapper.model_path + "_augment.onnx") + else ONNXModel(self.model_wrapper.model_path + "_augment.onnx", load_external_data=False) ) input_name_to_nodes = augment_model_wrapper.input_name_to_nodes output_name_to_node = augment_model_wrapper.output_name_to_node diff --git a/neural_compressor/adaptor/ox_utils/util.py b/neural_compressor/adaptor/ox_utils/util.py index 8e679b432e0..547a7fa919c 100644 --- a/neural_compressor/adaptor/ox_utils/util.py +++ b/neural_compressor/adaptor/ox_utils/util.py @@ -17,6 +17,7 @@ """Helper classes or functions for onnxrt adaptor.""" import os +import logging from enum import Enum import numpy as np @@ -27,6 +28,11 @@ numpy_helper = LazyImport("onnx.numpy_helper") onnx_proto = LazyImport("onnx.onnx_pb") torch = LazyImport("torch") +symbolic_shape_infer = LazyImport("onnxruntime.tools.symbolic_shape_infer") +onnx = LazyImport("onnx") + +logger = logging.getLogger("neural_compressor") + __producer__ = "onnx.quantize" __version__ = "0.1.0" @@ -586,3 +592,36 @@ def to_numpy(data): ) else: return data + +class SymbolicShapeInference(symbolic_shape_infer.SymbolicShapeInference): + def __init__(self, int_max, auto_merge, guess_output_rank, verbose, prefix="", base_dir=""): + super().__init__(int_max, auto_merge, guess_output_rank, verbose, prefix) + self.base_dir = base_dir + + def _get_value(self, node, idx): + name = node.input[idx] + assert name in self.sympy_data_ or name in self.initializers_ + return self.sympy_data_[name] if name in self.sympy_data_ else \ + numpy_helper.to_array(self.initializers_[name], base_dir=self.base_dir) + + @staticmethod + def infer_shapes(in_mp, int_max=2**31 - 1, auto_merge=False, guess_output_rank=False, verbose=0, base_dir=""): + onnx_opset = symbolic_shape_infer.get_opset(in_mp) + if (not onnx_opset) or onnx_opset < 7: + logger.warning("Only support models of onnx opset 7 and above.") + return None + symbolic_shape_inference = SymbolicShapeInference( + int_max, + auto_merge, + guess_output_rank, + verbose, + base_dir=base_dir) + all_shapes_inferred = False + symbolic_shape_inference._preprocess(in_mp) + while symbolic_shape_inference.run_: + all_shapes_inferred = symbolic_shape_inference._infer_impl() + symbolic_shape_inference._update_output_from_vi() + if not all_shapes_inferred: + onnx.save_model(symbolic_shape_inference.out_mp_, "sym_shape_infer_temp.onnx", save_as_external_data=True) + raise Exception("Incomplete symbolic shape inference") + return symbolic_shape_inference.out_mp_ \ No newline at end of file diff --git a/neural_compressor/model/onnx_model.py b/neural_compressor/model/onnx_model.py index c1365668f55..431ddd007e7 100644 --- a/neural_compressor/model/onnx_model.py +++ b/neural_compressor/model/onnx_model.py @@ -40,13 +40,20 @@ def __init__(self, model, **kwargs): Args: model (str or ModelProto): path to onnx model or loaded ModelProto model object. + ignore_warning (bool): ignore large model warning. Default is False. + load_external_data (bool): load external data for large model. """ - self._model = model if not isinstance(model, str) else onnx.load(model) + self._model = model if not isinstance(model, str) else onnx.load(model, load_external_data=False) self._model_path = None if not isinstance(model, str) else model self.check_is_large_model() if self._is_large_model and self._model_path is None and not kwargs.get("ignore_warning", False): logger.warning("Model size > 2GB. Please use model path instead of onnx model object to quantize") + + if self._is_large_model and isinstance(model, str) and kwargs.get("load_external_data", True): + from onnx.external_data_helper import load_external_data_for_model + + load_external_data_for_model(self._model, os.path.dirname(self._model_path)) self._config = None if isinstance(model, str) and os.path.exists(Path(model).parent.joinpath("config.json").as_posix()): @@ -1038,9 +1045,9 @@ def split_model_with_node( if shape_infer: try: # need ort.GraphOptimizationLevel <= ORT_ENABLE_BASIC - import onnxruntime.tools.symbolic_shape_infer as symbolic_shape_infer + from neural_compressor.adaptor.ox_utils.util import SymbolicShapeInference - self._model = symbolic_shape_infer.SymbolicShapeInference.infer_shapes(self._model, auto_merge=True) + self._model = SymbolicShapeInference.infer_shapes(self._model, auto_merge=True, base_dir=os.path.dirname(self._model_path)) except Exception as e: # pragma: no cover logger.error("Shape infer fails for layer-wise quantization") if "Incomplete symbolic shape inference" in str(e): From ca67739816822fa5c6540b87a8c2dcabb0cb821d Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Thu, 30 Nov 2023 17:16:18 +0800 Subject: [PATCH 03/22] fix docstring Signed-off-by: yuwenzho --- .../text_generation/llama/quantization/ptq_static/main.py | 4 ++-- neural_compressor/model/onnx_model.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py index cb26cc585a4..788abf5fc2d 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py @@ -284,8 +284,8 @@ def __iter__(self): config = PostTrainingQuantConfig( calibration_sampling_size=[8], recipes={'optypes_to_exclude_output_quant': ['MatMul'], - # 'smooth_quant': True, - # 'smooth_quant_args': {'alpha': args.smooth_quant_alpha}, + 'smooth_quant': True, + 'smooth_quant_args': {'alpha': args.smooth_quant_alpha}, }, op_type_dict={'^((?!(MatMul|Gather|Conv)).)*$': {'weight': {'dtype': ['fp32']}, 'activation': {'dtype': ['fp32']}}}) for model in ['decoder_model.onnx', 'decoder_with_past_model.onnx']: diff --git a/neural_compressor/model/onnx_model.py b/neural_compressor/model/onnx_model.py index 431ddd007e7..3d055d39f04 100644 --- a/neural_compressor/model/onnx_model.py +++ b/neural_compressor/model/onnx_model.py @@ -41,7 +41,7 @@ def __init__(self, model, **kwargs): Args: model (str or ModelProto): path to onnx model or loaded ModelProto model object. ignore_warning (bool): ignore large model warning. Default is False. - load_external_data (bool): load external data for large model. + load_external_data (bool): load external data for large model. Default is True. """ self._model = model if not isinstance(model, str) else onnx.load(model, load_external_data=False) self._model_path = None if not isinstance(model, str) else model From 54179d0603a2d27788e186be0004b7f64e8bafdf Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Thu, 30 Nov 2023 17:18:12 +0800 Subject: [PATCH 04/22] update README.md Signed-off-by: yuwenzho --- .../text_generation/llama/quantization/ptq_static/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md index 5c2bc11ab89..975ef34b617 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md @@ -55,7 +55,7 @@ bash run_quant.sh --input_model=/path/to/model \ # folder path of onnx model --dataset NeelNanda/pile-10k \ --tokenizer=meta-llama/Llama-2-7b-hf \ # model name or folder path containing all relevant files for model's tokenizer --quant_format="QOperator" \ # or QDQ, optional - --layer-wise=True + --layer_wise=True ``` From ea2078e710e3bc683ec05ac95c1d1b1e9f7cfb34 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Thu, 30 Nov 2023 17:24:46 +0800 Subject: [PATCH 05/22] add layer-wise quantization doc Signed-off-by: yuwenzho --- docs/source/imgs/ort_lwq.png | Bin 0 -> 161111 bytes docs/source/quantization_layer_wise.md | 101 +++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 docs/source/imgs/ort_lwq.png create mode 100644 docs/source/quantization_layer_wise.md diff --git a/docs/source/imgs/ort_lwq.png b/docs/source/imgs/ort_lwq.png new file mode 100644 index 0000000000000000000000000000000000000000..e2576acf28722f3c62615e86f49e92a9182b13a8 GIT binary patch literal 161111 zcmeEv30#utzCSa~jAy1vr`6nNw9Pg%bIYx!(l&L<(s04bg(gx|asyF2O_{lkWtn?r zYAG&BE~u!Drlta!0=a=niVBK`h=RcX#q6BQbh`JRd+yx(kI$!%0q^rXzxDfDp67iD z|HaB;$#;t1fk2=o2lnqh3IZ*y0D)vY|Mm^=iT+7P=@(xhj#~T-$|EWd0&l*8?zY?w z0zHgbJbmhG;QhB=`@skhXnVEv-xnKP?K?prNydS_yN?Ax*j>A2U+LXk*JF4>>$?FQ z={EETYol=2*Bp1|K2j7LUk~5)<&oOu`wpyFc`!t6RIBmn?bQ9|YwjEz3IS&R{Q4|` z&l338NCDPjhxcDe|mC{g)fjnJ^#CbiyuYO(CTA9Tl{x-F}M*R(fz zo;^+AnzlPq0tT0SFrfNp-dg?kh##Ztns!Ytb%hCaDw=4VqwTs zW4%cOYP1awt!XcvDn_MP^X{u>_n3NOz)zMFyWe|#%St76PZ3Oet; zysifvv6%QHteo-nPt66q!pwj>PY-)YJZ`N=DOfg-HPa*U^0a1%*}G$;|DV=G9cgF; zn6cwQ9JeFTyvj|G=H=|Q$V z25)i>s8MWpGBZ|p3!L63_)dw?IH$_&k z`=jFzjzbO}UGCAdL91eF%oU+9_J`63SBA#7qsE$3wBl9=4lYsdyQFWHdTy%*Bw*18 z*Nv)=y^dk8U6zJ(s3=e`_l%Oa_llFJO$QX>A}7%wTnq|`rA6#k&wUdtPP22^P}ITB$J(3c*fT|THq+W zqE>}rzXQeDuY12Ef7rCXM8B?OT|Xm<;^m|U?&bxL*e|zc5x{s3{8rAn;IrL2{rv;* z+UqXQ9r8U=HnrI!c^Fk}toBv2Dp+J{rRn>0dtnVWC#$T>1EV*X
  • &YoJ!-dji>y zcB5XA`1L9B3j8}cEy)VlE$!$D*(9}G3HugdbyjX928je<&n(L*1T z0k!B$(JGNyWr8VD_}&wT949Fhc1i?zl={_b^; z!shB=p>>LrmvNGtJbl;_4>wn`ZP|G-_XU(MuED%#I|qWQ!k6#a=p`*pOtpLv}|hih8O zjaWq_Z0f2JB%r0g5#C98LDf#eTR}y#qyjs-3PN9?L@jekHSa(Dgmdf_yY$h>Eo@@? z2*%BIq_|5vOQY%`xs@vOK1HepzGo`?HSrf;TFCi9(R=fhe~`2pVbIh&oPiQTY|S{- z4ZZIV8jnGO6`Ii-8zSTYdm<6{x0ORFiVdb_n^62Q50;j&3loWXKoceVo*%XlCbCJW za6>~4(-JbhLDA3#@ksOyzQcSZi^kvInb;vYV#m~Mf^2~bOmx=i+Nt3CTkKdgg_ zdcI;)tS;3fy3Ho4`PcDI%5h021sz3sJQi zZSC&ag}{lY51Wsm;Y1w=ZkrNkk~3_KE~e+C4{IG7e^@et)D2$Ok-SpZ6&<`zDaIPb zEzyS@f7E2^tbOa2!gZO;sIc1_7OrVAwO z^zQPbguEJ94G~ReO@IQDbP9uuw@F!@3Lo<;Iv6`Px2m zQ0hJk73$u$^QePkmZ%F#>~e;0?IlGw2(Bm(XJr>fMZ;0uiO1(qUEu$N@adu&9axj8 znjz|T899eHaB2d1LdgC}VmqwWlmK%Txf<>)Msd-8#=Qt|NNfTN)vsufZ4ZL}uVwUNBu9T9@9zGV*Do zy+~Gxne1X3M~}BA@zW9F2UO00h*u|OuT~kGYdhnzN*1Z6UdiNRh--Beo`w#Gccp!XJa=?j4h?C18t&E8 zkXm84`9EJx;0K!9+Pq@hKNVbU7MR-MwN36)Nti@_rI7_?7stp3;bzIJV+_)pFqtIB zlzcLGhYOm3!9gi}SGc3?P>1_ytb67(E(fP9ueLLWCae(MrnOz zaFpp(Sf&mgF=J%`Y`Kdl$t?~HETrv+RL4blSV&F8BI(z#CV-+!>VQ_c)vGIPuC9)o zh^h>}i02%BHN?-PO1kMcOu&|D2|uw)-2099xlh;SCg$UX3OCTj?)`A_70l%Z3_Y6$ zmo(r#qo%T}^yh@i(ci%`&75n1CSTn~PfRsj-NUpPs%XN<`~N1z{DSMvJ6Y2 zbp2FrMHtxWIF|BM5Z#>qKEEmWLerv}^NNOY`7OSsy+&BwV*jY7`C(e}JQE>J*o&lgriOPY@-pqh@M;FQcV`Qo zR-ppety=6#m%#o46dXewFL5I){tXY?H?oY1Mv5+B(i$+jC-#N3`>Jao89Pvx$M8kk zJ5BLrP&Bl-&Tm3l^#*wb7*}jgt-Bi7D9FPdf}fih*(5wnIEd%GY>XVr&Ct^}!-iDN zalnEXv^(te!(_i#yFvi}!^E#1KZ{3hGZL8kte!q2HF(VGCJh9HS2uDv#9_p7QSK(K z-wZYigqd%=kaCi&1gOV53BN9zwo_RpZ6!S+_hHJc*;930JTSMiFr38l@k)Ob{{}kK zJ%iSTX$D5n8=E;#<_K!l5MKGG?W||h3aBELShVSOR>ApvN=v-FtF3aP*&9ugF!d|7 zF8$3v_kt}a*j-I5se3~U$4t(a4cRa-Pl=(^J_c)JWBesy&?Q$(B6?z<9u6>5R}f>BWSgOvAzrz1KF< zZMyx)CdhY7-SaMN!N-G|%j!z<-?(GpMOr}2KrSB4B@Yqk1Cy9Z;<>0{wxRLH9L??J zc?vL>C&q4QYF7py-&vl|*3sR>>v!LG`uqI63zI?ct;O3J9WUqP?fNX04bf((gSOU$ zfyG~BOCeksQ9}kxV!=hl;lYeD4ViKjBIp>!!u83CRKwcJ-i#ma*zsF*e;?$TaZ!o9 zw7v&3y40!v$uYOQWWpU2-W=&$ID-{f=I9%&aK}Wgt;N^ zZXB?3_uLCxz*RsLVJra>2GL&k+{Z&_yYXYm57%wS`x?BjJ&dD6`afi)uQzMs>nnwu z_73BxrkYjgeRDwTaGTLBmB2sPiNLy8*}Z(oc5P%So@(*xU@m@9?;FkaCBz$yQ=TV? zi-AhQ0m=0*;tXwhmz*Mf7|mWMubt&W@pp%vgOXkXp)d9Z40b-!uGxm-PjCVNnElk$ z278plA0N~;-2BuBmSI}>OkSeKXk2?R}+Ws8`@Y?tX(OOxwMxPEy#4IiK)a* zNSPXGbqFBABsLw%KxS5L0m{HkD%|yE31-HeioVIo@y$;xx5g(zr&kdK#B*fBx7NL> z+?;TKJyX-C&)=ho%sHG-1G{X$>#t^=Kb9~drj-gIX>%&*Bb?tShZfbKWN0L1dlC>^ z%Ss@Gbgi!k2#?7Z1^CKYa&#C4fCZhZQ*3Y*k()7+_XMQ76ju`&2n4_OY{VOcOmMRE zi%L9VO|*H{$mzIA^LKZZwWW4mC;!6L2<-JAnQJ^-2UL<{Fvc5bMg^*d8bGpinKl^} z?KIJzn1ggdO6~ZS+gBRMFsd~2>FYxUozHWGY<`xV5Hpqs#56};#0+|~wG)o`1EUgH z`HgutI%l7}qp}2Ff&h|#E9*wp?mPbYNvuf@YxwEe`#46q1VLtVoTZvp#JIP0vlwhn`%Rj?*>*iCG%>c%C@(v~jgkl;6RK(t){S~!0hdL) zwbyot?BkkBKl#L-9XKcT{Tjb#GIphwdPANt0V@-a;`{Nojnhh_RL_x2l@hc^@QO2S z275JaH2@TJ5Jf{j2IY_FXz7Ell;RE1Aa?H&y$yB!hV%|1ve<~37H0akMcr3*( zlC$3#yhK-rIq)lFCKHZxDU1~r_e&u|v^F!^=*iK0~{#^?_^a6NbN7l(DR zkb~Qhcma$uNn#=@c%Ve+&im~kwXQxF!d}8Nn-c6po zZPXFqZDiz=gd!Dlf$7G&ZLIY^#Y76_McoWVH+6g`C^oO|ujC#ZCI`FGCIqJ36dH@m zo_r)4>E_e;+{RM3USvAsI4tSN9+Y!h`&^}JwHob}BpBQa>i-dWyjhR6Yfa zr1R3vgY~2HII-TQ*h&-w^{8%(14EnT{|bZ2%-x%-!C2}s(JyI60}gxEH8%wphW8Fn ze#lo^YPj9pfp6wRLKW>*vtw?`8#BB)R>gnho03rVEU5;fGN=1C`7?e|E%+{4c{6S2 zBNd6sb5$?Zr<)yQC}zv~4}AeDp(>?BQ%OQ@N4AcOG-j@;J9(LAJlrk0iPvsVOrPZ7 zb;lk63@8qe6?~ibb0CGRV0XB3FNt4QMgW7IbshL%Rs_&Y87>rSSUf4h26wOtNAB}~ zM~ijR`Rzl5mk^Ki;vq8!?q;whDtxR%F}{UUqp+!eu(csEVy~0QkUV$4-zqq15V8g!Y4QNw@GE$5d+GZQi9DN7e(mm7mJEVEV91);Y|? zc%F9IH-#jk6+D$zAkNMeo7&_j6{#d#r|A+`JB6i9RfIuIJnjyI@wiCTty3&WUYp4t zTN~H&>~tr~2_~2rJ$NHl?KKTvSVoykfKQ3Q6E_u3!c>ZY4b{mrP=@dC{y?h$J2MqmZpKI5olJ|C&RJ zjch)UFWKZC?VY4RuUVah;WXUf^ke~9{1M4YAf>RUI?-cvY@1twfXO)PP7SW-53_gT z({FB0AGuZkF^c2|RPw44k)t86MiGy2$REW+cew0*Upp|a7pZvjehe@&cMz%E;4Mw? zZK%-Kw4CUr`Z}Vpw<+iFRH|O*5poP#a95UI?~C0NE=AUq1sK z_i|Z7U4+&3E4gj0<>G{7QMpfTGpDl~;lCDI=S3;qOAGiA%GUPo8pAAVY=8Nxr82$y zxVH$y$pwn$upaD$oos!Y0xNeB9c%_Y+f>IMLKhe3Y46dqMk(M+&P6gH=4K(8#p#CI zt?8xW?G`X)r$g;Qwa}80vB*;MVw#0cdFl_kZZ128l~`D_{c>m#ugw*<&Or?Y*1n9T z)})p~Z0pbTa&!7c2XnFYx+8{y<~-ju-ez{NPWwaYxSe5hupuA;dD2LBBh@u5R$~mx z`{gz5@hc6Pw0zZZS)OU37}jkqRAv^}?aYf5I+hOyP`t&L10}2ACqnRwte{{n|6a*D z*pO(SQGZjN@iLdn3}PpR*-H{{@h?*`4sUvSy2_!R_=du>p@i+-4h^`_8lps$J(Eb7heH4<`Hi`Ohqi<3_M54p%(;rh; z1{q|dTP&mGX&=l00&P1!QJ8e)FZTtRE;$SkCYjWkL-FSiK1<-U1U^gPvjjd>0@aPIz@6L>0Ue9v24Oo<00@_fZl{7c!_mgYE$Z_oI94kR{mxZ4~#wdD=<}5E%4dP z_7?~@U1J5*R#YD5vR=j%FcKK6nX%;*Nq3p_GpGyc26@z#jgv0e5%)e36xz&T6)zn* zmI+4ffTqW{ax9D2Xuc#$0v+!NVkA$>EX$}^3ki8`O$+8f26|JNIxUhNy&UFW!+8pktQL&0N_?px)drec7}K* zDVghC0M>7ipJncF{sZ6^wkG2dFqQdfDe>oKWEBapZfCnE>3Pt%Qf_18W{7y&A)MLy z_Uu1}593K0e|bu@zmg-a8k0mqlHkbz-FM?k_S)`ILiDQbchxmc;3O?a<^B6){HKZr zqa;#$tR@&^_yxgI#*Hy*Px;*veQ{a~U_Mg1<6hrvID5v$YI+f1TI;!J#A(v;f18Z| zT+uMj%mI?pgfN~eB}QuDfG(ygg_1gn4$`C2TT1s)+A_dGn!`hWj0Nnm5^lxD{mbn5 zPi6e4&g42QnmKBs&n=+U2&Bd}3h09FFQHF{nCBDpi2&V48XeSg?nD`m$*lo=x0z1> z1oN-5<3E@2pE?r;A0oA*9(Pp=>V%mTMF3sA5{^ieB&30&sX8g$2Llhe>ORt5FTSuB z0G_Ez!-vd`pNJLzbs7IDU|lQX&unydHP;{kAd;d8pbG#yLl_bTydK>^O1CB)Z~?vk zTC?99&IW9!>#v`1{6wty7s&We0jqFAEIrW@niP0~0{{_V;Q@dyfUp3cHcO}WD=`7O zH}q|ZB|oc^f-3;-{lfm*)SSOwP>|Zvf9=H=isI9PyAS;;J=*>LizyN}kwVxRJ3cJE zK%v}1`L0KrmNQZ`nwExN7p)AEEE*SoDV2w^Q_x3$Z%0Q})QV@4?Md`u6*Z4!A_XG@Cg3hA96Ah}Tq_` z{T$R&K@XW4q~hN&@ZMLS-~6wWz^F>Fr0<{B@Gkw;f$fVuXYKUe&p}?0ckTaU)-ZbZ zpF{sAOx)hjFLTywx_&}!pw2quEEl16O+?~9T-;Tz?*k?Q>f00k_xkr|=HKAuXU+e` z%>G@Z08rr{VfH<6>8W6Ut^YwpoVmb&1608R-w$H(dt*Pp`ClV}ps;tX;XSgwXq>9) z>3YcfS<%cT23n}cmTyy|8sM^V)(4jX${)$yImGDABw!FRipa5hy z&z!F;B4#(tjkVCBo76x4dY%4cF?A@u#XdWr9pqa(eg3)yCf#P-`8;-G?P$6BwPOMvZu2WgQ%lJJMX>MR+~KdmY`*W zi@Mgm*GsFmd;R8)-O%{`YCU@Icd{z4%_^&6uK-kX;Jq%`=#96sSPMwZ^1WO!H2>AC zX0&2Wt375LU9-V4XsuSo%sqbr;ZpIu*SYK26!_*2-s(g5=vrPZC`PPx| z)4y=pZ%(*%vel{X-W1h8E&Kd8ktg@BJD9V=r1G{_l;X4BM#`;qzc=~j*s_F^>J9sO z#@AA@<#tbpkLL`HWS5l>m5@Y1*WlSjRl>H6?CaRVYxuW$$eC*wXFQ~Ob866JrA73i zV+r>idouUFd#dL40ogZaTd?*BQRKS-bhJePiF=8{v&# zdmcRY?rociuX<+gyWSRYRx^J2@pT*JOEr(Y+t~dl9oZRzJj#)QZ*?+2^_RZk z@75mQo^|W94T)MvNJ_rH{RpnwGJNAZJny=${KiP#y$Anj z*QsCov1!)GyaB0IcjuMq=DqXIeB#Qyf-d->?@Rr)+3(N;vh;a#zy8Ue=9PZ+&vA>p z;_koO>E7}5@yzY`p_ewyrxMWOBXi{QnX!-B!e3ZpQj12+TI!71H2pG1tI}(@uWF!a zJGZ`Z&J&mCwZP`y=a;b1|J!)+mjJELM9iYxUn7QmR>xmT9sg}19nUe|x=^a~IRekv z!CR;NoLbC?<82!I@5z~lhaM*#k|(AbE)cGz*Q{4F6}EZ5NnfkC|7UW*>dpU+Y589Q zQl&s5Rbgl+W~bNnKPgcb=`UOceL`;ZIq#p5_S?$wGn~%g$G;a&%aNn~&o80qfsSvg z--7SwR+Sy(i~824K@$%cZ%Y19w|NW2>dmvo^^%J@R*FJ&)vqo9|t!n_thS@Dy2`uH!i<1Uw`c6s`#zRCJ4umPV>3* z_PM3_rj7pZ$@^!!?H|jTq~UtjQfC_4pF{dTHaO<3V9HUW{WDu1|3b9*vt5I~aHsfl zZ~uQ=Z~tGfu{T|VN>1zoS^x79)e6H;=_^QMXP1x&O!Yss@Ao;y`@fdr{U6zo{%n~4 zPQwI1ZDy~#M=wXV>(#=?6o#rT7e0khdj2IlVL?dyAI}BhWXR2bUuSG&O}lDzL~t_ zt-6}|_ti^7mmi(82R;)^OHZ**@zQ1X@L?i%-W{Gh?`iNrrO%Vaqf*kdTj6E-=b*n? zM{)l!(2wNqiCk8_WS*wj;bL*KI7V4HFW3zaMn6)sl_Wc#kq6F&C0x6uVJaARe3+tj zx(&2$p5cyU!B{y2SpXLGA_s!2)XKD&@nq=+=_}{kzTx|DGP1sY+ZSdQwz`VZtcxdj&H~Om)vRnP~o#b-p zV7RR_Nh>)5IHuOsAB0Y}A;OtARy*|^+w}2sBUx=zFL_!l{qFYQ6X?o-hmHW%_i*D$ zWM=(T-$|_G_3B@H+BD}G+kjHVGxmv|h)VLpMznI`8>Bq1y6&B-U<+4bCaw1EIW~{P zvUY`(`um$<#P@^1)0BP~phs$FP$?ZlD^V>&kvz&IWg=eHcnvK{HQ*c^E@Xum@L$k2 zsg;)UuOY12XnzUSYzm&nZ9|AHr>(yL^%sgNRjT~!IrOZmuo8MbgYOe$R`u(2Wi)3P zl{Tg7jGi3i@WzCAF|Eo?ftl=03zw`dX#Kc4s%4EKt*etaB0Q z@OGjcLs^@+yc5@lc!Kdh9f;i8L9}2buc$KF4EgvWrxT@ea4};sC9E5c2_u`?Y9Z-C zWm=f95q}cAOmcp--KcVXumdP`xi(RrRfA7cVMYZHh>KG#JhwBFAEm_8@PXZndrK}o znddy&VqU5RC!hs)%9oBD5N8BE6s~(N*Ubc^q(7ta$zc%4Gu&HjVBpw|rJYa9>5ofl z^z$4l;kM#Pxs@itMkkWzk;yJm0iOaOL(wKkZ{>sY&Q5aO7ez0J$Ya1yPIcKb9JdqK zTaz^VrhRIr?9JF!y1_~(J|f;XJ9r~#rgV3%+DG}EZN5Fc%zA~E2i;3>N5}|1Av!<3 z3IwuLWZv-he9)huWX%uvuDnYBa@C!=N6Ms)ucu)oQ(tL6|5>Fl?S+sc?#<43ZmYZ# z^tvlk*e9beq$^Z?4XRd;B~v~;p9x>+ z3DL{P?!4^DGoa9YO3Xw=(#Rd35ggH22kr}xj_Lo+vCM0t8Q^E0W?HCu3hEr^2H==u zLzmbrVu4_EGH|Q~1TxA93F2M#rfD*7v#j`8{h8Now)B0xZzqjxx3xJT<10yKCE#x7 zs!%j~P`_J-D3>1s4%z{#wlXs7nkp0*2zTkqJHhJ$&m}kYrsH;#k8b_walaYRS1hEl zt1{JW)AAu@m8r_#=lDtn$#S*zUxJc1%9#dJ`EY1q`d5v1WuopQ z1Cd8BkGRMriQ?<)zW{kHstY`uoYNQx`&lq-%%@p*i~g82$h*7X?rh03T#bmzs~O^C z$7jYIssf8dxQK2gMmh*&@C74Cmk1_L`#RR)Jd-)S=^KovM$s|K@$;VUEZs+`8S)@U zJh$=j#Fj}!F_nE&V@E)sgub*=Rw`|?8gML|3uA@ zR(5<^{*7TBH8aTzC%Tf_BG9fybyMe(TeGKz?G>1hy|+fjTv%@N@f^(R*+qFiq)@et zbWF>{*{B=-!~z6*cH0}0!z_ilCNEaQGcv}Oda2J7YQGKac}T8u0J4^{-hE6LAFmg9 zZPPD7p_ii&4!R_j>snEW(?MT#J^DDjdwp3Ke@jRhlC0D+a5kwKddhn_l+~*N0-XpA z#!9YRlf216{x>2KchmKgKLYF02Cdh1N(^o6IS?W;rcVREe{o*o6^!M(j4cI$Ha8g5 zM{ykunNB9o$kATfpBr}|8|g=LT>y4|pNE}`G|7NNDqSw8YJ;)b}k+rm(b=4v*z5u=0-8nUS@yYiA)D$_p}_${sZJCTZjc zLyToX)tcLh7evoQfhOxI3QR(Pa^8cFv#89#r>Oc;UUt)$#sF6I zT@<&+K;D*n`i3_xhy&#WpE8!UJN|K(iEe?Hg=NQIYx=(1ELmzLs$YY8Rwyz5@HXAd ztW#$eBcR6L%{Msjj#`;>KE!;h4kp#Os$KXn0C-|bstz%Xk$i#}v#)Ln;Qr`5vcAfd zFQ?jS{p7qw24fQtMq}z-0ARy-2V?phw|C-ps9p@2E7;jH(R>phX;L+6$s&khjqy>e z>DHEWb5aD*Z8&|1d!J^aowAQm{5c&Bb= zHmfs|6=Sv#uglT-4GW}Qxrmryd?6B7deH2A$f_71A}mn?o*zfUY$xW7y|}wzF7pCN zO3iX1n=94#@X5S>A}2yBZvaaBbhvKX{{7!eWf^gFp>)lF2WX4Xcj(DJfiw_GCEr>% zRhVd$1Jr~hVDym%N0UcFPIDXpL~bH4eqzYf%MmiPftaq%NCp$q3>jTJiTPtM)E8vJ zs_5dWqCChtB7j|0A(C`>V6ZXp6x#Lf$vX9c*u*^uK}3$Js>5#!-Jw$bz_$R5&qjuq zHqPV~QX0G&6=1@h<8_95sxU}VT+_UnZ%>;vw?C%!gLCX1;R$14&lIb`B>Jf0)w|dC z#`YgyD4Vp(2!0U)6TKe2VfcbwWvsdg6xtq%fa=EB4u;`N>Rc>y7E1e)cX9&9qvdn; zbMpJ*FE@QJ-pqWyiCct7b3mhvDVT*>M|hs-wyW3{v=0aZ>Z)IWLLJ_pS`rT!`nW(Qo&X&8sPl4A)~^c_)wEE4G8Vd zf(y3-9Tp36M75!akz-d2aQ!TK1`ZQl5Oq`fs=xlckl&t!;+ZW*kYo6-k`bP*CM%?D zV}Z&*E+D)s z0iS6a`no61+FS(?Sc$4_+Gh6-DAY*CLR7q<*R$_kB#4`u;zMkR9%arm5({KZ+Y^cm znPJqb&8oQE1zpzY0sTBQM?M6h+QklmO1+H${3b_mZhuFzw`#P7XU>9_Vl||nL$cEn zt6Bo#M%fq0oFl!XbNf5f2gAtB^k9bt(aw)3tw3|)gAst8mg_-$?6(zI zI%&bBiC~~#`2aGB_AASm%4`)h8Ok!`?=MDpx4{=4Y85!}K;PIO=+%m@#-kCJM;=S9 zR60SEA-{b}V4zm7QL=AAW66>>7q7~r^Bblj{6)gI<8uu4p~*3Ttt@zpWPuG3CHdL% z=(Lm(#1R=#=o3X|2a;xzIKsah?69zV9F%rFn4LM@#lhWQvgtBRwl^iN#z&dcrZ z-c*=!oe}GhH(y2mJWmz4MbhfdMZyq(c0n=s)XJ)&c7oP32Oh_zE68O(%N)CJ$@AB9 zH%2bnTx&qLMQ~ao5k75j+=9C=MlL4jtDNyYLd^Hw9CMY>>xl6ajV%TNty^W_CoLkm z9)^5Xf}d5iV2j;Chq_KefkL+L_1E-t!!BX_{Y73r(W-|G6=mQZeLYLqSG+5?FlibJ z%vblZi$0!97A&WvwM$}$`e?})NwhH&z)aV#S38VT{^u0A{j^O!r4o&?zb&@=Xv!X% z;^kE;xbgZ4O=B=js_`DzzKfdij2_E!<%$a`+le^xG-?N&xv2mp@E?D1_$h|$RuEdXNX%%CfQ#13~U#}u6Wc)fL^;^G})(d$cq z0OaiU03-yyLqa`Z%D)gETj&r8N1%`AQA5B?r{fYWPUfy`_0uL9o2~Vall*`Z+{iuI zzjYe5uMTH8wE4uxOP5voYGu~PwZ>Hmj|)oFd_h(a(_212uf5F`Sh8-@!I;!N*JL%^fovSVL~KfO_)ZYF;T<8INk-)aPJ-Ksq$AKf^N8>auy3}wFQUQ% z=#HoDRS6Az^^OtlAmYi!)9mx`=$(t_)<@FTY3M#c4CO=efmI3O-XW7CGJdTA$u7p2 z?d#M=R_ll*^~8Xl^py}E(b!5MXQ=0=j!}4099nnajn-tz`@X#`JVK5G#!tX z^F93ZfGYQX^yNr1Dp@wlk=3yO21^9459S%#H60LvkENNoPh;;pa!7{#_Vo@{&nK}( zqjUHWrWuVu@d;+}W4NT_=#g8~?fOE#ejlmVyqit(+kp^8>I=Cl zB3xZa-cA?5{V&ZdZ3^T_^Gp6o88a1C>1J}CejoD6B$1CsO3Hheo@}=oTK%*oGA&Ri zQ@H|1ITg!#N{+stT+5`h)AsNZT%vWwXw{uI98sxAjy0L52*H6m0K%i!HgMH`<9UN1zuz zFwsy6i6*%O;SNvj&eGU_Z83A>m1xNRZ=xdQPt~<_eKmZ*`Go-?Fx5FrBwr!7p zxOV8t8375m?PQrSC?t!U6~5;81G_4IMQ;83ZLjLf;qGN2B!v2bOr=L*yS6^x5IeZ% z7yH3LQzmAJO&6fglC!gqqwI!7$j3?NQ#n}_2-Gl>g^e~I@42iEZE**v|P%$aCNc~Xo5X?1VlcU2S zbY!G*edmkOZ?)DBfuqmtw} z!4+|fsI49MTu7IdbNx{IKO|tf(w#E@4tOJ9I!4BU+MLllwaC zk#tyRgaG(Zuj0buh$;9^t;z$<>4yyE<4mh_j~}9^|!Ut`bqL3nrZzl8Oe(es`c1vX1rZa`Zvq9MhVnYNHeXJ=nTpS zz_~BGCAk2T%amf;lDrNp+$nn5q6cDN(r$_Pxn$H$5!U$ z+A3MZxYb_P_d2C5v1C5c4-Y)A$^1L;43^^u-+f4OwPrieWGcm!$Z;e`-zadhLv3qc zby1s=*Wvc^_WFB8gY}67o11zS;8xpQs7{KBy;2os*dGw<5$0wXsxfhieIY{wT2^e& zbgy;ak!xyxE?|-TDlYvTBq(VkMn+oGgNbJQGB~ydWf)46I zLob!wD2TadZETSmn5&puInvH{zgA@A1{Z478^T-~uXz70S3KcqtMl5V#C6yL>8xSo z*SNs~p1sI;e{%>na4i1yuk#r#sr)5@8iLTwSOF@{F43ZeY!ZFRv80&>4NHzSUSOT^ zsMS773BKpgjeFLytFK`RoHFiBY#=xn>Zxl848zBoAub1F0JF{I`s9y2dbz|A@DgA} zk3aE}VTMrRFBNhSH@BqIj5NsX9Ya1XCuJLKVLm@fCfO0 z6ea*pA%$9_wEnCrQ4!q6#?Y(~c{V z1I%2Lq{o}_bm`}~;tAz)^Kl(i)RiUhoJrG%vu0zIFgWd zsaH%mi>oDNfL}c8_>GY|==RTrDDK-1p(_EmQ_*o-qeQE|esv12rzJjWN=QF6^%8iN zx9>sClzE+Dys7-S_yT=eMr;Q_*=5Q%+xdsJlxRWL6mwTVY`J5QA@1?KT=uPofro(h zjdQG}#_pJPOl>P_gI!kKbItisyRov=AuH}ns`m}c1M70d9iw%Z^}#n?>-PdOKi|~< z#P{IfpHjz;Csx*fUM1JK^)BzdZ4#sYd9$0*UJ1tigA z%p3Q+f%Y!3()>D)(UNqnHd7s@m;Eb`dRd>L|Ld<&{G)04J|1rM=H@XOfUd&bxV<|w z#=Y9V(Vs_%CM+pnE^e6R^&xc6b&E67>8JoK4+~9>QmQmH0N6=Ap{JE+b5Jhc!^`GB zBsBMFz;+o+!82OGgxHt^G1>T`R0DCUBSvPD<#hXlni)3p#g4bkrWbu%KR~cCKjU#1 zuu&m^-#ch_xZ*;wrNXbTrev(v%}8S*;htCCYO~6XAEzHfRD9uBur)rXx zQrlerBh$EboY!b-e=*vR0$>K(F}lvR-qJ7vhfoDv|JmA4q-AE3h9zd^1uqGFDUx^w zv?{V_)ThxYJ~v@GJW~tLy)HFM0jJw5O06{e<_`$7RE4;KqVy)pV^aHP*rJty7inn-6bp;9#-n*a zn949!4Ro>uICONJF^2hbj7`E`&y=IzIg%cYncrNh6dd^mS4s&q@m7y#h30(l@{L=l=3?46`M3+^(zb#K9SYb zj3`6%bCW)sAn>MjYPnGdiaihduoCnt!p{xzyH+0tv%aHJj;d+EwnQ;Jq>(b75W$K& zI)A437Bkv94zF9L6u|C2whqGZ)A$|8IUWg0OW>zGuU}i5RQO2mIM_%Qtb_m~X#F%v zTrR_l2ivsWN125g-sH9Y6ZP%X`i*jKZND;1Z~X&(^#`5y=|!4PX-jPWa5Q7k`B%km z9u}17J(Rkm_RL(}ypnbeSiV(GOu~INUymXp$-{nlYZT*t<(KQPe(X&y*9o`KcSH@GT2B*i9(=}1`d^Xkg#q)Za zcyDJ*78~jy_%3<>Cz?-Zho#ws>#(^RLh16}3D1UQ{d!x&L+ClWd9eDQU>eJ!4MNiO zTn)KJx`XW%?jD&i#E3vex3c=8s}%O)=1td334;>|%mRd)Z@)95cNNpka>3E0sj9ZZ(9K z7oSB5oRx+AjZu7>?f|C#E5-WFu-^8SDqfj|KYpC$+ps3P*yJQ@Uon17mafk0XspQcq%XngFac?hVw z>C*)M1*peuejAkr{2(T?N}O~EC^f2&zfsb{W8M@YZ|wR%p#qaG|6uX@%A&XhlDSz> z|A*ifNLfBg4f4;j#m@_T7STIsF6EFrX#Vi;HpJadt#}5a=`8;s7KkJ1U-jb z1pK$!z8#UN2^FJ$45|N$`HIv0pO~z$Xv#xM*=;)({DS%z7yH`(u$PsX`2EJ22GpOp zc~_d*ia;mv57M>{@h68Q`pgdxBLOL|CeRahKbdLpd;p0+g|i0-MEu$EI#kFFQ_pWw zQg3d%h zHQEJ+sacW7N}OeYhYtnw?VG+xCO^Nd?_es&;k9=Rl~=j^y*@CbN=GODUc^lMZ#F4> z8y!BZ8u?7QeGuv<(NEIO;W_Vo=DJ+nGPAqD>?i6u)fepK(|p^ZJ#kw`cK~v>8IdNl&Jip z{3gIqfMWywxNiX?;GJmtBGNyfWSjQV%L+)%AVIQY4*yXJ>X9sg8T0DxWlvV1(8Yr9 z!w`i)<<^V1LjOdM`+ArqZApVbj{-dbZF`wed_atD7M2V(kUDOqeA=GrRWm))*?RSD z?(w|FO|EY!?&!|hoN9!mb;da!ur9#M2#Do?dRY%|E?Uijq~)5{yi5iPT>PYRZ^^sn zUEsWV4P5?a>X*ox{#LV92Px|vnXqQ2nn z?4mw5S!M=HC|{+nVM#q+LF_|&cx&ca#W0Lo>*QfyKt1=?X*4j$WRwU{*iIC)b6X>4~Nty<=XTwgC*g3Fp6zk zzHUjdBF>WtN$1g!=M+er9qP_mO_(}V>8J#nJc?YMg&UuM*76dgIQQ9j8bjnU?95UG za=fwAd6Nw|S}LyJgF*~sLb&&*SX~D2l$?6+7HSzjb+1`*<;Z>qA^o}uhqKv5X&CdX znHW7%mfD2R=cg*SWSv34Lew=#tTilB7m72CvQ{JcdERX1`L<0Nl!h?!CF2)g+771+c2L`1hR6;&TJyxM&*5U3?!aF2Yy^Fjo*MucA}^Ym(9?DMx{pwb$`CzJ9VQeN|h5xp7cK z-s1)Z)ze=j|2%7L?{$&9LUNsNdQR>0iU}a>$8<6&BV(K+%h3keYKTl{b!&7oi{4h> zpmA8#EHL2Zx{cuX0cJn4g<{YyxTpWH?tz<~;-+j|eY7*XTTrQovY>G>CrFnw#$t34tsiRNfiR}YJ`FhNyY ziB3uqq@880@Dt3x@o%}>&V{@eE!3ctwYj8pK}UNl)nMXWVX*mG7|ov*9Pv;UU77y> z*n8Korq1kr*qP2~=V#TTt;)GIqt;QRhyghS+EIy$hoV!IgoBksgw3H4!yzHWj%~$4 zLQw=cW)wNJ5F&vP0t9MB&Lnc$fdGku5+H;KA&Dd=yidf=h%@uP|Lb}`ydU1*{?d9~ zB-wlI=UMAsYu)R9cx+8P%+D}k#g?Q#GJAmjLHVQVCr1@(pO#$kL~E|s5;6}3Xu9rC zY>bFmBd7zO;qbTl()EmVKVS=1&3~adN+Aanq93H8ZJo5*1G1;xH>+hZENE^lt6)y@Y?+BebRUi$vTo~`GZ z&Y7mO&AIGYSFH5;acKy3MpZJy3(2ZTH_;q|Bk$tg!%EAymsw|TXGG_>>^Deeq}Y99 zuGH%)+Zd>yjG8atvqF5U#!F^U?zpB?Y5S&Mdbsp$Ws7`edxQb8Gi}v}tg8Y&o}~*V zo%h}dO5ncNh7JC#`shNl?Vk9C1FfDL4r00F_usYM{VGSis^bfmnmL)L{}oxA3fw;! zLWs8NJ*T~tQXYPhYr%$73?WQmu6D~%K`yW+?o(oSypEJLBYm|#RnAZuOpKD7*+YSs z)a9fq^|l*5!h;I?Z)XP6efEu+4eCbzu@g#l%i@?46u(<_Z`4G|^GCl-vhBQHrJGKNh?JOJ;#CnG6@`G^Bju6|B z+oM}_!iD;(llIQaNo5opAAQ@NP@ZCIUllo)nxV4YXG#d*w8RGyN{KAU+Szo<@D}PBc?rpT}_RnkH6*|s;gG~&%-`TOE+)&#D z=c(HQrWM^{)!!h7!?r@HrqHnUV^KE0?3L3jWccMibS{n~_lFrFZM#pZ|^rCnnYRE3#AEoSWi*S#;F%WW1w0$W0%or-|I@`Sny|CS-=469( z7{B=%MP{iaVexLl9b$Z%eVTma#c^_bQf%(zaK?<(huF%fSumSZPR5~VeNDljk#|m+ zgz$3Tu1nb6qhYYr+rIJ)*k)JEE~*)mYMdRhhXPnaItvZDiRwJbBQu>9HZ3Zj!@K5Y z6UsC6sO$3^n?hw${KyR?5RVQ1c|xKXsw;Xm$>W--ilz>aUqmf@>8m%@Wm%JX;eqzJ8_ zbv@jhkbGSDh37(13250PPj`xb2pa#iXf|$C&?TQE00^;fY-Gl(NgYu`qqK?2s?f0i zU5)Y|gr>dM@Ys?8@uh%0MdQz5#q<~@>{J`vaTYHRVOXK zci3YG#N=D~kkYqyk|4jGoNRN!3rzuOd#4$$-f$=1nGtO zoIu+QumE1C0Xp{P$ggcZHy}XY-O2L9c&C*$FwGX(^l<#` z$bLrRwHKTCIZ3nZSbZhILIymHMMS84qr0N=nZ^MMjyOcr)!tQEU9hZc5sydNi1oImdMw-~n9h~>akj)4ie!uEb)PhAq&cTZ8;Q{ib zpG%OM|5X;v`ZjXsY~}tEf0tNKMS_gGHFSaCES(7)HoLmVQ>eSEYDDnl$35uGP>Vu3 zw1-Lwl9N&#vRZbt4#Rw3nD+4L9%%>@)}14r+{ti=stV5gtBg1!$IC>+7Zy~Xg8f~R zx;5=clnc5;i_Ai4yihGYu!%72Zk#VxN|td$*$KI5aS6i4Z=5IW>UHX7d6512U+V`j zLO$FGPseI}?LDOhS5|^tsAsh%re=-LOjdp;w^{lqf|aw!Ypw0dp3Gx|$I{?i>B3*U85$AE%_G3}hIc(e z0MgLTiHQ(M`kZuJDUI(oQQn6WU0Hv23F$~)Of-hLE=?oHi4m(vu<7EqVrKuxXm4kz zb-VUD+di1et(hg;*zihFLjLJttlMr@ENjNJwJI-{htUkcf~g|Bb1&_@EKtFlpPU1@SdmUhjHC8r%kUoJ zr_=ZEv-hB$(gb-Duw=}rIe({9%kEXq$0L&CFXNQH1P z!w```El{;$JOkr_Jj!K6uv*Jgt+^>O>-L+eH2@vfz4tdWt_G*Y-+CB3i{ zw!c;Q|3y#L{xJfQZ!)bkw2OG%2zEVe&_RLg%aJDEj0szHgRK(D2=Y_prxgnby^uSgk2D& zBgby7k2%LQ+B!u3%4*l#aEk4$*qN+Z2H5Ro1BRTM2{-gMZ0fY|dePjd4#QB7Eh42@29v^A@YddskqE zsD*>hgLhP`LgiN+t#DJfrB*n~s(0g7wm80~6efK>oFk-Zi>7*QC$05spOv3Vw^6u5 zxDK&~TfO?4G5Qp-*ITOUtBO?P7|>29mB$I5>zZNb7=Y#4N-Hf?d4c=tcur{zt69Rl zUI2r}_p_FKbm@hy@2W`0uXSFPyHDW zQ@a{;+QBvvt=vb7Yef?)lqQ(*{nH5#gP!6J$G(j4XcJLKWFx7WysTYN7ixE|PQxBY z3Qq=6z2wJd=dw4>&N+iM+@pDk@u5fJLl^=MQu7%=CCG{xI_*&%4sK*RA(nK6Z@q+) zyU1dMy8@&OgvE_33?{fhwco0L{%va;Uy@<6fdR6$+LQ5rdMj)@PH(9mnmgmsA)PR@_swtk4sED70Jdo?&rv$lbgH5E9F54gGaVJ@ zq5a-r;Oi#{Xn%waFq4;X0|*wZ#lehp{8N&--A(~5pA{Ug?y6F-TWWZRw43S!dm_88#KW-N8JG>m4%Y-lc>EpS)A zjQcY_O_1^R^3KeOX_odJiH>9?q2UFTOHzkDh`mAjTl?n47W(Xcy`{3Km^8s{MBbtW zYMIyfx<;k0Iye5Fhr8<&14jDkzQ4+p(HCa1 z3pUMFc>vVG1UA?!#{rK+@k13z65s~h5_#-qOH4DKhAkYvuh0_&4Jua@Yp93!^u9{0y|=bV5HqdFI6~goOSo zPvWtYqBcVr=^hmcAUqaoN6fP9U%Jitw8V7gX#JaZCN?8~Lq)MQPL^l+JP%n5Q&nTc>`!IRQY7>!iz@jq$e^G)b}=r-*K3749P zskKeNmW`H>*e%ck!QN;L<8~s@(B$=&Pr9U&uW5_RRrcq+@6PY}pz841m&3ikDos+@ zKQ-Z*D3#7DWyD?;<=k|JKB2`6+`2=l!>1)a{NadLzFY<~K* zU2V4y|3izFtRH|mDiT~Y4JQ=NdjP-CgeA3#gU~QIsf$nC=^F zlWW4QPfWlD{E#U*1~YdV0rae0=3Euq!_{%BXIf(doIaU>+Tux0qo@6VGhMYx`Y0!B zbizuOjbu4ZWWXeh(v3A<(kxpLxo9FR1DZ6vc7Mctn^J;N9-?b%dk3wY*5BaHB~t4lgQHvD%)>vsR0A}IeV+X!1?(kf@j{OB^I zH&&F3XF?xU`|oDKr%4(>VbdZmi&$tRSX+ zmX~zm6OtljrgqpaDqq5oqicNA@ETlD3NKJ7vtrsiI-p0~A>aW<>I0(JZ(~3gPr&|7 zB<459Pq_Ax>&`;u9-0yYD)j?B&2?J4mJw~anBch6KM>THH(-DL-J7Ar-%ED?^HbAn zoY1zw`h}Z&&^AQ0ifJ`$ks@ZKTm33Co;id6RMvy+cw`_nvi9C|}rP|pDcMvmv zS1RmYt$b7VYE+EP)(B1uIm%e)8wuRQ@eS0}g@8~U#vlbQY#p@+1F~nK^}`))2@8x{ zSv3=NBvuG;C3{gA{EWCUs8#F&wM10ITW5Kzs~i;h;!0GE#SXk?G?QRJxShoWUg}4V z5b6K4wd>f3MUl!slfO8kI&Gaba455-kbsKp(JIC^GYnK$A#f?_bU)*2A1Ms5n`J;D zr>03k?YoVBMQcCb_+Fv=naGE_1D&cZvrZK^E^<_Lqz5al+g` zubp#ZlWrw==(@{BaBC(yps^#RN5}M}~Pr z^QJ$t?TbKJy`LXLpCvGL`>HE$F!)6MDBky=Oy%+2J8x##4c8HyOFh?0P&D5!(8HvM zB!S2(ot>gb=wfnBHmsvfLU{u3dSKtR^! zGL6 zZOcgtYL&PkgvTK^|GqdWI6~~gQ#@&lzSV`I4A!57RAd04urdNXf`<+*v4kn*rW^o3 zdZ4GI{9Hr?&yl4KZ@@}xx0j`b^EOLo1#O$Ltq}}T1Ak=Dg1oe+UHgI4&jF-aDRL-g zT@-IUOKUGFA&rhFWsfNBgM(%h^o$Q9*e!&h18;Vpel5F*`lM}R3fE+99fRoa5_aJm zbA{Z2w|3rU?Q}al=WAcggVozvMeb+R7-bGtcMd1x&RPz0W%oD=Y?q((OaO~+vt6tE z#D`LB@i9Pwc1h2x-^Ln|>#e7KOhT78W63+R$61P3Kq-jGfiNsJY{s zDSu6!?SnQ!MpXbsvsDPIE*)BF-rbXa|LC@PcU;2Rdx>+=27};jxK7lfGzD1;sc7iF z71O+(mfO1<)P)yfhDkJo8 zvoBAt2Od!ME8KOJn&9FY>^1+?I>%gTI{kM8Nau9 z6&cHYuGCb+Tu|TlZ`iERMw65VOS?g@3nZvky6x!v+`?e}4_#Er2zwvUa}u@r>V0)2K2FvUZYOMQ zD}9Abvdwc*4<&6$-)-cokmmK3|tD zI}H^w2XInOp01Ni}h64vYxpv_`37Renbg@RvQV^WW74~ zG1t+M2%VG*6aS)MktlmnIB^d#G0kwG9bxjMfmB!v-1_5p zHNFRFzDgrKTi;m9_VBac%a{XsSj6IEo2%$9i2Kgzpz;qXrn9G@sKO~pus9+Zk| z5r!mDg^|U}-o`7iDQJP~gehZYb0IRnRITy7ijVN9xyUO?mnvU@le9X@4j=lqLfuVX zOGHhBzpN@kMsS^W%d!7i&rn2t3zN2vGPxRSQ0g)`;A#z+apy67>3|`_hG{=-(X%`) zwM?dYGQ{4Tk#3XH5091eF^n3G>$?}(^|9Z8cm#%|PDXC&L&+_oBX@cNR&iMVl{S&` zQD5sWh?!qh!wF;8s}ohOiP@~!a7Dz^+J!K`G?E+i`0YH0iM*=FuHw+#!`8c8s3@?^ z-7Y@Z;4e5=J)z{yIu#(aYXOm@W!cS(k(1Y9XHJ|iCgPBKLFLoJ!bJo?epLV-+K-vt z>T{+bX6wBr==`&QlL5NzjosJbmkumKfgACpIL!8XD76Q}h!#0}ob5I1d3>aUwfCoVDsQ!|FPX-pvMj=s%`At!O;bK|3X}Jf`1Nww%*aBOR$uCDvA6N-7Sw943YFvUnOp9t`<^)pfpV1v~b#4(k4#1e>Pa zdQb!VqVOtjdP%rXAobkYQM@af@eU|P5ZSP6Aw{!q84eCejfS?Vt%gU(jY%b@30bjh z=C*1P6%;V2JsKq7_H4$t!rE=D{Q^EMJEngm0jh_DoA7~E!TzIW(jvR0)#!?675iT~ z_@{*W!wqS$)3s_b^l}fVZku5Ae#(R!meyS1pzRPTk_J0#R0+4r{G04DaD|lt&)I`^_G2e5`70%7L`yGUKo)14;8=U~eu55UXUy=LK_s z_{IC+o^~?u8{K6CW;&!iD6?f7Yht8YWKN(N>jQn|VkYvly>06$Qag;tZ}6cPkL1*6Wp-JfZBlUU zM5Mjgz@~>Ix@)P+34LKpA`{g3y!2t@2|1bSJ*+}DdBEZfEa=o<=Z;Ui(N#H4^Lg+@ z%SXNA$App*0?436g-9B@rh5erFYtlvOu^XW7b)fGpt4tAT2S3mh`TSs98a4LWO@hG zxPEf0kC=ai2^03+Y4Q|mbA%UpQr-rK9r*Fe31^J6Be)3^M#f96b=wCFtS$LN0SWh+zVVi|+LQ-s!Qh_%yX=f8p)1VXMq&h1qRNuv?z zY^PulhLUf-850YJ($@|gh3=b85z-pJFV`OtK-HUz5V}Ex>_G-^txlAz$+vU~2E(2^ zK>_cR9y<6FrZ*_f2HYT3i#xp(g{quroBMtL)~C0q##uUs>3JSdKD%FvYTX;k9xX!_ z6bk>a?32AAn|;$7+d|>bXeQkw^=Rx&2G65$3H0n6T0oPgDR{I=!#&FMZsLq=@zng% z)_4^@K&@l`ZdLr=p%J@YjJbuA!UDCEUad`Zv87|<=!m}CnPDG1e zO@y=FKJME*$=Re0={&j(8+P(>2?2l~4g7OCJsquKYpUFJ2~m=&KtvyEaQcBi10to6 z5{th*tDC92soJ&g=Kw|}k2k@Y!n}bPlC{ez)~zMU!_xiR?ld zh`l6s#UIz7e}mERyleWqz6kYRLnQTF`6zThi)}rX&=!+LU00sTh7b^$D(Dp6 zy_6|6@)Ds?#$k__d+(}9>|GFB+YmQ89OlxibNOjzur1jxLKST+r6(tS4=+k$Dqh#6 zG9{i?3Ed$Wy;0UEB9bpMTG!ngSa+c&SMSP_vazziqvZbg(}l-sI|i*#>%Z#cC1xZ=OmkfSe6E+sYr9TT z#Wg>;;T@oH`;<#4Y7`%o@fK*U7>3Y7&wZ!l`ddx`G4Y&%luvS%Vkdm;NCr<`E{_@w zbj8Y=jO8aUjI6HB-zS#|mD>)J`yA9{5+7=Taat%igme6fgqdLA{^!PnmgZHnmnmt~!Y8%bG zSB<)ey(2BPHsr6e#8*tJyeYH1yea3O*nH1hx&&sCTG|rQOmGy&2Bp9x(;mrD8KGdV zO6I+6>xA`K;jp*{t6z_ISFrmHBcx$nqerO9odwuE`BsdiS_8+QDuUb$QjC1d@ORZI3S7w50@pgut^^gR1 znkElMGb?$g0~EJa{>0Wmu$0BRg753!_7Nud7r>LGV<2up0x{M%s5!BIfP=)yQzvnd z+z~VqA0HWV4(cc%oPtnKxJCV#PIJgh4@_$Q$fU3C)AH?OIc(g8^9Sj-*UKb4bhU zPf{z=O)x?gC^*qxj*!@b3ak-3YYF{M;eJp$PuSW^&fpnx?66_(I!#-4kUp0s)%qA$ zweOVHCs)a@RF_umaKR9GUi}^{1XRDwXbY z4Dee2N&ICZ*U0l9aNv#UfOh5`fIzXvi)SkLuZB|N={8_>g9u3L*N&bIk~_zQp76#Q zX)+MyYSn@vs?TLJ!+}%YNou|HszWEa!q!^4W1*+SrXoO2<4Y!G=cz@NW(3;KIU>3V z6sH=)f!cUBR`NrFFDf9fH)q_^&ONI8cF9bBT#c5zV39PXm!q=Slgdd4&Vez^Pkvv8 zkKZbf0`WW7{OSvhRo@&**GFWcG`+pZO+%vAf*MYn=-L8Je;qIuNNw89LnQJkmIKC= zyuK>D@{&zj%y)u{LL=;(7Me)!5%N!%Ml*4850CuLa;mo#nbDuge;~p?Qc5a@7ixQr zDBG`0fL1dJ#;V@io9&r`~#RnP1k4Da)%x28K@- z)3k4yOH#+dkOS;jgLPfJP(a_#NJ^{QGJ9L!tLOS&F(H(6=|MmQMZ;cJeTpwS%41|RpRzF}#>QtZIgO-q+&;lcnI&Ok{i zQy8q$i8J9Fz3L4{`**kjOdgfE3K?p1p0vm2&x)Iw-qxBucQ|d@PoI6lz&Bh%0frsy z_K8m~G5HaRW~_mN26{z%I-trL;I4cn@R#KL;~3@;Y5n8tuj1`nBSE0^p940C+nJh* zKM_T<0~mB1?uGE(Q7vmkVteq-|dIoXk{;A{5cN872GKn}U@o0AH!f z&`E3R_Lc0IQ+m5s8R-Gn?6%$}6l98*@`~)TUC^Na2aS1`NqEA~%IH05CQ}KvLo(sD z#tJ9vzH@vwQ0pfLDJzdCUPeZqbljfQco(UlF4R=^XAP&`!tYhp^OJ>n3k|WUtvSL} z{Zzq z`NnNIh5-G3y6MzayTvXtv|Dbp`Z03aY8N4 z$E2S6vXvG0f-fO2cJ(J%Nbz@~{P)jGtE;tJ-RnzU?=w%~nb_lZr60GO0gp)7#*=>H zq83ldAz6+^LK;pU)ji@A#mnjuw%=9d@T9Bu?pmhWt`x8sk+eA_LR5#~h+GD8AOnei z2J5KhQvs(WMs^N9SFGU`)6W2>H+vgr>9jqpB*?v~`}C+ciirxIe$Z<-r~LG+V@$XC zHQ}|obGuJoTcXGY!7Bz0r+rj_ACX_b3{UaS$30Ulpnp5{|Si-Nr~y`;9){DuPNUj zM?F)!38oQv>0Sf*+j99HZ0l}lcV{8FPF;mHIg^5}*-Qc#qB?)tWDf!wOZP9d((MOz^DPF?3@}6NN5M@X$DF z4`j;}3rR$_Sd)32oCn1jqniBOOph}fFOve2Cd9fspb$eJxu}yD4vdKlIz^;Ethy`* zl#0^wY^JrHB-KGGvP4R|MEreWnjAE(bp!N^{`I69y{KL6; zIjycDtuc)jxnxB?ksr*_D+xUtnUVqZ^dN5PX1=~;0;tivJRfTfAp@#BDhs-4^40|n&>IYO(F-Tyv7pL|l;j+8>5~a~O>7yV|;U<0Yr7KvPkpkCamsly^MB z$b03L8qvAmFvP3wK;@1e{w4p33aS_WiWrKL5$2P13-9vA`)_DRf|2R$iWt4zvNN-g z-#0QC^VS9+zIzY%adxQ9-$^4?OlvORVg;_`sDNp%)|Z5k7Wn80w9N`-vg(0YnUKK{9wn0(4h#?0z=4JQ zff1dbeG*pSmB|RaX-alOt8IdqCO-CRPd+h{_DJW?nGU5q<*sGO;+PDfhk(?uOocp5 zn-;>#14Xk!UsB=3X`X)c=Mp_S0KpsMfsPdKnnRe6Ur>rz>6AtCqhNJIBu1*+DZD)T zGH&$w1Q14&NjMomr_A@uW%-eUFJ-^$FI{7L1OAoSiqh2$WNUA!zUuJ8NDq*HiP;UH z&k@EN3u7>e?HH=AXfMLYcns-o#e54YFt59|V8C)++yS0!DeE?#G$iB(ki8;Jan1{U* zgrD|;t{rXgaL~~Gcguuc7e*$uEU+w5I^N;f$Sjr=l^+~FK3HzuGy4giSy?3MVb&q%=&~~}vEDAH8$4oXi-jT=Z-CaKAVI`k0y8+UN-9pBYxq@_#pY!o zg~w9wj}RQQxX}WUPu~{rc-Y8T-}2)tr6D6^OP%$Z48g2zv|=2dD*aC3udxtpH|~b* zB*re!f1zm^#%=h)RT!D@aLsm&g$`zK{HEkL-)}zB|1Vvt5lK4!#eqg-iGS`zXu&lx zYH8Zg#pVc;E4y0umZccq%IWei1J`9Ru8c_l@{m1%&BCfDJ4dv{X+X1{ES zy=zad=*I{jWuR4ZD}G{Rd>%TnCMLBAKjrE#yyRbnH@`-2U8LOI?MpFop+y3JM3Q<^ z*}QHAjQwzJIk5-1eRB{Zcx}Tj)P;|4S(=-U3K{(VRybbaSB=9cP_Jm+ZC6(TwiwM5 z3P-C6-@>S*nnrc4lrU;mkC^@Vz|B{cd#is_2)nFHNgz5XES=U+g%`WyvNq~nVoq8_ za3%~s6WSYW`xu+*U<+(u+yWO2^Tubu&VVauyv}c0d6xn6KQH~`>tio_o*Y3KlI_5z zF|BT~k2wW~iN-o(NX#N{3OdQOazG%C4kh273lBIHt|C0G6?@9`sfI3MP34s@SJt!& z0=gmsXNNI29tyUlol_hqcWSq|yL65;3<#=JAA%}?$pq|a94ZIbWurXVD&N;MxB?&j zn(sKKq0QEIA+0B!I=8g48c%R>*5jR*e5|)c@AZIq{x8GZL5c05Q=31cSLe)Vw6d!q zRcnb4ZLu}tQ$cqDcdoItqi-cQ!}v=;LdiH*3$9P|-Way{)gIDEKPjbpU~*Fr5!nv) zTL*-^42p7jM`Z=v>0+B9Que-St9p(8osxf-#z{4n3}0R6 z53DdXtR4UL#gMdM_YHCM_yKu!S|%S7pRYv4_W*fRDi`NJTfbkZJ@x;b`0yg33@OM)89LKN@EndIw9)4<-NH=&gj2B z-q|IuClTs!F%VJf$UtT~)E~G~9>5ur#rk419cH&y+I99i`Hz=Uj|0ZZu?B+~psB$% zgbetWf5Ld?jQ;o#$qyF+-g?4!5B^<922q_x5oAJ`$o23=a3ohp@S z#nKN>F3<73sSgJ1>jKW?FkUO|>;Jj_>URX9<2zGB)ptQB^n~lSxivRPj>XBsTK$7g zvBsS%Kb6TgkLl8zO!n$#oU9?;MnGE3wectzE^rfv$eq^vxNwGgCm8We)vfX#GXYQ2 zRv4_9a)6k!B3ZD4`>kjDib*-+O?X_?TN#Jn!L@P|pM6y2bfmv4;<^cd!hs85_sILN zt@{fcK5e4H>wW8o4mU)?C-aNkT0mvFcwy3slU}6Rs`s51WFW_rpl(2woE|U5TL95H z|ELqgp5qN3W%&&0_9uhS~?SuIsj@tbH?I-Kdwb1P?pjK2WZ zgwA65x87P4N5f~oSW15a_nRjNAwhFd&AEx9jWh%^5RPk1d3J53jUFx>vJ22T`}*nN zNuS}?vxCofSRmDc{T~12p`4a~XVuDw${Bq6m16bAArBLPn!bfwtVpc5O&RT_hKC75 z(84*YSz(vBF$9p@Cz3UGW8(IW5s^cQMaU#UO?Iakdgg$r_+xGLif*v+XU>1imE1-R zE6QF!o6E8=?(&p&{FhjVA1_9HkghiwZ=-G>+prBCPBhWGUU-m#(ht`?IqC^UC3o_! zjVtERrE_MGZpe)JLW5LcXDo+-zE^zY%$-pj8#kgLoOm$x?|b|8W3Ethc|Kj-?K0tT z{lNGRlOFn{uwPRy=_CDcXcToq52+%imr%#a*|JkX(|v_}g5p;GiG|S6#1c9xCXs$Z zQEQGr^54ai{-gBJ|7ubFCzaf{L{$D?N_!CjW=zsc8x7?DbJeQZfBpUc{=jd7Z~tqef31rDT}7|u_1-QI{;!EvW&Quw7oBgC zXm&-P8l7<0XBb7+3dZWnm=9~$kDLAR@r~t_`M(IPfx}*xMfZOjw-3%uJ^l@;-N8TS zf^z%i<;jWvL+Qd_k{lYIya#gFM<4&E-o(FF=x-wMzlpQ_AAigk0RMxwv%2cZI^6jO zxk86H!?^NKwXcRV!3J4TLwWP(jo$-=89!Rq-JQWbFz5gBy(4oMpCq&YWFzvk|GO`E zPkn}>x|$>$ZWSawHVP7Jw3Eo9h-vmEq~U*qTN_Vu~Ubt z!LbAXG?|ICPQ^Xb3e@yh@_b*L>pYn8qw9YeZjFq1)0}l>}HyLz$C5s9(BmAek@Q ziW}R3QP{SKn0wwy`1DoP;LB4ERc{z3lA)~s;J<$HdW!bJhNbaWuVqFNzI?k+7dJw` zoLie2{PBj@Bhp7hfadaV?_RK*9}w$L0{YR*t7k=)4{Y)1xs;*`(8;5K?tJs`)wm_R z+R%yU*dVSBtYwWMeO*&)Jf6Hx}SwJNHw^bqS)%s>{MD~R^fHNdcuAptss29P)Ujq93 z`lrASO?l+ZWN182;QVMp3q2(P-R0i#w?907U*acCe7TUIe5iAe z`t8P^#TWk}b#9R(SuzjMr!Fr)6K;D6JJ81?W_j)1Jo#s!%lpqwX%*ebQf|$5$@(|b zT+(GnEj?;_0nYCbkhO^~s9NP;jhpcCOOLLD7u-%4?^t#4oOJntT?wvde($ijusGLR zCb(glpn;4ltCddCRasMX&xfoHEnDb%C1skaf27cEa8FXFy?nYPyGlVP90kj9bqexq zIq$-{%~0jJ8|0`6btu(AO?g+EmX-DNvt{~p_Gd}ki#`|>eD9d|&AO-pG&l<(kzegl z0ru*p6=bEadIX6t-dUO%zN(?@(&fDmjKGb%;J;CMW8as*8*Khqs( zYEGKMaAO7^1kROc&^-hHqD(7+n=th52luvf6I27dp|1VhgJ7s0d5s-ZzRR=E0ytQ3 z)JX8AHXxe*LLBmb<^4l7UmXHttsx($4L%Q7LJKu>`cn2Aw&5UeQE; zDOzgWZ{!>v24!$_I($ij9S(*XpEc*~gENbct_C5;DAM?}^5m}X&LSiF?IPaoxiR>~ zw&4SJ*gefhhC(cP+}02)I|q{1G3E@-M#|x%TK0rHF?}RsC>%RIT@fr8g4*}imF887>+VW*IhLpn-WN;#4ovr(AOE)cu9N7;;}C5 z1;gp$eXDl8elU|o23XdAb6a{ow6xeKeIU_yNDrR^ugV21GJS5*F7TH20wO~t>?yq5 zxmaJ>vgd>y@J&E<=hzNvz)|o-{q>6veWy>@Va!jYzORqqwd7c^=N^_C1_J9J!rkX4 z_T4#!SJQ^&i{XC4LWc%ZWDUANs^wL1C26igH00-r9@V^N1O2ANpV;o`mZ)9>V&u~B z5bp2BcjB{2nRTl*W7oQ0vVPkbM>Y#GF2BDd)6v~F!BMfs<6-8FfsHaBrGKvY#K;tS z{^tvfEl}e*T@mzatRb=->3Qb8WuJciPvd7#AA2$|GkQMgShan`ww^FN(@W1gQSH=` z(|rgI>RfFZ-DNWVAr~b%L6e6Q%cT()Zzhu??oL>rRZDxd?|ZMC*x|pPE6MnEx6=jtjKL#!24=0$3vnIE zR1{702Ox8CCG*w1p!rpc9^nBxBUTe0k6##!wCn={tu+gzX7b!83_%Jb@V!&ZtdQ4* z+wTh=9*x`otgY+~al64FS}}U*n|f>?m#056$Oiah;yOx8*y7M#^LDH13$h??yuL zAKo**B+7Nlj;;wu5ZCqAf4K_Afu3)3TT<)X7es68=J15{vYN);*=@e6CaOBIFZh* zha(kbi%KL|Z7>`Ie}L?Tk49qK(Gmj)rFpimreJ-3x<6 zI~7Hd$7SvK-Ky_jOf_lQ5=&+3t*E~#(`K7pcTTtz9esQlj8i2l$90st-~1ile)~UK zrIMtjs37|9@Cm>*+AduSczIvqv)=gD4?h$=myg#sGR9VOp8A;=-Y|wpwK0#W7E=_YCM2h)4V+ljLgH%&fzT)TD>fyya>1|-%ReYk z0b*g1#!s3o3z66ERuSVv8+LXP9ZEcW+T76~PD?7EX_F0ChD`RM*nRoZ&bVpc=o*%mk&12XD#40sHY4DnM&_wo;wO| z`P0B>#f}u7MT^Jl7;j;W@!)2Qw_{`129amH1zFCC<&}N6^C2#`mvzk2s zhU=fj7+XhojcB2BC}*E%`O(g=!e0*)w0{L)xFAyW{Nv7a^SI+af&CsI;W6glE_8r% zT?Ev_nGQr#_I%jIo3YHXl$uwu~vs0?FCaR24Z ziZcZg?2KsuZbz%9>W4y^iRSM$q1PwZNJx&2hvm5wLB+^FOO_xz8mxMR;n zhL=nO)T9R)Nz;u{8NqEpKTm)0(E4TmvoF9i9C;|RgMST0 zGZz|9cL!`X`eg7B_lys5ew`rWF@Z;pU)r>YGTeOGVmv8p?Xyq8yZkQdG&Lj7a#)79@Ys5^+%i-l-7>CgU@cAmC2sDH;n9>%E;Z2;CvE?~+16_ZA#){tQsAmFaw*?@SaDV%( zTh=!Zev`s_Wno*Rg^i~H?yS;jcP~DlJ$3USxXtr7&z&iTy8lw?VRQA~-(Tl}-+?^P zwJNmg>K+7j@ZB9Q4*OybX51@bf;p(!pV&R^Z6|zokQ}|aXDR4#@4jhS+lez3{lYTv zj4MHULX5jEWog8H0VEd^{_NbI#y zgdJKSPzoWE5EdceN>~#`Nk{?#MTn4uuq6RP;C=~cOV2s?o;$zJf95}PXF46+;LE!{ z@B2RA_qL1<6W@TkdQim8Q*2aOz&RaeN=20eD?8MhaJucg@0U6PKi)e5MA^mpX22n< z*#X>bK@1C^G*rt@Q;QZ4WNR`r9V-kHXn^<0XL81(6|2533N)($y zP?qVrjhh5smFZM+)WLJP8v>pp6PlB>f;}JTESt4iRpiYO=Tl>+lr=X^IDcy5Y|NBd z?3A`%{D&*to!b!?JzP3a6VaJ_o<^5weCGaRD9HCK3INh!h?LEW?bJNw>cYO6mPX0r z#sUq%MO%^egmaLYKFpciU2#S~{@K!P;f;@!`Q)^}!FpCC?zy_7u=iOZWrhZQ5yBXE zPgZiZr2e~;!7{f(X6Q?FqMLZqW0Cb+`w#|RRM_1l+xst-rXxu8yx81cu4hM`r@U#i z&6J}`@cKh0MvX0esOPEnG$8w@*i94PDrwn9 zBs1h6nv2vE)2+%_;M$8czI_ioP@SZHS!fR{#g3EFtxCeVoqvRJ)a2du>OAF(CaNnL zJH_K=3W9Q#!qeaMVm4@@Kr z9J9q$X(Cs4fn&}nO`}7oH6OxLBseQYZu`W|*fVZk@g$Ry^5WrQioW|$Dbg;g>EnNx z*4Gl11ZprzmgB351SD83;FR7#cZiWA@8b}%V5gk^P2U@j_9?s__@ov#c@lu(htg72@y){IPl2W zKZ0Dj-mVl&RZqG7hgvsorOvdF{UctWV|fCtpd2}6D~28bdcJW)>^a4M^qp#fG*P?(rG zGgNIOw@Fcjq8sOszQ|`56=5J0y(_%`5SlVr0-6x^$U<6EVHb_p@$In|rFzk(8(R`@ z-sa8i0^W4$-%w<4&kJjP9;?4qWv9PPAaX&+M`n}O`y!O5L^W;OftGcLEZJ7Soodf( z-TuISQOky)N70AS`T2rIsfKHF{SD_pABQgn1CjWDsZm( z5=oENpB;R%txDsci{{w6`V8U@X1vu$sU1x1vysB^Y7rtt)L!aR>-U1x2v7%q+ z$=sf-%@{~S&kBT(5<@@O3BW%~l3>VNNSe!4$N_|%w=oz52fK3P9NHJz$pr#Tc`>#$ z56Jom>^TEB0kdLn^M1ioo*y9!yLA!IwlSKaIew3`a*@Lx>PeNeY)b>eN$SrLkd#tQl41bzW(CpV}xyM8&AT$5dCeRNe;sVyYa~ zFf*>OQU39YtJ^OUG6;tow}D{juf$GwXB1I)gJPuv;Pb6mv2Hz*@%_oDyXPL`Lbff1 zY?TZ%o`zbdB_;C4hV-&ddt)fnAyJ;`I^GHyYhTM$f=zz$^g-;W20BLGIA8fs=mLr-21T$Y1w5;$?yWmEV{j zXBgu_Ssn9x%E~?C%4N5aVU4^Y(PKnxjmovRUle5!>>E=Yk=@RxmO|&AYp0dKi22Fq z1*Y5l0uF8%?&8#Yed{S}oi!cI#b}reQAw=yPI6K+b=GnuiUF8=y9wx_FU=zUrGFW@ zLs^xFgc zZTQm(gEszgz$Ir=9&nsY#hD(wIdz{^v=?1zy5(k%i*(lISYU(k>v}zk(M98ACPmBD zcz1y*9g+#QlC=K|8n%yCHTk`tFxk&mtXP<`KXUk6S#XYOY6Djqg|3lW>yVviOL63B z$VU+eRG3(+!&s-LNZCjY>O@O+2Q5hC3jw+Sl3ula!ZC*51rmOtwB4mL%Kn0~j1s;D z8;eZ^;BLLm;2QNaJzfL2&(*{AD3r>FZF^n0E=>RVWKA0%@$Q2#R-|_avXXNv*yh$5 z8_(dl&FZ|biwm~@F+s-@`+NFcY&%xws3OjxaW!X(i4!e3#u%XeYgx-N_l%0 zw1>0X_@LNx3nTJ|90A@6`xkH^f@j2 zFq6m?hFX^E!?MJ;yVHG!HHa182UZ6jCmmV`4j-|RWLi`fAOMJHhQyRy{_C`6Vi&O8 zd4P`xmR5&@(A5bB5ewMwbHC4Woc!H+omILD$ss;hIg&as2+${;9u?zdBPU}PdyLCZ z-M4@6(9(2n!!(%AMTEh!aAm#q1>tc?x=1|=^y0=e_;^%7-vZOxSJc!<(aYgN2PB}2 z0XBewIy*Lv;{jF+99>{mZKZi*ZrvX3`?8YG-k-RteOVhmazoxI+XmkE*s}$=kO$Co zguRjW3BJ82Jx_n|LEKZtx!yd_7PCgh{DKFF$t(IluaLGg3r}0Q{IJn}^f|!F0E9mH zm1N=vb_~Bu30FZ%sQ;mc2OsWTQ;2wxY=NYhj^B7vv z7>&N0@HnN+y3P2uy53)up+t(n^yi%$riHQ;WlZ^E@>gI*chk>;nDTHj@~s5s6Gf6& zg3+1MFMW`mAo_o@*bFwCtVVR%6i}rrM=Sfvh+znfGm_N zi`9&XS~iq%e61{;4C=5!lY3&uSH&WhOuTi-zWQe5Y28&??aHR|Rb$W$DBn}(2*d{r z!u(rseahv8-%{28k+SMPdoX0<&8U@0WxGigAnn5r(BbYq$j)%OH#)n(tYrhbOKcB zE#AHNu9M={B#HI5Vw=ga3@WCh30dB;UhMUR3Y@HmboeB4uBtNJlr6t+*T0B|NaU* zBQ!s6r`&}7qLHAUTY1D&>QCQ5>+^a6Hce0z{t0L2tjEeM?D?qi7NaJl z@r1>-<&U03ZeT;f3hED6)K~iFeha=(!}aC?KLtnA*ayF>+z$Jxx9rk?_TfbaYg!Gf zX%F&9Y`&*UA34TRtPAS+uZ!j>v!;>-t)hl@$)1CfVJH6m@OJv{*~&u=FBn>oh82y= zn8$L;NjGq~J$HHwmK|;>fct+4h7KRTTP*j! zANxQ2Z2z)9H1|lD8rV?|2qyYjd%$*iunSg(SSFe)AF`p!gZ}$?{eCOr5@RA|Pg3t= z<>z?-Ya6l$UyNN~D_sBl&p*rlIt;r^2AxZ=;8P_mc)x>mX)g5tMEL(i2+ZjJ11nS( zR)DaDQOLnEs1L4|w(eE}lE1~kmEU^V+Fw=1dcWFnY1?}1-R99(${5-Y?(`OdFCy*< z=Rwi=N5cE~)K_(O|1sBm{KrZ`#k7v|T5S^yvYVb99|+ws>-;HL>G_b)qh*?S#;%}r#=w%r=-2! z@i(9QwLJFC<`_N9am&5WVz2fkDckCX6U5WcoUit37KK4Sablza9%7CF3rS`+F z+A)J0`J3}2c0_I8o0;{?(G&N^4^d=yH*aq~TtC+HqiyjIRh#YZj+C#qJ$@f)(`E0! z-AT%zZd>ByxfJh4gKXR+*!jM`+YD}bMlC!60>r)>ubMQ@9^njxJ z=MwybyFLTP%3mv6WyntObHL#8nDTTzPlM#?6XHJe*jHXL6JFg#!p0iMws}4IHm#@K z*H<^Ij4VLK!`^;iAZ(7+#Gg@ba< zi1I}Q$v;~4*FiHQ?Bq5V7(hf#m%z!d)-`WJY<(2P-m%kG@6e;2Te&jlO%v&#?ZKVD z3;w}2J=JpnrXSo^e)cP1mIOYCmVw;apa@5Lz-pX{3&MN$Ur)I|yTRjY)9cF5@PGhN zF8EAVuBO&1iG%XIZa0t}D9^os;68kIX4B~2yo`aAr(f1+-MTpC^H81ssJj76#5jay z$6vX~Ht}=N$3OgGk*oVarG{#79m4A}hysR#7cc9sR{qSF&K^hp%b$2aA1?I0aWtr$ zmcIq#I(D6Wph{dYht2k-)jnBRG1BsXwiCT6Nzi;hd^oMfWKc{=*_`xWnv28!yi~#! z{Y_8bw*6q1{Zj=-+h;viXr5NHXYP$k^(W#!UK})D1zK9MA`skwpuGf>Fkmz2fE0`xpo9sznD3D1GH9#l@a zvVTTfmp}bO+P~^ux~n;`J=vT7O<6yPvZmlQSn`xDLv6?iYx!S8C&}{)Wo2#dur;--vNvW(9^rVb{O@C1STTi@^#K!8S zi@(ivWH951=V<(EAO`f_1>0h_kwIXtX(xK7WgssMpD%TO#=bP{q$db4c@;S_}as1SkG);#NkD?TnZfP*X!vI)()KbMK@~? zFqQrm()d;&g8qFZehv6K8kuLv0LXIetH*hi1(HHTFL&4zWlFc0eyX_Lj24wv{vdppqL#^D1JqLSY$8GBd!U5yzYW68)&2We+Xiaino~E|zY3k! z!W;Ijy0WRs)P3>v|G{pwG2-F|lQg&1V}q-EW@9JvNk$+dzIxu%|IoGO;ea*C%j%={ ztp0y4O31^twwzv$umbNEHtZZ`P^=Lb{QF>$=XO{!bz&IN!K&~BDsuG#pyB?X^&%c` zYOot_lYIc(ckSjo|JiybK5X4wFIjkVR2A(X71TbABVUQ5K`ZCaR>2;;mq34N0-tQ8 zb>aS|zN9I>Tb?nn2pvnh?c65W>t)&C!IvOZ=|^Kb)5I&yS9_@&EE&wXu}@ail=*+S z1*m&PzbP2|0-rGWO8qf7uU5xet?ZxFtww*P^d~H&1~jBB^Ht*Sk}>C-f)V7nrhRO= z1hUdgZr%EWDLYu}(XF;+dG74XS$gviW)9=f6P^uf{d%_pYOen0y!S=TV!nCruJNSb zPR=~Hxb<<+wDYo@SN}WnF-)f%Tn&702J(B)COZCN2A44i?}zF||D6SV=DwJE-g)Q> z&t>j;^=wyEL`k7L|BB`x3XlIdD9~V8T5@+1SngcSJMdJt7c*mh6*%gv5C3zBcJBXh znU$^l%jdMRm0obF9eq3Vjg#ONegmtE_1ZFd-d%wDq)CeLrbRTftgmlQQyJ5RYZEIt z%82uL)0#GAQvQk3I!A1uBngrx`r1EWsY{1n#%M}K_hK29*m!G%LTi>gS(ks<&#a;E z&Q6}#z>P+J)9iKs36GIiBHCA5$~4NeWDW+6o7fH|K0QM~{o>t?E8xZ6WLj&^npX#* zb;aZ&8|+(`qEPqvd5S{-yv2zrWEnS0#m^RM6=>5z%ip$xI!Or={;kKom$%!@tNYop zuI17=BbI6C-J_pg*|`ipjjb6#{v6y&u`?tfM>drIp>p(Iwe_j1bc!CW^;SZ(FV6j9 zb=B1BcG#0gW-6Fsxmj8W)!8Ghg~OS^JbG#J{G~iFiE4-B;<`yTWPdio zg(ULt;G=WZ^r6xh-{PbQ@r)a9Pea4msO*(Ka?(KKE&{4BO@F{=BpMmO&+&&0u`2?> zaFmgt&wp^I1odn6gciEViD4ZGX&A5y$s<6^T`5}$vGXoWM*8DTpW_?<3umZgs?nga?bjjL`wv%|*ZSvv0dUuzwk8Dd*NE%mCQr?& zLAMQ9UE@){BSQi7j&~jrP;+xJp{~BYxqfRW>Q$C1IHteI6zkhdp@+|$C4pD_?1|#t zQH)R+dN9-#jqdc~guiLKPSqoh%KHjw^2Ct0*Lne;P-0x zV${>SqaR^hNL#XiXPtsqqtoXPpK*ErS?QrHtM40h(A5wm*}Kdib%tz;oXw;*Acm&Q zS4AZhl%!afhaSm`N~k+bw8n*92RrH~yCidKw$T+@170T>^ND1idIyM$HX37%V59X4 zN)tpL3eDomTH4ETEp0#|;`6K}&gF3@S}$t4+IZF6`R{bxObz<%DsE@c*b0MP%W`q< zchM(}=&pgchoYnHI`hCnmqq2AhzLq+jDq5j&nK{DZskVNl*b$mK*lFTw1EpcD%gV7|R zlfKc~zlcQsw~2)y**TG`ZgqiB5C}f#?tk+^w+7uF()ia8`XKq^pzHpw)4Np(^&nC^ z#elV&Ah;V0?g*q(oH_+{H$70jCUSD6I{EuPoPR3iv~xRk;mRCqid-m6gZRkFEa~xK zakdu!K@#^HlDEw|oJN7O%d9&`6EPg7*K6p~iuMtm@@cD08%!mzZKzT{bMnIQc%Fq_ zniPY@V>Av5Uzo^4ODMF|TsHiz+}4xS*n#(N+Y01y@nP1j z_f~$5b-F2BVe>(5vBNHD*^|EHEW;)i}`yRyGLQ7BI(xEt`T3Up`gk2xS@nK(MW0 zfAr9#`3ldOSNXD4&dag#hpROr&(|#$g?e4H>m&BDv5kYY*JV|OeC*Rf%n3AG-mW8e zlK*26${c;M-G>_`4Q?CP8~MILMO#A#`He>R@5071YO)Rn{yQIX?{ujewP^qs z;Fl8*r}g*L_9D^b`g|SvuP^nC!u>BMniu1wp$DoJNbY{OJg5$g@%-?xZB;L7sEXh% zGD;tTwQoX+40?lLRhGVlTABG{{$Nk!7MH5w-$|makNzyypZmUaoBkx?XJPy;5AEa2 zasFp|SKfq*HmC}Zdry+J$KYERzUi6W9^HoR&!E_NJNmR?-@-(_#kL@OXka}sTSHzZ zd_X`U>|Zxr4hUEKcbVklh8?N^a^Zu<@ej;Qb5V&XT%}F6xK)2ae>S1BF{5D z>?0W-^efc6RM{Oomm`nfTFE(&))=CvQurRvM?Bm2XL9Fxv`g6(ewRBJPT};1obnN^ zVR!N_%w<#+=J04?{^cU@|F)Pc!GJlE!m)1S4^BsCwB@2+tsw>Mt4ODP{Jw$kK#)0G z{_K(216xW=O}G47%72a+`Y0O`v1M#$cxeaJ=Eu!Yx#cN_b{*p-&BSJ!*unGKN~nbt zq;}Hp5#0htyGk1eQcPv(7Q6v3igd1rTg+>1B)Zzr##fr=E#}yB4}zkt>@!}Z)Y_Y> z?T-~eiW_f{`rPD{!a3Qmqk8h1DX+KT%)Vb4b~nUkh4p*5m5Ka&L0GFI*cwIK?74a7 zHgy$d%&k4iIAnI6FNI0(Z@2TyS*c^&2L9fSvV5Q$_;5dtYncYvlD5 zmKMH?pKL5-0pnw`j3vQilw+-ulWWX25ba{cc{7iPDfezzS+m2x32yf-!5=s?w0i*D z7e3>Hs!9j1>7QtoOUBA-M>i0>DEJ^(OAwNgg6W`MEI}lOG{#6F&p6>QtQ}HanwXj^ zFAjBpRb3W64)$=W%CW7=Z6tVE(>YQ&%}<;jNTD`4@!$U9kBG$9igGMJfn!DIn4{)M z4N4y%$`UYWJfm$pl>QT9U*Az5?&;T-C98jC6~y)%&NaRez=uBv0Y-;1YO}v$Z0*?R zDD7i9aGN`?e>?#DqrdmXx&CGWM@NPHziaJT`vKfJMmRbXC+S|V1QLy`B#e8V7{_~i zZx1>sa0{R%h3L77MuXop^pYKm1IM<|bM*NLqF0ZL;1lcyn~?FFi=go1xhC4m)W~*D zE$d}{Lxp5l@W2(cYcQ%$t+hWC`*6l5vkt967O1_@C<~8! zbZ)LXCYw5$sxz6d(jHne)?7Qf$+huBm47UgZjP4vA#HH}PSRNB8GA;p#F%l7i6%m{ z2o)?_!MU;|YD_cFi^ExE>v2+0Bizb(+LLbJYHY1#L?>0jn3Z|dnB&A4gXb1`GA-`I z9wQIq-DH*;gxeiRVc8G5w@)x*zIyuUcQ|DScd>P-d+$LKivsgjHew&8=-B+Arb|JN6q6*^<8{oiDJfEz z$b7I-UnJLp$<4jHi;+@+GZNc{YgogMa##hI!KKwE?8`0aw9FEeQ7M*v-?5mZ4KDr$ z82G^Hdj*2eZ36$S%s64rWa03*kacK4B0Qn|711&>1_yvij<&fP3g>@PQ z#UY^h_?9Zek>GVx)_V;=gw7eb-^9b1@yrO5el4|QpGgx-BFfOWvAE7Yj{$_h zJcAsbmq@P0Qk$ke_mYcs#lb}=BV_e1w-8i*o{Zdtlwhj=ibZUDV#X zpe>a{p*qm^4lGzAym#0nE#`@PIJV4F2VP}^L>$K|GANc{zm4SVlHwv<$q8%$Ov~sL zPD<`NX0Jw2xPohIT<$H?V#n@^Y-6<|=}}Q_X$WKPuU4-W5hELV_K{O0%F$FwPejr3r+n<;I`E)v>Hjs>xaR1s>Rjvfn^~_@hN)Pt%-f3L*OAIh2Wtx2Gz?y3 z?k_hn6Zd36x%F?-Q>5sC$DIvTiNRPs2p`}jgryX3jYI@Vv*SQXxsF!bHI$5cH4>E* zkI#8^PPRqcC2@-xz9X03Nei(`d<(PplZ|@G^=OfLprY{)XhduAsSQ#iLj2FyYcghi zRmOdUo@4Gye)(!`+`;f$3p};q&<;~G?em~S@D@i0U72zZCo;29DpVmSg~kMAtd<1^ zwUX)Am{I~Tval$r4YJXD7;TlGN41Hi8*rPpQ!B0Gm>ShWiEWf$ju&i-!=$=#p9?0V zo_V!b&T^5Bl5JeB``p9CJ>BE?Tfv@31p{T7xel93HWwM5uUCNsK-Nez|OEh-{S(RzI zo63mCql5)A?mdh?lz#1uI+WyX`H9_aQE=34>dx77iHA!;IWqQhqWP+}d7+W@LA%6^ z&M-AQX>dXTZR$>4qKKS(%N4B?r>__dzKupg&wcy)T71@0qzr$Z-r*c?w&r!CWMC@0 zv~uVKSW}-O%n&4KLRg|#C*&j{u}qPy&p#`c7Fi8wT%sacwm}(5IPjZzzN3`09g5`_ zNNHqBlO56M{L~h0>^3j7jWIoBv~o813Bp-wZHp6D3qx(`rd8UZx5-i%^L7Vw#(iQR zDPaHjHw!JP=&yi^k~(x{Z>%7L^)XS&AK%o)vzf_RH_J3+M{QI`g&UXB#8GU#+vTfEfjdy`X2dAgY8`x^EvXTq3!*nsl&ZtG zM`q#NP;08ahi4kzoWrf5VX`Osa9Vv-X09Cw>CRQ+)B4a++bGcs0SEub4wJ|j?ZSFPUXqVjJb(M(%j;tL7 z*iiRXXvR~ZtEdr(FTKpfIx4ps+GJNsL~Ltg3$9x^OXqD9?@$?cj78Oew~P*GVOtyC zvP;N9gc!d3DHW{^6uCahKv#AN3K>L=-YDeOZO0R4lcF7=;YBxuu(_{WV+wC9j^=_- z39nlvO?tO&4hVKYl;A&4i2XdlHEQLtm-n46W`ljCF;hHwTqsc;?Pl|Pn(%ZLxt`V| zrEA6!XkmKD@NAPrP7q~>>g{)&H?v`qKP2is`EO56JLGFKVJUR z5&Fi)aU+tq7Aq62pR@YuR5q%kgPk@KN;uecYDNsJuJeD9jH&bC7ISheHy80rE6rq_ zWS#3GumMv&xr?UP>j?aA!ULbPSX{D^G}rr%t@CXjHic8+rL_V2eiR#4bx8_~njhjo z7A0g@NFUc`QGE^IQx?^@DfIA+mOi_9kO~(uBtgS7=~UCAN|R~=-nS*#U;_nz&V0QIKrNF*YMp<#nkyF`0^Q=Cs zO3~j`4jG|`e$8Oc`K#h8#>2P~ef=yuZ7>K)Dc@REX*T$r|249%N&~FwO7@_I-s8Lb ziw2A(lPBD`kyYctKUK^%qri5m_~e4w=N8roP7t&yOv*_%mP(ukhBDk_|4bE^ni~n$ z+_;gw6P`L3_XAJ5)cx83ngwCm(ixW)_D%euEIF)Ne`6vWH7 zqip7}qS~2@C1s*y6A^-_-C3Fks|@`7ZK&}hfSFJm4@+maql1oJ65ZO4oT{10$D0^{ z7sZVWS+!#hffE-f=5`_ayFwbK6U0)SJ4`QUr$NT*L~fmzo z@Y~E$+QpW+&}_WeD14p&u;42su~24J$Aa9m6QmpV@|a;MjRdGRpZ-{%FLTe*fpG%@ zf^bm&>x>RjJf6=;_S#5h_MrVC$54F5CeR)zgUZN^J{|7;H{H^=uy)+Qy*G2N99FF; z5t82s=Lj(dz9I)7m>ltZmghGvHmgsJfR0$|ByB93BRVWRQEQkB_$raD=eaI$y3P%C zPk)k#WA-d+3U$wJG|eG$;S4Eet+I>uX1$y=7eF8_kgHArwc)$OJL%VinCoS|<+TlI zjk4C<;PW1N)bcm&c{T{?Zz5r^&<&%R=LJos+O@cQw)K%&sZ+DJ-Sa7H?Id1|Dlwan z6hZm?S&M92D5;uwR8W&5dyB7k#QG1rxL$&A8#aIoyeoI?;J0S-gFSwTy>Wz|rObjB zDaujJ>+J5ekvsSu$%dTQ>2zn4_-CIVif|bTnS|*AbYQ1jQBrLb(q*mFcMeocq z_aMLgwF8-vRY%Bq+}>eEb44XMTTNQvy%WB(Ns&fP1q3#hQ@rObyaN`{9%{FJ0#c{$ zn{Ju1SS4-dp_`Kjo~z2_-jKyInb#ABB}*sypAA(l-0(W23h^3wXX=oQl3GS9MYOh6 z(4@#l@^5%qrX%aT3AK~Hq8NB5Z9ANvvp@TmtGcT+do=>^B^q{kU}sO+zd5p1|9j&b zz}NJD$~R&0j90Z)4*4k4!G9F}5fvE-faDP!p{ zo^{e5w85r2XAei;^x)n~@TB#{aTJ=I>S4#j^7)u@iE6a_^IkpgX`k zcJt$O!v`u?p|=@E4`}a5n%pf<+EKL5N6Be1-y^+K+#H(RTe+F`Jt*Vim}+Eb(8Sx| zI6JHC^XC+%4w3!Js~%8U1d{8 zr^ugD5!z?x%GCjTrE}uYSU;rpnqi6|eHWL$9o>X_zHPHD(>yAhLMP(g0#dA6duHlo z-Rs!Qm}M)uu1c6r%3o}tKyR8K<#8nS06b+bjDLV1FuyPgcjd5QS6gh0$-@vJn~_g z@mKH(dCbUyWfs`{M%uo*;cWe(h0kwho3XK97=`X#31}KmAQkaeM-4t-SRFdmHTzxF z93f51EW+w_{K&;1Pcmz5WT8+IudyASw_Ta>0(q?-@J@NtZ(114%bLjD|4tEVZc&P7 zotcwqWf-hodu>$vT5Kmu4i88b_T~|!EG^k#=9!kQM765RR9UCjM0+r4=v-x0r~q>C zOqB@|eH<$@zgsI3{TkYSLKzcDLmHR;L|FX5S7^~44==x)JFOj!2{K*R5m1hTY~aAZ z2>K7%9tupAKlenm_u3LIw({Q!|5WzJp7)*_<$y-a zFA}eChBgy!v%--2s^7`%1K}`IK%oX@X55wh`sTNo%0xzn6fVg2+y`LgR{892!IR3= ze|ItU^PY@n;@1Uu*v*+?vOU~TAnG8eZa~grYhO7@-EEJ8pbLy0&YyIJ>mcf_yYoXG znEqY>RZuHu?|%!b6l!JSAjb$5M)2xH!r^&`bzsnZ;!$QyOS0@Og#h&y+Z3|+l3!@) zEgqJIX&zqk&-kyuLMzOP5B}K}y<>6m{$c5+diL1YCsgFOF9@~WJNdQBG*DI>802>z zfU5%fdDawtDG4lwASmNndrxGA9gzEu!5-HTz4F-^lrLMFI<<`vr!}w>E!s&g%zGb! zDigqwQgVG=Mn|$}J%Xq>p}5wQi>t&XmXI9hx|Jn6 z{EJn)0QF`!I_HL7Gp)kinl)l46v!u59^$UwF;V|gEnmsNh*v_jW7!+g%`R_#bRGYA z5vr|`&UaJ4#j0C@;#nkfkZS3mFae7625Gzi>c+kDt&P-Qo{MQ<@=!75#K(}AJJE8t z%0yTs7L13cnsL@XT+@$sKZPVCw><8S-n$J+?jGLSaX z)wKpM%MXI2E0_UuGmr)JRpm!Oi?gz%r1BjF5`y!*T2!Q zY^!}SVrL(OrVWAi-Dsa-Te;rRo>~6$V4QxG6@BOxwMaPKM)?)Wjz2sZvVc0XV=y~;NJ$6{?xvMjabePt)KC644q=@Zuard@7 zH=Y=j9cP@9_NHc=X=7a>v4R72Si@BgRklaojDMh8&y7+5f#4Ne2v;?6Ydb#bbpL1WFFzDx?(ow_&p?y8f) ziGYiewDBp>gzUH4@36>}hdYPd(fkU#`<#Y$jzLCUU`8@X7Zj>4!+aIk={eMG{kmoo zsQk{@u>8f@xn4s;Qh-LM3wAt-TJ^@kYX-Ebv+ZOBk{BJ7R#%^gvRBrUp;Q4UnTNp? ziDMnA5_Ec&n3^AFs(lt3@s*BhtoO5BhvqG<$%?~D0zjlIu{IF7u#Pv*!t zY55Rti5&XKPxJjOZcQsB3C~&!k1Gfoi})7ou*7LEfF-P8ko*?2@lg^;AqQd5i+xhw z9^~xcZZ*?I%Bpgn@%_t8q9j|rYKVyBw`0?lwTag7Y3X23Xj@7dA(;Rc(oaF2S!_a9 zi`LU9-Dra<>3GI$Ghc!GYJeg0S8Av^VJ8upwn(`+ z4B11Pn*a4aZLO9v@fi31_G|)RuiA02{hDXCT}XXcund3oym{#Rewc|(l+szak>G@b z5l^xOPI8|U?8K7F*)TlDbzTn2H61uNYvL{HE0EQZ*rTQ50aS?{Nw(KD#K8KpwpheJ zZ53iE)2gd&SjRt$1u00Hxg9UE%TRw|*UUpU_nBLCtQ%;QZn$IIaTslJm|ET4SeVa=FMWWb5Ys(d@B<`h&Y?qmaV63Wm=U| zf?(2!RB*tmGswn3zn~LX)K@Po!q6N|q=d=}26#XZEhF>OA!L8~_3VJ?%0zoc!)P$B z;^l~4%X&8&VswkApMT!q7DM3eh^=Pu)Sqx<(Y{yj0}g`@dSVf-p0#=YC)Z7Ttgop! zY4+G)?*Bp;l7i_@bt@9f+5%;BqdLaH^L`H0sz4hZ7!BM^cJd$Sps`C5i4&N7XjcTm zX)!S7RNF}{e{jVLP;2j9?VEmkX-*vJ;7EY9b0Cw8;Rco(pzBBN%XIhiEYCn-LDl9T zuvigEJdjww=aw_?JjsZDUA+zw+Qz--89t!7Lnic0M z1Akl0rH(Y&D62w?|MkleG*JNsL!M^WN@=iI@f_8j?TWNym|D9Hd?(lI`b>8Y~j?dcKhg9RQ;!EtTiVY`xaE|3kB?5#R3A zKLzFv?$NYRghe&IkNNue1%g!e=7c;%0KqBivux;u z#uj8k0#E)n33!DsasMzz4;?d~&EDBGc|kl4)SQ%Ym+ITR(?@LcW-r0ZD%{$Ks0$hB zZfLDF2SKV6)#s}!ODTY~-Lyo8x-zv=cX8akWg$!Z#S5Mwy#g>Vpp`=&BTtJo zzc%vCxmLjISg&a2A(}UUR^-%6FMkA4Go*;omTwYBXR=IMB~D6p)b0CMbs%tqSNT^~ z#PsH8ePOnRu8#mTCC|lS-L3a&rqW*Ag5LsI78_)=_jb3FmY8ems6GAP5h^%7vRCp84>hw$89?V4Kg_4G4=D>Hl&B z#Q*^)5K1FeoruaKQyJGB1W>8JBa>G^elyqNakf)_PC7RSA=};NKjZIDfLD)lSHsyat+5zj5=;fY-Z5}vx=;LKMsaE|EjjNt%yfxh6xV?Y9eWQOxsh-L|h9{p4W~ZNUBxpZD_pG>r)BYW9 zQ)Jjn*8+_xsCWhlo?f5>HVR_GeeX zqweTW?zNr`ye^tM{1gcMta zJsT024=PTGlII&+8+t7A7)qUBo(VM4J1@tI!P;Sskln5eAg-#?pOuV9rw$AxVS1G@ z*-jaIoutvne27~?RRBOCD)^3sTqpzZ`@*k2yO)vSx2P|0waJL`7n!U?@)@Lo8)@)< zgNYg7K3au}L*c%Gb+E-@J}p2J*%o}6!kXNc&yrWt<2>XMtvwLTCfB~}Lbe?NV?!^^ zupEUqoqxV<<3O2aysNrxRN^p{)Q~`CO}htVSQb~SI2WUl{tQ!P)092J=M5|Xgq#8p z^3b`$nfF>?M6X-b&%%HZbogJ>F}RPx^~?gz-9Ja6%9!cVnSmg|v8#Bq4=Q7R^FZ%> z=rj}<5s`wJ@?zA25mv>U0zynU@VBXIXS2am`LnmNiZC`&f85+rG-aji;dW_D7k3<9`JvYL1A`1>(?LBV*V zzzF1(V_O$hVHs{KVKy`XXNWqVu~-d*DW8L2I(6z9fP@1aTV^Aui$EKI3D&DtqX06} zyvy^7!~zXz>7p05+qHm#A$z$lvMY#4B~=T($GreB7M1r0|9L2PrT4HAEvt1C0eLSO zkE&!!PUGP!Z-} z2#N*lVJ6cY#y#IjOQ*bsY#{sgIOvLvtgj-$Vy>3L~w#TzowxMsNJaywn2h29Y;=)gLJ(o^1#W)Q=d5 z?Y(yN&-dk0SO@0Ap+)>Tyos5+Wik3mcBzS{hY}P(C%eIL6Gl9rzvMu{z=1@g5W^-n z@pF{BI8_pKoS|yq0L>xPm4$5`5uNR$M*)NcNRNfJ4vNHD12|eLKs)Oi($rA?-LF2G zi%@pyZtD?3r1*}ru}QJs0>)n5wdzubPJHF8W%$}Og)G^GwSq06WsGotnWJ_w^ppub z1@p{SVBQh1kWz~|7FXs{r!SRdi=*DYiIs+)!56F_12%n@d1~%jF_K zV?>uGF#9~51aq3jp=GUo;9$d%b9`QDVSQg_Rh#uCN*yy3V-cFW3B|QQ_}RD(_akllxHLq&{Vw_VMP^uQniB}D<#Rq?KCo5QC-1JytkE(m4pjW&K^j^mI0Hw< zPFtT4L>0er(qdz^X!x$COu-Xf6hErP85NLYFR-eaFhSLS=cRFO*`s;7&9>B zscI?bH2?<87}tCX70u@=TN0!3K!a4d2?wnVfpv%lxR@-}fH)(yWfluRgFaMy;#|+o zr4C#^B?oI2uGNdB@|{Z?0VGhG*GVQVS%)^*u2<>c^5+|aKRF-`wz^!7_m0&@ln0jT zh@swZANL?z`)-xoH$n9}XHkq!Mdmr59b(zGE;LFw%rND|;-D~|>c(0-7zD56Xdt>B z*$tU5qOdyr2gzQ2J7LmSoeGmeA@P-l2I8`3Qwm2PR|&ptTQVypbB7$>QpySGNgI99 z83v)V`?MnG;!&tSLwNugDF_oSI9bSY>8rV$YRXK@OG_bqa)2W}Qj z<~wDjmwVMr^n6_!^7duSS}xs!{X=taaM6Ag(990T#k9Pf&Hvq7wAph-HP|4oyzu>j zit&8FG}o3U%yD6Kuwl%4yHe^anjDc;7d9d(h+^~3z)^~~q0~d+x6?+t=qY6#=_Vu= zplIDjUlIW-EJ`1QO!%MgzfEj&n@saSb>{=ulUrloqQKZ!Cs8<1AoU^62wo<1!HK$H zRvqP8a7t3=U0O!i4kd1OqfulroPp-xgG>M>&Q~QUvz0Y)gJFo4RN2b=-bR|(K_z;1(Mo!}SM1H7?;=E;XV1Nv_29td` zT*-GBZ|E)vy)A8*k)=%UQ>A?{`Zedf)T(e?E*6laO3E~M#_gOnx5kni)9OjQJR6lBF_nKY5u+G0D$xFUo9@fkmKWH*~f|+fDcroko`#XYUAOikXkUd@&Htl* z%1%tNIfn>XC|V7_q8EiRtJc0=W@Hvf&+lSTX$u)7k*=;; zOy!z|)xGnq8l#{eR<|fGVhx(KcNn4lI@Yu3UU1B4a6+e7UggkFXn8l7Ln(wNu{jn} zI1vJB9jW_cZ#TYh69BL>P+%jPUERkg`9iG<0yPmXnB?mLAEqVSMZP?@)mv=j-O{-B ztUC9sIY8Oku*B(bP} zj&_s;srVFzR$VNwa<0EuZm3Bu51grK>y7m7J>DC zCko}iFPhGJdL;`D9L}kx_6o%#VW@{+jr)DjXw$doQL0`Q`m z<;Tk%@`3jw=GP|`E)4QZ)j5cU0dQAH=YQi7S1&qA$*0lf$N?h^R zHbV>>gN#ye?*I?W*x>9{J*~0K7Q}mU+}kOgY_er-K41fVNU;-Pk5@ z^#M{~TSnnW;wHevd$pwC;9nF!aKF#W`j=H`!gmi~e5qu2@8Sy00Xy^zV#eDDk@BKc z-za&E2n9oiT-~x*R*ECj)#)DApm`tTjVK)mPuD8vxQxE|rLksI*WuTKqW+a^ zDV9g`Q4G0H=QXgjvYswOb>g_XXYs^4$y9FMklwGMQ>vr37M5-Mi~BZ|2BeqG`aky# zOMHoHMjYwpo>Thxw7eQp%#56iK}3*p)E_>@;Jx~$cL0nU0JWF%ov{b}Sv*z!SH-*^ zBAQl_thM_*cuL_Xu&QGTn7ZN?G&|JU9pAb!MTX$WSjB@%Skns$n-B<~Z54(cn{6gG zT(nm4%1#6otdB5UOa}pn49oTiGt3sRZs1E?(YbrhUz9Kt!Dcj$f}GXH(m=g$TUuTk zVyg{6xnN;(t6R!hPYxAHsT{QG3e3;M4Hld7B3{WXdYaURAxb02s#Wso?{8AvPaW^Dz8>Fl0>*fC5N zs<7thMq<@YLO4UydL*ZnGf5*o_dXd*r0BWIGN$OHn`IRPI`YG~V)j(l-Kn9IeW#7| zZIMl&M869iL|Zvr5@!UDrBYq5+fLPS%p^pJ2JhLZ#*}@1ibv3LATCPJg4?*jO)j%` z!L1f;*bYHOEc797wo#@jy0w%HMLf;8j`vP2C*mv{DAI(Xh(tJ|MbFcIaf|0@dKPad zH@_ne)JBhK72JL9fhptV)+7r-ZOZ|u?U}(?-{%w}D03@o%~VBdrFn!!Jr+#grf>hR zdc-N}mlE+1DoW$%*_P9W*jC5JQ8fEZXKTf;6`HTXO@VdtSx;^~ zREN&R5fdsLtLwTWw7Xtpj54jAkh?W!X$e~~QroX{^>XKWf{v++dhzGMA5LFZhVS;z z@MyK++s<&iH6}?>egw8em)_OM z--ylaPvvqNqnK$?kHc<1xF!n2VB^1vAYo2Xo~N9YLDc-@jxj}>&D@p?<6S95mMnwD z1VUgi^ENHDVkV&ZjlnRT*(av&=Zfl@y}*v=Y*d~qt+s4#3&dKr6XI`WA{@#egQ8XC zv8L<%L<+96hSsLH64}JJKtDj{dI4M$v zs+nLqh{K7Ge1tuP-LDgQPq71oIg{64Sj>1Mylb0g&G-UD*P;hmLL8ihHe}gC$x*D! zwX~F(2sTHa^bK%Xw37;fr*(Za130VCRGe+YG;SC7)i|RR$s-@SD5g?-8nJE);#Dt6 zACfLhHBMHRRh7j`#rRJal6FG9j>F94iUHuaL>Js>QtZibO~)UbMl$r)Hs8p{m9b|` z3e~U~9E3Adx0M=^$l4~AfEkQ+BgeUNp<8)dQ25$o5OHW_CC+<-;#HC9 zl;W&7d81f@on)#^pG!;zA@GPa!8+_xOa||U3DvP$9>LWdBWh%|Ra|dy^(_UVY;A=GXVT+XnyuIUF-M zQdn_fx9yO?Xpm>>nZoeI)l8RX4YCRGnn*XthQO89Rf@Jq$i*5F#4|GEG3s~A(x!29 z<)={!&nQgDqM1-=X)=VWfJ-3vh}c7K0?`e|Fbp(_73#Hmx{~YY_+s+d?o;TUYztyW5*--^mrBk3K!ni}@KE5b0%R{HiuHt0JIlU@tVLFm|>dj{~*8fVau zGo{t4WJ@@buWzui+`vsYa&PN%!K|8k%KP9Q)fvk0b%H9r6y=}|@jPUDr;gibH^LZ2 zn!=QIfgx)2X(eZFG)GQWAc(hs=dgz;ATU05WY}nxY1c_U*TN{)F?Zb>0oF+$Lv_5^ zS;I(`A-Fa0*DKRY+XUA@Ty+w=6@l2bUq5BaZLKAl)`&ux8^u{RT<2N4|4R3kvGvg8 z_xbzRw|VZwAU1}?uC%5Ut9mA1;-c9YBRxl(A~h^ALcs+@?wrcJ3T9d(=ctVH>9jZM zF+JTPXMZ7`Z-8ha0+4M95faZkQ@4OGwV)e~5|`~t*7-{5SZ-$M__na`@0jDCjKd7V zGcogq)tb^u^;f`gIx=b!6Zr+muUzuwc^=d9+6JLsO;n`zU4othQZqCxGgs%QZv`o3 zMvaRP$qX>Bd;kyPV5*ivPH1yT-J0YCbb*THNDr-zc)Nq9YQyj_Mxcs%QVK(+?klpd zYaJTEdv}a*;^oM|h#@ZGkapLz2-O@bHxTvAYNQiP368`3s@q%jP<4H3+^_}+u6&nm z92ay2a#P>@qJ*5nFAnqTf1$^M_zcaS(@gV78$45zCMjw;pN|Q~&lZLNq_XiJMF|vG z?d}h202XZi>C2NR4u-rqJ(68DN#p30`6jmI+@clMtl}}JpMpjz4x-`N;H-%<)sm<@ z<(ro|z>L$2^^Zb!QSgt{l63q94))gFbzi8gj{}MPYQ4mbDXO6+1DuiiJ6io8N5osRTR+W?j7dRqC_htCc7{+ z*G`W(y19n0Jiajw$J4FqbR|AnY~n`SDXTy?A@;TK&1Pz+F0jiih% z@m6WQxTROtbN{U$+k_oUK4B9U;G-5?-H-9diakdbO=ewOb-i3_1C4tkv}EXq**D{J zoATw!XP6gQ6F;zX*_~reoxZK?260oucCU7EaANVE2n_Bq)v;zTA}(Z@)XD2KbSouk zwreS-Y|}2!rr1>pitZ7Vh_Rl)O@Bo7KeY=N8$|Ri3+kStS{lRx)b&GkA9F&}yW9J_f|~g)CidJwzQL-OmF%rI|fd>WZ&31XbVdn{nK-97UOd>A1D4 zKr21F^!f4W!a0uWv~41`k$;g{s+I*0iwoL3_!|;GKHYt*3h?v%2y$3dfXqxrE6P_$m$i?|bs zFne%K2)z!GwmNv7vHdpk~KeYWHqZ0nM z9)cHyS$qHoHGUTSW6^H_lJDt}k9@MflD~q}04EW43iF{Ib$KY+yQ>Hdc98DyKk`D0 z8gL(glND_2J!(VC%9I&kefvK{Qoy70K$3s#KYbPrg~I`aRPfsm@ZOFi5elHJ>p|Y9 zfxJHFwBIc)QF0cl9xiUl0(g3sA3oGbp~AC|SE$ukZpAdia4N)1xlI_B4kWa3>XdAac%a;Ee0nAePB0e2DPsKT-d8YTqZZFAL1YU3U%m!*b-RLu8#{Zgs`XQzZNFz zL9_n{uFN}5TfnCLUnVek!AEeW|B~__^6I~&{Ff^igy~-a|Hu!RFXa0J3;3_D{5>G| z{|2*jDwD=ma@M)blF%F6p}$lrH$7;=PHyP5rop0n!ppJ$xl=op>2dYy=JB26-;DNy zLEC%c-^ah+vssAG@Uf2w6zWHS$ik6o5S;cv6x!7lRF=bh%thC&T&wXY6%awx?h~e? z=esukz-JLn_JH2AfQ$R?Q}Z1nZ{5!OaZ9{476T7Z4ov8M2ig69=aRvw$3bt`zH{mN zv+5Bf@4Nv(7oT7J7^(IN5vtz&K>49}bf4Kao)19K^+1ug81O6apLidKzV;^}%FUnQ zwZEBrlm>688`eP0J#rPCk{6EQOhgdr*QfGBSi3Rll8qTRK=~uA)_fqLwisM7{Npq3 z?cu5m_NyHugm7pzydCbsDZ01q!dysq;SB`D3!Byzyw_ngKE+PRQN!Ez-2KGYguvCG z!BM?E(dyD@U#-?4Fy|0{?>!pEniic#fRBUHsV@OK^eO4(RG+0F61CbnkCF}=!@xg( z+7*7^y$I*?8K~6D?Y>l;fKGe=6A?1f#q-~|@g=~N|J^sP9Rl9}y$+lA$rcn;S8%p~hN< z6;Xv?iy(8n1^_spM*@85bpUwt)UEmJi(n4FMG$|v&zE1X$baKq1paTi+2DS%j$68N zao#{}LPaYij$+|CUy@ii6si0JY8GHcV1E`(&=3AQKvAjMzA?w{QKz3UwfZ#xY5$n( zYL|p>D8+9e_%vq*3kIe0p^r?JN)Lh0$o8ioG{>Esb)qI_j)-=&mj;G3R&&S6aWZ{- z`&){^&OsD(pn~08d<4t^RrLV82ucSMpe_KqBS7=71&M;?^B2&yRUB`DpIR(FUQv(h z3Qn_DK56EJ*75aO&H3)&Lq-13%N9eQm}`d5ZWIKoS?IU8;&C<5eE7;;r1Tju*%dIt zs1X=^bdbCa!i@ijv%ioSALDfic1aTY!a0g4c9rlm$aEFn=Xq) z5Y?wW<(8)(M{iH;1lWYOGq&L^S0*W2#kheD5xN)JXdZ%<$ne1uT7H0Bl&4;kZ)8pr zG^7H6brWfLUQns;ToPD+rjU_CIWJpK*IN_nW;EXCG7-;0)H)Y2_DDSfq2;i@hWm)f zub5o}==J)x9m0ilQ)z&L0mlW%W&9;5o$7veU*mD`$zLiGmuy8ZQ9>`-9yPTWbL~mI z&Y4>=r$4D@&xt4)!c#?es zBV$G%eeIa0ltBi`2Mit+Uh|I605R52y4<%`02sK|UY-YM$+Pli*UamSaSxy`5MhA) zLG4Q1r0SV`z<7Z1capBpsJ^BdRk#Rw0_~v;@x(Ehv zTCkm(Zf_k>HJ}&APfU#68j6q<-{*!&l}|<=f^d(6GF`w;O`-0NP^lsLWVv9|*%zaB zC^q{y)AtPy|aEIW!$nKcv#18@u-Xh&n zH>ttYt46}1L(giyfS!3~Iq9os-gG{}M>uL-1ql?r1R3*ML%P@qm7TnJ$J7`vO&~8> z1#jT0qgZkL%e3jV5pWQ&Qwe^a)e7yQ zj)JQ5>Pu+PzN7NjR}*Z9Kh|HZbNE=i+BbXDoJ|M z$+MW{A2sa@dO;x5>ejWUs&>KMUs}vpP`zTU%H**<8XgyxHno;PGXE`(U za#9Lx$4emxyUvjD{n|3YOQZ?InY?q|5!WH*Q6<>>ZM;l!7r(ngDXFvMYy)Mz>+p;UiCGAEJO6Hg*AwJPcLFaAn~PKp8aui%ri~iU zTImHQqC({-`ye045g#!BQ!OzNqhQWjImNa{ang7Gwih z6Oxm_#!Zh!XY%e$J3|a)SdlG9N*-2qZ6+W(WIsT4eE+)kIZnN@7rWLh%I(%^0o5g%P~f~0|^^I z^+8Xu5L6C=+07za0<0lP*Wr>I+OF92tv(C&g_FK?ey&Dx!rT*^l`x;>&iro%_gnML zQzHh8)UMwi1#E@`x%<^>+ZOo^N=}!l zG3PH~C=R7-8&7imPt00V9UJyZ87NYf$_o)}+p8EQCr{f8oxU4gJ-Gl&tp7z>8O32Y zVrx@U^NpbW*bUJYtRQI25AI#ktfkA_a5qkG>b-7@jGoz`0cTmW^+$)$z7 z5~oF%JNIqC%|0;8SJahYT(b>#%Dp48E>)XEsy0BRapcOB)iC-18P8g>PiMB8etImu zD@@FW!O@U-xn^D+4Gu-T(dK7)9NjSk7xW3nBn96i*MUjDvaEDv+Sjbiz0Q0y_|o*m zQOGb!Jq$7&r_^wtbY<4p`6okrnSm$`I}8lNAkmeSXx*sl27VtF6mPr|cmvgW26FU0 z(px znEb3D)j`n2iLbNC!@wHid05W05r*B=w!|CyAH@glp*n9e&0qxMZ3BR7Lo?!h+~+O+ zls^+Sq@QqkRF1|QAA}czPX~YP9FfoxhE5DIMXvMl6ZyRHfR&|cSVrD88J9;Jtfqhj zMxGitqmF0$>xZ+ZXH5Y6Zcp0;v9EdG&l}E!j+FfOD1;EP{Di2_KJK~AfBSzqc+7@r zsXTO&$0)Hf&M(G^+scN@7bM_n`Wu&*>%3a)ola4M@KamEBJakXfPKY}GX=m}nSp|mAwrjT!tDcFjC(}%C90lW~I zwu=c!BcTh8SNP>Yvd#zKfMLYtp+#sNkNafwgAn#SZIFHLUgS2ke1j^tsmJt>7zx+treY z_)W2RMFOJk5dQ=cgp@%^N7v{dffkAQ;j)a)^6PR+B+?|uflMKFZUHFf+wNdiI7`M| z0jLobobIp)IrCY7ywcF-AK^t~Lx?VjI#+-rkul^nj2JeJlRG&A%GqR*!>30yINjcF zd4UZ=3;ipXnR3zyfgL1NGD$N>)rbuygGZ*FYoCbh$_}6#kdXA~6YD~^qJ~(~H9XyE zC?4s4JOr=i>B1ep86^GcR>PgRUJ3{wsok_a2GR&85zl_fT7Fy(6ZAtu=PT*BqfJ{m zL+J&p_Qxuh|M#d7fMU>{wmOgv`aBx1vt$7tFY#wvn;||$ZrE~^2XYWM=DoicM8PY4 z0SC_=XQNu{f|HpwK)+6BfvI^WgG%JN?MA86Ng*akSsx`KTfBY}Wa#6K8s`coy~BYO zQFL~!Nd>{on=0uDc3h_mSa|Ts0ZOsm;%rjXP{dKa{SW=X*mL5W}f>RM(C zP#Yv*w`+I?f;#4`F$NUcz#I-#bnYgF4^R|w+dYQ7aa;kiFSE$_hxWHOo#__rJ@=!} zo!*wZ2Z|JmU$2fiB$f^7;@bdPBO6ryPnHApwmndPzsJM08c?0@T&y_XQho$)pmxTWbt+Ah*HjO?}PeEc{A>cCv0 zk=1#jR_giByHfXlV%^STG@^xc6HQ8X^wE_&Ww9koyk}in-ZTSrL4O)HmIoMxv{kPv z8=K3~{5mH|*cUUt1#h#xM2E_vhuL%|UHNPe7=gz2!1Xa^JBHBAnt zZDGDw+dwO$VY1X6fP~ge@s|ad6+<-)O}&(Gb{Vypg{P>*a3nNYEB}6`n3~*|jXD-kLDX2ccQ+yVkb;=m% zK^93bB4Vz2NLr7}z_@`e9?P~XO+u9Wtw^!&Dm+q=EJsSjxLL`GGGys(Zyt8b)f$k$ zRE8wOU4DH@;K|FHg-=c-2Rp&bwM?Z;^~50D4sZMM?7KO4B&s#<6lBU+Iy}fy?R+c6 z^W*dWL!Xl7brAV4fnnyyk*|3VK!)-qYL15NlfDNeQ0HK3Tw=%(HMm<=GpbSNp4#H- zwInFPR|eu_t5KjbrGL%*x{EbvsTYwk=b9-PFU<_)@JmsB+VF6;Vl~&B#{Wi`g6x&% zN#aM5O!71Ah!dCLqd^VzvMeb1Yp_@#@XF`@}qiO()8*2lXAiS$E@{@s zR>E%*i~LE3Z*&5T!|T_Ua^RfA=D9?r@6kml4TJc32$Q5x*H|)n{8J&brA*p${N##h1!` zKT*%q+yQZy6I0dc7^G7%t#`5r_W~3$Ck`dQJ&4ea7jpZ;2Y5^KKL8~;H#wFB=FD&a2I==p)+`px|$}`{f~Oj8+-n-saVR zmQ&Mn z8_qyEH6=;Ga+U-Q__$mYTM^k;{(So)SmhhCbVOfyV#uLX@1JG}uM$II1>bo+$aElS z0+C@09MS+w^loXyq%#vEw}x|K%JnBe9F9*#di^4N>X9ZzE1Fc#;Ou_tndc0*q-B16 zk17px`de|rO*L#!7TUOVrM??h4dd=Tdj4v)3{({C2mD^^$-{*_-s>ga$>~OgeD+*< zRtGVQn>EesT{mP6I4Cr3!XWgqozwe{#lq!`7B`MR;wl)2qPG~j$|-Xyp7#pmplSSy zRkSySKwu-y8+C3s@6c;Crsl9Ne+N7xWn!-JV)gcEt)b;YVR0SQmgzmopHdX0;U{{y z<@!7*&!EB;TP=L9l8h_FvOT2F;yg%;s{DpA- z%NC+*=3+@!QzjlH^TM0)0&s39o-ou#v3Y4IdfeQrIKf~Libsgpk2Heyx-{zCY+lBH zObGcQRXSYr_)gZ(KzX%8uKe+a^fQjsxp1UW?_g|Y>&zIOPYfdW2 zHF0I;kyAo5$PyR3yPu4XcUXS8OnjZJb>8$$dwy}4Gg5N0UXM)~GIAF6&&7>+7qfJs zzKMoeD=?Tz4W$3W=C7+ns&g68Ou&>P#UZY;lX2EF%~6BF{cda(iM&6g2M${Q1hiUx zChyI`-9_7aLs=2Px)( z)&5s=9pBdm9?-1J|7@UZGxfPWAcw`vEG2#kc;#wob~C(DC;ONitme0XSdn-*fptG^ zssc$OD8&HWzS4n|KG(~DwB`xGDkk8L9XQbc+suHUr)AN^$bb|Kc6?H}?9^bPmCsYn zHexqYE`dOicSR+9sqUGV$+zVyl}HwdlgaT8Xs}G{IT`RXTugoQjUgEX^-c$sEPeox z*>3>2h+O>8+9nGAtxK+JhpjaRmw!^dL#T;N0T)sS%b?I3MbwjoG}N(#I98Ifw=63~ zD{0Z;iq?54fX4y(U(D-{_MYp6GFRL_*A|Ik*DGKn2O;w=+K9sRPaH{g{!j@4T$|6$A4RgLO6tAyj}nFur%-eBb2ve(YMG8E+Ycw1R%yQ&KM9 z3;A@eL!_T0sv$i#@+vqMz!%x;fxi55w8g0Zy}RKqOrFkqzVTX$T_x{=FAANACDrpm z_iI-x=rHtz|C-CcRCed|uQ)6r?8+LcC=QP;#fu7TW2bl*)b;#Q6Wu`;K zcV}0d5-}Ey?}LM@3I<0nLT~QIn~=?0lv8M2SN?cP1AqG#^d?kJ^Zv&0yT^=Sl|J)3 zn`{Hp1cP}biu_4{E?h!C-7CLp*kx5yN?^9sP6YvSWb?~hI&xFETo|+S5wS2jJEtul zPp*+4p4l_~JgnLCQ8Ev`x3F*GL_^#)QJQtK0yk8OpB8q_H4eSFPE0e=?;v6_xu%&o zC4Xc(SI*%~Ly3H9S6v&if-h!WN+WdA%VqtM^ngn-GTb{58o`jT=}{&NZrMT6wm^m#W{0fh~V)U8|IbrdQNh548kE~_KyJ=L8);oEs^bWwlB zRA^+dTbHx6v2JtqwgF6-bY8^Z;w~BaFm_9P`ab@hK}+Cxuk5_UHPPP2_!);?3Z4t@Xrjlkpc3`apf=zr% zd=A}u3RE43pQ=AMs;-?R z(9U1IT6V@yZvqT}8k7jgTQvUdfdA_r3GaxydF5F@V_MV$Jo$6+Zp&97cvCY#|6BgSM@bwA&>F)wklJe4b?7%0N#2tw6#kBQB}wP{axLEF$9t( zg6Bb~H)M54eb9JsG$(g}kcbOMHpBUEP=ZP50wyJHM!zmoWs6_jW62Ek`qd%4#E|b( zSB1~v^;?%y`2)(w-+=$xpzQs|k3a4!bu#B$Sk5ag1EG)DA0lLP9fNApRuK3B|0~-j z23CN)88-HCyc0kGQuk`_FEwJr)6CABmT)8OzM7U61XjzBwvyaNfY23a`Jsbz!;Rb(7NTez zcB-@(#ac%DRb3}ZU?C6#N(6dp8y<)0qM;EZFfWixtEEI|*{+Hzr@9^v*%N%2&5;X} z+U|uZzq@KbJmvYybrkq$>MDV$Jc$D5zg*!*r@S zg>lPL5E>rArds0ICtUynHBYs}C64i0DG;5gR^GZC%{B~8HzKxaDaK~#+lSyE?h+;F zU>?am<7OuBEzpS*3hD@i3)G=&>s*s8WuG6*jsA5=>UBnF?*rlZib4PZV8gWLt6|{D zdet?7cyUuMw*NIC<6t8Hi5xl3U-L5kVGl6!L1uM`4MYBJmnZ0~J43GfX1crEhOWIV zEhyLJ?M9AFGk?1|l{KBJmohm^MK&gKth$Ju{9OX1F;6^ukbn=I?w)v5(N}<6s}XjhGAWBe z)cvYv`qn@}p+&OZgX;c=(-<2rmbAE|Fv5G{7d`!>>|QUBuhZV$R}-Fm6q%8T&-9#2 z;U`9{KoVzlJxq_}hTXjd`kYtrP(R&LH08;zo1H3f#GT8$VLaU4&ELppH}jT*`G~>` zPnbu14}I@3d61G9KD`IfTpg(L2nJI(mr9nBx*coH-4hIYRU6Rk&5Yb-_-K!-n1rlq zj*UN$QQ;k?J5C*8iO0QWJ#tz#29lo;9K6uyT?Dh>&Z?uv3+XO7zJ|uo&AI3c+unU) z8jJ$N$_2KKxHgDGv*Z88VBbS8*w-3?nr<(MM@> z56jVM={B;(xgb9)dceSVuJ-)7I6q7~UYb=XTgbCTCw`JtR)H7Vt@~Exe`n8)r`70>#~zQ;*6w0xusV zCfl$4ZWG(8D%_0SRpu8D(>WOkHvk?fq~_#;S#F_rHMk zMwn3UwEzMNpYd&EHksPhyyZUzh{&^yQ@mvjNG9@72%_EB=GphAEzi9aVAta zo(-So-0L;q`Z`?r9z$D<^}?GnHdW;;NUeDF%wFh^t>#_G8onO6EFvIV^?2pC`pU0rzd1B2z#U9vNBKX{4nyO)T1UxY7d*(yz{q-EbX(RbXov zH&CR|Pqwy3e|+)_C7m#d5~q_JV#o_GO+Ox(DdgT$cSH>=K866sqrJ%t;NQ||83fsO zcf8YbP{0YRjSh{GjbT_zL=0(PL-&skfA7;f_LsY`Av=(%EcG2m6XVNKk@? zX@&WMJ$NJNjRUD6;lB#s0%z^g=gV4OKGAf&peNHboBgm;jF63K33Fl;HpqVDC9Fyjb_6Yoj1l%n3YS}}>dXld>5BOp zS)q8Vly&$&OLK_n5et1{d1e5H)T>QdT^ zCn*JRYtIG;z;k3jyHy_8IA9)ON=|rw$lcn)#0ySR-|E(GB1ih5x&8&kycK{#bQF|8KhnU_PHR_`tXN$U%^W z)w+MeGx-1WKl&tQ&RxTwFxB&~lNA)s@ye0SDv2CfUjaK6aeRg@{aj8zw_Z>FjJA@u1!B(a`oC- z#w!^m0|h5^(5|nYkDG*&LV~yVPG{f=CGs#n>3%LV;j4=P+ zckTcTmMwB0A~hdv*{bA-69?6sSDH^Tx9Aim267at<1x0%Sgb%-yn48VXhT37aK&+p z)E4qxs-N(OL1nR_$bq<~BDZIa%IlKg1`V9JvMj@i!D-FlyD*Z(u=Fn6a_W>$hBZ9a zOZuj)u#ylK+3^Xl_#HEzYQ6v9@KL4mU{LA`94u3E)s-V5$WE`_wp<&uIW)O8OIijL zQ>TUz<+-l(*X%_uq81F;eQ=f2BADfpTaNXG^L9jxV?IcFh&-eBN%7kjrNW<&G}4rv zm68FE&{g{quj``K92>)$_Q;6bf#!AM;@0u#;D-1mhm++J(dLMk$?*Wv z)fPS8`26-Bd24M`S>D)YMA$APs((XCy4H3aWto-S=aTMTyj`LhU6tqgOu*l zv(2lQ7lY|lo?mF z{`KqqJw2_RWZG@aTp2a>lwXEBm_M+k`aENDqWP?#$;38gr z7~`!%<|f&lewYT5+>AAm07Bar$}|0y!V_Em>tj&PnkDyBJOn8uxBuHr{*SNANYnD^{frg+b3+u4{%3kbowK}nF@aS?X znEv3GC>28oBThzBNv>2hhGG#nafMO zBTegtWp}Vsf2sI(t^p*N;~7G^Ik4t*5v=)VX!jqkt_9mK-~Ji>U%Y;o zgt1H#MJ0pKNmz|3iL*~ zaEG54J%6w~Xrl#WMe?W-Xoot=j*!u%0;#GU7AlO-OHTk;k9i{?n<-%(D&N@Vcr_7y z&lnWgZWWJ%MqVumM3X=n^Z(jPn}GIk!<=lhWlI6YL667PJpNH#6L3h?KVEBV8I<^r zs6QTYRIv~YmAv}*-ubMU|J!jCN(2NX3T*e}ggZECb^*QgO*DfoYGU*1$QXudCN#JFfE-Ph(!ZBS;3+~JOG zStHU%$J13}uN>L6VtDPE?}$CFmtrJ0*ig&&di}If%YW>|%`NRbN&PA+tJ{@xm<#1vbdU5GH!QBMx zrcuC4uVaZ{6qK&r8Q)Gikz5mf*F!lyd!TC2I;$rzKNs6V4!CB7DHdPs?Y@)>Q@1FL z@xT2o>=Xk0W&Zi2)vXNZH7*@~ns(5J=a+ZAsN3m^5eA!V|Qu|iXpU#`r@dKX#j-^7!&sCZy|8u|f%+15c;QOuFs@t5q9;#OLXYaSmLt@tk zn}SU}DEa&y`oCW6eI2m?$wM-)WAQa+TyLfEY!|Q*$Bs&k z9fpfwSn!v5vefP|M@oU!gTH>CGV)swAh=b|`nBy#X?5Ir)aO>iP zWTtfmdz2z+gL+Au1w(nlQ<{}*mt9^E_^NHrk%Ak%EKj9C4I{!8Y z@yKjCq#s&~c3#nqhKnY!Q}^7HDR#od8tC?+6%X ziIh!&e(?QT=+aF1>^W+lX#g$Y{+$s@>Zj>AVpIYBVCiqa*Yc4Y%}K2gVfL7bl>LIf z8K#=U8wa2t{Pz>4hJLRya634w$Xj_rdk7F3)(cfIr)bth0s6uDJ-@ei;HWVw{~p=D zNA~}`l=*xkeIo~#*&oi(xgRs4Ic?7hKDG$=57bxhc1qZhB(<_Hu^mwqGh$A%+SJnC zI-zIr_f*q^i);szC$*A+K-Jz4`UkSyeIQIR_e}4KDj@hTs)P#v@98VA+qe`nrST|x zpxo&8|3D|JKcgDutqp#p02Da82y&YKo)U{pJIjVF8}WvBjIU&x7ho|&*#-^spcXp_;<9o&o>O*-CxhE(}Ai##r)sG25}0< z;Z`Z{+3!!DtUZv$j45o z$;Tqv9jh39wpJV90hbsw3!cM@1wQ`EXhOYY?VXV9lw9ekuLXBR8z~k`CiIaqcE1(rzYLz! zti-nFqP<=ib2$g|P|QZ-XsU-+LDmW@H%yZos(g6D3(?-^?nby;+R_(n+ST2ufbZMI z(EFm>3es61PhGg~_ud(Rj>;0bw_azIE;*%@z1)%X5N?`Sh_(7P7m2hvFEt!-G**#ND|u7mv!ZAr{|C4|GRBs8 z-EUNQbi9+{Z<)`oV(=Wq&JQCVH09k=uIjF<+^m|mvdY?uqir2~Uj2K;&tG>wn(;K{ z=dKN%XvWQCkB%CqNmzQGqdR#(seoNtU$odwZDXio?~11}e-c%^ORWgG!!mGVV0(e& zPw30$+!4eTLku&yO6s}EM$;xNJ}po`k5|^+&*}D1o~rwMWZ0#?@y9kZuDz3FeiWwc z^ls!?PB{f79rwuCjPM-I@D1p}C+kH$E0FxV3kK;HhuQUJrk?o2aEpzj_BN{bTqHZ# zF;mX8{Q2q1%P1{D28Rik4R6A8QsCxj|rNUt5*V!G|-O7Ty=V zcig4tezKlv&!Hy0Xp9-5Bz+`UoNOk&-(Rg{Bp9eyuL9xQ)0E)`2B5$G4^lSDIIWr7 z$uFo`I@~r~@kPH#`+-~AV#J)m_Os$n%QN^`UYS{WiIJMQXVu|MNVn#FuxJZaEhv(j zf0o^ykMjVC*HP(=>J!kqhv`)On)eRO)HAG$9AMRSrrsDs)I;aGcO}+{uU7_G$Xe&CayhAxqR4aOVBhB7r`v5m^^Ogsvu;?BA#6z03Maj$JM3@f#>3Uy0yVKMz(J2FkzSkXY z%>4}5VxW2ZafA7<5NgvHnDYu9N(!#+-0J39$;hXj_<_n`**`xn?a*@}?Ac0T96nUK zBQsB-*S-}92^XRVQ01BsD|ghU*e|W8Kg#2h{SpZ6qIY*xzA;WMzH?^-$O)U@F@-!H z(c|;|e3toLg2jDyvcIbl5Jp9N=Hbp|FFJtT1gF>K_Q)=;&$fR%TX#I0NO%fd$70HC zxt)Y!R^gpH;mo4UuvuMUM(2C8%g011Rp=YFi*iM&Vvs~w>?O)`3_nUZ60nGwrmQ_v zb#mqXeBdfU`lr`Rrl75cA;JvElq@U(YsjW z5BIQ;2;nekg4--|KdC7uIAs#4Ki#jtF%3q)&5MiP1_Rs&MQ#-lebl~S|qY#0|Bo>^^L9X;F^Oc^xEw@&g z)KuJ8fiXAKL zeB|BQzvb_I&{+By-vqgtDWQy<=???kEWtqOm}!=sS2xDQ9UKe2W*AnT*XK^|s64GM z-({tq=+Z?a{I;Dy29c!CR?i;W!srwg-1WJvo%rt-(s~Cztx8@zoAy7BcNOL;(X9N2 zatnz`1y`-K@NIxiAph9^m&M)|E0WyE0jG>o)NVSrBa5`K_DgXdS_fPt)9C)TqY*or z-fguhRWCiTR94j!;OLGw;kop`57b z(bzPf>;sT^jTi8&SjF9n{l*o7-$=Zu7hj&T6>17CVDxH}lLP5pVwP@%5_ddM$o8$3 z+^R8+JP$+%(@`^D3Lijtq573;8tv=aw6bY0%*s}`(V-Xp7CLXUy?^}S)^lnz9sx>B zzwA{^q;uk84}{Pe0Jlx;swc7x^X1lWAzsN$(4&ncu`3~fItA_x;l@SfoA9bfv|-j-=G+B0pMOEBc{|PA{wXl}9Sw2|o7lfLxV;QX zcN{mCdW9}blQOMg6!Ch@NIut%O?8h0i4Rpr1}o=xJ_H7J5HhINpOyj`%xVa@Ytebj zL;a<~Uu}eor@=h#tUu=0ep=C*87r#hH zQdFH>RLi*)e%5ognTx$FtOIn)1H?wl9IZCE>eVGb>09EHPthuO5JYVD#BBf$qPoea zRTd0%o<{^sz6?LfbWAnYTMPc{p7mf=MJQVOD#%s4{Ao1~PS98#$5Ekk{YAb*6sSh&4cX;_C(qihNZN3Hk<@t zm5v`u)AEd9PK~8U_=y zuq9cH_7IfeI|$psX<4XgWxexc%lj{%-g{8mfEMy9SAzEcvG?XtO)F^Vo`;97=zlzk`Ex`x%K7e#W8 zActgwfMQ3v%`9XKtB7e~nvt8}xdayNeyriZdsJpqL~oZNHFsoSXv6uLLxiW{5eoaL zaVhgTQTii?z7-#}*<>Cp`tdnldr+!uu&RSa2MPjdtylVcEc6ydk(EF>Bns}_W^X0x zjqC+VgTXRW7zdR_y{9-go^xl+A|uzEhvpv{q4}^r?)3- z8rr9QX9z+=<3XjU4P2cw|3R)NQG{yL1S5cd|8DAF`#$s2>{h;}yJtpNgfuL%r%a`_ z8ADPK4|wYn@(iZkAOKGUFOGhEme=NknZcn9U}e8MIUN09m>E+cf?@1Nmx2KxY)mQ| zc>+Ap-CAmSY6@6RN$3?VAe&ZfqrginteQuRY-48#I{ z8`I+CpsgIViZqtCKxU>}Rf^4Jm6&VgrO7oQwUV;y-dAh9)i__9<0wM=;oHD{%rF?< zf?(&KW1zH?UV|nAe3W#~S{63Q>wy&N#%z0EjC*P^y5sF;#rOn_P@SG`h>)%v_@sKh zR^CCX3ADJEn5^?{T( zTsM2Q#7Mdw+&+~b_tHl!1F%&MESDLy65q_C0R>mP&k_tsnP3?|n%{kOk5ez|BUt=C z(Vg2+kFm{;bkK`gTWvnGT(yuan{75n(w!W5ttqAaIJ?Dl?^2#nJm#KF8C=J*bohhD%Fo~+oZ?`d=z(ESd@b2k2B}mud{7k z^;o*|Gy--!+%OK7WA~%!11~`s>REPxbkN4Z?Q3v-dsY(809rXq$zzZeNE~^rl+tb> z+P-&;cu_gj?NvWLpDqhqv)b)i*=I1b@0s4Gpk#^e5rLk_$X;+MSnuV*MxbF>d2mbn z^J3?+K|qZBf)34fYOl`HK=n|&tE+Xs9Nt%D2dh4uWwQioB%3V-ES^h@y)?6Wg6nqR z>~Ui;`do>71Nb7=w*H=Da}CwmhPQg8_~?tKtDA4Wzj}ci8@=tp48KV+0=Y3C@_7CM zmt;pC696%{Zk=YvT$A-b>fLkvo|fX8p$#EbE*RMPB%qOm6YLjEaXkUZm7`JC9jr&^ zQm29Auw^8W^|X!tA4qwi5s>@7i67tG7#{7X#X<9Bm59!k9K>uCico$*zp7OieX%KX zty=4|Gk&@YvsOC;em!V$HJdQqwxC|c-KB<0f`{jTuQkZ7doqs^u0i;$n*^_#F6)l4 z!B-t|-zX33UXBMolY5T$s@Ndc2-!*%jPN)s@GBpx+aw+y?`&^&p`KLZ--JWpjtcLw zlN_v~2~W^};5N>*D|a?y00kY6%Uk_I+c1Rl#~ zJcR;JeA9&xf#ol;SL&_GBPO(-dV$oy6xDSicQ?R$d+ct%kXLidu;uNp9hF}N027~i zbU)JwxI$b+xrTVc`m@-MT&E;B$v_XgvFQ0uD0oJ;uu=yiCJPQ=txU|r)R|ay^*eV` zj~EY{G=V9984JSKQfl6jh-vpEzE*Xy^8 ztnP)#vuZ*Et`ZwwYXEgmQ&eg@3(vIJ#(g~6Z)KwM^Z(hX0_AH8XB+Ug1L!U18;hLu zIV*vkZ4cfXHiDu!Qs#1k4l~G-=W{)*F*+K)y!&baxt)1g`kyF zb`@r_(-pZ_b$7T+-zqrpfG>46AbIP7*Ue#VbXnCvdomwmhdDXk2M8w42wct(omzrE zQ_`K&%p$(`K2VX@%}^WXtbJvg&hP%38@aPT4Ezi>nW2X7Xhx)Xq@PTTjjeyV4z~pw zFSkT=x}tGoGb^j0eGY##{yH8g;O$ro`V_Dsdv%P05hTC>E+MyqYDQ({v+@_0lGky* z#)B(7>N+e6oso{`crB#|fp5kT&B6M_o)=yyxF1!GCF(pZf=L{mra{{6MJ#=6`fnl?joc^yJb=0{h~r zQ@~Zj%%p>DfJ*DNuwt1Og^m4!GSOK-i?jbc?-$iNiJqWN3k$^0sxmVbQ6&*if7Bmu zWhLz^SPz5A5$R6$SlbHhC}{x(nrf_0{= zIRvcI&O1q!S@mhfi_!ij%{;#14J2T9Gb;v_dOQyvTP*VSv-o^gz%)uz=7saqkGq4y zr^|>Ek?BD1XwpFG>Yy*0GC@3rp1>57V}4fDb7wsp}Y63pWgF_P2`9vgs9elR6y*x6ZiX93p?x&z^S}4`XC_1 z_e%Brcu#yN#~rXlUHRnGb$U|>(-;Vd=t5`${>m(SnR&cdpTeOqyM zI-r|bmO1OZ4Iq`F7FC*6Y-k}SaZeppkLSp%X?)pwr(k$!7JVW^^fdwu4)SQ)iI=YK z`c^Gybl+)!5y?{D4&cM(J7?X(k1zL`;MT|9APb#l#;d+SRc>JYykV@KvK z0eAE-79JQ64T%2_BXZk`NdlswBmdcG|BTtKVX-g+5&3ISZp-oUj%u6B_XeC-7pkSI zrTZOh9sa9u0ic9XGID<)4{%Q9K0k03!>0|cQMhkO^PC>#>U*{vp4~MFDNk+v>d{`m zuRO3Na(Dq$K!=gRS|l@T>)r0Sr&@)oE|X-73d~`!HLqBqs8pw1Rb?|Pu|PWHpq~dg zezdjGoDZl6H3sli{u0>&;0o~P+wy8UO|c*9$xth=R#+y%2{yo%65K$>?16GKO|?^o zQ24|aRsw3}a`H0D$WsSEc=NsS+rD>0rn%7~!o?ZyS^axP3ppcj2GAb^Nne4qhnYm{ z0|koVkyC?tP!bs?9y!eeU&H`2(?wagve3*3l93SY>4K;;h<`Gd6to$|?F)1rB%OO_ zr#B`OXT}vJLgFt=#mRMOz4DQMZ9Veu;SOF&Dv^ehL>V{=uA`fGj#plqwxKn=OM1D~ z|GFDsd@Y$D)6XdHvgW*Z^H&K5j5L7}$t?|YL0M$QB|j4lw38Zfx7e}IPOj}<&AQt< zvSwxm5vu+AxYK~=JD)!p9O|W?Fats}?=i!LP0-mEd9EDv!`vn8h%F)WV4-01%#1(1 zGqc3Z#)e;>o##&Xj+TVQ9`|)8B|sQbm*e&{3RI~UNI&AD!`}u@G!%eB%HrcLWe&Uw zW9cU~{xGwTXO(IE{j8l`-+}gb+!1d%vxu;OASFm4e*xWwjMxRKt*0jrKw@dUX8DRN z{C5uWo@h})L(@|m7ML+>QNDBFvh3POXUI^j%0{rY^Ti&mXKtmhlBW9v_A!>l;*r8!@{O*IGN zRYcLwSH4^E^4d0#eTV*@OMijxxLYhjg6S>>8?j>CQKQm?r{cqE3EtzltLs$AE)dBv*O2gfs7JFwD@|taqeNL&;TBfwb{W%tAjeB zNC$(pJp;iKv_yB38UTYDu9tNG3*_-02_2QhT-pnCd&dD~)aZQJ;@|$gICY@0$~l45 zrPwgsQ==p^dRiOu32b{phXpaWEF0an>kdhH$_e%gzdPBRA-U6Pz_77UoiO>=#vn|% zm?$I+h*t+KyH<9!WU%A|?c;?nKnKHiM_r-zo$Qqtp(=~zSzcCY-uJC8I{od^2!13&n#Wl| z)`;$Q&{ys3F9-Y*8UZAIW)560kN__OsXF*)H05sNI@MwH_+%ZF?Tmg7i{4a>K2|~P z*o<1KP5vmFl66oS`|SMg2Vc#699DfkyRiWjEjEK91oS_Vl8edu_9;j^B^JF(v<@I9 z;jSvmdvq}@`d=ND>sj^#9X^;rUMVPW=mBvk+5djHvR*~e^8j<&UHKu*VUZV9Lt_gt z!z)!$qhG-K#)C6}VmT1~YvL_v+ z$^b!DG`4*UDgnN{`=vHMyobg99R@D#-&BULcg=KLtjqg?$dCF>Y=bD5!KTeVkOIC^X7pbOp@gwasQ(I_a*v zoieDzpu+@Z&0?J?@CD_7|1j}`*3k9`NR!9W^Tez>sq&u5CHlC{A&X#}zYoa45Asrj zRu-eY^x-I3teZx4Dp+W4)kJ5D;ETlb{mfXIAlsc={~o~r&IwZd>!1Jvgs%R)6bcggRY&C@=xWq3E4|spB9@g33;vhyik?6~s(dvU6Tm^A zPCpRj*8Dz$&t}UwimXW5Fvtk2D$OD@ULM5uf?)|tD59m zxFXMVL&5=e#0Kz_#XtV1vCW{gRt&gHAMo%CR(DWbQZA_ELLxq9}`Ta5cX^`W!_g{jCNmFs# z{O7}Z4%$J5Y!^aR!NK*dhL+r9sKWiFsRm3l%R)+6@~HaLckT{Om8os))ozH0>J|&6 zQ>NJAqN1c0Fy_tP)qEYOHS!YZ+OSGB5Q)?Sh4;V>9n*b}Ym#Ij^(SPp)|e`k{ zBBUwqBypC4U(h7}hxaUeqUEk|39!bQNfQ{}`l`tAlae?isf=6n9H+cUd`?sJBypx9 zd%fntBAQs*TrGjZ5=GgPYKP5pPw3o?O5KJuq|B6aKtDp0^l#r*BOaQVyo?6PZS?u6 zS24<`+5|>glnfl##9#q@^nFE%Z#A5hj` z$IOFWI+of7PI~;ucU9@V`i;;DzA8^|)B1~JHQvtY>bH@nyJp;D4!gk-KXqW(4*v|c z&}B9k>;tVvfg7lTB1r}dX#?_RkOK)Cg_oc>Y)P<7!-{<-ytxR~C9rF$tV;DLQZk58 z-SkFQN{j_KX?HS02D~nKI+XUtlr|tLlP@{%1dGPp!F881y2Mgh9rvwVSdKBEbA@Ha z4Bk{Hc3Md2(??9zaU*0=B!nbj6DqZZWqID->>G79xBUpkdnQ7>gVCWQvxo=cSarUG zx>guyMdkK!rXF#Nk_1*W4T7cG;J)MK2L^d}hU-Bw%!TvxNQ}lYb>U?#Gt%q7PzHVY zF*6D05n*hO2jRZ5>Z&WaPPe~$rsj}~oPp9`zFnj_d+udVw&W!gXt&QrH?T!{7UgWd zf-NhZO=YL5>yz{Wk5IMGG?<(6CPCeYmXLafN#@*j{5wULT-#25+TNG zp>0_ZK9>wL`?;|T8g~Lw#c9JixweD6L}}&2T>Dd5Phhht)m~BBoqvXn8b@^}0qi3K z@rV=_h@OC_G1jRXpdKgshe~h}{WCjZorNd0JIu3d3ljJA$j@KSroq3BSR4H_E?*g& zt|%(Yh0b;5X9)?|2_oFeDqI2-5d#hOMvvB#n}8ZW!2PG5dK zCb;wOU=-#H_1a!tYVRjQj*13=&_C%j(H`{!8Az-fI_Udzsm;{2QunjGE2Y@0k3Qc0 zyT$pN|54EZpBL-NQ$wRb4JQc~J{>rL0=pHE7PPyrU`v60Dq|fj%`h6sotSu9X|Ae+ zd=UriSyfgl+U}zL^s9?&&)vP-)z+k`d!^y|6MqTemCZkMFF_N;Gan1JG2MgBd!0VmZ21 zQ!a%@f0v*|g3+Hcaz7vYEh5D^d9leS<%c^pq}lQ{ZzG2C&FkK6N5T;DG(NYaW2VK z*Yv?_-j&lv$qD@>_1@jZB+RZVxym5Zjxt6*N6xBs&W$wO<}oqE53L^caV!EE>I z9C+1CZOba0k~p>NK0Idd=^SS_NKM4FfHTKROc7C*IYI#GKs|FPjMxyjuFjuTE9l z$-d&IpIBJkp5dUecByTcII9awjvFZrHNGNyEz+%w^CbC`6>c)q^8G}2whW8vyg5QG z^%G)0g6Uob2N|IvlE=FXYUb)a)l}J@JVSfVyJ<#iG#mf^FroN|#j<54g+5_VgT&^? z@2yx^^vs|xkihDS!K{>3vRCr9e|g1|8T=YF8+v+X=W%h>!m7WsIP19#J+x_eAu7+1 zfyJaDG20OF;qG@-UEB^1C<)XIJY5B8AQ0xb$aL5eqjjNeuuWoo;1qMq=&6m8b6v1U zCQ}ari2f~sixyvm)}eJRY~ia1_xFAS)o{v1yB={F?l@8JBn^Avycp?@OmPRtL=-MU z0?u0*K)9`vPg>D7tH_K}wqL_uAt85FR=3l>cb520#xN=OEqTOPwQcNtrvHOlk41~YWeq@S-@+CiLZ1S+d|mvqE%HHgEwHhacg`Cz zSFQvN8mN}+3vC#MiGA27O;O+dp!4*VamqslrrvF#PrKN}rIJt2b6ic z;^`TgeLYsK*QK<8|26Cgw8wa z{Vv~RbsIt6e35+iSjQ{^fY%J|eXo;tC3iXpbihE9Hx_NX+u46>*u!RV8H4R+e0!jD z{=G?SU9ansbIe~8Zn-63y09{=O^sb{L?gJNuk4|Eo77z8)8e`9(Qo z4rVCQ?By~+%a0|r0Y6XXmeT4~Ti*^Y0N1Ek9{sR_9He_1KC ze);}yn@kuNePlaJ+qF8^slBh<=ufqTvaZYv)(l^5?+hVAMFxAM^S!8ni2px_6Mu-yAvvN zyNh4QqeUL&o0wyU-Y)osUMCz{f{x;KYfQkZ+!BpPxt1i{(`q^^p+(5q3ZHYTkR`Hi zRo+rMvpIeZ9BEU>j^eynNa`KA&RUOOAkq+qv6?XzjI}uh2H}2vTSK#B z`{fa{rR^bCYqJ%bh)PT%-ex{tuO=OmJa@%ZQ{nRhC-$bovEZ{va15C5(mI|S=^E4_ zIIBa%W;m2~sSG+W-Qw~f|2{K(h~=2XX{D`Gwx^3Ik}sro_7}K`th#ohbvBt|2tTDd z=s$&a8O_n-KXyXxM>m3t*e-sVDcf_9GbMHIhpc80Cs6=;@+MqKmf{d8DtQIqF*Gv< zDGG~kcrep%^=M-)vKk7`e7w$aQRLb7kREVUxy+A{P?Jv2&)U+RF}jXU^%;7$YSF8q z%7igN5lS;_W8rg;-^2pbb`>=za{7IEtJiS2B`Q!+Ir<%2awEFC*P>`{3AF~( z(~9DaSOF1~(mUIB66oXV7tNgkNTeuau`>U}Q&qd-GCB>>_F z`Fr7eIZZR|*v)>#`TILBlt0Oh!9>RDBRBa^P*aP#K>oTKwHzro{j-o4eEZaQcjbq*;Q+md5cCwu*C+(H6}1!ez2L}Qpp($>cDD4V)z9eFpu@@95mCe$U^xCw|c&87j5aDEO4`u z+-gz!w|H_KN=Uat$gt|_V|Jg+bZg$HtkU=PdgRBMJo5-I-`?n!9q*lpx?+=4nQ#+* zX;o>(*kx40$yA^Y1Ga9YNDy1B(7l^S4`c==iUZsvXOix%ZOEnDcfoKk_m61BEg6zRVFB<*{`Oo{v_2Z>|nc|WZaNM^`(bzr^=G0YZkie^y(Z-Z_ zeGr?*rXb3E_jBNnv)gE(Knh5!a0ZHB*!;$A!AW#yZMww&izjX)yf(*0U~gzP_)=fG zSF7jHR)E4U)@kf=95|F4u`sxF`Rc-vlA~wX91J*VUE1YT#FvokZM03o`F_4#edMv~ zWK&a*Ia9j*n2GnI;r_w%mPbw&Ga(0Gi4# zFDv1Phacs|hqAeQfM_8YYOc=IO7 z0{2NG5Gok~19$yVBS#hMG;ftRgWG6B@yb{?K zS6+blx-OgTeA5*>~eYe6Cq+u1*!B`$kr!detjF?{IOWru{_NQMk=!!cPN z(c+IgN)(qhU>&o>U4>&xBBr==a?2+${&xTEnO^~jcpbMdBS0Rt3EC3o3vg8_R?*0i{=gOEK%d&&jv4y$2IbwJBlvHliv^!|p7lhI~JnlhSxKhG-`=Yi5S1$U= z6&yLJ?+_Pk08@j-S5J6cOrg$S4Q>JQxOU4fw}WB9i$B?NQrFa6Jv)=-pBARr3F_U3 zd7+0-lnHL(j~j3I!uPqOQ^q*ch5Yfh*V(cz`gmSsd_b@ckspjC#pTgG**_RFZJZ%a zJI~qQaR{7z0qdkPYAsc7xC5_#Cv^2X%-U9MZ_>`0YeZ-gqn3BO|8+tILO+c zX3x{cFZL`S-g4Tr${z2G9<6Vm#CAS%NH_`C*qf~|2x?J~EX3jfaF`wWkaTb30%Dpa zUN5+0Y&B*W#5%w`rVB^H^W%JEw^mif&x?NOb7W?V#b2YE8XBWf8?}}`Yxl~3Vw1Dt z;j6+d{NmGRZB4;#AnpC;^DBtI3O|n1#nqpjD<_bQ8xe_j-#mbQK5zvM~Qm zljn2nN-*_bxYJ?Vj~K59_pQzCr~~T&vTyFpW)^j5sy4Z$xuAd2y!xazalVP4(Z?s5 zSd$W;Oxya(zTv)Qx8lZaP^Y@Ka3{^%JmHSkS1`JfvXzc9zUS3v*_@qZB)(0eARQ|! zd1m33;Ju$fw|q!1P4GlPmkfpFeFQ7ZnVt5?u177C90j;l39>_g8SlJPT^GoY8+iPY zIeX!TwipKuozmq$<@+E&m>#`<<|Z}%+J zLfcu2^Pw>ue+-NPfEncd?T!GO99JGC&|kd|W{zDMCVTqRE3c$YZrSk=NWv_Q9R-29 zxsbnwRC?l}J;th($mgQY;9K2GF#F*Q2UyQ72fNRqFj&1VZXZUyrSorf;b8kEKjP4{ zY)`h=r%$r)t!6ueIGT5HIY!SG1m@y{Xv^5q2spnZyN};@o9u3jS@`9!=Xd*o56B8f zFlV|v-BB&g^u7u|PQn=KOpAIwuzvHRT@KCK!+(p3?+N*g8$2_;#j0p_n;4Pf~{ z&MwO_kUT07;e&C6*5nMDxI@U0&j6WJRcJHqjjb|*@2OWP6#it+Ye9R7Ge;kWr=nP z!S_0L@8=|Jf(0(fUU&H~VgFaas@s~pag)jbtf*J%RJzIpq%q2VD;0+q+ajP*trR*0 zxO4EdilCAYjEF+D)ik&IN%rH*MJOeegS{x4ou|*Jbzl z4fj=i4(lxZ2gxo!2*pUd4ary~#lHBS2}p6V9!lW@pfVdbivN{a3b)Zk&*0ah;lRsO z2YTVNQhKpZ<}*77nd@n(nOZ78rpIq++s)airRERktsv^zY6OTMROaqvWzAuXI|5@> zHto35yM7hLeO~mUS&UI{riBa;REJlAeMY)*40)TZdm17JVM@7*wOczwfCKH0vOL2^ zno}<%l z;w?K)Gd(-`t?L}ETTMVTVp$u`2*=>Qro!1a2_=Ti^Q9lc@cU_zZqUISPrjol@5yN6 z$plLojwY5H;Xr+2Qj^P&5}uPTddAMlrSs8wcFmWLi+tM7Og*;IEZWAzYM)q`F(g~2 zPUv+r!38&~j)NF6UdfBl4GmujZh(AIIBZi|Fml^8{D9+1u+1q60_HRqS(qQVC#vz5 zO&Sqay@`1D>%UNLTX2cvvdg;>pcs*mW~nIU)z4VDEKHPw5QDv-K(*hv=xshUP@1!1 z@>OB|?^hfy!Kd?mRGaKL^vzHyhD6g@BdpX7MxWFw!11GH)C4 zLD5eWp3@P41o_&`61@g1*HoB`X7I>*>3~;__K8@cF!6bslXdAB7M0zBO+<-{8Y{U^ z*0IJs3ACb}rND~DhzxqE7O1gxihRF@wf&&3_FBtcCQt;qs67o2xCE&-$&e!xwI+M3 z21Nr#9CMX44S*4&5vSA!>=&_BC2fLkwRcCRH4c)Rpfy!!3URnI$^cp#l16^|jwpdG z{g5G=?V-s#+XZO|dzy$DTLg~)XBrbGgrR#X0PdCm0A@g%8kREP0DXC_+V-kyUj~61 zsuZe>R05o!Bq4>$YXX6FT&hq!$mR6kjBPQ*jjdtG^NZ<%83T) zeZ!doUp$9d$vuwD#uyehj61Ro3wgm8PId*CoWTR=r`e=wV6x*2242M{tqD?ewzHK2 zF&`09xskNMs%C!N77gE9JtJ?|{GRXgq%ihc<>rlRzR!{W^6dV_Z;HGEKmLrrctT1!daki=`Fh!~8>3s= zBO_Yk)>=RHB0DPw+^FY^oTC^GqKRRNb+OlYEEz}rf}%S3rPkcB!LV&RBcFxeO%F6N z;SFS<9P8;O#9I5EzT(b8qNLW1LMwLLg!OY0E+Hdi@m(}ph=hxrdeTBt-H=$7$Tb+& z%C?wo8DRVUAuQp`N9RDLmnjM#dik{$d?>inQ*Z6zh#^}$Oyimt%gV-6_G#Iehji`4 z@VUqK32)aakH^#%*U8F@O|DcS$*kjX&ij2goVnVZ^5}YM8C6iS$`ii>Q@p&R^#-U{ z`n*J3{|fV6a>lvOx38=88a#LRBTcI?`M7Ge0$x~3Boj(bY*Mz4iFz$fi33G!<^*R< zDYI^pjimIwtKKA}pVcx4nmRbx#T(eo$q`Vd%iv3koNeH_Ek@6R@uFV+5S5(Tg-8Z^ znz$_LvZscN3$!?tYwpb{;OyjKG2eP|IyA|op@`eI_!U}xdn)yIBnFLYXzk-3b0G+y z8`YsKGxE@(qY=CjghKxHJ~T5dVdY)2C+Fzi;dz^E=|#SAD~|>^acq6ZgdTVARr++= zj}_#iHw8SJuA5xWycP7av*z%9t$~SccJrR?XnkpAcQmXYMW_%W103rSloK2UhfmVR z1r#~Ug=0Qa_>zE3^c9KPuA2(jVzU72fr-B2a=QJ*-K zW@F6v9%@^67$IBU!lhowliprfqUun*K2F@cZ}Ofi5E6E0=Lb!e_$h$*kRr@ZnE{xqMk z{zkm^nd|He2i<({-^xQ{e$n*3QCCvef!J1R+*qnNF-`#giK-WAT=0;Tqm=R z;E&|G8SAY`*y73dwrU=~GTCL^XuA{h@NXtr)_jRBkjGDc`w-8I#9@lu<$YauRXCxf z`oYq_c;n`4T`OdSQeLgZ|+-`efEmxFK1(GFlU$IF1DYTKxu_IlzcV6eu07Yvht z4uMII$hOC6enO+T5sb z+4jjdOWcfv+w9&rw%Jrn{X7P&$rKWkRZqCil0VS<%Tx|Eh<(QI0M~;1wZwe&OW-o;Hn`|P1r#%4Hn0GeS?k;nJUPi1fZ_^{3R zRazG2CUmQpZ+<z9GhPGaUFY0VW{zj}nsOv(@@sM33K_0r zfE$Wa7Lnx$^7z!9h6pLibU4<9H=rt!wW}(0GRpWJ)Nk#LQXpmx9;+WaDBL4=(>J$y zQRKx9KI7J8^rVio@~#=8$ZZo16^RirsO1qY`|&|An1OpBM|fyvg2c&ht~A$}$2Z*2 zGX9QfU6L2G|GKq+9eD5&wl01@vT2LND7o3n<^`Cf4>G{#^@V2zCA);@GZ`zmiGz8Q zN*(-bZW~%hl>9gIOnIc8cMD$Nl0oXo6DW$nTwSSH!}Y*)RP3Bvv`U#o6)9n;JS7Z( z(ow<&5v5h2lsd3Qy|{|-h;c-Qp+}c$OjFh=d&+f+RSgafM;b;-n5n7>eU5xY%jXJo zZx-~-XLD+7ZltfDETmLQI3VVk`mtg|cks^L_Y0v%jwna?$rQ;QQ|y7G0P}N~bZT0I zUxvX_b~I>m9%?Q>ApPPO*1Z_Zm*+>0G&AS^=mStwo8 z)9AK}))kyg!Ci%Z49l7Gu(dUZ`>U;|+ddy$X6Yo?wj<&EXj^I4mb$EjDozJ?!KEGW z7Xu)!{dz6$&P+&dR*mz#FoHO+)EW1UBmLW%vR{=Afa$n2WizD7l7bJAL8`2_*oRwk4SJIz(QKn46 z&~|uUt12sfA-Flnu+s1a@Ns~d`?Xf@IS0RcC?fDjneesACmxy9*1NCy$F%mf)7IS@ zu{6y}&l^YKLSBqtZvT_mTLtchh-n})O-=hDjh&;7D3Ea_W6Fw7h47|f zjsQE+P?Ug~;@eIBHh64S+8)2Lv5RmJjk+0@P9)5*f;d3*XVv5Ma`DqAs=SlwWvvqG zWQG1MIP=Cl*xNnV1WDZpJ8+EyDAj)cp__vcNR@d5HdE&BSdY`mtzR<84Lu?7iBy2_ z@*9_Qi%=Xs(HLWVX7;Oys%u|}=`{e^%DRwUimI;4DJqCRE>h>LRNx;A*u()&x9cWk zldc}DCvj<)5wud!dyjqyj3 z`-t&&OL*rEfZH=q*}-yL+M{zX){H6v>*&f^ibG(^Ry(gj7lqOJW}FaJ)<8YJMg#Wh zs(|oM6FzWC_PPr}KCy{Y<>E`bMWR#`S}Gg7C}Hdn%9IOUG_inyJ2hTl7e4#Wx6iJM zUjrx)f9jXF6iz!~cni3a_XG4{W8we<#i|e33pODQzJ9tB;X6>eT>5Lh1yOGyBWs8lf9+Kgw&kwG=fozQXK0F`JBP1)4$brW*WCeU{@#M_ zC5MuhsxZ^E0H7s80;^?5`$I!F$S4aXHp04ub71#|`kDW!xDR^e0!f;8_f%vlaRAw! zlgvAt=F3(3NEH$iAB%-1`2AHr`5z`2_$t_bBf^L5TonQ+$qon>_{UXVMb1^jlva}J zsNM3}cU6CRUm*oS(cqPWX9Wk4;RY(`aM`cvV87q@zX%@x zlaW>tLi4&On`OKKB5+NuoxF7HQbGH`_~gz^?X=_AV@IKz4xs1I-YTR@dG2n|s%F*G zAqY)?1-GBI@%0Ar__cyq%j>4NV=o*93%=wUpZ*Wid&}knC3O3{MR9af2DAZZQ#@f#thsA@=y5}f#JyCf&xv0*1rKnHMLw^? zj#!O9i5S<`i>lVFdI-EAddX*e=0CXZi#6Y1R`~#<@=AusvVI(>0$v<73KrFVTg;sQ z9Q>}0O-g3=wwCc~U4SWwK;XByVV+hVZOk^bPc%$EFYB26;vQIt z_`s-)hJ;R}Ax$pL=zfbd26QrT{!g;fm}U^jz?1&P2U%m#e#(?BXPO);l)nHp>5{b5 zi2;_=#3|O)25<~IgtF?#dvzy|U=)H$weOa`O@Fxi1(ZRR zVy3&jN9+1|A)Z1F@_DH=hVGkICBWS(4(k%yZ?q|0uLX-XTGB=Thj3A`(>+Sus0kEq z2$tU-c)a$5aMCU2gejo;p#cC`PeUF&^YkpdEK};g@S>E{>g_>dfGY%8A6Qf=JHyV< zCR9{7N69ahS&3KKJ=f+w?hD1zBMISs^iCR8mPHoIdb=vH3IlsE8)O?ob@a4pBLXB$ zSW#qaz|P`)&Tkp16cLwZ473SGghVGMG`pg?8hwljZ!5Y z>>|rwf}jKH4^gcOQExyuSemFJs^pVl+>0c=>qVG}Zb9VRqyXjbgOlqdJ&!f3`Y#g) zc8^Z%#RL3`I!T$*A+Ml$f~>rvK0)B4xJj92tObq#mwU1j!wJ;6lU8mTdzcbi@R*V90mX$I;BO*gH-lDv)X){+2LyF zUgTzo^8m;!k8-8HW zCp@54DFR-&h^)f)@d*i_*kCuz1pvVgRmo$9(PRWJg6v*}mIwgrDJm0m$hlnzq+de# zpfs^zq#KEY*qC3s9xQsn*PUE7!ThV|E*S4zQlC%>kp?eHl*wI22T17=B^2i>ewWf7 zIXja;Un|w?)C9n=kX3G*sKI1+B~dQ>DCn>>uFKU1s{&IGNTW&$y?&VoD-V2(;n@C7 z;DI;xq5z_}nDQc#zv`toSKTlg?=8Y{Lm{s?L9Uo)gT>E)r_rW?2&yd#PCbZ$HU;bF zyrsNWPu`D53|$uS5aGZkAzn^EImIHcQjdPa{jHV_T-vk?$x-xseGbGp(<`f%{Ee}- z-vmJul&O@}AthmB3Lcp--N^Jsc3hwz@J~A-ZvqJ?8VCY#a3EkHc-u}B;(!n&polPm zQ9Ysya6sy6qV!HA_~2XH^A`X*#7!C1o>di2vs<=grpeM zqQ2?7`$iMi^W|4v`cR1r8smL}Nj!5?Npg!V1KuCNb~jWti=Wr(%R7sZQcJvTaP=i1 zTcr-ko)gDzO^^8$_)O@}eU>O}zcNBPz(9zusIu43_V-{OH?*AsB8C;bfqGCea{?op z2DHZFDoz-7|0@dlL$AEqtE3#z2~HF!A@U)r9%fVwh}@H(WOaXstp^FUmLQ}Tv3Ub! z88O+AuNLF=1y;WypSD|tb8AZNIkM|rx1+lJ{jeUU1_;&Lu!f|r0-T={otO3jF@+_y z=O9&)0>F4~j73PZ8<-O=U}T?U93ZK<4J!AV2N1Q2Wix|H0D`p$ok9^@DBJVB0w&jO ze*pnil_1r<4-Hlhpj@PI!=3_#(Imjhui%+ohHM+k)XN5DZy&sBpg`-~KB}3P9!+T3 zEqkeh7@VQvsT^%VqlgEB(h^`Z+7nxDTawQ!o5n;FL=G8S1NVC|6J@~5%(67K^s4%m zOG1^eKyu1x&$)#xII*c4R8W=YrqgN+RO_b~KfOHA8p54CIy?^n%vjJ(bFT6yS4vKp z5-n-8Z{5+B-cx+FVUb@?Y@4Tc5fBV}^PF=5$%DaUdzYO4b)JGlNA}NO0ha=NJAtrD8!g4NCrj?u_|AzQ}7D) z8(^-5fh}hH0dBt>j2OS$g`Us^3uMS`ZQKtyS{Q?$(hagGl_&sr?OcicO&5(6ijYwO zGDixa`lp1$DfBJsbloHv>!4waoT(=zoB)tn7DzCk`A9eYzd&NOSUs%Bw>^!TkawSy z5S-<2#zYkRs5cSP39#fX0^}xTCty=M*`f&tAj^hOm7;PC@@i(UP6$pc0;tCeQ7KB; zlUyDdSrpme1j@Fg$`gj}sd|OMkSFoc1aVphUr_)R>iZ!yk>-JfAu#y(tX}-d4a}|p zK1hP1e*%>%;d!v+v7qV{3Y3jwT79?Ez=&3Wucrf9R3i{EV;$Hs`_XrQREI~*gjj7B zN!8yl!goRe*DeL6{8eH3zoghL4PfWp;#|OCBuV)7b0>7(eYo7!)SeUB4#E@{Y6$Q_ zHgbyWE{~AE{ns4}AdI&u5T+|f8>gJ1{9eDj`{dkT{7Kq*aM?3Z=_CZ>O5}B8)7^bQ zW8T7;qG#j(fO&JDo&7Vcm;%+G1i*r*CpOW5*MbzxU0x3yKX2#D^yQY5f_3nf?yDOoB&}kg@OwxP64uj*s1kP zMZUa)pMw3~y2l#;O_vOCd6bjHhUbOmN=$9%#(MW>wRw3kw^5V&_T|I_J_L9TV6jVy zwZ1L3=S_HI4%ux}i|uLb1+qI81Ng*-$X72XPi#tK4`cuy<{e^;VTdEf7cof5q|TgV zf*5aaXd~99sf=kvbLgzehmU`frWQY{2jZ=uqEHL(cIU{RYUd>zazMpKp=9USgl5j|nnE>Sg=ki9<+zhGy5)P7Vhoe$u#^ly05y+{sfk7~bSAnieEjK-; z4V0iFaihwSMtC@Xx^Xv^2bDQ==c)i7iu1pJeV_q@x7C1EG*KG+Sc&)6f3*&;Pp`XzWIC27dT8SrEkD$yFMYtv=^2o_*ud zeUSobT2x1dy=(#&9Gkay3_V;bTsc$F0Kd{R+K==)8_cG?JU_I5<9e#=T-ktrO)vk| zd=LL&+4d&UcLOGfsoKzovtvdVeT})Zs`g#?@V3k3_Mg~P9L6Oad_C7etG9!1rs8MH zHsAw+Tbq)XAF-PIR?2Aen|ix??wKZo=4y0s??qEBdg#OGcE3D6HrNU3g+NdBn$^== zZGU;!Ioh0YRuzJ9g&KN|*EGAWbUBG1d4UuYeqp9H4t>OG^@QQp;_h4oV(H9E-fj{R*1~tm`A#C&f^{&61U6j!&M$0oXch*=izHXh_ zxeX4V)w^nac2zI4Y0!k<*B$am+F$q*y(oIUbvCap^kKI)7=Jb_23ia9o##;5wis3!g&6Iy0_JMc8zf~yBch_ zj2G;gS9I-f5cPw3g7s&A`@ZsGW85}h0c)j~H?MQPUYIZt%<8t?B-sdRXM(5t>)D+G z6{ax%F57=0ZT+0RZP(T!dn|;k%vGa&(X110#{W$`N+~LSe#b<$7_EL1m+`o+60MLa3SZy%d=Kuo{#VTtW6eP`z!`4eCeB3 zkSB|pUDF8zq;p4uQy3@XlZMLI50?kG!7nwN6^{)@!PWhOl%f#Y`VP-?5~pQ(UCEWj z3Syq<70cCS#0cT86B*febI|C_3-htQHXh@ia9_p8V-;|Vgph@LmnGXR7-ttA8MILa z<2e2Iow=C>JcXrbe9dqe%I>fm3WXwf_-+s_UuiVZ$)P_w&s#y9ERq|entgrGwYs|a zUZ@i5c7%X!7Wm)dEn^g0zKgSE^E+Ghj{S{Rqk~)gtH%N=MRpNm%O3SH+xquobS-vu z<6fulY93EG|4aC_hvP3~_UQkoy>Ed^I$ihP`^NJfNU2G^(LB*Mk zWnyZ8W|^01NK44f(98^NpV^~b6q~{{uY-z;3J#-aYU1o^2Fe5!xfq$$n4lmsh2RKb z--}k$*?VTKeb(CRtnWMDcDdFc_+NOR`|o+)=Y82>T*WPtzW6U*vPW;Oge2JD*2X{6h;ndSYsi~X&RijgJp0U$abIQ)&lZweSFb2t zfk?i*_;l!u$G|A}-elSQNUlSbZVv+GG(Lwm z_xe!l6!nj&ZY?eoSL$<#M}N{dx96U~_Cw>z$NwN;TWt%QSptsX4qlu-T}iBu+`F^a&)#BK)sf{HIW*363XRy6AdMye^)NhfG-wYFFKHDgJ*W^~Ep?6ZYWgH_GM^}5n z<==7)bT*`Yy!hH38w8II)$1Zt0;?)AX1VfbLpronkV}Ohawiu31vGL*7%I@*M~cQZ z1_ryw;=Ub`+)6Br+$6*1Y+)?s^SVwj9wRypN~&AG%?M=)j<{up+T7aZ4oSPVFv!hc zqPX=wn(Ly6M|br5zd%JViX#so((%HuP>NZ`g(8yMxzWnwLegd~>r1*OU7LL#;!s+0V2?Uo7)AxX4gw5YGC&8HimMxFI9&A#pCQfD6HAI->;z04UI+0n+j2K3`&J?G-K@1>^t#o}nI<;rF4MpKSggWtRlnq3s1Sy-h3L;JER zUO1P@HdH{__v3Ys;-2x&L7KTI`5R@vC&Q)QKlDTz_iMZ2Lnu2a(ukV!h6EDBr2%!) z^?GLQ-@5)&`0>gW_ajxp8hTxid6%nJvdE=+r-Trr+>zX6%Qu}Venu~aL{<^l_Nq$fi+=B_DdFo&YZ17UOR?2Mv29Kmi|*ZEv` zMoRYXU2Ow zVDD{UZW-G+{I^)auY|pl+wt#;5amxg<*2SVbc~BtcGc8NW2VO>ItxgD4C#mQbNsR|G!D4*6&#E8kFMVX=LaP!j`|GPzQ*i5aGxV~BYQcosOl}4n z2SGz?6rS2mNk{YGnN?&AQ-m_QB;AUuxd-XeAtI%T6-|_Cw$U#t$6}nbpp?qGT=PiQ z?q$w$s7vN zZi-|YPVVlNgs|Y3h)5soN+?=;E}_4MM_TW;9jHr`!HMyBTueuf)Rgj@Y4{d+1BL&U zAs|Z_qRPFDON`fC<*$pJhKz`76_2JjA^Eu$DjJzQXYrwEPO0dqks1$|x;ag@(5mm{ ztA}8YZf!z(`bJq#4GcpV%B*HZ&C|)_k*dtE+oXoFMWS~5dZusm%~p!!p3 zA#>MNBcQmyb*;yip7z%bQIJqpLwT7ul_N_%O=CX0X%m zY|o%lDK;Kd?RVaSVZy}=neUtY&3)tWc|qY;I|}>Vx82i94{dMF$l<&#;yqXoDA!zx z(R`FqJT*4o_QcX7nRklUrR-*fGRHX+F3j%NsaO{QPjf&^7(6`_TR>3LNqzM-^TW{x zXn1XN)?loz+`#Km*SAe^Q~9kW-R$4TPx#I4izf$m^h^ALnJ^!8@!c%jNfsAo`22<( zj9XjY>tfP3^5cqRnQSOUrz=+kpaqXd6$V{%W@}R$E#roZh*EX)?uEa_vfn~!K5sTE z%Vv6qV+e$_3&x9pOfI6l(s-i-=Y|c!@{i4`Yk74msxCr(zP!glCG*U(gmLVtdF~9=+774y#v+5C!IHH*D}o2 zcpBY$2&ia@XuWp*z>uX^{;d<^G^sGH#aqNR6MAVPt|7LqFCmtZTflSdB|?n6+1MN_ zf`O|Nw6^2=DXR7HMoA+NDr%eKP30Vm?-5a2as6mTb_(Q))TI#Q>{%6J_HTDV|Jdb&1MG*LZvYSOilNP&0Wj&zHd|aC|6^gTRJq|HOmkLAsZ7^ z|1m12g5IO%g}Yp#_n3DGHpu)N_~?kf*L(PEL-*D3&jr*NreTej-hvNHp%(XDmBseQ zlku@=e3c5KID79WUdr}EyxzG$bs20UtCU85QzlDpLdYLGVFOe7K)<_F@njYiTYiPZ z-9ceMa*AqjQE9H%;lC1QKI)3CuoTdI))kyfP&IU{LC98rP`ijDrcx6IZqO_#c+&3J zVVIdb_}FXV4ul}`*RE%nD_3G;@dCL_^M)It|066uDrR8RSZu){CIhmdtk3be!^``( ze0BARN4=0^U6bOmL7Nq{)QzhoZ`4m#-E>b7+FEdlsnq?iF%~?wUnT4BSDju-aLT16K?f27ujx1@@*Vc9AgpB|o)%by|-64YOH zBom8lFpMT>?gq&m9mZ@RC~(shnHr+@Yu=PJvk(H5I5$n6K_$J@{3}`Rw_|4CTV_1z zN?S)Z@(`L$ddEk3w(11lPrIEVzrKUgSb6UTjelKuY0+|yI^4kjIoQ^T_&01klpqQO zJdq<^*Sh&PLmB+lTljHd>i1+N+fF9b@^BFdaXdzGRjF96ZJh1S)bE$=QD%iL*^Imz zbkB+oI<~17qnT|q!u(B)t=RcaxhCPn0DKS9dfz{F~-(dQxz-ZV%ytdF^U0~i5k%+ zZLbh9m~I!AZZFVOj^Z!SstM(vwZEnojgsX3!j;0NXEZv{&x*@wKkZ0WLN)=~|`cS#@jcJRg=DA%|Sr_*REf`}WDH5smPZ~TU#HIljyy@01yhJVps(Dp% zW)6Fr!b_ZPnl}8lB1zgH#qZ$Fc}#RluY66A;c^}%ZUUn z@uG+Ro($6Mp~uyP%>@^TDfqBy|6TajyL61H=q=Ng6ArqCOXWIWgalq-HwUsW%`e`A91V?7Y5!AQyvJIx6#W@CFUI@VV*Q=plqSMZv0Uc z>8m-E^NF!;ZO}d~0ye>b1|mC#se_7#}%IiI(g`G=VOG>LfY!S*1n?<7PsxhnDX zbJP+X=?tdg7QSlaO}Lt6?_1AdnWB+h<%C4=ul94)pq{HSp~UA{&?yC0g20zXaG z_HF@YbwgH*(Fd8Q1beB-?NV;s@&xpXY=BA}bA z@zz|2eAAy;(g?hmUA7?b-lU%v3@RX|b|9FYI2)+d)$`=6+FCzpk^O&kkp6|=24%}R)rg)}RF9S%F?azwr7*h8eJuYp!>! zy0>KBheSZ%_YOF5EC`bPAv?MsQgUQUWxgzsDr~UMh5P{1TyNMIqKr=hcPxc%6bhmUYE?}MD zoW-A}bR8Ln;i&f^eI40QnZInA0m?G3)H#ow2)#O2npI?9wy{mIF3+pklW{VTfk2rr z{)@(E$*0qdmHhWj9&Yoa5dY`)&={o*L!J!U7Ncx?9^FQ0Ij3$T^oc^w>!5H1h$jgF z&M}wBzZ)fAu$249Pk1Zh)-hn!2c~am24S^}4h(Z#ME9{;lqN!@yHjbXkwXNn3-dd? zWQjfRL9V={y&+G<7XnnKoaixB$`O^UUnyY@CJhMrYY;nA z&?kfNNB8X%gd)uQN`1C6PB&V%eVoxsi#5i@#t14$AY}h2X~+Pr;;AQg3zVya3LnD~ zSNRDL5rDxBWg72JrfTXG_rxG%Ic=*{R_3%aC>76RSva>Ki)xJ_xBdyO2>^Z|wjIy< zQ{40!>V7dckK9PARF~hWpW{_5(~L0`+aT@vj@M4y52lE!*z@5e(R?I{N{~Ob)|L=A z#lJ?ZH{9Sx!16rpK2de^1=SSBeoP z>}86VJRhLA6;SL16;wt_Rb$i5dF<}p#DqL*G}6$G zn}~HbRW!=s0KnN^6@e%6|4zVPcejH%yA`Q{DR%T6Ob}D zQ_Z%yN(?3Z%O$B5P40Ov>N`VQ|4?a#tOq6w7r;&Lx)Cn?ylr`g>-g;@vL7xjW&&iZ z0okJGg0?&S*mkbs!o~>07uVFrf{

    n%PpZL%YX9G&vuUSI{Bmq54_}ugRt!MO=00&pv(Y249*W}H&B4hxy@=)Bl z^dqy&-X1RS!)g2ZNU9~3?1drkClt=R#eNPYR~TEUS7du^Ew%^}s3@^xN6oXKcU#=bmauD67s^N&Jnh zHx5&gR{Aw|uW}}UR<3z>DF!Y}9_89euwxf4EM6Jxej> z3iiR<_L4v-C<$OyR%dc$>6;#bH@mc8;evNLsgU5N0U5|?so_n zxyAYcsDcv~7nuw5-$B)FA2!#8hjaN~d*`O|3kmxEO~AnUt^K>ea?gmq-xPk)H?gi~ z@X*wZlMMOmnE*y|2w5qw05%3Q6iRuHn-ivS7ZQ*tf2*ax^}qG4i2 zEZ)L9d;+dsj%g1dCFXr3d!%1NI2;X4s`Tjjv#mwq;&dQ!cF3>rS{p(ePNb%@f!6DL zqmes>>ByZaUZf&)i?=&@6N?2ioJW-KIh(lh`?SA`XHHMXr(zokr0}+jmN4hj!k1Bv z?{w9&%lVtVCm7tNs1Ad7dL0BJ2Q5R()2{$lg2wva``V}8$k0@pZ1h0|rlzN^#*Py~ z@+dIp{VnzBp@^MGag+FYP?y{FJ}#1hn6>WsQ`og7wdV(ffUHg(w)#~DTq7Dr;w9ss zv264yQ>rNGS#!thMEyngax2UT59zxdmGfstE}jVOQxT~nFrf6hzOju(f-wx~D|5C& z+BiFPIF(p+4^S^bB1(aKXOkWR2n%3MerpYQVna`^qkevsG3IUAa6@t+9LWA8RInlb zwI!QR4=l9Xk`A&2r9XQly^R2@EeXx(*~p!bsF_^2uql`BC$;8 zL{Q|@`xrdC#F_A`=y1d_D~3EafAL>Rw=D&6jxo+f0JWNvmZ$z7Tn_IRTyE($tO9<$ zMeSlmmwuB67oe>4ZjbE_WNC#IGC{%Sr4r?d0KjS6nBIKu_#GCS^wH$1TBNeFMdH*+ z>hf-c!sS2zXK8ct5^3@W97PFEg;ofFp4W@5z4dSx!JdOoj;M$W!|uB(%GIL>b~EYx@e#&M zVu^O9cm4gzp|Rq-Yb)!<4_yfS4yPB)ma5eOa#*bexJ#o2kB$ndDA!%Tx<<$JmO};Q zPh5^e4Hbu$&DuRSh$85ZB3SOih>Ly40c5>)V$3Xax(TX()aahvrMz>-bZB6qxm&ah zA|BN4?tUxgKnh{2r$V+k8a@a#m0%6`N=d+QpT}xA9|dJqJ*Sd*Y%T<(UoLm(`8=^; zW6?p?{JuG*kKlRA%`pZ{a+^Ee6X}u5w)|04k*(Q_J3me|9GoN;-hPG7Q`99o>&@;5 z6**re>seENsiGe|6Y_`~SQD|D_&Y<7b;KYwr9Vwe`WSdP@-~xSFo2{$sJj=&H?s6} zN{u}W(_F=c6_Il7qX2Urc05i~{DmB=Sx$P7W7SGO*1@+JI`~NgCiDFa4yg66#6iB7BS|(TdKVBPc*T)iG=VH{#J%z^}p6C8Y=HC1!#dB zIce?hc+VnwYw0__?cS*WH`!>K)?Qy}jxus`&eruh?>OENo4>G|YfHdT`bmGlx6J-VbeuL@L z0hbY7T`oSKU4$P@44ods5v5V5(-?DZK(bJZVpk}WZ--=5mI)_c+Me!rzl*CpQr9Yk z`jFQ$R^DU_Z?TK+acX%=>6`W_q4s={d5i`}f~=DwhMrEXAkc`<>qGmfZ^=f~qv1~_ zr&CAforX#GAa!Ot?Wv&NEGB50GxVrV`){R!h6%h**@fIIJzPz1P`YZBBazeho<+ge z!ROmBnR4afpeRFG-vg*1Gcl#DL+w=T{5dZob8HO;zGPEkW-zydX&U#J!$?y@R-SMl zqg*1UW-is0G*P<4RDye~FGY|$V8X_ZpslDMo@SmWrjTbwOGI7aDuUg<3MDEllmI4B z0LBg|u4(~Am8FsW&MQKpRwK@7i6h*HV_X>WB>wA;;e9tf2g7j91O4Mbx z9(LbVA9SWZspzgir*u6!l9e}wGq+(hfyt1y1w{Au&IT7n9BYDRkMR70V8Ky5`3B&y zTn%Xp;?l-*pqlraTf!AeeXNzBNMy|?hf1?5$N;Fy#Zih9;IwB{tMRO2w(j;2FA|m) zkAX7^SuNh{t<(PbjBclv$RfgSEa?RFRl*OdggPGBkl^U|B|avL+~)`NaVFcvwMc{7 z%94yyofRm1;^}flGCCjFsMOVw0y~!L=@yq-akoQsd2s+mqX8kB8Bc0yQNt{QG8lY$ zsYm5SHswot&IgqF3T5G2rjd13;x{SgFO+j`7sKxMWAt^oI0-^}ylIwhr0s^Wzm%ls z7pA9(@J$zO^ z$U|xT&XW`X(zB1uSlQiBd5GflF6SrX7=YH|d-z^SO(+6bDF-4q1nC%BV~lXFJIP{& z?SEnXUt(N)pebbiXwm(QLR)wbsv;fk`-eJ3S7_!+89W(Ysk%W+3 zH1KX+vVrP+hTh+F-=m!9xb-|JC&vL@nD9GP!+Eqvj~0COt_jp;7CdmV0!s0)DWNPZ zW5x#YPo54iW_n8(Np(^ex6S1F>F4xWXY&J;z3 z6wM(@*^e=y6jNZ^hi+X`!=CA%!6qcosx*rc=fMCK>L`4*x16HxDN%YLH7Fybk1f2E zdAYu)E?2n%(Y7~5&ww;@+)7b`8RSlkmUQb>{lry^Tdh=F2GbMlE~#r;Yy~rAC<+7R zAbZ=B>+$0%qap-(5yBl00r`JD05o?f~Y zjrGXJ)if&mk9(ngac$oFw| zsq=!%Xp_Tz4)~NAYKNt|0D>+T8s5HZnyun64MaOH`}lNym2MY6>iR>--;etBk(i zuhQ`X+t3Rl+8igsL|y*6tw70cN=**a$Y}$KRU1j3QeVUx>gY~Q;&4I_EFs0 zzw|$ZegaW@;#|aZ!rd59yCNudws_0YpeS)e-&PvyYO_yS^S{X_j1#P(wi&UC&h>vp zxdtj52LI&Ez5JvL*+}nB^z-87gE83{S*dzwll~#3Zf_rxM=8*{wFhN@>IRbnaNOck zw~3(z4FUB%$2iWA=BZ%R1pgiRP2Bgw5Xp|F(d_$?2_e#TlX233c^n3#n<$$n;dIV)VrI5WH%tV=EH*vCg1}- zh3J(G;Q~jKF|#u(cMNS?u25|zAx_>8(U6Z3HFQV!EGI7)IhH z=VLTJJ5rfq7gEDc3f2wuo00LSBi*mCGw)@Nxzy&)Z;zU$#A0elAJ2}B`;y~&$z+Ie z$f@{R`=J?^lq(+dd-CE9ZDUY%S|MBa$C%n|f2n2&cGN9)JLO98JbjT0?-ZeQ591Wi z0p-}nl(qWNa4D_h6GfI$KctS=)`^op^I#AjRh*}QiE4`b!05z>jb){1+?L_)$nLu? zRXNRcxn-Aw+k_KSe54hF@TOve2%jc<}q^{E@*fRqV$JM48dxRE*o76 z*mHri^z><~1;$+|gb~U_o=5@RdD}Yl3QAi&O|F;fZZvpT6GTCgNDWVO>PTM$iYQD2 z^+Ezf+fE~h))yV_xSGGtMBeQ-&BeOm+0y`bOgRat(akVJJ1q*GF*tV^dyno=rJ)&p z<@DGvgm><3IxIlSEA~EyCIxuQu|Wc9XWF2jxr7+dmHE80y=!mpCo#&_-Ff5cq1Pu< z71T)Yo%-ui<)!e!J4qW^SCphjohgblAph2UBiee98V2v2IJLWq(M_~2R{-DNOW3*| z0Cc6Yk3O|e^NsJJXU%N(WABEfl%vYW7x^D~2-x#E;qjTmc=4E$d!LH-eqH}_ef`Pe zT72AVW{oHESnkZd8xdP~D3Hv)oyP#^s~Y*43*VX9P*Zi&`gIh+pDK{1R4v21W!{6W z|JPa=_k3QwdwJXgTE}0$dGX?-H(qZ0^x`AXas8e<`2Cc>`uleC%Le;j&YazU>gCT5 z{qkx1PcM7qfB7&Ov>*TS?)j_1?_PHJ|8lPEi{BpTgL4gShBvU=lMnh_NVyC?_`~KKYeAguer z@4!|%zF999!Yp6Addn{h*k0~i^2%ebANiGr%2&>xz4+eWcJRJ(9)RnAKH#4`01Mjx zsR#c*@LbT)A{%(ipiL+;D5eyXK1=ulYENgy!=G zIQ&?>srg~Y(uD)pH7CFgml{)0;LL2dr5>949yoj-7!!3fWx{?TEnPebj%y-E&VVyx zWK$?!xfvYJ=XlwP{`7<4e>)52rer^V%}XqoBtN`qK|MZ50e6$B;%>fM+545$R&tKXw@Gru-4UeZ0_#>O0_wt_G~Lldio2cz#`@eP=!nyRxw;2V98f z+j-TL9{@OSCtbEaJf;0OILv~Lh(KBSe7&QnSK`fDXL6?e^z$Mh1Q2^>pK3j-wHR<- zln&^qg{HlSU70<2e(NqEylom^aI>B8>XyhQ$@t86*SbVF+u2#`^2A$s_@RARHQxX2 zB3x}J>G;L}5rR4JeSyhl*Iy6${CP)Rol8Uox$>RFOGgGOc0{4w?V1K;Jh&h{pTqvI zNF7MZbxA!6?G^&WTCN8AFbQP&4;JZCfotpoRI9=634lx@&+PL6mRdPc`SX7S? zf5D&pgADoa5b09kt?Qhl&dLDe12TEXLmsm1h!{5>9-8aS;wEB{r+jl&4{-e5q);A6}lrLnEl63|KtAv3<9fY literal 0 HcmV?d00001 diff --git a/docs/source/quantization_layer_wise.md b/docs/source/quantization_layer_wise.md new file mode 100644 index 00000000000..f6d3b80a1ba --- /dev/null +++ b/docs/source/quantization_layer_wise.md @@ -0,0 +1,101 @@ +Layer Wise Quantization (LWQ) +===== + +1. [Introduction](#introduction) + +2. [Supported Framework Model Matrix](#supported-framework-model-matrix) + +3. [Examples](#examples) + +## Introduction + +Large language models (LLMs) have shown exceptional performance across various tasks, meanwhile, the substantial parameter size poses significant challenges for deployment. Layer-wise quantization(LWQ) can greatly reduce the memory footprint of LLMs, usually 80-90% reduction, which means that users can quantize LLMs even on single node using GPU or CPU. We can quantize the model under memory-constrained devices, therefore making the huge-sized LLM quantization possible. + + + +*Figure 1: The process of layer-wise quantization for PyTorch model. The color grey means empty parameters and the color blue represents parameters need to be quantized. Every rectangle inside model represents one layer.* + + + +*Figure 2: The process of layer-wise quantization for ONNX model. The graph of LLM is split into several parts, and each subgraph is quantized in turn.* + +## Supported Framework Model Matrix + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Types/FrameworkPyTorchONNX Runtime
    W8A8 Post Training Static Quantization
    Weight-only QuantizationRTN
    AWQ
    GPTQ
    TEQ
    + +## Examples + +#### PyTorch framework example + +```python +from neural_compressor import PostTrainingQuantConfig, quantization +from neural_compressor.adaptor.torch_utils.layer_wise_quant import load_empty_model + +fp32_model = load_empty_model(model_name_or_path, torchscript=True) +conf = PostTrainingQuantConfig( + approach="weight_only", + recipes={ + "layer_wise_quant": True, + "rtn_args": {"enable_full_range": True}, + }, +) + +q_model = quantization.fit( + fp32_model, + conf, + calib_dataloader=eval_dataloader, + eval_func=lambda x: 0.1, +) +ouput_dir = "./saved_model" +q_model.save(ouput_dir) +q_model = load(ouput_dir, fp32_model, weight_only=True, layer_wise=True) +``` + +#### ONNX Runtime framework example + +```python +from neural_compressor import quantization, PostTrainingQuantConfig + +conf = PostTrainingQuantConfig(recipes={'layer_wise_quant': True}) +q_model = quantization.fit( + fp32_model_path, + conf, + calib_dataloader=dataloader) +q_model.save(int8_model_path) +``` + +Refer to [ONNX Runtime llama-2 LWQ example](../../examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/weight_only) \ No newline at end of file From 1acdc9cdec4e0db08a91f818b77793da194b3388 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Thu, 30 Nov 2023 17:27:33 +0800 Subject: [PATCH 06/22] update onnxrt lwq figure Signed-off-by: yuwenzho --- docs/source/imgs/{ort_lwq.png => lwq_ort.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/source/imgs/{ort_lwq.png => lwq_ort.png} (100%) diff --git a/docs/source/imgs/ort_lwq.png b/docs/source/imgs/lwq_ort.png similarity index 100% rename from docs/source/imgs/ort_lwq.png rename to docs/source/imgs/lwq_ort.png From a6a656b917227b5b3e411624497424b611530029 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 30 Nov 2023 09:29:47 +0000 Subject: [PATCH 07/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/quantization_layer_wise.md | 199 +++++++++--------- .../llama/quantization/ptq_static/main.py | 1 - neural_compressor/adaptor/ox_utils/util.py | 19 +- neural_compressor/model/onnx_model.py | 6 +- 4 files changed, 112 insertions(+), 113 deletions(-) diff --git a/docs/source/quantization_layer_wise.md b/docs/source/quantization_layer_wise.md index f6d3b80a1ba..c9d93761eaa 100644 --- a/docs/source/quantization_layer_wise.md +++ b/docs/source/quantization_layer_wise.md @@ -1,101 +1,98 @@ -Layer Wise Quantization (LWQ) -===== - -1. [Introduction](#introduction) - -2. [Supported Framework Model Matrix](#supported-framework-model-matrix) - -3. [Examples](#examples) - -## Introduction - -Large language models (LLMs) have shown exceptional performance across various tasks, meanwhile, the substantial parameter size poses significant challenges for deployment. Layer-wise quantization(LWQ) can greatly reduce the memory footprint of LLMs, usually 80-90% reduction, which means that users can quantize LLMs even on single node using GPU or CPU. We can quantize the model under memory-constrained devices, therefore making the huge-sized LLM quantization possible. - - - -*Figure 1: The process of layer-wise quantization for PyTorch model. The color grey means empty parameters and the color blue represents parameters need to be quantized. Every rectangle inside model represents one layer.* - - - -*Figure 2: The process of layer-wise quantization for ONNX model. The graph of LLM is split into several parts, and each subgraph is quantized in turn.* - -## Supported Framework Model Matrix - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Types/FrameworkPyTorchONNX Runtime
    W8A8 Post Training Static Quantization
    Weight-only QuantizationRTN
    AWQ
    GPTQ
    TEQ
    - -## Examples - -#### PyTorch framework example - -```python -from neural_compressor import PostTrainingQuantConfig, quantization -from neural_compressor.adaptor.torch_utils.layer_wise_quant import load_empty_model - -fp32_model = load_empty_model(model_name_or_path, torchscript=True) -conf = PostTrainingQuantConfig( - approach="weight_only", - recipes={ - "layer_wise_quant": True, - "rtn_args": {"enable_full_range": True}, - }, -) - -q_model = quantization.fit( - fp32_model, - conf, - calib_dataloader=eval_dataloader, - eval_func=lambda x: 0.1, -) -ouput_dir = "./saved_model" -q_model.save(ouput_dir) -q_model = load(ouput_dir, fp32_model, weight_only=True, layer_wise=True) -``` - -#### ONNX Runtime framework example - -```python -from neural_compressor import quantization, PostTrainingQuantConfig - -conf = PostTrainingQuantConfig(recipes={'layer_wise_quant': True}) -q_model = quantization.fit( - fp32_model_path, - conf, - calib_dataloader=dataloader) -q_model.save(int8_model_path) -``` - -Refer to [ONNX Runtime llama-2 LWQ example](../../examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/weight_only) \ No newline at end of file +Layer Wise Quantization (LWQ) +===== + +1. [Introduction](#introduction) + +2. [Supported Framework Model Matrix](#supported-framework-model-matrix) + +3. [Examples](#examples) + +## Introduction + +Large language models (LLMs) have shown exceptional performance across various tasks, meanwhile, the substantial parameter size poses significant challenges for deployment. Layer-wise quantization(LWQ) can greatly reduce the memory footprint of LLMs, usually 80-90% reduction, which means that users can quantize LLMs even on single node using GPU or CPU. We can quantize the model under memory-constrained devices, therefore making the huge-sized LLM quantization possible. + + + +*Figure 1: The process of layer-wise quantization for PyTorch model. The color grey means empty parameters and the color blue represents parameters need to be quantized. Every rectangle inside model represents one layer.* + + + +*Figure 2: The process of layer-wise quantization for ONNX model. The graph of LLM is split into several parts, and each subgraph is quantized in turn.* + +## Supported Framework Model Matrix + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Types/FrameworkPyTorchONNX Runtime
    W8A8 Post Training Static Quantization
    Weight-only QuantizationRTN
    AWQ
    GPTQ
    TEQ
    + +## Examples + +#### PyTorch framework example + +```python +from neural_compressor import PostTrainingQuantConfig, quantization +from neural_compressor.adaptor.torch_utils.layer_wise_quant import load_empty_model + +fp32_model = load_empty_model(model_name_or_path, torchscript=True) +conf = PostTrainingQuantConfig( + approach="weight_only", + recipes={ + "layer_wise_quant": True, + "rtn_args": {"enable_full_range": True}, + }, +) + +q_model = quantization.fit( + fp32_model, + conf, + calib_dataloader=eval_dataloader, + eval_func=lambda x: 0.1, +) +ouput_dir = "./saved_model" +q_model.save(ouput_dir) +q_model = load(ouput_dir, fp32_model, weight_only=True, layer_wise=True) +``` + +#### ONNX Runtime framework example + +```python +from neural_compressor import quantization, PostTrainingQuantConfig + +conf = PostTrainingQuantConfig(recipes={"layer_wise_quant": True}) +q_model = quantization.fit(fp32_model_path, conf, calib_dataloader=dataloader) +q_model.save(int8_model_path) +``` + +Refer to [ONNX Runtime llama-2 LWQ example](../../examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/weight_only) diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py index 788abf5fc2d..a41adee578e 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py @@ -296,4 +296,3 @@ def __iter__(self): q_model.save(os.path.join(args.output_model, model)) tokenizer.save_pretrained(args.output_model) - \ No newline at end of file diff --git a/neural_compressor/adaptor/ox_utils/util.py b/neural_compressor/adaptor/ox_utils/util.py index d2126b47604..4211e2000fc 100644 --- a/neural_compressor/adaptor/ox_utils/util.py +++ b/neural_compressor/adaptor/ox_utils/util.py @@ -17,8 +17,8 @@ """Helper classes or functions for onnxrt adaptor.""" import importlib -import os import logging +import os from enum import Enum import numpy as np @@ -601,6 +601,7 @@ def to_numpy(data): else: return data + class SymbolicShapeInference(symbolic_shape_infer.SymbolicShapeInference): def __init__(self, int_max, auto_merge, guess_output_rank, verbose, prefix="", base_dir=""): super().__init__(int_max, auto_merge, guess_output_rank, verbose, prefix) @@ -609,9 +610,12 @@ def __init__(self, int_max, auto_merge, guess_output_rank, verbose, prefix="", b def _get_value(self, node, idx): name = node.input[idx] assert name in self.sympy_data_ or name in self.initializers_ - return self.sympy_data_[name] if name in self.sympy_data_ else \ - numpy_helper.to_array(self.initializers_[name], base_dir=self.base_dir) - + return ( + self.sympy_data_[name] + if name in self.sympy_data_ + else numpy_helper.to_array(self.initializers_[name], base_dir=self.base_dir) + ) + @staticmethod def infer_shapes(in_mp, int_max=2**31 - 1, auto_merge=False, guess_output_rank=False, verbose=0, base_dir=""): onnx_opset = symbolic_shape_infer.get_opset(in_mp) @@ -619,11 +623,8 @@ def infer_shapes(in_mp, int_max=2**31 - 1, auto_merge=False, guess_output_rank=F logger.warning("Only support models of onnx opset 7 and above.") return None symbolic_shape_inference = SymbolicShapeInference( - int_max, - auto_merge, - guess_output_rank, - verbose, - base_dir=base_dir) + int_max, auto_merge, guess_output_rank, verbose, base_dir=base_dir + ) all_shapes_inferred = False symbolic_shape_inference._preprocess(in_mp) while symbolic_shape_inference.run_: diff --git a/neural_compressor/model/onnx_model.py b/neural_compressor/model/onnx_model.py index 3d055d39f04..d301964a2eb 100644 --- a/neural_compressor/model/onnx_model.py +++ b/neural_compressor/model/onnx_model.py @@ -49,7 +49,7 @@ def __init__(self, model, **kwargs): self.check_is_large_model() if self._is_large_model and self._model_path is None and not kwargs.get("ignore_warning", False): logger.warning("Model size > 2GB. Please use model path instead of onnx model object to quantize") - + if self._is_large_model and isinstance(model, str) and kwargs.get("load_external_data", True): from onnx.external_data_helper import load_external_data_for_model @@ -1047,7 +1047,9 @@ def split_model_with_node( # need ort.GraphOptimizationLevel <= ORT_ENABLE_BASIC from neural_compressor.adaptor.ox_utils.util import SymbolicShapeInference - self._model = SymbolicShapeInference.infer_shapes(self._model, auto_merge=True, base_dir=os.path.dirname(self._model_path)) + self._model = SymbolicShapeInference.infer_shapes( + self._model, auto_merge=True, base_dir=os.path.dirname(self._model_path) + ) except Exception as e: # pragma: no cover logger.error("Shape infer fails for layer-wise quantization") if "Incomplete symbolic shape inference" in str(e): From 703518771218614b0e14700483f34b2979e29ddd Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Thu, 30 Nov 2023 17:44:53 +0800 Subject: [PATCH 08/22] update quantization_layer_wise.md Signed-off-by: yuwenzho --- docs/source/quantization_layer_wise.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/quantization_layer_wise.md b/docs/source/quantization_layer_wise.md index c9d93761eaa..27218452fe1 100644 --- a/docs/source/quantization_layer_wise.md +++ b/docs/source/quantization_layer_wise.md @@ -11,11 +11,11 @@ Layer Wise Quantization (LWQ) Large language models (LLMs) have shown exceptional performance across various tasks, meanwhile, the substantial parameter size poses significant challenges for deployment. Layer-wise quantization(LWQ) can greatly reduce the memory footprint of LLMs, usually 80-90% reduction, which means that users can quantize LLMs even on single node using GPU or CPU. We can quantize the model under memory-constrained devices, therefore making the huge-sized LLM quantization possible. - + *Figure 1: The process of layer-wise quantization for PyTorch model. The color grey means empty parameters and the color blue represents parameters need to be quantized. Every rectangle inside model represents one layer.* - + *Figure 2: The process of layer-wise quantization for ONNX model. The graph of LLM is split into several parts, and each subgraph is quantized in turn.* From 66be652721f2e9de18db98abf31799bf258d4722 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Thu, 30 Nov 2023 18:01:54 +0800 Subject: [PATCH 09/22] update ox_utils Signed-off-by: yuwenzho --- neural_compressor/adaptor/ox_utils/util.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/neural_compressor/adaptor/ox_utils/util.py b/neural_compressor/adaptor/ox_utils/util.py index 4211e2000fc..5285b22c56a 100644 --- a/neural_compressor/adaptor/ox_utils/util.py +++ b/neural_compressor/adaptor/ox_utils/util.py @@ -17,7 +17,6 @@ """Helper classes or functions for onnxrt adaptor.""" import importlib -import logging import os from enum import Enum @@ -33,8 +32,6 @@ symbolic_shape_infer = LazyImport("onnxruntime.tools.symbolic_shape_infer") onnx = LazyImport("onnx") -logger = logging.getLogger("neural_compressor") - __producer__ = "onnx.quantize" __version__ = "0.1.0" @@ -603,7 +600,10 @@ def to_numpy(data): class SymbolicShapeInference(symbolic_shape_infer.SymbolicShapeInference): + """Shape inference for ONNX model.""" + def __init__(self, int_max, auto_merge, guess_output_rank, verbose, prefix="", base_dir=""): + """Initialize Shape inference class.""" super().__init__(int_max, auto_merge, guess_output_rank, verbose, prefix) self.base_dir = base_dir @@ -618,6 +618,7 @@ def _get_value(self, node, idx): @staticmethod def infer_shapes(in_mp, int_max=2**31 - 1, auto_merge=False, guess_output_rank=False, verbose=0, base_dir=""): + """Symbolic shape inference.""" onnx_opset = symbolic_shape_infer.get_opset(in_mp) if (not onnx_opset) or onnx_opset < 7: logger.warning("Only support models of onnx opset 7 and above.") From 785f6a631d8a79a17f5f03873c78e0f75af58f6a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 30 Nov 2023 10:05:23 +0000 Subject: [PATCH 10/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- neural_compressor/adaptor/ox_utils/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neural_compressor/adaptor/ox_utils/util.py b/neural_compressor/adaptor/ox_utils/util.py index 5285b22c56a..361edd7f36f 100644 --- a/neural_compressor/adaptor/ox_utils/util.py +++ b/neural_compressor/adaptor/ox_utils/util.py @@ -601,7 +601,7 @@ def to_numpy(data): class SymbolicShapeInference(symbolic_shape_infer.SymbolicShapeInference): """Shape inference for ONNX model.""" - + def __init__(self, int_max, auto_merge, guess_output_rank, verbose, prefix="", base_dir=""): """Initialize Shape inference class.""" super().__init__(int_max, auto_merge, guess_output_rank, verbose, prefix) From de59987b8793a52fb5bc58d7dbe5f1dfa701a776 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Fri, 1 Dec 2023 10:26:52 +0800 Subject: [PATCH 11/22] fix import bug Signed-off-by: yuwenzho --- neural_compressor/adaptor/ox_utils/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neural_compressor/adaptor/ox_utils/util.py b/neural_compressor/adaptor/ox_utils/util.py index 361edd7f36f..04f5fc45f64 100644 --- a/neural_compressor/adaptor/ox_utils/util.py +++ b/neural_compressor/adaptor/ox_utils/util.py @@ -29,6 +29,7 @@ numpy_helper = LazyImport("onnx.numpy_helper") onnx_proto = LazyImport("onnx.onnx_pb") torch = LazyImport("torch") +onnxruntime = LazyImport("onnxruntime") symbolic_shape_infer = LazyImport("onnxruntime.tools.symbolic_shape_infer") onnx = LazyImport("onnx") @@ -601,7 +602,7 @@ def to_numpy(data): class SymbolicShapeInference(symbolic_shape_infer.SymbolicShapeInference): """Shape inference for ONNX model.""" - + def __init__(self, int_max, auto_merge, guess_output_rank, verbose, prefix="", base_dir=""): """Initialize Shape inference class.""" super().__init__(int_max, auto_merge, guess_output_rank, verbose, prefix) From 595007b85ffc70f25a8dad641bb90a94c6ec60fa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 02:28:07 +0000 Subject: [PATCH 12/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- neural_compressor/adaptor/ox_utils/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neural_compressor/adaptor/ox_utils/util.py b/neural_compressor/adaptor/ox_utils/util.py index 04f5fc45f64..c30478a1352 100644 --- a/neural_compressor/adaptor/ox_utils/util.py +++ b/neural_compressor/adaptor/ox_utils/util.py @@ -602,7 +602,7 @@ def to_numpy(data): class SymbolicShapeInference(symbolic_shape_infer.SymbolicShapeInference): """Shape inference for ONNX model.""" - + def __init__(self, int_max, auto_merge, guess_output_rank, verbose, prefix="", base_dir=""): """Initialize Shape inference class.""" super().__init__(int_max, auto_merge, guess_output_rank, verbose, prefix) From 966aa9b53e1fc35b5d69b6c56cce307cb7ebbe06 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Fri, 1 Dec 2023 10:32:41 +0800 Subject: [PATCH 13/22] Update run_quant.sh Signed-off-by: yuwenzho --- .../text_generation/llama/quantization/ptq_static/run_quant.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/run_quant.sh b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/run_quant.sh index 384f43a84aa..f2e62698e16 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/run_quant.sh +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/run_quant.sh @@ -70,7 +70,7 @@ function run_tuning { python main.py \ --quant_format ${quant_format-QOperator} \ --model_path ${input_model} \ - --tokenizer ${tokenizer-meta-llama/Llama-2-7b-hf} \ + --tokenizer ${tokenizer-meta-llama/Llama-2-7b-hf} \ --output_model ${output_model} \ --batch_size ${batch_size-1} \ --smooth_quant_alpha ${alpha-0.6} \ From 1f10a92175734a6e8404e76aa765624a3c5b8146 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Fri, 1 Dec 2023 12:00:42 +0800 Subject: [PATCH 14/22] fix import bug Signed-off-by: yuwenzho --- neural_compressor/adaptor/ox_utils/util.py | 69 ++++++++++------------ neural_compressor/model/onnx_model.py | 4 +- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/neural_compressor/adaptor/ox_utils/util.py b/neural_compressor/adaptor/ox_utils/util.py index c30478a1352..2f0bc2caadc 100644 --- a/neural_compressor/adaptor/ox_utils/util.py +++ b/neural_compressor/adaptor/ox_utils/util.py @@ -29,7 +29,6 @@ numpy_helper = LazyImport("onnx.numpy_helper") onnx_proto = LazyImport("onnx.onnx_pb") torch = LazyImport("torch") -onnxruntime = LazyImport("onnxruntime") symbolic_shape_infer = LazyImport("onnxruntime.tools.symbolic_shape_infer") onnx = LazyImport("onnx") @@ -599,40 +598,36 @@ def to_numpy(data): else: return data +def infer_shapes(in_mp, int_max=2**31 - 1, auto_merge=False, guess_output_rank=False, verbose=0, base_dir=""): + """Symbolic shape inference.""" + + class SymbolicShapeInference(symbolic_shape_infer.SymbolicShapeInference): + def __init__(self, int_max, auto_merge, guess_output_rank, verbose, prefix="", base_dir=""): + super().__init__(int_max, auto_merge, guess_output_rank, verbose, prefix) + self.base_dir = base_dir + + def _get_value(self, node, idx): + name = node.input[idx] + assert name in self.sympy_data_ or name in self.initializers_ + return ( + self.sympy_data_[name] + if name in self.sympy_data_ + else numpy_helper.to_array(self.initializers_[name], base_dir=self.base_dir) + ) -class SymbolicShapeInference(symbolic_shape_infer.SymbolicShapeInference): - """Shape inference for ONNX model.""" - - def __init__(self, int_max, auto_merge, guess_output_rank, verbose, prefix="", base_dir=""): - """Initialize Shape inference class.""" - super().__init__(int_max, auto_merge, guess_output_rank, verbose, prefix) - self.base_dir = base_dir - - def _get_value(self, node, idx): - name = node.input[idx] - assert name in self.sympy_data_ or name in self.initializers_ - return ( - self.sympy_data_[name] - if name in self.sympy_data_ - else numpy_helper.to_array(self.initializers_[name], base_dir=self.base_dir) - ) - - @staticmethod - def infer_shapes(in_mp, int_max=2**31 - 1, auto_merge=False, guess_output_rank=False, verbose=0, base_dir=""): - """Symbolic shape inference.""" - onnx_opset = symbolic_shape_infer.get_opset(in_mp) - if (not onnx_opset) or onnx_opset < 7: - logger.warning("Only support models of onnx opset 7 and above.") - return None - symbolic_shape_inference = SymbolicShapeInference( - int_max, auto_merge, guess_output_rank, verbose, base_dir=base_dir - ) - all_shapes_inferred = False - symbolic_shape_inference._preprocess(in_mp) - while symbolic_shape_inference.run_: - all_shapes_inferred = symbolic_shape_inference._infer_impl() - symbolic_shape_inference._update_output_from_vi() - if not all_shapes_inferred: - onnx.save_model(symbolic_shape_inference.out_mp_, "sym_shape_infer_temp.onnx", save_as_external_data=True) - raise Exception("Incomplete symbolic shape inference") - return symbolic_shape_inference.out_mp_ + onnx_opset = symbolic_shape_infer.get_opset(in_mp) + if (not onnx_opset) or onnx_opset < 7: + logger.warning("Only support models of onnx opset 7 and above.") + return None + symbolic_shape_inference = SymbolicShapeInference( + int_max, auto_merge, guess_output_rank, verbose, base_dir=base_dir + ) + all_shapes_inferred = False + symbolic_shape_inference._preprocess(in_mp) + while symbolic_shape_inference.run_: + all_shapes_inferred = symbolic_shape_inference._infer_impl() + symbolic_shape_inference._update_output_from_vi() + if not all_shapes_inferred: + onnx.save_model(symbolic_shape_inference.out_mp_, "sym_shape_infer_temp.onnx", save_as_external_data=True) + raise Exception("Incomplete symbolic shape inference") + return symbolic_shape_inference.out_mp_ diff --git a/neural_compressor/model/onnx_model.py b/neural_compressor/model/onnx_model.py index d301964a2eb..ba9f35abf40 100644 --- a/neural_compressor/model/onnx_model.py +++ b/neural_compressor/model/onnx_model.py @@ -1045,9 +1045,9 @@ def split_model_with_node( if shape_infer: try: # need ort.GraphOptimizationLevel <= ORT_ENABLE_BASIC - from neural_compressor.adaptor.ox_utils.util import SymbolicShapeInference + from neural_compressor.adaptor.ox_utils.util import infer_shapes - self._model = SymbolicShapeInference.infer_shapes( + self._model = infer_shapes( self._model, auto_merge=True, base_dir=os.path.dirname(self._model_path) ) except Exception as e: # pragma: no cover From 472ebbe7b32717e962ac1abee1850e230d6e6f76 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 04:02:01 +0000 Subject: [PATCH 15/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- neural_compressor/adaptor/ox_utils/util.py | 1 + neural_compressor/model/onnx_model.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/neural_compressor/adaptor/ox_utils/util.py b/neural_compressor/adaptor/ox_utils/util.py index 2f0bc2caadc..7d3cda05915 100644 --- a/neural_compressor/adaptor/ox_utils/util.py +++ b/neural_compressor/adaptor/ox_utils/util.py @@ -598,6 +598,7 @@ def to_numpy(data): else: return data + def infer_shapes(in_mp, int_max=2**31 - 1, auto_merge=False, guess_output_rank=False, verbose=0, base_dir=""): """Symbolic shape inference.""" diff --git a/neural_compressor/model/onnx_model.py b/neural_compressor/model/onnx_model.py index ba9f35abf40..f5542bc120e 100644 --- a/neural_compressor/model/onnx_model.py +++ b/neural_compressor/model/onnx_model.py @@ -1047,9 +1047,7 @@ def split_model_with_node( # need ort.GraphOptimizationLevel <= ORT_ENABLE_BASIC from neural_compressor.adaptor.ox_utils.util import infer_shapes - self._model = infer_shapes( - self._model, auto_merge=True, base_dir=os.path.dirname(self._model_path) - ) + self._model = infer_shapes(self._model, auto_merge=True, base_dir=os.path.dirname(self._model_path)) except Exception as e: # pragma: no cover logger.error("Shape infer fails for layer-wise quantization") if "Incomplete symbolic shape inference" in str(e): From 23ce23b23cc90dcae1d34b072346aa6a184ff8f2 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Tue, 5 Dec 2023 11:32:11 +0800 Subject: [PATCH 16/22] update requirement Signed-off-by: yuwenzho --- .../text_generation/llama/quantization/ptq_static/README.md | 2 +- .../llama/quantization/ptq_static/requirements.txt | 2 +- .../llama/quantization/weight_only/requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md index 975ef34b617..201f5007864 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md @@ -27,7 +27,7 @@ Note that this README.md uses meta-llama/Llama-2-7b-hf as an example. There are Export to ONNX model: ```bash -optimum-cli export onnx --model meta-llama/Llama-2-7b-hf --task text-generation-with-past ./Llama-2-7b-hf +python prepare_model.py --input_model="meta-llama/Llama-2-7b-hf" --output_model="./llama-2-7b-hf" ``` # Run diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/requirements.txt b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/requirements.txt index 77b73003bf4..bb585524476 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/requirements.txt +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/requirements.txt @@ -5,7 +5,7 @@ onnx onnxruntime onnxruntime-extensions; python_version < '3.11' datasets -optimum +optimum==1.13.3 evaluate peft git+https://github.com/EleutherAI/lm-evaluation-harness.git@cc9778fbe4fa1a709be2abed9deb6180fd40e7e2 diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/weight_only/requirements.txt b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/weight_only/requirements.txt index 340fe76bbdc..e3a44cc0ab6 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/weight_only/requirements.txt +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/weight_only/requirements.txt @@ -5,7 +5,7 @@ onnx onnxruntime onnxruntime-extensions; python_version < '3.11' datasets -optimum +optimum==1.13.3 evaluate peft git+https://github.com/EleutherAI/lm-evaluation-harness.git@cc9778fbe4fa1a709be2abed9deb6180fd40e7e2 From da05ba4c5ccc4c9185c8c9c51724df2132311d55 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Tue, 5 Dec 2023 11:40:40 +0800 Subject: [PATCH 17/22] update README and doc Signed-off-by: yuwenzho --- README.md | 3 +- docs/source/quantization_weight_only.md | 48 +------------------ docs/source/user_guide.md | 7 +-- .../llama/quantization/ptq_static/README.md | 5 +- 4 files changed, 10 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index f5346636da8..eca65b77ef5 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,8 @@ q_model = fit( Weight-Only Quantization (INT8/INT4/FP4/NF4) - FP8 Quantization + FP8 Quantization + Layer-Wise Quantization diff --git a/docs/source/quantization_weight_only.md b/docs/source/quantization_weight_only.md index de5d70c6d09..a7d6722306b 100644 --- a/docs/source/quantization_weight_only.md +++ b/docs/source/quantization_weight_only.md @@ -7,9 +7,7 @@ Weight Only Quantization (WOQ) 3. [Examples](#examples) -4. [Layer Wise Quantization](#layer-wise-quantization) - -5. [WOQ Algorithms Tuning](#woq-algorithms-tuning) +4. [WOQ Algorithms Tuning](#woq-algorithms-tuning) ## Introduction @@ -143,50 +141,6 @@ The saved_results folder contains two files: `best_model.pt` and `qconfig.json`, To seek the performance of weight-only quantized models, Please go to [Intel Extension for Transformers](https://github.com/intel/intel-extension-for-transformers/tree/main/examples/huggingface/pytorch/text-generation/quantization#1-performance) to quantize and deploy the model. - -## Layer Wise Quantization - -Large language models (LLMs) have shown exceptional performance across various tasks, meanwhile, the substantial parameter size poses significant challenges for deployment. Layer-wise quantization(LWQ) can greatly reduce the memory footprint of LLMs, usually 80-90% reduction, which means that users can quantize LLMs even on single node using GPU or CPU. We can quantize the model under memory-constrained devices, therefore making the huge-sized LLM quantization possible. - - - -*Figure 1: The process of layer-wise quantization. The color grey means empty parameters and the color blue represents parameters need to be quantized. Every rectangle inside model represents one layer.* - -### Supported Matrix - -| Algorithms/Framework | PyTorch | -|:--------------:|:----------:| -| RTN | ✔ | -| AWQ | ✕ | -| GPTQ | ✔ | -| TEQ | ✕ | - -### Example -```python -from neural_compressor import PostTrainingQuantConfig, quantization -from neural_compressor.adaptor.torch_utils.layer_wise_quant import load_empty_model - -fp32_model = load_empty_model(model_name_or_path, torchscript=True) -conf = PostTrainingQuantConfig( - approach="weight_only", - recipes={ - "layer_wise_quant": True, - "rtn_args": {"enable_full_range": True}, - }, -) - -q_model = quantization.fit( - fp32_model, - conf, - calib_dataloader=eval_dataloader, - eval_func=lambda x: 0.1, -) -ouput_dir = "./saved_model" -q_model.save(ouput_dir) -q_model = load(ouput_dir, fp32_model, weight_only=True, layer_wise=True) -``` - - ## WOQ Algorithms Tuning To find the best algorithm, users can omit specifying a particular algorithm. In comparison to setting a specific algorithm, this tuning process will traverse through a set of pre-defined WOQ configurations and identify the optimal one with the best result. For details usage, please refer to the [tuning strategy](./tuning_strategies.md#Basic). diff --git a/docs/source/user_guide.md b/docs/source/user_guide.md index d9286f099fd..662a2ec177c 100644 --- a/docs/source/user_guide.md +++ b/docs/source/user_guide.md @@ -81,9 +81,10 @@ This part provides the advanced topics that help user dive deep into IntelĀ® Neu Add New Adaptor -Distillation for Quantization -SmoothQuant -Weight-Only Quantization +Distillation for Quantization +SmoothQuant +Weight-Only Quantization +Layer-Wise Quantization diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md index 201f5007864..a82da6d9c7a 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/README.md @@ -34,7 +34,7 @@ python prepare_model.py --input_model="meta-llama/Llama-2-7b-hf" --output_model ## 1. Quantization -Run SmoothQuant +### Run SmoothQuant ```bash bash run_quant.sh --input_model=/path/to/model \ # folder path of onnx model @@ -46,7 +46,8 @@ bash run_quant.sh --input_model=/path/to/model \ # folder path of onnx model --quant_format="QOperator" # or QDQ, optional ``` -Additionally set `--layer-wise=True` to use layer-wise quantization to save your memory. Please note that layer-wise quantization for ONNX models is still under development and only support W8A8 quantization now. More details please refer to [layer wise quantiation](https://github.com/intel/neural-compressor/blob/master/docs/source/quantization_layer_wise.md). +### Run layer-wise quantization +Set `--layer-wise=True` to use layer-wise quantization to save your memory. Please note that layer-wise quantization for ONNX models is still under development and only support W8A8 quantization now. More details please refer to [layer wise quantiation](https://github.com/intel/neural-compressor/blob/master/docs/source/quantization_layer_wise.md). ```bash bash run_quant.sh --input_model=/path/to/model \ # folder path of onnx model From e3116d9dc4014bb941862553c5e1fe685fc4b8c3 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Tue, 5 Dec 2023 13:33:35 +0800 Subject: [PATCH 18/22] update onnx_model.py Signed-off-by: yuwenzho --- neural_compressor/model/onnx_model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/neural_compressor/model/onnx_model.py b/neural_compressor/model/onnx_model.py index f5542bc120e..6950c742eed 100644 --- a/neural_compressor/model/onnx_model.py +++ b/neural_compressor/model/onnx_model.py @@ -1044,14 +1044,14 @@ def split_model_with_node( # infer shape of the model to be split if shape_infer: try: - # need ort.GraphOptimizationLevel <= ORT_ENABLE_BASIC from neural_compressor.adaptor.ox_utils.util import infer_shapes self._model = infer_shapes(self._model, auto_merge=True, base_dir=os.path.dirname(self._model_path)) except Exception as e: # pragma: no cover - logger.error("Shape infer fails for layer-wise quantization") - if "Incomplete symbolic shape inference" in str(e): - logger.warning("Please set graph optimization level to 'ENABLE_BASIC' for layer-wise quantization.") + logger.error("Shape infer fails for layer-wise quantization. "\ + "We would recommend checking the graph optimization level of your model " \ + "and setting it to levels 'DISABLE_ALL' and 'ENABLE_BASIC', "\ + "as this may help avoid this error.") raise e split_tensor_type, split_tensor_shape = self._get_output_type_shape_by_tensor_name(split_tensor_name) From cf126ce9c5d4652aa0836f2df586f2ebaa0d2f54 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 05:34:55 +0000 Subject: [PATCH 19/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- neural_compressor/model/onnx_model.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/neural_compressor/model/onnx_model.py b/neural_compressor/model/onnx_model.py index 6950c742eed..625e149c062 100644 --- a/neural_compressor/model/onnx_model.py +++ b/neural_compressor/model/onnx_model.py @@ -1048,10 +1048,12 @@ def split_model_with_node( self._model = infer_shapes(self._model, auto_merge=True, base_dir=os.path.dirname(self._model_path)) except Exception as e: # pragma: no cover - logger.error("Shape infer fails for layer-wise quantization. "\ - "We would recommend checking the graph optimization level of your model " \ - "and setting it to levels 'DISABLE_ALL' and 'ENABLE_BASIC', "\ - "as this may help avoid this error.") + logger.error( + "Shape infer fails for layer-wise quantization. " + "We would recommend checking the graph optimization level of your model " + "and setting it to levels 'DISABLE_ALL' and 'ENABLE_BASIC', " + "as this may help avoid this error." + ) raise e split_tensor_type, split_tensor_shape = self._get_output_type_shape_by_tensor_name(split_tensor_name) From 449114f57392952775bff4ac5c34baff75051796 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Tue, 5 Dec 2023 17:29:54 +0800 Subject: [PATCH 20/22] update onnx_model.py Signed-off-by: yuwenzho --- neural_compressor/model/onnx_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neural_compressor/model/onnx_model.py b/neural_compressor/model/onnx_model.py index 625e149c062..56c09af3c7e 100644 --- a/neural_compressor/model/onnx_model.py +++ b/neural_compressor/model/onnx_model.py @@ -1051,7 +1051,7 @@ def split_model_with_node( logger.error( "Shape infer fails for layer-wise quantization. " "We would recommend checking the graph optimization level of your model " - "and setting it to levels 'DISABLE_ALL' and 'ENABLE_BASIC', " + "and setting it to 'DISABLE_ALL' or 'ENABLE_BASIC', " "as this may help avoid this error." ) raise e From ed3f8446a5f357627ffa13898423d49f0e393b12 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Wed, 6 Dec 2023 09:46:43 +0800 Subject: [PATCH 21/22] update main.py Signed-off-by: yuwenzho --- .../llama/quantization/ptq_static/main.py | 59 +++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py index a41adee578e..90a68aa453a 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py @@ -17,6 +17,7 @@ # pylint:disable=redefined-outer-name,logging-format-interpolation import os import onnx +import json import torch import logging import argparse @@ -136,16 +137,26 @@ def benchmark(model): config = LlamaConfig.from_pretrained(args.model_path) sess_options = ort.SessionOptions() sess_options.intra_op_num_threads = args.intra_op_num_threads - sessions = ORTModelForCausalLM.load_model( - os.path.join(model, 'decoder_model.onnx'), - os.path.join(model, 'decoder_with_past_model.onnx'), + + if os.path.exists(os.path.join(model, "decoder_with_past_model.onnx")): + sessions = ORTModelForCausalLM.load_model( # pylint: disable=E1123 + os.path.join(model, "decoder_model.onnx"), + os.path.join(model, "decoder_with_past_model.onnx"), session_options=sess_options) - model = ORTModelForCausalLM( - sessions[0], - config, - model, - sessions[1], - use_cache=True) + model = ORTModelForCausalLM(sessions[0], # pylint: disable=E1121 + config, + model, + sessions[1], + use_cache=True) + else: + sessions = ORTModelForCausalLM.load_model( # pylint: disable=E1123 + os.path.join(model, "decoder_model.onnx"), + session_options=sess_options) + model = ORTModelForCausalLM(sessions[0], # pylint: disable=E1121 + config, + model, + use_cache=False, + use_io_binding=False) input_tokens = '32' max_new_tokens = 32 @@ -182,19 +193,45 @@ def benchmark(model): print(args) print("Inference latency: %.3f sec." % latency) +def replace_architectures(json_path): + # replace 'LLaMATokenizer' to lowercase 'LlamaTokenizer' + # to avoid bug 'Tokenizer class LLaMATokenizer does not exist or is not currently imported.' + # refer to https://github.com/huggingface/transformers/issues/22222#issuecomment-1477171703 + with open(json_path, "r") as file: + data = json.load(file) + data["architectures"] = ["LlamaForCausalLM"] + + with open(json_path, 'w') as file: + json.dump(data, file, indent=4) + def eval_func(model): + model_dir = model + if isinstance(model, str) and model.endswith(".onnx"): + model_dir = os.path.dirname(model) + + replace_architectures(os.path.join(model_dir, "config.json")) + results = evaluate( model="hf-causal", - model_args='pretrained=' + model + ',tokenizer='+ args.tokenizer, + model_args="pretrained=" + model_dir + ",tokenizer="+ args.tokenizer, batch_size=args.batch_size, tasks=args.tasks, - model_format="onnx" + model_format="onnx", ) + + eval_acc = 0 for task_name in args.tasks: if task_name == "wikitext": print("Accuracy for %s is: %s" % (task_name, results["results"][task_name]["word_perplexity"])) + eval_acc += results["results"][task_name]["word_perplexity"] else: print("Accuracy for %s is: %s" % (task_name, results["results"][task_name]["acc"])) + eval_acc += results["results"][task_name]["acc"] + + if len(args.tasks) != 0: + eval_acc /= len(args.tasks) + + return eval_acc class KVDataloader: def __init__(self, model_path, pad_max=196, batch_size=1, sub_folder='train'): From 95ed5fd41d8306203a5033c6def74d695dfa1511 Mon Sep 17 00:00:00 2001 From: yuwenzho Date: Wed, 6 Dec 2023 14:26:09 +0800 Subject: [PATCH 22/22] update main.py Signed-off-by: yuwenzho --- .../text_generation/llama/quantization/ptq_static/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py index 90a68aa453a..71e5aa39a26 100644 --- a/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py +++ b/examples/onnxrt/nlp/huggingface_model/text_generation/llama/quantization/ptq_static/main.py @@ -189,9 +189,10 @@ def benchmark(model): total_time += toc - tic print("\n", "-" * 10, "Summary:", "-" * 10) - latency = total_time / (num_iter - num_warmup) print(args) - print("Inference latency: %.3f sec." % latency) + throughput = (num_iter - num_warmup) / total_time + print("Throughput: {} samples/s".format(throughput)) + def replace_architectures(json_path): # replace 'LLaMATokenizer' to lowercase 'LlamaTokenizer'