ror HF Staff commited on
Commit
6f570d6
·
1 Parent(s): ef39cc6

More refactoring

Browse files
Files changed (3) hide show
  1. data.py +27 -0
  2. model_page.py +140 -164
  3. summary_page.py +3 -22
data.py CHANGED
@@ -82,6 +82,33 @@ def get_sample_data() -> pd.DataFrame:
82
  df = df.set_index("model_name")
83
  return df
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
 
87
  class CIResults:
 
82
  df = df.set_index("model_name")
83
  return df
84
 
85
+ def extract_model_data(row: pd.Series) -> tuple[dict[str, int], dict[str, int], int, int, int, int]:
86
+ """Extract and process model data from DataFrame row."""
87
+ # Handle missing values and get counts directly from dataframe
88
+ success_amd = int(row.get('success_amd', 0)) if pd.notna(row.get('success_amd', 0)) else 0
89
+ success_nvidia = int(row.get('success_nvidia', 0)) if pd.notna(row.get('success_nvidia', 0)) else 0
90
+ failed_multi_amd = int(row.get('failed_multi_no_amd', 0)) if pd.notna(row.get('failed_multi_no_amd', 0)) else 0
91
+ failed_multi_nvidia = int(row.get('failed_multi_no_nvidia', 0)) if pd.notna(row.get('failed_multi_no_nvidia', 0)) else 0
92
+ failed_single_amd = int(row.get('failed_single_no_amd', 0)) if pd.notna(row.get('failed_single_no_amd', 0)) else 0
93
+ failed_single_nvidia = int(row.get('failed_single_no_nvidia', 0)) if pd.notna(row.get('failed_single_no_nvidia', 0)) else 0
94
+ # Calculate total failures
95
+ total_failed_amd = failed_multi_amd + failed_single_amd
96
+ total_failed_nvidia = failed_multi_nvidia + failed_single_nvidia
97
+ # Create stats dictionaries directly from dataframe values
98
+ amd_stats = {
99
+ 'passed': success_amd,
100
+ 'failed': total_failed_amd,
101
+ 'skipped': 0, # Not available in this dataset
102
+ 'error': 0 # Not available in this dataset
103
+ }
104
+ nvidia_stats = {
105
+ 'passed': success_nvidia,
106
+ 'failed': total_failed_nvidia,
107
+ 'skipped': 0, # Not available in this dataset
108
+ 'error': 0 # Not available in this dataset
109
+ }
110
+ return amd_stats, nvidia_stats, failed_multi_amd, failed_single_amd, failed_multi_nvidia, failed_single_nvidia
111
+
112
 
113
 
114
  class CIResults:
model_page.py CHANGED
@@ -1,6 +1,63 @@
1
  import matplotlib.pyplot as plt
2
  import pandas as pd
3
  from utils import generate_underlined_line
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
 
6
  def extract_failure_info(failures_obj, device: str, multi_count: int, single_count: int) -> str:
