From 63b8fe11e136d73c8d3a8deea4706e6d8282f46a Mon Sep 17 00:00:00 2001 From: Zhu Guodong Date: Mon, 19 Jun 2023 10:34:43 +0800 Subject: [PATCH] add mindspore-lite-native-api based application development guidlines Change-Id: I3ac3661b856ecc0002ec36f1ac4d55973c794569 Signed-off-by: Zhu Guodong --- zh-cn/application-dev/ai/Readme-CN.md | 4 +- zh-cn/application-dev/ai/ai-overview.md | 41 +++ .../ai/figures/mindspore_workflow.png | Bin 0 -> 1049378 bytes ...es.md => mindspore-guidelines-based-js.md} | 43 ++- .../ai/mindspore-guidelines-based-native.md | 245 ++++++++++++++++++ zh-cn/application-dev/website.md | 4 +- 6 files changed, 313 insertions(+), 24 deletions(-) create mode 100644 zh-cn/application-dev/ai/ai-overview.md create mode 100644 zh-cn/application-dev/ai/figures/mindspore_workflow.png rename zh-cn/application-dev/ai/{mindspore-lite-js-guidelines.md => mindspore-guidelines-based-js.md} (68%) create mode 100644 zh-cn/application-dev/ai/mindspore-guidelines-based-native.md diff --git a/zh-cn/application-dev/ai/Readme-CN.md b/zh-cn/application-dev/ai/Readme-CN.md index 0ff55cec35..98c8e17ee6 100644 --- a/zh-cn/application-dev/ai/Readme-CN.md +++ b/zh-cn/application-dev/ai/Readme-CN.md @@ -1,3 +1,5 @@ # AI -- [使用MindSpore Lite引擎进行模型推理](mindspore-lite-js-guidelines.md) +- [AI开发概述](./ai-overview.md) +- [使用MindSpore Lite JS API开发AI应用](./mindspore-guidelines-based-js.md) +- [使用MindSpore Lite Native API开发AI应用](./mindspore-guidelines-based-native.md) diff --git a/zh-cn/application-dev/ai/ai-overview.md b/zh-cn/application-dev/ai/ai-overview.md new file mode 100644 index 0000000000..f39db02ae0 --- /dev/null +++ b/zh-cn/application-dev/ai/ai-overview.md @@ -0,0 +1,41 @@ +# AI开发概述 + +## 简介 + +OpenHarmony提供原生的分布式AI能力,AI子系统部件包括: +- MindSpore Lite:AI推理框架,为开发者提供统一的AI推理接口; +- Neural Network Runtime:神经网络运行时,作为中间桥梁连接推理框架和AI硬件。 + +## MindSpore Lite + +MindSpore Lite是OpenHarnomy内置的AI推理框架,提供面向不同硬件设备的AI模型推理能力,使能全场景智能应用,为开发者提供端到端的解决方案,目前已经在图像分类、目标识别、人脸识别、文字识别等应用中广泛使用。 + +**图 1** 使用MindSpore Lite进行模型推理的开发流程 +![mindspore workflow](figures/mindspore_workflow.png) + +MindSpore Lite开发流程分为两个阶段: + +- 模型转换 + + MindSpore Lite使用`.ms`格式模型进行推理。对于第三方框架模型,比如 TensorFlow、TensorFlow Lite、Caffe、ONNX等,可以使用MindSpore Lite提供的模型转换工具转换为`.ms`模型,使用方法可参考[推理模型转换](https://www.mindspore.cn/lite/docs/zh-CN/r1.8/use/converter_tool.html)。 + +- 模型推理 + + 调用MindSpore Lite运行时接口,实现模型推理,大致步骤如下: + + 1. 创建推理上下文,包括指定推理硬件、设置线程数等。 + 2. 加载`.ms`模型文件。 + 3. 设置模型输入数据。 + 4. 执行推理,读取输出。 + +MindSpore Lite已作为系统部件在OpenHarmony标准系统内置,基于MindSpore Lite开发AI应用的开发方式有: + +- 方式一:[使用MindSpore Lite JS API开发AI应用](./mindspore-guidelines-based-js.md)。开发者直接在UI代码中调用 MindSpore Lite JS API 加载模型并进行AI模型推理,此方式可快速验证效果。 +- 方式二:[使用MindSpore Lite Native API开发AI应用](./mindspore-guidelines-based-native.md)。开发者将算法模型和调用 MindSpore Lite Native API 的代码封装成动态库,并通过N-API封装成JS接口,供UI调用。 + +## Neural Network Runtime + +Neural Network Runtime(NNRt, 神经网络运行时)是面向AI领域的跨芯片推理计算运行时,作为中间桥梁连通上层AI推理框架和底层加速芯片,实现AI模型的跨芯片推理计算。 + +MindSpore Lite已支持配置Neural Network Runtime后端,开发者可直接配置MindSpore Lite来使用NNRt硬件。因此,这里不再对NNRt具体展开说明,主要针对MindSpore开发AI应用提供指导。关于更多NNRt的Native使用,请参见[NNRt Native模块](../napi/neural-network-runtime-guidelines.md)。 + diff --git a/zh-cn/application-dev/ai/figures/mindspore_workflow.png b/zh-cn/application-dev/ai/figures/mindspore_workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..ae809ca4785835c7204acd13d8e5a948b3542850 GIT binary patch literal 1049378 zcmeI51>79P{l+&WfndemLP>G=0)YTQf)saYfZ$#%1=^%YgQSIG#R`EUX^T@F{tZ^3 zxH|+7Zo%dL&$HQi*<9}KwY%HByYKJwd2VKBcV}n5v%5FH+1a}jH~rOm(@r(#R8bU7 zJ81oZn@3U4*`lb&hJ7Yi$|av4e2#)C4_be_L!#(s_gFp7T&vfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOL|zC9u#V zTmCN01!&YdP;Ll700Izz00bZa0SG_<0uX=z1R&4=1bQ?;87U$Js!gC!?F`8e0vQqD z9-a|+4hsPYKmY=z3G^zROezE>BJj#QpKZ@vfQgW>009WpnE-+8nEz1n6{ElO}mV00IzzK-CD~TvQDu89@L75P(392rv*( zBd}y!a{?0v0v2Cn#0<;@s5xv7kUs*G<*!JT2m%m*00bZa0SG_<0uX3K0yr0q*lLs+ z0uX=z1Rwwb2tWV=IV6B{k;6uzNDydj0{upuI)u3ZjopTn9s&@600baV1p`y96 z009U<00Izz00bZafux#$EeHiZBLAOHaf zKmY;|fB*!_6TrDB&!)O-&KP#qMa%`LE>m)Y00bZa0SG_<0uab80UV9oHVp-X00bZa z0SG_<0uX>eV-di)Xsq_4v=D#*1Rwx`RwS_PxJ%b!EpfDs3ui}K<)|f_#*dB zL;+ijz{lgB-JH1qE!OB%7XlD~00bb=j0A8rnz2=>69gat0SG_<0uX=z1TrAN6sZiL zb4Unug#gnrxemkC_*`|El$7of}ds=XsmF{(|J{2%}U2tWV= z5P$##Akd-&a4uT3;i)zRAOHafKmY;|fB*!lO#tVj+C<3@0uX>e-3Yw)>^00IzbSOUE+TH)yK%mw)R>#rNO?i3sX5Xg=Ijz)IuIWhzw z009WJ7=dBKhDE>p81o&I;Vj?U<00Izz z00e48;EON5h(7-K<2d&pfBZ3e=9y>W_}g#49j8C_)Kl^D3opD7dE4ioe?I!*haaNJ zCYvl;bkRlQbyr+*MRe$)hsN=8AImJWOmy|tS4V&R;~%3LW|$$`d+)uY|NQ4a(U>t~ zqN9)21C5k@^2sNWkDs>JUw=J%`st^m{rBHLPM&$@nWH=IxFdSxkw>Csmt8i!X&nFe zzyBS5|NZw9rQ11LXrYB7m!&fjzZW}vB>1F*Irxr z-S2)^(1_zpun@ZiFnbIw_Ieb`B(riC-kI3q09JlR#a!NTN|PaemA z^PArkzWL^x!o2g&8`zf(H2~0-*Sqh&TaaT@SaZ!a3;p}|kK^yZ|9;`sS6_|yms1jN z`{<*O#>;o#eRsTk+ikbS%jVEs>%#d}k6i-4dK`;mKp+1t4M=D%?ND0_15uJr*MF+w%RJ1Yp%H>b-tr} z@4Yu#Ypu1S<(6A68aHlSyosMwoP6@hQSaWpqYpm#AX;s;)xxe-3;oofBf;NM~@zn9c??;eqSQ( zNQWYYX;FS(BG~cAA0O!nLFAXgZ9eBcMatKK&+~&0Iw(%})Tv75=~Fguf5|16#EypZ z4exv`yzs)&UVH7;mc@Si?HBpw?{r$*%61}tk=+XBXQTziKmR=L%zMNz{34Az;rHBgPo(J=F{$UCdoJ=kZ!c?_Mm!1`5c(xe>+G}7 zj-ML%S8$w{M_WBAS?X75!oJ1fX{VhQ%{SkCk>_;##ZXPfh&TDwuYMK%_P4(+-aKCZ z=%bIK4L96yl6BKgJ8k?TtEPHIn(7g0sztQxuDiygrygbX$ZzOexnt)J+)Ojg6xU;t z%t(MhjR|xRZH9tAABQw=hQ+0ONirf4CvB@Tz#4nHg*QhjAGXB*pO_hrL zga?BF1e%8c&PDUIB{hLS8-YAd$8g8z;)^eipDuXB_VB|Gk30M>^enT?5_h!PDv*kV zDI=+*wy5Tf#}3Bgi!a`mw!sD)#8XQ=nrjE+*T4RCx!URk*hOq|wWMdV@g#0?kDLN29sgliENaCXmPJ81AgN zgR%D7Ye#?j)1Tr_h~KyHv?Wi^aJ-axT1h4I9QI;6Z#wk$)S9t3<%y0iZCpZ`3b8s%%j(*VPhL_cx!yBNiG>#euO&UtaVpHTW&qsl$~ z3)?$?KP|MA;wg&3nF;It6SIC&=%*?E)hGXC>@Bz468U;vV~sT;y*wT^jh%KsiP(Je z&EqsX55YWz(LcTG0R>;P;a#fWTzHVdPLHQx+5AM)QziX027>?unu`F=MRT<$wShoP zAaBz#`~<--X50M4!JQBNvQ0d)>Cw-%X%nAh!> zO5HK?`y8Hv;b~OvFnLPJ9((K&zqIPkkvn)81nNoPth3IFryhE`pB)A}8h#4km!fU| z```cCI^OAWg?B8{X`g-eS$zH2LGe2gmCQeC5IXqY?%;zDjyLzTNIUi}iyaTYzu|W) z{M0X4_$l15?57(3L%#IO-(g*FlGa~;{rKl*{nNBIKdJBw<1VwGs@Tc0`S{`gH@v>u zQ?lZN@Bsk`)PcYjcZ{B;!d!sDh-fI@MjhILLqH%40(qK~(YJ5kNWU)|`Q?cR9(W*r zyEOd!mxogQc4?^|Oet>mAl&P*opa7PZ9hrnIUD{tD$hajKZE(D6VDy<oyK>Vg_dMGx|kPisdfPnvL&*!7(G5IIeZ2E24$iG{nJjCJ#(M0_DcM+ zg{}C7B)`n$-nt(bgr9TrcJ?Ifjr*tD>|yx2^3PHE=hu8(&r7mLVe|5xcitI$E#Alf zs8{@}3x1))^RUupuOX!QpY^7ne){z4N#Z`HU;YYa&)=g3_P%QIQ(56Sgb;uL1Rwx` zdJ<^JA1K%Ge}_#&`UOpLE@;TUpy9}ZI@^VnR$8gxAFUVYg5JC=Xae{o(ofVBG(0-V zar|Ta>gdFOWD!=D`tf~rjtUx{Ea+{~f;tY9{5ZeMr57R!8ZHgl6xLa1o%qi<^d@$p zk~FufZJ!YF_IivFFKS4(aQf+|7xdehVRd1|h!F)n)@fVTFlziK1pY$~O%!iS?@FYe zCv1shHRL&5q8{xf<+`37J#XX$7c@U9{*wsJ;cNR-3OT|BIUW<~#1l`9>+I_&z0TKE z+B6@jpt((xNRKYsuJKaq_2@9p#^-;C`;RevEu~Gr$`II>4gCij#ioDdD`+lcTe9y7 z;ZHvthClka^wLWwO4lQgwtc!9Y42ppXno+9(cw8P1nN(KCl~c+&JiI{a{_srlMyPc zj#E6@+i!?^v~q<<2Nf5_i& zn%Z7_?X`GLR(j;r$MM^ze&Za2K*tGu^wN9!ckI5=pn+>n{1eI@&q7;z9+J;{kLah( zPYrh3X{U5jTU`G>*%o)i^B}|T*LvA=4gEx~ocVWUOZ~R2-!S)+3||ZFT@U_ww)Ag5 z`)9BG_uT&ZHe2zpD3mIT=Z1M+W3l-td!73<3~nd;&Ncjo*gU00I*c$m4Vj z7f_>@@iYz7G7BbTB){e!JR^gI|33%U}KyR<*sj5fbb$ zdx}-D`6UPc+JPMlzpY(te#xSvHrs5oO>&+1pDrt1J9+{U|D==$4E*M|e{CWCTfBZt z-Hyfi=bs->6ANWYPxT6Egb;v0V-mo*Xv}t_)DS33z`tSXx1Rkc5$R63M=9ND@jD8^ z+!^;=@AJ+(FCJM9>3;juZ<5-!-+uep8F1&{`3CbJTX^oc|5DpOcJEGyE#2{OM_i>)8x*E<@X=_j>OVSFCEW2_uOp%6PcfIxI^Z5DV!GG5DmvJ7yA7QKcNWb zdFUsca6;Q}V}|rfBfeo9LI~8FfIIJ&&2K^uaId{F6|A z+E;9L+U;D}K`*twczZAVcV$aid+%R(A3Fv90}bDA{BD)6U7PpylaF*AogUsP@e{}J zE{M}H2tc5D2;f{aPg_>ACTnluA2#pqT!3nph`hQ)Adk~AToM0cpQl3w$HPvKJ0^BU z{L<_7*I(cEb5VX7J6QM$sV%nHA|y=`-hA`Tk$=0^ov-w7a)x+&FQZP9Aqfzu0|7hI zcieGDJfiQXe0IY9Qnvs7(vH5JeE*}Qp9pNV)mD+80NM^X;DEBz2g4s|TzKJy6TM)a zu8w~~%0H6efBUl&?_-p5zWtP>_(_TX_`(Y20R;b4+)FRL6!{-C{S$E>Z15DXmtTH4 z^1tk*Eeuw;PGK-3toP4sd4CK75NK=yI2w)Jev}>plMu-3bPT_5;ddnL2>2JA?!EWk zX!qTBkDp|?li?9fcXB+P$$t)D$H4Qp{p&V^1`V2|o*}`1bm6}uKIou>qQCv^Zy`Aj z{Y1o1T4tVk=84ihvg$tvz#xz%0e7Z7rEmWE=Z}9?$;nNPdpL-@|mv# zUlaPFhPXrS-?0sU2VScD{$mY04SoVx%EFTb{~DE@5dUtjf4{ca^wM^GT@|ONm;GDP zemWWcT0FhZbt!)5#OwX18>Ot&)0$G-kp=+>OCF99sCtn zTrut}xk7##;rZcyxz(ecHjk$I34=pBB6cp^=?Q7^Mnxaa*Y8qnx7~K}6piASO+6}X zXD0j{ZrHulUnYljgb=7D0Z$2BdF7SkL+rE9KJib<+My0l^wP(kXP$YYi!Qn-p6cj7 zwg@Br>325L+jlnZ7q9)Vcm7cYD{Y=$>8B6fd6M91lb+_)l6K+#<2132xd1KM_*6Pg z07oNDgE$15m_XLkF+7dJ|5xGp+3tW`cG+d|Uwe-pJvx4RQ4YO-ci(fp!#|XT?Z%85 z6Tchc{QQ@}9+~x%jI{Yzcl>0eI6b`$aR}6tfE{kj=8k;vuSJzA%Ph0Z68RtPYGHl? zQOf+6;}`@W0D-O$z`5uOHTHo(0}{x3I);A{DsB3&j%e?__m1Daumcdx?`YT&@zV_d zijM!B%+s0tVrbe{Uw!rXC2qf98m4@t*As^T1Rwwb2tc6z1o$!d`ZMQ<*%R;|AK4n} z{P&+vO(=H9KmYf?|Hc0e;ZaEcbfsUwEH@n^m??;}F(eaVe+a`Ny!|Kfi@H-m* zH)Fr(T5L}DuhV$yNN3G++5IlZ;N%~`JG+0S?KQn6%X2B57g?5}5}sEUt|!-I7h1H2yJUP#maAE=&*5@~CLIFZAuw5Yc4l7)KmY;|fB*y_009US#EnbU zAOHafKmY;|fB*y_P)`C(k*X(cjs<~k5MVk+H{h`=1Rwwb2tWV=bs@l$i&|mcS`I|x7k0uX2=0u4PXaQ?-Axr4a?RXJT5 z8K??IGHF!;I2x_m^3*#k0(@y@MLxUZHgtzIJ8T>o0uX=z1R9Y5&P5}(8fAt61Rwx` zdJ$kipkAc2KMn%{*|z~DXaWLxe%reV@KS}k5ZHXg_t!EPpf0F62m~Mi0SG_<0ubmZ z0UV8vl43guKmY;|fB*y_009U}Q|00Izz00f$vz{{t7|1fg_n!1Uz zs~nC-cI-Ja1Rwwb2tWV=5P$##nu-95=m4N^RAOHafKmY;|fIxNxa4xcA&ygVj zfeZ-ry!NhVnG28scn%2x2tWV=Ek^)Hqve{M3PS(_5P$##AOHafK%h1Ra4u@Ym0TeJ z0SG_<0uX=z1R&641jY|o@c2&51t^S&hT?5B**QRUAOHafKmY;|fB*y_kQD)(i>%0V zTnIn_0uX=z1Rwwb2s9Z1oQo!FP^tp~2tWV=)gbWXTL*5}iMasPI3#&M00IzbG6Fan zP1c}P2Lcd)00bZa0SG_<0$CBjxyXt<$Atg{AOHafKmY;|fIyQGz`1C$2JK>XjymCt zvzQCeMR@E60SG_<0uX=z1RyXe0UV71V?Of-wt)S!WUFTzRI0&>yU`cIoc$tlyzC)RHT}_@m*|E36qotn7a<3#@L*d3r zyj0-w*8AEmRSwc10D(Fa2sep3AC|*G00NyPu&}ltAw4SnQ~FG@O(!iU{hze8G+w$} zvK=hV7006JBZYsK+)1$YmDZI)he6Tk_tMX$QBo;;O^Fvs&Npp0YT>LTVFQJKl|E0F zr&ai+^n>L4O&`f+v`r&9udBUK#_TY8pNq?^pF!zIlv!6P`4p{ux6B6fD&4FukwQJn zY)Uc&>PBE49m)5BVmn9)`%6zzbCm<5NuOLmJACDr;@zg;z``J{w7gN&_U@ z{gOLJ-7(+8`jyDQ9kMy3a-A;M*-qBRQn7iRHB@@Ac%_!FlYT6bP7(xaL0|!8>T7o? z$=B^QlCQ%(BtPZzwY!!yk7TEPkaV9^Y*#7a8L5ZlyhccVl3C8-{-f4CAl)t5>@56N zT1oPCzP@ysG`AG&9YuU!DYj{qu)MTw@d_<(DTVt*iPa=Q00NChz+cQ51R#)K0`BCx z!{E;B&eC_%rII@j?nG3wGqtj>w7cYvz>3lbl5JMWod7RSA=#Y2MY31>5kbMa_6Mj>=bPzm0B0J zbqDFxWMO87?sPdn+Z&RnoLI9wmbXe9N_KedU|7GChDm!% zZzb~i>}&QQsraeESfyFEancBBG3i~Y*nBOlD~*yo70R}%G+O$(w1MR7*A7eC?BEon zOC?`7cJRkYCraN-H%rCl!3p1+FbF`PNeJLvG)Y5Jjiw|pwQ6;Rd{rSJR)NZ~%B>i6sPnH&! zo=VajtS-$cUFQW10vQq5K*zmNxus>qPnuWqy~p17{E|J3HznI=()p6_CBA1kzP$9QKuY z9Uv_rEg}t-Z0_)T->~eCNcs!@BQ3jg@S*gz29$IgYFmhU8+oe+0cf_Yuq+!5MMT3>QU$ad_6ApV?2 zpLU{#OCL+MvJaH8%dm-L7F$cdm(G;zfE1g{c(3G+(Zf=I$@{uPhCv`p0)2Jx8R$@i4S zq>ZHiNx`O5WGl(eNI&Uu$>s7j=VjZ}6N30Y67F%+CTRo^fIx#2z`1Df2Bi2cNx&n6 zuXdp3r)e91>CY}bEzKw0B-y5v{1oG6FC@0RLU;OYM@W8h@RD?bw3#$cvbi(xNgS*E zagg#M%8QZ*LAH~8|L~K-{UqDm(qPGs&G!-p0SGh^fqH+*HxW9j0)e^{7_KAs zl{S*jmfEvRw1Kx9FL~tN(|nefY->oK0^w1~U>;cvcDN$%N&Tcdr8%Yfq*Au35+9Vl zEn4Z$$7e-pUbJK5i&lCn)kx{(WJ6!)9&B*>T9O?MTW@K4X&dP==>ut2$y1~r zlI&=lDLo}UBKckr?0QA6mVPY_NfPH#_`c-*caeHYHs@_SUb;Mv@c{t{G(3TDfj4|b zr~m|7fPkkcEFhgB%`0sr`6-3JuuIupN_iCGVd$J1*%w!aApCWtE%^muZl+xb(5)DI#9>QxFUSSrJ%P2R>3- zL|RB%O6nuo{w?*4W0gNV{m4%rUY3q2+H_wn-!EMt*&+B*@)L;jB|F-_cZ`q@mF|_s zNWna~@Q&0&DkO=)K}i0aDkD%H{GNN1=-b~R11Q}ugkb;+Zsf0R5WUnd zRL_=~3s5bd82V;U`9Fa1(_U-GD?%Xx`ZY##k|`Rq`YvfY*VPN@|gNOL)Nly;TO$elNLgaV(F z75|5HjC7Oa5#URu^Q3nr3DNMA^6 zN*_z5>^UV4kbGU*vEEqneP*EKYvXRo&c~h7PSQz|?@PXZd=Ib_Zb!t&_CUgdlJ5n+ zu6=)+O>*A8zFdy;rC`3seJ{cw0D&eU;0_1|0SM%qz;-I$-qJ55e{p{$jg-!n+@U{E zno{yeqdUJgcbu(FC3l4V1j5e8+R`f0%#!zU+)qi|8L{kqTqw;UrSA`R03MJWo+$N~ z?2wF?_L1E2aEIa`$#$)@a~v!E;q^Ntt41hvzV{@{>7Z0vdc9e6)!ccq<8YPqYsqCO zw%3&~So)o`r!+*e(}qDH4+PwCKV2Fntt|QawWG0%WV7>ruXKR)u~f;P(8?91Cu z0uacWKo&RqERLBqXO2ICKrPz77J25ey|S!lmdDOxTeL!%YSHGk$g>sNf;u)bfx?Jr z=wyvt0m=>m2tWV=5P$##AOHaf2kfB*y_009U<00Pw{fOAnzn&brm2tWV=5P$##AW%C3SIl_8oXiEN z9p5VFjH6LypkxOD2tWV=5P$##AOL|@C4h6$sx42wApijgKmY;|fB*y_P-Ox*7gYvI zb`XF71gb~i&f`Ws$y|Wy@gye*KmY;|s3!p&je64NSP*~!1Rwwb2tWV=5NKioI2TRa zxKs@S5P$##AOHafKmY>uB#z{}fB*y_009Ud(hB7oc{0$r%C= z=o$eWjjnNHF9<*Y0uX=z1Rwwb2tc5S0M11b5EdZ-0SG_<0uX=z1Rwx`t`X?fwcXeY z0vQsR`^77peHwfTdbORo{ zLI48!C$RG$1|P;;fc&=;C4>M3AOL~pBY>mPd~HgNAOHafKmY;|fB*y_kO={ti%hU{ zPzXQ(0uX=z1Rwwb2;`l>i^pFvv>S5)3L~PScpG_dWXcBt2tWV=5P$##AOL{|A%Jty zAPq$^ApijgKmY;|fB*y_kaq$&7kO_X$_D`mKmY=DAn@+mch1+1xd3%2A%}nf1R&68 z1aLGOt;HxW1Rwwb2tWV=5P$##a!LT_BBu>Qu^<2e2tWV=5P$##Akb(8a4s6H#i~=@ zqt}@CW#$4@hb6f{00Izz00bZa0SJ^JfTK|Y3P})v00bZa0SG_<0uX>e=Lz6kbef3o#U!sFc+Ye&j9M)K>|1$9R$T@5P$##AOHafKmY;|fIwvey(+I} zEd(F{0SG_<0uX=z1Uf~a(5bE2xX}pos8rx;ITxHnl_)pk~s+wfB*y_009U<00J#dfYHk4YW@yAU8uHm z!AaDDCsA`%nc6@A0uX=z1Rwx`su0*~=*K-H ziEyw00SG_<0uX=z1Rwwb2y~SI&P7+ru^$8=009U<00Izz00bZ~5doZwiEwmcVesVr z9$+p&CxEdj1Rwwb2tWV=5P(1{5x~)CrPijd5P$##AOHafKmY;|s38HIiyFcuYY0F9 z0uX=z1WFPZu-9f^F&CgD8WJJUJOpqwnx`$P2?X*&UffZDQ52Q0I6lEH%dq#S?!Zxge00inoU>O}|s&)=FkJeil z1Rwx`b_w8Iw2O!hAkZxW%T0USCapRb;1iMiQQAvdt_;QBD!s4tUYYeILjVHxA+UxH z^N95K3Wu9n>mEzCtFRfXAOHafRF^e;okp!<8xsVu&b%z5o3yY%(yq_ZWjztIa=rwO#`IozuC z?(_-LU^~>cUF&+V25X2$E^f27YqQoXFX!6yDu|y-nDY)&y0dakl0Hfy)^!4{at^mj zJ-dD;bbrqym4!J!CH0ol*Q=*b`AOt=(n1o}c>=9^4p*<3Xq9ugRqEOG zGokx?_E8ouOJ;q56daAM6u&!3AE^-Q8i7_lhg-GYT{}w}XRo@pYh4dkrVSQU`VmFz zHr4VLN!rg8R(kogXxEa9t1ET3WR2frnWf}b6B6B#>Q`ZXBdp3pyVinJv(~E?th$d%)0sfU1`=IOLr#m;AFh3<>@4>YXn;L9L{6C zr&SZ$QTERrcE&c;#;0~>2W{)=!9SNe`;&>=XrC3NVmn3&ix($mu-u_`b>*vJKA5dq z`Ax{JbDsmXxf|c`XHVo&7^PAs;PW zBONI1Ev+JbBrPDNPY62~CrK|!ws$1ov~3?qz2ex!9}jBrNNI|RlOx~UUo1+i$6_AZ zwKMhHT^qkIxqT{Gqt&ZYUGlun*M{H0NSn`NU!&iq6Nv``i|ule#*q6)g9fhYw^cf3 zjeI`)JHyVV@1Yk;KS<@Qk?UEmPI*jsoA|qOof3uHRcUS~tZM|C?Hu;BxlbhbQ7Ty; z>+OD``(^V>W2KJT?b`5bX`>1|%&B#MkQ~k{xqmphw5H^~WGTBqiKi8Ia{N`vWpvzHM6%h5I8<`SXWJz1 zlgRNUlJI_oHs`--9Gfs_^-jgD-qKpqB`GU{V=VIw1O{L{}TV30=t_Le3Y47lM=@H2%yyI>Z z+fLG8M`Y7WSH`hPKYRk%8L&C-e1dJO$oJI-*-tRc ztY5OcuEOV&xF3&&<2lJ?KHiZGEX#K7N_BV9PJW8va{FTU1#PFJ*cz=~#pQ3LWsl+n z-VU+q5C}}GlUx0B@t3Q=EJHmmSA3ol#R%(5tRkrf1Wr|+fw{wS-(fX4bkk-Tcs4@? zd#DnZN}*0KE51pYiiK^;tgd;o`(-Y}xRl)Kcbda=y3@kBZfDwkv!ELke=fBg(u+fP z^u3+?g=3}VrTI>9Zv6Wou*UH7W2+hbt?+-WHBu zm*aNjV7DJI;MPvh1!$)>(`)^<(q)prxExMary0d>?MVEQnc-YbsVcbfuTBtudfAbf2fu@LL*+A#f{_pp-1d| zzjb}SkdBu8MI5Xd>KW?N2%+zvfq8pVU>c!5c_?GxnTN7es|*8_(Gyarn>)=5OKA)1 z(rNXKyD#N3xW5(5eM6`D2}jyIFu8I%A&D=m@CV5Qz;jA-myw@T*iM!Xmc~l^NFIm| z)=!alB=_m=k&4Tpq`2Su2S-xo{9NYwC10OwOYU1b-czc#)wNygdayDi-9i6c3Mcp& z#r-s+Pm%0|43=y&NHEjSnJCvh(y;uW%)q6z7x1M|SZiVZl6(yT{?nlM3pFZYQ^wwnYV}j+5N-j(eWe z{YhLww1#w_BQn2iVY<&q-e(sn+=YGS`3yZ$8X0wYctQo%~&Zu9yDpuJ78=d)mvbg`HiCOmmQ@r)0P<k`VQ!#GjZ|w0zG&@k`q9Eg(tgqy z$sPTEhUgk6sGQ>oUn@w{!HW6l}O6w@FSvP0GqbyEaq!hG2&J)SGKo$E|lgIo?kM zYSrc;-)0V<6~<7%pNQ5s7LHH&Q(H7Ui)x=?&CaV-{eH~BwYzju2KTpY9!UN{8Z3>I z>=;ZY+1$5tKP{L$^0!MGll=&1yO5yJ^E+3Q_Lt0L&t!QP$Rj0tgWpO^ zNP8z*OyOJ7Cz9=h2|@hn&ij4RsCa3@Z?7Gz_(qaPy+4!uC1+1`dntIBicVxZX~~oB zE|Hd#21{Q`Ha`_9Wj?__lH3mAB=>fgORkGA&{d_Z?H5@WpbN*JLi_wi+BLQB-xc3W z^3x#<0?j~Rh9;=Q&s2w_rSuo(FfEUed@s*~`AZ#ZZUXK!)zaJ_X-4y`kuW$L>gTiv z!$JVNTCmUui~3X!DdzDRq0LXd+965cC~bh?Fkh5b z7WO+G^ekzh^i)!Sg%mEI((8)5qnS>dQp>ifrNyMBQ?hg5`uK!Or!T1Gw4I@aky1bD z{6y}+I{v8iViFJQR?xDom*o7tpUo$}%Q%@A67$LbSXjs?%(7j7s`C8XEi+@+=K}mn z8NDQhx?HJvUny8f56krmvpR0Q>c?>=B~Yt2uT{R2)}VGNzW4oFdPj17Jh0-CMSs}_ z%etOfA3ty)tUZBRwRNrXtv!*-N0?o!M@hbJUB|a254Z-WJnMS9ALlacA$c15xl$;b zqH#a(V}-{`-%Be?HV=HP5yvLXXmProirZ82ejb2Mr}fwJN|O74Z%IDh%aRAF=T77& z9REu#dtEU15xOC|c4gQfGOm!x2RB9gXMv@l3o zOPWLSTRpQS%S$P=Z6aM8$M|TEz{1+#RB63rLpz=aO8=KE6F>k0`5@r?^XZbmjBWQx z{^EVu3m60-kXr)1RWf&aeKaE*14Z{ zXKLBejfTXTv`fdXX8C-8OhGi6Ozq? zEbbEqvqNJu@0PI+c``MoF;MxtGnqD@__s>`l|Gc#lsaN|x?HF92~cW3KgGCT^2nP{ zEZfY|&C(B&zcB4Qq|GPu`;znb3FVIOHPQ!Cf5|79%X*M>h~yJ$St$$4L%Vjg-X3kR zqv$gGX0o5uQz~Uy)hnyxmSR9!eF)U5&1;o!J*Z5tgZK;5b$DCaQA&>}YO&3-u4mTA zZv#Q3<^*ch*0svFGeEw$`VD#DR zKIFdJ$|W|k<5VhrjMDci+J0LtUzwzhRk(Z-zg*$%iJnsUNov{YVX)=YBwaylujq5* zB)*$M-#?F$+~+x2DtG_Xs+wAd+O<9CH7#KcGxw8#Z6tTppOM_zUq!k?3ZADgkeQ^t zrDF4=d)p9cTpXMD<8NBDbJ16_hq|-0khFuetMsTezqEnmNB(b0!=ziJ$EEEhd#dkB zwpAsM>iI>eJNEkJ67{a-%0aI-%4rwmliz8Tx{E= z58d14Uj%*{;Ev~g(g5i#sh?EJE>hw&QmOS*Bs)B&@EGYw=^p7uX=~|L$#%9h zO0olUv9yNtob-m2Ht%mQG<@kEnp(N5;vSU`j!0@1(G&rXJlLVcAOL}U6Y%|Rk9-%A z(m?TTzKyNbY-GC_ODzGS7-|pKmJZ9qIq1wE0+{ zq!T(3UqRbGERB{XlNOZDmu8ggoD7m~lWcZi`b(EfeWhGF7b;g(%#ByB0jqL=4rF4l z(oZ66_C_n2PmXF^dwB+jq||Y}u5YOo8K>o;U2B0Lt9oU1+*&ZqZJx)Ee0>pLPJ;%n zxn<4@S*!N1RlYgH*ESU+t8%pp=dAeV>A1CO>ssZTCw8UE(hT*URTb-9R5?2)?V0rlIv@~pahVD^Uq1Rwwb2tWV= z5P$##8jiq_e|~mX73Ts>H~f=fcpD9O7EoXaKmY;|fB*y_009W(lK{>|KHG&-K>z{} zfB*y_009UmPbWKWy zAOHafKmY;|fB*y_kPQKxi)^@aR0u!-0uX=z1Rwwb2s9l5oQtMw(vlV0?bO5c$1u74 zkARZpB@qG;fB*y_009UgMj}!^oXMUH`TKL>eTi#>C8CR^^euH z>ug24pQdFWyQ_AHcHAJfv;Us;pULuEsdSkQYvMCSK`A`f9Z7TDaoCYYouVGD>6vB zCnR(roKxFaHkZxExWo&xnWc-RRipOEvr4JJhbd zvM(nkfB*z?KwvwSVPDD4g>6;I4u^GxWZPJJR=QF8Q2L{^v*b?5anfMvX=yQO2dP}g z!FhfsrOi&lS?L7DuakbCq-~^dk0jntVP9#rWcfse(?~B#2T9jTezGxE!XVJ>1b*m1 z1^u+)cxfBS*YMQR?NTXwu2f=@wwXfDe(?3?d&HBHuerZVw*N}AO1>ZXUNXJ(vgE;u z#U&2_+#wB>JaBQfw3jrWgh2oT5SWO7J(=LmOtgZ9h9=Og?V44+vN~?@Nw|O#+_88> z8lS|sQfS*r8tjP79fK?5*rXrsD14nHJMMgfZL7%r$%02OHAcOr@jaynq>rWN68ZS=rIzPc zJeZw^+rxrF-xCHW%U382*YFvNyNp>|t=hU)`DV?lz2moPz1!o`=#$kwPcM9eC&CPQtgT z_}58%{Umg!!uFJ8M`Ma88eSNt$nw(4lEG}AEWe`Ak3GEeI4Rf(ii}DY)>HUqN?$4N z-c?BRe$Ml)RGQbVAZ2Z}Itgl(Z`QnukDqltvp&9g=mLtboBb8~5u&f5S)`?;!zDkO z@U>;x8yGAF^Tq#yw04s4TZJ7-LQ%MvmW#cO zca-*=^qCaA6nigt8-mbemWOMn$&luvIvr|P-#{=3K%lhMknBF$JdLmGhZu?`{l>?B|pY<=xfXN z`GlbD)7Sc9lJ`4AvNQ58X$Ps2d5)s*3%)0KU&sF;IX~Y6oNn{AH?8DlU&}5_CHt#Z zI`5OECndi~HB`bN0D&eVfOFAAjY(A?&=(`w@h{>{pa*CrL-ev7bKXRrJqp~>q*=%U92M=A+0CvE^Q;(eEs&9yv`1NKgrI=P12l_pRkOPhDkQ(X-CCn z_TYl!A4nJknuWly#m@Xo+gyNVsZ@9BfuqqKdh82<#wBo&itgzPb4d?NJ4!Y?5Vyp! zpFU3eDH_Mzf!I^>h-fLhONm=a=SmAmmwONVfDyqdWKR?2nRc?@E4Z;E_^07Jj<%tmF~Q?WG5#U_SQCVL{=~ zBs&^*7Ao0oT6wy(kK~cuEu^(2n>$TUOG`fTW-%f??0_h>?EHB8uoDlE{0SGh$foeM!&A@~@K%m(PxWoT<$s_)@ zkEMkqk1ATWt0aH%yYmvv4uBm2cX0Yj?#u_fT@iN{JlgrZbgyJ9?lkzx!hhp66FMdC zfP5i2?@G3+R!$|2lH9qmg5$HI;&yi2>6$`vUA+DW39BK2>lWJXDdqyy5b&f~-=ZXI zpycZ?m>v4Lr2VBGq%dF*I`c}3pBNsZP;J&JN5js+g~>kWE41?vY=|OOuyqs}A-yHd z9TJN|Uz@*7rOlwYofqF5Y`(|b8OQi&76Lt*r6Sa$^8|WzewUJaa1xcoLLvlmN#M^a zl{@%;Y7uOxA}b^dQ!Dfr|8VJbDVU!e*om-X;O*V1D7G_|knV^)rsefh8*QMt_qDl$ zVRI*G<~UaV<7%x6Bd3EEcjsnxX-4UD$uFGFD!C(N$H_08{zCdt!XVI$1a?%V#!F{Q z9u%-6{x``^yRVOTq#Gn(gR4oMF+bHTXXk5$9U9wp(*H@PN`8t_%BEN1bkcFDl|NU! zue3~(=JzsiE)p075NLD)y&Am&lpg{R=rjR42Ugm-*d@tjT7_3i?wI&#fIAWYk&11n zq6FVdzLN$@?#z_3b(J`$OyUksmu8b@krt4gk2^qq zN@AyHm}L7#noqJLVkZNGKr<4sV}6YEC+UZzg1)BxPLi*aC8RM@N6iC-<(z}!Jbb*b zlf)ktdN3g@JK0XY2NAZE`b!^5yGy>NgI%G>{nE&gKnMW{G!Frsi;lMCS@(XayLI1a z(7-i)$HXAe#01EJ4qM>nt{L% zs)QZ-nWa-CUtgA=6!-~3+D2<(Tglh+bLkZ&;&u!!kfxCAu(dBtI=oI|W{U zn{<*RCG2ITIj?XXhfTgz#7;~wm$TS>J!24n00bZafrcbdP^qnYTUN&{SI^0{!cQV> zQ%(rVeoj?(O~+UC>G-Am;WFZTiS_iedV%z;= z>+9<>7hrf{SXat7$v(Ae{95HZNd-uN00bZaf$Rumb+gavxY?2B$hB(oTIHL!7R$Px zSsy=d?a|ETsa0FoD&J;q3+mS31hN_qX>e>59|91700bZa0SG_<0uU%BfOAnygk=bH zkHDu74j5LKxd7cOCVN8w0uX=z1Rwx`+7iIgs4ZV|hX4d1009U<00IzzK+6!oxoDZD zrlJsl00bZa0SG`K=LBZjVQ>8=Apa4N^JeO3@o+RcN{a0u009U<00Izz00bZafri-Y`P0Q) z)Nd}p@Phu30D}MoAOHafKmY;|fB*zCBT!FoBeP0!U4;D)=Wf3JRX0lHOGc7^~1AkantN23h|5ePs40uX=z1Rwwb2tc4a z1aL08LyvtS009U<00Izz00bZafi?oY+G0c?kQV}fUh(Qbw9Z_BLSC>_4hTR10uX=z z1RzjN0yrAgq)A>7fB*y_(2N8a5NO6$rA`olKuriRMXDxP$rJ)53G`5EE9%OG##Sv_ zf44aX0SG{#UIdovaoLNlGZ&y00IzzKnoCP#esoyl6Ku&VJp_Z z1x{e#|hwEbet7iLI45~fB*y_009U<00I>V;9OLI!zu_s00I#BiNO9R?Y$>+0e<2^ z3<3~<00b%%z|pA8hqVxZ00bZa0SG_<0uX>e2MOR@bPyDqK>z|BCNP7x?9tBFQ)<11 zK>z{}fB*y_&_o1qF3L6LgI}C*Zf0`X4 z1*T11NmZ^@>Y0ajoLhzXBE{V784T!t%Ww|2$8gbqy>}+R3(y`V)4O|6^4gm;200Qk1;AY<* zq3W(5s_ed%7OZZzEuCAdoWreB&%B(>eN>RZ+#%L|0)c1ub0P-_KmY;|$Sr{^Z}#EX zgcEmSnw>{-F|2;|A@f&#E(O~7;Oyy;Cpn8qQIb1!K zwe!ted*q>>p$vIrLwRaLpxW0| zIDe|0Z*6K%t`MjKfnHU}gDfBbfzA`)i>C9;8gY-%v1r7yQ)UQ200IzzK&J`R$hp{j zrEgxZ`dolcSB@PZ009U<00K22z~Dd)pynt`1_g4|7!;+k2~5`5#i#TTfB*y_009U< z00IzzKwb#oT;!!CCeDFQeerKpew0SG_<0uX=z z1Rwwb2y~hN&PAt@u>%Ak009U<00Izz00bJ0z;b;*c)8AV0dO=L>>Q!E5P$##AOHaf zKmY;|$SVPyi@dfB<$?eNAOHafKmY;|fIx#0z`1C!2BWwTs4{^CmERFXd2XuZEt0gK zDLk`ieP|0? zrIgoSit-z(<#mhFsE=BNg>fB*!# zNMK&=x07_DG<%|7DeNg}%4Rf^w5N2QWP4uvN*W>kQYvMCSK`A`f9Z7TEa_n)c5^^N6JI7@mD36*>P(T$!5plQ0ZG~+a&&V61RikwEHES^Y@dHU{5HrO|r1G z!tW&g4(%jzCux+FHai|)O3O&|NEb`)1bM{Pb+JyCzL4Bu$=Yhw-nGg%YhK02&qF=) zPzFEwdov|>%zsENd!3z#Pm0p|YT34;B=}Z>Br(qvWH=F+`W}x<-83Ki@q-PlCGBQJ-A0w$_`TEs1hsAQJQ-=wmy>g zzd-6O+1vP3`n8k?t5ti~D&IUXOP3)J^~^&V%*1mB=S|=&t?-N_9)h8Y*z5M=3~!q@ zUvI8Qza-JyJ)gww`QM+!9r$`bUYbSn^=a?p5XoMH?+w3`?49f`&7Ekt!mp$~rL_6l z#M`Ki7n9~p90Iu_;2RDG0SL4nfe%&X^Ci!Tawo!ZKVldytt{Dw$Db2E#ws{exqs|C zF8_Se5a}`rgFrJ8=&#DyAzxqe^)pUNo1F?@%YT*LlJ=L1&5t7dC~#)U*Q3o&#)5Hd z!pD*d{vo|D**SPcx>WL`#eJncq_dDZw42 zMWmUeq0+a~=h8h=u{rI9BynAZrzP=A6dot}m}^PfNJ~iW2-(RQB26LPD!G$|L7*83 z+@(q!Esc@hknCinZ2>L3CfWJ+wX>OIXC!TQ!rznZxNn}UcvhjWrTHa05dEYFB-;kk zjnWm;Na;N3ZfU$^N98N&Wy!X%0@3+-Ep`1g$;Ldj+5Lm zaeQ&<{v@s-T0^?e5m|4^(=*&ratCKl=`!g|$@_gFy(E<~ud_WP4VQM7>|(V~>(PlzK|;L+m8|MY>oTDEZnB z=Ie9YL{1B3I#qEGQUrT3Lb^g ziI*Re(&kQy^R>-5A!z%2Q?UVUOA|U>y;93rL~GUFwaPaOMwO14hkE9r3_g$jw7}Qq z0+O$(d8E;joqEgHpP$-=<=Yew=KWm{cl>?bdobZuDcEpDyuH&;lY%+VP=ANVNzX|$ zOMN9@pNmWPNIOcSq)IlsRyv=)l{U&~U8rZZ4_~W%t6fg=g8&2|0D*20$ns{N<*}pa zk1wvc+@x~>+*$E7rR}AsBu``TlYw(2cOEQHg?K5oypiI;u296C5<3*OS)?N*ZxcpU z_f>qjw6zrM4~lrC^gQV`X$R?5>1Jv1RCs%`{E))5r5%k~wc>-6JSzHWYWWn!v$k5b zeXa7%npg4h^H9$`l)+p)1<==(FuT?wOD&GtcaYzVs zjDT-A7z7~DdIaoL{9Sr4sk)~GY+p_Y;?Gs}dA;8svVmng_-=`8EQ+)kod0-OGq0moHR+GGbJIN!Xb0wkI zhmlf6Co*?hPLSNOnLD+{>F)fvvlQ$$MO;=40*y?-4#S<&i;|s!-$?UIHaiH%Nta8$ zrjL>KkV@GqN*pAuCHWfjlRi5DUJe5Vn<##*w5)WbWGBW04_@C#vi)1?8OQj5Kx+~B z$8yL2YSOs?t;K-aLI48o5Xkb8b(Y62g~c6zPmxO7v|6}T`mf{{I@gpsV!gDH>$h~7 zjoqR7qvWRzc0?|bK9e?=Zk85Lg|{cm4=GHWr-yta*?~A#x+f*4KbczgIy){|TCLi< zR{3Vhs?@RbP|rM+!5#d)Q-Fpk?x&Epkjy z>Mt!L`8q#HIz)0_b~5~=;`fqzRnenTaQvTBd}V2E$=Bzs((clZ(kLm|YKr)nX|u!R zw7%&?#a$McGfQ*1ipyJ#^3*EdYLtyUAW#zmy=syxnL+>pjYzk>X-c;KD(ijPYKr<2eT9vt2@)LzAq^+durIV!tqz$FFB-_r?KO|pkhf9x3 z+e=SK?@G2+r4^*{k{$W0rBfx_64HO9^`v7YoAXKAD_WRE`cd)#fgPRSNZ(1tW(Oo# z=yP1Bgl|Kl!s$|Rq7Z;UgA)iBdV^Pk;zIxeRUqI_hC2!eOIIcN-K20?X_$1MG(d7E zrk_;GE>hw&QmOTqD1D36PR}cTi!_zwj>fCf&?G!eq4TmslC~qX@Pzb5I+1v72rT~n zshV=uHySi>%`Izlgihu7(fbB4N;*^uwyPo@0SKN+NKc3BXu+cfqokFk8ztwtixkWk{7zw^o$#;itYEP1GB z9?DRzT_!8HLg_s#tn=fLz7^INukvHXsf!bd_=s zY`+?Bt$c>&bmV9*K>I~z3kW~}0uX=z1Rwwb2;`aojz+GVhr&Ss0uX=z1Rwwb2tc55 z2;f{aPFqn@2tWV=5P(1q3Cw=Nv3jMPxd1tA8j1vgj0xaqWZVE89s&@600bZa0SG_< z0?k4I=b~9!l6pV@0uX=z1Rwwb2tXiX0yq~LHvorkI0Cb-yvH%j1!%Ztqreb=00bZa z0SMHa0FFkziE}&%KmY;|fB*y_009UzDFK{|CT&=%1px>^00Izz00eSJVA=hnr}H-# zV0d9z?npIHf$%n(r!A=o1Rwwb2tWV=5P$##G9`d>ktu!-4gm;200Izz00bZaf#xBA zbJ09)NlhRCfjkk|Y?0`x{LKZ(6Cuh10SG_<0!>Z;N2AFbnCd|Q0uX=z1Rwwb2tc4- z1aL0uMVjM400Izz00bZa0SG{#$q7`+x%m9vA8ueSK$D*voeMC$Ff2cuCKMKMky+!K>z{}fB*y_009U<00NyR zfOFAlWb6O|2tWV=5P$##AOL}!6WI5cv+a=aTmT%6oS!2U4+0Q?00bZa0SG_<0*yid z=b}+sigH2#0uX=z1Rwwb2tXj`1aK~L-ar%&0uX=z1Uf=spUdX`I^($j6^l8%Fs$Nw zRzd&*5NJ{YcpFXHuv7~I5P$##AOHafKmY>uCV+EMZ{i#e0uX=z1Rwwb2tWV=O-cag zqTw2L>JOheBF}RH8V)E0h5!U0009U<00I!G0RbG18bBpW2tWV=5P$##AOHafv?2kV zi&kuP>I?x0KmY;|Xi)-NoHXav%mrxC#;4kKCxD|-cjO!p0uX=z1Rwwb2tWV=%}D^~ zqB+}@T0sB;5P$##AOHafK%njfa4zbOoC89jjs*6(z1Q~41*ju%4g~=SKmY;|fIuLC zqY+3DLI45~fB*y_009U<00P}2fOF9;eC!MX2tWV=5P$##Akd@)ZrXjF({nx-pvQot z|8s1<@#AgeyL~7f1Rwwb2tWV=5P$##8ioMQMZ+`|1%&_vAOHafKmY;|fIz+p^vZYf zC>;c9N}y2F+{qjQjYDA3tN!;z&gTNO!Q{GaLqP-r5P(3w3Gfsn-|a)`AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX3=0y{7H%}R|j7oh1Ig9<_b0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00gQ)p#O9?Z4yOuj~z5{%`K~t9a%sC0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00I!GIe|+~A3np5&;0n94>V| { - //1.模型准备 let syscontext = globalThis.context; syscontext.resourceManager.getRawFileContent(this.inputName).then((buffer) => { this.inputBuffer = buffer; @@ -57,20 +52,24 @@ build() { }).catch(error => { console.error('Failed to get buffer, error code: ${error.code},message:${error.message}.'); }) - //2.创建上下文 + + // 1.创建上下文 let context: mindSporeLite.Context = {}; context.target = ['cpu']; context.cpu = {} context.cpu.threadNum = 1; context.cpu.threadAffinityMode = 0; context.cpu.precisionMode = 'enforce_fp32'; - //3.加载模型 + + // 2.加载模型 let modelFile = '/data/storage/el2/base/haps/entry/files/mnet.caffemodel.ms'; let msLiteModel = await mindSporeLite.loadModelFromFile(modelFile, context); - //4.加载数据 + + // 3.设置输入数据 const modelInputs = msLiteModel.getInputs(); modelInputs[0].setData(this.inputBuffer.buffer); - //5.执行推理并打印输出 + + // 4.执行推理并打印输出 console.log('=========MSLITE predict start=====') msLiteModel.predict(modelInputs).then((modelOutputs) => { let output0 = new Float32Array(modelOutputs[0].getData()); @@ -89,7 +88,7 @@ build() { ## 调测验证 -1. 在DevEco Studio 中连接rk3568开发板,点击Run entry,编译自己的hap,有如下显示: +1. 在DevEco Studio 中连接设备,点击Run entry,编译自己的hap,有如下显示: ```shell Launching com.example.myapptfjs @@ -98,12 +97,12 @@ build() { $ hdc shell aa start -a EntryAbility -b com.example.myapptfjs ``` -2. 使用hdc连接rk3568开发板,并将mnet.caffemodel.ms推送到设备中的沙盒目录。mnet_caffemodel_nhwc.bin在本地项目中的rawfile目录下。 +2. 使用hdc连接设备,并将mnet.caffemodel.ms推送到设备中的沙盒目录。mnet_caffemodel_nhwc.bin在本地项目中的rawfile目录下。 ```shell hdc -t 7001005458323933328a00bcdf423800 file send .\mnet.caffemodel.ms /data/app/el2/100/base/com.example.myapptfjs/haps/entry/files/ ``` -3. 在rk3568屏幕中点击Test_MSLiteModel_predict触发用例,在HiLog打印结果中得到如下结果: +3. 在设备屏幕点击Test_MSLiteModel_predict触发用例,在HiLog打印结果中得到如下结果: ```shell 08-27 23:25:50.278 31782-31782/? I C03d00/JSAPP: =========MSLITE predict start===== diff --git a/zh-cn/application-dev/ai/mindspore-guidelines-based-native.md b/zh-cn/application-dev/ai/mindspore-guidelines-based-native.md new file mode 100644 index 0000000000..ed288808fc --- /dev/null +++ b/zh-cn/application-dev/ai/mindspore-guidelines-based-native.md @@ -0,0 +1,245 @@ +# 使用MindSpore Lite Native API开发AI应用 + +## 使用场景 + +开发者可使用MindSpore Lite提供的Native API来部署AI算法,并提供高层接口供UI层调用,进行AI模型推理。典型场景如:AI套件SDK开发。 + +## 基本概念 + +- [N-API](../reference/native-lib/third_party_napi/napi.md):用于构建JS本地化组件的一套接口。可利用N-API,将C/C++开发的库封装成JS模块。 + +## 环境准备 + +- 安装DevEco Studio,要求版本 >= 3.1.0.500,并更新SDK到API 10或以上。 + +## 开发步骤 + +### 1. 新建Native工程 + +打开DevEco Studio,新建工程,依次点击 **File -> New -> Create Project** 创建 **Native C++** 模板工程。在创建出的工程 **entry/src/main/** 目录下会默认包含 **cpp/** 目录,可以在此目录放置C/C++代码,并提供JS API供UI调用。 + +### 2. 编写C++推理代码 + +假设开发者已准备好.ms格式模型。 + +在使用MindSpore Lite Native API进行开发前,需要先引用对应的头文件。 + +```c +#include +#include +#include +#include +``` + +(1). 读取模型文件。 + +```C++ +void *ReadModelFile(NativeResourceManager *nativeResourceManager, const std::string &modelName, size_t *modelSize) { + auto rawFile = OH_ResourceManager_OpenRawFile(nativeResourceManager, modelName.c_str()); + if (rawFile == nullptr) { + LOGE("Open model file failed"); + return nullptr; + } + long fileSize = OH_ResourceManager_GetRawFileSize(rawFile); + void *modelBuffer = malloc(fileSize); + if (modelBuffer == nullptr) { + LOGE("Get model file size failed"); + } + int ret = OH_ResourceManager_ReadRawFile(rawFile, modelBuffer, fileSize); + if (ret == 0) { + LOGI("Read model file failed"); + OH_ResourceManager_CloseRawFile(rawFile); + return nullptr; + } + OH_ResourceManager_CloseRawFile(rawFile); + *modelSize = fileSize; + return modelBuffer; +} +``` + +(2). 创建上下文,设置线程数、设备类型等参数,并加载模型。 + +```c++ +OH_AI_ModelHandle CreateMSLiteModel(void *modelBuffer, size_t modelSize) { + // 创建上下文 + auto context = OH_AI_ContextCreate(); + if (context == nullptr) { + DestroyModelBuffer(&modelBuffer); + LOGE("Create MSLite context failed.\n"); + return nullptr; + } + auto cpu_device_info = OH_AI_DeviceInfoCreate(OH_AI_DEVICETYPE_CPU); + OH_AI_ContextAddDeviceInfo(context, cpu_device_info); + + // 加载.ms模型文件 + auto model = OH_AI_ModelCreate(); + if (model == nullptr) { + DestroyModelBuffer(&modelBuffer); + LOGE("Allocate MSLite Model failed.\n"); + return nullptr; + } + + auto build_ret = OH_AI_ModelBuild(model, modelBuffer, modelSize, OH_AI_MODELTYPE_MINDIR, context); + DestroyModelBuffer(&modelBuffer); + if (build_ret != OH_AI_STATUS_SUCCESS) { + OH_AI_ModelDestroy(&model); + LOGE("Build MSLite model failed.\n"); + return nullptr; + } + LOGI("Build MSLite model success.\n"); + return model; +} +``` + +(3). 设置模型输入数据,执行模型推理并获取输出数据。 + +```js +void RunMSLiteModel(OH_AI_ModelHandle model) { + // 设置模型输入数据 + auto inputs = OH_AI_ModelGetInputs(model); + FillInputTensors(inputs); + + auto outputs = OH_AI_ModelGetOutputs(model); + + // 执行推理并打印输出 + auto predict_ret = OH_AI_ModelPredict(model, inputs, &outputs, nullptr, nullptr); + if (predict_ret != OH_AI_STATUS_SUCCESS) { + OH_AI_ModelDestroy(&model); + LOGE("Predict MSLite model error.\n"); + return; + } + LOGI("Run MSLite model success.\n"); + + LOGI("Get model outputs:\n"); + for (size_t i = 0; i < outputs.handle_num; i++) { + auto tensor = outputs.handle_list[i]; + LOGI("- Tensor %{public}d name is: %{public}s.\n", static_cast(i), OH_AI_TensorGetName(tensor)); + LOGI("- Tensor %{public}d size is: %{public}d.\n", static_cast(i), (int)OH_AI_TensorGetDataSize(tensor)); + auto out_data = reinterpret_cast(OH_AI_TensorGetData(tensor)); + std::cout << "Output data is:"; + for (int i = 0; (i < OH_AI_TensorGetElementNum(tensor)) && (i <= kNumPrintOfOutData); i++) { + std::cout << out_data[i] << " "; + } + std::cout << std::endl; + } + OH_AI_ModelDestroy(&model); +} +``` + + +(4). 调用以上3个方法,实现完整的模型推理流程。 + +```C++ +static napi_value RunDemo(napi_env env, napi_callback_info info) +{ + LOGI("Enter runDemo()"); + GET_PARAMS(env, info, 2); + napi_value error_ret; + napi_create_int32(env, -1, &error_ret); + + const std::string modelName = "ml_headpose.ms"; + size_t modelSize; + auto resourcesManager = OH_ResourceManager_InitNativeResourceManager(env, argv[1]); + auto modelBuffer = ReadModelFile(resourcesManager, modelName, &modelSize); + if (modelBuffer == nullptr) { + LOGE("Read model failed"); + return error_ret; + } + LOGI("Read model file success"); + + auto model = CreateMSLiteModel(modelBuffer, modelSize); + if (model == nullptr) { + OH_AI_ModelDestroy(&model); + LOGE("MSLiteFwk Build model failed.\n"); + return error_ret; + } + + RunMSLiteModel(model); + + napi_value success_ret; + napi_create_int32(env, 0, &success_ret); + + LOGI("Exit runDemo()"); + return success_ret; +} +``` + +(5). 编写CMake脚本,链接MindSpore Lite动态库`libmindspore_lite_ndk.so`。 + +```cmake +cmake_minimum_required(VERSION 3.4.1) +project(OHOSMSLiteNapi) + +set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +include_directories(${NATIVERENDER_ROOT_PATH} + ${NATIVERENDER_ROOT_PATH}/include) + +add_library(mslite_napi SHARED mslite_napi.cpp) +target_link_libraries(mslite_napi PUBLIC mindspore_lite_ndk) # 链接MindSpore Lite动态库。 +target_link_libraries(mslite_napi PUBLIC hilog_ndk.z) +target_link_libraries(mslite_napi PUBLIC rawfile.z) +target_link_libraries(mslite_napi PUBLIC ace_napi.z) +``` + + +### 3. 使用N-API将C++动态库封装成JS模块 + + +在 **entry/src/main/cpp/types/** 新建 **libmslite_api/** 子目录,并在子目录中创建 **index.d.ts**,内容如下: + +```js +export const runDemo: (a:String, b:Object) => number; +``` + +以上代码用于定义JS接口`runDemo()` 。 + +另外,新增 **oh-package.json5** 文件,将API与so相关联,成为一个完整的JS模块: + +```json +{ + "name": "libmslite_napi.so", + "types": "./index.d.ts" +} +``` + +### 4. 在UI代码中调用封装的MindSpore模块 + +在 **entry/src/ets/MainAbility/pages/index.ets** 中,定义`onClick()`事件,并在事件回调中调用封装的`runDemo()`接口。 + +```js +import msliteNapi from 'libmslite_napi.so' // 导入msliteNapi模块。 + +...省略... + +// 点击UI中的文本,触发此事件。 +.onClick(() => { + resManager.getResourceManager().then(mgr => { + hilog.info(0x0000, TAG, '*** Start MSLite Demo ***'); + let ret = 0; + ret = msliteNapi.runDemo("", mgr); // 调用runDemo(),执行AI模型推理。 + if (ret == -1) { + hilog.info(0x0000, TAG, 'Error when running MSLite Demo!'); + } + hilog.info(0x0000, TAG, '*** Finished MSLite Demo ***'); + }) +}) +``` + +## 调测验证 + +在DevEco Studio 中连接设备,点击Run entry运行,应用进程有如下日志: + +```text +08-08 16:55:33.766 1513-1529/com.mslite.native_demo I A00000/MSLiteNativeDemo: *** Start MSLite Demo *** +08-08 16:55:33.766 1513-1529/com.mslite.native_demo I A00000/[MSLiteNapi]: Enter runDemo() +08-08 16:55:33.772 1513-1529/com.mslite.native_demo I A00000/[MSLiteNapi]: Read model file success +08-08 16:55:33.799 1513-1529/com.mslite.native_demo I A00000/[MSLiteNapi]: Build MSLite model success. +08-08 16:55:33.818 1513-1529/com.mslite.native_demo I A00000/[MSLiteNapi]: Run MSLite model success. +08-08 16:55:33.818 1513-1529/com.mslite.native_demo I A00000/[MSLiteNapi]: Get model outputs: +08-08 16:55:33.818 1513-1529/com.mslite.native_demo I A00000/[MSLiteNapi]: - Tensor 0 name is: output_node_0. +08-08 16:55:33.818 1513-1529/com.mslite.native_demo I A00000/[MSLiteNapi]: - Tensor 0 size is: 12. +08-08 16:55:33.826 1513-1529/com.mslite.native_demo I A00000/[MSLiteNapi]: Exit runDemo() +08-08 16:55:33.827 1513-1529/com.mslite.native_demo I A00000/MSLiteNativeDemo: *** Finished MSLite Demo *** +``` + diff --git a/zh-cn/application-dev/website.md b/zh-cn/application-dev/website.md index ee2def7baf..dc96964b17 100644 --- a/zh-cn/application-dev/website.md +++ b/zh-cn/application-dev/website.md @@ -503,7 +503,9 @@ - [Hap包签名工具指导](security/hapsigntool-guidelines.md) - [HarmonyAppProvision配置文件](security/app-provision-structure.md) - AI - - [使用MindSpore Lite引擎进行模型推理](ai/mindspore-lite-js-guidelines.md) + - [AI开发概述](./ai/ai-overview.md) + - [使用MindSpore Lite JS API开发AI应用](./ai/mindspore-guidelines-based-js.md) + - [使用MindSpore Lite Native API开发AI应用](./ai/mindspore-guidelines-based-native.md) - 网络与连接 - 网络管理 - [网络管理开发概述](connectivity/net-mgmt-overview.md) -- GitLab