Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/cache_config/hf_models_and_datasets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ models:
- ibm-granite/granite-3.3-2b-instruct # granite2b
- ibm-granite/granite-4.0-1b-base # granite4
- ibm-granite/granite-4.0-1b # granite4_instruct
- ibm-research/granite-4.1-20b # granite41_20b
# Embedding models
- BAAI/bge-base-en-v1.5 # bge_base (embed-compare)
- BAAI/bge-m3 # bge_m3 (load)
39 changes: 20 additions & 19 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ which models are supported on Spyre.
| Granite 3.3 2B | granite | 64→128 | 64 | Yes (padded) | Yes | Yes | Yes |
| Granite 4.0 1B Base | granitemoehybrid | 128 | 64 | Yes | Yes | Yes | Yes |
| Granite 4.0 1B Instruct | granitemoehybrid | 128 | 64 | Yes | Yes | Yes | Yes |
| Granite 4.1 20B (bf16) | granite_swa | 128 | 64 | Yes | Yes | Yes | Yes |
| SmolLM3 3B | smollm3 | 128 | 64 | Yes | Yes | Yes | Yes |
| Llama 3.2 3B | llama | 128 | 64 | Yes | Yes | Yes | Yes |
| TinyLlama 1.1B | llama | 64→128 | 64 | Yes (padded) | Yes | Yes | Yes |
Expand All @@ -29,8 +30,6 @@ which models are supported on Spyre.
| Yi 1.5 6B | llama | 128 | 64 | Yes | Yes | Yes | Yes |
| Granite Vision 4.1 4B (text backbone) | granite (text) | 64→128 | 64 | Yes (padded) | Yes | Yes | Yes |
| Gemma 4 12B | gemma4\_unified | 256 / 512 | 128 / 256 | Yes | Yes | Yes | Yes |
| Gemma 4 12B Base | gemma4\_unified | 256 / 512 | 128 / 256 | Yes | Yes | Yes | Yes |
| Gemma 4 31B | gemma4 | 256 / 512 | 128 / 256 | Yes | Yes | Yes | Yes |
| Gemma 3 1B | gemma3\_text | 256 | 128 | Yes | Yes | Yes | Yes |
| GPT-2 124M | gpt2 | 64 | n/a (no RoPE) | Yes | Yes | Yes | Yes |
| GPT-Neo 125M | gpt_neo | 64 | n/a (no RoPE) | Yes | Yes | Yes | Yes |
Expand Down Expand Up @@ -112,8 +111,8 @@ single-token decode path (seq_len=1), not an adapter issue.
> adapter or verify a checkpoint, update *only* this file (and the badge
> counts in README.md, noted below).

