mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-10-10 23:07:21 -05:00
Compare commits
14
Commits
xsn/json_patch
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23b0202a18 | ||
|
|
69f201a205 | ||
|
|
abee0c8476 | ||
|
|
aa94f20861 | ||
|
|
781dbc5ac9 | ||
|
|
0fd868cbca | ||
|
|
1623d8ce47 | ||
|
|
1bb2b9fcbe | ||
|
|
2bbca8f202 | ||
|
|
404f557b5b | ||
|
|
b797c82c7d | ||
|
|
f2918cabbf | ||
|
|
1e6f04a75e | ||
|
|
10a60cf303 |
@@ -45,6 +45,9 @@ insert_final_newline = unset
|
||||
trim_trailing_whitespace = unset
|
||||
insert_final_newline = unset
|
||||
|
||||
[vendor/**.patch]
|
||||
trim_trailing_whitespace = unset
|
||||
|
||||
[tools/ui/**]
|
||||
indent_style = unset
|
||||
indent_size = unset
|
||||
|
||||
+1
-1
@@ -3643,7 +3643,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
params.slot_save_path += DIRECTORY_SEPARATOR;
|
||||
}
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}));
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_SLOT_SAVE_PATH"));
|
||||
add_opt(common_arg(
|
||||
{"--media-path"}, "PATH",
|
||||
"directory for loading local media files; files can be accessed via file:// URLs using relative paths (default: disabled)",
|
||||
|
||||
+25
-2
@@ -1403,6 +1403,14 @@ std::vector<llama_adapter_lora_ptr> & common_init_result::lora() {
|
||||
return pimpl->lora;
|
||||
}
|
||||
|
||||
// only for warmup and probe decodes, fill zeros as dummy input
|
||||
static void common_batch_set_zero_state(common_batch & batch, const llama_model * model, std::vector<float> & zeros) {
|
||||
zeros.assign(llama_model_n_embd_out(model), 0.0f);
|
||||
for (int32_t i = 0; i < batch.size(); ++i) {
|
||||
batch.set_embd_state(i, { zeros.data(), 1, zeros.size() });
|
||||
}
|
||||
}
|
||||
|
||||
common_init_result_ptr common_init_from_params(common_params & params, bool model_only) {
|
||||
common_init_result_ptr res(new common_init_result(params, model_only));
|
||||
|
||||
@@ -1509,6 +1517,8 @@ common_init_result_ptr common_init_from_params(common_params & params, bool mode
|
||||
if (llama_model_has_decoder(model)) {
|
||||
tmp.resize(std::min(tmp.size(), (size_t) params.n_batch));
|
||||
common_batch batch = common_batch_get_one(lctx, tmp);
|
||||
std::vector<float> zeros;
|
||||
common_batch_set_zero_state(batch, model, zeros);
|
||||
llama_process(lctx, LLAMA_PROCESS_TYPE_DECODE, batch.get());
|
||||
}
|
||||
llama_memory_clear(llama_get_memory(lctx), true);
|
||||
@@ -1576,6 +1586,8 @@ common_context_seq_rm_type common_context_can_seq_rm(llama_context * ctx) {
|
||||
int ret;
|
||||
{
|
||||
common_batch batch = common_batch_get_one(ctx, tmp);
|
||||
std::vector<float> zeros;
|
||||
common_batch_set_zero_state(batch, llama_get_model(ctx), zeros);
|
||||
ret = llama_process(ctx, LLAMA_PROCESS_TYPE_DECODE, batch.get());
|
||||
}
|
||||
if (ret != 0) {
|
||||
@@ -2161,7 +2173,7 @@ void common_batch::clear() {
|
||||
}
|
||||
|
||||
int32_t common_batch::add(llama_token id, llama_pos pos, llama_seq_id seq_id, bool output) {
|
||||
tokens.push_back({ id, { pos, 0, 0, 0 }, seq_id, output, { nullptr, 0, 0 }, {} });
|
||||
tokens.push_back({ id, { pos, 0, 0, 0 }, seq_id, output, { nullptr, 0, 0 }, { nullptr, 0, 0 }, {} });
|
||||
return size() - 1;
|
||||
}
|
||||
|
||||
@@ -2199,8 +2211,16 @@ bool common_batch::set_embd(int32_t idx, llama_embd embd) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool common_batch::set_embd_state(int32_t idx, llama_embd state) {
|
||||
if (idx < 0 || idx >= size() || tokens[idx].state.data != nullptr) {
|
||||
return false;
|
||||
}
|
||||
tokens[idx].state = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t common_batch::add_embd(llama_embd embd, const llama_pos * pos, llama_seq_id seq_id, bool output) {
|
||||
token t = { LLAMA_TOKEN_NULL, { 0, 0, 0, 0 }, seq_id, output, embd, {} };
|
||||
token t = { LLAMA_TOKEN_NULL, { 0, 0, 0, 0 }, seq_id, output, embd, { nullptr, 0, 0 }, {} };
|
||||
for (int32_t j = 0; j < n_pos; ++j) {
|
||||
t.pos[j] = pos[j];
|
||||
}
|
||||
@@ -2245,6 +2265,9 @@ llama_batch_ext * common_batch::get_sub_batch(int32_t off, int32_t n) {
|
||||
if (t.output) {
|
||||
llama_batch_ext_set_output_logits(res, idx, true);
|
||||
}
|
||||
if (t.state.data) {
|
||||
llama_batch_ext_set_embd_state(res, idx, t.state); // contexts without a state input ignore it
|
||||
}
|
||||
if (t.decision_order != 0) {
|
||||
llama_batch_ext_set_decision_order(res, idx, (llama_decision_order) t.decision_order);
|
||||
}
|
||||
|
||||
@@ -1074,6 +1074,7 @@ struct common_batch {
|
||||
llama_seq_id seq_id; // the first sequence id, see add_seq()
|
||||
bool output;
|
||||
llama_embd embd; // non-owning view of the data passed to add_embd()/set_embd(), data == NULL if none
|
||||
llama_embd state; // non-owning view of the data passed to set_embd_state(), data == NULL if none
|
||||
std::vector<llama_seq_id> seq_ids_extra; // see add_seq()
|
||||
int32_t decision_order = 0; // see llama_batch_ext_set_decision_order()
|
||||
};
|
||||
@@ -1111,6 +1112,9 @@ struct common_batch {
|
||||
// attach a token embedding to the entry at idx, can only be set once per entry
|
||||
bool set_embd(int32_t idx, llama_embd embd);
|
||||
|
||||
// attach a state embedding (e.g. the target hidden state for MTP) to the entry at idx, can only be set once per entry
|
||||
bool set_embd_state(int32_t idx, llama_embd state);
|
||||
|
||||
// add an embedding-only entry (no token id)
|
||||
// pos points to n_pos positions
|
||||
int32_t add_embd(llama_embd embd, const llama_pos * pos, llama_seq_id seq_id, bool output);
|
||||
|
||||
+13
-9
@@ -1541,8 +1541,7 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: how to make it work with vision tokens?
|
||||
if (!batch_in.has_token() || batch_in.has_embd()) {
|
||||
if (!batch_in.has_token() && !batch_in.has_embd()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1581,15 +1580,20 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
|
||||
const float * h_tgt = llama_get_embeddings_nextn(ctx_tgt);
|
||||
|
||||
for (int k = 0; k < n_tokens; ++k) {
|
||||
const llama_seq_id seq_id = batch_in.tokens[k].seq_id;
|
||||
const auto & t = batch_in.tokens[k];
|
||||
|
||||
const int32_t idx = batch.add(batch_in.tokens[k].id, batch_in.tokens[k].pos[0], seq_id, false);
|
||||
const llama_seq_id seq_id = t.seq_id;
|
||||
|
||||
// vision tokens carry an embedding instead of an id
|
||||
const int32_t idx = t.id != LLAMA_TOKEN_NULL
|
||||
? batch.add(t.id, t.pos[0], seq_id, false)
|
||||
: batch.add_embd(t.embd, t.pos.data(), seq_id, false);
|
||||
|
||||
const float * h_row = k == i_batch_beg[seq_id]
|
||||
? pending_h[seq_id].data()
|
||||
: h_tgt + (size_t) (k - 1) * n_embd;
|
||||
|
||||
batch.set_embd(idx, { h_row, 1, (size_t) n_embd });
|
||||
batch.set_embd_state(idx, { h_row, 1, (size_t) n_embd });
|
||||
}
|
||||
|
||||
auto * mem_dft = llama_get_memory(ctx_dft);
|
||||
@@ -1679,7 +1683,7 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
|
||||
}
|
||||
|
||||
const int32_t idx = batch.add(dp.id_last, dp.pos0, seq_id, true);
|
||||
batch.set_embd(idx, { pending_h[seq_id].data(), 1, (size_t) n_embd });
|
||||
batch.set_embd_state(idx, { pending_h[seq_id].data(), 1, (size_t) n_embd });
|
||||
|
||||
i_last[seq_id] = idx;
|
||||
|
||||
@@ -1772,18 +1776,18 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
|
||||
for (int t = 0; t < n_rows; ++t) {
|
||||
const llama_token tok = (t == 0) ? dp.id_last : result[t - 1];
|
||||
const int32_t idx = batch.add(tok, dp.pos0 + t, seq_id, t == n_rows - 1);
|
||||
batch.set_embd(idx, { chain_h[seq_id].data() + (size_t) t * n_embd, 1, (size_t) n_embd });
|
||||
batch.set_embd_state(idx, { chain_h[seq_id].data() + (size_t) t * n_embd, 1, (size_t) n_embd });
|
||||
i_last[seq_id] = idx;
|
||||
}
|
||||
} else if (is_mem_shared) {
|
||||
// note: with shared memory (e.g. Gemma4 assistants) we use the same position for all draft tokens
|
||||
// ref: https://github.com/huggingface/transformers/blob/effde20942e3f82a1b97449f60b3a48c5ff96145/docs/source/en/model_doc/gemma4_assistant.md?plain=1#L36-L37
|
||||
const int32_t idx = batch.add(id, dp.pos0, seq_id, true);
|
||||
batch.set_embd(idx, { h_row, 1, (size_t) n_embd });
|
||||
batch.set_embd_state(idx, { h_row, 1, (size_t) n_embd });
|
||||
i_last[seq_id] = idx;
|
||||
} else {
|
||||
const int32_t idx = batch.add(id, dp.pos0 + i + 1, seq_id, true);
|
||||
batch.set_embd(idx, { h_row, 1, (size_t) n_embd });
|
||||
batch.set_embd_state(idx, { h_row, 1, (size_t) n_embd });
|
||||
i_last[seq_id] = idx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"MiniCPM3ForCausalLM": "minicpm",
|
||||
"MiniCPMForCausalLM": "minicpm",
|
||||
"MiniCPMV4_6ForConditionalGeneration": "minicpm",
|
||||
"MiniCPMV4_7ForConditionalGeneration": "minicpm",
|
||||
"MiniMaxText01ForCausalLM": "minimax",
|
||||
"MiniMaxM1ForCausalLM": "minimax",
|
||||
"MiniMaxM2ForCausalLM": "minimax",
|
||||
@@ -346,6 +347,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"MiMoV2ForCausalLM": "mimo",
|
||||
"MiniMaxM3SparseForConditionalGeneration": "minimax",
|
||||
"MiniCPMV4_6ForConditionalGeneration": "minicpm",
|
||||
"MiniCPMV4_7ForConditionalGeneration": "minicpm",
|
||||
"Mistral3ForConditionalGeneration": "llava",
|
||||
"NemotronH_Nano_VL_V2": "nemotron",
|
||||
"MuseGlimmerForConditionalGeneration": "muse_glimmer",
|
||||
|
||||
+5
-2
@@ -1389,14 +1389,17 @@ class TextModel(ModelBase):
|
||||
name, gen = item
|
||||
|
||||
# Skip multimodal tensors
|
||||
if name.startswith(("mlp", "vit.", "vpm.", "siglip2.", "conformer.", "merger.", "resampler.", "sound_encoder.", "sound_projection.", "speech_embeddings.")) \
|
||||
# strip the "model." wrapper so the prefixes below match (name is not returned)
|
||||
if name.startswith("model."):
|
||||
name = name[len("model."):]
|
||||
if name.startswith(("mlp", "vit.", "vpm.", "siglip2.", "conformer.", "connector.", "merger.", "resampler.", "sound_encoder.", "sound_projection.", "speech_embeddings.")) \
|
||||
or "visual." in name or "vision." in name or "audio." in name or "talker." in name \
|
||||
or "vision_" in name or "audio_" in name \
|
||||
or "token2wav." in name or "code2wav." in name \
|
||||
or "projector." in name or "pre_mm_projector_norm" in name \
|
||||
or "image_newline" in name or "view_seperator" in name \
|
||||
or "patch_embed" in name or "patch_embedding" in name \
|
||||
or "patch_merger." in name or "patch_merge_mlp." in name or "model.connector." in name:
|
||||
or "patch_merger." in name or "patch_merge_mlp." in name:
|
||||
return None
|
||||
|
||||
return super().filter_tensors(item)
|
||||
|
||||
+85
-4
@@ -139,9 +139,16 @@ class MiniCPMV4_6TextModel(Qwen3_5TextModel):
|
||||
@ModelBase.register("MiniCPMV4_6ForConditionalGeneration")
|
||||
@ModelBase.example("openbmb/MiniCPM-V-4_6")
|
||||
class MiniCPMV4_6VisionModel(MmprojModel):
|
||||
projector_type = gguf.VisionProjectorType.MINICPMV4_6
|
||||
# fallback for checkpoints whose preprocessor config omits `scale_resolution`
|
||||
default_scale_resolution: int | None = None
|
||||
|
||||
def get_downsample_mode(self) -> str:
|
||||
return self.preprocessor_config.get("downsample_mode", "16x")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.downsample_mode = self.preprocessor_config.get("downsample_mode", "16x")
|
||||
self.downsample_mode = self.get_downsample_mode()
|
||||
if self.downsample_mode not in {"4x", "16x"}:
|
||||
raise ValueError(f"Unsupported downsample mode: {self.downsample_mode}")
|
||||
if self.downsample_mode == "4x":
|
||||
@@ -157,7 +164,8 @@ class MiniCPMV4_6VisionModel(MmprojModel):
|
||||
# The CLIP loader in tools/mtmd/clip.cpp consumes `clip.vision.image_size`
|
||||
# as the slice size and warmup resolution, so report `scale_resolution` there
|
||||
# to match the upstream MiniCPMV4_6ImageProcessorPil slicing rules.
|
||||
scale_resolution = self.preprocessor_config.get("scale_resolution")
|
||||
scale_resolution = self.preprocessor_config.get(
|
||||
"scale_resolution", self.default_scale_resolution)
|
||||
if scale_resolution is not None:
|
||||
self.hparams_vision["image_size"] = int(scale_resolution)
|
||||
|
||||
@@ -166,12 +174,15 @@ class MiniCPMV4_6VisionModel(MmprojModel):
|
||||
assert self.hparams_vision is not None
|
||||
|
||||
# projector type string is consumed by clip_projector_type_from_string() in clip.cpp
|
||||
# (mapped to PROJECTOR_TYPE_MINICPMV4_6).
|
||||
self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.MINICPMV4_6)
|
||||
self.gguf_writer.add_clip_projector_type(self.projector_type)
|
||||
|
||||
self.gguf_writer.add_vision_projector_scale_factor(
|
||||
2 if self.downsample_mode == "4x" else 4)
|
||||
|
||||
max_slice_nums = self.preprocessor_config.get("max_slice_nums")
|
||||
if max_slice_nums is not None:
|
||||
self.gguf_writer.add_vision_max_slice_nums(int(max_slice_nums))
|
||||
|
||||
# borrow wa_layer_indexes for vit_merger insertion point
|
||||
insert_layer_id = int(self.global_config.get(
|
||||
"insert_layer_id", self.hparams_vision.get("insert_layer_id", 6)))
|
||||
@@ -191,3 +202,73 @@ class MiniCPMV4_6VisionModel(MmprojModel):
|
||||
return None
|
||||
|
||||
return super().filter_tensors(item)
|
||||
|
||||
|
||||
# MiniCPM-V 4.7 shares the v4.6 stack: the same Qwen3.5 text tower (MoE variant when the checkpoint says so) and the same SigLIP + vit_merger + merger vision tower.
|
||||
|
||||
@ModelBase.register("MiniCPMV4_7ForConditionalGeneration")
|
||||
@ModelBase.example("openbmb/MiniCPM-V-4.7")
|
||||
class MiniCPMV4_7TextModel(Qwen3_5TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.QWEN35
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
# mtmd puts the time of the image canvas in slot z, slot t stays the KV cache position
|
||||
self.gguf_writer.add_rope_section_order(gguf.RopeSectionOrder.ZYXT)
|
||||
|
||||
def __init__(self, dir_model, ftype, fname_out, *, hparams: dict | None = None, **kwargs):
|
||||
if hparams is None:
|
||||
hparams = ModelBase.load_hparams(dir_model, is_mistral_format=False)
|
||||
text_config = hparams.get("text_config", {})
|
||||
if text_config.get("model_type") == "qwen3_5_moe_text":
|
||||
self.model_arch = gguf.MODEL_ARCH.QWEN35MOE
|
||||
else:
|
||||
self.model_arch = gguf.MODEL_ARCH.QWEN35
|
||||
super().__init__(dir_model, ftype, fname_out, hparams=hparams, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
|
||||
# MTP tensors are not used yet
|
||||
if name.startswith("mtp"):
|
||||
return None
|
||||
|
||||
return super().filter_tensors(item)
|
||||
|
||||
|
||||
@ModelBase.register("MiniCPMV4_7ForConditionalGeneration")
|
||||
@ModelBase.example("openbmb/MiniCPM-V-4.7")
|
||||
class MiniCPMV4_7VisionModel(MiniCPMV4_6VisionModel):
|
||||
projector_type = gguf.VisionProjectorType.MINICPMV4_7
|
||||
# MiniCPMV4_7ImageProcessorPil default
|
||||
default_scale_resolution = 448
|
||||
# rows of v.tok_embd_sep, the order must match clip_suffix_rows() in clip-impl.h
|
||||
tok_embd_sep = ["</image>", "<slice>", "</slice>", "\n"]
|
||||
|
||||
def get_downsample_mode(self) -> str:
|
||||
# 4.7 moved downsample_mode to the model config; preprocessor value takes priority
|
||||
return self.preprocessor_config.get(
|
||||
"downsample_mode", self.global_config.get("downsample_mode", "16x"))
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
# keep the text tok_embd, the separator rows are taken from it in modify_tensors
|
||||
if item[0] == "model.language_model.embed_tokens.weight":
|
||||
return item
|
||||
return super().filter_tensors(item)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name == "model.language_model.embed_tokens.weight":
|
||||
# the tile separators are text tokens; clip appends their embeddings so that one chunk holds the whole image
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model)
|
||||
ids = []
|
||||
for text in self.tok_embd_sep:
|
||||
tok = tokenizer.encode(text, add_special_tokens=False)
|
||||
if len(tok) != 1:
|
||||
raise ValueError(f"separator {text!r} must be a single token, got {tok}")
|
||||
ids.append(tok[0])
|
||||
yield self.format_tensor_name(gguf.MODEL_TENSOR.V_TOK_EMBD_SEP, suffix=""), data_torch[ids]
|
||||
return
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
## MiniCPM-V 4.7
|
||||
|
||||
### Prepare models and code
|
||||
|
||||
Download [MiniCPM-V-4.7](https://huggingface.co/openbmb/MiniCPM-V-4.7) PyTorch model from huggingface to "MiniCPM-V-4.7" folder.
|
||||
|
||||
The model must be the standard `transformers` checkpoint (no `trust_remote_code` for the text and vision graph used here); the architecture in `config.json` is `MiniCPMV4_7ForConditionalGeneration` with a `qwen3_5_text` (or `qwen3_5_moe_text`) text model and a SigLIP-based vision tower plus a window-attention `vit_merger`, same as MiniCPM-V 4.6.
|
||||
|
||||
If the checkpoint ships no MTP weights, pass `--no-mtp` to skip the nextn layers.
|
||||
|
||||
### Build llama.cpp
|
||||
|
||||
If there are differences in usage, please refer to the official build [documentation](https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md)
|
||||
|
||||
Clone llama.cpp:
|
||||
```bash
|
||||
git clone https://github.com/ggml-org/llama.cpp
|
||||
cd llama.cpp
|
||||
```
|
||||
|
||||
Build llama.cpp using `CMake`:
|
||||
```bash
|
||||
cmake -B build
|
||||
cmake --build build --config Release
|
||||
```
|
||||
|
||||
|
||||
### Usage of MiniCPM-V 4.7
|
||||
|
||||
MiniCPM-V 4.7 is converted directly through `convert_hf_to_gguf.py`. The same script is invoked twice on the original Hugging Face directory: once to produce the language-model GGUF and once with `--mmproj` to produce the multimodal projector GGUF.
|
||||
|
||||
```bash
|
||||
# language model
|
||||
python ./convert_hf_to_gguf.py ../MiniCPM-V-4.7 --outfile ../MiniCPM-V-4.7/ggml-model-f16.gguf --no-mtp
|
||||
|
||||
# multimodal projector (vision tower + window-attention vit_merger + DownsampleMLP merger)
|
||||
python ./convert_hf_to_gguf.py ../MiniCPM-V-4.7 --mmproj --outfile ../MiniCPM-V-4.7/mmproj-model-f16.gguf
|
||||
|
||||
# optional: quantize to Q4_K_M
|
||||
./build/bin/llama-quantize ../MiniCPM-V-4.7/ggml-model-f16.gguf ../MiniCPM-V-4.7/ggml-model-Q4_K_M.gguf Q4_K_M
|
||||
```
|
||||
|
||||
The default projector merges 16x (4x4 patches into one token). To keep 4x more visual tokens, copy the model dir and set `"downsample_mode": "4x"` in the copy's `preprocessor_config.json` before running the `--mmproj` conversion; the loader reads `clip.vision.projector.scale_factor` to pick the graph.
|
||||
|
||||
|
||||
Inference on Linux or Mac
|
||||
```bash
|
||||
# run in single-turn mode
|
||||
./build/bin/llama-mtmd-cli -m ../MiniCPM-V-4.7/ggml-model-f16.gguf --mmproj ../MiniCPM-V-4.7/mmproj-model-f16.gguf -c 4096 --jinja --image xx.jpg -p "What is in the image?"
|
||||
|
||||
# run in conversation mode
|
||||
./build/bin/llama-mtmd-cli -m ../MiniCPM-V-4.7/ggml-model-Q4_K_M.gguf --mmproj ../MiniCPM-V-4.7/mmproj-model-f16.gguf --jinja
|
||||
```
|
||||
|
||||
The chat template enables thinking by default. Pass `--chat-template-kwargs '{"enable_thinking": false}'` to `llama-server` to turn it off.
|
||||
@@ -462,6 +462,8 @@ function(ggml_add_cpu_backend_variant tag_name)
|
||||
set(GGML_INTERNAL_${feat} ON)
|
||||
endforeach()
|
||||
elseif (GGML_SYSTEM_ARCH STREQUAL "s390x")
|
||||
set(GGML_NATIVE OFF)
|
||||
|
||||
foreach (feat VXE2 NNPA)
|
||||
set(GGML_INTERNAL_${feat} OFF)
|
||||
endforeach()
|
||||
@@ -569,6 +571,14 @@ if (GGML_CPU_ALL_VARIANTS)
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
ggml_add_cpu_backend_variant(z15 Z15 VXE2)
|
||||
ggml_add_cpu_backend_variant(z16 Z16 VXE2 NNPA)
|
||||
|
||||
# check if compiler supports "-march=z17" codename
|
||||
check_cxx_compiler_flag("-march=arch15" GGML_CXX_SUPPORTS_Z17)
|
||||
if (GGML_CXX_SUPPORTS_Z17)
|
||||
ggml_add_cpu_backend_variant(arch15 Z17 VXE2 NNPA)
|
||||
else()
|
||||
message(WARNING "Skipping z17 target: compiler must be GCC 15.1 and later")
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported s390x target OS: ${CMAKE_SYSTEM_NAME}")
|
||||
endif()
|
||||
|
||||
@@ -593,7 +593,12 @@ function(ggml_add_cpu_backend_variant_impl tag_name)
|
||||
foreach (ZHW RANGE 15 17)
|
||||
if(DEFINED GGML_INTERNAL_Z${ZHW})
|
||||
message(STATUS "z${ZHW} cross-compile target")
|
||||
list(APPEND ARCH_FLAGS -march=z${ZHW})
|
||||
if (ZHW EQUAL 17)
|
||||
# z17 is an alias of arch15, use the arch level for wider toolchain support
|
||||
list(APPEND ARCH_FLAGS -march=arch15)
|
||||
else()
|
||||
list(APPEND ARCH_FLAGS -march=z${ZHW})
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
@@ -390,17 +390,16 @@ typedef unsigned char uchar8x16_t __attribute__((vector_size(16)));
|
||||
typedef int8_t int8x16_t __attribute__((vector_size(16)));
|
||||
typedef int16_t int16x8_t __attribute__((vector_size(16)));
|
||||
typedef int32_t int32x4_t __attribute__((vector_size(16)));
|
||||
typedef int64_t int64x2_t __attribute__((vector_size(16)));
|
||||
|
||||
typedef uint8_t uint8x16_t __attribute__((vector_size(16)));
|
||||
typedef uint16_t uint16x8_t __attribute__((vector_size(16)));
|
||||
typedef uint32_t uint32x4_t __attribute__((vector_size(16)));
|
||||
typedef uint64_t uint64x2_t __attribute__((vector_size(16)));
|
||||
|
||||
typedef float float32x4_t __attribute__((vector_size(16)));
|
||||
typedef double double64x2_t __attribute__((vector_size(16)));
|
||||
|
||||
typedef signed long long long64x2_t __attribute__((vector_size(16)));
|
||||
typedef unsigned long long ulong64x2_t __attribute__((vector_size(16)));
|
||||
|
||||
typedef struct ggml_uint8x16x2_t {
|
||||
uint8x16_t val[2];
|
||||
} ggml_uint8x16x2_t;
|
||||
|
||||
@@ -3504,6 +3504,12 @@ void ggml_cpu_fp32_to_fp16(const float * x, ggml_fp16_t * y, int64_t n) {
|
||||
vfloat16m1_t vy = __riscv_vfncvt_f_f_w_f16m1(vx, vl);
|
||||
__riscv_vse16_v_f16m1((_Float16 *)&y[i], vy, vl);
|
||||
}
|
||||
#elif defined(__VXE__) || defined(__VXE2__)
|
||||
for (; i + 7 < n; i += 8) {
|
||||
const uint32x4_t v_yl = __lzs_f32cx4_to_f16(vec_xl(0, x + i + 0));
|
||||
const uint32x4_t v_yh = __lzs_f32cx4_to_f16(vec_xl(0, x + i + 4));
|
||||
vec_xst(vec_pack(v_yl, v_yh), 0, (uint16_t *)(y + i));
|
||||
}
|
||||
#endif
|
||||
for (; i < n; ++i) {
|
||||
y[i] = GGML_CPU_FP32_TO_FP16(x[i]);
|
||||
|
||||
@@ -1223,6 +1223,24 @@ static inline void __lsx_f16x4_store(ggml_fp16_t * x, __m128 y) {
|
||||
#define GGML_F16_STEP GGML_F32_STEP
|
||||
#define GGML_F16_EPR GGML_F32_EPR
|
||||
|
||||
static inline uint32x4_t __lzs_f32cx4_to_f16(float32x4_t v_f) {
|
||||
float32x4_t v_base = vec_mul(vec_mul(vec_abs(v_f), vec_splats(0x1.0p+112f)), vec_splats(0x1.0p-110f));
|
||||
|
||||
const uint32x4_t v_w = (uint32x4_t)v_f;
|
||||
const uint32x4_t v_shl1_w = vec_add(v_w, v_w);
|
||||
const uint32x4_t v_sign = vec_and(v_w, vec_splats(UINT32_C(0x80000000)));
|
||||
const uint32x4_t v_bias = vec_max(vec_and(v_shl1_w, vec_splats(UINT32_C(0xFF000000))), vec_splats(UINT32_C(0x71000000)));
|
||||
|
||||
v_base = vec_add((float32x4_t)vec_add(vec_sr(v_bias, 1), vec_splats(UINT32_C(0x07800000))), v_base);
|
||||
|
||||
const uint32x4_t v_bits = (uint32x4_t)v_base;
|
||||
const uint32x4_t v_nonsign = vec_add(vec_and(vec_sr(v_bits, 13), vec_splats(UINT32_C(0x00007C00))),
|
||||
vec_and(v_bits, vec_splats(UINT32_C(0x00000FFF))));
|
||||
const uint32x4_t v_is_nan = (uint32x4_t)vec_cmpgt(v_shl1_w, vec_splats(UINT32_C(0xFF000000)));
|
||||
|
||||
return vec_or(vec_sr(v_sign, 16), vec_sel(v_nonsign, vec_splats(UINT32_C(0x7E00)), v_is_nan));
|
||||
}
|
||||
|
||||
static inline float32x4_t __lzs_f16cx4_load(const ggml_fp16_t * x) {
|
||||
float tmp[4];
|
||||
|
||||
@@ -1236,15 +1254,9 @@ static inline float32x4_t __lzs_f16cx4_load(const ggml_fp16_t * x) {
|
||||
}
|
||||
|
||||
static inline void __lzs_f16cx4_store(ggml_fp16_t * x, float32x4_t v_y) {
|
||||
float arr[4];
|
||||
|
||||
// note: keep type-cast here to prevent compiler bugs
|
||||
// see: https://github.com/ggml-org/llama.cpp/issues/12846
|
||||
vec_xst(v_y, 0, (float *)(arr));
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
x[i] = GGML_CPU_FP32_TO_FP16(arr[i]);
|
||||
}
|
||||
const uint32x4_t v_h = __lzs_f32cx4_to_f16(v_y);
|
||||
const uint64_t tmp = ((uint64x2_t)vec_pack(v_h, v_h))[0];
|
||||
memcpy(x, &tmp, sizeof(tmp));
|
||||
}
|
||||
|
||||
#define GGML_F16_VEC GGML_F32x4
|
||||
|
||||
@@ -1772,7 +1772,7 @@ static __device__ __forceinline__ void flash_attn_ext_f16_process_tile(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (np > 1) {
|
||||
if (np > 1 || nbatch_combine != DKQ/2) {
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,9 +283,9 @@ static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_rdna4(ggml_type
|
||||
|
||||
// Host function: returns the max batch size for the current arch+type at runtime.
|
||||
int get_mmvq_mmid_max_batch(ggml_type type, int cc) {
|
||||
// NVIDIA: Volta, Ada Lovelace, and Blackwell always use MMVQ for MUL_MAT_ID.
|
||||
// NVIDIA: P100, Volta, Ada Lovelace, and Blackwell always use MMVQ for MUL_MAT_ID.
|
||||
if (GGML_CUDA_CC_IS_NVIDIA(cc)) {
|
||||
if (cc == GGML_CUDA_CC_VOLTA || cc >= GGML_CUDA_CC_ADA_LOVELACE) {
|
||||
if (cc == GGML_CUDA_CC_PASCAL || cc == GGML_CUDA_CC_VOLTA || cc >= GGML_CUDA_CC_ADA_LOVELACE) {
|
||||
return MMVQ_MAX_BATCH_SIZE;
|
||||
}
|
||||
if (cc >= GGML_CUDA_CC_TURING) {
|
||||
@@ -440,7 +440,7 @@ static constexpr __device__ int get_mmvq_mmid_max_batch_for_device() {
|
||||
return get_mmvq_mmid_max_batch_cdna(type);
|
||||
#elif defined(GCN)
|
||||
return get_mmvq_mmid_max_batch_gcn(type);
|
||||
#elif !defined(GGML_USE_MUSA) && (__CUDA_ARCH__ == GGML_CUDA_CC_VOLTA || __CUDA_ARCH__ >= GGML_CUDA_CC_ADA_LOVELACE)
|
||||
#elif !defined(GGML_USE_MUSA) && (__CUDA_ARCH__ == GGML_CUDA_CC_PASCAL || __CUDA_ARCH__ == GGML_CUDA_CC_VOLTA || __CUDA_ARCH__ >= GGML_CUDA_CC_ADA_LOVELACE)
|
||||
return MMVQ_MAX_BATCH_SIZE;
|
||||
#elif !defined(GGML_USE_MUSA) && __CUDA_ARCH__ >= GGML_CUDA_CC_TURING
|
||||
return get_mmvq_mmid_max_batch_turing_plus(type);
|
||||
|
||||
@@ -1024,6 +1024,8 @@ struct ggml_backend_opencl_context {
|
||||
cl_kernel kernel_gemm_moe_q4_0_q8_1_dp4a = nullptr; // dp4a (int8) q4_0 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_mxfp4_q8_1_dp4a_bin = nullptr; // binary dp4a (int8) mxfp4 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_q4_0_q8_1_dp4a_bin = nullptr; // binary dp4a (int8) q4_0 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_q4_k_q8_1_dp4a_bin = nullptr; // binary dp4a (int8) q4_k MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_q6_k_q8_1_dp4a_bin = nullptr; // binary dp4a (int8) q6_k MoE prefill GEMM
|
||||
cl_kernel kernel_moe_reorder_b;
|
||||
cl_kernel kernel_moe_histogram, kernel_moe_scan, kernel_moe_fill, kernel_moe_scatter;
|
||||
cl_kernel kernel_moe_scatter_stable = nullptr; // deterministic slot assignment
|
||||
@@ -4830,6 +4832,24 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
|
||||
// gemm_moe_q4_k_q8_1_dp4a_bin (dp4a prefill GEMM)
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
size_t bin_size = 0;
|
||||
backend_ctx->kernel_gemm_moe_q4_k_q8_1_dp4a_bin = nullptr;
|
||||
|
||||
if (use_adreno_bin_kernels(backend_ctx)) {
|
||||
const char * kernel_bin = (const char *)backend_ctx->get_adreno_bin_kernel("gemm_moe_q4_k_q8_1_dp4a_ila", &bin_size);
|
||||
if (kernel_bin && bin_size > 0) {
|
||||
cl_program prog =
|
||||
build_program_from_binary(backend_ctx->context, backend_ctx->device, kernel_bin, CL_moe_compile_opts, bin_size);
|
||||
|
||||
CL_CHECK((backend_ctx->kernel_gemm_moe_q4_k_q8_1_dp4a_bin = clCreateKernel(prog, "kernel_gemm_moe_q4_k_q8_1_dp4a_ila", &err), err));
|
||||
CL_CHECK(clReleaseProgram(prog));
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gemm_moe_mxfp4_q8_1_dp4a (dp4a prefill GEMM)
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
@@ -5049,6 +5069,24 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
|
||||
// gemm_moe_q6_k_q8_1_dp4a_bin (dp4a prefill GEMM)
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
size_t bin_size = 0;
|
||||
backend_ctx->kernel_gemm_moe_q6_k_q8_1_dp4a_bin = nullptr;
|
||||
|
||||
if (use_adreno_bin_kernels(backend_ctx)) {
|
||||
const char * kernel_bin = (const char *)backend_ctx->get_adreno_bin_kernel("gemm_moe_q6_k_q8_1_dp4a_ila", &bin_size);
|
||||
if (kernel_bin && bin_size > 0) {
|
||||
cl_program prog =
|
||||
build_program_from_binary(backend_ctx->context, backend_ctx->device, kernel_bin, CL_moe_compile_opts, bin_size);
|
||||
|
||||
CL_CHECK((backend_ctx->kernel_gemm_moe_q6_k_q8_1_dp4a_bin = clCreateKernel(prog, "kernel_gemm_moe_q6_k_q8_1_dp4a_ila", &err), err));
|
||||
CL_CHECK(clReleaseProgram(prog));
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gemv_moe_mxfp4_f32_ns
|
||||
{
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
@@ -27185,8 +27223,10 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E || backend_ctx->adreno_gen == ADRENO_GPU_GEN::X1E);
|
||||
// dot prod has to be available
|
||||
use_moe_dp4a = backend_ctx->has_integer_dot && use_moe_dp4a;
|
||||
// bin kernel takes precedence
|
||||
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_q4_k_f32_ns_bin == nullptr;
|
||||
// bin kernel takes precedence, dp4a bin kernel has higher priority than normal bin kernel
|
||||
if (backend_ctx->kernel_gemm_moe_q4_k_q8_1_dp4a_bin == nullptr) {
|
||||
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_q4_k_f32_ns_bin == nullptr;
|
||||
}
|
||||
|
||||
cl_buffer_region region;
|
||||
region.origin = 0;
|
||||
@@ -27288,6 +27328,10 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
|
||||
// dp4a GEMM
|
||||
cl_kernel dk = backend_ctx->kernel_gemm_moe_q4_k_q8_1_dp4a;
|
||||
if (backend_ctx->kernel_gemm_moe_q4_k_q8_1_dp4a_bin) {
|
||||
dk = backend_ctx->kernel_gemm_moe_q4_k_q8_1_dp4a_bin;
|
||||
}
|
||||
|
||||
int aidx = 0;
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &extra0_q4_K->q_img));
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &extra0_q4_K->d));
|
||||
@@ -27695,8 +27739,10 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
|| backend_ctx->adreno_gen == ADRENO_GPU_GEN::X1E);
|
||||
// dot prod has to be available
|
||||
use_moe_dp4a = backend_ctx->has_integer_dot && use_moe_dp4a;
|
||||
// bin kernel takes precedence
|
||||
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_q6_k_f32_ns_bin == nullptr;
|
||||
// bin kernel takes precedence, dp4a bin kernel has higher priority than normal bin kernel
|
||||
if (backend_ctx->kernel_gemm_moe_q6_k_q8_1_dp4a_bin == nullptr) {
|
||||
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_q6_k_f32_ns_bin == nullptr;
|
||||
}
|
||||
|
||||
cl_buffer_region region;
|
||||
region.origin = 0;
|
||||
@@ -27798,6 +27844,10 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
backend_ctx->enqueue_ndrange_kernel(rq, 2, rq_global, rq_local, dst);
|
||||
|
||||
cl_kernel dk = backend_ctx->kernel_gemm_moe_q6_k_q8_1_dp4a;
|
||||
if (backend_ctx->kernel_gemm_moe_q6_k_q8_1_dp4a_bin) {
|
||||
dk = backend_ctx->kernel_gemm_moe_q6_k_q8_1_dp4a_bin;
|
||||
}
|
||||
|
||||
int qi = 0;
|
||||
CL_CHECK(clSetKernelArg(dk, qi++, sizeof(cl_mem), &extra0_q6_K->ql_img));
|
||||
CL_CHECK(clSetKernelArg(dk, qi++, sizeof(cl_mem), &extra0_q6_K->qh));
|
||||
|
||||
@@ -537,6 +537,18 @@ static void dequantize_row_mxfp4_sycl(const void * vx, dst_t * y, const int64_t
|
||||
});
|
||||
}
|
||||
|
||||
template <typename dst_t>
|
||||
static void dequantize_row_mxfp4_sycl_reorder(const void * vx, dst_t * y, const int64_t k, dpct::queue_ptr stream) {
|
||||
GGML_ASSERT(k % QK_MXFP4 == 0);
|
||||
const int n_warp = (k / QK_MXFP4 + WARP_SIZE - 1) / WARP_SIZE;
|
||||
stream->parallel_for(
|
||||
sycl::nd_range<3>(sycl::range<3>(1, 1, n_warp) * sycl::range<3>(1, 1, WARP_SIZE),
|
||||
sycl::range<3>(1, 1, WARP_SIZE)),
|
||||
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]] {
|
||||
dequantize_block_mxfp4_reorder(vx, y, k, item_ct1);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename dst_t>
|
||||
static void dequantize_row_nvfp4_sycl(const void * vx, dst_t * y, const int64_t k, dpct::queue_ptr stream) {
|
||||
GGML_ASSERT(k % QK_NVFP4 == 0);
|
||||
@@ -728,6 +740,9 @@ to_fp16_sycl_t ggml_get_to_fp16_sycl(ggml_type type, ggml_tensor * dst) {
|
||||
case GGML_TYPE_IQ4_NL:
|
||||
return dequantize_row_iq4_nl_sycl;
|
||||
case GGML_TYPE_MXFP4:
|
||||
if (dst->src[0]->extra && ((ggml_tensor_extra_gpu *) dst->src[0]->extra)->optimized_feature.reorder) {
|
||||
return dequantize_row_mxfp4_sycl_reorder;
|
||||
}
|
||||
return dequantize_row_mxfp4_sycl;
|
||||
case GGML_TYPE_NVFP4:
|
||||
return dequantize_row_nvfp4_sycl;
|
||||
@@ -819,6 +834,9 @@ to_fp32_sycl_t ggml_get_to_fp32_sycl(ggml_type type, ggml_tensor *dst) {
|
||||
case GGML_TYPE_IQ4_NL:
|
||||
return dequantize_row_iq4_nl_sycl;
|
||||
case GGML_TYPE_MXFP4:
|
||||
if (dst->src[0]->extra && ((ggml_tensor_extra_gpu *) dst->src[0]->extra)->optimized_feature.reorder) {
|
||||
return dequantize_row_mxfp4_sycl_reorder;
|
||||
}
|
||||
return dequantize_row_mxfp4_sycl;
|
||||
case GGML_TYPE_NVFP4:
|
||||
return dequantize_row_nvfp4_sycl;
|
||||
|
||||
@@ -1646,6 +1646,26 @@ static void dequantize_block_mxfp4(const void * __restrict__ vx, dst_t * __restr
|
||||
}
|
||||
}
|
||||
|
||||
// Reordered MXFP4 ([qs...][e...], see ggml_sycl_reordered::block_q_t<MXFP4>): one work-item per block.
|
||||
template <typename dst_t>
|
||||
static void dequantize_block_mxfp4_reorder(const void * __restrict__ vx, dst_t * __restrict__ yy, int64_t k,
|
||||
const sycl::nd_item<3> & item_ct1) {
|
||||
const int64_t ib = (int64_t) item_ct1.get_group(2) * WARP_SIZE + item_ct1.get_local_id(2);
|
||||
if (ib >= k / QK_MXFP4) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t * qs = (const uint8_t *) vx + ib * (QK_MXFP4 / 2);
|
||||
const float d = ggml_sycl_e8m0_to_fp32(((const uint8_t *) vx)[k / 2 + ib]) * 0.5f;
|
||||
dst_t * y = yy + ib * QK_MXFP4;
|
||||
|
||||
#pragma unroll
|
||||
for (int j = 0; j < QK_MXFP4 / 2; ++j) {
|
||||
y[j] = d * kvalues_mxfp4[qs[j] & 0xf];
|
||||
y[j + QK_MXFP4 / 2] = d * kvalues_mxfp4[qs[j] >> 4];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename dst_t>
|
||||
static void dequantize_block_nvfp4(
|
||||
|
||||
@@ -773,7 +773,8 @@ ggml_backend_sycl_buffer_init_tensor(ggml_backend_buffer_t buffer,
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q4_K:
|
||||
case GGML_TYPE_Q5_K:
|
||||
case GGML_TYPE_Q6_K:{
|
||||
case GGML_TYPE_Q6_K:
|
||||
case GGML_TYPE_MXFP4:{
|
||||
ggml_tensor_extra_gpu * extra = new ggml_tensor_extra_gpu{};
|
||||
tensor->extra = extra;
|
||||
ctx->tensor_extras.push_back(extra);
|
||||
@@ -4623,6 +4624,58 @@ static bool reorder_qw_q6_k_moe(uint8_t * data_device, size_t expert_bytes, int6
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reorder each MXFP4 expert slice into [qs][e]: 16-byte nibble blocks, then one E8M0 byte per block.
|
||||
// Experts are self-contained, so the tensor is reordered a few experts at a time through a small
|
||||
// temporary: a whole-tensor temporary (hundreds of MB) can exceed the VRAM left on a nearly full card,
|
||||
// and on Windows the driver then pages device memory out to host RAM instead of failing.
|
||||
static bool reorder_qw_mxfp4_moe(uint8_t * data_device, size_t expert_bytes, int64_t n_expert, dpct::queue_ptr stream) {
|
||||
GGML_ASSERT(expert_bytes % sizeof(block_mxfp4) == 0);
|
||||
const int blocks_per_expert = (int) (expert_bytes / sizeof(block_mxfp4));
|
||||
const size_t max_chunk_bytes = 32u << 20;
|
||||
const int64_t chunk_experts = std::max<int64_t>(1, std::min<int64_t>(n_expert, (int64_t) (max_chunk_bytes / expert_bytes)));
|
||||
|
||||
sycl_reorder_temp_buffer tmp(stream, (size_t) chunk_experts * expert_bytes);
|
||||
if (!tmp) {
|
||||
GGML_LOG_WARN("%s: failed to allocate %zu bytes for reorder temp buffer, skipping reorder\n", __func__,
|
||||
(size_t) chunk_experts * expert_bytes);
|
||||
return false;
|
||||
}
|
||||
uint8_t * tmp_buf = static_cast<uint8_t *>(tmp.ptr);
|
||||
|
||||
// the queue is in-order: each chunk's copy into tmp_buf waits for the previous chunk's kernel
|
||||
for (int64_t e0 = 0; e0 < n_expert; e0 += chunk_experts) {
|
||||
const int64_t n_chunk = std::min(chunk_experts, n_expert - e0);
|
||||
uint8_t * chunk = data_device + (size_t) e0 * expert_bytes;
|
||||
|
||||
sycl::event copy_event;
|
||||
SYCL_CHECK(CHECK_TRY_ERROR(copy_event = stream->memcpy(tmp_buf, chunk, (size_t) n_chunk * expert_bytes)));
|
||||
if (!g_ggml_sycl_use_async_mem_op) {
|
||||
copy_event.wait();
|
||||
}
|
||||
|
||||
const int total_blocks = blocks_per_expert * (int) n_chunk;
|
||||
auto reorder_event = stream->parallel_for(total_blocks, [=](auto gb_) {
|
||||
const int gb = gb_;
|
||||
const int e = gb / blocks_per_expert;
|
||||
const int ib = gb % blocks_per_expert;
|
||||
const block_mxfp4 * x = (const block_mxfp4 *) (tmp_buf + (size_t) e * expert_bytes);
|
||||
uint8_t * base = chunk + (size_t) e * expert_bytes;
|
||||
|
||||
uint8_t * qs_ptr = base;
|
||||
uint8_t * e_ptr = qs_ptr + (QK_MXFP4 / 2) * (size_t) blocks_per_expert;
|
||||
|
||||
for (int j = 0; j < QK_MXFP4 / 2; ++j) {
|
||||
qs_ptr[(size_t) ib * (QK_MXFP4 / 2) + j] = x[ib].qs[j];
|
||||
}
|
||||
e_ptr[ib] = x[ib].e;
|
||||
});
|
||||
if (!g_ggml_sycl_use_async_mem_op) {
|
||||
reorder_event.wait_and_throw();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool reorder_qw_q2_k(uint8_t * data_device, size_t size, size_t offset, dpct::queue_ptr stream) {
|
||||
GGML_ASSERT(size % sizeof(block_q2_K) == 0);
|
||||
GGML_ASSERT(offset % sizeof(block_q2_K) == 0);
|
||||
@@ -4832,6 +4885,8 @@ static bool reorder_qw(const ggml_tensor * src0, dpct::queue_ptr stream) {
|
||||
return reorder_qw_q5_k_moe(data_device, src0->nb[2], src0->ne[2], stream);
|
||||
case GGML_TYPE_Q6_K:
|
||||
return reorder_qw_q6_k_moe(data_device, src0->nb[2], src0->ne[2], stream);
|
||||
case GGML_TYPE_MXFP4:
|
||||
return reorder_qw_mxfp4_moe(data_device, src0->nb[2], src0->ne[2], stream);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -4905,7 +4960,12 @@ static void opt_for_reorder_id(ggml_backend_sycl_context * ctx, const ggml_tenso
|
||||
if (!g_ggml_sycl_enable_optimize || !ctx->opt_feature.reorder) {
|
||||
return;
|
||||
}
|
||||
if (src0->type != GGML_TYPE_Q4_K && src0->type != GGML_TYPE_Q5_K && src0->type != GGML_TYPE_Q6_K) {
|
||||
if (src0->type != GGML_TYPE_Q4_K && src0->type != GGML_TYPE_Q5_K && src0->type != GGML_TYPE_Q6_K &&
|
||||
src0->type != GGML_TYPE_MXFP4) {
|
||||
return;
|
||||
}
|
||||
// The MXFP4 reorder kernels use 8-byte vector loads, so every expert slice must stay aligned.
|
||||
if (src0->type == GGML_TYPE_MXFP4 && (src0->nb[2] % 16 != 0 || (uintptr_t) src0->data % 16 != 0)) {
|
||||
return;
|
||||
}
|
||||
ggml_tensor_extra_gpu * extra = static_cast<ggml_tensor_extra_gpu *>(src0->extra);
|
||||
@@ -5388,6 +5448,11 @@ static void ggml_sycl_mul_mat_id(ggml_backend_sycl_context & ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// The per-expert loop below reads the experts in whatever layout they have: reorder MXFP4 here as well, so prompt processing does not depend on a single-token decode having run first.
|
||||
if (src0->type == GGML_TYPE_MXFP4) {
|
||||
opt_for_reorder_id(&ctx, src0);
|
||||
}
|
||||
|
||||
std::vector<char> ids_host(ggml_nbytes(ids));
|
||||
const char * ids_dev = (const char *) ids->data;
|
||||
|
||||
|
||||
@@ -1285,6 +1285,65 @@ static void reorder_mul_mat_vec_q8_0_q8_1_sycl_switch_ncols(
|
||||
}
|
||||
}
|
||||
|
||||
// MXFP4 reorder GEMV. Only MoE expert slices are reordered (opt_for_reorder_id); these dense entry
|
||||
// points serve per-expert ggml_sycl_mul_mat calls from multi-token MUL_MAT_ID after that reorder.
|
||||
static void reorder_mul_mat_vec_mxfp4_q8_1_sycl(const void * vx, const void * vy, float * dst, const int ncols,
|
||||
const int nrows, dpct::queue_ptr stream) {
|
||||
GGML_ASSERT(ncols % QK_MXFP4 == 0);
|
||||
constexpr size_t num_subgroups = WARP_SIZE;
|
||||
const int block_num_y = ceil_div(nrows, GGML_SYCL_MMV_Y * (int) num_subgroups);
|
||||
const sycl::range<3> block_nums(1, 1, block_num_y);
|
||||
const sycl::range<3> block_dims(1, GGML_SYCL_MMV_Y, num_subgroups * WARP_SIZE);
|
||||
|
||||
stream->submit([&](sycl::handler & cgh) {
|
||||
cgh.parallel_for(sycl::nd_range<3>(block_nums * block_dims, block_dims),
|
||||
[=](sycl::nd_item<3> nd_item) [[sycl::reqd_sub_group_size(WARP_SIZE)]] {
|
||||
mul_mat_vec_q_reorder<reorder_vec_dot_q_sycl<GGML_TYPE_MXFP4>>(vx, vy, dst, ncols, nrows,
|
||||
nd_item);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <int ncols_dst>
|
||||
static void reorder_mul_mat_vec_mxfp4_q8_1_sycl_ncols(
|
||||
const void * vx, const void * vy, float * dst,
|
||||
const int ncols, const int nrows,
|
||||
const int stride_col_y_bytes, const int stride_col_dst,
|
||||
dpct::queue_ptr stream) {
|
||||
GGML_ASSERT(ncols % QK_MXFP4 == 0);
|
||||
constexpr size_t num_subgroups = WARP_SIZE;
|
||||
const int block_num_y = ceil_div(nrows, GGML_SYCL_MMV_Y * (int) num_subgroups);
|
||||
const sycl::range<3> block_nums(1, 1, block_num_y);
|
||||
const sycl::range<3> block_dims(1, GGML_SYCL_MMV_Y, num_subgroups * WARP_SIZE);
|
||||
|
||||
stream->submit([&](sycl::handler & cgh) {
|
||||
cgh.parallel_for(sycl::nd_range<3>(block_nums * block_dims, block_dims),
|
||||
[=](sycl::nd_item<3> nd_item) [[sycl::reqd_sub_group_size(WARP_SIZE)]] {
|
||||
mul_mat_vec_q_reorder_ncols<reorder_vec_dot_q_sycl<GGML_TYPE_MXFP4>, ncols_dst>(
|
||||
vx, /*vgate=*/ nullptr, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst,
|
||||
/*glu_op=*/ GGML_GLU_OP_SWIGLU, nd_item);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static void reorder_mul_mat_vec_mxfp4_q8_1_sycl_switch_ncols(
|
||||
const void * vx, const void * vy, float * dst,
|
||||
const int ncols, const int nrows, const int ncols_dst,
|
||||
const int stride_col_y_bytes, const int stride_col_dst,
|
||||
dpct::queue_ptr stream) {
|
||||
switch (ncols_dst) {
|
||||
case 1: reorder_mul_mat_vec_mxfp4_q8_1_sycl(vx, vy, dst, ncols, nrows, stream); break;
|
||||
case 2: reorder_mul_mat_vec_mxfp4_q8_1_sycl_ncols<2>(vx, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, stream); break;
|
||||
case 3: reorder_mul_mat_vec_mxfp4_q8_1_sycl_ncols<3>(vx, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, stream); break;
|
||||
case 4: reorder_mul_mat_vec_mxfp4_q8_1_sycl_ncols<4>(vx, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, stream); break;
|
||||
case 5: reorder_mul_mat_vec_mxfp4_q8_1_sycl_ncols<5>(vx, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, stream); break;
|
||||
case 6: reorder_mul_mat_vec_mxfp4_q8_1_sycl_ncols<6>(vx, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, stream); break;
|
||||
case 7: reorder_mul_mat_vec_mxfp4_q8_1_sycl_ncols<7>(vx, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, stream); break;
|
||||
case 8: reorder_mul_mat_vec_mxfp4_q8_1_sycl_ncols<8>(vx, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, stream); break;
|
||||
default: GGML_ABORT("unsupported ncols_dst=%d for MXFP4 reorder multi-col MMVQ", ncols_dst);
|
||||
}
|
||||
}
|
||||
|
||||
static void mul_mat_vec_q8_0_q8_1_sycl(const void *vx, const void *vy,
|
||||
float *dst, const int ncols,
|
||||
const int nrows,
|
||||
@@ -2765,7 +2824,21 @@ void ggml_sycl_op_mul_mat_vec_q(ggml_backend_sycl_context & ctx, const ggml_tens
|
||||
}
|
||||
break;
|
||||
case GGML_TYPE_MXFP4:
|
||||
if (i == 0 && src1_ncols > 1 && src1_ncols <= 8) {
|
||||
if ((ggml_tensor_extra_gpu *) dst->src[0]->extra &&
|
||||
((ggml_tensor_extra_gpu *) dst->src[0]->extra)->optimized_feature.reorder) {
|
||||
if (i == 0 && src1_ncols > 1 && src1_ncols <= 8) {
|
||||
const int stride_col_y_bytes = src1_padded_col_size * q8_1_ts / q8_1_bs;
|
||||
const int stride_col_dst = dst->ne[0];
|
||||
GGML_SYCL_DEBUG("Calling reorder_mul_mat_vec_mxfp4_q8_1_sycl_switch_ncols ncols=%d\n", (int)src1_ncols);
|
||||
reorder_mul_mat_vec_mxfp4_q8_1_sycl_switch_ncols(
|
||||
src0_dd_i, src1_ddq_i, dst_dd_i, ne00, row_diff,
|
||||
src1_ncols, stride_col_y_bytes, stride_col_dst, stream);
|
||||
return;
|
||||
} else {
|
||||
GGML_SYCL_DEBUG("Calling reorder_mul_mat_vec_mxfp4_q8_1_sycl\n");
|
||||
reorder_mul_mat_vec_mxfp4_q8_1_sycl(src0_dd_i, src1_ddq_i_bs, dst_dd_i_bs, ne00, row_diff, stream);
|
||||
}
|
||||
} else if (i == 0 && src1_ncols > 1 && src1_ncols <= 8) {
|
||||
const int stride_col_y = src1_padded_col_size / QK8_1;
|
||||
const int stride_col_dst = dst->ne[0];
|
||||
GGML_SYCL_DEBUG("Calling mul_mat_vec_mxfp4_q8_1_sycl_switch_ncols ncols=%d\n", (int)src1_ncols);
|
||||
@@ -3111,6 +3184,11 @@ bool ggml_sycl_mul_mat_vec_q_id_reorder(
|
||||
vx_base, vy, ids_dev, dst_base, ncols, nrows, n_experts_used,
|
||||
expert_weight_stride, dst_row_stride, src1_row_stride, stream);
|
||||
return true;
|
||||
case GGML_TYPE_MXFP4:
|
||||
launch_mul_mat_vec_q_moe_reorder<reorder_vec_dot_q_sycl<GGML_TYPE_MXFP4>>(
|
||||
vx_base, vy, ids_dev, dst_base, ncols, nrows, n_experts_used,
|
||||
expert_weight_stride, dst_row_stride, src1_row_stride, stream);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -199,6 +199,27 @@ template <> struct block_q_t<GGML_TYPE_Q8_0> {
|
||||
static constexpr int block_to_q8_1_ratio() { return traits::qk / QK8_1; } // 1
|
||||
};
|
||||
|
||||
template <> struct block_q_t<GGML_TYPE_MXFP4> {
|
||||
struct traits {
|
||||
static constexpr uint32_t qk = QK_MXFP4; // 32
|
||||
static constexpr uint32_t qi = QI_MXFP4; // 4
|
||||
static constexpr uint32_t qr = QR_MXFP4; // 2
|
||||
static constexpr uint32_t vdr_mmvq = 2;
|
||||
};
|
||||
|
||||
// MXFP4 reorder layout: [qs0|qs1|...|qsN][e0|e1|...|eN]
|
||||
// The 17-byte AoS block leaves qs unaligned; split out, every 16-byte nibble block is aligned.
|
||||
static constexpr std::pair<int, int> get_block_offset(const int block_index, const int /* nblocks */) {
|
||||
return { block_index * (QK_MXFP4 / 2), 0 };
|
||||
}
|
||||
|
||||
static constexpr std::pair<int, int> get_d_offset(int nrows, int ncols, const int block_index) {
|
||||
return { (ncols / 2 * nrows) + block_index, 0 };
|
||||
}
|
||||
|
||||
static constexpr int block_to_q8_1_ratio() { return traits::qk / QK8_1; } // 1
|
||||
};
|
||||
|
||||
} // namespace ggml_sycl_reordered
|
||||
|
||||
#endif // GGML_SYCL_QUANTS_HPP
|
||||
|
||||
@@ -148,6 +148,28 @@ static __dpct_inline__ sycl::int2 get_int_from_table_16(
|
||||
dpct::byte_level_permute(tmp[0], tmp[1], 0x7531));
|
||||
}
|
||||
|
||||
// Four E2M1 codes (one per byte, bits 0..3) to their kvalues_mxfp4 int8 values. SWAR arithmetic
|
||||
// replaces get_int_from_table_16 for MXFP4: dpct::byte_level_permute is emulated with 64-bit shifts,
|
||||
// eight per int, which made the MXFP4 GEMV compute-bound on Intel GPUs.
|
||||
// Magnitudes 0,1,2,3,4,6,8,12 = m + [m>=5] + [m>=6] + 3*[m>=7]; each byte stays below 256, so the
|
||||
// byte-wise adds never carry. -0 (code 8) is left as 0 so the two's-complement +1 cannot carry either.
|
||||
static __dpct_inline__ int mxfp4_codes_to_int8(const uint32_t x) {
|
||||
const uint32_t m = x & 0x07070707u;
|
||||
const uint32_t ge5 = ((m + 0x03030303u) >> 3) & 0x01010101u;
|
||||
const uint32_t ge6 = ((m + 0x02020202u) >> 3) & 0x01010101u;
|
||||
const uint32_t ge7 = ((m + 0x01010101u) >> 3) & 0x01010101u;
|
||||
const uint32_t mag = m + ge5 + ge6 + 3u * ge7;
|
||||
const uint32_t nz = ((mag + 0x7f7f7f7fu) >> 7) & 0x01010101u;
|
||||
const uint32_t neg = (x >> 3) & nz & 0x01010101u;
|
||||
return (int) ((mag ^ (neg * 0xffu)) + neg);
|
||||
}
|
||||
|
||||
// Same result as get_int_from_table_16(q4, kvalues_mxfp4): x = low nibbles, y = high nibbles.
|
||||
static __dpct_inline__ sycl::int2 get_int_from_mxfp4(const int q4) {
|
||||
return sycl::int2(mxfp4_codes_to_int8((uint32_t) q4 & 0x0f0f0f0fu),
|
||||
mxfp4_codes_to_int8(((uint32_t) q4 >> 4) & 0x0f0f0f0fu));
|
||||
}
|
||||
|
||||
#define VDR_Q2_K_Q8_1_MMVQ 1
|
||||
|
||||
// contiguous v/x values
|
||||
@@ -795,6 +817,41 @@ template <> struct reorder_vec_dot_q_sycl<GGML_TYPE_Q6_K> {
|
||||
vl, vh, u0, u1, scs[0], scs[4], *d, d80, d81);
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct reorder_vec_dot_q_sycl<GGML_TYPE_MXFP4> {
|
||||
static constexpr ggml_type gtype = GGML_TYPE_MXFP4;
|
||||
|
||||
using mxfp4_block = ggml_sycl_reordered::block_q_t<GGML_TYPE_MXFP4>;
|
||||
using mxfp4_traits = typename mxfp4_block::traits;
|
||||
|
||||
__dpct_inline__ float operator()(const void * __restrict__ vbq, const std::pair<int, int> ibx_offset,
|
||||
const std::pair<int, int> d_offset, const int8_t * q8_1_quant_ptr,
|
||||
const sycl::half2 * q8_1_ds, const int & iqs) {
|
||||
static_assert(mxfp4_traits::vdr_mmvq == 2, "vector load assumes vdr_mmvq == 2");
|
||||
const uint8_t * base = static_cast<const uint8_t *>(vbq);
|
||||
|
||||
// Reordered nibble blocks are 16 contiguous bytes and iqs is 0 or 2, so each lane's two
|
||||
// weight ints are one aligned 8-byte load (the AoS layout needed eight byte loads).
|
||||
const sycl::int2 q4 = *reinterpret_cast<const sycl::int2 *>(base + ibx_offset.first + sizeof(int) * iqs);
|
||||
const uint8_t e = base[d_offset.first];
|
||||
|
||||
// Low nibbles pair with q8_1 ints iqs..iqs+1, high nibbles with iqs+4..iqs+5.
|
||||
const sycl::int2 u_lo = *reinterpret_cast<const sycl::int2 *>(q8_1_quant_ptr + sizeof(int) * iqs);
|
||||
const sycl::int2 u_hi = *reinterpret_cast<const sycl::int2 *>(q8_1_quant_ptr + sizeof(int) * (iqs + 4));
|
||||
|
||||
const sycl::int2 v0 = get_int_from_mxfp4(q4.x());
|
||||
const sycl::int2 v1 = get_int_from_mxfp4(q4.y());
|
||||
|
||||
int sumi = 0;
|
||||
sumi = ggml_sycl_dp4a(v0.x(), u_lo.x(), sumi);
|
||||
sumi = ggml_sycl_dp4a(v0.y(), u_hi.x(), sumi);
|
||||
sumi = ggml_sycl_dp4a(v1.x(), u_lo.y(), sumi);
|
||||
sumi = ggml_sycl_dp4a(v1.y(), u_hi.y(), sumi);
|
||||
|
||||
const float d = ggml_sycl_e8m0_to_fp32(e) * 0.5f * static_cast<float>((*q8_1_ds)[0]);
|
||||
return d * sumi;
|
||||
}
|
||||
};
|
||||
#define VDR_Q4_0_Q8_1_MMVQ 2
|
||||
#define VDR_Q4_0_Q8_1_MMQ 4
|
||||
|
||||
@@ -1124,7 +1181,7 @@ static __dpct_inline__ float vec_dot_mxfp4_q8_1(const void * __restrict__ vbq,
|
||||
#pragma unroll
|
||||
for (int l = 0; l < VDR_MXFP4_Q8_1_MMVQ; ++l) {
|
||||
const int aux_q4 = get_int_b1(bq4->qs, iqs + l);
|
||||
const sycl::int2 v = get_int_from_table_16(aux_q4, kvalues_mxfp4);
|
||||
const sycl::int2 v = get_int_from_mxfp4(aux_q4);
|
||||
sumi = ggml_sycl_dp4a(v.x(), q8[l + 0], sumi);
|
||||
sumi = ggml_sycl_dp4a(v.y(), q8[l + 4], sumi);
|
||||
}
|
||||
|
||||
@@ -2892,8 +2892,14 @@ void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
// RDNA3/4: above four columns, static 4 rows for all types bench faster than the default
|
||||
const bool is_rdna3_or_4 = device->vendor_id == VK_VENDOR_ID_AMD && (device->architecture == AMD_RDNA3 || device->architecture == AMD_RDNA4);
|
||||
auto const &rm_int_n = [&](uint32_t rows, uint32_t i) { return (is_rdna3_or_4 && i >= 4) ? 4u : rows; };
|
||||
// RDNA3/4: Static 4 rows for all types bench faster than the default
|
||||
auto const &rm_id = [&](uint32_t rows) { return is_rdna3_or_4 ? 4u : rows; };
|
||||
// RDNA3/4 and NVIDIA except pre-Turing: use 4 rows for MUL_MAT_ID MMVQ.
|
||||
auto const &rm_id = [&](uint32_t rows) {
|
||||
if (device->vendor_id == VK_VENDOR_ID_NVIDIA &&
|
||||
device->architecture != vk_device_architecture::NVIDIA_PRE_TURING) {
|
||||
return 4u;
|
||||
}
|
||||
return is_rdna3_or_4 ? 4u : rows;
|
||||
};
|
||||
uint32_t rm_iq = 2 * rm_kq;
|
||||
|
||||
const bool use_subgroups = device->subgroup_arithmetic;
|
||||
|
||||
@@ -259,6 +259,7 @@ class Keys:
|
||||
DIMENSION_COUNT = "{arch}.rope.dimension_count"
|
||||
DIMENSION_COUNT_SWA = "{arch}.rope.dimension_count_swa"
|
||||
DIMENSION_SECTIONS = "{arch}.rope.dimension_sections"
|
||||
SECTION_ORDER = "{arch}.rope.section_order"
|
||||
FREQ_BASE = "{arch}.rope.freq_base"
|
||||
FREQ_BASE_SWA = "{arch}.rope.freq_base_swa"
|
||||
SCALING_TYPE = "{arch}.rope.scaling.type"
|
||||
@@ -409,6 +410,7 @@ class Keys:
|
||||
BLOCK_COUNT = "clip.vision.block_count"
|
||||
IMAGE_MEAN = "clip.vision.image_mean"
|
||||
IMAGE_STD = "clip.vision.image_std"
|
||||
MAX_SLICE_NUMS = "clip.vision.max_slice_nums"
|
||||
IMAGE_RESIZE_ALGO = "clip.vision.image_resize_algo"
|
||||
SPATIAL_MERGE_SIZE = "clip.vision.spatial_merge_size"
|
||||
SWIGLU_CLAMP = "clip.vision.swiglu_clamp"
|
||||
@@ -1051,6 +1053,7 @@ class MODEL_TENSOR(IntEnum):
|
||||
V_SAM_NET_3 = auto() # Deepseek-OCR
|
||||
V_ENC_EMBD_IMGNL = auto() # Deepseek-OCR
|
||||
V_ENC_EMBD_VSEP = auto() # Deepseek-OCR
|
||||
V_TOK_EMBD_SEP = auto() # MiniCPM-V 4.7
|
||||
V_RESMPL_QUERY_768 = auto() # Deepseek-OCR-2
|
||||
V_RESMPL_QUERY_1024 = auto() # Deepseek-OCR-2
|
||||
|
||||
@@ -1832,6 +1835,7 @@ TENSOR_NAMES: dict[MODEL_TENSOR, str] = {
|
||||
MODEL_TENSOR.V_SAM_NET_3: "v.sam.net_3",
|
||||
MODEL_TENSOR.V_ENC_EMBD_IMGNL: "v.image_newline", # Deepseek-OCR, Granite4Vision
|
||||
MODEL_TENSOR.V_ENC_EMBD_VSEP: "v.view_seperator", # Deepseek-OCR
|
||||
MODEL_TENSOR.V_TOK_EMBD_SEP: "v.tok_embd_sep", # MiniCPM-V 4.7
|
||||
MODEL_TENSOR.V_RESMPL_QUERY_768: "v.resample_query_768", # Deepseek-OCR-2 qwen2
|
||||
MODEL_TENSOR.V_RESMPL_QUERY_1024: "v.resample_query_1024", # Deepseek-OCR-2 qwen2
|
||||
# Granite4 Vision
|
||||
@@ -2079,6 +2083,7 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
|
||||
MODEL_TENSOR.V_ENC_EMBD_POS,
|
||||
MODEL_TENSOR.V_ENC_EMBD_IMGNL,
|
||||
MODEL_TENSOR.V_ENC_EMBD_VSEP,
|
||||
MODEL_TENSOR.V_TOK_EMBD_SEP,
|
||||
MODEL_TENSOR.V_ENC_INPUT_NORM,
|
||||
MODEL_TENSOR.V_ENC_ATTN_QKV,
|
||||
MODEL_TENSOR.V_ENC_ATTN_Q,
|
||||
@@ -5927,6 +5932,12 @@ class RopeScalingType(Enum):
|
||||
LONGROPE = 'longrope'
|
||||
|
||||
|
||||
# M-RoPE: input position slot (t, y, x, z) that feeds each RoPE section, in section order
|
||||
class RopeSectionOrder(Enum):
|
||||
TYXZ = 'tyxz' # default
|
||||
ZYXT = 'zyxt'
|
||||
|
||||
|
||||
class PoolingType(IntEnum):
|
||||
NONE = 0
|
||||
MEAN = 1
|
||||
@@ -6133,6 +6144,7 @@ class VisionProjectorType:
|
||||
PARAKEET = "parakeet" # audio
|
||||
MINIMAXM3 = "minimax_m3"
|
||||
MINICPMV4_6 = "minicpmv4_6"
|
||||
MINICPMV4_7 = "minicpmv4_7"
|
||||
GRANITE_SPEECH = "granite_speech" # audio
|
||||
MIMOVL = "mimovl"
|
||||
MIMO_AUDIO = "mimo_audio"
|
||||
|
||||
@@ -24,6 +24,7 @@ from .constants import (
|
||||
GGUFEndian,
|
||||
GGUFValueType,
|
||||
Keys,
|
||||
RopeSectionOrder,
|
||||
RopeScalingType,
|
||||
PoolingType,
|
||||
TokenType,
|
||||
@@ -1154,6 +1155,9 @@ class GGUFWriter:
|
||||
def add_rope_dimension_sections(self, dims: Sequence[int]) -> None:
|
||||
self.add_array(Keys.Rope.DIMENSION_SECTIONS.format(arch=self.arch), dims)
|
||||
|
||||
def add_rope_section_order(self, value: RopeSectionOrder) -> None:
|
||||
self.add_string(Keys.Rope.SECTION_ORDER.format(arch=self.arch), value.value)
|
||||
|
||||
def add_rope_freq_base(self, value: float) -> None:
|
||||
self.add_float32(Keys.Rope.FREQ_BASE.format(arch=self.arch), value)
|
||||
|
||||
@@ -1462,6 +1466,9 @@ class GGUFWriter:
|
||||
def add_vision_projector_scale_factor(self, value: int) -> None:
|
||||
self.add_uint32(Keys.ClipVision.Projector.SCALE_FACTOR, value)
|
||||
|
||||
def add_vision_max_slice_nums(self, value: int) -> None:
|
||||
self.add_uint32(Keys.ClipVision.MAX_SLICE_NUMS, value)
|
||||
|
||||
def add_vision_n_wa_pattern(self, value: int) -> None:
|
||||
"""Add window attention pattern interval for vision models.
|
||||
|
||||
|
||||
+2
-1
@@ -1056,6 +1056,7 @@ extern "C" {
|
||||
// "state" here means extra hidden state carried over from a previous stage, e.g.:
|
||||
// - MTP: state from N layers of the target model
|
||||
// - Qwen3 VL (deepstack): state from N layers of the vision encoder
|
||||
// Returns false if the context does not take a state embedding (currently only MTP contexts do)
|
||||
LLAMA_API bool llama_batch_ext_set_embd_state(
|
||||
struct llama_batch_ext * batch,
|
||||
int32_t idx,
|
||||
@@ -1077,7 +1078,7 @@ extern "C" {
|
||||
|
||||
// Set custom position for the token at index idx in the batch
|
||||
// For M-RoPE models:
|
||||
// - Embedding tokens must have multiple positions per token
|
||||
// - Embedding tokens must have n_pos_per_embd positions per token, in order [t, y, x, z]; t is also the KV cache position
|
||||
// - Text token only requires one single position per token
|
||||
LLAMA_API bool llama_batch_ext_set_pos(
|
||||
struct llama_batch_ext * batch,
|
||||
|
||||
@@ -101,6 +101,13 @@ patches = {
|
||||
)],
|
||||
}
|
||||
|
||||
# local changes too large for the replacements above, kept as diffs and applied with git apply
|
||||
patch_files = [
|
||||
# backport of the fix for the stack overflow on deeply nested values (nlohmann/json#5387)
|
||||
# TODO: remove once nlohmann/json releases a version newer than 3.12.0
|
||||
"vendor/nlohmann/json-deep-nesting.patch",
|
||||
]
|
||||
|
||||
for url, filename in vendor.items():
|
||||
print(f"downloading {url} to {filename}") # noqa: NP100
|
||||
urllib.request.urlretrieve(url, filename)
|
||||
@@ -117,6 +124,14 @@ for filename, replacements in patches.items():
|
||||
with open(filename, "w", encoding="utf-8", newline="") as f:
|
||||
f.write(content)
|
||||
|
||||
for patch_file in patch_files:
|
||||
print(f"applying {patch_file}") # noqa: NP100
|
||||
try:
|
||||
subprocess.check_call(["git", "apply", patch_file])
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"Error: cannot apply {patch_file}, upstream code has changed") # noqa: NP100
|
||||
sys.exit(1)
|
||||
|
||||
print("Splitting httplib.h...") # noqa: NP100
|
||||
try:
|
||||
subprocess.check_call([
|
||||
|
||||
@@ -330,6 +330,7 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {
|
||||
{ LLM_KV_ROPE_DIMENSION_COUNT, "%s.rope.dimension_count" },
|
||||
{ LLM_KV_ROPE_DIMENSION_COUNT_SWA, "%s.rope.dimension_count_swa" },
|
||||
{ LLM_KV_ROPE_DIMENSION_SECTIONS, "%s.rope.dimension_sections" },
|
||||
{ LLM_KV_ROPE_SECTION_ORDER, "%s.rope.section_order" },
|
||||
{ LLM_KV_ROPE_FREQ_BASE, "%s.rope.freq_base" },
|
||||
{ LLM_KV_ROPE_FREQ_BASE_SWA, "%s.rope.freq_base_swa" },
|
||||
{ LLM_KV_ROPE_SCALE_LINEAR, "%s.rope.scale_linear" },
|
||||
|
||||
@@ -335,6 +335,7 @@ enum llm_kv {
|
||||
LLM_KV_ROPE_DIMENSION_COUNT,
|
||||
LLM_KV_ROPE_DIMENSION_COUNT_SWA,
|
||||
LLM_KV_ROPE_DIMENSION_SECTIONS,
|
||||
LLM_KV_ROPE_SECTION_ORDER,
|
||||
LLM_KV_ROPE_FREQ_BASE,
|
||||
LLM_KV_ROPE_FREQ_BASE_SWA,
|
||||
LLM_KV_ROPE_SCALE_LINEAR,
|
||||
|
||||
+83
-15
@@ -31,9 +31,10 @@ bool llama_batch_allocr::init(
|
||||
bool output_all) {
|
||||
clear();
|
||||
|
||||
this->vocab = &vocab;
|
||||
this->n_embd = batch_inp.n_embd > 0 ? batch_inp.n_embd : batch_inp.n_embd_inp;
|
||||
this->n_seq_max = batch_inp.n_seq_max;
|
||||
this->vocab = &vocab;
|
||||
this->n_embd = batch_inp.n_embd > 0 ? batch_inp.n_embd : batch_inp.n_embd_inp;
|
||||
this->n_embd_state = batch_inp.n_embd_state;
|
||||
this->n_seq_max = batch_inp.n_seq_max;
|
||||
|
||||
const int32_t n_tok = (int32_t) batch_inp.tokens.size();
|
||||
|
||||
@@ -48,14 +49,17 @@ bool llama_batch_allocr::init(
|
||||
|
||||
//
|
||||
// determine the content types of the batch
|
||||
// an entry can carry a token id, a token embedding, or both (e.g. MTP hook batches)
|
||||
// an entry can carry a token id, a token embedding, or both
|
||||
// all entries must carry the same combination, or be a mix of token and embd entries
|
||||
// a state embedding (e.g. MTP hook batches) is set on all entries or on none
|
||||
//
|
||||
|
||||
int32_t n_tok_only = 0;
|
||||
int32_t n_embd_only = 0;
|
||||
int32_t n_both = 0;
|
||||
|
||||
const bool has_state = batch_inp.tokens[0].has_state;
|
||||
|
||||
for (int32_t i = 0; i < n_tok; ++i) {
|
||||
const bool is_tok = batch_inp.tokens[i].id != LLAMA_TOKEN_NULL;
|
||||
const bool is_emb = batch_inp.tokens[i].has_embd;
|
||||
@@ -65,6 +69,11 @@ bool llama_batch_allocr::init(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (batch_inp.tokens[i].has_state != has_state) {
|
||||
LLAMA_LOG_ERROR("%s: all entries in the batch must have the same state embedding presence\n", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
n_tok_only += is_tok && !is_emb;
|
||||
n_embd_only += is_emb && !is_tok;
|
||||
n_both += is_tok && is_emb;
|
||||
@@ -124,6 +133,10 @@ bool llama_batch_allocr::init(
|
||||
embd_vec = batch_inp.embd;
|
||||
}
|
||||
|
||||
if (has_state) {
|
||||
state_vec = batch_inp.state;
|
||||
}
|
||||
|
||||
//
|
||||
// build flat pos array, section-major: pos[j*n_tok + i] = section j of entry i
|
||||
// token entry: [p, p, p, 0] (M-RoPE text position)
|
||||
@@ -292,6 +305,7 @@ bool llama_batch_allocr::init(
|
||||
/*.n_pos =*/ n_pos_per_embd,
|
||||
/*.token =*/ batch.token,
|
||||
/*.embd =*/ batch.embd,
|
||||
/*.embd_state =*/ state_vec.empty() ? nullptr : state_vec.data(),
|
||||
/*.pos =*/ batch.pos,
|
||||
/*.n_seq_id =*/ batch.n_seq_id,
|
||||
/*.seq_id =*/ batch.seq_id,
|
||||
@@ -493,6 +507,7 @@ llama_ubatch llama_batch_allocr::ubatch_reserve(uint32_t n_seq_tokens, uint32_t
|
||||
|
||||
udata->token .resize(n_tokens);
|
||||
udata->embd .clear();
|
||||
udata->embd_state.clear();
|
||||
udata->pos .resize(n_pos_all);
|
||||
udata->n_seq_id .resize(n_tokens);
|
||||
udata->seq_id .resize(n_tokens);
|
||||
@@ -515,6 +530,7 @@ llama_ubatch llama_batch_allocr::ubatch_reserve(uint32_t n_seq_tokens, uint32_t
|
||||
|
||||
/*.token =*/ udata->token.data(),
|
||||
/*.embd =*/ nullptr,
|
||||
/*.embd_state =*/ nullptr,
|
||||
/*.pos =*/ udata->pos.data(),
|
||||
/*.n_seq_id =*/ udata->n_seq_id.data(),
|
||||
/*.seq_id =*/ udata->seq_id.data(),
|
||||
@@ -821,6 +837,7 @@ void llama_batch_allocr::clear() {
|
||||
token_vec .clear();
|
||||
embd_vec .clear();
|
||||
is_embd_vec .clear();
|
||||
state_vec .clear();
|
||||
seq_id_data .clear();
|
||||
pos .clear();
|
||||
n_seq_id .clear();
|
||||
@@ -863,12 +880,15 @@ llama_ubatch llama_batch_allocr::ubatch_add(const std::vector<int32_t> & idxs, u
|
||||
const bool mixed = mixed_batch && n_embd_rows > 0 && n_embd_rows < n_tokens;
|
||||
const bool use_token = batch.token && !(mixed_batch && n_embd_rows == n_tokens);
|
||||
const bool use_embd = batch.embd && !(mixed_batch && n_embd_rows == 0);
|
||||
const bool has_state = !state_vec.empty();
|
||||
|
||||
const int64_t n_embd_all = use_embd ? (int64_t) n_tokens*n_embd : 0;
|
||||
const int64_t n_pos_all = (int64_t) n_tokens*n_pos_per_embd;
|
||||
const int64_t n_embd_all = use_embd ? (int64_t) n_tokens*n_embd : 0;
|
||||
const int64_t n_state_all = has_state ? (int64_t) n_tokens*n_embd_state : 0;
|
||||
const int64_t n_pos_all = (int64_t) n_tokens*n_pos_per_embd;
|
||||
|
||||
udata->token .resize(n_tokens);
|
||||
udata->embd .resize(n_embd_all);
|
||||
udata->embd_state.resize(n_state_all);
|
||||
udata->pos .resize(n_pos_all);
|
||||
udata->n_seq_id .resize(n_tokens);
|
||||
udata->seq_id .resize(n_tokens);
|
||||
@@ -896,6 +916,10 @@ llama_ubatch llama_batch_allocr::ubatch_add(const std::vector<int32_t> & idxs, u
|
||||
udata->type[i] = is_embd_vec[idxs[i]];
|
||||
}
|
||||
|
||||
if (has_state) {
|
||||
memcpy(udata->embd_state.data() + i*n_embd_state, state_vec.data() + (int64_t) idxs[i]*n_embd_state, n_embd_state*sizeof(float));
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < (size_t)n_pos_per_embd; ++j) {
|
||||
udata->pos[j*n_tokens + i] = batch.pos[j*batch.n_tokens + idxs[i]];
|
||||
}
|
||||
@@ -942,6 +966,7 @@ llama_ubatch llama_batch_allocr::ubatch_add(const std::vector<int32_t> & idxs, u
|
||||
|
||||
/*.token =*/ use_token ? udata->token.data() : nullptr,
|
||||
/*.embd =*/ use_embd ? udata->embd.data() : nullptr,
|
||||
/*.embd_state =*/ has_state ? udata->embd_state.data() : nullptr,
|
||||
/*.pos =*/ udata->pos.data(),
|
||||
/*.n_seq_id =*/ udata->n_seq_id.data(),
|
||||
/*.seq_id =*/ udata->seq_id.data(),
|
||||
@@ -993,6 +1018,7 @@ void llama_batch_allocr::ubatch_print(const llama_ubatch & ubatch, int debug) {
|
||||
|
||||
LLAMA_LOG_DEBUG("%s: token = %p\n", __func__, (void *) ubatch.token);
|
||||
LLAMA_LOG_DEBUG("%s: embd = %p\n", __func__, (void *) ubatch.embd);
|
||||
LLAMA_LOG_DEBUG("%s: embd_state = %p\n", __func__, (void *) ubatch.embd_state);
|
||||
LLAMA_LOG_DEBUG("%s: pos = %p\n", __func__, (void *) ubatch.pos);
|
||||
LLAMA_LOG_DEBUG("%s: n_seq_id = %p\n", __func__, (void *) ubatch.n_seq_id);
|
||||
LLAMA_LOG_DEBUG("%s: seq_id = %p\n", __func__, (void *) ubatch.seq_id);
|
||||
@@ -1110,19 +1136,25 @@ void llama_batch_free(struct llama_batch batch) {
|
||||
// llama_batch_ext
|
||||
|
||||
size_t llama_batch_ext_select_n_embd_inp(llama_context_type ctx_type, llm_arch arch, const llama_hparams & hparams) {
|
||||
if (ctx_type == LLAMA_CONTEXT_TYPE_MTP) {
|
||||
return hparams.n_embd_out();
|
||||
}
|
||||
GGML_UNUSED(ctx_type);
|
||||
if (arch == LLM_ARCH_DFLASH) {
|
||||
return hparams.n_embd_inp_enc();
|
||||
}
|
||||
return hparams.n_embd_inp();
|
||||
}
|
||||
|
||||
size_t llama_batch_ext_select_n_embd_state(llama_context_type ctx_type, const llama_hparams & hparams) {
|
||||
if (ctx_type == LLAMA_CONTEXT_TYPE_MTP) {
|
||||
return hparams.n_embd_out();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
llama_batch_ext::llama_batch_ext(llama_context * ctx) :
|
||||
n_tokens_max(llama_n_batch(ctx)),
|
||||
n_embd_inp(llama_batch_ext_select_n_embd_inp(ctx->get_cparams().ctx_type, llama_get_model(ctx)->arch, llama_get_model(ctx)->hparams)),
|
||||
n_embd_inp_enc(llama_get_model(ctx)->hparams.n_embd_inp_enc()),
|
||||
n_embd_state(llama_batch_ext_select_n_embd_state(ctx->get_cparams().ctx_type, llama_get_model(ctx)->hparams)),
|
||||
n_seq_max(llama_n_seq_max(ctx)),
|
||||
mem(llama_get_memory(ctx)),
|
||||
n_vocab(llama_vocab_n_tokens(llama_model_get_vocab(llama_get_model(ctx)))),
|
||||
@@ -1141,6 +1173,7 @@ llama_batch_ext::llama_batch_ext(
|
||||
n_tokens_max(n_tokens_max),
|
||||
n_embd_inp(n_embd_inp),
|
||||
n_embd_inp_enc(n_embd_inp_enc),
|
||||
n_embd_state(0),
|
||||
n_seq_max(n_seq_max),
|
||||
mem(mem),
|
||||
n_vocab(n_vocab),
|
||||
@@ -1151,6 +1184,7 @@ llama_batch_ext::llama_batch_ext(
|
||||
void llama_batch_ext::clear() {
|
||||
tokens.clear();
|
||||
embd .clear();
|
||||
state .clear();
|
||||
n_embd = 0;
|
||||
}
|
||||
|
||||
@@ -1233,6 +1267,38 @@ bool llama_batch_ext::set_token_embd(int32_t idx, llama_embd embd_in) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool llama_batch_ext::set_token_state(int32_t idx, llama_embd state_in) {
|
||||
if (idx < 0 || idx >= (int32_t) tokens.size()) {
|
||||
return false;
|
||||
}
|
||||
if (!state_in.data) {
|
||||
return false;
|
||||
}
|
||||
if (n_embd_state == 0) {
|
||||
return false; // this context does not take state embeddings
|
||||
}
|
||||
|
||||
const size_t n_total = state_in.n_rows * state_in.n_embd;
|
||||
if (n_total != n_embd_state) {
|
||||
LLAMA_LOG_ERROR("%s: state size mismatch, got %zu rows x %zu = %zu, expected %zu\n",
|
||||
__func__, state_in.n_rows, state_in.n_embd, n_total, n_embd_state);
|
||||
return false;
|
||||
}
|
||||
|
||||
token & t = tokens[idx];
|
||||
|
||||
if (t.has_state) {
|
||||
LLAMA_LOG_ERROR("%s: state for token %d is already set\n", __func__, idx);
|
||||
return false;
|
||||
}
|
||||
|
||||
t.has_state = true;
|
||||
t.state_off = state.size();
|
||||
state.insert(state.end(), state_in.data, state_in.data + n_total);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool llama_batch_ext::set_token_pos(int32_t idx, const llama_pos * pos_in) {
|
||||
if (idx < 0 || idx >= (int32_t) tokens.size()) {
|
||||
return false;
|
||||
@@ -1320,11 +1386,7 @@ bool llama_batch_ext_set_embd_token(llama_batch_ext * batch, int32_t idx, llama_
|
||||
}
|
||||
|
||||
bool llama_batch_ext_set_embd_state(llama_batch_ext * batch, int32_t idx, llama_embd embd) {
|
||||
// TODO
|
||||
GGML_UNUSED(batch);
|
||||
GGML_UNUSED(idx);
|
||||
GGML_UNUSED(embd);
|
||||
return false;
|
||||
return batch->set_token_state(idx, embd);
|
||||
}
|
||||
|
||||
bool llama_batch_ext_set_output_embd(llama_batch_ext * batch, int32_t idx, bool value) {
|
||||
@@ -1393,7 +1455,13 @@ void llama_batch_compat::init(llama_batch_ext & dst, const llama_batch & batch_i
|
||||
t.id = batch_inp.token[i];
|
||||
}
|
||||
|
||||
if (has_embd) {
|
||||
// legacy MTP hook batches carry the hidden state next to the token ids
|
||||
if (has_embd && has_token && batch_ext->n_embd_state > 0) {
|
||||
t.has_state = true;
|
||||
t.state_off = batch_ext->state.size();
|
||||
const float * src = batch_inp.embd + (size_t) i * batch_ext->n_embd_state;
|
||||
batch_ext->state.insert(batch_ext->state.end(), src, src + batch_ext->n_embd_state);
|
||||
} else if (has_embd) {
|
||||
t.has_embd = true;
|
||||
t.embd_off = batch_ext->embd.size();
|
||||
const float * src = batch_inp.embd + (size_t) i * n_embd_row;
|
||||
|
||||
+17
-6
@@ -48,10 +48,11 @@ struct llama_ubatch {
|
||||
// seq_idx: indices of the unique sequence ids in the ubatch in [0, n_seqs_unq)
|
||||
// used for extracting sequence pooled embeddings
|
||||
|
||||
// // size | idx | val
|
||||
llama_token * token; // [n_tokens] | i | id, token
|
||||
float * embd; // [n_embd, n_tokens] | i | embd
|
||||
llama_pos * pos; // [n_tokens*n_pos] | i | pos
|
||||
// // size | idx | val
|
||||
llama_token * token; // [n_tokens] | i | id, token
|
||||
float * embd; // [n_embd, n_tokens] | i | embd
|
||||
float * embd_state; // [n_embd_state, n_tokens] | i | hidden state carried over from a previous stage (e.g. MTP)
|
||||
llama_pos * pos; // [n_tokens*n_pos] | i | pos
|
||||
int32_t * n_seq_id; // [n_tokens] | i | -
|
||||
llama_seq_id ** seq_id; // [n_tokens] | s | s0, s1, seq_id
|
||||
llama_seq_id * seq_id_unq; // [n_seqs_unq] | s | seq_id
|
||||
@@ -63,6 +64,7 @@ struct llama_ubatch {
|
||||
struct data_t {
|
||||
std::vector<llama_token> token;
|
||||
std::vector<float> embd;
|
||||
std::vector<float> embd_state;
|
||||
std::vector<llama_pos> pos;
|
||||
std::vector<int32_t> n_seq_id;
|
||||
std::vector<llama_seq_id *> seq_id; // these point into the seq_id_data below
|
||||
@@ -85,15 +87,18 @@ struct llama_ubatch {
|
||||
|
||||
struct llama_hparams;
|
||||
|
||||
// MTP hook batches carry the target model's hidden state (n_embd_out size).
|
||||
// DFlash batches carry the fused target features at the encoder input width (n_embd_inp_enc size).
|
||||
// Normal batches carry token embeddings (n_embd_inp size).
|
||||
// Other batches carry token embeddings (n_embd_inp size).
|
||||
size_t llama_batch_ext_select_n_embd_inp(llama_context_type ctx_type, llm_arch arch, const llama_hparams & hparams);
|
||||
|
||||
// MTP contexts also take the target model's hidden state (n_embd_out size), 0 = no state input
|
||||
size_t llama_batch_ext_select_n_embd_state(llama_context_type ctx_type, const llama_hparams & hparams);
|
||||
|
||||
struct llama_batch_ext {
|
||||
const size_t n_tokens_max; // max number of tokens that can be stored in the batch
|
||||
const size_t n_embd_inp; // decoder embd row width
|
||||
const size_t n_embd_inp_enc; // encoder embd row width (e.g. eagle3/dflash extracted features)
|
||||
const size_t n_embd_state; // state embd row width, 0 if the context takes no state
|
||||
const llama_seq_id n_seq_max; // max number of sequences
|
||||
llama_memory_i * mem; // memory for position inference
|
||||
const llama_token n_vocab; // max token ID that we accept
|
||||
@@ -107,6 +112,8 @@ struct llama_batch_ext {
|
||||
llama_token id = LLAMA_TOKEN_NULL;
|
||||
bool has_embd = false; // whether embd_off is set
|
||||
size_t embd_off = 0; // index offset in the embd array
|
||||
bool has_state = false; // whether state_off is set
|
||||
size_t state_off = 0; // index offset in the state array
|
||||
bool output = false; // TODO: have dedicated output flags
|
||||
int32_t decision_order = 0; // see llama_batch_ext_set_decision_order()
|
||||
std::unordered_set<llama_seq_id> seq_ids;
|
||||
@@ -114,6 +121,7 @@ struct llama_batch_ext {
|
||||
};
|
||||
std::vector<token> tokens;
|
||||
std::vector<float> embd;
|
||||
std::vector<float> state;
|
||||
|
||||
llama_batch_ext(llama_context * ctx);
|
||||
|
||||
@@ -136,6 +144,7 @@ struct llama_batch_ext {
|
||||
bool add_seq(int32_t idx, llama_seq_id seq_id);
|
||||
bool set_token_id(int32_t idx, llama_token id);
|
||||
bool set_token_embd(int32_t idx, llama_embd embd_in);
|
||||
bool set_token_state(int32_t idx, llama_embd state_in);
|
||||
bool set_token_pos(int32_t idx, const llama_pos * pos_in);
|
||||
bool set_output(int32_t idx, bool output_last);
|
||||
bool set_decision_order(int32_t idx, int32_t order);
|
||||
@@ -205,12 +214,14 @@ private:
|
||||
const bool allow_mixed;
|
||||
|
||||
uint32_t n_embd;
|
||||
uint32_t n_embd_state;
|
||||
uint32_t n_seq_max;
|
||||
uint32_t n_outputs;
|
||||
|
||||
std::vector<llama_token> token_vec; // owned token IDs built from llama_batch_ext
|
||||
std::vector<float> embd_vec; // owned embeddings built from llama_batch_ext
|
||||
std::vector<int8_t> is_embd_vec; // mixed batch only (= 1 if embd, 0 if text token)
|
||||
std::vector<float> state_vec; // owned state embeddings built from llama_batch_ext, llama_batch has no slot for them
|
||||
std::vector<llama_seq_id> seq_id_data; // flat storage for seq_id pointers below
|
||||
|
||||
std::vector<llama_pos> pos;
|
||||
|
||||
+35
-13
@@ -149,25 +149,21 @@ void llm_graph_input_embd_h::set_input(const llama_ubatch * ubatch) {
|
||||
GGML_ASSERT(ubatch->embd);
|
||||
GGML_ASSERT(n_embd == embd->ne[0]);
|
||||
|
||||
ggml_backend_tensor_set(embd, ubatch->embd, 0, n_tokens*n_embd*ggml_element_size(h));
|
||||
ggml_backend_tensor_set(embd, ubatch->embd, 0, n_tokens*n_embd*ggml_element_size(embd));
|
||||
}
|
||||
|
||||
// TODO: extend llama_ubatch to differentiate between token embeddings and hidden states
|
||||
// for now, we assume that the hidden state is always provided as an embedding
|
||||
// ref: https://github.com/ggml-org/llama.cpp/pull/23643
|
||||
if (ubatch->embd) {
|
||||
GGML_ASSERT(n_embd == h->ne[0]);
|
||||
GGML_ASSERT(ubatch->embd_state && "this graph requires a state embedding, see llama_batch_ext_set_embd_state()");
|
||||
GGML_ASSERT(n_embd_state == h->ne[0]);
|
||||
|
||||
ggml_backend_tensor_set(h, ubatch->embd, 0, n_tokens*n_embd*ggml_element_size(h));
|
||||
}
|
||||
ggml_backend_tensor_set(h, ubatch->embd_state, 0, n_tokens*n_embd_state*ggml_element_size(h));
|
||||
}
|
||||
|
||||
bool llm_graph_input_embd_h::can_reuse(const llm_graph_params & params) {
|
||||
bool res = true;
|
||||
|
||||
res &= (!params.ubatch.token) || (tokens && tokens->ne[0] == params.ubatch.n_tokens);
|
||||
res &= (!params.ubatch.embd) || (embd && embd->ne[1] == params.ubatch.n_tokens);
|
||||
res &= (!params.ubatch.embd) || (h && h->ne[1] == params.ubatch.n_tokens);
|
||||
res &= (!params.ubatch.token) || (tokens && tokens->ne[0] == params.ubatch.n_tokens);
|
||||
res &= (!params.ubatch.embd) || (embd && embd->ne[1] == params.ubatch.n_tokens);
|
||||
res &= (!params.ubatch.embd_state) || (h && h->ne[1] == params.ubatch.n_tokens);
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -176,7 +172,33 @@ void llm_graph_input_pos::set_input(const llama_ubatch * ubatch) {
|
||||
if (ubatch->pos && pos) {
|
||||
const int64_t n_tokens = ubatch->n_tokens;
|
||||
|
||||
ggml_backend_tensor_set(pos, ubatch->pos, 0, n_tokens*n_pos_per_embd*ggml_element_size(pos));
|
||||
const bool has_embd = ubatch->is_mixed() || ubatch->token == nullptr;
|
||||
if (rope_section_order == LLAMA_ROPE_SECTION_ORDER_TYXZ || !has_embd) {
|
||||
ggml_backend_tensor_set(pos, ubatch->pos, 0, n_tokens*n_pos_per_embd*ggml_element_size(pos));
|
||||
return;
|
||||
}
|
||||
|
||||
// input is always [t, y, x, z]
|
||||
// token entries are expanded by the batch to [p, p, p, 0]
|
||||
GGML_ASSERT(n_pos_per_embd == 4);
|
||||
|
||||
// slot index per section, slots are t = 0, y = 1, x = 2, z = 3
|
||||
std::array<int64_t, 4> slot_of_section = { 0, 1, 2, 3 };
|
||||
switch (rope_section_order) {
|
||||
case LLAMA_ROPE_SECTION_ORDER_TYXZ: break;
|
||||
case LLAMA_ROPE_SECTION_ORDER_ZYXT: slot_of_section = { 3, 1, 2, 0 }; break;
|
||||
default: GGML_ABORT("unsupported rope section order");
|
||||
}
|
||||
|
||||
std::vector<llama_pos> pos_data(n_tokens*n_pos_per_embd);
|
||||
for (int64_t i = 0; i < n_tokens; ++i) {
|
||||
const bool is_embd = ubatch->is_mixed() ? ubatch->type[i] != 0 : true;
|
||||
for (int64_t s = 0; s < 4; ++s) {
|
||||
const int64_t slot = is_embd ? slot_of_section[s] : s;
|
||||
pos_data[s*n_tokens + i] = ubatch->pos[slot*n_tokens + i];
|
||||
}
|
||||
}
|
||||
ggml_backend_tensor_set(pos, pos_data.data(), 0, pos_data.size()*ggml_element_size(pos));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2624,7 +2646,7 @@ ggml_tensor * llm_graph_context::build_inp_embd(ggml_tensor * tok_embd, float to
|
||||
}
|
||||
|
||||
ggml_tensor * llm_graph_context::build_inp_pos() const {
|
||||
auto inp = std::make_unique<llm_graph_input_pos>(hparams.n_pos_per_embd());
|
||||
auto inp = std::make_unique<llm_graph_input_pos>(hparams.n_pos_per_embd(), hparams.rope_section_order);
|
||||
|
||||
auto & cur = inp->pos;
|
||||
|
||||
|
||||
+10
-6
@@ -149,10 +149,10 @@ public:
|
||||
const int64_t n_embd = 0;
|
||||
};
|
||||
|
||||
// similar to llm_graph_input_embd but with an additional hidden state input
|
||||
// similar to llm_graph_input_embd but with an additional hidden state input, fed from ubatch.embd_state
|
||||
class llm_graph_input_embd_h : public llm_graph_input_i {
|
||||
public:
|
||||
llm_graph_input_embd_h(int64_t n_embd) : n_embd(n_embd) {}
|
||||
llm_graph_input_embd_h(int64_t n_embd, int64_t n_embd_state) : n_embd(n_embd), n_embd_state(n_embd_state) {}
|
||||
virtual ~llm_graph_input_embd_h() = default;
|
||||
|
||||
void set_input(const llama_ubatch * ubatch) override;
|
||||
@@ -161,14 +161,16 @@ public:
|
||||
|
||||
ggml_tensor * tokens = nullptr; // I32 [n_batch]
|
||||
ggml_tensor * embd = nullptr; // F32 [n_embd, n_batch]
|
||||
ggml_tensor * h = nullptr; // F32 [n_embd, n_batch]
|
||||
ggml_tensor * h = nullptr; // F32 [n_embd_state, n_batch]
|
||||
|
||||
const int64_t n_embd = 0;
|
||||
const int64_t n_embd = 0;
|
||||
const int64_t n_embd_state = 0;
|
||||
};
|
||||
|
||||
class llm_graph_input_pos : public llm_graph_input_i {
|
||||
public:
|
||||
llm_graph_input_pos(uint32_t n_pos_per_embd) : n_pos_per_embd(n_pos_per_embd) {}
|
||||
llm_graph_input_pos(uint32_t n_pos_per_embd, llama_rope_section_order rope_section_order = LLAMA_ROPE_SECTION_ORDER_TYXZ)
|
||||
: n_pos_per_embd(n_pos_per_embd), rope_section_order(rope_section_order) {}
|
||||
virtual ~llm_graph_input_pos() = default;
|
||||
|
||||
void set_input(const llama_ubatch * ubatch) override;
|
||||
@@ -178,6 +180,7 @@ public:
|
||||
ggml_tensor * pos = nullptr; // I32 [n_batch]
|
||||
|
||||
const uint32_t n_pos_per_embd = 1;
|
||||
const llama_rope_section_order rope_section_order = LLAMA_ROPE_SECTION_ORDER_TYXZ;
|
||||
};
|
||||
|
||||
// temperature tuning, used by llama4
|
||||
@@ -838,7 +841,8 @@ struct llm_graph_params {
|
||||
(!ubatch.token && !other.ubatch.token) ||
|
||||
(!ubatch.embd && !other.ubatch.embd) ||
|
||||
(ubatch.token && other.ubatch.token && ubatch.embd && other.ubatch.embd)
|
||||
);
|
||||
) &&
|
||||
(!ubatch.embd_state == !other.ubatch.embd_state);
|
||||
|
||||
// when we split the batch using "equal_seqs" we have to verify that the participating sequences are the same
|
||||
// the reason is because the set of attention streams would be different for different sequences
|
||||
|
||||
@@ -36,6 +36,13 @@ enum llama_non_causal_type {
|
||||
LLAMA_NON_CAUSAL_TYPE_SWA_FULL = 2, // all layers non-causal, SWA not applied between tokens of the current ubatch (deepseek 4)
|
||||
};
|
||||
|
||||
// M-RoPE: which input position slot feeds each RoPE section
|
||||
enum llama_rope_section_order {
|
||||
LLAMA_ROPE_SECTION_ORDER_UNSPECIFIED = -1,
|
||||
LLAMA_ROPE_SECTION_ORDER_TYXZ = 0, // default, slot i feeds section i
|
||||
LLAMA_ROPE_SECTION_ORDER_ZYXT = 1, // MiniCPM-V 4.7: time last
|
||||
};
|
||||
|
||||
// forward declaration; full definition in llama-graph.h
|
||||
enum llm_ffn_op_type : int;
|
||||
|
||||
@@ -166,6 +173,8 @@ struct llama_hparams {
|
||||
|
||||
std::array<int, 4> rope_sections;
|
||||
|
||||
enum llama_rope_section_order rope_section_order = LLAMA_ROPE_SECTION_ORDER_TYXZ;
|
||||
|
||||
// Per-layer RoPE enable flags (1 = use RoPE, 0 = NoPE)
|
||||
// by default, all layers use RoPE (controlled by rope_finetuned)
|
||||
std::array<uint32_t, LLAMA_MAX_LAYERS> rope_pattern;
|
||||
|
||||
@@ -159,6 +159,7 @@ static llama_ubatch dsv4_build_raw_write_ubatch(const llama_ubatch & ubatch) {
|
||||
/*.n_pos =*/ ubatch.n_pos,
|
||||
/*.token =*/ data->token.empty() ? nullptr : data->token.data(),
|
||||
/*.embd =*/ nullptr,
|
||||
/*.embd_state =*/ nullptr,
|
||||
/*.pos =*/ data->pos.data(),
|
||||
/*.n_seq_id =*/ data->n_seq_id.data(),
|
||||
/*.seq_id =*/ data->seq_id.data(),
|
||||
|
||||
@@ -365,6 +365,7 @@ void llama_model_saver::add_kv_from_model() {
|
||||
add_kv(LLM_KV_ROPE_DIMENSION_COUNT, hparams.n_rot_full);
|
||||
add_kv(LLM_KV_ROPE_DIMENSION_COUNT_SWA, hparams.n_rot_swa);
|
||||
add_kv(LLM_KV_ROPE_DIMENSION_SECTIONS, hparams.rope_sections);
|
||||
add_kv(LLM_KV_ROPE_SECTION_ORDER, llama_rope_section_order_name(hparams.rope_section_order));
|
||||
add_kv(LLM_KV_ROPE_FREQ_BASE, hparams.rope_freq_base_train);
|
||||
add_kv(LLM_KV_ROPE_FREQ_BASE_SWA, hparams.rope_freq_base_train_swa);
|
||||
// add_kv(LLM_KV_ROPE_SCALE_LINEAR, rope_scaling_factor); // old name
|
||||
|
||||
@@ -1064,6 +1064,25 @@ static llama_rope_scaling_type llama_rope_scaling_type_from_string(const std::st
|
||||
return LLAMA_ROPE_SCALING_TYPE_UNSPECIFIED;
|
||||
}
|
||||
|
||||
static const std::map<llama_rope_section_order, const char *> LLAMA_ROPE_SECTION_ORDERS = {
|
||||
{ LLAMA_ROPE_SECTION_ORDER_TYXZ, "tyxz" },
|
||||
{ LLAMA_ROPE_SECTION_ORDER_ZYXT, "zyxt" },
|
||||
};
|
||||
|
||||
std::string llama_rope_section_order_name(llama_rope_section_order rope_section_order) {
|
||||
return LLAMA_ROPE_SECTION_ORDERS.at(rope_section_order);
|
||||
}
|
||||
|
||||
static llama_rope_section_order llama_rope_section_order_from_string(const std::string & name) {
|
||||
for (const auto & kv : LLAMA_ROPE_SECTION_ORDERS) {
|
||||
if (kv.second == name) {
|
||||
return kv.first;
|
||||
}
|
||||
}
|
||||
|
||||
return LLAMA_ROPE_SECTION_ORDER_UNSPECIFIED;
|
||||
}
|
||||
|
||||
// Maps GGUF activation names to the FFN op type used by the graph builders.
|
||||
static const std::map<std::string, llm_ffn_op_type> LLM_FFN_OP_TYPES_FROM_STRING = {
|
||||
{ "gelu", LLM_FFN_GEGLU_ERF },
|
||||
@@ -1447,6 +1466,13 @@ void llama_model_base::load_hparams(llama_model_loader & ml) {
|
||||
hparams.rope_scaling_type_train = llama_rope_scaling_type_from_string(rope_scaling);
|
||||
GGML_ASSERT(hparams.rope_scaling_type_train != LLAMA_ROPE_SCALING_TYPE_UNSPECIFIED);
|
||||
|
||||
std::string rope_section_order("tyxz");
|
||||
ml.get_key(LLM_KV_ROPE_SECTION_ORDER, rope_section_order, false);
|
||||
hparams.rope_section_order = llama_rope_section_order_from_string(rope_section_order);
|
||||
if (hparams.rope_section_order == LLAMA_ROPE_SECTION_ORDER_UNSPECIFIED) {
|
||||
throw std::runtime_error("unknown rope section order: " + rope_section_order);
|
||||
}
|
||||
|
||||
// TODO: Handle SWA metadata similarly when models start implementing it
|
||||
// rope_freq_scale (inverse of the kv) is optional
|
||||
float ropescale = 0.0f;
|
||||
@@ -1517,6 +1543,10 @@ void llama_model_base::load_hparams(llama_model_loader & ml) {
|
||||
}
|
||||
|
||||
hparams.rope_type = llama_model_rope_type(this);
|
||||
|
||||
if (hparams.rope_section_order != LLAMA_ROPE_SECTION_ORDER_TYXZ && hparams.n_pos_per_embd() != 4) {
|
||||
throw std::runtime_error("rope section order " + llama_rope_section_order_name(hparams.rope_section_order) + " requires M-RoPE");
|
||||
}
|
||||
}
|
||||
|
||||
void llama_model_base::load_vocab(llama_model_loader & ml) {
|
||||
@@ -2158,6 +2188,9 @@ void llama_model::print_info() const {
|
||||
if (const auto & s = hparams.rope_sections; s[0] || s[1] || s[2] || s[3]) {
|
||||
LLAMA_LOG_INFO("%s: mrope sections = [%d, %d, %d, %d]\n", __func__, s[0], s[1], s[2], s[3]);
|
||||
}
|
||||
if (hparams.rope_section_order != LLAMA_ROPE_SECTION_ORDER_TYXZ) {
|
||||
LLAMA_LOG_INFO("%s: rope section order = %s\n", __func__, llama_rope_section_order_name(hparams.rope_section_order).c_str());
|
||||
}
|
||||
if (!classifier_labels.empty()) {
|
||||
LLAMA_LOG_INFO("%s: n_cls_out = %u\n", __func__, hparams.n_cls_out);
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ enum llm_type {
|
||||
};
|
||||
|
||||
std::string llama_rope_scaling_type_name(llama_rope_scaling_type rope_scaling_type);
|
||||
std::string llama_rope_section_order_name(llama_rope_section_order rope_section_order);
|
||||
|
||||
// Map a GGUF activation-name string to llm_ffn_op_type. Returns `fallback` if
|
||||
// the string is empty or not recognized.
|
||||
|
||||
@@ -438,15 +438,17 @@ llama_model_bailingmoe3::graph_mtp::graph_mtp(const llama_model & model, const l
|
||||
const int64_t kv_lora_rank = hparams.n_lora_kv;
|
||||
const float kq_scale = 1.0f / sqrtf((float) qk_head_dim);
|
||||
|
||||
auto inp = std::make_unique<llm_graph_input_embd>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd, n_tokens);
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_inp(), n_tokens);
|
||||
ggml_set_input(inp->embd);
|
||||
ggml_set_name(inp->embd, "mtp_h_input");
|
||||
inp->h = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd, n_tokens);
|
||||
ggml_set_input(inp->h);
|
||||
ggml_set_name(inp->h, "mtp_h_input");
|
||||
|
||||
ggml_tensor * tok_embd = ggml_get_rows(ctx0, model.tok_embd, inp->tokens);
|
||||
ggml_tensor * h_norm = build_norm(inp->embd, layer.nextn.hnorm, nullptr, LLM_NORM_RMS, il);
|
||||
ggml_tensor * tok_embd = ubatch.token ? ggml_get_rows(ctx0, model.tok_embd, inp->tokens) : inp->embd;
|
||||
ggml_tensor * h_norm = build_norm(inp->h, layer.nextn.hnorm, nullptr, LLM_NORM_RMS, il);
|
||||
ggml_tensor * e_norm = build_norm(tok_embd, layer.nextn.enorm, nullptr, LLM_NORM_RMS, il);
|
||||
ggml_tensor * cur = ggml_mul_mat(ctx0, layer.nextn.eh_proj, ggml_concat(ctx0, e_norm, h_norm, 0));
|
||||
cb(cur, "mtp_eh_proj", il);
|
||||
|
||||
@@ -297,7 +297,7 @@ llama_model_cohere2moe::graph_mtp::graph_mtp(const llama_model & model, const ll
|
||||
const llm_norm_type cohere2moe_norm_type = hparams.f_norm_rms_eps == 0.0f ? LLM_NORM : LLM_NORM_RMS;
|
||||
|
||||
// TODO: extract in a common llm_graph_context::build_inp_embd_h()
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
@@ -206,7 +206,7 @@ llama_model_deepseek2::graph_mtp::graph_mtp(const llama_model & model, const llm
|
||||
GGML_ASSERT(layer.ffn_down_shexp);
|
||||
GGML_ASSERT(layer.ffn_up_shexp);
|
||||
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
@@ -520,7 +520,7 @@ llama_model_deepseek32::graph_mtp::graph_mtp(const llama_model & model, const ll
|
||||
const float kq_scale = 1.0f * mscale * mscale / sqrtf(float(n_embd_head_k));
|
||||
|
||||
// TODO: extract in a common llm_graph_context::build_inp_embd_h()
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
@@ -1378,20 +1378,26 @@ llama_model_deepseek4::graph_mtp::graph_mtp(const llama_model & model, const llm
|
||||
GGML_ASSERT(layer.nextn.enorm && "MTP block missing nextn.enorm");
|
||||
GGML_ASSERT(layer.nextn.hnorm && "MTP block missing nextn.hnorm");
|
||||
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_out());
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd_out());
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_out(), n_tokens);
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_inp(), n_tokens);
|
||||
ggml_set_input(inp->embd);
|
||||
|
||||
inp->h = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_out(), n_tokens);
|
||||
ggml_set_input(inp->h);
|
||||
ggml_set_name(inp->h, "mtp_h_input");
|
||||
|
||||
ggml_tensor * tok_embd_w = layer.nextn.embed_tokens ? layer.nextn.embed_tokens : model.tok_embd;
|
||||
ggml_tensor * tok_embd = ggml_get_rows(ctx0, tok_embd_w, inp->tokens);
|
||||
ggml_tensor * tok_embd;
|
||||
if (ubatch.token) {
|
||||
ggml_tensor * tok_embd_w = layer.nextn.embed_tokens ? layer.nextn.embed_tokens : model.tok_embd;
|
||||
|
||||
tok_embd = ggml_get_rows(ctx0, tok_embd_w, inp->tokens);
|
||||
} else {
|
||||
tok_embd = inp->embd;
|
||||
}
|
||||
cb(tok_embd, "mtp_tok_embd", il);
|
||||
|
||||
ggml_tensor * h_state = ggml_reshape_3d(ctx0, inp->h, n_embd, hc, n_tokens);
|
||||
|
||||
@@ -86,9 +86,10 @@ llama_model_gemma4_assistant::graph::graph(const llama_model & model, const llm_
|
||||
const int64_t n_embd_backbone = hparams.n_embd_inp();
|
||||
|
||||
ggml_tensor * inp_tokens;
|
||||
ggml_tensor * inp_embd;
|
||||
ggml_tensor * inp_h;
|
||||
{
|
||||
auto inp = std::make_unique<llm_graph_input_embd>(n_embd_backbone);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(n_embd_backbone, n_embd_backbone);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, ubatch.n_tokens);
|
||||
cb(inp->tokens, "inp_tokens", -1);
|
||||
@@ -97,18 +98,23 @@ llama_model_gemma4_assistant::graph::graph(const llama_model & model, const llm_
|
||||
res->t_inp_tokens = inp->tokens;
|
||||
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_embd_backbone, ubatch.n_tokens);
|
||||
cb(inp->embd, "inp_h", -1);
|
||||
cb(inp->embd, "inp_embd", -1);
|
||||
ggml_set_input(inp->embd);
|
||||
inp_h = inp->embd;
|
||||
inp_embd = inp->embd;
|
||||
res->t_inp_embd = inp->embd;
|
||||
|
||||
inp->h = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_embd_backbone, ubatch.n_tokens);
|
||||
cb(inp->h, "inp_h", -1);
|
||||
ggml_set_input(inp->h);
|
||||
inp_h = inp->h;
|
||||
|
||||
res->add_input(std::move(inp));
|
||||
}
|
||||
|
||||
GGML_ASSERT(cparams.ctx_other != nullptr);
|
||||
const auto * model_other = llama_get_model(cparams.ctx_other);
|
||||
|
||||
ggml_tensor * x = ggml_get_rows(ctx0, model_other->tok_embd, inp_tokens);
|
||||
ggml_tensor * x = ubatch.token ? ggml_get_rows(ctx0, model_other->tok_embd, inp_tokens) : inp_embd;
|
||||
x = ggml_scale(ctx0, x, sqrtf((float) n_embd_backbone));
|
||||
cb(x, "inp_embd_target", -1);
|
||||
|
||||
|
||||
@@ -560,7 +560,7 @@ llama_model_glm_dsa::graph_mtp::graph_mtp(const llama_model & model, const llm_g
|
||||
const float kq_scale = 1.0f * mscale * mscale / sqrtf(float(n_embd_head_k));
|
||||
|
||||
// TODO: extract in a common llm_graph_context::build_inp_embd_h()
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
@@ -143,7 +143,7 @@ llama_model_glm4_moe::graph_mtp::graph_mtp(const llama_model & model, const llm_
|
||||
GGML_ASSERT(layer.nextn.hnorm && "MTP block missing nextn.hnorm");
|
||||
GGML_ASSERT(layer.ffn_gate_inp && "MTP block missing ffn_gate_inp");
|
||||
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
@@ -568,20 +568,25 @@ llama_model_glm5_next::graph_mtp::graph_mtp(const llama_model & model, const llm
|
||||
|
||||
ggml_tensor * inp_out_ids = build_inp_out_ids();
|
||||
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_embd, n_tokens);
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_inp(), n_tokens);
|
||||
ggml_set_input(inp->embd);
|
||||
|
||||
inp->h = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_embd, n_tokens);
|
||||
ggml_set_input(inp->h);
|
||||
ggml_set_name(inp->h, "mtp_h_input");
|
||||
|
||||
ggml_tensor * tok_embd = ggml_get_rows(ctx0,
|
||||
layer.nextn.embed_tokens ? layer.nextn.embed_tokens : model.tok_embd, inp->tokens);
|
||||
ggml_tensor * tok_embd;
|
||||
if (ubatch.token) {
|
||||
tok_embd = ggml_get_rows(ctx0,
|
||||
layer.nextn.embed_tokens ? layer.nextn.embed_tokens : model.tok_embd, inp->tokens);
|
||||
} else {
|
||||
tok_embd = inp->embd;
|
||||
}
|
||||
cb(tok_embd, "mtp_tok_embd", il);
|
||||
|
||||
ggml_tensor * h = inp->h;
|
||||
|
||||
@@ -245,19 +245,22 @@ llama_model_hy_v3::graph_mtp::graph_mtp(const llama_model & model, const llm_gra
|
||||
GGML_ASSERT(layer.nextn.enorm && "MTP block missing nextn.enorm");
|
||||
GGML_ASSERT(layer.nextn.hnorm && "MTP block missing nextn.hnorm");
|
||||
|
||||
auto inp = std::make_unique<llm_graph_input_embd>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd, n_tokens);
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_inp(), n_tokens);
|
||||
ggml_set_input(inp->embd);
|
||||
ggml_set_name(inp->embd, "mtp_h_input");
|
||||
|
||||
inp->h = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd, n_tokens);
|
||||
ggml_set_input(inp->h);
|
||||
ggml_set_name(inp->h, "mtp_h_input");
|
||||
|
||||
ggml_tensor * tok_embd_w = layer.nextn.embed_tokens ? layer.nextn.embed_tokens : model.tok_embd;
|
||||
|
||||
ggml_tensor * h_input = inp->embd;
|
||||
ggml_tensor * tok_embd = ggml_get_rows(ctx0, tok_embd_w, inp->tokens);
|
||||
ggml_tensor * h_input = inp->h;
|
||||
ggml_tensor * tok_embd = ubatch.token ? ggml_get_rows(ctx0, tok_embd_w, inp->tokens) : inp->embd;
|
||||
cb(tok_embd, "mtp_tok_embd", il);
|
||||
|
||||
res->add_input(std::move(inp));
|
||||
|
||||
@@ -282,18 +282,21 @@ llama_model_mimo2::graph_mtp::graph_mtp(const llama_model & model, const llm_gra
|
||||
const float freq_scale_l = model.get_rope_freq_scale(cparams, il);
|
||||
const float v_scale = hparams.f_attn_value_scale;
|
||||
|
||||
auto inp = std::make_unique<llm_graph_input_embd>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd, n_tokens);
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_inp(), n_tokens);
|
||||
ggml_set_input(inp->embd);
|
||||
ggml_set_name(inp->embd, "mtp_h_input");
|
||||
|
||||
inp->h = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd, n_tokens);
|
||||
ggml_set_input(inp->h);
|
||||
ggml_set_name(inp->h, "mtp_h_input");
|
||||
|
||||
ggml_tensor * tok_embd_w = layer.nextn.embed_tokens ? layer.nextn.embed_tokens : model.tok_embd;
|
||||
ggml_tensor * h_input = inp->embd;
|
||||
ggml_tensor * tok_embd = ggml_get_rows(ctx0, tok_embd_w, inp->tokens);
|
||||
ggml_tensor * h_input = inp->h;
|
||||
ggml_tensor * tok_embd = ubatch.token ? ggml_get_rows(ctx0, tok_embd_w, inp->tokens) : inp->embd;
|
||||
cb(tok_embd, "mtp_tok_embd", il);
|
||||
|
||||
res->add_input(std::move(inp));
|
||||
|
||||
@@ -25,7 +25,7 @@ llama_model_nemotron_h_moe::graph_mtp::graph_mtp(const llama_model & model, cons
|
||||
ggml_tensor * tok_embd_w = layer.nextn.embed_tokens ? layer.nextn.embed_tokens : model.tok_embd;
|
||||
GGML_ASSERT(tok_embd_w != nullptr && "NEMOTRON_H_MOE MTP requires token embeddings");
|
||||
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
@@ -518,7 +518,7 @@ llama_model_qwen35::graph_mtp::graph_mtp(const llama_model & model, const llm_gr
|
||||
std::copy(std::begin(hparams.rope_sections), std::begin(hparams.rope_sections) + 4, sections);
|
||||
|
||||
// TODO: extract in a common llm_graph_context::build_inp_embd_h()
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
@@ -568,7 +568,7 @@ llama_model_qwen35moe::graph_mtp::graph_mtp(const llama_model & model, const llm
|
||||
std::copy(std::begin(hparams.rope_sections), std::begin(hparams.rope_sections) + 4, sections);
|
||||
|
||||
// TODO: extract in a common llm_graph_context::build_inp_embd_h()
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
@@ -642,7 +642,7 @@ llama_model_qwen3next::graph_mtp::graph_mtp(const llama_model & model, const llm
|
||||
GGML_ASSERT(layer.ffn_gate_inp && "MTP block missing ffn_gate_inp");
|
||||
|
||||
// TODO: extract in a common llm_graph_context::build_inp_embd_h()
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
@@ -540,19 +540,24 @@ llama_model_qwen4exp::graph_mtp::graph_mtp(const llama_model & model, const llm_
|
||||
int sections[4];
|
||||
std::copy(std::begin(hparams.rope_sections), std::begin(hparams.rope_sections) + 4, sections);
|
||||
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_out());
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd_out());
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_out(), n_tokens);
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_inp(), n_tokens);
|
||||
ggml_set_input(inp->embd);
|
||||
|
||||
inp->h = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_out(), n_tokens);
|
||||
ggml_set_input(inp->h);
|
||||
ggml_set_name(inp->h, "mtp_h_input");
|
||||
|
||||
ggml_tensor * tok_embd = ggml_get_rows(ctx0, model.tok_embd, inp->tokens);
|
||||
ggml_tensor * tok_embd;
|
||||
if (ubatch.token) {
|
||||
tok_embd = ggml_get_rows(ctx0, model.tok_embd, inp->tokens);
|
||||
} else {
|
||||
tok_embd = inp->embd;
|
||||
}
|
||||
cb(tok_embd, "mtp_tok_embd", il);
|
||||
|
||||
ggml_tensor * h = inp->h;
|
||||
|
||||
@@ -380,19 +380,22 @@ llama_model_step35::graph_mtp::graph_mtp(const llama_model & model, const llm_gr
|
||||
const float freq_base_l = model.get_rope_freq_base(cparams, il);
|
||||
const float freq_scale_l = model.get_rope_freq_scale(cparams, il);
|
||||
|
||||
auto inp = std::make_unique<llm_graph_input_embd>(hparams.n_embd);
|
||||
auto inp = std::make_unique<llm_graph_input_embd_h>(hparams.n_embd_inp(), hparams.n_embd);
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd, n_tokens);
|
||||
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_inp(), n_tokens);
|
||||
ggml_set_input(inp->embd);
|
||||
ggml_set_name(inp->embd, "mtp_h_input");
|
||||
|
||||
inp->h = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd, n_tokens);
|
||||
ggml_set_input(inp->h);
|
||||
ggml_set_name(inp->h, "mtp_h_input");
|
||||
|
||||
ggml_tensor * tok_embd_w = layer.nextn.embed_tokens ? layer.nextn.embed_tokens : model.tok_embd;
|
||||
|
||||
ggml_tensor * h_input = inp->embd;
|
||||
ggml_tensor * tok_embd = ggml_get_rows(ctx0, tok_embd_w, inp->tokens);
|
||||
ggml_tensor * h_input = inp->h;
|
||||
ggml_tensor * tok_embd = ubatch.token ? ggml_get_rows(ctx0, tok_embd_w, inp->tokens) : inp->embd;
|
||||
cb(tok_embd, "mtp_tok_embd", il);
|
||||
|
||||
res->add_input(std::move(inp));
|
||||
|
||||
@@ -1132,7 +1132,7 @@ static void test_compat(testing & t) {
|
||||
}
|
||||
|
||||
static void test_mtp_embd_width(testing & t) {
|
||||
t.test("mtp_uses_n_embd_out", [&](testing & t) {
|
||||
t.test("mtp_keeps_n_embd_inp_and_takes_state_at_n_embd_out", [&](testing & t) {
|
||||
llama_hparams hparams = {};
|
||||
hparams.n_embd = 64;
|
||||
hparams.n_deepstack_layers = 2; // makes n_embd_inp() = 64 + 64*2 = 192
|
||||
@@ -1141,16 +1141,22 @@ static void test_mtp_embd_width(testing & t) {
|
||||
t.assert_equal("default context uses n_embd_inp (deepstack-aware)",
|
||||
(size_t) 192, llama_batch_ext_select_n_embd_inp(LLAMA_CONTEXT_TYPE_DEFAULT, LLM_ARCH_LLAMA, hparams));
|
||||
|
||||
t.assert_equal("MTP context uses n_embd_out instead (target-model hidden state width)",
|
||||
(size_t) 96, llama_batch_ext_select_n_embd_inp(LLAMA_CONTEXT_TYPE_MTP, LLM_ARCH_LLAMA, hparams));
|
||||
t.assert_equal("MTP context keeps n_embd_inp for the token embeddings",
|
||||
(size_t) 192, llama_batch_ext_select_n_embd_inp(LLAMA_CONTEXT_TYPE_MTP, LLM_ARCH_LLAMA, hparams));
|
||||
|
||||
t.assert_equal("MTP context takes the target hidden state at n_embd_out",
|
||||
(size_t) 96, llama_batch_ext_select_n_embd_state(LLAMA_CONTEXT_TYPE_MTP, hparams));
|
||||
|
||||
t.assert_equal("default context takes no state",
|
||||
(size_t) 0, llama_batch_ext_select_n_embd_state(LLAMA_CONTEXT_TYPE_DEFAULT, hparams));
|
||||
});
|
||||
|
||||
t.test("mtp_falls_back_to_n_embd_when_no_override", [&](testing & t) {
|
||||
t.test("mtp_state_falls_back_to_n_embd_when_no_override", [&](testing & t) {
|
||||
llama_hparams hparams = {};
|
||||
hparams.n_embd = 64; // no deepstack, no n_embd_out_impl override
|
||||
|
||||
t.assert_equal((size_t) 64, llama_batch_ext_select_n_embd_inp(LLAMA_CONTEXT_TYPE_DEFAULT, LLM_ARCH_LLAMA, hparams));
|
||||
t.assert_equal((size_t) 64, llama_batch_ext_select_n_embd_inp(LLAMA_CONTEXT_TYPE_MTP, LLM_ARCH_LLAMA, hparams));
|
||||
t.assert_equal((size_t) 64, llama_batch_ext_select_n_embd_state(LLAMA_CONTEXT_TYPE_MTP, hparams));
|
||||
});
|
||||
|
||||
t.test("dflash_uses_n_embd_inp_enc", [&](testing & t) {
|
||||
@@ -1165,8 +1171,8 @@ static void test_mtp_embd_width(testing & t) {
|
||||
t.assert_equal("other archs ignore n_embd_inp_enc",
|
||||
(size_t) 64, llama_batch_ext_select_n_embd_inp(LLAMA_CONTEXT_TYPE_DEFAULT, LLM_ARCH_LLAMA, hparams));
|
||||
|
||||
t.assert_equal("MTP takes precedence over DFlash",
|
||||
(size_t) 96, llama_batch_ext_select_n_embd_inp(LLAMA_CONTEXT_TYPE_MTP, LLM_ARCH_DFLASH, hparams));
|
||||
t.assert_equal("MTP context does not change the DFlash input width",
|
||||
(size_t) 128, llama_batch_ext_select_n_embd_inp(LLAMA_CONTEXT_TYPE_MTP, LLM_ARCH_DFLASH, hparams));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,10 @@ struct clip_graph {
|
||||
|
||||
ggml_tensor * build_inp_raw(int channels = 3);
|
||||
|
||||
// f16 if flash attn is enabled, set it with set_input_attn_mask()
|
||||
// idx is only needed when the graph has more than one mask
|
||||
ggml_tensor * build_inp_attn_mask(int64_t n_kv, int64_t n_q, int idx = 0);
|
||||
|
||||
ggml_tensor * build_norm(
|
||||
ggml_tensor * cur,
|
||||
ggml_tensor * mw,
|
||||
@@ -162,4 +166,7 @@ struct clip_graph {
|
||||
// Generic function to stack frames for audio processing
|
||||
// Abstracts out the StackAudioFrames logic used by ultravox
|
||||
ggml_tensor * build_stack(ggml_tensor * cur, int32_t stack_factor, int32_t n_embed);
|
||||
|
||||
// append the separators of img.suffix_type after the image tokens
|
||||
ggml_tensor * build_suffix(ggml_tensor * cur);
|
||||
};
|
||||
|
||||
+41
-3
@@ -68,6 +68,7 @@
|
||||
|
||||
#define KEY_MM_PATCH_MERGE_TYPE "clip.vision.mm_patch_merge_type"
|
||||
#define KEY_IMAGE_GRID_PINPOINTS "clip.vision.image_grid_pinpoints"
|
||||
#define KEY_MAX_SLICE_NUMS "clip.vision.max_slice_nums"
|
||||
#define KEY_WIN_ATTN_PATTERN "clip.vision.n_wa_pattern"
|
||||
#define KEY_WIN_ATTN_LAYER_INDEXES "clip.vision.wa_layer_indexes"
|
||||
#define KEY_WA_PATTERN_MODE "clip.vision.wa_pattern_mode"
|
||||
@@ -146,6 +147,7 @@
|
||||
#define TN_MVLM_PROJ_PEG "mm.model.peg.%d.%s"
|
||||
#define TN_IMAGE_NEWLINE "v.image_newline"
|
||||
#define TN_IMAGE_SEPERATOR "v.view_seperator"
|
||||
#define TN_TOK_EMBD_SEP "v.tok_embd_sep"
|
||||
#define TN_MM_INP_NORM "mm.input_norm.weight"
|
||||
#define TN_MM_INP_NORM_B "mm.input_norm.bias"
|
||||
#define TN_MM_INP_PROJ "mm.input_projection.weight" // gemma3
|
||||
@@ -500,6 +502,7 @@ enum projector_type {
|
||||
PROJECTOR_TYPE_PARAKEET,
|
||||
PROJECTOR_TYPE_EXAONE4_5,
|
||||
PROJECTOR_TYPE_MINICPMV4_6,
|
||||
PROJECTOR_TYPE_MINICPMV4_7,
|
||||
PROJECTOR_TYPE_GRANITE_SPEECH,
|
||||
PROJECTOR_TYPE_MIMOVL,
|
||||
PROJECTOR_TYPE_MINIMAX_M3,
|
||||
@@ -569,6 +572,7 @@ static std::map<projector_type, std::string> PROJECTOR_TYPE_NAMES = {
|
||||
{ PROJECTOR_TYPE_EXAONE4_5, "exaone4_5"},
|
||||
{ PROJECTOR_TYPE_HUNYUANVL, "hunyuanvl"},
|
||||
{ PROJECTOR_TYPE_MINICPMV4_6, "minicpmv4_6"},
|
||||
{ PROJECTOR_TYPE_MINICPMV4_7, "minicpmv4_7"},
|
||||
{ PROJECTOR_TYPE_GRANITE_SPEECH, "granite_speech"},
|
||||
{ PROJECTOR_TYPE_MIMOVL, "mimovl"},
|
||||
{ PROJECTOR_TYPE_MINIMAX_M3, "minimax_m3"},
|
||||
@@ -660,6 +664,38 @@ struct clip_image_u8 {
|
||||
|
||||
struct mtmd_serialization; // forward declaration
|
||||
|
||||
// separators appended after the image tokens of one entry, as rows of v.tok_embd_sep
|
||||
enum clip_suffix_type : int32_t {
|
||||
CLIP_SUFFIX_NONE = 0,
|
||||
// MiniCPM-V 4.7 tiles
|
||||
CLIP_SUFFIX_MINICPMV_OV, // </image>
|
||||
CLIP_SUFFIX_MINICPMV_OV_SLICE, // </image><slice>
|
||||
CLIP_SUFFIX_MINICPMV_SLICE, // </slice><slice>
|
||||
CLIP_SUFFIX_MINICPMV_ROW_END, // </slice>\n<slice>
|
||||
CLIP_SUFFIX_MINICPMV_LAST, // </slice>
|
||||
CLIP_SUFFIX_COUNT,
|
||||
};
|
||||
|
||||
// rows of v.tok_embd_sep for each suffix type
|
||||
// MiniCPM-V 4.7 rows (set by the converter): 0 = </image>, 1 = <slice>, 2 = </slice>, 3 = \n
|
||||
static inline const std::vector<int> & clip_suffix_rows(clip_suffix_type type) {
|
||||
static const std::vector<int> none;
|
||||
static const std::vector<int> minicpmv_ov = { 0 };
|
||||
static const std::vector<int> minicpmv_ov_slice = { 0, 1 };
|
||||
static const std::vector<int> minicpmv_slice = { 2, 1 };
|
||||
static const std::vector<int> minicpmv_row_end = { 2, 3, 1 };
|
||||
static const std::vector<int> minicpmv_last = { 2 };
|
||||
switch (type) {
|
||||
case CLIP_SUFFIX_NONE: return none;
|
||||
case CLIP_SUFFIX_MINICPMV_OV: return minicpmv_ov;
|
||||
case CLIP_SUFFIX_MINICPMV_OV_SLICE: return minicpmv_ov_slice;
|
||||
case CLIP_SUFFIX_MINICPMV_SLICE: return minicpmv_slice;
|
||||
case CLIP_SUFFIX_MINICPMV_ROW_END: return minicpmv_row_end;
|
||||
case CLIP_SUFFIX_MINICPMV_LAST: return minicpmv_last;
|
||||
default: GGML_ABORT("invalid suffix type");
|
||||
}
|
||||
}
|
||||
|
||||
// For images, buf.size() == nx*ny*3
|
||||
// Memory layout: RGBRGBRGB...
|
||||
// For seq, buf.size() == nx*ny*3*nt
|
||||
@@ -675,10 +711,12 @@ struct clip_image_f32 {
|
||||
// deepseek4v: number of leading IMAGE_PAD embeddings, aligns IMAGE_START to the LLM compressor ratio
|
||||
// depends on the chunk position, set at tokenize time (see mtmd_tokenizer::add_media)
|
||||
int32_t lead_pad = 0;
|
||||
// separators appended after the image tokens
|
||||
clip_suffix_type suffix_type = CLIP_SUFFIX_NONE;
|
||||
|
||||
// llava-next "anyres" tiling, used by Granite4 Vision
|
||||
// the whole grid is encoded and assembled in a single graph
|
||||
// NOTE: excluded from serialized: a deserialized image is always a placeholder, which is never encoded
|
||||
// tile grid of the image group this entry belongs to
|
||||
// llava-next "anyres" (Granite4 Vision): the whole grid is encoded and assembled in a single graph
|
||||
// MiniCPM-V 4.7: set on the overview entry, the decoder positions of all tiles are derived from it
|
||||
struct anyres_info {
|
||||
int grid_x = 0; // tiles per row, 0 means the image is not tiled
|
||||
int grid_y = 0; // tiles per column
|
||||
|
||||
@@ -71,6 +71,7 @@ struct clip_hparams {
|
||||
std::vector<clip_image_size> image_res_candidates;
|
||||
int32_t preproc_min_tiles = 0;
|
||||
int32_t preproc_max_tiles = 0;
|
||||
int32_t max_slice_nums = 9; // llava-uhd slice cap; per-model, carried in the GGUF
|
||||
int32_t preproc_tile_size = 0; // local tile size (deepseek-ocr)
|
||||
resize_algo image_resize_algo_rf = RESIZE_ALGO_BICUBIC;
|
||||
resize_algo image_resize_algo_ov = RESIZE_ALGO_BICUBIC;
|
||||
@@ -614,6 +615,7 @@ struct clip_model {
|
||||
|
||||
ggml_tensor * image_newline = nullptr;
|
||||
ggml_tensor * view_seperator = nullptr;
|
||||
ggml_tensor * tok_embd_sep = nullptr; // [n_embd_text, n_sep] rows of the text model tok_embd (MiniCPM-V 4.7)
|
||||
|
||||
|
||||
// Yi type models with mlp+normalization projection
|
||||
|
||||
+67
-13
@@ -588,6 +588,18 @@ ggml_tensor * clip_graph::build_inp_raw(int channels) {
|
||||
return inp_raw;
|
||||
}
|
||||
|
||||
static std::string get_attn_mask_name(int idx) {
|
||||
return idx == 0 ? "attn_mask" : "attn_mask_" + std::to_string(idx);
|
||||
}
|
||||
|
||||
ggml_tensor * clip_graph::build_inp_attn_mask(int64_t n_kv, int64_t n_q, int idx) {
|
||||
const ggml_type type = flash_attn_type == CLIP_FLASH_ATTN_TYPE_ENABLED ? GGML_TYPE_F16 : GGML_TYPE_F32;
|
||||
ggml_tensor * mask = ggml_new_tensor_2d(ctx0, type, n_kv, n_q);
|
||||
ggml_set_name(mask, get_attn_mask_name(idx).c_str());
|
||||
ggml_set_input(mask);
|
||||
return mask;
|
||||
}
|
||||
|
||||
ggml_tensor * clip_graph::build_norm(
|
||||
ggml_tensor * cur,
|
||||
ggml_tensor * mw,
|
||||
@@ -777,9 +789,8 @@ ggml_tensor * clip_graph::build_attn(
|
||||
|
||||
k = ggml_cast(ctx0, k, GGML_TYPE_F16);
|
||||
v = ggml_cast(ctx0, v, GGML_TYPE_F16);
|
||||
if (kq_mask) {
|
||||
kq_mask = ggml_cast(ctx0, kq_mask, GGML_TYPE_F16);
|
||||
}
|
||||
// mask must be f16 here, use build_inp_attn_mask()
|
||||
GGML_ASSERT(!kq_mask || kq_mask->type == GGML_TYPE_F16);
|
||||
|
||||
cur = ggml_flash_attn_ext(ctx0, q, k, v, kq_mask, kq_scale, 0.0f, 0.0f);
|
||||
ggml_prec_set_acc(cur, GGML_PREC_F32);
|
||||
@@ -898,6 +909,16 @@ ggml_tensor * clip_graph::build_stack(ggml_tensor * cur, int32_t stack_factor, i
|
||||
|
||||
// aka pixel_shuffle / pixel_unshuffle / patch_merger (Kimi-VL)
|
||||
// support dynamic resolution
|
||||
ggml_tensor * clip_graph::build_suffix(ggml_tensor * cur) {
|
||||
for (int idx : clip_suffix_rows(img.suffix_type)) {
|
||||
GGML_ASSERT(model.tok_embd_sep && idx < model.tok_embd_sep->ne[1]);
|
||||
ggml_tensor * row = ggml_view_2d(ctx0, model.tok_embd_sep, model.tok_embd_sep->ne[0], 1,
|
||||
model.tok_embd_sep->nb[1], idx * model.tok_embd_sep->nb[1]);
|
||||
cur = ggml_concat(ctx0, cur, ggml_cast(ctx0, row, cur->type), 1);
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
ggml_tensor * clip_graph::build_patch_merge_permute(ggml_tensor * cur, int scale_factor) {
|
||||
GGML_ASSERT(scale_factor > 1);
|
||||
|
||||
@@ -1009,6 +1030,7 @@ static std::unique_ptr<clip_graph> clip_get_graph_builder(clip_ctx * ctx, const
|
||||
builder = std::make_unique<clip_graph_minicpmv>(ctx, img);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_MINICPMV4_6:
|
||||
case PROJECTOR_TYPE_MINICPMV4_7:
|
||||
{
|
||||
builder = std::make_unique<clip_graph_minicpmv4_6>(ctx, img);
|
||||
} break;
|
||||
@@ -1321,6 +1343,7 @@ struct clip_model_loader {
|
||||
if (is_vision) {
|
||||
get_u32(KEY_IMAGE_SIZE, hparams.image_size);
|
||||
get_u32(KEY_PATCH_SIZE, hparams.patch_size);
|
||||
get_u32(KEY_MAX_SLICE_NUMS, hparams.max_slice_nums, false);
|
||||
get_i32(KEY_MINICPMV_VERSION, hparams.minicpmv_version, false); // legacy
|
||||
get_u32(KEY_MINICPMV_QUERY_NUM, hparams.minicpmv_query_num, false);
|
||||
if (hparams.minicpmv_query_num == 0) {
|
||||
@@ -1460,13 +1483,18 @@ struct clip_model_loader {
|
||||
}
|
||||
} break;
|
||||
case PROJECTOR_TYPE_MINICPMV4_6:
|
||||
case PROJECTOR_TYPE_MINICPMV4_7:
|
||||
{
|
||||
// MiniCPM-V 4.6 unified merger projector
|
||||
// MiniCPM-V 4.6/4.7 unified merger projector
|
||||
// ViT merger 2x2 + final merger 2x2 = 4x spatial merge per dimension
|
||||
hparams.n_merge = 4;
|
||||
get_u32(KEY_PROJ_SCALE_FACTOR, hparams.n_merge, false);
|
||||
GGML_ASSERT(hparams.n_merge == 2 || hparams.n_merge == 4);
|
||||
|
||||
// no padding: the reference stretches the refined image to the target size
|
||||
hparams.image_pad_ov = PAD_NONE;
|
||||
hparams.image_pad_rf = PAD_NONE;
|
||||
|
||||
// borrow wa_layer_indexes for vit_merger insertion point
|
||||
std::vector<int> wa_layer_indexes_vec;
|
||||
get_arr_int(KEY_WIN_ATTN_LAYER_INDEXES, wa_layer_indexes_vec, false);
|
||||
@@ -2382,6 +2410,7 @@ struct clip_model_loader {
|
||||
|| model.proj_type == PROJECTOR_TYPE_IDEFICS3
|
||||
|| model.proj_type == PROJECTOR_TYPE_MINICPMV
|
||||
|| model.proj_type == PROJECTOR_TYPE_MINICPMV4_6
|
||||
|| model.proj_type == PROJECTOR_TYPE_MINICPMV4_7
|
||||
) && layer.ff_up_w && layer.ff_down_w && layer.ff_down_w->ne[0] == hparams.n_embd;
|
||||
if (is_ffn_swapped) {
|
||||
// swap up and down weights
|
||||
@@ -2484,6 +2513,7 @@ struct clip_model_loader {
|
||||
model.mm_model_ln_post_b = get_tensor(string_format(TN_MINICPMV_LN, "post", "bias"));
|
||||
} break;
|
||||
case PROJECTOR_TYPE_MINICPMV4_6:
|
||||
case PROJECTOR_TYPE_MINICPMV4_7:
|
||||
{
|
||||
const bool merger_required = hparams.n_merge == 4;
|
||||
auto get_merger_tensor = [&](const std::string & name, bool required = true) {
|
||||
@@ -2515,6 +2545,7 @@ struct clip_model_loader {
|
||||
model.mm_ffn_up_b = get_tensor(string_format(TN_MM_UP, "bias"), false);
|
||||
model.mm_ffn_down_w = get_tensor(string_format(TN_MM_DOWN, "weight"));
|
||||
model.mm_ffn_down_b = get_tensor(string_format(TN_MM_DOWN, "bias"), false);
|
||||
model.tok_embd_sep = get_tensor(TN_TOK_EMBD_SEP, model.proj_type == PROJECTOR_TYPE_MINICPMV4_7);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_GLM_EDGE:
|
||||
{
|
||||
@@ -4158,6 +4189,8 @@ int clip_n_output_tokens_x(const clip_ctx * ctx, const clip_image_f32 * img) {
|
||||
case PROJECTOR_TYPE_MUSE_GLIMMER:
|
||||
return (img->nx() / params.patch_size) / 2;
|
||||
case PROJECTOR_TYPE_STEP3VL:
|
||||
case PROJECTOR_TYPE_MINICPMV4_6:
|
||||
case PROJECTOR_TYPE_MINICPMV4_7:
|
||||
return img->nx() / (params.patch_size * params.n_merge);
|
||||
case PROJECTOR_TYPE_DEEPSEEKOCR:
|
||||
case PROJECTOR_TYPE_DEEPSEEKOCR2:
|
||||
@@ -4186,6 +4219,8 @@ int clip_n_output_tokens_y(const clip_ctx * ctx, const clip_image_f32 * img) {
|
||||
case PROJECTOR_TYPE_MUSE_GLIMMER:
|
||||
return (img->ny() / params.patch_size) / 2;
|
||||
case PROJECTOR_TYPE_STEP3VL:
|
||||
case PROJECTOR_TYPE_MINICPMV4_6:
|
||||
case PROJECTOR_TYPE_MINICPMV4_7:
|
||||
return img->ny() / (params.patch_size * params.n_merge);
|
||||
default:
|
||||
break;
|
||||
@@ -4251,6 +4286,7 @@ int clip_n_output_tokens(const clip_ctx * ctx, const clip_image_f32 * img) {
|
||||
}
|
||||
} break;
|
||||
case PROJECTOR_TYPE_MINICPMV4_6:
|
||||
case PROJECTOR_TYPE_MINICPMV4_7:
|
||||
{
|
||||
n_patches /= params.n_merge * params.n_merge;
|
||||
} break;
|
||||
@@ -4502,6 +4538,8 @@ int clip_n_output_tokens(const clip_ctx * ctx, const clip_image_f32 * img) {
|
||||
GGML_ABORT("unsupported projector type");
|
||||
}
|
||||
|
||||
n_patches += (int) clip_suffix_rows(img->suffix_type).size();
|
||||
|
||||
return n_patches;
|
||||
}
|
||||
|
||||
@@ -4591,6 +4629,20 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
ggml_backend_tensor_set(cur, values.data(), 0, ggml_nbytes(cur));
|
||||
};
|
||||
|
||||
// mask from build_inp_attn_mask(), f16 if flash attn is enabled
|
||||
auto set_input_attn_mask = [&get_inp_tensor](const std::vector<float> & values, int idx = 0) {
|
||||
ggml_tensor * cur = get_inp_tensor(get_attn_mask_name(idx).c_str());
|
||||
GGML_ASSERT(ggml_nelements(cur) == (int64_t)values.size());
|
||||
if (cur->type == GGML_TYPE_F16) {
|
||||
std::vector<ggml_fp16_t> values_f16(values.size());
|
||||
ggml_fp32_to_fp16_row(values.data(), values_f16.data(), values.size());
|
||||
ggml_backend_tensor_set(cur, values_f16.data(), 0, ggml_nbytes(cur));
|
||||
} else {
|
||||
GGML_ASSERT(cur->type == GGML_TYPE_F32);
|
||||
ggml_backend_tensor_set(cur, values.data(), 0, ggml_nbytes(cur));
|
||||
}
|
||||
};
|
||||
|
||||
auto set_input_i32 = [&get_inp_tensor](const char * name, std::vector<int32_t> & values) {
|
||||
ggml_tensor * cur = get_inp_tensor(name);
|
||||
GGML_ASSERT(cur->type == GGML_TYPE_I32);
|
||||
@@ -4639,7 +4691,7 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
}
|
||||
}
|
||||
}
|
||||
set_input_f32("kq_mask", mask);
|
||||
set_input_attn_mask(mask);
|
||||
};
|
||||
|
||||
// set input pixel values
|
||||
@@ -4753,7 +4805,7 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
off += s;
|
||||
}
|
||||
}
|
||||
set_input_f32("muse_glimmer_sp_mask", sp_mask);
|
||||
set_input_attn_mask(sp_mask);
|
||||
|
||||
// pixel-shuffle gather (original order): f*f spatial neighbours grouped
|
||||
std::vector<int32_t> dsp; dsp.reserve(n_tok);
|
||||
@@ -4811,6 +4863,7 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
set_input_f32("omega", omega);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_MINICPMV4_6:
|
||||
case PROJECTOR_TYPE_MINICPMV4_7:
|
||||
{
|
||||
const bool is_4x = hparams.n_merge == 2;
|
||||
|
||||
@@ -4876,7 +4929,7 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
}
|
||||
}
|
||||
}
|
||||
set_input_f32("vit_merger_window_mask", window_mask_data);
|
||||
set_input_attn_mask(window_mask_data);
|
||||
|
||||
// ViT merger 2x2 downsample indices
|
||||
auto vit_merger_ds_0 = make_ds_idx(0, 0, half_h, half_w, pos_w);
|
||||
@@ -5061,7 +5114,7 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
|
||||
set_input_i32("window_idx", idx);
|
||||
set_input_i32("inv_window_idx", inv_idx);
|
||||
set_input_f32("window_mask", mask);
|
||||
set_input_attn_mask(mask);
|
||||
} else {
|
||||
for (int i = 0; i < ph * pw; i++) {
|
||||
idx[i] = i;
|
||||
@@ -5172,7 +5225,7 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
set_input_i32("mimovl_positions_row", positions_row);
|
||||
set_input_i32("mimovl_positions_col", positions_col);
|
||||
set_input_f32("mimovl_idx_col", idx_col);
|
||||
set_input_f32("mimovl_window_mask", mask);
|
||||
set_input_attn_mask(mask);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_PIXTRAL:
|
||||
case PROJECTOR_TYPE_KIMIVL:
|
||||
@@ -5366,7 +5419,7 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
qwen2_mask[static_cast<size_t>(i) * seq_len + j] = zero ? 0.0f : -1e9f;
|
||||
}
|
||||
}
|
||||
set_input_f32("qwen2_attn_mask", qwen2_mask);
|
||||
set_input_attn_mask(qwen2_mask);
|
||||
}
|
||||
} break;
|
||||
case PROJECTOR_TYPE_GEMMA3:
|
||||
@@ -5612,8 +5665,8 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
window_mask[(size_t) q * n_pos + k] = (causal_ok && (q - k) <= window) ? 0.0f : neg_inf;
|
||||
}
|
||||
}
|
||||
set_input_f32("mimo_audio_full_mask", full_mask);
|
||||
set_input_f32("mimo_audio_window_mask", window_mask);
|
||||
set_input_attn_mask(full_mask, 0);
|
||||
set_input_attn_mask(window_mask, 1);
|
||||
|
||||
// input_local_transformer: block-diagonal mask + in-group positions
|
||||
{
|
||||
@@ -5636,7 +5689,7 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
local_mask[(size_t) q * n_padded + k] = same_group ? 0.0f : neg_inf;
|
||||
}
|
||||
}
|
||||
set_input_f32("mimo_audio_local_mask", local_mask);
|
||||
set_input_attn_mask(local_mask, 2);
|
||||
}
|
||||
} break;
|
||||
case PROJECTOR_TYPE_LFM2A:
|
||||
@@ -6055,6 +6108,7 @@ int clip_n_mmproj_embd(const struct clip_ctx * ctx) {
|
||||
case PROJECTOR_TYPE_MINICPMV:
|
||||
return ctx->model.mm_model_proj->ne[0];
|
||||
case PROJECTOR_TYPE_MINICPMV4_6:
|
||||
case PROJECTOR_TYPE_MINICPMV4_7:
|
||||
return ctx->model.mm_ffn_down_w->ne[1];
|
||||
case PROJECTOR_TYPE_GLM_EDGE:
|
||||
return ctx->model.mm_model_mlp_3_w->ne[1];
|
||||
|
||||
@@ -41,9 +41,7 @@ ggml_cgraph * clip_graph_deepseekocr2::build() {
|
||||
auto seq_len = inp->ne[1];
|
||||
|
||||
// qwen2 encoder attention mask
|
||||
ggml_tensor * attn_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, seq_len, seq_len);
|
||||
ggml_set_name(attn_mask, "qwen2_attn_mask");
|
||||
ggml_set_input(attn_mask);
|
||||
ggml_tensor * attn_mask = build_inp_attn_mask(seq_len, seq_len);
|
||||
|
||||
ggml_tensor * inp_pos = ggml_cast(ctx0, ggml_arange(ctx0, 0, seq_len, 1), GGML_TYPE_I32);
|
||||
|
||||
|
||||
@@ -58,13 +58,7 @@ ggml_cgraph * clip_graph_exaone4_5::build() {
|
||||
ggml_set_name(inv_window_idx, "inv_window_idx");
|
||||
ggml_set_input(inv_window_idx);
|
||||
|
||||
window_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_pos, n_pos);
|
||||
ggml_set_name(window_mask, "window_mask");
|
||||
ggml_set_input(window_mask);
|
||||
|
||||
if (flash_attn_type == CLIP_FLASH_ATTN_TYPE_ENABLED) {
|
||||
window_mask = ggml_cast(ctx0, window_mask, GGML_TYPE_F16);
|
||||
}
|
||||
window_mask = build_inp_attn_mask(n_pos, n_pos);
|
||||
}
|
||||
|
||||
ggml_tensor * inpL = inp;
|
||||
|
||||
@@ -21,13 +21,8 @@ ggml_cgraph * clip_graph_mimo_audio::build() {
|
||||
ggml_set_name(inp_pos, "mimo_audio_positions");
|
||||
ggml_set_input(inp_pos);
|
||||
|
||||
ggml_tensor * full_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_pos, n_pos);
|
||||
ggml_set_name(full_mask, "mimo_audio_full_mask");
|
||||
ggml_set_input(full_mask);
|
||||
|
||||
ggml_tensor * window_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_pos, n_pos);
|
||||
ggml_set_name(window_mask, "mimo_audio_window_mask");
|
||||
ggml_set_input(window_mask);
|
||||
ggml_tensor * full_mask = build_inp_attn_mask(n_pos, n_pos, 0);
|
||||
ggml_tensor * window_mask = build_inp_attn_mask(n_pos, n_pos, 1);
|
||||
|
||||
build_vit_opts opts;
|
||||
opts.attn_mask_layers.resize(n_layer);
|
||||
@@ -150,9 +145,7 @@ ggml_cgraph * clip_graph_mimo_audio::build() {
|
||||
ggml_set_name(local_pos, "mimo_audio_local_positions");
|
||||
ggml_set_input(local_pos);
|
||||
|
||||
ggml_tensor * local_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_padded, n_padded);
|
||||
ggml_set_name(local_mask, "mimo_audio_local_mask");
|
||||
ggml_set_input(local_mask);
|
||||
ggml_tensor * local_mask = build_inp_attn_mask(n_padded, n_padded, 2);
|
||||
|
||||
const float local_rope_theta = 640000.0f; // audio_config.rope_theta (differs from the encoder's)
|
||||
auto apply_local_rope = [&](ggml_tensor * x) {
|
||||
|
||||
@@ -84,13 +84,7 @@ ggml_cgraph * clip_graph_mimovl::build() {
|
||||
ggml_tensor * idx_col = ggml_cast(ctx0, idx_col_f, GGML_TYPE_I32);
|
||||
ggml_tensor * idx_col_inv = ggml_argsort(ctx0, idx_col_f, GGML_SORT_ORDER_ASC);
|
||||
|
||||
ggml_tensor * window_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_pos, n_pos);
|
||||
ggml_set_name(window_mask, "mimovl_window_mask");
|
||||
ggml_set_input(window_mask);
|
||||
|
||||
ggml_tensor * window_mask_attn = (flash_attn_type == CLIP_FLASH_ATTN_TYPE_ENABLED)
|
||||
? ggml_cast(ctx0, window_mask, GGML_TYPE_F16)
|
||||
: window_mask;
|
||||
ggml_tensor * window_mask = build_inp_attn_mask(n_pos, n_pos);
|
||||
|
||||
// Reorder helper: permute patches at merge-unit granularity. The patch
|
||||
// sequence is laid out as n_units groups of merge_unit (=4) consecutive
|
||||
@@ -151,7 +145,7 @@ ggml_cgraph * clip_graph_mimovl::build() {
|
||||
cb(Kcur, "Kcur_rope", il);
|
||||
|
||||
// Full layers: plain attention. Windowed layers: banded mask and per-head sinks.
|
||||
ggml_tensor * mask = is_full ? nullptr : window_mask_attn;
|
||||
ggml_tensor * mask = is_full ? nullptr : window_mask;
|
||||
ggml_tensor * sinks = is_full ? nullptr : layer.attn_sinks;
|
||||
if (!is_full) {
|
||||
GGML_ASSERT(layer.attn_sinks != nullptr);
|
||||
|
||||
@@ -146,12 +146,7 @@ ggml_cgraph * clip_graph_minicpmv4_6::build() {
|
||||
// so each window-major group of 4 tokens only attends to itself)
|
||||
vit_merger_window_idx = add_i32_input("vit_merger_window_idx", n_pos);
|
||||
vit_merger_inv_window_idx = add_i32_input("vit_merger_inv_window_idx", n_pos);
|
||||
vit_merger_window_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_pos, n_pos);
|
||||
ggml_set_name(vit_merger_window_mask, "vit_merger_window_mask");
|
||||
ggml_set_input(vit_merger_window_mask);
|
||||
if (flash_attn_type == CLIP_FLASH_ATTN_TYPE_ENABLED) {
|
||||
vit_merger_window_mask = ggml_cast(ctx0, vit_merger_window_mask, GGML_TYPE_F16);
|
||||
}
|
||||
vit_merger_window_mask = build_inp_attn_mask(n_pos, n_pos);
|
||||
|
||||
// ViT merger 2x2 downsample gather indices
|
||||
vit_merger_ds_idx_0 = add_i32_input("vit_merger_ds_idx_0", n_ds);
|
||||
@@ -355,6 +350,8 @@ ggml_cgraph * clip_graph_minicpmv4_6::build() {
|
||||
inpL = cur;
|
||||
}
|
||||
|
||||
inpL = build_suffix(inpL);
|
||||
|
||||
ggml_build_forward_expand(gf, inpL);
|
||||
return gf;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
// muse_glimmer_sp_perm [n_tok] i32 : window grouping permutation (applied after ln_pre)
|
||||
// muse_glimmer_inv_perm [n_tok] i32 : inverse of sp_perm (applied after blocks)
|
||||
// muse_glimmer_ds_perm [n_tok] i32 : pixel-shuffle gather (original order)
|
||||
// muse_glimmer_sp_mask [n_tok, n_tok] f32 : block-diagonal window mask (sparse layers)
|
||||
// attn_mask [n_tok, n_tok] f32 (f16 with flash attn) : block-diagonal window mask (sparse layers)
|
||||
ggml_cgraph * clip_graph_muse_glimmer::build() {
|
||||
const int ds = hparams.n_merge; // downsample factor (2)
|
||||
const int sf = hparams.muse_glimmer_sparse_factor; // 4
|
||||
@@ -31,9 +31,7 @@ ggml_cgraph * clip_graph_muse_glimmer::build() {
|
||||
ggml_tensor * inv_perm = inp_i32("muse_glimmer_inv_perm", n_tok);
|
||||
ggml_tensor * ds_perm = inp_i32("muse_glimmer_ds_perm", n_tok);
|
||||
|
||||
ggml_tensor * sp_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_tok, n_tok);
|
||||
ggml_set_name(sp_mask, "muse_glimmer_sp_mask");
|
||||
ggml_set_input(sp_mask);
|
||||
ggml_tensor * sp_mask = build_inp_attn_mask(n_tok, n_tok);
|
||||
|
||||
// patchify via build_inp (conv2d over raw pixels) + bilinear-resized learned pos-emb
|
||||
ggml_tensor * x = build_inp(); // [n_embd, n_tok, 1]
|
||||
|
||||
@@ -225,6 +225,9 @@ ggml_cgraph * clip_graph_pockettts_gen::build() {
|
||||
keep = ggml_mul(ctx0, keep,
|
||||
ggml_step(ctx0, ggml_scale_bias(ctx0, ggml_add(ctx0, pos_k, base), 1.0f, 0.5f - (float) prefix)));
|
||||
ggml_tensor * kq_mask = ggml_reshape_4d(ctx0, ggml_log(ctx0, keep), n_kv, n_pos, 1, 1);
|
||||
if (flash_attn_type == CLIP_FLASH_ATTN_TYPE_ENABLED) {
|
||||
kq_mask = ggml_cast(ctx0, kq_mask, GGML_TYPE_F16);
|
||||
}
|
||||
|
||||
for (int il = 0; il < n_layer; il++) {
|
||||
const auto & layer = model.gen_tfm_layers[il];
|
||||
|
||||
@@ -53,9 +53,7 @@ ggml_cgraph * clip_graph_pockettts_spkenc::build() {
|
||||
ggml_set_input(inp_pos);
|
||||
|
||||
// the mimi transformer is causal with a sliding window, see _build_attention_mask()
|
||||
ggml_tensor * kq_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, cur->ne[1], cur->ne[1]);
|
||||
ggml_set_name(kq_mask, "kq_mask");
|
||||
ggml_set_input(kq_mask);
|
||||
ggml_tensor * kq_mask = build_inp_attn_mask(cur->ne[1], cur->ne[1]);
|
||||
|
||||
for (int il = 0; il < n_layer; il++) {
|
||||
cur = tfm_layer_forward(cur, model.layers[il], inp_pos, kq_mask, il);
|
||||
|
||||
@@ -82,14 +82,7 @@ ggml_cgraph * clip_graph_qwen2vl::build() {
|
||||
ggml_set_name(inv_window_idx, "inv_window_idx");
|
||||
ggml_set_input(inv_window_idx);
|
||||
// mask for window attention
|
||||
window_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_pos, n_pos);
|
||||
ggml_set_name(window_mask, "window_mask");
|
||||
ggml_set_input(window_mask);
|
||||
|
||||
// if flash attn is used, we need to pad the mask and cast to f16
|
||||
if (flash_attn_type == CLIP_FLASH_ATTN_TYPE_ENABLED) {
|
||||
window_mask = ggml_cast(ctx0, window_mask, GGML_TYPE_F16);
|
||||
}
|
||||
window_mask = build_inp_attn_mask(n_pos, n_pos);
|
||||
|
||||
// inpL shape: [n_embd, n_patches_x * n_patches_y, batch_size]
|
||||
GGML_ASSERT(batch_size == 1);
|
||||
|
||||
@@ -109,7 +109,11 @@ ggml_tensor * clip_graph_qwen3tts_gen::code_gen::causal_mask_row(int64_t n_kv_pa
|
||||
ggml_tensor * keep = ggml_tri(ctx0, ones, GGML_TRI_TYPE_LOWER_DIAG);
|
||||
ggml_tensor * row = ggml_view_1d(ctx0, keep, n_kv_pad, (size_t) pos * keep->nb[1]);
|
||||
ggml_tensor * mask = ggml_log(ctx0, row); // 0 = keep, -inf = masked
|
||||
return ggml_reshape_4d(ctx0, mask, n_kv_pad, 1, 1, 1);
|
||||
mask = ggml_reshape_4d(ctx0, mask, n_kv_pad, 1, 1, 1);
|
||||
if (flash_attn_type == CLIP_FLASH_ATTN_TYPE_ENABLED) {
|
||||
mask = ggml_cast(ctx0, mask, GGML_TYPE_F16);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
// talker hidden size -> predictor hidden size (small_to_mtp_projection)
|
||||
@@ -481,6 +485,9 @@ ggml_tensor * clip_graph_qwen3tts_gen::code2wav::tfm_layer_forward(ggml_tensor *
|
||||
keep = ggml_mul(ctx0, keep, warm);
|
||||
|
||||
ggml_tensor * mask = ggml_reshape_4d(ctx0, ggml_log(ctx0, keep), total_kv, N, 1, 1); // 0 = keep, -inf = masked
|
||||
if (flash_attn_type == CLIP_FLASH_ATTN_TYPE_ENABLED) {
|
||||
mask = ggml_cast(ctx0, mask, GGML_TYPE_F16);
|
||||
}
|
||||
|
||||
ggml_tensor * q_cur = ggml_reshape_4d(ctx0, q, d_head, n_head, N, 1);
|
||||
ggml_tensor * k_cur = ggml_reshape_4d(ctx0, k_full, d_head, n_head_kv, total_kv, 1);
|
||||
|
||||
@@ -69,14 +69,7 @@ ggml_cgraph * clip_graph_youtuvl::build() {
|
||||
ggml_set_name(inv_window_idx, "inv_window_idx");
|
||||
ggml_set_input(inv_window_idx);
|
||||
// mask for window attention
|
||||
window_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_pos, n_pos);
|
||||
ggml_set_name(window_mask, "window_mask");
|
||||
ggml_set_input(window_mask);
|
||||
|
||||
// if flash attn is used, we need to pad the mask and cast to f16
|
||||
if (flash_attn_type == CLIP_FLASH_ATTN_TYPE_ENABLED) {
|
||||
window_mask = ggml_cast(ctx0, window_mask, GGML_TYPE_F16);
|
||||
}
|
||||
window_mask = build_inp_attn_mask(n_pos, n_pos);
|
||||
|
||||
// inpL shape: [n_embd, n_patches_x * n_patches_y, batch_size]
|
||||
GGML_ASSERT(batch_size == 1);
|
||||
|
||||
+12
-16
@@ -507,9 +507,7 @@ mtmd_image_preproc_out mtmd_image_preprocessor_llava_uhd::preprocess(const clip_
|
||||
|
||||
mtmd_image_preprocessor_llava_uhd::slice_instructions mtmd_image_preprocessor_llava_uhd::get_slice_instructions(const clip_image_size & original_size) const {
|
||||
mtmd_image_preprocessor_llava_uhd::slice_instructions res;
|
||||
// align slices by patch_size * n_merge so an integer number of merger output tokens fits per slice
|
||||
const int n_merge = hparams.n_merge;
|
||||
const int patch_size = hparams.patch_size * n_merge;
|
||||
const int patch_size = get_slice_align();
|
||||
const int slice_size = hparams.image_size;
|
||||
const int original_width = original_size.width;
|
||||
const int original_height = original_size.height;
|
||||
@@ -568,7 +566,7 @@ mtmd_image_preprocessor_llava_uhd::slice_instructions mtmd_image_preprocessor_ll
|
||||
res.overview_size = best_size;
|
||||
|
||||
{
|
||||
const int max_slice_nums = 9; // TODO: this is only used by minicpmv, maybe remove it
|
||||
const int max_slice_nums = hparams.max_slice_nums > 0 ? hparams.max_slice_nums : 9;
|
||||
const float log_ratio = log((float)original_width / original_height);
|
||||
const float ratio = (float)original_width * original_height / (slice_size * slice_size);
|
||||
const int multiple = fmin(ceil(ratio), max_slice_nums);
|
||||
@@ -691,7 +689,7 @@ clip_image_size mtmd_image_preprocessor_llava_uhd::select_best_resolution(const
|
||||
}
|
||||
|
||||
int mtmd_image_preprocessor_llava_uhd::ensure_divide(int length, int patch_size) const {
|
||||
return std::max(static_cast<int>(std::round(static_cast<float>(length) / patch_size) * patch_size), patch_size);
|
||||
return std::max(align_round(static_cast<double>(length) / patch_size) * patch_size, patch_size);
|
||||
}
|
||||
|
||||
clip_image_size mtmd_image_preprocessor_llava_uhd::get_refine_size(const clip_image_size & original_size, const clip_image_size & grid, int scale_resolution, int patch_size, bool allow_upscale) const {
|
||||
@@ -893,17 +891,15 @@ mtmd_image_preproc_out mtmd_image_preprocessor_longest_edge::preprocess(const cl
|
||||
//
|
||||
|
||||
mtmd_image_preprocessor_llava_uhd::slice_instructions mtmd_image_preprocessor_minicpmv::get_slice_instructions(const clip_image_size & original_size) const {
|
||||
if (hparams.n_merge == 2) {
|
||||
const int slice_size = hparams.image_size;
|
||||
const float ratio = (float)original_size.width * original_size.height / (slice_size * slice_size);
|
||||
if (ratio <= 1.0f) {
|
||||
mtmd_image_preprocessor_llava_uhd::slice_instructions inst;
|
||||
const int patch_size = hparams.patch_size * hparams.n_merge;
|
||||
inst.overview_size = get_best_resize(original_size, slice_size, patch_size, true);
|
||||
inst.refined_size = clip_image_size{0, 0};
|
||||
inst.grid_size = clip_image_size{0, 0};
|
||||
return inst;
|
||||
}
|
||||
// overview only for small images, unlike generic llava-uhd which slices once one side exceeds scale resolution
|
||||
const int slice_size = hparams.image_size;
|
||||
const float ratio = (float) original_size.width * original_size.height / (slice_size * slice_size);
|
||||
if (ratio <= 1.0f) {
|
||||
mtmd_image_preprocessor_llava_uhd::slice_instructions inst;
|
||||
inst.overview_size = get_best_resize(original_size, slice_size, get_slice_align(), true);
|
||||
inst.refined_size = clip_image_size{0, 0};
|
||||
inst.grid_size = clip_image_size{0, 0};
|
||||
return inst;
|
||||
}
|
||||
return mtmd_image_preprocessor_llava_uhd::get_slice_instructions(original_size);
|
||||
}
|
||||
|
||||
@@ -83,6 +83,17 @@ struct mtmd_image_preprocessor_llava_uhd : mtmd_image_preprocessor {
|
||||
slice_output slice_image(const clip_image_u8 & img, const slice_instructions & inst) const;
|
||||
|
||||
protected:
|
||||
// align slices to a multiple of the merger factor (integer merger tokens per slice)
|
||||
virtual int get_slice_align() const {
|
||||
const int merge = hparams.n_merge > 0 ? hparams.n_merge : 1;
|
||||
return hparams.patch_size * merge;
|
||||
}
|
||||
|
||||
// rounding for snapping a length to a multiple of the align size
|
||||
virtual int align_round(double v) const {
|
||||
return static_cast<int>(std::round(v));
|
||||
}
|
||||
|
||||
clip_image_size get_best_resize(const clip_image_size & original_size, int scale_resolution, int patch_size, bool allow_upscale = false) const;
|
||||
|
||||
/**
|
||||
@@ -155,6 +166,26 @@ private:
|
||||
struct mtmd_image_preprocessor_minicpmv : mtmd_image_preprocessor_llava_uhd {
|
||||
using mtmd_image_preprocessor_llava_uhd::mtmd_image_preprocessor_llava_uhd;
|
||||
slice_instructions get_slice_instructions(const clip_image_size & original_size) const override;
|
||||
|
||||
protected:
|
||||
// always patch_size * 4, even in 4x mode (the 2x2 vit_merger slot stays)
|
||||
int get_slice_align() const override {
|
||||
return hparams.patch_size * 4;
|
||||
}
|
||||
|
||||
// Python's round() breaks ties to even, unlike std::round
|
||||
int align_round(double v) const override {
|
||||
const double fl = std::floor(v);
|
||||
const double diff = v - fl;
|
||||
if (diff > 0.5) {
|
||||
return static_cast<int>(fl) + 1;
|
||||
}
|
||||
if (diff < 0.5) {
|
||||
return static_cast<int>(fl);
|
||||
}
|
||||
const int lo = static_cast<int>(fl);
|
||||
return (lo % 2 == 0) ? lo : lo + 1;
|
||||
}
|
||||
};
|
||||
|
||||
// custom llava-uhd slicing logic for LFM2
|
||||
|
||||
+207
-4
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -27,7 +28,10 @@
|
||||
#include <vector>
|
||||
|
||||
// remember to bump this if the serialization format changes
|
||||
#define MTMD_SERIALIZATION_VERSION 2
|
||||
#define MTMD_SERIALIZATION_VERSION 3
|
||||
|
||||
// oldest compat version that can be loaded
|
||||
#define MTMD_SERIALIZATION_VERSION_MIN 2
|
||||
|
||||
struct mtmd_serialization {
|
||||
// note: using 64-bit here for future-proofing
|
||||
@@ -45,7 +49,7 @@ struct mtmd_serialization {
|
||||
// copy buf to data
|
||||
data.assign(buf, buf + len);
|
||||
uint64_t ver_in = read<uint64_t>();
|
||||
if (ver_in != version) {
|
||||
if (ver_in < MTMD_SERIALIZATION_VERSION_MIN || ver_in > version) {
|
||||
throw std::runtime_error("version mismatch");
|
||||
}
|
||||
this->version = ver_in;
|
||||
@@ -106,6 +110,11 @@ void clip_image_f32::serialize(mtmd_serialization & ser) const {
|
||||
ser.write(add_viewsep);
|
||||
ser.write(add_newline);
|
||||
ser.write(lead_pad);
|
||||
ser.write((int32_t)suffix_type);
|
||||
ser.write((int32_t)anyres.grid_x);
|
||||
ser.write((int32_t)anyres.grid_y);
|
||||
ser.write((int32_t)anyres.orig_nx);
|
||||
ser.write((int32_t)anyres.orig_ny);
|
||||
ser.write((int32_t)nx_);
|
||||
ser.write((int32_t)ny_);
|
||||
}
|
||||
@@ -113,6 +122,17 @@ void clip_image_f32::deserialize(mtmd_serialization & ser) {
|
||||
add_viewsep = ser.read<bool>();
|
||||
add_newline = ser.read<bool>();
|
||||
lead_pad = ser.read<int32_t>();
|
||||
if (ser.version >= 3) {
|
||||
const int32_t suffix_raw = ser.read<int32_t>();
|
||||
if (suffix_raw < 0 || suffix_raw >= CLIP_SUFFIX_COUNT) {
|
||||
throw std::runtime_error("invalid suffix type");
|
||||
}
|
||||
suffix_type = (clip_suffix_type)suffix_raw;
|
||||
anyres.grid_x = ser.read<int32_t>();
|
||||
anyres.grid_y = ser.read<int32_t>();
|
||||
anyres.orig_nx = ser.read<int32_t>();
|
||||
anyres.orig_ny = ser.read<int32_t>();
|
||||
}
|
||||
nx_ = ser.read<int32_t>();
|
||||
ny_ = ser.read<int32_t>();
|
||||
buf.clear(); // always a placeholder after loading
|
||||
@@ -204,9 +224,11 @@ enum mtmd_pos_type {
|
||||
MTMD_POS_TYPE_NORMAL, // number of positions equals to number of tokens
|
||||
MTMD_POS_TYPE_MROPE, // qwen-vl mrope style, each image takes max(t,h,w) position indexes
|
||||
MTMD_POS_TYPE_HUNYUANVL, // HunyuanVL mrope + BOI/EOI/newline layout with XD-RoPE dim-3
|
||||
MTMD_POS_TYPE_CANVAS, // MiniCPM-V 4.7: overview + slices in one chunk, sharing one 2D canvas (see mtmd_image_tokens::canvas_tile_grid)
|
||||
MTMD_POS_TYPE_COUNT, // for validation
|
||||
};
|
||||
|
||||
|
||||
struct mtmd_image_tokens {
|
||||
uint32_t nx = 0; // number of tokens in x direction
|
||||
uint32_t ny = 0; // number of tokens in y direction
|
||||
@@ -218,6 +240,14 @@ struct mtmd_image_tokens {
|
||||
// [BOI] [row0 tokens + newline] ... [row(ny-1) tokens + newline] [EOI]
|
||||
return (nx + 1) * ny + 2;
|
||||
}
|
||||
if (pos == MTMD_POS_TYPE_CANVAS) {
|
||||
uint32_t n = 0;
|
||||
for (size_t k = 0; k < batch_f32.entries.size(); ++k) {
|
||||
const auto [gw, gh] = canvas_tile_grid(k);
|
||||
n += gw * gh + (uint32_t) clip_suffix_rows(batch_f32.entries[k].suffix_type).size();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
uint32_t nz = batch_f32.entries.size();
|
||||
if (n_temporal_merge > 1) {
|
||||
// [QWEN_VIDEO] this logic is quite ugly, it's mostly to make qwen-vl temporal merge work, can be improved in the future
|
||||
@@ -243,8 +273,17 @@ struct mtmd_image_tokens {
|
||||
return false;
|
||||
}
|
||||
|
||||
// MTMD_POS_TYPE_CANVAS: entries are [overview, slices row by row], nx/ny is the token grid of the last entry
|
||||
// returns the token grid (w, h) of entry k, scaled from its pixel size
|
||||
std::pair<uint32_t, uint32_t> canvas_tile_grid(size_t k) const {
|
||||
const auto & ref = batch_f32.entries.back();
|
||||
const auto & e = batch_f32.entries[k];
|
||||
return { (uint32_t) e.nx() * nx / ref.nx(), (uint32_t) e.ny() * ny / ref.ny() };
|
||||
}
|
||||
|
||||
bool can_batch_with(const mtmd_image_tokens & other) {
|
||||
return nx == other.nx && ny == other.ny && pos == other.pos;
|
||||
// a canvas chunk holds a whole image group, its layout is not given by nx/ny alone
|
||||
return nx == other.nx && ny == other.ny && pos == other.pos && pos != MTMD_POS_TYPE_CANVAS;
|
||||
}
|
||||
|
||||
mtmd_image_tokens clone() {
|
||||
@@ -516,6 +555,9 @@ struct mtmd_context {
|
||||
bool tok_row_end_trail = false;
|
||||
bool ov_img_first = false;
|
||||
|
||||
// MiniCPM-V 4.6/4.7 prepends an <image_id>N</image_id> tag before <image>
|
||||
bool use_image_id = false;
|
||||
|
||||
// string template for slice image delimiters with row/col (idefics3)
|
||||
std::string sli_img_start_tmpl;
|
||||
|
||||
@@ -680,6 +722,7 @@ struct mtmd_context {
|
||||
image_preproc = std::make_unique<mtmd_image_preprocessor_llava_uhd>(ctx_v);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_MINICPMV4_6:
|
||||
case PROJECTOR_TYPE_MINICPMV4_7:
|
||||
{
|
||||
slice_tmpl = MTMD_SLICE_TMPL_MINICPMV_2_6;
|
||||
tok_ov_img_start = {lookup_token("<image>")};
|
||||
@@ -689,6 +732,7 @@ struct mtmd_context {
|
||||
tok_row_end = {lookup_token("\n")};
|
||||
tok_row_end_trail = false; // no trailing end-of-row token
|
||||
ov_img_first = true;
|
||||
use_image_id = true;
|
||||
image_preproc = std::make_unique<mtmd_image_preprocessor_minicpmv>(ctx_v);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_QWEN2VL:
|
||||
@@ -1429,7 +1473,15 @@ struct mtmd_tokenizer {
|
||||
const bool has_tiling_grid = (preproc_out.grid_x > 0 && preproc_out.grid_y > 0)
|
||||
|| preproc_out.has_overview();
|
||||
|
||||
if (has_tiling_grid) {
|
||||
if (has_tiling_grid && ctx->proj_type_v() == PROJECTOR_TYPE_MINICPMV4_7) {
|
||||
GGML_ASSERT(bitmaps.size() == 1);
|
||||
if (ctx->use_image_id) {
|
||||
add_text("<image_id>" + std::to_string(n_images_added) + "</image_id>", true);
|
||||
}
|
||||
add_text(ctx->tok_ov_img_start);
|
||||
// the separators after <image> are appended by clip, see add_canvas_chunk()
|
||||
add_canvas_chunk(std::move(preproc_out), bitmaps[0]->id);
|
||||
} else if (has_tiling_grid) {
|
||||
// [QWEN_VIDEO] we do not support "frame merging" for llama-uhd style, so no batching for now
|
||||
GGML_ASSERT(bitmaps.size() == 1);
|
||||
|
||||
@@ -1448,6 +1500,9 @@ struct mtmd_tokenizer {
|
||||
|
||||
// add overview image (first)
|
||||
if (ctx->ov_img_first) {
|
||||
if (ctx->use_image_id) {
|
||||
add_text("<image_id>" + std::to_string(n_images_added) + "</image_id>", true);
|
||||
}
|
||||
add_text(ctx->tok_ov_img_start);
|
||||
cur.entries.emplace_back(std::move(ov_chunk));
|
||||
add_text(ctx->tok_ov_img_end);
|
||||
@@ -1673,6 +1728,62 @@ struct mtmd_tokenizer {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// MiniCPM-V 4.7: the overview and all slices go in one chunk, clip appends the separators after each tile:
|
||||
// [ov] </image><slice> [S00] </slice><slice> [S01] </slice>\n<slice> [S10] </slice><slice> [S11] </slice>
|
||||
void add_canvas_chunk(mtmd_image_preproc_out && preproc_out, const std::string & id) {
|
||||
const int n_col = preproc_out.grid_x;
|
||||
const int n_row = preproc_out.grid_y;
|
||||
auto & slices = preproc_out.entries;
|
||||
GGML_ASSERT(preproc_out.has_overview());
|
||||
GGML_ASSERT((int) slices.size() == n_col * n_row);
|
||||
|
||||
auto & ov = preproc_out.overview;
|
||||
ov.suffix_type = CLIP_SUFFIX_MINICPMV_OV;
|
||||
if (!slices.empty()) {
|
||||
ov.suffix_type = CLIP_SUFFIX_MINICPMV_OV_SLICE;
|
||||
ov.anyres.grid_x = n_col;
|
||||
ov.anyres.grid_y = n_row;
|
||||
}
|
||||
for (int y = 0; y < n_row; y++) {
|
||||
for (int x = 0; x < n_col; x++) {
|
||||
auto & suffix = slices[y * n_col + x].suffix_type;
|
||||
if (y == n_row - 1 && x == n_col - 1) {
|
||||
suffix = CLIP_SUFFIX_MINICPMV_LAST;
|
||||
} else if (x == n_col - 1) {
|
||||
suffix = CLIP_SUFFIX_MINICPMV_ROW_END;
|
||||
} else {
|
||||
suffix = CLIP_SUFFIX_MINICPMV_SLICE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mtmd_image_tokens_ptr image_tokens(new mtmd_image_tokens);
|
||||
image_tokens->pos = MTMD_POS_TYPE_CANVAS;
|
||||
image_tokens->id = id;
|
||||
auto & entries = image_tokens->batch_f32.entries;
|
||||
entries.push_back(std::move(ov));
|
||||
for (auto & slice : slices) {
|
||||
entries.push_back(std::move(slice));
|
||||
}
|
||||
// token grid of the last entry, the grids of the other entries are scaled from it
|
||||
image_tokens->nx = clip_n_output_tokens_x(ctx->ctx_v, &entries.back());
|
||||
image_tokens->ny = clip_n_output_tokens_y(ctx->ctx_v, &entries.back());
|
||||
|
||||
size_t n_tokens = 0;
|
||||
for (const auto & entry : entries) {
|
||||
n_tokens += clip_n_output_tokens(ctx->ctx_v, &entry);
|
||||
}
|
||||
GGML_ASSERT(n_tokens == image_tokens->n_tokens());
|
||||
|
||||
mtmd_input_chunk chunk{
|
||||
MTMD_INPUT_CHUNK_TYPE_IMAGE,
|
||||
{}, // text tokens
|
||||
std::move(image_tokens),
|
||||
nullptr, // audio tokens
|
||||
};
|
||||
cur.entries.emplace_back(std::move(chunk));
|
||||
}
|
||||
|
||||
std::vector<mtmd_input_chunk> split_batch_to_chunk(mtmd_image_preproc_out && preproc_out, const std::string & id) {
|
||||
std::vector<mtmd_input_chunk> chunks;
|
||||
|
||||
@@ -1814,6 +1925,24 @@ static int32_t mtmd_encode_impl(mtmd_context * ctx, const mtmd_image_tokens * im
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (image_tokens->pos == MTMD_POS_TYPE_CANVAS) {
|
||||
// the tiles differ in size, encode them one by one
|
||||
size_t offset = 0;
|
||||
for (const auto & entry : image_tokens->batch_f32.entries) {
|
||||
clip_image_f32_batch one;
|
||||
one.entries.push_back(entry);
|
||||
std::vector<float> embd((size_t) n_embd_out * clip_n_output_tokens(ctx_clip, &entry));
|
||||
if (!clip_image_batch_encode(ctx_clip, ctx->n_threads, &one, embd)) {
|
||||
return 1;
|
||||
}
|
||||
GGML_ASSERT(offset + embd.size() <= out_embd.size());
|
||||
std::copy(embd.begin(), embd.end(), out_embd.begin() + offset);
|
||||
offset += embd.size();
|
||||
}
|
||||
GGML_ASSERT(offset == out_embd.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ok = clip_image_batch_encode(
|
||||
ctx_clip,
|
||||
ctx->n_threads,
|
||||
@@ -2494,6 +2623,67 @@ size_t mtmd_image_tokens_get_ny(const mtmd_image_tokens * image_tokens) {
|
||||
return image_tokens->ny;
|
||||
}
|
||||
|
||||
// map a tile coordinate onto the canvas like the reference: round(linspace(0, canvas - 1, grid)), round() breaks ties to even
|
||||
static uint32_t mtmd_canvas_scale(uint32_t coord, uint32_t grid, uint32_t canvas) {
|
||||
if (grid <= 1 || canvas <= 1) {
|
||||
return 0;
|
||||
}
|
||||
const double v = (double) coord * (double) (canvas - 1) / (double) (grid - 1);
|
||||
return std::min((uint32_t) std::nearbyint(v), canvas - 1);
|
||||
}
|
||||
|
||||
// MTMD_POS_TYPE_CANVAS: every tile shares the <image> token before the chunk as origin
|
||||
// the overview is stretched over the whole canvas, each slice fills its own cell; the time component is the origin, in slot z
|
||||
// a tile takes one position in slot t (the KV cache position), the separators after it take one position each
|
||||
static mtmd_decoder_pos mtmd_canvas_decoder_pos(const mtmd_image_tokens * image_tokens, llama_pos pos_0, size_t i) {
|
||||
const auto & entries = image_tokens->batch_f32.entries;
|
||||
const auto & grid = entries[0].anyres;
|
||||
const uint32_t nx = image_tokens->nx;
|
||||
const uint32_t ny = image_tokens->ny;
|
||||
const uint32_t canvas_w = grid.is_tiled() ? grid.grid_x * nx : nx;
|
||||
const uint32_t canvas_h = grid.is_tiled() ? grid.grid_y * ny : ny;
|
||||
const uint32_t base = pos_0 - 1;
|
||||
|
||||
mtmd_decoder_pos pos;
|
||||
uint32_t t = pos_0;
|
||||
for (size_t k = 0; k < entries.size(); ++k) {
|
||||
const auto [gw, gh] = image_tokens->canvas_tile_grid(k);
|
||||
if (i < gw * gh) {
|
||||
const uint32_t row = i / gw;
|
||||
const uint32_t col = i % gw;
|
||||
uint32_t h;
|
||||
uint32_t w;
|
||||
if (k == 0) {
|
||||
h = mtmd_canvas_scale(row, gh, canvas_h);
|
||||
w = mtmd_canvas_scale(col, gw, canvas_w);
|
||||
} else {
|
||||
const uint32_t s = k - 1;
|
||||
h = (s / grid.grid_x) * ny + row;
|
||||
w = (s % grid.grid_x) * nx + col;
|
||||
}
|
||||
pos.t = t;
|
||||
pos.x = base + w;
|
||||
pos.y = base + h;
|
||||
pos.z = base;
|
||||
return pos;
|
||||
}
|
||||
i -= gw * gh;
|
||||
|
||||
const size_t n_sep = clip_suffix_rows(entries[k].suffix_type).size();
|
||||
if (i < n_sep) {
|
||||
const uint32_t p = t + 1 + i;
|
||||
pos.t = p;
|
||||
pos.x = p;
|
||||
pos.y = p;
|
||||
pos.z = p;
|
||||
return pos;
|
||||
}
|
||||
i -= n_sep;
|
||||
t += 1 + n_sep;
|
||||
}
|
||||
GGML_ABORT("token index out of range");
|
||||
}
|
||||
|
||||
mtmd_decoder_pos mtmd_image_tokens_get_decoder_pos(const mtmd_image_tokens * image_tokens, llama_pos pos_0, size_t i) {
|
||||
mtmd_decoder_pos pos;
|
||||
switch (image_tokens->pos) {
|
||||
@@ -2543,6 +2733,10 @@ mtmd_decoder_pos mtmd_image_tokens_get_decoder_pos(const mtmd_image_tokens * ima
|
||||
pos.z = image_tokens->image_idx;
|
||||
}
|
||||
} break;
|
||||
case MTMD_POS_TYPE_CANVAS:
|
||||
{
|
||||
pos = mtmd_canvas_decoder_pos(image_tokens, pos_0, i);
|
||||
} break;
|
||||
default:
|
||||
GGML_ABORT("invalid position type");
|
||||
}
|
||||
@@ -2563,6 +2757,15 @@ llama_pos mtmd_image_tokens_get_n_pos(const mtmd_image_tokens * image_tokens) {
|
||||
// HunyuanVL: the sequential (dim-0) position advances by the full token count
|
||||
// (includes BOI/EOI and row newline tokens), not by max(nx, ny)
|
||||
return image_tokens->n_tokens();
|
||||
case MTMD_POS_TYPE_CANVAS:
|
||||
{
|
||||
// one position per tile, plus one per separator
|
||||
llama_pos n_pos = 0;
|
||||
for (const auto & entry : image_tokens->batch_f32.entries) {
|
||||
n_pos += 1 + (llama_pos) clip_suffix_rows(entry.suffix_type).size();
|
||||
}
|
||||
return n_pos;
|
||||
}
|
||||
default:
|
||||
GGML_ABORT("invalid position type");
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ For the full list of features, please refer to [server's changelog](https://gith
|
||||
| `--metrics` | enable prometheus compatible metrics endpoint (default: disabled)<br/>(env: LLAMA_ARG_ENDPOINT_METRICS) |
|
||||
| `--props` | enable changing global properties via POST /props (default: disabled)<br/>(env: LLAMA_ARG_ENDPOINT_PROPS) |
|
||||
| `--slots, --no-slots` | expose slots monitoring endpoint (default: enabled)<br/>(env: LLAMA_ARG_ENDPOINT_SLOTS) |
|
||||
| `--slot-save-path PATH` | path to save slot kv cache (default: disabled) |
|
||||
| `--slot-save-path PATH` | path to save slot kv cache (default: disabled)<br/>(env: LLAMA_ARG_SLOT_SAVE_PATH) |
|
||||
| `--media-path PATH` | directory for loading local media files; files can be accessed via file:// URLs using relative paths (default: disabled) |
|
||||
| `--models-dir PATH` | directory containing models for the router server (default: disabled)<br/>(env: LLAMA_ARG_MODELS_DIR) |
|
||||
| `--models-preset PATH` | path to INI file containing model presets for the router server (default: disabled)<br/>(env: LLAMA_ARG_MODELS_PRESET) |
|
||||
|
||||
@@ -1686,6 +1686,11 @@ private:
|
||||
if (task.id_slot != -1) {
|
||||
ret = get_slot_by_id(task.id_slot);
|
||||
if (ret) {
|
||||
// a busy slot is returned untouched, the caller defers the task
|
||||
if (ret->is_processing()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
SLT_INF(*ret, "selected slot by id (%d)\n", task.id_slot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +237,29 @@ def test_nocache_long_input_prompt():
|
||||
})
|
||||
assert res.status_code == 400
|
||||
|
||||
|
||||
# a request pinned to a busy slot leaves the generation running on it untouched
|
||||
def test_pinned_request_on_busy_slot():
|
||||
global server
|
||||
server.n_ctx = 4096
|
||||
server.start()
|
||||
story = "Once upon a time a dragon named Ember guarded a golden key in a deep cave. " * 8
|
||||
|
||||
def run(pin_busy: bool) -> str:
|
||||
server.make_request("POST", "/completion", data={"prompt": story, "id_slot": 0, "n_predict": 4, "temperature": 0.0})
|
||||
res = server.make_stream_request("POST", "/completion", data={
|
||||
"prompt": "To bake bread, mix flour, water and salt, then",
|
||||
"id_slot": 0, "n_predict": 1024, "ignore_eos": True, "temperature": 0.0, "stream": True,
|
||||
})
|
||||
content = next(res)["content"]
|
||||
if pin_busy:
|
||||
server.make_request("POST", "/completion", data={"prompt": story + "The dragon", "id_slot": 0, "n_predict": 4, "temperature": 0.0})
|
||||
return content + "".join(chunk["content"] for chunk in res)
|
||||
|
||||
baseline = run(pin_busy=False)
|
||||
assert run(pin_busy=True) == baseline
|
||||
|
||||
|
||||
def test_json_prompt_no_mtmd():
|
||||
global server
|
||||
server.start()
|
||||
|
||||
+1184
File diff suppressed because it is too large
Load Diff
Vendored
+913
-72
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user