File size: 43,507 Bytes
fe51a63 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 | # frnet-p150 baseline on the p150 (before optimization)
Date 2026-10-09. The model code is the port's final commit `14fa968` (M2-M4; VERIFY_PORT round 1: PASS), unchanged.
The baseline commits add only the measurement scripts (`c246f92`: `code/scripts/bench.py`, `profile_ops.py`,
`profile_summary.py`, `visualizer_report.py`; this commit: `visualizer_report.py --json-dir`) and this file.
tt-metal `44d66500520` + `patches/tt-metal-eth-dispatch.patch`. Weights `AutowareFoundation/lidar_frnet` @
`a53a1c11b0b` (tag `v2.0`), read from the sha256-pinned local copy through `FRNET_WEIGHTS_DIR`.
Both serve profiles are measured: **`ot128`** (default; Hesai OT128, 128 Γ 1024 frustum grid, `N_cap` 160,000
points, `M_cap` 60,000 cells) and **`qt128`** (Hesai QT128, 128 Γ 256 grid, `N_cap` 120,000, `M_cap` 60,000). The
device graph has fixed shapes per profile (`N_cap`, `M_cap`). So the point-wise device work does not depend on the
frame's point count, while the host rows and the data-dependent K1 segment reduction do.
Inputs, batch 1:
- **Synthetic scan** (generated by us, no dataset, Apache-2.0):
- made by `research/frnet/scripts/frnet_preproc_ref.py synthetic_scan` (ray cast against ground, cars, facades,
poles and pedestrians; seed 0; sensor 2 m above the ground) through
`logs/frnet/m5_baseline/scripts/make_bench_inputs.py`. Its fp32 CPU reference (ONNX Runtime on the pinned
deployed ONNX) sits next to it;
- OT128: 512 columns, 53,411 points, 52,613 after the ego crop, no interpolated point, N = M = 52,613;
- QT128: 600 columns, 45,783 points, 41,806 after the crop, N = 69,553 after interpolation, M = 18,926;
- it stands in for the release's synthetic shipped sample (the same generator), which is not built yet. Update at
the release: the shipped `code/tt_frnet/samples/synthetic_<variant>.npz` (`code/scripts/make_sample.py`) are
byte-identical to these bench inputs, so the synthetic rows below are the shipped sample's.
- **PandaSet 019 frame 40** (CC BY 4.0 + PandaSet terms), today's `code/tt_frnet/samples/pandaset_019_40.npz`:
- 107,146 points, 106,501 after the ego crop;
- OT128: N = 145,961 after interpolation, M = 40,691; QT128: N = 106,585, M = 6,255.
The first release moved it to the git-ignored `staging_samples_pandaset/samples/` (not shipped until PandaSet
samples are approved); the commands below name its old path.
Configuration of every number, unless a row says otherwise:
- ETH dispatch, 1 CQ, 12Γ10 grid (`device.compute_with_storage_grid_size()`, printed in each log);
- the shipped precision knobs of `tt-model.yaml` serve.env: `FRNET_POINT_TERMS=2` (two-term bf16 point island),
`FRNET_MAP_DTYPE=float32` (fp32 image-branch maps), `FRNET_CONV_TERMS=1`. The job unsets every other `FRNET_*`, so
the code defaults (= the serve.env pins) apply.
## Environment
| item | value |
|---|---|
| tt-metal | `44d66500520` (main) + `patches/tt-metal-eth-dispatch.patch` (sha256 `08d0ddf6β¦`, the same file as the workspace copy) |
| ttaw | 0.20.0, vendored from `common` `89dec49` (`code/tt_frnet/ttaw/VENDORED.json`, `source_dirty: false`). `vendor.py --check`: 0 differences, so no re-vendor was needed |
| device | Blackhole p150b, KMD 2.10.0. ETH dispatch, 1 CQ, 12Γ10 = 120 cores, `l1_small_size` 64 KiB, `trace_region_size` 256 MiB (8.7 MiB used by OT128, 5.6 MiB by QT128). One trace per process; 227 programs in the program cache (OT128), 174 (QT128) |
| AICLK | 1350 MHz median (min 1343) over 4,568 (OT128) and 3,278 (QT128) samples, one every 50 ms, during the stage benches (`ttaw.profiling.AiclkSampler`, sysfs); the same in every matrix run. Board power median 78 W (max 209 W), ASIC 64.9β71.7 Β°C. The profiler CSV header reads CHIP_FREQ 1350 MHz |
| numerics | point island (encoder, compression, point stem / fusions / fuse, head): fp32 activations, every linear as three bf16 matmuls (`x_hi W_hi + x_hi W_lo + x_lo W_hi`) with fp32 outputs summed in fp32, HiFi4. Image branch: fp32 weights, bf16 conv operands, fp32 maps (convs write fp32; HardSwish, adds and `addcmul` in fp32), HiFi4 + fp32 dest acc + packer L1 acc. Gathers and resize outputs bf16. The profile shows every matmul and conv as HiFi4 |
| host | AMD EPYC-Rome VM, 8 vCPUs, 90 GB RAM, Linux 6.8. Shared with other agents' jobs: load average 4.1β6.6 during the device job. `OMP_NUM_THREADS=4`. Host rows move with that load; device rows do not (b2b rounds repeat to 0.02 ms) |
## How to run
```bash
ROOT=/home/ubuntu/experiments/tt-models; cd $ROOT/bundles/frnet-p150; source $ROOT/bin/tt-env.sh
export PYTHONPATH=$PWD/code:$PYTHONPATH HF_HUB_OFFLINE=1 OMP_NUM_THREADS=4 \
FRNET_WEIGHTS_DIR=$ROOT/assets/frnet/hf_lidar_frnet # v2.0, sha256-pinned
# accuracy gates (per-module PCC + end-to-end agreement vs the fp32 CPU reference, both profiles), one job
$ROOT/bin/devrun -t 1800 -- env TTAW_GATES_READONLY=1 python -m pytest -p no:cacheprovider -q -s \
code/tt_frnet/tests/test_pcc_device.py code/tt_frnet/tests/test_e2e_device.py
# stage breakdown (pre split, host staging, H2D split, trace, b2b, D2H split, unsort, post, e2e p50/p99), with a check
# of each served output against its stored CPU reference (<stem>.<variant>.reference.json next to the input)
$ROOT/bin/devrun -t 900 -- python code/scripts/bench.py --variant ot128 --iters 100 --warmup 10 --b2b-iters 50 \
--b2b-rounds 5 --input <synthetic_ot128.npz> --input code/tt_frnet/samples/pandaset_019_40.npz --json <out>.json
# dispatch / CQ matrix: one process per configuration
$ROOT/bin/devrun -t 900 -- python code/scripts/bench.py --variant qt128 --iters 50 --input <npz> --dispatch worker --cqs 2 --json <out>.json
# device profile: one eager forward (module / layer signposts) + one traced replay between signposts (1,104 programs:
# above the profiler's default 1,000-op buffer, hence --op-support-count; each section is flushed on its own)
$ROOT/bin/devrun -t 3600 -- python -m tracy -r -p -v --no-web-server --op-support-count 4000 \
-o $ROOT/generated/profiler/frnet_baseline code/scripts/profile_ops.py --variant ot128 --input <npz>
tt-perf-report <ops_perf_results_*.csv> --start-signpost trace --end-signpost trace_end --tracing-mode
python code/scripts/profile_summary.py <ops_perf_results_*.csv> profile_summary.json --md profile_summary.md
# ttnn-visualizer memory + graph report of one eager forward (slow: never used for timing; the 7.5 GB graph JSON goes to tmpfs)
$ROOT/bin/devrun -t 2400 -- python code/scripts/visualizer_report.py $ROOT/generated/ttnn_visualizer/frnet_baseline \
--variant ot128 --input code/tt_frnet/samples/pandaset_019_40.npz --json-dir /dev/shm/frnet_visualizer
ttnn-visualizer --profiler-path $ROOT/generated/ttnn_visualizer/frnet_baseline \
--performance-path $ROOT/generated/profiler/frnet_baseline/reports/2026_10_09_13_56_53
```
- All device measurements ran in two `bin/devrun` windows on 2026-10-09, every step rc 0 (job script
`logs/frnet/m5_baseline/scripts/job1_all.sh`, environment `env.sh`):
- 13:08:29β13:10:07 UTC: the device suite (step 1);
- 13:26:44β14:04:39 UTC: stage benches, matrix, both profiles and the visualizer report (steps 2β5).
The first window also started steps 2β5, which failed in 1 s each: the HF cache holds no snapshot of the pinned
revision, so `env.sh` now sets `FRNET_WEIGHTS_DIR` (logs of that attempt: `logs/frnet/m5_baseline/try0/`).
- The OT128 replay is **1,104 programs**, above the device profiler's default buffer of 1,000. The run raises the
buffer with `--op-support-count 4000`, and `profile_ops.py` flushes each section with `ttnn.ReadDeviceProfiler`.
The replay's op sequence matches the eager one op code by op code (1,104 / 1,104; QT128 759 / 759), so no op was
dropped.
## Accuracy (baseline)
The device suite ran read-only (`TTAW_GATES_READONLY=1`, reports tagged `_m5`: `logs/frnet/{pcc,e2e,latency}_{ot128,qt128}_m5.json`,
`logs/frnet/m5_baseline/try0/suite_r1.log`): **12 passed** in 94.5 s. The values are bit-identical to PORT_LOG M4 (j6)
and VERIFY_PORT round 1 (the graph is deterministic). The gate JSONs are unchanged (`git status` clean).
| check | gate | OT128 | QT128 |
|---|---|---|---|
| every gated module tap (PCC vs the fp32 reference, sample, replay outputs) | β₯ 0.999 | min 0.999729 (`point_fusion1`) | min 0.999649 (`attention4`) |
| `pred_probs` PCC / raw-point argmax agreement, sample | β₯ 0.998 / β₯ 98.5 % | 0.999942 / 99.581 % | 0.999844 / 99.295 % |
| public frames (OT128 17, QT128 5): min `pred_probs` PCC | β₯ 0.998 | 0.999688 | 0.999805 |
| public frames: min raw-point argmax agreement | β₯ 98.5 % | **98.907 %** (nuScenes 0916 kf40; margin 0.41 pt) | 99.158 % (PandaSet 029 f40) |
| replay == eager | bit-exact | 45 / 45 taps | 45 / 45 taps |
| CPU reference vs ONNX Runtime on the deployed ONNX (the reference itself is right) | β₯ 0.9999 per tap | β₯ 0.99999999999 (VERIFY_PORT, research venv) | same |
- **Every bench run also checks its served output** against the stored fp32 CPU reference with the container-smoke
gates (`server/smoke_test.py` `REFERENCE_GATES`); all passed:
- OT128: synthetic labels 99.55 % / filter 99.77 % (max |Ξscore| 0.072); PandaSet 99.59 % / 99.76 % (0.109);
- QT128: synthetic 99.62 % / 99.87 % (0.144); PandaSet 99.29 % / 99.62 % (0.150);
- every matrix run (ETH / WORKER Γ 1 / 2 CQs, both profiles) passes the same check.
- **The binding constraint of the optimization phase is nuScenes 0916 kf40 (OT128): 98.907 % against the 98.5 %
gate.** Every precision-reducing change measured in the port failed it: bf16 maps 98.28 %, an fp32 Γ fp32 point
island 97.50 % (PORT_LOG M3). See "The precision cost of the policy" below.
- Host suite on the fake ttnn with the new scripts: **112 passed, 13 skipped** (the device files and the
onnxruntime-only test). Log: `logs/frnet/m5_baseline/host_suite.log`.
## Performance (warm, batch 1, median of 100 iterations)
Each stage is synchronised on its own, so the rows do not add up to e2e exactly. Values are p50 (p99) in ms; b2b is
the median of 5 rounds of 50 back-to-back replays. `pre` includes the file decode (`pre.load`); an HTTP request
decodes its points envelope inside the call.
| stage | OT128 synthetic | OT128 PandaSet 019 f40 | QT128 synthetic | QT128 PandaSet 019 f40 |
|---|---:|---:|---:|---:|
| host preprocess as served (`model._prepare`) | 55.3 (59.1) | 107.2 (120.0) | 54.2 (69.7) | 83.5 (106.1) |
| β¦ decode (`load_points`) | 6.9 | 12.9 | 6.2 | 13.1 |
| β¦ ego crop / projection / unique cells | 1.6 / 3.4 / 1.8 | 3.2 / 8.0 / 2.4 | 1.5 / 4.7 / 0.9 | 3.3 / 5.9 / 1.0 |
| β¦ fp16 interpolation grid + interpolated points | 18.4 | 37.4 | 17.2 | 29.3 |
| β¦ device plan (`build_plan`: sort, CSR offsets, gather tables, 8-d features) | 22.5 | 41.8 | 23.3 | 31.4 |
| `FrameStaging.write` (range checks + persistent host buffers) | 3.3 | 3.2 | 2.4 | 2.3 |
| H2D, 9 inputs (8.6 MB OT128, 6.2 MB QT128) | 5.42 (5.57) | 5.41 (5.65) | 4.04 (5.59) | 4.00 (4.48) |
| β¦ of which `features` fp32 `[N_cap, 8]` | 4.13 | 4.13 | 3.14 | 3.13 |
| device trace, one blocking forward | 210.02 (210.15) | 211.72 (211.89) | 133.53 (134.01) | 134.91 (135.12) |
| **back-to-back trace replays, per forward** | **209.948** | **211.643** | **133.434** | **134.866** |
| D2H as served (fp32 `pred_probs` `[N_cap, 32]` β numpy) | 7.65 (20.2) | 7.43 (19.8) | 5.80 (8.7) | 5.55 (7.6) |
| β¦ copy / host untilize (to numpy) | 2.25 / 4.95 | 2.29 / 5.01 | 1.77 / 4.15 | 1.78 / 3.75 |
| unsort (first N rows / 27 columns, network-input order) | 7.1 (14.1) | 12.8 (23.3) | 6.1 (13.2) | 5.9 (13.7) |
| host postprocess (first-max argmax, filter, output assembly) | 5.0 (6.8) | 14.8 (19.3) | 6.8 (9.8) | 10.5 (15.9) |
| **e2e `model()`** | **292.6 (299.4)** | **355.7 (367.0)** | **221.1 (233.5)** | **244.3 (258.7)** |
- **Throughput.**
- Back-to-back replays give **4.72 frames/s** of device throughput on OT128 (PandaSet) and **7.41 frames/s** on
QT128.
- A synchronous `model()` call gives 2.81 frames/s (OT128 PandaSet), 3.42 (OT128 synthetic), 4.09 (QT128
PandaSet) and 4.52 (QT128 synthetic).
- **The API's own `timing_ms` medians** (OT128 PandaSet): preprocess 105.4 ms, device 235.7 ms, postprocess 14.6 ms,
total 355.7 ms. The API's `device` stage is staging + H2D + trace + D2H + unsort: 24 of its 236 ms are outside the replay.
- **The device is 60 % of e2e on OT128** (211.6 of 355.7 ms, PandaSet) and 55 % on QT128 (134.9 of 244.3 ms). The
host preprocess is 30 % (OT128) and 34 % (QT128).
- **The device time barely depends on the frame** (fixed `N_cap` / `M_cap`): the synthetic OT128 scan with 36 % of
PandaSet's points replays only 1.7 ms faster (209.95 vs 211.64 ms). The difference is in K1 and the gathers.
- **Load** (warm JIT cache): `from_pretrained` 6.5 s OT128 / 6.9 s QT128 in the stage bench (build 0.28 s,
warm-up + capture 2.0 s). WORKER (11Γ10) compiles its own program set: 137 s OT128, 82 s QT128. A process with the
device profiler compiles its own kernels (146 s).
## Dispatch / CQ matrix (D14)
Setup:
- synthetic scan, one process per configuration, all in one devrun job (13:34β13:53 UTC);
- 50 iterations plus 5 Γ 30 back-to-back replays each;
- host load 4.1β5.8 throughout.
| profile | configuration | grid | programs | b2b replay ms | trace p50 ms | H2D p50 / min ms | D2H p50 / min ms | e2e p50 / min ms |
|---|---|---|---:|---:|---:|---:|---:|---:|
| OT128 | **ETH-1CQ** (shipped) | 12Γ10 | 227 | **209.948** | 210.00 | 5.39 / 5.35 | 7.61 / 7.33 | 292.9 / 291.1 |
| OT128 | ETH-2CQ | 12Γ10 | 227 | 211.317 | 211.45 | 5.49 / 5.46 | 7.65 / 7.30 | 290.5 / 289.7 |
| OT128 | WORKER-2CQ | 11Γ10 | 231 | 211.567 | 211.73 | 4.57 / 4.55 | 7.56 / 7.19 | 293.4 / 291.7 |
| OT128 | WORKER-1CQ | 11Γ10 | 231 | 211.563 | 211.63 | 4.49 / 4.47 | 7.48 / 7.32 | 291.7 / 291.2 |
| OT128 | ETH-1CQ again (drift check) | 12Γ10 | 227 | 209.959 | 210.02 | 5.40 / 5.34 | 7.46 / 7.17 | 291.7 / 290.8 |
| QT128 | **ETH-1CQ** (shipped) | 12Γ10 | 174 | **133.437** | 133.51 | 4.02 / 3.98 | 5.51 / 5.33 | 208.8 / 207.5 |
| QT128 | ETH-2CQ | 12Γ10 | 174 | 134.250 | 134.38 | 4.11 / 4.08 | 5.50 / 5.28 | 210.0 / 209.1 |
| QT128 | WORKER-2CQ | 11Γ10 | 174 | 153.721 | 153.82 | 3.45 / 3.42 | 5.53 / 5.31 | 229.3 / 227.7 |
| QT128 | WORKER-1CQ | 11Γ10 | 174 | 153.709 | 153.77 | 3.39 / 3.37 | 5.43 / 5.30 | 226.0 / 225.3 |
| QT128 | ETH-1CQ again (drift check) | 12Γ10 | 174 | 133.433 | 133.50 | 4.01 / 3.98 | 5.42 / 5.31 | 206.3 / 205.5 |
- **ETH stays the default: it is better on the device in both profiles.**
- OT128: ETH replays 1.6 ms faster than WORKER (209.95 vs 211.56 ms, β0.8 %).
- QT128: ETH replays **20.3 ms faster** (133.44 vs 153.71 ms, β13.2 %). On the 128 Γ 256 grid the 11Γ10
partitioning of the point matmuls and convs is clearly worse. This is the largest ETH/WORKER gap measured so
far in this workspace.
- WORKER's H2D is 0.6β0.9 ms faster, as in the other matrices. It does not offset the replay.
- **1 CQ.** ETH with 2 CQs replays 0.8β1.4 ms slower (ETH-2CQ runs without dispatch_s, PLAN Β§0.2). A synchronous
request gains nothing from the second queue (e2e within host noise). Uploads are 5.4 ms and readbacks 7.5 ms per
frame, so pipelined 2CQ staging could hide at most about 13 ms of a 212 ms frame (β6 %).
- **Decision: ETH, 1 CQ stays pinned** (`serve.env FRNET_DISPATCH=eth`, `FRNET_NUM_CQS=1`, D14).
- Re-measure a pipelined 2CQ mode once the replay is no longer 15β30Γ the transfers.
- The drift checks repeat to 0.01 ms (OT128) and 0.004 ms (QT128). One OT128 ETH-1CQ b2b round read 211.76 ms
(the other four 209.94β209.95), and the median is unaffected.
## Host vs device, and every host<->device transfer per forward
**Host.** Only the documented fallbacks of PLAN Β§2.8 run on the host; there is no host round trip and no host
fallback op inside the device graph.
Before the device (`host/preprocess.py`, numpy), in the node's order:
- the ego crop (float32 `R p + t`);
- the float32 projection;
- the fp16 farthest-point interpolation grid and the interpolated points;
- the unique cells and the profile checks;
- the device plan: the sort by cell, CSR offsets, `point2pix_s` / `pix2vox_s` for s = 1, 2, 4, 8, and the 8-d
features with `pre_norm` folded.
After the readback:
- the un-permute (`plan.unsort`) of the first N rows / 27 columns;
- the first-max argmax with the β1e9 start (never a device last-dim argmax, P10), the score, the class filter
(`prob β₯ 0.05` on `drivable_surface`) and the output assembly.
**H2D, 9 per frame.** `copy_host_to_device_tensor` from the persistent `HostStaging` buffers into the trace inputs,
all ROW_MAJOR in DRAM (OT128 bytes; QT128 in brackets):
| input | shape, dtype | bytes | ms |
|---|---|---:|---:|
| `features` | `[1, 1, N_cap, 8]` fp32 (32 B pages) | 5,120,000 (3,840,000) | 4.13 (3.13) |
| `offsets` (K1 CSR) | `[1, 60002]` uint32 | 240,008 | 0.17 |
| `p2p_1/2/4/8` (pixel2point) | 4 Γ `[1, N_cap]` uint32 | 4 Γ 640,000 (480,000) | 0.27β0.31 each |
| `v2p_1/2/4/8` (frustum2pixel) | `[1, (H/s)(W/s)]` uint32 | 524,288 + 131,072 + 32,768 + 8,192 | 0.06β0.26 |
| **total** | | **8,616,328** (6,174,088) | **5.41** (4.00) |
`features` moves at 1.24 GB/s: 160,000 pages of 32 bytes. The other inputs reach 1.4β2.1 GB/s.
**D2H, 1 per frame.** The fp32 `pred_probs` TILE `[N_cap, 32]`:
- 20,480,000 B (OT128) / 15,360,000 B (QT128);
- of the 32 columns, 27 hold classes and 5 are padding (bias β1e30: probability 0);
- rows beyond N are padding (PandaSet OT128 uses 145,961 of 160,000, the nuScenes frames 20β28 k);
- copy 2.29 ms (8.9 GB/s), then host untilize 5.0 ms.
**The served device path** (the API's `device` stage) is 236 ms on OT128 PandaSet. 212 ms of it run on the device.
## Grid usage
- **Nothing hard-codes 11Γ10 or 12Γ10** (VERIFY_PORT Β§3(b): the grid comes from `device.compute_with_storage_grid_size()`;
K1 splits its work on the device by two binary searches, so ETH and WORKER give the same outputs). On 11Γ10, WORKER
builds 231 instead of 227 programs (OT128).
- **OT128: 456 of the 1,104 programs per frame run on all 120 cores, 648 on fewer** (26.3 ms of kernel time):
- every point-island op on `N_cap` rows, the K1 kernels and the softmax run on 120 cores. The `M_cap`-row
compression matmuls run on 118;
- the image-branch convs run on 96 (28 conv programs), 108 (22) and 40 cores (stage 4, `pixel_fusion4`,
`attention4`: the 16 Γ 128 stride-8 map). Their glue (halo, move, padded slice, slice write, resharding) runs on
40β114 cores. The height-sharded configurations use whole row groups;
- the convs reach a weighted 59.7 % of the HiFi4 FLOPs (tt-perf-report). The ten block-sharded conv programs reach
26.1 %, but take only 0.42 ms.
- **QT128: the point matmuls run on 118 cores** (3,750 tile rows of 120,000 points). The convs of the narrow
128 Γ 256 grid use 32β108 cores.
- **L1 is empty between ops.** The ttnn-visualizer report shows no L1 buffer at any op boundary.
- Every activation round-trips DRAM interleaved.
- DRAM peaks at **218.6 MB per bank (1.75 GB in all, weights included)** at the first partial-sum add of
`decoder.point_fuse1`. At that point the fp32 point features p0βp4 (`[160000, 128]`, 82 MB each) are still alive
next to the fp32 `[160000, 256]` partials (164 MB each).
- L1 for tensors is 167.5 MB (120 Γ 1,395,712 B, the visualizer's device record).
- **What would fit in L1:**
- a 128 Γ 1024 Γ 128 image map: 33.5 MB in bf16, 67 MB in fp32;
- every point-island weight: the largest, `point_fuse1` 640 Γ 256, is 655 KB as two bf16 terms.
No fp32 point tensor at `N_cap` fits (82β164 MB each), so the point island needs row-block streaming.
## What is already fused / traced
**Done:**
- **One trace per process** covers the whole device graph: encoder β K1 β compression β frustum2pixel β stem β
point stem β fusion stem β 4 stages β decoder β point fuse β head β softmax β one packed output.
- Capture takes 2.6 s (OT128) / 2.0 s (QT128).
- No eager op runs between replays; replay == eager bit for bit (a gate).
- Eagerly, under the profiler, the same 1,104 programs span 260.2 ms (op-to-op gaps 47.6 ms of host dispatch),
against 213.3 ms traced.
- **K1 `segment_reduce`** (owned `ttnn.generic_op`, bit-exact). It replaces the log-step stock-op fallback, which
would take 3R + 1 programs per site, with one program per segmented-max site (6 per frame). It reads the
matmul's TILE output directly (no untilize). Sites 1β5 write the bf16 frustum2pixel gather table, zero row
included.
- **Exact rewrites** (all from the port, each tested against ORT or an fp32 emulation):
- BN folded into the 14 point linears and the 53 convs;
- `pre_norm` folded into the host features;
- frustum2pixel / pixel2point as TILE + PADDED `ttnn.embedding` gathers with a zero sentinel row (C19);
- concat-free point layers (K-split linears) and K-split fusion convs. `fuse_layer1` is a 5 Γ 128 split-K
`KSplitConv` (P14), so the 160 MiB decoder concat never exists;
- align-corners resizes as interpolation matmuls (C18);
- the 27 logits padded to 32 with a β1e30 bias, so the softmax is exact;
- fused SIGMOID in the attention convs, ReLU in the point linears;
- a bf16 input of a two-term linear (the gathered map rows) takes two matmuls instead of three (`lo = 0`).
- **Host side:** `HostStaging` writes the plan into persistent zero-copy host tensors (no per-frame `from_torch`),
and out-of-profile frames return before touching the device.
**Not fused / not resident:**
- **the two-term point island:**
- each point linear runs one split per fp32 input: typecast to bf16, typecast back, subtract, typecast. So
4 programs per input;
- then 2β3 bf16 matmuls per part, each writing a full fp32 `[N_cap, C_out]` partial to DRAM;
- then one fp32 add per partial.
The 5-part `point_fuse1` alone is 49 programs and 39.9 ms;
- every activation in DRAM interleaved between ops. The 128 Γ 1024 convs run ttnn's automatic DRAM slicing, each
slice with its own padded slice, halo, move and slice write;
- every HardSwish is its own fp32 `ttnn.hardswish` (P5: a fused HardSwish is silently skipped), 46 per frame;
- the resizes (C18) run as transpose + matmul + reshape + tilize / untilize chains with bf16 outputs (C18 cannot
write fp32 rows at 1024 Γ 128);
- pixel2point: untilize of the map, then the gather, then a typecast to fp32;
- every point-wise op runs on all `N_cap` rows and K1 writes all `M_cap` segments, whatever the frame's N and M;
- no megakernel. Projection, interpolation, the plan, the argmax and the filter run on the host.
## Device profile (traced replay; kernel sum vs span, op-to-op gaps)
One traced replay, ETH-1CQ, PandaSet 019 f40. Reports: OT128 `generated/profiler/frnet_baseline/reports/2026_10_09_13_56_53/`,
QT128 `generated/profiler/frnet_baseline_qt128/reports/2026_10_09_13_59_21/`.
| | OT128 | QT128 |
|---|---:|---:|
| programs per frame | **1,104** | **759** |
| distinct programs (program cache) | 227 | 174 |
| ttnn calls per frame (visualizer, OT128) | 491: 123 typecast, 84 add, 72 linear, 62 conv2d, 46 hardswish, 27 to_layout, 21 reshape, 20 subtract, 12 embedding, 6 generic_op (K1), 6 transpose, 6 matmul (resize), 4 addcmul, 1 pad, 1 softmax; plus 439 deallocate | β |
| device kernel sum | **212.64 ms** | **134.81 ms** |
| span (first FW start β last FW end at 1350 MHz) | 213.27 ms (b2b 211.64) | n/a (see below; b2b 134.87) |
| op-to-op gaps | 0.628 ms in all, median 0.55 Β΅s | 0.443 ms, median 0.57 Β΅s |
| conv + matmul kernels | 76.70 ms (36 %) | 51.62 ms (38 %) |
| math fidelity | HiFi4 on all 677 compute ops | HiFi4 on all 539 |
- **The trace is kernel-time bound: dispatch is not a sink** (0.3 % of the span). Cutting programs pays only through
the kernel time of the programs it removes. A megakernel's dispatch saving is at most 0.6 ms.
- QT128's span is not usable: one replayed conv (row 3631 of the CSV, `backbone.layer4`) reports a FW duration of
3.18 s against a 41 Β΅s kernel, a profiler artefact. The b2b replay (134.87 ms, five rounds within 0.01 ms) shows no
such stall. The kernel sum plus gaps (135.25 ms) is the QT128 reference.
**By part of the graph** (`logs/frnet/m5_baseline/scripts/group_profile.py`; ops attributed through the eager
module / layer signposts):
| part | OT128 ops | OT128 kernel ms | share | QT128 ms | share | of which (OT128) |
|---|---:|---:|---:|---:|---:|---|
| **point island** (encoder ffe0β3, compression, point stem, point fusion 1β4, point fuse, head + softmax) | 215 | **133.66** | **62.9 %** | **101.12** | **75.0 %** | matmul 61.2, fp32 adds 52.6, typecasts (two-term splits) 19.5 |
| β¦ encoder ffe0β3 (8 β 64 β 128 β 256 β 256) | 36 | 22.14 | 10.4 % | 16.71 | 12.4 % | `ffe3` 9.9 (three 1.6 ms matmuls) |
| β¦ point stem + point fusion 1β4 (K-split 384 / 256 β 128) | 65 | 37.88 | 17.8 % | 28.36 | 21.0 % | 7.0 per fusion, 10.0 the stem |
| β¦ point fuse (640 β 256 β 128) | 58 | 47.40 | **22.3 %** | 35.77 | 26.5 % | `point_fuse1` 39.9: 15 matmuls 17.4, 15 adds 16.4, 20 split ops 6.1 |
| β¦ head (4 Γ linear + skips, logits, softmax) | 44 | 23.89 | 11.2 % | 17.95 | 13.3 % | |
| β¦ compression (256 β 16, on `M_cap` rows) | 12 | 2.35 | 1.1 % | 2.33 | 1.7 % | |
| **K1 segment max** (6 sites) + tilize | 7 | **17.22** | 8.1 % | **15.18** | 11.3 % | encoder site 4.93 (fp32 Γ 256), 5 Γ 2.39 (Γ 128) |
| pixel2point gathers (6 Γ untilize + embedding + typecast) | 18 | 6.15 | 2.9 % | 3.50 | 2.6 % | embedding 3.47 |
| frustum2pixel gathers (6 embeddings) | 6 | 0.65 | 0.3 % | 0.15 | 0.1 % | |
| decoder resizes (C18, 3 maps) | 39 | 9.03 | 4.2 % | 2.24 | 1.7 % | matmul 2.89, reshape 2.62, (un)tilize 2.30 |
| image branch: stem, fusion stem, stages 1β4 (convs + glue) | 605 | 30.66 | 14.4 % | 8.63 | 6.4 % | conv 7.62, fp32 HardSwish 5.25, typecast 4.24, DRAM slicing 5.19, adds 3.21, halo 1.96 |
| image branch: decoder `fuse1` (K-split 5 Γ 128 β 256) + `fuse2` | 214 | 15.26 | 7.2 % | 3.98 | 3.0 % | conv 4.96, K-split adds 3.62, slicing 3.89 |
| **total** | **1,104** | **212.64** | | **134.81** | | |
| sink (op type, OT128) | Β΅s | % | where |
|---|---:|---:|---|
| Matmul (78) | 64,118 | 30.2 | 72 point-island matmuls at 1.4β7.9 % of the HiFi4 FLOPs and 130β200 GB/s (26β39 % of the DRAM peak): each writes a full fp32 partial `[160000, C_out]` (82β164 MB). The `ffe3` parts take 1.6 ms each, a `point_fuse1` part 1.16 ms, a point-fusion part 0.67 ms. Plus 6 resize matmuls (2.9 ms) |
| BinaryNg (104) | 59,441 | 28.0 | point island 52.6 ms: 59 fp32 adds (the two-term / K-split partial sums and the head's skips; 1.09 ms each at `[160000, 256]`, about 450 GB/s: DRAM-bound) and the 20 subtracts of the two-term splits. Image branch 6.8 ms: fp32 residual adds and the decoder's K-split sums (3.6 ms) |
| Typecast (123) | 26,795 | 12.6 | 61 fp32 β bf16 casts of the two-term splits (19.5 ms); 53 casts of the fp32 maps to bf16 before the convs (5.2 ms); 6 pixel2point casts to fp32 (1.5 ms); 3 in the resizes (0.7 ms) |
| GenericOp: K1 (6) | 16,903 | 7.9 | scalar RISC-V segment max. The encoder site reads 164 MB in 4.93 ms (33 GB/s); each 128-channel site takes 2.39 ms. It scales with `M_cap` (60,000 output rows each) more than with the rows: QT128 runs the same sites in 2.0β4.4 ms on 75 % of the rows |
| Conv2d (138) | 12,582 | 5.9 | 53 convs in 2β4 DRAM slices each, weighted 59.7 % of the HiFi4 FLOPs |
| SliceWrite + PaddedSlice (194) | 9,073 | 4.3 | ttnn's DRAM slicing of the 128 Γ 1024 convs |
| Unary (46) | 6,269 | 2.9 | the 46 fp32 HardSwish |
| Embedding (12) | 4,118 | 1.9 | pixel2point (3.47 ms) and frustum2pixel (0.65 ms) gathers |
| Halo + Move + resharding (358) | 4,816 | 2.3 | conv glue |
| Reshape + (Un)Tilize + Transpose (39) | 7,340 | 3.5 | resize chains, pixel2point untilize, the input tilize |
| Ternary `addcmul` (4) | 852 | 0.4 | attention gating |
Top 10 device ops of the OT128 replay:
| # | op | where | Β΅s | cores |
|---:|---|---|---:|---:|
| 1 | K1 `segment_reduce` | `segmax.encoder`: `[160000, 256]` fp32 β `[60000, 256]` fp32 (voxel max) | 4,928 | 120 |
| 2β6 | K1 `segment_reduce` | `segmax.points` sites 1β5: `[160000, 128]` β the bf16 gather table `[60001, 128]` | 2,394β2,396 each | 120 |
| 7β9 | Matmul | `encoder.ffe3` (256 β 256), the three two-term products `[160000, 256] Γ [256, 256]` β fp32 | 1,641 / 1,599 / 1,598 | 120 |
| 10 | Matmul | `head.mlp2` part `[160000, 256] Γ [256, 128]` β fp32 | 1,221 | 120 |
Next come the 15 `point_fuse1` matmuls (1.15β1.20 ms each), the 25 fp32 adds at `[160000, 256]` (1.08β1.11
ms each) and the `point_stem` part matmuls (1.19 ms).
**Most expensive layers:**
- `decoder.point_fuse1`: 39.85 ms (18.7 % of the replay), of which 17.41 ms matmul;
- `decoder.fuse1`: 12.55 ms in 157 programs, of which 3.92 ms conv;
- K1 `segmax.points` (5 sites): 11.98 ms;
- `backbone.point_stem`: 9.96 ms;
- `encoder.ffe3`: 9.88 ms;
- `head.mlp1` / `head.mlp2`: 8.23 / 8.09 ms;
- each point fusion: 6.97β6.99 ms;
- stage-1 convs: 1.43β1.90 ms each, of which 0.37 ms compute (the rest is the DRAM slicing, halo and the fp32
output).
`logs/frnet/m5_baseline/profile_summary_{ot128,qt128}.{json,md}` have the per-module, per-layer and per-op tables,
`tt_perf_report_{ot128,qt128}.txt` the tt-perf-report output (with its per-op advice), and
`grp_{ot128,qt128}.txt` the part breakdown above.
**The precision cost of the policy.** These are the port's same-session device A/Bs on the OT128 frames (PORT_LOG
M1βM3, jobs j2βj5, gates read-only). They were not re-run for this baseline.
| policy | replay ms | Ξ vs shipped | OT128 frames min argmax (nuScenes 0916 kf40) | gate |
|---|---:|---:|---|---|
| fp32 Γ fp32 point island (`POINT_TERMS=1`), bf16 maps (j2) | 107 | β105 ms (β50 %) | **97.50 %** | fails |
| two-term point island, bf16 maps (j3) | 201 | β11 ms (β5 %) | **98.28 %** | fails |
| two-term point island, bf16 maps, `CONV_TERMS=2` (j4) | 244 | +32 ms | **98.35 %** | fails |
| **two-term point island, fp32 maps (shipped; j5 / this baseline)** | **212** | β | **98.91 %** | passes, 0.41 pt margin |
| two-term point island, fp32 maps fed to the convs directly (j5) | 217 | +5 ms | **98.13 %** | fails |
| two-term point island, fp32 maps, `CONV_TERMS=2` (j5) | 249 | +37 ms | 99.00 % | passes, 0.50 pt margin |
- **The two-term point island costs about 94 ms per OT128 frame** (j2 β j3: 107 β 201 ms, +88 %). That is 44 % of
today's replay, and it is how the port reaches the gate: the device's fp32 matmul is TF32-like (P12). The
profile shows that the cost is not the extra products (bf16 HiFi4 is exact) but the fp32 partial tensors, their
DRAM adds and the split typecasts. Item 1 below targets exactly that, with the same products.
- **The fp32 maps cost about 11 ms** (201 β 212 ms): fp32 HardSwish, residual adds and `addcmul`, and a cast to bf16
before every conv. They buy 0.63 pt on the binding frame.
- **Cheaper precision fails.** Every cheaper policy measured fails the binding nuScenes frame. HiFi2 / LoFi or
bfp8 were not measured: with a 0.41 pt margin and 98.28 % for bf16 maps, they need fresh device evidence, and the
FPU is not the bottleneck (the matmuls run at 1.4β7.9 % of the FLOPs).
- **More margin costs time.** `CONV_TERMS=2` raises the margin to 0.50 pt for +37 ms. fp32 resize outputs (blocked
by C18 at 1024 Γ 128 rows) and fp32 gathers for the 128-channel maps are unmeasured options.
## Ranked optimization opportunities
How to read the estimates:
- Gains are on the OT128 trace (211.6 ms, PandaSet) unless marked e2e (OT128 PandaSet e2e 355.7 ms p50). QT128
follows the same ranking (the point island is 75 % of its 134.9 ms).
- Each estimate is the measured kernel or host time of the work the item removes, with a range for what replaces
it. Items overlap, so the gains do not add up.
- Every item keeps the frozen gates and the precision policy (PLAN Β§9.3), and lands as one commit with one
`FRNET_*` A/B knob (PLAN Β§5.1).
- **Judge numerics changes on the binding frame first: nuScenes 0916 kf40, OT128, 98.907 % against the 98.5 % gate.**
A change in the order of fp32 accumulation is not bit-identical to today, so items 1β2 must re-run the full
device suite.
1. **Two-term point linears as one K-concatenated matmul per layer (trace β60..β80 ms, β28..β38 %; the largest
item).**
- Today each point linear writes one fp32 `[N_cap, C_out]` partial per product: 2β3 per input part, up to 15 for
`point_fuse1`. One fp32 add per partial then sums them. Matmuls take 61.2 ms and the adds 52.6 ms, at
130β450 GB/s: DRAM traffic, not FLOPs (1.4β7.9 % of HiFi4).
- The fix: one matmul per layer over the K-concatenated bf16 operands `[x_hi | x_hi | x_lo]` (all parts) Γ
`[W_hi; W_lo; W_hi]`, with fp32 dest accumulation and packer L1 accumulation.
- The products are the same exact bf16 products. Only the fp32 summation order changes, so the gates must be
re-run.
- The fp32 output is written once. The bf16 inputs are a quarter of an fp32 partial's bytes.
- The concat of the bf16 operands costs about 0.3β1 ms per layer, unless the split kernel (item 3) writes them
straight into the K-slices of one tensor.
- Expected result: matmul about 61 β 25β35 ms, adds 52.6 β about 5 ms (the residual / skip adds stay).
2. **MK-PointMLP: a row-block-streamed point MLP megakernel (trace β100..β115 ms beyond today, overlapping 1;
D19).**
- The point island is 133.7 ms (OT128) and 101.1 ms (QT128): 63β75 % of the replay. Its work is row-local
except the gathers and K1.
- A kernel per row block (for example 32β64 points per core) chains the linears of one segment in L1:
- the encoder `ffe0..ffe3`;
- point fuse `640 β 256 β 128`;
- the head's 4 layers + skips + logits + softmax.
It does the two-term splits on the fly, holds the bf16 weight terms in L1 (at most 655 KB for `point_fuse1`)
and writes only the segment's outputs. The head segment writes `pred_probs` directly.
- Its DRAM floor is a few tens of MB per frame, about 0.1β0.3 ms. Compute at the measured bf16 rates fits in
roughly 15β30 ms for the whole island.
- Stepping stones:
- item 1;
- then fuse each linear's split into the producer's epilogue (item 3);
- then one segment at a time, the head first (23.9 ms, no gather inside).
3. **A fused two-term split (trace β10..β14 ms; on the way to 1β2).**
- Each fp32 input today runs 4 programs: typecast, typecast back, subtract, typecast. That is 19.5 ms for 61
casts, plus 20 subtracts (in the BinaryNg row), at `N_cap`.
- One `generic_op` (or SFPU eltwise) pass can read fp32 and write hi and lo bf16, about 0.3β0.5 ms per
`[160000, 256]` input. It can write into the K-slices of item 1's operand. Exact: the same RNE casts.
4. **N_cap / M_cap buckets (trace β100..β160 ms on frames with N β€ 32β64 k; e.g. every nuScenes frame).**
- Every point-wise op runs on all 160,000 rows (OT128) or 120,000 (QT128), and K1 writes all 60,000
segments. That covers the point island, K1, the gathers and the readback: about 157 ms of the OT128 replay.
- The catalogued nuScenes frames have N = 20β28 k (13β17 % of `N_cap`) and M = 17β25 k. The QT128 PandaSet
frame has M = 6,255 against `M_cap` = 60,000.
- The fix: two or three trace variants per profile (for example `N_cap` 32 k / 64 k / 160 k, `M_cap` 16 k /
60 k), sharing weights. The host picks the smallest bucket that covers the frame. Each variant costs about
6β9 MiB of trace region (256 MiB available) plus its activations.
- PandaSet-like frames (N = 146 k) gain nothing. The readback shrinks with N too (item 9).
5. **K1 throughput (trace β10..β14 ms; owned shared kernel).**
- K1 takes 16.9 ms: 33 GB/s on the 164 MB encoder site, and 2.0β2.4 ms per 128-channel site almost
independent of the row count. So per-segment output overhead dominates: 60,000 segments of one row each
through an 8-row staging ring.
- Fixes, in order:
- batch output rows per worker (contiguous segments β one NOC write per block);
- move the accumulator from L1 into the RISC-V's local memory (PORT_LOG known issue);
- with item 4, write only M_cap-bucket segments;
- a compute-engine reduction is the long form.
- Bit-exactness must stay (max of integer keys). The shared-kernel rule (PLAN Β§5.1.5) applies: re-vendor and
re-run K1's host / device tests.
6. **Image branch DRAM slicing and glue (trace β8..β14 ms; MK-ImageStage).**
- The 128 Γ 1024 convs spend 12.6 ms in conv kernels but 9.1 ms in DRAM slicing (padded slice + slice write) and
4.8 ms in halo / move / resharding. The fp32-map glue adds the following:
- 46 fp32 HardSwish: 6.3 ms;
- the casts to bf16 before each conv: 5.2 ms;
- fp32 residual adds and K-split sums: 6.8 ms.
- A 128 Γ 1024 Γ 128 map is 33.5 MB in bf16 / 67 MB in fp32 against 167.5 MB of L1. So the stride-1 stage chain
can stay height-sharded in L1 (C17 `slicing="l1"`), which drops the slicing.
- Fold the bf16 cast of the conv input and the HardSwish into one eltwise pass (exact: the same ops).
- The longer form is a BasicBlock megakernel with halo exchange (MK-ImageStage).
7. **Decoder resizes and `fuse1` (trace β5..β8 ms).**
- Resizes take 9.0 ms: reshape 2.6, tilize / untilize 2.3, transpose 0.4 and matmul 2.9.
- `decoder.fuse1` takes 12.5 ms in 157 programs: a 5-part K-split over five 128 Γ 1024 maps, with 3.6 ms of
K-split adds and 3.9 ms of slicing.
- Fix: run the resize matmuls in TILE layout end to end, so no reshape / (un)tilize is needed. That also lifts
the C18 fp32 limitation, a precision-margin option. Accumulate the K-split parts in the conv (dest / L1
accumulation) instead of fp32 adds.
8. **pixel2point gathers (trace β2..β4 ms).**
- Today: untilize of each map (1.2 ms), the bf16 embedding (3.5 ms), a typecast to fp32 (1.5 ms).
- The two-term linear already treats a bf16 input as exact (`lo = 0`). So feed the gather output as bf16 straight
into item 1's operand (no cast). Exact.
- Later: gather from the TILE map in the MK-PointMLP reader.
9. **Host preprocess (e2e β60..β85 ms OT128).**
- Today 107 ms on PandaSet:
- plan 41.8 ms: sort, CSR, gather tables, features;
- fp16 interpolation 37.4 ms;
- decode 12.9 ms;
- projection 8.0 ms;
- crop and unique cells 5.6 ms.
- The fixes:
- a C++ / numba implementation of the node's CUDA kernels (projection + interpolation grid in one pass,
counting-sort plan): about 10β20 ms in all;
- keep the decode outside the timed request path.
- The numerics must stay bit-identical to today's host code (float32 / fp16 policies, the documented race
policies). The host tests and the research goldens check that bit for bit.
10. **Host after the device (e2e β15..β25 ms OT128).**
- Today: unsort 12.8 ms + post 14.8 ms + host untilize 5.0 ms.
- Fixes: read back row-major (device untilize, or a ROW_MAJOR output), with only N rows (item 4). Fuse the
un-permute into the argmax pass: take the argmax over the 27 columns on the permuted rows, then scatter
int8 labels and float scores instead of the 27-column float rows.
- Exact: the same first-max rule on the same fp32 values.
11. **H2D `features` layout (e2e β3.5 ms).** `features` moves 5.1 MB at 1.24 GB/s in 160,000 pages of 32 bytes. Upload
it as `[N_cap / 4, 32]` rows (128 B pages) and view or reshape on the device. A TILE upload would also drop the
input tilize (0.36 ms).
12. **Throughput by pipelining (frames/s; latency unchanged).**
- Device b2b allows 4.72 frames/s (OT128) and 7.41 (QT128). A synchronous request gives 2.81 / 4.09 frames/s on
PandaSet.
- Overlap the host pre / post of frames k Β± 1 with the device work of frame k in the server.
- 2CQ staging can hide the 5.4 ms upload (and, with double-buffered outputs, the 7.4 ms readback). It is worth
measuring only after items 1β2 shrink the replay.
13. **Megakernel candidates (D19, PLAN Β§5.2 / Β§5.3).**
- The Β§5.2 gate is met through DRAM round trips and layout glue, not dispatch. 64 % of the replay is not conv /
matmul compute, while op-to-op gaps are 0.3 %.
- Candidates, by measured block time:
- **MK-PointMLP** (point island, 133.7 ms OT128 / 101.1 ms QT128; items 1β3, 8): first, by far. It is
row-local, and its weights fit L1;
- **MK-SegGather** (K1 + the frustum2pixel / pixel2point gathers, 24 ms; items 5, 8): fold the gather tables
into K1's writer and the pixel2point gather into the point reader;
- **MK-ImageStage** (stem + stages 1β4 + decoder convs, 46 ms, of which 12.6 ms conv compute; items 6β7):
band-fused BasicBlocks with halo exchange. The 128 Γ 1024 maps fit L1 in bf16.
- A single whole-model kernel is not practical: the fp32 point tensors at `N_cap` (82β164 MB each) do not fit L1.
MK-PointMLP's row-block streaming is the practical "single" form for 63β75 % of the work.
14. **Precision margin (accuracy, not speed; optimization-phase precision item).**
- The margin on nuScenes 0916 kf40 is 0.41 pt. Items 1β2 change the fp32 accumulation order, so re-check the
margin after each step.
- Options to buy margin, measured: `CONV_TERMS=2` +0.09 pt for +37 ms. Unmeasured: fp32 resize outputs (after
item 7) and fp32 gathers of the 128-channel maps (`ttnn.embedding` is bf16-only; a K1-style generic_op gather).
- No precision relaxation (bf16 maps, a TF32 point island, HiFi2) is a candidate without new device evidence on
that frame: each one measured so far fails it.
## Raw logs and files
All under `/home/ubuntu/experiments/tt-models/`:
- `logs/frnet/m5_baseline/job1.log` (+ `job1.rcs`): the device job (steps 2β5). `try0/`: the first attempt (suite log
`suite_r1.log`; the other steps failed on the weights path). `job1_prereboot.log`: the job queued before the server
reboot, which never started;
- `logs/frnet/m5_baseline/bench_eth1cq_{ot128,qt128}.{json,log}`: the stage benches;
- `logs/frnet/m5_baseline/matrix_{ot128,qt128}_{eth-1cq,eth-2cq,worker-2cq,worker-1cq,eth-1cq-repeat}.{json,log}`;
- `logs/frnet/m5_baseline/profile_{ot128,qt128}.log`, `profile_summary_{ot128,qt128}.{json,md}`,
`tt_perf_report_{ot128,qt128}.txt`, `grp_{ot128,qt128}.txt`;
- `logs/frnet/m5_baseline/visualizer.log`, `host_suite.log`; `logs/frnet/{pcc,pcc_small,e2e,latency}_{ot128,qt128}_m5.json`;
- `logs/frnet/m5_baseline/inputs/`: the synthetic scans, their calibrations and CPU references;
- device profiles: `generated/profiler/frnet_baseline/reports/2026_10_09_13_56_53/ops_perf_results_2026_10_09_13_56_53.csv`
(OT128) and `generated/profiler/frnet_baseline_qt128/reports/2026_10_09_13_59_21/ops_perf_results_2026_10_09_13_59_21.csv`
(QT128);
- ttnn-visualizer report: `generated/ttnn_visualizer/frnet_baseline/` (`db.sqlite` 26 MB, `config.json`; OT128 PandaSet,
one eager forward. The 7.5 GB graph JSON went to `/dev/shm` and was deleted after the import).
|