Smollm3 drops 3/4 RoPE layers while 1/4 is intended by whats claimed in the blog post.
Author: tobiaskatsch
Pull request: no
Labels: name: bug

### System Info

- `transformers` version: v5.0.0
- Python version: N/A (bug in library code)
- PyTorch version: N/A (bug in library code)

### Who can help?

@ArthurZucker @Cyrilvallez

### Information

- [ ] The official example scripts
- [ ] My own modified scripts

### Tasks

- [ ] An officially supported task in the `examples` folder (such as GLUE/SQuAD, ...)
- [ ] My own task or dataset (give details below)


**Relevant files:**
- Modeling: [`src/transformers/models/smollm3/modeling_smollm3.py`](https://github.com/huggingface/transformers/blob/main/src/transformers/models/smollm3/modeling_smollm3.py)

**Blog statement** ([SmolLM3 blog post](https://huggingface.co/blog/smollm3)):
> "removes positional encoding every 4th layer"

This means 25% of layers should be NoPE (1 out of every 4 layers has no RoPE).

**Original training config** ([smollm3-configs/stage1_8T.yaml](https://huggingface.co/datasets/HuggingFaceTB/smollm3-configs/blob/main/stage1_8T.yaml)):

```yaml
no_rope_layer: 4
```

This integer means "skip RoPE every 4th layer". During conversion to HuggingFace format, this becomes a list where `1` marks "this is a no-rope layer":

**The HF config** ([`configuration_smollm3.py`](https://github.com/huggingface/transformers/blob/main/src/transformers/models/smollm3/configuration_smollm3.py)):

```python
no_rope_layers = [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, ...]
#                 0  1  2  3  4  5  6  7  8  9  10 11
```

The naming is semantically correct: `1` at index 3, 7, 11... marks layers that should have **no RoPE** ✓

**The bug** ([`modeling_smollm3.py`](https://github.com/huggingface/transformers/blob/main/src/transformers/models/smollm3/modeling_smollm3.py)):

```python
self.use_rope = config.no_rope_layers[layer_idx]
```

This interprets the value directly, but `no_rope_layers[i] = 1` means "layer i has NO rope", so `use_rope` should be the **negation**.

With this logic:
- Layer 3: `no_rope_layers[3] = 1` → `use_rope = True` → RoPE applied ❌
- Layer 0: `no_rope_layers[0] = 0` → `use_rope = False` → No RoPE ❌

**Result:** 75% of layers have no positional encoding (layers 0,1,2, 4,5,6, 8,9,10...) and only 25% have RoPE.

| Source | Description | NoPE % |
|--------|-------------|--------|
| Blog | "removes positional encoding every 4th layer" | 25% NoPE |
| Config | `no_rope_layers = [0,0,0,1,...]` | 25% intended NoPE |
| Code | `use_rope = no_rope_layers[i]` (inverted!) | **75% actual NoPE** |

### Expected behavior

The code should apply RoPE to 75% of layers and skip RoPE for 25% of layers (every 4th layer), matching the blog description.

**Suggested fix:**

```python
self.use_rope = not config.no_rope_layers[layer_idx]
```

Or alternatively, rename the config field to clarify the semantics (e.g., `rope_layers` instead of `no_rope_layers`).



Comments:
hi, the model was trained with 25% NoPE. in [configuration_smollm3.py](https://github.com/huggingface/transformers/blob/main/src/transformers/models/smollm3/configuration_smollm3.py) we are describing how `no_rope_layers` works: 
```
        no_rope_layers (`List[int]`, *optional*):
            List with at least the same length as the number of layers in the model.
            A `1` at an index position indicates that the corresponding layer will use RoPE,
            while a `0` indicates that it's a NoPE layer.
```
so `self.use_rope = config.no_rope_layers[layer_idx]` is the correct way to implement this.

I agree that the naming of `no_rope_layers`is ambiguous but i'd prefer to leave it as is to not break the `config.json` of all the smollm3 variant.

I recommend you to update the blog post then, because "removes positional encoding every 4th layer" is clearly misleading. 
And could you please clearify whether this was intended or a bug in the config? Did you intentionally train the model like that or was this a accident? Because the choice is hard for me to understand given the ablation eval data you have shared. Thanks!

But appraciate ovearll that you put together the smollm3 recipe, really helpful stuff in there! Just came across this subtle difference btween blog and code i wanted to point out here. Best regards!

Thanks for the kind words on Smollm! So there is no bug, 25% NoPE is "removing PE every 4 layers". If you check the 'config.json' of smollm3 you will see 'no_rope_layers = [ 1,1,1,0,...]'

ok thx you are right!