Skip to content

Commit 542ac06

Browse files
sam-mccallchihuahua
authored andcommitted
Add a visualization for op profiling
* Skeleton for op_profile: serve json, load it from client, allow the tool to be selected, show top-level children * Add function to compute bad->good colors. Color top-level children as demo. * Add overview text. * Basic implementation of tf-op-table. The UI is very rough, but the interface is implemented to enable integration tests. * Keep the selected entry active if there is one. * Stylized the table. * Removed debug logs * Add basic details card for selected item. Just has title and expression for now. * Merged with sam-mccall/op_profile. * Highlight all children for selected group * Add flops utilization bar * Add comments to each custom element. * colors -> utils, moved other shared functionality there. * Reinstate hover behavior - it seems inconsistent to offer it only until a click * Tweak margin * Add subtitle to cards. * Add table header and op provenance. (#5) * Add table header and op provenance. * Unconditionally create provenance spans, which can be empty. * Fix level counter, and sort function with fused ops. * Peformance fixes and cleanups for tf-profile-dashboard. No longer bind selection/active everywhere, which kills performance for large profiles. Access these through the root instead. Move the header from the row element to the table, and improve styles. Remove some extraneous elements and styles. Manipulate complex CSS through the DOM rather than style bindings. * Format missing metrics %age as - * whitespace cleanup * Discard tools when not using them * Nicer display for fusions * Simplify costlyOp now we have categories on nodes * item->node consistently * trivial simplifications and renames * license * Add comment for NaN utilization. * Indent HTML. Add missing </template> tags. Move <script> into <dom-module>. * Remove more console.log * Remove stale TODO * Rearrange module order and add a comment to clarify that tf-op-table-entry is an implementation detail * Add comment to 'table' property of tf-op-table-entry. * Pass around callbacks to decouple tf-op-table from tf-op-table-entry. This is a bunch more code, but I guess cleaner...
1 parent 1ec9041 commit 542ac06

File tree

10 files changed

+673
-5
lines changed

10 files changed

+673
-5
lines changed

tensorboard/plugins/profile/profile_plugin.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
_FILE_NAME = 'TOOL_FILE_NAME'
4444
TOOLS = {
4545
'trace_viewer': 'trace',
46+
'op_profile': 'op_profile.json',
4647
}
4748

4849

@@ -145,8 +146,6 @@ def data_impl(self, run, tool):
145146
asset_path = os.path.join(self.plugin_logdir, rel_data_path)
146147
raw_data = None
147148
try:
148-
# TODO(ioeric): use plugin_asset_util.RetieveAsset when it support reading
149-
# bytes data in python 3.
150149
with tf.gfile.Open(asset_path, "rb") as f:
151150
raw_data = f.read()
152151
except tf.errors.NotFoundError:
@@ -158,6 +157,8 @@ def data_impl(self, run, tool):
158157
return None
159158
if tool == 'trace_viewer':
160159
return process_raw_trace(raw_data)
160+
if tool == 'op_profile':
161+
return raw_data
161162
return None
162163

163164
@wrappers.Request.application
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package(default_visibility = ["//tensorboard:internal"])
2+
3+
load("//tensorboard/defs:web.bzl", "ts_web_library")
4+
5+
licenses(["notice"]) # Apache 2.0
6+
7+
ts_web_library(
8+
name = "tf_op_profile",
9+
srcs = ["tf-op-profile.html"],
10+
path = "/tf-op-profile",
11+
visibility = ["//visibility:public"],
12+
deps = [
13+
":tf_op_details",
14+
":tf_op_table",
15+
":utils",
16+
],
17+
)
18+
19+
ts_web_library(
20+
name = "tf_op_details",
21+
srcs = ["tf-op-details.html"],
22+
path = "/tf-op-profile",
23+
deps = [
24+
":utils",
25+
"@org_polymer_paper_card",
26+
],
27+
)
28+
29+
ts_web_library(
30+
name = "tf_op_table",
31+
srcs = ["tf-op-table.html"],
32+
path = "/tf-op-profile",
33+
deps = [
34+
":utils",
35+
],
36+
)
37+
38+
ts_web_library(
39+
name = "utils",
40+
srcs = [
41+
"utils.html",
42+
"utils.ts",
43+
],
44+
path = "/tf-op-profile",
45+
)
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<!--
2+
@license
3+
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
18+
<link rel="import" href="../paper-card/paper-card.html">
19+
<link rel="import" href="utils.html">
20+
21+
<!--
22+
tf-op-bar shows a small % utilization bar, colored using flameColor.
23+
-->
24+
<dom-module id="tf-op-bar">
25+
<style>
26+
:host {
27+
display: inline-block;
28+
height: 1.5em;
29+
line-height: 1.5em;
30+
}
31+
</style>
32+
<template>
33+
&nbsp;[[_percent(value)]]
34+
</template>
35+
<script>
36+
import {flameColor, percent} from './utils.js';
37+
38+
Polymer({
39+
is: 'tf-op-bar',
40+
properties: {
41+
value: {
42+
type: Number,
43+
notify: true,
44+
observer: '_updateValue',
45+
},
46+
},
47+
_percent: percent,
48+
_updateValue: function(value) {
49+
this.style.background = "linear-gradient(to right, " + flameColor(value) +
50+
" " + percent(value) + ", #ccc " + percent(value) + ")";
51+
},
52+
});
53+
</script>
54+
</dom-module>
55+
56+
<!--
57+
tf-op-details is a card that describes an op or group of ops (the 'node').
58+
-->
59+
<dom-module id="tf-op-details">
60+
<style>
61+
paper-card {
62+
--paper-card-header-color: white;
63+
width: 100%;
64+
}
65+
tf-op-bar {
66+
width: 100%;
67+
}
68+
#subheader {
69+
padding: 0 16px 16px;
70+
color: rgba(255, 255, 255, 0.7);
71+
position: relative;
72+
top: -10px;
73+
}
74+
.card-content > div {
75+
margin-bottom: 1em;
76+
}
77+
.expression {
78+
display: block;
79+
word-wrap: break-word;
80+
}
81+
.unavailable {
82+
font-style: italic;
83+
color: #666;
84+
}
85+
</style>
86+
<template>
87+
<paper-card id="card" heading="[[node.name]]" elevation="2">
88+
<div id="subheader">[[_subheader(node)]]</div>
89+
<div class="card-content">
90+
<div hidden="[[!node.metrics]]">
91+
<b>FLOPS utilization: </b>
92+
<tf-op-bar value="[[_utilization(node)]]"></tf-op-bar>
93+
</div>
94+
<div hidden="[[!node.xla.expression]]">
95+
<b>Expression: </b>
96+
<code class="expression">[[node.xla.expression]]</code>
97+
</div>
98+
<div class="unavailable" hidden="[[!_fused(node)]]">
99+
Performance information for individual fused operations is not available.
100+
</div>
101+
<div class="unavailable" hidden="[[!node.category]]">
102+
Select items within this category for performace details.
103+
</div>
104+
</div>
105+
</paper-card>
106+
</template>
107+
108+
<script>
109+
import {flameColor, utilization} from './utils.js';
110+
111+
Polymer({
112+
is: 'tf-op-details',
113+
properties: {
114+
node: {
115+
type: Object,
116+
notify: true,
117+
observer: '_updateCard',
118+
},
119+
},
120+
_utilization: utilization,
121+
_updateCard: function(node){
122+
if (node == null) return;
123+
var color = flameColor(utilization(node), 0.7);
124+
this.$.card.updateStyles({"--paper-card-header": "background-color:" + color});
125+
this.$.subheader.style.backgroundColor = color;
126+
},
127+
_subheader: function(node) {
128+
if (!node) return null;
129+
if (node.xla) return node.xla.category + " operation";
130+
if (node.category) return "Operation category";
131+
return "Unknown";
132+
},
133+
_fused: function(node){ return node && node.xla && !node.metrics; },
134+
});
135+
</script>
136+
</dom-module>
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<!--
2+
@license
3+
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
18+
<link rel="import" href="tf-op-table.html">
19+
<link rel="import" href="tf-op-details.html">
20+
<link rel="import" href="utils.html">
21+
22+
<!--
23+
tf-op-profile is a view within the tf-profile-dashboard.
24+
It shows a hierarchical profile of XLA operations broken (a tf-op-table).
25+
A single node can be selected, and its details are shown (with tf-op-details).
26+
Summary information and the most expensive op are described at the top, as
27+
a gentle introduction.
28+
-->
29+
<dom-module id="tf-op-profile">
30+
<template>
31+
<style>
32+
tf-op-details {
33+
position: fixed;
34+
top: 80px;
35+
right: 16px;
36+
width: 330px;
37+
}
38+
:host {
39+
display: block;
40+
margin-right: 360px;
41+
}
42+
</style>
43+
<div class="tf-op-profile">
44+
<tf-op-details hidden="[[!_active]]" node="[[_active]]"></tf-op-details>
45+
<h4>Overall TPU utilization is
46+
<span style$="color:[[_textColor(_root)]]">[[_utilizationPercent(_root)]]</span>
47+
</h4>
48+
<p>Modifying your model's architecture, data dimensions, and improving the
49+
efficiency of CPU operations may help reach the TPU's FLOPS potential.</p>
50+
<h4>The most time was spent in a <b>[[_costlyOp.xla.category]] operation</b>
51+
([[_percent(_costlyOp.metrics.time)]])</h4>
52+
<p hidden$="[[_hasFlops(_costlyOp)]]">
53+
[[_costlyOp.name]] is
54+
<span style$="color:[[_textColor(_costlyOp)]];">overhead</span>,
55+
and a good target for optimization.
56+
</p>
57+
<!-- extra div to avoid HTML parsing bugs in polymer/vulcanize -->
58+
<div hidden$="[[!_hasFlops(_costlyOp)]]">
59+
<p>
60+
While active, <code>[[_costlyOp.name]]</code> uses
61+
<span style="color:[[_textColor(_costlyOp)]]">[[_utilizationPercent(_costlyOp)]]</span>
62+
of the computational potential of the chip.
63+
<span hidden$="[[_goodFlops(_costlyOp)]]">
64+
This is a good target for optimization.
65+
</span>
66+
<span hidden$="[[!_goodFlops(_costlyOp)]]">
67+
This is pretty good - other operations may be better targets for optimization.
68+
</span>
69+
</p>
70+
</div>
71+
<tf-op-table root-node="[[_root]]" active={{_active}}></tf-op-table>
72+
</div>
73+
</template>
74+
75+
<script>
76+
import {RequestManager} from '../tf-backend/requestManager.js';
77+
import {getRouter} from '../tf-backend/router.js';
78+
import {flameColor, utilization, percent} from './utils.js';
79+
80+
Polymer({
81+
is: 'tf-op-profile',
82+
properties: {
83+
_requestManager: {
84+
type: Object,
85+
readOnly: true,
86+
value: () => new RequestManager(),
87+
},
88+
run: {
89+
type: String,
90+
observer: '_load'
91+
},
92+
_data: {
93+
type: Object,
94+
notify: true,
95+
},
96+
_root: {
97+
type: Object,
98+
computed: '_getRoot(_data, "byCategory")',
99+
notify: true,
100+
},
101+
_active: {
102+
type: Object,
103+
value: null,
104+
notify: true,
105+
},
106+
_costlyOp: {
107+
type: Object,
108+
computed: '_worstOp(_data.byCategory, "time")',
109+
},
110+
},
111+
_load: function(run) {
112+
this._requestManager.request(
113+
getRouter().
114+
pluginRunTagRoute('profile', '/data')('op_profile', run)
115+
).then((data) => {
116+
this._data = data;
117+
});
118+
},
119+
_getRoot: function(data, breakdown) { return data[breakdown]; },
120+
_percent: percent,
121+
_utilizationPercent: function(node) { return percent(utilization(node)); },
122+
_worstOp: function(root, metric) {
123+
var worst = null, worstValue = -Infinity;
124+
function visit(node) {
125+
if (node.xla != null && node.metrics != null && node.metrics[metric] > worstValue) {
126+
worst = node;
127+
worstValue = node.metrics[metric];
128+
}
129+
node.children.forEach(visit);
130+
}
131+
visit(root);
132+
return worst;
133+
},
134+
_hasFlops: function(node) { return node.metrics.flops > 0; },
135+
_goodFlops: function(node) { return utilization(node) > 0.4; },
136+
_textColor: function(node) { return flameColor(utilization(node), 0.7); },
137+
});
138+
</script>
139+
</dom-module>

0 commit comments

Comments
 (0)