Skip to content
This repository has been archived by the owner on Aug 15, 2019. It is now read-only.

Further modularize unit tests. #1665

Merged
merged 15 commits into from
Apr 11, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ add new backends.
### Adding an op

When adding ops to the library and deciding whether to write a kernel
implementation in [backend.ts](/~https://github.com/tensorflow/tfjs-core/blob/master/src/kernels/backend.ts),
implementation in [backend.ts](/~https://github.com/tensorflow/tfjs-core/blob/master/src/backends/backend.ts),
be sure to check out the TensorFlow ops list [here](/~https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/ops/ops.pbtxt).
This list shows the kernels available for the TensorFlow C API. To ensure that
we can bind to this with node.js, we should ensure that our backend.ts
Expand Down
File renamed without changes.
144 changes: 144 additions & 0 deletions src/backends/cpu/backend_cpu_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/**
* @license
* Copyright 2019 Google LLC. 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 tf from '../../index';
import {describeWithFlags} from '../../jasmine_util';
import {tensor2d} from '../../ops/ops';
import {expectArraysEqual} from '../../test_util';

import {MathBackendCPU} from './backend_cpu';
import {CPU_ENVS} from './backend_cpu_test_registry';

describeWithFlags('backendCPU', CPU_ENVS, () => {
let backend: MathBackendCPU;
beforeEach(() => {
backend = tf.backend() as MathBackendCPU;
});

it('register empty string tensor', () => {
const t = tf.Tensor.make([3], {}, 'string');
expect(backend.readSync(t.dataId) == null).toBe(true);
});

it('register empty string tensor and write', () => {
const t = tf.Tensor.make([3], {}, 'string');
backend.write(t.dataId, ['c', 'a', 'b']);
expectArraysEqual(backend.readSync(t.dataId), ['c', 'a', 'b']);
});

it('register string tensor with values', () => {
const t = tf.Tensor.make([3], {values: ['a', 'b', 'c']}, 'string');
expectArraysEqual(backend.readSync(t.dataId), ['a', 'b', 'c']);
});

it('register string tensor with values and overwrite', () => {
const t = tf.Tensor.make([3], {values: ['a', 'b', 'c']}, 'string');
backend.write(t.dataId, ['c', 'a', 'b']);
expectArraysEqual(backend.readSync(t.dataId), ['c', 'a', 'b']);
});

it('register string tensor with values and mismatched shape', () => {
expect(() => tf.tensor(['a', 'b', 'c'], [4], 'string')).toThrowError();
});
});

describeWithFlags('depthToSpace', CPU_ENVS, () => {
it('throws when CPU backend used with data format NCHW', () => {
const t = tf.tensor4d([1, 2, 3, 4], [1, 4, 1, 1]);
const blockSize = 2;
const dataFormat = 'NCHW';

expect(() => tf.depthToSpace(t, blockSize, dataFormat))
.toThrowError(
`Only NHWC dataFormat supported on CPU for depthToSpace. Got ${
dataFormat}`);
});
});

describeWithFlags('gatherND CPU', CPU_ENVS, () => {
it('should throw error when index out of range', () => {
const indices = tensor2d([0, 2, 99], [3, 1], 'int32');
const input = tensor2d(
[100, 101, 102, 777, 778, 779, 10000, 10001, 10002], [3, 3], 'float32');
expect(() => tf.gatherND(input, indices)).toThrow();
});
});

describeWithFlags('scatterND CPU', CPU_ENVS, () => {
it('should throw error when index out of range', () => {
const indices = tf.tensor2d([0, 4, 99], [3, 1], 'int32');
const updates = tf.tensor2d(
[100, 101, 102, 777, 778, 779, 10000, 10001, 10002], [3, 3], 'float32');
const shape = [5, 3];
expect(() => tf.scatterND(indices, updates, shape)).toThrow();
});

it('should throw error when indices has wrong dimension', () => {
const indices = tf.tensor2d([0, 4, 99], [3, 1], 'int32');
const updates = tf.tensor2d(
[100, 101, 102, 777, 778, 779, 10000, 10001, 10002], [3, 3], 'float32');
const shape = [2, 3];
expect(() => tf.scatterND(indices, updates, shape)).toThrow();
});
});

describeWithFlags('sparseToDense CPU', CPU_ENVS, () => {
it('should throw error when index out of range', () => {
const defaultValue = 2;
const indices = tf.tensor1d([0, 2, 6], 'int32');
const values = tf.tensor1d([100, 101, 102], 'int32');
const shape = [6];
expect(() => tf.sparseToDense(indices, values, shape, defaultValue))
.toThrow();
});
});

describeWithFlags('memory cpu', CPU_ENVS, () => {
it('unreliable is true due to auto gc', () => {
tf.tensor(1);
const mem = tf.memory();
expect(mem.numTensors).toBe(1);
expect(mem.numDataBuffers).toBe(1);
expect(mem.numBytes).toBe(4);
expect(mem.unreliable).toBe(true);

const expectedReason =
'The reported memory is an upper bound. Due to automatic garbage ' +
'collection, the true allocated memory may be less.';
expect(mem.reasons.indexOf(expectedReason) >= 0).toBe(true);
});

it('unreliable is true due to both auto gc and string tensors', () => {
tf.tensor(1);
tf.tensor('a');

const mem = tf.memory();
expect(mem.numTensors).toBe(2);
expect(mem.numDataBuffers).toBe(2);
expect(mem.numBytes).toBe(6);
expect(mem.unreliable).toBe(true);

const expectedReasonGC =
'The reported memory is an upper bound. Due to automatic garbage ' +
'collection, the true allocated memory may be less.';
expect(mem.reasons.indexOf(expectedReasonGC) >= 0).toBe(true);
const expectedReasonString =
'Memory usage by string tensors is approximate ' +
'(2 bytes per character)';
expect(mem.reasons.indexOf(expectedReasonString) >= 0).toBe(true);
});
});
11 changes: 8 additions & 3 deletions src/test_env.ts → ...backends/cpu/backend_cpu_test_registry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Copyright 2019 Google LLC. 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
Expand All @@ -15,5 +15,10 @@
* =============================================================================
*/

import './kernels/cpu/backend_cpu';
import './kernels/webgl/backend_webgl';
import {Constraints, registerTestEnv} from '../../jasmine_util';

export const CPU_ENVS: Constraints = {
backends: 'cpu'
};

registerTestEnv({name: 'cpu', backendName: 'cpu'});
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Copyright 2019 Google LLC. 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
Expand All @@ -16,9 +16,10 @@
*/

import * as tf from '../../index';
import {describeWithFlags, WEBGL_ENVS} from '../../jasmine_util';
import {describeWithFlags} from '../../jasmine_util';
import {expectArraysClose, expectArraysEqual} from '../../test_util';
import {MathBackendWebGL, WebGLMemoryInfo} from './backend_webgl';
import {WEBGL_ENVS} from './backend_webgl_test_registry';

describeWithFlags('lazy packing and unpacking', WEBGL_ENVS, () => {
let webglLazilyUnpackFlagSaved: boolean;
Expand Down Expand Up @@ -307,3 +308,127 @@ describeWithFlags('upload tensors as uniforms', FLOAT32_WEBGL_ENVS, () => {
expectArraysClose(res, expected);
});
});

describeWithFlags('debug on webgl', WEBGL_ENVS, () => {
beforeAll(() => {
tf.ENV.set('DEBUG', true);
});

afterAll(() => {
tf.ENV.set('DEBUG', false);
});

it('debug mode errors when overflow in tensor construction', () => {
const savedRenderFloat32Flag =
tf.ENV.getBool('WEBGL_RENDER_FLOAT32_ENABLED');
tf.ENV.set('WEBGL_RENDER_FLOAT32_ENABLED', false);
const a = () => tf.tensor1d([2, Math.pow(2, 17)], 'float32');
expect(a).toThrowError();
tf.ENV.set('WEBGL_RENDER_FLOAT32_ENABLED', savedRenderFloat32Flag);
});

it('debug mode errors when underflow in tensor construction', () => {
const savedRenderFloat32Flag =
tf.ENV.getBool('WEBGL_RENDER_FLOAT32_ENABLED');
tf.ENV.set('WEBGL_RENDER_FLOAT32_ENABLED', false);
const a = () => tf.tensor1d([2, 1e-8], 'float32');
expect(a).toThrowError();
tf.ENV.set('WEBGL_RENDER_FLOAT32_ENABLED', savedRenderFloat32Flag);
});
});

describeWithFlags('memory webgl', WEBGL_ENVS, () => {
it('unreliable is falsy/not present when all tensors are numeric', () => {
tf.tensor(1);
const mem = tf.memory();
expect(mem.numTensors).toBe(1);
expect(mem.numDataBuffers).toBe(1);
expect(mem.numBytes).toBe(4);
expect(mem.unreliable).toBeFalsy();
});
});

// We do not yet fully support half float backends. These tests are a starting
// point.
describeWithFlags('backend without render float32 support', WEBGL_ENVS, () => {
const savedRenderFloat32Flag = tf.ENV.getBool('WEBGL_RENDER_FLOAT32_ENABLED');

beforeAll(() => {
tf.ENV.set('WEBGL_RENDER_FLOAT32_ENABLED', false);
});

beforeEach(() => {
tf.registerBackend('half-float-webgl', () => new MathBackendWebGL(null));
});

afterEach(() => {
tf.removeBackend('half-float-webgl');
});

afterAll(() => {
tf.ENV.set('WEBGL_RENDER_FLOAT32_ENABLED', savedRenderFloat32Flag);
});

it('basic usage', () => {
tf.setBackend('half-float-webgl');

const a = tf.tensor2d([1, 2], [1, 2]);
const b = tf.tensor2d([1, 2], [1, 2]);
const c = tf.add(a, b);
expectArraysClose(c, [2, 4]);
});

it('disposing tensors should not cause errors', () => {
tf.setBackend('half-float-webgl');
expect(() => tf.tidy(() => {
const a = tf.tensor2d([1, 2], [1, 2]);
const b = tf.tensor2d([1, 2], [1, 2]);
const c = tf.add(a, b);
c.dataSync();
return c.add(tf.tensor2d([2, 4], [1, 2]));
})).not.toThrowError();
});
});

describeWithFlags('time webgl', WEBGL_ENVS, () => {
it('upload + compute', async () => {
const a = tf.zeros([10, 10]);
const time = await tf.time(() => a.square()) as tf.webgl.WebGLTimingInfo;
expect(time.uploadWaitMs > 0);
expect(time.downloadWaitMs === 0);
expect(time.kernelMs > 0);
expect(time.wallMs >= time.kernelMs);
});

it('upload + compute + dataSync', async () => {
const a = tf.zeros([10, 10]);
const time =
await tf.time(() => a.square().dataSync()) as tf.webgl.WebGLTimingInfo;
expect(time.uploadWaitMs > 0);
expect(time.downloadWaitMs > 0);
expect(time.kernelMs > 0);
expect(time.wallMs >= time.kernelMs);
});

it('upload + compute + data', async () => {
const a = tf.zeros([10, 10]);
const time = await tf.time(async () => await a.square().data()) as
tf.webgl.WebGLTimingInfo;
expect(time.uploadWaitMs > 0);
expect(time.downloadWaitMs > 0);
expect(time.kernelMs > 0);
expect(time.wallMs >= time.kernelMs);
});

it('preupload (not included) + compute + data', async () => {
const a = tf.zeros([10, 10]);
// Pre-upload a on gpu.
a.square();
const time = await tf.time(() => a.sqrt()) as tf.webgl.WebGLTimingInfo;
// The tensor was already on gpu.
expect(time.uploadWaitMs === 0);
expect(time.downloadWaitMs === 0);
expect(time.kernelMs > 0);
expect(time.wallMs >= time.kernelMs);
});
});
45 changes: 45 additions & 0 deletions src/backends/webgl/backend_webgl_test_registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @license
* Copyright 2019 Google LLC. 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 {Constraints, registerTestEnv} from '../../jasmine_util';

export const WEBGL_ENVS: Constraints = {
backends: 'webgl'
};
export const PACKED_ENVS: Constraints = {
flags: {'WEBGL_PACK': true}
};

registerTestEnv({
name: 'webgl1',
backendName: 'webgl',
flags: {
'WEBGL_VERSION': 1,
'WEBGL_CPU_FORWARD': false,
'WEBGL_SIZE_UPLOAD_UNIFORM': 0
}
});

registerTestEnv({
name: 'webgl2',
backendName: 'webgl',
flags: {
'WEBGL_VERSION': 2,
'WEBGL_CPU_FORWARD': false,
'WEBGL_SIZE_UPLOAD_UNIFORM': 0
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
*/

import * as tf from '../../index';
import {describeWithFlags, WEBGL_ENVS} from '../../jasmine_util';
import {describeWithFlags} from '../../jasmine_util';
import {expectArraysClose} from '../../test_util';
import {WEBGL_ENVS} from './backend_webgl_test_registry';
import {GPGPUContext} from './gpgpu_context';
import * as gpgpu_util from './gpgpu_util';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
*/

import * as tf from '../../index';
import {describeWithFlags, PACKED_ENVS} from '../../jasmine_util';
import {describeWithFlags} from '../../jasmine_util';
import {expectArraysClose} from '../../test_util';
import {PACKED_ENVS} from './backend_webgl_test_registry';

describeWithFlags('expensive reshape', PACKED_ENVS, () => {
const cValues =
Expand Down
Loading