diff --git a/README.md b/README.md index 15906b9d..c1819e44 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ For the latest updates and features, please see [releases](https://github.com/te - Operation flow graph for a holistic view of model execution - Load reports via the local file system or through an SSH connection - Supports multiple instances of the application running concurrently +- BETA: Network-on-chip performance estimator (NPE) for Tenstorrent Tensix-based devices ### Demo @@ -68,6 +69,10 @@ https://github.com/user-attachments/assets/d00a2629-0bd1-4ee1-bb12-bd796d85221d |-----------------------------------------------|------------------------------------------| | Performance analysis | | +| NPE | | +|-----------------------------------------------|------------------------------------------| +| NPE | NPE | + ## Getting started How to [get started](./docs/getting-started.md) with TT-NN Visualizer. @@ -76,11 +81,11 @@ How to [get started](./docs/getting-started.md) with TT-NN Visualizer. Use [remote querying](./docs/remote-querying.md) instead of syncing the report data to your local file system. -## Sample models +## Sample reports You may test the application using the following sample reports. -Unzip the files into their own directories and select them with the local folder selector. +Unzip the files into their own directories and select them with the local folder selector, or load the NPE data on the `/npe` route. **Segformer encoder** [report](https://github.com/user-attachments/files/17996493/segformer_encoder.zip) @@ -89,8 +94,13 @@ Unzip the files into their own directories and select them with the local folder [report](https://github.com/user-attachments/files/17996491/segformer_decoder_good.zip) **Llama mlp** -[report](https://github.com/user-attachments/files/18129462/llama_mlp.zip) -[performance trace](https://github.com/user-attachments/files/18129457/llama_mlp_tracy.zip) +[report + performance trace](https://github.com/user-attachments/files/18770763/llama_attn_32l_10iter_30jan.zip) + +### NPE report + +**Llama decode** +[npe data](https://github.com/user-attachments/files/18772778/llama-decode-3B.zip) + ## Contributing diff --git a/backend/ttnn_visualizer/csv_queries.py b/backend/ttnn_visualizer/csv_queries.py index 4b119575..542b593d 100644 --- a/backend/ttnn_visualizer/csv_queries.py +++ b/backend/ttnn_visualizer/csv_queries.py @@ -1,13 +1,17 @@ # SPDX-License-Identifier: Apache-2.0 # # SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. - +import csv import os +import tempfile +from io import StringIO from pathlib import Path from typing import List, Dict, Union, Optional import pandas as pd +from tt_perf_report import perf_report +from ttnn_visualizer.exceptions import DataFormatError from ttnn_visualizer.models import TabSession from ttnn_visualizer.ssh_client import get_client @@ -538,3 +542,66 @@ def get_all_entries( return self.runner.execute_query( columns=self.PERF_RESULTS_COLUMNS, as_dict=as_dict, limit=limit ) + + +class OpsPerformanceReportQueries: + REPORT_COLUMNS = [ + "id", + "total_percent", + "bound", + "op_code", + "device_time", + "op_to_op_gap", + "cores", + "dram", + "dram_percent", + "flops", + "flops_percent", + "math_fidelity", + "output_datatype", + "input_0_datatype", + "input_1_datatype", + "dram_sharded", + "input_0_memory", + "inner_dim_block_size", + "output_subblock_h", + "output_subblock_w" + ] + + DEFAULT_SIGNPOST = None + DEFAULT_IGNORE_SIGNPOSTS = None + DEFAULT_MIN_PERCENTAGE = 0.5 + DEFAULT_ID_RANGE = None + DEFAULT_NO_ADVICE = False + + @classmethod + def generate_report(cls, session): + raw_csv = OpsPerformanceQueries.get_raw_csv(session) + csv_file = StringIO(raw_csv) + csv_output_file = tempfile.mktemp(suffix=".csv") + perf_report.generate_perf_report( + csv_file, + cls.DEFAULT_SIGNPOST, + cls.DEFAULT_IGNORE_SIGNPOSTS, + cls.DEFAULT_MIN_PERCENTAGE, + cls.DEFAULT_ID_RANGE, + csv_output_file, + cls.DEFAULT_NO_ADVICE, + ) + + report = [] + + try: + with open(csv_output_file, newline="") as csvfile: + reader = csv.reader(csvfile, delimiter=",") + next(reader, None) + for row in reader: + report.append({ + column: row[index] for index, column in enumerate(cls.REPORT_COLUMNS) + }) + except csv.Error as e: + raise DataFormatError() from e + finally: + os.unlink(csv_output_file) + + return report diff --git a/backend/ttnn_visualizer/exceptions.py b/backend/ttnn_visualizer/exceptions.py index 03d53b74..066cd2bc 100644 --- a/backend/ttnn_visualizer/exceptions.py +++ b/backend/ttnn_visualizer/exceptions.py @@ -34,3 +34,7 @@ def __init__(self, message, status): class DatabaseFileNotFoundException(Exception): pass + + +class DataFormatError(Exception): + pass diff --git a/backend/ttnn_visualizer/requirements.txt b/backend/ttnn_visualizer/requirements.txt index c263df9e..29965c43 100644 --- a/backend/ttnn_visualizer/requirements.txt +++ b/backend/ttnn_visualizer/requirements.txt @@ -1,4 +1,3 @@ - gunicorn~=22.0.0 uvicorn==0.30.1 paramiko~=3.4.0 @@ -17,6 +16,7 @@ wheel build PyYAML==6.0.2 python-dotenv==1.0.1 +tt-perf-report==1.0.0 # Dev dependencies mypy diff --git a/backend/ttnn_visualizer/views.py b/backend/ttnn_visualizer/views.py index 9b4d69ee..39365b2e 100644 --- a/backend/ttnn_visualizer/views.py +++ b/backend/ttnn_visualizer/views.py @@ -14,8 +14,9 @@ from flask import Blueprint, Response, jsonify from flask import request, current_app -from ttnn_visualizer.csv_queries import DeviceLogProfilerQueries, OpsPerformanceQueries +from ttnn_visualizer.csv_queries import DeviceLogProfilerQueries, OpsPerformanceQueries, OpsPerformanceReportQueries from ttnn_visualizer.decorators import with_session +from ttnn_visualizer.exceptions import DataFormatError from ttnn_visualizer.enums import ConnectionTestStates from ttnn_visualizer.exceptions import RemoteConnectionException from ttnn_visualizer.file_uploads import ( @@ -387,6 +388,20 @@ def get_profiler_perf_results_data_raw(session: TabSession): ) +@api.route("/profiler/perf-results/report", methods=["GET"]) +@with_session +def get_profiler_perf_results_report(session: TabSession): + if not session.profiler_path: + return Response(status=HTTPStatus.NOT_FOUND) + + try: + report = OpsPerformanceReportQueries.generate_report(session) + except DataFormatError: + return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY) + + return jsonify(report), 200 + + @api.route("/profiler/device-log/raw", methods=["GET"]) @with_session def get_profiler_data_raw(session: TabSession): diff --git a/docs/getting-started.md b/docs/getting-started.md index c69492af..3e5c8294 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -45,6 +45,12 @@ Consult the TT Metal documentation on [how to generate a performance trace](http Screenshot 2024-12-13 at 12 29 44 PM +### NPE + +Network-on-chip performance estimator data can be loaded separately on the `/npe` route. + +To generate this data for your model, refer to the [tt-npe documentation](https://github.com/tenstorrent/tt-npe). + ## Installing as a Python Wheel The application is designed to run on user local system and has python requirement of `3.12.3`. diff --git a/package.json b/package.json index c8270cdc..d3a43d45 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ttnn-visualzer", "private": true, - "version": "0.20.0", + "version": "0.21.0", "type": "module", "scripts": { "dev": "vite", diff --git a/pyproject.toml b/pyproject.toml index 98c47994..be8c3548 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "ttnn_visualizer" authors = [] -version = "0.20.0" +version = "0.21.0" description = "TT Visualizer" readme = "README.md" requires-python = ">=3.12" @@ -21,7 +21,8 @@ dependencies = [ "flask-socketio==5.4.1", "flask-sqlalchemy==3.1.1", "PyYAML==6.0.2", - "python-dotenv==1.0.1" + "python-dotenv==1.0.1", + "tt-perf-report==1.0.0" ] classifiers = [ diff --git a/src/components/DeviceOperations.tsx b/src/components/DeviceOperations.tsx deleted file mode 100644 index 23a1ce48..00000000 --- a/src/components/DeviceOperations.tsx +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// SPDX-FileCopyrightText: © 2024 Tenstorrent AI ULC - -import { Node } from '../model/APIData'; - -interface DeviceOperationsData { - deviceOperations: Node[]; -} - -function DeviceOperations({ deviceOperations }: DeviceOperationsData) { - return ( -
- - - - - - - - - - - {deviceOperations.map((op) => ( - - - - - - ))} - -
Device Operations
IDTypeParams
{op.id}{op.node_type}{JSON.stringify(op.params)}
-
- ); -} - -export default DeviceOperations; diff --git a/src/components/FooterInfobar.tsx b/src/components/FooterInfobar.tsx index 688672d6..30e67788 100644 --- a/src/components/FooterInfobar.tsx +++ b/src/components/FooterInfobar.tsx @@ -8,7 +8,13 @@ import { IconNames } from '@blueprintjs/icons'; import { useEffect, useState } from 'react'; import { useAtomValue } from 'jotai'; import { useLocation } from 'react-router'; -import { activePerformanceTraceAtom, activeReportAtom, operationRangeAtom, selectedRangeAtom } from '../store/app'; +import { + activePerformanceTraceAtom, + activeReportAtom, + operationRangeAtom, + performanceRangeAtom, + selectedOperationRangeAtom, +} from '../store/app'; import { useGetDeviceOperationListPerf } from '../hooks/useAPI'; import Range from './RangeSlider'; import ROUTES from '../definitions/Routes'; @@ -17,9 +23,10 @@ import 'styles/components/FooterInfobar.scss'; const MAX_TITLE_LENGTH = 20; function FooterInfobar() { - const [sliderIsOpen, setSliderIsOpen] = useState(true); - const selectedRange = useAtomValue(selectedRangeAtom); + const [sliderIsOpen, setSliderIsOpen] = useState(false); + const selectedRange = useAtomValue(selectedOperationRangeAtom); const operationRange = useAtomValue(operationRangeAtom); + const performanceRange = useAtomValue(performanceRangeAtom); const activeReport = useAtomValue(activeReportAtom); const activePerformanceTrace = useAtomValue(activePerformanceTraceAtom); const location = useLocation(); @@ -28,12 +35,22 @@ function FooterInfobar() { const isInSync = useGetDeviceOperationListPerfResult.length > 0; const isOperationDetails = location.pathname.includes(`${ROUTES.OPERATIONS}/`); + const isPerformanceRoute = location.pathname === ROUTES.PERFORMANCE; + const isNPE = location.pathname.includes(`${ROUTES.NPE}`); useEffect(() => { - if (isOperationDetails) { + if (isOperationDetails || isNPE) { setSliderIsOpen(false); } - }, [isOperationDetails]); + }, [isNPE, isOperationDetails]); + + const getSelectedRangeLabel = (): string | null => { + if (isPerformanceRoute) { + return performanceRange && `Selected Performance: ${performanceRange[0]} - ${performanceRange[1]}`; + } + + return selectedRange && `Selected: ${selectedRange[0]} - ${selectedRange[1]}`; + }; return (