Add deep training acceptance checks
This commit is contained in:
@@ -62,6 +62,66 @@ MMSEG_FULL_BUILD_SNIPPET = (
|
||||
)
|
||||
|
||||
|
||||
SEGMODEL_TRAIN_STEP_SNIPPET = (
|
||||
"import torch, segmentation_models_pytorch as smp; "
|
||||
"torch.manual_seed(7); "
|
||||
"model=smp.Unet(encoder_name='resnet18', encoder_weights=None, classes=2).train(); "
|
||||
"inputs=torch.randn(2,3,64,64); "
|
||||
"targets=torch.randint(0,2,(2,64,64)); "
|
||||
"optimizer=torch.optim.SGD(model.parameters(), lr=1e-3); "
|
||||
"outputs=model(inputs); "
|
||||
"loss=torch.nn.functional.cross_entropy(outputs, targets); "
|
||||
"loss.backward(); optimizer.step(); "
|
||||
"print('loss', round(float(loss.detach()), 6), 'shape', tuple(outputs.shape))"
|
||||
)
|
||||
|
||||
|
||||
def _yolo_tiny_train_snippet(root: Path, weight: Path) -> str:
|
||||
return (
|
||||
"import shutil, cv2, numpy as np; "
|
||||
"from pathlib import Path; "
|
||||
"from ultralytics import YOLO; "
|
||||
f"root=Path({str(root)!r}); weight={str(weight)!r}; "
|
||||
"shutil.rmtree(root, ignore_errors=True); "
|
||||
"[ (root / item).mkdir(parents=True, exist_ok=True) for item in ['images/train','images/val','labels/train','labels/val','runs'] ]; "
|
||||
"image=np.zeros((64,64,3), dtype=np.uint8); "
|
||||
"cv2.rectangle(image, (16,16), (48,48), (255,255,255), -1); "
|
||||
"label='0 0.25 0.25 0.75 0.25 0.75 0.75 0.25 0.75\\n'; "
|
||||
"\nfor split in ['train','val']:\n"
|
||||
" cv2.imwrite(str(root / 'images' / split / 'sample.jpg'), image)\n"
|
||||
" (root / 'labels' / split / 'sample.txt').write_text(label, encoding='utf-8')\n"
|
||||
"(root / 'data.yaml').write_text('path: '+str(root)+'\\ntrain: images/train\\nval: images/val\\nnc: 1\\nnames:\\n 0: object\\n', encoding='utf-8'); "
|
||||
"model=YOLO(weight); "
|
||||
"model.train(data=str(root/'data.yaml'), epochs=1, imgsz=64, batch=1, workers=0, device='cpu', project=str(root/'runs'), name='tiny', exist_ok=True, verbose=False, plots=False, val=False); "
|
||||
"results=root/'runs'/'tiny'/'results.csv'; best=root/'runs'/'tiny'/'weights'/'best.pt'; "
|
||||
"assert results.exists() and results.stat().st_size > 0; "
|
||||
"assert best.exists() and best.stat().st_size > 0; "
|
||||
"print('results', results, results.stat().st_size, 'best', best.stat().st_size)"
|
||||
)
|
||||
|
||||
|
||||
def _mmseg_train_step_snippet(config_path: Path) -> str:
|
||||
return (
|
||||
"import torch; "
|
||||
"from mmengine.config import Config; "
|
||||
"from mmengine.structures import PixelData; "
|
||||
"from mmseg.registry import MODELS; "
|
||||
"from mmseg.structures import SegDataSample; "
|
||||
"from mmseg.utils import register_all_modules; "
|
||||
"register_all_modules(init_default_scope=True); "
|
||||
f"cfg=Config.fromfile({str(config_path)!r}); "
|
||||
"cfg.model.backbone.init_cfg=None; cfg.model.pretrained=None; "
|
||||
"model=MODELS.build(cfg.model).train(); "
|
||||
"sample=SegDataSample(); "
|
||||
"sample.gt_sem_seg=PixelData(data=torch.randint(0,19,(1,64,64), dtype=torch.long)); "
|
||||
"losses=model(torch.randn(1,3,64,64), [sample], mode='loss'); "
|
||||
"loss=sum(value if torch.is_tensor(value) else sum(value) for value in losses.values()); "
|
||||
"optimizer=torch.optim.SGD(model.parameters(), lr=1e-4); "
|
||||
"loss.backward(); optimizer.step(); "
|
||||
"print('loss', round(float(loss.detach()), 6), sorted(losses.keys()))"
|
||||
)
|
||||
|
||||
|
||||
def _request_json(method: str, url: str, payload: dict[str, Any] | None = None, timeout: int = 10) -> dict[str, Any]:
|
||||
data = None
|
||||
headers = {"Accept": "application/json"}
|
||||
@@ -252,6 +312,56 @@ def latest_acceptance_report() -> dict[str, Any]:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def latest_deep_acceptance_report() -> dict[str, Any]:
|
||||
path = settings.project_root / "var" / "acceptance" / "deep_latest.json"
|
||||
if not path.exists():
|
||||
return {"available": False, "path": str(path)}
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def run_deep_acceptance() -> dict[str, Any]:
|
||||
"""Run minimal training loops for each model family without full datasets."""
|
||||
acceptance_root = settings.project_root / "var" / "acceptance"
|
||||
run_id = uuid.uuid4().hex[:8]
|
||||
fixture_root = acceptance_root / f"deep_{run_id}"
|
||||
fixture_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
yolo_weight = settings.source_root / "Seg_All_In_One_YoloModel" / "yolo11n-seg.pt"
|
||||
mmseg_config = settings.source_root / "Seg_All_In_One_MMSeg" / "configs" / "fcn" / "fcn_r18-d8_4xb2-80k_cityscapes-512x1024.py"
|
||||
|
||||
checks = [
|
||||
{
|
||||
"name": "segmodel_tiny_train_step",
|
||||
"passed": False,
|
||||
"detail": _run_snippet(SEGMODEL_TRAIN_STEP_SNIPPET, timeout=90),
|
||||
},
|
||||
{
|
||||
"name": "yolo_tiny_segment_train_epoch",
|
||||
"passed": False,
|
||||
"detail": _run_snippet(_yolo_tiny_train_snippet(fixture_root / "yolo_tiny", yolo_weight), timeout=180),
|
||||
},
|
||||
{
|
||||
"name": "mmseg_tiny_train_step",
|
||||
"passed": False,
|
||||
"detail": _run_conda_snippet(settings.mmseg_conda_env, _mmseg_train_step_snippet(mmseg_config), timeout=120),
|
||||
},
|
||||
]
|
||||
for check in checks:
|
||||
check["passed"] = bool(check["detail"].get("passed"))
|
||||
|
||||
report = {
|
||||
"available": True,
|
||||
"run_id": run_id,
|
||||
"fixture_root": str(fixture_root),
|
||||
"passed": all(item["passed"] for item in checks),
|
||||
"checks": checks,
|
||||
"created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||
}
|
||||
latest = acceptance_root / "deep_latest.json"
|
||||
latest.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
return report
|
||||
|
||||
|
||||
def run_live_acceptance(base_url: str = "http://127.0.0.1:8010") -> dict[str, Any]:
|
||||
"""Run a lightweight end-to-end smoke against the live API and job runner."""
|
||||
acceptance_root = settings.project_root / "var" / "acceptance"
|
||||
|
||||
Reference in New Issue
Block a user