/* 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', 'offset_x', 'offset_y', 'limit', 'channel', 'total_shape' ]; // shader中需要的常量 const shaderAttrs = { scale: { 'bias': 'bias_value', 'scale': 'multi_value' }, pool2d: { 'pooling_type': 'type_pool' }, pool2d_winograd: { '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', 'isApplySeparableConv' ], batchnorm: [ 'needBatch', 'mergeTensor' ], elementwise_add: [ 'broadcast', 'needBatch' ], conv2d_elementwise_add: [ 'mergeAttrs', 'setActiveFunc', 'needBatch' ], pool2d: [ 'isMax', 'needBatch', 'setPacked', 'isGlobalPooling' ], relu: [ 'transToPrelu', 'needBatch' ], leaky_relu: [ 'transToLeakyrelu', 'needBatch' ], mul: [ 'reshape', 'needBatch' ] }; const mergeType = 'conv2d-elementwise_add'; export default class OpData { constructor(name, input = {}, output = {}, attrs = {}) { this.realName = name; this.name = name; this.attrs = attrs; // 检查是否是融合op this.checkIsMerge(); // 是否忽略当前当前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 => { // console.log(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({ type: data.name, name: data.tensorName, shape: data.shape, data: data.data, needBatch: data.needBatch || false, notCompressed: data.notCompressed || false, isPacked: data.isPacked || 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]]; } } mergeAttrs() { this.attrs = this.attrs.reduce((attrs, item) => { return Object.assign(attrs, item); }, {}); } isApplyWinoGrad(tensorData = []) { const filter = tensorData.filter(item => { const [b, c, h, w] = item.shape; return (h === 3) && (w === 3) && (item.tensorName === 'filter'); }); // 使用winograd算法 if (filter && filter.length) { this.setPacked(tensorData); this.applyWinograd(tensorData); this.setOutputPacked(tensorData); this.name += '_winograd'; } } isApplySeparableConv(tensorData = []) { const groups = this.attrs.groups; const filter = tensorData.filter(item => { const [b, c, h, w] = item.shape; return (b === groups) && (c === 1) && (item.tensorName === 'filter'); }); if (filter && filter.length) { // 可以执行separable conv this.name += '_depthwise'; } } setPacked(tensorData = []) { const isPacked = this.attrs.ispacked; tensorData.forEach(item => { if (item.tensorName === 'origin' && isPacked) { item.isPacked = true; if (this.name.indexOf('pool') > -1) { this.name += '_winograd'; } } }); } applyWinograd(tensorData = []) { tensorData.forEach(item => { if (item.tensorName === 'filter') { const [b, c, h, w] = item.shape; item.shape = [b, c, 4, 4]; item.data = Utils.applyFilterWinograd(item.data, item.shape); } }); } setOutputPacked(tensorData = []) { tensorData.forEach(item => { if (item.tensorName === 'out') { item.isPacked = true; } }); } broadcast(tensorData = []) { tensorData.forEach(item => { if (item.tensorName === 'counter') { item.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; if (type === 1) { this.name += '_max'; } } 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() { // 用于融合op const suffix = this.realName.replace(mergeType + '-', ''); if (suffix === 'leaky_relu') { this.data['multi_value'] = this.attrs.alpha; this.data['active_function'] = 'leakyRelu'; } } 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); } checkIsMerge() { if (this.name.indexOf(mergeType) > -1 && Object.prototype.toString.apply(this.attrs) === '[object Array]') { // 第一个融合op this.name = 'conv2d_elementwise_add'; return true; } return false; } 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 = {}; } }