feat(perception): measure M48S load envelope

This commit is contained in:
DCCONSTRUCTIONS
2026-08-25 20:36:21 +03:00
parent 078b8421a6
commit e21332b9e1
6 changed files with 257 additions and 10 deletions
@@ -0,0 +1,89 @@
{
"schema_version": "missioncore.m48s-load-envelope-profile/v1",
"profile_id": "m48s-rf-detr-reference-graph-load-envelope/v1",
"decision_question": "Can the unchanged single-pass RF-DETR reference graph sustain the recorded production rate and 20 percent reserve on Worker 006, and where does its bounded latest-wins limit begin?",
"hypothesis": "The hardened single-pass graph sustains 10 FPS and 12 FPS without unbounded queues, failed frames, a second inference pass, or more than the predeclared delivery loss; 15 FPS is measured only to locate the limit.",
"source": {
"source_id": "RAVNOVES00",
"source_profile_id": "m4-ravnoves00-recorded-realtime/v1",
"frame_count": 4489,
"graph_id": "reference-perception-graph/v2",
"detector_provider_id": "triton-rf-detr-large-coco-risk-fp16-shadow/v0",
"sensor_timestamps_preserved": true
},
"controlled_change": {
"only_variable": "wall_clock_source_rate_hz",
"unchanged": [
"source frames and source timestamps",
"RF-DETR TensorRT engine and threshold",
"geometry, temporal, motion, rolling-map and threat providers",
"latest-wins queue capacities",
"single inference pass per admitted detector frame",
"detector prewarm and cyclic-GC hot-loop policy"
]
},
"scenarios": [
{
"id": "production-10fps",
"run_id": "m48s-load-envelope-v1-production-10fps-a1",
"load_purpose": "production-rate",
"source_rate_hz": 10.0,
"thresholds": {
"minimum_delivery_ratio": 0.999,
"minimum_effective_world_state_fps": 9.5,
"maximum_world_state_completion_p95_ms": 125.0
},
"required_for_reserve_decision": true
},
{
"id": "reserve-12fps",
"run_id": "m48s-load-envelope-v1-reserve-12fps-a1",
"load_purpose": "reserve-gate",
"source_rate_hz": 12.0,
"thresholds": {
"minimum_delivery_ratio": 0.995,
"minimum_effective_world_state_fps": 11.4,
"maximum_world_state_completion_p95_ms": 150.0
},
"required_for_reserve_decision": true
},
{
"id": "limit-15fps",
"run_id": "m48s-load-envelope-v1-limit-15fps-a1",
"load_purpose": "limit-discovery",
"source_rate_hz": 15.0,
"thresholds": {
"minimum_delivery_ratio": 0.95,
"minimum_effective_world_state_fps": 14.25,
"maximum_world_state_completion_p95_ms": 175.0
},
"required_for_reserve_decision": false
}
],
"common_integrity_gates": [
"closed terminal accounting",
"zero failed, stale, rejected or unavailable frames",
"all queue high-watermarks at or below capacity two",
"complete single-pass pipeline timing",
"detector prewarm before source admission",
"cyclic GC disabled only during the hot loop and restored after",
"all command, actuation, navigation and safety authority remains false"
],
"visual_evidence": {
"binding": "reuse exact M4.8S full camera plus LiDAR plus 3D/PLAN timeline",
"reason": "Only wall-clock pacing changes; source frames, sensor timestamps and graph semantics remain immutable.",
"independent_ground_truth": false
},
"exit_decision": {
"reserve_accepted_when": "Both production-10fps and reserve-12fps pass every integrity and operating target gate.",
"optimization_scope_when_rejected": "Optimize only the measured bottleneck stage; do not add another detector or inference pass.",
"limit_discovery_interpretation": "The 15 FPS result is valid evidence when integrity gates pass even if its operating target gate fails."
},
"authority": {
"candidate_accepted": false,
"commands_enabled": false,
"actuation_allowed": false,
"navigation_or_safety_accepted": false,
"production_accepted": false
}
}
@@ -52,7 +52,7 @@ from k1link.perception.reference_graph_runtime import ReferenceGraphRuntimePaths
from k1link.perception.rolling_map import RollingLocalObstacleMapProvider
from k1link.perception.temporal import BoundedSpatialTemporalProvider
SCHEMA_VERSION: Final = "missioncore.m48s-reference-graph-shadow-load/v3"
SCHEMA_VERSION: Final = "missioncore.m48s-reference-graph-shadow-load/v4"
FRAME_EVIDENCE_SCHEMA: Final = "missioncore.m48s-reference-graph-frame-evidence/v1"
PIPELINE_TIMING_SCHEMA: Final = "missioncore.m48s-frame-pipeline-timing/v0"
GC_POLICY_SCHEMA: Final = "missioncore.cyclic-gc-hot-loop-policy/v0"
@@ -63,6 +63,7 @@ AUTHORITY: Final = {
"actuation_allowed": False,
"navigation_or_safety_accepted": False,
}
LOAD_PURPOSES: Final = ("production-rate", "reserve-gate", "limit-discovery")
class GpuTelemetry:
@@ -359,6 +360,13 @@ def main() -> int:
parser.add_argument("--triton-origin", default="http://127.0.0.1:8000")
parser.add_argument("--loops", type=int, default=1)
parser.add_argument("--maximum-frames", type=int)
parser.add_argument("--source-rate-hz", type=float)
parser.add_argument("--minimum-delivery-ratio", type=float, default=0.0)
parser.add_argument("--minimum-effective-world-state-fps", type=float, default=9.5)
parser.add_argument("--maximum-world-state-completion-p95-ms", type=float, default=175.0)
parser.add_argument("--load-purpose", choices=LOAD_PURPOSES, default="production-rate")
parser.add_argument("--runtime-artifact-sha256", required=True)
parser.add_argument("--runner-sha256", required=True)
parser.add_argument("--telemetry-interval-seconds", type=float, default=1.0)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--progress", type=Path, required=True)
@@ -370,6 +378,21 @@ def main() -> int:
raise RuntimeError("maximum frame count must be positive")
if arguments.telemetry_interval_seconds <= 0:
raise RuntimeError("telemetry interval must be positive")
if arguments.source_rate_hz is not None and (
not np.isfinite(arguments.source_rate_hz) or arguments.source_rate_hz <= 0
):
raise RuntimeError("source rate must be positive and finite")
if not 0.0 <= arguments.minimum_delivery_ratio <= 1.0:
raise RuntimeError("minimum delivery ratio must be between zero and one")
if arguments.minimum_effective_world_state_fps <= 0:
raise RuntimeError("minimum effective world-state FPS must be positive")
if arguments.maximum_world_state_completion_p95_ms <= 0:
raise RuntimeError("maximum completion p95 must be positive")
for digest in (arguments.runtime_artifact_sha256, arguments.runner_sha256):
if len(digest) != 64 or any(character not in "0123456789abcdef" for character in digest):
raise RuntimeError("runtime and runner SHA-256 values must be lowercase hex")
if _sha256(Path(__file__)) != arguments.runner_sha256:
raise RuntimeError("runner SHA-256 changed")
output = arguments.output.absolute()
progress = arguments.progress.absolute()
frame_ledger = arguments.frame_ledger.absolute()
@@ -426,6 +449,7 @@ def main() -> int:
decode_timing_observer=timing_store.observe_decode,
detector_timing_observer=timing_store.observe_detector,
maximum_frames=arguments.maximum_frames,
source_rate_hz=arguments.source_rate_hz,
) as runtime:
for stage_id, attribute in (
("geometry", "geometry"),
@@ -546,7 +570,9 @@ def main() -> int:
identity = _identity_metrics(all_deliveries)
semantic = _semantic_metrics(all_deliveries, advisories)
world_state_fps = delivered / processing_wall_seconds
checks = {
delivery_ratio = delivered / admitted if admitted else 0.0
completion_distribution = _distribution(completion_ages_ms)
integrity_checks = {
"loop_count_completed": len(loop_documents) == arguments.loops,
"graph_stopped_cleanly": all(
loop["state"] == GraphState.STOPPED.value for loop in loop_documents
@@ -561,10 +587,6 @@ def main() -> int:
TerminalOutcomeType.UNAVAILABLE,
)
),
"minimum_world_state_fps": world_state_fps >= 9.5,
"maximum_world_state_completion_p95_ms": (
_distribution(completion_ages_ms)["p95"] <= 175.0
),
"bounded_latest_wins_queues": all(value <= 2 for value in queue_high_watermarks.values()),
"temporal_identity_reuse_observed": cast(
int,
@@ -588,6 +610,19 @@ def main() -> int:
),
"authority_remains_false": all(value is False for value in AUTHORITY.values()),
}
operating_target_checks = {
"minimum_delivery_ratio": delivery_ratio >= arguments.minimum_delivery_ratio,
"minimum_world_state_fps": (
world_state_fps >= arguments.minimum_effective_world_state_fps
),
"maximum_world_state_completion_p95_ms": (
completion_distribution["p95"]
<= arguments.maximum_world_state_completion_p95_ms
),
}
checks = {**integrity_checks, **operating_target_checks}
evidence_integrity_gate_passed = all(integrity_checks.values())
operating_target_gate_passed = all(operating_target_checks.values())
integrated_runtime_gate_passed = all(checks.values())
document = {
"schema_version": SCHEMA_VERSION,
@@ -595,20 +630,28 @@ def main() -> int:
"source_id": "RAVNOVES00",
"loops": arguments.loops,
"maximum_frames_per_loop": arguments.maximum_frames,
"requested_rate_hz": arguments.source_rate_hz,
"pacing_contract": "wall-clock-scaled-source-timestamps-immutable/v1",
},
"identity": {
"worker_id": "worker-006",
"graph_id": "reference-perception-graph/v2",
"detector_provider_id": "triton-rf-detr-large-coco-risk-fp16-shadow/v0",
"inputs": _input_digests(paths, arguments.detector_profile),
"runtime_artifact_sha256": arguments.runtime_artifact_sha256,
"runner_sha256": arguments.runner_sha256,
},
"execution": {
"run_mode": GraphRunMode.SOURCE_PACED_LATEST_WINS.value,
"load_purpose": arguments.load_purpose,
"requested_source_rate_hz": arguments.source_rate_hz,
"source_timestamps_preserved": True,
"wall_seconds": round(wall_seconds, 6),
"source_processing_wall_seconds": round(processing_wall_seconds, 6),
"admitted_frames": admitted,
"delivered_world_states": delivered,
"effective_world_state_fps": round(world_state_fps, 6),
"delivery_ratio": round(delivery_ratio, 9),
"terminal_outcomes": dict(sorted(accounting.items())),
"queue_high_watermarks": queue_high_watermarks,
"loops": loop_documents,
@@ -624,7 +667,7 @@ def main() -> int:
},
},
"metrics": {
"world_state_completion_age_ms": _distribution(completion_ages_ms),
"world_state_completion_age_ms": completion_distribution,
"local_obstacle_map_output_age_ms": _distribution(map_output_ages_ms),
"identity_continuity": identity,
"semantic_advisory": semantic,
@@ -635,6 +678,19 @@ def main() -> int:
"process_peak_rss_after_mib": round(rss_after_kib / 1024.0, 6),
},
"checks": checks,
"integrity_checks": integrity_checks,
"operating_target_checks": operating_target_checks,
"predeclared_thresholds": {
"minimum_delivery_ratio": arguments.minimum_delivery_ratio,
"minimum_effective_world_state_fps": (
arguments.minimum_effective_world_state_fps
),
"maximum_world_state_completion_p95_ms": (
arguments.maximum_world_state_completion_p95_ms
),
},
"evidence_integrity_gate_passed": evidence_integrity_gate_passed,
"operating_target_gate_passed": operating_target_gate_passed,
"integrated_runtime_gate_passed": integrated_runtime_gate_passed,
"independent_track_identity_quality_evaluated": False,
"independent_risk_policy_quality_evaluated": False,
@@ -647,7 +703,10 @@ def main() -> int:
output.write_bytes(_canonical_json(document) + b"\n")
print(output)
print(json.dumps(checks, indent=2, sort_keys=True))
return 0 if integrated_runtime_gate_passed else 2
discovery_completed = (
arguments.load_purpose == "limit-discovery" and evidence_integrity_gate_passed
)
return 0 if integrated_runtime_gate_passed or discovery_completed else 2
def _record_completion_age(
@@ -12,6 +12,16 @@ param(
[int]$Loops = 1,
[ValidateRange(1, 4489)]
[int]$MaximumFrames = 120,
[ValidateRange(1.0, 120.0)]
[double]$SourceRateHz = 10.0,
[ValidateRange(0.0, 1.0)]
[double]$MinimumDeliveryRatio = 0.999,
[ValidateRange(0.1, 120.0)]
[double]$MinimumEffectiveWorldStateFps = 9.5,
[ValidateRange(1.0, 10000.0)]
[double]$MaximumWorldStateCompletionP95Ms = 125.0,
[ValidateSet("production-rate", "reserve-gate", "limit-discovery")]
[string]$LoadPurpose = "production-rate",
[string]$OutputRoot = "D:\NDC_MISSIONCORE\runtime\results\m48s-reference-graph-shadow"
)
@@ -103,6 +113,7 @@ $runner = Get-Item -LiteralPath (
if ($runner.PSIsContainer -or ($runner.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
throw "M48S graph runner must be a regular file"
}
$runnerSha256 = Get-Sha256 $runner.FullName
$experimentRoot = Resolve-DDirectory (
"D:\NDC_MISSIONCORE\runtime\experiments\m48t-fixed-detector-20260825T095425Z"
@@ -253,6 +264,13 @@ try {
"--valid-fov-mask", "/source/mask.png",
"--triton-origin", "http://127.0.0.1:8000",
"--loops", ([string]$Loops),
"--source-rate-hz", ([string]::Format([Globalization.CultureInfo]::InvariantCulture, "{0:R}", $SourceRateHz)),
"--minimum-delivery-ratio", ([string]::Format([Globalization.CultureInfo]::InvariantCulture, "{0:R}", $MinimumDeliveryRatio)),
"--minimum-effective-world-state-fps", ([string]::Format([Globalization.CultureInfo]::InvariantCulture, "{0:R}", $MinimumEffectiveWorldStateFps)),
"--maximum-world-state-completion-p95-ms", ([string]::Format([Globalization.CultureInfo]::InvariantCulture, "{0:R}", $MaximumWorldStateCompletionP95Ms)),
"--load-purpose", $LoadPurpose,
"--runtime-artifact-sha256", $ExpectedWheelSha256,
"--runner-sha256", $runnerSha256,
"--output", "/output/result.json",
"--progress", "/output/progress.jsonl",
"--frame-ledger", "/output/frames.jsonl"
@@ -92,6 +92,7 @@ def build_m48s_reference_graph_runtime(
decode_timing_observer: DecodeTimingObserver | None = None,
detector_timing_observer: DetectorTimingObserver | None = None,
maximum_frames: int | None = None,
source_rate_hz: float | None = None,
) -> M48sReferenceGraphRuntime:
"""Instantiate the complete graph with only its detector pin replaced."""
@@ -125,6 +126,11 @@ def build_m48s_reference_graph_runtime(
if run_mode is GraphRunMode.SOURCE_PACED_LATEST_WINS
else ReplayPacing.UNCAPPED
),
target_rate_hz=(
source_rate_hz
if run_mode is GraphRunMode.SOURCE_PACED_LATEST_WINS
else None
),
),
decoder=PyAvRecordedImageDecoder(paths.video),
timing_observer=decode_timing_observer,
+33 -2
View File
@@ -99,7 +99,11 @@ DecodeTimingObserver = Callable[[DecodedFrameTiming], None]
class RecordedRavnoves00Source:
"""Emit the admitted synchronized source timeline at 1.0x or uncapped speed."""
"""Emit the admitted timeline at its recorded or an explicit replay rate.
A target rate changes wall-clock pacing only. Immutable sensor timestamps stay
untouched so accelerated load measurements cannot silently change scene motion.
"""
provider_id: str = RECORDED_SOURCE_PROVIDER_ID
@@ -111,16 +115,24 @@ class RecordedRavnoves00Source:
pacing: ReplayPacing = ReplayPacing.UNCAPPED,
expected_frame_count: int = DEFAULT_FRAME_COUNT,
expected_source_pack_sha256: str | None = RECORDED_SOURCE_PACK_SHA256,
target_rate_hz: float | None = None,
clock_ns: Callable[[], int] = time.monotonic_ns,
wait: WaitFunction | None = None,
) -> None:
if expected_frame_count < 1:
raise RecordedSourceError("expected frame count must be positive")
if target_rate_hz is not None and (
not np.isfinite(target_rate_hz) or target_rate_hz <= 0
):
raise RecordedSourceError("target replay rate must be positive and finite")
if target_rate_hz is not None and pacing is not ReplayPacing.ONE_X:
raise RecordedSourceError("target replay rate requires paced replay")
self.camera_index_path = camera_index_path.resolve()
self.source_pack_path = source_pack_path.resolve()
self.pacing = pacing
self.expected_frame_count = expected_frame_count
self.expected_source_pack_sha256 = expected_source_pack_sha256
self.target_rate_hz = target_rate_hz
self._clock_ns = clock_ns
self._wait = wait or _event_wait
@@ -130,6 +142,7 @@ class RecordedRavnoves00Source:
repository_root: Path,
*,
pacing: ReplayPacing = ReplayPacing.UNCAPPED,
target_rate_hz: float | None = None,
) -> RecordedRavnoves00Source:
root = repository_root.resolve()
camera_index = (
@@ -148,6 +161,7 @@ class RecordedRavnoves00Source:
camera_index_path=camera_index,
source_pack_path=source_pack,
pacing=pacing,
target_rate_hz=target_rate_hz,
)
def packets(self, stop_event: Event) -> Iterator[SourcePacket]:
@@ -163,6 +177,7 @@ class RecordedRavnoves00Source:
started_ns = int(self._clock_ns())
source_origin_ns = _source_time_ns(timeline_rows[0])
pacing_scale = _pacing_scale(timeline_rows, self.target_rate_hz)
for frame_index, (camera, timeline) in enumerate(
zip(camera_rows, timeline_rows, strict=True)
):
@@ -170,7 +185,8 @@ class RecordedRavnoves00Source:
return
packet = _packet(frame_index, camera, timeline)
if self.pacing is ReplayPacing.ONE_X:
target_ns = started_ns + packet.envelope.timestamps.source_ns - source_origin_ns
source_elapsed_ns = packet.envelope.timestamps.source_ns - source_origin_ns
target_ns = started_ns + round(source_elapsed_ns * pacing_scale)
if not self._pace_until(stop_event, target_ns):
return
yield packet
@@ -349,6 +365,21 @@ def _source_time_ns(document: _SourceTimelineRow) -> int:
return round(value * 1_000_000_000)
def _pacing_scale(
timeline: tuple[_SourceTimelineRow, ...],
target_rate_hz: float | None,
) -> float:
if target_rate_hz is None:
return 1.0
if len(timeline) < 2:
raise RecordedSourceError("target-rate replay requires at least two frames")
duration_seconds = timeline[-1].session_seconds - timeline[0].session_seconds
if not np.isfinite(duration_seconds) or duration_seconds <= 0:
raise RecordedSourceError("source timeline duration is invalid")
recorded_rate_hz = (len(timeline) - 1) / duration_seconds
return recorded_rate_hz / target_rate_hz
def _integer(document: dict[str, object], key: str) -> int:
value = document.get(key)
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
+44
View File
@@ -835,6 +835,50 @@ def test_recorded_source_reuses_one_timeline_for_1x_and_uncapped(tmp_path: Path)
assert one_x[1].registered_point_increment_payload is None
def test_recorded_source_target_rate_changes_only_wall_clock_pacing(tmp_path: Path) -> None:
camera_path, timeline_path = _write_recorded_fixture(tmp_path)
now = [1_000_000_000]
waits: list[float] = []
def wait(stop_event: Event, seconds: float) -> bool:
waits.append(seconds)
now[0] += round(seconds * 1_000_000_000)
return stop_event.is_set()
source = RecordedRavnoves00Source(
camera_index_path=camera_path,
source_pack_path=timeline_path,
pacing=ReplayPacing.ONE_X,
target_rate_hz=20.0,
expected_frame_count=2,
expected_source_pack_sha256=None,
clock_ns=lambda: now[0],
wait=wait,
)
packets = list(source.packets(Event()))
assert waits == pytest.approx([0.05])
assert (
packets[1].envelope.timestamps.source_ns
- packets[0].envelope.timestamps.source_ns
== 100_000_000
)
def test_recorded_source_rejects_target_rate_for_uncapped_replay(tmp_path: Path) -> None:
camera_path, timeline_path = _write_recorded_fixture(tmp_path)
with pytest.raises(RecordedSourceError, match="requires paced replay"):
RecordedRavnoves00Source(
camera_index_path=camera_path,
source_pack_path=timeline_path,
pacing=ReplayPacing.UNCAPPED,
target_rate_hz=12.0,
expected_frame_count=2,
expected_source_pack_sha256=None,
)
def test_recorded_source_rejects_timeline_mismatch(tmp_path: Path) -> None:
camera_path, timeline_path = _write_recorded_fixture(tmp_path, mismatched=True)
source = RecordedRavnoves00Source(