onnxruntime/js/web/test/unittests/backends/webgl/test-pack-unpack.ts
Yulong Wang af21a04977
[js] upgrade async@3.2.3 /js/ (#11421)
* [js] upgrade async@3.2.3 /js/

* format code
2022-05-03 23:41:36 -07:00

376 lines
16 KiB
TypeScript

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import {expect} from 'chai';
import {Backend, InferenceHandler, resolveBackend, SessionHandler} from '../../../../lib/onnxjs/backend';
import {WebGLInferenceHandler} from '../../../../lib/onnxjs/backends/webgl/inference-handler';
import {createPackProgramInfoLoader} from '../../../../lib/onnxjs/backends/webgl/ops/pack';
import {createUnpackProgramInfoLoader} from '../../../../lib/onnxjs/backends/webgl/ops/unpack';
import {createTextureLayoutFromShape} from '../../../../lib/onnxjs/backends/webgl/texture-layout';
import {Profiler} from '../../../../lib/onnxjs/instrument';
import {Tensor} from '../../../../lib/onnxjs/tensor';
import {ShapeUtil} from '../../../../lib/onnxjs/util';
import {createArrayFromTexture, createAscendingArray, createTextureFromArray, generateExpected, getExpectedElementCount} from './test-utils';
interface TestData {
elementCount: number;
inputShape: number[];
outputShape: number[];
inputTextureShape: number[];
outputTextureShape: number[];
rawData?: Float32Array;
useGeneratedOutput?: boolean;
}
function getTestData(isPacked = true): TestData[] {
if (isPacked) {
return [
// test scalar
{elementCount: 1, inputShape: [], outputShape: [], inputTextureShape: [], outputTextureShape: [1, 1]},
// test 1D tensor
{elementCount: 1, inputShape: [1], outputShape: [], inputTextureShape: [], outputTextureShape: [1, 1]},
{elementCount: 16, inputShape: [16], outputShape: [], inputTextureShape: [], outputTextureShape: [1, 8]},
{elementCount: 9, inputShape: [9], outputShape: [], inputTextureShape: [], outputTextureShape: [1, 5]},
// test 2D tensor
{elementCount: 1, inputShape: [1, 1], outputShape: [], inputTextureShape: [], outputTextureShape: [1, 1]},
{elementCount: 16, inputShape: [4, 4], outputShape: [], inputTextureShape: [], outputTextureShape: [2, 2]},
{elementCount: 16, inputShape: [2, 8], outputShape: [], inputTextureShape: [], outputTextureShape: [1, 4]},
{elementCount: 16, inputShape: [8, 2], outputShape: [], inputTextureShape: [], outputTextureShape: [4, 1]},
{elementCount: 15, inputShape: [3, 5], outputShape: [], inputTextureShape: [], outputTextureShape: [2, 3]},
{elementCount: 18, inputShape: [3, 6], outputShape: [], inputTextureShape: [], outputTextureShape: [2, 3]},
{elementCount: 10, inputShape: [2, 5], outputShape: [], inputTextureShape: [], outputTextureShape: [1, 3]},
{elementCount: 6, inputShape: [1, 6], outputShape: [], inputTextureShape: [], outputTextureShape: [1, 3]},
{elementCount: 6, inputShape: [6, 1], outputShape: [], inputTextureShape: [], outputTextureShape: [3, 1]},
{elementCount: 5, inputShape: [5, 1], outputShape: [], inputTextureShape: [], outputTextureShape: [3, 1]},
{elementCount: 5, inputShape: [1, 5], outputShape: [], inputTextureShape: [], outputTextureShape: [1, 3]},
// test 3D tensor
{elementCount: 1, inputShape: [1, 1, 1], outputShape: [], inputTextureShape: [], outputTextureShape: [1, 1]},
{elementCount: 16, inputShape: [2, 2, 4], outputShape: [], inputTextureShape: [], outputTextureShape: [2, 2]},
{elementCount: 24, inputShape: [2, 3, 4], outputShape: [], inputTextureShape: [], outputTextureShape: [4, 2]},
{elementCount: 30, inputShape: [5, 3, 2], outputShape: [], inputTextureShape: [], outputTextureShape: [10, 1]},
{elementCount: 9, inputShape: [1, 3, 3], outputShape: [], inputTextureShape: [], outputTextureShape: [2, 2]},
{elementCount: 8, inputShape: [1, 4, 2], outputShape: [], inputTextureShape: [], outputTextureShape: [2, 1]},
{elementCount: 8, inputShape: [4, 2, 1], outputShape: [], inputTextureShape: [], outputTextureShape: [4, 1]},
{elementCount: 8, inputShape: [4, 1, 2], outputShape: [], inputTextureShape: [], outputTextureShape: [4, 1]},
// test 4D tensor
{elementCount: 1, inputShape: [1, 1, 1, 1], outputShape: [], inputTextureShape: [], outputTextureShape: [1, 1]},
{elementCount: 15, inputShape: [1, 1, 3, 5], outputShape: [], inputTextureShape: [], outputTextureShape: [2, 3]},
{elementCount: 16, inputShape: [1, 2, 2, 4], outputShape: [], inputTextureShape: [], outputTextureShape: [2, 2]},
{elementCount: 32, inputShape: [2, 2, 2, 4], outputShape: [], inputTextureShape: [], outputTextureShape: [4, 2]},
{elementCount: 36, inputShape: [2, 2, 3, 3], outputShape: [], inputTextureShape: [], outputTextureShape: [8, 2]},
{elementCount: 80, inputShape: [2, 5, 2, 4], outputShape: [], inputTextureShape: [], outputTextureShape: [10, 2]},
{elementCount: 12, inputShape: [2, 1, 3, 2], outputShape: [], inputTextureShape: [], outputTextureShape: [4, 1]},
{elementCount: 8, inputShape: [4, 1, 1, 2], outputShape: [], inputTextureShape: [], outputTextureShape: [4, 1]},
{
elementCount: 3840,
inputShape: [1, 1, 48, 80],
outputShape: [],
inputTextureShape: [],
outputTextureShape: [24, 40]
},
// test 6D tensor
{
elementCount: 32,
inputShape: [1, 1, 2, 2, 2, 4],
outputShape: [],
inputTextureShape: [],
outputTextureShape: [4, 2]
},
{
elementCount: 3840,
inputShape: [1, 1, 2, 24, 2, 40],
outputShape: [],
inputTextureShape: [],
outputTextureShape: [48, 20]
},
];
} else {
return [
// test 1D tensor
{
elementCount: 8,
inputShape: [8],
outputShape: [8],
inputTextureShape: [4, 1],
outputTextureShape: [1, 8],
rawData: new Float32Array([1, 2, 0, 0, 3, 4, 0, 0, 5, 6, 0, 0, 7, 8, 0, 0]),
},
{
elementCount: 18,
inputShape: [3, 6],
outputShape: [3, 6],
inputTextureShape: [3, 2],
outputTextureShape: [3, 6],
rawData: new Float32Array([1, 2, 7, 8, 3, 4, 9, 10, 5, 6, 11, 12, 13, 14, 0, 0, 15, 16, 0, 0, 17, 18, 0, 0]),
},
// // test 2D tensor
{
elementCount: 16,
inputShape: [4, 4],
outputShape: [4, 4],
inputTextureShape: [2, 2],
outputTextureShape: [4, 4],
useGeneratedOutput: true,
},
{
elementCount: 8,
inputShape: [2, 4],
outputShape: [2, 4],
inputTextureShape: [2, 1],
outputTextureShape: [2, 4],
useGeneratedOutput: true,
},
{
elementCount: 6,
inputShape: [2, 3],
outputShape: [2, 3],
inputTextureShape: [2, 1],
outputTextureShape: [2, 3],
rawData: new Float32Array([1, 2, 4, 5, 3, 0, 6, 0]),
},
// // test 3d tensor
{
elementCount: 16,
inputShape: [2, 2, 4],
outputShape: [2, 2, 4],
inputTextureShape: [2, 2],
outputTextureShape: [4, 4],
useGeneratedOutput: true,
},
{
elementCount: 24,
inputShape: [2, 3, 4],
outputShape: [2, 3, 4],
inputTextureShape: [2, 4],
outputTextureShape: [6, 4],
rawData: new Float32Array([
1, 2, 5, 6, 3, 4, 7, 8, 9, 10, 0, 0, 11, 12, 0, 0,
13, 14, 17, 18, 15, 16, 19, 20, 21, 22, 0, 0, 23, 24, 0, 0
])
},
// test 4d tensor
{
elementCount: 32,
inputShape: [2, 2, 2, 4],
outputShape: [2, 2, 2, 4],
inputTextureShape: [2, 4],
outputTextureShape: [8, 4],
useGeneratedOutput: true,
},
{
elementCount: 64,
inputShape: [2, 2, 4, 4],
outputShape: [2, 2, 4, 4],
inputTextureShape: [2, 8],
outputTextureShape: [16, 4],
useGeneratedOutput: true,
},
// test 6d tensor
{
elementCount: 32,
inputShape: [1, 1, 2, 2, 2, 4],
outputShape: [1, 1, 2, 2, 2, 4],
inputTextureShape: [2, 4],
outputTextureShape: [8, 4],
useGeneratedOutput: true,
},
{
elementCount: 64,
inputShape: [1, 2, 1, 2, 4, 4],
outputShape: [1, 2, 1, 2, 4, 4],
inputTextureShape: [2, 8],
outputTextureShape: [16, 4],
useGeneratedOutput: true,
},
];
}
}
let backend: Backend|undefined;
let sessionhandler: SessionHandler|undefined;
let inferenceHandler: InferenceHandler|undefined;
describe('#UnitTest# - pack - Tensor pack', () => {
before('Initialize Context', async () => {
const profiler = Profiler.create();
backend = await resolveBackend('webgl');
sessionhandler = backend!.createSessionHandler({profiler});
inferenceHandler = sessionhandler.createInferenceHandler();
});
const testDataSet = getTestData();
// iterate through different input texture layout.
// 'hw-reverted' is the new texture layout all packed kernels use
// 'hw-unreverted' is the old texture layout existing unpacked kernels use
// before we unify those two texture layout, pack kernel should be able to handle
// both texture layout correctly
const textureLayout = ['hw-reverted', 'hw-unreverted'];
for (let w = 0; w < textureLayout.length; ++w) {
for (let k = 0; k < testDataSet.length; ++k) {
const testData = testDataSet[k];
describe('Test pack', () => {});
it(`Test pack kernal ${textureLayout[w]} ${JSON.stringify(testData)}`, () => {
const webglInferenceHandler = inferenceHandler as WebGLInferenceHandler;
const elementCount = testData.elementCount;
const inputData = createAscendingArray(elementCount);
const inputTensorShape = testData.inputShape;
const outputTextureShape = testData.outputTextureShape;
const inputTensor = new Tensor(inputTensorShape, 'float32', undefined, undefined, inputData);
// test old texture layout with width and height not inverted
if (w === 1) {
console.log('Testing unreverted HW input texture');
// use inputTensorShape to create a texture layout that is unpacked(channel === 1)&& hw unreverted.
const inputUnpackedLayout =
createTextureLayoutFromShape(webglInferenceHandler.session.layoutStrategy, inputTensorShape);
// create texture data from the layout. The texture data is cached inside inference handler such that
// when pack kernel is invoked, it will read this texture data from cache instead of creating it from
// scratch
webglInferenceHandler.createTextureDataFromLayoutBindTensor(
inputUnpackedLayout, inputTensor.type, inputTensor.numberData, inputTensor);
}
// compile shader code
const programInfo = createPackProgramInfoLoader(inferenceHandler! as WebGLInferenceHandler, inputTensor);
// run kernal and get output
const resultTextureData = webglInferenceHandler.executeProgram(programInfo, [inputTensor]);
const gl = webglInferenceHandler.session.textureManager.glContext.gl;
const resultDataBuffer =
createArrayFromTexture(gl, resultTextureData.texture, outputTextureShape[1], outputTextureShape[0]);
expect(resultDataBuffer).to.not.equal(null);
const outputElementCount = getExpectedElementCount(testData.inputShape);
expect(resultDataBuffer).to.have.lengthOf(outputElementCount);
const expectedOutput = generateExpected(inputData, testData.inputShape);
expect(resultDataBuffer).to.deep.equal(expectedOutput);
});
}
}
});
describe('#UnitTest# - unpack - Tensor unpack', () => {
before('Initialize Context', async () => {
const profiler = Profiler.create();
backend = await resolveBackend('webgl');
sessionhandler = backend!.createSessionHandler({profiler});
inferenceHandler = sessionhandler.createInferenceHandler();
});
const testDataSet = getTestData(false);
for (let k = 0; k < testDataSet.length; ++k) {
const testData = testDataSet[k];
describe(`Test unpack ${JSON.stringify(testData)}`, () => {});
it(`Test unpack kernal ${testData.inputShape}`, () => {
const webglInferenceHandler = inferenceHandler as WebGLInferenceHandler;
const elementCount = testData.elementCount;
const inputTensorShape = testData.inputShape;
const inputTextureShape = testData.inputTextureShape;
const outputTensorShape = testData.outputShape;
// create input data and tensor. The input data will be used to verify if the output tensor contains the
// same value but possibly different order depending on our packing algorithm.
const inputData = createAscendingArray(elementCount);
const inputTensor = new Tensor(inputTensorShape, 'float32', undefined, undefined, inputData);
// manually creat packed texture from inputTensor, and insert in cache
const gl = webglInferenceHandler.session.textureManager.glContext.gl;
webglInferenceHandler.session.textureManager.glContext.checkError();
const webglTexture = createTextureFromArray(
webglInferenceHandler.session.textureManager.glContext, testData.rawData ? testData.rawData : inputData,
gl.RGBA, inputTextureShape[0], inputTextureShape[1]);
webglInferenceHandler.session.textureManager.glContext.checkError();
const packedShape = inputTextureShape;
const textureData = {
width: inputTextureShape[0],
height: inputTextureShape[1],
channels: 4 as const,
isPacked: true,
shape: packedShape,
strides: ShapeUtil.computeStrides(packedShape),
unpackedShape: outputTensorShape,
tensor: inputTensor,
texture: webglTexture!
};
webglInferenceHandler.setTextureData(inputTensor.dataId, textureData, true);
// compile shader code
const programInfo = createUnpackProgramInfoLoader(inferenceHandler! as WebGLInferenceHandler, inputTensor);
// run kernal and get output
const resultTextureData = webglInferenceHandler.executeProgram(programInfo, [inputTensor]);
const result = resultTextureData.tensor.data;
const resultDataBuffer = createArrayFromTexture(gl, webglTexture!, inputTextureShape[0], inputTextureShape[1]);
webglInferenceHandler.session.textureManager.glContext.checkError();
// verify result.
const expectedOutput = testData.useGeneratedOutput ? generateExpected(inputData, testData.inputShape) : inputData;
expect(result).to.not.equal(null);
expect(result).to.have.lengthOf(elementCount);
expect(resultDataBuffer).to.deep.equal(testData.rawData ? testData.rawData : inputData);
const outputElementCount = getExpectedElementCount(testData.inputShape);
expect(resultDataBuffer).to.have.lengthOf(outputElementCount);
expect(result).to.deep.equal(expectedOutput);
});
}
});
describe('#UnitTest# - pack-unpack round trip', () => {
before('Initialize Context', async () => {
const profiler = Profiler.create();
backend = await resolveBackend('webgl');
sessionhandler = backend!.createSessionHandler({profiler});
inferenceHandler = sessionhandler.createInferenceHandler();
});
const testDataSet = getTestData();
for (let k = 0; k < testDataSet.length; ++k) {
const testData = testDataSet[k];
describe(`Test pack-unpack ${JSON.stringify(testData)}`, () => {});
it(`Test pack-unpack round trip ${JSON.stringify(testData)}`, () => {
const webglInferenceHandler = inferenceHandler as WebGLInferenceHandler;
const elementCount = testData.elementCount;
const inputData = createAscendingArray(elementCount);
const inputTensorShape = testData.inputShape;
const inputTensor = new Tensor(inputTensorShape, 'float32', undefined, undefined, inputData);
// compile pack shader code
const packProgramInfo = createPackProgramInfoLoader(inferenceHandler! as WebGLInferenceHandler, inputTensor);
const packResultData = webglInferenceHandler.executeProgram(packProgramInfo, [inputTensor]);
// create unpack kernel
// compile unpack shader code
const unpackProgramInfo =
createPackProgramInfoLoader(inferenceHandler! as WebGLInferenceHandler, packResultData.tensor);
// run unpack kernal and get output
const unpackResultData = webglInferenceHandler.executeProgram(unpackProgramInfo, [inputTensor]);
const resultData = unpackResultData.tensor.data;
expect(resultData).to.not.equal(null);
expect(resultData).to.have.lengthOf(testData.elementCount);
expect(unpackResultData.tensor.data).to.deep.equal(inputTensor.data);
});
}
});