File size: 9,810 Bytes
6ed7e38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
"""
CUDA-accelerated TTT (LaCT fast-weight SwiGLU) operators for VLANeXt.

This package provides drop-in CUDA implementations of the two hot operators in
`src/models/ttt.py`:

  - causal_ttt_forward      <-> causal_block_fast_weight_swish_glu  (vision expert)
  - bidirectional_ttt_forward <-> fast_weight_swish_glu_weight_norm_mini_batch_apply (action expert; Phase 2+)

Design (see plans/ttt-cuda-kernel.plan.md):
  * GEMMs are batched 64x64 over (b * num_heads) and are already cuBLAS-optimal;
    we keep them as ATen `bmm` calls from C++.
  * The real cost in the eager torch path is the *kernel-launch storm* of ~20
    tiny elementwise / reduction ops per chunk per layer (silu, the gate/hidden
    products, silu_backprop, the Frobenius normalize, the weight-norm). Those are
    fused into a handful of custom CUDA kernels here.

The extension is JIT-compiled on first import via torch.utils.cpp_extension.load
(no pre-build step). If compilation or CUDA is unavailable, `HAVE_CUDA_TTT` is
False and callers should fall back to the pure-torch ops in `ttt.py`.

IMPORTANT (parity): with muon_update_steps == 0 the reference's
`zeropower_via_newtonschulz5` still divides each [d,d] gradient matrix by its
Frobenius norm (the NS loop is skipped, the normalize is NOT). The kernels
reproduce this. muon_update_steps > 0 (the Newton-Schulz orthogonalization) is
NOT yet supported by the CUDA path and falls back to torch.
"""

import os
import warnings

import torch

_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
_CSRC = os.path.join(_THIS_DIR, "csrc")

HAVE_CUDA_TTT = False
_EXT = None


def _load_extension():
    """JIT-compile and cache the CUDA extension. Returns the module or None."""
    global _EXT, HAVE_CUDA_TTT
    if _EXT is not None:
        return _EXT
    if not torch.cuda.is_available():
        return None
    try:
        from torch.utils.cpp_extension import load

        # Compile for the actual device capability (don't assume sm_90 just
        # because the path says h200 -- these machines are A800/sm_80). Allow
        # override via TORCH_CUDA_ARCH_LIST.
        if "TORCH_CUDA_ARCH_LIST" not in os.environ:
            major, minor = torch.cuda.get_device_capability()
            os.environ["TORCH_CUDA_ARCH_LIST"] = f"{major}.{minor}"
        _EXT = load(
            name="ttt_fused_cuda",
            sources=[
                os.path.join(_CSRC, "ttt_fused.cpp"),
                os.path.join(_CSRC, "ttt_fused.cu"),
            ],
            extra_cuda_cflags=["-O3", "--use_fast_math"],
            extra_cflags=["-O3"],
            verbose=os.environ.get("TTT_CUDA_VERBOSE", "0") == "1",
        )
        HAVE_CUDA_TTT = True
        return _EXT
    except Exception as e:  # pragma: no cover - depends on toolchain
        warnings.warn(f"[ttt_cuda] JIT build failed, falling back to torch: {e}")
        _EXT = None
        HAVE_CUDA_TTT = False
        return None


def causal_ttt_forward(
    w0, w1, w2, q, k, v, lr0, lr1, lr2,
    chunk_size=256,
    vlm_k=None, vlm_v=None, vlm_lr0=None, vlm_lr1=None, vlm_lr2=None,
):
    """CUDA causal block fast-weight SwiGLU forward.

    Mirrors `causal_block_fast_weight_swish_glu` (muon_update_steps == 0 only).
    Shapes match the torch reference:
        w0,w2: [B, d_in, d_h]   w1: [B, d_h, d_out]
        q,k,v: [B, L, d]        lr0,lr1,lr2: [B, L, 1]
        vlm_*: [B, T_ctx, *] or None
    Returns (output [B, L, d_out], w0, w1, w2) -- updated fast weights.
    """
    ext = _load_extension()
    if ext is None:
        raise RuntimeError("ttt_cuda extension unavailable; use the torch fallback.")
    return ext.causal_ttt_forward(
        w0, w1, w2, q, k, v, lr0, lr1, lr2, chunk_size,
        vlm_k, vlm_v, vlm_lr0, vlm_lr1, vlm_lr2,
    )


