GraniteMoeForCausalLM
GraniteMoeForCausalLM at 24 layers and hidden size 1024. 4 published checkpoints share this shape.
Implementing it
How to read this sheet: method.
From granite-3.1-1b-a400m-instruct
Also covers granite-3.1-1b-a400m-base.
x = embed[ids] * 12 # [T, 1024] <- embedding_multiplier
for i in 0 .. 23:
h = rmsnorm(x, layers[i].input_layernorm, eps=1e-06)
q = h @ layers[i].self_attn.q_proj.T # [T, 16*64]
k = h @ layers[i].self_attn.k_proj.T # [T, 8*64]
v = h @ layers[i].self_attn.v_proj.T # [T, 8*64]
q, k = rope(q, k, theta=1500000)
k, v = repeat_kv(k, v, 2) # 16 query heads share 8 key/value heads
a = softmax(q @ k.T * 0.015625, mask=causal) @ v # <- attention_multiplier
a = a @ layers[i].self_attn.o_proj.T
x = x + a * 0.22 # <- residual_multiplier
h = rmsnorm(x, layers[i].post_attention_layernorm)
moe = layers[i].block_sparse_moe
r = (h @ moe.router.layer.weight.T).float() # [T, 32]
r_top, sel = topk(r, 8) # tie order unspecified
w = softmax(r_top) # over the 8 selected, not all 32
y = 0
for (e, w_e) in zip(sel, w): # summation order: see notes
g, u = split(h @ moe.input_linear.weight[e].T, 2)
y += w_e * ((silu(g) * u) @ moe.output_linear.weight[e].T)
x = x + y * 0.22
x = rmsnorm(x, model.norm)
logits = (x @ embed.T) / 6 # tied to the input embedding; <- logits_scaling
Differs from the block above
Only the rope base differs. Everything else is as above.
| checkpoints | rope theta |
|---|---|
granite-3.0-1b-a400m-base, granite-3.0-1b-a400m-instruct | 10,000 |
Notes
head_dim is not published; 1024 / 16 = 64 is used.
Output head: the configuration says tied, the checkpoint ships a separate one. granite-3.0-1b-a400m-base sets tie_word_embeddings: true, which is what the block follows, but its checkpoint also ships a separate lm_head.weight [49152, 1024]. A standard transformers load reads that tensor and then, because the configuration asks for tying, replaces it with the input embedding (modeling_utils.py L2994, L5065), so the logits it computes are the ones written here. The published headers carry shapes, not values, so whether the shipped head equals embed is not known; if it does not, a loader that uses the shipped lm_head.weight computes different logits (findings §10). granite-3.0-1b-a400m-instruct does not.
Routing. The router logits are cast to float32, the top 8 are taken with topk, and the gates are the softmax of those 8 logits only, not the top 8 of a softmax over all experts (modeling_granitemoe.py L294–L296). PyTorch does not guarantee which index topk returns for tied values (torch.topk). transformers sorts the token–expert pairs by ascending expert index (L310) and adds the gated expert outputs back with one index_add (L371); summation order beyond that is not part of the model's definition.
Multiplier placement: embedding, attention scale, residual, logits.
Geometry
| layers | 24 |
| hidden size | 1,024 |
| attention heads | 16, 8 key/value |
| feed-forward width | 512 |
| experts | 32, 8 active per token |
| vocabulary | 49,152 (granite-3.0-1b-a400m-base, granite-3.1-1b-a400m-base); 49,155 (granite-3.0-1b-a400m-instruct, granite-3.1-1b-a400m-instruct) |
| trained context | 4,096 tokens (granite-3.0-1b-a400m-base, granite-3.0-1b-a400m-instruct); 131,072 tokens (granite-3.1-1b-a400m-instruct, granite-3.1-1b-a400m-base) |
| largest checkpoint | 1.4B |
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 |
|---|---|---|
model.layers.# | 24 | block_sparse_moe.input_linear.weight, block_sparse_moe.output_linear.weight, block_sparse_moe.router.layer.weight, input_layernorm.weight, post_attention_layernorm.weight, self_attn.k_proj.weight, self_attn.o_proj.weight, self_attn.q_proj.weight, self_attn.v_proj.weight |
Other shapes of GraniteMoeForCausalLM
Identical across all of them: KV heads 8, FFN width 512, active 8.
Only the columns that differ are shown; a cell with several values means the checkpoints of that shape disagree.
| shape | layers | width | heads | experts | vocabulary | context | checkpoints |
|---|---|---|---|---|---|---|---|
32L x 1,536 | 32 | 1,536 | 24 | 40 | 49,152 / 49,155 | 4,096 / 131,072 | 5 |
24L x 1,024 (this sheet) | 24 | 1,024 | 16 | 32 | 49,152 / 49,155 | 4,096 / 131,072 | 4 |
How this was checked
Status and evidence grade in the assessment (see what the grades mean):
- each of the 4 checkpoints of this shape: runs,
shape-parity— a stored checkpoint of the same shape was compared against a reference
Parts of the author's unpublished implementation that exist for this shape:
- a decoder 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
Checkpoints with this architecture
| model | parameters | context | vocabulary |
|---|---|---|---|
granite-3.0-1b-a400m-base | 1.4B | 4,096 | 49,152 |
granite-3.0-1b-a400m-instruct | 1.3B | 4,096 | 49,155 |
granite-3.1-1b-a400m-instruct | 1.3B | 131,072 | 49,155 |
granite-3.1-1b-a400m-base | 1.3B | 131,072 | 49,152 |
Recorded pass: passes.