From 25b7f147d1f10c7907d3678b6d05fecdeba4c996 Mon Sep 17 00:00:00 2001 From: Justin Chu Date: Thu, 23 Jan 2025 16:22:36 -0800 Subject: [PATCH 1/5] Update onnx_registry_tutorial.py --- beginner_source/onnx/onnx_registry_tutorial.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/beginner_source/onnx/onnx_registry_tutorial.py b/beginner_source/onnx/onnx_registry_tutorial.py index 0f64ba9c4d4..5e4ad216b88 100644 --- a/beginner_source/onnx/onnx_registry_tutorial.py +++ b/beginner_source/onnx/onnx_registry_tutorial.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """ `Introduction to ONNX `_ || `Exporting a PyTorch model to ONNX `_ || From 4ea18e1428b95d9161c846858cde2e4dfe88fe4e Mon Sep 17 00:00:00 2001 From: Justin Chu Date: Fri, 24 Jan 2025 12:52:17 -0800 Subject: [PATCH 2/5] update --- .../img/onnx/custom_addandround_function.png | Bin 34769 -> 0 bytes _static/img/onnx/custom_addandround_model.png | Bin 7544 -> 0 bytes _static/img/onnx/custom_aten_add_function.png | Bin 16332 -> 0 bytes _static/img/onnx/custom_aten_add_model.png | Bin 8613 -> 0 bytes _static/img/onnx/custom_aten_gelu_model.png | Bin 26439 -> 0 bytes .../onnx/onnx_registry_tutorial.py | 358 ++++++++---------- 6 files changed, 158 insertions(+), 200 deletions(-) delete mode 100644 _static/img/onnx/custom_addandround_function.png delete mode 100644 _static/img/onnx/custom_addandround_model.png delete mode 100644 _static/img/onnx/custom_aten_add_function.png delete mode 100644 _static/img/onnx/custom_aten_add_model.png delete mode 100644 _static/img/onnx/custom_aten_gelu_model.png diff --git a/_static/img/onnx/custom_addandround_function.png b/_static/img/onnx/custom_addandround_function.png deleted file mode 100644 index a0c7000161e9585859c331b73497557814345855..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34769 zcmd?RcT^PLwl3O;ib_VwNkBk}A~_>bL_kGyk_?h_Y`{QHB9b!*NY344RG`UCk{p`Y zWSX2B=vVD;pFQ?Dd*5@<*!R9a-gq^Jqek0QuT`t&n)93AH)n;tR8u0nLvsfN0ud@d zlYb2Y-5>>laLI1n1penz@nZyV!f}1ABnv8o)2#y^u3J4-eF_3q#^7I=+yFk`c6z4k z3Ib8EV*lYNzkaX_0)<#A%RhbNWwbSmpQO10=DlJJwZo zf0hsWa9xj^KPMPtWLqv-ZAB%-|5*Ff@4Tes1Uzn0-aPN!aJg^fv)z3g6v&WOR>tG8 zJrzF+0tLeN-`f!b(*+GOGcjY&FTaogHwpqhR1Up`J%4>5e;s>%PR4uqtPD9j**`_NZ0*~>`$Pbe&tVj7^ z%rTNt@-;mIGk3ChMs$ZP=+7?=8aAO(d*z$Z{EkvyEtK@L7r5;~dvWRY`{huWy0JIQ z+yJAE_TNV1k#dtw^IZRQYJl*oHUF$bcOQ9pIrFLRaOUpf$Fp~h8@250oVXx2TX%Q& zO55?WlCc{Qp6EwrR`T{*^Z|(QI_GCv-FBFtU#T?WrE~L^3N>9#Cu;Glp8q`ca_WfU z6DbzdqjwE9r0*Yac)aja$GL4zJHExi>xI6AILuP|h5p#sri%2c6hh&hFIUDBHp_cP zc9vc%Uy?36=5$M+RZEBDAN$921hDx&@(*A|O>c=ON+jE*Xs zRa@B=psEK>Zh#J7967f0Bg&&G57NmFa2CAT<#k)8Tmp6cn$p+9Zh@QvDI}UiAXY15 z%D<{ixUYwX-EcmtFyULlvu`weV@%)XGTyHnKPs+Z-$ykKu`+bpmV&Ea8#VF83k{np z;;T1vHFei=Ouz88!<@uo;OXd$)LzWxq}Ba9AUeGsy|viWnX!wPD{4d}cY!4i#MKmM z$%l$PvmKk}S;v8zD){S5a0grUD?333vN+a9wd&ZQk|IpD1G-MzUv<(hIggA|456kS zS3XQa8h)4HrE1u+Zm~Ymsh+QY97D3Qdh1ro%-HEHMf<46^G~%3C}(Ah1#V>7l+usX z!%(iYOb71c#lA@d7#$rosc>TBHii2s`N$CvtoV8nu9rRJzAZu{ac$L(+7vRPxtPIP{9&R&|d{4EHG zlrNrn*Qcbh&xQ8kQE2pz96TansdT&YbYT%xX;UG38q6A%sUgl%q(6jPaY9*ccTqB> zx~Hfmq0=lK;2_sE9X~fQF@fuRLEitXn@O0ahX52()a>&>YGfV9R2JAVPM~0IwccyR zQ%QT{XyQvPUf%1Lq)SRQgXmI`v|>Y=fm*htK59Q3GhWzt|NJPqfX2bGRZ|rHwCMV`wrJWyfY4W(UFW1#(PKR5{3CPZIk}i=@f9T~7+k5AQftueh9YOPh zv0PRAn5$N=rP-2p1YTJeFvO&=F9&Aa^L`AsMeLnQJ*GXgbys}RhG&XK{+}+Y@d=R9mCmHj0rzM^2zL2p_ z-q|lMIfob15tP2ln#Qxo@y$4aJLTSiUdQdHJ>IRecG2zd+kdEi5qM4O3ICr zX7zV;pA7X640`2UB7!B@&6@Qu`jFYvKaXX&x^|{3oK%JJ*L?z~o zSXbdpj(i}~cPVr6wk2h}`)Ll=9J2%smFrmhackLJC+rRBG+-3Jevr8m3ROx{sN1Bu zR%nC}K=t;VP{;#CqG(!L9?L+5?6iF*it;L;(-QqT6?MMOL z7x}q9IoCkxo3qC47-%b~GAr-?UVZ7Ff6G)E){wSMx}u`wjP-wu!{82EqhobWN)EQS zNa94%&-(;*ntmt`ePCRdWzGwx60YZIb)OoXX)#;s2|gLNo9mhpx220ty&e`dpiLLe zV^hx5BigTRk(GXr7;HZ|UspLP;XzpCeSLariKq2t*HfOa z!#`7+5-r$eOns6~26; zuumLs&kCTaKnISqY(%kkdC5(XlhYRjdC>jJQmio#s@^8S2|V`5YUTaU=<45ET)K0u z{xFj^WcxuhT^!?hmB%6I&J(jyM1^_{)?E?h~)-zeX6h)6agNy}Zsi2%B74b@q_zRLY#^bPj4+ z%GuP<-5|3hub#k z4!Yo*o!)nu-`QqxZfMp?Qb;%52arb>J@@yhE9@I<*O z4t8Cro1IZ7Q880R8*2Jp7MlGRp~{ps8jfv#s3whK+K^)@u}3!QJ_5*%(YvlIVUwTN z(Mw1RoQAbrpDu|i-}mlcPV&P%s+4Fv9#Zbj**}I=92{vUZl9o! zsHMadrOVIT{_G7&NUP5@@fp7<|_4yHOZwjd*Elz3Q0J-_*YM}(z#m7qTF&}mIJ zrX^pJDWttrrPy@22_>+Zr|oCdQeZ4W%Xc2Fk~7HY)U{H-N%Nr*_tL+Y`Rc9r%+#{) zvRp6fo5|eMCa7ysPEq!WX=a`Ie*284wclLO348WVu0m$IFXiZ6v&ou}l*3Qg12g2Y z#<%c@5SKPYz!8&}_e%U?Mx-%&+$1O9jX)vP?q{unD;wMN1of*_`b}qe-Th7a3BQSh z7CV&?G-M*T91$oYn9!k7tKj+0!zeROz5fzb$ajBxXre_;vi|Jx3X}8CZ=n|DEg7T4 zeO=O2lO$gGY7(r9$d=XMWr)5D;$$*3EH|Y&>M%{)3>+sz4)P(wy7!R6Ip^c=3zMIb z+j|D8_5=XdD{~K+3stQpy;SE&6P3v4=IIO3X(A{7xZ{OK8!1Rb24!oD%q)#0UH9`> zT_v4KOz=HSrz)U3>=^``{;5?;tkQl);wl3-P1~v zBLn#`{JF~(aQ`cvTR4nHG_`l(oO&aa-YtUeLq)*AiAxcxzXROox(ev3N5LB|m)!;#`#MEzQ`-nwOZT`K%$ zNEKbtSe}~K0s?hG^_Rnsd8n^u%vq5a90Pp0Mf5%ha&RlMSkh6KDsE)JrXlpX>10h$ z4#U^_6DMB_F7X16GA^UtWzdeko_}_{`HfXJ6komZ!2m!Vd>hY5D38N$hVaxo4oPtWu_&g+9QewrFW~Mp96| z!2x9@VINhSdeyG-6VHX(RMV-&vQ3*?pg|2PI05L8DwyB3jxX&DL=AjGTd2u{=0CH_ zFFUaSzDq1z^e=+M0~_jr<3AB6S*cIJqM*S!qQjFQUC0?GJ3ydK)ebjR4frjfz-i zQ9fI}l@B(BQ~FGEsv|_7Oqj1d-hl5R0SZ$_@B?zFoMcZmF$X zh!qI*7n=IvkDbGs3*iAlS<2nE;FX?rA@<>9ywn=iunzqZm6T`Yjw0;PS^=wtu@1hx zwX{dlUM%7-D*&J}wJLx*Oz`(V=0aDEH$$y}l_w&^qMU}1jbgKj{R7jEaPs=?pHz(a ziyMzLOncfc&6KAg%nWHSY5A{woCOr!&gM}YUXL4j8ine$oj)gCe-DsR81$Xhl2A@z zBYOUK)k}D0<-XEXm&GtQqtB;*aGLxZDcVZAsZfLZ$z8;uR~h#-fYEBLAlxZ4%U(X) z2|;|K+AVfQJDngjd^qiG&E-8={!+Q6eY2Fh!K?X5NUY*Y>a%&t_EJ zB_46d2exrm06avvu4+nuJ<^+Dh+s4#Y2!?6kCGd{`}(8fvkyHV<6AvncvY;;)ep2f z`u*0@Ox$UnFoM?!j+N>&Xs*gfrd3L48&PgLbW6XW`KszASd@UA`f{A7xwr8t=ksP> zGN0*G;hwb%*DOO+n6fVx*cn_7DcA3RQ#$BmsC#h!pfg1o$z|sgGgFm4jm z)5w+6;H2tMoI!OE(W=jOTZ=Q7BS38j9peXi!cm?~$6|E(&cl9x2Y_R~H_A&J11>6_ z+#EBgz#M?GLhb_gH7NN)gf~3*#~l07z@;PR`&w!_%HCH(TcdH@y|9J6p3~NBz11#e z;@p6%KYa}IP4=Fylk6d5208H?57Zt@isaARmu6me;ZfWK%{vmsi`h24rpu8ED~ zexts8PvLulL}a3(phcGc?ceZo`1I+?pq4o1#Z3bIJgLG@iW27Z++qN1TQ`Qt-5vly z_gtpIf%S}!;@DS_r7<0&!&#a%4RXz;s#E@x%T}>RrjykLuw25-Sl_wsyDUh_C429H65diruu^~^RDHqfU{NucBV{>P}Ab4k~byZG9nV^P&YksT_^h`ztD9d55?iI zD%<8m^S+o^J|C}hI|<0HCs#DyOy^N3yL;BrosYZo7ty(v;jLN_zh{7#0h1dPa(Uh;m2K)k=gh z6Lpo=+3|g2QI(H^JL@x1vu@U|cl$rW{7@aL<${uqXxr4P*ZAid^s7XU>vOXm?K$-~ zBcM48oywt!HLM3W*zl9S>d54^0XLh_5uDI)*4)D6`Aw2;=JG&NK;_WmdLYBIKMOmG zj?jjkPEqsad$A(TJCK$9%p8urKG%?xHie}r8Qe8rCIGKxK#yOgXC(F2uIhdd;8mK7 z-Xy~2_>RNPN#ptW3hHnTcN!k4W)$M5IX`_7dA4V6OL_W?;z^;y^y8U}m&?BlKKODn zh|R7YP3+of*_8<49VO<y@W&f=fhzKEXVCxLE>G863qVZ8W0AstQDg!gdJb0rXdpg_pu}#iwS1qW4c2GZK~t*xjm&4@O5c;6 zlDs2q;2TtBmEMhGJO{|KY?Avsz?#|GG#mL|jIaTF=J)6*{U9*|2ngG2fXo0K|3ks& zp8|K`KS4vf^JuB_HIe-;mTDT%kDt@cz9%@B`D4Jn0l4RZ%%S5Ia0ANz+7DWJzXW`@ z><_Y%zXFL2er2`qk?n2)C8n$7?CB_vAW z&`ku~{e3LXX_F_rZ**K;q|@SHSki0XYbF0CL!4jBq7w&ntng>^L(X>qYGY@Vv>4f5 zVN&k`>9f{tClBY}IqQdRHuz&3+q3#Itx zYeFrg4Jb_IL0R{(PbMUAZ*QLPW3@~Byb3Ui4#|Lkx%I~-*rZvmr&ff4%@+9*OHroH zRI$@(P2G0Hq%OD<7LO$;(3G={XYK$Q)P~Q@CP5`kq)hALlIe~({a#u$*Vt(qlERos z==X%DE107kh~a#ws-&~mS8JQ)JP{)HGIG4_AArC@y|)$YC0UFMs=;Q~G6TX3P1cbl zuIF0vm>Gu=TUB3NA?gIb?GGijL%Xl6C~riOvcDl15Il~#TXk*(k)l+wWr4+5@2}V6 zqdRve?$JUmkJO%2o{w2~JsGHbQUni6GS={cwMnsOro@9l$Ev^}KeBmKtDRo#eMX{T zR(Qm7A8ct?0e$_zY+%|!y)r$ zuD;YjjH5)#am`tFPP9xunw@}Tw$eDmA32+9;eo8f{$v@$8!NuVRu0vFNDVB~aBCY) zN>yH!n$a$Ar$9cx4tn^96#1M8x7TS3?I{vZSjJw&i{qPoM6o6Gms)$dwsLF4K)G z|HH=%>D%QM=i!%fMf6Nl3LPrWK)yc#c$y;EddMx%*D+glHLaVVwXuzj4b$;5LtR25 z@XE@Hu8p)&3Nip`XK2$+~dQnf+=OSk_$Ml$4NiFH5!qzQwN?aHd(=yKFcNs4Km!pS;S_s31geJoznJz`;|8|oW;4E z2OKm}bOtap(BBpAe`{j@pS}E_!mRuIza;YE#Z$Bv0Qp^lN+ZmAu>2K-D8?qj3b1b* z)@m^%L8I7kF|f@!9jIouQY`PRKF~XFx*2ssZ&d%lGT^EnjHrIPcR((wUAZt~+Pzgd zBwl(Ky4ZOqqcD_L+v~nL+DXlCr`5N&2AT-u7TdUXdo1PQvG2*Z_ z-_kD8iJeYpuUXJF+hG_ju5z(GqNlAMK?^i$4_8-YORfiDX;Fhv;<${kAhYh{w~+(U z!1Le(oSBSxacK%e1k^HK3s42its{4Zn#Z!ir2Ln;Io*SsKb>>40Yacg2C$YvHLRfP zvp))pM3t+l%mOIMXCiIwwmQJ6k7JY5n$+qg7`kfi6X2}Vgb0;+7%b_AFyEp#h$qBh z00H|j(gThc)@C|U@d$6wmkbCl*vvmmlCYkSA?-6SPn`#*`QlwMti3{@o%SyO9TiS> zuZqd%ecN2=(v{Y8?QZ}@Uz{ty#zaa9FiJ*XGY5JIod*B9#tNjGjhV_SrGi&279T{Amsntj!uAXBU7`+F3O^jt{6+oaaY@RLlY^ef?tJ!qxxj+0D@_?PFJdI>> zepTopSANyWrRlGDK8PdWPXZ&ctIDPo`T?>5nd*MJW1%$|Ahn}2V zw=$XSfDbT}SR-GTswW^BsFt=iC6*F-2Ru-#AuOW!FZc-f{GW&y(7(q#06+8Z5-b0G zz0ChDZ}PfaQdE~3l%-!>TpVH8=wTr!a!3z89CBWcRkFllC@25~anDstvD%=UhH~H< z;sTJEzWiaE9$;QhYGI?pTK*Tt^JbqT?0Vv{pBcWoMfNvd=PQU5WbKG|j>Z-@0LQ1x~>H~^*le_-{f7e+PQwz!~%yTIlI(M3LxY4Xr`q2I); zRnlc2QK0Z9DonJvYH16RV6peR`fRa^ktf1|l%++0|0Yoy9X{0D9VF6+rGw@J?PEHK zc|#ZCp&b(~x*7o2s=ua zFW#-R9vaq392Ngl0Y&s1TbHrDD{^wsTpip5Py;dFf$|Oeewqnvs*by9I@{pxjHxS& z1|+)#tijLAG*>=)mmBcro(~g6+MsmsYP6jXOR;B-+uC-YPCU9b8TY^yKYu!1wewAZ znhp%*e|Aoc9^ zv0adBcxGd_ca$sVj=Ry&0lBo&lDKk<#??9LNP>k@B9K1^ndxfahi8`V&2Ii@& zGqK6Tls+PSEt6b);G)iMKF;}PgI`RG`zSyi7$)3w@&*VC#^~skpry?OCCxXmP|K%g zF8x1J@T*(_7N#2vFeq^->AZz0C3bEA8KmCuUe$sXjsjqkra+B$$RXleU*_Y-JgJGA zWCL1?7WgL_(kAk6EL}PCT~?#4DZYAqVuCKuSqRbj0_sTB;8Aw4Rk98^z+jly_-5e8 zYHlMh(^p)2Ul9qI9I5f+Vx72AN^T!!qqVG960B{C$iFCQ5ezn85n1XHm`E|!xOE#) zZ-@qEfK)PZGtG-gCC5iI7S9Ys&CJ`YNpn%wO&(n%|Kino-KTwo0LG}Hp@xhLxo)hcV7KKe~O+#;7VFiP8PqdtrvW( zi@4Y*!*BI>*LS^?8OH#ox5qph2TS4+S}O?UoNiy7z>lNs@FpjNNZLe%qF)u)xQAU@ z#%O0$<72p;lx$8q)-JM|?PsY{O}!4KnTkEb!ALNkrlI0*?a(hTmoMRR=e z={0TJ)U9Mmcea&ts<)f6(B*Akl%W9m2>sbc-`#fFR4XexOys!x;wR#(L~)ev5!Qt6iqCD!ug)4iSlu>4U_PrMl(GR1CdQk&)pw%~ z9+icM&}f-Dd}aKVZr%2BEzwel>*|S%qUIP&<>WP>V&mf<|8V~hh;cfvsrk&dDyW3G zpizc2t2`O>W{o2+k!e7lG3iDcpEI~Qzf4K4D`YP}^n6gXdw2a;&%or%jbpu1@60dp zc_RzIqlyJ$(wr@XzMZy<JE}lwp(sf=$#_4l=sS(4 zvj5W*`JHM84u4)C=l*LVtWOt#ysoMgeIqnXYN;q^Bkjyt*986xtsb_rEy%t|4b4B` z6iTMtn<+3oTuB#anQ!07cO{EXt8B7XSTkQ)X^s&tYd83YV0|_E+BX8y@ynK`t?Sn! z4_3ljs79(!7~i@Lnl=6x0U|yz(TeqWJujKC_ywS}P(p=tvuQvRiRt2=mU&};!s%OS zty$gQy;YNA=2XHUx|~A*3-}O70^Gz}*u)(`bNGStx3Fuija48dgEKUrZg|}!$PsmENp4=l27y^z(3U#c{d3^}z`y>L;dM*Dfy4uM zw_lDqFJHf^8BvQM_WQwT@2Qc5U%9(_9aJIRP5e*Xl8x9WyU8lnSz@Zk_h(0%?_C+( z2~Y#l4Hg-IOPhXw)k4C|APcddPS^OX(&j z?-V>n$W%Q9z5u>q1~vk4{0|_gckwEWkw`))cy8Cru0I8P)qO0(73fiQiKk&Y@wKB; zvUo&q+H^q>7i+wH-b0WQLeQGpr_g1>7LF@agK%D`f?+8IUI$|x6i5KT|h8qn6fXNO;0Ay9AW z`();Hm`doejkr*-k(1fX(Z@`f_?ZT6vzNBd1!qicQ;ABPFWa2~^t$ch-nXlMgnBuz zdrWil-2s2_+ntp6d$oDKlCLf<7F0&epjW%nRTNlNlimkl_g1bPkv}eGKCFAJ7HxfM zstGaN+q0z*q84k~fN!N7`Uf3+K_dlhY)z6-0M%T-;8k5uSZ#*RKvk^O;|~}ddh;9i zWRyCo?YRnzmODFiG{ZT4S{7HW{n)!bdyJ(`r>eHrs=iA=ow#h@J>9DooNGMQC5X70 zwR)F%nB$0dhBm+pp4rh#CoWWWZj1=sndhUJ=yh#^h)U0v3eV3I6pW&ximMq*w8DmDt?9o5P(`PTuLtaN)vug< zdal4C@cO--x+2tAohNMORCz3A>gr9|cHx@udb;xg;{~(?NX5ic|Dxk`5>Z3YG9T0S zrru!?kE5o>F4N|{%x$YLDVpPsodVv}_NC&<;SGYpN{`9`_gM-FPjxqBig@}w$ zO1)`lQPiiA7 zF?dVDt>}Tu#h;Gf#tlvq`IpeW{`a6@p2Z+XE!fQHWFg*SYdUqnml@aL8_q7Tr4M2b zGGnK&mR;>{UHZgCr(%#_*3gTyd#$uSbS-xW;Pp&M7_0N@xX*+vKj?>vz3EXY-viE0 zYVG`=V;3p1&qN+LfakyDoZUpXv&O|`?RERDjlMON@KIUdkz4GcW?{*Qq5>-|)ynad zhsO>89PIF%JFwj^4_MeFX`mUg4DfES)=@i5rWC>IjA{4?@U@MeG-^EkV282ai`TeT z-;m!(*VS)xg(&*#9PKZQ7bq%@4lnzo@o}m>lT#QGx-}daH8RyXd=OoUlru7D$#kaf zLTEA0q8?(lbXX$gFcai#iFboqR$kA<)(;7rUi_kNIr=+fsqeOSr$YU3^#JC9r=qKV zF2dat_S>ZYT60VqwFc^3@xr5tBmkqXdVb}e+=97;e7|Te}&zhnAai4W;jSCk31AT`#ponpus&R z&#~a-RHxJQ(}af=w0C(4jzQZV&jtHZa{n0q5*8Qn0sboJrK7H$mn5ghH+~2ETRe%X zja-{SI&@vvWCqyezmcEAR`CihFH^)8mM>l|o+sR7JBf<$7nW>*{>+RSs-IQAS}EZE z6l1usd>U5{W`P?>N?LOJ2~ohW^qO{kT^FNO^iSb{il!;VJ|DWs)ZhJi)#D3+sKYJx zxb2yqhkaZVqCnXbyJd$wjVZGE{2-cPFim(l!Ti)S*77|U=+GQk%LUKH zg*8B{O_k0%e$`zT$8@f(^Di~%P{kwsnokNE{iiH}>CMKxau?tHoUAP_cVCHdyU2vG3Jn1`QcM0I95+KeJYF{g*WMP!(E2VsD#a{Gy6m@Z+l zAfPU&p?KlZqRdyN$a8s7Gt@Zj zTEgjZPRo%i3Y@a+oV+M-lQS!l`I0BBC}B#!1|Yyks1zwEJlFYmiU~6X1Abk}J_D^? z{UKNno#NQlqZ5sM9{s^oicvK2wUA+2{3XuJ7B&V4{uHd-U4nMR zWTeqpQM_*V%!pdgJZr1tOff!C+wnJZJ@IAIc32RFNHL5PTs-GW+ijlkI#DRynwoSgmywRaT;^pvFR7P>qojrT6`>;mH|*w`M9b_UHHja_I#p4~Ns#@P~f{rczl1C2#yE3>3RgL~m_e6lF+1qEv_oSKc z5~*Ug_Sx3dB<(JA)*WmXm~^@;y2n1k8`h#9_j!+a3FbJL+Gy!BbDn47d=^~1^j^Q} zAY~Ih?F>h)9FIcVwXdx9MU5jG{Re~JcTGMjWs&P=yJ9LuqmXG30hNg z?&d#&CJk(Rzt9)*`6CT{Ci*$?dnrGl;LK^4zzXSPCgRG8twqCYcd`t{gl6&t2xZI; z9LEVCJ_bnJy)O!ehqtZ=60yrKlN^qCU9@6ME*AVDjTzAMCji2f0kRL#>*>@QKnid= z>JLE~8c5&+%o~7vnE^~o2M~zs{y+}0p0Sg+ySg76FzFp&n7-eGS=Deu)+tA2-egX_M zQmoDU6u^NFf0dE}$lr>)6p)cV5zq>8&0-cQTSKu44)+N7o)7aC9Ce}AfCrk${9ojR z65kA*JIE_7`%{ucfP(6?USqi4`a9m%3zv(tOpLdf;|g)WXiOSDJQ2y*yuLj5`C0x*|J zQeYpNw?nlpXZ?=(0Yse)zIKs}(o(kA?$0&RPoW`MzNlv5n?!U}`VhOY;<5$~Bd>*^ zty}5qquR!*2#gAE^VhPp%OAMJ-+*-hhEO+CYnOZT#Q68uM;ePAl*TVMbCqJEX&vgA zqQy48*Qj+W`@HATsklG;<%WaQUYPo~pF6kN(>|x;{CNN554}ssw8*rR;ky%t@;_ZrT`y|eL=@#5{_`Uv~^(I#ak z=3=#43pADbx3o0YVRIUBzB2xupPJmT_)Ag46LL8ApQZR4&>Ox z16azxb*28_V%h(_p4fjH{jU;<*&b-?<#yeAe(K_|3U8DWBUr%p#=O(tB@IK{quI8_ zjg68o)PFRyN>447l4#MkvgjMi&c8lll^z@0r)`qK>Jh8~q%@DA3DT8(OTRG1NYz->vB$=!_nwgH~29{SVa8tWgC<4ZY>rU&x0hl7BC zlEsZJf^Ito8bE6`&Hk2mdIlX*?--?ziYb!$??^iVmH98RnXPs0C|^M~^zAUGlLA1N zAQIjXsq5!@C`O8YCMicvB&E;Vc7(I?aW@)HsZ?fzJtDVLYpuR81fQ7^!%j=Mix|T@ z^s8NL0R^m4z1o*m@*(Y%W8_buG(s*2RNGy>0zuQ1T?5UNU#aO)>d-d$0_PjCUg3Tp?YRgxLSkNR{( z0IBLp>)W|`(x42l+R8)Y2A7wv?1e22Z6Rtv9jPRi25x6zJNmLa&D6*q5YdjUxIP6c&ML8^NKSmP4Ox+E`o%FQ+Q#WP zZM)Nc{FPwVsM1Sso5oWjpFDros)p>8wfO277^Z8o86*dJH!p3ynk4_Dv(kKQ0@`jF_^TWu zmXrwKlz@x?=zzaEwN-h2=76Tzax3#@HhrT9mYG=F>8I3~1S%wF5q_~iq_76IZ{XPT z;N7|Lx@!lKl|g5>B7oLJK=UPx%6^ODof{ieDTlm!(ROBbCn9SZo;jF6Qm*RLInny? zdS=Ifcs|n)IwMi0DLNAh3XMm0X87;T+fO-R3{K_G0TzoFa8xq-1oc3FU0~F3c1Lw; zh;_FUf_(2SK>aELMX5DT{^BaDJbsVw3|Zw`9*0u`{k_B;@GH7lW)nF62ZRpx&;HFO`hRj5|C8E> z|M$J!Z*1j9FwX(ttvcp^eq8EeiRsg0Zn@lc>{Jz=y$8To8;SF^TwU8a*8xdNt4y#5 zz;Zgh`VWcLX-ypXHuXa48c0Ov@BMMZ+=nQHG=O-?Q-vLY%8X;UII7(u(GV$pA1AO) z_FqwZu@2dusJT7y)&udq4B}0T)Zya6WbiQ(k*SU_W%l&1aPY(zs|;^8xa7%9 z$;}@NuwNW>4n4iS)np(U^i;j8mDW3YNz!$#Xn=99TL`AD($i4pmB;ym16D`w#yYT5 zL(&cvOUla!^!5{Y3~5-DqWj)~=%NpSdP0CU1r5Fe@N=gMQn6_dMq90CG>rQy@D_F+ zf`{^3AYX_>)Cp!Av3QwzCE$PAYiwCsqcA*TK31Z;w`q(KD>Zf>)pTAAW6!skLgIqa zFg!fMaN;zdcYgD6nSFXVK5Tzc{kK4FUm?fx_n0AZC*vhy4bu)0C&oU?PS2pD#p;=- z^954mj8`MlQ7oM?1He^4k5LFMur&u?eF`;vdm=~N5ulouG@4~4vj0V8ktG0gqDzi%$*xFhyJYm2@L z1dvE4z2hkDdueb2$A<6jfwl{aBhr-^(?^eweH_rv4*Kb1S(|j3)9^0)*7z~K<4UW{ zC>F)Yu6Ll>#Eeik73Dy$?ffK&7dAE(J7Rm4;h|S2rJ=MT%j%*peV8G#WXymxOu?>I z9-$w{D8;QT+wRI~rduZB0^6?(Fv#>12hrsdSnWK(cq5bdt|E9XYZX0LAOT(Ww!<`T zjO@!0=^lII0ehtfgbF_3HMv(UzMfjRYb~Eb-K4RNk$jbIzyEq&gnro(K=i%hcj@p?nqOKA40rvA-*Sum{*?rju-AUXH5C-Qvlcbu z;pm{^^FS!3^`r^y@EhUp3-qGu{l(sTrJ>LAUzwod&Xs=iM6E(B34sWZMp79yXWS^X zVb$OB&ikm|-r50eqxW%#I~ix~!%V_q61Ub#lw*6NN`ko{C`)s0;$H2LG{+Z{z7DlD z#x_?aAy!QIIQ6hMjuotE2Gie%yi|dTYg@1{+v^L|@99(-RzJS9e_4U$O>?|&9-Q>s z3}Xu{m3eL62&}%X5={1dEUxvA=htC%M439&av;$I?oSFL$7oz4}e_# zrW7@0i9Y4#m(oAnWjF<`E;;d~lN%Q=oiS9R$fea>Nq~66Wt^a-{=WF?P*T#&NlG{0 z*_8%O8Q5*O>U&f3!@w8U8eKV;esBD0W{lD(h_Y_G#x`wwIZ$OgWAo! zKJ{PJ?slQ`qw^!(M<{f?Jt~uX@Ul66I)5dC&>v_@NQej26>ET79I8m@1-Qkh69Bh( zOZX4BXq|SuLfznr=xv%zyRlTq|DId)!g7oC#_e$o+$k}SPjpA`(&ERX1 zJU-zfzIUxl_GL0XhXf)aXfNH{M&(@h?0}}NMG+_zkR9|=(|om??D#HyYxWzm)03jK zC!kY44s{!pUaFqvy--Ib%+nfmYOY(hJs~1l97F4Ak~Zo0NnV^3c!ubD#`_)qN!!`% zMg7M&TKy0DPml6dfu^xcGjt{`R^o<+@S!n`sHtmv&&Yn4f8EZSVEh zIw<3KtO=N1&(r+g?F6Vt;h;CXp%%IlM|L|SN`m^~=j|G%=1nx4(*j#iXOUxzpdXJL zJ0VjART-aHayRyWS&@f;lM^%SPL;00;!Wl_tQ!b&rG~B6i<{>d6{k3-K=e2S4~E zK-Q$?hh49htV6o2mzOS-W`!T11VsQ!5P)5DfM8=k==bm-cxFu3)k03mW)8JG4uB-< zWgS z{lWYhKWI?zAH;TP@_!QBw+B(>*h(2VR&B41_`bD$q)of}xakS~muBvS%L*H5*hR%g zd)0TpZ;vCEijpS%o_UBxiaB0g26Y$%GW7KHF#nTPKwOq9Qi6Z)0sNBwOW>^$EZBTb zhTRW3qb zeT<6*3kDLrAWsy(wWTNtlzf`Gum6xOzGSMYd|?3K+sY{@UOjSe{SyAG8)(ygH^X{% z!;-AtSL)4$B+mTfKcd>luh+vkfubVF4&{Be^T^`xB3Epa_VzLKdrK+}i#-TNSTXGzET52gQt)yko+#*mIB&7Sg@yxdDz9uztqAne zp(Tj}eLlYQz4g4f%msTP5B3KY;}tpmY#kBp%_TDm0x!0KDhM7?U;MpLYwLMtBM)+{B&=V2gE={+s$z6|g3dOhj-3t6BOa44CH$y} zqPgkB*Vk65q%fIQW1zT8Z3|?0Hi?C>hVr2ktQ?CCA>-}QtEe#LRl;!p6^mCG4{)27 z)*SCm@IphiWOn5}8A zc6>RbwA5y3D!Z@)1_~@=lixBYuV#!WI_P297>70S(lfS|*K@ubv%e$lxd7e_Y89uU94IOKTKa6;qI1pZ+TWyAy z%PCC@43I@j!t7C0$am6{{fa_77kSL*Ji4Uo%K4&nG8Wzpwe%kwZ2#@$qx3`=k&={J zdyk+wTg>QcCHlLsaB3@RwqatgcPc6Vrh%)Os#pQy8`al0y-FP=cG0bpwyCo=KTexh zuf5o*^-%6RcftSYxmvC~F!_pKkfTL{Gh;3(;L5ys8p2l8nqufWRH9P_Sv#AXqT`kJ zn>A{C&)%O*BG=2jp*=%x#wHb83$a^DU+pXjD2fp=eHmGA^Li}Bb!4QHNr2vpag?B|j&OxC?I2<{h9q_iE>&pZ86t zJF@S3pq$Y@28{!n8u=rad*qArL@%t=U!Jp@o~P3u*we55I(bJCDt^rrco^(2CTeCj1v!rAj*U+9ceJv5u~~0#KXhOy!1*q-96oSEM+@NuhoxJ* zlG^>1)G)u|b)Eq3yLMWMZ`OTL$afLie`Y%u`B2>9Wn^aa1EjyZL&;fVq4R=ry>D6X ztgFV(g47gmu#psJg2ZEv#;2x!k+f61Pz0GE@f&1@4<>)0ygQwK$Z@T1=KtyLJHwjF z+IE?jcVrv}kwFARKtMpHiAs^KQbZ{-NN-B0QX@zw(NU3((xnR`NR{4+1&Gp{v;)Lj;H+c_kdz*A_xBi&TFz607ht16o&B$mNFWrp@y+Rb` zl1-vc{<_hjL5=i{z-}j*qQ@iunFN#sum0C$^r@ICA>}j4o*XJwIs8hLH%9{PZZqLq zp+c~c=AwoDD<$Za$EHc<)RTPq9dJjD1GoEwJI$(-LDX-O6o0~c25zoC57T38YI|Qk zes$U$SAi1`lF}q4s!5rA8N6IPbXM{Eiw4hMlvfkc&X$~yEGc;XZp9&c(Gp~#|3GGD zQJiNs%N zVbr_)5=gY;7R=Z^IuM^HROhQE)4LXcj6#OpIB7mYut^f)OXChC7yARk>I?dNQ^YbT z#XP(Euo<#$E8Y!TCz)nf?h!|eNqeDq*M?|huMcdy+wyH#r@XI_<#8`j0$+(!ic;er z7ji-;LKkcHUSS(}>v|4-@n5o0EQ|2zUwaT{pVlaOEls++pea9TMN~*6%@fn~-V`nDmV{ zWH0(MpYSoC(t{G^Es1zLeSY;Pa#x>Mb=)hzrcL(={kQmI^+aF2B&)?C*bJFA}DqUHLRrVA8K2RkDTK-nRJ%bO4+Z zbC&Mq&~qKBKG0g?_O{|&SkTVp_ga(aedfR);55anKA|NX;3tqJ?3X))5Etwds@ZP% zo&6oMMM5|cBk)jZrbQxwGuzfFBmZ$t199*wy0%tT%f}-Dwov*bbgq0z+pb1MrKiIN zj%p>;$t+}^zSh5vK9IG$Rs1D=C(tNSRFqHUG>f*BD4|QxjdR4jAj%wa${?y^aaP?c zkB5@7MI*FO1-y~YH&}(7d{eF8E5^(1PBpc5(7?srb%X2OLlu}VCyH1V3`dG zW?`C(jFqw>`J~!Q(XzG#H&MGScTRA-!+;Z$>w%LP!t@=5lp7FUSZ8OxAUQr#> zRwXD>a&k;YD;!rimAPX?$3B1${n?RD_ZaE*&h()eN)Bo-mC~QjQxk905o5Egk~`xV z&f`iJ;gxsVn_^2_-EI>J(Q@UYm~fu#TG*cB=IM*2rlTFz;Hh8xV7^YDu1tTLVL*t+ zADlrI+~~t>HlKUh2H0S11?-1;)W^)zYVPPocJc5j68$o|#*bTGoXZ&S!*j5S&ZC}w z4dK+B--9jA@}nu-RYrYsgHBU_hG$5ZE8qu36&pOd7o7Rbl)^kth*#OqtzP-)qa3-! z>&a%P?0ru|xg=1#_mxw&&?Hx%rWvERG;j3dA7>cPdR{MBJm)-w<;@q)#Gsm#9TBSY zsUDL3v(MXltvtH1uL2;Wg)&MMAlsU_n~+yK!twcZUFnQ9_BkW9D9j*S&lFS(=FIaPHL%lh^#}O^f{3 zJQs@4x_aApwuQC}rEGqA<3tSg@{N0Q@tkGXJ}Of7jnu2@Y$dZu*vwEZOalS8Ke+X~ zM|$_LkVqVt>Fw_Qr6ILD5w|sK#O`fex4beZsMO?iQl)w*QOS%4w{%;jLo3*47nkC^ z%+nHF*REEj&{^f6-L~tCXqL(~hcADR=K*(h%nV2EIdG3U9KVYPHSPv? zQ+PLGvnE$1dO5oHUz(z$g4YU(l|4Q~oB_B-?Gwk)C9o$K=SyQ(AGCP&9_oIrr|$e| zRjI~pH)^UT$nrO2os=a}DLLepeTex3?^2nZY>YB7S`ul`V}?=|p-nhUhsOL|bK!7% zqQHsCj(1|3!Q0yb+s~XG<6ETc3KrYTLRyv)LXAGrtJOymYO#zZV-4-tjVYeIOjRkV zNOxC4M78X>PJg@G2i~S*5 zEIIC{XMyRt;Wz4=KYM(>^;}urOfvIjlGF3>9w#?s;+O4C+I?&QrQox{N9bt_-;AE( z6G2@bJm}hNUd@hkUi4BQ6Y+Hw9cZj|k^GTXvz%?!{Um0Z@>Y7GEY3IaX9eGwTr&~A ze34_14~g*ZmLKmL0F+LOq+P70XhSz%$_^DRryw7_W{@LdrJ0wTUKVrM9IPN?!#}KF z*ld_NLP>z|P#m+vnRTGRU5j}s@&lzaZvW_X_rnAlZR2u*Ja@T=OKyKl!B zl&jej-AE`ORMU!@w{ZU%Sru1V13`OKJ(#5Ow4LGf-E1#T6m`3q*H4&wsD zKKQVixmhXgyq>*lhWKL-ue!O975y<2vOvVnzwoLxhe}s!w;-bKOGZfW%4i6!eY@7S zOM9`gh9a){!Ppd?I9*g2G26bZv|Lv4qPt~1c)R=d^dw5efbrGIRp}wI_&?_Z=l$3b zU}R}qTInDK_o6n1ZJ_YbuwU&`-v8!V;#%B3^h5cIGxt{IHZIe#1Jrf<&rWxKV`o3) z0(@-Q9%_g=3+<>aW$wR^k#=inzX*UB-TZ!^MT#>!K>ffb2LVf3=l$8N-~R#P+y^RkO2k28Wr~6dzR=`7bQZ{6E?O`&=WQ1sks;5 z3EteAU-Fk4myp>>^aXx^n}T&x#M1v%?l?J7{~$1FsG)nI0Rfmwig;i+z=LM<6qHpL z2z5Bv!Hy{a8pF%}*2I8~t`6{RhX<1)W%t?{L6&`(98B`}0qyHv!;JWT2&cO?YHN-n z;Y86KfVNwALWc}hULIBfil@#76Q|vS;djjZniZzh#5mp5@f>3*%{Bbg|7c?S zLhKnNZGC^UVbV=*IN-EQ0+L{$4LPQOeNR$X<9dhI)(cgt`g}~1to2dR($deiufk>8 zC`MCiu9G`^KrPW8NW40^|K3o#o5wwuPn1r_ zk3BU{-0ycUoJ4Eq3IDuo^BbtFLO1vtL)~ja39HNon%nM89(xEmivphT_x`|e2->%V zdQ$))lHl)uwEg{mv|{mp_bhIGDreW|Qas_gMuCR#q^g+s>_eTxIxbVyEY4y8 z6cioiSy3)4D|4o7Eh@Tve!fpiLgq9q`2fm{e!Kt=c{bPyi=eHOK5!$>t3zX~V*u8S z&*=^xh0gtjkoAVigD_`7Q{VMY-(ilIKub}%+)%gEV$a-x@shp=)HvdSz37jcF*cT?2#mNc=wn3MJ zh|Rdfit*c{zVZNF0PRbI31YaUPsf3ws7Mc9eOt2nlvQZZ15ozOgEb_hx*|2B;xqjb z_x{?i;48y?vK6Br01bfuVAts&?W;$eS5=D?poioaFJ@(B1wP`Nk+&&b`XtKq2kT*4 zwgTKdVAf9!<{_vnflt4?gfFI9E#wBzeM!`r6x0auZ)Gg`G2L<^_omGp9rJ{LTDJLn zm94w1O;jqn59aKD=SF5W=#T@qzDT33I=9jU)PuYy{xKZz7lHj39?XZ);+r^7oiXY2 z>)5p)1*2eMU4T$ig%G)P^`fD~gH7eeX=+m#Mknze!4ZNWT# z=P^GdmDnx+BP~d zn>ygCJNaM_`UY{;abKkKjryuP_H0|9<(;9l5@Qqw>z!T&AO#YpBut7pkSfH99w;*6`W~p{*+PlxtTmoYt-Zta3p0VeR=O zHu_G9wlVpsi2ZO%2V)Pjf?!a0SAeSoTft?qg`f5p6_789DNij5N@R9)Wo2uj=?=~c zXwv-`jK;gtZn*kQ-7t~{Z?D_3;i{awaSMb4}2rsyo62WDj7 zk??X1PqtHtY+2RGtlW^zHxmSp_5)^PLqJ_Q^t(sou69-3AE^il!)28+XPQ;Zk)f%2 zk>jXbpN%xV(1SW>dOgP3J}AsU=s?HEd(MpCYssB!_I9_GXdG90exi+4QtTCUn%+2n zN0UTv(LBDV+h;pqelZ49s9D%WZ3@0Lr;YhFtBD`T-U0BL`U_9nN4)^pz`SvS8Kch` zFB9iX?HbYtM_+7_R=SVDZtT#UszRPq(?vCzbT!2Lf@Lem^slXNvqj=&rg4Sh~nNz5}XA*)#jUc1%4!g>C(!Z&WKSI9rwgT3p9_N`sV2}X$(39C};E$jVj zV_vMgRl3?`qH-31AZHIrUYWY6f_CR{I+?~@*`KIn8%nlNq*uh@<|<5x9kXikRHgQ& z!|=(ALUpj8Wngm&cr?i=i!RK)RT(cWVt7ndI0qoSD!pQM#D)02)yJz)O}+X&e!gP6cZ^yf!--#y ztbV1^pHPxL?}c6pp?qJn6;RVq%A0$UQzx~wl;}v_%$hRGB$H-^YhJlo+Gpljy-?q~ z?`<+P;HN$zA7jH`%pr8UzeWCFPFZf|AUvF2*31-525{gwANkTsf;BzwT&JzD!a=Ra z;HOsMnx&YJ0S&ViX%t$;OH}XyniQvkN7->A5f(#}i-}+L)`e}Zog)v{hKdU1DZG@~ z&QLJewa9LDWkN&s zkH*O${b~4P=jCto%h{87j7sOrYLy(T@$^HH$R@FSc%xXeFCC38_zKMLq!EPT&Q^F~ z&^k8qiJz;PnK>^xg0esLBEpiGMLm-gyZuIgH^m%wZ=er8w0Nz^+nt$bmn`yxT_OJM zc7EbQ^Li@}<(u~+PNiwJ<13b_TM?mPwZF63EI&U!K8$UnU`UGf-Ugq74VP}8+8|)x%r9}1YUv8*zV?NT)g=T|~G)U8Muv-UM;(D6dc0$BWv>>0wWPI$_ z>38&=vv8yC6*%=9b}t6DkV*paa{0EqT(euq3}Rz7un;x1e>-UI+jsa^2~YFe*b6FU z%x3Z4Rh6DX?Sa`f-s#Ja6O)Pt%i^O6FDiHxitFdLC=!J;XuxM+tmLci zmggegLhoy+Yt0r>k!vWJ{w;eu;E0IIIjMdVzvmZ~qNFzDlzV0_R}$J<5N6&Ha!uNW z;!#AU*DV_Q4q6(@+84x3RswF|f(0`~4>>6NU%frxOY@Euav#?}A+aYqbDAw-gdbgi z;871=>a<2(fs>XiG~tv|Rn3)uwlWvJm&5doA`8y-aGi;xVuPCz!-{Zv0U;8(P`Xc8 z|I#KCcg1QjPSWZPKQvx}s$$OWCnAkr-^>`F>|UyWCBc<|KugjFYC=QDzWLw|S}yxC zEiI?3uL=m(Y<<|qEo@xkBsu1Y;7#q1+1>9(;EiK3Tb0J%5%i^ICaEI1jx@MpR(IA) zwNSR#av(0wPj=O=nOT+Dq|suh+g-w`Nli?GX=PzLnl36=4>b{-j0tBN&KVN}(DpS2 zEAN)k)v=1)QrqSr)7`cupsZ)8Ki4Qz92ZN_rG4ARF|i8Vv9Va8Fbd^|X@~f^&gXun zx8p^Q8H#o%l-IcnI`B~HZl2-W2!m6P-4{Xb-@Z#|V5{j5}?(VO@=cHL#2bFxXbdZFjC z*Y^5AIB(+(V~s{N(M_&akK3JRS8P-RZ*>hSk-D0;$RdH37R+0N4<4szXC#0TCJ0B0 z9X=_s>I8zTd4oIDF75W5s76k_XEeP#YDjCN^re~ANfvs) zZ_c!+6I%tKxkeCr|I+Jw4SgKlB*TZ~y+rF2KhF<;PUQ^!Q)2H|T2R-Lw{_sn)p?A; z`RT41IwR>!5{~1C-JaiVAhF&P+~o>TKTe1rpxuB^PA*od`g7*lq#XHLC2%{Fve_!Y zT)~=GgliGQR%-$sKh-{Rcm2uL;y2UIq4WmQl)2}%cv1g|*7+8nc|UcL>74NL%PqkU z(Kf8)GWlqYx$9l)y6;uOZVU%Fla!xO-q~_yqSHpyRhlaS?o^(rBm|(Ji75D@>+Gpg z+ham0Rl#Nehw*m(r`jF++c~KiREMHv`Y(!@*`|BW>zJ0vgE5J9zYz4MK%j19MMT*J zE1RQAUIECTenm1>a`+x=ou~=$F(zK0Q*?E{0v>+>jR%xT+6tG_Ugz7P1Q3hkjk~|d zz8zkxk_2Z=eH?B_%b=LEOe25i$r|$RZO!ps$_{Zb%oMScsjBJYVffY&xqcTWYyEPg zMs=sqrqcE@<)a{f&+u!jAJm!a3vF+D7fJ)|Lpgm*qw5vE4d%9}1fP0?KZw^>J2TjN zcA;@QO5bLrw=bA6HiNZW<$tVi)m6&FxAIZN>tLmd{vjt^WzoG)cB5{iX;!t#EmFF_ z@-XQ2DP2O~n7h`C!8j{~qbmTkl;GBG?uh3;6ltxt;C<`m2Th`1{yg`oM02EvvU9wv z*TuIp=|}=6xxtyFH@RqKI=6LScD29}snnzpjUN+1;GE}pi`l1Q6|B$|amg1OyLbb9 z2UV0`$mWdRk5*gZEGdcm>}^BSy_El@rr}(^Y0r%e^a_3;)+}E>E~Hr5F7z^fZ^oMD zNEGBSO#oV=XuY3cs~c8tklMXb_5yW#BX&C<%b&fONA~sVacK(t{q*ZJSxDBXP^AV? z8d`gowy>&z69wY>vlmqEsUWh<`YGX$Gh;8<*CM#d63jelP8W|Gt+qpi?p=&A5{nw^nx%DE* zOCeM*1gqKnMmR=KwXS=*3K^wiG~q~(in@gc<5 zma1;FX3{?9m4=sB!NY^EX=n!gk)!lZL)%-4blVk6Gg5oy9G}%9rCja6>t%`G^OK7) zM1R#EaFn5lh`Kw3ngYB(F)CoPV)mpLbE^WY8LU4*U(x$u+11gPa_8dVW#X576x>+0 z7{5O4gE=hv^qF%w+ENSDI_#VFI2_g(LEK?cZER09STOTlRi;pe9>wZ{4xedfsCaK-1}w!DP8ORQ5+)#|}^p0Vs~yx2?SoAvAK zwN7md+W)z`V_j{q*DpTC{E%t%%E7w}jRY*M!-rn-Gkm*r440}fYqu4uJaph~fAG0? z$|f%|152Kz%=B9O<2F?z^S{HqzIJsexa4Y$W7CUgn9=e!^~MUc7!xrZJ8L-Gv`>i>a7+Qq&JrT4Qys~~DZ-Z^Q-waV8K zW^=oE%FIAGjc5wg*8e@}`b$b7O8IOss=4DL%2+lSV?6wceUv3m4!GLI?hmrz5*p;@ zJA2L4aycfX_iV{uI_)Kp&lMmKm@8U`d5}02goNBR)161$5e2Ud*58)oWf7s3lEo`7 z@(`j%!aw}X|9UaSMC~kSCzbAC@^SOSV-ED^7ZQNL4={o^pfwKwoUD92_vO|wqyw;# z**WRz>dF=O7VH0sD+&*#K(x~UOG66>+Z*MoDanz63;<9Z#xCsq)9}u|9ctIkld=jt z6d@gnry#hzK$7*6izDyzLBib)n57x%^C7r|srJwS+bH5{7q4kN$TC4Okl!gnQRb&W zq34>^bl2IYH0uWpDS zz(nI`vB63^yc)s&fD{0r`Jix;I{0&o8cH`Zu;n(D{P=ar%*S zWMt&TRbY^8L2D7RLRdMT2hbqvMsFKS86N;OkY+a!D!@Phtke;JN^utHC&WU!8Da}QucFx1 zaCpBBMQbA&Aa@LanFII;8%JMn8U{E>Ge9fGPQe$tVpyo3!3oWs;cIXv1&?0La1w!L zE8qnGjm_DEH4Ue(l&Cy;y1TpUNz2m=U!bm4n||ts3=Ej`p#u#csEY*wfb?_v(y%do zcAnv2AGT8Ld3nWWAD~bA`C5_Am_{E@45aa?Y9rq0V>^lXw`2}TX=JbJwx&Ul6e$}; z$zEAFJ0RI{)CtLWaP8$!M_^Y0CbxVd>f%^s#5Z=)Xuui*C0}EQ@b!j&u+TdB0%`GC zlbanpDUQ+E%098UBp^uo>6`!Szw|+XaU28~&1q?AHgX$awH-eNAu)kG;Q#4!0LNU$ zH2D#xY9uOAfK&{A3u3)Llo!{1ZGj-gId%1>$>0Epl~8Hd^ya&x!NFIXIyd7_W&MDg zY(y4bfpD`3I!-hx#Z>FE*7nPxuJ@1F>m=9$|8gijaaH%mjgOzIk&Lw(U{+eOxXB_Q z$F=_^tai!kr-JgoEiR@@ApH^?m`b=bL{q8MeF)mz(Vj~_h*lpz9oGcO(?~2$+OItc zLj2}$jACkQYb$`dU}y4RD#o#rAOF+~j?jGz@V{2)6uqk0W#peMiac-?V+ET5Wx|*5kB@oK^(0SzT?dM2IG}^*)06tzX13IWOL%H zW6-{We0;7Tc+oaWDjy{98AK9Fd)BYzC#->TKS+u49Y!7{mPcQaPBWP1v0_7w^ zY2R{s$>H*uUT61lEOlBn(%!W{Tv2`Ejw?2NU{iou9A2mP?=GG{w=@@acc(8+S?hw> zASwXX*^(ADcU~pWv90I}J>~&sqAU3&znl9pp%asu^!or_oOG$`9_Vvl%oE4IDS;V` zDu>@rmEL`na$*nz=w6=CEGPh02PfM{Cc8@?wJzYh=!RLy6s3I=Lt zTAlPRRV><@iF}d%>SrTlOjECl4==eL6@3&M78p>odR$9Vmzv#}YEhnTa?p!!UCb** z6e>?t%pFyqD~oFlmhLY2af?EtL@t03Ry^11heRq zlYmqS41_^jK;N(lI56y}tt&;&5uL?p`g*&Bw{S}ob!w}>1OD}8N8zNILAPSQs-}r{ zos>5E?nP6heqo^p0sXWR`k&_>4}zdy10~^@H#T|$xDWu$pF>QXV`DJOnik|c?-yd! zS{$OZ$PkEh$Wc1gH2(*$xq*Y|cGf^#vw~25i%iuC_>Gg>uZDVd58f%R0*v963YPb4 zQ5`STl!w(--I)(NrBt+wVCt5D(t1rZzA$190f3p&>0`mZb6#etv6u6Gaya4nmOUM~ z!KBdgv(|T>mf#tON%QQt=vWu9X_S^$I>a?-N~OXS?t6zBH4n<1@~jeL$hg zsKX3uhZ?N-Ig_i2<9QjgM<=XgKG?NUTyCzVOPbCKDOHafoCKT; z{`F|C^2x5OhU_BWk#T`~N!0HFypI7c%Bie7+sIWTby=m!XR7gZ4>0g^B3om3D+%pg zvrok)_|gAI)b6Fl;O^IP_qIEZ#mQACq*brSpQ(<%@};F^ zeP5!ntJk0^>HFiVSn1latvBc=cKls40M4XJ;w^QjF>-QWp_BMMlf@Rk9MAY6tsN7? zE8Rn!ZG(Ywo{h{v?)~1g@P0*f7l<63>HCe9dR*^6{M+>rtb)4#y@9u(@Pl!)6=t+V z;&pO;#U=vsqhnGO$9wVJ6z<%hr>uW#{6m6Iy1-!Aq!~bN+GDTCzdMh3t?9wW0Ry!U z#cxo`VQc!aD|jwc8kFdSW{yZ2CiR^@ZPW-w5H?<2`y?C;WKK3*53Bx83SPg4s>zDM zMC?AO4f{_q3^25VCQJiC6CZNHOgJmAhJ7v|Wv}9(~zB`$iuW zI*vuUtAaA_!k?V-(MgD%A`tV={$0QPzsjsf*BwB>Y6F2^533;4UGVw;MfSLI+!5Rx z^iV7@D12VH{9meZR?Yy0b!J8S-Ae=6b?NCHz}5>s?V7=2;~$5{fK~thPYDqJm4yh6 zy)lk!nhTQ|1ZCuAFq!Hm+ClwW;DXC_I+*^6D`O?@tO{&k6pPjp*kb3~A_*J}($clb zEkoUL3Z0pICJ|6EoI##94HqYtmx!}HgH(v-fcK%pe^h+5vYoP&4Ied>E)EcF{PEH~ zF9syaAQSRu3G8$x&*!}KT?7UFI4Bz`S4!Ktdh8S{#YgdKjW@P-(9PE=VEaP zcRa`8inG5z&WL}$$Yd?*!wGm_kDiWE!k^>T{0Xs9u6uc7kHrS>%%hMwz*rF8#be-76nJQ( zgX{=tQhmrb5cz;BU5nYTZ$=4R68eSoOLY2A;<>W z_ojbSEC0d001}7p$J|;1qDVSiEojmqSkKtl*p_`0tbR7OV^K$Ictf@aRK|uk%d0zM zAF*@v2nS<%ARphsKh7m<$BTw7zX}0_jnU3iJl}M#f(-G@Z(`0bZYrC$EM<9(^4$YK zMcmBH4B@xp)u;T9(U2&%a&2u5N8cVY^>DHNN?s9sok2plGvvHv+;$QuRzMy+dICJ8f?H3b^}GQkgS0bdTCxka$d{d*FRH{s4_uQ(Gz%LV zl=6UB()$>6>3QfHY}67nk0hq>md^sMCVlabRuLtZ!HsX7TUE z=V|)If5nJ*V%yc097BVukU_Gx`Np5?mE_`VCVxw)OBv7!I}&Tw5Lfse8RX{9J5X~* zQ{~)hGSTmiO8|LR!s+FTM=bE%mD7ARyXrdH5U)Ef-b^07>seVV02rL{`MS!YiKM>ceHG z9&|(2^TzK@SQ1kBli~RZRpjlQ$i<2f1Ub5yNu($u-7I#j?f6w3>D$&jGjqRGil0jf z2sVu^d#&yt(SM!R0=uK9h8qg{98P#w9%?uqzn>k_fN#!BZ6l7C_ZB`_#A2<9bK1R9 zk)~-GCegiECCm19-~8vBLY37Oi9Oi(nJgZ&^n?aYMa@GDjo`|m!8j5)_=o+>Wod0@ z@R9b&Px@d`AC8{Zm4zoH(Jw3vX5exg_dNXkk_7pK;-x=W4`?^_?9&jUC}hNy{QeK2!$zB_jMae%0^jjYXB)&MQkFN~y}na6+ERL+#ouFT1ZDq<5*K)hV|5)mcO?t>41D3b^r|F;l$%N4)5*yxqNX zN!w8lsa`^b7Oc6S3d+?n*vCs3UZ#$)-rv=u2dOgp`!H!?$MWc0Wjf6wTr2kq$V9Im$#MMB~G;1=E z!;^+n+t)J2#M@mwpH?n$N2 z>u*nN38-#SGb6U6y&+>69ZxWi>$g;?%N1C3h7;3oCb9Xh3Hs34XR7zOL!OY){+r=+ zxP|Uli^)jZizR~D61%0IZ{(#n^sj|eA$(I6}VJn6x(F{FTqh2Mh>Uz4)cSoDl{LFf}5vs4+rkwhGgwlz+ zR*xJ&xAv+t$kO&o?0JSMyuUgnP~&7W)hN-jtu-RUh@i~~t~nAzAQ+HM=Pl@&-lBx_ zM?!CO8hBe|*@U;hsJcq`Diu%0>^94W{;KM{mZ-XE9J4s$cgv#6C%U*i3)Ns_*xsh* zHpldsI4k7}ANee3Hqt1bwU(0L2J-%58u1JERiV|^+@~lH59%Jde)}mhR!^pml@rH+ zUo-?Wv6{ap=Q~fipYXBAWhH2A_f2O*Vrg5X^KWV}4^qfk1?hURP8L9A_L5 z5U}P9 zzE<9;Rc=fDF99&?2SI)?P}-P2qcgb+jH0^m`N+IoFy-=N)1p(KC^R+b!pw2OC$nRH zGdw_(4+lXCXE@aoWHxSZvn_0J&AWFAs>p0I4)dHn%x9(f`}+F&JiI^p!*m4Hg-&e{ zCES>Mnz;`!;?eY~v!P_GLSX#+_i1ST8nXSZh=pn~mLq8d@a~m4KxM5b0_WlxXjqT^ zd9>y_lyi=NcRtn637sBksBdh9lG!mTDBKwRb-o`3=X-B&Z~bho>rHUJ6C568Rb16r zuOIs>m^cbW2s_Iop0tHHg&vUVU3(gUAE+{o~W=GY%gBTnz)k2pkv}qXBH4o51QCh?=5>Lw~%0>_F;$eSH(5JfQNd z&llWUUxtF|8pyi!q1^Z10S$4hPaz48>nYv^sUd%o=AJdRRHoQ-eWpPX#47b#_?&=3 z;C)xg3Kdq~Y5#Ch(j!1zU@ON^$x0e|LljU*6U_nheSM4~nN*?R*scntA@%dXlNzdF zfP0i!XCM9!0{-YB?E{T9(2_PEP)8-V^TWTES=dxdc&M#a)Imw^2#AYS7osIVfmyHJF>@LAZV7+VnBM_%v@M|aOV}WHERF(36@&{DI zK!f@fY`oNLCF&g8N$7=kthn~IvM*Ku1U}v@k}9w`0mtOf;IPd}B%QQE#DKIpS`pUs zTIT3apZ|`9f#dhT!cljr{}>KIlj#)X)dN1yGydXxf%doL!2qP XYk$35U2BP@A2QT4(XG0A=gG((XlQUe0giP95F1W{1wAiaqcDFPzB3MkS$ z(wi81sG){3m;cPHcjo&$Yt4tdvhK;b_pE!?e)ita@4S7at8ty2iJX9d;JTJ3)PR71 zP#L%$fJuOnpWMn1fFD9n0}T~|(tegT;0ENNtOLyW8clI#Lk!%LJ=Zk#Bp_hmy}Ags z40v`32w1OcL6wdCtgy4x&y6rmm%E6+Wlv$6uw%n?3ndPb1R`4h*6sw6)C-w>Mx}I= zpel{#-9g!#bg1`>qO4(^xHxNJR~H%dM#H6=iQJb$HR@B{QW zy4s@R2$>luDcvy1lctzvcZ6(t*1ws#lK##AaC6HcOVPoXF5n%(%TMlKlL!`gR3I-$ zz{&)=+#tdSkr2>=_`iKX5w7t=H%meAwq9m}?|h(%5WLwH#(a>FWZ>aZ5y2|^Tbo-w zqC8K@FOSp?-J9!{P5v}ft8g|niL}O;v5gt)s3>V zx1W@FsUFIadazzx);(3_f+>cd7u|z5&wy-7q&=4-&Ndws;}tJY2KXQ&!^4}j-=iVW zCpU$y62jO}n(9$3{KUk>)#8$Csf)vTFwAdr#0Sd;pGkx7(L*+&2e=Wvo?qo z`m!)i-llBy_wQ7v?e&1JCza-cgfdYO=zz~LaaRzp-eIZpSwCW`s44E~VRhs42?hdI zC2&r=&<$anxbfHx1K8c^>~6^fiP9BQwtBb4MNxjEB#QUvE(xriH%;XYL(qpDY6cb1 zJ?gc410R1z|Ozt=AR_F#N?%x9~Ql+ zH5xk><;~+H__D|^+GRytPsAA>CEa4qP_zI$kbDa_lxTfKXCPPg^QD%Ks)&m52j0FJ zzClSKZZtJD%g4=34trerO`B>yW|euK;4dBu6mJHU=Lf!yU2JII;Q|e<^@h8<7=4p6 zln95Z>Um1TdABEuQ`hhmSh$gA-iEQf=xY?m)s}~elNGkVj{8VdWZcf4Kwqr3w|x|Z z|D;WRyC^j;VaGVxH9J~=@D1LLBdcD86M;u$z)ufih#(SJ=b?e zp&N9R+-@Jv4>hc(a}82V8%$njnxy_+tvF!3eodG$Cv{$#yH5$fs{wP&DJkhatLf~B zWX8OQpXCT*1y1eOC}YQ+Wt;MkI+zii(Lp$Fwug=inQ?6^h_C7x#O!9kTXX;SXunzWtK^U*IHa%L}G$I*CgGw^I+%HWWjhNF~47p zs}ZN`Es!zy2CSqfnIo`?w`6@v>f{1XD}m{Mk+Pe|=#+MpbEm!>PcF=7=>7s5-w{LRY zD(K8MrB*yEw`lOudH3#}Hdf^E+9jh^y;oswUS5}8&>o#R`LRA7^qYsLr_RfgCtp>r zk`2tZCk@feFD)&-j9Q@G$>9rUINyy%CxnC$oh-bf=DB+pw$-?=I|6^;w`1L)K455R z>2`XF%S~&gyR9ouIB{@0h?@Zt;y4z`EY(}15p;>4?-?sLadR3-N>0Axxzhgu$ODtK z+59RHH;?6BMrq_;?&#RqB<%s0bGshm^=S|Mg6k^lVBX%&T&*mtSTkEszHe)H{Jg(b zeFLoQb@yC;4oFX(L!UJ4_x^TO-&mEQj3D@*S|>?H$XyGAD@xxI`}CK=TY|1sp{1)^&8vh1)LAoF)!N&uExS?^aNrY65RU=V10nqVXqYc>41?dDb2 zBI!28Q&?E&qjmNcKeTxDs=ZM|y%fEHmWzWj5CvV zphFU}YIIbP_vRo9kJu6|Dk>5UTvbz3s}{fb;SFBj8M($eHa6yg>83X)=P;<}J08+t z?>fXzy8+8=K;(erQ^!LPd^obre%_-1yB(VwA?AW(w+t7U{TV>+8V{ro=A5uU6F#dym-b;>eJJS{=v*{iunbWG6H!f=S#hjYz&+?_d29w<`71FT zAVN~t4~&IeNm0!Yk6$Z;-%Oafr&0RsNx^Hn$-K{hNM6cZ*KQdntE$wW!1kfm;~(hr zym%apPN`9J#3&T*yN=`v_uU(}&*Y0tJ;O6t{7wwYPe1pLoL=L%5aYL-Uq6hlp$H*X z+*+`ug`iwRj;g*kaQL;f;qF$~>qXX#J1q7 zt=8T4%P!#;htbqy%@`8aOH@sm1FOjLD%P=ry7{qk}y>ySwIgRoeN;hgEFLn@- znT=(i7al{kexe%Jna~iLgxsWbMM(% zXTSs=jcZz322`@N$>u^_ox7Zgo>8=oqPCc3Ky^3`-4mv4h%il!`!3U`2jO zEOKma2|GU?@;q9W2aFi%+4OEP??r6#&OAl`kWlVBI!*5t@KXs`me${w#$)!fy}G7X z>hh{L5#q@NR`*UPZ?$N?e$K4GlkqWgu(ry5R%U**(C}cJw_2u9#K1g#r!Yw~8Dl<1 zTjgc0c_L}(T+_8%D3z?&RQPJ#DY;GXXx_cn&FFeZfmQj%$p?G@YP-#Cx;!c)q}pZU z#?TU3-F!As)Gv2S_6s?v1&(x|Wz+{M&@(#Eq0y21KB6`3wHe?e{+#JO-PQrbP7rZE z%RF<{LG#qL;(#W1t(~rxrY-!=v!=4GmW$c(nj^tytn|;9kBg_WCm$2LhT@qgM~h8_ z=Pi9_G0dWNniH5l-99;uSl!bXYxvn}x3sy9T2-AvCPnQ)aTAss=<^bJa?IGLdY{J- z8y)RQN!36VcpNvcP8vUEtY|7(rhTM@(z46~Cy-RsgAKqZr#faAE`MqIa#pWx9G2@u z*Ntu=G%qN26EZFywtjtSyZ&9rFYO2Mx75dBsW|g<-zwDKx1yWx*HNMP(d=cfLAR5m zgYd@FNQ z<`(_9eZBTJ1&tkUP$UbZbmda8L9#-pxS`HB)h14(>)(Z(w5rnG_wrp8pfZuz0&-_2 zcj~~iq}{w!GsLqpoxRFGfyw20Wk}09KK!Y}gESsaMN4nz^#jyr>_RsewwZ+}% z&fxxid%$W#PZbupkx?vaU>{M|&|szeuQ<3y>F)8}T?yDTTC3Hg1C3~IG{eWo6o#KF!*D7HMzeDhh%rDK|zU;F{l5sk(E z{>l2Qyl6oG%Kn$++wwimwr8QUvoC(&=qP3>pReTm#uCx-G5=^t+hyWW z+MR2kV z!$4DGPM$4M&zK$;$7S0Q5xX6*B70TJ1HtRJMTLd+NGNY{(D7-DWH}Rl4s?p=e-G+MdyuYGY!HJ41OAC_w3me?`~fm1h>BT?bxP&8h6@<2;$yf8Az&`51MY=wV?CEBcoxS zU+64l-}A{6ZE_W;LT0xyN$|w~@-I5qL95O$6h(*#1UdiDnA5*N){hvb$&*JRJUpQM z0BO4<0<0QfGSX*-3s#KY53Gn89`tjIE`7`pUluf>wy@7>K z4zsF=m1Kd||AnFbU$8&DBPn1lHURJwfzLI49cCt&Xn#|TWHr(FM-_8o)ik9082iVaREypQfOzQw+tEI0Wn_EzD z)gS9j)_Jb{Ep6C}1B|ievWi3KZPEMlU>qd+c&Iu7L(!-^M$1aiEEtk#dGTzyqFc{1pz%5Lc`_dd>!HI73&6Yk}GyGx>{o?F7 z;Z*8jpEgq*GVc=tKIDpt;LgA$=J^=^?Isg)3JOebidglHjJJ@89NS+}DLr|xq*Pe8JcE#h<0-{Fa)s4A zq#p`ak5#zN$IGjBda&UDds$7<_y)H@mJtn!;5yG4TL~m&&4X)fH{(k)R2{fLpES{Q zPZz!i`%pP5p3Bq+o!9>}{NLbMq{H%#)g3LZ*9Om4+8a0;Zt`jkzeyZl7Z0NtQxHs; zUBL!W-<-S;ZRGlw?!##U`V?irt(kw$+)a}c>@Gap6<=={ehc&u_GZ8)3N-L&cDf1P zqx$yv?vY8g@`p7A=t#?Wt<|DULr9oujZKC~pv$tP5wblHwOy=D&Rl(+@w=Zy zd^3yw@X3VJw^Bt$)}<+a+h^67<|yk6sOcRZR#cR)#AkifEUq z>M7>2b$V;*z6MDK_?4AQ;}`l5jB)4hEZwVu(A}q$S~4QvY%-euA$6ro)Cm!E=DBU9 zhW>JDlCxQeB>yJvw7nv`*QSUtxd#^XDc`#xyPGap7v#D^TbNt1+3Y+x+J>T+qx;A6 zU!`kdkF~7g68fDj5LY0U1MJVDQXZyhZvU$FVP=hr5sPNw^;Z->(|8nNultI`(ZP~} zzZ!>_3m>5*;iB&acpOp%4Tc|Mjce#js_^?g@lBa{n7`e7JJh)JSqQmFRsSI=?<7Ol z4ynDWetyQTK&)1eXYTIrGrU0Y5ec?9NSodnf9Bv@EHPDtIgi}DRh_GC^(Z*Sw374h z1olx40PXJ}#2%>s z#DHI4qFIYy=XYGfb2Mgp^W3!90i^p%4Ay+cr(a@I`HR+nzXo z>x{C2waG_4Z)NY=f0_9bvPnkN*R2FMt@J)Pmneo7wT&J2o2+%%Q6K0|1U5gS?;5bM zj47hap!orC5oS}O)RnnxwUBhSVFD0wm1{Ze2w5FjzNQ%1U7YT+@yL&P#_w9-dsD7KJ zU)2Scu%26;Q+_MTPswN_k#SaL*CU`;P|sG_`vl<-A#VC8fu9#amq9+Apz5e~t> zhgys9w&T4U0fx*pmsN7+6xB5c0L7p@6wd*cb1k?jfqZQ9GoAg;v=PNz|2k_`dhDAZ0 zo>;Sk$UA_O-!9P2_;WjEkW)fJ z!sBGmejlZ>!VUU#M?%8beXfyTxCs|0NI11M-f~e@#{>HG3P`!Y$&Q|=sA%rz&zf`o z+w$h*(5=Cbsucj3x}sIu=?{hybHrX`S|_Z<{zL@rr+MJ9H9fY`@_ZxtyWWJ*?j3wr3&_!~EFbgqpQ*k7ET>|un_ra%dE3!&K=z75?_hOoW_ zj?F&s8|G`ey9?;q`UKMp7)(|KFrLw zvu4c)mJ7Ud-+N!zuXf01S#cCZd_*WHD3nhUA_`DY(3RjP5gr!&W&H1}T<{OHor1U! zRQU+u4*2Dzsi2G?6jW6t(!KsG@H>LFgt{FR)a%aYALw4ILL(?B0sT)Rf=bRh2TKTA zSeuWq$GvPe^fT7<{U~E^a=OYWS>;(jDa7r0%8(78C(zD`F2tFub|Y#bj@b$-wk!Qm zxKCXhMv2W`v_@j58VDE&V0jol+T=qF*7QzGK6Br5H`z)TjIv8JT{d2R>o^BRPEH=% zAoV^ZI9NIWT1-SFzYB_joLtes8y!9REpuCNa9t;XBPbQYxy=fxY?_&j~l$_qAqILVmC)tGm0QV&V8%vgsdOBVDw9 zTCWYQmpd0bJTwE z=DiN#=3wHCW?Z6Tf$UB#gK*I6?(c$r=LrvYyCoH9*w}&Vy^(tu-!0~8L%~+T0-9q{R~!)e@@1RNHh*;NsK&?y&hH(weLB{8ejTM}t$X-(ce_EAJ`^Vt{l1&qRx zxO?a3QvPgc&~NvBYcAOuX4Y!)ixuEAmYitZR(056aHMP z##}2aD*pJOT%v+1GfC@6~Bqr}i#zNO##>sYxqPi?L*6^vf)CzTHeo5t^reiUs_7R74BczjuE zti@Nba`P&MK#Sd&+y!++2L|-2bXFC_#@sWon@@`*vl!bW%pu*>U*8?IuJ=V#Gcv^vlK(ntxlPUQ6bfe)18rcJ`%B~^To zi(tUqA;8gFwATG$U*azre?C}7x)IF_ooj}XcCEyroU*0u zr9{{X5F_cMr8kz^G^CHY^zMNHhKHfu!S1HKJ*daN6LUznZP^JvUEJ-*naQQ~NDJwY zo?E?86axowHooDHA@N-u)*R^ieDelcl5qZB%~+b)vO;~wEyrKiQCi#daWCg789Ybf zE>GA>q;oAHLia~byRp^f4#cW4}m; zGpCZP!3V-do9zvaq0A)YBd?&giUh`ASU3~WXm}9lTRxS{5TEwo`ItT7td&;is-9o* z8QwdcJRQOjPUZ+toPVNRalR~P;-Ge%T;cp{ zO1o6}?iK3fN+(yH@sUmNTxQLs{l!_lPu#4{vDeTwA#SgmJ+4n-Zi&ECnY%zEHVkBc zu>Yn11!|A%OnVZWIXAYrGt5}~7lKMC^@4H=*50zY)~9^C?{M z1)c9un9GmVs|W`R&e^A%(axi*7#Ap-tS1_Uh)B*4U?{z+Pv)ZrB-)CdCf0~%UmZ5~ zp&NVFqaZ<6N=DZi8#k~eVhx?%U(3n}y0q5UEJsq2ZF@Vvbh7kmn>81}l1Pqh`)dM2 zBz)XrbH9kARms``UmSvXxHFv##ogm(kRVo#KTILI>Zh$v4 zw>Zy?yYL84l>1goFiQ+uUp{f7m;)uPATXik{J=uw!{2$f&GUeO5DQ1@EDnS0Ni)vH zLAcywMSflMk%24Dp-ZNfWD|6mODfxUOqR?&S0{n01n-@%lKuOxAZVI60m>^ohUM^` zKjNU=FXYkg=iTsn-8x6F=h=HsLNLWCc2m}^@_Dtwk9Ia0X=YEqtiJWiKRLAyRGfz9 z{7g*b3fq43D{nSw@6N;mBK+ny+p1uJ%SQ8a&!=R(W(2dTq4^f>rH!AXgV$;d;?CdY zrtije*slD-80gUN`sA*Y9MrcqBH`tPg<+pZ^WY_g z9)IW;RUgA!7!)Dzan4r!zL*9qdK&6Zk69@fHtrAYHF8kykQC{~CtLR8*G&Av+!0Pf6Dp(l zgPGs|l8W6(GFtbjlrE+1u__3?x9aIEw3!*bz$sdJ^9>@p7`=!x!Vll~z+GneQUx=o z$1@Oy%t@}dHz^5Ovn&mj+kV-de5;+I-2FN*QQ%;hKphB22rA%*G06?c=! zLDL31h^8-j4;+wcxx(nlT78Uimv+ebcS%;UyQ89l%cv$lQ(Iq`Q`3a0{y|S^jsEVF0osZYS5s<{ zOwg8AL6Hqzq_EH-I=5)YtL3c0s8V&HxuaqVPUeCZ=E&HUH|5{V8b3Z|J#JI;v|DI0-q$Td7%L<@5epP z?A58V&Xx#KuE8YqJnq4gY1FGk$42VNiE@^)vnHnF5vF4#XkAFGO!2c6|npk2nNJ9@$;O_1r0%{sGvHw$Ze(avFReb&ye^c`2v z$iPPJR`if&#DrY`@FgV|0XvzT$A>YR4u2xIiU=W0Q_M=+%L{z#I`RveW>?LD&Jsi2 z+*X1}f0UEp>j*#2a$CX38KKn%zBdmq@^$UZyV%n-+U~GSDdjQ)lgv|y!ZkjhGjthy zWm<_Jl3i?SuPyb%NjxE#+CFIojoWgZJ;a&0e}Ga=Ogl2)3XD&xm^q`my2{xz@Qx0- z`R<-A;4X|!>S_1_gQG|Pwk_c}9O_;usC|LsO9vPFCGOvl^Wcxy3lZ8_0qCPLaQES{ zDqvzj1h|Q6$a5yEn8)flIdxsE1YUB#?~Z%wf+lQ=_KOuqOJG3qU5K#xqPYO`m?MOa zW$ypb`JdUkL3x zoU4dPUws_FD0L};8L7a7ekyOdo@r8YmdOlSbE!R;I__LR3d;IwP-$a9nvWabw6H5< zcj`M@X(q9l5$O3%)B0G9adRP1uK`)mYmgu>#t@BAPs`u_LVPs|e(32E-i*D_@xn=E z8dETnTK}X{gNw!K2Y6xrR<>6Qbf}$wtW?QqXM(3Rm6xZGbZ}G7wiwW&b}t)g4oA9_ z&)Z6(ty(n*+bnCp@OZoT{>PWG7bpZABF@e~8Q;0H*!gIB^Gv593Qec;TvBmoznkwl zZwssl#FBVYwY6pO_4WN-AV<=4IzZp}V|#zL3dYCBXM4GsClDDgTdH5j{qg2+(gz1R zfQVrP1O&9r_)2}ymqKgs`ucvh)8BpfA9~p(Be%7Rk}cE&MbY>wgw-s^@qnPoM2qNG z0y>FbrTyz=QaTF&F%u2CLyI;^yza9SR5rP9-^ivnwwwRJ{6oHuPQv#ZM(myU=n(1k zo7L6up`p@~*t!)bdAqo)rBaK`#9xQimv1uwk|clnxI0r3=?8~e{KzDpz%oVhxW2x| zYYRjEfK+!$)vCL;yIY(fS_Vjp+1I;yPg?SMuYT+h|uTXp|QOQh3@M?zD2Y z4CP?B7SVsq6owou`SyLhdO_my#R}SSl<(@hN|}b!IbVUM)Ar|SwyVoim$wYh5E&Mw zdaHP$)vHxQp*{9H;0GJ=DUp0pu{msHhjTUJiBWu99=AUlSBo_38Pn2AJuu;5Ag3~c zV_|r#Cab+9UP^mP)>}i#euljfS>UI!i{IJBrD$^~nRO~=zw?r2fQ+16sBG|69tG`VKH!U+gTknM{@TAB^#>E)UAI2n z2{;(o^|PaNFo8SM_k++LOxfe#PQ)cMs1B(iRbzdTxOxflwL3n zRhCO1*vY3H4e~ndj+(EE$G)>fnXR_@G)~$uwH5yU6r1#NZmy>O^i5|+VnRaxho5<) zhf}J$Q+c9Ck9S9^wb&&Ra&oGICV>QtoL0+_5KNkEtCbeV1+COnca;s+^*&s^4eJeR zxv%pv>Wz5`5DsTpR2LCKGxS3sxNjv`SWG*C$o;{jWOHTagoG%99Z1V9w=5CSTzpQ z(F{>FHC&Z)t)e;twlCg*02jq-$I~f4Pn_+s9H0(-cg@SsFRsoK4l?-tUC?YPgQ<;{ z6E=yPxs9#pTR1NB3mVz9&+Y6+eQ3(D-xun^EEobjzxDB!p4j7D-u-$l$TiXfNF0{P zOm=@rUw_-3ETU_AJc;zuoB#0@xx)R+N(&!31;t4hrrc_(Rf~M99C@}cc)X!hPSjLR zD>1P24yyYHA#|$c5U`v(=H~D~4P>tSwvc~?hK_|Cgvzv2fq;rCzB8VuoQZ;!#$r66 zb(1c+LXIvkXnPHH2WHn)HK6o`DrG4e1-@t3>ybHNftp$ORvGr%zeSgSsep7!Vb>V^JZ>C)K354FloEe;$iklL zUqs|p`x>Yfnay(VL`Am#t6AYCY@7RjF(v%tqK)=P5fLXsnEg8A;-`?{!Y|BimZ_MF zLGOE)57v&0vcmQy>He(X`m8GNxTXg80Y;w?8?h1m>N=!WguI&T-%{7U)&HZXdDEDz zl^Ap~9Fi;wXlH=}8jW}h@&96|tmpdkQx&5k1I0{`?NxmXRo?}}NB9V7k&#YJbd!Ao zv{dof)k~)JJy=pPRO(M@3@bGhLg5!bp*Sy0O;)@-@PbK<36D5lBbYiw$AoM+@!)T^ z2MZL11qbiHgefIWm;R{sq_=NR1o>2xU})pdiADsegGRDGpp?Xk!sj7;FodpHy{dBW zHjv(aSxPv8&KFL4&-2c^yk#(4z!c*ae@`dpFaBON(aVYf^}C7+-X?L`K$3h4d!1@V zRzi)?-zP=5_d4)RR=1{T=u7IzmA-EpuyN~g*YBJ=OTuxQ?xck_+{A**!a9nN&Y4yH z^))1+T5f6KJr9~8>&mI%J>u@QrOWf(dbk6e7BA3gNDni<>B0(K!g26oh06IIGqO?Qh0q_ zqQ@&Sk$KkV@*-#jzb_KY#a2DIP~`U@hKAt+Pb$SFb2Q>ltq=k ztsZbSnAGfy;&5Thx2RI_U-GzGGIKfkBMJr{wAiWf%Q@2LVZ-}xK`wVHYWgoRz0Fd6 zYnhNXFlXW24d2m0X1!1NSr#ccO|P44aXS|=(HAGU9C!ICAm`m2Qoy>Cr!?rRZva;$AVads9R$92JJ zY)_RWGE%wq;wMpXrTRc11WgQFpV!4C<7l%IN`0ck!91xp`t;8{;r8yrtCjzCiK4?= zKC-(_D;ZA*fD--l*F|Q6Tm8SFl7h`>OR3I6J&ngn)+#!u#sho9PH=1O7+R&-)gg`7 zC5EO($o_m=zP40@y+V*ibbZpr4w1M=@`WmW4x3xus_!&SRz5IJ< z7oHMnh`fphhf1t82#bPWn8)`l<0?IoMC*Ac$9BRc9pswv^eeR2JXmULYfp_6_k~DW z>hO6zHZhWkUoKd+7UgNpM!7Jx*?V_)ciWf*3Pr`l_yVL}h$-CcdSU-{tVa8*6bWhc zfn6JZQUjN}H@oE`?&ITQai$4C-;HsIp$AU41N0SzdCi3id7&DQ=M!>fpCV-V;cVSR z|It&EeZXVDBO)qv_N$hv`8e!N?7@l}jS%TVg&_-q`iHztXB39_oJB@V64cC~e7zFa0r8#UxscMjO6? zLw?YXMB}BEz_pknq^@}O75GG@#3ajgn(?vdRl3$A1Xy&W&p_m1w2(J0lEnYQV-Hy9_G zZ*sBwWp$ZvenLbMH1({bZ!~*Fm>5=~WJU+{c$`#E;Bm`=!pLYj#$xma;cGF3uke_gX(q zrqUT{%neXtvcQCpaabNr=Sdt6e!Z0w$*&*^0!b7y>lk7*{HL zSP<8rZ#A~hz%_HUEX&GkfJ8)%)+hB>D!o-VJU zWa)5Otc5$IGB8GtSp;Blqc4HG)48;yy|j3H`0Hp_p&HSJVI24#lef=xD8UGzxNHj} z(q6u+MZeWmy{Rsd>8Rx7qKr0=$9tEir^mZpp@5-CXtB8PbHCwC0TP_|I!dUVye_A? z%fcqqIVe~Ab1)<%B>&Kcm^q-p+wRQQ(g&iFl*598GUmr>pR}09ZC9z+{=&hg+Ime$ zTT-PBi2cFt-p*$US9soY-QH6uu3~1+Yu@3=EXtUlEEIQ{WNr4 z8z!dJUKl##_w*jgEZIK*`v8|jfPrko{)1#6E@rG99aYFoc;3c5R5#1n%B?`~SdUK! z-|z7mv4e3*{W4h?opO;2=r4ClUf-jU)nWWtE?B)&ZhfJ^O{xFxcrZWPI=}zUbB5c|11B+Lo{^s zEVEwCddqG_IljEBm?<#wmWse5tFqrwa6fK`+uhYQ*>^ta#+kAgwg1bYUM-^iaeuKv zQTsC`6;;tUTSD-DtvdlYI-k#|B%fxVId^b3kP&v9mDCuuetypnJiWS7#X(~k|IA3c z(cw#o1U?0c1-rLtriTijc`^XYr|KNk&u^{_Z)pA z7!2#Hjl4vi4&cIuCA`xt&bqXHO7c%x{G6I9O-D~-F+k3m)>nD;!C`khreuSdw`dNS zdDvIWZWWEKQjEA89X^BvFpxs1es?z($rJEwA}4>kr;aUm2x9dH%Pp50cm0QpimIiP zSwt^Lz?W4{gu=taGb3M~F4Hsw@<8#5l$mZCcBsYkla#7e8SG|Ia?f4vPDuqM^0s(R z7RqzEU6y|6nVM3Lm&@Q)|He}d1NVCMVn#P8H8qtH8hEdzb(=yTJyjKuVhQ4nbtK7C z4HCJ#>xv|FKZ2qRBnzaA$dvsR;6^Hc2UP6OGh6OF%2SuWwb^KnYbQA^E!MQzoqVe} zIS-aBf3u^Z?QJym%(sQQBK)FryZ$3dPg7e(TL}qx&;@f$W=IFZp-or-8;cR2izE@y zVUO>3o2~n%X)#+VcwoNV)ZqF)Z!017JeG=*5;)wVuhP{s(B7?4u+tQYK6lh~9;azH z+nAN!=bCgIE5~1CDZ&qkYg1DblhdIV@Aaym8Hv0Ot{!zK6f37oX*2TOBe1RQKVfr6 z!@YX7Q^xSj-+=v{UtbOTp|78>LXhlI?1eNj`PYdS4>t%Qx5DtQRgKq^C-3=h;a%2m z6nA;*wO&t;jsDxW|GH|$b4&CjEK*P7vJpx&iH0Uo=rsHN-6w_k>v-;`Vs#mzJ@9bX zz_FI$oCspOi)Ylv2L`Pw1?;BSIw)K0=+$YCxh4s2l=<#;#&u7Eez5He3p}G5Z=+FQ zhKz9aw#a3ISfuI2JwdGzKBI#%g8P8_kv%nJHG^TRk!D zH_VF{lWq($<|^)|HOK=|-Z3dtmHM6bZ^u?T@7jX*{mVYd5Mr1=Flj%YWQIkNBd zywT^Nxs#===VT96sIRIKlFhHW+#zEfl z0y3{UsPMDW164M0!dQg*`Nh4L?4~JDrhXsZplQcUd_O)sri~7~i2fCRtHD@tPa><^ zVsVcL%-Zi41vWRQmX*DbeRMDl4qNUlo5kXW#r4&iacHN;3j?Mmj8*;hKBtdvZ({K-Xpxn@=`az>;n61b&=%9r8>cT_HEu_fGyCftopA3Su53rT*NW1ux|uxX$w- z@88sL=0ji1lYLP@o1cOiZ%K=ghdVzcDD-?{YBo_MKq*oEy(e-R1Rf4U&mEUkdvqVY zN}^E>i2MOdKolq{1E zY_>@SFWExp0n0qVDpB8}iLo`~dPnW4*+%eOkF$$-*?-n$Ecnp^PL_fB&wdc=Eh+gO zs18|(Sahq;`58@-8AN4Hjsks;YucRGj;wpk*A4P&UnaY(z3A=tW(b-Wl-dr150gfG z!i;)DwG)bp`jv>i?rq#bQ!d7S7M-^hSZX#ljCh~!&w!5DJC-BvP{bbpP7~YG(sFyQ zhQ`;=&*AOZfg#F}e=aQbEJQ*i0#IV1Iw(U{a&&GM@H*T)JSBN-oYCxD+}uTZ5T@D> z3oG)l$!ME!Fhl+zsgswWazt-KSzcal7Fs!;C$)#M7bE{88`KW7Pv=g|ZPMF5aKxVn z0pc&DO4bFE-RBA&J}f;9BNY>*uL$3s7gTc8yEZkLnuI_Ug5tcg$cfNUocHBoWRhq4 z2lJe+o@`^o;4+0a>pf--PDeVml;+#xd0`sMUQZ7R2S~AD6){v&*{T&fpb#Dzf{UG# z^-Y;8D8w+5(H|clf7H|a?)s&uEa4j!5b%UH2jZ0{)++TI{%TV;dVtyy6T6hmYI+P> z+%`t_3xrBgfs123Kx{_&TQ_2{a*1PJ|1l6`m8G)p4ZM?;0AieEVP=+p*Z-j>oFM2q zJJeDMl1)V#Vc(LPlO%?B;xmr9Qs8>ZQqa z2hyzM)<`-~jo_PK-Gjcz1$qVO@VQd^iOA!9#Es7+7vxte7CR-B&_I2sR;>fjTu~-O z<*QkCn&Z)OxWE6)+}vC}z`?klfu!LIW!uKtTRY-`?>QsscD#iFg&&#Pr1!%)m)igp z3J3Vz9su>kn0O%HNltYkhC*QDyyNgaK>o+!>*Epth_2y<$KaHqX~=b?*F6jf0*&X1 z)R%*{5*!)X`@Z!)`*uEf$ls4kS~|t#*)cI5Ojzp*!EB7%o}Rm%Fxuvio&)F0e&~6+ zUPqNqaOIKB6t?y>6f>1){1e>ew(J=w{Dpt7o_*6+Gm} z&J-Klu(`2}Ulb4!7`UsDtx3VoG~Loy3T6n@PHzg{h_RH!#v_Gk4``n~LUaRD8$$=+gurI#2DmG*vXV(#(%wVC5T~y9 zCt5OSZ9-e4nUTOLK{M=uv;zs%{;mA7(Q%-jcQlZR7(}RLwOia9TpO(bEvrC|rXCpN zTyf7y0CpffY*Kfh^a>F6Gj>;^)UiDuE>xR?XbS)%Dhf*n=8RS%S~3#MkneNktLc1P z3g{F_K;Sh@LD1V7`2d@$3Ty-T8b>`~>j7?|{L%zUTHGPUXx2FKqT> z7u2j0@ERfPOmO5fVzx(eU>Qw~FD(&)fEvFu$~rEiPE)WC3|#RAK)T>vl9}5Gr9nEd z((bP;`;ZQC7TK@_%Vb7ugT@NT*-id-!AqxOw~lAkran4cz1Om4nz7U}3d5loU&JE_F#LDg!M`I+a6)&sXP=EN0At)nw>1do{nuZDBM-iOon! zUqd5h`Gw>DY*q%06A*3PFXv2=-&eCuw{6$;ce2pPWt6610WF|lAj4$~4^Y8CRPmDZ{H#SIYLn}pkFWTnFEoC8p$9t-7e(b(DCwW@wp9M{+4$6_$L^h;x`Cb zE&_qF7=jq2CBb{#24lv@&#!}$m?+43+65hm!~Fs%rl~`=?TIN~29mgOCMBHru?koAg8qx*B)v(;-% zoYw+ye*bc^0uxp&erzQcShU0;wtVA1Xzz^2w*af!Rd~iG@>zguz2Uz<7G^h-zqRmR zjHS#0*6NAqv-uQ1b`ZUisYSS<0<5j;>+8bA`uh4}XiRl17|0>~SDiRGGchr-KfuBI z+}RX>d>f^qUZK-G3AuI}$n<&)0siBKGrD@p&X&c^+_zTYovWBfF$DUr#hZo-ebG#9 z+Yxs_1W}IaZ$nkbIZcWLHOFAKQoq=$fT`c&j>RqDrB8exn(Gc+>_-aQm&i?GC;<&8Y$Tm$FQ%y1y4Xuy zi>oP+o(z(@S{GE3!^#P^=%h8deJ@)G#pEKqp~V~pO=6RHoEZ5ZuMPK?eyPUyOEi{i z|0-sRrDoyQ*Nr5c$Ptg+3!FV!?^Pxp@KAV$QM-|`tJN4qE_2kH37_0IIbg}@4ZWk6 zqLU}6`mo<=3#vUE6e__b_{6@6P7Q6DTo2cN>Pt2Me=i&F$snxN=4bXwYedc zpvM(!Nn|GS{!ld2xw|@=$(vTLG7x2#%X^u;I4frK;mN09hLi@^jK#j~ zvnw<5Pampd6r_x_jZSqie*N*QM%;sVB(ZX@q`=HGr!*;w!cRoTj)o?Pz|awTp*wr~ zd@{6H{zRp|aMRfckJ)nfv-XQ0cz!dRUx8r@Q#&-t$j+>v%Dy5mp(bU0G+!%~h^CI3 ziq?;jTOsYsaD~!eYd#|eFzO`ApaQEMCCjX6isDR}rjt}X1?Z0EdPZhIoECYOJy;5@~t!Ec$kb;TQt1 zvY12y)*3CEy$T^(TIz))tkq&4FyO0P&P;%uayG!^F0M}yLQBEAiB2z~(ut3oJU!eI z7F-(sAIWt(BY8K*_0!Fz;PC}c?0}`j@b#<3`LSoTDrS#~$&tzVVw**_0(G29#0VA8 zA5H};{12S;0Y84Q7Ou__ku@d-hXPKw4Ljewghxzusdq6+#HIL5BW9-Ef(r|Q@Katw z1nYwDQ|p8&MDxBYpRFxN1`StV|sA!6$&NhF(4@IG_ zxzevBf0?zQsmuL5o+y)-a=+G^ryO7N%d7Fxz^8||@if`sVUOqr8;2B(G3jwM@)bt* z=bxobB|S)*vN71;?j0pCd6c+i})xV~g-AnnDV zE9BI|zrgj9IpQdy0H5DCv4z)fF$hzB8H}n+@;p)50Q4t<&M4(43?%e)T7>D>emy(n z@>Xc`4S0iAIn2(dQ#svh$?x@>-$B*RKg{E?zh58KVY@ctq6=bPf^SOmdV#Ku3cdt> zE%U)A)KvqF>Bv4j8YBu1JZMEk;&hsA3_m$`B{uA!tM`Kx?yuL~%irSuTB?ZXS^LSG zz1!x(wcK&r79ywJcSbAWc^Pi)6>X{yUDvfjIQ{8jq?degUuP$l;HZ1Dn~^ z==@0gFLIz7SfYa)1qs5N8BSJt%5_wau>2&CoY_-^Y|4*4=7aPpgLItkiDz0SE62%ufg$cUg_OGbuWR_Y?8J%Zi}rbUQK(dSXo)k z&BY}PRLsV>f6jN2<2`wK|L~x!8@0Ho2`X1}uj_MU0_+YTp^{1v`TTjBflNfD8;T-` zl;(_DCZ!P5Jx473b5%BP?BEB=IODFoZ3E6EF-|z6f`e08j6k0nuQ2N3XhpXIq%{7Q>A9cr?o222GxgM*!ArR@ zw(jm%U%!5J-mmIC2JE_Xa1aRqY%Z@y-e=Ygh|^w9$UhR@t9{_lwSg@^7t_i>#F(uy zMNLHwL4*hDx3_UcfH9}nqx<>6f~pldco>DKLhnzyvjkR6S@qSEWtup^*PGS^As96n z3c8mHNMLqSd){o~)HVY;_Uu}eVam4y8o6`_TH-MfNVjmOOkqhpfKED`S};9Rp&R)e zUv#LURm?}m=ddWO58el*Z9f7s9R_l90A*DH#UqX1L*pnbo77K^vaiwc0MW_GNn29s z94rr23=E9p5uPJbK0YFVQ$))&YA3znO-)TbUM{`0C9~P+L%#roz@P5y?5tDjeY3yq z=0MT6>}>F5eleR2;g)LFzm}Diea5h>>+3mJpXFoG;{gxq0AfH8@E)F7%a`%okYy<) zHgG$f4cHYRSUj)K>#NqMVf@5&4Un}m8A&Tz&P+MS)=Rgjwv1kE0H-wXcK{^ow3k6{5B7iP20nNdG&X+db?v^||-{-{Dsw`Lu=Q8DL zRWDLGz5xZ|S&a=kp5-5G#PmNDj?_q+%e&@_rREW7ZUmc95xAv+GGsO zi=l3gG#xQo3@j{gqqhqPQCB$0Qa}&*++b_$0NoK}ry&2lp{06Bmy&ZZrlD_m7(<6e z5m8z}v)3tCViEz^Io!!DX)o;Xl49+q_L=Q1@5*?(-gBUI_R zqM3*(*hMT*$ z{aBIpX65-^FUM1q{_Vr)+QVYhqQ;44V>#nwEPNLJcUd^}YL? zv)dFUhV8HS(w*z&_YW+WU1SzNt)`En;A*T&F&5-bxB%{(n zn?a8==JY8Oitm54nb%61xg5+G@6EPtJ6pCt^76nP6JNEQcx;ik8Pnun9R>dmub*{R z?}hWYgF2}q2yfM94B{X+NoVzSb^Pcu$I;=^WtdY{<&5J$6w^ARbzWADz5d}!L=A&L zs$T<4PX(LVEe!vtOKuX+Wa7IIFL3HVZp?SX?;N63aOfN#vJ!_0ycVkE&Qd#0w~2~n zT?{|PF1HzzQjyNl>AYZi#k+K*3&1J=9I$RznL%2n<>)lO);um?#&kvu16{0_~ z1FsovU8~|Oyt#HUq^BqRMx}o=Q|2^Sl&~|KgI<>Csf#N?34c|rXs2bx|KJI84@nxz zw@`F;U{2V<b0F~fvk~yxFD;s|=47|>=>Y5*7$NY>?8i!{QH(+(2-v~&WB#7{-ks!3 zXce(JvQG$%x6TaPjPZ#(6-$X6eTAy$ZG&a5$k%2CXI>tZpgSk2DtUGa6Y{k)b%~>i z8F;x`%}?6QFNiNR|LBrXQCNNusB~lq~qP7432={f#TE2!L3{o{s8Z{aX4=aPl9w5yc-K*Uxs z^>A4tGu`|8S~)4h@Hmi+59JP$Ihq$ygT6UOI3Ju>a{eJY09;LBF*6eqWnu%rzZ!-f zZ6h(8^j*hhEDQdSo3FQ7dxY`OS`0bg1J{rcGqMm%y;(}O%oW#5>_a~4N$=0?Yp|by z(8tsiiZAr%+mSp)-E*19&bZ@vZa6D=Qzz$IxIfGAVqYw#M~Ogay`_S(Ge9QTNDx4y zgz5}<4Y1z`ZmOfD5Bm6E7iq;?Z%8K*dZvw$TO5(Y4MA%8L|DxJyNwppA67}@c%BA| zFJt(SEB;93&WCW1rw(6|X0($rYqob84ieEt4E3)lDQw&(%;BtX*?+XKZvN%>OY$zw zYRyEw_lzDk0Ef3Z(jxfwoko-wDg0S%+L} zrp-DTQ~0|IR!nJf#DtV$9k!z8mWzA94MIX;pUXXv1t(8ITQ>W&_1xv31t4qN z0h<}TF``bW9CBl%%ICN~RkE;#20n-mxotjB&>;ftLc#V$(_ZiAl3?l@%?$7*QxMq`b=g*&P zoivl(kzdwpFBB9Nr|!tsF1m4a3vCgqK+d?>;a`25oV^0X2A)Iv#mS-jKpT@d%PKJ^ z<9idg%>iODjQxb(0O^3B09?z>7J)-1u0XL<0`fSpLfSwyCsdr+MdVc?1edBB=U*Ge nb!7inY#{$n31NTlC%DHO9(Zioxq7ezAL^5+tVp?#p6~ww$2ye+ diff --git a/_static/img/onnx/custom_aten_add_model.png b/_static/img/onnx/custom_aten_add_model.png deleted file mode 100644 index e5ef1c71742ccff347d84c65c6b7f5d15e960654..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8613 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 D>9(|{ diff --git a/_static/img/onnx/custom_aten_gelu_model.png b/_static/img/onnx/custom_aten_gelu_model.png deleted file mode 100644 index 5b326690eb75f6297501cc760f9f590da2ca20ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26439 zcmeFZcUV(fw>}y~L8a+ddQ(wQdZc$01(BvGB~+!i5Rgu27C^dNq(~Q}2!W6QNhks7 z(wlThDAIckox9@R-|wFDopbNG=l91s_c`~j=iv!S)|zw8HRl}T9q&6v!k%cW(OqP_ z2m*oV)E_<21%b{^fIw$<&r<=P%>Pc~j`B0s6cs!zbC#+fM~A zyU(1zkJ{#F;H@Ef;E^PmwJmCCwN8)D$&^EXY{AlwuW2B0ZQ2!4JC)a+?nO@ji_C*S(|n6cbc3rr;Xuv$W?`fv+F zGx^tzy5B=KN9feBs7GQP`$#12I_t^Nz^X*E%OcK0CODPROTUNJeGbo~y}#GE@#l#` z?}(xbj%w2QLCgtvScFo*<4JmGmW9qeeJW5z*vQC8-nVZp4lA(cw|!GbkbVf%uiacq zg36{WlN04MvJd7{$S$9L$nMO>5zA~H_wmA(<+xr)3LXm4WLMyOtOG@$9el>T?;#QT z&MNKf@-j@eWg+~QO=xyY_cg=_Lf@>Wi!|1?m4x?P8x%j16Yp&&4K0&W@0_0O`Vd=L zQAbTZPf1$Hbmq56yOUA-j8*^E>Mqr_A^mjQBj}RFVg}+2h~tPb!m8sxm$rA3v+1%z z*;DdUsBn0_aM_!^o01+h{~}_#h}z3)hUxK}oLcyWw@g~mwQqA9(NG9JINvt)6jLN8 z+7Gtfo=6;+>N1xPBpe`&crV0KNwJSHB3b?VLJ)-e3nXa~BcJ{gy{@pWEgY=i=5+$0 z{sAZ>LguTjUxup=jygSTFgDF)5qG~U}^}ks88mBqk9PO{!v6ypv^FRAQ1Du(Mu#^ z^GDG=og%6=X{E(9wB4rS+v9U02FVBIzLM>6S(@~*6#p*TAo)R@UD zE)+IXFK78e(^N8RwE|D&Pj|ICMx(W^wnM}^o3dY^ldFfdu8*`5=H_*qHy>X!$DPv_ z;D$cU8@%c6u;IZ8Yu3^4Kld??r&E}Qco(nM+<<zEYiU+3C+I$HD}%vsFJ& zVh+5_ecRl0l9T&=&0fUO-#r=H4v@}{!Wasn$(9=Wkr(QdO%t#Ck|L@h;C$?Zh(j3X zAhtSmm{6oKC&JG0b_qk7*yePc>GWsVo7R`EyNaRc)%VusB1`*T>i#0w1j)hin<1&g zN21-+2mK`Z(anhChY`WH-Mg9__qyLy|N}gbld8cD277Xg^j%}r_4eSn7Bp* zale{_Ni1Fcz?6wsu$z&b8W+gLRewc6E0-zlTz1}!zq&1{lm<9&MQrT1meE8f?d zF)P|toLA8xIvzn+u2f5yz5B9p<8q4$OdWol^7>(m1CDdEiyQO-$vZh}-teo3JCZ@t zj8h`_k~VpC7xBW05UHyk8{-j+^~JAd>K`y47w=D|3ti|u*nD; zt^HmQo{p7&?^x9;2y*!VcT|(#U)fw=d6Vc~3+IU%XA^1v@a#Na5G6=94E-gqKf==0 z-{v`zGz>-vzjh3`_xf1)aW^8q9X{YnWv0jdLa!@n{MvUmX{*2pwdKIc@FTj&Z`gkS zH%v(H(X>|PHNbFx5cf0YIDKlgB2Gut4)glNXR&gr?!p8ps57fJJhkIax=4|9jp4pa zEhmF^#FoaDV}s83pM{d<16A4`r+vCc2#RMA_ns}mVYl4cwFDTjAG;)F9`{(l=}w*W zvq)p7;aH#H%WXLlE5X>w>Vt>Eua+n-fJ#f4-ob8l1iQ4Qg>9Cx(f{6F&s*Vei2~9* z4kaAPAL7#3NJ%vq3Tt_AddNnAU~%`*{nXDFF7@C$;riWHj6Xe7N&}x9JD{Q{5w8t| zU@nLg&Lt&d>;6qiTBhWpH?p{U>w{IBW;Rjq&9btlf(%I$6lUR2r)cSxA*-11qKU#a ziXd!(LCR+CntEu`s+ga^9fnDIAGPhn*no}b>@!Rue*@KRFZ?OoHvgHS{^573c4T-Zpp2st?KB4KGm>-(t|dk?MwI zjIaQKQq5vwPjB!B&WgEioYY}I3cfpRZ;YLK6B~h_9J$|^_69)0#H%f^h+k5w8s{efoVodU()i)ShXZFo>$|sV?mWJ3^4I6U@&+jz zJ9NNk{&TqO-`0h&MSdm6x@ zH?-mlRYMVgK0%=F9|ud+4M8sjEPmxjpm9y^q`6iXib#W$9OI^692&H z2f&909gkuJbLhALFvi##!P`7>-)Ftl8fAH0b>GKGa8zHukPwn+AZ<%HxDQsaHTz<% zCYY>`6E4TN`)~a6T`1p~`#Oj%x#`DF+JbESXd1P2N=Kl+b`9ZJRx>}qPrd-axn&ztkp#lw3C zPDPoRqiMN6^;>F;NkBatm}v%Y4Hf|?$U-~CoT+Zb+r@~s+CrnBuk`f() zxdp7L2@q}MOV_&?`ek*mB|oo-gUNPq$5UfFDu8aW<6E>$R@>}*4U7PjZL@6eL87r_ zL#gxmg)0orbu4T=0_;s5&1v0A79Cp~Z3udhyEA3TjMR218B~0ySY)(~$!ib!vie@l zKwoXJ#slpdy3!qg!l6$zMGD_k*y#@F zqh<3iDh;*G1+k01eCbIGnm@ucTQb~=5JiMXK{U9?%b26s%$8n&Ud1$>R*94QiijOY zjZieVB*Z5}dRd$^DM@&?yIp1lF$9M`L=ciBmIuG$uva@JhH})#53Yce`7k_Y#5hK` zn`Cp*7`|+{2-Wb8JKC8FI$G(@*q*?8E``Hd#%KkQEeA{}>iM)*baT~N2y z&w&9|EiEnmx02`pGq%JIn7r*MVPy1aSDHeJZX48G_Et!{VQT-PmQg7X^`wP8eQKr!*667QvB~= z59QFXi9@-@l)V)Po`hXk{8^Wfj58pYi2;#tnGWGO@1=JmH zv^9DkSft7cSU71lFE8&>9R&z_V_)mfBg~@!%ThA52@*!;HmI2|8G6V{|nsNza%L*GU&jt^@+sYiD(4j4>^nB z(g8C{AebPZqnv;d%x7lxZl+D3zxi-YYj?}V5ysbNkqu~)&K(bVo zl_S(9f+)aNO^XOR&`1I>Eg3Y!E`1X$K>YlEXUk%J;=auI?87hFPe)4)F+%vSueT2> z+%6iwLN>9D*}Akljy=4i04!e3@yeC9lU2IHG7k@g;j?8^(vOrc5FSve2;eZ44NB}@ z(FRy~BaE>{#T_vlC~01uWTw8=Rra<%j0ZUIJ`l$qp!3Yne8GLF`<0k(nqZndRv;)o zx-n78;@23Th(fq`ZqCC=zv`~go5aFJk}n$<%_m+G=FCs*REvkw%|0{cc591a!~lLK zRJW1K;90yg%sbs_-m)IPX8C?*;1<~W`zJ$mSv9o*`W8uOtv}A0j(|Mbr~670sLa`3 zu9*87ShU&oOi9WZBavq83?m8IzmoUS_Y^!7ZWg+Il{r#?tLCT(C$R5yg^7ucxe3QR z_{oZRPt0vRd(FgQO-#~RgwF9?5d95ckIlboYCbBUvYB}e{*r$;Q`TaL8E~q)OAo~| z(Y|`}Njqw4<-qU!4CQ$(SCuJz=Df#h+uM zcrz{iqk>X7>5Z#a8c}(Z5PY~T0$)>`O-bYwG}@Rko^H@OgF(T zb^rL(k9V6)Q6=F?y~|JTu}RV!U*;(}SdMYy_=@h4#613RJFTIBF2b z4{(l6i&Sgr(w^-ru4~x^S&M1@{)_Q4m{(R;tM|Qe@_tUUsi2H3vb}wzx9gaKxD_2y zO;>?k5%?kZ1mM2hkB?vro)3*_ouTC`B2MAbwpO&z-qHMPsTzp=G`+u|L%5--q~_g) z)VN02H!l*m5snnLv5FwvRk5_Pep}fPHh0zN5IZGooF1X2wc7Xm^h#?CET*4mJbqMn z-d{WX0b5Y z&3!}8lFU4E3ma8-t2gNcyUh z)7hY1Uhfqj~y_eI@8VfSky<3=uS*ilW>LceG zueTw&5f8=7y>d%r(@w4R1rtI{OuH7fIs%5g1GMxt8RuLJlKYBk-&{zZD(8*hvr?cN zedKEB?8@RG>*NVjV4=vMB+tovGiJ@mjefVOBQ&EKJ+>6yGwJ8rPQOa3nBM-cENnc zl4GuYG+I=~tMGXTwKm|1C-OmS&}r66fkg}5y?A`4u&^nZqIe1R0X*1tWnqiFb@|Cu<^vXUyCb+9a zVxJRS5R&xZRpY}eAN#e~5z+?@V=Q-hECvRfxL&M3Gw~=52=2k$z0J!T@j&zT+UNLf zCgWbe?nHHX{4Es?_9`9M{=VBnh$nl?bTtuERj|fM1RTEli3OXnF-70;D0D}X__hco zE`7WoZiuqc^~02|p98ty_%{%$2x;4u2Ki<^V6JqnN$3avxt5EW*n~H!g&G#E#rf## zxv^7(sF{Ks=a^dw3}KAiLX|n!E~JI{!K~z8sX?BQfV&pGj$VodIhzPpZ4-tP-#uG! z({K2phfOHFF>-|OBSkG&;8ULY4b&lYT{;m{mEwmfAE)s7tsyDeMy03#iT);1q}91$ zRnqXNTZu#FWH&Q!UQUIMUMGT8t4-ZNe*NycE`{>EVc zd%#8+TMUdGaTb7Z|3A$7&xt+V*@ZO$%Cxuue|^fJT{2}D5Mj%gt}ApwFP<^_YIHo7E{%D{AZK^WX`Xg~_>8eY1TO`|0}>4xiPd(xh%{G&(VZvosiHh1 zu{*1H&nE*%{K4VNvKcg7x@nCgXL_Px*(FjBH-X&aViH={=h@mRkeO^}#ihUoY>}8ZMFpKSLIVH7SnKHX^F0 zWDse>;=16|vwmkS`bt-rAr4J>l71$Lk_^f++V>Iz`@bVN!0VZ;Pgx_TL$gouM7aBy zp=Xtgs%Bn(6dDbAZZa-sX>|8PrU<^we}53vgZ*+qb59n2tqhNWW3qQ;39^ zDoLBYcgcon6~DJ?u~xqOvAstUfMhr+_Fb&&G!M8S(6jhfTH@&Z0RWMD3ARpSY4kjT zpF}!B7ztp>=#C;*(HAC@hqq0i{V&MYcn!gC!`KCp5C|#y7M|ah+wegvy`xB|YE6d5 z(u8L2rg30bI7@l{NqJ0rSRPyaIqzJZu2p8RdnZq$8=4DZ?=AlFSv$X}v3G)&7QxG7 z(oFBNd4Uq2Kaq*9U-M1=l&$|wI5w(4JJOg>usu5AX03JBmn1%e#t&5?4$l6R&`XDI*|k~AaP%d3waTLzxBax z=(Pc6JfMr57UyU5Q-`_lPXnc2MY|tDo(Lrl3D82jcMC<aQ)zHXP{Q=P8XE$n^4vWT-AYet0eaS5l0iL2_z*QhV}r^lGv?9`1)$^cVJH771Y zcQ<+8WI=4vNvFOFPuK5c4=auC-di)|sH>1oBNCY@zu!|QN8vOp@2$N=qFeK4P(`(( zKQVU|XKlZj)k7WX&3t4IW^(#3^QHe3Ww9@`8nThS9w_FPv!W-a!gj^}c5JTGYU#_F z`X?pVOAg~`MTCMqhX<^@=P4W(h?(NKX5;!P*dqVqyJ;fwC8rmtw{^M#%374NK&5hI z14R8oO}+xXaOtN$ySRK)LYIVBvBsRX#;jm6qe`rcUCq{?E7@~>BIzO=El=$e=bA$5 zpBVF&7hN5R1H?(MQMK_S_bANhQiwO^i+d3kzw>-lv+|=1?tX(pNqSnqMZb zzS1Yi`9TpjL0s{4?Jzi(ZWG&UK@aFhOl=rzt+1V~Bt&si?$mk@z9SWr@WjgeIt0C5 zmucmU^rtOf1|ILKHO|VIHVj(k?!6EIKwX;IFgekMC;c`jObcGRb3^X8Z6jts6>LeK zMAKaj#Y#fL#E%IF&Fau?ti#C`v%!mw$^=djREPYqBj~Ce0wR%VSLSTpN>gimERs7Y z1_51`h7=yM_1*L`7*(3&0GIZ+a6Q*Ur7(bL{+Qj!@|C4~oK0hK)(VJY+36}x0{t;> z>~c0tZ0w7BUof^!ojq##&`%}sKBwTDYWsoF@Qt%jA&w{t;ahp3^xk#r*Di=`s!-SF9cA4-!BeKx7kwl+Fn^@caF zBrkBS;p8OxFfO2ZMLD9wkiYRlJDx{m3Z!o$n zd`1~g7S=n+P|ckPDzfh?_KkuMm6h-+oV1EBNEnK&l<#igxnVR~zTo)bKpm|1Q7YvJ zvYyc`NWn24#pObswF4$?&RJMMy7f7#*`mmrc{7>L`Q7OG33}DW^4|ObtNu@1Rv>pp z0HtR@&PZ3V15JEL0B~Wb6&xVEK-Z}MU3N`$2mJq(T@!)qV6OtS8%Lh*Ui?v$U0Arn ziNrOKyBr{<37w#TJ~&tuCVuzz?=g7*EyzI*a)4X;pZB%i@|_>8AZ4ki#>VUt5)zQY zW3mRMY4+(8z}$E|vwWdzq9j6Pj*`B^_^B+x$(X56wJU7#KI-qZ7}Y`X=U&>G%T*iL z!iaBgdo65!XUTlbT99ZK8CgM>dB13GPO%a*r~VQf z>?@Xq5JPbRNdEPwto|`=;m|^_W4WAupSmdD)j8 z`2uF4=-%(WvpJ@g-KU3<#urkXGtA*VEJ^%`Q`|Bl5_5MyFL8Qd#badq}#WQ~>k2x_FPZ#23jaBc3z*)t zZJ?3=(L3`0!qhzfH-3mKT6Qr{`e?nrgbnol^)Mdp6%qJs3CIahPB6GgJgT$>WeDA@ zx$}|8)B=nAohH>i6IX`~4-ZG215K=G1U&n6ANSYiLg$H~Ak5DsBGbQPYyXo^TlQx( zb#CKC?ORJA5ADm?fzJZUo?-O|+fe#%^^t<|;wZpBOE``dte}2v{u-kITQBSaoV%SP zxo_F$NE+wJ{VO+^%)ug(R?vs+76ir!9^UpCc9HqGvYs4FQ8c4tcq2f|ai(5>V;p!K z#Nx95QF}ue)U8hjK<>EL2EU2*RsN*~%yez#MWJcS6FtfjrV*3$FW}7-3)gwI&?Z!) z1@)cXCU)$#f-}mM!z=~y!cn1IU2Ep2zlHm=R88;lTl3N+e!9d+{>ZduvJ)7Sr8@Ud=Y@4^Bi+5OQmL-W0wa_hMc{~Izx#f9bNgS{^kJ?3tb?z9x) ztIIEJ5u(vvcgDs1NfySN)pUgHBevrz9hFtQh)0Pm)8D3~N+L_W6@y z5kzR_(V@`kDkx+6_gHTFHqMK5zHfaPYq^=(nz+%}6C591ESn#q_t39!NX%(E9|CsQ z(t93v&U$GLa+$}#x|xQ|Si5LV7u&v!WM6klNl0~D%MOkQ)Vrg(Os!KX#e8o!rAOakVChyL;- zKvND!`J*4Fqc(;$*oAdP=SN1DVwgGPW=npnpC0()a9*K&h9D|ad4Z57fOw7{Kim!j zFQxROGG!~bu6O|o%2oSM^8|N&?$;nyW}U$82pNgeeDIRrG{e$#5o_=o zE~A!s3B(2`7PE-iXkt>?ny)7LR@pxCA1$V6+!}cgSlS@%Ox&V9kb{({^o8slAU{P~ zy3<0Mezb<5x+)JDq71kEj_wxZveBw=ChS!3OWo;|QxM=iNV$4UQ+dL-te|UDgLX-9 zQ*G*|s_j9!_O@IukdpBBtxOcZL37Z`_} zrpnX4>+v%*!ibpiDS4u58E6miaEF!st&b<)&*^U zBjoPaz@B9vY^?B*YMLiNo|WHwM$waia}XF zhYK(xa$I4M9v3d0>&CD)K6CEtLpr6?Zkd>dmF$O?J~C=EOGuN|pF(e=9KU`1bv(+DwBje|cf{}`wIiGgEIu@_Mylrzo(3L|y9o5$`lz1-C zNC<%cy6F}u)V#8&P3QqP9`p(L1UR~D$#0w=F#y;fXeOv?{;#|?PEBB`0>{6~Wp}5^ zQKSNm=sE@%|CM3oxklh*WMsS#U`MTg_I9@lPU3+tf++Pa5D0bhYyZXzRA-hr-&T7#=y zY=NT{iL$qaY!=s^LdxWw<+pkgC4IG@Gc&9{++aOkWhW=mYtf1Oh5pV4wY}x#G&_zF zDuA0EI6pltf=dDLE}4yzCi?&`^>f<|NWCcTr33)zCG~Z2ocdmT9*_h?-CPRfy6;3k z@eB1?Dsd3@Y(U$;O{N1Rgn1*tzbhXaD?H#7Rk?l%&8jJj=YA@>#S7;qO3eQgeIu6BBYYufS(z^B3HOK|EGz+S zk;Z?>nGhH5YwD6UnF|7$k`26_`6ySEo`*tBNbXjKgt%yk&X4eZAf*ZlDW}o!J@i=q zUsst+ zV5wGI1k9i^AXKUE#W{HhBZ*Yfy)_7k3x zVWyV`gVK?U{K0(I#;;hNrf5#NsVFJJk7$=->DfffO!Jxm*WF>;=1|4$FVY(gS4wz5 z%US?ELVXeuNlxPUb$eG#L!Uc!#EuJxo>spUFW3v@jgt#1I4g|E>!vCLVQw^x0%AtT zQtw*8h;aAqH%tFu zKUtg@6;KA8bz6KRpW;SXA~Un)g&#q7=-`Z!+W}+01J0E-}v3tI(_&v!RV-1UsECRj)K3_-HYk8J^Zxba5RYTmV*S#e3&r z%2h}6LnF5Dx1tqxhot9H+@`utmb-diAiCsQmN@$mjc5mIL2f#)Fw~;*Ggtr3UZCzl2XzgvbbEr&}J?j_9KVFs2DDn!zyz( zJW#kKZRk-6T6If5G&%akr=;E%xML8=n11jxcj|+RX7Hfi&A`!M8s?@Pt>j+cUV$Ps zn$n<`a{jBV3Mj!r^7*I%%Rq6cNl$fMz9j9$AJm)GQIt6Rx$uxQAX!UhoIgll$4_qa z2hVhb`BCPJoQTIKYBPg1Cp&pXMbTc>TdAlQwQt;`r9q1xNxSQ?tsu4+*;u2;l@;iNKS-cB)gQbV|Kw(U#`wL5e~q`1uk+^c#jCsYL6PX z7D*+PzHH*Yl-R3ZKit!|-)MwnodHfL#XA3~T4-LQIG*k^`*|U4by{J~M<>Ol?t=Y( zuF|o61=+*c>5Sh98bw*ERp^9d^d57#wSyO`x4V9)TfrAMDaMwNHC(W&4OJuIb6|H7 zyzkCq60&OZ@jothxL-;Rs?GvUfmZ6nf4~066<1>c@}9?F*LHAv*ewxjy2ACvK+TxG zXx2;IH4ERoc^t`MZU&sJoBbt@${f69=!)VqC$JF97b#sI40wuq?`+6f9yG)>2*uda zk$bO#o{>)So(@)<>ReM~szl3@KZKEkeBc1m(%Sl_r5eD^99PoI1C0kh3^8f&^3uqS zU5a6JKq{8ptQHS&p}n2r)gDyq+G7yk&}Al+1}UnwxFp9FA%`r#O~vqNBs#3GGnq|F zey=2lMnU}W$Tx~Pame%3ws(tKGvHcbmm<>pbU^6-hALveHKTIb9bWTHrF@fWw8mo_h1^!1_GCN}w@VA(6PQi82mQ7g(5k>a z1r=#A;ONDa@@La8c^&P^f!AQjNC9LLtJ~>{oNMz#JP+5Yu3zP6g5_nU==_=_?zO`? z?&ga9LB^sxCM2xebag*5<}-Js_wri7R43i=H&{Kl-+JGI0$E)PYg`eqQ#+*p+=$*+LL%A%&gT^oLtw)n(W#Q+?(Y!kt5+a@FNlT7u;p8XH5QbB%+-{JTZu zl{znc)`fG{zbChUTJ%&uKRq4Mlv;8UrR zvC9fKK7&_Zr%QNsl%QVf>9;r1pneG?G*eRAHc7+^M-)_pC8w$#r;|MjGRc<1o#71b zZF~5VP4h?QlE*DTuOp5p5*8EfIBzS+DV9VZ`Dcr~+v(k!9tl{3<)Asc`N#)7cDYiP zNqq6nN~pES7-AiGdE+(I zt4VKb;DMWC)$cJrB#-d|^Y~?@fcaMG$;Mk7*+{=T&fe?6-fLgWH&P{tVFgNtY1NFW zE|XfQ<<-v{3#6(`r@qGtQyQmeUeDBxGQ-^tlxrP<>7n#SpKG(qVB?S(d!M88K}>4T zH0LntxHTlL=|^70uI1C}?xoAzM}B@Q`2^HTj!3iAVAuqC+E%}iG3e??pr&;y#jR7l ztu0s0O!5a>rbs3^|tct2?mSqQO)MRQ#W^fX#KXuLF{+)V9)Z0h3LM-CCy)`m~J zrYM`se#K&Uj(36Tt?o}`D>)JaTpfPTP=7eN2FD{wkigWgw^8fl+)`Pjjh9OXx zqGX`uQCqOk{x7J14Iaoo6~7gfB?oYESySuv3|$TYIch^%9Tz&$g3{ykWv53Hre_jJ zC1tLA12VvNl_Ejdp+XF8BL@K>5FB9YD)wsNKuDv$nS5g(`X^bR5a#}V%e>)w=vAdz zSm5EPyADbBppx5vy9aQ3^FW2`Ki8DFM{v0`bGT_%5P@Kik^A0lvEAKz~iO8CaG%ICn7(-pB#_Y92@(QDv4Tc2Bscy>Ip#qLG*plGdL^A5Ym6e zaii0#S}e_X@!Q@~&B>DY!9s3HmOCS?df%o!SxRKKD?NQciB#UzsHV92XDKbvUH4`F z-K$nJ&et!DS0BxYG{*}W_a6NClc#T`v`<@Oi6bRoBf`8PCh%w)MY0$dQtQ4)*gUY_$@g|QMpaK%%RE(hX%V?7eekLr4y`dZnc{1o|vq66fY{ytC6B@fZ18o$V8q zj;cP{5PiUcXk{Jsm75t<^|*)i;wxA2<6==_&Jp2;W+ zyA%^fuf$?LDL;>&CTD^$%t1o>`nra zjW52)RQ!&97jVg1cr6i^-W9I4xQhSglHNojgo<-;7(OLzuW#RegaU?wpNY%Pw>Q6+ElTE zfa(%O%juE?Rm2Hlu_Dd#Z4p$Odjw^v$R2C)QhqFtH2C#lUzT|Gct(V6W49saMh_Ye z>|%1wiT-n2uYlkAR^m4PP^V8e88ZKiO$BlM#ipXeHrXJ7I(nO*-dGVZ2N_7zibetY ziL>rla7KB!6cqZT;}A6*9ZZ zDNP#fW`PX5oS6l3cG*y~`+L^N@ykH_hAr;aVFe`yY%z z6G6WjrRP9(4rH&Zx(E5zw`Rp$QfZ7#h>}$M6m4LAN%<_g%*%|}ruqk4SlKj~I)NHg zN{;+NMm(InNwlL*(zInACcZGHMTwDJ@bY$0$}=UR$-X4Mio^wSx2LoiN(6%!-@@0j z12V{Rg-b@7Hx4K-DU`Bn_)3`)xH9f)VV}3~#(0U@>yb)5B1Kj*B*5Jch0EbzS{cGJmI< z!Sf_J=SN}!K-tdBZ()TWFN^~Z6ehEAmYuB+*kNTanWYt?Dg5{N$ETQ#x|0+|d`o_A zEED7E@QceK573zoZY!}F2XKlO7qKec*+9gX``9V*RWdENNk)N0Mh$LW86ggQR znrjWJ!2aXc8@hLPmWRazJPg#k%AK+YZi==!D#`#Qjh?GxpW}A5835U6{&7P@wqJqNh zJ4JV*%*4ENJHS2uYUKEvm?V$;Uyx;FeODR#V$l*%*2-qF!N9x+3Bv$izfkv_OhEoC zxy^%e4REyRKS0W)PlyBKi<3YS?DIQ*8bHy{gGR1Xw&FH+(HF|{hs!0XS2L|FihkY$ z7nQyAjg3dAxM@IuqSpgZW5Dsdnh0=t|8sK?$TRhSn_Mx3HAhCB>l2A^>yicY^YdRE z4STxZ;%Kp<<7LBF8kM@l)F2Vt8PysaYkL(FD==NR5HbB3<4^YW%9U5XqhQAQmJ5#l z7VSTir>O-3FSRk7v*w=oKGkHA=C?tP#-+MXNFq(yDx!!Px}X-4qzCcUdreJxdViBpytcdHDgOh)GcNOMfU&` z!y%>Z2zek$0a$rJ(Gb+hO2vymk#Qmjh5)1`KWRX3#Wrh(iY%HNS)I7GsK65+l-T^KJQJd%J)Sqs%+)PYR#EOt*yg3x zI^1%s&;=rfDY0yKAyG!!5vD&$h%spH ztI-NKp8m`&9Amr+;XtO`6=OOcMX+qBHI{(v*va66949VO(TfK;Iv61E&JUgk$V?^c z=r{MjMbA>9U*>j2zAw=6k#+X|^)sGMNr*HulBj^bn?IGjk?x3e4ycv^5tzunzm!|$ zi4VJP1B5wHv8iDsFTloeK)>i}byWBau|ll6ym%wUMaX>{0zPrGyfL8HELQVlg#7I3 z@1Bt!G4$E@jJq&Qxdm>hZR`tRugV-@UPQ@4nodDo>bC`+DCiaWmSkc+`?9t)R{h4wv!o zqv@2+(DtId$Yi3DBClqpsOruNs~i>)8AmkZz#hS&k>)0x5sLjHawHHUnUm3WoFmuI z;l9anLRO}ES1M{hRKRip>l_wp#?BovARN9?_*srV==d&KG%4j)m>3EznQEmYHP3Yo zu`zVuL_0ZMolT@|t&enw1(y3=>GsL%Lg2ktZ{LNvtXE4aPbFr@>fD4QhXqdf4zx_U zx|DeQ$b+YM_WkZb39Y%v52F4{Njm;p1Nv9m&p%mW;KKh8f9*lprFiHB#2;i9^gpzq zz*+x^?6o2qpQS>q#qq%o1bP~HLTldJn4a!d zVdyX-N0UF^f{G$!0J&kB#(eMon8%>0>uUL2PQ7MZ+z5PknKgaC-4?A+Zg*3ctWWek ztlAxM+$i$G1HqN6V9TPK`9;iq!w_FT_RI93Fy>X@a(of+r_joyI;-(dA=Ayoat!Ge@FCdTWgOuC% zvJ&F)UNdUWpoZW1^solv81V5SbM@)*TJDTj)imeE4l+=IUSYetG%YtgO=0?jqW=`> zfD{vcx)lfvS(XMsCV=R5`Fq)0sRZFDDJGF2e$4vcT6`Oi5;&&YgIJ163q2u!t zuh55S@TnaQ;TDHaaz|q6VO~dDO12Up**9cl3veRB)RnUx5hFJG*Mx@6hA@nnar;8Y zMZFe36u0*^Gj;{pg8svYFAqcOcDaLBYGOm2 zuR$zG^}#|5)YAO+cE8ruW2ZQ2n5-l~BfslP$P5hK%Ki#$y8qX5anzg{ zvwHN<+;GTQz^Nu-V3E^fsnE57(SUqi#P7S*=~WMtL@%wKR;vT1^`!@4!51b*9`znV2gk}Gg?2tK4_~1k)mGFE;Ea1{9G7&iNelFVexUXAtE-wi#+S5pFl-X;`DzGT*dH3JY?I~$B68Y zhclw)$_9V$73IMj&?n)-Xhc3n+P6dy*GAP$3U065Ai@*hm+! zY0Rruuo!e>Xk1ngWMfq0VpTf{IM#P<+J7n$2z4TSHvR4Ia(RQ6#D$|~yi8bgcOr-O z>m8?l#&*TzSEjW`$N3z6N%Z%zPVpYF25NZzZa;QjYOR%}Klh2Zv#TbHtBRAbBzDB! zKq&n5Xw6`8E~>!)R$9P(GWg`{@;mpBd_&x6qBm@geb9Mz zMTT(FJMX-qp~uHlt$}dG{JE%@zJu-eh3x5JwgCs4PRFKQ(lcGi=yMEXtXbiFWHZ|h z{Fq*9-JZ12!k68VSkz{Pg+_t@WM|?|!f|FZaTb=ds?kj zQBe^nBJdQIt4tS>S-=(%6_8pW%%Frp$`HbkAdm#KiYSt+ltCbbib7Ea5kUxqAVZkL zJkMbsLI_g=A<5kb+rHkl-hJ!7_11ms<==DmIeYK(?fre<@AunZbeFqAdYMEy5zVE? zvb$8CB2tD`X2B8D@R)6?1soBx(wmIeP*NvDgn zAS9`aYxh2S*0g&yLod3d2woK3ADBp;9yMP3COmk^5Mk9b@Pi^e@cdoJj{APD1yhWE zsj_{{L;BoW;+ePaHQQ9FsXlvmf}EZsmBA9CW{+A&SIrh5%OVG?*>$Umh*(p@BpJoP zff`G?Qm4IhN4~+KoAd151hLX<5vW`1x z-|eOSg}Bde>Xomx> znQVs6b_#1v;Kt@K2W$;kPB(c*>h;j~{*WM)7kBs0JdrAww!Ue*TvPkNWR+9i5xUe-JXc?(H0*6h zlqG83z^8≷qC?qsQ|gdx7YU6w0<%*jLPscnV@?dgo42u3 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 WT|~IV3D8HOYsNP&=Y4nU-roWIfwd?A diff --git a/beginner_source/onnx/onnx_registry_tutorial.py b/beginner_source/onnx/onnx_registry_tutorial.py index 5e4ad216b88..9f9b028fc42 100644 --- a/beginner_source/onnx/onnx_registry_tutorial.py +++ b/beginner_source/onnx/onnx_registry_tutorial.py @@ -1,12 +1,12 @@ """ `Introduction to ONNX `_ || `Exporting a PyTorch model to ONNX `_ || -**Extending the ONNX Registry** +**Extending the ONNX Exporter Operator Support** -Extending the ONNX Registry -=========================== +Extending the ONNX Exporter Operator Support +============================================ -**Authors:** Ti-Tai Wang (titaiwang@microsoft.com) +**Authors:** Ti-Tai Wang (titaiwang@microsoft.com), Justin Chu (justinchu@microsoft.com) """ @@ -14,262 +14,211 @@ # Overview # -------- # -# This tutorial is an introduction to ONNX registry, which empowers users to implement new ONNX operators -# or even replace existing operators with a new implementation. +# This tutorial describes how you can create ONNX implementation for unsupported Torch operators +# or replace existing implementation with your own. # -# During the model export to ONNX, the PyTorch model is lowered to an intermediate -# representation composed of `ATen operators `_. -# While ATen operators are maintained by PyTorch core team, it is the responsibility of the ONNX exporter team -# to independently implement each of these operators to ONNX through `ONNX Script `_. -# The users can also replace the behavior implemented by the ONNX exporter team with their own implementation -# to fix bugs or improve performance for a specific ONNX runtime. +# We will cover three scenarios that require extending the ONNX registry with custom operators: # -# The ONNX Registry manages the mapping between PyTorch operators and the ONNX operators counterparts and provides -# APIs to extend the registry. -# -# In this tutorial, we will cover three scenarios that require extending the ONNX registry with custom operators: -# -# * Unsupported ATen operators +# * Unsupported Torch operators # * Custom operators with existing ONNX Runtime support # * Custom operators without ONNX Runtime support # -# Unsupported ATen operators +# Unsupported Torch operators # -------------------------- # -# Although the ONNX exporter team does their best efforts to support all ATen operators, some of them +# Although the ONNX exporter team does their best efforts to support all Torch operators, some of them # might not be supported yet. In this section, we will demonstrate how you can add -# unsupported ATen operators to the ONNX Registry. +# unsupported Torch operators to the ONNX Registry. # # .. note:: -# The steps to implement unsupported ATen operators are the same to replace the implementation of an existing -# ATen operator with a custom implementation. -# Because we don't actually have an unsupported ATen operator to use in this tutorial, we are going to leverage -# this and replace the implementation of ``aten::add.Tensor`` with a custom implementation the same way we would -# if the operator was not present in the ONNX Registry. +# The steps to implement unsupported Torch operators are the same to replace the implementation of an existing +# Torch operator with a custom implementation. +# Because we don't actually have an unsupported Torch operator to use in this tutorial, we are going to leverage +# this and replace the implementation of ``torch.ops.aten.add.Tensor`` with a custom implementation the same way we would +# if the operator was not implemented by the ONNX exporter. # # When a model cannot be exported to ONNX due to an unsupported operator, the ONNX exporter will show an error message # similar to: # # .. code-block:: python # -# RuntimeErrorWithDiagnostic: Unsupported FX nodes: {'call_function': ['aten.add.Tensor']}. +# No decompositions registered for [...] # -# The error message indicates that the fully qualified name of unsupported ATen operator is ``aten::add.Tensor``. -# The fully qualified name of an operator is composed of the namespace, operator name, and overload following -# the format ``namespace::operator_name.overload``. +# The error message indicates that the unsupported Torch operator is ``torch.ops.aten.add.Tensor``. +# The operator is of type ````, and this operator is what we will use as the +# target to register our custom implementation. # -# To add support for an unsupported ATen operator or to replace the implementation for an existing one, we need: +# To add support for an unsupported Torch operator or to replace the implementation for an existing one, we need: # -# * The fully qualified name of the ATen operator (e.g. ``aten::add.Tensor``). -# This information is always present in the error message as show above. +# * The target Torch operator. # * The implementation of the operator using `ONNX Script `__. # ONNX Script is a prerequisite for this tutorial. Please make sure you have read the # `ONNX Script tutorial `_ # before proceeding. -# -# Because ``aten::add.Tensor`` is already supported by the ONNX Registry, we will demonstrate how to replace it with a -# custom implementation, but keep in mind that the same steps apply to support new unsupported ATen operators. -# -# This is possible because the :class:`OnnxRegistry` allows users to override an operator registration. -# We will override the registration of ``aten::add.Tensor`` with our custom implementation and verify it exists. -# import torch import onnxruntime import onnxscript -from onnxscript import opset18 # opset 18 is the latest (and only) supported version for now -class Model(torch.nn.Module): - def forward(self, input_x, input_y): - return torch.ops.aten.add(input_x, input_y) # generates a aten::add.Tensor node +# Opset 18 is the standard supported version as of PyTorch 2.6 +from onnxscript import opset18 as op -input_add_x = torch.randn(3, 4) -input_add_y = torch.randn(3, 4) -aten_add_model = Model() +# Create a model that uses the operator torch.ops.aten.add.Tensor +class Model(torch.nn.Module): + def forward(self, input_x, input_y): + return torch.ops.aten.add.Tensor(input_x, input_y) -# Now we create a ONNX Script function that implements ``aten::add.Tensor``. -# The function name (e.g. ``custom_aten_add``) is displayed in the ONNX graph, so we recommend to use intuitive names. -custom_aten = onnxscript.values.Opset(domain="custom.aten", version=1) -# NOTE: The function signature must match the signature of the unsupported ATen operator. -# https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/native/native_functions.yaml +# NOTE: The function signature (including param names) must match the signature of the unsupported Torch operator. +# https://github.com/pytorch/pytorch/blob/main/aten/src/Torch/native/native_functions.yaml # NOTE: All attributes must be annotated with type hints. -@onnxscript.script(custom_aten) -def custom_aten_add(input_x, input_y, alpha: float = 1.0): - input_y = opset18.Mul(input_y, alpha) - return opset18.Add(input_x, input_y) - - -# Now we have everything we need to support unsupported ATen operators. -# Let's register the ``custom_aten_add`` function to ONNX registry, and export the model to ONNX again. -onnx_registry = torch.onnx.OnnxRegistry() -onnx_registry.register_op( - namespace="aten", op_name="add", overload="Tensor", function=custom_aten_add - ) -print(f"aten::add.Tensor is supported by ONNX registry: \ - {onnx_registry.is_registered_op(namespace='aten', op_name='add', overload='Tensor')}" - ) -export_options = torch.onnx.ExportOptions(onnx_registry=onnx_registry) -onnx_program = torch.onnx.dynamo_export( - aten_add_model, input_add_x, input_add_y, export_options=export_options - ) +def custom_aten_add(self, other, alpha: float = 1.0): + if alpha != 1.0: + alpha = op.CastLike(alpha, other) + other = op.Mul(other, alpha) + # To distinguish the custom implementation from the builtin one, we switch the order of the inputs + return op.Add(other, self) + + +x = torch.tensor([1.0]) +y = torch.tensor([2.0]) + +# Then we provide the custom implementation to the ONNX exporter as a ``custom_translation_table``. +onnx_program = torch.onnx.export( + Model().eval(), + (x, y), + dynamo=True, + custom_translation_table={ + torch.ops.aten.add.Tensor: custom_aten_add, + }, +) +# Optimize the ONNX graph to remove redundant nodes +onnx_program.optimize() ###################################################################### -# Now let's inspect the model and verify the model has a ``custom_aten_add`` instead of ``aten::add.Tensor``. -# The graph has one graph node for ``custom_aten_add``, and inside of it there are four function nodes, one for each -# operator, and one for constant attribute. -# - -# graph node domain is the custom domain we registered -assert onnx_program.model_proto.graph.node[0].domain == "custom.aten" -assert len(onnx_program.model_proto.graph.node) == 1 -# graph node name is the function name -assert onnx_program.model_proto.graph.node[0].op_type == "custom_aten_add" -# function node domain is empty because we use standard ONNX operators -assert {node.domain for node in onnx_program.model_proto.functions[0].node} == {""} -# function node name is the standard ONNX operator name -assert {node.op_type for node in onnx_program.model_proto.functions[0].node} == {"Add", "Mul", "Constant"} +# Now let's inspect the model and verify the model is using the custom implementation. +print(onnx_program.model) -###################################################################### -# This is how ``custom_aten_add_model`` looks in the ONNX graph using Netron: -# -# .. image:: /_static/img/onnx/custom_aten_add_model.png -# :width: 70% -# :align: center +# We get # -# Inside the ``custom_aten_add`` function, we can see the three ONNX nodes we -# used in the function (``CastLike``, ``Add``, and ``Mul``), and one ``Constant`` attribute: -# -# .. image:: /_static/img/onnx/custom_aten_add_function.png -# :width: 70% -# :align: center -# -# This was all that we needed to register the new ATen operator into the ONNX Registry. -# As an additional step, we can use ONNX Runtime to run the model, and compare the results with PyTorch. -# - - -# Use ONNX Runtime to run the model, and compare the results with PyTorch -onnx_program.save("./custom_add_model.onnx") -ort_session = onnxruntime.InferenceSession( - "./custom_add_model.onnx", providers=['CPUExecutionProvider'] - ) - -def to_numpy(tensor): - return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy() - -onnx_input = onnx_program.adapt_torch_inputs_to_onnx(input_add_x, input_add_y) -onnxruntime_input = {k.name: to_numpy(v) for k, v in zip(ort_session.get_inputs(), onnx_input)} -onnxruntime_outputs = ort_session.run(None, onnxruntime_input) - -torch_outputs = aten_add_model(input_add_x, input_add_y) -torch_outputs = onnx_program.adapt_torch_outputs_to_onnx(torch_outputs) - -assert len(torch_outputs) == len(onnxruntime_outputs) -for torch_output, onnxruntime_output in zip(torch_outputs, onnxruntime_outputs): - torch.testing.assert_close(torch_output, torch.tensor(onnxruntime_output)) +# .. code-block:: python +# < +# ir_version=10, +# opset_imports={'pkg.onnxscript.torch_lib.common': 1, '': 18}, +# producer_name='pytorch', +# producer_version='2.7.0.dev20250124+cu124', +# domain=None, +# model_version=None, +# > +# graph( +# name=main_graph, +# inputs=( +# %"input_x", +# %"input_y" +# ), +# outputs=( +# %"add" +# ), +# ) { +# 0 | # node_Add_0 +# %"add" ⬅️ ::Add(%"input_y", %"input_x") +# return %"add" +# } +# +# The translation is using our custom implementation: In node ``node_Add_0``, ``input_y`` now +# comes first, and ``input_x`` comes second. +# +# We can use ONNX Runtime to run the model and verify the results by calling +# the ONNXProgram directly on the input tensors. + +result = onnx_program(x, y)[0] +torch.testing.assert_close(result, torch.tensor([3.0])) ###################################################################### # Custom operators with existing ONNX Runtime support # --------------------------------------------------- # -# In this case, the user creates a model with standard PyTorch operators, but the ONNX runtime +# In this case, the user creates a model with standard PyTorch operators, but the runtime # (e.g. Microsoft's ONNX Runtime) can provide a custom implementation for that kernel, effectively replacing the -# existing implementation in the ONNX Registry. Another use case is when the user wants to use a custom implementation -# of an existing ONNX operator to fix a bug or improve performance of a specific operator. -# To achieve this, we only need to register the new implementation with the existing ATen fully qualified name. -# -# In the following example, we use the ``com.microsoft.Gelu`` from ONNX Runtime, -# which is not the same ``Gelu`` from ONNX spec. Thus, we register the Gelu with -# the namespace ``com.microsoft`` and operator name ``Gelu``. +# existing implementation. # -# Before we begin, let's check whether ``aten::gelu.default`` is really supported by the ONNX registry. +# In the following example, we use the ``com.microsoft.Gelu`` operator provided by ONNX Runtime, +# which is not the same ``Gelu`` from ONNX spec. -onnx_registry = torch.onnx.OnnxRegistry() -print(f"aten::gelu.default is supported by ONNX registry: \ - {onnx_registry.is_registered_op(namespace='aten', op_name='gelu', overload='default')}") - - -###################################################################### -# In our example, ``aten::gelu.default`` operator is supported by the ONNX registry, -# so :meth:`onnx_registry.is_registered_op` returns ``True``. - -class CustomGelu(torch.nn.Module): +class GeluModel(torch.nn.Module): def forward(self, input_x): return torch.ops.aten.gelu(input_x) -# com.microsoft is an official ONNX Runtime namspace -custom_ort = onnxscript.values.Opset(domain="com.microsoft", version=1) +# Create a namespace for the custom operator using ONNX Script +# com.microsoft is an official ONNX Runtime namespace +microsoft_op = onnxscript.values.Opset(domain="com.microsoft", version=1) -# NOTE: The function signature must match the signature of the unsupported ATen operator. -# https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/native/native_functions.yaml +# NOTE: The function signature (including param names) must match the signature of the unsupported Torch operator. +# https://github.com/pytorch/pytorch/blob/main/aten/src/Torch/native/native_functions.yaml # NOTE: All attributes must be annotated with type hints. -@onnxscript.script(custom_ort) -def custom_aten_gelu(input_x, approximate: str = "none"): - # We know com.microsoft::Gelu is supported by ONNX Runtime - # It's only not supported by ONNX - return custom_ort.Gelu(input_x) +# The function must be scripted using the ``@onnxscript.script()`` decorator when +# using operators from custom domains. This may be improved in future versions. +from onnxscript import FLOAT +@onnxscript.script(microsoft_op) +def custom_aten_gelu(self: FLOAT, approximate: str = "none") -> FLOAT: + return microsoft_op.Gelu(self) -onnx_registry = torch.onnx.OnnxRegistry() -onnx_registry.register_op( - namespace="aten", op_name="gelu", overload="default", function=custom_aten_gelu) -export_options = torch.onnx.ExportOptions(onnx_registry=onnx_registry) +onnx_program = torch.onnx.export( + GeluModel().eval(), + (x,), + dynamo=True, + custom_translation_table={ + torch.ops.aten.gelu.default: custom_aten_gelu, + }, +) -aten_gelu_model = CustomGelu() -input_gelu_x = torch.randn(3, 3) - -onnx_program = torch.onnx.dynamo_export( - aten_gelu_model, input_gelu_x, export_options=export_options - ) +# Optimize the ONNX graph to remove redundant nodes +onnx_program.optimize() ###################################################################### # Let's inspect the model and verify the model uses op_type ``Gelu`` # from namespace ``com.microsoft``. # -# .. note:: -# :func:`custom_aten_gelu` does not exist in the graph because -# functions with fewer than three operators are inlined automatically. -# - -# graph node domain is the custom domain we registered -assert onnx_program.model_proto.graph.node[0].domain == "com.microsoft" -# graph node name is the function name -assert onnx_program.model_proto.graph.node[0].op_type == "Gelu" +print(onnx_program.model) -###################################################################### -# The following diagram shows ``custom_aten_gelu_model`` ONNX graph using Netron, -# we can see the ``Gelu`` node from module ``com.microsoft`` used in the function: -# -# .. image:: /_static/img/onnx/custom_aten_gelu_model.png +# We get # -# That is all we need to do. As an additional step, we can use ONNX Runtime to run the model, -# and compare the results with PyTorch. -# - -onnx_program.save("./custom_gelu_model.onnx") -ort_session = onnxruntime.InferenceSession( - "./custom_gelu_model.onnx", providers=['CPUExecutionProvider'] - ) +# .. code-block:: python +# < +# ir_version=10, +# opset_imports={'pkg.onnxscript.torch_lib.common': 1, 'com.microsoft': 1, '': 18}, +# producer_name='pytorch', +# producer_version='2.7.0.dev20250124+cu124', +# domain=None, +# model_version=None, +# > +# graph( +# name=main_graph, +# inputs=( +# %"input_x" +# ), +# outputs=( +# %"gelu" +# ), +# ) { +# 0 | # n0 +# %"gelu" ⬅️ com.microsoft::Gelu(%"input_x") +# return %"gelu" +# } -def to_numpy(tensor): - return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy() -onnx_input = onnx_program.adapt_torch_inputs_to_onnx(input_gelu_x) -onnxruntime_input = {k.name: to_numpy(v) for k, v in zip(ort_session.get_inputs(), onnx_input)} -onnxruntime_outputs = ort_session.run(None, onnxruntime_input) +###################################################################### +# Similar to the previous example, we can use ONNX Runtime to run the model and verify the results. -torch_outputs = aten_gelu_model(input_gelu_x) -torch_outputs = onnx_program.adapt_torch_outputs_to_onnx(torch_outputs) +result = onnx_program(x)[0] +torch.testing.assert_close(result, torch.ops.aten.gelu(x)) -assert len(torch_outputs) == len(onnxruntime_outputs) -for torch_output, onnxruntime_output in zip(torch_outputs, onnxruntime_outputs): - torch.testing.assert_close(torch_output, torch.tensor(onnxruntime_output)) ###################################################################### # Custom operators without ONNX Runtime support @@ -298,24 +247,29 @@ def to_numpy(tensor): # NOTE: This is a beta feature in PyTorch, and is subject to change. from torch._custom_op import impl as custom_op + @custom_op.custom_op("mylibrary::addandround_op") -def addandround_op(tensor_x: torch.Tensor) -> torch.Tensor: - ... +def addandround_op(tensor_x: torch.Tensor) -> torch.Tensor: ... + @addandround_op.impl_abstract() def addandround_op_impl_abstract(tensor_x): return torch.empty_like(tensor_x) + @addandround_op.impl("cpu") def addandround_op_impl(tensor_x): return torch.round(tensor_x + tensor_x) # add x to itself, and round the result + torch._dynamo.allow_in_graph(addandround_op) + class CustomFoo(torch.nn.Module): def forward(self, tensor_x): return addandround_op(tensor_x) + input_addandround_x = torch.randn(3) custom_addandround_model = CustomFoo() @@ -335,8 +289,9 @@ def forward(self, tensor_x): custom_opset = onnxscript.values.Opset(domain="test.customop", version=1) -# NOTE: The function signature must match the signature of the unsupported ATen operator. -# https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/native/native_functions.yaml + +# NOTE: The function signature must match the signature of the unsupported Torch operator. +# https://github.com/pytorch/pytorch/blob/main/aten/src/Torch/native/native_functions.yaml # NOTE: All attributes must be annotated with type hints. @onnxscript.script(custom_opset) def custom_addandround(input_x): @@ -350,13 +305,16 @@ def custom_addandround(input_x): onnx_registry = torch.onnx.OnnxRegistry() onnx_registry.register_op( - namespace="mylibrary", op_name="addandround_op", overload="default", function=custom_addandround - ) + namespace="mylibrary", + op_name="addandround_op", + overload="default", + function=custom_addandround, +) export_options = torch.onnx.ExportOptions(onnx_registry=onnx_registry) onnx_program = torch.onnx.dynamo_export( custom_addandround_model, input_addandround_x, export_options=export_options - ) +) onnx_program.save("./custom_addandround_model.onnx") @@ -436,7 +394,7 @@ def custom_addandround(input_x): # ---------- # # Congratulations! In this tutorial, we explored the :class:`ONNXRegistry` API and -# discovered how to create custom implementations for unsupported or existing ATen operators +# discovered how to create custom implementations for unsupported or existing Torch operators # using ONNX Script. # Finally, we leveraged ONNX Runtime to execute the model and compare the results with PyTorch, # providing us with a comprehensive understanding of handling unsupported From 152b8e4e1af2b3ca1f548fbc3af08e22e37cdc6f Mon Sep 17 00:00:00 2001 From: Justin Chu Date: Fri, 24 Jan 2025 13:52:03 -0800 Subject: [PATCH 3/5] done --- .../onnx/onnx_registry_tutorial.py | 265 +++++++----------- 1 file changed, 102 insertions(+), 163 deletions(-) diff --git a/beginner_source/onnx/onnx_registry_tutorial.py b/beginner_source/onnx/onnx_registry_tutorial.py index 9f9b028fc42..5cfb7635d00 100644 --- a/beginner_source/onnx/onnx_registry_tutorial.py +++ b/beginner_source/onnx/onnx_registry_tutorial.py @@ -14,26 +14,26 @@ # Overview # -------- # -# This tutorial describes how you can create ONNX implementation for unsupported Torch operators +# This tutorial describes how you can create ONNX implementation for unsupported PyTorch operators # or replace existing implementation with your own. # -# We will cover three scenarios that require extending the ONNX registry with custom operators: +# We will cover three scenarios that require extending the ONNX exporter's operator support: # -# * Unsupported Torch operators -# * Custom operators with existing ONNX Runtime support -# * Custom operators without ONNX Runtime support +# * Overriding the implementation of an existing PyTorch operator +# * Using custom ONNX operators +# * Supporting a custom PyTorch operator # -# Unsupported Torch operators -# -------------------------- +# Overriding the implementation of an existing PyTorch operator +# ------------------------------------------------------------- # -# Although the ONNX exporter team does their best efforts to support all Torch operators, some of them +# Although the ONNX exporter team does their best efforts to support all PyTorch operators, some of them # might not be supported yet. In this section, we will demonstrate how you can add -# unsupported Torch operators to the ONNX Registry. +# unsupported PyTorch operators to the ONNX Registry. # # .. note:: -# The steps to implement unsupported Torch operators are the same to replace the implementation of an existing -# Torch operator with a custom implementation. -# Because we don't actually have an unsupported Torch operator to use in this tutorial, we are going to leverage +# The steps to implement unsupported PyTorch operators are the same to replace the implementation of an existing +# PyTorch operator with a custom implementation. +# Because we don't actually have an unsupported PyTorch operator to use in this tutorial, we are going to leverage # this and replace the implementation of ``torch.ops.aten.add.Tensor`` with a custom implementation the same way we would # if the operator was not implemented by the ONNX exporter. # @@ -44,13 +44,13 @@ # # No decompositions registered for [...] # -# The error message indicates that the unsupported Torch operator is ``torch.ops.aten.add.Tensor``. +# The error message indicates that the unsupported PyTorch operator is ``torch.ops.aten.add.Tensor``. # The operator is of type ````, and this operator is what we will use as the # target to register our custom implementation. # -# To add support for an unsupported Torch operator or to replace the implementation for an existing one, we need: +# To add support for an unsupported PyTorch operator or to replace the implementation for an existing one, we need: # -# * The target Torch operator. +# * The target PyTorch operator. # * The implementation of the operator using `ONNX Script `__. # ONNX Script is a prerequisite for this tutorial. Please make sure you have read the # `ONNX Script tutorial `_ @@ -70,8 +70,8 @@ def forward(self, input_x, input_y): return torch.ops.aten.add.Tensor(input_x, input_y) -# NOTE: The function signature (including param names) must match the signature of the unsupported Torch operator. -# https://github.com/pytorch/pytorch/blob/main/aten/src/Torch/native/native_functions.yaml +# NOTE: The function signature (including param names) must match the signature of the unsupported PyTorch operator. +# https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/native/native_functions.yaml # NOTE: All attributes must be annotated with type hints. def custom_aten_add(self, other, alpha: float = 1.0): if alpha != 1.0: @@ -101,6 +101,7 @@ def custom_aten_add(self, other, alpha: float = 1.0): print(onnx_program.model) +###################################################################### # We get # # .. code-block:: python @@ -138,8 +139,8 @@ def custom_aten_add(self, other, alpha: float = 1.0): ###################################################################### -# Custom operators with existing ONNX Runtime support -# --------------------------------------------------- +# Using custom ONNX operators +# --------------------------- # # In this case, the user creates a model with standard PyTorch operators, but the runtime # (e.g. Microsoft's ONNX Runtime) can provide a custom implementation for that kernel, effectively replacing the @@ -148,25 +149,29 @@ def custom_aten_add(self, other, alpha: float = 1.0): # In the following example, we use the ``com.microsoft.Gelu`` operator provided by ONNX Runtime, # which is not the same ``Gelu`` from ONNX spec. + class GeluModel(torch.nn.Module): def forward(self, input_x): return torch.ops.aten.gelu(input_x) + # Create a namespace for the custom operator using ONNX Script # com.microsoft is an official ONNX Runtime namespace microsoft_op = onnxscript.values.Opset(domain="com.microsoft", version=1) -# NOTE: The function signature (including param names) must match the signature of the unsupported Torch operator. -# https://github.com/pytorch/pytorch/blob/main/aten/src/Torch/native/native_functions.yaml +# NOTE: The function signature (including param names) must match the signature of the unsupported PyTorch operator. +# https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/native/native_functions.yaml # NOTE: All attributes must be annotated with type hints. # The function must be scripted using the ``@onnxscript.script()`` decorator when # using operators from custom domains. This may be improved in future versions. from onnxscript import FLOAT + @onnxscript.script(microsoft_op) def custom_aten_gelu(self: FLOAT, approximate: str = "none") -> FLOAT: return microsoft_op.Gelu(self) + onnx_program = torch.onnx.export( GeluModel().eval(), (x,), @@ -187,6 +192,7 @@ def custom_aten_gelu(self: FLOAT, approximate: str = "none") -> FLOAT: print(onnx_program.model) +###################################################################### # We get # # .. code-block:: python @@ -221,181 +227,114 @@ def custom_aten_gelu(self: FLOAT, approximate: str = "none") -> FLOAT: ###################################################################### -# Custom operators without ONNX Runtime support -# --------------------------------------------- -# -# In this case, the operator is not supported by any ONNX runtime, but we -# would like to use it as custom operator in ONNX graph. Therefore, we need to implement -# the operator in three places: +# Supporting a custom PyTorch operator +# ------------------------------------ # -# 1. PyTorch FX graph -# 2. ONNX Registry -# 3. ONNX Runtime +# In this case, the operator is an operator that is user implemented and registered to PyTorch. # # In the following example, we would like to use a custom operator # that takes one tensor input, and returns one output. The operator adds # the input to itself, and returns the rounded result. # -# -# Custom Ops Registration in PyTorch FX Graph (Beta) -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# -# Firstly, we need to implement the operator in PyTorch FX graph. -# This can be done by using ``torch._custom_op``. -# +# Firstly, we assume the custom operator is implemented and registered with ``torch.library.custom_op()``. +# You can refer to `Creating new custom ops in Python `_ +# for a detailed guide on how to create custom operators. -# NOTE: This is a beta feature in PyTorch, and is subject to change. -from torch._custom_op import impl as custom_op +# Define and use the operator in PyTorch +@torch.library.custom_op("mylibrary::add_and_round_op", mutates_args=()) +def add_and_round_op(input: torch.Tensor) -> torch.Tensor: + return torch.round(input + input) -@custom_op.custom_op("mylibrary::addandround_op") -def addandround_op(tensor_x: torch.Tensor) -> torch.Tensor: ... - -@addandround_op.impl_abstract() -def addandround_op_impl_abstract(tensor_x): +@add_and_round_op.register_fake +def _add_and_round_op_fake(tensor_x): return torch.empty_like(tensor_x) -@addandround_op.impl("cpu") -def addandround_op_impl(tensor_x): - return torch.round(tensor_x + tensor_x) # add x to itself, and round the result - - -torch._dynamo.allow_in_graph(addandround_op) - - -class CustomFoo(torch.nn.Module): - def forward(self, tensor_x): - return addandround_op(tensor_x) +class AddAndRoundModel(torch.nn.Module): + def forward(self, input): + return add_and_round_op(input) -input_addandround_x = torch.randn(3) -custom_addandround_model = CustomFoo() +# Implement the custom operator in ONNX using ONNX Script +def onnx_add_and_round(input): + return op.Round(op.Add(input, input)) -###################################################################### -# -# Custom Ops Registration in ONNX Registry -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# -# For the step 2 and 3, we need to implement the operator in ONNX registry. -# In this example, we will implement the operator in ONNX registry -# with the namespace ``test.customop`` and operator name ``CustomOpOne``, -# and ``CustomOpTwo``. These two ops are registered and built in -# `cpu_ops.cc `__. -# - - -custom_opset = onnxscript.values.Opset(domain="test.customop", version=1) - - -# NOTE: The function signature must match the signature of the unsupported Torch operator. -# https://github.com/pytorch/pytorch/blob/main/aten/src/Torch/native/native_functions.yaml -# NOTE: All attributes must be annotated with type hints. -@onnxscript.script(custom_opset) -def custom_addandround(input_x): - # The same as opset18.Add(x, x) - add_x = custom_opset.CustomOpOne(input_x, input_x) - # The same as opset18.Round(x, x) - round_x = custom_opset.CustomOpTwo(add_x) - # Cast to FLOAT to match the ONNX type - return opset18.Cast(round_x, to=1) - - -onnx_registry = torch.onnx.OnnxRegistry() -onnx_registry.register_op( - namespace="mylibrary", - op_name="addandround_op", - overload="default", - function=custom_addandround, -) - -export_options = torch.onnx.ExportOptions(onnx_registry=onnx_registry) -onnx_program = torch.onnx.dynamo_export( - custom_addandround_model, input_addandround_x, export_options=export_options +onnx_program = torch.onnx.export( + AddAndRoundModel().eval(), + (x,), + dynamo=True, + custom_translation_table={ + torch.ops.mylibrary.add_and_round_op.default: onnx_add_and_round, + }, ) -onnx_program.save("./custom_addandround_model.onnx") - - -###################################################################### -# The ``onnx_program`` exposes the exported model as protobuf through ``onnx_program.model_proto``. -# The graph has one graph nodes for ``custom_addandround``, and inside ``custom_addandround``, -# there are two function nodes, one for each operator. -# - -assert onnx_program.model_proto.graph.node[0].domain == "test.customop" -assert onnx_program.model_proto.graph.node[0].op_type == "custom_addandround" -assert onnx_program.model_proto.functions[0].node[0].domain == "test.customop" -assert onnx_program.model_proto.functions[0].node[0].op_type == "CustomOpOne" -assert onnx_program.model_proto.functions[0].node[1].domain == "test.customop" -assert onnx_program.model_proto.functions[0].node[1].op_type == "CustomOpTwo" +# Optimize the ONNX graph to remove redundant nodes +onnx_program.optimize() +print(onnx_program) ###################################################################### -# This is how ``custom_addandround_model`` ONNX graph looks using Netron: -# -# .. image:: /_static/img/onnx/custom_addandround_model.png -# :width: 70% -# :align: center -# -# Inside the ``custom_addandround`` function, we can see the two custom operators we -# used in the function (``CustomOpOne``, and ``CustomOpTwo``), and they are from module -# ``test.customop``: -# -# .. image:: /_static/img/onnx/custom_addandround_function.png -# -# Custom Ops Registration in ONNX Runtime -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# -# To link your custom op library to ONNX Runtime, you need to -# compile your C++ code into a shared library and link it to ONNX Runtime. -# Follow the instructions below: -# -# 1. Implement your custom op in C++ by following -# `ONNX Runtime instructions <`https://github.com/microsoft/onnxruntime/blob/gh-pages/docs/reference/operators/add-custom-op.md>`__. -# 2. Download ONNX Runtime source distribution from -# `ONNX Runtime releases `__. -# 3. Compile and link your custom op library to ONNX Runtime, for example: -# -# .. code-block:: bash -# -# $ gcc -shared -o libcustom_op_library.so custom_op_library.cc -L /path/to/downloaded/ort/lib/ -lonnxruntime -fPIC -# -# 4. Run the model with ONNX Runtime Python API and compare the results with PyTorch. +# We get # # .. code-block:: python +# < +# ir_version=10, +# opset_imports={'pkg.onnxscript.torch_lib.common': 1, '': 18}, +# producer_name='pytorch', +# producer_version='2.7.0.dev20250124+cu124', +# domain=None, +# model_version=None, +# > +# graph( +# name=main_graph, +# inputs=( +# %"input" +# ), +# outputs=( +# %"add_and_round_op" +# ), +# ) { +# 0 | # node_Add_0 +# %"val_0" ⬅️ ::Add(%"input", %"input") +# 1 | # node_Round_1 +# %"add_and_round_op" ⬅️ ::Round(%"val_0") +# return %"add_and_round_op" +# } # -# ort_session_options = onnxruntime.SessionOptions() -# -# # NOTE: Link the custom op library to ONNX Runtime and replace the path -# # with the path to your custom op library -# ort_session_options.register_custom_ops_library( -# "/path/to/libcustom_op_library.so" -# ) -# ort_session = onnxruntime.InferenceSession( -# "./custom_addandround_model.onnx", providers=['CPUExecutionProvider'], sess_options=ort_session_options) +# And exported program # -# def to_numpy(tensor): -# return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy() +# .. code-block:: python +# ExportedProgram: +# class GraphModule(torch.nn.Module): +# def forward(self, input: "f32[1]"): +# input_1 = input # -# onnx_input = onnx_program.adapt_torch_inputs_to_onnx(input_addandround_x) -# onnxruntime_input = {k.name: to_numpy(v) for k, v in zip(ort_session.get_inputs(), onnx_input)} -# onnxruntime_outputs = ort_session.run(None, onnxruntime_input) +# add_and_round_op: "f32[1]" = torch.ops.mylibrary.add_and_round_op.default(input_1); input_1 = None +# return (add_and_round_op,) # -# torch_outputs = custom_addandround_model(input_addandround_x) -# torch_outputs = onnx_program.adapt_torch_outputs_to_onnx(torch_outputs) +# Graph signature: ExportGraphSignature(input_specs=[InputSpec(kind=, arg=TensorArgument(name='input'), target=None, persistent=None)], output_specs=[OutputSpec(kind=, arg=TensorArgument(name='add_and_round_op'), target=None)]) +# Range constraints: {} # -# assert len(torch_outputs) == len(onnxruntime_outputs) -# for torch_output, onnxruntime_output in zip(torch_outputs, onnxruntime_outputs): -# torch.testing.assert_close(torch_output, torch.tensor(onnxruntime_output)) +# The translation is using our custom implementation to translate the ``torch.ops.mylibrary.add_and_round_op.default`` +# operator in the ExportedProgram to the ONNX operator ``Add`` and ``Round``. # + +###################################################################### +# Finally we verify the results. + +result = onnx_program(x)[0] +torch.testing.assert_close(result, add_and_round_op(x)) + +###################################################################### # Conclusion # ---------- # -# Congratulations! In this tutorial, we explored the :class:`ONNXRegistry` API and -# discovered how to create custom implementations for unsupported or existing Torch operators +# Congratulations! In this tutorial, we explored the ``custom_translation_table`` option and +# discovered how to create custom implementations for unsupported or existing PyTorch operators # using ONNX Script. +# # Finally, we leveraged ONNX Runtime to execute the model and compare the results with PyTorch, # providing us with a comprehensive understanding of handling unsupported # operators in the ONNX ecosystem. From 6a70faf346ea7ae8e705758193a5657560f53871 Mon Sep 17 00:00:00 2001 From: Justin Chu Date: Fri, 24 Jan 2025 13:55:56 -0800 Subject: [PATCH 4/5] you --- beginner_source/onnx/onnx_registry_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/onnx/onnx_registry_tutorial.py b/beginner_source/onnx/onnx_registry_tutorial.py index 5cfb7635d00..8c9bb2770f0 100644 --- a/beginner_source/onnx/onnx_registry_tutorial.py +++ b/beginner_source/onnx/onnx_registry_tutorial.py @@ -142,7 +142,7 @@ def custom_aten_add(self, other, alpha: float = 1.0): # Using custom ONNX operators # --------------------------- # -# In this case, the user creates a model with standard PyTorch operators, but the runtime +# In this case, you create a model with standard PyTorch operators, but the runtime # (e.g. Microsoft's ONNX Runtime) can provide a custom implementation for that kernel, effectively replacing the # existing implementation. # From 2aae03039f9109cbf5b68cee9e0d22a1dba56e3f Mon Sep 17 00:00:00 2001 From: Justin Chu Date: Fri, 24 Jan 2025 13:57:55 -0800 Subject: [PATCH 5/5] we --- beginner_source/onnx/onnx_registry_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/onnx/onnx_registry_tutorial.py b/beginner_source/onnx/onnx_registry_tutorial.py index 8c9bb2770f0..8266f912a13 100644 --- a/beginner_source/onnx/onnx_registry_tutorial.py +++ b/beginner_source/onnx/onnx_registry_tutorial.py @@ -142,7 +142,7 @@ def custom_aten_add(self, other, alpha: float = 1.0): # Using custom ONNX operators # --------------------------- # -# In this case, you create a model with standard PyTorch operators, but the runtime +# In this case, we create a model with standard PyTorch operators, but the runtime # (e.g. Microsoft's ONNX Runtime) can provide a custom implementation for that kernel, effectively replacing the # existing implementation. #