Compare commits

..

5 Commits

Author SHA1 Message Date
uvos d63aa398de hip: compile debug builds with -O2 on hip to avoid a compiler bug (#20392) 2026-03-12 10:37:10 +08:00
Mishusha a8304b4d27 common/parser: add GigaChatV3/3.1 models support (#19931)
Co-authored-by: Mishusha <pmv26021975@gmail.com>
2026-03-12 01:22:25 +01:00
DAN™ fdb17643d3 model : add support for Phi4ForCausalLMV (#20168)
* Add support for Phi4ForCausalLMV.

* Fix Phi-4 vision parity (correcting SigLIP2 patch-kernel export layout) and matching HF NaFlex resize behavior in mtmd.

* Rename contants + fix tokenizer label

* Clean-ups.

* Fix GGUF export.

* Set tokenizer.ggml.pre explicitly.

* Default vocab name rather than forcing it.

* Clean-ups.

* Fix indent.

* Fix subscriptable error.

* remov overcomplicated code path

* Clean-ups.

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
2026-03-12 00:25:54 +01:00
Richard Davison 1eea6a2968 graph : add optional scale parameter to build_lora_mm [no ci] (#20427) 2026-03-12 00:22:49 +01:00
ddh0 4a748b8f15 common : fix --n-cpu-moe, --cpu-moe for models with fused gate + up (#20416) 2026-03-12 00:13:28 +01:00
18 changed files with 996 additions and 62 deletions
+80
View File
@@ -1354,6 +1354,77 @@ static common_chat_params common_chat_params_init_lfm2(const common_chat_templat
return data;
}
static common_chat_params common_chat_params_init_gigachat_v3(
const common_chat_template & tmpl,
const autoparser::templates_params & inputs) {
common_chat_params data;
data.prompt = common_chat_template_direct_apply(tmpl, inputs);
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.supports_thinking = false;
data.preserved_tokens = {
"<|message_sep|>\n\n",
"<|role_sep|>\n",
};
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
auto tool_call_start_prefix = "<|message_sep|>\n\nfunction call<|role_sep|>\n";
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
// Build a choice of all available tools
auto tool_choice = p.choice();
for (const auto & tool : inputs.tools) {
const auto & function = tool.at("function");
std::string name = function.at("name");
const auto & schema = function.at("parameters");
auto tool_name = p.json_member("name", "\"" + p.tool_name(p.literal(name)) + "\"");
auto tool_args = p.json_member("arguments", p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema)));
auto tool_open = p.tool_open(p.literal("{") << tool_name);
tool_choice |= p.rule("tool-" + name, tool_open << "," << tool_args << "}");
}
// Define the tool call structure
auto min_calls = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? 1 : 0;
auto max_calls = 1; // parallel toolcalls are not supported
auto tool_call = p.rule("tool-call", p.literal(tool_call_start_prefix) + tool_choice);
auto tool_calls = p.trigger_rule("tool-call-root", p.repeat(tool_call, /* min = */ min_calls, /* max = */ max_calls));
return p.content(p.until("<|message_sep|>\n\n")) << tool_calls;
}
// Content only parser
include_grammar = false;
return p.content(p.rest());
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.at("parameters");
builder.resolve_refs(schema);
});
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{COMMON_GRAMMAR_TRIGGER_TYPE_WORD, tool_call_start_prefix}
};
}
return data;
}
namespace workaround {
static void map_developer_role_to_system(json & messages) {
@@ -1525,6 +1596,15 @@ static common_chat_params common_chat_templates_apply_jinja(const struct common_
return common_chat_params_init_lfm2(tmpl, params);
}
// GigaChatV3 format detection
if (src.find("<|role_sep|>") != std::string::npos &&
src.find("<|message_sep|>") != std::string::npos &&
src.find("<|function_call|>") == std::string::npos
) {
LOG_DBG("Using specialized template: GigaChatV3\n");
return common_chat_params_init_gigachat_v3(tmpl, params);
}
try {
LOG_DBG("Using differential autoparser\n");
struct autoparser::autoparser autoparser;
+1 -1
View File
@@ -926,7 +926,7 @@ const char * const LLM_KV_SPLIT_TENSORS_COUNT = "split.tensors.count";
// MoE utils
//
const char * const LLM_FFN_EXPS_REGEX = "\\.ffn_(up|down|gate)_(ch|)exps";
const char * const LLM_FFN_EXPS_REGEX = "\\.ffn_(up|down|gate|gate_up)_(ch|)exps";
inline std::string llm_ffn_exps_block_regex(int idx) {
return string_format("blk\\.%d%s", idx, LLM_FFN_EXPS_REGEX);
+124 -1
View File
@@ -5062,7 +5062,7 @@ class Phi2Model(TextModel):
self.gguf_writer.add_add_bos_token(False)
@ModelBase.register("Phi3ForCausalLM")
@ModelBase.register("Phi3ForCausalLM", "Phi4ForCausalLMV")
class Phi3MiniModel(TextModel):
model_arch = gguf.MODEL_ARCH.PHI3
@@ -5237,6 +5237,129 @@ class Phi3MiniModel(TextModel):
yield (self.format_tensor_name(gguf.MODEL_TENSOR.ROPE_FACTORS_LONG), torch.tensor(long_factors, dtype=torch.float32))
yield (self.format_tensor_name(gguf.MODEL_TENSOR.ROPE_FACTORS_SHORT), torch.tensor(short_factors, dtype=torch.float32))
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
if name.startswith(("model.vision_tower.", "vision_tower.", "model.mm_projector.", "mm_projector.")):
return
yield from super().modify_tensors(data_torch, name, bid)
@ModelBase.register("Phi4ForCausalLMV")
class Phi4VisionMmprojModel(MmprojModel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
assert self.hparams_vision is not None
self.vision_total_layers = int(self.find_vparam(self.n_block_keys))
if self.vision_total_layers < 2:
raise ValueError(
f"Phi-4 vision mmproj conversion requires at least 2 vision layers, got {self.vision_total_layers}"
)
# Phi-4 uses SigLIP2 hidden_states[-2], so export one fewer encoder block and
# drop post-layernorm/head weights. This makes the GGUF runtime output match
# the feature map consumed by the patched siglip.cpp Phi-4 projector path.
self.vision_export_layers = self.vision_total_layers - 1
self.vision_last_layer_idx = self.vision_total_layers - 1
for key in self.n_block_keys:
if key in self.hparams_vision:
self.hparams_vision[key] = self.vision_export_layers
break
self.block_count = self.vision_export_layers
self.tensor_map = gguf.get_tensor_name_map(gguf.MODEL_ARCH.MMPROJ, self.block_count)
patch_size = self.preprocessor_config.get("patch_size")
if patch_size is None:
raise KeyError("Phi-4 vision mmproj conversion requires patch_size in preprocessor_config.json")
self.hparams_vision["patch_size"] = patch_size
pos_emb_name = next(
(
name for name in self.model_tensors
if name.endswith("vision_model.embeddings.position_embedding.weight")
),
None,
)
if pos_emb_name is None:
raise KeyError("Phi-4 vision mmproj conversion could not find position_embedding.weight")
pos_emb_shape = self.model_tensors[pos_emb_name]().shape
base_grid_tokens = int(pos_emb_shape[0])
grid_side = math.isqrt(base_grid_tokens)
if grid_side * grid_side != base_grid_tokens:
raise ValueError(f"Unexpected Phi-4 position embedding shape: {tuple(pos_emb_shape)}")
self.hparams_vision["image_size"] = grid_side * patch_size
min_num_patches = self.preprocessor_config.get("min_num_patches", self.global_config.get("min_num_patches"))
max_num_patches = self.preprocessor_config.get("max_num_patches", self.global_config.get("max_num_patches"))
if min_num_patches is None or max_num_patches is None:
raise KeyError("Phi-4 vision mmproj conversion requires min_num_patches and max_num_patches")
self.min_pixels = int(min_num_patches) * patch_size * patch_size
self.max_pixels = int(max_num_patches) * patch_size * patch_size
def set_gguf_parameters(self):
super().set_gguf_parameters()
assert self.hparams_vision is not None
self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.PHI4)
self.gguf_writer.add_vision_min_pixels(self.min_pixels)
self.gguf_writer.add_vision_max_pixels(self.max_pixels)
self.gguf_writer.add_vision_use_gelu(True)
self.gguf_writer.add_vision_attention_layernorm_eps(self.hparams_vision.get("layer_norm_eps", 1e-6))
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
if name.startswith(("model.vision_tower.vision_tower.", "vision_tower.")):
if ".vision_model.head." in name:
return
new_name = name.replace("model.vision_tower.vision_tower.", "vision_tower.")
if ".vision_model.post_layernorm." in new_name:
return
if bid is not None and bid == self.vision_last_layer_idx:
return
if new_name.endswith("vision_model.embeddings.patch_embedding.weight"):
assert self.hparams_vision is not None
if data_torch.ndim != 2:
raise ValueError(f"Unexpected Phi-4 patch embedding shape: {tuple(data_torch.shape)}")
patch_area = self.hparams_vision["patch_size"] ** 2
in_features = data_torch.shape[1]
if in_features % patch_area != 0:
raise ValueError(
f"Phi-4 patch embedding input dim {in_features} is not divisible by patch area {patch_area}"
)
num_channels = in_features // patch_area
patch_size = self.hparams_vision["patch_size"]
data_torch = data_torch.view(data_torch.shape[0], patch_size, patch_size, num_channels)
data_torch = data_torch.permute(0, 3, 1, 2)
yield from super().modify_tensors(data_torch, new_name, bid)
return
if name.startswith(("model.mm_projector.", "mm_projector.")):
local_name = name
local_name = local_name.replace("model.mm_projector.", "")
local_name = local_name.replace("mm_projector.", "")
if not (local_name.startswith("0.") or local_name.startswith("2.")):
return
suffix = ".bias" if local_name.endswith(".bias") else ".weight"
mm_idx = int(local_name.split(".", maxsplit=1)[0])
yield (self.format_tensor_name(gguf.MODEL_TENSOR.V_MMPROJ, mm_idx, suffix=suffix), data_torch)
return
return
@ModelBase.register("PhiMoEForCausalLM")
class PhiMoeModel(Phi3MiniModel):
+4
View File
@@ -11,6 +11,10 @@ endif()
list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH})
list(APPEND CMAKE_PREFIX_PATH "${ROCM_PATH}/lib64/cmake")
if (NOT DEFINED CMAKE_HIP_FLAGS_DEBUG)
set(CMAKE_HIP_FLAGS_DEBUG "-g -O2")
endif()
# CMake on Windows doesn't support the HIP language yet
if (WIN32)
set(CXX_IS_HIPCC TRUE)
+1
View File
@@ -3881,6 +3881,7 @@ class VisionProjectorType:
GEMMA3 = "gemma3"
GEMMA3NV = "gemma3nv"
GEMMA3NA = "gemma3na"
PHI4 = "phi4"
IDEFICS3 = "idefics3"
PIXTRAL = "pixtral"
LLAMA4 = "llama4"
+355
View File
@@ -0,0 +1,355 @@
{#--------TOOL RENDERING FUNCTIONS---------#}
{#---------------------------------------------------------------
Converts JSON Schema (dict) to a TypeScript type definition
----------------------------------------------------------------#}
{%- macro json_schema_to_typescript(schema, indent="") -%}
{%- set ADDITIONAL_JSON_KEYS = ['format', 'maxItems', 'maximum', 'minItems', 'minimum', 'pattern'] -%}
{%- set ty = schema.get("type") -%}
{# ---------------- OBJECT ---------------- #}
{%- if ty == "object" -%}
{{- "{\n" -}}
{# Start building property list #}
{%- set props = schema.get("properties", {}) -%}
{%- set required = schema.get("required", []) -%}
{%- set has_additional_props = schema.get("additionalProperties") is defined -%}
{%- set additional_props_type = none -%}
{%- if has_additional_props -%}
{%- if schema.additionalProperties == true -%}
{%- set additional_props_type = {'type': 'any'} -%}
{%- elif schema.additionalProperties is mapping -%}
{%- set additional_props_type = schema.additionalProperties -%}
{%- endif -%}
{%- endif -%}
{%- for key, val in props.items() -%}
{# ---------- Description Comments ---------- #}
{%- if "description" in val -%}
{%- for line in val['description'].split('\n') -%}
{%- if line.strip() -%}
{{- indent + '// ' + line + '\n' -}}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{# ---------- Additional JSON Keys ---------- #}
{%- for add_key, add_val in val.items() -%}
{%- if add_key in ADDITIONAL_JSON_KEYS -%}
{%- if add_val is string -%}
{{- indent + '// ' + add_key + ': "' + add_val + '"' + '\n' -}}
{%- else -%}
{{- indent + '// ' + add_key + ': ' ~ add_val ~ '\n' -}}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{# ---------- Property Definition ---------- #}
{%- set type_str = json_schema_to_typescript(
val,
indent + " "
) -%}
{{- indent + key + ('' if key in required else '?') + ': ' + type_str + ',' -}}
{%- if "default" in val or "defalut_value" in val -%}
{%- set default = val.get("default", val.get("defalut_value")) -%}
{%- if default is string -%}
{{- ' // default: "' + default + '"' -}}
{%- else -%}
{{- ' // default: ' ~ default -}}
{%- endif -%}
{%- endif -%}
{{- "\n" -}}
{%- endfor -%}
{# Handle additionalProperties as index signature #}
{%- if has_additional_props and additional_props_type is not none -%}
{%- set additional_type_str = json_schema_to_typescript(
additional_props_type,
indent + " "
) -%}
{{- indent + '[key: string]: ' + additional_type_str + '\n' -}}
{%- endif -%}
{{- indent[: (indent|length - " "|length) ] + '}' -}}
{# ---------------- STRING ---------------- #}
{%- elif ty == "string" -%}
{%- if schema.get("enum") -%}
{%- set ns = namespace(enum = []) -%}
{%- for en in schema['enum'] -%}
{%- set ns.enum = ns.enum + ['"' ~ en ~ '"'] -%}
{%- endfor -%}
{{- ns.enum | join(' | ') -}}
{%- elif schema.get("format", "none") in ['date-time', 'date'] -%}
{{- 'Date' -}}
{%- else -%}
{{- 'string' -}}
{%- endif -%}
{# ---------------- NUMBER / INTEGER ---------------- #}
{%- elif ty in ["number", "integer"] -%}
{%- if schema.get("enum") -%}
{{- schema.enum | join(' | ') -}}
{%- else -%}
{{- 'number' -}}
{%- endif -%}
{# ---------------- BOOLEAN ---------------- #}
{%- elif ty == "boolean" -%}
{{- 'boolean' -}}
{# ---------------- ARRAY ---------------- #}
{%- elif ty == "array" -%}
{%- if "items" in schema -%}
{{- json_schema_to_typescript(schema['items'], indent) + '[]' -}}
{%- else -%}
{{- 'Array<any>' -}}
{%- endif -%}
{# ---------------- FALLBACK ---------------- #}
{%- else -%}
{{- 'any' -}}
{%- endif -%}
{%- endmacro -%}
{#---------------------------------------------------------------
Renders a namespace and its tool definitions in TypeScript style
----------------------------------------------------------------#}
{%- macro render_tool_namespace(namespace_name, tools) -%}
{%- set ns = namespace(sections = ['namespace ' ~ namespace_name ~ ' {']) -%}
{%- for tool in tools -%}
{%- if tool.function -%}
{%- set tool = tool.function -%}
{%- endif -%}
{%- set ns_tool = namespace(content_lines=[]) -%}
{# ---------- TOOL DESCRIPTION ---------- #}
{%- if tool.get('description') -%}
{%- for line in tool['description'].split('\n') -%}
{%- if line.strip() -%}
{%- set ns_tool.content_lines = ns_tool.content_lines + ['// ' ~ line] -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{# ---------- TOOL SIGNATURE ---------- #}
{%- set main_body = "" -%}
{%- set params = tool.get("parameters") -%}
{%- if params and params.get("properties") -%}
{%- set param_type = json_schema_to_typescript(params, " ") -%}
{%- set main_body = 'type ' ~ tool.name ~ ' = (_: ' ~ param_type ~ ') => ' -%}
{%- else -%}
{%- set main_body = 'type ' ~ tool.name ~ ' = () => ' -%}
{%- endif -%}
{# ---------- RETURN TYPE ---------- #}
{%- set return_params = tool.get("return_parameters") -%}
{%- if return_params and return_params.get("properties") -%}
{%- set return_type = json_schema_to_typescript(return_params, " ") -%}
{%- set main_body = main_body ~ return_type -%}
{%- else -%}
{%- set main_body = main_body ~ 'any' -%}
{%- endif -%}
{%- set main_body = main_body ~ ';\n' -%}
{%- set ns_tool.content_lines = ns_tool.content_lines + [main_body] -%}
{# ---------- ADD TOOL TO SECTIONS ---------- #}
{%- set ns.sections = ns.sections + [ns_tool.content_lines | join('\n')] -%}
{%- endfor -%}
{%- set ns.sections = ns.sections + ['} // namespace ' ~ namespace_name] -%}
{{- ns.sections | join('\n') -}}
{%- endmacro -%}
{# ----------- MESSAGE RENDERING HELPER FUNCTIONS ------------ #}
{%- macro render_role_message(message, role=None) -%}
{%- if not role -%}
{%- set role = message["role"] -%}
{%- endif -%}
{%- set message_content = message['content'] or '' -%}
{%- if message_content is not string -%}
{%- set message_content = message_content | tojson(ensure_ascii=False) -%}
{%- endif -%}
{{- role + add_tokens.role_sep + message_content + add_tokens.message_sep -}}
{%- endmacro -%}
{%- macro render_function_call(message) -%}
{%- set call = message['content'] -%}
{%- if call.function -%}
{%- set call = call.function -%}
{%- endif -%}
{%- set arguments = call['arguments'] -%}
{%- if arguments is not string -%}
{%- set arguments = arguments| tojson(ensure_ascii=False) -%}
{%- endif -%}
{{- render_role_message(
{
'role': 'function call',
'content': '{"name": "' ~ call['name'] ~ '", "arguments": ' ~ arguments ~ '}'
}
) -}}
{%- endmacro -%}
{# ----- SPECIAL TOKENS ----- #}
{%- set add_tokens = namespace(
role_sep="<|role_sep|>\n",
message_sep="<|message_sep|>\n\n"
) -%}
{# ----- DEFAULT DEVSYSTEM ----- #}
{%- set DEVSYSTEM -%}
<role_description>
Description of the roles available in the dialog.
`developer system`
A message added by Sber before the main dialog. It has the highest priority and sets global, non-overridable conditions (for example, conversation rules, the safety policy, the assistant's overall response style, etc.).
`system`
A system instruction added by developers or by the user, but with a lower priority than `developer system`. It usually describes the assistant's instructions, a specific response style, and other conditions for this particular dialog.
`user`
A message or request from the user. The assistant follows it if it does not conflict with higher-priority instructions (see <instruction_priority>).
`user memory`
A sequence of the most up-to-date long-term facts about the user at the time of their request, presented as a JSON list of strings. Facts are listed in chronological order, meaning newer facts are appended to the end of the sequence. When facts are changed or deleted, records of previous facts remain in the sequence. The assistant saves facts using a function and uses them in accordance with the <memory_guidelines> block below.
`added files`
Metadata about files available for use in the dialog, presented in JSON format. It contains the following keys: id (a unique file identifier), name (file name), type (file type).
`assistant`
The assistant's reply to the user's request. If the system instruction or the user does not set additional rules for `assistant`, this reply must comply with the instructions in the <assistant_guidelines> block below. The list of functions available to call is contained in `function descriptions`. The name of the required function and its arguments will be generated next by the `function call` role. In its replies, the assistant follows the instructions in accordance with <instruction_priority>.
`function descriptions`
Function descriptions in TypeScript format. A function is a special tool (or a set of instructions) that the assistant can call to perform specific actions, computations, or obtain data needed to solve the user's task. Each function description contains blocks with the name, description, and arguments. Sometimes the description contains separate blocks with return parameters and usage examples that illustrate the correct call and arguments.
`function call`
The function that `assistant` calls based on the dialog context, and its arguments. The function is invoked in strict accordance with the instructions in the <function_usage> block.
`function result`
The result of the last function call.
</role_description>
<available_modalities>
The assistant can work with the following modalities: text, available functions.
</available_modalities>
<instruction_priority>
If instructions from different roles conflict within the dialog context, observe the following priorities:
`developer system` > `system` > `user` > `function descriptions` > `function result` > `user memory`
</instruction_priority>
<function_usage>
Basic instructions for working with functions.
Only call those functions that are described in `function descriptions`.
Call available functions when, according to their description, such a call will help provide a more complete and/or accurate answer to the user's request. Fill in function arguments using information from the dialog context. If a function could help answer the request but a required argument is missing from the context, ask the user for the missing data before calling the function. If a necessary function is unavailable or an error occurs, briefly inform the user and, if possible, suggest an alternative.
</function_usage>
<memory_guidelines>
Rules for using facts in long-term memory:
If there is no message under the `user memory` role in the dialog, this is equivalent to the absence of long-term facts about the user in memory. In that case, information about the user is limited to the current dialog, and no new facts should be saved.
</memory_guidelines>
<assistant_guidelines>
You are a helpful assistant.
# Instructions
- Strictly follow the instruction priority.
- Maintain a logical chain of reasoning when answering the user's question.
- For complex questions (for example, STEM), try to answer in detail unless the system message or dialog context limits the response length.
- Be helpful, truthful, and avoid unsafe or prohibited content in your responses.
- Try to reply in the language in which the user asked their question.
</assistant_guidelines>
A dialog will follow below.
The dialog may include various roles described in the <role_description> block.
Each turn begins with the role name and a special token that marks the end of the role's full name, and ends with a special end-of-turn token.
Your task is to continue the dialog from the last specified role in accordance with the dialog context.
{%- endset -%}
{#- ---------------------- RENDERING STARTS HERE ---------------------- -#}
{# ----- RENDER BOS TOKEN ----- #}
{{- bos_token -}}
{# ----- RENDER DEVSYSTEM ----- #}
{{- render_role_message({"role": "developer system", "content": DEVSYSTEM}) -}}
{# ----- RENDER SYSTEM IF PRESENT ----- #}
{%- if messages and messages[0]['role'] == 'system' -%}
{{- render_role_message(messages[0]) -}}
{%- set messages = messages[1:] -%}
{%- endif -%}
{# ----- RENDER TOOLS ----- #}
{%- if tools -%}
{%- set tools_content = (
render_tool_namespace('functions', tools)
+ "\n\n"
) -%}
{{- render_role_message({'role': 'function descriptions', 'content': tools_content}) -}}
{%- endif -%}
{# ----- MAIN MESSAGE LOOP ----- #}
{%- for message in messages -%}
{# ----- TOOL MESSAGE -------#}
{%- if message['role'] == 'tool' -%}
{{- render_role_message(message, 'function result') -}}
{# ----- ASSISTANT MESSAGE ----- #}
{%- elif message['role'] == 'assistant' -%}
{# ----- FUNCTION CALL PART CHECKING: SINGLE CALL SETUP ----- #}
{%- if message.tool_calls is defined and message.tool_calls -%}
{%- set function_call = message.tool_calls[0] -%}
{%- else -%}
{%- set function_call = None -%}
{%- endif -%}
{# ----- MAIN ASSISTANT RENDERING ----- #}
{{- render_role_message({'role': 'assistant', 'content': message.content}) -}}
{%- if function_call -%}
{{- render_function_call({'role': 'function call', 'content': function_call}) -}}
{%- endif -%}
{# ----- OTHER MESSAGES ----- #}
{%- else -%}
{{- render_role_message(message) -}}
{%- endif -%}
{# ----- ADDING GENERATION PROMPT ----- #}
{%- if loop.last and add_generation_prompt and message['role'] != 'assistant' -%}
{{- 'assistant' + add_tokens.role_sep -}}
{%- endif -%}
{%- endfor -%}
@@ -0,0 +1,339 @@
{#--------TOOL RENDERING FUNCTIONS---------#}
{#---------------------------------------------------------------
Converts JSON Schema (dict) to a TypeScript type definition
----------------------------------------------------------------#}
{%- macro json_schema_to_typescript(schema, indent="") -%}
{%- set ADDITIONAL_JSON_KEYS = ['format', 'maxItems', 'maximum', 'minItems', 'minimum', 'pattern'] -%}
{%- set ty = schema.get("type") -%}
{# ---------------- OBJECT ---------------- #}
{%- if ty == "object" -%}
{{- "{\n" -}}
{# Start building property list #}
{%- set props = schema.get("properties", {}) -%}
{%- set required = schema.get("required", []) -%}
{%- set has_additional_props = schema.get("additionalProperties") is defined -%}
{%- set additional_props_type = none -%}
{%- if has_additional_props -%}
{%- if schema.additionalProperties == true -%}
{%- set additional_props_type = {'type': 'any'} -%}
{%- elif schema.additionalProperties is mapping -%}
{%- set additional_props_type = schema.additionalProperties -%}
{%- endif -%}
{%- endif -%}
{%- for key, val in props.items() -%}
{# ---------- Description Comments ---------- #}
{%- if "description" in val -%}
{%- for line in val['description'].split('\n') -%}
{%- if line.strip() -%}
{{- indent + '// ' + line + '\n' -}}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{# ---------- Additional JSON Keys ---------- #}
{%- for add_key, add_val in val.items() -%}
{%- if add_key in ADDITIONAL_JSON_KEYS -%}
{%- if add_val is string -%}
{{- indent + '// ' + add_key + ': "' + add_val + '"' + '\n' -}}
{%- else -%}
{{- indent + '// ' + add_key + ': ' ~ add_val ~ '\n' -}}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{# ---------- Property Definition ---------- #}
{%- set type_str = json_schema_to_typescript(
val,
indent + " "
) -%}
{{- indent + key + ('' if key in required else '?') + ': ' + type_str + ',' -}}
{%- if "default" in val or "defalut_value" in val -%}
{%- set default = val.get("default", val.get("defalut_value")) -%}
{%- if default is string -%}
{{- ' // default: "' + default + '"' -}}
{%- else -%}
{{- ' // default: ' ~ default -}}
{%- endif -%}
{%- endif -%}
{{- "\n" -}}
{%- endfor -%}
{# Handle additionalProperties as index signature #}
{%- if has_additional_props and additional_props_type is not none -%}
{%- set additional_type_str = json_schema_to_typescript(
additional_props_type,
indent + " "
) -%}
{{- indent + '[key: string]: ' + additional_type_str + '\n' -}}
{%- endif -%}
{{- indent[: (indent|length - " "|length) ] + '}' -}}
{# ---------------- STRING ---------------- #}
{%- elif ty == "string" -%}
{%- if schema.get("enum") -%}
{%- set ns = namespace(enum = []) -%}
{%- for en in schema['enum'] -%}
{%- set ns.enum = ns.enum + ['"' ~ en ~ '"'] -%}
{%- endfor -%}
{{- ns.enum | join(' | ') -}}
{%- elif schema.get("format", "none") in ['date-time', 'date'] -%}
{{- 'Date' -}}
{%- else -%}
{{- 'string' -}}
{%- endif -%}
{# ---------------- NUMBER / INTEGER ---------------- #}
{%- elif ty in ["number", "integer"] -%}
{%- if schema.get("enum") -%}
{{- schema.enum | join(' | ') -}}
{%- else -%}
{{- 'number' -}}
{%- endif -%}
{# ---------------- BOOLEAN ---------------- #}
{%- elif ty == "boolean" -%}
{{- 'boolean' -}}
{# ---------------- ARRAY ---------------- #}
{%- elif ty == "array" -%}
{%- if "items" in schema -%}
{{- json_schema_to_typescript(schema['items'], indent) + '[]' -}}
{%- else -%}
{{- 'Array<any>' -}}
{%- endif -%}
{# ---------------- FALLBACK ---------------- #}
{%- else -%}
{{- 'any' -}}
{%- endif -%}
{%- endmacro -%}
{#---------------------------------------------------------------
Renders a namespace and its tool definitions in TypeScript style
----------------------------------------------------------------#}
{%- macro render_tool_namespace(namespace_name, tools) -%}
{%- set ns = namespace(sections = ['namespace ' ~ namespace_name ~ ' {']) -%}
{%- for tool in tools -%}
{%- if tool.function -%}
{%- set tool = tool.function -%}
{%- endif -%}
{%- set ns_tool = namespace(content_lines=[]) -%}
{# ---------- TOOL DESCRIPTION ---------- #}
{%- if tool.get('description') -%}
{%- for line in tool['description'].split('\n') -%}
{%- if line.strip() -%}
{%- set ns_tool.content_lines = ns_tool.content_lines + ['// ' ~ line] -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{# ---------- TOOL SIGNATURE ---------- #}
{%- set main_body = "" -%}
{%- set params = tool.get("parameters") -%}
{%- if params and params.get("properties") -%}
{%- set param_type = json_schema_to_typescript(params, " ") -%}
{%- set main_body = 'type ' ~ tool.name ~ ' = (_: ' ~ param_type ~ ') => ' -%}
{%- else -%}
{%- set main_body = 'type ' ~ tool.name ~ ' = () => ' -%}
{%- endif -%}
{# ---------- RETURN TYPE ---------- #}
{%- set return_params = tool.get("return_parameters") -%}
{%- if return_params and return_params.get("properties") -%}
{%- set return_type = json_schema_to_typescript(return_params, " ") -%}
{%- set main_body = main_body ~ return_type -%}
{%- else -%}
{%- set main_body = main_body ~ 'any' -%}
{%- endif -%}
{%- set main_body = main_body ~ ';\n' -%}
{%- set ns_tool.content_lines = ns_tool.content_lines + [main_body] -%}
{# ---------- ADD TOOL TO SECTIONS ---------- #}
{%- set ns.sections = ns.sections + [ns_tool.content_lines | join('\n')] -%}
{%- endfor -%}
{%- set ns.sections = ns.sections + ['} // namespace ' ~ namespace_name] -%}
{{- ns.sections | join('\n') -}}
{%- endmacro -%}
{# ----------- MESSAGE RENDERING HELPER FUNCTIONS ------------ #}
{%- macro render_function_call(call) -%}
{%- if call.function -%}
{%- set call = call.function -%}
{%- endif -%}
{%- set arguments = call['arguments'] -%}
{%- if arguments is not string -%}
{%- set arguments = arguments| tojson(ensure_ascii=False) -%}
{%- endif -%}
{{- '{"name": "' ~ call['name'] ~ '", "arguments": ' ~ arguments ~ '}' -}}
{%- endmacro -%}
{%- macro render_role_message(message, role=None) -%}
{%- if not role -%}
{%- set role = message["role"] -%}
{%- endif -%}
{%- set message_content = message['content'] or '' -%}
{%- if message_content is not string -%}
{%- set message_content = message_content | tojson(ensure_ascii=False) -%}
{%- endif -%}
{{- role + add_tokens.role_sep + message_content -}}
{%- if message.tool_calls is defined and message.tool_calls -%}
{{- add_tokens.function_call + render_function_call(message.tool_calls[0]) -}}
{%- endif -%}
{{- add_tokens.message_sep -}}
{%- endmacro -%}
{# ----- SPECIAL TOKENS ----- #}
{%- set add_tokens = namespace(
role_sep="<|role_sep|>\n",
message_sep="<|message_sep|>\n\n",
function_call="<|function_call|>"
) -%}
{# ----- DEFAULT DEVSYSTEM ----- #}
{%- set DEVSYSTEM -%}
<role_description>
Description of the roles available in the dialog.
`developer system`
A message added by Sber before the main dialog. It has the highest priority and sets global, non-overridable conditions (for example, conversation rules, the safety policy, the assistant's overall response style, etc.).
`system`
A system instruction added by developers or by the user, but with a lower priority than `developer system`. It usually describes the assistant's instructions, a specific response style, and other conditions for this particular dialog.
`user`
A message or request from the user. The assistant follows it if it does not conflict with higher-priority instructions (see <instruction_priority>).
`user memory`
A sequence of the most up-to-date long-term facts about the user at the time of their request, presented as a JSON list of strings. Facts are listed in chronological order, meaning newer facts are appended to the end of the sequence. When facts are changed or deleted, records of previous facts remain in the sequence. The assistant saves facts using a function and uses them in accordance with the <memory_guidelines> block below.
`added files`
Metadata about files available for use in the dialog, presented in JSON format. It contains the following keys: id (a unique file identifier), name (file name), type (file type).
`assistant`
The assistant's reply to the user's request. If the system instruction or the user does not set additional rules for `assistant`, this reply must comply with the instructions in the <assistant_guidelines> block below. The list of functions available to call is contained in `function descriptions`. The name of the required function and its arguments will be generated next by the `function call` role. In its replies, the assistant follows the instructions in accordance with <instruction_priority>.
`function descriptions`
Function descriptions in TypeScript format. A function is a special tool (or a set of instructions) that the assistant can call to perform specific actions, computations, or obtain data needed to solve the user's task. Each function description contains blocks with the name, description, and arguments. Sometimes the description contains separate blocks with return parameters and usage examples that illustrate the correct call and arguments.
`function call`
The function that `assistant` calls based on the dialog context, and its arguments. The function is invoked in strict accordance with the instructions in the <function_usage> block.
`function result`
The result of the last function call.
</role_description>
<available_modalities>
The assistant can work with the following modalities: text, available functions.
</available_modalities>
<instruction_priority>
If instructions from different roles conflict within the dialog context, observe the following priorities:
`developer system` > `system` > `user` > `function descriptions` > `function result` > `user memory`
</instruction_priority>
<function_usage>
Basic instructions for working with functions.
Only call those functions that are described in `function descriptions`.
Call available functions when, according to their description, such a call will help provide a more complete and/or accurate answer to the user's request. Fill in function arguments using information from the dialog context. If a function could help answer the request but a required argument is missing from the context, ask the user for the missing data before calling the function. If a necessary function is unavailable or an error occurs, briefly inform the user and, if possible, suggest an alternative.
</function_usage>
<memory_guidelines>
Rules for using facts in long-term memory:
If there is no message under the `user memory` role in the dialog, this is equivalent to the absence of long-term facts about the user in memory. In that case, information about the user is limited to the current dialog, and no new facts should be saved.
</memory_guidelines>
<assistant_guidelines>
You are a helpful assistant.
# Instructions
- Strictly follow the instruction priority.
- Maintain a logical chain of reasoning when answering the user's question.
- For complex questions (for example, STEM), try to answer in detail unless the system message or dialog context limits the response length.
- Be helpful, truthful, and avoid unsafe or prohibited content in your responses.
- Try to reply in the language in which the user asked their question.
</assistant_guidelines>
A dialog will follow below.
The dialog may include various roles described in the <role_description> block.
Each turn begins with the role name and a special token that marks the end of the role's full name, and ends with a special end-of-turn token.
Your task is to continue the dialog from the last specified role in accordance with the dialog context.
{%- endset -%}
{#- ---------------------- RENDERING STARTS HERE ---------------------- -#}
{# ----- RENDER BOS TOKEN ----- #}
{{- bos_token -}}
{# ----- RENDER DEVSYSTEM ----- #}
{{- render_role_message({"role": "developer system", "content": DEVSYSTEM}) -}}
{# ----- RENDER SYSTEM IF PRESENT ----- #}
{%- if messages and messages[0]['role'] == 'system' -%}
{{- render_role_message(messages[0]) -}}
{%- set messages = messages[1:] -%}
{%- endif -%}
{# ----- RENDER TOOLS ----- #}
{%- if tools -%}
{%- set tools_content = (
render_tool_namespace('functions', tools)
+ "\n\n"
) -%}
{{- render_role_message({'role': 'function descriptions', 'content': tools_content}) -}}
{%- endif -%}
{# ----- MAIN MESSAGE LOOP ----- #}
{%- for message in messages -%}
{# ----- TOOL MESSAGE -------#}
{%- if message['role'] == 'tool' -%}
{{- render_role_message(message, 'function result') -}}
{# ----- OTHER MESSAGES ----- #}
{%- else -%}
{{- render_role_message(message) -}}
{%- endif -%}
{# ----- ADDING GENERATION PROMPT ----- #}
{%- if loop.last and add_generation_prompt and message['role'] != 'assistant' -%}
{{- 'assistant' + add_tokens.role_sep -}}
{%- endif -%}
{%- endfor -%}
+6 -1
View File
@@ -900,7 +900,8 @@ ggml_tensor * llm_graph_context::build_cvec(
ggml_tensor * llm_graph_context::build_lora_mm(
ggml_tensor * w,
ggml_tensor * cur) const {
ggml_tensor * cur,
ggml_tensor * w_s) const {
ggml_tensor * res = ggml_mul_mat(ctx0, w, cur);
for (const auto & lora : *loras) {
@@ -921,6 +922,10 @@ ggml_tensor * llm_graph_context::build_lora_mm(
res = ggml_add(ctx0, res, ab_cur);
}
if (w_s) {
res = ggml_mul(ctx0, res, w_s);
}
return res;
}
+3 -2
View File
@@ -764,10 +764,11 @@ struct llm_graph_context {
ggml_tensor * cur,
int il) const;
// do mat_mul, while optionally apply lora
// do mat_mul, while optionally apply lora and per-tensor scale
ggml_tensor * build_lora_mm(
ggml_tensor * w,
ggml_tensor * cur) const;
ggml_tensor * cur,
ggml_tensor * w_s = nullptr) const;
// do mat_mul_id, while optionally apply lora
ggml_tensor * build_lora_mm_id(
+5 -20
View File
@@ -29,10 +29,7 @@ llm_build_bitnet::llm_build_bitnet(const llama_model & model, const llm_graph_pa
// self-attention
{
// compute Q and K and RoPE them
ggml_tensor * Qcur = build_lora_mm(model.layers[il].wq, cur);
if (model.layers[il].wq_s) {
Qcur = ggml_mul(ctx0, Qcur, model.layers[il].wq_s);
}
ggml_tensor * Qcur = build_lora_mm(model.layers[il].wq, cur, model.layers[il].wq_s);
cb(Qcur, "Qcur", il);
if (model.layers[il].bq) {
Qcur = ggml_add(ctx0, Qcur, model.layers[il].bq);
@@ -40,10 +37,7 @@ llm_build_bitnet::llm_build_bitnet(const llama_model & model, const llm_graph_pa
}
// B1.K
ggml_tensor * Kcur = build_lora_mm(model.layers[il].wk, cur);
if (model.layers[il].wk_s) {
Kcur = ggml_mul(ctx0, Kcur, model.layers[il].wk_s);
}
ggml_tensor * Kcur = build_lora_mm(model.layers[il].wk, cur, model.layers[il].wk_s);
cb(Kcur, "Kcur", il);
if (model.layers[il].bk) {
Kcur = ggml_add(ctx0, Kcur, model.layers[il].bk);
@@ -51,10 +45,7 @@ llm_build_bitnet::llm_build_bitnet(const llama_model & model, const llm_graph_pa
}
// B1.V
ggml_tensor * Vcur = build_lora_mm(model.layers[il].wv, cur);
if (model.layers[il].wv_s) {
Vcur = ggml_mul(ctx0, Vcur, model.layers[il].wv_s);
}
ggml_tensor * Vcur = build_lora_mm(model.layers[il].wv, cur, model.layers[il].wv_s);
cb(Vcur, "Vcur", il);
if (model.layers[il].bv) {
Vcur = ggml_add(ctx0, Vcur, model.layers[il].bv);
@@ -90,10 +81,7 @@ llm_build_bitnet::llm_build_bitnet(const llama_model & model, const llm_graph_pa
LLM_NORM_RMS, il);
cb(cur, "attn_sub_norm", il);
cur = build_lora_mm(model.layers[il].wo, cur);
if (model.layers[il].wo_s) {
cur = ggml_mul(ctx0, cur, model.layers[il].wo_s);
}
cur = build_lora_mm(model.layers[il].wo, cur, model.layers[il].wo_s);
if (model.layers[il].bo) {
cur = ggml_add(ctx0, cur, model.layers[il].bo);
}
@@ -127,10 +115,7 @@ llm_build_bitnet::llm_build_bitnet(const llama_model & model, const llm_graph_pa
LLM_NORM_RMS, il);
cb(cur, "ffn_sub_norm", il);
cur = build_lora_mm(model.layers[il].ffn_down, cur);
if (model.layers[il].ffn_down_s) {
cur = ggml_mul(ctx0, cur, model.layers[il].ffn_down_s);
}
cur = build_lora_mm(model.layers[il].ffn_down, cur, model.layers[il].ffn_down_s);
cb(cur, "ffn_down", il);
cur = ggml_add(ctx0, cur, ffn_inp);
+3 -12
View File
@@ -43,28 +43,19 @@ llm_build_llama<embed>::llm_build_llama(const llama_model & model, const llm_gra
ggml_tensor * rope_factors = model.get_rope_factors(cparams, il);
// compute Q and K and RoPE them
ggml_tensor * Qcur = build_lora_mm(model.layers[il].wq, cur);
if (model.layers[il].wq_s) {
Qcur = ggml_mul(ctx0, Qcur, model.layers[il].wq_s);
}
ggml_tensor * Qcur = build_lora_mm(model.layers[il].wq, cur, model.layers[il].wq_s);
cb(Qcur, "Qcur", il);
if (model.layers[il].bq) {
Qcur = ggml_add(ctx0, Qcur, model.layers[il].bq);
cb(Qcur, "Qcur", il);
}
ggml_tensor * Kcur = build_lora_mm(model.layers[il].wk, cur);
if (model.layers[il].wk_s) {
Kcur = ggml_mul(ctx0, Kcur, model.layers[il].wk_s);
}
ggml_tensor * Kcur = build_lora_mm(model.layers[il].wk, cur, model.layers[il].wk_s);
cb(Kcur, "Kcur", il);
if (model.layers[il].bk) {
Kcur = ggml_add(ctx0, Kcur, model.layers[il].bk);
cb(Kcur, "Kcur", il);
}
ggml_tensor * Vcur = build_lora_mm(model.layers[il].wv, cur);
if (model.layers[il].wv_s) {
Vcur = ggml_mul(ctx0, Vcur, model.layers[il].wv_s);
}
ggml_tensor * Vcur = build_lora_mm(model.layers[il].wv, cur, model.layers[il].wv_s);
cb(Vcur, "Vcur", il);
if (model.layers[il].bv) {
Vcur = ggml_add(ctx0, Vcur, model.layers[il].bv);
+3 -12
View File
@@ -30,22 +30,13 @@ llm_build_qwen3::llm_build_qwen3(const llama_model & model, const llm_graph_para
// self-attention
{
// compute Q and K and RoPE them
ggml_tensor * Qcur = build_lora_mm(model.layers[il].wq, cur);
if (model.layers[il].wq_s) {
Qcur = ggml_mul(ctx0, Qcur, model.layers[il].wq_s);
}
ggml_tensor * Qcur = build_lora_mm(model.layers[il].wq, cur, model.layers[il].wq_s);
cb(Qcur, "Qcur", il);
ggml_tensor * Kcur = build_lora_mm(model.layers[il].wk, cur);
if (model.layers[il].wk_s) {
Kcur = ggml_mul(ctx0, Kcur, model.layers[il].wk_s);
}
ggml_tensor * Kcur = build_lora_mm(model.layers[il].wk, cur, model.layers[il].wk_s);
cb(Kcur, "Kcur", il);
ggml_tensor * Vcur = build_lora_mm(model.layers[il].wv, cur);
if (model.layers[il].wv_s) {
Vcur = ggml_mul(ctx0, Vcur, model.layers[il].wv_s);
}
ggml_tensor * Vcur = build_lora_mm(model.layers[il].wv, cur, model.layers[il].wv_s);
cb(Vcur, "Vcur", il);
Qcur = ggml_reshape_3d(ctx0, Qcur, n_embd_head, n_head, n_tokens);
+3 -12
View File
@@ -30,22 +30,13 @@ llm_build_qwen3moe::llm_build_qwen3moe(const llama_model & model, const llm_grap
// self_attention
{
// compute Q and K and RoPE them
ggml_tensor * Qcur = build_lora_mm(model.layers[il].wq, cur);
if (model.layers[il].wq_s) {
Qcur = ggml_mul(ctx0, Qcur, model.layers[il].wq_s);
}
ggml_tensor * Qcur = build_lora_mm(model.layers[il].wq, cur, model.layers[il].wq_s);
cb(Qcur, "Qcur", il);
ggml_tensor * Kcur = build_lora_mm(model.layers[il].wk, cur);
if (model.layers[il].wk_s) {
Kcur = ggml_mul(ctx0, Kcur, model.layers[il].wk_s);
}
ggml_tensor * Kcur = build_lora_mm(model.layers[il].wk, cur, model.layers[il].wk_s);
cb(Kcur, "Kcur", il);
ggml_tensor * Vcur = build_lora_mm(model.layers[il].wv, cur);
if (model.layers[il].wv_s) {
Vcur = ggml_mul(ctx0, Vcur, model.layers[il].wv_s);
}
ggml_tensor * Vcur = build_lora_mm(model.layers[il].wv, cur, model.layers[il].wv_s);
cb(Vcur, "Vcur", il);
Qcur = ggml_reshape_3d(ctx0, Qcur, n_embd_head, n_head, n_tokens);
+36
View File
@@ -2765,6 +2765,42 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
.run();
}
// GigaChat V3
{
auto tst = peg_tester("models/templates/GigaChat3-10B-A1.8B.jinja", detailed_debug);
tst.test("Hello, world!\nWhat's up?").expect(message_assist).run();
tst.test("<|message_sep|>\n\nfunction call<|role_sep|>\n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}")
.tools({ special_function_tool })
.expect(message_assist_call)
.run();
tst.test(
"Hello, world!\nWhat's up?"
"<|message_sep|>\n\nfunction call<|role_sep|>\n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}"
)
.tools({ special_function_tool })
.expect(message_assist_call_content)
.run();
}
// GigaChat V3.1
{
auto tst = peg_tester("models/templates/GigaChat3.1-10B-A1.8B.jinja", detailed_debug);
tst.test("Hello, world!\nWhat's up?").expect(message_assist).run();
tst.test("<|function_call|>{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}")
.tools({ special_function_tool })
.expect(message_assist_call)
.run();
tst.test(
"Hello, world!\nWhat's up?"
"<|function_call|>{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}"
)
.tools({ special_function_tool })
.expect(message_assist_call_content)
.run();
}
}
// Test the developer role to system workaround with a simple mock template
+2
View File
@@ -216,6 +216,7 @@ enum projector_type {
PROJECTOR_TYPE_GEMMA3,
PROJECTOR_TYPE_GEMMA3NV,
PROJECTOR_TYPE_GEMMA3NA,
PROJECTOR_TYPE_PHI4,
PROJECTOR_TYPE_IDEFICS3,
PROJECTOR_TYPE_PIXTRAL,
PROJECTOR_TYPE_QWEN25VL,
@@ -253,6 +254,7 @@ static std::map<projector_type, std::string> PROJECTOR_TYPE_NAMES = {
{ PROJECTOR_TYPE_GEMMA3, "gemma3"},
{ PROJECTOR_TYPE_GEMMA3NV, "gemma3nv"},
{ PROJECTOR_TYPE_GEMMA3NA, "gemma3na"},
{ PROJECTOR_TYPE_PHI4, "phi4"},
{ PROJECTOR_TYPE_IDEFICS3, "idefics3"},
{ PROJECTOR_TYPE_PIXTRAL, "pixtral"},
{ PROJECTOR_TYPE_ULTRAVOX, "ultravox"},
+19
View File
@@ -792,6 +792,7 @@ static ggml_cgraph * clip_image_build_graph(clip_ctx * ctx, const clip_image_f32
case PROJECTOR_TYPE_IDEFICS3:
case PROJECTOR_TYPE_LFM2:
case PROJECTOR_TYPE_JANUS_PRO:
case PROJECTOR_TYPE_PHI4:
{
builder = std::make_unique<clip_graph_siglip>(ctx, img);
} break;
@@ -1144,6 +1145,13 @@ struct clip_model_loader {
// ref: https://huggingface.co/LiquidAI/LFM2.5-VL-1.6B/blob/main/processor_config.json
hparams.set_limit_image_tokens(64, 256);
} break;
case PROJECTOR_TYPE_PHI4:
{
hparams.n_merge = 1;
get_u32(KEY_IMAGE_MIN_PIXELS, hparams.image_min_pixels);
get_u32(KEY_IMAGE_MAX_PIXELS, hparams.image_max_pixels);
hparams.set_warmup_n_tokens(16*16);
} break;
case PROJECTOR_TYPE_PIXTRAL:
case PROJECTOR_TYPE_LIGHTONOCR:
{
@@ -1841,6 +1849,13 @@ struct clip_model_loader {
model.mm_1_w = get_tensor(string_format(TN_LLAVA_PROJ, 1, "weight"));
model.mm_1_b = get_tensor(string_format(TN_LLAVA_PROJ, 1, "bias"));
} break;
case PROJECTOR_TYPE_PHI4:
{
model.mm_0_w = get_tensor(string_format(TN_LLAVA_PROJ, 0, "weight"));
model.mm_0_b = get_tensor(string_format(TN_LLAVA_PROJ, 0, "bias"));
model.mm_2_w = get_tensor(string_format(TN_LLAVA_PROJ, 2, "weight"));
model.mm_2_b = get_tensor(string_format(TN_LLAVA_PROJ, 2, "bias"));
} break;
case PROJECTOR_TYPE_LFM2A:
{
for (int i : {0, 2, 3, 5, 6}) {
@@ -3157,6 +3172,7 @@ bool clip_image_preprocess(struct clip_ctx * ctx, const clip_image_u8 * img, str
res_imgs->entries.push_back(std::move(img_f32));
} break;
case PROJECTOR_TYPE_PHI4:
case PROJECTOR_TYPE_PIXTRAL:
case PROJECTOR_TYPE_LIGHTONOCR:
{
@@ -3383,6 +3399,7 @@ int clip_n_output_tokens(const struct clip_ctx * ctx, struct clip_image_f32 * im
case PROJECTOR_TYPE_MLP:
case PROJECTOR_TYPE_MLP_NORM:
case PROJECTOR_TYPE_JANUS_PRO:
case PROJECTOR_TYPE_PHI4:
{
// do nothing
} break;
@@ -3884,6 +3901,7 @@ bool clip_image_batch_encode(clip_ctx * ctx, const int n_threads, const clip_ima
case PROJECTOR_TYPE_VOXTRAL:
case PROJECTOR_TYPE_MUSIC_FLAMINGO:
case PROJECTOR_TYPE_JANUS_PRO:
case PROJECTOR_TYPE_PHI4:
case PROJECTOR_TYPE_COGVLM:
{
// do nothing
@@ -4013,6 +4031,7 @@ int clip_n_mmproj_embd(const struct clip_ctx * ctx) {
case PROJECTOR_TYPE_LDPV2:
return ctx->model.mm_model_peg_0_b->ne[0];
case PROJECTOR_TYPE_MLP:
case PROJECTOR_TYPE_PHI4:
case PROJECTOR_TYPE_PIXTRAL:
case PROJECTOR_TYPE_LIGHTONOCR:
return ctx->model.mm_2_w->ne[1];
+9 -1
View File
@@ -4,7 +4,7 @@ ggml_cgraph * clip_graph_siglip::build() {
ggml_tensor * inp = build_inp();
ggml_tensor * learned_pos_embd = model.position_embeddings;
if (proj_type == PROJECTOR_TYPE_LFM2) {
if (proj_type == PROJECTOR_TYPE_LFM2 || proj_type == PROJECTOR_TYPE_PHI4) {
learned_pos_embd = resize_position_embeddings();
}
@@ -75,6 +75,14 @@ ggml_cgraph * clip_graph_siglip::build() {
hparams.ffn_op,
-1);
} else if (proj_type == PROJECTOR_TYPE_PHI4) {
cur = build_ffn(cur,
model.mm_0_w, model.mm_0_b,
nullptr, nullptr,
model.mm_2_w, model.mm_2_b,
FFN_GELU,
-1);
} else {
GGML_ABORT("SigLIP: Unsupported projector type");
}
+3
View File
@@ -290,6 +290,9 @@ struct mtmd_context {
img_beg = "<|vision_start|>";
img_end = "<|vision_end|>";
} else if (proj == PROJECTOR_TYPE_PHI4) {
// Phi-4 uses media marker insertion only. Keep image boundary text empty.
} else if (proj == PROJECTOR_TYPE_LLAMA4) {
// (more details in mtmd_context constructor)
img_beg = "<|image_start|>";