Download code/tt_bevdet/reference/weights.py from changh95/bevdet-p150: direct link, hf CLI and curl.
- Browser
- Download file 10.3 kB
-
https://huggingface.co/changh95/bevdet-p150/resolve/main/code/tt_bevdet/reference/weights.py
- Command line
-
hf download hf://changh95/bevdet-p150/code/tt_bevdet/reference/weights.py
-
curl -L -o weights.py https://huggingface.co/changh95/bevdet-p150/resolve/main/code/tt_bevdet/reference/weights.py
10.3 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """Weights of ``bevdet_one_lt_d.onnx`` (AutowareFoundation/tensorrt_bevdet@v1.0), read the way the TT port reads them. | |
| The ONNX is parsed as data with the vendored ``ttaw.weights.OnnxWeights`` (C04): nothing is executed, and every | |
| parameter is addressed through the graph node that consumes it (``consumer_of(<initializer>, op)`` -> the node -> | |
| ``conv()`` / ``gemm()`` / ``batchnorm()``), so the convolution attributes (strides, pads, dilations, groups) come from | |
| the deployed graph itself, never from re-typed constants. The export folded every BatchNorm2d into its convolution | |
| (``fuse_conv_bn=True``, S:271), so the conv weights are used as stored; the one remaining BatchNorm | |
| (``depth_net.bn`` = BatchNorm1d(27) over ``cam_params``) is folded by the port at calibration time into the camera | |
| gates (``reference.port_form.camera_gates``, float64, one final rounding), exactly where the TT port folds it. | |
| :meth:`BEVDetWeights.check_graph` verifies that the file is the network this port was written for (node coverage, | |
| plugin attributes, resize modes, softmax axis, output names) and fails loudly otherwise. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import Any, Dict, Iterator, List, Optional, Tuple | |
| import numpy as np | |
| from .config import HEAD_NAMES, BEVDetConfig | |
| __all__ = ["ONNX_FILE", "ONNX_SHA256", "BEVDetWeights", "module_names", "find_onnx"] | |
| ONNX_FILE = "bevdet_one_lt_d.onnx" | |
| # sha256 of the pinned file (HF LFS sha256 == local, SPEC section 1) | |
| ONNX_SHA256 = "47f156cb63ca97fa1d47b5d46eba648d4bbe71c95a08af14d3d0fdf53d800d77" | |
| HEAD_BRANCHES = ("reg", "height", "dim", "rot", "vel", "heatmap") | |
| def module_names() -> Dict[str, List[str]]: | |
| """Every convolution / linear module of the network by stage, in graph order (initializer prefixes).""" | |
| backbone = ["img_backbone.conv1"] | |
| for li, nb in enumerate([3, 4, 6, 3], start=1): | |
| for bi in range(nb): | |
| p = f"img_backbone.layer{li}.{bi}" | |
| backbone += [f"{p}.conv1", f"{p}.conv2", f"{p}.conv3"] + ([f"{p}.downsample.0"] if bi == 0 else []) | |
| neck = ["img_neck.lateral_convs.0.conv", "img_neck.lateral_convs.1.conv", "img_neck.fpn_convs.0.conv"] | |
| dn = "img_view_transformer.depth_net." | |
| depthnet = [dn + "reduce_conv.0", dn + "context_se.conv_reduce", dn + "context_se.conv_expand", | |
| dn + "context_conv", dn + "depth_se.conv_reduce", dn + "depth_se.conv_expand"] | |
| for bi in range(3): | |
| depthnet += [dn + f"depth_conv.{bi}.conv1", dn + f"depth_conv.{bi}.conv2"] | |
| depthnet += [dn + f"depth_conv.3.aspp{i}.atrous_conv" for i in range(1, 5)] | |
| depthnet += [dn + "depth_conv.3.global_avg_pool.1", dn + "depth_conv.3.conv1", dn + "depth_conv.4"] | |
| linears = [dn + "context_mlp.fc1", dn + "context_mlp.fc2", dn + "depth_mlp.fc1", dn + "depth_mlp.fc2"] | |
| bev = [] | |
| for li in range(3): | |
| for bi in range(2): | |
| p = f"img_bev_encoder_backbone.layers.{li}.{bi}" | |
| bev += [f"{p}.conv1", f"{p}.conv2"] + ([f"{p}.downsample"] if bi == 0 else []) | |
| bev_neck = ["img_bev_encoder_neck.conv.0", "img_bev_encoder_neck.conv.3", "img_bev_encoder_neck.up2.1", | |
| "img_bev_encoder_neck.up2.4"] | |
| head = ["pts_bbox_head.shared_conv.conv"] | |
| for h in HEAD_BRANCHES: | |
| head += [f"pts_bbox_head.task_heads.0.{h}.0.conv", f"pts_bbox_head.task_heads.0.{h}.1"] | |
| return {"img_backbone": backbone, "img_neck": neck, "depth_net": depthnet, "depth_net_linear": linears, | |
| "bev_backbone": bev, "bev_neck": bev_neck, "head": head} | |
| class Conv: | |
| """One convolution as the deployed graph runs it (float32 weights as stored).""" | |
| node: str | |
| weight: np.ndarray # [Cout, Cin / groups, kh, kw] | |
| bias: Optional[np.ndarray] # [Cout] | |
| stride: Tuple[int, int] | |
| pads: Tuple[int, int, int, int] # ONNX order: top, left, bottom, right | |
| dilation: Tuple[int, int] | |
| groups: int | |
| def padding(self) -> Tuple[int, int]: | |
| t, l, b, r = self.pads | |
| if (t, l) != (b, r): | |
| raise ValueError(f"{self.node}: asymmetric pads {self.pads}") | |
| return t, l | |
| class BEVDetWeights: | |
| """Parameters of the deployed network, addressed by consuming node. ``conv(module)`` / ``linear(module)`` take | |
| the module prefix of the training code (e.g. ``img_backbone.layer1.0.conv1``).""" | |
| def __init__(self, onnx_path: Any, *, check: bool = True): | |
| from ..ttaw.weights import OnnxWeights | |
| self.path = Path(onnx_path) | |
| self.onnx = OnnxWeights(self.path) | |
| self._convs: Dict[str, Conv] = {} | |
| if check: | |
| self.check_graph() | |
| def sha256(self) -> str: | |
| return self.onnx.sha256 | |
| # ---- addressing ---------------------------------------------------------------------------------- | |
| def conv_node(self, module: str) -> str: | |
| return self.onnx.consumer_of(module + ".weight", "Conv").name | |
| def conv(self, module: str) -> Conv: | |
| if module not in self._convs: | |
| node = self.conv_node(module) | |
| p = self.onnx.conv(node) | |
| if p.auto_pad not in ("NOTSET", ""): | |
| raise ValueError(f"{node}: auto_pad {p.auto_pad} is not supported") | |
| self._convs[module] = Conv(node, np.asarray(p.weight, np.float32), | |
| None if p.bias is None else np.asarray(p.bias, np.float32), | |
| tuple(p.strides), tuple(p.pads), tuple(p.dilations), int(p.group)) | |
| return self._convs[module] | |
| def linear(self, module: str) -> Tuple[np.ndarray, np.ndarray]: | |
| """Gemm (``transB=1``, alpha = beta = 1) -> (weight [out, in], bias [out]) float32.""" | |
| node = self.onnx.consumer_of(module + ".weight", "Gemm").name | |
| g = self.onnx.gemm(node) | |
| if g.trans_a or not g.trans_b or g.alpha != 1.0 or g.beta != 1.0: | |
| raise ValueError(f"{node}: unexpected Gemm attributes {g}") | |
| return np.asarray(g.weight, np.float32), np.asarray(g.bias, np.float32) | |
| def depth_bn(self): | |
| """``depth_net.bn``: BatchNorm1d(27) over ``cam_params`` (``ttaw.weights.BatchNorm``, float64).""" | |
| node = self.onnx.consumer_of("img_view_transformer.depth_net.bn.weight", "BatchNormalization").name | |
| return self.onnx.batchnorm(node) | |
| def iter_convs(self) -> Iterator[Tuple[str, Conv]]: | |
| for modules in module_names().values(): | |
| for m in modules: | |
| if m.endswith(("fc1", "fc2")): | |
| continue | |
| yield m, self.conv(m) | |
| # ---- graph checks --------------------------------------------------------------------------------- | |
| def check_graph(self, cfg: Optional[BEVDetConfig] = None) -> None: | |
| """The file is the deployed BEVDet this port reproduces: every Conv / Gemm node maps to exactly one module, | |
| the 4 ``bevdet::`` plugins carry the expected attributes, the resize / softmax semantics are the ones the | |
| reference implements, and the outputs are the 6 head maps + ``curr_bevfeat``.""" | |
| from .config import default_config | |
| cfg = cfg or default_config() | |
| w = self.onnx | |
| names = module_names() | |
| conv_modules = [m for k, v in names.items() if k != "depth_net_linear" for m in v] | |
| used = [self.conv_node(m) for m in conv_modules] | |
| all_convs = [n.name for n in w.nodes("Conv")] | |
| if sorted(used) != sorted(all_convs): | |
| raise ValueError(f"{self.path.name}: Conv nodes do not map 1:1 to the BEVDet modules " | |
| f"({len(set(used))} mapped, {len(all_convs)} in the graph)") | |
| gemms = sorted(w.consumer_of(m + ".weight", "Gemm").name for m in names["depth_net_linear"]) | |
| if gemms != sorted(n.name for n in w.nodes("Gemm")): | |
| raise ValueError(f"{self.path.name}: Gemm nodes do not map to the 4 camera MLP layers") | |
| pre = w.find_node("*", "Preprocess") | |
| r = float(np.float32(pre.attrs["resize_radio"])) | |
| if (int(pre.attrs["crop_h"]), int(pre.attrs["crop_w"])) != cfg.crop_hw or r != float(cfg.resize): | |
| raise ValueError(f"Preprocess attributes {pre.attrs} differ from the configuration") | |
| bp = w.find_node("*", "BEVPool") | |
| ny, nx = cfg.bev_hw | |
| if (int(bp.attrs["bev_h"]), int(bp.attrs["bev_w"]), int(bp.attrs["n"])) != (ny, nx, cfg.n_cams): | |
| raise ValueError(f"BEVPool attributes {bp.attrs} differ from the configuration") | |
| w.find_node("*", "AlignBEV") | |
| w.find_node("*", "GatherBEV") | |
| modes = [(n.attrs.get("mode"), n.attrs.get("coordinate_transformation_mode"), n.attrs.get("nearest_mode")) | |
| for n in w.nodes("Resize")] | |
| expect = [("nearest", "asymmetric", "floor"), ("linear", "align_corners", "floor"), | |
| ("linear", "align_corners", "floor"), ("linear", "align_corners", "floor")] | |
| if modes != expect: | |
| raise ValueError(f"Resize modes {modes} differ from the implemented ones {expect}") | |
| sm = w.nodes("Softmax") | |
| if len(sm) != 1 or int(sm[0].attrs.get("axis", -1)) != 1: | |
| raise ValueError("expected one Softmax over axis 1 (depth bins)") | |
| mp = w.nodes("MaxPool") | |
| if len(mp) != 1 or tuple(mp[0].attrs["kernel_shape"]) != (3, 3) or tuple(mp[0].attrs["strides"]) != (2, 2) \ | |
| or tuple(mp[0].attrs["pads"]) != (1, 1, 1, 1) or int(mp[0].attrs.get("ceil_mode", 0)) != 0: | |
| raise ValueError("expected the ResNet stem MaxPool 3x3 s2 p1") | |
| if tuple(w.output_names) != tuple(HEAD_NAMES) + ("curr_bevfeat",): | |
| raise ValueError(f"outputs {w.output_names} differ from {HEAD_NAMES + ('curr_bevfeat',)}") | |
| bn = self.depth_bn() | |
| if bn.channels != cfg.cam_params_size: | |
| raise ValueError("depth_net.bn must have 27 channels") | |
| def find_onnx(weights_dir: Any = None) -> Optional[Path]: | |
| """``<weights_dir>/bevdet_one_lt_d.onnx`` if it exists (``weights_dir`` may also be the file itself).""" | |
| if weights_dir is None: | |
| return None | |
| p = Path(weights_dir) | |
| if p.is_file(): | |
| return p | |
| q = p / ONNX_FILE | |
| return q if q.is_file() else None | |