"""Action expert (flow-matching trajectory decoder) of Alpamayo2-Super on one Tenstorrent chip, batch 1. Per Euler step: action_in_proj (host torch, tiny) -> 64-layer Qwen3-VL text transformer (device, traced) whose layer i attends non-causally to [VLM KV cache of layer i ; its own 64 tokens] -> final RMSNorm (device) -> action_out_proj (host). See EXPERT_NOTES.md for layouts, masking and measurements. KV cache interface (provided by the text model): ``kv_cache[i] = (K, V)``, each ``[1, n_kv, Smax, head_dim]`` TILE DRAM (bf16 or bfp8) in HF head order, rows ``[0, S)`` valid. The expert writes its own 64 roped keys / values into rows ``[Smax-64, Smax)`` of every layer's cache (``ttnn.fill_cache`` with a tile-aligned offset) and runs one attention op over the whole cache with an additive mask that hides rows ``[S, Smax-64)``. Hence the traced step does not depend on S; only the mask / cos / sin buffers (rewritten from host per trajectory) do. Attention op (``attn_mode``): * "decode" (default): flash-decode kernel ``scaled_dot_product_attention_decode`` with the cache viewed as 8 users x 1 kv head ``[8, 1, Smax, 128]`` and the queries viewed as ``[1, 8, 128, 128]`` (user g = kv head g, its 128 "heads" = the 2 q heads of the group x 64 tokens). K/V are split across cores (~16 per kv head), so the cache is read once, in parallel: ~170 us for Smax = 8192 vs ~520 us for the standard kernel. * "standard": ``scaled_dot_product_attention`` (non-causal, GQA); every q chunk re-reads the whole K/V. """ from __future__ import annotations import time from dataclasses import dataclass import torch import ttnn from .action_proj import ActionInProj, ActionOutProj, expert_positions, rope_cos_sin from .config import ModelConfig from .weights import DTYPES, EXPERT, Checkpoint, TTWeights, fused_qkv, linear_weight MASK_MIN = -1e9 # additive mask value for hidden cache rows (bf16/bfp4 representable, exp() underflows to 0) def _norm_weight(w: torch.Tensor) -> torch.Tensor: """HF RMSNorm weight [dim] -> ttnn.rms_norm gamma layout [1, 1, 1, dim] (TILE; width must equal input width).""" return w.reshape(1, 1, 1, w.numel()) @dataclass class LayerWeights: ln1: ttnn.Tensor ln2: ttnn.Tensor qn: ttnn.Tensor kn: ttnn.Tensor wqkv: ttnn.Tensor wo: ttnn.Tensor w1: ttnn.Tensor w3: ttnn.Tensor w2: ttnn.Tensor class TTExpert: """Op-graph implementation. ``layer_fn`` (one layer: (i, x, k_cache, v_cache) -> x) is the unit a fused kernel can replace; everything else (buffers, context, trace, Euler loop) stays.""" def __init__(self, device, cfg: ModelConfig, ckpt: Checkpoint, tw: TTWeights, dtype: str = "bfp8", layers: range | None = None, attn_mode: str = "decode", mask_dtype: str | None = None, sdpa_k_chunk: int | None = None, sdpa_q_chunk: int = 32, fold_heads: bool = False, use_trace: bool = True, in_proj_dtype: torch.dtype = torch.float32, tuned_matmul: bool = True): assert attn_mode in ("decode", "standard") self.device = device self.cfg = cfg self.tc = cfg.expert.text self.n_tok = cfg.expert.n_waypoints self.layers = list(layers) if layers is not None else list(range(self.tc.n_layers)) self.dtype = dtype self.attn_mode = attn_mode self.mask_dtype = mask_dtype or ("bfp4" if attn_mode == "decode" else "bf16") self.use_trace = use_trace self.scale = self.tc.head_dim ** -0.5 tc = self.tc assert tc.n_heads % tc.n_kv_heads == 0 self.rep = tc.n_heads // tc.n_kv_heads # decode mode always groups the q heads of a kv head onto rows; standard mode optionally does (fold_heads) self.fold_heads = True if attn_mode == "decode" else fold_heads self.sq = self.n_tok * self.rep if self.fold_heads else self.n_tok # mask rows / query rows per kv head grid = device.compute_with_storage_grid_size() self.grid = grid if attn_mode == "decode": self.sdpa_cfg = ttnn.SDPAProgramConfig( compute_with_storage_grid_size=grid, q_chunk_size=32, k_chunk_size=sdpa_k_chunk or 128, exp_approx_mode=False, max_cores_per_head_batch=16) else: self.sdpa_cfg = ttnn.SDPAProgramConfig( compute_with_storage_grid_size=grid, q_chunk_size=sdpa_q_chunk, k_chunk_size=sdpa_k_chunk or 512, exp_approx_mode=False) self.ck_mm = ttnn.WormholeComputeKernelConfig( math_fidelity=ttnn.MathFidelity.HiFi2, math_approx_mode=False, fp32_dest_acc_en=True, packer_l1_acc=True) self.ck_sdpa = ttnn.WormholeComputeKernelConfig( math_fidelity=ttnn.MathFidelity.HiFi2, math_approx_mode=False, fp32_dest_acc_en=False, packer_l1_acc=False) # rms_norm: the op default config is ~9 us faster per call than HiFi4+fp32 acc on [64, 1536] (same PCC) self.ck_norm = None self.mem = ttnn.DRAM_MEMORY_CONFIG # matmul program configs (measured in EXPERT_NOTES.md; None -> ttnn default heuristics) H, Q, I = tc.hidden, tc.q_dim, tc.inter if tuned_matmul: self.pc_qkv = self._mm1d(H, tc.qkv_dim, in0_block_w=8) self.pc_o = self._mm1d(Q, H, in0_block_w=8) self.pc_w1 = self._mm1d(H, I, in0_block_w=8, activation=ttnn.UnaryOpType.SILU) self.pc_w3 = self._mm1d(H, I, in0_block_w=8) self.pc_w2 = self._mm1d(I, H, in0_block_w=48) else: self.pc_qkv = self.pc_o = self.pc_w1 = self.pc_w3 = self.pc_w2 = None # ---- weights ----------------------------------------------------------------------------- t0 = time.time() self.w: dict[int, LayerWeights] = {} for i in self.layers: self.w[i] = self._load_layer(i, ckpt, tw) self.final_norm = tw.to_device("expert.norm_g", _norm_weight(ckpt.get(EXPERT + "norm.weight")), "bf16") self.weight_load_s = time.time() - t0 self.in_proj = ActionInProj(cfg.expert, ckpt, dtype=in_proj_dtype) self.out_proj = ActionOutProj(ckpt) # ---- persistent device buffers (inputs of the traced graph) ------------------------------ self.x_dev = self._dev_zeros((1, 1, self.n_tok, H)) self.cos_dev = self._dev_zeros((1, 1, self.n_tok, tc.head_dim)) self.sin_dev = self._dev_zeros((1, 1, self.n_tok, tc.head_dim)) # The mask must exist before ANY trace is captured in this process (a trace's intermediates are placed in # memory that is free at capture time; a buffer allocated later can alias them and get clobbered on replay). self.mask_dev: ttnn.Tensor = self._dev_zeros((1, 1, self.sq, cfg.max_seq_len), self.mask_dtype) self.ctx: tuple | None = None # (S, rope_delta, Smax) currently loaded into cos/sin/mask self.tail: int | None = None # row offset of own K/V inside the cache self._trace: dict | None = None self._views: dict[tuple, tuple] = {} # cache buffer addresses -> [8, 1, Smax, dh] views (decode mode) self.layer_fn = self.layer # swap point for a fused implementation # ------------------------------------------------------------------------------------------ setup def _mm1d(self, K: int, N: int, in0_block_w: int, activation=None): """1D multicast matmul config for x [64, K] @ W [K, N]: in0 broadcast, N split over all cores.""" nc = self.grid.x * self.grid.y Kt, Nt = K // 32, N // 32 assert Kt % in0_block_w == 0, (K, in0_block_w) per_core_N = -(-Nt // nc) return ttnn.MatmulMultiCoreReuseMultiCast1DProgramConfig( compute_with_storage_grid_size=(self.grid.x, self.grid.y), in0_block_w=in0_block_w, out_subblock_h=1, out_subblock_w=per_core_N if per_core_N <= 4 else 1, out_block_h=self.n_tok // 32, out_block_w=per_core_N, per_core_M=self.n_tok // 32, per_core_N=per_core_N, fuse_batch=True, fused_activation=ttnn.UnaryWithParam(activation) if activation is not None else None, mcast_in0=True) def _linear(self, x, w, pc, activation: str | None = None): if pc is not None: return ttnn.linear(x, w, dtype=ttnn.bfloat16, program_config=pc, compute_kernel_config=self.ck_mm, memory_config=self.mem) return ttnn.linear(x, w, dtype=ttnn.bfloat16, activation=activation, compute_kernel_config=self.ck_mm, memory_config=self.mem) def _dev_zeros(self, shape, dtype="bf16"): return ttnn.from_torch(torch.zeros(*shape), dtype=DTYPES[dtype], layout=ttnn.TILE_LAYOUT, device=self.device, memory_config=self.mem) def _load_layer(self, i: int, ckpt: Checkpoint, tw: TTWeights) -> LayerWeights: w = ckpt.text_layer(i, prefix=EXPERT) p = f"expert.L{i}." d = self.dtype return LayerWeights( ln1=tw.to_device(p + "ln1_g", _norm_weight(w["input_layernorm.weight"]), "bf16"), ln2=tw.to_device(p + "ln2_g", _norm_weight(w["post_attention_layernorm.weight"]), "bf16"), qn=tw.to_device(p + "qn_g", _norm_weight(w["self_attn.q_norm.weight"]), "bf16"), kn=tw.to_device(p + "kn_g", _norm_weight(w["self_attn.k_norm.weight"]), "bf16"), wqkv=tw.to_device(p + "wqkv", fused_qkv(w["self_attn.q_proj.weight"], w["self_attn.k_proj.weight"], w["self_attn.v_proj.weight"]), d), wo=tw.to_device(p + "wo", linear_weight(w["self_attn.o_proj.weight"]), d), w1=tw.to_device(p + "w1", linear_weight(w["mlp.gate_proj.weight"]), d), w3=tw.to_device(p + "w3", linear_weight(w["mlp.up_proj.weight"]), d), w2=tw.to_device(p + "w2", linear_weight(w["mlp.down_proj.weight"]), d), ) def _upload(self, dev: ttnn.Tensor, t: torch.Tensor): host = ttnn.from_torch(t.reshape(tuple(dev.shape)).to(torch.float32), dtype=dev.dtype, layout=ttnn.TILE_LAYOUT) ttnn.copy_host_to_device_tensor(host, dev) def build_mask(self, S: int, Smax: int) -> torch.Tensor: """Additive mask [1, 1, sq, Smax]: 0 on valid cache rows [0, S) and on own rows [Smax-64, Smax).""" m = torch.full((1, 1, self.sq, Smax), MASK_MIN, dtype=torch.float32) m[..., :S] = 0.0 m[..., Smax - self.n_tok:] = 0.0 return m def set_context(self, S: int, rope_delta: int, Smax: int, force: bool = False): """Load cos/sin (positions i + rope_delta + S) and the cache mask for a trajectory (~1 ms). Skips the upload when (S, rope_delta, Smax) is unchanged unless ``force``: other traces (the text model's decode) may run between two trajectories, so ``sample()`` always forces a refresh at its start.""" assert S <= Smax - self.n_tok, f"cache too small: S={S}, Smax={Smax} (last {self.n_tok} rows are expert scratch)" key = (int(S), int(rope_delta), int(Smax)) if self.ctx == key and not force: return cos, sin = rope_cos_sin(expert_positions(S, rope_delta, self.n_tok), self.tc.head_dim, self.tc.rope_theta) self._upload(self.cos_dev, cos) self._upload(self.sin_dev, sin) if self.mask_dev.shape[-1] != Smax: # cache longer/shorter than cfg.max_seq_len: reallocate (unsafe if traces exist) print(f"[TTExpert] warning: reallocating mask for Smax={Smax} (was {self.mask_dev.shape[-1]}); " "do this before any trace capture") self.release_trace() self.mask_dev.deallocate() self.mask_dev = self._dev_zeros((1, 1, self.sq, Smax), self.mask_dtype) self._upload(self.mask_dev, self.build_mask(S, Smax)) self.tail = Smax - self.n_tok self.ctx = key def _kv_views(self, k_cache: ttnn.Tensor, v_cache: ttnn.Tensor): """[1, n_kv, Smax, dh] -> zero-copy views [n_kv, 1, Smax, dh] for the decode kernel (cached per buffer).""" key = (id(k_cache), id(v_cache), k_cache.buffer_address(), v_cache.buffer_address()) v = self._views.get(key) if v is None: shp = (self.tc.n_kv_heads, 1, k_cache.shape[2], self.tc.head_dim) v = (ttnn.view(k_cache, shp), ttnn.view(v_cache, shp)) self._views[key] = v return v # ------------------------------------------------------------------------------------------ layer def attention_core(self, q: ttnn.Tensor, k_cache: ttnn.Tensor, v_cache: ttnn.Tensor) -> ttnn.Tensor: """q [1, n_heads, 64, dh] (roped) -> attention output [1, n_heads, 64, dh] over the full cache + mask.""" tc = self.tc if self.attn_mode == "decode": k8, v8 = self._kv_views(k_cache, v_cache) qd = ttnn.view(q, (1, tc.n_kv_heads, self.sq, tc.head_dim)) # [1, users=8, heads=128, dh] a = ttnn.transformer.scaled_dot_product_attention_decode( qd, k8, v8, is_causal=False, attn_mask=self.mask_dev, scale=self.scale, program_config=self.sdpa_cfg, compute_kernel_config=self.ck_sdpa, memory_config=self.mem) return ttnn.view(a, (1, tc.n_heads, self.n_tok, tc.head_dim)) if self.fold_heads: q = ttnn.view(q, (1, tc.n_kv_heads, self.sq, tc.head_dim)) a = ttnn.transformer.scaled_dot_product_attention( q, k_cache, v_cache, attn_mask=self.mask_dev, is_causal=False, scale=self.scale, program_config=self.sdpa_cfg, compute_kernel_config=self.ck_sdpa, memory_config=self.mem) if self.fold_heads: a = ttnn.view(a, (1, tc.n_heads, self.n_tok, tc.head_dim)) return a def layer(self, i: int, x: ttnn.Tensor, k_cache: ttnn.Tensor, v_cache: ttnn.Tensor) -> ttnn.Tensor: """One decoder layer. x [1, 1, 64, hidden] bf16 -> new tensor of the same shape (x is not freed).""" L = self.w[i] tc = self.tc eps = tc.rms_eps # --- attention h = ttnn.rms_norm(x, epsilon=eps, weight=L.ln1, compute_kernel_config=self.ck_norm) qkv = self._linear(h, L.wqkv, self.pc_qkv) h.deallocate() q, k, v = ttnn.experimental.nlp_create_qkv_heads( qkv, num_heads=tc.n_heads, num_kv_heads=tc.n_kv_heads, transpose_k_heads=False, memory_config=self.mem) qkv.deallocate() qn = ttnn.rms_norm(q, epsilon=eps, weight=L.qn, compute_kernel_config=self.ck_norm) q.deallocate() kn = ttnn.rms_norm(k, epsilon=eps, weight=L.kn, compute_kernel_config=self.ck_norm) k.deallocate() q = ttnn.experimental.rotary_embedding_hf(qn, self.cos_dev, self.sin_dev, is_decode_mode=False) qn.deallocate() k = ttnn.experimental.rotary_embedding_hf(kn, self.cos_dev, self.sin_dev, is_decode_mode=False) kn.deallocate() if k.dtype != k_cache.dtype: k = ttnn.typecast(k, k_cache.dtype) v = ttnn.typecast(v, v_cache.dtype) ttnn.fill_cache(k_cache, k, 0, update_idx=self.tail) ttnn.fill_cache(v_cache, v, 0, update_idx=self.tail) k.deallocate() v.deallocate() a = self.attention_core(q, k_cache, v_cache) q.deallocate() o = ttnn.experimental.nlp_concat_heads(a, memory_config=self.mem) # [1, 1, 64, n_heads*head_dim] a.deallocate() o2 = self._linear(o, L.wo, self.pc_o) o.deallocate() x1 = ttnn.add(x, o2, memory_config=self.mem) o2.deallocate() # --- MLP h2 = ttnn.rms_norm(x1, epsilon=eps, weight=L.ln2, compute_kernel_config=self.ck_norm) g = self._linear(h2, L.w1, self.pc_w1, activation="silu") u = self._linear(h2, L.w3, self.pc_w3) h2.deallocate() gu = ttnn.multiply(g, u, memory_config=self.mem) g.deallocate() u.deallocate() m = self._linear(gu, L.w2, self.pc_w2) gu.deallocate() x2 = ttnn.add(x1, m, memory_config=self.mem) x1.deallocate() m.deallocate() return x2 def _forward_dev(self, kv_cache, layers, final_norm: bool) -> ttnn.Tensor: x = self.x_dev for i in layers: k_cache, v_cache = kv_cache[i] x_new = self.layer_fn(i, x, k_cache, v_cache) if x is not self.x_dev: x.deallocate() x = x_new if final_norm: out = ttnn.rms_norm(x, epsilon=self.tc.rms_eps, weight=self.final_norm, compute_kernel_config=self.ck_norm) if x is not self.x_dev: x.deallocate() return out return x # ------------------------------------------------------------------------------------------ trace def _trace_key(self, kv_cache): return tuple((kv_cache[i][0].buffer_address(), kv_cache[i][1].buffer_address()) for i in self.layers) + ( self.mask_dev.buffer_address(),) def _capture(self, kv_cache): out = self._forward_dev(kv_cache, self.layers, True) # warm-up compiles all kernels ttnn.synchronize_device(self.device) out.deallocate() tid = ttnn.begin_trace_capture(self.device, cq_id=0) out = self._forward_dev(kv_cache, self.layers, True) ttnn.end_trace_capture(self.device, tid, cq_id=0) ttnn.synchronize_device(self.device) self._trace = {"tid": tid, "out": out, "key": self._trace_key(kv_cache)} def release_trace(self): if self._trace is not None: ttnn.release_trace(self.device, self._trace["tid"]) self._trace = None def forward_hidden(self, emb: torch.Tensor, kv_cache, S: int, rope_delta: int) -> torch.Tensor: """emb [1, 64, hidden] (action_in_proj output) -> final-normed hidden [1, 64, hidden] fp32.""" Smax = kv_cache[self.layers[0]][0].shape[2] self.set_context(S, rope_delta, Smax) self._upload(self.x_dev, emb) if self.use_trace: if self._trace is None or self._trace["key"] != self._trace_key(kv_cache): self.release_trace() self._capture(kv_cache) ttnn.execute_trace(self.device, self._trace["tid"], cq_id=0, blocking=False) res = ttnn.to_torch(self._trace["out"]).float() else: out = self._forward_dev(kv_cache, self.layers, True) res = ttnn.to_torch(out).float() out.deallocate() return res.reshape(1, self.n_tok, self.tc.hidden) # ------------------------------------------------------------------------------------------ public API def step(self, x_t: torch.Tensor, t: float, kv_cache, S: int, rope_delta: int) -> torch.Tensor: """One expert forward: x_t [1, 64, 2], t scalar -> velocity v [1, 64, 2] fp32.""" emb = self.in_proj(x_t, t) hidden = self.forward_hidden(emb, kv_cache, S, rope_delta) return self.out_proj(hidden) def sample(self, x0: torch.Tensor, kv_cache, S: int, rope_delta: int, n_steps: int = 10, record: list | None = None) -> torch.Tensor: """Euler flow matching: t = linspace(0, 1, n_steps+1)[:-1], x <- x + dt * v(x, t). Returns the action.""" ts = torch.linspace(0.0, 1.0, n_steps + 1) x = x0.clone().float() self.set_context(S, rope_delta, kv_cache[self.layers[0]][0].shape[2], force=True) # see set_context for i in range(n_steps): v = self.step(x, float(ts[i]), kv_cache, S, rope_delta) if record is not None: record.append({"t": float(ts[i]), "x": x.clone(), "v": v.clone()}) x = x + (ts[i + 1] - ts[i]) * v return x # ------------------------------------------------------------------------------------------ testing helpers def run_layers(self, x: torch.Tensor, kv_cache, S: int, rope_delta: int, layers=None, final_norm: bool = False) -> torch.Tensor: """Eager: x [1, 64, hidden] -> output of the given layers (default: all loaded), optional final norm.""" layers = list(layers) if layers is not None else self.layers Smax = kv_cache[layers[0]][0].shape[2] self.set_context(S, rope_delta, Smax) self._upload(self.x_dev, x) out = self._forward_dev(kv_cache, layers, final_norm) res = ttnn.to_torch(out).float().reshape(1, self.n_tok, self.tc.hidden) if out is not self.x_dev: out.deallocate() return res def bench_step(self, kv_cache, S: int, rope_delta: int, iters: int = 10, warmup: int = 2) -> dict: """Wall time of the device part of one Euler step (traced, warm). Returns ms statistics.""" emb = torch.randn(1, self.n_tok, self.tc.hidden) for _ in range(warmup): self.forward_hidden(emb, kv_cache, S, rope_delta) ts = [] for _ in range(iters): t0 = time.perf_counter() self.forward_hidden(emb, kv_cache, S, rope_delta) ts.append((time.perf_counter() - t0) * 1e3) ts.sort() return {"ms_median": ts[len(ts) // 2], "ms_min": ts[0], "ms_max": ts[-1], "n_layers": len(self.layers)} def _trace_us(self, fn, reps: int, protected: set[int]) -> float: """Device time (us) per call of fn(), measured as a trace of `reps` back-to-back calls.""" dev = self.device r = fn() ttnn.synchronize_device(dev) tid = ttnn.begin_trace_capture(dev, cq_id=0) tmp = [fn() for _ in range(reps)] ttnn.end_trace_capture(dev, tid, cq_id=0) ttnn.synchronize_device(dev) ttnn.execute_trace(dev, tid, cq_id=0, blocking=True) t0 = time.perf_counter() for _ in range(3): ttnn.execute_trace(dev, tid, cq_id=0, blocking=True) us = (time.perf_counter() - t0) / 3 / reps * 1e6 ttnn.release_trace(dev, tid) for t in tmp: for tt in (t if isinstance(t, (tuple, list)) else (t,)): if isinstance(tt, ttnn.Tensor) and tt.buffer_address() not in protected: tt.deallocate() return us, r def bench_layer_traced(self, i: int, kv_cache, S: int, rope_delta: int, reps: int = 8) -> float: """Device time of one full layer (us): trace of `reps` back-to-back executions of layer i.""" k_cache, v_cache = kv_cache[i] self.set_context(S, rope_delta, k_cache.shape[2]) us, r = self._trace_us(lambda: self.layer_fn(i, self.x_dev, k_cache, v_cache), reps, set()) r.deallocate() return us def layer_breakdown(self, i: int, kv_cache, S: int, rope_delta: int, reps: int = 10) -> dict[str, float]: """Per-op device time (us) of layer i: each op is captured `reps` times in its own trace (no host overhead).""" k_cache, v_cache = kv_cache[i] self.set_context(S, rope_delta, k_cache.shape[2]) L = self.w[i] tc = self.tc eps = tc.rms_eps x = self.x_dev prot = {k_cache.buffer_address(), v_cache.buffer_address()} res: dict[str, float] = {} def timed(name, fn): us, r = self._trace_us(fn, reps, prot) res[name] = us return r h = timed("rms_norm", lambda: ttnn.rms_norm(x, epsilon=eps, weight=L.ln1, compute_kernel_config=self.ck_norm)) qkv = timed("wqkv", lambda: self._linear(h, L.wqkv, self.pc_qkv)) q, k, v = timed("split_heads", lambda: ttnn.experimental.nlp_create_qkv_heads( qkv, num_heads=tc.n_heads, num_kv_heads=tc.n_kv_heads, transpose_k_heads=False)) qn = timed("q_norm", lambda: ttnn.rms_norm(q, epsilon=eps, weight=L.qn, compute_kernel_config=self.ck_norm)) kn = timed("k_norm", lambda: ttnn.rms_norm(k, epsilon=eps, weight=L.kn, compute_kernel_config=self.ck_norm)) qr = timed("rope_q", lambda: ttnn.experimental.rotary_embedding_hf(qn, self.cos_dev, self.sin_dev, is_decode_mode=False)) kr = timed("rope_k", lambda: ttnn.experimental.rotary_embedding_hf(kn, self.cos_dev, self.sin_dev, is_decode_mode=False)) timed("fill_k", lambda: ttnn.fill_cache(k_cache, kr, 0, update_idx=self.tail)) timed("fill_v", lambda: ttnn.fill_cache(v_cache, v, 0, update_idx=self.tail)) a = timed("attention", lambda: self.attention_core(qr, k_cache, v_cache)) o = timed("concat_heads", lambda: ttnn.experimental.nlp_concat_heads(a)) o2 = timed("wo", lambda: self._linear(o, L.wo, self.pc_o)) x1 = timed("add", lambda: ttnn.add(x, o2)) h2 = timed("rms_norm2", lambda: ttnn.rms_norm(x1, epsilon=eps, weight=L.ln2, compute_kernel_config=self.ck_norm)) g = timed("w1_silu", lambda: self._linear(h2, L.w1, self.pc_w1, activation="silu")) u = timed("w3", lambda: self._linear(h2, L.w3, self.pc_w3)) gu = timed("mul", lambda: ttnn.multiply(g, u)) m = timed("w2", lambda: self._linear(gu, L.w2, self.pc_w2)) timed("add2", lambda: ttnn.add(x1, m)) res["sum_of_ops"] = sum(res.values()) return res