ror HF Staff commited on
Commit
c1a3d27
·
1 Parent(s): 18f5759

Version 0.4

Browse files
Files changed (6) hide show
  1. app.py +32 -32
  2. data.py +1 -1
  3. model_page.py +26 -26
  4. styles.css +5 -5
  5. summary_page.py +23 -23
  6. utils.py +8 -8
app.py CHANGED
@@ -11,7 +11,7 @@ from model_page import plot_model_stats
11
 
12
  # Configure matplotlib to prevent memory warnings and set dark background
13
  matplotlib.rcParams['figure.facecolor'] = '#000000'
14
- matplotlib.rcParams['axes.facecolor'] = '#000000'
15
  matplotlib.rcParams['savefig.facecolor'] = '#000000'
16
  plt.ioff() # Turn off interactive mode to prevent figure accumulation
17
 
@@ -35,36 +35,36 @@ def load_css():
35
 
36
  # Create the Gradio interface with sidebar and dark theme
37
  with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
38
-
39
  with gr.Row():
40
- # Sidebar for model selection
41
  with gr.Column(scale=1, elem_classes=["sidebar"]):
42
  gr.Markdown("# 🤖 TCID", elem_classes=["sidebar-title"])
43
-
44
  # Description with integrated last update time
45
  if Ci_results.last_update_time:
46
  description_text = f"**Transformer CI Dashboard**\n\n*Result overview by model and hardware (last updated: {Ci_results.last_update_time})*\n"
47
  else:
48
  description_text = f"**Transformer CI Dashboard**\n\n*Result overview by model and hardware (loading...)*\n"
49
  description_display = gr.Markdown(description_text, elem_classes=["sidebar-description"])
50
-
51
  # Summary button at the top
52
  summary_button = gr.Button(
53
- "summary\n📊",
54
  variant="primary",
55
  size="lg",
56
  elem_classes=["summary-button"]
57
  )
58
-
59
  # Model selection header
60
  gr.Markdown(f"**Select model ({len(Ci_results.available_models)}):**", elem_classes=["model-header"])
61
-
62
  # Scrollable container for model buttons
63
  with gr.Column(scale=1, elem_classes=["model-container"]):
64
  # Create individual buttons for each model
65
  model_buttons = []
66
  model_choices = [model.lower() for model in Ci_results.available_models] if Ci_results.available_models else ["auto", "bert", "clip", "llama"]
67
-
68
  for model_name in model_choices:
69
  btn = gr.Button(
70
  model_name,
@@ -73,31 +73,31 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
73
  elem_classes=["model-button"]
74
  )
75
  model_buttons.append(btn)
76
-
77
  # CI job links at bottom of sidebar
78
  ci_links_display = gr.Markdown("🔗 **CI Jobs:** *Loading...*", elem_classes=["sidebar-links"])
79
-
80
  # Main content area
81
  with gr.Column(scale=4, elem_classes=["main-content"]):
82
  # Summary display (default view)
83
  summary_display = gr.Plot(
84
  value=create_summary_page(Ci_results.df, Ci_results.available_models),
85
- label="",
86
  format="png",
87
  elem_classes=["plot-container"],
88
  visible=True
89
  )
90
-
91
  # Detailed view components (hidden by default)
92
  with gr.Column(visible=False, elem_classes=["detail-view"]) as detail_view:
93
-
94
  # Create the plot output
95
  plot_output = gr.Plot(
96
- label="",
97
  format="png",
98
  elem_classes=["plot-container"]
99
  )
100
-
101
  # Create two separate failed tests displays in a row layout
102
  with gr.Row():
103
  with gr.Column(scale=1):
@@ -118,7 +118,7 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
118
  container=False,
119
  elem_classes=["failed-tests"]
120
  )
121
-
122
  # Set up click handlers for model buttons
123
  for i, btn in enumerate(model_buttons):
124
  model_name = model_choices[i]
@@ -129,12 +129,12 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
129
  fn=lambda: [gr.update(visible=False), gr.update(visible=True)],
130
  outputs=[summary_display, detail_view]
131
  )
132
-
133
  # Summary button click handler
134
  def show_summary_and_update_links():
135
  """Show summary page and update CI links."""
136
  return create_summary_page(Ci_results.df, Ci_results.available_models), get_description_text(), get_ci_links()
137
-
138
  summary_button.click(
139
  fn=show_summary_and_update_links,
140
  outputs=[summary_display, description_display, ci_links_display]
@@ -142,7 +142,7 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
142
  fn=lambda: [gr.update(visible=True), gr.update(visible=False)],
143
  outputs=[summary_display, detail_view]
144
  )
145
-
146
  # Function to get current description text
147
  def get_description_text():
148
  """Get description text with integrated last update time."""
@@ -150,7 +150,7 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
150
  return f"**Transformer CI Dashboard**\n\n*Result overview by model and hardware (last updated: {Ci_results.last_update_time})*\n"
151
  else:
152
  return f"**Transformer CI Dashboard**\n\n*Result overview by model and hardware (loading...)*\n"
153
-
154
  # Function to get CI job links
155
  def get_ci_links():
156
  """Get CI job links from the most recent data."""
@@ -158,16 +158,16 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
158
  # Check if df exists and is not empty
159
  if Ci_results.df is None or Ci_results.df.empty:
