File size: 10,207 Bytes
3c2b90f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5e2c860
 
 
 
 
 
 
 
3c2b90f
 
 
 
 
 
 
 
 
 
20672ab
3c2b90f
20672ab
 
5e2c860
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c2b90f
 
 
bfe7b2e
f01b8a7
3c2b90f
 
 
 
 
 
 
 
 
 
bfe7b2e
 
 
 
 
3c2b90f
 
 
 
 
 
 
 
 
 
 
5e2c860
 
 
 
 
 
 
 
3c2b90f
 
 
 
 
 
 
 
 
 
f01b8a7
3c2b90f
 
 
 
 
bfe7b2e
 
 
 
3c2b90f
 
 
 
 
 
 
 
 
 
 
 
 
 
20672ab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# -*- coding: utf-8 -*-
"""Kernel MAMBA-3 (v11, doc 22 §2) — varredura seletiva com discretização
TRAPETZOIDAL, em PyTorch puro, vetorizado e DIFERENCIÁVEL por autograd.

Fonte: arquivo MAMBA_3.txt (kernel Cython `mamba3_cpu_kernel.pyx`) — NÃO
copiado: o kernel Cython (a) fixava `a_val = A[b, d, 0]` (ignorava a
dependência de A em n — gradiente de A[·,·,n>0] errado), (b) exigia
compilação por plataforma e backward manual propenso a erro, e (c)
materializava h_states (B,L,D,N) inteiro. Aqui (Teoremas 22.2–22.5):

  • A é por (d, n): α_{t,d,n} = exp(δ_{t,d}·a_{d,n}) — recuperação do SSM
    canônico; o gradiente de A flui EXATO via autograd (Corolário 22.2.1);
  • atualização trapezoidal: h_t = α_t·h_{t−1} + (δ_t/2)·(1+α_t)·B̄_t·u_t
    com B̄_{t,n} = Σ_r B_{t,r,n} (MIMO rank-reduzido, como a fonte);
  • ESTABILIDADE (Teo 22.3): δ = σ(·) ∈ (0,1) e a_{d,n} < 0 ⇒
    α_{t,d,n} ∈ (e^{a}, 1) ⊂ (0,1) — a recorrência é CONTRAÇÃO; estado
    limitado ‖h_t‖ ≤ max_s (δ_s/2)(1+α_s)|B̄_s·u_s| / (1−α_max);
  • v18 — CLAMPS DE SANIDADE (Teo 30.2, requisito §1.1.5/§2.4): a
    contração do Teo 22.3 exige δ∈(0,1) e a<0. Se uma anomalia
    (inicialização ruim, gradiente explosivo, uso direto da API) violar
    isso, α = exp(δ·a) ≥ 1 e a recorrência DIVERGE geometricamente.
    A varredura agora fixa delta ∈ [1e-6, 1−1e-6] e A ≤ −1e-6 ANTES de
    α=exp(δ·a). Como δ e A válidos NUNCA são alterados pelo clamp
    (identidade na região válida), o gradiente autograd é inalterado
    (Teo 30.2-cor: clamp idêntico na região válida ⟹ ∇ idêntico);
  • memória O(B·D·N) por passo (estado vivo) — sem tensor (B,L,D,N);
  • semente inicial h_0 = 0 (convenção de estado frio — idêntica à fonte).

Assinatura compatível com o autograd manual da fonte (u, delta, A, B, C) —
troca DROP-IN: o autograd do PyTorch substitui `mamba3_backward_cpu`
(prova de exatidão: teste `test_v11_mamba3_grad_vs_finitediff`).
"""
from __future__ import annotations

import torch
import torch.nn.functional as F

__all__ = ["varredura_trapezoidal", "Mamba3Funcao", "verificar_estabilidade",
           "conv1d_causal", "set_conv_cython"]

# v18 — clamps de sanidade (Teo 30.2). Ligados por padrão: custam O(1)
# por tensor e garantem a contração mesmo sob anomalia numérica.
CLAMPS_SEGURANCA = True
DELTA_MIN, DELTA_MAX = 1e-6, 1.0 - 1e-6
A_MAX = -1e-6                       # força a < 0 (contração)


def verificar_estabilidade(delta: torch.Tensor, A: torch.Tensor) -> dict:
    """Auditoria (sem mutação): quantas entradas de δ/A violariam a
    contração do Teo 22.3 ANTES do clamp. Telemetria para o TelemetryHub
    (violações > 0 ⟹ anomalia a montante: investigar inicialização/grads)."""
    with torch.no_grad():
        n_delta_fora = int(((delta <= 0.0) | (delta >= 1.0)).sum())
        n_A_nao_neg = int((A >= 0.0).sum())
    return {"mamba3/delta_fora": n_delta_fora, "mamba3/A_nao_negativa": n_A_nao_neg}