@@ -22,193 +79,112 @@ def extract_failure_info(failures_obj, device: str, multi_count: int, single_cou
22
  # Try to extract detailed failure information
23
  try:
24
  if isinstance(failures_obj, dict):
25
- # Check for multi and single failure categories
26
- if 'multi' in failures_obj and failures_obj['multi']:
27
- info_lines.append(generate_underlined_line(f"Multi GPU failure details:"))
28
- if isinstance(failures_obj['multi'], list):
29
- # Handle list of failures (could be strings or dicts)
30
- for i, failure in enumerate(failures_obj['multi'][:10]): # Limit to first 10
31
- if isinstance(failure, dict):
32
- # Extract meaningful info from dict (e.g., test name, line, etc.)
33
- failure_str = failure.get('line', failure.get('test', failure.get('name', str(failure))))
34
- info_lines.append(f" {i+1}. {failure_str}")
35
- else:
36
- info_lines.append(f" {i+1}. {str(failure)}")
37
- if len(failures_obj['multi']) > 10:
38
- info_lines.append(f"... and {len(failures_obj['multi']) - 10} more")
39
- else:
40
- info_lines.append(str(failures_obj['multi']))
41
- info_lines.append("")
42
-
43
- if 'single' in failures_obj and failures_obj['single']:
44
- info_lines.append(generate_underlined_line(f"Single GPU failure details:"))
45
- if isinstance(failures_obj['single'], list):
46
- # Handle list of failures (could be strings or dicts)
47
- for i, failure in enumerate(failures_obj['single'][:10]): # Limit to first 10
48
- if isinstance(failure, dict):
49
- # Extract meaningful info from dict (e.g., test name, line, etc.)
50
- failure_str = failure.get('line', failure.get('test', failure.get('name', str(failure))))
51
- info_lines.append(f" {i+1}. {failure_str}")
52
- else:
53
- info_lines.append(f" {i+1}. {str(failure)}")
54
- if len(failures_obj['single']) > 10:
55
- info_lines.append(f"... and {len(failures_obj['single']) - 10} more")
56
- else:
57
- info_lines.append(str(failures_obj['single']))
58
 
59
  return "\n".join(info_lines) if info_lines else f"No detailed failure info for {device}"
60
 
61
  except Exception as e:
62
  if multi_count > 0 or single_count > 0:
63
- return f"Failures detected on {device} (Multi: {multi_count}, Single: {single_count})\nDetails unavailable: {str(e)}"
 
 
64
  return f"Error processing failure info for {device}: {str(e)}"
65
 
66
 
67
- def plot_model_stats(
68
- df: pd.DataFrame,
69
- model_name: str,
70
- ) -> tuple[plt.Figure, str, str]:
71
- """Draws a pie chart of model's passed, failed, skipped, and error stats."""
72
- if df.empty or model_name not in df.index:
73
- # Handle case where model data is not available
74
- fig, ax = plt.subplots(figsize=(10, 8), facecolor='#000000')
75
- ax.set_facecolor('#000000')
76
- ax.text(0.5, 0.5, f'No data available for {model_name}',
77
- horizontalalignment='center', verticalalignment='center',
78
- transform=ax.transAxes, fontsize=16, color='#888888',
79
- fontfamily='monospace', weight='normal')
80
- ax.set_xlim(0, 1)
81
- ax.set_ylim(0, 1)
82
  ax.axis('off')
83
- return fig, "No data available", "No data available"
84
-
85
- row = df.loc[model_name]
86
-
87
- # Handle missing values and get counts directly from dataframe
88
- success_amd = int(row.get('success_amd', 0)) if pd.notna(row.get('success_amd', 0)) else 0
89
- success_nvidia = int(row.get('success_nvidia', 0)) if pd.notna(row.get('success_nvidia', 0)) else 0
90
- failed_multi_amd = int(row.get('failed_multi_no_amd', 0)) if pd.notna(row.get('failed_multi_no_amd', 0)) else 0
91
- failed_multi_nvidia = int(row.get('failed_multi_no_nvidia', 0)) if pd.notna(row.get('failed_multi_no_nvidia', 0)) else 0
92
- failed_single_amd = int(row.get('failed_single_no_amd', 0)) if pd.notna(row.get('failed_single_no_amd', 0)) else 0
93
- failed_single_nvidia = int(row.get('failed_single_no_nvidia', 0)) if pd.notna(row.get('failed_single_no_nvidia', 0)) else 0
94
-
95
- # Calculate total failures
96
- total_failed_amd = failed_multi_amd + failed_single_amd
97
- total_failed_nvidia = failed_multi_nvidia + failed_single_nvidia
98
-
99
- # Softer color palette - less pastel, more vibrant
100
- colors = {
101
- 'passed': '#4CAF50', # Medium green
102
- 'failed': '#E53E3E', # More red
103
- 'skipped': '#FFD54F', # Medium yellow
104
- 'error': '#8B0000' # Dark red
105
- }
106
-
107
- # Create stats dictionaries directly from dataframe values
108
- amd_stats = {
109
- 'passed': success_amd,
110
- 'failed': total_failed_amd,
111
- 'skipped': 0, # Not available in this dataset
112
- 'error': 0 # Not available in this dataset
113
- }
114
-
115
- nvidia_stats = {
116
- 'passed': success_nvidia,
117
- 'failed': total_failed_nvidia,
118
- 'skipped': 0, # Not available in this dataset
119
- 'error': 0 # Not available in this dataset
120
- }
121
-
122
- # Filter out categories with 0 values for cleaner visualization
123
- amd_filtered = {k: v for k, v in amd_stats.items() if v > 0}
124
- nvidia_filtered = {k: v for k, v in nvidia_stats.items() if v > 0}
125
-
126
- if not amd_filtered and not nvidia_filtered:
127
- # Handle case where all values are 0 - minimal empty state
128
- fig, ax = plt.subplots(figsize=(10, 8), facecolor='#000000')
129
- ax.set_facecolor('#000000')
130
- ax.text(0.5, 0.5, 'No test results available',
131
- horizontalalignment='center', verticalalignment='center',
132
- transform=ax.transAxes, fontsize=16, color='#888888',
133
- fontfamily='monospace', weight='normal')
134
- ax.set_xlim(0, 1)
135
- ax.set_ylim(0, 1)
136
- ax.axis('off')
137
- return fig, "", ""
138
-
139
- # Create figure with two subplots side by side with padding
140
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 9), facecolor='#000000')
141
- ax1.set_facecolor('#000000')
142
- ax2.set_facecolor('#000000')
143
-
144
- def create_pie_chart(ax, device_label, filtered_stats):
145
- if not filtered_stats:
146
- ax.text(0.5, 0.5, 'No test results',
147
- horizontalalignment='center', verticalalignment='center',
148
- transform=ax.transAxes, fontsize=14, color='#888888',
149
- fontfamily='monospace', weight='normal')
150
- ax.set_title(device_label,
151
- fontsize=28, weight='bold', pad=2, color='#FFFFFF',
152
- fontfamily='monospace')
153
- ax.axis('off')
154
- return
155
-
156
- chart_colors = [colors[category] for category in filtered_stats.keys()]
157
 
