feat(perception): add PointPillars transfer gate
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"datasets": [
|
||||
{
|
||||
"annotations": [
|
||||
"point-semantic-labels",
|
||||
"point-instance-labels"
|
||||
],
|
||||
"dataset_id": "goose-3d/v2025-08-22",
|
||||
"independent_ground_truth": true,
|
||||
"installed": true,
|
||||
"license": "CC-BY-SA-4.0",
|
||||
"point_fields": [
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"intensity"
|
||||
],
|
||||
"release_identity_sha256": "0be9e0f8459bafcbc92ff7c3cc366e9b4e2f6e1e9bdf50e6439e1557e864c26f",
|
||||
"splits": [
|
||||
"validation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"annotations": [
|
||||
"point-semantic-labels",
|
||||
"point-instance-labels"
|
||||
],
|
||||
"dataset_id": "rellis-3d/v1.1",
|
||||
"independent_ground_truth": true,
|
||||
"installed": true,
|
||||
"license": "CC-BY-NC-SA-3.0",
|
||||
"point_fields": [
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"intensity"
|
||||
],
|
||||
"release_identity_sha256": "a3f3f161a5a7edccdf66cea75ecf9004b8f6a895282faabe661c075c15a82774",
|
||||
"splits": [
|
||||
"train",
|
||||
"validation",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"annotations": [
|
||||
"oriented-3d-boxes"
|
||||
],
|
||||
"dataset_id": "kitti-3d-object/v2017",
|
||||
"independent_ground_truth": true,
|
||||
"installed": true,
|
||||
"license": "CC-BY-NC-SA-3.0",
|
||||
"point_fields": [
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"intensity"
|
||||
],
|
||||
"release_identity_sha256": "2c9615bedca56b492b204b614d4419db6431626e2a6867e207a95999beefcf47",
|
||||
"splits": [
|
||||
"validation"
|
||||
]
|
||||
}
|
||||
],
|
||||
"observed_at_utc": "2026-07-31T06:45:42.249612Z",
|
||||
"schema_version": "missioncore.l3-lidar-dataset-inventory/v1"
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
{
|
||||
"authority": {
|
||||
"commands_enabled": false,
|
||||
"navigation_or_safety_accepted": false,
|
||||
"shadow_only": true
|
||||
},
|
||||
"detector": {
|
||||
"batch_size": 1,
|
||||
"candidate_frozen": true,
|
||||
"candidate_model_version": "deployable_v1.1",
|
||||
"candidate_label_sha256": "0adaeb5a374421b61bf83b8fa4522e11abd68461f239a4c72cf5627de913b3da",
|
||||
"candidate_source_sha256": "2dcabddc3a365e9608a112d7bbbb7db769a6dddeeaa59aa03611a83113326da1",
|
||||
"model_classes": [
|
||||
"Vehicle",
|
||||
"Pedestrian",
|
||||
"Cyclist"
|
||||
],
|
||||
"onnx_contract_sha256": "2fd29cd054ab058c2cfec3dfba305c71e123ef3f04b457d0c64de0c8dac2e1be",
|
||||
"family": "nvidia-tao-pointpillars",
|
||||
"input_coordinate_frame": "sensor/lidar",
|
||||
"input_fields": [
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"intensity"
|
||||
],
|
||||
"input_representation": "native-sensor-scan",
|
||||
"maximum_points": 204800,
|
||||
"point_cloud_range": [
|
||||
-51.20000076293945,
|
||||
-51.20000076293945,
|
||||
-1.399999976158142,
|
||||
51.20000076293945,
|
||||
51.20000076293945,
|
||||
4.400000095367432
|
||||
],
|
||||
"postprocessing": {
|
||||
"class_agnostic_nms": true,
|
||||
"embedded_contract_source": "onnx-node-attributes",
|
||||
"embedded_score_threshold": 0.1,
|
||||
"nms_iou_threshold": 0.01,
|
||||
"output_row_fields": [
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"length",
|
||||
"width",
|
||||
"height",
|
||||
"yaw",
|
||||
"class_id",
|
||||
"score"
|
||||
],
|
||||
"pre_nms_top_n": 4096,
|
||||
"reference_commit": "a540badc47812a17a94e924b537d49ad3969b5a8",
|
||||
"reference_repository": "https://github.com/NVIDIA-AI-IOT/tao_toolkit_recipes"
|
||||
},
|
||||
"required_source_format": "onnx",
|
||||
"training_domain": "proprietary-solid-state-lidar",
|
||||
"training_ground_truth_publicly_reproducible": false,
|
||||
"triton_model_name": "pointpillars",
|
||||
"upstream_model_id": "nvidia/tao/pointpillarnet"
|
||||
},
|
||||
"k1_transfer_stability": {
|
||||
"accuracy_claim_allowed": false,
|
||||
"metrics": [
|
||||
"input-admission-rate",
|
||||
"output-schema-valid-rate",
|
||||
"deterministic-replay-rate",
|
||||
"end-to-end-latency-ms",
|
||||
"queue-wait-ms",
|
||||
"drop-rate"
|
||||
],
|
||||
"requires_completed_public_cross_domain_probe": true,
|
||||
"retuning_allowed": false
|
||||
},
|
||||
"public_cross_domain_probe": {
|
||||
"benchmark_classes": [
|
||||
"Car",
|
||||
"Pedestrian",
|
||||
"Cyclist"
|
||||
],
|
||||
"independent_ground_truth_required": true,
|
||||
"metrics": [
|
||||
"bev-map",
|
||||
"3d-map",
|
||||
"center-error-m",
|
||||
"range-error-m",
|
||||
"yaw-error-rad",
|
||||
"distance-bucket-recall",
|
||||
"false-occupied-rate",
|
||||
"end-to-end-latency-ms"
|
||||
],
|
||||
"metric_contract": {
|
||||
"ap_interpolation": "40-point",
|
||||
"difficulty_filtering": false,
|
||||
"distance_buckets_m": [
|
||||
[
|
||||
0,
|
||||
20
|
||||
],
|
||||
[
|
||||
20,
|
||||
40
|
||||
],
|
||||
[
|
||||
40,
|
||||
70
|
||||
]
|
||||
],
|
||||
"iou_thresholds": {
|
||||
"Car": 0.7,
|
||||
"Cyclist": 0.5,
|
||||
"Pedestrian": 0.5
|
||||
},
|
||||
"evaluation_kind": "public-cross-domain-transfer-probe",
|
||||
"official_kitti_server_metric": false,
|
||||
"predictions_outside_shared_range_ignored": true
|
||||
},
|
||||
"model_to_benchmark_class_mapping": {
|
||||
"Cyclist": "Cyclist",
|
||||
"Pedestrian": "Pedestrian",
|
||||
"Vehicle": "Car"
|
||||
},
|
||||
"native_accuracy_claim_allowed": false,
|
||||
"required_ground_truth": "oriented-3d-boxes",
|
||||
"required_split": "validation",
|
||||
"retuning_allowed": false
|
||||
},
|
||||
"profile_id": "l3-pointpillars-public-transfer-probe-v1",
|
||||
"runtime_policy": {
|
||||
"engine_built_on_target_required": true,
|
||||
"existing_triton_only": true,
|
||||
"precision": "strongly-typed",
|
||||
"second_serving_stack_allowed": false,
|
||||
"triton_image": "nvcr.io/nvidia/tritonserver:26.06-py3",
|
||||
"triton_image_digest": "58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
|
||||
},
|
||||
"schema_version": "missioncore.l3-pointpillars-benchmark-profile/v1"
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"schema_version": "missioncore.l3-pointpillars-onnx-contract/v1",
|
||||
"source": {
|
||||
"model_id": "nvidia/tao/pointpillarnet",
|
||||
"model_version": "deployable_v1.1",
|
||||
"onnx_sha256": "2dcabddc3a365e9608a112d7bbbb7db769a6dddeeaa59aa03611a83113326da1"
|
||||
},
|
||||
"extraction": {
|
||||
"onnx_version": "1.22.0",
|
||||
"method": "read-only ONNX graph attribute inspection",
|
||||
"observed_at_utc": "2026-07-31T07:16:00Z"
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"dtype": "FP32",
|
||||
"name": "points",
|
||||
"shape": [
|
||||
"batch",
|
||||
204800,
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"dtype": "INT32",
|
||||
"name": "num_points",
|
||||
"shape": [
|
||||
"batch"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"dtype": "FP32",
|
||||
"name": "output_boxes",
|
||||
"shape": [
|
||||
"batch",
|
||||
393216,
|
||||
9
|
||||
]
|
||||
},
|
||||
{
|
||||
"dtype": "INT32",
|
||||
"name": "num_boxes",
|
||||
"shape": [
|
||||
"batch"
|
||||
]
|
||||
}
|
||||
],
|
||||
"plugins": {
|
||||
"DecodeBbox3DPlugin": {
|
||||
"anchor_bottom_height": [
|
||||
-1.7799999713897705,
|
||||
-0.6000000238418579,
|
||||
-0.6000000238418579
|
||||
],
|
||||
"anchors": [
|
||||
3.9000000953674316,
|
||||
1.600000023841858,
|
||||
1.559999942779541,
|
||||
0.0,
|
||||
3.9000000953674316,
|
||||
1.600000023841858,
|
||||
1.559999942779541,
|
||||
1.5700000524520874,
|
||||
0.800000011920929,
|
||||
0.6000000238418579,
|
||||
1.7300000190734863,
|
||||
0.0,
|
||||
0.800000011920929,
|
||||
0.6000000238418579,
|
||||
1.7300000190734863,
|
||||
1.5700000524520874,
|
||||
1.7599999904632568,
|
||||
0.6000000238418579,
|
||||
1.7300000190734863,
|
||||
0.0,
|
||||
1.7599999904632568,
|
||||
0.6000000238418579,
|
||||
1.7300000190734863,
|
||||
1.5700000524520874
|
||||
],
|
||||
"dir_limit_offset": 0.0,
|
||||
"dir_offset": 0.7853900194168091,
|
||||
"num_dir_bins": 2,
|
||||
"point_cloud_range": [
|
||||
-51.20000076293945,
|
||||
-51.20000076293945,
|
||||
-1.399999976158142,
|
||||
51.20000076293945,
|
||||
51.20000076293945,
|
||||
4.400000095367432
|
||||
],
|
||||
"score_threshold": 0.10000000149011612
|
||||
},
|
||||
"PillarScatterPlugin": {
|
||||
"dense_shape": [
|
||||
512,
|
||||
512
|
||||
]
|
||||
},
|
||||
"VoxelGeneratorPlugin": {
|
||||
"max_num_points_per_voxel": 32,
|
||||
"max_voxels": 10000,
|
||||
"point_cloud_range": [
|
||||
-51.20000076293945,
|
||||
-51.20000076293945,
|
||||
-1.399999976158142,
|
||||
51.20000076293945,
|
||||
51.20000076293945,
|
||||
4.400000095367432
|
||||
],
|
||||
"voxel_feature_num": 10,
|
||||
"voxel_size": [
|
||||
0.20000000298023224,
|
||||
0.20000000298023224,
|
||||
5.800000190734863
|
||||
]
|
||||
}
|
||||
},
|
||||
"claim_boundary": {
|
||||
"model_card_training_domain": "proprietary-solid-state-lidar",
|
||||
"public_training_truth_available": false,
|
||||
"public_kitti_is_cross_domain": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"gpu": {
|
||||
"driver_version": "610.47",
|
||||
"memory_total_mib": 24564,
|
||||
"name": "NVIDIA GeForce RTX 4090"
|
||||
},
|
||||
"host_id": "worker-006",
|
||||
"observed_at_utc": "2026-07-31T06:50:46.660883Z",
|
||||
"schema_version": "missioncore.l3-worker-inventory/v1",
|
||||
"serving_stack_count": 1,
|
||||
"staged_models": [
|
||||
{
|
||||
"artifact_sha256": "12005d972a4632d56342a5da44442b632c1dcc5144fa3c70b162dec334532481",
|
||||
"backend": "tensorrt",
|
||||
"model_classes": [
|
||||
"Vehicle",
|
||||
"Pedestrian",
|
||||
"Cyclist"
|
||||
],
|
||||
"engine_built_on_target": true,
|
||||
"input_fields": [
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"intensity"
|
||||
],
|
||||
"maximum_points": 204800,
|
||||
"name": "pointpillars",
|
||||
"outputs": [
|
||||
{
|
||||
"dtype": "FP32",
|
||||
"name": "output_boxes",
|
||||
"shape": [
|
||||
1,
|
||||
393216,
|
||||
9
|
||||
]
|
||||
},
|
||||
{
|
||||
"dtype": "INT32",
|
||||
"name": "num_boxes",
|
||||
"shape": [
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"point_cloud_range": [
|
||||
-51.20000076293945,
|
||||
-51.20000076293945,
|
||||
-1.399999976158142,
|
||||
51.20000076293945,
|
||||
51.20000076293945,
|
||||
4.400000095367432
|
||||
],
|
||||
"precision": "strongly-typed",
|
||||
"provenance_verified": true,
|
||||
"representation_smoke": {
|
||||
"accuracy_evaluated": false,
|
||||
"input_artifact_sha256": "95be16aec8496260ec25ff716c79d343a3fd9e3f4957df99bb8007c6b21c9f59",
|
||||
"input_point_count": 169883,
|
||||
"navigation_or_safety_accepted": false,
|
||||
"single_query_gpu_compute_ms": 55.2069,
|
||||
"source_frame_id": "2022-07-22_flight__0071_1658494234334310308",
|
||||
"source_id": "goose-3d/v2025-08-22",
|
||||
"status": "engine-executed"
|
||||
},
|
||||
"source_format": "onnx",
|
||||
"source_label_sha256": "0adaeb5a374421b61bf83b8fa4522e11abd68461f239a4c72cf5627de913b3da",
|
||||
"source_model_sha256": "2dcabddc3a365e9608a112d7bbbb7db769a6dddeeaa59aa03611a83113326da1",
|
||||
"upstream_version": "deployable_v1.1"
|
||||
}
|
||||
],
|
||||
"triton": {
|
||||
"container_name": "ndc-mission-core-triton",
|
||||
"healthy": true,
|
||||
"image": "nvcr.io/nvidia/tritonserver:26.06-py3",
|
||||
"image_digest": "58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794",
|
||||
"model_control_mode": "explicit",
|
||||
"model_repository_read_only": true,
|
||||
"models": [
|
||||
{
|
||||
"artifact_sha256": "c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063",
|
||||
"backend": "onnxruntime",
|
||||
"name": "yolox_s"
|
||||
},
|
||||
{
|
||||
"artifact_sha256": "12005d972a4632d56342a5da44442b632c1dcc5144fa3c70b162dec334532481",
|
||||
"backend": "tensorrt",
|
||||
"engine_built_on_target": true,
|
||||
"input_fields": [
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"intensity"
|
||||
],
|
||||
"live_schema_smoke": {
|
||||
"accuracy_evaluated": false,
|
||||
"inference_ms": 106.79901000548853,
|
||||
"input_artifact_sha256": "59a02fdaaab3b7e903713cb618e8f53efcaf71c144436ddfcdf4f28bdbd73d20",
|
||||
"input_point_count": 120268,
|
||||
"observed_model_classes": [
|
||||
"Cyclist",
|
||||
"Vehicle"
|
||||
],
|
||||
"output_box_count": 58,
|
||||
"source_frame_id": "000001",
|
||||
"source_id": "kitti-3d-object/v2017",
|
||||
"status": "engine-schema-executed"
|
||||
},
|
||||
"maximum_points": 204800,
|
||||
"model_classes": [
|
||||
"Vehicle",
|
||||
"Pedestrian",
|
||||
"Cyclist"
|
||||
],
|
||||
"name": "pointpillars",
|
||||
"outputs": [
|
||||
{
|
||||
"dtype": "FP32",
|
||||
"name": "output_boxes",
|
||||
"shape": [
|
||||
1,
|
||||
393216,
|
||||
9
|
||||
]
|
||||
},
|
||||
{
|
||||
"dtype": "INT32",
|
||||
"name": "num_boxes",
|
||||
"shape": [
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"point_cloud_range": [
|
||||
-51.20000076293945,
|
||||
-51.20000076293945,
|
||||
-1.399999976158142,
|
||||
51.20000076293945,
|
||||
51.20000076293945,
|
||||
4.400000095367432
|
||||
],
|
||||
"precision": "strongly-typed",
|
||||
"provenance_verified": true,
|
||||
"representation_smoke": {
|
||||
"accuracy_evaluated": false,
|
||||
"input_artifact_sha256": "95be16aec8496260ec25ff716c79d343a3fd9e3f4957df99bb8007c6b21c9f59",
|
||||
"input_point_count": 169883,
|
||||
"navigation_or_safety_accepted": false,
|
||||
"single_query_gpu_compute_ms": 55.2069,
|
||||
"source_frame_id": "2022-07-22_flight__0071_1658494234334310308",
|
||||
"source_id": "goose-3d/v2025-08-22",
|
||||
"status": "engine-executed"
|
||||
},
|
||||
"source_format": "onnx",
|
||||
"source_label_sha256": "0adaeb5a374421b61bf83b8fa4522e11abd68461f239a4c72cf5627de913b3da",
|
||||
"source_model_sha256": "2dcabddc3a365e9608a112d7bbbb7db769a6dddeeaa59aa03611a83113326da1",
|
||||
"upstream_version": "deployable_v1.1"
|
||||
}
|
||||
],
|
||||
"strict_readiness": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the minimal immutable L3 PointPillars package for Worker 006."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
SCHEMA = "missioncore.l3-pointpillars-worker-package/v1"
|
||||
_SOURCE_FILES = {
|
||||
"runtime/k1link/__init__.py": "src/k1link/__init__.py",
|
||||
"runtime/k1link/artifacts.py": "src/k1link/artifacts.py",
|
||||
"runtime/k1link/compute/kitti_pointpillars_benchmark.py": (
|
||||
"src/k1link/compute/kitti_pointpillars_benchmark.py"
|
||||
),
|
||||
"runtime/k1link/compute/l3_pointpillars_admission.py": (
|
||||
"src/k1link/compute/l3_pointpillars_admission.py"
|
||||
),
|
||||
"runtime/k1link/compute/pointpillars_postprocess.py": (
|
||||
"src/k1link/compute/pointpillars_postprocess.py"
|
||||
),
|
||||
"runtime/k1link/datasets/kitti_3d_admission.py": (
|
||||
"src/k1link/datasets/kitti_3d_admission.py"
|
||||
),
|
||||
"runtime/install_l3_pointpillars_triton_model.py": (
|
||||
"experiments/perception/worker/install_l3_pointpillars_triton_model.py"
|
||||
),
|
||||
"runtime/admit_l3_kitti_release.py": (
|
||||
"experiments/perception/worker/admit_l3_kitti_release.py"
|
||||
),
|
||||
"runtime/run_l3_pointpillars_admission.py": (
|
||||
"experiments/perception/run_l3_pointpillars_admission.py"
|
||||
),
|
||||
"runtime/run_l3_pointpillars_public_baseline.py": (
|
||||
"experiments/perception/worker/run_l3_pointpillars_public_baseline.py"
|
||||
),
|
||||
"runtime/smoke_l3_pointpillars_live.py": (
|
||||
"experiments/perception/worker/smoke_l3_pointpillars_live.py"
|
||||
),
|
||||
}
|
||||
_GENERATED_FILES = {
|
||||
"runtime/k1link/compute/__init__.py": (
|
||||
'"""Minimal L3 worker projection; import compute modules explicitly."""\n'
|
||||
),
|
||||
"runtime/k1link/datasets/__init__.py": (
|
||||
'"""Minimal L3 worker projection; import KITTI admission explicitly."""\n'
|
||||
),
|
||||
}
|
||||
_INPUT_FILES = {
|
||||
"input/profile.json": "experiments/perception/l3_pointpillars_benchmark_profile.json",
|
||||
"input/onnx-contract.json": (
|
||||
"experiments/perception/l3_pointpillars_onnx_contract_2026-07-31.json"
|
||||
),
|
||||
"input/dataset-inventory.json": (
|
||||
"experiments/perception/l3_dataset_inventory_2026-07-30.json"
|
||||
),
|
||||
"input/worker-inventory.json": (
|
||||
"experiments/perception/l3_worker_inventory_2026-07-30.json"
|
||||
),
|
||||
"input/triton/config.pbtxt": "experiments/perception/triton/pointpillars/config.pbtxt",
|
||||
}
|
||||
|
||||
|
||||
class L3WorkerPackageError(RuntimeError):
|
||||
"""The L3 worker package source or immutable package is invalid."""
|
||||
|
||||
|
||||
def build_l3_worker_package(
|
||||
*,
|
||||
repository_root: Path,
|
||||
output_root: Path,
|
||||
admission_result: Path | None = None,
|
||||
) -> Path:
|
||||
"""Build or reopen a content-addressed minimal Worker 006 runtime."""
|
||||
|
||||
repository = repository_root.resolve(strict=True)
|
||||
sources: dict[str, Path | None] = {
|
||||
target: repository / relative for target, relative in _SOURCE_FILES.items()
|
||||
}
|
||||
sources.update(
|
||||
{
|
||||
target: repository / relative
|
||||
for target, relative in _INPUT_FILES.items()
|
||||
}
|
||||
)
|
||||
sources.update({target: None for target in _GENERATED_FILES})
|
||||
if admission_result is not None:
|
||||
result = admission_result.resolve(strict=True)
|
||||
if not result.is_dir() or result.is_symlink():
|
||||
raise L3WorkerPackageError("L3 admission result root is invalid")
|
||||
admission_root = f"input/admission/{result.name}"
|
||||
sources[f"{admission_root}/manifest.json"] = result / "manifest.json"
|
||||
sources[f"{admission_root}/admission-report.json"] = (
|
||||
result / "admission-report.json"
|
||||
)
|
||||
|
||||
descriptors: list[dict[str, Any]] = []
|
||||
for relative, source in sorted(sources.items()):
|
||||
payload = _payload(relative, source)
|
||||
descriptors.append(
|
||||
{
|
||||
"path": relative,
|
||||
"byte_length": len(payload),
|
||||
"sha256": hashlib.sha256(payload).hexdigest(),
|
||||
}
|
||||
)
|
||||
identity = {
|
||||
"schema_version": SCHEMA,
|
||||
"classification": "minimal-l3-pointpillars-worker-runtime",
|
||||
"worker_host_id": "worker-006",
|
||||
"source_artifacts": descriptors,
|
||||
"artifact_paths": [row["path"] for row in descriptors],
|
||||
"execution_policy": {
|
||||
"sequential": True,
|
||||
"parallel_workers": 1,
|
||||
"existing_triton_only": True,
|
||||
"container_creation_allowed": False,
|
||||
"container_restart_allowed": False,
|
||||
"raw_tensor_export_allowed": False,
|
||||
},
|
||||
"authority": {
|
||||
"shadow_only": True,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
package_id = f"l3-pointpillars-worker-package-{identity_sha256}"
|
||||
output = output_root.expanduser().absolute()
|
||||
output.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
destination = output / package_id
|
||||
if destination.exists():
|
||||
validate_l3_worker_package(destination)
|
||||
return destination
|
||||
|
||||
staging = output / f".{package_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
for relative, source in sources.items():
|
||||
target = staging / relative
|
||||
target.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
if source is None:
|
||||
target.write_text(_GENERATED_FILES[relative], encoding="utf-8")
|
||||
else:
|
||||
shutil.copyfile(source, target)
|
||||
artifacts = [
|
||||
{
|
||||
"kind": relative,
|
||||
"path": relative,
|
||||
"byte_length": (staging / relative).stat().st_size,
|
||||
"sha256": _sha256(staging / relative),
|
||||
}
|
||||
for relative in sorted(sources)
|
||||
]
|
||||
manifest = {
|
||||
"schema_version": SCHEMA,
|
||||
"package_id": package_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": datetime.now(UTC)
|
||||
.isoformat(timespec="milliseconds")
|
||||
.replace("+00:00", "Z"),
|
||||
"artifacts": artifacts,
|
||||
}
|
||||
_write_json(staging / "manifest.json", manifest)
|
||||
validate_l3_worker_package(staging, allow_staging=True)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
validate_l3_worker_package(destination)
|
||||
return destination
|
||||
|
||||
|
||||
def validate_l3_worker_package(
|
||||
root: Path,
|
||||
*,
|
||||
allow_staging: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""Validate package identity, exact file set, and every member digest."""
|
||||
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest = _read_json(resolved / "manifest.json")
|
||||
identity = manifest.get("identity")
|
||||
identity_sha256 = manifest.get("identity_sha256")
|
||||
package_id = manifest.get("package_id")
|
||||
artifacts = manifest.get("artifacts")
|
||||
expected_name = (
|
||||
isinstance(package_id, str)
|
||||
and (
|
||||
resolved.name == package_id
|
||||
or (
|
||||
allow_staging
|
||||
and resolved.name.startswith(f".{package_id}.")
|
||||
and resolved.name.endswith(".tmp")
|
||||
)
|
||||
)
|
||||
)
|
||||
if (
|
||||
manifest.get("schema_version") != SCHEMA
|
||||
or not isinstance(identity, dict)
|
||||
or not isinstance(identity_sha256, str)
|
||||
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
|
||||
or package_id != f"l3-pointpillars-worker-package-{identity_sha256}"
|
||||
or not expected_name
|
||||
or not isinstance(artifacts, list)
|
||||
):
|
||||
raise L3WorkerPackageError("L3 worker package identity is invalid")
|
||||
expected_paths = set(identity.get("artifact_paths", []))
|
||||
actual_paths = {
|
||||
path.relative_to(resolved).as_posix()
|
||||
for path in resolved.rglob("*")
|
||||
if path.is_file()
|
||||
}
|
||||
if (
|
||||
not expected_paths
|
||||
or actual_paths != expected_paths | {"manifest.json"}
|
||||
or len(artifacts) != len(expected_paths)
|
||||
):
|
||||
raise L3WorkerPackageError("L3 worker package file set changed")
|
||||
observed: set[str] = set()
|
||||
for row in artifacts:
|
||||
if not isinstance(row, dict):
|
||||
raise L3WorkerPackageError("L3 worker package artifact is invalid")
|
||||
relative = row.get("path")
|
||||
path = resolved / str(relative)
|
||||
if (
|
||||
not isinstance(relative, str)
|
||||
or relative not in expected_paths
|
||||
or relative in observed
|
||||
or Path(relative).is_absolute()
|
||||
or ".." in Path(relative).parts
|
||||
or not path.is_file()
|
||||
or path.is_symlink()
|
||||
or row.get("kind") != relative
|
||||
or row.get("byte_length") != path.stat().st_size
|
||||
or row.get("sha256") != _sha256(path)
|
||||
):
|
||||
raise L3WorkerPackageError("L3 worker package artifact changed")
|
||||
observed.add(relative)
|
||||
if observed != expected_paths:
|
||||
raise L3WorkerPackageError("L3 worker package artifact coverage changed")
|
||||
return manifest
|
||||
|
||||
|
||||
def _payload(relative: str, source: Path | None) -> bytes:
|
||||
if source is None:
|
||||
return _GENERATED_FILES[relative].encode()
|
||||
if not source.is_file() or source.is_symlink():
|
||||
raise L3WorkerPackageError(f"L3 package source is invalid: {relative}")
|
||||
return source.read_bytes()
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode()
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
while chunk := stream.read(1024 * 1024):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8-sig"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise L3WorkerPackageError("L3 worker package manifest is invalid") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise L3WorkerPackageError("L3 worker package manifest is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
with path.open("x", encoding="utf-8", newline="\n") as stream:
|
||||
json.dump(value, stream, indent=2, sort_keys=True)
|
||||
stream.write("\n")
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--repository-root", type=Path, required=True)
|
||||
parser.add_argument("--output-root", type=Path, required=True)
|
||||
parser.add_argument("--admission-result", type=Path)
|
||||
args = parser.parse_args()
|
||||
package = build_l3_worker_package(
|
||||
repository_root=args.repository_root,
|
||||
output_root=args.output_root,
|
||||
admission_result=args.admission_result,
|
||||
)
|
||||
print(package)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the immutable L3 NVIDIA PointPillars benchmark admission."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from k1link.compute.l3_pointpillars_admission import (
|
||||
build_l3_pointpillars_admission,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--profile", type=Path, required=True)
|
||||
parser.add_argument("--dataset-inventory", type=Path, required=True)
|
||||
parser.add_argument("--worker-inventory", type=Path, required=True)
|
||||
parser.add_argument(
|
||||
"--output-root",
|
||||
type=Path,
|
||||
default=Path(".runtime/compute-experiments/l3/pointpillars-admissions"),
|
||||
)
|
||||
args = parser.parse_args()
|
||||
result = build_l3_pointpillars_admission(
|
||||
profile_path=args.profile,
|
||||
dataset_inventory_path=args.dataset_inventory,
|
||||
worker_inventory_path=args.worker_inventory,
|
||||
output_root=args.output_root,
|
||||
)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"result_id": result.result_id,
|
||||
"result_root": str(result.result_root),
|
||||
"status": result.report["status"],
|
||||
"blocker_codes": result.report["blocker_codes"],
|
||||
"next_gate": result.report["next_gate"],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 0 if result.public_transfer_probe_authorized else 3
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,42 @@
|
||||
name: "pointpillars"
|
||||
platform: "tensorrt_plan"
|
||||
max_batch_size: 0
|
||||
|
||||
input [
|
||||
{
|
||||
name: "points"
|
||||
data_type: TYPE_FP32
|
||||
dims: [1, 204800, 4]
|
||||
},
|
||||
{
|
||||
name: "num_points"
|
||||
data_type: TYPE_INT32
|
||||
dims: [1]
|
||||
}
|
||||
]
|
||||
|
||||
output [
|
||||
{
|
||||
name: "output_boxes"
|
||||
data_type: TYPE_FP32
|
||||
dims: [1, 393216, 9]
|
||||
},
|
||||
{
|
||||
name: "num_boxes"
|
||||
data_type: TYPE_INT32
|
||||
dims: [1]
|
||||
}
|
||||
]
|
||||
|
||||
instance_group [
|
||||
{
|
||||
count: 1
|
||||
kind: KIND_GPU
|
||||
}
|
||||
]
|
||||
|
||||
version_policy {
|
||||
specific {
|
||||
versions: 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Admit the canonical archive-only KITTI release on Worker 006."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from k1link.datasets.kitti_3d_admission import (
|
||||
admit_kitti_3d_object_release,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--dataset-root",
|
||||
type=Path,
|
||||
default=Path("/mnt/d/NDC_MISSIONCORE/datasets"),
|
||||
)
|
||||
args = parser.parse_args()
|
||||
result = admit_kitti_3d_object_release(args.dataset_root)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"source_id": result["source_id"],
|
||||
"status": result["status"],
|
||||
"release_identity_sha256": result["release_identity_sha256"],
|
||||
"training_frame_count": result["alignment"][
|
||||
"training_frame_count"
|
||||
],
|
||||
"validation_frame_count": result["alignment"]["split_counts"][
|
||||
"validation"
|
||||
],
|
||||
"next_action": result["next_action"],
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Promote the verified PointPillars engine into canonical Triton exactly once."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import urllib.request
|
||||
from contextlib import suppress
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from k1link.compute.l3_pointpillars_admission import (
|
||||
read_l3_pointpillars_admission,
|
||||
)
|
||||
|
||||
PROVENANCE_SCHEMA = "missioncore.triton-model-provenance/v1"
|
||||
MODEL_NAME = "pointpillars"
|
||||
EXPECTED_ENGINE_SHA256 = (
|
||||
"12005d972a4632d56342a5da44442b632c1dcc5144fa3c70b162dec334532481"
|
||||
)
|
||||
EXPECTED_ENGINE_BYTES = 8_785_436
|
||||
EXPECTED_CONFIG_SHA256 = (
|
||||
"a68e7e37ae611b7d4c3fb633b31360ddbdf26ab0a37a363cc566357e614a385c"
|
||||
)
|
||||
EXPECTED_SOURCE_SHA256 = (
|
||||
"2dcabddc3a365e9608a112d7bbbb7db769a6dddeeaa59aa03611a83113326da1"
|
||||
)
|
||||
EXPECTED_LABEL_SHA256 = (
|
||||
"0adaeb5a374421b61bf83b8fa4522e11abd68461f239a4c72cf5627de913b3da"
|
||||
)
|
||||
CANONICAL_MODEL_REPOSITORY = Path("/mnt/d/NDC_MISSIONCORE/runtime/models")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--admission-result", type=Path, required=True)
|
||||
parser.add_argument("--engine", type=Path, required=True)
|
||||
parser.add_argument("--config", type=Path, required=True)
|
||||
parser.add_argument(
|
||||
"--model-repository",
|
||||
type=Path,
|
||||
default=CANONICAL_MODEL_REPOSITORY,
|
||||
)
|
||||
parser.add_argument("--triton-url", default="http://127.0.0.1:8000")
|
||||
args = parser.parse_args()
|
||||
|
||||
admission = read_l3_pointpillars_admission(args.admission_result)
|
||||
if (
|
||||
admission.report.get("status") != "blocked-foundation-assets"
|
||||
or admission.report.get("blocker_codes")
|
||||
!= ["pointpillars-model-not-installed-live"]
|
||||
or admission.report.get("eligible_public_probe_dataset_ids")
|
||||
!= ["kitti-3d-object/v2017"]
|
||||
or admission.report.get("detector", {}).get("staged_target_engine_ready")
|
||||
is not True
|
||||
):
|
||||
raise RuntimeError(
|
||||
"L3 admission does not authorize canonical PointPillars promotion"
|
||||
)
|
||||
engine = args.engine.resolve(strict=True)
|
||||
config = args.config.resolve(strict=True)
|
||||
if engine.stat().st_size != EXPECTED_ENGINE_BYTES or _sha256(
|
||||
engine
|
||||
) != EXPECTED_ENGINE_SHA256:
|
||||
raise RuntimeError("PointPillars target engine identity changed")
|
||||
if _sha256(config) != EXPECTED_CONFIG_SHA256:
|
||||
raise RuntimeError("PointPillars Triton configuration identity changed")
|
||||
repository = args.model_repository.expanduser().absolute()
|
||||
if repository != CANONICAL_MODEL_REPOSITORY or not repository.is_dir():
|
||||
raise RuntimeError("PointPillars promotion requires the canonical model repository")
|
||||
destination = repository / MODEL_NAME
|
||||
if destination.exists():
|
||||
_validate_existing(destination)
|
||||
_require_model_ready(args.triton_url)
|
||||
print(
|
||||
json.dumps(
|
||||
{"status": "already-installed", "model": MODEL_NAME},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
provenance = {
|
||||
"schema_version": PROVENANCE_SCHEMA,
|
||||
"model_name": MODEL_NAME,
|
||||
"upstream_model_id": "nvidia/tao/pointpillarnet",
|
||||
"upstream_version": "deployable_v1.1",
|
||||
"source_format": "onnx",
|
||||
"source_model_sha256": EXPECTED_SOURCE_SHA256,
|
||||
"source_label_sha256": EXPECTED_LABEL_SHA256,
|
||||
"engine_sha256": EXPECTED_ENGINE_SHA256,
|
||||
"engine_byte_length": EXPECTED_ENGINE_BYTES,
|
||||
"config_sha256": EXPECTED_CONFIG_SHA256,
|
||||
"engine_built_on_target": True,
|
||||
"worker_host_id": "worker-006",
|
||||
"gpu": "NVIDIA GeForce RTX 4090",
|
||||
"compute_capability": "8.9",
|
||||
"tensorrt_version": "11.0.0",
|
||||
"precision": "strongly-typed",
|
||||
"admission_result_id": admission.result_id,
|
||||
"installed_at_utc": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
|
||||
"authority": {
|
||||
"shadow_only": True,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
}
|
||||
staging = Path(
|
||||
tempfile.mkdtemp(
|
||||
dir=repository,
|
||||
prefix=f".{MODEL_NAME}.",
|
||||
suffix=".incomplete",
|
||||
)
|
||||
)
|
||||
try:
|
||||
version = staging / "1"
|
||||
version.mkdir(mode=0o700)
|
||||
shutil.copyfile(engine, version / "model.plan")
|
||||
shutil.copyfile(config, staging / "config.pbtxt")
|
||||
_atomic_json(staging / "provenance.json", provenance)
|
||||
if (
|
||||
_sha256(version / "model.plan") != EXPECTED_ENGINE_SHA256
|
||||
or _sha256(staging / "config.pbtxt") != EXPECTED_CONFIG_SHA256
|
||||
):
|
||||
raise RuntimeError("PointPillars staged model package changed during copy")
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
|
||||
request = urllib.request.Request(
|
||||
f"{args.triton_url.rstrip('/')}/v2/repository/models/{MODEL_NAME}/load",
|
||||
data=b"{}",
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=180) as response:
|
||||
if response.status != 200:
|
||||
raise RuntimeError("canonical Triton rejected PointPillars load")
|
||||
_require_model_ready(args.triton_url)
|
||||
except OSError as exc:
|
||||
raise RuntimeError(
|
||||
"PointPillars package is installed but canonical Triton did not load it"
|
||||
) from exc
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "installed-and-ready",
|
||||
"model": MODEL_NAME,
|
||||
"engine_sha256": EXPECTED_ENGINE_SHA256,
|
||||
"provenance_sha256": _sha256(destination / "provenance.json"),
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def _validate_existing(destination: Path) -> None:
|
||||
engine = destination / "1/model.plan"
|
||||
config = destination / "config.pbtxt"
|
||||
provenance_path = destination / "provenance.json"
|
||||
if (
|
||||
not engine.is_file()
|
||||
or not config.is_file()
|
||||
or not provenance_path.is_file()
|
||||
or engine.stat().st_size != EXPECTED_ENGINE_BYTES
|
||||
or _sha256(engine) != EXPECTED_ENGINE_SHA256
|
||||
or _sha256(config) != EXPECTED_CONFIG_SHA256
|
||||
):
|
||||
raise RuntimeError("existing PointPillars model package is not admitted")
|
||||
try:
|
||||
provenance = json.loads(provenance_path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise RuntimeError("existing PointPillars provenance is invalid") from exc
|
||||
if (
|
||||
not isinstance(provenance, dict)
|
||||
or provenance.get("schema_version") != PROVENANCE_SCHEMA
|
||||
or provenance.get("model_name") != MODEL_NAME
|
||||
or provenance.get("engine_sha256") != EXPECTED_ENGINE_SHA256
|
||||
or provenance.get("source_model_sha256") != EXPECTED_SOURCE_SHA256
|
||||
or provenance.get("source_label_sha256") != EXPECTED_LABEL_SHA256
|
||||
or provenance.get("engine_built_on_target") is not True
|
||||
):
|
||||
raise RuntimeError("existing PointPillars provenance is invalid")
|
||||
|
||||
|
||||
def _require_model_ready(url: str) -> None:
|
||||
request = urllib.request.Request(
|
||||
f"{url.rstrip('/')}/v2/models/{MODEL_NAME}/ready",
|
||||
method="GET",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=30) as response:
|
||||
if response.status != 200:
|
||||
raise RuntimeError("PointPillars is not ready")
|
||||
except OSError as exc:
|
||||
raise RuntimeError("PointPillars is not ready in canonical Triton") from exc
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as source:
|
||||
for chunk in iter(lambda: source.read(8 * 1024**2), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _canonical_json(payload: Any) -> bytes:
|
||||
return json.dumps(
|
||||
payload,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _atomic_json(path: Path, payload: dict[str, Any]) -> None:
|
||||
descriptor, temporary = tempfile.mkstemp(
|
||||
dir=path.parent,
|
||||
prefix=f".{path.name}.",
|
||||
suffix=".tmp",
|
||||
)
|
||||
try:
|
||||
with os.fdopen(descriptor, "wb") as target:
|
||||
target.write(_canonical_json(payload) + b"\n")
|
||||
target.flush()
|
||||
os.fsync(target.fileno())
|
||||
os.replace(temporary, path)
|
||||
except BaseException:
|
||||
with suppress(OSError):
|
||||
os.unlink(temporary)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,588 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the complete KITTI validation baseline through canonical Triton."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from contextlib import suppress
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
from k1link.compute.kitti_pointpillars_benchmark import (
|
||||
PointPillarsFramePrediction,
|
||||
evaluate_pointpillars_predictions,
|
||||
read_kitti_validation_truth,
|
||||
)
|
||||
from k1link.compute.pointpillars_postprocess import (
|
||||
POINTPILLARS_EMBEDDED_SCORE_THRESHOLD,
|
||||
POINTPILLARS_MODEL_POINT_CLOUD_RANGE,
|
||||
PointPillarsBox,
|
||||
decode_pointpillars_output,
|
||||
)
|
||||
from k1link.datasets.kitti_3d_admission import (
|
||||
KITTI_3D_RELEASE_ROOT,
|
||||
KITTI_CALIB_ARCHIVE,
|
||||
KITTI_LABEL_ARCHIVE,
|
||||
KITTI_VELODYNE_ARCHIVE,
|
||||
read_kitti_3d_admission,
|
||||
read_kitti_standard_splits,
|
||||
)
|
||||
|
||||
RUN_SCHEMA = "missioncore.l3-pointpillars-kitti-transfer-run/v1"
|
||||
FRAME_SCHEMA = "missioncore.l3-pointpillars-kitti-transfer-frame/v1"
|
||||
REPORT_SCHEMA = "missioncore.l3-pointpillars-kitti-transfer-report/v1"
|
||||
MANIFEST_SCHEMA = "missioncore.l3-pointpillars-kitti-transfer-result/v1"
|
||||
WORKER_PACKAGE_SCHEMA = "missioncore.l3-pointpillars-worker-package/v1"
|
||||
MODEL_NAME = "pointpillars"
|
||||
MAXIMUM_POINTS = 204_800
|
||||
EXPECTED_MODEL_SHA256 = (
|
||||
"2dcabddc3a365e9608a112d7bbbb7db769a6dddeeaa59aa03611a83113326da1"
|
||||
)
|
||||
EXPECTED_LABEL_SHA256 = (
|
||||
"0adaeb5a374421b61bf83b8fa4522e11abd68461f239a4c72cf5627de913b3da"
|
||||
)
|
||||
EXPECTED_ENGINE_SHA256 = (
|
||||
"12005d972a4632d56342a5da44442b632c1dcc5144fa3c70b162dec334532481"
|
||||
)
|
||||
EXPECTED_ONNX_CONTRACT_SHA256 = (
|
||||
"2fd29cd054ab058c2cfec3dfba305c71e123ef3f04b457d0c64de0c8dac2e1be"
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dataset-root", type=Path, required=True)
|
||||
parser.add_argument("--profile", type=Path, required=True)
|
||||
parser.add_argument("--model-provenance", type=Path, required=True)
|
||||
parser.add_argument("--worker-package", type=Path, required=True)
|
||||
parser.add_argument(
|
||||
"--output-root",
|
||||
type=Path,
|
||||
default=Path(
|
||||
"/mnt/d/NDC_MISSIONCORE/runtime/experiments/l3/pointpillars-kitti"
|
||||
),
|
||||
)
|
||||
parser.add_argument("--triton-url", default="http://127.0.0.1:8000")
|
||||
args = parser.parse_args()
|
||||
|
||||
dataset_root = args.dataset_root.expanduser().absolute()
|
||||
admission = read_kitti_3d_admission(dataset_root)
|
||||
splits = read_kitti_standard_splits(dataset_root)
|
||||
validation_frame_ids = splits["validation"]
|
||||
profile_path = args.profile.resolve(strict=True)
|
||||
provenance_path = args.model_provenance.resolve(strict=True)
|
||||
profile = _read_json(profile_path)
|
||||
provenance = _read_json(provenance_path)
|
||||
_validate_profile_and_provenance(profile, provenance)
|
||||
worker_package = _read_worker_package(args.worker_package)
|
||||
_require_triton_ready(args.triton_url)
|
||||
|
||||
identity = {
|
||||
"schema_version": RUN_SCHEMA,
|
||||
"dataset_source_id": admission["source_id"],
|
||||
"dataset_release_identity_sha256": admission["release_identity_sha256"],
|
||||
"validation_frame_count": len(validation_frame_ids),
|
||||
"validation_frame_ids_sha256": hashlib.sha256(
|
||||
"\n".join(validation_frame_ids).encode("ascii") + b"\n"
|
||||
).hexdigest(),
|
||||
"profile_sha256": _sha256(profile_path),
|
||||
"model_provenance_sha256": _sha256(provenance_path),
|
||||
"worker_package_id": worker_package["package_id"],
|
||||
"worker_package_identity_sha256": worker_package["identity_sha256"],
|
||||
"engine_sha256": EXPECTED_ENGINE_SHA256,
|
||||
"producer_sha256": _sha256(Path(__file__)),
|
||||
"execution": {
|
||||
"worker_host_id": "worker-006",
|
||||
"triton_model_name": MODEL_NAME,
|
||||
"sequential": True,
|
||||
"parallel_workers": 1,
|
||||
},
|
||||
"authority": {
|
||||
"shadow_only": True,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
run_id = f"l3-pointpillars-kitti-{identity_sha256}"
|
||||
run_root = args.output_root.expanduser().absolute() / run_id
|
||||
frames_root = run_root / "frames"
|
||||
frames_root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
_write_once(run_root / "identity.json", identity)
|
||||
|
||||
archive_root = dataset_root / KITTI_3D_RELEASE_ROOT / "archives"
|
||||
points_archive = archive_root / KITTI_VELODYNE_ARCHIVE
|
||||
labels_archive = archive_root / KITTI_LABEL_ARCHIVE
|
||||
calibrations_archive = archive_root / KITTI_CALIB_ARCHIVE
|
||||
truth_by_frame = read_kitti_validation_truth(
|
||||
labels_archive=labels_archive,
|
||||
calibrations_archive=calibrations_archive,
|
||||
validation_frame_ids=validation_frame_ids,
|
||||
)
|
||||
|
||||
predictions: list[PointPillarsFramePrediction] = []
|
||||
try:
|
||||
with zipfile.ZipFile(points_archive.resolve(strict=True)) as points_zip:
|
||||
for completed, frame_id in enumerate(validation_frame_ids, start=1):
|
||||
member = f"training/velodyne/{frame_id}.bin"
|
||||
point_bytes = points_zip.read(member)
|
||||
point_sha256 = hashlib.sha256(point_bytes).hexdigest()
|
||||
frame_path = frames_root / f"{frame_id}.json"
|
||||
if frame_path.exists():
|
||||
prediction = _read_frame(
|
||||
frame_path,
|
||||
frame_id=frame_id,
|
||||
point_sha256=point_sha256,
|
||||
)
|
||||
else:
|
||||
prediction = _run_frame(
|
||||
triton_url=args.triton_url,
|
||||
frame_id=frame_id,
|
||||
point_bytes=point_bytes,
|
||||
)
|
||||
_atomic_json(
|
||||
frame_path,
|
||||
_frame_payload(prediction, point_sha256=point_sha256),
|
||||
)
|
||||
predictions.append(prediction)
|
||||
if completed == 1 or completed % 50 == 0:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"run_id": run_id,
|
||||
"completed": completed,
|
||||
"total": len(validation_frame_ids),
|
||||
"frame_id": frame_id,
|
||||
},
|
||||
sort_keys=True,
|
||||
),
|
||||
flush=True,
|
||||
)
|
||||
except (OSError, KeyError, zipfile.BadZipFile) as exc:
|
||||
raise RuntimeError("KITTI validation point clouds could not be read") from exc
|
||||
|
||||
metrics = evaluate_pointpillars_predictions(
|
||||
truth_by_frame=truth_by_frame,
|
||||
predictions=tuple(predictions),
|
||||
)
|
||||
report = {
|
||||
"schema_version": REPORT_SCHEMA,
|
||||
"run_id": run_id,
|
||||
"status": "public-cross-domain-transfer-probe-measured",
|
||||
"dataset_source_id": admission["source_id"],
|
||||
"dataset_release_identity_sha256": admission["release_identity_sha256"],
|
||||
"model": {
|
||||
"name": MODEL_NAME,
|
||||
"source_model_sha256": EXPECTED_MODEL_SHA256,
|
||||
"source_label_sha256": EXPECTED_LABEL_SHA256,
|
||||
"engine_sha256": EXPECTED_ENGINE_SHA256,
|
||||
},
|
||||
"metrics": metrics,
|
||||
"completed_at_utc": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
|
||||
"authority": identity["authority"],
|
||||
}
|
||||
_atomic_json(run_root / "report.json", report)
|
||||
manifest = {
|
||||
"schema_version": MANIFEST_SCHEMA,
|
||||
"run_id": run_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"status": report["status"],
|
||||
"artifacts": [
|
||||
_artifact(run_root / "identity.json", "run-identity"),
|
||||
_artifact(run_root / "report.json", "benchmark-report"),
|
||||
],
|
||||
"frame_result_count": len(predictions),
|
||||
"frame_results_identity_sha256": _frame_results_identity(frames_root),
|
||||
"authority": identity["authority"],
|
||||
}
|
||||
_atomic_json(run_root / "manifest.json", manifest)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"run_id": run_id,
|
||||
"status": report["status"],
|
||||
"frame_count": len(predictions),
|
||||
"bev_map40": metrics["aggregates"]["bev_map40"],
|
||||
"3d_map40": metrics["aggregates"]["3d_map40"],
|
||||
"false_occupied_rate": metrics["aggregates"][
|
||||
"false_occupied_rate"
|
||||
],
|
||||
},
|
||||
sort_keys=True,
|
||||
),
|
||||
flush=True,
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def _run_frame(
|
||||
*,
|
||||
triton_url: str,
|
||||
frame_id: str,
|
||||
point_bytes: bytes,
|
||||
) -> PointPillarsFramePrediction:
|
||||
if not point_bytes or len(point_bytes) % 16:
|
||||
raise RuntimeError("KITTI point frame is not packed XYZI")
|
||||
native = np.frombuffer(point_bytes, dtype="<f4").reshape(-1, 4)
|
||||
if not np.isfinite(native).all() or native.shape[0] > MAXIMUM_POINTS:
|
||||
raise RuntimeError("KITTI point frame violates the model input contract")
|
||||
points = np.zeros((1, MAXIMUM_POINTS, 4), dtype=np.float32)
|
||||
points[0, : native.shape[0]] = native
|
||||
num_points = np.asarray([native.shape[0]], dtype=np.int32)
|
||||
output_boxes, output_count, elapsed_ms = _infer(
|
||||
triton_url,
|
||||
points,
|
||||
num_points,
|
||||
)
|
||||
boxes = decode_pointpillars_output(output_boxes, output_count)
|
||||
return PointPillarsFramePrediction(
|
||||
frame_id=frame_id,
|
||||
boxes=boxes,
|
||||
inference_ms=elapsed_ms,
|
||||
)
|
||||
|
||||
|
||||
def _infer(
|
||||
triton_url: str,
|
||||
points: np.ndarray,
|
||||
num_points: np.ndarray,
|
||||
) -> tuple[np.ndarray, np.ndarray, float]:
|
||||
points_binary = np.ascontiguousarray(points, dtype=np.float32).tobytes()
|
||||
count_binary = np.ascontiguousarray(num_points, dtype=np.int32).tobytes()
|
||||
header = {
|
||||
"inputs": [
|
||||
{
|
||||
"name": "points",
|
||||
"shape": [1, MAXIMUM_POINTS, 4],
|
||||
"datatype": "FP32",
|
||||
"parameters": {"binary_data_size": len(points_binary)},
|
||||
},
|
||||
{
|
||||
"name": "num_points",
|
||||
"shape": [1],
|
||||
"datatype": "INT32",
|
||||
"parameters": {"binary_data_size": len(count_binary)},
|
||||
},
|
||||
],
|
||||
"outputs": [
|
||||
{"name": "output_boxes", "parameters": {"binary_data": True}},
|
||||
{"name": "num_boxes", "parameters": {"binary_data": True}},
|
||||
],
|
||||
}
|
||||
encoded_header = _canonical_json(header)
|
||||
request = urllib.request.Request(
|
||||
f"{triton_url.rstrip('/')}/v2/models/{MODEL_NAME}/infer",
|
||||
data=encoded_header + points_binary + count_binary,
|
||||
headers={
|
||||
"Content-Type": "application/octet-stream",
|
||||
"Inference-Header-Content-Length": str(len(encoded_header)),
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
started = time.perf_counter()
|
||||
with urllib.request.urlopen(request, timeout=120) as response:
|
||||
payload = response.read()
|
||||
header_length = int(response.headers["Inference-Header-Content-Length"])
|
||||
elapsed_ms = (time.perf_counter() - started) * 1000.0
|
||||
response_header = json.loads(payload[:header_length])
|
||||
outputs = response_header.get("outputs")
|
||||
if not isinstance(outputs, list) or len(outputs) != 2:
|
||||
raise RuntimeError("Triton PointPillars output set changed")
|
||||
offset = header_length
|
||||
decoded: dict[str, np.ndarray] = {}
|
||||
for descriptor in outputs:
|
||||
if not isinstance(descriptor, dict):
|
||||
raise RuntimeError("Triton PointPillars output descriptor is invalid")
|
||||
name = descriptor.get("name")
|
||||
parameters = descriptor.get("parameters")
|
||||
if not isinstance(name, str) or not isinstance(parameters, dict):
|
||||
raise RuntimeError("Triton PointPillars output descriptor is invalid")
|
||||
byte_length = parameters.get("binary_data_size")
|
||||
if (
|
||||
isinstance(byte_length, bool)
|
||||
or not isinstance(byte_length, int)
|
||||
or byte_length < 1
|
||||
):
|
||||
raise RuntimeError("Triton PointPillars binary output size is invalid")
|
||||
binary = payload[offset : offset + byte_length]
|
||||
if len(binary) != byte_length:
|
||||
raise RuntimeError("Triton PointPillars binary output is truncated")
|
||||
offset += byte_length
|
||||
if name == "output_boxes" and descriptor.get("datatype") == "FP32":
|
||||
decoded[name] = np.frombuffer(binary, dtype="<f4").reshape(
|
||||
1, 393_216, 9
|
||||
)
|
||||
elif name == "num_boxes" and descriptor.get("datatype") == "INT32":
|
||||
decoded[name] = np.frombuffer(binary, dtype="<i4").reshape(1)
|
||||
else:
|
||||
raise RuntimeError("Triton PointPillars output contract changed")
|
||||
if offset != len(payload) or set(decoded) != {"output_boxes", "num_boxes"}:
|
||||
raise RuntimeError("Triton PointPillars binary payload is invalid")
|
||||
return decoded["output_boxes"], decoded["num_boxes"], elapsed_ms
|
||||
|
||||
|
||||
def _require_triton_ready(url: str) -> None:
|
||||
for endpoint in ("/v2/health/ready", f"/v2/models/{MODEL_NAME}/ready"):
|
||||
request = urllib.request.Request(f"{url.rstrip('/')}{endpoint}", method="GET")
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=10) as response:
|
||||
if response.status != 200:
|
||||
raise RuntimeError("canonical Triton is not ready")
|
||||
except OSError as exc:
|
||||
raise RuntimeError("canonical Triton or PointPillars is not ready") from exc
|
||||
|
||||
|
||||
def _validate_profile_and_provenance(
|
||||
profile: dict[str, Any],
|
||||
provenance: dict[str, Any],
|
||||
) -> None:
|
||||
detector = profile.get("detector")
|
||||
postprocessing = (
|
||||
detector.get("postprocessing") if isinstance(detector, dict) else None
|
||||
)
|
||||
if (
|
||||
profile.get("schema_version")
|
||||
!= "missioncore.l3-pointpillars-benchmark-profile/v1"
|
||||
or not isinstance(detector, dict)
|
||||
or not isinstance(postprocessing, dict)
|
||||
or detector.get("candidate_source_sha256") != EXPECTED_MODEL_SHA256
|
||||
or detector.get("candidate_label_sha256") != EXPECTED_LABEL_SHA256
|
||||
or detector.get("triton_model_name") != MODEL_NAME
|
||||
or detector.get("model_classes")
|
||||
!= ["Vehicle", "Pedestrian", "Cyclist"]
|
||||
or detector.get("point_cloud_range")
|
||||
!= list(POINTPILLARS_MODEL_POINT_CLOUD_RANGE)
|
||||
or detector.get("training_domain")
|
||||
!= "proprietary-solid-state-lidar"
|
||||
or detector.get("training_ground_truth_publicly_reproducible") is not False
|
||||
or detector.get("onnx_contract_sha256")
|
||||
!= EXPECTED_ONNX_CONTRACT_SHA256
|
||||
or postprocessing.get("embedded_score_threshold")
|
||||
!= POINTPILLARS_EMBEDDED_SCORE_THRESHOLD
|
||||
or postprocessing.get("embedded_contract_source")
|
||||
!= "onnx-node-attributes"
|
||||
):
|
||||
raise RuntimeError("L3 PointPillars profile is invalid")
|
||||
if (
|
||||
provenance.get("model_name") != MODEL_NAME
|
||||
or provenance.get("source_model_sha256") != EXPECTED_MODEL_SHA256
|
||||
or provenance.get("source_label_sha256") != EXPECTED_LABEL_SHA256
|
||||
or provenance.get("engine_sha256") != EXPECTED_ENGINE_SHA256
|
||||
or provenance.get("engine_built_on_target") is not True
|
||||
or provenance.get("worker_host_id") != "worker-006"
|
||||
):
|
||||
raise RuntimeError("live PointPillars provenance is invalid")
|
||||
|
||||
|
||||
def _read_worker_package(path: Path) -> dict[str, Any]:
|
||||
package_root = path.expanduser().resolve(strict=True)
|
||||
runtime_package_root = Path(__file__).resolve(strict=True).parents[1]
|
||||
if (
|
||||
not package_root.is_dir()
|
||||
or package_root != runtime_package_root
|
||||
or package_root.is_symlink()
|
||||
):
|
||||
raise RuntimeError("L3 runner is not executing from its declared worker package")
|
||||
manifest = _read_json(package_root / "manifest.json")
|
||||
identity = manifest.get("identity")
|
||||
identity_sha256 = manifest.get("identity_sha256")
|
||||
package_id = manifest.get("package_id")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if (
|
||||
manifest.get("schema_version") != WORKER_PACKAGE_SCHEMA
|
||||
or not isinstance(identity, dict)
|
||||
or not isinstance(identity_sha256, str)
|
||||
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
|
||||
or package_id != f"l3-pointpillars-worker-package-{identity_sha256}"
|
||||
or package_root.name != package_id
|
||||
or not isinstance(artifacts, list)
|
||||
):
|
||||
raise RuntimeError("L3 worker package identity is invalid")
|
||||
expected_paths = identity.get("artifact_paths")
|
||||
if not isinstance(expected_paths, list) or not expected_paths:
|
||||
raise RuntimeError("L3 worker package file set is invalid")
|
||||
expected = set(expected_paths)
|
||||
actual = {
|
||||
member.relative_to(package_root).as_posix()
|
||||
for member in package_root.rglob("*")
|
||||
if member.is_file()
|
||||
}
|
||||
if actual != expected | {"manifest.json"} or len(artifacts) != len(expected):
|
||||
raise RuntimeError("L3 worker package file set changed")
|
||||
observed: set[str] = set()
|
||||
for descriptor in artifacts:
|
||||
if not isinstance(descriptor, dict):
|
||||
raise RuntimeError("L3 worker package artifact is invalid")
|
||||
relative = descriptor.get("path")
|
||||
artifact = package_root / str(relative)
|
||||
if (
|
||||
not isinstance(relative, str)
|
||||
or relative not in expected
|
||||
or relative in observed
|
||||
or Path(relative).is_absolute()
|
||||
or ".." in Path(relative).parts
|
||||
or not artifact.is_file()
|
||||
or artifact.is_symlink()
|
||||
or descriptor.get("kind") != relative
|
||||
or descriptor.get("byte_length") != artifact.stat().st_size
|
||||
or descriptor.get("sha256") != _sha256(artifact)
|
||||
):
|
||||
raise RuntimeError("L3 worker package artifact changed")
|
||||
observed.add(relative)
|
||||
if observed != expected:
|
||||
raise RuntimeError("L3 worker package artifact coverage changed")
|
||||
return manifest
|
||||
|
||||
|
||||
def _frame_payload(
|
||||
prediction: PointPillarsFramePrediction,
|
||||
*,
|
||||
point_sha256: str,
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": FRAME_SCHEMA,
|
||||
"frame_id": prediction.frame_id,
|
||||
"point_sha256": point_sha256,
|
||||
"inference_ms": prediction.inference_ms,
|
||||
"boxes": [
|
||||
{
|
||||
"x_m": box.x_m,
|
||||
"y_m": box.y_m,
|
||||
"z_m": box.z_m,
|
||||
"length_m": box.length_m,
|
||||
"width_m": box.width_m,
|
||||
"height_m": box.height_m,
|
||||
"yaw_rad": box.yaw_rad,
|
||||
"class_id": box.class_id,
|
||||
"model_class": box.model_class,
|
||||
"score": box.score,
|
||||
}
|
||||
for box in prediction.boxes
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _read_frame(
|
||||
path: Path,
|
||||
*,
|
||||
frame_id: str,
|
||||
point_sha256: str,
|
||||
) -> PointPillarsFramePrediction:
|
||||
payload = _read_json(path)
|
||||
boxes = payload.get("boxes")
|
||||
if (
|
||||
payload.get("schema_version") != FRAME_SCHEMA
|
||||
or payload.get("frame_id") != frame_id
|
||||
or payload.get("point_sha256") != point_sha256
|
||||
or not isinstance(boxes, list)
|
||||
):
|
||||
raise RuntimeError("cached PointPillars frame result is invalid")
|
||||
try:
|
||||
decoded = tuple(PointPillarsBox(**box) for box in boxes)
|
||||
inference_ms = float(payload["inference_ms"])
|
||||
except (KeyError, TypeError, ValueError) as exc:
|
||||
raise RuntimeError("cached PointPillars frame result is invalid") from exc
|
||||
if not math.isfinite(inference_ms) or inference_ms <= 0.0:
|
||||
raise RuntimeError("cached PointPillars frame latency is invalid")
|
||||
return PointPillarsFramePrediction(
|
||||
frame_id=frame_id,
|
||||
boxes=decoded,
|
||||
inference_ms=inference_ms,
|
||||
)
|
||||
|
||||
|
||||
def _frame_results_identity(frames_root: Path) -> str:
|
||||
descriptors = [
|
||||
{
|
||||
"name": path.name,
|
||||
"sha256": _sha256(path),
|
||||
"byte_length": path.stat().st_size,
|
||||
}
|
||||
for path in sorted(frames_root.glob("*.json"))
|
||||
]
|
||||
return hashlib.sha256(_canonical_json(descriptors)).hexdigest()
|
||||
|
||||
|
||||
def _artifact(path: Path, role: str) -> dict[str, Any]:
|
||||
return {
|
||||
"path": path.name,
|
||||
"role": role,
|
||||
"media_type": "application/json",
|
||||
"sha256": _sha256(path),
|
||||
"byte_length": path.stat().st_size,
|
||||
}
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise RuntimeError(f"{path.name} is invalid") from exc
|
||||
if not isinstance(payload, dict):
|
||||
raise RuntimeError(f"{path.name} is not an object")
|
||||
return payload
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as source:
|
||||
for chunk in iter(lambda: source.read(8 * 1024**2), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _canonical_json(payload: Any) -> bytes:
|
||||
return json.dumps(
|
||||
payload,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _write_once(path: Path, payload: dict[str, Any]) -> None:
|
||||
encoded = _canonical_json(payload) + b"\n"
|
||||
if path.exists():
|
||||
if path.read_bytes() != encoded:
|
||||
raise RuntimeError(f"{path.name} identity changed")
|
||||
return
|
||||
_atomic_bytes(path, encoded)
|
||||
|
||||
|
||||
def _atomic_json(path: Path, payload: dict[str, Any]) -> None:
|
||||
_atomic_bytes(path, _canonical_json(payload) + b"\n")
|
||||
|
||||
|
||||
def _atomic_bytes(path: Path, payload: bytes) -> None:
|
||||
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
descriptor, temporary = tempfile.mkstemp(
|
||||
dir=path.parent,
|
||||
prefix=f".{path.name}.",
|
||||
suffix=".tmp",
|
||||
)
|
||||
try:
|
||||
with os.fdopen(descriptor, "wb") as target:
|
||||
target.write(payload)
|
||||
target.flush()
|
||||
os.fsync(target.fileno())
|
||||
os.replace(temporary, path)
|
||||
except BaseException:
|
||||
with suppress(OSError):
|
||||
os.unlink(temporary)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Execute one schema-only PointPillars smoke frame in canonical Triton."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import zipfile
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
from run_l3_pointpillars_public_baseline import (
|
||||
EXPECTED_ENGINE_SHA256,
|
||||
EXPECTED_LABEL_SHA256,
|
||||
EXPECTED_MODEL_SHA256,
|
||||
_atomic_json,
|
||||
_read_json,
|
||||
_require_triton_ready,
|
||||
_run_frame,
|
||||
_validate_profile_and_provenance,
|
||||
)
|
||||
|
||||
from k1link.datasets.kitti_3d_admission import (
|
||||
KITTI_3D_RELEASE_ROOT,
|
||||
KITTI_VELODYNE_ARCHIVE,
|
||||
read_kitti_3d_admission,
|
||||
read_kitti_standard_splits,
|
||||
)
|
||||
|
||||
SCHEMA = "missioncore.l3-pointpillars-live-smoke/v1"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dataset-root", type=Path, required=True)
|
||||
parser.add_argument("--profile", type=Path, required=True)
|
||||
parser.add_argument("--model-provenance", type=Path, required=True)
|
||||
parser.add_argument("--frame-id")
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--triton-url", default="http://127.0.0.1:8000")
|
||||
args = parser.parse_args()
|
||||
|
||||
dataset_root = args.dataset_root.expanduser().absolute()
|
||||
admission = read_kitti_3d_admission(dataset_root)
|
||||
validation = read_kitti_standard_splits(dataset_root)["validation"]
|
||||
frame_id = args.frame_id or validation[0]
|
||||
if frame_id not in validation:
|
||||
raise RuntimeError("smoke frame is not in the admitted validation split")
|
||||
profile = _read_json(args.profile.resolve(strict=True))
|
||||
provenance = _read_json(args.model_provenance.resolve(strict=True))
|
||||
_validate_profile_and_provenance(profile, provenance)
|
||||
_require_triton_ready(args.triton_url)
|
||||
|
||||
points_archive = (
|
||||
dataset_root
|
||||
/ KITTI_3D_RELEASE_ROOT
|
||||
/ "archives"
|
||||
/ KITTI_VELODYNE_ARCHIVE
|
||||
)
|
||||
member = f"training/velodyne/{frame_id}.bin"
|
||||
try:
|
||||
with zipfile.ZipFile(points_archive.resolve(strict=True)) as source:
|
||||
point_bytes = source.read(member)
|
||||
except (OSError, KeyError, zipfile.BadZipFile) as exc:
|
||||
raise RuntimeError("smoke point frame could not be read") from exc
|
||||
prediction = _run_frame(
|
||||
triton_url=args.triton_url,
|
||||
frame_id=frame_id,
|
||||
point_bytes=point_bytes,
|
||||
)
|
||||
result = {
|
||||
"schema_version": SCHEMA,
|
||||
"status": "engine-schema-executed",
|
||||
"source_id": admission["source_id"],
|
||||
"dataset_release_identity_sha256": admission[
|
||||
"release_identity_sha256"
|
||||
],
|
||||
"frame_id": frame_id,
|
||||
"point_frame_sha256": hashlib.sha256(point_bytes).hexdigest(),
|
||||
"point_count": len(point_bytes) // 16,
|
||||
"post_nms_box_count": len(prediction.boxes),
|
||||
"observed_model_classes": sorted(
|
||||
{box.model_class for box in prediction.boxes}
|
||||
),
|
||||
"inference_ms": prediction.inference_ms,
|
||||
"model": {
|
||||
"name": "pointpillars",
|
||||
"source_model_sha256": EXPECTED_MODEL_SHA256,
|
||||
"source_label_sha256": EXPECTED_LABEL_SHA256,
|
||||
"engine_sha256": EXPECTED_ENGINE_SHA256,
|
||||
},
|
||||
"observed_at_utc": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
|
||||
"claim_boundary": {
|
||||
"accuracy_measured": False,
|
||||
"k1_transfer_evaluated": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
}
|
||||
_atomic_json(args.output.expanduser().absolute(), result)
|
||||
print(json.dumps(result, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user