├── .eslintrc.json ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.ja.md ├── README.md ├── example ├── benchmark │ ├── make_mobilenetv2.py │ ├── make_models.py │ ├── models │ │ └── detr.py │ └── runner │ │ ├── .gitignore │ │ ├── bench.js │ │ ├── optimized.html │ │ └── standard.html ├── custom_operator │ ├── README.ja.md │ ├── README.md │ ├── index.html │ ├── index.js │ ├── make_model.py │ └── twice.ts ├── detr │ ├── README.ja.md │ ├── README.md │ ├── common.js │ ├── conversion.py │ ├── index.html │ ├── index.js │ ├── test.html │ └── test.js ├── minimum │ ├── README.ja.md │ ├── README.md │ ├── index.html │ ├── make_model.py │ └── model │ │ └── model.onnx └── resnet │ ├── export_pytorch_model.py │ ├── imagenet_classes.js │ ├── index.html │ ├── index.js │ └── optimized.html ├── package.json ├── requirements.test.txt ├── scripts ├── extract_subgraph.py └── make_operator_entries.py ├── setup.py ├── src ├── descriptor_runner │ ├── backend │ │ ├── cpu │ │ │ ├── cpuContextImpl.ts │ │ │ └── cpuTensorImpl.ts │ │ ├── wasm │ │ │ ├── wasmContextImpl.ts │ │ │ └── wasmTensorImpl.ts │ │ ├── webgl │ │ │ ├── pack.ts │ │ │ ├── webglContextImpl.ts │ │ │ └── webglTensorImpl.ts │ │ └── webgpu │ │ │ ├── webgpuContextImpl.ts │ │ │ ├── webgpuMetaBuffer.ts │ │ │ └── webgpuTensorImpl.ts │ ├── core │ │ ├── inputProxy.ts │ │ ├── modelTransform.ts │ │ ├── operatorTable.ts │ │ ├── outputProxy.ts │ │ ├── runnerImpl.ts │ │ ├── tensorDecoder │ │ │ ├── decodeTensorEightbit.ts │ │ │ └── decodeTensorRaw.ts │ │ ├── tensorImpl.ts │ │ └── tensorLoaderImpl.ts │ ├── image.ts │ ├── image │ │ ├── canvas.ts │ │ ├── enums.ts │ │ ├── image_array.ts │ │ ├── image_data.ts │ │ └── image_source.ts │ ├── index.ts │ ├── interface │ │ ├── backend │ │ │ ├── cpu │ │ │ │ ├── cpuContext.ts │ │ │ │ └── cpuTensor.ts │ │ │ ├── wasm │ │ │ │ ├── wasmContext.ts │ │ │ │ └── wasmTensor.ts │ │ │ ├── webgl │ │ │ │ ├── webglContext.ts │ │ │ │ └── webglTensor.ts │ │ │ └── webgpu │ │ │ │ ├── webgpuContext.ts │ │ │ │ └── webgpuTensor.ts │ │ └── core │ │ │ ├── backendContext.ts │ │ │ ├── constants.ts │ │ │ ├── operator.ts │ │ │ ├── runner.ts │ │ │ ├── tensor.ts │ │ │ └── tensorLoader.ts │ ├── logging.ts │ ├── math.ts │ ├── math │ │ ├── argsort.ts │ │ └── random.ts │ ├── operators │ │ ├── base │ │ │ ├── averagepool.ts │ │ │ ├── conv.ts │ │ │ ├── convtranspose.ts │ │ │ ├── flatten.ts │ │ │ ├── gemm.ts │ │ │ ├── matmul.ts │ │ │ ├── maxpool.ts │ │ │ ├── pad11.ts │ │ │ ├── reshape5.ts │ │ │ ├── split.ts │ │ │ ├── squeeze.ts │ │ │ ├── transpose.ts │ │ │ └── unsqueeze.ts │ │ ├── cpu │ │ │ ├── opEntriesStandard.ts │ │ │ ├── operators │ │ │ │ ├── autogen │ │ │ │ │ └── .gitignore │ │ │ │ ├── custom │ │ │ │ │ └── .gitignore │ │ │ │ └── standard │ │ │ │ │ ├── averagepool.ts │ │ │ │ │ ├── binary7.ts │ │ │ │ │ ├── cast.ts │ │ │ │ │ ├── clip.ts │ │ │ │ │ ├── concat.ts │ │ │ │ │ ├── constant.ts │ │ │ │ │ ├── constantofshape.ts │ │ │ │ │ ├── conv.ts │ │ │ │ │ ├── convtranspose.ts │ │ │ │ │ ├── dynamicunary.ts │ │ │ │ │ ├── eachelementwise.ts │ │ │ │ │ ├── flatten.ts │ │ │ │ │ ├── gather.ts │ │ │ │ │ ├── gemm.ts │ │ │ │ │ ├── globalaveragepool.ts │ │ │ │ │ ├── instancenormalization.ts │ │ │ │ │ ├── matmul.ts │ │ │ │ │ ├── maxpool.ts │ │ │ │ │ ├── pad11.ts │ │ │ │ │ ├── reduce.ts │ │ │ │ │ ├── reshape5.ts │ │ │ │ │ ├── shape.ts │ │ │ │ │ ├── slice.ts │ │ │ │ │ ├── softmax.ts │ │ │ │ │ ├── split.ts │ │ │ │ │ ├── squeeze.ts │ │ │ │ │ ├── tile.ts │ │ │ │ │ ├── transpose.ts │ │ │ │ │ ├── unary.ts │ │ │ │ │ └── unsqueeze.ts │ │ │ └── rawcomputation │ │ │ │ └── averagepool.ts │ │ ├── index.ts │ │ ├── operatorImpl.ts │ │ ├── operatorUtil.ts │ │ ├── wasm │ │ │ ├── opEntriesStandard.ts │ │ │ ├── operators │ │ │ │ ├── autogen │ │ │ │ │ └── .gitignore │ │ │ │ ├── custom │ │ │ │ │ └── .gitignore │ │ │ │ └── standard │ │ │ │ │ ├── binary7.ts │ │ │ │ │ ├── dynamicunary.ts │ │ │ │ │ ├── flatten.ts │ │ │ │ │ ├── gemm.ts │ │ │ │ │ ├── reshape5.ts │ │ │ │ │ ├── squeeze.ts │ │ │ │ │ ├── unary.ts │ │ │ │ │ └── unsqueeze.ts │ │ │ └── worker │ │ │ │ └── .gitignore │ │ ├── webgl │ │ │ ├── opEntriesStandard.ts │ │ │ ├── operators │ │ │ │ ├── autogen │ │ │ │ │ └── .gitignore │ │ │ │ ├── custom │ │ │ │ │ └── .gitignore │ │ │ │ └── standard │ │ │ │ │ ├── averagepool.ts │ │ │ │ │ ├── binary7.ts │ │ │ │ │ ├── cast.ts │ │ │ │ │ ├── clip.ts │ │ │ │ │ ├── conv.ts │ │ │ │ │ ├── convtranspose.ts │ │ │ │ │ ├── flatten.ts │ │ │ │ │ ├── gemm.ts │ │ │ │ │ ├── globalaveragepool.ts │ │ │ │ │ ├── instancenormalization.ts │ │ │ │ │ ├── matmul.ts │ │ │ │ │ ├── maxpool.ts │ │ │ │ │ ├── pad11.ts │ │ │ │ │ ├── reduce.ts │ │ │ │ │ ├── reshape5.ts │ │ │ │ │ ├── softmax.ts │ │ │ │ │ ├── split.ts │ │ │ │ │ ├── squeeze.ts │ │ │ │ │ ├── transpose.ts │ │ │ │ │ ├── unary.ts │ │ │ │ │ └── unsqueeze.ts │ │ │ ├── rawcomputation │ │ │ │ └── averagepool.ts │ │ │ └── shaderHelper.ts │ │ └── webgpu │ │ │ ├── opEntriesStandard.ts │ │ │ ├── operators │ │ │ ├── custom │ │ │ │ └── .gitignore │ │ │ └── standard │ │ │ │ ├── binary7.ts │ │ │ │ ├── conv.ts │ │ │ │ ├── gemm.ts │ │ │ │ └── unary.ts │ │ │ └── shaders.ts │ ├── separateBuild │ │ ├── coreOnly.ts │ │ ├── operatorCPU.ts │ │ ├── operatorWasm.ts │ │ ├── operatorWebGL.ts │ │ └── operatorWebGPU.ts │ └── util.ts ├── graph_transpiler │ └── webdnn │ │ ├── __init__.py │ │ ├── constant_codec_eightbit.py │ │ ├── model.py │ │ ├── onnx_util.py │ │ ├── operator_shader.py │ │ ├── operator_shader_cpu.py │ │ ├── operator_shader_wasm.py │ │ ├── operator_shader_webgl.py │ │ ├── operator_shader_webgpu.py │ │ ├── optimization_pass.py │ │ ├── optimization_pass_result_cpu.py │ │ ├── optimization_pass_result_wasm.py │ │ ├── optimization_pass_result_webgl.py │ │ ├── optimize_model.py │ │ ├── parse_onnx.py │ │ ├── pass_conv_reshape_webgl.py │ │ ├── pass_fusion_unary.py │ │ ├── pass_fusion_unary_cpu.py │ │ ├── pass_fusion_unary_wasm.py │ │ ├── pass_fusion_unary_webgl.py │ │ ├── pass_matmul_transpose_webgl2.py │ │ ├── passes.py │ │ ├── tensor_export.py │ │ └── util.py └── shader │ ├── wasm │ ├── compile.py │ ├── pre.js │ └── src │ │ ├── common │ │ ├── binary7.hpp │ │ ├── kernel.hpp │ │ └── unary.hpp │ │ ├── core │ │ └── allocation.cpp │ │ └── kernels │ │ ├── autogen │ │ └── .gitignore │ │ ├── custom │ │ └── .gitignore │ │ └── standard │ │ ├── binary7s.cpp │ │ ├── copy.cpp │ │ ├── dynamic_unarys.cpp │ │ ├── gemm.cpp │ │ └── unarys.cpp │ └── webgpu │ ├── compile.js │ └── shadersources │ ├── autogen │ └── .gitignore │ ├── custom │ └── .gitignore │ └── standard │ ├── binary_broadcast_add_0d.glsl │ ├── binary_broadcast_add_1d.glsl │ ├── binary_broadcast_add_2d.glsl │ ├── binary_broadcast_add_3d.glsl │ ├── binary_broadcast_add_4d.glsl │ ├── binary_elementwise_add.glsl │ ├── conv_bias.glsl │ ├── conv_im2col.glsl │ ├── conv_matmul.glsl │ ├── conv_transpose.glsl │ ├── gemm.glsl │ └── relu.glsl ├── test └── model_test │ ├── make_models.py │ └── runner │ ├── .gitignore │ ├── optimized.html │ ├── standard.html │ └── test.js ├── tsconfig.json ├── webpack-core.config.js ├── webpack-cpu.config.js ├── webpack-wasm.config.js ├── webpack-webgl1-16384.config.js ├── webpack-webgl1-4096.config.js ├── webpack-webgl2-16384.config.js ├── webpack-webgl2-4096.config.js ├── webpack-webgpu.config.js ├── webpack.config.js └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/eslint-recommended", 5 | "plugin:@typescript-eslint/recommended", 6 | "plugin:prettier/recommended" 7 | ], 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "env": { "node": true, "es6": true }, 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "sourceType": "module", 15 | "project": "./tsconfig.json" 16 | }, 17 | "rules": { 18 | } 19 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | /dist 64 | *.egg-info 65 | __pycache__ 66 | output 67 | 68 | # custom operator related 69 | /src/descriptor_runner/operators/cpu/opEntriesAll.ts 70 | /src/descriptor_runner/operators/wasm/opEntriesAll.ts 71 | /src/descriptor_runner/operators/webgl/opEntriesAll.ts 72 | /src/descriptor_runner/operators/webgpu/opEntriesAll.ts 73 | /src/descriptor_runner/custom 74 | /src/shader/wasm/lib 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | We welcome contributions to WebDNN. This document describes the procedures and rules. 3 | 4 | Kinds of contributions will be one of the following, but not restricted to: 5 | - Bugfix 6 | - Implementation of a layer 7 | - Implementation of a converter from a deep learning framework 8 | - Improvement of performance 9 | - Documantation 10 | 11 | For new layer implementation, at least WebAssembly backend implementation is required. WebGPU backend can only be tested on Mac, so it is not mandatory. 12 | 13 | # Testing 14 | If you have added some features, implementing tests corresponding to them is recommended. 15 | 16 | `test/webdnn_test` is for tests which can be completed within graph transpiler. `test/runtime` is for tests which generates graph descriptor and compares its behavior on web browsers. 17 | 18 | See how to run test commands in `test/README.md`. 19 | 20 | # Pull Request 21 | Send pull request from your fork branch to our master branch. The project organizer checks the request and accepts or gives request for revision. 22 | 23 | # License 24 | WebDNN is distributed under the MIT License. Every contributor holds the copyright of his/her part. 25 | 26 | By contributing to the mil-tokyo/webdnn repository through pull-request, comment, 27 | or otherwise, the contributor releases their content to the license and copyright 28 | terms herein. 29 | 30 | ## Developer Certificate of Origin 1.1 31 | Developer Certificate of Origin 32 | Version 1.1 33 | 34 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 35 | 1 Letterman Drive 36 | Suite D4700 37 | San Francisco, CA, 94129 38 | 39 | Everyone is permitted to copy and distribute verbatim copies of this 40 | license document, but changing it is not allowed. 41 | 42 | 43 | Developer's Certificate of Origin 1.1 44 | 45 | By making a contribution to this project, I certify that: 46 | 47 | (a) The contribution was created in whole or in part by me and I 48 | have the right to submit it under the open source license 49 | indicated in the file; or 50 | 51 | (b) The contribution is based upon previous work that, to the best 52 | of my knowledge, is covered under an appropriate open source 53 | license and I have the right under that license to submit that 54 | work with modifications, whether created in whole or in part 55 | by me, under the same open source license (unless I am 56 | permitted to submit under a different license), as indicated 57 | in the file; or 58 | 59 | (c) The contribution was provided directly to me by some other 60 | person who certified (a), (b) or (c) and I have not modified 61 | it. 62 | 63 | (d) I understand and agree that this project and the contribution 64 | are public and that a record of the contribution (including all 65 | personal information I submit with it, including my sign-off) is 66 | maintained indefinitely and may be redistributed consistent with 67 | this project or the open source license(s) involved. 68 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Machine Intelligence Laboratory (The University of Tokyo) 4 | and other contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | By contributing to the mil-tokyo/webdnn repository through pull-request, comment, 25 | or otherwise, the contributor releases their content to the license and copyright 26 | terms herein. 27 | 28 | ---- 29 | 30 | # lib/inflate.min.js 31 | /** 32 | * @license 33 | * zlib.js 34 | * JavaScript Zlib Library 35 | * https://github.com/imaya/zlib.js 36 | * 37 | * The MIT License 38 | * 39 | * Copyright (c) 2012 imaya 40 | * 41 | * Permission is hereby granted, free of charge, to any person obtaining a copy 42 | * of this software and associated documentation files (the "Software"), to deal 43 | * in the Software without restriction, including without limitation the rights 44 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 45 | * copies of the Software, and to permit persons to whom the Software is 46 | * furnished to do so, subject to the following conditions: 47 | * 48 | * The above copyright notice and this permission notice shall be included in 49 | * all copies or substantial portions of the Software. 50 | * 51 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 52 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 53 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 54 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 55 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 56 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 57 | * THE SOFTWARE. 58 | */ 59 | 60 | # Photos used for examples have their own licenses 61 | 62 | -------------------------------------------------------------------------------- /README.ja.md: -------------------------------------------------------------------------------- 1 | # WebDNN 2 | 3 | [English](README.md) 4 | 5 | WebDNN version 2のα版です。WebDNN 1.xとの大きな違いは、入力としてONNX形式のモデルのみを受け付ける点です。Pythonによる前処理なしで、ONNXモデルを直接Webブラウザで読み込むことが可能です。さらに、オフラインでのモデル最適化を行うことも可能です。 6 | 7 | [Version 1.x](https://github.com/mil-tokyo/webdnn/tree/v1.2.11) 8 | 9 | # 対応バックエンド(高速化技術) 10 | 11 | モダンブラウザのほとんどで、WebGLが使用可能。 12 | 13 | - WebGPU 14 | - Chrome Canary搭載版。 15 | - iOS13に搭載のWebGPUは廃止予定のWSL言語によるシェーダを必要とするため非対応。 16 | - WebGL 17 | - WebGL2が使用可能な場合は使用する。WebGL1のみ対応のSafariにも対応。 18 | - WebAssembly 19 | 20 | # 開発環境セットアップ 21 | 22 | node.js 14, python 3.6以降, emscripten 2.0以降が動作する環境が必要です。 23 | 24 | ``` 25 | yarn 26 | python setup.py develop 27 | ``` 28 | 29 | # ビルド 30 | ``` 31 | yarn build:all 32 | ``` 33 | 34 | ビルド成果物 35 | - `dist/webdnn.js` 36 | - 最適化されていないONNXモデルを読み込むことができるライブラリ 37 | - `dist/webdnn-core.js` 38 | - WebDNNにより最適化されたモデルを読み込むことができるライブラリ 39 | 40 | # 基本的な使い方 41 | 42 | `dist/webdnn.js`を` 9 | 10 | 11 | 12 |

WebDNN Benchmark (Optimized Model)

13 |
14 | 15 | 18 |
19 | WebGL Options
20 | maxAllocationBytes: 21 | MB
22 | deallocateToBytes: 23 | MB
24 | version: 25 | 32 |
33 |
34 |
35 | Backend: 36 | 42 | Model: 43 | 46 | 50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /example/benchmark/runner/standard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | WebDNN Benchmark (Standard ONNX Model) 8 | 9 | 10 | 11 | 12 |

WebDNN Benchmark (Standard ONNX Model)

13 |
14 | 15 | 18 |
19 | WebGL Options
20 | maxAllocationBytes: 21 | MB
22 | deallocateToBytes: 23 | MB
24 | version: 25 | 32 |
33 |
34 |
35 | Backend: 36 | 42 | Model: 43 | 46 | 50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /example/custom_operator/README.ja.md: -------------------------------------------------------------------------------- 1 | # カスタムオペレータの実装例 2 | 3 | ONNX仕様に存在しないカスタムオペレータを実装し、動作させるサンプル。 4 | 5 | 入力を2倍にする`Twice`という名前のカスタムオペレータを含むモデルを作成し、WebDNN上で実行する。 6 | 7 | Pythonスクリプトの実行には、PyTorch (`>=1.7`)が必要。 8 | 9 | # 操作手順 10 | ## カスタムオペレータを含むONNXモデルを生成 11 | ``` 12 | python make_model.py 13 | ``` 14 | 15 | ## カスタムオペレータの実装を設置 16 | 17 | カスタムオペレータの実装である`twice.ts`を`/src\descriptor_runner\operators\cpu\operators\custom\twice.ts`にコピー。 18 | 19 | ## カスタムオペレータを含むオペレータセットのビルド 20 | 21 | ``` 22 | python -m webdnn.optimize_model output/model.onnx output 23 | ``` 24 | 25 | 出力ファイル`model-{cpu,wasm,webgl}.onnx`, `op-{cpu,wasm,webgl}.js`, `weight-{cpu,wasm,webgl}-0.bin`が生成される。`op-{cpu,wasm,webgl}.js`に標準オペレータおよびカスタムオペレータの実装が含まれる。`model.onnx`はもはや必要ない。 26 | 27 | ## Webブラウザ上での実行 28 | 29 | repository rootにて 30 | 31 | ``` 32 | yarn server 33 | ``` 34 | 35 | を実行。この状態で、Webブラウザで[http://localhost:8080/example/custom_operator/](http://localhost:8080/example/custom_operator/)を開く。 36 | 37 | このサンプルでは、カスタムオペレータはCPU上で動作する実装である。GPU上で動作する実装が存在する標準オペレータは、GPU上で動作し、カスタムオペレータの実行前後で自動的にテンソルデータがCPU/GPU間で転送される。 38 | -------------------------------------------------------------------------------- /example/custom_operator/README.md: -------------------------------------------------------------------------------- 1 | # Example of custom operator implementation 2 | 3 | A sample that implements and runs a custom operator that does not exist in the ONNX specification. 4 | 5 | Create a model that contains a custom operator named `Twice` that doubles the input and run it on WebDNN. 6 | 7 | PyTorch (`>=1.7`) is required to run the Python script. 8 | 9 | # Operation procedure 10 | ## Generate ONNX models with custom operators 11 | ``` 12 | python make_model.py 13 | ``` 14 | 15 | ## Install custom operator implementation 16 | 17 | Copy `twice.ts`, the implementation of the custom operator, to `/src\descriptor_runner\operators\cpu\operators\custom\twice.ts`. 18 | 19 | ## Build an operator set with custom operators 20 | 21 | ``` 22 | python -m webdnn.optimize_model output/model.onnx output 23 | ``` 24 | 25 | Output files `model-{cpu,wasm,webgl}.onnx`, `op-{cpu,wasm,webgl}.js`, `weight-{cpu,wasm,webgl}-0.bin` are generated. `op-{cpu,wasm,webgl}.js` includes standard and custom operator implementations.`model.onnx` is no longer needed. 26 | 27 | ## Run on a web browser 28 | 29 | At repository root, execute 30 | 31 | ``` 32 | yarn server 33 | ``` 34 | 35 | With this running, open [http://localhost:8080/example/custom_operator/](http://localhost:8080/example/custom_operator/) with a web browser. 36 | 37 | In this sample, the custom operator is an implementation running on the CPU; the standard operator, for which there is an implementation running on the GPU, runs on the GPU, and tensor data is automatically transferred between the CPU and GPU before and after the custom operator is executed. 38 | -------------------------------------------------------------------------------- /example/custom_operator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | WebDNN custom operator test 12 | 18 | 30 | 31 | 32 |

WebDNN custom operator test

33 |
34 |

35 |
36 | 43 |
44 |
45 |
46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/custom_operator/index.js: -------------------------------------------------------------------------------- 1 | let runner; 2 | 3 | window.addEventListener("DOMContentLoaded", async () => { 4 | srcCanvas = document.getElementById("source"); 5 | resultCanvas = document.getElementById("result"); 6 | updateMessage("Loading model"); 7 | 8 | runner = await WebDNN.load("output/", {optimized: true}); 9 | updateMessage(`Model loaded (backend: ${runner.backendName})`); 10 | }); 11 | 12 | function updateMessage(message) { 13 | document.getElementById("msg").innerText = message; 14 | } 15 | 16 | async function run() { 17 | const cols = 4, rows = 3; 18 | const inputArray = new Float32Array(cols * rows); 19 | for (let i = 0; i < inputArray.length; i++) { 20 | inputArray[i] = Math.random() - 0.5; 21 | } 22 | const input = new WebDNN.CPUTensor([rows, cols], "float32", inputArray); 23 | 24 | const [output] = await runner.run([input]); 25 | 26 | displayResult(input, output); 27 | 28 | updateMessage(`Completed`); 29 | } 30 | 31 | function displayResult(input, output) { 32 | const resultDom = document.getElementById("result"); 33 | while (resultDom.firstChild) { 34 | resultDom.removeChild(resultDom.firstChild); 35 | } 36 | 37 | const inputTensorDom = displayTensor(input); 38 | const outputTensorDom = displayTensor(output); 39 | resultDom.appendChild(inputTensorDom); 40 | resultDom.appendChild(document.createTextNode("⇒")); 41 | resultDom.appendChild(outputTensorDom); 42 | } 43 | 44 | function displayTensor(tensor) { 45 | const element = document.createElement("table"); 46 | let html = ""; 47 | for (let row = 0; row < tensor.dims[0]; row++) { 48 | html += ""; 49 | for (let col = 0; col < tensor.dims[1]; col++) { 50 | html += `${tensor.getValue([row, col]).toFixed(2)}`; 51 | } 52 | html += ""; 53 | } 54 | html += ""; 55 | element.innerHTML = html; 56 | return element; 57 | } -------------------------------------------------------------------------------- /example/custom_operator/make_model.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | from torch.autograd import Function 4 | import torch.nn.functional as F 5 | import numpy as np 6 | 7 | class TwiceFunction(Function): 8 | @staticmethod 9 | def symbolic(g, x): 10 | y = g.op("foo_domain::Twice", x) 11 | return y 12 | 13 | @staticmethod 14 | def forward(ctx, input): 15 | numpy_input = input.detach().numpy() 16 | result = numpy_input * 2 17 | return input.new(result) 18 | 19 | @staticmethod 20 | def backward(ctx, grad_output): 21 | numpy_go = grad_output.numpy() 22 | result = numpy_go * 2 23 | return grad_output.new(result) 24 | 25 | def twice(input): 26 | return TwiceFunction.apply(input) 27 | 28 | class MyModel(torch.nn.Module): 29 | def __init__(self): 30 | super().__init__() 31 | 32 | def forward(self, x): 33 | h = twice(x) 34 | h = F.relu(h) 35 | return h 36 | 37 | def main(): 38 | output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "output") 39 | os.makedirs(output_dir, exist_ok=True) 40 | 41 | model = MyModel() 42 | example_input = torch.zeros((2, 3)) 43 | # ONNX model contains two operators: Twice (custom), Relu (standard) 44 | torch.onnx.export(model, (example_input, ), os.path.join(output_dir, "model.onnx")) 45 | # TODO: how to avoid TracerWarning? 46 | 47 | if __name__ == '__main__': 48 | main() 49 | -------------------------------------------------------------------------------- /example/custom_operator/twice.ts: -------------------------------------------------------------------------------- 1 | // Example implementation of custom operator. 2 | import { DataArrayConstructor } from "../../../../interface/core/constants"; 3 | import { OperatorImpl } from "../../../operatorImpl"; 4 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 5 | import { Tensor } from "../../../../interface/core/tensor"; 6 | import { OperatorEntry } from "../../../../interface/core/operator"; 7 | 8 | class Twice extends OperatorImpl { 9 | constructor() { 10 | super("cpu"); 11 | } 12 | 13 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 14 | context.assertsCPUTensorArray(inputs); 15 | const input = inputs[0]; 16 | // constructs TypedArray (e.g. Float32Array) for output 17 | const newData = new DataArrayConstructor[input.dataType](input.data.length); 18 | // computation core 19 | for (let i = 0; i < newData.length; i++) { 20 | newData[i] = input.data[i] * 2; 21 | } 22 | // create output CPUTensor specifying shape, data type, data 23 | const output = context.emptyTensor(input.dims, input.dataType, newData); 24 | return [output]; 25 | } 26 | } 27 | 28 | export function getOpEntries(): OperatorEntry[] { 29 | return [ 30 | { 31 | opType: "Twice", // ONNX Operator name 32 | backend: "cpu", 33 | opsetMin: 1, 34 | factory: () => new Twice(), // Function to construct operator 35 | }, 36 | ]; 37 | } 38 | -------------------------------------------------------------------------------- /example/detr/README.ja.md: -------------------------------------------------------------------------------- 1 | # DETRによる物体検出 2 | 3 | Pythonスクリプトの実行には、PyTorch (`>=1.7`)が必要。 4 | 5 | # 操作手順 6 | ## PyTorchモデルをONNXモデルに変換 7 | ``` 8 | python conversion.py 9 | ``` 10 | 11 | ## Webブラウザ上での実行 12 | 13 | repository rootにて 14 | 15 | ``` 16 | yarn server 17 | ``` 18 | 19 | を実行。この状態で、Webブラウザで[http://localhost:8080/example/detr/](http://localhost:8080/example/detr/)を開く。 20 | -------------------------------------------------------------------------------- /example/detr/README.md: -------------------------------------------------------------------------------- 1 | # Object detection using DETR 2 | 3 | PyTorch (`>=1.7`) is required to run the Python script. 4 | 5 | # Operation procedure 6 | ## Convert PyTorch model into ONNX model 7 | ``` 8 | python conversion.py 9 | ``` 10 | 11 | ## Run on a web browser 12 | 13 | At repository root, execute 14 | 15 | ``` 16 | yarn server 17 | ``` 18 | 19 | With this running, open [http://localhost:8080/example/detr/](http://localhost:8080/example/detr/) with a web browser. 20 | -------------------------------------------------------------------------------- /example/detr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | DETR Object Detection on Web Browser 12 | 18 | 19 | 20 |