158
- # Create minimal pie chart - full pie, no donut effect
159
- wedges, texts, autotexts = ax.pie(
160
- filtered_stats.values(),
161
- labels=[label.lower() for label in filtered_stats.keys()], # Lowercase for minimal look
162
- colors=chart_colors,
163
- autopct=lambda pct: f'{int(pct/100*sum(filtered_stats.values()))}',
164
- startangle=90,
165
- explode=None, # No separation
166
- shadow=False,
167
- wedgeprops=dict(edgecolor='#1a1a1a', linewidth=0.5), # Minimal borders
168
- textprops={'fontsize': 12, 'weight': 'normal', 'color': '#CCCCCC', 'fontfamily': 'monospace'}
169
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
- # Enhanced percentage text styling for better readability
172
- for autotext in autotexts:
173
- autotext.set_color('#000000') # Black text for better contrast
174
- autotext.set_weight('bold')
175
- autotext.set_fontsize(14)
176
- autotext.set_fontfamily('monospace')
177
 
178
- # Minimal category labels
179
- for text in texts:
180
- text.set_color('#AAAAAA')
181
- text.set_weight('normal')
182
- text.set_fontsize(13)
183
- text.set_fontfamily('monospace')
184
 
185
- # Device label closer to chart and bigger
186
- ax.set_title(device_label,
187
- fontsize=28, weight='normal', pad=2, color='#FFFFFF',
188
- fontfamily='monospace')
 
 
 
 
189
 
190
  # Create both pie charts with device labels
191
- create_pie_chart(ax1, "amd", amd_filtered)
192
- create_pie_chart(ax2, "nvidia", nvidia_filtered)
193
 
194
  # Add subtle separation line between charts - stops at device labels level
195
  line_x = 0.5
196
- fig.add_artist(plt.Line2D([line_x, line_x], [0.0, 0.85],
197
- color='#333333', linewidth=1, alpha=0.5,
198
- transform=fig.transFigure))
199
 
200
  # Add central shared title for model name
201
- fig.suptitle(f'{model_name.lower()}',
202
- fontsize=32, weight='bold', color='#CCCCCC',
203
- fontfamily='monospace', y=1)
204
 
205
  # Clean layout with padding and space for central title
206
  plt.tight_layout()
207
- plt.subplots_adjust(top=0.85, wspace=0.4) # Added wspace for padding between charts
208
-
209
- # Generate failure info directly from dataframe
210
- failures_amd = row.get('failures_amd', {})
211
- failures_nvidia = row.get('failures_nvidia', {})
212
 
213
  amd_failed_info = extract_failure_info(failures_amd, 'AMD', failed_multi_amd, failed_single_amd)
214
  nvidia_failed_info = extract_failure_info(failures_nvidia, 'NVIDIA', failed_multi_nvidia, failed_single_nvidia)
 
1
  import matplotlib.pyplot as plt
2
  import pandas as pd
3
  from utils import generate_underlined_line
4
+ from data import extract_model_data
5
+
6
+ # Figure dimensions
7
+ FIGURE_WIDTH_DUAL = 18
8
+ FIGURE_HEIGHT_DUAL = 9
9
+
10
+ # Colors
11
+ COLORS = {
12
+ 'passed': '#4CAF50', # Medium green
13
+ 'failed': '#E53E3E', # More red
14
+ 'skipped': '#FFD54F', # Medium yellow
15
+ 'error': '#8B0000' # Dark red
16
+ }
17
+
18
+ # Styling constants
19
+ BLACK = '#000000'
20
+ LABEL_COLOR = '#AAAAAA'
21
+ TITLE_COLOR = '#FFFFFF'
22
+
23
+ # Font sizes
24
+ DEVICE_TITLE_FONT_SIZE = 28
25
+
26
+ # Layout constants
27
+ SEPARATOR_LINE_Y_END = 0.85
28
+ SUBPLOT_TOP = 0.85
29
+ SUBPLOT_WSPACE = 0.4
30
+ PIE_START_ANGLE = 90
31
+ BORDER_LINE_WIDTH = 0.5
32
+ SEPARATOR_ALPHA = 0.5
33
+ SEPARATOR_LINE_WIDTH = 1
34
+ DEVICE_TITLE_PAD = 2
35
+ MODEL_TITLE_Y = 1
36
+
37
+ # Processing constants
38
+ MAX_FAILURE_ITEMS = 10
39
+
40
+
41
+ def _process_failure_category(failures_obj: dict, category: str, info_lines: list) -> None:
42
+ """Process a single failure category (multi or single) and add to info_lines."""
43
+ if category in failures_obj and failures_obj[category]:
44
+ info_lines.append(generate_underlined_line(f"{category.title()} GPU failure details:"))
45
+ if isinstance(failures_obj[category], list):
46
+ # Handle list of failures (could be strings or dicts)
47
+ for i, failure in enumerate(failures_obj[category][:MAX_FAILURE_ITEMS]):
48
+ if isinstance(failure, dict):
49
+ # Extract meaningful info from dict (e.g., test name, line, etc.)
50
+ failure_str = failure.get('line', failure.get('test',
51
+ failure.get('name', str(failure))))
52
+ info_lines.append(f" {i+1}. {failure_str}")
53
+ else:
54
+ info_lines.append(f" {i+1}. {str(failure)}")
55
+ if len(failures_obj[category]) > MAX_FAILURE_ITEMS:
56
+ remaining = len(failures_obj[category]) - MAX_FAILURE_ITEMS
57
+ info_lines.append(f"... and {remaining} more")
58
+ else:
59
+ info_lines.append(str(failures_obj[category]))
60
+ info_lines.append("")
61
 
62
 
63
  def extract_failure_info(failures_obj, device: str, multi_count: int, single_count: int) -> str:
 
79
  # Try to extract detailed failure information
80
  try:
81
  if isinstance(failures_obj, dict):
82
+ _process_failure_category(failures_obj, 'multi', info_lines)
83
+ _process_failure_category(failures_obj, 'single', info_lines)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  return "\n".join(info_lines) if info_lines else f"No detailed failure info for {device}"
86
 
87
  except Exception as e:
88
  if multi_count > 0 or single_count > 0:
89
+ error_msg = (f"Failures detected on {device} (Multi: {multi_count}, Single: {single_count})\n"
90
+ f"Details unavailable: {str(e)}")
91
+ return error_msg
92
  return f"Error processing failure info for {device}: {str(e)}"
93
 
94
 
95
+ def _create_pie_chart(ax: plt.Axes, device_label: str, filtered_stats: dict) -> None:
96
+ """Create a pie chart for device statistics."""
97
+ if not filtered_stats:
98
+ ax.text(0.5, 0.5, 'No test results',
99
+ horizontalalignment='center', verticalalignment='center',
100
+ transform=ax.transAxes, fontsize=14, color='#888888',
101
+ fontfamily='monospace', weight='normal')
102
+ ax.set_title(device_label, fontsize=DEVICE_TITLE_FONT_SIZE, weight='bold',
103
+ pad=DEVICE_TITLE_PAD, color=TITLE_COLOR, fontfamily='monospace')
 
 
 
 
 
 
104
  ax.axis('off')
105
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
+ chart_colors = [COLORS[category] for category in filtered_stats.keys()]
108
+
109
+ # Create minimal pie chart - full pie, no donut effect
110
+ wedges, texts, autotexts = ax.pie(
111
+ filtered_stats.values(),
112
+ labels=[label.lower() for label in filtered_stats.keys()], # Lowercase for minimal look
113
+ colors=chart_colors,
114
+ autopct=lambda pct: f'{int(pct/100*sum(filtered_stats.values()))}',
115
+ startangle=PIE_START_ANGLE,
116
+ explode=None, # No separation
117
+ shadow=False,
118
+ wedgeprops=dict(edgecolor='#1a1a1a', linewidth=BORDER_LINE_WIDTH), # Minimal borders
119
+ textprops={'fontsize': 12, 'weight': 'normal',
120
+ 'color': LABEL_COLOR, 'fontfamily': 'monospace'}
121
+ )
122
+
123
+ # Enhanced percentage text styling for better readability
124
+ for autotext in autotexts:
125
+ autotext.set_color(BLACK) # Black text for better contrast
126
+ autotext.set_weight('bold')
127
+ autotext.set_fontsize(14)
128
+ autotext.set_fontfamily('monospace')
129
+
130
+ # Minimal category labels
131
+ for text in texts:
132
+ text.set_color(LABEL_COLOR)
133
+ text.set_weight('normal')
134
+ text.set_fontsize(13)
135
+ text.set_fontfamily('monospace')
136
+
137
+ # Device label closer to chart and bigger
138
+ ax.set_title(device_label, fontsize=DEVICE_TITLE_FONT_SIZE, weight='normal',
139
+ pad=DEVICE_TITLE_PAD, color=TITLE_COLOR, fontfamily='monospace')
140
+
141
+
142
+ def plot_model_stats(df: pd.DataFrame, model_name: str) -> tuple[plt.Figure, str, str]:
143
+ """Draws pie charts of model's passed, failed, skipped, and error stats for AMD and NVIDIA."""
144
+ # Handle case where the dataframe is empty or the model name could not be found in it
145
+ if df.empty or model_name not in df.index:
146
+ # Create empty stats for both devices
147
+ amd_filtered = {}
148
+ nvidia_filtered = {}
149
+ failed_multi_amd = failed_single_amd = failed_multi_nvidia = failed_single_nvidia = 0
150
+ failures_amd = failures_nvidia = {}
151
+ else:
152
+ row = df.loc[model_name]
153
 
154
+ # Extract and process model data
155
+ amd_stats, nvidia_stats, failed_multi_amd, failed_single_amd, failed_multi_nvidia, failed_single_nvidia = \
156
+ extract_model_data(row)
 
 
 
157
 
158
+ # Filter out categories with 0 values for cleaner visualization
159
+ amd_filtered = {k: v for k, v in amd_stats.items() if v > 0}
160
+ nvidia_filtered = {k: v for k, v in nvidia_stats.items() if v > 0}
 
 
 
161
 
162
+ # Generate failure info directly from dataframe
163
+ failures_amd = row.get('failures_amd', {})
164
+ failures_nvidia = row.get('failures_nvidia', {})
165
+
166
+ # Always create figure with two subplots side by side with padding
167
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(FIGURE_WIDTH_DUAL, FIGURE_HEIGHT_DUAL), facecolor=BLACK)
168
+ ax1.set_facecolor(BLACK)
169
+ ax2.set_facecolor(BLACK)
170
 
