diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..e291365a9d43c9af7a09f3922d7a84fd082a6bfd --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..7a40821bffd24aca22db5f84963bc19498e91830 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,78 @@ +# Referenced from https://github.com/github/gitignore/blob/master/Node.gitignore + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +# other stuff +.DS_Store +Thumbs.db + +# IDE configurations +.idea +.vscode + +# build assets +/output +/dist +/dll +.cache +package-lock.json diff --git a/web/.npmrc b/web/.npmrc new file mode 100644 index 0000000000000000000000000000000000000000..b291ce91efdd1f7a2ebc02f637e3f14d2263c233 --- /dev/null +++ b/web/.npmrc @@ -0,0 +1 @@ +registry=http://registry.npm.baidu-int.com diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4922135af076577e064451d93a1f37a8f8cc3dd9 --- /dev/null +++ b/web/README.md @@ -0,0 +1,16 @@ +# paddle-web-demo 前端机器学习框架 + +[icode地址](http://icode.baidu.com/repos/baidu/mms/paddle-web-demo/tree/master) + +## get start + +## 编译编译 + +本地环境已安装node + +```bash +# 安装编译依赖 +npm i +# 本地编译部署 +npm run server +``` diff --git a/web/ci.yml b/web/ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..a78e80a1a56bcecd67128fc96194b6b0ed77a7ee --- /dev/null +++ b/web/ci.yml @@ -0,0 +1,19 @@ +Global: + tool: build_submitter + +Default: + profile: [buildProduction] + +Profiles: + + - profile: + name: buildProduction + env: cmc_standard + command: export NODE_ENV=production && sh scripts/build.sh + release: true + + - profile: + name: buildDevelopment + env: cmc_standard + command: export NODE_ENV=development && sh scripts/build.sh + release: true diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000000000000000000000000000000000000..42d6b11941734269f5e4c7f37b60f71be5fda3b4 --- /dev/null +++ b/web/package.json @@ -0,0 +1,35 @@ +{ + "name": "paddle-web-demo", + "version": "1.0.0", + "description": "paddle", + "main": "index.js", + "scripts": { + "server": "parcel ./src/index.html", + "testDemo": "parcel ./demo/index.html", + "unit": "parcel ./test/unitTest.html", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "ssh://yangmingming@icode.baidu.com:8235/baidu/mms/paddle-web-demo" + }, + "devDependencies": { + "babel-core": "^6.26.3", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-decorators-legacy": "^1.3.5", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-polyfill": "^6.26.0", + "babel-preset-env": "^1.7.0", + "babel-preset-react": "^6.24.1", + "babel-preset-stage-0": "^6.24.1", + "babel-runtime": "^6.26.0", + "parcel-bundler": "^1.10.3", + "axios": "^0.17.1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "js-file-download": "^0.4.5" + } +} diff --git a/web/scripts/build.sh b/web/scripts/build.sh new file mode 100644 index 0000000000000000000000000000000000000000..1b6bb68b12517bf9bc7b268b4f97a6f748b8d8a1 --- /dev/null +++ b/web/scripts/build.sh @@ -0,0 +1,8 @@ +export PATH=$NODEJS_BIN_LATEST:$PATH + +echo "node: $(node -v)" +echo "npm: v$(npm -v)" + +npm install + +npm run build diff --git a/web/src/banana.jpeg b/web/src/banana.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..36db8e726d55f53f6767ff114208c5184f7da907 Binary files /dev/null and b/web/src/banana.jpeg differ diff --git a/web/src/executor/executor.es6 b/web/src/executor/executor.es6 new file mode 100644 index 0000000000000000000000000000000000000000..dc9b689f1017575c92bff2b04d156a35f32b2cac --- /dev/null +++ b/web/src/executor/executor.es6 @@ -0,0 +1,103 @@ +/* eslint-disable */ +/** + * @file GraphExecutor,封装可执行单元 + * @author wangqun@baidu.com + */ +// const fileDownload = require('js-file-download'); +let start; +export default class GraphExecutor { + + constructor(model) { + this.inputs = model.inputs; + this.outputs = model.outputs; + this.attrs = model.attrs; + this.type = model.type; + this.finish = false; + this.next = null; + this.opData = null; + this.id = +new Date() + model.type + Math.floor(Math.random() * 10 + 1) + model.idx; + } + + get inputsName() { + + if (this.type === 'feed') { + return this.inputs.X; + } + else if (this.type === 'batchnorm' || this.type === 'batch_norm') { + return this.inputs.X; + } + else if (this.type === 'conv2d') { + return this.inputs.Input; + } + else if (this.type === 'depthwise_conv2d') { + return this.inputs.Input; + } + else if (this.type === 'elementwise_add') { + return this.inputs.X; + } + else if (this.type === 'relu' || this.type === 'leaky_relu') { + return this.inputs.X; + } + else if (this.type === 'pool2d') { + return this.inputs.X; + } + else if (this.type === 'mul') { + return this.inputs.X; + } + else if (this.type === 'softmax') { + return this.inputs.X; + } + else if (this.type === 'scale') { + return this.inputs.X; + } + else if (this.type === 'fetch') { + return this.inputs.X; + } + return null; + } + + get outputsName() { + if (this.type === 'conv2d') { + return this.outputs.Output; + } + else if (this.type === 'depthwise_conv2d') { + return this.outputs.Output; + } + else if (this.type === 'batchnorm' || this.type === 'batch_norm') { + this.outputs.out = this.outputs.Y; + return this.outputs.Y; + } + else { + return this.outputs.Out; + } + + } + + /** + * 将输入数据和具体op进行关联,触发执行具体每一个op + * @param inputs + * @param runtime + */ + execute(runtime) { + // console.log(inputs, outputs); + if (this.type !== 'feed') { + let time = +Date.now(); + runtime.run(this.type, this.opData); + // if (runtime.gpu.frameBufferIsComplete().isComplete) { + // var result = runtime.read(); + // let res = Array.prototype.slice.call(result); + // fileDownload(res, "result.csv"); + // } + let length = statistic.length; + statistic[length - 1].type = this.type; + statistic[length - 1].runTime = +Date.now() - time; + // if (this.type === 'scale') { + // console.log('时间是:' + (+Date.now() - start)); + // } + } else { + start = +Date.now(); + } + } +} + +/* eslint-enable */ diff --git a/web/src/executor/loader.es6 b/web/src/executor/loader.es6 new file mode 100644 index 0000000000000000000000000000000000000000..de7341aa8050b4ee90ff135bd9b9f1b95df8f333 --- /dev/null +++ b/web/src/executor/loader.es6 @@ -0,0 +1,403 @@ +/* eslint-disable */ +import GraphExecutor from './executor'; +import IO from '../feed/imageFeed'; +import Runtime from '../../src/runtime/runtime'; +import OpData from '../utils/opData'; +import Factory from '../factory/fshader/factory'; +import Utils from '../utils/utils'; +/** + * @file GraphModel,绘制生成model网络 + * @author wangqun@baidu.com + */ +// 生成factory实例 +const factory = new Factory({}); +// 获取op的输入配置 +const opConfs = factory.getOpConfs(); +export default class GraphModel { + constructor(modelGonfig, loadOptions) { + this.version = '0.0.1'; + this.handler = 'io.IOHandler'; + this.modelGonfig = modelGonfig; + this.loadOptions = loadOptions; + this.multipart = false; + // feed数据 + this.feed = null; + this.index = 0; + this.feedOp = null; + this.feedItem = null; + this.isExecuted = false; + // fetch xhr jsonp + this.params = {type: 'fetch'}; + // 设置分片加载model + if (this.loadOptions) { + this.multipart = this.loadOptions.multipart; + this.feed = {input: this.loadOptions.feed}; + if (loadOptions.dataType === 'binary') { + this.binaryOption = loadOptions.binaryOption; + } + } + // op runner + this.inst = Runtime.init({ + 'width_raw_canvas': 512, + 'height_raw_canvas': 512 + }); + if (this.loadOptions === null) { + this.loadOptions = {}; + } + } + fetchOneChunk(path) { + console.time(path) + return fetch(path).then(request => { + console.timeEnd(path); + return request.arrayBuffer(); + }) + } + fetchAllData() { + // todo 兼容一下json的模式 + let counts = this.binaryOption.fileCount; + let chunkArray = []; + for (let i = 1; i <= counts; i++) { + chunkArray.push( + this.fetchOneChunk(this.modelGonfig.dir + this.binaryOption.getFileName(i)) + ); + } + // 1个文件 + // let chunkArray = [this.fetchOneChunk('/faceModel/mergedData.dat')]; + console.time('加载时间'); + return Promise.all(chunkArray).then(chunks => { + console.timeEnd('加载时间'); + let chunksLength = 0; + let f32Array = []; + let float32Chunk; + chunks.forEach(i => { + float32Chunk = new Float32Array(i); + f32Array.push(float32Chunk); + chunksLength += float32Chunk.length; + }); + this.allData = new Float32Array(chunksLength); + let offset = 0; + f32Array.forEach(i => { + i.forEach(num => { + this.allData[offset] = num; + offset += 1; + }) + }); + }); + } + traverse (arr) { + const TMP_SCHEME_REGEX = /\.tmp/; + const TMP_REGEX = /\-/; + let marker = 0; // 读到哪个位置了 + let len; // 当前op长度 + arr.filter(item => { + return item.name + && item.name.match(TMP_SCHEME_REGEX) === null + && item.name.match(TMP_REGEX) === null; + }) + // .sort((a, b) => { + // if (a.name > b.name) { + // return 1; + // } + // if (a.name < b.name) { + // return -1; + // } + // return 0; + // }) // 按字母顺序排列 在model.json里 + .forEach(item => { + len = item.shape.reduce((a, b) => a * b); // 长度为shape的乘积 + item.data = this.allData.slice(marker, marker + len); + marker += len; + }); + } + fetchModel(params) { + params = params || this.params; + const path = this.modelGonfig.dir + this.modelGonfig.main; + let URL_SCHEME_REGEX = /^https?:\/\//; + let load = null; + let method = params.method || 'get'; + let mode = params.mode || 'cors'; + // jsonp请求方式 + if (params && params.type === 'jsonp') { + let json; + let s = document.createElement('script'); + s.src = path + '&jsonpCallback=fn'; + window.fn = function(data) { + json = data; + // console.log(json); + }; + //当script被插入文档中时,src中的资源就会开始加载 + document.body.appendChild(s); + load = new Promise((resolve, reject) => { + s.onload = function(e) { + resolve(json); + } + s.onerror = function() { + reject(json); + } + }); + this.handler = load; + } + // 原生fetch + else if (params.type === 'fetch') { + let myHeaders = new Headers(); + load = new Promise((resolve, reject) => { + fetch(path, { + method: method, + mode: mode, + credentials: "include", + headers: myHeaders + }) + .then(response => response.json()) + .then(responseData => resolve(responseData)) + .then(err => reject(err)) + }); + this.handler = load; + } + // ajax + else if (params.type === 'xhr') { + this.handler = load; + } + return load; + } + async load() { + let that = this; + console.time('生成op数据之前') + console.time('fetchModel'); + const artifacts = this.handler = await this.fetchModel(); + console.timeEnd('fetchModel'); + if (this.multipart === true) { + console.time('6个文件准备好op数据'); + await this.fetchAllData() + .then(() => this.traverse(artifacts.vars)); + console.timeEnd('6个文件准备好op数据'); + } + console.time('createOpsMap'); + const opsMap = this.createOpsMap(artifacts.ops, artifacts.vars); + console.timeEnd('createOpsMap'); + console.time('constructOpsMap'); + this.weightMap = this.constructOpsMap(opsMap); + console.timeEnd('constructOpsMap'); + console.timeEnd('生成op数据之前') + // 生成op数据 + this.weightMap.forEach(op => { + const type = op.type; + if (type !== 'feed' && type !== 'fetch') { + that.buildOpData(op); + } + }); + return true; + } + buildOpData(op) { + const tensor = this.constructTensor(op); + const opData = new OpData(op.type, tensor.inputs, tensor.outputs, tensor.attrs); + const name = opData.name; + const fsCode = factory.buildShader(name, opData.data); + opData.fshader = this.inst.createFragmentShader(fsCode); + opData.renderData = opConfs[name].map(elem => { + let item = Object.assign({}, elem); + const tensorData = opData.tensor[item.tensor]; + if (item.type === 'texture') { + item.data = tensorData.data; + if (this.feedOp.id === op.id) { + item.shape = tensorData.shape; + this.feedItem = item; + } + item['width_texture'] = tensorData['width_texture']; + item['height_texture'] = tensorData['height_texture']; + } else if (item.type === 'uniform') { + item.data = tensorData[item.variable]; + } + return item; + }); + op.opData = opData; + // delete op.inputs; + // delete op.outputs; + // delete op.attrs; + } + execute_(executor) { + if (executor.type === 'fetch') { + return; + } + executor.execute(this.inst); + if (executor.next) { + const id = executor.next; + const next = this.getTensor(id); + this.execute_(next[0]) + } + } + /** + * Executes inference for the model for given input tensors. + * @param inputs + * @param outputs + * @returns {*} + */ + execute(inputs) { + this.feed = inputs; + const executor = this.getNetsStart(this.weightMap); + if (!this.inst) { + this.inst = Runtime.init({ + 'width_raw_canvas': 512, + 'height_raw_canvas': 512 + }); + } + if (this.isExecuted) { + this.updateFeed(); + } + let start = +Date.now(); + this.execute_(executor[0]); + console.log('总的执行时间是' + (+Date.now() - start)); + this.isExecuted = true; + return this.inst; + } + updateFeed() { + this.feedItem.data = this.feed.input[0].data; + Utils.img2texture(this.feedItem); + } + /** + * predict enter + * @param inputs + * @param config + */ + predict(inputs, config) { + return this.execute_(inputs, true, this.outputNodes); + } + getTensorAttr(name) { + return this.handler.vars.filter((item, i) => { + if (name === item.name) + return item; + }); + } + constructTensor(executor) { + const that = this; + const inputName = executor.inputsName[0]; + const input = executor.inputs; + const output = executor.outputs; + Object.keys(output).forEach(function(key){ + output[key] = that.getTensorAttr(output[key][0]); + }); + Object.keys(input).forEach(function(key){ + if ((key === 'Input') && (inputName === 'pixel')) { + const pixel = that.getTensorAttr(inputName); + const io = new IO(); + input[key] = io.fromPixels(data, pixel); + } + else if ((key === 'Input') && (inputName === 'image' || inputName === 'x')) { + input[key] = that.feed.input; + that.feedOp = executor; + } + else { + input[key] = that.getTensorAttr(input[key][0]); + } + }); + const tensor = { + inputs: input, + outputs: output, + attrs: executor.attrs, + type: executor.type, + next: executor.next + }; + return tensor; + } + /** + * Construct Ops Relationship + * @param ops + * @returns {*} + */ + constructOpsMap(ops) { + return ops.map((item, idx) => { + const outputsName = item.outputsName[0]; + const next = this.getNextExecutor(ops, outputsName); + if (next.length > 0) { + item.next = next[0].id; + } + return item; + }); + } + /** + * Get Ops Nets Start Node + * @param ops + * @returns {*} + */ + getNetsStart(ops) { + return ops.filter((item) => { + if (item.type === 'feed') { + return true; + } + }); + } + /** + * Get Ops Nets Last Node + * @param ops + * @returns {*} + */ + getNetsEnd(ops) { + return ops.filter((item) => { + if (item.type === 'fetch') { + return true; + } + }); + } + /** + * get tensor by id + * @param id + * @returns {*} + */ + getTensor(id) { + return this.weightMap.filter((item, i) => { + if (id === item.id) + return item; + }); + } + /** + * Create Ops Executor Object Map + * @param ops + * @returns {*} + */ + createOpsMap(ops) { + return ops.map((item, idx) => { + item.idx = idx; + const graphExecutor = new GraphExecutor(item); + return graphExecutor; + }); + } + /** + * Get The Next Executor need Exec + * @param ops + * @param id + * @returns {*} + */ + getNextExecutor(ops, id) { + return ops.filter((item, key) => { + if (id === item.inputsName[0]) { + return true; + } + }); + } + /** + * Load a graph model given a URL to the model definition. + * @param modelGonfig + * @param options + * @returns {Promise} + */ + async loadGraphModel(modelGonfig, options) { + if (modelGonfig === null) { + // todo saniac 报错提示修改 + throw new Error( + 'modelGonfig in loadGraphModel() cannot be null. Please provide a url ' + + 'or an IOHandler that loads the model'); + } + if (options === null) { + options = {}; + } + const model = new GraphModel(modelGonfig, options); + await model.load(); + return model; + } + /** + * dispose + */ + dispose() { + this.executor.dispose(); + } +} +/* eslint-enable */ diff --git a/web/src/factory/fshader/factory.es6 b/web/src/factory/fshader/factory.es6 new file mode 100644 index 0000000000000000000000000000000000000000..0b218fc4589e5a306a1bbdcf26b8eabb4772a295 --- /dev/null +++ b/web/src/factory/fshader/factory.es6 @@ -0,0 +1,70 @@ +import ops from './ops'; +/** + * @file 工厂类,生成fragment shader + * @author yangmingming + */ +export default class Factory { + constructor(opts) { + this.defaultOpts = Object.assign({}, opts); + } + + buildShader(opName, data) { + let result = ''; + result = this.buildPrefix(opName); + result += this.buildCommon(opName); + result += this.buildOp(opName); + result = this.populateData(result, data); + return result; + } + + buildPrefix(opName) { + return ops.common.prefix; + } + + buildCommon(opName) { + return ops.common.params + ops.common.func; + } + + buildOp(opName) { + let code = ops.ops[opName].params; + // 依赖的方法 + let atoms = ops.atoms; + let confs = ops.ops[opName].confs; + let dep = confs.dep || []; + dep.map(item => { + let func = item.func; + let data = item.conf; + let snippet = atoms[func]; + code += this.populateData(snippet, data); + }); + // suffix + code += this.buildSuffix(opName); + // main方法 + code += ops.ops[opName].func; + return code; + } + + buildSuffix(opName) { + return ops.common.suffix; + } + + populateData(result, data) { + let code = result; + for (let key in data) { + code = code.replace(new RegExp(key.toUpperCase(), 'g'), + ((typeof data[key]) === 'undefined') ? 1 : data[key]); + } + return code; + } + + getOpConfs() { + const opsConfs = {}; + for (let key in ops.ops) { + if (ops.ops.hasOwnProperty(key)) { + opsConfs[key] = ops.ops[key].confs.input; + } + } + return opsConfs; + } +} + diff --git a/web/src/factory/fshader/ops.es6 b/web/src/factory/fshader/ops.es6 new file mode 100644 index 0000000000000000000000000000000000000000..dfdf6e653567b46d315e391f7ea8b7a38e2cecb9 --- /dev/null +++ b/web/src/factory/fshader/ops.es6 @@ -0,0 +1,120 @@ +/* eslint-disable */ +import common_params from '../../shader/atom/common_params'; +import common_func from '../../shader/atom/common_func'; +import prefix from '../../shader/atom/prefix'; +import suffix from '../../shader/atom/suffix'; +import ivec56 from '../../shader/atom/type_ivec56'; + +import conv2d_params from '../../shader/conv2d/params'; +import conv2d_func from '../../shader/conv2d/main'; +import conv2d_conf from '../../shader/conv2d/conf'; +import dynamic_params from '../../shader/dynamic/params'; +import dynamic_func from '../../shader/dynamic/main'; +import dynamic_conf from '../../shader/dynamic/conf'; +import pool2d_params from '../../shader/pool2d/params'; +import pool2d_func from '../../shader/pool2d/main'; +import pool2d_conf from '../../shader/pool2d/conf'; +import elementwise_add_params from '../../shader/elementwise_add/params'; +import elementwise_add_func from '../../shader/elementwise_add/main'; +import elementwise_add_conf from '../../shader/elementwise_add/conf'; +import mul_params from '../../shader/mul/params'; +import mul_func from '../../shader/mul/main'; +import mul_conf from '../../shader/mul/conf'; +import softmax_params from '../../shader/softmax/params'; +import softmax_func from '../../shader/softmax/main'; +import softmax_conf from '../../shader/softmax/conf'; +import batchnorm_params from '../../shader/batchnorm/params'; +import batchnorm_func from '../../shader/batchnorm/main'; +import batchnorm_conf from '../../shader/batchnorm/conf'; + +import getArrayIndexFromTensorPos from '../../shader/atom/getArrayIndexFromTensorPos'; +import getArrayIndexFromTexturePos from '../../shader/atom/getArrayIndexFromTexturePos'; +import getTensorPosFromArrayIndex from '../../shader/atom/getTensorPosFromArrayIndex'; +import getTexturePosFromArrayIndex from '../../shader/atom/getTexturePosFromArrayIndex'; +import getValueFromTexturePos from '../../shader/atom/getValueFromTexturePos'; +import getValueFromTensorPos from '../../shader/atom/getValueFromTensorPos'; +import moveTexture2PosToReal from '../../shader/atom/moveTexture2PosToReal'; +import getPixelsFromTexturePos from '../../shader/atom/getPixelsFromTexturePos'; +import getRangePowSumFromArrayIndex from '../../shader/atom/getRangePowSumFromArrayIndex'; +import getRangeSumFromArrayIndex from '../../shader/atom/getRangeSumFromArrayIndex'; +import sigmoid from '../../shader/atom/sigmoid'; +import prelu from '../../shader/atom/prelu'; +import scale from '../../shader/atom/scale'; +import softmax from '../../shader/atom/softmax'; +/** + * @file op文件 + * @author yangmingming + */ + +export default { + common: { + params: common_params, + func: common_func, + prefix, + suffix, + ivec56 + }, + ops: { + conv2d: { + params: conv2d_params, + func: conv2d_func, + confs: conv2d_conf + }, + dynamic: { + params: dynamic_params, + func: dynamic_func, + confs: dynamic_conf + }, + pool2d: { + params: pool2d_params, + func: pool2d_func, + confs: pool2d_conf + }, + elementwise_add: { + params: elementwise_add_params, + func: elementwise_add_func, + confs: elementwise_add_conf + }, + mul: { + params: mul_params, + func: mul_func, + confs: mul_conf + }, + relu: { + params: dynamic_params, + func: dynamic_func, + confs: dynamic_conf + }, + scale: { + params: dynamic_params, + func: dynamic_func, + confs: dynamic_conf + }, + softmax: { + params: softmax_params, + func: softmax_func, + confs: softmax_conf + }, + batchnorm: { + params: batchnorm_params, + func: batchnorm_func, + confs: batchnorm_conf + } + }, + atoms: { + getArrayIndexFromTensorPos, + getArrayIndexFromTexturePos, + getTensorPosFromArrayIndex, + getTexturePosFromArrayIndex, + getValueFromTexturePos, + getValueFromTensorPos, + moveTexture2PosToReal, + getPixelsFromTexturePos, + getRangeSumFromArrayIndex, + getRangePowSumFromArrayIndex, + sigmoid, + prelu, + scale, + softmax + } +}; diff --git a/web/src/feed/ImageFeed.es6 b/web/src/feed/ImageFeed.es6 new file mode 100644 index 0000000000000000000000000000000000000000..59c2bd66ab4b6dc060afc00afaa6d3122db1b500 --- /dev/null +++ b/web/src/feed/ImageFeed.es6 @@ -0,0 +1,226 @@ +/* eslint-disable */ +/** + * @file image,feed 获取图像相关输入 + * @author wangqun@baidu.com + */ +export default class imageFeed { + constructor() { + this.fromPixels2DContext = document.createElement('canvas').getContext('2d'); + this.defaultWidth = 224; + this.defaultHeight = 224; + this.minPixels = 225; + this.pixels = ''; + this.defaultParams = { + gapFillWith: '#000', + std: [1, 1, 1] + }; + }; + + /** + * 处理图像方法 + * @param inputs + */ + process(inputs) { + const input = inputs.input; + const mode = inputs.mode; + const channel = inputs.channel; + const rotate = inputs.rotate; + const params = { + ...this.defaultParams, + ...inputs.params + }; + let output = []; + + output = this.fromPixels(input, params); + return output; + }; + + /** + * crop图像&重新设定图片tensor形状 + * @param shape + */ + reshape(imageData, opt, scaleSize) { + const {sw, sh} = scaleSize; + const {width, height} = opt; + const hPadding = Math.ceil((sw - width) / 2); + const vPadding = Math.ceil((sh - height) / 2); + + let data = imageData.data; + let red = []; + let green = []; + let blue = []; + let mean = opt.mean; + let std = opt.std; + for (let i = 0; i < data.length; i += 4) { + // img_mean 0.485, 0.456, 0.406 + //img_std 0.229, 0.224, 0.225 + let index = i / 4; + let vIndex = Math.floor(index / sw); + let hIndex = index - (vIndex * sw) - 1; + if (hIndex >= hPadding && hIndex < (hPadding + width) && + vIndex >= vPadding && vIndex < (vPadding + height)) { + red.push(((data[i] / 255) - mean[0]) / std[0]); // red + green.push(((data[i + 1] / 255) - mean[1]) / std[1]); // green + blue.push(((data[i + 2] / 255) - mean[2]) / std[2]); // blue + } + } + let tmp = green.concat(blue); + return red.concat(tmp); + }; + + /** + * 全部转rgb * H * W + * @param shape + */ + allReshapeToRGB(imageData, opt, scaleSize) { + const {sw, sh} = scaleSize; + const {width, height} = opt; + let data = imageData.data; + let mean = opt.mean; + let dataLength = data.length; + let result = new Float32Array(dataLength * 3 / 4); + let offsetR = 0; + let offsetG = dataLength / 4; + let offsetB = dataLength / 2; + for (let i = 0; i < data.length; i += 4) { + result[offsetR++] = (data[i] - mean[0]) / 256; + result[offsetG++] = (data[i + 1] - mean[1]) / 256; + result[offsetB++] = (data[i + 2] - mean[2]) / 256; + // result.push((data[i] - mean[0]) / 256); // red + // result.push((data[i + 1] - mean[1]) / 256); // green + // result.push((data[i + 2] - mean[2]) / 256); // blue + } + return result; + }; + + /** + * 根据scale缩放图像 + * @param image + * @param params + * @return {Object} 缩放后的尺寸 + */ + reSize(image, params) { + // 原始图片宽高 + const width = this.pixelWidth; + const height = this.pixelHeight; + // 缩放后的宽高 + let sw = width; + let sh = height; + // 最小边缩放到scale + if (width < height) { + sw = params.scale; + sh = Math.round(sw * height / width); + } else { + sh = params.scale; + sw = Math.round(sh * width / height); + } + this.fromPixels2DContext.canvas.width = sw; + this.fromPixels2DContext.canvas.height = sh; + this.fromPixels2DContext.drawImage( + image, 0, 0, sw, sh); + return {sw, sh}; + }; + + + /** + * 缩放成目标尺寸并居中 + */ + fitToTargetSize(image, params, center) { + // 目标尺寸 + const targetWidth = params.targetSize.width; + const targetHeight = params.targetSize.height; + this.fromPixels2DContext.canvas.width = targetWidth; + this.fromPixels2DContext.canvas.height = targetHeight; + this.fromPixels2DContext.fillStyle = params.gapFillWith; + this.fromPixels2DContext.fillRect(0, 0, targetHeight, targetWidth); + // 缩放后的宽高 + let sw = targetWidth; + let sh = targetHeight; + let x = 0; + let y = 0; + // target的长宽比大些 就把原图的高变成target那么高 + if (targetWidth / targetHeight * this.pixelHeight / this.pixelWidth >= 1) { + sw = Math.round(sh * this.pixelWidth / this.pixelHeight); + x = Math.floor((targetWidth - sw) / 2); + } + // target的长宽比小些 就把原图的宽变成target那么宽 + else { + sh = Math.round(sw * this.pixelHeight / this.pixelWidth); + y = Math.floor((targetHeight - sh) / 2); + } + // console.log(x, y, sw, sh); + if (center) { + this.fromPixels2DContext.drawImage( + image, x, y, sw, sh); + } + else { + this.fromPixels2DContext.drawImage( + image, 0, 0, sw, sh); + } + document.getElementById('p-c').appendChild(this.fromPixels2DContext.canvas);// test only, demele me + return {sw: targetWidth, sh: targetHeight}; + } + + /** + * 获取图像内容 + * @param pixels + * @returns {Uint8ClampedArray} + */ + getImageData(pixels, scaleSize) { + const {sw, sh} = scaleSize; + let vals = this.fromPixels2DContext + .getImageData(0, 0, sw, sh); + // crop图像 + const width = pixels.width; + const height = pixels.height; + return vals; + }; + + /** + * 计算灰度图 + * @param imageData + * @returns {*} + */ + grayscale (imageData) { + let data = imageData.data; + + for (let i = 0; i < data.length; i += 4) { + let avg = (data[i] + data[i + 1] + data[i + 2]) / 3; + data[i] = avg; // red + data[i + 1] = avg; // green + data[i + 2] = avg; // blue + } + return data; + }; + + fromPixels(pixels, opt) { + let data; + let scaleSize; + if (pixels instanceof HTMLImageElement || pixels instanceof HTMLVideoElement) { + this.pixelWidth = pixels.naturalWidth || pixels.width; + this.pixelHeight = pixels.naturalHeight || pixels.height; + if (opt.scale) { // 兼容以前的,如果有scale就是短边缩放到scale模式 + scaleSize = this.reSize(pixels, opt); + data = this.getImageData(opt, scaleSize); + } + else if (opt.targetSize) { // 如果有targetSize,就是装在目标宽高里的模式 + scaleSize = this.fitToTargetSize(pixels, opt); + data = this.getImageData(opt, scaleSize); + } + } + + if (opt.gray) { + data = grayscale(data); + } + + if (opt.shape) { + data = this.reshape(data, opt, scaleSize); + } + + if (opt.targetShape) { + data = this.allReshapeToRGB(data, opt, scaleSize); + } + return [{data: data, shape: opt.shape || opt.targetShape, name: 'image'}]; + } +} +/* eslint-enable */ diff --git a/web/src/feed/io.es6 b/web/src/feed/io.es6 new file mode 100644 index 0000000000000000000000000000000000000000..032f192687e58e0fe67e9ef78e606fec71d8d77c --- /dev/null +++ b/web/src/feed/io.es6 @@ -0,0 +1,853 @@ +/* eslint-disable */ +/** + * @file io,loader相关输入输出 + * @author wangqun@baidu.com + */ +export default class io { + constructor() { + this.fromPixels2DContext = document.createElement('canvas').getContext('2d'); + }; + + fromPixels(pixels, opt) { + pixels = pixels.input; + const shape = opt[0].shape; + const numChannels = opt[0].shape[0]; + if (pixels == null) { + throw new Error( + 'pixels passed to tf.browser.fromPixels() can not be null'); + } + let vals; + // tslint:disable-next-line:no-any + // tslint:disable-next-line:no-any + if (pixels.getContext != null) { + // tslint:disable-next-line:no-any + vals = pixels + .getContext('2d') + .getImageData(0, 0, pixels.width, pixels.height) + .data; + } else if (pixels instanceof ImageData) { + vals = pixels.data; + } else if ( + pixels instanceof HTMLImageElement || + pixels instanceof HTMLVideoElement) { + if (this.fromPixels2DContext == null) { + throw new Error( + 'Can\'t read pixels from HTMLImageElement outside ' + + 'the browser.'); + } + this.fromPixels2DContext.canvas.width = pixels.width; + this.fromPixels2DContext.canvas.height = pixels.height; + this.fromPixels2DContext.drawImage( + pixels, 0, 0, pixels.width, pixels.height); + vals = this.fromPixels2DContext + .getImageData(0, 0, pixels.width, pixels.height) + .data; + } else { + + } + let values; + if (numChannels === 4) { + values = new Array(vals); + } else { + const numPixels = (shape[1] || pixels.width) * (shape[2] ||pixels.height); + // console.log(numPixels, numPixels * numChannels); + values = new Array(numPixels * numChannels); + for (let i = 0; i < numPixels; i++) { + for (let channel = 0; channel < numChannels; ++channel) { + values[i * numChannels + channel] = vals[i * 4 + channel]; + } + } + } + // console.log(pixels.height, pixels.width, numChannels, values); + // const outShape: [number, number, number] = + // [pixels.height, pixels.width, numChannels]; + values = [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 7.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 6.0, + 7.0, + 0.0, + 0.0, + 0.0, + 0.0, + 3.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 3.0, + 0.0, + 0.0, + 14.0, + 16.0, + 8.0, + 1.0, + 0.0, + 0.0, + 0.0, + 14.0, + 1.0, + 0.0, + 0.0, + 14.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 5.0, + 13.0, + 0.0, + 0.0, + 0.0, + 9.0, + 0.0, + 27.0, + 0.0, + 0.0, + 0.0, + 5.0, + 0.0, + 0.0, + 3.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 0.0, + 0.0, + 5.0, + 11.0, + 5.0, + 4.0, + 8.0, + 0.0, + 0.0, + 15.0, + 7.0, + 0.0, + 2.0, + 7.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 11.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 11.0, + 3.0, + 0.0, + 2.0, + 0.0, + 5.0, + 3.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 0.0, + 0.0, + 10.0, + 6.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 9.0, + 0.0, + 0.0, + 2.0, + 3.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 8.0, + 0.0, + 8.0, + 11.0, + 0.0, + 4.0, + 113.0, + 202.0, + 249.0, + 255.0, + 255.0, + 135.0, + 44.0, + 0.0, + 7.0, + 3.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 0.0, + 2.0, + 0.0, + 33.0, + 188.0, + 230.0, + 101.0, + 52.0, + 6.0, + 106.0, + 162.0, + 183.0, + 11.0, + 0.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 9.0, + 0.0, + 4.0, + 58.0, + 230.0, + 189.0, + 31.0, + 0.0, + 3.0, + 0.0, + 14.0, + 0.0, + 204.0, + 17.0, + 7.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 20.0, + 24.0, + 231.0, + 181.0, + 0.0, + 0.0, + 5.0, + 4.0, + 2.0, + 0.0, + 119.0, + 228.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 173.0, + 232.0, + 32.0, + 4.0, + 10.0, + 0.0, + 0.0, + 7.0, + 79.0, + 230.0, + 108.0, + 18.0, + 0.0, + 10.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 100.0, + 246.0, + 47.0, + 0.0, + 5.0, + 0.0, + 1.0, + 8.0, + 63.0, + 216.0, + 109.0, + 0.0, + 0.0, + 6.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 8.0, + 122.0, + 210.0, + 0.0, + 31.0, + 0.0, + 8.0, + 28.0, + 109.0, + 235.0, + 182.0, + 0.0, + 13.0, + 0.0, + 22.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 128.0, + 233.0, + 0.0, + 6.0, + 66.0, + 126.0, + 180.0, + 191.0, + 220.0, + 27.0, + 0.0, + 0.0, + 11.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 78.0, + 246.0, + 233.0, + 220.0, + 255.0, + 199.0, + 59.0, + 235.0, + 68.0, + 12.0, + 0.0, + 1.0, + 2.0, + 1.0, + 10.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 0.0, + 80.0, + 120.0, + 139.0, + 62.0, + 0.0, + 155.0, + 211.0, + 5.0, + 10.0, + 0.0, + 0.0, + 0.0, + 3.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 5.0, + 2.0, + 0.0, + 0.0, + 90.0, + 255.0, + 70.0, + 0.0, + 0.0, + 0.0, + 9.0, + 0.0, + 0.0, + 9.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 17.0, + 5.0, + 0.0, + 11.0, + 47.0, + 227.0, + 159.0, + 0.0, + 0.0, + 8.0, + 0.0, + 0.0, + 2.0, + 6.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 5.0, + 0.0, + 0.0, + 0.0, + 4.0, + 213.0, + 207.0, + 19.0, + 0.0, + 0.0, + 3.0, + 12.0, + 0.0, + 2.0, + 4.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 16.0, + 7.0, + 91.0, + 253.0, + 50.0, + 0.0, + 0.0, + 4.0, + 0.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 5.0, + 0.0, + 45.0, + 252.0, + 131.0, + 0.0, + 8.0, + 0.0, + 7.0, + 0.0, + 15.0, + 5.0, + 0.0, + 0.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 8.0, + 11.0, + 207.0, + 205.0, + 30.0, + 2.0, + 0.0, + 0.0, + 22.0, + 0.0, + 0.0, + 4.0, + 9.0, + 11.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 14.0, + 155.0, + 255.0, + 28.0, + 0.0, + 0.0, + 6.0, + 4.0, + 0.0, + 5.0, + 150.0, + 210.0, + 91.0, + 17.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 14.0, + 40.0, + 250.0, + 91.0, + 0.0, + 0.0, + 7.0, + 0.0, + 0.0, + 24.0, + 0.0, + 10.0, + 130.0, + 183.0, + 147.0, + 11.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 207.0, + 146.0, + 4.0, + 0.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 25.0, + 237.0, + 29.0, + 0.0, + 12.0, + 0.0, + 0.0, + 14.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 13.0, + 0.0, + 15.0, + 7.0, + 0.0, + 9.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 0.0, + 4.0, + 3.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ]; + return [{data: values, shape: shape, name: 'pixel'}]; + } +} +/* eslint-enable */ diff --git a/web/src/gpu/gpu.es6 b/web/src/gpu/gpu.es6 new file mode 100644 index 0000000000000000000000000000000000000000..f2c1c4c5ea912b0d99d8f0bea2d7c600d213ed5c --- /dev/null +++ b/web/src/gpu/gpu.es6 @@ -0,0 +1,376 @@ +/* eslint-disable */ +import VSHADER from '../shader/v_shader'; +/** + * @file gpu运算 + * @author yangmingming + */ +export default class gpu { + constructor(opts = {}) { + this.opts = opts; + opts.width_raw_canvas = Number(opts.width_raw_canvas) || 512; + opts.height_raw_canvas = Number(opts.height_raw_canvas) || 512; + let canvas = opts.el ? opts.el : document.createElement('canvas'); + canvas.width = opts.width_raw_canvas; + canvas.height = opts.height_raw_canvas; + this.gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); + this.gl.viewport(0, 0, canvas.width, canvas.height); + // Attempt to activate the extension, returns null if unavailable + this.textureFloat = this.gl.getExtension('OES_texture_float'); + // this.setOutProps(); + this.initCache(); + console.log('float extension is started or not? ' + !!this.textureFloat); + console.log('WebGl版本是 ' + this.gl.getParameter(this.gl.SHADING_LANGUAGE_VERSION)); + } + + initCache() { + // 运行次数 + this.times = 0; + const gl = this.gl; + // 缓存每个op的texture + // this.textures = []; + // 顶点数据 + let vertices = new Float32Array([ + -1.0, 1.0, 0.0, 1.0, + -1.0, -1.0, 0.0, 0.0, + 1.0, 1.0, 1.0, 1.0, + 1.0, -1.0, 1.0, 0.0]); + this.vertexBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); + // shader + this.vertexShader = null; + // 生成vertextShader + this.initShader(VSHADER); + this.fragmentShader = null; + // 上一个texture + this.prevTexture = null; + // 当前op输出texture + this.currentTexture = null; + // 帧缓存 + this.frameBuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer); + // 计算texture cache, 最多3个 + this.cacheTextures = [gl.createTexture(), gl.createTexture(), gl.createTexture()]; + // texture buffer + this.textureBuffer = [gl.createTexture(), gl.createTexture()]; + // program + this.programs = [gl.createProgram(), gl.createProgram()]; + this.program = this.programs[0]; + this.textureBufferIndex = 0; + for (let i = 0; i < 2; i++) { + gl.bindTexture(gl.TEXTURE_2D, this.textureBuffer[i]); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.bindTexture(gl.TEXTURE_2D, null); + } + } + + runVertexShader() { + const gl = this.gl; + let aPosition = gl.getAttribLocation(this.program, 'position'); + // Turn on the position attribute + gl.enableVertexAttribArray(aPosition); + // Bind the position buffer. + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 16, 0); + } + + setOutProps(opts) { + this.width_shape_out = opts.width_shape || 1; + this.height_shape_out = opts.height_shape || 1; + this.width_texture_out = opts.width_texture || 1; + this.height_texture_out = opts.height_texture || 1; + this.total_shape = opts.total_shape || 0; + } + + isFloatingTexture() { + return (this.textureFloat !== null); + } + + attachShader(fshader) { + const gl = this.gl; + let index = this.textureBufferIndex % 2; + const program = this.programs[index]; + this.program = program; + if (this.times < 2) { + gl.attachShader(program, this.vertexShader); + } + this.textureBufferIndex = (this.textureBufferIndex + 1) >= 2 ? 0 : 1; + this.gl.attachShader(program, fshader); + gl.linkProgram(program); + gl.useProgram(program); + if (this.times++ < 2) { + this.runVertexShader(); + } + if (!!this.fragmentShader) { + const cache = this.programs[(index + 1) % 2]; + gl.detachShader(cache, this.fragmentShader); + // gl.deleteShader(this.fragmentShader); + } + this.fragmentShader = fshader; + } + + create(vshaderCode, fshaderCode) { + let gl = this.gl; + if (this.program) { + this.dispose(); + } + // 创建 & 绑定程序对象 + let program = this.program = gl.createProgram(); + // 创建&绑定vertex&frament shader + this.initShader(vshaderCode); + this.fragmentShader = this.initShader(fshaderCode, 'fragment'); + this.gl.attachShader(program, this.vertexShader); + this.gl.attachShader(program, this.fragmentShader); + gl.linkProgram(program); + gl.useProgram(program); + + let aPosition = gl.getAttribLocation(program, 'position'); + // Turn on the position attribute + gl.enableVertexAttribArray(aPosition); + // Bind the position buffer. + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 16, 0); + } + + /** + * 初始化shader + * @param code shader代码 + * @param type shader类型 + * @return {object} 初始化成功返回shader + */ + initShader(code, type = 'vertex') { + const shaderType = type === 'vertex' ? this.gl.VERTEX_SHADER : this.gl.FRAGMENT_SHADER; + let shader; + if (type === 'vertex' && this.vertexShader) { + shader = this.vertexShader; + } else { + shader = this.gl.createShader(shaderType); + if (type === 'vertex') { + this.vertexShader = shader; + } + this.gl.shaderSource(shader, code); + this.gl.compileShader(shader); + if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { + throw new Error("compile: " + this.gl.getShaderInfoLog(shader)); + } + } + + return shader; + } + + /** + * 更新fragment shader + * @param code shader代码 + * @return {boolean} 更新成功过返回true + */ + updateShader(code) { + this.gl.useProgram(this.program); + // 删除fragment shader + if (this.fragmentShader) { + this.gl.detachShader(this.program, this.fragmentShader); + this.gl.deleteShader(this.fragmentShader); + // 删除texture + this.gl.deleteTexture(this.texture); + } + // 更新 + this.fragmentShader = this.initShader(code, 'fragment'); + return true; + } + + /** + * 创建并绑定framebuffer, 之后attach a texture + * @param {WebGLTexture} texture 材质 + * @returns {WebGLFramebuffer} The framebuffer + */ + attachFrameBuffer(opts = {}) { + this.prevTexture = this.currentTexture; + this.currentTexture = this.textureBuffer[this.textureBufferIndex % 2]; + // this.textureBufferIndex = (this.textureBufferIndex + 1) >= 2 ? 0 : 1; + const gl = this.gl; + gl.framebufferTexture2D(gl.FRAMEBUFFER, // The target is always a FRAMEBUFFER. + gl.COLOR_ATTACHMENT0, // We are providing the color buffer. + gl.TEXTURE_2D, // This is a 2D image texture. + this.currentTexture, // The texture. + 0 // 0, we aren't using MIPMAPs + ); + gl.viewport( + 0, + 0, + opts.width_texture_out || this.width_texture_out, + opts.height_texture_out || this.height_texture_out + ); + return this.frameBuffer; + } + + // 帧缓存检测 + frameBufferIsComplete() { + let gl = this.gl; + let message; + let status; + let value; + + status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + + switch (status) + { + case gl.FRAMEBUFFER_COMPLETE: + message = "Framebuffer is complete."; + value = true; + break; + case gl.FRAMEBUFFER_UNSUPPORTED: + message = "Framebuffer is unsupported"; + value = false; + break; + case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + message = "Framebuffer incomplete attachment"; + value = false; + break; + case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + message = "Framebuffer incomplete (missmatched) dimensions"; + value = false; + break; + case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + message = "Framebuffer incomplete missing attachment"; + value = false; + break; + default: + message = "Unexpected framebuffer status: " + status; + value = false; + } + return {isComplete: value, message: message}; + } + + /** + * 更新材质 + * @param {WebGLTexture} texture 材质对象 + * @param {number} type 材质类型. FLOAT, UNSIGNED_BYTE, etc. + * @param {Float32Array[]} data 材质数据 + */ + refreshTexture(texture, type, data) { + const gl = this.gl; + // Bind the texture so the following methods effect it. + gl.bindTexture(gl.TEXTURE_2D, texture); + + // Replace the texture data + gl.texSubImage2D(gl.TEXTURE_2D, // Target, matches bind above. + 0, // Level of detail. + 0, // xOffset + 0, // yOffset + this.opts.width_raw_canvas, // Width - normalized to s. + this.opts.height_raw_canvas, // Height - normalized to t. + gl.RGBA, // Format for each pixel. + type, // Data type for each chanel. + data); // Image data in the described format. + + // Unbind the texture. + gl.bindTexture(gl.TEXTURE_2D, null); + + return texture; + } + + /** + * 初始化材质 + * @param {int} index 材质索引 + * @param {string} tSampler 材质名称 + * @param {Object} bufferData 数据 + */ + initTexture(index, item) { + const gl = this.gl; + let texture; + if (!item.data) { + texture = this.prevTexture; + } else { + // texture = gl.createTexture(); + texture = this.cacheTextures[index]; + // this.textures.push(texture); + } + gl.activeTexture(gl[`TEXTURE${index}`]); + gl.bindTexture(gl.TEXTURE_2D, texture); + if (item.data) { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, item.width_texture || this.opts.width_raw_canvas, + item.height_texture || this.opts.height_raw_canvas, 0, + gl.RGBA, gl.FLOAT, item.data, 0); + } + } + + getUniformLoc(name) { + let loc = this.gl.getUniformLocation(this.program, name); + if (loc === null) throw `getUniformLoc ${name} err`; + return loc; + } + + // 生成帧缓存的texture + makeTexure(type, data, opts = {}) { + const gl = this.gl; + let index = this.textureBufferIndex % 2; + let texture = this.textureBuffer[index]; + gl.bindTexture(gl.TEXTURE_2D, texture); + + // Pixel format and data for the texture + gl.texImage2D(gl.TEXTURE_2D, // Target, matches bind above. + 0, // Level of detail. + gl.RGBA, // Internal format. + opts.width_texture_out || this.width_texture_out, + opts.height_texture_out || this.height_texture_out, + 0, // Always 0 in OpenGL ES. + gl.RGBA, // Format for each pixel. + type, // Data type for each chanel. + data); // Image data in the described format, or null. + // Unbind the texture. + // gl.bindTexture(gl.TEXTURE_2D, null); + this.attachFrameBuffer(); + + return texture; + } + + render(data = []) { + const gl = this.gl; + let textureIndex = 0; + // 输入数据 + data.forEach(item => { + if (item.type === 'texture') { + this.initTexture(textureIndex, item); + gl.uniform1i(this.getUniformLoc(item.variable + '_' + item.tensor), textureIndex++); + } else if (item.type === 'uniform') { + gl[item.setter](this.getUniformLoc(item.variable + '_' + item.tensor), item.data); + } + }); + // gl.clearColor(.0, .0, .0, 1); + // gl.clear(gl.COLOR_BUFFER_BIT); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + } + + compute() { + let gl = this.gl; + let pixels = new Float32Array(this.width_texture_out * this.height_texture_out * 4); + // gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); + gl.readPixels(0, 0, this.width_texture_out, this.height_texture_out, gl.RGBA, gl.FLOAT, pixels, 0); + + let result = []; + for (let i = 0; i < this.width_texture_out * this.height_texture_out; i++) { + result.push(pixels[4 * i]); + } + return result; + } + + dispose() { + const gl = this.gl; + this.cacheTextures.forEach(texture => { + gl.deleteTexture(texture); + }); + this.cacheTextures = []; + this.programs.forEach(program => { + gl.detachShader(program, this.vertexShader); + gl.deleteShader(this.vertexShader); + gl.deleteProgram(program); + }); + this.programs = []; + } +} diff --git a/web/src/index.es6 b/web/src/index.es6 new file mode 100644 index 0000000000000000000000000000000000000000..b085e64f698ec5b8361aae691643e41f7817a106 --- /dev/null +++ b/web/src/index.es6 @@ -0,0 +1,20 @@ +import 'babel-polyfill'; +import Graph from './executor/loader'; +import IO from './executor/io'; +/** + * @file model demo 入口文件 + * @author yangmingming@baidu.com + * + */ +// 'http://mms-xr.cdn.bcebos.com/paddle/mnist/model.json' +const MODEL_URL = '../demo/model/model.json'; +const graphModel = new Graph(); +const model = graphModel.loadGraphModel(MODEL_URL); +const cat = document.getElementById('pic'); +const io = new IO(); + +let inst = model.execute({input: cat}); +let res = inst.read(); +console.dir(['result', res]); +var fileDownload = require('js-file-download'); +fileDownload(res, "result.csv"); diff --git a/web/src/index.html b/web/src/index.html new file mode 100644 index 0000000000000000000000000000000000000000..b61f210b38dc0e3a70778f887e91f23b299821f7 --- /dev/null +++ b/web/src/index.html @@ -0,0 +1,13 @@ + + + + + paddle web demo + + + + +
+ + + diff --git a/web/src/ops/dummy.js b/web/src/ops/dummy.js new file mode 100644 index 0000000000000000000000000000000000000000..120d022cf74c31200d48cd642cfa46b15d6e2d36 --- /dev/null +++ b/web/src/ops/dummy.js @@ -0,0 +1,12 @@ + +module.exports.create = function (args, scope, gl) { + const output = scope[args.outputs.Out[0]] + return { + inferShape() { + output.dim = output.dim.forEach(d => d === -1 ? 1 : d) + }, + compute() { + console.log(output) + } + } +} \ No newline at end of file diff --git a/web/src/ops/feed.js b/web/src/ops/feed.js new file mode 100644 index 0000000000000000000000000000000000000000..e6057684668fcdd23313349f5dca4aa5100912e4 --- /dev/null +++ b/web/src/ops/feed.js @@ -0,0 +1,15 @@ +module.exports.create = function (args, scope, gl) { + const output = scope[args.outputs.Out[0]] + const input = scope[args.inputs.X[0]] + return { + inferShape() { + output.dim = output.dim.map(d => d === -1 ? 1 : d) + }, + compute() { + console.log(input) + console.log(output) + } + } +} + + diff --git a/web/src/ops/fetch.js b/web/src/ops/fetch.js new file mode 100644 index 0000000000000000000000000000000000000000..bfef4f80febca899bfd2b413c067577a4e991592 --- /dev/null +++ b/web/src/ops/fetch.js @@ -0,0 +1,12 @@ + +module.exports.create = function (args, scope, gl) { + // const output = scope[args.outputs.Out[0]] + return { + inferShape() { + // output.dim = output.dim.forEach(d => d === -1 ? 1 : d) + }, + compute() { + console.log(`${args.type} is going to be implemented`) + } + } +} \ No newline at end of file diff --git a/web/src/package.json b/web/src/package.json new file mode 100644 index 0000000000000000000000000000000000000000..49f0c29c77e624142e9223ade6790408f39d6986 --- /dev/null +++ b/web/src/package.json @@ -0,0 +1,11 @@ +{ + "name": "paddel-web", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/web/src/runtime/runtime.es6 b/web/src/runtime/runtime.es6 new file mode 100644 index 0000000000000000000000000000000000000000..f95a6a29fde9a30c10dd9e5758681a0e9e8731d2 --- /dev/null +++ b/web/src/runtime/runtime.es6 @@ -0,0 +1,69 @@ +/* eslint-disable */ +import Gpu from '../gpu/gpu'; +/** + * @file gpu运行时 + * @author yangmingming + * + */ +export default { + /** + * 初始化, 生成gpu实例 + * @param {Object} opts 运行时参数,包含el:canvas,dim: 256 + * @return {Object} this 实例对象 + */ + init(opts = {}) { + const gpu = this.gpu = new Gpu(opts); + if (gpu.isFloatingTexture()) { + return this; + } else { + return null; + } + }, + + run(opName, opData) { + let time = +Date.now(); + let start = time; + let timeObj = {}; + if (!opData.isPass) { + console.log('跳过当前op:' + opName); + return this; + } + // 设置gpu参数 + const gpu = this.gpu; + gpu.setOutProps(opData.tensor['out']); + // 生成帧缓存材质 + gpu.makeTexure(WebGLRenderingContext.FLOAT, null); + let end = +Date.now(); + let bufferStatus = gpu.frameBufferIsComplete(); + if (bufferStatus.isComplete) { + start = +Date.now(); + timeObj['buferstatus-time'] = start - end; + gpu.attachShader(opData.fshader); + end = +Date.now(); + timeObj['createshader-time'] = end - start; + timeObj['jsTime'] = end - time; + statistic.push(timeObj); + // 开始计算 + this.gpu.render(opData.renderData); + return this; + } else { + return bufferStatus.message; + } + }, + + /** + * 读取op计算结果, 并返回数据 + */ + read() { + return this.gpu.compute(); + }, + + createFragmentShader(fsCode) { + return this.gpu.initShader(fsCode, 'fragment'); + }, + + // 释放资源 + dispose() { + this.gpu.dispose(); + } +}; diff --git a/web/src/shader/atom/common_func.es6 b/web/src/shader/atom/common_func.es6 new file mode 100644 index 0000000000000000000000000000000000000000..68ac47f652b8e4054297db0d26b5b36ff5b4b68b --- /dev/null +++ b/web/src/shader/atom/common_func.es6 @@ -0,0 +1,35 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +export default ` +// 激活函数 +float prelu(float x, float p, float b) { + float result = x; + if (x < 0.0) { + result = x * p; + } + return result; +} + +float leakyRelu(float x, float p, float b) { + float result = max(x, x * p); + return result; +} + +float scale(float x, float p, float b) { + float result = p * x + b; + return result; +} + +float sigmoid(float x, float y, float z) { + float result = 1.0 / (1.0 + exp(-x)); + return result; +} + +float softmax(float x, float p, float b) { + float result = exp(x) / (10.0 * exp(x)); + return result; +} +`; diff --git a/web/src/shader/atom/common_params.es6 b/web/src/shader/atom/common_params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..a99019f223b47827dcbfd0009bb36c27c8c05c10 --- /dev/null +++ b/web/src/shader/atom/common_params.es6 @@ -0,0 +1,36 @@ +/* eslint-disable */ +/** + * @file 公共参数 + * @author yangmingming + */ +export default ` + // varying变量 + // 顶点shader透传的材质坐标 + varying vec2 vCoord; + // 扩展shader的ivec类型 + // struct ivec5 { + // int x; + // int y; + // int z; + // int w; + // int u; + // }; + // struct ivec6 { + // int x; + // int y; + // int z; + // int w; + // int u; + // int v; + // }; + // dynamic的input数据 + const float multi_value = float(MULTI_VALUE); + const float bias_value = float(BIAS_VALUE); + + // 输出数据 + const int width_shape_out = WIDTH_SHAPE_OUT; + const int height_shape_out = HEIGHT_SHAPE_OUT; + const int width_texture_out = WIDTH_TEXTURE_OUT; + const int height_texture_out = HEIGHT_TEXTURE_OUT; + const int channel_out = CHANNEL_OUT; +`; diff --git a/web/src/shader/atom/getArrayIndexFromTensorPos.es6 b/web/src/shader/atom/getArrayIndexFromTensorPos.es6 new file mode 100644 index 0000000000000000000000000000000000000000..b0d17e4f3b9f23655eb4751a61c5277300884813 --- /dev/null +++ b/web/src/shader/atom/getArrayIndexFromTensorPos.es6 @@ -0,0 +1,20 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +export default ` +// TENSOR_TYPE, tensor坐标的类型,ivec4 +// TENSOR_NAME, tensor name + +// 获取tensor坐标对应数组中的索引 +// uniform int numbers_shape_TENSOR_NAME[LENGTH_SHAPE_TENSOR_NAME]; + +int getArrayIndexFromTensorPos_TENSOR_NAME(TENSOR_TYPE tensorPos) { + int index = 0; + for (int i = 0; i < length_shape_TENSOR_NAME; i++) { + index += tensorPos[i] * numbers_shape_TENSOR_NAME[i]; + } + return index; +} +`; diff --git a/web/src/shader/atom/getArrayIndexFromTexturePos.es6 b/web/src/shader/atom/getArrayIndexFromTexturePos.es6 new file mode 100644 index 0000000000000000000000000000000000000000..1cff8a703e44296127864de402ffe6b047ac4b0a --- /dev/null +++ b/web/src/shader/atom/getArrayIndexFromTexturePos.es6 @@ -0,0 +1,18 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +// TEXTURE_NAME, texture name +// WIDTH_TEXTURE_NAME_VALUE, texture的宽度 + +// 获取材质元素在数组中的索引 +// const int width_TEXTURE_NAME = WIDTH_TEXTURE_NAME_VALUE; +export default ` +int getArrayIndexFromTexturePos_TEXTURE_NAME(vec3 pos) { + int x = int(floor(pos.x)); + int y = int(floor(pos.y)); + int d = int(floor(pos.z)); + return (width_TEXTURE_NAME * y + x) * 4 + d; +} +`; diff --git a/web/src/shader/atom/getOutputTensorPos.es6 b/web/src/shader/atom/getOutputTensorPos.es6 new file mode 100644 index 0000000000000000000000000000000000000000..fafe6a198e633fa83bec8106920dba3b440eca92 --- /dev/null +++ b/web/src/shader/atom/getOutputTensorPos.es6 @@ -0,0 +1,16 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +export default ` +ivec4 getOutputTensorPos() { + // 获取原始长度 + vec2 outCoord = moveTexture2PosToReal_texture_out(vCoord.xy); + int x = int(outCoord.x / float(channel_out)); + int c = int(mod(outCoord.x, float(channel_out))); + int y = int(mod(outCoord.y, float(height_shape_out))); + int b = int(outCoord.y / float(height_shape_out)); + return ivec4(b, c, y, x); +} +`; diff --git a/web/src/shader/atom/getPixelsFromTexturePos.es6 b/web/src/shader/atom/getPixelsFromTexturePos.es6 new file mode 100644 index 0000000000000000000000000000000000000000..052bc93759b6eaea78dd3563455d998e0b3d3a8b --- /dev/null +++ b/web/src/shader/atom/getPixelsFromTexturePos.es6 @@ -0,0 +1,11 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +// TEXTURE_NAME, tensor name +// 获取材质中的像素 +// uniform sampler2D TEXTURE_NAME; +export default ` +#define getPixelsFromTexturePos_TEXTURE_NAME(pos) texture2D(TEXTURE_NAME, pos) +`; diff --git a/web/src/shader/atom/getRangePowSumFromArrayIndex.es6 b/web/src/shader/atom/getRangePowSumFromArrayIndex.es6 new file mode 100644 index 0000000000000000000000000000000000000000..7361cc194679cc9f0afb5375a19bfdeb168429e5 --- /dev/null +++ b/web/src/shader/atom/getRangePowSumFromArrayIndex.es6 @@ -0,0 +1,15 @@ +/* eslint-disable */ +/** + * @file 公共方法, 获取[H, W]的power的总和 + * @author yangmingming + */ +export default ` +float getRangePowSumFromArrayIndex_TEXTURE_NAME(int start, float p, float mean) { + float result = 0.0; + for (int i = 0; i < (width_shape_TENSOR_NAME * height_shape_TENSOR_NAME); i++) { + vec3 pos = getTexturePosFromArrayIndex_TEXTURE_NAME(i + start); + result += pow(getValueFromTexturePos_TEXTURE_NAME(pos) - mean, p); + } + return result; +} +`; diff --git a/web/src/shader/atom/getRangeSumFromArrayIndex.es6 b/web/src/shader/atom/getRangeSumFromArrayIndex.es6 new file mode 100644 index 0000000000000000000000000000000000000000..47616e6b5c9b6da65874084eb8df10db1156675c --- /dev/null +++ b/web/src/shader/atom/getRangeSumFromArrayIndex.es6 @@ -0,0 +1,15 @@ +/* eslint-disable */ +/** + * @file 公共方法, 获取[H, W]的总和 + * @author yangmingming + */ +export default ` +float getRangeSumFromArrayIndex_TEXTURE_NAME(int start) { + float result = 0.0; + for (int i = 0; i < (width_shape_TENSOR_NAME * height_shape_TENSOR_NAME); i++) { + vec3 pos = getTexturePosFromArrayIndex_TEXTURE_NAME(i + start); + result += getValueFromTexturePos_TEXTURE_NAME(pos); + } + return result; +} +`; diff --git a/web/src/shader/atom/getTensorPosFromArrayIndex.es6 b/web/src/shader/atom/getTensorPosFromArrayIndex.es6 new file mode 100644 index 0000000000000000000000000000000000000000..e59e9f12d6bb3b4bba8eef7a71138414391971f2 --- /dev/null +++ b/web/src/shader/atom/getTensorPosFromArrayIndex.es6 @@ -0,0 +1,18 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +// TENSOR_NAME, tensor name +// 获取数组元素索引为N的元素,在tensor上的坐标ivec4(batch, channel, height, width) +export default ` +iTENSOR_TYPE getTensorPosFromArrayIndex_TENSOR_NAME(int n) { + iTENSOR_TYPE pos; + pos[0] = n / numbers_shape_TENSOR_NAME[0]; + for (int i = 1; i < length_shape_TENSOR_NAME; i++) { + n = int(mod(float(n), float(numbers_shape_TENSOR_NAME[i - 1]))); + pos[i] = n / numbers_shape_TENSOR_NAME[i]; + } + return pos; +} +`; diff --git a/web/src/shader/atom/getTexturePosFromArrayIndex.es6 b/web/src/shader/atom/getTexturePosFromArrayIndex.es6 new file mode 100644 index 0000000000000000000000000000000000000000..51bb108ec49a63f97e4927d22622fe6fcc624a8d --- /dev/null +++ b/web/src/shader/atom/getTexturePosFromArrayIndex.es6 @@ -0,0 +1,25 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +// TEXTURE_NAME, 材质名称 +// WIDTH_TEXTURE_NAME_VALUE, 材质宽度 +// HEIGHT_TEXTURE_NAME_VALUE, 材质高度 + +// 获取数组元素索引为N的元素,在texture上的坐标 +// const int width_TEXTURE_NAME = WIDTH_TEXTURE_NAME_VALUE; +// const int height_TEXTURE_NAME = HEIGHT_TEXTURE_NAME_VALUE; +export default ` +vec3 getTexturePosFromArrayIndex_TEXTURE_NAME(int n) { + vec3 pos; + pos.z = mod(float(n), 4.0); + n /= 4; + int y = n / width_TEXTURE_NAME; + float width = float(width_TEXTURE_NAME); + float x = mod(float(n), width); + pos.x = x / width; + pos.y = float(y) / float(height_TEXTURE_NAME); + return pos; +} +`; diff --git a/web/src/shader/atom/getValueFromTensorPos.es6 b/web/src/shader/atom/getValueFromTensorPos.es6 new file mode 100644 index 0000000000000000000000000000000000000000..5a5239705339583eaa94a54f83c8e16972657dbc --- /dev/null +++ b/web/src/shader/atom/getValueFromTensorPos.es6 @@ -0,0 +1,19 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +export default ` +float getValueFromTensorPos_TENSOR_NAME(int r, int g, int b, int a) { + vec4 pixels = texture2D(texture_TENSOR_NAME, vec2((float(a * channel_TENSOR_NAME + g) + 0.5) / float(width_texture_TENSOR_NAME), (float(r * height_shape_TENSOR_NAME + b) + 0.5) / float(height_texture_TENSOR_NAME))); + return pixels.r; +} + +float getValueFromTensorPos_TENSOR_NAME(ivec4 pos) { + float offset = 0.5; + float width = float(pos.a * channel_TENSOR_NAME + pos.g) + offset; + float height = float(pos.r * height_shape_TENSOR_NAME + pos.b) + offset; + vec4 pixels = texture2D(texture_TENSOR_NAME, vec2(width / float(width_texture_TENSOR_NAME), height / float(height_texture_TENSOR_NAME))); + return pixels.r; +} +`; diff --git a/web/src/shader/atom/getValueFromTexturePos.es6 b/web/src/shader/atom/getValueFromTexturePos.es6 new file mode 100644 index 0000000000000000000000000000000000000000..f68f3f4a385326cf1574b9cbfae6512f30c8fd2d --- /dev/null +++ b/web/src/shader/atom/getValueFromTexturePos.es6 @@ -0,0 +1,22 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +// TEXTURE_NAME, tensor name +// 获取材质中的数据 +// uniform sampler2D TEXTURE_NAME; +export default ` +float getValueFromTexturePos_TEXTURE_NAME(vec3 pos) { + vec4 pixels = texture2D(TEXTURE_NAME, pos.xy); + int d = int(pos.z); + if (d == 0) { + return pixels.r; + } else if (d == 1) { + return pixels.g; + } else if (d == 2) { + return pixels.b; + } + return pixels.a; +} +`; diff --git a/web/src/shader/atom/moveTexture2PosToReal.es6 b/web/src/shader/atom/moveTexture2PosToReal.es6 new file mode 100644 index 0000000000000000000000000000000000000000..05a7bbfe8f52f5c2d5e28e7ab23aac233a73bfde --- /dev/null +++ b/web/src/shader/atom/moveTexture2PosToReal.es6 @@ -0,0 +1,19 @@ +/* eslint-disable */ +/** + * @file 公共方法 + * @author yangmingming + */ +// TEXTURE_NAME, 材质name +// 材质坐标转化成真实尺寸坐标 +export default ` + +vec2 _2d_shape_TEXTURE_NAME = vec2(float(width_TEXTURE_NAME), float(height_TEXTURE_NAME)); + +vec2 moveTexture2PosToReal_TEXTURE_NAME(vec2 v) { + return v * _2d_shape_TEXTURE_NAME; + // vec2 v2; + // v2.x = v.x * float(width_TEXTURE_NAME); + // v2.y = v.y * float(height_TEXTURE_NAME); + // return v2; +} +`; diff --git a/web/src/shader/atom/prefix.es6 b/web/src/shader/atom/prefix.es6 new file mode 100644 index 0000000000000000000000000000000000000000..54a1742088c915dc27f66459bac15f0c8426e1a9 --- /dev/null +++ b/web/src/shader/atom/prefix.es6 @@ -0,0 +1,14 @@ +/* eslint-disable */ +/** + * @file 预设条件 + * @author yangmingming + */ +export default ` +#ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + precision highp int; +#else + precision mediump float; + precision mediump int; +#endif +`; diff --git a/web/src/shader/atom/prelu.es6 b/web/src/shader/atom/prelu.es6 new file mode 100644 index 0000000000000000000000000000000000000000..36b63f8c821d5f33dc6941f2eacb73b94ae4a356 --- /dev/null +++ b/web/src/shader/atom/prelu.es6 @@ -0,0 +1,15 @@ +/* eslint-disable */ +/** + * @file 激活函数 + * @author yangmingming + */ +// 激活函数 +export default ` +float prelu(float x, float p, float b) { + float result = x; + if (x < 0.0) { + result = x * p; + } + return result; +} +`; diff --git a/web/src/shader/atom/scale.es6 b/web/src/shader/atom/scale.es6 new file mode 100644 index 0000000000000000000000000000000000000000..05a69f32fb89e6b4f5df4feb0b09316bfb401418 --- /dev/null +++ b/web/src/shader/atom/scale.es6 @@ -0,0 +1,11 @@ +/* eslint-disable */ +/** + * @file 激活函数 + * @author yangmingming + */ +export default ` +float scale(float x, float p, float b) { + float result = p * x + b; + return result; +} +`; diff --git a/web/src/shader/atom/sigmoid.es6 b/web/src/shader/atom/sigmoid.es6 new file mode 100644 index 0000000000000000000000000000000000000000..a6c3eac0c9d9fb1d075668947bdf1745e0310188 --- /dev/null +++ b/web/src/shader/atom/sigmoid.es6 @@ -0,0 +1,12 @@ +/* eslint-disable */ +/** + * @file 激活函数 + * @author yangmingming + */ +// 激活函数 +export default ` +float sigmoid(float x, float y, float z) { + float result = 1.0 / (1.0 + exp(-x)); + return result; +} +`; diff --git a/web/src/shader/atom/softmax.es6 b/web/src/shader/atom/softmax.es6 new file mode 100644 index 0000000000000000000000000000000000000000..04bd1a20069ccf7dc2f9df9ce13d5fa7be3a9c55 --- /dev/null +++ b/web/src/shader/atom/softmax.es6 @@ -0,0 +1,14 @@ +/* eslint-disable */ +/** + * @file softmax激活函数 + * @author yangmingming + */ +export default ` +float softmax(float x, float p, float b) { + float result = x; + if (x < 0.0) { + result = x * p; + } + return result; +} +`; diff --git a/web/src/shader/atom/suffix.es6 b/web/src/shader/atom/suffix.es6 new file mode 100644 index 0000000000000000000000000000000000000000..97eb819ffd0b06262886782bf11ad7588558996d --- /dev/null +++ b/web/src/shader/atom/suffix.es6 @@ -0,0 +1,17 @@ +/* eslint-disable */ +/** + * @file 公共方法-尾部, 方法1: 获取输出坐标 + * @author yangmingming + */ +export default ` +vec2 _2d_shape_texture_out = vec2(float(width_texture_out), float(height_texture_out)); +ivec4 getOutputTensorPos() { + // 获取原始长度 + vec2 outCoord = vCoord.xy * _2d_shape_texture_out; + int x = int(outCoord.x / float(channel_out)); + int c = int(mod(outCoord.x, float(channel_out))); + int y = int(mod(outCoord.y, float(height_shape_out))); + int b = int(outCoord.y / float(height_shape_out)); + return ivec4(b, c, y, x); +} +`; diff --git a/web/src/shader/atom/type_ivec56.es6 b/web/src/shader/atom/type_ivec56.es6 new file mode 100644 index 0000000000000000000000000000000000000000..4b70e44ca878d2fa1ebeafe3353da3cbe3659edb --- /dev/null +++ b/web/src/shader/atom/type_ivec56.es6 @@ -0,0 +1,23 @@ +/* eslint-disable */ +/** + * @file 新增类型 + * @author yangmingming + */ +// 扩展shader的ivec类型 +export default ` +struct ivec5 { + int x; + int y; + int z; + int w; + int u; +}; +struct ivec6 { + int x; + int y; + int z; + int w; + int u; + int v; +}; +`; diff --git a/web/src/shader/batchnorm/conf.es6 b/web/src/shader/batchnorm/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..7cdfd95e6392ff5543e36df3bf9248c263ed6ef3 --- /dev/null +++ b/web/src/shader/batchnorm/conf.es6 @@ -0,0 +1,57 @@ +/* eslint-disable */ +/** + * @file batchnorm的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + }, + { + func: 'getPixelsFromTexturePos', + conf: { + TEXTURE_NAME: 'texture_scale' + } + } + ], + conf: [ + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + 'TOTAL_SHAPE_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + + 'EPSILON', + 'WIDTH_TEXTURE_SCALE', + 'HEIGHT_TEXTURE_SCALE', + 'MULTI_VALUE', + 'BIAS_VALUE', + 'ACTIVE_FUNCTION' + ], + input: [ + { + tensor: 'scale', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/batchnorm/main.es6 b/web/src/shader/batchnorm/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..ae702250ff075278d14e7c69efe57a926ec51d86 --- /dev/null +++ b/web/src/shader/batchnorm/main.es6 @@ -0,0 +1,18 @@ +/* eslint-disable */ +/** + * @file softmax主函数 + * @author yangmingming + */ +export default ` +// start函数 +void main(void) { + // 输出数据 + ivec4 oPos = getOutputTensorPos(); + float o = getValueFromTensorPos_origin(oPos); + // 归一化数据 + vec4 scale = getPixelsFromTexturePos_texture_scale(vec2((float(int(oPos.g)) + 0.5) / float(width_texture_scale), 0.0)); + float x = (o - scale[3]) / sqrt(scale[2] + epsilon); + float res = scale[0] * x + scale[1]; + gl_FragColor.r = res; +} +`; diff --git a/web/src/shader/batchnorm/params.es6 b/web/src/shader/batchnorm/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..2592344029b7e68e25d86f7a6fada9ff62237b18 --- /dev/null +++ b/web/src/shader/batchnorm/params.es6 @@ -0,0 +1,24 @@ +/* eslint-disable */ +/** + * @file batchnorm参数文件 + * @author yangmingming + */ +export default ` +// 输入数据 +const int width_shape_origin = WIDTH_SHAPE_ORIGIN; +const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; +const int length_shape_origin = LENGTH_SHAPE_ORIGIN; +const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; +const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; +const int channel_origin = CHANNEL_ORIGIN; +const int total_shape_origin = TOTAL_SHAPE_ORIGIN; + +// 计算数据 +const float epsilon = float(EPSILON); +const int width_texture_scale = WIDTH_TEXTURE_SCALE; +const int height_texture_scale = HEIGHT_TEXTURE_SCALE; + +// 输入数据 +uniform sampler2D texture_origin; +uniform sampler2D texture_scale; +`; diff --git a/web/src/shader/conv2d/conf.es6 b/web/src/shader/conv2d/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..c66c40a9d36ee7fdb0ad0539da041efb1e5b819c --- /dev/null +++ b/web/src/shader/conv2d/conf.es6 @@ -0,0 +1,85 @@ +/* eslint-disable */ +/** + * @file conv2d的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + }, + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'filter' + } + } + ], + conf: [ + 'LENGTH_SHAPE_FILTER', + 'WIDTH_SHAPE_FILTER', + 'HEIGHT_SHAPE_FILTER', + 'WIDTH_TEXTURE_FILTER', + 'HEIGHT_TEXTURE_FILTER', + 'CHANNEL_FILTER', + + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + + 'STRIDE_HORIZONTAL', + 'STRIDE_VERTICAL', + 'PAD_LEFT', + 'PAD_TOP', + 'DILATION_HORIZONTAL', + 'DILATION_VERTICAL', + 'GROUPS', + 'MULTI_VALUE', + 'BIAS_VALUE', + 'ACTIVE_FUNCTION' + ], + input: [ + // { + // tensor: 'filter', + // variable: 'numbers_shape', + // setter: 'uniform1iv', + // type: 'uniform' + // }, + { + tensor: 'filter', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + // { + // tensor: 'origin', + // variable: 'numbers_shape', + // setter: 'uniform1iv', + // type: 'uniform' + // }, + // { + // tensor: 'out', + // variable: 'numbers_shape', + // setter: 'uniform1iv', + // type: 'uniform' + // } + ] +}; diff --git a/web/src/shader/conv2d/main.es6 b/web/src/shader/conv2d/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..e6513899af5b8220442dbf831248ed1237d58ed6 --- /dev/null +++ b/web/src/shader/conv2d/main.es6 @@ -0,0 +1,48 @@ +/* eslint-disable */ +/** + * @file 主函数 + * @author yangmingming + */ +export default ` + // start函数 + void main(void) { + ivec4 oPos = getOutputTensorPos(); + int x = oPos.a; + int c = oPos.g; + int y = oPos.b; + int b = oPos.r; + float res = 0.0; + + // 获取output的坐标 + int oTensorChannel = (c / (channel_out / groups)) * channel_filter; + int oy = y * stride_v - padTop; + for (int fy = 0; fy < height_shape_filter; fy++) { + if (oy >= height_shape_origin) { + break; + } + if (oy < 0) { + oy += dilation_v; + continue; + } + int ox = x * stride_h - padLeft; + for (int fx = 0; fx < width_shape_filter; fx++) { + if (ox >= width_shape_origin) { + break; + } + if (ox < 0) { + ox += dilation_h; + continue; + } + // channel计算 + for (int j = 0; j < channel_filter; j++) { + float f = getValueFromTensorPos_filter(c, j, fy, fx); + float o = getValueFromTensorPos_origin(b, oTensorChannel + j, oy, ox); + res += f * o; + } + ox += dilation_h; + } + oy += dilation_v; + } + gl_FragColor.r = res; + } +`; diff --git a/web/src/shader/conv2d/params.es6 b/web/src/shader/conv2d/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..367072995aad6bab9649aa01f10376f95df28c4a --- /dev/null +++ b/web/src/shader/conv2d/params.es6 @@ -0,0 +1,45 @@ +/* eslint-disable */ +/** + * @file 参数文件 + * @author yangmingming + */ +export default ` + // conv2d的input数据 + + // 常量 + // 卷积核 + const int length_shape_filter = LENGTH_SHAPE_FILTER; + const int width_shape_filter = WIDTH_SHAPE_FILTER; + const int height_shape_filter = HEIGHT_SHAPE_FILTER; + const int width_texture_filter = WIDTH_TEXTURE_FILTER; + const int height_texture_filter = HEIGHT_TEXTURE_FILTER; + const int channel_filter = CHANNEL_FILTER; + + // 输入数据 + const int width_shape_origin = WIDTH_SHAPE_ORIGIN; + const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; + const int length_shape_origin = LENGTH_SHAPE_ORIGIN; + const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; + const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; + const int channel_origin = CHANNEL_ORIGIN; + + // 计算相关 + // 拆分步长 + const int stride_h = STRIDES_X; + const int stride_v = STRIDES_Y; + // padding的数目 + const int padLeft = PADDINGS_X; + const int padTop = PADDINGS_Y; + // dilation膨胀系数 + const int dilation_h = DILATIONS_X; + const int dilation_v = DILATIONS_Y; + // groups + const int groups = GROUPS; + + // uniform变量 + // 卷积核 + uniform sampler2D texture_filter; + + // 输入数据 + uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/dynamic/conf.es6 b/web/src/shader/dynamic/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..2541ecf7555417ceb65f1b131e3404c484f715b6 --- /dev/null +++ b/web/src/shader/dynamic/conf.es6 @@ -0,0 +1,34 @@ +/* eslint-disable */ +/** + * @file dynamic的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getPixelsFromTexturePos', + conf: { + TEXTURE_NAME: 'texture_origin' + } + } + ], + conf: [ + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + + 'MULTI_VALUE', + 'BIAS_VALUE', + 'ACTIVE_FUNCTION' + ], + input: [ + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/dynamic/main.es6 b/web/src/shader/dynamic/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..f747a477d774953f1b9de2fde5346e9cc457701f --- /dev/null +++ b/web/src/shader/dynamic/main.es6 @@ -0,0 +1,14 @@ +/* eslint-disable */ +/** + * @file 主函数 + * @author yangmingming + */ +export default ` +// start函数 +void main(void) { + // 输出数据 + float o = getPixelsFromTexturePos_texture_origin(vCoord).r; + float res = ACTIVE_FUNCTION(o, multi_value, bias_value); + gl_FragColor.r = res; +} +`; diff --git a/web/src/shader/dynamic/params.es6 b/web/src/shader/dynamic/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..3eb029546639c32151137086ead0ff429c583c8d --- /dev/null +++ b/web/src/shader/dynamic/params.es6 @@ -0,0 +1,9 @@ +/* eslint-disable */ +/** + * @file 参数文件 + * @author yangmingming + */ +export default ` + // 输入数据 + uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/elementwise_add/conf.es6 b/web/src/shader/elementwise_add/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..a1cef63cad015f4921a3a43ec98b9a87bd76946a --- /dev/null +++ b/web/src/shader/elementwise_add/conf.es6 @@ -0,0 +1,56 @@ +/* eslint-disable */ +/** + * @file 加法的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getPixelsFromTexturePos', + conf: { + TEXTURE_NAME: 'texture_origin' + } + }, + { + func: 'getPixelsFromTexturePos', + conf: { + TEXTURE_NAME: 'texture_counter' + } + } + ], + conf: [ + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'TOTAL_SHAPE_COUNTER', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + + 'AXIS', + 'MULTI_VALUE', + 'BIAS_VALUE', + 'ACTIVE_FUNCTION' + ], + input: [ + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'counter', + variable: 'data', + setter: 'uniform1fv', + type: 'uniform' + } + ] +}; diff --git a/web/src/shader/elementwise_add/main.es6 b/web/src/shader/elementwise_add/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..5e9b1bd12579c52d0dfc2a12ade27a2a870984b3 --- /dev/null +++ b/web/src/shader/elementwise_add/main.es6 @@ -0,0 +1,17 @@ +/* eslint-disable */ +/** + * @file 加法主函数 + * @author yangmingming + */ +export default ` +// start函数 +void main(void) { + // 输出数据 + ivec4 oPos = getOutputTensorPos(); + int index = oPos[axis]; + float o = getPixelsFromTexturePos_texture_origin(vCoord).r; + float c = getValueFromCounter(index); + float res = ACTIVE_FUNCTION(o + c, multi_value, bias_value); + gl_FragColor.r = res; +} +`; diff --git a/web/src/shader/elementwise_add/params.es6 b/web/src/shader/elementwise_add/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..4ca394ef83b59d50495ae77f35eb0b1b2ee8530c --- /dev/null +++ b/web/src/shader/elementwise_add/params.es6 @@ -0,0 +1,20 @@ +/* eslint-disable */ +/** + * @file 加法参数 + * @author yangmingming + */ +export default ` + // 输入数据 + const int axis = AXIS; + // const int total_shape_counter = TOTAL_SHAPE_COUNTER; + uniform float data_counter[TOTAL_SHAPE_COUNTER]; + uniform sampler2D texture_origin; + float getValueFromCounter(int index) { + for (int i = 0; i < TOTAL_SHAPE_COUNTER; i++) { + if (i == index) { + return data_counter[i]; + } + } + return 0.0; + } +`; diff --git a/web/src/shader/mul/conf.es6 b/web/src/shader/mul/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..25b657575793debfb79e6abb30826ffcbd5d563b --- /dev/null +++ b/web/src/shader/mul/conf.es6 @@ -0,0 +1,56 @@ +/* eslint-disable */ +/** + * @file mul的配置文件 + * @author yangmingming zhangmiao06 + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'counter' + } + }, + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + } + ], + conf: [ + 'LENGTH_SHAPE_COUNTER', + 'WIDTH_SHAPE_COUNTER', + 'HEIGHT_SHAPE_COUNTER', + 'WIDTH_TEXTURE_COUNTER', + 'HEIGHT_TEXTURE_COUNTER', + 'CHANNEL_COUNTER', + + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT' + ], + input: [ + { + tensor: 'counter', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/mul/main.es6 b/web/src/shader/mul/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..4628b96ec37d24d00442aed660323fdf990cf934 --- /dev/null +++ b/web/src/shader/mul/main.es6 @@ -0,0 +1,18 @@ +/* eslint-disable */ +/** + * @file mul主函数 + */ +export default ` +// start函数 +void main(void) { + float res = 0.0; + // 获取output的坐标 + ivec4 out_pos = getOutputTensorPos(); + for (int j = 0; j < width_shape_origin; j++) { + float c = getValueFromTensorPos_counter(out_pos[0], out_pos[1], j, out_pos[3]); + float o = getValueFromTensorPos_origin(out_pos[0], out_pos[1], out_pos[2], j); + res += c * o; + } + gl_FragColor.r = res; +} +`; diff --git a/web/src/shader/mul/params.es6 b/web/src/shader/mul/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..4f9ec64503ef9592dab0b33c0391295f412c9e03 --- /dev/null +++ b/web/src/shader/mul/params.es6 @@ -0,0 +1,27 @@ +/* eslint-disable */ +/** + * @file mul参数文件 + */ +export default ` +// mul的input数据 +// 常量 +// 输入数据 +const int length_shape_counter = LENGTH_SHAPE_COUNTER; +const int width_shape_counter = WIDTH_SHAPE_COUNTER; +const int height_shape_counter = HEIGHT_SHAPE_COUNTER; +const int width_texture_counter = WIDTH_TEXTURE_COUNTER; +const int height_texture_counter = HEIGHT_TEXTURE_COUNTER; +const int channel_counter = CHANNEL_COUNTER; + +const int width_shape_origin = WIDTH_SHAPE_ORIGIN; +const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; +const int length_shape_origin = LENGTH_SHAPE_ORIGIN; +const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; +const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; +const int channel_origin = CHANNEL_ORIGIN; + +// uniform变量 +// 输入数据 +uniform sampler2D texture_counter; +uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/pool2d/conf.es6 b/web/src/shader/pool2d/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..9d2ca4d0db414b10e39cb94a13e44498e84f3144 --- /dev/null +++ b/web/src/shader/pool2d/conf.es6 @@ -0,0 +1,47 @@ +/* eslint-disable */ +/** + * @file pool2d的配置文件 + * @author yangmingming zhangmiao06 + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + } + ], + conf: [ + 'KSIZE_X', + 'KSIZE_Y', + 'TYPE_POOL', + + 'WIDTH_SHAPE_ORIGIN', + 'HEIGHT_SHAPE_ORIGIN', + 'LENGTH_SHAPE_ORIGIN', + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'CHANNEL_ORIGIN', + + 'WIDTH_SHAPE_OUT', + 'HEIGHT_SHAPE_OUT', + 'WIDTH_TEXTURE_OUT', + 'HEIGHT_TEXTURE_OUT', + 'CHANNEL_OUT', + + 'STRIDES_X', + 'STRIDES_Y', + 'PADDING_X', + 'PADDING_Y' + ], + input: [ + // texture类型,若添加from: 'prev', 表示读取上一个op的产出 + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/pool2d/main.es6 b/web/src/shader/pool2d/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..df295618a41259162d4ec118fd4d0b0ce8b94c42 --- /dev/null +++ b/web/src/shader/pool2d/main.es6 @@ -0,0 +1,49 @@ +/* eslint-disable */ +/** + * @file pool2d主函数 + */ +export default ` +// start函数 +void main(void) { + float res = (-1.0 / exp(-20.0)); + // 获取output的坐标 + ivec4 out_pos = getOutputTensorPos(); + // X、Y方向的移动步长 + int count_pool = 0; + int oy_base = out_pos[2] * stride_v - padTop; + int ox_base = out_pos[3] * stride_h - padLeft; + for (int fy = 0; fy < height_shape_pool; fy++) { + int oy = oy_base + fy; + if (oy >= height_shape_origin) { + break; + } + if (oy < 0) { + continue; + } + for (int fx = 0; fx < width_shape_pool; fx++) { + int ox = ox_base + fx; + if (ox >= width_shape_origin) { + break; + } + if (ox < 0) { + continue; + } + // origin数据 + float curr = getValueFromTensorPos_origin(out_pos[0], out_pos[1], oy, ox); + if (type_pool == 1) { + if (curr > res) { + res = curr; + } + } else { + res += curr; + // 在平均池化模式忽略填充值(exclusive默认为true) + count_pool++; + } + } + } + if (type_pool != 1) { + res = res / float(count_pool); + } + gl_FragColor.r = res; +} +`; diff --git a/web/src/shader/pool2d/params.es6 b/web/src/shader/pool2d/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..e836276e32d5278bc8075c2edba7689e84c1659d --- /dev/null +++ b/web/src/shader/pool2d/params.es6 @@ -0,0 +1,30 @@ +/* eslint-disable */ +/** + * @file pool2d参数文件 + */ +export default ` +// 常量 +// 池化大小 +const int width_shape_pool = KSIZE_X; +const int height_shape_pool = KSIZE_Y; +const int type_pool = TYPE_POOL; +// 输入数据 +const int width_shape_origin = WIDTH_SHAPE_ORIGIN; +const int height_shape_origin = HEIGHT_SHAPE_ORIGIN; +const int length_shape_origin = LENGTH_SHAPE_ORIGIN; +const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; +const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; +const int channel_origin = CHANNEL_ORIGIN; + +// 计算相关 +// 拆分步长 +const int stride_h = STRIDES_X; +const int stride_v = STRIDES_Y; +// padding的数目 +const int padLeft = PADDINGS_X; +const int padTop = PADDINGS_Y; + + +// uniform变量 +uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/softmax/conf.es6 b/web/src/shader/softmax/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..ff04060e3b116dd46dbf0af3646c2eb248574c6e --- /dev/null +++ b/web/src/shader/softmax/conf.es6 @@ -0,0 +1,28 @@ +/* eslint-disable */ +/** + * @file softmax的配置文件 + * @author yangmingming + */ +export default { + dep: [ + { + func: 'getPixelsFromTexturePos', + conf: { + TEXTURE_NAME: 'texture_origin' + } + } + ], + conf: [ + 'WIDTH_TEXTURE_ORIGIN', + 'HEIGHT_TEXTURE_ORIGIN', + 'TOTAL_SHAPE_ORIGIN' + ], + input: [ + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/web/src/shader/softmax/main.es6 b/web/src/shader/softmax/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..3f7ba78af0373c46d6f2ebd1382ab334507aed2f --- /dev/null +++ b/web/src/shader/softmax/main.es6 @@ -0,0 +1,55 @@ +/* eslint-disable */ +/** + * @file softmax主函数 + * @author yangmingming + */ +export default ` +// start函数 +void main(void) { + vec4 v4 = getPixelsFromTexturePos_texture_origin(vCoord); + vec2 onePixel = vec2(1.0 / float(width_texture_origin), 1.0 / float(height_texture_origin)); + float total = 0.0; + float maxValue = getPixelsFromTexturePos_texture_origin(onePixel).r; + int number = 0; + vec4 pixels; + // 求最大 + for (int i = 0; i < height_texture_origin; i++) { + for (int j = 0; j < width_texture_origin; j++) { + pixels = getPixelsFromTexturePos_texture_origin(onePixel * vec2(float(j), float(i))); + number = i * width_texture_origin + j; + if ((number * 4 + 1) < total_shape_origin) { + maxValue = max(pixels.r, maxValue); + } + if ((number * 4 + 2) < total_shape_origin) { + maxValue = max(pixels.g, maxValue); + } + if ((number * 4 + 3) < total_shape_origin) { + maxValue = max(pixels.b, maxValue); + } + if ((number * 4 + 4) < total_shape_origin) { + maxValue = max(pixels.a, maxValue); + } + } + } + // 求和 + for (int i = 0; i < height_texture_origin; i++) { + for (int j = 0; j < width_texture_origin; j++) { + pixels = getPixelsFromTexturePos_texture_origin(onePixel * vec2(float(j), float(i))); + number = i * width_texture_origin + j; + if ((number * 4 + 1) < total_shape_origin) { + total += exp(pixels.r - maxValue); + } + if ((number * 4 + 2) < total_shape_origin) { + total += exp(pixels.g - maxValue); + } + if ((number * 4 + 3) < total_shape_origin) { + total += exp(pixels.b - maxValue); + } + if ((number * 4 + 4) < total_shape_origin) { + total += exp(pixels.a - maxValue); + } + } + } + gl_FragColor = exp(v4 - vec4(maxValue, maxValue, maxValue, maxValue)) / vec4(total, total, total, total) ; +} +`; diff --git a/web/src/shader/softmax/params.es6 b/web/src/shader/softmax/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..514aeba721f07d038848025f24b589b443452ddc --- /dev/null +++ b/web/src/shader/softmax/params.es6 @@ -0,0 +1,15 @@ +/* eslint-disable */ +/** + * @file softmax参数文件 + * @author yangmingming + */ +export default ` +// 输入数据 +const int width_texture_origin = WIDTH_TEXTURE_ORIGIN; +const int height_texture_origin = HEIGHT_TEXTURE_ORIGIN; +const int total_shape_origin = TOTAL_SHAPE_ORIGIN; + +// uniform变量 +// 输入数据 +uniform sampler2D texture_origin; +`; diff --git a/web/src/shader/v_shader.es6 b/web/src/shader/v_shader.es6 new file mode 100644 index 0000000000000000000000000000000000000000..47ecbc24c2097428d38eeae8b4266e7e66515d2e --- /dev/null +++ b/web/src/shader/v_shader.es6 @@ -0,0 +1,15 @@ +/* eslint-disable */ +/** + * @file 顶点文件 + * @author yangmingming + */ +export default ` +attribute vec4 position; +varying vec2 vCoord; + +void main() { + vCoord.x = (position.x + 1.0) / 2.0; + vCoord.y = (position.y + 1.0) / 2.0; + gl_Position = position; +} +`; diff --git a/web/src/utils/opData.es6 b/web/src/utils/opData.es6 new file mode 100644 index 0000000000000000000000000000000000000000..ae9d21f3b85fd2780bd729494ff2bef2b84fac3a --- /dev/null +++ b/web/src/utils/opData.es6 @@ -0,0 +1,312 @@ +/* eslint-disable */ +import Utils from './utils'; +import Tensor from './tensor'; +/** + * @file op的数据对象 + * @author yangmingming + * + */ +const keys = [ + 'paddings', + 'strides', + 'dilations', + 'ksize' +]; +// 从tensor对象中获取的数据 +const tensorAttrs = [ + 'length_shape', + 'width_shape', + 'height_shape', + 'width_texture', + 'height_texture', + 'channel', + 'total_shape' +]; +// shader中需要的常量 +const shaderAttrs = { + scale: { + 'bias': 'bias_value', + 'scale': 'multi_value' + }, + pool2d: { + 'pooling_type': 'type_pool' + } +}; +// model的名字和paddle web的tensor名字mapping +const tensorName = { + 'input': 'origin', + 'x': 'origin', + 'filter': 'filter', + 'y': 'counter', + 'output': 'out', + 'out': 'out', + 'scale': 'scale', + 'bias': 'bias', + 'mean': 'mean', + 'variance': 'variance' +}; +// unique behavior +const opBehavior = { + conv2d: [ + 'needBatch' + ], + batchnorm: [ + 'needBatch', + 'mergeTensor' + ], + elementwise_add: [ + 'broadcast', + 'needBatch' + ], + pool2d: [ + 'isMax', + 'needBatch', + 'isGlobalPooling' + ], + relu: [ + 'transToPrelu', + 'needBatch' + ], + leaky_relu: [ + 'transToLeakyrelu', + 'needBatch' + ], + mul: [ + 'reshape', + 'needBatch' + ] +}; +export default class OpData { + constructor(name, input = {}, output = {}, attrs = {}) { + this.name = name; + this.attrs = attrs; + // 是否忽略当前当前op, 使用dropout + this.isPass = this.checkIsPass(); + if (this.isPass) { + this.input = input; + this.output = output; + // op数据, 当前不扩展 + this.data = { + 'active_function': 'scale', + 'multi_value': '1.0', + 'bias_value': '0.0' + }; + // tensor数据 + this.tensor = {}; + this.buildTensor(); + this.buildAttrs(); + } + } + + buildTensor() { + // todo: 是否需要形状对齐 + // todo: 是否需要广播tensor + const tensorData = []; + for (let key in this.input) { + if (this.input.hasOwnProperty(key)) { + const data = this.input[key] || [{}]; + // 默认取第一个数据 + if (tensorName[key.toLowerCase()]) { + data[0].tensorName = tensorName[key.toLowerCase()]; + tensorData.push(data[0]); + } + } + } + // debugger + // todo: 临时删除output里的Y + delete this.output.Y; + // 输出tensor + for (let key in this.output) { + if (this.output.hasOwnProperty(key)) { + // 默认取第一个数据 + const data = this.output[key] || [{}]; + if (tensorName[key.toLowerCase()]) { + data[0].tensorName = tensorName[key.toLowerCase()]; + tensorData.push(data[0]); + } + } + } + // unique behavior + const behavior = opBehavior[this.name] || []; + behavior.forEach(behavior => { + this[behavior](tensorData); + }); + // 生成tensor对象 + tensorData.forEach(data => { + if (data) { + if (data.notTensor) { + this.tensor[data.tensorName] = { + name: data.tensorName, + data: new Float32Array(data.data), + total_shape: data.data.length + }; + } else { + this.tensor[data.tensorName] = new Tensor({ + name: data.tensorName, + shape: data.shape, + data: data.data, + needBatch: data.needBatch || false, + notCompressed: data.notCompressed || false + }); + } + } + }); + // console.dir(['tensors', this.tensor]); + } + + buildAttrs() { + // 计算属性 + for (let key in this.attrs) { + if (this.attrs.hasOwnProperty(key)) { + const item = this.attrs[key]; + if (Object.prototype.toString.call(item) === '[object Array]') { + if (keys.indexOf(key) > -1) { + this.data[key + '_x'] = item[0]; + this.data[key + '_y'] = item[1]; + } + } else { + this.data[key] = item; + // 获取shader所需的数据 + let shaderAttr = shaderAttrs[this.name]; + if (shaderAttr && shaderAttr.hasOwnProperty(key)) { + this.data[shaderAttr[key]] = item; + } + } + } + } + // 获取tensor的数据 + for (let key in this.tensor) { + const tensor = this.tensor[key]; + tensorAttrs.forEach(attr => { + this.data[attr+ '_' + tensor.name] = tensor[attr]; + }); + } + } + + needBatch(tensorData = []) { + tensorData.forEach(data => (data.needBatch = true)); + } + + isGlobalPooling(tensorData = []) { + let counter = tensorData.filter(tensor => (tensor.tensorName === 'origin'))[0] || {}; + let length = counter.shape && counter.shape.length || 0; + if (length > 2 && this.attrs['global_pooling']) { + this.attrs.ksize = [counter.shape[length - 2], counter.shape[length - 1]]; + } + } + + broadcast(tensorData = []) { + const x = tensorData[0]; + const y = tensorData[1]; + let small = y; + if (x.shape.length - y.shape.length < 0) { + small = x; + } + // face model + small.notTensor = true; + return; + + // mobilenet model + // todo: 默认y的shape length是1, 以后需要实现通用版本 + let shape = Utils.getBroadcastShapeInPaddle(x.shape, y.shape, this.attrs['axis']); + // 填充shape数据 + if (small.shape.length === 1) { + const result = []; + small.shape = shape; + let total = shape.reduce((all, num) => all * num); + for (let i = 0; i < small.shape[0]; i++) { + let item = small.data[i]; + for (let j = 0; j < total / shape[0]; j++) { + result.push(item); + } + } + small.data = result; + } + } + + isMax(tensorData = []) { + const type = this.attrs['pooling_type'] === 'max' ? 1 : 0; + this.attrs['pooling_type'] = type; + } + + transToPrelu(tensorData = []) { + this.data['multi_value'] = '0.0'; + this.data['active_function'] = 'prelu'; + } + + transToLeakyrelu(tensorData = []) { + this.data['multi_value'] = this.attrs.alpha; + this.data['active_function'] = 'leakyRelu'; + this.name = 'relu'; + } + + setActiveFunc(tensorData = []) { + this.data['multi_value'] = '0.0'; + this.data['active_function'] = 'softmax'; + + } + + reshape(tensorData = []) { + let input = tensorData[0]; + let counter = tensorData[1]; + if (counter.shape.length > input.shape.length) { + input = tensorData[1]; + counter = tensorData[0]; + } + if (input.shape.length > 2 && counter.shape.length === 2) { + let shape = Utils.getReshapeInPaddle(input.shape, counter.shape, tensorData[2].shape); + input.shape = shape; + } + + } + + mergeTensor(tensorData = []) { + // 融合scale、bias、variance、mean + let constants = ['scale', 'bias', 'variance', 'mean']; + let result = {}; + let data = []; + tensorData.forEach((tensor, index) => { + result[tensor.tensorName] = tensor; + result[tensor.tensorName + 'Index'] = index; + }); + for (let i = 0; i < result[constants[0]].shape[0]; i++) { + data.push(result[constants[0]].data[i]); + data.push(result[constants[1]].data[i]); + data.push(result[constants[2]].data[i]); + data.push(result[constants[3]].data[i]); + } + tensorData[result[constants[0] + 'Index']].data = data; + // 充分利用shader空间 + tensorData[result[constants[0] + 'Index']].notCompressed = true; + tensorData[result[constants[0] + 'Index']].shape[0] *= 4; + tensorData.splice(result[constants[1] + 'Index'], 1, 0); + tensorData.splice(result[constants[2] + 'Index'], 1, 0); + tensorData.splice(result[constants[3] + 'Index'], 1, 0); + } + + checkIsPass() { + if (this.name === 'dropout') { + if (this.attrs['dropout_implementation'] === 'downgrade_in_infer') { + this.name = 'scale'; + this.attrs['scale'] = this.attrs['dropout_prob']; + this.attrs['bias'] = 0.0; + return true; + } + return false; + } + if (this.name === 'depthwise_conv2d') { + this.name = 'conv2d'; + } + return true; + } + + dispose() { + this.input = null; + this.output = null; + this.attrs = null; + for (let key in this.tensor) { + this.tensor[key].dispose(); + } + this.tensor = {}; + } +} diff --git a/web/src/utils/tensor.es6 b/web/src/utils/tensor.es6 new file mode 100644 index 0000000000000000000000000000000000000000..3afad8eb2eb9b75be450002929a01486990472f8 --- /dev/null +++ b/web/src/utils/tensor.es6 @@ -0,0 +1,139 @@ +/* eslint-disable */ +import Utils from './utils'; +/** + * @file Tensor类 + * @author yangmingming + */ +export default class Tensor { + constructor(opts = {}) { + this.opts = opts; + // 设置tensor名字 + this.name = opts.name; + // tensor的形状 + let shape = this.shape = opts.shape; + // 原始数据个数 + this.total = shape.reduce((all, num) => all * num); + // 图像tensor是否带有batch + if (opts.needBatch && shape.length < 4) { + let batch = []; + for (let i = 0; i < (4 - shape.length); i++) { + batch.push(1); + } + shape = batch.concat(shape); + this.shape = shape; + } + // 获取转换到texture后的信息 + let {zeroNumber, shape: shape_texture} = Utils.getTextureInfoFromTensorShape(shape); + this.shape_texture = shape_texture; + // tensor数据 + let data = []; + if (opts.data && opts.data.length) { + if (!opts.notCompressed) { + let b = shape[0]; + let c = shape[1]; + let h = shape[2]; + let w = shape[3]; + for (let i = 0; i < opts.data.length; i++) { + let j = Math.floor(i / (c * w)); + let k = Math.floor(i % (c * w)); + let b1 = Math.floor(j / h); + let h1 = Math.floor(j % h); + let c1 = Math.floor(k % c); + let w1 = Math.floor(k / c); + let l = b1 * (c * h * w) + c1 * (h * w) + h1 * (w) + w1; + data.push(opts.data[l]); + data.push(0); + data.push(0); + data.push(0); + } + } else { + // batchnorm的scale + this.shape_texture = [4, 1, this.total / 4]; + data = [].concat(opts.data); + } + + this.data = new Float32Array(data); + // 清理缓存 + opts.data = null; + } + } + + /** + * 获取数组下标, shape例子[M, W, H, D] + * @param pos {Array} tensor坐标索引 + * @return {Number} tensor数据 + */ + getValue(pos = []) { + let p = [].concat(pos); + let len = p.length; + let sLen = this.shape.length; + // 补齐 + for (let i = 0; i < (sLen - len); i++) { + p.unshift(0); + } + let index = 0; + for (let i = 0; i < sLen; i++) { + index += p[i] * this.shapeNumbers[i]; + } + return this.data[index]; + } + + get width_texture() { + let length = this.shape_texture.length; + return this.shape_texture[length - 1]; + } + + get height_texture() { + let length = this.shape_texture.length; + return this.shape_texture[length - 2]; + } + + get width_shape() { + let length = this.shape.length; + return this.shape[length - 1]; + } + + get height_shape() { + let length = this.shape.length; + return this.shape[length - 2]; + } + + get channel() { + let length = this.shape.length; + if (length >= 3) { + return this.shape[length - 3]; + } + return 0; + } + + get length_shape() { + return this.shape.length || 0; + } + + /** + * 获取shape对应的个数 + * @return {Array} 和shape长度相等的对应个数 + */ + get numbers_shape() { + let numbers = []; + let sLen = this.shape.length; + for (let i = 0; i < (sLen - 1); i++) { + let number = this.shape.slice(i + 1).reduce((total, num) => total * num); + numbers.push(number); + } + // 和shape长度保持一致 + numbers.push(1); + return numbers; + } + + get total_shape() { + return this.total; + } + + dispose() { + if (this.data) { + this.data = null; + } + } +} +/* eslint-enable */ diff --git a/web/src/utils/utils.es6 b/web/src/utils/utils.es6 new file mode 100644 index 0000000000000000000000000000000000000000..6c747c097874a7099cd8f925079cdcee47d4c310 --- /dev/null +++ b/web/src/utils/utils.es6 @@ -0,0 +1,127 @@ +/** + * @file 工具类 + * @author yangmingming + */ +/* eslint-disable */ +export default { + // todo: 适用2维矩阵乘法,以后实现通用版本 + getReshapeInPaddle(inputShape = [], counterShape = [], outShape = []) { + let total = inputShape.reduce((all, num) => all * num); + if (outShape.length === 1) { + return [1, total]; + } else { + return [outShape[0], total / outShape[0]]; + } + }, + + getBroadcastShapeInPaddle(shapeA= [], shapeB = [], axis = 1) { + // todo: 简易版本,以后需要实现一个通用版本 + let bigger = shapeA; + let result = shapeB; + if (shapeA.length - shapeB.length < 0) { + bigger = shapeB; + result = shapeA; + } + return result.concat(bigger.slice(axis)); + }, + + getBroadcastDims(inShape = [], outShape = []) { + const inRank = inShape.length; + const dims = []; + for (let i = 0; i < inRank; i++) { + const dim = inRank - 1 - i; + const a = inShape[dim] || 1; + const b = outShape[outShape.length - 1 - i] || 1; + if (b > 1 && a === 1) { + dims.unshift(dim); + } + } + return dims; + }, + + getBroadcastShape(shapeA = [], shapeB = []) { + const result = []; + const max = Math.max(shapeA.length, shapeB.length); + for (let i = 0; i < max; i++) { + let a = shapeA[shapeA.length - i - 1]; + if (a === null) { + a = 1; + } + let b = shapeB[shapeB.length - i - 1]; + if (b === null) { + b = 1; + } + if (a === 1) { + result.unshift(b); + } else if (b === 1) { + result.unshift(a); + } else if (a !== b) { + return null; + } else { + result.unshift(a); + } + } + return result; + }, + + /** + * 获取texture形状和补0个数 + * @param shape {Array} tensor的形状 + * @return {{shape: *[], zeroNumber: number}} {Object} texture信息 + */ + getTextureInfoFromTensorShape(shape = []) { + let b = shape[0]; + let c = shape[1]; + let h = shape[2]; + let w = shape[3]; + return { + shape: [4, b * h, c * w], + zeroNumber: 0 + }; + }, + + // 获取数组中的最大值和索引 + getMaxItem(datas = []) { + let max = Math.max.apply(null, datas); + let index = datas.indexOf(max); + return {value: max, index}; + }, + + // 压缩 + async loadShader(name) { + let shader = await fetch(this.getShaderFile(name)); + return shader.text(); + }, + + getShaderFile(url) { + // todo: 根据脚手架获取shader文件 + const aa = url.split('/'); + let length = aa.length; + return '/' + aa[length - 1]; + }, + + img2texture(renderData = {}) { + const {height_texture, width_texture, shape} = renderData; + const total = height_texture * width_texture * 4; + const b = shape[0]; + const c = shape[1]; + const h = shape[2]; + const w = shape[3]; + const data = []; + for (let i = 0; i < total; i++) { + let j = Math.floor(i / (c * w)); + let k = Math.floor(i % (c * w)); + let b1 = Math.floor(j / h); + let h1 = Math.floor(j % h); + let c1 = Math.floor(k % c); + let w1 = Math.floor(k / c); + let l = b1 * (c * h * w) + c1 * (h * w) + h1 * (w) + w1; + data.push(renderData.data[l]); + data.push(0); + data.push(0); + data.push(0); + } + renderData.data = new Float32Array(data); + } +}; +/* eslint-enable */ diff --git a/web/test/testUtils/diff.js b/web/test/testUtils/diff.js new file mode 100644 index 0000000000000000000000000000000000000000..132213cff6af69098d3462c7fdc28a3f11819efb --- /dev/null +++ b/web/test/testUtils/diff.js @@ -0,0 +1,1055 @@ +/*! + + diff v2.0.1 + +Software License Agreement (BSD License) + +Copyright (c) 2009-2015, Kevin Decker + +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Kevin Decker nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +@license +*/ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define(factory); + else if(typeof exports === 'object') + exports["JsDiff"] = factory(); + else + root["JsDiff"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + /* See LICENSE file for terms of use */ + + /* + * Text diff implementation. + * + * This library supports the following APIS: + * JsDiff.diffChars: Character by character diff + * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace + * JsDiff.diffLines: Line based diff + * + * JsDiff.diffCss: Diff targeted at CSS content + * + * These methods are based on the implementation proposed in + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 + */ + 'use strict'; + + exports.__esModule = true; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _diffBase = __webpack_require__(1); + + var _diffBase2 = _interopRequireDefault(_diffBase); + + var _diffCharacter = __webpack_require__(3); + + var _diffWord = __webpack_require__(4); + + var _diffLine = __webpack_require__(5); + + var _diffSentence = __webpack_require__(6); + + var _diffCss = __webpack_require__(7); + + var _diffJson = __webpack_require__(8); + + var _patchApply = __webpack_require__(9); + + var _patchCreate = __webpack_require__(10); + + var _convertDmp = __webpack_require__(12); + + var _convertXml = __webpack_require__(13); + + exports.Diff = _diffBase2['default']; + exports.diffChars = _diffCharacter.diffChars; + exports.diffWords = _diffWord.diffWords; + exports.diffWordsWithSpace = _diffWord.diffWordsWithSpace; + exports.diffLines = _diffLine.diffLines; + exports.diffTrimmedLines = _diffLine.diffTrimmedLines; + exports.diffSentences = _diffSentence.diffSentences; + exports.diffCss = _diffCss.diffCss; + exports.diffJson = _diffJson.diffJson; + exports.structuredPatch = _patchCreate.structuredPatch; + exports.createTwoFilesPatch = _patchCreate.createTwoFilesPatch; + exports.createPatch = _patchCreate.createPatch; + exports.applyPatch = _patchApply.applyPatch; + exports.convertChangesToDMP = _convertDmp.convertChangesToDMP; + exports.convertChangesToXML = _convertXml.convertChangesToXML; + exports.canonicalize = _diffJson.canonicalize; + + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports['default'] = Diff; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _utilMap = __webpack_require__(2); + + var _utilMap2 = _interopRequireDefault(_utilMap); + + function Diff(ignoreWhitespace) { + this.ignoreWhitespace = ignoreWhitespace; + } + + Diff.prototype = { + diff: function diff(oldString, newString, callback) { + var self = this; + + function done(value) { + if (callback) { + setTimeout(function () { + callback(undefined, value); + }, 0); + return true; + } else { + return value; + } + } + + // Allow subclasses to massage the input prior to running + oldString = this.castInput(oldString); + newString = this.castInput(newString); + + // Handle the identity case (this is due to unrolling editLength == 0 + if (newString === oldString) { + return done([{ value: newString }]); + } + if (!newString) { + return done([{ value: oldString, removed: true }]); + } + if (!oldString) { + return done([{ value: newString, added: true }]); + } + + newString = this.removeEmpty(this.tokenize(newString)); + oldString = this.removeEmpty(this.tokenize(oldString)); + + var newLen = newString.length, + oldLen = oldString.length; + var editLength = 1; + var maxEditLength = newLen + oldLen; + var bestPath = [{ newPos: -1, components: [] }]; + + // Seed editLength = 0, i.e. the content starts with the same values + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) { + // Identity per the equality and tokenizer + return done([{ value: newString.join('') }]); + } + + // Main worker method. checks all permutations of a given edit length for acceptance. + function execEditLength() { + for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { + var basePath = undefined; + var addPath = bestPath[diagonalPath - 1], + removePath = bestPath[diagonalPath + 1], + _oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath - 1] = undefined; + } + + var canAdd = addPath && addPath.newPos + 1 < newLen, + canRemove = removePath && 0 <= _oldPos && _oldPos < oldLen; + if (!canAdd && !canRemove) { + // If this path is a terminal then prune + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canAdd || canRemove && addPath.newPos < removePath.newPos) { + basePath = clonePath(removePath); + self.pushComponent(basePath.components, undefined, true); + } else { + basePath = addPath; // No need to clone, we've pulled it from the list + basePath.newPos++; + self.pushComponent(basePath.components, true, undefined); + } + + _oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); + + // If we have hit the end of both strings, then we are done + if (basePath.newPos + 1 >= newLen && _oldPos + 1 >= oldLen) { + return done(buildValues(basePath.components, newString, oldString, self.useLongestToken)); + } else { + // Otherwise track this path as a potential candidate and continue. + bestPath[diagonalPath] = basePath; + } + } + + editLength++; + } + + // Performs the length of edit iteration. Is a bit fugly as this has to support the + // sync and async mode which is never fun. Loops over execEditLength until a value + // is produced. + if (callback) { + (function exec() { + setTimeout(function () { + // This should not happen, but we want to be safe. + /* istanbul ignore next */ + if (editLength > maxEditLength) { + return callback(); + } + + if (!execEditLength()) { + exec(); + } + }, 0); + })(); + } else { + while (editLength <= maxEditLength) { + var ret = execEditLength(); + if (ret) { + return ret; + } + } + } + }, + + pushComponent: function pushComponent(components, added, removed) { + var last = components[components.length - 1]; + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length - 1] = { count: last.count + 1, added: added, removed: removed }; + } else { + components.push({ count: 1, added: added, removed: removed }); + } + }, + extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath, + commonCount = 0; + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { + newPos++; + oldPos++; + commonCount++; + } + + if (commonCount) { + basePath.components.push({ count: commonCount }); + } + + basePath.newPos = newPos; + return oldPos; + }, + + equals: function equals(left, right) { + var reWhitespace = /\S/; + return left === right || this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right); + }, + removeEmpty: function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + }, + castInput: function castInput(value) { + return value; + }, + tokenize: function tokenize(value) { + return value.split(''); + } + }; + + function buildValues(components, newString, oldString, useLongestToken) { + var componentPos = 0, + componentLen = components.length, + newPos = 0, + oldPos = 0; + + for (; componentPos < componentLen; componentPos++) { + var component = components[componentPos]; + if (!component.removed) { + if (!component.added && useLongestToken) { + var value = newString.slice(newPos, newPos + component.count); + value = _utilMap2['default'](value, function (value, i) { + var oldValue = oldString[oldPos + i]; + return oldValue.length > value.length ? oldValue : value; + }); + + component.value = value.join(''); + } else { + component.value = newString.slice(newPos, newPos + component.count).join(''); + } + newPos += component.count; + + // Common case + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = oldString.slice(oldPos, oldPos + component.count).join(''); + oldPos += component.count; + + // Reverse add and remove so removes are output first to match common convention + // The diffing algorithm is tied to add then remove output and this is the simplest + // route to get the desired output with minimal overhead. + if (componentPos && components[componentPos - 1].added) { + var tmp = components[componentPos - 1]; + components[componentPos - 1] = components[componentPos]; + components[componentPos] = tmp; + } + } + } + + return components; + } + + function clonePath(path) { + return { newPos: path.newPos, components: path.components.slice(0) }; + } + module.exports = exports['default']; + + +/***/ }, +/* 2 */ +/***/ function(module, exports) { + + // Following this pattern to make sure the ignore next is in the correct place after babel builds + "use strict"; + + exports.__esModule = true; + exports["default"] = map; + + /* istanbul ignore next */ + function map(arr, mapper, that) { + if (Array.prototype.map) { + return Array.prototype.map.call(arr, mapper, that); + } + + var other = new Array(arr.length); + + for (var i = 0, n = arr.length; i < n; i++) { + other[i] = mapper.call(that, arr[i], i, arr); + } + return other; + } + module.exports = exports["default"]; + + +/***/ }, +/* 3 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.diffChars = diffChars; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + var _base2 = _interopRequireDefault(_base); + + var characterDiff = new _base2['default'](); + exports.characterDiff = characterDiff; + + function diffChars(oldStr, newStr, callback) { + return characterDiff.diff(oldStr, newStr, callback); + } + + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.diffWords = diffWords; + exports.diffWordsWithSpace = diffWordsWithSpace; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode + // + // Ranges and exceptions: + // Latin-1 Supplement, 0080–00FF + // - U+00D7 × Multiplication sign + // - U+00F7 ÷ Division sign + // Latin Extended-A, 0100–017F + // Latin Extended-B, 0180–024F + // IPA Extensions, 0250–02AF + // Spacing Modifier Letters, 02B0–02FF + // - U+02C7 ˇ ˇ Caron + // - U+02D8 ˘ ˘ Breve + // - U+02D9 ˙ ˙ Dot Above + // - U+02DA ˚ ˚ Ring Above + // - U+02DB ˛ ˛ Ogonek + // - U+02DC ˜ ˜ Small Tilde + // - U+02DD ˝ ˝ Double Acute Accent + // Latin Extended Additional, 1E00–1EFF + + var _base2 = _interopRequireDefault(_base); + + var extendedWordChars = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/; + + var wordDiff = new _base2['default'](true); + exports.wordDiff = wordDiff; + var wordWithSpaceDiff = new _base2['default'](); + exports.wordWithSpaceDiff = wordWithSpaceDiff; + wordDiff.tokenize = wordWithSpaceDiff.tokenize = function (value) { + var tokens = value.split(/(\s+|\b)/); + + // Join the boundary splits that we do not consider to be boundaries. This is primarily the extended Latin character set. + for (var i = 0; i < tokens.length - 1; i++) { + // If we have an empty string in the next field and we have only word chars before and after, merge + if (!tokens[i + 1] && tokens[i + 2] && extendedWordChars.test(tokens[i]) && extendedWordChars.test(tokens[i + 2])) { + tokens[i] += tokens[i + 2]; + tokens.splice(i + 1, 2); + i--; + } + } + + return tokens; + }; + + function diffWords(oldStr, newStr, callback) { + return wordDiff.diff(oldStr, newStr, callback); + } + + function diffWordsWithSpace(oldStr, newStr, callback) { + return wordWithSpaceDiff.diff(oldStr, newStr, callback); + } + + +/***/ }, +/* 5 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.diffLines = diffLines; + exports.diffTrimmedLines = diffTrimmedLines; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + var _base2 = _interopRequireDefault(_base); + + var lineDiff = new _base2['default'](); + exports.lineDiff = lineDiff; + var trimmedLineDiff = new _base2['default'](); + exports.trimmedLineDiff = trimmedLineDiff; + trimmedLineDiff.ignoreTrim = true; + + lineDiff.tokenize = trimmedLineDiff.tokenize = function (value) { + var retLines = [], + lines = value.split(/^/m); + for (var i = 0; i < lines.length; i++) { + var line = lines[i], + lastLine = lines[i - 1], + lastLineLastChar = lastLine && lastLine[lastLine.length - 1]; + + // Merge lines that may contain windows new lines + if (line === '\n' && lastLineLastChar === '\r') { + retLines[retLines.length - 1] = retLines[retLines.length - 1].slice(0, -1) + '\r\n'; + } else { + if (this.ignoreTrim) { + line = line.trim(); + // add a newline unless this is the last line. + if (i < lines.length - 1) { + line += '\n'; + } + } + retLines.push(line); + } + } + + return retLines; + }; + + function diffLines(oldStr, newStr, callback) { + return lineDiff.diff(oldStr, newStr, callback); + } + + function diffTrimmedLines(oldStr, newStr, callback) { + return trimmedLineDiff.diff(oldStr, newStr, callback); + } + + +/***/ }, +/* 6 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.diffSentences = diffSentences; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + var _base2 = _interopRequireDefault(_base); + + var sentenceDiff = new _base2['default'](); + exports.sentenceDiff = sentenceDiff; + sentenceDiff.tokenize = function (value) { + return value.split(/(\S.+?[.!?])(?=\s+|$)/); + }; + + function diffSentences(oldStr, newStr, callback) { + return sentenceDiff.diff(oldStr, newStr, callback); + } + + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.diffCss = diffCss; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + var _base2 = _interopRequireDefault(_base); + + var cssDiff = new _base2['default'](); + exports.cssDiff = cssDiff; + cssDiff.tokenize = function (value) { + return value.split(/([{}:;,]|\s+)/); + }; + + function diffCss(oldStr, newStr, callback) { + return cssDiff.diff(oldStr, newStr, callback); + } + + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.diffJson = diffJson; + exports.canonicalize = canonicalize; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + var _base2 = _interopRequireDefault(_base); + + var _line = __webpack_require__(5); + + var objectPrototypeToString = Object.prototype.toString; + + var jsonDiff = new _base2['default'](); + // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a + // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: + exports.jsonDiff = jsonDiff; + jsonDiff.useLongestToken = true; + + jsonDiff.tokenize = _line.lineDiff.tokenize; + jsonDiff.castInput = function (value) { + return typeof value === 'string' ? value : JSON.stringify(canonicalize(value), undefined, ' '); + }; + jsonDiff.equals = function (left, right) { + return _base2['default'].prototype.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); + }; + + function diffJson(oldObj, newObj, callback) { + return jsonDiff.diff(oldObj, newObj, callback); + } + + // This function handles the presence of circular references by bailing out when encountering an + // object that is already on the "stack" of items being processed. + + function canonicalize(obj, stack, replacementStack) { + stack = stack || []; + replacementStack = replacementStack || []; + + var i = undefined; + + for (i = 0; i < stack.length; i += 1) { + if (stack[i] === obj) { + return replacementStack[i]; + } + } + + var canonicalizedObj = undefined; + + if ('[object Array]' === objectPrototypeToString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + replacementStack.push(canonicalizedObj); + for (i = 0; i < obj.length; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack); + } + stack.pop(); + replacementStack.pop(); + } else if (typeof obj === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + replacementStack.push(canonicalizedObj); + var sortedKeys = [], + key = undefined; + for (key in obj) { + /* istanbul ignore else */ + if (obj.hasOwnProperty(key)) { + sortedKeys.push(key); + } + } + sortedKeys.sort(); + for (i = 0; i < sortedKeys.length; i += 1) { + key = sortedKeys[i]; + canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack); + } + stack.pop(); + replacementStack.pop(); + } else { + canonicalizedObj = obj; + } + return canonicalizedObj; + } + + +/***/ }, +/* 9 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + exports.applyPatch = applyPatch; + + function applyPatch(oldStr, uniDiff) { + var diffstr = uniDiff.split('\n'), + hunks = [], + i = 0, + remEOFNL = false, + addEOFNL = false; + + // Skip to the first change hunk + while (i < diffstr.length && !/^@@/.test(diffstr[i])) { + i++; + } + + // Parse the unified diff + for (; i < diffstr.length; i++) { + if (diffstr[i][0] === '@') { + var chnukHeader = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); + hunks.unshift({ + start: chnukHeader[3], + oldlength: +chnukHeader[2], + removed: [], + newlength: chnukHeader[4], + added: [] + }); + } else if (diffstr[i][0] === '+') { + hunks[0].added.push(diffstr[i].substr(1)); + } else if (diffstr[i][0] === '-') { + hunks[0].removed.push(diffstr[i].substr(1)); + } else if (diffstr[i][0] === ' ') { + hunks[0].added.push(diffstr[i].substr(1)); + hunks[0].removed.push(diffstr[i].substr(1)); + } else if (diffstr[i][0] === '\\') { + if (diffstr[i - 1][0] === '+') { + remEOFNL = true; + } else if (diffstr[i - 1][0] === '-') { + addEOFNL = true; + } + } + } + + // Apply the diff to the input + var lines = oldStr.split('\n'); + for (i = hunks.length - 1; i >= 0; i--) { + var hunk = hunks[i]; + // Sanity check the input string. Bail if we don't match. + for (var j = 0; j < hunk.oldlength; j++) { + if (lines[hunk.start - 1 + j] !== hunk.removed[j]) { + return false; + } + } + Array.prototype.splice.apply(lines, [hunk.start - 1, hunk.oldlength].concat(hunk.added)); + } + + // Handle EOFNL insertion/removal + if (remEOFNL) { + while (!lines[lines.length - 1]) { + lines.pop(); + } + } else if (addEOFNL) { + lines.push(''); + } + return lines.join('\n'); + } + + +/***/ }, +/* 10 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + exports.structuredPatch = structuredPatch; + exports.createTwoFilesPatch = createTwoFilesPatch; + exports.createPatch = createPatch; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _diffPatch = __webpack_require__(11); + + var _utilMap = __webpack_require__(2); + + var _utilMap2 = _interopRequireDefault(_utilMap); + + function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { + if (!options) { + options = { context: 4 }; + } + + var diff = _diffPatch.patchDiff.diff(oldStr, newStr); + diff.push({ value: '', lines: [] }); // Append an empty value to make cleanup easier + + function contextLines(lines) { + return _utilMap2['default'](lines, function (entry) { + return ' ' + entry; + }); + } + + var hunks = []; + var oldRangeStart = 0, + newRangeStart = 0, + curRange = [], + oldLine = 1, + newLine = 1; + + var _loop = function (i) { + var current = diff[i], + lines = current.lines || current.value.replace(/\n$/, '').split('\n'); + current.lines = lines; + + if (current.added || current.removed) { + // If we have previous context, start with that + if (!oldRangeStart) { + var prev = diff[i - 1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : []; + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + + // Output our changes + curRange.push.apply(curRange, _utilMap2['default'](lines, function (entry) { + return (current.added ? '+' : '-') + entry; + })); + + // Track the updated file position + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + // Identical context lines. Track line changes + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= options.context * 2 && i < diff.length - 2) { + // Overlapping + curRange.push.apply(curRange, contextLines(lines)); + } else { + // end the range and output + var contextSize = Math.min(lines.length, options.context); + curRange.push.apply(curRange, contextLines(lines.slice(0, contextSize))); + + var hunk = { + oldStart: oldRangeStart, + oldLines: oldLine - oldRangeStart + contextSize, + newStart: newRangeStart, + newLines: newLine - newRangeStart + contextSize, + lines: curRange + }; + if (i >= diff.length - 2 && lines.length <= options.context) { + // EOF is inside this hunk + var oldEOFNewline = /\n$/.test(oldStr); + var newEOFNewline = /\n$/.test(newStr); + if (lines.length == 0 && !oldEOFNewline) { + // special case: old has no eol and no trailing context; no-nl can end up before adds + curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file'); + } else if (!oldEOFNewline || !newEOFNewline) { + curRange.push('\\ No newline at end of file'); + } + } + hunks.push(hunk); + + oldRangeStart = 0; + newRangeStart = 0; + curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + }; + + for (var i = 0; i < diff.length; i++) { + _loop(i); + } + + return { + oldFileName: oldFileName, newFileName: newFileName, + oldHeader: oldHeader, newHeader: newHeader, + hunks: hunks + }; + } + + function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { + var diff = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options); + + var ret = []; + if (oldFileName == newFileName) { + ret.push('Index: ' + oldFileName); + } + ret.push('==================================================================='); + ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader)); + ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader)); + + for (var i = 0; i < diff.hunks.length; i++) { + var hunk = diff.hunks[i]; + ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines + ' @@'); + ret.push.apply(ret, hunk.lines); + } + + return ret.join('\n') + '\n'; + } + + function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) { + return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options); + } + + +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + // istanbul ignore next + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + var _base = __webpack_require__(1); + + var _base2 = _interopRequireDefault(_base); + + var patchDiff = new _base2['default'](); + exports.patchDiff = patchDiff; + patchDiff.tokenize = function (value) { + var ret = [], + linesAndNewlines = value.split(/(\n|\r\n)/); + + // Ignore the final empty token that occurs if the string ends with a new line + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } + + // Merge the content and line separators into single tokens + for (var i = 0; i < linesAndNewlines.length; i++) { + var line = linesAndNewlines[i]; + + if (i % 2) { + ret[ret.length - 1] += line; + } else { + ret.push(line); + } + } + return ret; + }; + + +/***/ }, +/* 12 */ +/***/ function(module, exports) { + + // See: http://code.google.com/p/google-diff-match-patch/wiki/API + "use strict"; + + exports.__esModule = true; + exports.convertChangesToDMP = convertChangesToDMP; + + function convertChangesToDMP(changes) { + var ret = [], + change = undefined, + operation = undefined; + for (var i = 0; i < changes.length; i++) { + change = changes[i]; + if (change.added) { + operation = 1; + } else if (change.removed) { + operation = -1; + } else { + operation = 0; + } + + ret.push([operation, change.value]); + } + return ret; + } + + +/***/ }, +/* 13 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + exports.convertChangesToXML = convertChangesToXML; + + function convertChangesToXML(changes) { + var ret = []; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + + ret.push(escapeHTML(change.value)); + + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + } + return ret.join(''); + } + + function escapeHTML(s) { + var n = s; + n = n.replace(/&/g, '&'); + n = n.replace(//g, '>'); + n = n.replace(/"/g, '"'); + + return n; + } + + +/***/ } +/******/ ]) +}); +; \ No newline at end of file diff --git a/web/test/testUtils/testUtils.es6 b/web/test/testUtils/testUtils.es6 new file mode 100644 index 0000000000000000000000000000000000000000..5c173472cd6e54c4a969b21cb985e7bf91ac1e17 --- /dev/null +++ b/web/test/testUtils/testUtils.es6 @@ -0,0 +1,124 @@ +import 'babel-polyfill'; +// import model from '../data/model.test2'; +// import model from '../data/model.test.conv2d'; +import GraphExecutor from '../../src/executor/executor'; +import Loader from '../../src/executor/loader'; +import Runtime from '../../src/runtime/runtime'; +// 获取map表 +import Map from '../data/map'; +console.dir(['map', Map]); + +let Diff = require('./diff'); +let datas; +let otherResult; +let output +async function run() { + const MODEL_URL = '/test/unitData/model.test.batchnorm.json'; + const graphModel= new Loader(); + const model = await graphModel.loadGraphModel(MODEL_URL); + datas = model.handler; + output = deepCopy(model.handler); + // 测试单元 + let item = getTensor('batchnorm'); + func(item); + // let inst = model.execute({input: cat}); + // console.dir(['result', inst.read()]); +} +run(); + +function deepCopy (data) { + return JSON.parse(JSON.stringify(data)); +} + +// let output = deepCopy(datas); +let getTensor = function(id, times = 1) { + let find = 0; + let data = datas.ops.filter((item, idx) => { + if (id === item.type) { + ++find; + if (find === times) { + return true; + } + } + }); + return getInputs(data[0]); +}; + +let getInputs = function(data) { + + Object.keys(data.inputs).forEach(function(key){ + data.inputs[key] = getValue(data.inputs[key][0], datas); + + }); + Object.keys(data.outputs).forEach(function(key){ + let out = getValue(data.outputs[key][0], datas) + data.outputs[key] = out; + otherResult = out[0].data; + }); + return data; + +}; + +let getResult = function(id) { + let data = output.ops.filter((item, idx) => { + if (id === item.type) { + + return true; + } + }); + return getoutputs(data[0]); +}; +let getoutputs = function(data) { + let otherResult; + Object.keys(data.outputs).forEach(function(key){ + let out = getValue(data.outputs[key][0], output); + otherResult = out[0].data; + }); + return otherResult; +}; + +let getValue = function(name, datas) { + return datas.vars.filter((item, idx) => { + if (name === item.name) { + return item; + } + }); +}; +// // 测试单元 +// let item = getTensor('conv2d'); + +let func = function (item) { + let inst = Runtime.init({ + 'width_raw_canvas': 512, + 'height_raw_canvas': 512 + }); + const executor = new GraphExecutor(item); + executor.execute(executor, {}, inst); + console.dir(['result', inst.read()]); + + + // var one = inst.read(); + // var other = getResult('softmax'); + // var color =''; + // var span = null; + + // var diff = Diff.diffChars(one.toString(), other.toString()), + // display = document.getElementById('display'), + // fragment = document.createDocumentFragment(); + // + // diff.forEach(function(part){ + // // green for additions, red for deletions + // // grey for common parts + // color = part.added ? 'green' : + // part.removed ? 'red' : 'grey'; + // span = document.createElement('span'); + // span.style.color = color; + // span.appendChild(document + // .createTextNode(part.value)); + // fragment.appendChild(span); + // }); + // + // display.appendChild(fragment); + +}; + diff --git a/web/test/unitTest.es6 b/web/test/unitTest.es6 new file mode 100644 index 0000000000000000000000000000000000000000..9ef10c6b7c04f5af14e2765cd34e3e1d16d2f377 --- /dev/null +++ b/web/test/unitTest.es6 @@ -0,0 +1,62 @@ +import 'babel-polyfill'; +import units from './units/units'; +let qs = require('qs'); +/** + * @file 入口文件 + * @author wangqun@baidu.com + * + */ +// 引入 op +const FSHADER_CON2D = require('../src/shader/f_elementwise_conv2d3_shader.c'); + + +const shapeA = [1, 3, 256, 256]; +const shapeB = [3]; +const imgUrl = require('./data/banana.jpeg'); +let shapeAData; +let shapeBData; +let inst; + +const matrix = units.mockOrigin(); +const filter = units.mockFilter(); +// 原始张量,上下左右1个单位的padding,步长是1 +let conf = { + 'filter_size_width': 3, + 'filter_size_height': 3, + 'origin_size_width': matrix.sx, + 'origin_size_height': matrix.sx, + 'out_size_width': 3, + 'out_size_height': 3, + 'stride_horizontal': 1, + 'stride_vertical': 1, + 'pad_left': 1, + 'pad_top': 1, + 'dilation_horizontal': 2, + 'dilation_vertical': 2 +} +units.init(conf, FSHADER_CON2D).then(instance => { + if (!instance || typeof instance === 'string') { + throw new Error(instance || '不支持float texture'); + } + inst = instance; +}).then(() => { + console.dir(['卷积核', filter]); + console.dir(['origin data', matrix.data]); + // 执行conv2d + inst.compute(filter, matrix.data, 'conv2d'); +}).then(() => { + // 读取结果 + const result = inst.read(); + console.dir(['conv2d的执行结果', result]); + + let input = { + filter: filter, + origin: matrix.data, + }; + Object.assign(input, conf); + console.dir(['完整input', input]); + // console.dir(['完整输入和输出', params]); + inst.getResult('pool2d', input, result); +}).catch(err => { + console.log('-----------error---------' + err); +}); diff --git a/web/test/unitTest.html b/web/test/unitTest.html new file mode 100644 index 0000000000000000000000000000000000000000..7699fe2303fa4944f5ce3141094060c1432a1eb6 --- /dev/null +++ b/web/test/unitTest.html @@ -0,0 +1,115 @@ + + + + + paddle web unitTest + + + + + + + +
+
+ paddle Web Unit Test +
+
+
    +
  • +
    pool
    +
    pass
    +
  • +
  • +
    relu
    +
    pass
    +
  • +
  • +
    prelu
    +
    pass
    +
  • +
  • +
    softmax
    +
    pass
    +
  • +
  • +
    dropout
    +
    pass
    +
  • +
  • +
    conv2d
    +
    pass
    +
  • +
+
+
+ +
+ + + + \ No newline at end of file diff --git a/web/test/units/units.es6 b/web/test/units/units.es6 new file mode 100644 index 0000000000000000000000000000000000000000..bfb1b2f1a8d6bd0a9ec51e36c7a14f962167b706 --- /dev/null +++ b/web/test/units/units.es6 @@ -0,0 +1,168 @@ +import Utils from '../../src/utils/utils'; +import Gpu from '../../src/gpu/gpu'; +import Matrix from '../../src/utils/dims'; +import axios from 'axios'; +let qs = require('qs'); + +/** + * @file gpu运行时 + * @author wangqun + * + */ +// v_shader.c表示计算容器 +const VSHADER = require('../../src/shader/v_shader.c'); + +export default { + + /** + * 初始化op + * @param {Object} opts 运行时参数,包含el:canvas,dim: 256 + * @return {Object} this 实例对象 + */ + + async init(opts = {}, opShader) { + const gpu = this.gpu = new Gpu(opts); + if (gpu.isFloatingTexture()) { + let texture = gpu.makeTexure(WebGLRenderingContext.FLOAT, null); + let framebuffer = gpu.attachFrameBuffer(texture); + let bufferStatus = gpu.frameBufferIsComplete(); + if (bufferStatus.isComplete) { + console.log(bufferStatus.isComplete); + // 获取shader + const vshaderCode = await Utils.loadShader(VSHADER); + let fshaderCode = await Utils.loadShader(opShader); + fshaderCode = Utils.populateData('conv2d', fshaderCode, opts); + gpu.create(vshaderCode, fshaderCode); + return this; + } else { + return bufferStatus.message; + } + + } else { + return null; + } + + }, + + /** + * 计算op + * @param bufferA + * @param bufferB + */ + compute(bufferA, bufferB, type) { + this.gpu.render(bufferA, bufferB, type); + }, + + /** + * 读取op计算结果, 并返回数据 + */ + read() { + return this.gpu.compute(); + }, + + // 生成feed数据 + feed(pixelData, size) { + return Utils.shapeData(pixelData, size); + }, + + // mock生成shapeB的数据 + mockShapeB(shapeA, shapeB) { + return Utils.mock(shapeA, shapeB); + }, + + // mock origin 1 * 5 * 5 + mockOrigin() { + return new Matrix({ + sx: 5, + sy: 5, + depth: 4 + }); + }, + + // mock filter 1 * 3 * 3 + mockFilter() { + return new Float32Array([1.0, 1.0, 0.0, 0.0, -2.0, 0.0, 1.0, -3.0, 1.0]); + }, + + // 更新op + updateOp(name) { + // this.gpu.updateShader(); + }, + + // get paddle mobile result + getResult(name, input, output) { + + + + if (name) { + let that = this; + axios.defaults.withCredentials = false; + axios.defaults.headers = { + 'Content-type': 'application/x-www-form-urlencoded' + } + axios.post('http://yq01-paddle-mobile.epc.baidu.com:8088/uniTest', qs.stringify({ + name: name, + input: JSON.stringify(input, function (key, value) { + if (value.constructor === Float32Array) { + return that.formatData(value); + }else { + return that.formatData(value); + } + }), + output: JSON.stringify(output, function (key, value) { + return that.formatData(value); + }) + },{ indices: false })) + .then(function (response) { + if (response.status === 200) { + that.displayResult(response.data); + } + console.log(response); + }) + .catch(function (error) { + console.log(error); + }); + } + + }, + + displayResult(res) { + if (res.name) { + let assert = (res.correct == 1? 'Pass' : 'Not pass'); + let passCls = (res.correct == 1? 'pass' : 'no-pass'); + if (res.correct === 1) { + + let unitHtml = '
  • ' + res.name + '
    ' + + '
    ' + assert + '
    ' + '
  • '; + let oli = document.createElement('li'); + oli.innerHTML = unitHtml; + document.getElementById('paddle-web-unit-list').appendChild(oli); + } + else if (res.correct === 0) { + let serverData = res.server_data; + let unitHtml = '
  • ' + res.name + '
    ' + + '
    ' + assert + '
    ' + + '

    ' + serverData + '

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