Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8922349
granite-switch: add llama.cpp backend (POC, CPU)
barvhaim Jun 18, 2026
7633e95
granite-switch: add Mac (Metal) build + mid-sequence switch demo script
barvhaim Jun 18, 2026
80a470c
granite-switch mac demo: add -no-cnv so each run is one-shot
barvhaim Jun 18, 2026
3096444
granite-switch: replace global sticky index with in-graph router atte…
barvhaim Jun 22, 2026
b4eb404
granite-switch: drop scratch tests and mac demo for upstream PR
barvhaim Jun 24, 2026
5b4fd0e
granite-switch: trim comments to match native llama.cpp style
barvhaim Jun 24, 2026
f51c185
granite-switch: trim conversion comments to match native style
barvhaim Jun 24, 2026
8da8df5
granite-switch: drop unused adapter_ranks metadata
barvhaim Jun 25, 2026
205e1fa
granite-switch: rename arch to graniteswitch and drop obid alias
barvhaim Jun 25, 2026
723db80
granite-switch: fix non-ASCII comments and document router gain assum…
barvhaim Jun 25, 2026
a8ee797
granite-switch: drop section comments from constants.py to match nati…
barvhaim Jun 28, 2026
4e9ac66
granite-switch: add functional tensor block comments matching Granite…
barvhaim Jun 28, 2026
42346fd
granite-switch: clarify n_expert_used comment
barvhaim Jun 28, 2026
c18749c
granite-switch: note n_layer_nextn reuse has no MTP
barvhaim Jun 28, 2026
3ad6033
granite-switch: rename source file and apply review nits
barvhaim Jun 30, 2026
3fdc1e4
granite-switch: don't force LoRA tensors to F16, follow --outtype ins…
barvhaim Jun 30, 2026
8ded284
granite-switch: drop redundant _permute_qk wrapper, call LlamaModel.p…
barvhaim Jun 30, 2026
846e6b8
granite-switch: read router gain from GGUF (control_token_gain) inste…
barvhaim Jun 30, 2026
e5b1539
granite-switch: derive n_slots()
barvhaim Jun 30, 2026
37a3f87
granite-switch: move llm_graph_input_switch into granite-switch.cpp
barvhaim Jun 30, 2026
0173b54
granite-switch: cut AI-style narration comments
barvhaim Jun 30, 2026
9b6cb51
granite-switch: collapse multi-line comments
barvhaim Jun 30, 2026
4a54c03
granite-switch: rename control_token_* maps to adapter_token_*
barvhaim Jun 30, 2026
4675356
granite-switch: cut noise comments
barvhaim Jun 30, 2026
ea39782
granite-switch: rename embedded LoRA tensors to <base>.lora_a/lora_b
barvhaim Jun 30, 2026
600ef1b
granite-switch: GGML_ASSERT token input to avoid UB on embeddings
barvhaim Jul 1, 2026
8379826
granite-switch: TODO for raw embedding input support
barvhaim Jul 1, 2026
010f22d
granite-switch: collapse LoRA tensor constants to .lora_a/.lora_b suffix
barvhaim Jul 1, 2026
48fc26f
granite-switch: drop n_expert_used hack, guard mul_mat_id buft probe
barvhaim Jul 2, 2026
1870e92
granite-switch: stop forcing dense expert counts, read from config
barvhaim Jul 2, 2026
c9a2850
granite-switch: renamed control_token_gain metadata key to router_gain
barvhaim Jul 2, 2026
dbfd779
granite-switch: trim header comments to match native style
barvhaim Jul 2, 2026
17ad165
granite-switch: collapse LoRA tensors to base name + suffix
barvhaim Jul 2, 2026
9fc6854
granite-switch: inline suffix checks in tensor op resolution
barvhaim Jul 7, 2026
fc5e001
granite-switch: drop switch-lora struct comment
barvhaim Jul 7, 2026
0cd4391
granite-switch: guard router layer index and inline n_slots
barvhaim Jul 8, 2026
f769da1
granite-switch: group adapter metadata under {arch}.adapters.* namespace
barvhaim Jul 8, 2026
aac9a24
granite-switch: add hparams.has_rope(il) for KV-shift rope skipping
barvhaim Jul 8, 2026
1a0acb5
granite-switch: skip arch in test-llama-archs (adapter fixture missin…
barvhaim Jul 12, 2026
f57f905
granite-switch: Keys.Adapters namespace + simplify n_slots
barvhaim Jul 12, 2026
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 conversion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"GraniteMoeForCausalLM": "granite",
"GraniteMoeHybridForCausalLM": "granite",
"GraniteMoeSharedForCausalLM": "granite",
"GraniteSwitchForCausalLM": "granite",
"GraniteSpeechForConditionalGeneration": "granite",
"GraniteSpeechPlusForConditionalGeneration": "granite",
"Grok1ForCausalLM": "grok",
Expand Down
160 changes: 160 additions & 0 deletions conversion/granite.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,166 @@ def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iter
yield from super().modify_tensors(data_torch, name, bid)


@ModelBase.register("GraniteSwitchForCausalLM")
class GraniteSwitchModel(GraniteMoeModel):
"""Dense, all-attention Granite with N per-token embedded LoRA adapters, stacked
over the adapter dim with a zero adapter at slot 0 (N = num_adapters + 1)."""
model_arch = gguf.MODEL_ARCH.GRANITE_SWITCH

# permute q/k per-slice below (NORM-rope layout), not via the parent's auto-permute
undo_permute = False

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# the weightless switch reserves one cache slot: one fewer block than num_hidden_layers
self.block_count = self.block_count - 1
self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self.block_count)

