Download code/models/experimental/gr00t/tt/megakernel/descriptors.py from changh95/GR00T-N1.7-3B-p150: direct link, hf CLI and curl.
- Browser
- Download file 47.3 kB
-
https://huggingface.co/changh95/GR00T-N1.7-3B-p150/resolve/main/code/models/experimental/gr00t/tt/megakernel/descriptors.py
- Command line
-
hf download hf://changh95/GR00T-N1.7-3B-p150/code/models/experimental/gr00t/tt/megakernel/descriptors.py
-
curl -L -o descriptors.py https://huggingface.co/changh95/GR00T-N1.7-3B-p150/resolve/main/code/models/experimental/gr00t/tt/megakernel/descriptors.py
47.3 kB
| # SPDX-FileCopyrightText: © 2026 Tenstorrent USA, Inc. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| """``UnifiedKernelDescriptor`` / CB / semaphore builders of the DiT megakernel (IMPLEMENTATION_PLAN.md §5.1, §5.5, §5.6). | |
| One ``ttnn.ProgramDescriptor`` over the whole ``(0,0)-(10,9)`` rectangle from one source ``dit_kernel.cpp``: | |
| * **3 role flags as the only per-core compile-time args** (``is_reader`` / ``is_hub`` / ``is_compute``) -> at most 4 | |
| kernel groups (readers, hub, compute, spare) and one binary per variant (JIT 0.4-1.8 s, mb3 F6); | |
| * **structural constants as named CTAs shared by all cores** (``n_blocks``, ``m_logical``, ``l_*_pad``, ``a_pad``, | |
| ``h``, ``euler_fp32``, ``n_steps``, ``reader_mode``, ``{text,image,all}_mask_present``, CB ids/sizes, semaphore | |
| ids, ...); | |
| * **per-core runtime args** ``(compute_idx, bank_id, vc, head_idx, group)`` (``0xFFFF`` = n/a) on every core and RISC; | |
| * **common runtime args**: the io buffer addresses, the step index and the arena offsets table (<= 341 words); | |
| * CBs per plan §5.5 (ids <= 15 < 64) and the semaphores of the §5.6 protocol. | |
| Variants (plan §5.1): ``block`` (the first ``n_blocks`` DiT blocks -- unit tests), ``step`` (blocks + output head; the | |
| decoder / Euler / action encoder run as ttnn ops between 4 launches -- K4 bring-up), ``steps4`` (the whole denoise, | |
| ``PersistentLoop<false>(4)``). | |
| :func:`build_program` with ``dry_run=True`` builds the ttnn-free :class:`ProgramSpec` and returns | |
| :class:`DescriptorStats`; without it, the spec is materialised into ``ttnn`` objects (lazy import) and the | |
| ``ttnn.ProgramDescriptor`` is returned. ``ttnn`` is never imported at module level. | |
| **K4 extension (WP-K4, plan §5.4 P1-P12 + "Per step", §5.5 with the K2/K3 CB requests):** :func:`build_program_spec_k4` | |
| builds the spec of the *full* megakernel -- the K1 CBs plus the statistics CBs of the distributed LayerNorm / AdaLN | |
| (``dist_layernorm.hpp``: ``cb_stat`` / ``cb_stat32`` / ``cb_const`` / the row-broadcast AdaLN tiles, and ``cb_h`` = the | |
| own normalised tile the NCRISC gathers), the ``HeadSDPA`` CBs of ``head_sdpa.hpp`` (K3 §3.3), the tail-weight CB | |
| ``cb_tw`` (bf16 forever, plan D4) and the hub's Euler / velocity CBs (``euler_head.hpp``). ``cb_x`` holds only the | |
| core's tile row of the residual stream (``x_rep`` = HEIGHT_SHARDED ``[32, 1536]`` per compute core). It is a | |
| separate path on purpose: the default :func:`build_program_spec` keeps the WP-05 shape (ids <= 15, compute L1 <= 1 MB) | |
| that ``tests/tt/test_mk_cpu.py`` pins; the K4 spec uses ids up to 31 (``NUM_CIRCULAR_BUFFERS``) and a compute-core L1 | |
| of ~1.3 MB (:class:`K4Budget`, checked against the 1,427 KiB usable minus a margin). :func:`materialise` takes the | |
| ``unpack_to_dest_fp32_cbs`` list the fp32 ``copy_tile`` CBs need (K2 §7.2). | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple | |
| from models.experimental.gr00t.tt.megakernel.arena import ( | |
| ArenaLayout, | |
| K_BLOCK_TILES, | |
| KEY_SETS, | |
| N_STEPS, | |
| TILE, | |
| TILE_BYTES, | |
| TILE_ORDERS, | |
| READER_MODES, | |
| ) | |
| from models.experimental.gr00t.tt.megakernel.core_map import Core, CoreMap, Role | |
| VARIANTS: Tuple[str, ...] = ("block", "step", "steps4") | |
| ROLE_FLAGS: Tuple[Tuple[str, Role], ...] = ( | |
| ("is_reader", Role.READER), | |
| ("is_hub", Role.HUB), | |
| ("is_compute", Role.COMPUTE), | |
| ) | |
| KERNEL_SOURCE: str = "models/experimental/gr00t/tt/megakernel/kernels/dit_kernel.cpp" | |
| MAX_COMMON_RT_ARGS: int = 341 | |
| MAX_CBS: int = 64 | |
| MAX_KERNEL_GROUPS: int = 4 | |
| NUM_CIRCULAR_BUFFERS: int = 32 # tt-metal CB ids 0..31 (the K4 spec uses all of them on the compute cores) | |
| #: "compute-core L1 budget <= 1,000 KB" (WP-05 acceptance; plan §5.5: 850-980 KB of 1,427 KiB usable) | |
| L1_COMPUTE_BUDGET_BYTES: int = 1_000_000 | |
| L1_USABLE_BYTES: int = 1427 * 1024 | |
| NA: int = 0xFFFF | |
| BF16, FP32 = "bf16", "fp32" | |
| #: CB ids (plan §5.5). Reader-only / hub-only CBs reuse ids 14/15 on disjoint core ranges. | |
| CB_IDS: Mapping[str, int] = { | |
| "cb_x": 0, # residual [64,1536] bf16, tensor-backed (x_rep, HEIGHT_SHARDED replicated on the compute cores) | |
| "cb_a": 1, # in0 arena A (same L1 address on all 110 cores; mcast target), tensor-backed (a_rep) | |
| "cb_w": 2, # weight ring (K-blocks relayed by the readers) | |
| "cb_mm0": 3, # matmul output ping | |
| "cb_mm1": 4, # matmul output pong | |
| "cb_o": 5, # attention output / gather source | |
| "cb_bias0": 6, | |
| "cb_bias1": 7, | |
| "cb_adaln": 8, # 2 AdaLN rows (raw) + tilised staging | |
| "cb_mask": 9, # self mask 8 KB + cross chunk ring 2 x 8 KB | |
| "cb_k": 10, | |
| "cb_v": 11, | |
| "cb_kv_ring": 12, # cross K/V 64-key chunk ring 2 x 16 KB | |
| "cb_scores": 13, # fp32 scores / probs scratch 2 x 16 KB | |
| "cb_relay": 14, # readers only: landed K-blocks (NumBuffers 3 x 2) | |
| "cb_act": 14, # hub only: action buffer [64, A_pad] fp32 | |
| "cb_sa": 15, # hub only: sa_embs staging [64, 1536] bf16 | |
| } | |
| SEM_IDS: Mapping[str, int] = { | |
| "sem_w": 0, # K-block landed in cb_w (reader BRISC -> compute NCRISC) | |
| "sem_credit": 1, # cb_w slot credit (compute -> reader) | |
| "sem_kv": 2, # K_h/V_h arrived (== 2) | |
| "sem_mcast_sender": 3, | |
| "sem_mcast_receiver": 4, | |
| "sem_gather_noc0": 5, | |
| "sem_gather_noc1": 6, | |
| "sem_ring_credit": 7, # ff2 K-chunk ring | |
| "sem_sync": 8, # sync_riscs_enter/exit around CB reconfig | |
| "sem_hub_done": 9, # K4b: hub NCRISC -> hub BRISC, rounds whose NoC-1 multicast part is flushed (allgather.hpp) | |
| "sem_ready_odd": 10, # K4b: hub receivers-ready count of the odd ff2 ring chunks (lookahead credits, dit_kernel P11) | |
| "sem_mcast_receiver1": 11, # K4b: the receivers' round counter of hub 1's multicasts (hub 0: sem_mcast_receiver) | |
| "sem_land1": 12, # K4b: hub 1 publishes its landing-ring base here (hub 0: sem_sync) | |
| } | |
| TILE_BYTES_BF16 = TILE_BYTES["bf16"] | |
| TILE_BYTES_FP32 = TILE * TILE * 4 | |
| class CBBudget: | |
| """CB sizes in bytes (plan §5.5). ``cb_w`` defaults to ``cb_w_slots[dtype]`` K-blocks of the largest streamed | |
| slice (15 x 17,408 B = 255 KiB bfp8 / 9 x 34,816 B = 306 KiB bf16: >= 1 op slice of lookahead, 104 / 197 KB per | |
| core for ``Wqkv``); an explicit ``cb_w`` must be a multiple of the K-block bytes.""" | |
| cb_x: int = 64 * 1536 * 2 # 196,608 | |
| cb_a: int = 64 * 2048 * 2 # 262,144 = max(h 192 KB, attn 256 KB, g 192 KB, ff2 ring 2 x 131 KB) | |
| cb_w: Optional[int] = None | |
| cb_w_slots: Mapping[str, int] = field(default_factory=lambda: {"bfp8_b": 15, "bf16": 9}) | |
| cb_mm: int = 4 * TILE_BYTES_BF16 # [64,64] bf16 (x2) | |
| cb_o: int = 4 * TILE_BYTES_BF16 | |
| cb_bias: int = 2 * TILE_BYTES_BF16 # ct <= 2 tiles (x2) | |
| cb_adaln: int = 16 * 1024 | |
| cb_mask: int = 4 * TILE_BYTES_BF16 + 2 * 4 * TILE_BYTES_BF16 # self [64,64] + 2 cross chunks [64,64] | |
| cb_k: int = 4 * TILE_BYTES_BF16 | |
| cb_v: int = 4 * TILE_BYTES_BF16 | |
| cb_kv_ring: int = 2 * 8 * TILE_BYTES_BF16 # 2 x [64,128] bf16 (K^T and V of a 64-key chunk) | |
| cb_scores: int = 2 * 4 * TILE_BYTES_FP32 # 2 x [64,64] fp32 | |
| cb_relay_slots: int = 6 # NumBuffers 3 (trid ring) x 2 (relay lookahead) | |
| cb_sa: int = 64 * 1536 * 2 | |
| l1_budget_compute: int = L1_COMPUTE_BUDGET_BYTES | |
| def cb_w_bytes(self, dtype_stream: str, k_block_bytes: int) -> int: | |
| if self.cb_w is not None: | |
| if self.cb_w <= 0 or self.cb_w % k_block_bytes: | |
| raise ValueError(f"cb_w {self.cb_w} is not a positive multiple of the K-block ({k_block_bytes} B)") | |
| return int(self.cb_w) | |
| try: | |
| return int(self.cb_w_slots[dtype_stream]) * k_block_bytes | |
| except KeyError: | |
| raise KeyError(f"no default cb_w slot count for stream dtype {dtype_stream!r}") from None | |
| class CBSpec: | |
| """One ``CBDescriptor`` to build: ``role_set`` names a :meth:`CoreMap.to_core_range_sets` key.""" | |
| name: str | |
| cb_id: int | |
| role_set: str | |
| total_bytes: int | |
| page_bytes: int | |
| data_format: str | |
| tensor_backed: Optional[str] = None # io name of the backing sharded tensor | |
| class SemSpec: | |
| name: str | |
| sem_id: int | |
| initial_value: int = 0 | |
| class DescriptorStats: | |
| """What the dry run reports (WP-05 acceptance: <= 64 CBs, <= 341 common RT args, <= 4 kernel groups, compute | |
| L1 <= 1,000 KB).""" | |
| n_cbs: int | |
| n_common_rt_args: int | |
| n_kernel_groups: int | |
| l1_bytes_compute: int | |
| l1_bytes_reader: int | |
| l1_bytes_hub: int | |
| n_named_ctas: int | |
| n_semaphores: int | |
| n_io_tensors: int | |
| n_per_core_rt_args: int | |
| def check(self, budget: CBBudget) -> None: | |
| """Raise if any acceptance limit is exceeded.""" | |
| if self.n_cbs > MAX_CBS: | |
| raise ValueError(f"{self.n_cbs} CBs > {MAX_CBS}") | |
| if self.n_common_rt_args > MAX_COMMON_RT_ARGS: | |
| raise ValueError(f"{self.n_common_rt_args} common RT args > {MAX_COMMON_RT_ARGS}") | |
| if self.n_kernel_groups > MAX_KERNEL_GROUPS: | |
| raise ValueError(f"{self.n_kernel_groups} kernel groups > {MAX_KERNEL_GROUPS}") | |
| if self.l1_bytes_compute > budget.l1_budget_compute: | |
| raise ValueError(f"compute L1 {self.l1_bytes_compute} B > {budget.l1_budget_compute}") | |
| for role, b in (("reader", self.l1_bytes_reader), ("hub", self.l1_bytes_hub)): | |
| if b > L1_USABLE_BYTES: | |
| raise ValueError(f"{role} L1 {b} B > usable {L1_USABLE_BYTES}") | |
| class IoSlot: | |
| """One entry of the io tensor list: ``required`` tensors must be present (non-``None``) when materialising.""" | |
| name: str | |
| required: bool | |
| description: str | |
| def mask_presence(layout: ArenaLayout) -> Dict[str, bool]: | |
| """Which cross masks the kernel of ``layout`` reads: ``{"text", "image", "all"}`` -> present (static-shape-plan.md | |
| §6.2; mirrors ``DeviceLayout.mask_presence`` in ``tt/layout.py``). | |
| * ``text`` / ``all``: per-call masks, present whenever a block of the layout uses that key set; | |
| * ``image``: the constant pad-key mask of the ``idx % 4 == 2`` blocks, present when the layout uses the ``image`` | |
| key set **and** ``L_img_real != L_img_pad`` (``cross_image_keys = (real, padded)``): n16 gr1 ``(81, 96)`` has 15 | |
| zero-pad key columns that must be masked, n17 droid ``(448, 448)`` has none and | |
| ``DeviceLayout.cross_image_mask()`` returns ``None`` there. | |
| """ | |
| used = set(layout.key_sets) | |
| keys = layout.layout_cfg.cross_image_keys | |
| if "image" in used and keys is None: | |
| raise ValueError(f"layout {layout.layout_cfg.name!r} of {layout.version} uses the image key set without keys") | |
| return { | |
| "text": "text" in used, | |
| "image": "image" in used and int(keys[0]) != int(keys[1]), | |
| "all": "all" in used, | |
| } | |
| def io_spec(variant: str, layout: ArenaLayout) -> List[IoSlot]: | |
| """The io tensors of ``variant`` in the order their addresses appear in the common runtime args (plan §5.1). | |
| The three cross masks are required exactly when :func:`mask_presence` says the kernel reads them; a required tensor | |
| that is missing makes :func:`build_program_spec` raise before any address is emitted (a silent zero address for the | |
| image mask would let the pad keys of the ``idx % 4 == 2`` blocks leak into the softmax). | |
| """ | |
| if variant not in VARIANTS: | |
| raise ValueError(f"variant must be in {VARIANTS}, got {variant!r}") | |
| masks = mask_presence(layout) | |
| slots = [ | |
| IoSlot("x_rep", True, "residual [64,1536] bf16 HEIGHT_SHARDED replicated on the compute cores (cb_x)"), | |
| IoSlot("a_rep", True, "in0 arena [64,2048] bf16 HEIGHT_SHARDED on all 110 cores (cb_a, mcast target)"), | |
| IoSlot("arena_stream", True, "block projections, WIDTH_SHARDED over the 8 DRAM banks"), | |
| IoSlot("arena_small", variant != "block", "tail ops (proj_out_2, decoder, encoder), WIDTH_SHARDED"), | |
| IoSlot("adaln_rows", True, "AdaLN table [4*(2*n_blocks+2), 1536] bf16 ROW_MAJOR"), | |
| IoSlot("bias_rows", True, "bias table [4*n_blocks+4, 6144] bf16 ROW_MAJOR"), | |
| IoSlot("b2_step", variant == "steps4", "[4,1536] bf16 action-encoder step biases"), | |
| IoSlot("b3_pos_rows", variant == "steps4", "[64,1536] bf16 (b3 + position rows at M-H..M)"), | |
| IoSlot("future_tokens", variant == "steps4" and layout.n_future_tokens > 0, "[F,1536] bf16 (n15)"), | |
| IoSlot("self_mask", True, "[1,1,64,64] bf16 additive self mask"), | |
| IoSlot("text_mask", masks["text"], "[1,1,64,L_txt_pad] bf16 per-call text mask"), | |
| IoSlot( | |
| "image_mask", | |
| masks["image"], | |
| "[1,1,64,L_img_pad] bf16 constant image pad-key mask (absent when L_img_real == L_img_pad, e.g. n17 droid)", | |
| ), | |
| IoSlot("all_mask", masks["all"], "[1,1,64,L_all_pad] bf16 per-call all-token mask (n15)"), | |
| ] | |
| for i in range(layout.n_blocks): | |
| if layout.is_cross(i): | |
| slots.append(IoSlot(f"kv_k_{i}", True, f"hoisted K of cross block {i} [1,32,L_pad,64] bf16")) | |
| slots.append(IoSlot(f"kv_v_{i}", True, f"hoisted V of cross block {i} [1,32,L_pad,64] bf16")) | |
| if variant == "step": | |
| slots.append(IoSlot("y_out", True, "output head result [64,1024] bf16")) | |
| if variant == "steps4": | |
| slots += [ | |
| IoSlot("x_t", True, "Euler state [64,A_pad] (fp32 for n15, bf16 otherwise); noise placed at rows M-H..M"), | |
| IoSlot("state_features", True, "[1,1536] bf16 (row 0 of sa_embs)"), | |
| IoSlot("action_pred", True, "[64,A_pad] output"), | |
| ] | |
| return slots | |
| class ProgramSpec: | |
| """ttnn-free description of the program: everything :func:`materialise` needs plus the statistics.""" | |
| variant: str | |
| kernel_source: str | |
| core_map: CoreMap | |
| layout: ArenaLayout | |
| budget: CBBudget | |
| named_ctas: List[Tuple[str, int]] | |
| role_flags: List[Tuple[str, Any]] # (flag CTA, Role | core-set name) | |
| per_core_rt_args: Dict[Core, List[int]] | |
| common_rt_names: List[str] | |
| common_rt_args: List[int] | |
| cbs: List[CBSpec] | |
| semaphores: List[SemSpec] | |
| io: List[IoSlot] | |
| l1_bytes: Dict[str, int] | |
| def kernel_groups(self) -> List[Tuple[int, ...]]: | |
| """Distinct role-flag combinations present on the grid (one kernel group each). A flag maps to a | |
| :class:`Role` or (K4b) to a core set name: ``"hubs"`` = the hub and :attr:`CoreMap.hub2`.""" | |
| cm = self.core_map | |
| hubs = {cm.hub, cm.hub2} if cm.spare else {cm.hub} | |
| def has(core: Core, target: Any) -> bool: | |
| if target == "hubs": | |
| return core in hubs | |
| return cm.role_of(core) is target | |
| combos = [] | |
| for core in cm.all_cores(): | |
| key = tuple(1 if has(core, r) else 0 for _, r in self.role_flags) | |
| if key not in combos: | |
| combos.append(key) | |
| return combos | |
| def stats(self) -> DescriptorStats: | |
| return DescriptorStats( | |
| n_cbs=len(self.cbs), | |
| n_common_rt_args=len(self.common_rt_args), | |
| n_kernel_groups=len(self.kernel_groups()), | |
| l1_bytes_compute=self.l1_bytes["compute"], | |
| l1_bytes_reader=self.l1_bytes["reader"], | |
| l1_bytes_hub=self.l1_bytes["hub"], | |
| n_named_ctas=len(self.named_ctas), | |
| n_semaphores=len(self.semaphores), | |
| n_io_tensors=len(self.io), | |
| n_per_core_rt_args=len(next(iter(self.per_core_rt_args.values()))), | |
| ) | |
| def io_order(self) -> List[str]: | |
| """Names in the order ``dit_program.py`` must pass the tensors to ``ttnn.generic_op``.""" | |
| return [s.name for s in self.io] | |
| def manifest(self) -> Dict[str, Any]: | |
| st = self.stats() | |
| return { | |
| "variant": self.variant, | |
| "kernel_source": self.kernel_source, | |
| "stats": st.__dict__, | |
| "named_ctas": dict(self.named_ctas), | |
| "common_rt_names": self.common_rt_names, | |
| "cbs": [cb.__dict__ for cb in self.cbs], | |
| "semaphores": [s.__dict__ for s in self.semaphores], | |
| "io": [s.name for s in self.io], | |
| "l1_bytes": dict(self.l1_bytes), | |
| } | |
| def _cb_specs(layout: ArenaLayout, budget: CBBudget, core_map: CoreMap) -> Tuple[List[CBSpec], Dict[str, int]]: | |
| """CBs of plan §5.5 and the per-role L1 totals.""" | |
| k_block_bytes = max(op.k_block_bytes for op in layout.stream_ops("stream")) | |
| cb_w = budget.cb_w_bytes(layout.dtype_stream, k_block_bytes) | |
| stream_tile = TILE_BYTES[layout.dtype_stream] | |
| cb_act = layout.m_pad * layout.a_pad * 4 | |
| compute_cbs = [ | |
| CBSpec("cb_x", CB_IDS["cb_x"], "compute", budget.cb_x, TILE_BYTES_BF16, BF16, tensor_backed="x_rep"), | |
| CBSpec("cb_a", CB_IDS["cb_a"], "all", budget.cb_a, TILE_BYTES_BF16, BF16, tensor_backed="a_rep"), | |
| CBSpec("cb_w", CB_IDS["cb_w"], "compute", cb_w, stream_tile, layout.dtype_stream), | |
| CBSpec("cb_mm0", CB_IDS["cb_mm0"], "compute", budget.cb_mm, TILE_BYTES_BF16, BF16), | |
| CBSpec("cb_mm1", CB_IDS["cb_mm1"], "compute", budget.cb_mm, TILE_BYTES_BF16, BF16), | |
| CBSpec("cb_o", CB_IDS["cb_o"], "compute", budget.cb_o, TILE_BYTES_BF16, BF16), | |
| CBSpec("cb_bias0", CB_IDS["cb_bias0"], "compute", budget.cb_bias, TILE_BYTES_BF16, BF16), | |
| CBSpec("cb_bias1", CB_IDS["cb_bias1"], "compute", budget.cb_bias, TILE_BYTES_BF16, BF16), | |
| CBSpec("cb_adaln", CB_IDS["cb_adaln"], "compute", budget.cb_adaln, TILE_BYTES_BF16, BF16), | |
| CBSpec("cb_mask", CB_IDS["cb_mask"], "compute", budget.cb_mask, TILE_BYTES_BF16, BF16), | |
| CBSpec("cb_k", CB_IDS["cb_k"], "compute", budget.cb_k, TILE_BYTES_BF16, BF16), | |
| CBSpec("cb_v", CB_IDS["cb_v"], "compute", budget.cb_v, TILE_BYTES_BF16, BF16), | |
| CBSpec("cb_kv_ring", CB_IDS["cb_kv_ring"], "compute", budget.cb_kv_ring, TILE_BYTES_BF16, BF16), | |
| CBSpec("cb_scores", CB_IDS["cb_scores"], "compute", budget.cb_scores, TILE_BYTES_FP32, FP32), | |
| ] | |
| reader_cbs = [ | |
| CBSpec( | |
| "cb_relay", | |
| CB_IDS["cb_relay"], | |
| "readers", | |
| budget.cb_relay_slots * k_block_bytes, | |
| stream_tile, | |
| layout.dtype_stream, | |
| ) | |
| ] | |
| hub_cbs = [ | |
| CBSpec("cb_act", CB_IDS["cb_act"], "hub", cb_act, TILE_BYTES_FP32, FP32), | |
| CBSpec("cb_sa", CB_IDS["cb_sa"], "hub", budget.cb_sa, TILE_BYTES_BF16, BF16), | |
| ] | |
| cbs = compute_cbs + reader_cbs + hub_cbs | |
| for cb in cbs: | |
| if cb.total_bytes <= 0 or cb.total_bytes % cb.page_bytes: | |
| raise ValueError( | |
| f"{cb.name}: total {cb.total_bytes} is not a positive multiple of the page {cb.page_bytes}" | |
| ) | |
| ids_by_set: Dict[str, List[int]] = {} | |
| for cb in cbs: | |
| for role_set in (("compute", "readers", "hub", "spare") if cb.role_set == "all" else (cb.role_set,)): | |
| ids = ids_by_set.setdefault(role_set, []) | |
| if cb.cb_id in ids: | |
| raise ValueError(f"CB id {cb.cb_id} used twice on {role_set}") | |
| ids.append(cb.cb_id) | |
| on = lambda s: sum(cb.total_bytes for cb in cbs if cb.role_set in ("all", s)) # noqa: E731 | |
| l1 = {"compute": on("compute"), "reader": on("readers"), "hub": on("hub"), "spare": on("spare")} | |
| if core_map.n_compute and l1["compute"] > L1_USABLE_BYTES: | |
| raise ValueError(f"compute L1 {l1['compute']} B exceeds usable {L1_USABLE_BYTES}") | |
| return cbs, l1 | |
| def _named_ctas( | |
| layout: ArenaLayout, | |
| core_map: CoreMap, | |
| variant: str, | |
| budget: CBBudget, | |
| cbs: Sequence[CBSpec], | |
| n_io: int, | |
| cb_ids: Optional[Mapping[str, int]] = None, | |
| ) -> List[Tuple[str, int]]: | |
| """Structural constants shared by all cores (plan §5.1), incl. the :func:`mask_presence` flags. ``cb_ids`` (default | |
| :data:`CB_IDS`) is the name -> id map emitted as named CTAs (the K4 spec passes :data:`K4_CB_IDS`).""" | |
| cfg = layout.cfg | |
| masks = mask_presence(layout) | |
| (x0, y0), (x1, y1) = core_map.mcast_rect | |
| cb_bytes = {cb.name: cb.total_bytes for cb in cbs} | |
| ctas: List[Tuple[str, int]] = [ | |
| ("variant", VARIANTS.index(variant)), | |
| ("n_steps", N_STEPS if variant == "steps4" else 1), | |
| ("n_blocks", layout.n_blocks), | |
| ("m_logical", layout.m_logical), | |
| ("m_pad", layout.m_pad), | |
| ("l_txt_pad", layout.l_pad.get("text", 0)), | |
| ("l_img_pad", layout.l_pad.get("image", 0)), | |
| ("l_all_pad", layout.l_pad.get("all", 0)), | |
| ("a_pad", layout.a_pad), | |
| ("h", layout.h), | |
| ("action_row0", layout.action_rows[0]), | |
| ("n_future_tokens", layout.n_future_tokens), | |
| ("euler_fp32", 1 if cfg.sampler.euler_dtype == "fp32_actions_bf16_velocity" else 0), | |
| ("reader_mode", READER_MODES.index(layout.reader_mode)), | |
| ("tile_order", TILE_ORDERS.index(layout.tile_order)), | |
| ("fold_attn_scale", 1 if layout.fold_attn_scale else 0), | |
| ("text_mask_present", 1 if masks["text"] else 0), | |
| ("image_mask_present", 1 if masks["image"] else 0), | |
| ("all_mask_present", 1 if masks["all"] else 0), | |
| ("width", layout.width), | |
| ("n_heads", layout.n_heads), | |
| ("head_dim_pad", layout.head_dim_pad), | |
| ("ff_dim", layout.ff_dim), | |
| ("out_dim", layout.out_dim), | |
| ("hidden", layout.hidden), | |
| ("stream_tile_bytes", TILE_BYTES[layout.dtype_stream]), | |
| ("small_tile_bytes", TILE_BYTES[layout.dtype_small]), | |
| ("k_block_tiles", K_BLOCK_TILES), | |
| ("n_compute", core_map.n_compute), | |
| ("n_readers", core_map.n_readers), | |
| ("n_spare", len(core_map.spare)), | |
| ("grid_x", core_map.grid[0]), | |
| ("grid_y", core_map.grid[1]), | |
| ("hub_x", core_map.hub[0]), | |
| ("hub_y", core_map.hub[1]), | |
| ("mcast_x0", x0), | |
| ("mcast_y0", y0), | |
| ("mcast_x1", x1), | |
| ("mcast_y1", y1), | |
| ("cb_relay_slots", budget.cb_relay_slots), | |
| ("n_io_tensors", n_io), | |
| ("n_key_sets", len(KEY_SETS)), | |
| ] | |
| ids = CB_IDS if cb_ids is None else cb_ids | |
| ctas += [(name, cb_id) for name, cb_id in ids.items()] | |
| ctas += [(f"{name}_bytes", cb_bytes[name]) for name in cb_bytes] | |
| ctas += [(name, sem_id) for name, sem_id in SEM_IDS.items()] | |
| seen = set() | |
| for name, value in ctas: | |
| if name in seen: | |
| raise ValueError(f"duplicate named CTA {name}") | |
| seen.add(name) | |
| if not 0 <= int(value) < 2**32: | |
| raise ValueError(f"named CTA {name}={value} does not fit uint32") | |
| return ctas | |
| def _address_of(io: Mapping[str, Any], slot: IoSlot, dry_run: bool) -> int: | |
| t = io.get(slot.name) | |
| if t is None: | |
| if slot.required and not dry_run: | |
| raise KeyError(f"io tensor {slot.name!r} is required for this variant/version but missing or None") | |
| return 0 | |
| if dry_run: | |
| return 0 | |
| return int(t.buffer_address()) | |
| def build_program_spec( | |
| core_map: CoreMap, | |
| arena_layout: ArenaLayout, | |
| variant: str, | |
| io: Mapping[str, Any], | |
| cb_budget: Optional[CBBudget] = None, | |
| *, | |
| dry_run: bool = False, | |
| step_index: int = 0, | |
| kernel_source: str = KERNEL_SOURCE, | |
| ) -> ProgramSpec: | |
| """The ttnn-free :class:`ProgramSpec` of ``variant`` (see module docstring); ``dry_run`` skips address reads.""" | |
| if variant not in VARIANTS: | |
| raise ValueError(f"variant must be in {VARIANTS}, got {variant!r}") | |
| if not 0 <= step_index < N_STEPS: | |
| raise ValueError(f"step_index {step_index} out of range") | |
| budget = cb_budget if cb_budget is not None else CBBudget() | |
| slots = io_spec(variant, arena_layout) | |
| unknown = set(io) - {s.name for s in slots} | |
| if unknown: | |
| raise KeyError(f"io has tensors unknown to variant {variant!r}: {sorted(unknown)}") | |
| cbs, l1 = _cb_specs(arena_layout, budget, core_map) | |
| ctas = _named_ctas(arena_layout, core_map, variant, budget, cbs, len(slots)) | |
| names = [f"addr_{s.name}" for s in slots] + ["step_index", "offsets_table_len"] | |
| args = [_address_of(io, s, dry_run) for s in slots] + [step_index] | |
| offsets = arena_layout.offsets_table() | |
| args.append(len(offsets)) | |
| args += offsets | |
| names += [f"offsets[{i}]" for i in range(len(offsets))] | |
| ctas.append(("offsets_table_base", len(slots) + 2)) | |
| ctas.append(("common_rt_args_len", len(args))) | |
| if len(args) > MAX_COMMON_RT_ARGS: | |
| raise ValueError(f"{len(args)} common runtime args exceed {MAX_COMMON_RT_ARGS}") | |
| per_core = {core: list(a) for core, a in core_map.per_core_args().items()} | |
| sems = [SemSpec(name, sem_id) for name, sem_id in SEM_IDS.items()] | |
| return ProgramSpec( | |
| variant=variant, | |
| kernel_source=kernel_source, | |
| core_map=core_map, | |
| layout=arena_layout, | |
| budget=budget, | |
| named_ctas=ctas, | |
| role_flags=list(ROLE_FLAGS), | |
| per_core_rt_args=per_core, | |
| common_rt_names=names, | |
| common_rt_args=args, | |
| cbs=cbs, | |
| semaphores=sems, | |
| io=slots, | |
| l1_bytes=l1, | |
| ) | |
| def materialise(spec: ProgramSpec, io: Mapping[str, Any], unpack_to_dest_fp32_cbs: Sequence[int] = ()) -> Any: | |
| """Turn a :class:`ProgramSpec` into a ``ttnn.ProgramDescriptor`` (lazy ttnn; needs the device tensors of ``io``). | |
| ``unpack_to_dest_fp32_cbs``: CB ids whose ``copy_tile`` must land exact fp32 in DST | |
| (``ComputeConfigDescriptor.unpack_to_dest_mode[cb] = UnpackToDestFp32``; row_layernorm.hpp ``cb_stat32``, | |
| euler_head.hpp ``cb_act``; K2 §7.2). The mode list is program-wide (indexed by CB id on every core), so the K4 | |
| spec gives those CBs an id no other-format CB uses on another role (:data:`K4_CB_IDS` / :data:`K4_HUB_CB_IDS`). | |
| """ | |
| import ttnn # lazy | |
| from models.demos.deepseek_v3_b1.unified_kernel_descriptor import ( | |
| PerCoreRuntimeArgsDescriptor, | |
| UnifiedCompileTimeCoreDescriptor, | |
| UnifiedKernelDescriptor, | |
| ) | |
| cm = spec.core_map | |
| crs = cm.to_core_range_sets() | |
| dt = {BF16: ttnn.bfloat16, FP32: ttnn.float32, "bfp8_b": ttnn.bfloat8_b} | |
| tile_desc = ttnn.TileDescriptor(ttnn.Tile([TILE, TILE])) | |
| cbs = [] | |
| for cb in spec.cbs: | |
| if cb.tensor_backed is not None: | |
| t = io.get(cb.tensor_backed) | |
| if t is None: | |
| raise KeyError(f"CB {cb.name} is backed by io tensor {cb.tensor_backed!r}, which is missing") | |
| cbs.append(ttnn.cb_descriptor_from_sharded_tensor(cb.cb_id, t)) | |
| continue | |
| fmt = ttnn.CBFormatDescriptor( | |
| buffer_index=cb.cb_id, data_format=dt[cb.data_format], page_size=cb.page_bytes, tile=tile_desc | |
| ) | |
| cbs.append(ttnn.CBDescriptor(total_size=cb.total_bytes, core_ranges=crs[cb.role_set], format_descriptors=[fmt])) | |
| sems = [ | |
| ttnn.SemaphoreDescriptor(id=s.sem_id, core_ranges=crs["all"], initial_value=s.initial_value) | |
| for s in spec.semaphores | |
| ] | |
| device = io["x_rep"].device() | |
| (x0, y0), (x1, y1) = cm.mcast_rect | |
| noc_start = device.worker_core_from_logical_core(ttnn.CoreCoord(x0, y0)) | |
| noc_end = device.worker_core_from_logical_core(ttnn.CoreCoord(x1, y1)) | |
| hub_noc = device.worker_core_from_logical_core(ttnn.CoreCoord(*cm.hub)) | |
| hub1_noc = device.worker_core_from_logical_core(ttnn.CoreCoord(*(cm.hub2 if cm.spare else cm.hub))) | |
| named = list(spec.named_ctas) + [ | |
| ("mcast_noc_x0", noc_start.x), | |
| ("mcast_noc_y0", noc_start.y), | |
| ("mcast_noc_x1", noc_end.x), | |
| ("mcast_noc_y1", noc_end.y), | |
| ("hub_noc_x", hub_noc.x), | |
| ("hub_noc_y", hub_noc.y), | |
| ("hub1_noc_x", hub1_noc.x), # K4b: the second hub (ff2 chunks 2..5); == the hub in the K1 program | |
| ("hub1_noc_y", hub1_noc.y), | |
| ] | |
| per_core = [(ttnn.CoreCoord(x, y), args) for (x, y), args in spec.per_core_rt_args.items()] | |
| role_sets: Dict[Any, str] = {Role.READER: "readers", Role.HUB: "hub", Role.COMPUTE: "compute", "hubs": "hubs"} | |
| cc = ttnn.ComputeConfigDescriptor( | |
| math_fidelity=ttnn.MathFidelity.HiFi2, fp32_dest_acc_en=True, math_approx_mode=False | |
| ) | |
| if unpack_to_dest_fp32_cbs: | |
| modes = [ttnn.UnpackToDestMode.Default] * MAX_CBS | |
| for cb_id in unpack_to_dest_fp32_cbs: | |
| if not 0 <= int(cb_id) < NUM_CIRCULAR_BUFFERS: | |
| raise ValueError(f"unpack_to_dest_fp32 CB id {cb_id} out of range") | |
| modes[int(cb_id)] = ttnn.UnpackToDestMode.UnpackToDestFp32 | |
| cc.unpack_to_dest_mode = modes | |
| uk = UnifiedKernelDescriptor( | |
| kernel_source=spec.kernel_source, | |
| core_ranges=crs["all"], | |
| ncrisc_named_compile_time_args=named, | |
| brisc_named_compile_time_args=named, | |
| trisc_named_compile_time_args=named, | |
| ncrisc_common_runtime_args=list(spec.common_rt_args), | |
| brisc_common_runtime_args=list(spec.common_rt_args), | |
| trisc_common_runtime_args=list(spec.common_rt_args), | |
| trisc_compute_config=cc, | |
| unified_compile_time_core_descriptors=[ | |
| UnifiedCompileTimeCoreDescriptor( | |
| named_compile_time_arg=flag, core_range=crs[role_sets[role]], value=1, other_value=0 | |
| ) | |
| for flag, role in spec.role_flags | |
| ], | |
| per_core_runtime_args_descriptor=PerCoreRuntimeArgsDescriptor( | |
| ncrisc_args=per_core, brisc_args=per_core, trisc_args=per_core | |
| ), | |
| ) | |
| result = uk.get_kernel_descriptors() | |
| if len(result.groups) > MAX_KERNEL_GROUPS: | |
| raise RuntimeError(f"{len(result.groups)} kernel groups > {MAX_KERNEL_GROUPS}") | |
| return ttnn.ProgramDescriptor(kernels=result.kernels, semaphores=sems, cbs=cbs) | |
| # ==================================================================================================================== | |
| # K4: the full megakernel spec (WP-K4; plan §5.4 P1-P12 + per-step tail, §5.5 + K2 §7.2 / K3 §3.3 CB requests) | |
| # ==================================================================================================================== | |
| #: Compute-core CB ids of the K4 kernel (``dit_kernel.cpp`` / ``ops/*.hpp`` CB structs). Ids 0-3, 6, 7, 13 keep the | |
| #: K1 meaning; 4, 9-12 are re-purposed (K1 never used ``cb_mm1`` / ``cb_kv_ring``); 14 / 15 are free on the compute | |
| #: cores (K1 used them on readers / hub only); 16-31 are new. ``cb_stat32`` (19) is the only compute-core CB read | |
| #: through ``UnpackToDestFp32``; the hub's ``cb_act`` shares id 19 so the program-wide mode list stays consistent. | |
| K4_CB_IDS: Mapping[str, int] = { | |
| "cb_x": 0, # this core's tile row of the residual [32,1536] bf16 (48 tiles), tensor-backed x_rep, updated in place | |
| "cb_a": 1, # in0 arena A (mcast target), tensor-backed a_rep on all 110 cores | |
| "cb_w": 2, # weight ring (direct-mode PageRing pages of 8 tiles) | |
| "cb_mm0": 3, # matmul output (4-page transactions; the NCRISC gathers / pops) | |
| "cb_q": 4, # Q_h [64,64] of the head cores (TRISC-only: P2 packs, HeadSDPA pops) | |
| "cb_o": 5, # HeadSDPA output O_h [64,64] (TRISC packs, NCRISC gathers / pops) | |
| "cb_bias0": 6, # row-broadcast bias tiles (BRISC builds, TRISC pops) | |
| "cb_bias1": 7, # BRISC raw scratch (no CB ops) | |
| "cb_adaln_raw": 8, # the 2 row-broadcast AdaLN tiles (1+s, b) of the own column tile (BRISC builds, TRISC pops) | |
| "cb_mask": 9, # additive mask chunks, 2 x 4 tiles (BRISC pushes, TRISC pops) | |
| "cb_k": 10, # K / K^T chunks, 2 x 4 tiles (NCRISC pushes, TRISC pops) | |
| "cb_v": 11, # V chunks, 2 x 4 tiles | |
| "cb_kt": 12, # K^T transpose scratch (TRISC-only) | |
| "cb_scores": 13, # fp32 matmul partials (K1 cb_acc) 2 x 4 pages | |
| "cb_tw": 14, # tail weights: one fixed 48-tile bf16 transaction per tail op (NCRISC pushes, TRISC pops) | |
| "cb_tconst": 15, # tail constants: b3_pos tiles, action-row mask, state/future rows (BRISC once) | |
| "cb_h": 16, # the own normalised LN tile h_(r,t) (TRISC packs, NCRISC gathers / pops) | |
| "cb_part": 17, # K4b: the two remote (m, l, O) attention parts of a split cross block, 2 x 8 fp32 tiles (NCRISC pushes, TRISC pops) | |
| "cb_stat": 18, # LN fp32 GI staging + the residual fence page | |
| "cb_stat32": 19, # LN -mean (UnpackToDestFp32) | |
| "cb_const": 21, # LN ONES / IDENT / ZERO tiles | |
| "cb_zero": 23, # SDPA zero tile (also the tail epilogue's acc_to_dest operand) | |
| "cb_ones": 24, # SDPA reduce scaler | |
| "cb_s": 25, # SDPA scores / probabilities fp32 | |
| "cb_oacc": 26, # SDPA running O fp32 | |
| "cb_m0": 27, | |
| "cb_m1": 28, | |
| "cb_l0": 29, | |
| "cb_l1": 30, | |
| "cb_alpha": 31, | |
| } | |
| #: Hub-only CB ids (disjoint core range: may reuse compute ids; ``cb_act`` = 19 shares the UnpackToDestFp32 mode). | |
| K4_HUB_CB_IDS: Mapping[str, int] = { | |
| "cb_act": 19, # Euler action state x_t [64,A_pad] fp32 (NCRISC reads x_t once; TRISC updates in place) | |
| "cb_vel": 16, # decoder output v per step, bf16, n_steps x 2*nt tiles (TRISC packs; BRISC writes the taps) | |
| "cb_xin": 17, # bf16 copy of x_t for the action encoder (TRISC packs, BRISC multicasts / pops) | |
| "cb_hconst": 18, # dt row tiles (2*nt), dec b2 bias tiles (nt), zero tile (BRISC once) | |
| "cb_hacc": 20, # fp32 fence / scratch page | |
| "cb_tok_in": 21, # NCRISC -> TRISC token: "the dec_W2 partials landed in cb_a" | |
| "cb_tok_out": 22, # TRISC -> BRISC token: "partials consumed, cb_a may be popped" | |
| "cb_xbf": 23, # bf16 round-trip scratch of the bf16 Euler mode (TRISC-only) | |
| "cb_land": 24, # K4b: the gathers' landing ring (K4_LAND_PAGES x 2 KB; the multicast source), NCRISC pushes, BRISC pops | |
| } | |
| #: K4b: pages of the hub's gather landing ring ``cb_land`` -- 2 x the 128-page transaction, i.e. four 64-page ff2 chunk | |
| #: slots, so every chunk gather can land while earlier chunks are still being multicast (the K4 hub landed everything | |
| #: in its 128-page ``a_rep``: chunk kc + 2 could only be sent after chunk kc's multicast, and its gather then ran | |
| #: concurrently with the next multicast at half speed, mk_k4b_summary.md). Must be a multiple of 128. | |
| K4_LAND_PAGES: int = 256 | |
| #: fixed transaction of the tail weight CB (the largest tail slice per core: proj_out_2 / enc_W2a / enc_W3 = 48 tiles) | |
| K4_TW_TILES: int = 48 | |
| #: tail constant tiles per compute core: b3_pos rows (2), action-row mask (2), state + future rows (2) | |
| K4_TCONST_TILES: int = 6 | |
| K4_SDPA_SLOTS: int = 2 # K / V / mask chunk rings (plan §5.5 "2 x") | |
| #: K4b split cross attention: cross blocks with at least this many 64-key chunks share every head's keys over the three | |
| #: cores h / 32+h / 64+h (head_sdpa.hpp run_partial + merge_part); N1.5 `all` (6 chunks) and N1.7 `image` (7) split, | |
| #: N1.6 `image` (2) and every `text` set (1) do not. Mirrored by dit_kernel.cpp `mk::split_blk`. | |
| K4_SPLIT_MIN_CHUNKS: int = 3 | |
| K4_SPLIT_WAYS: int = 3 | |
| #: fp32 pages of `cb_part`: the two remote parts (m 2, l 2, O 4 tiles each) landing on the head core | |
| K4_PART_PAGES: int = 2 * 8 | |
| class K4Budget: | |
| """L1 budget of the K4 spec (bytes / counts). Defaults: ~1.15 MB (bf16 arena) / ~0.98 MB (bfp8) of the 1,427 KiB | |
| usable on the compute cores with a weight-ring depth of 13 bf16 / 15 bfp8 K-block slots of lookahead (K1 used 9 | |
| bf16; 13 buys 6 % of step time on the bf16 arena, mk_k4_summary.md §5.2, and still fits under ``l1_budget_compute``), | |
| the distributed LN of ``dist_layernorm.hpp`` (no centred-row scratch, no fp32 affine ring: the K2 ``cb_xc`` / | |
| ``cb_y32`` are gone) and the per-core tile row of x (96 KB instead of the replicated 192 KB); ``l1_budget_compute`` | |
| is the acceptance limit checked by :meth:`DescriptorStats.check`.""" | |
| cb_w_slots: Mapping[str, int] = field(default_factory=lambda: {"bfp8_b": 15, "bf16": 13}) | |
| cb_w: Optional[int] = None | |
| cb_mm: int = 8 * TILE_BYTES_BF16 # 2 slots of 4 pages | |
| cb_scores_pages: int = 8 # 2 slots of 4 fp32 partial pages | |
| cb_adaln_slots: int = 2 # (1+s, b) row-broadcast tile pairs the BRISC may run ahead | |
| cb_h_tiles: int = 4 # own LN tiles in flight (TRISC -> NCRISC) | |
| l1_budget_compute: int = 1_340_000 | |
| #: below L1_USABLE_BYTES: dit_program.K4_KERNEL_CONFIG_EXTRA_BYTES (96 KiB of the worker L1 go to the kernel-config | |
| #: ring buffer) + the L1 allocator's headroom for the sharded buffers | |
| l1_margin: int = 96 * 1024 + 24_000 | |
| def cb_w_bytes(self, dtype_stream: str, k_block_bytes: int) -> int: | |
| if self.cb_w is not None: | |
| if self.cb_w <= 0 or self.cb_w % k_block_bytes: | |
| raise ValueError(f"cb_w {self.cb_w} is not a positive multiple of the K-block ({k_block_bytes} B)") | |
| return int(self.cb_w) | |
| try: | |
| return int(self.cb_w_slots[dtype_stream]) * k_block_bytes | |
| except KeyError: | |
| raise KeyError(f"no K4 cb_w slot count for stream dtype {dtype_stream!r}") from None | |
| def k4_n_tiles_a(layout: ArenaLayout) -> int: | |
| """Column tiles of the action buffer ``[64, A_pad]`` (``nt`` of dec_W2 / enc_W1; 1 / 4 / 5 for N1.5 / N1.6 / N1.7).""" | |
| if layout.a_pad % TILE: | |
| raise ValueError(f"A_pad {layout.a_pad} is not a tile multiple") | |
| return layout.a_pad // TILE | |
| def k4_cb_specs(layout: ArenaLayout, k4: K4Budget, core_map: CoreMap) -> Tuple[List[CBSpec], Dict[str, int]]: | |
| """CBs of the K4 kernel per role (compute / hub / spare; readers idle in direct mode) and the per-role L1 totals.""" | |
| ids = K4_CB_IDS | |
| hid = K4_HUB_CB_IDS | |
| k_block_bytes = max(op.k_block_bytes for op in layout.stream_ops("stream")) | |
| cb_w = k4.cb_w_bytes(layout.dtype_stream, k_block_bytes) | |
| stream_tile = TILE_BYTES[layout.dtype_stream] | |
| if layout.dtype_small != "bf16": | |
| raise ValueError("K4 streams the tail ops as bf16 tiles (plan D4)") | |
| nt = k4_n_tiles_a(layout) | |
| n_steps = N_STEPS | |
| row_tiles = 2 * (layout.width // TILE) # 96 tiles of the [64,1536] stream | |
| b = TILE_BYTES_BF16 | |
| f = TILE_BYTES_FP32 | |
| compute = [ | |
| CBSpec("cb_x", ids["cb_x"], "compute", (row_tiles // 2) * b, b, BF16, tensor_backed="x_rep"), | |
| CBSpec("cb_a", ids["cb_a"], "all", 64 * 2048 * 2, b, BF16, tensor_backed="a_rep"), | |
| CBSpec("cb_w", ids["cb_w"], "compute", cb_w, stream_tile, layout.dtype_stream), | |
| CBSpec("cb_mm0", ids["cb_mm0"], "compute", k4.cb_mm, b, BF16), | |
| CBSpec("cb_q", ids["cb_q"], "compute", 4 * b, b, BF16), | |
| CBSpec("cb_o", ids["cb_o"], "compute", 4 * b, b, BF16), | |
| CBSpec("cb_bias0", ids["cb_bias0"], "compute", 4 * b, b, BF16), | |
| CBSpec("cb_bias1", ids["cb_bias1"], "compute", 4 * b, b, BF16), | |
| CBSpec("cb_adaln_raw", ids["cb_adaln_raw"], "compute", k4.cb_adaln_slots * 2 * b, b, BF16), | |
| CBSpec("cb_mask", ids["cb_mask"], "compute", K4_SDPA_SLOTS * 4 * b, b, BF16), | |
| CBSpec("cb_k", ids["cb_k"], "compute", K4_SDPA_SLOTS * 4 * b, b, BF16), | |
| CBSpec("cb_v", ids["cb_v"], "compute", K4_SDPA_SLOTS * 4 * b, b, BF16), | |
| CBSpec("cb_kt", ids["cb_kt"], "compute", 4 * b, b, BF16), | |
| CBSpec("cb_scores", ids["cb_scores"], "compute", k4.cb_scores_pages * f, f, FP32), | |
| CBSpec("cb_tw", ids["cb_tw"], "compute", K4_TW_TILES * b, b, BF16), | |
| CBSpec("cb_tconst", ids["cb_tconst"], "compute", K4_TCONST_TILES * b, b, BF16), | |
| CBSpec("cb_h", ids["cb_h"], "compute", k4.cb_h_tiles * b, b, BF16), | |
| CBSpec("cb_part", ids["cb_part"], "compute", K4_PART_PAGES * f, f, FP32), | |
| CBSpec("cb_stat", ids["cb_stat"], "compute", 2 * f, f, FP32), | |
| CBSpec("cb_stat32", ids["cb_stat32"], "compute", f, f, FP32), | |
| CBSpec("cb_const", ids["cb_const"], "compute", 3 * b, b, BF16), | |
| CBSpec("cb_zero", ids["cb_zero"], "compute", b, b, BF16), | |
| CBSpec("cb_ones", ids["cb_ones"], "compute", b, b, BF16), | |
| CBSpec("cb_s", ids["cb_s"], "compute", 4 * f, f, FP32), | |
| CBSpec("cb_oacc", ids["cb_oacc"], "compute", 4 * f, f, FP32), | |
| CBSpec("cb_m0", ids["cb_m0"], "compute", 2 * f, f, FP32), | |
| CBSpec("cb_m1", ids["cb_m1"], "compute", 2 * f, f, FP32), | |
| CBSpec("cb_l0", ids["cb_l0"], "compute", 2 * f, f, FP32), | |
| CBSpec("cb_l1", ids["cb_l1"], "compute", 2 * f, f, FP32), | |
| CBSpec("cb_alpha", ids["cb_alpha"], "compute", 2 * f, f, FP32), | |
| ] | |
| hub = [ | |
| CBSpec("cb_act", hid["cb_act"], "hubs", 2 * nt * f, f, FP32), | |
| CBSpec("cb_vel", hid["cb_vel"], "hubs", n_steps * 2 * nt * b, b, BF16), | |
| CBSpec("cb_xin", hid["cb_xin"], "hubs", 2 * nt * b, b, BF16), | |
| CBSpec("cb_hconst", hid["cb_hconst"], "hubs", (3 * nt + 1) * b, b, BF16), | |
| CBSpec("cb_hacc", hid["cb_hacc"], "hubs", f, f, FP32), | |
| CBSpec("cb_tok_in", hid["cb_tok_in"], "hubs", 2 * b, b, BF16), | |
| CBSpec("cb_tok_out", hid["cb_tok_out"], "hubs", 2 * b, b, BF16), | |
| CBSpec("cb_xbf", hid["cb_xbf"], "hubs", 2 * nt * b, b, BF16), | |
| CBSpec("cb_land", hid["cb_land"], "hubs", K4_LAND_PAGES * b, b, BF16), | |
| ] | |
| if K4_LAND_PAGES % 128: | |
| raise ValueError("K4_LAND_PAGES must be a multiple of the 128-page hub transaction") | |
| cbs = compute + hub | |
| for cb in cbs: | |
| if cb.total_bytes <= 0 or cb.total_bytes % cb.page_bytes: | |
| raise ValueError( | |
| f"{cb.name}: total {cb.total_bytes} is not a positive multiple of the page {cb.page_bytes}" | |
| ) | |
| if not 0 <= cb.cb_id < NUM_CIRCULAR_BUFFERS: | |
| raise ValueError(f"{cb.name}: CB id {cb.cb_id} out of range") | |
| ids_by_set: Dict[str, List[int]] = {} | |
| for cb in cbs: | |
| for role_set in (("compute", "readers", "hubs", "spare") if cb.role_set == "all" else (cb.role_set,)): | |
| lst = ids_by_set.setdefault(role_set, []) | |
| if cb.cb_id in lst: | |
| raise ValueError(f"CB id {cb.cb_id} used twice on {role_set}") | |
| lst.append(cb.cb_id) | |
| on = lambda s: sum(cb.total_bytes for cb in cbs if cb.role_set in ("all", s)) # noqa: E731 | |
| l1 = {"compute": on("compute"), "reader": on("readers"), "hub": on("hubs"), "spare": on("spare")} | |
| limit = L1_USABLE_BYTES - k4.l1_margin | |
| for role, used in l1.items(): | |
| if used > limit: | |
| raise ValueError(f"K4 {role} L1 {used} B exceeds usable {L1_USABLE_BYTES} - margin {k4.l1_margin}") | |
| if l1["compute"] > k4.l1_budget_compute: | |
| raise ValueError(f"K4 compute L1 {l1['compute']} B > budget {k4.l1_budget_compute}") | |
| return cbs, l1 | |
| def k4_io_extra(variant: str, layout: ArenaLayout) -> List[IoSlot]: | |
| """Io tensors the K4 kernel needs beyond :func:`io_spec`: the per-step velocity taps of ``steps4`` (optional; | |
| ``pred_velocity[k]`` acceptance) -- the ``x_t`` / ``action_pred`` slots of :func:`io_spec` carry fp32 ``[64,A_pad]`` | |
| DRAM tensors in K4 (the hub keeps the Euler state in fp32 for every version, rounding to bf16 per step when | |
| ``euler_fp32 == 0``; ``euler_head.hpp``).""" | |
| if variant not in VARIANTS: | |
| raise ValueError(f"variant must be in {VARIANTS}, got {variant!r}") | |
| if variant != "steps4": | |
| return [] | |
| return [IoSlot("velocity_out", False, "[4*64, A_pad] bf16 TILE: decoder output of every step (taps; optional)")] | |
| def build_program_spec_k4( | |
| core_map: CoreMap, | |
| arena_layout: ArenaLayout, | |
| variant: str, | |
| io: Mapping[str, Any], | |
| k4: Optional[K4Budget] = None, | |
| *, | |
| dry_run: bool = False, | |
| step_index: int = 0, | |
| kernel_source: str = KERNEL_SOURCE, | |
| ) -> ProgramSpec: | |
| """The K4 :class:`ProgramSpec` (module docstring): K1 io order + :func:`k4_io_extra`, :func:`k4_cb_specs`, | |
| named CTAs with :data:`K4_CB_IDS` (+ ``k4_*`` hub ids / sizes), the same semaphores and common RT layout as | |
| :func:`build_program_spec` (``dit_program.py`` appends its own RT args after the offsets table).""" | |
| if variant not in VARIANTS: | |
| raise ValueError(f"variant must be in {VARIANTS}, got {variant!r}") | |
| if not 0 <= step_index < N_STEPS: | |
| raise ValueError(f"step_index {step_index} out of range") | |
| if arena_layout.reader_mode != "direct": | |
| raise ValueError("the K4 kernel streams weights in direct mode only (K1 GO decision, mk_k1_summary.md §0)") | |
| k4b = k4 if k4 is not None else K4Budget() | |
| slots = io_spec(variant, arena_layout) + k4_io_extra(variant, arena_layout) | |
| unknown = set(io) - {s.name for s in slots} | |
| if unknown: | |
| raise KeyError(f"io has tensors unknown to variant {variant!r}: {sorted(unknown)}") | |
| cbs, l1 = k4_cb_specs(arena_layout, k4b, core_map) | |
| budget = CBBudget(l1_budget_compute=k4b.l1_budget_compute) | |
| ctas = _named_ctas(arena_layout, core_map, variant, budget, cbs, len(slots), cb_ids=K4_CB_IDS) | |
| ctas += [(f"k4_{name}", cb_id) for name, cb_id in K4_HUB_CB_IDS.items()] | |
| ctas += [ | |
| ("k4_tw_tiles", K4_TW_TILES), | |
| ("k4_tconst_tiles", K4_TCONST_TILES), | |
| ("k4_sdpa_slots", K4_SDPA_SLOTS), | |
| ("k4_nt_a", k4_n_tiles_a(arena_layout)), | |
| ("k4_velocity_out_present", 1 if io.get("velocity_out") is not None else 0), | |
| ] | |
| seen = set() | |
| for name, _ in ctas: | |
| if name in seen: | |
| raise ValueError(f"duplicate named CTA {name}") | |
| seen.add(name) | |
| names = [f"addr_{s.name}" for s in slots] + ["step_index", "offsets_table_len"] | |
| args = [_address_of(io, s, dry_run) for s in slots] + [step_index] | |
| offsets = arena_layout.offsets_table() | |
| args.append(len(offsets)) | |
| args += offsets | |
| names += [f"offsets[{i}]" for i in range(len(offsets))] | |
| ctas.append(("offsets_table_base", len(slots) + 2)) | |
| ctas.append(("common_rt_args_len", len(args))) | |
| if len(args) > MAX_COMMON_RT_ARGS: | |
| raise ValueError(f"{len(args)} common runtime args exceed {MAX_COMMON_RT_ARGS}") | |
| per_core = {core: list(a) for core, a in core_map.per_core_args().items()} | |
| sems = [SemSpec(name, sem_id) for name, sem_id in SEM_IDS.items()] | |
| # K4b: `is_hub` on the hub AND CoreMap.hub2 (the ff2-chunk hub); the per-core `group` word tells them apart | |
| role_flags = [(flag, "hubs" if role is Role.HUB else role) for flag, role in ROLE_FLAGS] | |
| return ProgramSpec( | |
| variant=variant, | |
| kernel_source=kernel_source, | |
| core_map=core_map, | |
| layout=arena_layout, | |
| budget=budget, | |
| named_ctas=ctas, | |
| role_flags=role_flags, | |
| per_core_rt_args=per_core, | |
| common_rt_names=names, | |
| common_rt_args=args, | |
| cbs=cbs, | |
| semaphores=sems, | |
| io=slots, | |
| l1_bytes=l1, | |
| ) | |
| def k4_unpack_fp32_cbs() -> Tuple[int, ...]: | |
| """CB ids read through ``UnpackToDestFp32`` in the K4 program (compute ``cb_stat32`` == hub ``cb_act``).""" | |
| if K4_CB_IDS["cb_stat32"] != K4_HUB_CB_IDS["cb_act"]: | |
| raise AssertionError("cb_stat32 and cb_act must share the UnpackToDestFp32 id (program-wide mode list)") | |
| return (K4_CB_IDS["cb_stat32"],) | |
| def build_program( | |
| core_map: CoreMap, | |
| arena_layout: ArenaLayout, | |
| variant: str, | |
| io: Mapping[str, Any], | |
| cb_budget: Optional[CBBudget] = None, | |
| *, | |
| dry_run: bool = False, | |
| step_index: int = 0, | |
| kernel_source: str = KERNEL_SOURCE, | |
| ) -> Any: | |
| """Build the megakernel program (plan §5.1). | |
| Returns :class:`DescriptorStats` when ``dry_run`` (no ttnn import, ``io`` values may be ``None``), otherwise the | |
| ``ttnn.ProgramDescriptor`` whose io tensor order is :meth:`ProgramSpec.io_order` (see :func:`build_program_spec`). | |
| """ | |
| spec = build_program_spec( | |
| core_map, | |
| arena_layout, | |
| variant, | |
| io, | |
| cb_budget, | |
| dry_run=dry_run, | |
| step_index=step_index, | |
| kernel_source=kernel_source, | |
| ) | |
| if dry_run: | |
| return spec.stats() | |
| return materialise(spec, io) | |