bevdet-p150 on Tenstorrent p150
Derived from AutowareFoundation/tensorrt_bevdet (tag
v1.0), the BEVDet-R50-4DLongterm-Depth network deployed by Autoware'sautoware_tensorrt_bevdetcamera 3D detector, with unmodified weights. BEVDet: Huang et al., arXiv:2112.11790; BEVDet4D: arXiv:2203.17054. See the original model card for training details.
BEVDet-R50-4DLongterm-Depth, a 76M-parameter camera-only 3D detector with temporal fusion: a ResNet-50 + FPN image encoder with a camera-aware depth network (118 depth bins) on six cameras at 256x704, a BEVPool v2 lift-splat into a 128 x 128 bird's-eye-view grid (Β±51.2 m), fusion of the last 8 BEV maps, a ResNet + FPN BEV encoder and a CenterPoint-style head (498 GFLOPs per frame). Six surround-view camera images go in, with their intrinsics and camera-to-base_link extrinsics (and optionally the ego pose for motion-compensated fusion). Out come 3D boxes with scores and velocities for the 10 nuScenes classes and the Autoware label of each, with the pre- and post-processing of the autoware_tensorrt_bevdet node (resize and crop, BEV history, box decoding, Scale-NMS, label mapping). The image and BEV encoders run on the device; the lift-splat between them, the image resize and the NMS run on the host.
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 for Autoware-style six-camera rigs (Autoware's camera perception mode): research, prototyping and offline processing of recorded drives, with the outputs of one autoware_tensorrt_bevdet 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/bevdet-p150
tt serve downloads the Docker image and the AutowareFoundation/tensorrt_bevdet at 391466349a1b39c93b9865364a8088110ca8d97b 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/bevdet-p150.
Send the synthetic six-camera sample shipped in this repo (with its calibration preset) to /predict, on the port tt serve reported:
B=https://huggingface.co/changh95/bevdet-p150/resolve/main/code/tt_bevdet/samples/synthetic_6cam
CAMS="CAM_FRONT_LEFT CAM_FRONT CAM_FRONT_RIGHT CAM_BACK_LEFT CAM_BACK CAM_BACK_RIGHT"
for c in $CAMS; do curl -sLO $B/$c.png; done
{ printf '{"calibration": {"preset": "synthetic_6cam"}, "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_bevdet/server/client.py in this repo (standard library only) builds requests from image files with a
calibration file or preset and a stream id 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 six cameras as JSON and returns its 3D detections. GET /health and
GET /info report the server and device state, the labels and the calibration presets; GET /v1/models is a stub,
not a chat API.
| request field | meaning |
|---|---|
images |
the six cameras CAM_FRONT_LEFT, CAM_FRONT, CAM_FRONT_RIGHT, CAM_BACK_LEFT, CAM_BACK, CAM_BACK_RIGHT, each once: [{"camera": "<name>", "data": "<base64 PNG, JPEG or .npy>"}], any size (resized to 1600x900 and cropped to 256x704 as the node does) |
calibration |
per camera intrinsics of the image as sent and T_ref_from_camera (camera optical frame to base_link), or {"preset": "<name>"} (shipped: synthetic_6cam) |
stream (optional) |
the 8-frame BEV history: {"id": "<name>", "reset": false}, plus T_world_from_ego (map from base_link) for motion-compensated fusion; without it the history advances like the deployed node's, without ego motion |
params (optional) |
score_threshold (default 0.2), decode_score_threshold (0.1), nms_iou_threshold (0.2), max_pre_nms (500), max_detections (500), nms_rescale_factor (per class) |
output_format (optional) |
json (default) or npz (adds the box arrays as a lossless base64 npz) |
Response for the sample, the first request after the server starts (two of the ten detections shown, meta
shortened). It fuses an empty history and builds the lift-splat tables for the new calibration, so its
model_call takes 0.5 s; warm requests take the times under Expected performance, and repeated requests on the
same stream fuse the BEV history, so their scores change slightly:
{"model": "bevdet-p150", "frame_id": "base_link", "num_detections": 10,
"detections": [
{"label": "traffic_cone", "label_id": 9, "score": 0.9572, "center": [7.639, -3.562, 0.298], "size": [0.376, 0.365, 0.78], "yaw": 0.128, "velocity": [-0.0, 0.0], "autoware_label": "UNKNOWN", "twist": {"linear_x": 0.0, "angular_z": 5.3139}},
{"label": "car", "label_id": 0, "score": 0.924, "center": [6.039, 9.203, 0.873], "size": [4.827, 1.982, 1.967], "yaw": 0.2323, "velocity": [-0.0, -0.0], "autoware_label": "CAR", "twist": {"linear_x": 0.0, "angular_z": -4.2956}}],
"meta": {"class_names": ["car", "truck", "..."], "autoware_labels": ["CAR", "TRUCK", "..."], "score_threshold": 0.2, "stream": {"id": "default", "frame": 0, "flag": 1}},
"timing_ms": {"encode": 10.483, "bevpool": 8.029, "bev_decode": 8.796, "preprocess": 481.679, "device": 27.95, "postprocess": 1.834, "total": 935.998, "decode": 423.309, "model_call": 511.62}}
center(z at the box's gravity centre),size([length, width, height]) andyaw(counter-clockwise from +x) are inbase_link, metres and radians, as the node publishes them. Detections are sorted by score.labelis the network class (one of the 10 nuScenes classes,label_idits index);autoware_labelis the label the node publishes (construction_vehicle, barrier and traffic_cone become UNKNOWN).velocityis[vx, vy]in m/s inbase_linkaxes;twistis the node'stwist.linear.x/twist.angular.z, reproduced as deployed.- A Python API with the same outputs (
tt_bevdet.BEVDet) is described incode/PYTHON.md; it needs a tt-metal build with the two patches in this repo'spatches/directory.
Example output
3D boxes (score β₯ 0.4) computed on the p150 for 20 consecutive key-frames of nuScenes scene-0553 (Boston: waiting
at an intersection, a cyclist, a large truck, pedestrians on the crosswalk; 2 Hz, shown at 4 frames/s), on the
three front cameras and in bird's-eye view. Also as MP4. The video was rendered with an
earlier build of this port (same weights and precision); its boxes differ from this package's only within the
agreement figures under Expected performance. 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 with score β₯ 0.2 counts as recalled when a box of the same class lies within 0.5 m, and the reverse for precision:
| eval | this package | fp32 CPU reference | frames |
|---|---|---|---|
| Boxes matched, synthetic sample | 10 of 10, max score difference 0.0038, centres within 0.005 m | 10 | 1 |
| Depth / image feature / BEV map PCC, PandaSet sequence 019 | 0.99994 / 0.99994 / 0.99995 | β | 2 |
| Head output PCC (six maps), minimum, PandaSet sequence 019 | 0.99953 | β | 2 |
| Box recall / precision, nuScenes scene-0103 / scene-0916 with ego poses | 0.9919 / 0.9913, 0.9892 / 0.9902; p99 score difference 0.0079 / 0.0086 | β | 81 |
| Box recall / precision, PandaSet sequence 019 frames 38-40, worst of the three | 0.992 / 0.981, max score difference 0.0078 | β | 3 |
| nuScenes mini_val mAP / NDS, with ego poses | 0.3658 / 0.4354 | 0.3673 / 0.4367 | 81 |
| nuScenes mini_val mAP / NDS, the deployed node's temporal semantics (no ego motion) | 0.2533 / 0.2742 | 0.2535 / 0.2746 | 81 |
nuScenes rows: the 81 key-frames of the two mini_val scenes (Boston and Singapore; 3 of the 10 classes do not
occur), scored with the nuScenes devkit (detection_cvpr_2019); the weights were trained on nuScenes, and the
BEVDet authors report 0.394 / 0.515 mAP / NDS on the full validation set for this configuration, which 81 frames
cannot reproduce. PandaSet 019 is a daytime San Francisco drive from another camera rig.
| speed (batch 1, warm) | input | median | throughput |
|---|---|---|---|
| Device time per frame (image encoder, BEV encoder and head, history update), back-to-back trace replays | six 256x704 network inputs | 9.99 ms | 100 frames/s |
| Device time per frame, with ego-motion alignment | six 256x704 network inputs | 14.71 ms | 68 frames/s |
Python model() call (resize, device, host lift-splat, NMS) |
six 1600x900 images | 30.8-32.5 ms | 31-32 frames/s |
Python model() call, with ego poses |
six 1600x900 nuScenes JPEGs | 39.4-39.6 ms | 25 frames/s |
Python model() call |
six 1920x1080 PandaSet JPEGs | 34.0-34.5 ms | 29 frames/s |
Served POST /predict, server timing_ms.model_call |
synthetic sample | 27.7 ms | β |
Served POST /predict, server timing_ms.total |
synthetic sample (9.6 MB request of six PNGs) | 393.5 ms | β |
Served POST /predict, client round trip on loopback |
synthetic sample | 474.8 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 per stage; the ranges span two sessions) and 40 requests of the sample to the package's server app
running on the host. About two thirds of a model() call is host work: pre-processing with the image resize
(about 3.5 ms at 1600x900, 7.3-7.5 ms at 1920x1080), the lift-splat between the two device stages (7-8 ms) and box
decoding with NMS (1.5-4.5 ms). Decoding the six base64 PNGs takes most 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 BEVDet accuracy benchmark: the mini_val figures cover 81 frames, and on camera rigs other than nuScenes' the weights' depth prior is biased (on CPU and p150 alike: on PandaSet front objects come out about 15 % too close and side objects 10-20 % too far).
- 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: six cameras, a 256x704 network input, a 128 x 128 BEV grid (Β±51.2 m), 8 history frames and 10 classes; a rig whose lift-splat tables exceed 370,000 points / 14,000 intervals is refused, as in the TensorRT node. At most 4 stream histories are kept; a fifth stream id evicts the least recently used one.
- One p150 only. The image runs device dispatch on the chip's Ethernet cores, so multi-chip meshes are not supported.
- The first call on a new calibration or image size builds its lift-splat tables: 0.18-1.6 s, measured.
- By default the temporal fusion follows the deployed node (no ego-motion alignment, which lowers mAP / NDS by
0.11 / 0.16 on nuScenes mini_val); pass
stream.T_world_from_egofor the motion-compensated history. - Intrinsics are taken for the image as sent and scaled with the resize to 1600x900; the deployed node keeps the
raw
CameraInfo.kwhen it resizes (calibration.scale_intrinsics: falsereproduces it, with far fewer and misplaced boxes on a 1920x1080 rig). - Device precision: fp32 weights, bf16 activations, HiFi4 with fp32 accumulation. Scores differ from the fp32 reference by up to about 0.009, so an object within a few thousandths of the 0.2 publication threshold can appear or disappear. The node's default fp16 TensorRT engine is not reproduced: outputs were compared with the fp32 CPU reference, not with TensorRT.
Licensing
- Weights: AutowareFoundation/tensorrt_bevdet at tag
v1.0(commit391466349a1b39c93b9865364a8088110ca8d97b), Apache-2.0 per its model card. Downloaded bytt serveat that commit, not redistributed here. The upstream card's Legal Notice: the weights were trained on nuScenes (CC BY-NC-SA 4.0, non-commercial); read it before any commercial use. - Pre- and post-processing ported from autoware_universe
perception/autoware_tensorrt_bevdet(Apache-2.0). The node's inference librarybevdet_vendorcarries no licence; its behaviour was re-implemented from a written description, without copying code. - Port and serving code (
code/) and the tt-metal patches inpatches/: Apache-2.0. - Sample
code/tt_bevdet/samples/synthetic_6cam/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/bevdet-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 |
434224b18fba83a3 (sha256, first 16 hex digits) |
| built | 2026-10-17T02:22:33+00:00 by tt-model 0.1.0 |
Model tree for changh95/bevdet-p150
Base model
AutowareFoundation/tensorrt_bevdet