**Coverage:** 27 adapters · 46 verified checkpoints · 100+ compatible models.
The 46 verified rows are 29 generative + 13 embedding + 4 vision-language (see the
**Coverage:** 28 adapters · 45 verified checkpoints · 100+ compatible models.
The 45 verified rows are 28 generative + 13 embedding + 4 vision-language (see the
Verified Checkpoints tables above). `hf_siglip_vision` and `hf_pixtral_vision` are
vision-tower components used by VLM adapters rather than standalone model adapters.
Granite Vision 4.1 is verified both as a text backbone (generative) and as a full VLM.
Expand All @@ -140,8 +139,9 @@ pattern, norms, and weight layout.
| hf\_ministral.py | ministral | 1 | Ministral-8B Instruct fine-tunes |
| hf\_phi3.py | phi3 | 1 | Phi-3 mini 4k/128k, Phi-3 small 8k |
| hf\_granitemoehybrid.py | granitemoehybrid | 2 | Granite 4.0 Micro |
| hf\_granite\_swa.py | granite\_swa | 1 | Granite 4.1 8B (unverified), Granite 4.1 20B |
| hf\_smollm3.py | smollm3 | 1 | — |
| hf\_gemma4.py | gemma4\_unified / gemma4 (dense) | 3 | Not E2B/E4B (PLE) or 26B-A4B (MoE). |
| hf\_gemma4.py | gemma4\_unified / gemma4 (dense) | 1 | Gemma 4 31B (dense). Not E2B/E4B (PLE) or 26B-A4B (MoE). |
| hf\_gemma4\_mm.py | gemma4\_unified (multimodal) | 1 | Gemma 4 31B (dense unified VLM). Not E2B/E4B (PLE) or 26B-A4B (MoE). |
| hf\_gemma3.py | gemma3\_text / gemma3 (dense) | 2 | Gemma 3 4B/12B/27B (text decoder of the multimodal checkpoints); EmbeddingGemma (bidirectional embedder). Not Gemma 3n (PLE). |
| hf\_olmo.py | olmo | 1 | OLMo 7B |
Expand Down Expand Up @@ -392,20 +392,21 @@ modification:

### Model-Specific Adaptations

| Feature | Granite 3.3 | Granite Vision 4.1 | Qwen3 | Granite 4.0 | SmolLM3 | Llama | Qwen2 | Mistral | Phi-4 mini | OLMo | OLMo2 | Gemma 3 | Gemma 4 |
|---------|------------|-------------------|-------|-------------|---------|-------|-------|---------|-----------|------|-------|---------|---------|
| Embedding multiplier | Yes | Yes | No | Yes | No | No | No | No | No | No | No | Yes | Yes |
| Residual multiplier | Yes | Yes | No | Yes | No | No | No | No | No | No | No | No | No |
| Logits scaling | Yes | Yes | No | Yes | No | No | No | No | No | No | No | No | No |
| Q/K RMSNorm | No | No | Yes (per-head) | No | No | No | No | No | No | No | Yes (flattened) | Yes (per-head Q/K) | Yes (per-head Q/K/V) |
| Fused QKV split | No | No | No | No | No | No | No | No | Yes | No | No | No | No |
| Fused MLP split | No | No | No | Yes | No | No | No | No | Yes | No | No | No | No |
| NoPE layers | No | No | No | No | Yes | No | No | No | No | No | No | No | No |
| Partial RoPE | No | No | No | No | No | No | No | No | Yes | No | No | No | Yes (global layers) |
| Head-dim padding | 2B only | Yes (64→128) | No | Micro only (64→128) | No | TinyLlama | No | No | No | No | No | No | No |
| Custom model loading | No | Yes (safetensor remap) | No | No | No | No | No | No | No | No | No | No | No |
| Attention scaling | `config.attention_multiplier` | `config.attention_multiplier` | `head_dim**-0.5` | `config.attention_multiplier` | `head_dim**-0.5` | `head_dim**-0.5` | `head_dim**-0.5` | `head_dim**-0.5` | `head_dim**-0.5` | `head_dim**-0.5` | `head_dim**-0.5` | `query_pre_attn_scalar**-0.5` | `1.0` (unscaled) |
| Norm type | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | LayerNorm (pre, no weight) | RMSNorm (post) | RMSNorm (sandwich) | RMSNorm (sandwich) |
| Feature | Granite 3.3 | Granite Vision 4.1 | Qwen3 | Granite 4.0 | Granite 4.1 SWA | SmolLM3 | Llama | Qwen2 | Mistral | Phi-4 mini | OLMo | OLMo2 | Gemma 3 | Gemma 4 |
|---------|------------|-------------------|-------|-------------|-----------------|---------|-------|-------|---------|-----------|------|-------|---------|---------|
| Embedding multiplier | Yes | Yes | No | Yes | Yes | No | No | No | No | No | No | No | Yes | Yes |
| Residual multiplier | Yes | Yes | No | Yes | Yes | No | No | No | No | No | No | No | No | No |
| Logits scaling | Yes | Yes | No | Yes | Yes | No | No | No | No | No | No | No | No | No |
| Q/K RMSNorm | No | No | Yes (per-head) | No | No | No | No | No | No | No | No | Yes (flattened) | Yes (per-head Q/K) | Yes (per-head Q/K/V) |
| Fused QKV split | No | No | No | No | No | No | No | No | No | Yes | No | No | No | No |
| Fused MLP split | No | No | No | Yes | No | No | No | No | No | Yes | No | No | No | No |
| NoPE layers | No | No | No | No | No | Yes | No | No | No | No | No | No | No | No |
| Partial RoPE | No | No | No | No | No | No | No | No | No | Yes | No | No | No | Yes (global layers) |
| Sliding-window layers | No | No | No | No | Yes (alternating) | No | No | No | No | No | No | No | No | No |
| Head-dim padding | 2B only | Yes (64→128) | No | Micro only (64→128) | No | No | TinyLlama | No | No | No | No | No | No | No |
| Custom model loading | No | Yes (safetensor remap) | No | No | No | No | No | No | No | No | No | No | No | No |
| Attention scaling | `config.attention_multiplier` | `config.attention_multiplier` | `head_dim**-0.5` | `config.attention_multiplier` | `config.attention_multiplier` | `head_dim**-0.5` | `head_dim**-0.5` | `head_dim**-0.5` | `head_dim**-0.5` | `head_dim**-0.5` | `head_dim**-0.5` | `head_dim**-0.5` | `query_pre_attn_scalar**-0.5` | `1.0` (unscaled) |
| Norm type | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | RMSNorm (pre) | LayerNorm (pre, no weight) | RMSNorm (post) | RMSNorm (sandwich) | RMSNorm (sandwich) |

**Partial RoPE** (Phi-4): `PartialPrecomputedRotaryEmbedding` pads
the rotation matrix with identity `[[1,0],[0,1]]` entries so
Expand Down
34 changes: 28 additions & 6 deletions hf_adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
| 6 | hf\_mistral.py | `mistral` | 1 | 4+ |
| 7 | hf\_phi3.py | `phi3` | 1 | 3+ |
| 8 | hf\_granitemoehybrid.py | `granitemoehybrid` | 2 | 1+ |
| 9 | hf\_smollm3.py | `smollm3` | 1 | — |
| 10 | hf\_olmo.py | `olmo` | 1 | 1+ |
| 11 | hf\_olmo2.py | `olmo2` | 1 | 1+ |
| 9 | hf\_granite\_swa.py | `granite_swa` | 1 | 1+ |
| 10 | hf\_smollm3.py | `smollm3` | 1 | — |
| 11 | hf\_olmo.py | `olmo` | 1 | 1+ |
| 12 | hf\_olmo2.py | `olmo2` | 1 | 1+ |

---

Expand Down Expand Up @@ -173,7 +174,28 @@ Vision-specific adapter; extracts and adapts the text backbone from Granite Visi

---

## 9. hf\_smollm3.py
## 9. hf\_granite\_swa.py

**HF model\_type:** `granite_swa`

Spyre adapter for `GraniteSWAForCausalLM`. Decoder layers alternate between full
attention and sliding-window attention (`layer_type` on each `GraniteSWADecoderLayer`).
Sliding-window layers restrict attention to a local `sliding_window`-wide band;
full-attention layers use the standard causal mask.

### Verified Checkpoints

| Checkpoint | head\_dim | Notes |
|-----------|---------|-------|
| Granite 4.1 20B (`ibm-research/granite-4.1-20b`) | 128 | ibm-research org; bfloat16 |

### Compatible Models

- Granite 4.1 8B / 20B variants

---

## 10. hf\_smollm3.py

**HF model\_type:** `smollm3`

Expand All @@ -189,7 +211,7 @@ No additional compatible models listed.

---

## 10. hf\_olmo.py
## 11. hf\_olmo.py

**HF model\_type:** `olmo`

Expand All @@ -205,7 +227,7 @@ No additional compatible models listed.

---

## 11. hf\_olmo2.py
## 12. hf\_olmo2.py

**HF model\_type:** `olmo2`

Expand Down
4 changes: 4 additions & 0 deletions hf_adapters/auto_spyre_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
Granite4VisionConfig,
GraniteConfig,
GraniteMoeHybridConfig,
GraniteSWAConfig,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import currently fails, so to merge this change we would need to wait for a transformers release that includes this config

LlamaConfig,
MistralConfig,
ModernBertConfig,
Expand Down Expand Up @@ -88,6 +89,7 @@
hf_gpt_neo,
hf_gpt_neox,
hf_granite,
hf_granite_swa,
hf_granite_vision,
hf_granite_vision_mm,
hf_granitemoehybrid,
Expand Down Expand Up @@ -128,6 +130,7 @@
Granite4VisionConfig: hf_granite_vision,
GraniteConfig: hf_granite,
GraniteMoeHybridConfig: hf_granitemoehybrid,
GraniteSWAConfig: hf_granite_swa,
LlamaConfig: hf_llama,
MistralConfig: hf_mistral,
MinistralConfig: hf_ministral,
Expand Down Expand Up @@ -189,6 +192,7 @@
"google/gemma-4-31b": torch.bfloat16,
"ibm-granite/granite-4.0-1b-base": torch.float32,
"ibm-granite/granite-4.0-1b": torch.float32,
"ibm-research/granite-4.1-20b": torch.bfloat16,
}


