diff --git a/demos/benchmarks/benchmark.ts b/demos/benchmarks/benchmark.ts index 3bd7aa91da..58cf547de4 100644 --- a/demos/benchmarks/benchmark.ts +++ b/demos/benchmarks/benchmark.ts @@ -45,5 +45,5 @@ export class BenchmarkRun { export abstract class BenchmarkTest { constructor(protected params?: {}) {} - abstract run(size: number, option?: string): number; + abstract run(size: number, option?: string): Promise; } diff --git a/demos/benchmarks/conv_benchmarks.ts b/demos/benchmarks/conv_benchmarks.ts index 91a3a5b088..e3beeff75e 100644 --- a/demos/benchmarks/conv_benchmarks.ts +++ b/demos/benchmarks/conv_benchmarks.ts @@ -19,12 +19,11 @@ import {initializeGPU} from '../../src/math/ndarray'; import {Conv2DProgram} from '../../src/math/webgl/conv_gpu'; import * as gpgpu_math from '../../src/math/webgl/gpgpu_math'; import {TextureManager} from '../../src/math/webgl/texture_manager'; -import {Array1D, Array3D, Array4D, conv_util, GPGPUContext} from '../deeplearn'; +// tslint:disable-next-line:max-line-length +import {Array1D, Array3D, Array4D, conv_util, ENV, GPGPUContext} from '../deeplearn'; import {BenchmarkTest} from './benchmark'; -const OP_RUNS = 40; - export interface ConvBenchmarkParams { inDepth: number; outDepth: number; @@ -39,45 +38,67 @@ export abstract class ConvBenchmark extends BenchmarkTest { } export class ConvGPUBenchmark extends ConvBenchmark { - run(size: number): number { - const gpgpu = new GPGPUContext(); - const texManager = new TextureManager(gpgpu); - initializeGPU(gpgpu, texManager); + run(size: number): Promise { + return new Promise((resolve, reject) => { + const gpgpu = new GPGPUContext(); + const texManager = new TextureManager(gpgpu); + initializeGPU(gpgpu, texManager); + + const inDepth = this.params.inDepth; + const inShape: [number, number, number] = [size, size, inDepth]; + const outDepth = this.params.outDepth; + const filterSize = this.params.filterSize; + const stride = this.params.stride; + const hasBias = true; + const convInfo = conv_util.computeConvInfo( + inShape, filterSize, filterSize, outDepth, stride, stride, 'same'); + const program = new Conv2DProgram(convInfo, hasBias); + const outputShape = program.outputShape as [number, number, number]; + const out = Array3D.zeros(outputShape); + const x = Array3D.randUniform(inShape, -1, 1); + const wShape = + conv_util.computeWeightsShape4D(1, outDepth, filterSize, filterSize); + const W = Array4D.randUniform(wShape, -1, 1); + const b = Array1D.randUniform([outDepth], -1, 1); + const inputs = [x, W, b]; + const binary = gpgpu_math.compileProgram(gpgpu, program, inputs, out); + + const benchmark = () => { + gpgpu_math.runProgram(binary, inputs, out); + }; + + const immediateCleanup = () => { + x.dispose(); + W.dispose(); + b.dispose(); + out.dispose(); + texManager.dispose(); + gpgpu.deleteProgram(binary.webGLProgram); + }; + + const delayedCleanup = () => { + gpgpu.dispose(); + }; + + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { + gpgpu.runBenchmark(benchmark).then((timeElapsed: number) => { + delayedCleanup(); + resolve(timeElapsed); + }); + immediateCleanup(); + } else { + const start = performance.now(); - const inDepth = this.params.inDepth; - const inShape: [number, number, number] = [size, size, inDepth]; - const outDepth = this.params.outDepth; - const filterSize = this.params.filterSize; - const stride = this.params.stride; - const hasBias = true; - const convInfo = conv_util.computeConvInfo( - inShape, filterSize, filterSize, outDepth, stride, stride, 'same'); - const program = new Conv2DProgram(convInfo, hasBias); - const outputShape = program.outputShape as [number, number, number]; - const out = Array3D.zeros(outputShape); - const x = Array3D.randUniform(inShape, -1, 1); - const wShape = - conv_util.computeWeightsShape4D(1, outDepth, filterSize, filterSize); - const W = Array4D.randUniform(wShape, -1, 1); - const b = Array1D.randUniform([outDepth], -1, 1); - const inputs = [x, W, b]; - const binary = gpgpu_math.compileProgram(gpgpu, program, inputs, out); + benchmark(); + out.getValues(); - const start = performance.now(); - for (let i = 0; i < OP_RUNS; i++) { - gpgpu_math.runProgram(binary, inputs, out); - } - out.getValues(); - const avgTime = (performance.now() - start) / OP_RUNS; + const totalTime = performance.now() - start; - x.dispose(); - W.dispose(); - b.dispose(); - out.dispose(); - texManager.dispose(); - gpgpu.deleteProgram(binary.webGLProgram); - gpgpu.dispose(); + immediateCleanup(); + delayedCleanup(); - return avgTime; + resolve(totalTime); + } + }); } } diff --git a/demos/benchmarks/conv_transposed_benchmarks.ts b/demos/benchmarks/conv_transposed_benchmarks.ts index a0d332b26c..9bdb4c34e1 100644 --- a/demos/benchmarks/conv_transposed_benchmarks.ts +++ b/demos/benchmarks/conv_transposed_benchmarks.ts @@ -19,12 +19,11 @@ import {initializeGPU} from '../../src/math/ndarray'; import {Conv2DDerInputProgram} from '../../src/math/webgl/conv_backprop_gpu'; import * as gpgpu_math from '../../src/math/webgl/gpgpu_math'; import {TextureManager} from '../../src/math/webgl/texture_manager'; -import {Array3D, Array4D, conv_util, GPGPUContext} from '../deeplearn'; +// tslint:disable-next-line:max-line-length +import {Array3D, Array4D, conv_util, ENV, GPGPUContext} from '../deeplearn'; import {BenchmarkTest} from './benchmark'; -const OP_RUNS = 40; - export interface ConvTransposedBenchmarkParams { inDepth: number; outDepth: number; @@ -39,41 +38,68 @@ export abstract class ConvTransposedBenchmark extends BenchmarkTest { } export class ConvTransposedGPUBenchmark extends ConvTransposedBenchmark { - run(size: number): number { - const origInputDepth = 1; - const origOutputDepth = 1; - const xShape: [number, number, number] = [size, size, origOutputDepth]; - const fieldSize = 11; - const origStride = 1; - const origPad = 1; + run(size: number): Promise { + return new Promise((resolve, reject) => { + const origInputDepth = 1; + const origOutputDepth = 1; + const xShape: [number, number, number] = [size, size, origOutputDepth]; + const fieldSize = 11; + const origStride = 1; + const origPad = 1; + + const gpgpu = new GPGPUContext(); + const texManager = new TextureManager(gpgpu); + initializeGPU(gpgpu, texManager); + gpgpu.enableAutomaticDebugValidation(true); + + const convInfo = conv_util.computeConvInfo( + xShape, fieldSize, fieldSize, origOutputDepth, origStride, origStride, + origPad); + const program = new Conv2DDerInputProgram(convInfo); + const outputShape = program.outputShape as [number, number, number]; + const out = Array3D.zeros(outputShape); + const x = Array3D.randUniform(xShape, -1, 1); + const wShape = conv_util.computeWeightsShape4D( + origInputDepth, origOutputDepth, fieldSize, fieldSize); + const W = Array4D.randUniform(wShape, -1, 1); + const inputs = [x, W]; + const binary = gpgpu_math.compileProgram(gpgpu, program, inputs, out); + + const benchmark = () => { + gpgpu_math.runProgram(binary, inputs, out); + }; + + const immediateCleanup = () => { + out.dispose(); + x.dispose(); + W.dispose(); + texManager.dispose(); + gpgpu.deleteProgram(binary.webGLProgram); + }; + + const delayedCleanup = () => { + gpgpu.dispose(); + }; + + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { + gpgpu.runBenchmark(benchmark).then((timeElapsed: number) => { + delayedCleanup(); + resolve(timeElapsed); + }); + immediateCleanup(); + } else { + const start = performance.now(); + + benchmark(); + out.getValues(); - const gpgpu = new GPGPUContext(); - const texManager = new TextureManager(gpgpu); - initializeGPU(gpgpu, texManager); - gpgpu.enableAutomaticDebugValidation(true); + const totalTime = performance.now() - start; - const convInfo = conv_util.computeConvInfo( - xShape, fieldSize, fieldSize, origOutputDepth, origStride, origStride, - origPad); - const program = new Conv2DDerInputProgram(convInfo); - const outputShape = program.outputShape as [number, number, number]; - const out = Array3D.zeros(outputShape); - const x = Array3D.randUniform(xShape, -1, 1); - const wShape = conv_util.computeWeightsShape4D( - origInputDepth, origOutputDepth, fieldSize, fieldSize); - const W = Array4D.randUniform(wShape, -1, 1); - const inputs = [x, W]; - const binary = gpgpu_math.compileProgram(gpgpu, program, inputs, out); - const start = performance.now(); - for (let i = 0; i < OP_RUNS; i++) { - gpgpu_math.runProgram(binary, inputs, out); - } - out.getValues(); - const avgTime = (performance.now() - start) / OP_RUNS; + immediateCleanup(); + delayedCleanup(); - texManager.dispose(); - gpgpu.deleteProgram(binary.webGLProgram); - gpgpu.dispose(); - return avgTime; + resolve(totalTime); + } + }); } } diff --git a/demos/benchmarks/logsumexp_benchmarks.ts b/demos/benchmarks/logsumexp_benchmarks.ts index bd895135f3..afed074153 100644 --- a/demos/benchmarks/logsumexp_benchmarks.ts +++ b/demos/benchmarks/logsumexp_benchmarks.ts @@ -20,48 +20,71 @@ import * as gpgpu_math from '../../src/math/webgl/gpgpu_math'; import {LogSumExpProgram} from '../../src/math/webgl/logsumexp_gpu'; import {TextureManager} from '../../src/math/webgl/texture_manager'; // tslint:disable-next-line:max-line-length -import {Array2D, GPGPUContext, NDArray, NDArrayMathCPU, Scalar} from '../deeplearn'; +import {Array2D, ENV, GPGPUContext, NDArray, NDArrayMathCPU, Scalar} from '../deeplearn'; import {BenchmarkTest} from './benchmark'; -const CPU_OPS_PER_RUN = 10; -const GPU_OPS_PER_RUN = 10; - export class LogSumExpCPUBenchmark extends BenchmarkTest { - run(size: number): number { + run(size: number): Promise { const math = new NDArrayMathCPU(); const a = NDArray.randUniform([size, size], -1, 1); const start = performance.now(); - for (let i = 0; i < CPU_OPS_PER_RUN; i++) { - math.logSumExp(a); - } + math.logSumExp(a); + const end = performance.now(); - return (end - start) / CPU_OPS_PER_RUN; + + return new Promise((resolve, reject) => { + resolve(end - start); + }); } } export class LogSumExpGPUBenchmark extends BenchmarkTest { - run(size: number): number { - const gpgpu = new GPGPUContext(); - const texManager = new TextureManager(gpgpu); - initializeGPU(gpgpu, texManager); - const out = new Scalar({texture: texManager.acquireTexture([1, 1])}); - const a = Array2D.randUniform([size, size], -1, 1); - const program = new LogSumExpProgram(a.size); - const binary = gpgpu_math.compileProgram(gpgpu, program, [a], out); + run(size: number): Promise { + return new Promise((resolve, reject) => { + const gpgpu = new GPGPUContext(); + const texManager = new TextureManager(gpgpu); + initializeGPU(gpgpu, texManager); + const out = new Scalar({texture: texManager.acquireTexture([1, 1])}); + const a = Array2D.randUniform([size, size], -1, 1); + const program = new LogSumExpProgram(a.size); + const binary = gpgpu_math.compileProgram(gpgpu, program, [a], out); - const start = performance.now(); - for (let i = 0; i < GPU_OPS_PER_RUN; i++) { - gpgpu_math.runProgram(binary, [a], out); - } - out.getValues(); - const avgTime = (performance.now() - start) / GPU_OPS_PER_RUN; - a.dispose(); - out.dispose(); - texManager.dispose(); - gpgpu.deleteProgram(binary.webGLProgram); - gpgpu.dispose(); + const benchmark = () => { + gpgpu_math.runProgram(binary, [a], out); + }; + + const immediateCleanup = () => { + a.dispose(); + out.dispose(); + texManager.dispose(); + gpgpu.deleteProgram(binary.webGLProgram); + gpgpu.deleteProgram(binary.webGLProgram); + }; + + const delayedCleanup = () => { + gpgpu.dispose(); + }; + + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { + gpgpu.runBenchmark(benchmark).then((timeElapsed: number) => { + delayedCleanup(); + resolve(timeElapsed); + }); + immediateCleanup(); + } else { + const start = performance.now(); + + benchmark(); + out.getValues(); + + const totalTime = performance.now() - start; + + immediateCleanup(); + delayedCleanup(); - return avgTime; + resolve(totalTime); + } + }); } } diff --git a/demos/benchmarks/math-benchmark-run-groups.ts b/demos/benchmarks/math-benchmark-run-groups.ts index cf2bdc24bd..6fee3affb1 100644 --- a/demos/benchmarks/math-benchmark-run-groups.ts +++ b/demos/benchmarks/math-benchmark-run-groups.ts @@ -25,9 +25,9 @@ import {LogSumExpCPUBenchmark, LogSumExpGPUBenchmark} from './logsumexp_benchmar import {MatmulCPUBenchmark, MatmulGPUBenchmark} from './matmul_benchmarks'; // tslint:disable-next-line:max-line-length import {PoolBenchmarkParams, PoolCPUBenchmark, PoolGPUBenchmark} from './pool_benchmarks'; -import {UnaryOpsCPUBenchmark, UnaryOpsGPUBenchmark} from './unary_ops_benchmark'; // tslint:disable-next-line:max-line-length import {ReductionOpsCPUBenchmark, ReductionOpsGPUBenchmark} from './reduction_ops_benchmark'; +import {UnaryOpsCPUBenchmark, UnaryOpsGPUBenchmark} from './unary_ops_benchmark'; export function getRunGroups(): BenchmarkRunGroup[] { const groups: BenchmarkRunGroup[] = []; @@ -117,34 +117,36 @@ export function getRunGroups(): BenchmarkRunGroup[] { }); groups.push({ - name : 'Unary Op Benchmark (CPU vs GPU): input [size, size]', - min : 0, - max : 1024, - options : [ - "log", "exp", "neg", "sqrt", "abs", "relu", "sigmoid", "sin", "cos", - "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "logSumExp" + name: 'Unary Op Benchmark (CPU vs GPU): input [size, size]', + min: 0, + max: 1024, + stepToSizeTransformation: (step: number) => Math.max(1, step), + options: [ + 'log', 'exp', 'neg', 'sqrt', 'abs', 'relu', 'sigmoid', 'sin', 'cos', + 'tan', 'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh', 'logSumExp' ], - selectedOption : "log", - stepSize : 64, - benchmarkRuns : [ + selectedOption: 'log', + stepSize: 64, + benchmarkRuns: [ new BenchmarkRun('unary ops CPU', new UnaryOpsCPUBenchmark()), new BenchmarkRun('unary ops GPU', new UnaryOpsGPUBenchmark()) ], - params : {} + params: {} }); groups.push({ - name : 'Reduction Op Benchmark (CPU vs GPU): input [size, size]', - min : 0, - max : 1024, - options : [ "max", "min", "sum" ], - selectedOption : "max", - stepSize : 64, - benchmarkRuns : [ + name: 'Reduction Op Benchmark (CPU vs GPU): input [size, size]', + min: 0, + max: 1024, + stepToSizeTransformation: (step: number) => Math.max(1, step), + options: ['max', 'min', 'sum'], + selectedOption: 'max', + stepSize: 64, + benchmarkRuns: [ new BenchmarkRun('reduction ops CPU', new ReductionOpsCPUBenchmark()), new BenchmarkRun('reduction ops GPU', new ReductionOpsGPUBenchmark()) ], - params : {} + params: {} }); return groups; diff --git a/demos/benchmarks/math-benchmark.ts b/demos/benchmarks/math-benchmark.ts index 480d7cd15d..5cbf1cc4d6 100644 --- a/demos/benchmarks/math-benchmark.ts +++ b/demos/benchmarks/math-benchmark.ts @@ -24,11 +24,10 @@ import {BenchmarkRunGroup} from './benchmark'; import {getRunGroups} from './math-benchmark-run-groups'; // tslint:disable-next-line:variable-name -export let MathBenchmarkPolymer: new () => PolymerHTMLElement = PolymerElement( - {is: 'math-benchmark', properties: { - benchmarks: Array, - benchmarkRunGroupNames: Array - }}); +export let MathBenchmarkPolymer: new () => PolymerHTMLElement = PolymerElement({ + is: 'math-benchmark', + properties: {benchmarks: Array, benchmarkRunGroupNames: Array} +}); function getDisplayParams(params?: {}): string { if (params == null) { @@ -207,6 +206,7 @@ export class MathBenchmark extends MathBenchmarkPolymer { const runNumberRowElement = document.createElement('div'); runNumberRowElement.className = 'run-numbers-row math-benchmark'; + const runPromises: Array> = []; const rowValues: string[] = ['' + step]; for (let i = 0; i < benchmarkRunGroup.benchmarkRuns.length; i++) { const benchmarkRun = benchmarkRunGroup.benchmarkRuns[i]; @@ -216,37 +216,49 @@ export class MathBenchmark extends MathBenchmarkPolymer { benchmarkRunGroup.stepToSizeTransformation(step) : step; - let resultString: string; - let logString: string; - let time = 0; - let success = true; - - try { - time = benchmarkTest.run(size, benchmarkRunGroup.selectedOption); - resultString = time.toFixed(3) + 'ms'; - logString = resultString; - } catch (e) { - success = false; - resultString = 'Error'; - logString = e.message; - } + runPromises.push( + benchmarkTest.run(size, benchmarkRunGroup.selectedOption)); + } + + Promise.all(runPromises).then(results => { + for (let i = 0; i < benchmarkRunGroup.benchmarkRuns.length; i++) { + const benchmarkRun = benchmarkRunGroup.benchmarkRuns[i]; - if (time >= 0) { - if (success) { - benchmarkRun.chartData.push({x: step, y: time}); + const size = benchmarkRunGroup.stepToSizeTransformation != null ? + benchmarkRunGroup.stepToSizeTransformation(step) : + step; + + let resultString: string; + let logString: string; + let time = 0; + let success = true; + try { + time = results[i]; + resultString = time.toFixed(3) + 'ms'; + logString = resultString; + } catch (e) { + success = false; + resultString = 'Error'; + logString = e.message; } - rowValues.push(resultString); + + if (time >= 0) { + if (success) { + benchmarkRun.chartData.push({x: step, y: time}); + } + rowValues.push(resultString); + } + console.log(benchmarkRun.name + '[' + size + ']: ' + logString); } - console.log(benchmarkRun.name + '[' + size + ']: ' + logString); - } - runNumbersTable.appendChild(this.buildRunNumbersRow(rowValues)); - - step += benchmarkRunGroup.stepSize; - // Allow the UI to update. - setTimeout( - () => this.runBenchmarkSteps( - chart, benchmarkRunGroup, benchmarkRunGroupIndex, step), - 100); + runNumbersTable.appendChild(this.buildRunNumbersRow(rowValues)); + + step += benchmarkRunGroup.stepSize; + // Allow the UI to update. + setTimeout( + () => this.runBenchmarkSteps( + chart, benchmarkRunGroup, benchmarkRunGroupIndex, step), + 100); + }); } } document.registerElement(MathBenchmark.prototype.is, MathBenchmark); diff --git a/demos/benchmarks/matmul_benchmarks.ts b/demos/benchmarks/matmul_benchmarks.ts index 17ddb8a45b..019223c845 100644 --- a/demos/benchmarks/matmul_benchmarks.ts +++ b/demos/benchmarks/matmul_benchmarks.ts @@ -14,20 +14,20 @@ * limitations under the License. * ============================================================================= */ - import * as gpgpu_math from '../../src/math/webgl/gpgpu_math'; import {MatMulProgram} from '../../src/math/webgl/mulmat_gpu'; import * as test_util from '../../src/test_util'; -import {Array2D, GPGPUContext, NDArray, NDArrayMathCPU} from '../deeplearn'; +// tslint:disable-next-line:max-line-length +import {Array2D, ENV, GPGPUContext, NDArray, NDArrayMathCPU} from '../deeplearn'; import {BenchmarkTest} from './benchmark'; -const GPU_OPS_PER_RUN = 40; - export class MatmulCPUBenchmark extends BenchmarkTest { - run(size: number): number { + run(size: number): Promise { if (size > 512) { - return -1; + return new Promise((resolve, reject) => { + resolve(-1); + }); } const math = new NDArrayMathCPU(); const a = NDArray.randUniform([size, size], -1, 1); @@ -35,44 +35,70 @@ export class MatmulCPUBenchmark extends BenchmarkTest { const start = performance.now(); math.matMul(a, b); const end = performance.now(); - return end - start; + + return new Promise((resolve, reject) => { + resolve(end - start); + }); } } export class MatmulGPUBenchmark extends BenchmarkTest { - run(size: number): number { - const gpgpu = new GPGPUContext(); - const aTexture = gpgpu.createMatrixTexture(size, size); - const bTexture = gpgpu.createMatrixTexture(size, size); - const resultTexture = gpgpu.createMatrixTexture(size, size); + run(size: number): Promise { + return new Promise((resolve, reject) => { + const gpgpu = new GPGPUContext(); - const aArr = new Array2D( - [size, size], {texture: aTexture, textureShapeRC: [size, size]}); - const bArr = new Array2D( - [size, size], {texture: bTexture, textureShapeRC: [size, size]}); - const resArr = new Array2D( - [size, size], {texture: resultTexture, textureShapeRC: [size, size]}); - const program = new MatMulProgram(aArr.shape, bArr.shape); - const binary = - gpgpu_math.compileProgram(gpgpu, program, [aArr, bArr], resArr); - const a = test_util.randomArrayInRange(size * size, -1, 1); - const b = test_util.randomArrayInRange(size * size, -1, 1); - gpgpu.uploadMatrixToTexture(aTexture, size, size, a); - gpgpu.uploadMatrixToTexture(bTexture, size, size, b); + const aTexture = gpgpu.createMatrixTexture(size, size); + const bTexture = gpgpu.createMatrixTexture(size, size); + const resultTexture = gpgpu.createMatrixTexture(size, size); - const start = performance.now(); - for (let i = 0; i < GPU_OPS_PER_RUN; i++) { - gpgpu_math.runProgram(binary, [aArr, bArr], resArr); - } - gpgpu.downloadMatrixFromTexture(resultTexture, size, size); - const avgTime = (performance.now() - start) / GPU_OPS_PER_RUN; + const aArr = new Array2D( + [size, size], {texture: aTexture, textureShapeRC: [size, size]}); + const bArr = new Array2D( + [size, size], {texture: bTexture, textureShapeRC: [size, size]}); + const resArr = new Array2D( + [size, size], {texture: resultTexture, textureShapeRC: [size, size]}); + const program = new MatMulProgram(aArr.shape, bArr.shape); + const binary = + gpgpu_math.compileProgram(gpgpu, program, [aArr, bArr], resArr); + const a = test_util.randomArrayInRange(size * size, -1, 1); + const b = test_util.randomArrayInRange(size * size, -1, 1); + + gpgpu.uploadMatrixToTexture(aTexture, size, size, a); + gpgpu.uploadMatrixToTexture(bTexture, size, size, b); + + const benchmark = () => { + gpgpu_math.runProgram(binary, [aArr, bArr], resArr); + }; + + const immediateCleanup = () => { + gpgpu.deleteMatrixTexture(aTexture); + gpgpu.deleteMatrixTexture(bTexture); + gpgpu.deleteMatrixTexture(resultTexture); + gpgpu.deleteProgram(binary.webGLProgram); + }; + const delayedCleanup = () => { + gpgpu.dispose(); + }; + + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { + gpgpu.runBenchmark(benchmark).then((timeElapsed: number) => { + delayedCleanup(); + resolve(timeElapsed); + }); + immediateCleanup(); + } else { + const start = performance.now(); + + benchmark(); + gpgpu.downloadMatrixFromTexture(resultTexture, size, size); + + const totalTime = performance.now() - start; - gpgpu.deleteMatrixTexture(aTexture); - gpgpu.deleteMatrixTexture(bTexture); - gpgpu.deleteMatrixTexture(resultTexture); - gpgpu.deleteProgram(binary.webGLProgram); - gpgpu.dispose(); + immediateCleanup(); + delayedCleanup(); - return avgTime; + resolve(totalTime); + } + }); } } diff --git a/demos/benchmarks/pool_benchmarks.ts b/demos/benchmarks/pool_benchmarks.ts index 37984248a0..a1322ab2d7 100644 --- a/demos/benchmarks/pool_benchmarks.ts +++ b/demos/benchmarks/pool_benchmarks.ts @@ -20,12 +20,11 @@ import * as gpgpu_math from '../../src/math/webgl/gpgpu_math'; import {Pool2DProgram} from '../../src/math/webgl/pool_gpu'; import {TextureManager} from '../../src/math/webgl/texture_manager'; // tslint:disable-next-line:max-line-length -import {Array3D, conv_util, GPGPUContext, NDArray, NDArrayMathCPU} from '../deeplearn'; +import {Array3D, conv_util, ENV, GPGPUContext, NDArray, NDArrayMathCPU} from '../deeplearn'; import {BenchmarkTest} from './benchmark'; const CPU_OP_RUNS = 1; -const GPU_OP_RUNS = 40; export interface PoolBenchmarkParams { depth: number; @@ -41,7 +40,7 @@ export abstract class PoolBenchmark extends BenchmarkTest { } export class PoolCPUBenchmark extends PoolBenchmark { - run(size: number): number { + run(size: number): Promise { const math = new NDArrayMathCPU(); const outputDepth = this.params.depth; const xShape: [number, number, number] = [size, size, outputDepth]; @@ -57,40 +56,65 @@ export class PoolCPUBenchmark extends PoolBenchmark { } const avgTime = (performance.now() - start) / CPU_OP_RUNS; - return avgTime; + return new Promise((resolve, reject) => { + resolve(avgTime); + }); } } export class PoolGPUBenchmark extends PoolBenchmark { - run(size: number): number { - const gpgpu = new GPGPUContext(); - const texManager = new TextureManager(gpgpu); - initializeGPU(gpgpu, texManager); + run(size: number): Promise { + return new Promise((resolve, reject) => { - const outputDepth = this.params.depth; - const xShape: [number, number, number] = [size, size, outputDepth]; - const fieldSize = this.params.fieldSize; - const stride = this.params.stride; - const convInfo = conv_util.computeConvInfo( - xShape, fieldSize, fieldSize, outputDepth, stride, stride, 'same'); - const program = new Pool2DProgram(convInfo, this.params.type, false); - const res = NDArray.zeros(program.outputShape); - const x = Array3D.randUniform(xShape, -1, 1); - const binary = gpgpu_math.compileProgram(gpgpu, program, [x], res); + const gpgpu = new GPGPUContext(); + const texManager = new TextureManager(gpgpu); + initializeGPU(gpgpu, texManager); - const start = performance.now(); - for (let i = 0; i < GPU_OP_RUNS; i++) { - gpgpu_math.runProgram(binary, [x], res); - } - res.getValues(); - const avgTime = (performance.now() - start) / GPU_OP_RUNS; + const outputDepth = this.params.depth; + const xShape: [number, number, number] = [size, size, outputDepth]; + const fieldSize = this.params.fieldSize; + const stride = this.params.stride; + const convInfo = conv_util.computeConvInfo( + xShape, fieldSize, fieldSize, outputDepth, stride, stride, 'same'); + const program = new Pool2DProgram(convInfo, this.params.type, false); + const res = NDArray.zeros(program.outputShape); + const x = Array3D.randUniform(xShape, -1, 1); + const binary = gpgpu_math.compileProgram(gpgpu, program, [x], res); + + const benchmark = () => { + gpgpu_math.runProgram(binary, [x], res); + }; + + const immediateCleanup = () => { + x.dispose(); + res.dispose(); + texManager.dispose(); + gpgpu.deleteProgram(binary.webGLProgram); + }; + + const delayedCleanup = () => { + gpgpu.dispose(); + }; + + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { + gpgpu.runBenchmark(benchmark).then((timeElapsed: number) => { + delayedCleanup(); + resolve(timeElapsed); + }); + immediateCleanup(); + } else { + const start = performance.now(); + + benchmark(); + res.getValues(); + + const totalTime = performance.now() - start; - x.dispose(); - res.dispose(); - texManager.dispose(); - gpgpu.deleteProgram(binary.webGLProgram); - gpgpu.dispose(); + immediateCleanup(); + delayedCleanup(); - return avgTime; + resolve(totalTime); + } + }); } } diff --git a/demos/benchmarks/reduction_ops_benchmark.ts b/demos/benchmarks/reduction_ops_benchmark.ts index ea2e52ef57..fce53d82a4 100644 --- a/demos/benchmarks/reduction_ops_benchmark.ts +++ b/demos/benchmarks/reduction_ops_benchmark.ts @@ -14,60 +14,87 @@ * limitations under the License. * ============================================================================= */ - -import {NDArrayMath} from '../../src/math/math'; -import {NDArrayMathCPU} from '../../src/math/math_cpu'; -import {NDArrayMathGPU} from '../../src/math/math_gpu'; -import {Array2D, NDArray} from '../../src/math/ndarray'; -import {Scalar} from '../../src/math/ndarray'; +// tslint:disable-next-line:max-line-length +import {Array2D, ENV, NDArray, NDArrayMath, NDArrayMathCPU, NDArrayMathGPU, Scalar} from '../deeplearn'; import {BenchmarkTest} from './benchmark'; -const OPS_PER_RUN = 10; - export abstract class ReductionOpsBenchmark extends BenchmarkTest { - - protected getReductionOp(option: string, - math: NDArrayMath): (input: NDArray) => Scalar { + protected getReductionOp(option: string, math: NDArrayMath): + (input: NDArray) => Scalar { switch (option) { - case "max": - return (input: NDArray) => math.max(input); - case "min": - return (input: NDArray) => math.min(input); - case "sum": - return (input: NDArray) => math.sum(input); - default: - throw new Error(`Not found such ops: ${option}`); + case 'max': + return (input: NDArray) => math.max(input); + case 'min': + return (input: NDArray) => math.min(input); + case 'sum': + return (input: NDArray) => math.sum(input); + default: + throw new Error(`Not found such ops: ${option}`); } } } export class ReductionOpsCPUBenchmark extends ReductionOpsBenchmark { - run(size: number, option: string) { - const math = new NDArrayMathCPU(); - const input = NDArray.randUniform([ size, size ], -1, 1); - const op = this.getReductionOp(option, math); - const start = performance.now(); + run(size: number, option: string): Promise { + return new Promise((resolve, reject) => { + const math = new NDArrayMathCPU(); + const input = NDArray.randUniform([size, size], -1, 1); + const op = this.getReductionOp(option, math); + const start = performance.now(); - for (let i = 0; i < OPS_PER_RUN; i++) { - math.scope(() => { op(input).get(); }); - } - const end = performance.now(); - return (end - start) / OPS_PER_RUN; + math.scope(() => { + op(input).get(); + }); + + const end = performance.now(); + resolve(end - start); + }); } } export class ReductionOpsGPUBenchmark extends ReductionOpsBenchmark { run(size: number, option: string) { - const math = new NDArrayMathGPU(); - const input = NDArray.randUniform([ size, size ], -1, 1); - const op = this.getReductionOp(option, math); - const start = performance.now(); + return new Promise((resolve, reject) => { + const math = new NDArrayMathGPU(); + const input = NDArray.randUniform([size, size], -1, 1); + const op = this.getReductionOp(option, math); - for (let i = 0; i < OPS_PER_RUN; i++) { - math.scope(() => { op(input).get(); }); - } - const end = performance.now(); - return (end - start) / OPS_PER_RUN; + let output: NDArray; + const benchmark = () => { + math.scope(() => { + output = op(input); + }); + }; + + const immediateCleanup = () => { + input.dispose(); + }; + + const delayedCleanup = () => { + math.dispose(); + }; + + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { + math.getGPGPUContext().runBenchmark(benchmark).then( + (timeElapsed: number) => { + delayedCleanup(); + resolve(timeElapsed); + }); + immediateCleanup(); + } else { + const start = performance.now(); + + benchmark(); + output.get(); + + const totalTime = performance.now() - start; + + immediateCleanup(); + delayedCleanup(); + + resolve(totalTime); + } + }); } -} \ No newline at end of file +} diff --git a/demos/benchmarks/unary_ops_benchmark.ts b/demos/benchmarks/unary_ops_benchmark.ts index b1c50e402c..727a507564 100644 --- a/demos/benchmarks/unary_ops_benchmark.ts +++ b/demos/benchmarks/unary_ops_benchmark.ts @@ -15,84 +15,114 @@ * ============================================================================= */ -import {NDArrayMath} from '../../src/math/math'; -import {NDArrayMathCPU} from '../../src/math/math_cpu'; -import {NDArrayMathGPU} from '../../src/math/math_gpu'; -import {Array2D, NDArray} from '../../src/math/ndarray'; -import {BenchmarkTest} from './benchmark'; +// tslint:disable-next-line:max-line-length +import {Array2D, ENV, NDArray, NDArrayMath, NDArrayMathCPU, NDArrayMathGPU} from '../deeplearn'; -const OPS_PER_RUN = 10; +import {BenchmarkTest} from './benchmark'; export abstract class UnaryOpsBenchmark extends BenchmarkTest { - protected getUnaryOp(option: string, math: NDArrayMath) { switch (option) { - case "log": - return (input: NDArray) => math.log(input); - case "exp": - return (input: NDArray) => math.exp(input); - case "neg": - return (input: NDArray) => math.neg(input); - case "sqrt": - return (input: NDArray) => math.sqrt(input); - case "abs": - return (input: NDArray) => math.abs(input); - case "relu": - return (input: NDArray) => math.relu(input); - case "sigmoid": - return (input: NDArray) => math.sigmoid(input); - case "sin": - return (input: NDArray) => math.sin(input); - case "cos": - return (input: NDArray) => math.cos(input); - case "tan": - return (input: NDArray) => math.tan(input); - case "asin": - return (input: NDArray) => math.asin(input); - case "acos": - return (input: NDArray) => math.acos(input); - case "atan": - return (input: NDArray) => math.atan(input); - case "sinh": - return (input: NDArray) => math.sinh(input); - case "cosh": - return (input: NDArray) => math.cosh(input); - case "tanh": - return (input: NDArray) => math.tanh(input); - case "logSumExp": - return (input: NDArray) => math.logSumExp(input); - default: - throw new Error(`Not found such ops: ${option}`); + case 'log': + return (input: NDArray) => math.log(input); + case 'exp': + return (input: NDArray) => math.exp(input); + case 'neg': + return (input: NDArray) => math.neg(input); + case 'sqrt': + return (input: NDArray) => math.sqrt(input); + case 'abs': + return (input: NDArray) => math.abs(input); + case 'relu': + return (input: NDArray) => math.relu(input); + case 'sigmoid': + return (input: NDArray) => math.sigmoid(input); + case 'sin': + return (input: NDArray) => math.sin(input); + case 'cos': + return (input: NDArray) => math.cos(input); + case 'tan': + return (input: NDArray) => math.tan(input); + case 'asin': + return (input: NDArray) => math.asin(input); + case 'acos': + return (input: NDArray) => math.acos(input); + case 'atan': + return (input: NDArray) => math.atan(input); + case 'sinh': + return (input: NDArray) => math.sinh(input); + case 'cosh': + return (input: NDArray) => math.cosh(input); + case 'tanh': + return (input: NDArray) => math.tanh(input); + case 'logSumExp': + return (input: NDArray) => math.logSumExp(input); + default: + throw new Error(`Not found such ops: ${option}`); } } } export class UnaryOpsCPUBenchmark extends UnaryOpsBenchmark { - run(size: number, option: string) { - const math = new NDArrayMathCPU(); - const input = NDArray.randUniform([ size, size ], -1, 1); - const op = this.getUnaryOp(option, math); - const start = performance.now(); + run(size: number, option: string): Promise { + return new Promise((resolve, reject) => { + const math = new NDArrayMathCPU(); + const input = NDArray.randUniform([size, size], -1, 1); + const op = this.getUnaryOp(option, math); + const start = performance.now(); - for (let i = 0; i < OPS_PER_RUN; i++) { - math.scope(() => { op(input).get(); }); - } - const end = performance.now(); - return (end - start) / OPS_PER_RUN; + math.scope(() => { + op(input).get(); + }); + + const end = performance.now(); + resolve(end - start); + }); } } export class UnaryOpsGPUBenchmark extends UnaryOpsBenchmark { run(size: number, option: string) { - const math = new NDArrayMathGPU(); - const input = NDArray.randUniform([ size, size ], -1, 1); - const op = this.getUnaryOp(option, math); - const start = performance.now(); + return new Promise((resolve, reject) => { + const math = new NDArrayMathGPU(); + const input = NDArray.randUniform([size, size], -1, 1); + const op = this.getUnaryOp(option, math); - for (let i = 0; i < OPS_PER_RUN; i++) { - math.scope(() => { op(input).get(); }); - } - const end = performance.now(); - return (end - start) / OPS_PER_RUN; + let output: NDArray; + const benchmark = () => { + math.scope(() => { + output = op(input); + }); + }; + + const immediateCleanup = () => { + input.dispose(); + }; + + const delayedCleanup = () => { + math.dispose(); + }; + + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { + math.getGPGPUContext().runBenchmark(benchmark).then( + (timeElapsed: number) => { + delayedCleanup(); + resolve(timeElapsed); + }); + immediateCleanup(); + } else { + const start = performance.now(); + + benchmark(); + output.get(); + + const totalTime = performance.now() - start; + + immediateCleanup(); + delayedCleanup(); + + resolve(totalTime); + } + }); } -} \ No newline at end of file +} diff --git a/src/device_util.ts b/src/device_util.ts new file mode 100644 index 0000000000..0f3125d2fc --- /dev/null +++ b/src/device_util.ts @@ -0,0 +1,27 @@ +/** + * @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. + * ============================================================================= + */ + +export function isMobile(): boolean { + // tslint:disable-next-line:no-any + const a = navigator.userAgent || navigator.vendor || (window as any).opera; + // tslint:disable-next-line:max-line-length + return /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i + .test(a) || + // tslint:disable-next-line:max-line-length + /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i + .test(a.substr(0, 4)); +} diff --git a/src/environment.ts b/src/environment.ts new file mode 100644 index 0000000000..5b9fd94f13 --- /dev/null +++ b/src/environment.ts @@ -0,0 +1,114 @@ +/** + * @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 * as device_util from './device_util'; +import * as webgl_util from './math/webgl/webgl_util'; +import * as util from './util'; + +export enum Type { + NUMBER, + BOOLEAN +} + +export interface Features { + 'WEBGL_DISJOINT_QUERY_TIMER'?: boolean; + 'WEBGL_VERSION'?: number; +} + +export const URL_PROPERTIES: URLProperty[] = [ + {name: 'WEBGL_DISJOINT_QUERY_TIMER', type: Type.BOOLEAN}, + {name: 'WEBGL_VERSION', type: Type.NUMBER} +]; + +export interface URLProperty { + name: keyof Features; + type: Type; +} + +function evaluateFeature(feature: K): Features[K] { + if (feature === 'WEBGL_DISJOINT_QUERY_TIMER') { + return !device_util.isMobile(); + } else if (feature === 'WEBGL_VERSION') { + if (webgl_util.isWebGL2Enabled()) { + return 2; + } else if (webgl_util.isWebGL1Enabled()) { + return 1; + } + return 0; + } + throw new Error(`Unknown feature ${feature}.`); +} + +export class Environment { + private features: Features = {}; + + constructor(features?: Features) { + if (features != null) { + this.features = features; + } + } + + get(feature: K): Features[K] { + if (feature in this.features) { + return this.features[feature]; + } + + this.features[feature] = evaluateFeature(feature); + + return this.features[feature]; + } +} + +// Expects flags from URL in the format ?dljsflags=FLAG1:1,FLAG2:true. +const DEEPLEARNJS_FLAGS_PREFIX = 'dljsflags'; +function getFeaturesFromURL(): Features { + const features: Features = {}; + + const urlParams = util.getQueryParams(window.location.search); + if (DEEPLEARNJS_FLAGS_PREFIX in urlParams) { + const urlFlags: {[key: string]: string} = {}; + + const keyValues = urlParams[DEEPLEARNJS_FLAGS_PREFIX].split(','); + keyValues.forEach(keyValue => { + const [key, value] = keyValue.split(':') as [string, string] + urlFlags[key] = value; + }); + + URL_PROPERTIES.forEach(urlProperty => { + if (urlProperty.name in urlFlags) { + console.log( + `Setting feature override from URL ${urlProperty.name}: ` + + `${urlFlags[urlProperty.name]}`); + if (urlProperty.type === Type.NUMBER) { + features[urlProperty.name] = +urlFlags[urlProperty.name]; + } else if (urlProperty.type === Type.BOOLEAN) { + features[urlProperty.name] = urlFlags[urlProperty.name] === 'true'; + } else { + console.warn(`Unknown URL param: ${urlProperty.name}.`); + } + } + }); + } + + return features; +} + +export let ENV = new Environment(getFeaturesFromURL()); + +export function setEnvironment(environment: Environment) { + ENV = environment; +} diff --git a/src/environment_test.ts b/src/environment_test.ts new file mode 100644 index 0000000000..50a02c206c --- /dev/null +++ b/src/environment_test.ts @@ -0,0 +1,66 @@ +/** + * @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 * as device_util from './device_util'; +import {Environment} from './environment'; +import * as webgl_util from './math/webgl/webgl_util'; + +describe('disjoint query timer', () => { + it('mobile', () => { + spyOn(device_util, 'isMobile').and.returnValue(true); + + const env = new Environment(); + + expect(env.get('WEBGL_DISJOINT_QUERY_TIMER')).toBe(false); + }); + + it('not mobile', () => { + spyOn(device_util, 'isMobile').and.returnValue(false); + + const env = new Environment(); + + expect(env.get('WEBGL_DISJOINT_QUERY_TIMER')).toBe(true); + }); +}); + +describe('WebGL version', () => { + it('webgl 1', () => { + spyOn(webgl_util, 'isWebGL1Enabled').and.returnValue(true); + spyOn(webgl_util, 'isWebGL2Enabled').and.returnValue(false); + + const env = new Environment(); + + expect(env.get('WEBGL_VERSION')).toBe(1); + }); + + it('webgl 2', () => { + spyOn(webgl_util, 'isWebGL1Enabled').and.returnValue(true); + spyOn(webgl_util, 'isWebGL2Enabled').and.returnValue(true); + + const env = new Environment(); + + expect(env.get('WEBGL_VERSION')).toBe(2); + }); + + it('no webgl', () => { + spyOn(webgl_util, 'isWebGL1Enabled').and.returnValue(false); + spyOn(webgl_util, 'isWebGL2Enabled').and.returnValue(false); + + const env = new Environment(); + + expect(env.get('WEBGL_VERSION')).toBe(0); + }); +}); diff --git a/src/index.ts b/src/index.ts index b03a0ba99b..a049bde4cb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,7 @@ export {DataStats, InMemoryDataset} from './data/dataset'; // tslint:disable-next-line:max-line-length export {InCPUMemoryShuffledInputProviderBuilder, InGPUMemoryShuffledInputProviderBuilder, InputProvider} from './data/input_provider'; export {XhrDataset, XhrDatasetConfig, XhrModelConfig} from './data/xhr-dataset'; +export {ENV, Features} from './environment'; export {Graph, Tensor} from './graph/graph'; export {AdagradOptimizer} from './graph/optimizers/adagrad_optimizer'; export {MomentumOptimizer} from './graph/optimizers/momentum_optimizer'; diff --git a/src/math/webgl/gpgpu_context.ts b/src/math/webgl/gpgpu_context.ts index 497fef6a8d..cbffb9d098 100644 --- a/src/math/webgl/gpgpu_context.ts +++ b/src/math/webgl/gpgpu_context.ts @@ -15,10 +15,12 @@ * ============================================================================= */ +import {ENV} from '../../environment'; +import * as util from '../../util'; + import * as gpgpu_util from './gpgpu_util'; import * as tex_util from './tex_util'; import * as webgl_util from './webgl_util'; - import {WebGLLoseContextExtension} from './webgl_util'; export class GPGPUContext { @@ -42,7 +44,7 @@ export class GPGPUContext { } // WebGL 2.0 enables texture floats without an extension. - if (!webgl_util.isWebGL2Enabled()) { + if (ENV.get('WEBGL_VERSION') === 1) { this.textureFloatExtension = webgl_util.getExtensionOrThrow(this.gl, 'OES_texture_float'); this.colorBufferFloatExtension = @@ -257,6 +259,105 @@ export class GPGPUContext { webgl_util.callAndCheck(this.gl, () => this.gl.finish()); } + public runBenchmark(benchmark: () => void): Promise { + if (ENV.get('WEBGL_VERSION') === 2) { + return this.runBenchmarkWebGL2(benchmark); + } + return this.runBenchmarkWebGL1(benchmark); + } + + private runBenchmarkWebGL2(benchmark: () => void): Promise { + const ext = webgl_util.getExtensionOrThrow( + this.gl, 'EXT_disjoint_timer_query_webgl2'); + // tslint:disable-next-line:no-any + const query = (this.gl as any).createQuery(); + + // tslint:disable-next-line:no-any + (this.gl as any).beginQuery((ext as any).TIME_ELAPSED_EXT, query); + + benchmark(); + + // tslint:disable-next-line:no-any + (this.gl as any).endQuery((ext as any).TIME_ELAPSED_EXT); + + return new Promise((resolve, reject) => { + const queryGPU = () => { + const available = + // tslint:disable-next-line:no-any + (this.gl as any) + .getQueryParameter( + // tslint:disable-next-line:no-any + query, (this.gl as any).QUERY_RESULT_AVAILABLE); + + const disjoint = + // tslint:disable-next-line:no-any + this.gl.getParameter((ext as any).GPU_DISJOINT_EXT); + return available && !disjoint; + }; + + const getTimeElapsed = () => { + const timeElapsed = + // tslint:disable-next-line:no-any + (this.gl as any) + // tslint:disable-next-line:no-any + .getQueryParameter(query, (this.gl as any).QUERY_RESULT); + // Return milliseconds. + resolve(timeElapsed / 1000000); + }; + + const resolveWithWarning = () => { + console.warn('Disjoint query timer never available.'); + resolve(-1); + }; + + const maxBackoffMs = 2048; + util.tryWithBackoff(queryGPU, maxBackoffMs) + .then(getTimeElapsed) + .catch(resolveWithWarning); + }); + } + + private runBenchmarkWebGL1(benchmark: () => void): Promise { + const ext = webgl_util.getExtensionOrThrow( + // tslint:disable-next-line:no-any + this.gl, 'EXT_disjoint_timer_query') as any; + const query = ext.createQueryEXT(); + + ext.beginQueryEXT(ext.TIME_ELAPSED_EXT, query); + + benchmark(); + + ext.endQueryEXT(ext.TIME_ELAPSED_EXT); + + return new Promise((resolve, reject) => { + const queryGPU = () => { + const available = + ext.getQueryObjectEXT(query, ext.QUERY_RESULT_AVAILABLE_EXT); + + const disjoint = this.gl.getParameter(ext.GPU_DISJOINT_EXT); + + return available && !disjoint; + }; + + const getTimeElapsed = () => { + const timeElapsed = ext.getQueryObjectEXT(query, ext.QUERY_RESULT_EXT); + // Return milliseconds. + resolve(timeElapsed / 1000000); + }; + + const resolveWithWarning = () => { + console.warn('Disjoint query timer never available.'); + resolve(-1); + }; + + const maxBackoffMs = 2048; + util.tryWithBackoff(queryGPU, maxBackoffMs) + .then(getTimeElapsed) + .catch(resolveWithWarning); + }); + } + + private downloadMatrixDriver( texture: WebGLTexture, downloadAndDecode: () => Float32Array): Float32Array { diff --git a/src/math/webgl/gpgpu_context_test.ts b/src/math/webgl/gpgpu_context_test.ts index f53b50c822..ea9004bbc5 100644 --- a/src/math/webgl/gpgpu_context_test.ts +++ b/src/math/webgl/gpgpu_context_test.ts @@ -15,10 +15,12 @@ * ============================================================================= */ +import * as environment from '../../environment'; +import {Environment, Features} from '../../environment'; import * as test_util from '../../test_util'; + import {GPGPUContext} from './gpgpu_context'; import * as tex_util from './tex_util'; -import * as webgl_util from './webgl_util'; describe('GPGPUContext downloadMatrixFromTexture WebGL 2.0', () => { let gpgpu: GPGPUContext; @@ -75,7 +77,10 @@ describe('GPGPUContext downloadMatrixFromTexture WebGL 1.0', () => { let texture: WebGLTexture; beforeEach(() => { - webgl_util.preferWebGL1(); + const featureValues: Features = {}; + featureValues['WEBGL_VERSION'] = 1; + environment.setEnvironment(new Environment(featureValues)); + gpgpu = new GPGPUContext(); gpgpu.enableAutomaticDebugValidation(true); texture = gpgpu.createMatrixTexture(1, 1); @@ -84,7 +89,7 @@ describe('GPGPUContext downloadMatrixFromTexture WebGL 1.0', () => { afterEach(() => { gpgpu.deleteMatrixTexture(texture); gpgpu.dispose(); - webgl_util.preferWebGL2(); + environment.setEnvironment(new Environment()); }); it('returns clear color from the output texture', () => { @@ -186,14 +191,18 @@ describe('GPGPUContext setOutputMatrixTexture WebGL 1.0', () => { let texture: WebGLTexture; beforeEach(() => { - webgl_util.preferWebGL1(); + const featureValues: Features = {}; + featureValues['WEBGL_VERSION'] = 1; + environment.setEnvironment(new Environment(featureValues)); + gpgpu = new GPGPUContext(); gpgpu.enableAutomaticDebugValidation(true); texture = gpgpu.createMatrixTexture(1, 1); }); afterEach(() => { - webgl_util.preferWebGL2(); + environment.setEnvironment(new Environment()); + gpgpu.deleteMatrixTexture(texture); gpgpu.dispose(); }); @@ -247,13 +256,17 @@ describe('GPGPUContext setOutputMatrixTexture WebGL 2.0', () => { let texture: WebGLTexture; beforeEach(() => { - webgl_util.preferWebGL2(); + const featureValues: Features = {}; + featureValues['WEBGL_VERSION'] = 2; + environment.setEnvironment(new Environment(featureValues)); + gpgpu = new GPGPUContext(); gpgpu.enableAutomaticDebugValidation(true); texture = gpgpu.createMatrixTexture(1, 1); }); afterEach(() => { + environment.setEnvironment(new Environment()); gpgpu.deleteMatrixTexture(texture); gpgpu.dispose(); }); diff --git a/src/math/webgl/gpgpu_util.ts b/src/math/webgl/gpgpu_util.ts index 481b773c5d..5d74a61a46 100644 --- a/src/math/webgl/gpgpu_util.ts +++ b/src/math/webgl/gpgpu_util.ts @@ -15,6 +15,8 @@ * ============================================================================= */ +import {ENV} from '../../environment'; + import * as tex_util from './tex_util'; import * as webgl_util from './webgl_util'; @@ -79,7 +81,7 @@ export function createIndexBuffer(gl: WebGLRenderingContext): WebGLBuffer { function getTextureInternalFormat( gl: WebGLRenderingContext, numChannels: number): number { - if (webgl_util.isWebGL2Enabled()) { + if (ENV.get('WEBGL_VERSION') === 2) { if (numChannels === 4) { // tslint:disable-next-line:no-any return (gl as any).RGBA32F; @@ -92,7 +94,7 @@ function getTextureInternalFormat( function getTextureFormat( gl: WebGLRenderingContext, numChannels: number): number { - if (webgl_util.isWebGL2Enabled()) { + if (ENV.get('WEBGL_VERSION') === 2) { if (numChannels === 4) { // tslint:disable-next-line:no-any return (gl as any).RGBA; diff --git a/src/math/webgl/webgl_util.ts b/src/math/webgl/webgl_util.ts index dc61843c95..07ed208e79 100644 --- a/src/math/webgl/webgl_util.ts +++ b/src/math/webgl/webgl_util.ts @@ -15,11 +15,10 @@ * ============================================================================= */ -let USE_WEBGL2_WHEN_AVAILABLE = true; -let WEBGL2_ENABLED: boolean|undefined = null; let MAX_TEXTURE_SIZE: number = null; import * as util from '../../util'; +import {ENV} from '../../environment'; export interface WebGLContextAttributes { alpha?: boolean; @@ -41,59 +40,50 @@ export function createWebGLRenderingContext(attributes: WebGLContextAttributes): return createWebGLRenderingContextFromCanvas(canvas, attributes); } -/** - * Force the library to prefer WebGL 1.0 instead of WebGL 2.0 even when WebGL - * 2.0 is available. - */ -export function preferWebGL1() { - USE_WEBGL2_WHEN_AVAILABLE = false; - WEBGL2_ENABLED = null; -} - -/** - * Prefer WebGL 2.0 to WebGL 1.0. This is the default configuration. - */ -export function preferWebGL2() { - USE_WEBGL2_WHEN_AVAILABLE = true; - WEBGL2_ENABLED = null; -} - export function isWebGL2Enabled() { - if (!USE_WEBGL2_WHEN_AVAILABLE) { - return false; + const tempCanvas = document.createElement('canvas'); + const gl = tempCanvas.getContext('webgl2'); + if (gl != null) { + const loseContextExtension = + getExtensionOrThrow( + gl as WebGLRenderingContext, 'WEBGL_lose_context') as + WebGLLoseContextExtension; + loseContextExtension.loseContext(); + return true; } - - if (WEBGL2_ENABLED == null) { - const tempCanvas = document.createElement('canvas'); - const gl = tempCanvas.getContext('webgl2'); - if (gl != null) { - WEBGL2_ENABLED = true; - - const loseContextExtension = - getExtensionOrThrow( - gl as WebGLRenderingContext, 'WEBGL_lose_context') as - WebGLLoseContextExtension; - loseContextExtension.loseContext(); - } else { - WEBGL2_ENABLED = false; - } + return false; +} + +export function isWebGL1Enabled() { + const tempCanvas = document.createElement('canvas'); + const gl = + (tempCanvas.getContext('webgl') || + tempCanvas.getContext('experimental-webgl')) as WebGLRenderingContext; + if (gl != null) { + const loseContextExtension = + getExtensionOrThrow( + gl as WebGLRenderingContext, 'WEBGL_lose_context') as + WebGLLoseContextExtension; + loseContextExtension.loseContext(); + return true; } - return WEBGL2_ENABLED; + return false; } export function createWebGLRenderingContextFromCanvas( canvas: HTMLCanvasElement, attributes: WebGLContextAttributes): WebGLRenderingContext { let gl: WebGLRenderingContext; - if (isWebGL2Enabled()) { + const webglVersion = ENV.get('WEBGL_VERSION'); + if (webglVersion === 2) { gl = canvas.getContext('webgl2', attributes) as WebGLRenderingContext; - } else { + } else if (webglVersion === 1) { gl = (canvas.getContext('webgl', attributes) || canvas.getContext('experimental-webgl', attributes)) as WebGLRenderingContext; } - if (gl == null) { + if (webglVersion === 0 || gl == null) { throw new Error('This browser does not support WebGL.'); } return gl; @@ -263,7 +253,7 @@ export function queryMaxTextureSize(gl: WebGLRenderingContext): number { } export function getChannelsPerTexture(): number { - if (isWebGL2Enabled()) { + if (ENV.get('WEBGL_VERSION') === 2) { return 1; } return 4; diff --git a/src/util.ts b/src/util.ts index b6144e6a7d..b4ed92e01c 100644 --- a/src/util.ts +++ b/src/util.ts @@ -15,8 +15,8 @@ * ============================================================================= */ -export type Vector = number[] | Float64Array | Float32Array | Int32Array | - Int8Array | Int16Array; +export type Vector = + number[]|Float64Array|Float32Array|Int32Array|Int8Array|Int16Array; /** Shuffles the array using Fisher-Yates algorithm. */ // tslint:disable-next-line:no-any @@ -105,8 +105,7 @@ export function flatten(arr: any[], ret?: number[]): number[] { return ret; } -export type ArrayData = - number | number[] | number[][] | number[][][] | number[][][][]; +export type ArrayData = number|number[]|number[][]|number[][][]|number[][][][]; export function inferShape(arr: ArrayData): number[] { const shape: number[] = []; @@ -222,3 +221,43 @@ export function rightPad(a: string, size: number): string { } return a + ' '.repeat(size - a.length); } + +export function tryWithBackoff( + checkFn: () => boolean, maxBackoffMs: number): Promise { + return new Promise((resolve, reject) => { + let tryCount = 0; + + const tryFn = () => { + if (checkFn()) { + resolve(); + return; + } + + tryCount++; + + const nextBackoff = Math.pow(2, tryCount); + + if (nextBackoff >= maxBackoffMs) { + reject(); + return; + } + setTimeout(tryFn, nextBackoff); + }; + + setTimeout(tryFn, 0); + }); +} + +export function getQueryParams(queryString: string): {[key: string]: string} { + const params = {}; + queryString.replace(/[?&]([^=?&]+)(?:=([^&]*))?/g, (s, ...t) => { + decodeParam(params, t[0], t[1]); + return t.join('='); + }); + return params; +} + +function decodeParam( + params: {[key: string]: string}, name: string, value?: string) { + params[decodeURIComponent(name)] = decodeURIComponent(value || ''); +} diff --git a/src/util_test.ts b/src/util_test.ts index 61822c5de6..c37dc2c95a 100644 --- a/src/util_test.ts +++ b/src/util_test.ts @@ -146,3 +146,36 @@ describe('util.getBroadcastedShape', () => { expect(res).toEqual([7, 1, 1]); }); }); + +describe('util.tryWithBackoff', () => { + it('resolves', (doneFn) => { + let counter = 0; + const checkFn = () => { + counter++; + if (counter === 2) { + return true; + } + return false; + }; + + util.tryWithBackoff(checkFn, 128).then(doneFn).catch(() => { + throw new Error('Rejected backoff.'); + }); + }); + it('rejects', (doneFn) => { + const checkFn = () => false; + + util.tryWithBackoff(checkFn, 32) + .then(() => { + throw new Error('Backoff resolved'); + }) + .catch(doneFn); + }); +}); + +describe('util.getQueryParams', () => { + it('basic', () => { + expect(util.getQueryParams('?a=1&b=hi&f=animal')) + .toEqual({'a': '1', 'b': 'hi', 'f': 'animal'}); + }); +});