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 loops for the three model families: one SegModel optimizer step, one YOLO
segmentation epoch on a synthetic 64x64 dataset, one YOLO GradCAM heatmap segmentation epoch on a synthetic 64x64 dataset, one YOLO GradCAM heatmap
generation pass from the trained tiny checkpoint, and one MMSeg optimizer step generation pass from the trained tiny checkpoint, and one MMSeg optimizer step
through the full `mmcv._ext` runtime. The latest report is available from through the full `mmcv._ext` runtime. It also writes tiny SegModel mask/loss
`GET /api/acceptance/deep/latest` and is surfaced in the coverage panel. 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` 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 wheel is available on this machine and `nvcc` is not installed for source

View File

@@ -62,18 +62,25 @@ MMSEG_FULL_BUILD_SNIPPET = (
) )
SEGMODEL_TRAIN_STEP_SNIPPET = ( def _segmodel_train_step_snippet(root: Path) -> str:
"import torch, segmentation_models_pytorch as smp; " return (
"torch.manual_seed(7); " "import cv2, torch, segmentation_models_pytorch as smp; "
"model=smp.Unet(encoder_name='resnet18', encoder_weights=None, classes=2).train(); " "import numpy as np; "
"inputs=torch.randn(2,3,64,64); " "from pathlib import Path; "
"targets=torch.randint(0,2,(2,64,64)); " f"root=Path({str(root)!r}); root.mkdir(parents=True, exist_ok=True); "
"optimizer=torch.optim.SGD(model.parameters(), lr=1e-3); " "torch.manual_seed(7); "
"outputs=model(inputs); " "model=smp.Unet(encoder_name='resnet18', encoder_weights=None, classes=2).train(); "
"loss=torch.nn.functional.cross_entropy(outputs, targets); " "inputs=torch.randn(2,3,64,64); "
"loss.backward(); optimizer.step(); " "targets=torch.randint(0,2,(2,64,64)); "
"print('loss', round(float(loss.detach()), 6), 'shape', tuple(outputs.shape))" "optimizer=torch.optim.SGD(model.parameters(), lr=1e-3); "
) "outputs=model(inputs); "
"loss=torch.nn.functional.cross_entropy(outputs, targets); "
"loss.backward(); optimizer.step(); "
"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')"
)
def _yolo_tiny_train_snippet(root: Path, weight: Path) -> str: def _yolo_tiny_train_snippet(root: Path, weight: Path) -> str:
@@ -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 ( return (
"import torch; " "import torch; "
"from pathlib import Path; "
"from mmengine.config import Config; " "from mmengine.config import Config; "
"from mmengine.structures import PixelData; " "from mmengine.structures import PixelData; "
"from mmseg.registry import MODELS; " "from mmseg.registry import MODELS; "
"from mmseg.structures import SegDataSample; " "from mmseg.structures import SegDataSample; "
"from mmseg.utils import register_all_modules; " "from mmseg.utils import register_all_modules; "
"register_all_modules(init_default_scope=True); " "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}); " f"cfg=Config.fromfile({str(config_path)!r}); "
"cfg.model.backbone.init_cfg=None; cfg.model.pretrained=None; " "cfg.model.backbone.init_cfg=None; cfg.model.pretrained=None; "
"model=MODELS.build(cfg.model).train(); " "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()); " "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); " "optimizer=torch.optim.SGD(model.parameters(), lr=1e-4); "
"loss.backward(); optimizer.step(); " "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" 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" 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" yolo_root = fixture_root / "yolo_tiny"
mmseg_root = fixture_root / "mmseg_tiny"
checks = [ checks = [
{ {
"name": "segmodel_tiny_train_step", "name": "segmodel_tiny_train_step",
"passed": False, "passed": False,
"detail": _run_snippet(SEGMODEL_TRAIN_STEP_SNIPPET, timeout=90), "detail": _run_snippet(_segmodel_train_step_snippet(segmodel_root), timeout=90),
}, },
] ]
yolo_train = { yolo_train = {
@@ -387,7 +399,7 @@ def run_deep_acceptance() -> dict[str, Any]:
{ {
"name": "mmseg_tiny_train_step", "name": "mmseg_tiny_train_step",
"passed": False, "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: 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()) roots.extend(path for path in upload_root.glob("*/results") if path.is_dir())
acceptance_root = project / "var" / "acceptance" acceptance_root = project / "var" / "acceptance"
if acceptance_root.exists(): 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()) roots.extend(path for path in acceptance_root.glob("deep_*/yolo_tiny/runs/tiny/HeartMap_Visual") if path.is_dir())
return roots return roots