Findings
Things worth knowing before you read a Granite configuration as though it were a description of the model. These are not claims about model quality; they are places where the metadata says something other than what a careful reader would take it to mean, and each one is a trap somebody has to walk into once.
Every count below is recomputed from the published configurations and their digests each time this page is generated.
1. The class name does not always describe the stack
7 models declare GraniteMoeHybridForCausalLM and then list every
layer as attention. There is no state-space layer in them. The hybrid class
is the configuration family they were cut from, not a statement about what is
in the model.
| model | layers | stack as listed |
|---|---|---|
granite-4.0-1b | 40 | 40 attention |
granite-4.0-1b-base | 40 | 40 attention |
granite-4.0-350m | 28 | 28 attention |
granite-4.0-350m-base | 28 | 28 attention |
granite-4.0-micro | 40 | 40 attention |
granite-4.0-micro-base | 40 | 40 attention |
granite-guardian-4.0-3b-toxicity-ja | 40 | 40 attention |
Read the layer table, not the class name. The published layer_types
table is the reliable fact; grouping on the class name puts pure-attention
models in the family whose whole point is that it is not one. Each of the first
six shares its class and its nominal size with a hybrid twin, but not its
geometry: granite-4.0-1b is 40 x 2048 where granite-4.0-h-1b is 40 x 1536,
granite-4.0-350m is 28 x 1024 where granite-4.0-h-350m is 32 x 768, and
granite-4.0-micro is 40 x 2560 where granite-4.0-h-micro is 40 x 2048. The
class name does not separate them; the layer table does. Nor does the model
name settle it the other way: granite-4.0-tiny-preview and
granite-4.0-tiny-base-preview carry no -h- and are hybrid all the same.
So the family grouping in models.md follows the layer table, and a sheet's shape includes its stack as well as its class, depth and width (method §4). The pairs are set out in generations.md.
2. Keys left behind by the template
11 models carry mamba_* geometry keys — state dimensions, convolution
widths — with no state-space layer to use them. 17 declare
num_local_experts: 0, an expert count on a model that has no experts.
Read literally, a reader is told a model is a mixture of experts with zero of them. Read as what it is — a field the template carried and this checkpoint does not use — it is harmless. A tool that branches on presence rather than on value gets this wrong.
3. One fact, several spellings
Granite spans several upstream codebases and each brought its own vocabulary for the same number.
| fact | spelling | models |
|---|---|---|
| depth | num_hidden_layers | 118 |
| depth | n_layer | 9 |
| depth | num_layers | 5 |
| depth | encoder_num_layers | 1 |
| width | hidden_size | 117 |
| width | d_model | 8 |
| width | n_embd | 7 |
| width | embedding_feature_dim | 1 |
| context | max_position_embeddings | 117 |
| context | n_positions | 7 |
Reading only the common spelling silently loses every model of the older code
lineage. Depth is not always num_hidden_layers.
4. The geometry is not always in the same place
| where | models |
|---|---|
| at the top level | 116 |
| nowhere — no configuration published | 33 |
under text_config | 15 |
| published, but no depth field | 6 |
under encoder_config | 2 |
A model with a language half nests under text_config. One without — an
encoder that emits tokens directly — nests under encoder_config and has no
text config at all. Looking in one place finds most models and misses a family.
5. Multi-query attention is declared by a flag
7 models set multi_query: true and publish no
num_key_value_heads. Every query head shares one key/value head. A reader
that infers grouping only from a head count records these as having no grouping
at all, which is the exact opposite of the truth.
| model | heads | key/value heads |
|---|---|---|
granite-20b-code-base-8k | 48 | 1 (by flag) |
granite-20b-code-base-r1.1 | 48 | 1 (by flag) |
granite-20b-code-instruct-8k | 48 | 1 (by flag) |
granite-20b-code-instruct-r1.1 | 48 | 1 (by flag) |
granite-20b-functioncalling | 48 | 1 (by flag) |
granite-34b-code-base-8k | 48 | 1 (by flag) |
granite-34b-code-instruct-8k | 48 | 1 (by flag) |
6. Many checkpoints are indistinguishable by configuration
16 groups of models publish a byte-identical config.json — the same
sha256 in evidence/claims.json, so a reader can confirm each group from the
digests alone. A base and an instruction-tuned model, a code base and its
instruct variant, a revision and its predecessor, even two consecutive releases, are one
architecture; the
weights differ and nothing in the configuration says so. Nor does a shared
configuration promise a shared tensor set: granite-4.1-8b and
granite-4.1-8b-base publish the same file byte for byte, and only the first
ships a separate lm_head.weight
(§10).
These are the groups that match byte for byte, provenance fields included.
Setting aside fields that record how a file was written rather than what the
model is — _name_or_path, transformers_version, torch_dtype — can only
merge more: safety classifiers and adapter libraries that repeat their base's
geometry are candidates. This bundle carries digests, not whole configurations,
so it does not list those further groups rather than list them unchecked.
This is why nothing in this reference tries to pick "the" model for an
architecture, and why the role column in models.md is derived
from the name — it is the only place that distinction is recorded.
7. Context lengths are not all round numbers
| tokens | models | note |
|---|---|---|
| 131,072 | 61 | = 128 x 1024 |
| 8,192 | 24 | |
| 4,096 | 17 | |
| 514 | 7 | = 512 + 2 (RoBERTa-style position offset); every one is a RoBERTa or XLM-RoBERTa model |
| 32,768 | 6 | |
| 128,000 | 4 | = 125 x 1024, and universally called 128k |
| 2,048 | 2 | |
| 512 | 2 | |
| 16,384 | 1 |
Both values get called "128k". They differ by 3,072 tokens. This reference prints the number the configuration holds and leaves the naming alone.
8. Not every model publishes a configuration, and not every configuration has a depth
33 of 172 models have no
config.json at all: adapters that ship only adapter weights,
earth-observation releases with their own formats, and dataset-ablation
artifacts. They are real published models and they cannot be described by the
method used here, which is why they appear in models.md with
dashes rather than being dropped.
A further 6 publish a configuration that
simply has no layer count — the speculative-decoding heads, whose size is how
many tokens they propose (n_predict) rather than how many layers they stack.
A published configuration without a depth field is not a missing one, and
counting the two together overstates how much of this catalogue is undocumented.
9. A global rope base lower than the local one
ModernBERT-class configurations carry two rope bases: global_rope_theta for
the layers that attend over the whole input (every third layer,
global_attn_every_n_layers: 3) and local_rope_theta for the rest, which
attend within 64 positions either side (local_attention: 128)
(modeling_modernbert.py at transformers v4.57.1, L464–L471). In three of the five
Granite models of this class the global base is the larger one, as the longer
reach of those layers would suggest. In the two multilingual models it is the
other way round: global_rope_theta is 150,000 and local_rope_theta is
160,000.
| model | class | context | global base | local base |
|---|---|---|---|---|
granite-embedding-311m-multilingual-r2 | ModernBertModel | 32,768 | 150,000 | 160,000 |
granite-embedding-97m-multilingual-r2 | ModernBertModel | 32,768 | 150,000 | 160,000 |
granite-embedding-english-r2 | ModernBertModel | 8,192 | 80,000 | 10,000 |
granite-embedding-small-english-r2 | ModernBertModel | 8,192 | 80,000 | 10,000 |
granite-embedding-reranker-english-r2 | ModernBertForSequenceClassification | 8,192 | 160,000 | 40,000 |
These are the values the configurations publish, and the modelling code applies
each to the layers its name says. Nothing here says whether the multilingual values were
intended; the point is that a reader who assumes the global base is the larger
of the two, or who reads one rope_theta for the whole model, gets these two
checkpoints wrong.
10. The configuration does not always describe the shipped tensors
A configuration says what a loader should build; the weight files say what was
saved. The two were compared for every model that publishes both a
configuration and readable safetensors headers — 135 of them — using the
tensor inventory in evidence/tensors.json
(how it was built is under provenance).
Declared depth, width, vocabulary, query and key/value heads, expert counts
and Mamba projection widths agree with the tensor shapes everywhere. The one
depth gap is expected: the three granite-switch-4.1 previews declare one
layer more than they ship, and that layer is the switch slot, which holds no
weights. What does not agree:
| model | the configuration says | the shipped tensors show | revision |
|---|---|---|---|
granite-3.0-1b-a400m-base | tie_word_embeddings: true | a separate lm_head.weight [49152, 1024] | d91cbed |
granite-3.0-2b-instruct | tie_word_embeddings: true | a separate lm_head.weight [49155, 2048] | 5ad66c1 |
granite-3.0-3b-a800m-base | tie_word_embeddings: true | a separate lm_head.weight [49152, 1536] | 142d2c8 |
granite-3.0-3b-a800m-instruct | tie_word_embeddings: true | a separate lm_head.weight [49155, 1536] | 51ced9b |
granite-4.1-8b | tie_word_embeddings: true | a separate lm_head.weight [100352, 4096] | 1504002 |
granite-vision-3.3-2b-chart2csv-preview | tie_word_embeddings: true, at the top level and in text_config | a separate language_model.lm_head.weight [49156, 2048] | 298ed4d |
granite-4.0-3b-vision | tie_word_embeddings: true, at the top level and in text_config | a separate lm_head.weight [100353, 2560] | bf108f3 |
granite-speech-3.2-8b | GraniteSpeechForConditionalGeneration, whose encoder in transformers v4.57.1 is encoder.input_linear and encoder.layers.# | encoder tensors under a different layout: encoder.rnn_tr.#, with attn.fn.to_q, ff1.fn.fn.net.# and conv.net.# inside each block | d583502 |
granite-speech-3.3-2b | one 40-layer decoder | two complete shard sets side by side: the 4-file set that model.safetensors.index.json references (6.1 GB) and an older 3-file set (5.7 GB) that nothing references; a loader that follows the index reads only the 4-file set | 4ac2f02 |
granite-guardian-3.2-5b-lora-harm-correction | a complete GraniteForCausalLM, 28 × 4,096 | only adapter_model.safetensors: rank-32 LoRA factors on the query, key and value projections of 28 layers, and no base weights | 9d20ea5 |
What a loader does with each.
- A tied flag and a shipped head (the first seven rows). In transformers
v4.57.1,
from_pretrainedcallstie_weights()again after the checkpoint has been read (L5065). When the decoder's text configuration hastie_word_embeddingstrue (L2994), that call sets the output projection's weight to the input embedding's (L3113–L3118). The shippedlm_head.weightis read in first, each tensor being assigned to its module (L663–L667), and is then replaced: the head the model computes with isembed_tokens. LlavaNext first renameslanguage_model.lm_headtolm_head(L540), andgranite-4.0-3b-vision's own class subclassesLlavaNextForConditionalGeneration(modeling.py L39), so both end the same way. The headers give shapes, not values, so this reference cannot say whether any shipped head equals its embedding. If it does, the tie changes nothing. If it does not, a standard load discards the shipped head, and a loader that uses whateverlm_head.weightit finds computes different logits from the same files. The extra tensor does show up in the published totals:granite-4.1-8bis 8.8B wheregranite-4.1-8b-base, same configuration, is 8.4B, and the difference is exactly 100,352 × 4,096. granite-speech-3.2-8b. transformers v4.57.1 builds the encoder asinput_linearandlayers(L262–L263) and the class declares no key mapping, so going by names alone none of the shipped encoder tensors has a parameter to load into. The standard path would report them as unexpected and leave the encoder at its initial values. This reference has not run that load. The recorded forward in support comes from the author's implementation, whose loader reads the published names.granite-speech-3.3-2b. A loader that followsmodel.safetensors.index.jsonreads one copy. One that opens every.safetensorsfile in the repository meets each name twice, and which copy wins depends on the order it reads them in. The inventory does not record which files the index lists, and it does not say whether the two copies are equal.granite-guardian-3.2-5b-lora-harm-correction. Loaded as the class it names, it finds none of that class's weights. The configuration describes the base the adapter applies to, and support treats the row as an adapter.
Parameter counts. The published totals match the sum of tensor elements to
within stored integer counters (num_batches_tracked, and the granite-switch
adapter lookup tables), except in four models. For granite-4.0-3b-vision
and granite-vision-3.3-2b-chart2csv-preview the published total is the
tensor sum minus exactly the lm_head.weight elements (256,903,680 and
100,671,488). For granite-speech-3.3-2b it matches the indexed shard set; the
unreferenced older set is left out. For granite-speech-4.1-2b-plus the headers hold 65,536 more
elements than the published total, and nothing in them explains the gap.
Adapter files shipped beside a checkpoint are left out of these sums.
So a configuration is a statement of what the model should be, and the published weights are what a loader actually has to reconcile with it. Where the two disagree, the pages here report the configuration and point here.