mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-06-09 00:30:53 +00:00
376 lines
16 KiB
TypeScript
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);
|
|
});
|
|
}
|
|
});
|