From 74c85279c3e3d3bd46f75230bfee02f172fb4662 Mon Sep 17 00:00:00 2001 From: Yanzhan Yang Date: Mon, 1 Jul 2019 11:03:40 +0800 Subject: [PATCH] setup paddle web (#1715) --- web/.editorconfig | 9 + web/.gitignore | 78 ++ web/.npmrc | 1 + web/README.md | 16 + web/ci.yml | 19 + web/package.json | 35 + web/scripts/build.sh | 8 + web/src/banana.jpeg | Bin 0 -> 30262 bytes web/src/executor/executor.es6 | 103 ++ web/src/executor/loader.es6 | 403 +++++++ web/src/factory/fshader/factory.es6 | 70 ++ web/src/factory/fshader/ops.es6 | 120 ++ web/src/feed/ImageFeed.es6 | 226 ++++ web/src/feed/io.es6 | 853 +++++++++++++ web/src/gpu/gpu.es6 | 376 ++++++ web/src/index.es6 | 20 + web/src/index.html | 13 + web/src/ops/dummy.js | 12 + web/src/ops/feed.js | 15 + web/src/ops/fetch.js | 12 + web/src/package.json | 11 + web/src/runtime/runtime.es6 | 69 ++ web/src/shader/atom/common_func.es6 | 35 + web/src/shader/atom/common_params.es6 | 36 + .../atom/getArrayIndexFromTensorPos.es6 | 20 + .../atom/getArrayIndexFromTexturePos.es6 | 18 + web/src/shader/atom/getOutputTensorPos.es6 | 16 + .../shader/atom/getPixelsFromTexturePos.es6 | 11 + .../atom/getRangePowSumFromArrayIndex.es6 | 15 + .../shader/atom/getRangeSumFromArrayIndex.es6 | 15 + .../atom/getTensorPosFromArrayIndex.es6 | 18 + .../atom/getTexturePosFromArrayIndex.es6 | 25 + web/src/shader/atom/getValueFromTensorPos.es6 | 19 + .../shader/atom/getValueFromTexturePos.es6 | 22 + web/src/shader/atom/moveTexture2PosToReal.es6 | 19 + web/src/shader/atom/prefix.es6 | 14 + web/src/shader/atom/prelu.es6 | 15 + web/src/shader/atom/scale.es6 | 11 + web/src/shader/atom/sigmoid.es6 | 12 + web/src/shader/atom/softmax.es6 | 14 + web/src/shader/atom/suffix.es6 | 17 + web/src/shader/atom/type_ivec56.es6 | 23 + web/src/shader/batchnorm/conf.es6 | 57 + web/src/shader/batchnorm/main.es6 | 18 + web/src/shader/batchnorm/params.es6 | 24 + web/src/shader/conv2d/conf.es6 | 85 ++ web/src/shader/conv2d/main.es6 | 48 + web/src/shader/conv2d/params.es6 | 45 + web/src/shader/dynamic/conf.es6 | 34 + web/src/shader/dynamic/main.es6 | 14 + web/src/shader/dynamic/params.es6 | 9 + web/src/shader/elementwise_add/conf.es6 | 56 + web/src/shader/elementwise_add/main.es6 | 17 + web/src/shader/elementwise_add/params.es6 | 20 + web/src/shader/mul/conf.es6 | 56 + web/src/shader/mul/main.es6 | 18 + web/src/shader/mul/params.es6 | 27 + web/src/shader/pool2d/conf.es6 | 47 + web/src/shader/pool2d/main.es6 | 49 + web/src/shader/pool2d/params.es6 | 30 + web/src/shader/softmax/conf.es6 | 28 + web/src/shader/softmax/main.es6 | 55 + web/src/shader/softmax/params.es6 | 15 + web/src/shader/v_shader.es6 | 15 + web/src/utils/opData.es6 | 312 +++++ web/src/utils/tensor.es6 | 139 +++ web/src/utils/utils.es6 | 127 ++ web/test/testUtils/diff.js | 1055 +++++++++++++++++ web/test/testUtils/testUtils.es6 | 124 ++ web/test/unitTest.es6 | 62 + web/test/unitTest.html | 115 ++ web/test/units/units.es6 | 168 +++ web/tools/toBinaryFile.py | 111 ++ 73 files changed, 5824 insertions(+) create mode 100644 web/.editorconfig create mode 100644 web/.gitignore create mode 100644 web/.npmrc create mode 100644 web/README.md create mode 100644 web/ci.yml create mode 100644 web/package.json create mode 100644 web/scripts/build.sh create mode 100644 web/src/banana.jpeg create mode 100644 web/src/executor/executor.es6 create mode 100644 web/src/executor/loader.es6 create mode 100644 web/src/factory/fshader/factory.es6 create mode 100644 web/src/factory/fshader/ops.es6 create mode 100644 web/src/feed/ImageFeed.es6 create mode 100644 web/src/feed/io.es6 create mode 100644 web/src/gpu/gpu.es6 create mode 100644 web/src/index.es6 create mode 100644 web/src/index.html create mode 100644 web/src/ops/dummy.js create mode 100644 web/src/ops/feed.js create mode 100644 web/src/ops/fetch.js create mode 100644 web/src/package.json create mode 100644 web/src/runtime/runtime.es6 create mode 100644 web/src/shader/atom/common_func.es6 create mode 100644 web/src/shader/atom/common_params.es6 create mode 100644 web/src/shader/atom/getArrayIndexFromTensorPos.es6 create mode 100644 web/src/shader/atom/getArrayIndexFromTexturePos.es6 create mode 100644 web/src/shader/atom/getOutputTensorPos.es6 create mode 100644 web/src/shader/atom/getPixelsFromTexturePos.es6 create mode 100644 web/src/shader/atom/getRangePowSumFromArrayIndex.es6 create mode 100644 web/src/shader/atom/getRangeSumFromArrayIndex.es6 create mode 100644 web/src/shader/atom/getTensorPosFromArrayIndex.es6 create mode 100644 web/src/shader/atom/getTexturePosFromArrayIndex.es6 create mode 100644 web/src/shader/atom/getValueFromTensorPos.es6 create mode 100644 web/src/shader/atom/getValueFromTexturePos.es6 create mode 100644 web/src/shader/atom/moveTexture2PosToReal.es6 create mode 100644 web/src/shader/atom/prefix.es6 create mode 100644 web/src/shader/atom/prelu.es6 create mode 100644 web/src/shader/atom/scale.es6 create mode 100644 web/src/shader/atom/sigmoid.es6 create mode 100644 web/src/shader/atom/softmax.es6 create mode 100644 web/src/shader/atom/suffix.es6 create mode 100644 web/src/shader/atom/type_ivec56.es6 create mode 100644 web/src/shader/batchnorm/conf.es6 create mode 100644 web/src/shader/batchnorm/main.es6 create mode 100644 web/src/shader/batchnorm/params.es6 create mode 100644 web/src/shader/conv2d/conf.es6 create mode 100644 web/src/shader/conv2d/main.es6 create mode 100644 web/src/shader/conv2d/params.es6 create mode 100644 web/src/shader/dynamic/conf.es6 create mode 100644 web/src/shader/dynamic/main.es6 create mode 100644 web/src/shader/dynamic/params.es6 create mode 100644 web/src/shader/elementwise_add/conf.es6 create mode 100644 web/src/shader/elementwise_add/main.es6 create mode 100644 web/src/shader/elementwise_add/params.es6 create mode 100644 web/src/shader/mul/conf.es6 create mode 100644 web/src/shader/mul/main.es6 create mode 100644 web/src/shader/mul/params.es6 create mode 100644 web/src/shader/pool2d/conf.es6 create mode 100644 web/src/shader/pool2d/main.es6 create mode 100644 web/src/shader/pool2d/params.es6 create mode 100644 web/src/shader/softmax/conf.es6 create mode 100644 web/src/shader/softmax/main.es6 create mode 100644 web/src/shader/softmax/params.es6 create mode 100644 web/src/shader/v_shader.es6 create mode 100644 web/src/utils/opData.es6 create mode 100644 web/src/utils/tensor.es6 create mode 100644 web/src/utils/utils.es6 create mode 100644 web/test/testUtils/diff.js create mode 100644 web/test/testUtils/testUtils.es6 create mode 100644 web/test/unitTest.es6 create mode 100644 web/test/unitTest.html create mode 100644 web/test/units/units.es6 create mode 100644 web/tools/toBinaryFile.py diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100644 index 0000000000..e291365a9d --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000000..7a40821bff --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,78 @@ +# Referenced from https://github.com/github/gitignore/blob/master/Node.gitignore + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +# other stuff +.DS_Store +Thumbs.db + +# IDE configurations +.idea +.vscode + +# build assets +/output +/dist +/dll +.cache +package-lock.json diff --git a/web/.npmrc b/web/.npmrc new file mode 100644 index 0000000000..b291ce91ef --- /dev/null +++ b/web/.npmrc @@ -0,0 +1 @@ +registry=http://registry.npm.baidu-int.com diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000000..4922135af0 --- /dev/null +++ b/web/README.md @@ -0,0 +1,16 @@ +# paddle-web-demo 前端机器学习框架 + +[icode地址](http://icode.baidu.com/repos/baidu/mms/paddle-web-demo/tree/master) + +## get start + +## 编译编译 + +本地环境已安装node + +```bash +# 安装编译依赖 +npm i +# 本地编译部署 +npm run server +``` diff --git a/web/ci.yml b/web/ci.yml new file mode 100644 index 0000000000..a78e80a1a5 --- /dev/null +++ b/web/ci.yml @@ -0,0 +1,19 @@ +Global: + tool: build_submitter + +Default: + profile: [buildProduction] + +Profiles: + + - profile: + name: buildProduction + env: cmc_standard + command: export NODE_ENV=production && sh scripts/build.sh + release: true + + - profile: + name: buildDevelopment + env: cmc_standard + command: export NODE_ENV=development && sh scripts/build.sh + release: true diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000000..42d6b11941 --- /dev/null +++ b/web/package.json @@ -0,0 +1,35 @@ +{ + "name": "paddle-web-demo", + "version": "1.0.0", + "description": "paddle", + "main": "index.js", + "scripts": { + "server": "parcel ./src/index.html", + "testDemo": "parcel ./demo/index.html", + "unit": "parcel ./test/unitTest.html", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "ssh://yangmingming@icode.baidu.com:8235/baidu/mms/paddle-web-demo" + }, + "devDependencies": { + "babel-core": "^6.26.3", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-decorators-legacy": "^1.3.5", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-polyfill": "^6.26.0", + "babel-preset-env": "^1.7.0", + "babel-preset-react": "^6.24.1", + "babel-preset-stage-0": "^6.24.1", + "babel-runtime": "^6.26.0", + "parcel-bundler": "^1.10.3", + "axios": "^0.17.1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "js-file-download": "^0.4.5" + } +} diff --git a/web/scripts/build.sh b/web/scripts/build.sh new file mode 100644 index 0000000000..1b6bb68b12 --- /dev/null +++ b/web/scripts/build.sh @@ -0,0 +1,8 @@ +export PATH=$NODEJS_BIN_LATEST:$PATH + +echo "node: $(node -v)" +echo "npm: v$(npm -v)" + +npm install + +npm run build diff --git a/web/src/banana.jpeg b/web/src/banana.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..36db8e726d55f53f6767ff114208c5184f7da907 GIT binary patch literal 30262 zcmd?R2Ut_@nlBo<(o3Rrl^zhJSCM9b00BZm3q_hpXwnf71t~(LH!&bm5=iK1=%6T4 z1%wcK5$RP_K8;yPrL?=gd9#-r3JtdA{V!YVc;g_4j+%I+;FM0&tobLJa{l zGys4x^#wS24@iOBLVE%LFc?4@005iI*=_4`BFf4gi?Y2>g$^6^+!t z?n4Uz+(!fG|8*Y}_4Cg{J@cQV|MiounC5?;v6%M1?@hN^O!wdCw4;9xJJ|!MTYB9L zxf$$rGvK_EoIF5X9}1)Yb9UUnbJYKWf#$E}ufwQsG_-W|42(>tPBXJmcWC4U(9+P+(bCf~F#P#5 zG||-U0D3M4?h6Wfj685xCXqm(Vr*u~DN+4~mmsULk77!0LHABGpE=9R$1g4+DFv2R zR#8<`*U&V$3NbV?hMFLd);6|w_9%Cc>z+5f(B8ozp<&^-A|m7B6B6$yC1bL(b8_<@ z=0C!gmf`T_6$E1C(`Tf{rskH`wpZP+dwTo&2L{RGl!?iy>6zL0%PXsE>l>R}+n+uk z93Fi+{`&3vAGxT%=f8+Weg7B9{zfh?s$8`6^mO!0f8?T}4gVuJ7d^uT1x9W?IFoB2 zkBDOIDWHC4NyE$2qDodDL2f}~%xA=u-;00xBidh*{l^5m_n(sNe+c$J<(dN+0NAM4 zh4~a43o{ElI~xbjc@PgbH;<&C@Y(ZnUH!H0O9Sv2P=(qqnfWvL6 z(irCdLx;}E=>OsJkNuO$kLy1wW+=@Toy_~+rnEXI?LMCVYcf_Ov#$F65ZENc=Z!6b0&kc? zB#dh0yoX+4q>5?(9}JF@@qe1@sqx~hj|G0Z)S=Kqq3h-c_Vtd>H=+{UY~@zho;^7M zIEB98Eb;sD>|TI%M%^;UhNI=eU&WKJ%lip6%zA z(g*EcY@$`8p|t%M*L4)r7qTML4fY)-HuAz#Su>xWTQoG_i2M-pqp^bo&GDO8G%Bef zns2`iPHWWCs(FxOL5{rmW6(>dmXXa_Y@1SzyCIrR-k#Id;m2}Q%?aQx zbBUv7VPTpbon7-OVpz>gqULe!z5aG1U{Y8LE%eLMoro%{HnT-+yQKUxIfc|Nt|xH# zDow|e$BL%hwm~T_A0yM96RUg8o;_lD&xdhs&)qrt>0Lt+S^TUW6LZ>nffB|1U|}<) zu|dmxu8f0JtGiKg0;qf}cSiqH)|~De_F9gk!^($T{RwFYqz$d;Xzg@j#VZ!Y1VxBF|lIX)c|@bCust0TU>^vJh) z$`R_0f;=yO^zrT-flS{7gYB!7#;Y%tylAw4?_!;JC)eW%KB6eMYM`Pa6UtI34#QwNFxF#e7(mYpivJX90K^yjJqcj|1=t<*a78u zT;e3BamwxvXzeq{Jh)HaXR20|$HA0~Z!zJng0qG4byt_WvHdDN7*x3*^iauvls9~( zNa3qZ`4f?+b@a3bv;b}0{u99S7mwjNca}Da%V+pf3mw#W1Y5NXO~9+!yMbSALTV>c z$JfHu;-n1IloA1|Z%an*a@mN7w5IY=SnH*On>TOmMr;470y_G(aPSmnAJfKAkD7fW zG|?{8?h#`k7gQnSjX!%PEeQ(ax=ia>yHNS_XHCMwPUhS9)`L*ivBcS|x;MNl0-c({ z=GjB{eM}6*yiIlVJKU889{8lF4Q}%a)ueUaW#*}$6=`|&^Tj*UJl`hcwTR8=sNXr1 zJaJ}wsCaN62(;;|?i)XItlR&Fo#t(`3jOJ)msfY)kMn=f()_y1+~`U2Xsqph{1~UE z9kP+*JT)Eyqq z%3i9o3tW>Z#mJCiN-;gsj)nu>d=U3YMddl;&+pTPO3Pd}>B>?d!O_x{@0msv zp4{|B&gP|k$$Ijvf26g4q5F;-an9kXs@UL<6F^vlRb1-wNPnYSgt+v69&ftNp8rOn z@Zto%d@qy6R(h3gs+mk^Y)x4@0es&&@0n0aix5csCZIldNKIJ0D-`RO$hjkV#wFJk z8Z2aO*<*4wv{S9qMkXHMOxJwP)CGxG;=?O#rrrv@F$ff>F3e6}(Qe(G9`ysHpSq+@YrMU^B0OE8pCEd@Z7}3Q#0I8W z#9`jhB{$|`{JmSuiye`_AJxc6J)bHm$)w2u+kB5p-?E>q*)4q>yE(L}1_kGcCJh42 zuh@vrodBksYMR(^7t8L&3Az+9@$T7WqE3~OLPf7cRz+q+@049GTAc!JOJg>B%i5Jx z31M?+={y~Uxv8VAVF&W#;>?<9H^ty{&CLhIq$9$MbQy{uW+c_fq%@ipmu;hRU}|0R zSd4dsBhW_usIIueO3Bu2-MgDmrfgICzgj}-oJ{=_=;|;vB?U-EVj@zVNT z&0;qu+cphHnka}Hnb4f|a~pE(W?^KCs3zP2V|K*5w~8MkZ-?x9-wH*B42@pC(;l~Km+{oHSh}9IG089=7Z>_$o~yR){DdmHFqy%i z`1J<=)z2IDOl}-sCmY98+>5K7^xHq~AT$(*$#e(oxoo(0$KHkS$a=L$*ZH=?4!#Cj zOE{%w7H)`w+8lZkc0c&rS*)39%TzaJKzTK4&zue?-1$8*?lq+~Dk|(V+%w+XkvN68 zB`hQJn&0k4(>u;zKO>}~E%0bjR^)?mdK;unP$NNZ^X12dHx_8|+*d8Po+aEg_|0@k zWXbpRdQ$mhq*>&iSa=A8wVQqglwIf0pLYWANiZepKD5-hzMp;^`P;3j&>D)SG|Kv4 zIX%cZc&o-(&c^x#;N2Rf)sssndXJrBgG>_1FFz&G`c6g7tS}h4v`uc^GVCT(OV!l-PGHo617+)cMEUyY^<<}(y6|)|L{J{(A zH}jD$hz198m1dNpSoVCYAo0AGonYt}A_piRNnGHY?JhaDfsJnb-ch=^a#~eUTVOu& zoc9sIE$1*VMb;+Gr0<9_-G(mKfQ6n>w|OzFe5f*%97uGxs5%{?}TO4g!p z(E&ez@zGD+;r-WO5c0S3jSWVfOUUE zeol$A)s~iz5)rUhg^~{abDvd=xD^J!tiAo~J%!W9aM5wk8#@XEF@<`DPm)iIFjg?} zR@aCuK~I4y3T@-3#ISstD%vrwPuAX1tX;Y%akhI7`o(t3N9}we0`D4UiK;vb^HpRC zZKKhnUs}xV=rHkAlfn;MPM62T#z;ZZZHjx>T?^9<9HZBBziD(CWFG2aZ44QG!a`K^E_4tMxPzb@i58<{j;CBFY3U z10Z_zuk2nj;+Z5!FqL5K1ZKUFMQOM(sW=!*YKs#reT>nws4`h?QpOM?eC0&C`U8S+ zp^UZ5`4F3iaNS2|oE1)3T=8jEBgKDcg*3(>7tuq z4*8V(NC#@VJF58asq24Dgl?Fxm~ku;+mQ;TktX53+o8cK2d7ZG( z6g%1a%gD;-p4)@9^a@A+6998vM7ZpHeCyJ=+$iQ4Nez`Hw}#J;SA@8xOfaZ^U5x?d zW$?1<70Sn$yGJ{3fo5LydQ>nfAy>AG*aZ8!6hy0MzeYE zE_01N_Dr}UctJbT)=(hL2Q$8|FjAp1;~<}PJ{oc>ztYcqhv>(hMk>=NR|0s+sb60D zZUeW`mn}KM=K(XBH4_a5j72X;MuU^Fg@TyPxTe>n;Io=hcFz&v85V=V$9HE4Z%wwv zlf|rRX%JIejz>oYvu}D$c)IVJ1i{*K@_(>or2&g%{MA^Cr(_ESfzw~ccWPTDPd;~(gJN7h+Q^`^m1eR}7LkOm>f z^vKHvzFo75?Fs1t+}FC*`yT$5KKPY;V6JFIMW-19Z&ky^)ekk;S$Bc&0nrfI!Xn4A zA~wJ6rYBy_^JR*L>c_RW=w5 zbefBI?)TVwtF+q|VgYNIvo<=0^?I*A(iLfA-E;UfOXSyV(XY2(t;0Msn-#>4HOiN! zQj>?~7s?tY%Uv>sX@HMiiK^jGOj&3GqJ?s-RIPi^U+q(sn2`nT34@o z=2;J5jn5A9p(4NcHR!0(%NsjO8JR0|)jR=o_`uwmby(fWZEJ8nd;jaQVWM_j=MojR zj8{NlU?1ph*uo4O=N>`M$kpZ($zQ#a90aWqIpR0GU1j|`BG;&;<}{BA&@v(m5Xm0w z1b>{dM+_Pczk>9jAw~O?j_(cmwBDC^MtT!U54f^5d=oP2!uC-4`a>Xz%VtcJ*CV7h+^Xq^_SozdsN$PnkdXXJ zO%knx+WFgC2bacwu9pV3jiP5B*>L;bwi&u{c}j=JH5SzD{iBSr6+ucxwY7%AjM?0D1c!=Yh@7$p{+(yc4-~&jl7h`2LH1QyjLbR|!2*HEIOu9f{Gc+T5(Md1(3g`4aP$+&7m)d5@G+`K~0kx-dck*66yu zm6ZVZO^xdN5gs~Pt#94Xv>bP90<4E^r&ZJV-|@&niLJ|GeLsw_#Eax1i~QA%MjG8c z)1Cli?}$VHqVkGxyq?alki8B>14afD+FI)sPX}x%1GZH2vh~P;y^863B*4MOFj_iz zHa1Xs7R`!YFAGdNl5t_3aCne3D(qudzIabSQZO6oUq=hJ-m(5r$HMbgc;bmzd=YLc zR8!l|&LR0hb{2ZFDbY4e?D)anDAJdWv6p|)7>Nzv6+g2u5P)^-tf+TyLdvZNMW<_kOGwM} zrx>MOCp!kS+FQ8$CaWb;i5GgkUolT+xp~Aj8iPv}zwS$7FAzN`~Oh`)?U=#`F4?e)>jY-?FQ65yKMzRTRfc0=L( z4I$}W+2`UnE!o1BBs-v$ZP{=LXfrSuR9=mKoI`S`@QYA;3T5zl&l@aVY;EO22}Jti!NTjw z>em+-iw?Btxqy4Ma%#B%lMT!jNL9!m*5a+O;(;yJZ#hx6#I(d%*OKV6%w_a@yEDb!9I)#iUU~7$yQt6N^Yid;g=sBYdnf_@ z6dHuhQ`FVO{n86`UR}WA$nh_QW8~J>l-?o+ND3u?82pOk3&E_wDYLm>y%%Qd0wv~i z2TvAY#GKFW|Q;@HbJVK4og`7<8+AYOM=CP*_Ae&`rQ;8 zt!s{I%=*6=^xum9Iu6nv4t%SQdkzYQ+|Qn$jY`ViH!l9=%|vmz)@FLn)!vn9WzR$u z)OUFq_Z;X||7rWpO`^t+%oyzvZ>IK^yWhNY4^jSumbpC$A?K)ee(8`g0mdNHhMVrX z=24V(v~F$Gi2vtS=$wcA`95=W7-Gw7?fFW(=!`BZj7-vj3Pc2abs6Qs@gB&Q<$Mdg%i{s}Q(fg;EuXAh8oEki z^v1F1j3ZHXy?x83;A|_^;A}II8nz29<=9}v=_&}`H7(G(OA*@t%}57f1gw004TW@lWabdK?Tgv1?T_PcIsuBEoNV?O4`HRQF1QtLi;4>O=sJ zwum8IUsci9Qo$T}uTUl=$%R3kSr`0@WSrLxoQ*chuwYDDbR>cyo>5f3U;tv z`Ne(h80LOeqfUpXb==6)Yqn@2gt^RbvQGB0Dgj%NjtVHp1k_G{aO-2{K*9AuPi?Qp zY(aImAX=NW!Z~?A&5Q`RV3KEn@7>5&?O+q`M&pFeNiV9y|G@jV&i>E$bN?w&w@l<; zdXvD43WXR&sg~1*-W^kx-Ny^Tn-P^k-eov=_6MK5a>ZQjRY&xQgQ!(Tz2K@1u=)5> z@5cw5rN!alzU?2b;HKEj7Fm{TRqgDxhqvTvMsJhrHY}T{&>^eEsEwq#(I(n3S|-iD z)99(Ho!yZqaiVlj;7J#Py;diY=i6ie%JNaQFxu(p+{Ds@KI?3f`+@RZ^w8b4jGx>2 zr~pvGkk7Hqba%teABK1Ob9y^l{8P?uN^^n(Ek8}HRmkV6%iUAX(s}18x%h|_wnT}E zLfpI2=(b^X(;?TR>8c+3CCp5e4r|6?;ymbNG?eJqLr0HzOO633PpIbC(xAi0W7t_` z(sV(hR1aU7%AFxPpj!^yNSY-XqhN?u8a99eNLHRH6cX(i6e2Or@)hwEOzJ*5mwR^f zA~e$&tp)%m>LDa*0-))oB}z1lB~h6>_KQEx6wi3ogZOf2kj}w7mX|jsiH2r)+JkwT z#j1heqD10=TIW@d;~G)ZDp`m~gv7A^=NpqH5zEc&0crf@t`b|2H1qmI12aZ%MB3v!LbrlJq>b47 zx;aP}qRyXI)Yx&oCdn%U(N!*M%mf-^2v+5-VD{4imD-3WKw>l_v#ls0<)?@QLDZYW z>iqAMdF9hhuG_l5d{jk;BUj$D`IrYasEh)@;L~)Sh?eJe14yS~yNNpUtd2(}vh5-tZmn0n ziJ;es?u}+r$yFJC-EnqSo4YEh@9szkcBT!5Kt1N{zGly_xZRz}&GP| z#vf}%aWZM`=@|F8`RhW2F>o^T zY!wY`v8DgA)x;rvvH}z)CY%^!7&)44;c^bbCrnzHsC3b!dKo6HW=w_hI5zASVSrGy zlrT)2@ieVG|2llJJ_xgIU2BSwe&$FZIPVKd3+xe1*dB({z!atS?f1hhOcH8O?<3HP zxk_e3#i0b}rLgJ)1#Ou8z&?`L+-h?A?C`)50TmQAp}#SSy>^8(p+LadEAcFrMMQ76 zMy52>06EDQ`J%T~v#|A!&|}fNwF-#Zf`G3J3Bo@oepH2>g{a*`z+^`>hP``mgmojg z=$0zDz22!`^WM@d^e0hyn*nH9?SF+R+Lqi8nP3k(p1*go#ve z374lZID;Rzo#cH0QtnynQfiv$3K zq%9R3$$n3DS&;;AF`}!2!P8Vs2#ltwpm9QP8UU0mXk)-jHW?4*6MI1Cx8SkE#xmtX zDbpm2RE<_uF{Lk?(JP;%g0JsKi1LamLVM`R;ehbV5Yq ziEhjTd4mFrQm6s z;6j&H+-_pFfB&-#&4~!P_v!b#ugp&(r?Q6be3{rSEja;b)Cq7E%*~pOb~5WFHHWDni(~Pb&mOvCJ+I)#KZ0elLMzm&T76&{LvTz) zkNAyX(^T3VW;HASFXjAcw2zyxY;r5+(yQ%hU590~DP!5zgFOVdR1&w52v#tA^b@4I z8$MkeYq+wBREbyAM+K`J4(M8?Vv(#wO2Ht&V9a1Cla?5HiDpmQ$ z<#5>O-4#kV!`Sb0KO-B%(oN*S&I`hYaF7AULTDGo${#C zBo2<*x~J%C7HmPA4Seq)?&iR3U<#AO_)QzTu2QU28KBmeJU$nP3+JChkXxnIJkcOx z&fdDC@k)lpIlUeW-pWx-E9ZL0VA}3ZWA20c6o7X>h}B>XBwss!*$)^DTSxk8^jvtUA}7aW@egf`zStOo(bF(VX)ZwPmlY)+Z2v~p#FgMdQ-k~ zG)M50SFJMUgT03<;bBnjPd253m^78HDtS3O+oGc|Rsa5`G5~W1HJ!yAZpnxRHK|)+ z^h{(*#S~s)7#JHjFM!J*!&Eq2OA?>>{eHFyuE0aRHQc{iyXI~eXCK@9W2EXrHvT@k z@$a;KgB7*C`j3Es_)pNxv_`g`p@3Rbs*fAV`v476YK)?B zA-5|a&@lRkDrwlkc}UB>zCg(*2fNari6FLyU$c2amBY7x*=xX>ZZ^fjBpzeY>kYaR zUQAd8LvYLzdWqszByHD6H+-2tg+D+=_7oL0GGx2VuRB1qWzLw-4t^a=C0|E}YoJ=5 zIiWaKg*cOH<{wigs8ZhdrciUx(_}Sn49X-7(8G(zi#(BX^aEN(k{e@8q)M>{G+TyX z-7>-P$yY5j)LCg-YyfSF6-Em5nu=lfX5l;amxFfrpPqn98q=kP zRG?hOtJIFfuVuwQv6-v~Q!dZI4m{16B<^*6KG?B-YtrENn-C2PamfljP5hWgt+Bqa z>i?ZPX*m8JCWb!{xqtJ_yq^^d7Q4Kx6~bu5Y{9HVnWQI^$j_Q>HnyDoT`^ze*V1+u z6Rz9{uWx_T;3<|(SubQWjAO)sK96T-w};<|gnHd)M(G*)$Sd5wDwzMk_>C|-bNVtp z&yz0ZzGlvdxf08g^r2vCv;Tzi9Dbtx>aXwT4Caoi!MhTM2g&R+a z3694p>^t-}6?v#Xh;L%oY-LwfX}rIecv0W4ZL@`2*utw&80{ zd03X*GeK%JU&q@hG_5-p+<^|gr5@@HuU$SFd$DSfZ#3f(J|W5yXua}!ZHS6m_^>SJ zUWf^_O!-l1wp;x+SP24B*r^<)qJa9DP8JMhWt7b4`ptqK*Iv(viHp(0tHpF>knM5IB;)b93 zlUHk_Jw6O0eJ^)j-8Vx^pUs%)WxyGKMw+c2t|yJHZK`W;&)aO~FXerhFdv)u!$Aba zQeM#26EoGjKvEARwJWbXhJZ8&Hcj2R&npx)s_Uh=CD3DYWKs8&viXyk{7E0dx_DYr z3itGIJ?%p(p#>_vIXs65;8;1Qa67&H8$Adr)|8A0uqc^0Uidb)pM%&7B5W!B8)!qq8| zNn(Av4Oidj>OHNs_DU**+-j7~`;u>z$%{8zgE3pR?mP|yJnTZowbCr<7xI`cAEX=g z!JojLRndCGV9{n4netTlj%f>X)rORix9a0`lW!*g&p9oigB+<+i((PPfRKw?$C^h? zNNc}=JDIWtQ499fOH&oT7=NiA@8UcHE{k8GNE$+a&KpK5=7J{ogoqn1KUw!c>Lpwv zKl4hn(nUji^iMNBZWl?s*L{FRrSRWU#@3^{U*h}A{eh2&^~p39tltqd{f8A`^;NPL zjGNAaF<-%a%W~ixR*9OJDaV8zq)9DCqe(d*dOVB;m$M^|2m|;X*ue9$r^37&8WpG6 zjL0!w2p><0O!=HN(%w%BAD&NW2ZAyUlLXo=VAYU%1PPK|s+wfZ!_f1$Dc6a5#L$If zb}J`JD?#tDg0aT>&g$o^4|v8B4&A_gnC*jYLEIAAvus{!_*$d>nwZ2xl{5R1mXrw( zIV&@A9Q*^&zw%4Jjo7e2r8eaz@K>d)8;n8+-v_q$2v0C1gS~|gBNEg7IM+Pl5A;;H zvhqDZ1$7{bH)|o*^TW~UywZs19kAP_2JZq%v@SvTT4e0nF#&}d7_?GJ;g+PDN>(vC zdRsCFk-`RqE?Pt;P&b>k_9{`GEL(L}ummzPoZ6cbM5ua7a>!W2`Bk#;*pxoozofIf zUM+^^e~;ww_WwM8eqrBoBU;2X`hLKJX&S4mYQd(0w{I`Npn|tuK4FS%DT!ntD|=2E zHo`Ft3{r3qzwRbyTor3UOJy+EYs5UJsofzc8B@E0Wa=Gn^82P3K!sb45ut#)kl{S= zJxWYM>aT*|NS_x0t>8xk3+OK>_i8K`QUQgH!o5=P1ts{vd59SKS&WQk#O>qRid7*F zVyy<{dD56n?(pmJ#i#kFl8x9M;)fNxC&;n6fd<2s2A&%O|OjgN24+VG-|_+z!%0? z2g^QHl~&HCG+ZgPZu=4dZ`3+gARL{+NY#$ZQ$zjV-rqv^@7xo>nSxolZSa1N{D2}W zQnoKMO5D{uJU7`cN0|hqa0dR|mSj+Id#}b69GBcpkd90r#4$Er3Q@%@L#rwIt6Erv zUV+wg*S7ZUYXoHcuAl$Th50CBnX94VPWf{iLrV@dlj(A=2@Wc7Vg&0_g-T3}1HubK zOG~O!3}9lIP-Uz)IPGP}cjllbZ%x-LCxBdb8V|c+iJ#WNqL(_=MubiP=H3y!g{D_r zg<$H?J{4p?o75ez03OKe0P`s1`-B@21%^hz_gXYXi;e7$`vaoc&Uca52{nB}+i-bN zm4fmycG)v8E9k7_b3PuiU$haIxaX}sgJ(my0OL&9E-2HPF~wm~F3P)`;mQNLOXgCqozSe<)Vf z8kdju+L%nwx93>cH*NV&aeJsd?nAKWzNvguMR;2cKJ?>3PIjokwl-yf0ye%)crt{$ zWO;^)xieP{r0UjRTP#I4SQmN6uD*@L!#dws%96ekgnf@Ihc9*Nf8OZy;Wxybk-gkK zS&Z)7ZtwT@@?9@nO^utVy3Qcf4QUm4b3|MwVg0X3Q8D(3ohYfAZ*;N?1vU20fwA_3 zm9z93weg&&9uDc#l2yXx<#e@UEtEHB_B)5oIN1R^RwZEXp~6ANnKj>b5PS?`m&TwM zYa_2>usEL(cv zd#nt&V@;$=&V$fj|4>mkfd=ln_zJg-NsQ{%c_tt^n4E+4lh`XJpH+@u&Zwu&w}^a& zckoKg?U5)8>nbS_^0~4ed_m#rt=}Tr+A^46zZrh1gx9@6?kPVz-yY(w0%%g;g06;u z<#HA~6DO`x8_-$cM=3f?fIu3<^_CBsVsEUA)ysSHLE@3>k+-Xyh9qU`sVE+UY?!#a zs;tl!7=q#?wc<*NUKhY@vKI&$qurAkqmg4N8BCFbn(ZQQRNpk|)TwBz7*5au8xNJs z-q5GnYc=&z@dU!Xznk;@`;R&hm@T~}n&CRC_TyD~86zGyNjOuWSb%Qwo1~}S?uSmr z&7Aw@#zd|gv)p{S&piQ^D|a2M(P>-R?8Sy?`SHNp>M#Nw^4sP}b9u!+)**9KQ5LXl#4)O8gWQqTfY!V)?#c@JLf= zF-YjtcWg4r;oS$~PoK)t`!TpL{5=#6(UQgHk5VUqSj)>=x8~1KJ9eg=p!#pNLOE)Y z8rMIP;@4`anSWcO_a!#N4rrigx*3hbq5BT%qPTdQh#j_$i1{tlB=e&=^h&39{GxEH zp)7Fl1OSh7vIORTC+D%}wAqO!5!wnJ+x&KQ?o8x!d|;hGtY7G^0w0Qk-no_Cg7*rG z-*i*@xszw_>pV2$E)MvcdDpnB~`G?(!X_jJ-#;)=)28S68c10n~{=Kq$y)v3b%U{{h6 zoroX4!O}2)hx93g1H_(vRz`09F}7UJmQU9F?5Vpc$TnZcY7&2;owc6k-P?%QmECa$ zaiHX9dMal=?U?XCl(M_KVww$`ms)__>)sqz3gshGJ%{o<)vdgzt~sDOmmcK-YnO)e z=PgksipynL*PRb30xXIzGmwTZ(q;3pi&dTdzmI(QW%G)iBf9pji|$6^#rw6{Y@!dYWx!kzxP`4ywB48)`P=Ua<(Eg;i!myv_luCS6$h`}18&Cqgt}m_U z7p6aZk8WisLQIk@7AyAX;xIiGvK~W)`>h-~LsV#RcQnU6GOfdT?Ds_% zxEr!W-d@ysFdY?%U2D%)Nilj8;OUlm?%~%b-=W4QvO-GMb8FU00EZ!1qp{Y?NH6MIY7a zA)>}BmnDE!Wd573r(nw1)8*Or(436;W1@@G+oD&c+R_tkyXi$0*eNE9qS9#2^4;Xk zQp|B$z~70=f1)(qf0(QL#}AWHw>SrOsIqrb;6v~QmGOy1#{CX<3_2~DbVlsFj+~~T z`{$!Nn@{WoXR7cpzw;aZ$h2b2fp#VePm*r^*1K`;b>P&`69B?7x5RU1PTv;FALfuE zdPj}@N~2b>HWIN{wzL#r0a8fUuIeJnz{)zcS&y%oq{{&`UVc%*xiU2xx(MG(fCC>H zjpPLI=U7~7w3n|DwrI3fjAqo8^Z1}%!IE;si2oc-_#91i*2aupZSA@2c!>ynCo`Ka zorZkXpW{SBX*7|G-R!N5Q05&`FPkoxWqGysqSNB+wLKeqTfUbfWxqa}J6x20PAD33 zB+UG=3k#PrKjOg(k#~$)rrVP1&^h|+}~>(?z1V@R69Qn{Rs^Iw%q2x zaeAw%m=8JMuOa@`@dVKRTcf(tg3Tyc&C23xn&(6#+tplBa|B@f_3f%7NzA*3kDtDw zey>E`eXE|_^}QMK)vZk|)g6M=<%*ONQ{&;14)kWI^)Q#ZXz^h!JcYI3(s($$r z_+B(kjIQ~pq_$frtc8VBhzv!WZ8 z^P(%Hs6;1fpegqX8E%3?OaXt&Q_O^j11mKy>N8WxuE0dt1!W$y8??`IujUnJv=dXl zEg@JoN(i&M41vg+XdY#_FwCZ<&GgnAEe3#FrSmqLm0+4@QmqbAf~6&$TL8Gs4XGKP)azL~}|nMY^yeCKOWA(9>TCIe1xB-FL1f z=;^L4^BzV#M0TJBF{bEsbpxBv%AVV)32X06%BiVm=+OegMx)Kao+4E^R&aHxsQ z)${lsS_~08T@sMVR=Dz&y!m}uK)2z zJ3)}-{*4`{FVf)Rv}Gm1em>ryQ`OrBkGz~T3$tDrBs{asyu_<*9=$!qNV=&E7WMlQ z;rfdlaW2)fZGgouPccP1YAQDA)rg{hUlntG)>x7a))HBeAfd-s3t>#Ml8NkY~yve-r%&v6goJRRZKj5nsK0|4y zl}5AS5v_?E{?y)DItL~d7CA$2k2I)*ud!2Sd$fSKz^YRbkBg7LWrp+6fOY?@S?;?7KN@oC$!J8WA>9#n1lo7*)z#~|WeguAG1 zJ)u>|g&97L(t1PDn838wili2S1Ub(vr7Z=ltBFy901;CJ7U*PFA%kp} z86t+-JQ#`1{H_2D3d_AMk`atZA5Y}uwSWl%Oe^+kXY}^#-dz4@@AUgHC*d zrm<#$DYhzRf|8Y^i@cBejELR-_~FoIO~0BEvy7O&uMgww)r5uaz&hq5(l)fMR@+rF2uZR9`?VV>-Q{9@!L)Rdp5EKv)6hi{y z3npOby#xr5009A!CK8GY2vSr!Xy~An&>;y49i$guM5+ivC{h)WA_3(^K*T!7J2PwD zHS^y0&fI(FuKQuWo$Qlj<>c(M_p|@c@A>btGtYV?g|E!^e!KkOaiz~^@JOUyxcn-= z#Lk!EoeFHyvz*e&q4P4tz-RSHTOp-dl(S)mt9)trls<={gyhl(wY8@KJ_$u7Y+xA$ zO#dsB{{A9~BV1oSF3SeYGmZ|zWQQkejMNk)8oE2!*y_2~w2)E5lhGpP@McP(Ty;|s z5kAX79uz8y>F`I;ODqU#THGeT5PeiFb+3|!iV5z>wAnzVl1MBoVP05#hBKCdBg$CQ zPqCOmvdBU4{2Nc4%)Ysf40nDt{|&|L_(OaZKhL`cWi1`|kC&yFfiGt``$A zcvlyytxgbJPTJpDvyNc-2KO{2D0p5JdglE$s)rLq>E?_dn>uyvj08nwv@>|bZAUAF z$4YXHm|5Z&P{LxTopGnnwu7(=XSU7Ams4S7hHB{uXg@n^}^=mrqw1 zus&{-x|dZBA2QQM?_5%}QunS4RL}qAL%B)?H#)(_w(Z{Pmr9$v_1DnUe05L$cci57 zey^*Hw%htw2mMVo5Vg5*gNufg>m_z}jkB{qK(Fp%E(X|i^zC%)ewFr*PkGWik-J0_ zIxSVjXzRTo6@<8@FjiUkS`b&Pv~EE*C>G8=640$GUMSh;&&h5MpX%r+aWvQLRyfvh zYHt=ugfs;E7;-45nl)v*rKhG>T)KCVOhHB@7Mz0<5XK}j<49c$2_q{d6CGU?u6Gc) zi6nR@o$UzGNk9Vm6pIW$0PiGm$m$xogahik2WHkPM7K`hK7x3S5Iu;j*)7lY^itsS zo}>zKXu9sv@*LYSQrPyR9vrFPclnJIO9b>iceI{JsmWub+nT=AY*>us$A7mUm-Y>L zOT;eEHp()p@TK0tuHMz~fE|a|wlx6d5qaYsd}nCg8Py>_cfRJ>uNS1%--NTb|Cddt@z;67ZgA86ddM&slI_= z8BwW{aCq=AgrvqUK{v^T8tWbP&$dzaadB?s;=jS<5vVnt=1Kh&Sw^t)12 zD}0Lmb}w>1{05f?!iYr_PBr$DH;{ZoG$)UC2zZ>+_QHnA!QCM-4Mu$_gk}mg99>=A z{250?k)bi~ns^7Hfz%ZeMu(RE#How1QanV^qFJ3&;T;sBvKgV*r^~Ww%70sdA)xq}IEc98G7RA1a z^ZgCGrlgXU(9Q;J4mQx1nV>*powsejP)kywk*GS%)LDHAEN_t27TW9P`HJ5vHsaTD5l7;e-z791g(>OTII$VUQ+3cCL4_ z2fAo!ClCpcG$Bm|Sj*&S3Yk2Jk2ppoAfv{bB~`#b*Yo7^oJyQFgC9*Yig_YVu{t+n z@W!yzCfcFi7uMxDx_${|f}qQ^-_I$JPGCL9b$ucqVBCSCAW5f?8elbG^Lr8@>z~Pn zy#KILC!i*|fW27N(!xvx-8{)_)YmeWXJ4)%o%j9M1^dkSOUg2Mg6RY5j()=6j^b1H zT_eQnRk;8ko36-w=U^+@xOX_3%^)sBmUzKXT0uqaM3XL99rnb+cb>mxA^bo!d^VW88V$ zA-WbFMk$l-vv~OfRE3ceWx^%7U;awLr@j$ojw?RHdew~ze3uU2WZhLTvdcNNdaBIL zE_RCV(SqXj=e>)#a$MWo~E&Ori1c~JcN z%u07gO zhP|UZ7sb74%54OcY`l`!RTPH+(e(rTeSSbc0^rXB+Q4M~pk9y(DoU?2OfDj7cUXaN z*8owy8+zE!eAzoD`RfSj8U~rSdoK9!)B9v-cFr~Vu$} z%=h3Pb(fAGAmMiJ12N+5h9abmr%n_+8dxzuFt6@jPVyTYtE;naJ)qB>E~Vn#TH_8V zVE=VY{=L^H{~+7+=YjYi9}hwom@pPp>8*_Z>mTA(0Jwxv*gmG{hT>J58U5-hao=rr zHgCV`q);Z!0kWq|8}^;B<4q?>-C@*7e%B`PtGNq8HeS9MD$Z`pD{(fG9&pnC_hj{1 zXQDa4=}K^u>_-WmVG!^Q)M6HJi~}qaG?1fc5<(}<$x*0aX#^nEXdPI67@0L(ORzu; z2CcAtdE6Xnfi6}!8z8E80qGW2T^BWq2IhOG?l2%`1vpF-3v|t75Q=-Gpm5FxPOXLw(!0w9KU^Lvs3e60`ko;_VVf zddEV8eto(=(fF>Ss6jJD9Q*Dy@KE4(zF#(Ss9&|}GP-Nn>E|n&tc-IDH}1Z2Y>I_s z!ia#)?^NJgSX$|0g?IE&Echax!f?o&$7*W}nj7#Z22Q3^o3>}Xy>yaU-kElTuyqr3 zWP?OCWFB6(#h}a7$ZJv{en}; zJ|gz5;X|9RPANelElL(B!$3DW`wkQ6r=6r%V{-X-*vuYxs% zZ^{V)23lX4iv?O?s48Rb2!FLT_xP*GIvwYG`l$7F9B#y6$wjczCR1^y6-a;nj~u0b zvLAn+tJHrO>0pA6vRrip?UARA<_~q8Lxb>o@Ak4X7Z%Jo+C5|jCYrqO8TF2SW70|c zUjv*511+w4i>S4)aaE}TTm~@1Y}JS4x@>DApEDxSg}kDK#Y`TQaC%rEg$&`xNgxJM z!f^pX0^-PNmJY@!3zDfRN*L=PO#Sp}M&hc=Wg%IM$)vM@nsAW15lg}mUIg9Y^TDJS z6eZJO#v$;HQ!T}w*K|a90qRJ+Gv~0UA^NB>A^OlF1}CoaKmJTP$k2eGo#oDwEMWST7JD$Jgr-VOy!=9goa4(6TT9 zcX5t?^>7&92{ftKX3)!&uHrw7gLo;jvI0zUYVA)G9@?Kr0e|h7_wPsxoX``nWvQ$s0C7!Hea6LJTXo)1qmI(laMHobyN}lW~ z=FBB!ACQk6B?h3#LxjM>Y8IkBgQXi11`h?pm88^YEFB{xw;MqPje?tcVZc2`+@K7( zS91g5mXs(xnob77RymT>ZA(5=P@jSaPYtcXl;P5jPj}?9tytwVMbjwB^AlT?N@}TS z7wL`A3VlFcX(d}F`3A3ok#0*KrlAH|83L0Mcx{xV*xjo8aH(J6(hDi?>o$RxaV_Sf zRZ|Z>`n^~}RmQyN3^?Lc@>=C^Ys+1$IWwLM!z*-RiD;wh{0zqTfPBs6#YsfUH|gCv z-)UIh8J+619(8jywI3kqoz}Pm!ku>R#WoM$$b2{lh|w#1*Q%d=Ote`rJ*G|sapyl} zpLhs+rg%w6u8aPP+4C*CSCU&7cT^b`sHa3$*DaW zyjgQou!{GDS!%$!J?pcP`Uin@lS7CE5i}Vm{wx$opxt~>#;Cs4NnQ*4uiI@OGW`0Nc#XVwx0vYXq3#9|eLHM=9WPrX{5q9LKR@_1O~ zCtSRufh5z^t{PyE@li*AJz!?f=Act{_LepDowX1b6j7J6bSUwu!iHE++l#J-iV_dM zO2$a@wsSvfwKS=xd40|8x{kH8D4zn_RqwWUT!2bmP-Px#z@e9-#mxlWK5*H>aShW@ zi%YkKCS_LrI5x#1J{fB=h>plD2RmIN2I^jv%;`SlPA+f>r$B(jewY{!444!B(27yR zzq!x)0q-mrr=Pby!v$bJnxl{G^a*?;62AAitqgfi#vr&sFK%OvZ8b ztb%+GH}^rer765<9>Z97)?a=T1!*9F#tr`mM`lbGj}Xj+v6U;N3yOrlYWF@9>4A z+2!S-bGGdwaJ~h8w&`|zb*$3V3tcDWHG-(oktr3I=lj-Q>hGd(XF6#DP&LAVD&H9J zL*8f)KO-_(9iX{7|Jp?HN9X@mzka^j+xnMos?bk6QuuF3QMoQN>Pr)=yk3&525Xb@ z45tiU3LvcGbmHn#R?|sInQ`K`%lMSPJ)H~ftanO7tybJn+#a|Ra!NfQvKBk*2;f_< z>R+zyvGikG!9Mv8yYnt`vyVSa?xcB5a@89uTS|8UG$!n)041-4;4U>gU7PCp(1e*XcLGu$Y6~qFmo1ks0 zXUI5?wbpMQqMf;U%D82L`v+eH>i{*Dn(&$|N$(m7=U^976XQVxoZJXKE01kkuuK_} z{#JS4UuH9Z&nP&t9Jq#ghS$QABe70j&9+ZYw1btr(aeuZtM{XjwvW;iT(iY2uXX(Z z`D5(k^ncCBjSvs1S>36mIP0C1X4w&d$_$sJYisZ;QR34+VI#d4`Z73I@Sz{A!)+Tr z;y*Ik<)y>l;oTKx4yj7iDlOyv=3>2^0mR+!B@`u@PzCh1I>kj!6~BbR%Ir$k#` zUc%h8#+9?MyRhT^Pc5G~Rj7W^ew#ZmV^F*1;Q=`oP+l{zlACGF0JA8R&;q?}vd-Sq zdFgpr1KizI*V1qXnlO5@i&HNURjy#nOfnL1QfMZpvROouo)NunamA0T05Vv?NQ4@v)(5WyHxE_LJKg`1mO#DeCdy;LD ztr+G4a+cv18*q>p3NWEoOv2koh8nj{oV*)RU!w2#$*OBw+wn8wm?hJrbFGn2q;G+% zy1(qmn&Q@nC*1JKxQYW=%;!HqXWeZ+AI{G;6ODfQ?c)Kp{fe6jM{d(V zpDk*-gKzp0eI{%(2l!rh5`752^=`|Zy<#r=@QIq_G{} zUrNvZZ`wouR`J}(7RJ`C>Qrq1cDk!paZUEU-zmf6;V;BT>(sOo`&_+}StFUsNdqaH z4n13vM7-`HUUo69!JyvwHuaV(8^I5W(G4jLeUU{qANMM~^v=ML{@AjLtl(Ex{zD$q z)e5l$xoM?UMpdtdz_tPbIkXZtLyyjo9QQ*}_sJ6;*}{=m>X9D;OFbo6AG$$fIdO5# zo=Ap|RojHyX^{}~4@bL2;_LY=U;@IXe%ptxzA7_TE@YWxHDr6OCau`~ZDqLJRQaPo zEqQt}K<{Pd5=XCEPyxD!6J0765JJ3t$}YXT%~xqi-BR*SP?=4TvFmZ-Z2O9%-$e22 z{vnU?yh!?UmL{vn*GdXiT(J`7hZu&dGsIVd;9B`SXik6|Ys1QPPGiB+c;C(Jf%HQH z#cW?zBR^SVt~+^q73hFrViDzU;+aJ;uIRvb-pb%)0vF6r zYs$Zs_R+hg9gVsTTYiW(4v+=zdDb6E$p=kb@gkL>;5!f!=3h@+o*ByXpK`ZhPf_MH)F3dw4=ka-BzNTJOcJ%u`UH53!Ts&_<=|yBqU&j4Q@d9qw1O&eeoS&Z4 zFs#{CXgHqups8XZ&Ehh@#NuULS86swq_s53$pV^nWO*v*n~$yFX9i%_oG}t>oYgXV z#r>UQ()%tBQ%)6JnETiDQsf&B*K)U+KZ%>};zj?REe8D^ZESx~FZ0*J!V~89GkX4P zPtS8DJmD~0$HU0uF|?fKv(>nlWiYt`TC zR=h>@bZ-n6Fe6X*vx_n3EjSbCJ)kd$-1Y%B(k84tk2PP%Dn9FJWg7isy+#~3EnVt`G0CbF4$V;NV_*`x8n zvL*u7B`1yUoN+Ho?~8@*xzLJ;g}*1V{j0V8-&%P6{wm-~OZ8HU5)Q0?M8Vem@-!NdU~0c6!X5b>uH?a-jdy R((UitlkH#a^SnQ%{td7jUc3MR literal 0 HcmV?d00001 diff --git a/web/src/executor/executor.es6 b/web/src/executor/executor.es6 new file mode 100644 index 0000000000..dc9b689f10 --- /dev/null +++ b/web/src/executor/executor.es6 @@ -0,0 +1,103 @@ +/* eslint-disable */ +/** + * @file GraphExecutor,封装可执行单元 + * @author wangqun@baidu.com + */ +// const fileDownload = require('js-file-download'); +let start; +export default class GraphExecutor { + + constructor(model) { + this.inputs = model.inputs; + this.outputs = model.outputs; + this.attrs = model.attrs; + this.type = model.type; + this.finish = false; + this.next = null; + this.opData = null; + this.id = +new Date() + model.type + Math.floor(Math.random() * 10 + 1) + model.idx; + } + + get inputsName() { + + if (this.type === 'feed') { + return this.inputs.X; + } + else if (this.type === 'batchnorm' || this.type === 'batch_norm') { + return this.inputs.X; + } + else if (this.type === 'conv2d') { + return this.inputs.Input; + } + else if (this.type === 'depthwise_conv2d') { + return this.inputs.Input; + } + else if (this.type === 'elementwise_add') { + return this.inputs.X; + } + else if (this.type === 'relu' || this.type === 'leaky_relu') { + return this.inputs.X; + } + else if (this.type === 'pool2d') { + return this.inputs.X; + } + else if (this.type === 'mul') { + return this.inputs.X; + } + else if (this.type === 'softmax') { + return this.inputs.X; + } + else if (this.type === 'scale') { + return this.inputs.X; + } + else if (this.type === 'fetch') { + return this.inputs.X; + } + return null; + } + + get outputsName() { + if (this.type === 'conv2d') { + return this.outputs.Output; + } + else if (this.type === 'depthwise_conv2d') { + return this.outputs.Output; + } + else if (this.type === 'batchnorm' || this.type === 'batch_norm') { + this.outputs.out = this.outputs.Y; + return this.outputs.Y; + } + else { + return this.outputs.Out; + } + + } + + /** + * 将输入数据和具体op进行关联,触发执行具体每一个op + * @param inputs + * @param runtime + */ + execute(runtime) { + // console.log(inputs, outputs); + if (this.type !== 'feed') { + let time = +Date.now(); + runtime.run(this.type, this.opData); + // if (runtime.gpu.frameBufferIsComplete().isComplete) { + // var result = runtime.read(); + // let res = Array.prototype.slice.call(result); + // fileDownload(res, "result.csv"); + // } + let length = statistic.length; + statistic[length - 1].type = this.type; + statistic[length - 1].runTime = +Date.now() - time; + // if (this.type === 'scale') { + // console.log('时间是:' + (+Date.now() - start)); + // } + } else { + start = +Date.now(); + } + } +} + +/* eslint-enable */ diff --git a/web/src/executor/loader.es6 b/web/src/executor/loader.es6 new file mode 100644 index 0000000000..de7341aa80 --- /dev/null +++ b/web/src/executor/loader.es6 @@ -0,0 +1,403 @@ +/* eslint-disable */ +import GraphExecutor from './executor'; +import IO from '../feed/imageFeed'; +import Runtime from '../../src/runtime/runtime'; +import OpData from '../utils/opData'; +import Factory from '../factory/fshader/factory'; +import Utils from '../utils/utils'; +/** + * @file GraphModel,绘制生成model网络 + * @author wangqun@baidu.com + */ +// 生成factory实例 +const factory = new Factory({}); +// 获取op的输入配置 +const opConfs = factory.getOpConfs(); +export default class GraphModel { + constructor(modelGonfig, loadOptions) { + this.version = '0.0.1'; + this.handler = 'io.IOHandler'; + this.modelGonfig = modelGonfig; + this.loadOptions = loadOptions; + this.multipart = false; + // feed数据 + this.feed = null; + this.index = 0; + this.feedOp = null; + this.feedItem = null; + this.isExecuted = false; + // fetch xhr jsonp + this.params = {type: 'fetch'}; + // 设置分片加载model + if (this.loadOptions) { + this.multipart = this.loadOptions.multipart; + this.feed = {input: this.loadOptions.feed}; + if (loadOptions.dataType === 'binary') { + this.binaryOption = loadOptions.binaryOption; + } + } + // op runner + this.inst = Runtime.init({ + 'width_raw_canvas': 512, + 'height_raw_canvas': 512 + }); + if (this.loadOptions === null) { + this.loadOptions = {}; + } + } + fetchOneChunk(path) { + console.time(path) + return fetch(path).then(request => { + console.timeEnd(path); + return request.arrayBuffer(); + }) + } + fetchAllData() { + // todo 兼容一下json的模式 + let counts = this.binaryOption.fileCount; + let chunkArray = []; + for (let i = 1; i <= counts; i++) { + chunkArray.push( + this.fetchOneChunk(this.modelGonfig.dir + this.binaryOption.getFileName(i)) + ); + } + // 1个文件 + // let chunkArray = [this.fetchOneChunk('/faceModel/mergedData.dat')]; + console.time('加载时间'); + return Promise.all(chunkArray).then(chunks => { + console.timeEnd('加载时间'); + let chunksLength = 0; + let f32Array = []; + let float32Chunk; + chunks.forEach(i => { + float32Chunk = new Float32Array(i); + f32Array.push(float32Chunk); + chunksLength += float32Chunk.length; + }); + this.allData = new Float32Array(chunksLength); + let offset = 0; + f32Array.forEach(i => { + i.forEach(num => { + this.allData[offset] = num; + offset += 1; + }) + }); + }); + } + traverse (arr) { + const TMP_SCHEME_REGEX = /\.tmp/; + const TMP_REGEX = /\-/; + let marker = 0; // 读到哪个位置了 + let len; // 当前op长度 + arr.filter(item => { + return item.name + && item.name.match(TMP_SCHEME_REGEX) === null + && item.name.match(TMP_REGEX) === null; + }) + // .sort((a, b) => { + // if (a.name > b.name) { + // return 1; + // } + // if (a.name < b.name) { + // return -1; + // } + // return 0; + // }) // 按字母顺序排列 在model.json里 + .forEach(item => { + len = item.shape.reduce((a, b) => a * b); // 长度为shape的乘积 + item.data = this.allData.slice(marker, marker + len); + marker += len; + }); + } + fetchModel(params) { + params = params || this.params; + const path = this.modelGonfig.dir + this.modelGonfig.main; + let URL_SCHEME_REGEX = /^https?:\/\//; + let load = null; + let method = params.method || 'get'; + let mode = params.mode || 'cors'; + // jsonp请求方式 + if (params && params.type === 'jsonp') { + let json; + let s = document.createElement('script'); + s.src = path + '&jsonpCallback=fn'; + window.fn = function(data) { + json = data; + // console.log(json); + }; + //当script被插入文档中时,src中的资源就会开始加载 + document.body.appendChild(s); + load = new Promise((resolve, reject) => { + s.onload = function(e) { + resolve(json); + } + s.onerror = function() { + reject(json); + } + }); + this.handler = load; + } + // 原生fetch + else if (params.type === 'fetch') { + let myHeaders = new Headers(); + load = new Promise((resolve, reject) => { + fetch(path, { + method: method, + mode: mode, + credentials: "include", + headers: myHeaders + }) + .then(response => response.json()) + .then(responseData => resolve(responseData)) + .then(err => reject(err)) + }); + this.handler = load; + } + // ajax + else if (params.type === 'xhr') { + this.handler = load; + } + return load; + } + async load() { + let that = this; + console.time('生成op数据之前') + console.time('fetchModel'); + const artifacts = this.handler = await this.fetchModel(); + console.timeEnd('fetchModel'); + if (this.multipart === true) { + console.time('6个文件准备好op数据'); + await this.fetchAllData() + .then(() => this.traverse(artifacts.vars)); + console.timeEnd('6个文件准备好op数据'); + } + console.time('createOpsMap'); + const opsMap = this.createOpsMap(artifacts.ops, artifacts.vars); + console.timeEnd('createOpsMap'); + console.time('constructOpsMap'); + this.weightMap = this.constructOpsMap(opsMap); + console.timeEnd('constructOpsMap'); + console.timeEnd('生成op数据之前') + // 生成op数据 + this.weightMap.forEach(op => { + const type = op.type; + if (type !== 'feed' && type !== 'fetch') { + that.buildOpData(op); + } + }); + return true; + } + buildOpData(op) { + const tensor = this.constructTensor(op); + const opData = new OpData(op.type, tensor.inputs, tensor.outputs, tensor.attrs); + const name = opData.name; + const fsCode = factory.buildShader(name, opData.data); + opData.fshader = this.inst.createFragmentShader(fsCode); + opData.renderData = opConfs[name].map(elem => { + let item = Object.assign({}, elem); + const tensorData = opData.tensor[item.tensor]; + if (item.type === 'texture') { + item.data = tensorData.data; + if (this.feedOp.id === op.id) { + item.shape = tensorData.shape; + this.feedItem = item; + } + item['width_texture'] = tensorData['width_texture']; + item['height_texture'] = tensorData['height_texture']; + } else if (item.type === 'uniform') { + item.data = tensorData[item.variable]; + } + return item; + }); + op.opData = opData; + // delete op.inputs; + // delete op.outputs; + // delete op.attrs; + } + execute_(executor) { + if (executor.type === 'fetch') { + return; + } + executor.execute(this.inst); + if (executor.next) { + const id = executor.next; + const next = this.getTensor(id); + this.execute_(next[0]) + } + } + /** + * Executes inference for the model for given input tensors. + * @param inputs + * @param outputs + * @returns {*} + */ + execute(inputs) { + this.feed = inputs; + const executor = this.getNetsStart(this.weightMap); + if (!this.inst) { + this.inst = Runtime.init({ + 'width_raw_canvas': 512, + 'height_raw_canvas': 512 + }); + } + if (this.isExecuted) { + this.updateFeed(); + } + let start = +Date.now(); + this.execute_(executor[0]); + console.log('总的执行时间是' + (+Date.now() - start)); + this.isExecuted = true; + return this.inst; + } + updateFeed() { + this.feedItem.data = this.feed.input[0].data; + Utils.img2texture(this.feedItem); + } + /** + * predict enter + * @param inputs + * @param config + */ + predict(inputs, config) { + return this.execute_(inputs, true, this.outputNodes); + } + getTensorAttr(name) { + return this.handler.vars.filter((item, i) => { + if (name === item.name) + return item; + }); + } + constructTensor(executor) { + const that = this; + const inputName = executor.inputsName[0]; + const input = executor.inputs; + const output = executor.outputs; + Object.keys(output).forEach(function(key){ + output[key] = that.getTensorAttr(output[key][0]); + }); + Object.keys(input).forEach(function(key){ + if ((key === 'Input') && (inputName === 'pixel')) { + const pixel = that.getTensorAttr(inputName); + const io = new IO(); + input[key] = io.fromPixels(data, pixel); + } + else if ((key === 'Input') && (inputName === 'image' || inputName === 'x')) { + input[key] = that.feed.input; + that.feedOp = executor; + } + else { + input[key] = that.getTensorAttr(input[key][0]); + } + }); + const tensor = { + inputs: input, + outputs: output, + attrs: executor.attrs, + type: executor.type, + next: executor.next + }; + return tensor; + } + /** + * Construct Ops Relationship + * @param ops + * @returns {*} + */ + constructOpsMap(ops) { + return ops.map((item, idx) => { + const outputsName = item.outputsName[0]; + const next = this.getNextExecutor(ops, outputsName); + if (next.length > 0) { + item.next = next[0].id; + } + return item; + }); + } + /** + * Get Ops Nets Start Node + * @param ops + * @returns {*} + */ + getNetsStart(ops) { + return ops.filter((item) => { + if (item.type === 'feed') { + return true; + } + }); + } + /** + * Get Ops Nets Last Node + * @param ops + * @returns {*} + */ + getNetsEnd(ops) { + return ops.filter((item) => { + if (item.type === 'fetch') { + return true; + } + }); + } + /** + * get tensor by id + * @param id + * @returns {*} + */ + getTensor(id) { + return this.weightMap.filter((item, i) => { + if (id === item.id) + return item; + }); + } + /** + * Create Ops Executor Object Map + * @param ops + * @returns {*} + */ + createOpsMap(ops) { + return ops.map((item, idx) => { + item.idx = idx; + const graphExecutor = new GraphExecutor(item); + return graphExecutor; + }); + } + /** + * Get The Next Executor need Exec + * @param ops + * @param id + * @returns {*} + */ + getNextExecutor(ops, id) { + return ops.filter((item, key) => { + if (id === item.inputsName[0]) { + return true; + } + }); + } + /** + * Load a graph model given a URL to the model definition. + * @param modelGonfig + * @param options + * @returns {Promise} + */ + async loadGraphModel(modelGonfig, options) { + if (modelGonfig === null) { + // todo saniac 报错提示修改 + throw new Error( + 'modelGonfig in loadGraphModel() cannot be null. Please provide a url ' + + 'or an IOHandler that loads the model'); + } + if (options === null) { + options = {}; + } + const model = new GraphModel(modelGonfig, options); + await model.load(); + return model; + } + /** + * dispose + */ + dispose() { + this.executor.dispose(); + } +} +/* eslint-enable */ diff --git a/web/src/factory/fshader/factory.es6 b/web/src/factory/fshader/factory.es6 new file mode 100644 index 0000000000..0b218fc458 --- /dev/null +++ b/web/src/factory/fshader/factory.es6 @@ -0,0 +1,70 @@ +import ops from './ops'; +/** + * @file 工厂类,生成fragment shader + * @author yangmingming + */ +export default class Factory { + constructor(opts) { + this.defaultOpts = Object.assign({}, opts); + } + + buildShader(opName, data) { + let result = ''; + result = this.buildPrefix(opName); + result += this.buildCommon(opName); + result += this.buildOp(opName); + result = this.populateData(result, data); + return result; + } + + buildPrefix(opName) { + return ops.common.prefix; + } + + buildCommon(opName) { + return ops.common.params + ops.common.func; + } + + buildOp(opName) { + let code = ops.ops[opName].params; + // 依赖的方法 + let atoms = ops.atoms; + let confs = ops.ops[opName].confs; + let dep = confs.dep || []; + dep.map(item => { + let func = item.func; + let data = item.conf; + let snippet = atoms[func]; + code += this.populateData(snippet, data); + }); + // suffix + code += this.buildSuffix(opName); + // main方法 + code += ops.ops[opName].func; + return code; + } + + buildSuffix(opName) { + return ops.common.suffix; + } + + populateData(result, data) { + let code = result; + for (let key in data) { + code = code.replace(new RegExp(key.toUpperCase(), 'g'), + ((typeof data[key]) === 'undefined') ? 1 : data[key]); + } + return code; + } + + getOpConfs() { + const opsConfs = {}; + for (let key in ops.ops) { + if (ops.ops.hasOwnProperty(key)) { + opsConfs[key] = ops.ops[key].confs.input; + } + } + return opsConfs; + } +} + diff --git a/web/src/factory/fshader/ops.es6 b/web/src/factory/fshader/ops.es6 new file mode 100644 index 0000000000..dfdf6e6535 --- /dev/null +++ b/web/src/factory/fshader/ops.es6 @@ -0,0 +1,120 @@ +/* eslint-disable */ +import common_params from '../../shader/atom/common_params'; +import common_func from '../../shader/atom/common_func'; +import prefix from '../../shader/atom/prefix'; +import suffix from '../../shader/atom/suffix'; +import ivec56 from '../../shader/atom/type_ivec56'; + +import conv2d_params from '../../shader/conv2d/params'; +import conv2d_func from '../../shader/conv2d/main'; +import conv2d_conf from '../../shader/conv2d/conf'; +import dynamic_params from '../../shader/dynamic/params'; +import dynamic_func from '../../shader/dynamic/main'; +import dynamic_conf from '../../shader/dynamic/conf'; +import pool2d_params from '../../shader/pool2d/params'; +import pool2d_func from '../../shader/pool2d/main'; +import pool2d_conf from '../../shader/pool2d/conf'; +import elementwise_add_params from '../../shader/elementwise_add/params'; +import elementwise_add_func from '../../shader/elementwise_add/main'; +import elementwise_add_conf from '../../shader/elementwise_add/conf'; +import mul_params from '../../shader/mul/params'; +import mul_func from '../../shader/mul/main'; +import mul_conf from '../../shader/mul/conf'; +import softmax_params from '../../shader/softmax/params'; +import softmax_func from '../../shader/softmax/main'; +import softmax_conf from '../../shader/softmax/conf'; +import batchnorm_params from '../../shader/batchnorm/params'; +import batchnorm_func from '../../shader/batchnorm/main'; +import batchnorm_conf from '../../shader/batchnorm/conf'; + +import getArrayIndexFromTensorPos from '../../shader/atom/getArrayIndexFromTensorPos'; +import getArrayIndexFromTexturePos from '../../shader/atom/getArrayIndexFromTexturePos'; +import getTensorPosFromArrayIndex from '../../shader/atom/getTensorPosFromArrayIndex'; +import getTexturePosFromArrayIndex from '../../shader/atom/getTexturePosFromArrayIndex'; +import getValueFromTexturePos from '../../shader/atom/getValueFromTexturePos'; +import getValueFromTensorPos from '../../shader/atom/getValueFromTensorPos'; +import moveTexture2PosToReal from '../../shader/atom/moveTexture2PosToReal'; +import getPixelsFromTexturePos from '../../shader/atom/getPixelsFromTexturePos'; +import getRangePowSumFromArrayIndex from '../../shader/atom/getRangePowSumFromArrayIndex'; +import getRangeSumFromArrayIndex from '../../shader/atom/getRangeSumFromArrayIndex'; +import sigmoid from '../../shader/atom/sigmoid'; +import prelu from '../../shader/atom/prelu'; +import scale from '../../shader/atom/scale'; +import softmax from '../../shader/atom/softmax'; +/** + * @file op文件 + * @author yangmingming + */ + +export default { + common: { + params: common_params, + func: common_func, + prefix, + suffix, + ivec56 + }, + ops: { + conv2d: { + params: conv2d_params, + func: conv2d_func, + confs: conv2d_conf + }, + dynamic: { + params: dynamic_params, + func: dynamic_func, + confs: dynamic_conf + }, + pool2d: { + params: pool2d_params, + func: pool2d_func, + confs: pool2d_conf + }, + elementwise_add: { + params: elementwise_add_params, + func: elementwise_add_func, + confs: elementwise_add_conf + }, + mul: { + params: mul_params, + func: mul_func, + confs: mul_conf + }, + relu: { + params: dynamic_params, + func: dynamic_func, + confs: dynamic_conf + }, + scale: { + params: dynamic_params, + func: dynamic_func, + confs: dynamic_conf + }, + softmax: { + params: softmax_params, + func: softmax_func, + confs: softmax_conf + }, + batchnorm: { + params: batchnorm_params, + func: batchnorm_func, + confs: batchnorm_conf + } + }, + atoms: { + getArrayIndexFromTensorPos, + getArrayIndexFromTexturePos, + getTensorPosFromArrayIndex, + getTexturePosFromArrayIndex, + getValueFromTexturePos, + getValueFromTensorPos, + moveTexture2PosToReal, + getPixelsFromTexturePos, + getRangeSumFromArrayIndex, + getRangePowSumFromArrayIndex, + sigmoid, + prelu, + scale, + softmax + } +}; diff --git a/web/src/feed/ImageFeed.es6 b/web/src/feed/ImageFeed.es6 new file mode 100644 index 0000000000..59c2bd66ab --- /dev/null +++ b/web/src/feed/ImageFeed.es6 @@ -0,0 +1,226 @@ +/* eslint-disable */ +/** + * @file image,feed 获取图像相关输入 + * @author wangqun@baidu.com + */ +export default class imageFeed { + constructor() { + this.fromPixels2DContext = document.createElement('canvas').getContext('2d'); + this.defaultWidth = 224; + this.defaultHeight = 224; + this.minPixels = 225; + this.pixels = ''; + this.defaultParams = { + gapFillWith: '#000', + std: [1, 1, 1] + }; + }; + + /** + * 处理图像方法 + * @param inputs + */ + process(inputs) { + const input = inputs.input; + const mode = inputs.mode; + const channel = inputs.channel; + const rotate = inputs.rotate; + const params = { + ...this.defaultParams, + ...inputs.params + }; + let output = []; + + output = this.fromPixels(input, params); + return output; + }; + + /** + * crop图像&重新设定图片tensor形状 + * @param shape + */ + reshape(imageData, opt, scaleSize) { + const {sw, sh} = scaleSize; + const {width, height} = opt; + const hPadding = Math.ceil((sw - width) / 2); + const vPadding = Math.ceil((sh - height) / 2); + + let data = imageData.data; + let red = []; + let green = []; + let blue = []; + let mean = opt.mean; + let std = opt.std; + for (let i = 0; i < data.length; i += 4) { + // img_mean 0.485, 0.456, 0.406 + //img_std 0.229, 0.224, 0.225 + let index = i / 4; + let vIndex = Math.floor(index / sw); + let hIndex = index - (vIndex * sw) - 1; + if (hIndex >= hPadding && hIndex < (hPadding + width) && + vIndex >= vPadding && vIndex < (vPadding + height)) { + red.push(((data[i] / 255) - mean[0]) / std[0]); // red + green.push(((data[i + 1] / 255) - mean[1]) / std[1]); // green + blue.push(((data[i + 2] / 255) - mean[2]) / std[2]); // blue + } + } + let tmp = green.concat(blue); + return red.concat(tmp); + }; + + /** + * 全部转rgb * H * W + * @param shape + */ + allReshapeToRGB(imageData, opt, scaleSize) { + const {sw, sh} = scaleSize; + const {width, height} = opt; + let data = imageData.data; + let mean = opt.mean; + let dataLength = data.length; + let result = new Float32Array(dataLength * 3 / 4); + let offsetR = 0; + let offsetG = dataLength / 4; + let offsetB = dataLength / 2; + for (let i = 0; i < data.length; i += 4) { + result[offsetR++] = (data[i] - mean[0]) / 256; + result[offsetG++] = (data[i + 1] - mean[1]) / 256; + result[offsetB++] = (data[i + 2] - mean[2]) / 256; + // result.push((data[i] - mean[0]) / 256); // red + // result.push((data[i + 1] - mean[1]) / 256); // green + // result.push((data[i + 2] - mean[2]) / 256); // blue + } + return result; + }; + + /** + * 根据scale缩放图像 + * @param image + * @param params + * @return {Object} 缩放后的尺寸 + */ + reSize(image, params) { + // 原始图片宽高 + const width = this.pixelWidth; + const height = this.pixelHeight; + // 缩放后的宽高 + let sw = width; + let sh = height; + // 最小边缩放到scale + if (width < height) { + sw = params.scale; + sh = Math.round(sw * height / width); + } else { + sh = params.scale; + sw = Math.round(sh * width / height); + } + this.fromPixels2DContext.canvas.width = sw; + this.fromPixels2DContext.canvas.height = sh; + this.fromPixels2DContext.drawImage( + image, 0, 0, sw, sh); + return {sw, sh}; + }; + + + /** + * 缩放成目标尺寸并居中 + */ + fitToTargetSize(image, params, center) { + // 目标尺寸 + const targetWidth = params.targetSize.width; + const targetHeight = params.targetSize.height; + this.fromPixels2DContext.canvas.width = targetWidth; + this.fromPixels2DContext.canvas.height = targetHeight; + this.fromPixels2DContext.fillStyle = params.gapFillWith; + this.fromPixels2DContext.fillRect(0, 0, targetHeight, targetWidth); + // 缩放后的宽高 + let sw = targetWidth; + let sh = targetHeight; + let x = 0; + let y = 0; + // target的长宽比大些 就把原图的高变成target那么高 + if (targetWidth / targetHeight * this.pixelHeight / this.pixelWidth >= 1) { + sw = Math.round(sh * this.pixelWidth / this.pixelHeight); + x = Math.floor((targetWidth - sw) / 2); + } + // target的长宽比小些 就把原图的宽变成target那么宽 + else { + sh = Math.round(sw * this.pixelHeight / this.pixelWidth); + y = Math.floor((targetHeight - sh) / 2); + } + // console.log(x, y, sw, sh); + if (center) { + this.fromPixels2DContext.drawImage( + image, x, y, sw, sh); + } + else { + this.fromPixels2DContext.drawImage( + image, 0, 0, sw, sh); + } + document.getElementById('p-c').appendChild(this.fromPixels2DContext.canvas);// test only, demele me + return {sw: targetWidth, sh: targetHeight}; + } + + /** + * 获取图像内容 + * @param pixels + * @returns {Uint8ClampedArray} + */ + getImageData(pixels, scaleSize) { + const {sw, sh} = scaleSize; + let vals = this.fromPixels2DContext + .getImageData(0, 0, sw, sh); + // crop图像 + const width = pixels.width; + const height = pixels.height; + return vals; + }; + + /** + * 计算灰度图 + * @param imageData + * @returns {*} + */ + grayscale (imageData) { + let data = imageData.data; + + for (let i = 0; i < data.length; i += 4) { + let avg = (data[i] + data[i + 1] + data[i + 2]) / 3; + data[i] = avg; // red + data[i + 1] = avg; // green + data[i + 2] = avg; // blue + } + return data; + }; + + fromPixels(pixels, opt) { + let data; + let scaleSize; + if (pixels instanceof HTMLImageElement || pixels instanceof HTMLVideoElement) { + this.pixelWidth = pixels.naturalWidth || pixels.width; + this.pixelHeight = pixels.naturalHeight || pixels.height; + if (opt.scale) { // 兼容以前的,如果有scale就是短边缩放到scale模式 + scaleSize = this.reSize(pixels, opt); + data = this.getImageData(opt, scaleSize); + } + else if (opt.targetSize) { // 如果有targetSize,就是装在目标宽高里的模式 + scaleSize = this.fitToTargetSize(pixels, opt); + data = this.getImageData(opt, scaleSize); + } + } + + if (opt.gray) { + data = grayscale(data); + } + + if (opt.shape) { + data = this.reshape(data, opt, scaleSize); + } + + if (opt.targetShape) { + data = this.allReshapeToRGB(data, opt, scaleSize); + } + return [{data: data, shape: opt.shape || opt.targetShape, name: 'image'}]; + } +} +/* eslint-enable */ diff --git a/web/src/feed/io.es6 b/web/src/feed/io.es6 new file mode 100644 index 0000000000..032f192687 --- /dev/null +++ b/web/src/feed/io.es6 @@ -0,0 +1,853 @@ +/* eslint-disable */ +/** + * @file io,loader相关输入输出 + * @author wangqun@baidu.com + */ +export default class io { + constructor() { + this.fromPixels2DContext = document.createElement('canvas').getContext('2d'); + }; + + fromPixels(pixels, opt) { + pixels = pixels.input; + const shape = opt[0].shape; + const numChannels = opt[0].shape[0]; + if (pixels == null) { + throw new Error( + 'pixels passed to tf.browser.fromPixels() can not be null'); + } + let vals; + // tslint:disable-next-line:no-any + // tslint:disable-next-line:no-any + if (pixels.getContext != null) { + // tslint:disable-next-line:no-any + vals = pixels + .getContext('2d') + .getImageData(0, 0, pixels.width, pixels.height) + .data; + } else if (pixels instanceof ImageData) { + vals = pixels.data; + } else if ( + pixels instanceof HTMLImageElement || + pixels instanceof HTMLVideoElement) { + if (this.fromPixels2DContext == null) { + throw new Error( + 'Can\'t read pixels from HTMLImageElement outside ' + + 'the browser.'); + } + this.fromPixels2DContext.canvas.width = pixels.width; + this.fromPixels2DContext.canvas.height = pixels.height; + this.fromPixels2DContext.drawImage( + pixels, 0, 0, pixels.width, pixels.height); + vals = this.fromPixels2DContext + .getImageData(0, 0, pixels.width, pixels.height) + .data; + } else { + + } + let values; + if (numChannels === 4) { + values = new Array(vals); + } else { + const numPixels = (shape[1] || pixels.width) * (shape[2] ||pixels.height); + // console.log(numPixels, numPixels * numChannels); + values = new Array(numPixels * numChannels); + for (let i = 0; i < numPixels; i++) { + for (let channel = 0; channel < numChannels; ++channel) { + values[i * numChannels + channel] = vals[i * 4 + channel]; + } + } + } + // console.log(pixels.height, pixels.width, numChannels, values); + // const outShape: [number, number, number] = + // [pixels.height, pixels.width, numChannels]; + values = [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 7.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 6.0, + 7.0, + 0.0, + 0.0, + 0.0, + 0.0, + 3.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 3.0, + 0.0, + 0.0, + 14.0, + 16.0, + 8.0, + 1.0, + 0.0, + 0.0, + 0.0, + 14.0, + 1.0, + 0.0, + 0.0, + 14.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 5.0, + 13.0, + 0.0, + 0.0, + 0.0, + 9.0, + 0.0, + 27.0, + 0.0, + 0.0, + 0.0, + 5.0, + 0.0, + 0.0, + 3.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 0.0, + 0.0, + 5.0, + 11.0, + 5.0, + 4.0, + 8.0, + 0.0, + 0.0, + 15.0, + 7.0, + 0.0, + 2.0, + 7.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 11.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 11.0, + 3.0, + 0.0, + 2.0, + 0.0, + 5.0, + 3.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 0.0, + 0.0, + 10.0, + 6.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 9.0, + 0.0, + 0.0, + 2.0, + 3.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 8.0, + 0.0, + 8.0, + 11.0, + 0.0, + 4.0, + 113.0, + 202.0, + 249.0, + 255.0, + 255.0, + 135.0, + 44.0, + 0.0, + 7.0, + 3.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 0.0, + 2.0, + 0.0, + 33.0, + 188.0, + 230.0, + 101.0, + 52.0, + 6.0, + 106.0, + 162.0, + 183.0, + 11.0, + 0.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 9.0, + 0.0, + 4.0, + 58.0, + 230.0, + 189.0, + 31.0, + 0.0, + 3.0, + 0.0, + 14.0, + 0.0, + 204.0, + 17.0, + 7.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 20.0, + 24.0, + 231.0, + 181.0, + 0.0, + 0.0, + 5.0, + 4.0, + 2.0, + 0.0, + 119.0, + 228.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 173.0, + 232.0, + 32.0, + 4.0, + 10.0, + 0.0, + 0.0, + 7.0, + 79.0, + 230.0, + 108.0, + 18.0, + 0.0, + 10.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 100.0, + 246.0, + 47.0, + 0.0, + 5.0, + 0.0, + 1.0, + 8.0, + 63.0, + 216.0, + 109.0, + 0.0, + 0.0, + 6.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 8.0, + 122.0, + 210.0, + 0.0, + 31.0, + 0.0, + 8.0, + 28.0, + 109.0, + 235.0, + 182.0, + 0.0, + 13.0, + 0.0, + 22.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 128.0, + 233.0, + 0.0, + 6.0, + 66.0, + 126.0, + 180.0, + 191.0, + 220.0, + 27.0, + 0.0, + 0.0, + 11.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 78.0, + 246.0, + 233.0, + 220.0, + 255.0, + 199.0, + 59.0, + 235.0, + 68.0, + 12.0, + 0.0, + 1.0, + 2.0, + 1.0, + 10.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 0.0, + 80.0, + 120.0, + 139.0, + 62.0, + 0.0, + 155.0, + 211.0, + 5.0, + 10.0, + 0.0, + 0.0, + 0.0, + 3.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 5.0, + 2.0, + 0.0, + 0.0, + 90.0, + 255.0, + 70.0, + 0.0, + 0.0, + 0.0, + 9.0, + 0.0, + 0.0, + 9.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 17.0, + 5.0, + 0.0, + 11.0, + 47.0, + 227.0, + 159.0, + 0.0, + 0.0, + 8.0, + 0.0, + 0.0, + 2.0, + 6.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 5.0, + 0.0, + 0.0, + 0.0, + 4.0, + 213.0, + 207.0, + 19.0, + 0.0, + 0.0, + 3.0, + 12.0, + 0.0, + 2.0, + 4.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 16.0, + 7.0, + 91.0, + 253.0, + 50.0, + 0.0, + 0.0, + 4.0, + 0.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 5.0, + 0.0, + 45.0, + 252.0, + 131.0, + 0.0, + 8.0, + 0.0, + 7.0, + 0.0, + 15.0, + 5.0, + 0.0, + 0.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 8.0, + 11.0, + 207.0, + 205.0, + 30.0, + 2.0, + 0.0, + 0.0, + 22.0, + 0.0, + 0.0, + 4.0, + 9.0, + 11.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 14.0, + 155.0, + 255.0, + 28.0, + 0.0, + 0.0, + 6.0, + 4.0, + 0.0, + 5.0, + 150.0, + 210.0, + 91.0, + 17.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 14.0, + 40.0, + 250.0, + 91.0, + 0.0, + 0.0, + 7.0, + 0.0, + 0.0, + 24.0, + 0.0, + 10.0, + 130.0, + 183.0, + 147.0, + 11.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 207.0, + 146.0, + 4.0, + 0.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 25.0, + 237.0, + 29.0, + 0.0, + 12.0, + 0.0, + 0.0, + 14.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 13.0, + 0.0, + 15.0, + 7.0, + 0.0, + 9.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 0.0, + 4.0, + 3.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ]; + return [{data: values, shape: shape, name: 'pixel'}]; + } +} +/* eslint-enable */ diff --git a/web/src/gpu/gpu.es6 b/web/src/gpu/gpu.es6 new file mode 100644 index 0000000000..f2c1c4c5ea --- /dev/null +++ b/web/src/gpu/gpu.es6 @@ -0,0 +1,376 @@ +/* eslint-disable */ +import VSHADER from '../shader/v_shader'; +/** + * @file gpu运算 + * @author yangmingming + */ +export default class gpu { + constructor(opts = {}) { + this.opts = opts; + opts.width_raw_canvas = Number(opts.width_raw_canvas) || 512; + opts.height_raw_canvas = Number(opts.height_raw_canvas) || 512; + let canvas = opts.el ? opts.el : document.createElement('canvas'); + canvas.width = opts.width_raw_canvas; + canvas.height = opts.height_raw_canvas; + this.gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); + this.gl.viewport(0, 0, canvas.width, canvas.height); + // Attempt to activate the extension, returns null if unavailable + this.textureFloat = this.gl.getExtension('OES_texture_float'); + // this.setOutProps(); + this.initCache(); + console.log('float extension is started or not? ' + !!this.textureFloat); + console.log('WebGl版本是 ' + this.gl.getParameter(this.gl.SHADING_LANGUAGE_VERSION)); + } + + initCache() { + // 运行次数 + this.times = 0; + const gl = this.gl; + // 缓存每个op的texture + // this.textures = []; + // 顶点数据 + let vertices = new Float32Array([ + -1.0, 1.0, 0.0, 1.0, + -1.0, -1.0, 0.0, 0.0, + 1.0, 1.0, 1.0, 1.0, + 1.0, -1.0, 1.0, 0.0]); + this.vertexBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); + // shader + this.vertexShader = null; + // 生成vertextShader + this.initShader(VSHADER); + this.fragmentShader = null; + // 上一个texture + this.prevTexture = null; + // 当前op输出texture + this.currentTexture = null; + // 帧缓存 + this.frameBuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer); + // 计算texture cache, 最多3个 + this.cacheTextures = [gl.createTexture(), gl.createTexture(), gl.createTexture()]; + // texture buffer + this.textureBuffer = [gl.createTexture(), gl.createTexture()]; + // program + this.programs = [gl.createProgram(), gl.createProgram()]; + this.program = this.programs[0]; + this.textureBufferIndex = 0; + for (let i = 0; i < 2; i++) { + gl.bindTexture(gl.TEXTURE_2D, this.textureBuffer[i]); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.bindTexture(gl.TEXTURE_2D, null); + } + } + + runVertexShader() { + const gl = this.gl; + let aPosition = gl.getAttribLocation(this.program, 'position'); + // Turn on the position attribute + gl.enableVertexAttribArray(aPosition); + // Bind the position buffer. + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 16, 0); + } + + setOutProps(opts) { + this.width_shape_out = opts.width_shape || 1; + this.height_shape_out = opts.height_shape || 1; + this.width_texture_out = opts.width_texture || 1; + this.height_texture_out = opts.height_texture || 1; + this.total_shape = opts.total_shape || 0; + } + + isFloatingTexture() { + return (this.textureFloat !== null); + } + + attachShader(fshader) { + const gl = this.gl; + let index = this.textureBufferIndex % 2; + const program = this.programs[index]; + this.program = program; + if (this.times < 2) { + gl.attachShader(program, this.vertexShader); + } + this.textureBufferIndex = (this.textureBufferIndex + 1) >= 2 ? 0 : 1; + this.gl.attachShader(program, fshader); + gl.linkProgram(program); + gl.useProgram(program); + if (this.times++ < 2) { + this.runVertexShader(); + } + if (!!this.fragmentShader) { + const cache = this.programs[(index + 1) % 2]; + gl.detachShader(cache, this.fragmentShader); + // gl.deleteShader(this.fragmentShader); + } + this.fragmentShader = fshader; + } + + create(vshaderCode, fshaderCode) { + let gl = this.gl; + if (this.program) { + this.dispose(); + } + // 创建 & 绑定程序对象 + let program = this.program = gl.createProgram(); + // 创建&绑定vertex&frament shader + this.initShader(vshaderCode); + this.fragmentShader = this.initShader(fshaderCode, 'fragment'); + this.gl.attachShader(program, this.vertexShader); + this.gl.attachShader(program, this.fragmentShader); + gl.linkProgram(program); + gl.useProgram(program); + + let aPosition = gl.getAttribLocation(program, 'position'); + // Turn on the position attribute + gl.enableVertexAttribArray(aPosition); + // Bind the position buffer. + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 16, 0); + } + + /** + * 初始化shader + * @param code shader代码 + * @param type shader类型 + * @return {object} 初始化成功返回shader + */ + initShader(code, type = 'vertex') { + const shaderType = type === 'vertex' ? this.gl.VERTEX_SHADER : this.gl.FRAGMENT_SHADER; + let shader; + if (type === 'vertex' && this.vertexShader) { + shader = this.vertexShader; + } else { + shader = this.gl.createShader(shaderType); + if (type === 'vertex') { + this.vertexShader = shader; + } + this.gl.shaderSource(shader, code); + this.gl.compileShader(shader); + if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { + throw new Error("compile: " + this.gl.getShaderInfoLog(shader)); + } + } + + return shader; + } + + /** + * 更新fragment shader + * @param code shader代码 + * @return {boolean} 更新成功过返回true + */ + updateShader(code) { + this.gl.useProgram(this.program); + // 删除fragment shader + if (this.fragmentShader) { + this.gl.detachShader(this.program, this.fragmentShader); + this.gl.deleteShader(this.fragmentShader); + // 删除texture + this.gl.deleteTexture(this.texture); + } + // 更新 + this.fragmentShader = this.initShader(code, 'fragment'); + return true; + } + + /** + * 创建并绑定framebuffer, 之后attach a texture + * @param {WebGLTexture} texture 材质 + * @returns {WebGLFramebuffer} The framebuffer + */ + attachFrameBuffer(opts = {}) { + this.prevTexture = this.currentTexture; + this.currentTexture = this.textureBuffer[this.textureBufferIndex % 2]; + // this.textureBufferIndex = (this.textureBufferIndex + 1) >= 2 ? 0 : 1; + const gl = this.gl; + gl.framebufferTexture2D(gl.FRAMEBUFFER, // The target is always a FRAMEBUFFER. + gl.COLOR_ATTACHMENT0, // We are providing the color buffer. + gl.TEXTURE_2D, // This is a 2D image texture. + this.currentTexture, // The texture. + 0 // 0, we aren't using MIPMAPs + ); + gl.viewport( + 0, + 0, + opts.width_texture_out || this.width_texture_out, + opts.height_texture_out || this.height_texture_out + ); + return this.frameBuffer; + } + + // 帧缓存检测 + frameBufferIsComplete() { + let gl = this.gl; + let message; + let status; + let value; + + status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + + switch (status) + { + case gl.FRAMEBUFFER_COMPLETE: + message = "Framebuffer is complete."; + value = true; + break; + case gl.FRAMEBUFFER_UNSUPPORTED: + message = "Framebuffer is unsupported"; + value = false; + break; + case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + message = "Framebuffer incomplete attachment"; + value = false; + break; + case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + message = "Framebuffer incomplete (missmatched) dimensions"; + value = false; + break; + case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + message = "Framebuffer incomplete missing attachment"; + value = false; + break; + default: + message = "Unexpected framebuffer status: " + status; + value = false; + } + return {isComplete: value, message: message}; + } + + /** + * 更新材质 + * @param {WebGLTexture} texture 材质对象 + * @param {number} type 材质类型. FLOAT, UNSIGNED_BYTE, etc. + * @param {Float32Array[]} data 材质数据 + */ + refreshTexture(texture, type, data) { + const gl = this.gl; + // Bind the texture so the following methods effect it. + gl.bindTexture(gl.TEXTURE_2D, texture); + + // Replace the texture data + gl.texSubImage2D(gl.TEXTURE_2D, // Target, matches bind above. + 0, // Level of detail. + 0, // xOffset + 0, // yOffset + this.opts.width_raw_canvas, // Width - normalized to s. + this.opts.height_raw_canvas, // Height - normalized to t. + gl.RGBA, // Format for each pixel. + type, // Data type for each chanel. + data); // Image data in the described format. + + // Unbind the texture. + gl.bindTexture(gl.TEXTURE_2D, null); + + return texture; + } + + /** + * 初始化材质 + * @param {int} index 材质索引 + * @param {string} tSampler 材质名称 + * @param {Object} bufferData 数据 + */ + initTexture(index, item) { + const gl = this.gl; + let texture; + if (!item.data) { + texture = this.prevTexture; + } else { + // texture = gl.createTexture(); + texture = this.cacheTextures[index]; + // this.textures.push(texture); + } + gl.activeTexture(gl[`TEXTURE${index}`]); + gl.bindTexture(gl.TEXTURE_2D, texture); + if (item.data) { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, item.width_texture || this.opts.width_raw_canvas, + item.height_texture || this.opts.height_raw_canvas, 0, + gl.RGBA, gl.FLOAT, item.data, 0); + } + } + + getUniformLoc(name) { + let loc = this.gl.getUniformLocation(this.program, name); + if (loc === null) throw `getUniformLoc ${name} err`; + return loc; + } + + // 生成帧缓存的texture + makeTexure(type, data, opts = {}) { + const gl = this.gl; + let index = this.textureBufferIndex % 2; + let texture = this.textureBuffer[index]; + gl.bindTexture(gl.TEXTURE_2D, texture); + + // Pixel format and data for the texture + gl.texImage2D(gl.TEXTURE_2D, // Target, matches bind above. + 0, // Level of detail. + gl.RGBA, // Internal format. + opts.width_texture_out || this.width_texture_out, + opts.height_texture_out || this.height_texture_out, + 0, // Always 0 in OpenGL ES. + gl.RGBA, // Format for each pixel. + type, // Data type for each chanel. + data); // Image data in the described format, or null. + // Unbind the texture. + // gl.bindTexture(gl.TEXTURE_2D, null); + this.attachFrameBuffer(); + + return texture; + } + + render(data = []) { + const gl = this.gl; + let textureIndex = 0; + // 输入数据 + data.forEach(item => { + if (item.type === 'texture') { + this.initTexture(textureIndex, item); + gl.uniform1i(this.getUniformLoc(item.variable + '_' + item.tensor), textureIndex++); + } else if (item.type === 'uniform') { + gl[item.setter](this.getUniformLoc(item.variable + '_' + item.tensor), item.data); + } + }); + // gl.clearColor(.0, .0, .0, 1); + // gl.clear(gl.COLOR_BUFFER_BIT); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + } + + compute() { + let gl = this.gl; + let pixels = new Float32Array(this.width_texture_out * this.height_texture_out * 4); + // gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); + gl.readPixels(0, 0, this.width_texture_out, this.height_texture_out, gl.RGBA, gl.FLOAT, pixels, 0); + + let result = []; + for (let i = 0; i < this.width_texture_out * this.height_texture_out; i++) { + result.push(pixels[4 * i]); + } + return result; + } + + dispose() { + const gl = this.gl; + this.cacheTextures.forEach(texture => { + gl.deleteTexture(texture); + }); + this.cacheTextures = []; + this.programs.forEach(program => { + gl.detachShader(program, this.vertexShader); + gl.deleteShader(this.vertexShader); + gl.deleteProgram(program); + }); + this.programs = []; + } +} diff --git a/web/src/index.es6 b/web/src/index.es6 new file mode 100644 index 0000000000..b085e64f69 --- /dev/null +++ b/web/src/index.es6 @@ -0,0 +1,20 @@ +import 'babel-polyfill'; +import Graph from './executor/loader'; +import IO from './executor/io'; +/** + * @file model demo 入口文件 + * @author yangmingming@baidu.com + * + */ +// 'http://mms-xr.cdn.bcebos.com/paddle/mnist/model.json' +const MODEL_URL = '../demo/model/model.json'; +const graphModel = new Graph(); +const model = graphModel.loadGraphModel(MODEL_URL); +const cat = document.getElementById('pic'); +const io = new IO(); + +let inst = model.execute({input: cat}); +let res = inst.read(); +console.dir(['result', res]); +var fileDownload = require('js-file-download'); +fileDownload(res, "result.csv"); diff --git a/web/src/index.html b/web/src/index.html new file mode 100644 index 0000000000..b61f210b38 --- /dev/null +++ b/web/src/index.html @@ -0,0 +1,13 @@ + + + + + paddle web demo + + + + +
+ + + diff --git a/web/src/ops/dummy.js b/web/src/ops/dummy.js new file mode 100644 index 0000000000..120d022cf7 --- /dev/null +++ b/web/src/ops/dummy.js @@ -0,0 +1,12 @@ + +module.exports.create = function (args, scope, gl) { + const output = scope[args.outputs.Out[0]] + return { + inferShape() { + output.dim = output.dim.forEach(d => d === -1 ? 1 : d) + }, + compute() { + console.log(output) + } + } +} \ No newline at end of file diff --git a/web/src/ops/feed.js b/web/src/ops/feed.js new file mode 100644 index 0000000000..e605768466 --- /dev/null +++ b/web/src/ops/feed.js @@ -0,0 +1,15 @@ +module.exports.create = function (args, scope, gl) { + const output = scope[args.outputs.Out[0]] + const input = scope[args.inputs.X[0]] + return { + inferShape() { + output.dim = output.dim.map(d => d === -1 ? 1 : d) + }, + compute() { + console.log(input) + console.log(output) + } + } +} + + diff --git a/web/src/ops/fetch.js b/web/src/ops/fetch.js new file mode 100644 index 0000000000..bfef4f80fe --- /dev/null +++ b/web/src/ops/fetch.js @@ -0,0 +1,12 @@ + +module.exports.create = function (args, scope, gl) { + // const output = scope[args.outputs.Out[0]] + return { + inferShape() { + // output.dim = output.dim.forEach(d => d === -1 ? 1 : d) + }, + compute() { + console.log(`${args.type} is going to be implemented`) + } + } +} \ No newline at end of file diff --git a/web/src/package.json b/web/src/package.json new file mode 100644 index 0000000000..49f0c29c77 --- /dev/null +++ b/web/src/package.json @@ -0,0 +1,11 @@ +{ + "name": "paddel-web", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/web/src/runtime/runtime.es6 b/web/src/runtime/runtime.es6 new file mode 100644 index 0000000000..f95a6a29fd --- /dev/null +++ b/web/src/runtime/runtime.es6 @@ -0,0 +1,69 @@ +/* eslint-disable */ +import Gpu from '../gpu/gpu'; +/** + * @file gpu运行时 + * @author yangmingming + * + */ +export default { + /** + * 初始化, 生成gpu实例 + * @param {Object} opts 运行时参数,包含el:canvas,dim: 256 + * @return {Object} this 实例对象 + */ + init(opts = {}) { + const gpu = this.gpu = new Gpu(opts); + if (gpu.isFloatingTexture()) { + return this; + } else { + return null; + } + }, + + run(opName, opData) { + let time = +Date.now(); + let start = time; + let timeObj = {}; + if (!opData.isPass) { + console.log('跳过当前op:' + opName); + return this; + } + // 设置gpu参数 + const gpu = this.gpu; + gpu.setOutProps(opData.tensor['out']); + // 生成帧缓存材质 + gpu.makeTexure(WebGLRenderingContext.FLOAT, null); + let end = +Date.now(); + let bufferStatus = gpu.frameBufferIsComplete(); + if (bufferStatus.isComplete) { + start = +Date.now(); + timeObj['buferstatus-time'] = start - end; + gpu.attachShader(opData.fshader); + end = +Date.now(); + timeObj['createshader-time'] = end - start; + timeObj['jsTime'] = end - time; + statistic.push(timeObj); + // 开始计算 + this.gpu.render(opData.renderData); + return this; + } else { + return bufferStatus.message; + } + }, + + /** + * 读取op计算结果, 并返回数据 + */ + read() { + return this.gpu.compute(); + }, + + createFragmentShader(fsCode) { + return this.gpu.initShader(fsCode, 'fragment'); + }, + + // 释放资源 + dispose() { + this.gpu.dispose(); + } +}; diff --git a/web/src/shader/atom/common_func.es6 b/web/src/shader/atom/common_func.es6 new file mode 100644 index 0000000000..68ac47f652 --- /dev/null +++ b/web/src/shader/atom/common_func.es6 @@ -0,0 +1,35 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +export default ` +// 激活函数 +float prelu(float x, float p, float b) { + float result = x; + if (x < 0.0) { + result = x * p; + } + return result; +} + +float leakyRelu(float x, float p, float b) { + float result = max(x, x * p); + return result; +} + +float scale(float x, float p, float b) { + float result = p * x + b; + return result; +} + +float sigmoid(float x, float y, float z) { + float result = 1.0 / (1.0 + exp(-x)); + return result; +} + +float softmax(float x, float p, float b) { + float result = exp(x) / (10.0 * exp(x)); + return result; +} +`; diff --git a/web/src/shader/atom/common_params.es6 b/web/src/shader/atom/common_params.es6 new file mode 100644 index 0000000000..a99019f223 --- /dev/null +++ b/web/src/shader/atom/common_params.es6 @@ -0,0 +1,36 @@ +/* eslint-disable */ +/** + * @file 公共参数 + * @author yangmingming + */ +export default ` + // varying变量 + // 顶点shader透传的材质坐标 + varying vec2 vCoord; + // 扩展shader的ivec类型 + // struct ivec5 { + // int x; + // int y; + // int z; + // int w; + // int u; + // }; + // struct ivec6 { + // int x; + // int y; + // int z; + // int w; + // int u; + // int v; + // }; + // dynamic的input数据 + const float multi_value = float(MULTI_VALUE); + const float bias_value = float(BIAS_VALUE); + + // 输出数据 + const int width_shape_out = WIDTH_SHAPE_OUT; + const int height_shape_out = HEIGHT_SHAPE_OUT; + const int width_texture_out = WIDTH_TEXTURE_OUT; + const int height_texture_out = HEIGHT_TEXTURE_OUT; + const int channel_out = CHANNEL_OUT; +`; diff --git a/web/src/shader/atom/getArrayIndexFromTensorPos.es6 b/web/src/shader/atom/getArrayIndexFromTensorPos.es6 new file mode 100644 index 0000000000..b0d17e4f3b --- /dev/null +++ b/web/src/shader/atom/getArrayIndexFromTensorPos.es6 @@ -0,0 +1,20 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +export default ` +// TENSOR_TYPE, tensor坐标的类型,ivec4 +// TENSOR_NAME, tensor name + +// 获取tensor坐标对应数组中的索引 +// uniform int numbers_shape_TENSOR_NAME[LENGTH_SHAPE_TENSOR_NAME]; + +int getArrayIndexFromTensorPos_TENSOR_NAME(TENSOR_TYPE tensorPos) { + int index = 0; + for (int i = 0; i < length_shape_TENSOR_NAME; i++) { + index += tensorPos[i] * numbers_shape_TENSOR_NAME[i]; + } + return index; +} +`; diff --git a/web/src/shader/atom/getArrayIndexFromTexturePos.es6 b/web/src/shader/atom/getArrayIndexFromTexturePos.es6 new file mode 100644 index 0000000000..1cff8a703e --- /dev/null +++ b/web/src/shader/atom/getArrayIndexFromTexturePos.es6 @@ -0,0 +1,18 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +// TEXTURE_NAME, texture name +// WIDTH_TEXTURE_NAME_VALUE, texture的宽度 + +// 获取材质元素在数组中的索引 +// const int width_TEXTURE_NAME = WIDTH_TEXTURE_NAME_VALUE; +export default ` +int getArrayIndexFromTexturePos_TEXTURE_NAME(vec3 pos) { + int x = int(floor(pos.x)); + int y = int(floor(pos.y)); + int d = int(floor(pos.z)); + return (width_TEXTURE_NAME * y + x) * 4 + d; +} +`; diff --git a/web/src/shader/atom/getOutputTensorPos.es6 b/web/src/shader/atom/getOutputTensorPos.es6 new file mode 100644 index 0000000000..fafe6a198e --- /dev/null +++ b/web/src/shader/atom/getOutputTensorPos.es6 @@ -0,0 +1,16 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +export default ` +ivec4 getOutputTensorPos() { + // 获取原始长度 + vec2 outCoord = moveTexture2PosToReal_texture_out(vCoord.xy); + int x = int(outCoord.x / float(channel_out)); + int c = int(mod(outCoord.x, float(channel_out))); + int y = int(mod(outCoord.y, float(height_shape_out))); + int b = int(outCoord.y / float(height_shape_out)); + return ivec4(b, c, y, x); +} +`; diff --git a/web/src/shader/atom/getPixelsFromTexturePos.es6 b/web/src/shader/atom/getPixelsFromTexturePos.es6 new file mode 100644 index 0000000000..052bc93759 --- /dev/null +++ b/web/src/shader/atom/getPixelsFromTexturePos.es6 @@ -0,0 +1,11 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +// TEXTURE_NAME, tensor name +// 获取材质中的像素 +// uniform sampler2D TEXTURE_NAME; +export default ` +#define getPixelsFromTexturePos_TEXTURE_NAME(pos) texture2D(TEXTURE_NAME, pos) +`; diff --git a/web/src/shader/atom/getRangePowSumFromArrayIndex.es6 b/web/src/shader/atom/getRangePowSumFromArrayIndex.es6 new file mode 100644 index 0000000000..7361cc1946 --- /dev/null +++ b/web/src/shader/atom/getRangePowSumFromArrayIndex.es6 @@ -0,0 +1,15 @@ +/* eslint-disable */ +/** + * @file 公共方法, 获取[H, W]的power的总和 + * @author yangmingming + */ +export default ` +float getRangePowSumFromArrayIndex_TEXTURE_NAME(int start, float p, float mean) { + float result = 0.0; + for (int i = 0; i < (width_shape_TENSOR_NAME * height_shape_TENSOR_NAME); i++) { + vec3 pos = getTexturePosFromArrayIndex_TEXTURE_NAME(i + start); + result += pow(getValueFromTexturePos_TEXTURE_NAME(pos) - mean, p); + } + return result; +} +`; diff --git a/web/src/shader/atom/getRangeSumFromArrayIndex.es6 b/web/src/shader/atom/getRangeSumFromArrayIndex.es6 new file mode 100644 index 0000000000..47616e6b5c --- /dev/null +++ b/web/src/shader/atom/getRangeSumFromArrayIndex.es6 @@ -0,0 +1,15 @@ +/* eslint-disable */ +/** + * @file 公共方法, 获取[H, W]的总和 + * @author yangmingming + */ +export default ` +float getRangeSumFromArrayIndex_TEXTURE_NAME(int start) { + float result = 0.0; + for (int i = 0; i < (width_shape_TENSOR_NAME * height_shape_TENSOR_NAME); i++) { + vec3 pos = getTexturePosFromArrayIndex_TEXTURE_NAME(i + start); + result += getValueFromTexturePos_TEXTURE_NAME(pos); + } + return result; +} +`; diff --git a/web/src/shader/atom/getTensorPosFromArrayIndex.es6 b/web/src/shader/atom/getTensorPosFromArrayIndex.es6 new file mode 100644 index 0000000000..e59e9f12d6 --- /dev/null +++ b/web/src/shader/atom/getTensorPosFromArrayIndex.es6 @@ -0,0 +1,18 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +// TENSOR_NAME, tensor name +// 获取数组元素索引为N的元素,在tensor上的坐标ivec4(batch, channel, height, width) +export default ` +iTENSOR_TYPE getTensorPosFromArrayIndex_TENSOR_NAME(int n) { + iTENSOR_TYPE pos; + pos[0] = n / numbers_shape_TENSOR_NAME[0]; + for (int i = 1; i < length_shape_TENSOR_NAME; i++) { + n = int(mod(float(n), float(numbers_shape_TENSOR_NAME[i - 1]))); + pos[i] = n / numbers_shape_TENSOR_NAME[i]; + } + return pos; +} +`; diff --git a/web/src/shader/atom/getTexturePosFromArrayIndex.es6 b/web/src/shader/atom/getTexturePosFromArrayIndex.es6 new file mode 100644 index 0000000000..51bb108ec4 --- /dev/null +++ b/web/src/shader/atom/getTexturePosFromArrayIndex.es6 @@ -0,0 +1,25 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +// TEXTURE_NAME, 材质名称 +// WIDTH_TEXTURE_NAME_VALUE, 材质宽度 +// HEIGHT_TEXTURE_NAME_VALUE, 材质高度 + +// 获取数组元素索引为N的元素,在texture上的坐标 +// const int width_TEXTURE_NAME = WIDTH_TEXTURE_NAME_VALUE; +// const int height_TEXTURE_NAME = HEIGHT_TEXTURE_NAME_VALUE; +export default ` +vec3 getTexturePosFromArrayIndex_TEXTURE_NAME(int n) { + vec3 pos; + pos.z = mod(float(n), 4.0); + n /= 4; + int y = n / width_TEXTURE_NAME; + float width = float(width_TEXTURE_NAME); + float x = mod(float(n), width); + pos.x = x / width; + pos.y = float(y) / float(height_TEXTURE_NAME); + return pos; +} +`; diff --git a/web/src/shader/atom/getValueFromTensorPos.es6 b/web/src/shader/atom/getValueFromTensorPos.es6 new file mode 100644 index 0000000000..5a52397053 --- /dev/null +++ b/web/src/shader/atom/getValueFromTensorPos.es6 @@ -0,0 +1,19 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +export default ` +float getValueFromTensorPos_TENSOR_NAME(int r, int g, int b, int a) { + vec4 pixels = texture2D(texture_TENSOR_NAME, vec2((float(a * channel_TENSOR_NAME + g) + 0.5) / float(width_texture_TENSOR_NAME), (float(r * height_shape_TENSOR_NAME + b) + 0.5) / float(height_texture_TENSOR_NAME))); + return pixels.r; +} + +float getValueFromTensorPos_TENSOR_NAME(ivec4 pos) { + float offset = 0.5; + float width = float(pos.a * channel_TENSOR_NAME + pos.g) + offset; + float height = float(pos.r * height_shape_TENSOR_NAME + pos.b) + offset; + vec4 pixels = texture2D(texture_TENSOR_NAME, vec2(width / float(width_texture_TENSOR_NAME), height / float(height_texture_TENSOR_NAME))); + return pixels.r; +} +`; diff --git a/web/src/shader/atom/getValueFromTexturePos.es6 b/web/src/shader/atom/getValueFromTexturePos.es6 new file mode 100644 index 0000000000..f68f3f4a38 --- /dev/null +++ b/web/src/shader/atom/getValueFromTexturePos.es6 @@ -0,0 +1,22 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +// TEXTURE_NAME, tensor name +// 获取材质中的数据 +// uniform sampler2D TEXTURE_NAME; +export default ` +float getValueFromTexturePos_TEXTURE_NAME(vec3 pos) { + vec4 pixels = texture2D(TEXTURE_NAME, pos.xy); + int d = int(pos.z); + if (d == 0) { + return pixels.r; + } else if (d == 1) { + return pixels.g; + } else if (d == 2) { + return pixels.b; + } + return pixels.a; +} +`; diff --git a/web/src/shader/atom/moveTexture2PosToReal.es6 b/web/src/shader/atom/moveTexture2PosToReal.es6 new file mode 100644 index 0000000000..05a7bbfe8f --- /dev/null +++ b/web/src/shader/atom/moveTexture2PosToReal.es6 @@ -0,0 +1,19 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +// TEXTURE_NAME, 材质name +// 材质坐标转化成真实尺寸坐标 +export default ` + +vec2 _2d_shape_TEXTURE_NAME = vec2(float(width_TEXTURE_NAME), float(height_TEXTURE_NAME)); + +vec2 moveTexture2PosToReal_TEXTURE_NAME(vec2 v) { + return v * _2d_shape_TEXTURE_NAME; + // vec2 v2; + // v2.x = v.x * float(width_TEXTURE_NAME); + // v2.y = v.y * float(height_TEXTURE_NAME); + // return v2; +} +`; diff --git a/web/src/shader/atom/prefix.es6 b/web/src/shader/atom/prefix.es6 new file mode 100644 index 0000000000..54a1742088 --- /dev/null +++ b/web/src/shader/atom/prefix.es6 @@ -0,0 +1,14 @@ +/* eslint-disable */ +/** + * @file 预设条件 + * @author yangmingming + */ +export default ` +#ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + precision highp int; +#else + precision mediump float; + precision mediump int; +#endif +`; diff --git a/web/src/shader/atom/prelu.es6 b/web/src/shader/atom/prelu.es6 new file mode 100644 index 0000000000..36b63f8c82 --- /dev/null +++ b/web/src/shader/atom/prelu.es6 @@ -0,0 +1,15 @@ +/* eslint-disable */ +/** + * @file 激活函数 + * @author yangmingming + */ +// 激活函数 +export default ` +float prelu(float x, float p, float b) { + float result = x; + if (x < 0.0) { + result = x * p; + } + return result; +} +`; diff --git a/web/src/shader/atom/scale.es6 b/web/src/shader/atom/scale.es6 new file mode 100644 index 0000000000..05a69f32fb --- /dev/null +++ b/web/src/shader/atom/scale.es6 @@ -0,0 +1,11 @@ +/* eslint-disable */ +/** + * @file 激活函数 + * @author yangmingming + */ +export default ` +float scale(float x, float p, float b) { + float result = p * x + b; + return result; +} +`; diff --git a/web/src/shader/atom/sigmoid.es6 b/web/src/shader/atom/sigmoid.es6 new file mode 100644 index 0000000000..a6c3eac0c9 --- /dev/null +++ b/web/src/shader/atom/sigmoid.es6 @@ -0,0 +1,12 @@ +/* eslint-disable */ +/** + * @file 激活函数 + * @author yangmingming + */ +// 激活函数 +export default ` +float sigmoid(float x, float y, float z) { + float result = 1.0 / (1.0 + exp(-x)); + return result; +} +`; diff --git a/web/src/shader/atom/softmax.es6 b/web/src/shader/atom/softmax.es6 new file mode 100644 index 0000000000..04bd1a2006 --- /dev/null +++ b/web/src/shader/atom/softmax.es6 @@ -0,0 +1,14 @@ +/* eslint-disable */ +/** + * @file softmax激活函数 + * @author yangmingming + */ +export default ` +float softmax(float x, float p, float b) { + float result = x; + if (x < 0.0) { + result = x * p; + } + return result; +} +`; diff --git a/web/src/shader/atom/suffix.es6 b/web/src/shader/atom/suffix.es6 new file mode 100644 index 0000000000..97eb819ffd --- /dev/null +++ b/web/src/shader/atom/suffix.es6 @@ -0,0 +1,17 @@ +/* eslint-disable */ +/** + * @file 公共方法-尾部, 方法1: 获取输出坐标 + * @author yangmingming + */ +export default ` +vec2 _2d_shape_texture_out = vec2(float(width_texture_out), float(height_texture_out)); +ivec4 getOutputTensorPos() { + // 获取原始长度 + vec2 outCoord = vCoord.xy * _2d_shape_texture_out; + int x = int(outCoord.x / float(channel_out)); + int c = int(mod(outCoord.x, float(channel_out))); + int y = int(mod(outCoord.y, float(height_shape_out))); + int b = int(outCoord.y / float(height_shape_out)); + return ivec4(b, c, y, x); +} +`; diff --git a/web/src/shader/atom/type_ivec56.es6 b/web/src/shader/atom/type_ivec56.es6 new file mode 100644 index 0000000000..4b70e44ca8 --- /dev/null +++ b/web/src/shader/atom/type_ivec56.es6 @@ -0,0 +1,23 @@ +/* eslint-disable */ +/** + * @file 新增类型 + * @author yangmingming + */ +// 扩展shader的ivec类型 +export default ` +struct ivec5 { + int x; + int y; + int z; + int w; + int u; +}; +struct ivec6 { + int x; + int y; + int z; + int w; + int u; + int v; +}; +`; diff --git a/web/src/shader/batchnorm/conf.es6 b/web/src/shader/batchnorm/conf.es6 new file mode 100644 index 0000000000..7cdfd95e63 --- /dev/null +++ b/web/src/shader/batchnorm/conf.es6 @@ -0,0 +1,57 @@ +/* eslint-disable */ +/** + * @file batchnorm的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + }, + { + func: 'getPixelsFromTexturePos', + conf: { + TEXTURE_NAME: 'texture_scale' + } + } + ], + conf: [ + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + 'TOTAL_SHAPE_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + + 'EPSILON', + 'WIDTH_TEXTURE_SCALE', + 'HEIGHT_TEXTURE_SCALE', + 'MULTI_VALUE', + 'BIAS_VALUE', + 'ACTIVE_FUNCTION' + ], + input: [ + { + tensor: 'scale', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/batchnorm/main.es6 b/web/src/shader/batchnorm/main.es6 new file mode 100644 index 0000000000..ae702250ff --- /dev/null +++ b/web/src/shader/batchnorm/main.es6 @@ -0,0 +1,18 @@ +/* eslint-disable */ +/** + * @file softmax主函数 + * @author yangmingming + */ +export default ` +// start函数 +void main(void) { + // 输出数据 + ivec4 oPos = getOutputTensorPos(); + float o = getValueFromTensorPos_origin(oPos); + // 归一化数据 + vec4 scale = getPixelsFromTexturePos_texture_scale(vec2((float(int(oPos.g)) + 0.5) / float(width_texture_scale), 0.0)); + float x = (o - scale[3]) / sqrt(scale[2] + epsilon); + float res = scale[0] * x + scale[1]; + gl_FragColor.r = res; +} +`; diff --git a/web/src/shader/batchnorm/params.es6 b/web/src/shader/batchnorm/params.es6 new file mode 100644 index 0000000000..2592344029 --- /dev/null +++ b/web/src/shader/batchnorm/params.es6 @@ -0,0 +1,24 @@ +/* eslint-disable */ +/** + * @file batchnorm参数文件 + * @author yangmingming + */ +export default ` +// 输入数据 +const int width_shape_origin = WIDTH_SHAPE_ORIGIN; +const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; +const int length_shape_origin = LENGTH_SHAPE_ORIGIN; +const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; +const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; +const int channel_origin = CHANNEL_ORIGIN; +const int total_shape_origin = TOTAL_SHAPE_ORIGIN; + +// 计算数据 +const float epsilon = float(EPSILON); +const int width_texture_scale = WIDTH_TEXTURE_SCALE; +const int height_texture_scale = HEIGHT_TEXTURE_SCALE; + +// 输入数据 +uniform sampler2D texture_origin; +uniform sampler2D texture_scale; +`; diff --git a/web/src/shader/conv2d/conf.es6 b/web/src/shader/conv2d/conf.es6 new file mode 100644 index 0000000000..c66c40a9d3 --- /dev/null +++ b/web/src/shader/conv2d/conf.es6 @@ -0,0 +1,85 @@ +/* eslint-disable */ +/** + * @file conv2d的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + }, + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'filter' + } + } + ], + conf: [ + 'LENGTH_SHAPE_FILTER', + 'WIDTH_SHAPE_FILTER', + 'HEIGHT_SHAPE_FILTER', + 'WIDTH_TEXTURE_FILTER', + 'HEIGHT_TEXTURE_FILTER', + 'CHANNEL_FILTER', + + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + + 'STRIDE_HORIZONTAL', + 'STRIDE_VERTICAL', + 'PAD_LEFT', + 'PAD_TOP', + 'DILATION_HORIZONTAL', + 'DILATION_VERTICAL', + 'GROUPS', + 'MULTI_VALUE', + 'BIAS_VALUE', + 'ACTIVE_FUNCTION' + ], + input: [ + // { + // tensor: 'filter', + // variable: 'numbers_shape', + // setter: 'uniform1iv', + // type: 'uniform' + // }, + { + tensor: 'filter', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + // { + // tensor: 'origin', + // variable: 'numbers_shape', + // setter: 'uniform1iv', + // type: 'uniform' + // }, + // { + // tensor: 'out', + // variable: 'numbers_shape', + // setter: 'uniform1iv', + // type: 'uniform' + // } + ] +}; diff --git a/web/src/shader/conv2d/main.es6 b/web/src/shader/conv2d/main.es6 new file mode 100644 index 0000000000..e6513899af --- /dev/null +++ b/web/src/shader/conv2d/main.es6 @@ -0,0 +1,48 @@ +/* eslint-disable */ +/** + * @file 主函数 + * @author yangmingming + */ +export default ` + // start函数 + void main(void) { + ivec4 oPos = getOutputTensorPos(); + int x = oPos.a; + int c = oPos.g; + int y = oPos.b; + int b = oPos.r; + float res = 0.0; + + // 获取output的坐标 + int oTensorChannel = (c / (channel_out / groups)) * channel_filter; + int oy = y * stride_v - padTop; + for (int fy = 0; fy < height_shape_filter; fy++) { + if (oy >= height_shape_origin) { + break; + } + if (oy < 0) { + oy += dilation_v; + continue; + } + int ox = x * stride_h - padLeft; + for (int fx = 0; fx < width_shape_filter; fx++) { + if (ox >= width_shape_origin) { + break; + } + if (ox < 0) { + ox += dilation_h; + continue; + } + // channel计算 + for (int j = 0; j < channel_filter; j++) { + float f = getValueFromTensorPos_filter(c, j, fy, fx); + float o = getValueFromTensorPos_origin(b, oTensorChannel + j, oy, ox); + res += f * o; + } + ox += dilation_h; + } + oy += dilation_v; + } + gl_FragColor.r = res; + } +`; diff --git a/web/src/shader/conv2d/params.es6 b/web/src/shader/conv2d/params.es6 new file mode 100644 index 0000000000..367072995a --- /dev/null +++ b/web/src/shader/conv2d/params.es6 @@ -0,0 +1,45 @@ +/* eslint-disable */ +/** + * @file 参数文件 + * @author yangmingming + */ +export default ` + // conv2d的input数据 + + // 常量 + // 卷积核 + const int length_shape_filter = LENGTH_SHAPE_FILTER; + const int width_shape_filter = WIDTH_SHAPE_FILTER; + const int height_shape_filter = HEIGHT_SHAPE_FILTER; + const int width_texture_filter = WIDTH_TEXTURE_FILTER; + const int height_texture_filter = HEIGHT_TEXTURE_FILTER; + const int channel_filter = CHANNEL_FILTER; + + // 输入数据 + const int width_shape_origin = WIDTH_SHAPE_ORIGIN; + const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; + const int length_shape_origin = LENGTH_SHAPE_ORIGIN; + const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; + const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; + const int channel_origin = CHANNEL_ORIGIN; + + // 计算相关 + // 拆分步长 + const int stride_h = STRIDES_X; + const int stride_v = STRIDES_Y; + // padding的数目 + const int padLeft = PADDINGS_X; + const int padTop = PADDINGS_Y; + // dilation膨胀系数 + const int dilation_h = DILATIONS_X; + const int dilation_v = DILATIONS_Y; + // groups + const int groups = GROUPS; + + // uniform变量 + // 卷积核 + uniform sampler2D texture_filter; + + // 输入数据 + uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/dynamic/conf.es6 b/web/src/shader/dynamic/conf.es6 new file mode 100644 index 0000000000..2541ecf755 --- /dev/null +++ b/web/src/shader/dynamic/conf.es6 @@ -0,0 +1,34 @@ +/* eslint-disable */ +/** + * @file dynamic的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getPixelsFromTexturePos', + conf: { + TEXTURE_NAME: 'texture_origin' + } + } + ], + conf: [ + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + + 'MULTI_VALUE', + 'BIAS_VALUE', + 'ACTIVE_FUNCTION' + ], + input: [ + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/dynamic/main.es6 b/web/src/shader/dynamic/main.es6 new file mode 100644 index 0000000000..f747a477d7 --- /dev/null +++ b/web/src/shader/dynamic/main.es6 @@ -0,0 +1,14 @@ +/* eslint-disable */ +/** + * @file 主函数 + * @author yangmingming + */ +export default ` +// start函数 +void main(void) { + // 输出数据 + float o = getPixelsFromTexturePos_texture_origin(vCoord).r; + float res = ACTIVE_FUNCTION(o, multi_value, bias_value); + gl_FragColor.r = res; +} +`; diff --git a/web/src/shader/dynamic/params.es6 b/web/src/shader/dynamic/params.es6 new file mode 100644 index 0000000000..3eb0295466 --- /dev/null +++ b/web/src/shader/dynamic/params.es6 @@ -0,0 +1,9 @@ +/* eslint-disable */ +/** + * @file 参数文件 + * @author yangmingming + */ +export default ` + // 输入数据 + uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/elementwise_add/conf.es6 b/web/src/shader/elementwise_add/conf.es6 new file mode 100644 index 0000000000..a1cef63cad --- /dev/null +++ b/web/src/shader/elementwise_add/conf.es6 @@ -0,0 +1,56 @@ +/* eslint-disable */ +/** + * @file 加法的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getPixelsFromTexturePos', + conf: { + TEXTURE_NAME: 'texture_origin' + } + }, + { + func: 'getPixelsFromTexturePos', + conf: { + TEXTURE_NAME: 'texture_counter' + } + } + ], + conf: [ + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'TOTAL_SHAPE_COUNTER', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + + 'AXIS', + 'MULTI_VALUE', + 'BIAS_VALUE', + 'ACTIVE_FUNCTION' + ], + input: [ + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'counter', + variable: 'data', + setter: 'uniform1fv', + type: 'uniform' + } + ] +}; diff --git a/web/src/shader/elementwise_add/main.es6 b/web/src/shader/elementwise_add/main.es6 new file mode 100644 index 0000000000..5e9b1bd125 --- /dev/null +++ b/web/src/shader/elementwise_add/main.es6 @@ -0,0 +1,17 @@ +/* eslint-disable */ +/** + * @file 加法主函数 + * @author yangmingming + */ +export default ` +// start函数 +void main(void) { + // 输出数据 + ivec4 oPos = getOutputTensorPos(); + int index = oPos[axis]; + float o = getPixelsFromTexturePos_texture_origin(vCoord).r; + float c = getValueFromCounter(index); + float res = ACTIVE_FUNCTION(o + c, multi_value, bias_value); + gl_FragColor.r = res; +} +`; diff --git a/web/src/shader/elementwise_add/params.es6 b/web/src/shader/elementwise_add/params.es6 new file mode 100644 index 0000000000..4ca394ef83 --- /dev/null +++ b/web/src/shader/elementwise_add/params.es6 @@ -0,0 +1,20 @@ +/* eslint-disable */ +/** + * @file 加法参数 + * @author yangmingming + */ +export default ` + // 输入数据 + const int axis = AXIS; + // const int total_shape_counter = TOTAL_SHAPE_COUNTER; + uniform float data_counter[TOTAL_SHAPE_COUNTER]; + uniform sampler2D texture_origin; + float getValueFromCounter(int index) { + for (int i = 0; i < TOTAL_SHAPE_COUNTER; i++) { + if (i == index) { + return data_counter[i]; + } + } + return 0.0; + } +`; diff --git a/web/src/shader/mul/conf.es6 b/web/src/shader/mul/conf.es6 new file mode 100644 index 0000000000..25b6575757 --- /dev/null +++ b/web/src/shader/mul/conf.es6 @@ -0,0 +1,56 @@ +/* eslint-disable */ +/** + * @file mul的配置文件 + * @author yangmingming zhangmiao06 + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'counter' + } + }, + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + } + ], + conf: [ + 'LENGTH_SHAPE_COUNTER', + 'WIDTH_SHAPE_COUNTER', + 'HEIGHT_SHAPE_COUNTER', + 'WIDTH_TEXTURE_COUNTER', + 'HEIGHT_TEXTURE_COUNTER', + 'CHANNEL_COUNTER', + + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT' + ], + input: [ + { + tensor: 'counter', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/mul/main.es6 b/web/src/shader/mul/main.es6 new file mode 100644 index 0000000000..4628b96ec3 --- /dev/null +++ b/web/src/shader/mul/main.es6 @@ -0,0 +1,18 @@ +/* eslint-disable */ +/** + * @file mul主函数 + */ +export default ` +// start函数 +void main(void) { + float res = 0.0; + // 获取output的坐标 + ivec4 out_pos = getOutputTensorPos(); + for (int j = 0; j < width_shape_origin; j++) { + float c = getValueFromTensorPos_counter(out_pos[0], out_pos[1], j, out_pos[3]); + float o = getValueFromTensorPos_origin(out_pos[0], out_pos[1], out_pos[2], j); + res += c * o; + } + gl_FragColor.r = res; +} +`; diff --git a/web/src/shader/mul/params.es6 b/web/src/shader/mul/params.es6 new file mode 100644 index 0000000000..4f9ec64503 --- /dev/null +++ b/web/src/shader/mul/params.es6 @@ -0,0 +1,27 @@ +/* eslint-disable */ +/** + * @file mul参数文件 + */ +export default ` +// mul的input数据 +// 常量 +// 输入数据 +const int length_shape_counter = LENGTH_SHAPE_COUNTER; +const int width_shape_counter = WIDTH_SHAPE_COUNTER; +const int height_shape_counter = HEIGHT_SHAPE_COUNTER; +const int width_texture_counter = WIDTH_TEXTURE_COUNTER; +const int height_texture_counter = HEIGHT_TEXTURE_COUNTER; +const int channel_counter = CHANNEL_COUNTER; + +const int width_shape_origin = WIDTH_SHAPE_ORIGIN; +const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; +const int length_shape_origin = LENGTH_SHAPE_ORIGIN; +const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; +const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; +const int channel_origin = CHANNEL_ORIGIN; + +// uniform变量 +// 输入数据 +uniform sampler2D texture_counter; +uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/pool2d/conf.es6 b/web/src/shader/pool2d/conf.es6 new file mode 100644 index 0000000000..9d2ca4d0db --- /dev/null +++ b/web/src/shader/pool2d/conf.es6 @@ -0,0 +1,47 @@ +/* eslint-disable */ +/** + * @file pool2d的配置文件 + * @author yangmingming zhangmiao06 + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + } + ], + conf: [ + 'KSIZE_X', + 'KSIZE_Y', + 'TYPE_POOL', + + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + + 'STRIDES_X', + 'STRIDES_Y', + 'PADDING_X', + 'PADDING_Y' + ], + input: [ + // texture类型,若添加from: 'prev', 表示读取上一个op的产出 + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/pool2d/main.es6 b/web/src/shader/pool2d/main.es6 new file mode 100644 index 0000000000..df295618a4 --- /dev/null +++ b/web/src/shader/pool2d/main.es6 @@ -0,0 +1,49 @@ +/* eslint-disable */ +/** + * @file pool2d主函数 + */ +export default ` +// start函数 +void main(void) { + float res = (-1.0 / exp(-20.0)); + // 获取output的坐标 + ivec4 out_pos = getOutputTensorPos(); + // X、Y方向的移动步长 + int count_pool = 0; + int oy_base = out_pos[2] * stride_v - padTop; + int ox_base = out_pos[3] * stride_h - padLeft; + for (int fy = 0; fy < height_shape_pool; fy++) { + int oy = oy_base + fy; + if (oy >= height_shape_origin) { + break; + } + if (oy < 0) { + continue; + } + for (int fx = 0; fx < width_shape_pool; fx++) { + int ox = ox_base + fx; + if (ox >= width_shape_origin) { + break; + } + if (ox < 0) { + continue; + } + // origin数据 + float curr = getValueFromTensorPos_origin(out_pos[0], out_pos[1], oy, ox); + if (type_pool == 1) { + if (curr > res) { + res = curr; + } + } else { + res += curr; + // 在平均池化模式忽略填充值(exclusive默认为true) + count_pool++; + } + } + } + if (type_pool != 1) { + res = res / float(count_pool); + } + gl_FragColor.r = res; +} +`; diff --git a/web/src/shader/pool2d/params.es6 b/web/src/shader/pool2d/params.es6 new file mode 100644 index 0000000000..e836276e32 --- /dev/null +++ b/web/src/shader/pool2d/params.es6 @@ -0,0 +1,30 @@ +/* eslint-disable */ +/** + * @file pool2d参数文件 + */ +export default ` +// 常量 +// 池化大小 +const int width_shape_pool = KSIZE_X; +const int height_shape_pool = KSIZE_Y; +const int type_pool = TYPE_POOL; +// 输入数据 +const int width_shape_origin = WIDTH_SHAPE_ORIGIN; +const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; +const int length_shape_origin = LENGTH_SHAPE_ORIGIN; +const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; +const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; +const int channel_origin = CHANNEL_ORIGIN; + +// 计算相关 +// 拆分步长 +const int stride_h = STRIDES_X; +const int stride_v = STRIDES_Y; +// padding的数目 +const int padLeft = PADDINGS_X; +const int padTop = PADDINGS_Y; + + +// uniform变量 +uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/softmax/conf.es6 b/web/src/shader/softmax/conf.es6 new file mode 100644 index 0000000000..ff04060e3b --- /dev/null +++ b/web/src/shader/softmax/conf.es6 @@ -0,0 +1,28 @@ +/* eslint-disable */ +/** + * @file softmax的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getPixelsFromTexturePos', + conf: { + TEXTURE_NAME: 'texture_origin' + } + } + ], + conf: [ + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'TOTAL_SHAPE_ORIGIN' + ], + input: [ + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/softmax/main.es6 b/web/src/shader/softmax/main.es6 new file mode 100644 index 0000000000..3f7ba78af0 --- /dev/null +++ b/web/src/shader/softmax/main.es6 @@ -0,0 +1,55 @@ +/* eslint-disable */ +/** + * @file softmax主函数 + * @author yangmingming + */ +export default ` +// start函数 +void main(void) { + vec4 v4 = getPixelsFromTexturePos_texture_origin(vCoord); + vec2 onePixel = vec2(1.0 / float(width_texture_origin), 1.0 / float(height_texture_origin)); + float total = 0.0; + float maxValue = getPixelsFromTexturePos_texture_origin(onePixel).r; + int number = 0; + vec4 pixels; + // 求最大 + for (int i = 0; i < height_texture_origin; i++) { + for (int j = 0; j < width_texture_origin; j++) { + pixels = getPixelsFromTexturePos_texture_origin(onePixel * vec2(float(j), float(i))); + number = i * width_texture_origin + j; + if ((number * 4 + 1) < total_shape_origin) { + maxValue = max(pixels.r, maxValue); + } + if ((number * 4 + 2) < total_shape_origin) { + maxValue = max(pixels.g, maxValue); + } + if ((number * 4 + 3) < total_shape_origin) { + maxValue = max(pixels.b, maxValue); + } + if ((number * 4 + 4) < total_shape_origin) { + maxValue = max(pixels.a, maxValue); + } + } + } + // 求和 + for (int i = 0; i < height_texture_origin; i++) { + for (int j = 0; j < width_texture_origin; j++) { + pixels = getPixelsFromTexturePos_texture_origin(onePixel * vec2(float(j), float(i))); + number = i * width_texture_origin + j; + if ((number * 4 + 1) < total_shape_origin) { + total += exp(pixels.r - maxValue); + } + if ((number * 4 + 2) < total_shape_origin) { + total += exp(pixels.g - maxValue); + } + if ((number * 4 + 3) < total_shape_origin) { + total += exp(pixels.b - maxValue); + } + if ((number * 4 + 4) < total_shape_origin) { + total += exp(pixels.a - maxValue); + } + } + } + gl_FragColor = exp(v4 - vec4(maxValue, maxValue, maxValue, maxValue)) / vec4(total, total, total, total) ; +} +`; diff --git a/web/src/shader/softmax/params.es6 b/web/src/shader/softmax/params.es6 new file mode 100644 index 0000000000..514aeba721 --- /dev/null +++ b/web/src/shader/softmax/params.es6 @@ -0,0 +1,15 @@ +/* eslint-disable */ +/** + * @file softmax参数文件 + * @author yangmingming + */ +export default ` +// 输入数据 +const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; +const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; +const int total_shape_origin = TOTAL_SHAPE_ORIGIN; + +// uniform变量 +// 输入数据 +uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/v_shader.es6 b/web/src/shader/v_shader.es6 new file mode 100644 index 0000000000..47ecbc24c2 --- /dev/null +++ b/web/src/shader/v_shader.es6 @@ -0,0 +1,15 @@ +/* eslint-disable */ +/** + * @file 顶点文件 + * @author yangmingming + */ +export default ` +attribute vec4 position; +varying vec2 vCoord; + +void main() { + vCoord.x = (position.x + 1.0) / 2.0; + vCoord.y = (position.y + 1.0) / 2.0; + gl_Position = position; +} +`; diff --git a/web/src/utils/opData.es6 b/web/src/utils/opData.es6 new file mode 100644 index 0000000000..ae9d21f3b8 --- /dev/null +++ b/web/src/utils/opData.es6 @@ -0,0 +1,312 @@ +/* eslint-disable */ +import Utils from './utils'; +import Tensor from './tensor'; +/** + * @file op的数据对象 + * @author yangmingming + * + */ +const keys = [ + 'paddings', + 'strides', + 'dilations', + 'ksize' +]; +// 从tensor对象中获取的数据 +const tensorAttrs = [ + 'length_shape', + 'width_shape', + 'height_shape', + 'width_texture', + 'height_texture', + 'channel', + 'total_shape' +]; +// shader中需要的常量 +const shaderAttrs = { + scale: { + 'bias': 'bias_value', + 'scale': 'multi_value' + }, + pool2d: { + 'pooling_type': 'type_pool' + } +}; +// model的名字和paddle web的tensor名字mapping +const tensorName = { + 'input': 'origin', + 'x': 'origin', + 'filter': 'filter', + 'y': 'counter', + 'output': 'out', + 'out': 'out', + 'scale': 'scale', + 'bias': 'bias', + 'mean': 'mean', + 'variance': 'variance' +}; +// unique behavior +const opBehavior = { + conv2d: [ + 'needBatch' + ], + batchnorm: [ + 'needBatch', + 'mergeTensor' + ], + elementwise_add: [ + 'broadcast', + 'needBatch' + ], + pool2d: [ + 'isMax', + 'needBatch', + 'isGlobalPooling' + ], + relu: [ + 'transToPrelu', + 'needBatch' + ], + leaky_relu: [ + 'transToLeakyrelu', + 'needBatch' + ], + mul: [ + 'reshape', + 'needBatch' + ] +}; +export default class OpData { + constructor(name, input = {}, output = {}, attrs = {}) { + this.name = name; + this.attrs = attrs; + // 是否忽略当前当前op, 使用dropout + this.isPass = this.checkIsPass(); + if (this.isPass) { + this.input = input; + this.output = output; + // op数据, 当前不扩展 + this.data = { + 'active_function': 'scale', + 'multi_value': '1.0', + 'bias_value': '0.0' + }; + // tensor数据 + this.tensor = {}; + this.buildTensor(); + this.buildAttrs(); + } + } + + buildTensor() { + // todo: 是否需要形状对齐 + // todo: 是否需要广播tensor + const tensorData = []; + for (let key in this.input) { + if (this.input.hasOwnProperty(key)) { + const data = this.input[key] || [{}]; + // 默认取第一个数据 + if (tensorName[key.toLowerCase()]) { + data[0].tensorName = tensorName[key.toLowerCase()]; + tensorData.push(data[0]); + } + } + } + // debugger + // todo: 临时删除output里的Y + delete this.output.Y; + // 输出tensor + for (let key in this.output) { + if (this.output.hasOwnProperty(key)) { + // 默认取第一个数据 + const data = this.output[key] || [{}]; + if (tensorName[key.toLowerCase()]) { + data[0].tensorName = tensorName[key.toLowerCase()]; + tensorData.push(data[0]); + } + } + } + // unique behavior + const behavior = opBehavior[this.name] || []; + behavior.forEach(behavior => { + this[behavior](tensorData); + }); + // 生成tensor对象 + tensorData.forEach(data => { + if (data) { + if (data.notTensor) { + this.tensor[data.tensorName] = { + name: data.tensorName, + data: new Float32Array(data.data), + total_shape: data.data.length + }; + } else { + this.tensor[data.tensorName] = new Tensor({ + name: data.tensorName, + shape: data.shape, + data: data.data, + needBatch: data.needBatch || false, + notCompressed: data.notCompressed || false + }); + } + } + }); + // console.dir(['tensors', this.tensor]); + } + + buildAttrs() { + // 计算属性 + for (let key in this.attrs) { + if (this.attrs.hasOwnProperty(key)) { + const item = this.attrs[key]; + if (Object.prototype.toString.call(item) === '[object Array]') { + if (keys.indexOf(key) > -1) { + this.data[key + '_x'] = item[0]; + this.data[key + '_y'] = item[1]; + } + } else { + this.data[key] = item; + // 获取shader所需的数据 + let shaderAttr = shaderAttrs[this.name]; + if (shaderAttr && shaderAttr.hasOwnProperty(key)) { + this.data[shaderAttr[key]] = item; + } + } + } + } + // 获取tensor的数据 + for (let key in this.tensor) { + const tensor = this.tensor[key]; + tensorAttrs.forEach(attr => { + this.data[attr+ '_' + tensor.name] = tensor[attr]; + }); + } + } + + needBatch(tensorData = []) { + tensorData.forEach(data => (data.needBatch = true)); + } + + isGlobalPooling(tensorData = []) { + let counter = tensorData.filter(tensor => (tensor.tensorName === 'origin'))[0] || {}; + let length = counter.shape && counter.shape.length || 0; + if (length > 2 && this.attrs['global_pooling']) { + this.attrs.ksize = [counter.shape[length - 2], counter.shape[length - 1]]; + } + } + + broadcast(tensorData = []) { + const x = tensorData[0]; + const y = tensorData[1]; + let small = y; + if (x.shape.length - y.shape.length < 0) { + small = x; + } + // face model + small.notTensor = true; + return; + + // mobilenet model + // todo: 默认y的shape length是1, 以后需要实现通用版本 + let shape = Utils.getBroadcastShapeInPaddle(x.shape, y.shape, this.attrs['axis']); + // 填充shape数据 + if (small.shape.length === 1) { + const result = []; + small.shape = shape; + let total = shape.reduce((all, num) => all * num); + for (let i = 0; i < small.shape[0]; i++) { + let item = small.data[i]; + for (let j = 0; j < total / shape[0]; j++) { + result.push(item); + } + } + small.data = result; + } + } + + isMax(tensorData = []) { + const type = this.attrs['pooling_type'] === 'max' ? 1 : 0; + this.attrs['pooling_type'] = type; + } + + transToPrelu(tensorData = []) { + this.data['multi_value'] = '0.0'; + this.data['active_function'] = 'prelu'; + } + + transToLeakyrelu(tensorData = []) { + this.data['multi_value'] = this.attrs.alpha; + this.data['active_function'] = 'leakyRelu'; + this.name = 'relu'; + } + + setActiveFunc(tensorData = []) { + this.data['multi_value'] = '0.0'; + this.data['active_function'] = 'softmax'; + + } + + reshape(tensorData = []) { + let input = tensorData[0]; + let counter = tensorData[1]; + if (counter.shape.length > input.shape.length) { + input = tensorData[1]; + counter = tensorData[0]; + } + if (input.shape.length > 2 && counter.shape.length === 2) { + let shape = Utils.getReshapeInPaddle(input.shape, counter.shape, tensorData[2].shape); + input.shape = shape; + } + + } + + mergeTensor(tensorData = []) { + // 融合scale、bias、variance、mean + let constants = ['scale', 'bias', 'variance', 'mean']; + let result = {}; + let data = []; + tensorData.forEach((tensor, index) => { + result[tensor.tensorName] = tensor; + result[tensor.tensorName + 'Index'] = index; + }); + for (let i = 0; i < result[constants[0]].shape[0]; i++) { + data.push(result[constants[0]].data[i]); + data.push(result[constants[1]].data[i]); + data.push(result[constants[2]].data[i]); + data.push(result[constants[3]].data[i]); + } + tensorData[result[constants[0] + 'Index']].data = data; + // 充分利用shader空间 + tensorData[result[constants[0] + 'Index']].notCompressed = true; + tensorData[result[constants[0] + 'Index']].shape[0] *= 4; + tensorData.splice(result[constants[1] + 'Index'], 1, 0); + tensorData.splice(result[constants[2] + 'Index'], 1, 0); + tensorData.splice(result[constants[3] + 'Index'], 1, 0); + } + + checkIsPass() { + if (this.name === 'dropout') { + if (this.attrs['dropout_implementation'] === 'downgrade_in_infer') { + this.name = 'scale'; + this.attrs['scale'] = this.attrs['dropout_prob']; + this.attrs['bias'] = 0.0; + return true; + } + return false; + } + if (this.name === 'depthwise_conv2d') { + this.name = 'conv2d'; + } + return true; + } + + dispose() { + this.input = null; + this.output = null; + this.attrs = null; + for (let key in this.tensor) { + this.tensor[key].dispose(); + } + this.tensor = {}; + } +} diff --git a/web/src/utils/tensor.es6 b/web/src/utils/tensor.es6 new file mode 100644 index 0000000000..3afad8eb2e --- /dev/null +++ b/web/src/utils/tensor.es6 @@ -0,0 +1,139 @@ +/* eslint-disable */ +import Utils from './utils'; +/** + * @file Tensor类 + * @author yangmingming + */ +export default class Tensor { + constructor(opts = {}) { + this.opts = opts; + // 设置tensor名字 + this.name = opts.name; + // tensor的形状 + let shape = this.shape = opts.shape; + // 原始数据个数 + this.total = shape.reduce((all, num) => all * num); + // 图像tensor是否带有batch + if (opts.needBatch && shape.length < 4) { + let batch = []; + for (let i = 0; i < (4 - shape.length); i++) { + batch.push(1); + } + shape = batch.concat(shape); + this.shape = shape; + } + // 获取转换到texture后的信息 + let {zeroNumber, shape: shape_texture} = Utils.getTextureInfoFromTensorShape(shape); + this.shape_texture = shape_texture; + // tensor数据 + let data = []; + if (opts.data && opts.data.length) { + if (!opts.notCompressed) { + let b = shape[0]; + let c = shape[1]; + let h = shape[2]; + let w = shape[3]; + for (let i = 0; i < opts.data.length; i++) { + let j = Math.floor(i / (c * w)); + let k = Math.floor(i % (c * w)); + let b1 = Math.floor(j / h); + let h1 = Math.floor(j % h); + let c1 = Math.floor(k % c); + let w1 = Math.floor(k / c); + let l = b1 * (c * h * w) + c1 * (h * w) + h1 * (w) + w1; + data.push(opts.data[l]); + data.push(0); + data.push(0); + data.push(0); + } + } else { + // batchnorm的scale + this.shape_texture = [4, 1, this.total / 4]; + data = [].concat(opts.data); + } + + this.data = new Float32Array(data); + // 清理缓存 + opts.data = null; + } + } + + /** + * 获取数组下标, shape例子[M, W, H, D] + * @param pos {Array} tensor坐标索引 + * @return {Number} tensor数据 + */ + getValue(pos = []) { + let p = [].concat(pos); + let len = p.length; + let sLen = this.shape.length; + // 补齐 + for (let i = 0; i < (sLen - len); i++) { + p.unshift(0); + } + let index = 0; + for (let i = 0; i < sLen; i++) { + index += p[i] * this.shapeNumbers[i]; + } + return this.data[index]; + } + + get width_texture() { + let length = this.shape_texture.length; + return this.shape_texture[length - 1]; + } + + get height_texture() { + let length = this.shape_texture.length; + return this.shape_texture[length - 2]; + } + + get width_shape() { + let length = this.shape.length; + return this.shape[length - 1]; + } + + get height_shape() { + let length = this.shape.length; + return this.shape[length - 2]; + } + + get channel() { + let length = this.shape.length; + if (length >= 3) { + return this.shape[length - 3]; + } + return 0; + } + + get length_shape() { + return this.shape.length || 0; + } + + /** + * 获取shape对应的个数 + * @return {Array} 和shape长度相等的对应个数 + */ + get numbers_shape() { + let numbers = []; + let sLen = this.shape.length; + for (let i = 0; i < (sLen - 1); i++) { + let number = this.shape.slice(i + 1).reduce((total, num) => total * num); + numbers.push(number); + } + // 和shape长度保持一致 + numbers.push(1); + return numbers; + } + + get total_shape() { + return this.total; + } + + dispose() { + if (this.data) { + this.data = null; + } + } +} +/* eslint-enable */ diff --git a/web/src/utils/utils.es6 b/web/src/utils/utils.es6 new file mode 100644 index 0000000000..6c747c0978 --- /dev/null +++ b/web/src/utils/utils.es6 @@ -0,0 +1,127 @@ +/** + * @file 工具类 + * @author yangmingming + */ +/* eslint-disable */ +export default { + // todo: 适用2维矩阵乘法,以后实现通用版本 + getReshapeInPaddle(inputShape = [], counterShape = [], outShape = []) { + let total = inputShape.reduce((all, num) => all * num); + if (outShape.length === 1) { + return [1, total]; + } else { + return [outShape[0], total / outShape[0]]; + } + }, + + getBroadcastShapeInPaddle(shapeA= [], shapeB = [], axis = 1) { + // todo: 简易版本,以后需要实现一个通用版本 + let bigger = shapeA; + let result = shapeB; + if (shapeA.length - shapeB.length < 0) { + bigger = shapeB; + result = shapeA; + } + return result.concat(bigger.slice(axis)); + }, + + getBroadcastDims(inShape = [], outShape = []) { + const inRank = inShape.length; + const dims = []; + for (let i = 0; i < inRank; i++) { + const dim = inRank - 1 - i; + const a = inShape[dim] || 1; + const b = outShape[outShape.length - 1 - i] || 1; + if (b > 1 && a === 1) { + dims.unshift(dim); + } + } + return dims; + }, + + getBroadcastShape(shapeA = [], shapeB = []) { + const result = []; + const max = Math.max(shapeA.length, shapeB.length); + for (let i = 0; i < max; i++) { + let a = shapeA[shapeA.length - i - 1]; + if (a === null) { + a = 1; + } + let b = shapeB[shapeB.length - i - 1]; + if (b === null) { + b = 1; + } + if (a === 1) { + result.unshift(b); + } else if (b === 1) { + result.unshift(a); + } else if (a !== b) { + return null; + } else { + result.unshift(a); + } + } + return result; + }, + + /** + * 获取texture形状和补0个数 + * @param shape {Array} tensor的形状 + * @return {{shape: *[], zeroNumber: number}} {Object} texture信息 + */ + getTextureInfoFromTensorShape(shape = []) { + let b = shape[0]; + let c = shape[1]; + let h = shape[2]; + let w = shape[3]; + return { + shape: [4, b * h, c * w], + zeroNumber: 0 + }; + }, + + // 获取数组中的最大值和索引 + getMaxItem(datas = []) { + let max = Math.max.apply(null, datas); + let index = datas.indexOf(max); + return {value: max, index}; + }, + + // 压缩 + async loadShader(name) { + let shader = await fetch(this.getShaderFile(name)); + return shader.text(); + }, + + getShaderFile(url) { + // todo: 根据脚手架获取shader文件 + const aa = url.split('/'); + let length = aa.length; + return '/' + aa[length - 1]; + }, + + img2texture(renderData = {}) { + const {height_texture, width_texture, shape} = renderData; + const total = height_texture * width_texture * 4; + const b = shape[0]; + const c = shape[1]; + const h = shape[2]; + const w = shape[3]; + const data = []; + for (let i = 0; i < total; i++) { + let j = Math.floor(i / (c * w)); + let k = Math.floor(i % (c * w)); + let b1 = Math.floor(j / h); + let h1 = Math.floor(j % h); + let c1 = Math.floor(k % c); + let w1 = Math.floor(k / c); + let l = b1 * (c * h * w) + c1 * (h * w) + h1 * (w) + w1; + data.push(renderData.data[l]); + data.push(0); + data.push(0); + data.push(0); + } + renderData.data = new Float32Array(data); + } +}; +/* eslint-enable */ diff --git a/web/test/testUtils/diff.js b/web/test/testUtils/diff.js new file mode 100644 index 0000000000..132213cff6 --- /dev/null +++ b/web/test/testUtils/diff.js @@ -0,0 +1,1055 @@ +/*! + + diff v2.0.1 + +Software License Agreement (BSD License) + +Copyright (c) 2009-2015, Kevin Decker + +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Kevin Decker nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +@license +*/ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define(factory); + else if(typeof exports === 'object') + exports["JsDiff"] = factory(); + else + root["JsDiff"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + /* See LICENSE file for terms of use */ + + /* + * Text diff implementation. + * + * This library supports the following APIS: + * JsDiff.diffChars: Character by character diff + * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace + * JsDiff.diffLines: Line based diff + * + * JsDiff.diffCss: Diff targeted at CSS content + * + * These methods are based on the implementation proposed in + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 + */ + 'use strict'; + + exports.__esModule = true; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _diffBase = __webpack_require__(1); + + var _diffBase2 = _interopRequireDefault(_diffBase); + + var _diffCharacter = __webpack_require__(3); + + var _diffWord = __webpack_require__(4); + + var _diffLine = __webpack_require__(5); + + var _diffSentence = __webpack_require__(6); + + var _diffCss = __webpack_require__(7); + + var _diffJson = __webpack_require__(8); + + var _patchApply = __webpack_require__(9); + + var _patchCreate = __webpack_require__(10); + + var _convertDmp = __webpack_require__(12); + + var _convertXml = __webpack_require__(13); + + exports.Diff = _diffBase2['default']; + exports.diffChars = _diffCharacter.diffChars; + exports.diffWords = _diffWord.diffWords; + exports.diffWordsWithSpace = _diffWord.diffWordsWithSpace; + exports.diffLines = _diffLine.diffLines; + exports.diffTrimmedLines = _diffLine.diffTrimmedLines; + exports.diffSentences = _diffSentence.diffSentences; + exports.diffCss = _diffCss.diffCss; + exports.diffJson = _diffJson.diffJson; + exports.structuredPatch = _patchCreate.structuredPatch; + exports.createTwoFilesPatch = _patchCreate.createTwoFilesPatch; + exports.createPatch = _patchCreate.createPatch; + exports.applyPatch = _patchApply.applyPatch; + exports.convertChangesToDMP = _convertDmp.convertChangesToDMP; + exports.convertChangesToXML = _convertXml.convertChangesToXML; + exports.canonicalize = _diffJson.canonicalize; + + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports['default'] = Diff; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _utilMap = __webpack_require__(2); + + var _utilMap2 = _interopRequireDefault(_utilMap); + + function Diff(ignoreWhitespace) { + this.ignoreWhitespace = ignoreWhitespace; + } + + Diff.prototype = { + diff: function diff(oldString, newString, callback) { + var self = this; + + function done(value) { + if (callback) { + setTimeout(function () { + callback(undefined, value); + }, 0); + return true; + } else { + return value; + } + } + + // Allow subclasses to massage the input prior to running + oldString = this.castInput(oldString); + newString = this.castInput(newString); + + // Handle the identity case (this is due to unrolling editLength == 0 + if (newString === oldString) { + return done([{ value: newString }]); + } + if (!newString) { + return done([{ value: oldString, removed: true }]); + } + if (!oldString) { + return done([{ value: newString, added: true }]); + } + + newString = this.removeEmpty(this.tokenize(newString)); + oldString = this.removeEmpty(this.tokenize(oldString)); + + var newLen = newString.length, + oldLen = oldString.length; + var editLength = 1; + var maxEditLength = newLen + oldLen; + var bestPath = [{ newPos: -1, components: [] }]; + + // Seed editLength = 0, i.e. the content starts with the same values + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) { + // Identity per the equality and tokenizer + return done([{ value: newString.join('') }]); + } + + // Main worker method. checks all permutations of a given edit length for acceptance. + function execEditLength() { + for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { + var basePath = undefined; + var addPath = bestPath[diagonalPath - 1], + removePath = bestPath[diagonalPath + 1], + _oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath - 1] = undefined; + } + + var canAdd = addPath && addPath.newPos + 1 < newLen, + canRemove = removePath && 0 <= _oldPos && _oldPos < oldLen; + if (!canAdd && !canRemove) { + // If this path is a terminal then prune + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canAdd || canRemove && addPath.newPos < removePath.newPos) { + basePath = clonePath(removePath); + self.pushComponent(basePath.components, undefined, true); + } else { + basePath = addPath; // No need to clone, we've pulled it from the list + basePath.newPos++; + self.pushComponent(basePath.components, true, undefined); + } + + _oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); + + // If we have hit the end of both strings, then we are done + if (basePath.newPos + 1 >= newLen && _oldPos + 1 >= oldLen) { + return done(buildValues(basePath.components, newString, oldString, self.useLongestToken)); + } else { + // Otherwise track this path as a potential candidate and continue. + bestPath[diagonalPath] = basePath; + } + } + + editLength++; + } + + // Performs the length of edit iteration. Is a bit fugly as this has to support the + // sync and async mode which is never fun. Loops over execEditLength until a value + // is produced. + if (callback) { + (function exec() { + setTimeout(function () { + // This should not happen, but we want to be safe. + /* istanbul ignore next */ + if (editLength > maxEditLength) { + return callback(); + } + + if (!execEditLength()) { + exec(); + } + }, 0); + })(); + } else { + while (editLength <= maxEditLength) { + var ret = execEditLength(); + if (ret) { + return ret; + } + } + } + }, + + pushComponent: function pushComponent(components, added, removed) { + var last = components[components.length - 1]; + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length - 1] = { count: last.count + 1, added: added, removed: removed }; + } else { + components.push({ count: 1, added: added, removed: removed }); + } + }, + extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath, + commonCount = 0; + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { + newPos++; + oldPos++; + commonCount++; + } + + if (commonCount) { + basePath.components.push({ count: commonCount }); + } + + basePath.newPos = newPos; + return oldPos; + }, + + equals: function equals(left, right) { + var reWhitespace = /\S/; + return left === right || this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right); + }, + removeEmpty: function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + }, + castInput: function castInput(value) { + return value; + }, + tokenize: function tokenize(value) { + return value.split(''); + } + }; + + function buildValues(components, newString, oldString, useLongestToken) { + var componentPos = 0, + componentLen = components.length, + newPos = 0, + oldPos = 0; + + for (; componentPos < componentLen; componentPos++) { + var component = components[componentPos]; + if (!component.removed) { + if (!component.added && useLongestToken) { + var value = newString.slice(newPos, newPos + component.count); + value = _utilMap2['default'](value, function (value, i) { + var oldValue = oldString[oldPos + i]; + return oldValue.length > value.length ? oldValue : value; + }); + + component.value = value.join(''); + } else { + component.value = newString.slice(newPos, newPos + component.count).join(''); + } + newPos += component.count; + + // Common case + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = oldString.slice(oldPos, oldPos + component.count).join(''); + oldPos += component.count; + + // Reverse add and remove so removes are output first to match common convention + // The diffing algorithm is tied to add then remove output and this is the simplest + // route to get the desired output with minimal overhead. + if (componentPos && components[componentPos - 1].added) { + var tmp = components[componentPos - 1]; + components[componentPos - 1] = components[componentPos]; + components[componentPos] = tmp; + } + } + } + + return components; + } + + function clonePath(path) { + return { newPos: path.newPos, components: path.components.slice(0) }; + } + module.exports = exports['default']; + + +/***/ }, +/* 2 */ +/***/ function(module, exports) { + + // Following this pattern to make sure the ignore next is in the correct place after babel builds + "use strict"; + + exports.__esModule = true; + exports["default"] = map; + + /* istanbul ignore next */ + function map(arr, mapper, that) { + if (Array.prototype.map) { + return Array.prototype.map.call(arr, mapper, that); + } + + var other = new Array(arr.length); + + for (var i = 0, n = arr.length; i < n; i++) { + other[i] = mapper.call(that, arr[i], i, arr); + } + return other; + } + module.exports = exports["default"]; + + +/***/ }, +/* 3 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.diffChars = diffChars; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + var _base2 = _interopRequireDefault(_base); + + var characterDiff = new _base2['default'](); + exports.characterDiff = characterDiff; + + function diffChars(oldStr, newStr, callback) { + return characterDiff.diff(oldStr, newStr, callback); + } + + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.diffWords = diffWords; + exports.diffWordsWithSpace = diffWordsWithSpace; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode + // + // Ranges and exceptions: + // Latin-1 Supplement, 0080–00FF + // - U+00D7 × Multiplication sign + // - U+00F7 ÷ Division sign + // Latin Extended-A, 0100–017F + // Latin Extended-B, 0180–024F + // IPA Extensions, 0250–02AF + // Spacing Modifier Letters, 02B0–02FF + // - U+02C7 ˇ ˇ Caron + // - U+02D8 ˘ ˘ Breve + // - U+02D9 ˙ ˙ Dot Above + // - U+02DA ˚ ˚ Ring Above + // - U+02DB ˛ ˛ Ogonek + // - U+02DC ˜ ˜ Small Tilde + // - U+02DD ˝ ˝ Double Acute Accent + // Latin Extended Additional, 1E00–1EFF + + var _base2 = _interopRequireDefault(_base); + + var extendedWordChars = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/; + + var wordDiff = new _base2['default'](true); + exports.wordDiff = wordDiff; + var wordWithSpaceDiff = new _base2['default'](); + exports.wordWithSpaceDiff = wordWithSpaceDiff; + wordDiff.tokenize = wordWithSpaceDiff.tokenize = function (value) { + var tokens = value.split(/(\s+|\b)/); + + // Join the boundary splits that we do not consider to be boundaries. This is primarily the extended Latin character set. + for (var i = 0; i < tokens.length - 1; i++) { + // If we have an empty string in the next field and we have only word chars before and after, merge + if (!tokens[i + 1] && tokens[i + 2] && extendedWordChars.test(tokens[i]) && extendedWordChars.test(tokens[i + 2])) { + tokens[i] += tokens[i + 2]; + tokens.splice(i + 1, 2); + i--; + } + } + + return tokens; + }; + + function diffWords(oldStr, newStr, callback) { + return wordDiff.diff(oldStr, newStr, callback); + } + + function diffWordsWithSpace(oldStr, newStr, callback) { + return wordWithSpaceDiff.diff(oldStr, newStr, callback); + } + + +/***/ }, +/* 5 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.diffLines = diffLines; + exports.diffTrimmedLines = diffTrimmedLines; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + var _base2 = _interopRequireDefault(_base); + + var lineDiff = new _base2['default'](); + exports.lineDiff = lineDiff; + var trimmedLineDiff = new _base2['default'](); + exports.trimmedLineDiff = trimmedLineDiff; + trimmedLineDiff.ignoreTrim = true; + + lineDiff.tokenize = trimmedLineDiff.tokenize = function (value) { + var retLines = [], + lines = value.split(/^/m); + for (var i = 0; i < lines.length; i++) { + var line = lines[i], + lastLine = lines[i - 1], + lastLineLastChar = lastLine && lastLine[lastLine.length - 1]; + + // Merge lines that may contain windows new lines + if (line === '\n' && lastLineLastChar === '\r') { + retLines[retLines.length - 1] = retLines[retLines.length - 1].slice(0, -1) + '\r\n'; + } else { + if (this.ignoreTrim) { + line = line.trim(); + // add a newline unless this is the last line. + if (i < lines.length - 1) { + line += '\n'; + } + } + retLines.push(line); + } + } + + return retLines; + }; + + function diffLines(oldStr, newStr, callback) { + return lineDiff.diff(oldStr, newStr, callback); + } + + function diffTrimmedLines(oldStr, newStr, callback) { + return trimmedLineDiff.diff(oldStr, newStr, callback); + } + + +/***/ }, +/* 6 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.diffSentences = diffSentences; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + var _base2 = _interopRequireDefault(_base); + + var sentenceDiff = new _base2['default'](); + exports.sentenceDiff = sentenceDiff; + sentenceDiff.tokenize = function (value) { + return value.split(/(\S.+?[.!?])(?=\s+|$)/); + }; + + function diffSentences(oldStr, newStr, callback) { + return sentenceDiff.diff(oldStr, newStr, callback); + } + + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.diffCss = diffCss; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + var _base2 = _interopRequireDefault(_base); + + var cssDiff = new _base2['default'](); + exports.cssDiff = cssDiff; + cssDiff.tokenize = function (value) { + return value.split(/([{}:;,]|\s+)/); + }; + + function diffCss(oldStr, newStr, callback) { + return cssDiff.diff(oldStr, newStr, callback); + } + + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.diffJson = diffJson; + exports.canonicalize = canonicalize; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + var _base2 = _interopRequireDefault(_base); + + var _line = __webpack_require__(5); + + var objectPrototypeToString = Object.prototype.toString; + + var jsonDiff = new _base2['default'](); + // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a + // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: + exports.jsonDiff = jsonDiff; + jsonDiff.useLongestToken = true; + + jsonDiff.tokenize = _line.lineDiff.tokenize; + jsonDiff.castInput = function (value) { + return typeof value === 'string' ? value : JSON.stringify(canonicalize(value), undefined, ' '); + }; + jsonDiff.equals = function (left, right) { + return _base2['default'].prototype.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); + }; + + function diffJson(oldObj, newObj, callback) { + return jsonDiff.diff(oldObj, newObj, callback); + } + + // This function handles the presence of circular references by bailing out when encountering an + // object that is already on the "stack" of items being processed. + + function canonicalize(obj, stack, replacementStack) { + stack = stack || []; + replacementStack = replacementStack || []; + + var i = undefined; + + for (i = 0; i < stack.length; i += 1) { + if (stack[i] === obj) { + return replacementStack[i]; + } + } + + var canonicalizedObj = undefined; + + if ('[object Array]' === objectPrototypeToString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + replacementStack.push(canonicalizedObj); + for (i = 0; i < obj.length; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack); + } + stack.pop(); + replacementStack.pop(); + } else if (typeof obj === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + replacementStack.push(canonicalizedObj); + var sortedKeys = [], + key = undefined; + for (key in obj) { + /* istanbul ignore else */ + if (obj.hasOwnProperty(key)) { + sortedKeys.push(key); + } + } + sortedKeys.sort(); + for (i = 0; i < sortedKeys.length; i += 1) { + key = sortedKeys[i]; + canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack); + } + stack.pop(); + replacementStack.pop(); + } else { + canonicalizedObj = obj; + } + return canonicalizedObj; + } + + +/***/ }, +/* 9 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + exports.applyPatch = applyPatch; + + function applyPatch(oldStr, uniDiff) { + var diffstr = uniDiff.split('\n'), + hunks = [], + i = 0, + remEOFNL = false, + addEOFNL = false; + + // Skip to the first change hunk + while (i < diffstr.length && !/^@@/.test(diffstr[i])) { + i++; + } + + // Parse the unified diff + for (; i < diffstr.length; i++) { + if (diffstr[i][0] === '@') { + var chnukHeader = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); + hunks.unshift({ + start: chnukHeader[3], + oldlength: +chnukHeader[2], + removed: [], + newlength: chnukHeader[4], + added: [] + }); + } else if (diffstr[i][0] === '+') { + hunks[0].added.push(diffstr[i].substr(1)); + } else if (diffstr[i][0] === '-') { + hunks[0].removed.push(diffstr[i].substr(1)); + } else if (diffstr[i][0] === ' ') { + hunks[0].added.push(diffstr[i].substr(1)); + hunks[0].removed.push(diffstr[i].substr(1)); + } else if (diffstr[i][0] === '\\') { + if (diffstr[i - 1][0] === '+') { + remEOFNL = true; + } else if (diffstr[i - 1][0] === '-') { + addEOFNL = true; + } + } + } + + // Apply the diff to the input + var lines = oldStr.split('\n'); + for (i = hunks.length - 1; i >= 0; i--) { + var hunk = hunks[i]; + // Sanity check the input string. Bail if we don't match. + for (var j = 0; j < hunk.oldlength; j++) { + if (lines[hunk.start - 1 + j] !== hunk.removed[j]) { + return false; + } + } + Array.prototype.splice.apply(lines, [hunk.start - 1, hunk.oldlength].concat(hunk.added)); + } + + // Handle EOFNL insertion/removal + if (remEOFNL) { + while (!lines[lines.length - 1]) { + lines.pop(); + } + } else if (addEOFNL) { + lines.push(''); + } + return lines.join('\n'); + } + + +/***/ }, +/* 10 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.structuredPatch = structuredPatch; + exports.createTwoFilesPatch = createTwoFilesPatch; + exports.createPatch = createPatch; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _diffPatch = __webpack_require__(11); + + var _utilMap = __webpack_require__(2); + + var _utilMap2 = _interopRequireDefault(_utilMap); + + function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { + if (!options) { + options = { context: 4 }; + } + + var diff = _diffPatch.patchDiff.diff(oldStr, newStr); + diff.push({ value: '', lines: [] }); // Append an empty value to make cleanup easier + + function contextLines(lines) { + return _utilMap2['default'](lines, function (entry) { + return ' ' + entry; + }); + } + + var hunks = []; + var oldRangeStart = 0, + newRangeStart = 0, + curRange = [], + oldLine = 1, + newLine = 1; + + var _loop = function (i) { + var current = diff[i], + lines = current.lines || current.value.replace(/\n$/, '').split('\n'); + current.lines = lines; + + if (current.added || current.removed) { + // If we have previous context, start with that + if (!oldRangeStart) { + var prev = diff[i - 1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : []; + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + + // Output our changes + curRange.push.apply(curRange, _utilMap2['default'](lines, function (entry) { + return (current.added ? '+' : '-') + entry; + })); + + // Track the updated file position + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + // Identical context lines. Track line changes + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= options.context * 2 && i < diff.length - 2) { + // Overlapping + curRange.push.apply(curRange, contextLines(lines)); + } else { + // end the range and output + var contextSize = Math.min(lines.length, options.context); + curRange.push.apply(curRange, contextLines(lines.slice(0, contextSize))); + + var hunk = { + oldStart: oldRangeStart, + oldLines: oldLine - oldRangeStart + contextSize, + newStart: newRangeStart, + newLines: newLine - newRangeStart + contextSize, + lines: curRange + }; + if (i >= diff.length - 2 && lines.length <= options.context) { + // EOF is inside this hunk + var oldEOFNewline = /\n$/.test(oldStr); + var newEOFNewline = /\n$/.test(newStr); + if (lines.length == 0 && !oldEOFNewline) { + // special case: old has no eol and no trailing context; no-nl can end up before adds + curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file'); + } else if (!oldEOFNewline || !newEOFNewline) { + curRange.push('\\ No newline at end of file'); + } + } + hunks.push(hunk); + + oldRangeStart = 0; + newRangeStart = 0; + curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + }; + + for (var i = 0; i < diff.length; i++) { + _loop(i); + } + + return { + oldFileName: oldFileName, newFileName: newFileName, + oldHeader: oldHeader, newHeader: newHeader, + hunks: hunks + }; + } + + function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { + var diff = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options); + + var ret = []; + if (oldFileName == newFileName) { + ret.push('Index: ' + oldFileName); + } + ret.push('==================================================================='); + ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader)); + ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader)); + + for (var i = 0; i < diff.hunks.length; i++) { + var hunk = diff.hunks[i]; + ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines + ' @@'); + ret.push.apply(ret, hunk.lines); + } + + return ret.join('\n') + '\n'; + } + + function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) { + return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options); + } + + +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + var _base2 = _interopRequireDefault(_base); + + var patchDiff = new _base2['default'](); + exports.patchDiff = patchDiff; + patchDiff.tokenize = function (value) { + var ret = [], + linesAndNewlines = value.split(/(\n|\r\n)/); + + // Ignore the final empty token that occurs if the string ends with a new line + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } + + // Merge the content and line separators into single tokens + for (var i = 0; i < linesAndNewlines.length; i++) { + var line = linesAndNewlines[i]; + + if (i % 2) { + ret[ret.length - 1] += line; + } else { + ret.push(line); + } + } + return ret; + }; + + +/***/ }, +/* 12 */ +/***/ function(module, exports) { + + // See: http://code.google.com/p/google-diff-match-patch/wiki/API + "use strict"; + + exports.__esModule = true; + exports.convertChangesToDMP = convertChangesToDMP; + + function convertChangesToDMP(changes) { + var ret = [], + change = undefined, + operation = undefined; + for (var i = 0; i < changes.length; i++) { + change = changes[i]; + if (change.added) { + operation = 1; + } else if (change.removed) { + operation = -1; + } else { + operation = 0; + } + + ret.push([operation, change.value]); + } + return ret; + } + + +/***/ }, +/* 13 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + exports.convertChangesToXML = convertChangesToXML; + + function convertChangesToXML(changes) { + var ret = []; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + + ret.push(escapeHTML(change.value)); + + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + } + return ret.join(''); + } + + function escapeHTML(s) { + var n = s; + n = n.replace(/&/g, '&'); + n = n.replace(//g, '>'); + n = n.replace(/"/g, '"'); + + return n; + } + + +/***/ } +/******/ ]) +}); +; \ No newline at end of file diff --git a/web/test/testUtils/testUtils.es6 b/web/test/testUtils/testUtils.es6 new file mode 100644 index 0000000000..5c173472cd --- /dev/null +++ b/web/test/testUtils/testUtils.es6 @@ -0,0 +1,124 @@ +import 'babel-polyfill'; +// import model from '../data/model.test2'; +// import model from '../data/model.test.conv2d'; +import GraphExecutor from '../../src/executor/executor'; +import Loader from '../../src/executor/loader'; +import Runtime from '../../src/runtime/runtime'; +// 获取map表 +import Map from '../data/map'; +console.dir(['map', Map]); + +let Diff = require('./diff'); +let datas; +let otherResult; +let output +async function run() { + const MODEL_URL = '/test/unitData/model.test.batchnorm.json'; + const graphModel= new Loader(); + const model = await graphModel.loadGraphModel(MODEL_URL); + datas = model.handler; + output = deepCopy(model.handler); + // 测试单元 + let item = getTensor('batchnorm'); + func(item); + // let inst = model.execute({input: cat}); + // console.dir(['result', inst.read()]); +} +run(); + +function deepCopy (data) { + return JSON.parse(JSON.stringify(data)); +} + +// let output = deepCopy(datas); +let getTensor = function(id, times = 1) { + let find = 0; + let data = datas.ops.filter((item, idx) => { + if (id === item.type) { + ++find; + if (find === times) { + return true; + } + } + }); + return getInputs(data[0]); +}; + +let getInputs = function(data) { + + Object.keys(data.inputs).forEach(function(key){ + data.inputs[key] = getValue(data.inputs[key][0], datas); + + }); + Object.keys(data.outputs).forEach(function(key){ + let out = getValue(data.outputs[key][0], datas) + data.outputs[key] = out; + otherResult = out[0].data; + }); + return data; + +}; + +let getResult = function(id) { + let data = output.ops.filter((item, idx) => { + if (id === item.type) { + + return true; + } + }); + return getoutputs(data[0]); +}; +let getoutputs = function(data) { + let otherResult; + Object.keys(data.outputs).forEach(function(key){ + let out = getValue(data.outputs[key][0], output); + otherResult = out[0].data; + }); + return otherResult; +}; + +let getValue = function(name, datas) { + return datas.vars.filter((item, idx) => { + if (name === item.name) { + return item; + } + }); +}; +// // 测试单元 +// let item = getTensor('conv2d'); + +let func = function (item) { + let inst = Runtime.init({ + 'width_raw_canvas': 512, + 'height_raw_canvas': 512 + }); + const executor = new GraphExecutor(item); + executor.execute(executor, {}, inst); + console.dir(['result', inst.read()]); + + + // var one = inst.read(); + // var other = getResult('softmax'); + // var color =''; + // var span = null; + + // var diff = Diff.diffChars(one.toString(), other.toString()), + // display = document.getElementById('display'), + // fragment = document.createDocumentFragment(); + // + // diff.forEach(function(part){ + // // green for additions, red for deletions + // // grey for common parts + // color = part.added ? 'green' : + // part.removed ? 'red' : 'grey'; + // span = document.createElement('span'); + // span.style.color = color; + // span.appendChild(document + // .createTextNode(part.value)); + // fragment.appendChild(span); + // }); + // + // display.appendChild(fragment); + +}; + diff --git a/web/test/unitTest.es6 b/web/test/unitTest.es6 new file mode 100644 index 0000000000..9ef10c6b7c --- /dev/null +++ b/web/test/unitTest.es6 @@ -0,0 +1,62 @@ +import 'babel-polyfill'; +import units from './units/units'; +let qs = require('qs'); +/** + * @file 入口文件 + * @author wangqun@baidu.com + * + */ +// 引入 op +const FSHADER_CON2D = require('../src/shader/f_elementwise_conv2d3_shader.c'); + + +const shapeA = [1, 3, 256, 256]; +const shapeB = [3]; +const imgUrl = require('./data/banana.jpeg'); +let shapeAData; +let shapeBData; +let inst; + +const matrix = units.mockOrigin(); +const filter = units.mockFilter(); +// 原始张量,上下左右1个单位的padding,步长是1 +let conf = { + 'filter_size_width': 3, + 'filter_size_height': 3, + 'origin_size_width': matrix.sx, + 'origin_size_height': matrix.sx, + 'out_size_width': 3, + 'out_size_height': 3, + 'stride_horizontal': 1, + 'stride_vertical': 1, + 'pad_left': 1, + 'pad_top': 1, + 'dilation_horizontal': 2, + 'dilation_vertical': 2 +} +units.init(conf, FSHADER_CON2D).then(instance => { + if (!instance || typeof instance === 'string') { + throw new Error(instance || '不支持float texture'); + } + inst = instance; +}).then(() => { + console.dir(['卷积核', filter]); + console.dir(['origin data', matrix.data]); + // 执行conv2d + inst.compute(filter, matrix.data, 'conv2d'); +}).then(() => { + // 读取结果 + const result = inst.read(); + console.dir(['conv2d的执行结果', result]); + + let input = { + filter: filter, + origin: matrix.data, + }; + Object.assign(input, conf); + console.dir(['完整input', input]); + // console.dir(['完整输入和输出', params]); + inst.getResult('pool2d', input, result); +}).catch(err => { + console.log('-----------error---------' + err); +}); diff --git a/web/test/unitTest.html b/web/test/unitTest.html new file mode 100644 index 0000000000..7699fe2303 --- /dev/null +++ b/web/test/unitTest.html @@ -0,0 +1,115 @@ + + + + + paddle web unitTest + + + + + + + +
+
+ paddle Web Unit Test +
+
+
    +
  • +
    pool
    +
    pass
    +
  • +
  • +
    relu
    +
    pass
    +
  • +
  • +
    prelu
    +
    pass
    +
  • +
  • +
    softmax
    +
    pass
    +
  • +
  • +
    dropout
    +
    pass
    +
  • +
  • +
    conv2d
    +
    pass
    +
  • +
+
+
+ +
+ + + + \ No newline at end of file diff --git a/web/test/units/units.es6 b/web/test/units/units.es6 new file mode 100644 index 0000000000..bfb1b2f1a8 --- /dev/null +++ b/web/test/units/units.es6 @@ -0,0 +1,168 @@ +import Utils from '../../src/utils/utils'; +import Gpu from '../../src/gpu/gpu'; +import Matrix from '../../src/utils/dims'; +import axios from 'axios'; +let qs = require('qs'); + +/** + * @file gpu运行时 + * @author wangqun + * + */ +// v_shader.c表示计算容器 +const VSHADER = require('../../src/shader/v_shader.c'); + +export default { + + /** + * 初始化op + * @param {Object} opts 运行时参数,包含el:canvas,dim: 256 + * @return {Object} this 实例对象 + */ + + async init(opts = {}, opShader) { + const gpu = this.gpu = new Gpu(opts); + if (gpu.isFloatingTexture()) { + let texture = gpu.makeTexure(WebGLRenderingContext.FLOAT, null); + let framebuffer = gpu.attachFrameBuffer(texture); + let bufferStatus = gpu.frameBufferIsComplete(); + if (bufferStatus.isComplete) { + console.log(bufferStatus.isComplete); + // 获取shader + const vshaderCode = await Utils.loadShader(VSHADER); + let fshaderCode = await Utils.loadShader(opShader); + fshaderCode = Utils.populateData('conv2d', fshaderCode, opts); + gpu.create(vshaderCode, fshaderCode); + return this; + } else { + return bufferStatus.message; + } + + } else { + return null; + } + + }, + + /** + * 计算op + * @param bufferA + * @param bufferB + */ + compute(bufferA, bufferB, type) { + this.gpu.render(bufferA, bufferB, type); + }, + + /** + * 读取op计算结果, 并返回数据 + */ + read() { + return this.gpu.compute(); + }, + + // 生成feed数据 + feed(pixelData, size) { + return Utils.shapeData(pixelData, size); + }, + + // mock生成shapeB的数据 + mockShapeB(shapeA, shapeB) { + return Utils.mock(shapeA, shapeB); + }, + + // mock origin 1 * 5 * 5 + mockOrigin() { + return new Matrix({ + sx: 5, + sy: 5, + depth: 4 + }); + }, + + // mock filter 1 * 3 * 3 + mockFilter() { + return new Float32Array([1.0, 1.0, 0.0, 0.0, -2.0, 0.0, 1.0, -3.0, 1.0]); + }, + + // 更新op + updateOp(name) { + // this.gpu.updateShader(); + }, + + // get paddle mobile result + getResult(name, input, output) { + + + + if (name) { + let that = this; + axios.defaults.withCredentials = false; + axios.defaults.headers = { + 'Content-type': 'application/x-www-form-urlencoded' + } + axios.post('http://yq01-paddle-mobile.epc.baidu.com:8088/uniTest', qs.stringify({ + name: name, + input: JSON.stringify(input, function (key, value) { + if (value.constructor === Float32Array) { + return that.formatData(value); + }else { + return that.formatData(value); + } + }), + output: JSON.stringify(output, function (key, value) { + return that.formatData(value); + }) + },{ indices: false })) + .then(function (response) { + if (response.status === 200) { + that.displayResult(response.data); + } + console.log(response); + }) + .catch(function (error) { + console.log(error); + }); + } + + }, + + displayResult(res) { + if (res.name) { + let assert = (res.correct == 1? 'Pass' : 'Not pass'); + let passCls = (res.correct == 1? 'pass' : 'no-pass'); + if (res.correct === 1) { + + let unitHtml = '
  • ' + res.name + '
    ' + + '
    ' + assert + '
    ' + '
  • '; + let oli = document.createElement('li'); + oli.innerHTML = unitHtml; + document.getElementById('paddle-web-unit-list').appendChild(oli); + } + else if (res.correct === 0) { + let serverData = res.server_data; + let unitHtml = '
  • ' + res.name + '
    ' + + '
    ' + assert + '
    ' + + '

    ' + serverData + '

    ' + '
  • '; + let oli = document.createElement('li'); + oli.innerHTML = unitHtml; + document.getElementById('paddle-web-unit-list').appendChild(oli); + } + } + }, + + formatData(list) { + if (list.constructor === Float32Array) { + return '[' + list.toString() + ']'; + } + else { + return list; + } + }, + +// 释放资源 + dispose() { + this.gpu.dispose(); + } +}; diff --git a/web/tools/toBinaryFile.py b/web/tools/toBinaryFile.py new file mode 100644 index 0000000000..bc5629a94f --- /dev/null +++ b/web/tools/toBinaryFile.py @@ -0,0 +1,111 @@ +#coding:utf-8 +#! /bin/python +# 把文本文件转为二进制文件的工具 +import os +import struct +import random +import math + +class BinaryFileConverter: + def __init__(self, delimiter, ignorChar, ignorLine, types, originDir, resultDir, ext, formatter, dotPrintRatio, merge): + # 每行中数字间的分隔符 + self.delimiter = delimiter + # 需要忽略的符号 + self.ignorChar = ignorChar + # 需要忽略的行 + self.ignorLine = ignorLine + # 需要转的文件 + self.types = types + # 转之前的 + self.originDir = originDir + # 转之后的文件夹 + self.resultDir = resultDir + # 转换后的后缀 + self.ext = ext + # 格式 可选内容参考:https://docs.python.org/3/library/struct.html?#format-characters + self.formatter = formatter + # 打点率 + self.dotPrintRatio = dotPrintRatio + # 合成几个文件 0代表不合并 + self.merge = merge + # 存合并的数据的文件 + self.mergedResultFileName = resultDir + '/mergedData.dat' + # 计数器 + self.i = 0 + + def dfs(self, rootDir): + for item in sorted(os.listdir(rootDir)): + path = os.path.join(rootDir, item) + if os.path.isdir(path): + self.dfs(path) + else: + self.process(path) + # print(path) + + def process(self, path): + (curFile, curType) = os.path.splitext(path) + if curType in self.types: + originFile = open(path, 'r') + if not self.merge: + newFilePath = self.resultDir + curFile[len(self.originDir):] + self.ext + newFileDir = os.path.dirname(newFilePath) + if not os.path.exists(newFileDir): + os.makedirs(newFileDir) + self.resultFile = open(newFilePath ,'wb') + print('开始写啦' + path) + self.writeToFile(originFile, self.resultFile) + if not self.merge: + self.resultFile.close() + print('\n') + print('写完啦' + path) + + def writeToFile(self, originFile, resultFile): + lines = originFile.readlines() + for line in lines: + if (line in self.ignorLine) or (line.strip() in self.ignorLine): + continue + curLine = line.strip().split(self.delimiter) + for i in curLine: + if (not len(i.strip())) or (i in self.ignorChar): + continue + if random.random() < self.dotPrintRatio: + print('.', end = '') + self.i += 1 + parsedata = struct.pack(self.formatter, float(i)) + resultFile.write(parsedata) + originFile.close() + + def convert(self): + if self.merge: + if not os.path.exists(self.resultDir): + os.makedirs(self.resultDir) + self.resultFile = open(self.mergedResultFileName ,'wb') + self.dfs(self.originDir) + print('共写入了%s条数据' % self.i) + self.resultFile.close() + if self.merge > 1: + f = open(self.mergedResultFileName, 'rb') + data = f.read() # read the entire content of the file + f.close() + bytes = len(data) + size = (int(bytes / self.merge // 16) + 1) * 16 + count = 1 + for i in range(0, bytes + 1, size): + fni = self.resultDir + '/chunk_%s' % count + f = open(fni, 'wb') + f.write(data[i : i + size]) + f.close() + count += 1 + + +BinaryFileConverter( + delimiter = ',', + ignorChar = ['[', ']'], + ignorLine = ['[', ']'], + types = ['.txt', '.json'], + originDir = './mobileNet', + resultDir = './binf', + ext = '.dat', + formatter = 'f', + dotPrintRatio = 0, + merge = 6).convert() \ No newline at end of file -- GitLab