fix(ceus): 降低长视频内存峰值,快照开跑即写,build 核对 CEUS 运行与交付包

- 内存:prepare_segmentation 改为流式解码,只留分屏和配准的抽样帧,绿框逐帧在两半上找;
  分割那一半由 SegmentationInput.target_frames() 到用时再解码,segment_video 边读边写 jpg;
  MedSAM2 init_state 的读帧换成直接在存放设备上建张量(不改 MedSAM2 源码,结果逐位相同);
  传播结果写进预分配数组。帧数最多的一段视频常驻内存峰值 11.2 GiB → 2.3 GiB,分辨率最大的
  一段原代码被 OOM 杀掉、现为 4.2 GiB;两段的掩码和全部产物与改前逐字节相同,33 段视频的
  第 1–2 步结果与改前一致,29 例配准与 review.csv 一致。
- 快照:ceus_quant 开跑时写 snapshot/(git、ceus.yaml、review.csv、uv.lock、segment.json),
  配置和核对表从快照副本读;收尾由新函数 runs.finish_snapshot 只新建 finished.json。
  build 与 ceus_align_review 同样改用 finish_snapshot 记结束时间。
- build:summary.json 加 video_sha256;装 CEUS 结果时核对快照里的 delivery_dir、逐例核对
  视频 SHA-256 和推注时间,不同就整次停下;新字段做类型检查,不进 tic.json。

来源:主会话任务(问题 1–3 及补充的 delivery_dir 比对)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 09:01:44 -04:00
parent e165b32b9d
commit 1de88a5d0a
23 changed files with 561 additions and 202 deletions
+39 -3
View File
@@ -13,7 +13,7 @@ import csv
import hashlib
import importlib.util
import json
from collections.abc import Callable, Mapping, Sequence
from collections.abc import Callable, Iterable, Mapping
from datetime import datetime
from pathlib import Path
from types import ModuleType
@@ -88,11 +88,12 @@ class _CountingSegment:
def __call__(
self,
frames: Sequence[npt.NDArray[np.uint8]],
frames: Iterable[npt.NDArray[np.uint8]],
prompt_masks: Mapping[int, npt.NDArray[np.bool_]],
cfg: object,
) -> npt.NDArray[np.bool_]:
self.calls += 1
frames = list(frames)
height, width = frames[0].shape[:2]
masks = np.zeros((len(frames), height, width), dtype=bool)
x1, y1, x2, y2 = LESION
@@ -170,7 +171,11 @@ def test_run_writes_outputs_and_failed_case(
assert (snapshot / "align_review.csv").read_bytes() == (tmp_path / "review.csv").read_bytes()
segment_record = json.loads((snapshot / "segment.json").read_text(encoding="utf-8"))
assert segment_record["medsam2_commit"] == "c" * 40
assert segment_record["cuda_peak_memory_bytes"] is None # device 是 cpu
assert segment_record["checkpoint_sha256"] == hashlib.sha256(b"fake weights").hexdigest()
finished = json.loads((snapshot / "finished.json").read_text(encoding="utf-8"))
assert finished["cuda_peak_memory_bytes"] is None # device 是 cpu
assert finished["segment_cache_hits"] == 0
assert "finished_at" in finished
assert (snapshot / "snapshot.json").is_file()
out = capsys.readouterr().out
@@ -178,6 +183,37 @@ def test_run_writes_outputs_and_failed_case(
assert "U001" not in out and "U002" not in out
def test_snapshot_written_before_cases_run(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
setup: tuple[ModuleType, _CountingSegment, dict[str, Any], Callable[[str], None]],
) -> None:
"""跑病例时快照已在 .partial 里;运行中途改了 ceus.yaml 和 review.csv,快照仍是开跑时那份。"""
module, segment, document, next_run_id = setup
config = _write_config(tmp_path, document, "ceus.yaml")
review = Path(document["paths"]["align_review"])
original_config, original_review = config.read_bytes(), review.read_bytes()
snapshot = tmp_path / "runs" / "run-a.partial" / "snapshot"
seen: list[bool] = []
def editing_segment(*args: Any) -> npt.NDArray[np.bool_]:
seen.append(all((snapshot / name).is_file() for name in ("snapshot.json", "segment.json")))
config.write_text("# 运行中途改了\n", encoding="utf-8")
review.write_text("改了\n", encoding="utf-8")
return segment(*args)
monkeypatch.setattr(module, "segment_video", editing_segment)
next_run_id("run-a")
module.main(["--config", str(config)])
assert seen == [True]
done = tmp_path / "runs" / "run-a" / "snapshot"
assert (done / "ceus.yaml").read_bytes() == original_config
assert (done / "align_review.csv").read_bytes() == original_review
snapshot_json = json.loads((done / "snapshot.json").read_text(encoding="utf-8"))
assert "finished_at" not in snapshot_json and "started_at" in snapshot_json
def test_build_reads_real_run_layout(
tmp_path: Path,
setup: tuple[ModuleType, _CountingSegment, dict[str, Any], Callable[[str], None]],