Add a frontend for viewing the first ops that exhibit bad values (NaN, +/- Inf).
This helps the user identify problematic ops. Also moved the debugger data logic within tf-graph-info into a new tf-graph-debugger-data-card component. PiperOrigin-RevId: 158208679
This commit is contained in:
parent
7f5384dccf
commit
0ea0bf5aae
@ -356,6 +356,8 @@ filegroup(
|
||||
"//tensorflow/tensorboard/components/tf_graph_controls/demo:all_files",
|
||||
"//tensorflow/tensorboard/components/tf_graph_dashboard:all_files",
|
||||
"//tensorflow/tensorboard/components/tf_graph_dashboard/demo:all_files",
|
||||
"//tensorflow/tensorboard/components/tf_graph_debugger_data_card:all_files",
|
||||
"//tensorflow/tensorboard/components/tf_graph_debugger_data_card/demo:all_files",
|
||||
"//tensorflow/tensorboard/components/tf_graph_info:all_files",
|
||||
"//tensorflow/tensorboard/components/tf_graph_info/demo:all_files",
|
||||
"//tensorflow/tensorboard/components/tf_graph_loader:all_files",
|
||||
|
@ -138,7 +138,7 @@ paper-progress {
|
||||
render-hierarchy="{{renderHierarchy}}"
|
||||
devices-for-stats="[[devicesForStats]]"
|
||||
stats="[[stats]]"
|
||||
selected-node="{{_selectedNode}}"
|
||||
selected-node="{{selectedNode}}"
|
||||
highlighted-node="{{_highlightedNode}}"
|
||||
color-by="[[colorBy]]"
|
||||
color-by-params="{{colorByParams}}"
|
||||
@ -153,13 +153,14 @@ paper-progress {
|
||||
graph-hierarchy="[[graphHierarchy]]"
|
||||
render-hierarchy="[[renderHierarchy]]"
|
||||
graph="[[graph]]"
|
||||
selected-node="{{_selectedNode}}"
|
||||
selected-node="{{selectedNode}}"
|
||||
selected-node-include="{{_selectedNodeInclude}}"
|
||||
highlighted-node="{{_highlightedNode}}"
|
||||
color-by="[[colorBy]]"
|
||||
color-by-params="[[colorByParams]]"
|
||||
debugger-data-enabled="[[debuggerDataEnabled]]"
|
||||
are-health-pills-loading="[[areHealthPillsLoading]]"
|
||||
debugger-numeric-alerts="[[debuggerNumericAlerts]]"
|
||||
node-names-to-health-pills="[[nodeNamesToHealthPills]]"
|
||||
all-steps-mode-enabled="{{allStepsModeEnabled}}"
|
||||
specific-health-pill-step="{{specificHealthPillStep}}"
|
||||
@ -198,6 +199,12 @@ Polymer({
|
||||
debuggerDataEnabled: Boolean,
|
||||
// Whether health pills are currently being loaded.
|
||||
areHealthPillsLoading: Boolean,
|
||||
// An array of alerts (in chronological order) provided by debugging libraries on when bad
|
||||
// values (NaN, +/- Inf) appear.
|
||||
debuggerNumericAlerts: {
|
||||
type: Array,
|
||||
notify: true,
|
||||
},
|
||||
// A mapping between node name to the tf.graph.scene.HealthPill to render.
|
||||
nodeNamesToHealthPills: Object,
|
||||
// Whether the user can request health pills for individual steps from the server. This can be
|
||||
@ -217,7 +224,10 @@ Polymer({
|
||||
// The step of health pills to show throughout the graph.
|
||||
healthPillStepIndex: Number,
|
||||
// Private API: Data routing between child components.
|
||||
_selectedNode: String,
|
||||
selectedNode: {
|
||||
type: String,
|
||||
notify: true,
|
||||
},
|
||||
// The enum value of the include property of the selected node.
|
||||
_selectedNodeInclude: Number,
|
||||
_highlightedNode: String
|
||||
@ -226,7 +236,7 @@ Polymer({
|
||||
'node-toggle-extract': '_nodeToggleExtract'
|
||||
},
|
||||
observers: [
|
||||
'_updateNodeInclude(_selectedNode)'
|
||||
'_updateNodeInclude(selectedNode)'
|
||||
],
|
||||
/** True if the progress is not complete yet (< 100 %). */
|
||||
_isNotComplete: function(progress) {
|
||||
@ -248,7 +258,7 @@ Polymer({
|
||||
node ? node.include : tf.graph.InclusionType.UNSPECIFIED);
|
||||
},
|
||||
_nodeToggleExtract: function() {
|
||||
this._updateNodeInclude(this._selectedNode);
|
||||
this._updateNodeInclude(this.selectedNode);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -1630,4 +1630,44 @@ function extractHighDegrees(renderNode: RenderGroupNodeInfo) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands nodes in the graph until the desired node is visible.
|
||||
*
|
||||
* @param scene The scene polymer component.
|
||||
* @param renderHierarchy The render hierarchy.
|
||||
* @param tensorName The name of a tensor.
|
||||
* @return A string that is the name of the node representing the given tensor.
|
||||
* Note that the original tensor name might differ from this returned node
|
||||
* name. Specifically, for instance, the tensor name usually ends with an
|
||||
* output slot index (such as :0), while the node name lacks that suffix.
|
||||
*/
|
||||
export function expandUntilNodeIsShown(
|
||||
scene, renderHierarchy, tensorName: string) {
|
||||
const splitTensorName = tensorName.split('/');
|
||||
|
||||
// Graph names do not take into account the output slot. Strip it.
|
||||
const lastNodeNameMatch =
|
||||
splitTensorName[splitTensorName.length - 1].match(/(.*):\d+/);
|
||||
if (lastNodeNameMatch.length === 2) {
|
||||
splitTensorName[splitTensorName.length - 1] = lastNodeNameMatch[1];
|
||||
}
|
||||
|
||||
let nodeName = splitTensorName[0];
|
||||
let renderNode = renderHierarchy.getRenderNodeByName(nodeName);
|
||||
for (let i = 1; i < splitTensorName.length; i++) {
|
||||
// Op nodes are not expandable.
|
||||
if (renderNode.node.type === tf.graph.NodeType.OP) {
|
||||
break;
|
||||
}
|
||||
renderHierarchy.buildSubhierarchy(nodeName);
|
||||
renderNode.expanded = true;
|
||||
scene.setNodeExpanded(renderNode);
|
||||
nodeName += '/' + splitTensorName[i];
|
||||
renderNode = renderHierarchy.getRenderNodeByName(nodeName);
|
||||
}
|
||||
|
||||
return renderNode.node.name;
|
||||
}
|
||||
|
||||
} // close module tf.graph.render
|
||||
|
@ -288,4 +288,29 @@ module tf.graph.util {
|
||||
|
||||
return _.object(queryParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a timestamp in microseconds, return a human-friendly string denoting
|
||||
* how long ago the timestamp was.
|
||||
*/
|
||||
export function computeHumanFriendlyTime(timeInMicroseconds: number) {
|
||||
var timeDifferenceInMs =
|
||||
+(new Date()) - +(new Date(timeInMicroseconds / 1e3));
|
||||
if (timeDifferenceInMs < 30000) {
|
||||
return 'just now';
|
||||
} else if (timeDifferenceInMs < 60000) {
|
||||
return Math.floor(timeDifferenceInMs / 1000) + ' seconds ago';
|
||||
} else if (timeDifferenceInMs < 120000) {
|
||||
return 'a minute ago';
|
||||
} else if (timeDifferenceInMs < 3600000) {
|
||||
return Math.floor(timeDifferenceInMs / 60000) + ' minutes ago';
|
||||
} else if (Math.floor(timeDifferenceInMs / 3600000) == 1) {
|
||||
return 'an hour ago';
|
||||
} else if (timeDifferenceInMs < 86400000) {
|
||||
return Math.floor(timeDifferenceInMs / 3600000) + ' hours ago';
|
||||
} else if (timeDifferenceInMs < 172800000) {
|
||||
return 'yesterday';
|
||||
}
|
||||
return Math.floor(timeDifferenceInMs / 86400000) + ' days ago';
|
||||
}
|
||||
}
|
||||
|
@ -78,11 +78,13 @@ out-hierarchy-params="{{_hierarchyParams}}"
|
||||
progress="[[_progress]]"
|
||||
debugger-data-enabled="[[debuggerDataEnabled]]"
|
||||
are-health-pills-loading="[[_areHealthPillsLoading]]"
|
||||
debugger-numeric-alerts="[[_debuggerNumericAlerts]]"
|
||||
node-names-to-health-pills="[[_nodeNamesToHealthPills]]"
|
||||
all-steps-mode-enabled="{{allStepsModeEnabled}}"
|
||||
specific-health-pill-step="{{specificHealthPillStep}}"
|
||||
health-pill-step-index="[[_healthPillStepIndex]]"
|
||||
render-hierarchy="{{_renderHierarchy}}"
|
||||
selected-node="{{_selectedNode}}"
|
||||
stats="[[_stats]]"
|
||||
></tf-graph-board>
|
||||
</div>
|
||||
@ -128,12 +130,20 @@ Polymer({
|
||||
allStepsModeEnabled: Boolean,
|
||||
specificHealthPillStep: {type: Number, value: 0},
|
||||
healthPillsToggledOn: {type: Boolean, value: true, observer: '_healthPillsToggledOnChanged'},
|
||||
_selectedNode: Object,
|
||||
_isAttached: Boolean,
|
||||
// Whether this dashboard is initialized. This dashboard should only be initialized once.
|
||||
_initialized: Boolean,
|
||||
// Whether health pills are currently being loaded, in which case we may want to say show a
|
||||
// spinner.
|
||||
_areHealthPillsLoading: Boolean,
|
||||
// An array of alerts (in chronological order) provided by debugging libraries on when bad
|
||||
// values (NaN, +/- Inf) appear.
|
||||
_debuggerNumericAlerts: {
|
||||
type: Array,
|
||||
value: [],
|
||||
notify: true,
|
||||
},
|
||||
// Maps the names of nodes to an array of health pills (HealthPillDatums).
|
||||
_nodeNamesToHealthPills: {
|
||||
type: Object,
|
||||
@ -158,7 +168,7 @@ Polymer({
|
||||
'node-toggle-expand': '_handleNodeToggleExpand',
|
||||
},
|
||||
observers: [
|
||||
'_maybeFetchHealthPills(allStepsModeEnabled, specificHealthPillStep)',
|
||||
'_maybeFetchHealthPills(allStepsModeEnabled, specificHealthPillStep, _selectedNode)',
|
||||
'_maybeInitializeDashboard(backend, _isAttached)',
|
||||
],
|
||||
attached: function() {
|
||||
@ -212,7 +222,7 @@ Polymer({
|
||||
},
|
||||
_requestHealthPills: function() {
|
||||
this.set('_areHealthPillsLoading', true);
|
||||
const requestId = ++this._healthPillRequestId;
|
||||
var requestId = ++this._healthPillRequestId;
|
||||
|
||||
if (this._healthPillStepRequestTimerId !== null) {
|
||||
// A request for health pills is already scheduled to be initiated. Clear it, and schedule a
|
||||
@ -243,9 +253,17 @@ Polymer({
|
||||
return;
|
||||
}
|
||||
|
||||
const specificStep = this.allStepsModeEnabled ? this.specificHealthPillStep : undefined;
|
||||
this.backend.healthPills(this._renderHierarchy.getNamesOfRenderedOps(), specificStep).then(
|
||||
var specificStep = this.allStepsModeEnabled ? this.specificHealthPillStep : undefined;
|
||||
|
||||
var healthPillsPromise = this.backend.healthPills(
|
||||
this._renderHierarchy.getNamesOfRenderedOps(), specificStep);
|
||||
var alertsPromise = this.backend.debuggerNumericsAlerts();
|
||||
|
||||
Promise.all([healthPillsPromise, alertsPromise]).then(
|
||||
function(result) {
|
||||
var healthPillsResult = result[0];
|
||||
var alertsResult = result[1];
|
||||
|
||||
if (!this.healthPillsToggledOn) {
|
||||
// The user has opted to hide health pills via the toggle button.
|
||||
return;
|
||||
@ -259,12 +277,13 @@ Polymer({
|
||||
// Set the index for which step to show for the health pills. By default, show the last step.
|
||||
// A precondition we assume (that Tensorboard's reservoir sampling guarantees) is that all
|
||||
// node names should be mapped to the same number of steps.
|
||||
for (let nodeName in result) {
|
||||
this.set('_healthPillStepIndex', result[nodeName].length - 1);
|
||||
for (var nodeName in healthPillsResult) {
|
||||
this.set('_healthPillStepIndex', healthPillsResult[nodeName].length - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
this.set('_nodeNamesToHealthPills', result);
|
||||
this.set('_debuggerNumericAlerts', alertsResult);
|
||||
this.set('_nodeNamesToHealthPills', healthPillsResult);
|
||||
this.set('_areHealthPillsLoading', false);
|
||||
this.set('_healthPillStepRequestTimerId', null);
|
||||
}.bind(this));
|
||||
|
@ -0,0 +1,44 @@
|
||||
package(default_visibility = ["//tensorflow:internal"])
|
||||
|
||||
load("//tensorflow/tensorboard:defs.bzl", "tensorboard_webcomponent_library")
|
||||
load("//tensorflow/tensorboard:web.bzl", "ts_web_library")
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
ts_web_library(
|
||||
name = "tf_graph_debugger_data_card",
|
||||
srcs = [
|
||||
"tf-graph-debugger-data-card.html",
|
||||
],
|
||||
path = "/tf-graph-debugger-data-card",
|
||||
deps = [
|
||||
"//tensorflow/tensorboard/components/tf_dashboard_common",
|
||||
"//tensorflow/tensorboard/components/tf_graph_common",
|
||||
"//tensorflow/tensorboard/components/tf_imports:polymer",
|
||||
"@org_polymer_paper_slider",
|
||||
"@org_polymer_paper_spinner",
|
||||
],
|
||||
)
|
||||
|
||||
tensorboard_webcomponent_library(
|
||||
name = "legacy",
|
||||
srcs = [":tf_graph_debugger_data_card"],
|
||||
destdir = "tf-graph-debugger-data-card",
|
||||
deps = [
|
||||
"//tensorflow/tensorboard/components/tf_dashboard_common:legacy",
|
||||
"//tensorflow/tensorboard/components/tf_graph_common:legacy",
|
||||
"//third_party/javascript/polymer/v1/iron-collapse:lib",
|
||||
"//third_party/javascript/polymer/v1/iron-list:lib",
|
||||
"//third_party/javascript/polymer/v1/paper-icon-button:lib",
|
||||
"//third_party/javascript/polymer/v1/paper-item:lib",
|
||||
"//third_party/javascript/polymer/v1/paper-slider:lib",
|
||||
"//third_party/javascript/polymer/v1/paper-spinner:lib",
|
||||
"//third_party/javascript/polymer/v1/polymer:lib",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all_files",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["notsan"],
|
||||
)
|
@ -0,0 +1,24 @@
|
||||
package(default_visibility = ["//tensorflow:internal"])
|
||||
|
||||
load("//tensorflow/tensorboard:web.bzl", "ts_web_library")
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
# bazel run //third_party/tensorflow/tensorboard/components/tf_graph_debugger_data_card/demo
|
||||
ts_web_library(
|
||||
name = "demo",
|
||||
srcs = ["index.html"] + glob(["data/**"]),
|
||||
path = "/tf-graph-debugger-data-card/demo",
|
||||
deps = [
|
||||
"//tensorflow/tensorboard/components/tf_graph_debugger_data_card",
|
||||
"//tensorflow/tensorboard/components/tf_imports:webcomponentsjs",
|
||||
"@org_polymer_iron_demo_helpers",
|
||||
"@org_polymer_paper_styles",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all_files",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["notsan"],
|
||||
)
|
@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
@license
|
||||
Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<script src="../../webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||
<link rel="import" href="../tf-graph-debugger-data-card.html">
|
||||
<link rel="import" href="../../iron-demo-helpers/demo-snippet.html">
|
||||
<title>TF Graph Info Demo</title>
|
||||
<style>
|
||||
#demo-container {
|
||||
border: 2px solid #808080;
|
||||
width: 1000px;
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
||||
<demo-snippet>
|
||||
<template>
|
||||
<div id='demo-container'>
|
||||
<!-- This simple demo starts up a page with a health pill legend. -->
|
||||
<tf-graph-debugger-data-card></tf-graph-debugger-data-card>
|
||||
</div>
|
||||
</template>
|
||||
</demo-snippet>
|
@ -0,0 +1,560 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<link rel="import" href="../polymer/polymer.html">
|
||||
<link rel="import" href="../paper-slider/paper-slider.html">
|
||||
<link rel="import" href="../paper-spinner/paper-spinner-lite.html">
|
||||
<link rel="import" href="../tf-graph-common/tf-graph-common.html">
|
||||
|
||||
<dom-module id="tf-graph-debugger-data-card">
|
||||
<template>
|
||||
<style>
|
||||
:host {
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.health-pill-legend {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.health-pill-legend h2 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.health-pill-entry {
|
||||
margin: 10px 10px 10px 0;
|
||||
}
|
||||
|
||||
.health-pill-entry .color-preview {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.health-pill-entry .color-label, .health-pill-entry .tensor-count {
|
||||
color: #777;
|
||||
display: inline-block;
|
||||
height: 26px;
|
||||
font-size: 22px;
|
||||
line-height: 26px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.health-pill-entry .tensor-count {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#health-pill-step-slider {
|
||||
width: 100%;
|
||||
margin: 0 0 0 -15px;
|
||||
/* 31 comes from adding a padding of 15px from both sides of the paper-slider, subtracting
|
||||
* 1px so that the slider width aligns with the image (the last slider marker takes up 1px),
|
||||
* and adding 2px to account for a border of 1px on both sides of the image. 30 - 1 + 2.
|
||||
* Apparently, the paper-slider lacks a mixin for those padding values. */
|
||||
width: calc(100% + 31px);
|
||||
}
|
||||
|
||||
#health-pills-loading-spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#health-pill-step-number-input {
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#numeric-alerts-table-container {
|
||||
max-height: 400px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#numeric-alerts-table {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#numeric-alerts-table td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#numeric-alerts-table .first-offense-td {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.first-offense-td {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.tensor-device-td {
|
||||
max-width: 140px;
|
||||
word-wrap : break-word;
|
||||
}
|
||||
|
||||
.tensor-section-within-table {
|
||||
color: #266236;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.tensor-section-within-table:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.device-section-within-table {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.mini-health-pill {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.mini-health-pill > div {
|
||||
height: 100%;
|
||||
width: 60px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#event-counts-th {
|
||||
padding: 0 0 0 10px;
|
||||
}
|
||||
|
||||
.negative-inf-mini-health-pill-section {
|
||||
background: rgb(255, 141, 0);
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.positive-inf-mini-health-pill-section {
|
||||
background: rgb(0, 62, 212);
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.nan-mini-health-pill-section {
|
||||
background: rgb(204, 47, 44);
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.negative-inf-mini-health-pill-section,
|
||||
.positive-inf-mini-health-pill-section,
|
||||
.nan-mini-health-pill-section {
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
line-height: 20px;
|
||||
margin: 0 0 0 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-numeric-alerts-notification {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<paper-material elevation="1" class="card health-pill-legend">
|
||||
<div class="title">
|
||||
Enable all (not just sampled) steps. Requires slow disk read.
|
||||
</div>
|
||||
<paper-toggle-button id="enableAllStepsModeToggle" checked="{{allStepsModeEnabled}}">
|
||||
</paper-toggle-button>
|
||||
<h2>
|
||||
Step of Health Pills:
|
||||
<template is="dom-if" if="[[allStepsModeEnabled]]">
|
||||
<input type="number"
|
||||
id="health-pill-step-number-input"
|
||||
min="0"
|
||||
max="[[_biggestStepEverSeen]]"
|
||||
value="{{specificHealthPillStep::input}}">
|
||||
</template>
|
||||
<template is="dom-if" if="[[!allStepsModeEnabled]]">
|
||||
[[_currentStepDisplayValue]]
|
||||
</template>
|
||||
<paper-spinner-lite active
|
||||
hidden$=[[!areHealthPillsLoading]]
|
||||
id="health-pills-loading-spinner"></paper-spinner-lite>
|
||||
</h2>
|
||||
<template is="dom-if" if="[[allStepsModeEnabled]]">
|
||||
<paper-slider
|
||||
id="health-pill-step-slider"
|
||||
immediate-value="{{specificHealthPillStep}}"
|
||||
max="[[_biggestStepEverSeen]]"
|
||||
snaps
|
||||
step="1"
|
||||
value="{{specificHealthPillStep}}"></paper-slider>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!allStepsModeEnabled]]">
|
||||
<template is="dom-if" if="[[_maxStepIndex]]">
|
||||
<paper-slider
|
||||
id="health-pill-step-slider"
|
||||
immediate-value="{{healthPillStepIndex}}"
|
||||
max="[[_maxStepIndex]]"
|
||||
snaps
|
||||
step="1"
|
||||
value="{{healthPillStepIndex}}"></paper-slider>
|
||||
</template>
|
||||
</template>
|
||||
<h2>
|
||||
Health Pill
|
||||
<template is="dom-if" if="[[healthPillValuesForSelectedNode]]">
|
||||
Counts for Selected Node
|
||||
</template>
|
||||
<template is="dom-if" if="[[!healthPillValuesForSelectedNode]]">
|
||||
Legend
|
||||
</template>
|
||||
</h2>
|
||||
<template is="dom-repeat" items="[[healthPillEntries]]">
|
||||
<div class="health-pill-entry">
|
||||
<div class="color-preview" style="background:[[item.background_color]]"></div>
|
||||
<div class="color-label">[[item.label]]</div>
|
||||
<div class="tensor-count">
|
||||
[[_computeTensorCountString(healthPillValuesForSelectedNode, index)]]
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div hidden$="[[!_hasDebuggerNumericAlerts(debuggerNumericAlerts)]]">
|
||||
<h2 id="numeric-alerts-header">Numeric Alerts</h2>
|
||||
<p>
|
||||
Alerts are sorted from top to bottom by increasing timestamp.
|
||||
</p>
|
||||
<div id='numeric-alerts-table-container'>
|
||||
<table id="numeric-alerts-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>First Offense</th>
|
||||
<th>Tensor (Device)</th>
|
||||
<th id='event-counts-th'>Event Counts</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="numeric-alerts-body">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<template is="dom-if" if="[[!_hasDebuggerNumericAlerts(debuggerNumericAlerts)]]">
|
||||
<p class="no-numeric-alerts-notification">
|
||||
No numeric alerts so far. That is likely good. Alerts indicate the presence of NaN
|
||||
or (+/-) Infinity values, which may be concerning.
|
||||
</p>
|
||||
</template>
|
||||
</paper-material>
|
||||
</template>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
(function() {
|
||||
Polymer({
|
||||
is: 'tf-graph-debugger-data-card',
|
||||
|
||||
properties: {
|
||||
renderHierarchy: Object,
|
||||
debuggerNumericAlerts: {
|
||||
type: Array,
|
||||
notify: true,
|
||||
},
|
||||
nodeNamesToHealthPills: Object,
|
||||
healthPillStepIndex: {
|
||||
type: Number,
|
||||
notify: true,
|
||||
},
|
||||
// Only relevant if we are in all steps mode, in which case the user may want to view health
|
||||
// pills for a specific step.
|
||||
specificHealthPillStep: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
notify: true,
|
||||
},
|
||||
// Two-ways
|
||||
selectedNode: {
|
||||
type: String,
|
||||
notify: true
|
||||
},
|
||||
highlightedNode: {
|
||||
type: String,
|
||||
notify: true
|
||||
},
|
||||
// The enum value of the include property of the selected node.
|
||||
selectedNodeInclude: {
|
||||
type: Number,
|
||||
notify: true
|
||||
},
|
||||
// Whether health pills are currently being loaded, in which case we show a spinner (and the
|
||||
// current health pills shown might be out of date).
|
||||
areHealthPillsLoading: Boolean,
|
||||
healthPillEntries: {
|
||||
type: Array,
|
||||
value: tf.graph.scene.healthPillEntries,
|
||||
readOnly: true,
|
||||
},
|
||||
healthPillValuesForSelectedNode: {
|
||||
type: Array,
|
||||
computed: '_computeHealthPillForNode(nodeNamesToHealthPills, healthPillStepIndex, selectedNode, allStepsModeEnabled, areHealthPillsLoading)',
|
||||
},
|
||||
// When all-steps mode is enabled, the user can request health pills for any step. In this
|
||||
// mode, Tensorboard makes a request every time the user drags the slider to a different step.
|
||||
allStepsModeEnabled: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
},
|
||||
// The biggest step value ever seen. Used to determine what steps of health pills to let the
|
||||
// user fetch in all steps mode.
|
||||
_biggestStepEverSeen: {
|
||||
type: Number,
|
||||
computed: '_computeBiggestStepEverSeen(nodeNamesToHealthPills)',
|
||||
},
|
||||
_maxStepIndex: {
|
||||
type: Number,
|
||||
computed: '_computeMaxStepIndex(nodeNamesToHealthPills)',
|
||||
},
|
||||
_currentStepDisplayValue: {
|
||||
type: String,
|
||||
computed: '_computeCurrentStepDisplayValue(nodeNamesToHealthPills, healthPillStepIndex, allStepsModeEnabled, specificHealthPillStep, areHealthPillsLoading)',
|
||||
},
|
||||
},
|
||||
observers: [
|
||||
'_updateAlertsList(debuggerNumericAlerts)',
|
||||
],
|
||||
ready: function() {
|
||||
var mainContainer = document.getElementById('mainContainer');
|
||||
var scrollbarContainer = document.querySelector('tf-dashboard-layout .scrollbar');
|
||||
if (mainContainer && scrollbarContainer) {
|
||||
// If this component is being used inside of TensorBoard's dashboard layout, it may easily
|
||||
// cause the dashboard layout element to overflow, giving the user 2 scroll bars. Prevent
|
||||
// that by hiding whatever content overflows - the user will have to expand the viewport to
|
||||
// use this debugging card.
|
||||
mainContainer.style.overflow = 'hidden';
|
||||
scrollbarContainer.style.overflow = 'hidden';
|
||||
}
|
||||
},
|
||||
_healthPillsAvailable: function(debuggerDataEnabled, nodeNamesToHealthPills) {
|
||||
// So long as there is a mapping (even if empty) from node name to health pills, show the
|
||||
// legend and slider. We do that because, even if no health pills exist at the current step,
|
||||
// the user may desire to change steps, and the slider must show for the user to do that.
|
||||
return debuggerDataEnabled && nodeNamesToHealthPills;
|
||||
},
|
||||
_computeTensorCountString: function(healthPillValuesForSelectedNode, valueIndex) {
|
||||
if (!healthPillValuesForSelectedNode) {
|
||||
// No health pill data is available.
|
||||
return '';
|
||||
}
|
||||
|
||||
return healthPillValuesForSelectedNode[valueIndex].toFixed(0);
|
||||
},
|
||||
_computeHealthPillForNode: function(
|
||||
nodeNamesToHealthPills, healthPillStepIndex, selectedNode, allStepsModeEnabled, areHealthPillsLoading) {
|
||||
if (areHealthPillsLoading) {
|
||||
// Health pills are loading. Do not render data that is out of date.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!selectedNode) {
|
||||
// No node is selected.
|
||||
return null;
|
||||
}
|
||||
|
||||
const healthPills = nodeNamesToHealthPills[selectedNode];
|
||||
if (!healthPills) {
|
||||
// This node lacks a health pill.
|
||||
return null;
|
||||
}
|
||||
|
||||
// If all steps mode is enabled, we use the first health pill in the list because the JSON
|
||||
// response from the server is a mapping between node name and a list of 1 health pill.
|
||||
const healthPill = healthPills[allStepsModeEnabled ? 0 : healthPillStepIndex];
|
||||
if (!healthPill) {
|
||||
// This node lacks a health pill at the current step.
|
||||
return null;
|
||||
}
|
||||
|
||||
// The health pill count values start at 2. Each health pill contains 6 values.
|
||||
return healthPill.value.slice(2, 8);
|
||||
},
|
||||
_computeCurrentStepDisplayValue: function(
|
||||
nodeNamesToHealthPills,
|
||||
healthPillStepIndex,
|
||||
allStepsModeEnabled,
|
||||
specificHealthPillStep,
|
||||
areHealthPillsLoading) {
|
||||
if (allStepsModeEnabled) {
|
||||
// The user seeks health pills for specific step from the server.
|
||||
return specificHealthPillStep.toFixed(0);
|
||||
}
|
||||
|
||||
if (areHealthPillsLoading) {
|
||||
// The current step is undefined.
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (let nodeName in nodeNamesToHealthPills) {
|
||||
// All nodes have the same number of steps stored, so only examine 1 node. We cannot
|
||||
// directly index into the nodeNamesToHealthPills object because we do not have a key.
|
||||
// If all steps mode is enabled, we only have 1 step to show.
|
||||
return nodeNamesToHealthPills[nodeName][healthPillStepIndex].step.toFixed(0);
|
||||
}
|
||||
|
||||
// The current step could not be computed.
|
||||
return 0;
|
||||
},
|
||||
_computeBiggestStepEverSeen: function(nodeNamesToHealthPills) {
|
||||
for (let nodeName in nodeNamesToHealthPills) {
|
||||
// All nodes have the same number of steps stored, so only examine 1 node.
|
||||
// The index is 1 less than the count. Tensorboard backend logic guarantees that the length
|
||||
// of the array will be greater than 1.
|
||||
var healthPills = nodeNamesToHealthPills[nodeName];
|
||||
return Math.max(this._biggestStepEverSeen, healthPills[healthPills.length - 1].step);
|
||||
}
|
||||
|
||||
// No steps seen so far. Default to 0.
|
||||
return this._biggestStepEverSeen || 0;
|
||||
},
|
||||
_computeMaxStepIndex: function(nodeNamesToHealthPills) {
|
||||
for (let nodeName in nodeNamesToHealthPills) {
|
||||
// All nodes have the same number of steps stored, so only examine 1 node.
|
||||
// The index is 1 less than the count. Tensorboard backend logic guarantees that the length
|
||||
// of the array will be greater than 1.
|
||||
return nodeNamesToHealthPills[nodeName].length - 1;
|
||||
}
|
||||
|
||||
// Return a falsy value. The slider should be hidden.
|
||||
return 0;
|
||||
},
|
||||
_hasDebuggerNumericAlerts: function(debuggerNumericAlerts) {
|
||||
return debuggerNumericAlerts && debuggerNumericAlerts.length;
|
||||
},
|
||||
_updateAlertsList: function(debuggerNumericAlerts) {
|
||||
var alertBody = this.$$('#numeric-alerts-body');
|
||||
if (!alertBody) {
|
||||
return;
|
||||
}
|
||||
|
||||
alertBody.innerHTML = '';
|
||||
|
||||
for (var i = 0; i < debuggerNumericAlerts.length; i++) {
|
||||
var alert = debuggerNumericAlerts[i];
|
||||
var tableRow = document.createElement('tr');
|
||||
|
||||
var timestampTd = document.createElement('td');
|
||||
timestampTd.innerHTML = tf.graph.util.computeHumanFriendlyTime(alert.first_timestamp);
|
||||
timestampTd.classList.add('first-offense-td');
|
||||
tableRow.appendChild(timestampTd);
|
||||
|
||||
var tensorDeviceTd = document.createElement('td');
|
||||
tensorDeviceTd.classList.add('tensor-device-td')
|
||||
|
||||
var tensorSection = document.createElement('div');
|
||||
tensorSection.classList.add('tensor-section-within-table');
|
||||
tensorSection.innerHTML = alert.tensor_name;
|
||||
this._addOpExpansionListener(tensorSection, alert.tensor_name);
|
||||
tensorDeviceTd.appendChild(tensorSection);
|
||||
|
||||
var deviceSection = document.createElement('div');
|
||||
deviceSection.classList.add('device-section-within-table');
|
||||
deviceSection.innerHTML = '(' + alert.device_name + ')';
|
||||
tensorDeviceTd.appendChild(deviceSection);
|
||||
tableRow.appendChild(tensorDeviceTd);
|
||||
|
||||
var miniHealthPill = document.createElement('div');
|
||||
miniHealthPill.classList.add('mini-health-pill');
|
||||
|
||||
var miniHealthPillTd = document.createElement('td');
|
||||
miniHealthPillTd.classList.add('mini-health-pill-td');
|
||||
miniHealthPillTd.appendChild(miniHealthPill);
|
||||
tableRow.appendChild(miniHealthPillTd);
|
||||
|
||||
if (alert.neg_inf_event_count) {
|
||||
var negativeInfCountSection = document.createElement('div');
|
||||
negativeInfCountSection.classList.add('negative-inf-mini-health-pill-section');
|
||||
negativeInfCountSection.innerHTML = alert.neg_inf_event_count;
|
||||
negativeInfCountSection.setAttribute(
|
||||
'title', alert.neg_inf_event_count + ' events with -∞')
|
||||
miniHealthPill.appendChild(negativeInfCountSection);
|
||||
}
|
||||
|
||||
if (alert.pos_inf_event_count) {
|
||||
var positiveInfCountSection = document.createElement('div');
|
||||
positiveInfCountSection.classList.add('positive-inf-mini-health-pill-section');
|
||||
positiveInfCountSection.innerHTML = alert.pos_inf_event_count;
|
||||
positiveInfCountSection.setAttribute(
|
||||
'title', alert.pos_inf_event_count + ' events with +∞')
|
||||
miniHealthPill.appendChild(positiveInfCountSection);
|
||||
}
|
||||
|
||||
if (alert.nan_event_count) {
|
||||
var nanCountSection = document.createElement('div');
|
||||
nanCountSection.classList.add('nan-mini-health-pill-section');
|
||||
nanCountSection.innerHTML = alert.nan_event_count;
|
||||
nanCountSection.setAttribute(
|
||||
'title', alert.nan_event_count + ' events with NaN')
|
||||
miniHealthPill.appendChild(nanCountSection);
|
||||
}
|
||||
|
||||
Polymer.dom(alertBody).appendChild(tableRow);
|
||||
}
|
||||
},
|
||||
// Adds a listener to an element, so that when that element is clicked, the tensor with
|
||||
// tensorName expands.
|
||||
_addOpExpansionListener: function(clickableElement, tensorName) {
|
||||
clickableElement.addEventListener('click', () => {
|
||||
// When the user clicks on a tensor name, expand all nodes until the user can see the
|
||||
// associated node.
|
||||
var nameOfNodeToSelect = tf.graph.render.expandUntilNodeIsShown(
|
||||
document.getElementById('scene'), this.renderHierarchy, tensorName);
|
||||
|
||||
// Store the current scroll of the graph info card. Node selection alters that scroll, and
|
||||
// we restore the scroll later.
|
||||
var previousScrollFromBottom;
|
||||
var graphInfoCard = document.querySelector('tf-graph-info#graph-info');
|
||||
if (graphInfoCard) {
|
||||
previousScrollFromBottom = graphInfoCard.scrollHeight - graphInfoCard.scrollTop;
|
||||
}
|
||||
|
||||
// Update the selected node within graph logic.
|
||||
var previousSelectedNode = this.selectedNode;
|
||||
this.set('selectedNode', nameOfNodeToSelect);
|
||||
|
||||
// Scroll the graph info card back down if necessary so that user can see the alerts section
|
||||
// again. Selecting the node causes the info card to scroll to the top, which may mean the
|
||||
// user no longer sees the list of alerts.
|
||||
var scrollToOriginalLocation = () => {
|
||||
graphInfoCard.scrollTop = graphInfoCard.scrollHeight - previousScrollFromBottom;
|
||||
};
|
||||
if (graphInfoCard) {
|
||||
// This component is used within an info card. Restore the original scroll.
|
||||
if (previousSelectedNode) {
|
||||
// The card for the selected node has already opened. Immediately restore the scroll.
|
||||
scrollToOriginalLocation();
|
||||
} else {
|
||||
// Give some time for the DOM of the info card to be created before scrolling down.
|
||||
window.setTimeout(scrollToOriginalLocation, 20);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</dom-module>
|
@ -17,6 +17,7 @@ ts_web_library(
|
||||
deps = [
|
||||
"//tensorflow/tensorboard/components/tf_dashboard_common",
|
||||
"//tensorflow/tensorboard/components/tf_graph_common",
|
||||
"//tensorflow/tensorboard/components/tf_graph_debugger_data_card",
|
||||
"//tensorflow/tensorboard/components/tf_imports:polymer",
|
||||
"@org_polymer_iron_collapse",
|
||||
"@org_polymer_iron_list",
|
||||
@ -34,6 +35,7 @@ tensorboard_webcomponent_library(
|
||||
deps = [
|
||||
"//tensorflow/tensorboard/components/tf_dashboard_common:legacy",
|
||||
"//tensorflow/tensorboard/components/tf_graph_common:legacy",
|
||||
"//tensorflow/tensorboard/components/tf_graph_debugger_data_card:legacy",
|
||||
"//third_party/javascript/polymer/v1/iron-collapse:lib",
|
||||
"//third_party/javascript/polymer/v1/iron-list:lib",
|
||||
"//third_party/javascript/polymer/v1/paper-icon-button:lib",
|
||||
|
@ -19,6 +19,7 @@ limitations under the License.
|
||||
<link rel="import" href="../paper-slider/paper-slider.html">
|
||||
<link rel="import" href="../paper-spinner/paper-spinner-lite.html">
|
||||
<link rel="import" href="../tf-graph-common/tf-graph-common.html">
|
||||
<link rel="import" href="../tf-graph-debugger-data-card/tf-graph-debugger-data-card.html">
|
||||
<link rel="import" href="tf-node-info.html">
|
||||
|
||||
<dom-module id="tf-graph-info">
|
||||
@ -29,6 +30,9 @@ limitations under the License.
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: block;
|
||||
max-height: 650px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@ -36,60 +40,6 @@ h2 {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.health-pill-legend {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.health-pill-legend h2 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.health-pill-entry {
|
||||
margin: 10px 10px 10px 0;
|
||||
}
|
||||
|
||||
.health-pill-entry .color-preview {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.health-pill-entry .color-label, .health-pill-entry .tensor-count {
|
||||
color: #777;
|
||||
display: inline-block;
|
||||
height: 26px;
|
||||
font-size: 22px;
|
||||
line-height: 26px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.health-pill-entry .tensor-count {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#health-pill-step-slider {
|
||||
width: 100%;
|
||||
margin: 0 0 0 -15px;
|
||||
/* 31 comes from adding a padding of 15px from both sides of the paper-slider, subtracting
|
||||
* 1px so that the slider width aligns with the image (the last slider marker takes up 1px),
|
||||
* and adding 2px to account for a border of 1px on both sides of the image. 30 - 1 + 2.
|
||||
* Apparently, the paper-slider lacks a mixin for those padding values. */
|
||||
width: calc(100% + 31px);
|
||||
}
|
||||
|
||||
#health-pills-loading-spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#health-pill-step-number-input {
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
<template is="dom-if" if="{{selectedNode}}">
|
||||
<paper-material elevation="1" class="card">
|
||||
@ -104,68 +54,17 @@ h2 {
|
||||
</paper-material>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_healthPillsAvailable(debuggerDataEnabled, nodeNamesToHealthPills)]]">
|
||||
<paper-material elevation="1" class="card health-pill-legend">
|
||||
<div class="title">
|
||||
Enable all (not just sampled) steps. Requires slow disk read.
|
||||
</div>
|
||||
<paper-toggle-button id="enableAllStepsModeToggle" checked="{{allStepsModeEnabled}}">
|
||||
</paper-toggle-button>
|
||||
<h2>
|
||||
Step of Health Pills:
|
||||
<template is="dom-if" if="[[allStepsModeEnabled]]">
|
||||
<input type="number"
|
||||
id="health-pill-step-number-input"
|
||||
min="0"
|
||||
max="[[_biggestStepEverSeen]]"
|
||||
value="{{specificHealthPillStep::input}}">
|
||||
</template>
|
||||
<template is="dom-if" if="[[!allStepsModeEnabled]]">
|
||||
[[_currentStepDisplayValue]]
|
||||
</template>
|
||||
|
||||
<paper-spinner-lite active
|
||||
hidden$=[[!areHealthPillsLoading]]
|
||||
id="health-pills-loading-spinner"></paper-spinner-lite>
|
||||
</h2>
|
||||
<template is="dom-if" if="[[allStepsModeEnabled]]">
|
||||
<paper-slider
|
||||
id="health-pill-step-slider"
|
||||
immediate-value="{{specificHealthPillStep}}"
|
||||
max="[[_biggestStepEverSeen]]"
|
||||
snaps
|
||||
step="1"
|
||||
value="{{specificHealthPillStep}}"></paper-slider>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!allStepsModeEnabled]]">
|
||||
<template is="dom-if" if="[[_maxStepIndex]]">
|
||||
<paper-slider
|
||||
id="health-pill-step-slider"
|
||||
immediate-value="{{healthPillStepIndex}}"
|
||||
max="[[_maxStepIndex]]"
|
||||
snaps
|
||||
step="1"
|
||||
value="{{healthPillStepIndex}}"></paper-slider>
|
||||
</template>
|
||||
</template>
|
||||
<h2>
|
||||
Health Pill
|
||||
<template is="dom-if" if="[[healthPillValuesForSelectedNode]]">
|
||||
Counts for Selected Node
|
||||
</template>
|
||||
<template is="dom-if" if="[[!healthPillValuesForSelectedNode]]">
|
||||
Legend
|
||||
</template>
|
||||
</h2>
|
||||
<template is="dom-repeat" items="[[healthPillEntries]]">
|
||||
<div class="health-pill-entry">
|
||||
<div class="color-preview" style="background:[[item.background_color]]"></div>
|
||||
<div class="color-label">[[item.label]]</div>
|
||||
<div class="tensor-count">
|
||||
[[_computeTensorCountString(healthPillValuesForSelectedNode, index)]]
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</paper-material>
|
||||
<tf-graph-debugger-data-card render-hierarchy="[[renderHierarchy]]"
|
||||
debugger-numeric-alerts="[[debuggerNumericAlerts]]"
|
||||
node-names-to-health-pills="[[nodeNamesToHealthPills]]"
|
||||
render-hierarchy="[[renderHierarchy]]"
|
||||
selected-node="{{selectedNode}}"
|
||||
highlighted-node="{{highlightedNode}}"
|
||||
are-health-pills-loading="[[areHealthPillsLoading]]"
|
||||
all-steps-mode-enabled="{{allStepsModeEnabled}}"
|
||||
specific-health-pill-step="{{specificHealthPillStep}}"
|
||||
health-pill-step-index="{{healthPillStepIndex}}">
|
||||
</tf-graph-debugger-data-card>
|
||||
</template>
|
||||
</template>
|
||||
<script>
|
||||
@ -185,13 +84,6 @@ h2 {
|
||||
type: Number,
|
||||
notify: true,
|
||||
},
|
||||
// Only relevant if we are in all steps mode, in which case the user may want to view health
|
||||
// pills for a specific step.
|
||||
specificHealthPillStep: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
notify: true,
|
||||
},
|
||||
colorBy: String,
|
||||
// Two-ways
|
||||
selectedNode: {
|
||||
@ -211,36 +103,6 @@ h2 {
|
||||
debuggerDataEnabled: Boolean,
|
||||
// Whether health pills are currently being loaded, in which case we show a spinner (and the
|
||||
// current health pills shown might be out of date).
|
||||
areHealthPillsLoading: Boolean,
|
||||
healthPillEntries: {
|
||||
type: Array,
|
||||
value: tf.graph.scene.healthPillEntries,
|
||||
readOnly: true,
|
||||
},
|
||||
healthPillValuesForSelectedNode: {
|
||||
type: Array,
|
||||
computed: '_computeHealthPillForNode(nodeNamesToHealthPills, healthPillStepIndex, selectedNode, allStepsModeEnabled, areHealthPillsLoading)',
|
||||
},
|
||||
// When all-steps mode is enabled, the user can request health pills for any step. In this
|
||||
// mode, Tensorboard makes a request every time the user drags the slider to a different step.
|
||||
allStepsModeEnabled: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
},
|
||||
// The biggest step value ever seen. Used to determine what steps of health pills to let the
|
||||
// user fetch in all steps mode.
|
||||
_biggestStepEverSeen: {
|
||||
type: Number,
|
||||
computed: '_computeBiggestStepEverSeen(nodeNamesToHealthPills)',
|
||||
},
|
||||
_maxStepIndex: {
|
||||
type: Number,
|
||||
computed: '_computeMaxStepIndex(nodeNamesToHealthPills)',
|
||||
},
|
||||
_currentStepDisplayValue: {
|
||||
type: String,
|
||||
computed: '_computeCurrentStepDisplayValue(nodeNamesToHealthPills, healthPillStepIndex, allStepsModeEnabled, specificHealthPillStep, areHealthPillsLoading)',
|
||||
},
|
||||
},
|
||||
listeners: {
|
||||
'node-list-item-click': '_nodeListItemClicked',
|
||||
@ -262,92 +124,6 @@ h2 {
|
||||
// the user may desire to change steps, and the slider must show for the user to do that.
|
||||
return debuggerDataEnabled && nodeNamesToHealthPills;
|
||||
},
|
||||
_computeTensorCountString: function(healthPillValuesForSelectedNode, valueIndex) {
|
||||
if (!healthPillValuesForSelectedNode) {
|
||||
// No health pill data is available.
|
||||
return '';
|
||||
}
|
||||
|
||||
return healthPillValuesForSelectedNode[valueIndex].toFixed(0);
|
||||
},
|
||||
_computeHealthPillForNode: function(
|
||||
nodeNamesToHealthPills, healthPillStepIndex, selectedNode, allStepsModeEnabled, areHealthPillsLoading) {
|
||||
if (areHealthPillsLoading) {
|
||||
// Health pills are loading. Do not render data that is out of date.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!selectedNode) {
|
||||
// No node is selected.
|
||||
return null;
|
||||
}
|
||||
|
||||
const healthPills = nodeNamesToHealthPills[selectedNode];
|
||||
if (!healthPills) {
|
||||
// This node lacks a health pill.
|
||||
return null;
|
||||
}
|
||||
|
||||
// If all steps mode is enabled, we use the first health pill in the list because the JSON
|
||||
// response from the server is a mapping between node name and a list of 1 health pill.
|
||||
const healthPill = healthPills[allStepsModeEnabled ? 0 : healthPillStepIndex];
|
||||
if (!healthPill) {
|
||||
// This node lacks a health pill at the current step.
|
||||
return null;
|
||||
}
|
||||
|
||||
// The health pill count values start at 2. Each health pill contains 6 values.
|
||||
return healthPill.value.slice(2, 8);
|
||||
},
|
||||
_computeCurrentStepDisplayValue: function(
|
||||
nodeNamesToHealthPills,
|
||||
healthPillStepIndex,
|
||||
allStepsModeEnabled,
|
||||
specificHealthPillStep,
|
||||
areHealthPillsLoading) {
|
||||
if (allStepsModeEnabled) {
|
||||
// The user seeks health pills for specific step from the server.
|
||||
return specificHealthPillStep.toFixed(0);
|
||||
}
|
||||
|
||||
if (areHealthPillsLoading) {
|
||||
// The current step is undefined.
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (let nodeName in nodeNamesToHealthPills) {
|
||||
// All nodes have the same number of steps stored, so only examine 1 node. We cannot
|
||||
// directly index into the nodeNamesToHealthPills object because we do not have a key.
|
||||
// If all steps mode is enabled, we only have 1 step to show.
|
||||
return nodeNamesToHealthPills[nodeName][healthPillStepIndex].step.toFixed(0);
|
||||
}
|
||||
|
||||
// The current step could not be computed.
|
||||
return 0;
|
||||
},
|
||||
_computeBiggestStepEverSeen: function(nodeNamesToHealthPills) {
|
||||
for (let nodeName in nodeNamesToHealthPills) {
|
||||
// All nodes have the same number of steps stored, so only examine 1 node.
|
||||
// The index is 1 less than the count. Tensorboard backend logic guarantees that the length
|
||||
// of the array will be greater than 1.
|
||||
var healthPills = nodeNamesToHealthPills[nodeName];
|
||||
return Math.max(this._biggestStepEverSeen, healthPills[healthPills.length - 1].step);
|
||||
}
|
||||
|
||||
// No steps seen so far. Default to 0.
|
||||
return this._biggestStepEverSeen || 0;
|
||||
},
|
||||
_computeMaxStepIndex: function(nodeNamesToHealthPills) {
|
||||
for (let nodeName in nodeNamesToHealthPills) {
|
||||
// All nodes have the same number of steps stored, so only examine 1 node.
|
||||
// The index is 1 less than the count. Tensorboard backend logic guarantees that the length
|
||||
// of the array will be greater than 1.
|
||||
return nodeNamesToHealthPills[nodeName].length - 1;
|
||||
}
|
||||
|
||||
// Return a falsy value. The slider should be hidden.
|
||||
return 0;
|
||||
},
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
Loading…
Reference in New Issue
Block a user