diff --git a/plugins/tensorboard-plugins/tb_plugin/README.md b/plugins/tensorboard-plugins/tb_plugin/README.md index 9397864c8b67243d064db809e9ac061d7390a107..7f50fc20ecdc72a6d22183fe32cb0a826f9a0e37 100644 --- a/plugins/tensorboard-plugins/tb_plugin/README.md +++ b/plugins/tensorboard-plugins/tb_plugin/README.md @@ -2,39 +2,42 @@ ### 介绍 此工具是PyTorch profiling数据以及可视化的TensorBoard的插件。 -它支持将Ascend平台采集、解析的Pytorch Profiling数据可视化呈现,也兼容GPU数据采集、解析可视化。同时集成了精度比对的功能,支持查看loss曲线和比对两个网络的loss收敛趋势。 +它支持将Ascend平台采集、解析的Pytorch Profiling数据可视化呈现,也兼容GPU数据采集、解析可视化,现已支持PyTorch 2.0GPU版本的profiling数据可视化。同时集成了精度比对的功能,支持查看loss曲线和比对两个网络的loss收敛趋势。 ### 快速安装说明 -1. 插件方式安装 +* 相关依赖: + pandas >= 1.0.0 ,tensorboard >= 2.11.0,protobuf <= 3.20.3 +* 安装方式 + 1. pip安装(推荐) + 现本插件已经上传到pypi社区,用户可在python环境下直接通过以下pip指令进行安装: + `pip install torch-tb-profiler-ascend` -* 插件下载地址 \ - 正式版:https://mindstudio-sample.obs.cn-north-4.myhuaweicloud.com/torch-tb-profiler-ascend/v0.4.0/torch_tb_profiler_ascend-0.4.0-py3-none-any.whl \ - 离线版:https://mindstudio-sample.obs.cn-north-4.myhuaweicloud.com/torch-tb-profiler-ascend/v0.4.0/offline/torch_tb_profiler_ascend-0.4.0-py3-none-any.whl + 2. 插件离线方式安装 -* 安装相关依赖: - pandas >= 1.0.0 ,tensorboard >= 2.11.0 + * 插件下载地址 \ + 正式版:https://mindstudio-sample.obs.cn-north-4.myhuaweicloud.com/torch-tb-profiler-ascend/v0.4.0/torch_tb_profiler_ascend-0.4.0-py3-none-any.whl \ + 离线版:https://mindstudio-sample.obs.cn-north-4.myhuaweicloud.com/torch-tb-profiler-ascend/v0.4.0/offline/torch_tb_profiler_ascend-0.4.0-py3-none-any.whl -* 插件形式为whl包,使用指令安装 + * 插件形式为whl包,使用指令安装 - `pip install torch-tb-profiler_npu_0.4.0_py3_none_any.whl` + `pip install torch-tb-profiler_npu_0.4.0_py3_none_any.whl` -2. 从源代码安装 + 3. 从源代码安装 -* 从仓库下载源码: + * 从仓库下载源码: - `git clone https://gitee.com/ascend/att.git` + `git clone https://gitee.com/ascend/att.git` -* 进入目录 `/plugins/tensorboard_plugins/tb_plugin` 下. + * 进入目录 `/plugins/tensorboard_plugins/tb_plugin` 下. -* 执行安装命令: + * 执行安装命令: + `pip install .` + * 构建whl包 + - `python setup.py build_fe sdist bdist_wheel` \ + 注意: build_fe步骤需要安装yarn和Node.js环境 + - `python setup.py sdist bdist_wheel` - `pip install .` -* 构建whl包 - - `python setup.py build_fe sdist bdist_wheel` \ - 注意: build_fe步骤需要安装yarn和Node.js环境 - - `python setup.py sdist bdist_wheel` - - 在 `/tb_plugins/profiling/tb_plugin/dist` 目录下取出whl包,使用方式1进行安装 + 在 `/tb_plugins/profiling/tb_plugin/dist` 目录下取出whl包,使用方式1进行安装 ### 解析数据说明 diff --git a/plugins/tensorboard-plugins/tb_plugin/fe/src/api/generated/api.ts b/plugins/tensorboard-plugins/tb_plugin/fe/src/api/generated/api.ts index bedc3abf901d9af3485f8994d73cf934eb03c2ab..b00601fba8852eeed9be052c6ed8adc106d49215 100644 --- a/plugins/tensorboard-plugins/tb_plugin/fe/src/api/generated/api.ts +++ b/plugins/tensorboard-plugins/tb_plugin/fe/src/api/generated/api.ts @@ -370,6 +370,50 @@ export interface Graph { */ rows: Array> } +/** + * + * @export + * @interface ValueAndTooltip + */ +export interface ValueAndTooltip { + /** + * + * @type {string | number} + * @memberof ValueAndTooltip + */ + value: string | number + /** + * + * @type {string} + * @memberof ValueAndTooltip + */ + tooltip?: string +} +/** + * + * @export + * @interface StepedGraph + */ +export interface StepedGraph { + /** + * + * @type {string} + * @memberof StepedGraph + */ + title?: string + /** + * + * @type {Array} + * @memberof StepedGraph + */ + columns: Array + /** + * + * @type {Array>} + * @memberof StepedGraph + */ + rows: Array> +} /** * * @export @@ -1147,10 +1191,10 @@ export interface Overview { environments: Array /** * - * @type {Graph} + * @type {StepedGraph} * @memberof Overview */ - steps: Graph + steps: StepedGraph /** * * @type {string} diff --git a/plugins/tensorboard-plugins/tb_plugin/fe/src/app.tsx b/plugins/tensorboard-plugins/tb_plugin/fe/src/app.tsx index 22888dbf0fa49d2550224a7b2c02ade93a4eafe0..c8cd2ddec26fee10f0a6d448a2051e749ae20696 100644 --- a/plugins/tensorboard-plugins/tb_plugin/fe/src/app.tsx +++ b/plugins/tensorboard-plugins/tb_plugin/fe/src/app.tsx @@ -215,7 +215,9 @@ export const App = () => { // #endregion React.useEffect(() => { - setup().then(() => { + setup().catch(() => { + console.log('google chart is not supported offline') + }).finally(() => { setLoaded(true) }) }, []) diff --git a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/DiffOverview.tsx b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/DiffOverview.tsx index 05c51fb2ffe02c41f001372ca4972c7f6b961c05..a6f6a15d9027d1915cd32d771af4ed5527df76ed 100644 --- a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/DiffOverview.tsx +++ b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/DiffOverview.tsx @@ -15,6 +15,7 @@ import * as React from 'react' import * as api from '../api' import { useResizeEventDependency } from '../utils/resize' import { FullCircularProgress } from './FullCircularProgress' +import * as echarts from 'echarts' const { Option } = Select @@ -51,6 +52,15 @@ const useStyles = makeStyles((theme) => ({ } })) +const getAngleByDataLength = (data: number) => { + if (data < 10) { + return 0 + } else { + // 数量越大越趋近于旋转90度 + return 90 * (1 - 10 / data) + } +} + export interface DiffColumnChartIProps { rawData: any[] selectCallback: (row: number, column: number) => void @@ -100,47 +110,115 @@ const DiffColumnChart: React.FC = ( right_accumulated_duration_max ) - var options = { - title: 'Execution Comparsion', - height: 500, - seriesType: 'bars', - series: { - 0: { type: 'bars', targetAxisIndex: 0 }, - 1: { type: 'bars', targetAxisIndex: 0 }, - 2: { type: 'line', targetAxisIndex: 1 }, - 3: { type: 'line', targetAxisIndex: 1 } + const chart = echarts.init(element) + + const options: echarts.EChartsOption = { + title: { + text: 'Execution Comparsion' + }, + legend: { + top: 10, + right: 10 + }, + tooltip: { + trigger: 'axis', + formatter: function (params: any) { + const index = params[0].name.indexOf('@') + var res = `${index > -1 ? params[0].name.slice(index + 1) : params[0].name}
` + for (const item of params) { + if (typeof item.value[item.encode.y[0]] === 'number') { + res += ` + + ${item.seriesName}: ${item.value[item.encode.y[0]]}
` + } + } + return res + } }, - vAxes: { - 0: { - logScale: false, - maxValue: duration_max + series: [ + { + type: 'bar', + itemStyle: { + color: '#3366cc' + }, + yAxisIndex: 0, + + }, + { + type: 'bar', + itemStyle: { + color: '#dc3912' + }, + yAxisIndex: 0 }, - 1: { - logScale: false, - maxValue: accumulated_max + { + type: 'line', + itemStyle: { + color: '#ff9900' + }, + yAxisIndex: 1 + }, + { + type: 'line', + itemStyle: { + color: '#109618' + }, + yAxisIndex: 1 + } + ], + xAxis: { + type: 'category', + axisLabel: { + interval: 0, + rotate: getAngleByDataLength(rawData.length), + formatter: (name: string) => { + const index = name.indexOf('@') + if (index > -1) { + name = name.slice(index + 1) + } + return name.length > 16 ? name.slice(0, 14) + "..." : name; + } } + }, + yAxis: [{ + type: 'value', + name: 'Time Difference(us)', + scale: true + }, { + type: 'value', + name: 'Accumulated Difference(us)', + scale: true + }], + dataset: { + source: rawData.map((item, idx) => { + // 添加索引保证x轴刻度不重复 + let param: any[] = [...item] + param[0] = `${idx}@${param[0]}` + return param + }) } } - const chart = new google.visualization.ComboChart(element) - const data = google.visualization.arrayToDataTable(rawData) - chart.draw(data, options) - - google.visualization.events.addListener(chart, 'select', (entry: any) => { - var selectedItem = chart.getSelection()[0] - if (selectedItem && selectedItem.hasOwnProperty('row')) { - selectCallback(selectedItem.row, selectedItem.column) + options && chart.setOption(options, true) + chart.on('click', (param) => { + if (param.seriesIndex !== undefined) { + selectCallback(param.dataIndex, param.seriesIndex + 1) } }) return () => { - chart.clearChart() + chart.dispose() } }, [rawData, resizeEventDependency]) return (
-
+
) } @@ -155,24 +233,93 @@ const DiffStepChart: React.FC = ( React.useLayoutEffect(() => { const element = graphRef.current if (!element) return - - var options = { - title: 'Execution Diff', - height: 500 + const chart = echarts.init(element) + const options: echarts.EChartsOption = { + title: { + text: 'Execution Diff' + }, + legend: { + top: 10, + right: 10 + }, + dataset: { + source: rawData.map((item, idx) => { + // 添加索引保证x轴刻度不重复 + let param: any[] = [...item] + param[0] = `${idx}@${param[0]}` + return param + }) + }, + xAxis: { + type: 'category', + axisLabel: { + interval: 0, + rotate: getAngleByDataLength(rawData.length), + formatter: (name: string) => { + const index = name.indexOf('@') + if (index > -1) { + name = name.slice(index + 1) + } + return name.length > 16 ? name.slice(0, 14) + "..." : name; + } + } + }, + yAxis: { + type: 'value', + scale: true + }, + tooltip: { + trigger: 'axis', + formatter: function (params: any) { + const index = params[0].name.indexOf('@') + var res = `${index > -1 ? params[0].name.slice(index + 1) : params[0].name}
` + for (const item of params) { + if (typeof item.value[item.encode.y[0]] === 'number') { + res += ` + + ${item.seriesName}: ${item.value[item.encode.y[0]]}
` + } + } + return res + } + }, + series: [ + { + type: 'line', + color: '#3366cc', + symbolSize: 0, + step: 'middle', + areaStyle: { + color: '#c1d1ef', + opacity: 1 + } + }, { + type: 'line', + color: '#dc3912', + symbolSize: 0, + step: 'middle', + areaStyle: { + color: '#f4c3b7', + opacity: 1 + } + } + ] } - const chart = new google.visualization.SteppedAreaChart(element) - const data = google.visualization.arrayToDataTable(rawData) - chart.draw(data, options) - + options && chart.setOption(options, true) return () => { - chart.clearChart() + chart.dispose() } }, [rawData, resizeEventDependency]) return (
-
+
) } diff --git a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/DistributedView.tsx b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/DistributedView.tsx index f742711b4aa3badbc923bb8a222b8ee74c4208aa..aad14aa29828fa1a8886ab3f68c54dd62cd396f9 100644 --- a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/DistributedView.tsx +++ b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/DistributedView.tsx @@ -10,12 +10,13 @@ import InputLabel from '@material-ui/core/InputLabel' import MenuItem from '@material-ui/core/MenuItem' import Select, { SelectProps } from '@material-ui/core/Select' import { makeStyles } from '@material-ui/core/styles' +import { Table } from 'antd' +import { ColumnsType } from 'antd/es/table' import * as React from 'react' import * as api from '../api' import { DistributedGraph, GpuInfo, Graph } from '../api' import { firstOrUndefined } from '../utils' import { ColumnChart } from './charts/ColumnChart' -import { TableChart } from './charts/TableChart' import { DataLoading } from './DataLoading' import { GpuInfoTable } from './GpuInfoTable' import { makeChartHeaderRenderer, useTooltipCommonStyles } from './helpers' @@ -49,6 +50,17 @@ const useStyles = makeStyles((theme) => ({ }, description: { marginLeft: theme.spacing(1) + }, + table: { + height: '100%', + border: '1px solid #efefef', + '& .ant-table-tbody > tr': { + height: 20, + fontSize: '10pt', + '& > td': { + padding: '0 8px!important' + } + } } })) @@ -79,6 +91,8 @@ export const DistributedView: React.FC = (props) => { const [overlapStep, setOverlapStep] = React.useState('') const [waittimeStep, setWaittimeStep] = React.useState('') const [commopsWorker, setCommopsWorker] = React.useState('') + const [columns, setColumns] = React.useState>([]) + const [pageSize, setPageSize] = React.useState(30) React.useEffect(() => { if (waittimeSteps.includes('all')) { @@ -153,10 +167,40 @@ export const DistributedView: React.FC = (props) => { ) const getTableData = (tableData?: any, worker?: string) => { - if (!tableData || !worker) return undefined - return tableData[worker] as Graph + if (!tableData || !worker) { + return [] + } + let dataInfo: api.Graph = tableData[worker] + const stringCompare = (a: string, b: string) => a.localeCompare(b) + const numberCompare = (a: number, b: number) => a - b + let column: any[] = dataInfo.columns.map(item => { + return { + title: item.name, + key: item.name, + dataIndex: item.name, + sorter: item.type == 'string' ? (a: any, b: any) => stringCompare(a[item.name], b[item.name]) + : (a: any, b: any) => numberCompare(a[item.name], b[item.name]) + } + }) + setColumns(column) + return dataInfo.rows.map((row, index) => { + if (row.length !== dataInfo.columns.length) { + return null + } + const dataRow: { [column: string]: number | string } = { key: index } + dataInfo.columns.forEach((column, index) => { + dataRow[column.name] = row[index] as string | number + }) + return dataRow + }) + } + const commopsTable: any[] = React.useMemo(() => { + return getTableData(commopsTableData, commopsWorker) + }, [commopsTableData, commopsWorker]) + + const onShowSizeChange = (current: number, size: number) => { + setPageSize(size) } - const commopsTable = getTableData(commopsTableData, commopsWorker) return (
@@ -217,7 +261,6 @@ export const DistributedView: React.FC = (props) => { )} - {(chartData) => ( @@ -286,9 +329,18 @@ export const DistributedView: React.FC = (props) => { - - {(graph) => } - + diff --git a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/ModuleView.tsx b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/ModuleView.tsx index 5b8ce929124e6b1d15af7e09837e828f9da9d521..396188aba4e69cced5208ff4af86631bf02e172c 100644 --- a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/ModuleView.tsx +++ b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/ModuleView.tsx @@ -157,45 +157,47 @@ export const ModuleView: React.FC = (props) => { if (cardRef.current) { setCardWidth(cardRef.current.offsetWidth - 10) } - if (timelineRef.current) { - defaultApi.treeGet(run, worker, span).then((resp) => { - if (resp) { - const data = new google.visualization.DataTable() - data.addColumn({ type: 'string', id: 'Layer' }) - data.addColumn({ type: 'string', id: 'Name' }) - data.addColumn({ type: 'string', role: 'tooltip' }) - data.addColumn({ type: 'number', id: 'Start' }) - data.addColumn({ type: 'number', id: 'End' }) - - let timeline_data: any[] = [] - getOperatorTree(0, resp, timeline_data) - timeline_data.sort((a, b) => a.level - b.level) - const max_level = timeline_data[timeline_data.length - 1].level - timeline_data.forEach((d) => { - data.addRow([ - d.level.toString(), - d.name, - `${d.name} Duration: ${d.end - d.start} us`, - d.start / 1000.0, // the time unit is us returned from server, but the google charts only accept milliseconds here - d.end / 1000.0 - ]) - }) + try { + if (timelineRef.current) { + defaultApi.treeGet(run, worker, span).then((resp) => { + if (resp) { + const data = new google.visualization.DataTable() + data.addColumn({ type: 'string', id: 'Layer' }) + data.addColumn({ type: 'string', id: 'Name' }) + data.addColumn({ type: 'string', role: 'tooltip' }) + data.addColumn({ type: 'number', id: 'Start' }) + data.addColumn({ type: 'number', id: 'End' }) - const chart = new google.visualization.Timeline(timelineRef.current) + let timeline_data: any[] = [] + getOperatorTree(0, resp, timeline_data) + timeline_data.sort((a, b) => a.level - b.level) + const max_level = timeline_data[timeline_data.length - 1].level + timeline_data.forEach((d) => { + data.addRow([ + d.level.toString(), + d.name, + `${d.name} Duration: ${d.end - d.start} us`, + d.start / 1000.0, // the time unit is us returned from server, but the google charts only accept milliseconds here + d.end / 1000.0 + ]) + }) - // console.info(timeline_data) - const options = { - height: (max_level + 1) * 50, - tooltip: { - isHtml: true - }, - timeline: { - showRowLabels: false + const chart = new google.visualization.Timeline(timelineRef.current) + const options = { + height: (max_level + 1) * 50, + tooltip: { + isHtml: true + }, + timeline: { + showRowLabels: false + } } + chart.draw(data, options) } - chart.draw(data, options) - } - }) + }) + } + } catch (e) { + console.warn('Timeline in module view is not supported offline.') } }, [run, worker, span]) diff --git a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/Overview.tsx b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/Overview.tsx index afa36801af79bfb0444e8d31d339203ed768a05e..e5f6f17bdaae3d276f24ed24f3566fc994fec0ad 100644 --- a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/Overview.tsx +++ b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/Overview.tsx @@ -7,11 +7,12 @@ import CardContent from '@material-ui/core/CardContent' import CardHeader from '@material-ui/core/CardHeader' import Grid from '@material-ui/core/Grid' import { makeStyles } from '@material-ui/core/styles' +import { Table } from 'antd' +import { ColumnsType } from 'antd/es/table' import * as React from 'react' import * as api from '../api' import { PieChart } from './charts/PieChart' import { SteppedAreaChart } from './charts/SteppedAreaChart' -import { TableChart } from './charts/TableChart' import { DataLoading } from './DataLoading' import { makeChartHeaderRenderer, useTooltipCommonStyles } from './helpers' import { TextListItem } from './TextListItem' @@ -48,19 +49,20 @@ const useStyles = makeStyles((theme) => ({ }, topGraph: { height: topGraphHeight + 40 + }, + table: { + height: '100%', + border: '1px solid #efefef', + '& .ant-table-tbody > tr': { + height: 20, + fontSize: '10pt', + '& > td': { + padding: '0 8px!important' + } + } } })) -const highlightNoTopLevel = ( - row: number, - column: number, - cb: (key: string, value: any) => void -) => { - if (row !== 0) { - cb('style', 'background: #e0e0e0') - } -} - export interface IProps { run: string worker: string @@ -70,16 +72,43 @@ export interface IProps { export const Overview: React.FC = (props) => { const { run, worker, span } = props - const [steps, setSteps] = React.useState(undefined) + const [steps, setSteps] = React.useState(undefined) const [performances, setPerformances] = React.useState([]) const [environments, setEnvironments] = React.useState([]) const [gpuMetrics, setGpuMetrics] = React.useState< api.GpuMetrics | undefined >(undefined) const [recommendations, setRecommendations] = React.useState('') + const [columns, setColumns] = React.useState>([]) - const synthesizedTableGraph = React.useMemo(() => { - return transformPerformanceIntoTable(performances) + const tableRows = React.useMemo(() => { + let dataInfo: api.Graph = transformPerformanceIntoTable(performances) + if (dataInfo.columns.length < 3) { + return [] + } + const stringCompare = (a: string, b: string) => a.localeCompare(b) + const numberCompare = (a: number, b: number) => a - b + let column: any[] = dataInfo.columns.map(item => { + return { + title: item.name, + key: item.name, + dataIndex: item.name, + sorter: item.type == 'string' ? (a: any, b: any) => stringCompare(a[item.name], b[item.name]) + : (a: any, b: any) => numberCompare(a[item.name], b[item.name]) + } + }) + setColumns(column) + return dataInfo.rows.map((row, index) => { + if (row.length < 3) { + return null + } + return { + key: index, + [dataInfo.columns[0].name]: row[0], + [dataInfo.columns[1].name]: row[1], + [dataInfo.columns[2].name]: row[2] + } + }) }, [performances]) const synthesizedPieGraph = React.useMemo(() => { @@ -161,14 +190,15 @@ export const Overview: React.FC = (props) => { - - + ({ - root: { - height: 500 - } -})) - export interface ColumnChartData { legends: Array barLabels: Array @@ -29,59 +22,78 @@ export interface ColumnChartData { export const ColumnChart: React.FC = (props) => { const { title, units, colors, chartData } = props const { legends, barLabels, barHeights } = chartData - const classes = useStyles() const graphRef = React.useRef(null) const [resizeEventDependency] = useResizeEventDependency() + const getAngleByDataLength = (data: number) => { + if (data < 10) { + return 0 + } else { + // 数量越大越趋近于旋转90度 + return 90 * (1 - 10 / data) + } + } + React.useLayoutEffect(() => { const element = graphRef.current if (!element) return - const data = new google.visualization.DataTable() - data.addColumn({ - type: 'string', - label: 'Worker' - }) - legends.forEach((label) => { - data.addColumn({ - type: 'number', - label - }) + const chart = echarts.init(element) + const dataSource: Array> = [] + dataSource.push(['worker', ...legends]) + barHeights.forEach((item, index) => { + barLabels[index] !== undefined && dataSource.push([barLabels[index], ...item]) }) - const rows = barHeights.map((heights, i) => - [barLabels[i] as string | number].concat(heights) - ) - data.addRows(rows) - - const options = { - height: 500, - title, - isStacked: true, - legend: { position: 'bottom' }, - vAxis: { - title: units + const options: echarts.EChartsOption = { + title: { + text: title }, - tooltip: { isHtml: true }, - chartArea: { - left: '15%', - width: '80%', - top: title ? '10%' : '5%' + legend: { + bottom: 0 }, - colors + xAxis: { + type: 'category', + axisLabel: { + interval: 0, + rotate: getAngleByDataLength(barLabels.length), + formatter: (name: string) => { + const index = name.indexOf('@') + if (index > -1) { + name = name.slice(index + 1) + } + return name.length > 16 ? name.slice(0, 14) + "..." : name; + } + } + }, + yAxis: { + type: 'value', + name: units, + nameTextStyle: { + fontSize: 16 + } + }, + tooltip: { + trigger: 'item' + }, + dataset: { + source: dataSource + }, + series: Array(legends.length).fill({ + type: 'bar', + stack: 'samesign' + }), + } + if (colors) { + options.color = colors.slice(0, barLabels.length) } - const chart = new google.visualization.ColumnChart(element) - - chart.draw(data, options) - + options && chart.setOption(options, true) return () => { - chart.clearChart() + chart.dispose() } }, [title, chartData, resizeEventDependency]) return ( -
-
-
+
) } diff --git a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/charts/NewLineChart.tsx b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/charts/NewLineChart.tsx index 56f946f71ef4478ef2c1bd96a1f7da8248735b0f..c66bf58c3e17f6b2766d5c334d6a80e35ae59348 100644 --- a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/charts/NewLineChart.tsx +++ b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/charts/NewLineChart.tsx @@ -29,17 +29,10 @@ interface IProps { tag: string hAxisTitle?: string vAxisTitle?: string - explorerOptions?: object onSelectionChanged?: (start: number, end: number) => void record?: any } -const useStyles = makeStyles(() => ({ - root: { - height: (props: Pick) => props.height - } -})) - export const LineChart: React.FC = (props) => { const { graph, @@ -51,7 +44,6 @@ export const LineChart: React.FC = (props) => { onSelectionChanged, record } = props - const classes = useStyles({ height }) const graphRef = React.useRef(null) const [resizeEventDependency] = useResizeEventDependency() const [chartObj, setChartObj] = React.useState() @@ -62,7 +54,6 @@ export const LineChart: React.FC = (props) => { if (!element) return element.oncontextmenu = () => { return false } - echarts.init(element).dispose() let myChart = echarts.init(element) let option: echarts.EChartsOption = { @@ -367,6 +358,9 @@ export const LineChart: React.FC = (props) => { }) setChartObj(myChart) + return () => { + myChart.dispose() + } }, [graph, height, resizeEventDependency]) React.useEffect(() => { @@ -409,8 +403,6 @@ export const LineChart: React.FC = (props) => { }, [graph, record, chartObj]) return ( -
-
-
+
) } diff --git a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/charts/PieChart.tsx b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/charts/PieChart.tsx index adae3cb9e540db6a8dd60a61b7dc7b9397ee23c6..3290d253943f9095171a778cf4bc40c6d47d7d18 100644 --- a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/charts/PieChart.tsx +++ b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/charts/PieChart.tsx @@ -1,5 +1,22 @@ /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. + *-------------------------------------------------------------------------------------------- + * Copyright (c) 2023, Huawei Technologies. + * 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. + * + * Modifications: Offer offline supporting. *--------------------------------------------------------------------------------------------*/ import { makeStyles } from '@material-ui/core/styles' @@ -7,6 +24,7 @@ import * as React from 'react' import { Graph } from '../../api' import { value } from '../../utils' import { useResizeEventDependency } from '../../utils/resize' +import * as echarts from 'echarts' interface IProps { graph: Graph @@ -18,12 +36,6 @@ interface IProps { tooltip_mode?: string } -const useStyles = makeStyles(() => ({ - root: { - height: (props: IProps) => props.height ?? 300 - } -})) - const noLegendArea = { left: '5%', width: '90%', top: '5%', height: '90%' } const normalArea = { left: '5%', width: '95%' } const noTitleArea = { left: '5%', width: '95%', top: '10%', height: '80%' } @@ -38,7 +50,6 @@ export const PieChart: React.FC = (props) => { colors, tooltip_mode = 'both' } = props - const classes = useStyles(props) const graphRef = React.useRef(null) const [resizeEventDependency] = useResizeEventDependency() @@ -47,60 +58,95 @@ export const PieChart: React.FC = (props) => { const element = graphRef.current if (!element) return - const data = new google.visualization.DataTable() - graph.columns.forEach((column) => { - data.addColumn({ - type: column.type, - label: column.name, - role: column.role, - p: column.p - }) - }) + const chart = echarts.init(element) - const rows = + let totalValue = 0 + const rowsWithUniqueName: Array<{ name: string, value: number }> = top === undefined - ? graph.rows + ? graph.rows.map((item, index) => { + totalValue += item[1] as number + return { name: `${index}_${item[0]}`, value: item[1] as number } + }) : graph.rows - .sort((a, b) => (value(b[1]) as number) - (value(a[1]) as number)) - .slice(0, top) - data.addRows(rows) + .sort((a, b) => (value(b[1]) as number) - (value(a[1]) as number)) + .slice(0, top).map((item, index) => { + totalValue += item[1] as number + return { name: `${index}_${item[0]}`, value: item[1] as number } + }) - const options = { + const option: echarts.EChartsOption = { height, width: '100%', - title, - pieHole: 0.4, - tooltip: { trigger: 'selection', isHtml: true, text: tooltip_mode }, + title: { + text: title + }, + tooltip: { + trigger: 'item', + formatter: (data) => { + const typedData = data as echarts.DefaultLabelFormatterCallbackParams + const index = typedData.name.indexOf('_') + return `${index > -1 ? typedData.name.slice(index + 1) : + typedData.name}
${tooltip_mode === 'both' ? + typedData.value : ''}(${typedData.percent}%)` + }, + confine: true, + extraCssText: `max-width: 300px; + word-wrap:break-word; + white-space:pre-wrap; + padding-right: 10px` + }, chartArea: noLegend ? noLegendArea : !title ? noTitleArea : normalArea, - legend: noLegend ? 'none' : undefined, + legend: { + type: noLegend ? 'plain' : 'scroll', + orient: 'vertical', + left: 'right', + z: 10, + // Display at most 36 characters. + formatter: (name) => { + // Show legends for datas with the same name. + const index = name.indexOf('_') + if (index > -1) { + name = name.slice(index + 1) + } + return name.length > 36 ? name.slice(0, 34) + "..." : name; + }, + tooltip: { + show: true, + triggerOn: 'mousemove', + formatter: (data) => { + const currentItem = rowsWithUniqueName.find(item => item.name === data.name) + const index = data.name.indexOf('_') + const percent = ((currentItem?.value || 0) * 100 / totalValue).toFixed(2) + return `${index > -1 ? data.name.slice(index + 1) : data.name}
${tooltip_mode === 'both' ? + (currentItem?.value || 0) : ''}(${percent}%)` + } + } + }, sliceVisibilityThreshold: 0, - colors + colors, + series: [ + { + type: 'pie', + radius: ['32%', '80%'], + center: ['32%', '50%'], + label: { + position: 'inside', + formatter: `{d}%`, + color: '#ffffff' + }, + data: rowsWithUniqueName + } + ] } - const chart = new google.visualization.PieChart(element) - - google.visualization.events.addListener( - chart, - 'onmouseover', - function (entry: any) { - chart.setSelection([{ row: entry.row }]) - } - ) - - google.visualization.events.addListener(chart, 'onmouseout', function () { - chart.setSelection([]) - }) - - chart.draw(data, options) + option && chart.setOption(option, true) return () => { - chart.clearChart() + chart.dispose() } }, [graph, height, top, resizeEventDependency]) return ( -
-
-
+
) } diff --git a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/charts/SteppedAreaChart.tsx b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/charts/SteppedAreaChart.tsx index 6d2647878bb7623f9739c3106d516514e9d87238..bc38cc31747cd69e8fee7af4d55476f49bef9914 100644 --- a/plugins/tensorboard-plugins/tb_plugin/fe/src/components/charts/SteppedAreaChart.tsx +++ b/plugins/tensorboard-plugins/tb_plugin/fe/src/components/charts/SteppedAreaChart.tsx @@ -1,14 +1,32 @@ /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. + *-------------------------------------------------------------------------------------------- + * Copyright (c) 2023, Huawei Technologies. + * 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. + * + * Modifications: Offer offline supporting. *--------------------------------------------------------------------------------------------*/ import { makeStyles } from '@material-ui/core/styles' import * as React from 'react' -import { Graph } from '../../api' +import { StepedGraph } from '../../api' import { useResizeEventDependency } from '../../utils/resize' +import * as echarts from 'echarts' interface IProps { - graph: Graph + graph: StepedGraph height?: number hAxisTitle?: string vAxisTitle?: string @@ -30,46 +48,55 @@ export const SteppedAreaChart: React.FC = (props) => { const element = graphRef.current if (!element) return - const data = new google.visualization.DataTable() - graph.columns.forEach((column) => { - data.addColumn({ - type: column.type, - label: column.name, - role: column.role, - p: column.p - }) + const chart = echarts.init(element) + const dataSource: Array> = [] + dataSource.push(graph.columns) + graph.rows.forEach((row) => { + dataSource.push(row.map(item => item.value)) }) - data.addRows(graph.rows) - - const options = { - title: graph.title, - isStacked: true, - height, - legend: { position: 'bottom' }, - chartArea: { left: '15%', width: '80%', top: '10%' }, - connectSteps: false, - areaOpacity: 0.9, - tooltip: { isHtml: true }, - hAxis: { - title: hAxisTitle + const options: echarts.EChartsOption = { + title: { + text: graph.title + }, + legend: { + bottom: 0 + }, + xAxis: { + type: 'category', + name: hAxisTitle, + axisLabel: { + interval: 0, + } + }, + yAxis: { + type: 'value', + name: vAxisTitle }, - vAxis: { - title: vAxisTitle - } + tooltip: { + trigger: 'item', + formatter: (params: any) => { + return graph.rows[params.dataIndex][params.seriesIndex + 1]?.tooltip || '' + } + }, + dataset: { + source: dataSource + }, + series: Array(graph.columns.length - 1).fill({ + type: 'bar', + stack: 'samesign' + }) } - const chart = new google.visualization.SteppedAreaChart(element) - - chart.draw(data, options) + options && chart.setOption(options, true) return () => { - chart.clearChart() + chart.dispose() } }, [graph, height, resizeEventDependency]) return (
-
+
) } diff --git a/plugins/tensorboard-plugins/tb_plugin/setup.py b/plugins/tensorboard-plugins/tb_plugin/setup.py index d6306d0b4f86bb85f608bf1225de4cad231441ee..6a023c8243bdf6e57b6084a0cb9c341f94a991f6 100644 --- a/plugins/tensorboard-plugins/tb_plugin/setup.py +++ b/plugins/tensorboard-plugins/tb_plugin/setup.py @@ -84,8 +84,8 @@ setuptools.setup( version=get_version(os.path.join('torch_tb_profiler', '__init__.py')), description="PyTorch Ascend Profiler TensorBoard Plugin", long_description="PyTorch Ascend Profiler TensorBoard Plugin : \ - https://gitee.com/ascend/att/tree/master/tb_plugins/profiling/tb_plugin", - url="https://gitee.com/ascend/att/tree/master/tb_plugins/profiling/tb_plugin", + https://gitee.com/ascend/att/tree/master/plugins/tensorboard-plugins/tb_plugin", + url="https://gitee.com/ascend/att/tree/master/plugins/tensorboard-plugins/tb_plugin", author="Ascend Team", author_email="pmail_mindstudio@huawei.com", cmdclass={ diff --git a/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/__init__.py b/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/__init__.py index c94e71bf05f670cdbc1121ba1cec31150f68792b..077365c7e6aaf2392797eb50d0cd44b684320f26 100644 --- a/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/__init__.py +++ b/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/__init__.py @@ -4,4 +4,4 @@ # Entry point for Pytorch TensorBoard plugin package. -__version__ = '0.4.0' +__version__ = '0.4.0.2' diff --git a/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/plugin.py b/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/plugin.py index 6343d503ecda863ac9d0f3f07435a8dc135febab..6d431fa1a9146b203369ba632bec46c4cbc400e3 100644 --- a/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/plugin.py +++ b/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/plugin.py @@ -111,6 +111,7 @@ class TorchProfilerPlugin(base_plugin.TBPlugin): '/index.html': self.static_file_route, '/trace_viewer_full.html': self.static_file_route, '/trace_embedding.html': self.static_file_route, + '/trace_script.js': self.static_file_route, '/runs': self.runs_route, '/views': self.views_route, '/workers': self.workers_route, diff --git a/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/profiler/data.py b/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/profiler/data.py index a1eef7831c3131e65f594671a4b052dbfd6c6ce5..8c88f83dc7ad280c5af446e8f575ab7fc02c6a10 100644 --- a/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/profiler/data.py +++ b/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/profiler/data.py @@ -122,6 +122,7 @@ class RunProfileData(object): # npu memory data self.memory_operator_path: str = None + self.memory_curve_path: str = None self.memory_component_path: str = None self.start_ts: float = 0.0 @@ -173,10 +174,12 @@ class RunProfileData(object): profile.kernel_file_path = io.join(path, file) if str(file) == 'memory_record.csv': has_memory_record = True - profile.memory_component_path = io.join(path, file) + profile.memory_curve_path = io.join(path, file) if str(file) == 'operator_memory.csv': has_memory_operator = True profile.memory_operator_path = io.join(path, file) + if str(file) == 'npu_module_mem.csv': + profile.memory_component_path = io.join(path, file) if str(file) == 'operator_details.csv': profile.has_operator_view = True profile.operator_path = io.join(path, file) diff --git a/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/profiler/run_generator.py b/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/profiler/run_generator.py index c7cc94ecd3dd058d279a78c6f3cffdbd77277a81..f80c933f52ee5036ab4ca21a67456c72c4aed84e 100644 --- a/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/profiler/run_generator.py +++ b/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/profiler/run_generator.py @@ -101,8 +101,9 @@ class RunGenerator(object): if self.profile_data.has_memory: profile_run.views.append(consts.MEMORY_VIEW) profile_run.memory_div_curve = None - self.process_data, self.component_curve_data, peak_memory_events = self._handle_memory_data() + self.process_data, self.component_curve_data = self._handle_memory_data() profile_run.memory_all_curve = self._get_memory_all_curve() + peak_memory_events = self._handle_memory_component() profile_run.memory_events = self._get_memory_event(peak_memory_events) if self.profile_data.has_communication: @@ -565,18 +566,7 @@ class RunGenerator(object): def _handle_memory_data(self): process_data = defaultdict() pta_or_ge_data = defaultdict() - path = self.profile_data.memory_component_path - datas = RunGenerator._get_csv_data(path) - peak_memory_events = { - 'metadata': { - 'title': 'Component Peak Memory', - 'default_device': '', - }, - 'columns': [{'name': 'Component', 'type': 'string'}, - {'name': 'Peak Memory Usage(MB)', 'type': 'number'}, - {'name': 'Time(ms)', 'type': 'number'}] - } - peak_memory_rows = defaultdict(list) + datas = RunGenerator._get_csv_data(self.profile_data.memory_curve_path) required_column_idxs = { 'Component': -1, 'Device Type': -1, @@ -586,7 +576,7 @@ class RunGenerator(object): } (tag_type_idx, device_type_idx, time_idx, reserved_idx, allocated_idx), column_exist_count = \ RunGenerator._check_csv_columns(datas[0], required_column_idxs) - if column_exist_count < 5: + if column_exist_count < len(required_column_idxs): logger.error('Required column is missing in file "memory_record.csv"') else: for ls in datas[1:]: @@ -604,7 +594,34 @@ class RunGenerator(object): line_chart_data = [time_column, round(float(ls[allocated_idx]), 3), round(float(ls[reserved_idx]), 3)] pta_or_ge_data.setdefault(device_type, {}).setdefault(ls[tag_type_idx], []).append(line_chart_data) - else: + + return process_data, pta_or_ge_data + + def _handle_memory_component(self): + peak_memory_events = { + 'metadata': { + 'title': 'Component Peak Memory', + 'default_device': '', + }, + 'columns': [{'name': 'Component', 'type': 'string'}, + {'name': 'Peak Memory Reserved(MB)', 'type': 'number'}, + {'name': 'Time(ms)', 'type': 'number'}] + } + peak_memory_rows = defaultdict(list) + component_datas = RunGenerator._get_csv_data(self.profile_data.memory_component_path) + if component_datas: + required_column_idxs = { + 'Component': -1, + 'Timestamp(us)': -1, + 'Total Reserved(MB)': -1, + 'Device': -1 + } + (tag_type_idx, time_idx, reserved_idx, device_type_idx), column_exist_count = \ + RunGenerator._check_csv_columns(component_datas[0], required_column_idxs) + if column_exist_count < len(required_column_idxs): + logger.error('Required column is missing in file "npm_module_mem.csv"') + else: + for ls in component_datas[1:]: memory_curve_id_dict = { 'device_type_idx': device_type_idx, 'reserved_idx': reserved_idx, @@ -612,9 +629,8 @@ class RunGenerator(object): 'time_idx': time_idx } self._handle_peak_memory_rows(memory_curve_id_dict, ls, peak_memory_rows) - peak_memory_events['rows'] = peak_memory_rows - return process_data, pta_or_ge_data, peak_memory_events + return peak_memory_events def _handle_peak_memory_rows(self, memory_curve_id_dict, ls, peak_memory_rows): # Record the peak memory usage of other components. @@ -660,51 +676,39 @@ class RunGenerator(object): column_tootip = {'type': 'string', 'role': 'tooltip', 'p': {'html': 'true'}} data = {} data['steps'] = {} - data['steps']['columns'] = [{'type': 'string', 'name': 'Step'}] + data['steps']['columns'] = ['Step'] if show_gpu: - data['steps']['columns'].extend([{'type': 'number', 'name': 'Kernel'}, - column_tootip, - {'type': 'number', 'name': 'Memcpy'}, - column_tootip, - {'type': 'number', 'name': 'Memset'}, - column_tootip]) + data['steps']['columns'].extend(['Kernel', 'Memcpy', 'Memset']) if self.profile_data.has_communication: - data['steps']['columns'].extend([{'type': 'number', 'name': 'Communication'}, - column_tootip]) + data['steps']['columns'].append('Communication') if show_gpu: - data['steps']['columns'].extend([{'type': 'number', 'name': 'Runtime'}, - column_tootip]) - data['steps']['columns'].extend([{'type': 'number', 'name': 'DataLoader'}, - column_tootip, - {'type': 'number', 'name': 'CPU Exec'}, - column_tootip, - {'type': 'number', 'name': 'Other'}, - column_tootip]) + data['steps']['columns'].append('Runtime') + data['steps']['columns'].extend(['DataLoader', 'CPU Exec', 'Other']) data['steps']['rows'] = [] for i in range(len(self.profile_data.steps_costs)): costs = self.profile_data.steps_costs[i] step_name = self.profile_data.steps_names[i] - row = [step_name] + row = [{'value': step_name}] if show_gpu: - row.extend([costs.costs[ProfileRole.Kernel], - build_part_time_str(costs.costs[ProfileRole.Kernel], 'Kernel'), - costs.costs[ProfileRole.Memcpy], - build_part_time_str(costs.costs[ProfileRole.Memcpy], 'Memcpy'), - costs.costs[ProfileRole.Memset], - build_part_time_str(costs.costs[ProfileRole.Memset], 'Memset')]) + row.extend([{'value': costs.costs[ProfileRole.Kernel], + 'tooltip': build_part_time_str(costs.costs[ProfileRole.Kernel], 'Kernel')}, + {'value': costs.costs[ProfileRole.Memcpy], + 'tooltip': build_part_time_str(costs.costs[ProfileRole.Memcpy], 'Memcpy')}, + {'value': costs.costs[ProfileRole.Memset], + 'tooltip': build_part_time_str(costs.costs[ProfileRole.Memset], 'Memset')}]) if self.profile_data.has_communication: - row.extend([costs.costs[ProfileRole.Communication], - build_part_time_str(costs.costs[ProfileRole.Communication], 'Communication')]) + row.append({'value': costs.costs[ProfileRole.Communication], + 'tooltip': build_part_time_str(costs.costs[ProfileRole.Communication], 'Communication')}) if show_gpu: - row.extend([costs.costs[ProfileRole.Runtime], - build_part_time_str(costs.costs[ProfileRole.Runtime], 'Runtime')]) - row.extend([costs.costs[ProfileRole.DataLoader], - build_part_time_str(costs.costs[ProfileRole.DataLoader], 'DataLoader'), - costs.costs[ProfileRole.CpuOp], - build_part_time_str(costs.costs[ProfileRole.CpuOp], 'CPU Exec'), - costs.costs[ProfileRole.Other], - build_part_time_str(costs.costs[ProfileRole.Other], 'Other')]) + row.append({'value': costs.costs[ProfileRole.Runtime], + 'tooltip': build_part_time_str(costs.costs[ProfileRole.Runtime], 'Runtime')}) + row.extend([{'value': costs.costs[ProfileRole.DataLoader], + 'tooltip': build_part_time_str(costs.costs[ProfileRole.DataLoader], 'DataLoader')}, + {'value': costs.costs[ProfileRole.CpuOp], + 'tooltip': build_part_time_str(costs.costs[ProfileRole.CpuOp], 'CPU Exec')}, + {'value': costs.costs[ProfileRole.Other], + 'tooltip': build_part_time_str(costs.costs[ProfileRole.Other], 'Other')}]) data['steps']['rows'].append(row) avg_costs = [] diff --git a/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/static/trace_embedding.html b/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/static/trace_embedding.html index 7b5c2bb65631ed59a148d9b1e5ee98e2b7a0af96..bb84da0d0c0cb92d51a2d6ab1cb92ce308b23241 100644 --- a/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/static/trace_embedding.html +++ b/plugins/tensorboard-plugins/tb_plugin/torch_tb_profiler/static/trace_embedding.html @@ -6,7 +6,7 @@ found in the LICENSE file. --> - +