DETR Object Detection on Web Browser

21 |
22 |
23 | 29 | 30 |
31 | Drag and drop the input image onto the box above. 32 |

33 |
34 | 35 | Image input rescale width: 36 | px 45 | 52 | 59 |
60 |
61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /example/detr/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DETR test 9 | 10 | 11 | DETR test (see developer console) 12 | 13 | 14 | 15 |

16 |
17 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /example/minimum/README.ja.md: -------------------------------------------------------------------------------- 1 | # 最小の実行サンプル 2 | 3 | 既存のONNXモデルをWebDNNを用いて実行する最小のサンプル。 4 | 5 | このサンプルは、`model/model.onnx`を実行する。このモデルには、`Relu`オペレータだけが含まれている。`make_model.py`に生成方法が記載されている(PyTorchを使用)。 6 | 7 | ## Webブラウザ上での実行 8 | 9 | repository rootにて 10 | 11 | ``` 12 | yarn server 13 | ``` 14 | 15 | を実行。この状態で、Webブラウザで[http://localhost:8080/example/minimum/](http://localhost:8080/example/minimum/)を開く。 16 | -------------------------------------------------------------------------------- /example/minimum/README.md: -------------------------------------------------------------------------------- 1 | # Minimum running example 2 | 3 | A minimal sample of running an existing ONNX model using WebDNN. 4 | 5 | This example runs `model/model.onnx`. This model contains only the `Relu` operator; the generation method is described in `make_model.py` (using PyTorch). 6 | 7 | ## Run on a web browser 8 | 9 | At repository root, execute 10 | 11 | ``` 12 | yarn server 13 | ``` 14 | 15 | With this running, open [http://localhost:8080/example/minimum/](http://localhost:8080/example/minimum/) with a web browser. 16 | -------------------------------------------------------------------------------- /example/minimum/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Minimum example of WebDNN 5 | 6 | 30 | 31 | 32 |

Minimum example of WebDNN

33 |

Runs model which only contains Relu operator.

34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/minimum/make_model.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | import torch.nn.functional as F 4 | import numpy as np 5 | 6 | class MyModel(torch.nn.Module): 7 | def __init__(self): 8 | super().__init__() 9 | 10 | def forward(self, x): 11 | h = F.relu(x) 12 | return h 13 | 14 | def main(): 15 | output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "model") 16 | os.makedirs(output_dir, exist_ok=True) 17 | 18 | model = MyModel() 19 | example_input = torch.zeros((2, 3)) 20 | torch.onnx.export(model, (example_input, ), os.path.join(output_dir, "model.onnx")) 21 | 22 | if __name__ == '__main__': 23 | main() 24 | -------------------------------------------------------------------------------- /example/minimum/model/model.onnx: -------------------------------------------------------------------------------- 1 | pytorch1.7:R 2 |  3 | x1Relu_0"Relutorch-jit-exportZ 4 | x 5 |  6 |  7 | b 8 | 1 9 |  10 |  11 | B -------------------------------------------------------------------------------- /example/resnet/export_pytorch_model.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import os 4 | import subprocess 5 | import urllib.request 6 | import numpy as np 7 | import torch 8 | import torch.onnx 9 | import torch.nn as nn 10 | import torch.nn.functional as F 11 | import torchvision.models as models 12 | from webdnn.tensor_export import serialize_tensors 13 | 14 | torch.manual_seed(0) 15 | 16 | 17 | def export_test_case(output_dir, model): 18 | example_input = torch.randn((1, 3, 224, 224)) 19 | with torch.no_grad(): 20 | example_output = model(example_input) 21 | serialize_tensors(os.path.join(output_dir, "expected.bin"), { 22 | "input": example_input.numpy(), 23 | "output": example_output.numpy(), 24 | }) 25 | 26 | class Model(nn.Module): 27 | def __init__(self): 28 | super().__init__() 29 | # resnet does not include softmax 30 | self.resnet = models.resnet50(pretrained=True) 31 | 32 | def forward(self, x): 33 | h = self.resnet(x) 34 | h = F.softmax(h, dim=-1) 35 | return h 36 | 37 | def download_sample_image(path, url): 38 | if os.path.exists(path): 39 | return 40 | urllib.request.urlretrieve(url, path) 41 | 42 | def main(): 43 | parser = argparse.ArgumentParser() 44 | parser.add_argument("--optimize", action="store_true", help="specify this to make optimized model (takes time)") 45 | args = parser.parse_args() 46 | model = Model() 47 | 48 | output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "output") 49 | 50 | os.makedirs(output_dir, exist_ok=True) 51 | 52 | download_sample_image(os.path.join(output_dir, "000000039769.jpg"), 'http://images.cocodataset.org/val2017/000000039769.jpg') 53 | 54 | onnx_path = os.path.join(output_dir, "model.onnx") 55 | 56 | with torch.no_grad(): 57 | torch.onnx.export(model, (torch.zeros((1, 3, 224, 224))), onnx_path, 58 | verbose=True, 59 | input_names=["input"], 60 | output_names=["output"], opset_version=10) 61 | 62 | export_test_case(output_dir, model) 63 | if args.optimize: 64 | subprocess.check_call(["python", "-m", "webdnn.optimize_model", onnx_path, os.path.join(output_dir, "optimized")]) 65 | 66 | if __name__ == '__main__': 67 | main() 68 | -------------------------------------------------------------------------------- /example/resnet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | ResNet-50 Image Classification on Web Browser 12 | 18 | 19 | 20 |

21 | ResNet-50 Image Classification on Web Browser 22 |

23 |
24 |
25 | 31 | 32 |
33 | Drag and drop the input image onto the box above. 34 |

35 |
36 | 43 | 50 |
51 |
52 | 53 | 54 |
55 |
56 |
57 | Change backend: 58 | WebGPU 59 | WebGL 60 | Webassembly 61 | CPU 62 | Optimized 63 |
64 |
65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /example/resnet/optimized.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | ResNet-50 Image Classification on Web Browser 12 | 18 | 19 | 20 |

21 | ResNet-50 Image Classification on Web Browser (Optimized model) 22 |

23 |
24 |
25 | 31 | 32 |
33 | Drag and drop the input image onto the box above. 34 |

