File size: 12,002 Bytes
0887b31 c699c4c 0887b31 c699c4c 0887b31 | 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 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | # SPDX-FileCopyrightText: © 2026 Tenstorrent USA, Inc.
# SPDX-License-Identifier: Apache-2.0
"""Host half of the ``TT_FUSED`` serving path (torch only, no ttnn import). DEFAULT ON since the
2026-09-13 device validation (DEVICE_VALIDATION.md "Results"); ``TT_FUSED=0`` restores the legacy path.
The device half lives in ``models/tt/superpoint_ttnn.py`` (``TtSuperPoint.build_fused_graph``,
``capture_trace``, ``run_fused``). Everything here is either plumbing for the knob or a
*torch emulation of the exact device op sequence* so that the reformulations can be proven
against the legacy host math without hardware (``models/tests/test_fused_host.py``).
Knob (read ONCE at model build / server start, never per request):
TT_FUSED unset / empty / 1 (default) = the fused path: one metal trace over the
whole device graph (persistent input, resident outputs, ``execute_trace``
per request) plus the stages below. 0/false/no/off = the legacy untraced
path, untouched (the pre-2026-09-13 shipped behaviour).
TT_FUSED_STAGES comma list, default ``wide,nms,rms,rm`` (all). For device A/B only:
``""`` = trace-only (legacy graph, traced); ``wide`` = 64-byte-page
input upload + in-trace reshape; ``nms`` = the device NMS-T chain;
``rms`` = descriptor L2-norm as ``ttnn.rms_norm``; ``rm`` = untilize
the descriptor output on device.
SP_TRACE_REGION trace_region_size bytes for ``ttnn.CreateDevice`` (default 32 MiB).
Exactness classes (see reports/megakernel/superpoint-p150.md §4):
* trace, wide, nms, rm: bit-identical to the legacy path on the same bf16 values;
* rms: bf16-rounding-level (one final rounding instead of three), gated on device by
descriptor PCC >= 0.999 and keypoint F1 >= 98.8 %.
"""
from __future__ import annotations
import os
from dataclasses import dataclass
from typing import Dict, FrozenSet, Iterable, Mapping, Optional, Tuple
import torch
import torch.nn.functional as F
FUSED_ENV = "TT_FUSED"
STAGES_ENV = "TT_FUSED_STAGES"
TRACE_REGION_ENV = "SP_TRACE_REGION"
#: Default of the TT_FUSED knob when unset/empty (flipped to True after the p150a validation).
FUSED_DEFAULT = True
#: Every fused stage, in the A/B order of DEVICE_VALIDATION.md. Unknown names are an error.
ALL_STAGES: Tuple[str, ...] = ("wide", "nms", "rms", "rm", "l1", "nmsk", "kpc", "u8", "rsz")
_TRUE = ("1", "true", "yes", "on")
#: Wide-page upload: the 614 400 input bytes as [1, 1, N/32, 32] (64-byte RM pages) instead of
#: [1, 1, N, 1] (2-byte pages). The trace reshapes back to [1, 1, N, 1] before the first conv.
WIDE_PAGE_W = 32
#: NMS-T lane width: the dense 480x640 map is pooled as NHWC with C = 32 consecutive pixels of the
#: non-pooled axis, so each 1-D max-pool runs on 64-byte sticks (no 2-byte pages, no 32x padding).
NMS_LANES = 32
#: rms_norm gamma: x / ||x||_2 = x / (sqrt(256) * sqrt(mean(x^2))) = rms_norm(x) * (1/16). 1/16 is
#: a power of two, hence exact in bf16 (asserted in the host test).
RMS_GAMMA = 1.0 / 16.0
DESCRIPTOR_DIM = 256
#: trace_region_size when TT_FUSED=1 (port used 6 MiB for ~100 launches; the fused graph adds ~30).
DEFAULT_TRACE_REGION = 32 * 1024 * 1024
# ----------------------------------------------------------------------------- knob plumbing
def fused_enabled(env: Optional[Mapping[str, str]] = None) -> bool:
"""``TT_FUSED`` unset or empty -> ``FUSED_DEFAULT`` (True); otherwise truthy ("1", "true",
"yes", "on"; case-insensitive) -> fused, anything else ("0", "false", "no", "off") -> legacy."""
env = os.environ if env is None else env
raw = str(env.get(FUSED_ENV, "")).strip().lower()
if raw == "":
return FUSED_DEFAULT
return raw in _TRUE
def parse_stages(spec: Optional[Iterable[str] | str]) -> FrozenSet[str]:
"""Normalise a stage list; ``None`` -> all stages, ``""`` -> none. Unknown -> ValueError."""
if spec is None:
return frozenset(ALL_STAGES)
if isinstance(spec, str):
items = [s.strip().lower() for s in spec.split(",")]
else:
items = [str(s).strip().lower() for s in spec]
items = [s for s in items if s]
unknown = sorted(set(items) - set(ALL_STAGES))
if unknown:
raise ValueError(f"{STAGES_ENV}: unknown stage(s) {unknown}; valid: {list(ALL_STAGES)}")
return frozenset(items)
def fused_stages(env: Optional[Mapping[str, str]] = None) -> FrozenSet[str]:
"""Stages selected by ``TT_FUSED_STAGES`` (default: all). Independent of ``TT_FUSED`` itself
so an explicit ``TtSuperPoint(..., fused=True)`` gets the full set."""
env = os.environ if env is None else env
return parse_stages(env.get(STAGES_ENV))
def trace_region_size(env: Optional[Mapping[str, str]] = None) -> int:
env = os.environ if env is None else env
raw = str(env.get(TRACE_REGION_ENV, "")).strip()
return int(raw) if raw else DEFAULT_TRACE_REGION
def device_open_kwargs(
device_id: int, l1_small_size: int, fused: bool, trace_region: Optional[int] = None
) -> Dict[str, int]:
"""kwargs for ``ttnn.CreateDevice``. Legacy: exactly ``device_id`` + ``l1_small_size`` (the
shipped call). Fused: adds ``trace_region_size`` (default 0 in ttnn -> no capture possible)."""
kwargs = {"device_id": int(device_id), "l1_small_size": int(l1_small_size)}
if fused:
kwargs["trace_region_size"] = int(DEFAULT_TRACE_REGION if trace_region is None else trace_region)
return kwargs
#: uint8 pixel -> bf16(fp32(u8) / 255): exactly the value the server's fp32 ``/255`` preprocess
#: followed by ``prepare_host_input``'s bf16 cast produces (``TtSuperPoint.prepare_host_input_u8``).
U8_TO_BF16 = (torch.arange(256, dtype=torch.float32) / 255.0).to(torch.bfloat16)
# ----------------------------------------------------------------------------- wide-page upload
def wide_input_shape(batch_size: int, height: int, width: int) -> Tuple[int, int, int, int]:
n = batch_size * height * width
if n % WIDE_PAGE_W:
raise ValueError(f"input volume {n} is not a multiple of {WIDE_PAGE_W}")
return (1, 1, n // WIDE_PAGE_W, WIDE_PAGE_W)
def wide_input_view(nhwc_bf16: torch.Tensor) -> torch.Tensor:
"""[1, 1, N, 1] bf16 (the legacy host input) -> [1, 1, N/32, 32] view of the SAME bytes."""
if nhwc_bf16.dim() != 4 or nhwc_bf16.shape[-1] != 1:
raise ValueError(f"expected a [1, 1, N, 1] tensor, got {tuple(nhwc_bf16.shape)}")
n = nhwc_bf16.numel()
if n % WIDE_PAGE_W:
raise ValueError(f"input volume {n} is not a multiple of {WIDE_PAGE_W}")
return nhwc_bf16.contiguous().view(1, 1, n // WIDE_PAGE_W, WIDE_PAGE_W)
# ----------------------------------------------------------------------------- NMS-T emulation
def _maxpool_rows(x_flat: torch.Tensor, n: int, h: int, w: int, c: int, radius: int) -> torch.Tensor:
"""Emulates ``ttnn.max_pool2d(x, batch_size=n, input_h=h, input_w=w, channels=c,
kernel_size=[2r+1, 1], stride=[1, 1], padding=[r, 0])`` on an NHWC tensor flattened to
[1, 1, n*h*w, c]: a 1-D max along H for every (n, w, c) lane, -inf padded (pool_utils.cpp:40,
same as ``F.max_pool2d``). Max of bf16 values is exact, so fp32 is used for the pool itself."""
dtype = x_flat.dtype
x = x_flat.reshape(n, h, w, c).permute(0, 3, 1, 2).float() # NCHW for torch
y = F.max_pool2d(x, kernel_size=(2 * radius + 1, 1), stride=1, padding=(radius, 0))
return y.permute(0, 2, 3, 1).to(dtype).reshape(1, 1, n * h * w, c)
def nms_t_reference(s_sm: torch.Tensor, b: int, enc_h: int, enc_w: int, radius: int) -> torch.Tensor:
"""Torch emulation of ``TtSuperPoint._device_nms_t`` -- the SAME op sequence, step for step.
``s_sm``: device-softmaxed scores, logical [1, 1, b*enc_h*enc_w, 65] (any dtype; bf16 on
device). Returns the post-NMS dense map [b, 8*enc_h, 8*enc_w] in ``s_sm.dtype``. Must equal
``postprocess.fold_scores(scores_nchw, radius)`` bit for bit -- the host test proves it.
"""
H, W = enc_h * 8, enc_w * 8
C = NMS_LANES
if W % C or H % C:
raise ValueError(f"NMS-T needs H and W multiples of {C}, got {H}x{W}")
x = s_sm.reshape(1, 1, b * enc_h * enc_w, s_sm.shape[-1])
# 1-2 slice(..., [1,1,N,64]) on the TILE tensor (tile-aligned), to_layout(ROW_MAJOR)
x = x[..., :64].contiguous()
# 3-5 reshape [b*enc_h, enc_w, 8, 8] -> permute(0,2,1,3) (= transpose_hc, RM) -> reshape
# [1, 1, b*H*(W/32), 32]: the dense map D[y, x] as NHWC (N=b, H=H, W=W/32, C=32)
x = x.reshape(b * enc_h, enc_w, 8, 8).permute(0, 2, 1, 3).contiguous()
D = x.reshape(1, 1, b * H * (W // C), C)
# 6-7 max_pool2d k=[2r+1,1] p=[r,0] over H (the y direction) -> to DRAM
My = _maxpool_rows(D, b, H, W // C, C, radius)
# 8-11 reshape [b,1,H,W] -> tilize -> transpose(-2,-1) -> untilize -> reshape [1,1,b*W*(H/32),32]
MyT = My.reshape(b, 1, H, W).transpose(-2, -1).contiguous().reshape(1, 1, b * W * (H // C), C)
# 12-13 max_pool2d over H (now the x direction) -> to DRAM -> reshape [b,1,W,H] -> tilize
WT = _maxpool_rows(MyT, b, W, H // C, C, radius).reshape(b, 1, W, H) # = 9x9 window max, transposed
# 14 D^T: reshape D [b,1,H,W] -> tilize -> transpose(-2,-1)
DT = D.reshape(b, 1, H, W).transpose(-2, -1)
# 15 eq(D^T, W^T) -> multiply(D^T, mask) (x*1 = x, x*0 = +0 for x >= 0)
nmsT = DT * (DT == WT).to(DT.dtype)
# 16 transpose(-2,-1) -> untilize: natural [b, 1, H, W] ROW_MAJOR map, read back as-is
return nmsT.transpose(-2, -1).contiguous().reshape(b, H, W)
# ----------------------------------------------------------------------------- rms_norm L2-norm
def l2norm_via_rms(x: torch.Tensor, gamma: float = RMS_GAMMA, eps: float = 0.0) -> torch.Tensor:
"""Emulates ``ttnn.rms_norm(x, epsilon=0, weight=gamma)`` with fp32 statistics and ONE final
rounding to ``x.dtype`` -- the numerics the fused path asks for (HiFi4, math_approx_mode=False,
fp32_dest_acc_en=True). For gamma = 1/sqrt(D) this is ``x / ||x||_2`` (``F.normalize``)."""
xf = x.float()
inv_rms = torch.rsqrt(xf.pow(2).mean(dim=-1, keepdim=True) + eps)
return (xf * inv_rms * gamma).to(x.dtype)
def l2norm_legacy_chain(x: torch.Tensor) -> torch.Tensor:
"""Emulates the legacy device chain with a bf16 rounding after every op: multiply(d, d) ->
sum(-1) -> rsqrt -> multiply(d, inv). Only used to *report* the precision of the two
formulations side by side in the host test; the device accumulates ``sum`` internally."""
d_sq = (x.float() * x.float()).to(x.dtype)
d_sum = d_sq.float().sum(dim=-1, keepdim=True).to(x.dtype)
d_inv = torch.rsqrt(d_sum.float()).to(x.dtype)
return (x.float() * d_inv.float()).to(x.dtype)
def bf16_ulp(v: torch.Tensor) -> torch.Tensor:
"""Spacing of bf16 values at |v| (8 significand bits): 2 ** (floor(log2|v|) - 7)."""
a = v.detach().float().abs().clamp_min(torch.finfo(torch.bfloat16).tiny)
return torch.exp2(torch.floor(torch.log2(a)) - 7)
# ----------------------------------------------------------------------------- result container
@dataclass
class FusedResult:
"""What ``TtSuperPoint.run_fused`` hands to the host post-processing.
Exactly one of ``nms_map`` / ``scores_nchw`` is set: ``nms_map`` [B, H, W] fp32 (the device
NMS map for the traced ``nms_radius``; feed ``postprocess.postprocess_from_nms_map``) or
``scores_nchw`` [B, 65, h, w] fp32 (device-softmaxed cell scores for any other radius; feed
``postprocess.postprocess_keypoints`` as on the legacy path). ``descriptors_nchw`` is the
device-L2-normalised [B, 256, h, w] fp32 map in both cases.
"""
descriptors_nchw: torch.Tensor
nms_map: Optional[torch.Tensor] = None
scores_nchw: Optional[torch.Tensor] = None
nms_radius: int = 0
@property
def device_nms(self) -> bool:
return self.nms_map is not None
|