Files
TheRock/rocm-systems/experimental/python/perfxpert/tests/test_agents/test_recommendation.py
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

219 lines
8.3 KiB
Python

"""Isolation tests for Recommendation decision-maker (Layer 1)."""
import pytest
from unittest.mock import MagicMock
from perfxpert.agents import recommendation as rec_module
from perfxpert.agents import schemas
from perfxpert.agents.framework import (
FakeProviderResponse, HandoffPolicyViolation, dispatch_handoff,
)
from perfxpert.tools import profiling
def _findings(bottleneck: str = "compute") -> schemas.AnalysisOutput:
return schemas.AnalysisOutput(
primary_bottleneck=bottleneck,
confidence=0.85,
time_breakdown={"kernel_pct": 0.9, "memcpy_pct": 0.05, "api_pct": 0.03, "idle_pct": 0.02},
hot_kernels=[{"name": "[K1]", "pct": 0.6}],
counter_data_available=True,
)
def test_recommendation_agent_builds():
agent = rec_module.build_recommendation_agent()
assert agent.name == "Recommendation"
assert agent.layer == 1
def test_recommendation_tool_count():
agent = rec_module.build_recommendation_agent()
# 3 tools: plateau.check, trace_fingerprint.fingerprint, profiling.fill_gap
assert 1 <= len(agent.tools) <= 5
def test_recommendation_binds_real_fill_gap_tool():
agent = rec_module.build_recommendation_agent()
tool_map = {tool.name: tool.fn for tool in agent.tools}
assert tool_map["profiling.fill_gap"] is profiling.fill_gap
def test_recommendation_no_execution_tools():
agent = rec_module.build_recommendation_agent()
forbidden = {"patch.apply", "compile.build", "profile.run", "anchors.check"}
declared = {t.name for t in agent.tools}
assert not (declared & forbidden)
def test_recommendation_handoff_whitelist_is_specialists_only():
agent = rec_module.build_recommendation_agent()
assert set(agent.allowed_handoffs) == {
"compute_specialist", "memory_specialist", "latency_specialist",
}
def test_recommendation_cannot_handoff_to_analysis_or_correctness():
agent = rec_module.build_recommendation_agent()
with pytest.raises(HandoffPolicyViolation):
dispatch_handoff(agent, "analysis")
with pytest.raises(HandoffPolicyViolation):
dispatch_handoff(agent, "correctness")
def test_recommendation_routes_compute_bottleneck_to_compute_specialist(monkeypatch):
called = {}
def fake_compute(payload, **kw):
called["compute"] = True
called["payload"] = payload
return schemas.ComputeSpecialistOutput(
techniques=[{"name": "launch_bounds"}], confidence=0.9,
)
monkeypatch.setattr(rec_module, "_run_specialist_compute", fake_compute)
result = rec_module.run_recommendation(
schemas.RecommendationInput(findings=_findings("compute"), gfx_id="gfx950"),
airgap=True, # skip LLM, exercise routing
)
assert called.get("compute") is True
assert called["payload"].gfx_id == "gfx950"
assert result.specialist_used == "compute"
def test_recommendation_routes_memory_bottleneck_to_memory_specialist(monkeypatch):
called = {}
def fake_memory(payload, **kw):
called["memory"] = True
called["payload"] = payload
return schemas.MemorySpecialistOutput(
techniques=[{"name": "coalesce"}], confidence=0.9,
)
monkeypatch.setattr(rec_module, "_run_specialist_memory", fake_memory)
result = rec_module.run_recommendation(
schemas.RecommendationInput(findings=_findings("memory_transfer"), gfx_id="gfx950"),
airgap=True,
)
assert called.get("memory") is True
assert called["payload"].gfx_id == "gfx950"
assert result.specialist_used == "memory"
def test_recommendation_routes_latency_bottleneck_to_latency_specialist(monkeypatch):
called = {}
def fake_latency(payload, **kw):
called["latency"] = True
called["payload"] = payload
return schemas.LatencySpecialistOutput(
techniques=[{"name": "fuse_kernels"}], confidence=0.9,
)
monkeypatch.setattr(rec_module, "_run_specialist_latency", fake_latency)
result = rec_module.run_recommendation(
schemas.RecommendationInput(findings=_findings("latency"), gfx_id="gfx950"),
airgap=True,
)
assert called.get("latency") is True
assert called["payload"].gfx_id == "gfx950"
assert result.specialist_used == "latency"
def test_recommendation_dedups_seen_hashes(monkeypatch):
"""Recommendation drops techniques whose hash is in seen_recommendation_hashes."""
def fake_compute(payload, **kw):
return schemas.ComputeSpecialistOutput(
techniques=[
{"name": "launch_bounds", "rationale": "r1"},
{"name": "mfma_enablement", "rationale": "r2"},
],
confidence=0.9,
)
monkeypatch.setattr(rec_module, "_run_specialist_compute", fake_compute)
# Compute a hash matching the first technique
import hashlib, json
h = hashlib.sha256(json.dumps({"name": "launch_bounds"}, sort_keys=True).encode()).hexdigest()
result = rec_module.run_recommendation(
schemas.RecommendationInput(
findings=_findings("compute"),
gfx_id="gfx950",
seen_recommendation_hashes=[h],
),
airgap=True,
)
# The "launch_bounds" recommendation should be filtered out (exact hash logic
# may differ in impl; this test asserts the count decreased).
assert len(result.recommendations) <= 1
def test_recommendation_plateau_detection(monkeypatch):
"""If plateau.check returns True → plateau_detected=True in output."""
monkeypatch.setattr(rec_module, "_plateau_check",
lambda history: {"plateau_detected": True, "iterations": 3})
def fake_compute(payload, **kw):
return schemas.ComputeSpecialistOutput(techniques=[], confidence=0.0)
monkeypatch.setattr(rec_module, "_run_specialist_compute", fake_compute)
result = rec_module.run_recommendation(
schemas.RecommendationInput(findings=_findings("compute"), gfx_id="gfx950"),
airgap=True,
)
assert result.plateau_detected is True
def test_recommendation_mixed_bottleneck_yields_no_specialist(monkeypatch):
"""Mixed/api_overhead falls through to 'none' specialist."""
result = rec_module.run_recommendation(
schemas.RecommendationInput(findings=_findings("mixed"), gfx_id="gfx950"),
airgap=True,
)
assert result.specialist_used == "none"
def test_recommendation_mixed_bottleneck_emits_triage():
"""'mixed' bottleneck must produce a non-empty triage recommendation, not silent empty."""
result = rec_module.run_recommendation(
schemas.RecommendationInput(findings=_findings("mixed"), gfx_id="gfx950"),
airgap=True,
)
assert result.specialist_used == "none"
# Must have at least one triage technique — not silently empty
assert len(result.recommendations) >= 1, (
"Expected at least one triage recommendation for 'mixed' bottleneck, got none."
)
triage = result.recommendations[0]
assert triage.get("name") == "mixed_bottleneck_triage", (
f"Expected 'mixed_bottleneck_triage', got {triage.get('name')!r}"
)
assert "ATT" in triage.get("description", ""), (
"Triage recommendation must mention ATT as the next step."
)
def test_recommendation_raises_on_unknown_bottleneck(monkeypatch):
"""An unhandled bottleneck type must raise ValueError, not silently return empty.
The Pydantic schema validates known types, so we bypass it by patching
the findings object's primary_bottleneck attribute to inject a rogue value.
This verifies the else-branch in run_recommendation raises ValueError.
"""
findings = _findings("compute")
# Bypass schema validation by replacing the frozen field via object.__setattr__
object.__setattr__(findings, "primary_bottleneck", "totally_unknown_type")
payload = schemas.RecommendationInput(findings=_findings("compute"), gfx_id="gfx950")
# Patch findings on the payload to inject the unknown type
object.__setattr__(payload.findings, "primary_bottleneck", "totally_unknown_type")
with pytest.raises(ValueError, match="unhandled bottleneck type"):
rec_module.run_recommendation(payload, airgap=True)
def test_recommendation_requires_non_empty_gfx_id_for_specialist_routing():
with pytest.raises(ValueError, match="gfx_id"):
rec_module.run_recommendation(
schemas.RecommendationInput(findings=_findings("compute"), gfx_id=""),
airgap=True,
)