From bb624d922f9d2262727ac9118668d710c1f66889 Mon Sep 17 00:00:00 2001 From: Nikhil Thorat Date: Fri, 22 Sep 2017 18:43:28 -0400 Subject: [PATCH 1/8] async values --- .vscode/settings.json | 3 + demos/benchmarks/conv_benchmarks.ts | 4 +- .../benchmarks/conv_transposed_benchmarks.ts | 4 +- demos/benchmarks/logsumexp_benchmarks.ts | 4 +- demos/benchmarks/matmul_benchmarks.ts | 4 +- demos/benchmarks/pool_benchmarks.ts | 4 +- demos/benchmarks/reduction_ops_benchmark.ts | 4 +- demos/benchmarks/unary_ops_benchmark.ts | 4 +- demos/loop/index.html | 1 + demos/loop/main.ts | 46 +++++++ package.json | 1 + src/environment.ts | 84 +++++++++--- src/environment_test.ts | 127 +++++++++++++++--- src/graph/graph.ts | 35 ++++- src/graph/graph_layers.ts | 54 -------- src/math/ndarray.ts | 23 ++++ src/math/ndarray_test.ts | 38 +++++- src/math/webgl/gpgpu_context.ts | 35 ++--- src/math/webgl/webgl_util.ts | 30 ----- src/util.ts | 9 +- src/util_test.ts | 4 +- tslint.json | 1 + 22 files changed, 362 insertions(+), 157 deletions(-) create mode 100644 demos/loop/index.html create mode 100644 demos/loop/main.ts delete mode 100644 src/graph/graph_layers.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 8c298b177d..bfd3c95539 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,9 @@ "files.trimTrailingWhitespace": true, "editor.tabSize": 2, "editor.insertSpaces": true, + "[json]": { + "editor.formatOnSave": false + }, "editor.formatOnSave": true, "clang-format.style": "Google", "files.insertFinalNewline": true, diff --git a/demos/benchmarks/conv_benchmarks.ts b/demos/benchmarks/conv_benchmarks.ts index e3beeff75e..870caaccd6 100644 --- a/demos/benchmarks/conv_benchmarks.ts +++ b/demos/benchmarks/conv_benchmarks.ts @@ -80,8 +80,8 @@ export class ConvGPUBenchmark extends ConvBenchmark { gpgpu.dispose(); }; - if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { - gpgpu.runBenchmark(benchmark).then((timeElapsed: number) => { + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE')) { + gpgpu.runQuery(benchmark).then((timeElapsed: number) => { delayedCleanup(); resolve(timeElapsed); }); diff --git a/demos/benchmarks/conv_transposed_benchmarks.ts b/demos/benchmarks/conv_transposed_benchmarks.ts index 9bdb4c34e1..bb5791d622 100644 --- a/demos/benchmarks/conv_transposed_benchmarks.ts +++ b/demos/benchmarks/conv_transposed_benchmarks.ts @@ -81,8 +81,8 @@ export class ConvTransposedGPUBenchmark extends ConvTransposedBenchmark { gpgpu.dispose(); }; - if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { - gpgpu.runBenchmark(benchmark).then((timeElapsed: number) => { + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE')) { + gpgpu.runQuery(benchmark).then((timeElapsed: number) => { delayedCleanup(); resolve(timeElapsed); }); diff --git a/demos/benchmarks/logsumexp_benchmarks.ts b/demos/benchmarks/logsumexp_benchmarks.ts index afed074153..a9add49e86 100644 --- a/demos/benchmarks/logsumexp_benchmarks.ts +++ b/demos/benchmarks/logsumexp_benchmarks.ts @@ -66,8 +66,8 @@ export class LogSumExpGPUBenchmark extends BenchmarkTest { gpgpu.dispose(); }; - if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { - gpgpu.runBenchmark(benchmark).then((timeElapsed: number) => { + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE')) { + gpgpu.runQuery(benchmark).then((timeElapsed: number) => { delayedCleanup(); resolve(timeElapsed); }); diff --git a/demos/benchmarks/matmul_benchmarks.ts b/demos/benchmarks/matmul_benchmarks.ts index 019223c845..60f61b61d2 100644 --- a/demos/benchmarks/matmul_benchmarks.ts +++ b/demos/benchmarks/matmul_benchmarks.ts @@ -80,8 +80,8 @@ export class MatmulGPUBenchmark extends BenchmarkTest { gpgpu.dispose(); }; - if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { - gpgpu.runBenchmark(benchmark).then((timeElapsed: number) => { + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE')) { + gpgpu.runQuery(benchmark).then((timeElapsed: number) => { delayedCleanup(); resolve(timeElapsed); }); diff --git a/demos/benchmarks/pool_benchmarks.ts b/demos/benchmarks/pool_benchmarks.ts index a1322ab2d7..2ddda60430 100644 --- a/demos/benchmarks/pool_benchmarks.ts +++ b/demos/benchmarks/pool_benchmarks.ts @@ -96,8 +96,8 @@ export class PoolGPUBenchmark extends PoolBenchmark { gpgpu.dispose(); }; - if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { - gpgpu.runBenchmark(benchmark).then((timeElapsed: number) => { + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE')) { + gpgpu.runQuery(benchmark).then((timeElapsed: number) => { delayedCleanup(); resolve(timeElapsed); }); diff --git a/demos/benchmarks/reduction_ops_benchmark.ts b/demos/benchmarks/reduction_ops_benchmark.ts index fce53d82a4..9f71c98101 100644 --- a/demos/benchmarks/reduction_ops_benchmark.ts +++ b/demos/benchmarks/reduction_ops_benchmark.ts @@ -75,8 +75,8 @@ export class ReductionOpsGPUBenchmark extends ReductionOpsBenchmark { math.dispose(); }; - if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { - math.getGPGPUContext().runBenchmark(benchmark).then( + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE')) { + math.getGPGPUContext().runQuery(benchmark).then( (timeElapsed: number) => { delayedCleanup(); resolve(timeElapsed); diff --git a/demos/benchmarks/unary_ops_benchmark.ts b/demos/benchmarks/unary_ops_benchmark.ts index 727a507564..ea0c6a7859 100644 --- a/demos/benchmarks/unary_ops_benchmark.ts +++ b/demos/benchmarks/unary_ops_benchmark.ts @@ -103,8 +103,8 @@ export class UnaryOpsGPUBenchmark extends UnaryOpsBenchmark { math.dispose(); }; - if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER')) { - math.getGPGPUContext().runBenchmark(benchmark).then( + if (ENV.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE')) { + math.getGPGPUContext().runQuery(benchmark).then( (timeElapsed: number) => { delayedCleanup(); resolve(timeElapsed); diff --git a/demos/loop/index.html b/demos/loop/index.html new file mode 100644 index 0000000000..c43c667bb9 --- /dev/null +++ b/demos/loop/index.html @@ -0,0 +1 @@ + diff --git a/demos/loop/main.ts b/demos/loop/main.ts new file mode 100644 index 0000000000..f273ea48a6 --- /dev/null +++ b/demos/loop/main.ts @@ -0,0 +1,46 @@ +import {Array2D, NDArray, NDArrayMathGPU} from '../deeplearn'; + +// tslint:disable-next-line:no-any +(window as any).going = true; + +const math = new NDArrayMathGPU(); + +const mats: Array2D[] = []; +for (let i = 0; i < 1; i++) { + mats.push(Array2D.randNormal([256, 256], 0, .01)); +} + +let beforeTimestamp = 0; + +const vecIn = NDArray.randNormal([256, 256], 0, .01); + +function loop() { + console.log(performance.now() - beforeTimestamp); + // tslint:disable-next-line:no-any + if (!(window as any).going) { + return; + } + + const start = performance.now(); + const scopeOut = math.scope((keep, track) => { + let out = vecIn; + for (let i = 0; i < mats.length; i++) { + out = math.matMul(mats[i], out); + } + return out; + }); + + // scopeOut.getValues(); + // console.log('time: ' + (performance.now() - start)); + + + scopeOut.getValuesAsync().then(values => { + console.log('time: ' + (performance.now() - start)); + // console.log(values); + scopeOut.dispose(); + beforeTimestamp = performance.now(); + setTimeout(loop, 0); + }); +} + +loop(); diff --git a/package.json b/package.json index 2d4681a57f..eb901f3ffa 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "polymer-bundler": "~3.0.1", "tsify": "~3.0.1", "tslint": "~5.6.0", + "tslint-no-circular-imports": "~0.2.0", "typedoc": "~0.8.0", "typescript": "2.4.2", "uglify-js": "~3.0.28", diff --git a/src/environment.ts b/src/environment.ts index b149ae91a9..4da912c5e1 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -16,7 +16,6 @@ */ import * as device_util from './device_util'; -import * as webgl_util from './math/webgl/webgl_util'; import * as util from './util'; export enum Type { @@ -25,12 +24,14 @@ export enum Type { } export interface Features { - 'WEBGL_DISJOINT_QUERY_TIMER'?: boolean; + 'WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_ENABLED'?: boolean; + 'WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE'?: boolean; 'WEBGL_VERSION'?: number; } export const URL_PROPERTIES: URLProperty[] = [ - {name: 'WEBGL_DISJOINT_QUERY_TIMER', type: Type.BOOLEAN}, + {name: 'WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_ENABLED', type: Type.BOOLEAN}, + {name: 'WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE', type: Type.BOOLEAN}, {name: 'WEBGL_VERSION', type: Type.NUMBER} ]; @@ -39,18 +40,48 @@ export interface URLProperty { 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; +function getWebGLRenderingContext(webGLVersion: number): WebGLRenderingContext { + if (webGLVersion === 0) { + throw new Error('Cannot get WebGL rendering context, WebGL is disabled.'); + } + + const tempCanvas = document.createElement('canvas'); + if (webGLVersion === 1) { + return (tempCanvas.getContext('webgl') || + tempCanvas.getContext('experimental-webgl')) as + WebGLRenderingContext; + } + return tempCanvas.getContext('webgl2') as WebGLRenderingContext; +} + +function loseContext(gl: WebGLRenderingContext) { + if (gl != null) { + console.log(gl); + const loseContextExtension = gl.getExtension('WEBGL_lose_context'); + loseContextExtension.loseContext(); + } +} + +function isWebGLVersionEnabled(webGLVersion: 1|2) { + const gl = getWebGLRenderingContext(webGLVersion); + if (gl != null) { + loseContext(gl); + return true; + } + return false; +} + +function isWebGLDisjointQueryTimerEnabled(webGLVersion: number) { + const gl = getWebGLRenderingContext(webGLVersion); + + const extensionName = webGLVersion === 1 ? 'EXT_disjoint_timer_query' : + 'EXT_disjoint_timer_query_webgl2'; + const ext = gl.getExtension(extensionName); + const isExtEnabled = ext != null; + if (gl != null) { + loseContext(gl); } - throw new Error(`Unknown feature ${feature}.`); + return isExtEnabled; } export class Environment { @@ -67,10 +98,33 @@ export class Environment { return this.features[feature]; } - this.features[feature] = evaluateFeature(feature); + this.features[feature] = this.evaluateFeature(feature); return this.features[feature]; } + + private evaluateFeature(feature: K): Features[K] { + if (feature === 'WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_ENABLED') { + const webGLVersion = this.get('WEBGL_VERSION'); + + if (webGLVersion === 0) { + return false; + } + + return isWebGLDisjointQueryTimerEnabled(webGLVersion); + } else if (feature === 'WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE') { + return this.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_ENABLED') && + !device_util.isMobile(); + } else if (feature === 'WEBGL_VERSION') { + if (isWebGLVersionEnabled(2)) { + return 2; + } else if (isWebGLVersionEnabled(1)) { + return 1; + } + return 0; + } + throw new Error(`Unknown feature ${feature}.`); + } } // Expects flags from URL in the format ?dljsflags=FLAG1:1,FLAG2:true. diff --git a/src/environment_test.ts b/src/environment_test.ts index 50a02c206c..f144f75d97 100644 --- a/src/environment_test.ts +++ b/src/environment_test.ts @@ -15,52 +15,143 @@ * ============================================================================= */ import * as device_util from './device_util'; -import {Environment} from './environment'; -import * as webgl_util from './math/webgl/webgl_util'; +import {Environment, Features} from './environment'; -describe('disjoint query timer', () => { - it('mobile', () => { +describe('disjoint query timer enabled', () => { + it('no webgl', () => { + const features: Features = {'WEBGL_VERSION': 0}; + + const env = new Environment(features); + + expect(env.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_ENABLED')).toBe(false); + }); + + it('webgl 1', () => { + const features: Features = {'WEBGL_VERSION': 1}; + + spyOn(document, 'createElement').and.returnValue({ + getContext: (context: string) => { + if (context === 'webgl') { + return { + getExtension: (extensionName: string) => { + if (extensionName === 'EXT_disjoint_timer_query') { + return {}; + } + return null; + } + }; + } + return null; + } + }); + + const env = new Environment(features); + + expect(env.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_ENABLED')).toBe(true); + }); + + it('webgl 2', () => { + const features: Features = {'WEBGL_VERSION': 2}; + + spyOn(document, 'createElement').and.returnValue({ + getContext: (context: string) => { + if (context === 'webgl2') { + return { + getExtension: (extensionName: string) => { + if (extensionName === 'EXT_disjoint_timer_query_webgl2') { + return {}; + } + return null; + } + }; + } + return null; + } + }); + + const env = new Environment(features); + + expect(env.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_ENABLED')).toBe(true); + }); + + +}); +describe('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE', () => { + it('disjoint query timer disabled', () => { + const features: + Features = {'WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_ENABLED': false}; + + const env = new Environment(features); + + expect(env.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE')) + .toBe(false); + }); + + it('disjoint query timer enabled, mobile', () => { + const features: + Features = {'WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_ENABLED': true}; spyOn(device_util, 'isMobile').and.returnValue(true); - const env = new Environment(); + const env = new Environment(features); - expect(env.get('WEBGL_DISJOINT_QUERY_TIMER')).toBe(false); + expect(env.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE')) + .toBe(false); }); - it('not mobile', () => { + it('disjoint query timer enabled, not mobile', () => { + const features: + Features = {'WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_ENABLED': true}; spyOn(device_util, 'isMobile').and.returnValue(false); - const env = new Environment(); + const env = new Environment(features); - expect(env.get('WEBGL_DISJOINT_QUERY_TIMER')).toBe(true); + expect(env.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE')).toBe(true); }); }); describe('WebGL version', () => { it('webgl 1', () => { - spyOn(webgl_util, 'isWebGL1Enabled').and.returnValue(true); - spyOn(webgl_util, 'isWebGL2Enabled').and.returnValue(false); + spyOn(document, 'createElement').and.returnValue({ + getContext: (context: string) => { + if (context === 'webgl') { + return { + getExtension: (a: string) => { + return {loseContext: () => {}}; + } + }; + } + return null; + } + }); 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); + spyOn(document, 'createElement').and.returnValue({ + getContext: (context: string) => { + if (context === 'webgl2') { + return { + getExtension: (a: string) => { + return {loseContext: () => {}}; + } + }; + } + return null; + } + }); 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); + spyOn(document, 'createElement').and.returnValue({ + getContext: (context: string): WebGLRenderingContext => null + }); const env = new Environment(); - expect(env.get('WEBGL_VERSION')).toBe(0); }); }); diff --git a/src/graph/graph.ts b/src/graph/graph.ts index 2858ba2177..8eb8d941d8 100644 --- a/src/graph/graph.ts +++ b/src/graph/graph.ts @@ -15,12 +15,45 @@ * ============================================================================= */ +// tslint:disable-next-line:max-line-length +import {Initializer, VarianceScalingInitializer, ZerosInitializer} from '../initializers'; import * as concat3d_util from '../math/concat3d_util'; import * as conv_util from '../math/conv_util'; import {NDArray, Scalar} from '../math/ndarray'; import * as util from '../util'; -import {GraphLayers} from './graph_layers'; +/** + * A layers sugar class around the graph that initializes variables + * automatically for layers. + */ +export class GraphLayers { + constructor(private g: Graph) {} + + dense( + name: string, x: Tensor, units: number, + activation: ((x: Tensor) => Tensor)|null = null, useBias = true, + kernelInitializer: Initializer = new VarianceScalingInitializer(), + biasInitializer: Initializer = new ZerosInitializer()) { + const weights = this.g.variable( + name + '-weights', + kernelInitializer.initialize([x.shape[0], units], x.shape[0], units)); + + let out = this.g.matmul(x, weights); + + if (useBias) { + const bias = this.g.variable( + name + '-bias', + biasInitializer.initialize([units], x.shape[0], units)); + out = this.g.add(out, bias); + } + + if (activation != null) { + out = activation(out); + } + + return out; + } +} /** * Graph is the primary container structure for deeplearn.js operations. Graph diff --git a/src/graph/graph_layers.ts b/src/graph/graph_layers.ts deleted file mode 100644 index 6922481bfc..0000000000 --- a/src/graph/graph_layers.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @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. - * ============================================================================= - */ - -// tslint:disable-next-line:max-line-length -import {Initializer, VarianceScalingInitializer, ZerosInitializer} from '../initializers'; - -import {Graph, Tensor} from './graph'; - -/** - * A layers sugar class around the graph that initializes variables - * automatically for layers. - */ -export class GraphLayers { - constructor(private g: Graph) {} - - dense( - name: string, x: Tensor, units: number, - activation: ((x: Tensor) => Tensor)|null = null, useBias = true, - kernelInitializer: Initializer = new VarianceScalingInitializer(), - biasInitializer: Initializer = new ZerosInitializer()) { - const weights = this.g.variable( - name + '-weights', - kernelInitializer.initialize([x.shape[0], units], x.shape[0], units)); - - let out = this.g.matmul(x, weights); - - if (useBias) { - const bias = this.g.variable( - name + '-bias', - biasInitializer.initialize([units], x.shape[0], units)); - out = this.g.add(out, bias); - } - - if (activation != null) { - out = activation(out); - } - - return out; - } -} diff --git a/src/math/ndarray.ts b/src/math/ndarray.ts index f9b140dc81..8371bb8815 100644 --- a/src/math/ndarray.ts +++ b/src/math/ndarray.ts @@ -15,12 +15,14 @@ * ============================================================================= */ +import {ENV} from '../environment'; import * as util from '../util'; import {GPGPUContext} from './webgl/gpgpu_context'; import {TextureManager} from './webgl/texture_manager'; import * as webgl_util from './webgl/webgl_util'; + // These global variables need to be initialized to null so that closure knows // not to seal them. /** @hidden */ @@ -243,6 +245,27 @@ export class NDArray { return this.data.values; } + getValuesAsync(): Promise { + return new Promise((resolve, reject) => { + if (this.data.values != null) { + resolve(this.data.values); + return; + } + + if (!ENV.get('WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_ENABLED')) { + resolve(this.getValues()); + return; + } + + // Construct an empty query. We're just interested in getting a callback + // when the GPU command queue has executed until this point in time. + const queryFn = () => {}; + GPGPU.runQuery(queryFn).then(() => { + resolve(this.getValues()); + }); + }); + } + private uploadToGPU(preferredTexShape?: [number, number]) { throwIfGPUNotInitialized(); this.data.textureShapeRC = webgl_util.getTextureShapeFromLogicalShape( diff --git a/src/math/ndarray_test.ts b/src/math/ndarray_test.ts index 5ca7ce01e7..ff65602dca 100644 --- a/src/math/ndarray_test.ts +++ b/src/math/ndarray_test.ts @@ -127,7 +127,7 @@ describe('NDArray', () => { expect(a.get(1, 2)).toBe(10); }); - it('NDArray CPU --> GPU', () => { + it('NDArray getValues CPU --> GPU', () => { const a = Array2D.new([3, 2], [1, 2, 3, 4, 5, 6]); expect(a.inGPU()).toBe(false); @@ -143,7 +143,7 @@ describe('NDArray', () => { a.dispose(); }); - it('NDArray GPU --> CPU', () => { + it('NDArray getValues GPU --> CPU', () => { const texture = textureManager.acquireTexture([3, 2]); gpgpu.uploadMatrixToTexture( texture, 3, 2, new Float32Array([1, 2, 3, 4, 5, 6])); @@ -155,6 +155,40 @@ describe('NDArray', () => { expect(a.inGPU()).toBe(false); }); + it('NDArray getValuesAsync CPU --> GPU', (doneFn) => { + const a = Array2D.new([3, 2], [1, 2, 3, 4, 5, 6]); + + expect(a.inGPU()).toBe(false); + + a.getValuesAsync().then(values => { + expect(values).toEqual(new Float32Array([1, 2, 3, 4, 5, 6])); + + expect(a.inGPU()).toBe(false); + + // Upload to GPU. + expect(a.getTexture() != null).toBe(true); + + expect(a.inGPU()).toBe(true); + a.dispose(); + doneFn(); + }); + }); + + it('NDArray getValuesAsync GPU --> CPU', (doneFn) => { + const texture = textureManager.acquireTexture([3, 2]); + gpgpu.uploadMatrixToTexture( + texture, 3, 2, new Float32Array([1, 2, 3, 4, 5, 6])); + + const a = new Array2D([3, 2], {texture, textureShapeRC: [3, 2]}); + expect(a.inGPU()).toBe(true); + + a.getValuesAsync().then(values => { + expect(values).toEqual(new Float32Array([1, 2, 3, 4, 5, 6])); + expect(a.inGPU()).toBe(false); + doneFn(); + }); + }); + it('Scalar basic methods', () => { const a = Scalar.new(5); expect(a.get()).toBe(5); diff --git a/src/math/webgl/gpgpu_context.ts b/src/math/webgl/gpgpu_context.ts index cbffb9d098..227cf6ddb8 100644 --- a/src/math/webgl/gpgpu_context.ts +++ b/src/math/webgl/gpgpu_context.ts @@ -259,14 +259,20 @@ export class GPGPUContext { webgl_util.callAndCheck(this.gl, () => this.gl.finish()); } - public runBenchmark(benchmark: () => void): Promise { + /** + * Executes a query function which contains GL commands and resolves when + * the command buffer has finished executing the query. + * @param queryFn The query function containing GL commands to execute. + * @return a promise that resolves with the ellapsed time. + */ + public runQuery(queryFn: () => void): Promise { if (ENV.get('WEBGL_VERSION') === 2) { - return this.runBenchmarkWebGL2(benchmark); + return this.runQueryWebGL2(queryFn); } - return this.runBenchmarkWebGL1(benchmark); + return this.runQueryWebGL1(queryFn); } - private runBenchmarkWebGL2(benchmark: () => void): Promise { + private runQueryWebGL2(benchmark: () => void): Promise { const ext = webgl_util.getExtensionOrThrow( this.gl, 'EXT_disjoint_timer_query_webgl2'); // tslint:disable-next-line:no-any @@ -296,13 +302,13 @@ export class GPGPUContext { }; const getTimeElapsed = () => { - const timeElapsed = + const timeElapsedMicros = // 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); + resolve(timeElapsedMicros / 1000000); }; const resolveWithWarning = () => { @@ -310,14 +316,11 @@ export class GPGPUContext { resolve(-1); }; - const maxBackoffMs = 2048; - util.tryWithBackoff(queryGPU, maxBackoffMs) - .then(getTimeElapsed) - .catch(resolveWithWarning); + util.repeatedTry(queryGPU).then(getTimeElapsed).catch(resolveWithWarning); }); } - private runBenchmarkWebGL1(benchmark: () => void): Promise { + private runQueryWebGL1(benchmark: () => void): Promise { const ext = webgl_util.getExtensionOrThrow( // tslint:disable-next-line:no-any this.gl, 'EXT_disjoint_timer_query') as any; @@ -340,9 +343,10 @@ export class GPGPUContext { }; const getTimeElapsed = () => { - const timeElapsed = ext.getQueryObjectEXT(query, ext.QUERY_RESULT_EXT); + const timeElapsedMicros = + ext.getQueryObjectEXT(query, ext.QUERY_RESULT_EXT); // Return milliseconds. - resolve(timeElapsed / 1000000); + resolve(timeElapsedMicros / 1000000); }; const resolveWithWarning = () => { @@ -350,10 +354,7 @@ export class GPGPUContext { resolve(-1); }; - const maxBackoffMs = 2048; - util.tryWithBackoff(queryGPU, maxBackoffMs) - .then(getTimeElapsed) - .catch(resolveWithWarning); + util.repeatedTry(queryGPU).then(getTimeElapsed).catch(resolveWithWarning); }); } diff --git a/src/math/webgl/webgl_util.ts b/src/math/webgl/webgl_util.ts index 07ed208e79..3dc809d3da 100644 --- a/src/math/webgl/webgl_util.ts +++ b/src/math/webgl/webgl_util.ts @@ -40,36 +40,6 @@ export function createWebGLRenderingContext(attributes: WebGLContextAttributes): return createWebGLRenderingContextFromCanvas(canvas, attributes); } -export function isWebGL2Enabled() { - 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; - } - 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 false; -} - export function createWebGLRenderingContextFromCanvas( canvas: HTMLCanvasElement, attributes: WebGLContextAttributes): WebGLRenderingContext { diff --git a/src/util.ts b/src/util.ts index b4ed92e01c..42815dafb6 100644 --- a/src/util.ts +++ b/src/util.ts @@ -222,8 +222,9 @@ export function rightPad(a: string, size: number): string { return a + ' '.repeat(size - a.length); } -export function tryWithBackoff( - checkFn: () => boolean, maxBackoffMs: number): Promise { +export function repeatedTry( + checkFn: () => boolean, delayFn = (counter: number) => 0, + maxCounter?: number) { return new Promise((resolve, reject) => { let tryCount = 0; @@ -235,9 +236,9 @@ export function tryWithBackoff( tryCount++; - const nextBackoff = Math.pow(2, tryCount); + const nextBackoff = delayFn(tryCount); - if (nextBackoff >= maxBackoffMs) { + if (maxCounter != null && tryCount >= maxCounter) { reject(); return; } diff --git a/src/util_test.ts b/src/util_test.ts index c37dc2c95a..c643cf7ae1 100644 --- a/src/util_test.ts +++ b/src/util_test.ts @@ -158,14 +158,14 @@ describe('util.tryWithBackoff', () => { return false; }; - util.tryWithBackoff(checkFn, 128).then(doneFn).catch(() => { + util.repeatedTry(checkFn).then(doneFn).catch(() => { throw new Error('Rejected backoff.'); }); }); it('rejects', (doneFn) => { const checkFn = () => false; - util.tryWithBackoff(checkFn, 32) + util.repeatedTry(checkFn, () => 0, 100) .then(() => { throw new Error('Backoff resolved'); }) diff --git a/tslint.json b/tslint.json index 546ec936f5..50bfa26bf8 100644 --- a/tslint.json +++ b/tslint.json @@ -1,4 +1,5 @@ { + "extends": ["tslint-no-circular-imports"], "rules": { "array-type": [true, "array-simple"], "arrow-return-shorthand": true, From 3f0a6fbdcb7c6b606717acf174b0f136451ea04f Mon Sep 17 00:00:00 2001 From: Nikhil Thorat Date: Fri, 22 Sep 2017 18:45:44 -0400 Subject: [PATCH 2/8] merge --- src/environment_test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/environment_test.ts b/src/environment_test.ts index e333376621..f144f75d97 100644 --- a/src/environment_test.ts +++ b/src/environment_test.ts @@ -15,11 +15,7 @@ * ============================================================================= */ import * as device_util from './device_util'; -<<<<<<< HEAD import {Environment, Features} from './environment'; -======= -import {Environment} from './environment'; ->>>>>>> origin describe('disjoint query timer enabled', () => { it('no webgl', () => { From 1178627db92d833c333375c1cdf5cd0a9011b8c4 Mon Sep 17 00:00:00 2001 From: Nikhil Thorat Date: Fri, 22 Sep 2017 18:50:17 -0400 Subject: [PATCH 3/8] fix unit tests --- src/environment_test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/environment_test.ts b/src/environment_test.ts index f144f75d97..b847c55408 100644 --- a/src/environment_test.ts +++ b/src/environment_test.ts @@ -31,12 +31,15 @@ describe('disjoint query timer enabled', () => { spyOn(document, 'createElement').and.returnValue({ getContext: (context: string) => { - if (context === 'webgl') { + if (context === 'webgl' || context === 'experimental-webgl') { return { getExtension: (extensionName: string) => { if (extensionName === 'EXT_disjoint_timer_query') { return {}; + } else if (extensionName === 'WEBGL_lose_context') { + return {loseContext: () => {}}; } + console.log('returning null'); return null; } }; @@ -60,6 +63,8 @@ describe('disjoint query timer enabled', () => { getExtension: (extensionName: string) => { if (extensionName === 'EXT_disjoint_timer_query_webgl2') { return {}; + } else if (extensionName === 'WEBGL_lose_context') { + return {loseContext: () => {}}; } return null; } From 7554198524353c85eeed472f14b9b57f5d8e724c Mon Sep 17 00:00:00 2001 From: Nikhil Thorat Date: Fri, 22 Sep 2017 18:52:39 -0400 Subject: [PATCH 4/8] remove loop demo --- demos/loop/index.html | 1 - demos/loop/main.ts | 46 ------------------------------------------- 2 files changed, 47 deletions(-) delete mode 100644 demos/loop/index.html delete mode 100644 demos/loop/main.ts diff --git a/demos/loop/index.html b/demos/loop/index.html deleted file mode 100644 index c43c667bb9..0000000000 --- a/demos/loop/index.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/demos/loop/main.ts b/demos/loop/main.ts deleted file mode 100644 index f273ea48a6..0000000000 --- a/demos/loop/main.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {Array2D, NDArray, NDArrayMathGPU} from '../deeplearn'; - -// tslint:disable-next-line:no-any -(window as any).going = true; - -const math = new NDArrayMathGPU(); - -const mats: Array2D[] = []; -for (let i = 0; i < 1; i++) { - mats.push(Array2D.randNormal([256, 256], 0, .01)); -} - -let beforeTimestamp = 0; - -const vecIn = NDArray.randNormal([256, 256], 0, .01); - -function loop() { - console.log(performance.now() - beforeTimestamp); - // tslint:disable-next-line:no-any - if (!(window as any).going) { - return; - } - - const start = performance.now(); - const scopeOut = math.scope((keep, track) => { - let out = vecIn; - for (let i = 0; i < mats.length; i++) { - out = math.matMul(mats[i], out); - } - return out; - }); - - // scopeOut.getValues(); - // console.log('time: ' + (performance.now() - start)); - - - scopeOut.getValuesAsync().then(values => { - console.log('time: ' + (performance.now() - start)); - // console.log(values); - scopeOut.dispose(); - beforeTimestamp = performance.now(); - setTimeout(loop, 0); - }); -} - -loop(); From ff063f5d6bab7ab79aeac5c4520992b8b5d802fc Mon Sep 17 00:00:00 2001 From: Nikhil Thorat Date: Fri, 22 Sep 2017 18:56:59 -0400 Subject: [PATCH 5/8] remove line from ndarray.ts --- src/math/ndarray.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/math/ndarray.ts b/src/math/ndarray.ts index 8371bb8815..508b4f5c55 100644 --- a/src/math/ndarray.ts +++ b/src/math/ndarray.ts @@ -22,7 +22,6 @@ import {GPGPUContext} from './webgl/gpgpu_context'; import {TextureManager} from './webgl/texture_manager'; import * as webgl_util from './webgl/webgl_util'; - // These global variables need to be initialized to null so that closure knows // not to seal them. /** @hidden */ From f0055f1b476a3b9765b6ae309eb116a9e7d2da33 Mon Sep 17 00:00:00 2001 From: Nikhil Thorat Date: Fri, 22 Sep 2017 19:02:00 -0400 Subject: [PATCH 6/8] throw when lose context not supported --- src/environment.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/environment.ts b/src/environment.ts index 4da912c5e1..ca2abccb8c 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -56,8 +56,11 @@ function getWebGLRenderingContext(webGLVersion: number): WebGLRenderingContext { function loseContext(gl: WebGLRenderingContext) { if (gl != null) { - console.log(gl); const loseContextExtension = gl.getExtension('WEBGL_lose_context'); + if (loseContextExtension == null) { + throw new Error( + 'Extension WEBGL_lose_context_extension not supported on this browser.'); + } loseContextExtension.loseContext(); } } From 3c8d4c41c118c19c5751cfc7d19a266efa4d72ec Mon Sep 17 00:00:00 2001 From: Nikhil Thorat Date: Fri, 22 Sep 2017 19:02:47 -0400 Subject: [PATCH 7/8] WEBGL_lose_context --- src/environment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/environment.ts b/src/environment.ts index ca2abccb8c..66038cf94a 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -59,7 +59,7 @@ function loseContext(gl: WebGLRenderingContext) { const loseContextExtension = gl.getExtension('WEBGL_lose_context'); if (loseContextExtension == null) { throw new Error( - 'Extension WEBGL_lose_context_extension not supported on this browser.'); + 'Extension WEBGL_lose_context not supported on this browser.'); } loseContextExtension.loseContext(); } From d3408e78f9141935c50a635d3d0ceee2394b86a4 Mon Sep 17 00:00:00 2001 From: Nikhil Thorat Date: Sat, 23 Sep 2017 10:59:47 -0400 Subject: [PATCH 8/8] respond to comments --- src/environment.ts | 4 ++++ src/environment_test.ts | 1 - src/math/webgl/gpgpu_context.ts | 10 +++++----- src/util.ts | 2 +- src/util_test.ts | 4 ++-- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/environment.ts b/src/environment.ts index 66038cf94a..09332026fb 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -24,8 +24,12 @@ export enum Type { } export interface Features { + // Whether the disjoint_query_timer extension is an available extension. 'WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_ENABLED'?: boolean; + // Whether the timer object from the disjoint_query_timer extension gives + // timing information that is reliable. 'WEBGL_DISJOINT_QUERY_TIMER_EXTENSION_RELIABLE'?: boolean; + // 0: No WebGL, 1: WebGL 1.0, 2: WebGL 2.0. 'WEBGL_VERSION'?: number; } diff --git a/src/environment_test.ts b/src/environment_test.ts index b847c55408..8a53ce0212 100644 --- a/src/environment_test.ts +++ b/src/environment_test.ts @@ -39,7 +39,6 @@ describe('disjoint query timer enabled', () => { } else if (extensionName === 'WEBGL_lose_context') { return {loseContext: () => {}}; } - console.log('returning null'); return null; } }; diff --git a/src/math/webgl/gpgpu_context.ts b/src/math/webgl/gpgpu_context.ts index 227cf6ddb8..66fa1a8601 100644 --- a/src/math/webgl/gpgpu_context.ts +++ b/src/math/webgl/gpgpu_context.ts @@ -263,7 +263,7 @@ export class GPGPUContext { * Executes a query function which contains GL commands and resolves when * the command buffer has finished executing the query. * @param queryFn The query function containing GL commands to execute. - * @return a promise that resolves with the ellapsed time. + * @return a promise that resolves with the ellapsed time in milliseconds. */ public runQuery(queryFn: () => void): Promise { if (ENV.get('WEBGL_VERSION') === 2) { @@ -302,13 +302,13 @@ export class GPGPUContext { }; const getTimeElapsed = () => { - const timeElapsedMicros = + const timeElapsedNanos = // 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(timeElapsedMicros / 1000000); + resolve(timeElapsedNanos / 1000000); }; const resolveWithWarning = () => { @@ -343,10 +343,10 @@ export class GPGPUContext { }; const getTimeElapsed = () => { - const timeElapsedMicros = + const timeElapsedNanos = ext.getQueryObjectEXT(query, ext.QUERY_RESULT_EXT); // Return milliseconds. - resolve(timeElapsedMicros / 1000000); + resolve(timeElapsedNanos / 1000000); }; const resolveWithWarning = () => { diff --git a/src/util.ts b/src/util.ts index 42815dafb6..ea3bca537d 100644 --- a/src/util.ts +++ b/src/util.ts @@ -224,7 +224,7 @@ export function rightPad(a: string, size: number): string { export function repeatedTry( checkFn: () => boolean, delayFn = (counter: number) => 0, - maxCounter?: number) { + maxCounter?: number): Promise { return new Promise((resolve, reject) => { let tryCount = 0; diff --git a/src/util_test.ts b/src/util_test.ts index c643cf7ae1..c1b55e38bd 100644 --- a/src/util_test.ts +++ b/src/util_test.ts @@ -147,7 +147,7 @@ describe('util.getBroadcastedShape', () => { }); }); -describe('util.tryWithBackoff', () => { +describe('util.repeatedTry', () => { it('resolves', (doneFn) => { let counter = 0; const checkFn = () => { @@ -165,7 +165,7 @@ describe('util.tryWithBackoff', () => { it('rejects', (doneFn) => { const checkFn = () => false; - util.repeatedTry(checkFn, () => 0, 100) + util.repeatedTry(checkFn, () => 0, 5) .then(() => { throw new Error('Backoff resolved'); })