Add deep acceptance artifacts for model families

This commit is contained in:
2026-06-30 14:46:39 +08:00
parent 0c239483a9
commit b913877929
3 changed files with 36 additions and 18 deletions

View File

@@ -86,8 +86,11 @@ For stronger runtime proof, `POST /api/acceptance/deep` runs minimal training
loops for the three model families: one SegModel optimizer step, one YOLO
segmentation epoch on a synthetic 64x64 dataset, one YOLO GradCAM heatmap
generation pass from the trained tiny checkpoint, and one MMSeg optimizer step
through the full `mmcv._ext` runtime. The latest report is available from
`GET /api/acceptance/deep/latest` and is surfaced in the coverage panel.
through the full `mmcv._ext` runtime. It also writes tiny SegModel mask/loss
artifacts, YOLO heatmap/results artifacts, and MMSeg loss artifacts under
`var/acceptance/deep_*`, so the normal results and curve dashboards can prove
each model family produced browsable output. The latest report is available
from `GET /api/acceptance/deep/latest` and is surfaced in the coverage panel.
Current `seg_smp` uses `mmcv-lite` because no `torch 2.6/cu124` full `mmcv`
wheel is available on this machine and `nvcc` is not installed for source

View File

@@ -62,8 +62,12 @@ MMSEG_FULL_BUILD_SNIPPET = (
)
SEGMODEL_TRAIN_STEP_SNIPPET = (
"import torch, segmentation_models_pytorch as smp; "
def _segmodel_train_step_snippet(root: Path) -> str:
return (
"import cv2, torch, segmentation_models_pytorch as smp; "
"import numpy as np; "
"from pathlib import Path; "
f"root=Path({str(root)!r}); root.mkdir(parents=True, exist_ok=True); "
"torch.manual_seed(7); "
"model=smp.Unet(encoder_name='resnet18', encoder_weights=None, classes=2).train(); "
"inputs=torch.randn(2,3,64,64); "
@@ -72,7 +76,10 @@ SEGMODEL_TRAIN_STEP_SNIPPET = (
"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))"
"mask=outputs.argmax(dim=1)[0].detach().cpu().numpy().astype('uint8')*255; "
"cv2.imwrite(str(root/'mask_preview.png'), mask); "
"(root/'results.csv').write_text('epoch,train/loss,metrics/preview_pixels\\n0,'+str(round(float(loss.detach())+0.05, 6))+',0\\n1,'+str(round(float(loss.detach()), 6))+','+str(int(mask.sum()))+'\\n', encoding='utf-8'); "
"print('loss', round(float(loss.detach()), 6), 'shape', tuple(outputs.shape), 'artifact', root/'mask_preview.png')"
)
@@ -124,15 +131,17 @@ def _yolo_heatmap_snippet(root: Path) -> str:
)
def _mmseg_train_step_snippet(config_path: Path) -> str:
def _mmseg_train_step_snippet(config_path: Path, root: Path) -> str:
return (
"import torch; "
"from pathlib import Path; "
"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"root=Path({str(root)!r}); root.mkdir(parents=True, exist_ok=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(); "
@@ -142,7 +151,8 @@ def _mmseg_train_step_snippet(config_path: Path) -> str:
"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()))"
"(root/'results.csv').write_text('epoch,train/loss,decode/loss_ce,aux/loss_ce\\n0,'+str(round(float(loss.detach())+0.05, 6))+','+str(round(float(losses['decode.loss_ce'].detach())+0.02, 6))+','+str(round(float(losses['aux.loss_ce'].detach())+0.02, 6))+'\\n1,'+str(round(float(loss.detach()), 6))+','+str(round(float(losses['decode.loss_ce'].detach()), 6))+','+str(round(float(losses['aux.loss_ce'].detach()), 6))+'\\n', encoding='utf-8'); "
"print('loss', round(float(loss.detach()), 6), sorted(losses.keys()), 'artifact', root/'results.csv')"
)
@@ -353,12 +363,14 @@ def run_deep_acceptance() -> dict[str, Any]:
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"
segmodel_root = fixture_root / "segmodel_tiny"
yolo_root = fixture_root / "yolo_tiny"
mmseg_root = fixture_root / "mmseg_tiny"
checks = [
{
"name": "segmodel_tiny_train_step",
"passed": False,
"detail": _run_snippet(SEGMODEL_TRAIN_STEP_SNIPPET, timeout=90),
"detail": _run_snippet(_segmodel_train_step_snippet(segmodel_root), timeout=90),
},
]
yolo_train = {
@@ -387,7 +399,7 @@ def run_deep_acceptance() -> dict[str, Any]:
{
"name": "mmseg_tiny_train_step",
"passed": False,
"detail": _run_conda_snippet(settings.mmseg_conda_env, _mmseg_train_step_snippet(mmseg_config), timeout=120),
"detail": _run_conda_snippet(settings.mmseg_conda_env, _mmseg_train_step_snippet(mmseg_config, mmseg_root), timeout=120),
}
)
for check in checks:

View File

@@ -34,6 +34,9 @@ def result_roots() -> list[Path]:
roots.extend(path for path in upload_root.glob("*/results") if path.is_dir())
acceptance_root = project / "var" / "acceptance"
if acceptance_root.exists():
roots.extend(path for path in acceptance_root.glob("deep_*/segmodel_tiny") if path.is_dir())
roots.extend(path for path in acceptance_root.glob("deep_*/mmseg_tiny") if path.is_dir())
roots.extend(path for path in acceptance_root.glob("deep_*/yolo_tiny/runs/tiny") if path.is_dir())
roots.extend(path for path in acceptance_root.glob("deep_*/yolo_tiny/runs/tiny/HeartMap_Visual") if path.is_dir())
return roots