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
// arch/granite_nar/projector.h - NLE EncoderProjectorQFormer.
//
// Reference: EncoderProjectorQFormer in the NLE HF repo's modeling_nle.py.
// Strictly simpler than the AR granite BLIP-2 Q-Former: only cross-
// attention + MLP per layer, no self-attention. Windowed mean-pool of
// the encoder K/V plus a learned `window_positions` bias acts in place
// of positional encodings.
//
// Forward shape (per upstream):
//
// in [4 * 1024, T_enc] // 4 captured encoder layers concatenated
// for j in [0..4):
// in[j*1024:(j+1)*1024] = per_layer_LN_j(in[j*1024:(j+1)*1024])
// in = layer_projector(in) + GELU // [2048, T_enc]
// pad along T_enc → nblocks * 15
// reshape to [2048, 15, nblocks]
// kv = mean over the window axis -> [2048, nblocks]
// kv = kv + window_positions (broadcast across nblocks) // [2048, 15, nblocks]
// query = learned [2048, 3, 1] repeated across nblocks -> [2048, 3, nblocks]
// for layer in [0, prj_n_layers):
// # cross-attention sublayer (post-LN, BLIP-2 style):
// q = norm_attn(query)
// q' = linear(cross_attn_q, q)
// k' = linear(cross_attn_k, kv)
// v' = linear(cross_attn_v, kv)
// attn = softmax(q' k'^T / sqrt(head_dim)) v'
// query = query + linear(cross_attn_o, attn)
// # FFN sublayer:
// ff_in = norm_ffn(query)
// query = query + linear(ffn_fc2, SiLU(linear(ffn_fc1, ff_in)))
// query = out_norm(query)
// tokens = reshape(query, [2048, 3 * nblocks])
// out = linear(out_linear, tokens) // [2048, 3 * nblocks]
//
// Note that the upstream layer math is PRE-LN (not post-LN like the AR
// BLIP-2 layer): norm comes BEFORE each sublayer's linear cluster, and
// the residual closes after the second linear.
//
// This header is INTERNAL to src/arch/granite_nar/.
;
;
;
namespace transcribe::granite_nar // namespace transcribe::granite_nar