160
  return "🔗 **CI Jobs:** *Loading...*"
161
-
162
  # Get links from any available model (they should be the same for all models in a run)
163
  amd_multi_link = None
164
  amd_single_link = None
165
  nvidia_multi_link = None
166
  nvidia_single_link = None
167
-
168
  for model_name in Ci_results.df.index:
169
  row = Ci_results.df.loc[model_name]
170
-
171
  # Extract AMD links
172
  if pd.notna(row.get('job_link_amd')) and (not amd_multi_link or not amd_single_link):
173
  amd_link_raw = row.get('job_link_amd')
@@ -176,7 +176,7 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
176
  amd_multi_link = amd_link_raw['multi']
177
  if 'single' in amd_link_raw and not amd_single_link:
178
  amd_single_link = amd_link_raw['single']
179
-
180
  # Extract NVIDIA links
181
  if pd.notna(row.get('job_link_nvidia')) and (not nvidia_multi_link or not nvidia_single_link):
182
  nvidia_link_raw = row.get('job_link_nvidia')
@@ -185,13 +185,13 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
185
  nvidia_multi_link = nvidia_link_raw['multi']
186
  if 'single' in nvidia_link_raw and not nvidia_single_link:
187
  nvidia_single_link = nvidia_link_raw['single']
188
-
189
  # Break if we have all links
190
  if amd_multi_link and amd_single_link and nvidia_multi_link and nvidia_single_link:
191
  break
192
-
193
  links_md = "🔗 **CI Jobs:**\n\n"
194
-
195
  # AMD links
196
  if amd_multi_link or amd_single_link:
197
  links_md += "**AMD:**\n"
@@ -200,7 +200,7 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
200
  if amd_single_link:
201
  links_md += f"• [Single GPU]({amd_single_link})\n"
202
  links_md += "\n"
203
-
204
  # NVIDIA links
205
  if nvidia_multi_link or nvidia_single_link:
206
  links_md += "**NVIDIA:**\n"
@@ -208,15 +208,15 @@ with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
208
  links_md += f"• [Multi GPU]({nvidia_multi_link})\n"
209
  if nvidia_single_link:
210
  links_md += f"• [Single GPU]({nvidia_single_link})\n"
211
-
212
  if not (amd_multi_link or amd_single_link or nvidia_multi_link or nvidia_single_link):
213
  links_md += "*No links available*"
214
-
215
  return links_md
216
  except Exception as e:
217
  logger.error(f"getting CI links: {e}")
218
  return "🔗 **CI Jobs:** *Error loading links*"
219
-
220
 
221
  # Auto-update CI links when the interface loads
222
  demo.load(
 
11
 
12
  # Configure matplotlib to prevent memory warnings and set dark background
13
  matplotlib.rcParams['figure.facecolor'] = '#000000'
14
+ matplotlib.rcParams['axes.facecolor'] = '#000000'
15
  matplotlib.rcParams['savefig.facecolor'] = '#000000'
16
  plt.ioff() # Turn off interactive mode to prevent figure accumulation
17
 
 
35
 
36
  # Create the Gradio interface with sidebar and dark theme
37
  with gr.Blocks(title="Model Test Results Dashboard", css=load_css()) as demo:
38
+
39
  with gr.Row():
40
+ # Sidebar for model selection
41
  with gr.Column(scale=1, elem_classes=["sidebar"]):
42
  gr.Markdown("# 🤖 TCID", elem_classes=["sidebar-title"])
43
+
44
  # Description with integrated last update time
45
  if Ci_results.last_update_time:
46
  description_text = f"**Transformer CI Dashboard**\n\n*Result overview by model and hardware (last updated: {Ci_results.last_update_time})*\n"
47
  else:
48
  description_text = f"**Transformer CI Dashboard**\n\n*Result overview by model and hardware (loading...)*\n"
49
  description_display = gr.Markdown(description_text, elem_classes=["sidebar-description"])
50
+
51
  # Summary button at the top
52
  summary_button = gr.Button(
53
+ "summary\n📊",
54
  variant="primary",
55
  size="lg",
56
  elem_classes=["summary-button"]
57
  )
58
+
59
  # Model selection header
60
  gr.Markdown(f"**Select model ({len(Ci_results.available_models)}):**", elem_classes=["model-header"])
61
+
62
  # Scrollable container for model buttons
63
  with gr.Column(scale=1, elem_classes=["model-container"]):
64
  # Create individual buttons for each model
65
  model_buttons = []
66
  model_choices = [model.lower() for model in Ci_results.available_models] if Ci_results.available_models else ["auto", "bert", "clip", "llama"]
67
+
68
  for model_name in model_choices:
69
  btn = gr.Button(
70
  model_name,
 
73
  elem_classes=["model-button"]
74
  )
75
  model_buttons.append(btn)
76
+
77
  # CI job links at bottom of sidebar
78
  ci_links_display = gr.Markdown("🔗 **CI Jobs:** *Loading...*", elem_classes=["sidebar-links"])
79
+
80
  # Main content area
81
  with gr.Column(scale=4, elem_classes=["main-content"]):
82
  # Summary display (default view)
83
  summary_display = gr.Plot(
84
  value=create_summary_page(Ci_results.df, Ci_results.available_models),
85
+ label="",
86
  format="png",
87
  elem_classes=["plot-container"],
88
  visible=True
89
  )
90
+
91
  # Detailed view components (hidden by default)
92
  with gr.Column(visible=False, elem_classes=["detail-view"]) as detail_view:
93
+
94
  # Create the plot output
95
  plot_output = gr.Plot(
96
+ label="",
97
  format="png",
98
  elem_classes=["plot-container"]
99
  )
100
+
101
  # Create two separate failed tests displays in a row layout
102
  with gr.Row():
103
  with gr.Column(scale=1):
 
118
  container=False,
119
  elem_classes=["failed-tests"]
120
  )
