File size: 6,807 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
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""First-order CPU emulation of the TT numerics of FRNet, to size the fp32 islands of the port before the device runs
(research venv: torch + onnx; CPU only, no device).

    cd bundles/frnet-p150 && OMP_NUM_THREADS=4 PYTHONPATH=code \\
        /home/ubuntu/experiments/tt-models/tools/research-venv/bin/python code/scripts/precision_study.py \\
        --out /home/ubuntu/experiments/tt-models/logs/frnet/m0/precision_study.json

The TT dataflow (``reference/rewrites.py`` ``FRNetPlanTorch``) runs with operand rounding per site
(``Emulation``): ``bf16`` (RNE), ``tf32`` (truncation to 10 mantissa bits: probe P12's model of the device's fp32
matmul, whose measured error is 1.4-1.75x that model's, so ``tf32`` rows are optimistic). Configurations:

- ``bf16_all``: bf16 everywhere (weights and activations): the research simulation's worst case;
- ``plan``: the planned functional numerics -- conv activations bf16 with fp32 weights (PLAN.md 9.3 precision policy),
  the point / voxel layers as an fp32 island (device fp32 matmul ~ tf32), bf16 rows out of the ``ttnn.embedding``
  gathers (bf16-only tables), fp32 interpolation matmuls (~ tf32);
- ``plan_bf16_points``: ``plan`` with bf16 activations and fp32 weights in the point layers (the fast path);
- ``plan_fp32_gather``: ``plan`` with exact fp32 gathers (what a fp32 row-gather kernel, K3, would buy);
- ``plan_fp32_convs`` / ``plan_exact_points``: ``plan`` with fp32 conv activations, resp. exact fp32 point layers
  (e.g. two-term bf16 operands): which island to deepen first when a frame misses a gate.

Metrics per frame against the stored ONNX Runtime ``pred_probs`` (the reference of record): PCC over all points and
argmax agreement on the raw points (the PLAN.md 2.8 gates: >= 0.998 and >= 98.5 %), and the module PCC of the device
modules' outputs against the exact fp32 dataflow (gate >= 0.999). Indicative only: no math-fidelity phases, no tile
layouts, no on-device accumulation order.
"""
from __future__ import annotations

import argparse
import json
import sys
import time
from pathlib import Path

import numpy as np

HERE = Path(__file__).resolve()
sys.path.insert(0, str(HERE.parents[1]))

WS = Path("/home/ubuntu/experiments/tt-models")
CONFIGS = {
    "bf16_all": "conv_act=bf16,conv_weight=bf16,point_act=bf16,point_weight=bf16,gather=bf16,upsample=bf16",
    "plan": "conv_act=bf16,point_act=tf32,point_weight=tf32,gather=bf16,upsample=tf32",
    "plan_bf16_points": "conv_act=bf16,point_act=bf16,gather=bf16,upsample=tf32",
    "plan_fp32_gather": "conv_act=bf16,point_act=tf32,point_weight=tf32,upsample=tf32",
    "plan_fp32_convs": "point_act=tf32,point_weight=tf32,gather=bf16,upsample=tf32",
    "plan_exact_points": "conv_act=bf16,gather=bf16,upsample=tf32",
    # M1b device follow-up (PORT_LOG "M1 device results"): the device's float32 conv-weight path is not exact
    # (C17 / YOLOX: TF32-like, close to bf16 weights); the point island exact (two-term operands)
    "exact_points_tf32_conv_w": "conv_act=bf16,conv_weight=tf32,gather=bf16,upsample=tf32",
    "exact_points_bf16_conv_w": "conv_act=bf16,conv_weight=bf16,gather=bf16,upsample=tf32",
}
FRAMES = ["pandaset_019_40.ot128", "pandaset_029_40.ot128", "nuscenes_scene-0103_20.ot128",
          "nuscenes_scene-0916_20.ot128", "vls128_frame000_fronthalf.ot128", "pandaset_019_40.qt128",
          "vls128_frame000_fronthalf.qt128"]
MODULES = ("encoder.pf3", "encoder.voxel_feats", "backbone.stem", "backbone.point_stem", "backbone.fusion_stem",
           "backbone.stage1", "backbone.stage2", "backbone.stage3", "backbone.stage4", "decoder.fuse2",
           "decoder.point_fuse2", "head.logits")


def main() -> int:
    ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
    ap.add_argument("--out", type=Path, required=True)
    ap.add_argument("--frames", nargs="*", default=FRAMES)
    ap.add_argument("--configs", nargs="*", default=list(CONFIGS)[:4])
    ap.add_argument("--threads", type=int, default=4)
    ap.add_argument("--weights-dir", default=str(WS / "assets" / "frnet" / "hf_lidar_frnet"))
    a = ap.parse_args()

    import torch

    from tt_frnet.host.preprocess import build_plan
    from tt_frnet.reference.config import FRNetConfig
    from tt_frnet.reference.rewrites import Emulation, FRNetPlanTorch
    from tt_frnet.reference.weights import load_weights
    from tt_frnet.tests import _data
    from tt_frnet.ttaw.golden import TapRegistry
    from tt_frnet.ttaw.metrics import pcc

    torch.set_num_threads(a.threads)
    weights, configs = {}, {}
    result = {"configs": {k: CONFIGS[k] for k in a.configs}, "frames": {}, "note": __doc__.split("\n\n")[2]}
    for frame in a.frames:
        variant = frame.rpartition(".")[2]
        if variant not in weights:
            weights[variant] = load_weights(a.weights_dir, variant)
            configs[variant] = FRNetConfig.from_model_dir(a.weights_dir, variant)
        w = weights[variant]
        inputs, ort_probs, _ = _data.load_frame(frame)
        plan = build_plan(inputs, configs[variant], pre_norm=(w.pre_norm_scale, w.pre_norm_shift))
        n_raw = inputs.num_points_raw
        ref_label = ort_probs.argmax(1)
        t0 = time.time()
        exact = TapRegistry(include=list(MODULES))
        FRNetPlanTorch(w)(plan, taps=exact)
        rows = {}
        for name in a.configs:
            taps = TapRegistry(include=list(MODULES))
            probs = plan.unsort(FRNetPlanTorch(w, emulation=Emulation.parse(CONFIGS[name]))(plan, taps=taps))
            label = probs.argmax(1)
            rows[name] = {"pred_probs_pcc": float(pcc(probs, ort_probs)),
                          "argmax_agreement_raw": float((label[:n_raw] == ref_label[:n_raw]).mean()),
                          "argmax_agreement_all": float((label == ref_label).mean()),
                          "modules": {m: float(pcc(taps[m][: plan.n] if taps[m].shape[0] == plan.n_cap else taps[m],
                                                   exact[m][: plan.n] if exact[m].shape[0] == plan.n_cap else exact[m]))
                                      for m in MODULES}}
            r = rows[name]
            worst = min(r["modules"].items(), key=lambda kv: kv[1])
            print(f"{frame} {name}: pcc {r['pred_probs_pcc']:.6f} argmax(raw) {r['argmax_agreement_raw']:.4%} "
                  f"worst module {worst[0]} {worst[1]:.6f}", flush=True)
        result["frames"][frame] = {"N": inputs.num_points, "N_raw": n_raw, "M": inputs.num_voxels,
                                   "seconds": round(time.time() - t0, 1), "results": rows}
        a.out.parent.mkdir(parents=True, exist_ok=True)
        a.out.write_text(json.dumps(result, indent=1) + "\n")
    return 0


if __name__ == "__main__":
    sys.exit(main())