Expand Down
215 changes: 215 additions & 0 deletions hf_adapters/hf_granite_swa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# Copyright 2025 The Torch-Spyre Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
HuggingFace Transformers adapter for Granite 4.1 SWA models on Spyre.

GraniteSWAForCausalLM is identical to Granite 3.x except that layers alternate
between full attention and sliding-window attention (``layer_type`` attribute on
each ``GraniteSWADecoderLayer``). Sliding-window layers receive a local attention
mask that restricts each token to attend only within the ``sliding_window`` window;
full-attention layers receive the normal causal mask.

Usage::

from hf_adapters import AutoSpyreModelForCausalLM
from transformers import AutoTokenizer

model = AutoSpyreModelForCausalLM.from_pretrained(
"/tmp/models/granite-4.1-20b")
tokenizer = AutoTokenizer.from_pretrained("/tmp/models/granite-4.1-20b")
outputs = model.generate(tokenizer, ["Hello!"], max_new_tokens=32)
"""

import torch
import torch.nn.functional as F

from hf_adapters.hf_common import (
apply_rope_matmul,
get_backbone,
kv_cache_update,
pad_lm_head,
patch_rmsnorm,
prepare_rope_and_heads,
)


def _make_compiled_block(layer, sliding_window: int):
"""Compiled block for GraniteSWA.

Full-attention layers receive the normal causal ``attn_mask``.
Sliding-window layers receive a local mask built from the same buffer but
restricted to a ``sliding_window``-wide band; positions outside the window
are masked to ``-inf``.
"""
attn = layer.self_attn
mlp = layer.mlp
input_ln = layer.input_layernorm
post_attn_ln = layer.post_attention_layernorm
res_mult = layer.residual_multiplier
v_head_dim = getattr(attn, "v_head_dim", attn.head_dim)
is_sliding = (
getattr(layer, "layer_type", "full_attention") == "sliding_attention"
)

def block_forward(
hidden_states,
selected_freqs,
attn_mask,
key_cache,
value_cache,
is_filling,
token_index,
cache_position,
):
residual = hidden_states
h = input_ln(hidden_states)

bsz, seq_len, _ = h.shape
q = attn.q_proj(h).view(bsz, seq_len, -1, attn.head_dim).transpose(1, 2)
k = attn.k_proj(h).view(bsz, seq_len, -1, attn.head_dim).transpose(1, 2)
v = attn.v_proj(h).view(bsz, seq_len, -1, v_head_dim).transpose(1, 2)

q = apply_rope_matmul(q, selected_freqs)
k = apply_rope_matmul(k, selected_freqs)

key_cache, value_cache = kv_cache_update(
k,
v,
key_cache,
value_cache,
is_filling,
token_index,
cache_position,
)

# For sliding-window layers, apply a local band mask on top of the
# causal mask: positions further than sliding_window steps back are
# set to -inf so the model never attends outside the window.
if is_sliding:
cache_len = key_cache.shape[2]
q_len = q.shape[2]
# Anchor query positions to the real token positions so that the
# window is correct during both prefill and single-token decode.
# During prefill token_index=0 and q_len>1; during decode
# token_index is the current position and q_len=1.
q_pos = (token_index + torch.arange(q_len, device=q.device)).unsqueeze(1) # [q, 1]
k_pos = torch.arange(cache_len, device=q.device).unsqueeze(0) # [1, k]
window_mask = (q_pos - k_pos) >= sliding_window # [q, k]
swa_mask = attn_mask.clone()
swa_mask = swa_mask.masked_fill(
window_mask.unsqueeze(0).unsqueeze(0), float("-inf")
)
effective_mask = swa_mask
else:
effective_mask = attn_mask

attn_out = F.scaled_dot_product_attention(
q,
key_cache,
value_cache,
attn_mask=effective_mask,
dropout_p=0.0,
scale=attn.scaling,
enable_gqa=True,
)
attn_out = attn_out.transpose(1, 2).reshape(bsz, seq_len, -1)
attn_out = attn.o_proj(attn_out)

h = residual + attn_out * res_mult

residual = h
h = post_attn_ln(h)
h = mlp(h)
h = residual + h * res_mult

return h, key_cache, value_cache

return torch.compile(block_forward, dynamic=False)


def _run_backbone_forward(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe better to do more reuse of the existing adapter, like in #281

model,
input_ids,
position_ids,
attn_mask,
key_caches,
value_caches,
is_filling,
token_index,
cache_position,
):
"""GraniteSWA backbone: embedding * multiplier, blocks, norm."""
backbone = get_backbone(model)
h = backbone.embed_tokens(input_ids)
h = h * backbone.embedding_multiplier

selected_freqs = model._spyre_rope(h, position_ids)

for i, compiled_block in enumerate(model._spyre_compiled_blocks):
h, key_caches[i], value_caches[i] = compiled_block(
h,
selected_freqs,
attn_mask,
key_caches[i],
value_caches[i],
is_filling,
token_index,
cache_position,
)

h = backbone.norm(h)
return h


def _run_forward(
model,
input_ids,
position_ids,
attn_mask,
key_caches,
value_caches,
is_filling,
token_index,
cache_position,
):
"""GraniteSWA causal-LM forward: backbone + lm_head / logits_scaling."""
h = _run_backbone_forward(
model,
input_ids,
position_ids,
attn_mask,
key_caches,
value_caches,
is_filling,
token_index,
cache_position,
)
logits = model.lm_head(h)
logits = logits / model.config.logits_scaling
return logits


def prepare_for_spyre(model):
"""Apply Spyre adaptations to a GraniteSWA model in-place."""
from transformers.models.granite_swa.modeling_granite_swa import GraniteSWARMSNorm

sliding_window = model.config.sliding_window
prepare_rope_and_heads(model)
patch_rmsnorm(GraniteSWARMSNorm)
pad_lm_head(model)
model._spyre_compiled_blocks = [
_make_compiled_block(layer, sliding_window)
for layer in get_backbone(model).layers
]
Loading
Loading