File size: 12,917 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 | # frnet-p150 optimization report (p150, ETH dispatch, 12Γ10 grid)
**Status: baseline port; optimization pending.** This is the first public release: the functional port, measured
once (`OPT_BASELINE.md`, baseline commit `0dd52b2`, 2026-10-09) and not optimized yet. No optimization round has run,
so there is no step, no rejected attempt and no hang to report, and every number below is the baseline. The device
graph runs as one metal trace per serve profile: 1,104 programs per frame for `ot128` (Autoware's default sensor
model) and 759 for `qt128`, 211.64 / 134.87 ms per back-to-back replay on a PandaSet frame (4.72 / 7.41 frames/s of
device throughput). A synchronous `model()` call takes 292.6 ms (ot128) / 221.1 ms (qt128) on the shipped synthetic
sample and 355.7 / 244.3 ms on the PandaSet frame; the device is 60 % (ot128) / 55 % (qt128) of it. The ranked
backlog at the end says where the time goes. The largest item is the precision island of the point-wise layers
(63 % of the ot128 replay, 75 % of qt128), whose cost is DRAM traffic of fp32 partial sums, not compute; then the
image-branch convs and glue (46 ms) and the custom K1 segment-max kernel (16.9 ms). The binding accuracy constraint
of every step is the nuScenes 0916 key-frame 40 frame on ot128: 98.907 % argmax agreement against the 98.5 % gate,
a 0.41-point margin.
All numbers: `code/scripts/bench.py` (median of 100 warm iterations, batch 1, the shipped sample
`code/tt_frnet/samples/synthetic_<variant>.npz` and the staged PandaSet 019 frame 40), ETH dispatch, 1 CQ, 12Γ10 grid,
the serve.env precision knobs (`FRNET_POINT_TERMS=2`: two-term bf16 operands with fp32 accumulation in the point-wise
layers; `FRNET_MAP_DTYPE=float32`: fp32 image-branch maps with bf16 conv operands; `FRNET_CONV_TERMS=1`), HiFi4 with
fp32 accumulation everywhere, AICLK 1350 MHz, unless marked otherwise. Device profile: `code/scripts/profile_ops.py`
under the device profiler (one traced replay between signposts). Accuracy gates: `OPT_BASELINE.md` "How to run".
## Summary
| | baseline `0dd52b2` (2026-10-09) | **final (= baseline: no round yet)** |
|---|---|---|
| device trace, one blocking forward (ot128 / qt128; shipped sample) | 210.02 / 133.53 ms | same |
| back-to-back traces (ot128 / qt128; PandaSet 019 f40) | 211.64 / 134.87 ms (4.72 / 7.41 frames/s) | same |
| e2e `model()` p50 (p99), shipped sample: ot128 / qt128 | 292.6 (299.4) / 221.1 (233.5) ms | same |
| e2e `model()` p50 (p99), PandaSet 019 f40 (107,146 points): ot128 / qt128 | 355.7 (367.0) / 244.3 (258.7) ms | same |
| host preprocess / H2D / D2H / un-permute / postprocess, ot128, PandaSet | 107.2 / 5.41 / 7.43 / 12.8 / 14.8 ms | same |
| device programs per frame (distinct programs in the trace): ot128 / qt128 | 1,104 (227) / 759 (174) | same |
| kernel sum / span / op-to-op gaps, ot128 | 212.64 / 213.27 / 0.63 ms | same |
| kernel sum / gaps, qt128 (span unusable: a profiler artefact, see `OPT_BASELINE.md`) | 134.81 / 0.44 ms | same |
| accuracy gates (vs the fp32 CPU reference / ONNX Runtime) | every frozen gate passes: module PCC min 0.999729 / 0.999649; public frames min `pred_probs` PCC 0.99969 / 0.99980, min argmax agreement **98.907 %** (nuScenes 0916 kf40, gate 98.5 %) / 99.158 % | same |
| `from_pretrained` load, warm JIT cache | 6.5 / 6.9 s (capture 2.6 / 2.0 s) | same |
Host-side rows move with the load of the shared host (load average 4.1β6.6 during the baseline job); the device rows
repeat to 0.01 ms. The release re-check (`VERIFICATION_2026-10-09.md`) reproduced the device rows on the release
commit.
## Steps (chronological; each row is one commit)
| step | commit | what | trace / b2b ms (ot128; qt128) | e2e ms (shipped sample: ot128; qt128) | accuracy | revert switch |
|---|---|---|---|---|---|---|
| baseline | `0dd52b2` | first correct port: one trace per profile, ETH 1CQ 12Γ10; two-term bf16 point layers, fp32 image-branch maps, K1 `segment_reduce` (bit-exact); host pre-processing, argmax and filter | 210.02 / 209.95; 133.53 / 133.43 | 292.6; 221.1 | all frozen gates (above) | β |
The release commit after the baseline changed no model numerics and no device code: the shipped sample became a
synthetic scan per serve profile (generated by `code/scripts/make_sample.py`, byte-identical to the baseline's
synthetic bench inputs), the PandaSet sample moved out of the shipped `code/` (staged until PandaSet samples are
approved), test hardening (`test_public_frames` fails on a missing golden and pins the frame counts; a new
`test_shipped_sample` gate), docs and demo media. The device suite was re-run on it with identical values for every
existing gate (`VERIFICATION_2026-10-09.md`).
## Round 1
Not started. The optimization phase follows the publication of every model of the collection (`research/PLAN.md`
Β§4.1, Β§5): each step is one commit with one `FRNET_*` A/B knob, keeps the frozen gates, and the precision policy
(PLAN.md Β§9.3) may be relaxed per module only with gate evidence. **Every numerics change is judged on the binding
frame first: nuScenes 0916 kf40, ot128, 98.907 % against the 98.5 % gate.** A change of the fp32 accumulation order
is not bit-identical to today, so it re-runs the full device suite.
### Findings (measured)
The baseline profile (`OPT_BASELINE.md` "Device profile") is the starting point:
- The trace is **kernel-bound, not dispatch-bound**: op-to-op gaps total 0.63 ms of a 213.27 ms span (ot128, 0.3 %).
Cutting programs pays only through the kernel time of the programs removed; a megakernel's dispatch saving is at
most 0.6 ms.
- **The point-wise layers kept at higher precision are 63 % of ot128 (133.7 ms) and 75 % of qt128 (101.1 ms).** Each
linear splits its fp32 input into two bf16 terms and runs 2β3 bf16 matmuls whose fp32 partials are summed in fp32.
The cost is DRAM traffic: 61.2 ms of matmuls at 1.4β7.9 % of the HiFi4 FLOPs, 52.6 ms of fp32 adds of the
partials, 19.5 ms of typecasts for the splits. `decoder.point_fuse1` alone is 39.9 ms.
- **K1 `segment_reduce`: 16.9 ms** (8 % of ot128, 11 % of qt128): 2.39 ms per 128-channel site almost independent of
the row count, so per-segment output overhead dominates.
- **Image-branch convs and glue: 46 ms on ot128**, of which 12.6 ms conv kernels; the rest is ttnn's DRAM slicing of
the 128 Γ 1024 convs (9.1 ms), halo / move / resharding (4.8 ms), 46 fp32 HardSwish (6.3 ms), bf16 casts before
every conv (5.2 ms), fp32 residual and K-split adds (6.8 ms), resizes (9.0 ms).
- Every point-wise op runs on the profile's `N_cap` rows (160,000 / 120,000) and K1 writes all 60,000 segments,
whatever the frame: the nuScenes frames (N = 20β28 k) cost the same as PandaSet (N = 146 k).
- L1 holds nothing between ops; DRAM use peaks at 218.6 MB per bank at the first partial-sum add of
`decoder.point_fuse1`.
- Dispatch / CQ matrix (back-to-back): ot128 ETH-1CQ 209.95, ETH-2CQ 211.32, WORKER 211.56 ms; qt128 133.44, 134.25,
153.71 ms. ETH and 1 CQ stay pinned (D14): on qt128's narrow grid WORKER's 11Γ10 partitioning costs 20.3 ms.
- The precision policy is how the port reaches the gate. Measured in the port (PORT_LOG M1βM3): an fp32 Γ fp32 point
island (the device's TF32-like matmul) replays in 107 ms but gives 97.50 % on the binding frame; bf16 image maps
98.28 %; `CONV_TERMS=2` buys +0.09 pt for +37 ms.
### Megakernel / fusion work
None yet. The candidates by measured block time are listed in the backlog (item 13).
## Known hangs (all rounds)
| when (UTC) | command | cause | status |
|---|---|---|---|
| β | β | none: no hang, device timeout, reset or FAULT marker in any device job of the port, its verification round, the baseline or the release runs (one build-time `TT_THROW` of the shared C18 resize at fp32 1024 Γ 128 rows, an L1 overflow reported at program build, is a known issue in `PORT_LOG.md`, not a hang) | β |
## Rejected / not kept
None yet in the optimization phase. Configurations measured during the port that are **not** the default, for
accuracy (`PORT_LOG.md` M1βM3; `OPT_BASELINE.md` "The precision cost of the policy"; ot128, binding frame nuScenes
0916 kf40):
| configuration | replay | binding frame | why not |
|---|---:|---:|---|
| fp32 Γ fp32 point island (`POINT_TERMS=1`), bf16 maps | 107 ms | 97.50 % | fails the 98.5 % gate |
| two-term point island, bf16 maps (`MAP_DTYPE=bfloat16`) | 201 ms | 98.28 % | fails |
| two-term point island, bf16 maps, `CONV_TERMS=2` | 244 ms | 98.35 % | fails, slower |
| two-term point island, fp32 maps fed to the convs directly (`MAP_DTYPE=float32_direct`) | 217 ms | 98.13 % | fails |
| two-term point island, fp32 maps, `CONV_TERMS=2` | 249 ms | 99.00 % | passes, +37 ms for +0.09 pt of margin |
## Profile at the end (trace replay)
The baseline profile (`OPT_BASELINE.md`): ot128 1,104 programs, kernel sum 212.64 ms, span 213.27 ms; qt128 759
programs, 134.81 ms. Top sinks by op type (ot128): Matmul 64.1 ms (30 %), BinaryNg 59.4 ms (28 %), Typecast 26.8 ms
(13 %), K1 16.9 ms (8 %), Conv2d 12.6 ms (6 %), DRAM slicing 9.1 ms, HardSwish 6.3 ms, resize chains 7.3 ms.
## Remaining backlog (gains on the ot128 trace of 211.6 ms, PandaSet, unless marked e2e; estimates, not measurements)
Each gain is the measured kernel or host time of the work an item removes, with a range for what replaces it. Items
overlap, so the gains do not add up. qt128 follows the same ranking. From `OPT_BASELINE.md` "Ranked optimization
opportunities":
1. **Two-term point linears as one K-concatenated matmul per layer (trace β60..β80 ms).** One matmul over
`[x_hi | x_hi | x_lo] Γ [W_hi; W_lo; W_hi]` with fp32 dest and packer L1 accumulation replaces 2β3 matmuls per part
and their fp32 partial adds. Same exact bf16 products; only the fp32 summation order changes (re-run the gates).
2. **MK-PointMLP, a row-block-streamed point MLP megakernel (trace β100..β115 ms beyond today, overlapping 1; D19).**
The point layers are row-local except the gathers and K1; their bf16 weight terms fit L1 (at most 655 KB). Chain
the encoder, the point fuse and the head per row block in L1, with the two-term split on the fly.
3. **A fused two-term split (trace β10..β14 ms; on the way to 1β2).** One pass reads fp32 and writes the hi / lo bf16
terms (exact: the same RNE casts) instead of 4 programs per input.
4. **`N_cap` / `M_cap` buckets (trace β100..β160 ms on frames with N β€ 32β64 k, e.g. every nuScenes frame).** Two or
three trace variants per profile sharing weights; the host picks the smallest that covers the frame. PandaSet-like
frames (N = 146 k) gain nothing.
5. **K1 throughput (trace β10..β14 ms; owned shared kernel).** Batch output rows per worker, accumulator in the
RISC-V local memory, then a compute-engine reduction; bit-exactness stays.
6. **Image-branch DRAM slicing and glue (trace β8..β14 ms; MK-ImageStage).** Keep the stride-1 stage chain
height-sharded in L1 (a 128 Γ 1024 Γ 128 map is 33.5 MB in bf16), fold the bf16 cast and the HardSwish into one pass.
7. **Decoder resizes and `fuse1` (trace β5..β8 ms).** Resize matmuls in TILE layout end to end (also lifts the C18 fp32
limitation), K-split accumulation inside the conv.
8. **pixel2point gathers (trace β2..β4 ms).** Feed the bf16 gather output straight into item 1's operand (exact).
9. **Host pre-processing (e2e β60..β85 ms).** 107 ms on PandaSet: a C++ / numba implementation of the node's CUDA
kernels (projection + interpolation grid in one pass, counting-sort plan), bit-identical to today's host code.
10. **Host after the device (e2e β15..β25 ms).** Row-major readback of only N rows; argmax over the permuted rows, then
scatter labels and scores instead of 27-column float rows (exact: the same first-max rule).
11. **H2D `features` layout (e2e β3.5 ms).** 160,000 pages of 32 bytes at 1.24 GB/s: upload as 128-byte rows.
12. **Throughput by pipelining (frames/s; latency unchanged).** Overlap the host stages of neighbouring frames with the
device work; 2 CQ staging once items 1β2 shrink the replay.
13. **Megakernel candidates (D19, PLAN Β§5.2 / Β§5.3)** by measured block time: MK-PointMLP (133.7 / 101.1 ms; first),
MK-SegGather (K1 + gathers, 24 ms), MK-ImageStage (46 ms). A whole-model kernel is not practical: the fp32 point
tensors at `N_cap` (82β164 MB each) do not fit L1.
14. **Precision margin (accuracy, an optimization-phase precision item).** The 0.41-point margin on nuScenes 0916 kf40
(ot128) is the narrowest of the release. Items 1β2 change the accumulation order, so re-check it after each step.
Measured option: `CONV_TERMS=2` (+0.09 pt, +37 ms). Unmeasured: fp32 resize outputs (after item 7), fp32 gathers of
the 128-channel maps. No relaxation (bf16 maps, a TF32 point island, HiFi2, bfp8) is a candidate without new device
evidence on that frame.
|