面倒くさい作業をPythonにやらせてみた(NSight Systems測定結果の集計を楽にするツールの実装)

この記事は[Python Advent Calendar 2023](https://qiita.com/advent-calendar/2023/python) シリーズ3 21日目の投稿です。

# 概要
NVIDIAが開発、公開してるNSight Systemsというプロファイラーを利用して測定した結果を集計する時にExcelで集計しやすい形式に変換するツールを実装します。

# 事の起こり
業務でGPUアプリケーション(AIモデル)の性能測定を行っています。測定にはNSight Systemsを利用しています。このプロファイラーはなかなか優れ物で、機能も豊富、~~動作は鈍重~~、ということでGPUを利用するアプリケーションの性能測定には必須のツールです。GPUを搭載したサーバーで測定を実施後、自分の端末に結果ファイルをダウンロードしてGUIアプリケーション上で結果を見ている時に、CUDA GPU Kernel Summary等の数値を見たい時に、以下のウィンドウで数値を確認出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/49cdd290-15d2-7d6a-28af-f693be0a40dd.png)

しかし、これらの結果数値を集計したい場合に困ったことが有ります。測定結果をクリップボードにコピーしてExcelにペーストすることが出来るのですが、以下の測定結果例からも分かる様に数値に単位が付いた状態になっています。この状態ではExcelでの集計に難儀します。。。集計しやすいフォーマットに編集するツールをPythonで実装すれば集計が楽になるだろうと思いました。どこか遠くから「サーバー上でnsysコマンドで*.nsys-repファイルを読み込んで。。。」という声が聞こえてきますが、再度コマンドを叩くなど面倒くさいことは出来ませんので何も聞かなかったことにします。

```text:測定結果例
Time	Total Time	Instances	Avg	Med	Min	Max	StdDev	Name
13.8%	3.546 s	12657	280.184 μs	35.872 μs	20.896 μs	7.548 ms	615.988 μs	ampere_sgemm_128x128_nn
10.3%	2.628 s	4038	650.846 μs	110.624 μs	4.193 μs	2.162 ms	825.565 μs	void implicit_convolve_sgemm<float, float, (int)128, (int)5, (int)5, (int)3, (int)3, (int)3, (int)1, (bool)0, (bool)0, (bool)1>(int, int, int, const T1 *, int, T2 *, const T1 *, kernel_conv_params, unsigned long long, int, float, float, int, const T2 *, const T2 *, bool, int, int)
7.4%	1.894 s	2452	772.585 μs	654.246 μs	111.233 μs	17.555 ms	879.154 μs	cudnn_infer_ampere_scudnn_128x64_relu_small_nn_v1
6.8%	1.736 s	2449	709.030 μs	543.556 μs	125.697 μs	17.277 ms	799.326 μs	cudnn_infer_ampere_scudnn_128x128_relu_medium_nn_v1
6.4%	1.640 s	64	25.621 ms	4.978 ms	89.153 μs	344.814 ms	64.745 ms	void cudnn::cnn::conv2d_grouped_direct_kernel<(bool)0, (bool)1, (bool)0, (bool)0, (bool)0, (bool)0, (int)0, (int)0, int, float, float, float, float, float, float>(cudnn::cnn::GroupedDirectFpropParams, const T11 *, const T13 *, T12 *, T14, T14, const T14 *, const T14 *, const T12 *, const T15 *, cudnnActivationStruct)
6.2%	1.579 s	19680	80.226 μs	25.825 μs	1.920 μs	436.131 μs	101.918 μs	void tensorflow::functor::ShuffleInTensor3Simple<float, (int)2, (int)1, (int)0, (bool)0>(int, const T1 *, tensorflow::functor::Dimension<(int)3>, T1 *)
5.8%	1.475 s	101784	14.487 μs	4.128 μs	1.472 μs	782.022 μs	76.600 μs	Mul_GPU_DT_FLOAT_DT_FLOAT_kernel
4.2%	1.066 s	4030	264.429 μs	278.626 μs	15.712 μs	987.111 μs	66.267 μs	cudnn_infer_ampere_scudnn_winograd_128x128_ldg1_ldg4_relu_tile148t_nt_v1
```

# 環境情報
* Python 3.10.7
* pandas 2.1.4
* openpyxl 3.1.2

# 実装例
改良の余地は多々有りますが、一応所望の動作がするツールを実装出来たので、使いながら改良をしていくことにします。

```python
import pandas as pd


class NsysConverter:

    def __init__(self, file_path: str, sheet_name: str) -> None:
        self.file_path = file_path
        self.sheet_name = sheet_name

    def convert(self, column_name: str) -> None:
        print(f'File: {self.file_path}, Sheet: {self.sheet_name}, Column: {column_name}')
        file = pd.read_excel(self.file_path, sheet_name=self.sheet_name, header=0)
        col_edited = []
        for data in file[column_name]:
            col_edited.append(self._convert_num(data))
        col = f'{column_name}_edited'
        file[col] = pd.DataFrame(col_edited)
        file.to_excel(self.file_path, index=False)

    def _convert_num(self, data: str) -> float:
        value, unit = data.split()
        value = float(value)
        if unit == 's':
            return value
        elif unit == 'ms':
            return value * 0.001
        elif unit == 'μs':
            return value * 0.000001
        else:
            raise ValueError(f'Wrong value {value}!')


if __name__ == '__main__':
    from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
    parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
    parser.add_argument('--file_path', type=str, required=True, help='File path of Nsyight Systems')
    parser.add_argument('--sheet_name', type=str, default='Sheet1', help='Sheet name of result file')
    parser.add_argument('--column_name', type=str, required=True, help='Target column name for convert')
    args = parser.parse_args()
    nc = NsysConverter(args.file_path, args.sheet_name)
    nc.convert(args.column_name)
```

# 実行結果

以下のコマンドでツールを実行してみました。

```bash
python nsys_converter.py --file_path nsys_results.xlsx  --column_name "Total Time"
```

下図が実行結果です。改良したい部分は幾つか有りますが、今回はこれで一先ず完成としておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/25781156-42c1-5610-bdf5-8b053f1e3b87.png)


# まとめ
今回実装したツールは以下のGitHubで公開しています。

https://github.com/y-vectorfield/nsys_converter

# Reference

https://pandas.pydata.org/docs/reference/api/pandas.read_excel.html