Spaces:
Running
Running
Version 0.4
Browse files- app.py +32 -32
- data.py +1 -1
- model_page.py +26 -26
- styles.css +5 -5
- summary_page.py +23 -23
- 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'{
|
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 |
|