def varredura_trapezoidal(u: torch.Tensor, delta: torch.Tensor, A: torch.Tensor,
                          B_m: torch.Tensor, C_m: torch.Tensor,
                          devolve_estado: bool = False):
    """Varredura causal seletiva MAMBA-3.

    Formas:
      u:     (B, L, D)     entrada u_t
      delta: (B, L, D)     passo de tempo δ_t ∈ (0,1) (gate sigmoid)
      A:     (D, N) ou (B, D, N) — dinâmica de estado (a < 0 estável)
      B_m:   (B, L, R, N)  projeção de entrada MIMO (rank R)
      C_m:   (B, L, R, N)  projeção de leitura MIMO (rank R)
    Devolve out: (B, L, D) com out_t,d = Σ_n (Σ_r C_{t,r,n}) · h_{t,d,n}.

    v12 — `devolve_estado=True` devolve TAMBÉM h_final: (B, D, N), o estado
    h_L da recorrência (Teorema 24.13: h_L é EXATAMENTE o estado que o
    decode incremental recebe — mesma recorrência, então
    varredura(u_{1..L}) ⊕ passo(u_{L+1}) ≡ varredura(u_{1..L+1})).

    Laço temporal O(L) vetorizado sobre (B, D, N, R) — CPU/GPU sem kernel
    custom; exato no tempo (sem aproximação além da própria regra do
    trapézio, que é a DEFINIÇÃO do MAMBA-3 — Teorema 22.2).
    """
    Bsz, L, D = u.shape
    N = A.shape[-1]
    R = B_m.shape[2]
    if A.dim() == 2:                      # (D, N) → expande p/ lote
        A = A.unsqueeze(0).expand(Bsz, -1, -1)
    assert B_m.shape == (Bsz, L, R, N) and C_m.shape == (Bsz, L, R, N), \
        "BUG (doc 22 §2): formas MIMO incompatíveis"
    if CLAMPS_SEGURANCA:
        # Teo 30.2 (v18, requisito §1.1.5/§2.4): contração GARANTIDA por
        # construção — δ∈[1e-6,1−1e-6] e A≤−1e-6 ⟹ α=exp(δ·a) ∈
        # [exp((1−1e-6)·(−1e-6)) , exp(1e-6·(−1e-6))) ⊂ (0,1) SEMPRE.
        # Na região válida o clamp é a identidade ⟹ ∇ inalterado
        # (Teo 30.2-cor: d clamp(x)/dx = 1 dentro da faixa).
        delta = delta.clamp(DELTA_MIN, DELTA_MAX)
        A = A.clamp(max=A_MAX)
    # agregação MIMO: b̄_t,n = Σ_r B[t,r,n]; c̄_t,n = Σ_r C[t,r,n]  (O(B·L·D·N·R))
    b_bar = B_m.sum(dim=2)                # (B, L, N)
    c_bar = C_m.sum(dim=2)                # (B, L, N)
    # δ_{t,d}·a_{d,n} → α por (B, L, D, N)
    dtA = delta.unsqueeze(-1) * A.unsqueeze(1)          # (B, L, D, N)
    alpha = torch.exp(dtA)                              # Teo 22.3: ∈ (0,1)
    beta = 0.5 * delta                                  # (B, L, D) — δ/2
    # termo de entrada: β_t·(1+α_t)·b̄_{t,n}·u_t,d → (B, L, D, N)
    entrada = (beta.unsqueeze(-1) * (1.0 + alpha) * b_bar.unsqueeze(2)) \
        * u.unsqueeze(-1)                               # (B,L,D,N)
    h = torch.zeros(Bsz, D, N, dtype=u.dtype, device=u.device)
    saidas = []
    for t in range(L):                                  # O(L) — estado vivo
        h = alpha[:, t] * h + entrada[:, t]
        # leitura: out_t,d = Σ_n h_{t,d,n}·c̄_{t,n}
        saidas.append((h * c_bar[:, t].unsqueeze(1)).sum(-1))
    out = torch.stack(saidas, dim=1)                    # (B, L, D)
    if devolve_estado:
        return out, h                                   # h_final (B, D, N)
    return out


