Skip to content

Commit 9b5758f

Browse files
authored
Fix Inspector plot crash on non-finite metrics (pytorch#23400)
### Summary Follow-up to pytorch#23276. While testing that one I noticed `compare_results(plot=True)` (and so `inspector_cli --compare_results`) still crashes when a metric isn't finite. SNR is inf when the run output matches the reference exactly, and -inf when the reference is all zeros. `plot_metric` then passes that into `plt.ylim` and matplotlib raises `ValueError: Axis limits cannot be NaN or Inf`. So the plot fails in the best case, when outputs match perfectly. This plots non-finite values as 0 (same as we do for None), while the bar label still shows the real value (`inf`, `-inf`, `nan`). Also fixed the `plot_metric` type hint to `List[Optional[float]]` to match what compare_results passes in. ### Test plan Added test_compare_results_plot_non_finite_metrics in devtools/inspector/tests/inspector_utils_test.py. It covers identical tensors (SNR inf) and an all-zero reference (SNR -inf) with plot=True. It fails on main with the ValueError above and passes with the fix, and the rest of inspector_utils_test.py passes (91 tests). ufmt and flake8 are clean with the versions from requirements-lintrunner.txt. cc @Gasoonjia @nil-is-all
1 parent ad40fa9 commit 9b5758f

2 files changed

Lines changed: 43 additions & 3 deletions

File tree

‎devtools/inspector/_inspector_utils.py‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,16 +394,23 @@ def style_text_size(val, size=12):
394394
)
395395

396396

397-
def plot_metric(result: List[float], metric_name: str):
397+
def plot_metric(result: List[Optional[float]], metric_name: str):
398+
# Nothing to plot (no outputs to compare), so don't write an empty chart.
399+
if len(result) == 0:
400+
return
401+
398402
import matplotlib.pyplot as plt
399403
import numpy as np
400404

401405
# Clear the current figure, otherwise this plot will be on top of previous plots
402406
plt.clf()
403407
plt.figure(figsize=(8, 6))
404408

405-
# Non-tensor outputs have no metric value (None); plot them as 0.
406-
values = [v if v is not None else 0.0 for v in result]
409+
# Non-tensor outputs have no metric value (None), and metrics can be
410+
# non-finite (SNR is inf for identical tensors, NaN for all-zero ones).
411+
# Plot these as 0; matplotlib cannot use inf/NaN as axis limits. The bar
412+
# label still shows the actual value.
413+
values = [v if v is not None and math.isfinite(v) else 0.0 for v in result]
407414
x_axis = np.arange(len(values))
408415
bars = plt.bar(x_axis, values, width=0.5)
409416
plt.grid(True, which="major", axis="y")

‎devtools/inspector/tests/inspector_utils_test.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
# pyre-unsafe
88

9+
import math
10+
import os
911
import tempfile
1012
import unittest
1113
from typing import Dict, Tuple
@@ -42,6 +44,7 @@
4244
map_runtime_aot_intermediate_outputs,
4345
merge_runtime_overlapping_debug_handles,
4446
NodeFilter,
47+
plot_metric,
4548
propagate_back_debug_handle,
4649
TimeScale,
4750
)
@@ -246,6 +249,36 @@ def test_compare_results_with_non_tensor_output(self):
246249
self.assertIsNotNone(values[0])
247250
self.assertIsNone(values[1])
248251

252+
def test_compare_results_plot_non_finite_metrics(self):
253+
# SNR is inf for identical tensors and -inf when the reference is all
254+
# zeros; plotting must not fail on non-finite axis limits.
255+
import matplotlib
256+
257+
matplotlib.use("Agg")
258+
a = torch.rand(4, 4)
259+
zeros = torch.zeros(4, 4)
260+
for ref, out, expected_snr in ((a, a.clone(), math.inf), (zeros, a, -math.inf)):
261+
results = compare_results([ref], [out], plot=True)
262+
self.assertEqual(results["snr"], [expected_snr])
263+
264+
def test_plot_metric_empty_result(self):
265+
# No values to plot: return without creating a figure or writing a file.
266+
import matplotlib
267+
268+
matplotlib.use("Agg")
269+
import matplotlib.pyplot as plt
270+
271+
plt.close("all")
272+
with tempfile.TemporaryDirectory() as tmp_dir:
273+
cwd = os.getcwd()
274+
os.chdir(tmp_dir)
275+
try:
276+
plot_metric([], "snr")
277+
finally:
278+
os.chdir(cwd)
279+
self.assertEqual(os.listdir(tmp_dir), [])
280+
self.assertEqual(plt.get_fignums(), [])
281+
249282
def test_merge_overlapping_debug_handles_basic(self):
250283
big_tensor = torch.rand(100, 100)
251284
intermediate_outputs = {

0 commit comments

Comments
 (0)