Skip to content

Commit

Permalink
[iree-prof-tools] Run iree-vis for mlir asm files. (#245)
Browse files Browse the repository at this point in the history
1) Refactor extension.ts into 2 ts files.
2) Convert mlir files to temporary graph json file when
   filename ends with ".mlir" and iree-vis path is set
   which is empty by default.
3) Remove temp files when webview is closed.
4) iree-vis returns non-zero on error.
5) Reset port number when model-explorer terminal is gone.

Signed-off-by: Byungchul Kim <byungchul@google.com>
  • Loading branch information
protobird-git authored May 10, 2024
1 parent fb982cd commit fabe743
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 83 deletions.
14 changes: 9 additions & 5 deletions iree-prof-tools/iree-vis.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ ABSL_FLAG(int, min_num_ops_to_group, 3,
namespace iree_prof::graph {
namespace {

void ConvertToGraphJson(mlir::ModuleOp module) {
int ConvertToGraphJson(mlir::ModuleOp module) {
std::string output_json_file = absl::GetFlag(FLAGS_output_json_file);
std::ofstream fout(output_json_file.c_str());
if (!fout.is_open() || !fout.good()) {
LOG(ERROR) << "Can't open output file: " << output_json_file;
return;
return 1;
}

auto label = std::filesystem::path(absl::GetFlag(FLAGS_input_iree_file))
Expand All @@ -49,7 +49,7 @@ void ConvertToGraphJson(mlir::ModuleOp module) {
absl::GetFlag(FLAGS_min_num_ops_to_group));
if (!graph_collection.ok()) {
LOG(ERROR) << graph_collection.status();
return;
return 1;
}

{
Expand All @@ -59,6 +59,7 @@ void ConvertToGraphJson(mlir::ModuleOp module) {

LOG(INFO) << "Wrote " << fout.tellp() << " bytes to " << output_json_file;
fout.close();
return 0;
}

} // namespace
Expand Down Expand Up @@ -89,18 +90,21 @@ int main(int argc, char** argv) {

auto* invoke = ireeCompilerInvocationCreate(session);
LOG(INFO) << "Parsing " << input_iree_file << "...";
int ret_val = 0;
if (ireeCompilerInvocationParseSource(invoke, source)) {
LOG(INFO) << "Parsed " << input_iree_file << " successfully.";
auto op = ireeCompilerInvocationExportStealModule(invoke);
iree_prof::graph::ConvertToGraphJson(
ret_val = iree_prof::graph::ConvertToGraphJson(
llvm::cast<mlir::ModuleOp>(reinterpret_cast<mlir::Operation*>(op.ptr)));
// Re-import op into the session to destroy it properly.
ireeCompilerInvocationImportStealModule(invoke, op);
} else {
ret_val = 1;
}

ireeCompilerInvocationDestroy(invoke);
ireeCompilerSourceDestroy(source);
ireeCompilerSessionDestroy(session);
ireeCompilerGlobalShutdown();
return 0;
return ret_val;
}
30 changes: 23 additions & 7 deletions iree-prof-tools/model-explorer-extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,31 @@ Model explorer must be installed before this extension get started.
pip install model-explorer
```

This extension is started by executing `Model Explorer` command from the Command Palette
on a VS code active text editor of a graph json file. The extension loads the graph json
file to the model explorer web server. If the model explorer is not running, it starts one
with a random port number in a terminal.
This extension is started by executing `Explore Model` command from the Command
Palette on a VS code active text editor of a graph json file. The extension
loads the file of the current focused editor to the model explorer web server.
If the model explorer is not running, it starts one with a random port number
in a terminal.

Currently, it supports Tensorflow saved_model.pb files, TFLite files, StableHLO
MLIR files, IREE MLIR files, and graph json files. For IREE MLIR files, it
requires `iree-vis` to convert them into graph json files.

```
cd <iree-experimental-root>/iree-prof-tools/
cmake -G Ninja -B ../../iree-prof-build/ .
cmake --build ../../iree-prof-build/ --target iree-vis
```

Once `iree-vis` is built, set its path in `ModelExplorer: Iree Vis Path`
setting.

![Model Explorer Settings](model-explorer-settings.png)

## How to make changes into it

To make changes of this extension, VS Code, Node.js and typescript compiler are necessary.
Please follow installation guides of
To make changes of this extension, VS Code, Node.js and typescript compiler are
necessary. Please follow installation guides of
[VS Code](https://code.visualstudio.com/docs/setup/setup-overview) and
[Node.js](https://nodejs.org/en/download).

Expand All @@ -32,4 +48,4 @@ code .
```

Once the workspace is open, follow the
[steps to build webview extensions](https://code.visualstudio.com/api/extension-guides/webview).
[steps to build webview extensions](https://code.visualstudio.com/api/extension-guides/webview).
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions iree-prof-tools/model-explorer-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
"type": "string",
"description": "Executable path to start model explorer internally when externalModelExplorerUrl is empty.",
"default": "model-explorer"
},
"modelExplorer.ireeVisPath": {
"type": "string",
"description": "Executable path to convert IREE intermediate MLIR asm files to graph JSON files.",
"default": ""
}
}
}
Expand Down
98 changes: 27 additions & 71 deletions iree-prof-tools/model-explorer-extension/src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,86 +1,42 @@
import * as vscode from 'vscode';
import {convertMlirToJsonIfNecessary} from './mlirUtil';
import {WebviewPanelForModelExplorer} from './modelExplorer';

export function activate(context: vscode.ExtensionContext) {
// A random port number of model explorer web server.
const port = 30080 + Math.floor(Math.random() * 9900);

// Internal model explorer web server shared by multiple webview panels.
var internalModelExplorerTerminal: vscode.Terminal|null = null

async function startModelExplorer() {
var modelFile = vscode.window.activeTextEditor?.document.fileName;
if (!modelFile) {
const openFiles = await vscode.window.showOpenDialog({
canSelectFiles: true,
canSelectMany: false,
title: 'No active text editor or too large file. Open a model file'
});
modelFile = openFiles?.length == 1 ? openFiles[0].fsPath : undefined;
context.subscriptions.push(
vscode.commands.registerCommand('modelExplorer.show', async () => {
const modelFile = await getModelFileName();
if (!modelFile) {
vscode.window.showInformationMessage('Invalid model file path.');
return;
}
}

const config = vscode.workspace.getConfiguration('modelExplorer');
const externalUrl = config.get<string>('externalModelExplorerUrl') ?? '';
const connectToExternalServer = externalUrl.length > 0;
const modelExplorerUrl = connectToExternalServer ? externalUrl : `http://localhost:${port}`;

const panel = vscode.window.createWebviewPanel(
'modelExplorer',
'Model Explorer',
vscode.window.activeTextEditor?.viewColumn ?? vscode.ViewColumn.One,
{ // Webview options.
enableScripts: true
}
);
if (connectToExternalServer || internalModelExplorerTerminal != null) {
panel.webview.html = getWebviewContent(modelExplorerUrl, modelFile);
return;
}
const panel = new WebviewPanelForModelExplorer(context);
context.subscriptions.push(panel);

// No model explorer is available. Starts one.
vscode.window.showInformationMessage('Starting a model explorer web server...');
internalModelExplorerTerminal =
vscode.window.createTerminal(
'modelExplorerWebServer',
config.get<string>('internalModelExplorerPath') ?? 'model-explorer',
['--no_open_in_browser', `--port=${port}`]
);
vscode.window.onDidCloseTerminal(terminal => {
if (terminal == internalModelExplorerTerminal) {
vscode.window.showInformationMessage('Model explorer web server is closed.');
internalModelExplorerTerminal = null;
const modelFileToLoad = await convertMlirToJsonIfNecessary(modelFile);
if (modelFileToLoad != modelFile) {
panel.addDisposeCallback(() => {
vscode.workspace.fs.delete(vscode.Uri.file(modelFileToLoad));
});
}
});
context.subscriptions.push(internalModelExplorerTerminal);

// Delay webview rendering to wait for model explorer ready.
const timeout = setTimeout(() => {
panel.webview.html = getWebviewContent(modelExplorerUrl, modelFile!!);
}, 2000); // 2000 is arbitrary. Need a more reliable way.
panel.startModelExplorer(modelFileToLoad);
})
);
}

panel.onDidDispose(() => { clearTimeout(timeout); }, null, context.subscriptions);
async function getModelFileName(): Promise<string | undefined> {
const fileName = vscode.window.activeTextEditor?.document.fileName;
if (fileName) {
return fileName;
}

context.subscriptions.push(
vscode.commands.registerCommand('modelExplorer.show', startModelExplorer));
}
const openFiles = await vscode.window.showOpenDialog({
canSelectFiles: true,
canSelectMany: false,
title: 'No active text editor or too large file. Open a model file'
});

function getWebviewContent(modelExplorerUrl: string, modelFile: string) {
vscode.window.showInformationMessage(`Loading a model file, ${modelFile}...`);
const encodedData = encodeURIComponent(`{"models":[{"url":"${modelFile}"}]}`);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Model Explorer</title>
</head>
<body>
<iframe src="${modelExplorerUrl}/?data=${encodedData}&renderer=webgl&show_open_in_new_tab=0"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none;">
</iframe>
</body>
</html>`;
}
return openFiles?.length == 1 ? openFiles[0].fsPath : undefined;
}
42 changes: 42 additions & 0 deletions iree-prof-tools/model-explorer-extension/src/mlirUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as vscode from 'vscode';

export async function convertMlirToJsonIfNecessary(
modelFile: string
): Promise<string> {
const config = vscode.workspace.getConfiguration('modelExplorer');
const ireeVisPath = config.get<string>('ireeVisPath') ?? '';
if (!modelFile.endsWith('.mlir') || ireeVisPath.length == 0) {
return modelFile;
}

vscode.window.showInformationMessage(
`Generating graph for a model file, ${modelFile}...`
);
const graphJsonFile = modelFile + '.graph.json';
const ireeVisTerminal = vscode.window.createTerminal({
'name': 'ireeVisRunner',
'shellPath': ireeVisPath,
'shellArgs': [
`--input_iree_file=${modelFile}`,
`--output_json_file=${graphJsonFile}`
],
'hideFromUser': true
});

return new Promise<string>((resolve, reject) => {
const token = vscode.window.onDidCloseTerminal(terminal => {
if (terminal == ireeVisTerminal) {
token.dispose();
if (terminal.exitStatus?.code == 0) {
vscode.window.showInformationMessage(
`Succeeded to generate a graph file, ${graphJsonFile}.`
);
resolve(graphJsonFile);
} else {
vscode.window.showErrorMessage('Failed to generate a graph file.');
resolve(modelFile);
}
}
});
});
}
106 changes: 106 additions & 0 deletions iree-prof-tools/model-explorer-extension/src/modelExplorer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import * as vscode from 'vscode';

// Internal model explorer web server shared by multiple webview panels.
var internalModelExplorerTerminal: vscode.Terminal | undefined = undefined;

// A random port number of internal model explorer web server.
var internalModelExplorerPort: number | undefined = undefined;

export class WebviewPanelForModelExplorer {
context: vscode.ExtensionContext;
panel: vscode.WebviewPanel;
disposeCallbacks: (() => void)[];

constructor(context: vscode.ExtensionContext) {
this.context = context;
this.panel = vscode.window.createWebviewPanel(
'modelExplorer',
'Model Explorer',
vscode.window.activeTextEditor?.viewColumn ?? vscode.ViewColumn.One,
{ // Webview options.
enableScripts: true,
retainContextWhenHidden: true
}
);

this.disposeCallbacks = [];

this.panel.onDidDispose(
() => { for (let f of this.disposeCallbacks) { f(); }},
null,
context.subscriptions);
}

dispose() {
this.panel.dispose();
}

addDisposeCallback(f: () => void) {
this.disposeCallbacks.push(f);
}

startModelExplorer(modelFile: string) {
const config = vscode.workspace.getConfiguration('modelExplorer');
const externalUrl = config.get<string>('externalModelExplorerUrl') ?? '';
if (externalUrl.length > 0) {
this.panel.webview.html = getWebviewContent(externalUrl, modelFile);
return;
}

internalModelExplorerPort = internalModelExplorerPort ?? getRandomPort();
const modelExplorerUrl = `http://localhost:${internalModelExplorerPort}`;
if (internalModelExplorerTerminal != null) {
this.panel.webview.html = getWebviewContent(modelExplorerUrl, modelFile);
return;
}

// No model explorer is available. Starts one.
vscode.window.showInformationMessage(
'Starting a model explorer web server...'
);
internalModelExplorerTerminal = vscode.window.createTerminal(
'modelExplorerWebServer',
config.get<string>('internalModelExplorerPath') ?? 'model-explorer',
['--no_open_in_browser', `--port=${internalModelExplorerPort}`]
);
const token = vscode.window.onDidCloseTerminal(terminal => {
if (terminal == internalModelExplorerTerminal) {
token.dispose();
vscode.window.showInformationMessage(
'Model explorer web server is closed.'
);
internalModelExplorerTerminal = undefined;
internalModelExplorerPort = undefined;
}
});
this.context.subscriptions.push(internalModelExplorerTerminal);

// Delay webview rendering to wait for model explorer ready.
const timeout = setTimeout(() => {
this.panel.webview.html = getWebviewContent(modelExplorerUrl, modelFile);
}, 2000); // 2000 is arbitrary. Need a more reliable way.

this.addDisposeCallback(() => { clearTimeout(timeout); });
}
}

function getRandomPort(): number {
return 30080 + Math.floor(Math.random() * 9900);
}

function getWebviewContent(modelExplorerUrl: string, modelFile: string) {
vscode.window.showInformationMessage(`Loading a model file, ${modelFile}...`);
const encodedData = encodeURIComponent(`{"models":[{"url":"${modelFile}"}]}`);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Model Explorer</title>
</head>
<body>
<iframe src="${modelExplorerUrl}/?data=${encodedData}&renderer=webgl&show_open_in_new_tab=0"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none;">
</iframe>
</body>
</html>`;
}

0 comments on commit fabe743

Please sign in to comment.