Download code/tt_diffusion_planner/ttaw/ops/heatmap.py from changh95/diffusion-planner-p150: direct link, hf CLI and curl.
- Browser
- Download file 12.1 kB
-
https://huggingface.co/changh95/diffusion-planner-p150/resolve/main/code/tt_diffusion_planner/ttaw/ops/heatmap.py
- Command line
-
hf download hf://changh95/diffusion-planner-p150/code/tt_diffusion_planner/ttaw/ops/heatmap.py
-
curl -L -o heatmap.py https://huggingface.co/changh95/diffusion-planner-p150/resolve/main/code/tt_diffusion_planner/ttaw/ops/heatmap.py
12.1 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """C22: heatmap peaks of the query-based LiDAR detectors -- sigmoid and the 3x3 local maximum (PLAN.md section 1.2 | |
| row C22; probe P6, common/PROBES.md). | |
| TransFusion, BEVFusion and PTv3 keep a heatmap cell only where it is the maximum of its 3x3 neighbourhood, with | |
| model-specific border and class rules: | |
| - ``"transfusion"`` (TF, S:transfusion:169): ``MaxPool 3x3 s1 p0`` padded back with a ZERO border, every class: the | |
| border cells are always suppressed; | |
| - ``"bevfusion"`` (BF, S:bevfusion:258): classes ``pooled_classes`` (default 0-3) as TF; the other classes are not | |
| pooled (``local_max = heat``): every cell passes; | |
| - ``"ptv3"`` (PT, S:ptv3:217): ``local_max`` starts as the heat itself and only the interior of the pooled classes is | |
| replaced by the pool: border cells and the other classes pass. | |
| All three are one exact masked chain (P6, bit-exact on the device, TF 95 / BF 85 / PT 145 us): | |
| keep = m_pass + (heat == max_pool2d(heat, 3x3, stride 1, padding 1)) * m_eq out = heat * keep | |
| with two constant masks ``m_eq`` / ``m_pass`` (disjoint, values 0 / 1, laid out as the NHWC rows ``[H*W, C]`` of the | |
| heatmap). The pool's padding never matters: a padded window only covers border cells, where ``m_eq`` is 0. Not the | |
| literal ``p0`` pool + pad form (222 us for TF and two more programs). ``ttnn.max_pool2d`` is bf16 only (FLOAT32 is | |
| rejected, ``pool_op.cpp:30``), so the heat is bf16: :func:`sigmoid_heat` takes fp32 logits through an fp32 sigmoid and | |
| rounds once. | |
| Device layout: the heatmap is a TILE ``[1, 1, H*W, C]`` tensor (what a conv head writes); the result has the same | |
| layout (the input of C21's class-major top-k row and of the query-heat gather of C26). | |
| numpy only at import; ttnn is imported inside the device functions. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any, Dict, Optional, Sequence, Tuple | |
| import numpy as np | |
| __all__ = ["RULES", "DEFAULT_POOLED_CLASSES", "local_max_masks", "local_max_numpy", "local_max_literal_numpy", | |
| "sigmoid_heat", "sigmoid_heat_numpy", "LocalMax"] | |
| RULES = ("transfusion", "bevfusion", "ptv3") | |
| DEFAULT_POOLED_CLASSES = 4 # BF / PT: classes 0-3 pooled (the last class(es) pass) | |
| def _pooled(classes: int, rule: str, pooled_classes: Optional[Sequence[int]]) -> np.ndarray: | |
| if rule not in RULES: | |
| raise ValueError(f"rule {rule!r}: expected one of {RULES}") | |
| if pooled_classes is None: | |
| pooled_classes = range(classes) if rule == "transfusion" else range(min(DEFAULT_POOLED_CLASSES, classes)) | |
| p = np.zeros(classes, np.float32) | |
| for c in pooled_classes: | |
| if not 0 <= int(c) < classes: | |
| raise ValueError(f"pooled class {c} outside [0, {classes})") | |
| p[int(c)] = 1.0 | |
| if rule == "transfusion" and not p.all(): | |
| raise ValueError("the transfusion rule pools every class") | |
| return p | |
| def local_max_masks(height: int, width: int, classes: int, *, rule: str = "transfusion", | |
| pooled_classes: Optional[Sequence[int]] = None, kernel: int = 3) -> Tuple[np.ndarray, np.ndarray]: | |
| """The constant masks ``(m_pass, m_eq)`` of the chain for ``rule`` (module docstring), float32 NHWC rows | |
| ``[height * width, classes]``. ``kernel`` (odd): the pool size; the border is ``kernel // 2`` cells wide.""" | |
| if kernel < 1 or kernel % 2 == 0: | |
| raise ValueError(f"kernel must be odd and positive, got {kernel}") | |
| r = kernel // 2 | |
| if height <= 2 * r or width <= 2 * r: | |
| raise ValueError(f"a {height}x{width} map has no interior for a {kernel}x{kernel} pool") | |
| pooled = _pooled(classes, rule, pooled_classes) | |
| interior = np.zeros((height, width, 1), np.float32) | |
| interior[r:height - r, r:width - r] = 1.0 | |
| m_eq = interior * pooled[None, None, :] | |
| if rule == "transfusion": | |
| m_pass = np.zeros_like(m_eq) | |
| elif rule == "bevfusion": | |
| m_pass = np.broadcast_to(1.0 - pooled[None, None, :], m_eq.shape).astype(np.float32) | |
| else: | |
| m_pass = 1.0 - m_eq | |
| return (np.ascontiguousarray(m_pass.reshape(height * width, classes)), | |
| np.ascontiguousarray(m_eq.reshape(height * width, classes))) | |
| def _pool_numpy(heat_nhwc: np.ndarray, height: int, width: int, kernel: int) -> np.ndarray: | |
| """3x3 (k x k) stride-1 max pool with ``-inf`` padding of NHWC rows ``[H*W, C]`` (exact on any float data).""" | |
| c = heat_nhwc.shape[-1] | |
| x = np.asarray(heat_nhwc, np.float64).reshape(height, width, c) | |
| r = kernel // 2 | |
| p = np.full((height + 2 * r, width + 2 * r, c), -np.inf) | |
| p[r:r + height, r:r + width] = x | |
| out = np.full_like(x, -np.inf) | |
| for dy in range(kernel): | |
| for dx in range(kernel): | |
| np.maximum(out, p[dy:dy + height, dx:dx + width], out=out) | |
| return out.reshape(height * width, c) | |
| def local_max_numpy(heat_nhwc: Any, height: int, width: int, m_pass: Any, m_eq: Any, kernel: int = 3) -> np.ndarray: | |
| """Host oracle of the masked chain on NHWC rows ``[H*W, C]`` (float32 out; exact on bf16-valued input).""" | |
| h = np.asarray(heat_nhwc, np.float32).reshape(height * width, -1) | |
| pooled = _pool_numpy(h, height, width, kernel) | |
| keep = np.asarray(m_pass, np.float32) + (h == pooled).astype(np.float32) * np.asarray(m_eq, np.float32) | |
| return (h * keep).astype(np.float32) | |
| def local_max_literal_numpy(heat_nchw: Any, rule: str = "transfusion", *, | |
| pooled_classes: Optional[Sequence[int]] = None, kernel: int = 3) -> np.ndarray: | |
| """The models' literal forms (``heat * (heat == local_max)``, NCHW ``(C, H, W)`` or ``(1, C, H, W)``), the oracle | |
| the masked chain is proven against: TF ``max_pool p0`` + zero border (all classes); BF pooled classes as TF and | |
| the others ``local_max = heat``; PT ``local_max = heat`` with the interior of the pooled classes pooled.""" | |
| x = np.asarray(heat_nchw, np.float32) | |
| squeeze = x.ndim == 4 | |
| if squeeze: | |
| x = x[0] | |
| c, h, w = x.shape | |
| pooled = _pooled(c, rule, pooled_classes).astype(bool) | |
| r = kernel // 2 | |
| full = _pool_numpy(x.reshape(c, h * w).T, h, w, kernel).T.reshape(c, h, w) | |
| interior = np.zeros((h, w), bool) | |
| interior[r:h - r, r:w - r] = True | |
| if rule == "transfusion": | |
| local = np.where(interior[None], full, 0.0) | |
| elif rule == "bevfusion": | |
| local = np.where(pooled[:, None, None], np.where(interior[None], full, 0.0), x) | |
| else: | |
| local = np.where(pooled[:, None, None] & interior[None], full, x) | |
| out = (x * (x == local)).astype(np.float32) | |
| return out[None] if squeeze else out | |
| def sigmoid_heat_numpy(logits: Any, dtype: str = "bfloat16") -> np.ndarray: | |
| """Host model of :func:`sigmoid_heat`: float64 sigmoid of the float32 logits, rounded once to ``dtype`` (bf16 RNE).""" | |
| s = (1.0 / (1.0 + np.exp(-np.asarray(logits, np.float64)))).astype(np.float32) | |
| if dtype in ("bfloat16", "bf16"): | |
| from ..tensors import round_to_bf16 | |
| return round_to_bf16(s) | |
| return s | |
| def sigmoid_heat(logits: Any, *, dtype: str = "bfloat16", memory_config: Any = None): | |
| """Device ``sigmoid(logits)`` computed in the logits' dtype (fp32 logits: an fp32 sigmoid) and cast once to | |
| ``dtype`` (bf16: what ``max_pool2d`` and C21's ``topk_large_indices`` take). Returns ``(heat, sig)``: the cast | |
| heat and the sigmoid before the cast (the same tensor when no cast is needed).""" | |
| import ttnn | |
| from ..tensors import ttnn_dtype | |
| kw = {} if memory_config is None else {"memory_config": memory_config} | |
| sig = ttnn.sigmoid(logits, **kw) | |
| want = ttnn_dtype(dtype) | |
| heat = sig if sig.dtype == want else ttnn.typecast(sig, want) | |
| return heat, sig | |
| def _mc(spec: Any): | |
| import ttnn | |
| if spec is None or spec == "dram": | |
| return ttnn.DRAM_MEMORY_CONFIG | |
| if spec == "l1": | |
| return ttnn.L1_MEMORY_CONFIG | |
| return spec | |
| class LocalMax: | |
| """The device chain for one heatmap geometry: ``heat`` (bf16 TILE ``[1, 1, H*W, C]``) -> ``heat * keep``. | |
| The masks are uploaded (bf16 TILE, exact 0 / 1) by the first call, outside any capture; with the TF rule | |
| (``m_pass`` = 0) the chain is ``max_pool2d``, ``eq``, ``multiply`` (keep = eq * m_eq), ``multiply``; otherwise | |
| ``addcmul(m_pass, eq, m_eq)`` replaces the first multiply. ``memory_config``: where the pool and the masks live | |
| (``"dram"`` default; ``"l1"`` is P6's 95 us form for maps that fit).""" | |
| def __init__(self, height: int, width: int, classes: int, *, rule: str = "transfusion", | |
| pooled_classes: Optional[Sequence[int]] = None, kernel: int = 3, memory_config: Any = "dram", | |
| name: str = "local_max"): | |
| self.height, self.width, self.classes = int(height), int(width), int(classes) | |
| self.rule = rule | |
| self.kernel = int(kernel) | |
| self.name = name | |
| self.m_pass, self.m_eq = local_max_masks(self.height, self.width, self.classes, rule=rule, | |
| pooled_classes=pooled_classes, kernel=self.kernel) | |
| self.passes = bool(self.m_pass.any()) | |
| self.memory_config = memory_config | |
| self._dev: Optional[Dict[str, Any]] = None | |
| def cells(self) -> int: | |
| return self.height * self.width | |
| def _upload(self, device: Any) -> Dict[str, Any]: | |
| import torch | |
| import ttnn | |
| def up(a: np.ndarray): | |
| t = torch.from_numpy(np.ascontiguousarray(a.reshape(1, 1, self.cells, self.classes))) | |
| return ttnn.from_torch(t, dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, device=device, | |
| memory_config=_mc(self.memory_config)) | |
| self._dev = {"m_eq": up(self.m_eq), "m_pass": up(self.m_pass) if self.passes else None} | |
| return self._dev | |
| def __call__(self, heat: Any): | |
| import ttnn | |
| shape = tuple(int(s) for s in heat.shape) | |
| if shape != (1, 1, self.cells, self.classes): | |
| raise ValueError(f"{self.name}: heat must be [1, 1, {self.cells}, {self.classes}], got {shape}") | |
| if heat.dtype != ttnn.bfloat16: | |
| raise ValueError(f"{self.name}: max_pool2d takes bf16 only (got {heat.dtype}): use sigmoid_heat") | |
| dev = self._dev or self._upload(heat.device()) | |
| r = self.kernel // 2 | |
| pooled = ttnn.max_pool2d(input_tensor=heat, batch_size=1, input_h=self.height, input_w=self.width, | |
| channels=self.classes, kernel_size=[self.kernel, self.kernel], stride=[1, 1], | |
| padding=[r, r], dilation=[1, 1], ceil_mode=False, | |
| memory_config=_mc(self.memory_config), output_layout=ttnn.TILE_LAYOUT) | |
| if tuple(int(s) for s in pooled.shape) != shape: | |
| full = pooled | |
| pooled = ttnn.slice(full, [0, 0, 0, 0], list(shape)) | |
| ttnn.deallocate(full, False) | |
| eq = ttnn.eq(heat, pooled) | |
| ttnn.deallocate(pooled, False) | |
| if self.passes: | |
| keep = ttnn.addcmul(dev["m_pass"], eq, dev["m_eq"]) | |
| else: | |
| keep = ttnn.multiply(eq, dev["m_eq"]) | |
| ttnn.deallocate(eq, False) | |
| out = ttnn.multiply(heat, keep) | |
| ttnn.deallocate(keep, False) | |
| return out | |
| def numpy(self, heat_nhwc: Any) -> np.ndarray: | |
| """Host oracle of this chain on NHWC rows (bf16-valued input gives the device result bit for bit).""" | |
| return local_max_numpy(heat_nhwc, self.height, self.width, self.m_pass, self.m_eq, self.kernel) | |
| def release(self) -> None: | |
| import ttnn | |
| if self._dev is not None: | |
| for t in self._dev.values(): | |
| if t is not None and t.is_allocated(): | |
| ttnn.deallocate(t) | |
| self._dev = None | |
| def describe(self) -> Dict[str, Any]: | |
| return {"name": self.name, "grid": [self.height, self.width], "classes": self.classes, "rule": self.rule, | |
| "kernel": self.kernel, "pooled_classes": [int(c) for c in np.flatnonzero(self.m_eq.max(axis=0))], | |
| "passes": self.passes, "memory_config": str(self.memory_config)} | |