35 |
36 | 43 | 50 |
51 |
52 | 53 | 54 |
55 |
56 | 72 |
73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webdnn", 3 | "version": "2.0.0", 4 | "description": "WebDNN", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist" 8 | ], 9 | "scripts": { 10 | "build": "webpack", 11 | "watch": "webpack -w", 12 | "build:all": "npm run shader:wasm && npm run shader:webgpu && npm run makeShaderList && npm run build && npm run build:core", 13 | "build:core": "webpack --config webpack-core.config.js", 14 | "build:cpu": "webpack --config webpack-cpu.config.js", 15 | "build:wasm": "webpack --config webpack-wasm.config.js", 16 | "build:webgl1-16384": "webpack --config webpack-webgl1-16384.config.js", 17 | "build:webgl1-4096": "webpack --config webpack-webgl1-4096.config.js", 18 | "build:webgl2-16384": "webpack --config webpack-webgl2-16384.config.js", 19 | "build:webgl2-4096": "webpack --config webpack-webgl2-4096.config.js", 20 | "build:webgpu": "webpack --config webpack-webgpu.config.js", 21 | "eslint": "eslint ./src/descriptor_runner/**/*.ts", 22 | "eslint:fix": "eslint --fix ./src/descriptor_runner/**/*.ts", 23 | "shader:wasm": "python src/shader/wasm/compile.py", 24 | "shader:webgpu": "node src/shader/webgpu/compile.js", 25 | "makeShaderList": "python scripts/make_operator_entries.py", 26 | "prepublishOnly": "npm run build", 27 | "server": "npx http-server -c-1", 28 | "test": "echo \"Error: no test specified\" && exit 1" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git@github:mil-tokyo/webdnn.git" 33 | }, 34 | "author": { 35 | "name": "Masatoshi Hidaka", 36 | "email": "hidaka@edgeintelligence.jp" 37 | }, 38 | "license": "MIT", 39 | "dependencies": { 40 | "@webgpu/types": "^0.0.45", 41 | "onnx-proto": "^4.0.4", 42 | "pako": "^2.0.3" 43 | }, 44 | "devDependencies": { 45 | "@types/pako": "^1.0.1", 46 | "@typescript-eslint/eslint-plugin": "^4.23.0", 47 | "@typescript-eslint/parser": "^4.23.0", 48 | "@webgpu/glslang": "^0.0.15", 49 | "eslint": "^7.26.0", 50 | "eslint-config-prettier": "^8.3.0", 51 | "eslint-plugin-prettier": "^3.4.0", 52 | "prettier": "^2.3.0", 53 | "ts-loader": "^8.0.17", 54 | "typescript": "^4.2.3", 55 | "webpack": "^5.24.4", 56 | "webpack-cli": "^4.5.0" 57 | }, 58 | "keywords": [ 59 | "deep-neural-networks", 60 | "accelerate", 61 | "optimization", 62 | "javascript", 63 | "webgpu" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /requirements.test.txt: -------------------------------------------------------------------------------- 1 | onnx 2 | onnxruntime 3 | torch 4 | torchvision 5 | -------------------------------------------------------------------------------- /scripts/make_operator_entries.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import re 4 | 5 | def make_operator_entries(base_dir, standard): 6 | os.chdir(base_dir) 7 | import_lines = [] 8 | push_lines = [] 9 | glob_path = "./operators/standard/**/*.ts" if standard else "./operators/**/*.ts" 10 | for ts_path in sorted(glob.glob(glob_path, recursive=True)): 11 | ts_relative_path = ts_path.replace("\\", "/")[:-3] 12 | func_name = "getOpEntries" + re.sub("[^a-zA-Z0-9]", "", ts_relative_path) 13 | import_lines.append(f"import {{ getOpEntries as {func_name} }} from \"{ts_relative_path}\";") 14 | push_lines.append(f" entries.push(...{func_name}());") 15 | entry_src = """// auto-generated by scripts/make_operator_entries.py 16 | import { OperatorEntry } from "../../interface/core/operator"; 17 | 18 | """+"\n".join(import_lines)+""" 19 | 20 | export function getOpEntries(): OperatorEntry[] { 21 | const entries: OperatorEntry[] = []; 22 | """+"\n".join(push_lines)+""" 23 | return entries; 24 | } 25 | """ 26 | with open(f"{base_dir}/opEntries{'Standard' if standard else 'All'}.ts", "w", newline="\n") as f: 27 | f.write(entry_src) 28 | 29 | def make_operator_entries_all_backend(): 30 | operators_root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/src/descriptor_runner/operators/" 31 | for standard in [False, True]: 32 | for backend in ["cpu", "wasm", "webgl", "webgpu"]: 33 | make_operator_entries(operators_root_dir + backend, standard) 34 | 35 | 36 | def main(): 37 | make_operator_entries_all_backend() 38 | 39 | 40 | main() 41 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info < (3, 6): 4 | sys.stderr.write("Sorry, this library only works with python >= 3.6\n") 5 | sys.exit(1) 6 | 7 | import json 8 | from setuptools import setup, find_packages 9 | 10 | with open("./package.json") as f: 11 | package_info = json.load(f) 12 | 13 | setup( 14 | name="webdnn", 15 | version=package_info["version"], 16 | python_requires=">=3.6", 17 | package_dir={"": "src/graph_transpiler"}, 18 | packages=find_packages("src/graph_transpiler"), 19 | package_data={"": ["*.js"]}, install_requires=['numpy'], 20 | url="https://github.com/mil-tokyo/webdnn", 21 | description=package_info["description"], 22 | author=package_info["author"]["name"], 23 | author_email=package_info["author"]["email"], 24 | keywords=" ".join(package_info["keywords"]), 25 | classifiers=[ 26 | "Development Status :: 5 - Production/Stable", 27 | "Topic :: Scientific/Engineering :: Artificial Intelligence", 28 | "Intended Audience :: Science/Research", 29 | "License :: OSI Approved :: MIT License", 30 | "Programming Language :: Python :: 3.6" 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /src/descriptor_runner/backend/cpu/cpuContextImpl.ts: -------------------------------------------------------------------------------- 1 | import { CPUTensor } from "../.."; 2 | import { WebDNNCPUContext } from "../../interface/backend/cpu/cpuContext"; 3 | import { DataArrayTypes, DataType } from "../../interface/core/constants"; 4 | import { Tensor } from "../../interface/core/tensor"; 5 | import { CPUTensorImpl } from "./cpuTensorImpl"; 6 | 7 | export class WebDNNCPUContextImpl implements WebDNNCPUContext { 8 | backend = "cpu" as const; 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-empty-function 11 | async initialize(): Promise {} 12 | 13 | isCPUTensor(tensor: Tensor): tensor is CPUTensor { 14 | return tensor.backend === this.backend; 15 | } 16 | 17 | assertsCPUTensor(tensor: Tensor): asserts tensor is CPUTensor { 18 | if (tensor.backend !== this.backend) { 19 | throw new Error( 20 | `Tensor backend ${this.backend} is expected, but ${tensor.backend} is given.` 21 | ); 22 | } 23 | } 24 | 25 | assertsCPUTensorArray(tensors: Tensor[]): asserts tensors is CPUTensor[] { 26 | for (const tensor of tensors) { 27 | if (tensor.backend !== this.backend) { 28 | throw new Error( 29 | `Tensor backend ${this.backend} is expected, but ${tensor.backend} is given.` 30 | ); 31 | } 32 | } 33 | } 34 | 35 | emptyTensor( 36 | dims: ReadonlyArray, 37 | dataType?: DataType, 38 | data?: DataArrayTypes 39 | ): CPUTensor { 40 | return new CPUTensorImpl(dims, dataType, data); 41 | } 42 | 43 | async moveTensor(tensor: Tensor): Promise { 44 | const dst = new CPUTensorImpl( 45 | tensor.dims, 46 | tensor.dataType, 47 | await tensor.getData() 48 | ); 49 | return dst; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/descriptor_runner/backend/cpu/cpuTensorImpl.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataArrayConstructor, 3 | DataArrayTypes, 4 | DataType, 5 | } from "../../interface/core/constants"; 6 | import { TensorImpl } from "../../core/tensorImpl"; 7 | import { CPUTensor } from "../../interface/backend/cpu/cpuTensor"; 8 | import { WebDNNLogging } from "../../logging"; 9 | 10 | const logger = WebDNNLogging.getLogger("WebDNN.CPUTensorImpl"); 11 | 12 | let perfTotalMemory = 0; 13 | 14 | export class CPUTensorImpl extends TensorImpl implements CPUTensor { 15 | data: DataArrayTypes; 16 | useExternalBuffer: boolean; 17 | 18 | constructor( 19 | dims: ReadonlyArray, 20 | dataType: DataType = "float32", 21 | data?: DataArrayTypes 22 | ) { 23 | super(dims, dataType, "cpu"); 24 | this.data = data || new DataArrayConstructor[dataType](this.length); 25 | if (data) { 26 | this.useExternalBuffer = true; 27 | logger.debug("CPU memory use existing buffer", { 28 | size: this.data.byteLength, 29 | total: perfTotalMemory, 30 | }); 31 | } else { 32 | this.useExternalBuffer = false; 33 | perfTotalMemory += this.data.byteLength; 34 | logger.debug("CPU memory allocation", { 35 | size: this.data.byteLength, 36 | total: perfTotalMemory, 37 | }); 38 | } 39 | } 40 | 41 | async getData(): Promise { 42 | return this.data; 43 | } 44 | 45 | async setData(data: DataArrayTypes): Promise { 46 | this.data.set(data); 47 | } 48 | 49 | dispose(): void { 50 | if (!this.useExternalBuffer) { 51 | perfTotalMemory -= this.data.byteLength; 52 | } 53 | logger.debug("CPU memory free", { 54 | size: this.data.byteLength, 55 | total: perfTotalMemory, 56 | }); 57 | this.data = new Float32Array(1); 58 | } 59 | 60 | static isCPUTensor(tensor: TensorImpl): tensor is CPUTensorImpl { 61 | return tensor.backend === "cpu"; 62 | } 63 | 64 | getDataSync(): DataArrayTypes { 65 | return this.data; 66 | } 67 | 68 | getValue(idxs: number[]): number { 69 | if (idxs.length !== this.ndim) { 70 | throw new Error("length of idxs does not match tensor.ndim"); 71 | } 72 | let ofs = 0; 73 | for (let i = 0; i < this.ndim; i++) { 74 | ofs += this.strides[i] * idxs[i]; 75 | } 76 | return this.data[ofs]; 77 | } 78 | 79 | setValue(value: number, idxs: number[]): void { 80 | if (idxs.length !== this.ndim) { 81 | throw new Error("length of idxs does not match tensor.ndim"); 82 | } 83 | let ofs = 0; 84 | for (let i = 0; i < this.ndim; i++) { 85 | ofs += this.strides[i] * idxs[i]; 86 | } 87 | this.data[ofs] = value; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/descriptor_runner/backend/webgpu/webgpuTensorImpl.ts: -------------------------------------------------------------------------------- 1 | import { DataArrayTypes, DataType } from "../../interface/core/constants"; 2 | import { TensorImpl } from "../../core/tensorImpl"; 3 | import { WebDNNWebGPUContextImpl } from "./webgpuContextImpl"; 4 | import { WebGPUTensor } from "../../interface/backend/webgpu/webgpuTensor"; 5 | 6 | export class WebGPUTensorImpl extends TensorImpl implements WebGPUTensor { 7 | buffer: GPUBuffer; 8 | 9 | private mappedForWriteFromCPU: boolean; 10 | 11 | bufferSize: number; // Unit: byte 12 | 13 | constructor( 14 | private context: WebDNNWebGPUContextImpl, 15 | dims: ReadonlyArray, 16 | dataType: DataType = "float32", 17 | public readonly forWriteFromCPU: boolean = false, 18 | public readonly forReadToCPU: boolean = true 19 | ) { 20 | super(dims, dataType, "webgpu"); 21 | if (dataType !== "float32") { 22 | throw new Error("WebGLTensor only supports float32"); 23 | } 24 | if (forWriteFromCPU && forReadToCPU) { 25 | throw new Error("WebGPUTensor cannot be both for read and write"); 26 | } 27 | 28 | this.bufferSize = Math.max(this.length * Float32Array.BYTES_PER_ELEMENT, 4); 29 | let usage = GPUBufferUsage.STORAGE; 30 | if (forReadToCPU) { 31 | usage |= GPUBufferUsage.COPY_SRC; 32 | } 33 | this.buffer = this.context.device.createBuffer({ 34 | mappedAtCreation: forWriteFromCPU, 35 | size: this.bufferSize, 36 | usage, 37 | }); 38 | this.mappedForWriteFromCPU = forWriteFromCPU; 39 | } 40 | 41 | async getData(): Promise { 42 | const data: Float32Array = new Float32Array(this.length), 43 | dst = this.context.device.createBuffer({ 44 | size: this.bufferSize, 45 | usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, 46 | }), 47 | commandEncoder = this.context.device.createCommandEncoder(); 48 | commandEncoder.copyBufferToBuffer(this.buffer, 0, dst, 0, this.bufferSize); 49 | this.context.device.queue.submit([commandEncoder.finish()]); 50 | await dst.mapAsync(GPUMapMode.READ); 51 | const arrayBuffer = dst.getMappedRange(), 52 | buffer_mapped_array = new Float32Array(arrayBuffer, 0, this.length); 53 | data.set(buffer_mapped_array); 54 | dst.unmap(); 55 | dst.destroy(); 56 | return data; 57 | } 58 | 59 | async setData(data: DataArrayTypes): Promise { 60 | if (!this.mappedForWriteFromCPU) { 61 | throw new Error("The buffer is not mapped"); 62 | } 63 | const ab = this.buffer.getMappedRange(), 64 | mappedArray = new Float32Array(ab); 65 | mappedArray.set(data); 66 | this.buffer.unmap(); 67 | this.mappedForWriteFromCPU = false; 68 | } 69 | 70 | dispose(): void { 71 | if (this.buffer) { 72 | this.buffer.destroy(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/descriptor_runner/core/inputProxy.ts: -------------------------------------------------------------------------------- 1 | import { DataType } from "../interface/core/constants"; 2 | import { arrayProd } from "../operators/operatorUtil"; 3 | 4 | export class InputProxy implements ArrayLike { 5 | readonly length: number; 6 | 7 | [n: number]: number; 8 | 9 | readonly dims: ReadonlyArray; 10 | 11 | constructor(dims: ReadonlyArray, public readonly dataType: DataType) { 12 | this.dims = dims; 13 | const length = arrayProd(dims); 14 | this.length = length; 15 | for (let i = 0; i < length; i++) { 16 | this[i] = 0; 17 | } 18 | /* 19 | * For large length, error occurs (RangeError: Maximum call stack size exceeded) 20 | * Array.prototype.push.apply( this, new Array(length) ); 21 | */ 22 | } 23 | 24 | set(array: ArrayLike): void { 25 | for (let i = 0; i < array.length; i++) { 26 | this[i] = array[i]; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/descriptor_runner/core/modelTransform.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import { onnx } from "onnx-proto"; 3 | import { Backend } from "../interface/core/constants"; 4 | import { WebDNNLogging } from "../logging"; 5 | 6 | const logger = WebDNNLogging.getLogger("WebDNN.modelTransform"); 7 | 8 | export function modelTransform( 9 | model: onnx.ModelProto, 10 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 11 | backendOrder: Backend[] 12 | ): void { 13 | /* 14 | * TODO: implementation 15 | * if (backendOrder.includes("webgl")) { 16 | * const webglContext = WebDNNWebGLContext.getInstance(); 17 | * if (webglContext.webgl2 && webglContext.canOnlyReadRGBA) { 18 | * outputPackRGBA(graph, backendOrder); 19 | * } 20 | * } 21 | */ 22 | renameDuplicatedNode(model); 23 | } 24 | 25 | function renameDuplicatedNode(model: onnx.ModelProto): void { 26 | const usedNames = new Set(); 27 | for (const node of model.graph!.node!) { 28 | let origName = node.name; 29 | if (!origName) { 30 | origName = "unnamed"; 31 | } 32 | if (usedNames.has(origName)) { 33 | let newName = origName + "_"; 34 | while (usedNames.has(newName)) { 35 | newName = newName + "_"; 36 | } 37 | node.name = newName; 38 | usedNames.add(newName); 39 | logger.warn( 40 | `node name ${origName} is already used: renaming to ${newName}` 41 | ); 42 | } else { 43 | usedNames.add(origName); 44 | } 45 | } 46 | } 47 | 48 | /** 49 | * テンソルを開放するタイミングを計算する。 50 | * @param model 計算対象のモデル 51 | * @returns key: オペレータ名, value: そのオペレータ完了直後に開放するテンソルの名前 52 | */ 53 | export function findTensorReleaseTiming( 54 | model: onnx.ModelProto, 55 | initializerTensorNames: Set 56 | ): Map { 57 | const lastReferencedAt = new Map(), 58 | graph = model.graph!; 59 | for (const node of graph.node!) { 60 | for (const inputName of node.input!) { 61 | lastReferencedAt.set(inputName, node.name!); 62 | } 63 | } 64 | // Optimized modelではgraph.initializer以外からテンソルを読み込むため、実際に読み込まれたテンソル名リストを用いる 65 | for (const initializer of initializerTensorNames) { 66 | lastReferencedAt.delete(initializer); 67 | } 68 | for (const input of graph.input!) { 69 | lastReferencedAt.delete(input.name!); 70 | } 71 | for (const output of graph.output!) { 72 | lastReferencedAt.delete(output.name!); 73 | } 74 | 75 | const timing = new Map(); 76 | for (const [name, last] of lastReferencedAt.entries()) { 77 | const t = timing.get(last) || []; 78 | t.push(name); 79 | timing.set(last, t); 80 | } 81 | 82 | return timing; 83 | } 84 | -------------------------------------------------------------------------------- /src/descriptor_runner/core/operatorTable.ts: -------------------------------------------------------------------------------- 1 | import { Backend } from "../interface/core/constants"; 2 | import { Operator, OperatorEntry } from "../interface/core/operator"; 3 | 4 | const registeredOperators: { 5 | [opType: string]: OperatorEntry[]; 6 | } = {}; 7 | 8 | function registerOperator(operatorEntry: OperatorEntry) { 9 | if (!(operatorEntry.opType in registeredOperators)) { 10 | registeredOperators[operatorEntry.opType] = []; 11 | } 12 | registeredOperators[operatorEntry.opType].push(operatorEntry); 13 | } 14 | 15 | export function registerOperators(operatorEntries: OperatorEntry[]): void { 16 | for (const entry of operatorEntries) { 17 | registerOperator(entry); 18 | } 19 | } 20 | 21 | export function instantiateOperator( 22 | opType: string, 23 | opset: number, 24 | backendOrder: Backend[], 25 | currentTensorsBackends: Backend[][] 26 | ): Operator | null { 27 | const entries = registeredOperators[opType]; 28 | if (!entries) { 29 | return null; 30 | } 31 | 32 | let localBackendOrder = backendOrder; 33 | // 特殊なオペレータ 34 | switch (opType) { 35 | case "Flatten": 36 | case "Pad": 37 | case "Reshape": 38 | case "Squeeze": 39 | case "Transpose": 40 | case "Unsqueeze": 41 | // データ側テンソル(currentTensorsBackends[0])のあるオペレータ上で実行 42 | for (const backend of backendOrder) { 43 | if (currentTensorsBackends[0].includes(backend)) { 44 | localBackendOrder = [backend]; 45 | } 46 | } 47 | break; 48 | case "Shape": 49 | // 常にCPU 50 | localBackendOrder = ["cpu"]; 51 | break; 52 | } 53 | 54 | for (const backend of localBackendOrder) { 55 | for (const entry of entries) { 56 | if (entry.backend !== backend) { 57 | continue; 58 | } 59 | if (entry.opsetMin > opset) { 60 | continue; 61 | } 62 | if (entry.opsetMax != null && entry.opsetMax <= opset) { 63 | continue; 64 | } 65 | return entry.factory(); 66 | } 67 | } 68 | return null; 69 | } 70 | -------------------------------------------------------------------------------- /src/descriptor_runner/core/outputProxy.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataArrayConstructor, 3 | DataArrayTypes, 4 | DataType, 5 | } from "../interface/core/constants"; 6 | import { arrayProd } from "../operators/operatorUtil"; 7 | 8 | export class OutputProxy implements ArrayLike { 9 | readonly length: number; 10 | 11 | [n: number]: number; 12 | 13 | readonly dims: ReadonlyArray; 14 | 15 | constructor(dims: ReadonlyArray, public readonly dataType: DataType) { 16 | this.dims = dims; 17 | const length = arrayProd(dims); 18 | this.length = length; 19 | for (let i = 0; i < length; i++) { 20 | this[i] = 0; 21 | } 22 | } 23 | 24 | set(array: ArrayLike): void { 25 | for (let i = 0; i < array.length; i++) { 26 | this[i] = array[i]; 27 | } 28 | } 29 | 30 | toActual(): DataArrayTypes { 31 | const ta = new DataArrayConstructor[this.dataType](this.length); 32 | for (let i = 0; i < this.length; i++) { 33 | ta[i] = this[i]; 34 | } 35 | return ta; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/descriptor_runner/core/tensorDecoder/decodeTensorRaw.ts: -------------------------------------------------------------------------------- 1 | import Long from "long"; 2 | import { onnx } from "onnx-proto"; 3 | import { DataArrayTypes } from "../../interface/core/constants"; 4 | import { clipLong } from "../../util"; 5 | 6 | export function decodeTensorRaw( 7 | buf: ArrayBuffer, 8 | bodyByteOffset: number, 9 | bodyCompressedLength: number, 10 | dataType: number, 11 | numel: number 12 | ): DataArrayTypes { 13 | let data: DataArrayTypes; 14 | switch (dataType) { 15 | case onnx.TensorProto.DataType.FLOAT: 16 | data = new Float32Array(numel); 17 | break; 18 | case onnx.TensorProto.DataType.INT32: 19 | data = new Int32Array(numel); 20 | break; 21 | case onnx.TensorProto.DataType.INT64: { 22 | data = new Int32Array(numel); 23 | const view = new DataView(buf, bodyByteOffset, numel * 8); 24 | for (let idx = 0; idx < numel; idx++) { 25 | data[idx] = clipLong( 26 | new Long( 27 | view.getUint32(idx * 8, true), 28 | view.getUint32(idx * 8 + 4, true) 29 | ) 30 | ); 31 | } 32 | return data; 33 | } 34 | default: 35 | throw new Error("Unsupported DataType"); 36 | } 37 | // Buf may not be aligned 38 | const dataUint8View = new Uint8Array(data.buffer), 39 | srcUint8View = new Uint8Array(buf, bodyByteOffset, data.byteLength); 40 | dataUint8View.set(srcUint8View); 41 | return data; 42 | } 43 | -------------------------------------------------------------------------------- /src/descriptor_runner/core/tensorImpl.ts: -------------------------------------------------------------------------------- 1 | import { Backend, DataArrayTypes, DataType } from "../interface/core/constants"; 2 | import { Tensor } from "../interface/core/tensor"; 3 | 4 | export abstract class TensorImpl implements Tensor { 5 | readonly dims: ReadonlyArray; 6 | 7 | readonly ndim: number; 8 | 9 | readonly length: number; 10 | 11 | readonly strides: ReadonlyArray; 12 | 13 | constructor( 14 | dims: ReadonlyArray, 15 | readonly dataType: DataType, 16 | readonly backend: Backend 17 | ) { 18 | this.dims = dims.slice(); // 呼び出し元で誤って書き換えることを防止 19 | this.ndim = dims.length; 20 | let length = 1; 21 | const strides: number[] = []; 22 | for (let d = dims.length - 1; d >= 0; d--) { 23 | const dim = dims[d]; 24 | strides.unshift(length); 25 | length *= dim; 26 | } 27 | this.length = length; 28 | this.strides = strides; 29 | } 30 | 31 | abstract getData(): Promise; 32 | 33 | abstract setData(data: DataArrayTypes): Promise; 34 | 35 | abstract dispose(): void; 36 | } 37 | -------------------------------------------------------------------------------- /src/descriptor_runner/image.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module webdnn/image 3 | * @preferred 4 | * 5 | * Module `WebDNN.Image` provides basic image processing operations like follows. 6 | * 7 | * - Load image by various way (File picker dialog, url string, canvas, video, etc.) 8 | * - Pack image data into TypedArray 9 | * - Crop and resize. 10 | * - Show result on canvas element 11 | * 12 | */ 13 | /** Don't Remove This comment block */ 14 | // Export * from "./image/canvas" // internal API 15 | export * from "./image/enums"; 16 | export * from "./image/image_array"; 17 | // Export * from "./image/image_data" // internal API 18 | export * from "./image/image_source"; 19 | -------------------------------------------------------------------------------- /src/descriptor_runner/image/canvas.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module webdnn/image 3 | */ 4 | /** Don't Remove This comment block */ 5 | 6 | /** 7 | * Get canvas rendering context and check whether it is nonnull value. 8 | * 9 | * @param {CanvasRenderingContext2D} canvas 10 | * @protected 11 | */ 12 | export function getContext2D( 13 | canvas: HTMLCanvasElement 14 | ): CanvasRenderingContext2D { 15 | const context = canvas.getContext("2d"); 16 | if (!context) throw Error("CanvasRenderingContext2D initialization failed"); 17 | 18 | return context as CanvasRenderingContext2D; 19 | } 20 | -------------------------------------------------------------------------------- /src/descriptor_runner/image/enums.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module webdnn/image 3 | */ 4 | 5 | /** Don't Remove This comment block */ 6 | 7 | /** 8 | * The data order 9 | */ 10 | export enum Order { 11 | /** `[Channel, Height, Width]` format */ 12 | CHW, 13 | 14 | /** `[Height, Width, Channel]` format */ 15 | HWC, 16 | } 17 | 18 | /** 19 | * The color format 20 | */ 21 | export enum Color { 22 | /** RGB format */ 23 | RGB, 24 | 25 | /** BGR format */ 26 | BGR, 27 | 28 | /** Grey scale */ 29 | GREY, 30 | 31 | /** RGBA format */ 32 | RGBA, 33 | 34 | /** BGRA format */ 35 | BGRA, 36 | } 37 | -------------------------------------------------------------------------------- /src/descriptor_runner/image/image_source.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module webdnn/image 3 | */ 4 | 5 | /** Don't Remove This comment block */ 6 | 7 | /** 8 | * Load image of specified url 9 | * 10 | * @param {string} url the image url 11 | * @returns {Promise} image element 12 | */ 13 | export async function loadImageByUrl(url: string): Promise { 14 | const image = document.createElement("img"); 15 | 16 | return new Promise((resolve, reject) => { 17 | image.onload = resolve; 18 | image.onerror = reject; 19 | image.src = url; 20 | }).then(() => image); 21 | } 22 | 23 | /* istanbul ignore next */ 24 | /** 25 | * Load image file selected in `` element. 26 | * 27 | * @param {HTMLInputElement} input the `` element 28 | * @returns {Promise} image element 29 | */ 30 | export async function loadImageFromFileInput( 31 | input: HTMLInputElement 32 | ): Promise { 33 | const { files } = input; 34 | if (!files || files.length == 0) throw new Error("No file is selected"); 35 | 36 | const url = URL.createObjectURL(files[0]); 37 | 38 | return loadImageByUrl(url); 39 | } 40 | 41 | /* istanbul ignore next */ 42 | /** 43 | * Load image selected in file picker dialog 44 | * 45 | * Currently, web specification not supported the case if the dialog is canceled and no file is selected. In this case, 46 | * the returned promise will never be resolved. 47 | * 48 | * @returns {Promise} image element 49 | * @protected 50 | */ 51 | export async function loadImageByDialog(): Promise { 52 | const input = document.createElement("input"); 53 | input.style.display = "none"; 54 | input.type = "file"; 55 | input.accept = "image/*"; 56 | // Avoid GC for iOS Safari 57 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 58 | (window as any)._webdnn_image_input = input; 59 | 60 | return new Promise((resolve) => { 61 | input.onchange = () => { 62 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 63 | delete (window as any)._webdnn_image_input; 64 | resolve(loadImageFromFileInput(input)); 65 | }; 66 | input.click(); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/backend/cpu/cpuContext.ts: -------------------------------------------------------------------------------- 1 | import { BackendContext } from "../../core/backendContext"; 2 | import { DataArrayTypes, DataType } from "../../core/constants"; 3 | import { Tensor } from "../../core/tensor"; 4 | import { CPUTensor } from "./cpuTensor"; 5 | 6 | export interface WebDNNCPUContext extends BackendContext { 7 | backend: "cpu"; 8 | initialize(): Promise; 9 | isCPUTensor(tensor: Tensor): tensor is CPUTensor; 10 | assertsCPUTensor(tensor: Tensor): asserts tensor is CPUTensor; 11 | assertsCPUTensorArray(tensors: Tensor[]): asserts tensors is CPUTensor[]; 12 | emptyTensor( 13 | dims: ReadonlyArray, 14 | dataType?: DataType, 15 | data?: DataArrayTypes 16 | ): CPUTensor; 17 | // eslint-disable-next-line @typescript-eslint/ban-types 18 | moveTensor(tensor: Tensor, option: {}): Promise; 19 | } 20 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/backend/cpu/cpuTensor.ts: -------------------------------------------------------------------------------- 1 | import { DataArrayTypes } from "../../core/constants"; 2 | import { Tensor } from "../../core/tensor"; 3 | 4 | export interface CPUTensor extends Tensor { 5 | data: DataArrayTypes; 6 | getDataSync(): DataArrayTypes; 7 | getValue(idxs: number[]): number; 8 | setValue(value: number, idxs: number[]): void; 9 | useExternalBuffer: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/backend/wasm/wasmContext.ts: -------------------------------------------------------------------------------- 1 | import { BackendContext } from "../../core/backendContext"; 2 | import { DataType } from "../../core/constants"; 3 | import { Tensor } from "../../core/tensor"; 4 | import { WebDNNCPUContext } from "../cpu/cpuContext"; 5 | import { WasmTensor } from "./wasmTensor"; 6 | 7 | // for future use 8 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 9 | export interface WebDNNWasmContextOption {} 10 | 11 | export interface WasmKernelArgumentTensor { 12 | type: "tensor"; 13 | value: WasmTensor; 14 | } 15 | 16 | export interface WasmKernelArgumentFloat32 { 17 | type: "float32"; 18 | value: number; 19 | } 20 | 21 | export interface WasmKernelArgumentInt32 { 22 | type: "int32"; 23 | value: number; 24 | } 25 | 26 | export type WasmKernelArgument = 27 | | WasmKernelArgumentTensor 28 | | WasmKernelArgumentFloat32 29 | | WasmKernelArgumentInt32; 30 | 31 | export interface WebDNNWasmContext extends BackendContext { 32 | backend: "wasm"; 33 | cpuContext: WebDNNCPUContext; 34 | initialize(wasmWorkerSrcUrl: string): Promise; 35 | isWasmTensor(tensor: Tensor): tensor is WasmTensor; 36 | assertsWasmTensor(tensor: Tensor): asserts tensor is WasmTensor; 37 | assertsWasmTensorArray(tensors: Tensor[]): asserts tensors is WasmTensor[]; 38 | emptyTensor(dims: ReadonlyArray, dataType?: DataType): WasmTensor; 39 | // eslint-disable-next-line @typescript-eslint/ban-types 40 | moveTensor(tensor: Tensor, option: {}): Promise; 41 | runKernel(name: string, args: WasmKernelArgument[]): void; 42 | } 43 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/backend/wasm/wasmTensor.ts: -------------------------------------------------------------------------------- 1 | import { Tensor } from "../../core/tensor"; 2 | 3 | export interface WasmSharedBufferInterface { 4 | backendBufferId: number; 5 | } 6 | export interface WasmTensor extends Tensor { 7 | sharedBuffer: WasmSharedBufferInterface; 8 | } 9 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/backend/webgl/webglContext.ts: -------------------------------------------------------------------------------- 1 | import { BackendContext } from "../../core/backendContext"; 2 | import { DataType } from "../../core/constants"; 3 | import { Tensor } from "../../core/tensor"; 4 | import { WebDNNCPUContext } from "../cpu/cpuContext"; 5 | import { WebGLTensor } from "./webglTensor"; 6 | 7 | export interface WebGLUniformItem { 8 | name: string; 9 | value: number; 10 | type: "float" | "int"; 11 | } 12 | 13 | export type WebDNNWebGLVersion = 14 | | "webgl2-16384" 15 | | "webgl2-4096" 16 | | "webgl1-16384" 17 | | "webgl1-4096"; 18 | 19 | export interface WebDNNWebGLContextOption { 20 | /** 21 | * Version order in which initialization is attempted. 22 | * The version means the combination of webgl version and max texture size. 23 | */ 24 | versionOrder?: WebDNNWebGLVersion[]; 25 | /** 26 | * Maximum GPU memory allocation in the context. 27 | * Pool deleted textures for future use until this capacity is exceeded. 28 | */ 29 | maxAllocationBytes?: number; 30 | /** 31 | * When memory deletion is needed, the deallocation occurs until total memory allocation becomes below this value. 32 | */ 33 | deallocateToBytes?: number; 34 | } 35 | 36 | export interface WebDNNWebGLContextPerformance { 37 | key: string; 38 | kernelName: string; 39 | inputs: { name: string; dims: number[] }[]; 40 | output: { dims: number[] }; 41 | elapsedNanoSecond: number; 42 | gpuDisjoint: boolean; 43 | } 44 | 45 | export interface WebDNNWebGLContext extends BackendContext { 46 | backend: "webgl"; 47 | cpuContext: WebDNNCPUContext; 48 | canOnlyReadRGBA: boolean; 49 | gl: WebGLRenderingContext | WebGL2RenderingContext; 50 | webgl2: boolean; 51 | maxTextureSize: number; 52 | version: WebDNNWebGLVersion; 53 | 54 | initialize(): Promise; 55 | isWebGLTensor(tensor: Tensor): tensor is WebGLTensor; 56 | assertsWebGLTensor(tensor: Tensor): asserts tensor is WebGLTensor; 57 | assertsWebGLTensorArray(tensors: Tensor[]): asserts tensors is WebGLTensor[]; 58 | emptyTensor( 59 | dims: ReadonlyArray, 60 | dataType?: DataType, 61 | option?: { dimPerPixel?: 1 | 4; textureShape?: ReadonlyArray } 62 | ): WebGLTensor; 63 | moveTensor( 64 | tensor: Tensor, 65 | option: { dimPerPixel?: 1 | 4; textureShape?: ReadonlyArray } 66 | ): Promise; 67 | addKernel(name: string, sourceCode: string): void; 68 | hasKernel(name: string): boolean; 69 | runKernel( 70 | name: string, 71 | inputs: { tensor: WebGLTensor; name: string }[], 72 | output: WebGLTensor, 73 | uniforms: WebGLUniformItem[] 74 | ): Promise; 75 | enablePerformanceQuery(key: string | null): void; 76 | gatherPerformanceQueryResult(): Promise; 77 | } 78 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/backend/webgl/webglTensor.ts: -------------------------------------------------------------------------------- 1 | import { Tensor } from "../../core/tensor"; 2 | 3 | export interface WebGLTensor extends Tensor { 4 | readonly textureWidth: number; 5 | readonly textureHeight: number; 6 | readonly dimPerPixel: 1 | 4; 7 | 8 | alias(dims: ReadonlyArray): WebGLTensor; 9 | } 10 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/backend/webgpu/webgpuContext.ts: -------------------------------------------------------------------------------- 1 | import { BackendContext } from "../../core/backendContext"; 2 | import { DataType } from "../../core/constants"; 3 | import { Tensor } from "../../core/tensor"; 4 | import { WebDNNCPUContext } from "../cpu/cpuContext"; 5 | import { WebGPUTensor } from "./webgpuTensor"; 6 | 7 | // for future use 8 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 9 | export interface WebDNNWebGPUContextOption {} 10 | 11 | type WorkGroupDim = "x" | "y" | "z"; 12 | 13 | export interface WebGPUMetaBufferContentElement { 14 | value: number; 15 | type: "int32" | "uint32" | "float32"; 16 | } 17 | 18 | export interface WebGPUMetaBufferContent { 19 | elements: WebGPUMetaBufferContentElement[]; 20 | } 21 | 22 | export interface WebGPURunnerRequest { 23 | pipelineName: string; 24 | tensors: WebGPUTensor[]; 25 | meta: WebGPUMetaBufferContent | null; 26 | workGroups: { [key in WorkGroupDim]: number }; 27 | } 28 | 29 | export interface WebDNNWebGPUContext extends BackendContext { 30 | backend: "webgpu"; 31 | cpuContext: WebDNNCPUContext; 32 | initialize(): Promise; 33 | isWebGLTensor(tensor: Tensor): tensor is WebGPUTensor; 34 | assertsWebGPUTensor(tensor: Tensor): asserts tensor is WebGPUTensor; 35 | assertsWebGPUTensorArray( 36 | tensors: Tensor[] 37 | ): asserts tensors is WebGPUTensor[]; 38 | 39 | emptyTensor( 40 | dims: ReadonlyArray, 41 | dataType?: DataType, 42 | forWriteFromCPU?: boolean, 43 | forReadToCPU?: boolean 44 | ): WebGPUTensor; 45 | // eslint-disable-next-line @typescript-eslint/ban-types 46 | moveTensor(tensor: Tensor, option: {}): Promise; 47 | hasPipeline(name: string): boolean; 48 | createPipeline(name: string, shader: Uint32Array, nBuffers: number): void; 49 | run(request: WebGPURunnerRequest): Promise; 50 | } 51 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/backend/webgpu/webgpuTensor.ts: -------------------------------------------------------------------------------- 1 | import { Tensor } from "../../core/tensor"; 2 | 3 | export type WebGPUTensor = Tensor; 4 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/core/backendContext.ts: -------------------------------------------------------------------------------- 1 | import { Backend, DataType } from "./constants"; 2 | import { Tensor } from "./tensor"; 3 | 4 | export interface BackendContext { 5 | backend: Backend; 6 | emptyTensor(dims: ReadonlyArray, dataType?: DataType): Tensor; 7 | moveTensor(tensor: Tensor, option: Record): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/core/constants.ts: -------------------------------------------------------------------------------- 1 | export type BackendWithoutCPU = "webgl" | "wasm" | "webgpu"; 2 | export type Backend = "cpu" | BackendWithoutCPU; 3 | export const backendsWithoutCPU: BackendWithoutCPU[] = [ 4 | "wasm", 5 | "webgl", 6 | "webgpu", 7 | ]; 8 | export const backends: Backend[] = ["cpu", "wasm", "webgl", "webgpu"]; 9 | export type DataType = "float32" | "int32" | "bool"; 10 | export const DataArrayConstructor = { 11 | float32: Float32Array, 12 | int32: Int32Array, 13 | bool: Uint8Array, 14 | }; 15 | export type DataArrayTypes = Float32Array | Int32Array | Uint8Array; 16 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/core/operator.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { BackendContext } from "./backendContext"; 3 | import { Backend } from "./constants"; 4 | import { Tensor } from "./tensor"; 5 | 6 | export interface Operator { 7 | backend: Backend; 8 | run( 9 | context: BackendContext, 10 | inputs: Tensor[], 11 | nOutputs: number 12 | ): Promise; 13 | initialize(attribute: onnx.IAttributeProto[]): void; 14 | getTensorBackendRequirement( 15 | nInputs: number, 16 | nOutputs: number 17 | ): (Backend | null)[]; 18 | } 19 | 20 | export interface OperatorEntry { 21 | opType: string; 22 | // Inclusive 23 | opsetMin: number; 24 | // Exclusive, undefined means infinite 25 | opsetMax?: number; 26 | // Operator set domain. Not yet supported. 27 | domain?: string; 28 | backend: Backend; 29 | factory: () => Operator; 30 | } 31 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/core/runner.ts: -------------------------------------------------------------------------------- 1 | import { CPUTensor } from "../backend/cpu/cpuTensor"; 2 | 3 | export interface Runner { 4 | run(inputs?: CPUTensor[]): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/core/tensor.ts: -------------------------------------------------------------------------------- 1 | import { Backend, DataArrayTypes, DataType } from "./constants"; 2 | 3 | export interface Tensor { 4 | readonly dims: ReadonlyArray; 5 | readonly ndim: number; 6 | readonly length: number; 7 | readonly strides: ReadonlyArray; 8 | readonly dataType: DataType; 9 | readonly backend: Backend; 10 | 11 | getData(): Promise; 12 | setData(data: DataArrayTypes): Promise; 13 | dispose(): void; 14 | } 15 | -------------------------------------------------------------------------------- /src/descriptor_runner/interface/core/tensorLoader.ts: -------------------------------------------------------------------------------- 1 | import { CPUTensor } from "../backend/cpu/cpuTensor"; 2 | 3 | export interface TensorLoader { 4 | loadAll: ( 5 | progressCallback?: (loadedBytes: number) => unknown 6 | ) => Promise>; 7 | } 8 | -------------------------------------------------------------------------------- /src/descriptor_runner/math.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module webdnn/math 3 | * @preferred 4 | * 5 | * Module `WebDNN.Math` provides basic mathematics operations for pre/post-processing. 6 | */ 7 | /** Don't Remove This comment block */ 8 | 9 | export * from "./math/argsort"; 10 | export * from "./math/random"; 11 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/base/conv.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { Backend } from "../../interface/core/constants"; 3 | import { OperatorImpl } from "../operatorImpl"; 4 | import { getAttrInt, getAttrInts } from "../operatorUtil"; 5 | 6 | // Version 11 7 | export abstract class Conv extends OperatorImpl { 8 | dilations!: number[]; // [y, x] 9 | 10 | group!: number; 11 | 12 | kernelShape!: number[]; // [y, x] 13 | 14 | pads!: number[]; // [y_begin, x_begin, y_end, x_end] 15 | 16 | strides!: number[]; // [y, x] 17 | 18 | constructor(backend: Backend) { 19 | super(backend); 20 | } 21 | 22 | initialize(attribute: onnx.IAttributeProto[]): void { 23 | super.initialize(attribute); 24 | this.dilations = getAttrInts(attribute, "dilations", []); 25 | this.group = getAttrInt(attribute, "group", 1); 26 | this.kernelShape = getAttrInts(attribute, "kernel_shape", []); 27 | this.pads = getAttrInts(attribute, "pads", []); 28 | this.strides = getAttrInts(attribute, "strides", []); 29 | } 30 | 31 | protected calcShape( 32 | dimsX: ReadonlyArray, 33 | dimsW: ReadonlyArray 34 | ) { 35 | const batch = dimsX[0], 36 | dilations = this.dilations.length > 0 ? this.dilations : [1, 1], 37 | { group } = this, 38 | kernelShape = 39 | this.kernelShape.length > 0 ? this.kernelShape : [dimsW[2], dimsW[3]], 40 | pads = this.pads.length > 0 ? this.pads : [0, 0, 0, 0], 41 | strides = this.strides.length > 0 ? this.strides : [1, 1], 42 | inShape = [dimsX[2], dimsX[3]], 43 | outShape = [ 44 | Math.floor( 45 | (inShape[0] + 46 | pads[0] + 47 | pads[2] - 48 | dilations[0] * (kernelShape[0] - 1) - 49 | 1) / 50 | strides[0] 51 | ) + 1, 52 | Math.floor( 53 | (inShape[1] + 54 | pads[1] + 55 | pads[3] - 56 | dilations[1] * (kernelShape[1] - 1) - 57 | 1) / 58 | strides[1] 59 | ) + 1, 60 | ], 61 | chIn = dimsX[1], 62 | chInPerGroup = chIn / group, 63 | chOut = dimsW[0], 64 | chOutPerGroup = chOut / group; 65 | return { 66 | batch, 67 | dilations, 68 | group, 69 | kernelShape, 70 | pads, 71 | strides, 72 | inShape, 73 | outShape, 74 | chIn, 75 | chInPerGroup, 76 | chOut, 77 | chOutPerGroup, 78 | }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/base/flatten.ts: -------------------------------------------------------------------------------- 1 | import { OperatorImpl } from "../operatorImpl"; 2 | import { Tensor } from "../../interface/core/tensor"; 3 | import { arrayProd, getAttrInt } from "../operatorUtil"; 4 | import { onnx } from "onnx-proto"; 5 | 6 | export abstract class Flatten extends OperatorImpl { 7 | initialize(attribute: onnx.IAttributeProto[]): void { 8 | super.initialize(attribute); 9 | const axis = getAttrInt(attribute, "axis", 1); 10 | if (axis !== 1) { 11 | throw new Error(`Flatten: only axis === 1 is supported`); 12 | } 13 | } 14 | 15 | protected calcShape(input: Tensor): number[] { 16 | return [input.dims[0], arrayProd(input.dims.slice(1))]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/base/gemm.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { Backend } from "../../interface/core/constants"; 3 | import { OperatorImpl } from "../operatorImpl"; 4 | import { getAttrFloat, getAttrInt } from "../operatorUtil"; 5 | 6 | // Version 13 7 | export abstract class Gemm extends OperatorImpl { 8 | alpha!: number; 9 | 10 | beta!: number; 11 | 12 | transA!: number; 13 | 14 | transB!: number; 15 | 16 | constructor(backend: Backend) { 17 | super(backend); 18 | } 19 | 20 | initialize(attribute: onnx.IAttributeProto[]): void { 21 | super.initialize(attribute); 22 | this.alpha = getAttrFloat(attribute, "alpha", 1.0); 23 | this.beta = getAttrFloat(attribute, "beta", 1.0); 24 | this.transA = getAttrInt(attribute, "transA", 0); 25 | this.transB = getAttrInt(attribute, "transB", 0); 26 | } 27 | 28 | calcShape(dimsA: ReadonlyArray, dimsB: ReadonlyArray) { 29 | let k: number, 30 | kcheck: number, 31 | m: number, 32 | n: number, 33 | strideA: number[], 34 | strideB: number[]; 35 | if (dimsA.length !== 2 || dimsB.length !== 2) { 36 | throw new Error(); 37 | } 38 | if (this.transA) { 39 | k = dimsA[0]; 40 | m = dimsA[1]; 41 | strideA = [1, m]; 42 | } else { 43 | m = dimsA[0]; 44 | k = dimsA[1]; 45 | strideA = [k, 1]; 46 | } 47 | if (this.transB) { 48 | n = dimsB[0]; 49 | kcheck = dimsB[1]; 50 | strideB = [1, kcheck]; 51 | } else { 52 | kcheck = dimsB[0]; 53 | n = dimsB[1]; 54 | strideB = [n, 1]; 55 | } 56 | if (k !== kcheck) { 57 | throw new Error(); 58 | } 59 | 60 | return { m, n, k, strideA, strideB }; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/base/pad11.ts: -------------------------------------------------------------------------------- 1 | import { OperatorImpl } from "../operatorImpl"; 2 | import { Tensor } from "../../interface/core/tensor"; 3 | import { onnx } from "onnx-proto"; 4 | import { getAttrString } from "../operatorUtil"; 5 | import { Backend } from "../../interface/core/constants"; 6 | import { CPUTensor } from "../../interface/backend/cpu/cpuTensor"; 7 | 8 | type PadMode = "constant" | "reflect" | "edge"; 9 | 10 | /* 11 | * Opset 11 12 | * opset 2は互換性なし 13 | */ 14 | export abstract class Pad11 extends OperatorImpl { 15 | mode!: PadMode; 16 | 17 | initialize(attribute: onnx.IAttributeProto[]): void { 18 | super.initialize(attribute); 19 | this.mode = getAttrString(attribute, "mode", "constant") as PadMode; 20 | } 21 | 22 | getTensorBackendRequirement( 23 | nInputs: number, 24 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 25 | nOutputs: number 26 | ): (Backend | null)[] { 27 | if (nInputs === 2) { 28 | return [this.backend, "cpu"]; 29 | } else { 30 | return [this.backend, "cpu", "cpu"]; 31 | } 32 | } 33 | 34 | protected calcShape( 35 | input: Tensor, 36 | padTensor: CPUTensor 37 | ): { outputShape: number[]; pads: number[] } { 38 | const outputShape: number[] = []; 39 | const pads: number[] = Array.from(padTensor.data); 40 | for (let i = 0; i < input.ndim; i++) { 41 | outputShape.push(input.dims[i] + pads[i] + pads[i + input.ndim]); 42 | } 43 | return { outputShape, pads }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/base/reshape5.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { OperatorImpl } from "../operatorImpl"; 3 | import { CPUTensor } from "../../interface/backend/cpu/cpuTensor"; 4 | import { Tensor } from "../../interface/core/tensor"; 5 | import { getAttrInt } from "../operatorUtil"; 6 | 7 | // Opset under 5 takes shape as attribute. not compatible. 8 | export abstract class Reshape5 extends OperatorImpl { 9 | allowzero!: boolean; 10 | 11 | initialize(attribute: onnx.IAttributeProto[]): void { 12 | super.initialize(attribute); 13 | this.allowzero = getAttrInt(attribute, "allowzero", 0) !== 0; 14 | } 15 | 16 | protected calcShape(input: Tensor, shapeTensor: CPUTensor): number[] { 17 | const shapeInput = Array.from(shapeTensor.data); 18 | 19 | let computedShape: number[]; 20 | if (this.allowzero) { 21 | let explicitProd = 1, 22 | minusDim = -1; 23 | shapeInput.forEach((s, i) => { 24 | if (s > 0) { 25 | explicitProd *= s; 26 | } else if (s === -1) { 27 | if (minusDim >= 0) { 28 | throw new Error("Reshape: multiple -1 dimensions"); 29 | } 30 | minusDim = i; 31 | } 32 | }); 33 | if (minusDim >= 0 && explicitProd <= 0) { 34 | throw new Error(); 35 | } 36 | const minusDimValue = input.length / explicitProd; 37 | computedShape = shapeInput.map((s) => { 38 | if (s >= 0) { 39 | return s; 40 | } 41 | return minusDimValue; 42 | }); 43 | } else { 44 | let explicitProd = 1, 45 | minusDim = -1; 46 | shapeInput.forEach((s, i) => { 47 | if (s > 0) { 48 | explicitProd *= s; 49 | } else if (s === 0) { 50 | explicitProd *= input.dims[i]; 51 | } else { 52 | if (s !== -1) { 53 | throw new Error(); 54 | } 55 | if (minusDim >= 0) { 56 | throw new Error("Reshape: multiple -1 dimensions"); 57 | } 58 | minusDim = i; 59 | } 60 | }); 61 | if (minusDim >= 0 && explicitProd <= 0) { 62 | throw new Error(); 63 | } 64 | const minusDimValue = input.length / explicitProd; 65 | computedShape = shapeInput.map((s, i) => { 66 | if (s > 0) { 67 | return s; 68 | } else if (s === 0) { 69 | return input.dims[i]; 70 | } 71 | return minusDimValue; 72 | }); 73 | } 74 | return computedShape; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/base/squeeze.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { OperatorImpl } from "../operatorImpl"; 3 | import { getAttrInts } from "../operatorUtil"; 4 | import { Tensor } from "../../interface/core/tensor"; 5 | import { CPUTensor } from "../.."; 6 | 7 | abstract class Squeeze extends OperatorImpl { 8 | protected calcShapeBase( 9 | inputShape: ReadonlyArray, 10 | axes: ReadonlyArray 11 | ): number[] { 12 | if (axes.length === 0) { 13 | // remove all dimensions of 1 14 | return inputShape.filter((s) => s !== 1); 15 | } else { 16 | const nonNegativeAxes = axes.map((a) => 17 | a >= 0 ? a : a + inputShape.length 18 | ); 19 | return inputShape.filter((_, i) => !nonNegativeAxes.includes(i)); 20 | } 21 | } 22 | } 23 | 24 | export abstract class Squeeze1 extends Squeeze { 25 | axes!: number[]; 26 | 27 | initialize(attribute: onnx.IAttributeProto[]): void { 28 | super.initialize(attribute); 29 | this.axes = getAttrInts(attribute, "axes", []); 30 | } 31 | 32 | protected calcShape(input: Tensor): number[] { 33 | return this.calcShapeBase(input.dims, this.axes); 34 | } 35 | } 36 | 37 | export abstract class Squeeze13 extends Squeeze { 38 | protected calcShape(input: Tensor, axes?: CPUTensor): number[] { 39 | return this.calcShapeBase(input.dims, axes ? Array.from(axes.data) : []); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/base/transpose.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { OperatorImpl } from "../operatorImpl"; 3 | import { Tensor } from "../../interface/core/tensor"; 4 | import { getAttrInts } from "../operatorUtil"; 5 | 6 | // Opset 1 (13はdifferentiableがついただけ) 7 | export abstract class Transpose extends OperatorImpl { 8 | perm!: number[]; 9 | 10 | initialize(attribute: onnx.IAttributeProto[]): void { 11 | super.initialize(attribute); 12 | this.perm = getAttrInts(attribute, "perm", []); 13 | } 14 | 15 | protected calcShape(input: Tensor): { 16 | outShape: number[]; 17 | inStrides: number[]; 18 | } { 19 | // Default perm: [ndim-1, ndim-2, ..., 0] 20 | const perm = 21 | this.perm.length > 0 22 | ? this.perm 23 | : Array.from({ length: input.ndim }, (v, i) => input.ndim - 1 - i); 24 | if (perm.length !== input.ndim) { 25 | throw new Error(); 26 | } 27 | const outShape: number[] = new Array(input.ndim), 28 | inStrides: number[] = new Array(input.ndim); 29 | for (let outAxis = 0; outAxis < input.ndim; outAxis++) { 30 | const inAxis = perm[outAxis]; 31 | outShape[outAxis] = input.dims[inAxis]; 32 | inStrides[outAxis] = input.strides[inAxis]; 33 | } 34 | return { outShape, inStrides }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/base/unsqueeze.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { OperatorImpl } from "../operatorImpl"; 3 | import { getAttrInts } from "../operatorUtil"; 4 | import { Tensor } from "../../interface/core/tensor"; 5 | import { CPUTensor } from "../.."; 6 | 7 | abstract class Unsqueeze extends OperatorImpl { 8 | protected calcShapeBase( 9 | inputShape: ReadonlyArray, 10 | axes: ReadonlyArray 11 | ): number[] { 12 | const expandedNdim = inputShape.length + axes.length; 13 | const expandedShape: number[] = []; 14 | let srcIdx = 0; 15 | const nonNegativeAxes = axes.map((a) => (a >= 0 ? a : a + expandedNdim)); 16 | for (let d = 0; d < expandedNdim; d++) { 17 | if (nonNegativeAxes.includes(d)) { 18 | expandedShape.push(1); 19 | } else { 20 | expandedShape.push(inputShape[srcIdx++]); 21 | } 22 | } 23 | return expandedShape; 24 | } 25 | } 26 | 27 | export abstract class Unsqueeze1 extends Unsqueeze { 28 | axes!: number[]; 29 | 30 | initialize(attribute: onnx.IAttributeProto[]): void { 31 | super.initialize(attribute); 32 | this.axes = getAttrInts(attribute, "axes", []); 33 | } 34 | 35 | protected calcShape(input: Tensor): number[] { 36 | return this.calcShapeBase(input.dims, this.axes); 37 | } 38 | } 39 | 40 | export abstract class Unsqueeze13 extends Unsqueeze { 41 | protected calcShape(input: Tensor, axes: CPUTensor): number[] { 42 | return this.calcShapeBase(input.dims, Array.from(axes.data)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/autogen/.gitignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/custom/.gitignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/averagepool.ts: -------------------------------------------------------------------------------- 1 | import { averagepool } from "../../rawcomputation/averagepool"; 2 | import { AveragePool } from "../../../base/averagepool"; 3 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 4 | import { Tensor } from "../../../../interface/core/tensor"; 5 | import { OperatorEntry } from "../../../../interface/core/operator"; 6 | 7 | // Version 1, 7, 10, 11+ 8 | class CpuAveragePool extends AveragePool { 9 | constructor() { 10 | super("cpu"); 11 | } 12 | 13 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 14 | context.assertsCPUTensorArray(inputs); 15 | const inputX = inputs[0]; 16 | // TODO: 2D以外対応 17 | if (inputX.ndim !== 4) { 18 | throw new Error("AveragePool other than 2D is not yet supported"); 19 | } 20 | const { batch, kernelShape, pads, strides, inShape, outShape, ch } = 21 | this.calcShape(inputX.dims), 22 | outputData = new Float32Array(batch * outShape[0] * outShape[1] * ch); 23 | averagepool( 24 | inputX.data as Float32Array, 25 | outputData, 26 | this.countIncludePad, 27 | batch, 28 | kernelShape, 29 | pads, 30 | strides, 31 | inShape, 32 | outShape, 33 | ch 34 | ); 35 | const output = context.emptyTensor( 36 | [batch, ch, outShape[0], outShape[1]], 37 | "float32", 38 | outputData 39 | ); 40 | return [output]; 41 | } 42 | } 43 | 44 | export function getOpEntries(): OperatorEntry[] { 45 | return [ 46 | { 47 | opType: "AveragePool", 48 | backend: "cpu", 49 | opsetMin: 1, 50 | factory: () => new CpuAveragePool(), 51 | }, 52 | ]; 53 | } 54 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/cast.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { 3 | DataArrayConstructor, 4 | DataType, 5 | } from "../../../../interface/core/constants"; 6 | import { OperatorImpl } from "../../../operatorImpl"; 7 | import { getAttrInt } from "../../../operatorUtil"; 8 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 9 | import { Tensor } from "../../../../interface/core/tensor"; 10 | import { OperatorEntry } from "../../../../interface/core/operator"; 11 | 12 | // Opset 6+ (opset 1 requires "to" is string) 13 | class Cast extends OperatorImpl { 14 | to!: onnx.TensorProto.DataType; 15 | 16 | constructor() { 17 | super("cpu"); 18 | } 19 | 20 | initialize(attribute: onnx.IAttributeProto[]): void { 21 | super.initialize(attribute); 22 | this.to = getAttrInt(attribute, "to", onnx.TensorProto.DataType.FLOAT); 23 | } 24 | 25 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 26 | // TODO: コピー回避 27 | context.assertsCPUTensorArray(inputs); 28 | const input = inputs[0]; 29 | let outputDataType: DataType; 30 | switch (this.to) { 31 | case onnx.TensorProto.DataType.FLOAT: 32 | outputDataType = "float32"; 33 | break; 34 | case onnx.TensorProto.DataType.UINT8: // TODO: clip value 35 | case onnx.TensorProto.DataType.INT32: 36 | case onnx.TensorProto.DataType.INT64: 37 | outputDataType = "int32"; 38 | break; 39 | default: 40 | throw new Error( 41 | `Cast: converting to DataType ${this.to} is not yet supported` 42 | ); 43 | } 44 | const newData = new DataArrayConstructor[outputDataType](input.data.length); 45 | newData.set(input.data); 46 | const output = context.emptyTensor(input.dims, outputDataType, newData); 47 | return [output]; 48 | } 49 | } 50 | 51 | export function getOpEntries(): OperatorEntry[] { 52 | return [ 53 | { opType: "Cast", backend: "cpu", opsetMin: 6, factory: () => new Cast() }, 54 | ]; 55 | } 56 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/clip.ts: -------------------------------------------------------------------------------- 1 | import { DataArrayConstructor } from "../../../../interface/core/constants"; 2 | import { OperatorImpl } from "../../../operatorImpl"; 3 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 4 | import { Tensor } from "../../../../interface/core/tensor"; 5 | import { OperatorEntry } from "../../../../interface/core/operator"; 6 | import { onnx } from "onnx-proto"; 7 | import { getAttrFloat } from "../../../operatorUtil"; 8 | 9 | class CPUClip extends OperatorImpl { 10 | clipMax!: number; 11 | clipMin!: number; 12 | 13 | constructor() { 14 | super("cpu"); 15 | } 16 | 17 | initialize(attribute: onnx.IAttributeProto[]): void { 18 | super.initialize(attribute); 19 | this.clipMax = getAttrFloat(attribute, "max", 65536); 20 | this.clipMin = getAttrFloat(attribute, "min", -65536); 21 | } 22 | 23 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 24 | context.assertsCPUTensorArray(inputs); 25 | const input = inputs[0]; 26 | if (!["float32"].includes(input.dataType)) { 27 | throw new Error(`Unary: DataType ${input.dataType} not supported`); 28 | } 29 | const newData = new DataArrayConstructor[input.dataType](input.data.length); 30 | const { clipMax, clipMin } = this; 31 | for (let i = 0; i < newData.length; i++) { 32 | newData[i] = Math.min(clipMax, Math.max(input.data[i], clipMin)); 33 | } 34 | const output = context.emptyTensor(input.dims, input.dataType, newData); 35 | return [output]; 36 | } 37 | } 38 | 39 | export function getOpEntries(): OperatorEntry[] { 40 | return [ 41 | { 42 | opType: "Clip", 43 | backend: "cpu", 44 | opsetMin: 1, 45 | opsetMax: 11, 46 | factory: () => new CPUClip(), 47 | }, 48 | ]; 49 | } 50 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/constant.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { DataArrayTypes, DataType } from "../../../../interface/core/constants"; 3 | import { OperatorImpl } from "../../../operatorImpl"; 4 | import { getAttrTensor } from "../../../operatorUtil"; 5 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 6 | import { Tensor } from "../../../../interface/core/tensor"; 7 | import { OperatorEntry } from "../../../../interface/core/operator"; 8 | 9 | class Constant extends OperatorImpl { 10 | constant!: { 11 | data: DataArrayTypes; 12 | dataType: DataType; 13 | dims: number[]; 14 | }; 15 | 16 | constructor() { 17 | super("cpu"); 18 | } 19 | 20 | initialize(attribute: onnx.IAttributeProto[]): void { 21 | super.initialize(attribute); 22 | const constant = getAttrTensor(attribute, "value"); 23 | if (!constant) { 24 | // Sparse_value, value_float etc in opset 12 is not yet supported 25 | throw new Error("value not exist in Constant"); 26 | } 27 | this.constant = constant; 28 | } 29 | 30 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 31 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 32 | const output = context.emptyTensor( 33 | this.constant.dims, 34 | this.constant.dataType 35 | ); 36 | output.data.set(this.constant.data); 37 | return [output]; 38 | } 39 | } 40 | 41 | export function getOpEntries(): OperatorEntry[] { 42 | return [ 43 | { 44 | opType: "Constant", 45 | backend: "cpu", 46 | opsetMin: 1, 47 | factory: () => new Constant(), 48 | }, 49 | ]; 50 | } 51 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/constantofshape.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { DataArrayTypes, DataType } from "../../../../interface/core/constants"; 3 | import { OperatorImpl } from "../../../operatorImpl"; 4 | import { getAttrTensor } from "../../../operatorUtil"; 5 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 6 | import { Tensor } from "../../../../interface/core/tensor"; 7 | import { OperatorEntry } from "../../../../interface/core/operator"; 8 | 9 | class ConstantOfShape extends OperatorImpl { 10 | constant!: { 11 | data: DataArrayTypes; 12 | dataType: DataType; 13 | dims: number[]; 14 | }; 15 | 16 | constructor() { 17 | super("cpu"); 18 | } 19 | 20 | initialize(attribute: onnx.IAttributeProto[]): void { 21 | super.initialize(attribute); 22 | const constant = getAttrTensor(attribute, "value"); 23 | if (!constant) { 24 | throw new Error("value not exist in ConstantOfShape"); 25 | } 26 | this.constant = constant; 27 | } 28 | 29 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 30 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 31 | context.assertsCPUTensorArray(inputs); 32 | const input = Array.from(inputs[0].data); 33 | const output = context.emptyTensor(input, this.constant.dataType); 34 | output.data.fill(this.constant.data[0]); 35 | return [output]; 36 | } 37 | } 38 | 39 | export function getOpEntries(): OperatorEntry[] { 40 | return [ 41 | { 42 | opType: "ConstantOfShape", 43 | backend: "cpu", 44 | opsetMin: 9, 45 | factory: () => new ConstantOfShape(), 46 | }, 47 | ]; 48 | } 49 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/flatten.ts: -------------------------------------------------------------------------------- 1 | import { Flatten } from "../../../base/flatten"; 2 | import { Tensor } from "../../../../interface/core/tensor"; 3 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 4 | import { OperatorEntry } from "../../../../interface/core/operator"; 5 | 6 | class CPUFlatten extends Flatten { 7 | constructor() { 8 | super("cpu"); 9 | } 10 | 11 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 12 | context.assertsCPUTensorArray(inputs); 13 | const input = inputs[0], 14 | output = context.emptyTensor( 15 | this.calcShape(input), 16 | input.dataType, 17 | input.data 18 | ); 19 | return [output]; 20 | } 21 | } 22 | 23 | export function getOpEntries(): OperatorEntry[] { 24 | return [ 25 | { 26 | opType: "Flatten", 27 | backend: "cpu", 28 | opsetMin: 1, 29 | factory: () => new CPUFlatten(), 30 | }, 31 | ]; 32 | } 33 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/gather.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { OperatorImpl } from "../../../operatorImpl"; 3 | import { getAttrInt } from "../../../operatorUtil"; 4 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 5 | import { Tensor } from "../../../../interface/core/tensor"; 6 | import { OperatorEntry } from "../../../../interface/core/operator"; 7 | 8 | class Gather extends OperatorImpl { 9 | axis!: number; 10 | 11 | constructor() { 12 | super("cpu"); 13 | } 14 | 15 | initialize(attribute: onnx.IAttributeProto[]): void { 16 | super.initialize(attribute); 17 | this.axis = getAttrInt(attribute, "axis", 0); 18 | } 19 | 20 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 21 | context.assertsCPUTensorArray(inputs); 22 | const data = inputs[0], 23 | indices = inputs[1], 24 | { axis } = this; 25 | if (!(data.ndim === 1 && indices.ndim === 0 && axis === 0)) { 26 | throw new Error( 27 | "Gather: currently supports data.ndim === 1 && indices.ndim === 0 && axis === 0" 28 | ); 29 | } 30 | const output = context.emptyTensor([], data.dataType); 31 | output.data[0] = data.data[indices.data[0]]; 32 | return [output]; 33 | } 34 | } 35 | 36 | export function getOpEntries(): OperatorEntry[] { 37 | return [ 38 | { 39 | opType: "Gather", 40 | backend: "cpu", 41 | opsetMin: 1, 42 | factory: () => new Gather(), 43 | }, 44 | ]; 45 | } 46 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/gemm.ts: -------------------------------------------------------------------------------- 1 | import { broadcastUni } from "../../../operatorUtil"; 2 | import { Gemm } from "../../../base/gemm"; 3 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 4 | import { Tensor } from "../../../../interface/core/tensor"; 5 | import { OperatorEntry } from "../../../../interface/core/operator"; 6 | 7 | // Version 13 8 | class CpuGemm extends Gemm { 9 | alpha!: number; 10 | 11 | beta!: number; 12 | 13 | transA!: number; 14 | 15 | transB!: number; 16 | 17 | constructor() { 18 | super("cpu"); 19 | } 20 | 21 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 22 | context.assertsCPUTensorArray(inputs); 23 | const inputA = inputs[0], 24 | inputB = inputs[1], 25 | inputC = inputs[2], 26 | { 27 | m, 28 | n, 29 | k, 30 | strideA: [strideA0, strideA1], 31 | strideB: [strideB0, strideB1], 32 | } = this.calcShape(inputA.dims, inputB.dims), 33 | newData = new Float32Array(m * n), 34 | dA = inputA.data, 35 | dB = inputB.data, 36 | { alpha } = this; 37 | for (let i = 0; i < m; i++) { 38 | for (let j = 0; j < n; j++) { 39 | let sum = 0; 40 | for (let x = 0; x < k; x++) { 41 | sum += 42 | dA[i * strideA0 + x * strideA1] * dB[x * strideB0 + j * strideB1]; 43 | } 44 | sum *= alpha; 45 | newData[i * n + j] = sum; 46 | } 47 | } 48 | 49 | if (inputC) { 50 | const [strideC0, strideC1] = broadcastUni([m, n], inputC.dims), 51 | dC = inputC.data, 52 | { beta } = this; 53 | for (let i = 0; i < m; i++) { 54 | for (let j = 0; j < n; j++) { 55 | newData[i * n + j] += dC[i * strideC0 + j * strideC1] * beta; 56 | } 57 | } 58 | } 59 | const output = context.emptyTensor([m, n], "float32", newData); 60 | return [output]; 61 | } 62 | } 63 | 64 | export function getOpEntries(): OperatorEntry[] { 65 | return [ 66 | { 67 | opType: "Gemm", 68 | backend: "cpu", 69 | opsetMin: 1, 70 | factory: () => new CpuGemm(), 71 | }, 72 | ]; 73 | } 74 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/globalaveragepool.ts: -------------------------------------------------------------------------------- 1 | import { OperatorImpl } from "../../../operatorImpl"; 2 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 3 | import { Tensor } from "../../../../interface/core/tensor"; 4 | import { averagepool } from "../../rawcomputation/averagepool"; 5 | import { OperatorEntry } from "../../../../interface/core/operator"; 6 | 7 | export class CpuGlobalAveragePool extends OperatorImpl { 8 | constructor() { 9 | super("cpu"); 10 | } 11 | 12 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 13 | context.assertsCPUTensorArray(inputs); 14 | const inputX = inputs[0]; 15 | // TODO: 2D以外対応 16 | if (inputX.ndim !== 4) { 17 | throw new Error("MaxPool other than 2D is not yet supported"); 18 | } 19 | const batch = inputX.dims[0], 20 | ch = inputX.dims[1], 21 | inShape = [inputX.dims[2], inputX.dims[3]], 22 | outputData = new Float32Array(batch * ch); 23 | averagepool( 24 | inputX.data as Float32Array, 25 | outputData, 26 | true, // わずかに計算量が減る 27 | batch, 28 | inShape, 29 | [0, 0, 0, 0], 30 | [1, 1], 31 | inShape, 32 | [1, 1], 33 | ch 34 | ); 35 | const output = context.emptyTensor( 36 | [batch, ch, 1, 1], 37 | "float32", 38 | outputData 39 | ); 40 | return [output]; 41 | } 42 | } 43 | 44 | export function getOpEntries(): OperatorEntry[] { 45 | return [ 46 | { 47 | opType: "GlobalAveragePool", 48 | backend: "cpu", 49 | opsetMin: 1, 50 | factory: () => new CpuGlobalAveragePool(), 51 | }, 52 | ]; 53 | } 54 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/instancenormalization.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { OperatorImpl } from "../../../operatorImpl"; 3 | import { getAttrInt } from "../../../operatorUtil"; 4 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 5 | import { Tensor } from "../../../../interface/core/tensor"; 6 | import { OperatorEntry } from "../../../../interface/core/operator"; 7 | import { arrayProd } from "../../../../util"; 8 | 9 | class InstanceNormalization extends OperatorImpl { 10 | epsilon!: number; 11 | 12 | constructor() { 13 | super("cpu"); 14 | } 15 | 16 | initialize(attribute: onnx.IAttributeProto[]): void { 17 | super.initialize(attribute); 18 | this.epsilon = getAttrInt(attribute, "epsilon", 1e-5); 19 | } 20 | 21 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 22 | context.assertsCPUTensorArray(inputs); 23 | const [input, scale, bias] = inputs; 24 | const reductionLength = arrayProd(input.dims.slice(2)), 25 | output = context.emptyTensor(input.dims, input.dataType), 26 | dI = input.data, 27 | dO = output.data, 28 | dS = scale.data, 29 | dB = bias.data; 30 | const [dimBatch, dimCh] = input.dims; 31 | const [strideBatch, strideCh] = input.strides; 32 | for (let batch = 0; batch < dimBatch; batch++) { 33 | for (let ch = 0; ch < dimCh; ch++) { 34 | const ofs = batch * strideBatch + ch * strideCh; 35 | let sum = 0.0; 36 | let sqsum = 0.0; 37 | for (let r = 0; r < reductionLength; r++) { 38 | const v = dI[ofs + r]; 39 | sum += v; 40 | sqsum += v * v; 41 | } 42 | const mean = sum / reductionLength; 43 | const variance = sqsum / reductionLength - mean * mean; 44 | const invstd = 1 / Math.sqrt(variance + this.epsilon); 45 | const chscale = dS[ch] * invstd; 46 | const chbias = -mean * chscale + dB[ch]; 47 | for (let r = 0; r < reductionLength; r++) { 48 | dO[ofs + r] = dI[ofs + r] * chscale + chbias; 49 | } 50 | } 51 | } 52 | return [output]; 53 | } 54 | } 55 | 56 | export function getOpEntries(): OperatorEntry[] { 57 | return [ 58 | { 59 | opType: "InstanceNormalization", 60 | backend: "cpu", 61 | opsetMin: 1, 62 | factory: () => new InstanceNormalization(), 63 | }, 64 | ]; 65 | } 66 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/reshape5.ts: -------------------------------------------------------------------------------- 1 | import { Backend } from "../../../../interface/core/constants"; 2 | import { Reshape5 } from "../../../base/reshape5"; 3 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 4 | import { Tensor } from "../../../../interface/core/tensor"; 5 | import { OperatorEntry } from "../../../../interface/core/operator"; 6 | 7 | class CPUReshape5 extends Reshape5 { 8 | constructor() { 9 | super("cpu"); 10 | } 11 | 12 | getTensorBackendRequirement( 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | nInputs: number, 15 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 16 | nOutputs: number 17 | ): (Backend | null)[] { 18 | return ["cpu", "cpu"]; 19 | } 20 | 21 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 22 | context.assertsCPUTensorArray(inputs); 23 | const input = inputs[0], 24 | shapeTensor = inputs[1], 25 | computedShape = this.calcShape(input, shapeTensor), 26 | output = context.emptyTensor(computedShape, input.dataType, input.data); 27 | return [output]; 28 | } 29 | } 30 | 31 | export function getOpEntries(): OperatorEntry[] { 32 | return [ 33 | { 34 | opType: "Reshape", 35 | backend: "cpu", 36 | opsetMin: 5, 37 | factory: () => new CPUReshape5(), 38 | }, 39 | ]; 40 | } 41 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/shape.ts: -------------------------------------------------------------------------------- 1 | import { Backend } from "../../../../interface/core/constants"; 2 | import { OperatorImpl } from "../../../operatorImpl"; 3 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 4 | import { Tensor } from "../../../../interface/core/tensor"; 5 | import { OperatorEntry } from "../../../../interface/core/operator"; 6 | 7 | class Shape extends OperatorImpl { 8 | constructor() { 9 | super("cpu"); 10 | } 11 | 12 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 13 | // メタデータしか使わないので、どのバックエンドに存在してもよい 14 | const input = inputs[0], 15 | shapeData = new Int32Array(input.dims), 16 | output = context.emptyTensor([shapeData.length], "int32", shapeData); 17 | return [output]; 18 | } 19 | 20 | getTensorBackendRequirement( 21 | nInputs: number, 22 | nOutputs: number 23 | ): (Backend | null)[] { 24 | // メタデータしか使わないので、どのバックエンドに存在してもよい 25 | return [null]; 26 | } 27 | } 28 | 29 | export function getOpEntries(): OperatorEntry[] { 30 | return [ 31 | { 32 | opType: "Shape", 33 | backend: "cpu", 34 | opsetMin: 1, 35 | factory: () => new Shape(), 36 | }, 37 | ]; 38 | } 39 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/softmax.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { OperatorImpl } from "../../../operatorImpl"; 3 | import { getAttrInt } from "../../../operatorUtil"; 4 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 5 | import { Tensor } from "../../../../interface/core/tensor"; 6 | import { OperatorEntry } from "../../../../interface/core/operator"; 7 | 8 | class Softmax extends OperatorImpl { 9 | axis!: number; 10 | 11 | constructor() { 12 | super("cpu"); 13 | } 14 | 15 | initialize(attribute: onnx.IAttributeProto[]): void { 16 | super.initialize(attribute); 17 | // TODO: support axis, whose default is different between opsets 18 | this.axis = getAttrInt(attribute, "axis", -1); 19 | } 20 | 21 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 22 | context.assertsCPUTensorArray(inputs); 23 | const input = inputs[0]; 24 | let { axis } = this; 25 | if (axis < 0) { 26 | axis += input.ndim; 27 | } 28 | if (axis !== input.ndim - 1) { 29 | throw new Error( 30 | "Softmax: currently only reducing final axis is supported" 31 | ); 32 | } 33 | // 最終軸のreductionに特化した実装 34 | const reductionLength = input.dims[axis], 35 | outerLength = input.length / reductionLength, 36 | output = context.emptyTensor(input.dims, input.dataType), 37 | dI = input.data, 38 | dO = output.data; 39 | for (let outer = 0; outer < outerLength; outer++) { 40 | let max = -Infinity; 41 | for (let r = 0; r < reductionLength; r++) { 42 | const v = dI[outer * reductionLength + r]; 43 | if (v > max) { 44 | max = v; 45 | } 46 | } 47 | let expsum = 0; 48 | for (let r = 0; r < reductionLength; r++) { 49 | const v = dI[outer * reductionLength + r], 50 | exp = Math.exp(v - max); 51 | dO[outer * reductionLength + r] = exp; 52 | expsum += exp; 53 | } 54 | for (let r = 0; r < reductionLength; r++) { 55 | dO[outer * reductionLength + r] /= expsum; 56 | } 57 | } 58 | return [output]; 59 | } 60 | } 61 | 62 | export function getOpEntries(): OperatorEntry[] { 63 | return [ 64 | { 65 | opType: "Softmax", 66 | backend: "cpu", 67 | opsetMin: 1, 68 | factory: () => new Softmax(), 69 | }, 70 | ]; 71 | } 72 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/squeeze.ts: -------------------------------------------------------------------------------- 1 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 2 | import { Tensor } from "../../../../interface/core/tensor"; 3 | import { OperatorEntry } from "../../../../interface/core/operator"; 4 | import { Squeeze1, Squeeze13 } from "../../../base/squeeze"; 5 | 6 | export class CPUSqueeze1 extends Squeeze1 { 7 | constructor() { 8 | super("cpu"); 9 | } 10 | 11 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 12 | context.assertsCPUTensorArray(inputs); 13 | const input = inputs[0], 14 | newShape = this.calcShape(input), 15 | output = context.emptyTensor(newShape, input.dataType, input.data); 16 | return [output]; 17 | } 18 | } 19 | 20 | export class CPUSqueeze13 extends Squeeze13 { 21 | constructor() { 22 | super("cpu"); 23 | } 24 | 25 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 26 | context.assertsCPUTensorArray(inputs); 27 | const input = inputs[0], 28 | axes = inputs[1], 29 | newShape = this.calcShape(input, axes), 30 | output = context.emptyTensor(newShape, input.dataType, input.data); 31 | return [output]; 32 | } 33 | } 34 | 35 | export function getOpEntries(): OperatorEntry[] { 36 | return [ 37 | { 38 | opType: "Squeeze", 39 | backend: "cpu", 40 | opsetMin: 13, 41 | factory: () => new CPUSqueeze13(), 42 | }, 43 | { 44 | opType: "Squeeze", 45 | backend: "cpu", 46 | opsetMin: 1, 47 | opsetMax: 13, 48 | factory: () => new CPUSqueeze1(), 49 | }, 50 | ]; 51 | } 52 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/operators/standard/unsqueeze.ts: -------------------------------------------------------------------------------- 1 | import { Unsqueeze1, Unsqueeze13 } from "../../../base/unsqueeze"; 2 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 3 | import { Tensor } from "../../../../interface/core/tensor"; 4 | import { OperatorEntry } from "../../../../interface/core/operator"; 5 | 6 | export class CPUUnsqueeze1 extends Unsqueeze1 { 7 | constructor() { 8 | super("cpu"); 9 | } 10 | 11 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 12 | context.assertsCPUTensorArray(inputs); 13 | const input = inputs[0], 14 | newShape = this.calcShape(input), 15 | output = context.emptyTensor(newShape, input.dataType, input.data); 16 | return [output]; 17 | } 18 | } 19 | 20 | export class CPUUnsqueeze13 extends Unsqueeze13 { 21 | constructor() { 22 | super("cpu"); 23 | } 24 | 25 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 26 | context.assertsCPUTensorArray(inputs); 27 | const input = inputs[0], 28 | axes = inputs[1], 29 | newShape = this.calcShape(input, axes), 30 | output = context.emptyTensor(newShape, input.dataType, input.data); 31 | return [output]; 32 | } 33 | } 34 | 35 | export function getOpEntries(): OperatorEntry[] { 36 | return [ 37 | { 38 | opType: "Unsqueeze", 39 | backend: "cpu", 40 | opsetMin: 13, 41 | factory: () => new CPUUnsqueeze13(), 42 | }, 43 | { 44 | opType: "Unsqueeze", 45 | backend: "cpu", 46 | opsetMin: 1, 47 | opsetMax: 13, 48 | factory: () => new CPUUnsqueeze1(), 49 | }, 50 | ]; 51 | } 52 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/cpu/rawcomputation/averagepool.ts: -------------------------------------------------------------------------------- 1 | export function averagepool( 2 | dX: Float32Array, 3 | dI: Float32Array, 4 | countIncludePad: boolean, 5 | batch: number, 6 | kernelShape: number[], 7 | pads: number[], 8 | strides: number[], 9 | inShape: number[], 10 | outShape: number[], 11 | ch: number 12 | ): void { 13 | let idx = 0; 14 | for (let b = 0; b < batch; b++) { 15 | for (let c = 0; c < ch; c++) { 16 | for (let oy = 0; oy < outShape[0]; oy++) { 17 | for (let ox = 0; ox < outShape[1]; ox++) { 18 | let ctr = 0, 19 | sum = 0; 20 | for (let ky = 0; ky < kernelShape[0]; ky++) { 21 | for (let kx = 0; kx < kernelShape[1]; kx++) { 22 | const iny = oy * strides[0] - pads[0] + ky, 23 | inx = ox * strides[1] - pads[1] + kx; 24 | if ( 25 | iny >= 0 && 26 | iny < inShape[0] && 27 | inx >= 0 && 28 | inx < inShape[1] 29 | ) { 30 | const xidx = 31 | ((b * ch + c) * inShape[0] + iny) * inShape[1] + inx, 32 | v = dX[xidx]; 33 | sum += v; 34 | ctr++; 35 | } 36 | } 37 | } 38 | 39 | dI[idx++] = countIncludePad 40 | ? sum / (kernelShape[0] * kernelShape[1]) 41 | : sum / ctr; 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mil-tokyo/webdnn/d15df8f60544ff948c5e529d6eb76707b7177ff4/src/descriptor_runner/operators/index.ts -------------------------------------------------------------------------------- /src/descriptor_runner/operators/operatorImpl.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { Backend } from "../interface/core/constants"; 3 | import { TensorImpl } from "../core/tensorImpl"; 4 | import { Operator } from "../interface/core/operator"; 5 | import { BackendContext } from "../interface/core/backendContext"; 6 | 7 | export abstract class OperatorImpl implements Operator { 8 | constructor(public backend: Backend) {} 9 | 10 | abstract run( 11 | context: BackendContext, 12 | inputs: TensorImpl[], 13 | nOutputs: number 14 | ): Promise; 15 | 16 | // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function 17 | initialize(attribute: onnx.IAttributeProto[]): void {} 18 | 19 | getTensorBackendRequirement( 20 | nInputs: number, 21 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 22 | nOutputs: number 23 | ): (Backend | null)[] { 24 | const backends: Backend[] = []; 25 | for (let i = 0; i < nInputs; i++) { 26 | backends.push(this.backend); 27 | } 28 | return backends; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/wasm/opEntriesStandard.ts: -------------------------------------------------------------------------------- 1 | // auto-generated by scripts/make_operator_entries.py 2 | import { OperatorEntry } from "../../interface/core/operator"; 3 | 4 | import { getOpEntries as getOpEntriesoperatorsstandardbinary7 } from "./operators/standard/binary7"; 5 | import { getOpEntries as getOpEntriesoperatorsstandarddynamicunary } from "./operators/standard/dynamicunary"; 6 | import { getOpEntries as getOpEntriesoperatorsstandardflatten } from "./operators/standard/flatten"; 7 | import { getOpEntries as getOpEntriesoperatorsstandardgemm } from "./operators/standard/gemm"; 8 | import { getOpEntries as getOpEntriesoperatorsstandardreshape5 } from "./operators/standard/reshape5"; 9 | import { getOpEntries as getOpEntriesoperatorsstandardsqueeze } from "./operators/standard/squeeze"; 10 | import { getOpEntries as getOpEntriesoperatorsstandardunary } from "./operators/standard/unary"; 11 | import { getOpEntries as getOpEntriesoperatorsstandardunsqueeze } from "./operators/standard/unsqueeze"; 12 | 13 | export function getOpEntries(): OperatorEntry[] { 14 | const entries: OperatorEntry[] = []; 15 | entries.push(...getOpEntriesoperatorsstandardbinary7()); 16 | entries.push(...getOpEntriesoperatorsstandarddynamicunary()); 17 | entries.push(...getOpEntriesoperatorsstandardflatten()); 18 | entries.push(...getOpEntriesoperatorsstandardgemm()); 19 | entries.push(...getOpEntriesoperatorsstandardreshape5()); 20 | entries.push(...getOpEntriesoperatorsstandardsqueeze()); 21 | entries.push(...getOpEntriesoperatorsstandardunary()); 22 | entries.push(...getOpEntriesoperatorsstandardunsqueeze()); 23 | return entries; 24 | } 25 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/wasm/operators/autogen/.gitignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/wasm/operators/custom/.gitignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/wasm/operators/standard/binary7.ts: -------------------------------------------------------------------------------- 1 | import { OperatorImpl } from "../../../operatorImpl"; 2 | import { 3 | WasmKernelArgument, 4 | WasmKernelArgumentInt32, 5 | WebDNNWasmContext, 6 | } from "../../../../interface/backend/wasm/wasmContext"; 7 | import { Tensor } from "../../../../interface/core/tensor"; 8 | import { OperatorEntry } from "../../../../interface/core/operator"; 9 | import { broadcastMulti } from "../../../operatorUtil"; 10 | 11 | class WasmBinary7 extends OperatorImpl { 12 | constructor(private kernelName: string) { 13 | super("wasm"); 14 | } 15 | 16 | async run(context: WebDNNWasmContext, inputs: Tensor[]): Promise { 17 | context.assertsWasmTensorArray(inputs); 18 | const inputA = inputs[0], 19 | inputB = inputs[1]; 20 | if (inputA.dataType !== "float32" || inputB.dataType !== "float32") { 21 | throw new Error("Only float32 is supported"); 22 | } 23 | 24 | const { dims: outShape, allStrides: inAllStrides } = broadcastMulti([ 25 | inputA.dims, 26 | inputB.dims, 27 | ]); 28 | const output = context.emptyTensor(outShape, inputA.dataType); 29 | const args: WasmKernelArgument[] = [ 30 | { type: "tensor", value: inputA }, 31 | { type: "tensor", value: inputB }, 32 | { type: "tensor", value: output }, 33 | ...outShape.map( 34 | (v) => ({ type: "int32", value: v } as WasmKernelArgumentInt32) 35 | ), 36 | ...inAllStrides[0].map( 37 | (v) => ({ type: "int32", value: v } as WasmKernelArgumentInt32) 38 | ), 39 | ...inAllStrides[1].map( 40 | (v) => ({ type: "int32", value: v } as WasmKernelArgumentInt32) 41 | ), 42 | ]; 43 | context.runKernel(`kernel_${this.kernelName}_d${outShape.length}`, args); 44 | return [output]; 45 | } 46 | } 47 | 48 | export function getOpEntries(): OperatorEntry[] { 49 | return [ 50 | { 51 | opType: "Add", 52 | backend: "wasm", 53 | opsetMin: 7, 54 | factory: () => new WasmBinary7("add"), 55 | }, 56 | { 57 | opType: "Sub", 58 | backend: "wasm", 59 | opsetMin: 7, 60 | factory: () => new WasmBinary7("sub"), 61 | }, 62 | { 63 | opType: "Mul", 64 | backend: "wasm", 65 | opsetMin: 7, 66 | factory: () => new WasmBinary7("mul"), 67 | }, 68 | { 69 | opType: "Div", 70 | backend: "wasm", 71 | opsetMin: 7, 72 | factory: () => new WasmBinary7("div"), 73 | }, 74 | { 75 | opType: "Pow", 76 | backend: "wasm", 77 | opsetMin: 7, 78 | factory: () => new WasmBinary7("pow"), 79 | }, 80 | ]; 81 | } 82 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/wasm/operators/standard/dynamicunary.ts: -------------------------------------------------------------------------------- 1 | import { OperatorImpl } from "../../../operatorImpl"; 2 | import { 3 | WasmKernelArgument, 4 | WebDNNWasmContext, 5 | } from "../../../../interface/backend/wasm/wasmContext"; 6 | import { Tensor } from "../../../../interface/core/tensor"; 7 | import { OperatorEntry } from "../../../../interface/core/operator"; 8 | import { onnx } from "onnx-proto"; 9 | import { getAttrFloat } from "../../../operatorUtil"; 10 | 11 | abstract class WasmDynamicUnary extends OperatorImpl { 12 | constructor(private kernelName: string) { 13 | super("wasm"); 14 | } 15 | 16 | protected abstract getKernelArgs(): WasmKernelArgument[]; 17 | 18 | async run(context: WebDNNWasmContext, inputs: Tensor[]): Promise { 19 | context.assertsWasmTensorArray(inputs); 20 | const input = inputs[0]; 21 | if (input.dataType !== "float32") { 22 | throw new Error("Only float32 is supported"); 23 | } 24 | const output = context.emptyTensor(input.dims, input.dataType); 25 | context.runKernel(this.kernelName, [ 26 | { type: "tensor", value: input }, 27 | { type: "tensor", value: output }, 28 | { type: "int32", value: input.length }, 29 | ...this.getKernelArgs(), 30 | ]); 31 | return [output]; 32 | } 33 | } 34 | 35 | class WasmLeakyRelu extends WasmDynamicUnary { 36 | alpha!: number; 37 | 38 | initialize(attribute: onnx.IAttributeProto[]): void { 39 | super.initialize(attribute); 40 | this.alpha = getAttrFloat(attribute, "alpha", 0.01); 41 | } 42 | 43 | protected getKernelArgs(): WasmKernelArgument[] { 44 | return [{ type: "float32", value: this.alpha }]; 45 | } 46 | } 47 | 48 | export function getOpEntries(): OperatorEntry[] { 49 | return [ 50 | { 51 | opType: "LeakyRelu", 52 | backend: "wasm", 53 | opsetMin: 1, 54 | factory: () => new WasmLeakyRelu("kernel_leakyrelu"), 55 | }, 56 | ]; 57 | } 58 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/wasm/operators/standard/flatten.ts: -------------------------------------------------------------------------------- 1 | import { Tensor } from "../../../../interface/core/tensor"; 2 | import { OperatorEntry } from "../../../../interface/core/operator"; 3 | import { WebDNNWasmContext } from "../../../../interface/backend/wasm/wasmContext"; 4 | import { Flatten } from "../../../base/flatten"; 5 | 6 | class WasmFlatten extends Flatten { 7 | constructor() { 8 | super("wasm"); 9 | } 10 | 11 | async run(context: WebDNNWasmContext, inputs: Tensor[]): Promise { 12 | // TODO: avoid copy 13 | const input = inputs[0]; 14 | context.assertsWasmTensor(input); 15 | const computedShape = this.calcShape(input), 16 | output = context.emptyTensor(computedShape, input.dataType); 17 | context.runKernel("kernel_copy", [ 18 | { type: "tensor", value: input }, 19 | { type: "tensor", value: output }, 20 | { type: "int32", value: output.length }, 21 | ]); 22 | return [output]; 23 | } 24 | } 25 | 26 | export function getOpEntries(): OperatorEntry[] { 27 | return [ 28 | { 29 | opType: "Flatten", 30 | backend: "wasm", 31 | opsetMin: 1, 32 | factory: () => new WasmFlatten(), 33 | }, 34 | ]; 35 | } 36 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/wasm/operators/standard/gemm.ts: -------------------------------------------------------------------------------- 1 | import { WebDNNWasmContext } from "../../../../interface/backend/wasm/wasmContext"; 2 | import { Tensor } from "../../../../interface/core/tensor"; 3 | import { OperatorEntry } from "../../../../interface/core/operator"; 4 | import { Gemm } from "../../../base/gemm"; 5 | 6 | class WasmGemm extends Gemm { 7 | constructor() { 8 | super("wasm"); 9 | } 10 | 11 | async run(context: WebDNNWasmContext, inputs: Tensor[]): Promise { 12 | context.assertsWasmTensorArray(inputs); 13 | const [inputA, inputB, inputC] = inputs; 14 | const { m, n, k } = this.calcShape(inputA.dims, inputB.dims); 15 | const output = context.emptyTensor([m, n]); 16 | if (this.alpha !== 1.0) { 17 | throw new Error("Gemm: alpha !== 1.0 is not yet supported"); 18 | } 19 | if (inputC) { 20 | if (this.beta !== 1.0) { 21 | throw new Error("Gemm: beta !== 1.0 is not yet supported"); 22 | } 23 | context.runKernel( 24 | `kernel_gemm_transa${this.transA ? "1" : "0"}_transb${ 25 | this.transB ? "1" : "0" 26 | }_c`, 27 | [ 28 | { type: "tensor", value: inputA }, 29 | { type: "tensor", value: inputB }, 30 | { type: "tensor", value: inputC }, 31 | { type: "tensor", value: output }, 32 | { type: "int32", value: m }, 33 | { type: "int32", value: n }, 34 | { type: "int32", value: k }, 35 | ] 36 | ); 37 | } else { 38 | context.runKernel( 39 | `kernel_gemm_transa${this.transA ? "1" : "0"}_transb${ 40 | this.transB ? "1" : "0" 41 | }`, 42 | [ 43 | { type: "tensor", value: inputA }, 44 | { type: "tensor", value: inputB }, 45 | { type: "tensor", value: output }, 46 | { type: "int32", value: m }, 47 | { type: "int32", value: n }, 48 | { type: "int32", value: k }, 49 | ] 50 | ); 51 | } 52 | return [output]; 53 | } 54 | } 55 | 56 | export function getOpEntries(): OperatorEntry[] { 57 | return [ 58 | { 59 | opType: "Gemm", 60 | backend: "wasm", 61 | opsetMin: 1, 62 | factory: () => new WasmGemm(), 63 | }, 64 | ]; 65 | } 66 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/wasm/operators/standard/reshape5.ts: -------------------------------------------------------------------------------- 1 | import { Backend } from "../../../../interface/core/constants"; 2 | import { Reshape5 } from "../../../base/reshape5"; 3 | import { Tensor } from "../../../../interface/core/tensor"; 4 | import { OperatorEntry } from "../../../../interface/core/operator"; 5 | import { WebDNNWasmContext } from "../../../../interface/backend/wasm/wasmContext"; 6 | 7 | class WasmReshape5 extends Reshape5 { 8 | constructor() { 9 | super("wasm"); 10 | } 11 | 12 | getTensorBackendRequirement( 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | nInputs: number, 15 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 16 | nOutputs: number 17 | ): (Backend | null)[] { 18 | return ["wasm", "cpu"]; 19 | } 20 | 21 | async run(context: WebDNNWasmContext, inputs: Tensor[]): Promise { 22 | // TODO: avoid copy 23 | const input = inputs[0], 24 | shapeTensor = inputs[1]; 25 | context.assertsWasmTensor(input); 26 | context.cpuContext.assertsCPUTensor(shapeTensor); 27 | const computedShape = this.calcShape(input, shapeTensor), 28 | output = context.emptyTensor(computedShape, input.dataType); 29 | context.runKernel("kernel_copy", [ 30 | { type: "tensor", value: input }, 31 | { type: "tensor", value: output }, 32 | { type: "int32", value: output.length }, 33 | ]); 34 | return [output]; 35 | } 36 | } 37 | 38 | export function getOpEntries(): OperatorEntry[] { 39 | return [ 40 | { 41 | opType: "Reshape", 42 | backend: "wasm", 43 | opsetMin: 5, 44 | factory: () => new WasmReshape5(), 45 | }, 46 | ]; 47 | } 48 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/wasm/operators/standard/squeeze.ts: -------------------------------------------------------------------------------- 1 | import { Backend } from "../../../../interface/core/constants"; 2 | import { Tensor } from "../../../../interface/core/tensor"; 3 | import { OperatorEntry } from "../../../../interface/core/operator"; 4 | import { WebDNNWasmContext } from "../../../../interface/backend/wasm/wasmContext"; 5 | import { Squeeze1, Squeeze13 } from "../../../base/squeeze"; 6 | 7 | class WasmSqueeze1 extends Squeeze1 { 8 | constructor() { 9 | super("wasm"); 10 | } 11 | 12 | async run(context: WebDNNWasmContext, inputs: Tensor[]): Promise { 13 | // TODO: avoid copy 14 | const input = inputs[0]; 15 | context.assertsWasmTensor(input); 16 | const computedShape = this.calcShape(input), 17 | output = context.emptyTensor(computedShape, input.dataType); 18 | context.runKernel("kernel_copy", [ 19 | { type: "tensor", value: input }, 20 | { type: "tensor", value: output }, 21 | { type: "int32", value: output.length }, 22 | ]); 23 | return [output]; 24 | } 25 | } 26 | 27 | class WasmSqueeze13 extends Squeeze13 { 28 | constructor() { 29 | super("wasm"); 30 | } 31 | 32 | getTensorBackendRequirement( 33 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 34 | nInputs: number, 35 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 36 | nOutputs: number 37 | ): (Backend | null)[] { 38 | return ["wasm", "cpu"]; 39 | } 40 | 41 | async run(context: WebDNNWasmContext, inputs: Tensor[]): Promise { 42 | // TODO: avoid copy 43 | const input = inputs[0], 44 | axes = inputs[1]; 45 | context.assertsWasmTensor(input); 46 | context.cpuContext.assertsCPUTensor(axes); 47 | const computedShape = this.calcShape(input, axes), 48 | output = context.emptyTensor(computedShape, input.dataType); 49 | context.runKernel("kernel_copy", [ 50 | { type: "tensor", value: input }, 51 | { type: "tensor", value: output }, 52 | { type: "int32", value: output.length }, 53 | ]); 54 | return [output]; 55 | } 56 | } 57 | export function getOpEntries(): OperatorEntry[] { 58 | return [ 59 | { 60 | opType: "Squeeze", 61 | backend: "wasm", 62 | opsetMin: 13, 63 | factory: () => new WasmSqueeze13(), 64 | }, 65 | { 66 | opType: "Squeeze", 67 | backend: "wasm", 68 | opsetMin: 1, 69 | opsetMax: 13, 70 | factory: () => new WasmSqueeze1(), 71 | }, 72 | ]; 73 | } 74 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/wasm/operators/standard/unary.ts: -------------------------------------------------------------------------------- 1 | import { OperatorImpl } from "../../../operatorImpl"; 2 | import { WebDNNWasmContext } from "../../../../interface/backend/wasm/wasmContext"; 3 | import { Tensor } from "../../../../interface/core/tensor"; 4 | import { OperatorEntry } from "../../../../interface/core/operator"; 5 | 6 | class WasmUnary extends OperatorImpl { 7 | constructor(private kernelName: string) { 8 | super("wasm"); 9 | } 10 | 11 | async run(context: WebDNNWasmContext, inputs: Tensor[]): Promise { 12 | context.assertsWasmTensorArray(inputs); 13 | const input = inputs[0]; 14 | if (input.dataType !== "float32") { 15 | throw new Error("Only float32 is supported"); 16 | } 17 | const output = context.emptyTensor(input.dims, input.dataType); 18 | context.runKernel(this.kernelName, [ 19 | { type: "tensor", value: input }, 20 | { type: "tensor", value: output }, 21 | { type: "int32", value: input.length }, 22 | ]); 23 | return [output]; 24 | } 25 | } 26 | 27 | export function getOpEntries(): OperatorEntry[] { 28 | return [ 29 | { 30 | opType: "Ceil", 31 | backend: "wasm", 32 | opsetMin: 1, 33 | factory: () => new WasmUnary("kernel_ceil"), 34 | }, 35 | { 36 | opType: "Exp", 37 | backend: "wasm", 38 | opsetMin: 1, 39 | factory: () => new WasmUnary("kernel_exp"), 40 | }, 41 | { 42 | opType: "Floor", 43 | backend: "wasm", 44 | opsetMin: 1, 45 | factory: () => new WasmUnary("kernel_floor"), 46 | }, 47 | { 48 | opType: "Relu", 49 | backend: "wasm", 50 | opsetMin: 1, 51 | factory: () => new WasmUnary("kernel_relu"), 52 | }, 53 | { 54 | opType: "Sigmoid", 55 | backend: "wasm", 56 | opsetMin: 1, 57 | factory: () => new WasmUnary("kernel_sigmoid"), 58 | }, 59 | { 60 | opType: "Sqrt", 61 | backend: "wasm", 62 | opsetMin: 1, 63 | factory: () => new WasmUnary("kernel_sqrt"), 64 | }, 65 | { 66 | opType: "Tanh", 67 | backend: "wasm", 68 | opsetMin: 1, 69 | factory: () => new WasmUnary("kernel_tanh"), 70 | }, 71 | ]; 72 | } 73 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/wasm/operators/standard/unsqueeze.ts: -------------------------------------------------------------------------------- 1 | import { Backend } from "../../../../interface/core/constants"; 2 | import { Tensor } from "../../../../interface/core/tensor"; 3 | import { OperatorEntry } from "../../../../interface/core/operator"; 4 | import { WebDNNWasmContext } from "../../../../interface/backend/wasm/wasmContext"; 5 | import { Unsqueeze1, Unsqueeze13 } from "../../../base/unsqueeze"; 6 | 7 | class WasmUnsqueeze1 extends Unsqueeze1 { 8 | constructor() { 9 | super("wasm"); 10 | } 11 | 12 | async run(context: WebDNNWasmContext, inputs: Tensor[]): Promise { 13 | // TODO: avoid copy 14 | const input = inputs[0]; 15 | context.assertsWasmTensor(input); 16 | const computedShape = this.calcShape(input), 17 | output = context.emptyTensor(computedShape, input.dataType); 18 | context.runKernel("kernel_copy", [ 19 | { type: "tensor", value: input }, 20 | { type: "tensor", value: output }, 21 | { type: "int32", value: output.length }, 22 | ]); 23 | return [output]; 24 | } 25 | } 26 | 27 | class WasmUnsqueeze13 extends Unsqueeze13 { 28 | constructor() { 29 | super("wasm"); 30 | } 31 | 32 | getTensorBackendRequirement( 33 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 34 | nInputs: number, 35 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 36 | nOutputs: number 37 | ): (Backend | null)[] { 38 | return ["wasm", "cpu"]; 39 | } 40 | 41 | async run(context: WebDNNWasmContext, inputs: Tensor[]): Promise { 42 | // TODO: avoid copy 43 | const input = inputs[0], 44 | axes = inputs[1]; 45 | context.assertsWasmTensor(input); 46 | context.cpuContext.assertsCPUTensor(axes); 47 | const computedShape = this.calcShape(input, axes), 48 | output = context.emptyTensor(computedShape, input.dataType); 49 | context.runKernel("kernel_copy", [ 50 | { type: "tensor", value: input }, 51 | { type: "tensor", value: output }, 52 | { type: "int32", value: output.length }, 53 | ]); 54 | return [output]; 55 | } 56 | } 57 | export function getOpEntries(): OperatorEntry[] { 58 | return [ 59 | { 60 | opType: "Unsqueeze", 61 | backend: "wasm", 62 | opsetMin: 13, 63 | factory: () => new WasmUnsqueeze13(), 64 | }, 65 | { 66 | opType: "Unsqueeze", 67 | backend: "wasm", 68 | opsetMin: 1, 69 | opsetMax: 13, 70 | factory: () => new WasmUnsqueeze1(), 71 | }, 72 | ]; 73 | } 74 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/wasm/worker/.gitignore: -------------------------------------------------------------------------------- 1 | worker* 2 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgl/operators/autogen/.gitignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgl/operators/custom/.gitignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgl/operators/standard/averagepool.ts: -------------------------------------------------------------------------------- 1 | import { AveragePool } from "../../../base/averagepool"; 2 | import { averagepool } from "../../rawcomputation/averagepool"; 3 | import { WebDNNWebGLContext } from "../../../../interface/backend/webgl/webglContext"; 4 | import { Tensor } from "../../../../interface/core/tensor"; 5 | import { OperatorEntry } from "../../../../interface/core/operator"; 6 | 7 | // Version 1, 7, 10, 11+ 8 | class WebGLAveragePool extends AveragePool { 9 | constructor() { 10 | super("webgl"); 11 | } 12 | 13 | async run(context: WebDNNWebGLContext, inputs: Tensor[]): Promise { 14 | context.assertsWebGLTensorArray(inputs); 15 | const inputX = inputs[0]; 16 | // TODO: 2D以外対応 17 | if (inputX.ndim !== 4) { 18 | throw new Error("MaxPool other than 2D is not yet supported"); 19 | } 20 | if (inputX.dimPerPixel !== 1) { 21 | throw new Error(); 22 | } 23 | 24 | const { batch, kernelShape, pads, strides, inShape, outShape, ch } = 25 | this.calcShape(inputX.dims), 26 | output = context.emptyTensor( 27 | [batch, ch, outShape[0], outShape[1]], 28 | "float32", 29 | { dimPerPixel: 1 } 30 | ); 31 | await averagepool( 32 | context, 33 | inputX, 34 | output, 35 | this.countIncludePad, 36 | batch, 37 | kernelShape, 38 | pads, 39 | strides, 40 | inShape, 41 | outShape, 42 | ch 43 | ); 44 | return [output]; 45 | } 46 | } 47 | 48 | export function getOpEntries(): OperatorEntry[] { 49 | return [ 50 | { 51 | opType: "AveragePool", 52 | backend: "webgl", 53 | opsetMin: 1, 54 | factory: () => new WebGLAveragePool(), 55 | }, 56 | ]; 57 | } 58 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgl/operators/standard/cast.ts: -------------------------------------------------------------------------------- 1 | import { onnx } from "onnx-proto"; 2 | import { OperatorImpl } from "../../../operatorImpl"; 3 | import { WebDNNWebGLContext } from "../../../../interface/backend/webgl/webglContext"; 4 | import { Tensor } from "../../../../interface/core/tensor"; 5 | import { getAttrInt } from "../../../operatorUtil"; 6 | import { OperatorEntry } from "../../../../interface/core/operator"; 7 | 8 | class Cast extends OperatorImpl { 9 | to!: onnx.TensorProto.DataType; 10 | 11 | constructor() { 12 | super("webgl"); 13 | } 14 | 15 | initialize(attribute: onnx.IAttributeProto[]): void { 16 | super.initialize(attribute); 17 | this.to = getAttrInt(attribute, "to", onnx.TensorProto.DataType.FLOAT); 18 | } 19 | 20 | async run(context: WebDNNWebGLContext, inputs: Tensor[]): Promise { 21 | context.assertsWebGLTensorArray(inputs); 22 | const input = inputs[0]; 23 | // 現状、trivialなfloat32->float32のキャストのみ通す 24 | if (input.dataType !== "float32") { 25 | throw new Error(`Cast: input must be float32`); 26 | } 27 | if (this.to !== onnx.TensorProto.DataType.FLOAT) { 28 | throw new Error(`Cast: output must be float32`); 29 | } 30 | 31 | return [input.alias(input.dims)]; 32 | } 33 | } 34 | 35 | export function getOpEntries(): OperatorEntry[] { 36 | return [ 37 | { 38 | opType: "Cast", 39 | backend: "webgl", 40 | opsetMin: 1, 41 | factory: () => new Cast(), 42 | }, 43 | ]; 44 | } 45 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgl/operators/standard/flatten.ts: -------------------------------------------------------------------------------- 1 | import { WebDNNWebGLContext } from "../../../../interface/backend/webgl/webglContext"; 2 | import { OperatorEntry } from "../../../../interface/core/operator"; 3 | import { Tensor } from "../../../../interface/core/tensor"; 4 | import { Flatten } from "../../../base/flatten"; 5 | 6 | class WebGLFlatten extends Flatten { 7 | constructor() { 8 | super("webgl"); 9 | } 10 | 11 | async run(context: WebDNNWebGLContext, inputs: Tensor[]): Promise { 12 | context.assertsWebGLTensorArray(inputs); 13 | const input = inputs[0], 14 | computedShape = this.calcShape(input); 15 | 16 | return [input.alias(computedShape)]; 17 | } 18 | } 19 | 20 | export function getOpEntries(): OperatorEntry[] { 21 | return [ 22 | { 23 | opType: "Flatten", 24 | backend: "webgl", 25 | opsetMin: 1, 26 | factory: () => new WebGLFlatten(), 27 | }, 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgl/operators/standard/globalaveragepool.ts: -------------------------------------------------------------------------------- 1 | import { OperatorImpl } from "../../../operatorImpl"; 2 | import { WebDNNWebGLContext } from "../../../../interface/backend/webgl/webglContext"; 3 | import { Tensor } from "../../../../interface/core/tensor"; 4 | import { averagepool } from "../../rawcomputation/averagepool"; 5 | import { OperatorEntry } from "../../../../interface/core/operator"; 6 | 7 | // Version 11 8 | export class WebGLGlobalAveragePool extends OperatorImpl { 9 | constructor() { 10 | super("webgl"); 11 | } 12 | 13 | async run(context: WebDNNWebGLContext, inputs: Tensor[]): Promise { 14 | context.assertsWebGLTensorArray(inputs); 15 | const inputX = inputs[0]; 16 | // TODO: 2D以外対応 17 | if (inputX.ndim !== 4) { 18 | throw new Error("MaxPool other than 2D is not yet supported"); 19 | } 20 | if (inputX.dimPerPixel !== 1) { 21 | throw new Error(); 22 | } 23 | 24 | const batch = inputX.dims[0], 25 | ch = inputX.dims[1], 26 | inShape = [inputX.dims[2], inputX.dims[3]], 27 | outShape = [1, 1], 28 | output = context.emptyTensor( 29 | [batch, ch, outShape[0], outShape[1]], 30 | "float32" 31 | ); 32 | await averagepool( 33 | context, 34 | inputX, 35 | output, 36 | true, // わずかに計算量が減る 37 | batch, 38 | inShape, 39 | [0, 0, 0, 0], 40 | [1, 1], 41 | inShape, 42 | outShape, 43 | ch 44 | ); 45 | return [output]; 46 | } 47 | } 48 | 49 | export function getOpEntries(): OperatorEntry[] { 50 | return [ 51 | { 52 | opType: "GlobalAveragePool", 53 | backend: "webgl", 54 | opsetMin: 1, 55 | factory: () => new WebGLGlobalAveragePool(), 56 | }, 57 | ]; 58 | } 59 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgl/operators/standard/reshape5.ts: -------------------------------------------------------------------------------- 1 | import { WebDNNWebGLContext } from "../../../../interface/backend/webgl/webglContext"; 2 | import { Backend } from "../../../../interface/core/constants"; 3 | import { OperatorEntry } from "../../../../interface/core/operator"; 4 | import { Tensor } from "../../../../interface/core/tensor"; 5 | import { Reshape5 } from "../../../base/reshape5"; 6 | 7 | export class WebGLReshape5 extends Reshape5 { 8 | constructor() { 9 | super("webgl"); 10 | } 11 | 12 | getTensorBackendRequirement( 13 | nInputs: number, 14 | nOutputs: number 15 | ): (Backend | null)[] { 16 | return [this.backend, "cpu"]; 17 | } 18 | 19 | async run(context: WebDNNWebGLContext, inputs: Tensor[]): Promise { 20 | const input = inputs[0], 21 | shapeTensor = inputs[1]; 22 | if (!context.cpuContext.isCPUTensor(shapeTensor)) { 23 | throw new Error(`Reshape: shapeTensor is not on cpu.`); 24 | } 25 | if (!context.isWebGLTensor(input)) { 26 | throw new Error("Reshape: input is not on webgl."); 27 | } 28 | const computedShape = this.calcShape(input, shapeTensor); 29 | 30 | return [input.alias(computedShape)]; 31 | } 32 | } 33 | 34 | export function getOpEntries(): OperatorEntry[] { 35 | return [ 36 | { 37 | opType: "Reshape", 38 | backend: "webgl", 39 | opsetMin: 5, 40 | factory: () => new WebGLReshape5(), 41 | }, 42 | ]; 43 | } 44 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgl/operators/standard/split.ts: -------------------------------------------------------------------------------- 1 | import { 2 | shaderGenHeader, 3 | shaderGenOutput, 4 | shaderGenTensorNDGet, 5 | shaderGenTensorNDGetUniformItem, 6 | shaderGenTensorOutputCoordsWithReturn, 7 | shaderGenTensorOutputUniform, 8 | shaderGenTensorOutputUniformItem, 9 | } from "../../shaderHelper"; 10 | import { Split2 } from "../../../base/split"; 11 | import { 12 | WebDNNWebGLContext, 13 | WebGLUniformItem, 14 | } from "../../../../interface/backend/webgl/webglContext"; 15 | import { Tensor } from "../../../../interface/core/tensor"; 16 | import { WebGLTensor } from "../../../../interface/backend/webgl/webglTensor"; 17 | import { OperatorEntry } from "../../../../interface/core/operator"; 18 | 19 | export class WebGLSplit2 extends Split2 { 20 | constructor() { 21 | super("webgl"); 22 | } 23 | 24 | async run( 25 | context: WebDNNWebGLContext, 26 | inputs: Tensor[], 27 | nOutputs: number 28 | ): Promise { 29 | context.assertsWebGLTensorArray(inputs); 30 | const input = inputs[0], 31 | { 32 | eachOutputParams, 33 | outerLength, 34 | innerLength, 35 | inOuterStride, 36 | inConcatStride, 37 | } = this.calcShape(input, nOutputs), 38 | outputs: WebGLTensor[] = [], 39 | kernelName = "split", 40 | kernelSource = `${shaderGenHeader(context.webgl2)} 41 | 42 | ${shaderGenTensorOutputUniform(3)} 43 | uniform int offset; 44 | 45 | ${shaderGenTensorNDGet("tex_input", 3, context.webgl2)} 46 | 47 | void main() { 48 | ${shaderGenTensorOutputCoordsWithReturn(3)} 49 | float s = get_tex_input(tex_output_0, tex_output_1 + offset, tex_output_2); 50 | ${shaderGenOutput("s", context.webgl2)} 51 | return; 52 | } 53 | `; 54 | context.addKernel(kernelName, kernelSource); 55 | for (let i = 0; i < nOutputs; i++) { 56 | const { dim, offset, outShape } = eachOutputParams[i], 57 | ot = context.emptyTensor(outShape, input.dataType), 58 | uniforms: WebGLUniformItem[] = [ 59 | ...shaderGenTensorNDGetUniformItem( 60 | "tex_input", 61 | [inOuterStride, inConcatStride, 1], 62 | input, 63 | context.webgl2 64 | ), 65 | ...shaderGenTensorOutputUniformItem( 66 | [outerLength, dim, innerLength], 67 | ot, 68 | context.webgl2 69 | ), 70 | { name: "offset", type: "int", value: offset }, 71 | ]; 72 | await context.runKernel( 73 | kernelName, 74 | [{ tensor: input, name: "tex_input" }], 75 | ot, 76 | uniforms 77 | ); 78 | outputs.push(ot); 79 | } 80 | return outputs; 81 | } 82 | } 83 | 84 | export function getOpEntries(): OperatorEntry[] { 85 | return [ 86 | { 87 | opType: "Split", 88 | backend: "webgl", 89 | opsetMin: 1, 90 | opsetMax: 13, 91 | factory: () => new WebGLSplit2(), 92 | }, 93 | ]; 94 | } 95 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgl/operators/standard/squeeze.ts: -------------------------------------------------------------------------------- 1 | import { WebDNNWebGLContext } from "../../../../interface/backend/webgl/webglContext"; 2 | import { Backend } from "../../../../interface/core/constants"; 3 | import { OperatorEntry } from "../../../../interface/core/operator"; 4 | import { Tensor } from "../../../../interface/core/tensor"; 5 | import { Squeeze1, Squeeze13 } from "../../../base/squeeze"; 6 | 7 | export class WebGLSqueeze1 extends Squeeze1 { 8 | constructor() { 9 | super("webgl"); 10 | } 11 | 12 | async run(context: WebDNNWebGLContext, inputs: Tensor[]): Promise { 13 | context.assertsWebGLTensorArray(inputs); 14 | const input = inputs[0], 15 | computedShape = this.calcShape(input); 16 | 17 | return [input.alias(computedShape)]; 18 | } 19 | } 20 | 21 | export class WebGLSqueeze13 extends Squeeze13 { 22 | constructor() { 23 | super("webgl"); 24 | } 25 | 26 | getTensorBackendRequirement( 27 | nInputs: number, 28 | nOutputs: number 29 | ): (Backend | null)[] { 30 | return [this.backend, "cpu"]; 31 | } 32 | 33 | async run(context: WebDNNWebGLContext, inputs: Tensor[]): Promise { 34 | const input = inputs[0], 35 | axes = inputs[1]; 36 | if (!context.cpuContext.isCPUTensor(axes)) { 37 | throw new Error(`Unsqueeze: axes is not on cpu.`); 38 | } 39 | if (!context.isWebGLTensor(input)) { 40 | throw new Error("Unsqueeze: input is not on webgl."); 41 | } 42 | const computedShape = this.calcShape(input, axes); 43 | 44 | return [input.alias(computedShape)]; 45 | } 46 | } 47 | 48 | export function getOpEntries(): OperatorEntry[] { 49 | return [ 50 | { 51 | opType: "Squeeze", 52 | backend: "webgl", 53 | opsetMin: 13, 54 | factory: () => new WebGLSqueeze13(), 55 | }, 56 | { 57 | opType: "Squeeze", 58 | backend: "webgl", 59 | opsetMin: 1, 60 | opsetMax: 13, 61 | factory: () => new WebGLSqueeze1(), 62 | }, 63 | ]; 64 | } 65 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgl/operators/standard/unsqueeze.ts: -------------------------------------------------------------------------------- 1 | import { WebDNNWebGLContext } from "../../../../interface/backend/webgl/webglContext"; 2 | import { Backend } from "../../../../interface/core/constants"; 3 | import { OperatorEntry } from "../../../../interface/core/operator"; 4 | import { Tensor } from "../../../../interface/core/tensor"; 5 | import { Unsqueeze1, Unsqueeze13 } from "../../../base/unsqueeze"; 6 | 7 | export class WebGLUnsqueeze1 extends Unsqueeze1 { 8 | constructor() { 9 | super("webgl"); 10 | } 11 | 12 | async run(context: WebDNNWebGLContext, inputs: Tensor[]): Promise { 13 | context.assertsWebGLTensorArray(inputs); 14 | const input = inputs[0], 15 | computedShape = this.calcShape(input); 16 | 17 | return [input.alias(computedShape)]; 18 | } 19 | } 20 | 21 | export class WebGLUnsqueeze13 extends Unsqueeze13 { 22 | constructor() { 23 | super("webgl"); 24 | } 25 | 26 | getTensorBackendRequirement( 27 | nInputs: number, 28 | nOutputs: number 29 | ): (Backend | null)[] { 30 | return [this.backend, "cpu"]; 31 | } 32 | 33 | async run(context: WebDNNWebGLContext, inputs: Tensor[]): Promise { 34 | const input = inputs[0], 35 | axes = inputs[1]; 36 | if (!context.cpuContext.isCPUTensor(axes)) { 37 | throw new Error(`Unsqueeze: axes is not on cpu.`); 38 | } 39 | if (!context.isWebGLTensor(input)) { 40 | throw new Error("Unsqueeze: input is not on webgl."); 41 | } 42 | const computedShape = this.calcShape(input, axes); 43 | 44 | return [input.alias(computedShape)]; 45 | } 46 | } 47 | 48 | export function getOpEntries(): OperatorEntry[] { 49 | return [ 50 | { 51 | opType: "Unsqueeze", 52 | backend: "webgl", 53 | opsetMin: 13, 54 | factory: () => new WebGLUnsqueeze13(), 55 | }, 56 | { 57 | opType: "Unsqueeze", 58 | backend: "webgl", 59 | opsetMin: 1, 60 | opsetMax: 13, 61 | factory: () => new WebGLUnsqueeze1(), 62 | }, 63 | ]; 64 | } 65 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgpu/opEntriesStandard.ts: -------------------------------------------------------------------------------- 1 | // auto-generated by scripts/make_operator_entries.py 2 | import { OperatorEntry } from "../../interface/core/operator"; 3 | 4 | import { getOpEntries as getOpEntriesoperatorsstandardbinary7 } from "./operators/standard/binary7"; 5 | import { getOpEntries as getOpEntriesoperatorsstandardconv } from "./operators/standard/conv"; 6 | import { getOpEntries as getOpEntriesoperatorsstandardgemm } from "./operators/standard/gemm"; 7 | import { getOpEntries as getOpEntriesoperatorsstandardunary } from "./operators/standard/unary"; 8 | 9 | export function getOpEntries(): OperatorEntry[] { 10 | const entries: OperatorEntry[] = []; 11 | entries.push(...getOpEntriesoperatorsstandardbinary7()); 12 | entries.push(...getOpEntriesoperatorsstandardconv()); 13 | entries.push(...getOpEntriesoperatorsstandardgemm()); 14 | entries.push(...getOpEntriesoperatorsstandardunary()); 15 | return entries; 16 | } 17 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgpu/operators/custom/.gitignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgpu/operators/standard/gemm.ts: -------------------------------------------------------------------------------- 1 | import { WebDNNWebGPUContext } from "../../../../interface/backend/webgpu/webgpuContext"; 2 | import { WebGPUTensor } from "../../../../interface/backend/webgpu/webgpuTensor"; 3 | import { OperatorEntry } from "../../../../interface/core/operator"; 4 | import { Tensor } from "../../../../interface/core/tensor"; 5 | import { Gemm } from "../../../base/gemm"; 6 | import { broadcastUni } from "../../../operatorUtil"; 7 | import { webgpuShaders } from "../../shaders"; 8 | 9 | export class WebGPUGemm extends Gemm { 10 | constructor() { 11 | super("webgpu"); 12 | } 13 | 14 | async run(context: WebDNNWebGPUContext, inputs: Tensor[]): Promise { 15 | context.assertsWebGPUTensorArray(inputs); 16 | const inputA = inputs[0], 17 | inputB = inputs[1], 18 | inputC = inputs[2]; 19 | if (inputC) { 20 | return this.runWithC(context, inputA, inputB, inputC); 21 | } 22 | throw new Error(); 23 | } 24 | 25 | async runWithC( 26 | context: WebDNNWebGPUContext, 27 | inputA: WebGPUTensor, 28 | inputB: WebGPUTensor, 29 | inputC: WebGPUTensor 30 | ): Promise { 31 | if (inputA.dataType !== "float32") { 32 | throw new Error(); 33 | } 34 | const { 35 | m, 36 | n, 37 | k, 38 | strideA: [strideA0, strideA1], 39 | strideB: [strideB0, strideB1], 40 | } = this.calcShape(inputA.dims, inputB.dims), 41 | [strideC0, strideC1] = broadcastUni([m, n], inputC.dims), 42 | outputTensor = context.emptyTensor([m, n], "float32"), 43 | shaderName = "gemm"; 44 | 45 | if (!context.hasPipeline(shaderName)) { 46 | context.createPipeline(shaderName, webgpuShaders.gemm, 5); 47 | } 48 | 49 | await context.run({ 50 | pipelineName: shaderName, 51 | tensors: [inputA, inputB, inputC, outputTensor], 52 | meta: { 53 | elements: [ 54 | { value: m, type: "uint32" }, 55 | { value: n, type: "uint32" }, 56 | { value: k, type: "uint32" }, 57 | { value: strideA0, type: "uint32" }, 58 | { value: strideA1, type: "uint32" }, 59 | { value: strideB0, type: "uint32" }, 60 | { value: strideB1, type: "uint32" }, 61 | { value: strideC0, type: "uint32" }, 62 | { value: strideC1, type: "uint32" }, 63 | { value: this.alpha, type: "float32" }, 64 | { value: this.beta, type: "float32" }, 65 | ], 66 | }, 67 | workGroups: { x: 256 / 8, y: 256 / 8, z: 1 }, 68 | }); 69 | 70 | return [outputTensor]; 71 | } 72 | } 73 | 74 | export function getOpEntries(): OperatorEntry[] { 75 | return [ 76 | { 77 | opType: "Gemm", 78 | backend: "webgpu", 79 | opsetMin: 1, 80 | factory: () => new WebGPUGemm(), 81 | }, 82 | ]; 83 | } 84 | -------------------------------------------------------------------------------- /src/descriptor_runner/operators/webgpu/operators/standard/unary.ts: -------------------------------------------------------------------------------- 1 | import { OperatorImpl } from "../../../operatorImpl"; 2 | import { WebDNNWebGPUContext } from "../../../../interface/backend/webgpu/webgpuContext"; 3 | import { Tensor } from "../../../../interface/core/tensor"; 4 | import { webgpuShaders } from "../../shaders"; 5 | import { OperatorEntry } from "../../../../interface/core/operator"; 6 | 7 | export class WebGPUUnary extends OperatorImpl { 8 | constructor(public shaderName: string, private shaderBinary: Uint32Array) { 9 | super("webgpu"); 10 | } 11 | 12 | async run(context: WebDNNWebGPUContext, inputs: Tensor[]): Promise { 13 | const input = inputs[0]; 14 | if (input.dataType !== "float32") { 15 | throw new Error(); 16 | } 17 | const outputTensor = context.emptyTensor(input.dims, "float32"); 18 | 19 | if (!context.hasPipeline(this.shaderName)) { 20 | context.createPipeline(this.shaderName, this.shaderBinary, 3); 21 | } 22 | 23 | await context.run({ 24 | pipelineName: this.shaderName, 25 | tensors: [input, outputTensor], 26 | meta: { 27 | elements: [{ value: input.length, type: "uint32" }], 28 | }, 29 | workGroups: { x: 4096 / 64, y: 1, z: 1 }, 30 | }); 31 | 32 | return [outputTensor]; 33 | } 34 | } 35 | 36 | export function getOpEntries(): OperatorEntry[] { 37 | return [ 38 | { 39 | opType: "Relu", 40 | backend: "webgpu", 41 | opsetMin: 1, 42 | factory: () => new WebGPUUnary("relu", webgpuShaders.relu), 43 | }, 44 | ]; 45 | } 46 | -------------------------------------------------------------------------------- /src/descriptor_runner/separateBuild/operatorCPU.ts: -------------------------------------------------------------------------------- 1 | import { getOpEntries as getOpEntriesCPU } from "../operators/cpu/opEntriesAll"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | declare let WebDNN: any; 5 | 6 | function injectOperators() { 7 | if (WebDNN.injectOperators) { 8 | WebDNN.injectOperators({ operatorEntries: [...getOpEntriesCPU()] }); 9 | } else { 10 | console.error( 11 | "WebDNN.injectOperators not found. webdnn-core.js seems to be not imported." 12 | ); 13 | } 14 | } 15 | 16 | injectOperators(); 17 | -------------------------------------------------------------------------------- /src/descriptor_runner/separateBuild/operatorWasm.ts: -------------------------------------------------------------------------------- 1 | import { getOpEntries as getOpEntriesCPU } from "../operators/cpu/opEntriesAll"; 2 | import { getOpEntries as getOpEntriesWasm } from "../operators/wasm/opEntriesAll"; 3 | import { wasmWorkerSrcUrl } from "../operators/wasm/worker/worker"; 4 | 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | declare let WebDNN: any; 7 | 8 | function injectOperators() { 9 | if (WebDNN.injectOperators) { 10 | WebDNN.injectOperators({ 11 | operatorEntries: [...getOpEntriesCPU(), ...getOpEntriesWasm()], 12 | wasmWorkerSrcUrl, 13 | }); 14 | } else { 15 | console.error( 16 | "WebDNN.injectOperators not found. webdnn-core.js seems to be not imported." 17 | ); 18 | } 19 | } 20 | 21 | injectOperators(); 22 | -------------------------------------------------------------------------------- /src/descriptor_runner/separateBuild/operatorWebGL.ts: -------------------------------------------------------------------------------- 1 | import { getOpEntries as getOpEntriesCPU } from "../operators/cpu/opEntriesAll"; 2 | import { getOpEntries as getOpEntriesWebGL } from "../operators/webgl/opEntriesAll"; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | declare let WebDNN: any; 6 | 7 | function injectOperators() { 8 | if (WebDNN.injectOperators) { 9 | WebDNN.injectOperators({ 10 | operatorEntries: [...getOpEntriesCPU(), ...getOpEntriesWebGL()], 11 | }); 12 | } else { 13 | console.error( 14 | "WebDNN.injectOperators not found. webdnn-core.js seems to be not imported." 15 | ); 16 | } 17 | } 18 | 19 | injectOperators(); 20 | -------------------------------------------------------------------------------- /src/descriptor_runner/separateBuild/operatorWebGPU.ts: -------------------------------------------------------------------------------- 1 | import { getOpEntries as getOpEntriesCPU } from "../operators/cpu/opEntriesAll"; 2 | import { getOpEntries as getOpEntriesWebGPU } from "../operators/webgpu/opEntriesAll"; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | declare let WebDNN: any; 6 | 7 | function injectOperators() { 8 | if (WebDNN.injectOperators) { 9 | WebDNN.injectOperators({ 10 | operatorEntries: [...getOpEntriesCPU(), ...getOpEntriesWebGPU()], 11 | }); 12 | } else { 13 | console.error( 14 | "WebDNN.injectOperators not found. webdnn-core.js seems to be not imported." 15 | ); 16 | } 17 | } 18 | 19 | injectOperators(); 20 | -------------------------------------------------------------------------------- /src/descriptor_runner/util.ts: -------------------------------------------------------------------------------- 1 | import Long from "long"; 2 | 3 | export function nonnull(value: T | null | undefined): T { 4 | if (value != null) { 5 | return value; 6 | } 7 | throw new Error("value is null"); 8 | } 9 | 10 | export function arange(stop: number): number[]; 11 | export function arange(start: number, stop: number): number[]; 12 | export function arange(start: number, stop: number, step: number): number[]; 13 | export function arange(start: number, stop?: number, step = 1): number[] { 14 | if (stop == null) { 15 | const len = start; 16 | const array = new Array(len); 17 | for (let i = 0; i < len; i++) { 18 | array[i] = i; 19 | } 20 | return array; 21 | } else { 22 | const array: number[] = []; 23 | if (step > 0) { 24 | for (let i = start; i < stop; i += step) { 25 | array.push(i); 26 | } 27 | } else { 28 | for (let i = start; i > stop; i += step) { 29 | array.push(i); 30 | } 31 | } 32 | return array; 33 | } 34 | } 35 | 36 | export function arraySum(vec: ArrayLike): number { 37 | let x = 0; 38 | for (let i = 0; i < vec.length; i++) { 39 | x += vec[i]; 40 | } 41 | return x; 42 | } 43 | 44 | export function arrayProd(vec: ArrayLike): number { 45 | let x = 1; 46 | for (let i = 0; i < vec.length; i++) { 47 | x *= vec[i]; 48 | } 49 | return x; 50 | } 51 | 52 | export function arrayEqual( 53 | vec1: ArrayLike, 54 | vec2: ArrayLike 55 | ): boolean { 56 | if (vec1.length !== vec2.length) { 57 | return false; 58 | } 59 | 60 | for (let i = 0; i < vec1.length; i++) { 61 | if (vec1[i] !== vec2[i]) { 62 | return false; 63 | } 64 | } 65 | 66 | return true; 67 | } 68 | 69 | const longPositive32BitMax = new Long(0x7fffffff, 0), 70 | longPositive32BitMin = new Long(0x80000000, 0xffffffff); 71 | 72 | // 符号付きLongを丸めて、-2^31から2^31-1の範囲のnumberを返す 73 | export function clipLong(v: Long): number { 74 | // Long(0xfffffff6, 0xffffffff) => -10 75 | if (v.lessThan(longPositive32BitMin)) { 76 | return -0x80000000; 77 | } else if (v.greaterThan(longPositive32BitMax)) { 78 | return 0x7fffffff; 79 | } 80 | return v.toNumber(); 81 | } 82 | 83 | export function intOrLongToInt(v: number | Long): number { 84 | if (v instanceof Long) { 85 | return clipLong(v); 86 | } 87 | return v; 88 | } 89 | 90 | export function intOrLongToIntVector(v: (number | Long)[]): number[] { 91 | return v.map(intOrLongToInt); 92 | } 93 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mil-tokyo/webdnn/d15df8f60544ff948c5e529d6eb76707b7177ff4/src/graph_transpiler/webdnn/__init__.py -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/onnx_util.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import onnx 3 | 4 | 5 | def get_attr_int(op: onnx.NodeProto, name, default=None): 6 | for attr in op.attribute: 7 | if attr.name == name: 8 | return attr.i 9 | return default 10 | 11 | 12 | def get_attr_ints(op: onnx.NodeProto, name, default=None): 13 | for attr in op.attribute: 14 | if attr.name == name: 15 | return list(attr.ints) 16 | return default 17 | 18 | 19 | def get_attr_float(op: onnx.NodeProto, name, default=None): 20 | for attr in op.attribute: 21 | if attr.name == name: 22 | return attr.f 23 | return default 24 | 25 | 26 | DATA_TYPE_TO_NUMPY = { 27 | onnx.TensorProto.FLOAT: np.float32, # 1 28 | onnx.TensorProto.UINT8: np.uint8, # 2 29 | onnx.TensorProto.INT8: np.int8, # 3 30 | onnx.TensorProto.UINT16: np.uint16, # 4 31 | onnx.TensorProto.INT16: np.int16, # 5 32 | onnx.TensorProto.INT32: np.int32, # 6 33 | onnx.TensorProto.INT64: np.int64, # 7 34 | onnx.TensorProto.BOOL: np.bool, # 9 35 | onnx.TensorProto.FLOAT16: np.float16, # 10 36 | onnx.TensorProto.DOUBLE: np.float64, # 11 37 | onnx.TensorProto.UINT32: np.uint32, # 12 38 | onnx.TensorProto.UINT64: np.uint64, # 13 39 | } 40 | 41 | def tensor_proto_to_numpy(tensor_proto: onnx.TensorProto) -> np.ndarray: 42 | shape = tuple(tensor_proto.dims) 43 | dtype = DATA_TYPE_TO_NUMPY[tensor_proto.data_type] 44 | if tensor_proto.raw_data: 45 | array = np.frombuffer(tensor_proto.raw_data, dtype=dtype) 46 | elif tensor_proto.int64_data: 47 | array = np.array(tensor_proto.int64_data, dtype=dtype) 48 | elif tensor_proto.int32_data: 49 | array = np.array(tensor_proto.int32_data, dtype=dtype) 50 | elif tensor_proto.uint64_data: 51 | array = np.array(tensor_proto.uint64_data, dtype=dtype) 52 | elif tensor_proto.float_data: 53 | array = np.array(tensor_proto.float_data, dtype=dtype) 54 | elif tensor_proto.double_data: 55 | array = np.array(tensor_proto.double_data, dtype=dtype) 56 | array = array.reshape(shape) 57 | 58 | if dtype == np.int64: 59 | array = np.clip(array, -2**31, 2**31-1).astype(np.int32) 60 | elif dtype == np.uint64: 61 | array = np.clip(array, 0, 2**32-1).astype(np.uint32) 62 | return array 63 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/operator_shader.py: -------------------------------------------------------------------------------- 1 | class OperatorShader: 2 | pass 3 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/operator_shader_cpu.py: -------------------------------------------------------------------------------- 1 | from webdnn.operator_shader import OperatorShader 2 | 3 | class OperatorShaderCPU(OperatorShader): 4 | ts_code: str 5 | 6 | def __init__(self, ts_code: str) -> None: 7 | super().__init__() 8 | self.ts_code = ts_code 9 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/operator_shader_wasm.py: -------------------------------------------------------------------------------- 1 | from webdnn.operator_shader import OperatorShader 2 | 3 | class OperatorShaderWasm(OperatorShader): 4 | ts_code: str 5 | shader_name: str 6 | cpp_code: str 7 | 8 | def __init__(self, ts_code: str, shader_name: str, cpp_code: str) -> None: 9 | super().__init__() 10 | self.ts_code = ts_code 11 | self.shader_name = shader_name 12 | self.cpp_code = cpp_code 13 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/operator_shader_webgl.py: -------------------------------------------------------------------------------- 1 | from webdnn.operator_shader import OperatorShader 2 | 3 | class OperatorShaderWebGL(OperatorShader): 4 | ts_code: str 5 | 6 | def __init__(self, ts_code: str) -> None: 7 | super().__init__() 8 | self.ts_code = ts_code 9 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/operator_shader_webgpu.py: -------------------------------------------------------------------------------- 1 | from webdnn.operator_shader import OperatorShader 2 | 3 | class OperatorShaderWebGPU(OperatorShader): 4 | ts_code: str 5 | shader_name: str 6 | glsl_code: str 7 | 8 | def __init__(self, ts_code: str, shader_name: str, glsl_code: str) -> None: 9 | super().__init__() 10 | self.ts_code = ts_code 11 | self.glsl_code = glsl_code 12 | self.shader_name = shader_name 13 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/optimization_pass.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Optional 2 | import numpy as np 3 | import onnx 4 | 5 | from webdnn.operator_shader import OperatorShader 6 | 7 | class OptimizationPassResult: 8 | operator_shaders: Dict[str, OperatorShader] 9 | initializers: Dict[str, np.ndarray] 10 | tensor_move_options: Dict[str, dict] 11 | 12 | def __init__(self) -> None: 13 | self.operator_shaders = {} 14 | self.initializers = {} 15 | self.tensor_move_options = {} 16 | 17 | def merge(self, other: "OptimizationPassResult"): 18 | self.operator_shaders.update(other.operator_shaders) 19 | self.initializers.update(other.initializers) 20 | # TODO: check conflict for same tensor 21 | self.tensor_move_options.update(other.tensor_move_options) 22 | 23 | def write_code(self, root_directory: str): 24 | raise NotImplementedError 25 | 26 | def remove_code(self, root_directory: str): 27 | raise NotImplementedError 28 | 29 | class OptimizationPass: 30 | def optimize(model: onnx.ModelProto) -> Optional[OptimizationPassResult]: 31 | raise NotImplementedError 32 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/optimization_pass_result_cpu.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | from webdnn.optimization_pass import OptimizationPassResult 4 | 5 | class OptimizationPassResultCPU(OptimizationPassResult): 6 | def write_code(self, root_directory: str): 7 | self.remove_code(root_directory) 8 | directory = os.path.join(root_directory, "src/descriptor_runner/operators/cpu/operators/autogen") 9 | for key, s in self.operator_shaders.items(): 10 | with open(os.path.join(directory, f"{key}.ts"), "w", encoding="utf-8", newline="\n") as f: 11 | f.write(s.ts_code) 12 | 13 | def remove_code(self, root_directory: str): 14 | directory = os.path.join(root_directory, "src/descriptor_runner/operators/cpu/operators/autogen") 15 | for path in glob.glob(os.path.join(directory, "*.ts")): 16 | os.remove(path) 17 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/optimization_pass_result_wasm.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | from webdnn.optimization_pass import OptimizationPassResult 4 | 5 | class OptimizationPassResultWasm(OptimizationPassResult): 6 | def write_code(self, root_directory: str): 7 | self.remove_code(root_directory) 8 | directory = os.path.join(root_directory, "src/descriptor_runner/operators/wasm/operators/autogen") 9 | for key, s in self.operator_shaders.items(): 10 | with open(os.path.join(directory, f"{key}.ts"), "w", encoding="utf-8", newline="\n") as f: 11 | f.write(s.ts_code) 12 | directory = os.path.join(root_directory, "src/shader/wasm/src/kernels/autogen") 13 | for key, s in self.operator_shaders.items(): 14 | with open(os.path.join(directory, f"{key}.cpp"), "w", encoding="utf-8", newline="\n") as f: 15 | f.write(s.cpp_code) 16 | 17 | def remove_code(self, root_directory: str): 18 | directory = os.path.join(root_directory, "src/descriptor_runner/operators/wasm/operators/autogen") 19 | for path in glob.glob(os.path.join(directory, "*.ts")): 20 | os.remove(path) 21 | directory = os.path.join(root_directory, "src/shader/wasm/src/kernels/autogen") 22 | for path in glob.glob(os.path.join(directory, "*.cpp")): 23 | os.remove(path) 24 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/optimization_pass_result_webgl.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | from webdnn.optimization_pass import OptimizationPassResult 4 | 5 | class OptimizationPassResultWebGL(OptimizationPassResult): 6 | def write_code(self, root_directory: str): 7 | self.remove_code(root_directory) 8 | directory = os.path.join(root_directory, "src/descriptor_runner/operators/webgl/operators/autogen") 9 | for key, s in self.operator_shaders.items(): 10 | with open(os.path.join(directory, f"{key}.ts"), "w", encoding="utf-8", newline="\n") as f: 11 | f.write(s.ts_code) 12 | 13 | def remove_code(self, root_directory: str): 14 | directory = os.path.join(root_directory, "src/descriptor_runner/operators/webgl/operators/autogen") 15 | for path in glob.glob(os.path.join(directory, "*.ts")): 16 | os.remove(path) 17 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/optimize_model.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import os 4 | from os.path import abspath 5 | import subprocess 6 | import onnx 7 | from webdnn.passes import run_passes 8 | from webdnn.tensor_export import export_initializers 9 | 10 | ALL_BACKENDS = ["webgl2-16384", "webgl2-4096", "webgl1-16384", "webgl1-4096", "wasm", "cpu"] # "webgpu" is not yet supported 11 | SUBPROCESS_SHELL = os.name=='nt' 12 | ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) 13 | 14 | def main(): 15 | parser = argparse.ArgumentParser() 16 | parser.add_argument("onnx_model") 17 | parser.add_argument("dst_dir") 18 | parser.add_argument("--compression", type=int, default=0, help="compression algorithm number (0=no compression)") 19 | parser.add_argument("--backends", default=",".join(ALL_BACKENDS)) 20 | args = parser.parse_args() 21 | backends = args.backends.split(",") 22 | os.makedirs(args.dst_dir, exist_ok=True) 23 | for backend in backends: 24 | src_model = onnx.load_model(args.onnx_model) 25 | optimization_result = run_passes(src_model, backend) 26 | # print(optimization_result) 27 | optimization_result.write_code(ROOT_DIR) 28 | optimized_model = src_model 29 | tensor_pathes = export_initializers(os.path.join(args.dst_dir, f"weight-{backend}-{{}}.bin"), optimized_model, optimization_result.initializers, 4 * 1024 * 1024, args.compression) 30 | weight_paths = ":".join([os.path.basename(tensor_path) for tensor_path in tensor_pathes]) 31 | optimized_model.metadata_props.append(onnx.StringStringEntryProto(key="WebDNN2.WeightPaths", value=weight_paths)) 32 | weight_sizes = ":".join([str(os.stat(tensor_path).st_size) for tensor_path in tensor_pathes]) 33 | optimized_model.metadata_props.append(onnx.StringStringEntryProto(key="WebDNN2.WeightSizes", value=weight_sizes)) 34 | optimized_model.metadata_props.append(onnx.StringStringEntryProto(key="WebDNN2.TensorMoveOptions", value=json.dumps(optimization_result.tensor_move_options))) 35 | onnx.save_model(optimized_model, os.path.join(args.dst_dir, f"model-{backend}.onnx")) 36 | if backend == "wasm": 37 | subprocess.check_call(["yarn", "shader:wasm"], shell=SUBPROCESS_SHELL, cwd=ROOT_DIR) 38 | if backend == "webgpu": 39 | subprocess.check_call(["yarn", "shader:webgpu"], shell=SUBPROCESS_SHELL, cwd=ROOT_DIR) 40 | subprocess.check_call(["yarn", "makeShaderList"], shell=SUBPROCESS_SHELL, cwd=ROOT_DIR) 41 | subprocess.check_call(["yarn", f"build:{backend}", "-o", os.path.abspath(args.dst_dir)], shell=SUBPROCESS_SHELL, cwd=ROOT_DIR) 42 | optimization_result.remove_code(ROOT_DIR) 43 | # reset shader list file (remove autogen entry) 44 | subprocess.check_call(["yarn", "makeShaderList"], shell=SUBPROCESS_SHELL, cwd=ROOT_DIR) 45 | 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/parse_onnx.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import onnx 3 | from webdnn.model import Model, Graph, Variable, Operator, ConstantVariable 4 | 5 | def _parse_node(node: onnx.NodeProto): 6 | pass 7 | 8 | def _parse_graph(graph: onnx.GraphProto) -> Graph: 9 | pass 10 | 11 | def parse_onnx(model: onnx.ModelProto) -> Model: 12 | opset_import_version = model.opset_import[0].version # type: int 13 | graph = _parse_graph(model.graph) 14 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/pass_fusion_unary_cpu.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | import onnx 3 | from webdnn.pass_fusion_unary import PassFusionUnary 4 | from webdnn.operator_shader_cpu import OperatorShaderCPU 5 | from webdnn.optimization_pass_result_cpu import OptimizationPassResultCPU 6 | 7 | SHADER_TEMPLATE = """ 8 | import { DataArrayConstructor, DataType } from "../../../../interface/core/constants"; 9 | import { OperatorImpl } from "../../../operatorImpl"; 10 | import { WebDNNCPUContext } from "../../../../interface/backend/cpu/cpuContext"; 11 | import { Tensor } from "../../../../interface/core/tensor"; 12 | import { OperatorEntry } from "../../../../interface/core/operator"; 13 | 14 | class CPUUnary extends OperatorImpl { 15 | constructor( 16 | private op: (value: number) => number, 17 | private allowDataTypes: DataType[] 18 | ) { 19 | super("cpu"); 20 | } 21 | 22 | async run(context: WebDNNCPUContext, inputs: Tensor[]): Promise { 23 | context.assertsCPUTensorArray(inputs); 24 | const input = inputs[0]; 25 | if (!this.allowDataTypes.includes(input.dataType)) { 26 | throw new Error(`Unary: DataType ${input.dataType} not supported`); 27 | } 28 | const newData = new DataArrayConstructor[input.dataType](input.data.length); 29 | const op = this.op; 30 | for (let i = 0; i < newData.length; i++) { 31 | newData[i] = op(input.data[i]); 32 | } 33 | const output = context.emptyTensor(input.dims, input.dataType, newData); 34 | return [output]; 35 | } 36 | } 37 | 38 | export function getOpEntries(): OperatorEntry[] { 39 | return [ 40 | { 41 | opType: "%%OP_TYPE%%", 42 | backend: "cpu", 43 | opsetMin: 1, 44 | factory: () => new CPUUnary((v0) => {%%FUNC_BODY%%}, ["float32"]), 45 | }, 46 | ]; 47 | } 48 | """ 49 | 50 | FUNC_TEMPLATES = { 51 | "Ceil": "const %%VAR_OUT%% = Math.ceil(%%VAR_IN%%);", 52 | "Exp": "const %%VAR_OUT%% = Math.exp(%%VAR_IN%%);", 53 | "Floor": "const %%VAR_OUT%% = Math.floor(%%VAR_IN%%);", 54 | "Relu": "const %%VAR_OUT%% = Math.max(%%VAR_IN%%, 0);", 55 | "Sigmoid": "const %%VAR_OUT%% = (Math.tanh(%%VAR_IN%% / 2) + 1) / 2;", 56 | "Sqrt": "const %%VAR_OUT%% = Math.sqrt(%%VAR_IN%%);", 57 | "Tanh": "const %%VAR_OUT%% = Math.tanh(%%VAR_IN%%);", 58 | } 59 | 60 | class PassFusionUnaryCPU(PassFusionUnary): 61 | def _make_shader(self, custom_op_type: str, nodes: List[onnx.NodeProto]) -> OperatorShaderCPU: 62 | func_body = "" 63 | for i, node in enumerate(nodes): 64 | tmpl = FUNC_TEMPLATES[node.op_type] 65 | func_body += tmpl.replace("%%VAR_IN%%", f"v{i}").replace("%%VAR_OUT%%", f"v{i+1}") 66 | func_body += f"return v{len(nodes)};" 67 | ts_code = SHADER_TEMPLATE.replace("%%OP_TYPE%%", custom_op_type).replace("%%FUNC_BODY%%", func_body) 68 | return OperatorShaderCPU(ts_code) 69 | 70 | def _construct_result(self): 71 | return OptimizationPassResultCPU() 72 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/passes.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import List 3 | import onnx 4 | from webdnn.pass_fusion_unary_cpu import PassFusionUnaryCPU 5 | 6 | from webdnn.optimization_pass import OptimizationPass, OptimizationPassResult 7 | from webdnn.optimization_pass_result_cpu import OptimizationPassResultCPU 8 | from webdnn.pass_fusion_unary_wasm import PassFusionUnaryWasm 9 | from webdnn.optimization_pass_result_wasm import OptimizationPassResultWasm 10 | from webdnn.pass_fusion_unary_webgl import PassFusionUnaryWebGL 11 | from webdnn.pass_matmul_transpose_webgl2 import PassMatMulTransposeWebGL2 12 | from webdnn.pass_conv_reshape_webgl import PassConvReshapeWebGL 13 | from webdnn.optimization_pass_result_webgl import OptimizationPassResultWebGL 14 | 15 | def make_backend_passes(backend: str) -> List[OptimizationPass]: 16 | if backend == "cpu": 17 | return [PassFusionUnaryCPU()] 18 | elif backend == "wasm": 19 | return [PassFusionUnaryWasm()] 20 | elif backend.startswith("webgl1"): 21 | max_texture_size = int(backend.split("-")[1]) 22 | return [PassFusionUnaryWebGL(), PassConvReshapeWebGL(webgl2=False, max_texture_size=max_texture_size)] 23 | elif backend.startswith("webgl2"): 24 | max_texture_size = int(backend.split("-")[1]) 25 | return [PassFusionUnaryWebGL(), PassConvReshapeWebGL(webgl2=True, max_texture_size=max_texture_size), PassMatMulTransposeWebGL2()] 26 | else: 27 | raise ValueError 28 | 29 | def run_passes(model: onnx.ModelProto, backend: str) -> OptimizationPassResult: 30 | passes = make_backend_passes(backend) 31 | if backend == "cpu": 32 | result_merged = OptimizationPassResultCPU() 33 | elif backend == "wasm": 34 | result_merged = OptimizationPassResultWasm() 35 | elif backend.startswith("webgl"): 36 | result_merged = OptimizationPassResultWebGL() 37 | else: 38 | raise NotImplementedError 39 | for p in passes: 40 | result = p.optimize(model) 41 | if result is not None: 42 | result_merged.merge(result) 43 | return result_merged 44 | -------------------------------------------------------------------------------- /src/graph_transpiler/webdnn/util.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | def make_random_identifier(): 4 | return "_" + str(uuid.uuid4()).replace("-", "") 5 | -------------------------------------------------------------------------------- /src/shader/wasm/compile.py: -------------------------------------------------------------------------------- 1 | """ 2 | compile operator kernels of c++ into wasm, then embed them in single ts file, to distribute single webdnn.js 3 | """ 4 | 5 | import base64 6 | import glob 7 | import os 8 | import subprocess 9 | import sys 10 | 11 | CPP_SRC_DIR = "src" 12 | DST_DIR = "../../descriptor_runner/operators/wasm/worker" 13 | OPTIMIZATION = "-O3" 14 | 15 | # change current directory to where this file is 16 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | # dependency C++ library 19 | if not os.path.exists("./lib/eigen-3.3.9"): 20 | sys.stderr.write(f"downloading eigen library into {os.path.join(os.getcwd(), 'lib')}\n") 21 | os.makedirs("./lib", exist_ok=True) 22 | import urllib.request 23 | import tarfile 24 | thetarfile = "https://gitlab.com/libeigen/eigen/-/archive/3.3.9/eigen-3.3.9.tar.bz2" 25 | ftpstream = urllib.request.urlopen(thetarfile) 26 | thetarfile = tarfile.open(fileobj=ftpstream, mode="r|bz2") 27 | thetarfile.extractall("./lib") 28 | 29 | srcs = glob.glob(CPP_SRC_DIR + "/**/*.cpp", recursive=True) 30 | 31 | subprocess.check_call(["emcc", "-std=c++11", "--pre-js", "pre.js", "-I", "lib/eigen-3.3.9", "-o", f"{DST_DIR}/workerRaw.js", OPTIMIZATION, "-s", "ALLOW_MEMORY_GROWTH=1", *srcs], shell=os.name=='nt') 32 | 33 | # embed wasm into worker js 34 | with open(f"{DST_DIR}/workerRaw.wasm", "rb") as f: 35 | worker_wasm = f.read() 36 | with open(f"{DST_DIR}/workerRaw.js", "rt", encoding="utf-8") as f: 37 | worker_js = f.read() 38 | 39 | worker_js_with_wasm = worker_js.replace("WASM_WORKER_WASM_BINARY_BASE64", base64.b64encode(worker_wasm).decode("ascii")) 40 | worker_js_with_wasm_escaped = worker_js_with_wasm.replace("\\", "\\\\").replace("`", "\\`").replace("$", "\\$") 41 | 42 | worker_data_url_src = f"""/* eslint-disable */ 43 | export const wasmWorkerSrcUrl = URL.createObjectURL(new File([`{worker_js_with_wasm_escaped}`], "worker.js", {{type: "text/javascript"}})); 44 | """ 45 | 46 | with open(f"{DST_DIR}/worker.ts", "wt", encoding="utf-8", newline="\n") as f: 47 | f.write(worker_data_url_src) 48 | -------------------------------------------------------------------------------- /src/shader/wasm/src/common/kernel.hpp: -------------------------------------------------------------------------------- 1 | #ifdef __EMSCRIPTEN__ 2 | #include 3 | #define WEBDNN_KERNEL EMSCRIPTEN_KEEPALIVE 4 | #else 5 | #define WEBDNN_KERNEL 6 | #endif 7 | -------------------------------------------------------------------------------- /src/shader/wasm/src/common/unary.hpp: -------------------------------------------------------------------------------- 1 | template 2 | static void webdnn_unary(const T *src, T *dst, int length, tFunction f) 3 | { 4 | for (int i = 0; i < length; i++) 5 | { 6 | dst[i] = f(src[i]); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/shader/wasm/src/core/allocation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern "C" 5 | { 6 | void* EMSCRIPTEN_KEEPALIVE webdnn_malloc(int byte_length) 7 | { 8 | return malloc((size_t)byte_length); 9 | } 10 | 11 | void EMSCRIPTEN_KEEPALIVE webdnn_free(void *buf) 12 | { 13 | free(buf); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/shader/wasm/src/kernels/autogen/.gitignore: -------------------------------------------------------------------------------- 1 | *.cpp 2 | *.hpp 3 | -------------------------------------------------------------------------------- /src/shader/wasm/src/kernels/custom/.gitignore: -------------------------------------------------------------------------------- 1 | *.cpp 2 | *.hpp 3 | -------------------------------------------------------------------------------- /src/shader/wasm/src/kernels/standard/binary7s.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../../common/kernel.hpp" 4 | #include "../../common/binary7.hpp" 5 | 6 | 7 | extern "C" 8 | { 9 | #define DEFINE_BINARY7(name, func) \ 10 | void WEBDNN_KERNEL kernel_##name##_d0(const float *srcl, const float *srcr, float *dst) { webdnn_binary7_d0(func, srcl, srcr, dst); } \ 11 | void WEBDNN_KERNEL kernel_##name##_d1(const float *srcl, const float *srcr, float *dst, int o0, int isl0, int isr0) { webdnn_binary7_d1(func, srcl, srcr, dst, o0, isl0, isr0); } \ 12 | void WEBDNN_KERNEL kernel_##name##_d2(const float *srcl, const float *srcr, float *dst, int o0, int o1, int isl0, int isl1, int isr0, int isr1) { webdnn_binary7_d2(func, srcl, srcr, dst, o0, o1, isl0, isl1, isr0, isr1); } \ 13 | void WEBDNN_KERNEL kernel_##name##_d3(const float *srcl, const float *srcr, float *dst, int o0, int o1, int o2, int isl0, int isl1, int isl2, int isr0, int isr1, int isr2) { webdnn_binary7_d3(func, srcl, srcr, dst, o0, o1, o2, isl0, isl1, isl2, isr0, isr1, isr2); } \ 14 | void WEBDNN_KERNEL kernel_##name##_d4(const float *srcl, const float *srcr, float *dst, int o0, int o1, int o2, int o3, int isl0, int isl1, int isl2, int isl3, int isr0, int isr1, int isr2, int isr3) { webdnn_binary7_d4(func, srcl, srcr, dst, o0, o1, o2, o3, isl0, isl1, isl2, isl3, isr0, isr1, isr2, isr3); } \ 15 | void WEBDNN_KERNEL kernel_##name##_d5(const float *srcl, const float *srcr, float *dst, int o0, int o1, int o2, int o3, int o4, int isl0, int isl1, int isl2, int isl3, int isl4, int isr0, int isr1, int isr2, int isr3, int isr4) { webdnn_binary7_d5(func, srcl, srcr, dst, o0, o1, o2, o3, o4, isl0, isl1, isl2, isl3, isl4, isr0, isr1, isr2, isr3, isr4); } \ 16 | void WEBDNN_KERNEL kernel_##name##_d6(const float *srcl, const float *srcr, float *dst, int o0, int o1, int o2, int o3, int o4, int o5, int isl0, int isl1, int isl2, int isl3, int isl4, int isl5, int isr0, int isr1, int isr2, int isr3, int isr4, int isr5) { webdnn_binary7_d6(func, srcl, srcr, dst, o0, o1, o2, o3, o4, o5, isl0, isl1, isl2, isl3, isl4, isl5, isr0, isr1, isr2, isr3, isr4, isr5); } 17 | 18 | DEFINE_BINARY7(add, [](float lhs, float rhs) { return lhs + rhs; }); 19 | DEFINE_BINARY7(sub, [](float lhs, float rhs) { return lhs - rhs; }); 20 | DEFINE_BINARY7(mul, [](float lhs, float rhs) { return lhs * rhs; }); 21 | DEFINE_BINARY7(div, [](float lhs, float rhs) { return lhs / rhs; }); 22 | DEFINE_BINARY7(pow, [](float lhs, float rhs) { return pow(lhs, rhs); }); 23 | } 24 | -------------------------------------------------------------------------------- /src/shader/wasm/src/kernels/standard/copy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../../common/kernel.hpp" 3 | 4 | extern "C" 5 | { 6 | void WEBDNN_KERNEL kernel_copy(const float *src, float *dst, int length) { 7 | memcpy(dst, src, length * sizeof(float)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/shader/wasm/src/kernels/standard/dynamic_unarys.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../../common/kernel.hpp" 4 | #include "../../common/unary.hpp" 5 | 6 | extern "C" 7 | { 8 | void WEBDNN_KERNEL kernel_leakyrelu(const float *src, float *dst, int length, float alpha) 9 | { 10 | webdnn_unary(src, dst, length, [alpha](float s) { return s < 0.0f ? s * alpha : s; }); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/shader/wasm/src/kernels/standard/unarys.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../../common/kernel.hpp" 4 | #include "../../common/unary.hpp" 5 | 6 | extern "C" 7 | { 8 | 9 | #define DEFINE_UNARY(name, func) \ 10 | void WEBDNN_KERNEL kernel_##name(const float *src, float *dst, int length) { webdnn_unary(src, dst, length, func); } 11 | DEFINE_UNARY(ceil, [](float s) { return std::ceil(s); }); 12 | DEFINE_UNARY(exp, [](float s) { return std::exp(s); }); 13 | DEFINE_UNARY(floor, [](float s) { return std::floor(s); }); 14 | DEFINE_UNARY(relu, [](float s) { return std::max(s, 0.0f); }); 15 | DEFINE_UNARY(sigmoid, [](float s) { return (std::tanh(s * 0.5f) + 1.0f) * 0.5f; }); 16 | DEFINE_UNARY(sqrt, [](float s) { return std::sqrt(s); }); 17 | DEFINE_UNARY(tanh, [](float s) { return std::tanh(s); }); 18 | } 19 | -------------------------------------------------------------------------------- /src/shader/webgpu/compile.js: -------------------------------------------------------------------------------- 1 | const glslang = require("@webgpu/glslang")(); 2 | 3 | const fs = require("fs"); 4 | 5 | let shaderList = "export const webgpuShaders = {\n"; 6 | 7 | // TODO: support custom/autogen 8 | 9 | const sourcesDir = __dirname + "/shadersources/standard"; 10 | 11 | const files = fs.readdirSync(sourcesDir); 12 | files.forEach((file) => { 13 | if (/^.*\.glsl$/.test(file)) { 14 | const shaderSource = fs.readFileSync(`${sourcesDir}/${file}`, {encoding: "utf-8"}); 15 | const glslShader = glslang.compileGLSL(shaderSource, "compute"); 16 | shaderList += `${file.split(".")[0]}: new Uint32Array([${Array.from(glslShader).toString()}]),\n`; 17 | } 18 | }); 19 | 20 | shaderList += "};"; 21 | 22 | fs.writeFileSync(`${__dirname}/../../descriptor_runner/operators/webgpu/shaders.ts`, shaderList); 23 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/autogen/.gitignore: -------------------------------------------------------------------------------- 1 | *.glsl 2 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/custom/.gitignore: -------------------------------------------------------------------------------- 1 | *.glsl 2 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/standard/binary_broadcast_add_0d.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 4 | 5 | layout(std430, set = 0, binding = 0) readonly buffer arrayA { 6 | float numbers[]; 7 | } array_a; 8 | 9 | layout(std430, set = 0, binding = 1) readonly buffer arrayB { 10 | float numbers[]; 11 | } array_b; 12 | 13 | layout(std430, set = 0, binding = 2) buffer arrayC { 14 | float numbers[]; 15 | } array_c; 16 | 17 | layout(std430, set = 0, binding = 3) readonly buffer Meta { 18 | uint len; 19 | } meta; 20 | 21 | void main() { 22 | uint i = gl_GlobalInvocationID.x; 23 | if (i == 0) { 24 | array_c.numbers[0] = array_a.numbers[0] + array_b.numbers[0]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/standard/binary_broadcast_add_1d.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 4 | 5 | layout(std430, set = 0, binding = 0) readonly buffer arrayA { 6 | float numbers[]; 7 | } array_a; 8 | 9 | layout(std430, set = 0, binding = 1) readonly buffer arrayB { 10 | float numbers[]; 11 | } array_b; 12 | 13 | layout(std430, set = 0, binding = 2) buffer arrayC { 14 | float numbers[]; 15 | } array_c; 16 | 17 | layout(std430, set = 0, binding = 3) readonly buffer Meta { 18 | uint len; 19 | uint outShape0; 20 | uint inAStride0; 21 | uint inBStride0; 22 | } meta; 23 | 24 | void main() { 25 | uint len = meta.len; 26 | uint outShape0 = meta.outShape0; 27 | uint inAStride0 = meta.inAStride0; 28 | uint inBStride0 = meta.inBStride0; 29 | for (uint i = gl_GlobalInvocationID.x; i < len; i += 4096) { 30 | uint dim0 = i; 31 | array_c.numbers[i] = array_a.numbers[dim0 * inAStride0] + array_b.numbers[dim0 * inBStride0]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/standard/binary_broadcast_add_2d.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 4 | 5 | layout(std430, set = 0, binding = 0) readonly buffer arrayA { 6 | float numbers[]; 7 | } array_a; 8 | 9 | layout(std430, set = 0, binding = 1) readonly buffer arrayB { 10 | float numbers[]; 11 | } array_b; 12 | 13 | layout(std430, set = 0, binding = 2) buffer arrayC { 14 | float numbers[]; 15 | } array_c; 16 | 17 | layout(std430, set = 0, binding = 3) readonly buffer Meta { 18 | uint len; 19 | uint outShape0, outShape1; 20 | uint inAStride0, inAStride1; 21 | uint inBStride0, inBStride1; 22 | } meta; 23 | 24 | void main() { 25 | uint len = meta.len; 26 | uint outShape0 = meta.outShape0, outShape1 = meta.outShape1; 27 | uint inAStride0 = meta.inAStride0, inAStride1 = meta.inAStride1; 28 | uint inBStride0 = meta.inBStride0, inBStride1 = meta.inBStride1; 29 | for (uint i = gl_GlobalInvocationID.x; i < len; i += 4096) { 30 | uint dim1 = i % outShape1; 31 | uint dim0 = i / outShape1; 32 | array_c.numbers[i] = array_a.numbers[dim0 * inAStride0 + dim1 * inAStride1] + array_b.numbers[dim0 * inBStride0 + dim1 * inBStride1]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/standard/binary_broadcast_add_3d.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 4 | 5 | layout(std430, set = 0, binding = 0) readonly buffer arrayA { 6 | float numbers[]; 7 | } array_a; 8 | 9 | layout(std430, set = 0, binding = 1) readonly buffer arrayB { 10 | float numbers[]; 11 | } array_b; 12 | 13 | layout(std430, set = 0, binding = 2) buffer arrayC { 14 | float numbers[]; 15 | } array_c; 16 | 17 | layout(std430, set = 0, binding = 3) readonly buffer Meta { 18 | uint len; 19 | uint outShape0, outShape1, outShape2; 20 | uint inAStride0, inAStride1, inAStride2; 21 | uint inBStride0, inBStride1, inBStride2; 22 | } meta; 23 | 24 | void main() { 25 | uint len = meta.len; 26 | uint outShape0 = meta.outShape0, outShape1 = meta.outShape1, outShape2 = meta.outShape2; 27 | uint inAStride0 = meta.inAStride0, inAStride1 = meta.inAStride1, inAStride2 = meta.inAStride2; 28 | uint inBStride0 = meta.inBStride0, inBStride1 = meta.inBStride1, inBStride2 = meta.inBStride2; 29 | for (uint i = gl_GlobalInvocationID.x; i < len; i += 4096) { 30 | uint dim2 = i % outShape2; 31 | uint dim1 = i / outShape2; 32 | uint dim0 = dim1 / outShape1; 33 | dim1 = dim1 % outShape1; 34 | array_c.numbers[i] = array_a.numbers[dim0 * inAStride0 + dim1 * inAStride1 + dim2 * inAStride2] + array_b.numbers[dim0 * inBStride0 + dim1 * inBStride1 + dim2 * inBStride2]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/standard/binary_broadcast_add_4d.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 4 | 5 | layout(std430, set = 0, binding = 0) readonly buffer arrayA { 6 | float numbers[]; 7 | } array_a; 8 | 9 | layout(std430, set = 0, binding = 1) readonly buffer arrayB { 10 | float numbers[]; 11 | } array_b; 12 | 13 | layout(std430, set = 0, binding = 2) buffer arrayC { 14 | float numbers[]; 15 | } array_c; 16 | 17 | layout(std430, set = 0, binding = 3) readonly buffer Meta { 18 | uint len; 19 | uint outShape0, outShape1, outShape2, outShape3; 20 | uint inAStride0, inAStride1, inAStride2, inAStride3; 21 | uint inBStride0, inBStride1, inBStride2, inBStride3; 22 | } meta; 23 | 24 | void main() { 25 | uint len = meta.len; 26 | uint outShape0 = meta.outShape0, outShape1 = meta.outShape1, outShape2 = meta.outShape2, outShape3 = meta.outShape3; 27 | uint inAStride0 = meta.inAStride0, inAStride1 = meta.inAStride1, inAStride2 = meta.inAStride2, inAStride3 = meta.inAStride3; 28 | uint inBStride0 = meta.inBStride0, inBStride1 = meta.inBStride1, inBStride2 = meta.inBStride2, inBStride3 = meta.inBStride3; 29 | for (uint i = gl_GlobalInvocationID.x; i < len; i += 4096) { 30 | uint dim3 = i % outShape3; 31 | uint dim2 = i / outShape3; 32 | uint dim1 = dim2 / outShape2; 33 | dim2 = dim2 % outShape2; 34 | uint dim0 = dim1 / outShape1; 35 | dim1 = dim1 % outShape1; 36 | array_c.numbers[i] = array_a.numbers[dim0 * inAStride0 + dim1 * inAStride1 + dim2 * inAStride2 + dim3 * inAStride3] + array_b.numbers[dim0 * inBStride0 + dim1 * inBStride1 + dim2 * inBStride2 + dim3 * inBStride3]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/standard/binary_elementwise_add.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 4 | 5 | layout(std430, set = 0, binding = 0) readonly buffer arrayA { 6 | float numbers[]; 7 | } array_a; 8 | 9 | layout(std430, set = 0, binding = 1) readonly buffer arrayB { 10 | float numbers[]; 11 | } array_b; 12 | 13 | layout(std430, set = 0, binding = 2) buffer arrayC { 14 | float numbers[]; 15 | } array_c; 16 | 17 | layout(std430, set = 0, binding = 3) readonly buffer Meta { 18 | uint len; 19 | } meta; 20 | 21 | void main() { 22 | uint len = meta.len; 23 | for (uint i = gl_GlobalInvocationID.x; i < len; i += 4096) { 24 | array_c.numbers[i] = array_a.numbers[i] + array_b.numbers[i]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/standard/conv_bias.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 4 | 5 | layout(std430, set = 0, binding = 0) readonly buffer arrayI { 6 | float numbers[]; 7 | } array_i; 8 | 9 | layout(std430, set = 0, binding = 1) readonly buffer arrayB { 10 | float numbers[]; 11 | } array_b; 12 | 13 | layout(std430, set = 0, binding = 2) buffer arrayO { 14 | float numbers[]; 15 | } array_o; 16 | 17 | layout(std430, set = 0, binding = 3) readonly buffer arrayMeta { 18 | int batch, chOut, outarea; 19 | } meta; 20 | 21 | void main() { 22 | int batch = meta.batch, chOut = meta.chOut, outarea = meta.outarea; 23 | int total = batch * chOut * outarea; 24 | for (int idx = int(gl_GlobalInvocationID.x); idx < total; idx += 4096) { 25 | int x = idx % outarea; 26 | int c = idx / outarea; 27 | // int b = c / chOut; 28 | c = c % chOut; 29 | array_o.numbers[idx] = array_i.numbers[idx] + array_b.numbers[c]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/standard/conv_im2col.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 4 | 5 | layout(std430, set = 0, binding = 0) readonly buffer arrayX { 6 | float numbers[]; 7 | } array_x; 8 | 9 | layout(std430, set = 0, binding = 1) buffer arrayY { 10 | float numbers[]; 11 | } array_y; 12 | 13 | layout(std430, set = 0, binding = 2) readonly buffer arrayMeta { 14 | int group, batch, outShape0, outShape1, chInPerGroup, kernelShape0, kernelShape1; 15 | int strides0, strides1, pads0, pads1, dilations0, dilations1; 16 | int inShape0, inShape1; 17 | int chIn; 18 | } meta; 19 | 20 | void main() { 21 | int group = meta.group, batch = meta.batch, outShape0 = meta.outShape0, outShape1 = meta.outShape1, chInPerGroup = meta.chInPerGroup, kernelShape0 = meta.kernelShape0, kernelShape1 = meta.kernelShape1; 22 | int strides0 = meta.strides0, strides1 = meta.strides1, pads0 = meta.pads0, pads1 = meta.pads1, dilations0 = meta.dilations0, dilations1 = meta.dilations1; 23 | int inShape0 = meta.inShape0, inShape1 = meta.inShape1; 24 | int chIn = meta.chIn; 25 | int total = group * batch * outShape0 * outShape1 * chInPerGroup * kernelShape0 * kernelShape1; 26 | for (int idx = int(gl_GlobalInvocationID.x); idx < total; idx += 4096) { 27 | int ky = idx / kernelShape1; 28 | int kx = idx % kernelShape1; 29 | int ci = ky / kernelShape0; 30 | ky = ky % kernelShape0; 31 | int ox = ci / chInPerGroup; 32 | ci = ci % chInPerGroup; 33 | int oy = ox / outShape1; 34 | ox = ox % outShape1; 35 | int b = oy / outShape0; 36 | oy = oy % outShape0; 37 | int g = b / batch; 38 | b = b % batch; 39 | 40 | int iny = oy * strides0 - pads0 + ky * dilations0; 41 | int inx = ox * strides1 - pads1 + kx * dilations1; 42 | float v; 43 | if (iny >= 0 && iny < inShape0 && inx >= 0 && inx < inShape1) { 44 | v = array_x.numbers[((b * chIn + g * chInPerGroup + ci) * inShape0 + iny) * inShape1 + inx]; 45 | } else { 46 | v = 0.0; 47 | } 48 | array_y.numbers[idx] = v; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/standard/conv_matmul.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 4 | 5 | layout(std430, set = 0, binding = 0) readonly buffer arrayI { 6 | float numbers[]; 7 | } array_i; 8 | 9 | layout(std430, set = 0, binding = 1) readonly buffer arrayW { 10 | float numbers[]; 11 | } array_w; 12 | 13 | layout(std430, set = 0, binding = 2) buffer arrayT { 14 | float numbers[]; 15 | } array_t; 16 | 17 | layout(std430, set = 0, binding = 3) readonly buffer arrayMeta { 18 | int group, bout, chOutPerGroup, cinkhkw; 19 | } meta; 20 | 21 | void main() { 22 | int group = meta.group, bout = meta.bout, chOutPerGroup = meta.chOutPerGroup, cinkhkw = meta.cinkhkw; 23 | int total = group * bout * chOutPerGroup; 24 | for (int idx = int(gl_GlobalInvocationID.x); idx < total; idx += 4096) { 25 | int x = idx % chOutPerGroup; 26 | int y = idx / chOutPerGroup; 27 | int g = y / bout; 28 | y = y % bout; 29 | float s = 0.0; 30 | for (int ip = 0; ip < cinkhkw; ip++) { 31 | s += array_i.numbers[(g * bout + y) * cinkhkw + ip] * array_w.numbers[(g * chOutPerGroup + x) * cinkhkw + ip]; 32 | } 33 | array_t.numbers[idx] = s; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/standard/conv_transpose.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 4 | 5 | layout(std430, set = 0, binding = 0) readonly buffer arrayT { 6 | float numbers[]; 7 | } array_t; 8 | 9 | layout(std430, set = 0, binding = 1) buffer arrayO { 10 | float numbers[]; 11 | } array_o; 12 | 13 | layout(std430, set = 0, binding = 2) readonly buffer arrayMeta { 14 | int group, batch, outarea, chOutPerGroup; 15 | } meta; 16 | 17 | void main() { 18 | int group = meta.group, batch = meta.batch, outarea = meta.outarea, chOutPerGroup = meta.chOutPerGroup; 19 | int total = group * batch * chOutPerGroup * outarea; 20 | for (int idx = int(gl_GlobalInvocationID.x); idx < total; idx += 4096) { 21 | int x = idx % outarea; 22 | int c = idx / outarea; 23 | int g = c / chOutPerGroup; 24 | c = c % chOutPerGroup; 25 | int b = g / group; 26 | g = g % group; 27 | array_o.numbers[idx] = array_t.numbers[((g * batch + b) * outarea + x) * chOutPerGroup + c]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/shader/webgpu/shadersources/standard/gemm.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 4 | 5 | layout(std430, set = 0, binding = 0) readonly buffer arrayA { 6 | float numbers[]; 7 | } array_a; 8 | 9 | layout(std430, set = 0, binding = 1) readonly buffer arrayB { 10 | float numbers[]; 11 | } array_b; 12 | 13 | layout(std430, set = 0, binding = 2) readonly buffer arrayC { 14 | float numbers[]; 15 | } array_c; 16 | 17 | layout(std430, set = 0, binding = 3) buffer arrayY { 18 | float numbers[]; 19 | } array_y; 20 | 21 | layout(std430, set = 0, binding = 4) readonly buffer arrayMeta { 22 | uint M; 23 | uint N; 24 | uint K; 25 | uint strideA0, strideA1; 26 | uint strideB0, strideB1; 27 | uint strideC0, strideC1; 28 | float alpha; 29 | float beta; 30 | } meta; 31 | 32 | void main() { 33 | uint M = meta.M, N = meta.N, K = meta.K; 34 | uint strideA0 = meta.strideA0, strideA1 = meta.strideA1, strideB0 = meta.strideB0, strideB1 = meta.strideB1, strideC0 = meta.strideC0, strideC1 = meta.strideC1; 35 | float alpha = meta.alpha, beta = meta.beta; 36 | for (uint x = uint(gl_GlobalInvocationID.x); x < N; x+=256) { 37 | for (uint y = uint(gl_GlobalInvocationID.y); y < M; y+=256) { 38 | float sum = 0.0; 39 | for(uint k=0;k 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 21 | 22 | 23 |

