UNPKG

@tensorflow/tfjs-core

Version:

Hardware-accelerated JavaScript library for machine intelligence

229 lines (204 loc) 7.58 kB
/** * @license * Copyright 2017 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============================================================================= */ import {ENV} from '../../environment'; import {Tensor} from '../../tensor'; import {TypedArray} from '../../types'; import * as util from '../../util'; import {GPGPUContext} from './gpgpu_context'; import * as shader_compiler from './shader_compiler'; import {InputInfo, ShapeInfo} from './shader_compiler'; import {TextureData} from './tex_util'; export interface GPGPUProgram { variableNames: string[]; outputShape: number[]; userCode: string; usesPackedTextures?: boolean; } export interface GPGPUBinary { webGLProgram: WebGLProgram; program: GPGPUProgram; uniformLocations: {[name: string]: WebGLUniformLocation}; source: string; inShapeInfos: ShapeInfo[]; outShapeInfo: ShapeInfo; infLoc: WebGLUniformLocation; nanLoc: WebGLUniformLocation; } export interface TensorData { shape: number[]; texData: TextureData; isUniform: boolean; // Available when we decide to upload as uniform instead of texture. uniformValues?: TypedArray; } export function compileProgram<T extends Tensor, K extends Tensor>( gpgpu: GPGPUContext, program: GPGPUProgram, inputs: TensorData[], output: TensorData): GPGPUBinary { const userCode = program.userCode; const inputInfos: InputInfo[] = inputs.map((input, i) => { const shapeInfo: ShapeInfo = { logicalShape: input.shape, texShape: input.isUniform ? null : input.texData.texShape, isUniform: input.isUniform, isPacked: input.isUniform ? false : input.texData.isPacked, flatOffset: null }; if (input.texData != null && input.texData.slice != null && input.texData.slice.flatOffset > 0) { shapeInfo.flatOffset = input.texData.slice.flatOffset; } return {name: program.variableNames[i], shapeInfo}; }); const inShapeInfos = inputInfos.map(x => x.shapeInfo); const outShapeInfo: ShapeInfo = { logicalShape: output.shape, texShape: output.texData.texShape, isUniform: false, isPacked: output.texData.isPacked, flatOffset: null }; const source = shader_compiler.makeShader( inputInfos, outShapeInfo, userCode, program.usesPackedTextures); const webGLProgram = gpgpu.createProgram(source); // Add special uniforms (NAN, INFINITY) let infLoc: WebGLUniformLocation = null; const nanLoc = gpgpu.getUniformLocation(webGLProgram, 'NAN', false); if (ENV.getNumber('WEBGL_VERSION') === 1) { infLoc = gpgpu.getUniformLocation(webGLProgram, 'INFINITY', false); } // Add user-defined uniforms const uniformLocations: {[name: string]: WebGLUniformLocation} = {}; for (let i = 0; i < program.variableNames.length; i++) { const varName = program.variableNames[i]; const shouldThrow = false; uniformLocations[varName] = gpgpu.getUniformLocation(webGLProgram, varName, shouldThrow); uniformLocations[`offset${varName}`] = gpgpu.getUniformLocation(webGLProgram, `offset${varName}`, shouldThrow); } return { program, source, webGLProgram, uniformLocations, inShapeInfos, outShapeInfo, infLoc, nanLoc, }; } function validateBinaryAndProgram( shapeInfos: ShapeInfo[], inputs: TensorData[]) { if (shapeInfos.length !== inputs.length) { throw Error( `Binary was compiled with ${shapeInfos.length} inputs, but ` + `was executed with ${inputs.length} inputs`); } shapeInfos.forEach((s, i) => { const shapeA = s.logicalShape; const input = inputs[i]; const shapeB = input.shape; if (!util.arraysEqual(shapeA, shapeB)) { throw Error( `Binary was compiled with different shapes than ` + `the current args. Shapes ${shapeA} and ${shapeB} must match`); } // The input is uploaded as uniform. if (s.isUniform && input.isUniform) { return; } const texShapeA = s.texShape; const texShapeB = input.isUniform ? null : input.texData.texShape; if (!util.arraysEqual(texShapeA, texShapeB)) { throw Error( `Binary was compiled with different texture shapes than the` + ` current args. Shape ${texShapeA} and ${texShapeB} must match`); } }); } export function runProgram<T extends Tensor, K extends Tensor>( gpgpu: GPGPUContext, binary: GPGPUBinary, inputs: TensorData[], output: TensorData, customSetup?: (gpgpu: GPGPUContext, webGLProgram: WebGLProgram) => void): void { validateBinaryAndProgram(binary.inShapeInfos, inputs); validateBinaryAndProgram([binary.outShapeInfo], [output]); const outTex = output.texData.texture; const outTexShape = output.texData.texShape; if (output.texData.isPacked) { gpgpu.setOutputPackedMatrixTexture(outTex, outTexShape[0], outTexShape[1]); } else { gpgpu.setOutputMatrixTexture(outTex, outTexShape[0], outTexShape[1]); } gpgpu.setProgram(binary.webGLProgram); // Set special uniforms (NAN, INFINITY) if (ENV.getNumber('WEBGL_VERSION') === 1) { if (binary.infLoc !== null) { gpgpu.gl.uniform1f(binary.infLoc, Infinity); } } if (binary.nanLoc !== null) { gpgpu.gl.uniform1f(binary.nanLoc, NaN); } // Set user-defined inputs inputs.forEach((input, i) => { const varName = binary.program.variableNames[i]; const varLoc = binary.uniformLocations[varName]; const varOffsetLoc = binary.uniformLocations[`offset${varName}`]; if (varLoc == null) { // The compiler inferred that this variable is not used in this shader. return; } if (input.isUniform) { // Upload the values of the tensor as uniform. if (util.sizeFromShape(input.shape) < 2) { gpgpu.gl.uniform1f(varLoc, input.uniformValues[0]); } else { let vals = input.uniformValues; if (!(vals instanceof Float32Array)) { vals = new Float32Array(vals); } gpgpu.gl.uniform1fv(varLoc, vals); } return; } // If the input was sliced, upload the flat offset index. if (input.texData.slice != null && varOffsetLoc != null) { gpgpu.gl.uniform1i(varOffsetLoc, input.texData.slice.flatOffset); } gpgpu.setInputMatrixTexture(input.texData.texture, varLoc, i); }); if (customSetup != null) { customSetup(gpgpu, binary.webGLProgram); } gpgpu.executeProgram(); } export function makeShaderKey( program: GPGPUProgram, inputs: TensorData[], output: TensorData): string { let keyInputs = ''; inputs.concat(output).forEach(x => { const hasOffset = x.texData != null && x.texData.slice != null && x.texData.slice.flatOffset > 0; const texShape = x.isUniform ? 'uniform' : x.texData.texShape; keyInputs += `${x.shape}_${texShape}_${hasOffset}`; }); const keyUserCode = program.userCode; let key = program.constructor.name; // Fast string concat. See https://jsperf.com/string-concatenation/14. key += '_' + keyInputs + '_' + keyUserCode; return key; }