From df5262daf8fea59fcb37570da8aa14dbb41f36a7 Mon Sep 17 00:00:00 2001 From: winskuo-quic Date: Wed, 17 Dec 2025 17:51:25 +0800 Subject: [PATCH 1/4] Qualcomm AI Engine Direct - Support Static LLM Model SQNR, Inference Refactor, Extract nn.Module Static Llama, Improve CI coverage with evaluating Static LLM SQNR --- .ci/scripts/test_qnn_static_llm.sh | 6 +- .github/workflows/nightly.yml | 2 +- .github/workflows/pull.yml | 34 ++ backends/qualcomm/tests/test_qnn_delegate.py | 275 +++++---- backends/qualcomm/tests/utils.py | 3 +- examples/qualcomm/custom_op/custom_ops_1.py | 2 +- examples/qualcomm/oss_scripts/albert.py | 3 +- examples/qualcomm/oss_scripts/bert.py | 2 +- examples/qualcomm/oss_scripts/conv_former.py | 2 +- .../qualcomm/oss_scripts/convnext_small.py | 2 +- examples/qualcomm/oss_scripts/cvt.py | 2 +- examples/qualcomm/oss_scripts/deit.py | 2 +- examples/qualcomm/oss_scripts/dino_v2.py | 2 +- examples/qualcomm/oss_scripts/distilbert.py | 2 +- examples/qualcomm/oss_scripts/dit.py | 2 +- .../oss_scripts/efficientSAM/efficientSAM.py | 2 +- examples/qualcomm/oss_scripts/efficientnet.py | 2 +- examples/qualcomm/oss_scripts/esrgan.py | 2 +- examples/qualcomm/oss_scripts/eurobert.py | 3 +- examples/qualcomm/oss_scripts/fastvit.py | 2 +- examples/qualcomm/oss_scripts/fbnet.py | 2 +- examples/qualcomm/oss_scripts/focalnet.py | 2 +- .../oss_scripts/gMLP_image_classification.py | 2 +- examples/qualcomm/oss_scripts/llama/README.md | 31 +- .../stories260k_hybrid_llama_qnn.pte | Bin 1347220 -> 1343124 bytes .../oss_scripts/llama/decoder_constants.py | 5 + .../llama/decoder_runtime_evaluator.py | 569 ++++++++++++++++++ .../oss_scripts/llama/decoder_utils.py | 224 ++----- .../oss_scripts/llama/eval_llama_qnn.py | 5 +- examples/qualcomm/oss_scripts/llama/llama.py | 452 +++++--------- .../oss_scripts/llama/masking_utils.py | 2 +- .../oss_scripts/llama/range_setting_pt2e.py | 46 +- .../oss_scripts/llama/runner/runner.cpp | 4 +- .../qualcomm/oss_scripts/llama/tokenizer.py | 1 - .../qualcomm/oss_scripts/llama/wrappers.py | 6 +- .../llm_utils/eval_decoder_model_qnn.py | 2 +- examples/qualcomm/oss_scripts/maxvit_t.py | 2 +- examples/qualcomm/oss_scripts/mobilevit_v1.py | 2 +- examples/qualcomm/oss_scripts/mobilevit_v2.py | 2 +- examples/qualcomm/oss_scripts/moshi/mimi.py | 4 +- examples/qualcomm/oss_scripts/pvt.py | 2 +- .../qualcomm/oss_scripts/qwen2_5/qwen2_5.py | 2 +- examples/qualcomm/oss_scripts/regnet.py | 2 +- examples/qualcomm/oss_scripts/retinanet.py | 2 +- examples/qualcomm/oss_scripts/roberta.py | 4 +- examples/qualcomm/oss_scripts/squeezenet.py | 2 +- examples/qualcomm/oss_scripts/ssd300_vgg16.py | 2 +- .../qualcomm/oss_scripts/swin_transformer.py | 2 +- examples/qualcomm/oss_scripts/swin_v2_t.py | 2 +- examples/qualcomm/oss_scripts/t5/t5.py | 2 +- examples/qualcomm/oss_scripts/vit_b_16.py | 2 +- .../qualcomm/oss_scripts/whisper/whisper.py | 2 +- .../qaihub_stable_diffusion.py | 2 +- examples/qualcomm/scripts/deeplab_v3.py | 2 +- examples/qualcomm/scripts/edsr.py | 2 +- examples/qualcomm/scripts/inception_v3.py | 2 +- examples/qualcomm/scripts/inception_v4.py | 2 +- .../qualcomm/scripts/mobilebert_fine_tune.py | 2 +- examples/qualcomm/scripts/mobilenet_v2.py | 2 +- examples/qualcomm/scripts/mobilenet_v3.py | 2 +- examples/qualcomm/scripts/torchvision_vit.py | 2 +- examples/qualcomm/scripts/wav2letter.py | 2 +- .../qnn_intermediate_debugger_demo.py | 2 +- examples/qualcomm/utils.py | 6 +- 64 files changed, 1071 insertions(+), 698 deletions(-) create mode 100644 examples/qualcomm/oss_scripts/llama/decoder_runtime_evaluator.py diff --git a/.ci/scripts/test_qnn_static_llm.sh b/.ci/scripts/test_qnn_static_llm.sh index 46923f52127..91e37aae7c8 100644 --- a/.ci/scripts/test_qnn_static_llm.sh +++ b/.ci/scripts/test_qnn_static_llm.sh @@ -39,6 +39,7 @@ pip install graphviz set +e echo "Executing task: $TASK_NAME" +EXTRA_FLAGS="" if [[ "${TASK_NAME}" == "stories_110m" ]]; then # Download stories llama110m artifacts download_stories_model_artifacts @@ -80,7 +81,10 @@ elif [[ "${TASK_NAME}" == "stories_260k_bc" ]]; then fi elif [[ "${TASK_NAME}" == "smollm2_135m" ]]; then - $PYTHON_EXECUTABLE backends/qualcomm/tests/test_qnn_delegate.py -k TestExampleLLMScript.test_static_llm_model --model_name smollm2_135m --model SM8650 --build_folder build-x86/ --executorch_root . --artifact_dir ./static_smollm2 --enable_x86_64 + if [ -n "$2" ]; then + EXTRA_FLAGS="$EXTRA_FLAGS --static_llm_eval_method $2" + fi + $PYTHON_EXECUTABLE backends/qualcomm/tests/test_qnn_delegate.py -k TestExampleLLMScript.test_static_llm_model --model_name smollm2_135m --model SM8650 --build_folder build-x86/ --executorch_root . --artifact_dir ./static_smollm2 --enable_x86_64 $EXTRA_FLAGS exit_code1=$? if [ $exit_code1 -ne 0 ]; then exit 1 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f2aa4a3511e..a26e0f0cc2e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -69,4 +69,4 @@ jobs: # Setup install_requirements for llama PYTHON_EXECUTABLE=python bash examples/models/llama/install_requirements.sh - PYTHON_EXECUTABLE=python bash .ci/scripts/test_qnn_static_llm.sh ${{ matrix.task }} + PYTHON_EXECUTABLE=python bash .ci/scripts/test_qnn_static_llm.sh ${{ matrix.task }} wikitext_ppl diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml index 77e1dc7eb63..ba3270c15b5 100644 --- a/.github/workflows/pull.yml +++ b/.github/workflows/pull.yml @@ -598,6 +598,40 @@ jobs: PYTHON_EXECUTABLE=python bash .ci/scripts/test_qnn_static_llm.sh ${{ matrix.task }} + test-sqnr-static-llm-qnn-linux: + name: test-sqnr-static-llm-qnn-linux + uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main + permissions: + id-token: write + contents: read + strategy: + matrix: + task: [smollm2_135m] + fail-fast: false + with: + runner: linux.2xlarge + docker-image: ci-image:executorch-ubuntu-22.04-qnn-sdk + submodules: 'recursive' + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + timeout: 180 + script: | + # The generic Linux job chooses to use base env, not the one setup by the image + CONDA_ENV=$(conda env list --json | jq -r ".envs | .[-1]") + conda activate "${CONDA_ENV}" + + BUILD_TOOL="cmake" + + PYTHON_EXECUTABLE=python bash .ci/scripts/setup-qnn-deps.sh + PYTHON_EXECUTABLE=python bash .ci/scripts/build-qnn-sdk.sh + + # Setup executorch + PYTHON_EXECUTABLE=python bash .ci/scripts/setup-linux.sh --build-tool "${BUILD_TOOL}" + + # Setup install_requirements for llama + PYTHON_EXECUTABLE=python bash examples/models/llama/install_requirements.sh + + PYTHON_EXECUTABLE=python bash .ci/scripts/test_qnn_static_llm.sh ${{ matrix.task }} sqnr + test-qnn-models-linux: name: test-qnn-models-linux uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main diff --git a/backends/qualcomm/tests/test_qnn_delegate.py b/backends/qualcomm/tests/test_qnn_delegate.py index 29074765082..b4552a49d32 100644 --- a/backends/qualcomm/tests/test_qnn_delegate.py +++ b/backends/qualcomm/tests/test_qnn_delegate.py @@ -7,6 +7,7 @@ import io import itertools import json +import logging import subprocess import sys import tempfile @@ -6122,54 +6123,130 @@ class TestExampleLLMScript(TestQNN): class LlmSpecs: SM8650: float SM8750: float - ppl: float pte_size: float + wikitext_ppl: float + hellaswag_acc_norm: float + sqnr: float # TODO: refactor to support different backends def setUp(self): + # TODO: add SQNR for all models self.llm_specs = { "gemma-2b": TestExampleLLMScript.LlmSpecs( - SM8650=32, SM8750=36, ppl=35, pte_size=2_700_000_000 - ), # 2.7 GB + SM8650=32, + SM8750=36, + pte_size=2_700_000_000, # 2.7 GB + wikitext_ppl=17, + hellaswag_acc_norm=None, + sqnr=27, + ), "gemma2-2b": TestExampleLLMScript.LlmSpecs( - SM8650=32, SM8750=36, ppl=14, pte_size=2_860_000_000 - ), # 2.86 GB + SM8650=32, + SM8750=36, + pte_size=2_860_000_000, # 2.86 GB + wikitext_ppl=14, + hellaswag_acc_norm=None, + sqnr=27, + ), "gemma3-1b": TestExampleLLMScript.LlmSpecs( - SM8650=70, SM8750=100, ppl=23, pte_size=1_200_000_000 - ), # 1.2 GB + SM8650=70, + SM8750=100, + pte_size=1_200_000_000, # 1.2 GB + wikitext_ppl=23, + hellaswag_acc_norm=None, + sqnr=10, + ), "glm-1_5b": TestExampleLLMScript.LlmSpecs( - SM8650=42, SM8750=52, ppl=21, pte_size=1_100_000_000 - ), # 1.1 GB + SM8650=42, + SM8750=52, + pte_size=1_100_000_000, # 1.1 GB + wikitext_ppl=21, + hellaswag_acc_norm=None, + sqnr=14, + ), + "granite_3_3-2b_instruct": TestExampleLLMScript.LlmSpecs( + SM8650=20, + SM8750=22, + pte_size=1_600_000_000, # 1.6 GB + wikitext_ppl=None, + hellaswag_acc_norm=0.2, + sqnr=None, + ), "phi_4_mini": TestExampleLLMScript.LlmSpecs( - SM8650=14, SM8750=19, ppl=12, pte_size=4_000_000_000 - ), # 4GB + SM8650=14, + SM8750=19, + pte_size=4_000_000_000, # 4GB + wikitext_ppl=14, + hellaswag_acc_norm=None, + sqnr=20, + ), "llama3_2-1b_instruct": TestExampleLLMScript.LlmSpecs( - SM8650=37, SM8750=45, ppl=16, pte_size=1_500_000_000 - ), # 1.5 GB + SM8650=37, + SM8750=45, + pte_size=1_500_000_000, # 1.5 GB + wikitext_ppl=16, + hellaswag_acc_norm=None, + sqnr=15, + ), "llama3_2-3b_instruct": TestExampleLLMScript.LlmSpecs( - SM8650=21, SM8750=26, ppl=11, pte_size=2_800_000_000 - ), # 2.8 GB + SM8650=21, + SM8750=26, + pte_size=2_800_000_000, # 2.8 GB + wikitext_ppl=11, + hellaswag_acc_norm=None, + sqnr=14, + ), "qwen2_5-0_5b": TestExampleLLMScript.LlmSpecs( - SM8650=115, SM8750=155, ppl=15, pte_size=600_000_000 - ), # 600 MB + SM8650=115, + SM8750=155, + pte_size=600_000_000, # 600 MB + wikitext_ppl=15, + hellaswag_acc_norm=None, + sqnr=8, + ), "qwen2_5-1_5b": TestExampleLLMScript.LlmSpecs( - SM8650=38, SM8750=47, ppl=10, pte_size=1_500_000_000 - ), # 1.5 GB + SM8650=38, + SM8750=47, + pte_size=1_500_000_000, # 1.5 GB + wikitext_ppl=10, + hellaswag_acc_norm=None, + sqnr=10, + ), "qwen3-0_6b": TestExampleLLMScript.LlmSpecs( - SM8650=47, SM8750=68, ppl=21, pte_size=700_000_000 - ), # 700 MB + SM8650=47, + SM8750=68, + pte_size=700_000_000, # 700 MB + wikitext_ppl=21, + hellaswag_acc_norm=None, + sqnr=8, + ), "qwen3-1_7b": TestExampleLLMScript.LlmSpecs( - SM8650=28, SM8750=34, ppl=15, pte_size=1_800_000_000 - ), # 1.8 GB + SM8650=28, + SM8750=34, + pte_size=1_800_000_000, # 1.8 GB + wikitext_ppl=15, + hellaswag_acc_norm=None, + sqnr=12, + ), "smollm2_135m": TestExampleLLMScript.LlmSpecs( - SM8650=214, SM8750=260, ppl=23, pte_size=210_000_000 - ), # 210 MB + SM8650=214, + SM8750=260, + pte_size=210_000_000, # 210 MB + wikitext_ppl=23, + hellaswag_acc_norm=None, + sqnr=20, + ), "smollm3-3b": TestExampleLLMScript.LlmSpecs( - SM8650=23, SM8750=28, ppl=10, pte_size=2_600_000_000 - ), # 2.6 GB + SM8650=23, + SM8750=28, + pte_size=2_600_000_000, # 2.6 GB + wikitext_ppl=10, + hellaswag_acc_norm=None, + sqnr=6, + ), } - def test_static_llm_model(self): + def test_static_llm_model(self): # noqa: C901 if not self.required_envs([self.model_name]): self.skipTest("missing required envs") assert ( @@ -6205,13 +6282,43 @@ def test_static_llm_model(self): "kv", "--max_seq_len", "1024", - "--run_lm_eval", - "--tasks", - "wikitext", - "--limit", - "1", ] + match self.static_llm_eval_method: + case "wikitext_ppl": + cmds.extend( + [ + "--eval_methods", + "tasks_eval", + "--tasks", + "wikitext", + "--limit", + "1", + ] + ) + case "hellaswag_acc_norm": + cmds.extend( + [ + "--eval_methods", + "tasks_eval", + "--tasks", + "hellaswag", + "--limit", + "10", + ] + ) + case "sqnr": + cmds.extend( + [ + "--eval_methods", + "sqnr_eval", + ] + ) + case _: + logging.warning( + "No llm eval method chosen. Only generate model output." + ) + if is_llama_model: cmds.extend( [ @@ -6231,24 +6338,36 @@ def test_static_llm_model(self): conn = listener.accept() p.communicate() msg = json.loads(conn.recv()) + logging.info(f"Model Name: {self.model_name}\nTarget Device: {self.model}") + logging.info(f"Eval Result: {msg}") if "Error" in msg: self.fail(msg["Error"]) else: llm_spec = self.llm_specs[self.model_name] pte_size = msg["pte_size"] self.assertLessEqual(pte_size, llm_spec.pte_size) - print(f"Model Name: {self.model_name}\nTarget Device: {self.model}") - print(f"PTE Size: {pte_size} bytes") if not self.compile_only: - ppl = msg["wiki_ppl"] - print(f"PPL: {ppl}") - self.assertLessEqual(ppl, llm_spec.ppl) + if self.static_llm_eval_method: + # Use "is not None" in case any eval_score is 0. + assert ( + getattr(llm_spec, self.static_llm_eval_method) is not None + ), f"{self.model_name} currently does not support {self.static_llm_eval_method}. Please choose other methods." + match self.static_llm_eval_method: + case "wikitext_ppl": + ppl = msg["wiki_ppl"] + self.assertLessEqual(ppl, llm_spec.wikitext_ppl) + case "hellaswag_acc_norm": + acc_norm = msg["acc_norm"] + self.assertGreaterEqual( + acc_norm, llm_spec.hellaswag_acc_norm + ) + case "sqnr": + sqnr = msg["sqnr"] + self.assertGreaterEqual(sqnr, llm_spec.sqnr) + if not self.enable_x86_64 and hasattr(llm_spec, self.model): device_inference_speed = msg["inference_speed"] expected_inference_speed = getattr(llm_spec, self.model) - print( - f"Prompt Evaluation: {device_inference_speed} tokens/second" - ) self.assertGreaterEqual( device_inference_speed, expected_inference_speed ) @@ -6299,73 +6418,6 @@ def test_codegen2_1b(self): if not self.compile_only and not self.enable_x86_64: self.assertGreaterEqual(msg["inference_speed"], 60) - def test_granite_3_3_2b_instruct(self): - if not self.required_envs(): - self.skipTest("missing required envs") - - prompt = "What is the meaning of life?" - cmds = [ - "python", - f"{self.executorch_root}/examples/qualcomm/oss_scripts/llama/llama.py", - "--artifact", - self.artifact_dir, - "--build_folder", - self.build_folder, - "--model", - self.model, - "--ip", - self.ip, - "--port", - str(self.port), - "--prompt", - f"{prompt}", - "--temperature", - "0", - "--decoder_model", - "granite_3_3-2b_instruct", - "--model_mode", - "kv", - "--max_seq_len", - "1024", - "--run_lm_eval", - "--tasks", - "hellaswag", - "--limit", - "10", - "--kv_updater", - "shift_pointer", - ] - if self.compile_only: - cmds.extend(["--compile_only"]) - elif self.device: - cmds.extend(["--device", self.device]) - if self.host: - cmds.extend(["--host", self.host]) - elif self.enable_x86_64: - cmds.extend(["--enable_x86_64"]) - if self.pre_gen_pte: - cmds.extend(["--pre_gen_pte", self.pre_gen_pte]) - - p = subprocess.Popen(cmds, stdout=subprocess.DEVNULL) - with Listener((self.ip, self.port)) as listener: - conn = listener.accept() - p.communicate() - msg = json.loads(conn.recv()) - if "Error" in msg: - self.fail(msg["Error"]) - else: - inference_speed_ref = {"SM8650": 20, "SM8750": 22} - if ( - not self.compile_only - and not self.enable_x86_64 - and self.model in inference_speed_ref - ): - self.assertLessEqual(msg["pte_size"], 1_600_000_000) - self.assertGreaterEqual(msg["acc_norm"], 0.2) - self.assertGreaterEqual( - msg["inference_speed"], inference_speed_ref[self.model] - ) - def test_llama_stories_260k(self): if not self.required_envs(): self.skipTest("missing required envs") @@ -8442,6 +8494,12 @@ def setup_environment(): help="A folder that contains: weight, tokenizer, and params.", type=str, ) + parser.add_argument( + "--static_llm_eval_method", + help="Methods for Static LLM evaluation.", + choices=["wikitext_ppl", "hellaswag_acc_norm", "sqnr"], + type=str, + ) args, ns_args = parser.parse_known_args(namespace=unittest) TestQNN.host = args.host @@ -8468,6 +8526,7 @@ def setup_environment(): TestQNN.op_package_dir = args.op_package_dir TestQNN.target = args.target TestQNN.backend = args.backend + TestQNN.static_llm_eval_method = args.static_llm_eval_method return sys.argv[:1] + ns_args diff --git a/backends/qualcomm/tests/utils.py b/backends/qualcomm/tests/utils.py index 77c093feae9..ad4843c1c3e 100644 --- a/backends/qualcomm/tests/utils.py +++ b/backends/qualcomm/tests/utils.py @@ -184,6 +184,7 @@ class TestQNN(unittest.TestCase): dump_intermediate_outputs: bool = False inference_speed: float = 0.0 inference_speed_output_path = "outputs/inference_speed.txt" + static_llm_eval_method = "" @classmethod def setUpClass(cls): @@ -516,7 +517,7 @@ def validate_intermediate_tensor(): f" --performance_output_path {self.inference_speed_output_path}" ) adb.execute(method_index=method_index, output_callback=output_callback) - adb.pull(output_path=tmp_dir, callback=post_process) + adb.pull(host_output_path=tmp_dir, callback=post_process) self._assert_outputs_equal(outputs, ref_outputs) if expected_profile_events != -1: diff --git a/examples/qualcomm/custom_op/custom_ops_1.py b/examples/qualcomm/custom_op/custom_ops_1.py index 1745e2df7fa..0962e44ee6c 100644 --- a/examples/qualcomm/custom_op/custom_ops_1.py +++ b/examples/qualcomm/custom_op/custom_ops_1.py @@ -261,7 +261,7 @@ def main(args): ) adb.push(inputs=sample_input, files=op_package_paths) adb.execute() - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) x86_golden = instance(*sample_input) device_output = torch.from_numpy( diff --git a/examples/qualcomm/oss_scripts/albert.py b/examples/qualcomm/oss_scripts/albert.py index a81458e688b..5b63d185077 100644 --- a/examples/qualcomm/oss_scripts/albert.py +++ b/examples/qualcomm/oss_scripts/albert.py @@ -130,8 +130,7 @@ def main(args): # accuracy analysis adb.push(inputs=inputs) adb.execute() - adb.pull(output_path=args.artifact) - + adb.pull(host_output_path=args.artifact) # since the original nn.Module could not perform well on this task either # we only measure the relative accuracy here goldens, predictions, nominal_predictions = [], [], [] diff --git a/examples/qualcomm/oss_scripts/bert.py b/examples/qualcomm/oss_scripts/bert.py index f17e9129435..874c647d9de 100644 --- a/examples/qualcomm/oss_scripts/bert.py +++ b/examples/qualcomm/oss_scripts/bert.py @@ -130,7 +130,7 @@ def main(args): # accuracy analysis adb.push(inputs=inputs) adb.execute() - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) goldens, predictions = [], [] for i in range(len(inputs)): indices = [i for i, x in enumerate(targets[i]) if x != -100] diff --git a/examples/qualcomm/oss_scripts/conv_former.py b/examples/qualcomm/oss_scripts/conv_former.py index a7150701796..dd7321de123 100644 --- a/examples/qualcomm/oss_scripts/conv_former.py +++ b/examples/qualcomm/oss_scripts/conv_former.py @@ -97,7 +97,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/convnext_small.py b/examples/qualcomm/oss_scripts/convnext_small.py index 35f4a4905ec..d7045bf55d2 100755 --- a/examples/qualcomm/oss_scripts/convnext_small.py +++ b/examples/qualcomm/oss_scripts/convnext_small.py @@ -92,7 +92,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/cvt.py b/examples/qualcomm/oss_scripts/cvt.py index f4e5ddeed12..891a441e5b1 100644 --- a/examples/qualcomm/oss_scripts/cvt.py +++ b/examples/qualcomm/oss_scripts/cvt.py @@ -161,7 +161,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/deit.py b/examples/qualcomm/oss_scripts/deit.py index 6c9bf70733d..3d93b9cfbc7 100644 --- a/examples/qualcomm/oss_scripts/deit.py +++ b/examples/qualcomm/oss_scripts/deit.py @@ -118,7 +118,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/dino_v2.py b/examples/qualcomm/oss_scripts/dino_v2.py index 745da8694cf..65a39873851 100644 --- a/examples/qualcomm/oss_scripts/dino_v2.py +++ b/examples/qualcomm/oss_scripts/dino_v2.py @@ -101,7 +101,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/distilbert.py b/examples/qualcomm/oss_scripts/distilbert.py index 15dc00a1dce..50462644e6c 100644 --- a/examples/qualcomm/oss_scripts/distilbert.py +++ b/examples/qualcomm/oss_scripts/distilbert.py @@ -131,7 +131,7 @@ def main(args): # accuracy analysis adb.push(inputs=inputs) adb.execute() - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) goldens, predictions = [], [] for i in range(len(inputs)): indices = [i for i, x in enumerate(targets[i]) if x != -100] diff --git a/examples/qualcomm/oss_scripts/dit.py b/examples/qualcomm/oss_scripts/dit.py index df4142e1079..230c759aed9 100644 --- a/examples/qualcomm/oss_scripts/dit.py +++ b/examples/qualcomm/oss_scripts/dit.py @@ -125,7 +125,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/efficientSAM/efficientSAM.py b/examples/qualcomm/oss_scripts/efficientSAM/efficientSAM.py index b5f0d6a72dc..df1579566d0 100644 --- a/examples/qualcomm/oss_scripts/efficientSAM/efficientSAM.py +++ b/examples/qualcomm/oss_scripts/efficientSAM/efficientSAM.py @@ -280,7 +280,7 @@ def post_process(): output = torch.from_numpy(output).reshape(output_shape) outputs.append(output) - adb.pull(output_path=args.artifact, callback=post_process) + adb.pull(host_output_path=args.artifact, callback=post_process) # MIoU analysis miou = 0 diff --git a/examples/qualcomm/oss_scripts/efficientnet.py b/examples/qualcomm/oss_scripts/efficientnet.py index e67839d8848..dba3ef17d1e 100644 --- a/examples/qualcomm/oss_scripts/efficientnet.py +++ b/examples/qualcomm/oss_scripts/efficientnet.py @@ -101,7 +101,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/esrgan.py b/examples/qualcomm/oss_scripts/esrgan.py index a45e4543174..58daf2721ec 100644 --- a/examples/qualcomm/oss_scripts/esrgan.py +++ b/examples/qualcomm/oss_scripts/esrgan.py @@ -118,7 +118,7 @@ def post_process(): ) cnt += 1 - adb.pull(output_path=args.artifact, callback=post_process) + adb.pull(host_output_path=args.artifact, callback=post_process) psnr_list = [] ssim_list = [] diff --git a/examples/qualcomm/oss_scripts/eurobert.py b/examples/qualcomm/oss_scripts/eurobert.py index 64dfd31f11a..78f38ef8a48 100644 --- a/examples/qualcomm/oss_scripts/eurobert.py +++ b/examples/qualcomm/oss_scripts/eurobert.py @@ -139,8 +139,7 @@ def get_custom_quantizer(): # accuracy analysis adb.push(inputs=inputs) adb.execute() - adb.pull(output_path=args.artifact) - + adb.pull(host_output_path=args.artifact) goldens, predictions = [], [] for i in range(len(inputs)): indices = [i for i, x in enumerate(targets[i]) if x != -100] diff --git a/examples/qualcomm/oss_scripts/fastvit.py b/examples/qualcomm/oss_scripts/fastvit.py index a363a7bada2..37062853b5c 100644 --- a/examples/qualcomm/oss_scripts/fastvit.py +++ b/examples/qualcomm/oss_scripts/fastvit.py @@ -154,7 +154,7 @@ def get_custom_quantizer(): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/fbnet.py b/examples/qualcomm/oss_scripts/fbnet.py index 6c8a452c924..6a372f920fb 100755 --- a/examples/qualcomm/oss_scripts/fbnet.py +++ b/examples/qualcomm/oss_scripts/fbnet.py @@ -93,7 +93,7 @@ def post_process(): output = np.fromfile(filename, dtype=np.float32) output_raws.append(output) - adb.pull(output_path=args.artifact, callback=post_process) + adb.pull(host_output_path=args.artifact, callback=post_process) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/focalnet.py b/examples/qualcomm/oss_scripts/focalnet.py index 36ee4171b8c..06dd2fda145 100644 --- a/examples/qualcomm/oss_scripts/focalnet.py +++ b/examples/qualcomm/oss_scripts/focalnet.py @@ -97,7 +97,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/gMLP_image_classification.py b/examples/qualcomm/oss_scripts/gMLP_image_classification.py index 0a718602b15..82e11a59a85 100644 --- a/examples/qualcomm/oss_scripts/gMLP_image_classification.py +++ b/examples/qualcomm/oss_scripts/gMLP_image_classification.py @@ -88,7 +88,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/llama/README.md b/examples/qualcomm/oss_scripts/llama/README.md index 896ffa6a7c1..8f7c08d73b8 100644 --- a/examples/qualcomm/oss_scripts/llama/README.md +++ b/examples/qualcomm/oss_scripts/llama/README.md @@ -162,7 +162,7 @@ python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL #### Granite3.3 2B Default example using hybrid mode ```bash -python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --temperature 0 --model_mode hybrid --max_seq_len 1024 --prefill_ar_len 128 --decoder_model granite_3_3-2b_instruct --prompt "I would like to learn python, could you teach me with a simple example?" --run_lm_eval --task hellaswag --limit 10 +python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --temperature 0 --model_mode hybrid --max_seq_len 1024 --prefill_ar_len 128 --decoder_model granite_3_3-2b_instruct --prompt "I would like to learn python, could you teach me with a simple example?" --eval_methods tasks_eval --task hellaswag --limit 10 ``` #### Phi4-mini-instruct @@ -382,13 +382,13 @@ We use Smart Mask mechanisms for updating the key-value (KV) cache. #### Compile Only If you would like to compile the model only, we have provided the flag `--compile_only`. Taking LLAMA3.2 as an example: ```bash -python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -m ${SOC_MODEL} --checkpoint consolidated.00.pth --params params.json --tokenizer_model tokenizer.model --decoder_model llama3_2 --model_mode hybrid --prefill_ar_len 32 --max_seq_len 128 --prompt "what is 1+1" --compile_only +python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -m ${SOC_MODEL} --checkpoint consolidated.00.pth --params params.json --tokenizer_model tokenizer.model --decoder_model llama3_2 --model_mode hybrid --prefill_ar_len 32 --max_seq_len 128 --prompt "I would like to learn python, could you teach me with a simple example?" --compile_only ``` #### Pre Generated PTE On the other hand, if you already have a pre-compiled .pte model, you can perform inference by providing the flag `--pre_gen_pte` and specifying the folder that contains the .pte model. Taking LLAMA3.2 as an example: ```bash -python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --checkpoint consolidated.00.pth --params params.json --tokenizer_model tokenizer.model --decoder_model llama3_2 --model_mode hybrid --prefill_ar_len 32 --max_seq_len 128 --prompt "what is 1+1" --pre_gen_pte ${FOLDER_TO_PRE_GEN_PTE} +python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --checkpoint consolidated.00.pth --params params.json --tokenizer_model tokenizer.model --decoder_model llama3_2 --model_mode hybrid --prefill_ar_len 32 --max_seq_len 128 --prompt "I would like to learn python, could you teach me with a simple example?" --pre_gen_pte ${FOLDER_TO_PRE_GEN_PTE} ``` #### Lookahead Decoding Mode @@ -401,30 +401,37 @@ You can choose the lookahead mode to enhance decoding speed. To use this mode, y For more details, please refer to the paper ["Break the Sequential Dependency of LLM Inference Using Lookahead Decoding"](https://arxiv.org/abs/2402.02057) ```bash -python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --checkpoint consolidated.00.pth --params params.json --tokenizer_model tokenizer.model --decoder_model llama3_2 --model_mode lookahead --prefill_ar_len 32 --max_seq_len 128 --prompt "what is 1+1" --ngram 3 --window 2 --gcap 2 +python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --checkpoint consolidated.00.pth --params params.json --tokenizer_model tokenizer.model --decoder_model llama3_2 --model_mode lookahead --prefill_ar_len 32 --max_seq_len 128 --prompt "I would like to learn python, could you teach me with a simple example?" --ngram 3 --window 2 --gcap 2 ``` -#### Perplexity Evaluation -This script supports perplexity evaluation and is capable of assessing perplexity scores across 3 phases: prepare_pt2e(CPU FP), convert_pt2e(CPU QDQ), QNN on device. +#### Tasks Evaluation +This script supports task evaluation and is capable of assessing evaluation scores across 3 phases: prepare_pt2e(CPU FP), convert_pt2e(CPU QDQ), QNN on device. -To evaluate the perplexity across all 3 phases, users should provide the `--run_lm_eval` flag and specify the evaluation task. Please notice when this flag is provided, the `--prompt ${PROMPT}` will be ignored. +To evaluate the perplexity across all 3 phases, users should provide the `--eval_methods tasks_eval` flag and specify the evaluation task. Please notice when this flag is provided, the `--prompt ${PROMPT}` will be ignored. For example, using the Qwen model and 1 wikitext sample as the evaluation task, users can assess all 3 phases perplexity score in a single run by including the appropriate configuration: ```bash -python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --prompt "What is 1+1?" --temperature 0 --model_mode kv --max_seq_len 1024 --decoder_model qwen2_5-0_5b --run_lm_eval --tasks wikitext --limit 1 +python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --prompt "I would like to learn python, could you teach me with a simple example?" --temperature 0 --model_mode kv --max_seq_len 1024 --decoder_model qwen2_5-0_5b --eval_methods tasks_eval --tasks wikitext --limit 1 --verbose ``` -For the example script above, 1 wikitext sample is used to evaluate all 3 phases. However, there are cases where a user may want to use one sample for quantization calibration and multiple samples for perplexity evaluation. In this case, the process should be split into two runs. In the 1st run, the model is compiled using one sample. In the 2nd run, the user can provide a different configuration for QNN device execution. +From the example script above, 1 wikitext sample is used to evaluate all 3 phases. However, there are cases where a user may want to use one sample for quantization calibration and multiple samples for perplexity evaluation. In this case, the process should be split into two runs. In the 1st run, the model is compiled using one sample. In the 2nd run, the user can provide a different configuration for QNN device execution. Example: ```bash # 1st run to compile with --limit 1 -python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --prompt "What is 1+1?" --temperature 0 --model_mode kv --max_seq_len 1024 --decoder_model qwen2_5-0_5b --run_lm_eval --tasks wikitext --limit 1 --compile_only +python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --prompt "I would like to learn python, could you teach me with a simple example?" --temperature 0 --model_mode kv --max_seq_len 1024 --decoder_model qwen2_5-0_5b --eval_methods tasks_eval --tasks wikitext --limit 1 --compile_only ``` ```bash # 2nd run to perform QNN device execution with --limit 3 -python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --prompt "What is 1+1?" --temperature 0 --model_mode kv --max_seq_len 1024 --decoder_model qwen2_5-0_5b --run_lm_eval --tasks wikitext --limit 3 --pre_gen_pte ${PATH_TO_ARTIFACT_IN_1ST_RUN} --quant_attrs_path ${PATH_TO_ARTIFACT_IN_1ST_RUN}/kv_llama_qnn_quant_attrs.json +python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --prompt "I would like to learn python, could you teach me with a simple example?" --temperature 0 --model_mode kv --max_seq_len 1024 --decoder_model qwen2_5-0_5b --eval_methods tasks_eval --tasks wikitext --limit 3 --pre_gen_pte ${PATH_TO_ARTIFACT_IN_1ST_RUN} --quant_attrs_path ${PATH_TO_ARTIFACT_IN_1ST_RUN}/kv_llama_qnn_quant_attrs.json ``` #### Tasks quantization calibration If `--tasks ${TASK}` is not provided, the program will use `--prompt ${PROMPT}` as the dataset for quantization calibration. -Regardless of whether `--run_lm_eval` is provided, as long as `--tasks ${TASK}` is specified, the specified tasks will be used for model quantization calibration instead of the prompt. +Regardless of whether `--eval_methods tasks_eval` is provided, as long as `--tasks ${TASK}` is specified, the specified tasks will be used for model quantization calibration instead of the prompt. + +#### SQNR Evalution +To evaluate QNN's output logits against the golden logits from `nn.Module`, users can provide the flag `--sqnr_eval`. Please note that SQNR evaluation will only compare the logits of the user's prompt and will not compare the new tokens generated by the model. +Example: +```bash +python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m ${SOC_MODEL} --prompt "I would like to learn python, could you teach me with a simple example?" --temperature 0 --model_mode kv --max_seq_len 1024 --decoder_model qwen2_5-0_5b --eval_methods sqnr_eval +``` diff --git a/examples/qualcomm/oss_scripts/llama/artifacts/stories260k_hybrid_llama_qnn.pte b/examples/qualcomm/oss_scripts/llama/artifacts/stories260k_hybrid_llama_qnn.pte index 01e15984812698c2a68bbf8a6c04747dca6e300c..0817a5edda94d4d285374d63d1dfdb52cbb3b0e5 100644 GIT binary patch delta 164781 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 delta 170602 zcmdqKe_UVl|NnoU^?bHkZHjE!+V*T~Yd^Bp)+{t@gpeu15Nm{?ZAD%(&GQ*yyh8Jc zvlpS!iy>1C!_b)DVIzdl2t%d_Lq9&(`{O*~yg%R1AK&lg^1Xcj`0jGKZMWOw{^Q)v z_KY5wAFxIkE-jV~FH!R)H<5#`={o50-)<%z4eoy$+ zFMk>uiuG8QS72Ed|LA|SzZ)8AH@sCfPnzQis@;>8jfiF}+=^1mVh;3po+yp^Wt6Mg zn}h%7W-0srZ!}Y-F^4}<>+cyga?=InMVs%fjI#>WEwhunDdN9((pa@~_H~{Vb=n*i z675wdESjLgQyN2l*k8@goU8Vv6nQGtiK&l-ydR^Mq|Q~zbIU`TqBU)Yj&NwzymC*M z8kqM;$l;@PtRt;FByN*kkxRzKl6!Er~@S+3;03LEX;y+ARqWa_e_tq6;y#-5D)gu@K{?w z5afV3&^6s-HGv9{4PwEbB=SHIA(wi`q^QBz+HXr0!lz8hyoo4k_XB`7KjC%Tp=1kImiOh zz}|G1#~;YtpDNH1Llr0oSs)g)M>7V>K{kj3d-mfb0P6r(BP$W~jOJtk>p(v6gYHq} zfhv#-;=!IMb{_;m4v355B0|g)j zctLw8^9q6>A4~@pXb-_8SO*G0I*0~)JnSsk07^i1fWQ1;pT%hjHh~I|1N@+8$g*~T zTCf77gDB83Xjx4l2=YN9*f(HVuY)>J0 z?E8}qf*Mc&rh~peXaK6gQjiGx_Av%(KmkYw`+mm&s0M`~4TOV^-z;krCv`Yda!8g>5Iz?NTV1ByTfhy~q0V-3`S5|9PrKzA=zKrL7SGC&mQ z=&`IuPyup4Jm~((vbKS0Pymua|6a>#1$CepWbCy9mKW0Yqh-~D5|9C+Kvy>w!8(u+ z5<%Y&>^GiL#punyz{ zKj{93je{zX3*y0^uPti}2!b3C2ljL^27(|L#DhIwQ46X-F7SiyFBt<>AQ!}gJ-ew5 z@K+GzfH=_A$>|0vKsJa4onJ7$pd4g@XwdOFM;erYOb`XyKjTP)5|9DHL3;;dpbTV! zXwdN~{XiKAWbl_4w0^>I1qC1xbbpLHPyw<)6liOw87KxBzzfc zPzaL2z7IGQpav9x>7cKTL(#_ZuZAoIiJ<3w4h2{T@_`?8zsJ;pDv%2j!M2KAr} zWP^Cnvy%;h4WJlgf>^NUUHXC=Pz2IJ6zF^>z+S$?20;Nx0bbDlHd6$GARkNz{jHX@ z6EuKwkOTan?=AKi)PWL^1>!*Wo9rQ|1uH-Xhz4D6utT646bAS!4TOV^*E#l}3M>W5 zzyfWrajZcF$OVaD->b|3s0U>r3&epvudt_}8We&Q;00|v=mE+>4u}UmFLV4`_^TGI z02v?}biIT_Pz?$}8t{Vl?KA-uAP4wCPYW9a>p(v6gYFlp2UQ>!#DP68uyY^?vOyf^ ze4gXq^gMe2vOzrP*~T#hHJ|`Y2Yt^mm7p3d1&N^VS@J+FC<1A~3)-IH5P@=#1N@+G zD-#LoKnchKaiII@c{F~SCSV1~0MVf9Db5Q}1@b{6=zEe4f?9Rr#Yut2CzuD24dOu8 z<5YlZPzX{$e=`RO)PWL^4g8>|i8%w+pa`UcaM1P`2NV>8bl?R$H&YK*fHYu%)<>DQ z4UbX`Qb2zr`v+=4AxH-M9>FfC0R><>=zEym1vQ`mOb7cOA`jGnLXZshZK5Bj1%)6P z?0b+rPy-4c3{VW|dw_t0VARokou0TC6P*4UkfETphi(^m#5<&MpYyeb%91sh-?xqe@fGiLT z+Uq!DKnchI;h^m<4h1L%>A(wi-pQc=D?lKPzbw%DA1*hb2&91i4NM)V1%)6P?6b)O z^`H!7gLu$$2U7|*fMSpdV!@u=g)td@vpK z--ct*0LnoQ@Poef7zA~o1Z06Y&|Sl!1hrrV$NsF>8RD(j02EuO*aJjmLV+E?f zQjiQR&~`Id2~YuYK_b{!%?yBgPzJI=Jm~&68wE9>2&92<(0&s;1S&u-@Pob^nJusZ ztgsJ!(8G)H2KE``f<(}FJ^KbWfFh6r`mbXQ)Ph2g4E9yASx^I(g6W{=TE@USuoT3D zo@>}7*Z_(_I`D#>mCRoy2Vn&y4OpNx$RH>N86XODFi(x30^|Tc=(`$2pbnIPED#5} z*J1$Ff)yYGM1!sh4gjbI1t1ypU&Z#Wilhmo7-XmktCIrxS27Qvr<_)x8Weyu5Dwa} z;CujOAPdBSo_}EnYyd?d4Mc&?%Q?KD0%U_&&{@XrfO3!pqCv-H9Bxo{8S{||i307H zvLR3cGC(+JE9LluVvr8JU}p)t09Jr>-~~HZlLv}HItT}Cs~7_%AOnPhwv~*5Vvr8J zE1BG##WVyfKpL<>>m}?RC;};<|6=wS)Ph2g4E9~bkp?xO089scE0{%44VHrGpzlKR zKn*Aa$)NuN#z1v|zw$vm=sKUN0%afrctL9s8wCX*5%itM>l{>re2@sb&t(h*K@Nxm zUCTKXpaNurSkPHWe^3sxKr{$+EaO;!GLQ+PK>Ily3Qz(vKsabSo2dh3AREMko&xp) zYyia|6U2f&XJH7`fFh6%qCn@FT#UdvPykZSjdwO|Fv0MVf9R9*|98We&w5Dq%>nW6ju7bD10kPIx)b_z2B zDnKqs1p7|rP=I<+2C_js=*eRSKrL7S(m@pH$fY?5f_#t&_ML=rPzQ=ZCWsF3SJx68 zf@)9*(m*)qIFTlx3gm<7U|$YKKpiLnnIHg=n=N6cr^R-nE^P4ZR4_NdCMrueqNz9M&O_&lo< z{;}Wwe2ZtAFKXMs(ED_p<;#HWQmGY5z7?={)Y%nN!fIe|`t4hu^i1?^fxjU}BRH&9 zC+c-^GLnq~rLV5m`41ZRhpi`pd0E_s?ssX#WU(yNnvn_%_2sD+|7RGlso6V{6ZLmPCF ze=9}F$lFyyAjz{-dDh0Nvjby&RgB%r*whhottNQ2=JTu$_>F2;AjucrGB9+#9-VKc z!LQZ)5UUU#)O>+e4G*ZwAhQa)8s-n@{Ov%jRl6AWZXXz`(8Ke%79*}w36)cP`LH#5 zm=mS~aiuC{xB*tKhk1>)BCb%|8SaDq3pUA)evaLVe`#RoausvU6kissOa}@jP>j4x zEhkV5yHp37B(M!xs+tLO!Ac}>$2P{JULF{_M0u`F@}R>0T^{`f0 zj@l0Ehn=8$Vacxy3>~i$uAAj6fE}muVAZf}RSDY$J61Krx?xAF-PcX=#lAW)bQCOM zM5>hmU#wEEpW-WmEz+a&tZMi|ReAk9-wxPeYVDPi?UNdLUG^Xk_1ovPcxI^mZt(dM zUmF;jufvZ?I2V~_!m9~ap;L5tzefo-A?KLzyM(*Yvvhd1gri>{7@DcWvwd0c>FoU! zRj@AHSB6T`sv6aRnx@q@R2yolR^2G?8v{d$TE*WO?n^^W(JBX3gqo~X1*#U6pj9Jk z2Wq0W@-9|VB~O?y?#+RrgLHJxW};chu@a43ux!P$RI3;rtHZZScmr~O zU3|3^w;=b^(VryRjT|LWHF#5)FZQj0p-7$kj^t(_BXn@dzr%b*s1Z6i@nwS5NS_Yg zLvRZ!R0mf{umkDQ!My~%t#VyV{ddBML#$-@fJ&(5B?S9ZEx(C_`4~=u$Uju+&3?I8 zY(o5|V~$OXJytqSCi#CT!CzIOt0 zQQC?8PHn$sQUn{b_MyIUg7mi%-x(PCT7q`xE1dc{$S<9!B$p$1>r!b{kNTY8lnCy? zRx9c=V!YyE9V+Hly*Kgi4h(&Y;PnpsL=RJ3jQChBXSf#Du7|lEZ$o^fni=kbeW-_d zMMmu$82UhYYNq(oU~MX|W|H0W8vY89?<1%9s$lQwK<4WN8j-uyMgncHojOn>fqvw> zs+U0GF1bBOt4;~zAm3Jt*H7`4!CKY&_1b?O;w{7!-wxQDdKmxRh&R-3hGXB8J4otn zIy(dL8iE%w>{UHXb~WM^Rmt!c*bcSJ$?ibBjNnT9J~tjcOm;G2yGl@;Ca@M&s&wxP z#ES?HDC`A2Om-{cd9|J4KG-%DQ|ruo+rZFs2<9F3tR5!281al+&TuVks~(nlM?9^X z8Sa8TrH5tSKNuK#Qh9D?-eFr*-tD?~0pbY+vkrS)57WC5(X2Ky+y-mX!}RV)Jf?aX zPW*6SXtN%scMjrFwfGL^8`h}S-=TZgAs#_6->`@EFul7G52@V@$9^RDRLQ1y2I4`L zYBS%k2lOz#s}cWIl?-ozHK<*-?%jd7AHjUL4-DO>hv}V+*r*aVFyF9xJxuQk#J#GN z;Re_}dYIm=h`ZHxhWlW5shIyHj7YKKKOPwR51i?S*?M%YRSdsfEoZbAR-;f6T z?Jn+LpAHNas))O0`LbYV!je=W<7KFPbrItYuqArDh4D62wt9ncZ^yvUk?Idv8Z1L+ zdCtW0l!tMB#s(oo-va+<6_?h$DRKBB4) z`Hk95)lOI!Y^GXN@AG+g%Ow=$RHY+3McOB|Q&o)oTs7Cv@zujRbk%+vx$2`n(N(f< zR`{24k(8=QA5)cqY;*j}rG5qSeO0+}mTv=WrxTDJ-GO{pH52HA@wJu*C#yL(`F#Gb z3Njj#+DS1wpF>?P&7pNAvZ zc9jZig|(>VuzuK!YCSCZYq@4^hZVq{SG}-m*fy2Wz{3&jIh6+9E za4{%{-$hPRgdCksC|l>rVy6vto6bwMyx+^CJ9(;Qlh>DqS|@p`rP}K&L|v=%=JJ84 zJdsO3xkb02uGIbH)aXW)k%ylj28LFtW>^kvh1w0PfSs#i9%jB^1!^&@19qA!g++A_ z4CShgungGoY8R{+mZdz8Fki4ER4S|$madk=`eAd`dRX$014GGbJFEaUP4&X6VSbg+ z$b7-ZsXSOWESB0CBj#E0dk2O_<0i=tcF7$V8LmTVRs}pnhdL$Hi0oI3AH^-~H&qIY z`bl1;8(|r+@6|3?F|12@Hscodxk`n#!aCIQ%`DAeAFG=;PYLt)@BlHvKIa=2fLZW1 z;!`5z%20xOpI%83a&@Uky{&dVHg&`Ts}=s5TK?E1Uk~h6*mza{7}wffc|APz*c4v| ztc7frny{yI-F!Y*m$QqGD!0k&+kx6FTN^1?njX|6Jj(9zO!me7EcdQQn23-^sKhZ$u{f+F{p8K-E0%^?CaShAMTDm`p*f)kQVmQ&fPwQuRKb#OF->>+Uv_grRyR& z041n2U9?k*>X0cab!(FEby%`43U2j=^`T}>P)B?;Sv77A4@>-$8?z%jkom|Nj*NOH zJgf>i-H{o{EyyHC79+cm(;QicjQxw1oFiM2S;$03_CLcK5II?;J}b{Xb#OnN$2HhQ zJv!IwhR3VQXQzyc?dL);!Q9kU@B0(gyU&ga%VBiv1a(jKWc{irM~<1GMn5+@tP#5Z z1hx0(X<_ZqmEl` z7f(c3Kicc)M?@zN^0?vXPem7?LmVC1hpt9@9DTUxZD`BUm!N&!=pj}4JQv=ffuTXw z49kK2rFK6%d8E95WtAiUP|IJCiK>VHMrevELbDl2J4k^{Z^RY8XWpbSIo7t z;oUkSSEe!)i#U4Cv+Ce`RO*XNI_w*myn)aS?^2bF#(D;M?ib*=i`$ve^lxJI#Zz+sNiw3^Sp1i2th8S2+t{4KSVEjksS`G8`K|$RbCE$<9D* zRJ#~1g0aYPva1pIs)W}#5nwEGoa_$7-KvyfZ^R()O_si0NqGwikcbDg4OC_nRkRzo;R3x*ll`P<{hzKQIUhZ1G3B?ALO05=C`NV@gqXQeL2VxTIPtXKzg*S5ZQ?QOD*1o zXBcmd!J-b3>*hvS2JAbv3swyKN_pPHGptjkzBkX;2K!9Kd^CCFJm!&)dE_#f*EUT( z_g;iA4b|=h<@#5M{747KzaQbNMZNC?IWSfWlDC`MrrC=kLLz*9$af^FZr#rE>cBys zBBkx8ROX}JLd_hhFJ|kIZ-}(Nh~O=}ZOGS+Z<_`p;xMe?4| z2P{!XQdNz75jn@#0()KpD)52N*N1veSIKjPHGQmH4y7s(NmT*zY1Kb<&4E z-wsr>vu~V>Ru8gC?fx)HKSKKBd$nprC61Hp zm2~Mq<)iM_D(d5KUlpoOs|?f@)LmK?qqC%eILT%8hA6163Rm3N} zc>=ow#+xdi@a75Xc6AZs-gvoy{Rfr?Q|b*^5$rbg2doyho-q~zpRy1@)u=N*Wi1Z7 zRow_noFLb(XJGlTo7H!)Dp<8TsAHCI3+&(IO;f8oc#9r&le&`e*oku4dI**UTc%N|sr?ljkm^AGYoE}i7G&PJW4^W>#dhRV}<^Q;E=3FJ*v%f8@!4b;&@ zrm9=M;LY)bK|bk_*o(x{P>1T+USdV4c{(UTBP)Rx#Al8RU(6P6Zl7-pDniFr5$i&Q>e#)zePOXv28Ra5sjELv zRPPYXLh{k&*VEJx!7@~@6P*2Jn6Cl(gAOkH(ihf-`q~NJLeQHyIP`@MzDO_)^|2G& zORxy}KEWjA|H>Oyi+bA$F8PX`M82kjL4tj#?M`qr!NjTZLYK@N31aJ_UPF*N7Rt67Wyw?l;c9XFMHq;Qvy)zu`&) z<23y?$+ro1sap8$9A5`)l`3T{e8%9=#W3DC`Id_d>H<-^dm-vvQFlz>HdBo{M;SfNA8l9-?6%zHOM+oGUi)Z@P(QmVwJ!T(|mzd2R~G$e$Vp+EFGqw5V{cyR3*c) z$%DKFE5lrsGZ6FDE{2O>X?mC&Q8j{B$qzhFz~<^TdE!#8&)`LbaN zuqn6w$Xm{c2_p0Xs7DE^-vU6 zfeh!Qi}Fmi?<%#2M-Et@ zis_piIalA4=ME12a*(}uYRC-rVheQ+j)rI;(t2$I{nyeC~ zt`(Jq;&} zlfr!DE*;~!p#s5^lf>j6vViG5hnfFw#B-{W;n)Ro&6Flw1v3y&t6dBi!L~SlarqRENLGhi3;F&$$+qSEELNbiRn=5*vEI);}*#3m;u$G95#ATeIEum&d}N4FDs zzbf75ER4esm4_eQQJ$~TkbE4qZ<1Xyi&s(svQDDvi(h=cb*Q^^rSxe+u?p);@^!#$ z#<>n0CXX=N_vsx;M%<3zDgdk1!xUB^l#2O-YXIyvJ)ShWu9l)yIRzf~TAF4#>v;Gaz(Dnp)VHWEmKtGxvT7UvU3oX2+j}~|E-r{ zvg;55mDJ8kI1hv#SwTA~=>Xo~#%q zy905BDrMNaNbX|Wo$O@9tUR1LtLWr818~ytcU3xwOF3;HZq(BTcL;PU4Xby z^)g%qyWsZ49%sH0=OdVJSdkv4cR%91+t)LkcqI2(JxuQ$#PZvBGh7BM)Wh_yLoBmX zJ&f;wo?};fobKJIvqdrGM-2`YI02b+)LHf}2^2xkbOLOl8g+)95JF%Jbg2_y8y%?A z?NSMNvslpD+e19^?preA6cNlkG|w5IYgHglvSUISZ-5?e9~$bJm{@UJX`D)i`s>~TK6Q?~^k?!;D0tP34-kiAA?vBwS$_1h2mJhQ^Gpuff0 z&w4#m?IH4fWeoJ#5n;4%fPQaZ6h`}HVT9VyU7}gMWOJo=LN7`v4c%_PA)zAZdroN5 z2tu{!R(r+>LOY;|?ZukS--4ULN_7@{OqrwUpe>P4n`E9cP zjAb428M`zBOZOfg!W*mTCnY@1w+FgKhxVJRzt6+PEdF>dx=yuRbhD9<{gc93!gY(W zN1PN+4r>$gVP^w}XRtW$KyMN~#}|D9Z=eaCX}=qR`z&;W*3PH1<>>pI_%<>Bg0ElYpl@;ZNA4Hp$eZniQJh$vNANtf34K$Xy65+4zBcHMPN*iz;|=pJ z;VS7=$~_|md7Tr^TtsCd`Wh!JH|c6*rG#1ML4$Ul&~9kJt`r)75*J3hS!fP)t-V`l z1+>DB84YcOUS%&9+5uf-mkN!_<)UbB6q*4ow|5CGhF)QN_Jh_z|7E8NZG~QLFBjSm zEwk4PP0r(rXm1x<0KL@i6J%=9_C}!@&=vMBp~cV(ZO{JDI_L#$N zz0l-*E_8P92+!n^@`S^e*X7E!KGq{^?sbS$CC~Z%b}KSZa+7>r&>SZ%)rvipm4r^S zcOJk+A`5+_ofl4C33Q>8H`l61EU>r7(oVLu6FJXrmb8B8Y)Qk=X@f)4C5Z`yPPTi6 zRzu_Mgae`5pkriohL?{HPP1Q#<;323Y>3wrZ~qwUIhdtQ&gnce>kZi{+1!jP(1Ui& z7^)kg`|QO+JD@+?r9z{Y@`;7LQD_GA8+(_~VrZxB84In0eqyHzZH2bk%Z2ts-?7&V zO+I6A=yiL$&;sa7cCXNC=r%hc4!RBcl$|HE8`@-73XMNgUh&OBbD$0OZlM*>d+eBl zppDQC_F|zO&^zqXgP5=Ivv}UI?>@*gB`gJc>sWj52`o_a;kQV}6#e;q5Xs9gj`@}g zP!sY7d-picR6bqlKvdeL<2;jmR)M^NpyTbPadM4E2kd9Yacjweu8|U!#LyC_I@M}G zT&z3Wh2tq{L$A;qb}lJrbE=KCUpSF<{B-zo`}y%~Cmc6dq1Y+n)l1Uqj6J1m!l8sxEB+9fB+V;lM? z(R@w|UF3x7C(tx@+2GLOPNiIVGm#6Nus@f|V)Q)QGtrae*+6^Q%|On{vI1n_fZ+i@89Nx*-wf1AMmGa7!vm5L zd+y*+v>E7@j0^_$8y=91A_hi}wg2>cCWlqSM~$_QKP6;x*cNycJc02Jc;s07P8s)} zH#jsBF5}7Yh_UwfGM*0)hs$^ce8gD$uzXI826%`}IS=3YEK+x(2krHfJ-maF&mrhX z|0z1jH@%4G1SixvnZuBe{#k4NRrnxsuM;jjmC8o+_x9o`RJKFEaY8jy3mbeE&Mr=W|_Upabp38re%UphJ`k@ybuZb$EziS9#pI=WHfi5CnGec|Xu zrPSx6KX-K0RN__W4tw!b^K^>(L==yw&~_&v&!?yl?ezpkC0@woX3X%jNy5?)zwK8Z zzFk&8V3*xH)sql;sGP#qItF&y6Q*(B-Ue;eG5ee-zS+A6{g%B}Lh&nDj5?vwuMo;c zziNLbp$aG$F5OXX!qz4Rw%ZGnh;=|;lpWz>c@dv#+8c#tK%cjF2`z?hvpv(HbxCv?%txH|cA*8(C(SnX@_QWvPuQh1oEd6DHA#4~z4`ziKswNy zCCX#+B|JMhp=GZUN(~$(&W;bfRK|=AR^6b+rp)BYMC)6ULVl)pxDTHdF7dWBl z<%G7O&$sg=)CDbaLWL5Fx|Btsy-`AG&~t_AlcE5%-0qb?6|~R^$Z3RHW-m@9&;~t6 zsB;>j&bHS}An`I;80vtWMyRvw-4ZB+o+(sU)S=F>Q|A)c0bS|@sOUzWZdXbmwv3M| zgzAb6)T#C^2^2x|od6ZpDDI#02yB6#EL2x?ptzArzwUZ~zi{J(gI*>09V7Bt%lu#IBWv9@PEfm-M> zPJnG}Lmh4BNuUdw}#4kXSxAl;H%}Mh@z5d$$D2poa<7+o(ewYNswBumhUz1lUG5YJpuTf!Hf~ zo7)MnjSSR$dzS=?plMEkZB(P?+6n0dwm?&aI>!Sw+b)%Wca1z)=zts#RFb_tU9OMN z1iM#gHFUh4a42*e^gv1AQ{0O~d?DRy>=)8mL|<|-tH$`NxcN$OvVE869CWCo-x6Jc z9`wLNlOW+q;AoL%*;+hePY2AKR%yTcPjU%Z2ts z-?rBaOsT5CYwgNKRJZU{62|}Im({2q=+Ogp3|uJ#d?_1xg%it7 zlw|KU`uZ%@K|PSpz-3NGlO8B$pw!OGq`Dr;8dH+_32RihF>s0A>nQ!dZ7l0ruB!*? zubX|E$=AbD&|Ey(^{54MJFp5ExWGw>TueeW`aF}M9}~7RaITYZxFqaBFVhM3-h(}1 zPiHOmL@$Z>VSkPh|7+7@m)`D4QcIh z`o@97xi^&X1eL6nKFTgn<-huX;D+!AYK*pC0p5 z$m!7s)?zyd_;Z5%HIBWQ@${N^L(ZIZU^M%rj|1buFo{~$ZBO^U6Zurg!AGrLb>8x_ z%wvw6T{ioeBWKRFGP5pUx$?@BO6FNBFE3uUx@6gTmzHH7l|6Ib>m9^j24Z3z2+Cg%@`21sKFkQQtp6g1|J+ZKTdxbXtP{aZ z5_W*5&-st)^7HYcD1MBlRy0D zVYpjIkKliGbHM6J;AhcUZCCU2<2w0Sai<0>%TMFW*!b|D1h`w*g!7FD@G%K~iqoFw zht3@mV%6~9s5etP#Wg(vYTf$n2+KMT3{ZOrwHe>@gX+!=v8MC?)MGHTwa8Wc0>xWh z`jwHEbu<`5;bP#84zc|AI=guh#bem`@Sk6)b?Y^emNgR0q>lHzKZ?w@?@kJh@~pHU zP70l9_k9$Zd(g7mk~3VBX3;Vjq~YE6+3k^YJg3?>w?~dWXy2r}|3?;vbXJ4?yo8^* z;M@NZ<|NbMPW#99$OlKo8hH-eim{tNjyxb{_o7pdIAGW}|BkVr|Cp1$`{T&)nEVIk z{*PFk{iDQ^KZy*VQQ!1G>v(1AEj^C|E=+&_8E+r_N#xj3@e;m*L-cmyVH51UPa+Qp z@%ZfOOTtdGQ@)9emA@+Nk}o5>?aZ$u{dVT@pSFon!&=kAp+eoin-w_%0H}l%n zUn4U+BIE3)PGLVphRa{kc1?Gr|BkfK*gMigqwJK=A~)EDw7lbUwo-C%Xq=t=>I2k zSL+xIe8iv;b3%(mC04c z@`51XyZ%-g`Mg5?@+!Tx%D!oDhI1+}bU<&T#j& zCfXnT6qz(?GMgCU!nt*5Xu%Xaq9^hapZw69M0;azeFdm>7kL z^;Px@J&|LMe+Cm=ytI+?jR`R+KhQ_UF=a_lF=2Ml@&v8mh%0WmqKJIP{nGE*wf4l` z$T0`qPn~Q}a+6G*)K9ff?2R0Im}h>bbpd6xj%LdBAKCwDSf;J@<^Vf|j`oIH7m-J+ z=os=|xxl`sH*(@!@?`fcKR+sX1@RE5#BZx&kU!Gv5@SoopIdA1#nzz>*pgk4`dMI@ zPlbCjt>9vOtu($4GrnZklk9~*M~)AiRc=`aBe{`T)3M4wnf2T`SDTVUDcO30Gh4FP zY1m&yjQnVc$eK>b7m$< ziD9AEDXtA1%?47<2DrZmhw}>A(8;cbhdT|WpEaExr@DF^Ll4$m+9W-!Ahr3#LwHQk zE^CcT3|pt0rUzpAjS8M>7-M3C{9}E!>cEBEogKrxuKbLJTX9xbM5uKdb>v1%-Sy}4 zqoj#(ht>y$84g;mdr!ZL#-o?DT!55 zzla($d;CCHk9G9k(13O&3AULwmI+ptX}X8;SB=5B#^Ar7cvlgGWa48Lr zrD)h-C$%RL7lRW2k1@#Onzh9A+`;(E#$cK;=xdJA2CFa_NF`BDJV`8L1IJM;4oTJy zEjE(kY9>Fojt$BWC9*m&IFFbZl={o5=X&+GK|i%~iHkvr-)Rg+k~81w8HnCVV25$Y z>o!QtS~%t}d97QuI7}l^3`*V=Y~V!3#h|>#^-1`H$6DYt(>V)W2D>nL2r)4z_2tx` zMBVTV*8jkd;y#oFaVQCQRoQ!gj~o+t#Teq9jUf5ftOLUJlr>=JaAT;0{53SpBQA#I zT@W$k$IxNMkmMZUGSrQsg~pJ?uA=@FYK9GEQk$_il;>LdNJ1UA^?ew6))?Xf6eQoe z)f1r&^@WF8M;Jp($v=aJrxF)Ka(tL)i-VCXqxDBTzueewy~ke3;nY77;TUqHh%Xefrr)rRUZlxPe|PMXV59fp#O zA&H$${aG$UE!0jMHY5r6RN3!{p>4(xSI!{$)*l6l+E6TprW->gBcZ#_0UNgGPT(Ei5II`Yq_;rYbHklaV~IrN>!I?x!BoCPjJTQC%B3`y()>Mx{j zc!n$tjU6^52^*{I&0^?9V<^KIy8P#P+E5mT4l;&n$zMUki;N+1opT67amJA3q`M4l z!_atRNMaXJe~HUb3$^2h4N1a%RrZp847C_T3yq<7)3)mCP(Fs@jiGw-FQMT|V~AYm z9HQwYV@Pribs5@$p^3(j#EPk3?J^Xk)-!BK67J_ZB!;#dL;SeBAo&Sa&LNtH7(Ke zJbow~iF|@J7&(4_?&$jAs*oWr(w6@t`p;p2_pl{5*T~kSQx{w+|VLJS}8Fo_z~rGcwJlWHW`J^L>x?i>Wsu`hY~Rxv(2d-ZR6tlv!SA=Hj!=H;f$<4KTztXro( zu<%H@e)cwxrkRZQjS98?kY^%pkkL|j9Ixx^35){rY;%;E13x?y_}}1H9=JAitvSS5 zrg=0E_F_5+Vq-$Bzl>?=eLTI_F)pTgED8RT8=yIj|1^z{B>A}${#k~kvkVy^Cd&|s zoy`;eO*BEvGR6wN&Tsf|)n>R>2g%IbkgN7sYO77{O#by#I~Z^kFLV{BP`uk!oK5j9 zu3~<`lhtWzPsCWAnf7Cx39#UJ$g64Yj@|SI!k_zJlJjF)q`7JSpzn7W#I{ z@ed{Wbdv8jd%)~VFYC^)@0UHW8p!z8IF`JNXBEdFBa*UB(%impiB7FD^E~GdU6mgRMV2nv!4vjZ3 zF2<;}mPkD_SqRSrLEd`)-nE@gVv_A!S)huk`_Z_RW+&3@PR7NhJVz1Ac;?%%YBLj zxx~ve?7^V^C4OOxtJ%rs7|TVfnpNRP#N@=2`lqRHU|eQz4rrnFLsL70c)Kx@%fdWJ z+*&f=2?wY!?-xr|g{bFq8iJD;}L25rSwd06? z6fjP>CI`v0zQ6c)XGw~Y&x{dCY@*S_#Kj1?&P|VI9j<1%E+Z8f`N9~H`ey1IsT($u zN$uyvbG$G|DKW{#jl#;Z%KIH7|AzA4!(1=ejcg9$Q#Cpb0mE$r}fRVS1k#7Su_%98%QaF5US}1g?O!8VtXOp4zz#Y4nDFh))@My`7A zP;I0EBhAK$a*AmA_KtE&@fFogBF*OPKqBl#a9u3 z(->j(79`D@xGY*5>EluHNn=FvZlupk#N`~3m7bgk;g zgB=tNADViKpEAWa5P!=US!#^D^V?Ew#LJ94V~j{%HGN*Cj~F3W8_A^C*5UiVBxSjb z#AD<+V?<&%)8KWNkwS`}HAWQiR%3+aXOJ}O?WvL4NGwL4H%27yR{Fe2T#S%w1!PJr znr(A6JKAL=5hE`eBhsLT2Cb9~&xoJe7fkKF#NReX&NN0^uewefNyNxTV?^?9qt82R zUppb$IZ0mN7jLgYF9RJ&C^d1Q^C9?X~Ckf3OT+NPk8A-#)gT{z#=nm@Js2iS)7Joz_nSy+rJZg+c;$1ZQ#26u28?k8C=xTPH%SaYR9y3OyzK;41mk~d; zn@#Pz#CI7Z=g1qw>V^+ZC$+Z`XDB)|cbL>ONj=)6%EtdOpqH1{ zqxKP_qvl2&JM7VJ9zDDO+~l$DGF>j9%W--3!=ud#&`<166DuZm!mt@#J|UA0Sr2%u zyG_caq?|ZxLZ<{IrRBf;3Pe)miDAxI)$NbnH)7iQSA3PkiRR z9_t=cdj_?U9JDCn%gm`5ePNEQ60KO)>Xqcl@}(gR5DU zIRmoDN#vpYT4O{S$)I*)a|t9G%gHj~;LU9~yX{^qLf3{bqz z6qn%a9y5J~X8KyDzpK|u-q=v<-{i^kN#08Kejklw_Q=(1C5v7+xq3ZjX74!SX{?oQ zCMJhb>Q_tC+gBVFf27U#s3!?gj5Poy}^6wkz2z0Ggg zbQK@vD$b_(s^Ma4mrxt-s%@cmjj25=&a%EU7i9J=NNel3&);;`Ws#xQTJi#Ni;~1| zn6r^I;snv}!ye4PnKY|#H9OZ#pIjHBqItw5CMT5Ce@lH7^)h?1Fe{|?YEzp-e2+0w zWQ<%hp-YSetTvo1CsB+@;#V{pZH$oY+@fe!=xVmyWyFh-^NbOx@1ma1L;g16r}kV^ zyHJc8Bj;-)y#AN2o~@T5J1}y-F(QebG~#!m>KP%~xkcTzm0TKdx5E)P5c`%!uG8T%#<85C0ZNVf{}}i5lQ@vM)FN7IW**IBZV|u;cB+b zWuy-ymlz{b|2g$>E+au|FE+J-K~ADC&6HedjEr3NnzJy&$Vy{G@^;aP-%+cLkn0>8 zniZR7l5>{JNE=308zT~XkNS9*5&s>YfVIjLkHqO$#>fg|B>nVV+DH{f@{AG5YopOb zV}x97B=dHD)vK#lfy+n-MouwCr2Yfy{n`lgug_MBPd3G4NRZ!W#R)0z*9ZB>^1QX5 zevGNb$f?GN@t!`@j0gWVd9?}BgMwZ)%BaS5ic{c*cg$#ZSFSlgmg65nP_Afo z7&*!qkp|DxAjM_G!pM=P_$lHY#>h%zq~@fzwUJni9Bqt9-i!3%D>?dAL#{SbkC80t z#rC5m>C}Iw#A?9EvBrqRT4<2wGSWivF~-PC#6L4eRv9DTjyhPMBZ(L}&KQxrm*{hd zF+#335-6luwyW7`E+dT?Il&l_1~1bf-DM<5?eV7e9paxGBdd*({B_R9yJ;9X(HN1u zSLkz?VoE$1nFd~WX(C8Rrgk)``kY?$wW@oyLv|{9NV?^rTr9RtbBzT*B z{rIT4At&Y8&yHu+vWT9ALB1bA4bKYESFy?d{{E#ycsM9&3?FsUqbRlXBHR zDV?MoVNz})!_Aeeh0g&IBU&6>!=)NV2-Q!B#H}kv9pdUq;|He_GnjaF10bP z+FWYSan%L`6i1t4g|m7yePw3)-n{Gyy?oih^5p>XWcnoUGWNcZMx)qsa`p11zQkkg z@9NcTX74!SwJcu_BqoPZ>dUAnZ6xu)P;0C) zB6QfX^*SnXf4*j5j7Z*6`m8WU$kpqpmX&dbW zgM^e0)H5RQr^(H}kY-V?W+g5o6&Tsi z7?JuI>bWleJtIMCN1NJ%iT@%-*uKT##R;D`Kl76|QiYLTV?+|YG`iXtAy*r*Xx8Iu zR_roTfRSH}5vli4&zL}n<+k&gg#@0e}eSqCX}r2_O%%K!x)jg z5j48q7$Mi05sd6}^;+pNQiPGej1j31r+%F_!u-qG>ZkZmQ+zcEe;6b52okpb8hfxl zG+h`OFh(SA42^CwM#yz$s(&rXqgj{VTlU|*!UaMS2iZSvPF};qG29Y$VaTzJ3_)Al~oeRzP z#t0J@q`!5@wfgmI_3<3`wJ{=j-|>9FW0!u_$jL3Q8jD_Cu3qQ;VzpI>>Xu$i zQM4qT|Id_Ii5U6G7?D^H4eDG*>M7o9jC@V}M`Pp~W8|jSoln{lF|x}Tk-VSjbB{4X zt~L_Lq}fhavkP2Ck}>kWF(M6qp+UXNNFlZFnc80BdySE6jgkLG_34u}4I>{IBa-(k zeeQFN1T2!BlZ0k%u4WgyjHF@YBV$Aw{6>QYmk~d;ADY?$;y)Q9RmRAJKc(q&Bnu-S z8zYjpPu5Wnm?7@1(Qyamns>c|) z&KUW0!H}Mje2jc1M&$TQ;$Jj+*cc(%IZ0^N;c9k~%SaYRzA#3lzMuL=myvpEKR2}p z67Mxet~W+r9sEHXSy_aW?cxL@k~m1C&Bh4H+DInNT3pR8b{WaR$jiov)DKbLM+czUBI%HX%9X6At%_0IVr}8BUa(H(5UyPjxd{ou*_Rl61fq1@1(pTiCrzYlNvuLQNCsJj%ZNCylU-V#D1c6xgb%wCAV=NF1>+# z;K7=_hRNi@sD&m5lh>`uJD5x@yswf;OkVQN;-e0jq?6a2y1aeEB&4BNC+*`P_a3Czgq>~-WH+3B{F&r^6~_L3 zl{uY{IylZ&;!H$(Nw}|i#1fH%5je=VMEqkg*oQ54Bjc|-$KZh+gCAu~$6$?(=D@ZS zY~g)X4!7i69dMt8Md#oVRNyfOd^X_ceQny_gB=3)MF$&=0e;+q zx54bL#XcwdzV_$dp{$6y^`-5+0Wi8NmRuuo(kM4q)oWOp%+Ub9574Q}vy z@yQ0q*H(Nbi8QLorx`2}@G-L(2XEj&2l9T1fk>VOzfOQ#ERicLk!Rb!riRkk80Pgou>9=bS9v}8B!h$P=k&xFdgA^hHU{_{z`HEC1r7>4KDpq4&v3vK z0B=l!!43ucfrHhri*K;lwe>hvEDm01ztUE3^)-Y2{d6B6B7Oin|Gr9wy~+L~9MK>K z<6vWl$R@|xQk-cXwJ&6_93Ny%4JG{P;P-$p&zMfCd0;nN?6r&^V~Jd4iF~tUgoyC| z6in7&sEu8Qr{d^SO9bO!W5?NQ$Jr8xNGXV1XNd^k3w(h?BnRxZ7Q2k`vX;myp9t5r zlb*T4FUj)|xxo^Vp@*Z-EfI`;B6>0MddFGZA@Vm@n1m%Fd{6NE93oM$H(Km$#>r6I_93t;Q_CREr zB_ezW@ZbAHsK4K>fR|eE2nFLu7-6S*EDD=ZP&b;8k)mI$^%kKk*$<7=@) zWFJH>wM2yPtoy2;osvWWue9I^7?id|uC+v-{OJat$a;ueZi&dQD}H{pM6eBd^e*2&*%Q}iTupDY?UPUrPjApGXv6Gq6b~mTYFb_f@w+WEO)WqQSa2&~}&H(LBHxS@7SC|80rf zV2S)!?s>l=)gUs*5|Lef{G{PWJ1e$6k%I1g_Cdbrzf2j;SzNTWu7}7xOGIN)9F%g1 z7>LZZM2=zV{;@=Ev_#7M@uN?q5kwYPBC>0MpE8yRwmy+q9!%yt&gMEqHbG>OB_an6 zad3=7Bp>WTi>=1^zm`bC63Lku{0ixb5Ls-A$gVMd$_GSZ2IHVfaCWidY@S18D@0pPC^M87m^1e;KyKQ58!BGq4rkAQa6%`9B^8@ZkrrSFby)0i7 zsg;OyvY#7_Hs{k1*7Q?MXIfLe&Y5c3f@dOF(|wrEvZjlUG|j_wnl(L$=^Se+k40W9 zpUjUrm=3~}Z!=F1ZVP*L;kIzzpv2HF_8qT+#u0P2b@wao7Vx`hjMeo3#TgrH$Npw) zQIb{Sf-d&+hdB)+<{WF1R+*L=l=v5TOz`uljF>5-}-)dXntJk;scV@G>GaH7TZY^Y&%M3QrgBNsffw6yQ zmRF0Derh~M`#&Vu* z0k-hYOdp6D;eh))hv6wy;6ev{F5p%^IJh&*tI0HvR6T6{8=`{a_-KRU>rq?1zKjpyhUhfL zG>yW?!M6urcxUE8q?ZMMfx+FD$nBQM_A7#Syw*eHbW22bOL26fC4y~miwKe4j<0PF zk>wESYl(p86)zlg#DOk={g6DseJuEU0^Dke++m5dx-!yZ0CxMhGdsf)k=+>lbhbpW z_3zB|fvbLwuT7SS`ssP@%my5`pB$8fu{h{ziKwA+0Qa}xzZu`MX|8P#PP@ zymkkhyfZ7P#zz?(?3qqa%%&&WC9x9#4+Gr8f?Fss;I`94mJ+9 zy~UQz=H)z_@tujtRq0jhmLlm6466+^{4Y75Vsp+*wCj;kdw|rrYyeusYi-RlyR__n z7vCp;#m-~QL^xBzrW5YemBNjOO)uQ3E5Vv|iJN<5)Q_Et8m)fiD{zr%JeeFQ&8HTC z6UxhGPwItT8T832_6Ag%pYH}&pU=$IA5^h3QA}Fg9YrWcuZ z3o@PLbBm0jIdP~y%R{^j9*y9DM-}Dl|8CpQWqCXTxfc@SXHeufYj#o5B5|H+bo!;U z8(Ft{Jj@G=6Fa?(7A;~6f(g10j6cUr5Jlw?EqqJ?dxe($vy&4wdS)~jc0YD~S8@@l z+okgczRx6C9Y9JUdJay+Gx6MSJkt!UCg&R*)GlfQ^Bxo5k}+I*wfLt2V`_-Z#G0NN zRbv~Axhz%W@~~%GSo(T+%*#~eSYWLKic#sFn#?r_-B}dBD_^@K1NnXsLj4WY{a@4* z#RwEzSsE3u5pI1*v9B)bJSuw9dLIK-Y64E^ZiDMHzPvMnM zH{M#Hm2JGgF{?*Bx!3fZTSB-Uhdh%BhO>cpIvi{Dr%c%EtS|&j1-^>jCe@5-i}Jmu zB8PkNs)y6h^ns@WtO<~qrXe!4IM8?)SFt%Az~~Kf(tK36IZ3yToT?=LM@|#noR*MN zHJg*BxehrI%``xUmylB~Ic3>!kAr!eaO?r5h7Bj(2Ex@y)IBw$c8>>0t=V$k$Jo}n zE8S+&JT3n;vllltt()h8yo(!^<2dU^x(6mFCY_p5FR`U>MyXg4m{<7^c&0Xnnz}v^ z-oGrUX*GVXg`TgT^fg&R(p5)>k&jMpl4RXC+q$dfl7cTIrmiLR76jghRC)Au4yRYr zI`SXwvW4VoCw7pWPpongu~KI5SDvY#xVcwG!w&Tv9<@?<Na6>eHH`zU z?LrfKPs^y<8rCMdp9gzdrh8zv8>Z#Zr=b&3=^lY--NeLRM-nYZRDdGMDu^Ocs*OpF^n;9{ybjKnC7q+av??&5!EgeQ8n`SMob0A{>CEvm>CrU zC;(920o+ssFk?dO^(tRQ$Nm<_etd|16#ES9E14`_{UN*0=+GK4Y#={jTnXjx7#F}5pubXnv;?KU0D@XQry(OtBzTNj zyWcavfP++~9;{P9gXmQ7Gdg~ZN_PVd(%d8(^v+xE;9q(}`Ze^aTDF zrs_-W&1p25Dx>fJL{A0$o}4u?=*Lshmu7S>6`6!gMy4QBkz|juzZ-8)#QX!E9?mSF zntg(4ixtjqFmHU83FG9P2f9)ry3*!voN0dL>>lPwnlPiGQ)( z%k=W@rm3T!b%s_0YT!cVSIV=H9GrwVV20WIg=hBK;QPVdOK{%e7e#*w5{FQ8zAoXbru89Ib=V7AmFe8okHe8Dp8J_+zQzFtnR=+v*YvCA?*KY} zj7oP0{n|e9!;$@3$z*@!nM0To+B=zCj^n714SWD{9&y!!VQsXut|KKf;eZem?^$sjx`Yo9JU0yK_CkP4FC;7Mx+oh5cm#T^k?w5de0-?4B^C5b#L8wflEc;dZ3k2@1r&Cbje~j1!PIv# z{^d0$*Lj$|>zGAdGx&sE7xTfq14hef138>QM{AOYAQQkOms7II(){f%(?s@p=6!IQ z|Gdgvj1M{HP?&EEZ<*?l~joDeu#1t&QKZU*3EbnFM2af;BH!pTW8Y343pCzT_0<;_QykbD^k$=4F(8i*9F zA}BtD5+)|%u@qT7p8&*Rrk*eL9mP|F>_NwmQR(ibc-_#cr7z+uE7Wc2l#Z@ya;fNN z;5Cz5NMZmpDFWvrvtl?2pUP@x?H8W;6rd_ni!5@fNDDwa(5ckYWs;e6OA%$3Qffc3 zG{3^t%@Qh{Z~bY3kHTN9CBWybLn+g6C>>*FH4ckPRlHI=KK0B_aLQ^MS)D~z3Vs1H zyTqhwm?b3jf+bPAD)T>@NUZ@qZ%LpuRUjeVazrFb&MM37{m?TnS%=lYj=^6Wa4(|M z)$uCGbr0>E3iJ*0U%Z(rg*4N#=6{x)0+fQB0<1u8I!c9lQlXc@sX|XdVh|NN9!w50 zCs`paktBgMf1Zvrxs>Qtn^7GCjwPeE*u7#&NLLLK(yc^9qT~wIG;68Q>(*a&u;cL8 z4%}<#VrnEQ%ivJTOOcz8lGWNzJo6?%Wwn#6&L*q&U`8X$l3DS6@uX^*o+R~#B~h1n z;~{Z^*8f8&MMBCNkU;5wz(gcU&Z@S_C95sgVGXboaCjovN31{Tj=`UFmm{}?CPn=t ztovNDdKjRxdYP=wA*&96#vv<8%&LybCaXs+iFyQ_2#JnhwptR>9R~^Nu0%wlSW=pQ zZ9ZxK+}AY`vUFPj2y48qC6q{poMM-6w9lJe~7x**nVt}XN z@Fa|SAnO=NXP(=kKY=R?zWiq}UBxB#F2?G@USlURCVQ?=W+q0u+-~A)DZW0!C3e7t z?4kqT=+cOv;p9R!Sa06U#{C!=4F&VQ(sl--Jkk*J50Wu@gw(BI?we?@vc=K(FqTzB4tK zj(InW@eww6@sIiBQs67v3CyfzthGJ>64sHF9*(5D&N>R4JP$`Vf}tvzV@Y7+++bO( z2Qz_}c5Y^@x?C~#Fk^kuUxcW-Pa+lgUR8XQ_{RHyk1++T%!v?~O@{r@GwkDL}#?H3=PC?34BF6gPDV`{@CAc{YZBTex$ome&i@L!;kn#lO^Sm#XcMj0B|O9 zC;DpemOh(}Upjo6<@ieWO)|sH=nJ?@y~DvYD8h8~L1FhXhMSEg4ya)f$Sg0qkBMRy zcgz~OX7CBSkMY1P1EUqDmj%vbYK~k*86mogzRsQ_Vs^P@nx!~XbbGY{GURb_4V#2Gm@C0( zndpZ2wt2zL@WslHG*Vvy^>{(^3djDAz&>UKxGV&a17MW{$a4Z*9%3KI{wl}*F30|g z5c@pruhFYZnxSlFb2l?IgzwKmuEtS1?InF(W(K@}&HSCq*vxN&)1}7hF(!L!%@{$4Vmu3_0!rE13HUbwFR}qO z)`l_JYvWNs-Alb~13pH;Uc8(iQh!3A7`yi3rOYzq&2*TL4Bk6+sB z$#g!0x*%w<7h945i;-;@X_tGSuUe3glWE^8`1smg*ZC)eJT5Yef`HMJz|JEeg)j?k zFzL>~UozHB#9HjfVi9VvKZ63q5e3uUyxRtQ0=51li9c(1?%D`_4#6(A!CHWQi(n(r z$I#V=ymW1;lyplGRVrng3M;iEMyW0doSB9IKg8JufJP#@=(We`Ubu1S^we}_$cIZAvfNVYX$Bd9F77r&iaz>M0`oN()v1* z9P23J@&DJrZlYz^7~sb^8x3d-GQm2Oz6lPcyA;`T6mmO0@yujPt~I!K@i!LCBakkXuWpQ!TkB0QcZ*9Jukw6!dlI5#3=n3+<6QA^lL#Ot(a0a@Y(e za&|fL#Zj_aLRQlPBGESB-h;>lFoUc!={n;}x+|@(l9F0NQUkx?3D9hXrYXQra5jTI8d>oOa5 zLjq@}E5P|Un*xXnzBw0tUDmnuz1cUUV=pp~K`v#J3hop>lb$-jn{hFz{uL1-Eb+ z^}h(vEMz1D>15EkGC%MKPX0C=Wv}kZ7$;Ltpj^63_6bD)bL6)eF6R5~e67G&oy(up zPR@N+obQi8^7+*i4p;PbrdBKTyjpqLufT^dI`~v>J9|v$xA%KsmgxD8ge~M#& zpJSgNVsAKnoF;qCzZh+B06RheQ2@Pc0G%2)VNccl4`aU+V(&rdbjN<7__m1FRqE2I5D$>#U!!6_xXGj8EZNR^0(D#*t3* z3z4&|GwJ)`OuFll7fT=&h18jrln3S#d@aJho%JK#VEjmTqxI92`F9T*{yNX$-mNVl zb-a$&bJ!Ly#_kp*`9|)mka!Jw9eD#$=WA* z#-jMuSP#Z7!N+aLQjFe0{zD)UGf(4KkCh*HS1121$29+C%LZoeN1o|~lWd&y#mONW zMAed$s{y|gI1t`>?WBt7Xi4RQsZf-;nNPM|7*nzu%U~=nk2mAsL7aVnq){_v`wU}u zE$8e$B{=iN$+ol2=oG*n87;x?c4Q5>_Yj*=A)z!sk%ne1iFC7x^aFdCM3!3Y^)Y}S zBASolnN&AjZNO6jokBqR$=re5fc+;(c)&&`Lco)3z%#&pLqKxzu?cXWI`LwS8DPuI z*T*lq+km|Q^&;R3#_mLJ1@Kvk0UMhgAA06w8?ZmvZwaU)m$0{kD{ul9#j9z0==vX} znx_HkO~6Y5tVQksu(!m3P0SJkX4`-Rz@+NdB_%UUn9;+2`<+x;|7-GnPBw$I9Fn?cKko&PejD(YHZuai>OmiD= zpx&(ek$^fl2)q&CH=zNuO*25+5p}hk$BwuL`+Jb&j`%&eACMoBpAdIPjIw-Ng4gs7 zgYbD&=321pHUE!e{5z65e|fuk$dv_61e6KrIzaa$PhkHCl3LC^;VGDbW?U9u69$_=@AJ2KG7t z$^U_6!4>^hzOSj*4(gG;2AXlVQ=f12GX5eBBF8b#kK$+US9oxj zj#F~>03!U80F;dERGB!hQ`f?t)$GT~yd)T_i!O$J=0#+oKPs0_OU<}q)?rZ7@*HEm zr-y9?4MMhuDj~XtrarzJK~}!@T@+l&KO+vO_?T%7gC!T>RBQ$ z0X`#?NcBQo(LU8EM3-eEmWX`qhsf=k|IY&^K9OdSXc0mr7b3=jUj=*su&&659c)W* ztwONbVAE|9Z-O1{CP947Q@-}tWcEc_)bu-^6PVwWQLZa#mV_J~gv6bUeT(RZNR>Pu zX&XXf$J?GM4YncEFcj%B}v)nA}cL5A_S%V2kHwhtK+b5k| zrn=0OwWQ_eFr?Rl{}m~m{}Tz`0ZCS%aQ@%$Oc~319N=BBz6+p8cf>w*sp&}sg>(f=`h2i&L3%y7 zN04sV7pt`9KTfI3Th<=~7#YgCnZr6sx_+@8P#3$3mbCnQ59zz{uMCs@(E|b}JEZ*& zuSYx+&xUmd;N&j#^|Ih9doTE7(I+>l=4?_P4zx*ex$rlsN{sWP5;ONYo74~JwMk8? z!mTEolzeSPgtwcN|6Yv0OX*1v-viL5^BL?%g;t??NENb+HJq-*KjB9kj{N*c6?DW% zbE{z0*7~ze zL`Q*6gNt3%{HuN`{P`EoL3^|3rj+F_ltz)U)gJpPV6zaZ1JRIlVwkwX@@8r>lu@0pl4iV|{ktC7%d_TzgOE#?sk;B$s9j13F{?zCq z`JK*x4XC09mYp3apJiYmPC|~-ej{;gHs4xYHE@f-sTyjs;YY#s0{1pJHTyCW*h;g< zl81}40484yt%4(!ILchUWdWA~*akoi^a3(8#6C{t-nI6tuzwtT&HGEp`62ev_c;H( z13*hh75IjwqwGgw>@J=BS0mV>vfJp8{MZt@gQ^^Y z&~xCQM?TWcEq*dJ&~rp^1ZN$FPP|fO#;B^N^E+&c?X81GkYKc3+|ILgE|35_dWG-hV~Rfo zpo%NWxwhgv(5d3{pg`IemYKR`~pw~<^thLAHk_xO(!GO>Dj~SR*Yh^`{b2NRv@ow*%#$(ZHo4hp zf#ki-bMtHvmA};FpmgstHZdg#Kl+*EzsrO4z%T`E)rWg&C$-=h42t#qn-TLr?9@)O z+q68mT9^pNFI$$^67)$*q)qKjWCTu9S8*NzU&UTiCPzc;l)ZvZeWR%<4RMutH!`^d z;yLd|I-1ulNF^-;NTvA@nOXwKqe8DGDqoV(u=JZiO`>i-FFL%zxAn*$kZH@rs9h)U z!z^SD-_PdjL)a*H**rk*`lHmhpjRr=_q3@zf==D{0Ia@U7L?*GetsCeQk4ED;z+kg zWt;&zto=M{^(Y{%hs}V}=m3G{&o4{1+EGP@_x+pGDdr-HuE3!u{Y?Bxw+~7BTgvwrrTEKxGo8=SCMqw<$cn88&v52t*d>3R zPv75b^(w#^TOju2I74RQ?d7_tP`^{Cd$4P23$<)`P^3>7TZq%eh>COxU-dFiW%SzD zk4Fk9(*1f7KH0df=`&V_nS-Gkmn!*vb{_$fjl0F^vyJGJeLKVU*#?L76AOcD*(;Dv zUdz<*7nkADeb_bgNz;fQ5m<~rLzWOYj)--6etwl7^~>j__&$#0!Fm&5wZ^MdW+Sbk z+4~Y%1TLgC{A)o0J@+6uD$rIHeP&7!uer*iQY}SPra=TID{Y1Gft+;8EPkRJ71DI( z>vv^KZ zad>G)wHR8JCZ8{UbnU509j`>2)z+(`t)3a!R4it5BrXk{GI8xl&Rpj({3u`;^EdHi z!1NFxl_^FnC`U_^6|XU2X~zlXTAXGxCnETDpP&@`!K?td5)l!uPVrB_qTGMnr83xr zTbLWZ;gfht@L>mhDZtB$z{M(Zy%X>|2Xi_0SA<|xjf8{w(ZO7)Cb%jDq)OZfNL`~B z40q5qpToR0wk0%;|NEZ7RJJrXgRNwE>j~^-%lLo|*qhL)LB2$ndqz;1Sn>I8S*+A@ zmd0D*Dxqt~gD%XiHX{WX+BXP5h0Q7mI%O}coZeEu#lZ{a;F{V^ z;Yzl_FTor@mVrCkHt4aTEExh=bE59;GO@5*ow*E_lFnD!43hKfPD!vLTA8f*H;!Mp zZM=ym+Fiv^ai3ZiC;Qh+J{Zi1USi;+=}Zv@lC8_BXKq zfz@RFg4|vlTa4NYFwJzG(=PcjJFJRv7|{-UE8^_1oG-}sg=!j#M8j63EcRg`~gW`9I-X?2~fe;1;_R{r{2qP zs6{98&$H-6ybX4m&AX74o!ZH~mANb+6a5zK6?(nt8^Bkh)C})O`FgPF6s4}uAw!qz4> z+X21rfVKb%pTpK>o`dv<-3RFuBH2{?sK%>*CFnD&tuIl0HBRl zZxd{1qt&wf6IqXKNgHjH*Ei;a(?<3uIDI%%z4b5hFgRx;vrDpU(EW=NCtbx^cOh8y zneIVrO%67|JaE&I75LcB*W>~0ZT7)QoO@T7X{skdnKVzX+L+w`d1kTY@DCg!aL~;C zhddI>LAciD;)9%Z6+DyRbqTH=bZyDOo#$>GZ_l{$!mrUSIOE7EOrwhaJ5BE9$HV&kau?s5+L{SABL#Sr zj1=H+CZ*i| zF-b{I0i&ZuOnc3(oGrA^{DV9J=xApPk9}%7VC^@0R#qBoT|HEWxp*Dyk^7C-dRsFM zjHW{sY&S8O4yqh;&Iv*3lhYBbg7zls{B2`L>>3-g3fh}NmPk58(jlTLDGQPJN+iiQQNvWQTM%sT0gvHmSiD ze;oLR;I%70oy2SYQ?^*g!HH%E4$iR-#P}^%hRF8Bfu`yMBy5+%BwxR_KLo5+#*bjH zUIvtbbJ1xk4x)>_*gI@#v_GQU={R$b1;O-mFj+(Ajy>cj{VrwKTh+Y@@_(1TgSh~#%Kj7BYnIWRU`L=+*$<&REqNW#{1;`{p;(E* z60mMF*0Orspf3IR$^( zq{^`DO0hSn8IM5@L854rqW(pzE8IfqBv-d1MOhD|Gyk`HW4!IZSi`Sx)pe3`7QXt|0jViCYFtZ zzJUYN38Cx$pXAw+k)>4&c@ORRRNuZC^R0E`;ZC}RK7MM35jU{elCjj6yliOUli z4J(U$ws`VVK-tq*j_IFxI+2l8{VdQePnu$O$+&);f6lj6j!}c;KdtXj@?I8BLZKy}jv0DqqulmkRi ze7_7`{SvvlG#(J5vPri)@iswX9dScZd^ZgT1+RN=VR#C!{B&WgCFv+cBgU%2ts0VE zisg&Q6>mbs8&~GV`SL5A8R%*{JJ==;wp4NKS%rMXt(yaC=735U2l?;x!6UyEn66+b zVCK1yTsI~0!8PIEXC z+_lW16044MN3Xg{--td}lK3m+DL<*YG)K2vt2H3k&76!gRaegLo?BGiF{RT@55{U! zU5(wtn1a_p%93maq!OY)ACb(m2k7GySNRVu4wC6^vg-Ys8?7;_LD>c!M`b&{?wd>J zC7m6ElZ@id3NuOLNi9(axFJa*QTeuvs(p#;6uP@k!s+Q%iMd8Ulm9$a~WR8WXq@)(gX)u=F;c0 z%%!WHXm>lyd}qWoXH+es(d$9gX0#5{484}Q^mkHh>FOqy+@6uu;uDy@b~&5W#KlEf z9)5phOCYQg)44j=3$}r}k;KqDGU~-X#eN4@*QVFD^j|B8c)aDb(=#owrvRCm*f(T@ z)7u~Gkq>B`){Ipl{7HPux>^KSvNpxmk?oZzGvFl2(`d`O@wG-&7C|VSH25B zr`Z$Vm96jk*fqg-6u<3}x+VC|#djtc&O6>DF!h*cW!ng;1*U?{b`xLC(>&nW+K$7v zF1GOQXimhqPKt5CQ+(#$lCFU9en>aOj||ZC;a=Tx*Q_*xyUb5W zynSaz)w&yP`6AG*X1#t)e|&`3ris>h8MRw~#$1205Jaot&NG`&GE0DDS;$vFJ^|7^ z(RwY528eTF>tS-A_Dl^-)s>SWQD%8ih{h19i5|2keZ?7kCL3K7q(1r7#9mSX(Vnsy zi9gAIqiw_f1h9axt*}@A+ZlhFW&YP)85L7VoNSgn<(b+}uu3Vxa?_3smPfE!4(8Yt z%;*qIKA5@(?IWW9gW(r2Y?ElcE~ECa;)U@{E}ZH({xehjj|=f%fdBfo0E=+H6ZdTs zQ*j?FpgbF9@gm0dyPT=&X?hZ-9uP|G*ZJakL5*8dx5m^h`4J1e1;3xVEn*^${Uwfl z(-8YO_C~jR$pCQ&&@2Ry2O!-Eu+*_{5n`W@eH!*^pz1YvL2McANz7(QY4oVP>Uw(Y ztZ9L=F9Qv6G>rnPb5!`oNSdb|{}j`cjw}VR4x80KupUX_T|3~|ofM+$VakF#j)1~F zFg1wR3qZRV0r~3(YsA+m>$;3(T^owO!otsvgv)Sg?hMX5&DWPNztlskDmJ8)=U%e+vtpvpr&t#a8W+jqM*4T6;ok zq!M~mp7ir5v~+`!CO9bS4qB(EJx(`E9{0=-0Bg}6f+Qaj81rU)x;^gyk zj``juB3}=Zh)A4)bW0{e{NUG-pWu`9Hj}0p|4AloFgzal(Pkq30y2^AY@~UyOx#5J zm|PP1#RfkX+!lfhcP7$38JyU$M*QOu{AU~dM2x>9cv}qHA-|zdry9}^m{W8lEX9@m zSH^TC)YzGf$$k!U5&@dywwL=YU!W=(RtrBjbToNsbB-Wyo+ozwQ#E zJ1s=l+w6~7rV26;rV210ISYmMZ(FC*55cE&7bAm?lEmKF!=8D|X7L$W zOe6~dFGM<(n8n#9LKg4ZfF~1hj19Qh1}r&=A*LruyknDybq4$sS!nA$33;CZb@!u? z85-K%eM36AUx=>1dCwA5jG+*e|4Wb&MHPwpz`ZnuzkKNKo>XKvKrr5ykeEzG1k6QF zEwLi!n7xmAW|s}<0Ul=qYKK+8@QTE=UWb|pCHWAry0Z)5pQ(s05GNxaSf|pThg0d| z$OT1N9FatXBtEuD93qLSBq7{rU5tO1BJ&jXX*gxes~fGn>T^WHFsA1RRU>ce5y##4xj# zBtEqPPa)tW8*l|U#dDH~u?M>qIN#)w#SWWAH^9G=g?6`7k$m)7H0Mi@84=o?XPW11 zU|~lPSh$tQgwRB(e}tx6vc)qy0fO;uVQc8Qq|H@S~_<^>z@RKQbgz)NjF zC)1QfN0{7Ao_SvTe-ZP5sH6F#T zU<V_iI1FRt<>Q*G?a)1pPWL(0$@3vj=(>9yj?BUje+%z(qTBgZiNH4I>CW+CjCI#I z9+{nD9A}!hVyxL1ifuVa=$@lL@)&xxj`TScR=UfP9{5OU9lx+qZpOA*%=us@%!au@ zS6EQkY!o~gF;4)Z;9i1%cFqlEVlo(2EBQ+^D!e}nnYl1 zPDOe_Ane2*e|jB{cSE|{AsT|S_-4yalRDD1>u045KN z7KCn$$JuubiueENA^qRS*A=i}orQ9SS%))qu$(xSjuTgZ=8fpNB-jxbFYJ` zk%9@Ejsh_EIG8L46H_2(KK!z1It-<~-vJ%xfP@K~jwqP>z>tf7OSqMn!*n+|3ppK3 z>g#g_kMc{L8y&zl2hb-3VAz>AIQEY__I*R_qu4*-*gxsm_w((U{|Y3_6a=@805$>8 zT=imJ|5_H@{+&k#529xm%#?oWoT6)326t$i8Pgq_#-=eQ`#H!mg6N`oHD9$qd}&O) z-g3^u*FPUTZa3!f{DiHZS!12|$H8L9c_z+RTjv_P#5$jAo$ID*wROH9=Oa&}hqliy zd|hHKzOMtXgiZthHwg^`dp>e4`j}oh)J;>XX<@g1XW3=qu989K21`f|XFy2K=F8!c z*#@hNBzJIsbG;=p0$c^Ez_lS}MgUAOpwen!GCN%IDt;eV@f#V_9n91HwWx2$L}Wm-gGeiy1K;oG&tKj*6~1&b@518 z#|Lm+Zw)#N({>I2z9J!;YB*~4Uup-$IS!scT0A3~_7~Mn(S5if?A#%AT z(hpn&BD&WZhg^nUOFyO?p?0D3JS=2Rhnp)c5jk845joSH*WE`+UMC z{OcqxP|BM3LHQ3w%+;2_8GzCtpqr>k$jn2*9w7b2WF(#L%i{|hxd=UH1yB0L69V1U==t=c|2Pz`3uYmtbfMEl z=t(4JW5mn>7po3Xhr#7s8g&l53|WkE>a{xO(TJH1AZfqSvA-n5K92o7$Nngcz3RDDdg* zWU~pnh@d(cld-{o)jab7UqbWf5}H6}0K6aBj_}w2wa!L-&F961b1cc3V9G#J2N4AvgW-DQ z$az1{46!69fw={eIy2m+>;G5)_aep0Qpj?&Spv&*Ey>FO*MOuh**7CI!EHeD5&rs5 zk7&f#f(Lkfz9o4Pn6i-Mu%UovJcjon0ZHnw^@%>8`xrCKvYZU)HdyL!z(ypT zhyWMICX)=Tm>d}93WjJ@{k1p9z#;g83i=L zg5rRdg3+~LEwTvv$3p@cFc;e7ba|dha=OyqgCvuCSRa^p1n^emQN+zIhwMh$psNX5 zi=eug+=X0>?Gs2U(E?62J;5}fBBy6DWoNTJV}Bp=0QR-GP}D~B+cU>7?T3gT=c~E! z+E(3n@>S2r@^GL#AYC*%4=(Ed=NQ*@0Y84D>1S=BNz#wq zgr84Sd`0nfI`%qiK+aTI&b0>H*tPuAn*kcue=YY!UIYFZuwDaRgy9^%@5?wpMg@Er zp#A_eeb6(t-nIObKbQR+<$6T;)K^8LEXgylXW992TCnmkjhTKZRqp*{F>ZQtUAoKU z0=rgS-GC6?NHYLmjUXgnKl56kuBy8borD|1elNs6N0J-V2I8BD$A7$euV?yOA_oCa z4ka?M5LmQlTo|GoY|gSoK;%pd{uc1>fOUoY z+yUu&>}=(ntY1u!?1b#5`%_=5D%VoOnb zeg{ihe*T8EuAGOF2&zxvkUk-RbtaX7A>mLB5E~h+5ce14a#C~BY z=}{r1&ns4GFUB_6(Xy7Ke_^dl>9F8A5u;U7qO* z)^0>Gles*&;@tuGBnFZjQ3p1nzi^<9XaO;_5p`!gxe+}APJe)zlhJD#r!V~ox3q1RTxpM;dE8Z#!({zr)9CvQw3e`e@Bv2_b;UX zLrO!k07)+MwQD^S1>2DL-YQCXfVs;9sJWAMYQuR9Gz7qqzkPOA<6_#tW}?e-x533P zR=NZeyYOPAt7Gc*CDvFhAh84opF={|bXJl{CsJwy6|fu{S^G8EAH-glW7(GqA+2nh zVy_pP)?@$vwBi>yE=sXtjiqaT697693GgJVL07a#M2LK~hnFVMF-S6#N69dsK+VCa zsoH|OaanL7x*J?G>reVcygDKsVSInz@(_9di7y4zNe=PrzdRDBR4uJTVw%d!gOkg> z09&9>Hfcwiv>YHc=^X6Tq^%iGHtAMy`UA|gLa!#hjDMb*%b^$LX6?Una733v;CKKG z`Sab=&UYJhoLt)KbW>qeaI*P;pydc~2-%6K3oBa^vU}7ak!gKO|1mxl-&94Z<#0bG z`XFVF;*>U7>gTY{OZhp<&sD%{%7lN4DbudG6-iE6C9*gcN&3;0<$$Yxh|gLp@MeIY zqy+X-0;&0u4n)K-Ud;v;{uu(J(yKOwfinqOB_(hiKUv_^D{b_FsRYfU&4SfpYFLMD zaJYbGk?vvZ@LMKzAE19Iw}{}^?-dVlO^bU3Ts}m!B6eDwnpX2zErm!9M2^$>Urbs7 zya^&)c@3|mnA+B%^z$Gh9q&8XuJu0*VKS`4B#{EruVww6%>3SrKXp21Qd1Xw1Exs& zb)1T&+lIs{fIjkA95j+n!*wjM6Tn*msv1uqHI9PQ(_-s`Bk|08NZ`4-MW^5?99-)$ zrj@{0%skJa*7uW0d7RcpR9+1Hx6|_}Q^W!`;_OB2)m%>@l|$^K*c)rV1^bt=S5G{H zR6oL=`OoL>ART~~mC93^Wu?;8#MoU{Iqc7A*wF@2*==){RcZ8?XZm`(R7;`L)E>hJS9%BjZ zpej|-tKzZk{8NE}#xT;r&+KT@y@1rg`8XzVJd&J}7s)7(5|;;~`P~U7eO}T0UdH@Z zWK8p`u~Ll5U%vI%5TD6q%s#;TZ#c^UfId@!0M!Yu=6M$R$p)AHSAt9TKcrp> z!SnF-qYeHVm}3Yo%<~r0Q0FhF9IOeR3U7mGI*yqflK2g(=Ly(`rs5jJ9 zf8+-J6ve(+19NZo8+Pgq**#8Tz`~!Q2GUaHR zowDE2HMUbz4&o~D8%Prj-9AbZH}^$4n!hYaB`ps~rFjc!RsyJi3;3UjwXgBnrGJ2O zC2mU}n(oYodbR`_S4{l=Gvos?GT-yr(X%W1wyqI=@UPFXQZMi73{ss2QWV#XL zWo#Q`t7LTkRWhGrlT7Bx<^J3S*?h?W<9;?%Z8rNHhh1@~K5c3p>dsyczj7RIcdE1B z@fWxLzDB3&>>{CCm(e9UJG_bq}LjCzBa@Zyz{gosk-TO!~iLdd~s(2DS(i(@~S%)71yd8(?-M5i% ztwZTo;ZVAd9ES&S*t)nw4!sfcHaK#~)Gxp#pX;jc-?DmTK9+{a)Y|>VX87{LHU5KL zO__A_E{08QiPjDGpV6nMQ=1bA))7&4E+IlEzOM`keE1J`jg!nfV3SSQg`T0y&GFc& z37KNQXWqxgZ$kCoQ%=*o#{jl|6JBAPW;Z%zBi2tY3@#I|fV&E!yBKR`iGE5jk=%uJ z!AWPN*fVY*_aXM0d%Z65DpMcR{Ctd@h;hi=`@c*UXK8%^P??Ivt|>u~-Qf0>3aZi- zyeg9XeY0<=>a&~mDb^<4sfKzWpQ4+r=64Tn`NClHG5$EV#xl#Y`MuRII0}AeLI3TY zd0bAv(G*zgxk;9^@o4UP;D$(G%a|Q$FDR>*x=~i0^T*;~Z=cK>mYRH2;7L z8Af3EnFE^WfKCRKd=iU`QRbgIm?;j%1Cx9bi;Gbuc7Ta#PW1}ghs@~*Wc49(pyWAq z9-=bN-9bg{K!zVPP_9R8Q4OmK)dk=~gE)^hNNX0X4>RHiJx@tt0~x`2^{S z2dB)1&NXd29n9qp<`gi==iIniYdT+WFjw`Z{>ean0EL%#oO#{>UE>7m3nsk0G=12*9Hn5J^_Np8}TY z|G+i}n?)7Nupg*Vzef6jEqWM^UwU1_^T~g#c^Q!AQKdh9Vqr%Yn``ie^BAn<@g}S8 zmxJn;4qG+j&8wD3Eqs=Rh^F!oGPpPq_pxB(%qxJjnHe|G_{MRnsB_bk^IG}6?Gz3ui zvKnt5b})?`%=uu#SJim)s9w8BvTN#qh64&;R^!c90;$Y;QT;Q@ypL?YM7}v%nG35t z!8`_DRW46Ow)dv~#47A;{IKUwI6{{eqT@pYW}D5r3eGmLFNnxNWCTvbH?0Zg2?z5) z%mIx86kgs5=5YtK#legQ6JFj4X1jxV#KDXKldNvcoqJ{UBp@w8EvbS@!E!oGMp}aB zBJL9OSVm7{uU$^BVSmgnr;AeWJ-KclW>wFAOx#;f-Yl$g9(TMkYfhZ1*Gnc=HS(8p^-zYtIo{+vOkt zEfAgS+^6>az;*r)4*u`o*Ti`+>Le4n!!v7b@F>B{5nO%n3o@m|;1f(<%mzOJPzQo* zi#(^~;Lh*mPd3>ET^Hburt5Q7gQn^mld=F?h_FC0Wwo@8onAP#>{#Lw8jC=0u;U)CYpO4%=Hdt zHkk0$FwxxOU=oEG*8dzp$>mUdHB2=31JY)rx0Sx6yn0LNJLI?jqr5?5PT|gf?7*@s z2Accuqbg@mnJw=n*4=R7d&Pc>e2`Tmzv(bTXbKw@3ZgIueN-#;qbyLg+TZd{; zSwbC1hyM(ioEDWhOH<4P4rqA*@-Y{eI7w5?CI@q=gIRpUBryrZ&rc(Z2OZEA4(O5+ zCuxe=3`VB_Z9&^P1!(*E37G`uXs3VzUgui_R=;}XHKZKnS+(ldzY$#+!?%oyX0 z|Im5erDMbV&oH;)Kyy|J2cPsi@~V)A4Zez9DblU3_=Ypx+-Vc2fv=ip1VsKK&}tk6 z)n)y;8?Ol5;ehHmpf!NP%R9p?aWM5AOae@Jb!YJFE=;}Fzt}Z!KsNyjFYgSq43O4; zb1)gKW%bIx$jxBF_l)UgDHv5$aju+L{J5hR0sXRrPg#mH&0dz`k@}0w4)iN5kybdZ z2oaU_Kjh95iOev|!DwI6+blgM`&z&3Z_pyE*o1Qoi-TpobU8Gug*tZWU(%e(%+@H5S2;57|rfsalP zrr`(T``c#_WOlJ~2fA4yx@qQ0$63RqvtquchTtY2p(Fkza|O;C(Nto#hqphKRm49y z2k+VEk>Oh8UgSRHeq;l(5!rasj)2C8zckyE_#+Ik@%At8l{4UM^0mvq{K7I#P}6z-AlxC$K+V z1F4!-yZBhYq4{MIuoc0M1-rk0fGY*|L2%^>eh;G0YF@27LA@|8!#lYdmr-eC=7Hex zhSEQGMxZN$z7$(k8|(-0J;BWu>+o2<&%2FX9bD319{z+Wi_AiwX$jqPa-cf~{e$`x z*)M<}!qIPh<%|AngZKBBPv0wPpY&N)6Q zSC@tp5tVK?5~1v+(9Omd@qJ%)2l=h!|3?1=KIuM>v6fUoaq2N9;^mRpRy;k1$lVO|Djx8iDf(sl>0|&FMIHq2)4CVu(#F-0Wc|Rq7IPv;^ z{u7Je;C075&f-ts<5Y64$$GcffcM7?*j;w_@&Sv0YU-B&UCY!fxeCZa^eV9Qm(2|F z#9tv-j$nT7t}e~d?WRVy!gI}HoTt{u1Na2Yc4&SboK-uo-XdJFq1Sh z&s^dF*E=Mh1oKP^j?8Rg^Gq%{c>9T!wbv<#nq9Jq`fV`JmqM~)Pqf!6Sz-VXh8elcpNd&2cXg7Es;jjLCg7^m3O8UH-#deZW zV6~H62lgv=65)6cL^G6o0i4!(mI*-50UjJP{VX%^6n z)}eNiI1a~yQ#;AuI{XP!1sR84JBjoUu#-rq_jFz;jq|cNdI?A6_^Pbf5#r@9raZ_>*WT!v2|(3> zCjf0ZKj^^qxY|PpPG+nM+;SLe&zM|hA^*eeD~NI{i)=(99*>W@!84NrD$)L6>OiHs zrEzS;BxW7Vs2uwn;|&(j69Q9hkU;?25=4OGkk?8GvV<<{)Fem^8>A|^R!7`t zRJrl?-r&W|bQ|y#z-R3Y(C;H4!t& z+Fp)rHnvKp1@gLY+px$uj`2X7NgIr7kV#DpUry9Ll#w;eCB6d=XItKysaEp?)8?4J zQv&a`*L!BLb^mV`?T!1ozWWPsANz-1I%yHFP0#G(JlS);$tKh}7FM266@k?kj4<`7 zZFWk1@#EYyE-;a6Ju?Knn*D0g2hEs=XTD5+D5MJ583^BpSOkQ8MMm2p6Mi=eQB6e3QD z3v7rxn6)1#^Pvr!eoInsos#~)K=k3yj}8ooIrDh2*}K{^BXFd}y%esqFv5@j`ww|6 z?h6^?(yX~KBN)>(WFxyE_6ef4u7QY@e#K;@V?WZd|De#G%SsXu1<=z0GNKgzNHIK(~&`%|&61h>wVus%&zS8syVLr!4;BAy1WoEGR#M*kj;k`JI$*EUAd zJl!%aHhY<>)41|0%zY5aQdC2?t2q%4s*?SHD7Xvu9{^*7*jupSqEUf@( ztsW2Mc1Jdg)1Wc4gC6N)9c;_uC;`5W#h(DS!%^{Zvljdr7Qa&;;Qk8-ZLNb&fV&*k z!4k6s2mNqB(tab3=BS`1?13bIHR)55`3xyQ_9Dr~+(bp=Dlh@6F9JtO@!lwZ0&$ZmXumw&0rUd1nnfYWKP6}Z^K z(56`!(lm>F9c$NbLW-2nZ&I0dHWN8}j6_7DC-O~dB3JWEAvTeI*ft;$wxO75jPVIJ z6Ai2(6Y2UQAC;KNG7}+__BMEXaE}vQxL(M&DZvX?@yi`H__^3-5nLNoQ+ArZcFjrrL>SzP9s zj^NelZ_ud!(r4|#s?obqzLV5vTK~z;^(nu_9tO6a@hh1uS4m0099J#exk(5o|vjb}aArxpy|1Z0_=Z{dltE%y~|mGiT=B z-M#q)V-dQ1Cu{5!4E2<;5L+VyV^d8}jGbtWltK?jEX@_eR1V_gyEr~!3c#G=VA5TTzuEa4 zaS>uCJ7VQrF;s``UWVN(3yhZ1k9d6x1*mz}_4Q|95=$vj`Dy<2Ofx+xz$iG)?|yZ% za^$=-AWiV8J_!={&;?vkNTYT4`U&Ys!oczZt z|Etl*S6@aY|1~Akt511lc0HcJlT@v(Hx-C_cIk+f$Vh!vuVz4KXEQ8 zy%=scbJ;gqA+A~a4WDmgmSpB!=m3s}&(a-c_e{^Ulz-r|ac{YG?>g&VQX&%@Y>Wil zX$q%!rVU^v@M6Hnkihl;J3y_iQt{`ZRJ`kIwYNIOUw}^W#z5(@ z!$grg-80o~6lnl+i9*2hp|49t;RbQHnM)8E*5EL3E3Lt?GKf7l2>*6NNe1pQyC-|* zXdA^4!1ahi-Sl{<27J}>PEtBLw3hQi>UL@fZ@Q@!I2iL}=$K{T@Vt~`e3x7r%K#TrZ<#cUHHiiS3dOEKDzDAdckK@s@0w~TKn&KpCE^L7@0 z(4d4}irsFS|62jqJxruoQ$3RoP(@c4;O#^zgO@?&!G(9k2Tg=X)2zYa;9m6&(owBF zKhPMd;(zTePSMRVkzCJ|2V9Mz@qjxJsV*B%gv!C!UMBvO8);t1EQeNd>$L)7dbo_n z@)(o+DCkR!v_PgI4wvIq*Emg~SOHf{M_7fo0J_sr_%aGBS%n&#X%${;6}CY^)GCay zIi59v>zb?Y>L$f;^i~9`049L#NB}x`p9EF5I^pxa5K6@y4}F`$%jk5z$znmbqU>&q zx!Gcp9PuObrbSFu>j2p@U2s5`4yQs@;H#0nQiu8fvTwC&@-<^34`xH4PJyJr*Z)+0YC66@E;` zqJbHtR7@R|e@rnA1$lgf1&}befw|XW;uaG&4RJ8PgHgdQA-XN&f`VOuL)Vul{@)m^ zg~HVgzQ>^EM<-LIvJF%jL7l$+smbLVEyydHn{nVcqS1B8>Chk6G4b!fG4XPtpHl?m z;n<_qN?`Om>ra4{t(@@&NZb;K&VVyF%~k;lR82BW2*i`z+Kf@V~@@+XH> z{uG~Q&#{RQSWh!WC?k6RN_h=csjK8Gq2rUPGW&MENMmJnE&3|5x(>Yx>XalKMRpIe znseQ&YD3C(vD6p(5`Il{wDB}hiHOC#0{WvQ=h~Z1uOj~y0>}sO ztpgb07&sInUx55Kj{GP`{_ha^BIN(~RVOf6mp>d-s-*%{M&jUA6S3i zYl`xBt#S=+v&wZ1C*`_<(90?>zVt$A&)Yq-9h@p_1h}RIaxNHMzwdyr`+d3u>NGlV ztQgCNK;OQ@T?6Xs1?-UKJ?n@R7T}1K>4b0^4kShCZ}^HXhv+`Aj_5#mJdUXG4}{)_ zuZ)U6iw*@#LC4IiY|bAssAjCe2N)DUyL}6csePTnYdK!iN$JYD+%TLt*590q*qlGK zil=~mv_vuQ?xvfMtzL~iX7y^*mtLJB&OCznrMMi5mtI4PGq?GQ^HIDLu;%%Cz}syG zt^=$Lq?(EdQ}HP{O~puX&B(wYFy}#^SbgHJMW1-rYnwV9g{e}SMM^)nz#9O*XMs0Z zTq;L)gZs=nG74OC962A%1<)7p)hcx@7V8{R6dX)KYU17y-u-5mbxuasZT;$hF{F;fO9>E`i>HueGnsnr@-f{6O%f2Dc&bGz+uIIwFOe zaYV{=9d%(?1U7&y0@z}4H-Y=a;&eGAT#5i<@f;%9Y$J#R>_h}Q0bUBd4PSjh{0CXT z;^}-b4N+M}EwB@}KbNxDY@#fEO)5N241t zpkvWwsB4IPoHN!dj{G=BzFUZV{_QdTc!C3%-~hUZ017a$#*x3-kv}m+z6kl%$ZJ2y zW$I3#wserxDfTt^4VZsV0}oNDVroC=jH0mWF5;yBsz6|-g2}g-xW$yMZe+l`VI7zZ zrZW!cbUFfh9ln3vq=83JCZ;Ymx}M0=*R#jZW2_O%HMW2; zog1b?jnLBvx)v&j{6A1rs0e*eS$)%wA^)GVJ`)OCas*qzU=)SZQMijSo$YRieqp)v zMA>x^|17@yoyMNT1oBp!XBL9Z0(%eG%!VvEi#1?QO~Uynt(ci%p0?gB1XRz7E>3jM zFsA4Dcl-lP0Z}fpfy@9~L?Al8-2s&Y(;HGC zx^I&d$-`#%6wfTNk=zTo4w2~OBX9*keIXa;N5bvz#n#~MVD?~8x3}+vDk9$x3dvlI z)IDN)V(?jO@IJuDU{EI-}he=Y<-)&)jPb&II%o26d3Q7pjT;`6&%PZgMa<3#@AAQLs-_JK8z#f*OMx0%>K$ z+@+DtTD{+T`2e8$c&P)34Aw^Y0;ue~&oZ;ElXrpn1}Ak^xF51^!xk66reV4`5+$Ug|R zMt&reoXuDPpvNs}KA^>bvUW%%OiO2BDyQUT?q|`u7cd) z48c7a*o=8AFxnWS+C(0L+9P~5l8Oh!g=`LbnDZ~}S)%w@>y8B4@-A^-;+Wd$gkYL#j1 z7sk}{rEDgO?oCowfU-Q~b;!Uu(>0H`F_4`BO@~jt`ZspHQLKNktKi>)oc4p*Gz55! z3ivue{YO7Hx5KYRG^?23?cC0kvPU2(Xk#7FK~tG2;CAK=e`5Da0GkO&P}-Qv^6oM-ts_!)1deD|+~znk8z<(3I5PJZ&)j3N)xlN- zd#Bd)P6zxDz=uP?Ie_oBQPcu_f2k-E4nG>=@I8J}CyHDx{)VrEOiEAeNW9Rl_le`g zV>s}5h!YWt{y_TXXIIV4WkK^=Q9GDm8^aRh`WSw4$)4$=g_7^!lU-y5Kmy?IrH(IY7VC<2y zUgPxRAx_UKmFl86f0DvFEj`t6T07}J$LXil{}+aMJ$Jlk##yiP@%l%+R`1V;79pRU z>Vir9c?#=v7RH_kb^3`iP80v^Qe7^aE;r%p^jTVJQj~$$+FHMJyk3F>OGBK_!RcFY zBGHJt(NDDFJpkPdKhcPK(uke~Pa}$%i7YpDqT3MQ#cxD!IF0C5_-aJUX++y-L{c^v zl7i$$@wk zIk4sFRO@(+^Lz3JGt4@%0Q}E5p)Gwrv#y&cQ|cI?(S)*tE-Sl)RX*L-lJhdi&Guk>KxkAT@0amH_zgnIB;qhz6Rx?uLy{u~M=rLGoC|Itj;J1< zf-bW9#Ge(z6EUB)3gt*&9CYv>3$ z75jCh)9_GmF#!hyTmx_spy!}XhXLwgtCs*wr>Nc{f*n*l5dRc>BL~;Z7}HA7*!zrW zc`t`vMd#Z#s7&NlC)ty@|8uDYYy<2jl${2{*tn40f^4v=nE$nSqQ9tfDuRqfCAijER=wbBt%|=r9xT<<;i8NrwnTsJ@0DYKbgj(>Y!(ikD zK&lNDRrO&(LA{5(yD#XEA$39yRZ+>UcWR@Hb#f<0KVikFdA$m~g^pPAnk>4NzYqXG zb0$SI?3{IjmzbKWc&-$$hjylLJRdz>Ek+rw21c3L0DV$|sasqy8;CegicSQ?KQnHf z^ZZHanR7b!D_1Vo9Uj|qM~c$)(YB0|_fawy#!c{w`1eW3z>Z4hO(*&u@D+^sH^qYj zJO!Q#@F0$zjI6Tp8gw#z1u6a^?1=Z44k@3Pcr;#+dca1M zWRszm;1X#)iaJnsK7{rlt0t6K)`_C`$Wj|H&9SA+{n*~~>2?4%L3;ruKP`*zkm5Uc zL|66qpS)UdOT@IcQ5?eAB}AcOeG_VDgAjiqL5Q~nN(3>0Aa<1wBE}y)ikP-m;XeSE zqEMB*5$a$SivJ7>#oO*E9Eid%N-H!)6ZlR(ID*g6H`@|VUe&*=TfQP6J`n*OQY+dB zD}T|#fAVU@BhpO}g{3owQ3Fc_9i6T6m znxV?gZsb&i#NpRW4P;n_Q+OnprA^DeX>;gGox9tR2V7=VgYPJGY1 zAu{qd^dLB8 z&7~tPsK$sI&I3&I4H+?vIgha^%S%Ify+En2^Vt%e#kh22TwV6bt8-1}<`q4*?&wi! z5PJo@DQD`UG>eoShoe{Z38H?F$bTT>Y0QfhR>tvy16)(A+*Hc(1`c+VgZ&Y#{{R^s zQUDiw$UAzbz5}|}0sRCh@dOzcqsSXMnCl(P&tMV{ka01}L_;wClUH@S?I-4R8&SOj zJpw4joH|eAoNFQ|0GiQ;AMrDTOt{mOM)s5)mzQpNa)!D{d@wMdwG^cJc$9s2R^Z4E zoH>XyPMV7|#pQ=dcQ8LVm|wsop3mb(t?5j2Fh8F~{uQwd{RJpIy+q-Fesv5T0u!Fz zxuzVL=G2YusvTshsd0S(9RO3j($xQT*zmuq&uhW>h^YWj%|~l=3(ZH%^L^-l01wxE z;w;l6z_tS09c;Bxe5eWB7tr5e!yXDm{Q0I5AmlTY`l%y}E4tKNyYm>V<#{G^?eRU< z4+>i~^GrqS$Vuot3P+UT-B7s-r5tgeygJuZ2Gr6 zk3?RXQi^wwDW&%_D1z+aG8JWys0!A9^6Gyyuqvf@$-k0~+XnjsO(C ztmc`64(5CZQw2=;s+wnh{@OlyRo-3XfT{utUsm(XuNYFAb&LIX(yY7dpF&R_F3rd@ zYX#h4Fu#BYn$eCQ=jh4HZe>e|&qS2rrG@a);r(u-J^^LT`v#5_;Y>A~Ug<8K-TCG> z2h%F%fNG{p?|k#818VDFYNbr?eDjBc>EK{$JK2r7-YKF(fV2d)r1p&pmeU?0(h{r$ z&|QK>G^@XmZ;rffXdj}>X{GFkjzqS&%kjJ0d>YWRJI({0U|Mf z?sNs!zQANIJJ@~s(I=X}QL66ep)&*LRq5YA4R9u``(b~c`412U4#(*b-& z#OGtxee!A*_W746y0N=2aGt#Sl$krmGyhuSr((Q1##IO3LJdn|MBRPBWp;JmtEJ*Z66K}V3y1)Tp;plr40$8~3f zt%e05tD!895BHco=*lAfozazkX5h#HoN0s;;am8EnEBEH9pQkQ0Sa$63(P(TQ^~FHFc+H4#Vfim-O=5A zjUHvW13y0R?PMAIb}kl{Npr-M<)y*<%2Br0Rc3XiTU_at6HHlY-a`J;xJ^qJJR8ix zQPF(`wM#i?3(YqUW~hT{pK{6;n*TYN>=I1ObVxa83(a>9=<*Vf>6miL7MdTxw6G27 zQ_cixMqfkq!IV7{kpINBTbyq8J>XV=dy|bCtYKB}&(N`e!yCu~^RC653Fc@p%HBcf zxRjWMW;>Xcaxw2Q~{tsG)14?aoD6G*0D`Bz8TrjBn z(__1v_tArjjE?*`u`lO;x1R@HX>l1|gm{wW(%^mMDC^nRSysg>p)@XVW{JtnKf3$8 zlrKvzC{dT$ZBJTAKf?$$`JtI&v;LYvG8p`YSXbr6WsB>`g!vhyNbV z1vCbAyRHD<{XZMaGLwbT^%g%IqZfl$=j-Sm zs>7HZ=YED6j`JfSMP40;``2U>&(97bw+_7IVCy>At2FfvxY%oX_@V=>Y&NbwbO=Juc}Yv7k2;?TO~S56|IH<=S=b-8&(ms=~`x}(d>KsECVfG%g| z6*r4;zVDutGvhR$L{jd93Ji^9=7_tS&cGu`pO>Wy*}ko3BII!x_OPU zdL&qe9$`$z)1XnrI~uwHQj&SXWc=HEIeLw0=r5CU@kjyh(VEN=vpSDj(fOJR*X-#0 zs-x>MJ#00h>}r%;S6W$dBxxvl!vQ|wIB^Y_>yvOLiGRVo4ldIMR^IMKIO;xZ^#wB* zAuWK#M0g*wqv+D1o8~k)nOUkyV9(voc;l5N>t^`a&iTM7XYU1IZRs6?P>}xG8;4Iy zN-eOHz%D4?xK!_$4_3Y7MX+DfJA@%kY_7V43LA zS5pW7eQ)uTS9@OVnP<=gEaL+793Pty)Z+pa7m=ey4A#I?4R&Ho1}a0hVB}Uvkye71 zKoO5GPml7S!w<~i4`UXMT@PX zY!sb|qUusKXMk6gx17(YS}wNO8r_ZTv&izEw6_4+Cd{#%tODJJ;mXL#$)!;C@Q7Jz z^|nLyB=pv>4)XaOlUvq7=iIc?>>lZv=K_4~=yfRQhk}|`0Uz8oQ|DxzQT6DN#l8*q z%ra}Q8{!_~wX8Vbgfx8lEIA>Ly;WxSm3%A3;wOPW7ksA0-wF0E-`q(Fa}mtli@2)p zDZ~fS@RWwC&@A}$Wl#J+uM2q9;K%3ubdi0qsJVh~mso43BX%m*>d4xhe|lEU%we`1 z=Uz00I5*qT`j@Mf#OJUs^iE}B0+#XbC?+0oFo#^soS%BJA4|+bY>xG$kY&;sPjnkl zg)|E{W+&zC7z*j?!F1$)?&C*aGIMe9LC4(Rt~qoKFY97mla29*t?{=p-VEdQ{WN5q zN{_;plZKZ~E;b%Q9El7+>ElZl(PJ#4M;WVS6Mr6KN_snJD)whWN)I1O^uOO(!~x|gcyh-|Cl2`*pa^@MBYRGN#s>gL--`=8TzP$mb5;t z@dN|4iA)3YM+dxP;ZH)*Xfmt*rJ`xZYns2ZUonNOpj>d7h@T4X?(0j-Azr{WrHLa$ zRy@hP#?_|mwTAB3@w6NYrMM}t1uW?)2&+$BNzrzuPpMV3g<{Bt^{Jk;jLBeVpwW++ z;g-yBL*#FsQ~mU+nXO9C@M@EDrDvvE@h=f?gLu>zA5d0&jp>Pao)!NI@%D%}_Qmf* zJgI(OHCc#HM_eh+A=*AHTJ^gS!##7mHUAIhkHvgb zFj_kcSynB2SFN5D;_D`Y6EiHR5?Sm7sJTFMa(b8X`3+Neg=g+W9Q7Gz;@iEUv$(QU z1q3s@DunlvCSa=@lPw5S? z~Jv13(Vb!wxiWuJu0i+qa3J=d@hSbGtLwCd142i#x z84|B6GzDehGgN4Dhk53P{XxX%f_sCAg*zU4EQq)`;B2l4u8$!skEh^qcRbb>Z~~N< z!sEFZ7z;)YUyFgWa9EA}7-$T9IV}D&I4oW_Xj)2#*P2)k3U9I=pAWDAj|J=uJ)Y!o z!7#p^VU6F6>?s&m@77np#=}?5>?M#%wq`U%>JUMFyyVwMY2M4h?Y+{>)n**Z(3v44 z|Kn)Gl=~;?wlv*|jA6sm*!PUd_yy3@INJ)jXLwb6D%Q#ACHGR#OaQ0s=u`8v$c}K` z_3@h9o%AF?X?{;zXL=6fTNT!s$pHUxoau-&H(O^k_OEqjh;^ndPAs*~)MXc&KZWAm zHqLqK=X$<_c(G@0Ly6||7L<6TR69}w=vJ#-{2x&+-X+k&!$g{Osb^GEiu4$8vx!u= z9?+d3fyRPJ-!xf7I>{QG0+7Eb;RAy=BPiSI6#r*} z5bsiGQ4j>*L9i)Gq&~uY?H=lx%WV{m0L~!_0Z)czheQzsvC%{bVwg3U2QG~ugu4uq z!LmUx{{eWD*?o~`uC!6y2DledXfMcuu7I!V{*}th3$5-orHjtr>s}4vy>70u4$8(a zdIpgUWJ4>1l!Rs^hl~sdsCjJ$@L@6{oCiIaG9z!9-8r5aX$?*VSB{JbmjlUOtZYgW z8QE;+l93UBRh*LmpGrow-8F%(wo1kSjUdDu2CX_w5LpB<$_CK}+@quE{{r@c=A?{b ziz&?U%(d3wG=N7Cg>aWcGMGAuttN*cuCYPHz@A1B+BBO%*X`%_o|=gU{vZl5uYg`W zOcaqzJTuxxaV)sUnF`@fgC0&9#M@?Wj%NngAf|(>ND$3pfUbmOFm)8$Ocqh}w^76a zpH39o#alpw;KRzOz>hmZi}Q_;LM$)?!NnB46#hl@QZ^haI85|&hj`|EfGWf;;GQIU z;d(=lq>TO@la=F{^T5g83~)yhJ-C?v0aO`G9sPE*`$ErL2v~)f3%EDY>uk^py1*(G z|8Ig2Z!}bRm>_Zp;$lCDnDI^mIF~2{><2xWGKzQ2+-%QWWDVX9t_o2IcbzquI*9j7 z7C{WLK|BKXLV{?m`ELUaW`LIr4J^5_WXIc7F5L`etO04$*iyzMKMI<%~o-J6ffm) z(y~k3f51f?#g_usb@?JDv}r%44_qJU0WgWn!lmela;)o#ESHP^1?puZgKMO7kS%@& z$z6c%hVFsxwF>G0mV)@;i1Dnkr5J05F`enog7U3w;wC^mhp&<#t2%j%HJBi{dXMSn;mW{{I9@S^#j;Xp}w=Wv5%vlYm~dplbmNlawJJQ>@kEdCQUk_c7pmapfriUoQTD{0h))B766j7l+X0^wU~S` zFI&tQiwVn;2j(m=)KAP?&YU)9PIc-%51Nl5w}7%q>DkuX$1yM%XLUI30`;?o#eV_A z;*Eu#O5v=*@VOQ<7tHD#$-h81T2Pp`1sC&07eJZ@T??!uwYmoKhJ{Ke2`Nrv+=d_1z6(>svuOP0DL zcOrBgd|ooduS&y8LuNVDsXYDn1P0ZUHCT;70d$=H90P3;D&keg=?3(G&C_|i<@CYiM=rbS&LEs_&^l|CjQHD4#o(yZY;^bZ^G*5zoXU zmwBc$3KY?uDCkE-I>nv@b+d}a&p@$wIvMXO>vV7dg1(-YdZw$z%>ozoapYeoTmh{| z35SY-y8)hP9hnL6Y#h-+^c1K&e9g5EsV9w2y72JNfEz=m^{o|gThp2Rz>M)M<#F54p z_aM0D7N@hKaHSm~|A~St1lY_vau2}%IHEJ%=}=SnnrEH&ywG`myJSvP!2aC76T*Af zw6M;}Mx7Ym$-ouR4~K~=i$iB~3!DS6rNv!oaiyaw32H7uwX#9o3vK{G>8N%F)Dpgb z{?kBR6pE?y-;alJq>!Ui8|%nJ;96VURTh`ZksJbOZ5?@rhI1Z{a9(I=&IH(wftqAi z1HW=3SG*C>R{svR|A$D5xcAW35&O**!{4x5(t6LegM<0q#rU_RuU*DuMG}OXjMKt+fYB-pK zE~exLdjVp#9I;?sgl|j_%Oh>7N~QwfT^lvH@IW0BZ!(|IHHr{xlkSWy0`o`-t6yNa(8_daylf| z+dpUEkgAPib&=zZMBP?4=ZxY<#o)W4TpO4O|8BrITVPb zIc|WISi|D4;S`hDpSN>HPiIWWF^#>-nB;Z#f1f1kuu=}=<#=tx4tgzi=6?{o**~Kf zQJ`s7@za3UIf~y$ae1p)W9zMAo&BY_1qzR_iW3LSeDswAqX6cExr6|8xEusku)4&5 zJ%%zduYo=&>!p88&gN)+gas`Gw83I@v=%1J0ey&~l6ByDrr`n{(0TEEs3QEDWJ&z0 z|D)i=ydK(FvgC>=k;;zT_pTT=LmS8tzCz+)esD1*OD>97RY&YcR}9sm4FpUTFgTg9 zm=v_L6}N``_Mqa9Vk}BX8f(v(&SW=1i_y0ms>b|KV6o!+oN1okUfE%CFZN6|z?!H9 zfQOPN9aS%aeuA%R62E|)i>Jf=rzlLSCV!p^$k~q;vk1&4i_zg;m=@SCQz_Yi4q6AE z0(2=3=*T)4`q?TIe=W+y8w-7r!htBdezllqz-+b{o!5nFf&DTL6mekx1yB}|z5u8l z-9X1qrSDlldm&O+oH-UhnOkpu2NMOe1WZR#t`p@rXc_YRAe-`HTpAh2{`;mUDgVtH zTmiT}26d_wcsal?Lk(^)e_De}!5oJ{9rngU&m;eJ3WF>V6T#jefxVcw5^x6$>cl7T z3V`2)8r)4ZTY>Lx%hA^&YkgCCm0i#+p}4R96Mj@Z+|Zi23#Ujg`CsKL$V zA8T+Kpw1Z7;q4Y^HS+&UY49VHi^0FG!56_Ei$NXWgnbp<_X&G3CePn={5jle{<8+3 z1Jo6RI#=Bay$0atlm>U2o*4Ys8hi=taTwIuO4v8R9ZVQ3WpA5_?Y0J&1L}@Jou6)l z)&lq?rNNI)1cOD^;LBi-$Dqzo!mbDRYpB8PW-l1+L^>+<(=&W^PMQS$1|a!O=O|Z& zdmO+yyeFKH|2;(BL;g!gzP}^?M~Hm(W#oUK0~qK4{tN-cF>t`j>v(h;@;VyDpua-o zvoGYYBRKNC9r;6&kMTpBukIEtgW>?bb^v`Gz~3PT@{#|_kw43k|0hJg0Qql`$KQNgBFVtzEZl`(DBap*0Sm8WO7vioY34SUDSyqQz6MsTw(n?H$Q z6>kRH#L=6F-tVkljWuOVdv&hvpKd{M1(de2iW3`l9ECqv(CdJ<0n(m48F~jy#nR6B z{a&AT{N7@=foTp#d-4=$JD5sk?6Cel4v+5wit^9r0rg-N=zusKdK1vUP+05an|Y?^ zg`U|CR;~IKu$@>1IwK0Z5!`=J*;f6Y*#SnY;CWrY^kB}k+2=t=;$iYCh+hyf?VT4KAR9v;ThInT?*YSvbk)WY zZRrJ$Be4_l;v{*JC|U^b(?3CcD0H)Glb?|{xh(Lk&0NE}%Q6`|~Hn^Ns?pYoCLa`3g8X;$TdB-}Vv2u*Db^6Ynha(pzIb&#M+pPQ> z$p4G;+BNTl&QEeawhpffsOsk};2i)*U_e`@02hQ9C_;WY@=CofSuZB_+Pr2#iK#ml z+;PzHP$$Tpx@@ww0zBO|mZ1%#*T5iM#pz+QaMP!KAkm;o`obsX1?%)WbnwT={SD|o zoVWyo$x~D?h(Aza<#!-o5&3C4{|hh_Kyn(30IUqW%;-aPuM1rz@0FTYZ9cB!+E-pe zM^iE-9gSHbIvebQ{JlUGdnv|qphSeldjU)2MeFfKJWj)7?R@t`!$KmA==sFvF?H9I}Y^2forV;w_rpL%yS&rVjZA;n5W=V zJHF#1b8X*%C#QDeR1r=|{~SmKCTy@1!X%U@Dl>SaER`1&rd~0w(3_gf?5P__yqDx`;?+17~oD=;G7(@?_5DvVr@ z5v9M_23dOm9PEzNRm`*+#>b8T-2g{I6p~rXYm`4rGzUSYNd@?}lcfjY%SrL)StlQc zhnkGmm z7=Lj}F#k^?_auDHsrai#1mC?~3O|wV7s=d28%!eIMd*71oTC2?+*bsX4`v~B6SA@7 zXrcge0Wh9X5wDMSPDS^U%D7dgv6_r2qGupQG{qV}l0cNnIJ%}Fugu(r{7X}UBAkc( zWcZD-?`hyunv0mvLN}u%q`CMtp{6p`I&g?+z9AYd)5XvPaLK_G4dAaESO=3hwiv*7=W)8eg!CZ}-P;Pf5V z=^9|_;IuH$TTCq3>o{KDWsRt6Gp-Ki_eB7CNk%l!D%v<{o@pJf4Q4nFYf6?t_gG!x z|A)iky$nqYbOoQEbJJXavb(LvnSkozu|O*AK><#M;Wnb8v-nE{7@(m}^dGCujDfF4B>wBR5#0n& zYhHC#o$RST)<83Yh>M?C>1Xd)lthW(MnG~zg>nLps7P*4awPvu{wTnKIfKXZCgC{b z*s!H2bh@u0Yt4Obr*t~f9)lTdL%N5`QPCSZ=Q(JQ}{zEB?9>y!8TaJ(NiAE9kfng>HHasHp*t)34qfoFz7+e<_akXRIam z{Z#EI_3~2p_8v#kqmVq+Ty232&J6;f@53qsNAM9i$-V*gKvvN`0V%S?7lWFTod=ij zCiDe6oCk}3%J;$VZ*lVrEzCgl>8TnSXQOa-@8G(znFTevzLxWR&QA^VY#1wk1_-w~ z%ka|RosZIl&PPjh7UR-O<6=r-0iPu>nPYpkxM^66GD;(iorlsaGIc(VdVPYZHxg++ z5l`b)&NavJZAC1xuA56aexZXs)xpjMJ44523oiChkzL?`dOM(bfOP978KcNAb}(l; znE7CWdr`iV#deg5i`1z!r@F0Li8(Dmc_HKhN-?L-Gu}QkL%=KD=YT)@%;G65Hj+m6 zlBX`+^2nHpvG%nA6>xSkgKY#CpzL@ekRzL*1vulRxmdZTZK#9k>|mY(GXrdLrZkhFPNA;&K-yFf3 zhPfF^tmoCF$!;Q2oIpTn{H%Rrn{*JyahdmY)E(W+iaXa0CK8zq%pWPQ-aNC z1GrwAe|e&TjW{Ca7AWD!8XTFO zqUmigGLVU>2ug%X7fY}-KR1~}_cXgKt$CS3ikQ#M83)p5$&qZ7X5xtIq(v8z$}<6L3Us6UXr@5Z_BQka*i=(c)GuQCSe))J z*8`_4Z`1l;c^IGvurI)tfw$%W5z5hbAhqe$klUv7aquh~og6$!aB}W_XbrMug2R5v z1_bzQ3znCM0B-~M0rYAZ*um!GXg{#(B`VG;^b$F_9qI{InSBjP+8^BY|G7CA1uBqp zQP8JvSTp&&Obw({18#!}YG#+oym(%-A#0nNbI_SZ_AW%{NOa1f522TEhMp39>{&&) z4bOQg;I6+p)&X7PfL;a^-c&v}9UaVd4yF)Hcu)D<9Is=0!n-jJXf2@drt-Nt4ns<_ z-a|TyG`~wU??I=7NtNc36}HQq0A5+{5B~00@3Lu*m|dk(s;s1i@Y3ORvQb}zvgUmP zM|R@OI-Ch_*}F^^2NP@MfZhTWp59%ivjb}DU^asZPwy_%&B1hVFk8Ub?8dZj7tg&S z>I#VT>Yk1nc6HFzRspybdJS1!0VPg2E!gu9-Nu_+!>)B6GWvz-c?O?UM^8)JC%$6Y zY6*S}Y1zIG1hoj&ajH-b4L+__Fy31q*Mvb~$4OW3t`w4Uw7e!6g=6BWeh+;`Wa!XpeSS&l;x$9y(TErGQJpHpPm^wvKwbUz=`lxP-GhW7;f7L zw95f~0w{bH6q#m@q1_JVQ!wGHpvW|JF#8=q=e=r1w+g zK7;xKI$U~#_PfVqo}1QeK;venIeJv21JN@jq}NdYd&;Dl&q|fyrNL|ED7!sGS$YyK zaAvQ`Jab;tzH6J7(HSv&W2U7|&tN<}rcV&%9wOaCq~UY7$Fy-U^&QM!FyT|T$Fz1Z zjfyeEzYkFO-0d;#98lw85K9tF`2MiRv<0K7Qy;0r)TxJj0i6p*+xUA>@ygKt&vlw} z1lwymSOt2YuO13^0WN~B2vgwTgLLmRnWqnHddB3YragKz_k++gDP+3R%--Os=I(V} zI!t#z$F~A&I!r)jUra4_{9%mFZVno9S73c?)ifEqcV?*WBR z(_T}KtRr&Em{@pv_L(|>R6OdA zgGQD#DYu6w8dnJ~=o_VXyuGF#x>^zFNOaX@e^F~b0R3H&{D<#qMW(I=T?42dAoYZ= zpnvc>9J9wXu$b$>)CZ#i{Tli=B__7l)CZJFq?(4z&`9^0Mpp3{WE!AYbNdaHRwf_@@QWCfx4 z{bnwBa_l>xTfw8jE9L)zzu$2n<{YIQ2M(A*6r@`Px1-<&6e#_=apx)s$A4vZgHN+~ z-N3vNyrv=o{=pIlV(wYaao}qc>EoI5fdX?M3dW;ADgPEqFRdhnj&DpBI?AC#g*+P_ z$JtL_B(lx@M|HPvN=-q~@xBh>ePt>*%1$VwOu7z);P!{`zBETj7wg~mtZS&U-66m) zd|pdVzK@cTeqYw>mc^`;i$^g2t;x)soOR5+ta=sdt<9>0fu<}wMP9^v(aPL+P~yp` zN0P-@WqwqFszTMEqo53^I#eFTN|g*m;yjeE^OzCUHl*)|LNbYYq6eA1DjMv55LE}g(&Jt2a%HRq45=XKhPR)4YsXfz(RDFHoIIbH2Z~YV8eVzIGa_QzEW2$pECa1EE=PZbY7l8%NXpxxs74hcPJ1nwd+YeG z4z{g>tydab%GqCZsh%*{-T~E50=d!rjv+FdQGk=H$Y>p8-zNNbAh$?~*vag7<~MMv zn~mUJCldlJbknr<*?*Q_3|q>4qQ!Qk+$s^6&hth*#odz-v#Y zHsRpJ+=1ObM82HAod0JstL}*&m6lb#LiNU3u|pWBN6Ka30gV5KC%;1tiK+qA7*g6% zR&11YW&Uz-4?4I;$TvxWO9S_hgL`NktukRS3aDukuq2}YnZE(5kl)79IYcEV|A3l- zc^Bt)KyLaw5dF6g5Wt~-OL|1*wbB33lSRNEpr=z;{1I)XlCb z)g%4|tRC?(;BWf{xWB-*2AAj&(Vh{r8#&FjkC(d+)) z)HR4h4foT+24AA1I^%Qckuj19{s{9QO4`kNb~ewgI}Tj4>AVZn|C8dUp6))yGyA|H zl`#?AiF|;MgM$AtmVK1!0mf?J*VBykVod7N%8^6-=>RFN%Fq%h;_(=&lRdLP@FRLZ zm|O9KUTNA@GIJU|Shbd%@HcM>=VJV8YhVt-9tI?w4jr4qKp_UcvIeGrnTUZ%V8HA} z_m`0S%sba!)+ru$k*&cQfKSGtz(+&Jr!d$PgWrHT8vk~HX-$PF1LdKPU=+P*DOA** zZ=_h+caUv`tb(Zp9p}qZnhD_oguk_x2XPZ`rkv@{oO8Phyefleg1yjCgrEbVB`}=zI};&%$yVghywWB0dy<@ zn3F@~J>)-gN$?E3u$0h)3MgK@? zoKIDJaDpE*8 zP*Be*r~tUqVHNyt3W;C?c-EC)i06+Cim(+r5+Cb7m0(VTPKSC!iNah;LcR8Uo8c`K zDE-%vx(%dXscQk9i7+q4>G!(v^$ldJ!Mg+58}fo~QVpRk@KrP7zd`!NJ09wRj_{iK z!{l`K%;p$Co#n;=TpU_7Kb9>T^OMihbf^e};`-UVZ3B_6jRYbm+Cl@91L?vyH*6rW zdl4R!7j&Hrgx|4|h+jw~;&p;fNg2tXrYDhXv&JWY+k|o9IzWSxj7Ks4yfyxKBhGqP zGz+TZV|5%iC z!f>LgtElqXm{(e3QDk#4*2p(Tma$XawS|3kM`u|bGPxLg$r{^;f)*Il&U^yYHOW{U zV=p4BqTG(`ZF#|Qp*FIwz}I#u{;|}ScwL~*=+I6i@;^;2@0USE%+1RV=(r?M*tTcb zUe-96Hrh-z3Y?&3fsp#K(7JPGV4qYETK4616`q>M3h)&iB*y2x8}c1 zF2(sOIL*EO+`$!0M-~`0xues_e=T=D$U^Dq_{Zch9dDpOh4~bQ&vpv)Gz#-|##ERZ zJI5C0g&0s_wng6+ys9ueQ5pB^kKuUO3dPP63L1;YL%~=&^8hM+ zT}j_nq^}7Wr7x4r9dp?1{bzP}^~_V?HElgG{4CQZ!`-2Nm017b^JXGh%-aI%@zVhN zTaO1@hm%t4Y{!vEE*V`2RvCQ~?8v-eW2y`GX{%HGUiw=eVh(|>I!qP{J9_3B8$~aG z&k==iCqrk03lAbK5+R61*5E>L0|`R7ORT-N*mn!Wi6T9+yNzd-*eLP=kID;n$NGR5 zTczTkhEnl{LL&|nL{EZv)&|iB-17tx6VQXsNf|}C$nFz7v(y?~1n@j-@KS3q^;DFP z%q58DY!Gw7UY!@T-iBb89pKxPs_upiJP}&me}z=?p%C6b=05A7jP%Aq8Mq7@dzg&O z?GW?Ky*4H109-*z1ndP3PMMJ-B86mRmNobcxC^YoY-=!eMk+*d$jJRRBlBW_ugMEG zh$!IMR_Q$WeNZZ14s_#TqR8psnFnkT=Yd;A5W<}XU6L}0ijlbl5u0NTE(Um!HF&u- zm^z9|kqA*dXrq`9_S(Fltu_Yxu+=I4S?Cn+3h1V?K@2W#G==Rw^N@{VFu<3HLcre8 z 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 V7yNF2)@<+d+JE>zzF6SP{{sy=1q%QG diff --git a/examples/qualcomm/oss_scripts/llama/decoder_constants.py b/examples/qualcomm/oss_scripts/llama/decoder_constants.py index 4282a73d6bb..e33fde461aa 100644 --- a/examples/qualcomm/oss_scripts/llama/decoder_constants.py +++ b/examples/qualcomm/oss_scripts/llama/decoder_constants.py @@ -4,6 +4,11 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +# eval methods +PROMPT_EVAL = "prompt_eval" +TASKS_EVAL = "tasks_eval" +SQNR_EVAL = "sqnr_eval" + # filenames for vision model VISION_ENCODER_INPUT_FILENAME = "vision_encoder_input" diff --git a/examples/qualcomm/oss_scripts/llama/decoder_runtime_evaluator.py b/examples/qualcomm/oss_scripts/llama/decoder_runtime_evaluator.py new file mode 100644 index 00000000000..158627d5a43 --- /dev/null +++ b/examples/qualcomm/oss_scripts/llama/decoder_runtime_evaluator.py @@ -0,0 +1,569 @@ +# Copyright (c) Qualcomm Innovation Center, Inc. +# All rights reserved +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +import getpass +import logging +import os +import subprocess +from abc import ABC, abstractmethod +from functools import partial +from typing import Any, final, List, Optional, Union + +import numpy as np +import torch +from executorch.examples.models.llama.evaluate.eager_eval import EagerEvalWrapper +from executorch.examples.qualcomm.oss_scripts.llama.decoder_constants import ( + DECODER_MODEL_VERSION, + EVAL_MODE, +) +from executorch.examples.qualcomm.oss_scripts.llama.decoder_utils import ( + INFERENCE_REGISTRY, + retrieve_info_from_pte, +) +from executorch.examples.qualcomm.utils import make_output_dir, SimpleADB +from pytorch_tokenizers.hf_tokenizer import HuggingFaceTokenizer +from pytorch_tokenizers.llama2c import Llama2cTokenizer as SentencePieceTokenizer +from pytorch_tokenizers.tiktoken import TiktokenTokenizer +from torchao.quantization.utils import compute_error + +try: + from lm_eval.evaluator import simple_evaluate +except ImportError: + raise ImportError( + "Please install the llm eval dependency via examples/models/llama/install_requirements.sh" + ) + + +def post_process_model_output(output_holder: List, host_output_response_path: str): + with open(host_output_response_path, "r") as f: + output_holder.append(f.read()) + + +def post_process_inference_speed(output_holder: List, host_performance_path: str): + with open(host_performance_path, "r") as f: + output_holder.append(float(f.read())) + + +def post_process_logits( + output_holder: List, + host_logits_path: str, + kv_io_bit_width: int, + num_input_tokens: int, + output_vocab_size: int, + logits_scale: float, + logits_zero_point: int, +): + with open(host_logits_path, "r") as f: + logits_dtype = np.float32 if kv_io_bit_width == 32 else np.uint16 + output_tensor = torch.from_numpy( + np.fromfile(f.name, dtype=logits_dtype).reshape(1, -1, output_vocab_size) + ) + output_tensor = output_tensor[:, :num_input_tokens, :] + output_tensor = ( + output_tensor.to(torch.float32) - logits_zero_point + ) * logits_scale + output_holder.append(output_tensor) + + +class EvalBase(ABC): + _adb: Optional[SimpleADB] = None # ADB shared across all instances + + def __init__(self, args, pte_path, runtime_tokenizer_path): + self.args = args + self.pte_path = pte_path + self.runtime_tokenizer_path = runtime_tokenizer_path + self.qnn_sdk = os.getenv("QNN_SDK_ROOT") + + self.device_workspace = ( + f"/data/local/tmp/{getpass.getuser()}/executorch/static_llm" + ) + + device_output_path = self._get_adb().output_folder + if args.enable_x86_64: + logging.warning( + "x86 emulator is NOT recommended as it is for CI purpose, expect significance drop in performance." + ) + device_output_path = f"{args.artifact}/outputs" + + self.device_output_response_path = f"{device_output_path}/outputs.txt" + self.device_performance_path = f"{device_output_path}/inference_speed.txt" + self.device_logits_path = f"{device_output_path}/all_logits.raw" + self.host_output_response_path = f"{args.artifact}/outputs/outputs.txt" + self.host_performance_path = f"{args.artifact}/outputs/inference_speed.txt" + self.host_logits_path = f"{args.artifact}/outputs/all_logits.raw" + make_output_dir(f"{args.artifact}/outputs") + self.runner_base_cmd = self._init_runner_base_cmd() + + def _init_runner_base_cmd(self): + """ + If a runner cmd could be shared across all EvalBase class, place it here to reduce redundant code. + """ + args = self.args + base_cmd = "" + if args.enable_x86_64: + base_cmd = " ".join( + [ + f"export LD_LIBRARY_PATH={self.qnn_sdk}/lib/x86_64-linux-clang/:{args.build_folder}/lib &&", + f"./{args.build_folder}/examples/qualcomm/oss_scripts/llama/qnn_llama_runner", + f"--decoder_model_version {DECODER_MODEL_VERSION[args.decoder_model]}", + f"--tokenizer_path {self.runtime_tokenizer_path}", + f"--model_path {self.pte_path}", + f"--output_path {self.device_output_response_path}", + f"--performance_output_path {self.device_performance_path}", + ] + ) + else: + base_cmd = " ".join( + [ + f"cd {self.device_workspace} &&", + "./qnn_llama_runner", + f"--decoder_model_version {DECODER_MODEL_VERSION[args.decoder_model]}", + f"--tokenizer_path {os.path.basename(self.runtime_tokenizer_path)}", + f"--model_path {os.path.basename(self.pte_path)}", + f"--output_path {self.device_output_response_path}", + f"--performance_output_path {self.device_performance_path}", + "--shared_buffer", + ] + ) + return base_cmd + + @final + def _get_adb(self): + args = self.args + if EvalBase._adb is None: + EvalBase._adb = SimpleADB( + qnn_sdk=self.qnn_sdk, + build_path=f"{args.build_folder}", + pte_path=self.pte_path, + workspace=self.device_workspace, + device_id=args.device, + host_id=args.host, + soc_model=args.model, + runner="examples/qualcomm/oss_scripts/llama/qnn_llama_runner", + target=args.target, + ) + return EvalBase._adb + + @abstractmethod + def run(self) -> Any: + # Performs execution and comparison. + # Returns: + # Any: The result of execution. + # The type of the return value are determined by the implementation. + # The caller can return anything and handle it themselves. + pass + + +class DefaultEval(EvalBase): + def __init__(self, args, pte_path, runtime_tokenizer_path): + super().__init__(args, pte_path, runtime_tokenizer_path) + self.adb = self._get_adb() + self.inference_speed = 0 + + lookahead_args = " ".join( + [ + f"--window {args.window}", + f"--gcap {args.gcap}", + f"--ngram {args.ngram}", + ] + ) + runner_args = " ".join( + [ + f"--eval_mode {EVAL_MODE[args.model_mode]}", + f"--temperature {args.temperature}", + f"--system_prompt '{args.system_prompt}'", + lookahead_args if args.model_mode == "lookahead" else "", + ] + ) + self.runner_cmd = " ".join( + [ + self.runner_base_cmd, + runner_args, + f"--seq_len {args.max_seq_len}", + ] + ) + + def run(self, prompt): + multi_prompts = " ".join([f'--prompt "{p}"' for p in prompt]) + + model_output_holder = [] + performance_holder = [] + + if self.args.enable_x86_64: + # x86 emulator is intended for CI and not performance. Check only the first few tokens. + seq_len = min(self.args.max_seq_len, 32) + runner_cmd = " ".join( + [ + self.runner_cmd, + multi_prompts, + f"--seq_len {seq_len}", + ] + ) + subprocess.run( + runner_cmd, + shell=True, + executable="/bin/bash", + capture_output=True, + ) + post_process_model_output( + output_holder=model_output_holder, + host_output_response_path=self.host_output_response_path, + ) + post_process_inference_speed( + output_holder=performance_holder, + host_performance_path=self.host_performance_path, + ) + else: + runner_cmd = " ".join( + [self.runner_cmd, multi_prompts, f"--seq_len {self.args.max_seq_len}"] + ) + self.adb.push(inputs=[], files=[self.runtime_tokenizer_path]) + self.adb.execute(custom_runner_cmd=runner_cmd) + self.adb.pull( + host_output_path=self.host_output_response_path, + device_output_path=self.device_output_response_path, + callback=partial( + post_process_model_output, + output_holder=model_output_holder, + host_output_response_path=self.host_output_response_path, + ), + ) + self.adb.pull( + host_output_path=self.host_performance_path, + device_output_path=self.device_performance_path, + callback=partial( + post_process_inference_speed, + output_holder=performance_holder, + host_performance_path=self.host_performance_path, + ), + ) + self.inference_speed = performance_holder[0] + return model_output_holder + + +class SqnrEval(EvalBase): + """ + SQNR Evaluator will only evaluate the given prompt's logit output. + It won't evaluate the generated token's logit. + """ + + def __init__( + self, + source_model, + example_input, + args, + pte_path, + tokenizer, + runtime_tokenizer_path, + ): + super().__init__(args, pte_path, runtime_tokenizer_path) + self.inference_speed = 0 + self.source_model = source_model + self.example_input = example_input + self.adb = self._get_adb() + self.tokenizer = tokenizer + self.enable_x86_64 = args.enable_x86_64 + self.max_seq_length = args.max_seq_len + + pte_meta_info = retrieve_info_from_pte(pte_path=pte_path) + self.output_vocab_size = pte_meta_info["output_vocab_size"] + pte_max_seq_len = pte_meta_info["pte_max_seq_len"] + self.logits_scale = pte_meta_info["logits_scale"] + self.logits_zero_point = pte_meta_info["logits_zero_point"] + self.kv_io_bit_width = pte_meta_info["kv_io_bit_width"] + + if args.model_mode != "kv": + logging.warning( + f"Current SqnrEval does not support {args.model_mode}, switching to kv mode." + ) + + if pte_max_seq_len != self.max_seq_length: + logging.warning( + f"The pte provided has a max_seq_len {pte_max_seq_len}, which is different from --max_seq_len {self.max_seq_length} provided to the script, please ensure this is desired." + ) + if pte_max_seq_len < self.max_seq_length: + logging.warning( + f"The pte max_seq_len {pte_max_seq_len} is used since it is shorter than --max_seq_len {self.max_seq_length}" + ) + self.max_seq_length = pte_max_seq_len + + def run(self, prompt): + golden_logits = INFERENCE_REGISTRY[True]( + example_input=self.example_input, + prompt=prompt, + module=self.source_model, + tokenizer=self.tokenizer, + max_seq_len=self.max_seq_length, + use_i64_token=self.args.embedding_quantize is not None, + collect_logits=True, + ) + + input_file_name = f"{self.args.artifact}/input_tokens.raw" + + # Make sure the encode param aligns with encode param under all register_inference function. + # This ensures the token used for device is same as token used for nn.Module. + inps = torch.tensor(self.tokenizer.encode(prompt, bos=True, eos=False)) + inps = inps.to(torch.uint64).numpy() + inps.tofile(input_file_name) + + assert ( + inps.size < self.max_seq_length + ), f"Number of input token is longer than max_seq_len, please shorten the input token length. input_token length: {inps.size}. max_seq_len: {self.max_seq_length}" + + output_logits_holder = [] + output_performance_holder = [] + + if self.enable_x86_64: + runner_cmd = " ".join( + [ + self.runner_base_cmd, + f"--seq_len {inps.size + 1}", + f"--eval_mode {EVAL_MODE['kv']}", + "--temperature 0", + f"--dump_logits_path {self.device_logits_path}", + f"--tokenized_prompt {input_file_name}", + ] + ) + + subprocess.run( + runner_cmd, + shell=True, + executable="/bin/bash", + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + post_process_logits( + output_holder=output_logits_holder, + host_logits_path=self.host_logits_path, + kv_io_bit_width=self.kv_io_bit_width, + num_input_tokens=inps.size, + output_vocab_size=self.output_vocab_size, + logits_scale=self.logits_scale, + logits_zero_point=self.logits_zero_point, + ) + post_process_inference_speed( + output_holder=output_performance_holder, + host_performance_path=self.host_performance_path, + ) + else: + runner_cmd = " ".join( + [ + self.runner_base_cmd, + f"--seq_len {inps.size + 1}", + f"--eval_mode {EVAL_MODE['kv']}", + "--temperature 0", + f"--dump_logits_path {self.device_logits_path}", + f"--tokenized_prompt {os.path.basename(input_file_name)}", + ] + ) + self.adb.push( + inputs=[], files=[input_file_name, self.runtime_tokenizer_path] + ) + self.adb.execute(custom_runner_cmd=runner_cmd) + self.adb.pull( + host_output_path=self.host_logits_path, + device_output_path=self.device_logits_path, + callback=partial( + post_process_logits, + output_holder=output_logits_holder, + host_logits_path=self.host_logits_path, + kv_io_bit_width=self.kv_io_bit_width, + num_input_tokens=inps.size, + output_vocab_size=self.output_vocab_size, + logits_scale=self.logits_scale, + logits_zero_point=self.logits_zero_point, + ), + ) + self.adb.pull( + host_output_path=self.host_performance_path, + device_output_path=self.device_performance_path, + callback=partial( + post_process_inference_speed, + output_holder=output_performance_holder, + host_performance_path=self.host_performance_path, + ), + ) + self.inference_speed = output_performance_holder[0] + + sqnr = compute_error(golden_logits, output_logits_holder[0]) + return sqnr.item() + + +class TaskEval(EvalBase): + class QnnRunnerEvalWrapper(EagerEvalWrapper): + """ + A wrapper class to run tasks with QNN on device. + """ + + def __init__( # noqa: C901 + self, + args, + runner_base_cmd: str, + adb: SimpleADB, + pte_path: str, + device_performance_path: str, + device_logits_path: str, + host_performance_path: str, + host_logits_path: str, + tokenizer: Union[ + SentencePieceTokenizer, TiktokenTokenizer, HuggingFaceTokenizer + ], + runtime_tokenizer_path, + ): + self.inference_speed = None + + self.args = args + self.runner_base_cmd = runner_base_cmd + self.adb = adb + self.pte_path = pte_path + self.runtime_tokenizer_path = runtime_tokenizer_path + + self.device_performance_path = device_performance_path + self.device_logits_path = device_logits_path + self.host_performance_path = host_performance_path + self.host_logits_path = host_logits_path + + self.enable_x86_64 = args.enable_x86_64 + self.max_seq_length = args.max_seq_len + pte_meta_info = retrieve_info_from_pte(pte_path=pte_path) + self.output_vocab_size = pte_meta_info["output_vocab_size"] + pte_max_seq_len = pte_meta_info["pte_max_seq_len"] + self.logits_scale = pte_meta_info["logits_scale"] + self.logits_zero_point = pte_meta_info["logits_zero_point"] + self.kv_io_bit_width = pte_meta_info["kv_io_bit_width"] + + if args.model_mode != "kv": + logging.warning( + f"Current QnnRunnerEvalWrapper does not support {args.model_mode}, switching to kv mode." + ) + + if pte_max_seq_len != self.max_seq_length: + logging.warning( + f"The pte provided has a max_seq_len {pte_max_seq_len}, which is different from --max_seq_len {self.max_seq_length} provided to the script, please ensure this is desired." + ) + if pte_max_seq_len < self.max_seq_length: + logging.warning( + f"The pte max_seq_len {pte_max_seq_len} is used since it is shorter than --max_seq_len {self.max_seq_length}" + ) + self.max_seq_length = pte_max_seq_len + + if not self.enable_x86_64: + self.adb.push(inputs=[], files=[self.runtime_tokenizer_path]) + # n seq len = n-1 cache len, so we set len(inps) = n-1 during _model_call + # pyre-ignore + super().__init__(None, tokenizer, self.max_seq_length - 1) + + def _model_call(self, inps): + input_file_name = f"{self.args.artifact}/input_tokens.raw" + # This is the dtype required by runtime tokenizer. + inps = inps.to(torch.uint64).numpy() + inps.tofile(input_file_name) + output_logits_holder = [] + output_performance_holder = [] + + if self.enable_x86_64: + runner_cmd = " ".join( + [ + self.runner_base_cmd, + f"--seq_len {self.max_seq_length}", + f"--eval_mode {EVAL_MODE['kv']}", + "--temperature 0", + f"--dump_logits_path {self.device_logits_path}", + f"--tokenized_prompt {input_file_name}", + ] + ) + subprocess.run( + runner_cmd, + shell=True, + executable="/bin/bash", + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + post_process_logits( + output_holder=output_logits_holder, + host_logits_path=self.host_logits_path, + kv_io_bit_width=self.kv_io_bit_width, + num_input_tokens=inps.size, + output_vocab_size=self.output_vocab_size, + logits_scale=self.logits_scale, + logits_zero_point=self.logits_zero_point, + ) + post_process_inference_speed( + output_holder=output_performance_holder, + host_performance_path=self.host_performance_path, + ) + + else: + runner_cmd = " ".join( + [ + self.runner_base_cmd, + f"--seq_len {self.max_seq_length}", + f"--eval_mode {EVAL_MODE['kv']}", + "--temperature 0", + f"--dump_logits_path {self.device_logits_path}", + f"--tokenized_prompt {os.path.basename(input_file_name)}", + ] + ) + self.adb.push(inputs=[], files=[input_file_name], init_env=False) + self.adb.execute(custom_runner_cmd=runner_cmd) + self.adb.pull( + host_output_path=self.host_logits_path, + device_output_path=self.device_logits_path, + callback=partial( + post_process_logits, + output_holder=output_logits_holder, + host_logits_path=self.host_logits_path, + kv_io_bit_width=self.kv_io_bit_width, + num_input_tokens=inps.size, + output_vocab_size=self.output_vocab_size, + logits_scale=self.logits_scale, + logits_zero_point=self.logits_zero_point, + ), + ) + self.adb.pull( + host_output_path=self.host_performance_path, + device_output_path=self.device_performance_path, + callback=partial( + post_process_inference_speed, + output_holder=output_performance_holder, + host_performance_path=self.host_performance_path, + ), + ) + self.inference_speed = output_performance_holder[0] + return output_logits_holder[0] + + def __init__(self, args, pte_path, tokenizer, runtime_tokenizer_path): + super().__init__( + args=args, pte_path=pte_path, runtime_tokenizer_path=runtime_tokenizer_path + ) + self.inference_speed = None + self.tasks = args.tasks + self.num_fewshot = args.num_fewshot + self.limit = args.limit + adb = self._get_adb() + self.eval_wrapper = TaskEval.QnnRunnerEvalWrapper( + args=args, + runner_base_cmd=self.runner_base_cmd, + adb=adb, + pte_path=self.pte_path, + device_performance_path=self.device_performance_path, + device_logits_path=self.device_logits_path, + host_performance_path=self.host_performance_path, + host_logits_path=self.host_logits_path, + tokenizer=tokenizer, + runtime_tokenizer_path=self.runtime_tokenizer_path, + ) + + def run(self): + with torch.no_grad(): + eval_results = simple_evaluate( + model=self.eval_wrapper, + tasks=self.tasks, + num_fewshot=self.num_fewshot, + limit=self.limit, + ) + self.inference_speed = self.eval_wrapper.inference_speed + return eval_results diff --git a/examples/qualcomm/oss_scripts/llama/decoder_utils.py b/examples/qualcomm/oss_scripts/llama/decoder_utils.py index 6edcc408a96..8da25f19a2b 100644 --- a/examples/qualcomm/oss_scripts/llama/decoder_utils.py +++ b/examples/qualcomm/oss_scripts/llama/decoder_utils.py @@ -5,24 +5,17 @@ # LICENSE file in the root directory of this source tree. import getpass +import copy import logging -import os -import subprocess from collections import defaultdict, OrderedDict from dataclasses import dataclass from typing import Callable, List, Optional, Tuple, Union -import numpy as np import torch from executorch.backends.qualcomm._passes import SeqMSE from executorch.examples.models.llama.evaluate.eager_eval import EagerEvalWrapper -from executorch.examples.qualcomm.oss_scripts.llama.decoder_constants import ( - DECODER_MODEL_VERSION, - EVAL_MODE, -) from executorch.examples.qualcomm.oss_scripts.llama.masking_utils import AttentionMask -from executorch.examples.qualcomm.utils import make_output_dir, SimpleADB from executorch.exir._serialize._program import deserialize_pte_binary from pytorch_tokenizers.hf_tokenizer import HuggingFaceTokenizer from pytorch_tokenizers.llama2c import Llama2cTokenizer as SentencePieceTokenizer @@ -288,184 +281,51 @@ def verify( return best_match, branch -class QnnRunnerEvalWrapper(EagerEvalWrapper): - """ - A wrapper class to run PPL scores with QNN on device. - """ - - def __init__( # noqa: C901 - self, - args, - pte_path: str, - tokenizer: Union[ - SentencePieceTokenizer, TiktokenTokenizer, HuggingFaceTokenizer - ], - runtime_tokenizer_path, - ): - self.args = args - self.pte_path = pte_path - self.enable_x86_64 = args.enable_x86_64 - self.max_seq_length = args.max_seq_len - - if self.enable_x86_64: - logging.warning( - "Using x86_64 emulator is NOT recommended as it is for CI purpose." - ) +def retrieve_info_from_pte(pte_path: str) -> dict: + # Retrieve vocab_size from get_metadata under static_llama that is passed to edge manager + output_vocab_size = None + pte_max_seq_len = None + logits_scale = None + logits_zero_point = None + kv_io_bit_width = 32 - with open(pte_path, "rb") as f: - program_data = f.read() + with open(pte_path, "rb") as f: + program_data = f.read() program = deserialize_pte_binary(program_data).program - # Retrieve vocab_size from get_metadata under static_llama that is passed to edge manager - self.output_vocab_size = None - pte_max_seq_len = None - self.logits_scale = None - self.logits_zero_point = None - self.kv_io_bit_width = 32 - for method in program.execution_plan: - # Don't use tokenizer.n_words, the numbers are off once calling get_tokenizer() - if method.name == "get_vocab_size": - # pyre-ignore - self.output_vocab_size = method.values[0].val.int_val - if method.name == "get_max_seq_len": - # pyre-ignore - pte_max_seq_len = method.values[0].val.int_val - if method.name == "get_logits_scale": - self.logits_scale = method.values[0].val.double_val - if method.name == "get_logits_zero_point": - self.logits_zero_point = method.values[0].val.int_val - if method.name == "get_kv_io_bit_width": - self.kv_io_bit_width = method.values[0].val.int_val - - # FP has no scale/zero_point, use following values, which is equivalent to not performing dequantize. - if self.kv_io_bit_width == 32: - self.logits_scale = 1 - self.logits_zero_point = 0 - elif self.logits_scale is None or self.logits_zero_point is None: - raise RuntimeError( - "Unable to find scale/offset. The .pte file might be deprecated. Please generate a new .pte file" - ) - - assert self.output_vocab_size is not None, "Couldn't find the vocab size" - assert pte_max_seq_len is not None, "Couldn't find the max_seq_len from pte" - if pte_max_seq_len != self.max_seq_length: - logging.warning( - f"The pte provided has a max_seq_len {pte_max_seq_len}, which is different from --max_seq_len {self.max_seq_length} provided to the script, please ensure this is desired." - ) - if pte_max_seq_len < self.max_seq_length: - logging.warning( - f"The pte max_seq_len {pte_max_seq_len} is used since it is shorter than --max_seq_len {self.max_seq_length}" - ) - self.max_seq_length = pte_max_seq_len - self.runtime_tokenizer_path = runtime_tokenizer_path - - self.output_dir = args.artifact - - self.workspace = f"/data/local/tmp/{getpass.getuser()}/executorch/single_llama" - self.adb = SimpleADB( - qnn_sdk=os.getenv("QNN_SDK_ROOT"), - build_path=args.build_folder, - pte_path=pte_path, - workspace=self.workspace, - device_id=args.device, - host_id=args.host, - soc_model=args.model, - runner="examples/qualcomm/oss_scripts/llama/qnn_llama_runner", - target=args.target, + for method in program.execution_plan: + # Don't use tokenizer.n_words, the numbers are off once calling get_tokenizer() + if method.name == "get_vocab_size": + # pyre-ignore + output_vocab_size = method.values[0].val.int_val + if method.name == "get_max_seq_len": + # pyre-ignore + pte_max_seq_len = method.values[0].val.int_val + if method.name == "get_logits_scale": + logits_scale = method.values[0].val.double_val + if method.name == "get_logits_zero_point": + logits_zero_point = method.values[0].val.int_val + if method.name == "get_kv_io_bit_width": + kv_io_bit_width = method.values[0].val.int_val + + # FP has no scale/zero_point, use following values, which is equivalent to not performing dequantize. + if kv_io_bit_width == 32: + logits_scale = 1 + logits_zero_point = 0 + elif logits_scale is None or logits_zero_point is None: + raise RuntimeError( + "Unable to find scale/offset. The .pte file might be deprecated. Please generate a new .pte file" ) - - # collect output data - output_data_folder = f"{self.args.artifact}/outputs" - make_output_dir(output_data_folder) - - if not self.enable_x86_64: - self.adb.push(inputs=[], files=[self.runtime_tokenizer_path]) - # n seq len = n-1 cache len, so we len(inps) = n-1 during _model_call - # pyre-ignore - super().__init__(None, tokenizer, self.max_seq_length - 1) - - def _model_call(self, inps): - - input_file_name = f"{self.args.artifact}/input_tokens.raw" - inps = inps.to(torch.uint64).numpy() - inps.tofile(input_file_name) - - outputs_path = "outputs/outputs.txt" - dump_logits_path = "outputs/all_logit.raw" - performance_output_path = "outputs/inference_speed.txt" - output_tensor_list = [] - - def post_process(): - with open(f"{self.args.artifact}/{dump_logits_path}", "r") as f: - logits_dtype = np.float32 if self.kv_io_bit_width == 32 else np.uint16 - output_tensor = torch.from_numpy( - np.fromfile(f.name, dtype=logits_dtype).reshape( - 1, -1, self.output_vocab_size - ) - ) - output_tensor = ( - output_tensor.to(torch.float32) - self.logits_zero_point - ) * self.logits_scale - output_tensor_list.append(output_tensor) - - # simple_eval will run multiple rounds, use last run for inference speed - with open(f"{self.args.artifact}/{performance_output_path}", "r") as f: - self.inference_speed = float(f.read()) - - if self.enable_x86_64: - qnn_sdk = os.getenv("QNN_SDK_ROOT") - target = "x86_64-linux-clang" - runner_cmd = " ".join( - [ - f"export LD_LIBRARY_PATH={qnn_sdk}/lib/{target}/:{self.args.build_folder}/lib &&", - f"./{self.args.build_folder}/examples/qualcomm/oss_scripts/llama/qnn_llama_runner", - f"--decoder_model_version {DECODER_MODEL_VERSION[self.args.decoder_model]}", - f"--tokenizer_path {self.runtime_tokenizer_path}", - f"--model_path {self.pte_path}", - f"--seq_len {self.max_seq_length}", - f"--output_path {self.args.artifact}/outputs/outputs.txt", - f"--performance_output_path {self.args.artifact}/{performance_output_path}", - f"--eval_mode {EVAL_MODE[self.args.model_mode]}", - "--temperature 0", - f"--dump_logits_path {self.args.artifact}/{dump_logits_path}", - f"--tokenized_prompt {input_file_name}", - ] - ) - subprocess.run( - runner_cmd, - shell=True, - executable="/bin/bash", - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - post_process() - - else: - runner_cmd = " ".join( - [ - f"cd {self.workspace} &&", - "./qnn_llama_runner", - f"--decoder_model_version {DECODER_MODEL_VERSION[self.args.decoder_model]}", - f"--tokenizer_path {os.path.basename(self.runtime_tokenizer_path)}", - f"--model_path {os.path.basename(self.pte_path)}", - f"--seq_len {self.max_seq_length}", - f"--output_path {outputs_path}", - f"--performance_output_path {performance_output_path}", - f"--window {self.args.window}", - f"--gcap {self.args.gcap}", - f"--ngram {self.args.ngram}", - f"--eval_mode {EVAL_MODE[self.args.model_mode]}", - "--temperature 0", - f"--dump_logits_path {dump_logits_path}", - f"--tokenized_prompt {os.path.basename(input_file_name)}", - "--shared_buffer", - ] - ) - - self.adb.push(inputs=[], files=[input_file_name], init_env=False) - self.adb.execute(custom_runner_cmd=runner_cmd) - self.adb.pull(output_path=self.output_dir, callback=post_process) - return output_tensor_list[0] + assert output_vocab_size is not None, "Couldn't find the vocab size" + assert pte_max_seq_len is not None, "Couldn't find the max_seq_len from pte" + meta_info = { + "output_vocab_size": output_vocab_size, + "pte_max_seq_len": pte_max_seq_len, + "logits_scale": logits_scale, + "logits_zero_point": logits_zero_point, + "kv_io_bit_width": kv_io_bit_width, + } + return meta_info def smart_mask_updater( diff --git a/examples/qualcomm/oss_scripts/llama/eval_llama_qnn.py b/examples/qualcomm/oss_scripts/llama/eval_llama_qnn.py index 46a7e26d58a..0c37ff52953 100644 --- a/examples/qualcomm/oss_scripts/llama/eval_llama_qnn.py +++ b/examples/qualcomm/oss_scripts/llama/eval_llama_qnn.py @@ -306,6 +306,7 @@ def eager_eval_quanty( def eval_llm(args): tokenizer = prepare_tokenizer(args) model, prefill_config = prepare_model(args) + get_example_inputs = model.get_example_inputs model, config, inputs, scales_state_dict = prequant_algorithm( model, prefill_config, args ) @@ -319,7 +320,7 @@ def eval_llm(args): ) quantizer = make_custom_quantizer( - quant_dtype, args.range_setting, custom_annotations, args.quant_linear_only + quant_dtype, custom_annotations, args.quant_linear_only ) with torch.no_grad(): @@ -371,7 +372,7 @@ def eval_llm(args): logging.info("Evaluation of QDQ model:") graph_module_inference( use_kv_cache=False, - get_example_inputs=lambda use_kv_cache=False: inputs, + example_input=get_example_inputs(), module=model, tokenizer=tokenizer, ar_len=args.max_seq_len, diff --git a/examples/qualcomm/oss_scripts/llama/llama.py b/examples/qualcomm/oss_scripts/llama/llama.py index bddebdc6387..e76eebd53ce 100755 --- a/examples/qualcomm/oss_scripts/llama/llama.py +++ b/examples/qualcomm/oss_scripts/llama/llama.py @@ -7,16 +7,15 @@ # TODO: reenable pyre after fixing the issues # pyre-ignore-all-errors -import getpass import json import logging import os -import subprocess import sys from multiprocessing.connection import Client from typing import Dict import torch + from executorch.backends.qualcomm.utils.utils import ( generate_htp_compiler_spec, generate_qnn_executorch_compiler_spec, @@ -31,26 +30,29 @@ AUDIO_ENCODER, DECODER_GRAPH_NAMES, EVAL_MODE, + PROMPT_EVAL, + SQNR_EVAL, + TASKS_EVAL, TEXT_DECODER, TEXT_EMBEDDING, TEXT_EMBEDDING_GRAPH_NAMES, TEXT_ENCODER, VISION_ENCODER, - VISION_ENCODER_INPUT_FILENAME, ) -from executorch.examples.qualcomm.oss_scripts.llama.decoder_utils import ( - QnnRunnerEvalWrapper, +from executorch.examples.qualcomm.oss_scripts.llama.decoder_runtime_evaluator import ( + DefaultEval, + SqnrEval, + TaskEval, ) + from executorch.examples.qualcomm.oss_scripts.llama.tokenizer import TokenizerWrapper from executorch.examples.qualcomm.oss_scripts.llama.wrappers import ( MultiModalManager, next_power_of_two, ) -from executorch.examples.qualcomm.utils import ( - make_output_dir, - setup_common_args_and_variables, - SimpleADB, -) +from executorch.examples.qualcomm.utils import setup_common_args_and_variables + + try: from lm_eval.evaluator import simple_evaluate @@ -59,6 +61,16 @@ "Please install the llm eval dependency via examples/models/llama/install_requirements.sh" ) +from executorch.examples.qualcomm.utils import setup_common_args_and_variables +from executorch.exir.capture._config import ExecutorchBackendConfig +from executorch.exir.dialects._ops import ops as exir_ops +from executorch.exir.passes.memory_planning_pass import MemoryPlanningPass +from executorch.extension.llm.custom_ops import model_sharding +from executorch.extension.llm.export.builder import DType +from pytorch_tokenizers import get_tokenizer, TiktokenTokenizer +from pytorch_tokenizers.llama2c import Llama2cTokenizer as SentencePieceTokenizer + + sys.setrecursionlimit(4096) FORMAT = "[%(levelname)s %(asctime)s %(filename)s:%(lineno)s] %(message)s" logging.basicConfig(level=logging.INFO, format=FORMAT) @@ -92,7 +104,7 @@ def compile( TEXT_DECODER: None, } is_modality = False - # compile spec for multimodlity encoder + # compile spec for multimodality encoder for modality in compile_specs: if not hasattr(decoder_model_config, modality): continue @@ -148,6 +160,7 @@ def inference( pte_filenames: Dict[str, str], runtime_tokenizer_path, tokenizer, + chat_template, ): assert args.model_mode in EVAL_MODE, f"Unknown model_mode: {args.model_mode}." @@ -155,130 +168,15 @@ def inference( is_modality = hasattr(decoder_model_config, VISION_ENCODER) or hasattr( decoder_model_config, AUDIO_ENCODER ) - pte_path = ( f"{args.pre_gen_pte}/{pte_filenames[TEXT_DECODER]}.pte" if args.pre_gen_pte else f"{args.artifact}/{pte_filenames[TEXT_DECODER]}.pte" ) - # For decoder-only models, enable accuracy evaluation using perplexity - # TODO: Add support for multimodal accuracy evaluation (e.g., VLM) - if not is_modality and args.run_lm_eval: - # Generate the eval wrapper - eval_wrapper = QnnRunnerEvalWrapper( - args=args, - pte_path=pte_path, - tokenizer=tokenizer, - runtime_tokenizer_path=runtime_tokenizer_path, - ) - - # Evaluate the model - with torch.no_grad(): - eval_results = simple_evaluate( - model=eval_wrapper, - tasks=args.tasks, - num_fewshot=args.num_fewshot, - limit=args.limit, - ) - + # TODO: support multimodal runtime, we only check pte size for now + if is_modality: if args.ip and args.port != -1: - assert len(args.tasks) == 1, "CI currently supports 1 lm_eval task only." - match args.tasks[0]: - case "wikitext": - wiki_ppl = eval_results["results"][args.tasks[0]][ - "word_perplexity,none" - ] - pte_size = os.path.getsize(pte_path) - with Client((args.ip, args.port)) as conn: - conn.send( - json.dumps( - { - "wiki_ppl": wiki_ppl, - "pte_size": pte_size, - "inference_speed": eval_wrapper.inference_speed, - } - ) - ) - case "hellaswag": - acc_norm = eval_results["results"][args.tasks[0]]["acc_norm,none"] - pte_size = os.path.getsize(pte_path) - with Client((args.ip, args.port)) as conn: - conn.send( - json.dumps( - { - "acc_norm": acc_norm, - "pte_size": pte_size, - "inference_speed": eval_wrapper.inference_speed, - } - ) - ) - case _: - raise RuntimeError( - "CI currently supports [wikitext, hellaswag] only." - ) - - else: - for task, res in eval_results["results"].items(): - logging.info(f"{task}: {res}") - return - - workspace = f"/data/local/tmp/{getpass.getuser()}/executorch/single_llama" - - # collect output data - output_data_folder = f"{args.artifact}/outputs" - make_output_dir(output_data_folder) - outputs = [] - - def post_process(): - with open(f"{args.artifact}/outputs/outputs.txt", "r") as f: - outputs.append(f.read()) - - seq_len = args.max_seq_len - multi_prompts = " ".join([f'--prompt "{prompt}"' for prompt in args.prompt]) - lookahead_args = " ".join( - [ - f"--window {args.window}", - f"--gcap {args.gcap}", - f"--ngram {args.ngram}", - ] - ) - runner_args = " ".join( - [ - multi_prompts, - f"--eval_mode {EVAL_MODE[args.model_mode]}", - f"--temperature {args.temperature}", - f"--system_prompt '{args.system_prompt}'", - lookahead_args if args.model_mode == "lookahead" else "", - ] - ) - - runner_cmd = "" - performance_output_path = "outputs/inference_speed.txt" - if args.enable_x86_64: - # x86 emulator is intended for CI and not performance. Check only the first few tokens. - seq_len = min(seq_len, 16) - - qnn_sdk = os.getenv("QNN_SDK_ROOT") - target = "x86_64-linux-clang" - if not is_modality: - runner_cmd = " ".join( - [ - f"export LD_LIBRARY_PATH={qnn_sdk}/lib/{target}/:{args.build_folder}/lib &&", - f"./{args.build_folder}/examples/qualcomm/oss_scripts/llama/qnn_llama_runner", - f"--decoder_model_version {decoder_model_config.decoder_model_version}", - f"--tokenizer_path {runtime_tokenizer_path}", - f"--model_path {pte_path}", - f"--seq_len {seq_len}", - f"--output_path {args.artifact}/outputs/outputs.txt", - f"--performance_output_path {args.artifact}/{performance_output_path}", - runner_args, - ] - ) - else: - # x86 emulator is intended for CI and not performance. Check only the first few tokens. - # For multimodal models, use 128 tokens (vs 16 for text-only) due to longer sequence length required for modality embeddings. - seq_len = min(seq_len, 128) encoder_pte_path = ( f"{args.pre_gen_pte}/{pte_filenames[VISION_ENCODER]}.pte" if args.pre_gen_pte @@ -289,174 +187,105 @@ def post_process(): if args.pre_gen_pte else f"{args.artifact}/{pte_filenames[TEXT_EMBEDDING]}.pte" ) - runner_cmd = " ".join( - [ - f"export LD_LIBRARY_PATH={qnn_sdk}/lib/{target}/:{args.build_folder}/lib &&", - f"./{args.build_folder}/examples/qualcomm/oss_scripts/llama/qnn_multimodal_runner", - f"--decoder_model_version {decoder_model_config.decoder_model_version}", - f"--tokenizer_path {runtime_tokenizer_path}", - f"--decoder_path {pte_path}", - f"--encoder_path {encoder_pte_path}", - f"--embedding_path {text_embedding_pte_path}", - f"--image_path {args.artifact}/{VISION_ENCODER_INPUT_FILENAME}.raw", - f"--seq_len {seq_len}", - f"--output_path {args.artifact}/outputs/outputs.txt", - f"--performance_output_path {args.artifact}/{performance_output_path}", - runner_args, - ] + # Prepare validation results for CI system + validation_results = { + "pte_size": os.path.getsize(pte_path), + "encoder_pte_size": os.path.getsize(encoder_pte_path), + "text_embedding_pte_size": os.path.getsize(text_embedding_pte_path), + } + with Client((args.ip, args.port)) as conn: + conn.send(json.dumps(validation_results)) + else: + logging.info("Multimodal runtime support is currently under development.") + logging.info( + "Detected vision/audio encoder in model config. Exiting process safely." ) + exit(0) + return None - subprocess.run( - runner_cmd, - shell=True, - executable="/bin/bash", - capture_output=True, + eval_results = { + "pte_size": os.path.getsize(pte_path), + } + if PROMPT_EVAL in args.eval_methods: + prompt_evaluator = DefaultEval( + args=args, pte_path=pte_path, runtime_tokenizer_path=runtime_tokenizer_path ) - post_process() - else: - if not is_modality: - runner_cmd = " ".join( - [ - f"cd {workspace} &&", - f"./qnn_llama_runner", - f"--decoder_model_version {decoder_model_config.decoder_model_version}", - f"--tokenizer_path {os.path.basename(runtime_tokenizer_path)}", - f"--model_path {pte_filenames[TEXT_DECODER]}.pte", - f"--seq_len {seq_len}", - "--output_path outputs/outputs.txt", - f"--performance_output_path {performance_output_path}", - "--shared_buffer", - runner_args, - ] - ) - else: - encoder_pte_path = ( - f"{args.pre_gen_pte}/{pte_filenames[VISION_ENCODER]}.pte" - if args.pre_gen_pte - else f"{args.artifact}/{pte_filenames[VISION_ENCODER]}.pte" - ) - text_embedding_pte_path = ( - f"{args.pre_gen_pte}/{pte_filenames[TEXT_EMBEDDING]}.pte" - if args.pre_gen_pte - else f"{args.artifact}/{pte_filenames[TEXT_EMBEDDING]}.pte" - ) - runner_cmd = " ".join( - [ - f"cd {workspace} &&", - f"./qnn_multimodal_runner", - f"--decoder_model_version {decoder_model_config.decoder_model_version}", - f"--tokenizer_path {os.path.basename(runtime_tokenizer_path)}", - f"--decoder_path {pte_filenames[TEXT_DECODER]}.pte", - f"--encoder_path {pte_filenames[VISION_ENCODER]}.pte", - f"--embedding_path {pte_filenames[TEXT_EMBEDDING]}.pte", - f"--image_path {VISION_ENCODER_INPUT_FILENAME}.raw", - f"--seq_len {seq_len}", - "--output_path outputs/outputs.txt", - f"--performance_output_path {performance_output_path}", - "--shared_buffer", - runner_args, - ] + output_prompt = prompt_evaluator.run(prompt=args.prompt) + eval_results.update( + { + "inference_speed": prompt_evaluator.inference_speed, + "result": output_prompt, + } + ) + for idx, output in enumerate(output_prompt): + logging.info(f"Device Inference Results[{idx}]:\n{output}") + + if SQNR_EVAL in args.eval_methods: + tokenizer_wrapper = TokenizerWrapper( + args, + decoder_model_config, + ) + prompt = ( + tokenizer_wrapper.apply_prompt_template( + chat_template, args.prompt[0], args.system_prompt ) + if chat_template is not None + else args.prompt[0] + ) + multi_modal_mgr = MultiModalManager( + control_args=args, config=decoder_model_config + ) + source_model = multi_modal_mgr.text_decoder.decode.decoder + sqnr_evaluator = SqnrEval( + source_model=source_model, + example_input=source_model.get_example_inputs(), + args=args, + pte_path=pte_path, + tokenizer=tokenizer, + runtime_tokenizer_path=runtime_tokenizer_path, + ) + sqnr = sqnr_evaluator.run(prompt=prompt) + logging.info(f"SQNR Eval Score: {sqnr}") + eval_results.update( + { + "sqnr": sqnr, + "inference_speed": sqnr_evaluator.inference_speed, + } + ) - adb = SimpleADB( - qnn_sdk=os.getenv("QNN_SDK_ROOT"), - build_path=f"{args.build_folder}", - pte_path=( - pte_path - if not is_modality - else [pte_path, encoder_pte_path, text_embedding_pte_path] - ), - workspace=workspace, - device_id=args.device, - host_id=args.host, - soc_model=args.model, - shared_buffer=True, - target=args.target, - runner=( - f"examples/qualcomm/oss_scripts/llama/qnn_llama_runner" - if not is_modality - else f"examples/qualcomm/oss_scripts/llama/qnn_multimodal_runner" - ), + if TASKS_EVAL in args.eval_methods: + # Generate the eval wrapper + ppl_evaluator = TaskEval( + args=args, + pte_path=pte_path, + tokenizer=tokenizer, + runtime_tokenizer_path=runtime_tokenizer_path, ) + ppl_eval_result = ppl_evaluator.run() + eval_results["inference_speed"] = ppl_evaluator.inference_speed - # No pregen inputs, input_list is not required - if not args.skip_push: - # Always use image from artifact folder since that's where it's saved during preprocessing - # regardless of whether pre_gen_pte is used (pre_gen_pte only applies to .pte model files) - image_path = f"{args.artifact}/{VISION_ENCODER_INPUT_FILENAME}.raw" - adb.push( - inputs=[], - files=[runtime_tokenizer_path] + ([image_path] if is_modality else []), - ) - adb.execute(custom_runner_cmd=runner_cmd) - adb.pull(output_path=args.artifact, callback=post_process) + for task, res in ppl_eval_result["results"].items(): + match task: + case "wikitext": + wiki_ppl = ppl_eval_result["results"][task]["word_perplexity,none"] + eval_results["wiki_ppl"] = wiki_ppl + case "hellaswag": + acc_norm = ppl_eval_result["results"][task]["acc_norm,none"] + eval_results["acc_norm"] = acc_norm + case _: + if args.ip and args.port != -1: + raise RuntimeError( + "CI currently supports [wikitext, hellaswag] only." + ) + logging.info(f"{task}: {res}") if args.ip and args.port != -1: - inference_speed = 0 - with open( - f"{os.path.abspath(args.artifact)}/{performance_output_path}", "r" - ) as f: - inference_speed = float(f.read()) - - # Prepare validation results for CI system - validation_results = { - "result": outputs, - "inference_speed": inference_speed, - "pte_size": os.path.getsize(pte_path), - } - - # Add multimodal-specific metrics if applicable - if is_modality: - validation_results.update( - { - "encoder_pte_size": os.path.getsize(encoder_pte_path), - "text_embedding_pte_size": os.path.getsize(text_embedding_pte_path), - } - ) - with Client((args.ip, args.port)) as conn: - conn.send(json.dumps(validation_results)) - else: - for idx, output in enumerate(outputs): - logging.info(f"Results[{idx}]:\n{output}") - - -def _build_tasks_parser(parser): - parser.add_argument( - "--run_lm_eval", - help="If enabled, this will use the tasks provided under args.tasks to calibrate the model", - action="store_true", - default=False, - ) - - parser.add_argument( - "--tasks", - nargs="+", - type=str, - default=None, - help="list of lm-eluther tasks to evaluate usage: --tasks task1 task2", - ) - - parser.add_argument( - "--limit", - type=int, - default=1, - help="number of samples to evalulate. If not set, evaluate all samples", - ) - parser.add_argument( - "--num_fewshot", - type=int, - default=None, - metavar="N", - help="Number of examples in few-shot context", - ) - - return parser + conn.send(json.dumps(eval_results)) def _build_parser(): parser = setup_common_args_and_variables() - parser = _build_tasks_parser(parser) parser.add_argument( "-a", "--artifact", @@ -588,6 +417,40 @@ def _build_parser(): default=None, type=str, ) + + parser.add_argument( + "--eval_methods", + choices=[PROMPT_EVAL, TASKS_EVAL, SQNR_EVAL], + nargs="+", + default=[PROMPT_EVAL], + help="Choose eval methods(default: prompt_eval). Users can provide more than 1 eval methods. For example: --eval_methods tasks_eval sqnr_eval." + "Following eval methods are supported:" + "1) prompt_eval: Model will generate the output response based on the provided prompt through the flag --prompt." + "2) tasks_eval: This will eval the tasks provided through the flag --tasks." + "3) sqnr_eval: This will eval the sqnr between between QNN's output logit V.S. Static Llama nn.Module's output logit. Eval is based on the provided prompt through the --prompt flag. Please note that sqnr will only eval the prompt's logit but not the new generated token's logit.", + ) + + parser.add_argument( + "--tasks", + nargs="+", + type=str, + default=None, + help="list of lm-eluther tasks to evaluate usage: --tasks task1 task2", + ) + + parser.add_argument( + "--limit", + type=int, + default=1, + help="number of samples to evalulate. If not set, evaluate all samples", + ) + parser.add_argument( + "--num_fewshot", + type=int, + default=None, + metavar="N", + help="Number of examples in few-shot context", + ) parser.add_argument("-v", "--verbose", action="store_true") @@ -597,9 +460,14 @@ def _build_parser(): def export_llama(args) -> None: if args.compile_only and args.pre_gen_pte: raise RuntimeError("Cannot set both compile_only and pre_gen_pte as true") - if args.run_lm_eval and args.model_mode != "kv": - raise RuntimeError("Eval device perplexity is only supported for KV mode") - if args.run_lm_eval and args.tasks is None: + if (TASKS_EVAL or SQNR_EVAL) in args.eval_methods and args.model_mode not in { + "kv", + "hybrid", + }: + raise RuntimeError( + "Eval device perplexity is only supported for KV mode. Hybrid mode will only use KV mode when evaluating tasks/sqnr." + ) + if TASKS_EVAL in args.eval_methods and args.tasks is None: raise RuntimeError("Please provide --tasks to eval perplexity") assert ( args.decoder_model in SUPPORTED_LLM_MODELS @@ -664,7 +532,12 @@ def export_llama(args) -> None: if args.pre_gen_pte: inference( - args, decoder_model_config, pte_filenames, runtime_tokenizer_path, tokenizer + args, + decoder_model_config, + pte_filenames, + runtime_tokenizer_path, + tokenizer, + chat_template, ) print(f"Finish the running pre_gen_pte from {args.pre_gen_pte}") return @@ -700,7 +573,12 @@ def export_llama(args) -> None: calibration_data, ) inference( - args, decoder_model_config, pte_filenames, runtime_tokenizer_path, tokenizer + args, + decoder_model_config, + pte_filenames, + runtime_tokenizer_path, + tokenizer, + chat_template, ) diff --git a/examples/qualcomm/oss_scripts/llama/masking_utils.py b/examples/qualcomm/oss_scripts/llama/masking_utils.py index ea68f89276a..232b885247f 100644 --- a/examples/qualcomm/oss_scripts/llama/masking_utils.py +++ b/examples/qualcomm/oss_scripts/llama/masking_utils.py @@ -194,7 +194,7 @@ def smart_mask_update(self, pos, n_updates, lade_pos_offset): After 2nd update (e.g., pos=5, n_updates=5, sliding_window=3): - Sliding window shifts again, masking older positions and activate new postion. + Sliding window shifts again, masking older positions and activate new position. 0 ○ ○ ○ ○ ○ ○ ○ ○ ● ● ● ○ ○ ○ ○ 1 ○ ○ ○ ○ ○ ○ ○ ○ ○ ● ● ● ○ ○ ○ diff --git a/examples/qualcomm/oss_scripts/llama/range_setting_pt2e.py b/examples/qualcomm/oss_scripts/llama/range_setting_pt2e.py index d5c68606f60..02c312bc89e 100644 --- a/examples/qualcomm/oss_scripts/llama/range_setting_pt2e.py +++ b/examples/qualcomm/oss_scripts/llama/range_setting_pt2e.py @@ -22,12 +22,6 @@ PerChannelParamObserver, ) -from executorch.backends.qualcomm.quantizer.qconfig import ( - _derived_bias_quant_spec, - QuantizationConfig, -) -from executorch.backends.qualcomm.quantizer.quantizer import QuantDtype - from executorch.examples.qualcomm.utils import make_quantizer from torchao.prototype.quantization.module_swap import ( @@ -46,7 +40,6 @@ ) from torchao.quantization.pt2e import MinMaxObserver, PerChannelMinMaxObserver -from torchao.quantization.pt2e.quantizer import QuantizationSpec class WrappedLlamaModel(nn.Module): @@ -201,49 +194,14 @@ def compute_scales(model, data, weight_bits, act_bits, num_points=1600): return scales_state_dict -def make_custom_quantizer( - quant_dtype, range_setting=None, custom_annotations=(), linear_only=False -): +def make_custom_quantizer(quant_dtype, custom_annotations=(), linear_only=False): quantizer = make_quantizer( quant_dtype=quant_dtype, per_channel_conv=True, per_channel_linear=True, act_observer=MinMaxObserver, ) - if range_setting in ("mse_weight_only", "mse_with_act_loss"): - assert ( - quant_dtype != QuantDtype.use_16a4w_block - ), "Range setting only supported for per-channel quantization" - if range_setting == "mse_weight_only": - observer = PerChannelMSEObserver.with_args( - **{"steps": 1600, "use_mse": True} - ) - else: - observer = PerChannelFixedQParamsObserver.with_args(**{"eps": 2**-12}) - weight_dtype = ( - torch.int4 - if quant_dtype in (QuantDtype.use_16a4w, QuantDtype.use_16a4w_block) - else torch.int8 - ) - per_channel_q_config = quantizer.default_quant_config.quant_config - weight_qspec = QuantizationSpec( - dtype=torch.int8 if weight_dtype == torch.int4 else weight_dtype, - quant_min=( - -7 if weight_dtype == torch.int4 else torch.iinfo(weight_dtype).min + 1 - ), - quant_max=( - 7 if weight_dtype == torch.int4 else torch.iinfo(weight_dtype).max - ), - qscheme=torch.per_channel_symmetric, - ch_axis=0, - observer_or_fake_quant_ctr=observer, - ) - quantizer.default_quant_config.per_channel_quant_config = QuantizationConfig( - input_activation=per_channel_q_config.input_activation, - output_activation=per_channel_q_config.output_activation, - weight=weight_qspec, - bias=_derived_bias_quant_spec, - ) + if linear_only: all_keys = set(OP_ANNOTATOR.keys()) conv_keys = { diff --git a/examples/qualcomm/oss_scripts/llama/runner/runner.cpp b/examples/qualcomm/oss_scripts/llama/runner/runner.cpp index aae61cec245..043447b13bd 100644 --- a/examples/qualcomm/oss_scripts/llama/runner/runner.cpp +++ b/examples/qualcomm/oss_scripts/llama/runner/runner.cpp @@ -165,8 +165,8 @@ Error Runner::load() { std::vector method_names; switch (eval_mode_) { case EvalMode::kKVCached: - prompt_processor_method_name = "forward"; - token_generator_method_name = "forward"; + prompt_processor_method_name = "kv_forward"; + token_generator_method_name = "kv_forward"; method_names.emplace_back(token_generator_method_name); break; case EvalMode::kHybrid: diff --git a/examples/qualcomm/oss_scripts/llama/tokenizer.py b/examples/qualcomm/oss_scripts/llama/tokenizer.py index 6bc75e3a984..b55cd61d616 100644 --- a/examples/qualcomm/oss_scripts/llama/tokenizer.py +++ b/examples/qualcomm/oss_scripts/llama/tokenizer.py @@ -92,7 +92,6 @@ def _from_hf(self): file.seek(0) json.dump(data, file, indent=4) file.truncate() - runtime_tokenizer_path = tokenizer_artifacts[-1] tokenizer = get_tokenizer(runtime_tokenizer_path, tokenizer_config) if self.decoder_model == "codegen2_1b": diff --git a/examples/qualcomm/oss_scripts/llama/wrappers.py b/examples/qualcomm/oss_scripts/llama/wrappers.py index 9f9c77b5c1e..f7e25c30f5b 100644 --- a/examples/qualcomm/oss_scripts/llama/wrappers.py +++ b/examples/qualcomm/oss_scripts/llama/wrappers.py @@ -462,7 +462,6 @@ def _get_model_instance(self) -> LlamaModel: **self._get_model_specific_kwargs(), ) # get example input - self.meta = decoder.get_metadata() self.example_input = decoder.get_example_inputs() self.get_example_inputs = decoder.get_example_inputs self.export_input = ( @@ -912,8 +911,7 @@ def compile(self, request: Request): # noqa: C901 data = request.method_data[TEXT_DECODER] models = [d for d in [self.decode, self.prefill] if d.decoder is not None] example_inputs = [m.export_input for m in models if m is not None] - # For backward compatibility, we keep the graph name as forward if we use kv mode for evaluation LLM models - graph_names = ["forward"] if len(models) == 1 else DECODER_GRAPH_NAMES + graph_names = DECODER_GRAPH_NAMES[: len(models)] # start lowering if self.apply_embedding: @@ -977,7 +975,7 @@ def compile(self, request: Request): # noqa: C901 if self.config.num_sharding > 1 and self.control_args.model_mode == "kv": # weight-sharing based context binaries cannot be opened in x86 host - update_spill_fill_size(edge_prog_mgr.exported_program()) + update_spill_fill_size(edge_prog_mgr.exported_program("kv_forward")) if self.control_args.verbose: for ep in edge_prog_mgr._edge_programs.values(): diff --git a/examples/qualcomm/oss_scripts/llm_utils/eval_decoder_model_qnn.py b/examples/qualcomm/oss_scripts/llm_utils/eval_decoder_model_qnn.py index df19519e847..461224e1ccf 100644 --- a/examples/qualcomm/oss_scripts/llm_utils/eval_decoder_model_qnn.py +++ b/examples/qualcomm/oss_scripts/llm_utils/eval_decoder_model_qnn.py @@ -171,7 +171,7 @@ def post_process(): result_logits.append(output_tensor) - self.adb.pull(output_path=self.output_dir, callback=post_process) + self.adb.pull(host_output_path=self.output_dir, callback=post_process) return torch.cat(result_logits, dim=1) diff --git a/examples/qualcomm/oss_scripts/maxvit_t.py b/examples/qualcomm/oss_scripts/maxvit_t.py index e4bf9cd9127..b1d6e8b8a74 100755 --- a/examples/qualcomm/oss_scripts/maxvit_t.py +++ b/examples/qualcomm/oss_scripts/maxvit_t.py @@ -202,7 +202,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/mobilevit_v1.py b/examples/qualcomm/oss_scripts/mobilevit_v1.py index b075f6d7288..a71334f05b8 100644 --- a/examples/qualcomm/oss_scripts/mobilevit_v1.py +++ b/examples/qualcomm/oss_scripts/mobilevit_v1.py @@ -125,7 +125,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/mobilevit_v2.py b/examples/qualcomm/oss_scripts/mobilevit_v2.py index d5f43e7bd6c..fe5245d4607 100644 --- a/examples/qualcomm/oss_scripts/mobilevit_v2.py +++ b/examples/qualcomm/oss_scripts/mobilevit_v2.py @@ -130,7 +130,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/moshi/mimi.py b/examples/qualcomm/oss_scripts/moshi/mimi.py index ebb4519fe10..6e0742094a1 100644 --- a/examples/qualcomm/oss_scripts/moshi/mimi.py +++ b/examples/qualcomm/oss_scripts/moshi/mimi.py @@ -195,7 +195,7 @@ def inference_mimi_encoder(args, encoder_inputs, encoder_pte_filename): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) encoder_predictions = [] for i in range(len(encoder_inputs)): @@ -371,7 +371,7 @@ def inference_static_mimi_decoder( output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) num_chunks = len(encoded_results) shape = num_chunks * pcm_chunk_size diff --git a/examples/qualcomm/oss_scripts/pvt.py b/examples/qualcomm/oss_scripts/pvt.py index dd564f7c043..22a7d78d61f 100644 --- a/examples/qualcomm/oss_scripts/pvt.py +++ b/examples/qualcomm/oss_scripts/pvt.py @@ -97,7 +97,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/qwen2_5/qwen2_5.py b/examples/qualcomm/oss_scripts/qwen2_5/qwen2_5.py index 2b4e8dd0b01..cb6f3430235 100644 --- a/examples/qualcomm/oss_scripts/qwen2_5/qwen2_5.py +++ b/examples/qualcomm/oss_scripts/qwen2_5/qwen2_5.py @@ -162,7 +162,7 @@ def post_process(): adb.push(inputs=[], input_list="", files=[tokenizer_json_path]) adb.execute(custom_runner_cmd=runner_cmd) - adb.pull(output_path=args.artifact, callback=post_process) + adb.pull(host_output_path=args.artifact, callback=post_process) if args.ip and args.port != -1: with Client((args.ip, args.port)) as conn: diff --git a/examples/qualcomm/oss_scripts/regnet.py b/examples/qualcomm/oss_scripts/regnet.py index b8844a5dc7a..da34052a2ce 100644 --- a/examples/qualcomm/oss_scripts/regnet.py +++ b/examples/qualcomm/oss_scripts/regnet.py @@ -97,7 +97,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/retinanet.py b/examples/qualcomm/oss_scripts/retinanet.py index c087a9cbef8..dd17d2f61ab 100644 --- a/examples/qualcomm/oss_scripts/retinanet.py +++ b/examples/qualcomm/oss_scripts/retinanet.py @@ -268,7 +268,7 @@ def main(args): # collect output data output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) predictions, classes = [], [n_classes, n_coord_of_bbox] for i in range(data_num): diff --git a/examples/qualcomm/oss_scripts/roberta.py b/examples/qualcomm/oss_scripts/roberta.py index 92dac72140f..8a001ba13a5 100644 --- a/examples/qualcomm/oss_scripts/roberta.py +++ b/examples/qualcomm/oss_scripts/roberta.py @@ -138,7 +138,7 @@ def main(args): golden = module(*sample_input)[0] adb.push(inputs=[sample_input]) adb.execute() - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) print(f"input: {tokenizer.batch_decode(sample_input[0])}") print(f"golden output: {tokenizer.batch_decode(golden.argmax(axis=2))}") @@ -150,7 +150,7 @@ def main(args): # accuracy analysis adb.push(inputs=inputs) adb.execute() - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) goldens, predictions = [], [] for i in range(len(inputs)): indice = [i for i, x in enumerate(targets[i]) if x != -100] diff --git a/examples/qualcomm/oss_scripts/squeezenet.py b/examples/qualcomm/oss_scripts/squeezenet.py index 453b3d146eb..80a7afbde66 100644 --- a/examples/qualcomm/oss_scripts/squeezenet.py +++ b/examples/qualcomm/oss_scripts/squeezenet.py @@ -88,7 +88,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/ssd300_vgg16.py b/examples/qualcomm/oss_scripts/ssd300_vgg16.py index 89127a8ce6a..20b1ef5a502 100644 --- a/examples/qualcomm/oss_scripts/ssd300_vgg16.py +++ b/examples/qualcomm/oss_scripts/ssd300_vgg16.py @@ -237,7 +237,7 @@ def post_process(): print("\nMean Average Precision (mAP): %.3f" % mAP) pp.pprint(APs) - adb.pull(output_path=args.artifact, callback=post_process) + adb.pull(host_output_path=args.artifact, callback=post_process) if __name__ == "__main__": diff --git a/examples/qualcomm/oss_scripts/swin_transformer.py b/examples/qualcomm/oss_scripts/swin_transformer.py index aca8d706691..a08de41691c 100644 --- a/examples/qualcomm/oss_scripts/swin_transformer.py +++ b/examples/qualcomm/oss_scripts/swin_transformer.py @@ -150,7 +150,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/swin_v2_t.py b/examples/qualcomm/oss_scripts/swin_v2_t.py index 7307237fbd5..af08d0cb739 100755 --- a/examples/qualcomm/oss_scripts/swin_v2_t.py +++ b/examples/qualcomm/oss_scripts/swin_v2_t.py @@ -143,7 +143,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/t5/t5.py b/examples/qualcomm/oss_scripts/t5/t5.py index f85953ac2cc..d7cc1fffe97 100644 --- a/examples/qualcomm/oss_scripts/t5/t5.py +++ b/examples/qualcomm/oss_scripts/t5/t5.py @@ -338,7 +338,7 @@ def post_process(): files=[runtime_tokenizer_path], ) adb.execute(custom_runner_cmd=runner_cmd) - adb.pull(output_path=args.artifact, callback=post_process) + adb.pull(host_output_path=args.artifact, callback=post_process) result = Seq2SeqLMExportableModulePipeline.evaluate_with_ground_truth( tokenizer, outputs, targets, evaluate_squad diff --git a/examples/qualcomm/oss_scripts/vit_b_16.py b/examples/qualcomm/oss_scripts/vit_b_16.py index a8d361daab5..988d5e662bd 100755 --- a/examples/qualcomm/oss_scripts/vit_b_16.py +++ b/examples/qualcomm/oss_scripts/vit_b_16.py @@ -93,7 +93,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/oss_scripts/whisper/whisper.py b/examples/qualcomm/oss_scripts/whisper/whisper.py index 6d94c823d4a..ec62012cacb 100644 --- a/examples/qualcomm/oss_scripts/whisper/whisper.py +++ b/examples/qualcomm/oss_scripts/whisper/whisper.py @@ -475,7 +475,7 @@ def post_process(): adb.push(inputs=inputs, files=[tokenizer_json]) adb.execute(custom_runner_cmd=runner_cmd) - adb.pull(output_path=args.artifact, callback=post_process) + adb.pull(host_output_path=args.artifact, callback=post_process) wer = eval_metric(outputs, target) if args.ip and args.port != -1: diff --git a/examples/qualcomm/qaihub_scripts/stable_diffusion/qaihub_stable_diffusion.py b/examples/qualcomm/qaihub_scripts/stable_diffusion/qaihub_stable_diffusion.py index ca3e3fd4c4a..400abd056da 100644 --- a/examples/qualcomm/qaihub_scripts/stable_diffusion/qaihub_stable_diffusion.py +++ b/examples/qualcomm/qaihub_scripts/stable_diffusion/qaihub_stable_diffusion.py @@ -336,7 +336,7 @@ def post_process_vae(): np.fromfile(f, dtype=np.float32).reshape(1, 512, 512, 3) ) - adb.pull(output_path=args.artifact, callback=post_process_vae) + adb.pull(host_output_path=args.artifact, callback=post_process_vae) if args.fix_latents: broadcast_ut_result(output_image, seed) diff --git a/examples/qualcomm/scripts/deeplab_v3.py b/examples/qualcomm/scripts/deeplab_v3.py index 54fa2e69e94..541efb7b402 100755 --- a/examples/qualcomm/scripts/deeplab_v3.py +++ b/examples/qualcomm/scripts/deeplab_v3.py @@ -161,7 +161,7 @@ def post_process(): output = output.reshape(output_shape) output.argmax(0).astype(np.uint8).tofile(filename) - adb.pull(output_path=args.artifact, callback=post_process) + adb.pull(host_output_path=args.artifact, callback=post_process) # segmentation metrics predictions = [] diff --git a/examples/qualcomm/scripts/edsr.py b/examples/qualcomm/scripts/edsr.py index 0f80666a7fb..efb6c36e92d 100755 --- a/examples/qualcomm/scripts/edsr.py +++ b/examples/qualcomm/scripts/edsr.py @@ -183,7 +183,7 @@ def post_process(): ) cnt += 1 - adb.pull(output_path=args.artifact, callback=post_process) + adb.pull(host_output_path=args.artifact, callback=post_process) psnr_list = [] ssim_list = [] diff --git a/examples/qualcomm/scripts/inception_v3.py b/examples/qualcomm/scripts/inception_v3.py index 599c0602237..28ae35bed25 100755 --- a/examples/qualcomm/scripts/inception_v3.py +++ b/examples/qualcomm/scripts/inception_v3.py @@ -92,7 +92,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/scripts/inception_v4.py b/examples/qualcomm/scripts/inception_v4.py index aa3da09407a..ac29e5af408 100755 --- a/examples/qualcomm/scripts/inception_v4.py +++ b/examples/qualcomm/scripts/inception_v4.py @@ -90,7 +90,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/scripts/mobilebert_fine_tune.py b/examples/qualcomm/scripts/mobilebert_fine_tune.py index 19a0f50ef0d..97e6fad3cbc 100755 --- a/examples/qualcomm/scripts/mobilebert_fine_tune.py +++ b/examples/qualcomm/scripts/mobilebert_fine_tune.py @@ -301,7 +301,7 @@ def calibrator(gm): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # get torch cpu result cpu_preds, true_vals = evaluate(model, data_val) diff --git a/examples/qualcomm/scripts/mobilenet_v2.py b/examples/qualcomm/scripts/mobilenet_v2.py index 2d86f4de5d2..93885b5768a 100755 --- a/examples/qualcomm/scripts/mobilenet_v2.py +++ b/examples/qualcomm/scripts/mobilenet_v2.py @@ -91,7 +91,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/scripts/mobilenet_v3.py b/examples/qualcomm/scripts/mobilenet_v3.py index eadd66917ed..ea37f450f76 100644 --- a/examples/qualcomm/scripts/mobilenet_v3.py +++ b/examples/qualcomm/scripts/mobilenet_v3.py @@ -96,7 +96,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/scripts/torchvision_vit.py b/examples/qualcomm/scripts/torchvision_vit.py index ed8dbb792c4..c26ac5d7d75 100755 --- a/examples/qualcomm/scripts/torchvision_vit.py +++ b/examples/qualcomm/scripts/torchvision_vit.py @@ -130,7 +130,7 @@ def main(args): output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/scripts/wav2letter.py b/examples/qualcomm/scripts/wav2letter.py index 7fddb48bfb7..8bf22bae266 100644 --- a/examples/qualcomm/scripts/wav2letter.py +++ b/examples/qualcomm/scripts/wav2letter.py @@ -168,7 +168,7 @@ def main(args): # collect output data output_data_folder = f"{args.artifact}/outputs" make_output_dir(output_data_folder) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) predictions = [] for i in range(data_num): diff --git a/examples/qualcomm/util_scripts/qnn_intermediate_debugger_demo.py b/examples/qualcomm/util_scripts/qnn_intermediate_debugger_demo.py index 727c94900ca..a02ed60cf83 100644 --- a/examples/qualcomm/util_scripts/qnn_intermediate_debugger_demo.py +++ b/examples/qualcomm/util_scripts/qnn_intermediate_debugger_demo.py @@ -170,7 +170,7 @@ def validate_intermediate_tensor(): args.artifact, args.artifact, callback=validate_intermediate_tensor ) - adb.pull(output_path=args.artifact) + adb.pull(host_output_path=args.artifact) # top-k analysis predictions = [] diff --git a/examples/qualcomm/utils.py b/examples/qualcomm/utils.py index d550a2a14b3..e28a727c246 100755 --- a/examples/qualcomm/utils.py +++ b/examples/qualcomm/utils.py @@ -247,8 +247,10 @@ def execute( ["shell", f"{qnn_executor_runner_cmds}"], output_callback=output_callback ) - def pull(self, output_path, callback=None): - self._adb(["pull", "-a", self.output_folder, output_path]) + def pull(self, host_output_path, device_output_path=None, callback=None): + if device_output_path is None: + device_output_path = self.output_folder + self._adb(["pull", "-a", device_output_path, host_output_path]) if callback: callback() From ebefd6c71e9f99e4a3832a2633c2492f3bc35afc Mon Sep 17 00:00:00 2001 From: winskuo-quic Date: Tue, 20 Jan 2026 12:58:50 +0800 Subject: [PATCH 2/4] CPU QDQ SQNR support --- .../oss_scripts/llama/decoder_constants.py | 3 ++ .../llama/decoder_runtime_evaluator.py | 2 +- examples/qualcomm/oss_scripts/llama/llama.py | 50 +++++++++++-------- .../qualcomm/oss_scripts/llama/wrappers.py | 12 ++++- 4 files changed, 45 insertions(+), 22 deletions(-) diff --git a/examples/qualcomm/oss_scripts/llama/decoder_constants.py b/examples/qualcomm/oss_scripts/llama/decoder_constants.py index e33fde461aa..caad81977ee 100644 --- a/examples/qualcomm/oss_scripts/llama/decoder_constants.py +++ b/examples/qualcomm/oss_scripts/llama/decoder_constants.py @@ -4,6 +4,9 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +# decode qdq file name +DECODE_QDQ_FILENAME = "decode_qdq.pt2" + # eval methods PROMPT_EVAL = "prompt_eval" TASKS_EVAL = "tasks_eval" diff --git a/examples/qualcomm/oss_scripts/llama/decoder_runtime_evaluator.py b/examples/qualcomm/oss_scripts/llama/decoder_runtime_evaluator.py index 158627d5a43..f3347b5a5a5 100644 --- a/examples/qualcomm/oss_scripts/llama/decoder_runtime_evaluator.py +++ b/examples/qualcomm/oss_scripts/llama/decoder_runtime_evaluator.py @@ -389,7 +389,7 @@ def run(self, prompt): self.inference_speed = output_performance_holder[0] sqnr = compute_error(golden_logits, output_logits_holder[0]) - return sqnr.item() + return sqnr.item(), golden_logits, output_logits_holder[0] class TaskEval(EvalBase): diff --git a/examples/qualcomm/oss_scripts/llama/llama.py b/examples/qualcomm/oss_scripts/llama/llama.py index e76eebd53ce..20ead0a1ace 100755 --- a/examples/qualcomm/oss_scripts/llama/llama.py +++ b/examples/qualcomm/oss_scripts/llama/llama.py @@ -28,6 +28,7 @@ from executorch.examples.qualcomm.oss_scripts.llama.dataset import DatasetBuilder from executorch.examples.qualcomm.oss_scripts.llama.decoder_constants import ( AUDIO_ENCODER, + DECODE_QDQ_FILENAME, DECODER_GRAPH_NAMES, EVAL_MODE, PROMPT_EVAL, @@ -51,24 +52,7 @@ next_power_of_two, ) from executorch.examples.qualcomm.utils import setup_common_args_and_variables - - - -try: - from lm_eval.evaluator import simple_evaluate -except ImportError: - raise ImportError( - "Please install the llm eval dependency via examples/models/llama/install_requirements.sh" - ) - -from executorch.examples.qualcomm.utils import setup_common_args_and_variables -from executorch.exir.capture._config import ExecutorchBackendConfig -from executorch.exir.dialects._ops import ops as exir_ops -from executorch.exir.passes.memory_planning_pass import MemoryPlanningPass -from executorch.extension.llm.custom_ops import model_sharding -from executorch.extension.llm.export.builder import DType -from pytorch_tokenizers import get_tokenizer, TiktokenTokenizer -from pytorch_tokenizers.llama2c import Llama2cTokenizer as SentencePieceTokenizer +from torchao.quantization.utils import compute_error sys.setrecursionlimit(4096) @@ -244,8 +228,8 @@ def inference( tokenizer=tokenizer, runtime_tokenizer_path=runtime_tokenizer_path, ) - sqnr = sqnr_evaluator.run(prompt=prompt) - logging.info(f"SQNR Eval Score: {sqnr}") + sqnr, golden_logits, _ = sqnr_evaluator.run(prompt=prompt) + logging.info(f"SQNR Eval Score between FP32 nn.Module and QNN: {sqnr}") eval_results.update( { "sqnr": sqnr, @@ -253,6 +237,32 @@ def inference( } ) + qdq_ep_path = ( + f"{args.pre_gen_pte}/{DECODE_QDQ_FILENAME}" + if args.pre_gen_pte + else f"{args.artifact}/{DECODE_QDQ_FILENAME}" + ) + if os.path.exists(qdq_ep_path): + qdq_ep = torch.export.load(qdq_ep_path) + qdq_sqnr_evaluator = SqnrEval( + source_model=qdq_ep.module(), + example_input=source_model.get_example_inputs(), + args=args, + pte_path=pte_path, + tokenizer=tokenizer, + runtime_tokenizer_path=runtime_tokenizer_path, + ) + qdq_sqnr, cpu_qdq_logits, _ = qdq_sqnr_evaluator.run(prompt=prompt) + eval_results["qdq_sqnr"] = qdq_sqnr + logging.info(f"SQNR Eval Score between CPU QDQ and QNN: {qdq_sqnr}") + logging.info( + f"SQNR Eval Score between FP32 nn.Module and CPU QDQ: {compute_error(golden_logits, cpu_qdq_logits).item()}" + ) + else: + logging.info( + f"Couldn't find saved qdq_ep under {qdq_ep_path}, skip eval sqnr for CPU QDQ." + ) + if TASKS_EVAL in args.eval_methods: # Generate the eval wrapper ppl_evaluator = TaskEval( diff --git a/examples/qualcomm/oss_scripts/llama/wrappers.py b/examples/qualcomm/oss_scripts/llama/wrappers.py index f7e25c30f5b..488ace64135 100644 --- a/examples/qualcomm/oss_scripts/llama/wrappers.py +++ b/examples/qualcomm/oss_scripts/llama/wrappers.py @@ -51,6 +51,7 @@ ) from executorch.examples.qualcomm.oss_scripts.llama.decoder_constants import ( AUDIO_ENCODER, + DECODE_QDQ_FILENAME, DECODER_GRAPH_NAMES, TEXT_DECODER, TEXT_EMBEDDING, @@ -769,8 +770,17 @@ def quantize(self, request: Request): # noqa: C901 tok_embedding=self.tok_embedding, intermediate_outputs=image_embedding, ) - self.decoder = convert_pt2e(self.decoder) + + # Saving Decode QDQ Model EP for SQNR evaluation + if self.mode == self.Mode.DECODE: + qdq_ep = torch.export.export( + self.decoder, self.export_input, strict=True + ) + qdq_ep_path = f"{self.control_args.artifact}/{DECODE_QDQ_FILENAME}" + torch.export.save(qdq_ep, qdq_ep_path) + logging.info(f"QDQ EP saved to {qdq_ep_path}") + if self.apply_embedding: self.tok_embedding = convert_pt2e(self.tok_embedding) From af5fc79922418d9eb5b0f13ba3050895f3e9f686 Mon Sep 17 00:00:00 2001 From: winskuo-quic Date: Mon, 26 Jan 2026 23:27:30 +0800 Subject: [PATCH 3/4] Resolve rebase conflict --- .../llama/decoder_runtime_evaluator.py | 107 ++++++++++++++---- .../oss_scripts/llama/decoder_utils.py | 2 - examples/qualcomm/oss_scripts/llama/llama.py | 76 +++++++------ .../multimodal_runner/multimodal_runner.cpp | 4 +- 4 files changed, 125 insertions(+), 64 deletions(-) diff --git a/examples/qualcomm/oss_scripts/llama/decoder_runtime_evaluator.py b/examples/qualcomm/oss_scripts/llama/decoder_runtime_evaluator.py index f3347b5a5a5..fad53379314 100644 --- a/examples/qualcomm/oss_scripts/llama/decoder_runtime_evaluator.py +++ b/examples/qualcomm/oss_scripts/llama/decoder_runtime_evaluator.py @@ -4,13 +4,14 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +import argparse import getpass import logging import os import subprocess from abc import ABC, abstractmethod from functools import partial -from typing import Any, final, List, Optional, Union +from typing import Any, Callable, Dict, final, List, Optional, Union import numpy as np import torch @@ -18,6 +19,10 @@ from executorch.examples.qualcomm.oss_scripts.llama.decoder_constants import ( DECODER_MODEL_VERSION, EVAL_MODE, + TEXT_DECODER, + TEXT_EMBEDDING, + VISION_ENCODER, + VISION_ENCODER_INPUT_FILENAME, ) from executorch.examples.qualcomm.oss_scripts.llama.decoder_utils import ( INFERENCE_REGISTRY, @@ -71,16 +76,25 @@ def post_process_logits( class EvalBase(ABC): _adb: Optional[SimpleADB] = None # ADB shared across all instances - def __init__(self, args, pte_path, runtime_tokenizer_path): + def __init__( + self, + args: argparse.Namespace, + pte_paths: Dict, + runtime_tokenizer_path: str, + is_modality: bool, + ): self.args = args - self.pte_path = pte_path + self.pte_paths = pte_paths self.runtime_tokenizer_path = runtime_tokenizer_path self.qnn_sdk = os.getenv("QNN_SDK_ROOT") + self.is_modality = is_modality self.device_workspace = ( f"/data/local/tmp/{getpass.getuser()}/executorch/static_llm" ) - + self.runner = ( + "qnn_multimodal_runner" if self.is_modality else "qnn_llama_runner" + ) device_output_path = self._get_adb().output_folder if args.enable_x86_64: logging.warning( @@ -95,6 +109,7 @@ def __init__(self, args, pte_path, runtime_tokenizer_path): self.host_performance_path = f"{args.artifact}/outputs/inference_speed.txt" self.host_logits_path = f"{args.artifact}/outputs/all_logits.raw" make_output_dir(f"{args.artifact}/outputs") + self.runner_base_cmd = self._init_runner_base_cmd() def _init_runner_base_cmd(self): @@ -107,27 +122,61 @@ def _init_runner_base_cmd(self): base_cmd = " ".join( [ f"export LD_LIBRARY_PATH={self.qnn_sdk}/lib/x86_64-linux-clang/:{args.build_folder}/lib &&", - f"./{args.build_folder}/examples/qualcomm/oss_scripts/llama/qnn_llama_runner", + f"./{args.build_folder}/examples/qualcomm/oss_scripts/llama/{self.runner}", f"--decoder_model_version {DECODER_MODEL_VERSION[args.decoder_model]}", f"--tokenizer_path {self.runtime_tokenizer_path}", - f"--model_path {self.pte_path}", f"--output_path {self.device_output_response_path}", f"--performance_output_path {self.device_performance_path}", ] ) + if self.is_modality: + base_cmd = " ".join( + [ + base_cmd, + f"--decoder_path {self.pte_paths[TEXT_DECODER]}", + f"--encoder_path {self.pte_paths[VISION_ENCODER]}", + f"--embedding_path {self.pte_paths[TEXT_EMBEDDING]}", + f"--image_path {args.artifact}/{VISION_ENCODER_INPUT_FILENAME}.raw", + ] + ) + else: + base_cmd = " ".join( + [ + base_cmd, + f"--model_path {self.pte_paths[TEXT_DECODER]}", + ] + ) else: base_cmd = " ".join( [ f"cd {self.device_workspace} &&", - "./qnn_llama_runner", + f"./{self.runner}", f"--decoder_model_version {DECODER_MODEL_VERSION[args.decoder_model]}", f"--tokenizer_path {os.path.basename(self.runtime_tokenizer_path)}", - f"--model_path {os.path.basename(self.pte_path)}", f"--output_path {self.device_output_response_path}", f"--performance_output_path {self.device_performance_path}", "--shared_buffer", ] ) + + if self.is_modality: + base_cmd = " ".join( + [ + base_cmd, + f"--decoder_path {os.path.basename(self.pte_paths[TEXT_DECODER])}", + f"--encoder_path {os.path.basename(self.pte_paths[VISION_ENCODER])}", + f"--embedding_path {os.path.basename(self.pte_paths[TEXT_EMBEDDING])}", + f"--image_path {VISION_ENCODER_INPUT_FILENAME}.raw", + ] + ) + else: + base_cmd = " ".join( + [ + base_cmd, + f"--model_path {os.path.basename(self.pte_paths[TEXT_DECODER])}", + ] + ) + return base_cmd @final @@ -137,12 +186,12 @@ def _get_adb(self): EvalBase._adb = SimpleADB( qnn_sdk=self.qnn_sdk, build_path=f"{args.build_folder}", - pte_path=self.pte_path, + pte_path=list(self.pte_paths.values()), workspace=self.device_workspace, device_id=args.device, host_id=args.host, soc_model=args.model, - runner="examples/qualcomm/oss_scripts/llama/qnn_llama_runner", + runner=f"examples/qualcomm/oss_scripts/llama/{self.runner}", target=args.target, ) return EvalBase._adb @@ -158,8 +207,8 @@ def run(self) -> Any: class DefaultEval(EvalBase): - def __init__(self, args, pte_path, runtime_tokenizer_path): - super().__init__(args, pte_path, runtime_tokenizer_path) + def __init__(self, args, pte_paths, runtime_tokenizer_path, is_modality): + super().__init__(args, pte_paths, runtime_tokenizer_path, is_modality) self.adb = self._get_adb() self.inference_speed = 0 @@ -220,7 +269,13 @@ def run(self, prompt): runner_cmd = " ".join( [self.runner_cmd, multi_prompts, f"--seq_len {self.args.max_seq_len}"] ) - self.adb.push(inputs=[], files=[self.runtime_tokenizer_path]) + + extra_files = [self.runtime_tokenizer_path] + if self.is_modality: + extra_files = extra_files + [ + f"{self.args.artifact}/{VISION_ENCODER_INPUT_FILENAME}.raw" + ] + self.adb.push(inputs=[], files=extra_files) self.adb.execute(custom_runner_cmd=runner_cmd) self.adb.pull( host_output_path=self.host_output_response_path, @@ -253,22 +308,23 @@ class SqnrEval(EvalBase): def __init__( self, source_model, - example_input, + get_example_inputs: Callable, args, - pte_path, + pte_paths, tokenizer, runtime_tokenizer_path, + is_modality, ): - super().__init__(args, pte_path, runtime_tokenizer_path) + super().__init__(args, pte_paths, runtime_tokenizer_path, is_modality) self.inference_speed = 0 self.source_model = source_model - self.example_input = example_input + self.get_example_inputs = get_example_inputs self.adb = self._get_adb() self.tokenizer = tokenizer self.enable_x86_64 = args.enable_x86_64 self.max_seq_length = args.max_seq_len - pte_meta_info = retrieve_info_from_pte(pte_path=pte_path) + pte_meta_info = retrieve_info_from_pte(pte_path=pte_paths[TEXT_DECODER]) self.output_vocab_size = pte_meta_info["output_vocab_size"] pte_max_seq_len = pte_meta_info["pte_max_seq_len"] self.logits_scale = pte_meta_info["logits_scale"] @@ -277,7 +333,7 @@ def __init__( if args.model_mode != "kv": logging.warning( - f"Current SqnrEval does not support {args.model_mode}, switching to kv mode." + f"Current Sqnr Eval does not support {args.model_mode}, switching to kv mode." ) if pte_max_seq_len != self.max_seq_length: @@ -292,7 +348,7 @@ def __init__( def run(self, prompt): golden_logits = INFERENCE_REGISTRY[True]( - example_input=self.example_input, + get_example_inputs=self.get_example_inputs, prompt=prompt, module=self.source_model, tokenizer=self.tokenizer, @@ -428,7 +484,7 @@ def __init__( # noqa: C901 self.enable_x86_64 = args.enable_x86_64 self.max_seq_length = args.max_seq_len - pte_meta_info = retrieve_info_from_pte(pte_path=pte_path) + pte_meta_info = retrieve_info_from_pte(pte_path=self.pte_path) self.output_vocab_size = pte_meta_info["output_vocab_size"] pte_max_seq_len = pte_meta_info["pte_max_seq_len"] self.logits_scale = pte_meta_info["logits_scale"] @@ -535,9 +591,12 @@ def _model_call(self, inps): self.inference_speed = output_performance_holder[0] return output_logits_holder[0] - def __init__(self, args, pte_path, tokenizer, runtime_tokenizer_path): + def __init__(self, args, pte_paths, tokenizer, runtime_tokenizer_path, is_modality): super().__init__( - args=args, pte_path=pte_path, runtime_tokenizer_path=runtime_tokenizer_path + args=args, + pte_paths=pte_paths, + runtime_tokenizer_path=runtime_tokenizer_path, + is_modality=is_modality, ) self.inference_speed = None self.tasks = args.tasks @@ -548,7 +607,7 @@ def __init__(self, args, pte_path, tokenizer, runtime_tokenizer_path): args=args, runner_base_cmd=self.runner_base_cmd, adb=adb, - pte_path=self.pte_path, + pte_path=self.pte_paths[TEXT_DECODER], device_performance_path=self.device_performance_path, device_logits_path=self.device_logits_path, host_performance_path=self.host_performance_path, diff --git a/examples/qualcomm/oss_scripts/llama/decoder_utils.py b/examples/qualcomm/oss_scripts/llama/decoder_utils.py index 8da25f19a2b..1f3ae458d85 100644 --- a/examples/qualcomm/oss_scripts/llama/decoder_utils.py +++ b/examples/qualcomm/oss_scripts/llama/decoder_utils.py @@ -4,8 +4,6 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. -import getpass -import copy import logging from collections import defaultdict, OrderedDict from dataclasses import dataclass diff --git a/examples/qualcomm/oss_scripts/llama/llama.py b/examples/qualcomm/oss_scripts/llama/llama.py index 20ead0a1ace..4c2e81c1abf 100755 --- a/examples/qualcomm/oss_scripts/llama/llama.py +++ b/examples/qualcomm/oss_scripts/llama/llama.py @@ -152,47 +152,46 @@ def inference( is_modality = hasattr(decoder_model_config, VISION_ENCODER) or hasattr( decoder_model_config, AUDIO_ENCODER ) - pte_path = ( + decoder_pte_path = ( f"{args.pre_gen_pte}/{pte_filenames[TEXT_DECODER]}.pte" if args.pre_gen_pte else f"{args.artifact}/{pte_filenames[TEXT_DECODER]}.pte" ) + pte_paths = {TEXT_DECODER: decoder_pte_path} + eval_results = { + "pte_size": os.path.getsize(decoder_pte_path), + } - # TODO: support multimodal runtime, we only check pte size for now if is_modality: - if args.ip and args.port != -1: - encoder_pte_path = ( - f"{args.pre_gen_pte}/{pte_filenames[VISION_ENCODER]}.pte" - if args.pre_gen_pte - else f"{args.artifact}/{pte_filenames[VISION_ENCODER]}.pte" - ) - text_embedding_pte_path = ( - f"{args.pre_gen_pte}/{pte_filenames[TEXT_EMBEDDING]}.pte" - if args.pre_gen_pte - else f"{args.artifact}/{pte_filenames[TEXT_EMBEDDING]}.pte" - ) - # Prepare validation results for CI system - validation_results = { - "pte_size": os.path.getsize(pte_path), - "encoder_pte_size": os.path.getsize(encoder_pte_path), + vision_encoder_pte_path = ( + f"{args.pre_gen_pte}/{pte_filenames[VISION_ENCODER]}.pte" + if args.pre_gen_pte + else f"{args.artifact}/{pte_filenames[VISION_ENCODER]}.pte" + ) + text_embedding_pte_path = ( + f"{args.pre_gen_pte}/{pte_filenames[TEXT_EMBEDDING]}.pte" + if args.pre_gen_pte + else f"{args.artifact}/{pte_filenames[TEXT_EMBEDDING]}.pte" + ) + eval_results.update( + { + "encoder_pte_size": os.path.getsize(vision_encoder_pte_path), "text_embedding_pte_size": os.path.getsize(text_embedding_pte_path), } - with Client((args.ip, args.port)) as conn: - conn.send(json.dumps(validation_results)) - else: - logging.info("Multimodal runtime support is currently under development.") - logging.info( - "Detected vision/audio encoder in model config. Exiting process safely." - ) - exit(0) - return None + ) + pte_paths.update( + { + VISION_ENCODER: vision_encoder_pte_path, + TEXT_EMBEDDING: text_embedding_pte_path, + } + ) - eval_results = { - "pte_size": os.path.getsize(pte_path), - } if PROMPT_EVAL in args.eval_methods: prompt_evaluator = DefaultEval( - args=args, pte_path=pte_path, runtime_tokenizer_path=runtime_tokenizer_path + args=args, + pte_paths=pte_paths, + runtime_tokenizer_path=runtime_tokenizer_path, + is_modality=is_modality, ) output_prompt = prompt_evaluator.run(prompt=args.prompt) eval_results.update( @@ -205,6 +204,7 @@ def inference( logging.info(f"Device Inference Results[{idx}]:\n{output}") if SQNR_EVAL in args.eval_methods: + assert not is_modality, "Modality Model does not support SQNR_EVAL." tokenizer_wrapper = TokenizerWrapper( args, decoder_model_config, @@ -222,11 +222,12 @@ def inference( source_model = multi_modal_mgr.text_decoder.decode.decoder sqnr_evaluator = SqnrEval( source_model=source_model, - example_input=source_model.get_example_inputs(), + get_example_inputs=source_model.get_example_inputs, args=args, - pte_path=pte_path, + pte_paths=pte_paths, tokenizer=tokenizer, runtime_tokenizer_path=runtime_tokenizer_path, + is_modality=is_modality, ) sqnr, golden_logits, _ = sqnr_evaluator.run(prompt=prompt) logging.info(f"SQNR Eval Score between FP32 nn.Module and QNN: {sqnr}") @@ -246,11 +247,12 @@ def inference( qdq_ep = torch.export.load(qdq_ep_path) qdq_sqnr_evaluator = SqnrEval( source_model=qdq_ep.module(), - example_input=source_model.get_example_inputs(), + get_example_inputs=source_model.get_example_inputs, args=args, - pte_path=pte_path, + pte_paths=pte_paths, tokenizer=tokenizer, runtime_tokenizer_path=runtime_tokenizer_path, + is_modality=is_modality, ) qdq_sqnr, cpu_qdq_logits, _ = qdq_sqnr_evaluator.run(prompt=prompt) eval_results["qdq_sqnr"] = qdq_sqnr @@ -264,12 +266,14 @@ def inference( ) if TASKS_EVAL in args.eval_methods: + assert not is_modality, "Modality Model does not support TASKS_EVAL." # Generate the eval wrapper ppl_evaluator = TaskEval( args=args, - pte_path=pte_path, + pte_paths=pte_paths, tokenizer=tokenizer, runtime_tokenizer_path=runtime_tokenizer_path, + is_modality=is_modality, ) ppl_eval_result = ppl_evaluator.run() eval_results["inference_speed"] = ppl_evaluator.inference_speed @@ -427,7 +431,7 @@ def _build_parser(): default=None, type=str, ) - + parser.add_argument( "--eval_methods", choices=[PROMPT_EVAL, TASKS_EVAL, SQNR_EVAL], diff --git a/examples/qualcomm/oss_scripts/llama/runner/multimodal_runner/multimodal_runner.cpp b/examples/qualcomm/oss_scripts/llama/runner/multimodal_runner/multimodal_runner.cpp index 274920ec00f..bb1fa91f158 100644 --- a/examples/qualcomm/oss_scripts/llama/runner/multimodal_runner/multimodal_runner.cpp +++ b/examples/qualcomm/oss_scripts/llama/runner/multimodal_runner/multimodal_runner.cpp @@ -151,8 +151,8 @@ Error MultimodalRunner::load() { case EvalMode::kKVCached: prompt_embedding_method_name = "tok_embedding_kv_forward"; token_embedding_method_name = "tok_embedding_kv_forward"; - prompt_processor_method_name = "forward"; - token_generator_method_name = "forward"; + prompt_processor_method_name = "kv_forward"; + token_generator_method_name = "kv_forward"; method_names.emplace_back(prompt_processor_method_name); method_names.emplace_back(token_generator_method_name); break; From 7d35c2e70762e708fa4113c1520fc4a3a2e061c8 Mon Sep 17 00:00:00 2001 From: winskuo-quic Date: Mon, 26 Jan 2026 23:53:07 +0800 Subject: [PATCH 4/4] eval_llama_qnn.py fix --- examples/qualcomm/oss_scripts/llama/eval_llama_qnn.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/qualcomm/oss_scripts/llama/eval_llama_qnn.py b/examples/qualcomm/oss_scripts/llama/eval_llama_qnn.py index 0c37ff52953..47a1b3e2add 100644 --- a/examples/qualcomm/oss_scripts/llama/eval_llama_qnn.py +++ b/examples/qualcomm/oss_scripts/llama/eval_llama_qnn.py @@ -306,7 +306,6 @@ def eager_eval_quanty( def eval_llm(args): tokenizer = prepare_tokenizer(args) model, prefill_config = prepare_model(args) - get_example_inputs = model.get_example_inputs model, config, inputs, scales_state_dict = prequant_algorithm( model, prefill_config, args ) @@ -372,7 +371,7 @@ def eval_llm(args): logging.info("Evaluation of QDQ model:") graph_module_inference( use_kv_cache=False, - example_input=get_example_inputs(), + get_example_inputs=lambda use_kv_cache=False: inputs, module=model, tokenizer=tokenizer, ar_len=args.max_seq_len,