Download code/tt_diffusion_planner/ttaw/models/centerhead.py from changh95/diffusion-planner-p150: direct link, hf CLI and curl.
- Browser
- Download file 9.08 kB
-
https://huggingface.co/changh95/diffusion-planner-p150/resolve/main/code/tt_diffusion_planner/ttaw/models/centerhead.py
- Command line
-
hf download hf://changh95/diffusion-planner-p150/code/tt_diffusion_planner/ttaw/models/centerhead.py
-
curl -L -o centerhead.py https://huggingface.co/changh95/diffusion-planner-p150/resolve/main/code/tt_diffusion_planner/ttaw/models/centerhead.py
9.08 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """C25: the CenterPoint dense head on the device (PLAN.md section 1.2 row C25; CenterPoint, PointPainting). | |
| mmdet3d ``CenterHead`` (single task, as Autoware's exports): ``shared = relu(conv3x3(concat[d_0..d_n]))``, then per | |
| output head ``out_h = conv1x1(relu(conv1x1(shared)))``. Two exact rewrites (each differs from the literal graph | |
| only by summation order; probe P14 and research/centerpoint/SPEC.md 8.1): | |
| - **K-split shared conv** (default; C17 ``KSplitConv``): ``conv(concat[d_i], W) == sum_i conv(d_i, W_i)``, the bias | |
| on the first partial conv and the ReLU fused into the last add. The concat (CenterPoint base: 177 MB) never | |
| exists; P14: 3.6 ms against 14.8 ms for the full-K conv, and more accurate. ``accumulate_dtype="float32"`` keeps | |
| the partials in fp32; | |
| - **merged sibling heads**: the six 1x1 -> ReLU -> 1x1 heads become two matmuls on NHWC rows (:class:`..second. | |
| RowLinear`): ``hidden = relu(x @ Wh + bh)`` with ``Wh = [Wh_1 | ... | Wh_6]`` (``64 -> 6*64``), then ``out = | |
| hidden @ Wo + bo`` with the block-diagonal ``Wo`` (``6*64 -> sum C_h``, zero-padded to a multiple of 32 columns so | |
| the output tensor is tile-exact). | |
| The output is ONE ``[1, 1, H*W, C_pad]`` tensor (fp32 by default: the logits feed host thresholds), i.e. one | |
| readback; :meth:`CenterHead.unpack` splits the host rows into ``{name: (C, H, W)}``. Module names for the precision | |
| policy: ``<prefix>.shared``, ``<prefix>.hidden``, ``<prefix>.out``. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any, Dict, List, Optional, Sequence, Tuple | |
| import numpy as np | |
| from ..ops.conv import Conv2d, FeatureMap, KSplitConv | |
| from ..precision import PrecisionPolicy | |
| from .second import ConvSpec, RowLinear, _precision, _to_dram, free_maps | |
| __all__ = ["CenterHead", "HeadSpec", "merge_heads", "centerhead_numpy"] | |
| HeadSpec = Tuple[str, ConvSpec, ConvSpec] # (output name, hidden 1x1 + ReLU, final 1x1) | |
| def merge_heads(heads: Sequence[HeadSpec], *, pad_to: int = 32 | |
| ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, List[Tuple[str, int, int]]]: | |
| """``(Wh [Cin, sum hidden], bh, Wo [sum hidden, C_pad], bo [C_pad], slices)`` of the merged heads; ``slices`` | |
| lists ``(name, start, stop)`` of each head's output columns; columns past ``sum C_h`` are zero.""" | |
| if not heads: | |
| raise ValueError("no heads") | |
| cin = heads[0][1].in_channels | |
| wh, bh, rows, cols = [], [], 0, 0 | |
| for name, hidden, final in heads: | |
| if hidden.kind != "conv" or final.kind != "conv" or hidden.kernel != 1 or final.kernel != 1: | |
| raise ValueError(f"head {name!r}: merged heads need 1x1 convs") | |
| if hidden.in_channels != cin or final.in_channels != hidden.out_channels: | |
| raise ValueError(f"head {name!r}: channel mismatch") | |
| if not hidden.relu or final.relu: | |
| raise ValueError(f"head {name!r}: expected a ReLU after the hidden conv only") | |
| wh.append(np.asarray(hidden.weight, np.float32)[:, :, 0, 0].T) | |
| bh.append(np.zeros(hidden.out_channels, np.float32) if hidden.bias is None | |
| else np.asarray(hidden.bias, np.float32)) | |
| rows += hidden.out_channels | |
| cols += final.out_channels | |
| cpad = -(-cols // pad_to) * pad_to | |
| wo = np.zeros((rows, cpad), np.float32) | |
| bo = np.zeros(cpad, np.float32) | |
| slices, r, c = [], 0, 0 | |
| for name, hidden, final in heads: | |
| co = final.out_channels | |
| wo[r:r + hidden.out_channels, c:c + co] = np.asarray(final.weight, np.float32)[:, :, 0, 0].T | |
| if final.bias is not None: | |
| bo[c:c + co] = np.asarray(final.bias, np.float32) | |
| slices.append((name, c, c + co)) | |
| r += hidden.out_channels | |
| c += co | |
| return (np.ascontiguousarray(np.concatenate(wh, axis=1)), np.concatenate(bh).astype(np.float32), wo, bo, | |
| slices) | |
| class CenterHead: | |
| """Shared conv (K-split over ``split``) + merged heads; see the module docstring. | |
| ``shared``: the 3x3 :class:`ConvSpec` over the concatenated inputs (ReLU after); ``split``: channels of each | |
| input, in concat order; ``heads``: ``(name, hidden, final)`` in output order. ``k_split=False`` runs one conv | |
| on a device concat instead (A/B only). ``output_dtype``: of the packed head output (``"float32"``). The shared | |
| conv's output dtype is its precision's ``activations`` (bf16 by default); ``accumulate_dtype`` (default: the same) | |
| is the dtype of the K-split partial sums.""" | |
| def __init__(self, shared: ConvSpec, split: Sequence[int], heads: Sequence[HeadSpec], *, | |
| policy: Optional[PrecisionPolicy] = None, prefix: str = "head", output_dtype: str = "float32", | |
| k_split: bool = True, accumulate_dtype: Optional[str] = None): | |
| if shared.kind != "conv" or shared.stride != 1 or not shared.relu: | |
| raise ValueError("the shared conv must be a stride-1 conv followed by a ReLU") | |
| self.prefix = prefix | |
| self.split = [int(s) for s in split] | |
| if sum(self.split) != shared.in_channels: | |
| raise ValueError(f"split {self.split} does not cover the {shared.in_channels} shared-conv inputs") | |
| self.k_split = bool(k_split) and len(self.split) > 1 | |
| pad = shared.kernel // 2 if shared.padding is None else int(shared.padding) | |
| prec = _precision(policy, f"{prefix}.shared") | |
| if self.k_split: | |
| self.shared = KSplitConv(shared.weight, shared.bias, self.split, activation="relu", padding=pad, | |
| precision=prec, accumulate_dtype=accumulate_dtype or prec.activations, | |
| output_dtype=prec.activations, name=f"{prefix}.shared") | |
| else: | |
| self.shared = Conv2d(shared.weight, shared.bias, padding=pad, activation="relu", precision=prec, | |
| output_dtype=prec.activations, name=f"{prefix}.shared") | |
| wh, bh, wo, bo, self.slices = merge_heads(heads) | |
| self.out_channels = self.slices[-1][2] | |
| self.padded_channels = int(wo.shape[1]) | |
| hidden_prec = _precision(policy, f"{prefix}.hidden") | |
| self.hidden = RowLinear(wh, bh, activation="relu", precision=hidden_prec, | |
| output_dtype=hidden_prec.activations, name=f"{prefix}.hidden") | |
| self.out = RowLinear(wo, bo, activation=None, precision=_precision(policy, f"{prefix}.out"), | |
| output_dtype=output_dtype, name=f"{prefix}.out") | |
| def shared_forward(self, inputs: Sequence[FeatureMap]) -> FeatureMap: | |
| """``relu(conv3x3(concat(inputs)))`` (inputs are kept), DRAM interleaved.""" | |
| import ttnn | |
| if [x.channels for x in inputs] != self.split: | |
| raise ValueError(f"{self.prefix}: inputs have {[x.channels for x in inputs]} channels, " | |
| f"expected {self.split}") | |
| if self.k_split: | |
| return _to_dram(self.shared(inputs)) | |
| cat = inputs[0].with_tensor(ttnn.concat([x.tensor for x in inputs], dim=-1), sum(self.split)) | |
| out = _to_dram(self.shared(cat)) | |
| free_maps(cat) | |
| return out | |
| def heads_forward(self, shared: FeatureMap) -> FeatureMap: | |
| """Merged heads on the shared map -> ``[1, 1, H*W, C_pad]`` (``channels`` = the padded width).""" | |
| hidden = self.hidden(shared.tensor) | |
| out = self.out(hidden) | |
| free_maps(hidden) | |
| return shared.with_tensor(out, self.padded_channels) | |
| def __call__(self, inputs: Sequence[FeatureMap]) -> FeatureMap: | |
| shared = self.shared_forward(inputs) | |
| out = self.heads_forward(shared) | |
| free_maps(shared) | |
| return out | |
| def unpack(self, rows: Any, height: int, width: int) -> Dict[str, np.ndarray]: | |
| """Host rows ``[H*W, >= C]`` (a read-back of the head output) -> ``{name: (C_h, H, W)}`` float32.""" | |
| a = np.asarray(rows, dtype=np.float32) | |
| a = a.reshape(-1, a.shape[-1])[: height * width] | |
| return {name: np.ascontiguousarray(a[:, s:e].T.reshape(e - s, height, width)) for name, s, e in self.slices} | |
| def release(self) -> None: | |
| self.shared.release() | |
| self.hidden.release() | |
| self.out.release() | |
| def describe(self) -> Dict[str, Any]: | |
| return {"shared": self.shared.describe(), "k_split": self.k_split, "hidden": self.hidden.describe(), | |
| "out": self.out.describe(), "slices": [list(s) for s in self.slices]} | |
| def centerhead_numpy(shared: ConvSpec, heads: Sequence[HeadSpec], inputs_nchw: Sequence[Any]) -> Dict[str, np.ndarray]: | |
| """fp32 oracle (torch, the literal graph: concat, conv, six heads) -> ``{"shared": NCHW, name: NCHW}``.""" | |
| import torch | |
| from .second import _apply_torch | |
| with torch.no_grad(): | |
| x = torch.cat([torch.as_tensor(np.asarray(t, dtype=np.float32)) for t in inputs_nchw], dim=1) | |
| s = _apply_torch(shared, x) | |
| out = {"shared": s.numpy().copy()} | |
| for name, hidden, final in heads: | |
| out[name] = _apply_torch(final, _apply_torch(hidden, s)).numpy().copy() | |
| return out | |