Skip to content

Commit f4f5019

Browse files
authored
model: add Solar Open model (ggml-org#18511)
* model: add Solar-Open model * vocab: add solar-open to end eog blacklist * model: add proper llm type * chat: basic template for solar open * typo: fix comment about vocab * convert: sugested changes * convert: suggested changes * chat: change reasoning end tag for solar-open * llama-chat: add solar-open template
1 parent d5574c9 commit f4f5019

11 files changed

Lines changed: 99 additions & 8 deletions

common/chat-parser.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,6 +1395,14 @@ static void common_chat_parse_seed_oss(common_chat_msg_parser & builder) {
13951395
builder.consume_reasoning_with_xml_tool_calls(form, "<seed:think>", "</seed:think>");
13961396
}
13971397

1398+
static void common_chat_parse_solar_open(common_chat_msg_parser & builder) {
1399+
builder.try_parse_reasoning("<|think|>", "<|end|><|begin|>assistant<|content|>");
1400+
1401+
// TODO: Tool calling
1402+
1403+
builder.add_content(builder.consume_rest());
1404+
}
1405+
13981406
static void common_chat_parse_content_only(common_chat_msg_parser & builder) {
13991407
builder.try_parse_reasoning("<think>", "</think>");
14001408
builder.add_content(builder.consume_rest());
@@ -1479,6 +1487,9 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
14791487
case COMMON_CHAT_FORMAT_XIAOMI_MIMO:
14801488
common_chat_parse_xiaomi_mimo(builder);
14811489
break;
1490+
case COMMON_CHAT_FORMAT_SOLAR_OPEN:
1491+
common_chat_parse_solar_open(builder);
1492+
break;
14821493
default:
14831494
throw std::runtime_error(std::string("Unsupported format: ") + common_chat_format_name(builder.syntax().format));
14841495
}

common/chat.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ const char * common_chat_format_name(common_chat_format format) {
669669
case COMMON_CHAT_FORMAT_QWEN3_CODER_XML: return "Qwen3 Coder";
670670
case COMMON_CHAT_FORMAT_APRIEL_1_5: return "Apriel 1.5";
671671
case COMMON_CHAT_FORMAT_XIAOMI_MIMO: return "Xiaomi MiMo";
672+
case COMMON_CHAT_FORMAT_SOLAR_OPEN: return "Solar Open";
672673
case COMMON_CHAT_FORMAT_PEG_SIMPLE: return "peg-simple";
673674
case COMMON_CHAT_FORMAT_PEG_NATIVE: return "peg-native";
674675
case COMMON_CHAT_FORMAT_PEG_CONSTRUCTED: return "peg-constructed";
@@ -2517,6 +2518,27 @@ static common_chat_params common_chat_params_init_granite(const common_chat_temp
25172518
return data;
25182519
}
25192520

2521+
static common_chat_params common_chat_params_init_solar_open(const common_chat_template & tmpl, const struct templates_params & inputs) {
2522+
common_chat_params data;
2523+
2524+
// TODO: Reasoning effort
2525+
json additional_context = {};
2526+
2527+
data.prompt = apply(tmpl, inputs, std::nullopt, std::nullopt, additional_context);
2528+
data.format = COMMON_CHAT_FORMAT_SOLAR_OPEN;
2529+
2530+
data.preserved_tokens = {
2531+
"<|think|>",
2532+
"<|content|>",
2533+
"<|begin|>",
2534+
"<|end|>",
2535+
};
2536+
2537+
// TODO: Tool calling
2538+
2539+
return data;
2540+
}
2541+
25202542
static common_chat_params common_chat_params_init_without_tools(const common_chat_template & tmpl, const struct templates_params & inputs) {
25212543
common_chat_params data;
25222544
data.prompt = apply(tmpl, inputs);
@@ -2780,6 +2802,13 @@ static common_chat_params common_chat_templates_apply_jinja(
27802802
return common_chat_params_init_magistral(tmpl, params);
27812803
}
27822804

2805+
// Solar Open
2806+
if (src.find("<|tool_response:begin|>") != std::string::npos &&
2807+
src.find("<|tool_response:name|>") != std::string::npos &&
2808+
src.find("<|tool_response:result|>") != std::string::npos) {
2809+
return common_chat_params_init_solar_open(tmpl, params);
2810+
}
2811+
27832812
// Plain handler (no tools)
27842813
if (params.tools.is_null() || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
27852814
return common_chat_params_init_without_tools(tmpl, params);

common/chat.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ enum common_chat_format {
124124
COMMON_CHAT_FORMAT_QWEN3_CODER_XML,
125125
COMMON_CHAT_FORMAT_APRIEL_1_5,
126126
COMMON_CHAT_FORMAT_XIAOMI_MIMO,
127+
COMMON_CHAT_FORMAT_SOLAR_OPEN,
127128

128129
// These are intended to be parsed by the PEG parser
129130
COMMON_CHAT_FORMAT_PEG_SIMPLE,

convert_hf_to_gguf.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,9 @@ def get_vocab_base_pre(self, tokenizer) -> str:
12301230
if chkhsh == "4a2e2abae11ca2b86d570fc5b44be4d5eb5e72cc8f22dd136a94b37da83ab665":
12311231
# ref: https://huggingface.co/KORMo-Team/KORMo-tokenizer
12321232
res = "kormo"
1233+
if chkhsh == "16389f0a1f51ee53e562ffd51c371dc508639ab0e4261502071836e50e223e91":
1234+
# ref: https://huggingface.co/upstage/Solar-Open-100B
1235+
res = "solar-open"
12331236

12341237
if res is None:
12351238
logger.warning("\n")
@@ -10617,6 +10620,26 @@ def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iter
1061710620
return []
1061810621

1061910622

10623+
@ModelBase.register("SolarOpenForCausalLM")
10624+
class SolarOpenModel(Glm4MoeModel):
10625+
model_arch = gguf.MODEL_ARCH.GLM4_MOE
10626+
10627+
def set_vocab(self):
10628+
from transformers import AutoTokenizer
10629+
tokenizer = AutoTokenizer.from_pretrained(self.dir_model)
10630+
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
10631+
tokens, toktypes, tokpre = self.get_vocab_base()
10632+
self.gguf_writer.add_tokenizer_model("gpt2")
10633+
self.gguf_writer.add_tokenizer_pre(tokpre)
10634+
self.gguf_writer.add_token_list(tokens)
10635+
self.gguf_writer.add_token_types(toktypes)
10636+
special_vocab._set_special_token("eos", tokenizer.get_added_vocab()["<|endoftext|>"])
10637+
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|endoftext|>"])
10638+
special_vocab._set_special_token("unk", tokenizer.get_added_vocab()["<unk>"])
10639+
special_vocab._set_special_token("bos", tokenizer.get_added_vocab()["<|startoftext|>"])
10640+
special_vocab.add_to_gguf(self.gguf_writer)
10641+
10642+
1062010643
###### CONVERSION LOGIC ######
1062110644

1062210645

convert_hf_to_gguf_update.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ class TOKENIZER_TYPE(IntEnum):
145145
{"name": "granite-docling", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/ibm-granite/granite-docling-258M", },
146146
{"name": "minimax-m2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/MiniMaxAI/MiniMax-M2", },
147147
{"name": "kormo", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/KORMo-Team/KORMo-tokenizer", },
148+
{"name": "solar-open", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/upstage/Solar-Open-100B", },
148149
]
149150

150151
# some models are known to be broken upstream, so we will skip them as exceptions

src/llama-chat.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ static const std::map<std::string, llm_chat_template> LLM_CHAT_TEMPLATES = {
7474
{ "seed_oss", LLM_CHAT_TEMPLATE_SEED_OSS },
7575
{ "grok-2", LLM_CHAT_TEMPLATE_GROK_2 },
7676
{ "pangu-embedded", LLM_CHAT_TEMPLATE_PANGU_EMBED },
77+
{ "solar-open", LLM_CHAT_TEMPLATE_SOLAR_OPEN },
7778
};
7879

7980
llm_chat_template llm_chat_template_from_str(const std::string & name) {
@@ -216,6 +217,8 @@ llm_chat_template llm_chat_detect_template(const std::string & tmpl) {
216217
return LLM_CHAT_TEMPLATE_GROK_2;
217218
} else if (tmpl_contains(LU8("[unused9]系统:[unused10]"))) {
218219
return LLM_CHAT_TEMPLATE_PANGU_EMBED;
220+
} else if (tmpl_contains("<|begin|>") && tmpl_contains("<|end|>") && tmpl_contains("<|content|>")) {
221+
return LLM_CHAT_TEMPLATE_SOLAR_OPEN;
219222
}
220223
return LLM_CHAT_TEMPLATE_UNKNOWN;
221224
}
@@ -845,6 +848,14 @@ int32_t llm_chat_apply_template(
845848
if (add_ass) {
846849
ss << "[unused9]助手:";
847850
}
851+
} else if (tmpl == LLM_CHAT_TEMPLATE_SOLAR_OPEN) {
852+
for (auto message : chat) {
853+
std::string role(message->role);
854+
ss << "<|begin|>" << role << "<|content|>" << message->content << "<|end|>";
855+
}
856+
if (add_ass) {
857+
ss << "<|begin|>assistant";
858+
}
848859
} else {
849860
// template not supported
850861
return -1;

src/llama-chat.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ enum llm_chat_template {
5454
LLM_CHAT_TEMPLATE_SEED_OSS,
5555
LLM_CHAT_TEMPLATE_GROK_2,
5656
LLM_CHAT_TEMPLATE_PANGU_EMBED,
57+
LLM_CHAT_TEMPLATE_SOLAR_OPEN,
5758
LLM_CHAT_TEMPLATE_UNKNOWN,
5859
};
5960

src/llama-model.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ const char * llm_type_name(llm_type type) {
126126
case LLM_TYPE_31B_A3_5B: return "31B.A3.5B";
127127
case LLM_TYPE_80B_A3B: return "80B.A3B";
128128
case LLM_TYPE_100B_A6B: return "100B.A6B";
129+
case LLM_TYPE_102B_A12B: return "102B.A12B";
129130
case LLM_TYPE_106B_A12B: return "106B.A12B";
130131
case LLM_TYPE_230B_A10B: return "230B.A10B";
131132
case LLM_TYPE_235B_A22B: return "235B.A22B";
@@ -1778,6 +1779,7 @@ void llama_model::load_hparams(llama_model_loader & ml) {
17781779

17791780
switch (hparams.n_layer) {
17801781
case 47: type = LLM_TYPE_106B_A12B; break; // GLM-4.5-Air (46 layers + 1 NextN layer)
1782+
case 48: type = LLM_TYPE_102B_A12B; break; // Solar Open
17811783
case 93: type = LLM_TYPE_355B_A32B; break; // GLM-4.5 (92 layers + 1 NextN layer)
17821784
default: type = LLM_TYPE_UNKNOWN;
17831785
}
@@ -5206,9 +5208,9 @@ bool llama_model::load_tensors(llama_model_loader & ml) {
52065208
layer.wq = create_tensor(tn(LLM_TENSOR_ATTN_Q, "weight", i), { n_embd, n_embd_head_k * n_head }, flags);
52075209
layer.wk = create_tensor(tn(LLM_TENSOR_ATTN_K, "weight", i), { n_embd, n_embd_k_gqa }, flags);
52085210
layer.wv = create_tensor(tn(LLM_TENSOR_ATTN_V, "weight", i), { n_embd, n_embd_v_gqa }, flags);
5209-
layer.bq = create_tensor(tn(LLM_TENSOR_ATTN_Q, "bias", i), { n_embd_head_k * n_head }, flags);
5210-
layer.bk = create_tensor(tn(LLM_TENSOR_ATTN_K, "bias", i), { n_embd_k_gqa }, flags);
5211-
layer.bv = create_tensor(tn(LLM_TENSOR_ATTN_V, "bias", i), { n_embd_v_gqa }, flags);
5211+
layer.bq = create_tensor(tn(LLM_TENSOR_ATTN_Q, "bias", i), { n_embd_head_k * n_head }, TENSOR_NOT_REQUIRED | flags);
5212+
layer.bk = create_tensor(tn(LLM_TENSOR_ATTN_K, "bias", i), { n_embd_k_gqa }, TENSOR_NOT_REQUIRED | flags);
5213+
layer.bv = create_tensor(tn(LLM_TENSOR_ATTN_V, "bias", i), { n_embd_v_gqa }, TENSOR_NOT_REQUIRED | flags);
52125214

52135215
layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", i), { n_embd_head_k * n_head, n_embd }, flags);
52145216

src/llama-model.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ enum llm_type {
119119
LLM_TYPE_31B_A3_5B,
120120
LLM_TYPE_80B_A3B, // Qwen3 Next
121121
LLM_TYPE_100B_A6B,
122+
LLM_TYPE_102B_A12B, // Solar-Open
122123
LLM_TYPE_106B_A12B, // GLM-4.5-Air
123124
LLM_TYPE_230B_A10B, // Minimax M2
124125
LLM_TYPE_235B_A22B,

src/llama-vocab.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ struct llm_tokenizer_bpe : llm_tokenizer {
355355
case LLAMA_VOCAB_PRE_TYPE_STABLELM2:
356356
case LLAMA_VOCAB_PRE_TYPE_QWEN2:
357357
case LLAMA_VOCAB_PRE_TYPE_HUNYUAN:
358+
case LLAMA_VOCAB_PRE_TYPE_SOLAR_OPEN:
358359
regex_exprs = {
359360
// original regex from tokenizer.json
360361
// "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+"
@@ -2015,6 +2016,10 @@ void llama_vocab::impl::load(llama_model_loader & ml, const LLM_KV & kv) {
20152016
tokenizer_pre == "minimax-m2") {
20162017
pre_type = LLAMA_VOCAB_PRE_TYPE_MINIMAX_M2;
20172018
clean_spaces = false;
2019+
} else if (
2020+
tokenizer_pre == "solar-open") {
2021+
pre_type = LLAMA_VOCAB_PRE_TYPE_SOLAR_OPEN;
2022+
clean_spaces = false;
20182023
} else {
20192024
throw std::runtime_error(format("unknown pre-tokenizer type: '%s'", tokenizer_pre.c_str()));
20202025
}
@@ -2358,6 +2363,8 @@ void llama_vocab::impl::load(llama_model_loader & ml, const LLM_KV & kv) {
23582363
|| t.first == "<|end|>"
23592364
|| t.first == "<|return|>" // o200k_harmony
23602365
|| t.first == "<|call|>" // o200k_harmony
2366+
|| t.first == "<|flush|>" // solar-open
2367+
|| t.first == "<|calls|>" // solar-open
23612368
|| t.first == "<end_of_turn>"
23622369
|| t.first == "<|endoftext|>"
23632370
|| t.first == "<|eom_id|>"
@@ -2404,13 +2411,14 @@ void llama_vocab::impl::load(llama_model_loader & ml, const LLM_KV & kv) {
24042411
LLAMA_LOG_WARN("%s: special_eom_id is not in special_eog_ids - the tokenizer config may be incorrect\n", __func__);
24052412
}
24062413

2407-
// TODO: workaround for o200k_harmony tokenizer: the "<|end|>" token should not be EOG
2408-
// we don't have a good way to detect this, so for now, if we have "<|return|>" and "<|call|>" tokens,
2414+
// TODO: workaround for o200k_harmony and solar-open tokenizer: the "<|end|>" token should not be EOG
2415+
// we don't have a good way to detect this, so for now, if we have "<|return|>" and "<|call|>" tokens ("<|calls|>" and "<|flush|>" for solar-open),
24092416
// we remove the "<|end|>" token from the EOG list
24102417
{
24112418
bool has_return = false;
24122419
bool has_call = false;
24132420
bool has_end = false;
2421+
bool has_flush = false;
24142422

24152423
llama_token end_id = LLAMA_TOKEN_NULL;
24162424

@@ -2420,18 +2428,20 @@ void llama_vocab::impl::load(llama_model_loader & ml, const LLM_KV & kv) {
24202428

24212429
if (id_to_token[tid].text == "<|return|>") {
24222430
has_return = true;
2423-
} else if (id_to_token[tid].text == "<|call|>") {
2431+
} else if (id_to_token[tid].text == "<|call|>" || id_to_token[tid].text == "<|calls|>") {
24242432
has_call = true;
2433+
} else if (id_to_token[tid].text == "<|flush|>") {
2434+
has_flush = true;
24252435
} else if (id_to_token[tid].text == "<|end|>") {
24262436
has_end = true;
24272437
end_id = tid;
24282438
}
24292439
}
24302440

2431-
if (has_return && has_call && has_end) {
2441+
if ((has_return && has_call && has_end) || (has_call && has_flush && has_end)) {
24322442
special_eog_ids.erase(end_id);
24332443
id_to_token[end_id].attr = LLAMA_TOKEN_ATTR_USER_DEFINED;
2434-
LLAMA_LOG_WARN("%s: special_eog_ids contains both '<|return|>' and '<|call|>' tokens, removing '<|end|>' token from EOG list\n", __func__);
2444+
LLAMA_LOG_WARN("%s: special_eog_ids contains both '<|return|>' and '<|call|>', or '<|calls|>' and '<|flush|>' tokens, removing '<|end|>' token from EOG list\n", __func__);
24352445
}
24362446
}
24372447
}

0 commit comments

Comments
 (0)