Skip to content

Commit

Permalink
[js/web] fix test runner with optional input/output (microsoft#20399)
Browse files Browse the repository at this point in the history
### Description
fix test runner with optional input/output.

This change fixes the OP test runner (.jsonc format test) with optional
input(s) and/or output(s).

this fix reveals a problem of dealing with optional outputs:

> Take SkipSimplifiedLayerNorm as example: 
>
> if in the ONNX model, the node's outputs are: [ 'output_0', '' ]
instead of [ 'output_0' ], the current implementation will fail. The
difference is, in the first case, context.outputCount == 2, and then the
typescript implementation will try to create a tensor for output[1]. It
will eventually call to C++ function (OpKernelContext::Output), and the
output.DataRaw() will be nullptr. WebGPU backend will fail because it
cannot deal with a TensorView with data == 0.
>

This problem may need to be fixed or workaround in separated PR. This PR
does not fix this problem. Failed test cases are modified to work -
please note this PR does not break those test cases as they never work.
  • Loading branch information
fs-eire authored Apr 22, 2024
1 parent d0e33d2 commit 4385602
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 45 deletions.
8 changes: 4 additions & 4 deletions js/web/test/data/ops/simplified-layer-norm.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@
],
"dims": [1, 2, 8],
"type": "float32"
},
{
"data": null,
"type": "float32"
}
// {
// "data": null,
// "type": "float32"
// }
]
}
]
Expand Down
43 changes: 29 additions & 14 deletions js/web/test/data/ops/skip-layer-norm.jsonc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[
{
"name": "SkipLayerNormalization",
"name": "SkipLayerNormalization - no output[3]",
"operator": "SkipLayerNormalization",
"opset": { "domain": "com.microsoft", "version": 1 },
"attributes": [
Expand Down Expand Up @@ -49,21 +49,36 @@
],
"dims": [1, 2, 4],
"type": "float32"
},
{
"data": null,
"type": "float32"
},
{
"data": null,
"type": "float32"
},
{
"data": null,
"type": "float32"
}
// {
// "data": null,
// "type": "float32"
// },
// {
// "data": null,
// "type": "float32"
// },
// {
// "data": null,
// "type": "float32"
// }
]
},
}
]
},
{
"name": "SkipLayerNormalization - has output[3]",
"operator": "SkipLayerNormalization",
"opset": { "domain": "com.microsoft", "version": 1 },
"attributes": [
{
"name": "epsilon",
"data": 1e-5,
"type": "float"
}
],
"inputShapeDefinitions": [[1, 2, 4], [1, 2, 4], [4], [4], [4]],
"cases": [
{
"name": "default",
"inputs": [
Expand Down
8 changes: 4 additions & 4 deletions js/web/test/data/ops/skip-simplified-layer-norm.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@
],
"dims": [1, 2, 8],
"type": "float32"
},
{
"data": null,
"type": "float32"
}
// {
// "data": null,
// "type": "float32"
// }
]
}
]
Expand Down
74 changes: 51 additions & 23 deletions js/web/test/test-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -763,15 +763,34 @@ export class ProtoOpTestContext {
throw new Error(
`Test cases for test: ${test.name} [${test.operator}] must have the same number of inputs and outputs`);
}
const inputsOmitted = test.cases[0].inputs.map(input => !input.data);
const outputsOmitted = test.cases[0].outputs.map(output => !output.data);
for (let caseIndex = 1; caseIndex < test.cases.length; caseIndex++) {
const testCase = test.cases[caseIndex];
for (let i = 0; i < inputCount; i++) {
if (inputsOmitted[i] !== !testCase.inputs![i].data) {
throw new Error(`Test cases for test: ${test.name} [${
test.operator}] must have consistent inputs data availability. Data of input[${i}] in testCase #0 and #${
caseIndex} should be both available or both omitted.`);
}
}
for (let i = 0; i < outputCount; i++) {
if (outputsOmitted[i] !== !testCase.outputs![i].data) {
throw new Error(`Test cases for test: ${test.name} [${
test.operator}] must have consistent outputs data availability. Data of output[${
i}] in testCase #0 and #${caseIndex} should be both available or both omitted.`);
}
}
}

const model = onnx.ModelProto.create();
model.irVersion = onnx.Version.IR_VERSION;
model.opsetImport.push(opsetImport);
model.graph = onnx.GraphProto.create();

model.graph.node = [onnx.NodeProto.create({
input: test.cases[0].inputs!.map((_, i) => `input_${i}`),
output: test.cases[0].outputs!.map((_, i) => `output_${i}`),
input: test.cases[0].inputs!.map((t, i) => t.data ? `input_${i}` : ''),
output: test.cases[0].outputs!.map((t, i) => t.data ? `output_${i}` : ''),
opType: operator,
domain: test.opset?.domain,
name: operator,
Expand Down Expand Up @@ -830,27 +849,36 @@ export class ProtoOpTestContext {
normalizedInputShapeDefinitions = test.inputShapeDefinitions;
}

model.graph.input = test.cases[0].inputs!.map((input, i) => {
const shapeDefinition = normalizedInputShapeDefinitions[i];
const shape = shapeDefinition ? onnx.TensorShapeProto.create({
dim: shapeDefinition.map(
dim => onnx.TensorShapeProto.Dimension.create(typeof dim === 'string' ? {dimParam: dim} : {dimValue: dim}))
}) :
undefined;
return onnx.ValueInfoProto.create({
name: `input_${i}`,
type: onnx.TypeProto.create({
tensorType: onnx.TypeProto.Tensor.create({elemType: tensorDataTypeStringToEnum(input.type), shape}),
}),
});
});

model.graph.output = test.cases[0].outputs!.map((output, i) => onnx.ValueInfoProto.create({
name: `output_${i}`,
type: onnx.TypeProto.create({
tensorType: onnx.TypeProto.Tensor.create({elemType: tensorDataTypeStringToEnum(output.type)}),
}),
}));
model.graph.input =
test.cases[0]
.inputs!
.map((input, i) => {
const shapeDefinition = normalizedInputShapeDefinitions[i];
const shape = shapeDefinition ? onnx.TensorShapeProto.create({
dim: shapeDefinition.map(
dim => onnx.TensorShapeProto.Dimension.create(
typeof dim === 'string' ? {dimParam: dim} : {dimValue: dim}))
}) :
undefined;
return onnx.ValueInfoProto.create({
name: `input_${i}`,
type: onnx.TypeProto.create({
tensorType: onnx.TypeProto.Tensor.create({elemType: tensorDataTypeStringToEnum(input.type), shape}),
}),
});
})
.filter((_, i) => test.cases[0].inputs![i].data);

model.graph.output =
test.cases[0]
.outputs!
.map((output, i) => onnx.ValueInfoProto.create({
name: `output_${i}`,
type: onnx.TypeProto.create({
tensorType: onnx.TypeProto.Tensor.create({elemType: tensorDataTypeStringToEnum(output.type)}),
}),
}))
.filter((_, i) => test.cases[0].outputs![i].data);

model.graph.name = test.name;

Expand Down

0 comments on commit 4385602

Please sign in to comment.