Safe By Design AI

GraniteSpeechPlusForConditionalGeneration

GraniteSpeechPlusForConditionalGeneration at 40 layers and hidden size 2048. One published checkpoint has this shape.

The pass (block level)

The order of operations for one forward. ×N marks a position that fires once per layer.

7 steps, one level. Enough to name the steps, not enough to show what nests inside what. Recorded from a complete forward of this shape in the author's independent implementation — the structure is what that run emitted, not a reading of the configuration.

encodeUnits
└─ block ×N
   ├─ ff1
   ├─ attn
   ├─ conv
   ├─ ff2
   └─ post_ln

The same thing in canonical form:

0(1*N(2 3 4 5 6))

Numbers are positions in the pass, not layer indices. Two passes count as the same structure when these strings match.

Implementing it

One forward, written out, per checkpoint whose arithmetic actually differs. A shape groups checkpoints by class, depth, width and layer stack — none of which decides an activation, a rope base or a routing rule — so where the members of this shape disagree there is a block each, and each names the checkpoint it was generated from.

It is not the recorded pass above, which is captured from a real forward; it is what the published configuration says the arithmetic is. Where the configuration does not say, the line says that instead of guessing.

The names are the ones the published checkpoint uses where a name is published, and canonical otherwise. A checkpoint loader may store them differently — fusing a gate/up pair into one matrix, or splitting one published projection in two — and those are that loader's names, not the model's. Everything spelled here is what the download contains.

Generated from granite-speech-4.1-2b-plus.

# Mel features in, tokens out. The encoder is a conformer; what sits on
# top of it is what distinguishes these four 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(prenorm(x))         # half-weight feed-forward
    # Blocked, not full: frames split into non-overlapping 200-frame
    # windows, attention is block-diagonal, no window reads another.
    for w in windows(x, 200):           # each window separately
        b = q @ k.T / sqrt(head_dim) + shaw_rel_pos(rel_pos_emb, w)
        w[:] = softmax(b) @ v             # Shaw relative positions:
                                          #   a learned bias per offset,
                                          #   added to the scores. No
                                          #   rope anywhere in this tower.
    x = x + to_out(attn_blocked(prenorm(x)))
    x = x + conv(x)                       # LayerNorm, pointwise, depthwise,
                                          #   BatchNorm, pointwise
    x = x + 0.5 * ff2(prenorm(x))         # the second half-weight FFN
    x = post_norm(x)
    if i in [3]:  # plus captures a copy here, before the detour
        captured.append(copy(x))      # order is ascending by index
    if i == 8:                          # self-conditioning, once, mid-stack
        x = x + out_mid(softmax(out(x)))  # the CTC head's own opinion,
                                          # fed back into the residual

#   The conformer tower, as published. Every line here is a tensor shape a
#   port allocates, and none of it is stated anywhere else on this page.
#     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`

# Plus: the projector reads more than the final residual. `cat_hidden_layers: [3]`
# names encoder states captured before the self-conditioning detour,
# and they are concatenated with the final one — so the Q-Former's
# input is 2 x hidden wide, not hidden. Plain 4.1 reads the
# residual stream directly and its projector is half as wide. Same
# class name, same depth, same width, a different input.
enc = concat(captured + [x], dim=-1)      # [T, 2 * hidden]

q = learned_query                         # a fixed set of queries, reused per window [., 1024]
for each window of 15 encoder rows:        # windowed Q-Former, BLIP-2 block
    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, so a
# clip that is not a multiple of the window still emits the full row
# count for its last window. Truncating loses the end of most clips.
rows = linear(q)                          # 3 rows per window, decoder-wide

#   The Q-Former projector, as published. Every line here is a tensor shape a
#   port allocates, and none of it is stated anywhere else on this page.
#     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`
#     vocab          30522   `vocab_size`
#     max positions   2048   `max_position_embeddings`

x = embed[ids]; x[at_the_audio_placeholder] = rows

# The decoder is an ordinary dense Granite, and its four multipliers are
# not optional. They are plain floats with no analogue in a Llama
# config, they apply at different points in the pass, and a port
# that drops them generates fluent text that is not this model's.

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: the norm feeds the
                                          #   block, the residual below
                                          #   carries the unnormalised x
    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, not
                                          #   after. Rotating the output
                                          #   of attention is a model
                                          #   whose positions do nothing.
    s = q @ k.T * 0.0078125               # attention_multiplier replaces
                                          #   1/sqrt(head_dim) — it is a
                                          #   substitute, 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)                # pre-norm stacks normalise once
                                          #   more at the end. Without it
                                          #   the head reads a residual
                                          #   stream nothing scaled.
logits = x @ embed.T
                                          #   Tied to the input embedding
                                          #   (`tie_word_embeddings: true`)
logits = logits / 8                       # logits_scaling divides.
                                          #   Multiplying has the same
                                          #   shape and flattens or
                                          #   sharpens every sample.

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 is a learned bias per offset, added to the scores. There is no rope in this tower at all, and a port that runs full attention with rope is more expensive, transcribes, and is not this model.

The self-conditioning detour fires once, after block 8 of 16: the mid-stack CTC head's distribution is projected back into the residual stream. A tower that skips it runs and transcribes worse.

cat_hidden_layers: [3] is the whole of what "plus" means. The projector reads those encoder states concatenated with the final residual, so its input is 2 x hidden where plain 4.1's is hidden. The captures are taken before the self-conditioning rejoin, which is the opposite of the NAR model's, and the copies are appended in ascending index order. Nothing else in the configuration differs — same class shape, same depth, same width.

The Q-Former's last window is zero-padded to a full window rather than truncated, so a clip whose frame count is not a multiple of the window still emits that window's full row count and the caller trims. Truncating instead loses the end of every clip that does not divide evenly, which is most of them.

Geometry

layers40
hidden size2,048
attention heads16, 4 key/value
feed-forward width4,096
vocabulary100,353
trained context4,096 tokens
largest checkpoint2.1B

Weight structure

The tensors one element of the repeating stack holds, by the names the published checkpoint uses.

repeating stackdepthwhat one element holds
encoder.layers.#16attn.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.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

How this was checked

implemented, evidence grade shape-pack, per the assessment: converted checkpoints of this shape run; this checkpoint's own weights are 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:

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

modelparameterscontext
granite-speech-4.1-2b-plus2.1B4,096