class Mamba3Funcao(torch.autograd.Function):
    """Ponte de compatibilidade com a API da fonte (`Mamba3Function.apply`).
    Mantida para os testes compararem com a interface (u, delta, A, B, C);
    o backward é o autograd PADRÃO (exato — Corolário 22.2.1)."""

    @staticmethod
    def forward(ctx, u, delta, A, B_m, C_m):
        return varredura_trapezoidal(u, delta, A, B_m, C_m)

    @staticmethod
    def backward(ctx, grad_out):                        # pragma: no cover
        raise RuntimeError("use tensor.backward() — autograd exato (doc 22)")


# ======================================================================
# v21 — CONV CAUSAL PROFUNDA FUNDIDA (doc 33 §4, Teos 33.11/33.12)
# ======================================================================
# Kernel Cython (acelerado/mamba3hot.pyx) com backward ANALÍTICO exato
# (regra da cadeia na conv causal — testado contra autograd: erro ≤ 1e-5
# relativo). Ganho medido: 2.3–7.6× vs o caminho torch (pad+Conv1d+
# 2 transposes+copy — ~5× o tráfego de memória do mínimo). FALLBACK
# automático ao caminho torch v20 (mesmo cálculo) quando: sem binário,
# dtype ≠ fp32 ou device ≠ cpu. Kill-switch: set_conv_cython(False).
try:                                    # binário commitado (praxe v9.1)
    import mamba3hot as _hot            # build top-level (acelerado/)
except ImportError:                     # pragma: no cover
    try:
        from khtst.acelerado import mamba3hot as _hot
    except Exception:
        _hot = None

_CONV_CYTHON = True                     # requisito v21: Cython prioritário


def set_conv_cython(ativa: bool) -> None:
    """Kill-switch do caminho Cython da conv (A/B e fallback de emergência)."""
    global _CONV_CYTHON
    _CONV_CYTHON = bool(ativa)


class _ConvCausalProfunda(torch.autograd.Function):
    """Ponte autograd: forward = kernel C fundido; backward = fórmulas
    analíticas EXATAS (Teo 33.11-ii — ∂u/∂w/∂b da conv causal depthwise)."""

    @staticmethod
    def forward(ctx, u, w, b):
        ctx.save_for_backward(u, w)
        from khtst.mamba3.kernel import _hot as hot
        out = hot.conv1d_causal_profunda(
            u.detach().contiguous().numpy(),
            w.detach().contiguous().numpy(),
            b.detach().contiguous().numpy())
        return torch.from_numpy(out)

    @staticmethod
    def backward(ctx, g):
        # v21-nota: backward ANALÍTICO de 1ª ordem (Teo 33.11-ii). `g` é
        # detachado — double-backward pelo kernel C não é suportado (os
        # testes de gradiente do projeto são de 1ª ordem; o caminho torch
        # de fallback mantém autograd de ordem arbitrávia).
        from khtst.mamba3.kernel import _hot as hot
        u, w = ctx.saved_tensors
        K = int(w.shape[1])
        gd = g.detach().contiguous().numpy()
        gu = torch.from_numpy(hot.conv1d_grad_u(
            gd, w.detach().contiguous().numpy()))
        gw = torch.from_numpy(hot.conv1d_grad_w2(
            gd, u.detach().contiguous().numpy(), K))
        gb = torch.from_numpy(hot.conv1d_grad_b(gd))
        return gu, gw, gb


def conv1d_causal(u: torch.Tensor, pesos_dk: torch.Tensor,
                  vies: torch.Tensor) -> torch.Tensor:
    """Conv depthwise causal (B,L,D) → (B,L,D) — FONTE ÚNICA do hot-path
    do Mamba-3 (R21.5).

    Caminho Cython fundido (fp32/CPU) quando disponível; caso contrário o
    caminho torch v20 EXATO (pad esquerdo K−1 + Conv1d groups=D + volta
    do layout — Teo 33.11: ambos computam
    uc[b,l,d] = Σ_j w[d,j]·u[b, l+j−K+1, d] + v[d], equivalência medida
    max|Δ| = 0)."""
    if _hot is not None and _CONV_CYTHON \
            and u.dtype == torch.float32 and u.is_cpu:
        return _ConvCausalProfunda.apply(u, pesos_dk, vies)
    K = pesos_dk.shape[1]
    return F.conv1d(F.pad(u.transpose(1, 2), (K - 1, 0)),
                    pesos_dk.unsqueeze(1), vies,
                    groups=pesos_dk.shape[0]).transpose(1, 2).contiguous()