Skip to content

Commit e0a7a55

Browse files
committed
Merge branch 'origin/main' into luke-maxkernel-updates-after-testing
2 parents b43f3fb + a1cdb72 commit e0a7a55

3 files changed

Lines changed: 171 additions & 100 deletions

File tree

MaxKernel/auto_agent/subagents/kernel_writing/agent.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
from auto_agent.callbacks import (
1212
add_pallas_docs,
13-
add_workdir_callback,
1413
extract_fix_summary,
1514
get_tpu_version_callback,
1615
load_kernel_and_plan_to_state,
@@ -279,7 +278,6 @@ async def _run_async_impl(
279278
before_agent_callback=[
280279
add_pallas_docs,
281280
get_tpu_version_callback,
282-
add_workdir_callback,
283281
],
284282
)
285283

MaxKernel/auto_agent/subagents/pipeline_agent.py

Lines changed: 164 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
import re
66
import shutil
7-
from typing import AsyncGenerator
7+
from typing import AsyncGenerator, Optional
88

99
from google.adk.agents import BaseAgent
1010
from google.adk.agents.invocation_context import InvocationContext
@@ -26,6 +26,8 @@ class AutonomousPipelineAgent(BaseAgent):
2626
autotune_agent: BaseAgent
2727
profile_agent: BaseAgent
2828
max_iterations: int = 2
29+
session_dir: Optional[str] = None
30+
end_agent: Optional[str] = None
2931

3032
def __init__(
3133
self,
@@ -38,6 +40,8 @@ def __init__(
3840
autotune_agent: BaseAgent,
3941
profile_agent: BaseAgent,
4042
max_iterations: int = 2,
43+
session_dir: Optional[str] = None,
44+
end_agent: Optional[str] = None,
4145
):
4246
super().__init__(
4347
name=name,
@@ -49,6 +53,8 @@ def __init__(
4953
autotune_agent=autotune_agent,
5054
profile_agent=profile_agent,
5155
max_iterations=max_iterations,
56+
session_dir=session_dir,
57+
end_agent=end_agent,
5258
)
5359

5460
async def _run_async_impl(
@@ -68,10 +74,19 @@ async def _run_async_impl(
6874
async for event in self.plan_agent.run_async(ctx):
6975
yield event
7076

77+
self._clear_iteration_metrics(ctx)
78+
79+
if self._should_end_at_step(ctx, iteration, "plan"):
80+
iteration += 1
81+
continue
82+
7183
# Step 2: Implement
7284
logging.info(f"[{self.name}] Running ImplementKernelAgent...")
7385
async for event in self.implement_agent.run_async(ctx):
7486
yield event
87+
if self._should_end_at_step(ctx, iteration, "implement"):
88+
iteration += 1
89+
continue
7590

7691
# Step 3: Validate
7792
logging.info(f"[{self.name}] Running ValidateKernelCompilationAgent...")
@@ -86,12 +101,16 @@ async def _run_async_impl(
86101
logging.error(
87102
f"[{self.name}] Compilation failed. Looping back to planning."
88103
)
89-
self._save_iteration_files(
90-
ctx, iteration, keys_to_save=["optimized_kernel_path"]
104+
self._save_iteration_files_and_snapshot(
105+
ctx, iteration, step_name="validate"
91106
)
92107
iteration += 1
93108
continue
94109

110+
if self._should_end_at_step(ctx, iteration, "validate"):
111+
iteration += 1
112+
continue
113+
95114
# Step 4: Test Gen
96115
logging.info(f"[{self.name}] Running ValidatedTestGenerationAgent...")
97116
async for event in self.test_gen_agent.run_async(ctx):
@@ -103,14 +122,16 @@ async def _run_async_impl(
103122
logging.error(
104123
f"[{self.name}] Test generation/validation failed. Looping back to planning."
105124
)
106-
self._save_iteration_files(
107-
ctx,
108-
iteration,
109-
keys_to_save=["optimized_kernel_path", "test_file_path"],
125+
self._save_iteration_files_and_snapshot(
126+
ctx, iteration, step_name="test_gen"
110127
)
111128
iteration += 1
112129
continue
113130

131+
if self._should_end_at_step(ctx, iteration, "test_gen"):
132+
iteration += 1
133+
continue
134+
114135
# Step 5: Test Run
115136
logging.info(f"[{self.name}] Running UnifiedTestAgent...")
116137
async for event in self.test_run_agent.run_async(ctx):
@@ -120,62 +141,40 @@ async def _run_async_impl(
120141
test_results = ctx.session.state.get("test_results", {})
121142
if not test_results.get("success", False):
122143
logging.error(f"[{self.name}] Tests failed. Looping back to planning.")
123-
self._save_iteration_files(
124-
ctx,
125-
iteration,
126-
keys_to_save=["optimized_kernel_path", "test_file_path"],
144+
self._save_iteration_files_and_snapshot(
145+
ctx, iteration, step_name="test_run"
127146
)
128147
iteration += 1
129148
continue
130149

150+
if self._should_end_at_step(ctx, iteration, "test_run"):
151+
iteration += 1
152+
continue
153+
131154
# Step 6: Autotune
132155
logging.info(f"[{self.name}] Running AutotuneAgent...")
133156
async for event in self.autotune_agent.run_async(ctx):
134157
yield event
158+
if self._should_end_at_step(ctx, iteration, "autotune"):
159+
iteration += 1
160+
continue
135161

136162
# Step 7: Profile
137163
logging.info(f"[{self.name}] Running ProfileAgentOrchestrator...")
138164
async for event in self.profile_agent.run_async(ctx):
139165
yield event
166+
if self._should_end_at_step(ctx, iteration, "profile"):
167+
iteration += 1
168+
continue
140169

141-
# Snapshot intermediate result
142-
kernel_path = ctx.session.state.get("optimized_kernel_path")
143-
kernel_code = ""
144-
if kernel_path and os.path.exists(kernel_path):
145-
try:
146-
with open(kernel_path, "r") as f:
147-
kernel_code = f.read()
148-
except Exception as e:
149-
logging.error(
150-
f"[{self.name}] Failed to read kernel file for snapshot: {e}"
151-
)
152-
153-
# Extract latency
154-
latency = self._extract_latency(ctx)
155-
156-
snapshot = {
157-
"iteration": iteration,
158-
"kernel_code": kernel_code,
159-
"compilation_status": ctx.session.state.get(
160-
"kernel_compilation_status", {}
161-
),
162-
"test_status": ctx.session.state.get("test_results", {}),
163-
"latency_ms": latency,
164-
"profiling_summary": ctx.session.state.get("profiling_summary", ""),
165-
}
166-
current_history = ctx.session.state.get("history", [])
167-
updated_history = current_history + [snapshot]
168-
ctx.session.state["history"] = (
169-
updated_history # Ensure local consistency within the loop
170-
)
170+
self._save_iteration_files_and_snapshot(ctx, iteration)
171171

172172
yield Event(
173173
author=self.name,
174-
actions=EventActions(state_delta={"history": updated_history}),
174+
actions=EventActions(
175+
state_delta={"history": ctx.session.state.get("history", [])}
176+
),
175177
)
176-
logging.info(f"[{self.name}] Saved snapshot for iteration {iteration}")
177-
178-
self._save_iteration_files(ctx, iteration)
179178

180179
# Step 7: Check if improvement is needed
181180
# needs_improvement = ctx.session.state.get("needs_improvement", False)
@@ -209,20 +208,83 @@ async def _run_async_impl(
209208
),
210209
)
211210

212-
def _save_iteration_files(
211+
def _should_end_at_step(
212+
self, ctx: InvocationContext, iteration: int, step_name: str
213+
) -> bool:
214+
"""Checks if the pipeline should terminate at the current step."""
215+
if self.end_agent == step_name:
216+
logging.info(
217+
f"[{self.name}] Ending iteration {iteration} early at step '{step_name}'."
218+
)
219+
self._save_iteration_files_and_snapshot(
220+
ctx, iteration, step_name=step_name
221+
)
222+
return True
223+
return False
224+
225+
def _record_history_snapshot(self, ctx: InvocationContext, iteration: int):
226+
"""Records the iteration snapshot into the session history."""
227+
current_history = ctx.session.state.get("history", [])
228+
if any(s.get("iteration") == iteration for s in current_history):
229+
return
230+
231+
kernel_path = ctx.session.state.get("optimized_kernel_path")
232+
kernel_code = ""
233+
if kernel_path and os.path.exists(kernel_path):
234+
try:
235+
with open(kernel_path, "r") as f:
236+
kernel_code = f.read()
237+
except Exception as e:
238+
logging.error(
239+
f"[{self.name}] Failed to read kernel file for snapshot: {e}"
240+
)
241+
242+
latency = self._extract_latency(ctx)
243+
244+
snapshot = {
245+
"iteration": iteration,
246+
"kernel_code": kernel_code,
247+
"compilation_status": ctx.session.state.get(
248+
"kernel_compilation_status", {}
249+
),
250+
"test_status": ctx.session.state.get("test_results", {}),
251+
"latency_ms": latency,
252+
"profiling_summary": ctx.session.state.get("profiling_summary", ""),
253+
}
254+
255+
updated_history = current_history + [snapshot]
256+
ctx.session.state["history"] = updated_history
257+
logging.info(f"[{self.name}] Saved snapshot for iteration {iteration}")
258+
259+
def _save_iteration_files_and_snapshot(
213260
self,
214261
ctx: InvocationContext,
215262
iteration: int,
216-
keys_to_save: list[str] | None = None,
263+
step_name: Optional[str] = None,
217264
):
218265
"""Saves artifacts with an iteration suffix."""
219-
if keys_to_save is None:
220-
keys_to_save = [
221-
"optimized_kernel_path",
222-
"test_file_path",
223-
"autotune_specs_path",
224-
"autotune_results_path",
225-
]
266+
all_keys = [
267+
"kernel_plan_path",
268+
"optimized_kernel_path",
269+
"test_file_path",
270+
"autotune_specs_path",
271+
"autotune_results_path",
272+
]
273+
if step_name is not None:
274+
step_to_last_key = {
275+
"plan": "kernel_plan_path",
276+
"implement": "optimized_kernel_path",
277+
"validate": "optimized_kernel_path",
278+
"test_gen": "test_file_path",
279+
"test_run": "test_file_path",
280+
"autotune": "autotune_results_path",
281+
"profile": "autotune_results_path",
282+
}
283+
last_key = step_to_last_key.get(step_name, "autotune_results_path")
284+
keys_to_save = all_keys[: all_keys.index(last_key) + 1]
285+
else:
286+
keys_to_save = all_keys
287+
226288
for path_key in keys_to_save:
227289
path = ctx.session.state.get(path_key)
228290
if path and os.path.exists(path):
@@ -238,14 +300,36 @@ def _save_iteration_files(
238300
f"[{self.name}] Failed to copy {path_key} to {new_path}: {e}"
239301
)
240302

303+
self._record_history_snapshot(ctx, iteration)
304+
305+
def _clear_iteration_metrics(self, ctx: InvocationContext):
306+
"""Clears iteration-specific metrics to avoid carrying over stale data."""
307+
for key in [
308+
"kernel_compilation_status",
309+
"validation_loop_status",
310+
"test_results",
311+
"autotune_results",
312+
"profiling_summary",
313+
]:
314+
ctx.session.state.pop(key, None)
315+
241316
def _initialize_state(self, ctx: InvocationContext) -> Event:
242317
"""Initializes session state with standard paths and returns the event."""
243318
# Initialize history
244319
if "history" not in ctx.session.state:
245320
ctx.session.state["history"] = []
246321

247322
# Path related states
248-
session_dir = os.path.join(WORKDIR, ctx.session.id)
323+
session_dir = self.session_dir or os.path.join(WORKDIR, ctx.session.id)
324+
325+
# Ensure session_dir is under WORKDIR
326+
abs_session_dir = os.path.abspath(session_dir)
327+
abs_workdir = os.path.abspath(WORKDIR)
328+
if os.path.commonpath([abs_workdir, abs_session_dir]) != abs_workdir:
329+
raise ValueError(
330+
f"session_dir ({session_dir}) must be a subdirectory of WORKDIR ({WORKDIR})"
331+
)
332+
249333
os.makedirs(session_dir, exist_ok=True)
250334

251335
if "workdir" not in ctx.session.state:
@@ -406,34 +490,32 @@ async def _apply_best_solution(self, ctx: InvocationContext):
406490
f"[{self.name}] Best solution found from iteration {best_solution['iteration']}"
407491
)
408492

409-
# Rollback if needed
410-
current_code = ""
411-
kernel_path = ctx.session.state.get("optimized_kernel_path")
412-
if kernel_path and os.path.exists(kernel_path):
413-
try:
414-
with open(kernel_path, "r") as f:
415-
current_code = f.read()
416-
except Exception as e:
417-
logging.error(
418-
f"[{self.name}] Failed to read current kernel file: {e}"
419-
)
493+
# Rollback all relevant files to the best iteration's state
494+
best_iter = best_solution["iteration"]
495+
keys_to_rollback = [
496+
"optimized_kernel_path",
497+
"kernel_plan_path",
498+
"test_file_path",
499+
"autotune_specs_path",
500+
"autotune_results_path",
501+
]
420502

421-
if best_solution["kernel_code"] != current_code:
422-
logging.info(
423-
f"[{self.name}] Reverting kernel file to best solution from iteration {best_solution['iteration']}"
424-
)
425-
if kernel_path:
426-
try:
427-
with open(kernel_path, "w") as f:
428-
f.write(best_solution["kernel_code"])
429-
except Exception as e:
430-
logging.error(
431-
f"[{self.name}] Failed to write best solution to file: {e}"
432-
)
433-
else:
434-
logging.info(
435-
f"[{self.name}] Current file is already the best solution."
436-
)
503+
logging.info(
504+
f"[{self.name}] Restoring files to match best solution from iteration {best_iter}"
505+
)
506+
507+
for path_key in keys_to_rollback:
508+
base_path = ctx.session.state.get(path_key)
509+
if base_path:
510+
directory, filename = os.path.split(base_path)
511+
name, ext = os.path.splitext(filename)
512+
suffixed_path = os.path.join(directory, f"{name}_{best_iter}{ext}")
513+
514+
if os.path.exists(suffixed_path):
515+
try:
516+
shutil.copy2(suffixed_path, base_path)
517+
except Exception as e:
518+
logging.error(f"[{self.name}] Failed to rollback {path_key}: {e}")
437519

438520
return best_solution
439521

0 commit comments

Comments
 (0)