Browse Source

ui support gpu profiling step trace

tags/v1.1.0
ph 5 years ago
parent
commit
c945c10ee1
3 changed files with 547 additions and 14 deletions
  1. +4
    -0
      mindinsight/ui/src/router.js
  2. +536
    -7
      mindinsight/ui/src/views/profiling-gpu/profiling-dashboard.vue
  3. +7
    -7
      mindinsight/ui/src/views/profiling/profiling-dashboard.vue

+ 4
- 0
mindinsight/ui/src/router.js View File

@@ -119,6 +119,10 @@ export default new Router({
path: 'operator',
component: () => import('./views/profiling-gpu/operator.vue'),
},
{
path: 'step-trace',
component: () => import('./views/profiling/step-trace.vue'),
},
],
},
{


+ 536
- 7
mindinsight/ui/src/views/profiling-gpu/profiling-dashboard.vue View File

@@ -16,19 +16,91 @@ limitations under the License.
<template>
<div class="pro-router-wrap">
<div class="pro-router-left">
<!-- Timeline display area -->
<!-- Step trace area -->
<div class="step-trace">
<div class="title-wrap">
<div class="title">{{ $t('profiling.stepTrace') }}</div>
<div class="view-detail">
<button @click="viewDetail('step-trace')"
:disabled="svg.noData && svg.data.length === 0"
:class="{disabled:svg.noData && svg.data.length === 0}">{{ $t('profiling.viewDetail') }}
<i class="el-icon-d-arrow-right"></i></button>
</div>
<!-- Step trace description -->
<div class="tip-icon">
<el-tooltip placement="bottom"
effect="light">
<div slot="content"
class="tooltip-container">
<div class="font-size-style">{{$t("profiling.features")}}</div>
<div>{{$t('profiling.iterationInfo')}}</div>
<div>
<span class="font-style">{{$t('profiling.queueInfo')}}&nbsp;</span>
<span>{{$t('profiling.iterationGapInfo')}}</span>
</div>
<div>
<span class="font-style">{{$t('profiling.fpbpTitle')}}&nbsp;</span>
<span>{{$t('profiling.fpbpInfo')}}</span>
</div>
<div>
<span class="font-style">{{$t('profiling.iterativeTailingTitle')}}&nbsp;</span>
<span>{{$t('profiling.iterativeTailingInfo')}}</span>
</div>
<br />
<div class="font-size-style">{{$t('profiling.statistics')}}</div>
<div>{{$t('profiling.totalTime')}}
<span>{{totalTime}}{{$t('profiling.millisecond')}}</span>
</div>
<div>{{$t('profiling.totalSteps')}}<span>{{totalSteps}}</span></div>
<div>{{$t('profiling.iterationGapTimeRatio')}}<span>{{iterationIntervalPercent}}</span></div>
<div>{{$t('profiling.fpbpTimeRatio')}}<span>{{fpBpPercent}}</span></div>
<div>{{$t('profiling.iterativeTailingTimeRatio')}}<span>{{tailPercent}}</span></div>
</div>
<i class="el-icon-info"></i>
</el-tooltip>
</div>
</div>
<!-- Timeline SVG container -->
<!-- Step trace SVG container -->
<div class="trace-container">
<div class="image-noData">
<div id="trace"
class="training-trace"
:style="{height: svg.totalHeight + 'px'}">
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg"
height="100%"
width="100%">
<defs>
<marker id="marker_end"
refX="5"
refY="4"
markerWidth="10"
markerHeight="8"
orient="auto">
<path d="M1,1 L1,7 L9,4 z"
fill="#6c7280"
stroke="#6c7280"></path>
</marker>
<marker id="marker_start"
refX="5"
refY="4"
markerWidth="10"
markerHeight="8"
orient="auto">
<path d="M9,1 L9,7 L1,4 z"
fill="#6c7280"
stroke="#6c7280"></path>
</marker>
</defs>
</svg>
</div>
<div class="image-noData"
v-if="svg.noData">
<div>
<img :src="require('@/assets/images/coming-soon.png')"
<img :src="require('@/assets/images/nodata.png')"
alt="" />
</div>
<p>{{$t("public.stayTuned")}}</p>
<p v-show="!svg.initOver">{{$t("public.dataLoading")}}</p>
<p v-show="svg.initOver">{{$t("public.noData")}}</p>
</div>
</div>
</div>
@@ -315,9 +387,43 @@ export default {
trainingJobId: this.$route.query.id, // Training job id
summaryPath: this.$route.query.dir, // Summary path data
relativePath: this.$route.query.path, // Relative path of summary log
currentCard: '', // Data of current card
currentCard: '', // current device card
queueInfoShow: false, // Whether to show queue information
deviceInfoShow: false, // Whether to show device information
fpBpPercent: '--', // Ratio of time consumed by forward and backward propagation
iterationIntervalPercent: '--', // Ratio of time consumed by step interval
totalSteps: '--',
totalTime: '--',
tailPercent: '--', // Ratio of time consumed by step tail
svg: {
// Step trace svg information
data: [], // Data of svg
svgPadding: 20, // Padding of svg
totalWidth: 0, // Total width of svg
totalTime: 0,
cellHeight: 40,
cellPadding: 0,
rowPadding: 20,
rowMargin: 10,
totalHeight: 0,
markerPadding: 4,
minRate: 0.1, // Minimum time share threshold of non wrapping display
minTime: 0, // Minimum time for non wrapping display
minWidth: 1, // Minimum width of graphics in SVG
fontSize: 12,
textMargin: 21, // The minimum margin of the text from the border
namespaceURI: 'http://www.w3.org/2000/svg', // XML namespace
resizeTimer: null, // Response delay of resize event
colors: {
// Colors of different types of data presentation
iteration_interval: ['#A6DD82', '#edf8e6'],
fp_and_bp: ['#6CBFFF', '#e2f2ff'],
tail: ['#fa8e5b', '#fff4de'],
stream_parallel: ['#01a5a7', '#cceded'],
},
noData: true,
initOver: false,
},
processSummary: {
// Data of process summary
noData: true,
@@ -361,12 +467,18 @@ export default {
},
};
},
mounted() {},
mounted() {
setTimeout(() => {
this.$bus.$on('collapse', this.resizeTrace);
}, 500);
},
watch: {
// Monitor current card information
'$parent.curDashboardInfo': {
handler(newValue, oldValue) {
if (newValue.curCardNum === '') {
this.svg.noData = true;
this.svg.initOver = true;
this.pieChart.noData = true;
this.pieChart.initOver = true;
this.processSummary.initOver = true;
@@ -391,6 +503,7 @@ export default {
'profiling.profilingDashboard',
)}-MindInsight`;
}
this.svg.initOver = false;
this.pieChart.initOver = false;
this.processSummary.initOver = false;
this.timelineInfo.initOver = false;
@@ -409,6 +522,8 @@ export default {
this.queryTimeline();
this.initPieChart();
this.getProccessSummary();
this.queryTrainingTrace();
window.addEventListener('resize', this.resizeTrace, false);
},
/**
* Get the data of proccess summary
@@ -686,8 +801,407 @@ export default {
}
return Math.round(num * Math.pow(10, pow)) / Math.pow(10, pow);
},
/**
* Get the data of training trace
*/
queryTrainingTrace() {
const params = {
dir: this.relativePath,
type: 0,
device_id: this.currentCard,
};
RequestService.queryTrainingTrace(params).then(
(res) => {
this.svg.initOver = true;
if (res && res.data && res.data.training_trace_graph && res.data.training_trace_graph.length) {
this.svg.noData = false;
this.removeTrace();
this.$nextTick(() => {
this.packageTraceData(
JSON.parse(JSON.stringify(res.data.training_trace_graph)),
);
});

// Set the display information in tip
if (res.data.summary) {
this.fpBpPercent = res.data.summary.fp_and_bp_percent;
this.iterationIntervalPercent = res.data.summary.iteration_interval_percent;
this.totalSteps = res.data.summary.total_steps;
this.totalTime = res.data.summary.total_time;
this.tailPercent = res.data.summary.tail_percent;
} else {
this.fpBpPercent = '--';
this.iterationIntervalPercent = '--';
this.totalSteps = '--';
this.totalTime = '--';
this.tailPercent = '--';
}
} else {
this.svg.totalHeight = 0;
this.svg.noData = true;
this.svg.data = [];
this.svg.initOver = true;
this.removeTrace();
}
},
(error) => {
this.svg.totalHeight = 0;
this.svg.noData = true;
this.svg.data = [];
this.svg.initOver = true;
this.removeTrace();
},
);
},
/**
* Encapsulating the data of training trace
* @param {Object} traceGraph Data of training trace
*/
packageTraceData(traceGraph) {
this.svg.totalTime = 0;
this.svg.minTime = 0;
this.svg.totalHeight = 0;
const data = [];

if (traceGraph && traceGraph[0] && traceGraph[0][0]) {
this.svg.totalTime = traceGraph[0][0].duration;
this.svg.minTime = this.svg.minRate * this.svg.totalTime;

// If there is data less than the minimum time in each row,
// the data in each row is divided into several rows
traceGraph.forEach((row, index) => {
const rowObj = {
rowCount: 0,
data: [],
height: 0,
startY: this.svg.totalHeight,
};
let obj = [];
for (let i = 0; i < row.length; i++) {
if (row[i].duration < this.svg.minTime) {
if (obj.length) {
rowObj.data.push(obj);
obj = [];
rowObj.rowCount++;
}
rowObj.data.push([row[i]]);
rowObj.rowCount++;
} else {
obj.push(row[i]);
}

if (i === row.length - 1 && obj.length) {
rowObj.data.push(obj);
obj = [];
rowObj.rowCount++;
}
}
rowObj.height =
rowObj.rowCount * this.svg.cellHeight +
(rowObj.rowCount - 1) * this.svg.cellPadding +
(index ? this.svg.rowPadding * 2 : 0);

this.svg.totalHeight += rowObj.height + this.svg.rowMargin;
data.push(rowObj);
});

this.svg.totalHeight += this.svg.rowPadding;
this.svg.data = JSON.parse(JSON.stringify(data));

this.$nextTick(() => {
this.dealTraceData();
});
}
},
/**
* Processing the data of training trace, Control data generation svg
*/
dealTraceData() {
const traceDom = document.querySelector('#trace');
if (traceDom) {
this.svg.totalWidth = traceDom.offsetWidth - this.svg.svgPadding * 2;

if (this.svg.data[0] && this.svg.data[0].data.length) {
const svg = traceDom.querySelector('svg');
if (this.svg.totalTime) {
this.svg.data.forEach((item, index) => {
let itemDom = {};
if (index) {
itemDom = this.createMultipleRowContainer(item);
} else {
itemDom = this.createRowContainer(item.data, item.startY);
}
svg.appendChild(itemDom);
});
}
} else {
this.removeTrace();
}
}
},
/**
* Generate a container with multiple rows
* @param {Object} item Multi row data
* @return {Object} Generated DOM object
*/
createMultipleRowContainer(item) {
const rectContainer = document.createElementNS(
this.svg.namespaceURI,
'g',
);
rectContainer.setAttribute('class', 'container');

const rect = document.createElementNS(this.svg.namespaceURI, 'rect');
rect.setAttribute('x', this.svg.svgPadding);
rect.setAttribute('y', item.startY + this.svg.rowPadding);
rect.setAttribute('height', item.height);
rect.setAttribute('width', this.svg.totalWidth);
rect.setAttribute('style', 'fill:#edf0f5;stroke:#E2E2E2;stroke-width:1');
rectContainer.appendChild(rect);

const temp = this.createRowContainer(
item.data,
item.startY + this.svg.rowPadding,
);
rectContainer.appendChild(temp);
return rectContainer;
},
/**
* DOM for generating a single SVG image
* @param {Object} data Data of single SVG image
* @param {Number} startY Start y position of box
* @return {Object}
*/
createRowContainer(data, startY) {
const g = document.createElementNS(this.svg.namespaceURI, 'g');

data.forEach((row, index) => {
const y =
startY +
this.svg.rowPadding +
index * (this.svg.cellPadding + this.svg.cellHeight);
row.forEach((i) => {
if (i.duration) {
let temp;
if (i.name) {
temp = this.createRect(i, y);
g.insertBefore(temp, g.querySelector('g'));
} else {
temp = this.createArrow(i, y);
g.appendChild(temp);
}
}
});
});
return g;
},
/**
* Create a box DOM from the data
* @param {Object} data Data of single SVG image
* @param {Number} startY Start y position of box
* @return {Object}
*/
createRect(data, startY) {
const color =
data.name && this.svg.colors[data.name]
? this.svg.colors[data.name]
: this.svg.colors.stream_parallel;
// Start x position of box
const x1 =
(data.start / this.svg.totalTime) * this.svg.totalWidth +
this.svg.svgPadding;
// The width of the box
const width = Math.max(
this.svg.minWidth,
(data.duration / this.svg.totalTime) * this.svg.totalWidth,
);

// Contents of the box
let name = '';
switch (data.name) {
case 'iteration_interval':
name = this.$t('profiling.lterationGap');
break;
case 'fp_and_bp':
name = this.$t('profiling.deviceQueueOpTip');
break;
case 'tail':
name = this.$t('profiling.lterationTail');
break;
default:
name = data.name;
break;
}

const textContent = `${name}: ${this.toFixedFun(data.duration, 4)}ms`;
const textWidth = this.getTextWidth(textContent);
const normalSize = data.duration >= this.svg.minTime;

const g = document.createElementNS(this.svg.namespaceURI, 'g');
g.setAttribute('class', 'rect');

const rect = document.createElementNS(this.svg.namespaceURI, 'rect');
rect.setAttribute('x', x1);
rect.setAttribute('y', startY);
rect.setAttribute('height', this.svg.cellHeight);
rect.setAttribute('width', width);
rect.setAttribute('style', `fill:${color[1]};stroke:${color[0]};`);

const foreignObject = document.createElementNS(
this.svg.namespaceURI,
'foreignObject',
);
foreignObject.textContent = textContent;
foreignObject.setAttribute(
'x',
normalSize
? x1
: Math.min(
this.svg.svgPadding * 2 +
this.svg.totalWidth -
textWidth -
this.svg.textMargin,
Math.max(this.svg.textMargin, x1 + width / 2 - textWidth / 2),
),
);

foreignObject.setAttribute('y', startY);
foreignObject.setAttribute('height', this.svg.cellHeight);
foreignObject.setAttribute('width', width);
foreignObject.setAttribute('style', `color:${color[0]}`);
foreignObject.setAttribute(
'class',
`content${normalSize ? '' : ' content-mini'}`,
);

const title = document.createElementNS(this.svg.namespaceURI, 'title');
title.textContent = textContent;

g.appendChild(rect);
g.appendChild(foreignObject);
g.appendChild(title);
return g;
},
/**
* Create a arrow DOM from the data
* @param {Object} data Data of single SVG image
* @param {Number} startY Start y position of arrow
* @return {Object}
*/
createArrow(data, startY) {
const width = (data.duration / this.svg.totalTime) * this.svg.totalWidth;
const x1 =
(data.start / this.svg.totalTime) * this.svg.totalWidth +
this.svg.svgPadding;
const centerY = startY + this.svg.cellHeight / 2;

const g = document.createElementNS(this.svg.namespaceURI, 'g');
g.setAttribute('class', 'arrow');

const line = document.createElementNS(this.svg.namespaceURI, 'line');
line.setAttribute('y1', centerY);
line.setAttribute('y2', centerY);
line.setAttribute('style', 'stroke:#6c7280;stroke-width:1');
if (width > this.svg.markerPadding) {
line.setAttribute('x1', x1 + this.svg.markerPadding);
line.setAttribute('x2', x1 + width - this.svg.markerPadding);
line.setAttribute('marker-end', 'url(#marker_end)');
line.setAttribute('marker-start', 'url(#marker_start)');
} else {
line.setAttribute('x1', x1);
line.setAttribute('x2', x1 + width);
}

const text = document.createElementNS(this.svg.namespaceURI, 'text');
text.textContent = `${
data.duration === this.svg.totalTime
? this.$t('profiling.approximateTime')
: ''
}${this.toFixedFun(data.duration, 4)}ms`;
const textWidth = text.textContent
? this.getTextWidth(text.textContent)
: 0;
// The position of the text cannot go beyond the border of the SVG
text.setAttribute(
'x',
Math.min(
this.svg.svgPadding * 2 +
this.svg.totalWidth -
textWidth -
this.svg.textMargin,
Math.max(this.svg.textMargin, width / 2 + x1 - textWidth / 2),
),
);
text.setAttribute('y', centerY - this.svg.fontSize / 2);
text.setAttribute('font-size', this.svg.fontSize);
text.setAttribute('fill', 'black');

const startLine = document.createElementNS(this.svg.namespaceURI, 'line');
startLine.setAttribute('x1', x1);
startLine.setAttribute('y1', startY);
startLine.setAttribute('x2', x1);
startLine.setAttribute('y2', startY + this.svg.cellHeight);
startLine.setAttribute('style', 'stroke:#6c7280;stroke-width:1');
g.appendChild(startLine);

const endLine = document.createElementNS(this.svg.namespaceURI, 'line');
endLine.setAttribute('x1', x1 + width);
endLine.setAttribute('y1', startY);
endLine.setAttribute('x2', x1 + width);
endLine.setAttribute('y2', startY + this.svg.cellHeight);
endLine.setAttribute('style', 'stroke:#6c7280;stroke-width:1');
g.appendChild(endLine);
g.appendChild(line);
g.appendChild(text);
return g;
},
/**
* Gets the width of a string
* @param {String} text
* @return {Number}
*/
getTextWidth(text) {
const body = document.querySelector('body');
const temp = document.createElement('span');
temp.style['font-size'] = '12px';
temp.textContent = text;
body.appendChild(temp);
const textWidth = temp.offsetWidth;
body.removeChild(temp);
return textWidth;
},
/**
* Remove SVG DOM from page
*/
removeTrace() {
const svgDom = document.querySelector('#trace svg');
if (svgDom) {
const gDoms = svgDom.children;
if (gDoms) {
for (let i = 0; i < gDoms.length; i++) {
if (gDoms[i].nodeName === 'g') {
svgDom.removeChild(gDoms[i--]);
}
}
}
}
},
/**
* Respond to the reset event and update the page display
*/
resizeTrace() {
if (this.svg.resizeTimer) {
clearTimeout(this.svg.resizeTimer);
}
this.svg.resizeTimer = setTimeout(() => {
this.removeTrace();
this.dealTraceData();
this.svg.resizeTimer = null;
}, 500);
},
},
destroyed() {
window.removeEventListener('resize', this.resizeTrace, false);
this.$bus.$off('collapse');
},
};
@@ -793,6 +1307,21 @@ export default {
width: 100%;
height: calc(100% - 54px);
overflow: auto;
.training-trace {
position: relative;
height: 0;
.content {
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
line-height: 40px;
}
.content-mini {
overflow: visible;
}
}
}
}
.minddata {


+ 7
- 7
mindinsight/ui/src/views/profiling/profiling-dashboard.vue View File

@@ -16,7 +16,7 @@ limitations under the License.
<template>
<div class="pro-router-wrap">
<div class="pro-router-left">
<!-- Timeline display area -->
<!-- Step trace area -->
<div class="step-trace">
<div class="title-wrap">
<div class="title">{{ $t('profiling.stepTrace') }}</div>
@@ -26,7 +26,7 @@ limitations under the License.
:class="{disabled:svg.noData && svg.data.length === 0}">{{ $t('profiling.viewDetail') }}
<i class="el-icon-d-arrow-right"></i></button>
</div>
<!-- Timeline description -->
<!-- Step trace description -->
<div class="tip-icon">
<el-tooltip placement="bottom"
effect="light">
@@ -60,7 +60,7 @@ limitations under the License.
</el-tooltip>
</div>
</div>
<!-- Timeline SVG container -->
<!-- Step trace SVG container -->
<div class="trace-container">
<div id="trace"
class="training-trace"
@@ -387,8 +387,8 @@ export default {
return {
fpBpPercent: '--', // Ratio of time consumed by forward and backward propagation
iterationIntervalPercent: '--', // Ratio of time consumed by step interval
totalSteps: '--', // Total steps
totalTime: '--', // Total time
totalSteps: '--',
totalTime: '--',
tailPercent: '--', // Ratio of time consumed by step tail
queueInfoShow: false, // Whether to show queue information
deviceInfoShow: false, // Whether to show device information
@@ -397,7 +397,7 @@ export default {
data: [], // Data of svg
svgPadding: 20, // Padding of svg
totalWidth: 0, // Total width of svg
totalTime: 0, // Total time
totalTime: 0,
cellHeight: 40,
cellPadding: 0,
rowPadding: 20,
@@ -424,7 +424,7 @@ export default {
trainingJobId: this.$route.query.id, // Training job id
summaryPath: this.$route.query.dir, // Summary path data
relativePath: this.$route.query.path, // Relative path of summary log
currentCard: '', // Data of current card
currentCard: '', // current device card
pieChart: {
// Pie graph information of operators
chartDom: null,


Loading…
Cancel
Save