self._n_adapters = int(self.hparams["num_adapters"])
self._max_lora_rank = int(self.hparams["max_lora_rank"])
self._n_slots = self._n_adapters + 1 # +1 for the zero slot at index 0

n_head = int(self.hparams["num_attention_heads"])
n_kv_head = int(self.hparams["num_key_value_heads"])
head_dim = (
self.hparams.get("projection_head_dim")
or self.hparams.get("head_dim")
or (self.hparams["hidden_size"] // n_head)
)
self._n_head = n_head
self._n_kv_head = n_kv_head
self._head_dim = int(head_dim)
self._q_size = n_head * self._head_dim
self._kv_size = n_kv_head * self._head_dim

def set_gguf_parameters(self):
super().set_gguf_parameters()

# dense: pin expert_used_count to 0 (config carries a leftover num_experts_per_tok)
if not self.hparams.get("num_local_experts"):
self.gguf_writer.add_expert_used_count(0)

self.gguf_writer.add_adapter_count(self._n_adapters)
self.gguf_writer.add_adapter_lora_rank(self._max_lora_rank)
self.gguf_writer.add_adapter_token_ids_activate(self.hparams["adapter_token_ids"])
self.gguf_writer.add_adapter_token_ids_substitute(self.hparams["adapter_substitute_token_ids"])
router_gain = float(self.hparams.get("control_token_gain", 15.0))
self.gguf_writer.add_adapter_router_gain(router_gain)
logger.info("gguf: (graniteswitch) num_adapters=%s max_lora_rank=%s n_slots=%s router_gain=%s", self._n_adapters, self._max_lora_rank, self._n_slots, router_gain)

def _lora_a(self, data: Tensor) -> Tensor:
# on-disk A: [n_adapters, 1, max_rank, in] -> [n_adapters+1, max_rank, in]
a = data.squeeze(1)
zero = torch.zeros_like(a[:1])
return torch.cat([zero, a], dim=0).contiguous()

def _lora_b(self, data: Tensor, permute_n_head: int | None = None) -> Tensor:
# on-disk B: [n_adapters, 1, out, max_rank] -> [n_adapters+1, out, max_rank]
b = data.squeeze(1)
if permute_n_head is not None:
# permute each adapter's B output rows to match the permuted q/k base
b = torch.stack([self.permute(b[i], permute_n_head, permute_n_head) for i in range(b.shape[0])], dim=0)
zero = torch.zeros_like(b[:1])
return torch.cat([zero, b], dim=0).contiguous()

def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
T = gguf.MODEL_TENSOR

# skip the weightless switch + control-token buffers (rebuilt at load time)
bare = name.split(".")[-1]
if (
name.startswith("model.switch.") or name.startswith("switch.")
or bare in ("adapter_token_ids", "control_to_substitute_lut")
):
return

if "self_attn.qkv_proj" in name:
if name.endswith("base_layer.weight"):
# fused [q|k|v] rows: permute q/k row-blocks for ggml's NORM-rope layout
q, k, v = data_torch.split([self._q_size, self._kv_size, self._kv_size], dim=0)
q = self.permute(q, self._n_head, self._n_head)
k = self.permute(k, self._n_kv_head, self._n_kv_head)
fused = torch.cat([q, k, v], dim=0)
yield (self.format_tensor_name(T.ATTN_QKV, bid), fused)
return
if "lora_A_slices." in name:
slot = int(name.rsplit(".", 1)[1])
key = {0: T.ATTN_Q, 1: T.ATTN_K, 2: T.ATTN_V}[slot]
yield (self.format_tensor_name(key, bid, suffix=".lora_a"), self._lora_a(data_torch))
return
if "lora_B_slices." in name:
slot = int(name.rsplit(".", 1)[1])
key, ph = {
0: (T.ATTN_Q, self._n_head),
1: (T.ATTN_K, self._n_kv_head),
2: (T.ATTN_V, None),
}[slot]
yield (self.format_tensor_name(key, bid, suffix=".lora_b"), self._lora_b(data_torch, ph))
return
raise ValueError(f"Unexpected qkv_proj tensor: {name}")

if "self_attn.o_proj" in name:
if name.endswith("base_layer.weight"):
yield (self.format_tensor_name(T.ATTN_OUT, bid), data_torch)
return
if name.endswith("lora_A"):
yield (self.format_tensor_name(T.ATTN_OUT, bid, suffix=".lora_a"), self._lora_a(data_torch))
return
if name.endswith("lora_B"):
yield (self.format_tensor_name(T.ATTN_OUT, bid, suffix=".lora_b"), self._lora_b(data_torch))
return
raise ValueError(f"Unexpected o_proj tensor: {name}")

if "shared_mlp.input_linear" in name:
ffn = self.hparams["shared_intermediate_size"]
if name.endswith("base_layer.weight"):
gate, up = data_torch.split([ffn, ffn], dim=0)
yield (self.format_tensor_name(T.FFN_GATE, bid), gate)
yield (self.format_tensor_name(T.FFN_UP, bid), up)
return
if "lora_A_slices." in name:
slot = int(name.rsplit(".", 1)[1])
key = {0: T.FFN_GATE, 1: T.FFN_UP}[slot]
yield (self.format_tensor_name(key, bid, suffix=".lora_a"), self._lora_a(data_torch))
return
if "lora_B_slices." in name:
slot = int(name.rsplit(".", 1)[1])
key = {0: T.FFN_GATE, 1: T.FFN_UP}[slot]
yield (self.format_tensor_name(key, bid, suffix=".lora_b"), self._lora_b(data_torch))
return
raise ValueError(f"Unexpected shared_mlp.input_linear tensor: {name}")

if "shared_mlp.output_linear" in name:
if name.endswith("base_layer.weight"):
yield (self.format_tensor_name(T.FFN_DOWN, bid), data_torch)
return
if name.endswith("lora_A"):
yield (self.format_tensor_name(T.FFN_DOWN, bid, suffix=".lora_a"), self._lora_a(data_torch))
return
if name.endswith("lora_B"):
yield (self.format_tensor_name(T.FFN_DOWN, bid, suffix=".lora_b"), self._lora_b(data_torch))
return
raise ValueError(f"Unexpected shared_mlp.output_linear tensor: {name}")

if bid is not None and ".layers." in name and (
"input_layernorm" in name or "post_attention_layernorm" in name
):
key = T.ATTN_NORM if "input_layernorm" in name else T.FFN_NORM
yield (self.format_tensor_name(key, bid), data_torch)
return

if name in ("model.embed_tokens.weight", "embed_tokens.weight"):
yield (self.format_tensor_name(T.TOKEN_EMBD), data_torch)
return
if name in ("model.norm.weight", "norm.weight"):
yield (self.format_tensor_name(T.OUTPUT_NORM), data_torch)
return
if name == "lm_head.weight":
return # tied to token_embd

raise ValueError(f"graniteswitch: unhandled tensor {name!r} (bid={bid})")


@ModelBase.register("GraniteMoeHybridForCausalLM", "BambaForCausalLM")
class GraniteHybridModel(Mamba2Model, GraniteMoeModel):
"""GraniteHybrid is a hybrid SSM + Attention model that uses Mamba2 SSM
Expand Down
24 changes: 24 additions & 0 deletions gguf-py/gguf/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ class LLM:
BLOCK_SIZE = "{arch}.block_size"
NORM_BEFORE_RESIDUAL = "{arch}.norm_before_residual"

class Adapters:
COUNT = "{arch}.adapters.count"
TOKEN_IDS_ACTIVATE = "{arch}.adapters.token_ids_activate"
TOKEN_IDS_SUBSTITUTE = "{arch}.adapters.token_ids_substitute"
LORA_RANK = "{arch}.adapters.lora_rank"
ROUTER_GAIN = "{arch}.adapters.router_gain"

class Attention:
HEAD_COUNT = "{arch}.attention.head_count"
HEAD_COUNT_KV = "{arch}.attention.head_count_kv"
Expand Down Expand Up @@ -499,6 +506,7 @@ class MODEL_ARCH(IntEnum):
GRANITE = auto()
GRANITE_MOE = auto()
GRANITE_HYBRID = auto()
GRANITE_SWITCH = auto()
CHAMELEON = auto()
WAVTOKENIZER_DEC = auto()
PLM = auto()
Expand Down Expand Up @@ -1079,6 +1087,7 @@ class MODEL_TENSOR(IntEnum):
MODEL_ARCH.GRANITE: "granite",
MODEL_ARCH.GRANITE_MOE: "granitemoe",
MODEL_ARCH.GRANITE_HYBRID: "granitehybrid",
MODEL_ARCH.GRANITE_SWITCH: "graniteswitch",
MODEL_ARCH.CHAMELEON: "chameleon",
MODEL_ARCH.WAVTOKENIZER_DEC: "wavtokenizer-dec",
MODEL_ARCH.PLM: "plm",
Expand Down Expand Up @@ -3669,6 +3678,21 @@ class MODEL_TENSOR(IntEnum):
MODEL_TENSOR.FFN_DOWN,
MODEL_TENSOR.FFN_UP,
],
MODEL_ARCH.GRANITE_SWITCH: [
MODEL_TENSOR.TOKEN_EMBD,
MODEL_TENSOR.OUTPUT_NORM,
MODEL_TENSOR.OUTPUT,
MODEL_TENSOR.ATTN_NORM,
MODEL_TENSOR.ATTN_QKV,
MODEL_TENSOR.ATTN_Q,
MODEL_TENSOR.ATTN_K,
MODEL_TENSOR.ATTN_V,
MODEL_TENSOR.ATTN_OUT,
MODEL_TENSOR.FFN_NORM,
MODEL_TENSOR.FFN_GATE,
MODEL_TENSOR.FFN_DOWN,
MODEL_TENSOR.FFN_UP,
],
MODEL_ARCH.CHAMELEON: [
MODEL_TENSOR.TOKEN_EMBD,
MODEL_TENSOR.OUTPUT_NORM,
Expand Down
15 changes: 15 additions & 0 deletions gguf-py/gguf/gguf_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,21 @@ def add_residual_scale(self, value: float) -> None:
def add_embedding_scale(self, value: float) -> None:
self.add_float32(Keys.LLM.EMBEDDING_SCALE.format(arch=self.arch), value)

def add_adapter_count(self, count: int) -> None:
self.add_uint32(Keys.Adapters.COUNT.format(arch=self.arch), count)

def add_adapter_token_ids_activate(self, ids: Sequence[int]) -> None:
self.add_array(Keys.Adapters.TOKEN_IDS_ACTIVATE.format(arch=self.arch), ids)

def add_adapter_token_ids_substitute(self, ids: Sequence[int]) -> None:
self.add_array(Keys.Adapters.TOKEN_IDS_SUBSTITUTE.format(arch=self.arch), ids)

def add_adapter_lora_rank(self, rank: int) -> None:
self.add_uint32(Keys.Adapters.LORA_RANK.format(arch=self.arch), rank)

def add_adapter_router_gain(self, gain: float) -> None:
self.add_float32(Keys.Adapters.ROUTER_GAIN.format(arch=self.arch), gain)

def add_wkv_head_size(self, size: int) -> None:
self.add_uint32(Keys.WKV.HEAD_SIZE.format(arch=self.arch), size)

Expand Down
6 changes: 6 additions & 0 deletions src/llama-arch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ static const std::map<llm_arch, const char *> LLM_ARCH_NAMES = {
{ LLM_ARCH_GRANITE, "granite" },
{ LLM_ARCH_GRANITE_MOE, "granitemoe" },
{ LLM_ARCH_GRANITE_HYBRID, "granitehybrid" },
{ LLM_ARCH_GRANITE_SWITCH, "graniteswitch" },
{ LLM_ARCH_CHAMELEON, "chameleon" },
{ LLM_ARCH_WAVTOKENIZER_DEC, "wavtokenizer-dec" },
{ LLM_ARCH_PLM, "plm" },
Expand Down Expand Up @@ -215,6 +216,11 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {
{ LLM_KV_TIME_DECAY_EXTRA_DIM, "%s.time_decay_extra_dim" },
{ LLM_KV_RESIDUAL_SCALE, "%s.residual_scale" },
{ LLM_KV_EMBEDDING_SCALE, "%s.embedding_scale" },
{ LLM_KV_ADAPTER_COUNT, "%s.adapters.count" },
{ LLM_KV_ADAPTER_TOKEN_IDS_ACTIVATE, "%s.adapters.token_ids_activate" },
{ LLM_KV_ADAPTER_TOKEN_IDS_SUBSTITUTE, "%s.adapters.token_ids_substitute" },
{ LLM_KV_ADAPTER_LORA_RANK, "%s.adapters.lora_rank" },
{ LLM_KV_ADAPTER_ROUTER_GAIN, "%s.adapters.router_gain" },
{ LLM_KV_TOKEN_SHIFT_COUNT, "%s.token_shift_count" },
{ LLM_KV_INTERLEAVE_MOE_LAYER_STEP, "%s.interleave_moe_layer_step" },
{ LLM_KV_FULL_ATTENTION_INTERVAL, "%s.full_attention_interval" },
Expand Down
6 changes: 6 additions & 0 deletions src/llama-arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ enum llm_arch {
LLM_ARCH_GRANITE,
LLM_ARCH_GRANITE_MOE,
LLM_ARCH_GRANITE_HYBRID,
LLM_ARCH_GRANITE_SWITCH,
LLM_ARCH_CHAMELEON,
LLM_ARCH_WAVTOKENIZER_DEC,
LLM_ARCH_PLM,
Expand Down Expand Up @@ -220,6 +221,11 @@ enum llm_kv {
LLM_KV_TIME_DECAY_EXTRA_DIM,
LLM_KV_RESIDUAL_SCALE,
LLM_KV_EMBEDDING_SCALE,
LLM_KV_ADAPTER_COUNT,
LLM_KV_ADAPTER_TOKEN_IDS_ACTIVATE,
LLM_KV_ADAPTER_TOKEN_IDS_SUBSTITUTE,
LLM_KV_ADAPTER_LORA_RANK,
LLM_KV_ADAPTER_ROUTER_GAIN,
LLM_KV_TOKEN_SHIFT_COUNT,
LLM_KV_INTERLEAVE_MOE_LAYER_STEP,
LLM_KV_FULL_ATTENTION_INTERVAL,
Expand Down
10 changes: 10 additions & 0 deletions src/llama-hparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,16 @@ bool llama_hparams::has_kv(uint32_t il) const {
return true;
}

bool llama_hparams::has_rope(uint32_t il) const {
// the router layer stores adapter routing signal, not positional info,
// so it must not be RoPE-shifted
if (router_layer >= 0 && (int32_t) il == router_layer) {
return false;
}

return true;
}

uint32_t llama_hparams::n_layer() const {
return n_layer_all - n_layer_nextn;
}
Expand Down
6 changes: 6 additions & 0 deletions src/llama-hparams.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ struct llama_hparams {
uint32_t n_embd;
uint32_t n_layer_all;
uint32_t n_layer_nextn = 0;

// granite-switch: index of the single-head "router" KV layer that encodes
// per-token adapter selection. -1 when the model has no such layer.
int32_t router_layer = -1;
uint32_t n_expert = 0;
uint32_t n_expert_used = 0;
uint32_t n_rel_attn_bkts = 0;
Expand Down Expand Up @@ -361,6 +365,8 @@ struct llama_hparams {

bool has_kv(uint32_t il) const;

bool has_rope(uint32_t il) const;

// number of effective layers (excludes nextn layers)
uint32_t n_layer() const;

Expand Down
4 changes: 4 additions & 0 deletions src/llama-kv-cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1925,6 +1925,10 @@ ggml_cgraph * llama_kv_cache::build_graph_shift(llm_graph_result * res, llama_co
for (const auto & layer : layers) {
const uint32_t il = layer.il;

if (!hparams.has_rope(il)) {
continue;
}

const int64_t n_head_kv = hparams.n_head_kv(il);
const int64_t n_embd_k_gqa = hparams.n_embd_k_gqa(il);

Expand Down
24 changes: 12 additions & 12 deletions src/llama-model-loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -932,10 +932,11 @@ static bool weight_buft_supported(const llama_hparams & hparams, ggml_tensor * w
} break;
case GGML_OP_MUL_MAT_ID:
{
const int n_expert_used = hparams.n_expert_used;
GGML_ASSERT(n_expert_used > 0);
ggml_tensor * b = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, w->ne[0], n_expert_used, 512);
ggml_tensor * ids = ggml_new_tensor_2d(ctx, GGML_TYPE_I32, n_expert_used, 512);
// Used for either MoE expert routing or embedded adapter routing
const int n_ids_used = hparams.router_layer >= 0 ? 1 : hparams.n_expert_used;
GGML_ASSERT(n_ids_used > 0);
ggml_tensor * b = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, w->ne[0], n_ids_used, 512);
ggml_tensor * ids = ggml_new_tensor_2d(ctx, GGML_TYPE_I32, n_ids_used, 512);
op_tensor = ggml_mul_mat_id(ctx, w, b, ids);
} break;
case GGML_OP_ADD:
Expand Down Expand Up @@ -1118,15 +1119,14 @@ struct ggml_tensor * llama_model_loader::create_tensor(
return nullptr;
}

// tensors with "bias" suffix are always used with GGML_OP_ADD or GGML_OP_ADD_ID
// tensors with "bias" suffix are always used with GGML_OP_ADD or GGML_OP_ADD_ID;
// tensors with ".lora_a"/".lora_b" suffix are always used with GGML_OP_MUL_MAT_ID
ggml_op op;
bool bias = tn.suffix != nullptr && strcmp(tn.suffix, "bias") == 0;
if (bias) {
if (info.op == GGML_OP_MUL_MAT_ID) {
op = GGML_OP_ADD_ID;
} else {
op = GGML_OP_ADD;
}
if (tn.suffix != nullptr && strcmp(tn.suffix, "bias") == 0) {
op = info.op == GGML_OP_MUL_MAT_ID ? GGML_OP_ADD_ID : GGML_OP_ADD;
} else if (tn.suffix != nullptr &&
(strcmp(tn.suffix, "lora_a") == 0 || strcmp(tn.suffix, "lora_b") == 0)) {
op = GGML_OP_MUL_MAT_ID;
} else {
op = info.op;
}
Expand Down
Loading