GraniteSpeechForConditionalGeneration
GraniteSpeechForConditionalGeneration at 40 layers and hidden size 2048. 3 published checkpoints share this shape.
Implementing it
How to read this sheet: method.
This section is not the recorded pass (that is on passes); it is what the published configuration and the modelling code say the arithmetic is.
From granite-4.0-1b-speech
Also covers granite-speech-4.1-2b.
# Mel features in, tokens out. The encoder is a conformer; what sits on
# top of it is what distinguishes the speech classes from each other.
x = input_linear(features) # [T, 1024]
for i in 0 .. 15: # conformer block, five modules in this order
x = x + 0.5 * ff1(x) # pre_norm -> up_proj -> silu -> down_proj
x = x + to_out(attn_blocked(pre_norm(x))) # defined below
x = x + conv(x) # LayerNorm, pointwise, GLU,
# depthwise, BatchNorm, silu,
# pointwise
x = x + 0.5 * ff2(x) # the second half-weight FFN
x = post_norm(x)
if i == 7: # after block 8 of 16: self-conditioning
x = x + out_mid(softmax(out(x))) # the CTC head's own distribution,
# fed back into the residual
# The conformer tower, as published:
# layers 16 `num_layers`
# width 1024 `hidden_dim`
# heads 8 `num_heads`
# head dim 128 `dim_head`
# ffn mult 4 `feedforward_mult`
# conv kernel 15 `conv_kernel_size`
# output dim 348 `output_dim`
# attn_blocked, used once per block above. Blocked, not full: frames are
# split into non-overlapping 200-frame windows (the tail is zero-padded
# and masked), attention is block-diagonal, no window reads another.
def attn_blocked(h):
out = empty_like(h)
for w, o in zip(windows(h, 200), windows(out, 200)):
q = to_q(w); k, v = split(to_kv(w), 2)
b = (q @ k.T + q @ rel_pos_emb[dist].T) / sqrt(head_dim)
o[:] = softmax(b) @ v # Shaw relative positions: a
# learned vector per clamped
# offset, dotted with q and
# added to the scores. No rope
# anywhere in this tower.
return out
enc = x # the final residual, only
for each window of 15 encoder rows: # windowed Q-Former, BLIP-2 block
q = layernorm(query) # 3 learned queries [3, 1024], reused
# for every window, LayerNorm'd once
# before layer 0
for layer in 0 .. 1:
q = self_attn(q); q = cross_attn(q, window); q = ffn(q) # post-norm
# The tail window is zero-padded to a full window, not truncated, and
# the processor sizes the audio placeholder run for the padded count.
rows = linear(q) # 3 rows per window, decoder-wide
# The Q-Former projector, as published:
# layers 2 `num_hidden_layers`
# width 1024 `hidden_size`
# heads 16 `num_attention_heads`
# ffn width 4096 `intermediate_size`
# activation gelu `hidden_act`
# norm eps 1e-12 `layer_norm_eps`
x = embed[ids]; x[at_the_audio_placeholder] = rows
# The decoder is an ordinary dense Granite, and its four multipliers
# apply at different points in the pass.
x = x * 12 # embedding_multiplier, once, on the way in
for i in 0 .. 39:
h = rmsnorm(x, layers[i].input_layernorm, eps=1e-05) # pre-norm
q, k, v = h @ layers[i].self_attn.q_proj.T, h @ layers[i].self_attn.k_proj.T, h @ layers[i].self_attn.v_proj.T # 16 q heads, 4 kv
q, k = rope(q, k, theta=10000) # before the scores
s = q @ k.T * 0.0078125 # attention_multiplier replaces
# 1/sqrt(head_dim), not a factor
# applied beside it
a = softmax(s, mask=causal)
o = (a @ v) @ layers[i].self_attn.o_proj.T
x = x + 0.22 * o # residual_multiplier on both
h = rmsnorm(x, layers[i].post_attention_layernorm)
m_ = (silu(h @ layers[i].mlp.gate_proj.T) * (h @ layers[i].mlp.up_proj.T)) @ layers[i].mlp.down_proj.T
x = x + 0.22 * m_ # ...and on the MLP add too
x = rmsnorm(x, model.norm)
logits = x @ lm_head.T
logits = logits / 8 # logits_scaling divides
granite-speech-3.3-2b: differs from the block above
Same arithmetic; only these values change.
granite-4.0-1b-speech, granite-speech-4.1-2b | granite-speech-3.3-2b | |
|---|---|---|
| decoder heads (q / kv) | 16 / 4 | 32 / 8 |
| decoder FFN width | 4,096 | 8,192 |
| rope theta | 10,000 | 10,000,000 |
attention_multiplier | 0.0078125 | 0.015625 |
| output head | lm_head (tie_word_embeddings: false) | the input embedding (tie_word_embeddings: true) |
| vocabulary | 100,353 | 49,160 |
encoder output_dim | 348 | 256 |
Sources, transformers v4.57.1: the conformer block, its feed-forward, block attention with Shaw positions and conv module; the encoder loop and self-conditioning; the windowed Q-Former projector and the BLIP-2 Q-Former's LayerNorm on the queries before layer 0; the placeholder count; the Granite decoder's attention_multiplier, residual_multiplier, embedding_multiplier and logits_scaling.
Notes that apply to more than one block
The conformer's five modules run in that order and the two feed-forwards are half-weighted — each contributes 0.5 * to the residual. That factor is structural, carries no tensor, and is invisible in a weight listing.
The tower never runs a full attention. Frames are split into non-overlapping context_size: 200-frame windows and attention is block-diagonal — no window reads another. Inside a window the positions are Shaw relative: attn.rel_pos_emb holds a learned vector per clamped offset, dotted with the query and added to the scores. There is no rope in this tower; full attention with rope is a different function.
Self-conditioning fires once, after the middle block (num_layers // 2, counting from 1): the mid-stack CTC head's distribution is projected back into the residual stream through out_mid. A tower that skips it is a different function.
The Q-Former's last window is zero-padded to a full window rather than truncated, and the processor counts ceil(frames / 15) * 3 audio placeholders, so every row the projector emits — the padded window's included — has a slot. Truncating instead loses the end of every clip that does not divide evenly.
Geometry
| layers | 40 |
| hidden size | 2,048 |
| attention heads | 32, 8 key/value (granite-speech-3.3-2b); 16, 4 key/value (granite-4.0-1b-speech, granite-speech-4.1-2b) |
| feed-forward width | 8,192 (granite-speech-3.3-2b); 4,096 (granite-4.0-1b-speech, granite-speech-4.1-2b) |
| vocabulary | 49,160 (granite-speech-3.3-2b); 100,353 (granite-4.0-1b-speech, granite-speech-4.1-2b) |
| trained context | 131,072 tokens (granite-speech-3.3-2b); 4,096 tokens (granite-4.0-1b-speech, granite-speech-4.1-2b) |
| largest checkpoint | 3B |
Weight structure
The tensors one element of the repeating stack holds, by the names the published checkpoint uses.
| repeating stack | depth | what one element holds |
|---|---|---|
encoder.layers.# | 16 | attn.pre_norm.bias, attn.pre_norm.weight, attn.rel_pos_emb.weight, attn.to_kv.weight, attn.to_out.bias, attn.to_out.weight, attn.to_q.weight, conv.batch_norm.bias, conv.batch_norm.num_batches_tracked, conv.batch_norm.running_mean, conv.batch_norm.running_var, conv.batch_norm.weight, conv.depth_conv.conv.weight, conv.down_conv.bias, conv.down_conv.weight, conv.norm.bias, conv.norm.weight, conv.up_conv.bias, conv.up_conv.weight, ff1.down_proj.bias, ff1.down_proj.weight, ff1.pre_norm.bias, ff1.pre_norm.weight, ff1.up_proj.bias, ff1.up_proj.weight, ff2.down_proj.bias, ff2.down_proj.weight, ff2.pre_norm.bias, ff2.pre_norm.weight, ff2.up_proj.bias, ff2.up_proj.weight, post_norm.bias, post_norm.weight |
granite-speech-3.3-2b's repository holds two shard sets: the 4-file set its model.safetensors.index.json references, and an older 3-file set (5.7 GB) that nothing references. A loader that follows the index reads only the 4-file set; a tool that globs *.safetensors sees every tensor twice (findings §10).
Other shapes of GraniteSpeechForConditionalGeneration
Identical across all of them: layers 40.
Only the columns that differ are shown; a cell with several values means the checkpoints of that shape disagree.
| shape | width | heads | KV heads | FFN width | vocabulary | context | checkpoints |
|---|---|---|---|---|---|---|---|
40L x 4,096 | 4,096 | 32 | 8 | 12,800 | 49,156 / 49,160 | 131,072 | 2 |
40L x 2,048 (this sheet) | 2,048 | 16 / 32 | 4 / 8 | 4,096 / 8,192 | 49,160 / 100,353 | 4,096 / 131,072 | 3 |
How this was checked
Status and evidence grade in the assessment (see what the grades mean):
granite-speech-3.3-2b: runs,class— the architecture class is implemented; nothing checkpoint-specific was measuredgranite-4.0-1b-speech,granite-speech-4.1-2b: runs,shape-pack— stored checkpoints of this shape run; this one's own is not among them
The implementation meant here and below is an unpublished independent inference implementation by the author.
What stands behind the block above, beyond the published configuration it is read from:
- a conformer tower and projector implemented from the published configuration
- a checkpoint loader, which is where published tensor names are read
- an independent reference implementation of this architecture in Python, driving the published modelling code — an executable statement of what the model should compute, written against the publication rather than against any one implementation of it
Where an independent implementation and the published configuration disagree, the configuration is what this page reports and the disagreement is what it says.
Checkpoints with this architecture
| model | parameters | context | heads | FFN width | vocabulary |
|---|---|---|---|---|---|
granite-speech-3.3-2b | 3B | 131,072 | 32, 8 key/value | 8,192 | 49,160 |
granite-4.0-1b-speech | 2.3B | 4,096 | 16, 4 key/value | 4,096 | 100,353 |
granite-speech-4.1-2b | 2.3B | 4,096 | 16, 4 key/value | 4,096 | 100,353 |
Recorded pass: passes.