171
  # Create both pie charts with device labels
172
+ _create_pie_chart(ax1, "amd", amd_filtered)
173
+ _create_pie_chart(ax2, "nvidia", nvidia_filtered)
174
 
175
  # Add subtle separation line between charts - stops at device labels level
176
  line_x = 0.5
177
+ fig.add_artist(plt.Line2D([line_x, line_x], [0.0, SEPARATOR_LINE_Y_END],
178
+ color='#333333', linewidth=SEPARATOR_LINE_WIDTH,
179
+ alpha=SEPARATOR_ALPHA, transform=fig.transFigure))
180
 
181
  # Add central shared title for model name
182
+ fig.suptitle(f'{model_name.lower()}', fontsize=32, weight='bold',
183
+ color='#CCCCCC', fontfamily='monospace', y=MODEL_TITLE_Y)
 
184
 
185
  # Clean layout with padding and space for central title
186
  plt.tight_layout()
187
+ plt.subplots_adjust(top=SUBPLOT_TOP, wspace=SUBPLOT_WSPACE)
 
 
 
 
188
 
189
  amd_failed_info = extract_failure_info(failures_amd, 'AMD', failed_multi_amd, failed_single_amd)
190
  nvidia_failed_info = extract_failure_info(failures_nvidia, 'NVIDIA', failed_multi_nvidia, failed_single_nvidia)
