File size: 10,056 Bytes
b88c26d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
// BURT-IMMA Python Bindings
// License: BSL-1.1
// Contact: jessica@collectivekitty.com

#include <torch/extension.h>
#include <pybind11/pybind11.h>
#include <vector>

namespace py = pybind11;


// ---------------------------------------------------------------------------
// Kernel declarations (implementations in CUDA source files)
// ---------------------------------------------------------------------------

// Constrained softmax with spectral norm bound on attention weights.
// Applies softmax along last dimension with constraint that resulting
// distribution has entropy <= max_entropy.
// Args:
//   logits: (batch, seq_len, seq_len) or (batch, heads, seq_len, seq_len)
//   max_entropy: maximum allowed entropy (default 0.20)
//   temperature: initial temperature for sharpening
// Returns:
//   Tensor of same shape with constrained softmax applied
torch::Tensor constrained_softmax(

    torch::Tensor logits,

    double max_entropy,

    double temperature

) {
    // Placeholder: calls CUDA kernel when available
    // For now, compute on CPU/GPU via PyTorch ops
    auto scaled = logits / temperature;
    auto probs = torch::softmax(scaled, -1);

    // Iteratively sharpen if entropy exceeds bound
    auto entropy = -(probs * (probs + 1e-10).log()).sum(-1);
    auto violations = entropy > max_entropy;

    if (violations.any().item<bool>()) {
        double temp = temperature;
        for (int i = 0; i < 50; i++) {
            temp *= 0.8;
            auto p = torch::softmax(logits / temp, -1);
            auto h = -(p * (p + 1e-10).log()).sum(-1);
            if ((h <= max_entropy).all().item<bool>()) {
                probs = p;
                break;
            }
            probs = p;
        }
    }

    return probs;
}


// Coupled Input-Forget Gate memory cell update.
// C_new = f * C_old + (1-f) * candidate
// where f is computed from the forget gate weight and input.
// Args:
//   C_old: (batch, d_mem, d_mem) - current memory matrix
//   key: (batch, d_mem) - key vector for write
//   value: (batch, d_mem) - value vector for write
//   forget_bias: scalar bias for forget gate
// Returns:
//   C_new: (batch, d_mem, d_mem) - updated memory matrix
torch::Tensor cifg_update(

    torch::Tensor C_old,

    torch::Tensor key,

    torch::Tensor value,

    double forget_bias

) {
    // Compute forget gate from key norm
    auto f = torch::sigmoid(key.norm(-1, true) + forget_bias);  // (batch, 1)

    // Outer product candidate
    auto candidate = torch::bmm(
        key.unsqueeze(-1),      // (batch, d_mem, 1)
        value.unsqueeze(-2)     // (batch, 1, d_mem)
    );  // (batch, d_mem, d_mem)

    // Normalize candidate
    auto cand_norm = candidate.norm() + 1e-8;
    candidate = candidate / cand_norm;

    // CIFG update: C_new = f * C_old + (1-f) * candidate
    auto f_expanded = f.unsqueeze(-1);  // (batch, 1, 1)
    auto C_new = f_expanded * C_old + (1.0 - f_expanded) * candidate;

    return C_new;
}


// Batched CIFG update for multiple memory slots.
// Args:
//   C_old: (batch, num_slots, d_mem, d_mem)
//   keys: (batch, num_slots, d_mem)
//   values: (batch, num_slots, d_mem)
//   forget_biases: (num_slots,)
// Returns:
//   C_new: (batch, num_slots, d_mem, d_mem)
torch::Tensor batched_cifg_update(

    torch::Tensor C_old,

    torch::Tensor keys,

    torch::Tensor values,

    torch::Tensor forget_biases

) {
    auto batch = C_old.size(0);
    auto num_slots = C_old.size(1);
    auto d_mem = C_old.size(2);

    auto C_new = torch::zeros_like(C_old);

    for (int64_t s = 0; s < num_slots; s++) {
        auto C_slot = C_old.select(1, s);           // (batch, d_mem, d_mem)
        auto k_slot = keys.select(1, s);            // (batch, d_mem)
        auto v_slot = values.select(1, s);          // (batch, d_mem)
        auto bias = forget_biases[s].item<double>();

        auto updated = cifg_update(C_slot, k_slot, v_slot, bias);
        C_new.select(1, s).copy_(updated);
    }

    return C_new;
}


