From 2c3220c2beb9d0310c0a463044428d94ebfe1b39 Mon Sep 17 00:00:00 2001 From: Gahow Wang Date: Thu, 23 Jul 2026 16:09:52 +0800 Subject: [PATCH] Add TP2 prefill serving-path smoke experiment --- .../.gitignore | 3 + .../analyze_entry.py | 89 ++++++++++ .../analyze_prefill_trace.py | 146 ++++++++++++++++ .../experiment-card.md | 79 +++++++++ .../figure-prototype.png | Bin 0 -> 71748 bytes .../fleet.toml | 48 ++++++ .../jobs-smoke.toml | 16 ++ .../materialize_prefill_request.py | 50 ++++++ .../plot_figure_prototype.py | 50 ++++++ .../results/entry-audit.json | 24 +++ .../run_prefill_profile.sh | 163 ++++++++++++++++++ .../run_request.py | 61 +++++++ 12 files changed, 729 insertions(+) create mode 100644 runs/frontier-tp2-prefill-serving-v0/.gitignore create mode 100644 runs/frontier-tp2-prefill-serving-v0/analyze_entry.py create mode 100644 runs/frontier-tp2-prefill-serving-v0/analyze_prefill_trace.py create mode 100644 runs/frontier-tp2-prefill-serving-v0/experiment-card.md create mode 100644 runs/frontier-tp2-prefill-serving-v0/figure-prototype.png create mode 100644 runs/frontier-tp2-prefill-serving-v0/fleet.toml create mode 100644 runs/frontier-tp2-prefill-serving-v0/jobs-smoke.toml create mode 100644 runs/frontier-tp2-prefill-serving-v0/materialize_prefill_request.py create mode 100644 runs/frontier-tp2-prefill-serving-v0/plot_figure_prototype.py create mode 100644 runs/frontier-tp2-prefill-serving-v0/results/entry-audit.json create mode 100644 runs/frontier-tp2-prefill-serving-v0/run_prefill_profile.sh create mode 100644 runs/frontier-tp2-prefill-serving-v0/run_request.py diff --git a/runs/frontier-tp2-prefill-serving-v0/.gitignore b/runs/frontier-tp2-prefill-serving-v0/.gitignore new file mode 100644 index 0000000..011358f --- /dev/null +++ b/runs/frontier-tp2-prefill-serving-v0/.gitignore @@ -0,0 +1,3 @@ +fleet-artifacts/ +fleet-state/ +remote-outputs/ diff --git a/runs/frontier-tp2-prefill-serving-v0/analyze_entry.py b/runs/frontier-tp2-prefill-serving-v0/analyze_entry.py new file mode 100644 index 0000000..fc5948f --- /dev/null +++ b/runs/frontier-tp2-prefill-serving-v0/analyze_entry.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +"""Freeze the EXP-TP2-PREFILL-SERVING entry audit from simulator ledgers.""" + +from __future__ import annotations + +import json +from pathlib import Path + +ROOT = Path(__file__).resolve().parent +REPO = ROOT.parents[1] +REPLAY = REPO / "runs/frontier-attn-structured-v0/replay" +REAL_CHUNK_MS = {2: 410.0, 4: 231.0} + + +def find_one(root: Path, name: str) -> Path: + matches = list(root.rglob(name)) + if len(matches) != 1: + raise ValueError(f"expected one {name} below {root}: {matches}") + return matches[0] + + +def chunk_zero(root: Path) -> dict[str, float | int]: + ledger = find_one(root, "frontier_stage_batch_ledger.jsonl") + chunks = [] + processed: dict[str, int] = {} + with ledger.open() as stream: + for line in stream: + row = json.loads(line) + request_ids = row.get("request_ids") or [] + request_tokens = row.get("request_num_tokens") or [] + if len(request_ids) != 1 or request_tokens != [8192]: + continue + request_id = request_ids[0] + before = processed.get(request_id, 0) + processed[request_id] = before + 8192 + if before == 0: + components = row["execution_time"]["component_ledger_ms"] + chunks.append( + { + "total_ms": row["execution_time"]["total_time_ms"], + "attention_prefill_ms": components[ + "attention_prefill_execution_time" + ], + "moe_grouped_gemm_ms": components[ + "moe_grouped_gemm_time" + ], + } + ) + if len(chunks) == 9: + break + if len(chunks) != 9: + raise ValueError(f"expected 9 initial q8k chunks, got {len(chunks)}") + return { + name: sum(float(row[name]) for row in chunks) / len(chunks) + for name in chunks[0] + } | {"samples": len(chunks)} + + +def main() -> None: + cells = {} + for tp, label in ((2, "tp2_rho0p0025"), (4, "tp4_rho0p0025")): + measured = chunk_zero(REPLAY / label) + real = REAL_CHUNK_MS[tp] + measured["real_total_ms"] = real + measured["total_bias"] = (measured["total_ms"] - real) / real + cells[f"tp{tp}"] = measured + tp2 = cells["tp2"] + required_moe = ( + tp2["moe_grouped_gemm_ms"] + + tp2["real_total_ms"] + - tp2["total_ms"] + ) + payload = { + "schema": "frontier-tp2-prefill-serving-entry-v1", + "cells": cells, + "tp2_required_moe_if_residual_is_all_moe_ms": required_moe, + "tp2_required_moe_shift": ( + required_moe / tp2["moe_grouped_gemm_ms"] - 1 + ), + "entry_gate": abs(tp2["total_bias"]) >= 0.10, + } + results = ROOT / "results" + results.mkdir(exist_ok=True) + (results / "entry-audit.json").write_text(json.dumps(payload, indent=2)) + print(json.dumps(payload, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/runs/frontier-tp2-prefill-serving-v0/analyze_prefill_trace.py b/runs/frontier-tp2-prefill-serving-v0/analyze_prefill_trace.py new file mode 100644 index 0000000..7ff762c --- /dev/null +++ b/runs/frontier-tp2-prefill-serving-v0/analyze_prefill_trace.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""Summarize the longest graph-on execute window in each TP rank.""" + +from __future__ import annotations + +import argparse +import gzip +import json +from collections import Counter, defaultdict +from pathlib import Path + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--trace-root", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + return parser.parse_args() + + +def load_events(path: Path) -> list[dict]: + opener = gzip.open if path.suffix == ".gz" else open + with opener(path, "rt") as source: + return json.load(source)["traceEvents"] + + +def classify(name: str) -> str: + lower = name.lower() + if any(token in lower for token in ("nccl", "allreduce", "all_reduce")): + return "collective" + if "topkgating" in lower or "fused_topk" in lower: + return "router" + if any( + token in lower + for token in ( + "fused_moe", + "moefcgemm", + "tensorrt_llm::kernels::cutlass_kernels", + "groupproblemshape", + "memcpy32_post", + ) + ): + return "moe" + if any( + token in lower + for token in ( + "flashattn", + "flashattnfwd", + "reshape_and_cache", + "prepare_varlen_num_blocks", + ) + ): + return "attention" + if any( + token in lower + for token in ("nvjet", "cublaslt", "rms_norm", "rsqrt", "rope") + ): + return "linear_norm_rope" + return "other" + + +def analyze_rank(path: Path) -> dict: + events = load_events(path) + kernels = [event for event in events if event.get("cat") == "kernel"] + windows = [ + event + for event in events + if event.get("cat") == "gpu_user_annotation" + and str(event.get("name", "")).startswith("execute_") + ] + if not windows: + raise ValueError(f"{path}: no execute annotation") + selected = max(windows, key=lambda event: float(event["dur"])) + start = float(selected["ts"]) + end = start + float(selected["dur"]) + current = [ + kernel for kernel in kernels if start <= float(kernel["ts"]) < end + ] + components: dict[str, float] = defaultdict(float) + kernel_totals: dict[str, float] = defaultdict(float) + for kernel in current: + duration_ms = float(kernel["dur"]) / 1000 + name = str(kernel["name"]) + components[classify(name)] += duration_ms + kernel_totals[name] += duration_ms + kernel_rows = [ + {"name": name, "duration_ms": duration} + for name, duration in sorted( + kernel_totals.items(), key=lambda item: -item[1] + ) + ] + wall_ms = float(selected["dur"]) / 1000 + busy_ms = sum(components.values()) + return { + "trace": str(path), + "selected_execute_annotation": str(selected["name"]), + "execute_annotation_histogram": dict( + sorted(Counter(str(window["name"]) for window in windows).items()) + ), + "all_execute_windows": [ + { + "name": str(window["name"]), + "duration_ms": float(window["dur"]) / 1000, + } + for window in sorted(windows, key=lambda event: float(event["ts"])) + ], + "execute_wall_ms": wall_ms, + "gpu_kernel_busy_ms": busy_ms, + "non_kernel_gap_ms": wall_ms - busy_ms, + "components_ms": dict(sorted(components.items())), + "kernel_rows": kernel_rows, + } + + +def main() -> None: + args = parse_args() + traces = sorted(args.trace_root.rglob("*.pt.trace.json*")) + if not traces: + raise ValueError(f"no traces below {args.trace_root}") + ranks = [analyze_rank(path) for path in traces] + critical = max(ranks, key=lambda rank: rank["execute_wall_ms"]) + payload = { + "schema": "frontier-tp2-prefill-serving-smoke.v1", + "contract": { + "selection": "longest execute annotation per TP rank", + "critical_path": "rank with largest selected execute wall", + "component_time": "sum of CUDA kernel duration within selected window", + }, + "ranks": ranks, + "critical_rank": critical, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") + print( + json.dumps( + { + "ranks": len(ranks), + "execute_wall_ms": critical["execute_wall_ms"], + "components_ms": critical["components_ms"], + }, + sort_keys=True, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/runs/frontier-tp2-prefill-serving-v0/experiment-card.md b/runs/frontier-tp2-prefill-serving-v0/experiment-card.md new file mode 100644 index 0000000..554279c --- /dev/null +++ b/runs/frontier-tp2-prefill-serving-v0/experiment-card.md @@ -0,0 +1,79 @@ +# 实验 EXP-TP2-PREFILL-SERVING:TP2 base-prefill residual 是否来自 serving-path MoE + +> **状态:** GPU smoke harness 已冻结,待远端执行 +> +> Parent campaign:[`../frontier-simulator-gap-campaign-v0/README.md`](../frontier-simulator-gap-campaign-v0/README.md) + +## Claim 与决策 + +- **Parent claim:** structured predictor 后,单请求 q8k/ctx0 的 TP2 sim chunk + 仍为 356 ms,而 real anchor 为 410 ms(−13.1%);TP4 为 231.3 vs + 231 ms。剩余量是 TP2 独有的 base serving-path residual。 +- **目的:** 判断约 54 ms/chunk 是否可由 TP2 prefill MoE 的 + tactic/warmup/routing/serving composition 工程修复。 +- **Competing hypotheses:** + - H1:standalone grouped-GEMM 在 TP2 的 q8k expert shape 上过于乐观; + warm real-routing 或 serving-path trace 会比当前 171.1 ms/chunk 高 ≥10%。 + - H2:MoE 三臂接近,残差位于其它 whole-layer/host/event path。 +- **事前预测:** 若 H1 成立,serving-path MoE 增量接近 54 ms,并且只注入 + TP2 row 后 chunk0 residual 降至 ≤5%;若 H2 成立,三臂差异 <10%。 +- **判定规则:** + - 先用一个 TP2 q8k smoke 验证 prefill execute annotation 与 kernel + component 可提取。 + - B/C 相对 A 都无稳定 ≥10% shift → 停止 profile 注入,转 whole-layer。 + - C 解释 ≥70% 的 54 ms residual → 注入 TP2,重放两个 TP2 trace cell。 + +## Setup + +- **自变量:** + - A:当前 Frontier standalone profile/predictor,TP2 q8k/ctx0 + `moe_grouped_gemm=171.118 ms/chunk`。 + - B:相同 expert GEMM shapes,充分 warmup/autotune,使用 serving trace + 抽取的 routing allocation。 + - C:vLLM 0.20 graph-aligned serving path 的 q8k prefill execute,按最慢 + TP rank 汇总 MoE kernels。 +- **控制变量:** Qwen3-30B-A3B BF16;H20;TP2/EP1;q8k/ctx0;同模型、 + runtime、FlashInfer workspace、CUDA/driver;TP4 仅作 control anchor。 +- **Hardware:** 只用 `dash1`--`dash4`;运行前要求目标主机 8×H20 全部 + idle/healthy。每个 arm fresh process,避免 profiler one-shot 与 tactic cache + 交叉污染。 +- **Smoke contract:** q8192→2,TP2,8192 max batched tokens;同一 fresh + process 先执行 2 次 profiler-off warmup,再 profile 1 次请求。每个 rank + 选择 duration 最大的 `execute_*` annotation 作为 prefill window,critical + path 取 wall 最大的 rank。 +- **Metrics:** chunk execute wall;MoE/attention/collective/other kernel ms; + 最慢 rank;独立 process repeat;注入后的 chunk residual 与 TTFT/E2E。 + +## 预期产物与 review + +- **预期数据:** `results/entry-audit.json`;三臂 trace/provenance;component + 对照;TP2 counterfactual replay。 +- **Figure prototype:** `figure-prototype.png`;左图为 TP2/TP4 q8k chunk + real vs sim,右图为三臂 MoE 事前预测。 +- **人工 review:** campaign 已批准;entry gate 已通过。 +- **Review 意见:** 先单 TP2 smoke;只有 annotation 与 component contract + 通过才扩为 repeat/control,不先铺满 GPU 网格。 + +## 复现信息 + +- **Code:** 当前 aituner worktree;serving profiler 复用 + `runs/frontier-component-residual-v0` 的 vLLM/Kineto harness。 +- **Environment:** vLLM 0.20.0;H20;remote repo + `/home/admin/cpfs/wjh/aituner/aituner`。 +- **产物路径:** 本目录。 +- **已知 deviation:** real 410/231 ms 是已冻结的 pure-prefill chunk anchor; + 本实验不重新声称它来自当前 1h trace 的在线 batch。 + +## 结果 + +- **Entry audit:** PASS。TP2 q8k/ctx0 structured sim=`356.25 ms`, + real=`410 ms`,bias=`−13.11%`;TP4=`231.33 vs 231 ms`。TP2 + component ledger 中 MoE=`171.12 ms`,若单独解释 residual 需增至约 + `224.9 ms`(+31.4%)。 +- **观察事实:** 待 GPU。 +- **Fleet preflight(2026-07-23):** dash1--dash4 均为 8×H20;32 张卡 + `memory.used=0 MiB`、`utilization=0%`,无 compute process。dry-run 选择 + `dash1:[0,1]`,正式 job 已 pin 到 dash1。 +- **含义:** 待 GPU。 +- **Claim update:** unchanged。 +- **下一步:** probe fleet;运行 TP2 serving-path prefill smoke。 diff --git a/runs/frontier-tp2-prefill-serving-v0/figure-prototype.png b/runs/frontier-tp2-prefill-serving-v0/figure-prototype.png new file mode 100644 index 0000000000000000000000000000000000000000..54ec6630271e206fc96b9e6b98ec146f32390498 GIT binary patch literal 71748 zcmeGEg;!Q<7dMP9K)R9c5~UGD=}rYfQc+StxFVm}BF4{e z_kUi&=ivODpF?Tz5xfhoqsk)}1ffns{fDON78sA9A&9Ditj>#1>(d@OI?8ld|DJo8 zuGHt_jMbUw^O)RGT5qGI$ZQ)EZljF6MyvjYx9ziWhFP@gy5aYTYqxp6ybsG@WoCHS zuzN1+-cnn4OEzAj(M3XeGdWTGaC3Tk+;gc%C5QxF;s1W`-Z$1Aw`B9DOEY+;rX*Le1npi z95vnF{bK_-r?UZn3#ds^AN}6VX##jPTi3fzq7~2&w>D)rAId(j?Q?j{cD$*I1PsPh zlv;1*CU6LGVtskZ9rI!X8E9dW$>L-#a}9C|ZY$kk#4Gz~=TdmjOXr4JbWzY>J>~(n z%Md|fQbBDx>VzUfq2BwYwzqKvwYl9+8n3@oRvNk*e|kYmz%V7;*%=nyx|^LGb8;le zlSC9Sc-@9Mc46bKBGZ!~{YH(KYwNEC+)Li)O2kY1Yw!>wHyV$&%!*a}J&(IgT2rS# zJf-c*!T2N5&4iG6So1cYO9xvO9XKYw(0qQfbWevsPmqbGe{K_v`tOLp_WNnMqx>?NWIC)X+~JS@`KTyTn{VL7PUY46N@|#-tdcUx~e?2x)$vS>*wYYTTHJD`Zy(9ZJPZD8} zd6{gC3OlXp0Gs^37jHLvTi3t(W7SUxWA}SEj3kXT^{CG^=nvm>Gjy3OzIQ)0_&C9F zo47UED0Jw$er#UY%b_tcEWP^z&IK5Qw00)LwlQLMriM)6FG&e(4NVM+uGxyKYv)Ev z9^v+7@HPbzga&GnQK2A<|;gt~Zx99>Dz>eW4J%z%Sf@zk6E$_>)d* zUa(WD_I507Vsqu7j>5%>sO>~lu+uTt55sH!YpM(nz4R;4{el?GLKxe*qu(2r?ikb! z8cB7mo?Tu~=8N#oC9TY$61-|8Yg9|i^Kmb5Xbc?|661Q#OvY~)su*|Scrs6=LKs=T z)1GV)7^eP|MNrFBX9R;X@>IscI-tSH*d`+bi|M@CpH&zDDxt8$-#V%c>p5SFJXu zF|KY*W*GX-wZBv%%fGbiqsr^U$Y$E+S#M8M58=B@*Pe(W_N+5swD9*H%-fxO@wDgS zJgaVloloaiaVPnNge%Rz;mMwjN761VC?rb@F<6;;#J+Ijf4lWb=57at$blgNbAh&~ z+1!ne!h7G#^N9)8pTyEIMYVR-k!)UazP%88qm}8`J9>Icx_vZB>MsjarP5BlwhC?y zdM=vuszk_8E8fUP+e%U3(2#*mk}8G62c7+q3BJMD14GhHX?myJ5gFDyFCT403AoI^EL zf)g2Qvv4&<9xeG-q`to&D{L7D3_SH{{ubqs(y5!n!QwUR^I^mi$#wt4U&o>*OS8Bz{7EY|NEf?!_uC`J4J}ku8KK^yY?5 z>AzQ1j;Is+581a0ehhnl8hImf0Yo?=iQ6nnH)q^%@$YM%z%nS1>y^Eox zs%;tW^3M0~!{j}$3eyUu4T2mk%Cv#BD|h)ISa`YE)qzk|u&9>4iKRNmSmP%HB? zpeFZV5Bn!^53AYShbO()7Jdp#81JwM&b9pM^T4&8SlfypxW;yyfHV3c$Sr9qT2WEi zsMgv^KfUk6=tjnFIE~>atjjmoiEY?+4mkyi`VAysiEJ$RD%d9x5{5<^nbp1oy$uf0W0}%T zIlri>#j52upU`BtZ-gYahtjg03wikj<{IBo`n!;<^6lGIN2p9l&_qIpW6SbD>R5UA^XS?KM zYj|@fIN4V&-5 z+&Q|j8dlgGIM=2OKMxgxdCyL&h6|zA)N!Qd9&bGD7saLR0b@zoy$eiDF0&mS4x}}8 z7B8Fic6(Y7W}_r%N|#X3{E{}H;^b&8p*NC&Mi{TIJ0Z;LlVJ?o2CH(1#iwx8cd zRqT9r0frbl68pGKK8xe~Vhd-fmC#+A#&p9+@rk8WEQ2;IQXNcsyizY!ukj;~*siiw zzQIOd-4qtWlPiU(s@XX=3ZCat0uK>6%O@DV z3QDwQ8fV0CO=iiRn+i%?&nTk=oQnw8ZfBb6WkxEUlv8<-`YwCluuqk-ctV}Up%?Q$ zAN_TkugUaXZv_vFX6|njQVijv2l9#Jg!1hXrN_Tr9ttxRmPx7lGsvnnIRg^mvR*^O zy3Hq0GD=w4U@T{0Q535uj2V4;=gvRtB{g=Vnb`fk5EqIuw`AO1FGChVArfB`7C1N= zC7l}tww+D2=P!yzA3n=sIhf~HsdXZ;>UCff+PucDWFg>QfS*Kk)kNT0iK_a~Vb@-U(iHy6!J~R>P~}?RS-xjS#rUL~EF?lHq#eVKNTZyC_H6 zGwE*+XBbSSiG$YOlpz77mxMto+D*rUgaW1RJuaTmkE~w3PqLJyl$3nf33wPH@>i{M zh99GdYQShpwsY>DfU9GumD}{1DD^|}miz@r;J=sib-8%tNJrPUiDsZ(%jUjirWn19UF%Urmggm#Ha*{ru;21vKHMPkN=j=9A>1~(|tPA zlVA2UyZ`=Z@FIq~Ls)EO7dD=7&UrtUnjxY)NF~_{a63f79&gZK1C4z1u=wu5isxH< zM%nc*2`c`_h?X|@htU;+(;0lX``6;hoH6@PDVdKiXl}&KF_$@qBq}S9xuu|CtZA*f zjyH0D)ezr5A=jw=oTKSX`;`**n55o=Q7h$1u#P>tP&6?Lk)BzMsCYV4JP?1*di-59 ztlRa2o(*pMh4k_n!B4d}N_**wDtI%2d-^P}UekFarb?#!1GOoM5hAV0XP8h;N+(^X z9bwS{-9vb76tpcs>B-vVoST>|i@y(5e7y)0absgXPBit+Fj!QGHx;inYS;zySq9I0 z=epjfcz6D-3_D$S_R@a0n8o^#9;z<*&Dc`u#$L8qQ~beSz6@d?>>tT<9bx#P-Q8Gl zy5Ebt)RMIyX9r8S7DzV){XMuD_U0CKjaO6UU^%@p)oZ1CQjfp>VfZ_t@+HTjIsYk& zlM=2WB)~kgA?$1)A9Z&9$H~C0f^%#@^k@TYj8+TxqJ0Q{Y&?yjrv6?z$9|qr!G~Eu zDNo{Ji2{j2B82_P43W9ktvB4aMkaC7Gi+9w6M|m*|6b?~L@?yzQ%W@ob|p*T>|S*@ zqj4l8Eqpv!`1oDr$c1j6PuO(a6=4tI=m_(D8IS!u{b(&riGi$yL1wIThGZpApkSDB zr_DD_rmN$GlMtKQTO8i7_@Y>iKMyvW3>I?p0ST!2uF~Q#zx< zWz6@gp`_ln6j~PzID-ZZ%r|ez{mtG8o06c!qC6WrY4Um zL2JnYwL{^keETY1z;-A}j3AFR2p-;MdUM3=udtDbkEr8q4w^qrHPswlC zGX^I9n)RTc(s=yx)yp=%*>O zzsH*^1@fi{fJkIl%Nk=`VfU4#LMEqdXf^n<8@*nE1!I}h#ZlwB6AG?|Bu`c4n;+6k z_37FAKGr|TJbaeUip@z$)WVU+#Q>`&Wn}zjK%0|3;;lE zIZt+kmOh3?&WW4E1PKU3eW>Kh5hAb#lm5>hBhi1BpRbapq133g8Sd*4CKj(t^dg_1 zhrI~$W@=5=!Xj?&rkYK;Y&QUtpCjMni$c<#6D6D_#r197Fwt*U1o6G&kA3`KE^YRz zMFjud%q#5iVc5N4lcn3r@~7EBz_|o_G;yh7;5moV`RQel{)^+rXdt)Wg4alLN`B#| zFrKRwoDKD@mD+CO0$dk#RodSn(rDOG=>EPXjE=I3Bo*F7Z@6#ECgPq7E1ec7Axaw` z3ETbd-CCnQMQ^ir`z^R%T^rZly!KS$@$Wz?>HU!OreBgobIz{>sTD*mW8lME zX$jHAKR$bCGvt`VN|Zi(OL=}v+q6GRI?MSgwb(Ot?yJBa$i~V;XEL}Ts;d^qakG`q zi~Db8t7!xaic0mG*QWYj0bqw=WZ%1 z07-v`i$%o_X&#DxuN{7FFd4U%GLYDwKc42{jHW#}!N4s1MGgesi-tmc9@_m7ry48) z*ZdDQ|2{L1F1g7ihFAM9Zu{5A&2~P&!FoGNY3ir2!6{1nb*KbqACkCXdl>!fwliHj zy=HWNo94l3Lw}8hf;%U1Swo##K~?vtlXKk8KZ&&N?)^IM{gr+Hk}^$;qD-d^B>tz9 zWLZ)T_GV1DB!?(@#`^Xhl(H0 zb{VyE*#9o@2bMKZa(M_b zMEolipPu8KW<8WRt)xJ~sb_MJK&dl2(s55;KG?jsTqD(5gg02d?#t>1)W<)wnv1jwVEjx$;h2+ouK1t0 zF&`h2ch>wb;~`C852gjT&ApY5vTblHrE5DBps`;a5AcY^$gq9SUh}+oXD5T<_hJTPMuIt#VQRRw4Lz^Dv`g?keg_a@ZXCO zUZjrbNbCJNC!zbtM%Lpn41r^S{AD&R!%B|s$CkcydhqNm>7(e8JM&QsWl@*E34#Q* z4e#WolV*wa$XZxq7Ayvm)@hojY! zkvfyZ{I+;g5lk%9dY#uY!T;cB(i5wBln4zGAw1(8?TG~@TkZK?s2IS{dhAES9NXN` z+k8g?|9ptyZQ_cGx2J|=)WE=E4e7OoYL=QEyJvq8b@dE%(8^?XXRbz17Ffo*+mdpPN{yvwo}EaQeDnK z#B`aqeM@^PuTc1A;qI3s2lo*>CANdHyR8h5ao0vNnLT()?i8(__B0uso^mbxtSI#6 zr<3=tk|?^2WjrTGmtuerW7_{Y zy3M0hfh#zBv>loX3#wBXly}qD6{HpWm~DT>%?)9O&+N0}n7b#E<&@Fy5+Ah`Px7E# z0i-DTR5qi+`=l}Z(+&?Bu_1g}me2@~3eYpfa%MR3Dd|5Zeug;|AMsGc?<`YkNgAQ9 z845}`)NExuN6YJVX!)$>mZdBm2t`i7(E0tOVg>Ejn)^$LMUqnkS9iZSqo;7ilJ3$w z$Gt5(ApT#cJTT?lLjqKTm163K6|u0ABayT8Y~#WnbGBdyw#{LflQv$-=f z!A2+L=TEKNbu7Y_0alDT#BUQHHg}g@1w+{$m*{5tpivyCJJ9J^$>96jcx8WdnW*UI z#`~K(sy3aBh`>>9rlb(35(A>*d32pwNTK=1hDwurkp%c8kkcJ%Zw7GGRMd-DJPOki zj4TXkS7M`0v)DeEnP}5?bjv`uQd_J^-1YUtzllx70>vt5*1|lXeQU?ORibw;aqS(Y zJ5}er2=#l1c->I+fjf6a8+Uf+o7Z;vmx2X(X>MXS>R%>1TGh4TJTMf@NI9Ft7LnAISS}`dBP|)qWk)L>E4M*yU$HDY1YMm zWF7Z(gLiZ3gb%WnLzp*Ha}~Z0x=rtT`xHf4hhMp$dY0i~N6753`=+s{va8#}n)G*f z*27l%VZVshoM{oawBB-qd)5BlRH!iGc*ZJAswQF{dkMAE5lPCV{_bpdqi1s|0=PT& z`ze@jI`0Q{4b2%aEGJZ%bg|eqa@N(hF!b@?(JT-&DxwVo`znomU@h9{^E{=g-f*&<(vLc~z+C?v4_3SQPxZe5XDr1*%#&@u7y z@)>Vh#^#vs{v>%;@N}@-hc+CCIolF#JbQRK{j=AD~~%nA>nvvawx1TOU%%<3FB3C3GQSglwjkOM2|u8Q-`8 zX_7~W^2eDYdrZT1@CLbl4QGB0CefV2Yw9w_l6P98h9oUS{nW`|Vg5ar3~T(OO#i1u z{7SY>(KwIC>CC01Ki^yu``3HbTJ{3G#eD`OOuSyNeoOt~t<}H}Y|_lz1Mck7XID8W z=+@Mpn=E{)NONDuFS_ng`0+GP{B*FHiS=2;fn45+3PG8Y*V3WOKvO5~JEL=!fjCsW zTT7kzU6Fo2PX${HGmCrAB$Bn78 z#9poD*RGsM>G;o@U!3~BKh`K!a^e=S+4Pc(&kJ9@y#8c7xRioP*36Os1`*62g-CcT z4Rb8(vArHMWdvN-$PA?sp?Rx9Q)+va>P@-@exeCt>}ou`er%yme#Lf=AbWG@)B z{`?1HH@{AQBV~+x*OOOtHkf@CyabVH4RMnHke3t_;i;EG^(v&QD$iOaMn6XUacfKX zO4+3jR;xQ}j9!_B%bp1X*|&U#fOA4gGR-MbqO`Q+vbTktS_JAOF3&hqc>lfZng-PmxpCMnOrEd5;y%%V%{!R3_}(zdlcgMN;Wm^q9T2qpIEHlpig{!As%65*hZr5S zf1c}9>NWK6XzUAoK@TFM@aIY)b?rZGc&TzPq$~cZ0mIM!)5c-(b9KMZ=&TMCrn)gb zt5v)!iw{~Ejd3Bp9P{7fh4I+$mGuyuLnflQ2=y6Dys#`MNd7%&gP(0Bv znM%JDEb`V#{hVCJd^4uS?Ld(_p*ZIYKZtHI<&GqXF~9|ft4 zj>#?yvn!q#xbflL1VxIu2@%!w*4f^M#JbF_cALWzlDe69Q%RYMUHp|kZEkSo2Sjws zoqGRgE5neD@Fo10-QBc{``*lKCp3^umf8;8Y2R~h-x0_gA!B9Jm85FqqjCDPN@}h* zzGz+fdG;!VDp?W|(OL9LgP=m|7hBV}SjSHM!ry#Y!ZGY4%S-oP_#jo+`g|E0EdU+5+w;3}W%2WYIQ?uS#(o=WZgota#m7a%?x^%wkT?^kU;vlgj@o z$9x=LrB^JjE;#M?57)s8`{tq42}C(GM4Q)ZPyX}su!ka~FmT%F;oT(JAr3(xRFxn| z8a=mp))va<)6(&`_%B_!R4GYpqaP7WqvDgD)t)b1PFm{@-;HMnJsFvJN2lw3?RX|pBKywC z5^g^%P&nnErs~KbmZmCx9d=$#vX__qe+mA84m&&~P`Y)KD!Y9>po=Qh+3*E9LVwB| ztY9Cp9EeZq?UOKavG-vKT&qyL>djKyP3*Y4>cgj59F&ghjRXiu0oyfpkx>a&A&hVk1La-?63P6?vsUF*0)en0c3>^EL^O6m zQKTt@v0J&2o=GK~CDd^gFjI zVP1*<<5#BGuF9_sxE!vzfpba}OKhjfwDlDQ1HtAyZ~9i_$!M$~apf7q{U#6Jkg-_D zA9G7BxOp-F;ta5l9@Xy`Q;TMWrr#Ae5<~U<)dFL^>kF;?@tkgU-7%l^6l2Fyf~Z__h zm?ssHOK!9160CoVgO|^i{`FS{bR)noc`l7sx(*L-(*<=ytkSTIk=1a!w=P03b#SDk zYX~JE!J5xkbw0w+Dlkg2#h=Ovy28c=*@fZ;{;78rbN8Bi@K84;E>)$Kuk;W4b2OB! zHOQ=JJG;#1Tn8aashYggT>a#{u=g)_9f^lma?dU3M;10Ix$~-*_2Pi7mOj$>EU0`2 z1^^?SCDk6ZtU~fOb=L9qL(8)(&TlRs=^XEKdXra0v;66`I~FP90e{IXT%k0SQW6UZ zLD9KfUJ#7W_Gd#X`C#~l1xp~MJBovZ@3*9A&t)p(F?HhhAAzFPA{Vj$%8ivPgEJtY z@cxH0o?0LX%1*Vh*Hqk6a;JK@vSBf~nhEJ|6*B!X?-jrFw6Uk$%VzA*AV7bzu@#;VK}~L`#o`0u zISrbU6d9+jTWVaw!O%*0K5w9e>9C!g zn4V>S`1e43jnUQ>g?6RGOx0oyZc-4C8VIC)<%1vBK+t0|Dv z=!ULCvpNTP2yu&oJ#+~+ria|3r0a0Q-&eL|-NhesJ$k~H40dr>z(2@mAw2BY91M(d zHK~Gltes#^WyezVS^bl)^gfQ-|5d_*nL#A5EHUWFZobQGw0+3N0N0WNL%=e0w*AMwj9I22@@POl6Y&jfZiGZHcR z=FYr-VNQiCpBr(nZ%v=QrA=z_?4bR2ly@h^cam(8(5tzxhXPJ3t(pc&Oc0{SE*j~0 z3NqS^h*u8Mh7Y4#L~tNkIU}Y0P8yWgmU?faDvzYWp4tpUuc=y29!zH}={Z4O?}gk% zS<(&hxxy3N>q!T5BwtL~Q@Zo9cTc8SVrlZJN^kG32SIQl+!;M3!ASF^*Yrt0e#pTl z%#dQq$llD0x0{+QVZBzmyzgl~K1JLgN(nJ<6^x4*Ky=4a`Z1~AA!_+%qz}PzVu?#! z;$-^X^6a*mCvi~E{wu3}n06MUsjzp15h?DpNT);cpDm2%#@LR={m<1%TfsgVfdYm7 z{9oH)ZH;*Paj9MskO-8_N*cSd|F3UP_&L8KGO{r23U{8JR#a=kgJTUia!CTC%v|Oe z@Mwb=9$+9xO>KWU00ia;PeO8{0diiU`M5|k=Nbe|gr12XfmW})!qwvPTcl3{b=X<%>B_W`#n>=+PCh0Oy&!vO19 zj-Eg%9|m`-X{qg}U7h<5&4upR@r;e|B+(MvpTAo%D$l_5B7EA>ERug6lF^X5p22jO zGp$Vc!2>Y(H-um^WfFykgukRW=U`M=55Hu&Sm%Ie2XJuEFp>tqB~d^FQ;dr?fYLO$Qa;SJ zcd0C3ITW@oDT#cAbKjNi(1bu6UL5=XiM-JNvL=R)yHq8U_Y{wndCjE*Qe2c=f{`x! ztaRX!5M7#@2I@%})TwNU(7aQ}p0{D3UDIPD@z|k)jfvl9e?^p+J{n8wbv|25P1^!< z%@MoyW|$d9Dh^zQ+3i#|CB)MEjAf;b9=l!N?5V2n6Uz<3;|fy}5o(I`1cjeWhn+>% z#d`Y;_NEK+!{IkVlPS$TCSDRFbbB;5}VYNvrd6p-Be}G*z+$ zN?o9YtV&^y0R`n|fN@VbKG_{TwoyIguOA8d;r*7edZ{NI46UL7-p2;dy9B8fVNLQGgC$#UE?|DZq6$ZU-FWz=;bDpCsHc~ce(3*ZN6X2& z&uoyh_b6IP{KD_r6Q`!2uvaOvcfklrS(s#wKVHT+7(mh?NA*fuh5F*pzc*DDj<|aK zx3H{m4FoJ^0`vCsJ93GiaMo_Ky{_X@P?Sr|mw^G3K;8g)&alt_eQ4+w7V^K#{`cuF z^L@1c-sgXxbM=5zd2=6HGqa8d6nP!iCiB4g6+ZjI6UV2LDJj3VJBI_lZ-66 zdD<>uHNbw$vJV*<84F)0CD|@a{bpGq3@Zdxamom;vSnBUy>C4tgx;v$Q=U$&+^K=XJFp*DU zZg_T+m6(W#xOnEHG7AAAp_YxwsxJ<+QtOjd#HoHKoO&fjhU~J$An~{if~Qj)Mha=9 z&i=`t{c9nnzHLj*rJEn0>ywg_A~q=+O3bA6;)RHWgv9TamAs1C@$qBz0yTxpus3g7 z*T>8H(_}IBmisfS3wBfgEG>l+(%o*Fot2)I9Xqy_OT@oLEHk4`T*lFEY%u$TvKZ z4?@q0i0DNEIJI+3mU@ysI|nOl^j$Y5bZzRQqlueaTKeH@sUOM?x`~43j_`^*o6L4& zr7W9MH5l63gL3(g`V-+T%$`0yHuA=15O*h7Sy{QEozqR1@YKR0L^Y1%&ujjMrhMH3 z0r&MWxqKGVSdRN>I&wUQwd9DvvyK=!UXP9#781GNp-g@^hMZS6%BrfAhx2tWUC;vH zr}CBi8u_{Oi{I3^ETJQ>UcDlrq{Kojhw~{a9cGJ%kBO0b??XN8iQD#5MhGIj%?Y0f-l2A~|%ggH&8BkDg>Ac8vR#R7hP+_fGU0X}3^(#Wj)|O|a z#5h{eyfaNcl<0>cgO_?R9))Iw^{^bx7iq`eGSsxRX2&}VBy!|(8)0GPHxW&ARMW$5 z$j`M!KFGdHQFIQsJ@(C0=;UxK8kXHWFhIt^!SPuu8?P;j;WZV%(K7F83$b)ieZ8dL z`M$n_vNF|z)o1h6GT-(n21_VO9$sEk*svV+Bcf5~RpFCwg{T7>; znE3GPGu+joJPa2X7ii7a_VyOI3KkX?4k6+2>(5-Yv|co={!hD}edxOk*+47-Vd3DSB3?1q<$$y_=K7_C$tUHazXoZj zsl#qQZEG4Fyn1kOARs9C>h)`UufxsAd++&HBrRl@A7e1{@DRXe$;0|gZEXp(wYB4U zA5#`Ru4>Cre2rH@g!!PvNOJ!&Rl@Uau7um_5Ly8J*6b9$sPmO%F}GKlnQZgUqIci( zMZyiu?JRVO4L(n0+MB4bS)Qq{I}t82su!c=H~O>`vR?S8j5$_Cz~{)Z-ipuY8&0^w zD>zwZHnvQeB^3~Y#XpNAQw?==$dw{#G4=KJscC4w|N2FEl|xNNP3_H$grA?ECRf5k zs1H+pN&RChCUWyS8pP<^i289p5WXNnp1O;6=+v(zmrHBKX zToDoy`jD8Iu2*F6LCRMQTElIzOUnN!Mbf)3UzdAqV#2z+CZ(RR&U4q|mhBkH;^LyC zqod4hgYWje~|!bku1_JV?gF;S&mpvT*tbcYx(Sk9q=Tff)@R*E|4H<6L1Ha7Su7WsHf zHIwkupu=lyV#eQb8d9<~&I=~bo?U^;s;;T|1BE*DvGaD7FtRyQ|N46%b_;9}luKzv zh2q*s(e~+9pFSEoAX#=9KyR#l?ln-Mh^JXc){1 z)*h2tIXQpfhm`BR_Dz7uYMu~1fBqcBTXEMlMvf;9(=|=TznY`kz}D7wD3z(9p<%4n ztzf2>JS)cu2k;7S^s=11)owBmBlL{+%?}pP&arW!iZL&OIyP@s2m;2f1rFa?U(F1__%>Xi1_dGVI zNCUA5$?o63j~-w-+fYw^%QEJ~SW`2GL@um7nh76i9v;?p-JE*VvEYnC`2H#f;|F!_ zb}T_8pfX62*7wGKd1dy*Oj6`z;R+8844?voB$9j6WCO1tH*LoROyOXrrl)@>Gx@Un z`1<~U77HSg1H`~*Vq!v*`H+-^@8ICj59>#P7n;o2_;~p5-zJgHqb0`t>}27v)Qkt2 zN*NwoGZ`*RJsI}XwQ?MB;ZAdHR#~aSHt!H-i(enJbx{fg50T?ah@j*(2m{UvTOBUI z&(q-o`nitaz$GFjpc2pnP&e4Kt7qtN=gyrXUjShe=b9$~aLTBggu^w|0ggsU0L#oy z4>k-uoyAk6@=rPIN=pUy&$m`rxoTZkSOHIXKi+ZRv$eAePDs#gh)OR=sqGQ3yL$cp za$!;@(9qDSvH!+?|DnOcFdX90))zq2hz8$NJPi#vsE%iXU)(ngiX>)cX3zsZh`Hs* zbS-2UR9I(dq)IhUPCkYgpvkNmk;@&PQ&UoM%sbaW2VT8-^QPFE2(SQ}44i82e;~rw zStMCvZH!vJ&Sy>8hmRk>E;g)-fPKQHqoYHU0mxMZE?VyP-(6IPFN>cXN3Q0b%#04F z3?=Y2ZUkmn-@#{T&C|{u8tt5(HblKqxx=hceUV|EJ1Z|Qukk3`jTH+h-j+Q!6p{ApIO~fD;hFY?+vNF6&vEgIr)5SHPcRY_Wr0iQl z2o(X76hW(CBHPDHscUv~GqrB_P;Y{eKsS;xh`EwLr>@RE2e4wjeOr%vwmBF-Scgk@ zHYtC!7IxO4k!}CH@@TE3F(^Ab`$V#@-rJ+UK%c)qOZBR$y*(cYty=>H6TfRGx=s(b zsM^}vR==L?t+2t5W;Z;SKAyi?B$0o6<9ezO>ysx>g5u)hP;ifGMSwsuL;<>J$Je34 zvR@7%0R}UGj8zDQP?(6Rl@+Jd(Qgb?>-6WS)9u&0?XT)aO2AR;Ue3T{MHHaYD11-$ zNPi{Wq0-aSGZ?K%+cISd`XJ(zaZ+WDx_j7Vg-oT$AZTR4M76@lRrk3y9!zZ=Z(xVN zc|(rsU{oa|3WGW7!Y35&8xz_03u=3!nWRTVEDQ`j{*Rf?=0TV)5iCw#zbSgV7O zByj1H(a_MWZUA3jMTXF#*5NNJ{En9Kk@eX|V}!)Q!eW()o(phTf3#iz1F3W0AnWh% z7ptP9rp6H$7f;u`vEwG;>+1HkH&IA=x=D*Gf%bG@V8Hcu`EGtGI%4dz9gHvoCsh;& z^O>5Ot}@Ydz31=ss3&F+%aWSu&r}vZD0)<;gph=Ug!~09OM9xbC61VZvZFnP-N>Rt1>Jkc)1UwJ*|RK=A7t<|tiaK;|1ti5&5DqKh@pJ&;K8ad^mKDq z*FA&;3ZOsHI8O%;b`yGETvU384(V_>P5V6Y3l@dyIFMRjoEPp!Ivdw}U4z2ZO+&*< zPfuqR5YQ}0QB_r4mBnXMjv7L>BYWJ>R%kZYcSW^C?(Xh?L7uE`01f*#4(03KLjeWg z^yfomC8dZ63T`~a3`pEl>iybi34Wp)N0wIJ5h$)(2X-^x9>G^#Z9X`)RT&hgrSLqOGbQV9YR)n+`J)MS|pb z!6I&}@ol$#(E{G{8|P0Bzy$}7A{|!-uDPWxL-mYw;zhP|_>5ID88*2COkDRP?E_)xr z`{`jf78^Gf>pI}$ac%B!GoUI^T?};gYf_Qq z@-{O#io$Jqg#~bCAezGlvhS_=66&~3R5_%2CFSK=H7Mn;tvT8a=0qaQIckX+LaEkQ zUYKGb&KW1M|Ge1Bq2OUH$cn{yX1X;_WZ^6U&Vdm!_hz? zd`cdG769N~Pj_IFt;(s1-Ej=0Bzm1fE(|~gbS*1X%nZ>5hx$hx#7q3sE^8xNz*!}6 zMEBdI0;Ujwif)C5{!miGH z2yN29;fwfRNJP<#W*F_M<>{aR8VLaSUdrQvl;Kd;Z(so z=zG}9BR)KV4^CmQrt!w+;j4qd`O)yJ*JIwj3qduMq`xm%;qG6*Vn2Kc`}fZgt|oO{JuNaV(aj*aQjJC zRu=hZDF~#*=$>&u&t4wd~&{o*I#ATv!t{-A)?i?j^HrfE|EnpcUjJBnu49u_|lO}5&z^S|n{*!}&^ ztWV(Ho@>3;-gI;@>UB6cI86|6^kpg&Lx+F`V1T?&zBfg(AAp8cypk`Ui53#loSYn# zeMMysAd(h8dQ4S2J*;!*1yCm;AxYcCUPHx=bsk&RcE;tFr0W|SyRJ+?wrBwtO#H#e zeh^CW^rT8hgK++gfq>3z2iJ+}uNR)43pJ=B^)NJ=*yR$f=;}=14h0_siJIEd#T~mr zDh1g|?BHp8y#*UU_9#!CV^kD1UFY!>)Rn<$C=o*h7$GzS@-)<4><7^L{+*k1!$S6w zpS#x!$*%6 zYNBdUDIs*ctceLT+%f{e6`)d@;%my~!CYcmFjBeBI(AQdm#pmU2p>Ow40?VJK;8f? zpxG9`K*$P$Gc0zY`R9d)&a}b<&@cHQ5L=2 zSFgs=AKqgp0}YA={D}h1-&ty`&d87-aou2{FGjk%o&C8>mUWEu54pSvRAS$r_jI7QHEi(Al7{(TfZ!f< zP0h^EAp_!XJR<{6?emtV#LCl&0M$R&#oyTa5(C>20@Y8F-joG;Ma@unzt`4YL+h;2 zd+L!xENGnO0|v7dA}3QjyU0xC=v9Uq=c9k75IoO#XvYMWn!aF~LmO(Wa;F=?9sp1|)04FUAUGKj<`$_hOUQ}y3isx?dvB77l ztIn$*7=HBhZmRW0?}i_b+HirsGH}&Rn4sXWIG!Cj?X8UxL;BDH z0`lgIf4{5r-k*Jx@DzmWp>uO{qe^doZFDqAZ(pTMYJ4lO@$%yF(o&GMf(9hxGoNQX zO-)QtOE-a0$io`vYjC0=0RafaeHjk34T^p~05nyc{2QJLP{C`{xz=Sh)z!;W#$*$F zn09yGLl}x;l6X#SXImljJKhJj-6@ifw=O{T(@VS{(aIx-tsBCb|9k*yNU+z9#~vv1 zJJjROc~TlAZzv@YLO}C2moqZJU)eJ>IGBZxZ!z-ulyvzJ^bp&}U?34qLr`3n^QG|7_Wbh^VOPSg9#$PJ;Y~ zA3vIp({Q{v^MpB+E<=-+p@X5u6)KM5)4`se;wFqwc!NnOwn}=k1c$M&gJ7bBgX3jZL)oS zINQ?MiHC-U=IK}dhzUw>U*)!MZ{VTpJ-!l)TL;r~wjsdIEqO$j9J!h`AD~U6zOfJQ~$g<6c!J=^pDayh0rl z5rq4t<>f>0Yj6PoOpQWpuXiDkq6@f~y#i8ysHUEIbYy&-26Pk{jn&&0rh)oz}#ZiJc)wHm>K+nv8S#tA8_XU*`2RFaK4naOwsUv2{JsAOyLJoZBP8sgiZEf?0=KPv^VBm_j%4~@JfvZ04zZ1(sWYxhb zaR~`E`}gm^Y5Vph8;uG$xBdN>TBJ|-q}6l5gQj4lU%$%=EF4bSrCl2L(WQnW(d;JF zyyvg4)ul?kaI2IU|1)c!Ro_cMDGjBkd#RKI!@4A*i6#vR^^>D7#E|m?$d)R1XI%5( zbhGt_FJIpCL*kL}de^VG6zRaWRBxR(OUDKY3;w&8uSxsvmXQGw6ZR}F~X>)jVsX9S;kj?X;HJdiosH&zG$RY|yKP5ig#KlEV1W{}2 z_m&^OK3m9g=#C%q6qH{SlsAC#q|VQ>GV5=d2{mffqL^)m_HIy;xm&fHW^R7IH>GfB z^J^n?e?-Q_7$V@T$bQ@gAg9Iw6ezl`ZjG8XZ?I8zfyt>!V!3SWbEw0%6yxhSM}d=0 zn6QI}j~>10YOj7=1rAiE%MvRqt8JS7)D6oE8^C0H6BPn@TDDm=^whP`>u*s(AKzOz zcWz~hySc7r(}Bxd1FP2~VZGs9MY@;uU+`f68q`pmtmcLugV8;uN;&FQ@=dPP(`V0C zUb{VC9Mh1ba6e#pICr9Y%+qZAO0k~}1x^)=KlCTBT972*48nI1+DdjPrA4?tucbe`q0oWA`5|&*^#GbMOol0 z95J75mg_3Y(Sv70orSPC24SIdc#zW_BHF}gH|L8>QpN9pkU!kfu`fE=aVHBuxRRn3 z=}sj}2UMA^<>FfWX~>S2`d{LjSj~QWbA8bC3muRl*Jt-`>Y6Sv z{kkdNo$T#zO6kLi8GFP)>rnrtLwN((&eoWn8sCPRpKI!28&mJX1cjEAD1URo zD4eQ6$`1DY-GZWXV?;zm`eOfY)|eSqmlk%8uK4ZR*Dk$9Kbz-9ATSW~-_V6ZUlNJp zz{HQ)q5}+OjaPqJNB9iZzB=(EUast=CDV!Ym7mIWF&Az1ub;ixO(HY)wRv6x`uJAp z)bQnb$=&3|g&aRv9HQ+$vcZ!BnaxB-?@Nz-H-!PL$U;E8m zy*lz|ZfN-M*H>4r=L-$)CcGfE_-3T8&A^?^)s{0?6{L5&e*OA7HY&tb6GCkhU`a!h z6O+navy1Y=>|9srMl^6Jo4I>NnSMv&Mm?@q12l=7uU=Wob8#X+x;*&#bM-l3L#5ob zbZIxN4I_dloeP=%#;@O&uKxFY_$g-3VP{?yp*yTgu6A>Zd$@%$X&QU-e&T+7P z*0?YNda538?WkkU)3)u}4YSU;wXsed0a%nOqG@Q){{0)1^^X#Egs*Gcx3Bra12t>b ze0QxNx&gZs%S+?jj5);8WWAWbqc=`|inCXvoNZflb!7FQ4f5lEH4*RCbQ zOkb20-Gg|u;ljeixqL!PH{a}T8ntQ-hY%IDSweaJ&Br4{NfPGswuI<=X5DKfOdK2H z7Aa2Sj3q`b3Y$9<49nL6^lzr5T*ekWVjgJw=ew;v@NF5+Cf#7vX{!?mFdZVV1~K)P zw7&kly~qW^rv5A`xsj0(Q`hFFhpuCGUAxb^a-<^a3&c}J?$Yt*c}s^#CmP;*8jj_4M+3>kZ?+*iN zBf>%N^@Jog1t-5pzD;Gz??jce`V-`>*sUG1Vyc(=2cluzOts2)qtp7*$ITEe*-PIv z_@D3ifB69azxlRe2(sZu{G23y{OMQhot!rA*fC+n)U}&_-@-D42TDEa6-=j?!qD*7 z&ssIHQ%Ihe(MN2oWGq>@qfW0N;m<)IybHVIdLurblR5lj3=IE={Z8;0_(0BHy5uov z4>Qwr<}!^{MgHh-d_djubrx;%_U@jqQ@7s4nwPL-!oAG?_48}o%soH;#$kyCzQA&P6&iw{px=wwCX%U290I`}gl}=rsAa8Eg&qpoy=N0;DSd zU869603N6{Ly{+4+P1A0)ne8(e>^#*6>#jLXCIo1!Gqy*=g#$BiTz~b*bY^c<0nqE(bb)9XR&#>Dv7r!BOxJS z^q4Wv4yAM6j}05pt5-cra+ha-IKi6H!t?~?W*F3Sb8OD{LmV;wDcO1D8vQ@PD(2K=}|g$PW1SzUmPY3 zh#v2DScx95+qrYN*~B@OixIT{j8#laVBa00!io8VO{IS@YSgISZUuy}=45Os}>g-^b!cNIy zGq+NvA&l38AQU&KFsNd~X?Nz@wLut$&ek*YhEw}*$Y#NZEW$dYv-9Gy<60Da&iK7 zR8{((JXr#>@?5Jw<@zmBe_gNziJMAVYQ5$5_D@}3Ux*oxM`bAg;LO#6H9>n;kn*H7 z2$>rGmoj?7gsC4%q1`Vnd#$br)F;7i#8uU4kI9R)v|~`NKQRbD&)K0hXaf?^TPX`S zZ8Ea4(E-}N`SfXS6Ev{yG?(+rs!9RgGgnf1|ZRbpTN%b9_;T`h3e-HSVi4DoV3;s)W?n(NnH+3 zwyrd2V^Rb}2};^tn0Ul+6q1A(;qhZP5@H_itM%@Lvq-=zuOdF*xPM=RLqsvu6WF-+ z#fxcv2JV_79M(`WBZT1*zbN%X(|$G$aV?&uCIq54<|4*{t&UGjxqW-Mn1Oi|(ufg) z+M2L>^%{9Ees;1=n>H#UTGmzNwBHfp7+c|X?*gnK0!!D1hxe#ftEm_7;w=DJd@I7z zz;jJMyb7QvsxijHUY@_*uQrmt)9~Avl~x|J_Fz=7o0M?v za2v?pH#hgadVS~4x_uWvt%?pJC=PS?*W9EoVj7prTJgI);_oh3sm$gk__V)!_pTSg zR@~YI+har5t6MB?q^>sJfAD6sj7F>TD=47f3FmRJWi-FrsYmhoqb21j9K>O%?&B$y-x2+NgcFmW$^S1CtWh#a0)-Zv~)()Vyo+` z11J7KED0TSZY2t29q_`j-`C()bgM00_+WqFlZ4;!lOLx2V1u-4+g4S1lbyYZdgQ}? z1J~EKNg?a0P_xICS-BiGQQdqu$63Q=A&Q8iAmSHK*>PVnN{CaGdQo=i5e3Hwl(d+v zuYx6iGF*r4;Fi~5PN~qEVftH?dj5oqD@BY=^6AYyy;Nq(`Ib(5Oioy|E3aN#a~8hYx*;*4m0RaI3{iq7Z}KS!T<>b4Jg!OZgd>U!7)hHcmE_k8|J=+NqawGipe zG{(c7>86z}D&Y3pe!33!*Q*U*|1d62ZD@_6yY@5fT^%cmb~&UT*&Q%$Naa86}C~^U5iero!8rd3qRs1 zaH0j0_QoSeT2X%_txP*VU&G+{Piy6IjruOm&O>UfA>D;msfsaV+V$1FPL4mkg_MG& zxG}g2`%QHinicp4RzB`ez~+1nKP^fm_+R^OzMow%NAev2#*<0Fp9p%(SZH8cC5!XI z^&2#(eBt=XCMN2zRU0t@wAI$G3L?7sE^g~rf;Y#eIz{{AkgYab)SzDp(ai+iL`6lp zRPehe;Z}ws7Q>XR@HXp8s;bXl59L}CX}?h7&i(vSD2RAM#hd*6u&*Aaq-C*5_!)Y( zgxdK@Il2OqZ&BXiH1nniQMl$JQRpNhqul|w18{AO9ycxy$>G$Ki7Ajg!i4p(imtk| z(ed20%RR)+SX4NpIHl%KyK(DpbRP42^3z$T03^BN4jniU{8dl0S+lI44Q*XquHK30 z*{jz?&jmT`-lNftLF@0YjcI~S(Bn0`=27CLMkglm2luk<;UaR!9oe-j@as%ld@I&o zeY$lU_ulyQtK=@m7wrbeWH+)pZkAjSFCrAIW9qtzmo?K7@Dp=(90oV1dn>ST~>bbp% zbk)%m5yz&zcc`aGFCNEHf}a?erdlL@%*oSwgDEne@_q$|hLu`^V_!wPH_veUUK{ql z-@W|gGtK3Uap-QPq%>i%V9zAm>HK`IySEyH_#Gd;Z}2AqU*w>p;fwZSqal(jr3IDC7@&hnhj-{w_v z=PU{CgOL0Ob+8YdopZ}^)hb}6B%hy!&n7^5o_O*kiSUm;D_9X=;X5V$Q_6fqJk2tC z%QiuZNE6lm-t*Pz_X-^E`7P5}Qagyxyn4@B;fLU>MlSU34#3V`{8d0Cw1h}2P$#0d z^$oLyi24e}6O?keGx*u-^x{rnJNr+O_wJW<9wb&j+fqG@ByML6ea$L1buS6N7M2+0 zmzlNlUiX--`Y{Gsv&a9`%UL*Yp090e>uV`ho?c1lyz`r0o8i&9K2&w#g#-}YYBsa> znJHuT{dztk*!)8mcR5=NmoGDUn&+EVZs}Wa>gcy4>WVlgsF*ekZ=2A|=9**9B-tnw z#f#Ff>-zcm)os{t9d!|2Pc4gf^~|B=wnC~62{aWGf^@S;PRH0W8(RT7({TEqwti~} zPm!&E(zfq&Q2kCnztavEwF6#H{0)Q>?;JXQn9J=QCPPkLcK-RLjX)dluFdv=9`3)F zxTl#&Vc;`GjmDjDw&mRt_ z@UK}cwm)dmFW`~jK45yal3cF`1?S2NbJmhmtzm@aIv?!6bPJsX0brk8?;rT9 zhW&8?YEIZUf9m7TD=^jWfMs}(Zl1f8wHL+s@tGL|M9EjvnHop?gYBV+h<+r66Dwy< zepV~kfA;s{;yc)*dV(J%=78BplD;0Fn$YTSK}{%#Uq2J8DpVm59i~{Pq?L%+pL`%6 zKY_4Y^LjVqES_3c6`LU1L^G)Z79Mfm0E)BSQ}JX}D=w0Br|viOjCRS8Ttfx==GYE{ zo20gHjZVOK9Ig0k=8j7`drYWALXOOSu-V3CeI0|J3T`0jv&Dl4RPTk+_KcU&EXr@O zXo+7z3_zHcVqd*#fl^H7ODS3HI(|p@7=s$^$`(EK%RKeq*WJ%6<{otdJUp|1b;RA# z76*y_xFa(`E)ITmeiZ$fx9q$6$34+MT#A2OnH0PZxhrtk#zKz`1h+mJi@y&ma@OO- zMt!dpdeYL>DzoT&WWcs<+i=5qVUm5rLW{jJ`sX{12Y$K@V8_?&*s-JZ>R$*R`k$ZX znQL@xvW|iu*6_6dd6S5r`Rt0XU)~N|sh4z7^sIHHX&3c$zwB#H-%HCQR=gi6rZo1z zZ8k_RNeBo)&`+aqjDK6PmmGKScOMlm%7(Y3REet|73EG*IgPtcQ`%ImR;T6ZUaC?* z!O@POj}oQiFR`3hSB+<1!0KvHH#8G#dM5{Lb}!CZSI-CTbJ6Ew@>%csH!on`c5;Y$ zO{sRFlpQ|%rP6E3yWU7FwH@5-3lxKDE0L#bXlPIw7_u+=7LOP;>Z9w$AQL1ldLXFo zBUDp9uMC;!bM=Gy)TQrzx0(A~Pc&P6eX|f10BY)ZOYe4N-w$1)!X8LeZE4^Z6cKsF z=C)_C{hHIdJ`M*hMosRn$a}Z78gFZKeSC(KSI95qE8DJh{i1%oGAI*&Am2+ai!TXr z&@0X>)VhZBxafIMJ+IRp>j*3$CA=rEtI-}DNsTja9^{@~RXTukrgxWGo zbVvo_oGBLvAdHVP8>mPth3Ai)XQT2axez54nUK*sD=(37IAN!vGEYHzY+jyKyJFh@B5zYy=BLaltB--cW34FPT8A1++INl zwdq>eg3cz=tzlhAu!9|>ff8_f#^t|Q?A*_giPBoji8i1w zTzPDKNYB7Xu)0vR{*y+6yWv#EYXRin9c+d}X+5SJDGkGdzL zJS(EF-Y6`6w_JxO9Yu8)HY+OR+{_}U@g?U^fg{@9+lrj|p6yPjWi85)H=jS(rtkir z>Q6*uX^&T=e(A*$aMmqcqpI}#yi&81mt zt56G`6=kiYtUENoE+h{|`z)d8;Oq-7Q^A!2HFNpo#5x^v}}LoA3&R&f63xXx-LgOO$d4 zm+{S;H_t+Zv}m$N?R>PY_#~S3S?~s@fb^h~W~ewfKyxj5dC_>G!GWaBYE~NQ@wWSCR&>|1!d}dRq+yIyRcP! zI{U+2Oe4H@iDjVGJ%C=)XpMOfw})=IckSxc5Bqxn_aQm#it0^gce;2cWZC1?u3`B5 zXS@D(={QFg7C*e0V3S|nW|^W1>r6=+&C zTfIk*9f54uDNn z#vkn}m(~w)u!A7pL(?FOjO3|$0lK&MYbbUk`2}G64*lm49X05{7ip2CbN`txOW9J~ zoy7HXbK}%&wD|(#Cmu&26q5ZNbk`k_lyuTctE~jyZ|2dvcGZI{7ybHIX!|NY^9i;(Gd7DDwD-bcr>pu>CTLp#RdB$9rYO z96HookPFaC48L&Vl(!rCA@DuO>1s=)&JYQ-FD~1)nicIvt79z&CCo)|{Yj7e+wAQ9 zc}1(!+}zx-Hg3ZT+y*%T_keh7_V%#y=HfMWDiVm>v7X&|o0o&~+3gZF=<8f-UCmb9a2Xu@0^(dwTgID0xvmzGDZLJo)g&g->TNsfcI(@ai4dbkRks}I)HcOlefPqslXgX`3yUqU*|@Qo^d6vnS2mq2O5kYt@XPtntsP(i$bgt%%G2`8s+zvlaW^J%Ml zRL(1wf9>hozP}CC%MT7smz{Fi$P!tpx#aOtL!u&clYl`MVt**@L z)_9(c4b6yaq^RZ`TR4ONYAI+1VRwqjs5CUzh3nlA9&YFV{XfqL9lTyvhZz~`>grXX z`P8VS1ZuRYTfe@lf_p;<5S4k|YW%t?Gg`Q)iWhr$@h7wLC_E5k{_XB2C+f?hDFKuk z>z#bKB>#T7_`YD`-Z-Y(z`wE;2fF<{>vC;qU@Xyn=Ya#8X-f#86wxdZpihU*s~FE# zR_;Z6?nI0Gk9vF~gC^LxKc_t6vi|$^JI$o9p^@6e%gbhVoprq_rcGT@|6xfnbG7Z% zsRpf`av+!tb7oH>z*KC6!XHU72TL7<@k~lL(vFzf#a@XVvgPgRe_w#@9kXGAk}Wki zUjZBgG4au~E5kWN?BCaYCMsyuBR3p#apZFGUBe?vsfcWP2VG!AdFjf&OfBhlq~?FE zcKKA}b#;$Mb{SIg@!dOBWi`(@LqF`yna?xyTSn<)?D!*iV_kS>lgM>%G@!WKlWV87 zi+3RN|7X9pJz9U$zY?$f@PGHM&N(1sO$ZC#U=tH(C;=*6Hq7WiUko68nfv~^kF84` zHsf60%s|SH6;suevq;86^UOO|zU0xPb4*_~YR!M^Y%23WO8$Gpy}UkKFaAQ2kV-*; za5Xy~e{Ui;p+f`w=iI>^6SdGKk|M1os)uLnI>_US|e29E!Wgt zm@yLj;ziR+>&Vc`2m5yUGkR^ybbI^WkWHH@2yUUl;-&fW<%`4@m3a@XtsoebSjboK z1OIzIK{e>Zl^;KT#Md$WB@=wb{b_Qi=3Dz)Hc*fQegfYhe@3!JU z3cBz5Ewd(i`bR7A4*`dW%{kX%U+4;H#4I1Wd#@q_JLLXvl@%cd#?>uK_>< zHCo^JTMcianqqm?u_p0)+C;HJx>i(rWp_!q|M8h`z@;qqmtu=$SJk5y=^iuNSIWhjU6W_r)`p>)yJ)<;FTugU9epE-K?$dT`;M>TD0%gin!pd#&|jPGbB z+U<;ezr+K?=}59|5hRi*#+%6HZ&hK$6eBI|W*B_%JFX#}x1|vp;%F@^gbABg)oiV+?{D!eyuz5%QJ7;r>Eb|bcRXwFi2Q!bb|Nof}9 z^!VJLSoZC2GeyErqdsgUasJur`2W(Rgm1p`_oxvgpk~Dx`}E4=v*s3#_#94plCYnFMN3sRDevGcf zpjlB@T^v_?Og=sB>-JnZWi{F`r>kTncTZq>u2z|qMNOf2K~mhhabw7PE1-;`pz0}J z1o-BPlnZChvkCl9>B%0X0S`!RAPSzipQfEvz-f=ZJ4ar-X8UU??R5$f;Mo;@Jw3g| z`G1mZqpE)#P&y0P0btS^;)lRwGncF(uuVizkW#zEb_L_cjr+dx72@OJG%Ks10fzSX zLJL-3%{_B)*&=M`Fx+PzxpA?&8`UmKqf0@Iz`FN+URuR()j5i|SQ z&pJWefx46i;;{L-p*tAkq-5r-)(tifuFrhY$8|9nC-hnhs8vze$w@DrDCq~cYM_vR z*TX%`VR{97#)!&Jg!aK||BSs%@8_o~dnu^t`~?eS3W)Wg;ceHT&Ul@6E>RI0%q!o! z=#@G24RLjpz5RKY87!PI@4FCu{{lD1GouAA_4wrR#r5)}oX?xflpw;TcxA22kY4CH zDjA$|1z^D@(3?W%DNXHX$~$-NOtw;#=b=;Ubno7M+T~@3OI=iq=n`Wc4xf4t0~C5h z&vb>O<8Vr&zW4sj`E@z+r7~F_o$I@ErB!E(#9NR2Ok1~hfT z<4D#uN#k{Y3S*dvThuHf_%raKs8fkNCzn3^d2j>i)kH<6oTwIIn{tQE@ZE7HZ3?1` zP)8g?+HTr=UJYsP!RGqvno}Kg%UBM;+xPFQP>8a(-a%-JWxl6lwyAjaAv|!0U;|kk z7B72_=(2$&m5fAG9f(DU2qQggnZb|#9vJ>w>@nh# zq|}@i?WT(<=fFq3Kc_!xjP~E5JEHdC=}{EA09-@kCqK*^(|&@Zqa*bNhalVVSmev^ z!wWxHc4*ZFZ(L5{k~w+r!iM^vwcDINzx_GSZMHI87CYyl&Zu2NOJhWqQDVH|TO2}8 zME0vIeHh8N)A~1yEd-8?RO{7i2pvj7>4=|ue*u#czkL6`7W4~zdIMH8G>mcUHgB9< zw!-fJ-bOLq7!7PFLeN=;Fi7nJAtqV&rXiKE zg4lYZ%`qq#i!!E3)uu+ToEO5-8-hQUy_SQUGN56N%Qbd86&a(fw$ME;jgOioG9nN$ zE&%s0MmYaF(L7Bc-^lbmy_WI{Pp)nG2X(vjNMg)^f@dovomP#Lvzp7u1q)};*`MA- zc$L1z!N2oq={`Q`;Sm#*0)&TZ3NyXpYVZKdD?$9W;fBc>-;xDNvq8 z)dXPM+Ycop$zVNccR_!*yI@lpy9!&1OIofXE>0=ZxK^5Flb`+0>^out#H$kf^y#E7 zR^y!~m&{5GO$5dl=9}qjnLck9y(y^E^-lTfb#rbPG(0+BYW<8+cI}lf5Ny((6^j{A z+?X}m^eyhvP+hcqVfQX9s4dPlxOQo7Ct`avpIn)4@Yuq%o^va)=ZFh)D}4l0G%@-q z)7S2h2Ia6SvQc(5zN;?X!fo~RG|BqwnF^Zj#?ikrzJih1a;iH$?d-*BF*UQUOwk~D ztUYj`nLy+AWA=(8NT@MUPDp2OA+e@04FObJ4X>yepg2Zxn9fDk^@Kd70chzNt^Zz(eiUoow>3m&us>x)19XIeD3tWQ1am<*s@S#y5#^+@}ULf{p*Q=^BWMj*wO^;>h&T`?VtMc3U4$0>>zb175Po;`h9 z$cOAgiwLULRJt}CwrSAvNOWz~ym@GBY;23A_!72k-@fP2p+i`8#w_^AGb4pcY~ z^LSP7d`rzKkJrX^y%84|_qb%XAJE)(6c4!DCU0n4V-|fP8r%0zA9Hg*P5_b?S!YH_8Mi3oRPY6+&goV5)>BS*ZGQcgEbZPVC==!;H=QeE6pdA;C zbqZ$D;?f7$GUde7ZCQB!*;Q3rPuT2mb;h8fyOuAjd1CtX!G;T-G*V88GTO$|(=%e( zYYT$?RFG!Yr%$I*%ooEfT*ZGD)(fQoB%<@>Bipy+ku#ZIt!mY~v_FAc7_n$(WaLFz zG$&!E8lUT)@1Y81>$Yv?U_gIE*6@D6RUnX;46N4(C*r6OFnkUhleW?V@xo zt^3Av%jV5b;EBTtLDP^%AEp1sB(Vqr869lj)~#D*tFu_Xcl{9jCxiOzwJaHX5>D8* z& z`7&)f%ub~*%<(wCfxY4r3Qjlp%GvQrU6KrQM)zQ+5hF(Ix$DQ<^z`2&je`r01Y}?r z+5DhUix#Ib!3ALhJE-fh!21$(cL?8@j2``%!hXy=gFmmJDBr9XsmunW*rZ>-0-18* zvOAs&%El)b5)DEzW7x27m_PO$qhnY9QGK~sp8d;I*mA{#h6I|oxE**iKuxZs%_qSmU_k-=#fF711sTkXGpj5mBT z`&FwRo$EGuuqEZyL_*4i%dFFA+&<$rKN5oUjOCR{xKdK_>?LuH z0@;3v!>U!u%s^nn9cG1RYL z@HTV?Iu_>(}?OPLqK^s39M9Ot4TGVUf*Ey>Kcw z`OG2x--rsyz^Z->(oKE&^26Q3fU9y>kB-G!En1xSWC(?QpqesZz-6~IeqkQv(l0K) zeAXLRMTxJebV?gM_|Wu0sw{@rqwL%dd#Ex;xceA`+WH-k*bKE+>hC1&&?(*rUtcPC|ihm@KI!nyD2GC zWu4$&wT-DS9&;{C`nLP`610zd+(_ZlotFMqWHI*;tC(mC_8?3ml1dWtcjKZ%Ef3;a zHcl|5Nx2`Z02$^>kU;hg;AH2JS3~eA2fi0iE2U^I=UKf@ovCbm?auKIDtOYqw{Rus zf66%Q=cj(S3zqDROQ(}mEWfG+^lZ^lQQCJVq(eizq>)f|UJgkrWYUQ(G_D7h6kOt)CxL z;qltFAgYvW)v#H!1Ma z8Of}O2VJL5O|krpCnn(N(WB3)VTOI1cX(3;S8MBYLq0MbxR3Am5KrT8#l=&mP1}z~ z_1*FY!s>z7k`f~ufOgHeY^FG-A1t^45Az%zrk`E9CGm0(!#?o^97o#h(xJmhtj$k} zCl_hJb#`^lV@gMlr|bLkEQ*Q$TQ$Wvglf{iV@3M}R8P9}H)IZU%J(DAw>M@_bbq>^ zf9nwKI_x&4Cr9@sbZjVfp6oo>l6~7BR_M=d)2ELa;e4+)6;JrOVtMYfejWW+4+Ses zhVguk`-lLGV6yB=_jOlNsq!OmvZ!H5CimnrB2T7H&`7NBLD;QQ@F)6Cb)(A}GKI$IFNV1Z!XI9Jn)cDm90T)pKbbP9rVW1G9gc^si6advTOY-rkS?wNw`%9g~>N$(DaDqY*J81G~|s#E<(-2(fL z89)Bgs&(}j68fcg7BnN^V11I-GLM}vK1-&76F33*$pv?8VQ>*NL3?JYtz2@V0a>oLE~+&m02p(F)fihe}FIb@cQs?4pYH z@}U}n?jg|IvFCPzr?KC!>U?wh_G8sp2>1|Ad%qR^rUPADDa;DEa$97Nntb7wiwF^A6?C`>Q92>x1yq~OhbOK zyt>QxhXW=UKNgDQS~G@LS4K)X@zNac$FeL#!(+_^`0 zJ(G~wNd=1DhPdOsRPbEFyr&?^K>nFX@Sg;yge63;%L#eCopCIKMvvHv$>Nu)!>0|I&3hgI?YEWVM3@8 z6lNhs`*981H$U`hWerd}zP{#Kvie2_lo6+%KY#uy1sla<*bZ_?K#Ot&hLw!BJoa7| z_GfhxGS+5ZaW@s;y#9w^Ks>Wt=ksBD_txYQUPGxYtSD2=7A!bPB)rIQ_yZ3YtYn{! ziZQJCWhT&Jk_nGgrq}AGTrYlU3+|f=fz%JuL>82oJ#wV{e)%D!_G66E(Jy{so%`h2 zMkzW-;UQ$XRCf-(L~9Ojmkt7=TQ{>*FwL2}#lNV%PiCN@ZD}EALMORlh2c?C14ta$!73CA) zXeL+YdtJKi;Ss~(`e$@*FR#;;zs`^PF{^#Yj?Hx|e^TEZYl?o+bF-35_(B3aHv+Wd+_GVL>Up7gQLqSyjspj_q*b)TtSI_4%FGj4%1u3UnC7 zjWpiq4Iw*;R3Z@p)6$GVf60S}HZ83?TYm2pMp(;TQ@!PzKc{bFfc#Vm7%in4yKnj+ zi5y;S&o5GW!!bRP>n^{3C1Wpb5FlQ?Y{0jX!=_zSgi&+{@ZGz6H}cF^#?Y1$&6BY3 z)*mlV@n*`nBL*`CYLS|PXsej^&pVk`*d~@tm5ezGpwYE(V^y-DOwXa#%=PU5@`bo=4Rnw`#aE z%%T(|bcS8}kTGYN6LTL2t|d;KB+1{S?qa#6reH8`8$!=iX!E@UAI^Fql~ykQT$(qQ zmsUp1<}5+WNZ^*K#PoWE8Ia`{TsR=1LoronHD>odQ23xvVs`K;|EKVjVKV<54J%ZR zV($a?MFS2!^f?ZxpR>NntjEmF38w0?=-rk-_hRb&Yp00WH^jg{p1%tsENheKY~$tW zch=e3SULa7AyMDu!-0jBuR)Pe3Zib*sL_dv@&Zqq-F(Ex!sWV8dm41XWaz4Gn<_V6 zV;Slpu^FpTXCzzOO6J3~#xQkd071te?s;+HMfn(8I^zVPW9{uf8{=;I#qRR+yE4Hi zi>&dvY30Keo-`QfGJTLlRjYCD(Wn-=%MI0*nq;7$p6+{{U=MYyDz z3>@hr?KECq%9Sfu5*Xjj)>5xg<1uWJiOY}5cfh~y?w+inu>DQ3?*NXw^^d2W=_1Z7 z4!&F_Y64#@)9@U95#0ebIfRWXcce5FxJx%}2)QITz-Tc_O1!>{lbgr%4@{C*cqpz=Ea-m^sJ(Q+Xt&N%cCUg(3aGHErPFz;g25dO~BOy6?3c>n`^mj_BfCQbJ z9(7GhdCXRs2mhCnfB*ibf`P$c2Yb)oLMcP?YCSW`C1QASZQkM8qo)jix^?m*hF?( zKx>kW_~B)^eK(7XGP{Q2!Z&mRj9Au(4=3$n%9GzY1Ch#_-GypOONrTqVzWLQ**#_rSKVagH|J{nXIggI8O6848MTn?mvv(&^p zP+eQZM8b%Gq z-|yePF$L|KK&DEjgQA>Fq|st`#LXX86{Y(+jNKnVD^>G)lD~{vlIgH569nVzR^z?MeD=lf% zgIc}gvZfNT0vMNLq=MsZ@=};K@%25qBKHvB25K$Rq?w7eG8}c1wKCTEl6f6AP&)@+ z=E|K$ef!&5Ccn6;t`H+iF7f7qsV=wdxbVrOrs8Da-9RzDk=lyPH%!@J7RZfepDRvG?D+6*s(z68WM7ne|p?*S^)gEL)w z7z`M>yb2`TM42~1<`icww(B4Y$<)i`SDbaf8_w7N^dKRjvuoPJ%NqCWmLx}ddOt+> zm>a!xdT5nx*WYQx9^U+3lCo^H1eg*k*Yixi>Wf(HPHT>y!-uh}W+2y#dcB>=PG}J> zv{ZKU|Hy7giTaTq%6*T3R3<(ow~oeFY033YD}B|K!eo?HPJyZy%!77k1B2+;6_{II zRR}Vauh|nhjHFSnP+cqoHZYS6FWCnp#I!sD#Ys~1EFg>eUw=LNG309dZh6ChVd3j+ z%p*6$`e+-cj#%S)?`1v`v$bn--V80d3i4%S+=u(~7!$qCIpEj=J0bIeqWRxza&#_} z#f7t#?d_9=Jq*rwuXaBomXA4FwEnld?R<`tTs~+za4|opUKr)`{IXp}i>1@Ur=GDm zfin~hBb>c)0U}wt6ksI(BghL2^Se;06qh_1Ic~`Gh>%o)8+(H4T zo#Z>l9o;U5M6Ueux(=><;|aSD-4pSmOy)^yf{^{)i>H&)5?~P|1wtF(APT2Q62w<_ z?^jn&+QC6iVI9W%VYYAyCizGb7C1peYrn-$haYkKW*k<+rH~YJH!*Pn#No@x702ZB zlTqSs-@eO%721Tf(Ct#bDz(XsEig^>qgMxwv z9~mItN?~W~=oju3(vCj5ySRGHPL7?38Wz1?gKl2@9rvlKVK>!l*PhH?1=Kgv(mI2T zKf~p+1PJ-+>7iyse;-m{C~9st>BKqRe^jaK%2=}JmVQ$?e5Cp3X)QC5s*AFpyx$uy zML7HTvVIT#-c<9jfxCqq@JShV(Iq1RaOal`I#kqpT~uUzbV*4`4)&$qhaRdG!?0F6 zAt~OpArBN?X&ymtc1$aepF?D4($764LH;;XYSsU9(XY*(ZCA-*MvfDZ%9w=!BK7^` za&o-vaNw?UKj>)2mvF<%TEH;QZXDIVLkCUWZv4=z{#-k|a6WV>MTMrW{qg8u-^OrZhLPuz zDJSh@g5S$}a(ydF$yux4s_Bdn?0P zcGtgdh&U!s5V<+9j>_KSc+3T5@iF#EJ&#o&;Z6r9t()_D=K!Hb#>={xRs}0Pm6Db^ zPKTyt*8RdNZGNLv%wD9@8igDCSWSokRr2ja0W={;=BZ( z7HMNU?8XGzt)8R-qk7FOl+~XjQW`?kvMp`U<=}^R;W*m1O%{Caw z{q)8HkCV9QMbvho2umR_lhKmpmD3&j^tmw2Fm5F!8`cIRreWAHCV@}Zk#daU?Zco2 zC_sb;wcjm(TNz&=5=30XpfGRY!j6!AFrC@x)^X_)wqqAjT}MLdQ9A43Kx}tPfcx_5 z9Qa-IOx;S;Oat@EQBLf?aN)OYotA32lma&d(*NKE4yOmSrB|>?Lp0Ebn8GH`X6$NvbnOx9LLum5RgOLCb z?X_!Fr_PSK4|}odEy9;B>FQMd*?(csM=Q4mvTQnx<|FJ{FmhO;=+-QGdJ~_(#7gP` z@IIeQG7gbSZo9DSS>F#=n12;U(*?cKL;pWHQV)TsEuWw<$weQ4YGwj}YTf097e$OfoFh9h z_M4zog7#uGMJn;VEX178qf6l3b_0NXtq*Y{J%;-9@bs)`k!Ch&lH>1R&c-Dfm+nG~ z`Zy8=PnhS`L{Dn&(^?%TaG|(vJL#PX z=1iIR2y;V`GXvLsGS&QiTiZ~2&w9I#H8C+ME+{$tg4_1p{ap@r8=<2xCn_rq0DPPi z+u0!h$Iy)Gku$zHD_b@u)-`oY{@DWLb|Jg0`5%I4`9~H`q#~2gaIP-7!Kdt(elIFY zBA*sAQP2iAGBA zr=#(`ua;iWe}m&^(XLqbUHRtt(QSj* z)6lLS-b+qv$qU3Do0K=a%i)muG> z6Ft7MrORma&SdY&^Vs`Ry&m_QT0X{y39G_XH)|G5HSkfh`F}(jGx(F`H1Sj5qfc0+ zhHnk&n=w^5waDi4XFhh|S4H`+XNc*6=m_QGI%mb=>j4X~EK4<#Fwcjnbl#WX^>v6? z)J0DbR2@DE&51{|n=xlpak_B?Rmkj+JblLKvR} z?jHsuc*2Del1fY8#oXsP?q90QPAQgB(Q<||@u@&@EqxU^QL0YvFA4w$4unHxgn-o> zr`z(v0hz4vmkIKzc(1{f3r$`WU=(8CKdn+{@NCoGJV;sJi+6_%*ac=SrI^uUE$V!Y zLP*k1_-Mmxi#ZHLm zHU``#jzMUH?Pa#|Hzyu2@KEEkb?*bP=*&Fn+DZz1qcvS;DM0`m?;aI&OMwQvE#C&k zK+5l`RhfLdVI;lp;L5g>(i?1HD~BG^gW2PA{>4&XRlT?u^D33rY%(sPPiUyEzgUJ^ zlMsuz%be&kC;q?&HDd3>%MYJDGlQ0Uf*u*(X4r-xAcR`6eBjy6je97su8)0j=gvq_ z-*YTV-Sh?9C|#NkTpo~){x8HQ%uhrm1NSCufpz9Q!Tn6kTl&Hcd;LCv~WtooRUMYODwvLI9>_5;4VSJV-uEa-_`wrW&>kH=huhZEUf=Q%P0E&P;r)>h(l4 z-xyC%fn-3ZdrvM=f$KB!v=oA02p4jV<$IaU1J7~n?sq&$i=nm&EYIkqZpc)Y*Zm>r zWF_&jJf-&#=^!pGT~K?#MGK~jNxg^0llXEXKk~XZ>p*vf)|_$hIrlFtr6F0&LUQ8) z@ZrblO5|j$d=$RhGzTm@Eih@(A&eokGl&6;hvd=A(5=`_o@$pEgY9NOd9wPaDeO>k z7Bdd68sjCQ_V)FjsdU;#{?&%L4IG|shm7cRT-4P7NFaHr6i=sGxsQ-w zr#Sh&dGlsy8QtJH+XtjGRqWQkn>#;OH5O&5z3>G0F1Ry4OeA=iOAJ(9A&w;igD;$m zcWl|>Q%L}&`bX%83UpiEaXKTqqnFYHW6BaU>THnI!qra7VcL1*+9l-+W$HYu8(fEq z!DaLGJN;aM2^nqABWy;s6UUEdL;v|`gN+7fB>vA#A3gAzyzQnoaDd6)@}F-SzuKeb z#Y9hvwXrCLB8WJ=_C5tvQ&(3PJ!UeFZR4jh%>oOQjC#>DaEqj?v4|UsEZ9LOal&u? zm6HqidqnVkjBM_4d`6apx9R7HGnnEDC_D)l$_!0I z&SW>sNK+=>F(5t?6Th~xuD`}kt9<&$=2hfpW}0VPQtOsnnt$f6H(Por>)#GkQ92pp zak#$9PZDsy*|Cb$%Or8<;#BTeC}Lk>VPX9v zo)9S_$qL_0$RI)G?ajFL2TM%FlJDK1kUe70V0#K>TnEt9d6w1<>etsc{trLnOA z+yuOFJQ)sa+_6jy36{0UjDI{s(J%XSp%g`+FDk;|G{6bzuLpc_zcQ7m_E{j;Bxd9Z zSI-+5htuM6k=#%2f4~gw&J1czJU2b497Q^*wa&Oel4+qI)6plRjH(4$VqXyGF z!vEv{=sdMvAot@T|2-r5^~E8T`r?vE!ZkQ$ z3hY2XTF;C3M3hbrD z^_(oxzcS*m6_XH0?4u^o@Ntp5$HR4WhRR1YG~e2|8$QruP(GJ2&QDSkV&LSrAo1k$7mPLKAd~ zZFrQ_PtZ1F#ev6F#FH8#;yw{K3ChLcRr<#Tkf2a`$usPtPGIW6(;32r`S#;S3dsv! z(%Sm3B?HBFVjS+{Q%5{Pn5)Md{#Sk7Php*Ev0HAcA_5;nHAG{y=yFF(C^8=$3X8I@ zW2@TmsQUX)c@7W{v)gQ6+ebo==}0CCGnB7%pLhvy^1>kJCVM-^pHKc#@|>Q> zafiP}C4ElPB0^?%{%@X?YbAewJAdd+b?Ssqc^{|k8v;p73_N z;Pv@dMOMUaOa{%G$?sy9mJ;;Mc`ngD(&EuR2oW!@J71XPS-!H$GkMHU@kHYFN&$NJ z<&dD8j=?t3)82(&DsfWc@SCv59$?|g+(lSrv;4dv#vCK!xCo+RQ=!xvIbQH&znn^=`{`2UjPRBA*qZ$!Jh{%29t zI@0$gyJsy3JG zmIB#!W(7f=@65w`Ue~t& zE%UTYNiu~{nO2sW$gG59Dnf=-%8-OiWm%@mP|8f9OeJGwE;AWYQc{GXLP80pdOz1M z)>`}7&wloP-{W|X_n)_8?`8Up-|u~2_jR4ud7hWDuwDPA>eW@hXsacQm0?;^z$G6Ch|@IoL2TtH{aGhl?6E1?51} zGyb^E3SnF$wbN2zgYsyPjsU{OWTttu^78XXlRnL-f9eR<3OnCrMX`Jutu|C9LV~e%=b8_hH=;y~?s}#q&@?Jw+D_*BPi4jxSJs$F9!v z=FzA+7(PwjU2mU-m4a>9Un#mWd!FEO&6-_)VknQ$7t~KlStZ+m!jg3?N4L~BqILi(2Y9s0 zv6q+pBxq{4Zf73!hQz!$eSKHqW#6f!3OLhAe3GLoRjg=$|83f(vS>Nzn*7_Qnk*S2 z+nq@9iGgvp0iE!f`m@`xr*`OrCQ`%F^rDMHK3Oy^9szzU*uk;P#EI-2Hhcl2wCM^{j8LbIq z%R~S(2seJy-eI@Y31S$i4Tr9Y>|dfvJv6p7*g2?&lJT8=jgeWTaPR}X)&RZEASiy zfe|JmP>?j2G{By`p4xKahA(Cmft>B~;TdkH$Itm$yy_04B@x2I{m~7*{!9SzXnRUh z?cZj2V=B;|`8hen%R942@CuYd2Z8+bbabLkRp|4OifQWhybcC?LA=pX0<}JLE*OYz zC4Sd(<`FRtP*A+;@vVc2&^=35PNyB){Rv*X?M@|!vZJR5eBT;`SkHkY{p5)gBY|0@ zK%Ztkckc1~ySncO-*F^C z3*{0^g=Q;%9;)LMaSaelfqKxdvw^LAgUG&Y=XgO{=Dz9PzIn5Ctg}_h{(bxAImIqs z7Lv|a_2_w7h%y}aOGlL0iM2F@_3Eaw)O7j~FcE(EZ{O~a;rFc&Q#Z`Z+nT&di*he0 zfbDBy0T=@3X~aB!5RE_>@Yye)Z*a%X6VVtu^u9whKYWsQDi!4h)FR+~#o>h^BK z6%Xf_Lc=d~k&^^+m5t12mFjszK^htpcAOvFT&ROb>k4<$xQmeE-k%8AO6i*g$Z$9E zX`Kolkdz!y^B;j5LTT2jLx*G5%K-6PXXh5}JP;I=@vV^u%UEt}HPMio4j(Sf6v2~n zyW!dw@=U!7FMg&mwsuK0s6@W_p`+bqd7$gCMtHhau8Xq0#y*rYy2(wpXe*?Vaj7XYzDv#Y8r6 z8>y7(ar6H7BIhtkEdN%EjnX_FPv#t^E+$T=?P8mlSP&OnPJhlx8Hb9R-Q~|K?@3P_ z^bc%=aKVy2UhHvu-GHLi6cgkg&M*JqB0w7o<5C~7?(S8xP7b-}n!-4B{3;Ag)JB8h zJhacxaNh#5dIuB>)%_FNc?AXd+8IH$1@<)ua(cu;(7*?uM*&dz{BDy1WEeO ztJM<)lDq2FAX^q;710h<;I*Zi{B3k$_2ShfkV}tH7;sBtBE{~DQ+Y@3?ZOE@yLRm= zUcy>nw$i8XR-8#;Cq#oJ4cQ0%YYHFW328SC+)g;&4ID7w7E{+_yW=U(dD#xo9l~=P z`ibm1O4V^VG}i1-IuPA>4%>b4*B5IDal$s0#15fZf=I)1FBRQ?@{x~5-@DbRWT!4O zPtqY5zDW-zuC6GC-f@V&y93F0#g^yuXp))uVv=5CchV0@=(1()k!7_Fa-O?7yv6e_ z&Uf3L@Ai7j#D!N4zxa=v6bE>7?0bp#uiMQQX9sT;4c5xdPpv{fFYDbUI`P3bj|t8P z8})xW>FJc5%Eo)@WX_LV@JI6(xsCj0be(KIX{?+|0_1Rd?=8j^_z1zeTpd|(dPe{~ z?BTba%iObsxd^%i6a?G+xbp(qbb^35?b(;8AB@cJ&yY(qJat(DiDqaOOoP}`tJy(ZOY`CgF3?crZ8;WZLTq$ zB$rFegs{V)?Iv4fE&Fx+lIpUN>Sv;T6j^Zy3G($l$I$aG2D|zUuQLmpT+*l!Mq{$J z@g(A)35oWTleS)xb{@4lHArfDvV5TwQ1`V;Izx1j-HoHOv{7hmF_6fBuV#-(&ve)@ zVVffac{DXwSW-3`e`d8 zT~~DoqV>o$B%iB4X;EJ;Hef{YX!J8iAEL`Ge!im8=u+WmxUm3Le7mM z2}_3lgKrQmhaX(8ua*he1MmhtScDnLC-*sVeUW8Dh&6LNu6LOt2gqR|1 zb=n}ODHB93@hLywPnUh}0P~?R*S$1E6fyvnXJ;3Gi!N8wveRTIr%;4fy83DLDuc}? zM>f7_mH77e8&6YzumL71n}X2fI?(@Ay#VXi#Frctc5{mf6DBwgP^B;8@s3M}FdO0G z9D_U-u%8-zXkpR504V#J_P=d66P|+orN1X@c1=~uT1$NhaeT+E`CrmnR#^Wx8tBxs z5-EnBFoj4*Lx9|{CxFcT@wMgqrv5I_L#xfVpBR4f_w{XVQen?7=m48^Ct}f3mb2W^ zp5XV-&4T=89_K3rR0>@5wYu7MyY;a?WM*EMq8^p?_TRTfV6s2Fg}lX?3L5f@;#b8g zve;zUuzQiQ#0g!7B@8d)il+9tsj2(F*VP9iorIWoqcqn z2i5O(|Is7=qEc@E^9^NqgYotUj6b5OVB#y9PP>3m$ve77*j>EzNHPv%he2|o6yxn; zpz{}Lab_PNdsa&e6G-QCHbjIbfxuZX_+>-H-^~l^#0q?6SyfVPMX|j>z^04JaNdq8 zG2aqW_C3oB9c-shKjdCoqEcNsLmvV=iQt^Cr=_)Rf4g6QHK!CnkH0x8DymtLd>p^> zO;pH9;AoKt-q_H1XK^7|j;|1_q|K9@0yW_nsf$Trk){!C(nsWO(mY^biWR>mQJ(rk z=a88&C@^k!9`~QY&HGk;(dO;-y8pO&rU*yIGMP6lhbD`unc3at%YdXB<%rA_XrMDk zS}``J#3z3YHUxvAc)S%CXBhj9I!P*7DTK(fgiuIq&%h@D{>NcDSS`=5^ThO_7f@Da6%{c1;1|1_ZY#wBAXp|Y4Q!ZEU7eyvzwnvUOHjglKiZ_P~cKOoq?=t zz(R<4Ev1NQL$CS@Woel75jd=p$G!fZH}nF$30>-GhtxC9BEhpIQOs6hBRoa%v(jv* z1R+&`dWp0j{r#5n=g$)?Gv2>%k2u97o|TKYRUoM=OD>BH^&td|)2LPb+uUsIDX-z^ z#>)yy`5ArtxD@L5S1(^SN6&3Ni}5QDVE+XYnPS#%lLvpA68GCX?&3r4(jyK~>2ML# zDioxJJ8tD>hk&eQGb;MQoeoh>w-?D%s{Tqo42dkmO2H)10!9XHR^O0WB$ARA)mE|h zG@@_#&MD#aIYhl&NTyZQh_ae2I)V4s;X8}=e~Y)kF@ailXXMRF3y{jx!;m~)rnM8i zTh*_>wfV-wlv?K&=7!NvnDDDG6jVR9I|o)%Xna24a?EzKhIxsSWX_5KKmR?oF|UJ< zv%d($;gZ&OGee3jZFg#I%l0ZsjXc(N7q5f$-31}_sZIx!>EJ4dmlo{-dj!A~StO)P zRkrUN_w)7j9>!RGx+mZq#fl?E&tf?@kf|Vw7zlK4wl}Bf^4j zsRG62h0G6F38`81lB^|68gxX)MpHJqby4YIDhFC&N17~oXE;XiLlWjIj8j>^;^e}Cb3EC)1d)e@cuyH5H(#5~HU0VlplD^RZn#lbNlrwJQb=!x)@v-YlO5-$>c zBz||UF_!cq4=eftV)`|N+4*Qnrg};72kSI)R_cn&%rueSSdszkzrFr4JSM&AqxHs z!{K&Z+=&6Ng(fMCMxlz+EQ8`*NE5=VQvo)XNq`ley~c4?2-HR*C|z|Pkvxl(;Sn+n zkrBzoCz-$SXre^IQ1O8lvX@A|sr1%s8TKQ6+@woB-A`B-bDw(vU>qc*f>z#&kwO(9 zQMCQy_#j!RI0GP4_tBN-B16-Ub0bsoPY++e{?+?MfJ8qXM$XPQ?1Xwj%w6y!gfo?d zcZlN&QM!2qUx2J61QNbNCoZ>j4qoT`o-KacP9%}J7xFfU&)847BDZsTcM?&Gz9YO& z2*njyr|>_8!Q*&Mzc!-!t-3}Zuhl(URSaLXdo4Pyn+>bGW3Pt1op~LJCLC%RZlC%L zrB;?k63FV$_RWh59_<9t?=F6d zX#+eE3|9Opr)j-A2v1y@6xJf$#8);6 z(N4It0IvSJT%;iYUlI7qhhl#RA%5(L-ruGdY}+}mV#b#>8&Z$EhTwp5?lC8PC`181M(x2GgxU40JZjOl3KvgsuKdXMR zf;mMQYEJ*R8Sa|E_aeuVjo?Z#qt&2Y3MEr8DTL^Q1X$zVH3!LNVd*cq4xglMsKqY6 zyb_wd-EL1DN|0c30HKhI-_e2;4kph0rrFRlN2Bo&Ui|eSdA#n*af%ZI-G>yzYP`MF zt9+v1OMfv-78Q&c2Ad;!S(*JvUem$ksRMU(_?e#$n)I>4^ZVBXj~tb_@$86RAODbP zs&bynT?hL+$U-o+C}?MRZ^kKP6P+>PcO0YwV0mlCd|2bxqJJ9TsYsZ{Z zInm<&=Dac@i7JK7?A`_9yDxqDm|}A_?cAFZq<|z|WoPSyr;zJq1K6-_#C}D>9kgo4 zo09wJtgNOGXGQ8%H79cV?K*7iD;@(P#kGI~^-uGVExH_eue*2obBTAJaaT*QHbjug z^>cBt%ObceX8jyhY4W2>?O-q-0@Dd^k>T*qOjJ`}A)5S?p6A9N^7Y$>a{r}GD9s~| zo*&X^cRiR*td~&`hdM5OhNZ?);ym^AokscbG8FTdMmnS z8X2wGyC7FP)q|P3k5SRQ@M!s@@Nnqd^xZ%MH1o zB0Z%Rg+U63*X&WsoT!PR z-SLtDjM+5xqMY#NJ3uc?*^Uuq&C$qPFDrNeJsK7*gqSZe2$E6w4>9&_NS#2hJ?3Ss z6*;2l3h-DwzM!pqq=D1X)5vg?i+D7YX=0vtLE0kvS8_f6{3>k=Eg6oS=&qczG=#}$9OzM(j6I6BiIg+Bs=UhVs z(ZD*hcUo_0Cot*bG`m1hft6-MG#!H{9zp*AZA0NsQ(+hgZ z73qpf3fG^*B-K)BrS&Fyd_snb;BabLM>z5btPZ%R+fneep@R;znKMM5I@{v@t=qJQ zq!V8MNh}=`$!W_lz(^n0= z_OIVsUR5ueJG&PkjvZzOWy>FH0s8KcrBtZ}cPVj0WghpuDYryC2+769lI)`-SaEQ)DzB>u6(vF^tuCy?nTuViG6U zn!XH3$}4*e2qY?tybAlCySG%8UddjtU15_E$3WA?08z*Q(l{xTF6pWB4JCW&U7SA( z!Fd+AnW&}M00t&Kn4}=`E%GTL4U>HS{IuOzK>+m3*a@M}+s42^S_K2O9YFyIF4bB~ zWHztGOS2mig|2me;T*hf`Xn{231f?sRiu8161-J)nU4G=%pOGGe#uVlKXt3h(-n`; z&=ofY@%nFJ02Ic$m+S|-9?t6X)x}I8i&BrtE&HLu&zY~4a*ni{HJHhTBH8&Cd>p2oNL%X_JQj2jI2^Y1-6mf0Yv{=v3Z8PXwth|y8K zit6wE`j#OE@&~<@)PalOyX2EE86aBmeW-d2|M74~m0-hpE#gFBN89aj8w5LM48-)7 zDvKM{BTvp%lM(kxKibLmwAsW!Yc$hZXle_2E0LVLtxA-pdcwkXG{>Z#gK{8sBM!75+d{^8V-X`PS1bH3U@R4WWNrV z`M=x@IbWqHpeZD%iMTe>n7FfW7j1`MTk=TsrDK&d+u=A9rGiD;N`WM4bprU6P*i2@ zi#-K24^W`DBmrgaI;o#A@IEZ^KJM{iIM8Bt!yiY07LUU9M+$j7jDAF+rIx|UT)II! z^nsnEwxw0{8S?NQ1Q-Ok2b_)Ja3&lF%XfZnN+|(JOZZ^<6xw3lBd96pv>-YB@y&^w zO+;IJv~iihJ#-S^0TzrnA@Qao5ZV+n(Zm(%%lMv}yyV<*7(!VPC^=dWHcDXxz#$Tt ziyv<^lunQsiIJ2hmXQZC4vwy;%pMDe^5Tap)ufIu;~Fg#qxZu|b-qQwTzMmS4B>-& z*T<80pkpvX)Q zF~W@8itqfb08ZvE0C1*?bVIEW6+K4cpyGYCY}LC7p-!yeG&RTCr;&6_g1eUhSWW+8 zO1U2|4L`oozbcwnZKy%_lhaf*auv^)zO9R@9^u(>edEvs?xN(K&k30VpwWQ7+;m=vd5YSlq z^;Cr7I|5@0CPDQ*zOBRuj}*JCs)~uA%$+$A zW<5UMS1f>Z#gtgqop6x|5}zum@`9?&+Y>jC*xujVPf;71`jWtUZy2M?)+Yv4D9H#n zu<})%G#DLjLP|v@cze-VTI&O-PlYcB zwkqb<*30-!OA}QG36fJAG?+5;k}c^|X0x44mWSrteL@at?-ozn+5$8SMhBhno!CgW zsUS5w@P>Z#=KVMZANH?lH2r#zT&Os48sli|cg)*x=RN#7#$vyoCyhP+!hHyB8OE8^v@u6gos$c}{c|zeP+) zFIwuXGW#Wid0;(@P4ed9!Hr17T_ozL(xkbAN!w`QHaU~N<%V!`S$fQ~ZVPK14Yy?ci3p=FY2SkNXF=>)~{yMJ`tT>V}erRI$ymv{xl&vOhEO9!!N z0E!!p3{p^4)`u@Xts9oJFvRqwjQ;ioh%Sy3H;ioDD^t!S)8A<+Nb|8}+9mUUQ{tPr zr|q+YJh21j0aS%xnlK^!DvDuBBgwYJ>h0`n!3_b{T3~MpgFtYPe>CepI`RBi0NK+Y zCdqni*`j^>`%h|n%EXtm5!8`G$x-fIdOTRn8n)x~8`)lLSw?p$naHE(E<7I7-(&qe z=oqWktjT<`2QwNpsPB?U5o6j(r!A?;wVXjMr^Cah+D{@bUqIA ziG52ZscpJWw2b{SpN24Poq8&!l;s8El+u~AFa|| zGF~L#-IqHVZZG}0vpK;xWkkZpCPp-g2p9G0)R{NIMNaBq6Uyg&l9IPX>hJ}A6J*3m zqhpO5H~x%VQq(>_Emp<7SW463#yl-rS*@W6)f4Fo#P%b^kmQ=TI+p>*0P|>W#I#B* z*yy5_k(bQ{Z5T;hIkmvM4jtM28x7~!^I;ISi02g`t4ru7W(xVAI^_~}f`Y(D7vVuY zhp?UF8kdere4`AV{PFXM6RYo6Ox51sr)FfXcX&C)?VJnWID3p{pTXcHGH_ea7#daW zO~jsqj)MWz0_h=hTrDXWQ^?j8o{BJF=x;uZSGBTISMBJGcj!BqMB+e|zF;s(GJX=a zVa@<>zN24zHs?$0ou`O;%$meEM$bgND|Cf8OBfj|7!>${)S;jPesEcVe8U&!P7)CY zcYr~CPun(-y%jxCpTItA8nxx!QvxCAiW_LM7siBkAZbypU&1ACQcZCWQd__*Rs4*vp@~KxFCud&b_PpQev9q3AC@vC`MwkkdywAk zns!%2OJY9;cKV}t=`Gz2j4QV-YZ%{FNPB!DbjSuKVyJ|!_uIPySwIrH*&HQ#0j1OD z!~!bt-CaMv9LU8ROR9V0oq!VNW||i(Jb*NM*uD7e54>^mYhetH7^{-lN4%* z!6M42DgznQxDS+TLK8cj4tINWjiH`<#Ds$}hb;zjD#!`le$s_QV%u}`=1^a0BN2-; zwZ_ro6Ki7-2W_Lpr$r-6%#kwy=DIoG{xkqKZ|C?FcuiBO@oyzCNV{qra$l_`3I2aR$cDNV>?NoXTWEqewucenlp~KLw5m;43+$b|#-8xsI zi0UV%6;C4_A@>1*A6i#^>6}EGrx5lA!r(8{eaoW)ox~N25|Pla`Q#XZR$9Jg zdNTQUy`6md$0;dqslR-Zo$g0&1b)P?jQd=}OmYMHF;4zr!XVETYyv_~#!QI`;ur;* z7sA;$=xk8t1PVRUybk1m0$u#WGfCnIXyUdn`98{E z@SfC0Ii%wv*c7j2Jm!xudZVdPHam0y?b> z+mq+SXk4wP_#*K+NM}OIpTuoHuc}AK*au}2M0ubx&0O`1{3o#bzCVBeKLLCmOPTM( zIZ*Gb3kY?un_{y6h<>J+VT;%uW?Ta5?0kZU_JyCGZ64>xfTQT;K>K-w)V@~=q%)UU zKzq9-2BKk?sEdq{!$;+uLUt$g$#0j0fuj0pxk}x*8Rb>5hlfY=&qmk=pr8!nnQ{gZ zcedzq)AUFxWo#tpP`xHi+M^A-obcoM9_JfJDh7T3+k{(}B@btqF@dwjnhD`ue$F&B z$FoS)ai%)xG;SwmhNMC*R5LEB-}|{0&lJtA7TAyYR#SM^(B<~g>Vv?w0AkWsh^+JS zhr(Zi@t`Ya_->cBVs$FnGn5dkUR}%HN_qZ%kXm3ZYR}m4qa^5pvo~4(^~JkcWtzOJ zS6jq>k^E2?hyG4OemqMlS)p#+TXZvU8qBFOw!*RsqrRiHBUY946i4D;h7D*J**8=d z%1O3D=C?gROWpeVEB|YO`?87JnF-FcQPVBcx4k8G`_Nr*u+#>6O2#yLtlcN|?9oFU zZbp;B;n+9%XAO&$q|I)RpC3)bLp{13Aw>jbf_acnBE9KLi%RBryx)ur1U;wqv|~Ri zPJ{S=7V6)4u}xLJb~ZM=SOiVN>1WuhX$7|!pSQl%OHQmGb=549=Gun9Q zDXCi^iP)9yaeWoVTmxv@dM1+8xL7AH00J+=+~sqoqNz(=4Jr#wF325K!O6%k94@{b zwlUVzsD<|tuuN37k+JEIFBbjOTiv*c*k)3uGZM+@W&wmhaVCI_;jeq~>q{>KnsW<^ zu7VXBo-ynfF#bF8(i0H_G za_iNtyLp*=DTXKZEWhqI-~vqmbduUy{D$aih;g9IMZ$Jh7`4JEfV`=F{?8@qbzEJi z=6{e0p!Inb7ErxWH*;!Xfs7dcvzMGerX&fJ!H9=b0Rpk8emq*9;APpgn2|i2wK0mG z18m^&zpJ#o@`y8?L(3k?fB)0y^5+x&b^AaP(y|}(iX(si_W!?usuWjU4!xEC`V!eR z6V!+HtoU(2;uqIvF$om1o#S9hddw?disje9wtQ4oM(7I?{;*sNe}Zh zM`^~gdu!~jdBe@-XtMYi9LV)H8XlN2_lFK7vD@p!!|5V@x|-R}>;3B7yw`WO<9m?m z+4I$sw*liiVtR0E)X~^kM;q@Q^0jBB;O%i~_jK>L2OL!9t|7D{+Vnqq^h9Yr0JRiG zka;7I+kJ6{1vM6#V)QVyCc_%Js0~Rh>V3?>)qD4`>O)aV>2AnexOe>hU8ef_OE#1vX=%XC zqt6%pqR}*Mg2hTt(0^ao}l|*4t{9)iOCY{C-&)Kx8(y>aP}99&($BFWkO> z-2RtOzR%2njc6&_6J=F;k@T)(w5uvDg+~C|0M<=qGOj3W;(55WI<&c|GVWxR!Pn7W z+~l=8Ybd6Vk+!xp5vGz*<1ZFd&}#Qw5UMMD{Y})qV?N2pxYe}m25wQ=PdsTBvqR*0 za72mK7wCYsCsPxjZ>ZxkjYtH0v|PsbY_)0qnj1**y1iBp<|RgIG>TpEGTo)TM$Ld` z8Woro_8g&50w+BbkV>M~Wz^Jqx-sIn^OhE6=0=IZff_%)U)8bvsqDwR;t5%Ls-ORZ z7V5v=;7aNWvsMq=mExQ{(04u!$X+QqIXO~9OP37T{f^;eN(TBG`-N)Fo)P7wn7#`* zf#SIkk3QVKj(+H%rfjjxH|*Mn&eh!}edvxXC4?LwnCH+1teXSn=Ny=_rF*-l{1U}pm3h1}6=2dbF!46fK3(gW4vsr`s379Y3a9BhpDld96 zL=#`CHSvh&92Hkhj>b*8Qn?As9FWv>sk>VA4)5NtU%%pC1)2eYl_??gg5JViY_sBG zIVE)ia1vQ)`=ZYRZm?V?7}ho~B;bvZzB-FU@`~(SuX_SL0kmYX3WT>WLLkcL3EkV7 z_YkUQ|JBt;0y6|f^}*IS9MIz;yx^L^dSVXGw-Ij~49(`ex~5lyyJ~w4YLYl}m|CnD zm!7t?qXE2OS^BftymF!EV^ASrI$TRSfua=u$x{?MA3lydjb2OVti{G&f(%OiAsijs z1+^5@+C!VTl?YTbspY5Ox~kddN@Bg%6!r z#mi#V1V!!ESjbe;ga!*bqQcB++C4zUCGcNnH)!cxx9>g zhF|E_Nh6lB)Q+;tMQYIuJ4&H*5V57P$<3Ro5*v}4S$1zQ*G|x>c{x}q35o3K{3YS&n(a4+V;=$==Qi!=T}Z5N@Hxlg`VRna^5`-Za(8Z;0K zF9;<-M3BR#&+mn8{K?_@8_^`+bPoevlg{V(_RoiZK^-u}m(*F;U zeX?PHhA$Wpn9v>8NFM`Tk?{$8mXdDE z+82synX~Z&UcY-qA>{r-cpSOnE#GF zCi)nev>|mVuc@|9?fLyN3De#)x%@PCv={U4_w041nqulB9dts8*wE#pkbPwz0{F-` z4yJCIiM=GdV~f9qdawxoAP;PsHUW|ikkkQmaMC1~jnW8(PTg91t4@yHU5pZ4E>`Ud z%JcRGh=Zl-itII8d>z4ZrEiJZg>-vbHvL5@nY?77cD?KV1!~{Z+7-1bf8E39(HX-5 zR(>8jI#sJz_58hU`!jQzZU1e5tKW81zmavVW$TZ+Z@haLv`;@X=;kWDmmZ$i4wYN$ zskJ}wj84^=qczK=epxwZoc;No11C5yE{SsazG=zZu||)c9qIVx@VRj}JAK)6o=~j_ zT&U`j+UU}|ZkKWS`4cw;a?9;SD z4cGhY^KuR!+-a#CZOmH?kBNziw}h(i$&oTnt48g((g;8f6O-gmpXN1!QkpIZ@-@QE z*WBs*G=1;9nmd2~HDZx7q^HDR)?|fugczslk65KYt+R9Bl~n2P=iz@hKNiH$`R{%rrMT3?B& zYI6;3_GONoJh{WN9QzqF63<<(b#VsAWUZ!6YbvlRD$?QCetIc1v^pu!(t3O2+|^ns zYY$oB8oM5Z+T}w46J6JqWXfn`Wz)88kFyqVV5lkYQX*@DU%6#F*Ut9D4QMH*(Px%? zX|L#X?0C|@Mf1hQOKEjyF=Uu0}x^9Ja&W=2Ma4V@dl z!;LnczFAQ>(@IMg26y(3xpT)E8k0`L7u?FR~uW~M;QfNlD@--|3bvLT3+$d77_3G!B?ZB71X_J*RQ`$u3B45 z>+#~twJ*MgI<(2hhn4Wl^MKOttxujhHay_J>~$PWr=a z?k7um%O5|xuXa}Cp8WWkU#qU7x=>~R>9Mx#^Zj^m<7?&1=CZ>btX$yQIS>0vNo4FdD98lkM21nR3+n_p8sZA08ws`EOH%6lrrGAjffWD(=%xHz} zIyK#do*FX;nklM@=C$M6-0WMWd3-&xGzPC}^<3cOxTfELpnk`~%l&wHd6OKDqnbd- z3Mfb7FRRAaj{5oL&G(Jn`&E1GoY=Y!0qfRPA=jvg%0cy*pIgQ*JXbhk5RX#qopDiz% zh1R*!m@#9tQ>-s8u18y;AGGT!bLQ0K<5-R#eJjv_H+eeerE)2Q#Cs?q%~Padw-u;Epf0qgoI6976jK^3uN$nfEV$p35@ z|BmDxw}C0tFNZ=yot(c1?Wv{Cj#_Emez2R{S&OG6&B2$>Lu>Glz+IH9e`emRsvend z7Xlx~yN(?o6~9=mrpu3o)5 z)~hQ?q#L0o$yvU0eC9Md>wKPVOuQKVIxA}}OeHl%25HXkGpg(LWHP{7nwm0OL3Rx! zw`-hE_?CaLj;e4``<`1wXCaMFrPYn5cLkpaB+UzY(-gAKnP1Y5=kk>5tZ=%4Mj7+E zLluw!x7}>D%u(=mrboz|6L_}9bQ%_!^kI_DAJH7`&$F|uj2blxoK%zI^(%y%&P{|s zsfAfTXhmzI4sd%t3kw%_>(S$gwO2w+dDS+m?V~9Jfh{d9D=YgzRi-{ZKBYNqw69;k zR{rg`-{LS+YW7DhdWUB#*QilX834V10EEkYJNoMJCA5e1l(l?ePY(~JcK!OTNjz2& zkrW*+=m2Jjm^M<%D<3oalv;6V;{aM~H*DAj`Mtg(`HkpH{1-r8tc4Cr-Gc>L4>d}2 zsv_1-HN4qgAR|}S!ng=t5U{L(mxZD<0GU3{%ypp;U14V{wOC+vKY#wLoxU^?6k`<# zx1z(efDSsbK7*Pcf71}73 zAt419B}?@&OLA^_XM-v$S4f8BO3~@fzBJv*sV%+E^D{~-=D+FmCHcT{5c%x!Sr~7w z_4KU6NniiKfdlFuS};mu3^kU`A=B;GeybP9&{}fg+I$gYSZ$fhqL(vbGg)^P?Wz zM7oxoMh+TunStC94&HfIpnV)i-rgEC6ntau#KinT8$;27>gM-EZ~2JpHkquHpmV&O z{)>+I%?2;<_xF!KEAuJ#9XK%P$dd10PpsXzF{zu)z*swB$9ildBeig2+ySOJwZJLs zZrQSBA7^k}m)M878Y`D8*KjXwDV_NEOH(FGK}60HCRt0QI94K$T4c(r_bapVh7t%2LF!UN>ihy37MbYn?0@7bpEIX z7~=S;k(iY!mFYYr51w>#vUG3y?3Xj)hza&J2!r@*EfQph1=M%TNE8LxG@z+XrP%0HdK3{ zb$!B4qK|gEQx=Jvy2r1EhP7E02D1bYRVtsgZqJqrns+@{fAj4DlP2vkJr7lxO_H2! zb@)5o?U4LIkv(cQ?X5v9>jzioRY`E)yLND&Y#287k>*A|mxr4~F1(^vOQ6!f@*ZQbG6_+C<9^eJQA=u33J z^c@}?x1Iut$4t4fI{!zq6;y$ux9`lE22@?EkwR(^mVfQoaq-?|iH{yJAGr|f2 zbschWLX!~Ac5Gw1xxUe$Ix!zLs$8@3O?oeVJbhP<&ffcBOBsBohXY}}HAvPfu_IQ}bSrn(=oTXNxdW z&%odcOZCOem%o$B@8#`DatDyYoIrGlk;2uKl!pB(e@7pICcHmKQv9V!L1|1sy%5(8 zsPf~^wC{t0zr&`j_~CSiCpfo+M!Dr3uyC6)W$%-3cQ|I$6e}yMnf28R^75{NraZc# zMV%U9?cCU5yyYTVl*p(WuO z2L%Of)vg(C<`OWAlkh8;hhl%};9@to%XjZq=lnYq7B)6{`+%v*UA`ertNcKw0D0%KGSbl0aRgQr=DL}ll8TT+DEO|#K=ktAbKF>di-pz?YpRB z%_y!^KSSC;_+Awn+Lr6mqHWvi#BaG1AZKfc{HT}C4=g$lvVwuUc(_k(L81z*ox22S1tw`!QqG(?^IZOQ zhGK%UJ`oeAavm|wpArQu zaX`G0j&)1}aSNGnP+#rA+_(>`cbwZm{TZZ%vgA>#j;BqWcQcC8a{L9$x|8gV1-$N^ z!a*MFINZm2wi!9CX}=+(0xIrlRg;4l(&{>TQ7k$&1-B?Kn7z^W?}uGC8INsFc)4%U z4>NBNyb^l1>3p4qpKO0}*Z4|*mJIb;wQALEepW5^%qqSYorQ+%GrU+k35;kK(7Q2+ zuim7W7R=LTi}SP8C<(LPzD*~)CQ>_{zG&UvJ#N%Y3tQWkOg{9)2WA=o zekggakJMrOUc>XS|rPb_9UIsR+RB27-1}{JTItppw6^w;8 z?b-7SD`%0LTi^CuuO=ncr+OT0Ya8s>NDF04Ldn3QiC4R>>FjW%L-^@q3|cv|p0QJP zVc2o(4+_kx*qvMGsUuGjZ zstC!#7!x(LtvgZ&PnpsT*8}`US8`kWB9(NX+yO-R(YAVXrf`foroJW%FtCNdd7Z<2X1lucC7!8TDP<HsQ2#Mr;aznidyO{WxqG| zRu&_^=|6U?CR_DIZf=K1j_1>|wXeGdIUJwp)9EA5cjuz?Dt7H)eI_vez{!(!z>{Zw zNlGYg+pU{c#flXfo;`rjdg}g)Nps%pnFTED6PCW!bwp=F!@h)*H*epb;4pjl^t^2T ztdz(O)Qg`iHY9I)dG-{t*YM(rKHh;z)&AOREEDtdS@sSN`eXt%o+mj>7w-pUMB3;Q zCe@98LX)Zj?{XQ;F}Q+JJ?4jap|3LyIZ;$pboJ`ha%6;8phF$(G;Xr_UL_ zdgaP;H1{?LIGwXIR-zLe@@h@lh!wwWZ8xE!!k|4sH7fV1px%d$9Qj?Mc7o26_Y2Rx zUKmx>nBfn{*#rL5hm0Sujk2zaVvrR6GFNPAO&y(jE&r~?+YB->S@$Ge|Giyo$wWP9 z^J=0HZqjGDH<^i)jJ7*>?9e7rsD$_MyX0ljDE}cF687Z>T`O z(nk#F+2ne^C{PrOj&H|YD6ZLS;`gM)L`_QhZS?Kqzo9UjN}6*__m`Os{yJ7*5v@Ws zbeSU=34&JVnHo^XUc7tfO|G+_3W0pP{D=H|wtQJXfB(z(?$uyC!WB-_k2m|2)K+9W zuo3o#h5Z7eQn`Bd0sZ=^LvO21wuG;>gHZ`7{5~WFk5AfG+qHW)wW$|hj%hD75)%_Y zeEc}lA^rXP4Ukrzzqq=Fb7HW)y&hz@xQj~`n(gjUkM7@W=SDDR<&H=T;qvXhAwh$>an1hyEts!ZoklJ6Gf7b>oqi7qX#>UT15O+EuNLLE zxo+d)>e`t?H)xf^ii3IcDpjap;x(tsu98ZNx)%@MDwqlPNzf>x#hv&zhFH^61vca0 z?cBMufD$pk9~SUp>(=_5ofSc1B>oez=FXefkH$u)4~3y9@$0jn=~J&-v!)U>ZA18? zZ6N?XnE{B4wlkUoA`Y`HzJN}T9@;EFS)JkGouRtLXLXvM(1Y8Z_u<1PUolmu=q|^t zF{Hgp-Ls!+W3G$KlD!?uKU?-?;{4oajr$bm*Tr{Y%FLObq^n1FIgTovL*GX50)z&*U_UXs@|=)i$1*RGX^^?lhcVA+As zU$NR+K|^*L+@?bzAy-LRcx?}A#ip43BzD_&g2=$-DbLw3kjO-NWHV>ZMpDzMFR!g@ zx8W))6mI?3hXEz!IbZ}(ChY!B>Y>@*dM%lerIppitZY+k3~*D}2wJuuOLXR--sD?+ zr2QVv0wD#s#tlOPzc_{qFur_#vRaAVodi|<+rj;4!7)#Ue#R}Y6+HFT95Y<(7VX;A zprSXJmH+v(Gd9aC_{*uOjmQ~z`N5+{Yfut(TJAc{#>Rt>+q`4PI;2`R$<~iEnd4nr z)Z{0F=o*xMz1e5ejvby*E%$++L%O?6;}k?=1sg(0(QQU8@mUa_pR8H8?n*Mqboi1+ zZQHeThP3()lo9oa;N$XbA=qCqH>;8Mb>oH&tG91Yu9kDGCB?=@5)d_BkWsDCTwZ5J zFt@cluFxLuM`X>IcQ(FIJK@~)eh+;=`~~9dj;!cF}FF|_?O~7%l{7a4m;

#-XX_j($R;1vl%#n8H+_~nYZBXrAJ}-3y$0&Of z)qfjb`@aup%Nxb_{fSVkP`qrNpW8&0kpEx7(f^;mZE2;3FXDc0ryuAkWD}Et!v}=* Iv-$mh0pA49QUCw| literal 0 HcmV?d00001 diff --git a/runs/frontier-tp2-prefill-serving-v0/fleet.toml b/runs/frontier-tp2-prefill-serving-v0/fleet.toml new file mode 100644 index 0000000..e4a0ef7 --- /dev/null +++ b/runs/frontier-tp2-prefill-serving-v0/fleet.toml @@ -0,0 +1,48 @@ +version = 1 + +[paths] +state_dir = "runs/frontier-tp2-prefill-serving-v0/fleet-state" +artifacts_dir = "runs/frontier-tp2-prefill-serving-v0/fleet-artifacts" + +[ssh] +connect_timeout_sec = 10 + +[scheduler] +gpu_free_memory_mb = 1024 +gpu_free_utilization_pct = 10 +prefer_pack = true + +# Code is synchronized by Git. The fleet's mandatory rsync pass is intentionally +# empty so it only schedules and harvests the committed remote checkout. +[sync] +mode = "rsync" +local_path = "runs/frontier-tp2-prefill-serving-v0" +exclude = ["*"] + +[[hosts]] +name = "dash1" +ssh_alias = "dash1" +enabled = true +sync_remote_path = "/home/admin/cpfs/wjh/aituner/aituner" +fleet_root = "/home/admin/cpfs/wjh/aituner/gpu-fleet-tp2-prefill-serving-v0/dash1" + +[[hosts]] +name = "dash2" +ssh_alias = "dash2" +enabled = true +sync_remote_path = "/home/admin/cpfs/wjh/aituner/aituner" +fleet_root = "/home/admin/cpfs/wjh/aituner/gpu-fleet-tp2-prefill-serving-v0/dash2" + +[[hosts]] +name = "dash3" +ssh_alias = "dash3" +enabled = true +sync_remote_path = "/home/admin/cpfs/wjh/aituner/aituner" +fleet_root = "/home/admin/cpfs/wjh/aituner/gpu-fleet-tp2-prefill-serving-v0/dash3" + +[[hosts]] +name = "dash4" +ssh_alias = "dash4" +enabled = true +sync_remote_path = "/home/admin/cpfs/wjh/aituner/aituner" +fleet_root = "/home/admin/cpfs/wjh/aituner/gpu-fleet-tp2-prefill-serving-v0/dash4" diff --git a/runs/frontier-tp2-prefill-serving-v0/jobs-smoke.toml b/runs/frontier-tp2-prefill-serving-v0/jobs-smoke.toml new file mode 100644 index 0000000..b00891f --- /dev/null +++ b/runs/frontier-tp2-prefill-serving-v0/jobs-smoke.toml @@ -0,0 +1,16 @@ +version = 1 + +[[jobs]] +name = "tp2-prefill-serving-smoke-20260723-v0" +gpus = 2 +gpu_model = "H20" +hosts = ["dash1"] +command = "timeout --signal=TERM --kill-after=60s 3600 bash runs/frontier-tp2-prefill-serving-v0/run_prefill_profile.sh" +artifacts = ["runs/frontier-tp2-prefill-serving-v0/remote-outputs/tp2-smoke"] + +[jobs.env] +TP = "2" +SERVER_PORT = "9827" +OUTPUT_ROOT = "runs/frontier-tp2-prefill-serving-v0/remote-outputs/tp2-smoke" +WARMUP_REQUESTS = "2" +FLASHINFER_WORKSPACE_BASE = "/tmp/frontier-tp2-prefill-serving-v0-flashinfer" diff --git a/runs/frontier-tp2-prefill-serving-v0/materialize_prefill_request.py b/runs/frontier-tp2-prefill-serving-v0/materialize_prefill_request.py new file mode 100644 index 0000000..5981a89 --- /dev/null +++ b/runs/frontier-tp2-prefill-serving-v0/materialize_prefill_request.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Create one exact-length, prefix-disjoint prefill request.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--model", type=Path, required=True) + parser.add_argument("--input-tokens", type=int, default=8192) + parser.add_argument("--output-tokens", type=int, default=2) + parser.add_argument("--output", type=Path, required=True) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + if min(args.input_tokens, args.output_tokens) <= 0: + raise ValueError("token counts must be positive") + if args.input_tokens + args.output_tokens > 40960: + raise ValueError("request exceeds the server max model length") + + from transformers import AutoTokenizer + + tokenizer = AutoTokenizer.from_pretrained(args.model, trust_remote_code=True) + special = set(tokenizer.all_special_ids) + candidates = [ + token for token in range(tokenizer.vocab_size) if token not in special + ] + if len(candidates) < 2: + raise ValueError("tokenizer has too few non-special token IDs") + + body = { + "model": "qwen30-prefill-profile", + "prompt": [candidates[1], *([candidates[0]] * (args.input_tokens - 1))], + "min_tokens": args.output_tokens, + "max_tokens": args.output_tokens, + "ignore_eos": True, + "temperature": 0, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(body, separators=(",", ":")) + "\n") + + +if __name__ == "__main__": + main() diff --git a/runs/frontier-tp2-prefill-serving-v0/plot_figure_prototype.py b/runs/frontier-tp2-prefill-serving-v0/plot_figure_prototype.py new file mode 100644 index 0000000..e4c5b87 --- /dev/null +++ b/runs/frontier-tp2-prefill-serving-v0/plot_figure_prototype.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Schematic figure frozen before EXP-TP2-PREFILL-SERVING GPU execution.""" + +from pathlib import Path + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np + +ROOT = Path(__file__).resolve().parent +fig, axes = plt.subplots(1, 2, figsize=(9.6, 4.0), dpi=160) +colors = {"real": "#111111", "sim": "#d95f02", "candidate": "#1b75bc"} + +labels = ["TP2", "TP4"] +x = np.arange(2) +width = 0.34 +axes[0].bar(x - width / 2, [410, 231], width, color=colors["real"], label="real anchor") +axes[0].bar(x + width / 2, [356.25, 231.33], width, color=colors["sim"], label="structured sim") +axes[0].set_xticks(x, labels) +axes[0].set_ylabel("q8k / ctx0 chunk time (ms)") +axes[0].set_title("(a) Entry residual is TP2-only", loc="left", fontsize=10) +axes[0].legend(frameon=False, fontsize=8) + +arms = ["A\nstandalone", "B\nwarm+route", "C\nserving"] +expected = [171.1, 205, 225] +axes[1].bar(np.arange(3), expected, color=[colors["sim"], colors["candidate"], colors["real"]]) +axes[1].axhline(224.9, color="#777777", linestyle="--", linewidth=1, label="needed to close 54 ms") +axes[1].set_xticks(np.arange(3), arms) +axes[1].set_ylabel("TP2 MoE per chunk (ms)") +axes[1].set_title("(b) Three-arm decision (schematic)", loc="left", fontsize=10) +axes[1].legend(frameon=False, fontsize=8) + +for ax in axes: + ax.spines[["top", "right"]].set_visible(False) + ax.grid(axis="y", color="#dedbd2", linewidth=0.8) + ax.set_axisbelow(True) + +fig.suptitle( + "MOCK / schematic — EXP-TP2-PREFILL-SERVING (B/C not measured)", + x=0.01, + ha="left", + color=colors["sim"], + fontsize=9, +) +fig.tight_layout(rect=(0, 0, 1, 0.95)) +fig.savefig(ROOT / "figure-prototype.png") +fig.savefig(ROOT / "figure-prototype.svg") +print(ROOT / "figure-prototype.png") diff --git a/runs/frontier-tp2-prefill-serving-v0/results/entry-audit.json b/runs/frontier-tp2-prefill-serving-v0/results/entry-audit.json new file mode 100644 index 0000000..9289ff3 --- /dev/null +++ b/runs/frontier-tp2-prefill-serving-v0/results/entry-audit.json @@ -0,0 +1,24 @@ +{ + "schema": "frontier-tp2-prefill-serving-entry-v1", + "cells": { + "tp2": { + "total_ms": 356.2487769916667, + "attention_prefill_ms": 100.244853113, + "moe_grouped_gemm_ms": 171.118426598, + "samples": 9, + "real_total_ms": 410.0, + "total_bias": -0.1311005439227641 + }, + "tp4": { + "total_ms": 231.3315694846667, + "attention_prefill_ms": 51.95132805766667, + "moe_grouped_gemm_ms": 116.27978482099999, + "samples": 9, + "real_total_ms": 231.0, + "total_bias": 0.0014353657344878235 + } + }, + "tp2_required_moe_if_residual_is_all_moe_ms": 224.86964960633333, + "tp2_required_moe_shift": 0.314117094675072, + "entry_gate": true +} \ No newline at end of file diff --git a/runs/frontier-tp2-prefill-serving-v0/run_prefill_profile.sh b/runs/frontier-tp2-prefill-serving-v0/run_prefill_profile.sh new file mode 100644 index 0000000..04be475 --- /dev/null +++ b/runs/frontier-tp2-prefill-serving-v0/run_prefill_profile.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash + +set -euo pipefail + +TP="${TP:?TP is required}" +OUTPUT_ROOT="${OUTPUT_ROOT:?OUTPUT_ROOT is required}" +SERVER_PORT="${SERVER_PORT:?SERVER_PORT is required}" +VENV_ROOT="${VENV_ROOT:-/home/admin/cpfs/wjh/venvs/vllm-0.20.0-cu129-workload-regime-v2}" +MODEL_ROOT="${MODEL_ROOT:-/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B}" +GPU_MEMORY_UTILIZATION="${GPU_MEMORY_UTILIZATION:-0.92}" +SERVER_READY_ATTEMPTS="${SERVER_READY_ATTEMPTS:-900}" +WARMUP_REQUESTS="${WARMUP_REQUESTS:-2}" +SCRIPT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MATERIALIZER="${SCRIPT_ROOT}/materialize_prefill_request.py" +CLIENT="${SCRIPT_ROOT}/run_request.py" +SERVER_PID="" + +IFS=',' read -r -a GPU_IDS <<< "${CUDA_VISIBLE_DEVICES:?GPU allocation is required}" +if [[ "${#GPU_IDS[@]}" -ne "${TP}" ]]; then + echo "ERROR: TP=${TP}, but CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES}" >&2 + exit 1 +fi +if [[ ! "${WARMUP_REQUESTS}" =~ ^[1-9][0-9]*$ ]]; then + echo "ERROR: WARMUP_REQUESTS must be a positive integer" >&2 + exit 1 +fi + +mkdir -p "${OUTPUT_ROOT}" +OUTPUT_ROOT="$(cd "${OUTPUT_ROOT}" && pwd)" +mkdir -p "${OUTPUT_ROOT}/logs" "${OUTPUT_ROOT}/provenance" \ + "${OUTPUT_ROOT}/requests" "${OUTPUT_ROOT}/results" \ + "${OUTPUT_ROOT}/trace-staging" "${OUTPUT_ROOT}/traces/profile" +exec > >(tee -a "${OUTPUT_ROOT}/logs/controller.log") 2>&1 + +cleanup() { + if [[ -n "${SERVER_PID}" ]] && kill -0 "${SERVER_PID}" 2>/dev/null; then + kill -TERM -- "-${SERVER_PID}" 2>/dev/null || true + for _ in $(seq 1 30); do + kill -0 "${SERVER_PID}" 2>/dev/null || break + sleep 1 + done + kill -KILL -- "-${SERVER_PID}" 2>/dev/null || true + fi + SERVER_PID="" +} +trap cleanup EXIT INT TERM + +sha256sum "${BASH_SOURCE[0]}" "${MATERIALIZER}" "${CLIENT}" \ + "${MODEL_ROOT}/config.json" > "${OUTPUT_ROOT}/provenance/inputs.sha256" +"${VENV_ROOT}/bin/python" -c \ + 'import torch, transformers, vllm; print(f"torch={torch.__version__}"); print(f"transformers={transformers.__version__}"); print(f"vllm={vllm.__version__}")' \ + > "${OUTPUT_ROOT}/provenance/runtime.versions" +nvidia-smi --query-gpu=index,name,uuid,driver_version,memory.total \ + --format=csv,noheader > "${OUTPUT_ROOT}/provenance/gpus.before.csv" +ps -eo user,pid,ppid,etimes,pcpu,pmem,args --sort=pid \ + > "${OUTPUT_ROOT}/provenance/processes.before.txt" +uptime > "${OUTPUT_ROOT}/provenance/uptime.before.txt" +env | sort > "${OUTPUT_ROOT}/provenance/environment.txt" + +PROFILE_CONFIG="$("${VENV_ROOT}/bin/python" - "${OUTPUT_ROOT}/trace-staging" <<'PY' +import json +import sys +print(json.dumps({ + "profiler": "torch", + "torch_profiler_dir": sys.argv[1], + "torch_profiler_with_stack": False, + "torch_profiler_record_shapes": True, + "torch_profiler_use_gzip": True, + "ignore_frontend": True, + "wait_iterations": 0, + "warmup_iterations": 0, + "active_iterations": 2, +}, separators=(",", ":"))) +PY +)" +printf '%s\n' "${PROFILE_CONFIG}" > "${OUTPUT_ROOT}/provenance/profiler-config.json" + +export TOKENIZERS_PARALLELISM=false +export VLLM_USE_V1=1 +export HF_HUB_OFFLINE=1 +export TRANSFORMERS_OFFLINE=1 +export FLASHINFER_WORKSPACE_BASE="${FLASHINFER_WORKSPACE_BASE:-${OUTPUT_ROOT}/flashinfer-workspace}" +mkdir -p "${FLASHINFER_WORKSPACE_BASE}" +ulimit -n 65536 + +"${VENV_ROOT}/bin/python" "${MATERIALIZER}" --model "${MODEL_ROOT}" \ + --input-tokens 8192 --output-tokens 2 \ + --output "${OUTPUT_ROOT}/requests/q8192-o2.json" + +printf 'LAUNCH host=%s tp=%s gpus=%s output=%s warmups=%s\n' \ + "$(hostname)" "${TP}" "${CUDA_VISIBLE_DEVICES}" "${OUTPUT_ROOT}" \ + "${WARMUP_REQUESTS}" +setsid "${VENV_ROOT}/bin/vllm" serve "${MODEL_ROOT}" \ + --host 127.0.0.1 --port "${SERVER_PORT}" \ + --served-model-name qwen30-prefill-profile \ + --tensor-parallel-size "${TP}" \ + --gpu-memory-utilization "${GPU_MEMORY_UTILIZATION}" \ + --max-model-len 40960 --max-num-batched-tokens 8192 --max-num-seqs 16 \ + --no-enable-prefix-caching --enable-chunked-prefill --no-enable-log-requests \ + --enable-logging-iteration-details --profiler-config "${PROFILE_CONFIG}" \ + > "${OUTPUT_ROOT}/logs/server.log" 2>&1 & +SERVER_PID=$! + +READY=0 +for _ in $(seq 1 "${SERVER_READY_ATTEMPTS}"); do + if curl -fsS --max-time 2 \ + "http://127.0.0.1:${SERVER_PORT}/v1/models" \ + > "${OUTPUT_ROOT}/results/models.json" 2>/dev/null; then + READY=1 + break + fi + if ! kill -0 "${SERVER_PID}" 2>/dev/null; then + tail -200 "${OUTPUT_ROOT}/logs/server.log" + exit 1 + fi + sleep 3 +done +if [[ "${READY}" -ne 1 ]]; then + echo "ERROR: server readiness timeout" >&2 + tail -200 "${OUTPUT_ROOT}/logs/server.log" + exit 1 +fi + +for index in $(seq 1 "${WARMUP_REQUESTS}"); do + "${VENV_ROOT}/bin/python" "${CLIENT}" --port "${SERVER_PORT}" \ + --request "${OUTPUT_ROOT}/requests/q8192-o2.json" \ + --output "${OUTPUT_ROOT}/results/warmup-${index}.json" +done + +curl -fsS -X POST "http://127.0.0.1:${SERVER_PORT}/start_profile" \ + > "${OUTPUT_ROOT}/logs/start-profile.txt" +"${VENV_ROOT}/bin/python" "${CLIENT}" --port "${SERVER_PORT}" \ + --request "${OUTPUT_ROOT}/requests/q8192-o2.json" \ + --output "${OUTPUT_ROOT}/results/profile.json" +curl -fsS -X POST "http://127.0.0.1:${SERVER_PORT}/stop_profile" \ + > "${OUTPUT_ROOT}/logs/stop-profile.txt" + +deadline=$((SECONDS + 120)) +while (( SECONDS < deadline )); do + trace_count="$(find "${OUTPUT_ROOT}/trace-staging" -maxdepth 1 -type f \ + -name '*.pt.trace.json*' | wc -l)" + if (( trace_count >= TP )); then + break + fi + sleep 2 +done +trace_count="$(find "${OUTPUT_ROOT}/trace-staging" -maxdepth 1 -type f \ + -name '*.pt.trace.json*' | wc -l)" +if (( trace_count < TP )); then + echo "ERROR: expected ${TP} rank traces, found ${trace_count}" >&2 + exit 1 +fi +find "${OUTPUT_ROOT}/trace-staging" -maxdepth 1 -type f \ + -name '*.pt.trace.json*' -exec mv -t "${OUTPUT_ROOT}/traces/profile" {} + + +cleanup +nvidia-smi --query-gpu=index,name,uuid,driver_version,memory.total \ + --format=csv,noheader > "${OUTPUT_ROOT}/provenance/gpus.after.csv" +find "${OUTPUT_ROOT}" -type f \ + ! -path '*/provenance/artifacts.sha256' -print0 \ + | sort -z | xargs -0 sha256sum \ + > "${OUTPUT_ROOT}/provenance/artifacts.sha256" +echo PREFILL_PROFILE_COMPLETE diff --git a/runs/frontier-tp2-prefill-serving-v0/run_request.py b/runs/frontier-tp2-prefill-serving-v0/run_request.py new file mode 100644 index 0000000..832624c --- /dev/null +++ b/runs/frontier-tp2-prefill-serving-v0/run_request.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Issue one non-streaming completion request and record wall/usage.""" + +from __future__ import annotations + +import argparse +import json +import time +import urllib.request +from pathlib import Path + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--port", type=int, required=True) + parser.add_argument("--request", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--timeout-seconds", type=float, default=1800) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + body = json.loads(args.request.read_text()) + expected_input = len(body["prompt"]) + expected_output = int(body["max_tokens"]) + request = urllib.request.Request( + f"http://127.0.0.1:{args.port}/v1/completions", + data=json.dumps(body, separators=(",", ":")).encode(), + headers={"Content-Type": "application/json"}, + method="POST", + ) + started = time.monotonic() + with urllib.request.urlopen( + request, timeout=args.timeout_seconds + ) as response: + payload = json.load(response) + wall_ms = (time.monotonic() - started) * 1000 + usage = payload.get("usage") or {} + observed = ( + int(usage.get("prompt_tokens", -1)), + int(usage.get("completion_tokens", -1)), + ) + if observed != (expected_input, expected_output): + raise ValueError( + f"usage mismatch: expected {expected_input}+{expected_output}, " + f"observed {observed[0]}+{observed[1]}" + ) + result = { + "wall_ms": wall_ms, + "input_tokens": observed[0], + "output_tokens": observed[1], + "usage": usage, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n") + print(json.dumps(result, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main()