121
+
122
  # Set up click handlers for model buttons
123
  for i, btn in enumerate(model_buttons):
124
  model_name = model_choices[i]
 
129
  fn=lambda: [gr.update(visible=False), gr.update(visible=True)],
130
  outputs=[summary_display, detail_view]
131
  )
132
+
133
  # Summary button click handler
134
  def show_summary_and_update_links():
135
  """Show summary page and update CI links."""
136
  return create_summary_page(Ci_results.df, Ci_results.available_models), get_description_text(), get_ci_links()
137
+
138
  summary_button.click(
139
  fn=show_summary_and_update_links,
140
  outputs=[summary_display, description_display, ci_links_display]
 
142
  fn=lambda: [gr.update(visible=True), gr.update(visible=False)],
143
  outputs=[summary_display, detail_view]
144
  )
145
+
146
  # Function to get current description text
147
  def get_description_text():
148
  """Get description text with integrated last update time."""
 
150
  return f"**Transformer CI Dashboard**\n\n*Result overview by model and hardware (last updated: {Ci_results.last_update_time})*\n"
151
  else:
152
  return f"**Transformer CI Dashboard**\n\n*Result overview by model and hardware (loading...)*\n"
153
+
154
  # Function to get CI job links
155
  def get_ci_links():
156
  """Get CI job links from the most recent data."""
 
158
  # Check if df exists and is not empty
159
  if Ci_results.df is None or Ci_results.df.empty:
160
  return "🔗 **CI Jobs:** *Loading...*"
161
+
162
  # Get links from any available model (they should be the same for all models in a run)
163
  amd_multi_link = None
164
  amd_single_link = None
165
  nvidia_multi_link = None
166
  nvidia_single_link = None
167
+
168
  for model_name in Ci_results.df.index:
169
  row = Ci_results.df.loc[model_name]
170
+
171
  # Extract AMD links
172
  if pd.notna(row.get('job_link_amd')) and (not amd_multi_link or not amd_single_link):
173
  amd_link_raw = row.get('job_link_amd')
 
176
  amd_multi_link = amd_link_raw['multi']
177
  if 'single' in amd_link_raw and not amd_single_link:
178
  amd_single_link = amd_link_raw['single']
179
+
180
  # Extract NVIDIA links
181
  if pd.notna(row.get('job_link_nvidia')) and (not nvidia_multi_link or not nvidia_single_link):
182
  nvidia_link_raw = row.get('job_link_nvidia')
 
185
  nvidia_multi_link = nvidia_link_raw['multi']
186
  if 'single' in nvidia_link_raw and not nvidia_single_link:
187
  nvidia_single_link = nvidia_link_raw['single']
188
+
189
  # Break if we have all links
190
  if amd_multi_link and amd_single_link and nvidia_multi_link and nvidia_single_link:
191
  break
192
+
193
  links_md = "🔗 **CI Jobs:**\n\n"
194
+
195
  # AMD links
196
  if amd_multi_link or amd_single_link:
197
  links_md += "**AMD:**\n"
 
200
  if amd_single_link:
201
  links_md += f"• [Single GPU]({amd_single_link})\n"
202
  links_md += "\n"
203
+
204
  # NVIDIA links
205
  if nvidia_multi_link or nvidia_single_link:
206
  links_md += "**NVIDIA:**\n"
 
208
  links_md += f"• [Multi GPU]({nvidia_multi_link})\n"
209
  if nvidia_single_link:
210
  links_md += f"• [Single GPU]({nvidia_single_link})\n"
211
+
212
  if not (amd_multi_link or amd_single_link or nvidia_multi_link or nvidia_single_link):
213
  links_md += "*No links available*"
214
+
215
  return links_md
216
  except Exception as e:
217
  logger.error(f"getting CI links: {e}")
218
  return "🔗 **CI Jobs:** *Error loading links*"
219
+
220
 
221
  # Auto-update CI links when the interface loads
