From d2e6cf28bc1ecf929878c4f27d08d6e8de441883 Mon Sep 17 00:00:00 2001 From: youben11 Date: Wed, 31 May 2023 05:09:07 +0100 Subject: [PATCH] docs(compiler): update compilation docs --- docs/SUMMARY.md | 4 +- .../compiling_and_executing_example_graph.png | Bin 42807 -> 0 bytes docs/dev/compilation.md | 142 --------------- docs/dev/compilation/README.md | 170 +++++++++++++++++- 4 files changed, 168 insertions(+), 148 deletions(-) delete mode 100644 docs/_static/basics/compiling_and_executing_example_graph.png delete mode 100644 docs/dev/compilation.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index a897adfe0..6b1077196 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -42,9 +42,9 @@ * [Terminology and Structure](dev/terminology_and_structure.md) * [Frontend fusing](dev/fusing.md) -### Compilation workflow +### Compilation -* [Compilation](dev/compilation/README.md) +* [Compilation Workflow](dev/compilation/README.md) * [Automatic Crypto Parameters choice](dev/compilation/optimizer.md) * [MLIR FHE Dialects](dev/compilation/dialects.md) * [FHE Dialect](dev/compilation/fhe_dialect.md) diff --git a/docs/_static/basics/compiling_and_executing_example_graph.png b/docs/_static/basics/compiling_and_executing_example_graph.png deleted file mode 100644 index 0ace6998325ed0a33f116b60b0add84cad96abbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42807 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�*Btq+aUHQ7+;E<|=XEsa=s*fnh8 zZ((U^6tE{Cj?~oD1QG1`j~_~=rVJor_&e+=TGMpf&5a>CR6uh$E+vJ5L5ctWL?6j% zU7ev~9j2+{YgV2#6&7K!WIoz?BJ%RXQf)h=F4)G-XyG=s2lP;2m@h5@E&Mhl+V%^D zN3mEfU_c5B3(vO06_$iS1tLNv|F5=tZ%@w=?d5{yQxlT`T9ERSG0XK`x%08HvBbZX z4bC6px|Y#Zjeq2D@W-YM>$6)+zc!w~eGGZ?NGTW1Y0yzGs5A1jZ1dBLV zz|uTqgnOJJOWA^hEi@2<$l90^8XUOE&(FU;SsT7oB=)_ih|6D|g*lF>0i$hh&IICo zsdIt2-Y4KSaJk+fY`{{<6pxGjlNFtZuv=6@?22yMHjLc!ZGCDk$R_O96X;>sW>oEd zAc_nIE^KPbq^PLqN|}I_>*-M$AYOIcA`_u)L!&9~6hZnPycZHofe3dfxN#D_y2so6 zb{GC5(*~Onxtn=*i@$cU|W-m{JfRpza*RaSPX_z^htPu#jJPgXM z4A^jLIFoCijl*mR{dTCe)pwC{>A<+kl376u%@}|D>_2zW4J9G$Ud_p!<#}FQG)LPW zA96n9=hhG)tIb38sku4JR;>T%2m)pcyn5p+w@ZEA$i2?T-XMp6d!LphCFQ9pv4ljk z!f?vjqZ=P<_(g`*l*338tJ66KgoQQr`o6tI+%vD3-rB{I0e?ToG-yN^+9~TBkXK#9 zwmG}-$>7FoV+!%1p+u_OJFxkkN1#-?<#>Xp!gjSsZ1{tY++p(HD?!jCEgk8M0WMb0 z)D##V-l(@lS>!PT$l*Fdv5EJFhGOTYCYda-iX|d?je&}fbyNrIA;UV-qZuVt)uJ$! zlM~yi$`|x2d<=BVG^)1VFHj$m_aOfFm;+zDvy#7i^N~>+HZv2kk(2q>@BpAKRGe8QfY6S#m*AxU|IK^JmR;I9dOpS~2JF>B-#Ig%00a z;t(~CZ+|P>hvS0IY%b15E)lMQds)B~i2}XX6mCUjh}*v~HqQI>=`9hncW`WMteGo( zxXQUPkO_1`VQ)!OIdNU8%;c<}c}&BuDytm@XYUlpxNO{?mChuplZPZFy}4-N_+D9| zG5qoQH)?q|H@Nsv;?P*|*RO0Rj*c95yPPyyh_uv!x*06us>1-27iWjfjIq6~y1O8k zq5(|eRg4WRng1zfmWw7@R8CGyjv1F1&J1L@nP=im0@UIJdix?!!6g^v6{-HCO)`;t z_~kqLaHX7FlVrvGHVe3UPcJmy%sd4X!vKfNd6F>uKb`0jpixnoTQ`Q`g}+~tMklUT zwfaHnzZP*7XL%bvGmOlqjFB1l9sTUzyXrt5tox8Nh#>4PZuC|19&Us(Qt7`$%-EQ2 zXlMvzB~c1zT?z$lZOmRp(O#l7!CG1!S+3v0h#AZ`>m`x*H`nwJbtipq&;9sBMd8x$ z%w1BVvp)$g))F|~pU?z))faBZ(>V{3GbYexHIAiU6GWBmLS*+L$WNySodJiDPZHyN zLDR!L8@2oWu36I($|tBDb)5HPjG5PR=ky%7{J5GLvHvW?3xW4gV-S`<1(}MyJvT_; z{svq|X`gW>>EDo-BXMn+k5DkUHZysvSVtW}HCdtO`Cp0T+eWISh`ISf@YxxT4Nse; z(Q7tkH8BcQvck0FgnqWs3U%BGoZElmhD4Fe`cTLpO2a%QM{9 zZS6_sE6;gbobbnwzWWN2Ttaafw{vn*Lw3#&p`hf4ZpX)4{*~_7*Cy`}4x4|w-zUBy zGKJ}8a&bz+e)cy%_>J;`av~tC=cHXPGYDff^${#Y^-WN#g@3ZPJ0ItbD!?D z&scg)YRQFbfTliP&0{q5r`V+tHV-(1gWTJ~b=Lez_w zJ^z)Wn$euCWZDv+6KAz_29(!LUDXj;g{0_#r6XnqGF>@VAuCoCvPM?#+UIqR!9>^T zX}BUHE6ExULqmRaaVn<#YnKT0byCiWY#aNit6WbUl3#Y}0o+GdPf z%s?dbFdee>Cs;U8&-%2R7CIoCow{~-5lD_-Uak+Z7V>>CymyBZXJ7zY@^5xNFy{#R!ty&`H_-i`=lcFLnH#fUKcN1%$lfb*ZhvOV}Hf0<=_Bo1~ z4L;pUD_LZ-pqFvY$WHNZC#K~XE-qR+YMM^Oc`aO*JFkNTyEH`kL;bE+!4dx&k_mEu zoI_lk4KIe$d*y>%+o45&0-tbNPt!;4k?9XGxxo?e%L~BW#g=sbO<4>&67fH%g?}I( zrRL>Lz_bO7v53%6UVDYn88vXNJY%YBh&~cW&pIHsEF^o6zr95r9q&=#N|Mip7ZVmI zrv+ga=H}_Jigh6%>pqX|iOw{_SN((2|B#2bh;O*&ncZ2WuCnoWn!PUp~F&2JukK$NJ|*UVtj&d+n&P+pRTQ z2XsATXZxkJ4}G;2eJNiv!D7TcA}<%6-SgpWN>z^R)Y;}~IOE|~_=vv>d?_mLD6h!` z^n-MuuY%esak$~gh~KrUzAxW*C35A#+M4@3FZ6j=%^jcDf#ywdc|tER=##wX_f1Wd zzW=R$=7~=Y8iBCg0tClHX7cMs6@6B8anasKS?wVc7NM2?$|kQso8xC^^DRtjrz>zI z=C0WIPjN`;UUC?c1GiV*%W+uQjN#qprSbJGPz3>xR_JUa>**j>I4BD)ATCy12kN%x zyLL^WURIceDIMKtC(P`fk%!#U!Tkl4xQ7qLmi^8e8CA2tKJ}!y8q4T!9zjbXv%B+B zqO1$7DHP=1^KYNUG0SpQZmlOTImY!OOMjL9&F)>QDbydsxxn>rGn|}uQ;QDg!jz^k z&>`IUX>{$SxpO&|kkFPzP@qpt|GrUw`b6Q9Wc+;DNDpb=yIvoITtTJjHiC zl6wgUMF0J0V0(_O+hf2oI(fgKm%DQ(I`nG|o56`t+RcB9z0k-8c2(3lzo4KF(FE#b zsQ_!?t9~#6tOETzSUdo~29BzGZ8OjIa?`lht^k$EYv#{2JHhnS5S#mdQ!eKI>pT{q4~yzFu&jL}Gyd?#8Z1WM?E?|I2c&R@4muFzNZC@rB@L&!a-!*V7TN zgBGQ1Z~M&3OVjaVLiEH(SU_9%RS&<%`yJAA11y z0A^M0-;0}e3eSwtfvcM|xUDfxSc}nfjvQJV+u13}4{@4^Vtoo+VPR~#WOaqVoYsFo zqS0)9etw+8_I3z=g>iIEo5j0X5RsUbmU(1a^b4*5D;4J)ElUyeZFL+@ePMZ7#?e1A z)E_y8OUiQK-kS>@(^$-~=QvcMHJhcUxJAyrxHy&tbAL9Knr^00o9YHFd{%L=-?4QT z%a|5RMkml}@sLKUt6uq)Qa(ISTu#8p?ur$R>CHa37y1C)qf#$z+x=5H#^7jFu1l&0fN1n_E#g*Vhtlmi8PF{QnyzsurNb4xqbPOFqXZ zeL*V5sX+O-m}yzXq_`_1dzIgTW$6cl=J%+TFUvtF&?haXX$=Hgm{T`^4px+#iYhez zf%H@6IFcw254Mix=K1fe>TmB5dgKP(OXWPa0f!d8IxYA^m1DaGqEJ3;tjN^y@m(+S z$cUQ&OPV-7uc?$6c4eW&P2!S5u0`%0&Zed3roX{DH<&K8G?~PXUVwg^-4J{laD))9yE2=dMkvych!3XemWdsdjAg&Lp*zu3V8}RaF_diZ6t)s z+hJX7cr1Z0iZHpp8V3Q8FQ;doSE0K(Vgr|(77(x;Wu_%00O=*L%?#Mvk_rABEP3$}GI6RpSa1|YlS`RIg$=$7V9a*~qf83Qo_Mi|Bvp1r7)j*KKQAUNJCI zRNIll&xS`tXgfe_Yk32|=2EM2Y|~|Oq`bVzr4|G^VC0aRtZE_Q=-;wlI`8_W+E`tF z_Vx9I{npg5#S;+pD1CQ_G5+1gL%*eJ^-sboHPb)$aL{ePa}Su&=?NpNGoymr^IL?4 z)5@wuXET7qb8zu2!jQ^{%t%kd+G^OWH_v<`H(dR@93ERokz+Ui10T8#%+1HRO3Jn2 zV$stvF}=#$=18`R+2*Z*kABYyl@BMpjv5k;HDfF5q03%>%k{1Y{A-^V%zHjEsSW2C5s$+ z5^g)|CkG$YLNKVl0q1~b|EByF5>A_-<3?*SF?9affn3_>SpjDA&D7T~6$61UOcIYa zH#|1Ext4rFBlk-A)EQP+$r~zb4d7;IboO58r*#!zd2i*TasIewt#aI$nXJS06#3jwS{b(7gH zb}SLn0@^CjT01U&p$!2iw`%4y|7Vme^+Jq~_MD;<>eGeQ2Iu zAbGFI=AM~}qWe;ow8%&n{re7~+n%|?2luIrgG~U})uYp(3TDk*JAoUSwS3y1AF|G@ z;sfS(A{Hk`ujO9Ut%rjAd^R020XaH?nJ6&mB~g`BV&aI|X% zhs%aY>99cfy^_;poud?4kyTDEsR6&S>r3W*8f{l+8tQCiIqj^}3zfhbHudT*Q`bmY z&*K^rF1vxj_=j(x#FA-fhoG@3J_2>WW{8cpwphnA!~WI`nbObkoxLOlc6=jR)j(mt zXLJf@dtT2*=4K=~gZ}^Ns>9^w{7dq>Cr`xY;^JgU5w%2^Ap%!P5Vw^d&I*(DJ-HM4 zr|gS6p`W?S>qA%>#L|q4Xd9fDAOtB!7LP9dFKefVM*N0k( z3`h2|Z$)?8;$>vQe|3T=v}*qtf$d!9$p(kR_jnm zXHA5p6xtc4C-ioZzQOJo`C{D;=8~krX>9&=#~UjkXv{%D6B6A0BN>&bPINO298aj9 zLwnvf7vffC$LyeE_m-iPIciEdrY7(rBrsa8uRRPc8l*is*NMl;xbMcdAJ>Q>rSPlD zG^d*C>eLWfQLmJFsjiN`Y1_(PxAT?ArfIN!rRH1y?_cB-h$L4^N)hS6&~Gfky4}N0 zmt_wB^sYCx?bvmkFuGf6Pfoq_UZjIpI>?c%W> zres}5vKI*>R8`?h z$<9af#lHIOf7Uww^~3-B&e`XUXa6a!@+4~IfYhadgloQP&+JKPBf;L@@P1N;r@0xG zmo-!w5e?8T!J0iHuha*3iR?mf+ujHh)|db16j#NFywH&AcxVQ@Y3KlxwJA_^UcG1wR z)VFkXn+@nK`9j5w+Lsp3EeW-P?SM13r4txTUg4hT~X08vuDp|P9*vZ#vN^}6^p(P+OSj6(n=k0h(~T0(TimyhoI}?ss(V>4#Q4_ zEmwz{Ataq2qgPhMR8&fj7ZqIWVXy?NZF3V-O1XbODrf4T(PU8?l6H}hBrd!++g2aK z!fpobNf}jb93KBDX12P#2$egs3by(lxrdZSO3^A36YEeqj+;2RAx_U3PSsA%85Xeu zJL%@?EH~B2qa9l77_zGP2VAbsWc!o281W#dhc#G>2CC~lZJeTC&!=r%K!J^267LN!yEFHr(?b=oQ|1!VK77dnUPU-V`GFf2GB+Vh5-ZN@l4FCbwi** z&Cbpan)Y(ccL1-$2>A}M<)DXQ_Y>|O3Sy3jX#yvepR>^5R;rOBv3u)=-dfnQ+F~9S zP@PnsyryOO=;%%H8`+Z2H{rWWw^}Uj%${$0@d^vu2L=LGNBjQWz9AJ}pR3+_Lqi(B zQAJfv&D)8;(94T+MrnA2l}Ipp84`wk)Sdgu{{i?13efPre;=0)PXSM%0BYI*t^k0E z{YXw>ZEe!pnj^Lu_Sgt&l&DjT3_O>)x?W$O3i@QAg}-y`{~|SI?}ZW7a{j2mEoEMc zvp@yEgY`TB#DF3+MdWo_A+wL|?JSu33D+n%f6Zb+Wo2A6t+2j<0q8Ii-3n7YIuA6< z^e^V|36Tko(&u=Q8iv^@0xbr=O=LL00%m-0Okk1vZ7I^wg+9Rps$u}+0q_@CKG3KJ z-I|d%N82v}{N>i_5cLUr&9|h8NBJ#zFz8G&Lf!-eK;nuT0bPTB+eCMlOe#K~9xRp- zv~jO``;CoinwrE!cbGZuk*xTRQz;_}KuJy`f&6xu_jV`I>gp;heTXlEEP?LB7Z z;eSQE&5W3Or2ny4`%$;>wznMMu0VJDF~ET2&AC+cc0r9iO5gU1gaH%};LX%ID6SeP z4<4Y_*T1ab@kw#Y9=f`_Cp%44fKG2oSy?`yslXbySh!Y58R#X;g7(@cDmshGv^Nkf zFg5_cfCl>RUhXC05F8u1@e!_RU40KUs;;2D!x_K0`g+MLa0jp}@c$Oz2H4NSzBkR& zR}_Yx-eUs;%E0#AnHdW%F0MNI7f8+>Zv<%bfOfo%jSY~_TCXjOp>u;|IpwPd^Zxw# z^9s2D9XJ>OQ?8Z{G^aqr1v&EoN()~eptcA+h61#3`}CYq#y#Q+vU|2vvsn9y$LmTq zdG!H8$i&3-%+T-(sRW1!F){HMz^(a?Cz~tQGsuaHcXf2&RloRkqmWru0}9Jb6Npd$C^(;19XWvpusG?}TT3g)w>5cs)Hm5Wj;f-0(wO z+|_#J`0uI%{OCW4^C$UFVyum6QlaD|gcD5J090DR;Xsd{gCf$*^-~Qo;4`fK{lTi^ zKY~IzK@$du$C5XiO8FQ9%HYXt#cgdl#W}uFfKW_LO}%&T-rT-a*J zZG*KV@|t291;xeT=ck9`KYtc(SW|J8m*wQK`W3%w{tc05@bnMNQ?)dJD>xdEx@ zG+`XrFGj#541gU3c+))KSUv)!FU}3j1nq1y*UbM#)OW{I`M>|$TO7%r$B2kR9A$4Z z5|Id%k<3HG9tj7T5htQ#q>NG-nMw8t?~LqYC!1t{uUntrJ z=?y?)Vo>_ackbLd2t-ZeQfN~}Wf7G~=|FfVCnuL%Ngx&~Zf<)YA9-Ze_;L>?>+4Ct zp9HQQ@Nj@<0=76|b#8|^>M|8_@C$jc2d2GxYqTwdl=5|9A;feza=p>fQFywNuV9Oc z;gm!Adf{M}?ae%pfvEnx7LmowxRX*KLc>=P40|*xiUwSL@5G~PYLvkG12~#l>@1OOFt%zM_rXd(eaeynvmRhJkgVQdg5VNl zqQ0DOS|n#QNDOO?@tUE7AA(sAOcm|NjTBVj{}ss@7$=?9fSoE5{6R_w?2WsB7GIT= zB!UkQu8%ALL|A{?>)%A{>+8ei*w7z8uKe@9!H%5R+C2(c7yf4*Z$(-uAU%nmK0OT{ zPuQVSU{^#ef#4Sfi&OjNjOb81pp<0IUo_aTHa0fC|M;N^iJ(aCBHV%&H#~*x_^LNJ z8w9wH_4f3riL*?$CW}E6dm3WLRS>Vw6r+VtOS^UZHdS%|(JB`e^$ja)imJedi7Fw^ z?9zsCC@hSbY>N9(TH4qZbl|0>r3G26RhB8P$->IY4%}Kuf0c^Nd!t+1$;64J6BuqGy=>3SbVBc7$2?nyZ6(^n1&e% zo8ctDE*{@Ge!Ytq=NA_duPL~|v$}7?>JCysn*2L08Pu|}at7=h;ChOM{)1SMh+YVi zD3a0;(niY^d~P`FyPkB~7B(6H{uuxqh<|Xb z48#ADId{%!x;TsZ402dfg=j8Y_RE(cucMG6ZG6;|p1(@bHvg2s9nw?3t*sWuV!QJ39>xGZb1NuNT+soO<%)3EbxNt#}>5 zzJ8yYyYoy$aY{QPK(6FRr8JF^J`-~HVC< zvYj`&YGpOGST;68_!bQb0NB&tjXY%oBO{}ugvG6q7YuPHfZ)Bm`z&CI?VUSOU}FYD z;gQj*dUEL2kSxITKXV_%wdtu(OM2IyeyVX55)y*!uE1l|{n-lO?frYiK-d5Lx^#6= z-au(}P3^1hxg9L<9aA$i$nT7=L@H10F95j3&dtxigg8&J3B=Q~N_YUB0VMIBdl})` zqt;OB+St?t>1pz$@;3F?uU|JdH&HQK?_D_|Iv{Mp0;{mU0cN@XAnhd|fB%^?6_1~& zn9aC#<3`BwlZM}Wd({I16s?Q5VwJ^`^{_yUki;>%Rnr7Ud58swo7LsR{o1@wpYCzi zgaBNEk6hDu2?wlr*i8;73J@S9W%KYjxVpM(mwhca0!t)39^4m97uKk(8ChATV-Y~e%Y+~g2|qV{Vwh)P zVR7T$y=347kXg%NPayVALqjC@h_eiZxaWu*`Tc-))-P{O-mjFuJI4sXvGM1R6eN(Z z49fbJmP4_w5w1fEHgT1Jis~XSj6_v{+dPvXp4%hyPUv$ILW&g z8dL`+&o0Q;O^}d+R#}6-gfb{K?{AG6@iB@--3$ZkY(~(5PmxWnA>ck_;o%2k0T1pL z^vug3`40vpkg5r?`Z{b;COV4kyu*!5&x1Xm=Q~%D6ke`$`9@{A$Un6=hN1vM_lvE^ zh*Mf>>WiEl`yuji@YF)(2k8UC4`K*56I5vG7_P>J&7uo_5-HI@Fu_C$?;9wk@X3?- zj0}exH;9KyX?-_oP3XC!zafzfn*)+N{9cF(QD)Zz?M(A)*Ytpq{6kB?LJeJAUE^t! zQ+oUe^-QYFg3$=zVc>ZKl1SLwUWfGY9vC*1mSvlVJTRL9@c_jOP;hTTZ@3jWWboosH+&q#G-6Wy5WzTg%y z=~?mjx1(~~tvE$wat5d?>_Z^Tw7!R;di+Naf1z=sZ++YUzrMbnfq`ZM0Zjy14n_xt z-L^_`BtxYZ*47TbzR!SJUCw6)H~B(uP1>Q+`oQEWgh2l5*L9_?eSLkY85y}yl)W%N zt1J8jay^nX0b3xQef;#PkhNua{wxhe=mMdO`TOg?CP{loM-@JZX`3cAL>>o9v(n)n z_yqD60`Dd!y}p&Hi?+9K6!Mpmw@ z1t%E!X(E9Hs^zNnW2}&T&d3s&1i_HXL$RoiN|1RVirKcov_RP{)1!EiyA^7`}JV$^rxG&-TV zbT6|M4m?xt8W>=MJO@Mv2qNr1^$}4p;6h=CEOyMxCP>_wnUc3DJ-~dN3AQn$RQnpi z-~~DWK#T|s6C*q^QT@L*kR@)xKBguk`LDm<#Kxx9I+-)Bsci93Uy!Nc=KIYWFSoK? z0a_0I(JWLXz^AuDO`)N^1QbpShVwuuAtV1A8!J1l48{4LDuRT(v_M_K<&2c(&kc(u zssF$@$i1P1E^h@x84e-5KH#i%F>CGE;Y)efr}n_Q``49vylrP(+ui3Cj;PnG_Ch@lOc-KxYGb|^2)yg|;q3Z06&cAwg$r;2 zZ-qchlJCp`A(Tx1#hiSim$bPP@grJ7Z z0SF54@<{DvoOn<9ap0YODFGi`(_mU_ZEY=LhBNoFD3p%iQXeRd0lPf7v`auN^ZW+V z6|6B*Lg@&Cc9sLRHxSOkgDcGT+z6R-VTarS!BJ%`seHKI1w%zQZ`_!8>bx;Z4>{J_ z+7KQou$DLehs4vA6y2Q5W&Sp&l3{eu=Q`1jzf%2#$Cu5o7X_KDY6G8x7KDn_c%7d= z0oBaMj~{>kw9*I0%A?2$CobBTkGMe`l#OyY0Fpy#4zn3REvKG-;ziV>w{N3+dM>*R zL3D(#uC6jHDy9M}=jCKg69sfFDJ2D| z3xHvMp{fJc_)ic-MpGbtm>4O3gXz+~yjD0GmJX?T2Bq3PouQnAHwbX)*qP3spi@xk zWyxO(F)woZbPmir0>A-B0oewED_&n1TVcgXV+a`@U=gZ&SYP-(At-f6M{De}WcooM z01^#M5BNq2l&^3>*crt$2cl7H#2W=KL|UItZtWtoUa#N0A$;n$z6D4IZyK4gg4vlS z7D27sN}#j>ZCjHAp&Epf^{>1K@c?FPe<$NW;sPu{Mm>=^NccO%>I}GVvqxY>SXzk6 z3)==`biz1n86-$ymge5QbADS(IUsV`*1Q738If@U>K0FZ3ENZ2=med2@7~pc#s8?N zor&8VY*f8lVa2839Sz`$VNx^y}v&KiZa+dJ3GFRgG)CKrOJTA9|Sc`VteK{c5V%o zMm9|@EQqH~lXUcigq@i#5<$k6A?~<9dV|UaQmvxjpLYOWFe!@^b&y_vCO$p$)E+5? z;K_qFw1beruGy|kM*x|ib@zjgaCE$#lrChjBf9D8I*nA!1qI^E-!l0jY)VS%!LmNL zxHvuV9tibaMmmbJy+aN*Hl+SQCZ89`mEgLFj0)@uc04df|NR9Un@sCscv!E@T`nXw zDhFC|>uL^?&)K|$WsL>wt*T0gaDl7{n&iuu`~Z!z;)8#%Z$qGlf<1@S+)%knD=4%B zyM@Fi$PokTxu~e<4Upi0SgavQAL17zSk=gQ4YVpu(ttt;S^n9xXLECN18X^Bu&1Gx zhZ+DGZW)ECZTi{=l_QW1T!v;~&gI+A`r_guj4H4d_g_5!e6(cIn0b0hG+i1HsxlV* z)(FD7yGttX&l|x=u)UkxF&JUCuo!&0#|v~0Mp1yK9nSIY3uJYJh$FUO@bbC@D@^V{ zqk@Bz6X=6wUz~>=>;!p;3J}tu#s~6L4k8%_jy44zW4RM#@l+GIKCIE+eCB~DOs|z~ z)<7is`Ca&8`|ib~vaBiTofJT%QiM!9I}32clt2{1%Y~GT=+nrCUrMsKAPZH6k`;tJ z-k?|Ok6}Lp_QEa)stN6c&CN|fZ}*CU67lFuA{xL6YH9*6Dq9*SJx4Taks*Z0!8?=$ z?dy>mKX5Pa#ZUbMFw|)TQufo+4Cd{?-bMp=T_vo_RUpMpc%(vo@vs#OGSWi#hfw$z z;m~t{==H!|{47K=C zK68bZ1&FXPXr;~L^C)2V&+4zY#cMNZ?uj~rrh5T&@FM>S{@DGUdmwLCz`gqHtu~d3 z*ItI|`rk8vPal{OJS`}qP<0~1$*@d7JYm?-%Khf(O|t_;Mg<7Cdmf5Y5M}7St3j&F zfVa(cghv;E9Vu*2Jwe9H;ay3Co(Iwtl#;!rd=GR_B@>=M7@Y`XwXR#(>=_;H1lbc* z)!L$cz)kHeO+G-h3xH_Qb$~nq`4@I?jTFw)(XP1REGSY?>w|Ux?-q%mvOqXU`wY01 zjFViOpg@p2xdfdOW*S7B2Qh28U~9p|E%T~)BM@F_7fpA_jUn1KXxwi^hQ@*BJUEi@ zhTe=4E#z#yjA|1nAK~?m%=R7z={F2E;8CRc$4Arx`Sj z-8kgPT?g}9^-mZRb`de0aq9M zcXbH;&6#f?&%wMdGJ6O!rMa4Xa)9cLFe@0|ez#N#aJ=otB6%%VaswMJ}fE98?o*G6- zFWK010lk@9UUmd-ep}|*g2B)g_+M*(hTI{7LjfUxmw_g5$=Eny!N7!B$O!4x=ygJ( zy#)vW6`(P+1q7KUKn?+^5fsMzCjht875$#7@V#3~1%_xPp%^9xJ7sQA-8d;7e!UTP zEXZehfIT3P7-lMUfJCj*ZA6;3tPDT{Bd$>9a9z0n7)pV?z}h3Vi9^v(Bq~uF28O3S zL3fY`NfLJPgHfLdh?k!EyWO;P+uJ^n43W+OU?QNxQsuG|jFmy*1m_sRm2Z&QLgfX~ z3*-*r+>pTFwQ5Qdv9huv^&UhRsJ+k)116sOM-TLn|NWr}ob;v6pr`>QIl#-~*jQxJ z9e5*bPkDO}k9dU10NL+`=m5L`rIal6%fA8U1IDyNbN^XtLLVYMmtH3L0dr7AzD}zFb5LaNsBO`?YfE0sL3KgS7aMrv!NcVtB zWbU>5vPf$MN(>16<1}G#A#x3knAli^z(V;BErn?y--rk?I9PG;d88StxZB!>z& zr43s34sdt|r<@xc{P0mpakQuBVb^Tiruil2e;vNoB1jzJNGl)y1*Fk7DDAtHc46%L zhpDNl2dGo<3i`O~K$e5`L~9} ze*d0oP7r_ysvQSNdjY2}H1YT$9RWblr}oo3Vj@wO`Hn!QAA%zP%^Nf`4T_5Fs<`}uD5U71_oxprh|qN zXqZS_YH7(0NK${^rz=P=>&m|?A>4Au)IEs%jBUcODP!l@S)SFiFhMM7tSs4q`{ zauow*RCez?S5PPev~6H~@G7XRD^)FyRba0`nt{L~!88B_u?~)AfCf5rhaf~gQUckr ze0Y?_(tqC>1-U^AFUS)I8Rskb8vwVUNP{Cn{QdpE?#f0hw3YOUJ$z6}nrfH_E(G3z zK4^kNa)aLky&sSRSm13LJ@^9GB=rdwl^C^>gI28A;r5mq>>W zAOSId)YfMHug3$q0O}<`XgKXaNv6098TW4$qx1lla#g%^!e^*d&floFZ)Rt60rvkI zc%6WO11Vtu_JYhwg2aKqC5FqIH3YuhQ5JplY;=>p-){V7b1Eu|KYw62v5*%IoL2dOak!*d=7fhvgU{>q7@KGa!c{QOXRLy;C3H;Zd%AO z4sezU(BhiCX-No{uf=<<#c=$>$LDE9kSmUsDBaeA1yV{yMa5=%dF8zoXyjT?5pk$K zC|kUvUz3eT%Wgk>c9;h%dL%J&f1CD%#Mh#{>v;IM-zMleR9Mgqfm1$mHF?U^mE?mV zM1TlY`qm!tLQpfD;NT;?W5&c(%acmq&EtS%C!fg|0G8f}l7${JtSIbFESzr+UESrM zEmB3-YB0fNX{6=sAI5-NL#+Van@i^A+R!0)EA!(lH8W09`^TjIXCW}jGh96h`n~_= zTbmqk3B)yhkYI9W9V%UKC4T(vCfv>irnk9cHz4;wS8cCzdG8ofcES6MgXUb6r}-T7 zyDX6lvH5HC6rq5=KnK16%;JHD*xHVleoJWKtSUTZ%c{5;>t#PzD?1=Aq!9hC|x6J3}HJ%DHwc3gDjWPn5}Iuy;;GKB(N?* zfGpG^?z)Q44Ay?;K;7Y1N9nU@#0v%p&^u$)Bx>dZ&#kXZ z#g0oGzCZ=$8_2oOI9MqM_1$7+Y*vuy8s0^r=DPMqDNBdKi3ZeV=QVapxmDms;Lr^) zUqMCu9Y|GY=KtIHo{07jl`*tyOn?#~#~GDDUW~g-+_B;ABGq{9CjD-uY!dNt73Nee zvI#o$A2W|!ALW%BWfkw`darBIeGWPGg6y?=!wohO^2$MB#b?6o`*UczM%Uf zO)vlLHk({HTjr9;-89ZOh$xAWu0*fL$FCY2e}PsyI~&`JSrvv}>0W&Uqkz9@t~wGz zk$IB@qMcxJjg`|Am_TX`aLm1YhYDdSq0evMM-K$1O}Qlg=TCECvzycQ#zct~RO9bJ z=SRB47%n)sM&r3MXaQtMCG(pDF>Jhj`&G{}yT7OE&yI7M5YesUXUz()^i(^cY)jWG zxxeZQN!s&tIUKeMJAA_pm6`85bJc0{1U?$u|GC^V_(NA+{p)>~Pgx?GN372?qUCEe zk%?{L)vI1C^s}7hvptI9`lru)A?wwbZsPi5!2CSMAW@L|0)MSsa;1bIL)VjgHi?@9 z&$kDs{#3}0w0nulno*t{#KgF)(e_K#TDJM7CoVe<$?9IvO^mZ1?X1u0aJ!XR9VZZ= zecL}Lq37l00tebV@uExZAJ=y7!?%it`Hv_Gx2`RUjcl}i*e1BBTjD(BdF;uNhRAgm z_p@CJbq1QY=@{pN1Yr}a2`w6_dL15XgP9uHh4&2kbLZ5)Kk?}tPMe*?pe19bSQ@d{ zTEz)d>_rYEX?tJE21L`?&Jr5jKkui>yYbFC=NEUcJ1|_It+ZACPLWP_KIAywbz|-2 zl_n(3B9=yxAtR2Hl%}*D$(Sy3nGPF~Rg%MOWbOmOYm!x%TP{3-4)B`GXQKoKUy8zWivP)R}o8 z##?1noU5xZsM9GpvDm)gt|r|9ZI>+%hTQ?}dsQ5?v|6Z%3X&=YE3!8s4t!)E9ct20 zr7t?+#(0n3SSc?jD(FL9W!;$-uk34EOz3xRHy?A46YUN(DXc#}+jy_&xKPydZAx29 z*~lKU_dnVG9DWVIr6PcSBiSf*Jfk625ZI>YnZXqzd)lk)91Q1R3n zR>z1Or6lRly@Pp-t6QQk$hGHYcKpPmN)`~B-rDy>Yxs?b2WhHy`rEMfNzE6TX=V2B z8Tm~p%m(VOFV0-uJX=5$&0BkT>!$kIr{+&BqUA5v3Kfu1i>aPZdK%s?T*lxj8LnWv z>q2;mi7X*2-Biu7G`DOTEvoj7?Ep^E;v%vmIA4$^e_;)KSHA&TiqE%w_ruq*YP4YDd%r>vZ3b3KpsTYZNje&b73a zLo*Gd$GPrYXlRzxp0imvi8o|2!kqY|o7iw2`#b)0{jAwgGVO2=g|Ib0iN^;wW)(aDe-ElU-7%@c-OU8XvF?@ zWX+IIyM9i9=p<^jn{tl{B`IgQ+5YjWDEWEDvR}tlSPjFRwX&~X^>$%!8y+?fsMuoLGf$|Xxv*)PW zvgjR_;4;SkE?l>M^6JilnN-<)yYpzXOaE-b{Df>e2|kS*=s*<3(Q|O4H1w;ZP4hFf z$j0d#B3m;`WO_d**P%277q!>=u1Rfe>S#JsbLNRlhcOHCo#CldoSyOe($!s&_r%?1 zTh{!hxlU7W!|dchkW;kt?esH~8+e|8w=TcCQ|hn>F0!A49fl0Xlm$NDbotF%9-OYL z?cq!R8Fe-lOVMlg%=?s~ztXX@RG%NpB*}PW?UC8M-RE#M<1A82`5oZ_*Fo7gUayk; zFt*g<-$dL%UG*3@{1!iX^G(t#9A6;Ho1r|Ixp_DaQ>=(F%!S+?hVVhRxZgj{xoF%L zCtBun)wR|n8+Wj3#tT2f#Zt!4j~b!tbE9J1%>NwT82-w3)tL71Bkx#h+-?BaB)@;N z40S(iOV4Q30O2;E?DZzJFz%Lb^5$mVlzt@ew>Q1(}_19_bT=(^!j>+k` zNu%BOS~;8a(=INT>CvYyGKSpFxF~SbkK!*on&PAfjYbddY7Bo!D=F_uXru;yr}+&sk(D$LKeL@o8xrM?=IQiLRs`p;e=oP zUijSw998<${;OBXs4uHgpOn|-zE|Z?t@ViNY7Z+M2IlD39`L913ITJ+!3o2PE~>(a zg_K{-47Bc|empTnEsVPH`RR60$@*%^QpaVFr>-br{k)NuSCQJHnPN@SvskjX7Gc@! zs9Ph=NyVtbL2?-u0?Si@o1I#@cUh%|6oYY!9u0TUAF9`znK#dkd(T`=*be%T_vi<` zY0|@QCGL*)p0H0+bQ=tOPqeI8hG6cRU;_5UH8f0#jQG?D7^&)^N4S(TXbEu*oH&2h zlG@B+c{tUR_wBP14od>Kx+vFSan7{qIw`T56E(bhN>7BI_~-14eT?|ID!23P%3IZj z>r^N+7`w4tVb5E3*ka-AC+=h^NaI#DiNEpYc?T2L$eJ^uXZykW=l z`m{BIVFfqw?So|%hs@;3DZ}vi{Ppu1Y<Aw3>FtVmWpP4 z;yoqR^j6xdJyw8RUI@Pk1u92=8*QEk$@gJ0}Hx>gjmekH5N&n)kF7KwXx)jHqi zt8u|+Go8quh_c)RP5#dB5Yy7!5$rKY- z-Nf?czsLG<9Q;?mX<^H**h!+~AaC{Js5r@-`|2|{j$2XpeEzQIA%s(&Z%X!ve0OfQ zX;?cxKp}Z^|2l5PS&hu<0%6Ywd#yWl_=_UG>u1Vf29>9{gn-0?RA-V0v-Wpw>CAgX zgF%BPlOR?w!>kQ_X70(@eQH#|vcr;rLT}xxV@8q~utvh{t=Jq)HB3@vSZobrX-9Hl5n> z@Z2xG6&CLjaV8cg)~n}mA~6#2w_nwbPPC(5dc^785dFz6vb=L?)v-@2qjg9BLv;== zzEI;&kjc#KuGH=vGrBJ`M1ce9)f6-lpS{|A1eh?}B56?OR% z(u9nQPb5Wt}}T=4;S>5N*#RQnaQ+AB`%<0WmfNNluzjg`W>&sR*=EjrZGv~|ks znC%B{8Y^U|iT=l!YX&;jR@HmIPk9*|Z1yciRPqlQ%k<{vbQUXm%euY7pU==<;{wP! z7cPCyHP_4FAVU;mp-P6c)qoTLNv47g{dUK&}%mlLIk1cDH z4V}}7=PWx@S(`f~ql~A1CuvaH_MwBvBX7+aFC+e@ zJ>Bd?%Uz07daCf9qb%kN4y_L$93Yp*#yosMhH@&zVJ;+A;KQ3lxrmQ{Ly0@0n(|rm z^!iMvI3~;=mPek(ca4UM+w^5roVdpG3`-Y-8+kk!aY6@0)x2e7zhoffNhg257vKMw z|7Hao^|AuE*y%QVl3rb zx=e&UzK@Z}#GZ)ObxD1Q|71|ct}##@h6!~?6Mhz8={yzMl?v27g)>ziZJ1zse+esp z7w3LJozJ2aT3+YC`FPitO@7Hn)eK$nBheFB7&&Ydz4wV4R=G|Mo0YNRLJrr8yctnu z(KuWH?$v)`7}de*-M;v$_gK4YSiW_9cl)9Jgz1~7Xo(&kjU+CON8|kIQG}aJesW(; z_n%H+f@M9$(KyQm?bzoPHtG#lGibuilJl=I~C z8cQ3DHIp@?5sOMRMsUXVzcJgj*ZEOuFqURSjlVM4ka@5PpKR=Xd~i$oo6~$^9ll2L zYd*&)tv|acoqVLg=uM1Rm{ps9-v``lE6vyh%9g_d(gP}NsKHxqh+!It;hX^-zRpez zdf1JR@Z}@v*^Akq__A?)@F|xLP^c2mm zs#$EDGWeWOrNB8@jJ4yaWyUV4H3!NW-eAhhFLlZd5L)+goe4i}h0}il^r)vfE-s&5fotX{WI3PUD%X`(=ru$%N&;&c<`4g{t@zjlOMb&n7YSsI zTh{sEysd6dUMsgwr!-h{5?Ay+gj>g2L}Le!W~gS<>N z<8S#+|>eyhSuV5E0c`Z`7TtUi?SGQUeM*Mf&x;ib~> z-}7rkXfje<{7~BVwg{TA6F+~w_u^ss8e zFfk~l-??GJ4{}ryG8rJsk{iOyM_8^!-*lsQyt8VZUBAxPv)l1epeIT5ob2+5eE7$mIZaQZTR18z z{%vpJv0`czxx*=sQ(k_&`77K_`BLPL7A0;Cnm^-Ax5B$czdRQlLW!%!X3DMqJMFK5KbOPTLh^+VZ$H?T`oH&uP zTJEodxb|Dpiwjz(O_C8qO%qQv)#wlb>g(7YUAwPf+7oM_s)A76MGhX)v=y9 zWUXMkt-4Egs#W4>xlQ$zF_WVsD!FTq|I+ejEmK{dcJ}?6WY&?s0{(NVjk2c1HZ%zPV8o`M{Z10{4MK@igWxXRSu`e1MvNZ z4oX2RsPHx%P9ge3ROI~e*Q_M4%5WlxF-`J0yDJe3I)Qy;hHJH)7K0OqVYpfo4CyC} z;;jp~A?X->{*)~DN=I2 zEhYYnt^V0dB$0)OkM-GW9|}+?;@11A_uO?(sZdq(T}rx+Ll*gVHY&xD*qag+EQVQF zdCD*5n|ei&!=rC4hQGpgnP1|&IAJCm`zX_*V$yqNpa1R~N#1uQZbG=X513lUtW-3H z`3#q4>6k7!qE31+7#1Y)txl?@Ynab$Hr~r18`YE$G@Ovx?xQ8u96Se_ev+C%3|*tT zPG+sO-Lo!?!e-r=dT!8%=e1X70+cBa9@wp9wCoI@%6{Hv9&!?O_EPMzU)wlZ)M^KA zZYYjx>oB&w!AUSM9HspiP59e~cFSPJlJjbD5j^bz2;}LdDq|`VEXN2_kAU$wBhq5n zL;R?v`cn#^rrypNld|hN4+G^Aho&1_wkwJENU-#?*uK?s6}DRfy^V@GFC)x+W=;-1 z9Erh{?=rqD3vT$bi=$0dBg>$4A|_)ZrN?jWpjhA2Inm$x*(ewvbxS!h{CiYG`kxR8 z*-$90L4T_=rIoN5#=5%ad47WS0*~J#7 z@6@-*xH85l=T43Cc-0*CEm|ib0(-2wS!S)6pY^yqL7!AR*apoiK&sOoc7J9;Bx-G9 zZr;jxjhpl2_JKIy_V`8+Eor8f#tVxqu^5b$B>Gr~-Hak1>0SDs@$FunOchB(PR5)w zXPab$nf?4By89U)U_z+MIduxSG;)2n(8p}$FAHvn3f69DHW(WUyp7o7_C)nWnURaN zN+6@4Q_{I*Lkfh>s+5YHfq|t-QM+Gdg6m2GM71&c>~WK()aHDlJ74SECkyDZm?^)s zFRD&u-C)Ni_v%<(*XEkxX}DMPRg`%R?UUQle22eH$Et(Bn!+(eGV98hs1jNv=(eL! zF1igl6~P#v`S#_9b(1yZ3&Uc~hg+mXxx~Z?Rb@3WhWc z_-K3&V8PE(2tRy zN@}i?Pi^lln9CG5NUhL7U(Br@x`jMg@hBJ**H?+{&SKsHb__c7pbEk$c z^iMC&@@jO^!rS5WeI0)*mEo$uY|VCN^pZ7+aIiLGH>;Y2hEkZg_TOtW_fS*lDf_9c zN$ZW9>c*bIrIT>rf?&+l0X;f$yA40vad%18POsl|KR5o%j%#{jm8{{~!M;wBt<%i@ zc|Nhht2(O9)^_q=?hBf!SXEn$OO-qtK4nPmOU`8O7eDY3g~G(?)2B^Ow!<_cH=Mqr z*`iH>Dx0PuG-xCc|KTekH1RY9tU3g4SgwAS6D2y@7V_a} zt4rbn4<%!#D30-03xwiS#Jj3$DE^|D9-M7B=P$cn)5TZxAyzA@+EYE=<|0@w<%<*P|T=ilk3Z@gKF{fz8%L^ulGrD^=dSa-^6bQUqqo;wR7)1 z(Q(mYM9rxaocY&(QgzCBNE%8zv7YA;Z9VqNq%JDaeW2NNh!#hCdxhVvt(d>9Is_v@ zTW1u%8Q5{YmM0rC^&r7tRq?n;*+oWOPH#G=UsfqTO0iat6&MsE^yw=-`q&QWN^(C^ zqM|P1Zu`?>-y^P^jb|5)*TR1P8e?zhj<(D25+Hd;p0^ksQ!XZk?~mns_}$dWj6ZV! z2E6yOh%n4I76OkpDsND2?Xm$DC%OJ1rolm!DaBZ@PoI2MDv|Q_0;yPQ?5n&0&6bll zn_mn@e;m77$NrcBO}$fyAzew+aeIoFFeFaKEo?Co+D(@R`ozK>a^`ac(L~CBpwXer z{}Vv%7~sU&A{ju^BG+MfF15Al;M6rT=+z;_CDDjY+Z}}p($yd`L!Un1njm^!s+Lom z>nT-d6fd!0+*_gb52XRdOW}HUWYhv~Ycz>B&xB$EqcIi1!pfgmuzCIQf2}kLUOaiH z6T(;oU}X^kJaydR^pCT4MCVLgB2e1J=!H+tI&pP2XwuX8{yu&KKjkoMl2Z@jSWyq+ z_Mwmgo3LOU|F>wS}nwI5z-1FD2qI!Uz_g-8ngyVYCzJ@%O$%ux@7LiF3X+p7BXS>F=v5g`*dL7V15$TwlYQEPL!X*2sFbKj*%&iMg>!jO-Z zc}(ReJwAU#E?y?grp-4%sK-m{k&K{`gPRG?vYeqq_ev5s(ubKkjuLEY{pfXe;cqzO z>Z~ePs-^~+*5JTS4i75G+HS>6sSdo@aM%I#pTT|r16?hf!i(18Q}tO)kzb@5Ixh0R z4Q)sFex?>Y@YkU;d%sHfI(1BT@%w$TD5TZA7A4y7L%Jh#0kxHLjQ!Dd+^(na`e&U! zzqWXVF9ElE9qkvW(C;6RVKc{6Y1DS&Q4S=iz#9$sltLQZ?^EoMFcoiJ_}CGZg^VCh zk?S_-T4-7+n(R9dv+@$=EO4ZlxN^ysg03@57wOM$zICBz={&`AV@j>T%1V&Y(H)h? z{g|^!bYuLn%2`1NgZGKhcS2CQh0=_1%bf*#k$|7&Ot=?x@q=}m#&F64=1Q`IX83Ux zhF|+)meDQhijlqHRDuU?d=+m+vvpc9ef)v}#CAXbp1=Gq8(QB;FMloMoW|e6_wRSg zm|OB%GkQKH5Z?=l`B9tG9K4{-tzz%|KA!8pJF+uRpXE@ccG+j2QRzr*QD zQz&vFc!ww|k)a)J*m|?ii-nN;`^KwgbMb{*<0tuWF{9j#%%ogkU&eP z&V_xok7JWbLW|+N69%lc2=^!=ZRDxD3PeMuD|_W>utY# zhU6VngqswF;yWn=^rw^%R4+5Mcv3@(ll$T#TnL$Q7jqk9+GzEHxg zLW{NZXZ}PA^Qj$32OCT-r2z}z!^ty(Z}h-N3%)IHt;4Cq;P?pY?Z0;9uFcMVggAU) zrf%`)-bhUHz2&BS86$z!a`^{8pzcTm$HdW1BeM)Gl#fZveQY=QBR3pqyw70)M_RTwTYp7Zx`6K9Hs=4m`6`mDD^JSq{Qtj1OGjQ~6)u`kkBUso3pS_^NRSJ50~bi#;omsrwR>X+f6DN9JdC%2sb;%+ zY*!Ka0)y)z1q%s_)F;b_(&SZ*l#+T@IFs^ZVgtseR$<+M%{f=NOSQ1 zMwdSP3sxzd+d;Gee|;w=kjG7N;LY2t-(T!~igw$`{TVS!a@Fo~fps!P6N+ir#XH{c9B+QX-r-wFd0U&JzYRb3Enc*& zrrmF-yY|3x1sRFx>kDt2hEH{YEjV?-;OBmJFlXHfyPf`V%Fm2)KVy0i-HBU>AKO{8 zlKde#ci}@tRMs5h3z6**26mN;Qp$M=U+$8(EJsh4i?N{D-eM2xLE>#kgBSdm>jNI- zlSD8ES0pE(eCBM57FV?>7v*;~LNODtB=_P3{|ftNFk8H^{xYz2g{4$1WvRegg@Zv?{FJS3=|O543!6 z2YxmScM;Rvs3tf`)VK6q_J2@JBIZSl?ybu;2+t>xEsC12HjB~E+x9qUJzmE~=(&CF zUCEWyX{uSq2>HkcD|z&9J?ztm(u+1M18jcC31qQHjMeBP$;pI}`y_dXV<$A&c+{De zw)jHbj-weJQI0bP8dOD|>y158o+q<|opqA8$p{*V#ev8pIL6sTlBl-1>SQHHHASSe zL7D0QsQXE^Wu9%&(*Khr`{PSx)g2_GOw17_es~PMlnxM-Vlf#73oZT#Mh@L z&_nj~$>vYC#L_cqKEKtheqBZYh8t0#T2B5!FSSaRWLzYkP59MzT;=-AHq$sWn&@PL z*%`5B+kG^FNhzwR^3AZK?)Yq7WwR!ZpGbk~w|ee_n7?T)zOEb#`o#= zy-bq)>oG$1xH{<{wO-B4R`+>cP&(3J{I0}{^eCJU#%aqL#=guNKIYIdW-qwZI5?-2 zB5;w(Y_hy78b3tSNy8VIz4Gc|%=Q~(vSDX7S7cj5D3Nj?q+Hk@_qs9)BWBY4pBoJd z7e{O~zQI6-y-`D274K76#85CSDqE7+6Y7Ry-vtyCS=yXs3x+%6{=Rv^;Q&zxwceJna*a6~h@>9F;2Ae=-~LuSvqd2RC9< zoD#5ZIOjwHsZO#|%8QEKBj&=bHxUeW*2`KA6C6?^wGpNi3pD(#9q&*t%W$|FFpKA5 z`(7_WeffT-KC;Ofzm=Lpqe?bLEO^)zX7*A{@7Q-yTc}(Wn(KTd(G&*JF;`~}W^wPF zHF5`AR%V z$|R?VA0Ue?Y;0d}?!Ic0#D%yN*XYF-YRK8IieWv8pXfv%i+37fYDiuch{?XC;}>gT z7aYq^>c=Z}rT>xsX|X5^C*eqOIdAIxshG*~pxp%F&P|>355?AZ1IN!_e*H0ZyMPG- znq600WTc`P%!pb?v3HZdB6zXxLBf~&nVnq8lqC^%8;x7{=ZTbD_hpb$^mm_8bRg%@H1SS@R29udIzSH!2+NeYNbD`ak`W2COg#n9~}H=nPMxv+LRX+(!cbl98z zFQ=W}gY)YbSW9}EcmxfPIaWKeh^qObBAeI@U+PG&&@qMP>H^086K})@D}uQ(`v+Og zD$iSoDH*lhiJN>x)pNr#Rjr<-F!p~hSXsF?ucp5{2r4A}7Z*#QC{ZmBIlqMTk3haS|z}XvA z0<7ejs+XzvES4>a1EZLu*-mn3$OKYER-7?B{}7%8IU|6XM#)a4h+Hdy$Awyh@qo!` z&z}wD)zMrx@`yRnpV*0VUD%Mf&f0Ds(`%h8uqZXIyopoF@>mpIkXb5H8}&T028Hc>Pz5?(s!`_KHj14A=wxg{LYr4nM28 z)CrBzlWehQlJBO;mO2?W0xOytM9+k%pbR>3B<6n+-=5L3dZuNkB4^;85`Pf%L}xXE z78R7!Oth%>D40Q%<7`_!y@Q*CU`s8BBX#lbL(3M)$LlF7Yno$}HDS2Dgo1u@#@Qv+ zaj)j2BByaK?FPb$aW2E67o4czP29rp!-nL>Nbs*BEBJCk)2c>qoZ9Zx^7V#q(fpws zHYAF5<_TY_=vuhDa`GPfAv5)LiVXE!H_C)) zA`b!qq}<`S1|4d{s5+$kPM+RY)fHnU=ua-XbGy-kU&Hu+YQD^H^9#!;{r>F`T8t*Y zv`$>9klmXBOvE%kP0}lxzc++B9gHM6mGW?XQaL_Cm*AhbUqAT0{}5;>(1e9n=K%|z zVAJYrz?eT!O^JQ{A;s+?T0SR0XMOcz^WAr04eC^)EQ4u2nY@5d?7$myE>WXfb|~XR ze5tUmkEs+$_bjvJhL-KDZNg>d6+`G|KH+HpvPg}js90qRjTbZWZ(G>CSZ=Pe8LUnn zjdzER0g2SKvsoN5%H>ntEBw%nhHmM$(*JAkI=h6`HFU)zkO09bAVO$Kh$1ynFw~p(-nH&OxZm#Q zz1P}%X3fl=J2=99VK_zWjfnx2(LUJ%*= z_|Mc{erg;l_3+H`C|lndhoVTh(x>={)V{_N-gDN_O0eoy-I3#>`l+9?>5DMAx6}8Y zVeC`u4Eb+Oeg-2HI99q)a8-C;NH^)|{RGrw$o zLu<)}{fph&+;$VQJFk>O?g(lqU-Zio^Vx1YR1;lc@Ypb|G!i~175h`UuKSkCra@9o_Vjp^scWUNv197u(1lL%c=xqT+#zH+vN*i_uqIbAMN;l5U{v; zdLSu8c5gc)PmZ3Ny%kv4fBPCd`G^WHF%f%fuhhIc)8vVVu=1fzmAeaiscAm}3z{)^ z1lmqkX3XHZsey)%Ur`OQpuTI$3v{ay&#MbFkt(Ywee|v|Zo<`va8P4qH+t9xZw$t9 zw+siKnrhp0^SgmN_~Xr_t41%8JgRz?d`4FXphnNy%mP#4O*t5P`1`$=Bb_tl7+e16 zo)?7^#JRRd5~&wmOx#8<`z1^~bud}-W;9iZzr)+MFiyj8Y$@e&up*_g)B^W3eK|s5 zf##&rur5{^#xNddf6#69Rz;0YKlh!pJaizh+F7TihK8M>XXCD;yQu{ z$B&C&)g^{2-L02@C=hCUCRAH_#yW%HTfK=IoE}jG&OuKY zo=M39ZQ+$@9C^|k$+*CuF!CiD2~@OXSU>%om`9f%PI7;J?kF9V!QL#*{CSjDgvx-;mw--8Far}i;_4IaaI<-{vtpzZ9gVvy zljsqnT<-nJ3birecZn*_;XPpL22`;<{*n7JY2%8kFPm0!>#jO8MUtc;y&g_-_9gjs zFc=5E2$h+RnFNGDHIs~q{AsdT$V)K@+9!@6)wUL>SIvoV*;m-7vcze71XyAn zI@Kj7KMevVLZYin^wO;0k+`{9kbL9%dNSIIM?kt7Ois5tbJ0l&H-YhBGJ-V03XF3F zrbuvh)W$F+7$Oe7{W`9+A5k2mo3mSlY>qdXIm65X?Q-_<`8|83oq7Y;(SIn|&8{5@ zOJf+JDx6=ws(Kdfx?jH2gtRNl!wNey72jx5kHnfsMyL8h6rbmmuQVze?_z0OK3czGz4N^FaOnS9Z~L@WZIgWTZRGJ*O^AkFyhQ0sOu;pcGaO zp=N++{mi8w9%!!~OL@+9yM(gHJz7`1O@a|BuLe+ppika2Yxp3a-4WY!fx1)dNIxml ztDPpU2yoAX^}9l(c3o)<^x?ws?Y|h;In!NWGM8*K_}0rMlKxg0^4@o^SDi9J9q{I& zxnIt_8H#&rH)fFx&SkttcfuO1R`zmG2W%zoB|j$F|5VTo73aknCyx%WK)7pUtuwl| z!=Yh&&#~e6o>j;wq*k`%<5S}M>%4IrOy&2wLu`F>qkphde%ITTa4}RJdA4|duf>B9q81sdYeMr zD*?Sf(*N-IJ3AvkB^S9d_`N(q!{=uyVCC#58#hd+>;L15w< zg+tS+aqI~1FJc!o%NU)<|FXt(PLLT@gD}?a%W3)S=tqN?2phl@kZ%cR1J6p)w6|N3 z=G(=f^NU40HOd7nQ~6x%Qw5D#ynlSHTL`WZExdl4ME*^HHpO`Y?<9)|O<4%&bYw2) zvWdPw8aJ;s{Nf(PBG^jRLYSA+_URq3WLKRWCuZP!6wH`N+P~;@PHS*MP>;f{)X0y* zETP{p^f!K73|kB%_P8; zynn_;I`lCvVR#VjMn)pti+O?U=77(bQ`-SIvhcMH_bqg8ajob z^4Ukv$+W@-{^zbO=S;NdwYC(c^`q#~0y}!lEyCDKJBSPP0@Zn^5Ofz;J3@7zn#&8* zSEsteqdBgav^UEsAFtqp?)RVTe@ko)Sa*mBPA0Z;1`RZ?HiI&YeO9xuDBA%#bp`q% zy@dkzO7H|O;3hjR9EQMUlD+Ow!!Er-8Z;dt^qxUc@D zaZCTuZKMWOoy!UNY=k*UvoD4FacY+uKr7)W9^H&2ms|JB(lOd{a67@ibgK0{n667S z=m$&pOpq^0n6WW(=kZq&Bc4aVIFTqq&xY%6y|Cx8KoE>9U!D9M3& zow_xWK#HaiV+kPJoF0GFTC-+OAfe+GRQ)O)swKJ_fgTu+^smbFv!;boCT0=APtL@F z03Q5`xz`VoMYyT!reMffjs3q3z${A zg}OwJ%mkx*OC682uaG!J`vcUQS(uXyD;wA6ceB^Dqb3ibxR0G@7&s3ru1jKLoYo(vIWGP}=K~6U0KneM*9W zJ26zS065Ls@A%RKh~HqLZ~bhziaV<+rm+6IZhyEb@PTO4hUuWYn9EAW76HUhKayw8 zaz^B8SOK%b9y`tWfxzE#xj(f?g1jSR)x~)3E+ELz5g-s3clIgPo3tI-11%5k0lCr6 zb6S>mn_Flt7epIB5hBFYi>IC-yS{>*n>WeQJ;xz&zV$uBvd|MnUvX2*j#=&ou~XJw zQC5RI-Ot6w1#-GHNSt;dw#}u%oRE`G4lK*oiaSl&K$4=?a@W9t@xLj`!s}Ze?+9+x@iGV%#dFaW6u0=~{_p5=E`zFPen?E$-EE5Smi&AaF|t`GxuLrv7@hZd;}t0WrM%L|4Ok8<<~C_e@Bv zLGs{!8*|f^fHt6@Vp6eXOKhwq?FU+ay$K3l)rK5u**O}DwIxPj@i5MK(4-hM4PCWX zIsGlu_a0#Gq%uIT4de4Y_tm5P$o2b6vD7vAVI>IC^`LR6}33Y+8H_x`nX+c1e63d)~s2?3f4G4 z`iEeud|BQe<_FyT%K=El%D{CLPJy7)m8Dqlw49hXt9`Y#jxaS^rJ*We@pLM!1rGq@vmLZsW)_u^ZyK*;)zqbRnR(Xcq6 z0|JDY@^=H0Mn-K#0D#dmf{Fluu^<0r0H9O}AP4|FNC6!F|AqepmGBgbg9lAt<^8F` Q*EQg#xs6$asn_HG0yLQWMgRZ+ diff --git a/docs/dev/compilation.md b/docs/dev/compilation.md deleted file mode 100644 index 67805bfe6..000000000 --- a/docs/dev/compilation.md +++ /dev/null @@ -1,142 +0,0 @@ -# Compilation - -Compilation begins with tracing to get an easy-to-manipulate representation of the function. We call this representation a `Computation Graph`, which is basically a Directed Acyclic Graph (DAG) containing nodes representing computations done in the function. Working with graphs is good because they have been studied extensively and there are a lot of available algorithms to manipulate them. Internally, we use [networkx](https://networkx.org), which is an excellent graph library for Python. - -The next step in compilation is transforming the computation graph. There are many transformations we perform, and they will be discussed in their own sections. The result of transformations is just another computation graph. - -After transformations are applied, we need to determine the bounds (i.e., the minimum and the maximum values) of each intermediate node. This is required because FHE currently allows limited precision for computations. Bound measurement helps determine the required precision for the function. - -The final step is to transform the computation graph to equivalent `MLIR` code. Once the MLIR is generated, our Compiler backend compiles it down to native binaries. - -## Tracing - -We start with a Python function `f`, such as this one: - -``` -def f(x): - return (2 * x) + 3 -``` - -The goal of tracing is to create the following computation graph without requiring any change from the user. - -![](../\_static/compilation-pipeline/two\_x\_plus\_three.png) - -(Note that the edge labels are for non-commutative operations. To give an example, a subtraction node represents `(predecessor with edge label 0) - (predecessor with edge label 1)`) - -To do this, we make use of `Tracer`s, which are objects that record the operation performed during their creation. We create a `Tracer` for each argument of the function and call the function with those tracers. `Tracer`s make use of the operator overloading feature of Python to achieve their goal: - -``` -def f(x, y): - return x + 2 * y - -x = Tracer(computation=Input("x")) -y = Tracer(computation=Input("y")) - -resulting_tracer = f(x, y) -``` - -`2 * y` will be performed first, and `*` is overloaded for `Tracer` to return another tracer: `Tracer(computation=Multiply(Constant(2), self.computation))`, which is equal to `Tracer(computation=Multiply(Constant(2), Input("y")))` - -`x + (2 * y)` will be performed next, and `+` is overloaded for `Tracer` to return another tracer: `Tracer(computation=Add(self.computation, (2 * y).computation))`, which is equal to `Tracer(computation=Add(Input("x"), Multiply(Constant(2), Input("y")))` - -In the end, we will have output tracers that can be used to create the computation graph. The implementation is a bit more complex than this, but the idea is the same. - -Tracing is also responsible for indicating whether the values in the node would be encrypted or not. The rule for that is: if a node has an encrypted predecessor, it is encrypted as well. - -## Topological transforms - -The goal of topological transforms is to make more functions compilable. - -With the current version of **Concrete**, floating-point inputs and floating-point outputs are not supported. However, if the floating-point operations are intermediate operations, they can sometimes be fused into a single table lookup from integer to integer, thanks to some specific transforms. - -Let's take a closer look at the transforms we can currently perform. - -### Fusing. - -We have allocated a whole new chapter to explaining fusing. You can find it after this chapter. - -## Bounds measurement - -Given a computation graph, the goal of the bounds measurement step is to assign the minimal data type to each node in the graph. - -If we have an encrypted input that is always between `0` and `10`, we should assign the type `EncryptedScalar` to the node of this input as `EncryptedScalar`. This is the minimal encrypted integer that supports all values between `0` and `10`. - -If there were negative values in the range, we could have used `intX` instead of `uintX`. - -Bounds measurement is necessary because FHE supports limited precision, and we don't want unexpected behaviour while evaluating the compiled functions. - -Let's take a closer look at how we perform bounds measurement. - -### Inputset evaluation. - -This is a simple approach that requires an inputset to be provided by the user. - -The inputset is not to be confused with the dataset, which is classical in ML, as it doesn't require labels. Rather, it is a set of values which are typical inputs of the function. - -The idea is to evaluate each input in the inputset and record the result of each operation in the computation graph. Then we compare the evaluation results with the current minimum/maximum values of each node and update the minimum/maximum accordingly. After the entire inputset is evaluated, we assign a data type to each node using the minimum and maximum values it contains. - -Here is an example, given this computation graph where `x` is encrypted: - -![](../\_static/compilation-pipeline/two\_x\_plus\_three.png) - -and this inputset: - -``` -[2, 3, 1] -``` - -Evaluation result of `2`: - -* `x`: 2 -* `2`: 2 -* `*`: 4 -* `3`: 3 -* `+`: 7 - -New bounds: - -* `x`: \[**2**, **2**] -* `2`: \[**2**, **2**] -* `*`: \[**4**, **4**] -* `3`: \[**3**, **3**] -* `+`: \[**7**, **7**] - -Evaluation result of `3`: - -* `x`: 3 -* `2`: 2 -* `*`: 6 -* `3`: 3 -* `+`: 9 - -New bounds: - -* `x`: \[2, **3**] -* `2`: \[2, 2] -* `*`: \[4, **6**] -* `3`: \[3, 3] -* `+`: \[7, **9**] - -Evaluation result of `1`: - -* `x`: 1 -* `2`: 2 -* `*`: 2 -* `3`: 3 -* `+`: 5 - -New bounds: - -* `x`: \[**1**, 3] -* `2`: \[2, 2] -* `*`: \[**2**, 6] -* `3`: \[3, 3] -* `+`: \[**5**, 9] - -Assigned data types: - -* `x`: EncryptedScalar<**uint2**> -* `2`: ClearScalar<**uint2**> -* `*`: EncryptedScalar<**uint3**> -* `3`: ClearScalar<**uint2**> -* `+`: EncryptedScalar<**uint4**> diff --git a/docs/dev/compilation/README.md b/docs/dev/compilation/README.md index f291f9830..0df42b633 100644 --- a/docs/dev/compilation/README.md +++ b/docs/dev/compilation/README.md @@ -1,6 +1,168 @@ -#TODO: +# Compilation -- theory and concepts -- full compilation workflow diagram +There are two main entrypoints to the Concrete Compiler. The first is to use the Concrete Python frontend. The second is to use the Compiler directly, which takes [MLIR](https://mlir.llvm.org/) as input. The first one is more high level and uses the second under the hood. --> the goal here is to help a contributor understand interactions between components in Concrete project. It should highlight also the input/output format of each components +Compilation begins in the **frontend** with tracing to get an easy-to-manipulate representation of the function. We call this representation a `Computation Graph`, which is basically a Directed Acyclic Graph (DAG) containing nodes representing computations done in the function. Working with graphs is good because they have been studied extensively and there are a lot of available algorithms to manipulate them. Internally, we use [networkx](https://networkx.org), which is an excellent graph library for Python. + +The next step in compilation is transforming the computation graph. There are many transformations we perform, and they will be discussed in their own sections. The result of transformations is just another computation graph. + +After transformations are applied, we need to determine the bounds (i.e., the minimum and the maximum values) of each intermediate node. This is required because FHE currently allows limited precision for computations. Bound measurement helps determine the required precision for the function. + +The **frontend** is almost done at this stage and only need to transform the computation graph to equivalent `MLIR` code. Once the `MLIR` is generated, our Compiler **backend** takes over. Any other **frontend** wishing to use the Compiler needs to plugin at this stage. + +The Compiler takes `MLIR` code that makes use of both the `FHE` and `FHELinalg` [dialects](https://mlir.llvm.org/docs/LangRef/#dialects) for scalar and tensor operations respectively. + +Compilation then ends with a series of [passes](#mlir-compiler-passes) that generates a native binary which contains executable code. Crypto parameters are generated along the way as well. + +## Tracing + +We start with a Python function `f`, such as this one: + +``` +def f(x): + return (2 * x) + 3 +``` + +The goal of tracing is to create the following computation graph without requiring any change from the user. + +![](../../_static/compilation-pipeline/two_x_plus_three.png) + +(Note that the edge labels are for non-commutative operations. To give an example, a subtraction node represents `(predecessor with edge label 0) - (predecessor with edge label 1)`) + +To do this, we make use of `Tracer`s, which are objects that record the operation performed during their creation. We create a `Tracer` for each argument of the function and call the function with those tracers. `Tracer`s make use of the operator overloading feature of Python to achieve their goal: + +``` +def f(x, y): + return x + 2 * y + +x = Tracer(computation=Input("x")) +y = Tracer(computation=Input("y")) + +resulting_tracer = f(x, y) +``` + +`2 * y` will be performed first, and `*` is overloaded for `Tracer` to return another tracer: `Tracer(computation=Multiply(Constant(2), self.computation))`, which is equal to `Tracer(computation=Multiply(Constant(2), Input("y")))` + +`x + (2 * y)` will be performed next, and `+` is overloaded for `Tracer` to return another tracer: `Tracer(computation=Add(self.computation, (2 * y).computation))`, which is equal to `Tracer(computation=Add(Input("x"), Multiply(Constant(2), Input("y")))` + +In the end, we will have output tracers that can be used to create the computation graph. The implementation is a bit more complex than this, but the idea is the same. + +Tracing is also responsible for indicating whether the values in the node would be encrypted or not. The rule for that is: if a node has an encrypted predecessor, it is encrypted as well. + +## Topological transforms + +The goal of topological transforms is to make more functions compilable. + +With the current version of **Concrete**, floating-point inputs and floating-point outputs are not supported. However, if the floating-point operations are intermediate operations, they can sometimes be fused into a single table lookup from integer to integer, thanks to some specific transforms. + +Let's take a closer look at the transforms we can currently perform. + +### Fusing. + +We have allocated a whole new chapter to explaining fusing. You can find it after this chapter. + +## Bounds measurement + +Given a computation graph, the goal of the bounds measurement step is to assign the minimal data type to each node in the graph. + +If we have an encrypted input that is always between `0` and `10`, we should assign the type `EncryptedScalar` to the node of this input as `EncryptedScalar`. This is the minimal encrypted integer that supports all values between `0` and `10`. + +If there were negative values in the range, we could have used `intX` instead of `uintX`. + +Bounds measurement is necessary because FHE supports limited precision, and we don't want unexpected behaviour while evaluating the compiled functions. + +Let's take a closer look at how we perform bounds measurement. + +### Inputset evaluation. + +This is a simple approach that requires an inputset to be provided by the user. + +The inputset is not to be confused with the dataset, which is classical in ML, as it doesn't require labels. Rather, it is a set of values which are typical inputs of the function. + +The idea is to evaluate each input in the inputset and record the result of each operation in the computation graph. Then we compare the evaluation results with the current minimum/maximum values of each node and update the minimum/maximum accordingly. After the entire inputset is evaluated, we assign a data type to each node using the minimum and maximum values it contains. + +Here is an example, given this computation graph where `x` is encrypted: + +![](../../_static/compilation-pipeline/two_x_plus_three.png) + +and this inputset: + +``` +[2, 3, 1] +``` + +Evaluation result of `2`: + +* `x`: 2 +* `2`: 2 +* `*`: 4 +* `3`: 3 +* `+`: 7 + +New bounds: + +* `x`: \[**2**, **2**] +* `2`: \[**2**, **2**] +* `*`: \[**4**, **4**] +* `3`: \[**3**, **3**] +* `+`: \[**7**, **7**] + +Evaluation result of `3`: + +* `x`: 3 +* `2`: 2 +* `*`: 6 +* `3`: 3 +* `+`: 9 + +New bounds: + +* `x`: \[2, **3**] +* `2`: \[2, 2] +* `*`: \[4, **6**] +* `3`: \[3, 3] +* `+`: \[7, **9**] + +Evaluation result of `1`: + +* `x`: 1 +* `2`: 2 +* `*`: 2 +* `3`: 3 +* `+`: 5 + +New bounds: + +* `x`: \[**1**, 3] +* `2`: \[2, 2] +* `*`: \[**2**, 6] +* `3`: \[3, 3] +* `+`: \[**5**, 9] + +Assigned data types: + +* `x`: EncryptedScalar<**uint2**> +* `2`: ClearScalar<**uint2**> +* `*`: EncryptedScalar<**uint3**> +* `3`: ClearScalar<**uint2**> +* `+`: EncryptedScalar<**uint4**> + +## MLIR Compiler Passes + +We describe below some of the main passes in the compilation pipeline. + +### FHE to TFHE + +This pass converts high level operations which are not crypto specific get lowered to operations from the TFHE scheme. Ciphertexts get introduced in the code as well. TFHE operations and ciphertexts require some parameters which need to be chosen, and the [TFHE Parameterization](#tfhe-parameterization) pass does just that. + +### TFHE Parameterization + +TFHE Parameterization takes care of introducing the chosen parameters in the Intermediate Representation (IR). After this pass, you should be able to see ciphertexts' dimensions, as well as other parameters in the IR. + +### TFHE to Concrete + +This pass lowers TFHE operations to low level operations that are closer to the backend implementations, working on tensors and memory buffers (after a bufferization pass). + +### Concrete to LLVM + +At the end, we lower everything to LLVM-IR in order to generate the final binary.