summary_page.py CHANGED
@@ -1,5 +1,6 @@
1
  import matplotlib.pyplot as plt
2
  import pandas as pd
 
3
 
4
  # Layout parameters
5
  COLUMNS = 3
@@ -95,28 +96,8 @@ def create_summary_page(df: pd.DataFrame, available_models: list[str]) -> plt.Fi
95
 
96
  row = df.loc[model_name]
97
 
98
- # Get values directly from dataframe
99
- success_amd = int(row.get('success_amd', 0)) if pd.notna(row.get('success_amd', 0)) else 0
100
- success_nvidia = int(row.get('success_nvidia', 0)) if pd.notna(row.get('success_nvidia', 0)) else 0
101
- failed_multi_amd = int(row.get('failed_multi_no_amd', 0)) if pd.notna(row.get('failed_multi_no_amd', 0)) else 0
102
- failed_multi_nvidia = int(row.get('failed_multi_no_nvidia', 0)) if pd.notna(row.get('failed_multi_no_nvidia', 0)) else 0
103
- failed_single_amd = int(row.get('failed_single_no_amd', 0)) if pd.notna(row.get('failed_single_no_amd', 0)) else 0
104
- failed_single_nvidia = int(row.get('failed_single_no_nvidia', 0)) if pd.notna(row.get('failed_single_no_nvidia', 0)) else 0
105
-
106
- # Calculate stats
107
- amd_stats = {
108
- 'passed': success_amd,
109
- 'failed': failed_multi_amd + failed_single_amd,
110
- 'skipped': 0,
111
- 'error': 0
112
- }
113
-
114
- nvidia_stats = {
115
- 'passed': success_nvidia,
116
- 'failed': failed_multi_nvidia + failed_single_nvidia,
117
- 'skipped': 0,
118
- 'error': 0
119
- }
120
 
121
  # Calculate position in 4-column grid
122
  col = visible_model_count % COLUMNS
 
1
  import matplotlib.pyplot as plt
2
  import pandas as pd
3
+ from data import extract_model_data
4
 
5
  # Layout parameters
6
  COLUMNS = 3
 
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