// Sparse Mixture-of-Experts dispatch.
// Routes each token to top-k experts based on gating scores.
// Args:
//   x: (batch, seq_len, d_model) - input tokens
//   gate_weights: (d_model, num_experts) - gating weight matrix
//   expert_weights: list of (d_model, d_model) - expert weight matrices
//   top_k: number of experts per token
// Returns:
//   output: (batch, seq_len, d_model) - routed output
torch::Tensor sparse_moe_dispatch(

    torch::Tensor x,

    torch::Tensor gate_weights,

    torch::Tensor expert_weights,

    int64_t top_k

) {
    auto batch = x.size(0);
    auto seq_len = x.size(1);
    auto d_model = x.size(2);
    auto num_experts = gate_weights.size(1);

    // Flatten batch and sequence
    auto x_flat = x.reshape({batch * seq_len, d_model});

    // Compute gating scores
    auto scores = torch::mm(x_flat, gate_weights);  // (B*S, num_experts)
    auto probs = torch::softmax(scores, -1);

    // Top-k selection
    auto [top_vals, top_idx] = torch::topk(probs, top_k, -1);
    top_vals = top_vals / (top_vals.sum(-1, true) + 1e-8);

    // Dispatch to experts
    auto output = torch::zeros_like(x_flat);
    for (int64_t k = 0; k < top_k; k++) {
        for (int64_t e = 0; e < num_experts; e++) {
            auto mask = (top_idx.select(1, k) == e);
            if (mask.any().item<bool>()) {
                auto x_masked = x_flat.index({mask});
                // expert_weights shape: (num_experts, d_model, d_model)
                auto W_e = expert_weights.select(0, e);
                auto out_e = torch::mm(x_masked, W_e);
                auto w_k = top_vals.select(1, k).index({mask}).unsqueeze(-1);
                output.index_put_({mask}, output.index({mask}) + w_k * out_e);
            }
        }
    }

    return output.reshape({batch, seq_len, d_model});
}


// Bi-encoder cross-attention.
// Computes cross-attention between two encoded sequences.
// Args:
//   query: (batch, q_len, d_model) - query sequence
//   key: (batch, kv_len, d_model) - key sequence
//   value: (batch, kv_len, d_model) - value sequence
//   W_Q: (d_model, d_model) - query projection
//   W_K: (d_model, d_model) - key projection
//   W_V: (d_model, d_model) - value projection
// Returns:
//   output: (batch, q_len, d_model)
torch::Tensor biencoder_attention(

    torch::Tensor query,

    torch::Tensor key,

    torch::Tensor value,

    torch::Tensor W_Q,

    torch::Tensor W_K,

    torch::Tensor W_V

) {
    auto d_model = query.size(-1);
    auto scale = std::sqrt(static_cast<double>(d_model));

    // Project
    auto Q = torch::mm(
        query.reshape({-1, d_model}), W_Q
    ).reshape_as(query);
    auto K = torch::mm(
        key.reshape({-1, d_model}), W_K
    ).reshape_as(key);
    auto V = torch::mm(
        value.reshape({-1, d_model}), W_V
    ).reshape_as(value);

    // Attention scores
    auto scores = torch::bmm(Q, K.transpose(-2, -1)) / scale;
    auto attn = torch::softmax(scores, -1);
    auto output = torch::bmm(attn, V);

    return output;
}


// Fused attention + softmax kernel.
// Computes scaled dot-product attention with optional causal mask.
// Args:
//   Q: (batch, heads, seq_len, d_head)
//   K: (batch, heads, seq_len, d_head)
//   V: (batch, heads, seq_len, d_head)
//   causal: whether to apply causal mask
// Returns:
//   output: (batch, heads, seq_len, d_head)
torch::Tensor attention_softmax(

    torch::Tensor Q,

    torch::Tensor K,

    torch::Tensor V,

    bool causal

) {
    auto d_head = Q.size(-1);
    auto seq_len = Q.size(-2);
    auto scale = std::sqrt(static_cast<double>(d_head));

    // Compute attention scores
    auto scores = torch::matmul(Q, K.transpose(-2, -1)) / scale;

    // Apply causal mask if requested
    if (causal) {
        auto mask = torch::triu(
            torch::full({seq_len, seq_len}, -std::numeric_limits<float>::infinity(),
                        Q.options()),
            1
        );
        scores = scores + mask;
    }

    // Softmax
    auto attn = torch::softmax(scores, -1);

    // Weighted sum
    return torch::matmul(attn, V);
}


// ---------------------------------------------------------------------------
// pybind11 module definition
// ---------------------------------------------------------------------------

PYBIND11_MODULE(_burt_imma_cuda, m) {
    m.doc() = "BURT-IMMA CUDA-accelerated kernels";

    m.def("constrained_softmax", &constrained_softmax,
          "Constrained softmax with entropy bound",
          py::arg("logits"),
          py::arg("max_entropy") = 0.20,
          py::arg("temperature") = 1.0);

    m.def("cifg_update", &cifg_update,
          "Coupled Input-Forget Gate memory update",
          py::arg("C_old"),
          py::arg("key"),
          py::arg("value"),
          py::arg("forget_bias") = 0.0);

    m.def("batched_cifg_update", &batched_cifg_update,
          "Batched CIFG memory update for multiple slots",
          py::arg("C_old"),
          py::arg("keys"),
          py::arg("values"),
          py::arg("forget_biases"));

    m.def("sparse_moe_dispatch", &sparse_moe_dispatch,
          "Sparse Mixture-of-Experts dispatch with top-k routing",
          py::arg("x"),
          py::arg("gate_weights"),
          py::arg("expert_weights"),
          py::arg("top_k") = 1);

    m.def("biencoder_attention", &biencoder_attention,
          "Bi-encoder cross-attention",
          py::arg("query"),
          py::arg("key"),
          py::arg("value"),
          py::arg("W_Q"),
          py::arg("W_K"),
          py::arg("W_V"));

    m.def("attention_softmax", &attention_softmax,
          "Fused attention + softmax with optional causal mask",
          py::arg("Q"),
          py::arg("K"),
          py::arg("V"),
          py::arg("causal") = true);
}