jax-hpc-profiler 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,396 @@
1
+ import os
2
+ from typing import Dict, List, Optional, Tuple
3
+
4
+ import matplotlib.pyplot as plt
5
+ import numpy as np
6
+ import pandas as pd
7
+ from matplotlib.axes import Axes
8
+
9
+ def inspect_data(dataframes: Dict[str, pd.DataFrame]):
10
+ """
11
+ Inspect the dataframes.
12
+
13
+ Parameters
14
+ ----------
15
+ dataframes : Dict[str, pd.DataFrame]
16
+ Dictionary of method names to dataframes.
17
+ """
18
+ print("=" * 80)
19
+ print("Inspecting dataframes...")
20
+ print("=" * 80)
21
+ for method, df in dataframes.items():
22
+ print(f"Method: {method}")
23
+ inspect_df(df)
24
+ print("=" * 80)
25
+
26
+
27
+ def inspect_df(df: pd.DataFrame):
28
+ """
29
+ Inspect the dataframe.
30
+
31
+ Parameters
32
+ ----------
33
+ df : pd.DataFrame
34
+ The dataframe to inspect.
35
+ """
36
+ print(df.to_markdown())
37
+ print("-" * 80)
38
+
39
+
40
+ params_dict = {
41
+ "%pn%": "%plot_name%",
42
+ "%m%": "%method_name%",
43
+ "%n%": "%node%",
44
+ "%b%": "%backend%",
45
+ "%f%": "%function%",
46
+ "%cn%": "%column_name%",
47
+ "%pr%": "%precision%",
48
+ "%p%": "%decomposition%",
49
+ "%d%": "%data_size%",
50
+ "%g%": "%nb_gpu%"
51
+ }
52
+
53
+
54
+ def expand_label(label_template: str, params: dict) -> str:
55
+ """
56
+ Expand the label template with the provided parameters.
57
+
58
+ Parameters
59
+ ----------
60
+ label_template : str
61
+ The label template with placeholders.
62
+ params : dict
63
+ The dictionary with actual values to replace placeholders.
64
+
65
+ Returns
66
+ -------
67
+ str
68
+ The expanded label.
69
+ """
70
+ for key, value in params_dict.items():
71
+ label_template = label_template.replace(key, value)
72
+
73
+ for key, value in params.items():
74
+ label_template = label_template.replace(f"%{key}%", str(value))
75
+ return label_template
76
+
77
+
78
+ def plot_with_pdims_strategy(ax: Axes, df: pd.DataFrame, method: str,
79
+ pdims_strategy: List[str],
80
+ print_decompositions: bool, x_col: str,
81
+ y_col: str, label_template: str):
82
+ """
83
+ Plot the data based on the pdims strategy.
84
+
85
+ Parameters
86
+ ----------
87
+ ax : Axes
88
+ The axes to plot on.
89
+ df : pd.DataFrame
90
+ The dataframe to plot.
91
+ method : str
92
+ The method name.
93
+ backend : str
94
+ The backend name.
95
+ nodes_in_label : bool
96
+ Whether to include node names in labels.
97
+ pdims_strategy : List[str]
98
+ Strategy for plotting pdims.
99
+ print_decompositions : bool
100
+ Whether to print decompositions on the plot.
101
+ x_col : str
102
+ The column name for the x-axis values.
103
+ x_label : str
104
+ The label for the x-axis.
105
+ y_label : str
106
+ The label for the y-axis.
107
+ label_template : str
108
+ Template for plot labels with placeholders.
109
+ """
110
+ label_params = {
111
+ "plot_name": y_col,
112
+ "method_name": method,
113
+ "backend": df['backend'].values[0],
114
+ "node": df['nodes'].values[0],
115
+ "precision": df['precision'].values[0],
116
+ "function": df['function'].values[0],
117
+ }
118
+
119
+ if 'plot_fastest' in pdims_strategy:
120
+ df_decomp = df.groupby([x_col])
121
+
122
+ # Sort all and keep fastest
123
+ sorted_dfs = []
124
+ for _, group in df_decomp:
125
+ group.sort_values(by=[y_col], inplace=True, ascending=False)
126
+ sorted_dfs.append(group.iloc[0])
127
+ sorted_df = pd.DataFrame(sorted_dfs)
128
+ label_params.update({
129
+ "decomposition":
130
+ f"{group['px'].values[0]}x{group['py'].values[0]}"
131
+ })
132
+ label = expand_label(label_template, label_params)
133
+ ax.plot(sorted_df[x_col].values,
134
+ sorted_df[y_col],
135
+ marker='o',
136
+ linestyle='-',
137
+ label=label)
138
+ # TODO(wassim) : this is not working very well
139
+ if print_decompositions:
140
+ for j, (px, py) in enumerate(zip(sorted_df['px'],
141
+ sorted_df['py'])):
142
+ ax.annotate(
143
+ f"{px}x{py}",
144
+ (sorted_df[x_col].values[j], sorted_df[y_col].values[j]),
145
+ textcoords="offset points",
146
+ xytext=(0, 10),
147
+ ha='center',
148
+ color='red' if j == 0 else 'white')
149
+ return sorted_df[x_col].values, sorted_df[y_col].values
150
+
151
+ elif any(strategy in pdims_strategy
152
+ for strategy in ['plot_all', 'slab_yz', 'slab_xy', 'pencils']):
153
+ df_decomp = df.groupby(['decomp'])
154
+ x_values = []
155
+ y_values = []
156
+ for _, group in df_decomp:
157
+ group.drop_duplicates(subset=[x_col, 'decomp'],
158
+ keep='last',
159
+ inplace=True)
160
+ group.sort_values(by=[x_col], inplace=True, ascending=False)
161
+ # filter decomp based on pdims_strategy
162
+ if 'plot_all' not in pdims_strategy and group['decomp'].values[
163
+ 0] not in pdims_strategy:
164
+ continue
165
+
166
+ label_params.update({"decomposition": group['decomp'].values[0]})
167
+ label = expand_label(label_template, label_params)
168
+ ax.plot(group[x_col].values,
169
+ group[y_col],
170
+ marker='o',
171
+ linestyle='-',
172
+ label=label)
173
+ x_values.extend(group[x_col].values)
174
+ y_values.extend(group[y_col].values)
175
+ return x_values, y_values
176
+
177
+
178
+ def concatenate_csvs(root_dir: str, output_dir: str):
179
+ """
180
+ Concatenate CSV files and remove duplicates by GPU type.
181
+
182
+ Parameters
183
+ ----------
184
+ root_dir : str
185
+ Root directory containing CSV files.
186
+ output_dir : str
187
+ Output directory to save concatenated CSV files.
188
+ """
189
+ # Iterate over each GPU type directory
190
+ for gpu in os.listdir(root_dir):
191
+ gpu_dir = os.path.join(root_dir, gpu)
192
+
193
+ # Check if the GPU directory exists and is a directory
194
+ if not os.path.isdir(gpu_dir):
195
+ continue
196
+
197
+ # Dictionary to hold combined dataframes for each CSV file name
198
+ combined_dfs = {}
199
+
200
+ # List CSV in directory and subdirectories
201
+ for root, dirs, files in os.walk(gpu_dir):
202
+ for file in files:
203
+ if file.endswith('.csv'):
204
+ csv_file_path = os.path.join(root, file)
205
+ print(f'Concatenating {csv_file_path}...')
206
+ df = pd.read_csv(
207
+ csv_file_path,
208
+ header=None,
209
+ names=[
210
+ "function", "precision", "x", "y", "z", "px",
211
+ "py", "backend", "nodes", "jit_time", "min_time",
212
+ "max_time", "mean_time", "std_div", "last_time",
213
+ "generated_code", "argument_size", "output_size",
214
+ "temp_size", "flops"
215
+ ],
216
+ index_col=False)
217
+ if file not in combined_dfs:
218
+ combined_dfs[file] = df
219
+ else:
220
+ combined_dfs[file] = pd.concat(
221
+ [combined_dfs[file], df], ignore_index=True)
222
+
223
+ # Remove duplicates based on specified columns and save
224
+ for file_name, combined_df in combined_dfs.items():
225
+ combined_df.drop_duplicates(subset=[
226
+ "function", "precision", "x", "y", "z", "px", "py", "backend",
227
+ "nodes"
228
+ ],
229
+ keep='last',
230
+ inplace=True)
231
+
232
+ gpu_output_dir = os.path.join(output_dir, gpu)
233
+ if not os.path.exists(gpu_output_dir):
234
+ print(f"Creating directory {gpu_output_dir}")
235
+ os.makedirs(gpu_output_dir)
236
+
237
+ output_file = os.path.join(gpu_output_dir, file_name)
238
+ print(f"Writing file to {output_file}...")
239
+ combined_df.to_csv(output_file, index=False)
240
+
241
+
242
+ def clean_up_csv(
243
+ csv_files: List[str],
244
+ precisions: Optional[List[str]] = None,
245
+ function_names: Optional[List[str]] = None,
246
+ gpus: Optional[List[int]] = None,
247
+ data_sizes: Optional[List[int]] = None,
248
+ pdims: Optional[List[str]] = None,
249
+ pdims_strategy: List[str] = ['plot_fastest'],
250
+ backends: List[str] = ['MPI', 'NCCL', 'MPI4JAX'],
251
+ memory_units: str = 'KB',
252
+ ) -> Tuple[Dict[str, pd.DataFrame], set, set]:
253
+ """
254
+ Clean up and aggregate data from CSV files.
255
+
256
+ Parameters
257
+ ----------
258
+ csv_files : List[str]
259
+ List of CSV files to process.
260
+ precisions : Optional[List[str]], optional
261
+ Precisions to filter by, by default None.
262
+ function_names : Optional[List[str]], optional
263
+ Function names to filter by, by default None.
264
+ gpus : Optional[List[int]], optional
265
+ List of GPU counts to filter by, by default None.
266
+ data_sizes : Optional[List[int]], optional
267
+ List of data sizes to filter by, by default None.
268
+ pdims : Optional[List[str]], optional
269
+ List of pdims to filter by, by default None.
270
+ pdims_strategy : List[str], optional
271
+ Strategy for plotting pdims, by default ['plot_fastest'].
272
+ backends : List[str], optional
273
+ List of backends to filter by, by default ['MPI', 'NCCL', 'MPI4JAX'].
274
+ time_columns : List[str], optional
275
+ Time columns to use for aggregation, by default ['mean_time'].
276
+
277
+ Returns
278
+ -------
279
+ Dict[str, pd.DataFrame]
280
+ Dictionary of method names to aggregated dataframes.
281
+ """
282
+ dataframes = {}
283
+ available_gpu_counts = set()
284
+ available_data_sizes = set()
285
+ for csv_file in csv_files:
286
+ file_name = os.path.splitext(os.path.basename(csv_file))[0]
287
+ ext = os.path.splitext(os.path.basename(csv_file))[1]
288
+ if ext != '.csv':
289
+ print(f"Ignoring {csv_file} as it is not a CSV file")
290
+ continue
291
+
292
+ df = pd.read_csv(csv_file,
293
+ header=None,
294
+ skiprows=1,
295
+ names=[
296
+ "function", "precision", "x", "y", "z", "px",
297
+ "py", "backend", "nodes", "jit_time", "min_time",
298
+ "max_time", "mean_time", "std_div", "last_time",
299
+ "generated_code", "argument_size", "output_size",
300
+ "temp_size", "flops"
301
+ ],
302
+ dtype={
303
+ "function": str,
304
+ "precision": str,
305
+ "x": int,
306
+ "y": int,
307
+ "z": int,
308
+ "px": int,
309
+ "py": int,
310
+ "backend": str,
311
+ "nodes": int,
312
+ "jit_time": float,
313
+ "min_time": float,
314
+ "max_time": float,
315
+ "mean_time": float,
316
+ "std_div": float,
317
+ "last_time": float,
318
+ "generated_code": float,
319
+ "argument_size": float,
320
+ "output_size": float,
321
+ "temp_size": float,
322
+ "flops": float
323
+ },
324
+ index_col=False)
325
+
326
+ # Filter precisions
327
+ if precisions:
328
+ df = df[df['precision'].isin(precisions)]
329
+ # Filter function names
330
+ if function_names:
331
+ df = df[df['function'].isin(function_names)]
332
+ # Filter backends
333
+ df = df[df['backend'].isin(backends)]
334
+
335
+ # Filter data sizes
336
+ if data_sizes:
337
+ df = df[df['x'].isin(data_sizes)]
338
+
339
+ # Filter pdims
340
+ if pdims:
341
+ px_list, py_list = zip(*[map(int, p.split('x')) for p in pdims])
342
+ df = df[(df['px'].isin(px_list)) & (df['py'].isin(py_list))]
343
+
344
+ # convert memory units columns to remquested memory_units
345
+ match memory_units:
346
+ case 'KB':
347
+ factor = 1024
348
+ case 'MB':
349
+ factor = 1024**2
350
+ case 'GB':
351
+ factor = 1024**3
352
+ case 'TB':
353
+ factor = 1024**4
354
+ case _:
355
+ factor = 1
356
+
357
+ df['generated_code'] = df['generated_code'] / factor
358
+ df['argument_size'] = df['argument_size'] / factor
359
+ df['output_size'] = df['output_size'] / factor
360
+ df['temp_size'] = df['temp_size'] / factor
361
+ # in case of the same test is run multiple times, keep the last one
362
+ df = df.drop_duplicates(subset=[
363
+ "function", "precision", "x", "y", "z", "px", "py", "backend",
364
+ "nodes"
365
+ ],
366
+ keep='last')
367
+
368
+ df['gpus'] = df['px'] * df['py']
369
+
370
+ if gpus:
371
+ df = df[df['gpus'].isin(gpus)]
372
+
373
+ if 'plot_all' in pdims_strategy or 'slab_yz' in pdims_strategy or 'slab_xy' in pdims_strategy or 'pencils' in pdims_strategy:
374
+
375
+ def get_decomp_from_px_py(row):
376
+ if row['px'] > 1 and row['py'] == 1:
377
+ return 'slab_yz'
378
+ elif row['px'] == 1 and row['py'] > 1:
379
+ return 'slab_xy'
380
+ else:
381
+ return 'pencils'
382
+
383
+ df['decomp'] = df.apply(get_decomp_from_px_py, axis=1)
384
+ df.drop(columns=['px', 'py'], inplace=True)
385
+ if not 'plot_all' in pdims_strategy:
386
+ df = df[df['decomp'].isin(pdims_strategy)]
387
+ # check available gpus in dataset
388
+ available_gpu_counts.update(df['gpus'].unique())
389
+ available_data_sizes.update(df['x'].unique())
390
+
391
+ if dataframes.get(file_name) is None:
392
+ dataframes[file_name] = df
393
+ else:
394
+ dataframes[file_name] = pd.concat([dataframes[file_name], df])
395
+
396
+ return dataframes, available_gpu_counts, available_data_sizes