RWKV-Gradio-3 / rwkv7_fast_v3a.py
NeverlandPeter's picture
better
db88d26
Raw History Blame Contribute Delete
171 kB
#!/usr/bin/env python3
import argparse
import json
import os
import time
from dataclasses import dataclass
from pathlib import Path
import torch
import torch.nn.functional as F
from torch.utils.cpp_extension import load
HEAD_SIZE = 64
DTYPE = torch.float16
MODEL_PATH = "/dev/shm/rwkv7-g1f-7.2b-20260414-ctx8192.pth"
THIS_DIR = Path(__file__).resolve().parent
CUDA_DIR = THIS_DIR / "cuda"
CUTLASS_INCLUDE_DIR = Path(os.environ.get(
"CUTLASS_INCLUDE_DIR",
# Unlike parents[1], parent.parent is safe for shallow installs such as /app.
THIS_DIR.parent.parent / "third_party" / "cutlass" / "include",
))
L,C,H,N,V = 0,0,0,HEAD_SIZE,0
WKV_MODE = "fp16"
WKV_STATE_LAYOUT = "kv_v2"
WKV_FP32_PROFILE_MODE = "auto"
WKV_FP32_PROFILE_ACTIVE = "generic"
WKV_FP16_POLICY = "tuned"
WKV_FP16_DEVICE_PROFILE_MODE = "auto"
WKV_FP16_DEVICE_PROFILE_ACTIVE = "generic"
WKV_BH_GRID_MODE = "tuned"
# DeltaLog is opt-in because append phases intentionally keep wkv_state as a
# physical base. A caller must execute a complete static 0..M-1 cycle before
# treating state as materialized or exposing it to clone/reorder/save logic.
WKV_DELTALOG_M = 0
WKV_DELTALOG_PHASE = 0
WKV_DELTALOG_WORKSPACE_LAYOUT = "separate"
# Exact BnT1 gates confirmed positive in both graph-capture orders on 107.
# C4096 entries are the conservative intersection of the 7.2B and 13.3B runs.
# Do not admit C1024/C2048/C2560 B128 to the ordinary table from the
# single-layer component result:
# adjacent phases keep that layer's logs in L2, while real decode separates them
# by a complete model pass. Cold-cache NCU and full-model nsys both reject it.
# B64/B128/B256 also compare against cp/direct/clone eager kernels respectively;
# never interpolate a DeltaLog gate across the B=64 or B=128 dispatch boundary.
WKV_DELTALOG_TUNED_M = {
(768, 16): 2,
(768, 32): 3,
(768, 64): 3,
(768, 128): 3,
(768, 256): 3,
(768, 512): 3,
(1024, 16): 2,
(1024, 32): 3,
(1024, 64): 3,
(1024, 256): 3,
(1024, 512): 3,
(2048, 8): 2,
(2048, 16): 3,
(2048, 32): 3,
(2048, 64): 3,
(2048, 256): 3,
(2048, 512): 4,
(2560, 8): 2,
(2560, 16): 3,
(2560, 32): 3,
(2560, 64): 3,
(2560, 256): 3,
(2560, 512): 4,
(4096, 8): 2,
(4096, 16): 3,
(4096, 32): 3,
(4096, 64): 3,
(4096, 128): 3,
(4096, 256): 3,
(4096, 512): 4,
}
# APW-only gates compare a model-packed workspace against the existing
# per-layer DeltaLog graph. They require the graph hook below; never fold the
# B128-only admissions into WKV_DELTALOG_TUNED_M, where eager callers would run
# them with cold logs. Values are (M, layout, protected region).
WKV_DELTALOG_APW_TUNED = {
(768, 32): (3, "model_slot_packed", "full"),
(768, 64): (3, "model_slot_packed", "full"),
(768, 128): (3, "model_slot_packed", "slot0"),
(768, 256): (3, "model_slot_packed", "full"),
(1024, 32): (3, "model_slot_packed", "full"),
(1024, 64): (3, "model_slot_packed", "slot0"),
(1024, 128): (3, "model_slot_layer_packed", "full"),
(2048, 64): (3, "model_slot_layer_packed", "slot0"),
(2048, 128): (3, "model_slot_packed", "slot0"),
}
def convert_wkv_state_layout(
state: list,
source_layout: str,
target_layout: str = WKV_STATE_LAYOUT,
) -> list:
"""Return a state list whose WKV matrices use the requested physical ABI."""
layouts = {"vk_v1", "kv_v2"}
if source_layout not in layouts or target_layout not in layouts:
raise ValueError(
f"unsupported WKV state layout: {source_layout} -> {target_layout}")
if source_layout == target_layout:
return state
if len(state) != 3:
raise ValueError("RWKV state must contain shift, WKV, and elapsed entries")
def transpose_wkv(tensor: torch.Tensor) -> torch.Tensor:
if tensor.ndim < 2 or tensor.shape[-2:] != (HEAD_SIZE, HEAD_SIZE):
raise ValueError("WKV state matrices must end in [64,64]")
return tensor.transpose(-1, -2).contiguous()
converted = list(state)
wkv = state[1]
converted[1] = (
[transpose_wkv(layer_state) for layer_state in wkv]
if isinstance(wkv, list)
else transpose_wkv(wkv)
)
return converted
ADD_VEC_MODE = "tuned"
LAST_LN_MODE = "indexed"
LNX_MODE = "tuned"
LN_OWNER_MODE = "tuned"
LN_STATS_MODE = "tuned"
CMIX_LN_STATS_MODE = "tuned"
CMIX_MIX_MODE = "tuned"
CMIX_VALUE_LOOP_MODE = "tuned"
CMIX_T512_ACCUM_MODE = "tuned"
CMIX_T512_REUSE_MODE = "tuned"
TMIX_MIX_MODE = "tuned"
HEAD_GRID_MODE = "tuned"
HEAD_ALL_LOGITS_GEMM_MODE = "tuned"
HEAD_LAST_LOGITS_GEMM_MODE = "tuned"
FFN_DOWN_GEMM_MODE = "tuned"
ORIG_DENSE_GEMM_MODE = "tuned"
ROWS_CUTLASS_MODE = "auto"
ROWS_CUTLASS_AVAILABLE = False
GEMM_PROFILE_MODE = "auto"
GEMM_PROFILE_ACTIVE = "generic"
CUDA_DEVICE_PROFILE_ACTIVE = "generic"
# Mode IDs are defined by rwkv7_wkv_fp32_v2.cu. These gates are deliberately
# tied to the 82-SM mobile Blackwell profile: CTA supply and power-limited
# clocks change the tile/large crossover on other SM120 devices.
SM120_SM82_WKV_FP32_MODES = {
(1, 1): 1, # large instead of the generic T=1 tile path
# Axis gates admitted on the 175 W mobile 5090 by component scans plus
# same-process full-model gates in both graph capture orders.
(1, 8): 9,
# large_occ8 lowers the large kernel from 134 to 128 registers without
# spilling. Keep exact gates: shapes with too little CTA supply or long
# per-CTA token loops regress even though they use the same kernel body.
(8, 8): 9,
(8, 16): 9,
(16, 8): 9,
(8, 32): 9,
(32, 8): 9,
(16, 16): 9,
(16, 32): 9,
(32, 16): 9,
(8, 64): 9,
(16, 64): 9,
(64, 16): 9,
(32, 32): 9,
}
SM120_SM82_WKV_FP32_MODES_BY_C = {
768: {
**{bt: 1 for bt in (
(2, 64), (4, 32), (2, 128), (4, 64), (2, 160),
(4, 80), (2, 256), (4, 128), (2, 512), (4, 256),
)},
(128, 8): 9,
},
1024: {
**{bt: 1 for bt in (
(4, 7), (3, 10), (5, 6), (4, 8), (2, 128),
(2, 160), (2, 256), (2, 512),
)},
**{bt: 9 for bt in (
(32, 16), (32, 32), (128, 8),
)},
},
2048: {
**{bt: 1 for bt in (
(1, 32), (1, 64), (1, 128), (1, 256), (1, 320),
(1, 512),
)},
**{bt: 9 for bt in (
(16, 16), (64, 4), (16, 20), (64, 5), (16, 32),
(64, 8),
)},
(160, 2): 7,
},
2560: {
**{bt: 1 for bt in (
(1, 8), (1, 28), (1, 31), (1, 64), (1, 128),
(1, 256), (1, 320), (1, 1024),
)},
**{bt: 9 for bt in (
(64, 2), (64, 4), (16, 20), (32, 10), (40, 8),
(16, 64), (64, 16),
)},
},
4096: SM120_SM82_WKV_FP32_MODES,
}
ROWS_CUTLASS_UP_4096 = frozenset((128, 512, 1024, 1536, 2048, 3072, 4096, 6144, 8192))
ROWS_CUTLASS_DOWN_4096 = frozenset((128, 192, 256, 512, 1024, 1536, 2048, 3072, 4096, 6144, 8192))
ROWS_CUTLASS_UP_BY_C = {
768: frozenset((512, 2048, 3072, 4096, 6144, 8192)),
1024: frozenset((384, 512, 768, 1536, 2048, 3072, 4096, 6144, 8192)),
2048: frozenset((192, 256, 1024, 1536, 2048, 3072, 4096, 6144, 8192)),
# rows512 flipped with graph-capture order and is deliberately excluded.
2560: frozenset((192, 256, 384, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192)),
4096: ROWS_CUTLASS_UP_4096,
}
# C768 rows768/1536 are stable only for the measured BnTn factorizations.
# Their B1Tn cases flipped with capture order, so a rows-wide route is unsafe.
ROWS_CUTLASS_UP_BT_BY_C = {
768: frozenset(((24, 32), (64, 12), (32, 48), (64, 24))),
}
ROWS_CUTLASS_DOWN_BY_C = {
768: frozenset((2048, 3072, 4096, 6144, 8192)),
1024: frozenset((1536, 2048, 3072, 4096, 6144, 8192)),
2048: frozenset((512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192)),
2560: frozenset((2048, 3072, 4096, 6144, 8192)),
4096: ROWS_CUTLASS_DOWN_4096,
}
ROWS_CUTLASS_C2C_BY_C = {
768: frozenset((2048, 3072, 4096, 6144, 8192)),
1024: frozenset((1536, 2048, 3072, 4096, 6144, 8192)),
2048: frozenset((768, 1024, 1536, 2048, 3072, 4096, 6144, 8192)),
2560: frozenset((768, 1024, 2048, 3072, 4096, 6144, 8192)),
4096: frozenset((1536, 2048, 3072, 4096, 6144, 8192)),
}
VRES_GATE_MODE = "tuned"
EMB_DEVICE = "cpu"
RKV_MODE = "off"
CMIX_SPARSE = "no-fc"
LOWRANK_WEIGHT = "both"
ORIG_LINEAR_GROUPS = {"att_c2c", "ffn_key", "head"}
PP_DEVICES: list[int] = []
# Set before constructing RWKV7; these opt-outs support hosts with restricted
# GPU initialization (e.g. HF Gradio ZeroGPU integrations). Keep both True on
# ordinary CUDA hosts. They do not make model construction entirely CUDA-free.
# True: precompute LN0 for the whole vocabulary once during model loading.
# False: store raw embeddings and run LN0 on selected tokens on every embed().
# This avoids the load-time LN0 kernel, but adds inference work and uses FP16
# LN0 parameters instead of the BF16-source precompute (rounding may differ).
PRECOMPUTE_EMB_LN0 = True
# False: skip only the final sync_all() and CUDA memory report in __init__.
# Weight transfers/other CUDA work remain; the model-ready timer then need not
# include GPU completion. This flag does not change steady-state inference.
SYNC_INIT = True
LOWRANK_SUFFIXES = ("att.w1", "att.w2", "att.a1", "att.a2", "att.g1", "att.g2", "att.v1", "att.v2")
LOWRANK_IN_ROWS_T = 7
LOWRANK_OUT_ROWS_T = 4
LOWRANK_FUSED_MIN_C = 1024
LOWRANK_GEMM_MODE = "tuned"
CMIX_NOFC_ROW20_MAX_T = 5
CMIX_NOFC_T512_MIN_ROWS = 8
LN1_TMIX_FUSE = True
CMIX_B1T1_SPARSE = "b1t1_sparse"
CMIX_ROWS2_SPARSE = "rows2_sparse"
CMIX_B1T1_NOFC = "b1t1_nofc"
CMIX_ROWS2_NOFC = "rows2_nofc"
CMIX_DENSE = "dense"
LNX_WARP_MIN_HEAD_TASKS = 4096
LNX_WARP_B1_T_4096 = frozenset((64, 96, 128, 160, 192, 240, 248, 264, 512))
CMIX_MIX_3D_B1_T_4096 = frozenset((2, 4, 16, 64, 512))
TMIX_MIX_3D_B1_T_4096 = frozenset((2, 4, 16, 64, 512))
CMIX_VALUE_SPLIT2_BT_4096 = frozenset({
(1, 1), (1, 2), (2, 1), (1, 3), (3, 1), (1, 4), (2, 2),
(1, 5), (5, 1), (3, 2), (1, 7), (7, 1),
})
CMIX_T512_ACC2_BT_4096 = frozenset({(8, 1), (4, 2), (3, 3), (19, 1)})
# Cross-width acc2 is an ILP/occupancy tradeoff, not a rows-wide property.
# These exact B/T shapes were positive in both balanced eight-slot orders;
# nearby factorizations flipped and must remain on one accumulator.
CMIX_T512_ACC2_BT_BY_C = {
2048: frozenset({
(1, 8), (2, 4), (8, 1),
(12, 1),
(4, 4), (16, 1),
}),
2560: frozenset({
(1, 8), (2, 4),
(1, 12), (3, 4), (4, 3), (12, 1),
(1, 16), (2, 8), (4, 4), (8, 2), (16, 1),
(1, 19),
}),
4096: CMIX_T512_ACC2_BT_4096,
}
CMIX_T512_REUSE_BT_4096 = frozenset({
(1, 8), (2, 4), (4, 2), (8, 1),
(3, 3),
(1, 11), (11, 1),
(1, 12), (3, 4), (4, 3), (12, 1),
(1, 13), (13, 1),
(1, 14), (14, 1),
(1, 15),
(1, 16), (2, 8), (4, 4), (8, 2), (16, 1),
(1, 17),
(1, 19), (19, 1),
(4, 5), (5, 4), (10, 2), (20, 1),
})
# Cross-width t512 mask-reuse winners from balanced eight-slot full-model E2E.
# Keep these exact: nearby B/T factorizations at the same rows flipped sign.
# C=768 never reaches the t512 kernel (C % 512 != 0); its apparent positive
# slots were same-code noise and must not be restored or generalized by rows.
CMIX_T512_REUSE_BT_BY_C = {
1024: frozenset({
(1, 16),
(19, 1),
}),
2048: frozenset({
(8, 1),
(1, 12), (3, 4), (4, 3), (12, 1),
(1, 16), (2, 8), (4, 4), (8, 2), (16, 1),
(1, 19), (19, 1),
}),
2560: frozenset({
(1, 8), (2, 4), (4, 2),
(1, 12), (3, 4), (4, 3), (12, 1),
(1, 16), (16, 1),
(1, 19), (19, 1),
}),
4096: CMIX_T512_REUSE_BT_4096,
}
# Current-environment all-logits head winners. These must not affect the usual
# last-logits path: the same row count can mean either B or B*T. Lt indices are
# GPU/CUDA-specific, so linear_f16_orig_lt_cfg deliberately falls back to algo 0
# when a requested index is unavailable after an environment change.
HEAD_ALL_LOGITS_GEMM_4096 = {
24: (0, 0),
32: (0, 0),
160: (0, 7),
192: (0, 5),
}
# Ordinary last-logits head winners measured with the complete real model in
# both graph-capture orders. The exact (B,T) guard is intentional: this GEMM
# has B rows regardless of T, while its fixed saving falls below graph-level
# noise as the body grows. Do not broaden this to a B-only dispatch table.
HEAD_LAST_LOGITS_GEMM_4096 = {
(24, 1): (0, 0),
(24, 2): (0, 0),
(24, 4): (0, 0),
(24, 8): (0, 0),
(32, 1): (0, 0),
(32, 2): (0, 0),
(32, 4): (0, 0),
(32, 8): (0, 0),
(32, 16): (0, 0),
(160, 1): (0, 7),
(160, 2): (0, 7),
(160, 4): (0, 7),
(160, 32): (0, 7),
(192, 1): (0, 5),
(192, 2): (0, 5),
(192, 4): (0, 5),
(192, 8): (0, 5),
(192, 16): (0, 5),
(192, 32): (0, 5),
}
# Cross-size candidates use the same strict (B,T) ownership rule as C=4096.
# These entries intentionally remain exact: selecting by B alone is dangerous
# because the head has B rows while the surrounding model body has B*T rows.
# A fixed head saving can therefore turn into an E2E regression at another T.
HEAD_LAST_LOGITS_GEMM_BY_C = {
768: {
(32, 4): (0, 0),
(32, 8): (0, 0),
(32, 16): (0, 0),
(32, 32): (0, 0),
},
1024: {
(4, 2): (0, 0),
(4, 4): (0, 0),
(4, 8): (0, 0),
(4, 16): (0, 0),
(16, 16): (32, 1),
(32, 4): (0, 0),
(32, 8): (0, 0),
(32, 16): (0, 0),
(32, 32): (0, 0),
},
2048: {
(32, 4): (0, 0),
(32, 8): (0, 0),
(32, 16): (0, 0),
},
2560: {
(32, 4): (0, 0),
(32, 16): (0, 0),
},
4096: HEAD_LAST_LOGITS_GEMM_4096,
}
# Runtime-layout FFN down winners on Blackwell/CUDA 13.2. The heuristic index
# is environment-specific, so the production op intentionally falls back to
# algo 0 if an index disappears. Exact shape/rows guards prevent accidental
# reuse by another model size; this table adds no persistent weight copy.
FFN_DOWN_GEMM_4096 = {
48: (32, 1),
256: (32, 5),
}
# Cross-size runtime-layout FFN down winners from strict same-graph, full-layer
# streams. Keep exact C/rows/shape guards: adjacent rows frequently select a
# different Lt heuristic, and the heuristic index is environment-specific.
FFN_DOWN_GEMM_BY_C = {
768: {256: (32, 2)},
1024: {64: (32, 1), 256: (32, 4), 1024: (0, 1)},
2048: {512: (128, 1)},
2560: {256: (32, 6), 512: (32, 1), 1024: (0, 0)},
4096: FFN_DOWN_GEMM_4096,
}
# Exact original-layout dense winners on Blackwell/CUDA 13.2. These replace
# severe heuristic cliffs in the older rows-range table. Keep both the C/N/K
# guards and the non-strict production Lt call: heuristic indices are not ABI
# and may disappear after a CUDA, driver, or GPU change.
ORIG_ATT_C2C_GEMM_4096 = {
8: ("lt", 128, 0),
16: ("lt", 32, 0),
24: ("lt", 0, 0),
32: ("gemmex", 0, 0),
48: ("lt", 32, 0),
64: ("lt", 32, 0),
96: ("gemmex", 0, 0),
192: ("lt", 32, 2),
}
ORIG_FFN_KEY_GEMM_4096 = {
8: ("lt", 32, 1),
16: ("lt", 128, 0),
24: ("lt", 0, 2),
32: ("lt", 0, 2),
128: ("lt", 32, 3),
192: ("lt", 0, 3),
384: ("lt", 0, 2),
}
# Strict same-graph winners measured over every layer of each real model.
# These tables intentionally describe isolated exact rows rather than ranges:
# old range dispatches contain large heuristic cliffs on the smaller widths.
ORIG_ATT_C2C_GEMM_BY_C = {
768: {
8: ("lt", 32, 2),
16: ("lt", 32, 1),
32: ("lt", 32, 0),
1024: ("lt", 32, 1),
},
1024: {
8: ("lt", 128, 2),
16: ("lt", 128, 2),
32: ("lt", 32, 3),
64: ("lt", 32, 4),
128: ("lt", 128, 6),
512: ("lt", 128, 2),
1024: ("lt", 128, 1),
},
2048: {
8: ("lt", 32, 1),
16: ("lt", 128, 2),
32: ("lt", 32, 1),
64: ("lt", 32, 1),
1024: ("lt", 128, 0),
},
2560: {
8: ("lt", 32, 2),
16: ("lt", 128, 2),
32: ("lt", 128, 3),
512: ("lt", 128, 2),
},
4096: ORIG_ATT_C2C_GEMM_4096,
}
ORIG_FFN_KEY_GEMM_BY_C = {
768: {
8: ("lt", 0, 1),
16: ("lt", 0, 1),
64: ("lt", 32, 1),
512: ("lt", 0, 0),
1024: ("lt", 32, 5),
},
1024: {
8: ("lt", 0, 1),
16: ("lt", 0, 1),
32: ("lt", 32, 0),
64: ("lt", 0, 0),
512: ("lt", 128, 0),
1024: ("lt", 0, 3),
},
2048: {
32: ("lt", 32, 3),
256: ("lt", 128, 1),
512: ("lt", 0, 3),
1024: ("lt", 0, 1),
},
2560: {
32: ("lt", 0, 0),
512: ("lt", 128, 1),
1024: ("lt", 128, 0),
},
4096: ORIG_FFN_KEY_GEMM_4096,
}
# cuBLASLt heuristic indices and the rows-CUTLASS crossover depend on the GPU
# tile supply. These exact overrides were admitted on the 82-SM RTX 5090
# Laptop with PyTorch cu132 / CUDA 13.3 by full-weight streams, dual-order E2E, and independent
# sustained-process gates at +300/0 under the 175W power wall. Several faster
# isolated strategies were rejected because they lowered the sustained clock.
# Keep these ahead of generic dispatch, but never apply them to another device.
# The non-strict Lt entry preserves the existing algo-0 fallback.
SM120_SM82_ORIG_ATT_C2C_GEMM_4096 = {
28: ("gemmex", 0, 0),
29: ("gemmex", 0, 0),
30: ("gemmex", 0, 0),
31: ("gemmex", 0, 0),
256: ("lt", 0, 0),
257: ("gemmex", 0, 0),
272: ("gemmex", 0, 0),
280: ("lt", 0, 0),
288: ("lt", 32, 0),
304: ("gemmex", 0, 0),
320: ("lt", 128, 0),
384: ("lt", 128, 4),
400: ("lt", 32, 0),
416: ("lt", 0, 0),
448: ("lt", 0, 0),
528: ("lt", 0, 0),
544: ("lt", 0, 0),
}
SM120_SM82_ORIG_FFN_KEY_GEMM_4096 = {
**{rows: (0, 2) for rows in range(28, 32)},
257: (32, 5),
272: (32, 5),
280: (32, 5),
288: (32, 5),
304: (128, 5),
320: (32, 5),
336: (32, 4),
384: (32, 4),
400: (128, 4),
416: (32, 4),
448: (128, 4),
528: (32, 5),
544: (128, 5),
}
SM120_SM82_FFN_DOWN_GEMM_4096 = {
**{rows: (32, 1) for rows in range(28, 32)},
32: (32, 1),
257: (32, 0),
272: (0, 0),
320: (0, 0),
336: (0, 0),
352: (0, 0),
384: (0, 0),
400: (128, 3),
416: (32, 3),
448: (0, 0),
480: (32, 4),
496: (128, 4),
528: (0, 0),
544: (0, 0),
}
SM120_SM82_HEAD_LAST_LOGITS_GEMM_4096 = {
(256, 1): (32, 5),
(257, 1): (128, 2),
(272, 1): (128, 5),
(280, 1): (128, 4),
(288, 1): (0, 2),
(304, 1): (32, 5),
(320, 1): (32, 4),
(336, 1): (0, 4),
(384, 1): ("cutlass", 0, 12),
(400, 1): (128, 3),
(416, 1): (0, 4),
(448, 1): (0, 3),
(480, 1): ("cutlass", 0, 12),
(496, 1): ("cutlass", 0, 12),
(512, 1): ("cutlass", 0, 12),
(528, 1): (32, 3),
(544, 1): (0, 4),
}
# Exact overrides for the 188-SM RTX PRO 6000 Blackwell workstation. These
# entries were admitted on GPU1/2/3 at 450 W and fixed 1035/13365 MHz using the
# all six real model weights across five channel widths, FP16 and FP32IO16, and
# both graph replay orders with independent recurrent state. The large gains
# repair cuBLAS heuristic cliffs at B28..31 and B320. Do not widen the row
# ranges or reuse this table at another SM count without E2E gates.
SM120_SM188_ORIG_ATT_C2C_GEMM_BY_C = {
768: {
**{rows: ("gemmex", 0, 0) for rows in range(28, 32)},
},
1024: {
**{rows: ("lt", 32, 3) for rows in range(28, 32)},
},
2048: {
28: ("lt", 32, 2),
**{rows: ("lt", 32, 1) for rows in range(29, 32)},
320: ("lt", 128, 2),
},
2560: {
320: ("lt", 32, 3),
},
4096: {
**{rows: ("gemmex", 0, 0) for rows in range(28, 32)},
# Do not restore the 82-SM Lt128/a0 choice here. On 188 SM it was only
# neutral for attention, while the matching 82-SM key/head choices
# caused a large full-model regression at B320.
320: ("gemmex", 0, 0),
},
}
SM120_SM188_ORIG_FFN_KEY_GEMM_BY_C = {
2048: {320: (0, 0)},
2560: {320: (128, 0)},
4096: {
**{rows: (0, 2) for rows in range(28, 32)},
320: (0, 0),
},
}
SM120_SM188_FFN_DOWN_GEMM_BY_C = {
2048: {320: (32, 3)},
4096: {
**{rows: (32, 1) for rows in range(28, 32)},
320: (0, 1),
},
}
SM120_SM188_HEAD_LAST_LOGITS_GEMM_BY_C = {
2048: {(320, 1): (32, 2)},
2560: {(320, 1): (128, 3)},
4096: {(320, 1): (32, 3)},
}
# Conservative cross-SM fallback for unrecognized Blackwell GPUs. Integer keys
# are deliberately row-wide only after every integer B/T factorization passed
# on both the 82-SM laptop and 188-SM workstation, in FP16 and FP32IO16 and in
# both graph capture orders. Tuple keys are exact B/T gates and must never leak
# to another factorization. In particular, C=2560 rows320 key-only wins at five
# exact shapes, while both the row-wide route and attention+key combinations
# have large FP16 regressions at several middle factorizations.
SM120_DEFAULT_ORIG_ATT_C2C_GEMM_4096 = {
**{rows: ("gemmex", 0, 0) for rows in range(28, 32)},
(257, 1): ("gemmex", 0, 0),
(272, 1): ("gemmex", 0, 0),
(280, 1): ("lt", 0, 0),
(304, 1): ("gemmex", 0, 0),
(320, 1): ("lt", 128, 0),
(528, 1): ("lt", 0, 0),
(544, 1): ("lt", 0, 0),
}
SM120_DEFAULT_ORIG_FFN_KEY_GEMM_4096 = {
**{rows: (0, 2) for rows in range(28, 32)},
(257, 1): (32, 5),
(272, 1): (32, 5),
(280, 1): (32, 5),
(304, 1): (128, 5),
(320, 1): (32, 5),
(528, 1): (32, 5),
(544, 1): (128, 5),
}
SM120_DEFAULT_FFN_DOWN_GEMM_4096 = {
**{rows: (32, 1) for rows in range(28, 32)},
(257, 1): (32, 0),
(272, 1): (0, 0),
(320, 1): (0, 0),
(528, 1): (0, 0),
(544, 1): (0, 0),
}
SM120_DEFAULT_HEAD_LAST_LOGITS_GEMM_4096 = {
(257, 1): (128, 2),
(272, 1): (128, 5),
(280, 1): (128, 4),
(304, 1): (32, 5),
(320, 1): (32, 4),
# This is a small but dual-order positive CUTLASS win on both devices.
(512, 1): ("cutlass", 0, 12),
(528, 1): (32, 3),
(544, 1): (0, 4),
}
SM120_DEFAULT_ORIG_ATT_C2C_GEMM_BY_C = {
768: {
# C768 is especially sensitive to B/T factorization under the 175 W
# wall. These are the intersection of two independent 82-SM gates;
# nearby factors at the same rows flipped and remain generic.
**{
shape: ("lt", 32, 3)
for shape in (
(1, 28), (2, 14), (4, 7), (14, 2), (28, 1),
(1, 29),
(1, 30), (3, 10), (5, 6), (10, 3), (30, 1),
(1, 31), (31, 1),
)
},
},
1024: {
**{rows: ("lt", 32, 3) for rows in range(28, 32)},
},
2048: {
28: ("lt", 32, 2),
**{rows: ("lt", 32, 1) for rows in range(29, 32)},
320: ("lt", 128, 2),
},
4096: SM120_DEFAULT_ORIG_ATT_C2C_GEMM_4096,
}
SM120_DEFAULT_ORIG_FFN_KEY_GEMM_BY_C = {
2048: {320: (0, 0)},
2560: {
**{
shape: (128, 0)
for shape in (
(1, 320), (2, 160), (4, 80),
(160, 2), (320, 1),
)
},
},
4096: SM120_DEFAULT_ORIG_FFN_KEY_GEMM_4096,
}
SM120_DEFAULT_FFN_DOWN_GEMM_BY_C = {
2048: {320: (32, 3)},
4096: SM120_DEFAULT_FFN_DOWN_GEMM_4096,
}
SM120_DEFAULT_HEAD_LAST_LOGITS_GEMM_BY_C = {
2048: {(320, 1): (32, 2)},
4096: SM120_DEFAULT_HEAD_LAST_LOGITS_GEMM_4096,
}
# Mobile RTX 5090 (82 SM, 175 W) winners for the standard four-axis grid.
# Row-wide entries passed every tested B/T factorization. Exact entries are
# intentionally sparse: adjacent factorizations can change CUDA Graph power
# and clock behavior enough to reverse a small component-only gain.
SM120_SM82_ORIG_ATT_C2C_GEMM_BY_C = {
768: {
**SM120_DEFAULT_ORIG_ATT_C2C_GEMM_BY_C.get(768, {}),
**{
shape: ("gemmex", 0, 0)
for shape in ((1, 16), (2, 8), (4, 4))
},
},
1024: {
**SM120_DEFAULT_ORIG_ATT_C2C_GEMM_BY_C.get(1024, {}),
32: ("lt", 32, 4),
**{
shape: ("lt", 32, 0)
for shape in ((1, 64), (4, 16))
},
128: ("lt", 128, 4),
256: ("lt", 128, 1),
**{
shape: ("lt", 0, 0)
for shape in (
(1, 512), (2, 256), (4, 128), (8, 64), (16, 32),
(32, 16), (64, 8), (128, 4), (256, 2),
)
},
**{
shape: ("gemmex", 0, 0)
for shape in ((16, 64), (32, 32), (64, 16))
},
},
2048: {
**SM120_DEFAULT_ORIG_ATT_C2C_GEMM_BY_C.get(2048, {}),
4: ("lt", 128, 0),
8: ("lt", 0, 0),
**{
shape: ("lt", 0, 0)
for shape in ((1, 16), (2, 8), (4, 4), (8, 2))
},
32: ("lt", 0, 1),
**{
shape: ("lt", 32, 0)
for shape in ((1, 64), (4, 16), (8, 8), (16, 4))
},
**{
shape: ("lt", 32, 0)
for shape in ((1, 256), (8, 32), (16, 16), (32, 8))
},
**{
shape: ("lt", 0, 0)
for shape in (
(1, 512), (2, 256), (4, 128), (8, 64), (16, 32),
(32, 16), (64, 8), (128, 4), (256, 2),
)
},
**{
shape: ("lt", 32, 5)
for shape in ((16, 64), (32, 32), (64, 16))
},
},
2560: {
**SM120_DEFAULT_ORIG_ATT_C2C_GEMM_BY_C.get(2560, {}),
4: ("lt", 32, 0),
8: ("lt", 0, 0),
16: ("lt", 32, 0),
32: ("gemmex", 0, 0),
128: ("lt", 128, 0),
256: ("lt", 0, 0),
512: ("lt", 0, 0),
},
4096: SM120_SM82_ORIG_ATT_C2C_GEMM_4096,
}
SM120_SM82_ORIG_FFN_KEY_GEMM_BY_C = {
768: {
**{
shape: (32, 1)
for shape in ((1, 16), (2, 8), (4, 4))
},
512: (32, 0),
1024: (128, 0),
},
1024: {
**{shape: (32, 1) for shape in ((2, 2), (4, 1))},
32: (0, 1),
128: (0, 0),
256: (32, 0),
**{
shape: (128, 0)
for shape in (
(1, 512), (2, 256), (4, 128), (8, 64), (16, 32),
(32, 16), (64, 8), (128, 4), (256, 2),
)
},
**{shape: (0, 0) for shape in ((16, 64), (32, 32), (64, 16))},
},
2048: {
**SM120_DEFAULT_ORIG_FFN_KEY_GEMM_BY_C.get(2048, {}),
32: (32, 1),
**{
shape: ("gemmex", 0, 0)
for shape in ((1, 64), (4, 16), (8, 8), (16, 4))
},
(1, 128): (128, 1),
**{
shape: (32, 5)
for shape in ((1, 256), (8, 32), (16, 16), (32, 8))
},
**{
shape: (128, 3)
for shape in (
(1, 512), (2, 256), (4, 128), (8, 64), (16, 32),
(32, 16), (64, 8), (128, 4), (256, 2),
)
},
**{shape: (128, 2) for shape in ((16, 64), (32, 32), (64, 16))},
},
2560: {
**SM120_DEFAULT_ORIG_FFN_KEY_GEMM_BY_C.get(2560, {}),
8: (128, 0),
16: (32, 1),
32: (0, 4),
128: (128, 0),
512: (0, 0),
},
4096: SM120_SM82_ORIG_FFN_KEY_GEMM_4096,
}
SM120_SM82_FFN_DOWN_GEMM_BY_C = {
768: {
512: (32, 2),
1024: (128, 2),
},
1024: {
32: (32, 7),
**{shape: (128, 4) for shape in ((1, 64), (4, 16))},
256: (128, 2),
**{
shape: (128, 2)
for shape in (
(1, 512), (2, 256), (4, 128), (8, 64), (16, 32),
(32, 16), (64, 8), (128, 4), (256, 2),
)
},
**{shape: (128, 1) for shape in ((16, 64), (32, 32), (64, 16))},
},
2048: {
**SM120_DEFAULT_FFN_DOWN_GEMM_BY_C.get(2048, {}),
**{
shape: (32, 2)
for shape in ((1, 16), (2, 8), (4, 4), (8, 2))
},
32: (128, 2),
**{
shape: (128, 2)
for shape in ((1, 64), (4, 16), (8, 8), (16, 4))
},
},
2560: {
32: (32, 1),
128: (0, 1),
256: (32, 0),
512: (0, 0),
},
4096: SM120_SM82_FFN_DOWN_GEMM_4096,
}
SM120_SM82_HEAD_LAST_LOGITS_GEMM_BY_B_C = {
768: {
1: (0, 0), 20: (32, 1), 29: (0, 2), 30: (0, 0),
31: (128, 1), 32: (128, 1), 80: (0, 0), 320: (0, 0),
},
1024: {
1: ("gemmex", 0, 0), 28: (0, 1), 29: (32, 1), 30: (128, 1),
31: (128, 1), 32: (0, 1), 256: (0, 0),
512: ("gemmex", 0, 0),
},
2048: {
1: ("gemmex", 0, 0), 2: (128, 0), 28: (0, 2), 29: (0, 2),
30: (0, 2), 31: (0, 2), 160: (128, 1), 256: (32, 0),
320: (0, 1), 512: (0, 1),
},
2560: {
1: (0, 3), 2: (0, 3), 20: (0, 2), 28: (0, 2), 29: (0, 2),
30: (0, 2), 31: (0, 2), 32: (0, 2), 64: (32, 3),
80: (128, 0), 128: (128, 1), 160: (32, 3), 256: (32, 1),
320: (32, 0), 512: (0, 1), 1024: (32, 4),
},
}
SM120_SM82_HEAD_LAST_LOGITS_ACCEPTED_BT_BY_C = {
# Exact-shape allowlist after both the isolated dense gate and the final
# combined dense+WKV gate. Do not widen these entries by row count.
768: (
(1, 16), (1, 512), (1, 1024),
(32, 16), (32, 32), (80, 4), (320, 1),
),
1024: (
(1, 8), (1, 30), (1, 31), (1, 32), (1, 64), (1, 128),
(1, 256), (1, 512), (32, 4), (32, 8),
(32, 16), (32, 32), (256, 1), (256, 2), (512, 1), (512, 2),
),
2048: (
(1, 4), (1, 8), (1, 16), (1, 32), (1, 64), (1, 128),
(1, 256), (1, 512), (2, 2), (2, 4), (2, 8), (2, 16),
(2, 128), (2, 160), (2, 256),
),
2560: (
(1, 4), (1, 8), (1, 16), (1, 32), (1, 128), (1, 256),
(1, 512), (2, 2), (2, 4), (2, 8), (2, 16), (2, 64),
(2, 128), (2, 256), (32, 1), (32, 4), (32, 8), (32, 10),
(32, 16), (64, 2), (64, 4), (64, 8), (80, 4), (128, 1),
(128, 2), (128, 4), (256, 1), (256, 2), (512, 1), (512, 2),
),
}
# C2560 B512T2 is +0.470% in the dual-order FP16 gate. FP32IO16 cannot keep
# both 24 GB graphs resident and has no matching sequential dense gate, so it
# must fall back instead of inheriting the shared head table without evidence.
SM120_SM82_HEAD_LAST_LOGITS_FP16_ONLY_BT_BY_C = {
2560: frozenset(((512, 2),)),
}
SM120_SM82_HEAD_LAST_LOGITS_GEMM_BY_C = {
**{
channels: {
**SM120_DEFAULT_HEAD_LAST_LOGITS_GEMM_BY_C.get(channels, {}),
**{
bt: SM120_SM82_HEAD_LAST_LOGITS_GEMM_BY_B_C[channels][bt[0]]
for bt in SM120_SM82_HEAD_LAST_LOGITS_ACCEPTED_BT_BY_C[channels]
},
}
for channels in (768, 1024, 2048, 2560)
},
4096: SM120_SM82_HEAD_LAST_LOGITS_GEMM_4096,
}
# RTX 5090 FE (170 SM) admission sets. Every shape below was positive in both
# graph capture orders and in all ten paired 8-iteration rounds at requested
# 1500 MHz / 450 W. Keep FP16 and FP32IO16 separate: a few otherwise identical
# dense routes cross zero when recurrent-state precision changes graph power.
def _factorizations(rows: int, batches: tuple[int, ...]) -> frozenset[tuple[int, int]]:
return frozenset((batch, rows // batch) for batch in batches)
_SM170_C768_SM82 = (
_factorizations(16, (1, 2, 4))
| _factorizations(1024, (1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024))
)
_SM170_C768_SM188 = frozenset(((7, 4), (29, 1), (2, 15), (6, 5), (15, 2)))
_SM170_C1024_COMMON = (
frozenset(((1, 64), (4, 16)))
| _factorizations(128, (1, 2, 4, 8, 16, 32, 64, 128))
| _factorizations(256, (1, 2, 4, 8, 16, 32, 64, 128, 256))
| _factorizations(512, (1, 2, 4, 8, 16, 32, 64, 128, 256))
)
_SM170_C2048_COMMON = (
_factorizations(8, (1, 2, 4, 8))
| _factorizations(64, (1, 4, 8, 16))
| frozenset(((1, 128),))
| _factorizations(256, (1, 8, 16, 32))
| _factorizations(512, (1, 2, 4, 8, 16, 32, 64, 128, 256))
)
_SM170_C2560_COMMON = (
_factorizations(256, (1, 2, 4, 8, 16, 32, 64, 128, 256))
| _factorizations(512, (1, 2, 4, 8, 16, 32, 64, 128, 256, 512))
)
_SM170_C2560_SM188 = _factorizations(
320, (1, 2, 4, 5, 8, 10, 16, 20, 32, 40, 64, 80, 160, 320))
SM120_SM170_DENSE_ACCEPTED_BT = {
("sm82", "fp16", 768): _SM170_C768_SM82,
("sm82", "fp32io16", 768): _SM170_C768_SM82,
("sm188", "fp16", 768): _SM170_C768_SM188,
("sm188", "fp32io16", 768): _SM170_C768_SM188,
("sm82", "fp16", 1024): _SM170_C1024_COMMON,
("sm82", "fp32io16", 1024): _SM170_C1024_COMMON - {(128, 2)},
("sm82", "fp16", 2048): _SM170_C2048_COMMON | {(1, 32)},
("sm82", "fp32io16", 2048): _SM170_C2048_COMMON,
("sm82", "fp16", 2560): _SM170_C2560_COMMON | {(64, 2)},
("sm82", "fp32io16", 2560): _SM170_C2560_COMMON,
("sm188", "fp16", 2560): _SM170_C2560_SM188,
("sm188", "fp32io16", 2560): _SM170_C2560_SM188,
}
def _bnt1(*batches: int) -> frozenset[tuple[int, int]]:
return frozenset((batch, 1) for batch in batches)
# Native 170-SM dense routes found with one-real-weight algorithm screening,
# then admitted only by real-model dual-order 8x5 gates. Configuration tuple
# order is attention, FFN key, FFN down, last logits; None keeps the current
# route for that group. These are exact BnT1 entries: never widen them by rows.
SM120_SM170_NATIVE_DENSE_CONFIGS_BY_C = {
768: {
(16, 1): (("lt", 32, 0), (0, 0), (128, 1), (0, 5)),
(24, 1): (("lt", 0, 0), None, (32, 3), (128, 2)),
(30, 1): (("gemmex", 0, 0), None, (128, 6), (32, 2)),
(31, 1): (("lt", 0, 0), None, (32, 6), (32, 2)),
(32, 1): (None, None, (32, 3), (0, 2)),
(48, 1): (("gemmex", 0, 0), (128, 3), None, None),
(64, 1): (("lt", 32, 1), None, (128, 3), None),
(96, 1): (("gemmex", 0, 0), None, None, (128, 1)),
(128, 1): (None, (128, 1), None, (32, 1)),
(192, 1): (None, (128, 0), (32, 2), (0, 1)),
(256, 1): (None, None, (32, 1), (0, 2)),
(384, 1): (("lt", 0, 5), None, None, (32, 3)),
(512, 1): (("lt", 128, 2), (0, 0), None, None),
(768, 1): (None, ("gemmex", 0, 0), None, None),
},
1024: {
(24, 1): (("lt", 128, 3), None, (32, 2), (0, 5)),
(28, 1): (("lt", 128, 5), None, None, (0, 6)),
(29, 1): (("lt", 128, 2), None, None, (0, 6)),
(30, 1): (("lt", 32, 4), None, None, (0, 6)),
(31, 1): (("lt", 128, 3), None, (128, 2), (0, 6)),
(32, 1): (None, None, (32, 2), (0, 6)),
(48, 1): (("lt", 32, 5), (32, 5), None, (0, 5)),
(64, 1): (("lt", 32, 6), None, (128, 0), (128, 1)),
(96, 1): (("lt", 32, 5), (32, 3), (128, 2), (128, 3)),
(192, 1): (("lt", 32, 4), (128, 4), None, (32, 1)),
(320, 1): (("lt", 128, 6), ("gemmex", 0, 0), None, (32, 2)),
(384, 1): (("lt", 0, 3), (32, 1), (32, 2), (0, 3)),
(512, 1): (("lt", 32, 1), None, None, (32, 3)),
(768, 1): (("lt", 128, 3), (128, 2), (32, 4), ("cutlass", 0, 12)),
(1024, 1): (("gemmex", 0, 0), None, (32, 2), ("cutlass", 0, 12)),
},
2048: {
(16, 1): (("lt", 128, 3), None, (128, 1), None),
(24, 1): (("lt", 128, 0), None, (32, 2), (0, 4)),
(28, 1): (("gemmex", 0, 0), None, (32, 1), (0, 4)),
(29, 1): (("lt", 128, 0), None, (32, 1), (0, 4)),
(30, 1): (("lt", 128, 0), None, (128, 1), (0, 4)),
(31, 1): (("gemmex", 0, 0), None, (32, 6), (0, 4)),
(32, 1): (("gemmex", 0, 0), (0, 0), (32, 6), (0, 4)),
(48, 1): (None, (32, 2), None, (0, 5)),
(64, 1): (("lt", 32, 4), (32, 1), (32, 1), (0, 4)),
(96, 1): (("lt", 128, 2), (0, 3), (128, 1), (32, 1)),
(128, 1): (("lt", 32, 6), ("gemmex", 0, 0), (32, 1), (0, 4)),
(192, 1): (("lt", 32, 1), (128, 3), None, (0, 1)),
(256, 1): (("lt", 32, 5), (128, 3), None, (32, 3)),
(320, 1): (("lt", 32, 4), (32, 2), (0, 2), (0, 2)),
(384, 1): (None, (128, 2), (128, 3), None),
(512, 1): (("lt", 128, 3), (32, 6), (32, 6), (0, 3)),
(768, 1): (("lt", 128, 3), (32, 4), (32, 5), (32, 4)),
(1024, 1): (("lt", 128, 3), (0, 2), None, (32, 4)),
},
2560: {
(28, 1): (("lt", 32, 3), None, None, (0, 4)),
(29, 1): (("lt", 32, 3), None, None, (0, 4)),
(30, 1): (("lt", 128, 3), None, None, (0, 4)),
(31, 1): (("lt", 32, 3), (128, 1), None, (0, 4)),
(48, 1): (("lt", 32, 4), (0, 0), None, (0, 5)),
(64, 1): (("lt", 128, 4), (0, 0), None, (128, 3)),
(96, 1): (("lt", 128, 6), (32, 0), None, (128, 2)),
(128, 1): (("lt", 128, 6), (128, 0), None, (0, 4)),
(192, 1): (("lt", 0, 2), (32, 3), (128, 5), (128, 2)),
(384, 1): (("lt", 32, 2), (0, 0), (128, 4), (0, 3)),
(768, 1): (("lt", 128, 4), None, (32, 7), (32, 4)),
(1024, 1): (None, None, None, ("cutlass", 0, 12)),
},
4096: {
(4, 1): (("lt", 32, 2), None, (128, 1), None),
(48, 1): (None, (0, 0), (32, 0), (0, 4)),
(64, 1): (None, (0, 0), None, (0, 0)),
(96, 1): (("lt", 128, 4), (32, 4), (128, 5), (32, 4)),
(128, 1): (("lt", 32, 6), (128, 5), (32, 5), (0, 4)),
(192, 1): (("lt", 32, 3), (128, 4), (128, 3), (32, 4)),
(256, 1): (None, (32, 5), None, (0, 4)),
(320, 1): (("gemmex", 0, 0), None, None, (32, 4)),
(384, 1): (("lt", 128, 4), (128, 5), (128, 3), (128, 4)),
(512, 1): (("lt", 128, 5), (32, 6), None, (0, 4)),
},
}
SM120_SM170_NATIVE_DENSE_ACCEPTED_BT = {
("fp16", 768): _bnt1(16, 24, 30, 31, 32, 48, 64, 96, 128, 192, 256, 384, 512),
("fp32io16", 768): _bnt1(16, 24, 31, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768),
("fp16", 1024): _bnt1(24, 28, 29, 30, 31, 32, 64, 96, 192, 320, 384, 512, 768, 1024),
("fp32io16", 1024): _bnt1(24, 28, 29, 30, 31, 32, 48, 64, 96, 192, 320, 384, 512, 768, 1024),
("fp16", 2048): _bnt1(16, 24, 28, 29, 30, 31, 32, 48, 64, 96, 128, 192, 256, 320, 384, 512, 768, 1024),
("fp32io16", 2048): _bnt1(16, 24, 28, 29, 30, 31, 32, 48, 64, 96, 128, 192, 256, 320, 384, 512, 768, 1024),
("fp16", 2560): _bnt1(28, 29, 30, 48, 64, 96, 128, 192, 384, 768, 1024),
("fp32io16", 2560): _bnt1(28, 29, 30, 31, 48, 64, 96, 128, 192, 384),
("fp16", 4096): _bnt1(4, 48, 96, 128, 192, 256, 320, 384, 512),
("fp32io16", 4096): _bnt1(4, 48, 64, 96, 128, 192, 256),
}
# Some dense combinations lost E2E even though individual groups were faster.
# Keep only the groups that passed per-group ablation and a second merged real-
# model gate. This table is precision-scoped because the accepted subsets are
# not interchangeable between FP16 and FP32IO16.
SM120_SM170_NATIVE_DENSE_PARTIAL_CONFIGS_BY_PRECISION_C = {
("fp16", 768): {
(28, 1): (None, None, (128, 4), (32, 2)),
(320, 1): (("lt", 32, 0), (32, 0), None, None),
(768, 1): (None, ("gemmex", 0, 0), None, None),
},
("fp32io16", 768): {
(28, 1): (None, None, (128, 4), (32, 2)),
(30, 1): (None, None, (128, 6), None),
(320, 1): (("lt", 32, 0), (32, 0), None, None),
},
("fp16", 1024): {
(48, 1): (("lt", 32, 5), None, None, (0, 5)),
},
("fp16", 2560): {
(24, 1): (("lt", 32, 3), None, None, None),
(31, 1): (("lt", 32, 3), None, None, (0, 4)),
},
("fp32io16", 2560): {
(16, 1): (("lt", 32, 2), (0, 1), (128, 1), None),
(24, 1): (("lt", 128, 3), None, None, (0, 5)),
},
("fp16", 4096): {
(16, 1): (None, (128, 1), None, None),
(24, 1): (None, None, None, (0, 4)),
# The generic B28..31 key route has a sharp heuristic cliff on SM170.
# Keep these exact BnT1 shapes: widening by rows would also affect B1Tn.
(28, 1): (("lt", 0, 0), (0, 4), None, None),
(29, 1): (None, (0, 1), (128, 0), None),
(30, 1): (("lt", 0, 0), (0, 4), None, None),
(31, 1): (("lt", 0, 0), (0, 4), None, (0, 2)),
(64, 1): (None, None, None, (0, 0)),
},
("fp32io16", 4096): {
(16, 1): (None, (128, 1), None, None),
(24, 1): (None, None, None, (0, 4)),
(28, 1): (("lt", 0, 0), (0, 4), None, None),
(29, 1): (None, (0, 1), (128, 0), None),
(30, 1): (("lt", 0, 0), (0, 4), None, None),
(31, 1): (None, None, None, (0, 4)),
},
}
# A source profile can be faster precisely because it does not own an override
# present in sm120-default. This sentinel blocks the inherited exact/row entry
# and deliberately resumes the normal generic/CUTLASS fallback in the caller.
GEMM_PROFILE_FALLBACK = object()
def _sm120_profile_parts(profile: str, channels: int) -> tuple[dict, dict, dict, dict]:
by_profile = {
"default": (
SM120_DEFAULT_ORIG_ATT_C2C_GEMM_BY_C,
SM120_DEFAULT_ORIG_FFN_KEY_GEMM_BY_C,
SM120_DEFAULT_FFN_DOWN_GEMM_BY_C,
SM120_DEFAULT_HEAD_LAST_LOGITS_GEMM_BY_C,
),
"sm82": (
SM120_SM82_ORIG_ATT_C2C_GEMM_BY_C,
SM120_SM82_ORIG_FFN_KEY_GEMM_BY_C,
SM120_SM82_FFN_DOWN_GEMM_BY_C,
SM120_SM82_HEAD_LAST_LOGITS_GEMM_BY_C,
),
"sm188": (
SM120_SM188_ORIG_ATT_C2C_GEMM_BY_C,
SM120_SM188_ORIG_FFN_KEY_GEMM_BY_C,
SM120_SM188_FFN_DOWN_GEMM_BY_C,
SM120_SM188_HEAD_LAST_LOGITS_GEMM_BY_C,
),
}
return tuple(table_by_c.get(channels, {}) for table_by_c in by_profile[profile])
def _sm170_exact_table(
baseline: dict,
source_tables_and_shapes: tuple[tuple[dict, frozenset[tuple[int, int]]], ...],
) -> dict:
result = dict(baseline)
for source, shapes in source_tables_and_shapes:
for batch, tokens in shapes:
result[(batch, tokens)] = source.get(
(batch, tokens), source.get(batch * tokens, GEMM_PROFILE_FALLBACK))
return result
def _sm170_profile_parts(precision: str, channels: int) -> tuple[dict, dict, dict, dict]:
baseline = _sm120_profile_parts("default", channels)
sources = []
for source_name in ("sm82", "sm188"):
accepted = SM120_SM170_DENSE_ACCEPTED_BT.get(
(source_name, precision, channels), frozenset())
if accepted:
sources.append((_sm120_profile_parts(source_name, channels), accepted))
result = tuple(
_sm170_exact_table(
baseline[index],
tuple((source[index], accepted) for source, accepted in sources),
)
for index in range(4)
)
native_configs = SM120_SM170_NATIVE_DENSE_CONFIGS_BY_C.get(channels, {})
for shape in SM120_SM170_NATIVE_DENSE_ACCEPTED_BT.get(
(precision, channels), ()
):
configs = native_configs[shape]
for index, config in enumerate(configs):
if config is not None:
result[index][shape] = config
for shape, configs in (
SM120_SM170_NATIVE_DENSE_PARTIAL_CONFIGS_BY_PRECISION_C.get(
(precision, channels), {}
).items()
):
for index, config in enumerate(configs):
if config is not None:
result[index][shape] = config
return result
SM120_GEMM_PROFILE_TABLES = {
**{
("sm120-default", channels): (
SM120_DEFAULT_ORIG_ATT_C2C_GEMM_BY_C.get(channels, {}),
SM120_DEFAULT_ORIG_FFN_KEY_GEMM_BY_C.get(channels, {}),
SM120_DEFAULT_FFN_DOWN_GEMM_BY_C.get(channels, {}),
SM120_DEFAULT_HEAD_LAST_LOGITS_GEMM_BY_C.get(channels, {}),
)
for channels in (768, 1024, 2048, 2560, 4096)
},
**{
("sm120-sm82", channels): (
SM120_SM82_ORIG_ATT_C2C_GEMM_BY_C.get(channels, {}),
SM120_SM82_ORIG_FFN_KEY_GEMM_BY_C.get(channels, {}),
SM120_SM82_FFN_DOWN_GEMM_BY_C.get(channels, {}),
SM120_SM82_HEAD_LAST_LOGITS_GEMM_BY_C.get(channels, {}),
)
for channels in (768, 1024, 2048, 2560, 4096)
},
**{
("sm120-sm188", channels): (
SM120_SM188_ORIG_ATT_C2C_GEMM_BY_C.get(channels, {}),
SM120_SM188_ORIG_FFN_KEY_GEMM_BY_C.get(channels, {}),
SM120_SM188_FFN_DOWN_GEMM_BY_C.get(channels, {}),
SM120_SM188_HEAD_LAST_LOGITS_GEMM_BY_C.get(channels, {}),
)
for channels in (768, 1024, 2048, 2560, 4096)
},
**{
("sm120-sm170", channels, precision): _sm170_profile_parts(
precision, channels)
for precision in ("fp16", "fp32io16")
for channels in (768, 1024, 2048, 2560, 4096)
},
}
def active_gemm_profile_tables(profile: str, channels: int):
return SM120_GEMM_PROFILE_TABLES.get(
(profile, channels, WKV_MODE),
SM120_GEMM_PROFILE_TABLES.get((profile, channels)),
)
def gemm_profile_config(table: dict, x: torch.Tensor, rows: int):
# Exact B/T ownership must win before the row-wide fallback. Reversing
# this order can silently apply a BnT1-only Lt algorithm to B1Tn/BnTn.
if x.dim() >= 3:
config = table.get((int(x.size(0)), int(x.size(1))))
if config is not None:
return config
return table.get(rows)
# Do not interpolate the sparse high-row entries. B368 down+head regressed in
# both capture orders. At B496 the isolated attention/key candidates regressed
# E2E by 3-6% under the 175W wall, while down+head gained 2.2-2.5% together.
# Exact B/T overrides admitted on the C=4096,H=64 model only. Keep this sparse:
# WKV task supply depends on B*H while the recurrent critical path depends on T,
# so neither a rows-only threshold nor cross-model reuse is justified.
WKV_FP16_TUNED_OVERRIDES = {
(4, 16): ("fused", "exact"),
(4, 64): ("fused", "exact"),
(8, 8): ("fused", "exact"),
}
# Exact cross-width WKV paths admitted by dual capture-order full-model gates.
# B controls (B*H) CTA supply while T controls the serial recurrence, so a
# rows-only or C-range dispatch would silently choose the wrong launch shape.
# The third field owns flat/2D launch independently of exact/seq and w0
# materialization. "auto" is reserved for T=1: forced APIs reject T<=1.
WKV_FP16_PATH_OVERRIDES_BY_C = {
768: {
(1, 32): ("split", "exact", "2d"),
(2, 8): ("fused", "seq", "flat"),
(8, 4): ("fused", "exact", "2d"),
(8, 32): ("fused", "seq", "flat"),
(32, 1): ("fused", "auto", "2d"),
},
1024: {
(8, 32): ("fused", "seq", "flat"),
(16, 1): ("fused", "auto", "2d"),
(32, 1): ("fused", "auto", "2d"),
},
2048: {
(2, 8): ("fused", "seq", "flat"),
(8, 1): ("fused", "auto", "2d"),
(8, 2): ("fused", "exact", "2d"),
(16, 2): ("fused", "exact", "2d"),
},
2560: {
(1, 32): ("split", "exact", "2d"),
(2, 4): ("fused", "exact", "2d"),
(2, 8): ("fused", "seq", "flat"),
(4, 32): ("fused", "seq", "flat"),
(8, 32): ("fused", "seq", "flat"),
(32, 1): ("fused", "auto", "2d"),
},
4096: {
**{bt: (w0_mode, kernel_mode, "2d")
for bt, (w0_mode, kernel_mode) in WKV_FP16_TUNED_OVERRIDES.items()},
# Dual-GPU 8x5 E2E: seq/2D was +0.016%/+0.037% over seq/flat.
# Keep this exact-shape guard; the same launch choice is noise at T=8.
(1, 16): ("fused", "seq", "2d"),
# This replaces the older fused/exact/2D owner at the same shape.
# Both capture orders were positive on both 7.2B and 13.3B; the flat
# seq path changes reduction association but keeps disjoint owners.
(2, 32): ("fused", "seq", "flat"),
(32, 1): ("fused", "auto", "2d"),
},
}
# These schedules trade register-resident state for a different KV owner map.
# They are bitwise identical to the prior T1 path at the admitted shapes, but
# their E2E gain depends on the 82-SM CTA supply and 175 W clock response.
SM120_SM82_WKV_FP16_PATH_OVERRIDES_4096 = {
(64, 1): ("fused", "spill48", "flat"),
(128, 1): ("fused", "vector", "2d"),
}
# Candidate schedules for the 82-SM mobile Blackwell. Each key is exact:
# direct kernels keep one recurrent state owner per (B,H), so B determines CTA
# supply while T determines serial work and neither axis may be interpolated.
# Entries remain here only after component scans, dual-order full-model gates,
# and the final combined dense+WKV gate. Do not interpolate adjacent B/T pairs.
SM120_SM82_WKV_FP16_PATH_OVERRIDES_BY_C = {
768: {
(256, 1): ("fused", "spill48", "flat"),
(5, 64): ("fused", "staged", "2d"),
(10, 32): ("fused", "staged", "2d"),
(320, 1): ("fused", "warp", "flat"),
(16, 32): ("fused", "seq", "2d"),
(64, 8): ("fused", "warp", "flat"),
(512, 1): ("fused", "spill32", "flat"),
(32, 32): ("fused", "exact", "flat"),
(64, 16): ("fused", "warp", "flat"),
(1024, 1): ("fused", "vector", "2d"),
},
1024: {
(1, 30): ("fused", "staged", "2d"),
(1, 31): ("fused", "staged", "2d"),
(4, 64): ("fused", "staged", "2d"),
(32, 8): ("fused", "warp", "flat"),
(256, 1): ("fused", "spill48", "flat"),
(8, 40): ("fused", "seq", "flat"),
(32, 10): ("fused", "warp", "flat"),
(320, 1): ("fused", "warp", "flat"),
(8, 64): ("fused", "auto", "2d"),
(16, 32): ("fused", "vector", "2d"),
(512, 1): ("fused", "vector", "2d"),
(16, 64): ("fused", "auto", "flat"),
(32, 32): ("fused", "warp", "flat"),
(64, 16): ("fused", "warp", "flat"),
(512, 2): ("fused", "vector_flat", "flat"),
(1024, 1): ("fused", "vector", "2d"),
},
2048: {
(2, 64): ("fused", "staged", "2d"),
(128, 1): ("fused", "spill48", "flat"),
(32, 8): ("fused", "warp", "flat"),
(256, 1): ("fused", "vector", "2d"),
(5, 64): ("fused", "seq", "2d"),
(10, 32): ("fused", "seq", "2d"),
(16, 20): ("fused", "warp", "flat"),
(20, 16): ("fused", "warp", "flat"),
(32, 10): ("fused", "warp", "flat"),
(320, 1): ("fused", "vector", "2d"),
(4, 128): ("fused", "auto", "flat"),
(8, 64): ("fused", "auto", "flat"),
(16, 32): ("fused", "warp", "flat"),
},
2560: {
(2, 14): ("fused", "staged", "2d"),
(29, 1): ("fused", "auto", "2d"),
(2, 64): ("fused", "staged", "2d"),
(128, 1): ("fused", "spill48", "flat"),
(256, 1): ("fused", "vector", "2d"),
(2, 160): ("fused", "staged", "2d"),
(5, 64): ("fused", "staged", "2d"),
(8, 40): ("fused", "exact", "2d"),
(10, 32): ("fused", "auto", "flat"),
(16, 20): ("fused", "warp", "flat"),
(20, 16): ("fused", "warp", "flat"),
(320, 1): ("fused", "warp", "flat"),
(4, 256): ("fused", "staged", "2d"),
(8, 128): ("fused", "exact", "flat"),
(16, 64): ("fused", "warp", "flat"),
(32, 32): ("fused", "auto", "flat"),
},
4096: SM120_SM82_WKV_FP16_PATH_OVERRIDES_4096,
}
# Exact WKV routes admitted on the 170-SM RTX 5090 FE at requested
# 1500 MHz / 450 W. Unlike dense GEMMs these tables are precision-specific by
# construction: FP16 stores recurrent state in half, while FP32IO16 selects a
# different CUDA implementation and mode ID.
SM120_SM170_WKV_FP32_MODES_BY_C = {
768: {
(2, 1): 6,
**{(batch, 1): 7 for batch in (4, 29, 30, 31, 32, 768)},
(1024, 1): 8,
},
1024: {
**{(batch, 1): 7 for batch in (2, 4, 24)},
**{(batch, 1): 8 for batch in (320, 768, 1024)},
},
2048: {
(320, 1): 7,
**{(batch, 1): 8 for batch in (384, 512, 768)},
},
2560: {
(64, 16): 9,
(256, 1): 7,
**{(batch, 1): 8 for batch in (320, 384, 512)},
},
4096: {
**{bt: 9 for bt in (
(16, 8), (16, 16), (32, 8), (16, 32),
(32, 16), (16, 64), (32, 32),
)},
(192, 1): 8,
(256, 1): 8,
},
}
SM120_SM170_WKV_FP16_PATH_OVERRIDES_BY_C = {
768: {
(16, 32): ("fused", "seq", "2d"),
(32, 32): ("fused", "exact", "flat"),
(384, 1): ("fused", "spill48", "flat"),
(1024, 1): ("fused", "vector", "2d"),
},
1024: {
(256, 1): ("fused", "spill48", "flat"),
(320, 1): ("fused", "spill48", "flat"),
(512, 1): ("fused", "vector", "2d"),
(64, 16): ("fused", "warp", "flat"),
(1024, 1): ("fused", "vector", "2d"),
},
2048: {
(4, 1): ("fused", "auto", "2d"),
(384, 1): ("fused", "vector", "2d"),
(8, 64): ("fused", "auto", "flat"),
},
2560: {
(384, 1): ("fused", "vector", "2d"),
(8, 128): ("fused", "exact", "flat"),
(32, 32): ("fused", "auto", "flat"),
},
4096: {
(256, 1): ("fused", "vector", "2d"),
},
}
def sm120_sm82_wkv_fp16_t1_range_override(B: int, T: int):
if T != 1:
return None
# These deliberately have gaps. Full-model dual-capture gates flipped at
# B36, B88/B92, and several B100..112 points under the 175 W power wall.
if 28 <= B <= 32 or 40 <= B <= 84:
return "fused", "spill48", "flat"
if 116 <= B <= 127:
return "fused", "warp", "2d"
if 129 <= B <= 208:
return "fused", "vector", "2d"
return None
# Tuned table for the 4096-wide 7B model, admitted by real B/T E2E and eval_src2
# on 2026-07-14. cuBLASLt heuristic indices are CUDA/GPU specific; production
# ops must keep the algo-0 fallback because an index can disappear after a CUDA,
# driver, or GPU change. Operator-group scans alone are not an admission test.
LOWRANK_IN_GEMM_4096 = {
(128, 8): ("orig_lt", 32, 2),
(128, 16): ("orig_lt", 32, 1),
(128, 48): ("orig_lt", 32, 1),
(128, 64): ("orig_lt", 128, 2),
(128, 96): ("orig_gemmex", 0, 0),
(128, 128): ("orig_gemmex", 0, 0),
(128, 192): ("orig_lt", 128, 1),
(128, 256): ("orig_lt", 128, 6),
(128, 512): ("orig_lt", 128, 0),
(128, 1024): ("orig_lt", 32, 1),
(480, 8): ("orig_lt", 128, 0),
(480, 16): ("runtime_lt", 128, 1),
(480, 24): ("orig_lt", 128, 5),
(480, 32): ("orig_lt", 32, 1),
(480, 48): ("orig_lt", 128, 5),
(480, 96): ("orig_lt", 128, 4),
(480, 128): ("orig_lt", 32, 4),
(480, 192): ("orig_lt", 128, 0),
(480, 256): ("orig_lt", 32, 1),
(480, 512): ("orig_lt", 128, 1),
(96, 8): ("orig_lt", 32, 2),
(96, 16): ("orig_lt", 128, 1),
(96, 96): ("orig_lt", 128, 2),
(96, 128): ("orig_lt", 32, 0),
(96, 192): ("runtime_lt", 32, 2),
(96, 256): ("orig_lt", 32, 1),
(96, 512): ("orig_gemmex", 0, 0),
(96, 1024): ("orig_lt", 128, 1),
}
LOWRANK_OUT_GEMM_4096 = {
(128, 8): ("runtime_lt", 128, 3),
(128, 16): ("runtime_lt", 128, 2),
(128, 24): ("orig_lt", 128, 0),
(128, 32): ("orig_lt", 128, 2),
(128, 48): ("orig_lt", 0, 5),
(128, 64): ("orig_lt", 0, 2),
(128, 96): ("orig_lt", 32, 3),
(128, 192): ("orig_lt", 128, 2),
(128, 256): ("runtime_lt", 128, 3),
(128, 512): ("runtime_lt", 128, 1),
(128, 1024): ("runtime_lt", 128, 1),
(480, 8): ("orig_lt", 0, 1),
(480, 16): ("orig_lt", 0, 1),
(480, 24): ("orig_lt", 128, 0),
(480, 32): ("orig_gemmex", 0, 0),
(480, 96): ("runtime_lt", 128, 1),
(480, 128): ("runtime_lt", 32, 0),
(480, 256): ("runtime_lt", 32, 2),
(480, 512): ("runtime_lt", 128, 1),
(480, 1024): ("runtime_lt", 0, 1),
(96, 8): ("runtime_lt", 128, 5),
(96, 16): ("runtime_lt", 32, 4),
(96, 24): ("orig_gemmex", 0, 0),
(96, 32): ("orig_lt", 128, 1),
(96, 48): ("orig_lt", 128, 3),
(96, 64): ("orig_lt", 128, 2),
(96, 96): ("orig_lt", 32, 3),
(96, 128): ("runtime_lt", 32, 2),
(96, 256): ("runtime_lt", 128, 4),
(96, 512): ("runtime_lt", 128, 0),
(96, 1024): ("runtime_lt", 128, 2),
}
def main() -> None:
global MODEL_PATH, WKV_MODE, WKV_FP32_PROFILE_MODE, WKV_FP16_POLICY, WKV_FP16_DEVICE_PROFILE_MODE, WKV_BH_GRID_MODE, ADD_VEC_MODE, LAST_LN_MODE, LNX_MODE, LN_OWNER_MODE, LN_STATS_MODE, CMIX_LN_STATS_MODE, CMIX_MIX_MODE, CMIX_VALUE_LOOP_MODE, CMIX_T512_ACCUM_MODE, CMIX_T512_REUSE_MODE, TMIX_MIX_MODE, HEAD_GRID_MODE, HEAD_ALL_LOGITS_GEMM_MODE, HEAD_LAST_LOGITS_GEMM_MODE, FFN_DOWN_GEMM_MODE, ORIG_DENSE_GEMM_MODE, ROWS_CUTLASS_MODE, GEMM_PROFILE_MODE, VRES_GATE_MODE, EMB_DEVICE, RKV_MODE, CMIX_SPARSE, LOWRANK_WEIGHT, LOWRANK_GEMM_MODE, ORIG_LINEAR_GROUPS, PP_DEVICES
parser = argparse.ArgumentParser()
parser.add_argument("--model", default=MODEL_PATH)
parser.add_argument("--warmup", type=int, default=1)
parser.add_argument("--iters", type=int, default=3)
parser.add_argument(
"--cases", default="",
help="comma-separated BxT cases; defaults to the standard grid, or all tuned BnT1 batches with --deltalog")
parser.add_argument("--profile-range", action="store_true")
parser.add_argument("--eval-json", default="")
parser.add_argument("--eval-out", default="")
parser.add_argument("--eval-all-logits-out", default="")
parser.add_argument("--eval-paths", default="b1tn")
parser.add_argument("--wkv", choices=("fp16", "fp32io16"), default="fp16") # fp32io16 is more accurate
parser.add_argument(
"--wkv-fp32-profile",
choices=("auto", "generic", "sm120-sm82", "sm120-sm170"),
default=WKV_FP32_PROFILE_MODE)
parser.add_argument(
"--deltalog", action="store_true",
help="use the fastest strictly gated DeltaLog path for FP16 BnT1")
parser.add_argument("--wkv-fp16-policy", choices=("current", "tuned"), default=WKV_FP16_POLICY)
parser.add_argument(
"--wkv-fp16-device-profile",
choices=("auto", "generic", "sm120-sm82", "sm120-sm170"),
default=WKV_FP16_DEVICE_PROFILE_MODE)
parser.add_argument("--wkv-bh-grid", choices=("current", "tuned"), default=WKV_BH_GRID_MODE)
parser.add_argument("--add-vec", choices=("current", "tuned"), default=ADD_VEC_MODE)
parser.add_argument("--last-ln", choices=("current", "indexed"), default=LAST_LN_MODE)
parser.add_argument("--lnx", choices=("current", "tuned", "warp"), default=LNX_MODE)
parser.add_argument("--ln-owner", choices=("current", "tuned"), default=LN_OWNER_MODE)
parser.add_argument(
"--ln-stats", choices=("current", "tuned", "welford", "welford-cache", "centered-cache"),
default=LN_STATS_MODE)
parser.add_argument(
"--cmix-ln-stats", choices=("current", "tuned", "welford", "welford-cache"),
default=CMIX_LN_STATS_MODE)
parser.add_argument("--cmix-mix", choices=("current", "tuned", "grid3d"), default=CMIX_MIX_MODE)
parser.add_argument(
"--cmix-value-loop", choices=("current", "tuned", "split2"),
default=CMIX_VALUE_LOOP_MODE)
parser.add_argument(
"--cmix-t512-accum", choices=("current", "tuned", "acc2", "acc4"),
default=CMIX_T512_ACCUM_MODE)
parser.add_argument(
"--cmix-t512-reuse", choices=("current", "tuned", "reuse"),
default=CMIX_T512_REUSE_MODE)
parser.add_argument("--tmix-mix", choices=("current", "tuned", "grid3d"), default=TMIX_MIX_MODE)
parser.add_argument(
"--head-grid", choices=("current", "tuned", "kk2d", "lnx2d", "grid2d"),
default=HEAD_GRID_MODE)
parser.add_argument(
"--head-all-logits-gemm", choices=("current", "tuned"),
default=HEAD_ALL_LOGITS_GEMM_MODE)
parser.add_argument(
"--head-last-logits-gemm", choices=("current", "tuned"),
default=HEAD_LAST_LOGITS_GEMM_MODE)
parser.add_argument(
"--ffn-down-gemm", choices=("current", "tuned"),
default=FFN_DOWN_GEMM_MODE)
parser.add_argument(
"--orig-dense-gemm", choices=("current", "tuned"),
default=ORIG_DENSE_GEMM_MODE)
parser.add_argument(
"--rows-cutlass", choices=("auto", "off", "tuned"),
default=ROWS_CUTLASS_MODE)
parser.add_argument(
"--gemm-profile",
choices=(
"auto", "generic", "sm120-default", "sm120-sm82",
"sm120-sm170", "sm120-sm188"),
default=GEMM_PROFILE_MODE)
parser.add_argument("--vres-gate", choices=("current", "tuned"), default=VRES_GATE_MODE)
parser.add_argument("--emb", choices=("gpu", "cpu"), default="cpu") # cpu is fast too, and saves VRAM
parser.add_argument("--batched-rkv", choices=("auto", "on", "off"), default="off") # auto is slightly faster but consumes lots of VRAM
parser.add_argument("--cmix-sparse", choices=("auto", "no-fc", "off"), default="no-fc") # auto is slightly faster but consumes lots of VRAM
parser.add_argument("--lowrank-weight", choices=("orig", "transpose", "both"), default="both") # orig saves VRAM but slows tiny B*T; transpose saves VRAM but slows large B*T
parser.add_argument("--lowrank-gemm", choices=("current", "tuned"), default=LOWRANK_GEMM_MODE)
parser.add_argument("--orig-linear-groups", default="att_c2c,ffn_key,head") # comma list: none, att_c2c, ffn_key, head
parser.add_argument("--pp-devices", default="") # comma list, e.g. 0,1. Empty means single GPU.
args = parser.parse_args()
MODEL_PATH = args.model
WKV_MODE = args.wkv
WKV_FP32_PROFILE_MODE = args.wkv_fp32_profile
WKV_FP16_POLICY = args.wkv_fp16_policy
WKV_FP16_DEVICE_PROFILE_MODE = args.wkv_fp16_device_profile
WKV_BH_GRID_MODE = args.wkv_bh_grid
ADD_VEC_MODE = args.add_vec
LAST_LN_MODE = args.last_ln
LNX_MODE = args.lnx
LN_OWNER_MODE = args.ln_owner
LN_STATS_MODE = args.ln_stats
CMIX_LN_STATS_MODE = args.cmix_ln_stats
CMIX_MIX_MODE = args.cmix_mix
CMIX_VALUE_LOOP_MODE = args.cmix_value_loop
CMIX_T512_ACCUM_MODE = args.cmix_t512_accum
CMIX_T512_REUSE_MODE = args.cmix_t512_reuse
TMIX_MIX_MODE = args.tmix_mix
HEAD_GRID_MODE = args.head_grid
HEAD_ALL_LOGITS_GEMM_MODE = args.head_all_logits_gemm
HEAD_LAST_LOGITS_GEMM_MODE = args.head_last_logits_gemm
FFN_DOWN_GEMM_MODE = args.ffn_down_gemm
ORIG_DENSE_GEMM_MODE = args.orig_dense_gemm
ROWS_CUTLASS_MODE = args.rows_cutlass
GEMM_PROFILE_MODE = args.gemm_profile
VRES_GATE_MODE = args.vres_gate
EMB_DEVICE = args.emb
RKV_MODE = args.batched_rkv
CMIX_SPARSE = args.cmix_sparse
LOWRANK_WEIGHT = args.lowrank_weight
LOWRANK_GEMM_MODE = args.lowrank_gemm
ORIG_LINEAR_GROUPS = parse_orig_linear_groups(args.orig_linear_groups)
PP_DEVICES = parse_pp_devices(args.pp_devices)
if args.deltalog and WKV_MODE != "fp16":
parser.error("--deltalog requires --wkv fp16")
if args.deltalog and len(PP_DEVICES) > 1:
parser.error("--deltalog does not support pipeline parallel execution")
if args.deltalog and args.eval_json:
parser.error("--deltalog is BnT1-only; the CLI eval paths use B1 and are not tuned")
groups = ",".join(sorted(ORIG_LINEAR_GROUPS)) if ORIG_LINEAR_GROUPS else "none"
pp = ",".join(str(x) for x in PP_DEVICES) if PP_DEVICES else "off"
log(f"start model={MODEL_PATH} wkv={WKV_MODE} wkv_state_layout={WKV_STATE_LAYOUT} wkv_fp32_profile={WKV_FP32_PROFILE_MODE} deltalog={'on' if args.deltalog else 'off'} wkv_fp16_policy={WKV_FP16_POLICY} wkv_fp16_device_profile={WKV_FP16_DEVICE_PROFILE_MODE} wkv_bh_grid={WKV_BH_GRID_MODE} add_vec={ADD_VEC_MODE} last_ln={LAST_LN_MODE} lnx={LNX_MODE} ln_owner={LN_OWNER_MODE} ln_stats={LN_STATS_MODE} cmix_ln_stats={CMIX_LN_STATS_MODE} cmix_mix={CMIX_MIX_MODE} cmix_value_loop={CMIX_VALUE_LOOP_MODE} cmix_t512_accum={CMIX_T512_ACCUM_MODE} cmix_t512_reuse={CMIX_T512_REUSE_MODE} tmix_mix={TMIX_MIX_MODE} head_grid={HEAD_GRID_MODE} head_all_logits_gemm={HEAD_ALL_LOGITS_GEMM_MODE} head_last_logits_gemm={HEAD_LAST_LOGITS_GEMM_MODE} ffn_down_gemm={FFN_DOWN_GEMM_MODE} orig_dense_gemm={ORIG_DENSE_GEMM_MODE} rows_cutlass={ROWS_CUTLASS_MODE} gemm_profile={GEMM_PROFILE_MODE} vres_gate={VRES_GATE_MODE} emb={EMB_DEVICE} batched_rkv={RKV_MODE} cmix_sparse={CMIX_SPARSE} lowrank_weight={LOWRANK_WEIGHT} lowrank_gemm={LOWRANK_GEMM_MODE} orig_linear_groups={groups} pp={pp}")
log(f"fixed fast path: ln=v3a linear=v3a/splitk lowrank={LOWRANK_IN_ROWS_T}/{LOWRANK_OUT_ROWS_T} nofc_rows=by_C row20_t=by_C nofc_t512_rows>={CMIX_NOFC_T512_MIN_ROWS} t512_acc2=exact_BT t512_reuse=exact_BT")
load_extensions(WKV_MODE)
model = RWKV7()
if args.eval_json:
run_eval(model, args.eval_json, args.eval_out, args.eval_all_logits_out, args.eval_paths)
return
case_spec = args.cases
if not case_spec:
if args.deltalog:
tuned_batches = sorted({
B for channels, B in set(WKV_DELTALOG_TUNED_M) | set(WKV_DELTALOG_APW_TUNED)
if channels == C
})
if not tuned_batches:
parser.error(f"no tuned DeltaLog CLI cases for C={C}")
case_spec = ",".join(f"{B}x1" for B in tuned_batches)
else:
case_spec = "1x1,1x2,1x4,1x8,1x16,1x32,1x64,1x128,1x256,2x1,4x1,8x1,16x1,32x1,64x1,128x1,256x1,2x2,4x4,8x8,16x16"
print("csv_header,label,B,T,iters,p10_ms,p50_ms,p90_ms,tok_s_p50", flush=True)
for item in case_spec.replace(",", " ").split():
B, T = [int(x) for x in item.lower().split("x", 1)]
bench_case(
model, B, T, args.warmup, args.iters, args.profile_range,
deltalog=args.deltalog)
def use_wkv_bh_grid_2d(B: int, T: int, C: int, H: int) -> bool:
if (
WKV_BH_GRID_MODE != "tuned"
or C != 4096
or H != 64
or B <= 0
or B > 65535
# CUDA bodies inherited signed-int token/state address arithmetic.
or B * T * C > 2**31 - 1
or B * C * 64 > 2**31 - 1
):
return False
if T == 1:
return B <= 16
if (B, T) in WKV_FP16_TUNED_OVERRIDES:
return True
# This must mirror use_v2_seq() in rwkv7_wkv_fp16_v2.cu. The 2D grid is
# faster for exact but slower for seq; seq also gains one register, but no
# occupancy change is claimed without profiler evidence. Keep it flat.
return not (
(B == 1 and T >= 8)
or (B == 4 and T >= 4)
or (B == 8 and T >= 8)
)
def wkv_fp16_path_override(B: int, T: int, C: int, H: int):
if WKV_FP16_POLICY != "tuned" or C != H * 64:
return None
if WKV_FP16_DEVICE_PROFILE_ACTIVE == "sm120-sm82":
device_override = SM120_SM82_WKV_FP16_PATH_OVERRIDES_BY_C.get(C, {}).get((B, T))
if device_override is not None:
return device_override
range_override = (
sm120_sm82_wkv_fp16_t1_range_override(B, T)
if C == 4096 else None)
if range_override is not None:
return range_override
if WKV_FP16_DEVICE_PROFILE_ACTIVE == "sm120-sm170":
device_override = SM120_SM170_WKV_FP16_PATH_OVERRIDES_BY_C.get(
C, {}).get((B, T))
if device_override is not None:
return device_override
return WKV_FP16_PATH_OVERRIDES_BY_C.get(C, {}).get((B, T))
def use_warp_lnx(B: int, T: int, C: int, H: int) -> bool:
if LNX_MODE == "warp":
return True
if LNX_MODE != "tuned" or C != 4096 or H != 64 or B * T * H < LNX_WARP_MIN_HEAD_TASKS:
return False
# B1 long-context E2E is unusually graph-pool/capture-order sensitive even
# when the operator is faster. Admit only T values positive in both orders.
return B >= 2 or T in LNX_WARP_B1_T_4096
def use_add_vec_2d(rows: int, channels: int) -> bool:
return ADD_VEC_MODE == "tuned" and channels == 4096 and 17 <= rows <= 65535
def tuned_add_ln_owner_config(rows: int, channels: int) -> tuple[int, bool] | None:
if LN_OWNER_MODE != "tuned":
return None
if channels == 4096 and rows < 1024:
return 256, True
# These exact large-row owners are bandwidth winners on the real C=2048
# and C=2560 model shapes. Do not broaden the rows guard: the same generic
# templates lose at rows<=512, often by more than their rows=1024 gain.
if rows == 1024 and channels == 2048:
return 256, True
if rows == 1024 and channels == 2560:
return 128, True
return None
def use_tuned_ln_owner(rows: int, channels: int) -> bool:
return tuned_add_ln_owner_config(rows, channels) is not None
def tuned_standalone_ln_config(rows: int, channels: int) -> tuple[int, bool] | None:
if LN_OWNER_MODE != "tuned" or rows != 1024:
return None
if channels in (2048, 2560):
return 256, True
return None
def tuned_ln_stats_mode(batch: int, rows: int, channels: int) -> int | None:
if LN_STATS_MODE == "current" or channels != 4096:
return None
if LN_STATS_MODE == "welford":
return 0
if LN_STATS_MODE == "welford-cache":
return 1
if LN_STATS_MODE == "centered-cache":
return 2
# Same-graph E2E is stable across BnTn/BnT1 factorizations for B>=2 and
# rows<=1024. B1Tn remains capture-layout sensitive, so tuned must not
# silently change that path until it has an independent stable gate.
if batch < 2 or rows > 1024:
return None
# Direct Welford retains unrounded FP32 sums and is strongest on small row
# sets. At higher task supply, caching the final FP16 residual removes one
# more input pass and has a substantially larger operator margin.
return 0 if rows < 192 else 1
def tuned_cmix_ln_stats_mode(batch: int, channels: int) -> int | None:
if CMIX_LN_STATS_MODE == "current" or channels != 4096:
return None
if CMIX_LN_STATS_MODE == "welford":
return 0
if CMIX_LN_STATS_MODE == "welford-cache":
return 1
# The fused op is only called at T=1, so rows == batch. Operator gains at
# smaller B do not survive the full graph; both capture orders are positive
# only from B=192 onward.
return 1 if 192 <= batch <= 1024 else None
def use_cmix_mix_3d(batch: int, tokens: int, channels: int) -> bool:
if CMIX_MIX_MODE == "current" or tokens == 1:
return False
if CMIX_MIX_MODE == "grid3d":
return batch <= 65535 and tokens <= 65535
# The production model is C=4096. Keep tuned conservative until other
# widths have their own real-model E2E evidence, even though the kernel is generic.
if channels != 4096 or batch > 65535 or tokens > 65535:
return False
# B1 long-context is capture-pool/order sensitive. T=256 flipped sign by
# capture order despite a bitwise-exact, 1.2x operator, so only admit the
# B1 points that were positive in both orders. B>=2 had broad factorization coverage.
return batch >= 2 or tokens in CMIX_MIX_3D_B1_T_4096
def use_cmix_value_split2(
batch: int, tokens: int, channels: int, hidden: int
) -> bool:
if CMIX_VALUE_LOOP_MODE == "current":
return False
if CMIX_VALUE_LOOP_MODE == "split2":
return True
# Arithmetic association and sparse task supply both vary with B/T. Admit
# only shapes positive in both real-model capture orders; B4T1 and B2T3
# each failed one order, and unmeasured factorizations are not extrapolated.
return (
channels == 4096 and hidden == 16384 and
(batch, tokens) in CMIX_VALUE_SPLIT2_BT_4096)
def cmix_t512_accumulators(
batch: int, tokens: int, channels: int, hidden: int
) -> int:
if channels % 512 != 0 or hidden != 4 * channels:
return 1
# Experimental force modes must be checked before the production width
# table. Putting the C4096 gate first silently turns cross-width A/B runs
# into acc1-vs-acc1 same-code measurements.
if CMIX_T512_ACCUM_MODE == "acc2":
return 2
if CMIX_T512_ACCUM_MODE == "acc4":
return 4
# The fixed savings are only a few microseconds per full graph. Keep the
# production table exact: nearby B/T factorizations did not survive the
# balanced full-model gate, and the result is width-sensitive.
if (
CMIX_T512_ACCUM_MODE == "tuned" and
(batch, tokens) in CMIX_T512_ACC2_BT_BY_C.get(channels, ())
):
return 2
return 1
def use_cmix_t512_reuse(
batch: int, tokens: int, channels: int, hidden: int
) -> bool:
if CMIX_T512_REUSE_MODE == "current":
return False
if CMIX_T512_REUSE_MODE == "reuse":
return True
# Retaining each warp's ballot removes one shared reload/test/ballot and
# has no register or occupancy cost. The graph-level saving is nevertheless
# B/T-sensitive, so only exact shapes positive in both balanced slot orders
# are admitted; nearby factorizations are intentionally not extrapolated.
return (
hidden == 4 * channels and
(batch, tokens) in CMIX_T512_REUSE_BT_BY_C.get(channels, ()))
def use_tmix_mix6_3d(batch: int, tokens: int, channels: int) -> bool:
if TMIX_MIX_MODE == "current" or tokens == 1:
return False
if TMIX_MIX_MODE == "grid3d":
return batch <= 65535 and tokens <= 65535
if channels != 4096 or batch > 65535 or tokens > 65535:
return False
# The generic kernel is exact and broadly faster, but only admit shapes
# that survived full-model timing. B1 points outside this set have no such
# evidence yet; keep their launch layout unchanged instead of extrapolating.
return batch >= 2 or tokens in TMIX_MIX_3D_B1_T_4096
def use_kk_head_grid_2d(batch: int, tokens: int, channels: int, heads: int) -> bool:
if HEAD_GRID_MODE in ("current", "lnx2d"):
return False
if HEAD_GRID_MODE == "tuned":
return (
channels == 4096 and heads == 64 and
batch > 0 and tokens > 0 and batch * tokens <= 65535)
return (
channels == heads * 64 and heads % 4 == 0 and
batch > 0 and tokens > 0 and batch * tokens <= 65535)
def use_lnx_head_grid_2d(batch: int, tokens: int, channels: int, heads: int) -> bool:
if HEAD_GRID_MODE in ("current", "kk2d"):
return False
if HEAD_GRID_MODE == "tuned":
return False
return channels == heads * 64 and batch > 0 and tokens > 0 and batch * tokens <= 65535
def tuned_vres_gate_threads(rows: int, channels: int) -> int | None:
if VRES_GATE_MODE != "tuned" or channels != 4096 or rows < 64 or rows > 65535:
return None
# At C=4096 vec2/128 preserves the scalar kernel's 16 CTAs per row while
# halving warps per CTA. Once rows supply enough work, vec2/256 halves the
# CTA count too. Keep this rows-based; per-layer tables are not justified.
return 128 if rows < 256 else 256
def log(message: str) -> None:
print(f"[rwkv7_fast_v3a] {message}", flush=True)
def select_gemm_profile() -> None:
global CUDA_DEVICE_PROFILE_ACTIVE, GEMM_PROFILE_ACTIVE, WKV_FP32_PROFILE_ACTIVE, WKV_FP16_DEVICE_PROFILE_ACTIVE
properties = torch.cuda.get_device_properties(torch.cuda.current_device())
detected_profile = "generic"
if properties.major == 12:
if ("sm120-default", C) in SM120_GEMM_PROFILE_TABLES:
detected_profile = "sm120-default"
if (
properties.minor == 0
and properties.multi_processor_count == 82
and active_gemm_profile_tables("sm120-sm82", C) is not None
):
detected_profile = "sm120-sm82"
elif (
properties.minor == 0
and properties.multi_processor_count == 170
and active_gemm_profile_tables("sm120-sm170", C) is not None
):
detected_profile = "sm120-sm170"
elif (
properties.minor == 0
and properties.multi_processor_count == 188
and active_gemm_profile_tables("sm120-sm188", C) is not None
):
detected_profile = "sm120-sm188"
CUDA_DEVICE_PROFILE_ACTIVE = detected_profile
if GEMM_PROFILE_MODE == "auto":
GEMM_PROFILE_ACTIVE = detected_profile
else:
GEMM_PROFILE_ACTIVE = GEMM_PROFILE_MODE
if WKV_FP32_PROFILE_MODE == "auto":
if (
detected_profile == "sm120-sm82"
and C in SM120_SM82_WKV_FP32_MODES_BY_C
):
WKV_FP32_PROFILE_ACTIVE = "sm120-sm82"
elif (
detected_profile == "sm120-sm170"
and C in SM120_SM170_WKV_FP32_MODES_BY_C
):
WKV_FP32_PROFILE_ACTIVE = "sm120-sm170"
else:
WKV_FP32_PROFILE_ACTIVE = "generic"
else:
WKV_FP32_PROFILE_ACTIVE = WKV_FP32_PROFILE_MODE
if WKV_FP16_DEVICE_PROFILE_MODE == "auto":
if (
detected_profile == "sm120-sm82"
and C in SM120_SM82_WKV_FP16_PATH_OVERRIDES_BY_C
):
WKV_FP16_DEVICE_PROFILE_ACTIVE = "sm120-sm82"
elif (
detected_profile == "sm120-sm170"
and C in SM120_SM170_WKV_FP16_PATH_OVERRIDES_BY_C
):
WKV_FP16_DEVICE_PROFILE_ACTIVE = "sm120-sm170"
else:
WKV_FP16_DEVICE_PROFILE_ACTIVE = "generic"
else:
WKV_FP16_DEVICE_PROFILE_ACTIVE = WKV_FP16_DEVICE_PROFILE_MODE
log(
f"GEMM profile active={GEMM_PROFILE_ACTIVE} mode={GEMM_PROFILE_MODE} "
f"device_profile={CUDA_DEVICE_PROFILE_ACTIVE} "
f"gpu={properties.name} cc={properties.major}.{properties.minor} "
f"sm={properties.multi_processor_count}")
log(
f"WKV FP32 profile active={WKV_FP32_PROFILE_ACTIVE} "
f"mode={WKV_FP32_PROFILE_MODE}")
log(
f"WKV FP16 device profile active={WKV_FP16_DEVICE_PROFILE_ACTIVE} "
f"mode={WKV_FP16_DEVICE_PROFILE_MODE}")
def wkv_fp32_mode(batch: int, tokens: int) -> int:
if WKV_FP32_PROFILE_ACTIVE == "sm120-sm82":
return SM120_SM82_WKV_FP32_MODES_BY_C.get(C, {}).get((batch, tokens), 0)
if WKV_FP32_PROFILE_ACTIVE == "sm120-sm170":
return SM120_SM170_WKV_FP32_MODES_BY_C.get(C, {}).get((batch, tokens), 0)
return 0
def cuda_mem() -> str:
if not torch.cuda.is_available():
return "cuda=unavailable"
free, total = torch.cuda.mem_get_info()
used = total - free
allocated = torch.cuda.memory_allocated()
reserved = torch.cuda.memory_reserved()
return f"gpu_mem used={used/2**30:.2f}GiB allocated={allocated/2**30:.2f}GiB reserved={reserved/2**30:.2f}GiB total={total/2**30:.2f}GiB"
def sync_all() -> None:
if PP_DEVICES:
for dev_id in PP_DEVICES:
torch.cuda.synchronize(dev_id)
else:
torch.cuda.synchronize()
def pp_enabled() -> bool:
return len(PP_DEVICES) > 1
def parse_pp_devices(text: str) -> list[int]:
if not text.strip():
return []
out = [int(x) for x in text.replace(",", " ").split()]
if len(out) != len(set(out)):
raise ValueError(f"duplicate pp devices: {out}")
return out
def first_device() -> torch.device:
return torch.device(f"cuda:{PP_DEVICES[0]}") if PP_DEVICES else torch.device("cuda")
def last_device() -> torch.device:
return torch.device(f"cuda:{PP_DEVICES[-1]}") if PP_DEVICES else torch.device("cuda")
def layer_device_index(layer: int) -> int:
if not pp_enabled():
return 0
return min(len(PP_DEVICES) - 1, layer * len(PP_DEVICES) // L)
def layer_device(layer: int) -> torch.device:
return torch.device(f"cuda:{PP_DEVICES[layer_device_index(layer)]}") if PP_DEVICES else torch.device("cuda")
def pp_segments() -> list[tuple[int, int]]:
if not pp_enabled():
return [(0, L)]
out = []
start = 0
while start < L:
idx = layer_device_index(start)
end = start + 1
while end < L and layer_device_index(end) == idx:
end += 1
out.append((start, end))
start = end
return out
def key_device(key: str) -> torch.device:
if key == "head.weight" or key.startswith("ln_out."):
return last_device()
if key == "emb.weight" or key.startswith("blocks.0.ln0."):
return first_device()
parts = key.split(".")
if len(parts) > 2 and parts[0] == "blocks":
return layer_device(int(parts[1]))
return first_device()
@dataclass(frozen=True)
class PathConfig:
rows: int
use_batched_rkv: bool
cmix_mode: str
def select_path(B: int, T: int) -> PathConfig:
"""All B/T dependent fast-path choices live here."""
rows = B*T
if CMIX_SPARSE == "off":
cmix_mode = CMIX_DENSE
elif CMIX_SPARSE == "no-fc":
use_nofc = rows <= cmix_nofc_max_rows() or (rows == 20 and T <= cmix_nofc_row20_max_t())
cmix_mode = CMIX_B1T1_NOFC if rows == 1 else (CMIX_ROWS2_NOFC if use_nofc else CMIX_DENSE)
elif rows == 1:
cmix_mode = CMIX_B1T1_SPARSE
elif rows == 2:
cmix_mode = CMIX_ROWS2_NOFC
else:
cmix_mode = CMIX_DENSE
if RKV_MODE == "auto":
use_batched_rkv = (rows == 1) or (4 <= rows <= 64)
elif RKV_MODE == "on":
use_batched_rkv = True
else:
use_batched_rkv = False
if use_orig_linear("att_c2c"):
use_batched_rkv = False
return PathConfig(rows=rows, use_batched_rkv=use_batched_rkv, cmix_mode=cmix_mode)
def cmix_nofc_max_rows() -> int:
return 19
def cmix_nofc_row20_max_t() -> int:
return CMIX_NOFC_ROW20_MAX_T
def parse_orig_linear_groups(text: str) -> set[str]:
groups = {x.strip() for x in text.replace(",", " ").split() if x.strip()}
if not groups or groups == {"none"}:
return set()
unknown = groups - {"att_c2c", "ffn_key", "head"}
if unknown:
raise ValueError(f"unknown orig linear groups: {sorted(unknown)}")
return groups
def use_orig_linear(group: str) -> bool:
return group in ORIG_LINEAR_GROUPS
def is_lowrank_weight(key: str) -> bool:
return key.endswith(LOWRANK_SUFFIXES)
def can_use_lowrank_fused(rows: int) -> bool:
return C >= LOWRANK_FUSED_MIN_C and rows <= LOWRANK_IN_ROWS_T
def can_use_lowrank_out_fused(rows: int) -> bool:
return C >= LOWRANK_FUSED_MIN_C and rows <= LOWRANK_OUT_ROWS_T
def is_att_c2c_weight(key: str) -> bool:
return ".att." in key and key.endswith(("receptance.weight", "key.weight", "value.weight", "output.weight"))
def is_orig_linear_weight(key: str) -> bool:
return (
(use_orig_linear("att_c2c") and is_att_c2c_weight(key))
or (use_orig_linear("ffn_key") and ".ffn.key.weight" in key)
or (use_orig_linear("head") and key == "head.weight")
)
_LOADED_WKV_MODES: set[str] = set()
def set_wkv_mode(wkv_mode: str) -> None:
"""Select the process-wide WKV route without reloading model weights.
Call between requests, not during inference/capture. Discard old states
and CUDA graphs, then create new ones; neither is converted by this API.
Directly assigning WKV_MODE does not load a missing CUDA extension.
"""
global WKV_MODE
# Publish only after loading succeeds, so a failed build keeps the old route.
load_extensions(wkv_mode)
WKV_MODE = wkv_mode
def load_extensions(wkv_mode: str = "fp16") -> None:
# Legacy setup remains WKV_MODE=mode; load_extensions(mode); RWKV7().
# Loading/preloading extensions alone must not change the active route.
if wkv_mode not in ("fp16", "fp32io16"):
raise ValueError(f"unknown wkv_mode: {wkv_mode}")
if wkv_mode in _LOADED_WKV_MODES:
return
t0 = time.perf_counter()
log(f"loading CUDA extensions v3a_ops + fast_ops + wkv={wkv_mode}")
cuda_flags = ["-O3", "--use_fast_math", "--extra-device-vectorization"] + ([] if os.name == "nt" else ["-Xptxas", "-O3"])
load(name="rwkv7_v3a_ops", sources=[str(CUDA_DIR / "rwkv7_v3a_ops.cpp"), str(CUDA_DIR / "rwkv7_v3a_ops.cu")], is_python_module=False, verbose=True, extra_cflags=["-O3"], extra_cuda_cflags=cuda_flags)
load(name="rwkv7_fast_ops_fp16", sources=[str(CUDA_DIR / "rwkv7_fast_ops_fp16.cpp"), str(CUDA_DIR / "rwkv7_fast_ops_fp16.cu")], is_python_module=False, verbose=True, extra_cflags=["-O3"], extra_cuda_cflags=cuda_flags)
if wkv_mode == "fp16":
load(name="rwkv7_wkv_fp16_v2", sources=[str(CUDA_DIR / "rwkv7_wkv_fp16_v2.cpp"), str(CUDA_DIR / "rwkv7_wkv_fp16_v2.cu")], is_python_module=False, verbose=True, extra_cflags=["-O3"], extra_cuda_cflags=["-O3", "-res-usage", "--extra-device-vectorization", "-Xptxas", "-O3"])
load(
name="rwkv7_wkv_deltalog_v3a",
sources=[
str(CUDA_DIR / "rwkv7_wkv_deltalog_v3a.cpp"),
str(CUDA_DIR / "rwkv7_wkv_deltalog_v3a.cu"),
],
is_python_module=False,
verbose=True,
extra_cflags=["-O3"],
extra_cuda_cflags=[
"-O3", "-res-usage", "--extra-device-vectorization",
"-Xptxas", "-O3",
],
)
elif wkv_mode == "fp32io16":
load(name="rwkv7_wkv_fp32_v2", sources=[str(CUDA_DIR / "rwkv7_wkv_fp32_v2.cpp"), str(CUDA_DIR / "rwkv7_wkv_fp32_v2.cu")], is_python_module=False, verbose=True, extra_cflags=["-O3", "-D_IO_FP16_"], extra_cuda_cflags=["-O3", "--use_fast_math", "-Xptxas", "-O3", "-D_IO_FP16_"])
else:
raise ValueError(f"unknown wkv_mode: {wkv_mode}")
_LOADED_WKV_MODES.add(wkv_mode)
log(f"CUDA extensions loaded in {time.perf_counter() - t0:.3f}s")
def load_rows_cutlass_extension() -> None:
global ROWS_CUTLASS_AVAILABLE
ROWS_CUTLASS_AVAILABLE = False
if (
ROWS_CUTLASS_MODE == "off"
or (
C not in ROWS_CUTLASS_UP_BY_C
and C not in ROWS_CUTLASS_DOWN_BY_C
and C not in ROWS_CUTLASS_C2C_BY_C
)
):
return
if not CUTLASS_INCLUDE_DIR.is_dir():
message = f"CUTLASS headers not found at {CUTLASS_INCLUDE_DIR}"
if ROWS_CUTLASS_MODE == "tuned":
raise RuntimeError(message)
log(f"{message}; admitted rows fall back to cuBLAS")
return
t0 = time.perf_counter()
try:
load(
name="rwkv7_rows_cutlass",
sources=[
str(CUDA_DIR / "rwkv7_rows_cutlass.cpp"),
str(CUDA_DIR / "rwkv7_rows_cutlass.cu"),
],
is_python_module=False,
verbose=True,
extra_include_paths=[str(CUTLASS_INCLUDE_DIR)],
extra_cflags=["-O3"],
extra_cuda_cflags=["-O3", "--use_fast_math", "-Xptxas", "-O3"],
)
except Exception as exc:
if ROWS_CUTLASS_MODE == "tuned":
raise
log(f"rows CUTLASS build unavailable; fallback to cuBLAS: {str(exc).splitlines()[0]}")
return
ROWS_CUTLASS_AVAILABLE = True
log(
f"rows CUTLASS FFN kernels loaded in {time.perf_counter() - t0:.3f}s "
f"C={C} up={sorted(ROWS_CUTLASS_UP_BY_C.get(C, ()))} "
f"down={sorted(ROWS_CUTLASS_DOWN_BY_C.get(C, ()))} "
f"c2c={sorted(ROWS_CUTLASS_C2C_BY_C.get(C, ()))}")
class RWKV7:
def __init__(self) -> None:
global L, C, H, N, V
torch.set_grad_enabled(False)
torch.backends.cudnn.benchmark = True
torch.backends.cudnn.allow_tf32 = True
torch.backends.cuda.matmul.allow_tf32 = True
torch.set_float32_matmul_precision("high")
torch._C._jit_set_autocast_mode(False)
self.wkv_state_layout = WKV_STATE_LAYOUT
t0 = time.perf_counter()
log(f"loading weights from {MODEL_PATH}")
z = torch.load(MODEL_PATH, map_location="cpu", mmap=True)
log(f"weights mmap loaded in {time.perf_counter() - t0:.3f}s tensors={len(z)}")
H, N = z["blocks.0.att.r_k"].shape
C, V = H * N, z["emb.weight"].shape[0]
assert N == HEAD_SIZE
max_layer = max(int(k.split(".")[1]) for k in z.keys() if k.startswith("blocks."))
L = max_layer + 1
log(f"detected model C={C} H={H} N={N} V={V}")
select_gemm_profile()
load_rows_cutlass_extension()
log(f"cmix no-fc path: rows<={cmix_nofc_max_rows()} row20_t<={cmix_nofc_row20_max_t()}")
emb_src = z["emb.weight"].squeeze()
ln0_w_src = z["blocks.0.ln0.weight"].squeeze()
ln0_b_src = z["blocks.0.ln0.bias"].squeeze()
emb_cpu = emb_src if EMB_DEVICE == "cpu" else None
t0 = time.perf_counter()
log(f"moving and preprocessing weights to CUDA emb={EMB_DEVICE}")
for key in list(z.keys()):
if key == "emb.weight" and emb_cpu is not None:
continue
value = z[key].squeeze()
dev = key_device(key)
is_lowrank = is_lowrank_weight(key)
if ".ffn.key.weight" in key and CMIX_SPARSE == "auto":
z[key + ".fc"] = value.to(device=dev, dtype=DTYPE).contiguous()
if (
not is_lowrank
and (("key.weight" in key and not is_orig_linear_weight(key))
or ("value.weight" in key and not is_orig_linear_weight(key))
or ("receptance.weight" in key and not is_orig_linear_weight(key))
or ("output.weight" in key and not is_orig_linear_weight(key))
or ("head.weight" in key and not is_orig_linear_weight(key)))
):
value = value.t()
value = value.to(device=dev, dtype=DTYPE).contiguous()
if key.endswith("att.r_k"):
value = value.flatten().contiguous()
if is_lowrank:
if LOWRANK_WEIGHT in ("orig", "both"):
z[key] = value
else:
del z[key]
if LOWRANK_WEIGHT in ("transpose", "both"):
z[key + ".t"] = value.t().contiguous()
else:
z[key] = value
emb_dev = first_device()
if PRECOMPUTE_EMB_LN0:
ln0_w_bf16 = ln0_w_src.to(device=emb_dev).contiguous()
ln0_b_bf16 = ln0_b_src.to(device=emb_dev).contiguous()
if emb_cpu is None:
with torch.cuda.device(emb_dev):
z["emb.weight"] = torch.ops.rwkv7_v3a_ops.emb_ln0_bf16_to_f16(
emb_src.to(device=emb_dev).contiguous(), ln0_w_bf16, ln0_b_bf16)
else:
emb = torch.empty((V,C), dtype=DTYPE, pin_memory=True)
with torch.cuda.device(emb_dev):
for start in range(0, V, 4096):
end = min(start + 4096, V)
chunk = emb_cpu[start:end].to(device=emb_dev).contiguous()
chunk = torch.ops.rwkv7_v3a_ops.emb_ln0_bf16_to_f16(chunk, ln0_w_bf16, ln0_b_bf16)
emb[start:end].copy_(chunk)
z["emb.weight"] = emb
elif emb_cpu is None:
z["emb.weight"] = emb_src.to(device=emb_dev, dtype=DTYPE).contiguous()
else:
z["emb.weight"] = emb_src.to(dtype=DTYPE).contiguous().pin_memory()
if RKV_MODE != "off" and not use_orig_linear("att_c2c"):
for layer in range(L):
p = f"blocks.{layer}.att."
z[p+"rkv.weight"] = torch.stack((z[p+"receptance.weight"], z[p+"key.weight"], z[p+"value.weight"])).contiguous()
self.z = z
self.emb_cpu = EMB_DEVICE == "cpu"
self.emb_ln0_runtime = not PRECOMPUTE_EMB_LN0
self.emb_cache: dict[tuple[int, int], tuple[torch.Tensor, torch.Tensor]] = {}
self.batch_rows_cache: dict[tuple[int, int], torch.Tensor] = {}
self.wkv_deltalog_workspace: dict[
tuple[int, int, int, str], tuple[torch.Tensor, ...]
] = {}
self.wkv_deltalog_sessions: dict[int, tuple[int, int, str] | None] = {}
if SYNC_INIT:
sync_all()
log(f"model ready in {time.perf_counter() - t0:.3f}s L={L} C={C} H={H} N={N} V={V}")
if SYNC_INIT:
log(cuda_mem())
def deltalog_workspace(
self,
layer: int,
wkv_state: torch.Tensor,
B: int,
merge_interval: int,
layout: str | None = None,
) -> tuple[torch.Tensor, ...]:
selected_layout = layout or WKV_DELTALOG_WORKSPACE_LAYOUT
if selected_layout not in (
"separate", "model_slot_packed", "model_slot_layer_packed",
):
raise RuntimeError(
f"unsupported DeltaLog workspace layout: {selected_layout}")
if selected_layout != "separate":
if pp_enabled():
raise RuntimeError("model-packed DeltaLog does not support PP")
state_key = wkv_state.untyped_storage().data_ptr()
else:
state_key = wkv_state.data_ptr()
key = (state_key, B, merge_interval, selected_layout)
workspace = self.wkv_deltalog_workspace.get(key)
if workspace is None:
shape = (merge_interval - 1, B, C)
options = dict(device=wkv_state.device, dtype=torch.float16)
if selected_layout == "separate":
workspace = tuple(torch.empty(shape, **options) for _ in range(5))
elif selected_layout == "model_slot_packed":
workspace = (
torch.empty((merge_interval - 1, 5, L, B, C), **options),
)
else:
workspace = (
torch.empty((merge_interval - 1, L, 5, B, C), **options),
)
self.wkv_deltalog_workspace[key] = workspace
return workspace
def zero_state(self, B: int) -> list[torch.Tensor]:
# A zero matrix is layout-invariant; every WKV path writes it as kv_v2.
if pp_enabled():
shift = []
wkv = []
for layer in range(L):
dev = layer_device(layer)
shift.append(torch.zeros((2,B,C), dtype=DTYPE, device=dev))
wkv.append(torch.zeros((B,H,N,N), dtype=torch.float32 if WKV_MODE == "fp32io16" else DTYPE, device=dev))
elapsed = [torch.zeros((B,), dtype=torch.int32, device=torch.device(f"cuda:{d}")) for d in PP_DEVICES]
return [shift, wkv, elapsed]
return [
torch.zeros((L,2,B,C), dtype=DTYPE, device="cuda"),
torch.zeros((L,B,H,N,N), dtype=torch.float32 if WKV_MODE == "fp32io16" else DTYPE, device="cuda"),
torch.zeros((B,), dtype=torch.int32, device="cuda"),
]
def deltalog_tuned_merge_interval(self, B: int) -> int:
return WKV_DELTALOG_TUNED_M.get((C, B), 0)
def deltalog_apw_policy(self, B: int) -> tuple[int, str, str] | None:
return WKV_DELTALOG_APW_TUNED.get((C, B))
def _select_deltalog_path(
self, B: int, merge_interval: int | None, apw: bool
) -> tuple[int, str]:
if apw:
policy = self.deltalog_apw_policy(B)
if policy is None:
raise ValueError(f"no tuned APW DeltaLog path for C={C} B={B}")
selected_m, layout, _ = policy
if merge_interval is not None and merge_interval != selected_m:
raise ValueError(
f"APW DeltaLog requires M={selected_m} for C={C} B={B}")
return selected_m, layout
return merge_interval or self.deltalog_tuned_merge_interval(B), "separate"
def deltalog_extra_bytes(self, B: int, merge_interval: int) -> int:
if merge_interval not in (2, 3, 4, 6, 8):
raise ValueError("DeltaLog merge interval must be one of 2,3,4,6,8")
return 5 * (merge_interval - 1) * B * C * DTYPE.itemsize * L
def deltalog_state_materialized(self, state: list[torch.Tensor]) -> bool:
if pp_enabled():
return False
session = self.wkv_deltalog_sessions.get(
state[1].data_ptr(), (0, 0, "separate"))
return session is not None and session[1] == 0
def forward_deltalog_step(
self,
tokens: torch.Tensor,
state: list[torch.Tensor],
merge_interval: int | None = None,
apw: bool = False,
) -> torch.Tensor:
"""Run one autoregressive token while preserving DeltaLog phase state."""
if tokens.dim() == 1:
tokens = tokens.unsqueeze(1)
if tokens.dim() != 2 or tokens.size(1) != 1:
raise ValueError("forward_deltalog_step requires [B,1] tokens")
x = self.embed(tokens)
return self.forward_from_x_deltalog_step(
x, state, select_path(tokens.size(0), 1), merge_interval, apw)
def forward_from_x_deltalog_step(
self,
x: torch.Tensor,
state: list[torch.Tensor],
path: PathConfig,
merge_interval: int | None = None,
apw: bool = False,
) -> torch.Tensor:
"""Run one T1 DeltaLog phase from a device-resident activation."""
global WKV_DELTALOG_M, WKV_DELTALOG_PHASE, WKV_DELTALOG_WORKSPACE_LAYOUT
if pp_enabled():
raise RuntimeError("DeltaLog does not support pipeline parallel state")
if x.dim() != 3 or x.size(1) != 1 or x.size(2) != C:
raise ValueError("forward_from_x_deltalog_step requires [B,1,C] x")
B = x.size(0)
selected_m, selected_layout = self._select_deltalog_path(
B, merge_interval, apw)
if selected_m not in (2, 3, 4, 6, 8):
raise ValueError(f"no tuned DeltaLog path for C={C} B={B}")
state_key = state[1].data_ptr()
session = self.wkv_deltalog_sessions.get(
state_key, (selected_m, 0, selected_layout))
if session is None:
raise RuntimeError("DeltaLog state was invalidated by an interrupted cycle")
active_m, phase, active_layout = session
if phase and (active_m != selected_m or active_layout != selected_layout):
raise RuntimeError("cannot change DeltaLog path inside a cycle")
previous_m = WKV_DELTALOG_M
previous_phase = WKV_DELTALOG_PHASE
previous_layout = WKV_DELTALOG_WORKSPACE_LAYOUT
WKV_DELTALOG_M = selected_m
WKV_DELTALOG_PHASE = phase
WKV_DELTALOG_WORKSPACE_LAYOUT = selected_layout
try:
logits = self.forward_from_x(x, state, path)
except Exception as exc:
# A failed append may leave physical state and logs out of sync.
self.wkv_deltalog_sessions[state_key] = None
raise RuntimeError("DeltaLog step failed; discard this state") from exc
finally:
WKV_DELTALOG_M = previous_m
WKV_DELTALOG_PHASE = previous_phase
WKV_DELTALOG_WORKSPACE_LAYOUT = previous_layout
self.wkv_deltalog_sessions[state_key] = (
selected_m, (phase + 1) % selected_m, selected_layout)
return logits
def forward_deltalog_cycle(
self,
tokens: torch.Tensor,
state: list[torch.Tensor],
merge_interval: int | None = None,
all_logits: bool = False,
apw: bool = False,
) -> torch.Tensor:
"""Run one complete teacher-forcing cycle and return materialized state."""
if tokens.dim() == 1:
tokens = tokens.unsqueeze(0)
if tokens.dim() != 2:
raise ValueError("forward_deltalog_cycle requires [B,M] tokens")
x = self.embed(tokens)
return self.forward_from_x_deltalog_cycle(
x, state, select_path(tokens.size(0), 1), merge_interval,
all_logits=all_logits, apw=apw)
def forward_from_x_deltalog_cycle(
self,
x: torch.Tensor,
state: list[torch.Tensor],
path: PathConfig,
merge_interval: int | None = None,
all_logits: bool = False,
apw: bool = False,
) -> torch.Tensor:
"""Run a complete DeltaLog cycle from one or M device activations."""
if x.dim() != 3 or x.size(2) != C:
raise ValueError("forward_from_x_deltalog_cycle requires [B,T,C] x")
selected_m, _ = self._select_deltalog_path(
x.size(0), merge_interval, apw)
if x.size(1) not in (1, selected_m):
raise ValueError("cycle x must contain one shared or M phase activations")
if not self.deltalog_state_materialized(state):
raise RuntimeError("DeltaLog cycle must start from materialized state")
outputs = []
for phase in range(selected_m):
phase_x = x if x.size(1) == 1 else x[:, phase:phase + 1]
logits = self.forward_from_x_deltalog_step(
phase_x, state, path, selected_m, apw=apw)
if all_logits:
outputs.append(logits)
if not self.deltalog_state_materialized(state):
raise RuntimeError("DeltaLog cycle did not materialize state")
return torch.stack(outputs, dim=1) if all_logits else logits
def instantiate_deltalog_apw_graph(
self,
graph: torch.cuda.CUDAGraph,
state: list[torch.Tensor],
B: int,
merge_interval: int | None = None,
) -> dict[str, int | float | str]:
"""Attach the tuned APW to a kept raw graph, then instantiate its exec."""
if pp_enabled():
raise RuntimeError("APW DeltaLog does not support pipeline parallel state")
selected_m, layout = self._select_deltalog_path(B, merge_interval, True)
if not self.deltalog_state_materialized(state):
raise RuntimeError("APW graph must end at a materialized DeltaLog phase")
state_key = state[1].untyped_storage().data_ptr()
workspace_key = (state_key, B, selected_m, layout)
workspace_tuple = self.wkv_deltalog_workspace.get(workspace_key)
if workspace_tuple is None:
raise RuntimeError(
"capture a complete forward_deltalog_cycle(..., apw=True) before "
"instantiating its APW graph")
workspace = workspace_tuple[0]
policy = self.deltalog_apw_policy(B)
assert policy is not None
window_mode = policy[2]
window_bytes = workspace.nbytes
if window_mode == "slot0":
window_bytes = workspace[0].nbytes
ops = torch.ops.rwkv7_wkv_deltalog_v3a
info = tuple(int(value) for value in ops.apw_device_info(workspace))
if window_bytes > info[1] or window_bytes > info[2]:
raise RuntimeError(
f"APW window {window_bytes} exceeds device limits "
f"persisting={info[1]} window={info[2]}")
persisting_bytes = max(info[3], window_bytes)
ops.set_persisting_l2_limit(workspace, persisting_bytes)
ops.reset_persisting_l2_cache(workspace)
kernel_nodes = ops.set_graph_persisting_window(
graph.raw_cuda_graph(), workspace, window_bytes, 1.0)
# Correctness/performance-critical ordering: executable graphs snapshot
# kernel-node attributes here. Re-instantiation is required if replay or
# an earlier instantiate already created an executable without this APW.
graph.instantiate()
return {
"merge_interval": selected_m,
"layout": layout,
"window_mode": window_mode,
"workspace_bytes": workspace.nbytes,
"window_bytes": window_bytes,
"persisting_bytes": persisting_bytes,
"kernel_nodes": int(kernel_nodes),
"l2_bytes": info[0],
}
def release_deltalog_workspace(self, state: list[torch.Tensor]) -> None:
# A captured graph retains raw workspace addresses, not Python tensor
# ownership. The caller must destroy every graph using this state before
# release, or a later allocation may reuse memory still named by a graph.
if not self.deltalog_state_materialized(state):
raise RuntimeError("cannot release DeltaLog logs inside a cycle")
wkv_state = state[1]
state_key = wkv_state.data_ptr()
state_end = state_key + wkv_state.numel() * wkv_state.element_size()
self.wkv_deltalog_sessions.pop(state_key, None)
for workspace_key in tuple(self.wkv_deltalog_workspace):
# Separate logs use each layer slice pointer; model-packed logs use
# this storage's base pointer. Both lie in the same state interval.
if state_key <= workspace_key[0] < state_end:
self.wkv_deltalog_workspace.pop(workspace_key)
def forward(self, tokens: torch.Tensor, state: list[torch.Tensor]) -> torch.Tensor:
if tokens.dim() == 1:
tokens = tokens.unsqueeze(0)
B, T = tokens.shape
path = select_path(B, T)
x = self.embed(tokens)
return self.forward_from_x(x, state, path)
def embed(self, tokens: torch.Tensor) -> torch.Tensor:
if not self.emb_cpu:
if tokens.device != self.z["emb.weight"].device:
tokens = tokens.to(self.z["emb.weight"].device, non_blocking=True)
x = self.z["emb.weight"][tokens]
return self.ln(x, self.z["blocks.0.ln0.weight"], self.z["blocks.0.ln0.bias"]) if self.emb_ln0_runtime else x
if tokens.dim() == 1:
tokens = tokens.unsqueeze(0)
B, T = tokens.shape
host, dev = self.emb_cache.get((B, T), (None, None))
if host is None:
host = torch.empty((B*T,C), dtype=DTYPE, pin_memory=True)
dev = torch.empty((B,T,C), dtype=DTYPE, device=first_device())
self.emb_cache[(B, T)] = (host, dev)
flat = tokens.reshape(-1)
if flat.device.type != "cpu":
flat = flat.cpu()
torch.index_select(self.z["emb.weight"], 0, flat, out=host)
dev.copy_(host.view(B,T,C), non_blocking=True)
return self.ln(dev, self.z["blocks.0.ln0.weight"], self.z["blocks.0.ln0.bias"]) if self.emb_ln0_runtime else dev
def forward_from_x(self, x: torch.Tensor, state: list[torch.Tensor], path: PathConfig, all_logits: bool = False, last_indices=None) -> torch.Tensor:
if pp_enabled():
return self.forward_from_x_pp(x, state, path, all_logits, last_indices)
z = self.z
B, T, _ = x.shape
v_first = x
xx = self.ln(x, z["blocks.0.ln1.weight"], z["blocks.0.ln1.bias"])
pre_mix = None
for layer in range(L):
p = f"blocks.{layer}."
xx, v_first = self.tmix(layer, xx, state[0][layer], state[1][layer], state[2], v_first, p+"att.", path, pre_mix)
pre_mix = None
if T == 1 and path.cmix_mode not in (CMIX_B1T1_SPARSE, CMIX_ROWS2_SPARSE):
x, mixed = self.add_ln_cmix_mix(
x, xx, state[0][layer][1], z[p+"ln2.weight"], z[p+"ln2.bias"], z[p+"ffn.x_k"])
xx = self.cmix_from_mixed(mixed, p+"ffn.", path)
else:
x, xx = self.add_ln(x, xx, z[p+"ln2.weight"], z[p+"ln2.bias"])
xx = self.cmix(xx, state[0][layer], p+"ffn.", path)
if layer + 1 < L:
p_next = f"blocks.{layer + 1}."
if LN1_TMIX_FUSE and B == 1 and T == 1:
outs = torch.ops.rwkv7_v3a_ops.add_layer_norm_tmix_mix6_f16(
x.contiguous(), xx.contiguous(), state[0][layer + 1][0],
z[p_next+"ln1.weight"], z[p_next+"ln1.bias"],
z[p_next+"att.x_r"], z[p_next+"att.x_w"], z[p_next+"att.x_k"],
z[p_next+"att.x_v"], z[p_next+"att.x_a"], z[p_next+"att.x_g"])
x, pre_mix = outs[0], outs[1:]
xx = x
else:
x, xx = self.add_ln(x, xx, z[p_next+"ln1.weight"], z[p_next+"ln1.bias"])
elif not all_logits:
if last_indices is not None:
if LAST_LN_MODE == "indexed":
x = torch.ops.rwkv7_v3a_ops.add_last_layer_norm_indexed_f16(
x.contiguous(), xx.contiguous(), last_indices.contiguous(),
z["ln_out.weight"], z["ln_out.bias"])
else:
x = self.ln(self.add(x, xx), z["ln_out.weight"], z["ln_out.bias"])
x = x[self.batch_rows(B, x.device), last_indices].contiguous()
else:
x = self.add_last_ln(x, xx, z["ln_out.weight"], z["ln_out.bias"])
torch.ops.rwkv7_v3a_ops.advance_i32(state[2], T) # !!! IMPORTANT FOR WKV16 DITHERING !!!
return self.linear_head_last(x, T)
else:
x = self.add(x, xx)
x = self.ln(x, z["ln_out.weight"], z["ln_out.bias"])
torch.ops.rwkv7_v3a_ops.advance_i32(state[2], T) # !!! IMPORTANT FOR WKV16 DITHERING !!!
return self.linear_head(x, all_logits=True)
def forward_from_x_pp(self, x: torch.Tensor, state: list[torch.Tensor], path: PathConfig, all_logits: bool = False, last_indices=None) -> torch.Tensor:
B, T, _ = x.shape
v_first = None
v_first_by_stage: dict[int, torch.Tensor] = {}
x = x.to(first_device())
segments = pp_segments()
for stage, (start, end) in enumerate(segments):
dev = layer_device(start)
if x.device != dev:
x = x.to(dev)
with torch.cuda.device(dev):
v_in = None if start == 0 else v_first_by_stage[stage]
x, v_first = self.forward_pp_segment(x, state, path, start, end, v_in)
if start == 0 and v_first is not None:
for next_stage, (next_start, _) in enumerate(segments[1:], 1):
next_dev = layer_device(next_start)
v_first_by_stage[next_stage] = v_first if next_dev == v_first.device else v_first.to(next_dev)
with torch.cuda.device(last_device()):
return self.forward_pp_tail(x, state, T, all_logits, last_indices, advance=True)
def forward_pp_segment(self, x: torch.Tensor, state: list[torch.Tensor], path: PathConfig, start: int, end: int, v_first: torch.Tensor | None) -> tuple[torch.Tensor, torch.Tensor | None]:
z = self.z
B, T, _ = x.shape
out_v_first = None
xx = self.ln(x, z[f"blocks.{start}.ln1.weight"], z[f"blocks.{start}.ln1.bias"])
pre_mix = None
for layer in range(start, end):
p = f"blocks.{layer}."
v_in = x if layer == 0 else v_first
xx, v_out = self.tmix(layer, xx, state[0][layer], state[1][layer], state[2][layer_device_index(layer)], v_in, p+"att.", path, pre_mix)
pre_mix = None
if layer == 0:
v_first = v_out
out_v_first = v_out
if T == 1 and path.cmix_mode not in (CMIX_B1T1_SPARSE, CMIX_ROWS2_SPARSE):
x, mixed = self.add_ln_cmix_mix(
x, xx, state[0][layer][1], z[p+"ln2.weight"], z[p+"ln2.bias"], z[p+"ffn.x_k"])
xx = self.cmix_from_mixed(mixed, p+"ffn.", path)
else:
x, xx = self.add_ln(x, xx, z[p+"ln2.weight"], z[p+"ln2.bias"])
xx = self.cmix(xx, state[0][layer], p+"ffn.", path)
if layer + 1 < end:
p_next = f"blocks.{layer + 1}."
if LN1_TMIX_FUSE and B == 1 and T == 1:
outs = torch.ops.rwkv7_v3a_ops.add_layer_norm_tmix_mix6_f16(
x.contiguous(), xx.contiguous(), state[0][layer + 1][0],
z[p_next+"ln1.weight"], z[p_next+"ln1.bias"],
z[p_next+"att.x_r"], z[p_next+"att.x_w"], z[p_next+"att.x_k"],
z[p_next+"att.x_v"], z[p_next+"att.x_a"], z[p_next+"att.x_g"])
x, pre_mix = outs[0], outs[1:]
xx = x
else:
x, xx = self.add_ln(x, xx, z[p_next+"ln1.weight"], z[p_next+"ln1.bias"])
else:
x = self.add(x, xx)
return x, out_v_first
def forward_pp_tail(self, x: torch.Tensor, state: list[torch.Tensor], T: int, all_logits: bool = False, last_indices=None, advance: bool = True) -> torch.Tensor:
B = x.size(0)
if not all_logits:
if last_indices is None:
x = x[:, -1].contiguous()
else:
x = x[self.batch_rows(B, x.device), last_indices].contiguous()
x = self.ln(x, self.z["ln_out.weight"], self.z["ln_out.bias"])
if advance:
self.advance_pp_elapsed(state, T)
if all_logits:
return self.linear_head(x, all_logits=True)
return self.linear_head_last(x, T)
def advance_pp_elapsed(self, state: list[torch.Tensor], T: int) -> None:
for idx, dev_id in enumerate(PP_DEVICES):
with torch.cuda.device(dev_id):
torch.ops.rwkv7_v3a_ops.advance_i32(state[2][idx], T)
def batch_rows(self, B: int, device: torch.device) -> torch.Tensor:
key = (device.index if device.index is not None else torch.cuda.current_device(), B)
rows = self.batch_rows_cache.get(key)
if rows is None:
rows = torch.arange(B, dtype=torch.long, device=device)
self.batch_rows_cache[key] = rows
return rows
def ln(self, x: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor) -> torch.Tensor:
rows = x.numel() // x.size(-1)
config = tuned_standalone_ln_config(rows, x.size(-1))
if config is not None:
threads, vectorized = config
return torch.ops.rwkv7_v3a_ops.layer_norm_f16_cfg(
x.contiguous(), weight, bias, 1.0e-5, threads, vectorized)
return torch.ops.rwkv7_v3a_ops.layer_norm_f16(x.contiguous(), weight, bias)
def forward_all_logits(self, tokens: torch.Tensor, state: list[torch.Tensor]) -> torch.Tensor:
if tokens.dim() == 1:
tokens = tokens.unsqueeze(0)
B, T = tokens.shape
path = select_path(B, T)
x = self.embed(tokens)
return self.forward_from_x(x, state, path, all_logits=True)
def forward_last_at(self, tokens: torch.Tensor, state: list[torch.Tensor], last_indices: torch.Tensor) -> torch.Tensor:
if tokens.dim() == 1:
tokens = tokens.unsqueeze(0)
B, T = tokens.shape
path = select_path(B, T)
x = self.embed(tokens)
return self.forward_from_x(x, state, path, last_indices=last_indices)
def tmix(self, layer: int, x: torch.Tensor, shift_state: torch.Tensor, wkv_state: torch.Tensor, elapsed_t: torch.Tensor, v_first: torch.Tensor, p: str, path: PathConfig, pre_mix=None) -> tuple[torch.Tensor, torch.Tensor]:
z = self.z
ops = torch.ops.rwkv7_fast_ops_fp16
B, T, _ = x.shape
if pre_mix is not None:
xr, xw, xk, xv, xa, xg = pre_mix
else:
tmix_mix_op = ops.tmix_mix6_3d if use_tmix_mix6_3d(B, T, C) else ops.tmix_mix6
xr, xw, xk, xv, xa, xg = tmix_mix_op(B, T, C, x.contiguous(), shift_state[0], z[p+"x_r"], z[p+"x_w"], z[p+"x_k"], z[p+"x_v"], z[p+"x_a"], z[p+"x_g"])
if pre_mix is not None:
if path.use_batched_rkv:
flat = torch.stack((xr.reshape(-1,C), xk.reshape(-1,C), xv.reshape(-1,C)))
rkv = torch.bmm(flat, z[p+"rkv.weight"])
r, k, v = [t.view(B,T,C) for t in rkv.unbind(0)]
else:
r = self.linear_orig_layout(xr, z[p+"receptance.weight"], path, "att_c2c")
k = self.linear_orig_layout(xk, z[p+"key.weight"], path, "att_c2c")
v = self.linear_orig_layout(xv, z[p+"value.weight"], path, "att_c2c")
else:
if path.use_batched_rkv:
flat = torch.stack((xr.reshape(-1,C), xk.reshape(-1,C), xv.reshape(-1,C)))
rkv = torch.bmm(flat, z[p+"rkv.weight"])
r, k, v = [t.view(B,T,C) for t in rkv.unbind(0)]
else:
r = self.linear_orig_layout(xr, z[p+"receptance.weight"], path, "att_c2c")
k = self.linear_orig_layout(xk, z[p+"key.weight"], path, "att_c2c")
v = self.linear_orig_layout(xv, z[p+"value.weight"], path, "att_c2c")
v1 = None
if LOWRANK_WEIGHT != "orig" and can_use_lowrank_fused(path.rows) and can_use_lowrank_out_fused(path.rows) and layer != 0:
w1, a1, g1, v1 = torch.ops.rwkv7_v3a_ops.linear_wagv_rank_in_f16(
xw.contiguous(), xa.contiguous(), xg.contiguous(), xv.contiguous(),
z[p+"w1.t"], z[p+"a1.t"], z[p+"g1.t"], z[p+"v1.t"])
elif LOWRANK_WEIGHT != "orig" and can_use_lowrank_fused(path.rows):
w1, a1, g1 = torch.ops.rwkv7_v3a_ops.linear_wag_rank_in_f16(
xw.contiguous(), xa.contiguous(), xg.contiguous(), z[p+"w1.t"], z[p+"a1.t"], z[p+"g1.t"])
else:
w1 = self.linear_rank_in(xw, z.get(p+"w1"), z.get(p+"w1.t"), path.rows)
a1 = self.linear_rank_in(xa, z.get(p+"a1"), z.get(p+"a1.t"), path.rows)
g1 = self.linear_rank_in(xg, z.get(p+"g1"), z.get(p+"g1.t"), path.rows)
v_done = False
if LOWRANK_WEIGHT != "orig" and can_use_lowrank_out_fused(path.rows) and layer != 0 and v1 is not None:
w, a, g, v = torch.ops.rwkv7_v3a_ops.linear_wagv_rank_out_f16(
w1.contiguous(), a1.contiguous(), g1.contiguous(), v1.contiguous(),
z[p+"w2.t"], z[p+"a2.t"], z[p+"g2.t"], z[p+"v2.t"],
v.contiguous(), v_first.contiguous(), z[p+"v0"])
v_done = True
elif LOWRANK_WEIGHT != "orig" and can_use_lowrank_out_fused(path.rows):
w, a, g = torch.ops.rwkv7_v3a_ops.linear_wag_rank_out_f16(
w1.contiguous(), a1.contiguous(), g1.contiguous(), z[p+"w2.t"], z[p+"a2.t"], z[p+"g2.t"])
else:
w = self.linear_rank_out_act(w1, z.get(p+"w2"), z.get(p+"w2.t"), path.rows, 1)
a = self.linear_rank_out(a1, z.get(p+"a2"), z.get(p+"a2.t"), path.rows)
g = self.linear_rank_out_act(g1, z.get(p+"g2"), z.get(p+"g2.t"), path.rows, 2)
kk_gate_op = ops.tmix_kk_a_gate_2d if use_kk_head_grid_2d(B, T, C, H) else ops.tmix_kk_a_gate
k, neg_kk, kka = kk_gate_op(B, T, C, H, k.contiguous(), z[p+"k_k"], z[p+"a0"], a.contiguous(), z[p+"k_a"])
if layer == 0:
v_first = v
elif not v_done:
if LOWRANK_WEIGHT != "orig" and can_use_lowrank_out_fused(path.rows):
if v1 is None:
v1 = self.linear_rank_in(xv, z.get(p+"v1"), z.get(p+"v1.t"), path.rows)
v = torch.ops.rwkv7_v3a_ops.linear_t_vres_f16(v1.contiguous(), z[p+"v2.t"], v.contiguous(), v_first.contiguous(), z[p+"v0"])
else:
v12 = self.linear_rank_out(self.linear_rank_in(xv, z.get(p+"v1"), z.get(p+"v1.t"), path.rows), z.get(p+"v2"), z.get(p+"v2.t"), path.rows)
v_contig = v.contiguous()
v_first_contig = v_first.contiguous()
v12_contig = v12.contiguous()
vres_threads = tuned_vres_gate_threads(path.rows, C)
if vres_threads is None:
v = ops.tmix_vres_gate(B, T, C, v_contig, v_first_contig, z[p+"v0"], v12_contig)
else:
v = ops.tmix_vres_gate_cfg(
B, T, C, v_contig, v_first_contig, z[p+"v0"], v12_contig, vres_threads, True)
y = torch.empty_like(r)
if WKV_DELTALOG_M:
if WKV_MODE != "fp16" or T != 1 or WKV_DELTALOG_M not in (2, 3, 4, 6, 8):
raise RuntimeError("DeltaLog requires FP16 T1 and M in {2,3,4,6,8}")
if not 0 <= WKV_DELTALOG_PHASE < WKV_DELTALOG_M:
raise RuntimeError("DeltaLog phase is outside the active cycle")
# Correctness-critical: append phases do not update wkv_state.
# External state operations are legal only after phase M-1 merges.
log_workspace = self.deltalog_workspace(
layer, wkv_state, B, WKV_DELTALOG_M)
factor_args = (
r.contiguous(), w.contiguous(), z[p+"w0"], k.contiguous(),
v.contiguous(), neg_kk.contiguous(), kka.contiguous(), y, elapsed_t)
if WKV_DELTALOG_WORKSPACE_LAYOUT in (
"model_slot_packed", "model_slot_layer_packed",
):
packed_step = (
torch.ops.rwkv7_wkv_deltalog_v3a.step_slot_packed
if WKV_DELTALOG_WORKSPACE_LAYOUT == "model_slot_packed"
else torch.ops.rwkv7_wkv_deltalog_v3a.step_slot_layer_packed
)
packed_step(
B, C, H, L, layer, WKV_DELTALOG_M, WKV_DELTALOG_PHASE,
wkv_state, log_workspace[0], *factor_args)
else:
torch.ops.rwkv7_wkv_deltalog_v3a.step(
B, C, H, WKV_DELTALOG_M, WKV_DELTALOG_PHASE,
wkv_state, *log_workspace, *factor_args)
elif WKV_MODE == "fp32io16":
w_raw = ops.add_vec(C, w.contiguous(), z[p+"w0"])
mode = wkv_fp32_mode(B, T)
if mode:
torch.ops.rwkv7_wkv_fp32_v2.forward_mode(B, T, C, H, mode, wkv_state, r.contiguous(), w_raw.contiguous(), k.contiguous(), v.contiguous(), neg_kk.contiguous(), kka.contiguous(), y)
else:
torch.ops.rwkv7_wkv_fp32_v2.forward(B, T, C, H, wkv_state, r.contiguous(), w_raw.contiguous(), k.contiguous(), v.contiguous(), neg_kk.contiguous(), kka.contiguous(), y)
elif (wkv_override := wkv_fp16_path_override(B, T, C, H)) is not None:
w0_mode, kernel_mode, grid_mode = wkv_override
grid2d = grid_mode == "2d"
forced_mode = 0 if kernel_mode == "exact" else 1
if w0_mode == "fused":
# These direct KV entries have different thread ownership and
# must remain behind exact B/T/device gates. One CTA owns the
# complete (B,H) state and advances T serially; broadening a
# gate by rows alone can change both occupancy and recurrence.
if kernel_mode.startswith("spill"):
keep_keys = int(kernel_mode.removeprefix("spill"))
if keep_keys not in (16, 32, 48):
raise RuntimeError(f"unsupported spill key count: {keep_keys}")
torch.ops.rwkv7_wkv_fp16_v2.wkv_kv_warp_spill_w0(
B, T, C, H, keep_keys, wkv_state, r.contiguous(), w.contiguous(),
z[p+"w0"], k.contiguous(), v.contiguous(), neg_kk.contiguous(),
kka.contiguous(), y, elapsed_t)
elif kernel_mode == "warp":
torch.ops.rwkv7_wkv_fp16_v2.wkv_kv_warp_w0(
B, T, C, H, wkv_state, r.contiguous(), w.contiguous(),
z[p+"w0"], k.contiguous(), v.contiguous(), neg_kk.contiguous(),
kka.contiguous(), y, elapsed_t)
elif kernel_mode == "vector":
torch.ops.rwkv7_wkv_fp16_v2.wkv_kv_vector_w0(
B, T, C, H, wkv_state, r.contiguous(), w.contiguous(),
z[p+"w0"], k.contiguous(), v.contiguous(), neg_kk.contiguous(),
kka.contiguous(), y, elapsed_t)
elif kernel_mode == "vector_flat":
torch.ops.rwkv7_wkv_fp16_v2.wkv_kv_vector_flat_w0(
B, T, C, H, wkv_state, r.contiguous(), w.contiguous(),
z[p+"w0"], k.contiguous(), v.contiguous(), neg_kk.contiguous(),
kka.contiguous(), y, elapsed_t)
elif kernel_mode == "staged":
torch.ops.rwkv7_wkv_fp16_v2.wkv_kv_staged_w0(
B, T, C, H, wkv_state, r.contiguous(), w.contiguous(),
z[p+"w0"], k.contiguous(), v.contiguous(), neg_kk.contiguous(),
kka.contiguous(), y, elapsed_t)
elif kernel_mode == "auto":
# T=1 must use auto: forced WKV APIs deliberately reject it.
wkv_op = (
torch.ops.rwkv7_wkv_fp16_v2.wkv_seq_w0_grid2d
if grid2d else torch.ops.rwkv7_wkv_fp16_v2.wkv_seq_w0)
wkv_op(
B, T, C, H, wkv_state, r.contiguous(), w.contiguous(), z[p+"w0"],
k.contiguous(), v.contiguous(), neg_kk.contiguous(), kka.contiguous(), y, elapsed_t)
else:
if kernel_mode not in ("exact", "seq"):
raise RuntimeError(f"unsupported fused WKV mode: {kernel_mode}")
wkv_op = (
torch.ops.rwkv7_wkv_fp16_v2.wkv_seq_w0_grid2d_forced
if grid2d else torch.ops.rwkv7_wkv_fp16_v2.wkv_seq_w0_forced)
wkv_op(
B, T, C, H, forced_mode, wkv_state, r.contiguous(), w.contiguous(), z[p+"w0"],
k.contiguous(), v.contiguous(), neg_kk.contiguous(), kka.contiguous(), y, elapsed_t)
else:
if kernel_mode == "auto":
raise RuntimeError("split auto WKV override is not admitted")
w_contig = w.contiguous()
add_vec_op = ops.add_vec_2d if use_add_vec_2d(path.rows, C) else ops.add_vec
w_raw = add_vec_op(C, w_contig, z[p+"w0"])
wkv_op = (
torch.ops.rwkv7_wkv_fp16_v2.wkv_seq_grid2d_forced
if grid2d
else torch.ops.rwkv7_wkv_fp16_v2.wkv_seq_forced)
wkv_op(
B, T, C, H, forced_mode, wkv_state, r.contiguous(), w_raw.contiguous(),
k.contiguous(), v.contiguous(), neg_kk.contiguous(), kka.contiguous(), y, elapsed_t)
elif T <= 16:
wkv_op = (
torch.ops.rwkv7_wkv_fp16_v2.wkv_seq_w0_grid2d
if use_wkv_bh_grid_2d(B, T, C, H)
else torch.ops.rwkv7_wkv_fp16_v2.wkv_seq_w0)
wkv_op(B, T, C, H, wkv_state, r.contiguous(), w.contiguous(), z[p+"w0"], k.contiguous(), v.contiguous(), neg_kk.contiguous(), kka.contiguous(), y, elapsed_t)
else:
w_contig = w.contiguous()
add_vec_op = ops.add_vec_2d if use_add_vec_2d(path.rows, C) else ops.add_vec
w_raw = add_vec_op(C, w_contig, z[p+"w0"])
wkv_op = (
torch.ops.rwkv7_wkv_fp16_v2.wkv_seq_grid2d
if use_wkv_bh_grid_2d(B, T, C, H)
else torch.ops.rwkv7_wkv_fp16_v2.wkv_seq)
wkv_op(B, T, C, H, wkv_state, r.contiguous(), w_raw.contiguous(), k.contiguous(), v.contiguous(), neg_kk.contiguous(), kka.contiguous(), y, elapsed_t)
if use_warp_lnx(B, T, C, H):
lnx_op = ops.tmix_lnx_rkvres_xg_warp_2d if use_lnx_head_grid_2d(B, T, C, H) else ops.tmix_lnx_rkvres_xg_warp
else:
lnx_op = ops.tmix_lnx_rkvres_xg
y = lnx_op(B, T, C, H, y.contiguous(), r.contiguous(), k.contiguous(), v.contiguous(), z[p+"r_k"], z[p+"ln_x.weight"], z[p+"ln_x.bias"], g.contiguous())
return self.linear_orig_layout(y, z[p+"output.weight"], path, "att_c2c"), v_first
def cmix(self, x: torch.Tensor, shift_state: torch.Tensor, p: str, path: PathConfig) -> torch.Tensor:
z = self.z
ops = torch.ops.rwkv7_fast_ops_fp16
B, T, _ = x.shape
if path.cmix_mode == CMIX_B1T1_SPARSE:
return ops.cmix_sparse_one(C, z[p+"key.weight.fc"].size(0), x.contiguous(), shift_state[1], z[p+"x_k"], z[p+"key.weight.fc"], z[p+"value.weight"])
if path.cmix_mode == CMIX_ROWS2_SPARSE:
return ops.cmix_sparse_rows(B, T, C, z[p+"key.weight.fc"].size(0), x.contiguous(), shift_state[1], z[p+"x_k"], z[p+"key.weight.fc"], z[p+"value.weight"])
cmix_mix_op = ops.cmix_mix_3d if use_cmix_mix_3d(B, T, C) else ops.cmix_mix
mixed = cmix_mix_op(B, T, C, x.contiguous(), shift_state[1], z[p+"x_k"])
return self.cmix_from_mixed(mixed, p, path)
def cmix_from_mixed(self, mixed: torch.Tensor, p: str, path: PathConfig) -> torch.Tensor:
z = self.z
ops = torch.ops.rwkv7_fast_ops_fp16
B, T, _ = mixed.shape
hid = self.linear_orig_layout(mixed, z[p+"key.weight"], path, "ffn_key")
if path.cmix_mode == CMIX_B1T1_NOFC:
F = z[p+"value.weight"].size(0)
down_op = (
ops.cmix_sparse_down_relu_one_split2
if use_cmix_value_split2(B, T, C, F)
else ops.cmix_sparse_down_relu_one)
return down_op(C, F, hid.view(-1).contiguous(), z[p+"value.weight"])
if path.cmix_mode == CMIX_ROWS2_NOFC:
F = z[p+"value.weight"].size(0)
if path.rows >= CMIX_NOFC_T512_MIN_ROWS and C % 512 == 0 and F % 512 == 0:
accumulators = cmix_t512_accumulators(B, T, C, F)
if accumulators <= 2 and use_cmix_t512_reuse(B, T, C, F):
return ops.cmix_sparse_down_relu_rows_t512_reuse_cfg(
B, T, C, F, hid.contiguous(), z[p+"value.weight"],
accumulators)
if accumulators == 1:
return ops.cmix_sparse_down_relu_rows_t512(
B, T, C, F, hid.contiguous(), z[p+"value.weight"])
return ops.cmix_sparse_down_relu_rows_t512_cfg(
B, T, C, F, hid.contiguous(), z[p+"value.weight"], accumulators)
down_op = (
ops.cmix_sparse_down_relu_rows_split2
if use_cmix_value_split2(B, T, C, F)
else ops.cmix_sparse_down_relu_rows)
return down_op(B, T, C, F, hid.contiguous(), z[p+"value.weight"])
k = ops.relu_square(hid.contiguous())
return self.linear_ffn_down(k, z[p+"value.weight"], path.rows)
def linear_ffn_down(self, x: torch.Tensor, weight: torch.Tensor, rows: int) -> torch.Tensor:
profile_tables = active_gemm_profile_tables(GEMM_PROFILE_ACTIVE, C)
if profile_tables is not None and tuple(weight.shape) == (4 * C, C):
# Lt indices are tied to the exact GPU profile selected at model load.
# Keep the non-strict call so CUDA/driver heuristic changes fall
# back to algo 0 instead of turning an optional tuning into failure.
profile_cfg = gemm_profile_config(profile_tables[2], x, rows)
if profile_cfg is GEMM_PROFILE_FALLBACK:
profile_cfg = None
if profile_cfg is not None:
workspace_mb, algo_index = profile_cfg
return torch.ops.rwkv7_v3a_ops.linear_f16_lt_cfg(
x.contiguous(), weight, workspace_mb, algo_index)
if (
ROWS_CUTLASS_AVAILABLE
and rows in ROWS_CUTLASS_DOWN_BY_C.get(C, ())
and tuple(weight.shape) == (4 * C, C)
):
return torch.ops.rwkv7_rows_cutlass.linear_runtime(
x.contiguous(), weight, 15)
if (
FFN_DOWN_GEMM_MODE == "tuned"
and tuple(weight.shape) == (4 * C, C)
):
cfg = FFN_DOWN_GEMM_BY_C.get(C, {}).get(rows)
if cfg is not None:
workspace_mb, algo_index = cfg
return torch.ops.rwkv7_v3a_ops.linear_f16_lt_cfg(
x.contiguous(), weight, workspace_mb, algo_index)
return self.linear(x, weight)
def linear(self, x: torch.Tensor, weight: torch.Tensor) -> torch.Tensor:
if x.numel() == x.size(-1) and weight.size(1) % 64 == 0:
return torch.ops.rwkv7_v3a_ops.linear_f16_m1_splitk(x.contiguous(), weight)
return torch.ops.rwkv7_v3a_ops.linear_f16(x.contiguous(), weight)
def linear_head(self, x: torch.Tensor, all_logits: bool = False) -> torch.Tensor:
z = self.z
if not use_orig_linear("head"):
return self.linear(x, z["head.weight"])
rows = x.numel() // C
if all_logits and HEAD_ALL_LOGITS_GEMM_MODE == "tuned" and C == 4096:
cfg = HEAD_ALL_LOGITS_GEMM_4096.get(rows)
if cfg is not None:
workspace_mb, algo_index = cfg
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(
x.contiguous(), z["head.weight"], workspace_mb, algo_index)
return self.linear_orig_layout(x, z["head.weight"], PathConfig(rows, False, CMIX_DENSE), "head")
def linear_head_last(self, x: torch.Tensor, tokens_count: int) -> torch.Tensor:
z = self.z
if not use_orig_linear("head"):
return self.linear(x, z["head.weight"])
rows = x.numel() // C
profile_tables = active_gemm_profile_tables(GEMM_PROFILE_ACTIVE, C)
if profile_tables is not None and tuple(z["head.weight"].shape) == (V, C):
# The head GEMM has B rows while the body has B*T rows. Keep the
# exact (B,T) gate: adjacent T values flipped in dual-order E2E.
profile_cfg = profile_tables[3].get((rows, tokens_count))
if (
GEMM_PROFILE_ACTIVE == "sm120-sm82"
and WKV_MODE == "fp32io16"
and (rows, tokens_count)
in SM120_SM82_HEAD_LAST_LOGITS_FP16_ONLY_BT_BY_C.get(C, ())
):
profile_cfg = None
if profile_cfg is GEMM_PROFILE_FALLBACK:
profile_cfg = None
if profile_cfg is not None:
# Do not turn these into ranges. On the 175W laptop, several
# component-fast high-row GEMMs lowered the sustained clock and
# regressed E2E by 5-7% at adjacent B480/B512.
if len(profile_cfg) == 2:
kind = "lt"
workspace_mb, algo_index = profile_cfg
else:
kind, workspace_mb, algo_index = profile_cfg
if kind == "cutlass" and ROWS_CUTLASS_AVAILABLE:
return torch.ops.rwkv7_rows_cutlass.linear_orig(
x.contiguous(), z["head.weight"], algo_index)
if kind == "gemmex":
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(
x.contiguous(), z["head.weight"])
if kind != "lt":
return self.linear_orig_layout(
x, z["head.weight"],
PathConfig(rows, False, CMIX_DENSE), "head")
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(
x.contiguous(), z["head.weight"], workspace_mb, algo_index)
if (
HEAD_LAST_LOGITS_GEMM_MODE == "tuned"
and tuple(z["head.weight"].shape) == (V, C)
):
cfg = HEAD_LAST_LOGITS_GEMM_BY_C.get(C, {}).get(
(rows, tokens_count))
if cfg is not None:
workspace_mb, algo_index = cfg
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(
x.contiguous(), z["head.weight"], workspace_mb, algo_index)
return self.linear_orig_layout(
x, z["head.weight"], PathConfig(rows, False, CMIX_DENSE), "head")
def linear_orig_layout(self, x: torch.Tensor, weight: torch.Tensor, path: PathConfig, group: str) -> torch.Tensor:
if not use_orig_linear(group):
return self.linear(x, weight)
profile_tables = active_gemm_profile_tables(GEMM_PROFILE_ACTIVE, C)
if profile_tables is not None:
if group == "att_c2c" and tuple(weight.shape) == (C, C):
profile_cfg = gemm_profile_config(profile_tables[0], x, path.rows)
elif group == "ffn_key" and tuple(weight.shape) == (4 * C, C):
profile_cfg = gemm_profile_config(profile_tables[1], x, path.rows)
else:
profile_cfg = None
if profile_cfg is GEMM_PROFILE_FALLBACK:
profile_cfg = None
# FP32IO16 B288 is the 24 GiB boundary. The FP16 winners reserve
# another 32 MiB for both attention and key. Keep both GEMMs on
# their existing generic paths here: replacing the 128 MiB key by
# a zero-workspace candidate saved memory but had no stable E2E
# speed gain. FP16 retains its faster profile.
if WKV_MODE == "fp32io16" and path.rows == 288:
if group in ("att_c2c", "ffn_key"):
profile_cfg = None
if profile_cfg is not None:
if group == "att_c2c":
if len(profile_cfg) == 2:
kind = "lt"
workspace_mb, algo_index = profile_cfg
else:
kind, workspace_mb, algo_index = profile_cfg
if kind == "gemmex":
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(
x.contiguous(), weight)
else:
if len(profile_cfg) == 2:
kind = "lt"
workspace_mb, algo_index = profile_cfg
else:
kind, workspace_mb, algo_index = profile_cfg
if kind == "gemmex":
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(
x.contiguous(), weight)
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(
x.contiguous(), weight, workspace_mb, algo_index)
if (
ROWS_CUTLASS_AVAILABLE
and path.rows in ROWS_CUTLASS_C2C_BY_C.get(C, ())
and group == "att_c2c"
and tuple(weight.shape) == (C, C)
# C2560 B1T1024 flipped with graph capture order. Its three tested
# BnTn factorizations were positive in both orders. This exception
# is rows1024-only: B1T768/B1T2048 passed both orders and must not
# be accidentally sent back to the slower backend.
and (C != 2560 or path.rows != 1024 or (x.dim() >= 3 and x.size(0) > 1))
):
return torch.ops.rwkv7_rows_cutlass.linear_orig(
x.contiguous(), weight, 12)
if (
ROWS_CUTLASS_AVAILABLE
and (
path.rows in ROWS_CUTLASS_UP_BY_C.get(C, ())
or (
x.dim() >= 3
and (x.size(0), x.size(1)) in ROWS_CUTLASS_UP_BT_BY_C.get(C, ())
)
)
and group == "ffn_key"
and tuple(weight.shape) == (4 * C, C)
):
return torch.ops.rwkv7_rows_cutlass.linear_orig(
x.contiguous(), weight, 12)
if ORIG_DENSE_GEMM_MODE == "tuned":
if group == "att_c2c" and tuple(weight.shape) == (C, C):
cfg = ORIG_ATT_C2C_GEMM_BY_C.get(C, {}).get(path.rows)
elif group == "ffn_key" and tuple(weight.shape) == (4 * C, C):
cfg = ORIG_FFN_KEY_GEMM_BY_C.get(C, {}).get(path.rows)
else:
cfg = None
if cfg is not None:
kind, workspace_mb, algo_index = cfg
if kind == "gemmex":
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(
x.contiguous(), weight, workspace_mb, algo_index)
if path.rows == 1:
if group == "ffn_key":
if C == 2560:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_exact_f16(x.contiguous(), weight, 128, 2, True)
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_exact_f16(x.contiguous(), weight, 128, 2, C <= 1024)
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_exact_f16(x.contiguous(), weight, 128, 2, group != "att_c2c" or C < 2048)
if path.rows == 2:
if group == "att_c2c":
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_exact_f16(x.contiguous(), weight, 64, 2, True)
if group == "ffn_key":
if C == 2560:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_exact_f16(x.contiguous(), weight, 128, 2, False)
if C < 4096:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_exact_f16(x.contiguous(), weight, 64, 2, True)
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_exact_f16(x.contiguous(), weight, 128, 2, False)
if group == "head" and C == 2560:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_exact_f16(x.contiguous(), weight, 128, 2, False)
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_exact_f16(x.contiguous(), weight, 64, 2, True)
if path.rows == 3:
if group == "head":
if C <= 2048:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
if C == 2560:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_f16(x.contiguous(), weight, 3, 2)
if group == "ffn_key":
if C <= 1024:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_cfg_f16(x.contiguous(), weight, 64, 3, 4)
if C == 2048:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
if C == 2560:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 0)
if group == "att_c2c":
if C == 768:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_f16(x.contiguous(), weight, 1, 2)
if C == 1024:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_f16(x.contiguous(), weight, 2, 2)
if C == 2048:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_f16(x.contiguous(), weight, 3, 4)
if C == 2560:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_f16(x.contiguous(), weight, 3, 2)
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 2)
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_cfg_f16(x.contiguous(), weight, 64, 3, 4)
if path.rows == 4:
if group == "ffn_key":
if C <= 1024:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_cfg_f16(x.contiguous(), weight, 64, 2, 4)
if C == 2048:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
if C == 2560:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 0)
if group == "att_c2c":
if C <= 1024:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_f16(x.contiguous(), weight, 2, 2)
if C == 2048:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_f16(x.contiguous(), weight, 4, 2)
if C == 2560:
return torch.ops.rwkv7_v3a_ops.linear_orig_rows_f16(x.contiguous(), weight, 4, 2)
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 2)
if group == "head":
if C == 768:
if 192 <= path.rows < 256:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 3)
if 96 <= path.rows < 160:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 1)
if C == 1024:
if 256 <= path.rows < 384:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
if 192 <= path.rows < 256:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 2)
if 96 <= path.rows < 160:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 1)
if C == 2048:
if 256 <= path.rows < 384:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 0)
if 192 <= path.rows < 256:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 6)
if 128 <= path.rows < 160:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 1)
if 96 <= path.rows < 112:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 0)
if C == 2560:
if path.rows >= 256:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 0)
if path.rows >= 192:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 5)
if path.rows >= 160:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 5)
if path.rows >= 128:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 1)
if path.rows >= 96:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 0)
if path.rows >= 80:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 0)
if path.rows >= 72:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 1)
if path.rows >= 1024:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 0)
if path.rows >= 512:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 2)
if path.rows >= 384:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 2)
if path.rows >= 256:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 1)
if path.rows >= 192:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 0)
if path.rows >= 160:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 0)
if path.rows >= 128:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 0)
if path.rows >= 112:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 0)
if path.rows >= 96:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 1)
if path.rows >= 80:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 2)
if path.rows >= 72:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 2)
if group == "att_c2c":
if C == 2560 and 17 <= path.rows <= 20:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 0)
if C == 768:
if 256 <= path.rows < 384:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 1)
if 96 <= path.rows < 112:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 3)
if C == 1024:
if 256 <= path.rows < 384:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 0)
if 96 <= path.rows < 112:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 6)
if C == 2048:
if 256 <= path.rows < 384:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 3)
if 192 <= path.rows < 256:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 0)
if 96 <= path.rows < 112:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 4)
if C == 2560:
if path.rows >= 256:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 1)
if path.rows >= 160:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 2)
if path.rows >= 128:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 2)
if path.rows >= 112:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 3)
if path.rows >= 96:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 2)
if path.rows >= 72:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 2)
if path.rows >= 5:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
if path.rows >= 1024:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 4)
if path.rows >= 768:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 0)
if path.rows >= 512:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 1)
if path.rows >= 384:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 2)
if path.rows >= 256:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 4)
if path.rows >= 192:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 0)
if path.rows >= 160:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 1)
if path.rows >= 112:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
if path.rows >= 96:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 5)
if path.rows >= 72:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 0)
if path.rows >= 48:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 6)
if path.rows >= 32:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 0)
if path.rows >= 24:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 6)
if path.rows >= 12:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 0)
if path.rows >= 5:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 2)
if group == "ffn_key":
if C == 2560 and 17 <= path.rows <= 20:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 0)
if C == 768:
if 256 <= path.rows < 384:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
if 96 <= path.rows < 112:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
if C == 1024:
if 256 <= path.rows < 384:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 2)
if 192 <= path.rows < 256:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 0)
if 96 <= path.rows < 160:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 2)
if C == 2048 and 128 <= path.rows < 160:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 3)
if C == 2560:
if path.rows >= 192:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 5)
if path.rows >= 160:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 4)
if path.rows >= 128:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 5)
if path.rows >= 112:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 4)
if path.rows >= 96:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 4)
if path.rows >= 80:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 3)
if path.rows >= 72:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 4)
if path.rows >= 3:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
if path.rows >= 1024:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 0)
if path.rows >= 768:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 1)
if path.rows >= 512:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 3)
if path.rows >= 384:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 0)
if path.rows >= 256:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 4)
if path.rows >= 192:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 1)
if path.rows >= 160:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 2)
if path.rows >= 128:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 0)
if path.rows >= 112:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 3)
if path.rows >= 96:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 32, 1)
if path.rows >= 72:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 128, 1)
if path.rows >= 48:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 1)
if path.rows >= 12:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 0)
if path.rows in (5, 6):
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(x.contiguous(), weight, 0, 1)
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight)
def linear_rank_in(self, x: torch.Tensor, weight: torch.Tensor, weight_t: torch.Tensor, rows: int) -> torch.Tensor:
if weight_t is not None and rows <= LOWRANK_IN_ROWS_T:
return torch.ops.rwkv7_v3a_ops.linear_t_f16(x.contiguous(), weight_t)
return self.linear_lowrank_large(x, weight, weight_t, rows, "in")
def linear_rank_out(self, x: torch.Tensor, weight: torch.Tensor, weight_t: torch.Tensor, rows: int) -> torch.Tensor:
if weight_t is not None and C >= LOWRANK_FUSED_MIN_C and rows <= LOWRANK_OUT_ROWS_T:
return torch.ops.rwkv7_v3a_ops.linear_t_f16(x.contiguous(), weight_t)
return self.linear_lowrank_large(x, weight, weight_t, rows, "out")
def linear_rank_out_act(self, x: torch.Tensor, weight: torch.Tensor, weight_t: torch.Tensor, rows: int, act: int) -> torch.Tensor:
if weight_t is not None and C >= LOWRANK_FUSED_MIN_C and rows <= LOWRANK_OUT_ROWS_T:
return torch.ops.rwkv7_v3a_ops.linear_t_act_f16(x.contiguous(), weight_t, act)
ops = torch.ops.rwkv7_fast_ops_fp16
x = ops.act_tanh(x.contiguous()) if act == 1 else ops.act_sigmoid(x.contiguous())
return self.linear_lowrank_large(x.contiguous(), weight, weight_t, rows, "out")
def linear_lowrank_large(self, x: torch.Tensor, weight: torch.Tensor, weight_t: torch.Tensor, rows: int, direction: str) -> torch.Tensor:
if LOWRANK_GEMM_MODE == "tuned" and C == 4096:
if direction == "in":
rank = weight.size(1) if weight is not None else weight_t.size(0)
cfg = LOWRANK_IN_GEMM_4096.get((rank, rows))
else:
rank = weight.size(0) if weight is not None else weight_t.size(1)
cfg = LOWRANK_OUT_GEMM_4096.get((rank, rows))
if cfg is not None:
backend, workspace_mb, algo_index = cfg
if backend == "runtime_lt" and weight is not None:
return torch.ops.rwkv7_v3a_ops.linear_f16_lt_cfg(
x.contiguous(), weight, workspace_mb, algo_index)
if backend == "orig_lt" and weight_t is not None:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig_lt_cfg(
x.contiguous(), weight_t, workspace_mb, algo_index)
if backend == "orig_gemmex" and weight_t is not None:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight_t)
return self.linear_lowrank_orig(x, weight) if weight is not None else self.linear_t_orig(x, weight_t)
def linear_lowrank_orig(self, x: torch.Tensor, weight: torch.Tensor) -> torch.Tensor:
return torch.ops.rwkv7_v3a_ops.linear_f16(x.contiguous(), weight)
def linear_t_orig(self, x: torch.Tensor, weight_t: torch.Tensor) -> torch.Tensor:
return torch.ops.rwkv7_v3a_ops.linear_f16_orig(x.contiguous(), weight_t)
def add(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
return torch.ops.rwkv7_v3a_ops.add_f16(x.contiguous(), y.contiguous())
def add_ln(self, x: torch.Tensor, residual: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
rows = x.numel() // x.size(-1)
stats_mode = tuned_ln_stats_mode(x.size(0), rows, x.size(-1))
if stats_mode is not None:
outs = torch.ops.rwkv7_v3a_ops.add_layer_norm_f16_stats_cfg(
x.contiguous(), residual.contiguous(), weight, bias, 1.0e-5, stats_mode)
else:
owner_config = tuned_add_ln_owner_config(rows, x.size(-1))
if owner_config is not None:
threads, vectorized = owner_config
outs = torch.ops.rwkv7_v3a_ops.add_layer_norm_f16_cfg(
x.contiguous(), residual.contiguous(), weight, bias, 1.0e-5, threads, vectorized)
else:
outs = torch.ops.rwkv7_v3a_ops.add_layer_norm_f16(
x.contiguous(), residual.contiguous(), weight, bias)
return outs[0], outs[1]
def add_ln_cmix_mix(
self,
x: torch.Tensor,
residual: torch.Tensor,
shift_state: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor,
x_k: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
mode = tuned_cmix_ln_stats_mode(x.size(0), x.size(-1))
if mode is None:
outs = torch.ops.rwkv7_v3a_ops.add_layer_norm_cmix_mix_f16(
x.contiguous(), residual.contiguous(), shift_state, weight, bias, x_k)
else:
outs = torch.ops.rwkv7_v3a_ops.add_layer_norm_cmix_mix_f16_stats_cfg(
x.contiguous(), residual.contiguous(), shift_state, weight, bias, x_k, 1.0e-5, mode)
return outs[0], outs[1]
def add_last_ln(self, x: torch.Tensor, residual: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor) -> torch.Tensor:
return torch.ops.rwkv7_v3a_ops.add_last_layer_norm_f16(x.contiguous(), residual.contiguous(), weight, bias)
def bench_case(
model: RWKV7,
B: int,
T: int,
warmup: int,
iters: int,
profile_range: bool,
deltalog: bool = False,
) -> None:
def percentile(values: list[float], q: float) -> float:
return float(torch.quantile(torch.tensor(values, dtype=torch.float64), q / 100.0).item())
deltalog_m = 0
deltalog_apw = False
deltalog_layout = "separate"
if deltalog:
if WKV_MODE != "fp16" or pp_enabled():
raise ValueError("DeltaLog CLI requires single-device FP16")
if T != 1:
raise ValueError(f"DeltaLog CLI requires BnT1, got B={B} T={T}")
policy = model.deltalog_apw_policy(B)
if policy is not None:
deltalog_m, deltalog_layout, _ = policy
deltalog_apw = True
else:
deltalog_m = model.deltalog_tuned_merge_interval(B)
if deltalog_m == 0:
raise ValueError(f"no tuned DeltaLog CLI path for C={C} B={B}")
state = model.zero_state(B)
token_device = "cpu" if model.emb_cpu else first_device()
tokens = torch.arange(B*T, dtype=torch.long, device=token_device).view(B,T)
tokens = (tokens * 1103515245 + 12345) % V
path = select_path(B, T)
x = model.embed(tokens) if (model.emb_cpu or pp_enabled() or deltalog) else None
def run_once():
if deltalog:
return model.forward_from_x_deltalog_cycle(
x, state, path, deltalog_m, apw=deltalog_apw)
if x is None:
return model.forward(tokens, state)
return model.forward_from_x(x, state, path)
for _ in range(warmup):
run_once()
sync_all()
if pp_enabled():
segments = pp_segments()
stage_inputs = []
stage_vfirst = []
stage_outputs = []
stage_graphs = []
v_first_out = None
prev = x
for stage, (start, end) in enumerate(segments):
dev = layer_device(start)
with torch.cuda.device(dev):
inp = torch.empty((B,T,C), dtype=DTYPE, device=dev)
inp.copy_(prev.to(dev))
vin = None
if start > 0:
vin = torch.empty((B,T,C), dtype=DTYPE, device=dev)
vin.copy_(v_first_out.to(dev))
graph = torch.cuda.CUDAGraph()
stream = torch.cuda.Stream(device=dev)
stream.wait_stream(torch.cuda.current_stream(dev))
with torch.cuda.stream(stream):
warm_out, warm_vf = model.forward_pp_segment(inp, state, path, start, end, vin)
if end == L:
model.forward_pp_tail(warm_out, state, T, advance=False)
torch.cuda.current_stream(dev).wait_stream(stream)
with torch.cuda.graph(graph, stream=stream):
seg_out, vf = model.forward_pp_segment(inp, state, path, start, end, vin)
out = model.forward_pp_tail(seg_out, state, T, advance=False) if end == L else seg_out
stage_inputs.append(inp)
stage_vfirst.append(vin)
stage_outputs.append((out, vf, end == L))
stage_graphs.append(graph)
prev = seg_out
if vf is not None:
v_first_out = vf
sync_all()
times = []
if profile_range:
torch.cuda.cudart().cudaProfilerStart()
for _ in range(iters):
t0 = time.perf_counter()
with torch.cuda.device(layer_device(segments[0][0])):
stage_inputs[0].copy_(x, non_blocking=True)
for stage, graph in enumerate(stage_graphs):
dev = layer_device(segments[stage][0])
with torch.cuda.device(dev):
graph.replay()
out, vf, final_stage = stage_outputs[stage]
if vf is not None:
v_first_out = vf
if not final_stage:
next_start = segments[stage + 1][0]
next_dev = layer_device(next_start)
with torch.cuda.device(next_dev):
stage_inputs[stage + 1].copy_(out, non_blocking=True)
if stage_vfirst[stage + 1] is not None:
stage_vfirst[stage + 1].copy_(v_first_out, non_blocking=True)
model.advance_pp_elapsed(state, T)
sync_all()
times.append((time.perf_counter() - t0) * 1000.0)
if profile_range:
torch.cuda.cudart().cudaProfilerStop()
p10 = percentile(times, 10)
p50 = percentile(times, 50)
p90 = percentile(times, 90)
tok_s = B*T*1000.0 / p50
print(f"RESULT B={B} T={T} iters={iters} p10_ms={p10:.4f} p50_ms={p50:.4f} p90_ms={p90:.4f} tok_s_p50={tok_s:.2f}", flush=True)
print(f"csv,rwkv7_fast_v3a_pp,{B},{T},{iters},{p10:.6f},{p50:.6f},{p90:.6f},{tok_s:.6f}", flush=True)
return
graph = torch.cuda.CUDAGraph(keep_graph=deltalog_apw)
stream = torch.cuda.Stream()
stream.wait_stream(torch.cuda.current_stream())
with torch.cuda.stream(stream):
run_once()
torch.cuda.current_stream().wait_stream(stream)
with torch.cuda.graph(graph, stream=stream):
run_once()
graph_info = None
if deltalog_apw:
graph_info = model.instantiate_deltalog_apw_graph(
graph, state, B, deltalog_m)
torch.cuda.synchronize()
if deltalog:
mode = "apw" if deltalog_apw else "ordinary"
window = graph_info["window_mode"] if graph_info is not None else "none"
print(
f"DELTALOG B={B} T={T} M={deltalog_m} mode={mode} "
f"layout={deltalog_layout} window={window}",
flush=True,
)
times = []
if profile_range:
torch.cuda.cudart().cudaProfilerStart()
for _ in range(iters):
start = torch.cuda.Event(enable_timing=True)
end = torch.cuda.Event(enable_timing=True)
start.record()
graph.replay()
end.record()
torch.cuda.synchronize()
elapsed_ms = float(start.elapsed_time(end))
times.append(elapsed_ms / deltalog_m if deltalog else elapsed_ms)
if profile_range:
torch.cuda.cudart().cudaProfilerStop()
p10 = percentile(times, 10)
p50 = percentile(times, 50)
p90 = percentile(times, 90)
tok_s = B*T*1000.0 / p50
label = "rwkv7_fast_v3a"
if deltalog:
label += "_deltalog_apw" if deltalog_apw else "_deltalog"
print(f"RESULT B={B} T={T} iters={iters} p10_ms={p10:.4f} p50_ms={p50:.4f} p90_ms={p90:.4f} tok_s_p50={tok_s:.2f}", flush=True)
print(f"csv,{label},{B},{T},{iters},{p10:.6f},{p50:.6f},{p90:.6f},{tok_s:.6f}", flush=True)
if deltalog_apw:
state_key = state[1].untyped_storage().data_ptr()
workspace = model.wkv_deltalog_workspace[
(state_key, B, deltalog_m, deltalog_layout)
][0]
ops = torch.ops.rwkv7_wkv_deltalog_v3a
ops.set_persisting_l2_limit(workspace, 0)
ops.reset_persisting_l2_cache(workspace)
if deltalog:
# Graphs retain raw workspace pointers. Destroy the executable before
# releasing logs so allocator reuse cannot invalidate a live graph.
del graph
torch.cuda.synchronize()
model.release_deltalog_workspace(state)
def run_eval(model: RWKV7, eval_json: str, eval_out: str, logits_out: str, paths: str) -> None:
with open(eval_json, "r", encoding="utf-8") as f:
data = json.load(f)
ids = data["tokens"]
outputs = {}
for path in paths.replace(",", " ").split():
token_device = "cpu" if model.emb_cpu else first_device()
targets = torch.tensor(ids[1:], dtype=torch.long, device=last_device() if pp_enabled() else "cuda")
state = model.zero_state(1)
sync_all()
t0 = time.perf_counter()
if path == "b1tn":
tokens = torch.tensor(ids[:-1], dtype=torch.long, device=token_device).view(1, -1)
logits = model.forward_all_logits(tokens, state).squeeze(0).float()
loss = F.cross_entropy(logits, targets, reduction="none")
elif path == "b1t1":
losses = []
for i, tok in enumerate(ids[:-1]):
token = torch.tensor([[tok]], dtype=torch.long, device=token_device)
logits = model.forward(token, state).float()
losses.append(F.cross_entropy(logits, targets[i:i + 1], reduction="none"))
loss = torch.cat(losses)
logits = None
else:
raise ValueError(f"unknown eval path: {path}")
sync_all()
dt = time.perf_counter() - t0
loss_cpu = loss.detach().cpu()
p90 = torch.quantile(loss_cpu.float(), 0.90).item()
p99 = torch.quantile(loss_cpu.float(), 0.99).item()
tok_s = loss_cpu.numel() / dt
print(
f"EVAL label=rwkv7_fast_v3a path={path} positions={loss_cpu.numel()} "
f"mean_loss={loss_cpu.mean().item():.8f} p90_loss={p90:.8f} p99_loss={p99:.8f} "
f"max_loss={loss_cpu.max().item():.8f} min_loss={loss_cpu.min().item():.8f} "
f"time_s={dt:.3f} tok_s={tok_s:.3f}",
flush=True,
)
if logits_out and path == "b1tn":
torch.save(logits.detach().cpu(), logits_out)
outputs[path] = {
"label": "rwkv7_fast_v3a",
"path": path,
"tokens": ids,
"loss": loss_cpu,
"mean_loss": float(loss_cpu.mean().item()),
"p90_loss": float(p90),
"p99_loss": float(p99),
"max_loss": float(loss_cpu.max().item()),
"min_loss": float(loss_cpu.min().item()),
"time_s": float(dt),
}
if eval_out:
torch.save(outputs, eval_out)
if __name__ == "__main__":
main()