class _CausalTTTFunction(torch.autograd.Function):
    """Autograd wrapper: CUDA forward + CUDA backward (Plan A).

    forward  -> ext.causal_ttt_forward  (CUDA, falls back to torch ref off-CUDA)
    backward -> ext.causal_ttt_backward (CUDA, Plan A fused BPTT) when available;
                else exact recompute (re-run torch reference under autograd).

    Both backends are validated bit-for-bit against the torch reference
    (test_ttt_cuda_backward.py) and the manual backward (test_ttt_manual_backward.py).
    """

    @staticmethod
    def forward(ctx, chunk_size, w0, w1, w2, q, k, v, lr0, lr1, lr2,
                vlm_k, vlm_v, vlm_lr0, vlm_lr1, vlm_lr2):
        from ..ttt import causal_block_fast_weight_swish_glu
        ctx.chunk_size = chunk_size
        use_cuda = (HAVE_CUDA_TTT and q.is_cuda
                    and q.dtype in (torch.float32, torch.float16, torch.bfloat16))
        ctx.used_cuda = use_cuda
        ext = _load_extension() if use_cuda else None
        entry = (None, None, None)
        if use_cuda and ext is not None and hasattr(ext, "causal_ttt_forward_save"):
            # Phase 1: forward that also saves per-chunk entry weights, so the
            # backward needs no forward-recompute loop.
            with torch.no_grad():
                res = ext.causal_ttt_forward_save(
                    w0, w1, w2, q, k, v, lr0, lr1, lr2, chunk_size,
                    vlm_k, vlm_v, vlm_lr0, vlm_lr1, vlm_lr2,
                )
            out, nw0, nw1, nw2 = res[0], res[1], res[2], res[3]
            entry = (res[4], res[5], res[6])  # entry_w0/w1/w2 [n_chunk,B,*,*]
        elif use_cuda:
            with torch.no_grad():
                out, nw0, nw1, nw2 = causal_ttt_forward(
                    w0, w1, w2, q, k, v, lr0, lr1, lr2, chunk_size=chunk_size,
                    vlm_k=vlm_k, vlm_v=vlm_v,
                    vlm_lr0=vlm_lr0, vlm_lr1=vlm_lr1, vlm_lr2=vlm_lr2,
                )
        else:
            with torch.no_grad():
                out, nw0, nw1, nw2 = causal_block_fast_weight_swish_glu(
                    w0, w1, w2, q, k, v, lr0, lr1, lr2,
                    chunk_size=chunk_size, muon_update_steps=0,
                    vlm_k=vlm_k, vlm_v=vlm_v,
                    vlm_lr0=vlm_lr0, vlm_lr1=vlm_lr1, vlm_lr2=vlm_lr2,
                )
        ctx.save_for_backward(w0, w1, w2, q, k, v, lr0, lr1, lr2,
                              vlm_k, vlm_v, vlm_lr0, vlm_lr1, vlm_lr2,
                              entry[0], entry[1], entry[2])
        return out, nw0, nw1, nw2

    @staticmethod
    def backward(ctx, g_out, g_w0, g_w1, g_w2):
        saved = ctx.saved_tensors
        (w0, w1, w2, q, k, v, lr0, lr1, lr2,
         vlm_k, vlm_v, vlm_lr0, vlm_lr1, vlm_lr2,
         entry_w0, entry_w1, entry_w2) = saved

        # ---- Plan A: CUDA fused backward ----
        ext = _load_extension()
        if ctx.used_cuda and ext is not None and hasattr(ext, "causal_ttt_backward"):
            g_out_c = g_out.contiguous()
            res = ext.causal_ttt_backward(
                w0, w1, w2, q, k, v, lr0, lr1, lr2, ctx.chunk_size,
                g_out_c, g_w0.contiguous(), g_w1.contiguous(), g_w2.contiguous(),
                vlm_k, vlm_v, vlm_lr0, vlm_lr1, vlm_lr2,
                entry_w0, entry_w1, entry_w2,  # Phase 1: skip forward-recompute
            )
            # res = [gw0,gw1,gw2, gq,gk,gv, glr0,glr1,glr2, (gvk,gvv,gvl0,gvl1,gvl2)]
            gw0, gw1, gw2, gq, gk, gv, gl0, gl1, gl2 = res[:9]
            if vlm_k is not None and len(res) >= 14:
                gvk, gvv, gvl0, gvl1, gvl2 = res[9:14]
            else:
                gvk = gvv = gvl0 = gvl1 = gvl2 = None
            # mask grads for inputs that didn't require grad
            def m(t, g):
                return g if (t is not None and t.requires_grad) else None
            return (None,  # chunk_size
                    m(w0, gw0), m(w1, gw1), m(w2, gw2),
                    m(q, gq), m(k, gk), m(v, gv),
                    m(lr0, gl0), m(lr1, gl1), m(lr2, gl2),
                    m(vlm_k, gvk), m(vlm_v, gvv),
                    m(vlm_lr0, gvl0), m(vlm_lr1, gvl1), m(vlm_lr2, gvl2))

        # ---- fallback: exact recompute backward ----
        from ..ttt import causal_block_fast_weight_swish_glu
        core = saved[:14]  # drop the 3 entry-weight tensors (CUDA-path only)
        diff_inputs = [t for t in core if t is not None and t.requires_grad]
        if not diff_inputs:
            return (None,) * 15
        with torch.enable_grad():
            ins = [t.detach().requires_grad_(t.requires_grad) if t is not None else None
                   for t in core]
            (w0_, w1_, w2_, q_, k_, v_, lr0_, lr1_, lr2_,
             vk_, vv_, vl0_, vl1_, vl2_) = ins
            out, nw0, nw1, nw2 = causal_block_fast_weight_swish_glu(
                w0_, w1_, w2_, q_, k_, v_, lr0_, lr1_, lr2_,
                chunk_size=ctx.chunk_size, muon_update_steps=0,
                vlm_k=vk_, vlm_v=vv_, vlm_lr0=vl0_, vlm_lr1=vl1_, vlm_lr2=vl2_,
            )
            need = [t for t in ins if t is not None and t.requires_grad]
            grads = torch.autograd.grad(
                [out, nw0, nw1, nw2], need,
                grad_outputs=[g_out, g_w0, g_w1, g_w2], allow_unused=True,
            )
        gi = iter(grads)
        out_grads = [None]
        for t in ins:
            if t is not None and t.requires_grad:
                out_grads.append(next(gi))
            else:
                out_grads.append(None)
        return tuple(out_grads)


def causal_ttt(
    w0, w1, w2, q, k, v, lr0, lr1, lr2,
    chunk_size=256,
    vlm_k=None, vlm_v=None, vlm_lr0=None, vlm_lr1=None, vlm_lr2=None,
):
    """Autograd-aware causal TTT: CUDA forward + exact recompute backward.

    Drop-in for `causal_block_fast_weight_swish_glu(..., muon_update_steps=0)`.
    Returns (output, w0, w1, w2).
    """
    return _CausalTTTFunction.apply(
        chunk_size, w0, w1, w2, q, k, v, lr0, lr1, lr2,
        vlm_k, vlm_v, vlm_lr0, vlm_lr1, vlm_lr2,
    )