streampetr-p150 on Tenstorrent p150
Derived from AutowareFoundation/camera_streampetr (tag
v1.0), the StreamPETR network that TIER IV trained for Autoware'sautoware_camera_streampetrcamera 3D detector, with unmodified weights. StreamPETR: Wang et al., arXiv:2303.11926. See the original model card for training details.
StreamPETR, an 82M-parameter camera-only 3D detector with a temporal object memory: a VoVNet-99-eSE + CPFPN image backbone on five cameras at 480x640, the PETR 3D position embedding (computed once per calibration) and a 6-layer transformer decoder over 644 learned and 256 propagated queries with a 1,024-row object memory kept on the device (about 1,100 GFLOPs per frame). Five surround-view camera images go in, with their intrinsics, camera-to-base_link extrinsics, the frame's time stamp and ego pose. Out come 3D boxes with scores and velocities for CAR, TRUCK, BUS, BICYCLE and PEDESTRIAN, with the pre- and post-processing of the autoware_camera_streampetr node (resize, crop and normalization, memory ego-motion compensation and resets, decoding from all six decoder layers, per-class score thresholds, IoU-BEV NMS).
Runs on Tenstorrent p150 (mesh P150).
Packaged and published with tt-model-manager 0.1.0 (manifest schema 5.1).
At a glance
| Hardware | p150 |
| License | apache-2.0 |
Intended use
Camera-only 3D object detection with temporal fusion for Autoware-style five-camera rigs: research, prototyping and offline processing of recorded drives, with the outputs of one autoware_camera_streampetr node.
Prerequisites
- The Tenstorrent CLI,
tt:uv tool install tenstorrent(or use tt-model alone, see the Quickstart) - Docker
- Tenstorrent hardware: p150
Quickstart
tt serve changh95/streampetr-p150
tt serve downloads the Docker image and the AutowareFoundation/camera_streampetr at 90f37d64186686ebc458509d69d3d4ca16d185c7 weights (into your HF cache; they are not in the image), then starts the model's own HTTP server on port 20000 (or the next free port, if that one is busy). The first start compiles kernels for your device, which takes several minutes; the server is ready when it logs Application startup complete.
Without tt-cli: tt-model serve changh95/streampetr-p150.
Send the synthetic five-camera sample shipped in this repo (with its calibration preset) to /predict, on the port tt serve reported:
B=https://huggingface.co/changh95/streampetr-p150/resolve/main/code/tt_streampetr/samples/synthetic_5cam
CAMS="CAM_FRONT CAM_FRONT_LEFT CAM_BACK_LEFT CAM_FRONT_RIGHT CAM_BACK_RIGHT"
for c in $CAMS; do curl -sLO $B/$c.png; done
{ printf '{"calibration": {"preset": "synthetic_5cam"}, "images": ['; s=
for c in $CAMS; do printf '%s{"camera": "%s", "data": "%s"}' "$s" $c "$(base64 -w0 $c.png)"; s=,; done
printf ']}'; } > req.json
curl -s localhost:20000/predict -H 'Content-Type: application/json' -d @req.json
code/tt_streampetr/server/client.py in this repo (standard library only) builds requests from image files with a
calibration file or preset and a stream id, time stamp and ego pose per frame.
Capabilities
This package exposes the model's own HTTP server on port 20000, not an OpenAI-compatible chat API — its request and response shapes are the model's own. See the author's notes for the payload it expects.
POST /predict takes one frame of the five cameras as JSON and returns its 3D detections. GET /health and
GET /info report the server and device state and the calibration presets; GET /v1/models is a stub, not a chat
API.
| request field | meaning |
|---|---|
images |
the five cameras CAM_FRONT, CAM_FRONT_LEFT, CAM_BACK_LEFT, CAM_FRONT_RIGHT, CAM_BACK_RIGHT, each once: [{"camera": "<name>", "data": "<base64 PNG, JPEG or .npy>"}], rgb8, any size (resized and cropped to 480x640 as the node does); optional per-camera timestamp_s for the node's 0.15 s camera-sync check |
calibration |
per camera intrinsics (CameraInfo P or K of the image as sent) and T_ref_from_camera (camera optical frame to base_link), or {"preset": "<name>"} (shipped: synthetic_5cam) |
stream (optional) |
the temporal memory: {"id": "<name>", "reset": false, "timestamp_s": <CAM_FRONT stamp>, "T_world_from_ego": <map from base_link>} per frame; without it, one frame from an empty memory |
params (optional) |
score_threshold (default: the node's per-class thresholds 0.36 / 0.39 / 0.38 / 0.41 / 0.43), iou_nms_threshold (0.5), iou_nms_search_distance (10 m), circle_nms_distance (0, off), decode_layers (6), awml_decode (false), max_detections (500) |
output_format (optional) |
json (default) or npz (adds the box arrays as a lossless base64 npz) |
Response for the sample (three of the five detections shown, meta shortened):
{"model": "streampetr-p150", "frame_id": "base_link", "num_detections": 5,
"detections": [
{"label": "PEDESTRIAN", "label_id": 4, "score": 0.9715, "center": [9.334, -2.159, 0.65], "size": [0.803, 0.749, 1.813], "yaw": 2.3695, "velocity": [-0.003, 0.003]},
{"label": "BUS", "label_id": 2, "score": 0.9697, "center": [8.977, 13.014, 1.606], "size": [11.762, 2.718, 3.189], "yaw": -2.8072, "velocity": [-0.001, -0.002]},
{"label": "CAR", "label_id": 0, "score": 0.9669, "center": [11.932, 1.191, 0.554], "size": [4.186, 1.731, 1.701], "yaw": 2.9098, "velocity": [-0.001, 0.001]}],
"meta": {"class_names": ["CAR", "TRUCK", "BUS", "BICYCLE", "PEDESTRIAN"], "num_pre_nms": 30, "fresh": true, "skipped": null, "frame_index": 1},
"timing_ms": {"preprocess": 20.058, "device": 38.295, "postprocess": 0.926, "total": 173.055, "decode": 113.471, "model_call": 59.32}}
- Boxes are in
base_link, metres and radians, as the node publishes them:centeris the box centre,sizeis[length along the heading, width, height]andyawis counter-clockwise from +x. Detections are sorted by score. labelis the class name, which the node also publishes as the Autoware label;label_idis the network class.velocityis the head's[vx, vy]in m/s, which the node computes but does not publish.metacarries the decoder layer and query of every box and the stream state (fresh,skipped,frame_index). A stream frame without an ego pose, or whose cameras are more than 0.15 s apart, returns no boxes and setsmeta.skipped, where the node publishes nothing.- A Python API with the same outputs (
tt_streampetr.StreamPETR) is described incode/PYTHON.md; it needs a tt-metal build with the two patches in this repo'spatches/directory.
Example output
3D boxes and 1.5 s track trails computed on the p150 from the five cameras of nuScenes scene-0796 (Singapore), run
free-running over every CAM_FRONT frame (about 12 Hz) and shown at key-frames 1-20, with the front-camera strip.
Also as MP4. The video was rendered with an earlier build of this port: same weights and
precision, identical outputs on single frames and short streams; long free-running streams differ slightly (see
Limitations). Non-commercial use only: rendered from the nuScenes dataset, © Motional AD Inc., CC BY-NC-SA 4.0
and the nuScenes Terms of Use; Motional does not endorse this work.
Attributions for every file in media/: media/ATTRIBUTION.md.
Expected performance
Agreement with the fp32 CPU reference of the same network (same weights, same pre- and post-processing). A reference box counts as recalled when a box of the same class lies within 1 m (F1 rows) and the reverse for precision; free-running means each side carries its own object memory through the whole sequence:
| eval | this package | fp32 CPU reference | frames |
|---|---|---|---|
| Boxes matched, synthetic sample | 5 of 5, max score difference 0.0021 | 5 | 1 |
| Boxes matched, PandaSet sequence 019 frame 40, empty memory: recall / precision | 0.957 / 1.000, max score difference 0.032 | – | 1 |
| Backbone feature PCC, PandaSet sequence 019 frame 40 | 0.999988 | – | 1 |
| Head output PCC with the reference's memory, classes / boxes, PandaSet frames 1 / 40 | 0.99953 / 0.99997, 0.99970 / 0.999994 | – | 2 |
| Top-256 memory selection overlap with the reference's memory, PandaSet frames 1 / 40 | 0.992 / 0.996 | – | 2 |
| Mean F1 per frame, free-running, PandaSet sequence 019 frames 0-79 (minimum) | 0.912 (0.794) | – | 80 |
| Mean F1 per frame, free-running, nuScenes scene-0103 / scene-0916 | 0.937 / 0.972 | – | 229 / 240 |
| nuScenes mini_val 5-class mAP, free-running, scored at the key-frames | 0.1956 | 0.1942 | 81 |
PandaSet 019 is a daytime San Francisco drive; nuScenes v1.0-mini scenes are from Boston and Singapore. Both are
outside the weights' training domain (TIER IV's own five-camera data), so the mAP row is a cross-domain check of
the port against the reference (nuScenes devkit, detection_cvpr_2019 settings on the 5 Autoware classes), not
comparable with the paper. The weights repo publishes no dataset-level accuracy to compare with.
| speed (batch 1, warm) | input | median | throughput |
|---|---|---|---|
| Device time per frame, back-to-back trace replays | five 480x640 network inputs | 35.04 ms | 28.5 frames/s |
| Device time, one blocking frame | five 480x640 network inputs | 35.11 ms (p99 35.14) | – |
Python model() call (resize and normalization, device, post-processing) |
five 1920x1080 PNG images (synthetic sample) | 67.1 ms (p99 79.4) | 14.9 frames/s |
Python model() call |
five 1920x1080 JPEG images (PandaSet) | 66.9 ms (p99 73.4) | 15.0 frames/s |
Served POST /predict, server timing_ms.total |
synthetic sample (4.0 MB request) | 172.0 ms | – |
Served POST /predict, client round trip on loopback |
synthetic sample | 203.9 ms | – |
Measured with this package's code on one p150 in an 8-vCPU AMD EPYC-Rome host: code/scripts/bench.py (100 warm
iterations for the blocking-frame and model() rows; the back-to-back row is the median over 3 batches of 30 trace
replays) and 30 warm requests of the sample to the package's server (tt_streampetr.server.app, run with uvicorn
on the host with the package's serve settings). The model() and served times are host-bound:
resizing and normalizing the five images takes about 27 ms of the call, and decoding the 4.0 MB base64 PNG body
takes 115.5 ms of the served time.
Limitations
- Do not use it to drive or control a vehicle or as any safety function: it is not a certified Autoware component and has no functional-safety qualification.
- Do not use its outputs as a StreamPETR accuracy benchmark: the weights are TIER IV's, trained on its own five-camera data, and accuracy on other rigs and public datasets is much lower (on CPU and p150 alike: vehicle recall / precision 0.37 / 0.40 on PandaSet 019 against the dataset's boxes).
- Not a ROS 2 node: it serves HTTP and Python only. Batch 1, one frame per request; requests are served one at a time. Fixed shapes: five cameras in the node's slots, a 480x640 network input, 644 + 256 queries, a 1,024-row memory and 5 classes. The server keeps one stream memory: a request with another stream id starts that stream from an empty memory.
- One p150 only. The image runs device dispatch on the chip's Ethernet cores, so multi-chip meshes are not supported.
- Long free-running streams: the backbone's eSE channel-mean kernel adds its fp32 sums in a fixed order that differs
from a plain matmul by about one fp32 ulp, and the temporal memory amplifies this. Single frames and short streams
are unaffected, but over long streams the boxes drift further from the fp32 reference: free-running mean F1 0.912
on PandaSet 019, against 0.921 with the matmul order. Setting
STREAMPETR_ESE_MEAN=matmulin the server environment selects the matmul order, at 2.4 ms more device time per frame. - Device precision: fp32 weights, bf16 backbone activations, fp32 head activations, HiFi4 with fp32 accumulation. Scores differ from the fp32 reference by up to 0.032 on a single frame and more on warm stream frames, so an object near a class threshold can appear or disappear. The node's default fp16 TensorRT engines are not reproduced: outputs were compared with the fp32 CPU reference, not with TensorRT.
- Differences from the deployed node: the position embedding is recomputed when a request's calibration changes;
skipped frames return an empty list with
meta.skippedwhere the node publishes nothing;velocityis reported.
Licensing
- Weights: AutowareFoundation/camera_streampetr at tag
v1.0(commit90f37d64186686ebc458509d69d3d4ca16d185c7), Apache-2.0 per its model card. Downloaded bytt serveat that commit, not redistributed here. The upstream card names TIER IV's T4 dataset (AWML StreamPETR t4base v2.5, 123,708 frames) as the training data and documents no further data restriction. - Pre- and post-processing ported from autoware_universe
perception/autoware_camera_streampetr(Apache-2.0). - Port and serving code (
code/) and the tt-metal patches inpatches/: Apache-2.0. - Sample
code/tt_streampetr/samples/synthetic_5cam/and its calibration preset: generated by this repo's code, no third-party data (Apache-2.0). - Demo media in
media/are renders, not part of the package. The demo video andmedia/*_NC.*come from nuScenes: non-commercial use only, CC BY-NC-SA 4.0, © Motional AD Inc. The PandaSet renders: CC BY 4.0 and the PandaSet Dataset Terms (Scale AI and Hesai). Neither dataset owner endorses this work. Full attributions:media/ATTRIBUTION.md.
Related packages
- The Autoware collection: the other Autoware perception and planning models packaged for the p150.
Feedback
Questions or problems with this package: open a discussion at https://huggingface.co/changh95/streampetr-p150/discussions — that is what reaches its author. A problem with the tt tooling itself: tt report issue (collects your environment and opens a prefilled issue against tenstorrent/tt-cli). Product feedback: support@tenstorrent.com.
Provenance
The exact sources the image was built from — code/ in this repo is byte-identical to the model code inside the image:
| component | built from |
|---|---|
| tt-metal | 44d66500520fda9f2c7060c0f6b41ec48f7ab37e (dirty tree — the image includes uncommitted changes) |
code/ digest |
aa55abbf455f8d28 (sha256, first 16 hex digits) |
| built | 2026-10-17T01:10:15+00:00 by tt-model 0.1.0 |