222
  demo.load(
data.py CHANGED
@@ -49,7 +49,7 @@ def get_distant_data() -> pd.DataFrame:
49
  # Retrieve NVIDIA dataframe
50
  nvidia_src = "hf://datasets/hf-internal-testing/transformers_daily_ci/**/ci_results_run_models_gpu/model_results.json"
51
  files_nvidia = sorted(fs.glob(nvidia_src), reverse=True)
52
- # NOTE: should this be removeprefix instead of lstrip?
53
  nvidia_path = files_nvidia[0].lstrip('datasets/hf-internal-testing/transformers_daily_ci/')
54
  nvidia_path = "https://huggingface.co/datasets/hf-internal-testing/transformers_daily_ci/raw/main/" + nvidia_path
55
  df_nvidia = read_one_dataframe(nvidia_path, "nvidia")
 
49
  # Retrieve NVIDIA dataframe
50
  nvidia_src = "hf://datasets/hf-internal-testing/transformers_daily_ci/**/ci_results_run_models_gpu/model_results.json"
51
  files_nvidia = sorted(fs.glob(nvidia_src), reverse=True)
52
+ # NOTE: should this be removeprefix instead of lstrip?
53
  nvidia_path = files_nvidia[0].lstrip('datasets/hf-internal-testing/transformers_daily_ci/')
54
  nvidia_path = "https://huggingface.co/datasets/hf-internal-testing/transformers_daily_ci/raw/main/" + nvidia_path
55
  df_nvidia = read_one_dataframe(nvidia_path, "nvidia")
model_page.py CHANGED
@@ -41,47 +41,47 @@ MAX_FAILURE_ITEMS = 10
41
  def _create_pie_chart(ax: plt.Axes, device_label: str, filtered_stats: dict) -> None:
42
  """Create a pie chart for device statistics."""
43
  if not filtered_stats:
44
- ax.text(0.5, 0.5, 'No test results',
45
  horizontalalignment='center', verticalalignment='center',
46
  transform=ax.transAxes, fontsize=14, color='#888888',
47
  fontfamily='monospace', weight='normal')
48
- ax.set_title(device_label, fontsize=DEVICE_TITLE_FONT_SIZE, weight='bold',
49
  pad=DEVICE_TITLE_PAD, color=TITLE_COLOR, fontfamily='monospace')
50
  ax.axis('off')
51
  return
52
-
53
  chart_colors = [COLORS[category] for category in filtered_stats.keys()]
54
-
55
  # Create minimal pie chart - full pie, no donut effect
56
  wedges, texts, autotexts = ax.pie(
57
- filtered_stats.values(),
58
  labels=[label.lower() for label in filtered_stats.keys()], # Lowercase for minimal look
59
  colors=chart_colors,
60
- autopct=lambda pct: f'{int(pct * sum(filtered_stats.values()) / 100)}',
61
  startangle=PIE_START_ANGLE,
62
  explode=None, # No separation
63
  shadow=False,
64
  wedgeprops=dict(edgecolor='#1a1a1a', linewidth=BORDER_LINE_WIDTH), # Minimal borders
65
- textprops={'fontsize': 12, 'weight': 'normal',
66
  'color': LABEL_COLOR, 'fontfamily': 'monospace'}
67
  )
68
-
69
  # Enhanced percentage text styling for better readability
70
  for autotext in autotexts:
71
  autotext.set_color(BLACK) # Black text for better contrast
72
  autotext.set_weight('bold')
73
  autotext.set_fontsize(14)
74
  autotext.set_fontfamily('monospace')
75
-
76
  # Minimal category labels
77
  for text in texts:
78
  text.set_color(LABEL_COLOR)
79
  text.set_weight('normal')
80
  text.set_fontsize(13)
81
  text.set_fontfamily('monospace')
82
-
83
  # Device label closer to chart and bigger
84
- ax.set_title(device_label, fontsize=DEVICE_TITLE_FONT_SIZE, weight='normal',
85
  pad=DEVICE_TITLE_PAD, color=TITLE_COLOR, fontfamily='monospace')
86
 
87
 
@@ -95,47 +95,47 @@ def plot_model_stats(df: pd.DataFrame, model_name: str) -> tuple[plt.Figure, str
95
  failures_amd = failures_nvidia = {}
96
  else:
97
  row = df.loc[model_name]
98
-
99
  # Extract and process model data
100
  amd_stats, nvidia_stats = extract_model_data(row)[:2]
101
-
102
  # Filter out categories with 0 values for cleaner visualization
103
  amd_filtered = {k: v for k, v in amd_stats.items() if v > 0}
104
  nvidia_filtered = {k: v for k, v in nvidia_stats.items() if v > 0}
105
-
106
  # Generate failure info directly from dataframe
107
  failures_amd = dict(row.get('failures_amd', {}))
108
  failures_nvidia = dict(row.get('failures_nvidia', {}))
109
 
110
  # failure_xxx = {"single": [test, ...], "multi": [...]}
111
  # test = {"line": test_name. "trace": error_msg}
112
-
113
  # Always create figure with two subplots side by side with padding
114
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(FIGURE_WIDTH_DUAL, FIGURE_HEIGHT_DUAL), facecolor=BLACK)
115
  ax1.set_facecolor(BLACK)
116
  ax2.set_facecolor(BLACK)
117
-
118
  # Create both pie charts with device labels
119
  _create_pie_chart(ax1, "amd", amd_filtered)
120
  _create_pie_chart(ax2, "nvidia", nvidia_filtered)
121
-
122
  # Add subtle separation line between charts - stops at device labels level
123
  line_x = 0.5
124
- fig.add_artist(plt.Line2D([line_x, line_x], [0.0, SEPARATOR_LINE_Y_END],
125
- color='#333333', linewidth=SEPARATOR_LINE_WIDTH,
126
  alpha=SEPARATOR_ALPHA, transform=fig.transFigure))
127
-
128
  # Add central shared title for model name
129
- fig.suptitle(f'{model_name.lower()}', fontsize=32, weight='bold',
130
  color='#CCCCCC', fontfamily='monospace', y=MODEL_TITLE_Y)
131
-
132
  # Clean layout with padding and space for central title
133
  plt.tight_layout()
134
  plt.subplots_adjust(top=SUBPLOT_TOP, wspace=SUBPLOT_WSPACE)
135
-
136
  amd_failed_info = prepare_textbox_content(failures_amd, 'AMD', bool(amd_filtered))
137
  nvidia_failed_info = prepare_textbox_content(failures_nvidia, 'NVIDIA', bool(nvidia_filtered))
138
-
139
  return fig, amd_failed_info, nvidia_failed_info
140
 
141
 
@@ -147,7 +147,7 @@ def prepare_textbox_content(failures: dict[str, list], device: str, data_availab
147
  # Catch the case where there are no failures
148
  if not failures:
149
  return generate_underlined_line(f"No failures for {device}")
150
-
151
  # Summary of failures
152
  single_failures = failures.get("single", [])
153
  multi_failures = failures.get("multi", [])
@@ -166,7 +166,7 @@ def prepare_textbox_content(failures: dict[str, list], device: str, data_availab
166
  name = name.split("::")[-1]
167
  info_lines.append(name)
168
  info_lines.append("\n")
169
-
170
  # Add multi-gpu failures
171
  if multi_failures:
172
  info_lines.append(generate_underlined_line("Multi GPU failures:"))
 
41
  def _create_pie_chart(ax: plt.Axes, device_label: str, filtered_stats: dict) -> None:
42
  """Create a pie chart for device statistics."""
43
  if not filtered_stats:
44
+ ax.text(0.5, 0.5, 'No test results',
45
  horizontalalignment='center', verticalalignment='center',
46
  transform=ax.transAxes, fontsize=14, color='#888888',
47
  fontfamily='monospace', weight='normal')
48
+ ax.set_title(device_label, fontsize=DEVICE_TITLE_FONT_SIZE, weight='bold',
49
  pad=DEVICE_TITLE_PAD, color=TITLE_COLOR, fontfamily='monospace')
50
  ax.axis('off')
51
  return
52
+
53
  chart_colors = [COLORS[category] for category in filtered_stats.keys()]
54
+
55
  # Create minimal pie chart - full pie, no donut effect
56
  wedges, texts, autotexts = ax.pie(
57
+ filtered_stats.values(),
58
  labels=[label.lower() for label in filtered_stats.keys()], # Lowercase for minimal look
59
  colors=chart_colors,
60
+ autopct=lambda pct: f'{round(pct * sum(filtered_stats.values()) / 100)}',
61
  startangle=PIE_START_ANGLE,
62
  explode=None, # No separation
63
  shadow=False,
64
  wedgeprops=dict(edgecolor='#1a1a1a', linewidth=BORDER_LINE_WIDTH), # Minimal borders
65
+ textprops={'fontsize': 12, 'weight': 'normal',
66
  'color': LABEL_COLOR, 'fontfamily': 'monospace'}
67
  )
68
+
69
  # Enhanced percentage text styling for better readability
70
  for autotext in autotexts:
71
  autotext.set_color(BLACK) # Black text for better contrast
72
  autotext.set_weight('bold')
73
  autotext.set_fontsize(14)
74
  autotext.set_fontfamily('monospace')
75
+
76
  # Minimal category labels
77
  for text in texts:
78
  text.set_color(LABEL_COLOR)
79
  text.set_weight('normal')
80
  text.set_fontsize(13)
81
  text.set_fontfamily('monospace')
82
+
83
  # Device label closer to chart and bigger
84
+ ax.set_title(device_label, fontsize=DEVICE_TITLE_FONT_SIZE, weight='normal',
85
  pad=DEVICE_TITLE_PAD, color=TITLE_COLOR, fontfamily='monospace')
86
 
87
 
 
95
  failures_amd = failures_nvidia = {}
96
  else:
97
  row = df.loc[model_name]
98
+
99
  # Extract and process model data
100
  amd_stats, nvidia_stats = extract_model_data(row)[:2]
101
+
102
  # Filter out categories with 0 values for cleaner visualization
103
  amd_filtered = {k: v for k, v in amd_stats.items() if v > 0}
104
  nvidia_filtered = {k: v for k, v in nvidia_stats.items() if v > 0}
105
+
106
  # Generate failure info directly from dataframe
107
  failures_amd = dict(row.get('failures_amd', {}))
108
  failures_nvidia = dict(row.get('failures_nvidia', {}))
109
 
110
  # failure_xxx = {"single": [test, ...], "multi": [...]}
111
  # test = {"line": test_name. "trace": error_msg}
112
+
113
  # Always create figure with two subplots side by side with padding
114
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(FIGURE_WIDTH_DUAL, FIGURE_HEIGHT_DUAL), facecolor=BLACK)
115
  ax1.set_facecolor(BLACK)
116
  ax2.set_facecolor(BLACK)
117
+
118
  # Create both pie charts with device labels
119
  _create_pie_chart(ax1, "amd", amd_filtered)
120
  _create_pie_chart(ax2, "nvidia", nvidia_filtered)
121
+
122
  # Add subtle separation line between charts - stops at device labels level
123
  line_x = 0.5
124
+ fig.add_artist(plt.Line2D([line_x, line_x], [0.0, SEPARATOR_LINE_Y_END],
125
+ color='#333333', linewidth=SEPARATOR_LINE_WIDTH,
126
  alpha=SEPARATOR_ALPHA, transform=fig.transFigure))
127
+
128
  # Add central shared title for model name
129
+ fig.suptitle(f'{model_name.lower()}', fontsize=32, weight='bold',
130
  color='#CCCCCC', fontfamily='monospace', y=MODEL_TITLE_Y)
131
+
132
  # Clean layout with padding and space for central title
133
  plt.tight_layout()
134
  plt.subplots_adjust(top=SUBPLOT_TOP, wspace=SUBPLOT_WSPACE)
135
+
136
  amd_failed_info = prepare_textbox_content(failures_amd, 'AMD', bool(amd_filtered))
137
  nvidia_failed_info = prepare_textbox_content(failures_nvidia, 'NVIDIA', bool(nvidia_filtered))
138
+
139
  return fig, amd_failed_info, nvidia_failed_info
140
 
141
 
 
147
  # Catch the case where there are no failures
148
  if not failures:
149
  return generate_underlined_line(f"No failures for {device}")
150
+
151
  # Summary of failures
152
  single_failures = failures.get("single", [])
153
  multi_failures = failures.get("multi", [])
 
166
  name = name.split("::")[-1]
167
  info_lines.append(name)
168
  info_lines.append("\n")
169
+
170
  # Add multi-gpu failures
171
  if multi_failures:
172
  info_lines.append(generate_underlined_line("Multi GPU failures:"))
styles.css CHANGED
@@ -56,7 +56,7 @@ div[data-testid="column"]:has(.sidebar) {
56
  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
57
  position: relative !important;
58
  overflow: hidden !important;
59
- box-shadow:
60
  0 4px 15px rgba(0, 0, 0, 0.3),
61
  inset 0 1px 0 rgba(255, 255, 255, 0.2) !important;
62
  font-weight: 600 !important;
@@ -110,8 +110,8 @@ div[data-testid="column"]:has(.sidebar) {
110
 
111
  /* Specific control for markdown content */
112
  .sidebar .markdown,
113
- .sidebar h1,
114
- .sidebar h2,
115
  .sidebar h3,
116
  .sidebar p {
117
  max-width: 100% !important;
@@ -261,7 +261,7 @@ div[data-testid="column"]:has(.model-container) {
261
  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
262
  position: relative !important;
263
  overflow: hidden !important;
264
- box-shadow:
265
  0 4px 15px rgba(0, 0, 0, 0.3),
266
  inset 0 1px 0 rgba(255, 255, 255, 0.2) !important;
267
  font-weight: 600 !important;
@@ -634,4 +634,4 @@ h1, h2, h3, p, .markdown {
634
  @keyframes resetScroll {
635
  0% { scroll-behavior: auto; }
636
  100% { scroll-behavior: auto; }
637
- }
 
56
  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
57
  position: relative !important;
58
  overflow: hidden !important;
59
+ box-shadow:
60
  0 4px 15px rgba(0, 0, 0, 0.3),
61
  inset 0 1px 0 rgba(255, 255, 255, 0.2) !important;
62
  font-weight: 600 !important;
 
110
 
111
  /* Specific control for markdown content */
112
  .sidebar .markdown,
113
+ .sidebar h1,
114
+ .sidebar h2,
115
  .sidebar h3,
116
  .sidebar p {
117
  max-width: 100% !important;
 
261
  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
262
  position: relative !important;
263
  overflow: hidden !important;
264
+ box-shadow:
265
  0 4px 15px rgba(0, 0, 0, 0.3),
266
  inset 0 1px 0 rgba(255, 255, 255, 0.2) !important;
267
  font-weight: 600 !important;
 
634
  @keyframes resetScroll {
635
  0% { scroll-behavior: auto; }
636
  100% { scroll-behavior: auto; }
637
+ }
summary_page.py CHANGED
@@ -25,7 +25,7 @@ NVIDIA_BAR_OFFSET = 0.54 # NVIDIA bar offset ratio
25
  # Colors
26
  COLORS = {
27
  'passed': '#4CAF50',
28
- 'failed': '#E53E3E',
29
  'skipped': '#FFD54F',
30
  'error': '#8B0000',
31
  'empty': "#5B5B5B"
@@ -47,14 +47,14 @@ def draw_text_and_bar(
47
  ) -> None:
48
  """Draw a horizontal bar chart for given stats and its label on the left."""
49
  # Text
50
- label_x = column_left_position - LABEL_OFFSET
51
  ax.text(
52
- label_x, y_bar, label, ha='right', va='center', color='#CCCCCC', fontsize=LABEL_FONT_SIZE,
53
  fontfamily='monospace', fontweight='normal'
54
  )
55
  # Bar
56
  total = sum(stats.values())
57
- if total > 0:
58
  left = column_left_position
59
  for category in ['passed', 'failed', 'skipped', 'error']:
60
  if stats[category] > 0:
@@ -69,44 +69,44 @@ def create_summary_page(df: pd.DataFrame, available_models: list[str]) -> plt.Fi
69
  if df.empty:
70
  fig, ax = plt.subplots(figsize=(16, 8), facecolor='#000000')
71
  ax.set_facecolor('#000000')
72
- ax.text(0.5, 0.5, 'No data available',
73
  horizontalalignment='center', verticalalignment='center',
74
  transform=ax.transAxes, fontsize=20, color='#888888',
75
  fontfamily='monospace', weight='normal')
76
  ax.axis('off')
77
  return fig
78
-
79
  # Calculate dimensions for N-column layout
80
  model_count = len(available_models)
81
  rows = (model_count + COLUMNS - 1) // COLUMNS # Ceiling division
82
-
83
  # Figure dimensions - wider for columns, height based on rows
84
  height_per_row = min(MIN_HEIGHT_PER_ROW, MAX_HEIGHT / max(rows, 1))
85
  figure_height = min(MAX_HEIGHT, rows * height_per_row + FIGURE_PADDING)
86
-
87
  fig, ax = plt.subplots(figsize=(FIGURE_WIDTH, figure_height), facecolor='#000000')
88
  ax.set_facecolor('#000000')
89
-
90
  visible_model_count = 0
91
  max_y = 0
92
-
93
  for i, model_name in enumerate(available_models):
94
  if model_name not in df.index:
95
  continue
96
-
97
  row = df.loc[model_name]
98
-
99
  # Extract and process model data
100
  amd_stats, nvidia_stats = extract_model_data(row)[:2]
101
-
102
  # Calculate position in 4-column grid
103
  col = visible_model_count % COLUMNS
104
  row = visible_model_count // COLUMNS
105
-
106
  # Calculate horizontal position for this column
107
  col_left = col * COLUMN_WIDTH + BAR_MARGIN
108
  col_center = col * COLUMN_WIDTH + COLUMN_WIDTH / 2
109
-
110
  # Calculate vertical position for this row - start from top
111
  vertical_spacing = height_per_row
112
  y_base = (VERTICAL_SPACING_RATIO + row) * vertical_spacing
@@ -114,22 +114,22 @@ def create_summary_page(df: pd.DataFrame, available_models: list[str]) -> plt.Fi
114
  y_amd_bar = y_base + vertical_spacing * AMD_BAR_OFFSET # AMD bar
115
  y_nvidia_bar = y_base + vertical_spacing * NVIDIA_BAR_OFFSET # NVIDIA bar
116
  max_y = max(max_y, y_nvidia_bar + vertical_spacing * 0.3)
117
-
118
  # Model name centered above the bars in this column
119
- ax.text(col_center, y_model_name, model_name.lower(),
120
- ha='center', va='center', color='#FFFFFF',
121
  fontsize=MODEL_NAME_FONT_SIZE, fontfamily='monospace', fontweight='bold')
122
-
123
  # AMD label and bar in this column
124
  bar_height = min(0.4, vertical_spacing * BAR_HEIGHT_RATIO)
125
  # Draw AMD bar
126
  draw_text_and_bar("amd", amd_stats, y_amd_bar, col_left, bar_height, ax)
127
  # Draw NVIDIA bar
128
  draw_text_and_bar("nvidia", nvidia_stats, y_nvidia_bar, col_left, bar_height, ax)
129
-
130
  # Increment counter for next visible model
131
  visible_model_count += 1
132
-
133
  # Style the axes to be completely invisible and span full width
134
  ax.set_xlim(-5, 105) # Slightly wider to accommodate labels
135
  ax.set_ylim(0, max_y)
@@ -142,7 +142,7 @@ def create_summary_page(df: pd.DataFrame, available_models: list[str]) -> plt.Fi
142
  ax.set_xticks([])
143
  ax.set_yticks([])
144
  ax.yaxis.set_inverted(True)
145
-
146
  # Remove all margins to make figure stick to top
147
- plt.tight_layout()
148
  return fig
 
25
  # Colors
26
  COLORS = {
27
  'passed': '#4CAF50',
28
+ 'failed': '#E53E3E',
29
  'skipped': '#FFD54F',
30
  'error': '#8B0000',
31
  'empty': "#5B5B5B"
 
47
  ) -> None:
48
  """Draw a horizontal bar chart for given stats and its label on the left."""
49
  # Text
50
+ label_x = column_left_position - LABEL_OFFSET
51
  ax.text(
52
+ label_x, y_bar, label, ha='right', va='center', color='#CCCCCC', fontsize=LABEL_FONT_SIZE,
53
  fontfamily='monospace', fontweight='normal'
54
  )
55
  # Bar
56
  total = sum(stats.values())
57
+ if total > 0:
58
  left = column_left_position
59
  for category in ['passed', 'failed', 'skipped', 'error']:
60
  if stats[category] > 0:
 
69
  if df.empty:
70
  fig, ax = plt.subplots(figsize=(16, 8), facecolor='#000000')
71
  ax.set_facecolor('#000000')
72
+ ax.text(0.5, 0.5, 'No data available',
73
  horizontalalignment='center', verticalalignment='center',
74
  transform=ax.transAxes, fontsize=20, color='#888888',
75
  fontfamily='monospace', weight='normal')
76
  ax.axis('off')
77
  return fig
78
+
79
  # Calculate dimensions for N-column layout
80
  model_count = len(available_models)
81
  rows = (model_count + COLUMNS - 1) // COLUMNS # Ceiling division
82
+
83
  # Figure dimensions - wider for columns, height based on rows
84
  height_per_row = min(MIN_HEIGHT_PER_ROW, MAX_HEIGHT / max(rows, 1))
85
  figure_height = min(MAX_HEIGHT, rows * height_per_row + FIGURE_PADDING)
86
+
87
  fig, ax = plt.subplots(figsize=(FIGURE_WIDTH, figure_height), facecolor='#000000')
88
  ax.set_facecolor('#000000')
89
+
90
  visible_model_count = 0
91
  max_y = 0
92
+
93
  for i, model_name in enumerate(available_models):
94
  if model_name not in df.index:
95
  continue
96
+
97
  row = df.loc[model_name]
98
+
99
  # Extract and process model data
100
  amd_stats, nvidia_stats = extract_model_data(row)[:2]
101
+
102
  # Calculate position in 4-column grid
103
  col = visible_model_count % COLUMNS
104
  row = visible_model_count // COLUMNS
105
+
106
  # Calculate horizontal position for this column
107
  col_left = col * COLUMN_WIDTH + BAR_MARGIN
108
  col_center = col * COLUMN_WIDTH + COLUMN_WIDTH / 2
109
+
110
  # Calculate vertical position for this row - start from top
111
  vertical_spacing = height_per_row
112
  y_base = (VERTICAL_SPACING_RATIO + row) * vertical_spacing
 
114
  y_amd_bar = y_base + vertical_spacing * AMD_BAR_OFFSET # AMD bar
115
  y_nvidia_bar = y_base + vertical_spacing * NVIDIA_BAR_OFFSET # NVIDIA bar
116
  max_y = max(max_y, y_nvidia_bar + vertical_spacing * 0.3)
117
+
118
  # Model name centered above the bars in this column
119
+ ax.text(col_center, y_model_name, model_name.lower(),
120
+ ha='center', va='center', color='#FFFFFF',
121
  fontsize=MODEL_NAME_FONT_SIZE, fontfamily='monospace', fontweight='bold')
122
+
123
  # AMD label and bar in this column
124
  bar_height = min(0.4, vertical_spacing * BAR_HEIGHT_RATIO)
125
  # Draw AMD bar
126
  draw_text_and_bar("amd", amd_stats, y_amd_bar, col_left, bar_height, ax)
127
  # Draw NVIDIA bar
128
  draw_text_and_bar("nvidia", nvidia_stats, y_nvidia_bar, col_left, bar_height, ax)
129
+
130
  # Increment counter for next visible model
131
  visible_model_count += 1
132
+
133
  # Style the axes to be completely invisible and span full width
134
  ax.set_xlim(-5, 105) # Slightly wider to accommodate labels
135
  ax.set_ylim(0, max_y)
 
142
  ax.set_xticks([])
143
  ax.set_yticks([])
144
  ax.yaxis.set_inverted(True)
145
+
146
  # Remove all margins to make figure stick to top
147
+ plt.tight_layout()
148
  return fig
utils.py CHANGED
@@ -5,11 +5,11 @@ from datetime import datetime
5
 
6
  class TimestampFormatter(logging.Formatter):
7
  """Custom formatter that matches the existing timestamp format used in print statements."""
8
-
9
  def format(self, record):
10
  # Create timestamp in the same format as existing print statements
11
  timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
12
-
13
  # Format the message with timestamp prefix
14
  if record.levelno == logging.WARNING:
15
  return f"WARNING: {record.getMessage()}"
@@ -22,23 +22,23 @@ class TimestampFormatter(logging.Formatter):
22
  def setup_logger(name="tcid", level=logging.INFO):
23
  """Set up logger with custom timestamp formatting to match existing print format."""
24
  logger = logging.getLogger(name)
25
-
26
  # Avoid adding multiple handlers if logger already exists
27
  if logger.handlers:
28
  return logger
29
-
30
  logger.setLevel(level)
31
-
32
  # Create console handler
33
  handler = logging.StreamHandler(sys.stdout)
34
  handler.setLevel(level)
35
-
36
  # Set custom formatter
37
  formatter = TimestampFormatter()
38
  handler.setFormatter(formatter)
39
-
40
  logger.addHandler(handler)
41
-
42
  return logger
43
 
44
 
 
5
 
6
  class TimestampFormatter(logging.Formatter):
7
  """Custom formatter that matches the existing timestamp format used in print statements."""
8
+
9
  def format(self, record):
10
  # Create timestamp in the same format as existing print statements
11
  timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
12
+
13
  # Format the message with timestamp prefix
14
  if record.levelno == logging.WARNING:
15
  return f"WARNING: {record.getMessage()}"
 
22
  def setup_logger(name="tcid", level=logging.INFO):
23
  """Set up logger with custom timestamp formatting to match existing print format."""
24
  logger = logging.getLogger(name)
25
+
26
  # Avoid adding multiple handlers if logger already exists
27
  if logger.handlers:
28
  return logger
29
+
30
  logger.setLevel(level)
31
+
32
  # Create console handler
33
  handler = logging.StreamHandler(sys.stdout)
34
  handler.setLevel(level)
35
+
36
  # Set custom formatter
37
  formatter = TimestampFormatter()
38
  handler.setFormatter(formatter)
39
+
40
  logger.addHandler(handler)
41
+
42
  return logger
43
 
44