WebDNN Operator Test (Optimized Model)

24 |
25 | 26 |
27 |
28 | 31 | 34 | 35 | 36 |
37 |
38 | 39 | -------------------------------------------------------------------------------- /test/model_test/runner/standard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 21 | 22 | 23 |

WebDNN Operator Test (Standard ONNX Model)

24 |
25 | 26 |
27 |
28 | 31 | 34 | 35 | 36 |
37 |
38 | 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "strict": true, 6 | "skipLibCheck": true, 7 | "declaration": true, 8 | "pretty": true, 9 | "esModuleInterop": true, 10 | "newLine": "lf", 11 | "outDir": "dist", 12 | "lib": [ 13 | "es2019", 14 | "dom" 15 | ], 16 | "typeRoots": ["./node_modules/@webgpu/types", "./node_modules/@types"] 17 | }, 18 | "include": [ 19 | "src/descriptor_runner" 20 | ] 21 | } -------------------------------------------------------------------------------- /webpack-core.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'development', 3 | entry: './src/descriptor_runner/separateBuild/coreOnly.ts', 4 | 5 | output: { 6 | filename: 'webdnn-core.js', 7 | path: __dirname + '/dist', 8 | library: 'WebDNN', 9 | libraryTarget: 'var' 10 | }, 11 | 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.ts$/, 16 | use: 'ts-loader' 17 | } 18 | ] 19 | }, 20 | resolve: { 21 | extensions: [ 22 | '.ts', 23 | '.js' 24 | ] 25 | } 26 | }; -------------------------------------------------------------------------------- /webpack-cpu.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'development', 3 | entry: './src/descriptor_runner/separateBuild/operatorCPU.ts', 4 | 5 | output: { 6 | filename: 'op-cpu.js', 7 | path: __dirname + '/dist', 8 | }, 9 | 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.ts$/, 14 | use: 'ts-loader' 15 | } 16 | ] 17 | }, 18 | resolve: { 19 | extensions: [ 20 | '.ts', 21 | '.js' 22 | ] 23 | } 24 | }; -------------------------------------------------------------------------------- /webpack-wasm.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'development', 3 | entry: './src/descriptor_runner/separateBuild/operatorWasm.ts', 4 | 5 | output: { 6 | filename: 'op-wasm.js', 7 | path: __dirname + '/dist', 8 | }, 9 | 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.ts$/, 14 | use: 'ts-loader' 15 | } 16 | ] 17 | }, 18 | resolve: { 19 | extensions: [ 20 | '.ts', 21 | '.js' 22 | ] 23 | } 24 | }; -------------------------------------------------------------------------------- /webpack-webgl1-16384.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'development', 3 | entry: './src/descriptor_runner/separateBuild/operatorWebGL.ts', 4 | 5 | output: { 6 | filename: 'op-webgl1-16384.js', 7 | path: __dirname + '/dist', 8 | }, 9 | 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.ts$/, 14 | use: 'ts-loader' 15 | } 16 | ] 17 | }, 18 | resolve: { 19 | extensions: [ 20 | '.ts', 21 | '.js' 22 | ] 23 | } 24 | }; 25 | // Currently, webgl1 and webgl2 has same operator 26 | // source codes and dynamically branching in each operator. 27 | // This may change in the future. 28 | -------------------------------------------------------------------------------- /webpack-webgl1-4096.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'development', 3 | entry: './src/descriptor_runner/separateBuild/operatorWebGL.ts', 4 | 5 | output: { 6 | filename: 'op-webgl1-4096.js', 7 | path: __dirname + '/dist', 8 | }, 9 | 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.ts$/, 14 | use: 'ts-loader' 15 | } 16 | ] 17 | }, 18 | resolve: { 19 | extensions: [ 20 | '.ts', 21 | '.js' 22 | ] 23 | } 24 | }; 25 | // Currently, webgl1 and webgl2 has same operator 26 | // source codes and dynamically branching in each operator. 27 | // This may change in the future. 28 | -------------------------------------------------------------------------------- /webpack-webgl2-16384.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'development', 3 | entry: './src/descriptor_runner/separateBuild/operatorWebGL.ts', 4 | 5 | output: { 6 | filename: 'op-webgl2-16384.js', 7 | path: __dirname + '/dist', 8 | }, 9 | 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.ts$/, 14 | use: 'ts-loader' 15 | } 16 | ] 17 | }, 18 | resolve: { 19 | extensions: [ 20 | '.ts', 21 | '.js' 22 | ] 23 | } 24 | }; 25 | // Currently, webgl1 and webgl2 has same operator 26 | // source codes and dynamically branching in each operator. 27 | // This may change in the future. 28 | -------------------------------------------------------------------------------- /webpack-webgl2-4096.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'development', 3 | entry: './src/descriptor_runner/separateBuild/operatorWebGL.ts', 4 | 5 | output: { 6 | filename: 'op-webgl2-4096.js', 7 | path: __dirname + '/dist', 8 | }, 9 | 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.ts$/, 14 | use: 'ts-loader' 15 | } 16 | ] 17 | }, 18 | resolve: { 19 | extensions: [ 20 | '.ts', 21 | '.js' 22 | ] 23 | } 24 | }; 25 | // Currently, webgl1 and webgl2 has same operator 26 | // source codes and dynamically branching in each operator. 27 | // This may change in the future. 28 | -------------------------------------------------------------------------------- /webpack-webgpu.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'development', 3 | entry: './src/descriptor_runner/separateBuild/operatorWebGPU.ts', 4 | 5 | output: { 6 | filename: 'op-webgpu.js', 7 | path: __dirname + '/dist', 8 | }, 9 | 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.ts$/, 14 | use: 'ts-loader' 15 | } 16 | ] 17 | }, 18 | resolve: { 19 | extensions: [ 20 | '.ts', 21 | '.js' 22 | ] 23 | } 24 | }; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'development', 3 | entry: './src/descriptor_runner/index.ts', 4 | 5 | output: { 6 | filename: 'webdnn.js', 7 | path: __dirname + '/dist', 8 | library: 'WebDNN', 9 | libraryTarget: 'var' 10 | }, 11 | 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.ts$/, 16 | use: 'ts-loader' 17 | } 18 | ] 19 | }, 20 | resolve: { 21 | extensions: [ 22 | '.ts', 23 | '.js' 24 | ] 25 | } 26 | }; --------------------------------------------------------------------------------