166 lines
6.5 KiB
Python
166 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Conservative research debugging without automatic patching."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Dict, List
|
|
|
|
|
|
CATEGORY_RULES = [
|
|
("cuda_oom", ["cuda out of memory", "outofmemoryerror", "out of memory"]),
|
|
("checkpoint_mismatch", ["size mismatch", "missing key", "unexpected key", "checkpoint"]),
|
|
("distributed_issue", ["nccl", "distributed", "ddp", "rank"]),
|
|
("device_mismatch", ["expected all tensors to be on the same device", "same device"]),
|
|
("shape_mismatch", ["shape", "dimension", "size mismatch"]),
|
|
("loss_nan", ["loss is nan", "nan", "not converging"]),
|
|
("file_missing", ["filenotfounderror", "no such file", "cannot find path"]),
|
|
]
|
|
|
|
|
|
def classify_error(text: str) -> str:
|
|
lower = text.lower()
|
|
for category, signals in CATEGORY_RULES:
|
|
if any(signal in lower for signal in signals):
|
|
return category
|
|
if "traceback" in lower or "runtimeerror" in lower or "valueerror" in lower:
|
|
return "runtime_failure"
|
|
return "unknown"
|
|
|
|
|
|
def suggested_actions(category: str) -> List[str]:
|
|
mapping = {
|
|
"cuda_oom": [
|
|
"Check effective batch size, input resolution, and mixed-precision settings before patching model code.",
|
|
"Prefer a configuration-only reduction before touching architecture.",
|
|
],
|
|
"checkpoint_mismatch": [
|
|
"Verify checkpoint source, model variant, and load strictness assumptions.",
|
|
"Confirm whether the mismatch is expected before introducing compatibility code.",
|
|
],
|
|
"distributed_issue": [
|
|
"Inspect launch command, world size, and environment variables before patching training logic.",
|
|
"Reproduce with a single process when possible to narrow the issue safely.",
|
|
],
|
|
"device_mismatch": [
|
|
"Trace where tensors and modules move across CPU and GPU boundaries.",
|
|
"Prefer a minimal device-placement fix over a broad refactor.",
|
|
],
|
|
"shape_mismatch": [
|
|
"Log tensor shapes at the failing boundary without changing unrelated code paths.",
|
|
"Check config, dataset, and head dimensions before editing model internals.",
|
|
],
|
|
"loss_nan": [
|
|
"Inspect data ranges, loss inputs, mixed precision, and learning rate before changing architecture.",
|
|
"Use a shorter controlled run to confirm whether NaNs appear at startup or later.",
|
|
],
|
|
"file_missing": [
|
|
"Validate dataset, checkpoint, and config paths before editing code.",
|
|
"Prefer a path fix or documented setup correction over logic changes.",
|
|
],
|
|
"runtime_failure": [
|
|
"Trace the failing file and symbol before proposing any patch.",
|
|
"Confirm whether the failure is environment-related, config-related, or code-related.",
|
|
],
|
|
"unknown": [
|
|
"Collect the full command, stack trace, and recent code change before patching anything.",
|
|
"Narrow the failure surface with the smallest reproducible example available.",
|
|
],
|
|
}
|
|
return mapping[category]
|
|
|
|
|
|
def analyze_error(text: str) -> Dict[str, object]:
|
|
category = classify_error(text)
|
|
needs_savepoint = category in {"checkpoint_mismatch", "distributed_issue", "shape_mismatch", "loss_nan"}
|
|
return {
|
|
"category": category,
|
|
"summary": f"Detected debug category: `{category}`.",
|
|
"needs_explicit_patch_approval": True,
|
|
"needs_savepoint_before_patch": needs_savepoint,
|
|
"actions": suggested_actions(category),
|
|
"error_excerpt": "\n".join(text.splitlines()[:12]) or text,
|
|
}
|
|
|
|
|
|
def write_outputs(output_dir: Path, data: Dict[str, object]) -> None:
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
diagnosis = [
|
|
"# Debug Diagnosis",
|
|
"",
|
|
f"- Category: `{data['category']}`",
|
|
f"- Patch authorized: `False`",
|
|
f"- Savepoint recommended before patching: `{data['needs_savepoint_before_patch']}`",
|
|
"",
|
|
"## Error excerpt",
|
|
"",
|
|
"```text",
|
|
data["error_excerpt"],
|
|
"```",
|
|
"",
|
|
"## Conservative analysis",
|
|
"",
|
|
data["summary"],
|
|
"",
|
|
]
|
|
(output_dir / "DIAGNOSIS.md").write_text("\n".join(diagnosis), encoding="utf-8")
|
|
|
|
patch_plan = [
|
|
"# Patch Plan",
|
|
"",
|
|
"- Do not modify repository code until the researcher approves the proposed fix.",
|
|
"- Prefer the smallest configuration or path fix before touching core model logic.",
|
|
f"- Savepoint recommended: `{data['needs_savepoint_before_patch']}`",
|
|
"",
|
|
"## Suggested actions",
|
|
"",
|
|
*[f"- {item}" for item in data["actions"]],
|
|
"",
|
|
]
|
|
(output_dir / "PATCH_PLAN.md").write_text("\n".join(patch_plan), encoding="utf-8")
|
|
|
|
status = {
|
|
"schema_version": "1.0",
|
|
"status": "diagnosed",
|
|
"category": data["category"],
|
|
"patch_authorized": False,
|
|
"needs_explicit_patch_approval": data["needs_explicit_patch_approval"],
|
|
"needs_savepoint_before_patch": data["needs_savepoint_before_patch"],
|
|
"suggested_actions": data["actions"],
|
|
"outputs": {
|
|
"diagnosis": "debug_outputs/DIAGNOSIS.md",
|
|
"patch_plan": "debug_outputs/PATCH_PLAN.md",
|
|
"status": "debug_outputs/status.json",
|
|
},
|
|
}
|
|
(output_dir / "status.json").write_text(json.dumps(status, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Conservative deep learning research debugging.")
|
|
parser.add_argument("--error-file", help="Path to a text file containing the error or symptom.")
|
|
parser.add_argument("--error-text", help="Inline error or symptom text.")
|
|
parser.add_argument("--output-dir", default="debug_outputs", help="Directory for debug outputs.")
|
|
parser.add_argument("--json", action="store_true", help="Emit JSON to stdout instead of writing files.")
|
|
args = parser.parse_args()
|
|
|
|
if not args.error_file and not args.error_text:
|
|
raise SystemExit("Provide --error-file or --error-text.")
|
|
|
|
text = args.error_text or Path(args.error_file).read_text(encoding="utf-8", errors="ignore")
|
|
data = analyze_error(text)
|
|
if args.json:
|
|
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
return 0
|
|
|
|
write_outputs(Path(args.output_dir).resolve(), data)
|
|
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|