From f70d5ad76947c2bb070e361efd371106e5fabe46 Mon Sep 17 00:00:00 2001 From: regisss <15324346+regisss@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:18:44 +0000 Subject: [PATCH 01/97] Update Gaudi2 CI workflow --- .github/workflows/slow_tests_gaudi2.yml | 28 +++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/slow_tests_gaudi2.yml b/.github/workflows/slow_tests_gaudi2.yml index 1a8ee5b909..a737cc7d3e 100644 --- a/.github/workflows/slow_tests_gaudi2.yml +++ b/.github/workflows/slow_tests_gaudi2.yml @@ -21,12 +21,15 @@ jobs: - name: Run tests run: | docker run \ + --rm \ -v $PWD:/root/workspace \ + -v /scratch-1:/data \ --workdir=/root/workspace \ --runtime=habana \ -e HABANA_VISIBLE_DEVICES=all \ -e OMPI_MCA_btl_vader_single_copy_mechanism=none \ -e GAUDI2_CI=1 \ + -e HF_HOME=/data \ --cap-add=sys_nice \ --net=host \ --ipc=host \ @@ -47,17 +50,20 @@ jobs: - name: Run tests run: | docker run \ + --rm \ -v $PWD:/root/workspace \ + -v /scratch-1:/data \ --workdir=/root/workspace \ --runtime=habana \ -e HABANA_VISIBLE_DEVICES=all \ -e OMPI_MCA_btl_vader_single_copy_mechanism=none \ -e GAUDI2_CI=1 \ + -e HF_HOME=/data \ --cap-add=sys_nice \ --net=host \ --ipc=host \ vault.habana.ai/gaudi-docker/1.17.0/ubuntu22.04/habanalabs/pytorch-installer-2.3.1:latest \ - /bin/bash tests/ci/slow_tests_deepspeed.sh + /bin/bash pip install huggingface_hub && huggingface-cli --token ${{ secrets.TEXT_GENERATION_CI_HUB_TOKEN }} && tests/ci/slow_tests_deepspeed.sh fsdp: name: Test FSDP models if: ${{ !cancelled() && (success() || failure()) }} @@ -73,12 +79,15 @@ jobs: - name: Run tests run: | docker run \ + --rm \ -v $PWD:/root/workspace \ + -v /scratch-1:/data \ --workdir=/root/workspace \ --runtime=habana \ -e HABANA_VISIBLE_DEVICES=all \ -e OMPI_MCA_btl_vader_single_copy_mechanism=none \ -e GAUDI2_CI=1 \ + -e HF_HOME=/data \ --cap-add=sys_nice \ --net=host \ --ipc=host \ @@ -99,12 +108,15 @@ jobs: - name: Run tests run: | docker run \ + --rm \ -v $PWD:/root/workspace \ + -v /scratch-1:/data \ --workdir=/root/workspace \ --runtime=habana \ -e HABANA_VISIBLE_DEVICES=all \ -e OMPI_MCA_btl_vader_single_copy_mechanism=none \ -e GAUDI2_CI=1 \ + -e HF_HOME=/data \ --cap-add=sys_nice \ --net=host \ --ipc=host \ @@ -126,13 +138,16 @@ jobs: - name: Run tests run: | docker run \ + --rm \ -v $PWD:/root/workspace \ + -v /scratch-1:/data \ --workdir=/root/workspace \ --runtime=habana \ -e HABANA_VISIBLE_DEVICES=all \ -e OMPI_MCA_btl_vader_single_copy_mechanism=none \ -e GAUDI2_CI=1 \ -e RUN_ALBERT_XXL_1X=1 \ + -e HF_HOME=/data \ --cap-add=sys_nice \ --net=host \ --ipc=host \ @@ -155,12 +170,15 @@ jobs: - name: Run tests run: | docker run \ + --rm \ -v $PWD:/root/workspace \ + -v /scratch-1:/data \ --workdir=/root/workspace \ --runtime=habana \ -e HABANA_VISIBLE_DEVICES=all \ -e OMPI_MCA_btl_vader_single_copy_mechanism=none \ -e GAUDI2_CI=1 \ + -e HF_HOME=/data \ --cap-add=sys_nice \ --net=host \ --ipc=host \ @@ -181,12 +199,15 @@ jobs: - name: Run tests run: | docker run \ + --rm \ -v $PWD:/root/workspace \ + -v /scratch-1:/data \ --workdir=/root/workspace \ --runtime=habana \ -e HABANA_VISIBLE_DEVICES=all \ -e OMPI_MCA_btl_vader_single_copy_mechanism=none \ -e GAUDI2_CI=1 \ + -e HF_HOME=/data \ --cap-add=sys_nice \ --net=host \ --ipc=host \ @@ -215,12 +236,15 @@ jobs: - name: Run tests run: | docker run \ + --rm \ -v $PWD:/root/workspace \ + -v /scratch-1:/data \ --workdir=/root/workspace \ --runtime=habana \ -e HABANA_VISIBLE_DEVICES=all \ - -e GAUDI2_CI=1 \ -e OMPI_MCA_btl_vader_single_copy_mechanism=none \ + -e GAUDI2_CI=1 \ + -e HF_HOME=/data \ --cap-add=sys_nice \ --net=host \ --ipc=host \ From 05683d32137e4d2b2517efa5d02fae8d57b3cc6b Mon Sep 17 00:00:00 2001 From: Neelesh Gokhale Date: Mon, 9 Sep 2024 18:39:02 +0530 Subject: [PATCH 02/97] Fix Sentence Transformer HPU graphs for training with PEFT model (#1320) --- optimum/habana/sentence_transformers/st_gaudi_trainer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/optimum/habana/sentence_transformers/st_gaudi_trainer.py b/optimum/habana/sentence_transformers/st_gaudi_trainer.py index a443ad4f2c..3a17688b3b 100644 --- a/optimum/habana/sentence_transformers/st_gaudi_trainer.py +++ b/optimum/habana/sentence_transformers/st_gaudi_trainer.py @@ -46,6 +46,8 @@ from transformers.trainer_utils import EvalLoopOutput from transformers.training_args import ParallelMode +from optimum.habana.transformers.trainer import _is_peft_model + from ..transformers import GaudiConfig, GaudiTrainer from .st_gaudi_training_args import SentenceTransformerGaudiTrainingArguments @@ -224,7 +226,11 @@ def _wrap_model(self, model, training=True, dataloader=None): if self.args.use_hpu_graphs_for_training: import habana_frameworks.torch as ht - ht.hpu.ModuleCacher()(model=model, allow_unused_input=True, inplace=True) + if _is_peft_model(model): + base_model = model.get_base_model() + ht.hpu.ModuleCacher()(model=base_model, allow_unused_input=True, inplace=True) + else: + ht.hpu.ModuleCacher()(model=model, allow_unused_input=True, inplace=True) return model From 1cb67e409169c37a50b9705a5a7b6462b5db700e Mon Sep 17 00:00:00 2001 From: regisss <15324346+regisss@users.noreply.github.com> Date: Mon, 9 Sep 2024 13:34:40 +0000 Subject: [PATCH 03/97] Fix DeepSpeed command in Gaudi2 CI workflow --- .github/workflows/slow_tests_gaudi2.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/slow_tests_gaudi2.yml b/.github/workflows/slow_tests_gaudi2.yml index a737cc7d3e..a08b8fe17b 100644 --- a/.github/workflows/slow_tests_gaudi2.yml +++ b/.github/workflows/slow_tests_gaudi2.yml @@ -63,7 +63,7 @@ jobs: --net=host \ --ipc=host \ vault.habana.ai/gaudi-docker/1.17.0/ubuntu22.04/habanalabs/pytorch-installer-2.3.1:latest \ - /bin/bash pip install huggingface_hub && huggingface-cli --token ${{ secrets.TEXT_GENERATION_CI_HUB_TOKEN }} && tests/ci/slow_tests_deepspeed.sh + pip install huggingface_hub && huggingface-cli login --token ${{ secrets.TEXT_GENERATION_CI_HUB_TOKEN }} && /bin/bash tests/ci/slow_tests_deepspeed.sh fsdp: name: Test FSDP models if: ${{ !cancelled() && (success() || failure()) }} From 45c114f82d2eea10745f650a684a16075f6075f6 Mon Sep 17 00:00:00 2001 From: regisss <15324346+regisss@users.noreply.github.com> Date: Tue, 10 Sep 2024 07:53:16 +0000 Subject: [PATCH 04/97] Fix Gaudi2 CI workflow --- .github/workflows/slow_tests_gaudi2.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/slow_tests_gaudi2.yml b/.github/workflows/slow_tests_gaudi2.yml index a08b8fe17b..7de58a9341 100644 --- a/.github/workflows/slow_tests_gaudi2.yml +++ b/.github/workflows/slow_tests_gaudi2.yml @@ -134,7 +134,7 @@ jobs: uses: actions/checkout@v2 - name: Pull image run: | - docker pull vault.habana.ai/gaudi-docker/1.17.0/ubuntu22.04/habanalabs/pytorch-installer-2.3.1:latest:latest + docker pull vault.habana.ai/gaudi-docker/1.17.0/ubuntu22.04/habanalabs/pytorch-installer-2.3.1:latest - name: Run tests run: | docker run \ From fa1fbc5f81ab1012263d36c4b73201db8ecd05e5 Mon Sep 17 00:00:00 2001 From: Soila Kavulya Date: Tue, 10 Sep 2024 02:06:11 -0700 Subject: [PATCH 05/97] Fix ZeroDivisionError in constrained beam search with static shapes (#1317) --- optimum/habana/transformers/generation/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/optimum/habana/transformers/generation/utils.py b/optimum/habana/transformers/generation/utils.py index d333986679..d4baf44c06 100755 --- a/optimum/habana/transformers/generation/utils.py +++ b/optimum/habana/transformers/generation/utils.py @@ -3129,7 +3129,11 @@ def _constrained_beam_search( this_peer_finished = False - decoder_prompt_len = input_ids.shape[-1] # record the prompt length of decoder + # record the prompt length of decoder + if token_idx is not None: + decoder_prompt_len = cur_len + else: + decoder_prompt_len = input_ids.shape[-1] hb_profer = HabanaProfile( warmup=profiling_warmup_steps, active=profiling_steps, record_shapes=profiling_record_shapes From feb6545202ef6812445bbca680eb3ad50ffe5aa8 Mon Sep 17 00:00:00 2001 From: Jimin Ha Date: Wed, 11 Sep 2024 06:01:24 -0700 Subject: [PATCH 06/97] Update esmfold model not to use param_buffer_assignment (#1324) --- examples/protein-folding/run_esmfold.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/protein-folding/run_esmfold.py b/examples/protein-folding/run_esmfold.py index 4fa8d7a441..6941e6e5c1 100644 --- a/examples/protein-folding/run_esmfold.py +++ b/examples/protein-folding/run_esmfold.py @@ -82,6 +82,9 @@ def convert_outputs_to_pdb(outputs): test_protein = "MGAGASAEEKHSRELEKKLKEDAEKDARTVKLLLLGAGESGKSTIVKQMKIIHQDGYSLEECLEFIAIIYGNTLQSILAIVRAMTTLNIQYGDSARQDDARKLMHMADTIEEGTMPKEMSDIIQRLWKDSGIQACFERASEYQLNDSAGYYLSDLERLVTPGYVPTEQDVLRSRVKTTGIIETQFSFKDLNFRMFDVGGQRSERKKWIHCFEGVTCIIFIAALSAYDMVLVEDDEVNRMHESLHLFNSICNHRYFATTSIVLFLNKKDVFFEKIKKAHLSICFPDYDGPNTYEDAGNYIKVQFLELNMRRDVKEIYSHMTCATDTQNVKFVFDAVTDIIIKENLKDCGLF" # len = 350 tokenizer = AutoTokenizer.from_pretrained("facebook/esmfold_v1") +# Set _supports_param_buffer_assignment to False since facebook/esmfold_v1's encoder weights are float16. +# Without this fix, we will have the weights loaded with float16 on gaudi2,gaudi3 and runtime error on gaudi1 +EsmForProteinFolding._supports_param_buffer_assignment = False model = EsmForProteinFolding.from_pretrained("facebook/esmfold_v1", low_cpu_mem_usage=False) model = model.to(device) From b2c29b1e54de02f2b19f49851a661c6481fa70c9 Mon Sep 17 00:00:00 2001 From: Yeonsil Yoon Date: Wed, 11 Sep 2024 14:30:00 -0700 Subject: [PATCH 07/97] Falcon inference crash fix for falcon-40b model (#1161) --- .../models/falcon/modeling_falcon.py | 86 +++++++++++-------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/optimum/habana/transformers/models/falcon/modeling_falcon.py b/optimum/habana/transformers/models/falcon/modeling_falcon.py index a7a0c0e920..52fc649948 100644 --- a/optimum/habana/transformers/models/falcon/modeling_falcon.py +++ b/optimum/habana/transformers/models/falcon/modeling_falcon.py @@ -87,6 +87,40 @@ def gaudi_falcon_linear_forward(self, input: torch.Tensor) -> torch.Tensor: return hidden_states +def repeat_kv( + query_states: torch.Tensor, + key_states: torch.Tensor, + value_states: torch.Tensor, + attention_mask: torch.Tensor, + n_rep: int, +): + """ + Copied from repeat_kv: https://github.com/huggingface/transformers/blob/main/src/transformers/models/llama/modeling_llama.py + The only differences are: + - Append num_key_value_heads == 1 check as kv states can be broadcasted during matmuls so need to expand and reshape them. + - Add new args query_states, key_states, value_states and attention_mask and update the logic for expansion. + The query states go from (batch, num_heads, seqlen, head_dim) to (batch, num_key_value_heads, n_rep, seqlen, head_dim) + The key/value states go from (batch, num_key_value_heads, seqlen, head_dim) to (batch, num_key_value_heads, 1, seqlen, head_dim) + """ + batch, num_key_value_heads, kv_len, head_dim = key_states.shape + if n_rep == 1 or num_key_value_heads == 1: + return query_states, key_states, value_states, attention_mask + + new_kv_shape = (batch, num_key_value_heads, 1, kv_len, head_dim) + key_states = key_states.reshape(new_kv_shape) + value_states = value_states.reshape(new_kv_shape) + + batch, _, q_len, head_dim = query_states.shape + new_q_shape = (batch, num_key_value_heads, n_rep, q_len, head_dim) + query_states = query_states.reshape(new_q_shape) + + if attention_mask is not None: + # Add groups dim and set to 1 + attention_mask = attention_mask.unsqueeze(1) + + return query_states, key_states, value_states, attention_mask + + # FusedScaledDotProductAttention class ModuleFusedSDPA(torch.nn.Module): def __init__(self, fusedSDPA): @@ -123,40 +157,6 @@ def __init__(self, config: FalconConfig): self.softmax = Softmax() self.num_key_value_groups = config.num_attention_heads // config.num_kv_heads - def repeat_kv( - self, - query_states: torch.Tensor, - key_states: torch.Tensor, - value_states: torch.Tensor, - attention_mask: torch.Tensor, - n_rep: int, - ): - """ - Copied from repeat_kv: https://github.com/huggingface/transformers/blob/main/src/transformers/models/llama/modeling_llama.py - The only differences are: - - Append num_key_value_heads == 1 check as kv states can be broadcasted during matmuls so need to expand and reshape them. - - Add new args query_states, key_states, value_states and attention_mask and update the logic for expansion. - The query states go from (batch, num_heads, seqlen, head_dim) to (batch, num_key_value_heads, n_rep, seqlen, head_dim) - The key/value states go from (batch, num_key_value_heads, seqlen, head_dim) to (batch, num_key_value_heads, 1, seqlen, head_dim) - """ - batch, num_key_value_heads, kv_len, head_dim = key_states.shape - if n_rep == 1 or num_key_value_heads == 1: - return query_states, key_states, value_states, attention_mask - - new_kv_shape = (batch, num_key_value_heads, 1, kv_len, head_dim) - key_states = key_states.reshape(new_kv_shape) - value_states = value_states.reshape(new_kv_shape) - - batch, _, q_len, head_dim = query_states.shape - new_q_shape = (batch, num_key_value_heads, n_rep, q_len, head_dim) - query_states = query_states.reshape(new_q_shape) - - if attention_mask is not None: - # Add groups dim and set to 1 - attention_mask = attention_mask.unsqueeze(1) - - return query_states, key_states, value_states, attention_mask - def forward(self, query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None) -> torch.Tensor: L, S = query.size(-2), key.size(-2) scale_factor = 1 / math.sqrt(self.head_dim) @@ -173,7 +173,7 @@ def forward(self, query, key, value, attn_mask=None, dropout_p=0.0, is_causal=Fa if attn_mask.dtype == torch.bool: attn_mask.masked_fill_(attn_mask.logical_not(), float("-inf")) - query, key, value, attn_mask = self.repeat_kv(query, key, value, attn_mask, self.num_key_value_groups) + query, key, value, attn_mask = repeat_kv(query, key, value, attn_mask, self.num_key_value_groups) attn_weight = self.bmm1(query, key.transpose(-2, -1)) attn_weight += attn_mask @@ -262,7 +262,7 @@ def __init__(self, config: FalconConfig): # TODO, Does this affect memory usage? if self.is_fp8: self.fused_scaled_dot_product_attention = ModuleFusedSDPA(FusedSDPA) - self.unfused_scaled_dot_product_attention = ScaledDotProductAttention(config) + self.unfused_scaled_dot_product_attention = ScaledDotProductAttention(config) self.k_cache = KVCache() self.v_cache = KVCache() @@ -353,7 +353,11 @@ def pre_attn_forward( train_with_flash_attention = self.training and self._use_sdpa and not output_attentions and head_mask is None (query_layer, key_layer, value_layer) = self._split_heads( - fused_qkv, not use_flash_attention and not self.is_fp8 and not train_with_flash_attention + fused_qkv, + not use_flash_attention + and not self.is_fp8 + and not train_with_flash_attention + and not (self.config.num_kv_heads == 8), ) batch_size, query_length, _, _ = query_layer.shape @@ -462,6 +466,14 @@ def pre_attn_forward( query_layer, key_layer, value_layer, attention_mask, 0.0, is_causal=False ) else: + if query_layer.shape != key_layer.shape: + query_layer, key_layer, value_layer, attention_mask = repeat_kv( + query_layer, + key_layer, + value_layer, + attention_mask, + self.config.num_attention_heads // self.config.num_kv_heads, + ) # Workaround util scaled_dot_product_attention support broadcast. if self.training is True and query_layer.shape != key_layer.shape: key_layer = torch.broadcast_to(key_layer, query_layer.shape) From 0027e320b3f6c3f8a98d733ae9a42901de8a1e9d Mon Sep 17 00:00:00 2001 From: Daniel Socek Date: Wed, 11 Sep 2024 23:36:40 +0200 Subject: [PATCH 08/97] Boost SDXL speed with initialized schedule step reset (#1284) Signed-off-by: Daniel Socek --- .../stable_diffusion_xl/pipeline_stable_diffusion_xl.py | 4 ++-- .../pipeline_stable_diffusion_xl_img2img.py | 2 ++ .../pipeline_stable_diffusion_xl_inpaint.py | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/optimum/habana/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl.py b/optimum/habana/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl.py index 8785dbbd26..0cd0cd28dd 100644 --- a/optimum/habana/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl.py +++ b/optimum/habana/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl.py @@ -651,6 +651,8 @@ def __call__( t1 = t0 self._num_timesteps = len(timesteps) + if hasattr(self.scheduler, "set_begin_index"): + self.scheduler.set_begin_index() hb_profiler = HabanaProfile( warmup=profiling_warmup_steps, @@ -688,8 +690,6 @@ def __call__( guidance_scale_tensor, embedding_dim=self.unet.config.time_cond_proj_dim ).to(device=device, dtype=latents.dtype) - self._num_timesteps = len(timesteps) - # 8.3 Denoising loop throughput_warmup_steps = kwargs.get("throughput_warmup_steps", 3) use_warmup_inference_steps = ( diff --git a/optimum/habana/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl_img2img.py b/optimum/habana/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl_img2img.py index 79fad16655..7b6f25d920 100644 --- a/optimum/habana/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl_img2img.py +++ b/optimum/habana/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl_img2img.py @@ -536,6 +536,8 @@ def denoising_value_valid(dnv): ).to(device=device, dtype=latents.dtype) self._num_timesteps = len(timesteps) + if hasattr(self.scheduler, "set_begin_index"): + self.scheduler.set_begin_index() # 8.3 Denoising loop throughput_warmup_steps = kwargs.get("throughput_warmup_steps", 3) diff --git a/optimum/habana/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl_inpaint.py b/optimum/habana/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl_inpaint.py index accb64fd6a..8d94596e3b 100644 --- a/optimum/habana/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl_inpaint.py +++ b/optimum/habana/diffusers/pipelines/stable_diffusion_xl/pipeline_stable_diffusion_xl_inpaint.py @@ -744,6 +744,8 @@ def denoising_value_valid(dnv): ).to(device=device, dtype=latents.dtype) self._num_timesteps = len(timesteps) + if hasattr(self.scheduler, "set_begin_index"): + self.scheduler.set_begin_index() outputs = { "images": [], From f87f0fb4d9e965b540cfec88f802c2dcdfe1fcc8 Mon Sep 17 00:00:00 2001 From: Thanaji Rao Thakkalapelli Date: Sat, 14 Sep 2024 09:19:04 -0700 Subject: [PATCH 09/97] Enable INC for llava models and change softmax to use torch.nn.functional.softmax as its supported module by INC (#1325) --- examples/image-to-text/README.md | 3 +- examples/image-to-text/run_pipeline.py | 48 ++++++++++++++++--- .../transformers/models/clip/modeling_clip.py | 2 +- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/examples/image-to-text/README.md b/examples/image-to-text/README.md index 97494e6846..6cde1313bb 100644 --- a/examples/image-to-text/README.md +++ b/examples/image-to-text/README.md @@ -73,8 +73,7 @@ python3 run_pipeline.py \ ``` ### Inference with FP8 - -Inference for Llava-1.5-7b, Llava-1.5-13b, Llava-v1.6-mistral-7b and Llava-v1.6-vicuna-13b in FP8 precision are enabled using the Quantization Toolkit (HQT), which provides model measurement and quantization capabilities in PyTorch. +Inference for Llava-1.5-7b, Llava-1.5-13b, Llava-v1.6-mistral-7b and Llava-v1.6-vicuna-13b in FP8 precision are enabled using [Intel Neural Compressor (INC)](https://docs.habana.ai/en/latest/PyTorch/Inference_on_PyTorch/Inference_Using_FP8.html), which provides model measurement and quantization capabilities in PyTorch. INC is used by default for measuring and quantization. Habana Quantization Toolkit (HQT), which was used earlier, will be removed in future releases. To use HQT, disable INC by setting the following environment variable: `USE_INC=0`. More information on enabling FP8 in SynapseAI is available here: https://docs.habana.ai/en/latest/PyTorch/Inference_on_PyTorch/Inference_Using_FP8.html diff --git a/examples/image-to-text/run_pipeline.py b/examples/image-to-text/run_pipeline.py index 8ef45c4f61..e93982e33f 100644 --- a/examples/image-to-text/run_pipeline.py +++ b/examples/image-to-text/run_pipeline.py @@ -36,6 +36,46 @@ logger = logging.getLogger(__name__) +def setup_quantization(model, args): + if os.getenv("USE_INC", "1") != "0": + try: + from neural_compressor.torch.quantization import FP8Config, convert, prepare + except ImportError: + raise ImportError( + "Module neural_compressor is missing. Please use a newer Synapse version to use quantization, or set the environment variable to USE_INC=0" + ) + + config = FP8Config.from_json_file(args.quant_config) + if config.measure: + model = prepare(model, config) + elif config.quantize: + model = convert(model, config) + else: + import habana_frameworks.torch.core as htcore + import habana_quantization_toolkit + + habana_quantization_toolkit.prep_model(model) + htcore.hpu_initialize(model) + + return model + + +def finalize_quantization(model): + if os.getenv("USE_INC", "1") != "0": + try: + from neural_compressor.torch.quantization import finalize_calibration + except ImportError: + raise ImportError( + "Module neural_compressor is missing. Please use a newer Synapse version to use quantization, or set the environment variable to USE_INC=0" + ) + + finalize_calibration(model) + else: + import habana_quantization_toolkit + + habana_quantization_toolkit.finish_measurements(model) + + def main(): parser = argparse.ArgumentParser() @@ -169,18 +209,14 @@ def main(): generator.model = wrap_in_hpu_graph(generator.model) if args.quant_config: - import habana_quantization_toolkit - - habana_quantization_toolkit.prep_model(generator.model) - - htcore.hpu_initialize(generator.model) + generator.model = setup_quantization(generator.model, args) # warm up for i in range(args.warmup): generator(images, prompt=args.prompt, batch_size=args.batch_size, generate_kwargs=generate_kwargs) torch.hpu.synchronize() if args.quant_config: - habana_quantization_toolkit.finish_measurements(generator.model) + finalize_quantization(generator.model) start = time.perf_counter() for i in range(args.n_iterations): diff --git a/optimum/habana/transformers/models/clip/modeling_clip.py b/optimum/habana/transformers/models/clip/modeling_clip.py index b7fb3a222e..96b03ab32a 100644 --- a/optimum/habana/transformers/models/clip/modeling_clip.py +++ b/optimum/habana/transformers/models/clip/modeling_clip.py @@ -60,7 +60,7 @@ def __init__(self): super().__init__() def forward(self, x, dim=None, invAttnHead=None): - return torch.ops.hpu.softmax_fp8(x, dim, None, None, invAttnHead) + return torch.nn.functional.softmax(x, dim) class GaudiCLIPAttention(CLIPAttention): From 520c875807ef2a5b7d9bd96df335d4d0e2487f43 Mon Sep 17 00:00:00 2001 From: Kim Yann Date: Sun, 15 Sep 2024 00:23:19 +0800 Subject: [PATCH 10/97] Add `--use_kv_cache` to image-to-text pipeline (#1292) --- examples/image-to-text/README.md | 20 ++++++++++++++++ examples/image-to-text/run_pipeline.py | 32 +++++++++++++++++++------- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/examples/image-to-text/README.md b/examples/image-to-text/README.md index 6cde1313bb..2ac99dc829 100644 --- a/examples/image-to-text/README.md +++ b/examples/image-to-text/README.md @@ -28,6 +28,8 @@ Models that have been validated: - [llava-hf/llava-v1.6-mistral-7b-hf](https://huggingface.co/llava-hf/llava-v1.6-mistral-7b-hf) - [llava-hf/llava-v1.6-vicuna-7b-hf](https://huggingface.co/llava-hf/llava-v1.6-vicuna-7b-hf) - [llava-hf/llava-v1.6-vicuna-13b-hf](https://huggingface.co/llava-hf/llava-v1.6-vicuna-13b-hf) + - [llava-hf/llava-v1.6-34b-hf](https://huggingface.co/llava-hf/llava-v1.6-34b-hf) + - [llava-hf/llama3-llava-next-8b-hf](https://huggingface.co/llava-hf/llama3-llava-next-8b-hf) ### Inference with BF16 @@ -72,6 +74,24 @@ python3 run_pipeline.py \ --bf16 ``` +To run Llava-hf/llava-v1.6-34b-hf inference, use the following command: + +```bash +python3 run_pipeline.py \ + --model_name_or_path llava-hf/llava-v1.6-34b-hf \ + --use_hpu_graphs \ + --bf16 +``` + +To run Llava-hf/llama3-llava-next-8b-hf inference, use the following command: + +```bash +python3 run_pipeline.py \ + --model_name_or_path llava-hf/llama3-llava-next-8b-hf \ + --use_hpu_graphs \ + --bf16 +``` + ### Inference with FP8 Inference for Llava-1.5-7b, Llava-1.5-13b, Llava-v1.6-mistral-7b and Llava-v1.6-vicuna-13b in FP8 precision are enabled using [Intel Neural Compressor (INC)](https://docs.habana.ai/en/latest/PyTorch/Inference_on_PyTorch/Inference_Using_FP8.html), which provides model measurement and quantization capabilities in PyTorch. INC is used by default for measuring and quantization. Habana Quantization Toolkit (HQT), which was used earlier, will be removed in future releases. To use HQT, disable INC by setting the following environment variable: `USE_INC=0`. diff --git a/examples/image-to-text/run_pipeline.py b/examples/image-to-text/run_pipeline.py index e93982e33f..9161285881 100644 --- a/examples/image-to-text/run_pipeline.py +++ b/examples/image-to-text/run_pipeline.py @@ -23,7 +23,7 @@ import PIL.Image import requests import torch -from transformers import AutoConfig, pipeline +from transformers import AutoConfig, LlavaNextProcessor, LlavaProcessor, pipeline from optimum.habana.transformers.modeling_utils import adapt_transformers_to_gaudi @@ -141,6 +141,11 @@ def main(): action="store_true", help="Whether to enable Habana Flash Attention in recompute mode on first token generation. This gives an opportunity of splitting graph internally which helps reduce memory consumption.", ) + parser.add_argument( + "--use_kv_cache", + action="store_true", + help="Whether to use the key/value cache for decoding. It should speed up generation.", + ) args = parser.parse_args() @@ -156,12 +161,21 @@ def main(): args.image_path = [ "https://github.com/haotian-liu/LLaVA/blob/1a91fc274d7c35a9b50b3cb29c4247ae5837ce39/images/llava_v1_5_radar.jpg?raw=true" ] - if args.prompt is None and model_type == "llava": - args.prompt = "\nUSER: What's the content of the image?\nASSISTANT:" - elif args.prompt is None and model_type == "llava_next": - args.prompt = "[INST] \nWhat is shown in this image? [/INST]" - if args.model_name_or_path in ["llava-hf/llava-v1.6-vicuna-13b-hf", "llava-hf/llava-v1.6-vicuna-7b-hf"]: - args.prompt = "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions. USER: \nWhat is shown in this image? ASSISTANT:" + if args.prompt is None: + if model_type == "llava": + processor = LlavaProcessor.from_pretrained(args.model_name_or_path) + elif model_type == "llava_next": + processor = LlavaNextProcessor.from_pretrained(args.model_name_or_path) + conversation = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "What is shown in this image?"}, + {"type": "image"}, + ], + } + ] + args.prompt = processor.apply_chat_template(conversation, add_generation_prompt=True) image_paths = args.image_path image_paths_len = len(image_paths) @@ -197,6 +211,7 @@ def main(): ) generate_kwargs = { "lazy_mode": True, + "use_cache": args.use_kv_cache, "hpu_graphs": args.use_hpu_graphs, "max_new_tokens": args.max_new_tokens, "ignore_eos": args.ignore_eos, @@ -233,8 +248,9 @@ def main(): total_new_tokens_generated = args.n_iterations * n_output_tokens throughput = total_new_tokens_generated / duration + logger.info(f"result = {result}") logger.info( - f"result = {result}, time = {(end-start) * 1000 / args.n_iterations }ms, Throughput (including tokenization) = {throughput} tokens/second" + f"time = {(end-start) * 1000 / args.n_iterations }ms, Throughput (including tokenization) = {throughput} tokens/second" ) # Store results if necessary From 1a8ad12422e5440424412041f124c1378057b0ed Mon Sep 17 00:00:00 2001 From: "Wang, Yi" Date: Tue, 17 Sep 2024 03:54:06 +0800 Subject: [PATCH 11/97] Trl upgrade (#1245) Signed-off-by: Wang, Yi A --- examples/trl/dpo.py | 11 +- examples/trl/requirements.txt | 4 +- examples/trl/sft.py | 12 +- optimum/habana/trl/__init__.py | 2 + optimum/habana/trl/trainer/__init__.py | 2 + optimum/habana/trl/trainer/dpo_config.py | 62 ++++ optimum/habana/trl/trainer/dpo_trainer.py | 403 +++++++++++++++++----- optimum/habana/trl/trainer/ppo_config.py | 13 +- optimum/habana/trl/trainer/ppo_trainer.py | 84 +++-- optimum/habana/trl/trainer/sft_config.py | 38 ++ optimum/habana/trl/trainer/sft_trainer.py | 242 ++++++++++--- 11 files changed, 689 insertions(+), 184 deletions(-) create mode 100644 optimum/habana/trl/trainer/dpo_config.py create mode 100644 optimum/habana/trl/trainer/sft_config.py diff --git a/examples/trl/dpo.py b/examples/trl/dpo.py index bc9049a8a7..b14d3e9a13 100644 --- a/examples/trl/dpo.py +++ b/examples/trl/dpo.py @@ -10,8 +10,8 @@ is_deepspeed_available, ) -from optimum.habana import GaudiConfig, GaudiTrainingArguments -from optimum.habana.trl import GaudiDPOTrainer +from optimum.habana import GaudiConfig +from optimum.habana.trl import GaudiDPOConfig, GaudiDPOTrainer from optimum.habana.utils import set_seed @@ -48,6 +48,9 @@ class ScriptArguments: gradient_checkpointing: Optional[bool] = field( default=False, metadata={"help": "whether to use gradient checkpointing"} ) + gradient_checkpointing_use_reentrant: Optional[bool] = field( + default=False, metadata={"help": "whether to use reentrant for gradient checkpointing"} + ) lora_alpha: Optional[float] = field(default=16, metadata={"help": "the lora alpha parameter"}) lora_dropout: Optional[float] = field(default=0.05, metadata={"help": "the lora dropout parameter"}) @@ -140,7 +143,7 @@ def return_prompt_and_responses(samples) -> Dict[str, str]: script_args = parser.parse_args_into_dataclasses()[0] # 1. initialize training arguments: - training_args = GaudiTrainingArguments( + training_args = GaudiDPOConfig( per_device_train_batch_size=script_args.per_device_train_batch_size, per_device_eval_batch_size=script_args.per_device_eval_batch_size, max_steps=script_args.max_steps, @@ -159,6 +162,7 @@ def return_prompt_and_responses(samples) -> Dict[str, str]: bf16=True, remove_unused_columns=False, run_name="dpo_llama2", + gradient_checkpointing_kwargs={"use_reentrant": script_args.gradient_checkpointing_use_reentrant}, use_habana=True, use_lazy_mode=True, use_hpu_graphs_for_training=not script_args.gradient_checkpointing and (not script_args.deepspeed), @@ -246,6 +250,7 @@ def return_prompt_and_responses(samples) -> Dict[str, str]: peft_config=peft_config, max_prompt_length=script_args.max_prompt_length, max_length=script_args.max_length, + force_use_ref_model=True, ) # 6. train diff --git a/examples/trl/requirements.txt b/examples/trl/requirements.txt index 01f0e51a80..248692fd27 100644 --- a/examples/trl/requirements.txt +++ b/examples/trl/requirements.txt @@ -1,5 +1,5 @@ -trl == 0.8.6 -peft == 0.6.2 +trl == 0.9.6 +peft == 0.12.0 datasets == 2.19.2 tyro evaluate diff --git a/examples/trl/sft.py b/examples/trl/sft.py index 170526a99f..00568438e5 100644 --- a/examples/trl/sft.py +++ b/examples/trl/sft.py @@ -15,8 +15,8 @@ is_deepspeed_available, ) -from optimum.habana import GaudiConfig, GaudiTrainingArguments -from optimum.habana.trl import GaudiSFTTrainer +from optimum.habana import GaudiConfig +from optimum.habana.trl import GaudiSFTConfig, GaudiSFTTrainer from optimum.habana.utils import set_seed @@ -33,9 +33,7 @@ class ScriptArguments: size_valid_set: Optional[int] = field(default=4000, metadata={"help": "the size of the validation set"}) streaming: Optional[bool] = field(default=True, metadata={"help": "whether to stream the dataset"}) shuffle_buffer: Optional[int] = field(default=5000, metadata={"help": "the shuffle buffer size"}) - max_seq_length: Optional[int] = field(default=1024, metadata={"help": "the max sequence length"}) num_workers: Optional[int] = field(default=4, metadata={"help": "the number of workers"}) - packing: Optional[bool] = field(default=True, metadata={"help": "whether to use packing for SFTTrainer"}) validation_split_percentage: Optional[int] = field( default=5, metadata={ @@ -73,7 +71,7 @@ class ScriptArguments: if __name__ == "__main__": - parser = HfArgumentParser((ScriptArguments, GaudiTrainingArguments)) + parser = HfArgumentParser((ScriptArguments, GaudiSFTConfig)) script_args, training_args = parser.parse_args_into_dataclasses() if script_args.use_peft: peft_config = LoraConfig( @@ -87,7 +85,7 @@ class ScriptArguments: else: peft_config = None - if training_args.group_by_length and script_args.packing: + if training_args.group_by_length and training_args.packing: raise ValueError("Cannot use both packing and group by length") set_seed(training_args.seed) @@ -187,8 +185,6 @@ def create_datasets(tokenizer, args, seed=None): train_dataset=train_dataset, eval_dataset=eval_dataset, peft_config=peft_config, - packing=script_args.packing, - max_seq_length=script_args.max_seq_length, tokenizer=tokenizer, args=training_args, formatting_func=formatting_func, diff --git a/optimum/habana/trl/__init__.py b/optimum/habana/trl/__init__.py index c35efc5a61..37f4d1156f 100644 --- a/optimum/habana/trl/__init__.py +++ b/optimum/habana/trl/__init__.py @@ -1,8 +1,10 @@ from .models.modeling_base import adapt_PreTrainedModelWrapper_to_gaudi from .models.modeling_sd_base import GaudiDefaultDDPOStableDiffusionPipeline from .trainer.ddpo_trainer import GaudiDDPOTrainer +from .trainer.dpo_config import GaudiDPOConfig from .trainer.dpo_trainer import GaudiDPOTrainer from .trainer.ppo_config import GaudiPPOConfig from .trainer.ppo_trainer import GaudiPPOTrainer from .trainer.reward_trainer import GaudiRewardTrainer, RewardDataCollatorWithPadding +from .trainer.sft_config import GaudiSFTConfig from .trainer.sft_trainer import GaudiSFTTrainer diff --git a/optimum/habana/trl/trainer/__init__.py b/optimum/habana/trl/trainer/__init__.py index e8a164ee58..6da9debbd8 100644 --- a/optimum/habana/trl/trainer/__init__.py +++ b/optimum/habana/trl/trainer/__init__.py @@ -24,3 +24,5 @@ from .reward_trainer import GaudiRewardTrainer, RewardDataCollatorWithPadding from .ddpo_trainer import GaudiDDPOTrainer +from .dpo_config import GaudiDPOConfig +from .sft_config import GaudiSFTConfig diff --git a/optimum/habana/trl/trainer/dpo_config.py b/optimum/habana/trl/trainer/dpo_config.py new file mode 100644 index 0000000000..75847f2ea4 --- /dev/null +++ b/optimum/habana/trl/trainer/dpo_config.py @@ -0,0 +1,62 @@ +# Copyright 2022 The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from dataclasses import dataclass +from typing import Dict, Literal, Optional + +from trl.trainer.dpo_config import FDivergenceType + +from ... import GaudiTrainingArguments + + +@dataclass +class GaudiDPOConfig(GaudiTrainingArguments): + r""" + Initialize GaudiDPOConfig. + Adapted from https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/dpo_config.py#L33 + - inherit from GaudiTrainingArguments + """ + + beta: float = 0.1 + label_smoothing: float = 0 + loss_type: Literal[ + "sigmoid", "hinge", "ipo", "bco_pair", "sppo_hard", "nca_pair", "robust", "aot", "aot_pair", "exo_pair" + ] = "sigmoid" + label_pad_token_id: int = -100 + padding_value: Optional[int] = None + truncation_mode: str = "keep_end" + max_length: Optional[int] = None + max_prompt_length: Optional[int] = None + max_target_length: Optional[int] = None + is_encoder_decoder: Optional[bool] = None + disable_dropout: bool = True + generate_during_eval: bool = False + precompute_ref_log_probs: bool = False + dataset_num_proc: Optional[int] = None + model_init_kwargs: Optional[Dict] = None + ref_model_init_kwargs: Optional[Dict] = None + model_adapter_name: Optional[str] = None + ref_adapter_name: Optional[str] = None + reference_free: bool = False + force_use_ref_model: bool = False + f_divergence_type: Optional[FDivergenceType] = FDivergenceType.REVERSE_KL + f_alpha_divergence_coef: Optional[float] = 1.0 + sync_ref_model: bool = False + ref_model_mixup_alpha: float = 0.9 + ref_model_sync_steps: int = 64 + rpo_alpha: Optional[float] = None + + def __post_init__(self): + if self.loss_type == "kto_pair": + raise ValueError("Support for kto_pair has been removed in DPOTrainer. Please use KTOTrainer.") + return super().__post_init__() diff --git a/optimum/habana/trl/trainer/dpo_trainer.py b/optimum/habana/trl/trainer/dpo_trainer.py index 1d74d7e331..bd07a981bb 100644 --- a/optimum/habana/trl/trainer/dpo_trainer.py +++ b/optimum/habana/trl/trainer/dpo_trainer.py @@ -19,6 +19,7 @@ import torch import torch.nn as nn +from accelerate import PartialState from accelerate.utils import is_deepspeed_available from datasets import Dataset from transformers import ( @@ -27,17 +28,22 @@ PreTrainedModel, PreTrainedTokenizerBase, ) +from transformers.models.auto.modeling_auto import MODEL_FOR_VISION_2_SEQ_MAPPING_NAMES from transformers.trainer_callback import TrainerCallback from transformers.trainer_utils import EvalLoopOutput from trl import DPOTrainer, create_reference_model from trl.import_utils import is_peft_available, is_wandb_available +from trl.trainer.dpo_config import FDivergenceConstants from trl.trainer.utils import ( DPODataCollatorWithPadding, + RunningMoments, + SyncRefModelCallback, disable_dropout_in_model, pad_to_length, ) -from ... import GaudiConfig, GaudiTrainer, GaudiTrainingArguments +from ... import GaudiConfig, GaudiTrainer +from .dpo_config import GaudiDPOConfig if is_peft_available(): @@ -58,8 +64,8 @@ def __init__( ref_model: Optional[Union[PreTrainedModel, nn.Module, str]] = None, beta: float = 0.1, label_smoothing: float = 0, - loss_type: Literal["sigmoid", "hinge", "ipo", "kto"] = "sigmoid", - args: GaudiTrainingArguments = None, + loss_type: Literal["sigmoid", "hinge", "ipo", "bco_pair", "robust", "aot", "aot_pair"] = "sigmoid", + args: Optional[GaudiDPOConfig] = None, gaudi_config: GaudiConfig = None, data_collator: Optional[DataCollator] = None, label_pad_token_id: int = -100, @@ -81,6 +87,7 @@ def __init__( generate_during_eval: bool = False, compute_metrics: Optional[Callable[[EvalLoopOutput], Dict]] = None, precompute_ref_log_probs: bool = False, + dataset_num_proc: Optional[int] = None, model_init_kwargs: Optional[Dict] = None, ref_model_init_kwargs: Optional[Dict] = None, model_adapter_name: Optional[str] = None, @@ -89,24 +96,67 @@ def __init__( force_use_ref_model: bool = False, ): """ - Copied from DPOTrainer.__init__: https://github.com/huggingface/trl/blob/v0.7.6/trl/trainer/dpo_trainer.py#L127 + Copied from DPOTrainer.__init__: https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/dpo_trainer.py#L134 The only differences are: - add new args gaudi_config - use graph for ref_model - use GaudiTrainer instead of Trainer - cast peft model to bf16. """ - if model_init_kwargs is None: + if model_init_kwargs is not None: + warnings.warn( + "You passed `model_init_kwargs` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.model_init_kwargs = model_init_kwargs + + if args.model_init_kwargs is None: model_init_kwargs = {} elif not isinstance(model, str): - raise ValueError("You passed model_kwargs to the DPOTrainer. But your model is already instantiated.") + raise ValueError( + "You passed model_init_kwargs to the DPOTrainer/DPOConfig, but your model is already instantiated." + ) + else: + model_init_kwargs = args.model_init_kwargs + + torch_dtype = model_init_kwargs["torch_dtype"] + if torch_dtype is not None: + # Convert to `torch.dtype` if an str is passed + if isinstance(torch_dtype, str) and torch_dtype != "auto": + torch_dtype = getattr(torch, torch_dtype) - if ref_model_init_kwargs is None: + if torch_dtype != "auto" and not isinstance(torch_dtype, torch.dtype): + raise ValueError( + f"Invalid `torch_dtype` passed to the DPOConfig. Expected a string with either `torch.dtype` or 'auto', but got {torch_dtype}." + ) + + model_init_kwargs["torch_dtype"] = torch_dtype + + if ref_model_init_kwargs is not None: + warnings.warn( + "You passed `ref_model_init_kwargs` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.ref_model_init_kwargs = ref_model_init_kwargs + + if args.ref_model_init_kwargs is None: ref_model_init_kwargs = {} elif not isinstance(ref_model, str): raise ValueError( - "You passed ref_model_kwargs to the DPOTrainer. But your ref_model is already instantiated." + "You passed ref_model_init_kwargs to the DPOTrainer/DPOConfig, but your ref_model is already instantiated." ) + else: + ref_model_init_kwargs = args.ref_model_init_kwargs + torch_dtype = ref_model_init_kwargs["torch_dtype"] + if torch_dtype is not None: + # Convert to `torch.dtype` if an str is passed + if isinstance(torch_dtype, str) and torch_dtype != "auto": + torch_dtype = getattr(torch, torch_dtype) + + if torch_dtype != "auto" and not isinstance(torch_dtype, torch.dtype): + raise ValueError( + f"Invalid `torch_dtype` passed to the DPOConfig. Expected a string with either `torch.dtype` or 'auto', but got {torch_dtype}." + ) + + ref_model_init_kwargs["torch_dtype"] = torch_dtype if isinstance(model, str): warnings.warn( @@ -126,6 +176,12 @@ def __init__( # has been called in order to properly call autocast if needed. self._peft_has_been_casted_to_bf16 = False + if force_use_ref_model: + warnings.warn( + "You passed `force_use_ref_model` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.force_use_ref_model = force_use_ref_model + if not is_peft_available() and peft_config is not None: raise ValueError( "PEFT is not installed and you passed a `peft_config` in the trainer's kwargs, please install it to use the PEFT models" @@ -135,6 +191,13 @@ def __init__( if isinstance(model, PeftModel): model = model.merge_and_unload() + if ref_model is not None and not args.force_use_ref_model: + raise ValueError( + "You passed both a ref_model and a peft_config. For training PEFT adapters with DPO there is no need to pass a reference" + " model. Please pass `ref_model=None` in case you want to train PEFT adapters, or pass a ref_model with `force_use_ref_model=True` in DPOTrainer's init." + " if you want to use a different ref_model." + ) + if getattr(model, "is_loaded_in_8bit", False) or getattr(model, "is_loaded_in_4bit", False): _support_gc_kwargs = hasattr( args, "gradient_checkpointing_kwargs" @@ -142,12 +205,12 @@ def __init__( inspect.signature(prepare_model_for_kbit_training).parameters ) - preprare_model_kwargs = {"use_gradient_checkpointing": args.gradient_checkpointing} + prepare_model_kwargs = {"use_gradient_checkpointing": args.gradient_checkpointing} if _support_gc_kwargs: - preprare_model_kwargs["gradient_checkpointing_kwargs"] = args.gradient_checkpointing_kwargs + prepare_model_kwargs["gradient_checkpointing_kwargs"] = args.gradient_checkpointing_kwargs - model = prepare_model_for_kbit_training(model, **preprare_model_kwargs) + model = prepare_model_for_kbit_training(model, **prepare_model_kwargs) elif getattr(args, "gradient_checkpointing", False): # For backward compatibility with older versions of transformers if hasattr(model, "enable_input_require_grads"): @@ -161,10 +224,12 @@ def make_inputs_require_grad(module, input, output): # get peft model with the given config model = get_peft_model(model, peft_config) - if args.bf16: + if args.bf16 and getattr(model, "is_loaded_in_4bit", False): model = model.to(torch.bfloat16) + # If args.bf16 we need to explicitly call `generate` with torch amp autocast context manager + self._peft_has_been_casted_to_bf16 = True - # For models that use gradient_checkpoiting, we need to attach a hook that enables input + # For models that use gradient_checkpointing, we need to attach a hook that enables input # to explicitly have `requires_grad=True`, otherwise training will either silently # fail or completely fail. elif getattr(args, "gradient_checkpointing", False): @@ -178,63 +243,132 @@ def make_inputs_require_grad(module, input, output): model.get_input_embeddings().register_forward_hook(make_inputs_require_grad) - if generate_during_eval and not is_wandb_available(): + if generate_during_eval: + warnings.warn( + "You passed `generate_during_eval` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.generate_during_eval = generate_during_eval + if args.generate_during_eval and not is_wandb_available(): raise ValueError( "`generate_during_eval=True` requires Weights and Biases to be installed." " Please install `wandb` to resolve." ) + if is_encoder_decoder is not None: + warnings.warn( + "You passed `is_encoder_decoder` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.is_encoder_decoder = is_encoder_decoder if model is not None: self.is_encoder_decoder = model.config.is_encoder_decoder - elif is_encoder_decoder is None: - raise ValueError("When no model is provided, you need to pass the parameter is_encoder_decoder.") + elif args.is_encoder_decoder is None: + raise ValueError( + "When no model is provided, you need to pass the parameter is_encoder_decoder to the DPOTrainer/DPOConfig." + ) else: - self.is_encoder_decoder = is_encoder_decoder + self.is_encoder_decoder = args.is_encoder_decoder + + if model is not None: + self.is_vision_model = model.config.model_type in MODEL_FOR_VISION_2_SEQ_MAPPING_NAMES.keys() + else: + warnings.warn( + "No model provided, cannot determine if it is a vision model. Setting is_vision_model to False." + ) + self.is_vision_model = False + + if self.is_vision_model: + self.processor = tokenizer + self.tokenizer = tokenizer.tokenizer # tokenizer is actually a processor at this point + else: + self.tokenizer = tokenizer self.is_peft_model = is_peft_available() and isinstance(model, PeftModel) - self.model_adapter_name = model_adapter_name - self.ref_adapter_name = ref_adapter_name - self.reference_free = reference_free + if model_adapter_name is not None: + warnings.warn( + "You passed `model_adapter_name` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.model_adapter_name = model_adapter_name + self.model_adapter_name = args.model_adapter_name + + if ref_adapter_name is not None: + warnings.warn( + "You passed `ref_adapter_name` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.ref_adapter_name = ref_adapter_name + self.ref_adapter_name = args.ref_adapter_name + + if reference_free: + warnings.warn( + "You passed `reference_free` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.reference_free = reference_free + self.reference_free = args.reference_free + + if precompute_ref_log_probs: + warnings.warn( + "You passed `precompute_ref_log_probs` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.precompute_ref_log_probs = precompute_ref_log_probs if ref_model: self.ref_model = ref_model - elif self.is_peft_model or precompute_ref_log_probs: + elif self.is_peft_model or args.precompute_ref_log_probs: # The `model` with adapters turned off will be used as the reference model self.ref_model = None else: self.ref_model = create_reference_model(model) - if data_collator is None: - if tokenizer is None: - raise ValueError( - "max_length or a tokenizer must be specified when using the default DPODataCollatorWithPadding" - ) - if max_length is None: - warnings.warn( - "When using DPODataCollatorWithPadding, you should set `max_length` in the DPOTrainer's init" - " it will be set to `512` by default, but you should do it yourself in the future.", - UserWarning, - ) - max_length = 512 - if max_prompt_length is None: - warnings.warn( - "When using DPODataCollatorWithPadding, you should set `max_prompt_length` in the DPOTrainer's init" - " it will be set to `128` by default, but you should do it yourself in the future.", - UserWarning, - ) - max_prompt_length = 128 + if tokenizer is None: + raise ValueError("tokenizer must be specified to tokenize a DPO dataset.") - if max_target_length is None and self.is_encoder_decoder: - warnings.warn( - "When using DPODataCollatorWithPadding with an encoder decoder architecture, you should set `max_target_length` in the DPOTrainer's init" - " it will be set to `128` by default, but you should do it yourself in the future.", - UserWarning, - ) - max_target_length = 128 + if max_length is not None: + warnings.warn( + "You passed `max_length` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.max_length = max_length + if args.max_length is None: + warnings.warn( + "`max_length` is not set in the DPOConfig's init" + " it will default to `512` by default, but you should do it yourself in the future.", + UserWarning, + ) + args.max_length = 512 + if max_prompt_length is not None: + warnings.warn( + "You passed `max_prompt_length` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.max_prompt_length = max_prompt_length + if args.max_prompt_length is None: + warnings.warn( + "`max_prompt_length` is not set in the DPOConfig's init" + " it will default to `128` by default, but you should do it yourself in the future.", + UserWarning, + ) + args.max_prompt_length = 128 + + if max_target_length is not None: + warnings.warn( + "You passed `max_target_length` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.max_target_length = max_target_length + if args.max_target_length is None and self.is_encoder_decoder: + warnings.warn( + "When using an encoder decoder architecture, you should set `max_target_length` in the DPOConfig's init" + " it will default to `128` by default, but you should do it yourself in the future.", + UserWarning, + ) + args.max_target_length = 128 + + if label_pad_token_id != -100: + warnings.warn( + "You passed `label_pad_token_id` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.label_pad_token_id = label_pad_token_id + if data_collator is None: data_collator = DPODataCollatorWithPadding( - pad_token_id=tokenizer.pad_token_id, - label_pad_token_id=label_pad_token_id, + pad_token_id=self.tokenizer.pad_token_id, + label_pad_token_id=args.label_pad_token_id, is_encoder_decoder=self.is_encoder_decoder, ) @@ -251,41 +385,88 @@ def make_inputs_require_grad(module, input, output): else: self.use_dpo_data_collator = False - if disable_dropout: + if not disable_dropout: + warnings.warn( + "You passed `disable_dropout` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.disable_dropout = disable_dropout + if args.disable_dropout: disable_dropout_in_model(model) if self.ref_model is not None: disable_dropout_in_model(self.ref_model) - self.max_length = max_length - self.generate_during_eval = generate_during_eval - self.label_pad_token_id = label_pad_token_id - self.padding_value = padding_value if padding_value is not None else tokenizer.pad_token_id - self.max_prompt_length = max_prompt_length - self.truncation_mode = truncation_mode - self.max_target_length = max_target_length - self.tokenizer = tokenizer - self.precompute_ref_log_probs = precompute_ref_log_probs + self.max_length = args.max_length + self.generate_during_eval = args.generate_during_eval + self.label_pad_token_id = args.label_pad_token_id + if padding_value is not None: + warnings.warn( + "You passed `padding_value` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.padding_value = padding_value + self.padding_value = args.padding_value if padding_value is not None else self.tokenizer.pad_token_id + self.max_prompt_length = args.max_prompt_length + if truncation_mode != "keep_end": + warnings.warn( + "You passed `truncation_mode` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.truncation_mode = truncation_mode + self.truncation_mode = args.truncation_mode + self.max_target_length = args.max_target_length + self.precompute_ref_log_probs = args.precompute_ref_log_probs # Since ref_logs are precomputed on the first call to get_train/eval_dataloader # keep track of first called to avoid computation of future calls self._precomputed_train_ref_log_probs = False self._precomputed_eval_ref_log_probs = False - if loss_type in ["hinge", "ipo", "kto_pair"] and label_smoothing > 0: + if loss_type != "sigmoid": + warnings.warn( + "You passed `loss_type` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.loss_type = loss_type + if label_smoothing != 0: + warnings.warn( + "You passed `label_smoothing` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.label_smoothing = label_smoothing + if args.loss_type in ["hinge", "ipo", "bco_pair"] and args.label_smoothing > 0: warnings.warn( "You are using a loss type that does not support label smoothing. Ignoring label_smoothing parameter." ) + if args.loss_type == "kto_pair": + raise ValueError("Support for kto_pair has been removed in DPOTrainer. Please use KTOTrainer.") - self.beta = beta - self.label_smoothing = label_smoothing - self.loss_type = loss_type + if beta != 0.1: + warnings.warn( + "You passed `beta` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.beta = beta + self.beta = args.beta + self.label_smoothing = args.label_smoothing + self.loss_type = args.loss_type + self.aux_loss_enabled = getattr(model.config, "output_router_logits", False) self._stored_metrics = defaultdict(lambda: defaultdict(list)) - # tokenize the dataset - train_dataset = train_dataset.map(self.tokenize_row) - if eval_dataset is not None: - eval_dataset = eval_dataset.map(self.tokenize_row) + self.f_divergence_type = args.f_divergence_type + self.f_divergence_params = {FDivergenceConstants.ALPHA_DIVERGENCE_COEF_KEY: args.f_alpha_divergence_coef} + + if dataset_num_proc is not None: + warnings.warn( + "You passed `dataset_num_proc` to the DPOTrainer, the value you passed will override the one in the `DPOConfig`." + ) + args.dataset_num_proc = dataset_num_proc + self.dataset_num_proc = args.dataset_num_proc + + # Compute that only on the main process for faster data processing. + # see: https://github.com/huggingface/trl/pull/1255 + with PartialState().local_main_process_first(): + # tokenize the dataset, lower writer batch size to avoid OOM (frequent in vision models) + train_dataset = train_dataset.map(self.tokenize_row, num_proc=self.dataset_num_proc, writer_batch_size=10) + if eval_dataset is not None: + eval_dataset = eval_dataset.map( + self.tokenize_row, num_proc=self.dataset_num_proc, writer_batch_size=10 + ) GaudiTrainer.__init__( self, @@ -303,6 +484,10 @@ def make_inputs_require_grad(module, input, output): preprocess_logits_for_metrics=preprocess_logits_for_metrics, ) + # Add tags for models that have been loaded with the correct transformers version + if hasattr(self.model, "add_model_tags"): + self.model.add_model_tags(self._tag_names) + if not hasattr(self, "accelerator"): raise AttributeError( "Your `Trainer` does not have an `accelerator` object. Consider upgrading `transformers`." @@ -320,28 +505,42 @@ def make_inputs_require_grad(module, input, output): raise ValueError( "No reference model and model is not a Peft model. Try setting `precompute_ref_log_probs=True`" ) + if args.sync_ref_model: + raise ValueError( + "You currently cannot use `ref_model=None` with TR-DPO method. Please provide `ref_model`." + ) else: if self.is_deepspeed_enabled: self.ref_model = self._prepare_deepspeed(self.ref_model) else: self.ref_model = self.accelerator.prepare_model(self.ref_model, evaluation_mode=True) - from habana_frameworks.torch.hpu import wrap_in_hpu_graph # use graph for ref_model ref_model = self.accelerator.unwrap_model(self.ref_model) ref_model = wrap_in_hpu_graph(ref_model) + if args.sync_ref_model: + if precompute_ref_log_probs: + raise ValueError( + "You cannot use `precompute_ref_log_probs=True` with TR-DPO method. Please set `precompute_ref_log_probs=False`." + ) + + self.add_callback(SyncRefModelCallback(ref_model=self.ref_model, accelerator=self.accelerator)) + if self.loss_type == "bco_pair": + self.running = RunningMoments(self.accelerator) + @staticmethod def concatenated_inputs( batch: Dict[str, Union[List, torch.LongTensor]], is_encoder_decoder: bool = False, + is_vision_model: bool = False, label_pad_token_id: int = -100, padding_value: int = 0, device: Optional[torch.device] = None, padded_max_length: int = 0, ) -> Dict[str, torch.LongTensor]: """ - Copied from DPOTrainer.concatenated_inputs: https://github.com/huggingface/trl/blob/v0.7.6/trl/trainer/dpo_trainer.py#L701 + Copied from DPOTrainer.concatenated_inputs: https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/dpo_trainer.py#L979 - pad to self.max_length in Gaudi2 """ concatenated_batch = {} @@ -386,18 +585,24 @@ def concatenated_inputs( batch["prompt_attention_mask"].repeat(2, 1).to(device=device) ) + if is_vision_model: + concatenated_batch["pixel_values"] = batch["prompt_pixel_values"].repeat(2, 1, 1, 1, 1).to(device=device) + concatenated_batch["pixel_attention_mask"] = ( + batch["prompt_pixel_attention_mask"].repeat(2, 1, 1, 1).to(device=device) + ) return concatenated_batch def concatenated_forward( self, model: nn.Module, batch: Dict[str, Union[List, torch.LongTensor]] - ) -> Tuple[torch.FloatTensor, torch.FloatTensor, torch.FloatTensor, torch.FloatTensor]: + ) -> Tuple[torch.FloatTensor, torch.FloatTensor, torch.FloatTensor, torch.FloatTensor, torch.FloatTensor]: """ - Copied from DPOTrainer.concatenated_forward: https://github.com/huggingface/trl/blob/v0.7.6/trl/trainer/dpo_trainer.py#L866 + Copied from DPOTrainer.concatenated_forward: https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/dpo_trainer.py#L1240 - pad to self.max_length in Gaudi2 """ concatenated_batch = self.concatenated_inputs( batch, is_encoder_decoder=self.is_encoder_decoder, + is_vision_model=self.is_vision_model, label_pad_token_id=self.label_pad_token_id, padding_value=self.padding_value, device=self.accelerator.device, @@ -405,32 +610,62 @@ def concatenated_forward( ) len_chosen = batch["chosen_labels"].shape[0] - model_kwargs = ( - { - "labels": concatenated_batch["concatenated_labels"], - "decoder_input_ids": concatenated_batch.pop("concatenated_decoder_input_ids", None), - } - if self.is_encoder_decoder - else {} - ) - all_logits = model( + model_kwargs = {} + + if self.is_encoder_decoder: + model_kwargs["labels"] = concatenated_batch["concatenated_labels"] + model_kwargs["decoder_input_ids"] = concatenated_batch.pop("concatenated_decoder_input_ids", None) + + if self.is_vision_model: + model_kwargs["pixel_values"] = concatenated_batch["pixel_values"] + model_kwargs["pixel_attention_mask"] = concatenated_batch["pixel_attention_mask"] + + if self.aux_loss_enabled: + model_kwargs["output_router_logits"] = True + + outputs = model( concatenated_batch["concatenated_input_ids"], attention_mask=concatenated_batch["concatenated_attention_mask"], + use_cache=False, **model_kwargs, - ).logits + ) + all_logits = outputs.logits - all_logps = self.get_batch_logps( + all_logps, size_completion = self.get_batch_logps( all_logits, concatenated_batch["concatenated_labels"], - average_log_prob=False, + # average_log_prob=self.loss_type == "ipo", is_encoder_decoder=self.is_encoder_decoder, label_pad_token_id=self.label_pad_token_id, ) + def cross_entropy_loss(logits, labels): + if not self.is_encoder_decoder: + # Shift so that tokens < n predict n + logits = logits[..., :-1, :].contiguous() + labels = labels[..., 1:].contiguous() + # Flatten the tokens + loss_fct = nn.CrossEntropyLoss() + logits = logits.view(-1, logits.shape[-1]) + labels = labels.view(-1) + # Enable model parallelism + labels = labels.to(logits.device) + loss = loss_fct(logits, labels) + return loss + + labels = concatenated_batch["concatenated_labels"].clone() + nll_loss = cross_entropy_loss(all_logits[:len_chosen], labels[:len_chosen]) + + if self.loss_type == "ipo": + all_logps = all_logps / size_completion + chosen_logps = all_logps[:len_chosen] rejected_logps = all_logps[len_chosen:] chosen_logits = all_logits[:len_chosen] rejected_logits = all_logits[len_chosen:] - return (chosen_logps, rejected_logps, chosen_logits, rejected_logits) + if self.aux_loss_enabled: + return (chosen_logps, rejected_logps, chosen_logits, rejected_logits, nll_loss, outputs.aux_loss) + + return (chosen_logps, rejected_logps, chosen_logits, rejected_logits, nll_loss) diff --git a/optimum/habana/trl/trainer/ppo_config.py b/optimum/habana/trl/trainer/ppo_config.py index 49e798be6b..bc5bcda60a 100644 --- a/optimum/habana/trl/trainer/ppo_config.py +++ b/optimum/habana/trl/trainer/ppo_config.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import warnings from dataclasses import dataclass import numpy as np @@ -34,12 +35,20 @@ class GaudiPPOConfig(PPOConfig): """max input length including padding. Only applicable if pad_for_acceleration is True""" def __post_init__(self): + """ + Copied from PPOConfig.__post_init__: https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/ppo_config.py#L152 + The only differences are: + - add adapt_transformers_to_gaudi for habana + """ + if self.forward_batch_size is not None: + warnings.warn( + "Note that using `forward_batch_size` is deprecated, use `mini_batch_size` instead. By setting it you overwrite `mini_batch_size` which affects both the batch size during forward passes and also the mini batch size for PPO optimization." + ) + self.mini_batch_size = self.forward_batch_size self.backward_batch_size = self.mini_batch_size * self.gradient_accumulation_steps exact_div( self.batch_size, self.backward_batch_size, - "`batch_size`", - "`mini_batch_size * gradient_accumulation_steps`", "`batch_size` must be a multiple of `mini_batch_size * gradient_accumulation_steps`", ) self.total_ppo_epochs = int(np.ceil(self.steps / self.batch_size)) diff --git a/optimum/habana/trl/trainer/ppo_trainer.py b/optimum/habana/trl/trainer/ppo_trainer.py index 9f72b02e44..c2303a5f8a 100644 --- a/optimum/habana/trl/trainer/ppo_trainer.py +++ b/optimum/habana/trl/trainer/ppo_trainer.py @@ -44,6 +44,7 @@ SUPPORTED_ARCHITECTURES, PreTrainedModelWrapper, create_reference_model, + unwrap_model_for_generation, ) from trl.trainer import ( AdaptiveKLController, @@ -63,18 +64,19 @@ class GaudiPPOTrainer(PPOTrainer): def __init__( self, - config: GaudiPPOConfig = None, - model: PreTrainedModelWrapper = None, + config: Optional[GaudiPPOConfig] = None, + model: Optional[PreTrainedModelWrapper] = None, ref_model: Optional[PreTrainedModelWrapper] = None, - tokenizer: PreTrainedTokenizerBase = None, + tokenizer: Optional[PreTrainedTokenizerBase] = None, dataset: Optional[Union[torch.utils.data.Dataset, Dataset]] = None, optimizer: Optional[torch.optim.Optimizer] = None, data_collator: Optional[typing.Callable] = None, num_shared_layers: Optional[int] = None, lr_scheduler: Optional[torch.optim.lr_scheduler._LRScheduler] = None, + training_data_collator: Optional[typing.Callable] = None, ): """ - Copied from PPOTrainer.__init__: https://github.com/huggingface/trl/blob/v0.7.6/trl/trainer/ppo_trainer.py#L145 + Copied from PPOTrainer.__init__: https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/ppo_trainer.py#L148 The only differences are: - add new args for Gaudi in config - use GaudiAccelerator instead of Accelerator @@ -180,7 +182,10 @@ def __init__( self.dataloader = None # Step 3: Initialize optimizer and data collator - self.data_collator = DataCollatorForLanguageModeling(self.tokenizer, mlm=False) + if training_data_collator is None: + self.data_collator = DataCollatorForLanguageModeling(self.tokenizer, mlm=False) + else: + self.data_collator = training_data_collator if optimizer is None: self.optimizer = Adam( filter(lambda p: p.requires_grad, self.model.parameters()), @@ -220,6 +225,18 @@ def __init__( self.accelerator.state, "deepspeed_plugin" ) + if config.gradient_checkpointing: + self.model.gradient_checkpointing_enable() + + if hasattr(self.model, "enable_input_require_grads"): + self.model.enable_input_require_grads() + else: + # For backward compatibility with older versions of transformers + def make_inputs_require_grad(module, input, output): + output.requires_grad_(True) + + self.model.pretrained_model.get_input_embeddings().register_forward_hook(make_inputs_require_grad) + ( self.model, self.optimizer, @@ -286,7 +303,7 @@ def generate( **generation_kwargs, ): """ - Copied from PPOTrainer.generate: https://github.com/huggingface/trl/blob/v0.7.6/trl/trainer/ppo_trainer.py#L433 + Copied from PPOTrainer.generate: https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/ppo_trainer.py#L455 The only differences are: - add hpu graph for acceleration """ @@ -304,17 +321,16 @@ def generate( **generation_kwargs, ) if generate_ref_response: - with self.optional_peft_ctx(): - if self.config.use_habana: - self.wrap_generation_for_hpu_graph_mode(ref_model) - ref_response = self._generate_batched( - ref_model, - query_tensor, - length_sampler=length_sampler, - batch_size=batch_size, - return_prompt=return_prompt, - **generation_kwargs, - ) + if self.config.use_habana: + self.wrap_generation_for_hpu_graph_mode(ref_model) + ref_response = self._generate_batched( + ref_model, + query_tensor, + length_sampler=length_sampler, + batch_size=batch_size, + return_prompt=return_prompt, + **generation_kwargs, + ) else: if len(query_tensor.shape) == 2: @@ -326,14 +342,17 @@ def generate( generation_kwargs["max_new_tokens"] = length_sampler() if self.config.use_habana: self.wrap_generation_for_hpu_graph_mode(self.model) - response = self.accelerator.unwrap_model(self.model).generate( - input_ids=query_tensor.unsqueeze(dim=0), **generation_kwargs - ) + with unwrap_model_for_generation(self.model, self.accelerator) as unwrapped_model: + response = unwrapped_model.generate(input_ids=query_tensor.unsqueeze(dim=0), **generation_kwargs) if generate_ref_response: - with self.optional_peft_ctx(): - if self.config.use_habana: - self.wrap_generation_for_hpu_graph_mode(ref_model) - ref_response = ref_model.generate(input_ids=query_tensor.unsqueeze(dim=0), **generation_kwargs) + if self.config.use_habana: + self.wrap_generation_for_hpu_graph_mode(ref_model) + with unwrap_model_for_generation( + ref_model, self.accelerator, is_peft_model=self.is_peft_model + ) as unwrapped_model: + ref_response = unwrapped_model.generate( + input_ids=query_tensor.unsqueeze(dim=0), **generation_kwargs + ) if not return_prompt and not self.is_encoder_decoder: response = response[:, query_tensor.shape[0] :] @@ -348,15 +367,15 @@ def _generate_batched( self, model: PreTrainedModelWrapper, query_tensors: List[torch.Tensor], - length_sampler: Callable = None, + length_sampler: Optional[Callable] = None, batch_size: int = 4, return_prompt: bool = True, - pad_to_multiple_of: int = None, + pad_to_multiple_of: Optional[int] = None, remove_padding: bool = True, **generation_kwargs, ): """ - Copied from PPOTrainer._generate_batched: https://github.com/huggingface/trl/blob/v0.7.6/trl/trainer/ppo_trainer.py#L509 + Copied from PPOTrainer._generate_batched: https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/ppo_trainer.py#L535 The only differences are: - pad to pad_max_input_len to get static shape for generation acceleration - use lazy mode and hpu_graphs for generation in hpu @@ -403,7 +422,8 @@ def _generate_batched( generation_kwargs["lazy_mode"] = True generation_kwargs["hpu_graphs"] = True - generations = self.accelerator.unwrap_model(model).generate(**padded_inputs, **generation_kwargs) + with unwrap_model_for_generation(model, self.accelerator) as unwrapped_model: + generations = unwrapped_model.generate(**padded_inputs, **generation_kwargs) for generation, mask in zip(generations, padded_inputs["attention_mask"]): if not self.is_encoder_decoder: @@ -433,7 +453,7 @@ def step( response_masks: Optional[List[torch.LongTensor]] = None, ): """ - Copied from PPOTrainer.step: https://github.com/huggingface/trl/blob/v0.7.6/trl/trainer/ppo_trainer.py#L620 + Copied from PPOTrainer.step: https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/ppo_trainer.py#L647 The only differences are: - use hpu_graphs for sampling and training - remove duplicated padding if padding is done in prepare_model_inputs @@ -678,7 +698,7 @@ def step( def prepare_model_inputs(self, queries: torch.Tensor, responses: torch.Tensor): """ - Copied from PPOTrainer.prepare_model_inputs: https://github.com/huggingface/trl/blob/v0.7.6/trl/trainer/ppo_trainer.py#L921 + Copied from PPOTrainer.prepare_model_inputs: https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/ppo_trainer.py#L949 The only differences are: - add padding to model inputs for static shape support in forward """ @@ -745,7 +765,7 @@ def batched_forward_pass( response_masks: Optional[torch.Tensor] = None, ): """ - Copied from PPOTrainer.batched_forward_pass: https://github.com/huggingface/trl/blob/v0.7.6/trl/trainer/ppo_trainer.py#L943 + Copied from PPOTrainer.batched_forward_pass: https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/ppo_trainer.py#L971 The only differences are: - input_kwargs/output need to clone() to avoid overidden in hpu """ @@ -825,7 +845,7 @@ def train_minibatch( returns: torch.FloatTensor, ): """ - Copied from PPOTrainer.batched_forward_pass: https://github.com/huggingface/trl/blob/v0.7.6/trl/trainer/ppo_trainer.py#L1034 + Copied from PPOTrainer.batched_forward_pass: https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/ppo_trainer.py#L1058 The only differences are: - add htcore.mark_step """ diff --git a/optimum/habana/trl/trainer/sft_config.py b/optimum/habana/trl/trainer/sft_config.py new file mode 100644 index 0000000000..ea2d9d08cc --- /dev/null +++ b/optimum/habana/trl/trainer/sft_config.py @@ -0,0 +1,38 @@ +# Copyright 2022 The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from dataclasses import dataclass +from typing import Dict, Optional + +from ... import GaudiTrainingArguments + + +@dataclass +class GaudiSFTConfig(GaudiTrainingArguments): + r""" + Initialize GaudiSFTConfig. + Adapted from https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/sft_config.py#L21 + - inherit from GaudiTrainingArguments + """ + + dataset_text_field: Optional[str] = None + packing: Optional[bool] = True + max_seq_length: Optional[int] = 1024 + dataset_num_proc: Optional[int] = None + dataset_batch_size: int = 1000 + neftune_noise_alpha: Optional[float] = None + model_init_kwargs: Optional[Dict] = None + dataset_kwargs: Optional[Dict] = None + eval_packing: Optional[bool] = None + num_of_sequences: Optional[int] = 1024 + chars_per_token: Optional[float] = 3.6 diff --git a/optimum/habana/trl/trainer/sft_trainer.py b/optimum/habana/trl/trainer/sft_trainer.py index 3d35c64202..4ef3f32c2b 100644 --- a/optimum/habana/trl/trainer/sft_trainer.py +++ b/optimum/habana/trl/trainer/sft_trainer.py @@ -18,6 +18,7 @@ import torch import torch.nn as nn +from accelerate import PartialState from datasets import Dataset from transformers import ( AutoModelForCausalLM, @@ -34,20 +35,22 @@ from trl.import_utils import is_peft_available from trl.trainer.utils import ( DataCollatorForCompletionOnlyLM, + RichProgressCallback, ) if is_peft_available(): from peft import PeftConfig, PeftModel, get_peft_model, prepare_model_for_kbit_training -from ... import GaudiConfig, GaudiTrainer, GaudiTrainingArguments +from ... import GaudiConfig, GaudiTrainer +from .sft_config import GaudiSFTConfig class GaudiSFTTrainer(SFTTrainer, GaudiTrainer): def __init__( self, model: Union[PreTrainedModel, nn.Module, str] = None, - args: GaudiTrainingArguments = None, + args: Optional[GaudiSFTConfig] = None, gaudi_config: GaudiConfig = None, data_collator: Optional[DataCollator] = None, train_dataset: Optional[Dataset] = None, @@ -74,16 +77,46 @@ def __init__( eval_packing: Optional[bool] = None, ): """ - Copied from SFTTrainer.__init__: https://github.com/huggingface/trl/blob/v0.7.6/trl/trainer/sft_trainer.py#L120 + Copied from SFTTrainer.__init__: https://github.com/huggingface/trl/blob/v0.9.6/trl/trainer/sft_trainer.py#L116 The only differences are: - add new args gaudi_config - use GaudiTrainer instead of Trainer - cast peft model to bf16. """ - if model_init_kwargs is None: + if args is None: + output_dir = "tmp_trainer" + warnings.warn(f"No `SFTConfig` passed, using `output_dir={output_dir}`.") + args = GaudiSFTConfig(output_dir=output_dir) + elif args is not None and args.__class__.__name__ == "TrainingArguments": + args_as_dict = args.to_dict() + # Manually copy token values as TrainingArguments.to_dict() redacts them + args_as_dict.update({k: getattr(args, k) for k in args_as_dict.keys() if k.endswith("_token")}) + args = GaudiSFTConfig(**args_as_dict) + + if model_init_kwargs is not None: + warnings.warn( + "You passed `model_init_kwargs` to the SFTTrainer, the value you passed will override the one in the `SFTConfig`." + ) + args.model_init_kwargs = model_init_kwargs + if getattr(args, "model_init_kwargs", None) is None: model_init_kwargs = {} elif not isinstance(model, str): - raise ValueError("You passed model_kwargs to the SFTTrainer. But your model is already instantiated.") + raise ValueError("You passed model_init_kwargs to the SFTConfig, but your model is already instantiated.") + else: + model_init_kwargs = args.model_init_kwargs + + torch_dtype = model_init_kwargs["torch_dtype"] + if torch_dtype is not None: + # Convert to `torch.dtype` if an str is passed + if isinstance(torch_dtype, str) and torch_dtype != "auto": + torch_dtype = getattr(torch, torch_dtype) + + if torch_dtype != "auto" and not isinstance(torch_dtype, torch.dtype): + raise ValueError( + f"Invalid `torch_dtype` passed to the SFTConfig. Expected a string with either `torch.dtype` or 'auto', but got {torch_dtype}." + ) + + model_init_kwargs["torch_dtype"] = torch_dtype if infinite is not None: warnings.warn( @@ -97,7 +130,18 @@ def __init__( ) model = AutoModelForCausalLM.from_pretrained(model, **model_init_kwargs) - if packing and data_collator is not None and isinstance(data_collator, DataCollatorForCompletionOnlyLM): + if packing: + warnings.warn( + "You passed a `packing` argument to the SFTTrainer, the value you passed will override the one in the `SFTConfig`." + ) + args.packing = packing + if eval_packing is not None: + warnings.warn( + "You passed a `eval_packing` argument to the SFTTrainer, the value you passed will override the one in the `SFTConfig`." + ) + args.eval_packing = eval_packing + + if args.packing and data_collator is not None and isinstance(data_collator, DataCollatorForCompletionOnlyLM): raise ValueError( "You passed a `DataCollatorForCompletionOnlyLM` to the SFTTrainer. This is not compatible with the `packing` argument." ) @@ -116,15 +160,26 @@ def __init__( inspect.signature(prepare_model_for_kbit_training).parameters ) gradient_checkpointing_kwargs = getattr(args, "gradient_checkpointing_kwargs", None) or {} - if getattr(model, "is_loaded_in_8bit", False) or getattr(model, "is_loaded_in_4bit", False): - preprare_model_kwargs = { + is_sharded_qlora = False + # Below is to support QLoRA + FSDP / DS-Zero3 - one should never call + # peft_module_casting_to_bf16 or prepare_model_for_kbit_training when doing + # QLoRA + FSDP / DS-Zero3 + if getattr(model, "is_loaded_in_4bit", False): + for _, param in model.named_parameters(): + if param.__class__.__name__ == "Params4bit": + is_sharded_qlora = param.data.device.type == "cpu" + break + if getattr(model, "is_loaded_in_8bit", False) or ( + getattr(model, "is_loaded_in_4bit", False) and not is_sharded_qlora + ): + prepare_model_kwargs = { "use_gradient_checkpointing": getattr(args, "gradient_checkpointing", False) } if _support_gc_kwargs: - preprare_model_kwargs["gradient_checkpointing_kwargs"] = gradient_checkpointing_kwargs + prepare_model_kwargs["gradient_checkpointing_kwargs"] = gradient_checkpointing_kwargs - model = prepare_model_for_kbit_training(model, **preprare_model_kwargs) + model = prepare_model_for_kbit_training(model, **prepare_model_kwargs) if args is not None: args = dataclasses.replace(args, gradient_checkpointing=False) @@ -142,8 +197,20 @@ def make_inputs_require_grad(module, input, output): model.get_input_embeddings().register_forward_hook(make_inputs_require_grad) - model = get_peft_model(model, peft_config) - if args.bf16: + if ( + "autocast_adapter_dtype" in list(inspect.signature(get_peft_model).parameters) + and getattr(model, "is_loaded_in_4bit", False) + and is_sharded_qlora + ): + model = get_peft_model(model, peft_config, autocast_adapter_dtype=False) + else: + model = get_peft_model(model, peft_config) + if ( + args is not None + and args.bf16 + and getattr(model, "is_loaded_in_4bit", False) + and not is_sharded_qlora + ): model = model.to(torch.bfloat16) if tokenizer is None: @@ -151,7 +218,13 @@ def make_inputs_require_grad(module, input, output): if getattr(tokenizer, "pad_token", None) is None: tokenizer.pad_token = tokenizer.eos_token - if max_seq_length is None: + if max_seq_length is not None: + warnings.warn( + "You passed a `max_seq_length` argument to the SFTTrainer, the value you passed will override the one in the `SFTConfig`." + ) + args.max_seq_length = max_seq_length + + if args.max_seq_length is None: # to overcome some issues with broken tokenizers max_seq_length = min(tokenizer.model_max_length, 1024) @@ -159,66 +232,119 @@ def make_inputs_require_grad(module, input, output): f"You didn't pass a `max_seq_length` argument to the SFTTrainer, this will default to {max_seq_length}" ) - self.dataset_num_proc = dataset_num_proc - self.dataset_batch_size = dataset_batch_size + if dataset_num_proc is not None: + warnings.warn( + "You passed a `dataset_num_proc` argument to the SFTTrainer, the value you passed will override the one in the `SFTConfig`." + ) + args.dataset_num_proc = dataset_num_proc + self.dataset_num_proc = args.dataset_num_proc - self._trainer_supports_neftune = hasattr(args, "neftune_noise_alpha") + if dataset_batch_size != args.dataset_batch_size: + warnings.warn( + "You passed a `dataset_batch_size` argument to the SFTTrainer, the value you passed will override the one in the `SFTConfig`." + ) + args.dataset_batch_size = dataset_batch_size + self.dataset_batch_size = args.dataset_batch_size + self._trainer_supports_neftune = hasattr(args, "neftune_noise_alpha") if neftune_noise_alpha is not None and self._trainer_supports_neftune: args.neftune_noise_alpha = neftune_noise_alpha warnings.warn( - "You passed a `neftune_noise_alpha` argument to the SFTTrainer, the value you passed will override the one in the `TrainingArguments`." + "You passed a `neftune_noise_alpha` argument to the SFTTrainer, the value you passed will override the one in the `SFTConfig`." ) # self.neftune_noise_alpha is done at Trainer level elif not self._trainer_supports_neftune: self.neftune_noise_alpha = neftune_noise_alpha - if formatting_func is None and dataset_text_field is None: + if dataset_text_field is not None: + warnings.warn( + "You passed a `dataset_text_field` argument to the SFTTrainer, the value you passed will override the one in the `SFTConfig`." + ) + args.dataset_text_field = dataset_text_field + + if formatting_func is None and args.dataset_text_field is None: # check if dataset has ChatML format or instruction format and is supported # if not stays #None formatting_func = get_formatting_func_from_dataset(train_dataset, tokenizer) + # if a template is detected, we don't need to add special tokens again + if formatting_func is not None: + if dataset_kwargs is None: + dataset_kwargs = {"add_special_tokens": False} + else: + dataset_kwargs["add_special_tokens"] = False - if not packing: - if dataset_text_field is None and formatting_func is None: + if not args.packing: + # If we aren't skipping data preparation, then a dataset_text_field + # or formatting_func must be provided. + if ( + args.dataset_text_field is None + and formatting_func is None + and dataset_kwargs is not None + and "skip_prepare_dataset" in dataset_kwargs + and dataset_kwargs["skip_prepare_dataset"] + ): raise ValueError( - "You passed `packing=False` to the SFTTrainer, but you didn't pass a `dataset_text_field` or `formatting_func` argument." + "You passed `packing=False` to the SFTTrainer/SFTConfig, but you didn't pass a `dataset_text_field` or `formatting_func` argument." ) if data_collator is None: data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False) - if dataset_kwargs is None: - dataset_kwargs = {} - if train_dataset is not None: - train_dataset = self._prepare_dataset( - train_dataset, - tokenizer, - packing, - dataset_text_field, - max_seq_length, - formatting_func, - num_of_sequences, - chars_per_token, - **dataset_kwargs, - ) - - if eval_dataset is not None: - _multiple = isinstance(eval_dataset, dict) - _eval_datasets = eval_dataset if _multiple else {"singleton": eval_dataset} - for _eval_dataset_name, _eval_dataset in _eval_datasets.items(): - _eval_datasets[_eval_dataset_name] = self._prepare_dataset( - _eval_dataset, + if num_of_sequences != args.num_of_sequences: + warnings.warn( + "You passed a `num_of_sequences` argument to the SFTTrainer, the value you passed will override the one in the `SFTConfig`." + ) + args.num_of_sequences = num_of_sequences + + if chars_per_token != args.chars_per_token: + warnings.warn( + "You passed a `chars_per_token` argument to the SFTTrainer, the value you passed will override the one in the `SFTConfig`." + ) + args.chars_per_token = chars_per_token + + # Pre-process the datasets only once per node. The remaining processes will use the cache. + with PartialState().local_main_process_first(): + if dataset_kwargs is not None: + warnings.warn( + "You passed a `dataset_kwargs` argument to the SFTTrainer, the value you passed will override the one in the `SFTConfig`." + ) + args.dataset_kwargs = dataset_kwargs + if args.dataset_kwargs is None: + args.dataset_kwargs = {} + if train_dataset is not None: + train_dataset = self._prepare_dataset( + train_dataset, tokenizer, - packing, - dataset_text_field, - max_seq_length, + args.packing, + args.dataset_text_field, + args.max_seq_length, formatting_func, - num_of_sequences, - chars_per_token, - **dataset_kwargs, + args.num_of_sequences, + args.chars_per_token, + remove_unused_columns=args.remove_unused_columns if args is not None else True, + **args.dataset_kwargs, ) - if not _multiple: - eval_dataset = _eval_datasets["singleton"] + if eval_dataset is not None: + _multiple = isinstance(eval_dataset, dict) + _eval_datasets = eval_dataset if _multiple else {"singleton": eval_dataset} + + eval_packing = args.packing if args.eval_packing is None else args.eval_packing + + for _eval_dataset_name, _eval_dataset in _eval_datasets.items(): + _eval_datasets[_eval_dataset_name] = self._prepare_dataset( + _eval_dataset, + tokenizer, + eval_packing, + args.dataset_text_field, + args.max_seq_length, + formatting_func, + args.num_of_sequences, + args.chars_per_token, + remove_unused_columns=args.remove_unused_columns if args is not None else True, + **args.dataset_kwargs, + ) + if not _multiple: + eval_dataset = _eval_datasets["singleton"] if tokenizer.padding_side is not None and tokenizer.padding_side != "right": warnings.warn( @@ -242,10 +368,20 @@ def make_inputs_require_grad(module, input, output): preprocess_logits_for_metrics=preprocess_logits_for_metrics, ) - if self.args.max_steps > 0 and packing: + # Add tags for models that have been loaded with the correct transformers version + if hasattr(self.model, "add_model_tags"): + self.model.add_model_tags(self._tag_names) + + if self.args.max_steps > 0 and args.packing: warnings.warn( - "You passed `packing=True` to the SFTTrainer, and you are training your model with `max_steps` strategy. The dataset will be iterated until the `max_steps` are reached." + "You passed `packing=True` to the SFTTrainer/SFTConfig, and you are training your model with `max_steps` strategy. The dataset will be iterated until the `max_steps` are reached." ) self.train_dataset.infinite = True - elif self.args.max_steps == -1 and packing: + elif self.args.max_steps == -1 and args.packing: self.train_dataset.infinite = False + + if any(isinstance(callback, RichProgressCallback) for callback in self.callback_handler.callbacks): + for callback in self.callback_handler.callbacks: + # Remove the PrinterCallback to avoid duplicated prints in case we passed a `RichProgressCallback` + if callback.__class__.__name__ == "PrinterCallback": + self.callback_handler.pop_callback(callback) From a324e7ccd8e58bd0eb6d875efb1f97eb44523c82 Mon Sep 17 00:00:00 2001 From: Ke Ding Date: Fri, 20 Sep 2024 01:30:41 -0700 Subject: [PATCH 12/97] Fix uint4 url typo. (#1340) Signed-off-by: Ding, Ke --- examples/text-generation/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/text-generation/README.md b/examples/text-generation/README.md index b720936ff4..22390d6497 100755 --- a/examples/text-generation/README.md +++ b/examples/text-generation/README.md @@ -489,7 +489,7 @@ python run_generation.py \ You can load pre-quantized 4bit models with the argument `--load_quantized_model`. Currently, uint4 checkpoints and single device are supported. More information on enabling 4 bit inference in SynapseAI is available here: -https://docs.habana.ai/en/latest/PyTorch/Inference_on_PyTorch/Inference_Using_INT4.html. +https://docs.habana.ai/en/latest/PyTorch/Inference_on_PyTorch/Inference_Using_UINT4.html. Below is an example to load a model with 4bit checkpoints from Hugging Face. Please note that model name is denoted as ``. From 253165ab1bee32c0ba1466403b512f57cfe723e7 Mon Sep 17 00:00:00 2001 From: Shiv Kaul Date: Fri, 20 Sep 2024 01:37:50 -0700 Subject: [PATCH 13/97] Use eager attention for wav2vec2 (#1333) --- optimum/habana/transformers/models/modeling_all_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimum/habana/transformers/models/modeling_all_models.py b/optimum/habana/transformers/models/modeling_all_models.py index 808e0f012a..c9eb95524e 100644 --- a/optimum/habana/transformers/models/modeling_all_models.py +++ b/optimum/habana/transformers/models/modeling_all_models.py @@ -115,7 +115,7 @@ def gaudi_conv1d_forward(self, x): @classmethod def gaudi_check_and_enable_sdpa(cls, config, hard_check_only: bool = False) -> PretrainedConfig: # This model doesn't support SDPA in Gaudi yet, fallback to original code. - MODELS_ATTN_IMPLEMENTATION_EAGER = ["bart", "gpt_bigcode", "mistral", "mixtral"] + MODELS_ATTN_IMPLEMENTATION_EAGER = ["bart", "gpt_bigcode", "mistral", "mixtral", "wav2vec2"] if config.model_type in MODELS_ATTN_IMPLEMENTATION_EAGER: config._attn_implementation = "eager" From fc2e671b2206427691b9f7b01e9a47a75024d21a Mon Sep 17 00:00:00 2001 From: Jimin Ha Date: Fri, 20 Sep 2024 01:40:07 -0700 Subject: [PATCH 14/97] Add _reorder_cache back to Llama for HPU (#1233) --- .../transformers/models/llama/modeling_llama.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/optimum/habana/transformers/models/llama/modeling_llama.py b/optimum/habana/transformers/models/llama/modeling_llama.py index 1abbfab12d..346d12111d 100755 --- a/optimum/habana/transformers/models/llama/modeling_llama.py +++ b/optimum/habana/transformers/models/llama/modeling_llama.py @@ -1401,6 +1401,17 @@ def prepare_inputs_for_generation( ) return model_inputs + # Transformer4.43 use new Cache mechanism while Gaudi is not. + # Adding _reorder_cache back to support HPU. + @staticmethod + def _reorder_cache(past_key_values, beam_idx): + reordered_past = () + for layer_past in past_key_values: + reordered_past += ( + tuple(past_state.index_select(0, beam_idx.to(past_state.device)) for past_state in layer_past), + ) + return reordered_past + def apply_customized_rope(q, k, cos, sin, position_ids): if q.device.type == "hpu" and has_fused_rope: From b75216c74d4521470532498c24f0facc31029124 Mon Sep 17 00:00:00 2001 From: Akihiro Takahashi Date: Mon, 23 Sep 2024 09:57:03 -0700 Subject: [PATCH 15/97] Improve MPT fp8 (#1256) Signed-off-by: dmsuehir Co-authored-by: Thanaji Rao Thakkalapelli Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> Co-authored-by: Libin Tang Co-authored-by: Yeonsil Yoon Co-authored-by: Dina Suehiro Jones Co-authored-by: Sayantan Sarkar Co-authored-by: Iman Gohari Co-authored-by: Daniel Huang Co-authored-by: Pramod Kumar <144990617+pramodkumar-habanalabs@users.noreply.github.com> --- optimum/habana/transformers/modeling_utils.py | 8 +- .../habana/transformers/models/__init__.py | 4 +- .../transformers/models/mpt/__init__.py | 4 +- .../transformers/models/mpt/modeling_mpt.py | 339 ++++++++++-------- 4 files changed, 189 insertions(+), 166 deletions(-) diff --git a/optimum/habana/transformers/modeling_utils.py b/optimum/habana/transformers/modeling_utils.py index 2b7bb32bce..d7f98f8376 100644 --- a/optimum/habana/transformers/modeling_utils.py +++ b/optimum/habana/transformers/modeling_utils.py @@ -76,6 +76,8 @@ GaudiMixtralDecoderLayer, GaudiMixtralForCausalLM, GaudiMixtralModel, + GaudiMptAttention, + GaudiMptBlock, GaudiMptForCausalLM, GaudiMptModel, GaudiOPTForCausalLM, @@ -152,8 +154,6 @@ gaudi_mistral_rmsnorm_forward, gaudi_mixtral_block_sparse_moe_forward, gaudi_mixtral_rmsnorm_forward, - gaudi_mpt_attention_forward, - gaudi_mpt_block_forward, gaudi_opt_attention_forward, gaudi_opt_decoder_forward, gaudi_opt_decoder_layer_forward, @@ -420,8 +420,8 @@ def adapt_transformers_to_gaudi(): # Optimization for mpt on Gaudi transformers.models.mpt.modeling_mpt.MptForCausalLM = GaudiMptForCausalLM transformers.models.mpt.modeling_mpt.MptModel = GaudiMptModel - transformers.models.mpt.modeling_mpt.MptAttention.forward = gaudi_mpt_attention_forward - transformers.models.mpt.modeling_mpt.MptBlock.forward = gaudi_mpt_block_forward + transformers.models.mpt.modeling_mpt.MptAttention = GaudiMptAttention + transformers.models.mpt.modeling_mpt.MptBlock = GaudiMptBlock # Optimization for mistral on Gaudi transformers.models.mistral.modeling_mistral.MistralForCausalLM = GaudiMistralForCausalLM diff --git a/optimum/habana/transformers/models/__init__.py b/optimum/habana/transformers/models/__init__.py index 99ef65c4e4..5a4861fbdf 100644 --- a/optimum/habana/transformers/models/__init__.py +++ b/optimum/habana/transformers/models/__init__.py @@ -138,10 +138,10 @@ gaudi_invert_attention_mask, ) from .mpt import ( + GaudiMptAttention, + GaudiMptBlock, GaudiMptForCausalLM, GaudiMptModel, - gaudi_mpt_attention_forward, - gaudi_mpt_block_forward, ) from .opt import ( GaudiOPTForCausalLM, diff --git a/optimum/habana/transformers/models/mpt/__init__.py b/optimum/habana/transformers/models/mpt/__init__.py index 1ab41c1a80..351152c026 100644 --- a/optimum/habana/transformers/models/mpt/__init__.py +++ b/optimum/habana/transformers/models/mpt/__init__.py @@ -1,6 +1,6 @@ from .modeling_mpt import ( + GaudiMptAttention, + GaudiMptBlock, GaudiMptForCausalLM, GaudiMptModel, - gaudi_mpt_attention_forward, - gaudi_mpt_block_forward, ) diff --git a/optimum/habana/transformers/models/mpt/modeling_mpt.py b/optimum/habana/transformers/models/mpt/modeling_mpt.py index 7cefc4e37f..369bae9234 100755 --- a/optimum/habana/transformers/models/mpt/modeling_mpt.py +++ b/optimum/habana/transformers/models/mpt/modeling_mpt.py @@ -21,14 +21,18 @@ from torch import nn from torch.nn import CrossEntropyLoss from transformers.modeling_outputs import BaseModelOutputWithPastAndCrossAttentions, CausalLMOutputWithCrossAttentions -from transformers.models.mpt.modeling_mpt import MptForCausalLM, MptModel +from transformers.models.mpt.modeling_mpt import ( + MptAttention, + MptBlock, + MptConfig, + MptForCausalLM, + MptModel, +) from transformers.utils import logging from ...modeling_attn_mask_utils import _gaudi_prepare_4d_causal_attention_mask -logger = logging.get_logger(__name__) - try: from habana_frameworks.torch.hpex.kernels import FusedSDPA except ImportError: @@ -36,159 +40,178 @@ FusedSDPA = None -def gaudi_mpt_attention_forward( - self, - hidden_states: torch.Tensor, - position_bias: torch.Tensor, - past_key_value: Optional[Tuple[torch.Tensor]] = None, - attention_mask: Optional[torch.Tensor] = None, - token_idx: Optional[torch.Tensor] = None, - use_flash_attention: Optional[bool] = False, - flash_attention_recompute: Optional[bool] = False, -): - """ - Copied from MptAttention.forward: https://github.com/huggingface/transformers/blob/v4.32.0/src/transformers/models/mpt/modeling_mpt.py - The only differences are: - - add new args token_idx - - optimize KV cache - - add new args use_flash_attention - - add new arg flash_attention_recompute - """ - - batch_size, seq_length = hidden_states.shape[:2] - - mixed_qkv = self.Wqkv(hidden_states) - if self.clip_qkv: - mixed_qkv = mixed_qkv.clamp(min=-self.clip_qkv, max=self.clip_qkv) - - bs, seq_len, three_times_hidden_size = mixed_qkv.shape - mixed_qkv = mixed_qkv.view(bs, seq_len, self.n_heads * 3, self.head_dim) - mixed_qkv = mixed_qkv.transpose(1, 2) - query_states, key_states, value_states = ( - mixed_qkv[:, : self.n_heads, ...], - mixed_qkv[:, self.n_heads : 2 * self.n_heads, ...], - mixed_qkv[:, 2 * self.n_heads :, ...], - ) - - if past_key_value is not None: - if len(past_key_value) != 0: - if token_idx is not None: - past_key_value[0].index_copy_(2, token_idx - 1, key_states) - past_key_value[1].index_copy_(2, token_idx - 1, value_states) - key_states = past_key_value[0] - value_states = past_key_value[1] - else: - key_states = torch.cat([past_key_value[0], key_states], dim=2) - value_states = torch.cat([past_key_value[1], value_states], dim=2) - past_key_value = [key_states, value_states] - else: - past_key_value = [ - torch.empty(key_states.shape, dtype=key_states.dtype, device=key_states.device), - torch.empty(key_states.shape, dtype=key_states.dtype, device=key_states.device), - ] - past_key_value[0][:] = key_states[:] - past_key_value[1][:] = value_states[:] - - query_length = seq_length if past_key_value is None else seq_length + past_key_value[0].shape[2] - - if position_bias is not None: - if len(position_bias.shape) != 3: - raise ValueError(f"Expecting position_bias shape to be 3 dimensions, got {len(position_bias.shape)}") - key_length = key_states.shape[-2] - - position_bias_query_index = max(0, position_bias.size(1) - query_length) - position_bias_key_index = max(0, position_bias.size(2) - key_length) - - position_bias = position_bias[:, position_bias_query_index:, position_bias_key_index:] - - if use_flash_attention and FusedSDPA: - import habana_frameworks.torch.hpu as ht - - with ht.sdp_kernel(enable_recompute=flash_attention_recompute): - attn_output = FusedSDPA.apply( - query_states, - key_states, - value_states, - attention_mask * torch.finfo(query_states.dtype).min + position_bias.to(query_states.dtype), - 0.0, - False, - None, - ) - attn_weights = None - else: - attention_scores = torch.matmul(query_states, key_states.transpose(-1, -2)) * self.softmax_scale +logger = logging.get_logger(__name__) + + +class Softmax(nn.Module): + def __init__(self): + super().__init__() + + def forward(self, x, dim=None, invAttnHead=None): + return torch.nn.functional.softmax(x, dim) + + +class GaudiMptAttention(MptAttention): + def __init__(self, config: MptConfig): + super().__init__(config) + + self.softmax = Softmax() + + def forward( + self, + hidden_states: torch.Tensor, + position_bias: torch.Tensor, + past_key_value: Optional[Tuple[torch.Tensor]] = None, + attention_mask: Optional[torch.Tensor] = None, + token_idx: Optional[torch.Tensor] = None, + use_flash_attention: Optional[bool] = False, + flash_attention_recompute: Optional[bool] = False, + ): + """ + Copied from MptAttention.forward: https://github.com/huggingface/transformers/blob/v4.44.1/src/transformers/models/mpt/modeling_mpt.py + The only differences are: + - add new args token_idx + - optimize KV cache + - add new args use_flash_attention + - add new arg flash_attention_recompute + """ + + batch_size, seq_length = hidden_states.shape[:2] + + mixed_qkv = self.Wqkv(hidden_states) + if self.clip_qkv: + mixed_qkv = mixed_qkv.clamp(min=-self.clip_qkv, max=self.clip_qkv) + + query_states, key_states, value_states = mixed_qkv.chunk(3, dim=2) + query_states = query_states.reshape(batch_size, seq_length, self.n_heads, self.head_dim).transpose(1, 2) + key_states = key_states.reshape(batch_size, seq_length, self.n_heads, self.head_dim).transpose(1, 2) + value_states = value_states.reshape(batch_size, seq_length, self.n_heads, self.head_dim).transpose(1, 2) + + if past_key_value is not None: + if len(past_key_value) != 0: + if token_idx is not None: + past_key_value[0].index_copy_(2, token_idx - 1, key_states) + past_key_value[1].index_copy_(2, token_idx - 1, value_states) + key_states = past_key_value[0] + value_states = past_key_value[1] + else: + key_states = torch.cat([past_key_value[0], key_states], dim=2) + value_states = torch.cat([past_key_value[1], value_states], dim=2) + past_key_value = [key_states, value_states] + else: + past_key_value = [ + torch.empty(key_states.shape, dtype=key_states.dtype, device=key_states.device), + torch.empty(key_states.shape, dtype=key_states.dtype, device=key_states.device), + ] + past_key_value[0][:] = key_states[:] + past_key_value[1][:] = value_states[:] + + query_length = seq_length if past_key_value is None else seq_length + past_key_value[0].shape[2] if position_bias is not None: - attention_scores = attention_scores + position_bias - if attention_mask is not None: - attention_scores = attention_scores.masked_fill(attention_mask, torch.finfo(query_states.dtype).min) - - # (batch_size, n_heads, seq_length, key_length) - attn_weights = nn.functional.softmax(attention_scores.float(), dim=-1).to(value_states.dtype) - attn_weights = nn.functional.dropout(attn_weights, p=self.attn_dropout_p, training=self.training) - - attn_output = torch.matmul(attn_weights, value_states) - - attn_output = attn_output.permute(0, 2, 1, 3).contiguous().view(batch_size, seq_length, -1) - attn_output = self.out_proj(attn_output) - - return attn_output, attn_weights, past_key_value - - -def gaudi_mpt_block_forward( - self, - hidden_states: torch.Tensor, - position_bias: torch.Tensor, - attention_mask: torch.Tensor, - layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, - use_cache: bool = False, - output_attentions: bool = False, - token_idx: Optional[torch.Tensor] = None, - use_flash_attention: Optional[bool] = False, - flash_attention_recompute: Optional[bool] = False, -): - """ - Copied from MptBlock.forward: https://github.com/huggingface/transformers/blob/v4.32.0/src/transformers/models/mpt/modeling_mpt.py - The only differences are: - - add new args token_idx - - add new args use_flash_attention - - add new arg flash_attention_recompute - """ - # hidden_states: [batch_size, seq_length, hidden_size] - # Layer norm at the beginning of the transformer layer. - layernorm_output = self.norm_1(hidden_states) - - residual = hidden_states - - # Self attention. - attn_outputs, attn_weights, past_key_value = self.attn( - layernorm_output, - position_bias=position_bias, - attention_mask=attention_mask, - past_key_value=layer_past, - token_idx=token_idx, - use_flash_attention=use_flash_attention, - flash_attention_recompute=flash_attention_recompute, - ) - - hidden_states = self.resid_attn_dropout(attn_outputs) + residual - - layernorm_output = self.norm_2(hidden_states) - - # Get residual - residual = hidden_states - - # MLP. - output = self.ffn(layernorm_output, residual) - outputs = (output,) - - if use_cache: - outputs += (past_key_value,) - - if output_attentions: - outputs += (attn_weights,) - - return outputs # hidden_states, present, attentions + if len(position_bias.shape) != 3: + raise ValueError(f"Expecting position_bias shape to be 3 dimensions, got {len(position_bias.shape)}") + key_length = key_states.shape[-2] + + position_bias_query_index = max(0, position_bias.size(1) - query_length) + position_bias_key_index = max(0, position_bias.size(2) - key_length) + + position_bias = position_bias[:, position_bias_query_index:, position_bias_key_index:] + + if use_flash_attention and FusedSDPA: + import habana_frameworks.torch.hpu as ht + + with ht.sdp_kernel(enable_recompute=flash_attention_recompute): + attn_output = FusedSDPA.apply( + query_states, + key_states, + value_states, + attention_mask * torch.finfo(query_states.dtype).min + position_bias.to(query_states.dtype), + 0.0, + False, + None, + ) + + attn_weights = None + else: + attention_scores = torch.matmul(query_states, key_states.transpose(-1, -2)) * self.softmax_scale + + if position_bias is not None: + attention_scores = attention_scores + position_bias + if attention_mask is not None: + attention_scores = attention_scores.masked_fill(attention_mask, torch.finfo(query_states.dtype).min) + + # (batch_size, n_heads, seq_length, key_length) + attn_weights = self.softmax(attention_scores.bfloat16(), dim=-1) + attn_weights = nn.functional.dropout(attn_weights, p=self.attn_dropout_p, training=self.training) + + attn_output = torch.matmul(attn_weights, value_states) + + attn_output = attn_output.permute(0, 2, 1, 3).contiguous().view(batch_size, seq_length, -1) + attn_output = self.out_proj(attn_output) + + return attn_output, attn_weights, past_key_value + + +class GaudiMptBlock(MptBlock): + def __init__(self, config: MptConfig): + super().__init__(config) + self.attn = GaudiMptAttention(config) + + def forward( + self, + hidden_states: torch.Tensor, + position_bias: torch.Tensor, + attention_mask: torch.Tensor, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + output_attentions: bool = False, + token_idx: Optional[torch.Tensor] = None, + use_flash_attention: Optional[bool] = False, + flash_attention_recompute: Optional[bool] = False, + ): + """ + Copied from MptBlock.forward: https://github.com/huggingface/transformers/blob/v4.32.0/src/transformers/models/mpt/modeling_mpt.py + The only differences are: + - add new args token_idx + - add new args use_flash_attention + - add new arg flash_attention_recompute + """ + # hidden_states: [batch_size, seq_length, hidden_size] + # Layer norm at the beginning of the transformer layer. + layernorm_output = self.norm_1(hidden_states) + + residual = hidden_states + + # Self attention. + attn_outputs, attn_weights, past_key_value = self.attn( + layernorm_output, + position_bias=position_bias, + attention_mask=attention_mask, + past_key_value=layer_past, + token_idx=token_idx, + use_flash_attention=use_flash_attention, + flash_attention_recompute=flash_attention_recompute, + ) + + hidden_states = self.resid_attn_dropout(attn_outputs) + residual + + layernorm_output = self.norm_2(hidden_states) + + # Get residual + residual = hidden_states + + # MLP. + output = self.ffn(layernorm_output, residual) + outputs = (output,) + + if use_cache: + outputs += (past_key_value,) + + if output_attentions: + outputs += (attn_weights,) + + return outputs # hidden_states, present, attentions class GaudiMptModel(MptModel): @@ -280,8 +303,6 @@ def forward( use_cache, output_attentions, None, - use_flash_attention, - flash_attention_recompute, ) else: outputs = block( @@ -340,6 +361,8 @@ def prepare_inputs_for_generation( - support for internal bucketing """ bucket_internal = kwargs.get("bucket_internal") + use_flash_attention = kwargs.get("use_flash_attention", False) + flash_attention_recompute = kwargs.get("flash_attention_recompute", False) # only last tokens for input_ids if past is not None if past_key_values is not None: if token_idx is None: @@ -375,8 +398,8 @@ def prepare_inputs_for_generation( "use_cache": use_cache, "attention_mask": attention_mask, "token_idx": token_idx, - "use_flash_attention": kwargs.get("use_flash_attention"), - "flash_attention_recompute": kwargs.get("flash_attention_recompute"), + "use_flash_attention": use_flash_attention, + "flash_attention_recompute": flash_attention_recompute, } ) return model_inputs From 0372c186b22cbebcc72f44ab1a86a71bd38f94ad Mon Sep 17 00:00:00 2001 From: Iman Gohari Date: Mon, 23 Sep 2024 10:32:51 -0700 Subject: [PATCH 16/97] SDXL CI script throughput (#1296) --- tests/test_diffusers.py | 72 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/test_diffusers.py b/tests/test_diffusers.py index 3015dc21db..afb7516247 100755 --- a/tests/test_diffusers.py +++ b/tests/test_diffusers.py @@ -1241,6 +1241,78 @@ def test_stable_diffusion_xl_hpu_graphs(self): self.assertEqual(len(images), 10) self.assertEqual(images[-1].shape, (64, 64, 3)) + @slow + def test_stable_diffusion_xl_inference_script(self): + path_to_script = ( + Path(os.path.dirname(__file__)).parent / "examples" / "stable-diffusion" / "text_to_image_generation.py" + ) + + with tempfile.TemporaryDirectory() as run_dir: + cmd_line = f""" + python3 + {path_to_script} + --model_name_or_path stabilityai/stable-diffusion-xl-base-1.0 + --num_images_per_prompt 1 + --num_inference_steps 30 + --batch_size 1 + --image_save_dir {run_dir} + --use_habana + --gaudi_config Habana/stable-diffusion + --bf16 + """.split() + cmd_line.append("--prompts") + cmd_line.append("Sailing ship painting by Van Gogh") + + # Run textual inversion + p = subprocess.Popen(cmd_line) + return_code = p.wait() + + # Ensure the run finished without any issue + self.assertEqual(return_code, 0) + + if IS_GAUDI2: + _sdxl_inferece_throughput_data = (("ddim", 1, 10, 0.301), ("euler_discrete", 1, 10, 0.301)) + else: + _sdxl_inferece_throughput_data = (("ddim", 1, 10, 0.074),) + + @parameterized.expand(_sdxl_inferece_throughput_data, skip_on_empty=True) + def test_stable_diffusion_xl_generation_throughput( + self, scheduler: str, batch_size: int, num_images_per_prompt: int, baseline: float + ): + def _sdxl_generation(self, scheduler: str, batch_size: int, num_images_per_prompt: int, baseline: float): + kwargs = {"timestep_spacing": "linspace"} + if scheduler == "euler_discrete": + scheduler = GaudiEulerDiscreteScheduler.from_pretrained( + "stabilityai/stable-diffusion-xl-base-1.0", subfolder="scheduler", **kwargs + ) + elif scheduler == "ddim": + scheduler = GaudiDDIMScheduler.from_pretrained( + "stabilityai/stable-diffusion-xl-base-1.0", subfolder="scheduler", **kwargs + ) + + kwargs = { + "scheduler": scheduler, + "use_habana": True, + "use_hpu_graphs": True, + "gaudi_config": "Habana/stable-diffusion", + } + pipeline = GaudiStableDiffusionXLPipeline.from_pretrained( + "stabilityai/stable-diffusion-xl-base-1.0", + **kwargs, + ) + num_images_per_prompt = num_images_per_prompt + res = {} + outputs = pipeline( + prompt="Sailing ship painting by Van Gogh", + num_images_per_prompt=num_images_per_prompt, + batch_size=batch_size, + num_inference_steps=30, + **res, + ) + self.assertGreaterEqual(outputs.throughput, 0.95 * baseline) + + _sdxl_generation(self, scheduler, batch_size, num_images_per_prompt, baseline) + class GaudiStableDiffusion3PipelineTester(TestCase): """ From b55c9cc3a5bbe98578a49489cd0ef23e373080e2 Mon Sep 17 00:00:00 2001 From: Shiv Kaul Date: Mon, 23 Sep 2024 10:34:30 -0700 Subject: [PATCH 17/97] Add image so that transformers tests can run (#1338) --- tests/resource/img/000000039769.png | Bin 0 -> 694498 bytes .../bridgetower/test_modeling_bridgetower.py | 2 +- .../tests/models/swin/test_modeling_swin.py | 2 +- .../tests/models/vit/test_modeling_vit.py | 2 +- .../tests/utils/test_image_utils.py | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 tests/resource/img/000000039769.png diff --git a/tests/resource/img/000000039769.png b/tests/resource/img/000000039769.png new file mode 100644 index 0000000000000000000000000000000000000000..a3b5225fc3cef5c492cc109aebe883f24941a156 GIT binary patch literal 694498 zcmV)NK)1h%P)~Mr5;{Q*+_g-twY>e?`J@1Xefm>+O+X`>;;D7r0KU6S~n34kt_V&n(>;9JV&!_2o zQUei%9T9tD@wWWw@o)eYLMpX(%pBs?s-wjZxBNPGhwAa^!A1YA_^DOy4@&|Hv0~F! z876g!EOtpyCI>mSx#uQ?NJam_B&DsdkN}OAR6tUNjF~4Ztg?K$o59>_U zHH02$EwN`dN^iN|owm!eD20Nmf!N-~oJvXA?}K#&xWuh<7bd%`*TZzG*~OBr!$dz0 z^`j~_%kIwIbDX^@qc(k36?$CE|mu;9b#^X}Iix8Aw2dyHvW*4N|l^R`v8)jE)^7$>)zOoxXI z&wkIJp8oE3fAe8GT`t=_C>aiYcfJ4AU-VvsT90D!o~|E0p8WNl8-Oxf&IkEr<`>~j zwL!HWu#o~#yrge~`V_bO`80An-1p~hO82>(?Nchd=~Yfxv+AA6@iwntr^h4Jz9c~z z86U~U_w8TyN37dO%C|g@N-o|kV$;axE3~Pesbvjwatu>-?-oI!& zcV>e2BqB;vu5VRVmJc<%3U41y-@UzE`|(7Iz4zK6QJe4Y+VFI^%-h!rqUj?u4t-nK z&|1kUIAciI*>T%8JMg-_o%BvZh<-FP1dq4vrzDyJDA9KaH3B@-wjr7y}A{bAsu3 ziqWUK3xra4qy9FQyB%QLZ$t!@HE5%I;jf4oOEtF3339+%=yH5~+5adaL+?lcfacp` zP2Y80MsMk1S^sGCP>Zs*^Q)aM#mCnifY=Eu-K*={^V4*anD06EWAFWDI~PlB0;F7w zxw+tg_kAzSwN}+2rJ|;O&2cJPRkdSoTpiR@;+)grG+ZUx6k)*LxLn$!g!p3g3L_rW@)CP5mannIW7$xW2cjNng?)qW( z@uM-e?`FTT*0~m=Ss|qmvp~;US3ss}IJF9;cC2dwQk0>D8mlx#Y}lYc@hw>-5F0W9 zdPh4**`qeinnQ~4e~W*phQDk2gE{_x+Wy-x7X$!OLIoOVVUUMRPY9=O{E*CVWuFy% zjFpW06NiVUb%h>Puap95r5Z%X8VeJFF$SQO(nLg&1%PdTwug^<7R=F^sw{ags`cg& zQ;4e4s73|0#jI892c0+6);18*>b|@yP0s22?VW6|ttq9NhAzvgA4Y@4ucETFW+tw?jzFs5|tEUl|YgGA7EHpW~FksecGt@@PrQt~*NWqWRtjdQ@` zyoAX{)Fw4(MI5;j%QzlFPH*XUw1aJIpnIR!;e3pB^O(@`cu5fvWAMx4=k;yWnu~c&?r5JV6e9HAEP)Kd5yq9uPqrhNYNx6ctT?66h?ciLNFEOpt_^6=lAq!}1X0WG{GN{*#Uk3HJ0ydUt z)DbGUUiLT_xcBl;v;5ddvw)pjMv^7i1;4Ihl1QdurptkNZBSR30Bc z+~%uMI&9%Eo&6S!9gUh=O(KX$q5VmyLHe~ei#Baue;UVwAsgr{nVds&(F+pI@81#w~+4E z8U;3cr}bT-x+Zgke8=7JgWC5nwGZlt*Y`Ku+mwRR*jlQPwPqp!iYXDyfGlnLp+~`|gf6dGlzll`i1TcU& zBZrDPW={$cvG0y?Z`St9`r~-Q)=b!`A!t4s>HzP>N7jW1q&ksvsf4!r^ln87w%7UF z>2i3zzCL|;*j9f$9==7vtG9bhN9Xsv~BDV5vopB_$e z&&*b^ftJm0qZ_DVQ-=`O3Z>ND_WJexU0aiL?%2NMT`BcshIQRir;the(rimYlR0oG zY~fpYH^y{Lz3ev-GRiWm(rS?khz*-Ay;3T-+CWNcDaWxlh}cpvmVENg*?r%g8W5FM z-MVMP3R`6+q2g-{ShpNc${1rxDU1?Q$+fXI4N#TMbgq;}sA7yt8HEf03ficw#4I9a z>{H1#ri1I3S}RJy+CXZ}tpTG_NL$^t)`*yMtSJLPQIHuvB>SyXe~|dk=WqZ0Zu&Ki znbm(b{}})Z0thLf0u8ungb!7HRO%D=w-^?UR-3M3%W38cjUB0hv_^nhxWTB@plr>J znE@LK5RoD+f~~?f7V}m|HGOS!=`yAG=pNTy6fr=;=6gkh=}9=}4ALB0rERXw^xge_ zKOT<&EhgVu7DWIksdT7};>NO0 zFR`8o&>17O2vo#YTND&BZd>EvjsS35-j0{??e^;W-nEXMp7*Wqhp_m;4Iwok99^7? zN2T(bC+Y}5Mt+Oe9v>_C0$Ym9)A5{kQ0lmDO{vZH!?NzIw6Qwph)}fR*l3R5YHOqW z?5Iy=iFt46lA)o=0l75@gfW*MVQBEI!i9z?l95|$TZ?$ zq?TnlU#8|=$%qPxqjPRus_WSFsm_mG-+z1iaX7VH)^0d$YjWE4tubEK?M0bPFp$;2 zNq{j-sR<}X$*t{90V6DcF+~{q(WYsx5qry$biNbpWR7#{dO5NSxvaL&KHNJu`E(^xTK}pR%H`QQ)xsoD zSZ^(>IX;7o){M?MY&I;3DYp8$hQq_Bw63$l1EGS#h=}^VawpX}ZA;Et*>BH3e)#-Q zGUeJ(Qp!6v)7b|Ejhy!IqBv+iHn0Fv+Ffes%n`(9>4d)$@Zb(1y_TebbiqG?at42+ zxwdk}IvTX=_Hvva-u5z`42ZJryw6KF>|n|k!IqPqa7kaQtd>fNUXYbnnFKym`= zVp$HyZg~MWiLZZ-Q5}zmE&N3C(30uhQ1YG4G5Vd|3sUFm_kz&q%i(amzAx7FDMCoq zm>z+p*=~^CUvp~CIe}7Q>bjxmuQiPz3DTkO4?aF4jj6s8JXteSr4WLh643zpnA6SK zLx?l0CXl2lg%=Sf z}r?-a>x;lRWYqil-1vr zGH;6vU8nV$a%6rGpe%Q33DpwfU7G2-&UQK{in|tCob9!Ceve9%NYO^#-=6L0q?uZ^ zG-I@DHEV{PBdD5E>iS{dgVwz5^I_@`p~zW8TFu_?U1yy!&}3N_>!#`8mO0cG`fl7q zt~iqxRR+k?yADVK#ME-hB9#&?%p5{!pkk_*hfmv@OK$9f)EZ-CYY>qDYprDUGJCQO znm=1!{1TKc$zAyIJ%3?lV@zu;##o_}!iG&nw$3#WHag{8YJye;s#Bv6%Q*J;`|Wf( zZCg;LPqB*Iq(X~Tu?6D2Nl~f*oJzLVw$=b3<_bhix`HH`3DF2E;g*55sa2HG%~grH zBc+r|r3ivXs=0uuT5Bah25H!8Rmeu^nIid`a`6U@(uXRsg*S|cWL`D=AL=tj`IUA5 z1O4Iup5OmfT#>Of0U-rgK!DmH@R7|&$>hf-vB8W7{@+sU8>jPnV0od zM2w!K4(G!UKi&VHxRNBR1%+BxC}XQEZu5KTyK~MNpjF8}d26jj5!KeX^@CmR^YL)G z-!{-1WEhwXfbQM%wt;3yxsx_i9L8zeywy%(o}7y%6sgAL+j~5nzR!`@ed`8b_A%dv zb_9*u3<~;^VoEcTR_1cwXKg8EKORo+x0}@_#&8(jCZYK}*=a>-t(lnhVDEmx|K#&O zIriU0ABmEbs`=zo?8jC;PUA(ShO{^{y}hJqnsU7{%4z(om?4z6r;mr%_uIO?R=PfQ z{~@|Rlt`Gr2iifs)}|3JXYJQmQFU7DRy;~DT8fkwXq=8I)m(*HD^$$<{`zJ{G|XB% zDPu`SD_Z@}wu{O<=Jq1Zq!gy%vtK*G1pssX3^Dw*R-`4=jUqtjMDD%l*yurENpAs~sGav22m>&I824hM9P8{=^nbAy$`M zerj1kZyIbRW@m;{f-x?a5bM>+MfY*tUx>!hevVsXR8E`wJ`bbu@m2F9*C$jMEv*Zdzm!{57 z=WYAFQX@dGc!=IllZyGOOpV)zHN7jtDYo-4#JCu9D7g#!ayUUPDyL?;kcv*p*#Rut z3iG6~)~d?=+b5fN!_wRu)8WuI!&TBleLI%r+1svk^iDadb`oU!Nkx6PW#D&vfO ze)%?>`dYJ~APN)Ino$}G0HFwEfBf`)|NL^#@j-RBM&;CMJ?OBKz7c#3?aqjDAL=7j%6X~#@R2Y!A8F%0;@F$0YE9G zl!A&IVb1x{*$T3IuZ+&EYOPvHB4X^h#GnnyK>sZwhNK|}x~ z0Y%!dnqwwGA(ItCD=MW5IhmFaez)KLi?;kTGhWz@X#e;ACj)ASTB{%sAyp|1@PP2i z^pE6fgWson&-#EIxg9HgXZjgcjIoqL3XL%e$<(GAKq;rw2?cUUnw26#A!qD$-v{nQ zWG%a%RP>=`Aa*17*0o&TQ{1eXES;tH1f9ljf4M&@y)4Vq)6>51%#1aaR)JJ&jhRc! zz$ge4ol|)yanQ)P)Ea>dA-z96d<^mae!Clf)LN%@?RyuzR3Ro?lQ?4mTP~Y+QftbI zx~_}S17RPY>;9fOhZK*~spX1_H@^WQn+d^D zF^*ISH)AX6oWn;uNJ#IcF07T`+hP3dH$YgCT@_txWL#M%0S?IwW@^C?*4(m(X}E9e z(OR3cAbf-HfyX8Y*rc}2w0=!W)5mqYF)9t0Mv`T3o3wC`d7Wn#!ZB7i`QK}s~}a_&Ns%egYy znAcXJmf92#N)O|acmi2U*=zNsc1%Z-$*M=t>-~Op=474T)`)HD$LW5*6Odz@tMo>1 z-jiUxEL>dv460|wQ8hqZju>ElPpIqnPuKq<}Aod_%E736g-K{-4T~*$t>DDvo z0cz>c03U09r#95MyF*`FVa_Ga6Mm=IAx(_Rm!bueuthhRUtH(k8vHbxe>C4JxmP{< zKmGKl??3&@uU%(#makbhmWk<+l*(aa$O1k z{tSv=xBuqx@HZhRrKQ%Q6*VEPOw4uPd#zFF7}JV~YcMTw@y3jcL`i9Ysu6~gGh(db zpHAO>efc+!51-g@k%(Bfb$NVxOg{KU4#TgT#M*93L*6r>@g?<#KT{*WH^gSmK`EWv zf0eTt2NxFKcaJ`XkgC^qmjvdrOp_)S9J(nl3y2kuZ3K9obP+r+1+o(l0YdrbynrVPIJk1+h^+SFW+uDyO zpVr_H4`<5phS-LwThrAScHIwL3>IKxja|tuCB9?+1o;>G{KvfPu|Vghm>Z(Yp;$ct zHqAq$fVdU)3N2N+;Htlr~yB#w1Le-z65dLY9m(uh*+?dKjh#6w5{hUa09_AChG6 zw@!m*|Lu)G^)a_$>R^XuIR165EG?Au=s??ZD59BR1Xr@X@d4trjmvEh)s;tq*wtxB0(A|gpDM)8ubmM^ZaA>5+x0kd{D#e)xP^lIuK zNc))Y4AZ_sqsWDo*+6Zgpn=3vOPfk#ZdECDz3#_{Ln*oIth9pCm@L<9jM;;CR_9cW zcA(Y1D6OANPqo}~usLKmxXw(i6l_o>33yh{C_r#6+luo%muWhM9ZAg4Q3$JXn!%K6 zr6Fq2OvJWU)`m8S$)sJClG8;x$Jz?`91|+#%pN>5Gc%!hzfWxOKmGJSZaGo!4 z7Gh4Rb*_&=SV<}KIF7Aqqyn_A>&kc>?Nf~Le4gHJ*Wu8Wx~pbe^$c;)50pDpZ#gZK z`@7fjCnOD|G!jd#r8HEm9M%>>GOjbW6G0-{*179C1TLkeST$K9HdbPJv98_M+;X@DB&`%F zjk})_(e;M`6ekg;jW^Jw@?nzCOM6kUjK*yF&cnEQaRha5;*_LLsYIA-vZ|AiuPK|S z{?gQQNK2KIj}`R_Bsot9*YErK^zh4Ny{G!zDm(oVDRZMO|FLtw%wf>oZC}NxljuKp zddRRMCBG-5&Q1@lrj!@J4@jY5C{6rE$HV3Q?d#>SYYnim($}2Ax%(&A>$~lJZ3E{^ zt~W+-hv4%IslOup=-ZF3vgsR;Uf*$aT`tcJGwWjr;W!*xsq?x#eg3qqGnO(Ky~moX zNxTl@E=gMWAQkMiZ11ff_;{SYynHEj`|$Dewx?~sf4KbS_1T}M4?ety@?|*a7?q|6 z#|z`3mN7<;>JE@Pcc_tLdsVt)#;`wCX?A>n{dT)tjs&mMGKg7Xynj#&yo&G znN~T5YRyne$tj)B=XHC5mWW{-$8EhDU8OPObQhn(Jm_C57ELyK7J5_W5ar+Y>QgOy zky6`HJ1O~9q9yq23Rl}FYE+9-+>ol`VPDMYGH&}Vi`NXsxVM+L%kdJjjFSl)IPDs~ zuG`(XA=i7--^b{&Ny&H|pXRr_HQeeB{e54(GY*0K?yxLt-yLF3p*4VY7z73Vsu(9@ zTu9y?U$$-Q__L(Xgs#x59m=-tB$r%X4dJ2x5OZ4mGr1n|sM}ug=PE1HM{Ji|_9Cy! z7UHRtMnt{sLkLQ#ZQB4qtB;BxriD}x2*9c&69Os1Zs-9(q~7QEzEg^tVyNXPh`AOZ ztTk)J1fm&Jlv1~8e9(r2PeQ^jEbGR)!-7(%QM-kOJCjOAP#KnOTZm{FrW}g1-K#CnGy~hA{N8ntWVzcl2G>$dp zuIpML1u|$YIU%(qxnQXXwi;0sYbmWFVyq=W!``ybT3aG&RR91Ho79xjlhrrKz9g4~ zh*pOl3dQpOi0S9%|2t+li9XihKZCD@giEUg+yDUzAtO8peP(mERx~q{>@+qb1G?k<^P|M926LK;}aV;I|psZoNZ#QQY zG(4RS%QEji)%Nvt_-GJmW_SChUtgtptS}AFlhDLQdZP$7>U;XX-(+7NBzQ26@)BX9X zldf*iHqCSD6f+(gttpt^;{W>TpLOa{S_4|7Hl;2l*9!M(`Y6?S@B5J%)qMB;z&?F( z_H*CqZP^VuR_N1~6OQ9Y9DdCH{BUyb*WVBJ^8V_r1J{{-%Nj>&eMg<;wufs(KU_wg z-R1)+W@T!v#+VfS;V|v~oyg?S`u;DnhnASBE3_)^D%_npUT<%%FF6sZUAGH}CTfDA z7AELx3ra5l{pxojP(}l^(n>=prD!CAfmL0?k6Lxs{IX`3^5X0_DX7pGdzfb-Hf%Q5 z_m++W4wKVckfhOn=vMx+p5aXD2LvDFm|}l*9tkNU9ey? zre6=kcgxi^*mHgFdJ9m?LaN{I_i{S@B9$dax*VU1JZG<)un^U@3xuKTvbS6(F|Pu7 zIM{D5FMU6zDAo*C8J~Bo#?l3GG5y`I*6EL1dO zl)>lnO|vR{UqG22Q}LLPD%oL-tu%?pPNn4*^Je-F4M$^fTmGa~g4PZ0-m+1f^$Z%F_e7$|ia{n;?G6ussxIs}pzP*37IzpSMzYg{{i+^E8 z0*qnTJSy|@w#!M*@W8Mv3Lj_kF5On_z!&FStvTiWd>WSRRp~k%y60DQ9zR8&6*rXX z^kG{{XND{P*Tw78aq7*7FF!v|2S?Zm?0&yp4&M>lnB=wuR8>))I+xV9(l*_GQPTnW zi*Xst3B<;byHS&h^YVH;9^zi(){Gu%|E?IzJ{s;>`w&uVuF1ZsZ#8v*M*twsVV^s5 zlw?ky^tS9$>e!F3&oBKrvPE41xjVSYBd&WxR~tKMMX@Yt>FQDWxQ12>`TK%eErK zCV|LE?VLMreor-A4#$+zx^3flsJSpv@}aX1I017^MG$I{zOx{e1bm(~oW>D0|5{2@ zYODqIc)#v}z=&8h-ICYTqO{gxR_0?$`{Cg3^Q-GfN&x^7x##?3hSuOY?89NQp@C5D zb@r)ffR;~&m#`$GYNQLFVhbXb6(M5Ixwa-mwSlp@9EgUYpt16ijB~h#8%~6 zKJqyG4Ykrr)h#eHc4kSTwoIsm5Rnk!FIBAEGF1di0IlSdYS|e)a{%~1`RiX5`tP1T zsJ;9j+w~O)r1n*IuDSvNMiiysdobUV8I}Ig$6bjN>QL2M30m)L-u!VGRRi0Qk&p=x zTdl2B>KLkEMN7sht%0ajLM+-&-aie)OAARU2{@UOm)9Mrlszmdb7D zznkY|r~SJ44-b!2N?dP_Ys0*@D%R;}hGE!4FxL7M6(KXGln8U@F42>7CYM{vcVk@V z4(sgfa4V$qva=6B{?GPX)q^S-ag(;?@WYAQ95qFVDX^$<_% z{-P{2A=0$_YXh|U@WkfpO+eenM;GT?N5_Eh=D&To6djF{Yn4-RBPii;!t`D&R!`%g zV_ZO}VFjg`4${Au_51Z6l=(UwDz)SD^Yi5i?zhXs{n-?-Z7r?v@?}VNQQ<`gl}-^Ll?1HjkI@=l849wHwvE`%>{?I)D4| z=kI>;!*#nGOKF}vT9wjG*>zjOvHJy7h4f*KKWWtyg_?s@czD$N9R*B;Y69}8 zfmcZIb-|-KYn^MYtpO1XefReM<#ZnQWsXKMo~6{&G0*cisJ2Et9lKD@$O)y?y5$lN zs$*IH}UKajqaR++h!mUAAufrOyR z40mo9s|`q$LaDu%wpsglou8HNKsh1V!d0Yny{$zcpSOMaczLv%=lRA8g`kvDBUme1 zBMOzIkvah=HO^)2T{m=gTi@IeZu1v~51KwnzV`h?h4^y&t`T5H6)2pOAmHB3;OVZ|17FbJ=+=zV zS`o#@qmE^>4YhO`wa?J^{TBaNTN{RnsNdG6sq3tZey6^+l5-IMGZs-onbp_mjO-0RQop$STcfIw+U&}r!gT5VxhghXjTm5Z1 z7z(G@!kq5I)B{mW;tPW^j~Y@~YTde?_I+2nCmw2J6|`|(p>^|Lq3Yg!0$n})wA67x zf;~Kpm-l&7-LS{K>s<^(9J2$4h zDeHUnF?xNU!{u`kk>tU^*%;a4??8+K3k?Y?G9Y4WjVinTSY&U)ET+-Qb-quYWCLLvse;yAfX@@Fhk3TbXEe*zs22}}S z4yJbkwUiXr%^9nXp2{c5k(F0+ELoD=yPixrFZ1>3@w;VxZ;(k7xmFt_Psiay@QY9H zX0XxI)T%;9k2WldH+ zxS_=5+VvJdy!T2ID-}XA#)Pmxe*Ca(szwp{`E=6vt493Z+P{aQsC{hsTF|#L6-vxU zFd^=Uu=j=uj?4OXJUM97`s$CFXn(2uj3 zRo&WtFrD7vCe)R(_pGt(KD6WU^Ss_c`)NAg-_&3}l={aibLT#G{rTnn-&Uxuce=Z$ z_bydWY!8PYLdv(>>+x_XiJdD zN(S?;7P%yKoA2*NqajXv>APbog_-yAweL<$#&4Tq z=j;^XopliYkc+ogm6~di6vdjU7HR}T*T=9!iwse*)R4MvxcgvxvxJpsY(>8jAlt;JxAZlsc!Y-#0_gV9{Nl_0`>tmY)NKCC&TeB_eG)FXCe95-JaMbH>pV(VIi6ZjycRu}UNbMt!q!x%PAO>RmZ>LFN+oWk z6i$^{H>-S8zIoN^z3gYP2OR!)_Wk{~{0-nQPoLjn_!s_VQwWW?RUr~VfeMSs9(y zdvANA2frm{BB`L2i{AD|x7M`Q_vOxv+Og6YqlcWvaf~6SQiuL<-*#56NYeAs{Op}A!pM~RR};?Ww&LWKH7Yr9Z-_oI^II;l`$DtITC_Y zZ!NNF`{o5~-<@vdodt|2tI{_X^9bhoMg) z5|A+lP$L-;F%;w0lZ>w8TyMneQyqtC_jkddmHkUbyT@l^2*eF0mT)Gmq)qHzbFfN# z!{$0)kB5sw)k+me0x_4QH4o~8FE6eiat$e8AzeoGy|mXrVqMT`ii?p}h;>X0>i4DH zPI$uZaSvvwZqJ?uIvRd?U0J7Ys==$yd>`UBR$Gaq(`MN>Hf(#htanZ5tZk)Q@)@Q* zUdV~+o;eEc6Qyh)C4?~9U%y`epk3>RBXD?mE>BNLqL!Q2rnf`CZnGuB)vCkma!;4@ zr$7Js&%gTh_ph(dQ1Uj}nXDJX+YvQlQ>^7UGN@x$NLaMea~LT&rE_pjq{l12@Sg!1t8 zc)#BSsz=W-`^egW{P|L?P2^x{;%izn;z~yRmUTWU$p7$TD znw#{XJ|=i&>8VhK-%kg%Yz?p@6~oS>`A*vw{7qTEu8ROO*(UwI6=Q*=_3l@slo{u@ z_ZNl-YbeXlTK*D4V&9F3SZkA}hyIZ3bBR`KT}or>t3>TmPR)ngU_V+uqd+rU06|kqt2JBokB1RM(0y6vmQD_4#%@xfE+m zwsXgQpOF)n!RyFHI&Gq=MszRTTbK{yuVTzTJ|E5@WpD7D!rl!v6_)CVZY4QITB~IE z6H&OArDi=&Prv`uZ}nKWvYPIY3dcMfW0X=U#hQ!JI{9FXNurqwLoT^Z<0aLyr5mVd z+NWG&Y$3Nq+^o_BtcYR)A}Xag=ScCoE?VnSN>6Fkq!Mc~Hv|m%!fwTEEXUnPMoLD?450stbMqOc}JV89So*INL{v2~_b#I+V|&f1fh z{~W&DmoSq1D`mjhpSI=48k%AjEVT#%2m!Uep+@n(7LW`wT5l)Y=4{%VGEMWp&toa$(~4vTL(Hd z46$@()Xa>u`MI$i$EWo^8)HKBq{((_OPi4N)VHdTYA$nUj#7?*Ih3E33;<9{>ue7I z2+bIib7UU2Z95zX@3-0_YX*i~6XTJQN=&U5Yju(6Q_z}0i!rX{Qfh214a1O|1YcDh zSqGo)&Q7yqH*5@*h=8e;8i7!;i9*@OPMfJaY%$fA z6&J-7{}#a8`8N0P~ZmNW^Dpb?>7(-67 zXce7b!58jcq%)y?~FzkB>g?{7=Y@^Ct*=#iSz zjX2-#FUq2p(b_Y#@jmb8i$Z=)YdhM1bPv7gf1`*PK3G#4-(rlc&Ae|9-RN6MQaO|) z*+YKl#@1|2L#l7Wvy`L4QJLM^TD;ND#*%#st}{7!5SgaoKA%YaR?^z@XTz6*LTpraEhW*vp^tY9AhKi4FNNXY}!ni8?uX^Nq7vIifM27yrAi{h&1FG#?KpRG-@aTfrzYj~?d|dL!?G-+ zaYa41W?7EbZR>nLpMQB>W`TL?epB+*?}fM?A9dP~%l>whF_r7-{CN*?%U_H=qdFFR zulX$}RsArI6w7^@&YO=Y1IrVudx=}uPkZ($%kkK4+n+^0VUDA@?EBsyhiwnmsO)!? zaU4H{{Ik!q#_tdUnX5DB6qtxh^-7tRopmPW*HR@O zX5AZOi0i)3q?nl*9&+&{I_E4Hv_`9Si1$R^C|gt0ECNLYtUhdBOt(7o*kaeJg!JA` zx)v;zhF-tD{OVpr9;AV}I>XcGV zeA?RKF#5QsTKZwy!bVnN*=_wBV8R>av2Udk9E4|q!(7F7?fLDCHp3Q>trL|( zdJ`}rf&{fzB4Ct~qEf1eK`K%rh&F0sWfy-_7LYzBr2u$uyws{ zt>zrr=u(nx(^=aa>`UGroV{(gV)4QzhMtHJF{Na!-Qx}fO3~uA>q}`1RK;*c)Y=r_ zb@9z;R3s%8gVum*DLSLGq&gT`qZ8UJVn`=rZ<1O%lVuXY5|LU}wL+m*3pGYXm2wg? zt~F3d8q``gXG|5@OA-WQT&}Xmw6^sh>4%@{R#N@1djD_2{ckeK(4U+7D(T0#i-18E zgw!e^01;I=0uIJ}0!Nnr%jNB_?PLf`Zmm|$%4%*YDuA)?B_I*BRBR>St;p6|5v9~btO00Zl~OW}=k4B&Dy3-qZr|dT=+ce8 zgw}D*1@4;}Y;FXiELQHi*P9|BJ=ec*E#3?vRpf6jxlQdyk3XiX%2D+QO>3Llu=3fEH0{`3`|1H5& zZ5r&~SKQM6S<3fJ7b#Oo*83dt&N`V6fJK9H5Nper7zk+=mD$r7F$X zUL^ba#nL0SQPGr=-o2h@sdZ5L)YE4vdxPI){8yrWvRze*kd&e|&V(3UzqQ=$dnC15 zed!Nd!*{@RpF=C1-!#&o0kMpw5n}4mcL+hY>2D!|$ zLjAnOKaAb4(=sWyqI*`utviG)@42l7BdWIfwYYyB`L|jBeYL+A^)9$I@(qaaLxH;H z9}BKXy~4AyI@X^IURvx*GAJjlep&OQ$Y|XmM1we{@E2B6_xir<5$=0KQ|JAy)@j{h zH*UM1MeMfkTihFq?VYl!A3JOHJkK%tY5d2_>XaFkQM+F{H*q`Y`jHV*x|JNEoCrRI z9ifi8{Q~GYm7hy}o0p%NbIwa^Ym8S`e*{=N`>E4^)!V-!d}wlB_5)HzM6GPjxzr$3 z1$32B@L-(Ia0l`vwq`?Gg9>wnsh$um%tv57};vG5|duWR{f`?j^0-~Rdcq3V0gVpHY&?fxv)S*@j35eZ?pR@a(cXO-sC zX02_jgI2vj+QYk6os?sa9l=4<)T_}DOO8kZbwJf(+1YT+d5^(#eF%w^?l``t7lJr3 zy{A2c8SygbK0{HR4kb6j*b-~bIkMq~IkyU;0SzKT%Q=OTa_@TYBLMeqD5;7_-}fT2 zH`SFl29U*O06X+ z2^tCjz%J!DjQ=$+p-67lGC;R&qP`tQ@+ z0pwOgPM|<(U8Uumwbp-e9W1a$YORrC(Eq=YG)V)I7~<66rzZv9k5JiEqA)Lsg?Vi+86hsTVc2Rj`q)>qm;3R zQ7MIZ&+Ub1aeY^zYO4WSjQ;8AY1=mKTn@1{xX&xFO0k`%2L#msLr5Yx41L$zDz%i- zKpQ<7qxOAQirA>tud!4+Ov|2C+P@KFmbT=jNetk1DH5Lr)V9ygJ?&YRusG!5@HFo) zWbRB_nk%V+-B2w|=M<&}&q`4(GqTNfR@S7Fn}p)(l2jw(qYJ?lQEPbC%!Y?tg0{8m z)$Qs(;53#+IkrJ$;2OnRC5kN`~U2a0^51{X%YC94`uSBIXI&<>P zD_WaCqma_p+ac%Jb%PJf;6@})<(_f^#D=1^Cf$If*1W|(mE3XnNp+nbVxwoIN0tx! z?W%|0a(f{4MezfL-?-_gQwz=J;veFwx&xxQ*IGny8Xl$5{eD-BG304_m}je<*KK5@T%AQY^>QCB{%19*@UdTdlQZ00x2VdTVLO zMQ+#Ue$>rJ;V+sdXS#7zx7#(gpH2MKx(TiOw*D;Ot^JUrZ{5ovJ68=e;%Kp7(&*Bsrl!M@wwuhe1RsFjp%Gt}D z|6IMcbSmYh^nUtaK>U1<9seqB9r5~jKK}Gmu%<8dM|Wr?aZA5RsSu7n-q_^W5|U)S zr~0t1JRQaRm&@VvGJn-$lxTKePN(lxcd-`t?Ha=Rarpi){-%{&@lW8k1Z3?)^dOK0 z+Lk{|=VPwEw7P9G_h#8WG_c_;_~$LYozLHSKNu#T|3RtK;oSCp8@fKn`us8<`p3RI zYFoIEHPv;;uKUzl9L>1yZ(8k4<$eca9{ZjV+xzpPnVY#<{V^)z-S@Xt zgELP|#+abhsGIAob%*Z$_TG1W&iBJ3etrJ^!*mqEJ=f66UTfjDvQ%oeM0e`9eV@id zEMZ%hX`D1`$wdg&9P`=rIf`_n4PBFs%M=6R;8fT*s@~8umom=s&Xo(?~r*f zvp`d%5Ew-vkThr+LTzRk{I)kK$~i*3-nZ^>Of}|xK}KWDvMgGWD7Y^-)0tKYn^nw; zQj;vDYIG2BYQ8SRlc?7)9$s42q4|ap-%*Xd!>y}`BoDj+^GbU$alS(mv_=sYu#7hxk;L` zhC@o-*x%Oecszy>hpyY#RcT4#np$KQGzD0-q8jLeA~nr^0Wzj{`|7uxUDqi{C61S4 zYrEN)r-%HTcQ?=B_oMw@@u5iY!5_zmn*E;d{qQKbGjj~h*&!rP>WGZ}Vdwo9tOFv{ zI6~Wk}y zh)QvNcb?Nf_q?|_X+F6wv^tge5Yt=ND+b+JjCE_0{JvI(EJ%k~_F->tTBo|5QJ!k^ z4MUM)yDp^+0Ecl*srsax@ApcXF|Ll$3t9C*ZuY3aR?!->-~6s2+7~JZ|Cl? zmsiCHf+6j#9XsQA+kce!xmAteq{2A#+m^@iJp1Jq|K0IvnCFAyE~(!ox;Hw+QXHNx z569PUNh&;aZ_C!>EE z!s7GW(4W@W@4g<7u=$@xd#+qdwaif4Zk4VrH1^8md3isWt^w3qV>Ui1ZGLmVzZbKS6K?B#7})#W>eKahQ;NuZOF2R78b?wT!^U-FC`| z-W_vUhn|gf$v066)Vm#A3{nw{aZP}&zHje;d9Fj(-S>ZmoQPS(D12-nsVo(q6%!c~ zVE}4U2bM$2Is^vuV`rvXOlwFS1-H&=#G!1@cRo)i?365ZMN9{bU*5lb?0+S=?ECuk z@OSt1{<{BI*&m(`5-8*1YhQ2+R7&Y0y;d0@DO3?m-1B^W4<9cd?)PuUktKK5nmvZ0 zpGqm6n{qDFuye;{d$wKK_uW9>+wXi>lrF{;X&7_pRFB$8EiJhcDyX8h4#7LuGh^5n z>rAaRyA$7iH9b0Pw}pBoQ^R}%W ztzc!84M{`@q-x9ssI)4QG_E=PP3`|d`#;s?gP70m@b~`p3konpYr0^S1_S^ANDXQ) z5D7kVA4$H(^{I2whY?vZ_!wN%T{m!*5`sc>)+nLUs1Z*-deLRQ3wU=7T>fns9W>W zFvS)tLamkAH$f=MD7WvcRVWCkq2yGdaK~Z0w=A9Ma)|=yth?VA*ITQlN<&JDq&5l> za_SQH8XqNdO~lM7B#BEcO>k><-=**S}iP+jr}_YNh-(i!=t?YDp;p zW+G6;DGLDU&dlp#v@!;7>&&c%VYuI}svX~)ch6cI|IYeraPuK|chc5P@ArRY;M1Wy ziTp$~V*OC{P31EILYo1A+5xf=NCj_u{7Feh#9Fhc8p_7PF_3kmpUu-#oUfVmpKSYu z-xs6KK&;pirJSm@9V;tTN{PqIA%$<=`Ac`d-S= z%DB%NpvWMIzDYw$+Vr}pJrLQ*Djoapu0gb0LwPBoH>Te=ueBb#;eNjpHDgRES?}H_ zPJ_Gp+kwb*ldh`ce4U?BgBkzb{1OKI?2dxgk34Q@hhULMGwQ+ ziVFE2>owF@-904C_w5Qo$ID}FvdsMW@b7DAG0nNg>D1ll7vjOsgVCdw+G_OsowX8# zl=l!wpeloHm8mI>F3{lczVivFs}8TY2mEc+~dDwKE&H+pl7WIH=e`3 z$DFOxxkOftw6yuo^3drs)^p1xiV#*%I&TMeXhO;_t~5bwoIA|hB<4$Le_>WyB-W~9 zHjGa5t1^1u7v!Ariyk>A?Df-q4%6Ur%1{BID91wczTZbVkAuC>ufU4vnD#nMl zX!V$5eEMKge0@5f_FEE_73oqdYaL3{#?}&{A>op7K@m|kaG=-M)2Qw}x%=+vRXBsg;mB zYn(cg4k)?ST&u>WbIKr|bv;gfCCD`yZ2%|*uMI6(Pt^vEJCmK$657uF=)uv9O)Ai) zVL>7^0nA%lf54=bEmE3*uUQZafB@Y>n2?e6N|FsYFtyfO&1W_KF5dnxdj3%0SN-9q z{q_YDTU85FD-CJ|00AlhPy@n2=)_KI{WZ=2Fo3Gah?sM*##*IgjGXhqxnAiSlUUfo zMnsks0oGJmDIoo}EWI@yu~LQsTS+B&f)>&;bW^Ue3ADnBd&K6$eQ@V0#B6et-bg5` zs2$3FI2_;J-%zplaT<@~aK7Kxlu&*5!L#pztzHArODR{)>ls1$QQ3{Y}D9>$z& z%IPrvg^3hXtGeq`N=9)jq3f(vxNp(uX&9z`oqdcdQ!X-2E~nem<)P-_mqnB6oXa6_ z1N@Ia{?k3mNP}{N6o8PyMU7jP9@?gwF{D|waeUe~QWi*V*rt+W!}osBIrU1N?)U4% z*|pMbn^z{c);fD4hE%RG#>?;bmwktK)8cPj_ue~`+#W}oa?UYh%a?Cdo z>FvjwEj7vQ?Jzz5`AzryM%gX>dzxEtO(iH?Q0A@Ze)Ypiw=2x&6!WFt9we zLPW5|b-Z-4cE{uK`TF-^?}lM)@}~I&kb1!of%Q@BJnvte{!NaCyPr(@wa}I9?^U}X zUbg)@bia;iUecHEF29NXd0np7e(xqLYV%7o6k{WB?^Mi)pP{9~ZIiXl?uTD|e!u<{ zW4xSx+0ccYmNl4W&G@x}Czzy>(I5RDWBY@lOJ@lpdrZrD_=x3{<=eKcN)cdFO10Lk>0*q+Neliol~%DQxe@VQdwJTH#x4@2-77FhqSwQE6dl`#M`~BEsb2Tga99p=+g> zRE8tA6--HsKo)SJR5h5F`L&_yxh5%%x=^E1s>L=KFlbXYRdT^figOZ2#4V3beVO0S z=Z8?L(r#OQD#EM{n^1S9bV_NMcwWEt&eljMJ?j)}6Gdx?0yuR&lxB#1k=?K3p|8yw z%Q0>~?^<(~34JyF!S8V-T_J<6qtSa(%({YTt&`ua;+j)QS@8qltn6NkXg+D4cE1p3 zY86PjZb$`kYMto>a>)T3qO&BZBbsPIh1clX> zQe*bsGaxCqx7ZMRq=vB7R2p=^LCSvtuWOcHSoc}_--Y`I&1-~A3R;CG3aCNXAOj>v zHnjhqxhw|$u)d#-ORXGL=VLr{+WY8~?%7burpaWTQ+m%8r1sX;+!PXlcCHIPk#jA@ zbKl$6l3z3{Z8a(mRS0xg^Uxh~@W?^i(!{rXV8bEoqjP1ChwfM^bi;9ZzuC@!66EId zzTW4Dhja94QnplxEt;NuXp(yk<~Zp$|L%-kxA`y(wG@SfCty-(fW;Zi#XIbB&Pws_Z{yMV6tx+AtV~DZCM~txYHqzXfLn-HXouFZ zXl^wzh(JxjBO;Q+Vg@r+Z12lAJy~f~G9HJ+w#_+i509tYax=!HRB97cTm(YN&h=&A z2WzBNB8cKqoK~Y>f*njZsJGiot##}!8f%Fgldj0h^d%OAhX3=Y-_$*SfBATSo5$%4 zsA|F94Qj1rD6-#=ZhSA5iaELICP}x3ymh^TrWm>ya%Z(ME?4mD7QH`CgNP)_z65Q| z)8W(g?gd!sx`#LC48x$|P|cE4Rmu{oT;g;h2(azfI(=C0>2!3hy+fn8*yD7`^@fK_TelGo@Av1@zpHNQ@T_Hj zU&Ih%Yit@?*<#2AJ99Yp(>lLJUr*B$Qr?r-T_@Be1fz!->*3h#;YDfidrVP`9*LAz zZC<}l!%^WSgv6>;Wgsc$*(#m(vvw-UH&%TOH`Wfxy}jO)(Qa_Nk0SDPd`OA*9JPVJ zaP3ogsrk7-PMvvLXBo%Cy4{GVBwMpSpG_>UDLo^w@CH7m8qDD<#7YIzsMP9y`}++j5Q1<=^M^gy*+$FUV+yX7Nduph*24%e1XB5RwNfNx(nVPoMlKE zVoIe|K&J69dGB*=%=(~BX=dF5U>+twmH+(Z=l*m#OZW5b`Sjso+cqj?nx=2_t2>YE z8JjX(h&HPan|H#rw{HfgRt7?g(v+s?Q|DT1_dUn48*37%WUZ@6Sz65L*tOf$ACFUM zOH4)cd0V4#0&=PR&GojWhn#{jFC@Okp{hp!-z0XOUDjKpMm!N7$uFsLHwl0;;GtE7|b5b&L`_pEE-Zve%lYzAlOpL&bgLa-O|JP;pW!_O*;d%)tW0vDp8qBOg|(X^d-# zS=vNhN&yL4E7n>>jv*Zmhkbh$G+OmJN5!=_swTn0TUm$xShHBGWAUKz?RuS#C(E>L zn>8IGqBiTkRRr62*KI+m1UPhkjYTO{%3n-SK&=|%?jtHLr3|i1DY*ikrBaiawKXfs zeUFdlhyU~2k9Qr9jel7=TJ!g16#;78xH2GAX#k=C0H8nz{2S2RyKnnVv~D1rn*%y1 zx0I+66o?{ilpEN&Cfo{QRmwPH;~t6BG>j?bQp!gjE43{K&`gBAGL)+Vx|P^DOD#i5 z)+j;90<3flqMa3~VPA*Nu?R~8sku!Wlmclr*-826L0GfvG6(UZDl3#!mRaQ^7=c@OLjBa0Y zrw_H}B#ScI@EJpMgI&{o&nqjep=eNt;qZ3<=7uw9-j?gJJ4rFi8c?BAIvX&WXxq^6 zJ4S-0mT1fgb-V7@#>Pz#^Bzp6Q{5b?qrQ-jr|63z?w0#X_nx<5$)rNsfnh)KvHi*$5N4NNWoIaL-osn7; zK-D@+Q!O8)6*k+#KN7G(Te4`?mx={Ar1Vie=pKB!-fr`+e)+e{y!(AUU&fsJb-f=> zmRWB1_h~%(y)f&6wBoK-K?+qCR>(L)>ofehW($~|(Nd1Y0I<~pFRKjwL2=0SnV^jQ zlj8p`OaIZNOVh3AVQa1QWggSr+r92{$HEXG07(NzNFymi5gJLM2u<`a^#eE}86-qP zAOIem@0`!PcJJNYk13mWEe$p%HK|FBvNBiR*L6#(&rAAsPswmK_VBu|{>*LPZ8>lB zvysq}L$J!*imkP3m6`MBbN{=vU4iexysfo@+Ke5vO|^1P!ZjSn_c3nM{KNUtqI?j% zr@ZbfoQ`i(no4fR!~1*v&rm3Ib-UkB!(Rf~260({`F4IdY}-c4?<igFm9mC26Jf>ESf5 zTZ#>^Y88dE3-1N;x_s2Sax<+4hTV@@!`IWal*+&U^{=Pf-5J$t-j~%Ko=R1q^mbSLMQWJ)Iu6skS_J<3u{$Uj}1P zhc$1fw~uhYQG<}%6gpK4fHb14Kykx8m0@&ozbb-bc+64F`EB1piC3#M3X!VHZ%g`I z>s!F=9oL-7PDA(BD7EJo@P3(Yr_*7XlhM6GJulbe$!~ioc^U?-jk(X!>+!?+o7>Bk z`p<;H=sxBhDYH~keJ!eP>*=w-++LW$=}{0W#th5K4<`qb?ppaRt6KSKv~9mc(<4i5 z8!18XfI*NOu2P!d974#OGRBnhs+3B*wr(h?LIY=PKMvD6HR9SD!X?;ut!iVb334+& z(6W7W^xGn@YxzlQFzQj&lP2DlUtri2KN{Wd>19jH@%VnvQPg}khs*T2^S`p{H!;oY z{=bb~ugh29g_Nx8pF=81ecd;uiA#3Q33DseTB}i40kH^E)x>sWqw>|CKc{t_0VW&~lwnnQp8FXG<=c((4 zl+qV*#I|kPI6+9YZ2R6HA5j1r6DVg)EiJc-h#59OYy?HX7?V=+#+Opi675M4HYKH$ zQTeLOAJ&(DfV60SAp5K4W(R+&ORa(oLI4a100^Q08W@3r%!4+f=&~F!m4n1d0eKtRu0(TzVy&%vb=nnff~|+< zoZZ*Ev81(*1(1fg3#y3Dq*nXkxLj_*=~_1JcuxK>cI$i}!ZAe{hNtU&alxzjqd;?^ zU-m2(3e_bS?GC57rc(OxEI=_vYZP)5t}%CqC;iLoFN1sFR+XBoaA})w#0d4b<9|V7b8kk z%Hn)?-gA&mI{KK^Pj9!u6hT_5J#^)U8zPfeM>IQ;H)YnjR!OQy1c>+{$- zs(i^g_x@`T<9tnNNAM}Fhr?lAmLY_f>&yH1@0YkJrKmEY-BM!6J}9mAGAHjwE}E1m z&CHdEjPWIlB5S1I(`%FI@$nJCynO!HkI)}vx*sfjwWrI&8~yq@9S?7p?W56^x?dEZ zN^~GW)7UE2I@u7js;)d*HLcJ{>x*F~@x0>rxvM_Ff-0Y(T%5*6aER zaytyp=)=!He}8`Xm-qSS!)5+>GUwA* z*ZWVlFU+;pdOp4T^Y_0D;WP}xI^R;9-n>11+H=b#xPeP{2BlWQ-qMlHysWR*9i)!d zsgyrDqw;Q>LV=<0`|JFp^JtV`rf1`K58rV)bzO|Mb0Rb;b9`9`CHxKWo>z0%9{R4B&`_y|>YTWj#Hh^Fh4mmYtxWW#c zU8hsY&@8oKoohJspp7giiZ&UsS)sRZofb%?!r@nH@IV)v?>y~8$-7~D#YVXeLy7mLr zT7mnSCn(KpWsHe2D!@+rG_Tca&`LA{K+7ekghaLv+nk*s;a&?64N@x~`O_ zeJU9k6e3!Unp7!GDi;k3jR;C@+6Z8cdk&%BYB5F|rKf4qTDxFVEz7d>2Sb3ZNr`zJ z$7PF^n^J~>D!0@c6DnhKs?J(yobuWo#(ADKpmXjP;|7vM{$pDgRS!bnVt8)*vtkiI z!7pZP08oZnWhF2m9<`r=0XUbU5f46KX&n-=BtfT?CBep6$)Oq*^;#h*6wum2V?`8f zkjC(32nMj+C+8fu5<-aUMnq??Pyj(Vl`f36f`H-%#0tQLB*DXAIjFe8BVO)Rw>CaCOf3xQ;^7n6h`+C{N;eAOq=QfU` za3XA(c$zsJ!gc!d7_77|aaKOn=)fqeG{?-Ob_GURlVM7~7-N(&T6ZXBo2RbRDNo_> z)b>Q&_S!b3Sh*I(RISI-XW^Vy%cV0)fGV>HhelI18LL&RYnAw2IMHRp|A+t8!3M}R z7(I;rzRc%g;9QoneslVETEGQMX(2~*%#Oq9*he)g^>!S7l@sRni)4jVv?=TUdN>`qp)qEUi?_dlwh&%((}M5s%l&x7v}ZDd zVxk79n{q5vTFsIx^e!zOwtPB)#_s-_Sn}~9+_(D}o?0=jFkpImI=p^<*2-;k8HT5I zT}$1@jU$>(=Wm^lwS6SOmPf0P>pq>%{m#6n7o*vmZhCEp{;A|`JlJW@-XHfe zn^3oHKMvpJm=Gt`*2Ym=)z~v1j60N;LQm86=a&1+k z0rvb$%FDYqznQM_>EYXd{KMb9{b09k+vnl&{8!7eko#k8^7QbI8^#!=m0{>(+NQWD zw2qzz|8!e_uJ+U3=B|GqO85HuJPwZZr6Q0j-NmOJRY-B zrf%GYN6xkz#&x}C-cRaj%6|}_1v_WEoEC?+Y*yKEkCP9u?XxE3-6(8Q%Dz9w^=37M zp?|q9{c!4Sx6E@mEOBLFqYd?g-nK>9S~HSz&#yP5U8~d3HNhBbLmi&z{m=Iwu#@0A z%OhvG=U42T%VUA5G69@K(79OCs-&|HYBtWQ+w_w$#+o;K5*Juo$tjUYZMl?`(mtM! zK#=PUZ9p=lnlY{wn^LlUn=cY@K)!us;G)*&cF zRhLy;BTAQ6wI*oQAjNeh>xt+~@6etTB8JeXl&nd$!0Gg`?y=@8Qr+qTem^FG-C z%XVY^@c3~0{Q2e7pSI=R^*YC*Z~(N-thG)xYSXkz^ZMCXV0o%oQ>nopOUxon&Sufb zJ0fZX5h0@sQOUK&^ym+{Z57r=xiX}Lhw)7<*wwINiYjqUK}dnw0NHz&mziqiG5~qf zk!uqkbWjQInxZ%ReSW8XK>D~X_;AAiyMO1=XwZgq=L0tGwF^ouac`PNv>tsm8gdn`jkWf6c~)Ai)tVePW#CZZW8WQ@sr3i0ZSTUc?w4cttB)Ul3L$7S zXzE{Y_YeXLSRn15_kBGazTT1`Ip$jGg)v(2-u6nHt;C$O0p*RKxAlAuBISOqO84E7 z8|1Y1LO(h4u+JPq5KhiQjQQ98Sd;oa&0?3f=aT>(oiogxod6t;H0-+uNTAM7{5 zoYLCY48~8n{TTP=_1DTIBCz%$rX{z}0@~{j+gg419MfG<2Xam+y?x(L_iYH@a6`aW z%eJhye_?EmjyR=5UJfbvyPSL?G_B;I!49Fg8w%owtuEt=cWy zK5Ga$tzDNJjlj{FClsjoL#Z%?ucvhmWJ+CtZ1C?Z6X}%F8vLoG&)Uec-{a21@Lt<- z-Il|-%OAlH+r0iVj_*=DknJ12WcYc=Z58b2P`}`^)9k z2YWglK7IbV>vc?XAD(j5wSW%_Ams#|JK0b#_wNbM0$qdEb^Y~rCwF7=+hmWY@xFah zr2GE(>G@;d9rt!cG-EhjulKI&YK=x?ZLyT(%tK1WI@$OAFurLuHFz_{xJYY*cbrot+gfv>%Q@#%UGQI=^WI9Wb>EZG)+$@L5kW4g zDQp$2^)bdi7~z7T2M05CqhUhaK=7BrQ`tJ#7_*2b8At*A0eYsI{h8HCk(c^q%t##5(^j71L@YylYj3tV)?}2z z+EUJh%CL!*ZjI^|_2KZ{yd(qBI#pW)E?VncIVaKD5;0?K0$n)o%g$U>@I^F2HC4Ff z0$G$8qg!jK79S`@A#@0uh@>QIA@AGP^*R>^twBsQXXLwR8r6U1^3PkqDAU zopB*irEu5xbDCRg4~NI~zH4oBtI6uFa@{K%q-Eb#2pYowyn>V{dvc1)1|6duJ3@;TG$vwW*b@bs~)jmMuAVNV#f_%1Xl|B3z`2 z3;lz#<$iw&GFU$n1g_f#9}nlI({F3k%l6#$Z(BT6yv}Ph^t(^LY^b4`OXg2;lhfln zV-DAs)`!;EE8SJD+C}0~U=Z_s=zq6O_mrZ+opjS69MwA4^Rc_Wz8ZC~PG|gB>oCw|kj&h|1=icu%??!W>`s>04#?(l%*B-}h_%oZ`}VeO|>mN8*#cFYQm-K(2?p zIdXV<d3;yWgVV0&ZQCyokADr-uKSCzAiO%KQd$sSFL2Cl11YL0YprJWtm(!caUw+u0 zo}Qj>|64E*KB#H?`OUi%z-wCSwn3|8KbR$d9v!uPas3GUGWtW^mJk1Rjc!sqEUkQrnQucl)?Q3_dmiv=gsy z8#~>~yGPpVORgWCSD!wOZ+Utw! zREv6=%i&>AX8(vc6z!mvk_yc(zVTkU;6)^wx=BL+KUj+E#{KikWEN z)_d1?gSFcljnH6yo;Tn7S}U^Nw%G_FG1O`!!G$K@Pgh0x8@3(D_DO1O_WJ4 zYjs7G+WeOiQ&ilzFe9Qi7>p?_iWF$l)_f`bC56nQ+P&|4C}OQ$mKRhi zmSnsW85txsCZb%EQ$(5!QK?8EUDw4JA08fJD%zM5i?Xg%fm&m(g`+3qj8{q%ccpCF zA#+CQTUpVVRvJ4Sp=-$5T&nq8m0%qNzcnq!vI#OO)NnushE&>phXGYf!o4;-N3Rt! zE3zBRU3XVBEZ46Or$P6BoNoW&JpR?3yI2x)A4t+fTRTpzlAPP_9a&)eJ6 z;dZ+{ou1;F`th9P75PXgf-eLGYNsI}HOmO7d^djjd^?PflHp-|h+A5_U&`*Jn0If_ zpP&EncnZr>oq1Zf*bfe{q&#OJgdl9v67wcP^zS;9n<52++OB9;5 zk(S^#*TX|1bX$Xqo=64)rA(dXPvfCO#MgN_4sT*yk4Jd^^zu0V zDwi}I8^_m_FgMF+RR~*dO7*3YE2%r)5mMO?ScfK)(t8Mginf;fVGJ6zG|CaJ8PSf;o_-=m2VbG=iq3i1H zx_8GnX)VFqzE{`li&ECRu`%dyyiL(K5?pf4-W-%t%knZ>YaOohBqCimg3!8eq*}|z zz7NKvR9}~=9Kx@Kjdf7qr9?RP56gNB0gY4F`({nA#DN%LMYc7W00{Z0=mBLd9Ao)x z!&148-M6nCOU`3BulvVFyCUz+ z`(??fqVH->nA^qhv$;Dph=vouO0c@$LHhY0WHV%_SNvXe!R^O)ROLepY|I= zR2cTe&IL3J)ZQ!C%BwM^=AH=~-bo2F#~6>}q1KjSlZJiY7rf+X565@wc4OvoI8ECN z>8&PhpwlWKnk}YUiw#NHwXIJChw(JLT>k;74BZDQRLY!kcY4%&{-u^XVX*pG>K;1$ zc-^e+VwqeB*K1Z*lW9tsY441MCV%3VO#7{ZAnF+;3lB)uIG(1<@qC(>TE%NU)cuFh z>6`@cfS}Oq+8|fcVYS-DoTojqvlS$l*oRK82@O(kK$7ylw_KGJ1#i^3)>=wY8i^q1 zR9ZDgq4%yqPGyA}Lhyxa%?lOPOwGwTqllcbMAUN8+Qt%zv=ZF_nHdxmE({!+5D}0T zMosr%>pP;!ia*F$Qv|? zjMmb)v&H~HEk={n)~T*mCZki%=ks}sQ3!L+gYl)6T5ITB+PA?6gJ6l4&1jOYcel&u zufBfw`SWMe1_;RpF04Rm%?!;ceXwV&lu*UIT1Uj}$mLkjQh_pJOPLxQBGRTYZ!H&N zELgSdOsWU2y_Z@yM7Jd{oym34rU_I6(4FS{>uFTW@_irj&)FIx zt27D_8Wxob2pMSEg?hbSANp^$WkNlp*IPD~)PwR$+HPgdTZg)}Ry3XnW5&|-Qg$7% zN>60i_UR9-puJ~!_0z+Hl-Zeb3&A=DMqRWW3P0=62bDj&z*&py; z@c(L_oOYVD(i#A$6$gCs?y}bp?oAc~iVsl8soO?R>O&+T)wpJB2-@PEg5pq)nf~Cr zzJYFxC9%`tOAL>CJKZjpaOnDdjaqBwsw^7!xUI?B4gf@&(wxf@(>$2pA(eHzjYDXy z2vBcE-N25pEP1`zwx;f`c?1%BRtj|7JtIwgWIi19m2Di5J`7p*b zGxyxGFdAc+c(0MP_cTaS(!H+hGwJBs`~-BDS3~GRbGUR)kk56V1U}5uE(>xEZ1QmLd4po ztp%rAEv-_uO4z2Yb%Ro7PkBOADT33sacr$fBduKLoIs1)T0?I8|%0&Y-pcT*&K5~pEVCch-KmGjp_$b2ro`W57JFxnF&<|Ywyno-hw<%ldekgcG zn6x5DLABrvOG`u=)YEkT{Ne3szHip_gc_-)I%9W^Y3e$vc?A}Qu57wp+!yA(Oq}q1eu#0cu((iTTD$%ncM%{ZFqm$dR%1uwTx+?M#gZ>=ah)z1_9)DH z7+vMHC4)+X0rpPbtRXxtJA}bg z(+G>Ea(}sBn!sd7AG_)hqF-rmnMnOmix?T<61~pP+0r z4M)4IQ>$lXc`u&`LzY6so%mbfdgOUnwWlu~oL7;&NdahmQZ+6UQ_fFu+U0K!gG zm72YQRIETIKmi~S0mX1210qqFjka8~uyRI3a;&4arbTXLo*$6;L8 zsK_&~@FDs}Ap`FicVYr@KTU7_#}&`2x5AmD^G2ob;5;51h+< zyFWY*OzG#B@85oSXf>xDtm#|Io_Y`$nG>W{mlJEwd3Utea!{e>w#CCAQLN>KxD3`r^c zKhq;Q<${$|bS|B;%2@4PA4jLa5F;1?Z^#*{B&nK-fFV(5ACaoE?RsZxf34}2xk^jU znpPQ=2GPcKb-oBTu&r_pejp4QyBc9FKUizWZcKaA)ZeCif3%G^!k&b=aAyLD+O`#J zv4Mv6uwf28rRzmIzW{amkUp*E&E3fFQu{d!;pL+|JUs0wR=$nHONk@Nab36L@$B9B z^71ksY?B$dYjrw3gmnoqTUPs8?%$5@UtWGR+Ie?gm+f#2rEx2I9AMdBR8Xa;QW`=& zA6(jFYez1uRq`V*brl+OVPj~ESFJUQ2kvv8S=M)N{^I)Co{m4&)S=ClZ#Da%4@he% zu{Xc5V_f6T^{BCHp)4 z=U)UKPvK#ozvvo~rNrBE_Tyc1r%$!SHU7-N$KmVV4srWj+p5Te7-#zilJcr_1#F_U zY+oBIRes#lJ{%9%>9f{@(u0Chm*v#IAvtEQ?odD7{^1S1@4C086mqK}b$#VNbocq^ zr?0>M{AnJ>H;A}y7vXj2daK=f{mGaEOHf9!tRm5=9ylbiRW`=eSlsvBssY&{t43F2 zl~QVtG5Ax?l_4|ELdH9XTUz&c9Kh1eft7 zuh;LJoWeoox#bML&!k;z;WB@}W;&nG%QA_GwH6vI%X&Va4H1+A%mv+!J~bhUT9Q2u z%k^#$oY6HGAR=v3$$JYAobCC|yv@^ge>{(M+d30s%+~nQqXvoV zHiRyvR8X4MdoE+844}Q=_hf;dj*m5`Qc5f@h>BPR*OkoH`qF9>NwQk&<6e%(^ZkDJ z-Unxxc~40c6aog}%8peBHYj&5_l5wPKq~;ECYs3rJ@XWd4sW#a>_ar3%I;1uV<2V#}u z-Sqw58YzPUP>Zo@jjPuk)OB=5fhkp6*X6V+$_cRvD`QJ0M_q6IdcF3Yo>LmSpoodv zx~{ex8)MA%IQE=l$y-16Kx*AGfu^SX&?o+^-FCgjpuQp=#)0PLXH>KC=RN5?HtkFo zsK#YmXVZPFX{~8KbdUSKtH75f3LHS4(n{52j7zByDgcN%5utPLURKwX_5Cj3NSoAv@HH>vp=__v zcc(@v*I8HBcjwD8J-k1^JYRxywXm@YSeUCw!~c^xTV_vMS|zQ#b-JMu9LsWoM%okrWFfv&afo`xEeQmPU#p)dz+MTC8f%R#sv zh9kGDaa^}!lcnXp^34ZL=#*y02}H%E3xm}gar^*4(|t!UU006d>Hc!a=09sq0=8sAiq7Y>ilm1cHf{B~M*tmcnCCkMx0m$6 z_N{c>l(H?B=Tc`IlJ6Mm@78HGp=LHfmeepZn425a#=zd^ojY zW0Jlf_muaw9>&LO{)acm^LpD3r-vz~uJbP+KOTeooUh03TyyI>UmGHHL4|p`h3-HE zm3M(8keRE}z|2|IV8*zV;~4hsZcSf{LSlegQzfzI)4M(8=i6t0&|tVvUO*#agFU76 z_VJr}o~^;cf?%|>F)lH!y${g5RAZig06vsFrc||QrA?hTHJ8p{++dygSHJqz_4ZjC zxlh-jKjclEbKDeZUwKiwrev@^GQAXd0VTpmVWUkE5haTe&A}h^mCKA=ezeB5fPlT z$LSusF~7!J1Z~i&n)W4D?H67S-3gFu&DPp4wTA1ST5AvETWAaOTCinu%wCJOq9eTk zH)*vqPHSC-W6Gd(mya7}*Q5IcEU_QLa+^BubFJDM;bSdHA=j2z zGH3y<8Z^BZ)A_`plxbU$Qv1HoIU1`j0#sY=ig09%RaI3DA&T5Uuy*P^v9R_19@%2;oZIOj}g zM3gi}stm0NvREKwOhsY^r-&>nQ-zuLR%Wal)l<>R8b+8~om;(0o?6}j2tXN~D}&Ol zR#X6ut)gbaX>gpAG$@s{*1eSpcsoE`HwX-bX-JuroHf;N3HIku{c3|nO zO)(c>QpScZm*$D`Hal;%)||zuE-m@^?&$;LM5s3&80&oLCMCMxRm#Hn3+WNk4ttmzGwi;u01%N6vC7Fvxh6cHg=IJIs`woYE zN)c8)E6se*)b}YB?|o|x5o^s_lhH~SDjn!StAda;RE-)aGzEIAMWDGxqo8fvfQnWH ziZb>?%*poKwp}cpR1MIo)F;rI6ja_wF;Y~x$Fe=bZyLaTE6KJj7k5U7ThP314XH5a zp?lMc*HCj_s=NrF;Y-ttQYffMG0MJWeXNh;ah~?*wC@9MTikBrhtszHs?ra}q zAgx>PM(3t%_kf`!1qeCuy3+A@sBOve<2tL}U@ghox7d>6_$K_5+wxQB>dqHwVzi^C zN&`%Tp}$ne?dAI4pC7(KGyd_nvl`u3aC=a)aP87!Tpsy=i^ajT?E zPUYJBLs|}ypNI;!Q`)N^lkp1b(OAAtzi78A^>~{<4nbSf3wO0;aCvL{J9)b+?@~ecGW8MCwra#EG}$iLAR#FSjT+@<6Uv-x^_2#K*B)regC^B2LqEe&`N; z-RB8Pr|r=9Lz3C`pHUu=oN-%Ox#aZFdXK*c0j zl@7uN02o*+k49-6S}iC^sSMZm@dCY2Ha5LKx8 zd_37@d6s5_|2Cy0!b&Luv2@HTG8Sk@QnU5b;GXVT6b3~`Tbs)URM-3u>pG3YH_|Ye zg9yixYm0MXCRpn}PkHRMs5-N*T_21-CP&7Unk!eXt+l2#f>Ef{W&7kv8EcV@M%|F5 zZMjC2QENR-7jSwUN2ArWPS*I(uu`XsDoLUfr)nlPqiKLPr`&ai`(^67!=9rXx@lVj z{*Y@voz7+56C!kuZs#a+YM3mCX=!R0cHfat`7C^*?uyL{0FEMvkb8BCW z+uSOMf8UYq`_PqZ+j~AbHzL=$TNUhyVx+2%&BwLG=}HF|Rov zhS1qAHmdss3O8lk*bQmV2yh(6X`f?F2mc;ueVu z?WI0gQ-&pj{|&^*L(e?kt~s|8-2z;1c?< z$Jtm)J1BURCS;~AU|frazf@L%i`V6sRH{)Y!eFi4$}gik?c)Djvuh)rmh7OsRQ#-5De{kkB8x1 zgM7LF;V}BV3DI|2Lz9iLemRa1>71*#f3=stI~=;YdS{7rO8a7+1Q?z#)806Yha%;8 zJg)NsETcX*H6zispzk}784;J8xbE4g!?Mun_%7~w9Q3-r4!wzscyB|ebD4$9bl*q+ z4rSQQj}`_60XU;6bDkN!^-s4?&y9ZW27xvLce$+Id#;Z3+~R|^=lk~4hi^~!`?RNK zEK*Z7`LR2q+V*s*O>2EH#-7;r?RBesA&DNmkXpaP2KT65NfM1>Kf6#HNhe z_h^h$wcEY|wcM)n12^0xD+Cgt1_IOwGSnnZCrKK1;*5eUC`1APaL2UN zd~du0Gz272NW_vwNGYcbw)(IB#lL)h`J}Z%t>Sj`y8G_KckiCwegEUnMzh8WE&hw& z{nyL7zuc}LFE3L`|MB;Kk5)Bi(E(beQWQvVu8BnETpMdZ0ZZtROlyH@B=59|T)j5L z%@Y{}Ey^g%%(kR)cxY9GcVNV(y>)~PoUQf3nAS*u!_cvTy(LOYDdmD+;*J`&X?lM+ ztK6~!6akST^!It!#>BQ)$-Pn1AlRW6R?4(i$YRX9c28yBzJ32x_H~adfm*NkuOHt{ z?IL_ocv!Yyy7PWtenjasK3WQ{3-f%h5>KZ?YdGI?7mhPW0|(1mV@!EzVhbD4%8+6D zlA%tz)n3*(|l9Yzl8vY-< z1A}N%SlN>vZQuqX*hz8N8|j6$5L%6fgt!cfq{ z((IJ;mG4AkWF!L$)E6casg=9}mfE7Fx2fG+ms1K*pZX)-=U)_#T|d6wKX#*Cr+u&w zUaaBV@(ljX;Y2_F{L|y(uWPNTUi!YjUs~6%B?D5oZ)~kah+PLUURBo{9riV)RIJsV z^CI&4`ug_mfdKEXTj$@^>TR!H_dls_P8*i(DY!RH?jma`Ed;}`boRJyTdTI~ABO{e zeEmsChwR08nIFgDsa3LMoSae;vlR3Qp7V$q%z`?;`#k>CvfwIqLe~V=1LI>3mN@_jRqDZKrbEGdBU93Jl}1wzaJWc(NVN#n=Gb^6?zrPg5R- zj*&|%#;TZeYc2Gh#I8YONwr0S=Ey4Y3eB9a&beG-*L8bJ%IJC9jkS-;PTbbL9L{DD_3>R7!aWdM`PG6eIydO&Qh}U9Yyi)tzfY5kvyf z2nL~6?Yc0niw-Vxi!pXxmvcrf!T^k^?#7!YwTeo)HPs9#VnUV%tr8@W)OeT52#6>m zjbVp2VNJ+8N?~b@S-!mea`XWp{_3lDd5?ejo8OeQBg#C_TI+GZub#evLbV_wK0Q6o z*Oy>)b#yqL|MdNz`|7MjD+0P zZBK75yyddyBnXb-Zo{T1Kz&1SXT;Q?F_2bY(tpM(p}@5|E3GDG2Gpf0Yr}2(96~_E zm{KXtITu2hrscsO+aAeMq^5+`meJTfJlYLP_5&TiVO_$L>^@Cn532YpWEuq?)UV9v+Xr zYnSPz32HG)DVCaZ1Qu(3Yt32@ZDLY3^!rk!n)Bh|KJSbaI(55E*sVIz2r+ZtGkcb-@#nQ6|MwS*-EOXwaAYrNz|^$D>f1w%1hhFrHZg>9uhrblZM+ zwhAkhBMNIHabM4;r)Aqy+O!jEO%Lzx%b%SmltB=+sq4DM=pIgo7&n1B3`0p-SPLs- zeCRKi=c3K2@D6GMOvnEHwkWW)Z&UE3H1F#+jK`b{0BPIBy;*B>xdSENxfF|tAoyIG zcHVk(y(}b0t)#-|fx0ag<405~w|(EPL|fyP+jrVD)A63~G4HjRR_)>N7Ux?H{@(7M zFjP8p=8|vR_ipzo@7fuWmTUPE#aj2hqpqPAWi+9J2DQ}IDw9&0nM*4nxLC4tA(vWf z^}bu$PO2W2l3b@!dULP|mp*NA@B6OA)LTE_?!NDfFzP}?^SpOq$Vn7=t$j|_*#)%D zrCDpCF_)^WnzrTGkJ~;Ifih&2&6x=TH;k}*Z;_?8?3|mXX&jE+T1se*(!h-qfPldy zEtN8u04%D~moD8x%mzWD&$fb8Y)P6RaBT{SnF}>$klM%)Dv7P^xhdldM?@`*%-pbK zl)@@AfPo}!ZzZ*sxE6o}oDq;rYYhPuAQLhnSdCWytH1oq&!2t)kl^jU%n#@DS6_Yg z$3OnD*Tqx!>u-K%j4LfaJe+?2pZ@;auYaBObh+I2eRnoIKfiwW>)-s{-~Ijb^K<7P zjgneotNHy`A6QhX^7!V%|M!3Sw_nx)6xKvv*lSIigq^jT#i6ny#1NcA0S8|Aj*WZs z=Fs&DwwzMS;r-Fy*I60YTGN0==~{Dt9LuBy?vD@t`E>>v6kme~kr8LxB4$jTDdp2_Jf7O!P;#HfA z3pE$2*@}t^LeR%$i6NLJ-mTY+Z1@TE)JR%AG9%$iT&Z@qm|Q5KhZLEc0W8|In3kY@ zWvW0dN*SGVCTvP!Vic~OH+kQf!CAk@s*Ih|Y^`lvh=>K9b3}SwcCY+hcI`>WY_!_A zqV|-S6$UA(?YlO>;n0kd+Im%U^u8-8fAPR*QUHu3_+O##tm$2-Io4d9(iV*~7Nt7q zEC&=qD&Bx2t(n$#8~b;ySZ7Qtubt>T_Cq?~V*h<7D+wDTQjOg-Q$ptqX@*RE3YEl7U}ixuRdez_x+) zrnYO%Rkf4t+rEU9iwRRKH)Gy#J60x(n^ZF%H0zgn-w@sy8O{ec&$H5iC?r;Etu)7c z?>wbe=R1$blQ1Eeb^93w2mROBa;z(LGud_BTC0x;J{Kqg0O-9bHO254KxvFme+TUmqe zSt=1>^<6kOxubbquH*Ue0VLIWX%!Wo85wBO?c5m8r^i&5Y5oMd0e8W^Yp^30rK|HW zFIN&!%4y|N{zz~Z9*kEaSAm*Vjpf+7j_camVhz>G-u1bN2r3A!zs3BTQ$KWX7}jOK z1V5yLT81hwO4q!dfpnvbvc__&T=p78QLAXp2+a~38kSPZzVEHI5CRb{%hFoYZm<@l zz@7`Jtc=rWw)J*;d_PT-Hq=^etpQ*NVO!(lp)I?KEf6|~O=K4=;)vI1tyW4ksq4B5 zDd$}9eSNMn=t{+FD#DkNmG-vKU|lNPnwHa{OUoAgIHe5AvT9t5g6527DNU*%RAz@K z0f;4c&Rd{Xv$p!aO=yk?&1)!eAtFJeR9Lsvb()!Tjtp`*9Jm29)LLVmy*8Q@Gyp&$ zTLfcmwkwKBizXpvPBld+OmfnPCGI-7XNp6S)^PzMHUbM0R@DBf{KL&7ywA4F(wRQ7={k*mNr2?Kc4%(AG-d> zKmB=Iwtw@_{#r6D)B4ZQ)!ymu@{ii<%Z4bfMEv~mI*wA%djOqLJ?nq;+ zp0q==d_$uim`8yePo;Pj6m7zVv->IwcXLf-@%7 z#d}vXP}Sq1+sazGh|(y!Grn$nAKc6J#W`oKC8AQABEt=Ht_Fug=a%&s zQcS6|T$~9(jVZF$)>tTYD+T+mgI2GX=QnT9sc~bqXAzwYVK5d75bDIaYA&rxu9^O z*cCNl0@Yk=1Xzs%WqEI>!QV+&j<{byT$H0XFyQspGzk%r0-aMb#&D@bfd5qWgWyS- z!YymD*7|~>VK!BZwW2MFQcVMU>>Y-P9>G&(} zY^U?MWyHp(u3PW7u6MWTB^-T$Vt}*?>(s{n?Y?E}bm0}LQuU!zjbkZADuEbhJRm=( z&%YYq*Mdpgd0XH3zNHO=o)_iaU`(nd5544sS{_~h!|S!TzA1(Cyq->{d7cSItKR1N z=L)OyB$W3p9s9q?NrAcqJWW$Ry?q<&yr&IOS%W3tLhwued5`x)_iKW8vE=*qC*S8T zKQ8O_csy?r#aPn0X+~M*3PN}VN z;#_h*lRH+LuJfnU_^42Fc@+Sm&ijX)z=1|ih)HNKdkOyi^?tKsA=OG&Ewi(=Gw0Ss zX`52A-fjC-YwCIn(V?Z={r>jt`?RNZUWRipUaw0O_6j?oUD-9}t2IMODzI)UEEPyq z;>l@A6;Y3cW3LYTbQ^{`Eqm8}=gdi|vc%{-0VjbXjk%E~U$Wq^Z3~axw_CiBex`P) zYd4O~+b~Vb>2$bUKlKOm`TF|w?wgeGtf=#EFY^`57GpH(JoE>~X?cBp^Y%1v^R|}5 z@UCUymTK9O74JHQ?ta@sI2EX_Q!lrl&vuE08hY~mx~_z^*D6?Z-R_+o6!~@ChdziD z5Lb(4m=)5oe|UX<`S4*}*4JFD!4Jl7)4XCiu+hII!Ya!)oW#r~;x5il;v8LZQ1pufbw{th# z_w{D5v)(u1*ZWQP1tMwR-IuJ5E!-YX$91{1T9%YT2(>boYOF#85zz`!hkN`?_C)NR z+Ln7hp1-^9zYro3Axh4<>$(yPA|j|>?Q@Zg+B;*3OUjvvRT#JdKq*NOx~>C&6iaKZ zswF%=eSW?l_0U>Fk2UWntvx;#C|kNYuWq-S>yL>Qx*qkuP4hAIoEniX0-}`mfwy_D zx$8PEm1EQj7@F(ctv(>9!HDDqQDUjuhRhfUlv1T6RFp~{hvV%w_2bc%oLbF_!6+7i z+MH0({&|0`fRz*}M?^~40vLp?N~{^QT2t(YkoMSX+j3fkyKdO#O>0UV?a*DOwE{(x zis-fR#wkHA1T0#bU0?$Q03alVnn4;M0232K1BIY~KrF!7g@=CJ*sgg)uMQ8#l+tM! z|M>eq{`0^6XaDA({*yodl$arr$nAh#zKR)EttafGH2Wu;&Tp}VIkH;8eEeN9e{#fuiuc=o@dEGhVa!h>) zaoYg^|EUf}(1S9hzL;VFm;^}tpmlF-X?ZKjkUiS21LYLvm-pANp2PXDwR@ZuROijURclYJ73V>lVYw^7+u*+vknIvIaP z)zGcDnd9MC>$={Smp5mccLT+!Z zPVv*b@Ew$}mt8u1onF2=AJXm9!MzntsUnlgauK*4`^VRh*$%z7KIhDU3Q1Z~NXSwo z1LoTH)5EzWiW{~{A@thF9%n@EJDazaK#>rEzI6->IH{GH-MHq@A07hSZB`elN0iYy z^M#u2x^AB5M{`Ott>tE%8;3XRQb4Jt#FKlc%^KrHLojN%FLQ3&IQA#hYhuxUiW?f1 zvKbOfc-!AO560zOZ`(c*X-z#h6`<_Np*gZahLzj)csSo*76_ZwBVbMW0#%Ov*ZZEe zs%yMIoZpowLpVuekp`S!FQ5B&AAs<3xflz_gS+28_n|APl%@`SH^1E9p5NyJP1ua) zx_s{2KuT%nQc6RAsMzKbuW|WcyW4FFp=&K`k9*p3E!OuS3+Yg+u1p{bfTR_q>dhgg z6|jl4Qi{{T>rhJjT>iv-J{;ap@pI@YPDPsZ9fImh-hmTRBO(TkWPq$MEv3CmRYvF1 zhT|#amP*U2vMNW5`8vDd++rJynR#D8tQsWu*sM0xWUlf`He37kyiF|88j)IhW%HKC zaGWa|W0X?sIxD3BSZiHsBCSa4X09x|M(B{^X>oj|NOuFyZ`WSkB{G#y8PY0|4(n-KO4IFzWn*8@8{FQ zBcVAR$1udp>;A8Q{V(30?d{Y2haY}c59*%$>%IK?FUI@&(?9(2r_1g7-~6lp=H-XK z|L*IjeSiFikN@FzU3nz|yHcx?BsE2PsysS#O7PbEdz`#=C({v27Eq{_fCUQLdTjt8 zx5~`k_|{sj)fsyTeOfn%YFk$Oa6~Xzsg`{l^tMmjz}ewew%`Ja4%(wMuEjW=@|FNp zWrOO_51%ie-@ku5&v&QPyeC5EY$!DfMy&xF_1)pMuKB(Nw8MCeQd>)9ncw$gQU=wT z0dKcIc@G*w1H%eh-JCIH+Yg6BDK#}Nh)wX=xn)_b(h6zcci#mv`f|DG!EP<1(ZIr~ zj$tUNDs86{2lNQ;UbmrBuug;k&JY<EA zj#2&BnV?gKNAIJ~C+SFZ;f0ItE|YSqYlZ9i&yOYAvY&E_)pYDUHEr8I2oC*KdwnV8eIMXhz;Dbgt} zoi>xzD(-!%YB<65^2axa(^fWTzk9jtPhb1zm!FUQ*HBGs$=gXQzo#mU003O9L8EY~ zU#PeQVy0U>oz7aTb(>KgOUfaHw6D$rx3%wS+o$v}*e$3uSi9}?Q_IH_{ElMI)>QeV z>}pL|=Hh)<3zkIZ12$v^EU_Jj$C9F`eTpxK{+!ZhB}cD*%d%tmk+CheelR8$(0)w0 zpr)E5*G8aNJ-fguZz!ErZ|12yK6RJ-pPjKz4J|9uDsifYi^YO+y1fmd0aUI1w#?ea zmdokzz8JeLD@*29ODVl~wbtYDxHg#Pl}LMU@3$9cj5G#DbKEq966M&3Wxb8Zr)4p% zja`3IC zxb2)vtHb#yjJN6joqe0iwv?S!VO^bbdDDi@R#k!0$`(3ZTV|j=W+Cd7$vG2Y2=0s9 zs21_wU-r*q?y4WQwmw?8m1T9IOFqd?fSN#SjUu@wma43>!^8CQ+7S`Au{&MYt&&9> z3~vGzsR+~%+&t~f82q@!;=SKfw?!04Z(_XZ96`Rzi zk&s0MwX+#2GzMfM1mG&QNd=Uk4qvd~Qc6D#RhXG;tvwxyK%h2G4fu=YKx@rRN-1sv zq>6wbtZCa)d<ZVY`=fhlb9?Qozue&2SiBUSA7910kgV zpta^ZmvjEBzxu1L>+bjaFP}ff7`e5F)6pm!V=gHJv-dU(!CL#AX3Y?kJ^TKAIDGT| z&CBbjpMU=TU;pp_>tDV7+fE;TdHKWn?eHJ}(;uW%&M$6!^FRF$zwZy_{fEb3y4&Zi z*Kfc6TK%)X`OE+I|M&mt-~48I_BL%!HmOYy@Z;-z{Vd=8>UchO|M2%8|MkE5Cx86o zA7k`+nasQ6kDp!!7=HNWK08>`ANRkP@srNnDf3N@$oob*7+3r zeOaNg>q3m1)e5-HXotR)GLQB|`CL{!^pwl%M2!KQ4LMgMVh~D=h{8Oe6C}N@rE`F4 z--`$z990p?IU=#vx<8Ebwyx`XK0eO($r#i7kW#9`#+cG-u70o4IILwqb-v`S8^V_2 z-kjl7N@71i2@m=xvL>~1tEmcE8{>8y`m}A@1{MW?L}}Sp=T6w9$fB*OQHfClw-xub zv?qNAYggK6oA58Jw?Gb*5*86eREP@U$bRF*gkGColou_iBDWyYc_*cMV}o@H@P5A^ z4hJgAn!zf|wQ);`VwEQ}>|VGjVOuh4G}?e*^j<4XC4Z4n*6E&3td#2+G&hXf8VrFnG`5N&%(=`8PYS!e{A3+!I>Z=BZ`j@|98s<7 z{)vdJ)1(uxCn6LQfx;LW_9lX0$MKw!>}@V`L9o602CCY(&pssMzm0{IozwO_xYKRg zPETXIZ6c})34-;q%$H;T(6ZT!cFqWabp!x;j|#O?=9>The12T!gyNBnv8uG$>PFtq zQ?5yUc=MZe-GNi(`FQGXm+bT;u?yWdH~xrz(}G&0qtWQ6`+a_VW9Q}a|EKFuf3?lB z^FC~?wa0sUhI7t)-ubP$x~rggatyeCGIi9u6Iw#~prIeVtZq^|dOj{%;wcYQtwRUv( zjFk%O9P{i#Ca4!nA6+al=OhGb>OvWcHP55a;!oRD zw3Jc`;1naLY?KZmND`x+iAWYOtc5ZmIw(O_(nUzo3js=F$}#u|BuHwrN4u;i4t<<6 zLt8CGlGAS1UCaqeCL+~!-EMcDd2ZWka+wj3h?I1}BjQC!nzUvZ2894FcG5IWvzrEw z#j;{a`Col}N+}T;w4?|*1?DUjB67$saUp^(E`<;|=h?fi>p}=jsFW%wbIyo4mr$@U zGZEyRb=}37f={z|s3j5=!6C)jd#M!&=}Pj5AIG&agM+M0tJW?lK=t0$CoZY zJh^9-NhuZ1%hmF1yF0#oiP(MUg^%32b!)rXf8{G*`QWq9eCbPnbnV(PWt39hy?a+F zb+|q_I#}Jmd$+0W`Psv+F)w`hW7C+H>(!g@y>Wc~xLsC8W+?OJ)wAPZ677e1y8YG( zmwukyoqKl=u7IENs%~yxw)?xgpZw8JfB(+uG>xlEhxZ=7DI|C5#^!GSfhU&VeB~_e z;un7S`B%RG_N@=xK$E`yrMK3t{@~*B6t`#NeZ5-Uc`)34uwSm${XE_GV_TW4%T>I8 zj=-^23AqAMXUREhpo)|c5(*=t5JCc`l(di+=c+~BOv6}Pt0QY|nK_kGASUIbT-EyBhHzvvY6;svxX#URG(y zMpIo6+g%4z17w-|^JQ&be;26ax2~?Rv5F!=#i-h=UJmVQG{xQ?kX)4-P7Uj9ZshRh59I5SylP^H5to z`v^iPRoUn^ac0pivg-4mn@SF%vhuz=SxDK+`t~rirdca|Z@h;$g*J#4pkM$fM5O;! zR3!Oin1s>^7*VvSYF3dL3LBC+g>|<`K9n4-wL(YY8nZ!8lAM%4Y`NGnb!}@4Oj4&1 z`>K}H+zTN}Y^9W&Gk>tU>f*VvlM+im9N5nLp4VOD;saFDxk`W(!j*iYbjnF%SsP&0q0_9J6Xd2G9y6C$_X zD`cDHJw$eMZkq!i&l8_3X>(|)m;za*AlhL{8rqwrCYsN{Sb1eU^Wd$ix?s%aC1Mog z`LR6b5_VQ4r^ebIPh&(ZA>Eu8pbIuiMjtLn;3bHbD?o;GNz25JKx~@seltoMo z_*x08x?1k02a9gKJIAVOm^OPiXl;$DN-QNLNL~vd%!LJGnG#j?vCk>fUTB;~-^qqK z`!H6O_1-I`Ldsg(RHD{8rPTMmlA^Ay_de%1q2h|<=vr<9)v5r6Ei4&f;angRgffJX zF$o13GP$=^U(CKnv&p5?2RaI585ZD4MUmkkzmDaklF8ESPKaOqN zE(R=>*o3|8tWqlb*pQxfqwT7gQ%+2XQp&>YFeL$XmLWLgqP0e37enSkQm2S@Z9@o3 ziG+1#=H?H+_ueDdZs{_u>g{Jf@*^+a4wt(OOzr~MU!h=dK)$yBeo%yC&Tv`qfPeLl+ zc=NSK4v#MDHqPNJ&F8-Fq>(m0DwTk9GF=OK}1-%scOi@8YBJ4 z*-}weZRWTW0#lZV)l%>rd;)aYOG(x!%uygml$eV~2?ZIUswy{)wKhuK===AZws;rA zM0M3B97y%-sw@z+P9bSP%n1}`1S*`MDu6<{6ucJg?42~Z!sQ%hkeI2Yl%O!D+*Wn( zy;2Ht2_X_nYipquBE~%DoH4U1LZkArkgd1Qk@Y4QW%VvbE~1K9S+$M*QklU8kcycJ zQqBpKa2Iq)zcb6WpSUU%aSnnyS|vVT*n`n!AQt3&ocq}yEiU6F6b(s>C`$qWMM9Gh zC{X}}WI>QnfioBEgl<3z+j{3}V@r;e)KrQjp@cxWgJdabiCL+rh`7j-jkZ3e5t7Y3PQEmN{Z6#dT+TD=cC<Dh3w#+$aB_{?QW)090T4lxThH>l{EeIWalKmK)u4ti&bxNmuXSG$v z7*1KE>idx-nx@Xfx?b*bWXqXqqBO@6XF>VF&katFI+4!qUT&dtK9MvM$o-^Lk0n?+Pa$ zS!uSFh+b#k1?@{L1{DD_WFWjqcV2YPC-0o=x=X{9>)Ot7L(+Aj4KPi4RJxu=ytF!Y z{aIJzz|pFs95Lr4q?u-{+bZKECZD2^YN=J@;=a)77~XHX4D&4?CL@J0RmnIGzFSn& z+(Tils+hpG*11WeqM}m3rlpt@BbAg&%578c$Gz>U7;{d5OhSlULfducyB(nh9)&C^ zu~Dm-5g^SzNKx(SOiEqZcIqch7^bPQLLdyN5GnYfZ6VFU^14=S@MADzVPs|yGAAen z5QI{iqlhIlV=hHkx^VJw(psf*a)1X@v{{k}W2wt|_6^qtDCa^TxL_(-Xn_Kpn?Y(q zmXM%CrLpgK^|Fg4Gne4AAR9tKM5WMHVw|=q*UU^rIp>g5)3q03=H?)|9JH+)N0+0l ztC_j#7U0H^GZ&P9S_429oUk;cLI}v9wLugiaw;f=#ykL&5PeryAzUN`gL8pwi6OOZ zJ55gMdU8?M-8lJ3j+sem7XwQg!jv%2F@SBy9M00b%Q49d%sUv8*4D$cuT16r+%CF# zo?T2`y9{~%nNNTE^_O3M>WRmEu`>l7aZKP7AKvkLTb{mMT zwU@6RfB*ZhKl%70+tZUL9(nZ6?f3N|FPr0O!;9+DrS}Jbs6~Ts+;_9cRS1* zy-2Iw_O1OmK;681dg8|ZMs?tGm@1k^r%7n>rw%O4MGL957LWzt1+UCVm1DBT#276Y zK~Yia>Vq^Pvh-nKa0m54;sR0t;eOa5K^%+AWrr&-rA%%H9DIR(xLN}{M_ z9erSvN(NvSBoW0lO(ABbl&%iEpBpRLI|*E{1S%dfWN4*AZK)GKXry{bARIF(rdoCRsq#>+H7c>S#Ckgt;{+4V({Sg$*u%YAI^~S&9Ib zLMV__xFY2^Wl0v4iL97`FhW}BLrz&rvP#F%1TCuG*`RE_>Q!R|FD?;vyr~08$CcY2VaU%2S>@ zV+F_u05Av7Dyp!uS}3LVdzm2`6BMqCP(no)-sMnNRXzI$oZH2E)$a#Rv$f!(lcr&= zH7qDqrM}zmq7X_O%n2%U6x?X41U#bjAv=PqADt2e#aMm7nKr|@v<8GOoS<-Hq;s35 zyRsWL^E?uW!^I&p`x0E5O}^Bva*QQ7L@@)F=t*gu=AG8Zrl|tlo5L=-#;Plos*`;% z4^v55fZ$>Qx&$z~{>J+9c6cx4sPzF66+U5}Axmb%c%bVB#QE<0j+N_6hmV}?worPl zOfE?)^e$F*kz)Y>yF4$+%p)PxM3iz5asnBndn&}fZaKu6NX}DQEsy$skY>vNYAvFi!}JlLy6C8s&q!iB{P-g@P4R<>q9q zCc$$Ia|)}P&Nf5SbOqu(``R`DDaF05uO-i%Gjb^$wN*YBsZHY#g%HEI(RE_%x4YBD z>Zsobw-gde4Y;lr+lTwcHZD3+*wzOj?4$F<)>L|mXCMlL6+#fI;6V!GV-f-Zvy{YX zIZfwPOAucd4$Yc`hUZ7h6@^ekZlC*2eutQmZQI@+Pxz zbJcWR5j(#-MI@i8)7~iry-z{ zyBI-I%H_gE)h!nNFw|W;?e>T3?&_tZVY{=sa1Gv^dy$ZgvyEr@6s_EKP7*AMUA>(-Z| zPi@sGrCbQX&6f|4_M3Bqv1A|;D3r@F5s|5UNviBRq4VrXtAQhFrBl(NWM0m&7g{ki z*%hUH<_SsX;*{1tC166*!Ijo_A*3uOX3i-AU$_Vr3Q|hB$T24ppb)2h-*wCLVNcc? z%ndaAy`PjLJ#~`ZnAvtU$^y!VT-U7|_SWjTL>3ZUbfPkGu=l5(MoC%Wl54qp%6?E^Fo>cr3k5kY${d|5W5sC8?CiKl1L?{ zrL9XSMk)uw2r>c{2q+4eKxC9i6D?QuZgan>^)&RW?kEQoRAur+(i-T;hwJ5mn<)8Y zD^aM)ajc}y8KpJ*X zmI|2ntLiBzRm@w!F6WqWPlX#>?fNY%L@B6bN~Joe#%)d55X~X&w5sMQS}h?7CNHFx zYMsj*k zD?ZBzH;?|kw5H}W{i$!idcQpELO$7#KiHm!ql2ZY zc7O2u-)ic`2aXSGotItW+HO+);`hJy!=L(C6K{mgTrJLC`8FIJ3KHj651&6hc~y$0 zedH-}XQty<-hcCxAN!m*1rca)(4;a*zJDz_3yoODD7(Ioh(E| z448`|VU?!LF~-cfR@Mj!G3T5lP}g;|1W7Qm;Q8I*ph>So45-Cnj zspVMCsnRJnlt~E3IE9dw%Wl8lmzb2$b=}M^WEMG>470WN;&gZc{l3UkkrrL>x^Ci} z8q>u9O4mLlt@kp|E{wTuxwI{`DZ5-{Oj9mW%g)cfuIxDWq-91D*n*nfuxs^_5lR{r z2=7H{rSY?uoP=EFuJ?WvZY=--r6OnvH55gtNQjIaik8Y0G$@%lVlkRJAyqEUxu$89 zQscH?wF|*SvDDTUt`H{4kyuJ-2ra6JGAI-X0t#x>wXyr%wz8VIOr=p$Ow+*GU%P(E zyTMHe*`lbG@Y#n&dpyK3;TcjcL0em=V34-iRh7{$Kj^xf!}L&DmcUvRH-V&^fJ*7f zyKN!aw)JLnuhWl9Y8>BVW{}f7*@ab=ggLn}`nEgtL#9YsDxqc{=C-~ZT~1|CCZ(8b zxCye?Ry!{NN{lB;3&G3Hcu#kn0rq3GR!5&S;i8Lae>uU7nxhLww#w)L^PSW8k5|`H z_JXvn`Qgc(b@w!uws&u>yJd_j6dHW5Ev1kp5kgA7uC1zVCYt6cIj0!`bQmFNQCU?0 z_HK?TFN@Rx_N@Su?NN8Qolg7Wn5weRR~H9P0QrYU?7sY zSQHE9D&_}ORr`=i7LyC6R-}RpJ5O10l_pu!Qj!GVScbYWGN?JQu5lQKqt$gEPdscv zbmg2(M~S14K}x9=N<_>hh-*Tse%N*mao9jggoe~2W)?!AaQ$?rsnDwBxbbb>O4L3; z$O1@*G#e}ZJSnA=s!9n`WWbPfK$O5bMQ%D@ihj5qbSni0g`jdHQZvX+r516Xgz(ek zjxRm+@T?bx7g7=lE&>ZXKN(95&REe8Hs*a(3q`7AHO{_jMaD5Wp$N0fRAjq2ym#{O z=;qPAv)h;Kt-?IaLv3ZF<>usmx2{Q|V-J}mnVfS7p>5k~4qDZMY)q#l!1Y+y^=^D< zs+w>;!>+3AFm9!kVGfmU{1lt2b4j8)6Cwtu1UA~>FpXL+iCijqn4GOE?`LaN2(wWl zb@kc9bE9gDrcw(0EQk>jSQBUgFE53N~HHYJfnN*n;AvC{2x5nx(- zzB>m&Qc3|xh~wa`)gg|~4~K_qA6!nQZQJb_y0#6o6M)GFt+m!Vgpk9|s+Q0M(i4=7 zMGG*@!lfD52^oO_pfi>r^}8*aB|`=d00jvcAu}*zN^;TF=bML3rIDeOB2W;@S*8mn ziEtpxhw?|$R$+b{mvpZl4!dGoy&ANcX%XFvSx`P;k49>3OJFDGue2G#F> z<+U46U;o~#Z+_s>50-LTwavkmENTR z{@{y$RFP&Y?xz7}Uztjgh#^4ERvAN*nVFdwtkMPuQt6sz97iHjprm5LToSjM4b$UT>#BR);t$w2~{vsdu*(E+pWxsr%g?5m69GC6p{BrIapA zPAR37BLg6!#3mE(-C!%tAs_$|&Cacsi#!BdRg=uI%y~Z;Qw@c&jLNhnD=jqU%*-wY zX{%U-oVTnIL|xBdJGq;~8V*nJJz1}0vE#lMt&j+k)ery?5ts=;BQ!!aC@ho6IU!3) z0vNa`B*{{NvC0&frfF)b1`7*7BTe*Su~>B8F%qLdL;)xW6oNpK-tmS$i-|L(DrO*Hp&Op$1Mu1yYjHV9arvaV}ak@$xip z501?+4^lMqcy5fb;`&TQLRVsvst1~8morP|5SkfO#WZe(WFm5N z(y9@pgaqe@iIdPfO-<-cWfd2fTtO(UUCntn@y0_bX>HZ9mNUTJZGuCpIOjcpv=#Qz z>S`B5Ef<@32P&t5N2{^!sN{=tE?uxb@cFm$b88%rUXbL%vldHPbZXT!TQAr@oi6X_*FpdDCvfd?AH95va z{Hdou@W%UZNClt%;B)WXzlT+G%d8gH55M-cmwxU?fA0EPWqkWre)nH~_OoAjq= z_o<&t;nx5AAOFMWe`xioANjZd|Xb^&?kr`Eu{}ZMEy)dFtks-D&gn@BZ$kOYSfJ^uPNz z|AYV4^~;wZy>_*0tGC~N{YO6g+4tUfjp1B!Za2B#~;PL@F8L4Bi^0r$VG;xAfk# z3l4Rq$PN4DViD(r!laT-rJe7E5K__{JP?))E~bO^VVV1!SlZh87;=(Q<{Ycqj`1NB zl#Ch;04BgfNK*N6)TT*5ZW@+toudyWXD-@yE;uQvgc))%x`~A|VkIu70rLe^g9{QV zOz-Fo~iRNo7h3(JIY} z1z;t0PRVK$ikBocDupD03=km`V6R(|QmuqyhRCFApRll4Nr%XUn1e4!gi`4WrJ_KE z&q_iC^`dL~eORw9o%e4GbpWLRIg$a;r-+gll|CMQ4@@L6msr`QpK?{xFx&&8l)biw zi&SDEP+85+ZBbKlCB{fZ(#Qgc#Yj<6nnI3JE<@~<9kn>dVy5^8fR$L6QmiIHl?yq- zdB*#ydTNfL?{~GeDa3=rOZ#a)>-!}QO?A{Xmr@EST!<579UMvBLwwYQ$>aOls#sjJ zFq}G^nDiK-%wDHLZJUu{8nA}71}yAmF((rR#SrI0Ns0>mf^sLN^vRPHLXI)rFTnsI z2!mvT2dxCa6vI}+6_h)lywS%3sxY5govku*PM$DGv3HXYYGLcN?^mY22UJxY!jN+= zS=4qNb1IORdNmG787gw`{Vd2>llRj*SDPQ2(;O)x14T?BM=p`FR0<>OTq=Xc!hYIq z+}Iu*CQsJXd5)ddkzLNQl%ng7gx;K;FPF>xW~;Sc9vt3(aH6$6k_|$=?a$1@yF9UK zg^`{&HDAsNL&(cz+ixFQsj|mLuf)Q;>8?(VQ3qbnISa{_;jAvrZl1x~R9GnmC4L?Cda`ViLCaU+_oJmKl`)VJX+uk z<<7ZD;EHl5s4k`GrLLkI#!aj>7h$8w5H~ztNk=Rw0XudchlDAXm$StuT7)uq*iP{^fkRivUB{6eB5JG^V=c!m^ni5UO|N|AHaELc!TMJSn4l3Mg}3Xo>+gfg*kDC|m#z{03;p0H}q=B=^1 zPa!5Sy7|;6KmO{=-@X3q6NlaM55MuXo0l*D%x6FUt+&2cBz@-kQ-A!OFMs}{pFKOh z_wgV4!k2#iOE=aLYH@cu{%b$~Z+-1gzVz*vzwzSq_EJ!GsLRLVQlwgQlT3PAJ-glcEM;mgZThKZb=A5liq-01@AXQQ; z#yfFSF$0!>STeS3jg}Ken&YCa+}yWy9bJ*ij=^QfwWD7HA;iQ2 zfJjLq@_sBSnWnwq@DN-~ktakXA%cs_HX*UqQa}MH<2 zv#*!>kjFtO6?w{HjyVEqiB`)h#^^&|w>1};69GHK(F!G?z0bo!*cIB+du!{V#0Rby z3oByLiennJ);S@ei7^VTlADcgLT~`VDlJ*YFdE}C9gkrY3Y1b9P)kjc20uznRnz)V zcGF<1>cY=eS4;8_dMaI33vJI!6fDaCv_4R{%{eQ=u^+0cA)$njE~I<&$$BM3orCM|?9mGog>L8j_z8yE8~pJHlT>cE})vBMGIh;hN3fHsLI zWmmJSy3~X?)~yJ!2TaD+-e)eLYus$<-0l`^)j z=kbBSwq%Ji6&R$-F+dE!)R8JFj;p4dhB*NOrlvCect^>GQK!g=%F2qc&H-7RMJ_@J z%%y3X{WO*W3dq4oD4D@~2UviV5OvHDN~$Y4?fYeQXoTDhC-$&A9cC?JWtto^mh8Q^ zRhLT6kgZ0HS!5<5J#i?k7%P%f*kRL7F~->~h0#L$qJp#{o*StEU>>Ksu3emk5JCvY zGRA0(RZ2PMGMA>QhhgYySyg&)`w&@*3du6aaq5{OGFWYxb15a2n2>!8jc!v)Iq?OR zx|G5>0{|)94I8Nmr9e{qew0Q^g>BF?mmpw_UMT5O%v7d0bHM`0LIz+~VmtPzWXy3C zGNlk1q*f{9FZ{^o!@TV(J+*i=s&Oj5Yd`t?3*Y+E?|<&|pX~kS>tPdEzsd+r^8o z{I?_QZ(RFJH9lg*>yJG1p>KZU+vl6zgVU2?44?mzAFrD9{{5GthSy#`y?;7?=Ho9s z`oWuj^gCZ%tX8+*eB;MH`4ijS>73@G@yGt`pZ(wcAO4RY{pbtVj*oA@d*}GpW3Wwc ze)r|4UU;k$VK}?XwQz66FTQ(n`9m+<{oc2qS~r(3A0FPQuHMwjKRfTzM?d_@eaPQ@ z?G&F%<-hKT<){kAi49?%Uas7PUx83ULTOT-i?;gT5@4M?aZ=UyWosH*r?nPGmjoaV3 zdhq1-!L~T()3~4b?%ALf)|ERS%7LzDPPf$JBu(oqcMwJ9g%nCDftX{V%#wseSxTw2 zNhy_5y1EH2Xd$^2A}S4HA39O>p|2ZN3AJ}_lT!m0p(sKkN?Z_>MX5=0st@oBS2vU&rld*$#jt=$VCzWLJ~xX0wCt3h`1CDWCVH0 zMo{5wXp4*hGBISNjKBg5VG4qM?o3q`Wm%8KxjYP*ZK**}F3gApKof9^Ir{1lP9E0E zl+)dvuDO|u53pq~I{N`7A;2c&9cQ6rYw>_e?cE)z!N^96!*TGb?2&lU-r_<^EQJuU zj4XEryrKrlLi1h`F1Wem#1q5}7HeHg43}c&n8&8;I;!XRK*}Mzjz!L~88=>-33Vbx zQ;R%!Hp=K-gYFPj?)-%XeVt}}-yU63N0 zhUMTVG9ZKmiRdPllC0IuR7S=EAef@SZ615+iU19sAQx3~Y_zGeI0w837s!qbf^Eu` z(v~4J1;!C1BUXSq^H|EPM9iQBs$<69@6S8vyTHYZQfe!zR7ObNCn;r4Y;AQxeutbQ zZ7NeZdjiRzQPw30A!O0PNCYT=u@EFK9CHriLHo#VGbM$}Hi<)agJ7sofnIRQZDoTS ztq}mIbTe|^oTp2dj`q7v+gwHz=VzPCSC6CLKW84DH&IzNp6^Aiy*N0_5zTN^a&Q*& zQbazyw7hqEN^rRBK9z@;!`RJo5{tcxaTo(r*Hq8$!&}DK5@Rb3ghO;qBRVSHsW(w5 z=PN5$6r(mru^#*P04YN=2Hf-=9_0 zVdk27O-1$7y`~k&YM*BYiY`{x?$ewzhhc)|Fff+DT!It~fH{Q}GU=Q(#u9WaDH(?) zaaoZ`xdA4nku(UEVsu4jL|Ei{o~Pz${cy8w>xLSee5e7m5HyR5b!|DKE~SYOikcG$ z5DZu%#lip}3aG$l(7Tp%1C&T6;K)TvT4*tm2>?U@jhO{!R9RxqSrk#%^*qlhMPp3N zX&8oru-`Ce>c`S8>kNEydb{nqLJ5mv0Dy8qKk(5BtAR);m6;JZrIevu1T^KGbI4K| zQaC%$0!&+jfPUO5A!m-B#J&V7WsVLd`W%I+eMpH@mQ)COC^>o}m{X8SK+GxRgwaqJ@(q!?FYAS z-#_U;{N$$(Zmh39dE+1aqksJG{RjWEKm6)fS|k4YpZ~?b^B@1GANw~x@$}W3X9q*f zH>68r!O}kc^z|qH+1vls&-}y--}?INKk~_+*iHL8XK!xy55PbA^mDCxT-U;JDD-n>6w){9%$uD<^MYuRGO4<9~!4Gx>TeJu0( z8NRYSxc;r-!E;YOdbT-VAFp1&bFTvY!Y6+0t=sn>#@wjmNxdzWhuYoT+}<6uQ(d)L ze_VFZK-%0L*X^yl=WjL#(qNIUMKztfaoMy}3f|35RA^-k(Icds0RT%OM1`0r0Rckh zl7Ts;1ehb2!bPH#LlT+1DLr9OkaJW>OHf!@30$%-kO>I^xdJ7lou!%E~R8HQpzw*4q~kRD=iuh1t+dqh=!@VY zkg+fW5C{gyAdHw}Bq1g@St)||+E%HsED;gC_d?=4`=-$hECj`bP)aO0bH>7)0GV>m zYuoOp9t8-AesV@>DQUOgA_84V@C5}L01%M?4I?2jWCGSwN&sY(BwQ)P0GunSH4zpz zBngV;Ab~0n289Gf5I|UnuIOf%=d4U*5I`2GL8^q1+$kjr2QJZjZ=@0^rBwa6J3f4R zyE|=`au}wV1|iF`eI$)%&N*YMre1I^Aq0j5n5w4khf^i%9NH^auIz_r3rC`(1x7RKWd6bqluI?+1;FQfjO!C2h0F859~|)&@iJjDi^!#tvhaIwu}Wi5?yp zW7s`H7IGY{t|?ih;?k)y6~d}dJ`02tyUZxcz4=s7=~8LsV@{kw6QeJ!sUXG4lMk&? z5g-YZV{yvp7=W{lfk>;E9Eqfjz)XIO0@LcCx_kaEBv^GzQ3!>aQz!_rWDPA50l+v< zb#0M>h(btGS?7DLrM2zJ*?C=8+Q2xDwOlDpTv9He$PfU>Ac!~dq*h&nHq1eSG)*@L zoB~UwbV*Ufx$HUxNffLaznN515pwXZt!ybJXGn!oEVhL(7cJy8owrpDEPa@zV$YYC zoh)(Bfi*RisYB`uj&rJOA?CCky|2}Ys&s&geV=4O;%OL|tu#l! zS#)}wcG^^-KqzQrqhLiPGoA)Nlw!51ZOyw~kvbx!6d73OTo#?$?H?R0o;lk+PzGI^ zgw#Y7;#}1#@PQlya?BcpWE)Zpd8A_CBIb`lWT6Q)0m_sy`Kcr2oC`*fP$shT5tlUt zN+}5;3VWfFcY{(&kZPnIW1dP*oN8mo?a*~y;kZk(#C;`<=9N+^@+K>YSz6pmw8(Xe zA{0hs5?N5(=2!t*B#9z&iK#H5kt*a8V-!*+7CLL^3nYun?LuH|I>f)_y6u= zA5`t~6K}ow?&*VvSFc=p{`sfA@zt+YdM&Vd>$Uw8PY6ERnOXkh-}vS;m!CO)`tl>! zyW>Z1{_=nIPk;6cKhtl{fB9E`>3{LR{a^q3Z+{BTh< zhxOgv{hG^X9)I%NFMriOad5qS?EdEd!Eu-T@bM3R==tY9^3`vDcYXDm%)|A|$Gh{* z+u#3wS6j~ByWEfI=9Aany?5`u_cup}H(z@3Yd0P_c;oh+lXI?Gx!G=?yZ-3ihYyZa zohmi$do)Vsl5@twl7v7;21Jx332viEkm?4_t; zCN4Pz<|33L)T&4YS_7TMy)a-<08kVnx!J_rRR_5Q@BMPQLQumz#T>Mjf{=)ub3zC} zWXMXXVHlJ}AmQdDr51oWCao~WC=>x!I?(+}q)SW6vzA(GZwfU+M` zWlUSGb{kh$+NHe|TB~ky52VfoLO(jKmYlUDDkUD)hx=4^uBZY_Ewo4uoD+G4wgv5R*(MxS}$oGV762Tl3k+7&0bB<96iYSO?Er}BdY<*;* za}gs@<3`K?{bp=UyXv%$Cv(~&Ntd9lE{AQu$=NRLWeI~HdQJzb&fXWS zhB>d+3xp|zS?F$#i1MC73qfnqauFeUQ>)p}%-l30`KW0nkW=YnKt+dRI_Xx6?e<)2 z2>@2he!tOLW*mvuvzLq@G^Z45vziCaFl$uEAT-R=rmbyCMcFpt42)Q?loE0%1(*$j zNxU&iUtIg=oCT40QPtMy*nYolAwPI$^5wI|G zDTSnrG3U&PShCPo#u8)UaSqj@^>H4iZ4g)#CBR&Ob0I_^W`MI45}+#?ks_B1Z|Tv| zL1CQ6-J>^e{p3&nE>f~Jg+ZZh8f4#zkBzcd*fgI%m1T?{X0UqH(tE^?9~qImEB628um7`j za5GKur=ELG#B%)T!Mz>)&KJM>m;TDn?9Mm!(ZSbW{{tSDw+^qq|GhUq^O?{5-~Rr8 z@wp%Q+`~KXedV{l^!Uf09^T({-Rj-bJGX8fT(+j)?wrx-_#}eS6=`1#Y!=+=IGu(`ID>br_UZpV=kYZ-v%A#Sl)c|&cVUa{rmUV zmoF_6-JADMFOGk3cDG6Bi7%L=7XnO(IY(k3qKb$ZjF232W2+c~yjU?=v~w=bP-To7 z=0=(Q>`%u}2 zhyXC>%*@6ZDdiY<%or0Wsg;z>jHSfj&_p2Qf{B3(Nt8k;WJuY8R3T<1iDS@O`%p57 zS}+R=B?}=^i~>+8<#X!D#u5@IA}GKq7G#oAQ)JRIWFMkaf2z4mM*XSc{%ifc@Ie3q z@$t_8g=G`LP#PA>Ci5!LY7oO zUrJ%l)@ZAYCR+lR#4+#d)&i_TWM3XG7Y8Q~{H4RgGkB!Y&5DJ9kPN-RY- zZr&S%Qp(_CRa@tPL}Fwgp)v<4A|fkUQn|}Ql`Km}t1O~$o_*le%Fa^+;jCGQu>v?R zysyRMRPA>|FLX8*_IK)OIwW00)+&k>;phS0hNtD zR2eGf6ufU#CuL&VF-nQGb1CNt7?}bWtg3EGTZ2~#Z9l&Yw6|vICRs8SVQgJZIVUbm zD1b{0lCY+sWa%SRjb(6LVsydihKp9lb4kTpZ5M^70t{Hs<2{8S2uT`{3IHmCPRR#z zAY)z0y`QyF5{Spr>Q8)_UnC zbbhd{8QrX_RY?XoYBTuoaHXC&PYDY)IkE_;mE#nusP{}pX)OS=Lv~7lKv^CV;es+Q}Nos0Li zmXZ#TiE-pvfMk#vAR#jsnL%eJQj(IR-~tq{tT0+}U^fO)3n2<{;*xVlWXJ`W5wrk6 zC{0!Gb~}c5^Ho0aIpFvK{+EdcJ@O3nAutz8JxjQubj)i zg`fEgU;Lf_&l4a0;6MGPfB2XG!Y|xDe`u>`Pw%|cEX<#L|E&)_^X#wv(*N^gpLzb~ z3m>liVxr;I@4OPv$7etK?91;yc zyAMk*C{1;>f9bn_vTkq68+ahD+uYqgd;h)R^sW1+`v;p(KJ(P8Z@hPW?aJG4zGdsG z*7p4T{CK(E@Ap|jPRuD2A|et46b68bL2C^_oJ(!2V1!g8(uBjJm|E0{&}f0PLy|kl z79(X)LIX=50})sO93!kR;WR-BBd3(0A|tE}6*YsCl0pbMvlIrIqyoU~e5|Tk$tuR2 zV!jCGS}ePvKVu-J3>RW%6396zf%`ZL6auki03aa*qhJKgfk{lWmy#r~lF~)CRf{Q* zR-1meSSovD1u_vuAB?h+Rjq0$Lf}^G^>#W1!je}?DJ4sA!YV54b8%XmN-gIpFz4l}p69bT zc8lg<9`9efw%%^0oY>ajf)i9Dn#e(#G)@}=QCG{&1Z!=G&d*QVww)#qx=IXEXHHQ7 zmxOh-ER3=E-tAe-t~!_}PavD7n%(N>!nTH8-qDHZ&_wN;+^+|I2-Riu*? zO74W*kK59yIG5T4!Le!&Pv@RQSy0PcInYT{UB=WZsSC`R`)S^4tr-l0MT*0G7f@Jr zC8Q$F3@$S>N}-HCKR>sPHAWA^zHSzPHnSS0-q?Z|t+J4*q>AC(2wCPzi#qcll#EWx zMu%yVB+%$FWM*y@nw)A?jr+cB8l`ZUw^1ZemW9ko_nZ#xV&0yKmDD_^-Buqg@^m>D z-_**_dnE|~N&#Rom0n6}>*c&pZM}?Xo}H_!<+(ajtW!UC#?I4TSurP9)iq9wzTI$~ zYqp>a^pc!NtCVBRlE&z@mKftgTMlU&=H<~riiHt{)Wd#UwB0<6)|j9+1>`u!wq52J zwa5~%I3AElWTBwn&y`&n*`(kFC2fTBVNO9=IZnIAbZ#uG_R>C{^2F=a;V$;3=DFW9 zs;ryF&qi4Q@G-Cip{tnWehhUTWMZY%m;*!RIasT+qBwYK>yR=-;_Q}-8d8ovk&-dD zbIwKdoo?7yR!N^uAud!Er_c+Mh=>s4oI88Ss*f2NwID-KVwwZHB8r+?s*rW@A?GA3 zQ!~Zgw5f7s8=FcVhM{R1?`LFa>w35#WaK0SA!5wAkZ>_*RnN0G*1E9Orc|}soDW*H zMN&%XBA6kBpp?!r0YDLeh~Cdii(G(6dKXBlUEYuq+M507QpN}@jqq_oR52AsiaBas zxsn{FeM}4h2+~w3coY(Ix^i^sg=e4r?Qeed-~U_xlSX54{`!@hzx{i^7r9)2^v30; z-NktC^?RTCfMfAG@-Hd+{WkZ>n2Q9>i(;$rnC2?EBs9 zpZL%-=i~Y9J8wV!^yiL@SzcZI;a9)-Q=j?t!_&>2(wKdj+xWJjpMKW`j`LOzy0rIguCY_ zBhL@cAKZQC^{MY~-#>lk>SO)A)7y9NR7b0X<+xd{P><;>l)O5+^z4&QlTSBq-Yk{C z){gya5Uc%}f9laE?!ABKV5|KZ(v zcWZU&thi?{-FUD$d-%?6t|S7KQaG{@LP<%CDJ7G@)Kv&U3ay21R29b=8LEZjA!*g@ z!rU0$0YQQ!g%BPRS=BPrI891ZXC)**CsS16=$FC*(-t^Op7y(CwI+c%XC<{j>zor* zgn%N)7&0?)!LC^$V&C^wRVfHDXC=)XVv0peN+}pEr$kChO$3FRxd4ZfV@{R8P?(XB z%X(3d!<<=|dNJ+JKuVvJ)>;<8!dh$RXGLCU1;jq5T!>H+X<+0`E_-d2k{IVnl8{=B z(}e$h&HdBz_oBuH$VeFqAOIj11|UKzB3(%$*VR<=;Aa3hWVwe_0JIRAM2x{0J9t+J zNC*`(0a#R4NmZCrmI&6E;-i+zO0%|2o_#H}L1~rdaqOyE0|*90^d%HR+csjeC8;ur zP#^;mkwgLj1*!lr0x8+|y)ot@siPMAY|J!f8?;uE;9@gulp?B-`db7RH=n6DZ=1>euPWir)qP7RaxLcM* zVi}9CG$t1{F%X#ga5{Zp7dl~xQ06IBWDaMJ0lfb&B9;CnO@t$%U$FosVt3Ofg$!DZLk%LFtTQnp|5q z>|>=(hNeJ9%>A%$n##}4ScM?QP!6kVoOTliQc?ziWVSgQ=#8orTF9kv6ap^<=zN-p zs4;a8c^c=#iFwp2dNF$`aaBP-R;YVSso z!sX+`wtkGY-FCYnqRBg3)dgf>6iQ(Uv!77QEO46rqF&*Yt0MP$V|0~#OsP~=!v(z$L{xRs zyUa+b6r}9JT-9v|NhVf8AeWqDCLo~%O3Wn@mGY-o%864dJ`)px%i`j{x!|j++7Cn1 zG^yk{xs(}|CZG}`0Rq7kXHl7yc}~IS1OQ4X35bPHWn_fC^P~t+Two?IqV9-5D)0eF z;1iEM{?yHzC%5mwasB6i{@?uLKmOg%|L{lSkiPy0uU$KOwBMe7;tS9G#&7=WPyO^S zy!qPM=fCjT@4xZo`}gllxO8b@?%zL&ikx((x8HbVdGyT}e_(Gun$9ML!<%>BrLI{o zu5RByJ>Tt*o_PF`GH?5!mvzxmRdzSTl;_ExUas}?Pdz((w;e6y`GrTH{POR={zHH6 z6R*DZl_zgqd+^S^kAL{{tD`I5d+X)(@#@C)M|bz`_ilLl!;ioF4t(#GhoAk-r`~z% zTbK@Cef{@rt?Ksby|az1#GK}I0YC~}VX6y>f*Z`9_<)Q?5pzOhDP@YWltMye;FyzA$`JzR zoU<09q)f=npp=v$V__|oK}DIVuoVg!7s@K71hW9FQR-3*Q)la4-jP&5q7>kQk}s@Y zMX)3#l~NaY{$=14l7)OJ6+%E3+ zry491r;?;pf(&3OIViz}m$SRy*hO^3)KSWI7$gXX#NN+RApme80FY9|G*^w?_IqXo zCMh+iB2YF}9U}VBD~mBkEt`@vLKYYsyY?{@LKNiO%(+;p1kxBo)zl$)DBM+bDZJ~) zf`qcyw9u*xe(qX|VXk#I4ymqnVwmPxO6g*#O`UU=Qj(kngF>TGP4g_Qb|tX{w@*r` zs#YQRoTCsz5bbumb-V7f&neV^rm|j%*(Z~zXeU5nkvW!9B48@OCBx#dJ3l+Cbk)?| zJo}V#NJ$x+-Ik=9V-nJsx&BB(SZiH?rpXB*jHyye8KZZ(t=kx*AV$u>Bv9?hOF-b9IYll= zQlPwm=_3*%0&wP(A*T`(=bVU{g_1r7q15c1vZj>6B9F5#B#cr}kho+5m%L}pz!^Ak z0YpS9rIeiakVpy?H2RnU7(gk-%qf%LboF2<1+CUck39P5gNOI#alCY^ee&_=nQ?v4 ze&^fY`TP(4#6SC||ILs6_{U}hcH!pX(UtWTDVndo_|m=YoyVWQ`OY7m%xcg}@y$Q{ z<42EfY1Mq^wO4d!tbnk`<>AVq3u77Y`x`f}Tz~H1rQiF;=RW_1m%jH3)ik@X-G-}I zj+C9R(UqG|AAbL}cfp6JpSpba?zyoGANsp}eB|0uiVr^Vi4T7F<=Y>4^7B6QlYi^^ zCqG_fxP0sBmtKAQ((1?l=&S$iCx7yXU;fU^@7{a;!Cm>&Klg<{`r|)->SL^FB$CqFJ-Z#Jgt-t*@|Js*-|DQhh!jFIJrSH7{#H0Un z_6K+F{h)1+Z(ggO{?N0kzV)>iU%vC;ojY&#{aO6bb4QzaN7W0L;IS(=k4*cOFaF_F zaN|A9{gqoc@9nlHyFO=Vq!1~uY&*J9+se6F037H_lgJ1GN}Y{XBFwYkA_b;YthL4% z;R*xPQWIbSzL?@I2#1pT7$c%6M5zcRC&Ak|9@vUg)Jo0cSl5m7QETmEspli80!X)BbJBt&K`2!fQk7(@CWS{E<(Mc?;d zt2837K%mifjy)HNV#UG>s3It(324Yc2vu^?+Jpou5lC4I`NYUnN|9RU93d63CODr3 zjyV~vQj9@!iP15Y7(z~nSPRh=(PmoY?1U3JDN+KZ1c6Wl5HZd%S`vzipXSC^l#2qQ zSiB@rgp>=Xm77OZvNmB`HiYX~vQh$(gb*?IngJ1XCr*bGDdkB&R#hd0=!d=JB+G=9 zqYGLavRDhpw>|F8;yG*$d0Gpf)(KPzG>Z(XNmlznbS=fQ#;$|W$1xjH~xv84$k{>50OJL7Mq@0yt zMAW2+WIwx{vk*c`l%PNW3JpYt5D`HLBHrxwZQE9D69MKaNS$5U3Tmc(rNko8EcN7q z1^_8FO0>ck<1e6M7Nd>iFlrb?2iYaGsy<@@v0RR9=L_t&pt!Z$xb2DJE)|ME_ z>M^<@y1K48^6XMVOeIuO@7*jQb0J_75=j|mj+jqQPMA4zx_|bd09H*SwB8OoNwTqx z5P9}-*bazjwM3FWdx-*&<`QS;l$Hf)a5GRUf-{yP3PXvQIHyvQkXHgmhB?Iq8KIz+ zbs`28a?GZ#$6+t4x|CF?%y|@|&N-GawN1V2x27_Q!KG9QPf%PbQtFYpK!%t!P>xv; z5n##ySd`GrImWn)uF?ic#uTFSF-AlrqF7pn?B^&XGIK7#DAOQ9$xTyRCO>dmW<99+MCa1(X;{jdDSkN)V7e)a2L zZV&1wo_*rC|G`U-eW+btYyaq_Kk3@zjXPVKqs=Sw2d}?&>A7pa^E+Sq@UtWad%I9CW+O+Fm`>k)5oIiT=(#JmW*!SOi=h=@wwdv32;7W)&;WLjt^YH$i zC$Hap#D4J2dtVpY95(shomV{``n28M*{`oZef#$7&pmtP{rAsT%j0+6{u3FAAa??IfDJBi~i63nV)~KyL;z!?z+dXKl!=W-+ljd8+Ne- zKM|nB@%56^wZnrr?W~4c(cY;!CSA9FnnuEz^Ax;p41_t8M#07!iBwz+C6|~qlpYcn zUMhQ@ohH(ZJw(};pk=Z$F+&X2Co9DkzZUJ-yo zFsVpGAeBu?Fk#8EWW!`*VPXIwNB{{5R8UGr9iRXK*p!P@8VUfalu|A_0Ei+AG8K^s zsZe4v03jtPDHDv4XTU9yhFG~Egd&OprTW*bF2?W)_T;3L=Y~Iv@!}7d)BIwW(6bh?1BdJ~&-2>YU@QOpWdKp>(SDK9Gc% zMypf=Ik;1)4_x3Bb{V)f>wbP<^d(MFL@QL~R6i%yrRya1bDy{{i0n#=KBa^hNKsb1 z5JK>w016a>j6eYe#*(Y1@th|&2tg^wlm46(IVcKIYQY?HPQ`O%$Xa+8bPJ;wjb5ib z?MEl6%e)V)gr*b=1C&w-AZI3Gw-;4Y3yFbz2#(1IoI-Zp^4MgGF)O1+H)>^v$r~k< zQb>r*SeO_R7Z)7>T zf+RqZNf*KUk_L8}BF0j&;Ms%Kr{^bK*VbLN-|t87>bf5KeHK`uRG_vHeRNAGlFB&@ z0L;LJAw#ADlsT6YBe%A8&OrfBNJ8y{MK zoHJ^pcDo(OElHIb07WSP1uJchlz~qmBNs?f0uYpxaRL#2=^~?mI>h9qkQ%&m-fKx5Vc5LA~U5V5esl3;jV>CWecbHUS3)hY$%A5*Nr^1eaVgAaX9o81FW>9{=D6Kk&q* z_3DL>ed?e5^Iy4j>(*nBJ@$#`o_+J}_X1B=(c5pn_VnYAfAZrW`;FiJWjs2Mw0ifA z;7@egoNacm*{XTv<#S)gcfRqUIJI2feDChthez%C={YC!fydifpzJ z{Kt2j`I~Y}fVunGn({d33PWioX5MFC9L1R4+bo?~RAU&^4>Y)kjym-S)x# zS3dOIQ=8MEMf33V_WCeCe*N&UT7T=SFaGS$ef*X0eyxw;#{ZYC{|>e-z3#)nwbmDQ zI`QW4a(=JV>)b#i5FkL11VJ%Dlq|`VX^lZmHjd z-L2`XgL?DNee!?zjcO&3x0Nc& z-BGg87++3TmX7alPmdix*Y0JvH@CVIzkDL>?d&W6@P$(+l}c_6_m7`FbLZ~;tlje7 zON4f-i^8s{lv2L--dG>17hlQK>6 zI#x{#L=>54UX^S>w9kq*wBGl&vmYLode2-Y?grkpBer&1+hcP>p_MM`P$ zl~Ed5(o}^Ig#$lOiLtG)*I*n1etZRnt%#Aw<%%|2#{BKnO5l z-%7J-?U*?sLs}h7r|mo&)zj2Ss#OS4CyDbMk-T?-L!zv6F0~>eP1ESaI^Q?|8!2!! zL<-0Vl8^y~iRH+iSxB73!+Pq2OL99p+#^(iiPAg1z5z37nBGbgi#SH zkBXX5mQCok+f`MTld-ai!Nj|ut*XL)no~KQ9C##rJ_<5N7Axij(a8pl!!7LNdO~9 z2m#Wxn1l+VC!@92qsiD9Lq-oKhk24=s;o9%cwCK?B4d`s@)Jjnw zl}2VzBq6w_76Iki`w372VyrR92yDb-2wVY=Yc~xOP1Z-{qiaGe+WENjhB!-;orA-o zD8^M|l2%}va%s^fY%vOkz*-u@hQX4lAxiWOqLCORG!j@b1Au^MFCr3whE-pK-N&B2(CII3m&0RcPd;|$ zVm%tY^y(KboO`ye?biM2Gml=(boTN$zRjZp0yW&r4}7Q^KNuaLUbuN<^Wnpt)_h(a zOivv@wRw1O^Wj#<(9t8OYP1WTbnD*MnX|{syZh%JInt=fz5T77aXlQ4JMG0JvF%=w zYJ2L;smX9>ckjmfv9nK{fArP2-f~zjto6?yJ+b*9l3ID}vVP;`8%LH-RkZ8eC|4)? z*^$?N_|oFirT5;tN`*^1_M<=k$uIu?*FOEpKeN5JyBbILA6`ide}C)tpZ)w_nog@~ zQvMHr|2O}Sf9>;kuDzER>i+G`gy+kx{G}@oE}neuyVt*PY(4qR(@*{8|N8PP@7(;$ zfB9c;aq`Z~UqAEV=fCvD-+unZpAOY_H`^K=Ok2(3^Ur-~S{;=2?#j~Xrj+9+1`oEb zI2nBW#lKpXx0jY7rG*<;c6+^KVfpCm@7!sEyM6b@`myDerL<*=_4TE}c&poy<%Oc& z52k59|M*9rdFtt}f8(n!JoVgYGCXy3{r2vbE8SdozMhtycDn+aOb7jV7hC`+5>h*-HMNs`(-rId4CM3Qt4 zfiXl)fQX_ggi?ZU0eY&oV=zcL*%Z(xGQu+PeXTSi0M$T^s9Mw{V5K0BF)E`g3CgNU zSS8j;)c-%#_fN{O2#5d>gCHSA$$`40T8VWb)WU7R3{=pRMFJ9yI)q@AM(;6-c($1m z!JuR&XVhd2Xo8R_gNY8#&*Tl(rWDv%8#oFhsAw5xewHBwN5V*+nMuc)#2z>TFp2+|o%56)TZI^!%5#9F?(%W|Cs) zhPJpdn^JVg$Dow5)--iwfj~hpgyQS^!T;p__c%rfn zNXo|;g}rk{o;qLzan57HlQIXYE5A#zrwr8o(1(Ol<(UCU2nj*FkN^m<8nZ}1to_Uf zFo|f&K)ISk#w^Y1+L5JEH40HeJ?$y2l=>hJg@A|V@oKX+}4V!%|F|b9@krhFJFhQwpY#FMn%9_MCp(;lL z)N1AXhX)$;z(nsumvTbc4y6^Vz>K{^g)BS+f(ZyUG2ny4gCt3u#JZd+uSqFTs;;W6 z$f^)rp0czC|TwsHQq%cc^ zGG$d81Pq)K9%4{N2?h=<8UUb*o&hWerKptJY7>MbNB|PYEvwdN6wu; zG1_VF-{4-h7B{6hg5SONhe>~~W_9!R`~9Tdrp&7T(Rtl1;KuHKljkkdK7IViFb)sy z9Del0%UADTpO$_!uEyQn2L~I8#jhEiei}d8#?&keLJ729vB`~G7$1*gu-0k~gXCM3C z_kZ{D6Q8_(>s_rEUVrt=mmXXHwO{*ZfBkR#&7b(p&wS~NfB4?Rmw)P~|6&X)ckbRT zQfYUrNt3!fJbhw$d;873{hd$!)qnkWzVgQF*WUV%{+)m8@}qms{W@(-#D-Wk~{=!FEo%O4?e)HlZr*6N`3-j|khp)`dojP;!XvMdZC~nfby1ALH zCM%boc=xqC80h@^+Px`vR#wpHcDu!{S?J_C%Kl`E>RNxP_uNN6`OdW)AOGA(4mJnV zH%5JGy)!sCy0kps=^rXpbbEXI`$iXeTHHPu8ick@(I!I?TN$}nUH*ISDFO^>WQ~j_A{RtdR2%H1; z#U!YthN@3ta zTLp@=@f?+)7g1!iQiPZY5a`HJi4GD0Sx}gOL82fMVT{ZewZ%wIP*^|}7||(5wUD>o zrqL0)NftHh6SWg>ki|GUs1v?!-?>X5^_e*vip6jN(u{+Y5Y-(HpQZ`5= zjS*2LH-lGoL+nyR+Gqt_dafOj)K2ATBQcHQLySsCPsXt4P&pSoaP5>*5}9xEXcuDO^u`tqEgB^H-d&3Fh~>GL?8&3 zh?ocsDivaALJg5QM&OvvI6F;Uw6ZkI>#9anq>{37x!Fg~rd8hSw`w;46Q;f~x>Gx! zAO#lEx*-NDrJoYRn4|d6PAqz_4FwQj2t>P+!&aVGv8f=YSz7t9Jv^wR*U?8XsBjvl zB2uz1>sTlQ7zHK77|@FZ1qy_iL8XRM^i*2fq^gT4mfrfd>p$ymqeYac>Pk~B+` z@noQkX2Gd%fYBJ`SOSNdcR^#!ye;y`G(4D2n&{aHj1ov1sJdBuQV+YTF>z!KHi0_5 zR#Q69wGt50X!W3~5(2a`t!vEeuqoSlyP4Dq$&!+QY$)geDKxGTv0990H*@Bjg)u}h zp1pw1vzF&r2Tw+WF*Zqq^V7yCo*aCQWRj6l&qfq4usUD=-KQAcWcg zNaKB!0E8Z8T2E(291Id0tb>n8!8;DvL?1wefdVxg7==X;SP&Ud00;mPl?W&mV^jnT z5E_5t_|fIX#a_2Nc7v-|?(FX$E-tK&%jv)NEC0%u|KPX!Ej`{`dH>oEKK!v4E`R9! zy<0a|7tY_^+P(bPMbkB8c9JkRxYC(Sd7-s3jt9}Corb%MiyK>a5#qwa^6J7u*$k?~ zhcA5O^8K6pSKr&UT~kiR@4w9^E^Hs%fBK2Xo5|qx@wI23e)6U7ehiZ=HGK}N5Uvy zO4kR2WaQGN3y;2arR>hHuAWmjZhX6y9^2b{ubAg)U7kPn$(uK?=e@ABe5~4?vaIe6 z?p=852>Vteaph!tdwZWT&y%(i7>oudk3Pb%KOT$?s@0We&pvW-XK&o=A3bySk#FAr zs#w1F;P%P2?hDU6dH?pqUhmlL?cIH;FC4$Haqq521qcnm)HPG_5>*fuq_L|V1f#S; z*r&iTf}#QVgR*L;nMn|0uprjCjCEnMGL%Xg-}tO(H%*fmQ#UR{8vu!vRw@bykQf>f zi9YsKJ_0uefe-~?A3_eQ54Pqw^ZoF`jNpR@LQxcBi4>e}2n|P-;mmS_4tQ@K3mXF{ zJrSA4Iu}zgc$v5eQ5-NDoi=qyl9X9*4+dm38KV_N&*B*)^|C_$|F6CPihvjc%$%$R z0W^RW!6sA`+F+6-p(bb-eX9DbCjbgb3AB=|@~o@@!oXaRVSp;QA}^-Xsn)hCE3w!~ zbB@s%{z#0(DTDt%@ONQ5wj^AX<^2NCX8UF)%0*2+~S&7XukPZE8Tp zh)GsB=h}Kfrc+NYW-7AFxRvQ-;H!y>F*0g$J{qM)O`X|9E8GtuwY0takRXbSd73d- z`&DW3)^ISLPVS3uVyG7Tz2QM+yexI{{ee!3+U9C+njlIrWi2+FPIbEMtR4;z6}L6Y zRAh56uJ(InHJZB8$B@~sM+qTLtC4LoZIrSaRB*8gj9L+y3`{V_fL7D0E>!9Qdyqnz zsyZN}0|Qy>L{d`G*NS-JM;2Q_MKLw7ZJMdJMq(-XQos$gO{H3wI#(` zD}<^%OpNXnW)LhuT85wqt8$zd#(STdECkRB9dk1?D`E2UEo!T`X;qAJg2sE|-V$mquVEGx=#3IM>G5?=@2os4lBh}wxdapKse z`SxN`bUN)t=XAT>xq0K=$@K1n%{QjQtvS=K9M%oDQmvGlP8yxs7!#X$&rKw5n#Pc+ zTxAsax=eIZ`REMS;5SXEk+{TTY*KfRa;^dq)`rNrQ&pz|QH@@+;$1gwqy;ooBADN%3gO6W+`nSG# z^}$1+9P)J?y-r3Zv3jj-HYbzOSdJe%Qp@4abUYs6FMr~5gL=EHch`@s-r60u>`^0= zt({w`ZDksUlgZlh!XJF`i${-~IM}Ne=2w)NeCO>i{%e2pFMs8$?~3VqY+v~BbHD!2 z|30J@wKiY4{JFt)5gO9kojSR$lF(iZp5D809Z#Ko;$Sn=#s2crh0U$^QSx8@rN8z^ zU;B+ueEes(A568hFP=Jb^~%fdymb9n{^IAp`OPo=!Y}^f!-sp9pT3;st#5zxyJ@FW znBl$6mwxeI{rI!bz4#yet-n33cAtIx#oIUd*EJa+Qf!>#e#4>peEr5mJWrY@Yj zJgje*VN1{DE9d7e07hZ0R1+G77(&#BAo4gBE@R4wpj0H^G$B37r=c zA0YHi*7ychSj@7jsx(PC*)Q^(V+_t)#Bpl8_tvQKaFlA()~1z!A{sUmh0u7{YcH`k zNC*6wx~x!R>W&?0kkpS5?!xU$h^T zL+7Km*|@0^AIfr+W+oaQYlKjxqKrs%b!yVlq)c>H0!+2GPRb_MRHvCO%SkDtLiIFa zfFVg~w02FcG4-`75{KkDkP?G|Q?eX^RcHpgg-u)xlxN%fdqunDSgNYZi4s!cnoO%n zITb|b*=wDZP6{}Xagy7{RhEoO%%rZ{1fy~ai3yEcYIV8snA$;8*}B%&6iJ5&rghoG zF#@N??g}%4*1|kV!D9+6+Sf`Yb%+!t#sHOzVN@!VG))iV&=UubpiILYwXPwENbr%Y z9yHT#k#il96r^Sxy#<`=j(`t1g2s8(u9;gIRYM)yJgg4-w(wrnN?WB{fk~S&Hp6(> zbM3;ZUaPg!>`YYIN{)=B98CAu=2oQfN~0jT2xKdoW|Z(j;MCt<$xk!L^~1wBJzUS8 z|D{j-#l_>tTPt&CpSV1+kgXTWGzHNUub_h9?(K{;@*eCO}I{JmdYOV^z& zIzH5vN6n<}aHt4E0G4*%^3G4Z2bQczOK&7`O`M4Mx&);QM<$T4R}m=GL5XJ1K4MUc zwV9T~#$Xab35G-kU)dlb)zCC$04AK)n#tA8R4EmMsQn2k*_WZ#pcw=p07ceD414e) zAgMexM&WG81HMVLI6n<2%2E?5vJ~W?lwd>5RhrDcVk|-HsX!LAM^!=at^yI#B!rAG zs4AlpvT1NW01m5?lEf1>E%w2G&g+aL#O}IKmYaBQ%{!% zvU%hEh7GP?d=DR(%X~$ znF_~iT{m1}rKMqp2;3pd(J2w~bz2gnRa5yyg;-B|-7fo3Rn@*e?AS~i?h>5Oh zPHg6y5CpvUM384WCh6g1f{3g&kPue{qPZapk$rS9QC6eY3JFk~;CO%l7{NE3D$0x% zkx1Z0%Bs=E+-n9_n}oGSCNCOMp+ywniN+L_)L~K+A}Gpq*;E=t5I_NH00NOH5tsxt zKmc&SQ2-*&iM2uj1w^cnW;TipC7DeivKEA~OO2hlS{DW8d9yc~&pPB{6(9wjD9ed4 zN@=YFi$dz^HXujm*wryOO~$55aZV%2T8U(~avB&(C2XZe?4)V*j3Nk1K$tPriLtgB zj^|sg$#m42?;q?Bt1aHcE=|U}bKPR- zz02y;)5VGG#(7d@x=wn?gG4PVXaaCxA&dfq7!5EXwxf6-6Jx4+LRmJNPLd>vfji1X zSEFzUac?>rv1>qTH!Ucs#D}U55^Eo$fy!6j*Cw+Zed$@DAfZye))1joIx;gYMka7xR4uT!PJU_ zL;z+NFfp?++psP(Evl?qsHF;0Wc$^mXIcg5(ttJzhiJ7KxXM@q>@}cL2*9AA;t(}C zi3-7qF9aiC<4aSEi$tPuF?Lwe@#|@X#_ISFjGa6&v zlx}{0aeClAC+E&Q_2z3|%{%LMq*a!O1P4v2Ri>~*N*#{2$00eLw(Idd1BFVCzP5i; zL3-rqr~d5c|MsaDkDa=_D6vEel!f&;Suw-W1oCcCN9Q<_G4sch6ZLWkaPqOgefsRF zFZ|R0cb?pC_<#_!?dbt`QcVDWVm!nEz$_xfJ|fR1odgO{II@?DxoWZ`iU=^x_>m+Y zql=tmI*;TNa0FSvh%!o`5bz3}L?Gp34Vql2A~1wdfDXbTMnOU(R0M#GLWvfUz%lxW zz(Hv}vx3(YW1M+w5RpWVks|;IGZ_yY0G!4k=s5KO9FZs@RKbBT z&k8?BVPFveh<~Da{E+|>z~kM|`r2Ff_UEVRcoL=u_ePs1=NA_ik3G0|cWH6`-M8<~FD_+W{m#`lm3rdg z*7XaIoWyoKoQzg-vuM@uc-rmXeDBKWnWtZT{rg`Sj+T1u^=g8*-km)5)9d%H?=JV% zqZg9#XzzRPzTSG`)OYvB7y3`__}-O+hr{vx&p-dz-S-}5`Rbjkhk%9gjoW-bZLdB5 z%O_5+og?r6+3)<*pZobgd-me-@BQ#w>uRYx zUFx+?-Zz7DA9@~>`KO;)Ut7HJgKxcb@Am%x^*{W3-}&~pN5cV!pJpLRXZr>f;-zcwLJod2`>7}QZ?jGFf7PPpW zzrQ{1Wh((DI}h;Kqus;Foh#UzNVUkXCGguc8BaO)qb$IS%Jm2S;_=5PY0V1y1jfftd#D? z7+LBjl*&SM7=mbR3E{9REh-W=nhrzR&Jr*T!J)`*8JRgrEJ>%Dv@_#vR@V+K29xsc zq^2wdLqz1gn^sdIVok;<0);Fh6gi483M)Va!RR@LK=gqcT|R({Ridg;2hWMI2wX=m zgju`QcM4gEg;-fqMxjLZ-bZU1fSD^bcq9p_rM6Bq3Mc@JsEANlKtLb@B!CmbAPB+< zV;^mbY6RZ1&xmr=O5to&EJz5zsi~&Ze$f>d5*BNV@wDm9^^39w1WRUyCShO^@e&iz zh*7iwh*~4FL)Pr0OJ*8h$ZUpQQYLW`074oQBqTyc7GWX+qzIj???YL|nx_ZzxkW|| zwyB*ef>Dwf)6_B5nShQuG6y3%&2)r=tlu=#a=xX7IW--Rk`;mLw2&6WDW_RoNgu^i z&q>3;GRs1jS+t}o{4^jNWyd;Eq$3g_u@X&8z=<(d+1fRt$XY8=Vi2pfMApj0=#A0L zlq5-2xx^SC4#E+|H;q^mII3FbSt~*uS02DBjXG&f8?O9BX`B&UopZphj?nm~6BQ#M zq9K)_B27jS6CO-Ur4&RC!OwMiE;tVaMrw$WqeArTvLp+U5vdAZS#42k)InluGw(es z6i{PdcXgVQ4n!qHu%tX&22vb?2d$iIP^;Q|(u#ek$cYFr8d5O?U{nIBw)?I^rPC~H zLhvZAuJR-`iJ~YvMnnuEG0x^Oh#+ne z$TTz!Ae7N)(!OzxRYj}cxGK$)UAIs5at=PLRO9y_xo~=aa61R+h~8}7v6v;Os?lVz zoweHC_a0n7a&|%PO!j%uWId+_)&AkRwIBQCzw)1c_!mE%&c(Bs1odS(u0b>-Sh&`J`GqXWG z1JPM9un0#W01?L$0?Yyo5D^K9C=v=pU~!~C2n94CAtB<-7kUbt-F@-P3DKlku(%dA0XX>a>|`1t4l!jNvia{GtRoOy9?aKCPCxA5|v z>Gy6v{Qe2Oy0m_BYcM$W;l-=_->!W8r4PLr(O=oRec|}2>kpf&2YW5-KJjDczV(%j zh1HXhk)-_ii>GWDCX?>YL5VB9y>k5IiAz8H!7HEsu}>#%63dZ|;oNEK!`;P|E%aGpEPkeJzQ9ryKL0l@Bh}-zCE%!kFVXn^JjkMbMI}wRZTWm?HQRYm1bv0 z_UCi!1YUV?``JsEA3d|Mv@E~->tDWb`J*@9U!Ko)7LUx`xq0jK(xu6E`Gv3j_Rs#( z&;Nzb|JDEGzxvM}KfU_!-c^0Na3=15JG{_x8H&;>pYF_aD4dH92?o`K_(3 zg`@qVbNtnJ-}sr2ef;i&I^h24^T*tHyL;Ryi|@X>dGSm#KX2GES74IeZu?>)kN@6p z|JNV+_~YC6?=CO2-gy1WlaGCBZ}LFpley(&FxtGkb+^BCaafw%L_cXpgGpj~AvBSv z?Ov}usOQ@2OtGx)Rn0gmkO&erN3ApqbJ6OUG#Tt4pu(z})*(Wo4x0u9w4xBC&>(_k zHOW;T;K5?j*K9x!+Gd&K%E{9UZ*0CI5{)9q;9RXylR|}HeN>1jf(R5rA!M08129X44ACcu5mPS&%!Z5s4_J~` zTJRv9%wel2Z75_E!XkDQmR8qRyvK-&W)1@cqLgX^f=NS&N!G5TPm697LuL#SrC`bE z7MNU2OMy~WGZPUiW^EbSp(oUcN^3i5MyX1H(8U0VlUQ4o)v;;k7^4foU@6P8-QiwK zwUk!VXe5f!R*9)11AvYQks&QaIDjxi1VITQN@OrWM8w5tj2Sjnh+$MVBBD?O&gQ=; z!Fd;zQl!+hs+){9i^_F}|!)Z6?8K6@84tP=`pM97puR z%xqv5@2XN;f(NYvvocZy<3p&&;~1kc8e()+-Eb(Nr?aZA!cl6x+gb$ay-APpi zZStebH{QQ?0ClgO&;gEu=yd+rrDt<%I%)Fka&i04HC?z#IZeCEkrqGuGk@pb`&&PE zY!&vl@5*qz-I$HL+ml@`^K3U-D*B4LewB>PGDR)qj39~L29LpwyZ4VgvT*UkPrdYe zw-@Ys>87K4v`7+W?#0G9?~O4Lgfua;4z)0fC>ACnq!|}PqW54>M1(nt7zHS-gi-Xw zp!JdjtSq8X=A^bV0q4aVmIea?Fae67Kn4^60Vpy@j=-}F3WikAh#(3ug81k;M3xX@ zpeQ7u0TD4GcoYxZCLLHNp(%p{tpp*=d|C)S$0`J7R){kjqo^>!=mS8Ez6{O)EiW(aZttExe){4g&%F2Ejk8OS9jjI@=>Gd}zx;N6 z>+^s9uN}G6|I&Z=kCwV~we>;IpPT!{rcd77xZR$+Tpz})w|I1A`Sn-d8r|vq%IE*> zx#ju);lKIMtR-qE-FA-#8z27EXJCJ8{BY~hr_S8lx^?`_r3ogx(y}h=OV6s2*n?{fP_?)kH2Kz8NC{C@%rPbux^^H?UkIsqQ+P!`L#q~FC{#LTI zx^b`E*x|=7{_K_aM;Tst@yO!`gS(sM{e1oO;bE!r=Ji(|o>)7#PIISP>#u+BU5xf) zPd@T5zVtiKJon<^!QSJKT-vy?d++Y%v7=|7d-laY`pWOa_=iVN{BK@5qNwTfhC}hfjR_`@gm@hi*v6 z7nd5@aq#|oI~xg|IQQ6-)9L11e^Qk%ug`bGWPD_KKI=Vk@bLc6fBN$`9$tUAd+jHl z`LTcgt$*6L-N88AyuG*Fg*rZv(aM8+yUWL{v8RyK-3Q+|e&oqs>+x@1|FwRvovQRO zUcYnqt@Ec&O(w=St>`T!yR9Xb(fsoK`8=Due)YlV;6bY|D)_ci6Bzi0`@Q+`Mh!?h zF(K9vgCd)zdAnV>$UIZaB9PY61+8?9QIqNR3-5jHe6KerlFau`6*;Xf&(9ZgbKRbZ zy!74IO}{r79EPR|!TT7LL=X{y6j2jtQY4@Nl_)J>5K-LBh8X}pXp3iLDTx3oD)j6_ zWFpNhVKOO#HkvdN2*nryL@Cl*2c0RUBuWTOEJ#`N5c5)vRFDZ(;F2C)bc zn2E$F4uUL^R6&L~)(|xz3Nonx*qXW|Nt}Y(vLQ00Eg?3IR~lG2L=9+2X;h>r3LBkP zHT&Q-qS6cyl~Oj%BUjN^5hO(Cd?Tn>Fh)^`P17)QjFE&w2nY%RI8>=kAW|>yOeamN zl^?pTG{>aM>bgV@y{ur*F;qTO^R1+52!RBI5J)Qx9EDIYnyOV6ImtwiFj*9j7&Qx_LP3NGz#0(L2vM3S3N;C9v=xg86pTt`%u=vUbk-n8AB|{bj0;T=VCKvu z!YDv-8W!?4B8Ct$Fjy!os7<1cF&+&o z6NCdtKp~DG=tVrRq{@eA6O*NBtert+tf{Mt2}OW?wAQ%BGjj+bOR`3zXo{kd*(u@> zy}||&5Dqcy$7va-X{itpjjko=hydV1P-LR74QUPz5lbHgq7Oj@sM(9sULr=Zwuk~v z(?CKYh9ogfU`2Y{Pn#IDcm@;%L&6e8BD<#XzLm5>^bIg6^4>dFPt-U8SV*{=wqz2U zdRqGW$kK|qs9c#B-53IsP0ZZVk}j=G{B>3t>2BcK zM^<}_@NjbT-UKG`Xn#0){Dr5i{_$V>i$DM9i*1!&f92JOZ4g*qYlbqB!t`5HKWEdP z?L%j-)_^&Hk!TG?6>6*u9@}ptm$WFrU*D2 zvH%B<$V!PqY=CFgI7(0qTB0C0#a&T;CLsS&`HB!-l(sRV6)|L5)gdTrLe(Uw22dlS zLID6q01_oi69ochmcX4iBC^x%SkH zPw#A8J$7X2Rx|Cc&E0+PjR({1OOHJFGe7%@|LXt#pB_1Sbn9WYy0(7$$+h8N`s!O> z{jtw{@(161W!j`mFJAcCyDuLqI`+&dQ>b76o!>B>mFJ$gG->vaEG+P2tE+ST`n?C- zFK%vENOLxaJe_P@+j(s5#cTV2a56KdiMIxao!m}#c6Uc%<=DC12fKN;zB4#nKRVa& zr1Jfyp|o({-WX>^|Hfwe;9h@zW#R1`-@g39Q(MK&-SN)luk>xxLXt%4jZM*}S#+>RaFa^ymKE zo!bW|7SH zT$=yT&DZZ-IN81N#*NM*+RpaiL2G}{FSYCCW7ETJ;p#*R&+LqUtKa$1*6z`i3hKC% zM<2a+_ZwR~S1+7??)%^Unj4WU+6S9ExV*nOe{y336uwVMTv_^=F}^VwtzLg{=OZ8f zm9M<~Yw7yC)p#{c?GL_u<&jJ4mrk7C+1i(hyL9Z-mD^EIWNAK2TJ^14FZc4rCoa8s z>(1M23;mtASC7N`v7^&EKG@oxQ3FT-6$I}V7v|>YItPPcrWF z0E!3_2#F8{oJP{5HJ}F6qGp{jff_dr02pfpv8o#pvDQ{8jiZyu3Xl*1B}OMf+A7gn ziG&asQAm?RanWm;Ax#kh5rqj*0f7LK0*MNd0ThbXTD)TfQIrZv<-CchvKEY@05vmU zQ@f%lV(=V&J1+)ws&7tOKON4&Z%9I9#L8Fog0_+gHQ3xuHF&brsF}Hc( zm?-PLUs2guVhxa?A)+XrkqODgNZP5hZG*zOlV<1Dm@hXZl#FH{d%P6&xoo3ilMXr)jY{c3zyEL~|S?5VQa%H7O zM?j@Kd!rykmnh31hSWF)LO}u@JOWroBMJgMXb!E?K>`ug!DrDafyCG;fr|_nB1050 z5(Sx%(}>olk*N-n7X28#cMXWCf@M;L7Kt&V{7NnJvN=V@#y8Vj~>5x`J`>fvfP>P zEx-4T+n45_|Lo^KePd_(`M>m&&tBHsw{Ks0=RTOCH2qkW!+U;O$Lwg(3)A)9B5N3D z&FQA45wwCL0i%`k5~FUslHF$ce0Dh6a|lCsAfTg$T@^b_0s4VgXu^mDh)f6|z#@cEa3tQ=QUM2zoq!&As3m9FV5fnJ1(X^~V*v!3g6t%+ z1!aI3If@`EMWO*mbvXlnl`=&1K{Pm_G>sxPL!O%$8i)=-2)qEW@(6+u6(T4Q3;-^; zs4x(SK%BL+90Lk7AkI=q(A-F|GjU0`|HD3uC1=+i>K7_ zW&iN@sT<8#e*PCf_0kXD6GiXe8BbO30dMW!I{x7gzqmhX>hb;KbK10(!uIZiL&v*B+zI*%nO7|p|D-ZYH zS~}nHFsgRkIJQPk9_FJJ2$^E**$q&&&QRxb@T1vSXS2-2W4w}a*c{(y5Dv(}mYAM9S;T+VNP{iQehNt~AZh3S#%T)lH^eQ_bB?$P!2?a|#ESLMViHg$6I&b!yH z?>_eU$7`6n>iU^S7V>^FsB0=ZD#B>@VSp+7*w#zj)W?@r+uix`^yWgpR&J~-Jh61* z;vGdNaxm&;nQ$17MkY-v z4lT(vDF&HU-^eTo9+;ZinnXgP6~r(}NmFF@+-iihQbso+BC8q@Eqa#Rdl1q@1ZuWt zAr@dpueGR7ZX-_==F{;g%Z=7~-LNr=bXqkIFtH|KZM}C^aR}5)*G7ZET<_RuvY%&( zi`e2G6eDq}D~aWCOf&TDo>Ud*G&7GP#S#OQ1X_V42%-!Ci6Q_n2!YU$q?AlTlNysN z+caJS&ho0pZenc;WJsB&X-E?S=tML7D1e%XbZ}wjo7T!wAaKnM-ImYOGt`nmbY1Kq_(Wyv?sIs7l2M>_vpdlL&|w zR8fGmpeR656a-xGjza`!X^RMe5CH;W6h$HkSYZT+sAyJGi2xP(5F!W&fg)syEHKma z{;0kZQH*gm21Y~>kQo99|56Q}o%`VFM@{jWyn?FJ0JNLKo^0S}axb?kE?S1pD`)}O3eYCedmmGWh2XB4y>E|46 zf8iVd_|fy9JF)is-~0Rj`Ny6-x&E2Qw>Nga|A$}cucS{OJ3Y7WTr&UNAAbF#pMKh; z+2(`6;wqAlnQs?Cx6?^}HVn59j}EBUwhKp&-F@}m;k~=iPbI>m7asKvuHL-P$&l@w zO)kAaVRUWWb*s}jIezvrqPYN`Kw-ac{_y2beDuk5uz4`u+a5kF#>d{hf1};ip>S?8 z-CsUAc(DD<6Zq<@Kg6z;MSFC)xc}Z;&D_ej-}(0PvE?jg&9FLl{`fmrUL)TezE?i| z+2?CBT8$PSw!i+voqz3@f9l~|ue$z|Qg$AH{QU3!^Z!MGKYMEZ)z{y;c=EZo-u>3< z#Zy<_y#MIqXYXwtf_?Pb?t6B1mp2!dRF^{9&gX7ie{b#R3%;4yR z2ZFENc+=YM!r~FT;94OBsTG^58V8_$XKpmy6&F@kmlj*aFl=L-Oa^7!t|$miQ+7Hg zG?To&R5^ctYuEb-01{%WC`4kwXfr$4Ul@Pd`wwpJ91afz!59;wCqWUJ!NPzuH0DQ-B7MM!kQf6Ca|9NI8BPNjK>=u`6=C$1 z25hC7MO8KRAyk@Fs*M3cX5nC!w$=)&pg1^BEHkiKS(PNIMnphh0U?E?00CKqxg~lZ zAfO;XA=DLJanq0hOJPjQP-UDE(h*Y9xwc(hR4%A2A1Mp~Et6waueZC66H6*cR6-FU z0-(`^LN4ScV4xIMp zgK|}@N%1t+oyFeH-hM~-lmQpK)kd4lWSMiGBO^h=O)FJpv(s-&sP1=K>Cm|}>1lLP z7E&5T7*|P=HP8;_IB8=u1XQE|Nkjs~h~N=~Yc$3*&gHH4csfn<#5bj}YNFJ@yUf@^ zTR$lYxs&87IGbdolBx;0NlU=Y+OnLe=)0X_I;lcr1PUm|q!T|*fK)6j_@c9Tu)kXr zg@o8iTFNVMRB69$nBw83zA<>Lb8$q;CKpBLxU9{@qq@A{s5Z7A0s;XFb zd=-pFtCR6`Y!a=6(j+Tg>~k`;cE2urNkNnGLTjOPzH(u`)h8E$V>5|_s;>PUCaEZe zN)WZRQ&+Qr+BMcBMAUd9LTe0qPlyq~U=k>TXr>4zt=!bIhAN~*XIdwtrR1Y46d6 zlSg-V?yMFaW7@ksN_e4hS=(yY$q#nQji-*BSHtZ1`L*52HwUi1 z^z29f>fiWt$+7SJKY#JB|J~pC(z~~=zWMORV7#|-_9KDg{Bq~I?cccn_Qv(ytoOpD zGf#I8rgNP@9izR<3~7ogc$5f$1`~|7X%EMvYqzhyJ}N~U?is@_X=1sNxEPruSi{jX zB1i-alp7@yMdHkfP6Ut1s|+4`zid#jH^F|KF<~*5(ZKbnIC!N)Vb3qKJ?tv zhll&;o_Pd1eh(jh_7^^L_5JUyEV}Z&+lwao;oINe+jzLQBES9nfAFvTgvz4`ENQRG!s{r!LZ>+{D>efq6^d*?ME)IE%lzf`|7={uP=Y(^2x>G;jNvI zed_1`>;Ljs*Ru8blwbSaAD&o0(qC2!^Q-T_yLo^2o%2thPJ4d$m1{Pc8|&V^ufAuz z4n^br!lRdt9S(jA_vpb>U zL2or}FRkY--5R_X}zhizIf`%0=nP+<|~gr`TV`BcXrE% z$LEiPTkg#1Q&%P%JJW|t!E~O?mX8$gUAew~OXaWV&{=H9oY{XbKU`<|A)9KRUj4WMgN}dE9ts@6uyu#t&p6y->x}xo~gq z&BbGM{^F_IH}1@}FFhFESKK*zvb8@fwhj)~SLYW=Z|?3727YmF`Cwab?;p&Nu>Ru0 zcrXm&)1spgMuQy-#AWG5lU~tYSXk)KEd-8R+k>;0mbxo*&7^4+X8-==y*GEM)joOV z6baOIeXzeDLLh;vsuYqUh#!oDf*=qP03(706K7Q#0g!-102m|))f^x~Vs(rXn<@c9 z0Id~AcFqxzC8bb7V9!+zF`zLDv%~_6bB-7ZwPs<9kQ)ObTBTfF8>LfY8dq8c+@QC# z8d`mCu-)s9VqMe%8o;z&l@yt=Z8IKo5h_$5z6VI2Gn;4w#h8SsNqQBO?%%@=bKD_WX1ly2V0_sMU`1 zWmS)K>PhQ}h$^W{wizmALx@dfmKKkXCPQtVve7rGBGpW*G%*TvE3+J`-S3>)y!V%;%%=p7}-lcWTB zB#Eq2+r;1`3G74TGNL50jd+96yV~ezDDid7NoQGJho)?%X=cGqlrkZ3%N8-R%~Kye zQ%Fs^V;k_JmWwJ+8qsarKNwBd`b*1;Ppm$3zPC`ESUaXd*6NvjZa(?)#+BW4adoZe zCmn_aR9DLcx!>w0^$u0%vX-@(J%Q%o*4GksI{bjii3|h)fRS(z+)QhvMOjiB5k)9Ut<+35G>asi6;UDz z41f@M#u~v05<~!jX8e@!K`#*ja5iri0YU&!NElgv@P{C16GA}zliU}rD8v99(iSum zxP1QXbC*wK#p3+pqmJXF$L3%9{_9J5@A{4RCXJmcefMDW#FL+YBk;hd*i;nIG+adv(A#{GAK>D=EOpFCxj=5}{CMhExrKY8}WFZ}NR z@bq(!y!OhMf9%8Oru+9s)8zKSo6oQI%Z8>o-yUsh6m8OUND|hZ!y{9%e9vr{4mM`vg`o)#EUp;zcW#heZ zYhm@&@%5dD56a2Vkx#8Uedy*4{(hScpW96er_ZgpvO=fR!%xvpAVS?ZtdSa+y44hG^1`Pk3B za5%7ot*aXk?j7t6s;05ll+Ly1=ZsAWOj%A`Ifc-G#26yX9&AO3qP6u+qqXs_)})9z z03!k^6{3hp4KM*lMB$)_P=sPoF&a`rSgPoemujlp=#IiD_P>hm-yBWCtQ;x?9tl zGF8U_0bx`+?T>(a~>mk-8QY?=V6vaA_It3)aEJ`_nS@+gMNP+Jo*)?Qe}e!ttUW9bo? z6>2h+Hi$mBJRU|D$vUs}l-Xh`)7V$@2Rv+>bO?s}SQj<(P$Ys|$7bX!FB;Wn#Z;=Glxx#Vzh8 zN%^44v|U{+rvC21VAAcJ%i8^V6z7upv&%pK-py|wOuu#Z^iO7Ko8WM9`5ZV&6g<3r zW&hsr-~D$!^*i7Ee|_~2um6pI<2Uk);nizbjvQb0lXnIW@!oBE^}T=bna}>E018NC0z{g*vkM~NjIbdr0yHb#BJ`u%Q2^s? zl@DB`PyhvfbVcw3MdJ+W;#t$lIAf!HFzfa3$7Zw&0HVkTdLa-2V2(3I0M7DS{-p6m z0r;07W%yBHMF9CzRIxv9cte=k;r;QipdyI?iu{C*EcE}%zxgZw=pX#|kDfa_p4=FU zez4ct9K7VFlVp17;l20ny#4T*b025ESCtzNHt(*^pI$%r=vTh+hpXA#^|)m^VSjSJ zJQyE&^7#uFANk#{{lTCbojd#JV849&iBGPr7sZ_V@^AkjHuZwIZ@qr=vp@H#(@(D4 zyXn`CUK&kQ|HPSp^k4k;YHR87N6t*{+&R2+?cD0hr3>o^^>nYEifX?y+JFA!kt;X8 z@$@zmKfH||_*=joH(ql%mMym|HR%cstL`iDPkT1#E& zzjbfF)m=PYEOhtoZUM>0+Z!)j{`7DB?w8L#`ON%+{l*WzIG0Sn^{sE6ICW$?zSWyc z%WD3;cOPJ?EoU3!-v0HS$1nWE#?9^F_+Bfmf}ZA!Pi{`@y}CBaR#i2g`Yge{s}I&q z_xiP)k34bi+UDkXoWA@1t3@B$ogBEcHGJvx@sqdiy4O=U+IO!O+)7duVn4*dv~*iOjNt zAja544l>)Kcv{ zkQJ{xS-+EXQdl9--8OKn`>i!!w>$0mByTc*IG?MsocdRSS^Q`M@g*v&g6z(tQG>FM4w@Qu?jeLg)6ca z3FT?l%5wr%!bN74L~CN6D1*ANEh{=VDp5_Q%T`(>kmM@I0NpfC!4x{vOcJ6JAb@t3 z`o^WoSW@C8rAelysjaLotu8f9jG9_qX11`(P$b1<&{(pXj3yPl(OR1%>oWM71c(Fey~g z(pe%pCzF#=0aH;Om8GH!ou`-;Dlb%;>oh@=u+G5F=O>TCVk;O)l_u=B=IWT3ban0Q z~V)CNhZg)CfrxttpO4}|;WpX{w zzSq>U78IFT-XnZ~n14{-|K$%5DpHD+Qi_Ox5D3`(lKsFx^ zreWvM-@LxD|KRp*ufh}O&%E;H4@o2WfJWSO*gBkhY;CclcdxziCJp9#;rQ*>ubrIl zT(J4}gKO{K+{)NKdG^U&e|JB{U--*^3x}6ZEIqPyXY-TKfBb8|`-SHpJMqjTr_L-s zxX^`u6wD~>AZja<iTjwty`R;3fc(A*Fw0q)2|Je3};oYst z!6*pRc(ng;@5VlFugx_F8{h9!<~GR>9-cZXyZ2uSqpRalSUmFR%QxOXa%S~oPrh*X zl{fe9+)8v|px@6=zW&l%FTC)=?|t=4cMjhYJzC4p-n;$q$!DK`=gwOPw0q^jmeLpI zvUAN~_qDga!e;xRzPUHqP)SlwVl#=i-g|F#F0G4vde^^x!EaMXXD_=!V9}w=CM;}T)o?jYRlo;Tdzu~>(T`r ztsGgse(QdS`Ocn4GKbUq9&QiDZw0b@pI1EkceLc&YQATU6Q9z@f zfkUX3F-8QVF#OA(S@oT5XJ=oP2oM20Km*)>IAw+sha3YD5Y!&Fg1e`Mtk#OiAW4h_ z2rM7WfthFEQbMY2tj>&53KVGTHMU4njitn4Y_!4LsH&Z0;zK)c_qubaOp+x1eh-0D z6%n^d?^&&bZ~F6GH5-}H+IK8w)&i&rsRqR7x|LHGv7(^F=gl-26O+tmxpocqlL}mw zsD4$U3#GEt&>UJIV^oqP&aqaxu*z*7eFN+W5-1V({T6oa3Ph6mlEg#oLV=P))CdVgO_D0f zQ|T49aMhsQ;^6mtodmoXDJ&_Cqy!Y9LC}C0Kru;8RC`nOg%4EtE#w3V^H63eVyk9gE>ksGE zbcIJ37K?Msy`Q}J=&{R>Ep|GKMJlO+cAB!xQI;lM0e9@;da)p0?G1TH=dPWwFL$%n zyv`IQTgqpS*?xHCD@(S2EPvv6 z|KacbSO5OMdA756`#WFa{dD@hH-<02dFP%z`~1a|3)RWi)7MAp&G)V%*EvmFCXA<( zt^KhNM9e0JipyyQd4zJju{ELX0bMvU{=5JE-y02LJ56$>a;6SoKe25^Eo%#{bE9*m z+QznwDM+;dTgtX<+EO+_l_`^;QVbSVf|wFzL@7~%XaQzdEI@{s6XisyqEu0?RH1as z=(g5vty@}kv}$YJ)uyXWf|Q`rf)$;#I4gPq&gRP1)l!u8wt z%W;_(sm=A)=8dx6T0iQRS2}OKe}he&J4SocYYmPdq1Se{Ci{^``#1JpTR-*j@?QGq ze*BjgTcyD8)JbU4Z8(RksdJFyIKYaVO&U*6L zrIYWx|Nc+?nP;xN|MsOzAG&k*{wF^E3zr}LIfdP=2e&@{$WJUS_wDJE-`g0x@#gho z`4hXh_s<+#bo+;Ms&jPy=+=XWZ@l@=vE}|+(c7I2_x6X=F}I4vgVFTWci!AR+_p-F zSO<8@F3aOV_uJ=O%}DuH3otpc<~6K6?G#`?0TE#}@bf zbayxuPc~#U!De%4j~{N@?ymA#7CLl6^}4ZjXXom@V(!^~ay~2D3wGfXKlYRBCr&(m z_E^vDzxdQg$GdgQ&Z88E`|j|dyms|=X0m&O?DoU0c7c9;d;9ixQ6}@N=dbN}r7%XH zrs@9vel@N4H#fD`omN{BDx(0=XhQ@H5<+Oa4EO!!{N}TN#*?EaM*a4s(Hm7KTQnIDef!o#WK~g zqhglmqN(a$(N+TOqUBxbs$r@rO{u9SMV>Wv*=uJ_IfWQ1TuZZ~s?j}AOE%+@i|)w^ z)4{M=*qu`}&Qk1WmUyjAvl zQsf|RqLi~aIuvX#(efHO)2|Ed3my8Z=?$rq^w{e$<&b5kiI?Yl`LVS^;|QZ(Up-eg z50{sXE9L2DFHPNj;q|TETQ%>EcyM$7-J>U-gwfG=zy4~z(FcC@xBu7wx}5CJFPbA~ z^9TFyUwG`|<)=UL638D{4>=|s>nBuJUkIa4NB2<1SzP$85F7EH66ZADVVT&YZ}oG3$_ zw`ot?Hc%#z17?U8K?`I`=h~!7TU3_OGAafPtN;}t5hzqe_6*DJS2!%8nqn{a$?wIoP%zBspbYim1o^vrWl_mkGa12cHg zU0*yu+8GQs>k}(aU%#@CumM^3%{L#k$^hZvele2yxzWx2C3SrN{;L=1;pl}IpV=GS|Ip`t!n797te?31*4qndXMN@9t*u*ATeds>qo-G=?qL6Lw7D6o z2|RlF@#`Dke|P7`!urx;Z_()D#)BQNle(0!+kEXi2d&Ptx9(S7$6|T$&1+jn&Y!I9 zO}H?BfATO?!*aM=`>NNQo0ra~k3D<+O0&4Ow6(u^>;Bf_`ZB5{NK?#j*rmt5_395E zyKt(0Q2x{lpC~6cW4<@go=v&AcY8S5dboS*(&ZC-!~1m@9y__Tdob21`8c@r$k{yW z9`1#9cX3!AqKi|7d7k@moi8ouG;LENCfRu7%IT%fO26wX*cm+B39r`0_IvlYnugMD zyng%6-3ODQKRoxuGqa6s?4UXH02Nxv1s>Q zB$JNQG!4;_8MJz?sit{mwb}<{5=g`vPAwNui&oJjjaC_l(k4}wvZYLX#tvE;7{Tdu zyVYlhb;MRHTk~a|CnZK^AF?bHh(uJ^jnRqLX{e2o#QP@AP_WJt6&--n6cVGDz0+zr zwOybCt;S(sTCF7?jB|kq$%sg_*7kaH&ef3(iveJzAy0}(X<|}Q87EYcc8m^D1R=^u zh^J*VG*?yDCT-6ZtI-SWQCMy zW3^4SwWOMIWEE!y*gNo?B{qfC(h5O?XwVi_ zrV|5{D6I%)sHrd;AWf)1tkQ;11I`>=2}KbC$64aK5>$*0>dnvus}rM6Tspsa_Vn`k zi@vvjh~qXL7Gx-=lG1LkYGnh*3J%k**R3jT`zelG9AR@nJbSJ2-O4kITZj@7(?7H?Dr?tp`^gxIb|1fAM=? z{Pk}?pnGuc`Df0{A-%Ve#cuWN>e_VTzWI&s&h>glN+#CJog@+FP&<}PAl3DOb9Xix z7(P5cH~u^S#eY{-4YAc?n#x%+(s&yoiHtrdX3e5mFh(6&16U#pXaXmI8BiveBDKjB zsPaE`q=Knn29%>pg>uxhBLOl3hQTnT5z>g3If-nT6PAS02o#_*rc@{cm7^{|r=U_$ zmN8*W88e|AR0~ZPZ3~qJumH&)AM`=DZA6TShCexGcV+|`VMbIC6+{_PrYKjGYRrtv zjLwWMv@VQkY17gsHccx@k;Q1zq(H!0E6f*Pc>0AWE;W05@7%v}_u&4?-csDE-gy7& zdtbYwCj0w0t*stozco4B(B+ZA#>1ke4-Ove4z^9I-+%9R71E2Ro*WI>jYBBwep_wb zd#l;oJlenH}_m`>X!H2Rn;Et&};w%Knk=VM*;zAG!XSPdSJweWJse4fk7uK=rDo@ z8@AED%XRgwx^=6rKi;2{-t63dxp_DZh9;l#A!FxL=30OA`@a9j*5>5mxfgS}+U|Dz z!iyLG?A_m*M8T`q7tJh&ZsRCAu2p9ya_ZFT-~HgeU7g!LJZUT~^wapgtxZL=;np>mG;)izS)3UMD3>EF9nZzUXi3IIb1uFd!Vl>e|9) zx=StFtrYh9dl5XDrVo$y_l^&6vGn5B-A&hU_KvnQ7@Tycrzgas3q`lo=BG!~(@=@m zU;k|4BTF?Z#*N9r%&48l^$hj57B8+v()#?hXMc43kSRNi+Q{Pj2b-43h~-G_OuA}g zb1+v}x%<)f-sb+{?v{}+2C*4MXuErmBMKVU2&19KmD1&JedA3#XG~CfvawS%42s1x z=yt{jLw=mv-2vKo{N6qFc=AkZ$<3jM$B(=1!z@Yd)&(AW%Fe|uMviKD6b<*z%_zHY zWQvn7ee&jw)w!zUtTboxlr(d!WNXMB4h)Y&lj zY`g;T*~tkAfJ{Lu0pmIaN@;0mO#om7D5L;{6k`IkMgSoV2&M=?M(NoBPB6hxL5%Z= zmQ2g18f!oR)T(Mhn{rLCZ4d*=2uKW-qp@_YyvQQ0xMi6Nn{g&6bF}gRjUmAzEm$O{ z^&umKf|(Ti5GY2J88ELJbKf#83)i=s<`GqMAyTWrC+bVv|~c0u5QG*S27Q5D`hSRLF;6 zI}vqAiNx7B7(=KX#{<*}CJ}X{(lp63Kmtq<$cl<1(n8RVQRrA%f3%^<)CQ7tqCkYX&f zR7wNQ2|$Emo@GF5!6l{yNlc(ZLNJXL0#FK~G{%@HB>|xb0}54^VMHLt24-3+Z6%JS z>uzG{%r*uEokO@{+H87x5zLy{%%w*_1>LwG0EXCeVxE(#Yv_#Rst*YhO#Sh>A2>kMwc^sabDbIKWXTIVQgjN`Z zfslSM*;)Vi`hxMTKmOAgG1pg&QboMv| zpg;&ihyaB8Co5eV0;zQ>&vq9y&{F#7we5*W3Ri{5T zArZrYTX90wB|_|N51P|1SWzEPvgDe@dFUb!@X&B#*5SlCDv; zqvK1}wWC2WP$X{>%teOHcv9_eb*L*n%Z;?HjmRpp<6dmqWuf@?@LhKogDLLpL>00clY~u?<`(iHf$+m6#6}ZbJd#D-#@urIe)S}^w46h zQa?UDT`1XuaY|w1M*imN>e|-MdT-~XJX0KwPs1?8UjB4Ap883xWI;WtmF$g!p;C&N zmeffcr{m*8j1hp!upL5(mV!&60mRfqgl1eCmX!(43}%|Ahr4YkXkB2 zh}K$53Auz)2_b+G6k*FAI1EsfVt8V(HUlYE3C5?ABc4c^W+oy!Nhm}F(u^anCvhkt zG`Jckyk{9+mI%{KDJwE&X6ZQRaz^4p&J~#vM;_F+Wtq(6rU6Ap3(hHYfCn)!n5*za=n;gDL5s}Pm0?0HyX-t4Fd1aF#*RV}g$eFVS zs(Dt^q@H8CS>_U2G;PcFhy=0HS^>*4T4(>8lv2~6oO4R4ZQBT;I2~)8QA0==OBgdl zn-;`Gq&$^CnWo`+9;Gx*Q=X<2W2uL(?NFVM13?V|(KzEFR1V0ff%BBuTww*EVOxyQ zG>N2wS|JDwpb0|Epe7>*r3OXZAlfyENw7%_2AM-W*C^VUK&cpl1|g730-((d`jcxP zrNkhFQED+}GinoTwP3N#%juCZN$1w_tiO75a`XDu8&^(lcqc8{uVhO~}%GER)F>Ke^rslpL3oT}y34?D*kO%lNo6;?jPEId8x zM`INFy4UNwWVUGKGCvL{(>r&MzxBiLM;q;P<=Jbqjp5;K(X)G7A_og`+nKB9bEaL& zmolyx(p8tjxbI-DcmyVXFC1^Q!`@*h9p5R{oqzO?exr)YAe`7xWwFl)iqnXhX3V+N z2>(QH03iTU3eGv_TnR}bLQ0>t8O}b#03r%7gs}!l{p21@AtC@`4G2J3L#zOSh!SED ziZR9zX{oeQS_!3;)Jke0m6BR9h|f0T3`8hEXWefD6O1tlYTh-l~$!#R5n#0JC`Bo3kuqF4i@08xNxNEM(OA_WM54yFdg6e0kC1ezm| zVIToFsVK#+6M?|1eIHeAquNPuEp89*Xv8G`dCz}S2L5yLBt0n97 z^a1D@Ww1~wI(`am^CApA<|1S9iFY=;%hx}#zOmmZgM}Kbc*gAyKfLh7mD0H{-X9;$ zpRZ4+IHOLPwdb?;-}>6$Jsm!Lp4*Z$sa_9KY6R)*6N>^rbh^Jcz z$;$PseBv0BGK%$;)p~d5{>|C*HC<6-O*C{kd+^cY=JHa=v*Z24L8m(j10O@9G`~MO zJs!eAP`|TrG|7tEDw5epE~vPX3Lfv&U3&lM{j7nGqRCOWZ95(y`Hwf>hgQ%iUo_R? z{@%$!_xPkw@-?eo%1Vfofcx1uo(sXjgVPUisID$ut-H&GHp33N2q%^7!+(5=+tYvMUR|x9!YmBVR-q{jm@Ktoz8m_ z4$6(IcK%`-j4_h#oF%ie!{ z>suEtzmO%?*3n36nYBM&I!}3UlFM{=e`g@~%h#L^KgEP~!k*!2Li}I+nNPGT=*rcF z+FTQQIY8>8zCPN1z$cH(Hs9U2ySIO+VY4&n$6Ros6wqg&Uq&$kno*My0}%=##?-Ri zyq7CFxt!(LuIpN^i!siU1W1M9nIBA_fo0BKKUsm+Kx?JX)L{UcggVr45H$f_WW{q% zW7et|*Z>e0t=y7XUeyKUX`P2K4#3DjVU7&i(e0wFLA2w~oW1-Y(#*04Y6 z8t$y06cX))D&=Ub&}q@CVQqzpPJzTs#%bTn6Cq>AW|mE-{0P|Nd^yKVMo3Jdl3>DU z>YBnK0o4-I{NC!a!GYCu`>o5~MpKm6yMg%IBMIaT_aw-5~JmuV{kzi?&x*m2Vk`z&GQmd2) zLgGluaj=m0YJyV-=omA%+4IMerXq(e43o4=pwa|#B!bC_=p_3*u;HKEo@j*K2 zib;wB=xU3kGL0r-+?OyO1>?bF43LhKV9?#ye5{!@4N{$`JfKFJp?ESK>>DhoBUvRH zL{pi@Q9Lyqf*4mgLZldSLm4JCG1MOB3wgHSP$-SI)+2*Zm=TwiWFnbTHOofOBuE-C zFd0aB;HN!|IEkQUMi?Y2mA2_<3>|zcQb(j0`T4!y$%eOEacSR59I@ouTo z;~Jc1;;6H`+kP+(F``X3w|v^p4o{9trPXSK_&kCnJi6OES6Sos{nY74s6ShZSMzHi zLH_jAh`48zx2Gqf!jHjZxW9=Hj!t*hKk@o&zxK<&mNL6atx&5V%WNxWLdzw#PBH@l ziX=jS0FBbj*0@ORqG7wtLJAOx0KpsxEV+##Brwww2?eOuDzgb82x^LzLAVe^LuQ~v zWQkG|Vh9Kl04Ku3$k5UOD2I(Av|P;$MG%AtAtV%rGE65zCTF#&VHjEo6B-oIIF2dA z2E-e`qOg)g6sTRmh64x$Dup5g ziXohXMxFytAu3ePv^-2p5a+mZQsI~-1xyYM39L_l>Wf9U^r=@~`TAGBaPs)U_Jbe0 z&}cdh;rk4b-cd9<^^rfYQPSz0PJ*yHlUFbnO7_O5oPqqz+>m(~R6SkbIJ2-vz>S!k zoc2za7M{hR?1%d|uFX$QhBfN^@bUlg=-^vne_}vC%({1We)QU>-q_vxUW?!vr#|Wp zDy1iO_C((w&aAFH`P#Mbz5g#h@$Ad%_up^k*%Pyuw+{!^%QxnmwS!o+malEz+nXyo zSnXS8sIpNxpB)`{i;lka&L5t;eD1-+4==aCFMa+si`gG<-BKbhS84+UyHs_W+H}rx6@ks;lty|sMFll z;+qiMwTT)9{-O-?#1 z#bQ2(5X_hcF~FVCBuklQmF;`S_90xWUo&Us7M@sM-+4#o=;{sg{m_bw#R#)wNr|7L>|Huq@DuQFJvk44W`S z2t)vDptaCK2tklW08oe-#6U|P#n@C(1B+6WMV_4(S;{mLJoIdmCVeMo7)-?(2ha(l zvFo~#52$4&SzsCU!DziuZW2T>lYTT(D$D1~u@ovR*`^&u2}FugELF;RRxTHVNl&VT zGBTNl2F(G9Ol)Tgr{XB)o4*yrfRHI zV7WR=d@WD{6H;L~Hlb?)S#DqnFR^tf*)G_juF?BfTO3Ca(EuYR%Y6^grgr4n1QIzq_DcA@Y2r`gL zl_`J`A~A(NVQJ)}LXlF4pjJprWN-$uh-axb0Dz&2lNcvy1fZEFED02%QQn;Jc}qLJW4phQl^wbtrZs;p->^1WwGMYbKMw4vB&^Yfl7HM z5eFu~fDJgel~&_m>J)O-`VGvehB9BelxxMYP=+ci<)f^S923%F%UQI2_}Y^{`?sI} zmFM@0M^&fQ?>;Wsafr?RA#atIr;~&7WqSJFXl|~05*}7^3)1J(5V>+WE(eo%T*h#v z;OzI_vRKhF=4|040uhaImLsB)NFMOsBHk?xqBXP-VmoGS81I{e`O~CU#h4`hez)1G zO3oG+7bga(lnx5sO9!fZFzwl0zM5YRcj9)iUGv~cZ*x3sBLS0sRM7LHBTqIy-rpNA zpq_Z<$`AkGkH33+=NzJamYA67p9mI#){0;PA%IXTArU+StO2BfRvKx5A<`7)1&suU zu04&SJhZbc)0PQ&W>YIm6GEs~DoqocnMg=YQNd(T;Z#V005wgfCE;Reij_rlB=Iv$DRqxx3q$pDVXV(O&y;LP(3Fbluyf_= zjm}`$9gYf~xwNnx^)_eR#lxL@P#V?dh0*waWOR^OKHk_{n|ZfK-_{T3B1_ zPj&w|f93pqxnfFA9&hZ;pSvJAibvV!Kl9mXdOUlsc--$^ZCzG_-g8&3|1ba3zgbP}7j(C7eP7zh2 zy-qNh6zeQR2)t%x%Sb+ z`>kTD#&V~}N5f#evN|_9JpRNNerj(tC8FYYM}GgL>E)}|OnW>SE4KlxEwXNRV;Y~B#gmu3OXEQH{5XoU zGM%w(a@^Z9Y-ONAG>s@U2u0&@gbgpl%SMCN~Dw` zO|%kO29#2OoK?e61D$Effr5lOl)8i{AhBshNlYx0X>C$M5hPTBpG<2o(f}Z+Z2}2E zDL_a8il7022WhCp+2l@(kg=SM<8rN*O*7A&O(Sh0)1fp4ZOJXHYmgi%ptB4bR-0J` zNSW^sEX#mI6kS&KHWYWed zrc?=wl3bPs7`akmB_ojaAPo_A6|^-2nFUgqCIGlVN55Fc=>+C;955`92;F)q7a~6v ziA@qE5#Z9KRuURo7(avK_XKHLo0~u5UknzNG+$_r!28dA*ON@cgsS+GvNPyHTC0Gmt?Kn;r z#aaRY6hNAq#$-CSGln5bqDV`{t0s?R;jGT9taEKRq;U0RjHi1Qy*s| z!s9aacoJRrGwPt6uFq0s(1}SfET$rat3%bN= z%{PHcg7(J3@k$q)&{pWMXA1(ip`^suRV2xPx;T=4!w9&;Rj1`Uk(VwH_`kHqKw1`PcvU8*XE{efsFa`8mJ6lM`;D z<0oEv=HLJOZ$A0#rw&d!;b1_6(J1Sg<~gB^oLe|09e^`tR2az>n|2b={heI{L#m60 za-zwQ>M~;B;`z&+qvJuZKOT-+jppO64NWj3af#qavoc ztM1%`jrOG#v&7O`6;8&RMCP?q9;Elmg_XGL<sR5g}0`rfZsd8WwUzjqF|^ z2q0zGg;Io3K*$+42meI1je)i>F*M>@bHOzR1?qW*6@*d7IYLNAE~1`md6Gldu!N8X zqiGa-j^<+OSZ-iC+*qi!22!U<6Ng4v3 zIG&wFgCxvM$K-++3dPAdHmF4q(y*U|hULI%(zC5boJ<@S#fj(96w@(~k&YWkAj|ZL zNJLz=43vnJ;D{Midn!%iL@-VBfGI#^s8ZV{8jcDs)I2nJCbQ;bIx#H^Gn&yfOvGS3 zqz0qN3nLQ%hgtI&P{nLFSEFX$m&FB-=WF=@d6_09!b`P61Sz#j++;RH3`<~0RUiam zSCR=kwk*SgHUv4qR3iJ3m`GJ!(zi@5)TH3%CovPi$i$>tDC5ix)6g)93ml-ON~K{t zqbM?A6<|RiS6U!#AVq-D$V> z!0;@Iivj+L*GOx9Cf`*GD}aSUP@}VPGR6`Bon;yTEG1C_VZb?O)Tlrr6h~BH3^Sc_ zC1RaH149KU!9uHyOC*4RS`k30iMYULm@5S^!~g<<5CTw3fi-Xq3keOC_GKCfJ(Ao* zB|nIty7}_8>n{cYsMeNyy`hw7=je8&S&l+6jlyD~Hl2n^9F~e!l;9{z3`hIEUoThs zy`E{hqrPvn(s2|oUzo2~8V)N>`$5?g7G%0mJO9MWqpe47t~%RVxwn3AYyUVF=*IIe z?;owlsS(G`zw-4jAD(>N?eBi(Q~#hF+`n-Cse{8)3d2_2*xx>ScjtKJ(t_fFhQp_?B2Ice3jqyoBp`?Xm zlDWV>z5B>6RAA2C;qgjk>F5~v`k>yrK*-{whwB%XK7r}{or7W(P`WF|ft5YO|L95p9A7nf-R5m};{N9gt{g_{EJ-c~l zJ&#B~nHJ9%Gm$?3{3pNj7k_eXp%L}twP&8+UVjXP^m~Klh4}~DTa3`Tm1P|1kA__c zlINDHMmWOda_ZWD{;m6;`SeRW8@^R^>T~&T{NA5_?aQyzbhP*3{Tt6ddvvgo^Bjbs zQl>`s{!uUQnaE?MviJ7e?+m8>nSsyx1f!NLk%GX;UjREI95D3SN;* zm(#uAAS>xhy)d*Z`SBrC*v)h=Bjwt{e95n6DUKZ9Tun&qpz+;j&&_exJngsb)tXwQ zr{ng?_$Zp#5L%^LVKSZqn?2o5n1Ya&gn_dcC9o`xJVt#l~N+D&RAIg zC4&T&RzN8Pfd)#eK=V{f1fauAnPyti`PzIE#VFI^)X#e^u`vMvX@CTTKq{almYQm* z0FVF(sU5?@3IRgmm@B9$Q;Z3!wUDt=07!tTMGTb09im&dBc@T0s&fLyMsAt~c?~0| zv|$phIff_>V#Sr^QVG)0pihwH+J!idRTkFj#nHIO41%$hq)8!HjH4{)IZ+fb!+^Si zm^}`LZr+@ZdO6q5qR@s_gj}WKOhuXMN}>ddBZO5(F%ESGiL_-xnPEoR)N&+-6C+|A zNP>Xyh33X2gHYKrGn`6JONvXa3^>{>AgkQEST0uUBf}yDSvkut8PHJ)NzJlLOg2f( zqL*L?0AaZKQaJ87RhMAWLkadX+cI#$PO=g52PPznu!5LVY`|RxC3{`DD26 zmU5|f#~rBstXP(Y@=*XOZ-y+4m5h06K!~JPQrm`EG%XDWNr z3&A|5H9EtwgjqyP!}hdN99Rlt0+k5?Ah{xzxoBvq{KZl|7g!|~d^`2qR<6KhE|nZ9 zPPGzHLB%u8ZDL7?k%UU56j6X^#xn>hA~a6YlpqylTBI|%V#;}#WPQ!;Vz~gHo4bI8 zGpwI$LZj}Mid;_xjvcZRYoEEMn|FEcC}{&q*Eq=L>gT$>HY3KQ*XykRxXx#R8_t%7 zj}`7m%5)kSd7+LW)#hv|8b#cjqXesPM8+Y@{1jq4LZt|T2xzS}2eKe=f>B5zR=^byDJ&2S4QTT-%rnpsYX}qsXXbGzH3Z5;gdl<- z(Fkd+G(etVrBMjt%qD~ph%g&waVBDvVgQr_TqQA}m>DolQ-Fz-ickYdNukzCBA}I$ zKqv~xnco-w1iS?Sh5*5enTvuDNX5ATmxY+?+Z-x!>t;9v_`z zi~+6%aW9He21_dO(ul**iYG}7q`^#+89d4`Le)YENR{&(+a=5_W@%{IWOrx($`j8$ z{lt}T{>h)3h&8?8sZLvCH}Og74m?s2@W3VC8_LnPwx#8cqP{M!MXG2`s4BGXx|{_Qgao!#WqOR&MkfS_y6Ea7gt@@d~^TKU;8^h z{X4(?A6tv(o_qca-~0COEw)On#-%tCjHbJLk1P423X+9|Dx~qH3k%yD_qWz}JgYwJ zag5A3n|hd3s~2}pALh%f=v*jUuO@s9%#UIosUQRs9GZ+-y{1TrYVJ?E(AjfAj-$|V zEXiY#DbtB^y2InedUY--j|K^X*5ds6i^=;hEPh00FXd~ztmeticfixk8#l|N&&GZ z7V!`zQbbrW1DnJqOep{%5l}0w6_5s^L<7wuKrm1ea4l1Xs38&Mf@3BKg*@cBVlf;> zlsG&k26cmwTUNn9nZQD(2|^s{giy_hZ8I7~KElAY^E^pC&rHK0lOxwHLv1JlJiCkl z%VLy8$aFo$Glq#joDgcH+-FoL@zirJOE@yk98Z;3$W)qGsEm<_(VS8d50qEZ~gE;_tG{rs#r^cfzK04@g#{ffSD9fL5(4V7-0exK@@lv zfe=o{Neww;EeBe1&11za%PLz=y3vdql;<8&(BGGbX_R>4k8p+pMy zgOrN|*sde1R$yj^m54(E53wRt+l=S(WV+JK2TSNr|G2$-I(NG5X_bU*T3ekBH+Fix zP1mks$4s-wr@cwBc!?=kE#kojzxU?uul_e*Fw+1;d}}Xy;VYHM2&+yL82r5-zu!8i zUDkg8(Vg$#dFz>LSMS_C`MWQ^{H4G1-&c_}O#^ILj9DTGCDa-qL?HkQVsw`2N06N1 zW3<*vYb~|bT5AcEiU_m>l2MizSj#vEu~srOA<)XSSQG^|Ap{VeWUgUN({v=cqp8OM zmm(wpGfEUTA_4IkmHtcA)!G~5JLnt09r{QIaUUT zN{TQ8LMcd548Sx=6oC-wum8+fE?r*zXaDm*yMFcB!}}lAn=?{MM6Kgaw^pgT)R;^r zGRv|oDdh{(Uf0bRX6lWN(a5&h!ugfGoqY%q2wI*KGPhKz&FuHOuYKib8E$R%|LD~# z7cyj7#aX+!;uNh*muLJ@WVo?bUdnd`oT_vw3?|Df*6LdG!}r@7l6!Y=U7BAz+S(lV zhflry%plbd){o+oqvxN#x%KdIGU=7w`TgyWDLY+Wao^u~>rwAV&$q7c?cILk+JE!S zKYaW2^!WA9T>G>C^asy2tDT*L{ShlKFK$14e{HpxjUf*X;@7ZH{qKJB7ryq>#1R<8b2Dq@ zI@WG=>^i#Z4~N5*bF1@nGjG26gXQJrVW;Q&e!j3|8L%%Vch>J+TUnKlc3!*jQe*A> ztv`D6fBe<|{?Fg}i}rEyU;q7|`SHDb4-bZ){>m>N9UWmJj@w5Xs%oP+=yoQ4eEr&$ zJMU~A9rv1*`CflmE|muT$TYklG{1ZM!P@!xVinfiq~WBu9vy+4*J@s#_As=R_HQM8 zx*c#!1^Y)2EoXV+O78FG^+r*crdQ544-R}Us6&Vk4~~(Q8;&Ow8sp)J^E8}J{{K*v2>q9L;4hC~ ztpNvWs0=E|@wXh@+I(WcUX z0)nyjoaA=@fDp2fD+azS7OGMyMO8fJ%(ml95kqqkS~gLs z&rC3lyFlb@zz7IfB2Y$Yh8Y!rEv3ZF46m`)EL5Am5VVrVMJFt*K zX<>Ly6pa7^p@lWkkV=7*LQwB{3iX!Gxz$3ysK7Of(?|WJF*TCvL&PC1+%2 ztrg5_q+~aoW=<0-6^B`Bo1shxR0^b#6u!<1m@;f1Ikwa0x1g3~qpr9~NzgyJ7bO$N zTMdpGG%#o5K5uK+Ih@M6L+lDY+L)}cQoUG)!7%ct*c3YN(Sn8KlkJmOrkdrbXPKET zQxmdcwPIdrHs;>_=$+NohG*&XS1#>s?($B?@X}x!jr@s*7+bk4JvOXhzIE-}Z@rn7 zz*fAz#ugsk{U%E0^H~%39_Wi#fzfQV`+FxJs$~5efAVc@!#k6cFaGq;{SW`|-%tWf z3|J9(Y;!v8od8ZD(po_cGpR6uLMei%)@Pu{v+1vtS||Z9gj(x_XHX-6kdzVtg&;y0 zYN9n)2qH?*8LR^UgfQa-QYkgUfoMAiRE3md{aV}Xv0FRW&7b{+r(S&K zt?$3P`RHhQ?oyV}U?kmKQ4?r8G*iBxXq9I5O10vZclLIw)w*S}e!oi%Qmo_z>w_!6UZW2_n%O?9?i& zTI!X>g?4{0Uz%Tkbf5rz^Lu}C~8Hk%8fx^%jj_H{-?$yWaOKKmZ|iF7!qJD_NAGO>?0@z7-~au8b^7sER)4wIwyMt3N4Gz4Jf=r( zmgsxa`^Tr-Yb)okox5~DcnA}n3fb1FocB)0LlPgPU~;j!RV9x%XJ;2@srvU_}YWr-E)_h`oqw&?dM*4Aqb}Tx7I7IMieE5LSgC$GoID& z_j^;{t2L2PwhHCTON&4L;MQOH#jheJPxlAE{`ddUci;TaH=ext%!{uUTMIvW_Z!DY zr$G?D`ttLg_CC~NZROJOZkQ$3m%jM*$6M==s`l`RGGiRbz5UMRa@`-LCppz@M2C|H z%qlI^uBDyBR6PdREfroS58mbOzCcK5Fr#!r_3OFhoiNEVV)a zDIgV43P`Cf%2XUuVi?%STb`PZ06+v$i91%lsL}*umLy5ub2Ff1n&#ccVA2s%0VKSf zm!>I!5kQ<|W*SC>5yd4`*kVo;`Z<@4Mx$aemu8`qQ%13oug8%fm=P-CBxDGf#N$R1 z1yg{P0z6-sn#P=m=wLiG=wc8A2*#=Jlm+3!1+d1Mt8xX9ub9Oet5z_^?rbG0P`l_A z9WMuEkfXkh%0>*evamVv2eS+F{a_+B7>a?NgA`*al!=YlkM%?v)U;WCV;o?W8CX;t z7fMK}8MG&Mqd+a_MO?^Hng;`d{`J56&OiM1zx~hu@D{PMhDM&f!?1UK5<3DZpk^MFXHXL`VqB+9bwQCkP^>AyN=Zg*C(oVE{D9piqQBgfIYvYmjoOg(`Ta6iOfJp=3q)piLbHAxc=bk6Wv<(ZdWdU1ig~m(E>2J=#O&7z2@jsuk%LD%*f!j|0G zdEk(sma9sxcaJ|_Tr8aIvI_Q&Pqv!NqTPPT=u~r-`rIE)KDvE>Wx3I6nkGTJ8@(rQ zT)cDdTbG_q{`d#~+aQ(sV&j3on>x&I@9dmxE?-=P+!(gseBApOaDDmm<=_A2_iN48 zk8gcQ85<49^}Y#-xsa=NuzTU+^WXg5ca>XiCqvVyAlW5xd&aJ~PWd3acV%ti_Pg)h zyfE87ZWnC3UcT`0y&paQ+@;>W>s8hIreFlt8mM0J4C)#d=}sP2Q57oT0cbN3L50-CBXeEyB{e0h*fj!ptNc8oNgne(!-u2(L)1+um@x4Hgk ztwCk=+JnarZhd^`{Kd8RZ@qVZdCeb8&YwHiJDzMDw_p6k3vQg~qk+QCNt7%-|GY8R z`rY68jW2%fr9b_>-+tqj=PI@3*2<-S|A+s2(hFT$YR)h1ZSNsCT3l=aHCbMqDL0Id zKK>D8?n!5i4I_@jUT55D<;VCDg9{{nz7)K`)n+l@C#(@@r$(P(sPNwKn;WB(rHz`w zReqsJ(`#fa99c!X6)l!nyW_*0DZ&$Q%%w~xDIrA@;XX~XxGxH|atz|ISIQU6soznG zmAuvc^RpzP62$D#u7^xg1HD-+bi6kkYIu?m#puQ2KSn4PM zFtM4T(+Nm1ppH$=kGm!^x-=PCNDno+|!=$6Eypy!xN%VK`y-d(i6?5*^N}ccd+$%9JFaU zKY#UR9U_hLnVy7`2y0e(<+V$-0*guV37YRl>4}Q#7iKRPDpi+8Hcu(4aCeX;hK3|X zNK1l&hEf5=7)b<&5Mq-?JXNWpmiT1=zZu$+z`5k!cRDJUu?!gPeC=dp6Y`;$0zgwCKvEHKVa zTMM%w?+gGTR62RH6A!iTE|kxQ>4fI7lFba9C$&hTFy%wbDnoEZW+y<66WO&Y^Ta>G zq+yUaC;0wEkGRf86B+$Z9C>!L;k#qJP@~!zo%C9e0Bh0VeN(2AO^s?EPi+HHh69kM zIpZcwu}~9GC=j%g9{NsFD>{2?H!i;x-``jt|MA_nZSF4T%bv)0Q)_8;#w+`$<1Npy zOK#33<(=(A&%@da){hBoS1MKW&5!>mB>vYf{p#_?KsqPqFJCu{YGeIT1#{hvXXb17 zcLNiE(XfpHz~M9?2GxiM1B4Jj3<5+c&{`7+kwS!EOko_yh+s-7PnA#-00a@tObf&* zkcq<>p)_yg<0MOw<6$Bc=2JlpHUOEHF-MAGW=Sb1kWdPcXr%!H5MqGOeY0;C3e z4X@Q5O<>(kMqVYL{-D=f<1=2V#9K2)Z8q#xc4)mt`9w|rN(T(CK4%?5nyBpcaR*s*R z+L58h=Cqy~O8b_+;JNwD%^x54-d$aWU^=TD6-1{`pZ~eNjka5%>l=YfS4#Xi79Y0Y zXWsnqZD{C=t+~O@-WJvM>b2={?aE8_?|k@S9GT6f^7`?8ueQ+Z1oOqk!|~l}cm4W8 z;UoW~i`-RB3`-9?-RAj*?&YgAj{Tlt&+VTEd2=a>4TE$^CI7Jd@$%BEijy>;(aGM- z+6q6>&Bf)tgIz+!&X4bZ@rBpleEjkAm#;qB|NeYKJ%8!qAH8||;)Uhq`IP{gMSW1X zT3WjN%+m6u_uhV+Dc8YR55Mur&wlmK z|KfL^x^%$=$ZgDWR=W4my}$m8U$?aS!{7himp}O&F2!-YeR2ND;~#DRo#+0=fB4Sd z`{ZYyyL7I~rTEd?-z_Gi)dg2)t$u%iH8!k<0K{#Y2S?rQyGQj#$)9$MMQi(@ZDO

7#0;$Dx`=YfdYPvT=E78NlAjVR!AqvxcR^D3ZwU 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