From 33da0edd886382f6d0fdb0e85c607bf002aaf51b Mon Sep 17 00:00:00 2001 From: qinjun-li Date: Thu, 10 Apr 2025 12:38:14 +0800 Subject: [PATCH 01/24] [rocketv] fix scoreboard clear for vector. --- rocketv/src/RocketCore.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocketv/src/RocketCore.scala b/rocketv/src/RocketCore.scala index 25eaf8d26..d144ed139 100644 --- a/rocketv/src/RocketCore.scala +++ b/rocketv/src/RocketCore.scala @@ -1609,8 +1609,8 @@ class Rocket(val parameter: RocketParameter) t1XRDRetireQueue.deq.ready := (!(wbWxd || (dmemResponseReplay && dmemResponseXpu)) || !vectorTryToWriteRd) && (!(dmemResponseReplay && dmemResponseFpu) || !vectorTryToWriteFP) when(t1XRDRetireQueue.deq.fire && vectorTryToWriteRd) { - longlatencyWdata := t1.retire.rd.bits.rdData - longlatencyWaddress := t1.retire.rd.bits.rdAddress + longlatencyWdata := t1XRDRetireQueue.deq.bits.rdData + longlatencyWaddress := t1XRDRetireQueue.deq.bits.rdAddress longLatencyWenable := true.B } io.fpu.foreach { fpu => From 98513da968221ee41e55b40c3b8048f9044ed039 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 10 Apr 2025 05:56:12 +0000 Subject: [PATCH 02/24] [ci] update t1 test case cycle data --- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .../t1rocketemu.json | 12 ++-- .github/designs/blastoise/t1rocketemu.json | 60 +++++++++---------- .github/designs/rookidee/t1rocketemu.json | 34 +++++------ 20 files changed, 155 insertions(+), 155 deletions(-) diff --git a/.github/designs/benchmark_dlen1024_vlen1024_fp/t1rocketemu.json b/.github/designs/benchmark_dlen1024_vlen1024_fp/t1rocketemu.json index 327b004cb..957b028bb 100644 --- a/.github/designs/benchmark_dlen1024_vlen1024_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen1024_vlen1024_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 1975, - "eval.ntt_128": 3573, - "eval.ntt_256": 5247, - "eval.ntt_512": 58013, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 2305, + "eval.ntt_128": 4337, + "eval.ntt_256": 6100, + "eval.ntt_512": 57515, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen1024_vlen2048_fp/t1rocketemu.json b/.github/designs/benchmark_dlen1024_vlen2048_fp/t1rocketemu.json index 438652fec..5837d21a1 100644 --- a/.github/designs/benchmark_dlen1024_vlen2048_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen1024_vlen2048_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 1975, - "eval.ntt_128": 3573, - "eval.ntt_256": 5229, - "eval.ntt_512": 8099, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 2305, + "eval.ntt_128": 4337, + "eval.ntt_256": 6083, + "eval.ntt_512": 8057, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen1024_vlen4096_fp/t1rocketemu.json b/.github/designs/benchmark_dlen1024_vlen4096_fp/t1rocketemu.json index 83c2e6f7c..bce8a91fc 100644 --- a/.github/designs/benchmark_dlen1024_vlen4096_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen1024_vlen4096_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 1975, - "eval.ntt_128": 3573, - "eval.ntt_256": 5229, - "eval.ntt_512": 8096, - "eval.ntt_1024": 20939, - "eval.ntt_4096": 56564 + "eval.ntt_64": 2305, + "eval.ntt_128": 4337, + "eval.ntt_256": 6083, + "eval.ntt_512": 8057, + "eval.ntt_1024": 21131, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen128_vlen1024_fp/t1rocketemu.json b/.github/designs/benchmark_dlen128_vlen1024_fp/t1rocketemu.json index 2c265ea46..c74703241 100644 --- a/.github/designs/benchmark_dlen128_vlen1024_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen128_vlen1024_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2757, - "eval.ntt_128": 4738, - "eval.ntt_256": 8777, - "eval.ntt_512": 58013, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 3087, + "eval.ntt_128": 5428, + "eval.ntt_256": 9060, + "eval.ntt_512": 57515, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen128_vlen128_fp/t1rocketemu.json b/.github/designs/benchmark_dlen128_vlen128_fp/t1rocketemu.json index c63a22ccf..423803b5a 100644 --- a/.github/designs/benchmark_dlen128_vlen128_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen128_vlen128_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 58036, - "eval.ntt_128": 58871, - "eval.ntt_256": 58884, - "eval.ntt_512": 58013, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 57783, + "eval.ntt_128": 57779, + "eval.ntt_256": 57922, + "eval.ntt_512": 57515, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen128_vlen2048_fp/t1rocketemu.json b/.github/designs/benchmark_dlen128_vlen2048_fp/t1rocketemu.json index 39d13ae70..4851f4241 100644 --- a/.github/designs/benchmark_dlen128_vlen2048_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen128_vlen2048_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2757, - "eval.ntt_128": 4734, - "eval.ntt_256": 8721, - "eval.ntt_512": 18629, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 3087, + "eval.ntt_128": 5428, + "eval.ntt_256": 9560, + "eval.ntt_512": 18674, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen128_vlen256_fp/t1rocketemu.json b/.github/designs/benchmark_dlen128_vlen256_fp/t1rocketemu.json index ce07a3e7f..700353d5d 100644 --- a/.github/designs/benchmark_dlen128_vlen256_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen128_vlen256_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2757, - "eval.ntt_128": 58871, - "eval.ntt_256": 58884, - "eval.ntt_512": 58013, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 3087, + "eval.ntt_128": 57779, + "eval.ntt_256": 57922, + "eval.ntt_512": 57515, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen128_vlen4096_fp/t1rocketemu.json b/.github/designs/benchmark_dlen128_vlen4096_fp/t1rocketemu.json index 3c42c0a5c..d04a9522a 100644 --- a/.github/designs/benchmark_dlen128_vlen4096_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen128_vlen4096_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2757, - "eval.ntt_128": 4734, - "eval.ntt_256": 8736, - "eval.ntt_512": 18627, - "eval.ntt_1024": 43035, - "eval.ntt_4096": 56564 + "eval.ntt_64": 3087, + "eval.ntt_128": 5428, + "eval.ntt_256": 9560, + "eval.ntt_512": 18673, + "eval.ntt_1024": 43078, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen128_vlen512_fp/t1rocketemu.json b/.github/designs/benchmark_dlen128_vlen512_fp/t1rocketemu.json index 43fe157ce..4a30f6553 100644 --- a/.github/designs/benchmark_dlen128_vlen512_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen128_vlen512_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2757, - "eval.ntt_128": 4722, - "eval.ntt_256": 58884, - "eval.ntt_512": 58013, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 3087, + "eval.ntt_128": 5429, + "eval.ntt_256": 57922, + "eval.ntt_512": 57515, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen256_vlen1024_fp/t1rocketemu.json b/.github/designs/benchmark_dlen256_vlen1024_fp/t1rocketemu.json index 8d19b941b..b30e4d553 100644 --- a/.github/designs/benchmark_dlen256_vlen1024_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen256_vlen1024_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2192, - "eval.ntt_128": 3469, - "eval.ntt_256": 6153, - "eval.ntt_512": 58013, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 2522, + "eval.ntt_128": 4082, + "eval.ntt_256": 6218, + "eval.ntt_512": 57515, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen256_vlen2048_fp/t1rocketemu.json b/.github/designs/benchmark_dlen256_vlen2048_fp/t1rocketemu.json index 2a0fa5e9c..7674364c8 100644 --- a/.github/designs/benchmark_dlen256_vlen2048_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen256_vlen2048_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2192, - "eval.ntt_128": 3469, - "eval.ntt_256": 6153, - "eval.ntt_512": 10429, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 2522, + "eval.ntt_128": 4082, + "eval.ntt_256": 6218, + "eval.ntt_512": 10473, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen256_vlen256_fp/t1rocketemu.json b/.github/designs/benchmark_dlen256_vlen256_fp/t1rocketemu.json index 6233f5bd6..0f7057718 100644 --- a/.github/designs/benchmark_dlen256_vlen256_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen256_vlen256_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2203, - "eval.ntt_128": 58871, - "eval.ntt_256": 58884, - "eval.ntt_512": 58013, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 2533, + "eval.ntt_128": 57779, + "eval.ntt_256": 57922, + "eval.ntt_512": 57515, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen256_vlen4096_fp/t1rocketemu.json b/.github/designs/benchmark_dlen256_vlen4096_fp/t1rocketemu.json index abd09b0d3..ee6ec7105 100644 --- a/.github/designs/benchmark_dlen256_vlen4096_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen256_vlen4096_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2192, - "eval.ntt_128": 3469, - "eval.ntt_256": 6153, - "eval.ntt_512": 10852, - "eval.ntt_1024": 26612, - "eval.ntt_4096": 56564 + "eval.ntt_64": 2522, + "eval.ntt_128": 4082, + "eval.ntt_256": 6218, + "eval.ntt_512": 10484, + "eval.ntt_1024": 26525, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen256_vlen512_fp/t1rocketemu.json b/.github/designs/benchmark_dlen256_vlen512_fp/t1rocketemu.json index bfa444787..60ecc84b1 100644 --- a/.github/designs/benchmark_dlen256_vlen512_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen256_vlen512_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2196, - "eval.ntt_128": 3335, - "eval.ntt_256": 58884, - "eval.ntt_512": 58013, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 2527, + "eval.ntt_128": 4093, + "eval.ntt_256": 57922, + "eval.ntt_512": 57515, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen512_vlen1024_fp/t1rocketemu.json b/.github/designs/benchmark_dlen512_vlen1024_fp/t1rocketemu.json index aa53562bc..734b209e3 100644 --- a/.github/designs/benchmark_dlen512_vlen1024_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen512_vlen1024_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2012, - "eval.ntt_128": 3278, - "eval.ntt_256": 5090, - "eval.ntt_512": 58013, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 2340, + "eval.ntt_128": 3717, + "eval.ntt_256": 6119, + "eval.ntt_512": 57515, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen512_vlen2048_fp/t1rocketemu.json b/.github/designs/benchmark_dlen512_vlen2048_fp/t1rocketemu.json index 7946f0d8b..e9faa7eb2 100644 --- a/.github/designs/benchmark_dlen512_vlen2048_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen512_vlen2048_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2012, - "eval.ntt_128": 3276, - "eval.ntt_256": 5090, - "eval.ntt_512": 9059, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 2340, + "eval.ntt_128": 3717, + "eval.ntt_256": 6119, + "eval.ntt_512": 9228, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen512_vlen4096_fp/t1rocketemu.json b/.github/designs/benchmark_dlen512_vlen4096_fp/t1rocketemu.json index f4bbdd2f9..0a841927a 100644 --- a/.github/designs/benchmark_dlen512_vlen4096_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen512_vlen4096_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2012, - "eval.ntt_128": 3276, - "eval.ntt_256": 5090, - "eval.ntt_512": 9059, - "eval.ntt_1024": 22351, - "eval.ntt_4096": 56564 + "eval.ntt_64": 2340, + "eval.ntt_128": 3717, + "eval.ntt_256": 6119, + "eval.ntt_512": 9223, + "eval.ntt_1024": 22370, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/benchmark_dlen512_vlen512_fp/t1rocketemu.json b/.github/designs/benchmark_dlen512_vlen512_fp/t1rocketemu.json index 914a533bb..255a1686d 100644 --- a/.github/designs/benchmark_dlen512_vlen512_fp/t1rocketemu.json +++ b/.github/designs/benchmark_dlen512_vlen512_fp/t1rocketemu.json @@ -1,8 +1,8 @@ { - "eval.ntt_64": 2012, - "eval.ntt_128": 3287, - "eval.ntt_256": 58884, - "eval.ntt_512": 58013, - "eval.ntt_1024": 57728, - "eval.ntt_4096": 56564 + "eval.ntt_64": 2340, + "eval.ntt_128": 3724, + "eval.ntt_256": 57922, + "eval.ntt_512": 57515, + "eval.ntt_1024": 58997, + "eval.ntt_4096": 56320 } \ No newline at end of file diff --git a/.github/designs/blastoise/t1rocketemu.json b/.github/designs/blastoise/t1rocketemu.json index 18b57abe5..e6917fc34 100644 --- a/.github/designs/blastoise/t1rocketemu.json +++ b/.github/designs/blastoise/t1rocketemu.json @@ -1,9 +1,9 @@ { - "asm.memcpy": 1813, - "asm.mmm": 52503, - "asm.smoke": 8556, - "asm.strlen": 14192, - "asm.utf8_count": 505, + "asm.memcpy": 2260, + "asm.mmm": 52837, + "asm.smoke": 9061, + "asm.strlen": 14589, + "asm.utf8_count": 859, "codegen.vaadd_vv": 509657, "codegen.vaadd_vx": 1082497, "codegen.vaaddu_vv": 509657, @@ -499,30 +499,30 @@ "codegen.vxor_vx": 272297, "codegen.vzext_vf2": 35727, "codegen.vzext_vf4": 9179, - "intrinsic.conv2d_less_m2": 4359, - "intrinsic.linear_normalization": 4818, + "intrinsic.conv2d_less_m2": 4684, + "intrinsic.linear_normalization": 5505, "intrinsic.matmul": 156950, - "intrinsic.softmax": 9003, - "mlir.axpy_masked": 11636, - "mlir.conv": 298996, - "mlir.hello": 371, - "mlir.matmul": 75518, - "mlir.maxvl_tail_setvl_front": 2375, - "mlir.rvv_vp_intrinsic_add": 774, - "mlir.rvv_vp_intrinsic_add_scalable": 1173, - "mlir.stripmining": 35059, - "mlir.vectoradd": 72377, - "pytorch.demo": 122735, - "pytorch.matmul": 109796, - "rvv_bench.ascii_to_utf16": 1383013, - "rvv_bench.ascii_to_utf32": 440566, - "rvv_bench.byteswap": 449395, - "rvv_bench.chacha20": 45624, - "rvv_bench.mandelbrot": 563959, - "rvv_bench.memcpy": 1267552, - "rvv_bench.memset": 510029, - "rvv_bench.mergelines": 777753, - "rvv_bench.poly1305": 45624, - "rvv_bench.strlen": 494533, - "rvv_bench.utf8_count": 2659715 + "intrinsic.softmax": 9329, + "mlir.axpy_masked": 11962, + "mlir.conv": 299284, + "mlir.hello": 694, + "mlir.matmul": 75963, + "mlir.maxvl_tail_setvl_front": 2762, + "mlir.rvv_vp_intrinsic_add": 1127, + "mlir.rvv_vp_intrinsic_add_scalable": 1539, + "mlir.stripmining": 35418, + "mlir.vectoradd": 72384, + "pytorch.demo": 123874, + "pytorch.matmul": 109914, + "rvv_bench.ascii_to_utf16": 1383170, + "rvv_bench.ascii_to_utf32": 440610, + "rvv_bench.byteswap": 449384, + "rvv_bench.chacha20": 45889, + "rvv_bench.mandelbrot": 564338, + "rvv_bench.memcpy": 1268232, + "rvv_bench.memset": 509712, + "rvv_bench.mergelines": 770459, + "rvv_bench.poly1305": 45889, + "rvv_bench.strlen": 495094, + "rvv_bench.utf8_count": 2669518 } \ No newline at end of file diff --git a/.github/designs/rookidee/t1rocketemu.json b/.github/designs/rookidee/t1rocketemu.json index 7e8005b9b..9d6b93797 100644 --- a/.github/designs/rookidee/t1rocketemu.json +++ b/.github/designs/rookidee/t1rocketemu.json @@ -1,6 +1,6 @@ { - "asm.mmm": 57732, - "asm.smoke": 8647, + "asm.mmm": 58062, + "asm.smoke": 9136, "codegen.vaadd_vv": 306399, "codegen.vaadd_vx": 819226, "codegen.vaaddu_vv": 306399, @@ -430,19 +430,19 @@ "codegen.vxor_vx": 214202, "codegen.vzext_vf2": 29723, "codegen.vzext_vf4": 11289, - "intrinsic.conv2d_less_m2": 4410, - "mlir.hello": 369, - "mlir.rvv_vp_intrinsic_add": 762, - "mlir.rvv_vp_intrinsic_add_scalable": 1062, - "mlir.stripmining": 58612, - "rvv_bench.ascii_to_utf16": 1385284, - "rvv_bench.ascii_to_utf32": 431848, - "rvv_bench.byteswap": 489626, - "rvv_bench.chacha20": 45624, - "rvv_bench.memcpy": 1285481, - "rvv_bench.memset": 509966, - "rvv_bench.mergelines": 795847, - "rvv_bench.poly1305": 45624, - "rvv_bench.strlen": 529066, - "rvv_bench.utf8_count": 2753737 + "intrinsic.conv2d_less_m2": 4728, + "mlir.hello": 692, + "mlir.rvv_vp_intrinsic_add": 1122, + "mlir.rvv_vp_intrinsic_add_scalable": 1428, + "mlir.stripmining": 58928, + "rvv_bench.ascii_to_utf16": 1386186, + "rvv_bench.ascii_to_utf32": 431879, + "rvv_bench.byteswap": 489489, + "rvv_bench.chacha20": 45889, + "rvv_bench.memcpy": 1285677, + "rvv_bench.memset": 510014, + "rvv_bench.mergelines": 790052, + "rvv_bench.poly1305": 45889, + "rvv_bench.strlen": 530552, + "rvv_bench.utf8_count": 2762239 } \ No newline at end of file From 3384a9cc07c10b34a95d61d34f8042881729224c Mon Sep 17 00:00:00 2001 From: Avimitin Date: Sun, 23 Mar 2025 20:19:59 +0800 Subject: [PATCH 03/24] [tests] add pytorch resnet18 AOT importer --- tests/pytorch/resnet18/build.nix | 69 +++++++++++++++++++++ tests/pytorch/resnet18/resnet18.py | 98 ++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 tests/pytorch/resnet18/build.nix create mode 100644 tests/pytorch/resnet18/resnet18.py diff --git a/tests/pytorch/resnet18/build.nix b/tests/pytorch/resnet18/build.nix new file mode 100644 index 000000000..8f73f4907 --- /dev/null +++ b/tests/pytorch/resnet18/build.nix @@ -0,0 +1,69 @@ +{ fetchurl +, buildBuddyE2ETest +}: +let + checkpointFile = "resnet18-f37072fd.pth"; + modelCache = fetchurl { + url = "https://download.pytorch.org/models/${checkpointFile}"; + hash = "sha256-83By/UfonF6CdiHFuv+nUAgZ94lrus7BYLGhbFYOB+w="; + }; +in +buildBuddyE2ETest { + caseName = "mobilenet"; + + optPhase = '' + mkdir -p pytorchCache/hub/checkpoints/ + cp -v ${modelCache} pytorchCache/hub/checkpoints/${checkpointFile} + export TORCH_HOME=pytorchCache + + python3 ./resnet18.py --output-dir $PWD + + echo "Lowering forward.mlir" + buddy-opt forward.mlir -pass-pipeline \ + "builtin.module(func.func(tosa-to-linalg-named, tosa-to-linalg, tosa-to-tensor, tosa-to-arith), \ + empty-tensor-to-alloc-tensor, convert-elementwise-to-linalg, arith-bufferize, \ + func.func(linalg-bufferize, tensor-bufferize), func-bufferize)" \ + | buddy-opt -pass-pipeline \ + "builtin.module(func.func(buffer-deallocation-simplification, convert-linalg-to-loops), \ + eliminate-empty-tensors, func.func(llvm-request-c-wrappers), \ + convert-math-to-llvm, convert-math-to-libm, convert-scf-to-cf, \ + convert-arith-to-llvm, expand-strided-metadata, finalize-memref-to-llvm, \ + convert-func-to-llvm, reconcile-unrealized-casts)" \ + > forward-lowered.mlir + + echo "Lowering subgraph0.mlir" + buddy-opt subgraph0.mlir -pass-pipeline \ + "builtin.module(func.func(tosa-to-linalg-named, tosa-to-arith, tosa-to-linalg, tosa-to-tensor))" \ + | buddy-opt \ + -convert-elementwise-to-linalg \ + -func-bufferize-dynamic-offset \ + -arith-bufferize \ + -func-bufferize \ + -tensor-bufferize \ + -linalg-bufferize \ + -finalizing-bufferize \ + -convert-linalg-to-loops \ + -lower-affine \ + -convert-scf-to-cf \ + -llvm-request-c-wrappers \ + -convert-math-to-llvm \ + -convert-math-to-libm \ + -convert-arith-to-llvm \ + -convert-func-to-llvm \ + -expand-strided-metadata \ + -finalize-memref-to-llvm \ + -reconcile-unrealized-casts \ + > subgraph0-lowered.mlir + + echo "Compiling memrefCopy library" + $CXX -nostdlib -c ${../lib/MemrefCopy.cc} -o memrefCopy.o + llcArtifacts+=( + memrefCopy.o + ) + + optArtifacts+=( + "forward-lowered.mlir" + "subgraph0-lowered.mlir" + ) + ''; +} diff --git a/tests/pytorch/resnet18/resnet18.py b/tests/pytorch/resnet18/resnet18.py new file mode 100644 index 000000000..1cbc61bc4 --- /dev/null +++ b/tests/pytorch/resnet18/resnet18.py @@ -0,0 +1,98 @@ +# ===- buddy-resnet-import.py -------------------------------------------------- +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ===--------------------------------------------------------------------------- +# +# This is the ResNet18 model AOT importer. +# +# ===--------------------------------------------------------------------------- + +import os +import argparse +from pathlib import Path +import numpy as np +import torch +import torchvision.models as models +import torch._inductor.lowering +from torch._inductor.decomposition import decompositions as inductor_decomp +from torch._decomp import remove_decompositions + +from buddy.compiler.frontend import DynamoCompiler +from buddy.compiler.graph import GraphDriver +from buddy.compiler.graph.transform import simply_fuse +from buddy.compiler.ops import tosa + +# Parse command-line arguments +parser = argparse.ArgumentParser(description="ResNet18 model AOT importer") +parser.add_argument( + "--output-dir", type=str, default="./", help="Directory to save output files." +) +args = parser.parse_args() + +# Ensure output directory exists +output_dir = os.path.abspath(args.output_dir) +os.makedirs(output_dir, exist_ok=True) + +# Retrieve the ResNet18 model path. +model_path = os.path.dirname(os.path.abspath(__file__)) + +model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) +model = model.eval() + +# Remove the num_batches_tracked attribute. +for layer in model.modules(): + if isinstance(layer, torch.nn.BatchNorm2d): + if hasattr(layer, "num_batches_tracked"): + del layer.num_batches_tracked + +DEFAULT_DECOMPOSITIONS = [ + torch.ops.aten.max_pool2d_with_indices.default, +] + +remove_decompositions(inductor_decomp, DEFAULT_DECOMPOSITIONS) + +# Initialize Dynamo Compiler with specific configurations as an importer. +dynamo_compiler = DynamoCompiler( + primary_registry=tosa.ops_registry, + aot_autograd_decomposition=inductor_decomp, +) +data = torch.randn([1, 3, 224, 224]) +# Import the model into MLIR module and parameters. +with torch.no_grad(): + graphs = dynamo_compiler.importer(model, data) +assert len(graphs) == 1 +graph = graphs[0] +params = dynamo_compiler.imported_params[graph] +pattern_list = [simply_fuse] +graphs[0].fuse_ops(pattern_list) +driver = GraphDriver(graphs[0]) +driver.subgraphs[0].lower_to_top_level_ir() + +# Write the MLIR module and forward graph to the specified output directory +with open(os.path.join(output_dir, "subgraph0.mlir"), "w") as module_file: + print(driver.subgraphs[0]._imported_module, file=module_file) +with open(os.path.join(output_dir, "forward.mlir"), "w") as module_file: + print(driver.construct_main_graph(True), file=module_file) + +params = dynamo_compiler.imported_params[graph] +current_path = os.path.dirname(os.path.abspath(__file__)) + +float32_param = np.concatenate( + [ + param.detach().numpy().reshape([-1]) + for param in params + if param.dtype == torch.float32 + ] +) +float32_param.tofile(Path(output_dir) / "arg0.data") From f1ff0b28e05e5b8f0f25bbe0285dddd149920bf3 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Tue, 25 Mar 2025 20:52:47 +0800 Subject: [PATCH 04/24] [pytorch] add buddy-codegen Signed-off-by: Avimitin --- nix/overlay.nix | 2 + nix/pkgs/buddy-codegen/buddy-codegen.cc | 129 ++++++++++++++++++++++++ nix/pkgs/buddy-codegen/default.nix | 56 ++++++++++ nix/pkgs/buddy-codegen/dip.mlir | 30 ++++++ nix/pkgs/buddy-mlir.nix | 6 ++ 5 files changed, 223 insertions(+) create mode 100644 nix/pkgs/buddy-codegen/buddy-codegen.cc create mode 100644 nix/pkgs/buddy-codegen/default.nix create mode 100644 nix/pkgs/buddy-codegen/dip.mlir diff --git a/nix/overlay.nix b/nix/overlay.nix index 7bfe75237..abfbddb47 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -151,4 +151,6 @@ rec { }; t1 = final.callPackage ./t1 { }; + + buddy-codegen = final.callPackage ./pkgs/buddy-codegen { }; } diff --git a/nix/pkgs/buddy-codegen/buddy-codegen.cc b/nix/pkgs/buddy-codegen/buddy-codegen.cc new file mode 100644 index 000000000..032a1be78 --- /dev/null +++ b/nix/pkgs/buddy-codegen/buddy-codegen.cc @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include +#include // For precision control +#include +#include + +template +std::string generate_c_array_code(T *data, size_t size, + const std::string &declare, + const std::string &array_name) { + std::ostringstream oss; + oss << declare << " " << array_name << "[" << size << "] = {\n "; + for (size_t i = 0; i < size; ++i) { + oss << std::fixed << std::setprecision(6) << data[i]; + if (i != size - 1) + oss << ", "; + if ((i + 1) % 10 == 0) + oss << "\n "; + } + + oss << "\n};\n"; + return oss.str(); +} + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("buddy-codegen"); + + argparse::ArgumentParser img_cmd("img"); + img_cmd.add_description("Convert image to MLIR memref C code"); + img_cmd.add_argument("-i", "--input") + .required() + .help("specify the input file"); + img_cmd.add_argument("-o", "--output") + .required() + .help("specify the output file."); + // TODO: support other format + img_cmd.add_argument("-m", "--image-mode").default_value("rgb"); + + argparse::ArgumentParser arg_cmd("arg"); + arg_cmd.add_description("Convert PyTorch parameter to MLIR memref C code"); + arg_cmd.add_argument("-i", "--input") + .required() + .help("specify the input file"); + arg_cmd.add_argument("-o", "--output") + .required() + .help("specify the output file."); + arg_cmd.add_argument("-s", "--size") + .scan<'d', size_t>() + .required() + .help("specify the parameter size."); + + program.add_subparser(img_cmd); + program.add_subparser(arg_cmd); + + try { + program.parse_args(argc, argv); + } catch (const std::exception &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + if (program.is_subcommand_used(img_cmd)) { + std::string imgPath = img_cmd.get("input"); + std::ofstream outPath; + outPath.open(img_cmd.get("output")); + + std::cout << "[buddy-codegen] Loading image..." << std::endl; + dip::Image input(imgPath, dip::DIP_RGB, true); + // TODO read the col, row using PNG lib + MemRef inputResize = dip::Resize4D_NCHW( + &input, dip::INTERPOLATION_TYPE::BILINEAR_INTERPOLATION, + {1, 3, 224, 224} /*{image_cols, image_rows}*/); + auto sizes = inputResize.getSizes(); + outPath << "#include " << std::endl; + outPath << generate_c_array_code(sizes, 4, "extern const int32_t", + "IMAGE_SIZES") + << std::endl; + outPath << generate_c_array_code( + inputResize.getData(), inputResize.getSize(), + "__attribute((section(\".vdata\"))) float", "IMAGE") + << std::endl; + outPath.close(); + std::cout << "[buddy-codegen] code generated" << std::endl; + } else if (program.is_subcommand_used(arg_cmd)) { + std::string argPath = arg_cmd.get("input"); + std::ofstream outPath; + outPath.open(arg_cmd.get("output")); + auto params_size = arg_cmd.get("size"); + + MemRef params({params_size}); + + const auto loadStart = std::chrono::high_resolution_clock::now(); + // Open the parameter file in binary mode. + std::ifstream paramFile(argPath, std::ios::in | std::ios::binary); + if (!paramFile.is_open()) { + throw std::runtime_error("[Error] Failed to open params file!"); + } + std::cout << "[buddy-codegen] Loading params..." << std::endl; + // Read the parameter data into the provided memory reference. + paramFile.read(reinterpret_cast(params.getData()), + sizeof(float) * (params.getSize())); + if (paramFile.fail()) { + throw std::runtime_error("Error occurred while reading params file!"); + } + paramFile.close(); + + outPath << "#include " << std::endl; + outPath << generate_c_array_code(params.getSizes(), 1, + "extern const int32_t", "PARAMS_SIZES") + << std::endl; + outPath << generate_c_array_code( + params.getData(), params.getSize(), + "extern __attribute((section(\".vdata\"))) float", "PARAMS") + << std::endl; + outPath.close(); + + const auto loadEnd = std::chrono::high_resolution_clock::now(); + const std::chrono::duration loadTime = + loadEnd - loadStart; + std::cout << "[buddy-codegen] Params load time: " + << (double)(loadTime.count()) / 1000 << "s\n" + << std::endl; + } +} diff --git a/nix/pkgs/buddy-codegen/default.nix b/nix/pkgs/buddy-codegen/default.nix new file mode 100644 index 000000000..19127c067 --- /dev/null +++ b/nix/pkgs/buddy-codegen/default.nix @@ -0,0 +1,56 @@ +{ lib +, stdenv +, argparse +, buddy-mlir +, libpng +}: +stdenv.mkDerivation { + name = "buddy-codegen"; + + src = with lib.fileset; toSource { + fileset = unions [ + ./dip.mlir + ./buddy-codegen.cc + ]; + root = ./.; + }; + + buildInputs = [ libpng argparse buddy-mlir ]; + + env.NIX_CFLAGS_COMPILE = toString [ + # TODO: BMP is now broken + "-lpng" + "-DBUDDY_ENABLE_PNG" + "-O3" + ]; + + buildPhase = '' + runHook preBuild + # We don't need to care about stripmining size here + buddy-opt dip.mlir \ + -lower-dip="DIP-strip-mining=256" \ + -arith-expand \ + -lower-affine \ + -llvm-request-c-wrappers \ + -convert-scf-to-cf \ + -convert-math-to-llvm \ + -convert-vector-to-llvm \ + -finalize-memref-to-llvm \ + -convert-func-to-llvm \ + -reconcile-unrealized-casts | \ + buddy-translate --mlir-to-llvmir | \ + buddy-llc \ + --filetype=obj \ + -o dip.o + + $CXX ./dip.o ./buddy-codegen.cc -o buddy-codegen + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mkdir -p $out/bin + cp -v buddy-codegen $out/bin/ + runHook postInstall + ''; +} diff --git a/nix/pkgs/buddy-codegen/dip.mlir b/nix/pkgs/buddy-codegen/dip.mlir new file mode 100644 index 000000000..44a0c194d --- /dev/null +++ b/nix/pkgs/buddy-codegen/dip.mlir @@ -0,0 +1,30 @@ +//===- DIP.mlir -----------------------------------------------------------===// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//===----------------------------------------------------------------------===// +// +// This file provides DIP dialect functions. +// +//===----------------------------------------------------------------------===// +func.func @resize_4d_nchw_nearest_neighbour_interpolation(%inputImage : memref, %horizontal_scaling_factor : f32, %vertical_scaling_factor : f32, %outputImage : memref) attributes{llvm.emit_c_interface} +{ + dip.resize_4d_nchw NEAREST_NEIGHBOUR_INTERPOLATION %inputImage, %horizontal_scaling_factor, %vertical_scaling_factor, %outputImage : memref, f32, f32, memref + return +} + +func.func @resize_4d_nchw_bilinear_interpolation(%inputImage : memref, %horizontal_scaling_factor : f32, %vertical_scaling_factor : f32, %outputImage : memref) attributes{llvm.emit_c_interface} +{ + dip.resize_4d_nchw BILINEAR_INTERPOLATION %inputImage, %horizontal_scaling_factor, %vertical_scaling_factor, %outputImage : memref, f32, f32, memref + return +} diff --git a/nix/pkgs/buddy-mlir.nix b/nix/pkgs/buddy-mlir.nix index 6cb60c8d7..eba17a4cc 100644 --- a/nix/pkgs/buddy-mlir.nix +++ b/nix/pkgs/buddy-mlir.nix @@ -43,6 +43,12 @@ let # No need to do check, and it also takes too much time to finish. doCheck = false; + # TODO: Upstream this to Buddy-MLIR cmake install + postInstall = '' + mkdir -p "$out/include" + cp -vr "$NIX_BUILD_TOP/$sourceRoot/frontend/Interfaces/buddy" "$out/include" + ''; + # Here we concatenate the LLVM and Buddy python module into one directory for easier import postFixup = '' mkdir -p $out/lib/python${python3.pythonVersion}/site-packages From 3839c7aecfdb701c19d29c258e63e2c17949bd58 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Tue, 25 Mar 2025 22:50:20 +0800 Subject: [PATCH 05/24] [pytorch] add resnet18 assets Signed-off-by: Avimitin --- nix/pkgs/buddy-codegen/buddy-codegen.cc | 13 ++++++------- tests/pytorch/default.nix | 2 ++ tests/pytorch/resnet18/build.nix | 6 +++++- tests/pytorch/resnet18/dog-224_224.png | Bin 0 -> 94456 bytes tests/pytorch/resnet18/resnet18.cc | 20 ++++++++++++++++++++ 5 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 tests/pytorch/resnet18/dog-224_224.png create mode 100644 tests/pytorch/resnet18/resnet18.cc diff --git a/nix/pkgs/buddy-codegen/buddy-codegen.cc b/nix/pkgs/buddy-codegen/buddy-codegen.cc index 032a1be78..c140f81b1 100644 --- a/nix/pkgs/buddy-codegen/buddy-codegen.cc +++ b/nix/pkgs/buddy-codegen/buddy-codegen.cc @@ -77,7 +77,7 @@ int main(int argc, char *argv[]) { {1, 3, 224, 224} /*{image_cols, image_rows}*/); auto sizes = inputResize.getSizes(); outPath << "#include " << std::endl; - outPath << generate_c_array_code(sizes, 4, "extern const int32_t", + outPath << generate_c_array_code(sizes, 4, "static const int32_t", "IMAGE_SIZES") << std::endl; outPath << generate_c_array_code( @@ -111,11 +111,11 @@ int main(int argc, char *argv[]) { outPath << "#include " << std::endl; outPath << generate_c_array_code(params.getSizes(), 1, - "extern const int32_t", "PARAMS_SIZES") + "static const int32_t", "PARAMS_SIZES") << std::endl; - outPath << generate_c_array_code( - params.getData(), params.getSize(), - "extern __attribute((section(\".vdata\"))) float", "PARAMS") + outPath << generate_c_array_code(params.getData(), params.getSize(), + "__attribute((section(\".vdata\"))) float", + "PARAMS") << std::endl; outPath.close(); @@ -123,7 +123,6 @@ int main(int argc, char *argv[]) { const std::chrono::duration loadTime = loadEnd - loadStart; std::cout << "[buddy-codegen] Params load time: " - << (double)(loadTime.count()) / 1000 << "s\n" - << std::endl; + << (double)(loadTime.count()) / 1000 << "s" << std::endl; } } diff --git a/tests/pytorch/default.nix b/tests/pytorch/default.nix index 44a4c09ea..4198511ea 100644 --- a/tests/pytorch/default.nix +++ b/tests/pytorch/default.nix @@ -7,6 +7,7 @@ getTestRequiredFeatures, t1main, callPackage, + buddy-codegen, }: let @@ -25,6 +26,7 @@ let nativeBuildInputs = [ buddy-mlir.pyenv buddy-mlir + buddy-codegen ]; src = sourcePath; diff --git a/tests/pytorch/resnet18/build.nix b/tests/pytorch/resnet18/build.nix index 8f73f4907..6a52c55d4 100644 --- a/tests/pytorch/resnet18/build.nix +++ b/tests/pytorch/resnet18/build.nix @@ -9,7 +9,7 @@ let }; in buildBuddyE2ETest { - caseName = "mobilenet"; + caseName = "resnet18"; optPhase = '' mkdir -p pytorchCache/hub/checkpoints/ @@ -57,6 +57,10 @@ buildBuddyE2ETest { echo "Compiling memrefCopy library" $CXX -nostdlib -c ${../lib/MemrefCopy.cc} -o memrefCopy.o + + buddy-codegen arg -i arg0.data -o arg0.inc -s 11699112 + buddy-codegen img -i ${./dog-224_224.png} -o generated-img.inc + llcArtifacts+=( memrefCopy.o ) diff --git a/tests/pytorch/resnet18/dog-224_224.png b/tests/pytorch/resnet18/dog-224_224.png new file mode 100644 index 0000000000000000000000000000000000000000..4c6649714c90c4d8ad7a564b1dad69c14f84df5c GIT binary patch literal 94456 zcmV)5K*_&}P)a@mVRMbec!vD-F2PSwZm^ZmTOs# z)AMV$>nnc8c3sc&_{pYe+P2ltI1ayKIp!~#58)U5uhZ9QnjEs6Pqlh}%4zzhx4YAe zsvt(#Bm&XQJ&}7VdsUxcs$ah*KMMHB;i=Lfc6;BqygD!WxN;JBSzTUjrl;}4_wFD3V!zpY ztzWnH!RkFXj1PfxS&ezqu&^e*-AtOc@*R7(*;;nbvw4B>~dW;^TiP>)AsE?-}8uNm9qtD8i!$+MsaIvAFGz{Z>+Y< zcl-R1g@M!Y^`U+C=%VNjmeW>s?z_(E@uG;rv2%L;{^sfXPft&#@yzPNZk6SmtG(C7 z&8CQ)fX}b(#!A~{uDxJe8keneSttxfW`UvBb&Vxq3$|;IMyc_uM3oo36n=T-UM8_qq;S+qPYQXxb=E#*Z)a(<0$qxHhJz@UeQ&(W0`g7T&;xV z`0R(xEAZ%%?>m;$aQGdLq!$Fe=lQ*sMN8PWy)4>Tr%t|Qu_%4pcbr^5sH&>dHgr)E zEmt?J@yW$ve!PfBquzH$Y0BSX58A3J4te0Z*c|)Sv|X9&`B`-Q39G^R?=60+)@N~y zx{lv3G9JI+fwV8S&04V+by+rbi+wbmwK$nQc>N(;#)9zoE-&-hGzv$f$V#$hk=^XD zf5!VeP{OB7%Q*U55~!FU}oCz!Twmm!88Y#X&EDR%s;RG)WiMC zmfw9S?B;msWH%!-*H^W^&I|~z2sH#2cGZq z1+M2usZ7)NbS&h~n-*8(dwx|mwhpIW%bbZ$`x*tW&630kS&2RhruU9(r)oI_tY+u* zw!_~{!=NjxzHR*|scIIp<7u0!;xpT};T1ifK;hsnIUX!IyU5F8pgi*-JMgsFgd4cp z3_L?8gW$=XfX~MTI-EV8rYH)&4r_2cEu0BQe3uLnH;GLd8?|xFCQx9w9XrA5xf~l; ztGvthony_8rhG`*R4ho8#C3@)tvGU+^?sl6Y`8)G76#!sgQKy^s$el$l(NX1uGYr2 zoz7FIP30%$-3b&tMu%k=SQ8tLKaEF07>DVUXlCDBUA1+;n2#`&z>3OMzFThC=MF#6 zSJTPN>m0us%&lix9UVfM1kQ%=&F;4tKC7dsO7QGmo|_ZFw{l+m(C2?yBbn5#fyzhn ztV39|OfVj7Rkn%W91f1`wHtio;64X)ytQN5sbxOu)_i60GF{tvQ-0XBoZ&-d|2$z7 zEADV)haZlT#I70^g(cGxHjQ!o4gMwweIA9)W8L)%>?p=;Sz#RE#G10k5wsQNWMQ?I zg?$jJe16Rl1&P#6$*vrq&d=SZ!fiIrIxnI{;)NXNvTNj$t4@)}bFD&K%dUjHUvE{O z@fbWYCzVGHqlm4{3*ZQQ$6xK_vRzk?+BC98-r3&|9VEGFA$EUIvV=i;R}eNEfPX$*cS%8~`HZ+06TIZvCUF@|201&`;-cMvo!bM^~kA#A$E-{tUdPD%{S_#%GI(_-TI3(s?n zqjR>0JsT0n$<1;(O{3%a(T}fx+#c%H~x= z!L1Ko^i>4EMX1K1{=|eGY?Is>9~lnhYNiI8iorgrze{oNp}??G)B!2%x~qHTJvP zWo=p8W*7J|EENL{0yj-ZtN>n>B`MhNz7vk;)7#!l3C9l4F-k_5vyZd#{R|jevcetcP1*4?*HN2jxr_wC)yvl~37;}OTZCUn!qBnksphUY|s z+ZuM3CF6f(quOs_4n33I5Lwc3?1x^5*J-=Kn0oV|yn|zM9eaQ+w@B16pr0e?{t(;o?NJ#ZTFZ6 zhmCcr%Zd=9fZ#j(1E+EyC$XRf#_9A&C&x8M+OwlLVX-kLPQnOGf?vl6*42K$wQzY+ z5QniX&>0yY(dx9278ob$3aTh9@PBMN7=}k|O|<0HRe%_fcXtc$4CI!-1z>08fnCX7 zknGm+nOzqkmqd~`C})tTi4B+|tuuk+d_m?d5?8Ckf75NmJTzz=1h1QL)N2h$IA z99uR~FRbl$^`(yAd!Y*^>njIuuXPy$nV`FClJLRnb%R^g7Jz*$h5FlU=+@WX8m0a6 zpPwX5=6`tvEr)!cQde2Oq6xr>6BQpHpJw|#pb=wg1i%30C7=~l(il%;yt0*~m|Vh9 z1`F~po7ua*Btxe7P|WwkCyq;5P|c7M?LYc{l=ebI6?7 zjb@3^v>jI4#;KkCF00tPSsG187LGF{Ck8lqJ+8!8MmuELjj<%-18ol5*6GUYiB-V_#Azd z7RQJ0_z(X5zw^KUZ~w;^KK~0Gd0-}Ap)ZXCHwp;V0)xKG7d3|M;;Ri#btdBx*3OA0 z%Kyw~KFde(;KiZYZnq@4d@1oO%XT2B!(lHs7KrA!S+8!^o2u_d)6?^ZkJF2z0`wUX zi=6FtpM;5Bw=+KtTDMnh^t)c0QdhaB%!%}<<>>~R1Ix#xY;%frL|vuixE*629@Ni= zA3i0x6^9xRPH^I2lMNdPEYvO|`UCWf%4Y>;niL9&j2_1FtQQ8#1Ij|@bvo0-g@O|2N-&bWzB!%K^1PO$|NRQ-! zcyi_CGW|dM^FKf2f`*-(t$BEnLijmX>s*&bnd3FZ0N7$%%d*;DZ_BnGpDrdx3;q_S zJT@@Rsk*ce_Ln=~iSV?_{@W%df}5983gakm5X|Vh&33Ot4&e+kGgz4iW*Ik|y+Wt! znRRiMdh`0e+#qByxi#@SFUy2@Fiv-Sz_fm*mw7vQi@Hz}!@sOD4*&q09nZ(JsaPb( z9!CTY*UR8^}bC?&#k5-fQ=^n{|6A z=h4E>oy(_}xtI$0jl=@ZbBU@DEtyWR&n1d4O0XXbbgii2=YQ`rZDZm+D9}nlOo# zZ*rC$H;LfXO-$@9lh|?^TXNgFtrVe~IvvHWP+E7l`juM+g^_|fV%WL{EgNN7bgG;Pa8#8`EtF+41+k~y+q5GWgO{&DIX|M$*4UO zJY55~^a{iVxF7zDIs!jJ>4AVtA)*r(000)`Wy6M?QXA3%Y@+dOW~>Pkn1!_VSqAb> z(ot`6QyeF2L-Yl`AGUk`rfmol^VZ|R0kQRhWxe^tR@4oUmkk>>ir_@GpTtdi2S1{N$keqGRDdU+qNtRrCBUm;8ra0p%oJWI73)T+{t1drX!%0gINS52$3beY&d`b zGS^TH+9ga=Twnx~TJfp9GGfd-bi_PD>~tweqV{e+xT;p!A{p0*tgpKLCO4dyCtBT6 z@_Hytk<7YfDhA3z3?XXH)I>sW(_5|?P^mM1tcg>y4~+q zI2+@}pbL8AM0xt#gh7V^fMMljwb^ViDbd;mWxs6rD~jn}04|a@^)Z`J_{{KAm8kWq zI8+>!rl`S5WnS_6@{z2eAL>6<`NMOQl2?=`x~~7}062t7j6^ikfJBGNi23K1UL44a zx+@MPPZD_}a&K(x_++t|r_g%fM<*&TUYnmim~Hmk>zhl0*+5%@+jO=q;Uwb|`3`(K z6%?RJZ5Smb)cm5I&Jq@c+z_Y8GAg-fESPiUo&(|-*7zkDm2>rdL;7g1O!jyCZlXVp zQMxZ#rvZ}cm8{!ji$8#pV;j#;F8<5E`*)Kd0v7TC7{tB%cfS6Ouf6}n_gO(=3>yg` zgVBJ`RhDH+21lpIszbNK z9V~0M*#fH$VEGWhQD%DzQLW%e=*AP+7(ZZ<`%(*H0$*o%HNLr1g}v3G=c_~ChNnAJ ztqJ5E)k5;)!jv(FLo>JvQ#w?kQv8VT^)HL;uPUHJ(dZ==W&d>kF34|*Q>vO4@5Seb zfz8%_WI~|4u^x5thI(uR0Ob<72zE$<(FPl z9e8f4^-7zZ9>$TNFB9n-A_rAI9caT&T_!uU4G0-hNoxqImO(CUf7tE$MvkN{E{}L^ zuUA|4akiM^OcY))gMl+vX%x>Z5WOYFHrX1=C-PVj78AZH5V8yQ-96oE7DoC z+}%KRo!sS&Ppi`AWOO@~0EHuY0pHe^$nz`gYJ)|v8JuB=U}*eG?0wf7;7R1;rxctx zhObIN1F#)_VN9+cEOH1mg!|q&`@v)f9)GY`kpbon^%A1_`%sX>fAjI1zwx)e$~!Kv zZ+L0s8~fdcHFJt|1lGKAa^WE4$+zY*1OlKVjAadCd7?URYCR%KEd zH}Ry~0G91`JKx1=_2ofzo5?s4U)r_XRY5*Kn~W~*o@D!V+0_nw6NudFsF3s}tX>V& z#rblcBj4gHd2Q+)2vtlRbOyG8|J5ccaw=;Sk}AlEl%4ht8WEHID3kGeVdzG+5_A;6 z#$*JLWVssEitrb`H}H`H`6T=9t$;!i*;DzGQbbvsI)!hF_o2f5cE|TFj%J)0S6Uv` zRLwf+cmr;dFe<9@u-xSUt4e;H-1)@;qQJoi@EDwTrK~16n97c8bQspK^?F&`pg5C% z_F7vYuR@5R|1EffSmyLKqTx2}`jg${;n}n6o5KzY4`-Q7CW014)8Q;^UOj+xZ+Qj5 zQkZ|+Fq*YavdPi}>BKaV*P$VT1ZmrCNS-%~cxet9{)x;$*okbW)43dMZs7k0r>>*q z*gu`H|1^AY%-ars>y<;LzABE>@XznQ`6$GWo` z$p0)HMZN0Vb-8Ms2KO_DxIx6UJFE_NJBib7o#UPYZ_;>B0S7!691gh|j14q`*)ly0 z=nOB2OH(nOBx>+6hLOSg*+L%G0cZAlLPgEtf!GBx85sH@<%>rnK;BLjUHLFmkj8gF z0=BoSr+08W6Caecz^*tpgeQQHqyh6=F0Zroh9G@%azdQo2o5?Kvt7M{6((3oEZ~dO zkob&eP@0d+;Mik-5NUk6(kj&>TCEnEe`xb3^2&e-r)9L`#KAr#E`{=<0?Q)jPlZf^GL%sa&7_~~q1 z1=^*$bF#XaIWtpm=lw|BLp(cyAtvX>%*v2+^GZ@UL=xz)YPp=CTLaLGQLj{~G6&L^ zOr%5e*2oE=k-+oT{HIH#gBFIXXI`PGMPlK2K0e>6G&dzKbtZz1cOja&mk- zjT61Bw$`x1{N27E(#qL*vfuB`-(|I7wV_=}j5v>VEWV6$h5DsX1e1eOEtJ2f!)&Bh zjyYY9HDEu-WDE8{a=S`!d$KMN1RExN($=pOLPnW}&e0JCgdqWY`A9I6CC#WUP8hB=bmDSXtE|U1 z=C!BT+yhnyC6f8Rm~<>tZ=orNt)!3|!v;2MT4Xp}CTwI?At6qxw|9{ebKX z8O5*s>aV>0_TzkgxxakoL#o$ROno+VC-doaarDpr$^UctbnW_Eir^$U(3;p8F&s&! zDR8mheNkrkdcFnegCEX4Z{b-4U*NS54XMRdR26NB4vu8RiTs#I0~B-oJI5DC2pR4K z=@DLRMllWeSS#L92pl;G55)>^%k9ln`By*^K%SJgXO-73tyMd5AW@KW95~lm@#N8C zuR~yKvuWNq0-1CrR4wwL1~C%~CzA4*0@x((f~Lb)L_S$F8Hio@{jNP8&v32VY-bmc z+i{COLYqRE2ji#^El$XhV3L}x-x4;`ppQuLfFLPGR_o#DaaM0Mi&BIY1Bv8e`cs;TaX!?71hsa)0B?f?!1TCmf+Ey+9x7aE&=tva%ljTKsaU|WL$$l~{esULsx zG!DjSL#P6vF9G`3w)Cd_5#LMAf!9Z;Qd>sPCe>bp9=Kc=5;a)e)z#JPczk?3V(GIz zTdh#VQCFSBC~}NbbH#o-s1%e%k>zrVdPR(8&`YpMeckTY+lqz4V^GzEY;)Sv1S5tj z9>rO?Zwq`1Cy~TPO4imC8+#){zvW{rBRA9T;nc~S6EbK*7Fws~Hkr`A`_!Uq`6k(; z?I#f^vfN8Q=15kE*)+zM3S0w39Y+}#3=3wbq{;9x9UO9g$lY+_58`4Yp)(wwaiqjM zBkF8lmarr^(l5v;1U#i{R9H3|Hu1)?O8Yii8xmsR{0ILka$rc~O$}ASMLCUvgHaQ! zzOQ`JgpM9%Cq)Ox|Lx!W%{#}l9a6%)@I~5$+Ste$A5A8U1&VVBoadixBtxunL+v48 zg>$wj^+;vmi!?cuIiz8P$XL8LzkoyFpEyV)7!_X*x++O;D0s&HA}caq1epMjF&9ig zhtyPaFdPDamXw%C+Sa<#`vUv01yV9nMjV#*$AbcIf+SKsQ%rVXo1zp%140MYDOyV< zm|kI?C#U@(v4@4iQQt?ZgrP3Gz?Vom%#-JsG3T>2o57n zkk2VicGVAv#E?f4C)pQI~>bGc0UG3uR z&foc+zim}{wcW(f9mFR);>;$KaC9_@#_43bz&47^p?TwzPd={I4Z`Wfx#IpD)>F0+ zKrNwXji$3BjHh+fWg!$IDX+-$N@|jt-{|H9n`oWpiXME8XhXW zxN|0rQmc|zQRQ(OI+9b#)2i~>>ev}g8M>BY$V`gzAS;yJ8U?9TUw%Iveb1D24bx?o z)8_^BP_>*AYG;uz6c_x+Xy6-=1_G4EFbI;?gTW3k01k{zcPk52$}he~oRm@jDB#PT zVXRnKAZZbwMCs~fH4M*8VQ**)Lg|sH^#REgpMZ+b8w8f!P;D~dOAs1n!&BgbKIrZJ z;h+Gn+P&@4_F!c2$P>%L$2ezG(2OV zoe|qBW3pH)=6FEpBh(Q8>YM*BIaDWE5%363M1r(N^$avC2~}p+h~4HJEA{lPhp|;Of!XW5~Pxwioi0-N0fAJ+wMIMx|~b80g57U_4Z7gP8B0l8>WQ z&zQG_upqJCq%_zEC}lJqDX}9=DQvolV=##^aTpswEq_;n=&Ht1vks0Fu~tN8O0LQS zM^jZ42TIHx5<{a%9rWHW8;{yoP*vEtlSbHnshE>n?OuwLt%1w6ZfPaR1*;!CH2*P( zuuZY5^M_s*%XC51wLaHShSB&a2*yA2?$7@A-}=ovC-ZE1d2{)KU^khZ`q4O^Cdp!i zO;Rc72r0IEesa8CufOrff9y!DTXMXiAdMeZ^-l!}KNS1|5$UkONGXRWy0%6e1{H-r zV0$SVvAC_yW=EF@_Ms9c*oC^VXX;^{CA~AAps!(XpbrmuAzp}%DB@R9k&P!f_#?lT zLLESo9`-o}s-v^na<_zB6ay++7UxitWb?_?i5vAQ+7i1sHkx+AjEV1jer_6!A|j(r$XZS`HcKvSHEZvx#xOk1jk$p5*@XgT|;pt9?(Mhg6zlokp=QG1SQUc-w)LVK|84e0X9-<1DqtjsM<0_CNrAw&2VJ4)Jz704uRGl zT5@_*z%u2Kp}=aGqnG3*`4!*K-!v?yexSeQJDgiUG@Q2E=8wR7*|wqFO+s72*-q_2 zDuP*1FonUp>bAh&*DS9XTVC(u*dtA^n?sPe2ty$j@khjT+|dxiHk)-6OZ|!Y)=C$9(SnW`DBJ~hgMa|N znvt#r(2FF^t_gdv`5cf-^v9_k$dQIz#In2H?-4wr3pV-zTbe7H zt+7JN8d4mDutV5G6|Vv=ms~b5Njy{jTv8BGW@B&F)~P-9Gok-4UZN?C@4otWFvK~D5=q=QzcTa_1V!G62NNhcCm z$?S|e&g6o0z_QN5ezNSLEmtuq@XTq~p_(q}Doh8~Q(RYXs@0~D8WK`~`8)^u3}U~k5lI_O!k0Ck{x-)JBkPz)Z3MO?>#U67 z32t_(X{on>-@$#`gWr{P_l|wrG1nVC)?kWaB#r15Ks%-Y0rNv(&J*2$hnvNG34IpJSic?kG zB#|*{n9fv|#p|-i)TWM(=BH=J^i*`F(9|mm$0XXcN22n@!ZPQ}q7`u#E1-EOZW&sBLMs(r?MccRQ|Fl) zqAQja!GM^~hRR{(h^H$Ls1|RPO6mivCFb{ru7O(Jn=Y}9C*UVd3dq9({RxjACu}l> z7dyeqea{`R0Yw(qG+FHSnR3`9D&UxmLdaHcCQk?rENhX=<8WsOg*H>zC60@QX2;11 z08m3wP7M_=D=wzZNa5Krjlf1?{IWhIw#*p{Cqs$ownW=0SusRuZ=@uasdm$hMzdZ* z^g+fQ_{nT~LR<97$@!PQ^ric+z5cn+zPlKsJn9q11E9+|T8vK4MyGRvFPw!sB@D)e z(<57>7PDB4jUu_H4pg-WM-HAu%N$@Tga}8(H$!U!0H)(vx1-ddDFdTqtZX!ZAr9Be z6)x7p1SRwH4WbtE@}-xlZmw57khUG+PBHl#aOB4bowu1#I49mOck15rzZNUy@# zB(PI339Ag@@{ZT?Avt};y-ta~l9yIWls%1p={(9ed@s!~HdPf#);OU)CM=mjr^yVw zPuRIiJ4Tc7VbAAO^)@d%d({@wnlN4f*Q(N1$Qf08QD=mLQJmAERkxJ!@cb~cc;Azw z6O@=u-YR>eE=3Ij?;S`)#=r4s%ziOYLuFD|9A!zAkLY|aJhd!)cyUvJRIh1>mmMdq zq)SZ_1!}F8volG$WrRz;W;N*7LxsvHSe>74$#~V+{<4#}8HoB|Boa*dJcDR zqI_E;9tRZ3nsdaxciwsT{=G;0eSzHev!8kI^>^M_OcO?LY_2XLnIFFX1hV_+?s=H{ zYQuGhCJO6S8=fzWWStzJ&1MVB`XOBjlA&2Z2m*y7U0a(hv}+=NwA<{{-oCoL8qe|8 zBla4R1d)PZY5;(*R+3H=B6u%>Zvf+JU2O3uMH@E;{h@>>Z*}HJGbrYRx7F>V#Fz*IBu=;WUN9`PF%gINvb}Ky;`?x zLD^_)t*$my2XK4ys4nvZuTNG%8iAg#)vycxn?5*-+UobA*=BJz4ZSo!)YXct@t{@~ z$Kva6r~Y7wzV0|W8A>xCxM;QWxmvOy;DjSMRu4ib!UVz(Gy3-HoYJj^7|;s%t9qJ{k@H~-jWijsk==fONW%2&=z`2fEh&^7 z*-}T_4cU-K5Wo1+-DXEV!B;Db!jSp~3Yh3GX`s9sn(>m}BkWR>W_ro=Ytw!W z)Ev#JxrRo)(NAZilZ$EH?+#nTZg!34HyBR^ZXheFOQl$Z$6x;Pmx)6E=O6yzbT*blEi%|` zmNKsjQH?M| z)sRe1?2R7ZuDBj?p>zf=*j)j9~C*+W~q7)?WL>*0lHfZH&S zq$$d=)%`5dIt~n5s7+HGTL4$usuUuhU^-^h_!WyIQfH1D*F3lE%3WS2GynYFxk~1) z`s~2i``wm>1!>UB)eGM%$|JWuWZ7gqX03_uo*E<6r>i>iD8`z1MTYi6NhpTeDk*A> zph->R@;%)k@%9GRYv7v%kd+rxkw*4G8-++25t&2jXkhBiuLo}mYiMR#*}^-@>9z10 z=i#cM4@?2;^~M`-0AEtbP3HEv90@%GTqI}@S41^P^$H`8^^Dx?cJ!02qMjoKF+~v- z^dCLE|HWVYrP=HV)@ifeV48#)n(RLF?xzXCpMUSY-}~L)`?vq*uTs}O6j`lVI<&g) zzV@|05*+Wmxs~h9MEC0p*SCB1A-3Nq558RvUM( zEWeYUlvyVgM-tgUAx)|8grk^r7}`ty-e?ARh5&v56K)|lsDY{B$;hlYM3YI%PJsnV zO*(<|r*Df~3F^*ToK4V2E0LnDuX7GN^b|MBlkiiydYmu`&NF@lVnjdI2rWR{DSgr7 zk)5X$Hdgzsi-LM+KR0>$kjM`Mw~%!azV+U%=*cyW#V>=q^eNVDW1sw6 zE3{xnLD)n7G)$F2lTMOp9UYy%{`#B0^E?0Eul?GupPioKKaP(nMXG42IyF`7jOPxh zKC0N5@B&ic@>trsq3y^v6r@pAsBPX;Ey47Kg`fZY=iYno3pqUmhyL!J$A9m?{d=b; zr<{Rb`tmP6e*NObhaZz=2%!hTd@=pbcfS2c|L7mlFRqGbSHu64hj|A1nK}iY+D}Fl zN{<~g!(~s~npLy$oH@-ZI~23&9NA7(}{tY){}k=Btr(onU_x6iIWp>csIA}J%XTyR{Sp~}eBLqe?z zOkcwyOlF_})ECD+R4w;6*E^*hrW+J5Pbv-jAc`x>B zX3*IbY(ueUQi^m*8jdIENNiJq?`HF@ z2&o*AhzEvel{}?HOyi6|K7D{oR+_KzLZ(2ffheK_xQJd>$l#0_g&tfOdW(l5eUti(cm{%%XQ3TgCtTQh>?eqwfJH+hrtXg zBxt(UpbGP)EGb=Q5ShCozB=m*UwG&3H~zbiKHQOn%qYE#6Y0;p&4(ZUdHbScxKF5n zsGQw&wAs)FSRy@5%C0C>dfsj)?8cYUcd$4;2WnVBhjM70F)DDkSWFMu#x*@mnrjD= zSK&YfHcyH~ROqEYRYhnNjt?}4WaJQjL4g};HmAwRJw2J1Y7f}Z9R&R1Vbo^bb^wny z@5W zXhwZ>=_G<_g)U%{5N?e*mSkGxu3C&|4<0;VeXh4JaFvjDS1;C9X{ktpPCV4KzQt_5 z>nrhju;3aI&<%Z#gYghFoM8@y>1p9G@X@$tu(K^Gobo}2o-t;xMso{0Z>D^9NM?~2 zjus0Nru{MlXn+GqKmgOqzX&{b3opn!&u~Aj5=%Tfln{WC!%h=~DQNXc;F{1zG^w`P z)9wCQ+ux^You)p}70k24Xoa4soJ2^wNjhf?WqDA;DR0s_!=T6JmR*%>#;C{iD}t?L zBGOIhMB1k8#qoLLodg0Q%eGACEa9TBud3&}?e!AovPF5F9tFxeyxeOFYg77VopL&w zaWXf(wvJLorB_Hl5Sg>7)EgVZnpL^mD?09@&0#~WxoFG4)gZC}R+NH#vZ3#0+)ceL zT}qiXjc}7#?$OBz3XB9Ffk<|0R{O^!9f!J=~O6Sdd=VLF}9(rINU`HlrUW<6SW+x6?S^K`uM{_I;n+TLWr#0H-F z*(h@R#eL_%fQSz}YoGKm0a1TLBahqDK_BCTt9?>iS1o^TWB{KE0D4#ulfnNxxq@sOFr@~}XC2F)=B&Pu_8;9oXbitgD)$?V0Q+a4` zL&hd;q>^$uF@ir&AXo)r)6vlk zj(I+xz4P{`%%BRYC$ZTZ6_2l%E7CfM2I|Pj!c9S4UV9h?ILv(bNnQ!34_^TU3>T4gua+smu<cOhrc`1d$-VWrPSgPC9AGXVu1RQxv6i?rWM|+i$lA zq%EUK4DN=4O;d`T3^BEhTB0{2q@1Bh$8tx}h+QL(7C%*FB&E`<;*!Zw%5FAYjHaon z!ZdY}@76bfa?&Eu7LFrK<4WxT=&7o4I>WQBuQw!NXVZ&%pRp37#h4dw=;M&UO(oi3 zj6yqMiMOjI&C{fOZAphvSW7daF(**x@DIpINMu=!`}gk$reNV3nK_tk&V1VutxlA* zJD!~kEA`|3a?5bli@SFiT9nTkybYWAW4s;)^vvO+zSAbwLxkh8aALI}AFtG<_AoL?t{yn&~37+#fRFrRhr1 z=lcc^5;fSv)*c3f7>ZQ0oCX03jW`VUJ$m%$5lSBD63O1+GHi=%i9rM8Lg#09Yz-Ky z%qXuwBWfMjltW*Nqy|2~w4U0gg-gnKjUy2M)R}%TtcdWfQ3vZyc6C***8M(X2qI>| zG{7(!jTW=X@%&qV@#kOr#@CB{PjSCdT~taP3K*5f5{V=TUE+U20%34nCAL@-p``Q~ zWM0Dw;`_=hCwgemFgx#fQRte=VQ+@gFu}z%=F-8ZWPv2ZqDoe3ei-IS!C&ps;_FDr zJ2ky@nifXMEobNGS|O2s{(NDM(WXABg2qR2_N&^^ zT@rDal43v1Si>HeiecsvQ|Z=dVDP9&0F@{@1!QIbgTVulGV+hMU&$;~>th?)OIO7h zI(z4n3IA_Frw||&BVeByXtTWB{_y=D;Zy;L&@R0?%c8EmozLtHv(s)Tf`Ha*yE_lW+XfKmHg0@}GAqkV-8n zZL5*hN&?tvvSD*jJ3wciT+?Ps(SH04^=0YAOfe15uSrQpI%$Snm?6l52(7@zWS)!U z>oi)?OpGBh1vRa98*w&`&O7`L|KJ3HLrCfXEc3?difKSozORK|tr6%P-@?b@Uq6)+dcTc!Dnw(H>fXZcbA3YniiY;F( z!OGcs|NQa=F$N(T`~+kh5Tciaj~DWJ8}L2hmjQ&VaGHXW9nE#cA>sJPlVmj3EFO*W zn>~AW3B2!TCv#2;sU^D&kpUBkwKb+}V02v0;6878P~gAsp|M4xfyGer!g%v6!&eQB za&j8hFj+vR3}U6{!Av_eHC;ga9@yS4W=Bt6dsA}AR4t3(aw1w@9RBD3{(t@b-~avN zqm$R4ye7faExWA2+L~(AwXa6z3{;+F8Z^zImfKbnGal4n3HTeDly9y!&o9g6%4XDb zZ`xQ%@{Z;w5AVSjeD{0b`N8+TFDVJHCjlTh-IDTLWUJW0zJy0ali#X1OgXqHE#|NW>meJE=7U3Kvka6HFwH zhRJ>?gec6$W*j6(fRUG213--?th2Lo(w8ex3SERb;^mes3g0MBFa`%FhJ|5= zgI-O6r*>ZJ%2{aqSSE*oIA}!K$RX}jc!G{@4_DAQ)$p)n4_)H`YsZ2b<))7 zbOI+`?iqe5diCzTyJQ~+_2MbvW}yhY`@KfDk}D%v&a%TO9cx4!tOp00CcA^?7YJA7 zq>fppx{bJ^&IniSL7#%Dgc{<8nm~QD>Yc4?+%;7)Uls{+)A0wecoNBWL_LN_yqW+* zbRZeR3QWC}5*)*@RbGM;P_e*kzxMU7|H;4l=KY8FHmh}D&>8kbC6Mzah2L#8wKH8Z zMg(UNXMa27c4*6nJ~q9ypcu-K6ft*uns+HoxTYMmxxDsI78<9ns}A~y4rY5IZ#tTmTb#WIsH4K93WQ>HF=Ae+YP3%VIGjq37H1m24se95vLMnyr)Ak$3Ojoi1*lUjGe9)~DR&!w?(^z@+3=1F~ak zT9Ve3)0N5JrLwGz_NCM^bw8Cu$br&yir*!I zA905WdiP`uMB5*lZvb9Ep}&6fo8VL~7`c0K7MiJJn(NJ8hJg~4VHR^Yw6u4_EPVIn zFmY2OaWtZ`Q9BG9F14;01cn#W`M>~2a=E=v+Z&myrI=1|CEw%B*czr8AsjWS`HGg_oNH z3gJl*z2F#660S_3F^%nmz(GzHOlRVfHR)HQ@`ax}O~QhG8>XitSwJ91;Nx9aYiJG7 z6oo;=iqU*_vfS^kuGi_59Ku72sHr|WSI9N0#l^xq?yG0Jn-_3r^GYQnhL*X|mi#cI ze{d&<1HPu=G82uHYn;muuOWdpyXC{zA9Uq`a7n-Nezkk!$!omm)y)g&whw;v(R?&- z=;kGR=D@FSf+G|^Q4x}Q(<=~+PSb6Ne4)&)a@~DG#^d#9w`b^(lW2NABL@JILd-BS z5eVyFuV>?FnQh4=d1GFdcORK+NlKdg!kO(Ffs-bta%$A-mC^O5?hrG`*^(dEy&9Mo zww73-Mp7#E>WMgJDyD9)Xq920!zE92@XwsH`FNrea}bNEnX4L`D$$W8$L3pkcsD72`J8clFVeN8|GoLbb{@owZ~HGVDF6vaaDM z$;|hR23&77*H|5ajyk{O_zkzASzRi;VW(u4yL@Nn6(W=6E(dqSm@ZO%s_nKe7~sGp zEE-~G(yp#L^i6J8b|9_bgrG}K;2(>!CdMgZ_)UE~6M(g1S{*M+V=+i|x|jpIfK{{{ zz?|@qg5Isf;)FwXicu(t;nFZw1(58C&X92*&D;edK5w#IDLn&=@h|kNCU`|+2ROoj zf?@I~g@9hPWpl{Uh+l~u1rBjx#Ac`Y5oz@FlMg8+pb_Pz{E1 zu_v?1^mrCd$0TjwGtL{Y$Y#u^Gd!p!N*t%R7lk0_D{8WA62<9w4k`!fnoy{*;`kWK zSSoJ!(2$zD8x0eR2UQlnh5%&p5>?)H(~oIeRp%6=>^1Yq9na^8uQbH25i$!zNJPmr zRi`)qLZH}vBH5T|5oain>MJ0(+fTjo=`a7)FWD%oA#oukjwhw;^LS8>X&}` zZ+!XqbYz=JzviY6r!_4K!(6{XeZpLZq1VnawCHdXgKAgpaBGd4jQKHU(n6qIKK*z} zGcm|!wJNt|d_12&yo+#O_nmOlMBQ%I4ENryAm7cpUsx6r`G5r)!gF;_o9TRtdWiC8>P0 z;eLoB(STL_PlbMEpOcwEGma6UVhuz9b;SDagV!E2d3Dl!+6u8A=mAw{ z*I9*DZ?jx8@BW1x6?M@SbXs^d4Oi%t{y~&rWg8!xPLd>{(?jKE&KdVB@ zbyJ>Z$$s?7Csbd^(&#G!cAFMxiG?)<()41hL6*@B&^TT&4Cfyv8X!$NL&JP}s+muk zoI&qs|HeCSkc4ix=+1Jik*rzXlu>*L+e0%nz3nGdr%-Tau1awV(BmbU94Ip#8+@-} zVdS}n=CjR2MWmSJ)~ZowLo=a(Vw~16Dv&=_6IDQ4@`wbFHZDkbUpI3x(Y}bIsbkUp z%KXjh{G4h|d|)E{AwN60)7K8a0IO@XQ}1whkwlq+CP|a?VFqd>(kdB*nCu3dmDW znB!3&3e;R0h~h&lHGDCDf-c+0ueewWDT2MpmW)oDP}6}MrG#xRX`uj1CAdR8+VxcT zH8)2tCFq9>Liagb~^C=R9AZp?_BAR6YFT1L#ol4)?JX`kc7 z(0sOMs-$Z3h&!d{z<*01O*Uf$oAlD?S`yNQLZ#kTJ<(i`vPhCK#P6Mpd!Kpd(?C4J z2P3-GK^8>Q=~(v|AyBk5qh7;(6s=|JjyTk2tCDW{{MmDk8G;5%7sEv$!#qBPU(@nt zD$L?trPp#D5_t=?@b&d~{`6mGH=AHSzw_kr=yVZ`a3s2W4jUzU?ur8#i>ONY+^$wc zDxR7JOKIS;hr*TUUfm;Q6hLIAcveTZxU`UNXwh4Zn?Oeg2h43R$$BFX!2{rsz{QN5 zL++UKTfnbciV+nCl;?ZHKA?&s#_`Fz8w5eBJVB?w+X)@P??|XYlAm}96iQ+)h)#zPWcQ9 zl}N*LsGfBQXB#936OZ9ispRZ|6*I4`({x$^Rw#M0Kj?z0(6lyP(^N6C2NwXAU@69v zsXITJNtbFy@<4W=&xpjHV4x&Sl&+`}al)Em=9T*TM$Ffc0t)uPI7|I5ewsxh?Px@| z3g94aa+WhM-S8sd0}V+9m(?4HDym7=X`%t=gtgAMyJEAw z{`ixRzV|+L)Z;fF-FxFT+M5au;gLXO`WK*+J!)g8Spu31PRM-`Z z**WG;2}!GTqkx9DbA<&4%5{+?QJgY!QyGP-E@@B$G#MVfa??qxI}PdTM1c?aFSS4U zfoejPBY~SAa8{Wu&YL2bALa@mCgR&P;mFpt5X009O6{;#oN9>uQj;GFApK-CIi4-J z&1tvZaO~4*dV1%SD8RNhIfsf&^8hFbe)uEAG zu)`yyt9;91HpayfxrAdplr*X42xUNv2Dg-e5R#J6hgukg5@eX>A5!1eOjgoNDmlgJ z=y>W)7{JC{d}dIM1GE>$F$QW%MC3y)ll1xYldPA|Uq-M@VQ{qH|{`%_Qee&@lX$K2Gz>){WK z3wDRzk?WhA4}SE)(+{6sUcPw${r5lo`00Q4U;O7^{L&Yd%$m}Xh-_g&>(!JV>vsMd zx93cr7Ukv5iV|Ma97gA-(RADnBZm9Vv~$XN!R6**WI5cL6B-abR@ z=NWmXdSLR5AO>gwETU3yLX+C~Lx&6-`d>Vz|-MdE}!3Enagc}38qFs+v^7}= zOKrp{Kyo@BQ4yg;L&Y&trNG%O%YI+9liC?ebhenqi*$~9#v6m7lNa@daIlz65v`N; za3o$_J;PimlTz}TjTUk2Y8`R*)PhA!NnbCRI8*yK}up*}xgFS1&dR zZpwC-%NLB7BJomM*$&rW)g+{5G)v3e7sL&c-jT)TOc3ctK;~A8K1`Qr%fs_jMC?wT zUS{-1I-y}k{h7MxPl{1*=xfFViQptMrt4Wv5v@P;zsh%+8E#aQ5u~NPX+O#~`|^+@ z%~{^uiXpP^udFN z3{#Pm#@s+)1}ti}_HMQPzyJBa{L^p#=^y@sKlt{yzs(+e@Zpc%c;m^Pi;JOQ*J!L- zGo8rXeqe4LBya)n3Cf6aNXjptJqH)weebQi4<49#K^kPl3!^qISHOQz&WoB&pHjSy|0?O`h!djh0qIwTz%xL#)d zQ17CM1KF!FD8x>Lr@Ft?E9*TMBw}>aqf6mrBz%jHrQ1WDc zYzCg7+3lFxfYGSts*yMD>FI(k-tX4P^;LhTK`-_h$UY2OL@}Uy$GSr6%Pk@#Jv5D= z?qzEDe!tl+2^|o9)DZ(NIMJ9A-VAQ4c0$qj1UAN6fqa0V=+HSl*-IVCCdy5 z0dMeTiS7l^90ah4fsbbXw`s_f3u6?cz|wFZm!_9Yr4%961Mh&6gp;q3q0!D3vC?3%-N+R5(Q`%PWs(+^$50 zj1ECc^lJP!{Q%qo_~I!StJ%Bd4)X<8aqrtGok3}l(PQEifz5??Xx^L`SEweI)A`so z#SRbz9|c{FLuHsEsbk;`my)QFpOlEv0!0b))o8{p;+FLe29)cK=kpQRxwzhiG^9jR z(U7o9xS+bFm;neyJOrbrOvQ7lI0l;NHVley$MZ4WfgSXlwCZ9`xDJB3A55I=3TfUM zbBF+$auTUJIs1CEBo)y`yRDV#1}Sr66&IZ)(>B_5>VMT-d694^tMYuaKX9w15wio$ z)%>o^(6oMY^B?VbHU{dbuMI-8Qs=TTV+5%|-mFEb+hMAApn2h?{3k=XG_+0Q7vQKg z_Y=R1fJsA?b;C`n9tzQzV3Nt0t0$=GLv*#wq+Q=EKls7>j=48hQb-&VW>mG@pYk)G zdyfj(P`JO9@msojgKeb_%E_T40{^<*G1I#H_@j?rd;Rr0ckfz*gkM7%P_Exnq0-sA zZYf29x$e(ZdG<3u``P0s_ssnT4Zus+ftjKQ?O>lgfA)=k@{j-WFaPYxlh+1GD+dE7 z>WGJ#!VNMnO%v;Jah$BAR7+afoNSG*S3Z#@EX$ zm{v!(V|fypb#zd1_1vbSdSf-9(#VRQfBbww1HdRQ)GS{$ltxQ8^`;abxT}C>I;dfu zL>0a`V0m?_$iF;`xOex?1;yCspFAguo!vcs{Kms=vC}w0m{>AhAEk(WG*9!bvnzJn zazn*K5)2eknzf7Ernpa}s+*8<4;Q1s=p}QI3 z=}1@@6(IgaXAo+Z1-Jenf>n1Z-R`;=%g(xLd6)*Ha#tkg-ex>0cP$YCkbuD{vDoGw zK_KoJs}`gK&n6?#4P_T}*`Z|TMEx?Qb#yZhmr?e3$S- zh~k=wdnXsa__?2tVec$mb7+R&iwo>E`5h|?A3S>U@bMd0FE*>It=C7FKfZo+=e5uM z8}C5s;_r3guBSRXgNm8$QA*26xD0hgWqklg&W?E-ED6xoGFLkuPnmzrcrRTBXIsnV z4K>~mKKST^4?e#C@DX$dw{sOVbHe$uG!r+yNbIqbL9KGma3LDc!aeETXgn37uX>!W zoG8U>&q#ws*jA=$0gHMKGbgR2cmk%EZNDuGb-LBTl z7gv;wQ~K+%+6}Ftc2{(Bbe(~oz|-J=tAj`hC6G(L6x#2ON!Oi_^1nJ_HeBD?U~O0bmqq zh@n8wd{W<(@-)|jxxt=pjHcP2GrFch8J;$d_-k8@U!0nW%e|S7WV+hP(aK#$v$t(} zZ5$KJhAaKRyMzl%y+qs~?J)L4^aCS?)zY6!kQ1k7j2m(+b-kf`fa%$1D}u!|Nl&IH zZ@>M{v#VzxeEg$QgZ`~2kDkDu0SxZmyZ@=reSUOKGs{RraZGw-M$mZH%WFZ%ied8K zdh5-_@nXK1-FOTBQ<%-*26$IJ?w)88KJB11d|i84b$ zOKw7{nvoZyCc~ICk1ER_p5I$cm?WXo5AMQi2IkIVpz!PnbDgNO%2pOQv>U)hVmVmz zj>aaE9<}^9!!_oRVwabf;h^*@tLxCwIW504(d7M`A}&Jx5jGL%OWue!q(Li~bWN)gQ8AT&CXX5{GW}}h0JX>y2{j9pAk=~k?OyAMa zGTjzps{e>SH9M2UhohxYF`;y(ow?FlH(O~KA|)7lk5Zm6&=gr`P>kPy@aW-dkFU9y zgIfmq5cqE>S(<@Np_mV$Er=SLCWka_(z&p2gu?~1E8@g6mwmtg*Zrpg)=iVOW>WEOW;9gga&$I^Y zqT4gfb*g59skwWd(q5+vX{4lfobf1qfXYs)Eyg7zy2V2`{Hd4x#r4h8r_V@wHtSV} zMWcX$?3gcBS4)s;krR{mXbTAu!h(KerhsUs%yxq~x6xEEC6}=IB6@6Dzq%s^O`33k;J2Huq; z%zqM%ITdgpYJxWN=cC~sTM^9`X`3Ls3f{W0oP zr0z7Ggi@|+*QK9;7U@m*x1oB>tjgi?27^+D;dFSfPPw2oS+*!5&G1mirg5*HY2Gz% z#5a95zE8mVi$D8|%??>jMAr)P07_qIP0UT>oXmW?*^0-AQv%|W&OR>l8ci>sU*0*t zpq3*AB6klE7Iw&rl_UG+SYQseJxl~w;TqF)jd26P2qTTT&lJ|>-lNX@u$#^#s99fIsV zZjjKfi51vZ-#;*Z`sv3%W`rAX!;@MMV=Bf$I0=(sJS7F_dg^;=%v`dv8`SG`#)afk znt zg#k=xAh6C*H@;~ao5skHA?Zr+ei*_NnwuO{-gIfinfNBWD!fOK5NZ{`KudE~oJLcd z`LvuI3{V7!8r2Lf(XxDwZqMLGA{fns$RXQ^ zLKw89J9fFm3RF?nq%ztC&NO-vBPcWj(T8hx)n&nBphMr2W$g|~*SMS!MU!H*aJthc z{!YWHXmn21c|MbW-`eNi%7$G4!S1Zg zoA;ix&)#c&>l+p`b~t=ofMob*lmYEtmv<^UFaEs%Xvj)^Z?Rl(@`7`5+R{@{JaBVm z{)I#$mH!@af#&j-w9DOxxyzaw_6Pi)+t+Ua6|b+Z4l=AeG60p(4D5+K;|ktV!5SuC z4nMU9U7c?rgaaBeqX&5l#AYI3t_n=!WP}(=tR8l*<+j7W3T_}=8rBt8S~E07pqR&` z3ul6FE~P?R)jxB?d_|)v%SSvDA@*RUYM5k#C8?#DX<|}U%11A0^?OP*^DCGAM}P5` zfBDgmKSnzJhp)cm&GE>#wSD3N3RR7BP%{BZInA>%@XzWadYw?(e!bn2KAuPcJSPno zU8<~2S)m4&?vT3(MoUMLoQOKqIh-ewfgI)UhvS7cVs;!1KmbJIoEEyGnXUS9|v)gIavza|* zTVXHwl;c&=K)FDljHw1&7tj4sCmow_xi{#u)p3Ih;Xp3`85SsIE{6FU5&#$qwr)m^ zDsp$8O9q!Z8QP86ajVK3FQiyh!_v{?Ju%cJ%3!M!TxQ7=g0swsiY0~v6wjfl4-wle z@39S9i&rIgJ7PcQb9Hr8P;wv$jEt5@5Nq0UzDVgj9*_AR&hdVKfTEv9aM&C0zNaea z`Q{2^Mx03$psQ3z)=CbkJ6j)1tQS}Y`{W3VJ{H)*ZH7}It6sZn&U#j=$9=V>?teS8aHd6 zcmV30BYjU3$F@N&+HUrrzxN)z<%_G~)#b&_>(`uC(x@YEAl|-`$x?}0St$lG>hR4U zK@6otQFp23--!ysPgQG*U9>TeSx#g&byP?*-M0hJUHGRb``OS;*V!yBThdN?lc7}H z4l2U}W4K}`G`*iL=L_+jrSjj)9c6cr!~m#OCQ}KPC)7;S+c{_$t3jD&qBUVPvs2a_ zQ_%gcta2ifIZUHrY#WJ$*iG3;P-5Lrro89gU_kM~ylpTZ5&5B}EeL=EWqNOUCkle# zG*F(RDhMNC2+EdMV>xVhyRmF-j_lu5fqY11-U8}m9^naP%mZPC$TB!&FXR~os!(#w zW;-0g6DNXV!)6(o&yG%;G$oZ;@CyzG&?;okoV#ubs|%h|UWwzIQ3C7l=FJU{8GG4R zU;lw>2HO9Ops-xt6SSN2<&xd(0PL;I1d37^n4eL*MD=dpn3gEFmLSvzFurSL?7m7}&&4NL)0wYqIGW_47M5BWs zWM0Pi$QZZY7Hd+sFJlwpTvYE2zmTo#aKv>5l87>UgNV=Yv!DL?#b7jOw!ZoDi~sxo z`dd7OX@=2xF}SOBYlq+nEMiU@R!dU4Aq`_8g?f_`Oe!Qx2iOk z?w}n&nHC2lL}gZO=w?enZFfT@sBkmNfRdR$%PA?90$!UbYXXu#oyr{Ig>+3Umny`V zSlOXwDD5c_mg_%x%QT4oPu4fwcMx$hrLq2ybF{`F!o^sfaJ48001#-icr*RM2o@}W z0JWeZT+8aGjNIoO*zq)6DO$i6{_ejc+;)viV_Eape|5^sig$7Dvf&M zv$JM)6qpA>X84`(a#rp2qigo#DK>P0GWN_wz_MgLh2Z8U*3pzi9FvN9t+I~PW=Y#f ze60-BP4y?iIudP2En+yR{=vcNpg#g`7~b7<}C|6LRW4e=msf$}EU z0_5%-n&%+mPc_mVw#r0po6Z{L0U&D~^zR*(&Y!k-xj@gtOXLL8;OEbW1Ni+t^t@_%`S9s?fE8Gd# zI#m;9l~rh0=V;GzH6pA*SyzkWfcA3bJO?dkGAS9|N&ZKucu{`R{4pF~Z>F!`1e)A2 z5I7Po!DpZC)-3%pck_;XGxTXb95_*R0a!Fc_h1ZB#k0Mk5m5DGk;RsMi7@Gq$cs5* z2v`@yk(xp}6gt8D3R)+^H@*klM)gqzcnHiAfEEiRegz*^SQrRVUE4OtDj#0`4Ea?& z?yy(IL@MDEQEK5r49o;>AfqOEBJs@?A+NrYvS_^6b3H6ej&fk^gz-Aw&-X-1b3Myo z*A-zX^vl#zw4Gboo*>O`;!7&!QmEKV^DKvlE!Y{_fj#*uUTBzTYzL!0I*!m8y+_Jy z#ea}qyH~0I>mPhF`Ry0q{pxdg2hOU2cfU~_Zwhax%}3Sl==`O1Ay|~$A-TuvbQYCP zu{u&+q-NZD$h;y&(X*m6uoRX|(6#ckb35F5koIP1ZGOao!K-q|a%niBoYf_Bv*N_F zkdNr??afzTeZ~HP2dF9jVhbD#pzcz`+4EU#*q##$vJs4r>z&Z+ok}E11u0XwTg;GE zvNI*ws+*JERyGJq$P@(_x`b>E1EJ;0YDvV;^40$-$^BqnT^XyK-QB(*V~(Su5Oazf z$aPevz>`hLyZfUgsb%1uMlJSgN^;069WH9+AomwsJwTB;-nU{!v|IcROFObOtW>8H zFca-qtbqY5GLo~sMFpkC!{mLBF2+jfrHocmEkn^N{X0Y%1`7PGSlF^0_u!TC81vDn z>!6J=E1@dVv>D&f&HA3{)?QCS2YaVh3*5jnD|=cu%5s)9It2ysB6kh*WxV6D^ie@X z%EZwmHjrhF$77-NH`w}8X^2&6##4my`U^~2GUXWV1%fJ;g=7o!R@ORcH9F(*1tSSr z=Wlmql~BrLBU;_t#N(jSkMkyY)aJlZp6&K+_ISNMtm5T*Hun#4o#<2*p*nX>X?GC3 zO_r_HFO0R65(Hepr6W~HX+w!knuO|Q&O*UOmMf~>s%b-=)Oe~A1)a#91A5t|wmKz{ zvL+1Njm^rQvj$jzBlY&?W@}h4@1Q0;tiey>Da;nxL1cVXu8t@z%19Nr&{H-ckvaIL z`|2m8Ge9FBapdxlE1u*i7+efkN#HtY zlxLeIo2Yc*GIlI6>$0@2*g49|M})I+yWz2^ij|?JjTQ$NP;kuZ?Ej$kX^X>1?)J3^#! zFvk5d*s-Xdq7M5daR3dR5wJEAJmBw9M=D-eNc!Shg_wb5gOrj#^v14_zgEP2a zFjB{9&MTTZmO2*WAOXx{lf?WeQFGhQEj^No;DEi5<*Rj#)F=$uOZYL)Q+ck$G20!N z6ijTjobw1>T#hqSwoslV(s||h8c2Z0hQviy3Uv&#?5aP|P|qBFi5)9z*5xhtYi2n- z4);gAojm>5&)W2M_$KVuipun)K&QSDyc9(iBIFBc3WbMIC$u}=igKwmz~*Aot@Er3 z2-k!u9S_G`H`x0`<3L^INa2_AQW#guI6Lq#HdBGb*-N#P#8xUQzk&21{2FN<%~3O7 zU}Z-A#S@A%8=t-wZu8?N-tyN>+o7dU%y1hT%2bn#G?HyxC?6tUy|6-q+8xdinMZ^6EyZH>&pj`d@tZ-e?#} z!l5^8f}8Y%51%kYrt4=|hmLPLe~r7y@BZ<>eeqAfo!q{D^YWW7KmYvx_J-q(nGFTL z?v*8mn?aLjcSe9zS*1)#z@uV&Ebt}@oEM)?c3Y2j#JjdNIJuRZWuLZE9rup?#L{FR zqZ^gK9DT$V>u_d|iyHDighWX`pDwm5zzN*4nH&k#sT;&iZNLm3xY09j5^11wG5vI0 z<`m)`(V4OgX9Ox>R-T|(B}tRW0ahhpP-sI6_64ZQ!{PY;?%woIAlR%V28P3^RUv zv6pbNM7hXTN-hGWZpK(hZPBi{kU=^R4rMk}XJM46I4;41SEgyz2oXw^0T(A7iY~jl zTrZV3qE93U+I0hr)vHn)DJq2248(0Ljs->%40PyP>k-$Pt#pl}ONgaNhe)0_KPlsm ziur%Z5tiN$wCDvYgLG7mYXHd^Li% z(5M}@tLe?{*I$1555N57cQ0SR{N`Jr(004$2B5I{4WhNhW)W^tsW2&(Beyufk&A3* zrJA8rrjEE=^?d&npI4Ylm^iMKSdq%sm8j;%6f$`E^3?(*)Os<;;fXO)e!Lism}snK zE76TDX~maKVJwdyIAe?T(cC3MC3Q=I%DVxbmW^f@QFX1AfPl1vsdV|MfC~|2WbCb- zJ2P|MM0iI@bQ9hffQ@g5cVND(8Zt-6vSm#1+c|2fOc-AoqacyVOtl8$i>wqBkQ0=n zi_#KeB;pM!k?nG2A#<^Jlcukmtv|Gf`q^ZBcn zui+U%mb04E6IN%bn-;y4xp)!F7u6V08fBY*M)KuS+a(~NRC5W=2H6x@&W4YA&YoC< zfAim~7!42A)bGFgJ=CquenHLCB)JCdhdv{M1+r`L+^V->6MF#_3fmehvDfY>QyFOv z!h}QyHEj*?Pk7+V8###{ z%Pq-DT{4dF(O4rDB1fqCDYIm*p)qE@P4@sxFoV5lIidKHU^uF!m_-vwJ3O z?|=9{mwJvQng&{WbwylLCa`INVc{ft-$-$ednf{${p`FiAUZ4!GK48RC&bq3R7J zmD2EW7iY2` z{FJV*FTeQWcel51n2c|>On5_xG>awGx2}Ax-I?I7ekUL?E7h?srAX;tKf8GHqTU<* z|fBtH1oKM^B&E;R~8yLau>Z=yt)f2+Nucsb2}tn`!Pyq6XR*Y|-L;w;=Q}XwwfjCInF6e{q4icRtDu#ix+bXGxLunt&08^QZ*b>7QTl2WkwZlfv zO+w4=HpSwZey&yj;@fW|CZnuGfxccN^rT2jWE>H%wrgb^BHx3mOB>Y0jD(nNPDAuS zQ1t|1QIA2_ynFL{yTp8Dok;GtQ`kB^c)Ejm03(hU=hu^m&8!arN6;u|m29$|LRVYh zandU9!u@2w;{`F>1-4AWxYDEuA?HNk50Xp_%t+}#0}TjAyUugEi}7nYS`DgnF>#~B zAOK(%ByB-5nPDuU9Kktfa7!)XTnyr-Fp`j1QeWvJ^hTTYB6pF?hR`lUvk$Ll0s2LC zg-;Im+-e$SK8&u$fHeS5XvFY*{N#r}`O}~M3z)^4z959@L1-Pkdi8Rz82iKRJ@Mdk zk>|j@528S0YI(cZzgg}kn|Oh*`ysts@4tR?_v_DpU+WEj@jv~Ct7p%-4LJOUvK=H7 zw;G(Hn@Y|BMG+akS?+6ubPjOx{lZ?WP#%6=|bYUjS>9B7%NRW@n+# z;+E2rWtdu6aiqr5Xe>760pjyx1#Tmmh^p|Gc^o}6u}e ziBTk7H;ZGoT+0?1u>F`aDMIkQ!zq;zZ_OOT%;dQxn@KdO*w|8yTO-G0Rt$We{E(HT zaob{wpKttzzs`49cQ3Q;{bA~nURK9$zJ}ZCWIx*`5a-IA)brhpr3rIsFGvecM~cV} z6Y9>Wz1S?U2p&JWoGxeBNOFMSMQUtX@n$Wh5*LUaZ3l?GRB_A*AVVGG6-eNR2@^GI z3*^ReI9Sl3@GbFZX})}DF>)4WLoL}Dg(Fhsm)+%x;#^yi6l#aZ&mIjiI1QuQyE!-U zvw!*XpMLh|<4f>}08ygYI(d#Kn|P_!3d*%EWCg%pD5S@jfWg1|#V>yTZ~nXYe*ELk z)kV~9GH<~7v^5&O|D%up^v{3xvw!^;khFPJ=$4iLCYg(z=<=jkfAWtKU=sQMdGmCk zwuNVo+$1%)56>ZE&KB>%4(h%0$UQAkHUc(2Xn@3jf!@1JaxN^)Km|Mqcdt2i3Vf@Q zsP$Y$LF<{wbs+XNiaAyTCajnqQlhZaWCm^0bI^bg?#I>ejCkf*aF$tB^IqVXRt`#G z%Rvk@98BTwth%Kd{5rXnP<^GcuSXz25m0Y6Io)6u5(bXEm4cFiJL*j~Kbm)q(C|y6 zcdv+uD2^P1u{=?<7u^C~TffgH4+@S5^yu%sDv6Fe;;H<)zewggHtOETdX&mZ|jKHXGN| zEz8^~{^C?+k$kLDuA7;{93yw<1j;G)AyKnnjyD=@Tl5PAFh0D3p6Zo$tp%RyTZ}WC zxRfPyPkwp1yrbn%Y!Jn(U%GzO8g{Qn!$-JTSIPpnd0}!;8kURjlYjYBmicV*mci6` zIG}Cdtz2GR!|dX*AC!hKK7GcUsfgaICd1BX_Ddh4EowtV;p~!N3m7>t)WPA6dafb` zoXaXHsLEzTSNucnIwAXtTO5Txq+GJ7)nbXes8N@N>vma9#&NBNtQl4QhkyU?dGjw{ zeXVCepl=DH!cK-9dvREdTr=M+>Y{Unbk0CaBIC#>N9t+7eD15S-M{KE)NTvZ433ll zLGWB?00%i7rWUS+R#+~RRt}@vgl))4g}XufS*1hsF~v`@S{ii6Po6(TT29-&*}|r) zFs;Vt5#WcHWRL+70JmO9ndm+v%f91Ggl-KZlCc?BD92>2+MX{s3*+I{1wR7ClZ$o8 z@uu-BEoBg(fMWq@3F7=6@f<3Zfyo*CNOq;k+c)g%lmi-=F(QJ#6v`#cWbcg1{Kfs0 z4o8~4`Ltg4*IQh0A#ALbibqdY$Ad$q1J=vP?fn-Y zk^e$@-EH?=XdiqFrZ?=L;Y%PaZ*6gVMBIapF(lXRuY!)sr`;;SR&H z?OMuzaXd>9YazekXbjvV>x?ikf7edQs2oNHE7BRAp{^Tmy64b^g}Zg^DDDWb9B z@$nv(q5QeMpHs}-Q8>SS%{joRP_T}noLq;aq}*Fk0g>>=GVi6-W2v!lE1JY34i=A2 z$h@Bu5Ywt7RjXo`mF?wb4;{x77fgA~zSSg*WEC@7q2FIIfiaf0{GRBfK^uqUJWca-o4GI7L{K0nr5s*Jy%tsw3y-6E zuQ%myPlzeKSS6AvnkRTfnw@wn48-@1!VIUcq1pO!1LY4$OX(72@i19`@{=kOTs`g% zze9(ck*>M>{JPK*!KF8Ba02hlz#lYS*7xAHF}tb7Wo^mGS4r5QoW=k`0;EJ! zQP!~FiJgs%LS8E6X15bqIs#RR<>oWqGVXQy1ef9T7XlD*%i@kuS~W9vgC}pST@w^T2DOkoQppG z3;2_<&1C9`iKMG(VI8AAkd0DFOvB(7rEgP%tWY%=mkglJc#STd%o&h~cxx#UcJv3u zKI&)Z9h7bI@^bv@)hoV@g*jiYT#^~vCWk-!H@h~+6IL^H)%Wp?J0znF=1w%im|~Lu z$KhKN(}fiVP6Brh!kpxdkZy3oT5hrqM0)CeC@Jz>XZvBEMvjcc?m#o-6Z;3Yt5%{` z0T)}5*?BNZgw+6NZ&d0qp2$M9^~{1dIz!Q&dyR5+4f>t#{3_PQnA39*?xc&1fv$wV zLY)SLV2?*ep@6qEo3HLUWMetp?x|et>H(*gu&hB@6qqGAZooV;Y-d1<<{c}&0IYo4 zK?c7yF^%9|EW}Pob(FQ+U9rD}k;6U{XhqS;Td^p3!1t1jX*Ew57o5fA`T@*{9PZCq zF1(boxu#DUNIS_8%;HzdvJg*tcogbKL`^@KGF_@Z@tX+~78xFP$lR!3kje+65lv^U z-uvW7fA-|+2{I^Z)>>#TVyGfbwE&Y=E-9i!7mQ=k4kD4^X>nz=0}CCl0x(fnE6L8I zK@mk0EN9uUta%}rq)%N1oJUCj+qvwx^a>{~ENFD3tB$ijPERM9yir43_37Gtb8Pbr zc@~^%bEUvu%93mhaAIBRf4AG)+uK*KzGE6KxgXNP-QJ|@svQFf@IgIGX5ghJ+Hprb zb9XSt4D+O|*gWw1wg(v%$wxUtNJx3QDBdY7FEF zuvm!}-fEyOW#gdwV%R#&XdwZZ8Jn3U9W*zd4(;uwX+|XUY^Yk68_iWwu>tRM>lwa_hCCXn4K+C)+w6Ja;)w!8cawl*c+9tkF8 zZI(soa6AcmJ&}P$O;n)jU^r-6UIM4;?0(8tP{y^8*Wx%xsR-NH4an93ZWF+V!Czs#sHvTE^7@2W#T4iCdxVT*n@bu*nbm6 zGqw<0vts(y9#@Hfl~bwjKe>90W>OJe2o@-QT-LAXFVY%-@k_AC&~2+Nu!VfB+(cKu zvEdf6@3|a#^+tfr#fbas}398~#TsCuH)EsZu_>*K!CR*l~r1m#-LUoHuqaf8~EhWWIAE5Dc3m!jxf=PY2-2^L)B1`h$L}3Sy54s z^CXg@m!(ey38(3@J8GzMzGSDT6@%>zp&^Kstu+LI5^zAxm7Ld{DxErGdNh5`pNj55owA5!cE_Th=vP6ZS92hg<|K_;^rZesG1^7clfkxGX1S ztpQO_xH$EyOnv|z*pzsjpfE6X;`xLG!w$ETUo(4jLrTodH!6uT$z^nm%Sa{)6HvH&S$WbeITMU(S?9fU%h@wN%#20GYSg+hNGB>&|F}CteJc6fzs0-KtQw% za>~0<#u@v#-z!z^k8cU(;;0PpLtKbRA6$8K5f$A@i2ktT}fs8}877ABA(yQ3! z^|q(b0O6WzBvf`V09in$zbPmAkYx!Aw<1MsR=S%MOpJXse@zg%^FCR;{L0Q&wqMG3Vz+1id9`2WF8bWrOjK zBHtQV9uLKW1;TJOWfAsg9w7oa751e`B)c^bAk?9{0)fS0buhc!3gm7O6g!_m^sdqI zPV3dN@SNhx?X_ClvU2uMYudo3VwZJa6cG(|8y1ki`Sq`r4t3bQ{Pt@m884nauE=>- zEhlRbza&-$A01;vY%3Fz%wW4~NE%?!%5aVwkIaDGp1z$eW;DVCyhfc+7M-3P3d(vW zbTaM=WW_V(;9$1~AptD{5ukMdORV=A%N_X`G9VQlqKqGWi?f(D!6^wlqB}qe!IY8& zZ#rI4dx>0q7VDTw}h7!JL#Qz?3Ej#dDKIIz8qnD&4rY)WVr)>)NeOxSA)wZSC6KX8Qf9SF}Ni2>v!|zMv<%$>;l|` zfi6;^RH10jp3r9Cx_-CArlc)nOQAp5Cu{lyNq6K+lvs@-3Mm*@#3-_qF9_P=Coz@_ zaTTtW$Z@0h1klCM4|-dpLYN{z6AO*OFn+Ju*GD@02IZTH?&aYAX-bSMdu)YNdOzD zRTL*&;6!4nP)twVC8h5im1GET38NIg0Nq|K#D|SX5Y-j8^T;T7_Le#jG z0^{fhRHBFlK!sE(Ld)h1Bf*!Jb-KJ9KiVvnaP#Q;F?f4VX1&l|;cjYnDp9i3Oo}j) zP2j1WmZgr?z4DH%UX&I*F?q4-d~vz4AcU_{XPd-|8{R63UyK5nL$PzRIr1yuE4!}T zncY0Z*xl48uwv8IlxD$@`KK(f(?4iCKi8dJ5er)UEq#rauVl}%48h!Cy`2B(2OnOJ z`*^S5mhs_7ACSbwWdzM&4i_tLxVk}^NtU1jj78dn^uC;vP9)i&Sl)8NDdBKI_!hyb zqf3&59-*tbo8H<^nk&yYEcKe*tMQmgKc^YJEoucUA*PF4kUJ_aHcjsC5`rJ!tVdNI z40sZQgOD{w2|=yD!#_Uqh_q8?s3040&C3mPHFL*FkmqR?xg5$r9KUvK_^nDLb~W?r zT7_Ri1H!ZgMkQMsyEV2?TnD66R` zR1&toxM-M(HpMPwV>pZ>G;w30c#-6VfIOgbX63CkR$Npd-CY{<0sGG`{4clu%~=JPT1I&Oe06U zmnxuKjk*|dHfb}0ieEBtC$iXLA(Xx{1QqZn7PVv-h?MGDkOZHVEs23KncNmio?<+u zw+{8IOsFY3z3;-l&URt~p1dL>&z)#qjDmSKyXW}1zn`e8MvpdMk)*So?-DjV1lnx3JC$nO+RZ*;9pGlom=Z<- zd=)D*X99L)0r9B-LE5H?QDZOs=j=I?IbO0HUN(DqXk0PR=h@qzVTiFwd|v1R>` z!UP1~htSy>K~doZU}slM1;<~bC2qJc7?qoa?FL&br7xYiW$g((Wr0GF53jB-qz56uZcbAR~MJpm)AI^klMyrg$Im# zESm@>X$PR6Oy-d*Clq!LO4^N zSSlXj*^$0XMPm!7*g@SU>HgI%!N@qvrx9rSM+;Qtsa2g>!6FS#jlMlT#-8En%FW(^ ziiADA+iu_EqI>g(wyt96CKw%lrSkIAmXBc&cI^R;NJUI$3PBykoF8?ffa(p9Jaj@n zGi%17#GCJF{JA|%jC2AspE7{Y_|9CDm~8juYIaWYeKsL24%UR7^Da)WLGOqgs<^I_55bKGY+Ps-!rWSwuCWUU%yS#tLPn3uL%%25CSm z#;?O}g=r`6u)#XUe3K2F1nvf74$?G5X%$Q`<08^#umeF&d?)VqFq zx!%laJ=k+X$7llA0UYKmxfn#S9QG>$f4dMrarj?A8Cxa0MU<|odFpJl7%FU*$L(r9 zo$OX*-$GTc%QDl;csHH8@**Xw2#lCOA0ra0{4!3FTN0}!mV6_oYPsf_lZ9z7BNHTp zr!d`Ksd&ya3;LCM5Effv_={_X)B~yy7YMDJj3? z^nvpuaf(J;p%!>&HHk-MQ+sszbg|ssO|Zy9pTMvyWc0!_-$J2AaC-}8By_kkL<+3k zVSg>-ZKdBhmiKx6xXI_oFm865dmX_HbxKgb@Ur4zkE)6{ftpU@0%$OHpg}X*M+$7L z4f=R}uLx}Q6V@6orwP!JaF`j2@#>(yIW8f2mR0LZ?!wA0Wu@gVJ%p-;n1?{J-x2*H z*8~_@bu1lLJJR4*jb3HCnNm14iK?N96|T7p@Y8QXVeyj1ViZ<-T$uG9KUK&t>ueQN zV&F@tgXH#g@L;OJAts2s4+WP?gmx4+0kY0H=W8-`%nsge0q>+%<}kTvRA=#>CSuB* z*s*(DjQYHkCsil)DReoSF_{8MIOKs2b;7C_;{;GvyD8UpKn(@aGA+%HvUBkyq+KoW ze!+R3Lno8F5C*-n?g#@5fn#YS@cfFi`$ zSZ#@0G^_>-k+1m1ZKE}&b+x36-1PF2A<6jA`fDS=C+96QGgi_0u$D!eUvTDgGZS!K z(HTN=Z^`}h`KO#v>D*K~3kD0~(=N#a2D6D8``cG9872XaC=^)`tm-b#`mk9uMpfYE z6YSreeusmTk1(OP6{|>dZT3{I4o?B2AcJp12g8CiuO-n8)}?z3A#n&&m?ujqHjmzTGb2|W-@E%sJECX`E=dg9b4vvUdd zSyS8qKm*A7-QJKcong+C$5(raS8)nY@r+^rnC=!_)7>_9@qB2c8AhtG(F8Y=S`ph3 zWJT2yPHB`i;Sa_a2$JbUBxV(?9T1!eV`_ign)|^~P?RPKxJIGs=jp9uAB(7zX{?lM z!ifOaYYfzJHlk7*3`Q51SHLYaYf6xee9Mq`%}Rb=tZuAh+gRXMSjocL-MCVuX=XJv z8OBaxYbo0w{S^g)*~amMl9|Q_H^8;kZcex7gW3Y1jGLU9@qXa!j?NpW;q;#Kd33Ty z_r}f(R4ZF^H4DYj7?}5_ABP5tu@O@Pgr>KXTR>)WddftIDT~sX+8f;{Bx?t(BY$A> z393o}6NGGudpzgzq40AHNt1sLWg)y5rm0}5wDn4xHFqk6Ys_Vpds$PdbibO>G6QcQ zex+RJ5t79QeSMxZdrd-DV5E4IA9)NQ#q#NOda`SjAtXz~!ezh3PU&mogd9o|ca? z4pB-w{F5h7n#x{g9my;lxOjP3Q9af>vY6zWf=BTA2~(3*6H%d7D=DKfQ=k#(2pl;I zSRQAlABa_lrgMM5dCWVSN8o0w%7B5tGQM?{0+SD8>()YyIn~v-S)w^V=%Uahc$kc>rdo3yJ_{UU2V^PNf6ndnr|l+U8%VzFAT zPGnWAg)l*c+bgb*f1|0tzrSOR>H%$tO2q0-stNTSkh>6;NQ7D{CNO25tBcD^a-P73 zSb1eh^HS+ZInpA%`I1pJ%bKLg1yRbxz>^;dl^ua_0RD~A5oZihCFEX#`2y@x;XCS? zByw!Fn~bSa1M{E^L!E@v*@P9?X(7#|c4BVNUcpu*f-*IJRL4LVNi7!G`~8yVpLkN> zTehlkuQ$J+0sYc%=B7D@>jNmkU6^lH?0&EzF@(yk6fMgeH6dYK6uwdg_OOy;4soa6 z1ct=`o)xV)2&iGx$!fEm&(@6TRSDm}zLens zfMywqU4r30v_&31w0Wrob!OI`@PT+Vd1^a;#U_@dJ|e=_JRwz0Q5%fM3`UWyn5dyWcj{Zp!Nz;QK4!9yQzKT6rg4< z1hk^FTh`zru}RmY?`{p&OI=0D+%g4*bj)s!C z8~>$D0C|A2t;$XLT{5#MDfS+aV=rpy^<^KvAn4?_EKV3IVU48cLDUEF~U*g@8P)(QSLnXS@j|Pk=mV9duMi9 zpv6w1ZD{{z{A0C zLbSW7{Gk*vxLt2cVn(6`AR$2UcF=6NY>r69Nd=z9&=16lhIu&X%SpzPJ@&HY$W|*% zc*=V{mxOpQ#q0s^Q`Ny`S8!3Q#Z8HKF`q^5|53pHhdkX&$m*=w3rK6vL$Td2EkmRP z3dDPqHMUp}tQgCV&t)RQO;;9_E*V%rL?=Myz2LxFEEW06;3k59xUsZSX8l+$Ac3N- zSEfvx=_1=*MRA>M_-1fUYM$r{=tAk89zTAnDL1p(5{t)V zY&7VPSYu3->x`chXOTpiDw?CKAS+g7YMn*mv^)4aJM}BYe9}A+T{iCQcQh{{$68{isaUDXdy@x-D$2}(pgPa(4@FmKAwzEAJ4%SrlY{f`4UKf-6e5n0 z=az3+q{eF6#HJ=d!rB>C%T{b4PJi_1@rNJ(5VVaQG!Ey~5cl^pZl~iv>lBk)oxhml z6vyV5NQxtcWQonm%u!N%Hk67+SSXWiP6)t4_XN_FW6u^#sK?L@8dq88Xqg0@gFS*i zk*wH-@W^l<4|fg0#gqX6~OD@8$OW0}>{04~e`V|InvbpCgL_xE4C{FUrN&2AwuPHLtoUr|Uog85~@$rJ*< zNCfm%IcecW6f84vX8RUCDJ}O^~an95Nu6D&6`q+b}RTS18^ca}d09@< zv<9U;Gk1QN%~Iuw$X(`OTN9VbP)rSuXB-13|gvP_FMD9u~8 zDmk9;7yqY}G9GxJXpHD&JEEw|=cHKGD<=@5|-R4ZmGiYjO$$E~_ql*u(yPIpJE zIC=1VQ8Q~tbGNc$0A@xkD^1xwmyBx@pI4;XEJp)%SiKGF3qZbr8tf7B3-b{()d^g+ z;3@&XD4H{?U5j#L&>tXc`R1FK|M~BKc{}@>o6Rn3?TDzTB@@{(&NfjrE-{McX8f@O ze-P(U*JLp0^S_js&gIlq!?LT!^-`>+n2X87uRXQIa^wRhV5RWZ=Z{!`ZI54k@pW(1E7yX_Y$C(-TAvxF*`nZYh!|O$`W!Btya-9X!M?45 z5IK;lTg-MlO392y$0Eg#+I}x`OiBp!11vC^`JbeF0R~KZ8F}zybZw?#1 z=?Z5gFS)~_O5sqP=Z(sg>ceuDd>NPo3I?T2P8FI(e_;CbN@DdrYYJ!3wxyZKT-&^8 zi}u_rv_5Qea(NZKO$RQ8qm<3A&nHy0Tylt#R*%pf^d!nUErkX3(=A7rQR+DJ4o2m% z<93UTCuMNteudf&qlP$SG(3Z*w$@xCXzqJw1i)|u-`c0(q@*iYCcfj@dT#0fyG&Ct zI(Ry6PRH34)3_xG1@z!jAevO4y^R76+)ys>iVCKh*i)Ah$tl&6z>;eE{- z6|7pXQVmlo&|kFUj7)$sbTP{{jPl_y@ofrBS}f_gSXf2rXo>Cep>rY+(b(xgS{IX2mbebH2YoGbc&*i~r;nd!lcX-~*{o zOyj06|D&+10o1(l%0&nt=+@*4pL3UiXTs*;?>Gq?A1wA zILLn#{RzE_DR#9b$C`ZWAnJ6RFv0=55cGM{jw>EbP|sa2XBx)iDrr6t$kZq?gI-Y5{ue74}y z$z~d<59u@@4_gM7h}he)xC49nt+OdzUbes!xVz7vk#N-k{DR&|rNKwSfSP}xJ%k9S z;GdF0yE)=P|BZW;DgOnePbPg7`+2P_+9DJ?O9;+6%((q^)2bT)>7T}o+PqA65uVo# z3(qrclLpYua{6 zq9Zo~5I?%)jKFi_gF2|*3FN&K`0D2Io?{7m`2*@xu|kzIrUAXI)-*R#B3@?tOg$0M zlPEJR{6x%xK(0(1@n4!UJNJsCAW+lK_mN@cp`^QN%#sMHp#KX7A_>O^w&!jcQ3{#sYv(*lrCEtTV> zI$(vxMB$^yeXIw#nruH$o?N>aQ_4ac;Y;<$*2c`EDd*FQ7)Pe#;ZA_DB<9b^_wzUs zgg_-P5 zl2#fq)kZpo^5-Y)*7B7-du3n6ekmG_MvtzaKY#j+QvTJ;m+VtKIWWGr@)Tj|C8obZ z;#1+$a8an#d4;ChBdEK$`9h?KWz8iyOG*CdY`}0glLXFG+4iFvw%1$>!f@do<#rM% zbVd*xgM6C^u%3NDMymgvf*6UxI9LwW-W5?iQ?%nCEy|BmJmSjJ$plOQ1rxb!1ox6B zn7C#6J205o%C`YK>0YP1S28iIcCm|)p~K0BVX`EUz&u|MOAO?C><7!;4hj*^>KfAn z)}vphr{|4nNR`eA9b(~FG(ez7al1o+rRSDSIALkk1r`}xEeG+@^FHMWg(Melc0U8I zySjYz_U(lA!1+N^ncm{9S*Oy$H|gY5iA13lY)n!Y%wl90Z;aSj`ozfzqfIw zIfD2m5S1Av+i$-nY@J*Mx%4#Z9Nk(;HF-A!r$O{*d*m2pBqRGR zf;V`z}LiuKy@{21qrE#yW$eKaoQxDDM|-7MCiG_ChUAT&*1qi^))6EGO}ob)tvdIZFie!opDO0rCD!C9zK3Gyx}No#T*1mXxE5nTX(E z99^Ztd6J=V>9^bEH{X7ZV-Te<-ggJx3Rn>svepuX8l$oH_DT@!*%&RG&1bMcT?5P6 z4NS;mR@FeDiqru75SvG6!JFN>)$Stc#!@%l9Pk{~DlAo#?E!{dw>yAamTlu!sR2l& z3NKh%B+Ev^?)@tF4)9JI)h1II_@`Wfs%#gao3uHkxLNI>P7n_sGRxhX`wPdf#X2q3 z2_)gkfmkTh@SM0KGH8;wclZ5PdwcX7or`Lz3GKfwU-243q4|bXK5QSGXqvI$;sYY2 z<=9}jPY4Ht9k?7#LULiCR%$z0ydP!MUukX#UtW=R?m>J9UJP457^dcbqlYWazkB`m zt1rLetj*2IRCZkD5>KrZclfC;r_EB^;K|5lsR?D4=)>s9)_Z>7f~7frH0TZLB+Cfj zj0`L?oU_=p6mhcGgSK-6o-=*PVVMU*j`+@k&6&AntR6^|A7BKp*ykcQTL&+}ep1S< zxEPZP#f;9KyX$z|YO`v!FmF;H7F#g9dbRLGB`M0}li!%b+Vk_ydfHAE8{)R+z$iqy^Z$}XedtPAyBI^rCrVEz8vgJAAa=F>o>18O@Y^@ z!1yg7h*Z{PoCkYQNboQ+S9mlv0mwb#UM>mq2B2a8yEKiuV3waUV7DY0~+| z(R|q*<3XzE9~mXnR1g8Y;v6`{cK!C`*1*Kh0@$Bju!QyxUkoDOJ z8*Lc;N)bxU7|W6Yj?$%Y(>W<5;gl+XPKa(4L}kk^Xk$m4w1&ZXK(8?pwk5WjaCKwh zO9cjRKMW}5_^6zf&Un$ulVsCi`7&~;wMoIR?L%JKH%U(`#H? z{1-lfpP6JO_lgbnG_ud#zN!bt+=6UKfYy|fju(!At*hg&zj^x|p;wdd;1-w5NJC4s zq*-umb(ydsGR(}(z+5e?LS8fu=8MGIfq)R>Og-A?G0FvUp=(vL7FY@U zB@ya4S@zlky7-#-`AE8(nRr=SOMRF5ek_bVurf5V_>kNT6x8?1{oZsdC%3ua!2w) zB%aTmA=rbjpZ-rI1QwT#NSWEhc<%o8cfU^jxZ7*9>SSNQ{w_ zmm-ma*c~&H52Cuf<$)XDBJP8egdl<|u0V*WAi;pLhLS1}D7r#m15`=PhGK<%f%}#! zc*tYcpm!xxK~2$2kW{(jv$XXN=`8j^ zrvNb*r_Z0Uw~=st-oJm*0JRdw8Y&jOwUv`Am&wY0T0*+whGR~d?IAF)^<+(?d^ z&2G=M6OsyG$?a^y(+m6_ns*~oMOhp$ab*l$6Jgh~&?y>7$c+_j3ChYtUe(z}FHfDJ!)*>FIZ6V9Kx%(jD`x%Yp(KyCWq zD|0cBi7wkG+E`*AX#!rodOexl(KPV*3(t%dhzgRwKdk!wq0xD=o4Y&EB^nW%!V6>O z9T6vFsafnvvUykG?#qBpiD6<)#p8={syg0R`j)DfK((xwwv>BRQs`i^2{lIaxMj+n zQ81dWk9pG92-QSR$$uGl@%HhL&P))+)$VlquV24q&g^23*}~-fU9B1iSLw?@X)IIO zhS9@qaVo8f-YOKLDwwRKN0VZcnTTA3{-EJt+l+ZD3PoXr|< z=e@it5L?%!x+#~){V^E1=$GYGDAz?M??7LQ5ekDZJdp6c<1IN#vhvu1OkdStsPWqVrko{6E}@d{$pLle#lo)cvy| zo#}Z`b!)K@94qNZKWw}@?+h57anJ{ccy4UF^xX8gZ+mEt_kg9E|50E6PP_8p&jY21 zuhhms<9nV&T4unKpMCZh|M74BmvQfkW-5`#0D@3>oPft$fIrN@Xbj6121rK5n}6iMBM%d6fBf+c~_dCr5U@(k9|{)k-K zBtL*~f*MdBciMfc;v}LChtGQ&MR>}T=DC7@&+n60AYRuR7i$Z#JX)O*f`?@H@7!qA z_z)KZUc^H<7z{5)7Zs%+Sy!YUg6c47i{#4HYiLo52BhlHFHS_aM_N@X>k}9H=AT!I z>yfo?42p8Akk@g>FRsm(n1!2BR$OUeVWL65L^cLWd*8?2eY4neSnIx#_|0_{) zg=lal_UC%LrymI)yln8UU@i7(t0;utQ-k?1Mu)@8)oSt8m;Vgoo{vp^1e28NJX54m zaB=x45#yGkk!95A;dconB%(f^)G|xs<5{>5`&LY!T@|9ouESihi5i&6C*RL5i}I6A zW7z9~Fia;CFlhe-@yD+mF zB4sR;r>t94!qO09p8+I-W3qrW%|YvDxV|ywouGV%1se9Bb{x)PX9Qhgf{@C_hH{-Y zqmY^^R0%0@h=%mUBz9l1Bv#-ezS(`SLBFq3*TSDg#$K&Bvo9_#`mH|SQ`C8dq*rr% zGwD)O;@HgI2`^QivEkcmOk+#X$a;q{244p-ug>cy#EVrB5I|?nuH_smIn=Y^>Kb`dt_tZ4=3O>K6tx%a@ zz4$!gJQTnC=iksM7&F!tM zvNCf{4J3Rry;;nbc6U3an+V*fO>u!ojR`mEH=J4lBP14bF^4n34Af4LS|0a>EPCoZ zM6=~RjVNGCEJ~uB)nuk4bDk}YNL2>rty~dT341NH*f{ z1k7~#!H6k?WtHlI07h04rtdihr??r`#ju`;uz(fHlLQ&U0lrcP3?FrlP z5y|y(x*YX~@4a|lmx)qa{YqXK&gAl13azgfgNZG*q=fmh@1GCiQweC_mYxn@3)&Jl z*}Qil$SPyl3>N}d*Sg@dVki(DFX)rR(xb{WHONsp+uO}#It60H zD&pDmCrT5=>s~k%rok{GSBeTlu5N<{i$v6AhTsRmiQf~-FgiA!wr)KS1|NzQ%rXT% zFpqVgolnGuz;j$2jtCAvI6`8RD>(0HJvV!#0;>5MWiE27W3a<37B6Q*xB_ZrxO2zD z8eB?90jQM+p=salRy5i;X5picMuKUi9Z@_ti#3*InH-Zj^tt%_!)B!jQ7sXiMa!}b zU}1{Kq+>MJj^-r@P^n-9HIGOuAWcm-tsZi+$G* z9c8$yh3MNL)YXn|=_u8d@db}p+3^e{*cGgxmrEi~^FMkSTh~+-W|8^Je)@wfwFaIM))cbP?_=i+|5ir1ijaabAA+ z5+TlJH^Zxjvl3E6wo7n*qVu2xj12l^3*0Q09I`usQ7Io*o^f(X6?KOH$E(j47=9=| zATx~>Nl38B@0mrBx`dmCB0*b5?G>?TvspfRbO|{KgxvDb?x7Fx_^ekuv3crMN?K(H zC&_BHlWN@bTUD{|c1~5us;DcZS-mN%6EI5`gp6R|XSHYErcgORN`@n;w*jS4eUxkF> zqfjR--t}Q@NWXXS`R{&@x5ply?siUX!bUzLhIh`aALA~hv&h!O8dRnXOM%3u_F}b^m0@yh8wb+eJ2Ck~Z2^k8R|v5buqG7q zA74MQj-JjKlmFUcfo7i$WMO6*Wtxg5fg}uOt2J4AA5(5UtJ5N5B13s$lXOAHxS)Ul zil*0$)-t_4)R=9=W6}}|5j)yL8zGRzvsI z=~URNajy-GF6lN13DdY$P)kml9-Vp|2V}>LFNSxB8&w&Q#C1NZm|6O-&ba%&AJ`dmB>J+n6a{?~Dx^-4K;GMKdxlV+l%6Mqz_;3Vza%Ox7GQIO2JCjG> zbzRba^4;S>@6SH{DZ?`ODRmLy%<W4(c$d~tJgOcz1fRAss1mP^Rnsi&y@ zbV!P0x)n^oCLe&5!?+iE13kZNu0D}DcHK|r;Fvv4eM|7Q#bSdz!XvlCsJQgWC!dVR zIJ=i_?rvoe$-HpYHw&-VFg*=zP;laB57P6M9}c$kP67U z6*ak9UM~sIhERI(;fs$u%|5b~T&f@u!dyJfDa}aj1>1wGwjcTlM3X7}=!aHA;$JqkG!ANtF$KkxQis=x%QSFQ%68O<3z z)4T(3#Kw!|`ip$T+)x=H%)U3wm>JelK(kGNOp|)4=m*8os+V8RmaMAHdI`QrWfgCZ zAfF6N$~xj!s2f=E+_Ov?SJ_ptJ`gZ*7bsiVkG49vGcbARf=a>P~?vVQuDy-1A+fa~+G-Z^#PU1+!?&}J^ zd7uC`{j2!}TgBD1YE`1$rbcmvT*gxfu@!1MWWi zk?vVm;BK;tm@jT_-tuvJ{VroLG8E+1VrJ)?`|D9fb)F*zKb<7deqrzu@H8;w%TT! zxse02!X)<{hCZQY zQ3Py6?oiu(3b{V_yWf)&W~=$;&6~;ntu2!BPNGq&UXjGPO|ar&u+Rl^xIlI*CXyE`16SI{bXC$@L!fSlaTm|&=#El2m$yIf32bqE~T;l;;e zl{Bh(R4rL*XNoEm2$$r0Gphgu)<(rSJRV)rcV^~*R8pWU7eXGy)I*${&06dp4-_&vmfV)13+bt|WM9kXxWkr;*jW zIn&8-)YIW$(LRbZRraO{0NXcr_b*?++-_&9?QJCYmJl@)1a8>n^g8{~C>G5^-rF#R zw@WCPW#wD}fs$4ympx{f;cC4Erjh+NX>m=tHYn)Qg7cV4T7a00&8T3MmfaxACx^7R zUd>sRfl@*I@x?%9qT(vxJg@@fqCeX#iL?W`JXk1qAa%SDI~KhIyd{K|y>UnpCGv7+ zvmzQD*ac8B;CV2ekW~@Rw4V3&?)LKHimU$i?Tx5|n$LsVdyB;_r$5@K4g}VX$S6Ei zta0jpNjGFQjll$E=@GDGfi6Xg!P3?O z@1f0e*6p*tP_uM9efeqtrK0R+99q$HY}(?`;i*p4>pG+~FZ>tY4b4w9(6jxlR;THP z9c=DSTlWpFPI$NhJAm`-p64{2?t9!99QWBRxbrWK&7{P=+W9Fu;iJB+z#cpp=ke?x zO+Fv?YObw!y;9~hOqx4DRhCUF%FKWJSO0yZ+TqQNM*XX+tM{J2c>d%GZ?IZ2?=@iZ{Bh{ctN&XQjNR#j>{34Ffi;6VSR?n%^LC0f{2ckV5FUaVd6b*nZtfJ1qI$9nvXZL&&p!K%tHJhY5^b<2jAVE20d|}R zx)II+34wqqkBTR4>JQ3QhL+7we)K2mVv)RQMKxbMeIftPBTl(vrmL(RuD!iDo9^QB z@)BYw@1fTn@Qa)UyCa@Qij*=7o5rBUqtoiuOAYvi{Klk<&r)Yh%~F@+cHZb#;UmuO z=Qwx{+Q?NQU&6>r_j?=%Qn@F{mNthbG8#Ol235Z6VFO!w&>E*(W}li$WM=*z+K^L~ zBFsYB(kE7QR*j638(JzS6*1l!Q$fZNL$G4Ryn?3WghZ}vaI~g8FZMIuy2DmE3sWhe zv0sZ^+z#(wi6J8D=71)S450G`UXbXz8f*o!vX(QQ>rah^Ef7$=Y;$~gKzb@1aa}%; zAxkHYpqb{FLxQ2hOW{zpF_1{Kb?Sy1bcJ-!ni7J?^j zJEpYhxHEcs`IvTu&Rs|~adOQO10E7!@_duTYtVb@e8x0H{tkN0&3roU51ALjN0k!H z&j1JVph2((?-cVylD*26HgpiGVN$XVTiimckte@nZV%HePL4)}+7aB9&6xE!7f}^u zai_lAPIkK``3y{6)<@+*l9^NSdpE0Bga!a%;y=TCE7zKsjjpycC{wL2`R!D@B><-5 z9rsD%y&~yl1ZmPDXxO?v5Z`q3_6D6X8vACwHy%78aNIozPXLuSOwDS>Ot`_!PPR2^MhVCs4G z=z<0g$}=BzzP`88R=>p2+W`3i!x^6NIII@rQittk-zI`sV$$wB5?g`Bd7$*JSQ~zXYd!+gJN!XaI0`;M&&rgb!~z9eyqQedmHNhke|X5BJRR*Z;Ge+Bosm9|4jB0Fe*gI{C48;KJtef4 zYen@42(N>J$~ez4u801!+^%|q9`1s7_e8quL+Ju~K7KRg+qODH4lu6SYUB5-qro*2 z3sNdc=bO%MyWO$Ui-|)hlSjZw3V~z)`ny-ub0!?GSVN3|;t|LRSd?2V1=OAscsj)6 zDPj6e(*i2;h2lH}&)xn6W55=H@0@Df@^t{y31bReMd?;vt^$A~{b*7k z*W<^oVB zzi3|hlZ0wMg@VooX?|y^02hTdT{`GFfpok+&%>?e9O@ud_ z{%~+{eaXLK(5w*l14)D`nb_udu4oc?c$b@ds2FUhJoV_^fu8BxfykH#FPAib>W+yb z$tBI3GTY<^@lKFDaL*K2QPp?}>l>0eLAFUpBTWQO^>}zeZ-?Fdb_dg~8Uy8Qrt&S2 z(VwT}DxP^{9$^I{3w(8WOupussfr&@-nZtpCXX$S-Pdnku`l40M+7%my#?j@YuQvB zW%7y-ACU=Rg-T7M1Q>>js{_7j?oEt?$JBwfMZW-?<(V-FDHef@4khR2SF$yF5-myE zqT6~n$R{@;=0X-wR~JtqAB=hzqrnBIEOj)SE?+lgd2vL#plr`dG^3IRXFY}+Gm20x z$G<=e@F)OMK&`*{t1R`jZgA*Zg9<QxN(Gepl#gyZ7>Z`T6ORD5w7lC+jKIvqyn;BlBdvC+!el0ojE1s@%( zzSkY2gjNJdSiJbzNDHcJu)f z;?=|INFz1ITpL+ggc2k3I?3Kyc(&U;s15P~Ycy%8`EVD5D;@zV6~N>rGOg{NN14-{ zbN+JpnBo^mmPW5^F%Fg|k`_rC>dz8)zxlG9?uzwPaF=pdn0f`GPOa;*-Orze9`Zy@gQ{p#x4ovO+7nl|-avu)>tFphvvQT^Rw9KK zRkGZ*X;5T*9fk9m5`}r#?k5vH_&Q-kc4LO;s8q76=Eg6WUUu-J-E8-Z`P7662$!3= zQsneTsByVm5P^B3sAgE5s6(CEpsJKyB=lkLD)aE|+He5wR$I0zCf1nJu#&o6sqtac z9ktJt1nF-#EYPZuZ~UNBjkPJJw<$^wQbu?+o-6pP!cIg&Vxa0Mk(_Ag_nR|VZ{tzQ zVYXiw#-jF3oH(VkvlUKCl{6R(yCs=<9J!p+yL;**bQ`QiI0usR>MVwB+H_XDUD5-3 z9llQR)UY?~ce^ZW8H7a@G%#-Iz>RgcnbmXR0*ksFD{&;T9FDXYZ5I=o%<#v;rY1X? zG}*bFy20iNFptluxcT9zF|s_**f%rhSiM5*kRx8FYEaxcs=^2H(Uds5@okmOI74+sF=?0? zLsSct*aim=y-`!KYzF?jrjsZ3`Sa(l`scHkB-*Z8@O=qcnS=Mq#7X7nZ8;XD0K#Xh zLy92{%q<5ufT*&rL_;Hlx}jGV8!=w58M1Hq5lRt?(%bt+5R?Pf{IU&{koF zdU6{|jTK-;zmCTEJkU{CSF?j9Dz|UDTj-jLgs?|r-==V8EkGHOOoB$@r)6ac}B>;@a{>6p_l*NN)IoK8F)h#y=ESBs5w2owNMM-|{InMXkF?`P$ zr$gObZ{(&qJU{LmPP*UpVf2w(Ne`n!9Ny`G+lNh`hxAYQEq!AXe9nDVXO+q)f`g4~!eV7lWdQr+$qNk*V(h@1nz;~a(BG#1Jzzh>TJ8Yj!&LE@hq2y6YFv`7V|AA0T#>`E8TT* zGWDIZOb_JWY21sllGh@E3z_(;2jfX53Z~3r1ayw}A>VqUAv`s4T6wVo2Gl_OzjVFl zcOz+b<{5#2B|v~6Ku@O6vfZt2wWQXpRwKAZYb8v=wW<4y%1%h?6WkRC!^_JnQ)x?gOA%%k6`GPz_|VStR}K#k z8ASpJKY#vQwO}0W?(RK*_R7UiEW4AF)A#Q_g6Eqzc@9GZ#68WH`sk>TvaMrUb$sMF zXBjdrchoq`a1REBeq!E$VPJ(yWv@Z-F?NPEAS7QHaPVZ&ikWt~z(lHCqQ$|PM>UCW znP!C|UmXUX9PSOcpVjhy3%*k6>hf+bTrZF54Hub}M@#qi?YsByFL8{*5P&+H(|kvk z_)wzArbB|KG;3-xQJ2U>%@R2q*YRwVujX>#hMeR1cePnMZ`J8%Q_h^&ATa>wAFc{i2+0|^=Hhs)$J|w zG07>LEv2kQ8$>KC=s5y^X)!_QoGR2-L5`lA7f*}Wl&nwY=U~uUTUs z!;_JeaGEMu8#sT3@;cRygPKw|d8vyJG%;NaIU7LTY0pJ@kt&M~7?(WeNxOI5D@zZg z1}jiK>W=Fqly^HPK=kBSPbpGl@xR1{4BydBz1bx0=%LfO`gpZ4M^Tp4lV?vK^w=SN zs_nN95G7K$w6Q%e<$DNd$~;McU~;}=KUQWfemIpf>>E~j$>@x68c<{wp@HRW#2(Y^ z-rw%`&gYR7Z=q&_4xptYQDWO8x{{Yio~Q=ytDUbDmExOi}D<_)Luflvu8JAMq2|4s%<2*D$+adSN@tn!uKL_AL$eZMYozqpA-U0H5N_uu$EJ4P znvR;!Q)q9Ug~NQCe&WCV_y5LQ-l>%)xVsH{qS(%sOp-artOYayOf1eZKUS`*;uCSL zkhUSZDkvhR+p8lD+uJ9Kz;;U0Q7l5F7i^?c5|o z{bSx7v}f{+0V`QC158kM>l778^A(1ULG(@$n%{;SG6!xTTd{d|{0vF~EIUTXOdO|@ zDX-l(&gs&F%3C$aYz>#Vp;lgGl(cufBL zj+K5^eSSu6iakO%nRKp`I?Vpm>u;5VMPvr>W+@HaX=R+e2g2n*QgQdIF z0qlVz{2hqLgbBHizb&O3o6yD-fE?%?>m2v&J2d;TY(=X4lwNm;xX9wlFP~QhD~(TL0K<3S;UVv zCqz5@Q`b(&Tq5;Mh`q*#&qlJJ!B6R?7;oi#X49ij3`oKRk>$oR1xUhhDfg>|GSA?< zcbC0R2ZlMv39~A!YneN&g(+{QxZ2LfdyU=kU{qAdECXITaeTqy+D|H_DoqR<%4i6d zvLWhkgSF> z)KpNO%Cth9qQg;-*}?I_2{(Tw?Uj{D7HOcfTg0IPVsz4Lo1*`nibE|4}4 zdjpm+M*zUPY(M2*7IT7QTw#EHpYF8jZm)De}1Uxae{t zYLMC(b5zVugV0-Y7mPa;pGy;utoYOVQ$id;X_l|zBlzroD~iQLAlsXnHzGy5(7tzW z5@6NLj>&c`mSPW(GhZD((O4P@TE#-<$HWifvpMO6MM?c_Ty!ZpSC$UWvlqwfOgoex@DSAf>gMfu(%r3B=zr-u<(n{q zm$ov6sEoHMl37n>0->}(5!*1Qq2^m-2&*?vW;;(ES%%eyNetl=>U3J0&DPp(D?>p( z1cA#<3yGgTLe#z z(x+CjX{RtgF9NQOhwJF&S1*R0yOKsR%>7thY=6V%QJqWP9}vq-5vKSX3S_9v5dTj0 z&k#sc$Et|I4|fQtnPa>J%i6}fY{N7q>_Ag__ZJjeC&n75sZ>_Vg(u@CkGLDvj5yU3 z>Bj|diLuEQSgMGg`&>cXKfaJOLhL51#v}!{7#K?YQeXx=;s?p)BCn9LwJLld4Kscy zIAm0m$rymcDtOjt3tOyQPr&U?^M~B3Gv)= zhuzl_mU8Y!8i~3X1xY+ZWbUD%=WIxQE%Th5S>ef&x81_8Ciu7C|LLFq`F~QL01Aa3 zZ(Q8z^`@0_lL$M=CHw6r>Vg=n*?o3W@_2wkUcC6~{(i)M@r!R> z%*I{VS*5ttqoX=%L|Q?)d6!U`h7i~~-ieE7m0yj6R`X!0F`F&`9+Oey0-x2QCgvDO z5EqWa{t90|x#j`HG16709QjK`O#;goa!613LM9WXqMv;VHf5%)q2r2i6z&nEGZBbL zfyMcY8;=Dux`no?J@YkEoS{=f*DDL+u!uK~S7?jAQCTu)!81LS4ItcSV|9V(jTwAy zZgLh9Vcy{!DCYjKwD6>LD#j0y?098dvMgF3M7dtnPx-k<|A{|Y#?|-cdz-54iQnG% zXJ&dCi}~{;`e578;q%=zB|5F;Pd!;&-`@ZE$L|8Il})F6r*XM9s@-SJ8j- z{8M%=#KBh8J_Tawd>tsU%!%nLuRfHBFnaei6(h0m+>D zjN4tWGwmG6tBmLpHbIiHr#+bF0PIGyFBxvAqUipiM^T~WUaK146s)!|H8mf_3&$_* zttHxQFo9?1fz(2kIhph~Qe^N707*LxvM{xyCh2#^+UPH7eaz ztGS2uZS?ZjPbejyT%0X!s&I6A#19b5j!^K$a-V}EWJ=rM?k_cRD6kXRz#y3|FgSBH?17Ozxa=@WCr;w;Bqp0HS6W>QIpfPVL zZ)1;P_UhBiWv6%g_xp-;xqY@SGtz3}FP=I+B+`+T#bi|~O}qBH3Db%RcDS9uPgQ=7B1)BDanN9V=) zITd<2Pb{o1Z~O3&vx&X;$@w_~D=6Q1+&kgGP2ed>=Qgy|o`Zkx4#toZs$JQCg3^~| zFDM17kut4|UyJ``SJ$dQwGNDYmZqwVbnfyGdvvl*I&J~%ff*n3 zh3KuyiK^H$0zGEse5H~oOGRH~nX9PDYvzuLdVg&xb}NT7izd5U+cuw-zu6Z*@AS9S zbN_)$tI!!~S*u56ZQ z+d)ywyrxuaBsWy|s6ZH91`4SG(&34&y#C@fCsL0Vo&oA^Rm@05AV9$}jB}%a`@GzB z;)8=jNXj4Hy=%7)$xbL1RBkTSgh;x=c!&}X@RUP?L-^sg0~v|&ETdY^1kX;5c8;Rf zep|L?*xkSzm6KP66TRJr8@k}laZ9#R!A7n-^7IIrP?HcsE?GS@EWcJLW(3Zmc5CAMDq4Ab#*Il%=5vlmhV zRBV4F_N@(9XSVKTK0gtVHg(B`_RCZz@7t$0YBpPnGu^$TOql@Rzu(XO%=%`_=`NT3 zHybY{9bWE{X!2Qyx3xwYB)8fl8D_Lv!o_T;2Eu9`^dttKU51%I`N?%sF%76!KVvpI}LH4350>u|_J$rSOwaJGaFlpVgz8lVhk6 zygnBe3KSt7LO(k<7{Cgk3y4B22(O*54qC@FF)%fW+v3LOEQOm->h=nJ1RljfuMca_ z3Kd9w%*=-Px*Z*!=EEX{jM)14v-77syq)e4q8&70c_9@TQ39aMcI%9$c1X@7fRyut zd}R=Jrd+z$vY=;hS;A&?IC=o{hWWu28uh!J-~jmS6ABfNLpL{9YJK?Z>=X+* zb=G;Vm^ab6N1~^|XU1D|Js5V_@(uYYrOd4GI-ZmtLH z8O6rlK4zZ`sH@mF;+h_6av2uHc_BR#*(di)83sXsghg{+Vmestqv6@G4(o+ISK5E!4Sk@e~)B~3cnDp$L$n-<$z$TY<&;@IuWc#Yk zlA~L)DsxqheQ)-~!Km85PS_#&#Og6EA(mco7R}_ymGxeL8daWIf3)rU{8<81oPP6G z(-UrkpWFS=e~9OsUaVmM)mBX`-A`|S{BZehsaCZQFu+e^3Jo!#THV7uF*X(ehZWH? z%I^xH2(@Tyq|gmz`oe9lfwt|zbCx4#Nc#qzXldd zBooCYCmh=$CqK0&%^3{>^%5Hs)4_ccCo$TURdGcBZ5s)ylt ztbN*QCyYOMDQHc0E{a&hxi~eGdO&i0hmXSywo=_WIzEj)whsBV@rQQkc29k&+%P%=zF5vztyMLwaoR>O@* zGijyyl&_7IqdaF?h1ldxHdWiWC0#~R=IyzKxwAuf9ZD#h0_0qXw^}7hV$LmQKTQc! z8|Oz+CS+S_rbG?5bex_wnhg$oO{O#Y#O3sp75wQzPx*+hM4oCEvdF#Un+D_l_dooR z=xa!B#$JfcW~_wO2lt3m=KZ_(s+y3@Zd4VD&=XMMAg9J7*-MiBIe>LOMPv?#Kb z01nJYgP!V&Hjaqp66G~b$iqWNkI2!h0|4yNa6~dQ9!yLQxp(kk5btfI-vMJIfqu*t z9D!q(YKKRs)NY(3#EIJOr8r(v!_@qm_?~TnIX8zp;Z%&g7Y+1OiVx&^l(l@iTIXKbHZPiSe=UaJnD3Hmn%y))VVmHc=B)%zWo_ z9+wE0rpL&e%cf7^z2aPokX3Xwv!C86_t`wm@?|T66K5&mo$9dyk9KfS)4@(7#cH8H zvYQh@NRaVSwq5cvZwO<#1Wqd9l%z4j|LDWzN5Cezgdpa;y&;B>waD$KD?dIu=8e(% z&=R%U2bY&0Z>~Rz0zJnyu7yfRY-maBH`)clD%B<8AvQaR9IP8&pC7VXTcX-*k`p(V zd>kU)`bvh)7xAm1mPHzcsqD#Viz_o4&~cXGLMdrm%D5OcsnS-9;hZ8OfT{;m1g&qK zeoK9=1Y-1S3cWxf%#13Qr}BDluQ}+0Bi%D`ayi@B*jByQ z#tm33HRMa9p`IaXP4FqmtbG*Spzi z>u0)~I8aLRS%9pU3J0o66>T=TcK9a5JRe6nJ4sGbW>VtKekhe=G2LRbaF4+DLg8Pj z;8;U{I@|Hh5JTMR^B-7*U^u+ChF;rohb{_6npghg|=1`WZY-}=uW3kR;rmFPP!1QyR zO5g?q45&nXpPD0xA~)HTd?mTvl7mprlXn!|ZA9I#Mx!bjTR9m%c0ejK5DjE5JQn>W zA@xP6D{}Hu=8>Dai`i1Mb}DcNu6=F3QmOU*_DXfI%sF-2w32)lx{y&pKgB_}>C6V^ z`%m9^h3$$xUKj-z4Fr4$@SOAWGX%E=d=bxi&z0P39`YQqe0#lHW+QxF$VAVde!=5} z6*nsq0x05k4sOuEe(#==kr4$9uZ5U(n2a9Y-!TNAH5<^B`kWzDf$V~C6_B4nN94le zzl2VNj)(G;rh~CKMUT#hHEoU5BamkA?k-!kV?3VdfU!pV;rpLxRWF`gxC0(vvk*xz zWASqcAr=Bu>GvRU%qzgB)m`HOqGeu;2UD4$1~5QCCn^XHXbHOg?#bCvbovS(y(yw; zlzV|pQ){9%ne7#*2fHUTByd_pOq0=kM_VaOP13z~aSXhMSWxGo}BzS+V80CeNo+t=9-5#G!wtBQ|5rgJ=vJ^H6E|dA)WddcO zV|n|EoD7vtxaL}^uu{qS(zIH$TNyn$$*9d|oM^f0I43$xVE=p; z^_i_UuQZ$hYJ@M#x0xZrRqE{ zxO&rPNQhP2v*)jQ{(t)EJI2Z6aI*h4>Ic-C_`X136VYi2tXA!HAKbAY-)_X0;|Xzr z8@3^oJok4u2!%L#0Z*X8FHIVc6R2q2w6b}*Lz2ZvPg2`y@va6S|Kd>BXq=`Yu=Z$x{;EW#JyC^4W0$TVN2Wv*owr7Am?2NpOd)u*5vt z4Iom3r`R~rEC1zE@#*t)LFc#aNVCyVh97O)H)lI^I_iwGt@krC(d=t|5F2mAn{`HX zrm-Z|_o3JOi(mgGZw$3@v)%xE=OCIa2WGIn3!m-ygt2^oSR&yI{*(N=n$?G zZ<;>AoL}iB!lR=LBEqP(_yC>z0ec%15!Ntu8vH%3IeRMauPA$Y#k|y^Hj@L7#IIB% zEr((^R<>9H#W^jPvC(6hZsKGT5q3Y7_C}#L48XM_ z!g8BQ5tvu7uGXs>$$*`Wc$4dcqxM7ZemR@U&$d+k^2@Iv5=Qx5k=1GSJALN*0Y0eU za8RikXPus3Fm@yAm^l_&f;}7nms*~zKh9+y33jZhp^OLZ7F^_ca4 ztlaF1#AGPC#SUAvGK7PI=PjS5BgJM1-;#5Ok)C>ZVrN$iKPA)Pw{p@bg_;tQqHSJZwn`Bo<$6K{jEO zLiE|Gy)6Q95?!`aZnj**x4-|B1sW?DBs2wMpd&)>hXeZE<@NvdfB!%HomVeka{;N* zfL556G!AzqtC)i#<6^zbfyjFL?)x8}KYsx`$#N*x_Z&-H@&~T!@Q9kTdFX! zVtK1KH{8{+BAkda5E4mQQi6^-nTi<<*NLPh>Iz8v1UeT21Vk;eEjyh%<^mL{KfU>p z%(%h0i&bMqZO0CE@T*rZ*?mAywLrto#l@+i)Fgo6-aO{n;6B1&IzBiP{#_{j?yvqD zV)8OxfY{?FT8iMmmNZRAhzAX1Kn}+Nz5PSC7o9vk=5xXlgPLu`Qkd{?pQb!mrZbn- zxY2#X``hcqbWp7~O$rSv%XTGx8b7-aId-U|IU}XR0mBNKNEGEQLoOYS2kh^_`-+&4 za_$mq*fbhwADBU3_*tMrwm@x*4KusO;1k=a z@UwwuW+v)5k`IXk^D{;wfF_aI64Oa~!C(FASG+DO1}f0WlVd+E_*=&(XDkb@|JC)S zd^&NaYtS#|-oE=0n29`g2KU&p7ovKrbpi0ViUEVF`2fhX{PgL$Q*UX2N|<`;$42-= zVW|Ozb9Mdk#j}^^XRk_m#l+A>Q%tc&IVZ9B>he?a|Kq{+Fy`Pd!dE)l-_>BGg*6WX%NyG!it`QF`9qyJzB z1#-8)`!9b-aSC{Oa`pt#6ATgP95A{S{0P2S3~)(>v%Oc}qxv6@I(MBBLRfc5UJ8Ck zB`4FL^OF-drwM#=7{=72JWoA~$Q9dzplSxS>}q95Is^VIYnoz(AzmocMNjf%(G|h` z`P#M1MoL;aiAgeIgB9GYhT*>P@WXV$ky)|I%2v-`qYsB@cE@>!PiMrsK6B$E?`^;L4_sn+WE-JbS{ z`Qpu+AAa-eUyHT4UXK=|UU$T!Nih%Jd2o2dp#iz%-Mb$FzyWmJFJn5R&!9o6KEL?7 zwsXiF8m{Bvanl7|jI_V~>MI`PKm73zC&w4{M!Td;9Bk?#`an9Kz=UGRnL{yEJA_2U zL51hz<>h-57>4)v*MKFFS;Ktu<*&iZK?5jKkqOB}NSa%4uGQrwwuepNFv?*LNxC8v z5KY%o{Nu1Y-)RsVR$q*l&!4=~o!=?tin7_n0ny$;$}f7&lq_wpfjdrCYRP7(HRrUO=6)IR2v@AYr{>iKs>7DftR z%kL4{NQiuUuldk``ba-&8tkgWkz=kO#c@Lu{&igs#qnS4W$P^S( zMXBq6j+_PJ2eG*w>C824r!H1#5+(D5kh+)BSv);EbXlylW+GKsbK|U~yd4E;Tb;`! znd#1`ALpj?8}q;srflBzo|)vaixw&#Weg_AQ=(7&6J{@+4(>*Shle{d>ArmSy0z21`*78}qsZ!F zV9zc_M9kja3GAgNg8HEP^5rYR9<%kAFMjcG-M@T)g-hnq!HX|n{q3Zm`|8VYh!(uR z`Z&1n9yN~l+o8(5hk0Z7EF{JeX<=pEpI%-*eAhfe0}VhP9v?ri)!L}{IB41@M{uPU z0i0pZdJx?z&K^;p=?Wxja6G4$$6j9)y*+ zyZo@s^?@?u8FPSLu;y}=niv;=bLxYcS#;%W<|gtr7XOhN6dc8G`Kd6XDVcBXZ^gA= zY3C8WS0uNPu@`3^88o*KcN(6OI9D!TUVp?S2T2Vy=P$nef_fhYE>@u^X(Y%hYJ%)k z7sw<54R_kZVF!zlumT8qui zAOuZt4L~X%%1w@cnaFcI+}z$!yy3PC_38Di*N7_7XWlrqor;tVH_U6a;msj^> zA;34~vHF*P`)8VKfK_RTqFSSVIP6oSqb6-A@^a84b zBlU8#b!@5W!!m>TQLaYVM*Uzs8r_J`!k3YR3sEIW|N(?#1^L0Q5++aMc*Y={l zr|@Bg0@B&FS_cK08s}-u*)7=c0U8wDNwUdmMFKq>Dk{1-Byae!!)9G(57tt;kzI1?jPL!{`j=@&_Qiofgq1-06&G}jg)zmC{+b{S173U zbOer*C#p1qZxSbE4goV`+B_y1ha20{at@S6l6I`~=9D$FzBXy%fo{c>_he%>VCSN`-_WnYNR(m{&aPF3AuEqTz~QME0*tv zk8cKpJ3Q)4VZgh47>6Qm`C-oz`{bo=PieXt7V*cRH{Xl5wYcHE4Azt^Cy|8$elY<9XGhmb`q z_@lC=;rx761zT8BnjMmQgr^vmjXghKls}&^B}k}GN|_H&=X#8dnh4lDHfWEE;qc%H zCfnUj2XfF?UwnIhe!&g?@BibUNlq@;h+TkwKZFBW2n+j-_P1aBr+STOi)WD3zW?(N z7iSk=y#DH|S6}f*3}--Wc$7G=-oAZ%bA5Y=CtUkDj7xw2cmG6ljh^D&$3H_}05w+1 zd{AucVu6cAcS(w%DsO?&h9e0_1?5 z8;S`>Fu$bFIXFCKd31YSd?gvt19-6K(c2?)X8Rfsaj3#2ABz6|)rU)#4~wveo(ads zTpmkSm?`x=j36qqhMrF0uCf=>38C{spBl+;Xx-^OKuameL#yDs7pss%#~Pr>3ecQP zZWL$3lx6OxHh=(aHVcG-MA4+fKH~DdHbX9h6k1QVBVt>)LGjsgoSswfAWj3{;rhYY1ET_Up{@wu|s{! zy649s$RLFgG6u{wZuIH#)0fY`W+?T?KYjPmxu$o*Q4TT&7ZtPScKeWHz141Xdshew zZ|^SIDA0c>Ae7BZG7OUADzx3KUC8IP$^o2FvbR?;&sQI2JpgELJV6?F{QPVg&hPK9 zI`_A1zQbvMvYarFY!k>aUFOUw44HpaJ%*_z6mW0vfWkt2{rpB2dz<`EKfROQFCW|C zNbG@R-kb|uri;^)sVX7)AmFb+as`WrmwUKYQYr1X#^M?BboN0(POFzLVe^6S6&#b34dPyXNk z`LDOPH~WzBmSf59)^tvdGI(mK2E83>8+{9yi~z%hVx8EG$YwQ1%=ql=VzB``8A$Dj zYe#vvzF(WnvED7>3^JZ|@|E2Q=ys{7)kTsM3VcWfEGLLUL1FYToDC4$G-B2G%@@Ba4hUcLN+3F7bn^$$OM{{wFn@(`1H_#gXwCq;%W zwNr-Ky@xTeU>2&F2SYtPJ}2B(#+pc$N!n;1-gfR8cg+?flugG+PZ>+!^>6xP<^@qx zc_14lEEnpNAso8CWp^VAResG6fBu2Dy1Tp2bbX5S4@LDV$Tu4MgW15TAd7Kl@uxvOZ!EdGl&QhF>C zLpT0h`Y}59sr{0pUuY3hkzC;Tk(tQli`xMV-Vgcy>h%LgNXQj4eOzL6lP;R=lJwKx z`GhaGEqG5M*v+=~!(TS>!}-}ipSk3PPSx{-GqI9OCL#?X8eocnAcGZ-#d=d+^ZM%U zrm?^GpPs(^R+C&8AZSUaET@gXrIS?Z+{>Er4aEx)oWN_ksLM;U_=e?d(B3>HyHCamN_Ot z%i^qbb9r;Le+wIFg#OAn;A*5QHhidu6Z$aTto!n8~C>sAi1QW!J(q zhw=JKpmboPIqe*vxKhDLq5|I3sO{Es7Nr0IezWpq4+Sfk8I$>zn)|~>4wrZ(D=}8t z<>HkJ9kW#uJN-T@1e2T^|DDje&-v}>SYIm|{FV(PwL_=AVQ(MPpJGSaaUTsO z({tYq&NF$_HfJvz0b~g~sRLOW*=TeWB?L*@ixz?z(hBxB3*zqXngNR1d?q>hh?_O0 zHGQ}x*!Qc~zx?LaZ!S(>gW@rP=NM&5UzEKM{#{GXKg9&HqANSJ!U)s(@raouFEm@F zdSjP4%c~b(9<)yC^~3sZQ$EJaCF2#|ALgLnegEg5-hIc8ad+SCjxYfosFT~>BfD|3 z=o7oe8Q<@Yn|s)Jl^*&XIAydn-Oj^k@&WN5mB8Ko{bW80SWp{`&`R)9*wsLL#wWjN!k}>Mj<-YlBF>)4P!jW`@nVA=wKm1jR5$PHkWw>%WvO&5AFRJ{b!d$ zdm#B<4kt@7h26>?#}pt}YI)XH9u%Unvsg`r;~_T_);*LvM62+jrpm75D#0HKNzt2$ z3O28o>P#Ih;Z#&q>u)S2O8ir#_x4$zPlUuk;?UW@fW^H)7 zO@`Y%Vy{-yHOgAdXS0b z|J`IV(!k$_*~tfH3#YnBS|8|Ict5Ure^oY3;346g1p>ossCOc+5~Zhm<4@4x@U z|G2(>2hVdny;TNRR2$9igWN*VxA(HToh=76U}GbiFy@vu6b2kfNtc*0a2LCaO*rK{ zzzug;ZzB{GG*`4)X7@Hn=UEOx*YWhs$u{$wzy6jZ6(;O%qsIGXJf$}s(=8#hm$fI5 zkdka9Q6KYR@yyMOufNQTHLL+$QS50fN#w%w^z4F9PSe*L_8|Y&D!Z-bkvZ@RU-EQ; zkC$+|5K86~l2~EBF*G_o zxcKVTFB-KL?qqrCTNtf*865cB*ibz0`gH7UEX0(bil_Ve=^5-FsdJ;EY3(Q1w;uJf%v+3vx1Bqumf&a<)3zJ1d1dU=*0pM7t+)`lR5f?^Wlk zMROm1j(A2nQ>9X@c&tX^DVKKl z>KL^Za(JGHPo83c@gR9DRRS)R6|Yqm_fLE3{2FRnj;}MqHY%A*xoR5=;`7lwBY~!rkre*eb zLq$d^msSsH&id3H<+36NxgH&XZ5AI zV$OV`z}P`sTkRHOMLBNFB>)C0*Uou;`TIZo3vRWICR{4)z;P%MyIwKL)BI#HQUDF) z4NmI`+Mxp65O`oP^30lDlRHhLLtG>CC;@@eF%zdnK`428e7rKt5O!WVY$CSU4eE8G zKT25G(8NZo%1nxH@{eU1G3dZ8!(4&n$NR7D zHtue2>%{gQiPt#s5 zFq8tc<3I#qOZ@w@lGFjLO!$$q^gpo%&wbjhKjKp< z(>_b~&VJ%&UDGGNBU^@#iRWWrMpiO&2HxpWcAfHHPORMJz22+R}(1OokdttK7$WgUbo$8R{j~=@I1U zN_lVCXVaBZ2O$&`qSa)9_YvO)ZH}UM*zX;;SxeDGEe%XgNq(kye&>r92gl^#@{p#;tqedbZ0s|^p0>zcPTL}HR%I-$NjY9}W z`&2xWX^&gO6nIAm3f1}=8dwnFwN9M@1~*hLXQ|9DVN!9#0|UuQV7{*JwHcI|K2X!! zScx>a1W$Ovbh$ThW)XdZ`Ju>;rTvSl80p}VE9PxpRCz7{44ps#6S&$2e-?ZLsg+$( z#H(2zoDTcIy19+)dMQ7W1?rP}PnUBIkkJL5RG-^Mf*gnUsKvDA(-HsK0`((1gQ6UV z+UlI*&!*~0W0W$RK0D$wU*8lA$ZdN&X8lvTeQJU7<7rZjJIip9QHKJvMC-?g8&Xek zx8EDn)Sn$43Q7($FKgR=?a7joTn~)-1!NW~@qrPw+;KEdY^ z(dgmf8C2BzE+Itct>)8{Bc|FX*wO<7z+ol4m-}3pQ)?`VS;Q z5@i^#iuJ|`GCB@?sY*Yla zy1VPlXdwa)y#Db)i+#7x)N0V%%hoT>jG%ooFIxwTU{B-o?V`)EQ-%+nU3)=EgWDX_Zi0)+p} zbISf|@L5RoWsHP?1h4}t|AO7Vq8!8Fh{zJ@j~9jtab0-YxG0jM$Iw*w`n`cHo{9f1 z?;L5kN@^gbf625yGW=i@#jli68^2RS%4d--;QziRWKl-PQz#IP$CH4KkvZLk?x3q} z*er0hS>)6ccv5n@K=W>Zzr8?xgLFSzuM#P35)Ek<32^NYb)~Sj4XG6LT(4$}n%>9(m+#R}4fR zk+8^x?{=;~e)vJ25xLU8{rey0^FH%L?D)8wUD$EmTc+@A(l<9V-0ez@T|DL3tmsRi zSW_rA501rt!&8#>dtHaP0Rt(K@2LFV%@dtSJ}Ci9 zb2gPa`yHH_rBU4xmhf>yly1)6HL$;A^u+ zLjXRYq_c8?i*IyFwDayEBl~{#QO0m4lqyy9r4S;Q`e_F;i`C=XUl!WlAc zlblU?0joFS?OEv7QqK9OlMqFI0F3HXs^s&fVTm7A5@rHAq5&Q8#*?f(8>{?uxnjL`+al#rner++;^JSf%!xTCXS zXBVU0MsZUGZ4~jp$|5F$NdggdRE?cyBj<~2Lf1VOyJaDuYv^#uiHY(6Y zR)LIyZr%ff;b8(~z?3Mbj}lpsI?Wf_tAO;CTWz~TxhBWBd5cqN2ZLe$}StTWQ5Z>F=yvT&+9vloXM~J95!Q!rF`{Q8j32H zn4lh8o+_Bq8csgKG7|>*CrNF`h{%zj-l+^Dm%`&gmT}{ooC)Y184a^!PU@y4mCK@$ zeH6*P@{}+)*hp8>#^vpgERZ1gnF=Loq@r8~rUqM1B-v*CSpn*J>nGS&sGT>?di+h0 z2?GW4o8ro?Ps6n2NU^YFtKLGdVxL81DpRvS)tJDOS2k9}h)TN@ydxDuys@Or3N)a> z;Qq}|-;HKN`FOFQ=JAJjH^I7abK664vs2xr-+)L!tTO&0M<*xGkG~i!?!)3TT<2C( zg{e-~6P16P%6zO3KanlE=FRbP< zEg>YRu&{*61U`In@?@PGL!Y4z#4;ojIb&|(B!-ivgQEkmE6WGKRBMd!fOnE(8xGVo9CuYHxz(N-BS*l`~O{zYjMCB3;!OM(vEv|p=?~rZMDTm+6 zhA^Lnou=?X@?|o(5@rOsSZd?&kq!76WU#$Nw@u1zjT+jOJSJMB8xI=SFj+4CuoW(}8aSy~0k$YLDmzX}xYfXkPBX(I1cacjtVGD(NRDE~%Lvbg+n&zf` zP|_7PjCXhUzxwvqHM~fd5IM-Ao!}xbcf{!^+G*`7qiNj7zOz!xGsTi!sM1eXG|luW z#4L+bv7Xg-_n41OmR%g2h;;==P+Z(nlMKXh&{xr!$XmIHMyiH01irmY;m1Aog^`j- z5GYs4tU*sBYXA$^hCEBw6IOmwR|)J_uq>YAFQ0z3r@C8O54D_fuUzLmkw_k6I8K3a zmL&3ERBawK#v{*pBE2F*nJ!doIa797CPfCm35qU0yImkGCW(-G{LgMENBU%qu3slb7z)UWlzu2iZ|MdU;%TBYnMm1DB zZtfhB5>u=d&dyKvcK7AuS)p!VG%)Mj^;t_?Om@(heNuU3w_U;xMR;o=r(SzH-`&~6 z-9i4SMY-bQouXbjnBCrX;uXO~3(Hqm1cEq0n#E9rt;uvkjX=UF3yz;hKK(|fJPkYm z5VI1$%qA+!&iUAT*2!r0^2L|uXBYCBAe4l-;&tIKfBCy|zM_Qt0?a9TDUU|b04XQ& z-9}L+d~PLydVsg%@S!*6d9_ziWNML>m0?pz;Lo?YkC)-De z!1;&%Gb3ehu6)Y1C4&w#JnOSy=IwkE--A=S^d=<-+J>MmPV3bmgFvQ3p*gZMsFb@Z zxJjVE!4;Paypb%tJmkrXXQ%M#@y^1VWH5R_h%(b0lca!UxSZorKU)!57(BmtEk+f2 zoJ?V63?E|L$q{Ao5OnVQifBQ+ck%TJt4ArLbA%HC{(2COaqA0}VWfiyMuG+gtN7!` zkMcy#$-a)<83onXv1*1=2n~OXH?Db4p8o5<{u`I+#f#_6`}wppg)t(hn#6KVmjyk5 zC0ai1I+2cyxd==ctyK7&bpn1sYZtk%U;heXXeid4IoHXWE4i(VML1eRP5K}*d*H3w z@U@UYzDZ~vcZE>`pkR<5Rk78n7G03E+etKhLK{usPk8_Vu}&sVr&k$jIhHF6_;QW4 zF-PsZ>&`dR&uFt+w{g$DKP#7_%iCS<|GiO{!&sImxQr942;6tk|DW=5a-6 zQ{G?=3_z1FRih#X9Ar7KmomfemNCY!!G~g?unCso>ld#$(=Tt{L(t=pMAFjf-Latn zY=LERfL9y%yt0v$|MTDfBmDNL{j>!&RSL#@1qur{83`$}9y|$oG-N2XQT8H&;lqPt z&2o5J7%r@1Gc&1TApkoN#VEgXdVV}mXgG&82A^f_ExYVw(vug%&W_Ea+4Gn8cMntJ zsz@YT63js`!>ux+A9x~B@Gchs!992zqs`jJ2 zJ`L=+&vwpjdC6x9(Y*xA`@A3v!G z$!?YL4KKm8+$~ggu`wVeg4zd&o~>~_@70tFQzY;PDg`1cOaOouR?0V$utx zPLJG^uU>Ej9~}{2hjg7yqDV=MR(h{-1OeF)!x{HjjS-dOY_L-=$IBQQ0^f!+C>tk) zRu&N34TTC^FB$XY@^`oQTodY4YFe0G%-@x57}Q(QbJw< zpNWg(7l*B5A0t``B*c#GX|2V_)nvUIC`XBD#3mOQ>Bi7^Qy|M^Pc+_*7oN@q2`$(- z@3Nr{d!-N3opd)*)j7)S9z^cYh=0DeWmHktd1QVkq3lcw&eJoW(VRP(CVTKyW$W6) z!Ag(O3vp;u0*50kg6T07IhQ6dZ9ZF++wA6yC~bzjYlk4RI>A9M$NUqHUy#-YxgdjP zA2F;H!3{n!p~xjol2;AZZYpeXAznE{c}~BDlB|}pBeu~4M;VKv-|gRbx?l%vT=Hur zaa0QQ2VM_Hoev)_1?PoiJm7t_OOel)5B*DMR_rvF?=Q`Hfcw=L(1;h}>3q1mx66Jt z87@dw%u^AfOX(4nRM5cx{(gLM{*t>zI+%9=%o!+y6IIh-#9-%$H!D*c*V%C4}&s0d@!3_DB_I0*hU@(@mrRvmVikv4+23 zd$V%tiz*zU{<$uN_YaTD=%9g1PxZHQNiw++vA5aKNm{)F3tmdv%O-v&+4knk$OrUU zxTq+$*?eo0bY@xjSoVUSM&!iCPzjQD{Sj)2ZHnfn&BhXeN>gDNGnsX+u=Cn@_qv;T8Q@y#jgT1{tlN+?<5AcS{IVDZ4=Xo1JU*FuQyn};Q zE^~gOYG&fNnv8NXL7@P^;JX{0y>5e~@?gVoLvBa?Yr7U~cFA`cDe>f=oSt*mk4AUG z_2Q**{i`R(PZ+;H++ni10YXO^((B#p{DpZEGQfZW(xtoNSRk#LM-ED|VrwSy{$tUD zNC=%?D`Se$DZLPP9h`qK>{d&SUw-}DX02)7$A%gkx@l@o3F_4<2gZ4zqzjV9NR&Qh zC~6&;8@$TyD=4)f%#R$j~-akF(JVL%w3k zHCEjPX6|6o$U$;gX0&{(B{Gg_;L9i}J#+q^Z89exsC0J?67#TZq4+%3PvK-o45Ca& zmARdzEWXES>c&p0oCo$MJUe_Le){e99?u2`oMqwgUa{#x!ly=qJ+AXG#HmlE zbg|Un8`cqtjyapX$+d^+>}U#d$t(A|_vNSxm+`Q5LZhz7Rn2M1u*P-lsIhgI+CSyN z99bhS4w@(dRtPF&PFaj=o4Jc7=%i1YilH>9Zlf%My<(i!YhSMg^U#~vq%>Y5+~2vo zyP^wPOp&H=MG%YbP8Xyt7G!=F63a2>aA90dZ?yw$`RvOETQ1BfHoOCIh(zNs#0A-O zZ@Hdq!WrMd7yu$M^#HDP^wEf2I6qI05&zS@aikWoC8al)fM$u)i#D6%qkZr)WnWk7 z;~v{m%!~?xh~o5Cs)7}!hzS!a)zum~6dxNvfaecm=r3RVlHd^*x0UIvJTJWLPFgdr zrmkQt94ndZ5MP@I)WTJfwy8KNuHZ)9e1jZGQ-+bX=7?Bu#ICp+#0uH92?t7+;vmcN zye9rDTlPv0*^!x}rYy2DG_GX=8`^-IPwheTS@#`DMICLUk5(0$Axh|4ye#P38}3H} zX!;M~Otq;5r+pcQ6=Si;|3-N8y~HxG45^K6R5*%RVQTRIg}J!DQ^Xza9y zox%V4fBcvIquRsZmO1Kdynx?^RadipAeN`7T6^?&fx_A4w^7D2>iqo0_dkArdU3Q- zEW{95k#xUn1%JhBl4!__K%RFr>XU!jh3g4thFdApdj&crn4rhqmvoYRL z2Sw=utIGw!JRJs`l6scaZ_qe%4Hyk@ui4(vJr{C2-+u8It-Zs1XeW2bjdw6*qhw1> z^73}PM1ku#)!fT(mL1gUJRcY7!ot1rPRQueD|f=F*os_Rc&@6?iI6Xs=Gh)uHjuUC zQJP^&RptvFa7Mjz+L$yzD0N)OOz`%Y)_676>OMcy*nKk>i`a!!fP%p9R? zJC>C7Lu>QmH#PkIzdm!` z&V1f6tQ>$r;$xthz5Vd!&D9Ux!QGE<{@kcmK)k31NRd+j>~gZVTV+81FuExh%DK3F zf8B5G9Vsy*ANQvZSNB)&hh_ssf{m5>!rz&Nvt_hHeo}JP95HxREKf>N1@?fP4W|-;@H`NL*y;8otEJg^XnrM)-z+RPgxwKS(@0@c zgDwH!v1X647N=J{Gg95(>Jh- zNjuMRG#2tfM<3YCfURUH-cCG)szSU4g(AYCqtiok)S7cU`klcv9@ffr=##IWe~Hu1 z!5)~;`rXGj_}G{3AzxlRi@x~Hle_C-Ywzr!btqO;hZL|5d|#&X zSV<$RCjyUZ;N%$oTiEON4_nRQfJ{!PBgB+2qJMxI((4leGDpMF>vnLaVkH<}ql_P7 z$-$xU55>a@Zwc~iNS;)_vxL}p6c%RMV;;C z&^I}Lhyp#20zo5JnElR%O*^bq|uUaL>b$*ouwUQDD2nGe z1QTQ!N@((Qc6RczwR_s{j;=d5&BI2!-Z(orjGlZ=-~&cvJEte-EXuq4OEh!17UNX` z9krNmYK9`1Z3Xv~a~#1CvmZ_W;x#H#F1X^(2QzLjQVp3?+Blo1AEl|*%eqf|3CK57 zg0u4jYF3acJ_+>(MK%XIbVHCe9!1gfkRHZ`YNheJzxv;ID|@jyO)|N%5LqwsH|YX3 znxNE1k0MJNH#FB;R4=oI8HWYH zK_I|bbVP8Fa&A??AaaTeP8>2-ANB{kl`7L2dUM7wL9{Hfyp|g}C((Qvl8&HjY!1&a z?(ar__}A}{+C2<9I}K3&vO>S>Pg;mC7_G06CpGtG%iK==fLt=}mtK8HHWynuxgj)7 zx($l&EZX#M!%!clG!`Q@C6hvRK0IQ7r>0~;Ud*ipBrF%lhrgrV8;)+nJcsE5ql?7= zqErEaU$=jYeu3LH94&KLL@uJuJnW6uzkc!cfBNQ^?0R_ZPXpG9o&l9YoU;Utvt#Bo z6;cQWzrPMeibgvopOP_>C4#DGs+h#{_{w3Ngf+r~XP7%H_X?7=*@P>uNgK14VNu(m z4&gkkSFxsI*^|PjR+*)Nz>c;s6&0#sjLx1Ucg412ttIGNdV``2!Y#wFwfF22PDD0G z55m;SSu;)r7aT! zdHH`^C8KEMrS4VIPMJ}aC?|mvxKuj(`|@+28aK3D`%Rc`*xp2W32GB2F;Z2RTDc-E z!)gxg4E*ToMN@*|dCMe|>E+GsTf!P@74B$p9!xd`7Upz$H?1K} z%a8l35eXkhd&i6Qu!v1bI6SB~D&+#Kq-g&cK_r8+bemA+Nh3YPWZueoWw;m}L}WgZ zWsJMT_q3a>>+4(NM+)~AP8s4e4}ZG~+)9tfgMoO;jJ|@wC=d`yoK6}3-Wpd`SbQvn zZ@&Es%gDJ@?Rlw)H=^1#_ZkoV!Ee9$i>D_S__1t+YzfTETbC1wyua4{p%C=*%jOwA zW(+js)U*;xiu)|MAW5FSv+>%fab;q0`i$d537#D>yMas5cqUvHA|de=fF(4<~- z6(k$pIwQOILE-VCDGxR8jHql(mHqrU!&lQWm2;_W*=-*2*ldm;B%CoVU(>lbQzcYn z@XQJ}o@k}HB0pk8!V?EBMBWQvGiDPUYjee9GWOV+ZYD~c*|0FDYzs{D1CV)?@-u-d z_5EFH_?=pXh8Hz_bojExu_%|bDV$D4ELz%&0vB|)%-ea)Pr84&XCEq859X^0=1rK~@^MPOy; z@KfI3Ly)4F9=qdq{V5G9mk~!Df%k=%qETUX)@x3-N%I@G3+HVzYho(xA2UNIJY_43tH?*F8b2BRtS?h1P+k!1873l$s_T6hqjPPhB<`g*?N z9>>7EyiXiOp$b(htrsWfUq63M*u1NaI0YYlyEe{@7p2xNd@L^)Gi-t?GnFJko>*(y z*Ql3f6+#%`d9ya%2ug*D$J{%c&Wys9&RTtaP8tdnxFYrEGqfo|TD7Xabz4BgZhcBuPj~7GL8KP{zZTIk1>7%*K_lnsaKA`X1YqSYCO`${raNFGbi`qmZOB;Og%*;+_tm5Bn#JYJ)> z*Ej7}>#)`0?O}?J>Vvp*7E*sO!nd+m-d#&@n5$X-Y$R_+rHGgWm~8t1=WhjZgll#; zyzbEyJvlu2#p`c)!jP_Ef|U!rU00zb9aoBo3i8BJ2uX&fO^{_6e@|=&eNg0uw#hls z=L=bhBl}-j&jd&;;7Jo*HAjVX@Ev$bV9=vxmjdLC3`|w&=6f2jD>TA$7Uzb5#u;BT z`yx^Bsl17@FouPw#8C>9N6z5bj+@`M(Th_a^0udl1%!GJzD_%APp*j#C*@%aWt_2? zC+=w30!?4a`l>jy=JT*q2Vb{F^I_H`g^6ockTG1%^j&O5Tg};!Uw=%~>9sVY#D%gk zN;!)F#^8v()J>w5FEgGHk4_bhSrp&N?=0mrRYhXfM)OvzQi$!h+mqP{Q3stq-XGD< zapa4SH6R>O-K!oHS2e)7cUZe*?J#>xyo_MQV+G0ry1%_gnS%KA=IR<*3EL6^Cak~i zyPexxRC0LcVqM$jkpdIYeUZc{#JCQF!3n zn4|gCFWx03x*nLg`mX{Kg7+Y3~ybCtpY#_%_&&Yo?45|@#KtJi0WGosdl zP6YLWBj?Ez%=hFme_q?=(pVZxjDs@+Mc5=@jqGHT-Uvx5R>hDyAalg<@<)rP{Q^ZL zw4;?yKrHN*Y^`OPI4uY$Dp{6t->Vt6=$jh?z6lzU6%F*{JOO^(V>Iqdy7C_F`bNe`o$kXRYUx-gw%RPiq}=(!Q>gmU-qRm1Te@ z%={#NB$;(df3OKZS%MpT|3MsX#Ybh+(F|}kN~*_{S!VfaV+ez)3ytdx&trnUw9I4D z@&5hy|MsUp|A8-#4%)2LBYGQDn;k5eX+UL_V%u?uc#9!=|FUGc9@J8gYMX+p7#)1WID1q!gexW=y!&*RjyvB z{ME1ia?bcxJXttMveH#qNjC#9WD9g|!*PHx8YA;|RjL{bn3W1!V9@VNfNwe5f~a(4 zlNC{PywhS1l~}iwakaQghU1ick@4~TezOfzKdly8mr`8u!yx9F1=mU?dys0`!Y$^J z=8*1bnsKO;VBE*^dRxXBo5M$(MUrO0OWOlXe}!}$pOg?wqiXtn@mKXGDKSvbvYOK1 zD1UDz^IN;gomU#`6#MiYI?8?OWSZsSh7Owg@Y4oY9vQe0QY(J~c3*}}K$25E%tmHF z*HF54FSoGNAAU{G_A#)5CyCsr+PYbiq}?Z8+N7zmi(MXYuodLdb;b3Tit?PTl1xdy?6mpASM>HntYV?v=5MK$2Yqr=#+S)CMHbNThHnP#bLHbxQ7Wn49qE&IdXY@rV&z{M-ixs#@R*;{BvG2Xvc5XY5PjY4vMukF z#0L8I+rMB3-SheF#?mQe$iZfU8PixcI@o~_Ad6c6^z@V;{PyiTe36JoW_AAX{dWKX ztVfPz3`!898k(&k=!UN?poDCh3yj3bS~9W;P^ID9@HP@_d{z(rq_i zStZInTVAXIdz_CJ6mYMob zo-!I(Xz$5^A(1`+8J`rR+AfPKBEXa1#7O75+Z*EpS|zHur0C^F3!tXkd*G7ow3@xi zn9~;P31*g>OyrwHE7#G#$BGCqD`PmNa`BMO^c1S?g|S(-e=z<+Q^q3RYwwc*M?6xS zWW>BmRfwgG-}{}e@@wE$QV{}A>JBkrtiUKGee!pI^Peyg=lJDS@@P96MVzVGmpga8 zd_XXAyLq~Q-ah`PfB3I(Kqm?ggKS3&zA>0noNmcq*Xjm1-r*pQMqRyBA+mh$iDJlX zqyWI1hnXfi^^Sc7YcIp_RNUR;??tKTZ7id&;oyNs6Mb~NrCo}J;0 zL=nVn=kTCK0@!SX+4A_ezx!*tDB&Hz`m@74*#C%pv^kPtNR&loBEsh7XQof2y=p>l_!{7ehS1-P}@ARgk z?&y4xiwOcIxe5m#-@oH)&@$5M@nKo7VOozgKNMMFO#w7PZM9hrn4fL*5prVXMOtX{ z1$G89)|1jh2`oABZL7YQl!QPi@?i6XDLPxXSgZ?X`M@n4Z}b-0s3vlgLnk>N zPj?tY(GJT-R(t-*v4VE`B| z0TCf(x_S#nRs`Ggpsxz`!prlg7ssb~fZcSvAXbFmk=w*nnU9B^&fU#rZ`cn_>v47U zks}LW2%t)Iuqeb1ouxAv^*P6Rs3JK=FOarX`8tX6`f>Qs@TlVhl{2@CsR5!>3la(` zy5R6&0MFZ?bEw*hPyXz?4?h7+!!WtKy@9&)^@}gBu5a$U_eZVva=du=!#mu%%6paQ z*MI*TJ{ajo%BmSNHl+Y9F+gJPp$Oo|U`oGBD&u4V&*c?>KH{f$s=94!0Y0Vj2LFhJ5zC5H7qB2rxa_V)H!XA4bbf@--+Z3@n{ zdtBeI)-MiDIW+$8{r6D*Py#5(Aq+ZX*LCiwRd4UEr*pdsLVx-^Hz)+sLcltCEH*>RZKkBMm_`6DM_~!euy&Y9bjWnzrdQv z?-V-Y&ML;f3n!fQ6NHx2!9n}rhd;c5pV>Y?Dz-||`PUb`A}%|$1jolm8m*Qop*95U z@qsbUgTf4~$;TkuRE=Ryq)D#MKK4%S1GIf~71RS@Ktyxwx7s|lygicTE>6$5lauj~ z0C4syc3i4AiWUVYS8JRHn1I7oG;XJSKm$9C7WvgLUw`xQ&0GG~pMLu3098qe;VQXP zllSl6u9kQKkW@BA0r=+a8-}#l{0+y9R7sF#9wBQa;zYDkH%tDMeFs}-bC#1MI7Nt2 zJZ*?4CWRWa}COb~(NjusgX}uE8mSMvOA8Z&0 z`2NxR^?uOVvhED~R$+H$XF9Q&Qx(Z7#$vk9Q=AoK7=kUWX1d9$_kEsk^w1>_y8HB8 z9zIo+NGPxbVF;{yR44v#p_e`88?FlruftHsgtOESJ(wb3x;>L_m^!D|EtUG`be~ zdw9ZlI6;wk%%5MsV55?75xQw7juQ6*_nCEkfl9Ibc>jR0oggxawTEb5uQ;?h_MM3x zk-1Dpaw4;$5?0rVVa+avV5bV%da|&MwEZTJ7SGw3$~b zoOk@x{3MT!dt4@5hW$ZTp2dw@f4Bz$gJ6JgC(X?Z$XqJkpefFzkf6>?Nbvq4FKZW( z3#vKq$dTuj&r}J2lHBur4h{%!%Z1D~wA$D_;3zYQLR_ggO>;<$GBWkcT` zuTF@uqZH`J4E!TOfAFRPvVh`VOx)&Ar6Ph#{#l&ZpIg}els|fG%MEy~I!C#_AWAnZo#8w}6J9?5K5Wm|`f1UPZVBAZ@@L1?#B#%!$kZC-TBF`!<}P{Zh~nO? z55ulWAU-}%!pH2Tvh%IS!^YI?#KH-lG&G?JUA5-Si{_CCgMFk7k3XR`?9*S*lPwlA z&e*8Bl(!eEHYHKLA!%*iI?LI3k%(r^5#HsD9z6&T9*z^(fBSf+>f!#lF`^B3uk@)f z^9|>L_INcChro(ZH8S^-OAj}qLXlt3vPD7TN$(Dl%de53ADXXhgwwkN9#O@=42cmg z9$4;X+rXA!799JaNSQQ|dRcBn3AEUGDa`)Oatj$2<~M@s@G?q?iQobW?d03thhqx= zC=;+CI9~B>K*CieOv-x=f;_BS(a?s4lnBeN89`keI4a?N9yQ=QLX%^An&Ae)pxAX@}I zA|ivrlRJWv?YJ}ta9APz#e2M8Z~`FE@VW#xQi!$Z93|t93Tu9nIrVet%=yewC+l0J zQ?%XUBx~QaZBZr8cy+9~$R|p*$BjLr4RY~65d1PWgWqk?1Dj@6b z$NyKF+q_v{m^{&G35o<&3~mz?kn-RSuhNi&vC)gQV{V9gX#iw(Eq4-MxRIBGh8kJU z-3{s@FX z?pWu!6?3{qd^5D-cmz<0qCFP4xI6`hf-k8QOPy9*L>7EUTbEChsg{NUqM}h%rn70T z?+m?12;-FiPh={gjmB(eemoWc2TZK2SX>7}35G*QNkJ?e&*rjxmh5q0V+c{>u_(A1 z3s$XqhEU*Fzxow9iQoM4o0Dq!hY#NqJ;@fR=gz33B)rdAAu8PgV}SzoyALpwJDPA& zpZcP3{XC#x3``L)uF$Ps0PZ>ZDH_6+KJQ^nPqPJjYz49(yHR0K3U?w3PLoeG8Bbxu9tBgF7)7gn#$#x2Utr zl@mZGj<{$faa@79FuX^N*F>OGua)`WB_Jrr{4x);4<9x8zE7zNA`1M(`uLBzBmU!P z5&SWCT>g$gSel__{M~yXn%^siWU&+7>_#9tKqV0Xb=p0KqI5=cW%Jv{l$i8StPpMn zWrtd0+5yc0troIlBR_4#^FE8tf9Iue5rjN(U>_-7%P`9B1zGcXe-WRw*xFgxHsVzT zq2@2eGr0F6hL{b6l%P)q81HJH6dmt66R%`Hq{DR1W!JPR>$HOrPw)b>~ zrly07*cXi8(+sC66H^nzQ4a>DpyxR(**W+gKvOw66ffnKPbX(&+r3skT_XIO>~WGE zN9IJ+ba3>3uTNA?@W+avy;tfb(clQ?UB9=@`B8*G9lnS$D11x zQvT&%evML9FC?bZ{mWNB3sud`uBb3+{p3e|tbm zojq&6_++JhOMr3DE$#O-#m8Ft{ZUjU>#*-gpTZ^V|CCze73dwr{^vc}a%lP$#9N%s zYmc+w)ISLS91=KO?hTG=Q6>n^3)KP!2$&$#q+i<0sbOWmuz3^!WvOaGxI4vOdtCXT zqWGH+Ql$`-o_g1{ridkMFK9AKT7?{`KKb`~Pgx*$-0XqGr(hR>t8oE|cTwyhVd<;p z%HmJshe5y5MIO)3=FZ{-G(ZW>7ms&0jDqv6!vnLLI=mB!RFd~B6-x;}xH}}$*DDGvmw_**xZv9L&L2Z-2^`%iKVtKZ$;6DG=|K)AtU<8I4s zyrUPook|+z^2KBG<7C#x&&;n+D>{bKcr*oxBP%Ly{mqPssKYiM3bPczp=+E%rZx7s zAJo?2QmI_8Kk%sV+sOqaXOgHHlB5Vt2}+Gf<@h0(j1!1*03hRnTnJ0HoX%|11T*e2 zGjQLJ7gkJSq_!Rv7!tpzyJgYkhz-S+&TxPV_WblLfVedh_a1^nL<%iM9d}%4WMl9j z=MB$KEJ7mw1Lf!$I)*WFa!jP;WG8-&NaVIuF`h_vl7P#2te|ZNz!~kB){JliFPWMWQ5ftEz4LS=d-SHV z_Y~$-KCqihTyiMjOuNx!!YGybbeBtWv!5-u3veC0a(vOzq{5|+$&ONc>Wq1jf`L@% zXFvan0~y7qKeB@$=_->IFHk`Ami_!&-r@Fr3!Lp5=Z)jfl)9X8+Gsn{=5A? z>p3e-ve9S^%vMO*CR0%cAV57O824>sAKu>~Esw@g;?Ex+`V`uvGdwMR%P^= z$WeGC;8G7!antca6PL~&jTOEc%n}nDLfaw7oqcFBvv{abTFaS@vW=Tx*asiBo$+{x(*f9+Cc=Pc|B}6s zE*h^?nx98vhN(9^Y>xQo+NGpwO=+7@Am8qF9p=_c#r^o+&EY zXtX&Ai1vVHYAd-=MJaI zjzG2$WjaJ!BLio^@bVP=64^w@NF;Fca7X@6R?`y{r_SN16u48b#P$d+MUF6`7b4=5 zNK?rNbdMbAv5<4;)fdTsCIZy3XUh{h$MeDmy|~8R=EqPpLBu3^hKAs6qgOYleV%Q1 zx%b+s_f)ejHz-;NG;mtY=6Ez>u%q695^H!pUb)$9gjk)QO;($rZ|n?dEQaY(A_*cq zN8LhV=@N)#g8H}Xl?*E6y{t8zKbb_z@RcwNhJ^wNHJS~c9-oj3 zi;e7Ll!no*Xo}AyhQeq`AGjh*6B|{GNo&v_F&iaP%1Nz=?5f@Fa|}Iwa>*CRx2_lR zyhU6tszw^wn1K>|D&J?CgjMfj;~C{~tk$YtC|-z=uzs)S&0f3SP~5SUoCJi+1Z_Tu zEYgFY*>ZU+Hsr4|>P*mX`^5Agl2RIW;YO$R{MmC(TScBNNrRR)y-&3szCWdp1iU}( zSVagXb(B^d?`|n^#4je2`>nj%40`}Lej#+upn_RS-u zt}$hbh;qJewI5g9^K+AS061}Oef}3zyVdQGHSO`P4pSq*By1nY;lgz&YMYWcH=aNs za5$Tj{(5W2BGhN0MB}X>Y;Z32paUf`jPldrX!!B=y~Kc#s5F~!O!BWDlDlfRo5&Hl zI@aPnlD04z*AFh&G^Y{=gl9v*sb+WkCU_em4`ingCl17OE>(JXXtUqq6u&$@UpixZ zZY|t-E?4Y641(p$a>3A@nf=G_e$O^0>?OPbY%i10kyHc;ZUF|t;GEU6yl0HtqBB@; zDks%Glp~6%Xb4%qIWR{(cC^6Rd6mQxXjWb(FVn$?m}N9D8F9RAAW$gC(H)5pF|vIB z!#fT$P$#isdwT-KF<`==T0zOWcNy3s5?r$+7dr^$cMiWkM$uqE(;>I@2S9dc?i7vSgKVv^?rUhLDx4uuI{&2Ki&R0d;>;;kTlWWj z{*d|1!ZR?PPo1S%Z+{dTb{9b5fI?$BXEg11WIo};BK?J8$^@2J{)A$Yl2$o87|8}a zqSFzXaQr<1iTp}f(-|Kc2?W~%kH_q>`2&F(0&)c-P?XlR%XP^9%09kW+3tS9UOzB< z%}$f7U~FWZ+_V(Ed;fdRQLZmWVKxnU$-PI23y>dP{7EVVWXK#llW~7+bpZ&oxe6$f zBAv?X??3)<_2ii%D)0Hu@2s&VyZ%mV6nyj5tL=kfj*cb=qp^4B6^>jxWp$ zjFqQ=*Y31)`8AgGmKEqLRfK#m2^yUHZJ(#{@c4Ryc3a zJRJ9`d`kQDaq1{DRP{Ky>E@84PIAslNw8*uP5Zh|I)5t-xOYHfFWP2NUN zge_5l<0c+Fh=>aG|&lxEQId0WMP+=ZLR<>9}eNeU;FvlJCNpjoo zADaBb!0M*m;nL@^qgI9~h{sgYw8y4!bw27?<0Grr=-kehlcCuYQYIMo4Dw>^hMSPp z(?*@v-yc@YGKQg<6ZSNVgOA^nu=tB7RlYcPwHOTBtIe2glYx~0l0v>5eE#N=%boBs zq$OPLj)aQrht^24M3Zq|wxAb4%1ns`o<0z83b=W%pnFXc6Vm2Vp$Mo;r1HBD?-@W# zrJ^ULmUtP(D;$EIry!T(hd-HT8}y1bnRg4)U(2LKjj>!3`p|f6V5+K>E?MNkaS`l| ztudE4;Nwa*8(*QQGvI-DGK4kT9Zs}oug;o}+zyKb^-Hb{z6_KHuQilMhpB+wZXvQ7 zFdZ2B19N-9Gr@#hd3(U;WKr|6NWmb52L-sFYxI za5Du2MoLm#GS&8IR$$J68#s@7cMcxm1Kko4G3YUhhwo(#%!E8(=ZU`%nLR#!WB`TnaOM-ZCr)9+HU539TuB?nM7#3{Z0l})cl8@O()cmC{WYzK z4>gRKTrWYFKOv1=44wG^ zcM@+k0Z#w|>%ANN{Oi|1AFb}AC`^IA)^5mSJ4D70vmA+k_Uf}?Z^YHcnZetPv_n?- zNMvk|!ZObCqhOLEiY(UZbihpGUKe*V5$kscMy`}lB?r_)cU?(^Rp`>R(XcV&rn3?` z+G@4oYAfZ695C(%#WMGcKkP%YB#AmS^F;!;H#a=r06N8z4)lSfpFQxgRi{zym%se# z-TU`I+Qni8QHw+wcq(x)IWNCha5$g$K_wQ~h)mmQ5?!-r{P&_(WOuAsG&V=QUqh%ssY8CZt=pcC{ zA{ml`fs`jUr5R}E(P=x+(FLax{%{e(KdBhi}q2m9sABew{1%memtOVIo-|R7%GLi!l z<#I^oRvbwrGV_&FytH;_=)ZD#otb)KEt9Fk^Xr$r-Vj}(y>OdT9%X|0zI*pwT1&B?3M2 z$*k7s)PMW!Z%G(jZDnyH;5P!fIN(sq*A$x*~|O`|E-F6TE6?4y8#qmfha7_K(T_%>(YP2$0wKHhX-MLXIV_0i8Z*g`J2p^6os6t)sra{Hin7RS( z-HfhM;0A02c;PX%+eiYI<6v6T@p!Ik2qC!xZN_>c1th&cD&)hU-yIGHCm4a08Mn5@ z95J!LJJabT8yJgUyVC*R7@6dHh>nr{0lyrbNo33<)Pndn_Itxn>7ErA^2VP~nD!NL z2~-pKARUW5IoDY_L8F%C;{z#>SP_Sa80Gx@0*wXO5ZBJx=|$tAh0ue86K;^fkykY! zTzz2W%(LlCoDl==o5^2&x30vt7}rEM1#1*eZj3s5gng3*CpL#9NYH1kA@zU z6w43`iXtQ)0N{$vq-d0Yi*ZD0T9Uztdy}|vik?`9udbdzTC}?@fCvsDsFmxdud0=^ zZns;jR@&`*bKJ)<0~8WkNBQwUTh_kS4-fCxK9_|6Ov9dmt3wMZ&SVa0&E)_^4Wr1> z%cIE$G!Y&HO=NQHDQH(>6g+V7-`(G(Gnqt6<^N{4<4taX2{jv-9+_Y%LJ{r3-sQ(0 znLW~D?(W~umQ#WXOl!dTM8hTXG~**T3FK0#Twz>eri33N!bMGHLL%SXx(5VdlZ2M3 zmdP5j!N`yUXYwhds+-fE5I?lQf_`v&M0}dAQY^vcL*UPHKi&G~YL>>82sgoGV!FEt zBR#XVn$(_LJ#)A~hz8*%bXeP!P;$nqKq&Fd7MCKQ>e_SC0p)U+5*{`{dKNq&Jx9Pp zRQzx{?hi~BSF9M1&0D@8SeUUn)%7f~!Tmvp4dL^*U$k5O;bcP7AuJ$>Lw-EO!n19J`r`3rrU4RKi``EoFhCyJd9SWUA=zu>h|Fsvl8&xpf_gZ)3a!7 zlI?bbcQVE7?l$@F?l)0*2Ch&*Tgu=0c-gk$9UmJHrDC2UF1!z12%H+-$q3g1pC8~r zI+F&ka3yiX=FEm2g~{vzqxPg$O+S5h z4!v(#avEW>*!yR4BT%e2la>u!cD@vAgcqr`aoNC(V)^po2^J=necmW& zr#GL!FIDyFr!lpR_9Or7`(d=|OOeXwbT#htW^#%j*uY|WOFe|gkI^!`X zD9;NVSi9Z&?z``pl{ru$3%EdlN&!Z=bLbKlODsB7K_tR|@%7iOR)gOWkxaMWVqaX& zZR%poKIi%&XJjC_b0veNft0TG;s6HESR7HIXGxHYBx8B1VDJ|}GAQw&HL>&3N);;R z^lbzs6W6hA7*(FRRiaF(Y+$N&aqsAfacbTYKE+)#0eyVML1t9I~FMa`f$?GiK=UOC?piIplTH6fEb7FW9%mCx3Gf85R$zt>G!E|&ul#BC=-mri7c(2ehfM*;W zFGI%*p0k}jYLS1eCMEEdIs!a6=+&diXiU$JP#@=WyNwn0S^G?I;fAyNGfeRAV!T?y zED#e*)Bu9vn7cpBTuIWr|L_r2VLqz|uU=PSaiKV20y(oUFP{jNy0+O~K+5N{O(vZ! z6tX}!oB@8}tQ=N#l!aJO3WW+w3E(+60B0@cOekCxvordD5dIW8@HX>_`Rw@>CplRP znwrO8giK5zm^o&Q%-Q)l6bW|$i#CqMlp4+_%jMG9S6{tx#tr_?Z+`h#!^ylC94`1p6INrQt0IV_{K4!@geiGn(?%mtS)6 zp_)f*@cR1f)r-$qpRN0CK# z2i=uuF++?)XkVbg+4s%yP)$d<+_@*|e=ND9MfG(hinR;$u3x0fI| zS-#WhTu2Iumjv2NejO+vq(qFS1ne0bVi({o`Oq1evRWGI5$Gv^Iuwh{c5#3Ah@3PM zPW=A&zt8EohOnv;Z96(W&-yS{$TV9vFR0w)om5R!K_ z^uyf`sLI08Bn%S>9Jb_`f-n&Zj;9SEY>GgBi!j|XLGl60Hiymo_wO07l1YsL!t1If ze+sK$Vg}+gw^M4)?~6Ih30OQ(8r^E};3DHvq9hZ|RCpK$C)?`?Ol)`5V$@nu@USvgsGgd7yo&vE0Kpm4Y)$ z^K?ABx_tM;5BW-w5cOy(CeFFViZ_@ys$8tlm_{5tszn6%CBx{WDZ%?HG;(~ohJ`VB zUU5~R${^I2IYFX24uP>XkA}dpc0PYttN0hx_ zg-Ebj21^%tFY4c4tzN!(`4c1poE8YRB#3K4fkOz}Ie{LWy3n4voK6cno4A+g7vj_9 zvQB3c9)jtfp45Quq0s@CV|;lGUb87zPq*7aFO!IBEWCVZ=~QIcALjK8`hFyYC>4vv zT%&on+1M{$KY4lji(miCzy9Vozga_fZ331vJ9}39^}qd3bJ9vB<9lE9_O8`@Z15R% zM;+uJzx>Gyet$Kq=8JhAh=fYdX^6{iwX_Pkq~yK2J5GH@2!v~B#pc5SC=^fUa555M zZA41Vd#y+CEj}X%qQ!2N%Hp8ia!8H)FizugwZgK$m!1Yu6vgAJm%t@X^MYYSVj%6~ zkYqMeZc9Trz^wvQwDr^ZV@ojkO!S&~s!2rm3DkvNo0$ZF7Is(LY$pw~rB@5+X7)=- zCV_@FPVxvmw0tg_R8g6tVqcyw2+tP1PxA;dR;t+~Cu< zm*}KAy*@t{ye-rbS2-FY7F8)3@`b=VB^eZKnz|W6;9>gXlK&>|i1k+I0Mz3Eb?r_o z8Bc(X6|w~=&Hi8rZi>uXOBTc=&RstR#f&2YesWoxIYSnsv$OM)lj6%)u;;MIeOMTA zBg~`kZ*FX|mE41t!JM+GcYc7-xx6gn1+CwAAm1Tl84RF0pS>t;EM~!w%p0Zbax;;T zh^b_s9Jce%UcPMA2mPKcZ6VPE_a=a__`!Vs{%FXipUT8x3b-G?c=MU`$1JGIjp22P zt0{u_alT5tmEYC#I*T?N$xd|XJ1IQ&Ngk2k%NZU|hXo_+ERe~jQ1nsKd2#-P6V#m4 zGleLdTJR9@+!QOH;)^6ws8vRNwxAFBT)}Z%XVDF7;gCvdtBp3UareOXkS}FYdQvYH zx$B@r)u_nxJ9g8bq8}j5iV39!u4Op`A}+bN==SZmTJCxtj0QQ-NGi@`HAZ_C^@eYo zO?hKFKwU%t>Y`p44lV2))VKq9qFu>l49+F+AlM+_5kNhBl&pFvgR^PFwwJjaZEwPK zpf6AqtZ55r+7JPCY!x70-d9lbKxF9rn4{1fjy;tHAC{q}ms3a8QwcqZFNG89B42xY zby=;|!0E=UO`_n#a+5{UFlw87m!51izL9pX11JV85q$oIl*+TbfJ;ZC3Dhne3=9t( zJzTvE9B9`wss<1XQp1UevzU+zqmV=1lGhEaEmM04MhWl=a0lG?M z(f(jwFGCCSSfAET{`dd-w~K1|>%aJ-)A=}PN+=?3+ihSGO9s67iLh-=PAdfnu~uW8 zi0aN_itr{xnfQ$`dBda37LGs%g)EpSP#1vCXgYwGBF|?ueWZMWjoV%Eln1tc zC>*RT1t!Y?ui<>eGzHMOJ4|-rosuS=m{K%HjYhAZUv$hC_db%Rv)UQ@fL`Z*<6rEe zuI-LFD3x#=x)C|z<^Ts{MPop4nl&2i*lZFC0->Fkl~`mNiNw%gZ)Ph!mFCzBc&h?! z3p@m87Z)smO{M6^ogH)!_EaYUPLm(#e|7 z6fWnN8(N`4NfYxKCC&pQois=CCd>fEmg{^Zc~UI2&EDdG>~8{U_s#3CNR9u)cYi<# z#_~lT1L_*D`aE9Bol0L0(IfO`rK4ni@rebKr5JcgI;(cOJt~e;$MOSFV6zI0B@}6k zDiqB!XV@9&fkIa}@93KJmEFa~6WGF(h6j!NWWlZQv#-8Jn+QV283xZG>3jV-&H~Ju zKqN$T%BjP7u2S(9fVHpHPJaEbzXlZ4b%QEfgx2$!Bq=46hiBI%-9U%}`+|it9XP0n z?jZEO`0NE*rEEISVMfR(gg~p?2h*W-$sCVBd0O=jLgP2DU+^W-5&H7Y8}FM7BILng zQoXqT@b1>X-u?WmpOE0R5rrP!IPoV!Gcml1Z9~b*+2Afwb)MHwaSae{264dDDFo7L zd3shu1c#iDh=79>Xeb#?H!T|m6*UO9GA?Sp0Gtg2k61m;FPJZ7v9B^ELmq;G@>b8} zpvM-xiK)?CQA9wqsUvp2?J8YJ51bL_?x@wvB~mj~53YshA|y&G@-Nl>)~`y8(EvV$ zpuuvf$_#x7vRc@vli4k*n$3Ryxl z#ay^>rf}z^$ui`T6QgCa^@k?L4Zg%q_s-p-PnpCNsxmZ)O%@gQzqFQF?p0u7>F};h<9t8yKTsy}ITA z1}GhoQoi3k)a$G&K^a^K7&AjYY$x`Mm*tn&r=UPLH}}}GB4NT8qOC^f?@j{LCpk&UM`h>c=sNY zhN#v7LH%Jb>z+kcEN^_f=zRh~dU(a+7z)MbOWYe6(AJn&0p1sz2{e_SGxS0U_!MX} z94B63MIh0auRr5s?~Vr)LxsmWOS^$_od+2BIabX1*!W)Ce~ zI(@4TlgEz1ISST%d3l9Mih4n5#M~nQl8OfjH3j3MN(9Nk7WkCadYetF zIA4(FoR@0K#z&mUkWkR2eNsNngj53BQU4=Fn#qN@o3BhBv0xB2x^@4@!9KK1r2NBa zM`{FlnZZ$$B?&f4Bw`TXe1)7bmN`aHQ#+{uCrOKXP?A2m?>g-k?A##|gu;r;IxgH~ zr`rYN-1$hyWK}Govmtd0zK(we0ZQ%k^z~0(uQx;@V*S==YQo7`sg%-c^4o8JkMbXy z^zNYnIGQhH*IVGNNg|X7is^SANK>lcqYztPzj*e?@BeUA&|RKiir&h{fmC4}du%;M z6X~(dgR5VhKCj=kF*?0@ea@q(>m_uP-QoQ{2nP|OW{)8=l?)8JPCk|V>h&9(B+b61 z6>HmY6xtaUl!vL;8%JUqp(ZnDdRWi$8Rjjq^KdK`OGPh0SrUEu89>a>Y8RL}n~fIk zEWSLRGi*9Y>(gRWoWm?jXw__9Fv>tObUEqyJW^MxqG3vSRuF}et+GJ&zFQ^IaZY_6 zRlj=}42H8k+D-rS+QpexBvZ3%TjpwZTCUNPdWgE%d!mX8X@UjqumAE_&X|BmUsMfr z%zGYF9Cs-tm1I&}0_DV?o~sAWE_)VYe}ryLEy?p!5NIs z)%AEdiO@s1!@c1XDG37Bc`T|iy&w@xC(~mBK2WX|^aMU~Zs3$kND$70oK`?MXpZ`b z%?&O8#q&3uY9BiFAmMa=rFY+ru&7}Ik>Cj&Y*75vv*&p2xb5lpIKMjC2T=X)C_O+D z1WkipHH_BZ-7T~XB?rsf+Z%qklX8UzkV8mCS>&~-^T0`2`|faYaUq@V))Yx&x7|R_ zhOD!Yqx;KwY&9#@lgf#)mLVFsMjVomf0lCv5qUDDsHVo%$q}Oht zY(Osj{&)W|H7($Tl96Lp{`m3s)tfhQHICB@U=P3l8M=yq-$-lY;h_F_&#mqYC6|0O z_=JLpTyaTp3(*pp+=%7TBkC!$4KFTI*%5w{==;D!O=tW~e7Cuf|UMEe>>g4ok_i3$Ep2&E%Wi z-nW1FekNhu`$HZFlm?O=+BJLatv~b+|M<^_QRHa@ zC=FI$;glc>V#F2+nvIEE(Hwsrzu6l!hERi@lKjHgsK0cuTKoZZz z1|y0>8$mJC*QjrCJLr09wwgxb3Y&g3mT5E|B+8zn!x!e)PO7o)pu?&GX35x2$TI)V z(Clg%l~cJ@UosUT7RB0XjxpK5^5I|@Ls^gofba6OQmNlRkg>B~Ei};eV4N2R9eJzX zeewL7e-lz0e>XNvxZO^_4YS8uii}XQz4tpBc5yXg$FS@*jJ0^-zyP-Hv_HmKC3<1* z8)`-8^Q;;l4|*8tBt%5A7eHJ4($O7?=ov#kE#&{Uqe?`&9F{oRW6bB9C<;W>q7b)n zh0|08yRrBjXGoo}I*=`6gGq6qdG3+mX0#p5`jyK9#-i<%)nu=g3akCA?;J3*#BbUTR`h?Q91(=ad~xt3kT~4!WeYI|r22XxxHl&*&LSmv25iy#D-a zv=pu8z??2(ASy{;R%hIgPHn!UO*x;h6sQM55Gk3XEDa1(Y|`U_!(W|I)Au)zybG%h zpoEG%@uYgf?!|J(8?rzKgHJGqP|b;^^gu*4?Fo9f*>pgj1cCS|0G?UP&?_>&O^;DtH({Q5;S5&7=n z1Ct9wUYr)kSOQlht5vyrPOwU^-BqARLXkNt_njF_g=Y3*>(Gpa$VP)^r1Wwd@ZHuM z$wcCRo$$~gDNc$fqGOy{Gzju7z|Xv#aOd{qYAN1vX+bxu71{afd|I?08^s{;RxdLgO*qD}fB;lazr(JdtM2Ue@ZHu?xs1=s( zXuYy8ubvD{8>WG~Efz}57x>q3(C$aQz8ly?f~q-~dfC7;&2_5o4J4S3c?vDD%A+{qHuLB6{CcNK#{@JV1)VgUsvTmZ2 z7ta>5KRmAS6!G+hg3k8FZG!WY$6}teGfDn@KST!Lm4Jns1P$5IjP3=C0 zDJBZU-dG4oCE>pX9a;LO1T3pk06gS9L zl1kbduy}0Go<6Uh);uHW>QTe$7!E26Wu9CO)>F>Lp%CH?yPoR}&af+1@r zW4_SVVmVROggGM9G;;m&Enr_dsuc1X8LRw^JL!+qD<>zdepix(m;M~jfjK}+)b9{AN7r?&P&o6)ev#5m#j3>dE6s@h1F$oQ$8<6) zS5EmGS&1AQ9m-TqM3_9lW``!`m?U==m$>cVr2?iR6B#t*DiO!@O$aT&ev+6jD6Eqj zsSP%5B-}eJ2L&S!H{NddphK|=tOCQ(CXh|#!ojE@h4Yz_*Ly=;2l#hme#9J0j)_e$ zMM1gZpwb?q8k=TxUc3Wtdwdhvn2e$!P|K3=3KHFI=tnRS%N*v6q@u=~ojT<*M$X{c z;w2`vnD0x=mZ##n@uSAXuL11Gse*qMlOSY08xK+vG{=^ttex<;0Dy*-ZKrz==^2k@ zky&9{N~ZYVB(YhMXgU^FVXfv1()Zi(w8Zsn=IgKzf8B}~zjShvN+x^V_JQ9POYRy0 zZrnI~n#)(MF7|)aDiJ!ahZZy#;335(67TEB#TC-z)0IMjoj6Zq@dlG2wNsSY$hiAfj^9s zEJ&p)es^wL?#zRf%g_NNMTYrYSqY;4lze>fc4nSwnCWlz4^WBCquXr4Igi8WX$IK3F+2n|=+;v_pvd9TRgo-)D1xsPz7QJ>nt3U& zI!vUmZ$~a2Sc!&zm=O>300000NkvXX Hu0mjf=Z3{v literal 0 HcmV?d00001 diff --git a/tests/pytorch/resnet18/resnet18.cc b/tests/pytorch/resnet18/resnet18.cc new file mode 100644 index 000000000..40f34a76b --- /dev/null +++ b/tests/pytorch/resnet18/resnet18.cc @@ -0,0 +1,20 @@ +#include "arg0.inc" +#include "generated-img.inc" +#include "memref.hpp" + +// Declare the resnet C interface. +extern "C" void _mlir_ciface_forward(MemRef *output, + MemRef *arg0, + MemRef *input); + +static const int32_t sizes[2] = {1, 1000}; +__attribute((section(".vdata"))) float output_float_1[1000]; +MemRef output(output_float_1, sizes); + +MemRef inputResize(IMAGE, IMAGE_SIZES); +MemRef paramsContainer(PARAMS, PARAMS_SIZES); + +extern "C" int test() { + _mlir_ciface_forward(&output, ¶msContainer, &inputResize); + return 0; +} From 2e63c0fc938036b817ea2d023a67bf2026aa252f Mon Sep 17 00:00:00 2001 From: Avimitin Date: Wed, 26 Mar 2025 22:40:40 +0800 Subject: [PATCH 06/24] [tests] fix 32-bit ABI and ensure C++ compilation safety Signed-off-by: Avimitin --- tests/builder.nix | 3 +++ tests/pytorch/lib/MemrefCopy.cc | 24 ++++++++++++------------ tests/pytorch/resnet18/build.nix | 4 ++++ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/tests/builder.nix b/tests/builder.nix index 3b1a9b9ae..aeffd6929 100644 --- a/tests/builder.nix +++ b/tests/builder.nix @@ -44,6 +44,9 @@ let "-static" "-mcmodel=medany" "-fvisibility=hidden" + "-fno-exceptions" + "-fno-rtti" + "-fno-threadsafe-statics" "-fno-PIC" "-g" "-O3" diff --git a/tests/pytorch/lib/MemrefCopy.cc b/tests/pytorch/lib/MemrefCopy.cc index c7f15ff79..42e2fe3a9 100644 --- a/tests/pytorch/lib/MemrefCopy.cc +++ b/tests/pytorch/lib/MemrefCopy.cc @@ -24,38 +24,38 @@ template struct UnrankedMemRefType { template struct StridedMemRefType { T *basePtr; T *data; - int64_t offset; - int64_t sizes[N]; - int64_t strides[N]; + int32_t offset; + int32_t sizes[N]; + int32_t strides[N]; }; /// StridedMemRef descriptor type specialized for rank 1. template struct StridedMemRefType { T *basePtr; T *data; - int64_t offset; - int64_t sizes[1]; - int64_t strides[1]; + int32_t offset; + int32_t sizes[1]; + int32_t strides[1]; - T &operator[](int64_t idx) { return *(data + offset + idx * strides[0]); } + T &operator[](int32_t idx) { return *(data + offset + idx * strides[0]); } }; /// StridedMemRef descriptor type specialized for rank 0. template struct StridedMemRefType { T *basePtr; T *data; - int64_t offset; + int32_t offset; }; // A reference to one of the StridedMemRef types. template class DynamicMemRefType { public: - int64_t rank; + int32_t rank; T *basePtr; T *data; - int64_t offset; - const int64_t *sizes; - const int64_t *strides; + int32_t offset; + const int32_t *sizes; + const int32_t *strides; explicit DynamicMemRefType(const StridedMemRefType &memRef) : rank(0), basePtr(memRef.basePtr), data(memRef.data), diff --git a/tests/pytorch/resnet18/build.nix b/tests/pytorch/resnet18/build.nix index 6a52c55d4..e4da49008 100644 --- a/tests/pytorch/resnet18/build.nix +++ b/tests/pytorch/resnet18/build.nix @@ -42,10 +42,14 @@ buildBuddyE2ETest { -tensor-bufferize \ -linalg-bufferize \ -finalizing-bufferize \ + -batchmatmul-optimize \ -convert-linalg-to-loops \ -lower-affine \ -convert-scf-to-cf \ -llvm-request-c-wrappers \ + -lower-vector-exp \ + -lower-rvv=rv32 \ + -convert-vector-to-llvm \ -convert-math-to-llvm \ -convert-math-to-libm \ -convert-arith-to-llvm \ From f84b377801cfcf90d0e985b86ca064cecaf54897 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Thu, 27 Mar 2025 15:22:17 +0800 Subject: [PATCH 07/24] [WIP] sync Container implementation from upstream Signed-off-by: Avimitin --- tests/pytorch/include/buddy/Core/Container.h | 144 ++++++++++++ tests/pytorch/include/img.hpp | 234 ------------------- tests/pytorch/include/memref.hpp | 80 ------- tests/pytorch/lib/MemrefCopy.cc | 1 + tests/pytorch/resnet18/build.nix | 13 +- tests/pytorch/resnet18/resnet18.cc | 22 +- 6 files changed, 169 insertions(+), 325 deletions(-) create mode 100644 tests/pytorch/include/buddy/Core/Container.h delete mode 100644 tests/pytorch/include/img.hpp delete mode 100644 tests/pytorch/include/memref.hpp diff --git a/tests/pytorch/include/buddy/Core/Container.h b/tests/pytorch/include/buddy/Core/Container.h new file mode 100644 index 000000000..5222719c8 --- /dev/null +++ b/tests/pytorch/include/buddy/Core/Container.h @@ -0,0 +1,144 @@ +//===- Container.h --------------------------------------------------------===// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//===----------------------------------------------------------------------===// +// +// Container descriptor. +// +//===----------------------------------------------------------------------===// +// +//===----------------------------------------------------------------------===// +// +// This is vendored version of buddy-mlir Container.h file at revision +// c57584a0e3c38e938a3902320f62b202ced84996. Modified for T1 embedded test env. +// +//===----------------------------------------------------------------------===// + +#include +#include + +// MemRef descriptor. +// - T represents the type of the elements. +// - N represents the number of dimensions. +// - The storage order is NCHW. +template class MemRef { +public: + // Construct using init to allocated area + MemRef(T *allocated, T init, intptr_t sizes[N]); + MemRef(T *allocated, intptr_t sizes[N], intptr_t offset = 0); + // Get the data pointer. + T *getData(); + // Get the sizes (shape). + const intptr_t *getSizes() { return sizes; } + // Get the strides. + const intptr_t *getStrides() { return strides; } + // Get the rank of the memref. + size_t getRank() const { return N; } + // Get the size (number of elements). + size_t getSize() const { return product(this->sizes); } + // Get the element at index. + const T &operator[](size_t index) const; + T &operator[](size_t index); + +protected: + // Set the strides. + // Computes the strides of the transposed tensor for transpose=true. + void setStrides(); + // Compute the product of array elements. + size_t product(const intptr_t sizes[N]) const; + + // Data. + // The `aligned` and `allocated` members point to the same address, `aligned` + // member is responsible for handling data, and `allocated` member is + // resposible for handling the memory space. + T *allocated = nullptr; + T *aligned = nullptr; + // Offset. + intptr_t offset = 0; + // Shape. + intptr_t sizes[N]; + // Strides. + intptr_t strides[N]; +}; + +template +MemRef::MemRef(T *allocated, T init, intptr_t sizes[N]) + : MemRef(allocated, sizes) { + size_t size = product(sizes); + std::fill(aligned, aligned + size, init); +} + +// MemRef Array Constructor. +// Construct a MemRef object from the data pointer, sizes, and offset. +// The default offset is 0. +template +MemRef::MemRef(T *data, intptr_t sizes[N], intptr_t offset) { + this->offset = offset; + for (size_t i = 0; i < N; i++) { + this->sizes[i] = sizes[i]; + } + setStrides(); + size_t size = product(sizes); + allocated = data; + aligned = allocated; + for (size_t i = 0; i < size; i++) { + aligned[i] = data[i]; + } +} + +// Get the data pointer. +// Return the `aligned` pointer if the container data size is greater than zero. +// If the data size is negative or zero, which means no space is allocated for +// the container data pointer, the function does not allow to return the data +// pointer. +template T *MemRef::getData() { + size_t size = product(this->sizes); + return aligned; +} + +// Get the element at index. +// Return the specific element if the container data size is greater than zero. +// If the data size is negative or zero, which means no space is allocated for +// the container data pointer, this operator does not allow to return the data +// element. +template +const T &MemRef::operator[](size_t index) const { + size_t size = product(this->sizes); + return aligned[index + offset]; +} + +template T &MemRef::operator[](size_t index) { + size_t size = product(this->sizes); + return aligned[index + offset]; +} + +// Calculate the stride values for each dimension based on the sizes. +template void MemRef::setStrides() { + strides[N - 1] = 1; + if (N < 2) + return; + // Prevent implicit conversions between unsigned and signed + for (std::size_t i = N - 1; i > 0; i--) { + strides[i - 1] = strides[i] * sizes[i]; + } +} + +// Calculate the total number of elements in the MemRef container. +template +size_t MemRef::product(const intptr_t sizes[N]) const { + size_t size = 1; + for (size_t i = 0; i < N; i++) + size *= sizes[i]; + return size; +} diff --git a/tests/pytorch/include/img.hpp b/tests/pytorch/include/img.hpp deleted file mode 100644 index f839df342..000000000 --- a/tests/pytorch/include/img.hpp +++ /dev/null @@ -1,234 +0,0 @@ -//===- ImgContainer.h -----------------------------------------------------===// -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//===----------------------------------------------------------------------===// -// -// original source from https://github.com/buddy-compiler/buddy-mlir -// Modified for embedded environment. -// -//===----------------------------------------------------------------------===// - -#ifndef IMG_H -#define IMG_H - -#include "memref.hpp" - -enum ImageModes { - DIP_GRAYSCALE = 0, - DIP_RGB = 1, -}; - -template class Image : public MemRef { -public: - // Constructor initializes the image by loading from a file. - // Params: - // file: Raw data to image file memory - // filesize: size of the file memory - // buffer: pre-allocated space for image modification - // mode: Specifies the image mode (e.g., DIP_GRAYSCALE, DIP_RGB). - // norm: Indicates whether to normalize pixel values (default is false). - constexpr Image(const char *file, int32_t filesize, T *buffer, - ImageModes mode, bool norm = false); - - // Overload - constexpr Image(T *data, const int32_t sizes[N]); - constexpr Image(T *data, T init, const int32_t sizes[N]); - - // Retrieves the name of the current image format as a string. - inline const char *getFormatName() const { - switch (this->imageFormat) { - case ImageFormat::BMP: - return "BMP"; - default: - return "Unsupported format"; - } - } - // Returns the width of the image in pixels. - inline size_t getWidth() const { return this->width; } - // Returns the height of the image in pixels. - inline size_t getHeight() const { return this->height; } - // Returns the bit depth of the image. - inline int getBitDepth() const { return this->bitDepth; } - -private: - // Enum to represent supported image formats. - enum class ImageFormat { - BMPDecodeError, // Represents an error or unsupported format. - BMP, // BMP file format. - Unsupported, - } imageFormat; - // Mode of the image (e.g., DIP_GRAYSCALE, DIP_RGB). - ImageModes imageMode; - // Width of the image in pixels. - int32_t width; - // Height of the image in pixels. - int32_t height; - // Bit depth of the image. - int32_t bitDepth; - // Normalization flag. - bool isNorm; - // Determines the image format from raw file data. - inline void determineFormat(const uint8_t *fileData, uint32_t filesize); - // Decodes a BMP image from raw file data. - inline bool decodeBMP(const uint8_t *fileData, uint32_t filesize); -}; - -template -constexpr Image::Image(T *data, const int32_t sizes[N]) - : MemRef(data, sizes){}; - -template -constexpr Image::Image(T *data, T init, const int32_t sizes[N]) - : MemRef(data, init, sizes){}; - -// Image Container Constructor -// Constructs an image container object from the image file path. -template -constexpr Image::Image(const char *file, int32_t filesize, T *buffer, - ImageModes mode, bool norm) - : imageMode(mode), isNorm(norm) { - - this->allocated = buffer; - this->aligned = this->allocated; - - determineFormat(file, filesize); - if (this->imageFormat == ImageFormat::BMP) { - bool success = decodeBMP(file, filesize); - if (!success) { - this->imageFormat = ImageFormat::BMPDecodeError; - }; - } else { - this->imageFormat = ImageFormat::Unsupported; - } -} - -// Determines the image format by inspecting the header of the file data. -template -inline void Image::determineFormat(const uint8_t *fileData, - uint32_t filesize) { - if (filesize > 2 && fileData[0] == 'B' && fileData[1] == 'M') { - this->imageFormat = ImageFormat::BMP; - } else { - this->imageFormat = ImageFormat::BMPDecodeError; - } -} - -// BMP Image File Decoder -template -inline bool Image::decodeBMP(const uint8_t *fileData, uint32_t filesize) { - // Check if the provided data is large enough to contain a minimal BMP header - // (54 bytes). - if (filesize < 54) { - return false; - } - - // Extract image information from BMP header - this->width = *reinterpret_cast(&fileData[18]); - this->height = *reinterpret_cast(&fileData[22]); - this->bitDepth = *reinterpret_cast(&fileData[28]); - uint32_t compression = *reinterpret_cast(&fileData[30]); - size_t pixelDataOffset = *reinterpret_cast(&fileData[10]); - - // Currently, only the BI_RGB (value 0) compression method is supported. - if (compression != 0) { - return false; - } - - // Currently, only the NCHW format with 4 dimensions is supported. - if (N == 4) { - if (this->imageMode == ImageModes::DIP_GRAYSCALE) { - // TODO: Add batch setting. - this->sizes[0] = 1; - this->sizes[1] = 1; - this->sizes[2] = this->height; - this->sizes[3] = this->width; - this->setStrides(); - size_t size = this->product(this->sizes); - // Fullfill data to memref container. - size_t memrefIndex = 0; - if (this->bitDepth == 32) { - // BMP file is upside-down storage. - for (size_t i = this->height; i > 0; i--) { - for (size_t j = 0; j < this->width; j++) { - // Locate the current pixel. - size_t pixelIndex = - pixelDataOffset + (((i - 1) * this->width) + j) * 4; - // Extract the blue, green, and red value from the current pixel. - int bluePixel = - *reinterpret_cast(&fileData[pixelIndex]); - int greenPixel = - *reinterpret_cast(&fileData[pixelIndex + 1]); - int redPixel = - *reinterpret_cast(&fileData[pixelIndex + 2]); - // Calculate the gray scale value. - int grayScaleValue = static_cast( - 0.299 * redPixel + 0.587 * greenPixel + 0.114 * bluePixel); - // Store the gray scale value into memref container. - this->aligned[memrefIndex] = - this->isNorm ? static_cast(grayScaleValue) / 255 - : static_cast(grayScaleValue); - memrefIndex++; - } - } - } else { - return false; - } - } else if (this->imageMode == ImageModes::DIP_RGB) { - // TODO: Add batch setting. - this->sizes[0] = 1; - this->sizes[1] = 3; - this->sizes[2] = this->height; - this->sizes[3] = this->width; - this->setStrides(); - size_t size = this->product(this->sizes); - // Fullfill data to memref container. - size_t memrefIndex = 0; - size_t colorStride = this->height * this->width; - if (this->bitDepth == 32) { - // BMP file is upside-down storage. - for (size_t i = height; i > 0; i--) { - for (size_t j = 0; j < width; j++) { - // Locate the current pixel. - size_t pixelIndex = pixelDataOffset + (((i - 1) * width) + j) * 4; - // Extract the blue, green, and red value from the current pixel. - int bluePixel = - *reinterpret_cast(&fileData[pixelIndex]); - int greenPixel = - *reinterpret_cast(&fileData[pixelIndex + 1]); - int redPixel = - *reinterpret_cast(&fileData[pixelIndex + 2]); - // Store the values into memref container as RGB order. (BGR -> RGB) - this->aligned[memrefIndex] = this->isNorm - ? static_cast(redPixel) / 255 - : static_cast(redPixel); - this->aligned[memrefIndex + colorStride] = - this->isNorm ? static_cast(greenPixel) / 255 - : static_cast(greenPixel); - this->aligned[memrefIndex + 2 * colorStride] = - this->isNorm ? static_cast(bluePixel) / 255 - : static_cast(bluePixel); - memrefIndex++; - } - } - } else { - return false; - } - } - } else { - return false; - } - return true; -} - -#endif // IMG_H diff --git a/tests/pytorch/include/memref.hpp b/tests/pytorch/include/memref.hpp deleted file mode 100644 index 95e6725f2..000000000 --- a/tests/pytorch/include/memref.hpp +++ /dev/null @@ -1,80 +0,0 @@ -//===- ImgContainer.h -----------------------------------------------------===// -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//===----------------------------------------------------------------------===// -// -// Original source from https://github.com/buddy-compiler/buddy-mlir. -// Modified for embedded environment. -// -//===----------------------------------------------------------------------===// - -#ifndef MEMREF_H -#define MEMREF_H - -#include -#include - -template class MemRef { -public: - constexpr MemRef(T *data, const int32_t sizes[N]); - constexpr MemRef(T *data, T init, const int32_t sizes[N]); - -protected: - inline void setStrides(); - - // https://github.com/llvm/llvm-project/blob/a50b9633357007ff886f3fd228ca4b8a9b9b9852/mlir/lib/Conversion/LLVMCommon/TypeConverter.cpp#L401 - T *allocated = nullptr; - T *aligned = nullptr; - int32_t offset = 0; - int32_t sizes[N]; - int32_t strides[N]; -}; - -template -constexpr MemRef::MemRef(T *data, const int32_t sizes[N]) { - for (size_t i = 0; i < N; i++) { - this->sizes[i] = sizes[i]; - } - - setStrides(); - - allocated = data; - aligned = data; -} - -template -constexpr MemRef::MemRef(T *data, T init, const int32_t sizes[N]) - : MemRef(data, sizes) { - - int32_t total_size = 0; - for (size_t i = 0; i < N; i++) { - total_size += sizes[i]; - } - - for (int32_t i = 0; i < total_size; i++) { - aligned[i] = init; - } -} - -template inline void MemRef::setStrides() { - strides[N - 1] = 1; - if (N < 2) - return; - - for (std::size_t i = N - 1; i > 0; i--) { - strides[i - 1] = strides[i] * sizes[i]; - } -} - -#endif // MEMREF_H diff --git a/tests/pytorch/lib/MemrefCopy.cc b/tests/pytorch/lib/MemrefCopy.cc index 42e2fe3a9..073333331 100644 --- a/tests/pytorch/lib/MemrefCopy.cc +++ b/tests/pytorch/lib/MemrefCopy.cc @@ -75,6 +75,7 @@ template class DynamicMemRefType { } }; +// TODO: can we vectorize this extern "C" void memrefCopy(int32_t elemSize, UnrankedMemRefType *srcArg, UnrankedMemRefType *dstArg) { DynamicMemRefType src(*srcArg); diff --git a/tests/pytorch/resnet18/build.nix b/tests/pytorch/resnet18/build.nix index e4da49008..eb7296129 100644 --- a/tests/pytorch/resnet18/build.nix +++ b/tests/pytorch/resnet18/build.nix @@ -23,12 +23,17 @@ buildBuddyE2ETest { "builtin.module(func.func(tosa-to-linalg-named, tosa-to-linalg, tosa-to-tensor, tosa-to-arith), \ empty-tensor-to-alloc-tensor, convert-elementwise-to-linalg, arith-bufferize, \ func.func(linalg-bufferize, tensor-bufferize), func-bufferize)" \ + | buddy-opt \ + -lower-vector-exp \ + -lower-rvv=rv32 \ + -convert-vector-to-llvm \ | buddy-opt -pass-pipeline \ "builtin.module(func.func(buffer-deallocation-simplification, convert-linalg-to-loops), \ eliminate-empty-tensors, func.func(llvm-request-c-wrappers), \ convert-math-to-llvm, convert-math-to-libm, convert-scf-to-cf, \ convert-arith-to-llvm, expand-strided-metadata, finalize-memref-to-llvm, \ convert-func-to-llvm, reconcile-unrealized-casts)" \ + | sed 's|i64|i32|g' \ > forward-lowered.mlir echo "Lowering subgraph0.mlir" @@ -45,11 +50,11 @@ buildBuddyE2ETest { -batchmatmul-optimize \ -convert-linalg-to-loops \ -lower-affine \ - -convert-scf-to-cf \ - -llvm-request-c-wrappers \ -lower-vector-exp \ -lower-rvv=rv32 \ -convert-vector-to-llvm \ + -convert-scf-to-cf \ + -llvm-request-c-wrappers \ -convert-math-to-llvm \ -convert-math-to-libm \ -convert-arith-to-llvm \ @@ -57,6 +62,7 @@ buildBuddyE2ETest { -expand-strided-metadata \ -finalize-memref-to-llvm \ -reconcile-unrealized-casts \ + | sed 's|i64|i32|g' \ > subgraph0-lowered.mlir echo "Compiling memrefCopy library" @@ -73,5 +79,8 @@ buildBuddyE2ETest { "forward-lowered.mlir" "subgraph0-lowered.mlir" ) + + mkdir -p "$out"/share + cp -v ''${optArtifacts[*]} "$out"/share/ ''; } diff --git a/tests/pytorch/resnet18/resnet18.cc b/tests/pytorch/resnet18/resnet18.cc index 40f34a76b..a77efdaf6 100644 --- a/tests/pytorch/resnet18/resnet18.cc +++ b/tests/pytorch/resnet18/resnet18.cc @@ -1,20 +1,24 @@ -#include "arg0.inc" -#include "generated-img.inc" -#include "memref.hpp" +#include // Declare the resnet C interface. extern "C" void _mlir_ciface_forward(MemRef *output, MemRef *arg0, MemRef *input); -static const int32_t sizes[2] = {1, 1000}; -__attribute((section(".vdata"))) float output_float_1[1000]; -MemRef output(output_float_1, sizes); - -MemRef inputResize(IMAGE, IMAGE_SIZES); -MemRef paramsContainer(PARAMS, PARAMS_SIZES); +__attribute((section(".vdata"))) float output_float_1[1000] = {0}; +__attribute((section(".vdata"))) float IMAGE[300] = {0}; +__attribute((section(".vdata"))) float PARAMS[100] = {0}; extern "C" int test() { + static int32_t sizes[2] = {1, 1000}; + static int32_t params_sizes[1] = {100}; + static int32_t image_sizes[4] = {1, 3, 100, 100}; + + MemRef output(output_float_1, sizes); + + MemRef inputResize(IMAGE, image_sizes); + MemRef paramsContainer(PARAMS, params_sizes); + _mlir_ciface_forward(&output, ¶msContainer, &inputResize); return 0; } From 6c47c94c38957bf531842cfd1db64719131e93cc Mon Sep 17 00:00:00 2001 From: Avimitin Date: Thu, 27 Mar 2025 15:22:31 +0800 Subject: [PATCH 08/24] [WIP] add spike debugger command line wrapper Signed-off-by: Avimitin --- nix/overlay.nix | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/nix/overlay.nix b/nix/overlay.nix index abfbddb47..4a3b3fe54 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -153,4 +153,28 @@ rec { t1 = final.callPackage ./t1 { }; buddy-codegen = final.callPackage ./pkgs/buddy-codegen { }; + + spike-t1 = final.writeShellApplication { + name = "spike-t1"; + + runtimeInputs = with final; [ + pkgsCross.riscv32-embedded.buildPackages.gcc + spike + dtc + ]; + + text = '' + elf=''${1:-} + if [[ -z "$elf" ]]; then + echo "Require argument to find elf" >&2 + exit 1 + fi + + spike -d --isa=rv32gcv_zvl2048b_zve32f \ + --priv=m \ + -m0x20000000:0x20000000,0x00000000:0x20000000,0x40000000:0x80000000,0xc0000000:0x40000000 \ + --pc=0x"$(grep '<_start>' <(riscv32-none-elf-objdump -d "$elf") | cut -d' ' -f1)" \ + "$elf" + ''; + }; } From eb32d81d28ff80453e452053c557e7c5a14f5c11 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Mon, 7 Apr 2025 14:01:16 +0800 Subject: [PATCH 09/24] [buddy-mlir] fix llvm malloc type declare Signed-off-by: Avimitin --- .../llvm/03-buddy-llvm-fix-malloc-type.patch | 13 +++++++++++++ nix/pkgs/buddy-llvm.nix | 3 +++ 2 files changed, 16 insertions(+) create mode 100644 nix/patches/llvm/03-buddy-llvm-fix-malloc-type.patch diff --git a/nix/patches/llvm/03-buddy-llvm-fix-malloc-type.patch b/nix/patches/llvm/03-buddy-llvm-fix-malloc-type.patch new file mode 100644 index 000000000..564001484 --- /dev/null +++ b/nix/patches/llvm/03-buddy-llvm-fix-malloc-type.patch @@ -0,0 +1,13 @@ +diff --git a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp +index 7312388bc9b4..2d72a32fca57 100644 +--- a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp ++++ b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp +@@ -1321,7 +1321,7 @@ prepareLLVMModule(Operation *m, llvm::LLVMContext &llvmContext, + // memref allocation/deallocation coming from standard ops lowering. + llvm::IRBuilder<> builder(llvmContext); + llvmModule->getOrInsertFunction("malloc", builder.getInt8PtrTy(), +- builder.getInt64Ty()); ++ builder.getInt32Ty()); + llvmModule->getOrInsertFunction("free", builder.getVoidTy(), + builder.getInt8PtrTy()); + diff --git a/nix/pkgs/buddy-llvm.nix b/nix/pkgs/buddy-llvm.nix index 690a7fe62..dfa8d2f25 100644 --- a/nix/pkgs/buddy-llvm.nix +++ b/nix/pkgs/buddy-llvm.nix @@ -23,6 +23,9 @@ stdenv.mkDerivation rec { rev = version; hash = "sha256-bMJJ2q1hSh7m0ewclHOmIe7lOHv110rz/P7D3pw8Uiw="; }; + patches = [ + ../patches/llvm/03-buddy-llvm-fix-malloc-type.patch + ]; requiredSystemFeatures = [ "big-parallel" ]; From d19c46a11ce7858f0d8db7d0fa856348f448176a Mon Sep 17 00:00:00 2001 From: Avimitin Date: Mon, 7 Apr 2025 18:01:28 +0800 Subject: [PATCH 10/24] [nix] format buddy-codegen Signed-off-by: Avimitin --- nix/pkgs/buddy-codegen/default.nix | 33 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/nix/pkgs/buddy-codegen/default.nix b/nix/pkgs/buddy-codegen/default.nix index 19127c067..caca78327 100644 --- a/nix/pkgs/buddy-codegen/default.nix +++ b/nix/pkgs/buddy-codegen/default.nix @@ -1,21 +1,28 @@ -{ lib -, stdenv -, argparse -, buddy-mlir -, libpng +{ + lib, + stdenv, + argparse, + buddy-mlir, + libpng, }: stdenv.mkDerivation { name = "buddy-codegen"; - src = with lib.fileset; toSource { - fileset = unions [ - ./dip.mlir - ./buddy-codegen.cc - ]; - root = ./.; - }; + src = + with lib.fileset; + toSource { + fileset = unions [ + ./dip.mlir + ./buddy-codegen.cc + ]; + root = ./.; + }; - buildInputs = [ libpng argparse buddy-mlir ]; + buildInputs = [ + libpng + argparse + buddy-mlir + ]; env.NIX_CFLAGS_COMPILE = toString [ # TODO: BMP is now broken From c53ef8269d6e61392a55cd9f9ce8e62fa54e29ef Mon Sep 17 00:00:00 2001 From: Avimitin Date: Mon, 7 Apr 2025 19:30:04 +0800 Subject: [PATCH 11/24] [tests] fix resnet18 build Signed-off-by: Avimitin --- tests/pytorch/resnet18/build.nix | 73 ++++++++++++++---------------- tests/pytorch/resnet18/resnet18.cc | 6 +-- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/tests/pytorch/resnet18/build.nix b/tests/pytorch/resnet18/build.nix index eb7296129..ecbb873d2 100644 --- a/tests/pytorch/resnet18/build.nix +++ b/tests/pytorch/resnet18/build.nix @@ -1,5 +1,6 @@ -{ fetchurl -, buildBuddyE2ETest +{ + fetchurl, + buildBuddyE2ETest, }: let checkpointFile = "resnet18-f37072fd.pth"; @@ -20,57 +21,53 @@ buildBuddyE2ETest { echo "Lowering forward.mlir" buddy-opt forward.mlir -pass-pipeline \ - "builtin.module(func.func(tosa-to-linalg-named, tosa-to-linalg, tosa-to-tensor, tosa-to-arith), \ + "builtin.module(func.func(tosa-to-linalg-named, tosa-to-linalg, tosa-to-tensor, tosa-to-arith{use-32-bit}), \ empty-tensor-to-alloc-tensor, convert-elementwise-to-linalg, arith-bufferize, \ - func.func(linalg-bufferize, tensor-bufferize), func-bufferize)" \ - | buddy-opt \ - -lower-vector-exp \ - -lower-rvv=rv32 \ - -convert-vector-to-llvm \ + func.func(linalg-bufferize, tensor-bufferize), func-bufferize, convert-vector-to-llvm)" \ | buddy-opt -pass-pipeline \ "builtin.module(func.func(buffer-deallocation-simplification, convert-linalg-to-loops), \ eliminate-empty-tensors, func.func(llvm-request-c-wrappers), \ convert-math-to-llvm, convert-math-to-libm, convert-scf-to-cf, \ - convert-arith-to-llvm, expand-strided-metadata, finalize-memref-to-llvm, \ - convert-func-to-llvm, reconcile-unrealized-casts)" \ - | sed 's|i64|i32|g' \ + convert-arith-to-llvm{index-bitwidth=32}, convert-func-to-llvm{index-bitwidth=32}, \ + expand-strided-metadata, finalize-memref-to-llvm{index-bitwidth=32}, \ + convert-func-to-llvm{index-bitwidth=32}, reconcile-unrealized-casts)" \ > forward-lowered.mlir echo "Lowering subgraph0.mlir" buddy-opt subgraph0.mlir -pass-pipeline \ - "builtin.module(func.func(tosa-to-linalg-named, tosa-to-arith, tosa-to-linalg, tosa-to-tensor))" \ + "builtin.module(func.func(tosa-to-linalg-named, tosa-to-arith{use-32-bit}, tosa-to-linalg, tosa-to-tensor))" \ | buddy-opt \ - -convert-elementwise-to-linalg \ - -func-bufferize-dynamic-offset \ - -arith-bufferize \ - -func-bufferize \ - -tensor-bufferize \ - -linalg-bufferize \ - -finalizing-bufferize \ - -batchmatmul-optimize \ - -convert-linalg-to-loops \ - -lower-affine \ - -lower-vector-exp \ - -lower-rvv=rv32 \ - -convert-vector-to-llvm \ - -convert-scf-to-cf \ - -llvm-request-c-wrappers \ - -convert-math-to-llvm \ - -convert-math-to-libm \ - -convert-arith-to-llvm \ - -convert-func-to-llvm \ - -expand-strided-metadata \ - -finalize-memref-to-llvm \ - -reconcile-unrealized-casts \ - | sed 's|i64|i32|g' \ + --convert-elementwise-to-linalg \ + --eliminate-empty-tensors \ + --empty-tensor-to-alloc-tensor \ + --one-shot-bufferize \ + --func-bufferize-dynamic-offset \ + --tensor-bufferize \ + --arith-bufferize \ + --buffer-deallocation \ + --finalizing-bufferize \ + --convert-linalg-to-affine-loops \ + --affine-loop-fusion \ + --lower-affine \ + --expand-strided-metadata \ + --convert-vector-to-scf \ + --convert-scf-to-cf \ + --memref-expand \ + --llvm-request-c-wrappers \ + --lower-vector-exp \ + --lower-rvv=rv32 \ + --convert-vector-to-llvm \ + --convert-math-to-llvm \ + --convert-arith-to-llvm=index-bitwidth=32 \ + --convert-index-to-llvm=index-bitwidth=32 \ + --convert-func-to-llvm=index-bitwidth=32 \ + --finalize-memref-to-llvm=index-bitwidth=32 \ + --reconcile-unrealized-casts \ > subgraph0-lowered.mlir echo "Compiling memrefCopy library" $CXX -nostdlib -c ${../lib/MemrefCopy.cc} -o memrefCopy.o - buddy-codegen arg -i arg0.data -o arg0.inc -s 11699112 - buddy-codegen img -i ${./dog-224_224.png} -o generated-img.inc - llcArtifacts+=( memrefCopy.o ) diff --git a/tests/pytorch/resnet18/resnet18.cc b/tests/pytorch/resnet18/resnet18.cc index a77efdaf6..460ea3a92 100644 --- a/tests/pytorch/resnet18/resnet18.cc +++ b/tests/pytorch/resnet18/resnet18.cc @@ -5,9 +5,9 @@ extern "C" void _mlir_ciface_forward(MemRef *output, MemRef *arg0, MemRef *input); -__attribute((section(".vdata"))) float output_float_1[1000] = {0}; -__attribute((section(".vdata"))) float IMAGE[300] = {0}; -__attribute((section(".vdata"))) float PARAMS[100] = {0}; +__attribute((section(".vdata"))) float output_float_1[1000]; +__attribute((section(".vdata"))) float IMAGE[300]; +__attribute((section(".vdata"))) float PARAMS[100]; extern "C" int test() { static int32_t sizes[2] = {1, 1000}; From 7f198bf859124a717222d8f3bbae86b17a80bfeb Mon Sep 17 00:00:00 2001 From: Avimitin Date: Mon, 7 Apr 2025 19:30:14 +0800 Subject: [PATCH 12/24] [tests] fix lenet build Signed-off-by: Avimitin --- tests/pytorch/lenet/build.nix | 40 ++++++++++++++++++----------------- tests/pytorch/lenet/lenet.cc | 18 ++++++++-------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/tests/pytorch/lenet/build.nix b/tests/pytorch/lenet/build.nix index baabc0454..31357cc4d 100644 --- a/tests/pytorch/lenet/build.nix +++ b/tests/pytorch/lenet/build.nix @@ -14,43 +14,42 @@ buildBuddyE2ETest { echo "Lowering forward.mlir" buddy-opt forward.mlir -pass-pipeline \ - "builtin.module(func.func(tosa-to-linalg-named, tosa-to-linalg, tosa-to-tensor, tosa-to-arith), \ + "builtin.module(func.func(tosa-to-linalg-named, tosa-to-linalg, tosa-to-tensor, tosa-to-arith{use-32-bit}), \ empty-tensor-to-alloc-tensor, convert-elementwise-to-linalg, arith-bufferize, \ func.func(linalg-bufferize, tensor-bufferize), func-bufferize)" \ | buddy-opt -pass-pipeline \ "builtin.module(func.func(buffer-deallocation-simplification, convert-linalg-to-loops), \ eliminate-empty-tensors, func.func(llvm-request-c-wrappers), \ - convert-math-to-llvm, convert-math-to-libm, convert-scf-to-cf, \ - convert-arith-to-llvm, expand-strided-metadata, finalize-memref-to-llvm, \ - convert-func-to-llvm, reconcile-unrealized-casts)" \ + convert-math-to-llvm, convert-scf-to-cf, \ + convert-arith-to-llvm{index-bitwidth=32}, expand-strided-metadata, finalize-memref-to-llvm{index-bitwidth=32}, \ + convert-func-to-llvm{index-bitwidth=32}, reconcile-unrealized-casts)" \ > forward-lowered.mlir echo "Lowering subgraphs[0]" buddy-opt subgraphs0.mlir -pass-pipeline \ - "builtin.module(func.func(tosa-to-linalg-named, tosa-to-arith, tosa-to-linalg, tosa-to-tensor))" \ + "builtin.module(func.func(tosa-to-linalg-named, tosa-to-arith{use-32-bit}, tosa-to-linalg, tosa-to-tensor))" \ | buddy-opt \ - --convert-elementwise-to-linalg \ + --eliminate-empty-tensors \ + --convert-tensor-to-linalg \ + --linalg-bufferize \ + --convert-linalg-to-loops \ + --lower-affine \ --func-bufferize-dynamic-offset \ --arith-bufferize \ - --func-bufferize \ --tensor-bufferize \ - --linalg-bufferize \ + --buffer-deallocation \ --finalizing-bufferize \ - --batchmatmul-optimize \ - --convert-linalg-to-affine-loops \ - --lower-affine \ - --convert-vector-to-scf \ - --convert-scf-to-cf \ + --memref-expand \ --llvm-request-c-wrappers \ + --convert-vector-to-llvm=force-32bit-vector-indices \ --lower-vector-exp \ --lower-rvv=rv32 \ - --convert-vector-to-llvm \ - --convert-math-to-llvm \ - --convert-math-to-libm \ - --convert-arith-to-llvm \ - --convert-func-to-llvm \ --expand-strided-metadata \ - --finalize-memref-to-llvm \ + --finalize-memref-to-llvm=index-bitwidth=32 \ + --convert-index-to-llvm=index-bitwidth=32 \ + --convert-scf-to-cf \ + --convert-arith-to-llvm=index-bitwidth=32 \ + --convert-func-to-llvm=index-bitwidth=32 \ --reconcile-unrealized-casts \ > subgraphs0-lowered.mlir @@ -58,5 +57,8 @@ buildBuddyE2ETest { "forward-lowered.mlir" "subgraphs0-lowered.mlir" ) + + mkdir -p "$out/resources" + cp -v ''${optArtifacts[*]} "$out/resources" ''; } diff --git a/tests/pytorch/lenet/lenet.cc b/tests/pytorch/lenet/lenet.cc index 3cb5d1a88..226ffe04c 100644 --- a/tests/pytorch/lenet/lenet.cc +++ b/tests/pytorch/lenet/lenet.cc @@ -1,4 +1,4 @@ -#include "memref.hpp" +#include #define INPUT_N 1 #define INPUT_C 1 @@ -13,14 +13,9 @@ __attribute((section(".vdata"))) float output_0[OUTPUT_N]; __attribute((section(".vdata"))) float param_0[PARAM_N]; // Define the sizes of the input and output tensors. -static const int32_t sizesInput[4] = {INPUT_N, INPUT_C, INPUT_H, INPUT_W}; -static const int32_t sizesOutput[2] = {1, OUTPUT_N}; -static const int32_t sizesParams[1] = {PARAM_N}; - -// Create input and output containers for the image and model output. -MemRef input(input_0, sizesInput); -MemRef output(output_0, sizesOutput); -MemRef params(param_0, 2.0, sizesParams); +static int32_t sizesInput[4] = {INPUT_N, INPUT_C, INPUT_H, INPUT_W}; +static int32_t sizesOutput[2] = {1, OUTPUT_N}; +static int32_t sizesParams[1] = {PARAM_N}; // Declare the target model C interface. extern "C" { @@ -29,6 +24,11 @@ void _mlir_ciface_forward(MemRef *output, MemRef *arg0, } extern "C" int test() { + // Create input and output containers for the image and model output. + MemRef input(input_0, sizesInput); + MemRef output(output_0, sizesOutput); + MemRef params(param_0, 2.0, sizesParams); + _mlir_ciface_forward(&output, ¶ms, &input); return 0; } From 4edfae3fa2b8e5a392d903895654617a3ccc61c9 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Mon, 7 Apr 2025 19:31:08 +0800 Subject: [PATCH 13/24] [nix] allow overriding i32 patch for llvm Signed-off-by: Avimitin --- nix/pkgs/buddy-codegen/default.nix | 4 +++- nix/pkgs/buddy-llvm.nix | 5 ++++- nix/pkgs/buddy-mlir.nix | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/nix/pkgs/buddy-codegen/default.nix b/nix/pkgs/buddy-codegen/default.nix index caca78327..331298303 100644 --- a/nix/pkgs/buddy-codegen/default.nix +++ b/nix/pkgs/buddy-codegen/default.nix @@ -21,7 +21,9 @@ stdenv.mkDerivation { buildInputs = [ libpng argparse - buddy-mlir + (buddy-mlir.override { + enableI32MallocFix = false; + }) ]; env.NIX_CFLAGS_COMPILE = toString [ diff --git a/nix/pkgs/buddy-llvm.nix b/nix/pkgs/buddy-llvm.nix index dfa8d2f25..f2d37cd9d 100644 --- a/nix/pkgs/buddy-llvm.nix +++ b/nix/pkgs/buddy-llvm.nix @@ -1,9 +1,11 @@ { + lib, stdenv, cmake, ninja, python3, fetchFromGitHub, + enableI32MallocFix ? true, }: let @@ -23,7 +25,8 @@ stdenv.mkDerivation rec { rev = version; hash = "sha256-bMJJ2q1hSh7m0ewclHOmIe7lOHv110rz/P7D3pw8Uiw="; }; - patches = [ + + patches = lib.optionals enableI32MallocFix [ ../patches/llvm/03-buddy-llvm-fix-malloc-type.patch ]; diff --git a/nix/pkgs/buddy-mlir.nix b/nix/pkgs/buddy-mlir.nix index eba17a4cc..56c0bb5bb 100644 --- a/nix/pkgs/buddy-mlir.nix +++ b/nix/pkgs/buddy-mlir.nix @@ -6,12 +6,13 @@ fetchurl, python3, callPackage, + enableI32MallocFix ? true, }: let stdenv = llvmPackages_17.stdenv; bintools = llvmPackages_17.bintools; - buddy-llvm = callPackage ./buddy-llvm.nix { inherit stdenv python3; }; + buddy-llvm = callPackage ./buddy-llvm.nix { inherit stdenv python3 enableI32MallocFix; }; self = stdenv.mkDerivation { pname = "buddy-mlir"; version = "unstable-2024-07-18"; From 38d5f27bc30d88def33cff9bfdba6f0db7948e9d Mon Sep 17 00:00:00 2001 From: Avimitin Date: Mon, 7 Apr 2025 19:40:53 +0800 Subject: [PATCH 14/24] [test] fix tinyllama build Signed-off-by: Avimitin --- nix/pkgs/buddy-mlir.nix | 1 + tests/pytorch/tinyllama/build.nix | 64 +++++++++------------------- tests/pytorch/tinyllama/tinyllama.cc | 47 ++++++++++---------- tests/pytorch/tinyllama/tinyllama.py | 33 +++++--------- 4 files changed, 56 insertions(+), 89 deletions(-) diff --git a/nix/pkgs/buddy-mlir.nix b/nix/pkgs/buddy-mlir.nix index 56c0bb5bb..f38474fe2 100644 --- a/nix/pkgs/buddy-mlir.nix +++ b/nix/pkgs/buddy-mlir.nix @@ -76,6 +76,7 @@ let # tinyllama ps.transformers ps.accelerate + ps.sentencepiece ]); }; }; diff --git a/tests/pytorch/tinyllama/build.nix b/tests/pytorch/tinyllama/build.nix index 24e4c00d7..e07a2a895 100644 --- a/tests/pytorch/tinyllama/build.nix +++ b/tests/pytorch/tinyllama/build.nix @@ -14,45 +14,21 @@ buildBuddyE2ETest { env.LLAMA_MODEL_PATH = "${model}"; optPhase = '' - python ./tinyllama.py + python3 ./tinyllama.py --output-dir $PWD echo "Lowering forward.mlir" - buddy-opt forward.mlir -pass-pipeline \ - "builtin.module(func.func(tosa-to-linalg-named),func.func(tosa-to-linalg),\ - func.func(tosa-to-tensor),func.func(tosa-to-arith))" \ - | buddy-opt --arith-expand \ - --eliminate-empty-tensors \ - --empty-tensor-to-alloc-tensor \ - --one-shot-bufferize \ - --matmul-parallel-vectorization-optimize \ - --batchmatmul-optimize \ - --convert-linalg-to-affine-loops \ - --affine-loop-fusion \ - --lower-affine \ - --func-bufferize \ - --arith-bufferize \ - --tensor-bufferize \ - --buffer-deallocation \ - --finalizing-bufferize \ - --convert-vector-to-scf \ - --expand-strided-metadata \ - --convert-vector-to-llvm \ - --memref-expand \ - --arith-expand \ - --convert-arith-to-llvm \ - --finalize-memref-to-llvm \ - --convert-scf-to-cf \ - --llvm-request-c-wrappers \ - --convert-arith-to-llvm \ - --convert-math-to-llvm \ - --convert-math-to-libm \ - --convert-func-to-llvm \ - --reconcile-unrealized-casts \ + cat forward.mlir \ + | buddy-opt \ + --expand-strided-metadata \ + --finalize-memref-to-llvm=index-bitwidth=32 \ + --llvm-request-c-wrappers \ + --convert-func-to-llvm=index-bitwidth=32 \ + --reconcile-unrealized-casts \ > forward-lowered.mlir echo "Lowering subgraphs[0]" - buddy-opt subgraphs0.mlir -pass-pipeline \ - "builtin.module(func.func(tosa-to-linalg-named, tosa-to-arith, tosa-to-linalg, tosa-to-tensor))" \ + buddy-opt subgraph0.mlir -pass-pipeline \ + "builtin.module(func.func(tosa-to-linalg-named, tosa-to-arith{use-32-bit}, tosa-to-linalg, tosa-to-tensor))" \ | buddy-opt \ --convert-elementwise-to-linalg \ --arith-expand \ @@ -64,29 +40,26 @@ buildBuddyE2ETest { --arith-bufferize \ --buffer-deallocation \ --finalizing-bufferize \ - --matmul-parallel-vectorization-optimize \ - --batchmatmul-optimize \ --convert-linalg-to-affine-loops \ --affine-loop-fusion \ --lower-affine \ --convert-vector-to-scf \ --expand-strided-metadata \ + --llvm-request-c-wrappers \ --cse \ --lower-vector-exp \ --lower-rvv=rv32 \ --convert-vector-to-llvm \ --memref-expand \ --arith-expand \ - --convert-arith-to-llvm \ - --finalize-memref-to-llvm \ + --convert-arith-to-llvm=index-bitwidth=32 \ + --finalize-memref-to-llvm=index-bitwidth=32 \ --convert-scf-to-cf \ - --llvm-request-c-wrappers \ - --convert-arith-to-llvm \ + --convert-arith-to-llvm=index-bitwidth=32 \ --convert-math-to-llvm \ - --convert-math-to-libm \ - --convert-func-to-llvm \ + --convert-func-to-llvm=index-bitwidth=32 \ --reconcile-unrealized-casts \ - > subgraphs0-lowered.mlir + > subgraph0-lowered.mlir echo "Compiling memrefCopy library" $CXX -nostdlib -c ${../lib/MemrefCopy.cc} -o memrefCopy.o @@ -96,7 +69,10 @@ buildBuddyE2ETest { optArtifacts+=( "forward-lowered.mlir" - "subgraphs0-lowered.mlir" + "subgraph0-lowered.mlir" ) + + mkdir -p "$out/resources" + cp -v ''${optArtifacts[*]} "$out/resources" ''; } diff --git a/tests/pytorch/tinyllama/tinyllama.cc b/tests/pytorch/tinyllama/tinyllama.cc index 705d16985..b53d2ade1 100644 --- a/tests/pytorch/tinyllama/tinyllama.cc +++ b/tests/pytorch/tinyllama/tinyllama.cc @@ -18,43 +18,44 @@ // //===----------------------------------------------------------------------===// -#include "memref.hpp" +#include +#include -constexpr size_t ParamsSize = 110581; -// constexpr size_t ParamsSize = 11058; -constexpr size_t MaxVocabSize = 32000; -constexpr size_t MaxTokenLength = 40; -constexpr size_t HiddenSize = 2048; +#define PARAMS_SIZE 673 +#define MAX_VOCAB_SIZE 320 +#define MAX_TOKEN_LENGTH 40 +#define HIDDEN_SIZE 128 // resultContainer[0] -__attribute((section(".vdata"))) float result0[1 + MaxTokenLength + HiddenSize]; -static constexpr int32_t sizesResult0[3] = {1, MaxTokenLength, HiddenSize}; - +__attribute(( + section(".vdata"))) float result0[1 + MAX_TOKEN_LENGTH + HIDDEN_SIZE]; // resultContainer[1] __attribute(( - section(".vdata"))) float result1[1 + MaxTokenLength + MaxVocabSize]; -static constexpr int32_t sizesResult1[3] = {1, MaxTokenLength, MaxVocabSize}; - + section(".vdata"))) float result1[1 + MAX_TOKEN_LENGTH + MAX_VOCAB_SIZE]; // inputContainer -__attribute((section(".vdata"))) int32_t input[1 + MaxTokenLength]; -static constexpr int32_t sizesInput[2] = {1, MaxTokenLength}; - +__attribute((section(".vdata"))) int32_t input[1 + MAX_TOKEN_LENGTH]; // paramsContainer -__attribute((section(".vdata"))) float param[ParamsSize]; -static constexpr int32_t sizesParam[1] = {ParamsSize}; +__attribute((section(".vdata"))) float param[PARAMS_SIZE]; extern "C" { void _mlir_ciface_forward(MemRef *a, MemRef *b, MemRef *c); } -MemRef resultContainer[2] = { - MemRef(result0, 2.0, sizesResult0), - MemRef(result1, 3.0, sizesResult1)}; -MemRef inputContainer(input, 4, sizesInput); -MemRef paramsContainerf32(param, 5.0, sizesParam); - extern "C" int test() { + int32_t sizesResult0[3] = {1, MAX_TOKEN_LENGTH, HIDDEN_SIZE}; + int32_t sizesResult1[3] = {1, MAX_TOKEN_LENGTH, MAX_VOCAB_SIZE}; + + int32_t sizesInput[2] = {1, MAX_TOKEN_LENGTH}; + MemRef inputContainer(input, 4.0, sizesInput); + + MemRef resultContainer[2] = { + MemRef(result0, 2.0, sizesResult0), + MemRef(result1, 3.0, sizesResult1)}; + + int32_t sizesParam[1] = {PARAMS_SIZE}; + MemRef paramsContainerf32(param, 5.0, sizesParam); + _mlir_ciface_forward(resultContainer, ¶msContainerf32, &inputContainer); return 0; } diff --git a/tests/pytorch/tinyllama/tinyllama.py b/tests/pytorch/tinyllama/tinyllama.py index bd60c6567..39143f7ed 100644 --- a/tests/pytorch/tinyllama/tinyllama.py +++ b/tests/pytorch/tinyllama/tinyllama.py @@ -1,24 +1,5 @@ -# ===- buddy_tinyllama_import.py ----------------------------------------------- -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ===--------------------------------------------------------------------------- -# -# This is the TinyLlama model AOT importer. -# -# ===--------------------------------------------------------------------------- - import os +import argparse import sys import torch from torch._inductor.decomposition import decompositions as inductor_decomp @@ -29,6 +10,14 @@ from buddy.compiler.graph import GraphDriver from buddy.compiler.graph.transform import simply_fuse +parser = argparse.ArgumentParser(description="LLaMA2 model AOT importer") +parser.add_argument( + "--output-dir", type=str, default="./", help="Directory to save output files." +) +args = parser.parse_args() +output_dir = args.output_dir +os.makedirs(output_dir, exist_ok=True) + checkpoint = os.environ.get("LLAMA_MODEL_PATH") if checkpoint is None: sys.exit("Error: No model path was provided. Please set $LLAMA_MODEL_PATH") @@ -54,7 +43,7 @@ graphs[0].fuse_ops(pattern_list) driver = GraphDriver(graphs[0]) driver.subgraphs[0].lower_to_top_level_ir() -with open("subgraphs0.mlir", "w") as module_file: +with open(os.path.join(output_dir, "subgraph0.mlir"), "w") as module_file: print(driver.subgraphs[0]._imported_module, file=module_file) -with open("forward.mlir", "w") as module_file: +with open(os.path.join(output_dir, "forward.mlir"), "w") as module_file: print(driver.construct_main_graph(True), file=module_file) From 3059b166b775eef859e5202ca01928d41f982227 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Mon, 7 Apr 2025 22:54:43 +0800 Subject: [PATCH 15/24] [tests] make memref container more compile time determinable Signed-off-by: Avimitin --- tests/pytorch/include/buddy/Core/Container.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/pytorch/include/buddy/Core/Container.h b/tests/pytorch/include/buddy/Core/Container.h index 5222719c8..9f6229c19 100644 --- a/tests/pytorch/include/buddy/Core/Container.h +++ b/tests/pytorch/include/buddy/Core/Container.h @@ -35,8 +35,8 @@ template class MemRef { public: // Construct using init to allocated area - MemRef(T *allocated, T init, intptr_t sizes[N]); - MemRef(T *allocated, intptr_t sizes[N], intptr_t offset = 0); + constexpr MemRef(T *allocated, T init, intptr_t sizes[N]); + constexpr MemRef(T *allocated, intptr_t sizes[N], intptr_t offset = 0); // Get the data pointer. T *getData(); // Get the sizes (shape). @@ -54,9 +54,9 @@ template class MemRef { protected: // Set the strides. // Computes the strides of the transposed tensor for transpose=true. - void setStrides(); + inline void setStrides(); // Compute the product of array elements. - size_t product(const intptr_t sizes[N]) const; + inline size_t product(const intptr_t sizes[N]) const; // Data. // The `aligned` and `allocated` members point to the same address, `aligned` @@ -73,7 +73,7 @@ template class MemRef { }; template -MemRef::MemRef(T *allocated, T init, intptr_t sizes[N]) +constexpr MemRef::MemRef(T *allocated, T init, intptr_t sizes[N]) : MemRef(allocated, sizes) { size_t size = product(sizes); std::fill(aligned, aligned + size, init); @@ -83,7 +83,7 @@ MemRef::MemRef(T *allocated, T init, intptr_t sizes[N]) // Construct a MemRef object from the data pointer, sizes, and offset. // The default offset is 0. template -MemRef::MemRef(T *data, intptr_t sizes[N], intptr_t offset) { +constexpr MemRef::MemRef(T *data, intptr_t sizes[N], intptr_t offset) { this->offset = offset; for (size_t i = 0; i < N; i++) { this->sizes[i] = sizes[i]; @@ -124,7 +124,7 @@ template T &MemRef::operator[](size_t index) { } // Calculate the stride values for each dimension based on the sizes. -template void MemRef::setStrides() { +template inline void MemRef::setStrides() { strides[N - 1] = 1; if (N < 2) return; @@ -136,7 +136,7 @@ template void MemRef::setStrides() { // Calculate the total number of elements in the MemRef container. template -size_t MemRef::product(const intptr_t sizes[N]) const { +inline size_t MemRef::product(const intptr_t sizes[N]) const { size_t size = 1; for (size_t i = 0; i < N; i++) size *= sizes[i]; From f39b3f705ded120148c8e1c25f5f2539472e5566 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Tue, 8 Apr 2025 15:08:34 +0800 Subject: [PATCH 16/24] WIP stash for buddymlir --- nix/pkgs/buddy-codegen/default.nix | 4 +--- nix/pkgs/buddy-llvm.nix | 12 ++++------- nix/pkgs/buddy-mlir.nix | 22 ++++++++++++-------- tests/pytorch/default.nix | 5 ++--- tests/pytorch/matmul/build.nix | 21 +++++++++---------- tests/pytorch/matmul/matmul.cc | 18 ++++++++--------- tests/pytorch/mobilenet/build.nix | 18 ++++++++--------- tests/pytorch/mobilenet/mobilenet.cc | 11 +++++----- tests/pytorch/resnet18/build.nix | 30 ++++++++++------------------ tests/pytorch/resnet18/resnet18.cc | 2 +- 10 files changed, 64 insertions(+), 79 deletions(-) diff --git a/nix/pkgs/buddy-codegen/default.nix b/nix/pkgs/buddy-codegen/default.nix index 331298303..caca78327 100644 --- a/nix/pkgs/buddy-codegen/default.nix +++ b/nix/pkgs/buddy-codegen/default.nix @@ -21,9 +21,7 @@ stdenv.mkDerivation { buildInputs = [ libpng argparse - (buddy-mlir.override { - enableI32MallocFix = false; - }) + buddy-mlir ]; env.NIX_CFLAGS_COMPILE = toString [ diff --git a/nix/pkgs/buddy-llvm.nix b/nix/pkgs/buddy-llvm.nix index f2d37cd9d..a97e2c97e 100644 --- a/nix/pkgs/buddy-llvm.nix +++ b/nix/pkgs/buddy-llvm.nix @@ -1,11 +1,9 @@ { - lib, stdenv, cmake, ninja, python3, fetchFromGitHub, - enableI32MallocFix ? true, }: let @@ -14,22 +12,19 @@ let ps.pybind11 ps.pyyaml ps.ml-dtypes + ps.nanobind ]); in stdenv.mkDerivation rec { name = "llvm-for-buddy-mlir"; - version = "6c59f0e1b0fb56c909ad7c9aad4bde37dc006ae0"; + version = "3bd3e06f3fe418e24af65457877f40cee0544f9d"; src = fetchFromGitHub { owner = "llvm"; repo = "llvm-project"; rev = version; - hash = "sha256-bMJJ2q1hSh7m0ewclHOmIe7lOHv110rz/P7D3pw8Uiw="; + hash = "sha256-JSquIeA14dXKXO6E8v0HV36fA/+bZypJKkcGMvPxxHI="; }; - patches = lib.optionals enableI32MallocFix [ - ../patches/llvm/03-buddy-llvm-fix-malloc-type.patch - ]; - requiredSystemFeatures = [ "big-parallel" ]; propagatedBuildInputs = [ @@ -72,6 +67,7 @@ stdenv.mkDerivation rec { # move all lib files to $lib except lib/cmake moveToOutput "lib" "$lib" + moveToOutput "python_packages" "$lib" moveToOutput "lib/cmake" "$dev" moveToOutput "src" "$dev" diff --git a/nix/pkgs/buddy-mlir.nix b/nix/pkgs/buddy-mlir.nix index f38474fe2..80bd03017 100644 --- a/nix/pkgs/buddy-mlir.nix +++ b/nix/pkgs/buddy-mlir.nix @@ -1,4 +1,5 @@ { + lib, cmake, ninja, llvmPackages_17, @@ -6,24 +7,29 @@ fetchurl, python3, callPackage, - enableI32MallocFix ? true, }: let stdenv = llvmPackages_17.stdenv; bintools = llvmPackages_17.bintools; - buddy-llvm = callPackage ./buddy-llvm.nix { inherit stdenv python3 enableI32MallocFix; }; + buddy-llvm = callPackage ./buddy-llvm.nix { inherit stdenv python3; }; self = stdenv.mkDerivation { pname = "buddy-mlir"; version = "unstable-2024-07-18"; src = fetchFromGitHub { - owner = "buddy-compiler"; + owner = "WuXintong123"; repo = "buddy-mlir"; - rev = "c57584a0e3c38e938a3902320f62b202ced84996"; - hash = "sha256-IBsShnkaA0qPkEMbkkSjUMWXnDGW/CrTeiSSLLttlXk="; + rev = "6586555adf921371906fe908293714bff4d92b24"; + hash = "sha256-NDdj72oNhIKcU7cOw+RDzPrjKLIUVY63TDUrJ2DzYL0="; }; + postPatch = '' + sed -i \ + 's|link_directories(''${LLVM_BINARY_DIR}/tools/mlir/|link_directories(''${LLVM_BINARY_DIR}/|' \ + midend/python/CMakeLists.txt + ''; + nativeBuildInputs = [ cmake ninja @@ -34,8 +40,8 @@ let ]; cmakeFlags = [ - "-DMLIR_DIR=${buddy-llvm.dev}/lib/cmake/mlir" - "-DLLVM_DIR=${buddy-llvm.dev}/lib/cmake/llvm" + "-DMLIR_DIR=${buddy-llvm}/lib/cmake/mlir" + "-DLLVM_DIR=${buddy-llvm}/lib/cmake/llvm" "-DLLVM_MAIN_SRC_DIR=${buddy-llvm.src}/llvm" "-DBUDDY_MLIR_ENABLE_PYTHON_PACKAGES=ON" "-DCMAKE_BUILD_TYPE=Release" @@ -54,7 +60,7 @@ let postFixup = '' mkdir -p $out/lib/python${python3.pythonVersion}/site-packages cp -vr $out/python_packages/buddy $out/lib/python${python3.pythonVersion}/site-packages/ - cp -vr ${buddy-llvm}/python_packages/mlir_core/mlir $out/lib/python${python3.pythonVersion}/site-packages/ + cp -vr ${buddy-llvm.lib}/python_packages/mlir_core/mlir $out/lib/python${python3.pythonVersion}/site-packages/ ''; passthru = { diff --git a/tests/pytorch/default.nix b/tests/pytorch/default.nix index 4198511ea..f88cd506e 100644 --- a/tests/pytorch/default.nix +++ b/tests/pytorch/default.nix @@ -7,7 +7,6 @@ getTestRequiredFeatures, t1main, callPackage, - buddy-codegen, }: let @@ -26,7 +25,7 @@ let nativeBuildInputs = [ buddy-mlir.pyenv buddy-mlir - buddy-codegen + buddy-mlir.llvm ]; src = sourcePath; @@ -43,7 +42,7 @@ let for mlir in ''${optArtifacts[@]}; do echo "Translating $mlir" - buddy-translate --buddy-to-llvmir "$mlir" -o "$mlir.ll" + mlir-translate --mlir-to-llvmir "$mlir" -o "$mlir.ll" translateArtifacts+=("$mlir.ll") done diff --git a/tests/pytorch/matmul/build.nix b/tests/pytorch/matmul/build.nix index 9e84d8219..67cd326c1 100644 --- a/tests/pytorch/matmul/build.nix +++ b/tests/pytorch/matmul/build.nix @@ -6,29 +6,26 @@ buildBuddyE2ETest { echo "Lowering forward.mlir" python ./matmul.py \ - | buddy-opt --pass-pipeline "builtin.module(func.func(tosa-to-linalg-named, tosa-to-arith, tosa-to-linalg, tosa-to-tensor))" \ - | buddy-opt --convert-elementwise-to-linalg \ + | buddy-opt --pass-pipeline "builtin.module(func.func(tosa-to-linalg-named, tosa-to-arith{use-32-bit}, tosa-to-linalg, tosa-to-tensor))" \ + | buddy-opt \ + --convert-elementwise-to-linalg \ + --one-shot-bufferize="bufferize-function-boundaries" \ --func-bufferize-dynamic-offset \ - --arith-bufferize \ - --func-bufferize \ - --tensor-bufferize \ - --linalg-bufferize \ - --finalizing-bufferize \ - --batchmatmul-optimize \ --convert-linalg-to-affine-loops \ + --batchmatmul-optimize \ --lower-affine \ --lower-vector-exp \ --lower-rvv=rv32 \ --convert-vector-to-scf \ --convert-scf-to-cf \ + --convert-cf-to-llvm \ --llvm-request-c-wrappers \ --convert-vector-to-llvm \ --convert-math-to-llvm \ - --convert-math-to-libm \ - --convert-arith-to-llvm \ - --convert-func-to-llvm \ + --convert-arith-to-llvm=index-bitwidth=32 \ + --convert-func-to-llvm=index-bitwidth=32 \ --expand-strided-metadata \ - --finalize-memref-to-llvm \ + --finalize-memref-to-llvm=index-bitwidth=32 \ --reconcile-unrealized-casts \ -o forward-lowered.mlir diff --git a/tests/pytorch/matmul/matmul.cc b/tests/pytorch/matmul/matmul.cc index b523f0626..2e1a020dd 100644 --- a/tests/pytorch/matmul/matmul.cc +++ b/tests/pytorch/matmul/matmul.cc @@ -1,22 +1,22 @@ -#include "memref.hpp" +#include extern "C" void _mlir_ciface_forward(MemRef *output, MemRef *arg1, MemRef *arg2); -// One-dimension, with length 512 -static const int32_t sizes[3] = {8, 8, 8}; - __attribute((section(".vdata"))) float input_float_1[512]; -MemRef input1(input_float_1, sizes); - __attribute((section(".vdata"))) float input_float_2[512]; -MemRef input2(input_float_2, sizes); - __attribute((section(".vdata"))) float output_float_1[512]; -MemRef output(output_float_1, sizes); extern "C" int test() { + // One-dimension, with length 512 + static int32_t sizes[3] = {8, 8, 8}; + + MemRef input1(input_float_1, sizes); + MemRef input2(input_float_2, sizes); + MemRef output(output_float_1, sizes); + _mlir_ciface_forward(&output, &input1, &input2); + return 0; } diff --git a/tests/pytorch/mobilenet/build.nix b/tests/pytorch/mobilenet/build.nix index ac666cc68..2eddc68c6 100644 --- a/tests/pytorch/mobilenet/build.nix +++ b/tests/pytorch/mobilenet/build.nix @@ -20,20 +20,20 @@ buildBuddyE2ETest { echo "Lowering forward.mlir" buddy-opt forward.mlir -pass-pipeline \ - "builtin.module(func.func(tosa-to-linalg-named, tosa-to-linalg, tosa-to-tensor, tosa-to-arith), \ + "builtin.module(func.func(tosa-to-linalg-named, tosa-to-linalg, tosa-to-tensor, tosa-to-arith{use-32-bit}), \ empty-tensor-to-alloc-tensor, convert-elementwise-to-linalg, arith-bufferize, \ func.func(linalg-bufferize, tensor-bufferize), func-bufferize)" \ | buddy-opt -pass-pipeline \ "builtin.module(func.func(buffer-deallocation-simplification, convert-linalg-to-loops), \ eliminate-empty-tensors, func.func(llvm-request-c-wrappers), \ - convert-math-to-llvm, convert-math-to-libm, convert-scf-to-cf, \ - convert-arith-to-llvm, expand-strided-metadata, finalize-memref-to-llvm, \ - convert-func-to-llvm, reconcile-unrealized-casts)" \ + convert-math-to-llvm, convert-scf-to-cf, \ + convert-arith-to-llvm{index-bitwidth=32}, expand-strided-metadata, finalize-memref-to-llvm{index-bitwidth=32}, \ + convert-func-to-llvm{index-bitwidth=32}, reconcile-unrealized-casts)" \ > forward-lowered.mlir echo "Lowering subgraphs[0]" buddy-opt subgraphs0.mlir -pass-pipeline \ - "builtin.module(func.func(tosa-to-linalg-named, tosa-to-arith, tosa-to-linalg, tosa-to-tensor))" \ + "builtin.module(func.func(tosa-to-linalg-named, tosa-to-arith{use-32-bit}, tosa-to-linalg, tosa-to-tensor))" \ | buddy-opt \ --convert-elementwise-to-linalg \ --func-bufferize-dynamic-offset \ @@ -42,7 +42,6 @@ buildBuddyE2ETest { --tensor-bufferize \ --linalg-bufferize \ --finalizing-bufferize \ - --batchmatmul-optimize \ --convert-linalg-to-affine-loops \ --lower-affine \ --convert-vector-to-scf \ @@ -52,11 +51,10 @@ buildBuddyE2ETest { --lower-rvv=rv32 \ --convert-vector-to-llvm \ --convert-math-to-llvm \ - --convert-math-to-libm \ - --convert-arith-to-llvm \ - --convert-func-to-llvm \ + --convert-arith-to-llvm=index-bitwidth=32 \ + --convert-func-to-llvm=index-bitwidth=32 \ --expand-strided-metadata \ - --finalize-memref-to-llvm \ + --finalize-memref-to-llvm=index-bitwidth=32 \ --reconcile-unrealized-casts \ > subgraphs0-lowered.mlir diff --git a/tests/pytorch/mobilenet/mobilenet.cc b/tests/pytorch/mobilenet/mobilenet.cc index d64064fbb..8b398d074 100644 --- a/tests/pytorch/mobilenet/mobilenet.cc +++ b/tests/pytorch/mobilenet/mobilenet.cc @@ -15,18 +15,17 @@ __attribute((section(".vdata"))) float output_0[OUTPUT_N]; __attribute((section(".vdata"))) float param_0[PARAM_N0]; __attribute((section(".vdata"))) int64_t param_1[PARAM_N1]; -// Define the sizes of the input and output tensors. -static const int32_t sizesInput[4] = {INPUT_N, INPUT_C, INPUT_H, INPUT_W}; -static const int32_t sizesOutput[2] = {1, OUTPUT_N}; -static const int32_t sizesParam0[1] = {PARAM_N0}; -static const int32_t sizesParam1[1] = {PARAM_N1}; - extern "C" { void _mlir_ciface_forward(MemRef *output, MemRef *arg0, MemRef *arg1, Image *input); } extern "C" int test() { + // Define the sizes of the input and output tensors. + static int32_t sizesInput[4] = {INPUT_N, INPUT_C, INPUT_H, INPUT_W}; + static int32_t sizesOutput[2] = {1, OUTPUT_N}; + static int32_t sizesParam0[1] = {PARAM_N0}; + static int32_t sizesParam1[1] = {PARAM_N1}; // Generate input memref container with random numbers. const int inputSize = INPUT_N * INPUT_C * INPUT_H * INPUT_W; diff --git a/tests/pytorch/resnet18/build.nix b/tests/pytorch/resnet18/build.nix index ecbb873d2..de2f869b5 100644 --- a/tests/pytorch/resnet18/build.nix +++ b/tests/pytorch/resnet18/build.nix @@ -22,12 +22,11 @@ buildBuddyE2ETest { echo "Lowering forward.mlir" buddy-opt forward.mlir -pass-pipeline \ "builtin.module(func.func(tosa-to-linalg-named, tosa-to-linalg, tosa-to-tensor, tosa-to-arith{use-32-bit}), \ - empty-tensor-to-alloc-tensor, convert-elementwise-to-linalg, arith-bufferize, \ - func.func(linalg-bufferize, tensor-bufferize), func-bufferize, convert-vector-to-llvm)" \ + empty-tensor-to-alloc-tensor, convert-elementwise-to-linalg, convert-vector-to-llvm)" \ | buddy-opt -pass-pipeline \ "builtin.module(func.func(buffer-deallocation-simplification, convert-linalg-to-loops), \ eliminate-empty-tensors, func.func(llvm-request-c-wrappers), \ - convert-math-to-llvm, convert-math-to-libm, convert-scf-to-cf, \ + convert-math-to-llvm, convert-scf-to-cf, \ convert-arith-to-llvm{index-bitwidth=32}, convert-func-to-llvm{index-bitwidth=32}, \ expand-strided-metadata, finalize-memref-to-llvm{index-bitwidth=32}, \ convert-func-to-llvm{index-bitwidth=32}, reconcile-unrealized-casts)" \ @@ -38,28 +37,21 @@ buildBuddyE2ETest { "builtin.module(func.func(tosa-to-linalg-named, tosa-to-arith{use-32-bit}, tosa-to-linalg, tosa-to-tensor))" \ | buddy-opt \ --convert-elementwise-to-linalg \ - --eliminate-empty-tensors \ - --empty-tensor-to-alloc-tensor \ - --one-shot-bufferize \ + --one-shot-bufferize="bufferize-function-boundaries" \ --func-bufferize-dynamic-offset \ - --tensor-bufferize \ - --arith-bufferize \ - --buffer-deallocation \ - --finalizing-bufferize \ - --convert-linalg-to-affine-loops \ - --affine-loop-fusion \ - --lower-affine \ - --expand-strided-metadata \ - --convert-vector-to-scf \ + --conv-nhwc-fhwc-optimize \ + --batchmatmul-optimize \ + --convert-linalg-to-loops \ + --convert-vector-to-llvm \ --convert-scf-to-cf \ - --memref-expand \ + --convert-cf-to-llvm \ + --expand-strided-metadata \ + --lower-affine \ --llvm-request-c-wrappers \ --lower-vector-exp \ --lower-rvv=rv32 \ - --convert-vector-to-llvm \ - --convert-math-to-llvm \ --convert-arith-to-llvm=index-bitwidth=32 \ - --convert-index-to-llvm=index-bitwidth=32 \ + --convert-math-to-llvm \ --convert-func-to-llvm=index-bitwidth=32 \ --finalize-memref-to-llvm=index-bitwidth=32 \ --reconcile-unrealized-casts \ diff --git a/tests/pytorch/resnet18/resnet18.cc b/tests/pytorch/resnet18/resnet18.cc index 460ea3a92..0ed2983fa 100644 --- a/tests/pytorch/resnet18/resnet18.cc +++ b/tests/pytorch/resnet18/resnet18.cc @@ -6,7 +6,7 @@ extern "C" void _mlir_ciface_forward(MemRef *output, MemRef *input); __attribute((section(".vdata"))) float output_float_1[1000]; -__attribute((section(".vdata"))) float IMAGE[300]; +__attribute((section(".vdata"))) float IMAGE[30000]; __attribute((section(".vdata"))) float PARAMS[100]; extern "C" int test() { From 422f5d45636f0a007e80540049537cc3b96b23d6 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Tue, 8 Apr 2025 23:27:38 +0800 Subject: [PATCH 17/24] [tests] replace buddy-llc with main stream llc Signed-off-by: Avimitin --- tests/pytorch/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pytorch/default.nix b/tests/pytorch/default.nix index f88cd506e..fb7ad6a43 100644 --- a/tests/pytorch/default.nix +++ b/tests/pytorch/default.nix @@ -56,7 +56,7 @@ let for llvmir in ''${translateArtifacts[@]}; do echo "Compiling $llvmir" - buddy-llc "$llvmir" \ + llc "$llvmir" \ -mtriple=riscv32 \ -target-abi=ilp32f \ -mattr=+m,+f,+zve32f \ From 4500d847bc09925bc3d289e4073b391816858272 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Tue, 8 Apr 2025 23:28:43 +0800 Subject: [PATCH 18/24] [tests] finalize resnet18 build Signed-off-by: Avimitin --- tests/pytorch/resnet18/build.nix | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/pytorch/resnet18/build.nix b/tests/pytorch/resnet18/build.nix index de2f869b5..eeb8e2a3c 100644 --- a/tests/pytorch/resnet18/build.nix +++ b/tests/pytorch/resnet18/build.nix @@ -42,19 +42,20 @@ buildBuddyE2ETest { --conv-nhwc-fhwc-optimize \ --batchmatmul-optimize \ --convert-linalg-to-loops \ - --convert-vector-to-llvm \ - --convert-scf-to-cf \ - --convert-cf-to-llvm \ --expand-strided-metadata \ - --lower-affine \ --llvm-request-c-wrappers \ - --lower-vector-exp \ - --lower-rvv=rv32 \ + --lower-affine \ + --convert-vector-to-llvm=force-32bit-vector-indices \ + --convert-scf-to-cf \ + --convert-cf-to-llvm=index-bitwidth=32 \ --convert-arith-to-llvm=index-bitwidth=32 \ --convert-math-to-llvm \ --convert-func-to-llvm=index-bitwidth=32 \ --finalize-memref-to-llvm=index-bitwidth=32 \ + --convert-index-to-llvm=index-bitwidth=32 \ --reconcile-unrealized-casts \ + --mlir-print-ir-after-change \ + --verify-each \ > subgraph0-lowered.mlir echo "Compiling memrefCopy library" From 8175d7d34558c7cab1f6342b01084aa70abc89bb Mon Sep 17 00:00:00 2001 From: Avimitin Date: Wed, 9 Apr 2025 04:30:53 +0800 Subject: [PATCH 19/24] [tests] fix llvm 32bit vector convertion error Signed-off-by: Avimitin --- .../llvm/03-buddy-llvm-fix-malloc-type.patch | 13 --- nix/patches/llvm/fix-vector-convert.patch | 100 ++++++++++++++++++ nix/pkgs/buddy-llvm.nix | 4 + tests/pytorch/default.nix | 2 +- tests/pytorch/resnet18/build.nix | 4 +- 5 files changed, 107 insertions(+), 16 deletions(-) delete mode 100644 nix/patches/llvm/03-buddy-llvm-fix-malloc-type.patch create mode 100644 nix/patches/llvm/fix-vector-convert.patch diff --git a/nix/patches/llvm/03-buddy-llvm-fix-malloc-type.patch b/nix/patches/llvm/03-buddy-llvm-fix-malloc-type.patch deleted file mode 100644 index 564001484..000000000 --- a/nix/patches/llvm/03-buddy-llvm-fix-malloc-type.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp -index 7312388bc9b4..2d72a32fca57 100644 ---- a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp -+++ b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp -@@ -1321,7 +1321,7 @@ prepareLLVMModule(Operation *m, llvm::LLVMContext &llvmContext, - // memref allocation/deallocation coming from standard ops lowering. - llvm::IRBuilder<> builder(llvmContext); - llvmModule->getOrInsertFunction("malloc", builder.getInt8PtrTy(), -- builder.getInt64Ty()); -+ builder.getInt32Ty()); - llvmModule->getOrInsertFunction("free", builder.getVoidTy(), - builder.getInt8PtrTy()); - diff --git a/nix/patches/llvm/fix-vector-convert.patch b/nix/patches/llvm/fix-vector-convert.patch new file mode 100644 index 000000000..9be7dc4a6 --- /dev/null +++ b/nix/patches/llvm/fix-vector-convert.patch @@ -0,0 +1,100 @@ +--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp 1970-01-01 00:00:01.000000000 +0000 ++++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp 2025-04-08 19:56:05.027381997 +0000 +@@ -4512,7 +4512,8 @@ + + // Src needs to have twice the number of elements. + unsigned NumElts = VT.getVectorNumElements(); +- if (Src.getValueType().getVectorNumElements() != (NumElts * 2)) ++ if (!Src.getValueType().isFixedLengthVector() || ++ Src.getValueType().getVectorNumElements() != (NumElts * 2)) + return SDValue(); + + // The extracts must extract the two halves of the source. +--- a/mlir/include/mlir/Conversion/Passes.td 1970-01-01 00:00:01.000000000 +0000 ++++ b/mlir/include/mlir/Conversion/Passes.td 2025-04-08 18:58:09.826017766 +0000 +@@ -1444,6 +1444,9 @@ + "vector::VectorTransformsOptions", + /*default=*/"vector::VectorTransformsOptions()", + "Options to lower some operations like contractions and transposes.">, ++ Option<"indexBitwidth", "index-bitwidth", "unsigned", ++ /*default=kDeriveIndexBitwidthFromDataLayout*/"0", ++ "Bitwidth of the index type, 0 to use size of machine word">, + ]; + } + +--- a/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp 1970-01-01 00:00:01.000000000 +0000 ++++ b/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp 2025-04-08 18:59:47.622433399 +0000 +@@ -1438,8 +1438,6 @@ + if (llvm::any_of(*targetStrides, ShapedType::isDynamic)) + return failure(); + +- auto int64Ty = IntegerType::get(rewriter.getContext(), 64); +- + // Create descriptor. + auto desc = MemRefDescriptor::undef(rewriter, loc, llvmTargetDescriptorTy); + // Set allocated ptr. +@@ -1450,21 +1448,23 @@ + Value ptr = sourceMemRef.alignedPtr(rewriter, loc); + desc.setAlignedPtr(rewriter, loc, ptr); + // Fill offset 0. +- auto attr = rewriter.getIntegerAttr(rewriter.getIndexType(), 0); +- auto zero = rewriter.create(loc, int64Ty, attr); ++ auto idxType = rewriter.getIndexType(); ++ auto zero = rewriter.create( ++ loc, typeConverter->convertType(idxType), ++ rewriter.getIntegerAttr(idxType, 0)); + desc.setOffset(rewriter, loc, zero); + + // Fill size and stride descriptors in memref. + for (const auto &indexedSize : + llvm::enumerate(targetMemRefType.getShape())) { + int64_t index = indexedSize.index(); +- auto sizeAttr = +- rewriter.getIntegerAttr(rewriter.getIndexType(), indexedSize.value()); +- auto size = rewriter.create(loc, int64Ty, sizeAttr); ++ auto size = rewriter.create( ++ loc, typeConverter->convertType(idxType), ++ rewriter.getIntegerAttr(idxType, indexedSize.value())); + desc.setSize(rewriter, loc, index, size); +- auto strideAttr = rewriter.getIntegerAttr(rewriter.getIndexType(), +- (*targetStrides)[index]); +- auto stride = rewriter.create(loc, int64Ty, strideAttr); ++ auto stride = rewriter.create( ++ loc, typeConverter->convertType(idxType), ++ rewriter.getIntegerAttr(idxType, (*targetStrides)[index])); + desc.setStride(rewriter, loc, index, stride); + } + +--- a/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.cpp 1970-01-01 00:00:01.000000000 +0000 ++++ b/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.cpp 2025-04-08 19:00:55.000728446 +0000 +@@ -8,6 +8,7 @@ + + #include "mlir/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.h" + ++#include "mlir/Analysis/DataLayoutAnalysis.h" + #include "mlir/Conversion/LLVMCommon/ConversionTarget.h" + #include "mlir/Conversion/LLVMCommon/TypeConverter.h" + #include "mlir/Dialect/AMX/AMXDialect.h" +@@ -64,6 +65,8 @@ + // Perform progressive lowering of operations on slices and all contraction + // operations. Also materializes masks, lowers vector.step, rank-reduces FMA, + // applies folding and DCE. ++ Operation *op = getOperation(); ++ const auto &dataLayoutAnalysis = getAnalysis(); + { + RewritePatternSet patterns(&getContext()); + populateVectorToVectorCanonicalizationPatterns(patterns); +@@ -85,8 +88,11 @@ + } + + // Convert to the LLVM IR dialect. +- LowerToLLVMOptions options(&getContext()); +- LLVMTypeConverter converter(&getContext(), options); ++ LowerToLLVMOptions options(&getContext(), ++ dataLayoutAnalysis.getAtOrAbove(op)); ++ if (indexBitwidth != kDeriveIndexBitwidthFromDataLayout) ++ options.overrideIndexBitwidth(indexBitwidth); ++ LLVMTypeConverter converter(&getContext(), options, &dataLayoutAnalysis); + RewritePatternSet patterns(&getContext()); + populateVectorTransferLoweringPatterns(patterns); + populateVectorToLLVMMatrixConversionPatterns(converter, patterns); diff --git a/nix/pkgs/buddy-llvm.nix b/nix/pkgs/buddy-llvm.nix index a97e2c97e..b04ce38bb 100644 --- a/nix/pkgs/buddy-llvm.nix +++ b/nix/pkgs/buddy-llvm.nix @@ -27,6 +27,10 @@ stdenv.mkDerivation rec { requiredSystemFeatures = [ "big-parallel" ]; + patches = [ + ../patches/llvm/fix-vector-convert.patch + ]; + propagatedBuildInputs = [ pythonEnv ]; diff --git a/tests/pytorch/default.nix b/tests/pytorch/default.nix index fb7ad6a43..a8c9867cb 100644 --- a/tests/pytorch/default.nix +++ b/tests/pytorch/default.nix @@ -59,7 +59,7 @@ let llc "$llvmir" \ -mtriple=riscv32 \ -target-abi=ilp32f \ - -mattr=+m,+f,+zve32f \ + -mattr=+m,+f,+zvl4096b,+zve32f \ --filetype=obj \ -o "$llvmir.o" diff --git a/tests/pytorch/resnet18/build.nix b/tests/pytorch/resnet18/build.nix index eeb8e2a3c..a143aace9 100644 --- a/tests/pytorch/resnet18/build.nix +++ b/tests/pytorch/resnet18/build.nix @@ -22,7 +22,7 @@ buildBuddyE2ETest { echo "Lowering forward.mlir" buddy-opt forward.mlir -pass-pipeline \ "builtin.module(func.func(tosa-to-linalg-named, tosa-to-linalg, tosa-to-tensor, tosa-to-arith{use-32-bit}), \ - empty-tensor-to-alloc-tensor, convert-elementwise-to-linalg, convert-vector-to-llvm)" \ + empty-tensor-to-alloc-tensor, convert-elementwise-to-linalg, convert-vector-to-llvm{force-32bit-vector-indices index-bitwidth=32})" \ | buddy-opt -pass-pipeline \ "builtin.module(func.func(buffer-deallocation-simplification, convert-linalg-to-loops), \ eliminate-empty-tensors, func.func(llvm-request-c-wrappers), \ @@ -45,7 +45,7 @@ buildBuddyE2ETest { --expand-strided-metadata \ --llvm-request-c-wrappers \ --lower-affine \ - --convert-vector-to-llvm=force-32bit-vector-indices \ + --convert-vector-to-llvm="force-32bit-vector-indices index-bitwidth=32" \ --convert-scf-to-cf \ --convert-cf-to-llvm=index-bitwidth=32 \ --convert-arith-to-llvm=index-bitwidth=32 \ From e331575fbfecad1f542537913a0348d29e0fb127 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Wed, 9 Apr 2025 14:18:21 +0800 Subject: [PATCH 20/24] [tests] align data size Signed-off-by: Avimitin --- tests/pytorch/resnet18/resnet18.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/pytorch/resnet18/resnet18.cc b/tests/pytorch/resnet18/resnet18.cc index 0ed2983fa..05378626b 100644 --- a/tests/pytorch/resnet18/resnet18.cc +++ b/tests/pytorch/resnet18/resnet18.cc @@ -6,18 +6,18 @@ extern "C" void _mlir_ciface_forward(MemRef *output, MemRef *input); __attribute((section(".vdata"))) float output_float_1[1000]; -__attribute((section(".vdata"))) float IMAGE[30000]; -__attribute((section(".vdata"))) float PARAMS[100]; +__attribute((section(".vdata"))) float IMAGE[150528]; +__attribute((section(".vdata"))) float PARAMS[11699112]; extern "C" int test() { static int32_t sizes[2] = {1, 1000}; - static int32_t params_sizes[1] = {100}; - static int32_t image_sizes[4] = {1, 3, 100, 100}; + static int32_t params_sizes[1] = {11699112}; + static int32_t image_sizes[4] = {1, 3, 224, 224}; - MemRef output(output_float_1, sizes); + MemRef output(output_float_1, 7.0, sizes); - MemRef inputResize(IMAGE, image_sizes); - MemRef paramsContainer(PARAMS, params_sizes); + MemRef inputResize(IMAGE, 6.0, image_sizes); + MemRef paramsContainer(PARAMS, 5.0, params_sizes); _mlir_ciface_forward(&output, ¶msContainer, &inputResize); return 0; From d02db83cfd67c87d1a5b158786349d8b083eb55e Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Wed, 9 Apr 2025 07:40:53 +0000 Subject: [PATCH 21/24] [tests] intrinsic_conv2d --- tests/intrinsic/conv2d_resnet/conv2d_resnet.c | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 tests/intrinsic/conv2d_resnet/conv2d_resnet.c diff --git a/tests/intrinsic/conv2d_resnet/conv2d_resnet.c b/tests/intrinsic/conv2d_resnet/conv2d_resnet.c new file mode 100644 index 000000000..2f7ed6200 --- /dev/null +++ b/tests/intrinsic/conv2d_resnet/conv2d_resnet.c @@ -0,0 +1,80 @@ +#include +#include +#include + +typedef int32_t vl_type; + +// Adapt from conv2d_less_m2, add outChannel and inChannel +// +// when AVL >= MAXVL, this is efficent +void conv2d(int32_t *restrict output_, int32_t const *restrict img_, + int32_t const *restrict kernel_, size_t imgRow, size_t imgCol, + size_t outChannel, size_t inChannel, size_t kernelSize) { + + size_t const outRow = imgRow - kernelSize + 1; + size_t const outCol = imgCol - kernelSize + 1; + + for (size_t coI = 0; coI < outChannel; coI++) { + for (size_t ciI = 0; ciI < inChannel; ciI++) { + int32_t *output = output_ + coI * (outRow * outCol); + int32_t const *img = img_ + ciI * (imgRow * imgCol); + int32_t const *kernel = kernel_ + (coI * inChannel + ciI) * kernelSize * kernelSize; + + for (size_t iI = 0; iI < imgRow; iI++) { + for (size_t kI = 0; kI < kernelSize; kI++) { + // only need img[kI] to img[imgRow + kI - kernelSize] + if (!(kI <= iI && iI < kI + outCol)) + continue; + + for (size_t kJ = 0; kJ < kernelSize; kJ++) { + int32_t const K = kernel[kI * kernelSize + kJ]; + // from img[iI][kJ] to img[iI][imgCol - kernelSize], step by 1 + // imgCol - kernelSize + 1 is the number of elements to be processed + int32_t const *imgPtr = img + iI * imgCol + kJ; + // from output[iI - kI][0] to output[iI - kI][imgCol - kernelSize], + int32_t *outPtr = output + (iI - kI) * outCol; + // when AVL >= MAXVL, this is efficent + size_t avl = imgCol - kernelSize + 1; + while (avl > 0) { + // TODO: exchange vl-loop and kJ loop, can be more cache friendly + + size_t vl = __riscv_vsetvl_e32m2(avl); + + vint32m2_t imgVec = __riscv_vle32_v_i32m2(imgPtr, vl); + vint32m2_t mulVec = __riscv_vmul_vx_i32m2(imgVec, K, vl); + + vint32m2_t outVec = __riscv_vle32_v_i32m2(outPtr, vl); + vint32m2_t resVec = __riscv_vadd_vv_i32m2(outVec, mulVec, vl); + + __riscv_vse32_v_i32m2(outPtr, resVec, vl); + + avl -= vl; + imgPtr += vl; + outPtr += vl; + } + } + } + } + } + } +} + +#define VDATA __attribute((section(".vdata"))) +#define VBASS __attribute((section(".vbss"))) + +// size of image is padded to (H+K-1, W+K-1) + +// CONV1: O[224, 224, 64], W[64, 3, 7, 7] +VDATA int32_t conv1_img[64*230*230]; +VDATA int32_t conv1_output[64*224*224]; +VDATA int32_t conv1_kernel[64*3*7*7]; + +// CONV2: ... + +int test() { + conv2d(conv1_output, conv1_img, conv1_kernel, 224, 224, 64, 3, 7); + + // conv2d(...) + + return 0; +} From 29259089311f9e4e92d86499032814cdea874b47 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Thu, 10 Apr 2025 00:13:20 +0800 Subject: [PATCH 22/24] [nix] fix buddy-mlir python module Signed-off-by: Avimitin --- nix/patches/buddy-mlir/00-fix-splat-op.patch | 59 ++++++++++++++++++++ nix/pkgs/buddy-mlir.nix | 4 ++ 2 files changed, 63 insertions(+) create mode 100644 nix/patches/buddy-mlir/00-fix-splat-op.patch diff --git a/nix/patches/buddy-mlir/00-fix-splat-op.patch b/nix/patches/buddy-mlir/00-fix-splat-op.patch new file mode 100644 index 000000000..3fd32476c --- /dev/null +++ b/nix/patches/buddy-mlir/00-fix-splat-op.patch @@ -0,0 +1,59 @@ +--- a/frontend/Python/ops/linalg.py 1970-01-01 08:00:01.000000000 +0800 ++++ b/frontend/Python/ops/linalg.py 1970-01-01 08:00:01.000000000 +0800 +@@ -1166,7 +1166,7 @@ + element = mlir_element_attr_get(dtype, 0.0) + attr = ir.DenseElementsAttr.get_splat(tensor_type, element) + matmul_result_buffer = arith.ConstantOp(tensor_type, attr).result +- op = linalg.matmul(input1, input2, outs=[matmul_result_buffer]) ++ op = linalg.matmul(input1, input2, outputs=[matmul_result_buffer]) + return op + + +@@ -1186,7 +1186,7 @@ + element = mlir_element_attr_get(dtype, 0.0) + attr = ir.DenseElementsAttr.get_splat(tensor_type, element) + result_buffer = arith.ConstantOp(tensor_type, attr).result +- op = linalg.matmul_transpose_b(input1, input2, outs=[result_buffer]) ++ op = linalg.matmul_transpose_b(input1, input2, outputs=[result_buffer]) + return op + + +@@ -1854,9 +1854,9 @@ + output = tensor.EmptyOp(output_shape, mlir_dtype) + + if not isinstance(input2.type, ir.RankedTensorType): +- input2 = tensor.SplatOp(tensor_type, input2).result ++ input2 = tensor.SplatOp(tensor_type, input2, []).result + if not isinstance(input3.type, ir.RankedTensorType): +- input3 = tensor.SplatOp(tensor_type, input3).result ++ input3 = tensor.SplatOp(tensor_type, input3, []).result + + generic_map = ir.AffineMap.get_permutation( + [i for i in range(len(output_shape))] +@@ -2038,7 +2038,7 @@ + input_shape = ir.RankedTensorType(input_tensor.type).shape + tensor_type = ir.RankedTensorType.get(input_shape, input_dtype) + scalar = arith.ConstantOp(input_dtype, node.args[1]) +- rhs = tensor.SplatOp(tensor_type, scalar) ++ rhs = tensor.SplatOp(tensor_type, scalar, []) + if str(input_dtype).find("i") != -1: + cmp_op = arith.CmpIOp(4, input_tensor, rhs) + else: +@@ -2069,7 +2069,7 @@ + tensor_type = ir.RankedTensorType.get(input_shape, input_dtype) + + scalar = arith.ConstantOp(input_dtype, node.args[1]) +- rhs = tensor.SplatOp(tensor_type, scalar) ++ rhs = tensor.SplatOp(tensor_type, scalar, []) + + if str(input_dtype).find("i") != -1: + cmp_op = arith.CmpIOp(5, input_tensor, rhs) +@@ -2390,7 +2390,7 @@ + scalar = arith.ConstantOp(input_dtype, float(node.args[1])) + else: + scalar = arith.ConstantOp(input_dtype, node.args[1]) +- rhs = tensor.SplatOp(tensor_type, scalar) ++ rhs = tensor.SplatOp(tensor_type, scalar, []) + if str(input_dtype).find("i") != -1: + cmp_op = arith.CmpIOp(0, input_tensor, rhs) + else: diff --git a/nix/pkgs/buddy-mlir.nix b/nix/pkgs/buddy-mlir.nix index 80bd03017..2491b0beb 100644 --- a/nix/pkgs/buddy-mlir.nix +++ b/nix/pkgs/buddy-mlir.nix @@ -24,6 +24,10 @@ let hash = "sha256-NDdj72oNhIKcU7cOw+RDzPrjKLIUVY63TDUrJ2DzYL0="; }; + patches = [ + ../patches/buddy-mlir/00-fix-splat-op.patch + ]; + postPatch = '' sed -i \ 's|link_directories(''${LLVM_BINARY_DIR}/tools/mlir/|link_directories(''${LLVM_BINARY_DIR}/|' \ From 6e7bf0e3ac575de8771e92c13c9fcbc1634cffa2 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Thu, 10 Apr 2025 00:14:38 +0800 Subject: [PATCH 23/24] [tests] update tinyllama importer Signed-off-by: Avimitin --- tests/pytorch/tinyllama/tinyllama.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/tests/pytorch/tinyllama/tinyllama.py b/tests/pytorch/tinyllama/tinyllama.py index 39143f7ed..b47bbb605 100644 --- a/tests/pytorch/tinyllama/tinyllama.py +++ b/tests/pytorch/tinyllama/tinyllama.py @@ -1,28 +1,37 @@ import os import argparse -import sys import torch +import torch._dynamo as dynamo +from transformers import LlamaForCausalLM, LlamaTokenizer from torch._inductor.decomposition import decompositions as inductor_decomp -from transformers import AutoModelForCausalLM, AutoTokenizer +import numpy from buddy.compiler.frontend import DynamoCompiler from buddy.compiler.ops import tosa from buddy.compiler.graph import GraphDriver -from buddy.compiler.graph.transform import simply_fuse +from buddy.compiler.graph.transform import simply_fuse, apply_classic_fusion +# Add argument parser to allow custom output directory. parser = argparse.ArgumentParser(description="LLaMA2 model AOT importer") parser.add_argument( "--output-dir", type=str, default="./", help="Directory to save output files." ) args = parser.parse_args() + +# Ensure the output directory exists. output_dir = args.output_dir os.makedirs(output_dir, exist_ok=True) -checkpoint = os.environ.get("LLAMA_MODEL_PATH") -if checkpoint is None: - sys.exit("Error: No model path was provided. Please set $LLAMA_MODEL_PATH") -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForCausalLM.from_pretrained(checkpoint, device_map="auto") +# Retrieve the LLaMA model path from environment variables. +model_path = os.environ.get("LLAMA_MODEL_PATH") +if model_path is None: + raise EnvironmentError( + "The environment variable 'LLAMA_MODEL_PATH' is not set or is invalid." + ) + +# Initialize the tokenizer and model from the specified model path. +tokenizer = LlamaTokenizer.from_pretrained(model_path, legacy=True) +model = LlamaForCausalLM.from_pretrained(model_path, torchscript=True) model.config.use_cache = False # Initialize Dynamo Compiler with specific configurations as an importer. @@ -43,6 +52,8 @@ graphs[0].fuse_ops(pattern_list) driver = GraphDriver(graphs[0]) driver.subgraphs[0].lower_to_top_level_ir() + +# Save the generated files to the specified output directory. with open(os.path.join(output_dir, "subgraph0.mlir"), "w") as module_file: print(driver.subgraphs[0]._imported_module, file=module_file) with open(os.path.join(output_dir, "forward.mlir"), "w") as module_file: From 20d067a0e55ecd156d29cb976cae2238d88979b7 Mon Sep 17 00:00:00 2001 From: qinjun-li Date: Wed, 20 Nov 2024 17:52:19 +0800 Subject: [PATCH 24/24] [rtl] disable chaining. --- t1/src/T1.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/t1/src/T1.scala b/t1/src/T1.scala index 88fc63c5f..82ebe2c80 100644 --- a/t1/src/T1.scala +++ b/t1/src/T1.scala @@ -684,10 +684,8 @@ class T1(val parameter: T1Parameter) val completeIndexInstruction: Bool = ohCheck(lsu.lastReport, slots.last.record.instructionIndex, parameter.chainingSize) && !slots.last.state.idle - val freeOR: Bool = VecInit(slots.map(_.state.idle)).asUInt.orR - /** slot is ready to accept new instructions. */ - val slotReady: Bool = Mux(specialInstruction, slots.map(_.state.idle).last, freeOR) + val slotReady: Bool = VecInit(slots.map(_.state.idle)).asUInt.andR val olderCheck: Bool = slots.map { re => // The same lsb will make it difficult to distinguish between the new and the old