[JS/Web] WebGL Profiling Tool (#7724)

This commit is contained in:
Du Li 2021-05-18 06:31:00 -07:00 committed by GitHub
parent 43e2ee37f2
commit e4a985ff17
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 67 additions and 18 deletions

View file

@ -32,6 +32,9 @@ export class WebGLInferenceHandler implements InferenceHandler {
let artifact = this.session.programManager.getArtifact(op);
if (!artifact) {
const programInfo = op.createProgramInfo(this, inputs);
if (!programInfo.name) {
programInfo.name = op.constructor?.name;
}
artifact = this.session.programManager.build(programInfo);
this.session.programManager.setArtifact(op, artifact);
}

View file

@ -299,6 +299,7 @@ export class WebGLUnpackedConv extends Conv {
}
`;
return {
name: 'Im2Col',
inputLayouts: [inferenceHandler.createTextureLayoutFromShape(xshape)],
outputLayout,
samplers: ['X'],
@ -361,6 +362,7 @@ export class WebGLUnpackedConv extends Conv {
return value;
}`;
return {
name: 'dotProduct',
inputLayouts: inputs.length === 3 ? [im2colLayout, kLayout, bLayout!] : [im2colLayout, kLayout],
outputLayout,
shaderSource,

View file

@ -82,6 +82,7 @@ export class WebGLIm2ColPacked implements WebGLOperator {
}
`;
return {
name: 'WebGLIm2ColPacked',
inputLayouts: [inferenceHandler.getOrCreateTextureLayout(inputs[0], 4, true, xshape, true)],
outputLayout:
inferenceHandler.createTextureLayoutFromShape(im2colShape, 4, im2colShape, {isPacked: true, reverseWH: true}),

View file

@ -12,8 +12,8 @@ export class WebGLInstanceNormalization extends InstanceNormalization {
if (!this.artifacts) {
this.artifacts = [];
const programInfos = this.createProgramInfos(inferenceHandler, inputs);
programInfos.forEach((pi) => {
const artifact = inferenceHandler.session.programManager.build(pi);
programInfos.forEach((programInfo) => {
const artifact = inferenceHandler.session.programManager.build(programInfo);
this.artifacts.push(artifact);
});
}
@ -78,6 +78,7 @@ export class WebGLInstanceNormalization extends InstanceNormalization {
outputLayout: inferenceHandler.createTextureLayoutFromShape(outputShape, 4, outputUnpackedShape),
samplers: ['X'],
shaderSource,
name: 'MeanAndVariance',
};
}
@ -114,6 +115,7 @@ export class WebGLInstanceNormalization extends InstanceNormalization {
samplers: ['X', 'MeanAndVariance', 'Scale', 'B'],
variables: [{name: 'epsilon', type: 'float'}],
shaderSource,
name: 'ComputOutput',
};
}
createProgramInfos(inferenceHandler: WebGLInferenceHandler, inputs: Tensor[]): ProgramInfo[] {

View file

@ -49,6 +49,7 @@ export class WebGLMatMulPacked extends MatMul implements WebGLOperator {
return value;
}`;
return {
name: 'WebGLMatMulPacked',
inputLayouts: inputs.map((t, i) => handler.getOrCreateTextureLayout(t, 4, true, inputs[i].dims, true)),
outputLayout:
handler.createTextureLayoutFromShape(outputShape, 4, outputShape, {isPacked: true, reverseWH: true}),

View file

@ -57,6 +57,7 @@ export class WebGLPack implements WebGLOperator {
`;
return {
name: 'WebGLPack',
inputLayouts: [handler.getOrCreateTextureLayout(inputs[0], 1, false, [], true)],
outputLayout,
samplers: ['A'],

View file

@ -106,6 +106,7 @@ export class WebGLReshapePacked extends Reshape implements WebGLOperator {
`;
return {
name: 'WebGLReshapePacked',
inputLayouts: [inputLayout],
outputLayout: this.outputLayout,
samplers: ['A'],

View file

@ -75,6 +75,7 @@ export class WebGLSoftmax extends Softmax {
outputLayout: inferenceHandler.createTextureLayoutFromShape(outputShape),
samplers: ['A', 'Max', 'Norm'],
shaderSource,
name: 'SoftMax',
};
}
@ -131,6 +132,7 @@ export class WebGLSoftmax extends Softmax {
outputLayout: inferenceHandler.createTextureLayoutFromShape(outputShape),
samplers: ['A', 'Max'],
shaderSource,
name: 'ComputScale',
};
}
/**
@ -179,6 +181,7 @@ export class WebGLSoftmax extends Softmax {
outputLayout: inferenceHandler.createTextureLayoutFromShape(outputShape),
samplers: ['A'],
shaderSource,
name: 'ComputeMax',
};
}
createProgramInfos(inferenceHandler: WebGLInferenceHandler, inputs: Tensor[]): ProgramInfo[] {

View file

@ -43,6 +43,7 @@ export class WebGLSplit extends Split {
return _A(indices);
}`;
return {
name: 'WebGLSplit',
inputLayouts: [inferenceHandler.getOrCreateTextureLayout(input)],
outputLayout: inferenceHandler.createTextureLayoutFromShape(outputShape),
samplers: ['A'],

View file

@ -71,7 +71,8 @@ export class WebGLUint8Encode {
float value = ${glsl.texture2D}(X,TexCoords).r;
${glsl.output} = encodeAsUint8(value);
}`;
const programInfo = {inputLayouts: [input], outputLayout, samplers: ['X'], shaderSource, hasMain: true};
const programInfo =
{name: 'Uint8Encode', inputLayouts: [input], outputLayout, samplers: ['X'], shaderSource, hasMain: true};
const artifact = inferenceHandler.session.programManager.build(programInfo);
const encoder = inferenceHandler.session.backend.glContext.getEncoder('byte', 4);

View file

@ -48,6 +48,7 @@ export class WebGLUnpack implements WebGLOperator {
`;
return {
name: 'WebGLUnpack',
inputLayouts: [handler.getOrCreateTextureLayout(inputs[0], 4, true, inputs[0].dims, true)],
outputLayout,
samplers: ['A'],

View file

@ -37,10 +37,7 @@ export class ProgramManager {
this.repo.set(key, artifact);
}
run(buildArtifact: Artifact, runData: RunData): void {
const inputInfo = runData.inputTextureDatas.map((d, i) => `input${i}:[${d.shape}]`).join(', ');
const outputInfo = `output: [${runData.outputTextureData.shape}]`;
this.profiler.event('backend', `ProgramManager.run ${inputInfo} ; ${outputInfo}`, () => {
this.profiler.event('op', `ProgramManager.run ${buildArtifact.programInfo.name ?? 'unknown kernel'}`, () => {
const gl = this.glContext.gl;
const program = buildArtifact.program;
gl.useProgram(program);
@ -57,7 +54,7 @@ export class ProgramManager {
this.profiler.event('backend', 'GlContext.draw()', () => {
this.doDraw(buildArtifact, runData);
});
});
}, this.glContext);
}
dispose(): void {
if (this.vertexShader) {

View file

@ -82,6 +82,7 @@ export interface ProgramInfo {
expectPackedInputs?: boolean;
expectPackedOutputs?: boolean;
name?: string;
}
export interface VariableInfo {

View file

@ -520,6 +520,7 @@ export class WebGLContext {
if (this.version === 2) {
const gl2 = this.gl as WebGL2RenderingContext;
timeElapsed = gl2.getQueryParameter(query, gl2.QUERY_RESULT);
gl2.deleteQuery(query);
} else {
// TODO: add webgl 1 handling.
throw new Error('WebGL1 profiling currently not supported');

View file

@ -2,8 +2,6 @@
// Licensed under the MIT License.
import {SessionHandler} from './backend';
import {WebGLBackend} from './backends/backend-webgl';
import {WebGLContext} from './backends/webgl/webgl-context';
import {Graph} from './graph';
import {Logger, Profiler} from './instrument';
import {Operator} from './operators';
@ -53,12 +51,6 @@ export class ExecutionPlan {
}
async execute(sessionHandler: SessionHandler, modelInputs: Tensor[]): Promise<Tensor[]> {
const isWebGLBackend = sessionHandler.backend instanceof WebGLBackend;
let glCtx: WebGLContext|undefined;
if (isWebGLBackend) {
glCtx = (sessionHandler.backend as WebGLBackend).glContext;
}
return this.profiler.event('session', 'ExecutionPlan.execute', async () => {
// reset mediem result
this.reset();
@ -114,8 +106,7 @@ export class ExecutionPlan {
return result;
};
const outputList = isWebGLBackend ? await this.profiler.event('node', thisOp.node.name, execNodeFn, glCtx) :
await this.profiler.event('node', thisOp.node.name, execNodeFn);
const outputList = await this.profiler.event('node', thisOp.node.name, execNodeFn);
// check output
if (outputList.length !== thisOp.node.outputs.length) {

View file

@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
/* eslint-disable @typescript-eslint/restrict-plus-operands */
// parse-profiler
//
// this script is used to parse performance profiling result.
// usage:
// STEP.1 - profiling
// > npm test -- model test/test-data/{path-to-my-model} --backend={cpu/webgl/wasm} --profile > profile.raw.log
// STEP.2 - parse
// > node script/parse-profiler < profile.raw.log > profile.parsed.log
import * as readline from 'readline';
const lines = readline.createInterface({input: process.stdin, output: process.stdout, terminal: false});
// eslint-disable-next-line no-control-regex
const matcher = /Profiler\.([^[\s\x1b]+)(\x1b\[0m)? (\d.+Z)\|([\d.]+)ms on event '([^']+)' at (\d*\.*\d*)/;
const allEvents: any[] = [];
lines.on('line', input => {
const matches = matcher.exec(input);
if (matches) {
// console.log(matches);
const category = matches[1];
const logTimeStamp = new Date(matches[3]);
const ms = Number.parseFloat(matches[4]);
const event = matches[5];
const endTimeInNumber = matches[6];
allEvents.push({event, ms, logTimeStamp, category, endTimeInNumber});
}
});
lines.on('close', () => {
for (const i of allEvents) {
console.log(`${(i.category + ' ').substring(0, 12)} ${((i.ms) + ' ').substring(0, 12)} ${
(i.event + ' ').substring(0, 40)} ${i.endTimeInNumber}`);
}
});