Skip to content

Commit 61b420a

Browse files
Merge pull request #62 from AI-Hypercomputer/shangkun-fix-evaluation
Support nested outputs correctness check and use Xprof time instead of wall time by default
2 parents a1cdb72 + 2ded7e4 commit 61b420a

5 files changed

Lines changed: 105 additions & 22 deletions

File tree

MaxKernel/evaluation/benchmark.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ def benchmark(
153153
# Convert the new result to a dictionary
154154
res_dict = asdict(result)
155155
res_dict["speedup"] = result.speedup if result.speedup is not None else None
156+
res_dict["speed_up_xprof"] = (
157+
result.speed_up_xprof if result.speed_up_xprof is not None else None
158+
)
156159

157160
# Append to the main list and save immediately
158161
results.append(res_dict)

MaxKernel/evaluation/custom_types/evaluation_result.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from dataclasses import dataclass
2-
from typing import Optional
1+
from dataclasses import dataclass, field
2+
from typing import List, Optional
33

44

55
@dataclass
@@ -14,9 +14,16 @@ class EvaluationResult:
1414
xprof_reference_time_ms: float = 0.0
1515
xprof_optimized_time_ms: float = 0.0
1616
error_trace: Optional[str] = None
17+
logs: List[str] = field(default_factory=list)
1718

1819
@property
1920
def speedup(self) -> Optional[float]:
2021
if self.optimized_time_ms == 0 or self.reference_time_ms == 0:
2122
return None
2223
return self.reference_time_ms / self.optimized_time_ms
24+
25+
@property
26+
def speed_up_xprof(self) -> Optional[float]:
27+
if self.xprof_optimized_time_ms == 0 or self.xprof_reference_time_ms == 0:
28+
return None
29+
return self.xprof_reference_time_ms / self.xprof_optimized_time_ms

MaxKernel/evaluation/evaluation_utils.py

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,30 @@ def print_eval_result(result: EvaluationResult):
9090
print("-" * 40)
9191
print(f"Reference time: {result.reference_time_ms:.3f} ms")
9292
print(f"Optimized time: {result.optimized_time_ms:.3f} ms")
93-
speedup = result.speedup
94-
if speedup is not None:
95-
print(f"Speedup: {speedup:.2f}x")
93+
wall_time_speedup = result.speedup
94+
if wall_time_speedup is not None:
95+
print(f"Wall time speedup: {wall_time_speedup:.2f}x")
9696
else:
97-
print("Speedup: N/A")
97+
print("Wall time speedup: N/A")
98+
99+
xprof_speedup = result.speed_up_xprof
100+
if xprof_speedup is not None:
101+
print(f"XProf speedup: {xprof_speedup:.2f}x")
102+
else:
103+
print("XProf speedup: N/A")
104+
105+
if result.logs:
106+
print("Harness Logs:")
107+
for log_msg in result.logs:
108+
print(f" - {log_msg}")
98109
print("=" * 40 + "\n")
99110

100111

101112
def summarize_results(
102113
results: list,
103114
speedup_threshold: float,
104115
output_dir: Optional[str] = None,
116+
use_xprof_speedup: bool = True,
105117
) -> None:
106118
"""
107119
Calculates and prints summary statistics for a list of evaluation results.
@@ -110,6 +122,7 @@ def summarize_results(
110122
results: A list of dictionaries, where each dictionary is an evaluation result.
111123
speedup_threshold: The minimum speedup factor to consider an improvement.
112124
output_dir: Optional directory path to save the summary report and stats.
125+
use_xprof_speedup: Whether to use XProf speedup for the summary.
113126
"""
114127
total_attempted = len(results)
115128
if not total_attempted:
@@ -123,9 +136,20 @@ def summarize_results(
123136
num_correct = len(correct_tasks)
124137

125138
# Speedup calculations should only be on tasks that are numerically correct.
126-
speedups = [
127-
r["speedup"] for r in correct_tasks if r.get("speedup") is not None
128-
]
139+
speedups = []
140+
for r in correct_tasks:
141+
s = None
142+
if use_xprof_speedup:
143+
if r.get("speed_up_xprof") is not None:
144+
s = r["speed_up_xprof"]
145+
elif r.get("speedup") is not None:
146+
s = r["speedup"]
147+
else:
148+
if r.get("speedup") is not None:
149+
s = r["speedup"]
150+
151+
if s is not None:
152+
speedups.append(s)
129153

130154
improvements = [s for s in speedups if s > speedup_threshold]
131155
num_improved = len(improvements)
@@ -214,7 +238,9 @@ def summarize_results(
214238
logger.info(f"Saved evaluation summary and stats to {output_dir}")
215239

216240

217-
def visualize_speed_up(results: list, output_dir: str) -> None:
241+
def visualize_speed_up(
242+
results: list, output_dir: str, use_xprof_speedup: bool = True
243+
) -> None:
218244
"""
219245
Visualizes the evaluation results.
220246
@@ -223,6 +249,7 @@ def visualize_speed_up(results: list, output_dir: str) -> None:
223249
output_dir: Directory path to save the output PNG files.
224250
Will generate speedup_distribution.png and
225251
speedup_barplot.png in this directory.
252+
use_xprof_speedup: Whether to use XProf speedup for the visualization.
226253
"""
227254
os.makedirs(output_dir, exist_ok=True)
228255

@@ -238,7 +265,16 @@ def set_log_ticks(ax, log_values):
238265
plot_data = []
239266
for r in results:
240267
is_valid = r.get("compiled_successfully") and r.get("numerically_correct")
241-
s = r.get("speedup") if is_valid else None
268+
s = None
269+
if is_valid:
270+
if use_xprof_speedup:
271+
if r.get("speed_up_xprof") is not None:
272+
s = r["speed_up_xprof"]
273+
else:
274+
s = r.get("speedup")
275+
else:
276+
s = r.get("speedup")
277+
242278
log_s = math.log2(s) if s and s > 0 else -10.0
243279
plot_data.append((r["task_id"], log_s, not is_valid))
244280

MaxKernel/evaluation/harness_code.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,25 @@ def main():
149149
out_base_cpu = jax.device_get(out_base)
150150
del out_base
151151
152+
harness_logs = []
153+
154+
# Dirty all HBM memory leaves with NaN / Sentinel values to prevent cache reuse
155+
try:
156+
for leaf in jax.tree_util.tree_leaves(out_base_cpu):
157+
if hasattr(leaf, "shape") and hasattr(leaf, "dtype"):
158+
if jnp.issubdtype(leaf.dtype, jnp.floating) or jnp.issubdtype(leaf.dtype, jnp.complexfloating):
159+
val = jnp.nan
160+
elif jnp.issubdtype(leaf.dtype, jnp.bool_):
161+
val = True
162+
else:
163+
val = 123 # Fits within int8/uint8 and all larger integer dtypes
164+
165+
dummy = jnp.full(leaf.shape, val, dtype=leaf.dtype)
166+
dummy.block_until_ready()
167+
del dummy
168+
except Exception as e:
169+
harness_logs.append(f"Failed to dirty HBM memory: {e}")
170+
152171
try:
153172
jit_optimized = jax.jit(optimized_mod.computation, static_argnums=static_argnums)
154173
out_optimized = jax.block_until_ready(jit_optimized(*args))
@@ -160,16 +179,23 @@ def main():
160179
"error": str(e),
161180
"traceback": traceback.format_exc()
162181
}
182+
if harness_logs:
183+
result["logs"] = harness_logs
163184
with open("result.json", "w", encoding="utf-8") as f:
164185
json.dump(result, f)
165186
return
166187
167-
is_correct = jnp.allclose(out_base_cpu,
168-
out_optimized_cpu,
169-
atol={atol},
170-
rtol={rtol})
171-
max_abs_diff = float(jnp.max(jnp.abs(out_base_cpu - out_optimized_cpu)))
172-
max_rel_diff = float(jnp.max(jnp.abs((out_base_cpu - out_optimized_cpu) / out_base_cpu)))
188+
out_base_flat = jax.tree_util.tree_leaves(out_base_cpu)
189+
out_optimized_flat = jax.tree_util.tree_leaves(out_optimized_cpu)
190+
191+
is_correct = True
192+
max_abs_diff = 0.0
193+
max_rel_diff = 0.0
194+
195+
for b, o in zip(out_base_flat, out_optimized_flat):
196+
is_correct = is_correct and bool(jnp.allclose(b, o, atol={atol}, rtol={rtol}))
197+
max_abs_diff = max(max_abs_diff, float(jnp.max(jnp.abs(b - o))))
198+
max_rel_diff = max(max_rel_diff, float(jnp.max(jnp.abs((b - o) / b))))
173199
174200
if not is_correct:
175201
result = {
@@ -178,6 +204,8 @@ def main():
178204
"max_abs_diff": max_abs_diff,
179205
"max_rel_diff": max_rel_diff,
180206
}
207+
if harness_logs:
208+
result["logs"] = harness_logs
181209
with open("result.json", "w", encoding="utf-8") as f:
182210
json.dump(result, f)
183211
return
@@ -195,6 +223,8 @@ def main():
195223
"xprof_reference_time_ms": xprof_time_base,
196224
"xprof_optimized_time_ms": xprof_time_optimized,
197225
}
226+
if harness_logs:
227+
result["logs"] = harness_logs
198228
with open("result.json", "w", encoding="utf-8") as f:
199229
json.dump(result, f)
200230
except Exception as e:

MaxKernel/evaluation/xprof_utils.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
def extract_xprof_time(
7-
trace_dir: str, event_name: str, num_runs: int = 20
7+
trace_dir: str, event_name: str, num_runs: int = 20, line_name: str = None
88
) -> float:
99
"""Extracts execution time for a named event from xprof trace.
1010
@@ -80,12 +80,19 @@ def extract_xprof_time(
8080
except Exception as e:
8181
logging.warning(f"Failed to parse {file_path}: {e}")
8282

83-
if xla_op_events:
84-
logging.info("Found kernel events starting with %. Using them.")
85-
target_events = xla_op_events
83+
if line_name == "XLA Ops":
84+
if xla_op_events:
85+
logging.info(f"Using XLA Ops events. Found {len(xla_op_events)} events.")
86+
target_events = xla_op_events
87+
else:
88+
logging.info(
89+
"XLA Ops events requested but not found. Falling back to XLA Modules events."
90+
)
91+
target_events = xla_module_events
8692
else:
93+
# Default behavior or explicit "XLA Modules"
8794
logging.info(
88-
"No xla_op_events events found. Falling back to xla_module_events matched events."
95+
f"Using XLA Modules events. Found {len(xla_module_events)} events."
8996
)
9097
target_events = xla_module_events
9198

0 commit comments

Comments
 (0)