diff --git a/README_cn.md b/README_cn.md index e0ab5d2c6e9a270f114c18d044dcfe48e5e4071e..a91422a3fc458ae8050cc3e4ef524450ea42fe75 100644 --- a/README_cn.md +++ b/README_cn.md @@ -32,7 +32,7 @@ import {runner as Paddlejs} from 'paddlejs'; const paddlejs = new Paddlejs({ modelPath: 'model/mobilenetv2', // model path - fileCount: 4, // model data file count + fileCount: 4, // model data file count 可以不填写 feedShape: { // input shape fw: 256, fh: 256 diff --git a/examples/benchmark/config.vue b/examples/benchmark/config.vue index 91477542e9b01da0fb187b51516c7982630ba9de..04eb23eafe72775d735a57db60140152844efd63 100644 --- a/examples/benchmark/config.vue +++ b/examples/benchmark/config.vue @@ -147,7 +147,7 @@ export default Vue.extend({ {name: 'preheat time', t: this.preheatT}, {name: 'subsequent average time', t: this.remainOthersT}, {name: 'best time', t: this.bestT}, - {name: 'op count', t: this.opCount}, + {name: 'op count', t: this.opCount} ] } }, @@ -239,7 +239,7 @@ export default Vue.extend({ totaltimeList.push(t); - ops.push(this.getOpPerf(quertyResults, this.aggregate, {})); + ops.push(this.getOpPerf(quertyResults, this.aggregate, {}, true)); curTimes++; } @@ -286,7 +286,7 @@ export default Vue.extend({ item.count++; return now; }, - getOpPerf(queryList, reduceF, acc) { + getOpPerf(queryList, reduceF, acc, needNoMean) { let timeRes = acc ? queryList.reduce(reduceF, acc) : queryList.reduce(reduceF); for(let key of Object.keys(timeRes)) { const item = timeRes[key]; @@ -294,7 +294,7 @@ export default Vue.extend({ if (name === 'feed') { return item; } - item.time = +(time / count).toFixed(4); + item.time = needNoMean ? time : +(time / count).toFixed(4); item.count = 1; } return timeRes; diff --git a/src/executor/camera.es6 b/src/executor/camera.es6 index f6cecf45cc23badd25091e11d0e25d75eb70426e..765469f3d349c193f83ba829f78fc7c75225f6d1 100644 --- a/src/executor/camera.es6 +++ b/src/executor/camera.es6 @@ -47,14 +47,11 @@ export default class Camera { if (this.deviceInfos.length) { constraints.video.deviceId = {exact: deviceId || this.deviceInfos[0].deviceId}; } - if (!constraints.video.deviceId) { + if (!(constraints.video.deviceId && constraints.video.deviceId.exact)) { constraints = { video: true }; } - else if (this.constraints) { - constraints = this.constraints; - } if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { // 最新的标准API diff --git a/src/factory/fshader/ops.es6 b/src/factory/fshader/ops.es6 index 869955a9aae5c877fdf733fbbd5e7ee744ec5f57..5d3084ae9873b947123ff6d63baafce28f9a96c7 100644 --- a/src/factory/fshader/ops.es6 +++ b/src/factory/fshader/ops.es6 @@ -34,6 +34,9 @@ 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 fc_params from '../../shader/fc/params'; +import fc_func from '../../shader/fc/main'; +import fc_conf from '../../shader/fc/conf'; import softmax_params from '../../shader/softmax/params'; import softmax_func from '../../shader/softmax/main'; import softmax_conf from '../../shader/softmax/conf'; @@ -157,6 +160,11 @@ export default { func: mul_func, confs: mul_conf }, + fc: { + params: fc_params, + func: fc_func, + confs: fc_conf + }, concat: { params: concat_params, func: concat_func, diff --git a/src/feed/ImageFeed.es6 b/src/feed/ImageFeed.es6 index 5f649fc30d51e90c607840a745731cb5fff71ebf..596c9b1a832da41a265fbfdf3bd93999b34f6f15 100644 --- a/src/feed/ImageFeed.es6 +++ b/src/feed/ImageFeed.es6 @@ -175,14 +175,17 @@ export default class imageFeed { let sh = height; // 最小边缩放到scale if (width < height) { - sw = params.scale; + sw = params.scale || width; sh = Math.round(sw * height / width); - } else { - sh = params.scale; + } + else if (width > height){ + sh = params.scale || height; sw = Math.round(sh * width / height); } - sw = params.scale; - sh = params.scale; + else { + sw = sh = params.scale || width; + } + this.fromPixels2DContext.canvas.width = sw; this.fromPixels2DContext.canvas.height = sh; this.fromPixels2DContext.drawImage( @@ -327,13 +330,13 @@ export default class imageFeed { data = this.resizeAndFitTargetSize(pixels, opt); data2 = this.fromPixels2DContext2.getImageData(0, 0, this.pixelWidth, this.pixelHeight); } - else if (opt.scale) { // 直接resize到targetShape Humanseg的情况 - scaleSize = this.reSize(pixels, opt); + else if (opt.targetSize) { // 如果有targetSize,就是装在目标宽高里的模式 TinyYolo的情况 + scaleSize = this.fitToTargetSize(pixels, opt); data = this.getImageData(opt, 0, 0, scaleSize); data2 = this.fromPixels2DContext2.getImageData(0, 0, this.pixelWidth, this.pixelHeight); } - else if (opt.targetSize) { // 如果有targetSize,就是装在目标宽高里的模式 TinyYolo的情况 - scaleSize = this.fitToTargetSize(pixels, opt); + else { + scaleSize = this.reSize(pixels, opt); data = this.getImageData(opt, 0, 0, scaleSize); data2 = this.fromPixels2DContext2.getImageData(0, 0, this.pixelWidth, this.pixelHeight); } diff --git a/src/loader/README_cn.md b/src/loader/README_cn.md index 9d7d8bc80abf25cdecea316175c77dcabc7d4b92..915f0e6f7cb5a823b95f39d2e90b34c2434fc4cb 100644 --- a/src/loader/README_cn.md +++ b/src/loader/README_cn.md @@ -1,4 +1,4 @@ -# PaddleJS Model 加载器 +# PaddleJS 模型加载器 百度 PaddleJS 的使用这个加载器进行模型获取到浏览器。模型加载器可以加载浏览器友好的json文件类型和二进制文件类型,支持单文件加载和文件分片加载,极大的利用浏览器并行请求的特性加载推理模型。 diff --git a/src/loader/loader.es6 b/src/loader/loader.es6 index c50a02da8e1b5287ba07391910eaa5065fc0082e..62191c1b17141314c80a04847031244ceb4859cd 100644 --- a/src/loader/loader.es6 +++ b/src/loader/loader.es6 @@ -12,6 +12,7 @@ export default class Loader { this.options = options; this.multipart = false; this.test = false; + this.chunkNum = 0; // fetch xhr jsonp this.params = {type: 'fetch'}; // 设置分片加载model @@ -62,7 +63,7 @@ export default class Loader { } fetchChunks() { - let counts = this.binaryOption.fileCount; + let counts = this.chunkNum || this.binaryOption.fileCount; let chunkArray = []; for (let i = 1; i <= counts; i++) { chunkArray.push( @@ -206,6 +207,7 @@ export default class Loader { async load() { let that = this; const artifacts = this.data = await this.fetchModel(); + this.chunkNum = artifacts.chunkNum; if (this.multipart === true) { if (this.dataType === 'binary') { await this.fetchChunks() diff --git a/src/shader/conv2d/conf.es6 b/src/shader/conv2d/conf.es6 index 8d493dae96a72ae12d4bcee35e1f5cbaa988c107..b9a41c4a9cfc2eea94928b2b779e106dc7fa3042 100644 --- a/src/shader/conv2d/conf.es6 +++ b/src/shader/conv2d/conf.es6 @@ -69,7 +69,10 @@ export default { 'MULTI_VALUE', 'BIAS_VALUE', 'FUSE_RELU', - 'ACTIVE_FUNCTION' + 'ACTIVE_FUNCTION', + + 'FILTER_REMAINDER_VEC4', + 'FILTER_NEAREST_VEC4' ], input: [ // { diff --git a/src/shader/conv2d/main.es6 b/src/shader/conv2d/main.es6 index 884402b3d1c24b2ea234c78ca2dbeb9dd4ed4c5f..0da9ef0564ab01bff679214e3fcf7bacd2417dc1 100644 --- a/src/shader/conv2d/main.es6 +++ b/src/shader/conv2d/main.es6 @@ -34,10 +34,50 @@ export default ` continue; } // channel计算 - for (int j = 0; j < channel_filter; j++) { - float f = getValueFromTensorPosLIMIT_FILTER_filter(c, j, fy, fx); - float o = getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + j, oy, ox); - res += f * o; + for (int j = 0; j < filter_nearest_vec4; j += 4) { + vec4 fValues = vec4( + getValueFromTensorPosLIMIT_FILTER_filter(c, j, fy, fx), + getValueFromTensorPosLIMIT_FILTER_filter(c, j + 1, fy, fx), + getValueFromTensorPosLIMIT_FILTER_filter(c, j + 2, fy, fx), + getValueFromTensorPosLIMIT_FILTER_filter(c, j + 3, fy, fx) + ); + + vec4 oValues = vec4( + getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + j, oy, ox), + getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + j + 1, oy, ox), + getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + j + 2, oy, ox), + getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + j + 3, oy, ox) + ); + + res += dot(fValues, oValues); + } + + if (filter_remainder_vec4 == 1) { + res += dot( + getValueFromTensorPosLIMIT_FILTER_filter(c, filter_nearest_vec4, fy, fx), + getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + filter_nearest_vec4, oy, ox)); + } else if (filter_remainder_vec4 == 2) { + vec2 fValues = vec2( + getValueFromTensorPosLIMIT_FILTER_filter(c, filter_nearest_vec4, fy, fx), + getValueFromTensorPosLIMIT_FILTER_filter(c, filter_nearest_vec4 + 1, fy, fx) + ); + vec2 oValues = vec2( + getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + filter_nearest_vec4, oy, ox), + getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + filter_nearest_vec4 + 1, oy, ox) + ); + res += dot(fValues, oValues); + } else if (filter_remainder_vec4 == 3) { + vec3 fValues = vec3( + getValueFromTensorPosLIMIT_FILTER_filter(c, filter_nearest_vec4, fy, fx), + getValueFromTensorPosLIMIT_FILTER_filter(c, filter_nearest_vec4 + 1, fy, fx), + getValueFromTensorPosLIMIT_FILTER_filter(c, filter_nearest_vec4 + 2, fy, fx) + ); + vec3 oValues = vec3( + getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + filter_nearest_vec4, oy, ox), + getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + filter_nearest_vec4 + 1, oy, ox), + getValueFromTensorPosLIMIT_ORIGIN_origin(b, oTensorChannel + filter_nearest_vec4 + 2, oy, ox) + ); + res += dot(fValues, oValues); } ox += dilation_h; } diff --git a/src/shader/conv2d/params.es6 b/src/shader/conv2d/params.es6 index 92db239afdd090f08db312ea56aae8e439fd79db..cf07f74a14d153986c56c5e4c219ccd298cd4829 100644 --- a/src/shader/conv2d/params.es6 +++ b/src/shader/conv2d/params.es6 @@ -54,4 +54,8 @@ export default ` // bias uniform sampler2D texture_bias; + + // 合并 channel 计算 + const int filter_nearest_vec4 = FILTER_NEAREST_VEC4; + const int filter_remainder_vec4 = FILTER_REMAINDER_VEC4; `; diff --git a/src/shader/conv2d_transpose/main.es6 b/src/shader/conv2d_transpose/main.es6 index e693b81fcaaea6bf6f05cac85bf0a63c3db25a8b..c405f525c47e359913a6cabdf8e7da7fe87ecb93 100644 --- a/src/shader/conv2d_transpose/main.es6 +++ b/src/shader/conv2d_transpose/main.es6 @@ -10,23 +10,22 @@ export default ` int x = oPos.a; int c = oPos.g; int y = oPos.b; - int b = oPos.r; + int b = oPos.r; float res = 0.0; int temp_x = 0; int temp_y = 0; float o = 0.0; float f = 0.0; - if (int(mod(float(x), 2.0)) == 1) x = x - 2; - if (int(mod(float(y), 2.0)) == 1) y = y - 2; + // 获取output的坐标 int oTensorChannel = int(c * groups / channel_out) * channel_origin; - int oy = y; + int oy = y - padTop; for (int fy = 0; fy < height_shape_filter; fy++) { if (oy < 0) { oy += dilation_v; continue; } - int ox = x; + int ox = x - padLeft; for (int fx = 0; fx < width_shape_filter; fx++) { if (ox < 0) { @@ -40,7 +39,7 @@ export default ` temp_y = int(floor(float(oy) / float(stride_v))); if (temp_x < width_shape_origin && temp_y < height_shape_origin){ o = getValueFromTensorPosLIMIT_ORIGIN_origin(b, j, temp_y, temp_x); - f = getValueFromTensorPosLIMIT_FILTER_filter(j, c, fy, fx); + f = getValueFromTensorPosLIMIT_FILTER_filter(j, c, height_shape_filter-1-fy, width_shape_filter-1-fx); res += f * o; } } diff --git a/src/shader/conv2d_transpose/params.es6 b/src/shader/conv2d_transpose/params.es6 index f475a07e9c5c9d1db391f4e2b5a1542e4e8b09ba..5687da1ef20916df84b24c816b2efa378847815b 100644 --- a/src/shader/conv2d_transpose/params.es6 +++ b/src/shader/conv2d_transpose/params.es6 @@ -28,10 +28,9 @@ export default ` const int stride_h = int(STRIDES_X); const int stride_v = int(STRIDES_Y); // padding的数目 - //const int padLeft = width_shape_filter - PADDINGS_X - 1; - //const int padTop = height_shape_filter - PADDINGS_Y - 1; - const int padLeft = PADDINGS_X; - const int padTop = PADDINGS_Y; + const int padLeft = WIDTH_SHAPE_FILTER - PADDINGS_X - 1; + const int padTop = HEIGHT_SHAPE_FILTER - PADDINGS_Y - 1; + // dilation膨胀系数 const int dilation_h = DILATIONS_X; const int dilation_v = DILATIONS_Y; diff --git a/src/shader/fc/conf.es6 b/src/shader/fc/conf.es6 new file mode 100644 index 0000000000000000000000000000000000000000..80890b53e36e1a14bfc111a73cad455a98b492c2 --- /dev/null +++ b/src/shader/fc/conf.es6 @@ -0,0 +1,48 @@ +/* eslint-disable */ +/** + * @file fc的配置文件 + * @author zhangjingyuan02 + */ +export default { + dep: [ + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'weight' + } + }, + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'origin' + } + }, + { + func: 'getValueFromTensorPos', + conf: { + TENSOR_NAME: 'bias' + } + } + ], + conf: [], + input: [ + { + tensor: 'weight', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'origin', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + }, + { + tensor: 'bias', + variable: 'texture', + setter: 'initTexture', + type: 'texture' + } + ] +}; diff --git a/src/shader/fc/main.es6 b/src/shader/fc/main.es6 new file mode 100644 index 0000000000000000000000000000000000000000..3d82bbea62062aa18521288f037bc15755afe4fd --- /dev/null +++ b/src/shader/fc/main.es6 @@ -0,0 +1,22 @@ +/* eslint-disable */ +/** + * @file 主函数 + * @author zhangjingyuan02 + */ +export default ` + // start函数 + void main(void) { + float res = 0.0; + ivec4 out_pos = getOutputTensorPosLIMIT_OUT(); + float bias = getValueFromTensorPosLIMIT_BIAS_bias(out_pos.r, out_pos.g, out_pos.b, out_pos.a); + + for (int j = 0; j < width_shape_origin; j++) { + float w = getValueFromTensorPosLIMIT_WEIGHT_weight(out_pos[0], out_pos[1], j, out_pos[3]); + float o = getValueFromTensorPosLIMIT_ORIGIN_origin(out_pos[0], out_pos[1], out_pos[2], j); + res += w * o; + } + + res = res + bias; + setOutput(res); + } +`; diff --git a/src/shader/fc/params.es6 b/src/shader/fc/params.es6 new file mode 100644 index 0000000000000000000000000000000000000000..0489936e6901fc004d0a5067721df69526275ea4 --- /dev/null +++ b/src/shader/fc/params.es6 @@ -0,0 +1,39 @@ +/* eslint-disable */ +/** + * @file fc参数文件 + */ +export default ` +// mul的input数据 +// 常量 +// 输入数据 +// weight +const int length_shape_weight = LENGTH_SHAPE_WEIGHT; +const int width_shape_weight = WIDTH_SHAPE_WEIGHT; +const int height_shape_weight = HEIGHT_SHAPE_WEIGHT; +const int width_texture_weight = WIDTH_TEXTURE_WEIGHT; +const int height_texture_weight = HEIGHT_TEXTURE_WEIGHT; +const int channel_weight = CHANNEL_WEIGHT; + +//input +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; + +// bias +const int width_shape_bias = WIDTH_SHAPE_BIAS; +const int height_shape_bias = HEIGHT_SHAPE_BIAS; +const int length_shape_bias = LENGTH_SHAPE_BIAS; +const int width_texture_bias = WIDTH_TEXTURE_BIAS; +const int height_texture_bias = HEIGHT_TEXTURE_BIAS; +const int channel_bias = CHANNEL_BIAS; + + +// uniform变量 +// 输入数据 +uniform sampler2D texture_weight; +uniform sampler2D texture_origin; +uniform sampler2D texture_bias; +`; diff --git a/src/utils/opData.es6 b/src/utils/opData.es6 index 47c0e12056d71e078186caa51be92fe5642e18e4..0d52e082fba86f713cfafa1ad7fea25698ed3835 100644 --- a/src/utils/opData.es6 +++ b/src/utils/opData.es6 @@ -51,14 +51,16 @@ const tensorName = { 'scale': 'scale', 'bias': 'bias', 'mean': 'mean', - 'variance': 'variance' + 'variance': 'variance', + 'w': 'weight' }; // unique behavior const opBehavior = { conv2d: [ 'needBatch', 'adaptPaddings', - 'isApplySeparableConv' + 'isApplySeparableConv', + 'batchComputeConv2d' ], conv2d_transpose: [ 'needBatch', @@ -130,6 +132,10 @@ const opBehavior = { scale: [ 'needBatch' ], + fc: [ + 'flattenShape', + 'needBatch' + ] }; const mergeType = 'conv2d-elementwise_add'; @@ -419,6 +425,13 @@ export default class OpData { }); } + batchComputeConv2d() { + let origin_shape_temp = this.input.Filter[0].shape; + let inChannels = origin_shape_temp[1]; + this.attrs.filter_nearest_vec4 = Math.floor(inChannels / 4) * 4; + this.attrs.filter_remainder_vec4 = inChannels % 4; + } + setPacked(tensorData = []) { const isPacked = this.attrs.ispacked; tensorData.forEach(item => { @@ -539,15 +552,26 @@ export default class OpData { } } + flattenShape(tensorData = []) { + const target = tensorData.find(item => item.shape.length > 2); + if (target) { + const padShape = Utils.padToFourDimShape(target.shape); + target.shape = [padShape[0] * padShape[2], padShape[1] * padShape[3]]; + } + + } + reshape(tensorData = []) { - let input = tensorData[0]; - let counter = tensorData[1]; + const input = tensorData.find(item => item.tensorName === 'origin'); + const counter = tensorData.find(item => item.tensorName === 'counter'); + const out = tensorData.find(item => item.tensorName === 'out' || item.tensorName === 'output'); + if (counter.shape.length > input.shape.length) { - input = tensorData[1]; - counter = tensorData[0]; + input = counter; + counter = input; } if (input.shape.length > 2 && counter.shape.length === 2) { - let shape = Utils.getReshapeInPaddle(input.shape, counter.shape, tensorData[2].shape); + let shape = Utils.getReshapeInPaddle(input.shape, counter.shape, out.shape); input.shape = shape; } diff --git a/src/utils/utils.es6 b/src/utils/utils.es6 index bf7ccf794284a0aaefb55709e39ca5cec973fa0f..95a10ed4f69ce78a1b652430771589a253df902e 100644 --- a/src/utils/utils.es6 +++ b/src/utils/utils.es6 @@ -153,6 +153,13 @@ export default { height *= 4; width = c * (Math.ceil(w / 4)); exceedMax = true; + if (height > GPU_TEXTURE_MAX_SIZE || width > GPU_TEXTURE_MAX_SIZE) { + const requested = `[${width}x${height}]`; + const max = `[${GPU_TEXTURE_MAX_SIZE}x${GPU_TEXTURE_MAX_SIZE}]`; + throw new Error( + 'Requested texture size ' + requested + + ' greater than WebGL maximum on this browser / GPU ' + max + '.'); + } } if (isPacked) { // 紧凑布局 diff --git a/test/data/model.test.fc.json b/test/data/model.test.fc.json new file mode 100644 index 0000000000000000000000000000000000000000..8acf17d31a23d6a0ecb579b0f67146d48e0c7240 --- /dev/null +++ b/test/data/model.test.fc.json @@ -0,0 +1,67 @@ +{ + "ops": [ + { + "attrs": { + "__@kernel_type_attr@__": "fc/def/4/1/1", + "force_fp32_output": false, + "in_num_col_dims": 1, + "op_device": "", + "scale_out": 1.0, + "scale_x": 1.0, + "scale_y": [ + 1.0 + ], + "use_mkldnn": false, + "x_num_col_dims": 1, + "y_num_col_dims": 1 + }, + "inputs": { + "Bias": [ + "fc10_offset" + ], + "Input": [ + "pool2d_0.tmp_0" + ], + "W": [ + "fc10_weights" + ] + }, + "outputs": { + "Out": [ + "fc_0.tmp_1" + ] + }, + "type": "fc" + } + ], + "vars": [ + { + "data": [ + 1, 2, 3, + 4, 5, 6, + 7, 8, 9, + 10, 11, 12 + ], + "name": "fc10_weights", + "persistable": 0, + "shape": [4, 3] + }, + { + "data": [2, 3, 4, 5], + "name": "pool2d_0.tmp_0", + "persistable": 0, + "shape": [4, 1, 1] + }, + { + "data": [1, 3, -1], + "name": "fc10_offset", + "persistable": 0, + "shape": [1, 3] + }, + { + "data": [93, 109, 119], + "name": "fc_0.tmp_1", + "shape": [1, 3] + } + ] +} diff --git a/test/opTest/fc.test.js b/test/opTest/fc.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9c8b01053e7a51b6e50e4060d645e3570c2c1389 --- /dev/null +++ b/test/opTest/fc.test.js @@ -0,0 +1,39 @@ +import Graph from '../../src/graph/graph'; +import GraphExecutor from '../../src/executor/executor'; +import opInfo from '../../test/data/model.test.fc.json'; +import Utils from '../../src/utils/utils'; +import {webgl} from './common'; +import {nchwShape2nhwcShape, getOutputShape, deepCopy} from './common/utils'; + +const modelType= 'fc'; +const output = deepCopy(opInfo); +const expected = output.vars.find(item => item.name === 'fc_0.tmp_1').data; + +const op = opInfo.ops[0]; +const graphExecutor = new GraphExecutor(op); +const graph = new Graph({ + options: { + test: true, + gl: webgl + } +}); +graph.data = opInfo; +graph.buildOpData(graphExecutor); + +async function run() { + graph.execute_(graphExecutor); + let result = await graph.inst.read(); + // 获取 NHWC -> NCHW 的 输出 + const outputNCHWShape = getOutputShape(output, modelType); + const outputNHWCShape = nchwShape2nhwcShape(outputNCHWShape); + + let nchwResult = Utils.nhwc2nchw(result, outputNHWCShape); + const formatData = Utils.formatReadData(nchwResult, outputNCHWShape); + console.log(formatData); + expect(JSON.stringify(formatData)).toBe(JSON.stringify(expected)); + +} + +test('test op fc ==============>', async () => { + await run(); +}); diff --git a/test/testUtils/testUtils.es6 b/test/testUtils/testUtils.es6 index 8ffb6365040415687566731399289f57b7fd7105..336569b78fda0532e0bef9e2339b7f5c1061fba5 100644 --- a/test/testUtils/testUtils.es6 +++ b/test/testUtils/testUtils.es6 @@ -22,7 +22,7 @@ const unitPath = { 'split': 'model.test.split.json' }; // 制定运行的 op -const modelType = 'conv2d'; +const modelType = 'conv2d_transpose'; // 制定运行的 op const unitData = unitPath[modelType]; @@ -51,6 +51,8 @@ async function run() { const type = op.type; if (type !== 'feed' && type !== 'fetch') { console.log(op.type); + console.log("this is standard output:"); + console.log(op.outputs.Output); model.graph.buildOpData(op); } }); diff --git a/tools/ModelConverter/README.md b/tools/ModelConverter/README.md new file mode 100644 index 0000000000000000000000000000000000000000..38577a7b03358b761536c9fba121d253a2673a77 --- /dev/null +++ b/tools/ModelConverter/README.md @@ -0,0 +1,72 @@ +# PaddleJS Model Converter + +Paddlejs model converter is a model transformation tool suitable for paddlejs. Its function is to convert paddlepadle model (or fluid model) into paddlejs model. This browser friendly format is used for loading prediction in paddlejs and browser. In addition, paddlejs model converter also provides powerful model optimization capabilities to help developers optimize model structure and improve runtime performance. + +## 1. Tutorial + +### 1.1. Environment Construction +#### Python Version +Confirm whether the python environment and version of the running platform meet the requirements. If Python 3 is used, you may need to change the `python3` in subsequent commands to `python3`: +- Python3: 3.5.1+ / 3.6 / 3.7 +- Python2: 2.7.15+ + +#### Install Virtual Environment +*Since the development environment may have multiple versions of Python installed, there may be different versions of dependent packages. In order to avoid conflicts, it is strongly recommended to use Python virtual environment to execute the commands required by the conversion tool to avoid various problems. If you are not using a virtual environment or if you have a virtual environment installed, you can skip this step.* + +Take Anaconda as an example: +Go to [Anaconda](https://www.anaconda.com/) main page,Select the corresponding platform and python version of anaconda and install it according to the official prompts; + +After installation, execute the following command on the command line to create a python virtual environment: +``` bash +conda create --name +``` + +Execute the following command to switch to the virtual environment +``` bash +# Linux or macOS +source activate + +# Windows +activate +``` + +#### Installation Dependency +- 如果`不需要`使用优化模型的能力,执行命令: +``` bash +python -m pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple +``` +- 如果`需要`使用优化模型的能力,执行命令: +``` bash +python -m pip install paddlepaddle paddlelite==2.6.0 -i https://mirror.baidu.com/pypi/simple +``` + +### 1.2. Get Start +- 如果待转换的 fluid 模型为`合并参数文件`,即一个模型对应一个参数文件: +``` bash +python convertToPaddleJSModel.py --modelPath= --paramPath= --outputDir= +``` +- 如果待转换的 fluid 模型为`分片参数文件`,即一个模型文件对应多个参数文件: +``` bash +# 注意,使用这种方式调用转换器,需要保证 inputDir 中,模型文件名为'__model__' +python convertToPaddleJSModel.py --inputDir= --outputDir= +```` +模型转换器将生成以下两种类型的文件以供 PaddleJS 使用: + +- model.json (模型结构与参数清单) +- chunk_\*.dat (二进制参数文件集合) + +## 2. Detailed Documentation + +参数 | 描述 +:-: | :-: +--inputDir | fluid 模型所在目录,当且仅当使用分片参数文件时使用该参数,将忽略 `modelPath` 和 `paramPath` 参数,且模型文件名必须为`__model__` +--modelPath | fluid 模型文件所在路径,使用合并参数文件时使用该参数 +--paramPath | fluid 参数文件所在路径,使用合并参数文件时使用该参数 +--outputDir | `必要参数`, paddleJS 模型输出路径 +--optimize | 是否进行模型优化, `0` 为关闭优化,`1` 为开启优化(需安装 PaddleLite ),默认关闭优化 +--logModelInfo | 是否打印模型结构信息, `0` 为不打印, `1` 为打印,默认不打印 +--sliceDataSize | 分片输出 PaddleJS 参数文件时,每片文件的大小,单位:KB,默认 4096 + +## 3. Other information +若需要转换的模型为 `TensorFlow/Caffe/ONNX` 格式,可使用 PaddlePaddle 项目下的 `X2Paddle`工具,将其他格式的模型转为 fluid 模型后,再使用本工具转化为 PaddleJS 模型。 +详细请参考 [X2Paddle 项目](https://github.com/PaddlePaddle/X2Paddle) diff --git a/tools/ModelConverter/convertModel.py b/tools/ModelConverter/convertModel.py index deb4570fdac3db5c96f939f3c8cdad8a110c7316..aeecb9f0bccf3681cdd637fb77a4a3e9ce07040d 100644 --- a/tools/ModelConverter/convertModel.py +++ b/tools/ModelConverter/convertModel.py @@ -30,7 +30,7 @@ sliceDataSize = 4 * 1024 # paddlepaddle运行程序实例 program = None # 存放模型结构 -modelInfo = {"vars": [], "ops": []} +modelInfo = {"vars": [], "ops": [], "chunkNum": 0} # 存放参数数值(未排序) paramValuesDict = {} @@ -210,6 +210,15 @@ def organizeModelOpInfo(): index += 1 print("Organizing model operators info successfully.") + +def addChunkNumToJson(paramValueList): + totalParamValuesCount = len(paramValueList) + countPerSlice = int(sliceDataSize * 1024 / 4) + count = totalParamValuesCount / countPerSlice + modelInfo["chunkNum"] = math.ceil(count) + print("Model chunkNum set successfully.") + + def convertToPaddleJSModel(): """ 转换fluid modle为paddleJS model """ # 初始化fluid运行环境和配置 @@ -224,12 +233,14 @@ def convertToPaddleJSModel(): # 获取program中所有的var,按照字母顺序加入到model info,同时读取参数数值 organizeModelVariableInfo() + # 对参数数值dict,按照key(参数名)进行字母顺序排序,并组合到一起 + paramValues = reorderParamsValue() + + # model.json 设置分片参数 + addChunkNumToJson(paramValues) # 导出模型文件到json dumpModelToJsonFile() - # 对参数数值dict,按照key(参数名)进行字母顺序排序,并组合到一起 - paramValues = reorderParamsValue() - # 导出分片参数文件 sliceDataToBinaryFile(paramValues) diff --git a/tools/ModelConverter/convertToPaddleJSModel.py b/tools/ModelConverter/convertToPaddleJSModel.py index 362a0d7de35beb582e1da672db951b84e5ffae9e..24a24df78a01a7b596ec498efb7aa587d3fcef3f 100644 --- a/tools/ModelConverter/convertToPaddleJSModel.py +++ b/tools/ModelConverter/convertToPaddleJSModel.py @@ -82,7 +82,7 @@ if __name__ == "__main__": print("enableLogModelInfo: " + str(enableLogModelInfo)) print("sliceDataSize:" + str(sliceDataSize)) - pythonCmd = "python" + pythonCmd = "python3" print("Starting...") if enableOptimization: