Skip to content

Granite-Switch Architecture#25107

Open
barvhaim wants to merge 40 commits into
ggml-org:masterfrom
barvhaim:feature/granite-switch
Open

Granite-Switch Architecture#25107
barvhaim wants to merge 40 commits into
ggml-org:masterfrom
barvhaim:feature/granite-switch

Conversation

@barvhaim

@barvhaim barvhaim commented Jun 28, 2026

Copy link
Copy Markdown

Overview

Adaptation of Granite-Switch model (https://huggingface.co/ibm-granite/granite-switch-4.1-3b-preview) for llama.cpp,
Today Granite-Switch supports HF and vLLM backends.
Granite-Switch OSS project: https://github.com/generative-computing/granite-switch
Converted model with this code: https://huggingface.co/barha/granite-switch-4.1-3b-preview-GGUF/blob/main/granite-switch-4.1-3b-preview-f16.gguf

The model is dense, not MoE. This value is only needed because ggml_mul_mat_id uses n_expert_used as the number of selected matrix IDs per token. For Granite Switch this is always one selected adapter slot per token.

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: Yes, parity with existing backends + documentations, assisted by Claude Code harness

P.S, maintainers, I'm looking forward for your comments on how to progress with getting this supported arch, so pretty much opened that PR for getting feedbacks. thanks!

@barvhaim
barvhaim requested review from CISC and ggerganov as code owners June 28, 2026 16:02
@github-actions github-actions Bot added model Model specific conversion labels Jun 28, 2026
@ggml-gh-bot

ggml-gh-bot Bot commented Jun 28, 2026

Copy link
Copy Markdown

Hi @barvhaim, thanks for your contribution!

Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:

  • AI-generated content: This project does not accept PRs, descriptions or commit messages that are fully or predominantly AI-generated. If you have used AI to assist you in writing code, please make sure to disclose that explicitly.

Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below.

@barvhaim

Copy link
Copy Markdown
Author

Hi @barvhaim, thanks for your contribution!

Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:

  • AI-generated content: This project does not accept PRs, descriptions or commit messages that are fully or predominantly AI-generated. If you have used AI to assist you in writing code, please make sure to disclose that explicitly.

Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below.

I already added before "AI usage disclosure"

@barvhaim
barvhaim marked this pull request as draft June 28, 2026 18:04
@pwilkin
pwilkin marked this pull request as ready for review June 28, 2026 18:14
@pwilkin
pwilkin marked this pull request as draft June 28, 2026 18:14
@barvhaim
barvhaim marked this pull request as ready for review June 28, 2026 18:42
@gabe-l-hart
gabe-l-hart self-requested a review June 29, 2026 15:40

@gabe-l-hart gabe-l-hart left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

A bunch of thoughts that loosely fall into the following buckets:

  1. Should we explore implementing the adapters themselves using the existing adapter mechanism so it's more extensible in the future?
  2. Some restructuring in the conversion code to hopefully simplify a little and extend flexibility

@CISC I'm particularly curious your thoughts on reusing existing adapter mechanisms vs this implementation that reimplements adapters in just the right places for this model so that it doesn't need to change anything about the existing adapter code paths.

Comment thread conversion/granite.py Outdated

@ModelBase.register("GraniteSwitchForCausalLM")
class GraniteSwitchModel(GraniteMoeModel):
"""Dense, all-attention Granite model with N embedded LoRA adapters selected

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I know the current model is built on the Dense 4.1 bases. I believe the intent is to also release switch models for other architectures going forward (including MoE). I haven't made it through the review yet, but I think we'll want to try to make sure the switch functionality is loosely coupled to the core model so it can easily extend to other core model architectures.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

with removing the hack, expert_count now follows num_local_experts from the config
(still need approval on shared code modification)

Comment thread conversion/granite.py Outdated
def set_gguf_parameters(self):
super().set_gguf_parameters()

# the model is dense; force the dense invariant in case the llama parent

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The GraniteMoeModel inherits directly from GraniteModel and only overrides set_gguf_parameters to optionally add the sahred_intermedaite_size (GraniteMoeShared). I don't think these explicit overrides are needed and they make the architecture less flexible to future core model architectures. In fact, I think the best course of action may be to inherit from GraniteHybridModel which itself acts as a child of both GraniteModel and GraniteMoeModel depending on the presence of SSM layers and experts. Some of the other logic may get more complex, but it's worth trying.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Dropped the hardcoded add_expert_count(0), it's config-driven now. Kept one dense guard on expert_used_count: the model's config.json carries num_experts_per_tok: 2 because the upstream HF config (GraniteSwitchConfig extends transformers.GraniteMoeHybridConfig) sets it, so without the guard a dense model emits expert_used_count=2 with expert_count=0 and fails the loader assert.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

On reparenting to GraniteHybridModel: I tried it, seems like it's the wrong base here. Its __init__ auto-downgrades the arch when there are no SSM layers, and since the config has num_experts_per_tok: 2 it rewrites graniteswitch → granitemoe. It'd also pull in Mamba2 machinery (hparam_prefixes, SSM params, mamba set_vocab) that a dense all-attention model doesn't use. GraniteMoeModel stays the cleaner parent, the flexibility comes from the config-driven expert count, not the base class.

Comment thread conversion/granite.py Outdated
Comment thread conversion/granite.py Outdated
Comment thread src/models/granite-switch.cpp
Comment thread src/models/granite-switch.cpp Outdated
Comment thread src/models/granite_switch.cpp Outdated
Comment thread src/models/granite_switch.cpp Outdated
Comment thread src/models/granite-switch.cpp Outdated
auto * inp_attn = build_attn_inp_kv();

// router attention: a single causal head whose K/V live in the KV cache at
// layer R, recovering the per-token adapter index in-graph. Only dim 0 carries

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, I see: you keep the KV cache for this layer at the end, not the beginning, so we don't have to shift everything else.

ggml_tensor * Kcur = ggml_cont(ctx0, ggml_view_2d(ctx0, qkv, n_embd_kv, qkv->ne[1], qkv->nb[1], n_embd_q*ggml_element_size(qkv)));
ggml_tensor * Vcur = ggml_cont(ctx0, ggml_view_2d(ctx0, qkv, n_embd_kv, qkv->ne[1], qkv->nb[1], (n_embd_q + n_embd_kv)*ggml_element_size(qkv)));

Qcur = ggml_add(ctx0, Qcur, build_switched_lora_delta(sl.a_q, sl.b_q, cur, adapter_ids));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ok, I think this is the core of the in-model adapter implementation point. If we explore the route of using the existing llama_lora_adapter infra, this would get deferred into the build_attn below automatically I think.

@CISC

CISC commented Jun 29, 2026

Copy link
Copy Markdown
Member

@CISC I'm particularly curious your thoughts on reusing existing adapter mechanisms vs this implementation that reimplements adapters in just the right places for this model so that it doesn't need to change anything about the existing adapter code paths.

We have avoided this path a couple of times in the past already, but I think it's long overdue to try and tackle this, I think it would be worth refactoring adapters to handle this, unfortunately the timing is bad for me as I am on forced vacation. :)

I'm sure @ngxson has some thoughts on this as well.

@gabe-l-hart

Copy link
Copy Markdown
Collaborator

unfortunately the timing is bad for me as I am on forced vacation. :)

Zero problem (I hope everything is ok!). @ngxson if you're eager, I'd love to hear your thoughts, but I'm also game to take a pass at this if you're swamped. I had started it already, but hadn't gotten much further since @barvhaim was able to get things working end-to-end.

@CISC

CISC commented Jun 29, 2026

Copy link
Copy Markdown
Member

unfortunately the timing is bad for me as I am on forced vacation. :)

Zero problem (I hope everything is ok!).

Oh yes, given the circumstances fantastic in fact. :) Long story short I'm between jobs, all will be revealed by August...

@barvhaim
barvhaim force-pushed the feature/granite-switch branch from 111d333 to 9249531 Compare June 30, 2026 06:53

@ngxson ngxson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Given recent models like deepseekv4 / minimax also have complicated cgraph and kv logic, I think it could be fine to add this one. We can accept it as long as the impl is mostly confined inside granite-switch.cpp

Things that I'm less comfortable about the PR:

  • Excessive AI-generated comments. Most comments are just noise and doesn't add much to my understanding of the code
  • Still a lot of hacks outside of granite-switch.cpp

Comment thread gguf-py/gguf/constants.py Outdated
Comment thread gguf-py/gguf/constants.py Outdated
Comment thread src/models/models.h Outdated
Comment thread src/models/models.h Outdated
Comment thread conversion/granite.py Outdated
Comment thread src/llama-model.h Outdated
Comment thread src/models/granite-switch.cpp
Comment thread src/models/models.h Outdated

@gabe-l-hart gabe-l-hart left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

A few more thoughts/comments, especially around simplifying the added tensor names with suffixes.

Comment thread conversion/granite.py Outdated
def set_gguf_parameters(self):
super().set_gguf_parameters()

# dense model: override any expert counts the llama parent emitted

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm still not sure we want to explicitly override like this. The upstream parents should handle this correctly for dense or MoE target models, so this just unnecessarily limits to dense-only.

Comment thread gguf-py/gguf/constants.py Outdated
Comment thread src/llama-arch.cpp Outdated
Comment thread src/llama-arch.cpp Outdated
Comment thread src/llama-arch.h Outdated
Comment thread src/models/granite-switch.cpp Outdated
Comment thread src/models/granite-switch.cpp Outdated
Comment thread src/models/granite-switch.cpp
Comment thread src/models/granite-switch.cpp
@barvhaim
barvhaim force-pushed the feature/granite-switch branch from 758fbbb to aa441eb Compare July 1, 2026 14:17
@barvhaim

barvhaim commented Jul 2, 2026

Copy link
Copy Markdown
Author

Given recent models like deepseekv4 / minimax also have complicated cgraph and kv logic, I think it could be fine to add this one. We can accept it as long as the impl is mostly confined inside granite-switch.cpp

Things that I'm less comfortable about the PR:

  • Excessive AI-generated comments. Most comments are just noise and doesn't add much to my understanding of the code
  • Still a lot of hacks outside of granite-switch.cpp

+1
Removed the redundant AI generated comments

@gabe-l-hart gabe-l-hart left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One tiny NIT, but otherwise this looks like it's in good shape to me. @ngxson I think it's ready for CI and your next review when you have time.

Comment thread src/llama-model-loader.cpp Outdated
Comment thread src/llama-model.h
// slots in dim 2 (slot 0 is zero-filled so base tokens add an exact-zero delta).
// A: {n_embd_in, max_lora_rank, N} B: {max_lora_rank, n_embd_out, N}
// 7 injection sites (q, k, v, o, gate, up, down), each with an A and a B = 14 tensors.
struct llama_layer_switch_lora {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Given @ngxson 's input, I think we're going to avoid the "overhaul how adapters work" piece for now.

@barvhaim
barvhaim force-pushed the feature/granite-switch branch from bae32b6 to 0a7e411 Compare July 7, 2026 07:08
@barvhaim
barvhaim requested a review from ngxson July 7, 2026 21:34
Comment thread src/llama-kv-cache.cpp Outdated
Comment thread gguf-py/gguf/constants.py Outdated
Comment thread gguf-py/gguf/constants.py Outdated
Comment thread gguf-py/gguf/constants.py Outdated
Comment thread src/llama-arch.cpp Outdated
Comment thread gguf-py/gguf/constants.py Outdated
Comment thread src/models/granite-switch.cpp
Comment thread src/models/granite-switch.cpp Outdated

@ngxson ngxson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

CI need to be fixed too

Comment thread gguf-py/gguf/constants.py Outdated
Comment thread src/models/granite-switch.cpp Outdated
barvhaim added 22 commits July 12, 2026 16:04
@barvhaim
barvhaim force-pushed the feature/granite-switch branch from 1cb71b5 to f57f905 Compare July 12, 2026 13:07
@barvhaim

Copy link
Copy Markdown
Author

CI need to be fixed too

skipped the arch in test-llama-archs and added FIXME for now

@barvhaim
barvhaim requested a review from ngxson July 12, 2026 13:57

@ngxson ngxson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

let's also wait for @CISC review for libllama changes

gabe-l-hart added a commit to gabe-l-hart/llama.cpp that referenced this pull request Jul 14, 2026
NOTE: This shadows the work done for Granite Speech
ggml-org#25107

Branch: GraniteSWAForCausalLM
AI-usage: full (Bob)
Signed-off-by: Gabe Goodhart <ghart@us.ibm.com>
@gabe-l-hart

Copy link
Copy Markdown
Collaborator

@ngxson

ngxson commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Quick note on this, running code-review reveals some correctness issues that must be address. You can get the skill here: #26042

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conversion model Model specific testing Everything test related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants