Download code/tt_diffusion_planner/ttaw/ops/gather.py from changh95/diffusion-planner-p150: direct link, hf CLI and curl.
- Browser
- Download file 6.73 kB
-
https://huggingface.co/changh95/diffusion-planner-p150/resolve/main/code/tt_diffusion_planner/ttaw/ops/gather.py
- Command line
-
hf download hf://changh95/diffusion-planner-p150/code/tt_diffusion_planner/ttaw/ops/gather.py
-
curl -L -o gather.py https://huggingface.co/changh95/diffusion-planner-p150/resolve/main/code/tt_diffusion_planner/ttaw/ops/gather.py
6.73 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """C19: row gathers with ``ttnn.embedding`` and the gather form of scatter (PLAN.md section 1.2 row C19; probe P4). | |
| A scatter of ``P`` source rows into an ``N``-row destination (a BEV canvas, a to-dense map) is written as a | |
| gather: the host builds ``index[n]`` = the source row of destination row ``n``, or ``sentinel`` (an explicit | |
| all-zero row appended to the table) where nothing lands. Every shape is static, so it traces, and nothing has to | |
| clear the destination first. Probe P4 (common/PROBES.md) measured it bit-exact at the real shapes, with: | |
| - the table ROW_MAJOR bf16 ``[1, 1, V, D]`` (``D % 32 == 0`` for the fused tilized output), UINT32 indices | |
| ``[1, N]`` (ROW_MAJOR); | |
| - **the default call** ``ttnn.embedding(index, table, layout=TILE, padding_idx=sentinel, | |
| embeddings_type=PADDED)``: 0.19-0.54 ms per canvas (CenterPoint 480 x 480: 0.21 ms); GENERIC is 5.4-14.6x slower | |
| on canvases (most indices hit the sentinel); | |
| - **PADDED returns the table's own pad row, not zeros** (S:ptv3:304): the zero row must really be in the table | |
| (:func:`with_zero_rows`). | |
| ``ttnn.embedding`` is bf16-only; fp32 row gathers are ``ttnn.gather`` (bit-exact but ~1 us per index, probe P13), | |
| not covered here. Indices must lie in ``[0, V)``: an out-of-range index reads outside the table (and can hang the | |
| chip), so :func:`check_index` validates host index arrays before they are uploaded. | |
| numpy only at import; ttnn is imported inside the device functions. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any, Optional, Tuple | |
| import numpy as np | |
| __all__ = [ | |
| "check_index", | |
| "gather_rows", | |
| "gather_rows_numpy", | |
| "scatter_rows", | |
| "scatter_rows_numpy", | |
| "with_zero_rows", | |
| ] | |
| def check_index(index: Any, *, num_rows: int, sentinel: Optional[int] = None) -> np.ndarray: | |
| """Validate a host gather index and return it as a contiguous ``uint32`` array of shape ``[1, N]``. | |
| ``num_rows`` is the number of real source rows; valid entries are ``0 <= i < num_rows`` or ``i == sentinel`` | |
| (the zero row, conventionally ``num_rows``). Raises ``ValueError`` for anything else (negative, too large, | |
| non-integral, non-finite), because an out-of-range index makes the device read outside the table.""" | |
| a = np.asarray(index) | |
| if a.dtype.kind == "f": | |
| if not np.all(np.isfinite(a)) or np.any(a != np.floor(a)): | |
| raise ValueError("gather index holds non-integral or non-finite values") | |
| elif a.dtype.kind not in "iu": | |
| raise ValueError(f"gather index must be integral, got dtype {a.dtype}") | |
| flat = a.reshape(-1).astype(np.int64) | |
| valid = (flat >= 0) & (flat < int(num_rows)) | |
| if sentinel is not None: | |
| valid |= flat == int(sentinel) | |
| bad = ~valid | |
| if np.any(bad): | |
| i = int(np.flatnonzero(bad)[0]) | |
| raise ValueError(f"gather index [{i}] = {int(flat[i])} is outside [0, {num_rows})" | |
| + ("" if sentinel is None else f" and is not the sentinel {sentinel}")) | |
| return np.ascontiguousarray(flat.astype(np.uint32).reshape(1, -1)) | |
| def gather_rows_numpy(index: Any, table: Any) -> np.ndarray: | |
| """Host oracle: ``out[n] = table[index[n]]`` (``table`` ``[V, D]``).""" | |
| t = np.asarray(table) | |
| return t.reshape(-1, t.shape[-1])[np.asarray(index).reshape(-1).astype(np.int64)] | |
| def scatter_rows_numpy(rows: Any, index: Any, *, sentinel: int) -> np.ndarray: | |
| """Host oracle of :func:`scatter_rows`: ``rows`` ``[P, D]`` with a zero row appended at ``sentinel`` | |
| (``sentinel >= P``; rows between ``P`` and ``sentinel`` are zero too), gathered by ``index``.""" | |
| r = np.asarray(rows, dtype=np.float32) | |
| r = r.reshape(-1, r.shape[-1]) | |
| table = np.zeros((max(int(sentinel) + 1, r.shape[0]), r.shape[1]), np.float32) | |
| table[: r.shape[0]] = r | |
| return gather_rows_numpy(index, table) | |
| def _rm(t: Any): | |
| import ttnn | |
| return ttnn.to_layout(t, ttnn.ROW_MAJOR_LAYOUT) if t.layout != ttnn.ROW_MAJOR_LAYOUT else t | |
| def with_zero_rows(rows: Any, count: int = 1): | |
| """A ROW_MAJOR gather table ``[1, 1, P + count, D]``: the device rows ``rows`` (``[1, 1, P, D]``, TILE or | |
| ROW_MAJOR, bf16) followed by ``count`` zero rows (the sentinel rows of :func:`scatter_rows`). One ``ttnn.pad`` | |
| on the device (plus an untilize for a TILE input); traceable.""" | |
| import ttnn | |
| if count < 1: | |
| raise ValueError("count must be >= 1") | |
| shape = tuple(int(s) for s in rows.shape) | |
| if len(shape) != 4 or shape[0] != 1 or shape[1] != 1: | |
| raise ValueError(f"rows must be [1, 1, P, D], got {shape}") | |
| rm = _rm(rows) | |
| return ttnn.pad(rm, [(0, 0), (0, 0), (0, int(count)), (0, 0)], 0.0) | |
| def gather_rows(index: Any, table: Any, *, sentinel: Optional[int] = None, layout: str = "tile", | |
| memory_config: Any = None): | |
| """``out[0, 0, n] = table[0, 0, index[0, n]]`` on the device -> ``[1, 1, N, D]``. | |
| ``index``: UINT32 ROW_MAJOR ``[1, N]`` device tensor; ``table``: bf16 ROW_MAJOR ``[1, 1, V, D]`` (a TILE table | |
| is untilized by ttnn first: one extra program). ``sentinel``: the index of an all-zero table row; given, the | |
| call is P4's fast PADDED form (the table must really hold zeros there). ``layout="tile"`` (default) writes | |
| the tilized output directly when ``N % 32 == 0`` and ``D % 32 == 0``.""" | |
| import ttnn | |
| lay = ttnn.TILE_LAYOUT if layout == "tile" else ttnn.ROW_MAJOR_LAYOUT | |
| kw = {"layout": lay} | |
| if sentinel is not None: | |
| kw.update(padding_idx=int(sentinel), embeddings_type=ttnn.EmbeddingsType.PADDED) | |
| if memory_config is not None: | |
| kw["memory_config"] = memory_config | |
| out = ttnn.embedding(index, table, **kw) # [1, N, D] | |
| n, d = int(out.shape[-2]), int(out.shape[-1]) | |
| return ttnn.reshape(out, (1, 1, n, d)) | |
| def scatter_rows(rows: Any, index: Any, *, sentinel: Optional[int] = None, layout: str = "tile", | |
| memory_config: Any = None) -> Tuple[Any, Any]: | |
| """Gather-form scatter: ``canvas[n] = rows[index[n]]``, or zeros where ``index[n] == sentinel``. | |
| ``rows``: ``[1, 1, P, D]`` device rows (TILE or ROW_MAJOR bf16); ``sentinel`` defaults to ``P`` (the zero | |
| row appended by :func:`with_zero_rows`). Returns ``(canvas, table)``: the ``[1, 1, N, D]`` canvas (TILE by | |
| default, e.g. the NHWC input of a conv) and the table, which the caller may deallocate.""" | |
| p = int(rows.shape[-2]) | |
| sentinel = p if sentinel is None else int(sentinel) | |
| if sentinel < p: | |
| raise ValueError(f"sentinel {sentinel} must be >= the number of source rows {p}") | |
| table = with_zero_rows(rows, sentinel - p + 1) | |
| return gather_rows(index, table, sentinel=sentinel, layout=layout, memory_config=memory_config), table | |