feat(perception): expire costmap permissions per cell

This commit is contained in:
DCCONSTRUCTIONS
2026-09-02 10:57:42 +03:00
parent bcacb02a22
commit 1f8101e4a5
7 changed files with 488 additions and 25 deletions
@@ -7,9 +7,17 @@ the scene, but makes no valid payload/terrain-permission claim in the ABI.
import hashlib
import json
from dataclasses import replace
from k1link.perception.costmap_freshness import CostmapCellEvidence
from k1link.perception.realtime_contract import LayerEvidence
from k1link.perception.realtime_scene import DEPENDENCIES, SceneFreshness, derive_layer
from k1link.perception.realtime_scene import (
DEPENDENCIES,
SceneFreshness,
_read_wire_integer,
_wire_integer,
derive_layer,
)
CLOCK_DOMAIN = "pilot-original-host-arrival"
@@ -25,6 +33,9 @@ def payload_digest(scene, layer):
"policy": ("policy_actions", "policy_counts"),
}[layer]
value = {key: scene[key] for key in fields}
if layer == "costmap" and "costmap_cell_evidence" in scene:
value["costmap_cell_evidence"] = scene["costmap_cell_evidence"]
value["costmap_freshness_mode"] = scene["costmap_freshness_mode"]
return hashlib.sha256(
json.dumps(value, sort_keys=True, allow_nan=False, separators=(",", ":")).encode()
).hexdigest()
@@ -59,7 +70,11 @@ def build_freshness(scene, bundle, ddr_layer, epoch_id):
if name == "costmap" and bundle["available"]:
# A rolling window can retain old occupied/unknown cells to prohibit
# terrain. Permissive cells must keep their own observation age.
oldest = scene["tgs_counts"]["oldest_permissive_cell_source_ns"]
oldest = (
_read_wire_integer(scene["cell_assessment"]["oldest_permissive_source_time_ns"])
if scene.get("costmap_freshness_mode") == "per-cell"
else scene["tgs_counts"]["oldest_permissive_cell_source_ns"]
)
layers[name] = derive_layer(
name,
epoch_id=epoch_id,
@@ -95,7 +110,41 @@ def suppress_policy(scene):
}
def prepare_publication(scene, bundle, ddr_layer, *, epoch_id, now_ns):
def apply_cell_expiry(scene, *, source_time_ns, observed_source_time_ns, uncertainty_ms=0):
evidence = CostmapCellEvidence.from_dict(scene["costmap_cell_evidence"])
checked = evidence.assess(
states=tuple(scene["costmap_states"]),
actions=tuple(scene["policy_actions"]),
source_time_ns=source_time_ns,
observed_source_time_ns=observed_source_time_ns,
maximum_age_ms=250,
clock_uncertainty_ms=uncertainty_ms,
)
scene["costmap_states"] = list(checked.states)
scene["policy_actions"] = list(checked.actions)
scene["policy_counts"] = {
key: checked.actions.count(index)
for index, key in enumerate(("ALLOW_candidate", "HIGH_COST", "NO_GO"))
}
scene["cell_assessment"] = {
"checked_at_source_time_ns": _wire_integer(observed_source_time_ns),
"expired_ground_cells": checked.expired_cells,
"missing_ground_support_cells": checked.missing_support_cells,
"suppressed_permissions": checked.suppressed_permissions,
"oldest_permissive_source_time_ns": _wire_integer(checked.oldest_permissive_source_time_ns),
}
def prepare_publication(scene, bundle, ddr_layer, *, epoch_id, now_ns, mode="whole-scene"):
if mode not in ("whole-scene", "per-cell"):
raise ValueError("unknown costmap freshness mode")
scene["costmap_freshness_mode"] = mode
if mode == "per-cell":
apply_cell_expiry(
scene,
source_time_ns=bundle["time_ns"],
observed_source_time_ns=bundle["time_ns"] + now_ns - bundle["due_ns"],
)
freshness = build_freshness(scene, bundle, ddr_layer, epoch_id)
checked = assess(freshness, bundle=bundle, now_ns=now_ns)
if not checked.fresh_complete:
@@ -112,6 +161,11 @@ def prepare_publication(scene, bundle, ddr_layer, *, epoch_id, now_ns):
def validate_receipt(scene, bundle, *, epoch_id):
mode = scene.get("costmap_freshness_mode", "whole-scene")
if mode not in ("whole-scene", "per-cell"):
raise ValueError("unknown costmap freshness mode")
if mode == "per-cell" or "costmap_cell_evidence" in scene:
CostmapCellEvidence.from_dict(scene["costmap_cell_evidence"])
freshness = SceneFreshness.from_dict(scene["freshness"])
if (
freshness.epoch_id,
@@ -137,3 +191,52 @@ def receipt_view(scene, checked):
if not checked.fresh_complete:
suppress_policy(view)
return view
def assess_receipt(scene, freshness, *, bundle, now_ns):
"""Derive a current consumer view without rewriting published evidence.
Cell expiry never upgrades a published action. Remaining permissions retain
their original support time. Layer identity/source timestamps do not change;
updated costmap/policy hashes belong to the derived view, not the wire bytes.
"""
observed = bundle["time_ns"] + now_ns - bundle["due_ns"]
previous = scene.get("freshness_at_receipt") or scene["freshness_at_publication"]
if observed < _read_wire_integer(previous["checked_at_source_time_ns"]):
raise ValueError("consumer clock moved backwards")
view = dict(scene)
if scene.get("costmap_freshness_mode") == "per-cell":
apply_cell_expiry(
view,
source_time_ns=freshness.source_time_ns,
observed_source_time_ns=observed,
)
layers = {item.layer: item for item in freshness.layers[:4]}
for name in ("costmap", "policy"):
oldest = (
_read_wire_integer(view["cell_assessment"]["oldest_permissive_source_time_ns"])
if name == "costmap"
else freshness.source_time_ns
)
layers[name] = derive_layer(
name,
epoch_id=freshness.epoch_id,
source_sequence=freshness.source_sequence,
source_time_ns=freshness.source_time_ns,
payload_sha256=payload_digest(view, name),
inputs=tuple(layers[key] for key in DEPENDENCIES[name]),
oldest_required_input_time_ns=oldest,
)
freshness = replace(freshness, layers=tuple(layers.values()))
checked = assess(freshness, bundle=bundle, now_ns=now_ns)
view = receipt_view(view, checked)
# Global suppression also changes the policy payload. A derived view must
# remain internally verifiable, including on a second consumer boundary.
policy = freshness.layers[-1]
if policy.payload_sha256 is not None:
policy = replace(policy, payload_sha256=payload_digest(view, "policy"))
freshness = replace(freshness, layers=(*freshness.layers[:-1], policy))
checked = assess(freshness, bundle=bundle, now_ns=now_ns)
view["freshness"] = freshness.to_dict()
view["freshness_at_receipt"] = checked.to_dict()
return view, checked
@@ -34,6 +34,7 @@ from k1link.perception.contracts import (
TemporalState,
TimestampBundle,
)
from k1link.perception.costmap_freshness import CostmapCellEvidence
from k1link.perception.detector import NativeRfDetrShadowDetectorProvider
from k1link.perception.geometry import (
GeometryFrame,
@@ -358,6 +359,7 @@ class JointGraph:
timing["motion_rolling_threat_ms"] = (time.monotonic_ns() - begin) / 1e6
begin = time.monotonic_ns()
costmap, actions, material, tgs_counts = self.costmap(bundle, segmentation, scene_map)
cell_evidence = tgs_counts.pop("cell_evidence")
timing["tgs_costmap_policy_ms"] = (time.monotonic_ns() - begin) / 1e6
return {
"timing_ms": timing,
@@ -370,6 +372,7 @@ class JointGraph:
"tgs_counts": tgs_counts,
"costmap_states": costmap.tolist(),
"costmap_material": material.tolist(),
"costmap_cell_evidence": cell_evidence,
"policy_actions": actions.tolist(),
"policy_counts": {
key: int(np.count_nonzero(actions == value))
@@ -395,7 +398,10 @@ class JointGraph:
np.zeros(count, np.uint8),
np.full(count, 2, np.uint8),
material,
{"unavailable": True},
{
"unavailable": True,
"cell_evidence": CostmapCellEvidence((None,) * count).to_dict(),
},
)
position, quaternion = bundle["pose"]
points = bundle["rolling_points"]
@@ -485,6 +491,9 @@ class JointGraph:
"rejected": int(np.count_nonzero(states == 3)),
"stale_cells": int(np.count_nonzero(stale)),
"oldest_permissive_cell_source_ns": oldest_permissive,
"cell_evidence": CostmapCellEvidence(
tuple(None if stamp < 0 else int(stamp) for stamp in last_seen)
).to_dict(),
"state_counts": dict(Counter(int(x) for x in cells)),
**({"diagnostic_cells": diagnostics} if diagnostics else {}),
},
@@ -24,7 +24,7 @@ from datetime import UTC, datetime
from pathlib import Path
import numpy as np
from pilot_freshness import assess, prepare_publication, receipt_view, validate_receipt
from pilot_freshness import assess_receipt, prepare_publication, validate_receipt
from pilot_ipc import receive, send
from pilot_queue import Mailbox
from pilot_scheduler import GpuStage
@@ -263,6 +263,7 @@ def run(args):
"telemetry_mode": args.telemetry_mode,
"telemetry_device_state": args.telemetry_device_state,
"triton_verbose": args.triton_verbose,
"costmap_freshness_mode": args.costmap_freshness,
"ddrnet_min_source_interval_ms": args.ddrnet_min_source_interval_ms,
}
children, logs, results, samples = [], [], [], []
@@ -270,6 +271,7 @@ def run(args):
mailbox = Mailbox(capacity=2)
source_report = {}
producer = monitor = graph = gpu_stage = ddr_backend = None
cpu_bundle = None
def child(name, command, env=None):
log = (output / (name + ".log")).open("wb")
@@ -410,9 +412,7 @@ def run(args):
tensor = np.frombuffer(raw, "<f4").reshape(1, 3, 512, 512)
else:
tensor = native_preprocess(bundle["image"])
preprocess_result = {
"preprocess_ms": (time.monotonic_ns() - begin) / 1e6
}
preprocess_result = {"preprocess_ms": (time.monotonic_ns() - begin) / 1e6}
preprocessed = time.monotonic_ns()
mask = ddr_backend.infer(tensor)
inferred = time.monotonic_ns()
@@ -436,9 +436,7 @@ def run(args):
ddr_result = {
"component_ms": 0.0,
"forward_ms": 0.0,
"stages_ms": {
key: 0.0 for key in ddr_cache["result"]["stages_ms"]
},
"stages_ms": {key: 0.0 for key in ddr_cache["result"]["stages_ms"]},
}
mask = ddr_cache["mask"]
ddr_done = time.monotonic_ns()
@@ -470,10 +468,12 @@ def run(args):
if item is None:
break
bundle, computed = item
cpu_bundle = bundle
else:
bundle = mailbox.take()
if bundle is None:
break
cpu_bundle = bundle
computed = compute_gpu(bundle)
begin, ddr_done, gpu_done, ddr_result, mask, proposals, ddr_layer = computed
cpu_started = time.monotonic_ns()
@@ -483,10 +483,17 @@ def run(args):
proposals=proposals,
detector_ms=(gpu_done - ddr_done) / 1e6,
)
raw_costmap_sha256 = hashlib.sha256(
json.dumps(scene["costmap_states"], separators=(",", ":")).encode()
).hexdigest()
freshness_started = time.monotonic_ns()
prepare_publication(
scene, bundle, ddr_layer, epoch_id=args.run_id,
scene,
bundle,
ddr_layer,
epoch_id=args.run_id,
now_ns=freshness_started,
mode=args.costmap_freshness,
)
scene.update(
sequence=bundle["sequence"],
@@ -504,8 +511,9 @@ def run(args):
raise ValueError("collector identity mismatch")
freshness = validate_receipt(received, bundle, epoch_id=args.run_id)
checked_at = time.monotonic_ns()
checked = assess(freshness, bundle=bundle, now_ns=checked_at)
view = receipt_view(received, checked)
view, checked = assess_receipt(
received, freshness, bundle=bundle, now_ns=checked_at
)
finished = time.monotonic_ns()
timing = {
**scene["timing_ms"],
@@ -541,11 +549,17 @@ def run(args):
"policy_counts": view["policy_counts"],
"freshness_at_receipt": checked.to_dict(),
"policy_counts_at_publication": scene["policy_counts"],
"cell_assessment_at_receipt": view.get("cell_assessment"),
"raw_costmap_states_sha256": raw_costmap_sha256,
"effective_costmap_states_sha256": hashlib.sha256(
json.dumps(view["costmap_states"], separators=(",", ":")).encode()
).hexdigest(),
"scene_bytes": len(encoded),
"scene_sha256": hashlib.sha256(encoded).hexdigest(),
}
results.append(result)
mailbox.release(bundle)
cpu_bundle = None
sink.write(encoded + b"\n")
if len(results) == 1:
report["reads_at_first_result"] = {
@@ -560,16 +574,14 @@ def run(args):
"completed": len(results),
"last_sequence": bundle["sequence"],
"last_age_ms": timing["source_due_to_receiver_ms"],
"drops": len(mailbox.dropped),
"drops": mailbox.dropped_count,
}
),
flush=True,
)
if mailbox.error:
raise RuntimeError(mailbox.error)
report["triton_statistics_delta"] = stats_delta(
stats_before, read_stats(triton_models)
)
report["triton_statistics_delta"] = stats_delta(stats_before, read_stats(triton_models))
report["execution_complete"] = True
except Exception:
report["execution_complete"] = False
@@ -595,6 +607,10 @@ def run(args):
if monitor:
monitor.join(timeout=2)
report["gpu_stage_stopped"] = gpu_stage.close() if gpu_stage else True
mailbox.cancel()
if cpu_bundle is not None:
mailbox.release(cpu_bundle, discard_reason="processing-failed")
report["input_payloads_released"] = mailbox.quiescent
if graph:
graph.backend.close()
if ddr_backend:
@@ -633,16 +649,14 @@ def run(args):
report["accounting"] = {
"released": camera_count,
"completed": len(results),
"dropped": len(mailbox.dropped),
"unaccounted": camera_count - len(results) - len(mailbox.dropped),
"dropped": mailbox.dropped_count,
"unaccounted": camera_count - len(results) - mailbox.dropped_count,
}
ddrnet_executed = sum(r.get("ddrnet_state") == "current" for r in results)
report["model_cadence"] = {
"ddrnet_executed": ddrnet_executed,
"ddrnet_reused": len(results) - ddrnet_executed,
"ddrnet_source_age_ms": distribution(
[r["ddrnet_source_age_ms"] for r in results]
),
"ddrnet_source_age_ms": distribution([r["ddrnet_source_age_ms"] for r in results]),
"detector_executed": len(results),
}
latency = report["distributions_ms"].get("source_due_to_receiver_ms")
@@ -669,7 +683,7 @@ def run(args):
report["gates"] = {
"execution": report["execution_complete"],
"zero_drops_complete_accounting": camera_count == args.frames == len(results)
and not mailbox.dropped,
and mailbox.dropped_count == 0,
"worker_p95_p99_125ms": bool(latency and latency["p95"] <= 125 and latency["p99"] <= 125),
"source_release_lag_25ms": bool(
source_report.get("release_lag_ms") and source_report["release_lag_ms"]["max"] <= 25
@@ -679,7 +693,8 @@ def run(args):
"warmup_120s": report.get("warmup_ms", math.inf) <= 120000,
"stop_5s": report["stop_ms"] <= 5000
and report["children_stopped"]
and report["gpu_stage_stopped"],
and report["gpu_stage_stopped"]
and report["input_payloads_released"],
"vram_22000MiB": bool(samples)
and max(s.get("gpu_used_mib", 99999) for s in samples) <= 22000,
"container_memory_8192MiB": bool(samples)
@@ -735,6 +750,9 @@ if __name__ == "__main__":
"--ddrnet-preprocess", choices=("pinned-child", "native-numpy"), default="pinned-child"
)
parser.add_argument("--schedule", choices=("serial", "overlap-cpu"), default="serial")
parser.add_argument(
"--costmap-freshness", choices=("whole-scene", "per-cell"), default="per-cell"
)
parser.add_argument("--telemetry-mode", choices=("nvml", "none"), default="nvml")
parser.add_argument("--telemetry-device-state", action="store_true")
parser.add_argument("--triton-verbose", action="store_true")