Safe By Design AI

ModernBertForSequenceClassification

ModernBertForSequenceClassification at 22 layers and hidden size 768. One published checkpoint has this shape.

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.

No forward was recorded for this shape, so there is no pass above to compare it against — this block is what the published configuration says the arithmetic is, not a transcript of a run. 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-embedding-reranker-english-r2.

x = layernorm(embed[ids], emb_norm)       # [T, 768], no position table

for i in 0 .. 21:
    # Alternating attention: layer i is global when i % 3 == 0
    # (so layer 0 is), and windowed otherwise. The window compares
    # |i - j| <= 128 // 2, so `128` spans 64 either side.
    glob = (i % 3 == 0)
    # Layer 0 has no attn_norm — the embedding norm already ran, so
    # 21 layers carry six tensors and layer 0 carries five. A loader
    # that assumes uniform layers normalizes twice here.
    h = x if i == 0 else layernorm(x, layers[i].attn_norm)
    q, k, v = split(h @ layers[i].attn.Wqkv.T, 3)
    q, k = rope(q, k, theta=160000 if glob else 40000)
    a = softmax(q @ k.T / sqrt(64), mask=bidirectional, window=None if glob else 128) @ v
    # Not causal. Every position sees every other one — that is the whole
    # difference between this and the decoders above, and it is why these
    # models embed a sequence rather than continue it.
    x = x + a @ layers[i].attn.Wo.T
    h = layernorm(x, layers[i].mlp_norm)
    g, u = split(h @ layers[i].mlp.Wi.T, 2)
    x = x + (gelu(g) * u) @ layers[i].mlp.Wo.T

x = layernorm(x, final_norm)
h_cls = x[0]                              # the CLS row, not a mean pool
p     = tanh(h_cls @ classifier.dense.T + classifier.dense.bias)
score = p @ classifier.out_proj.T + classifier.out_proj.bias
# The tanh is not optional. Dropping it leaves a smooth, confidently
# wrong ordering rather than an error — nothing about the output says
# the head was mis-ported.

The head is out_b + out_w · tanh(dense_w · h[CLS] + dense_b), not a single linear over a pooled row — four tensors, the CLS row rather than a mean, and a tanh between the two matrices. A reranker's entire output is the head's output, so this is also why the head stays f32 where the body is Q8: quantization noise here is not averaged over a residual stream, it lands in the number that decides the ranking.

The attention here is bidirectional, which is the one difference that matters: there is no causal mask, so a position may read the whole sequence. Running this stack causally is fluent and is not the model's function.

local_attention: 128 with global_attn_every_n_layers: 3 is the whole reason this model reads long documents cheaply, and a port that makes every layer global is correct-looking and quadratically slower.

position_embedding_type on these checkpoints is inert and misleading. It says absolute on two of them and relative_key_query on the others, and this family ignores it entirely — it always rotates. The keys that decide anything are the two rope bases, global_attn_every_n_layers, local_attention and hidden_activation, and they differ between siblings in both directions, so none can be assumed from another.

Layer 0 carries five tensors where every other layer carries six, because the embedding norm has already run and its attn_norm is an identity. That is the trap the shapes announce and a loader is most likely to miss: assume uniform layers and it either fails to find the tensor or, worse, normalizes twice.

Geometry

layers22
hidden size768
attention heads12
feed-forward width1,152
vocabulary50,368
trained context8,192 tokens
largest checkpoint149.6M

Weight structure

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

repeating stackdepthwhat one element holds
model.layers.#22attn.Wo.weight, attn.Wqkv.weight, attn_norm.weight, mlp.Wi.weight, mlp.Wo.weight, mlp_norm.weight

How this was checked

implemented, evidence grade own-pack, per the assessment: a converted weight file for this checkpoint exists, but no forward was recorded from it. 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-embedding-reranker-english-r2149.6M8,192