Compare commits

..
21 Commits
Author SHA1 Message Date
lhezandHongqiang Wang f2cc7282ce opencl: improve fa, allow dk512 for gemma-4, improve dk64 (#30266)
* opencl: enable Gemma-4 E4B GPU decode

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>

* opencl: extend Gemma-4 GPU decode to E2B

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>

* opencl: optimize DK64 GQA8 decode

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>

* opencl: optimize DK128 GQA4 decode

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>

---------

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>
2026-10-11 11:49:25 -07:00
lhezandHongqiang Wang c83f3058b6 opencl: fix image limit for q4_k dense bin kernels, q4_0 bcast and rms_norm (#30310)
* opencl: fallback when Q4_K weight images exceed device limits

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>

* opencl: avoid incomplete subgroup in rms_norm

* opencl: refine conditions for dense bin kernels and fix bcast for q4_0

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>

---------

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>
2026-10-11 09:41:29 -07:00
Georgi Gerganov 5c8854fd88 rpc : turn GGML_RPC_DEBUG into a verbosity level and add logs (#29544)
* rpc : turn GGML_RPC_DEBUG into a verbosity level and add logs

GGML_RPC_DEBUG is now parsed as a number: 0/unset disables debug logs,
1-3 emit increasingly detailed output (events, per-command trace,
transport detail). Non-numeric values fall back to 1.

The duplicated env/macro blocks in ggml-rpc.cpp and transport.cpp are
replaced by a shared log.h, which becomes the single choke point for
all logging of the RPC backend: LOG_ERROR/LOG_WARN/LOG_INFO for
unconditional severity logs and LOG_DBG/LOG_DBG2/LOG_DBG3 for the
verbosity-gated ones. The transport files no longer need ggml-impl.h,
and the server banner now also goes through the ggml logger (stderr).

Missing logs are added on both the client (handshake, buffer ops,
tensor transfers, graph computes, cache decisions) and the server
(per-command dispatch, graph nodes), including the negotiated
transport via the new socket_t::transport_name().

Assisted-by: pi:llama.cpp/MiMo-V2.6-Flash-RL

* rpc : align log levels with the documented verbosity semantics

The first pass introduced GGML_RPC_DEBUG levels, but many call sites did not
follow the documented semantics and some logs bypassed the macros entirely:

- Route the remaining raw GGML_LOG_* call sites through the local LOG_* macros
  so that log.h stays the single choke point for RPC logging
- Emit per-tensor transfers and per-command handler traces at level 2, and
  one-shot lifecycle events (backend and buffer type creation, buffer
  allocation, graph compute, graph cache eviction) at level 1
- Drop the duplicated enqueue trace in rpc_dispatcher::send, work() already
  logs every dispatched command together with its round-trip timing
- Promote degraded-operation events to LOG_WARN: remote allocation failure,
  RDMA falling back to TCP, unexpected peer disconnect
- Log graph cache eviction on both sides, since client and server must stay
  in sync for the incremental graph update to be valid
- Distinguish Unknown command (opcode out of range) from Unhandled command
  (valid opcode with no handler)
- Fix format specifiers (%zu for size_t, %u for device ids, 0x for hex
  pointers) and a missing newline in an error log
- Document that llama.cpp applications drop ggml debug records unless the
  verbosity threshold is raised with -lv 5

Assisted-by: pi:llama.cpp/Qwen3.8-Flash-Next
2026-10-11 17:43:35 +03:00
bri-prismandGeorgi Gerganov a9d27ac693 model : support for Prism Bonsai 2 27B (#29600)
* Runtime support for Prism Bonsai 2 27B

Assisted-by: Claude Code

* address prism hadamard runtime feedback

* move hadamard tensors into method, define folded weight

* load_* hadamard fixes

* cont : clean-up

* conversion cleanup

Assisted-by: Claude Code

* prism hadamard key and methods for converter

Assisted-by: Claude Code

* address review bot feedback

Assisted-by: Claude Code

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
2026-10-11 14:39:16 +02:00
Ploo 1cf7767765 cpu: fix AVX2 CPUs not using F16C when available (#30270) 2026-10-11 15:24:41 +03:00
Jeff BolzandAnjielon 966baae76b vulkan: handle mul_mat_id duplicates in the prepass rather than looping (#29998)
* vulkan: compute every row of an expert in mul_mm_id when ids repeat

* vulkan: handle mul_mat_id duplicates in the prepass rather than looping

Extend the "hoist row ids" optimization to always be enabled and to emit a
compact list of tile descriptions that need to run, and to emit multiple tiles
when needed. Then we can launch a tighter upper bound on the number of
workgroups and the tail can trivially early exit.

---------

Co-authored-by: Anjielon <[EMAIL_REDACTED]>
2026-10-11 14:02:57 +02:00
Xuan-Son Nguyen 62a6f74bab server: add component tests (#30312)
* server: add component tests

* nits

* add codeowners
2026-10-11 12:51:07 +02:00
Pascal 23b0202a18 server: leave a busy slot untouched when a request pins it (#30295)
A request asking for a busy id_slot still ran the prompt cache update
on that slot before being deferred. When the RAM cache held a better
match, it was loaded into the slot while another request was still
generating there, and that generation continued on the wrong context.

The busy slot is now returned as is and the request waits for it.
2026-10-10 21:25:38 +02:00
69f201a205 model : support MiniCPM-V 4.7 (#29416)
* mtmd : add MiniCPM-V 4.7 support

Signed-off-by: tc-mb <tianchi_cai@icloud.com>

* model : allow mrope time from an extra position slot

Signed-off-by: tc-mb <tianchi_cai@icloud.com>

* Update conversion/minicpm.py

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>

* Slim down comments

Signed-off-by: tc-mb <tianchi_cai@icloud.com>

* fix for "do not hand-wrap comments"

Signed-off-by: tc-mb <tianchi_cai@icloud.com>

* fix ci

Signed-off-by: tc-mb <tianchi_cai@icloud.com>

* rm 3d repo for pr one

Signed-off-by: tc-mb <tianchi_cai@icloud.com>

* gguf: add rope.section_order metadata

* fix comments

* handle grid layout

* allow compat

---------

Signed-off-by: tc-mb <tianchi_cai@icloud.com>
Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
2026-10-10 20:41:59 +02:00
Aaron Teo abee0c8476 cmake(s390x): disable z17 target for unsupported compilers (#30297) 2026-10-10 20:00:56 +02:00
Mikolaj Kucharski aa94f20861 args: add LLAMA_ARG_SLOT_SAVE_PATH env for --slot-save-path (#30272)
Allow configuring --slot-save-path via LLAMA_ARG_SLOT_SAVE_PATH so
it can be set from an EnvironmentFile in a systemd unit file.
2026-10-10 15:34:40 +02:00
Xuan-Son Nguyen 781dbc5ac9 spec: properly handle mtmd input for mtp (#30257)
* spec: properly handle mtmd input for mtp

* nits
2026-10-10 11:22:38 +02:00
Xuan-Son Nguyen 0fd868cbca mtmd: add build_inp_attn_mask (#30259) 2026-10-10 11:22:12 +02:00
David Friehs 1623d8ce47 cuda: always use MMVQ for MUL_MAT_ID on sm_60 (#27828) 2026-10-10 09:15:42 +03:00
uvos 1bb2b9fcbe CUDA/HIP: fix race in flash_attn_ext_f16_process_tile when nbatch_combine != DKQ/2 (#30103)
Suggested-by: Johannes Gäßler <johannesg@5d6.de>
2026-10-10 09:14:51 +03:00
Aaron Teo 2bbca8f202 ggml-cpu: vectorize fp32 to fp16 conversion (#30157)
ggml-cpu(s390x): rename ulong to uint64_t sized types



ggml-cpu(s390x): rm comment

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>
2026-10-10 09:08:52 +03:00
Amadeus dyw 404f557b5b vulkan: use 4 rows for NVIDIA MUL_MAT_ID MMVQ (#29274)
Keep the existing RDNA3/4 policy unchanged and update only rm_id to use 4 rows for NVIDIA except pre-Turing.
2026-10-10 09:08:19 +03:00
Aaron Teo b797c82c7d ggml: fix s390x all cpu build (#30140)
Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>
2026-10-10 09:07:52 +03:00
Shawn Gu f2918cabbf opencl: add bin kernels kernel_gemm_moe_q4_k_q8_1_dp4a_bin, kernel_gemm_moe_q6_k_q8_1_dp4a_bin (#30187) 2026-10-09 21:02:11 -07:00
Captain-Tripps 1e6f04a75e sycl : accelerate MXFP4 MoE with arithmetic decoding and weight reordering (#29809) 2026-10-09 22:56:23 -04:00
Xuan-Son Nguyen 10a60cf303 vendor: apply deep nested json patch from upstream (#30253) 2026-10-10 00:07:44 +02:00
99 changed files with 3395 additions and 836 deletions
+1
View File
@@ -97,6 +97,7 @@
/src/models/ @CISC
/tests/ @ggerganov
/tests/test-chat.* @pwilkin
/tests/test-server-component.cpp @ggml-org/llama-server
/tools/batched-bench/ @ggerganov
/tools/cli/ @ngxson
/tools/completion/ @ggerganov
+1 -1
View File
@@ -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
View File
@@ -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);
}
+4
View File
@@ -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
View File
@@ -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;
}
}
+2
View File
@@ -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",
+172 -2
View File
@@ -673,6 +673,171 @@ class ModelBase:
if algo == "W4A16_NVFP4":
self._prec_a4[gguf_name] = False
def hadamard_folded_names(self) -> set[str]:
"""Source-tensor names folded under a Hadamard manifest, or empty."""
cached = getattr(self, "_hadamard_folded_names", None)
if cached is not None:
return cached
names: set[str] = set()
manifest_path = self.dir_model / "hadamard_packing.json"
if manifest_path.is_file():
with manifest_path.open("r", encoding="utf-8") as f:
for record in json.load(f).get("tensors", []):
if isinstance(record, dict) and isinstance(record.get("name"), str):
names.add(record["name"])
self._hadamard_folded_names = names
return names
def add_hadamard_metadata(self) -> None:
"""Transfer a packed-checkpoint transform contract into GGUF metadata."""
manifest_path = self.dir_model / "hadamard_packing.json"
if not manifest_path.is_file():
return
with manifest_path.open("r", encoding="utf-8") as f:
manifest = json.load(f)
schema_version = manifest.get("schema_version")
if schema_version not in (1, 2, 3) or manifest.get("kind") != "hadamard-weight-fold":
raise ValueError(f"Unsupported Hadamard manifest: {manifest_path}")
if manifest.get("status") != "requires-matching-runtime":
raise ValueError(f"Unexpected Hadamard manifest status: {manifest.get('status')!r}")
transform = manifest.get("transform")
if not isinstance(transform, dict):
raise ValueError("Hadamard manifest is missing transform metadata")
block_size = transform.get("block_size")
if not isinstance(block_size, int) or block_size <= 0 or block_size & (block_size - 1):
raise ValueError(f"Invalid Hadamard block size: {block_size!r}")
if transform.get("name") != "normalized-signed-sylvester-walsh-hadamard":
raise ValueError(f"Unsupported Hadamard transform: {transform.get('name')!r}")
sign_mode = transform.get("sign_mode")
if sign_mode not in ("identity", "explicit"):
raise ValueError(f"Unsupported Hadamard sign mode: {sign_mode!r}")
sign_widths: list[int] = []
sign_values: list[int] = []
if sign_mode == "explicit":
signs = manifest.get("signs")
if not isinstance(signs, dict) or not signs:
raise ValueError("explicit sign mode requires a signs table")
for width_str, vec in sorted(signs.items(), key=lambda kv: int(kv[0])):
width = int(width_str)
# same width rule as the runtime, so a manifest that converts also loads
if width <= 0 or width % block_size != 0:
raise ValueError(
f"sign width {width} must be positive and a multiple of block size {block_size}"
)
if len(vec) != width or any(v not in (-1, 1) for v in vec):
raise ValueError(f"invalid sign vector for width {width}")
sign_widths.append(width)
sign_values.extend(int(v) for v in vec)
tensor_records = manifest.get("tensors")
if not isinstance(tensor_records, list) or not tensor_records:
raise ValueError("Hadamard manifest has no folded tensors")
# only build_lora_mm/build_lora_mm_id apply the transform: refuse archs and tensor kinds that can skip them
_HADAMARD_ARCHS = {
gguf.MODEL_ARCH.LLAMA,
gguf.MODEL_ARCH.QWEN3,
gguf.MODEL_ARCH.QWEN3MOE,
gguf.MODEL_ARCH.QWEN35,
gguf.MODEL_ARCH.QWEN35MOE,
gguf.MODEL_ARCH.QWEN3NEXT,
}
if self.model_arch not in _HADAMARD_ARCHS:
raise ValueError(
f"Hadamard folding is not verified for arch {self.model_arch.name}; "
"the runtime would load the GGUF without applying the activation transform"
)
_HADAMARD_KINDS = re.compile(
r"output\.weight|"
r"blk\.\d+\.("
r"attn_q|attn_k|attn_v|attn_qkv|attn_gate|attn_output"
r"|ffn_gate|ffn_up|ffn_down"
r"|ffn_gate_exps|ffn_up_exps|ffn_down_exps|ffn_gate_up_exps"
r"|ffn_gate_shexp|ffn_up_shexp|ffn_down_shexp"
r"|ssm_out"
r")\.weight"
)
weight_names: list[str] = []
inverse_weight_names: list[str] = []
for record in tensor_records:
if not isinstance(record, dict) or not isinstance(record.get("name"), str):
raise ValueError("Hadamard manifest has an invalid tensor record")
if record.get("axis") != -1:
raise ValueError(f"Unsupported Hadamard tensor axis for {record['name']!r}")
role = record.get("role", "fold-before-matmul")
if role not in ("fold-before-matmul", "inverse-after-lookup"):
raise ValueError(f"Unsupported Hadamard tensor role for {record['name']!r}: {role!r}")
filtered = self.filter_tensors((record["name"], lambda: torch.empty(0)))
if filtered is None:
raise ValueError(f"Hadamard tensor is filtered out: {record['name']!r}")
mapped = self.map_tensor_name(filtered[0])
if role == "inverse-after-lookup":
# the runtime applies the inverse only after the token-embedding lookup, other latent tables stay rotated
if mapped != "token_embd.weight":
raise ValueError(
f"Hadamard tensor {record['name']!r} maps to {mapped!r}, which is not a "
"verified inverse-after-lookup table"
)
inverse_weight_names.append(mapped)
else:
if not _HADAMARD_KINDS.fullmatch(mapped):
raise ValueError(
f"Hadamard tensor {record['name']!r} maps to {mapped!r}, which is not on a "
"verified Hadamard-aware matmul path"
)
weight_names.append(mapped)
# --fuse-qkv writes one attn_qkv per layer, so Q, K and V must all be folded and get one name
for bid in sorted(self._fusable_qkv_weight_layers):
qkv = [
self.format_tensor_name(t, bid)
for t in (gguf.MODEL_TENSOR.ATTN_Q, gguf.MODEL_TENSOR.ATTN_K, gguf.MODEL_TENSOR.ATTN_V)
]
n_folded = sum(name in weight_names for name in qkv)
if n_folded == 0:
continue
if n_folded != len(qkv):
raise ValueError(f"--fuse-qkv needs all of Q, K and V folded in layer {bid}, or none of them")
weight_names = [name for name in weight_names if name not in qkv]
weight_names.append(self.format_tensor_name(gguf.MODEL_TENSOR.ATTN_QKV, bid))
tied_output = manifest.get("tied_output", False)
if not isinstance(tied_output, bool) or (schema_version == 3) != tied_output:
raise ValueError("Hadamard schema 3 requires tied_output=true; older schemas forbid it")
if tied_output:
if inverse_weight_names != ["token_embd.weight"]:
raise ValueError("Tied Hadamard output requires one latent token embedding")
if not self.hparams.get("tie_word_embeddings", False):
raise ValueError("Tied Hadamard output requires tie_word_embeddings=true")
if "output.weight" in weight_names or any(
self.tensor_map.get_name(name, try_suffixes=(".weight", ".bias")) == "output.weight"
for name in self.model_tensors
):
raise ValueError("Tied Hadamard output must not carry a separate output head")
self.gguf_writer.add_prism_hadamard_tied_output(True)
elif "token_embd.weight" in inverse_weight_names and self.hparams.get("tie_word_embeddings", False):
raise ValueError("A tied latent embedding requires Hadamard schema 3 and tied_output=true")
self.gguf_writer.add_prism_hadamard_version(2 if tied_output else 1)
self.gguf_writer.add_prism_hadamard_block_size(block_size)
self.gguf_writer.add_prism_hadamard_transform("normalized-sylvester-walsh-hadamard")
self.gguf_writer.add_prism_hadamard_axis("input-last-dimension")
self.gguf_writer.add_prism_hadamard_sign_mode(sign_mode)
self.gguf_writer.add_prism_hadamard_weight_names(weight_names)
if sign_mode == "explicit":
self.gguf_writer.add_prism_hadamard_sign_widths(sign_widths)
self.gguf_writer.add_prism_hadamard_sign_values(sign_values)
if inverse_weight_names:
self.gguf_writer.add_prism_hadamard_inverse_weight_names(inverse_weight_names)
if getattr(self, "_hadamard_gdn_v_grouped", False):
self.gguf_writer.add_prism_hadamard_gdn_v_grouped(True)
logger.info("GGUF Hadamard: linear-attention out_proj kept in grouped V order")
logger.info("GGUF Hadamard contract: H%d, sign_mode=%s, %d folded weight(s), %d inverse-lookup",
block_size, sign_mode, len(weight_names), len(inverse_weight_names))
def set_gguf_parameters(self):
raise NotImplementedError("set_gguf_parameters() must be implemented in subclasses")
@@ -1220,6 +1385,8 @@ class ModelBase:
logger.info("Set model quantization version")
self.gguf_writer.add_quantization_version(gguf.GGML_QUANT_VERSION)
self.add_hadamard_metadata()
if self._prec_a4:
names = sorted(self._prec_a4.keys())
values = [self._prec_a4[n] for n in names]
@@ -1389,14 +1556,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
View File
@@ -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)
+19 -7
View File
@@ -570,11 +570,15 @@ class _LinearAttentionVReorderBase(Qwen3NextModel):
elif name.endswith((".linear_attn.in_proj_a.weight", ".linear_attn.in_proj_b.weight")):
weight, scale = reorder_rows(weight, scale, 1)
elif name.endswith(".linear_attn.out_proj.weight"):
col_perm = self._reorder_v_heads(
torch.arange(num_v_heads * head_v_dim, dtype=torch.long).unsqueeze(0),
1, num_k_heads, num_v_per_k, head_v_dim,
).squeeze(0)
weight, scale = apply_col_perm(weight, scale, col_perm)
if self._hadamard_folds_tensor(name):
# folded weight: keep the grouped V order, the runtime permutes the activation instead
self._hadamard_gdn_v_grouped = True
else:
col_perm = self._reorder_v_heads(
torch.arange(num_v_heads * head_v_dim, dtype=torch.long).unsqueeze(0),
1, num_k_heads, num_v_per_k, head_v_dim,
).squeeze(0)
weight, scale = apply_col_perm(weight, scale, col_perm)
return weight, scale
@@ -582,6 +586,10 @@ class _LinearAttentionVReorderBase(Qwen3NextModel):
weight, scale = self._transform_nvfp4_weight(name, weight, scale)
super()._repack_nvfp4(name, weight, scale, scale2, input_scale)
def _hadamard_folds_tensor(self, name: str) -> bool:
# a manifest name and `name` can differ only by leading wrapper prefixes
return any(name.endswith(n) or n.endswith(name) for n in self.hadamard_folded_names())
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
num_k_heads = self.hparams.get("linear_num_key_heads", 0)
num_v_heads = self.hparams.get("linear_num_value_heads", 0)
@@ -628,8 +636,12 @@ class _LinearAttentionVReorderBase(Qwen3NextModel):
data_torch = torch.cat([qk_part, v_part], dim=0)
elif ".out_proj." in name:
# Out projection weight: reorder columns (input dimension)
data_torch = self._reorder_v_heads(data_torch, 1, num_k_heads, num_v_per_k, head_v_dim)
if self._hadamard_folds_tensor(name):
# folded weight: keep the grouped V order, the runtime permutes the activation instead
self._hadamard_gdn_v_grouped = True
else:
# Out projection weight: reorder columns (input dimension)
data_torch = self._reorder_v_heads(data_torch, 1, num_k_heads, num_v_per_k, head_v_dim)
yield from super().modify_tensors(data_torch, name, bid)
+55
View File
@@ -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.
+10
View File
@@ -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()
+6 -1
View File
@@ -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()
+2 -3
View File
@@ -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;
+6
View File
@@ -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]);
+29 -9
View File
@@ -140,6 +140,14 @@ extern float ggml_table_f32_ue4m3[1 << 8];
#define GGML_CPU_UE4M3_TO_FP32(x) ggml_ue4m3_to_fp32(x)
#endif
// Pre AVX512 CPUs (AVX2 with F16C, such as i5-8th gen or i7-4790)
// do not produce codegen as checks below don't test for __F16C__
// but this check does.
#if defined(__F16C__)
#define GGML_CPU_FP16_TO_FP32(x) GGML_CPU_COMPUTE_FP16_TO_FP32(x)
#define GGML_CPU_FP32_TO_FP16(x) GGML_CPU_COMPUTE_FP32_TO_FP16(x)
#endif
// On ARM NEON, it's quicker to directly convert x -> x instead of calling into ggml_lookup_fp16_to_fp32,
// so we define GGML_CPU_FP16_TO_FP32 and GGML_CPU_FP32_TO_FP16 elsewhere for NEON.
// This is also true for POWER9.
@@ -1223,6 +1231,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 +1262,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
+1 -1
View File
@@ -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();
}
}
+3 -3
View File
@@ -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);
+306 -43
View File
@@ -48,6 +48,7 @@ typedef const void * (*get_adreno_bin_kernel_func_t)(
#include <mutex>
#include <regex>
#include <set>
#include <tuple>
#include <unordered_set>
#undef MIN
@@ -494,6 +495,13 @@ struct ggml_opencl_fa_kernels {
std::map<std::pair<int, int>, cl_kernel> f32_f16_q1_split; // flash-decoding K-split
// vec decode
std::map<std::pair<int, int>, cl_kernel> f32_f16_q1_vec;
bool f32_f16_vec_512_attempted = false;
std::map<std::tuple<int, int, int>, cl_kernel> f32_f16_mq_decode;
std::map<std::tuple<int, int, int>, size_t> f32_f16_mq_decode_wg;
std::map<std::tuple<int, int, int>, int> f32_f16_mq_decode_hs;
std::set<std::tuple<int, int, int>> f32_f16_mq_decode_attempted;
ggml_cl_buffer fd_partial;
cl_uint compute_units = 0;
// kv-head-coalesced vec decode
std::map<std::pair<int, int>, cl_kernel> f32_f16_q1_vec_mq;
// kv-head-coalesced + flash-decoding split
@@ -1024,6 +1032,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
@@ -1287,6 +1297,11 @@ struct ggml_backend_opencl_context {
ref_count--;
if (ref_count == 0) {
if (fa.fd_partial.buffer) {
CL_CHECK(clReleaseMemObject(fa.fd_partial.buffer));
fa.fd_partial.buffer = nullptr;
fa.fd_partial.size = 0;
}
#ifdef GGML_OPENCL_PROFILING
flush_profiling_batch();
write_profiling_info();
@@ -1471,14 +1486,7 @@ static bool use_adreno_bin_kernels(ggml_backend_opencl_context * backend_ctx) {
#endif // GGML_OPENCL_USE_ADRENO_BIN_KERNELS
}
static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
if (backend_ctx->kernels_loaded) {
return;
}
cl_int err;
// compiler options for general kernels
static std::string ggml_opencl_make_compile_opts(const ggml_backend_opencl_context * backend_ctx) {
auto opencl_c_std =
std::string("CL") + std::to_string(backend_ctx->opencl_c_version.major) + "." + std::to_string(backend_ctx->opencl_c_version.minor);
std::string compile_opts = std::string("-cl-std=") + opencl_c_std +
@@ -1489,7 +1497,18 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
compile_opts += " -qcom-enable-large-buffer ";
}
backend_ctx->kernel_compile_opts = compile_opts;
return compile_opts;
}
static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
if (backend_ctx->kernels_loaded) {
return;
}
cl_int err;
const std::string & compile_opts = backend_ctx->kernel_compile_opts;
const std::string opencl_c_std = "CL" + std::to_string(backend_ctx->opencl_c_version.major) +
"." + std::to_string(backend_ctx->opencl_c_version.minor);
GGML_LOG_INFO("ggml_opencl: loading OpenCL kernels");
@@ -4830,6 +4849,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 +5086,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
@@ -5568,6 +5623,127 @@ static void ggml_opencl_ensure_fa_pre_kernels(ggml_backend_opencl_context * back
clReleaseProgram(prog_pre_f16);
}
static bool ggml_opencl_ensure_fa_f32_f16_vec_512(ggml_backend_opencl_context * backend_ctx) {
const std::pair<int, int> key = {512, 512};
auto & fa = backend_ctx->fa;
if (fa.f32_f16_q1_vec.count(key) > 0) {
return true;
}
if (fa.f32_f16_vec_512_attempted || backend_ctx->kernel_compile_opts.empty()) {
return false;
}
fa.f32_f16_vec_512_attempted = true;
const ggml_opencl_fa_dim * cfg = nullptr;
for (const auto & d : g_opencl_fa_dims) {
if (d.dk == 512 && d.dv == 512) {
cfg = &d;
break;
}
}
if (cfg == nullptr) {
return false;
}
// Compile only vec decode and merge to stay within the Adreno compiler's memory limit.
const std::string opts = ggml_opencl_fa_compile_opts(backend_ctx, cfg, FA_VARIANT_F32_F16) +
" -D FA_DECODE_ONLY -D FA_VEC_ONLY";
cl_program prog = build_program_from_source_ex(
backend_ctx->context, backend_ctx->device,
ggml_opencl_fa_kernel_src(FA_VARIANT_F32_F16).c_str(), opts,
/*fatal=*/false, "fa f32_f16 decode512 vec", backend_ctx->queue);
if (!prog) {
return false;
}
cl_int err;
cl_kernel vec = clCreateKernel(prog, "flash_attn_f32_f16_q1_vec", &err);
if (err != CL_SUCCESS) {
clReleaseProgram(prog);
return false;
}
cl_kernel merge = clCreateKernel(prog, "flash_attn_f32_merge", &err);
clReleaseProgram(prog);
if (err != CL_SUCCESS) {
clReleaseKernel(vec);
return false;
}
if (!ggml_opencl_fa_kernel_fits_wg(backend_ctx, vec, 256, "flash_attn_f32_f16_q1_vec", 512, 512) ||
!ggml_opencl_fa_kernel_fits_wg(backend_ctx, merge, 128, "flash_attn_f32_merge", 512, 512)) {
clReleaseKernel(vec);
clReleaseKernel(merge);
return false;
}
fa.f32_f16_q1_vec[key] = vec;
if (fa.f32_merge.count(key) > 0) {
clReleaseKernel(merge);
} else {
fa.f32_merge[key] = merge;
}
return true;
}
static void ggml_opencl_ensure_fa_f32_f16_mq_decode(ggml_backend_opencl_context * backend_ctx, int dk, int dv, int gqa) {
const std::tuple<int, int, int> key = {dk, dv, gqa};
auto & fa = backend_ctx->fa;
if (fa.f32_f16_mq_decode.count(key) > 0 || fa.f32_f16_mq_decode_attempted.count(key) > 0 ||
backend_ctx->kernel_compile_opts.empty()) {
return;
}
if (gqa == 4 && dk != 128 && fa.f32_f16_q1_vec_mq_split.count({dk, dv}) > 0) {
fa.f32_f16_mq_decode[key] = fa.f32_f16_q1_vec_mq_split.at({dk, dv});
fa.f32_f16_mq_decode_wg[key] = 256;
fa.f32_f16_mq_decode_hs[key] = 1;
return;
}
fa.f32_f16_mq_decode_attempted.insert(key);
const ggml_opencl_fa_dim * cfg = nullptr;
for (const auto & d : g_opencl_fa_dims) {
if (d.dk == dk && d.dv == dv) {
cfg = &d;
break;
}
}
if (cfg == nullptr) {
return;
}
const bool cluster = dk == 64 || dk == 128;
const int head_sub = cluster ? 2 : (gqa == 8 ? (dk == 512 ? 4 : 2) : 1);
const int nsg_max = dk == 64 ? 1 : (dk == 128 || (gqa == 8 && dk == 256) ? 2 : 4);
const char * kernel_name = cluster ? "flash_attn_f32_f16_q1_vec_mq_split_c8" : "flash_attn_f32_f16_q1_vec_mq_split";
const std::string src = ggml_opencl_fa_kernel_src(FA_VARIANT_F32_F16);
const std::string opts = ggml_opencl_fa_compile_opts(backend_ctx, cfg, FA_VARIANT_F32_F16) +
" -D FA_MQ_ONLY -D MQ_GQA=" + std::to_string(gqa / head_sub) +
" -D FA_HEAD_SUB=" + std::to_string(head_sub) +
(cluster ? " -D MQ_NSG=" + std::to_string(nsg_max) + " -D FA_CL_C=16" : " -D FA_MQ_SPLIT_ONLY") +
(dk == 64 ? " -D FA_CL_MHRED -D FA_CL_MASK_BCAST" : "") +
(gqa == 8 && dk == 256 ? " -D FA_Q1_Q_REG" : "");
for (int nsg = nsg_max; nsg >= 1; nsg /= 2) {
const size_t wg = 64 * nsg;
cl_program prog = build_program_from_source_ex(
backend_ctx->context, backend_ctx->device, src.c_str(),
opts + " -D MQ_NSG_SPLIT=" + std::to_string(nsg),
/*fatal=*/false, "fa f32_f16 mq decode", backend_ctx->queue);
if (!prog) {
continue;
}
cl_int err;
cl_kernel kernel = clCreateKernel(prog, kernel_name, &err);
clReleaseProgram(prog);
if (err != CL_SUCCESS) {
continue;
}
if (!ggml_opencl_fa_kernel_fits_wg(backend_ctx, kernel, wg, kernel_name, dk, dv)) {
clReleaseKernel(kernel);
continue;
}
fa.f32_f16_mq_decode[key] = kernel;
fa.f32_f16_mq_decode_wg[key] = wg;
fa.f32_f16_mq_decode_hs[key] = head_sub;
return;
}
}
// DK=512 prefill BM-tile
static bool ggml_opencl_ensure_fa_f32_f16_prefill_512(ggml_backend_opencl_context * backend_ctx, bool split) {
const int dk = 512, dv = 512;
@@ -6796,6 +6972,10 @@ static ggml_backend_opencl_context * ggml_cl_init(ggml_backend_dev_t dev) {
backend_ctx->adreno_use_large_buffer = getenv("GGML_OPENCL_ADRENO_USE_LARGE_BUFFER") != nullptr &&
backend_ctx->gpu_family == GPU_FAMILY::ADRENO;
backend_ctx->kernel_compile_opts = ggml_opencl_make_compile_opts(backend_ctx.get());
CL_CHECK(clGetDeviceInfo(device, CL_DEVICE_MAX_COMPUTE_UNITS,
sizeof(backend_ctx->fa.compute_units), &backend_ctx->fa.compute_units, NULL));
// ragged moe, unspecified or non-zero means enabled, set to 0 to disable
static const char * ragged_fp16_env = getenv("GGML_OPENCL_MOE_RAGGED_FP16");
backend_ctx->adreno_use_moe_ragged = (ragged_fp16_env == NULL) ? 1 : (atoi(ragged_fp16_env) != 0);
@@ -8545,6 +8725,11 @@ inline bool use_adreno_kernels(const ggml_backend_opencl_context *backend_ctx, c
bool threashold_ok = tensor->ne[0] >= threshold_ne0 && tensor->ne[1] >= threshold_ne1 &&
tensor->ne[2] == 1 && tensor->ne[3] == 1;
// the transposed layout needs K % 32 == 0 and M % 4 == 0
if (tensor->ne[0] % 32 != 0 || tensor->ne[1] % 4 != 0) {
return false;
}
// The noshuffle layout packs 2 rows per 32-bit texel and the GEMV reads it at an
// ne1/2 texel stride with an exact-cover dispatch, so it is only addressable when
// ne1 is a multiple of 64; an unaligned ne1 truncates the stride and the weight is
@@ -8693,7 +8878,10 @@ inline bool use_q4_0_bin_kernels(const ggml_backend_opencl_context *backend_ctx,
!backend_ctx->kernel_gemm_noshuffle_q4_0_f32_32b_trans_ila_a8_bin) {
return false;
}
return (tensor->ne[0] % 32 == 0) && (tensor->ne[1] % 64 == 0);
// bin kernels require ne2 == 1 and ne3 == 1 for weights
return tensor->ne[2] == 1 && tensor->ne[3] == 1 &&
(tensor->ne[0] % 32 == 0) && (tensor->ne[1] % 64 == 0);
#else
GGML_UNUSED(backend_ctx);
GGML_UNUSED(tensor);
@@ -8741,22 +8929,30 @@ static inline bool flat_large_m_enabled() {
return en;
}
// The noshuffle Q4_K weight image stores eight weights per uint texel.
static inline bool q4_K_weight_image_fits(const ggml_backend_opencl_context *backend_ctx, const ggml_tensor *tensor) {
const size_t texels = (size_t) ggml_nelements(tensor) / 8;
return texels != 0 && texels <= backend_ctx->image_max_buffer_size;
}
static inline bool use_flat_gemv_for_large_m_q4_K(const ggml_backend_opencl_context *backend_ctx, const ggml_tensor *tensor) {
if (tensor->ne[1] % 4 != 0 && tensor->ne[2] == 1 && tensor->ne[3] == 1) {
return true;
}
if (!flat_large_m_enabled()) {
if (tensor->ne[2] != 1 || tensor->ne[3] != 1 || use_q4k_tiled(backend_ctx, tensor)) {
return false;
}
// The image limit is a correctness guard, independent of the large-M opt-in.
if (!q4_K_weight_image_fits(backend_ctx, tensor)) {
return true;
}
// gemv_noshuffle variant perf drops for large M, use flat variant for large M.
// threshold is well above typical hidden/FFN dims, but below typical vocab sizes.
// note that this forces large M weights to use LM GEMM.
// EXCEPT when this branch's tiled-canonical lm_head/embed layout is active: the
// weight is converted to the 64-row tiled layout, which the flat gemv would
// misread as garbage. use_q4k_tiled owns these large-M weights, so defer to it.
return tensor->ne[1] >= 32768 && tensor->ne[2] == 1 && tensor->ne[3] == 1
&& !use_q4k_tiled(backend_ctx, tensor);
return flat_large_m_enabled() && tensor->ne[1] >= 32768;
}
static inline bool use_flat_gemv_for_large_m_q6_K(const ggml_backend_opencl_context *backend_ctx, const ggml_tensor *tensor) {
@@ -8808,7 +9004,9 @@ inline bool use_q6_k_bin_kernels(const ggml_backend_opencl_context *backend_ctx,
!backend_ctx->kernel_gemm_noshuffle_q6_k_f32_32b_trans_ila_a8_bin) {
return false;
}
return (tensor->ne[0] % 256 == 0) && (tensor->ne[1] % 64 == 0) &&
// bin kernels require ne2 == 1 and ne3 == 1 for weights
return tensor->ne[2] == 1 && tensor->ne[3] == 1 &&
(tensor->ne[0] % 256 == 0) && (tensor->ne[1] % 64 == 0) &&
!use_q6k_tiled(backend_ctx, tensor) && !use_flat_gemv_for_large_m_q6_K(backend_ctx, tensor);
#else
GGML_UNUSED(backend_ctx);
@@ -8823,7 +9021,9 @@ inline bool use_q4_k_bin_kernels(const ggml_backend_opencl_context *backend_ctx,
!backend_ctx->kernel_gemm_noshuffle_q4_k_f32_32b_trans_ila_a8_bin) {
return false;
}
return (tensor->ne[0] % 256 == 0) && (tensor->ne[1] % 64 == 0) &&
// bin kernels require ne2 == 1 and ne3 == 1 for weights
return tensor->ne[2] == 1 && tensor->ne[3] == 1 &&
(tensor->ne[0] % 256 == 0) && (tensor->ne[1] % 64 == 0) &&
!use_q4k_tiled(backend_ctx, tensor) && !use_flat_gemv_for_large_m_q4_K(backend_ctx, tensor);
#else
GGML_UNUSED(backend_ctx);
@@ -9289,10 +9489,13 @@ static bool ggml_opencl_supports_op(ggml_backend_dev_t dev, const struct ggml_te
return false;
}
if (q->ne[1] == 1) {
// DK=512 decode is bandwidth-bound and slower on the GPU
// than on the CPU; decline it here so it runs on the CPU.
// Prefill (n_q > 1) stays on the GPU.
return false;
const char * decode_env = getenv("GGML_OPENCL_FA_DK512_DECODE");
if ((decode_env && decode_env[0] == '0') ||
backend_ctx->gpu_family != ADRENO || k->ne[2] <= 0 ||
(q->ne[2] / k->ne[2] != 4 && q->ne[2] / k->ne[2] != 8) || q->ne[2] % k->ne[2] != 0 ||
!ggml_opencl_ensure_fa_f32_f16_vec_512(backend_ctx)) {
return false;
}
} else {
// prefill, BM-tile in its own FA_PREFILL_ONLY program
if (!ggml_opencl_ensure_fa_f32_f16_prefill_512(backend_ctx, /*split=*/false)) {
@@ -15033,7 +15236,7 @@ static void ggml_cl_rms_norm(ggml_backend_t backend, const ggml_tensor * src0, c
GGML_ASSERT(ne00 % 4 == 0);
const int nth = MIN(64, ne00);
const int nth = 64;
size_t global_work_size[] = {(size_t)ne01*nth, (size_t)ne02, (size_t)ne03};
size_t local_work_size[] = {(size_t)nth, 1, 1};
@@ -17880,10 +18083,7 @@ static void ggml_cl_flash_attn(ggml_backend_t backend, const ggml_tensor * q, co
}
#endif
// DK=512 (Gemma-4 global layers) runs decode-only (q1 / q1_split) on
// Adreno - it never uses the BM-tile path, and the prepass + split-tile
// programs OOM the compiler at DK=512; supports_op only admits
// n_q==1 here and prefill goes to CPU
// Compile DK512 decode separately from the prefill programs.
const bool fa_decode_only_512 = (d_head_q == 512);
// per-variant lazy compile for this (dk, dv)
@@ -17912,7 +18112,11 @@ static void ggml_cl_flash_attn(ggml_backend_t backend, const ggml_tensor * q, co
if (is_f16) {
ggml_opencl_ensure_fa_variant(backend_ctx, d_head_q, d_head_v, FA_VARIANT_F16);
} else if (is_mixed) {
ggml_opencl_ensure_fa_variant(backend_ctx, d_head_q, d_head_v, FA_VARIANT_F32_F16);
if (fa_decode_only_512 && n_q == 1) {
GGML_ASSERT(ggml_opencl_ensure_fa_f32_f16_vec_512(backend_ctx));
} else {
ggml_opencl_ensure_fa_variant(backend_ctx, d_head_q, d_head_v, FA_VARIANT_F32_F16);
}
if (fa_decode_only_512) {
// DK=512: the BM-tile prefill kernels are specifically compiled from
// FA_PREFILL_ONLY
@@ -17946,6 +18150,16 @@ static void ggml_cl_flash_attn(ggml_backend_t backend, const ggml_tensor * q, co
}
const std::pair<int, int> dk_dv = {d_head_q, d_head_v};
const int mq_gqa = n_head_kv > 0 ? n_head / n_head_kv : 0;
const std::tuple<int, int, int> mq_decode_key = {d_head_q, d_head_v, mq_gqa};
const bool mq_decode_shape = backend_ctx->gpu_family == ADRENO && is_mixed && n_q == 1 &&
d_head_q == d_head_v && n_head_kv > 0 && n_head % n_head_kv == 0 &&
((((d_head_q == 64 && mq_gqa == 8) || (d_head_q == 128 && mq_gqa == 4)) &&
backend_ctx->has_subgroup_shuffle) ||
((d_head_q == 256 || d_head_q == 512) && (mq_gqa == 4 || mq_gqa == 8)));
if (mq_decode_shape && n_kv >= 32) {
ggml_opencl_ensure_fa_f32_f16_mq_decode(backend_ctx, d_head_q, d_head_v, mq_gqa);
}
const bool use_native_q8_0_q1 = is_q8_0 && n_q == 1 &&
backend_ctx->fa.f32_q8_0_q1.count(dk_dv) > 0;
// Native q8_0 prefill — reads q8_0 directly, wg_size = cfg->bm.
@@ -18204,6 +18418,8 @@ static void ggml_cl_flash_attn(ggml_backend_t backend, const ggml_tensor * q, co
const int fd_max_n_q = (d_head_q <= FD_MAX_DK_MULTI) ? FD_MAX_N_Q_MULTI : 1;
cl_kernel fd_k_split = NULL;
bool use_fd_mq = false;
bool use_fd_mq_decode = false;
int fd_head_sub = 1;
size_t fd_mq_wg = 256; // MQ_GQA=4 kernel: Q1_WG_SIZE(64) * MQ_NSG_SPLIT(4)
bool use_fa_k_img = false; // K bound as image1d_buffer_t instead of (buf, offset)
@@ -18236,9 +18452,15 @@ static void ggml_cl_flash_attn(ggml_backend_t backend, const ggml_tensor * q, co
if (mq_enabled && mq_kv_ok && nq_in_vec_range && !is_causal &&
backend_ctx->gpu_family != INTEL &&
!use_local_tile &&
n_kv >= FD_MIN_N_KV &&
n_kv >= (mq_decode_shape ? 32 : FD_MIN_N_KV) &&
backend_ctx->fa.f32_merge.count(dk_dv) > 0) {
if (nq1_only && lmq_on && is_mixed && d_head_q == 128 && d_head_v == 128 &&
if (mq_decode_shape && backend_ctx->fa.f32_f16_mq_decode.count(mq_decode_key) > 0) {
fd_k_split = backend_ctx->fa.f32_f16_mq_decode.at(mq_decode_key);
fd_mq_wg = backend_ctx->fa.f32_f16_mq_decode_wg.at(mq_decode_key);
fd_head_sub = backend_ctx->fa.f32_f16_mq_decode_hs.at(mq_decode_key);
use_fd_mq = true;
use_fd_mq_decode = true;
} else if (nq1_only && lmq_on && is_mixed && d_head_q == 128 && d_head_v == 128 &&
gqa_ratio_dispatch == 8 &&
backend_ctx->fa.f32_f16_q1_local_mq_split_g8.count(dk_dv) > 0) {
fd_k_split = backend_ctx->fa.f32_f16_q1_local_mq_split_g8.at(dk_dv);
@@ -18517,6 +18739,14 @@ static void ggml_cl_flash_attn(ggml_backend_t backend, const ggml_tensor * q, co
int n_splits = (n_kv + fd_kv_per_split - 1) / fd_kv_per_split;
if (n_splits < FD_MIN_SPLITS) { n_splits = FD_MIN_SPLITS; }
if (n_splits > fd_max_splits) { n_splits = fd_max_splits; }
if (use_fd_mq_decode) {
const size_t wg_per_split = (size_t) n_head_kv * n_batch;
const size_t wg_target = 4 * (size_t) backend_ctx->fa.compute_units;
while (wg_per_split * n_splits < wg_target && n_splits < fd_max_splits &&
n_kv / (n_splits + 1) >= 32) {
n_splits++;
}
}
const int kv_per_split = (n_kv + n_splits - 1) / n_splits;
const int fa_partial_floats = 2 + d_head_v;
@@ -18524,15 +18754,26 @@ static void ggml_cl_flash_attn(ggml_backend_t backend, const ggml_tensor * q, co
(size_t) n_batch * n_head * n_q * n_splits * fa_partial_floats * sizeof(float);
ggml_cl_flash_attn_temp_buffer temp_partial;
cl_mem partial_buffer;
cl_int err;
temp_partial.data = clCreateBuffer(backend_ctx->context, CL_MEM_READ_WRITE,
partial_size_bytes, NULL, &err);
if (err != CL_SUCCESS) {
CL_CHECK(clFinish(backend_ctx->queue));
if (use_fd_mq_decode) {
auto & pool = backend_ctx->fa.fd_partial;
if (partial_size_bytes > pool.size) {
CL_CHECK(clFinish(backend_ctx->queue));
pool.allocate(backend_ctx->context, partial_size_bytes);
}
partial_buffer = pool.buffer;
} else {
temp_partial.data = clCreateBuffer(backend_ctx->context, CL_MEM_READ_WRITE,
partial_size_bytes, NULL, &err);
if (err != CL_SUCCESS) {
CL_CHECK(clFinish(backend_ctx->queue));
temp_partial.data = clCreateBuffer(backend_ctx->context, CL_MEM_READ_WRITE,
partial_size_bytes, NULL, &err);
}
CL_CHECK(err);
partial_buffer = temp_partial.data;
}
CL_CHECK(err);
cl_kernel k_split = fd_k_split;
int argi = 0;
@@ -18600,7 +18841,7 @@ static void ggml_cl_flash_attn(ggml_backend_t backend, const ggml_tensor * q, co
CL_CHECK(clSetKernelArg(k_split, argi++, sizeof(cl_ulong), &mask_nb3));
CL_CHECK(clSetKernelArg(k_split, argi++, sizeof(int), &mask_ne2));
CL_CHECK(clSetKernelArg(k_split, argi++, sizeof(int), &mask_ne3));
CL_CHECK(clSetKernelArg(k_split, argi++, sizeof(cl_mem), &temp_partial.data));
CL_CHECK(clSetKernelArg(k_split, argi++, sizeof(cl_mem), &partial_buffer));
CL_CHECK(clSetKernelArg(k_split, argi++, sizeof(int), &n_splits));
CL_CHECK(clSetKernelArg(k_split, argi++, sizeof(int), &kv_per_split));
@@ -18608,7 +18849,7 @@ static void ggml_cl_flash_attn(ggml_backend_t backend, const ggml_tensor * q, co
// matches Q1_WG_SIZE * NSG (MQ_GQA=4 -> 256; MQ_GQA=8 -> 192)
const size_t fd_wg = use_fd_mq ? fd_mq_wg : 64;
const size_t fd_head_dim = use_fd_mq
? (size_t)(n_head_kv * n_batch)
? (size_t)(n_head_kv * fd_head_sub * n_batch)
: (size_t)(n_head * n_batch);
size_t fd_lws[3] = { fd_wg, 1, 1 };
// gid(2) packs q_idx * n_splits + split_idx.
@@ -18617,7 +18858,7 @@ static void ggml_cl_flash_attn(ggml_backend_t backend, const ggml_tensor * q, co
cl_kernel k_merge = backend_ctx->fa.f32_merge.at(dk_dv);
argi = 0;
CL_CHECK(clSetKernelArg(k_merge, argi++, sizeof(cl_mem), &temp_partial.data));
CL_CHECK(clSetKernelArg(k_merge, argi++, sizeof(cl_mem), &partial_buffer));
CL_CHECK(clSetKernelArg(k_merge, argi++, sizeof(cl_mem), &extra_o->data_device));
CL_CHECK(clSetKernelArg(k_merge, argi++, sizeof(cl_ulong), &offset_o));
CL_CHECK(clSetKernelArg(k_merge, argi++, sizeof(int), &n_head));
@@ -23415,7 +23656,17 @@ static void ggml_cl_mul_mat(ggml_backend_t backend, const ggml_tensor * src0, co
// quant kv without FA
// used for non-contiguous src0 (the usual head-major permuted K view when n_head_kv>1)
// AND for the contiguous case that occurs when n_head_kv==1 (e.g. Gemma-4 E2B)
if ((src0t == GGML_TYPE_Q4_0 || src0t == GGML_TYPE_Q8_0) &&
// Q4_0 bin kernels use a special weight layout, which the restore below does not support.
bool q4_0_bin_layout = false;
#ifdef GGML_OPENCL_USE_ADRENO_KERNELS
q4_0_bin_layout = src0t == GGML_TYPE_Q4_0 && src0->view_src == nullptr &&
ggml_is_contiguous(src0) &&
src0->ne[2] == 1 && src0->ne[3] == 1 &&
use_adreno_kernels(backend_ctx, src0) &&
!use_adreno_moe_kernels(backend_ctx, src0) &&
use_q4_0_bin_kernels(backend_ctx, src0);
#endif
if ((src0t == GGML_TYPE_Q4_0 || src0t == GGML_TYPE_Q8_0) && !q4_0_bin_layout &&
(!ggml_is_contiguous(src0) || src1->ne[2] > src0->ne[2])) {
cl_mem f16_buf = ggml_cl_mul_mat_dequant_quant_to_f16(backend_ctx, src0, nullptr);
@@ -27185,8 +27436,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 +27541,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 +27952,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 +28057,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));
@@ -665,7 +665,7 @@ __kernel void FA_TILE_NAME(
// allow bypassing decode kernels to avoid compiler crash for DK=512 on Adreno GPUs
#ifndef FA_PREFILL_ONLY
#ifndef FA_MQ_ONLY // q1 excluded from the MQ-only (g8) program
#if !defined(FA_MQ_ONLY) && !defined(FA_VEC_ONLY)
REQD_FA_SG
__kernel void flash_attn_f32_f16_q1(
const global void * q_void, ulong q_offset,
@@ -932,14 +932,14 @@ __kernel void flash_attn_f32_f16_q1_vec(
}
ACC_TYPE dot_partial = dot4.s0 + dot4.s1 + dot4.s2 + dot4.s3;
ACC_TYPE score = sub_group_reduce_add(dot_partial) * scale;
if (logit_softcap > 0.0f) {
score = logit_softcap * tanh(score / logit_softcap);
}
if (mask_base != NULL) {
const global MASK_DATA_TYPE * mask_ptr = (const global MASK_DATA_TYPE *) mask_base;
score += slope * (ACC_TYPE) mask_ptr[k_idx];
}
if (logit_softcap > 0.0f) {
score = logit_softcap * tanh(score / logit_softcap);
}
// FA-2 online update. All threads in the subgroup see the same score,
// so m_i and l_i evolve identically across lanes within the subgroup.
@@ -1385,6 +1385,7 @@ __kernel void flash_attn_f32_f16_q1_local_mq_split(
#endif
#define MQ_WG_SIZE (Q1_WG_SIZE * MQ_NSG)
#ifndef FA_MQ_SPLIT_ONLY
REQD_SUBGROUP_SIZE_64
__kernel void flash_attn_f32_f16_q1_vec_mq(
const global void * q_void, ulong q_offset,
@@ -1606,6 +1607,8 @@ __kernel void flash_attn_f32_f16_q1_vec_mq(
}
}
#endif // !FA_MQ_SPLIT_ONLY
#ifndef MQ_NSG_SPLIT
#define MQ_NSG_SPLIT 4
#endif
@@ -1615,6 +1618,10 @@ __kernel void flash_attn_f32_f16_q1_vec_mq(
#define FA_PARTIAL_FLOATS (2 + DV)
#endif
#ifndef FA_HEAD_SUB
#define FA_HEAD_SUB 1
#endif
REQD_SUBGROUP_SIZE_64
__kernel void flash_attn_f32_f16_q1_vec_mq_split(
const global void * q_void, ulong q_offset,
@@ -1652,8 +1659,12 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split(
const int split_idx = split_q_idx % n_splits;
const int q_idx = split_q_idx / n_splits;
const int batch_idx = kvhead_batch_idx / n_head_kv;
const int head_kv_idx = kvhead_batch_idx % n_head_kv;
const int hgroups = n_head_kv * FA_HEAD_SUB;
const int batch_idx = kvhead_batch_idx / hgroups;
const int hg = kvhead_batch_idx % hgroups;
const int head_kv_idx = hg / FA_HEAD_SUB;
const int head_sub = hg % FA_HEAD_SUB;
#define FA_MQS_HEAD_IDX(h) (head_kv_idx * (MQ_GQA * FA_HEAD_SUB) + head_sub * MQ_GQA + (h))
const int kv_start = split_idx * kv_per_split;
const int kv_end = min(kv_start + kv_per_split, n_kv);
@@ -1666,7 +1677,7 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split(
if (tid == 0) {
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) {
const int head_idx = head_kv_idx * MQ_GQA + h;
const int head_idx = FA_MQS_HEAD_IDX(h);
const ulong rec_idx = ((((ulong) batch_idx * n_head + head_idx) * n_q + q_idx)
* n_splits + split_idx);
global float * rec = partial_void + rec_idx * record_stride;
@@ -1681,22 +1692,33 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split(
const global char * k_base = (const global char *) k_void + k_offset;
const global char * v_base = (const global char *) v_void + v_offset;
#ifdef FA_Q1_Q_REG
ACC_TYPE4 q_reg[MQ_GQA];
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) {
const int head_idx = FA_MQS_HEAD_IDX(h);
const ulong q_row_offset = batch_idx * q_nb3 + head_idx * q_nb2 + (ulong) q_idx * q_nb1;
const global Q_DATA_TYPE4 * q_ptr = (const global Q_DATA_TYPE4 *) (q_base + q_row_offset);
q_reg[h] = (tid_sg < DK_VEC) ? CONVERT_Q_ACC4(q_ptr[tid_sg]) : (ACC_TYPE4)(0.0f);
}
#else
// stage MQ_GQA Q rows in __local once (uniform across WG)
__local ACC_TYPE4 q_shared[MQ_GQA * DK_VEC];
for (int i = tid; i < MQ_GQA * DK_VEC; i += MQ_SPLIT_WG_SIZE) {
const int h = i / DK_VEC;
const int k = i % DK_VEC;
const int head_idx = head_kv_idx * MQ_GQA + h;
const int head_idx = FA_MQS_HEAD_IDX(h);
const ulong q_row_offset = batch_idx * q_nb3 + head_idx * q_nb2 + (ulong) q_idx * q_nb1;
const global Q_DATA_TYPE4 * q_ptr = (const global Q_DATA_TYPE4 *) (q_base + q_row_offset);
q_shared[h * DK_VEC + k] = CONVERT_Q_ACC4(q_ptr[k]);
}
barrier(CLK_LOCAL_MEM_FENCE);
#endif
float slope[MQ_GQA];
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) {
slope[h] = get_alibi_slope(max_bias, head_kv_idx * MQ_GQA + h, n_head_log2, m0, m1);
slope[h] = get_alibi_slope(max_bias, FA_MQS_HEAD_IDX(h), n_head_log2, m0, m1);
}
const global char * mask_base[MQ_GQA];
@@ -1707,7 +1729,7 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split(
(ulong) q_idx * mask_nb1;
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) {
const int head_idx = head_kv_idx * MQ_GQA + h;
const int head_idx = FA_MQS_HEAD_IDX(h);
const int mask_head_idx = head_idx % mask_ne2;
mask_base[h] = mask_base_b + mask_head_idx * mask_nb2;
}
@@ -1742,6 +1764,15 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split(
ACC_TYPE4 dot4[MQ_GQA];
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) dot4[h] = (ACC_TYPE4)(0.0f);
#ifdef FA_Q1_Q_REG
if (tid_sg < DK_VEC) {
const ACC_TYPE4 k_vec = CONVERT_KV_ACC4(k_ptr[tid_sg]);
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) {
dot4[h] = mad(q_reg[h], k_vec, dot4[h]);
}
}
#else
for (int k = tid_sg; k < DK_VEC; k += Q1_WG_SIZE) {
const ACC_TYPE4 k_vec = CONVERT_KV_ACC4(k_ptr[k]);
#pragma unroll
@@ -1749,19 +1780,20 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split(
dot4[h] = mad(q_shared[h * DK_VEC + k], k_vec, dot4[h]);
}
}
#endif
ACC_TYPE score[MQ_GQA];
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) {
const ACC_TYPE dot_partial = dot4[h].s0 + dot4[h].s1 + dot4[h].s2 + dot4[h].s3;
ACC_TYPE s = sub_group_reduce_add(dot_partial) * scale;
if (logit_softcap > 0.0f) {
s = logit_softcap * tanh(s / logit_softcap);
}
if (mask_base[h] != NULL) {
const global MASK_DATA_TYPE * mask_ptr = (const global MASK_DATA_TYPE *) mask_base[h];
s += slope[h] * (ACC_TYPE) mask_ptr[k_idx];
}
if (logit_softcap > 0.0f) {
s = logit_softcap * tanh(s / logit_softcap);
}
score[h] = s;
}
@@ -1810,7 +1842,7 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split(
barrier(CLK_LOCAL_MEM_FENCE);
if (sgid == 0) {
const int head_idx = head_kv_idx * MQ_GQA + h;
const int head_idx = FA_MQS_HEAD_IDX(h);
// fold per-subgroup (m, l) into split-level (m_c, l_c)
ACC_TYPE m_c = sg_m[h][0];
@@ -1848,6 +1880,9 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split(
}
}
#undef FA_MQS_HEAD_IDX
#ifndef FA_MQ_SPLIT_ONLY
// Cluster-parallel variant of _q1_vec_mq_split
//
// Tthe baseline keeps one 256B K row in flight per subgroup (32 lanes cooperate
@@ -1936,8 +1971,12 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split_c8(
const int split_idx = split_q_idx % n_splits;
const int q_idx = split_q_idx / n_splits;
const int batch_idx = kvhead_batch_idx / n_head_kv;
const int head_kv_idx = kvhead_batch_idx % n_head_kv;
const int hgroups = n_head_kv * FA_HEAD_SUB;
const int batch_idx = kvhead_batch_idx / hgroups;
const int hg = kvhead_batch_idx % hgroups;
const int head_kv_idx = hg / FA_HEAD_SUB;
const int head_sub = hg % FA_HEAD_SUB;
#define FA_HEAD_IDX(h) (head_kv_idx * (MQ_GQA * FA_HEAD_SUB) + head_sub * MQ_GQA + (h))
const int kv_start = split_idx * kv_per_split;
const int kv_end = min(kv_start + kv_per_split, n_kv);
@@ -1948,7 +1987,7 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split_c8(
if (tid == 0) {
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) {
const int head_idx = head_kv_idx * MQ_GQA + h;
const int head_idx = FA_HEAD_IDX(h);
const ulong rec_idx = ((((ulong) batch_idx * n_head + head_idx) * n_q + q_idx)
* n_splits + split_idx);
global float * rec = partial_void + rec_idx * record_stride;
@@ -1968,7 +2007,7 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split_c8(
for (int i = tid; i < MQ_GQA * DK_VEC; i += MQ_SPLIT_WG_SIZE) {
const int h = i / DK_VEC;
const int k = i % DK_VEC;
const int head_idx = head_kv_idx * MQ_GQA + h;
const int head_idx = FA_HEAD_IDX(h);
const ulong q_row_offset = batch_idx * q_nb3 + head_idx * q_nb2 + (ulong) q_idx * q_nb1;
const global Q_DATA_TYPE4 * q_ptr = (const global Q_DATA_TYPE4 *) (q_base + q_row_offset);
q_shared[h * DK_VEC + k] = CONVERT_Q_ACC4(q_ptr[k]);
@@ -1978,9 +2017,17 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split_c8(
float slope[MQ_GQA];
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) {
slope[h] = get_alibi_slope(max_bias, head_kv_idx * MQ_GQA + h, n_head_log2, m0, m1);
slope[h] = get_alibi_slope(max_bias, FA_HEAD_IDX(h), n_head_log2, m0, m1);
}
#ifdef FA_CL_MASK_BCAST
const global char * mask_base_b = NULL;
if (mask_void != NULL) {
mask_base_b = (const global char *) mask_void + mask_offset +
(batch_idx % mask_ne3) * mask_nb3 + (ulong) q_idx * mask_nb1;
}
const int mask_bcast = mask_base_b != NULL && mask_ne2 == 1;
#else
const global char * mask_base[MQ_GQA];
if (mask_void != NULL) {
const int mask_batch_idx = batch_idx % mask_ne3;
@@ -1989,7 +2036,7 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split_c8(
(ulong) q_idx * mask_nb1;
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) {
const int head_idx = head_kv_idx * MQ_GQA + h;
const int head_idx = FA_HEAD_IDX(h);
const int mask_head_idx = head_idx % mask_ne2;
mask_base[h] = mask_base_b + mask_head_idx * mask_nb2;
}
@@ -1997,6 +2044,7 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split_c8(
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) mask_base[h] = NULL;
}
#endif
// Per-CLUSTER online-softmax state (uniform across the cluster's lanes);
// o_acc holds this lane's DV slice {lic + FA_CL_C*i}.
@@ -2031,6 +2079,73 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split_c8(
const global KV_DATA_TYPE4 * k_ptr = (const global KV_DATA_TYPE4 *) (k_base + kv_row_base + (ulong) k_safe * k_nb1);
const global KV_DATA_TYPE4 * v_ptr = (const global KV_DATA_TYPE4 *) (v_base + v_row_base + (ulong) k_safe * v_nb1);
#if defined(FA_CL_MHRED) && MQ_GQA == 4 && FA_CL_C == 16 && FA_CL_DK == 1 && FA_CL_DV == 1
ACC_TYPE mask_val = 0.0f;
if (mask_bcast) {
mask_val = (ACC_TYPE) ((const global MASK_DATA_TYPE *) mask_base_b)[k_safe];
}
const ACC_TYPE4 k_vec_1 = CONVERT_KV_ACC4(k_ptr[lic]);
const ACC_TYPE4 v_vec_1 = CONVERT_KV_ACC4(v_ptr[lic]);
// Reduce four heads with eight shuffles and keep each head's summation order.
const int mh_b0 = lic & 1;
const int mh_b1 = lic & 2;
ACC_TYPE mh_p[MQ_GQA];
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) {
const ACC_TYPE4 d4 = mad(q_shared[h * DK_VEC + lic], k_vec_1, (ACC_TYPE4)(0.0f));
mh_p[h] = d4.s0 + d4.s1 + d4.s2 + d4.s3;
}
ACC_TYPE mh_r2[2];
#pragma unroll
for (int j = 0; j < 2; ++j) {
const ACC_TYPE keep = mh_b0 ? mh_p[j + 2] : mh_p[j];
const ACC_TYPE send = mh_b0 ? mh_p[j] : mh_p[j + 2];
mh_r2[j] = keep + sub_group_shuffle_xor(send, 1);
}
ACC_TYPE mh_r1 = (mh_b1 ? mh_r2[1] : mh_r2[0]) +
sub_group_shuffle_xor(mh_b1 ? mh_r2[0] : mh_r2[1], 2);
mh_r1 += sub_group_shuffle_xor(mh_r1, 4);
mh_r1 += sub_group_shuffle_xor(mh_r1, 8);
ACC_TYPE mh_e2[2];
{
const ACC_TYPE other = sub_group_shuffle_xor(mh_r1, 2);
mh_e2[0] = mh_b1 ? other : mh_r1;
mh_e2[1] = mh_b1 ? mh_r1 : other;
}
ACC_TYPE mh_s[MQ_GQA];
#pragma unroll
for (int j = 0; j < 2; ++j) {
const ACC_TYPE other = sub_group_shuffle_xor(mh_e2[j], 1);
mh_s[j] = mh_b0 ? other : mh_e2[j];
mh_s[j + 2] = mh_b0 ? mh_e2[j] : other;
}
#pragma unroll
for (int h = 0; h < MQ_GQA; ++h) {
ACC_TYPE s = mh_s[h] * scale;
if (logit_softcap > 0.0f) {
s = logit_softcap * tanh(s / logit_softcap);
}
if (mask_bcast) {
s += slope[h] * mask_val;
} else if (mask_base_b != NULL) {
const int mask_head_idx = FA_HEAD_IDX(h) % mask_ne2;
const global MASK_DATA_TYPE * mask_ptr = (const global MASK_DATA_TYPE *) (mask_base_b + mask_head_idx * mask_nb2);
s += slope[h] * (ACC_TYPE) mask_ptr[k_safe];
}
const ACC_TYPE sc = valid ? s : FA_M_INIT;
const ACC_TYPE m_new = max(m_i[h], sc);
const ACC_TYPE sp = native_exp(m_i[h] - m_new);
const ACC_TYPE p = native_exp(sc - m_new);
l_i[h] = l_i[h] * sp + p;
m_i[h] = m_new;
o_acc[h][0] = mad(p, v_vec_1, o_acc[h][0] * sp);
}
#else
// Dot: this lane covers DK elements {lic + FA_CL_C*i} of the cluster's row.
ACC_TYPE4 dot4[MQ_GQA];
#pragma unroll
@@ -2055,13 +2170,13 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split_c8(
s += sub_group_shuffle_xor(s, step);
}
s *= scale;
if (logit_softcap > 0.0f) {
s = logit_softcap * tanh(s / logit_softcap);
}
if (mask_base[h] != NULL) {
const global MASK_DATA_TYPE * mask_ptr = (const global MASK_DATA_TYPE *) mask_base[h];
s += slope[h] * (ACC_TYPE) mask_ptr[k_safe];
}
if (logit_softcap > 0.0f) {
s = logit_softcap * tanh(s / logit_softcap);
}
score[h] = valid ? s : FA_M_INIT;
}
@@ -2087,6 +2202,7 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split_c8(
o_acc[h][i] = mad(p_h[h], v_vec, o_acc[h][i] * sp_h[h]);
}
}
#endif
}
// Merge stage 1: fold the FA_CL_NCL cluster partials inside the subgroup.
@@ -2148,7 +2264,7 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split_c8(
barrier(CLK_LOCAL_MEM_FENCE);
if (sgid == 0) {
const int head_idx = head_kv_idx * MQ_GQA + h;
const int head_idx = FA_HEAD_IDX(h);
ACC_TYPE m_c = sg_m[h][0];
#pragma unroll
@@ -2184,6 +2300,8 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split_c8(
}
}
#undef FA_HEAD_IDX
#endif // DK_VEC/DV_VEC divisible by FA_CL_C
#endif // HAS_SUBGROUP_SHUFFLE (q1_vec_mq_split_c8)
@@ -2419,9 +2537,11 @@ __kernel void flash_attn_f32_f16_q1_vec_mq_split_k_img(
barrier(CLK_LOCAL_MEM_FENCE);
}
}
#endif // !FA_MQ_SPLIT_ONLY
#endif // !FA_DECODE_ONLY
#ifndef FA_MQ_ONLY // q1_split + merge excluded from the MQ-only (g8) program
#ifndef FA_VEC_ONLY
__kernel void flash_attn_f32_f16_q1_split(
const global void * q_void, ulong q_offset,
const global void * k_void, ulong k_offset,
@@ -2578,6 +2698,8 @@ __kernel void flash_attn_f32_f16_q1_split(
}
}
#endif // !FA_VEC_ONLY
// FD Pass 2: merge per-split partials into final O
// empty splits drop via exp(-INF)=0.
__kernel void flash_attn_f32_merge(
+175 -101
View File
@@ -3,6 +3,7 @@
#include "ggml-backend-impl.h"
#include "ggml-cpp.h"
#include "transport.h"
#include "log.h"
#include <array>
#include <chrono>
@@ -24,12 +25,6 @@
#include <algorithm>
#include <atomic>
static const char * RPC_DEBUG = std::getenv("GGML_RPC_DEBUG");
#define LOG_DBG(...) \
do { if (RPC_DEBUG) GGML_LOG_DEBUG(__VA_ARGS__); } while (0)
namespace fs = std::filesystem;
// macro for nicer error messages on server crash
@@ -89,6 +84,31 @@ enum rpc_cmd {
static_assert(RPC_CMD_HELLO == 14, "RPC_CMD_HELLO must be always 14");
static const char * rpc_cmd_name(enum rpc_cmd cmd) {
switch (cmd) {
case RPC_CMD_ALLOC_BUFFER: return "ALLOC_BUFFER";
case RPC_CMD_GET_ALIGNMENT: return "GET_ALIGNMENT";
case RPC_CMD_GET_MAX_SIZE: return "GET_MAX_SIZE";
case RPC_CMD_BUFFER_GET_BASE: return "BUFFER_GET_BASE";
case RPC_CMD_FREE_BUFFER: return "FREE_BUFFER";
case RPC_CMD_BUFFER_CLEAR: return "BUFFER_CLEAR";
case RPC_CMD_SET_TENSOR: return "SET_TENSOR";
case RPC_CMD_SET_TENSOR_HASH: return "SET_TENSOR_HASH";
case RPC_CMD_GET_TENSOR: return "GET_TENSOR";
case RPC_CMD_COPY_TENSOR: return "COPY_TENSOR";
case RPC_CMD_GRAPH_COMPUTE: return "GRAPH_COMPUTE";
case RPC_CMD_GET_DEVICE_MEMORY: return "GET_DEVICE_MEMORY";
case RPC_CMD_INIT_TENSOR: return "INIT_TENSOR";
case RPC_CMD_GET_ALLOC_SIZE: return "GET_ALLOC_SIZE";
case RPC_CMD_HELLO: return "HELLO";
case RPC_CMD_DEVICE_COUNT: return "DEVICE_COUNT";
case RPC_CMD_GRAPH_RECOMPUTE: return "GRAPH_RECOMPUTE";
case RPC_CMD_MEMSET_TENSOR: return "MEMSET_TENSOR";
case RPC_CMD_NONE: return "NONE";
default: return "UNKNOWN";
}
}
// Try RPC_CMD_SET_TENSOR_HASH first when data size is larger than this threshold
const size_t HASH_THRESHOLD = 10 * 1024 * 1024;
@@ -324,7 +344,7 @@ static bool recv_msg(socket_ptr sock, std::vector<uint8_t> & input) {
try {
input.resize(size);
} catch (const std::bad_alloc & e) {
GGML_LOG_ERROR("Failed to allocate input buffer of size %" PRIu64 "\n", size);
LOG_ERROR("Failed to allocate input buffer of size %" PRIu64 "\n", size);
return false;
}
return sock->recv_data(input.data(), size);
@@ -405,12 +425,14 @@ static bool negotiate_hello(const std::shared_ptr<socket_t> & sock) {
RPC_STATUS_ASSERT(status);
if (response.major != RPC_PROTO_MAJOR_VERSION || response.minor > RPC_PROTO_MINOR_VERSION) {
GGML_LOG_ERROR("RPC server version mismatch: %d.%d.%d\n",
LOG_ERROR("RPC server version mismatch: %d.%d.%d\n",
response.major, response.minor, response.patch);
return false;
}
sock->update_caps(response.conn_caps);
LOG_DBG("[%s] handshake ok: server version %d.%d.%d, transport: %s\n",
__func__, response.major, response.minor, response.patch, sock->transport_name());
return true;
}
@@ -646,6 +668,9 @@ void rpc_dispatcher::work() {
break;
}
if (msg_ptr->cmd != RPC_CMD_NONE) {
LOG_DBG2("[%s] %s (in: %zu, out: %zu)\n", __func__, rpc_cmd_name(msg_ptr->cmd), msg_ptr->input_size, msg_ptr->output_size);
const bool trace = rpc_debug_level() >= 3;
const auto t0 = std::chrono::steady_clock::now();
if (msg_ptr->output) {
bool status = send_rpc_cmd(sock, msg_ptr->cmd, msg_ptr->input.get(), msg_ptr->input_size, msg_ptr->output, msg_ptr->output_size);
RPC_STATUS_ASSERT(status);
@@ -653,6 +678,12 @@ void rpc_dispatcher::work() {
bool status = send_rpc_cmd(sock, msg_ptr->cmd, msg_ptr->input.get(), msg_ptr->input_size);
RPC_STATUS_ASSERT(status);
}
if (trace) {
const std::chrono::duration<double, std::milli> elapsed = std::chrono::steady_clock::now() - t0;
LOG_DBG3("[%s] %s done in %.3f ms\n", __func__, rpc_cmd_name(msg_ptr->cmd), elapsed.count());
}
} else {
LOG_DBG3("[%s] sync barrier\n", __func__);
}
msg_ptr->completion.set_value();
last_cmd = std::chrono::steady_clock::now();
@@ -690,6 +721,7 @@ static void ggml_backend_rpc_buffer_free_buffer(ggml_backend_buffer_t buffer) {
ggml_backend_rpc_buffer_context * ctx = (ggml_backend_rpc_buffer_context *)buffer->context;
auto request = std::make_shared<rpc_msg_free_buffer_req>();
request->remote_ptr = ctx->remote_ptr;
LOG_DBG2("[%s] remote_ptr: 0x%" PRIx64 "\n", __func__, request->remote_ptr);
ctx->dispatcher->send(RPC_CMD_FREE_BUFFER, request, sizeof(*request));
delete ctx;
}
@@ -701,6 +733,7 @@ static void * ggml_backend_rpc_buffer_get_base(ggml_backend_buffer_t buffer) {
}
auto request = std::make_shared<rpc_msg_buffer_get_base_req>();
request->remote_ptr = ctx->remote_ptr;
LOG_DBG2("[%s] remote_ptr: 0x%" PRIx64 "\n", __func__, ctx->remote_ptr);
rpc_msg_buffer_get_base_rsp response;
ctx->dispatcher->send(RPC_CMD_BUFFER_GET_BASE, request, sizeof(*request), &response, sizeof(response));
ctx->base_ptr = reinterpret_cast<void *>(response.base_ptr);
@@ -765,6 +798,7 @@ static enum ggml_status ggml_backend_rpc_buffer_init_tensor(ggml_backend_buffer_
// Due to bandwidth constraints, we only call the server init tensor functions if necessary.
// In particular, only quantized tensors need padding
if (ggml_is_quantized(tensor->type) && (tensor->ne[0] % 512 != 0) && (tensor->view_src == nullptr)) {
LOG_DBG2("[%s] tensor: %s\n", __func__, tensor->name);
auto request = std::make_shared<rpc_msg_init_tensor_req>();
request->tensor = serialize_tensor(tensor);
ctx->dispatcher->send(RPC_CMD_INIT_TENSOR, request, sizeof(*request));
@@ -780,6 +814,7 @@ static void ggml_backend_rpc_buffer_memset_tensor(
request->offset = offset;
request->size = size;
request->value = value;
LOG_DBG2("[%s] tensor: %s, offset: %zu, size: %zu, value: %u\n", __func__, tensor->name, offset, size, value);
ctx->dispatcher->send(RPC_CMD_MEMSET_TENSOR, request, sizeof(*request));
}
@@ -807,6 +842,7 @@ static void ggml_backend_rpc_buffer_set_tensor(ggml_backend_buffer_t buffer, ggm
ggml_backend_rpc_buffer_context * ctx = (ggml_backend_rpc_buffer_context *)buffer->context;
rpc_tensor rpc_tensor = serialize_tensor(tensor);
uint8_t cache_flag = 0;
LOG_DBG2("[%s] tensor: %s, offset: %zu, size: %zu\n", __func__, tensor->name, offset, size);
if (rpc_use_hash_cache(tensor, size)) {
auto request = std::make_shared<rpc_msg_set_tensor_hash_req>();
request->tensor = rpc_tensor;
@@ -814,6 +850,7 @@ static void ggml_backend_rpc_buffer_set_tensor(ggml_backend_buffer_t buffer, ggm
request->hash = fnv_hash((const uint8_t*)data, size);
rpc_msg_set_tensor_hash_rsp response;
ctx->dispatcher->send(RPC_CMD_SET_TENSOR_HASH, request, sizeof(*request), &response, sizeof(response));
LOG_DBG2("[%s] tensor: %s, hash: 0x%" PRIx64 ", cache: %s\n", __func__, tensor->name, request->hash, response.result ? "hit" : "miss");
if (response.result) {
// the server has the same data, no need to send it
return;
@@ -868,6 +905,7 @@ static void ggml_backend_rpc_buffer_get_tensor_2d(ggml_backend_buffer_t buffer,
static void ggml_backend_rpc_buffer_get_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * tensor, void * data, size_t offset, size_t size) {
ggml_backend_rpc_buffer_context * ctx = (ggml_backend_rpc_buffer_context *)buffer->context;
LOG_DBG2("[%s] tensor: %s, offset: %zu, size: %zu\n", __func__, tensor->name, offset, size);
auto request = std::make_shared<rpc_msg_get_tensor_req>();
request->tensor = serialize_tensor(tensor);
request->offset = offset;
@@ -883,9 +921,11 @@ static bool ggml_backend_rpc_buffer_cpy_tensor(ggml_backend_buffer_t buffer, con
ggml_backend_buffer_t dst_buffer = dst->buffer;
ggml_backend_rpc_buffer_context * dst_ctx = (ggml_backend_rpc_buffer_context *)dst_buffer->context;
if (src_ctx->dispatcher != dst_ctx->dispatcher) {
LOG_DBG2("[%s] src and dst are on different servers, falling back to a host copy\n", __func__);
return false;
}
ggml_backend_rpc_buffer_context * ctx = (ggml_backend_rpc_buffer_context *)buffer->context;
LOG_DBG2("[%s] src: %s -> dst: %s\n", __func__, src->name, dst->name);
auto request = std::make_shared<rpc_msg_copy_tensor_req>();
request->src = serialize_tensor(src);
request->dst = serialize_tensor(dst);
@@ -901,6 +941,7 @@ static void ggml_backend_rpc_buffer_clear(ggml_backend_buffer_t buffer, uint8_t
auto request = std::make_shared<rpc_msg_buffer_clear_req>();
request->remote_ptr = ctx->remote_ptr;
request->value = value;
LOG_DBG2("[%s] remote_ptr: 0x%" PRIx64 ", value: %u\n", __func__, request->remote_ptr, request->value);
ctx->dispatcher->send(RPC_CMD_BUFFER_CLEAR, request, sizeof(*request));
}
@@ -933,12 +974,16 @@ static ggml_backend_buffer_t ggml_backend_rpc_buffer_type_alloc_buffer(ggml_back
auto dispatcher = get_dispatcher(buft_ctx->endpoint);
dispatcher->send(RPC_CMD_ALLOC_BUFFER, request, sizeof(*request), &response, sizeof(response));
if (response.remote_ptr != 0) {
LOG_DBG("[%s] endpoint: %s, device: %u, size: %" PRIu64 " -> remote_ptr: 0x%" PRIx64 ", remote_size: %" PRIu64 "\n",
__func__, buft_ctx->endpoint.c_str(), buft_ctx->device, request->size, response.remote_ptr, response.remote_size);
ggml_backend_buffer_t buffer = ggml_backend_buffer_init(buft,
ggml_backend_rpc_buffer_interface,
new ggml_backend_rpc_buffer_context{dispatcher, nullptr, response.remote_ptr},
response.remote_size);
return buffer;
} else {
LOG_WARN("[%s] endpoint: %s, device: %u, size: %" PRIu64 " -> failed\n",
__func__, buft_ctx->endpoint.c_str(), buft_ctx->device, request->size);
return nullptr;
}
}
@@ -948,6 +993,7 @@ static size_t get_alignment(const std::shared_ptr<rpc_dispatcher> & dispatcher,
request->device = device;
rpc_msg_get_alignment_rsp response;
dispatcher->send(RPC_CMD_GET_ALIGNMENT, request, sizeof(*request), &response, sizeof(response));
LOG_DBG2("[%s] device: %u, alignment: %" PRIu64 "\n", __func__, device, response.alignment);
return response.alignment;
}
@@ -961,6 +1007,7 @@ static size_t get_max_size(const std::shared_ptr<rpc_dispatcher> & dispatcher, u
request->device = device;
rpc_msg_get_max_size_rsp response;
dispatcher->send(RPC_CMD_GET_MAX_SIZE, request, sizeof(*request), &response, sizeof(response));
LOG_DBG2("[%s] device: %u, max_size: %" PRIu64 "\n", __func__, device, response.max_size);
return response.max_size;
}
@@ -1026,6 +1073,7 @@ static size_t ggml_backend_rpc_buffer_type_get_alloc_size(ggml_backend_buffer_ty
std::lock_guard<std::mutex> lock(cache_mutex);
auto it = cache.find(cache_hash);
if (it != cache.end()) {
LOG_DBG3("[%s] cache hit: %s [%s] -> %zu\n", __func__, ggml_op_name(tensor->op), tensor->name, it->second);
return std::max<size_t>(it->second, min_size);
}
}
@@ -1039,6 +1087,7 @@ static size_t ggml_backend_rpc_buffer_type_get_alloc_size(ggml_backend_buffer_ty
request->srcs[i] = serialize_tensor(tensor->src[i]);
}
LOG_DBG2("[%s] cache miss: %s [%s], querying server\n", __func__, ggml_op_name(tensor->op), tensor->name);
rpc_msg_get_alloc_size_rsp response;
auto dispatcher = get_dispatcher(buft_ctx->endpoint);
dispatcher->send(RPC_CMD_GET_ALLOC_SIZE, request, sizeof(*request), &response, sizeof(response));
@@ -1048,6 +1097,8 @@ static size_t ggml_backend_rpc_buffer_type_get_alloc_size(ggml_backend_buffer_ty
cache[cache_hash] = response.alloc_size;
}
LOG_DBG2("[%s] %s [%s] -> alloc_size: %" PRIu64 "\n", __func__, ggml_op_name(tensor->op), tensor->name, response.alloc_size);
return std::max<size_t>(response.alloc_size, min_size);
}
@@ -1081,6 +1132,7 @@ static void ggml_backend_rpc_set_tensor_async(ggml_backend_t backend, ggml_tenso
ggml_backend_rpc_context * ctx = (ggml_backend_rpc_context *)backend->context;
rpc_tensor rpc_tensor = serialize_tensor(tensor);
uint8_t cache_flag = 0;
LOG_DBG2("[%s] tensor: %s, offset: %zu, size: %zu\n", __func__, tensor->name, offset, size);
if (rpc_use_hash_cache(tensor, size)) {
auto request = std::make_shared<rpc_msg_set_tensor_hash_req>();
request->tensor = rpc_tensor;
@@ -1089,6 +1141,7 @@ static void ggml_backend_rpc_set_tensor_async(ggml_backend_t backend, ggml_tenso
rpc_msg_set_tensor_hash_rsp response;
// TODO: make this async
ctx->dispatcher->send(RPC_CMD_SET_TENSOR_HASH, request, sizeof(*request), &response, sizeof(response));
LOG_DBG2("[%s] tensor: %s, hash: 0x%" PRIx64 ", cache: %s\n", __func__, tensor->name, request->hash, response.result ? "hit" : "miss");
if (response.result) {
// the server has the same data, no need to send it
return;
@@ -1170,6 +1223,7 @@ void rpc_dispatcher::graph_compute(uint32_t device, const ggml_cgraph * cgraph)
GGML_ASSERT(cgraph->n_nodes > 0);
auto & device_graph_uids = graph_uids[device];
bool reuse = cgraph->uid != 0 && device_graph_uids.count(cgraph->uid) > 0;
LOG_DBG("[%s] device: %u, uid: %" PRIu64 ", n_nodes: %u, reuse: %s\n", __func__, device, cgraph->uid, cgraph->n_nodes, reuse ? "yes" : "no");
if (reuse) {
auto request = std::make_shared<rpc_msg_graph_recompute_req>();
request->device = device;
@@ -1178,12 +1232,15 @@ void rpc_dispatcher::graph_compute(uint32_t device, const ggml_cgraph * cgraph)
} else {
if (cgraph->uid != 0) {
if (device_graph_uids.size() >= GRAPH_CACHE_MAX) {
LOG_DBG("[%s] clearing graph cache for device %u (%zu entries)\n",
__func__, device, device_graph_uids.size());
device_graph_uids.clear();
}
device_graph_uids.insert(cgraph->uid);
}
size_t input_size = 0;
uint8_t * input = serialize_graph(device, cgraph, this, &input_size);
LOG_DBG2("[%s] serialized graph: %zu bytes\n", __func__, input_size);
std::shared_ptr<uint8_t> input_ptr(input, std::default_delete<uint8_t[]>());
send_async(RPC_CMD_GRAPH_COMPUTE, input_ptr, input_size);
}
@@ -1264,6 +1321,7 @@ ggml_backend_buffer_type_t ggml_backend_rpc_buffer_type(const char * endpoint, u
/* .context = */ buft_ctx
};
buft_map[buft_name] = buft;
LOG_DBG("[%s] created %s (alignment: %zu, max_size: %zu)\n", __func__, buft_name.c_str(), alignment, max_size);
return buft;
}
@@ -1277,6 +1335,7 @@ ggml_backend_t ggml_backend_rpc_init(const char * endpoint, uint32_t device) {
/* .name = */ dev_name,
};
auto reg = ggml_backend_rpc_add_server(endpoint);
LOG_DBG("[%s] %s\n", __func__, dev_name.c_str());
ggml_backend_t backend = new ggml_backend {
/* .guid = */ ggml_backend_rpc_guid(),
/* .iface = */ ggml_backend_rpc_interface,
@@ -1296,6 +1355,7 @@ void ggml_backend_rpc_get_device_memory(const char * endpoint, uint32_t device,
request->device = device;
rpc_msg_get_device_memory_rsp response;
dispatcher->send(RPC_CMD_GET_DEVICE_MEMORY, request, sizeof(*request), &response, sizeof(response));
LOG_DBG2("[%s] device: %u, free: %" PRIu64 ", total: %" PRIu64 "\n", __func__, device, response.free_mem, response.total_mem);
*free = response.free_mem;
*total = response.total_mem;
}
@@ -1393,7 +1453,7 @@ bool rpc_server::get_alloc_size(const rpc_msg_get_alloc_size_req & request, rpc_
ggml_tensor * tensor = deserialize_tensor(ctx, &request.tensor);
if (tensor == nullptr) {
GGML_LOG_ERROR("Null tensor pointer passed to server get_alloc_size function.\n");
LOG_ERROR("Null tensor pointer passed to server get_alloc_size function.\n");
return false;
}
for (int i = 0; i < GGML_MAX_SRC; i++) {
@@ -1402,7 +1462,7 @@ bool rpc_server::get_alloc_size(const rpc_msg_get_alloc_size_req & request, rpc_
}
}
LOG_DBG("[%s] device: %d, buffer: %p, data: %p\n", __func__, dev_id, (void*)tensor->buffer, tensor->data);
LOG_DBG2("[%s] device: %u, buffer: %p, data: %p\n", __func__, dev_id, (void*)tensor->buffer, tensor->data);
if (tensor->buffer == nullptr) {
//No buffer allocated.
buft = ggml_backend_get_default_buffer_type(backends[dev_id]);
@@ -1427,11 +1487,11 @@ bool rpc_server::alloc_buffer(const rpc_msg_alloc_buffer_req & request, rpc_msg_
if (buffer != nullptr) {
response.remote_ptr = reinterpret_cast<uint64_t>(buffer);
response.remote_size = buffer->size;
LOG_DBG("[%s] device: %d, size: %" PRIu64 " -> remote_ptr: %" PRIx64 ", remote_size: %" PRIu64 "\n",
__func__, dev_id, request.size, response.remote_ptr, response.remote_size);
LOG_DBG("[%s] device: %u, size: %" PRIu64 " -> remote_ptr: 0x%" PRIx64 ", remote_size: %" PRIu64 "\n",
__func__, dev_id, request.size, response.remote_ptr, response.remote_size);
buffers.insert(buffer);
} else {
LOG_DBG("[%s] device: %d, size: %" PRIu64 " -> failed\n", __func__, dev_id, request.size);
LOG_WARN("[%s] device: %u, size: %" PRIu64 " -> failed\n", __func__, dev_id, request.size);
}
return true;
}
@@ -1443,7 +1503,7 @@ bool rpc_server::get_alignment(const rpc_msg_get_alignment_req & request, rpc_ms
}
ggml_backend_buffer_type_t buft = ggml_backend_get_default_buffer_type(backends[dev_id]);
size_t alignment = ggml_backend_buft_get_alignment(buft);
LOG_DBG("[%s] device: %d, alignment: %lu\n", __func__, dev_id, alignment);
LOG_DBG2("[%s] device: %u, alignment: %zu\n", __func__, dev_id, alignment);
response.alignment = alignment;
return true;
}
@@ -1455,16 +1515,16 @@ bool rpc_server::get_max_size(const rpc_msg_get_max_size_req & request, rpc_msg_
}
ggml_backend_buffer_type_t buft = ggml_backend_get_default_buffer_type(backends[dev_id]);
size_t max_size = ggml_backend_buft_get_max_size(buft);
LOG_DBG("[%s] device: %d, max_size: %lu\n", __func__, dev_id, max_size);
LOG_DBG2("[%s] device: %u, max_size: %zu\n", __func__, dev_id, max_size);
response.max_size = max_size;
return true;
}
bool rpc_server::buffer_get_base(const rpc_msg_buffer_get_base_req & request, rpc_msg_buffer_get_base_rsp & response) {
LOG_DBG("[%s] remote_ptr: %" PRIx64 "\n", __func__, request.remote_ptr);
LOG_DBG2("[%s] remote_ptr: 0x%" PRIx64 "\n", __func__, request.remote_ptr);
ggml_backend_buffer_t buffer = reinterpret_cast<ggml_backend_buffer_t>(request.remote_ptr);
if (buffers.find(buffer) == buffers.end()) {
GGML_LOG_ERROR("[%s] buffer not found\n", __func__);
LOG_ERROR("[%s] buffer not found\n", __func__);
return false;
}
void * base = ggml_backend_buffer_get_base(buffer);
@@ -1474,14 +1534,15 @@ bool rpc_server::buffer_get_base(const rpc_msg_buffer_get_base_req & request, rp
bool rpc_server::free_buffer(const rpc_msg_free_buffer_req & request) {
sync_all_backends();
LOG_DBG("[%s] remote_ptr: %" PRIx64 "\n", __func__, request.remote_ptr);
LOG_DBG2("[%s] remote_ptr: 0x%" PRIx64 "\n", __func__, request.remote_ptr);
ggml_backend_buffer_t buffer = reinterpret_cast<ggml_backend_buffer_t>(request.remote_ptr);
if (buffers.find(buffer) == buffers.end()) {
GGML_LOG_ERROR("[%s] buffer not found\n", __func__);
LOG_ERROR("[%s] buffer not found\n", __func__);
return false;
}
// Discard all cached graphs to avoid use-after-free in graph_recompute,
// since their nodes may hold pointers to the buffer being freed.
// since their nodes may hold pointers to the buffer being freed. The client
// will have to re-serialize its graphs from now on.
for (auto & sgs : stored_graphs) {
for (auto & sg : sgs) {
sg.second.graph = nullptr;
@@ -1494,10 +1555,10 @@ bool rpc_server::free_buffer(const rpc_msg_free_buffer_req & request) {
bool rpc_server::buffer_clear(const rpc_msg_buffer_clear_req & request) {
sync_all_backends();
LOG_DBG("[%s] remote_ptr: %" PRIx64 ", value: %u\n", __func__, request.remote_ptr, request.value);
LOG_DBG2("[%s] remote_ptr: 0x%" PRIx64 ", value: %u\n", __func__, request.remote_ptr, request.value);
ggml_backend_buffer_t buffer = reinterpret_cast<ggml_backend_buffer_t>(request.remote_ptr);
if (buffers.find(buffer) == buffers.end()) {
GGML_LOG_ERROR("[%s] buffer not found\n", __func__);
LOG_ERROR("[%s] buffer not found\n", __func__);
return false;
}
ggml_backend_buffer_clear(buffer, request.value);
@@ -1516,13 +1577,13 @@ bool rpc_server::memset_tensor(const rpc_msg_memset_tensor_req & request) {
ggml_context * ctx = ctx_ptr.get();
ggml_tensor * tensor = deserialize_tensor(ctx, &request.tensor);
if (tensor == nullptr || tensor->buffer == nullptr) {
GGML_LOG_ERROR("[%s] error deserializing tensor\n", __func__);
LOG_ERROR("[%s] error deserializing tensor\n", __func__);
return false;
}
const uint64_t tensor_size = ggml_nbytes(tensor);
if (request.offset > tensor_size || request.size > tensor_size - request.offset) {
GGML_LOG_ERROR("[%s] tensor region (offset=%" PRIu64 ", size=%" PRIu64 ") out of tensor bounds [0, %" PRIu64 ")\n",
LOG_ERROR("[%s] tensor region (offset=%" PRIu64 ", size=%" PRIu64 ") out of tensor bounds [0, %" PRIu64 ")\n",
__func__, request.offset, request.size, tensor_size);
return false;
}
@@ -1530,22 +1591,22 @@ bool rpc_server::memset_tensor(const rpc_msg_memset_tensor_req & request) {
const uint64_t buffer_start = (uint64_t) ggml_backend_buffer_get_base(tensor->buffer);
const uint64_t buffer_size = ggml_backend_buffer_get_size(tensor->buffer);
if (request.tensor.data < buffer_start) {
GGML_LOG_ERROR("[%s] tensor data before buffer start\n", __func__);
LOG_ERROR("[%s] tensor data before buffer start\n", __func__);
return false;
}
const uint64_t data_offset = request.tensor.data - buffer_start;
if (data_offset > buffer_size ||
request.offset > buffer_size - data_offset ||
request.size > buffer_size - data_offset - request.offset) {
GGML_LOG_ERROR("[%s] tensor region out of buffer bounds\n", __func__);
LOG_ERROR("[%s] tensor region out of buffer bounds\n", __func__);
return false;
}
if (tensor->buffer->iface.memset_tensor == nullptr) {
GGML_LOG_ERROR("[%s] memset not implemented by backend buffer\n", __func__);
LOG_ERROR("[%s] memset not implemented by backend buffer\n", __func__);
return false;
}
LOG_DBG("[%s] buffer: %p, data: %p, offset: %" PRIu64 ", size: %" PRIu64 ", value: %u\n",
LOG_DBG2("[%s] buffer: %p, data: %p, offset: %" PRIu64 ", size: %" PRIu64 ", value: %u\n",
__func__, (void *) tensor->buffer, tensor->data, request.offset, request.size, request.value);
ggml_backend_tensor_memset(tensor, request.value, request.offset, request.size);
return true;
@@ -1554,13 +1615,13 @@ bool rpc_server::memset_tensor(const rpc_msg_memset_tensor_req & request) {
ggml_tensor * rpc_server::deserialize_tensor(struct ggml_context * ctx, const rpc_tensor * tensor) {
// Validate tensor type before using it
if (tensor->type >= GGML_TYPE_COUNT) {
GGML_LOG_ERROR("[%s] invalid tensor type received: %u\n", __func__, tensor->type);
LOG_ERROR("[%s] invalid tensor type received: %u\n", __func__, tensor->type);
return nullptr;
}
// Fix: Prevent division by zero if blck_size is 0 (e.g., deprecated types)
if (ggml_blck_size((enum ggml_type)tensor->type) == 0) {
GGML_LOG_ERROR("[%s] invalid tensor type received (blck_size is 0): %u\n", __func__, tensor->type);
LOG_ERROR("[%s] invalid tensor type received (blck_size is 0): %u\n", __func__, tensor->type);
return nullptr;
}
@@ -1569,7 +1630,7 @@ ggml_tensor * rpc_server::deserialize_tensor(struct ggml_context * ctx, const rp
// ggml_new_tensor_4d might fail if dimensions are invalid, although less likely to crash than invalid type
if (result == nullptr) {
GGML_LOG_ERROR("[%s] ggml_new_tensor_4d failed for type %u\n", __func__, tensor->type);
LOG_ERROR("[%s] ggml_new_tensor_4d failed for type %u\n", __func__, tensor->type);
return nullptr;
}
@@ -1588,11 +1649,11 @@ ggml_tensor * rpc_server::deserialize_tensor(struct ggml_context * ctx, const rp
uint64_t buffer_size = (uint64_t) ggml_backend_buffer_get_size(result->buffer);
if (tensor->data + tensor_size < tensor->data ||
tensor->data < buffer_start || tensor->data + tensor_size > buffer_start + buffer_size) {
GGML_LOG_ERROR("[%s] tensor '%s' (op %s, type %s, ne [%" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 "]) "
"data [0x%" PRIx64 ", 0x%" PRIx64 ") out of buffer bounds [0x%" PRIx64 ", 0x%" PRIx64 ")\n",
__func__, tensor->name, ggml_op_name((ggml_op) tensor->op), ggml_type_name(result->type),
result->ne[0], result->ne[1], result->ne[2], result->ne[3],
tensor->data, tensor->data + tensor_size, buffer_start, buffer_start + buffer_size);
LOG_ERROR("[%s] tensor '%s' (op %s, type %s, ne [%" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 "]) "
"data [0x%" PRIx64 ", 0x%" PRIx64 ") out of buffer bounds [0x%" PRIx64 ", 0x%" PRIx64 ")\n",
__func__, tensor->name, ggml_op_name((ggml_op) tensor->op), ggml_type_name(result->type),
result->ne[0], result->ne[1], result->ne[2], result->ne[3],
tensor->data, tensor->data + tensor_size, buffer_start, buffer_start + buffer_size);
return nullptr;
}
}
@@ -1632,10 +1693,10 @@ bool rpc_server::set_tensor(const std::vector<uint8_t> & input) {
ggml_context * ctx = ctx_ptr.get();
ggml_tensor * tensor = deserialize_tensor(ctx, in_tensor);
if (tensor == nullptr || tensor->buffer == nullptr) {
GGML_LOG_ERROR("[%s] error deserializing tensor\n", __func__);
LOG_ERROR("[%s] error deserializing tensor\n", __func__);
return false;
}
LOG_DBG("[%s] buffer: %p, data: %p, offset: %" PRIu64 ", size: %zu\n", __func__, (void*)tensor->buffer, tensor->data, offset, size);
LOG_DBG2("[%s] buffer: %p, data: %p, offset: %" PRIu64 ", size: %zu\n", __func__, (void*)tensor->buffer, tensor->data, offset, size);
// sanitize tensor->data
{
@@ -1643,7 +1704,7 @@ bool rpc_server::set_tensor(const std::vector<uint8_t> & input) {
const size_t p1 = p0 + ggml_backend_buffer_get_size(tensor->buffer);
if (in_tensor->data + offset < p0 || in_tensor->data + offset >= p1 || size > (p1 - in_tensor->data - offset)) {
GGML_LOG_ERROR("[%s] tensor data region (data=0x%" PRIx64 ", offset=%" PRIu64 ", size=%zu) out of buffer bounds [0x%zx, 0x%zx)\n",
LOG_ERROR("[%s] tensor data region (data=0x%" PRIx64 ", offset=%" PRIu64 ", size=%zu) out of buffer bounds [0x%zx, 0x%zx)\n",
__func__, in_tensor->data, offset, size, p0, p1);
return false;
}
@@ -1658,7 +1719,7 @@ bool rpc_server::set_tensor(const std::vector<uint8_t> & input) {
fs::path cache_file = fs::path(cache_dir) / hash_str;
std::ofstream ofs(cache_file, std::ios::binary);
ofs.write((const char *)data, size);
GGML_LOG_INFO("[%s] saved to '%s'\n", __func__, cache_file.string().c_str());
LOG_INFO("[%s] saved to '%s'\n", __func__, cache_file.string().c_str());
}
ggml_backend_tensor_set(tensor, data, offset, size);
return true;
@@ -1693,10 +1754,10 @@ bool rpc_server::set_tensor_2d(const std::vector<uint8_t> & input) {
ggml_context * ctx = ctx_ptr.get();
ggml_tensor * tensor = deserialize_tensor(ctx, in_tensor);
if (tensor == nullptr || tensor->buffer == nullptr) {
GGML_LOG_ERROR("[%s] error deserializing tensor\n", __func__);
LOG_ERROR("[%s] error deserializing tensor\n", __func__);
return false;
}
LOG_DBG("[%s] buffer: %p, data: %p, offset: %" PRIu64 ", size: %" PRIu64 ", n_copies: %" PRIu64 ", stride: %" PRIu64 "\n",
LOG_DBG2("[%s] buffer: %p, data: %p, offset: %" PRIu64 ", size: %" PRIu64 ", n_copies: %" PRIu64 ", stride: %" PRIu64 "\n",
__func__, (void*)tensor->buffer, tensor->data, offset, size, n_copies, stride);
// sanitize tensor->data
@@ -1709,13 +1770,13 @@ bool rpc_server::set_tensor_2d(const std::vector<uint8_t> & input) {
const uint64_t p1 = p0 + ggml_backend_buffer_get_size(tensor->buffer);
if (in_tensor->data < p0 || in_tensor->data > p1 || offset > p1 - in_tensor->data || span > p1 - in_tensor->data - offset) {
GGML_LOG_ERROR("[%s] tensor data region (data=0x%" PRIx64 ", offset=%" PRIu64 ", span=%" PRIu64 ") out of buffer bounds [0x%" PRIx64 ", 0x%" PRIx64 ")\n",
__func__, in_tensor->data, offset, span, p0, p1);
LOG_ERROR("[%s] tensor data region (data=0x%" PRIx64 ", offset=%" PRIu64 ", span=%" PRIu64 ") out of buffer bounds [0x%" PRIx64 ", 0x%" PRIx64 ")\n",
__func__, in_tensor->data, offset, span, p0, p1);
return false;
}
if (offset > ggml_nbytes(tensor) || span > ggml_nbytes(tensor) - offset) {
GGML_LOG_ERROR("[%s] tensor write region (offset=%" PRIu64 ", span=%" PRIu64 ") out of tensor bounds (%zu)\n",
__func__, offset, span, ggml_nbytes(tensor));
LOG_ERROR("[%s] tensor write region (offset=%" PRIu64 ", span=%" PRIu64 ") out of tensor bounds (%zu)\n",
__func__, offset, span, ggml_nbytes(tensor));
return false;
}
}
@@ -1750,10 +1811,12 @@ bool rpc_server::set_tensor_hash(const rpc_msg_set_tensor_hash_req & request, rp
sync_all_backends();
std::vector<uint8_t> cached_file;
if (!get_cached_file(request.hash, cached_file)) {
LOG_DBG2("[%s] hash: 0x%" PRIx64 ", cache miss\n", __func__, request.hash);
response.result = 0;
return true;
}
size_t size = cached_file.size();
LOG_DBG2("[%s] hash: 0x%" PRIx64 ", cache hit (%zu bytes)\n", __func__, request.hash, size);
struct ggml_init_params params {
/*.mem_size =*/ ggml_tensor_overhead(),
/*.mem_buffer =*/ NULL,
@@ -1764,10 +1827,10 @@ bool rpc_server::set_tensor_hash(const rpc_msg_set_tensor_hash_req & request, rp
ggml_context * ctx = ctx_ptr.get();
ggml_tensor * tensor = deserialize_tensor(ctx, &request.tensor);
if (tensor == nullptr || tensor->buffer == nullptr) {
GGML_LOG_ERROR("[%s] error deserializing tensor\n", __func__);
LOG_ERROR("[%s] error deserializing tensor\n", __func__);
return false;
}
LOG_DBG("[%s] buffer: %p, data: %p, offset: %" PRIu64 ", size: %zu, hash: %" PRIx64 "\n",
LOG_DBG2("[%s] buffer: %p, data: %p, offset: %" PRIu64 ", size: %zu, hash: %" PRIx64 "\n",
__func__, (void*)tensor->buffer, tensor->data, request.offset, size, request.hash);
// sanitize tensor->data
@@ -1778,7 +1841,7 @@ bool rpc_server::set_tensor_hash(const rpc_msg_set_tensor_hash_req & request, rp
if (request.tensor.data + request.offset < p0
|| request.tensor.data + request.offset >= p1
|| size > (p1 - request.tensor.data - request.offset)) {
GGML_LOG_ERROR("[%s] tensor data region (data=0x%" PRIx64 ", offset=%" PRIu64 ", size=%zu, hash=0x%" PRIx64 ") out of buffer bounds [0x%zx, 0x%zx)\n",
LOG_ERROR("[%s] tensor data region (data=0x%" PRIx64 ", offset=%" PRIu64 ", size=%zu, hash=0x%" PRIx64 ") out of buffer bounds [0x%zx, 0x%zx)\n",
__func__, request.tensor.data, request.offset, size, request.hash, p0, p1);
return false;
}
@@ -1800,24 +1863,24 @@ bool rpc_server::init_tensor(const rpc_msg_init_tensor_req & request) {
ggml_context * ctx = ctx_ptr.get();
ggml_tensor * tensor = deserialize_tensor(ctx, &request.tensor);
if (tensor == nullptr) {
GGML_LOG_ERROR("Null tensor pointer passed to server init_tensor function.\n");
LOG_ERROR("Null tensor pointer passed to server init_tensor function.\n");
return false;
}
LOG_DBG("[%s] buffer: %p, data: %p\n", __func__, (void*)tensor->buffer, tensor->data);
LOG_DBG2("[%s] buffer: %p, data: %p\n", __func__, (void*)tensor->buffer, tensor->data);
// Call the backend's buffer_init_tensor function
ggml_backend_buffer_t buffer = tensor->buffer;
if (buffer && buffer->iface.init_tensor) {
buffer->iface.init_tensor(buffer, tensor);
} else {
if (!buffer) {
GGML_LOG_ERROR("Tensor with null buffer passed to init_tensor function\n");
LOG_ERROR("Tensor with null buffer passed to init_tensor function\n");
}
}
if (tensor->extra != nullptr) {
// This pointer can either be passed around client/server, or probably better stored server-side and kept track of.
// Currently unimplemented.
GGML_LOG_ERROR("tensor->extra populated by the backend, this is currently unsupported.\n");
LOG_ERROR("tensor->extra populated by the backend, this is currently unsupported.\n");
return false;
}
@@ -1836,10 +1899,10 @@ bool rpc_server::get_tensor(const rpc_msg_get_tensor_req & request, std::vector<
ggml_context * ctx = ctx_ptr.get();
ggml_tensor * tensor = deserialize_tensor(ctx, &request.tensor);
if (tensor == nullptr || tensor->buffer == nullptr) {
GGML_LOG_ERROR("[%s] error deserializing tensor\n", __func__);
LOG_ERROR("[%s] error deserializing tensor\n", __func__);
return false;
}
LOG_DBG("[%s] buffer: %p, data: %p, offset: %" PRIu64 ", size: %" PRIu64 "\n", __func__, (void*)tensor->buffer, tensor->data, request.offset, request.size);
LOG_DBG2("[%s] buffer: %p, data: %p, offset: %" PRIu64 ", size: %" PRIu64 "\n", __func__, (void*)tensor->buffer, tensor->data, request.offset, request.size);
// sanitize tensor->data
{
@@ -1849,7 +1912,7 @@ bool rpc_server::get_tensor(const rpc_msg_get_tensor_req & request, std::vector<
if (request.tensor.data + request.offset < p0 ||
request.tensor.data + request.offset >= p1 ||
request.size > (p1 - request.tensor.data - request.offset)) {
GGML_LOG_ERROR("[%s] requested tensor region (data=0x%" PRIx64 ", offset=%" PRIu64 ", size=%" PRIu64 ") out of buffer bounds [0x%zx, 0x%zx)\n",
LOG_ERROR("[%s] requested tensor region (data=0x%" PRIx64 ", offset=%" PRIu64 ", size=%" PRIu64 ") out of buffer bounds [0x%zx, 0x%zx)\n",
__func__, request.tensor.data, request.offset, request.size, p0, p1);
return false;
}
@@ -1872,10 +1935,10 @@ bool rpc_server::get_tensor_2d(const rpc_msg_get_tensor_2d_req & request, std::v
ggml_context * ctx = ctx_ptr.get();
ggml_tensor * tensor = deserialize_tensor(ctx, &request.tensor);
if (tensor == nullptr || tensor->buffer == nullptr) {
GGML_LOG_ERROR("[%s] error deserializing tensor\n", __func__);
LOG_ERROR("[%s] error deserializing tensor\n", __func__);
return false;
}
LOG_DBG("[%s] buffer: %p, data: %p, offset: %" PRIu64 ", size: %" PRIu64 ", n_copies: %" PRIu64 ", stride: %" PRIu64 "\n",
LOG_DBG2("[%s] buffer: %p, data: %p, offset: %" PRIu64 ", size: %" PRIu64 ", n_copies: %" PRIu64 ", stride: %" PRIu64 "\n",
__func__, (void*)tensor->buffer, tensor->data, request.offset, request.size, request.n_copies, request.stride);
// sanitize tensor->data
@@ -1892,13 +1955,13 @@ bool rpc_server::get_tensor_2d(const rpc_msg_get_tensor_2d_req & request, std::v
if (request.tensor.data < p0 || request.tensor.data > p1 || request.offset > p1 - request.tensor.data ||
span > p1 - request.tensor.data - request.offset) {
GGML_LOG_ERROR("[%s] tensor data region (data=0x%" PRIx64 ", offset=%" PRIu64 ", span=%" PRIu64 ") out of buffer bounds [0x%" PRIx64 ", 0x%" PRIx64 ")\n",
__func__, request.tensor.data, request.offset, span, p0, p1);
LOG_ERROR("[%s] tensor data region (data=0x%" PRIx64 ", offset=%" PRIu64 ", span=%" PRIu64 ") out of buffer bounds [0x%" PRIx64 ", 0x%" PRIx64 ")\n",
__func__, request.tensor.data, request.offset, span, p0, p1);
return false;
}
if (request.offset > ggml_nbytes(tensor) || span > ggml_nbytes(tensor) - request.offset) {
GGML_LOG_ERROR("[%s] tensor read region (offset=%" PRIu64 ", span=%" PRIu64 ") out of tensor bounds (%zu)\n",
__func__, request.offset, span, ggml_nbytes(tensor));
LOG_ERROR("[%s] tensor read region (offset=%" PRIu64 ", span=%" PRIu64 ") out of tensor bounds (%zu)\n",
__func__, request.offset, span, ggml_nbytes(tensor));
return false;
}
}
@@ -1922,7 +1985,7 @@ bool rpc_server::copy_tensor(const rpc_msg_copy_tensor_req & request, rpc_msg_co
ggml_tensor * src = deserialize_tensor(ctx, &request.src);
ggml_tensor * dst = deserialize_tensor(ctx, &request.dst);
if (src == nullptr || dst == nullptr || src->buffer == nullptr || dst->buffer == nullptr) {
GGML_LOG_ERROR("[%s] error deserializing tensors\n", __func__);
LOG_ERROR("[%s] error deserializing tensors\n", __func__);
return false;
}
@@ -1932,7 +1995,7 @@ bool rpc_server::copy_tensor(const rpc_msg_copy_tensor_req & request, rpc_msg_co
uint64_t dst_buf_sz = (uint64_t) ggml_backend_buffer_get_size(dst->buffer);
if (dst_data + src_size > dst_base + dst_buf_sz) {
GGML_LOG_ERROR("[%s] out-of-bounds write in rpc_server::copy_tensor:\n"
LOG_ERROR("[%s] out-of-bounds write in rpc_server::copy_tensor:\n"
" write range : [0x%" PRIx64 ", 0x%" PRIx64 "]\n"
" buffer base: [0x%" PRIx64 ", 0x%" PRIx64 "]\n",
__func__,
@@ -1943,7 +2006,7 @@ bool rpc_server::copy_tensor(const rpc_msg_copy_tensor_req & request, rpc_msg_co
return false;
}
LOG_DBG("[%s] src->buffer: %p, dst->buffer: %p\n",
LOG_DBG2("[%s] src->buffer: %p, dst->buffer: %p\n",
__func__, (void*) src->buffer, (void*) dst->buffer);
response.result = ggml_backend_buffer_copy_tensor(src, dst);
@@ -1969,7 +2032,7 @@ ggml_tensor * rpc_server::create_node(uint64_t id,
return nullptr;
}
if (result->buffer == nullptr && result->data != nullptr) {
GGML_LOG_ERROR("[%s] invalid data ptr", __func__);
LOG_ERROR("[%s] invalid data ptr\n", __func__);
return nullptr;
}
tensor_map[id] = result;
@@ -1981,7 +2044,7 @@ ggml_tensor * rpc_server::create_node(uint64_t id,
result->src[i] = create_node(tensor->src[i], ctx, tensor_ptrs, tensor_map);
// If the recursive call failed for a non-zero ID, propagate the error
if (result->src[i] == nullptr) {
GGML_LOG_ERROR("[%s] failed to create source node %d (src_id=%" PRIu64 ") for node id %" PRIu64 "\n",
LOG_ERROR("[%s] failed to create source node %d (src_id=%" PRIu64 ") for node id %" PRIu64 "\n",
__func__, i, tensor->src[i], id);
// Must return nullptr to signal failure up the call stack
return nullptr;
@@ -1996,7 +2059,7 @@ ggml_tensor * rpc_server::create_node(uint64_t id,
result->view_src = create_node(tensor->view_src, ctx, tensor_ptrs, tensor_map);
// If the recursive call failed for a non-zero ID, propagate the error
if (result->view_src == nullptr) {
GGML_LOG_ERROR("[%s] failed to create view_src node (view_src_id=%" PRIu64 ") for node id %" PRIu64 "\n",
LOG_ERROR("[%s] failed to create view_src node (view_src_id=%" PRIu64 ") for node id %" PRIu64 "\n",
__func__, tensor->view_src, id);
// Must return nullptr to signal failure up the call stack
return nullptr;
@@ -2041,6 +2104,8 @@ bool rpc_server::graph_compute(const std::vector<uint8_t> & input) {
// graphs with uid == 0 are not cached, see GRAPH_CACHE_MAX for the eviction policy
if (uid != 0 && stored_graphs[device].size() >= GRAPH_CACHE_MAX) {
LOG_DBG("[%s] clearing graph cache for device %u (%zu entries)\n",
__func__, device, stored_graphs[device].size());
stored_graphs[device].clear();
}
stored_graph sg_tmp;
@@ -2067,6 +2132,9 @@ bool rpc_server::graph_compute(const std::vector<uint8_t> & input) {
}
std::unordered_map<uint64_t, ggml_tensor*> tensor_map;
tensor_map.reserve(n_nodes);
// the compute itself is asynchronous, so building the graph is the only server-side cost
// observable here - the round trip is timed on the client
const auto t_deserialize = std::chrono::steady_clock::now();
for (uint32_t i = 0; i < n_nodes; i++) {
int64_t id;
memcpy(&id, &nodes[i], sizeof(id));
@@ -2075,17 +2143,21 @@ bool rpc_server::graph_compute(const std::vector<uint8_t> & input) {
// If id was 0, create_node returning nullptr is expected.
// If id was non-zero and create_node returned nullptr, it indicates a deserialization error.
if (graph->nodes[i] == nullptr && id != 0) {
GGML_LOG_ERROR("[%s] failed to create graph node %d (id=%" PRId64 ")\n", __func__, i, id);
LOG_ERROR("[%s] failed to create graph node %d (id=%" PRId64 ")\n", __func__, i, id);
return false;
}
if (graph->nodes[i] != nullptr) {
LOG_DBG3("[%s] node %u: id=%" PRId64 ", op=%s, name=%s\n", __func__, i, id, ggml_op_name(graph->nodes[i]->op), graph->nodes[i]->name);
const size_t hash_pos = ggml_hash_insert(&graph->visited_hash_set, graph->nodes[i]);
graph->use_counts[hash_pos] = tensor_ptrs.at(id)->use_count;
}
}
const std::chrono::duration<double, std::milli> deserialize_ms = std::chrono::steady_clock::now() - t_deserialize;
ggml_status status = ggml_backend_graph_compute_async(backends[device], graph);
GGML_ASSERT(status == GGML_STATUS_SUCCESS && "Unsuccessful graph computations are not supported with RPC");
sg.graph = graph;
LOG_DBG3("[%s] device: %u, graph build: %.3f ms\n", __func__, device, deserialize_ms.count());
return true;
}
@@ -2096,7 +2168,7 @@ bool rpc_server::graph_recompute(const rpc_msg_graph_recompute_req & request) {
}
auto it = stored_graphs[device].find(request.uid);
if (it == stored_graphs[device].end() || it->second.graph == nullptr) {
GGML_LOG_ERROR("[%s] device: %u, graph with uid %" PRIu64 " not found\n", __func__, device, request.uid);
LOG_ERROR("[%s] device: %u, graph with uid %" PRIu64 " not found\n", __func__, device, request.uid);
return false;
}
ggml_cgraph * graph = it->second.graph;
@@ -2130,7 +2202,7 @@ bool rpc_server::comm_init(const rpc_msg_comm_init_req & request, rpc_msg_comm_i
if (request.rank == 0) {
socket_ptr srv = socket_t::create_server("0.0.0.0", request.port);
if (srv == nullptr) {
GGML_LOG_ERROR("[%s] failed to listen on comm port %u\n", __func__, request.port);
LOG_ERROR("[%s] failed to listen on comm port %u\n", __func__, request.port);
return true;
}
state.peer = srv->accept();
@@ -2157,7 +2229,7 @@ bool rpc_server::comm_init(const rpc_msg_comm_init_req & request, rpc_msg_comm_i
}
}
if (state.peer == nullptr) {
GGML_LOG_ERROR("[%s] failed to connect to peer %s:%u\n", __func__, host.c_str(), request.port);
LOG_ERROR("[%s] failed to connect to peer %s:%u\n", __func__, host.c_str(), request.port);
return true;
}
state.peer->get_caps(local_caps);
@@ -2170,7 +2242,7 @@ bool rpc_server::comm_init(const rpc_msg_comm_init_req & request, rpc_msg_comm_i
}
state.rank = request.rank;
state.world = request.world;
GGML_LOG_INFO("[%s] device %u joined pairwise comm as rank %u\n", __func__, request.device, request.rank);
LOG_INFO("[%s] device %u joined pairwise comm as rank %u\n", __func__, request.device, request.rank);
response.ok = 1;
return true;
}
@@ -2181,7 +2253,7 @@ bool rpc_server::comm_allreduce(const rpc_msg_comm_allreduce_req & request) {
}
comm_state & state = comm_states[request.device];
if (state.peer == nullptr) {
GGML_LOG_ERROR("[%s] no communicator for device %u\n", __func__, request.device);
LOG_ERROR("[%s] no communicator for device %u\n", __func__, request.device);
return false;
}
ggml_backend_t backend = backends[request.device];
@@ -2197,7 +2269,7 @@ bool rpc_server::comm_allreduce(const rpc_msg_comm_allreduce_req & request) {
ggml_context * ctx = ctx_ptr.get();
ggml_tensor * t_dst = deserialize_tensor(ctx, &request.tensor);
if (t_dst == nullptr || t_dst->buffer == nullptr) {
GGML_LOG_ERROR("[%s] error deserializing tensor\n", __func__);
LOG_ERROR("[%s] error deserializing tensor\n", __func__);
return false;
}
const size_t nbytes = ggml_nbytes(t_dst);
@@ -2314,7 +2386,7 @@ bool rpc_server::get_device_memory(const rpc_msg_get_device_memory_req & request
ggml_backend_dev_memory(dev, &free, &total);
response.free_mem = free;
response.total_mem = total;
LOG_DBG("[%s] device: %u, free_mem: %" PRIu64 ", total_mem: %" PRIu64 "\n", __func__, dev_id, response.free_mem, response.total_mem);
LOG_DBG2("[%s] device: %u, free: %" PRIu64 ", total: %" PRIu64 "\n", __func__, dev_id, response.free_mem, response.total_mem);
return true;
}
@@ -2332,7 +2404,7 @@ static void rpc_serve_client(const std::vector<ggml_backend_t> & backends, const
return;
}
if (cmd != RPC_CMD_HELLO) {
GGML_LOG_ERROR("Expected HELLO command, update client\n");
LOG_ERROR("Expected HELLO command, update client\n");
return;
}
@@ -2343,7 +2415,7 @@ static void rpc_serve_client(const std::vector<ggml_backend_t> & backends, const
}
if (hello_input_size != sizeof(rpc_msg_hello_req)) {
GGML_LOG_ERROR("HELLO request size mismatch (%zu vs %zu) — client needs upgrade to protocol v%d.x\n",
LOG_ERROR("HELLO request size mismatch (%zu vs %zu) — client needs upgrade to protocol v%d.x\n",
(size_t)hello_input_size, sizeof(rpc_msg_hello_req), RPC_PROTO_MAJOR_VERSION);
return;
}
@@ -2363,15 +2435,17 @@ static void rpc_serve_client(const std::vector<ggml_backend_t> & backends, const
// Activate transport upgrade using client's caps
sock->update_caps(req.conn_caps);
LOG_DBG("[%s] client connected, transport: %s\n", __func__, sock->transport_name());
while (true) {
if (!sock->recv_data(&cmd, 1)) {
break;
}
if (cmd >= RPC_CMD_COUNT) {
// fail fast if the command is invalid
GGML_LOG_ERROR("Unknown command: %d\n", cmd);
LOG_ERROR("Unknown command: %d\n", cmd);
break;
}
LOG_DBG2("[%s] %s\n", __func__, rpc_cmd_name((enum rpc_cmd)cmd));
switch (cmd) {
case RPC_CMD_HELLO: {
// HELLO command is handled above
@@ -2643,7 +2717,7 @@ static void rpc_serve_client(const std::vector<ggml_backend_t> & backends, const
break;
}
default: {
GGML_LOG_ERROR("Unknown command: %d\n", cmd);
LOG_ERROR("Unhandled command: %s\n", rpc_cmd_name((enum rpc_cmd)cmd));
return;
}
}
@@ -2653,26 +2727,26 @@ static void rpc_serve_client(const std::vector<ggml_backend_t> & backends, const
void ggml_backend_rpc_start_server(const char * endpoint, const char * cache_dir,
size_t n_threads, size_t n_devices, ggml_backend_dev_t * devices) {
if (n_devices == 0 || devices == nullptr) {
fprintf(stderr, "Invalid arguments to ggml_backend_rpc_start_server\n");
LOG_ERROR("Invalid arguments to ggml_backend_rpc_start_server\n");
return;
}
std::vector<ggml_backend_t> backends;
printf("Starting RPC server v%d.%d.%d\n",
LOG_INFO("Starting RPC server v%d.%d.%d\n",
RPC_PROTO_MAJOR_VERSION,
RPC_PROTO_MINOR_VERSION,
RPC_PROTO_PATCH_VERSION);
printf(" endpoint : %s\n", endpoint);
printf(" local cache : %s\n", cache_dir ? cache_dir : "n/a");
printf("Devices:\n");
LOG_INFO(" endpoint : %s\n", endpoint);
LOG_INFO(" local cache : %s\n", cache_dir ? cache_dir : "n/a");
LOG_INFO("Devices:\n");
for (size_t i = 0; i < n_devices; i++) {
auto dev = devices[i];
size_t free, total;
ggml_backend_dev_memory(dev, &free, &total);
printf(" %s: %s (%zu MiB, %zu MiB free)\n", ggml_backend_dev_name(dev), ggml_backend_dev_description(dev),
total / 1024 / 1024, free / 1024 / 1024);
LOG_INFO(" %s: %s (%zu MiB, %zu MiB free)\n", ggml_backend_dev_name(dev), ggml_backend_dev_description(dev),
total / 1024 / 1024, free / 1024 / 1024);
auto backend = ggml_backend_dev_init(dev, nullptr);
if (!backend) {
fprintf(stderr, "Failed to create backend for device %s\n", dev->iface.get_name(dev));
LOG_ERROR("Failed to create backend for device %s\n", dev->iface.get_name(dev));
return;
}
backends.push_back(backend);
@@ -2692,30 +2766,28 @@ void ggml_backend_rpc_start_server(const char * endpoint, const char * cache_dir
}
#ifdef GGML_RPC_RDMA
printf(" transport : TCP (RDMA auto-negotiate enabled)\n");
LOG_INFO(" transport : TCP (RDMA auto-negotiate enabled)\n");
#else
printf(" transport : TCP\n");
LOG_INFO(" transport : TCP\n");
#endif // GGML_RPC_RDMA
if (!rpc_transport_init()) {
fprintf(stderr, "Failed to initialize RPC transport\n");
LOG_ERROR("Failed to initialize RPC transport\n");
return;
}
auto server_socket = socket_t::create_server(host.c_str(), port);
if (server_socket == nullptr) {
fprintf(stderr, "Failed to create server socket\n");
LOG_ERROR("Failed to create server socket\n");
return;
}
while (true) {
auto client_socket = server_socket->accept();
if (client_socket == nullptr) {
fprintf(stderr, "Failed to accept client connection\n");
LOG_ERROR("Failed to accept client connection\n");
return;
}
printf("Accepted client connection\n");
fflush(stdout);
LOG_INFO("Accepted client connection\n");
rpc_serve_client(backends, cache_dir, client_socket);
printf("Client connection closed\n");
fflush(stdout);
LOG_INFO("Client connection closed\n");
}
rpc_transport_shutdown();
for (auto backend : backends) {
@@ -2898,7 +2970,7 @@ static void * ggml_backend_rpc_comm_init(ggml_backend_t * backends, size_t n_bac
// COMM_INIT on the same connection would deadlock behind the first
for (const auto & rank : ranks) {
if (rank.endpoint == rpc_ctx->endpoint) {
GGML_LOG_WARN("%s: multiple ranks on endpoint %s are not supported\n", __func__, rpc_ctx->endpoint.c_str());
LOG_WARN("%s: multiple ranks on endpoint %s are not supported\n", __func__, rpc_ctx->endpoint.c_str());
return nullptr;
}
}
@@ -2942,7 +3014,7 @@ static void * ggml_backend_rpc_comm_init(ggml_backend_t * backends, size_t n_bac
bool ok = true;
for (size_t i = 0; i < n_backends; i++) {
if (!responses[i].ok) {
GGML_LOG_WARN("%s: rank %zu (%s) failed to initialize\n", __func__, i, ranks[i].endpoint.c_str());
LOG_WARN("%s: rank %zu (%s) failed to initialize\n", __func__, i, ranks[i].endpoint.c_str());
ok = false;
}
}
@@ -2952,8 +3024,8 @@ static void * ggml_backend_rpc_comm_init(ggml_backend_t * backends, size_t n_bac
}
return nullptr;
}
GGML_LOG_INFO("%s: pairwise communicator initialized (%s <-> %s)\n", __func__,
ranks[0].endpoint.c_str(), ranks[1].endpoint.c_str());
LOG_INFO("%s: pairwise communicator initialized (%s <-> %s)\n", __func__,
ranks[0].endpoint.c_str(), ranks[1].endpoint.c_str());
return new ggml_backend_rpc_comm_context{std::move(ranks)};
}
@@ -3046,9 +3118,11 @@ ggml_backend_reg_t ggml_backend_rpc_add_server(const char * endpoint) {
static uint32_t dev_id = 0;
std::lock_guard<std::mutex> lock(mutex);
if (reg_map.find(endpoint) != reg_map.end()) {
LOG_DBG3("[%s] endpoint %s already registered\n", __func__, endpoint);
return reg_map[endpoint];
}
uint32_t dev_count = ggml_backend_rpc_get_device_count(endpoint);
LOG_DBG2("[%s] endpoint: %s, device count: %u\n", __func__, endpoint, dev_count);
if (dev_count == 0) {
return nullptr;
}
+48
View File
@@ -0,0 +1,48 @@
#pragma once
// Log infrastructure for the RPC backend: all RPC logging goes through this header.
//
// LOG_ERROR / LOG_WARN / LOG_INFO are unconditional severity logs.
//
// LOG_DBG* are debug logs gated by the GGML_RPC_DEBUG verbosity variable:
// unset / 0 - disabled (only warnings and errors are printed)
// 1 - high-level events: connections, handshake, buffer allocations, graph computes, cache evictions
// 2 - per-command trace: every RPC message sent/received, tensor transfers, cache hits/misses
// 3 - fine detail: per-command timings, graph node dumps, transport byte counts
// non-numeric values fall back to 1
#include "ggml-impl.h"
#include <cstdlib>
static inline int rpc_debug_level() {
static const int level = [] {
const char * env = std::getenv("GGML_RPC_DEBUG");
if (env == nullptr) {
return 0;
}
if (env[0] == '\0') {
return 1;
}
int value = 0;
for (const char * p = env; *p != '\0'; p++) {
if (*p < '0' || *p > '9') {
return 1;
}
value = value * 10 + (*p - '0');
if (value > 3) {
value = 3;
}
}
return value;
}();
return level;
}
#define LOG_INFO(...) GGML_LOG_INFO(__VA_ARGS__)
#define LOG_WARN(...) GGML_LOG_WARN(__VA_ARGS__)
#define LOG_ERROR(...) GGML_LOG_ERROR(__VA_ARGS__)
#define LOG_DBG(...) do { if (rpc_debug_level() >= 1) GGML_LOG_DEBUG(__VA_ARGS__); } while (0)
#define LOG_DBG2(...) do { if (rpc_debug_level() >= 2) GGML_LOG_DEBUG(__VA_ARGS__); } while (0)
#define LOG_DBG3(...) do { if (rpc_debug_level() >= 3) GGML_LOG_DEBUG(__VA_ARGS__); } while (0)
+15 -11
View File
@@ -1,6 +1,6 @@
#include "transport-apple.h"
#include "transport.h"
#include "ggml-impl.h"
#include "log.h"
#include <infiniband/verbs.h>
@@ -199,6 +199,7 @@ static bool rdma_library_present() {
// address, i.e. the one cabled to the peer.
std::unique_ptr<apple_rdma> apple_rdma::probe(int fd, const uint8_t * target_gid, uint8_t * caps) {
if (!rdma_library_present()) {
LOG_DBG2("[RDMA(Apple)] librdma.dylib not present, continuing with TCP\n");
return nullptr;
}
int ndev = 0;
@@ -224,7 +225,10 @@ std::unique_ptr<apple_rdma> apple_rdma::probe(int fd, const uint8_t * target_gid
break;
}
ibv_free_device_list(devs);
if (!ctx) return nullptr;
if (!ctx) {
LOG_DBG2("[RDMA(Apple)] no RDMA device matched the connection address, continuing with TCP\n");
return nullptr;
}
std::unique_ptr<impl> c(new impl());
c->fd = fd;
@@ -286,7 +290,7 @@ std::unique_ptr<apple_rdma> apple_rdma::probe(int fd, const uint8_t * target_gid
memcpy(rc.gid, gid.raw, RDMA_GID_SIZE);
memcpy(caps, &rc, sizeof(rc));
GGML_LOG_INFO("RDMA(Apple/UC) probed: dev=%s port=%u gid=%d qpn=%u lid=%u mtu=%d ring=%d x %zu KiB\n",
LOG_INFO("RDMA(Apple/UC) probed: dev=%s port=%u gid=%d qpn=%u lid=%u mtu=%d ring=%d x %zu KiB\n",
matched.c_str(), port, gid_idx, c->qpn, (unsigned)pa.lid, 128 << c->path_mtu,
RDMA_NBUF, RDMA_STRIDE / 1024);
return std::unique_ptr<apple_rdma>(new apple_rdma(std::move(c)));
@@ -317,7 +321,7 @@ bool apple_rdma::activate(const uint8_t * caps) {
memcpy(&a.ah_attr.grh.dgid, rc.gid, RDMA_GID_SIZE);
if (ibv_modify_qp(c->qp, &a,
IBV_QP_STATE | IBV_QP_AV | IBV_QP_PATH_MTU | IBV_QP_DEST_QPN | IBV_QP_RQ_PSN) != 0) {
GGML_LOG_ERROR("RDMA(Apple/UC) RTR failed: %s\n", strerror(errno));
LOG_ERROR("RDMA(Apple/UC) RTR failed: %s\n", strerror(errno));
ok = false;
}
}
@@ -326,7 +330,7 @@ bool apple_rdma::activate(const uint8_t * caps) {
a.qp_state = IBV_QPS_RTS;
a.sq_psn = RDMA_PSN;
if (ibv_modify_qp(c->qp, &a, IBV_QP_STATE | IBV_QP_SQ_PSN) != 0) {
GGML_LOG_ERROR("RDMA(Apple/UC) RTS failed: %s\n", strerror(errno));
LOG_ERROR("RDMA(Apple/UC) RTS failed: %s\n", strerror(errno));
ok = false;
}
}
@@ -334,7 +338,7 @@ bool apple_rdma::activate(const uint8_t * caps) {
// Recvs are posted only now: the controller starts processing them at RTR.
for (int i = 0; ok && i < RDMA_NBUF; i++) {
if (!c->post_recv(i)) {
GGML_LOG_ERROR("RDMA(Apple/UC) post_recv %d/%d failed\n", i, RDMA_NBUF);
LOG_ERROR("RDMA(Apple/UC) post_recv %d/%d failed\n", i, RDMA_NBUF);
ok = false;
}
}
@@ -350,7 +354,7 @@ bool apple_rdma::activate(const uint8_t * caps) {
return false;
}
GGML_LOG_INFO("RDMA(Apple/UC) activated: qpn=%u->%u mtu=%d rx_depth=%d\n",
LOG_INFO("RDMA(Apple/UC) activated: qpn=%u->%u mtu=%d rx_depth=%d\n",
c->qpn, rc.qpn, 128 << c->path_mtu, RDMA_NBUF);
return true;
}
@@ -360,20 +364,20 @@ bool apple_rdma::activate(const uint8_t * caps) {
int apple_rdma::impl::progress() {
struct ibv_wc wc[RDMA_NBUF * 2];
int n = ibv_poll_cq(cq, RDMA_NBUF * 2, wc);
if (n < 0) { GGML_LOG_ERROR("RDMA(Apple/UC) poll_cq failed\n"); broken = true; return -1; }
if (n < 0) { LOG_ERROR("RDMA(Apple/UC) poll_cq failed\n"); broken = true; return -1; }
for (int j = 0; j < n; j++) {
uint64_t id = wc[j].wr_id;
bool is_recv = (id & RDMA_RECV_WR) != 0;
if (wc[j].status != IBV_WC_SUCCESS) {
GGML_LOG_ERROR("RDMA(Apple/UC) %s wc error: status=%d\n", is_recv ? "recv" : "send", wc[j].status);
LOG_ERROR("RDMA(Apple/UC) %s wc error: status=%d\n", is_recv ? "recv" : "send", wc[j].status);
broken = true;
return -1;
}
if (is_recv) {
int b = (int)(id & RDMA_WR_IDX_MASK);
const rdma_seg_hdr * h = (const rdma_seg_hdr *)(recv_mem + (size_t)b * RDMA_STRIDE);
if (h->magic != RDMA_SEG_MAGIC) { GGML_LOG_ERROR("RDMA(Apple/UC) bad frame magic\n"); broken = true; return -1; }
if (h->len > RDMA_PAYLOAD) { GGML_LOG_ERROR("RDMA(Apple/UC) frame len %u exceeds payload\n", h->len); broken = true; return -1; }
if (h->magic != RDMA_SEG_MAGIC) { LOG_ERROR("RDMA(Apple/UC) bad frame magic\n"); broken = true; return -1; }
if (h->len > RDMA_PAYLOAD) { LOG_ERROR("RDMA(Apple/UC) frame len %u exceeds payload\n", h->len); broken = true; return -1; }
int slot = (inq_head + inq_count) % RDMA_NBUF;
inq[slot].buf = b;
inq[slot].off = 0;
+29 -18
View File
@@ -1,5 +1,5 @@
#include "transport.h"
#include "ggml-impl.h"
#include "log.h"
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
@@ -43,11 +43,6 @@ using ssize_t = __int64;
typedef int sockfd_t;
#endif
static const char * RPC_DEBUG = std::getenv("GGML_RPC_DEBUG");
#define LOG_DBG(...) \
do { if (RPC_DEBUG) GGML_LOG_DEBUG(__VA_ARGS__); } while (0)
#ifdef GGML_RPC_RDMA
static constexpr size_t RDMA_GID_SIZE = 16; // RoCE GID / IB GID is always 16 bytes
using rdma_gid_t = std::array<uint8_t, RDMA_GID_SIZE>;
@@ -342,7 +337,7 @@ bool socket_t::impl::rdma_probe() {
} else if (gid_version == IBV_GID_TYPE_ROCE_V1) {
ver_str = " RoCEv1";
}
GGML_LOG_INFO("RDMA probed: dev=%s gid=%d%s qpn=%u inline=%u\n",
LOG_INFO("RDMA probed: dev=%s gid=%d%s qpn=%u inline=%u\n",
matched_dev, gid_idx, ver_str, rdma_local.qpn, rdma->max_inline);
return true;
}
@@ -407,7 +402,7 @@ bool socket_t::impl::rdma_activate(uint32_t remote_qpn, uint32_t remote_psn, con
rdma->last_active = std::chrono::steady_clock::now();
GGML_LOG_INFO("RDMA activated: qpn=%u->%u mtu=%d rx_depth=%d\n",
LOG_INFO("RDMA activated: qpn=%u->%u mtu=%d rx_depth=%d\n",
rdma_local.qpn, remote_qpn, 128 << rdma_local.path_mtu, RDMA_RX_DEPTH);
return true;
}
@@ -445,7 +440,7 @@ bool socket_t::impl::rdma_poll(struct ibv_cq * cq, struct ibv_wc * wc) {
if (n > 0) {
c->last_active = std::chrono::steady_clock::now();
if (wc->status != IBV_WC_SUCCESS) {
GGML_LOG_ERROR("RDMA CQ wc error: status=%d (%s) vendor_err=0x%x\n",
LOG_ERROR("RDMA CQ wc error: status=%d (%s) vendor_err=0x%x\n",
wc->status, ibv_wc_status_str(wc->status), wc->vendor_err);
}
return wc->status == IBV_WC_SUCCESS;
@@ -537,6 +532,7 @@ bool socket_t::impl::rdma_recv(void * data, size_t size) {
#endif // GGML_RPC_RDMA
bool socket_t::impl::send_data(const void * data, size_t size) {
LOG_DBG3("[%s] transport: %s, size: %zu\n", __func__, use_rdma ? "RDMA" : "TCP", size);
#ifdef GGML_RPC_RDMA_APPLE
if (use_rdma) {
return rdma->send(data, size);
@@ -551,7 +547,7 @@ bool socket_t::impl::send_data(const void * data, size_t size) {
size_t size_to_send = std::min(size - bytes_sent, MAX_CHUNK_SIZE);
ssize_t n = send(fd, (const char *)data + bytes_sent, size_to_send, 0);
if (n < 0) {
GGML_LOG_ERROR("send failed (bytes_sent=%zu, size_to_send=%zu)\n",
LOG_ERROR("send failed (bytes_sent=%zu, size_to_send=%zu)\n",
bytes_sent, size_to_send);
return false;
}
@@ -561,6 +557,7 @@ bool socket_t::impl::send_data(const void * data, size_t size) {
}
bool socket_t::impl::recv_data(void * data, size_t size) {
LOG_DBG3("[%s] transport: %s, size: %zu\n", __func__, use_rdma ? "RDMA" : "TCP", size);
#ifdef GGML_RPC_RDMA_APPLE
if (use_rdma) {
return rdma->recv(data, size);
@@ -575,12 +572,12 @@ bool socket_t::impl::recv_data(void * data, size_t size) {
size_t size_to_recv = std::min(size - bytes_recv, MAX_CHUNK_SIZE);
ssize_t n = recv(fd, (char *)data + bytes_recv, size_to_recv, 0);
if (n < 0) {
GGML_LOG_ERROR("recv failed (bytes_recv=%zu, size_to_recv=%zu)\n",
LOG_ERROR("recv failed (bytes_recv=%zu, size_to_recv=%zu)\n",
bytes_recv, size_to_recv);
return false;
}
if (n == 0) {
LOG_DBG("recv returned 0 (peer closed?)\n");
LOG_WARN("recv returned 0 (peer closed?)\n");
return false;
}
bytes_recv += (size_t)n;
@@ -599,6 +596,9 @@ void socket_t::impl::get_caps(uint8_t * local_caps) {
if (target_gid) {
rdma = apple_rdma::probe(fd, target_gid->data(), local_caps);
}
if (!rdma) {
LOG_DBG2("[%s] RDMA probe failed, continuing with TCP\n", __func__);
}
# else
rdma_local = {};
if (rdma_probe()) {
@@ -609,6 +609,7 @@ void socket_t::impl::get_caps(uint8_t * local_caps) {
memcpy(local_caps, &rc, sizeof(rc));
} else {
rdma.reset();
LOG_DBG2("[%s] RDMA probe failed, continuing with TCP\n", __func__);
}
# endif
#endif // GGML_RPC_RDMA
@@ -623,6 +624,9 @@ void socket_t::impl::update_caps(const uint8_t * remote_caps) {
remote_rdma |= remote_caps[i] != 0;
}
if (!rdma || !remote_rdma) {
if (rdma && !remote_rdma) {
LOG_WARN("[%s] peer does not support RDMA, continuing with TCP\n", __func__);
}
rdma.reset();
return;
}
@@ -636,7 +640,7 @@ void socket_t::impl::update_caps(const uint8_t * remote_caps) {
if (activated) {
use_rdma = true;
} else {
GGML_LOG_ERROR("RDMA activate failed, staying on TCP\n");
LOG_WARN("RDMA activate failed, staying on TCP\n");
rdma.reset();
}
#else
@@ -679,6 +683,10 @@ void socket_t::update_caps(const uint8_t * remote_caps) {
return pimpl->update_caps(remote_caps);
}
const char * socket_t::transport_name() const {
return pimpl->use_rdma ? "RDMA" : "TCP";
}
static bool is_valid_fd(sockfd_t sockfd) {
#ifdef _WIN32
return sockfd != INVALID_SOCKET;
@@ -706,9 +714,10 @@ socket_ptr socket_t::accept() {
return nullptr;
}
if (!set_no_delay(client_socket_fd)) {
GGML_LOG_ERROR("Failed to set TCP_NODELAY\n");
LOG_ERROR("Failed to set TCP_NODELAY\n");
return nullptr;
}
LOG_DBG("[%s] accepted client connection\n", __func__);
return socket_ptr(new socket_t(std::make_unique<impl>(client_socket_fd)));
}
@@ -718,11 +727,11 @@ socket_ptr socket_t::create_server(const char * host, int port) {
return nullptr;
}
if (!set_reuse_addr(sockfd)) {
GGML_LOG_ERROR("Failed to set SO_REUSEADDR\n");
LOG_ERROR("Failed to set SO_REUSEADDR\n");
return nullptr;
}
if (inet_addr(host) == INADDR_NONE) {
GGML_LOG_ERROR("Invalid host address: %s\n", host);
LOG_ERROR("Invalid host address: %s\n", host);
return nullptr;
}
struct sockaddr_in serv_addr;
@@ -736,6 +745,7 @@ socket_ptr socket_t::create_server(const char * host, int port) {
if (listen(sockfd, 1) < 0) {
return nullptr;
}
LOG_DBG("[%s] listening on %s:%d\n", __func__, host, port);
return socket_ptr(new socket_t(std::make_unique<impl>(sockfd)));
}
@@ -745,7 +755,7 @@ socket_ptr socket_t::connect(const char * host, int port) {
return nullptr;
}
if (!set_no_delay(sockfd)) {
GGML_LOG_ERROR("Failed to set TCP_NODELAY\n");
LOG_ERROR("Failed to set TCP_NODELAY\n");
return nullptr;
}
struct sockaddr_in addr;
@@ -753,13 +763,14 @@ socket_ptr socket_t::connect(const char * host, int port) {
addr.sin_port = htons(port);
struct hostent * server = gethostbyname(host);
if (server == NULL) {
GGML_LOG_ERROR("Cannot resolve host '%s'\n", host);
LOG_ERROR("Cannot resolve host '%s'\n", host);
return nullptr;
}
memcpy(&addr.sin_addr.s_addr, server->h_addr, server->h_length);
if (::connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
return nullptr;
}
LOG_DBG("[%s] connected to %s:%d\n", __func__, host, port);
return socket_ptr(new socket_t(std::make_unique<impl>(sockfd)));
}
+1
View File
@@ -24,6 +24,7 @@ struct socket_t {
void get_caps(uint8_t * local_caps);
void update_caps(const uint8_t * remote_caps);
const char * transport_name() const;
static socket_ptr create_server(const char * host, int port);
static socket_ptr connect(const char * host, int port);
+18
View File
@@ -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;
+20
View File
@@ -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(
+67 -2
View File
@@ -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;
+79 -1
View File
@@ -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;
}
+21
View File
@@ -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
+58 -1
View File
@@ -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);
}
@@ -63,7 +63,7 @@ struct vk_mat_mat_id_push_constants {
uint32_t batch_stride_a; uint32_t batch_stride_b; uint32_t batch_stride_d;
uint32_t nei0; uint32_t nei1; uint32_t nbi1; uint32_t ne11;
uint32_t n_experts;
uint32_t hoist_row_ids;
uint32_t row_ids_offset;
};
struct vk_mat_vec_id_push_constants {
@@ -218,9 +218,10 @@ struct vk_op_count_experts_push_constants {
uint32_t nb01;
uint32_t a_offset;
uint32_t n_experts;
uint32_t hoist_row_ids;
uint32_t ne00mp;
uint32_t ne00L;
uint32_t row_tile_size;
uint32_t row_ids_offset;
};
struct vk_op_glu_push_constants {
+48 -49
View File
@@ -1473,9 +1473,8 @@ static bool ggml_vk_matmul_shmem_support(const vk_device& device, const std::vec
const uint32_t load_bufs = (warptile[1] + warptile[2]) * (warptile[3] + bank_conflict_offset) * type_size;
const uint32_t mmid_row_ids = mul_mat_id ? (warptile[2] * 2 * sizeof(uint16_t)) : 0;
const uint32_t coopmat_stage = device->coopmat_support ? warptile[7] * warptile[8] / warps * sizeof(float) : 0;
const uint32_t ballots_sh = mul_mat_id ? (warps * 4 * sizeof(uint32_t)) : 0;
const uint32_t total_size = load_bufs + mmid_row_ids + coopmat_stage + lut_size + ballots_sh;
const uint32_t total_size = load_bufs + mmid_row_ids + coopmat_stage + lut_size;
const bool supported = total_size <= device->properties.limits.maxComputeSharedMemorySize;
VK_LOG_DEBUG("ggml_vk_matmul_shmem_support(warptile=(" << warptile[0] << "," << warptile[1] << "," << warptile[2] << "), "
@@ -1540,10 +1539,7 @@ static bool ggml_vk_matmul_int_shmem_support(const vk_device& device, const std:
const uint32_t buf_b_size = BN * BK_STEP * block_b_size;
const uint32_t mmid_row_ids = mul_mat_id ? (BN * 2u * (uint32_t)sizeof(uint16_t)) : 0u;
const uint32_t warps = warptile[0] / warptile[10];
const uint32_t ballots_sh = mul_mat_id ? (warps * 4u * (uint32_t)sizeof(uint32_t)) : 0u;
const uint32_t total_size = buf_a_size + buf_b_size + mmid_row_ids + ballots_sh + lut_size;
const uint32_t total_size = buf_a_size + buf_b_size + mmid_row_ids + lut_size;
const bool supported = total_size <= device->properties.limits.maxComputeSharedMemorySize;
VK_LOG_DEBUG("ggml_vk_matmul_int_shmem_support(warptile=(" << warptile[0] << "," << warptile[1] << "," << warptile[2] << "), "
@@ -1573,10 +1569,8 @@ static bool ggml_vk_matmul_cm1_int_shmem_support(const vk_device& device, const
return false;
}
const uint32_t BLOCK_SIZE = warptile[0];
const uint32_t BM = warptile[1];
const uint32_t BN = warptile[2];
const uint32_t WARP = warptile[10];
const uint32_t BK = 32;
const uint32_t BK_STEP = mul_mat_id ? 2u : 4u;
@@ -1600,8 +1594,6 @@ static bool ggml_vk_matmul_cm1_int_shmem_support(const vk_device& device, const
}
if (mul_mat_id) {
total += BN * 2u * (uint32_t)sizeof(uint16_t); // row_ids[BN] (u16vec2)
const uint32_t num_warps = BLOCK_SIZE / std::max(WARP, 1u);
total += num_warps * 4u * (uint32_t)sizeof(uint32_t); // ballots_sh[NUM_WARPS] (uvec4)
}
const bool supported = total <= device->properties.limits.maxComputeSharedMemorySize;
@@ -2892,8 +2884,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;
@@ -3589,7 +3587,7 @@ void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
ggml_vk_create_pipeline(device, device->pipeline_count_equal_i32, "count_equal_i32", count_equal_i32_len, count_equal_i32_data, "main", 3, sizeof(vk_op_push_constants), {512, 1, 1}, { device->subgroup_size }, 1);
if (device->subgroup_arithmetic && device->subgroup_require_full_support) {
if (device->subgroup_arithmetic && device->subgroup_vote && device->subgroup_require_full_support) {
ggml_vk_create_pipeline(device, device->pipeline_count_experts, "count_experts", count_experts_subgroup_len, count_experts_subgroup_data, "main", 2, sizeof(vk_op_count_experts_push_constants), {1, 1, 1}, {}, 1, true, true);
} else {
ggml_vk_create_pipeline(device, device->pipeline_count_experts, "count_experts", count_experts_len, count_experts_data, "main", 2, sizeof(vk_op_count_experts_push_constants), {1, 1, 1}, {}, 1, true);
@@ -6008,20 +6006,27 @@ static uint32_t ggml_vk_guess_matmul_pipeline_align_map(ggml_backend_vk_context
return configs[idx].align;
}
static uint64_t ggml_vk_mul_mat_id_max_tiles(uint64_t n_experts, uint64_t n_rows, uint32_t bn) {
// Each nonempty expert can add at most one partial tile to the combined grid.
return std::min(n_rows, CEIL_DIV(n_rows, bn) + std::min(n_experts, n_rows) - 1);
}
static void ggml_vk_matmul_id(
ggml_backend_vk_context * ctx, vk_context& subctx, vk_pipeline& pipeline,
vk_subbuffer&& a, vk_subbuffer&& b, vk_subbuffer&& d, vk_subbuffer&& ids, const vk_subbuffer & expert_count_buf,
uint32_t m, uint32_t n, uint32_t k, uint32_t stride_a, uint32_t stride_b, uint32_t stride_d,
uint32_t batch_stride_a, uint32_t batch_stride_b, uint32_t batch_stride_d,
uint32_t n_as, uint32_t nei0, uint32_t nei1, uint32_t nbi1, uint32_t ne11,
bool hoist_row_ids) {
uint32_t row_ids_offset, uint32_t max_tiles) {
VK_LOG_DEBUG("ggml_vk_matmul_id(a: (" << a.buffer->buffer << ", " << a.offset << ", " << a.size << "), b: (" << b.buffer->buffer << ", " << b.offset << ", " << b.size << "), d: (" << d.buffer->buffer << ", " << d.offset << ", " << d.size << "), ids: (" << ids.buffer->buffer << ", " << ids.offset << ", " << ids.size << "), expert_count: (" << expert_count_buf.buffer->buffer << ", " << expert_count_buf.offset << ", " << expert_count_buf.size << "), " <<
"m: " << m << ", n: " << n << ", k: " << k << ", stride_a: " << stride_a << ", stride_b: " << stride_b << ", stride_d: " << stride_d << ", " <<
"batch_stride_a: " << batch_stride_a << ", batch_stride_b: " << batch_stride_b << ", batch_stride_d: " << batch_stride_d << ", " <<
"n_as: " << n_as << ", nei0: " << nei0 << ", nei1: " << nei1 << ", nbi1: " << nbi1 << ", ne11: " << ne11 << ")");
const vk_mat_mat_id_push_constants pc = { m, n, k, stride_a, stride_b, stride_d, batch_stride_a, batch_stride_b, batch_stride_d,
nei0, nei1, nbi1, ne11, n_as, uint32_t(hoist_row_ids) };
ggml_vk_dispatch_pipeline(ctx, subctx, pipeline, { a, b, d, ids, expert_count_buf }, pc, { m, nei1, n_as });
nei0, nei1, nbi1, ne11, n_as, row_ids_offset };
const uint32_t tiles_y = std::min(max_tiles, ctx->device->properties.limits.maxComputeWorkGroupCount[1]);
const std::array<uint32_t, 3> elements = { m, tiles_y * pipeline->wg_denoms[1], uint32_t(CEIL_DIV(max_tiles, tiles_y)) };
ggml_vk_dispatch_pipeline(ctx, subctx, pipeline, { a, b, d, ids, expert_count_buf }, pc, elements);
}
bool ggml_vk_dim01_contiguous(const ggml_tensor * tensor) {
@@ -7330,7 +7335,6 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context&
const uint32_t nbi0 = ids->nb[0];
const uint32_t nbi1 = ids->nb[1];
const uint32_t nbi2 = ids->nb[2];
const uint64_t ne20 = dst->ne[0];
const uint64_t ne21 = dst->ne[1];
@@ -7338,38 +7342,24 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context&
// const uint64_t ne23 = dst->ne[3];
const uint64_t n_as = ne02;
// n_as counts, n_as offsets, one total, then one packed row id per (expert, token).
// Hoisting requires 16-bit indices for the packing and a table that fits one binding.
const uint64_t hoisted_row_id_words = 2 * n_as + 1 + nei0 * nei1;
// 1024 matches MAX_EXPERTS in count_experts.comp and LLAMA_MAX_EXPERTS. It costs
// 3 * 1024 * 4 = 12 KiB of shared memory, within the 16 KiB Vulkan guarantees.
const bool hoist_row_ids = n_as <= 1024 && nei0 <= 0xffff && nei1 <= 0xffff &&
hoisted_row_id_words * sizeof(uint32_t) <=
ctx->device->properties.limits.maxStorageBufferRange;
ggml_backend_vk_buffer_context * dst_buf_ctx = (ggml_backend_vk_buffer_context *)dst->buffer->context;
ggml_backend_vk_buffer_context * src0_buf_ctx = (ggml_backend_vk_buffer_context *)src0->buffer->context;
ggml_backend_vk_buffer_context * src1_buf_ctx = (ggml_backend_vk_buffer_context *)src1->buffer->context;
ggml_backend_vk_buffer_context * ids_buf_ctx = (ggml_backend_vk_buffer_context *)ids->buffer->context;
vk_buffer d_Qx = nullptr;
size_t qx_buf_offset = 0;
vk_buffer d_Qy = nullptr;
size_t qy_buf_offset = 0;
vk_buffer d_ids = nullptr;
size_t ids_buf_offset = 0;
bool src0_uma = false;
bool src1_uma = false;
bool ids_uma = false;
if (ctx->device->uma) {
ggml_vk_host_get(ctx->device, src0->data, d_Qx, qx_buf_offset);
ggml_vk_host_get(ctx->device, src1->data, d_Qy, qy_buf_offset);
ggml_vk_host_get(ctx->device, ids->data, d_ids, ids_buf_offset);
src0_uma = d_Qx != nullptr;
src1_uma = d_Qy != nullptr;
ids_uma = d_ids != nullptr;
}
// Reformat and convert to fp16 if non-contiguous, or for coopmat2 for better perf
@@ -7439,6 +7429,11 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context&
vk_pipeline pipeline = ggml_vk_guess_matmul_pipeline_map(ctx, *mmp_map, ne01, nei1, aligned, true);
const uint32_t bn = pipeline->wg_denoms[1];
const uint64_t max_tiles = ggml_vk_mul_mat_id_max_tiles(n_as, nei0 * nei1, bn);
const uint64_t row_ids_offset = 1 + 2 * max_tiles;
const uint64_t row_map_words = row_ids_offset + max_tiles * bn;
if (ggml_nbytes(src0) > ctx->device->properties.limits.maxStorageBufferRange) {
pipeline = ggml_vk_get_64b_indexing_pipeline(ctx, pipeline);
}
@@ -7450,7 +7445,6 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context&
const uint64_t qy_sz = ggml_type_size(src1->type) * ggml_nelements(src1) / ggml_blck_size(src1->type);
const uint64_t x_sz = !qx_needs_dequant ? qx_sz : sizeof(ggml_fp16_t) * x_ne;
const uint64_t y_sz = quantize_y ? (ggml_vk_align_size(y_ne, 128) * ggml_type_size(GGML_TYPE_Q8_1) / ggml_blck_size(GGML_TYPE_Q8_1)) : (y_f32_kernel ? sizeof(float) * y_ne : sizeof(ggml_fp16_t) * y_ne);
const uint64_t ids_sz = nbi2;
const uint64_t d_sz = sizeof(float) * d_ne;
vk_pipeline to_fp16_vk_0 = nullptr;
@@ -7492,8 +7486,7 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context&
}
vk_pipeline count_experts = ctx->device->pipeline_count_experts;
const size_t expert_data_size = sizeof(uint32_t) *
(hoist_row_ids ? hoisted_row_id_words : n_as);
const size_t expert_data_size = sizeof(uint32_t) * row_map_words;
{
if (
@@ -7545,11 +7538,6 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context&
qy_buf_offset = vk_tensor_offset(src1) + src1->view_offs;
GGML_ASSERT(d_Qy != nullptr);
}
if (!ids_uma) {
d_ids = ids_buf_ctx->dev_buffer;
ids_buf_offset = vk_tensor_offset(ids) + ids->view_offs;
GGML_ASSERT(d_ids != nullptr);
}
if (qx_needs_dequant) {
d_X = ctx->prealloc_x;
GGML_ASSERT(d_X->size >= x_sz);
@@ -7575,6 +7563,8 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context&
ggml_vk_sync_buffers(ctx, subctx);
}
}
vk_subbuffer d_ids = ggml_vk_tensor_subbuffer(ctx, ids, true);
// Count how many times each expert is used
vk_subbuffer expert_count_buf = { ctx->prealloc_split_k, 0, expert_data_size };
if (ctx->prealloc_split_k_need_sync) {
@@ -7587,12 +7577,11 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context&
(uint32_t)(nbi1 / ggml_type_size(ids->type)),
(uint32_t)(get_misalign_bytes(ctx, ids) / ggml_type_size(ids->type)),
(uint32_t)n_as,
uint32_t(hoist_row_ids),
0, 0 };
0, 0, bn, (uint32_t)row_ids_offset };
init_pushconst_fastdiv(pc);
ggml_vk_dispatch_pipeline(ctx, subctx, count_experts,
{ vk_subbuffer{ d_ids, ids_buf_offset, ids_sz }, expert_count_buf }, pc,
{ hoist_row_ids ? 1u : (uint32_t)n_as, 1, 1});
{ d_ids, expert_count_buf }, pc,
{ 1, 1, 1 });
}
if (x_non_contig) {
@@ -7665,10 +7654,10 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context&
ggml_vk_matmul_id(
ctx, subctx, pipeline,
{ d_X, x_buf_offset, x_range }, { d_Y, y_buf_offset, y_range },
{ d_D, d_buf_offset, d_sz }, { d_ids, ids_buf_offset, ids_sz }, expert_count_buf,
{ d_D, d_buf_offset, d_sz }, std::move(d_ids), expert_count_buf,
ne01, ne21, ne10, ne10, stride_b_y, ne01,
stride_batch_x, stride_batch_y, ne20*ne21,
n_as, nei0, nei1, nbi1 / ggml_type_size(ids->type), ne11, hoist_row_ids
n_as, nei0, nei1, nbi1 / ggml_type_size(ids->type), ne11, row_ids_offset, max_tiles
); // NOLINT
if (x_non_contig || qx_needs_dequant) {
@@ -7917,11 +7906,14 @@ static void ggml_vk_mul_mat_vec_id_q_f16(ggml_backend_vk_context * ctx, vk_conte
}
}
static bool ggml_vk_use_mul_mat_vec_id(const ggml_tensor * dst) {
const ggml_tensor * src0 = dst->src[0];
const ggml_tensor * ids = dst->src[2];
return ids->ne[1] <= 8 && (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16 || ggml_is_quantized(src0->type));
}
bool ggml_vk_use_mul_mat_vec_id(const struct ggml_cgraph * cgraph, int node_idx) {
ggml_tensor * dst = cgraph->nodes[node_idx];
ggml_tensor * src0 = dst->src[0];
ggml_tensor * src2 = dst->src[2];
return (src2->ne[1] <= 8) && (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16 || ggml_is_quantized(src0->type));
return ggml_vk_use_mul_mat_vec_id(cgraph->nodes[node_idx]);
}
void ggml_vk_mul_mat_id(ggml_backend_vk_context * ctx, vk_context& subctx, const struct ggml_cgraph * cgraph, int node_idx) {
@@ -15425,6 +15417,13 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
{
ggml_type src0_type = op->src[0]->type;
if (op->op == GGML_OP_MUL_MAT_ID) {
if (!ggml_vk_use_mul_mat_vec_id(op)) {
const ggml_tensor * ids = op->src[2];
// Shaders store row IDs as uint16_t slot and token indices.
if (ids->ne[0] > int64_t(UINT16_MAX) + 1 || ids->ne[1] > int64_t(UINT16_MAX) + 1) {
return false;
}
}
if (!device->mul_mat_id_s[src0_type] && !device->mul_mat_id_m[src0_type] && !device->mul_mat_id_l[src0_type]) {
// If there's not enough shared memory for row_ids and the result tile, fallback to CPU
return false;
@@ -5,6 +5,7 @@
#ifdef USE_SUBGROUPS
#extension GL_KHR_shader_subgroup_basic : enable
#extension GL_KHR_shader_subgroup_arithmetic : enable
#extension GL_KHR_shader_subgroup_vote : enable
#endif
#include "types.glsl"
@@ -18,40 +19,47 @@ layout (push_constant) uniform parameter
uint32_t nb01;
uint32_t a_offset;
uint32_t n_experts;
uint32_t hoist_row_ids;
uint32_t ne00mp;
uint32_t ne00L;
uint32_t row_tile_size;
uint32_t row_ids_offset;
} p;
#define BLOCK_SIZE 256
#define EXPERTS_PER_CHUNK 1024
layout(local_size_x = BLOCK_SIZE, local_size_y = 1, local_size_z = 1) in;
layout (binding = 0) readonly buffer A {uint data_a[];};
layout (binding = 1) writeonly buffer D {uint data_d[];};
// Upper bound on n_experts for the hoisted row-id path. Must match the limit in
// ggml_vk_mul_mat_id_q_f16 (hoist_row_ids). The non-hoisted reduction below only
// needs BLOCK_SIZE entries.
#define MAX_EXPERTS 1024
shared uint vals[EXPERTS_PER_CHUNK];
shared uint offsets[EXPERTS_PER_CHUNK];
shared uint cursors[EXPERTS_PER_CHUNK];
shared uint vals[MAX_EXPERTS];
shared uint offsets[MAX_EXPERTS];
shared uint cursors[MAX_EXPERTS];
// data_d[0] is the tile count. Each tile has (expert, valid rows).
// Packed (token << 16) | slot row ids start at p.row_ids_offset, with each expert on a tile boundary.
void write_tiles(uint expert, uint count, uint tile_begin, uint n_tiles) {
for (uint t = 0; t < n_tiles; ++t) {
const uint descriptor = 1 + 2 * (tile_begin + t);
data_d[descriptor] = expert;
data_d[descriptor + 1] = min(p.row_tile_size, count - t * p.row_tile_size);
}
}
// data_d layout when p.hoist_row_ids is set:
// [0, n_experts) per-expert row count
// [n_experts, 2*n_experts) per-expert start offset into the row id region
// [2*n_experts] total row count
// [2*n_experts + 1, ) row ids grouped by expert, packed as (i01 << 16) | (i00 & 0xffff)
// Otherwise only data_d[expert_id] is written, holding that expert's row count.
void main() {
const uint expert_id = gl_WorkGroupID.x;
const uint num_elements = p.ne00 * p.ne01;
const uint tid = gl_LocalInvocationID.x;
const uint tile_shift = findLSB(p.row_tile_size);
uint total_tiles = 0;
#ifdef USE_SUBGROUPS
// Use the subgroup that contains invocation 0.
const bool prefix_subgroup = subgroupAny(tid == 0);
#endif
if (p.hoist_row_ids != 0) {
for (uint e = tid; e < p.n_experts; e += BLOCK_SIZE) {
for (uint expert_base = 0; expert_base < p.n_experts;) {
const uint n_experts = min(EXPERTS_PER_CHUNK, p.n_experts - expert_base);
for (uint e = tid; e < n_experts; e += BLOCK_SIZE) {
vals[e] = 0;
}
barrier();
@@ -59,46 +67,43 @@ void main() {
for (uint idx = tid; idx < num_elements; idx += BLOCK_SIZE) {
const uint i01 = fastdiv(idx, p.ne00mp, p.ne00L);
const uint i00 = idx - i01 * p.ne00;
const uint expert = data_a[p.a_offset + i01 * p.nb01 + i00 * p.nb00];
if (expert < p.n_experts) {
const uint expert = data_a[p.a_offset + i01 * p.nb01 + i00 * p.nb00] - expert_base;
if (expert < n_experts) {
atomicAdd(vals[expert], 1);
}
}
barrier();
#ifdef USE_SUBGROUPS
if (gl_SubgroupID == 0) {
// pad the trip count so the subgroup ops stay in uniform control flow
const uint n_experts_padded = (p.n_experts + gl_SubgroupSize - 1) & ~(gl_SubgroupSize - 1);
uint base = 0;
for (uint expert = gl_SubgroupInvocationID; expert < n_experts_padded; expert += gl_SubgroupSize) {
const bool in_range = expert < p.n_experts;
const uint count = in_range ? vals[expert] : 0;
const uint offset = base + subgroupExclusiveAdd(count);
if (prefix_subgroup) {
const uint n_experts_padded = (n_experts + gl_SubgroupSize - 1) & ~(gl_SubgroupSize - 1);
uint base = subgroupAdd(tid == 0 ? total_tiles : 0u);
for (uint e = gl_SubgroupInvocationID; e < n_experts_padded; e += gl_SubgroupSize) {
const bool in_range = e < n_experts;
const uint count = in_range ? vals[e] : 0;
const uint n_tiles = (count + p.row_tile_size - 1) >> tile_shift;
const uint tile_begin = base + subgroupExclusiveAdd(n_tiles);
if (in_range) {
data_d[expert] = count;
data_d[p.n_experts + expert] = offset;
offsets[expert] = offset;
cursors[expert] = 0;
offsets[e] = tile_begin * p.row_tile_size;
cursors[e] = 0;
write_tiles(expert_base + e, count, tile_begin, n_tiles);
}
base += subgroupAdd(count);
base += subgroupAdd(n_tiles);
}
if (subgroupElect()) {
data_d[2 * p.n_experts] = base;
if (tid == 0) {
total_tiles = base;
}
}
#else
if (tid == 0) {
uint offset = 0;
for (uint expert = 0; expert < p.n_experts; ++expert) {
const uint count = vals[expert];
data_d[expert] = count;
data_d[p.n_experts + expert] = offset;
offsets[expert] = offset;
cursors[expert] = 0;
offset += count;
for (uint e = 0; e < n_experts; ++e) {
const uint count = vals[e];
const uint n_tiles = (count + p.row_tile_size - 1) >> tile_shift;
offsets[e] = total_tiles * p.row_tile_size;
cursors[e] = 0;
write_tiles(expert_base + e, count, total_tiles, n_tiles);
total_tiles += n_tiles;
}
data_d[2 * p.n_experts] = offset;
}
#endif
barrier();
@@ -106,35 +111,16 @@ void main() {
for (uint idx = tid; idx < num_elements; idx += BLOCK_SIZE) {
const uint i01 = fastdiv(idx, p.ne00mp, p.ne00L);
const uint i00 = idx - i01 * p.ne00;
const uint expert = data_a[p.a_offset + i01 * p.nb01 + i00 * p.nb00];
if (expert < p.n_experts) {
const uint expert = data_a[p.a_offset + i01 * p.nb01 + i00 * p.nb00] - expert_base;
if (expert < n_experts) {
const uint row = atomicAdd(cursors[expert], 1);
const uint packed_row_id = (i01 << 16) | (i00 & 0xffffu);
data_d[2 * p.n_experts + 1 + offsets[expert] + row] = packed_row_id;
data_d[p.row_ids_offset + offsets[expert] + row] = (i01 << 16) | i00;
}
}
return;
// The next chunk clears vals. Scatter uses only offsets and cursors.
expert_base += n_experts;
}
uint count = 0;
for (uint idx = tid; idx < num_elements; idx += BLOCK_SIZE) {
const uint i01 = fastdiv(idx, p.ne00mp, p.ne00L);
const uint i00 = idx - i01 * p.ne00;
const uint a = data_a[p.a_offset + i01 * p.nb01 + i00 * p.nb00];
count += uint(a == expert_id);
}
vals[tid] = count;
barrier();
[[unroll]] for (uint s = BLOCK_SIZE / 2; s > 0; s >>= 1) {
if (tid < s) {
vals[tid] += vals[tid + s];
}
barrier();
}
if (tid == 0) {
data_d[expert_id] = vals[0];
data_d[0] = total_tiles;
}
}
+13 -37
View File
@@ -124,7 +124,7 @@ layout (binding = 2) writeonly buffer D {D_TYPE data_d[];};
#ifdef MUL_MAT_ID
layout (binding = 3) readonly buffer IDS {int data_ids[];};
layout (binding = 4) readonly buffer Counts {int data_expert_count[];};
layout (binding = 4) readonly buffer RowMap {uint data_row_map[];};
#endif
layout (push_constant) uniform parameter
@@ -146,7 +146,7 @@ layout (push_constant) uniform parameter
uint nbi1;
uint ne11;
uint n_experts;
uint hoist_row_ids;
uint row_ids_offset;
#else
uint base_work_group_z;
uint num_batches;
@@ -208,13 +208,17 @@ shared ACC_TYPE coopmat_stage[TM * TN * NUM_WARPS];
#endif
void main() {
const uint ic = gl_WorkGroupID.y;
#ifdef MUL_MAT_ID
const uint expert_idx = gl_WorkGroupID.z;
if (ic * BN >= data_expert_count[expert_idx]) {
const uint tile_idx = gl_WorkGroupID.y + gl_WorkGroupID.z * gl_NumWorkGroups.y;
if (tile_idx >= data_row_map[0]) {
return;
}
const uint expert_idx = data_row_map[1 + 2 * tile_idx];
const uint row_begin = tile_idx * BN;
_ne1 = data_row_map[2 + 2 * tile_idx];
const uint ic = 0;
#else
const uint ic = gl_WorkGroupID.y;
#endif
#if defined(NEEDS_INIT_IQ_SHMEM) || defined(MULMAT_QUANT)
init_iq_shmem(gl_WorkGroupSize);
@@ -285,37 +289,9 @@ void main() {
const uint loadstride_b = gl_WorkGroupSize.x * LOAD_VEC_B_EFF * LOAD_VEC_BATCH_B / BK;
#ifdef MUL_MAT_ID
if (p.hoist_row_ids != 0) {
load_row_ids_hoisted(expert_idx, ic);
} else {
#ifdef MUL_MAT_ID_USE_SUBGROUPS
if (bitCount(p.nei0) == 1) {
load_row_ids(expert_idx, true, ic);
} else {
load_row_ids(expert_idx, false, ic);
}
#else
_ne1 = 0;
for (uint ii1 = 0; ii1 < p.nei1 && _ne1 < (ic + 1) * BN; ii1++) {
for (uint ii0 = 0; ii0 < p.nei0 && _ne1 < (ic + 1) * BN; ii0++) {
if (data_ids[ii1*p.nbi1 + ii0] == expert_idx) {
if (_ne1 >= ic * BN) {
row_ids[_ne1 - ic * BN] = u16vec2(ii0, ii1);
}
_ne1++;
}
}
}
barrier();
#endif
}
// Workgroup has no work
if (ic * BN >= _ne1) return;
uint required_work_items = (_ne1 - ic * BN) * BK / LOAD_VEC_B_EFF / LOAD_VEC_BATCH_B;
uint required_warp_c = (_ne1 - ic * BN + WN - 1) / WN;
load_row_ids(row_begin);
uint required_work_items = _ne1 * BK / LOAD_VEC_B_EFF / LOAD_VEC_BATCH_B;
uint required_warp_c = (_ne1 + WN - 1) / WN;
#endif
#ifdef MUL_MAT_ID
@@ -82,7 +82,7 @@ layout (push_constant) uniform parameter
uint nbi1;
uint ne11;
uint n_experts;
uint hoist_row_ids;
uint row_ids_offset;
#else
uint base_work_group_z;
uint num_batches;
@@ -182,7 +182,7 @@ f16vec4 mmDecodeA_v(const in decodeBufA bl_in, const in uint blockCoords[2], con
#ifdef MUL_MAT_ID
layout (binding = 3) readonly buffer IDS {int data_ids[];};
layout (binding = 4) readonly buffer Counts {int data_expert_count[];};
layout (binding = 4) readonly buffer RowMap {uint data_row_map[];};
shared u16vec4 row_ids[BN];
@@ -191,7 +191,6 @@ layout(buffer_reference, std430, buffer_reference_align = 2) buffer decodeBufB {
};
uint _ne1;
shared uvec4 ballots_sh[BLOCK_SIZE / subgroup_size];
B_TYPE decodeFuncB(const in decodeBufB bl, const in uint blockCoords[2], const in uint coordInBlock[2])
{
@@ -238,82 +237,9 @@ D_TYPE perElemOpD(const in uint32_t r, const in uint32_t c, const in D_TYPE elem
return elem;
}
void load_row_ids(uint expert_idx, bool nei0_is_pow2, uint ic) {
_ne1 = 0;
uint num_elements = p.nei1 * p.nei0;
uint nei0shift = findLSB(p.nei0);
uint ids[16];
uint iter = 0;
uint expert_count = data_expert_count[expert_idx];
for (uint j = 0; j < num_elements; j += BLOCK_SIZE) {
// prefetch up to 16 elements
if (iter == 0) {
[[unroll]] for (uint k = 0; k < 16; ++k) {
uint i = j + gl_LocalInvocationIndex + k*BLOCK_SIZE;
bool in_range = i < num_elements;
uint ii1;
if (nei0_is_pow2) {
ii1 = i >> nei0shift;
} else {
ii1 = i / p.nei0;
}
uint ii0 = i - ii1 * p.nei0;
ids[k] = in_range ? data_ids[ii1*p.nbi1 + ii0] : 0;
}
}
uint i = j + gl_LocalInvocationIndex;
bool in_range = i < num_elements;
uint ii1;
if (nei0_is_pow2) {
ii1 = i >> nei0shift;
} else {
ii1 = i / p.nei0;
}
uint ii0 = i - ii1 * p.nei0;
uint id = ids[iter++];
uvec4 ballot = subgroupBallot(in_range && id == expert_idx);
if (gl_SubgroupInvocationID == 0) {
ballots_sh[gl_SubgroupID] = ballot;
}
barrier();
uint subgroup_base = 0;
uint total = 0;
for (uint k = 0; k < gl_NumSubgroups; ++k) {
if (k == gl_SubgroupID) {
subgroup_base = total;
}
total += subgroupBallotBitCount(ballots_sh[k]);
}
barrier();
uint idx = subgroup_base + subgroupBallotExclusiveBitCount(ballot);
if (in_range && id == expert_idx && _ne1 + idx >= ic * BN && _ne1 + idx < (ic + 1) * BN) {
row_ids[_ne1 + idx - ic * BN] = u16vec4(fastmod(ii0, p.ne11), ii1, ii0, 0);
}
_ne1 += total;
iter &= 15;
if (_ne1 >= (ic + 1) * BN || _ne1 == expert_count) {
break;
}
}
barrier();
}
void load_row_ids_hoisted(uint expert_idx, uint ic) {
_ne1 = uint(data_expert_count[expert_idx]);
const uint tile_begin = ic * BN;
const uint tile_count = tile_begin < _ne1 ? min(BN, _ne1 - tile_begin) : 0;
const uint expert_offset = uint(data_expert_count[p.n_experts + expert_idx]);
const uint row_ids_offset = 2 * p.n_experts + 1 + expert_offset + tile_begin;
for (uint i = gl_LocalInvocationIndex; i < tile_count; i += BLOCK_SIZE) {
const uint packed_row_id = uint(data_expert_count[row_ids_offset + i]);
void load_row_ids(uint row_begin) {
for (uint i = gl_LocalInvocationIndex; i < _ne1; i += BLOCK_SIZE) {
const uint packed_row_id = data_row_map[p.row_ids_offset + row_begin + i];
const uint ii0 = packed_row_id & 0xffffu;
const uint ii1 = packed_row_id >> 16;
row_ids[i] = u16vec4(fastmod(ii0, p.ne11), ii1, ii0, 0);
@@ -328,13 +254,20 @@ void load_row_ids_hoisted(uint expert_idx, uint ic) {
void main() {
const uint tid = gl_LocalInvocationIndex;
const uint ic = gl_WorkGroupID.y;
#ifdef MUL_MAT_ID
const uint expert_idx = gl_WorkGroupID.z;
if (ic * BN >= data_expert_count[expert_idx]) {
const uint tile_idx = gl_WorkGroupID.y + gl_WorkGroupID.z * gl_NumWorkGroups.y;
if (tile_idx >= data_row_map[0]) {
return;
}
const uint expert_idx = data_row_map[1 + 2 * tile_idx];
const uint row_begin = tile_idx * BN;
_ne1 = data_row_map[2 + 2 * tile_idx];
const uint ic = 0;
#else
const uint ic = gl_WorkGroupID.y;
#endif
#ifdef MUL_MAT_ID
// initialize to row 0 so we don't need to bounds check
if (tid < BN) {
row_ids[tid] = u16vec4(0);
@@ -365,16 +298,7 @@ void main() {
const uint ik = gl_WorkGroupID.x / blocks_m;
#ifdef MUL_MAT_ID
if (p.hoist_row_ids != 0) {
load_row_ids_hoisted(expert_idx, ic);
} else if (bitCount(p.nei0) == 1) {
load_row_ids(expert_idx, true, ic);
} else {
load_row_ids(expert_idx, false, ic);
}
// Workgroup has no work
if (ic * BN >= _ne1) return;
load_row_ids(row_begin);
#endif
#ifdef MUL_MAT_ID
@@ -2,86 +2,9 @@
shared u16vec2 row_ids[BN];
uint _ne1;
#ifdef MUL_MAT_ID_USE_SUBGROUPS
shared uvec4 ballots_sh[NUM_WARPS];
void load_row_ids(uint expert_idx, bool nei0_is_pow2, uint ic) {
_ne1 = 0;
uint num_elements = p.nei1 * p.nei0;
uint nei0shift = findLSB(p.nei0);
uint ids[16];
uint iter = 0;
uint expert_count = data_expert_count[expert_idx];
for (uint j = 0; j < num_elements; j += BLOCK_SIZE) {
// prefetch up to 16 elements
if (iter == 0) {
[[unroll]] for (uint k = 0; k < 16; ++k) {
uint i = j + gl_LocalInvocationIndex + k*BLOCK_SIZE;
bool in_range = i < num_elements;
uint ii1;
if (nei0_is_pow2) {
ii1 = i >> nei0shift;
} else {
ii1 = i / p.nei0;
}
uint ii0 = i - ii1 * p.nei0;
ids[k] = in_range ? data_ids[ii1*p.nbi1 + ii0] : 0;
}
}
uint i = j + gl_LocalInvocationIndex;
bool in_range = i < num_elements;
uint ii1;
if (nei0_is_pow2) {
ii1 = i >> nei0shift;
} else {
ii1 = i / p.nei0;
}
uint ii0 = i - ii1 * p.nei0;
uint id = ids[iter++];
uvec4 ballot = subgroupBallot(in_range && id == expert_idx);
if (gl_SubgroupInvocationID == 0) {
ballots_sh[gl_SubgroupID] = ballot;
}
barrier();
uint subgroup_base = 0;
uint total = 0;
for (uint k = 0; k < gl_NumSubgroups; ++k) {
if (k == gl_SubgroupID) {
subgroup_base = total;
}
total += subgroupBallotBitCount(ballots_sh[k]);
}
barrier();
uint idx = subgroup_base + subgroupBallotExclusiveBitCount(ballot);
if (in_range && id == expert_idx && _ne1 + idx >= ic * BN && _ne1 + idx < (ic + 1) * BN) {
row_ids[_ne1 + idx - ic * BN] = u16vec2(ii0, ii1);
}
_ne1 += total;
iter &= 15;
if (_ne1 >= (ic + 1) * BN || _ne1 == expert_count) {
break;
}
}
barrier();
}
#endif // MUL_MAT_ID_USE_SUBGROUPS
void load_row_ids_hoisted(uint expert_idx, uint ic) {
_ne1 = uint(data_expert_count[expert_idx]);
const uint tile_begin = ic * BN;
const uint tile_count = tile_begin < _ne1 ? min(BN, _ne1 - tile_begin) : 0;
const uint expert_offset = uint(data_expert_count[p.n_experts + expert_idx]);
const uint row_ids_offset = 2 * p.n_experts + 1 + expert_offset + tile_begin;
for (uint i = gl_LocalInvocationIndex; i < tile_count; i += BLOCK_SIZE) {
const uint packed_row_id = uint(data_expert_count[row_ids_offset + i]);
void load_row_ids(uint row_begin) {
for (uint i = gl_LocalInvocationIndex; i < _ne1; i += BLOCK_SIZE) {
const uint packed_row_id = data_row_map[p.row_ids_offset + row_begin + i];
row_ids[i] = u16vec2(packed_row_id & 0xffffu, packed_row_id >> 16);
}
barrier();
@@ -37,7 +37,7 @@ layout (binding = 2) writeonly buffer D {D_TYPE data_d[];};
#ifdef MUL_MAT_ID
layout (binding = 3) readonly buffer IDS {int data_ids[];};
layout (binding = 4) readonly buffer Counts {int data_expert_count[];};
layout (binding = 4) readonly buffer RowMap {uint data_row_map[];};
#endif
layout (push_constant) uniform parameter
@@ -59,7 +59,7 @@ layout (push_constant) uniform parameter
uint nbi1;
uint ne11;
uint n_experts;
uint hoist_row_ids;
uint row_ids_offset;
#else
uint base_work_group_z;
uint num_batches;
@@ -105,19 +105,22 @@ block_b_cache cache_b;
#define LOAD_VEC_A (4 * QUANT_R_MMQ)
#define LOAD_VEC_B 16
#define NUM_WARPS (BLOCK_SIZE / WARP)
#include "mul_mm_id_funcs.glsl"
#include "mul_mmq_funcs.glsl"
void main() {
const uint ic = gl_WorkGroupID.y;
#ifdef MUL_MAT_ID
const uint expert_idx = gl_WorkGroupID.z;
if (ic * BN >= data_expert_count[expert_idx]) {
const uint tile_idx = gl_WorkGroupID.y + gl_WorkGroupID.z * gl_NumWorkGroups.y;
if (tile_idx >= data_row_map[0]) {
return;
}
const uint expert_idx = data_row_map[1 + 2 * tile_idx];
const uint row_begin = tile_idx * BN;
_ne1 = data_row_map[2 + 2 * tile_idx];
const uint ic = 0;
#else
const uint ic = gl_WorkGroupID.y;
#endif
#ifdef NEEDS_INIT_IQ_SHMEM
init_iq_shmem(gl_WorkGroupSize);
@@ -161,34 +164,7 @@ void main() {
const uint loadstride_b = BLOCK_SIZE * LOAD_VEC_B / BK;
#ifdef MUL_MAT_ID
if (p.hoist_row_ids != 0) {
load_row_ids_hoisted(expert_idx, ic);
} else {
#ifdef MUL_MAT_ID_USE_SUBGROUPS
if (bitCount(p.nei0) == 1) {
load_row_ids(expert_idx, true, ic);
} else {
load_row_ids(expert_idx, false, ic);
}
#else
_ne1 = 0;
for (uint ii1 = 0; ii1 < p.nei1 && _ne1 < (ic + 1) * BN; ii1++) {
for (uint ii0 = 0; ii0 < p.nei0 && _ne1 < (ic + 1) * BN; ii0++) {
if (data_ids[ii1*p.nbi1 + ii0] == expert_idx) {
if (_ne1 >= ic * BN) {
row_ids[_ne1 - ic * BN] = u16vec2(ii0, ii1);
}
_ne1++;
}
}
}
barrier();
#endif
}
// Workgroup has no work
if (ic * BN >= _ne1) return;
load_row_ids(row_begin);
#endif
#ifdef MUL_MAT_ID
@@ -39,7 +39,7 @@ layout (binding = 2) writeonly buffer D {D_TYPE data_d[];};
#ifdef MUL_MAT_ID
layout (binding = 3) readonly buffer IDS {int data_ids[];};
layout (binding = 4) readonly buffer Counts {int data_expert_count[];};
layout (binding = 4) readonly buffer RowMap {uint data_row_map[];};
#endif
layout (push_constant) uniform parameter
@@ -61,7 +61,7 @@ layout (push_constant) uniform parameter
uint nbi1;
uint ne11;
uint n_experts;
uint hoist_row_ids;
uint row_ids_offset;
#else
uint base_work_group_z;
uint num_batches;
@@ -142,13 +142,22 @@ ACC_TYPE cm1_accumulate(ACC_TYPE prev, int acc_e, float scale_a, float nbias_a,
}
#ifdef MUL_MAT_ID
#define NUM_WARPS (BLOCK_SIZE / WARP)
#include "mul_mm_id_funcs.glsl"
#endif
#include "mul_mmq_cm1_funcs.glsl"
void main() {
#ifdef MUL_MAT_ID
const uint tile_idx = gl_WorkGroupID.y + gl_WorkGroupID.z * gl_NumWorkGroups.y;
if (tile_idx >= data_row_map[0]) {
return;
}
const uint expert_idx = data_row_map[1 + 2 * tile_idx];
const uint row_begin = tile_idx * BN;
_ne1 = data_row_map[2 + 2 * tile_idx];
const uint ic = 0;
#endif
#if defined(DATA_A_IQ4_NL) || defined(DATA_A_IQ4_XS)
if (gl_LocalInvocationIndex < 16u) {
cm1_kvalues[gl_LocalInvocationIndex] = kvalues_iq4nl_const[gl_LocalInvocationIndex];
@@ -175,12 +184,7 @@ void main() {
const uint ik = gl_WorkGroupID.x / blocks_m;
#ifdef MUL_MAT_ID
const uint ic = gl_WorkGroupID.y;
const uint ir = gl_WorkGroupID.x % blocks_m;
const uint expert_idx = gl_WorkGroupID.z;
if (ic * BN >= data_expert_count[expert_idx]) {
return;
}
#else
// L2-friendly workgroup scheduling
const uint blocks_n = (p.N + BN - 1) / BN;
@@ -231,33 +235,7 @@ void main() {
const uint loadstride_b = BLOCK_SIZE * LOAD_VEC_B / BK;
#ifdef MUL_MAT_ID
if (p.hoist_row_ids != 0) {
load_row_ids_hoisted(expert_idx, ic);
} else {
#ifdef MUL_MAT_ID_USE_SUBGROUPS
if (bitCount(p.nei0) == 1) {
load_row_ids(expert_idx, true, ic);
} else {
load_row_ids(expert_idx, false, ic);
}
#else
_ne1 = 0;
for (uint ii1 = 0; ii1 < p.nei1 && _ne1 < (ic + 1) * BN; ii1++) {
for (uint ii0 = 0; ii0 < p.nei0 && _ne1 < (ic + 1) * BN; ii0++) {
if (data_ids[ii1*p.nbi1 + ii0] == expert_idx) {
if (_ne1 >= ic * BN) {
row_ids[_ne1 - ic * BN] = u16vec2(ii0, ii1);
}
_ne1++;
}
}
}
barrier();
#endif
}
if (ic * BN >= _ne1) return;
load_row_ids(row_begin);
#endif
#ifdef MUL_MAT_ID
+25
View File
@@ -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"
@@ -493,6 +495,19 @@ class Keys:
BETA = "xielu.beta"
EPS = "xielu.eps"
class PrismHadamard:
VERSION = "prism.hadamard.version"
TIED_OUTPUT = "prism.hadamard.tied_output"
BLOCK_SIZE = "prism.hadamard.block_size"
TRANSFORM = "prism.hadamard.transform"
AXIS = "prism.hadamard.axis"
SIGN_MODE = "prism.hadamard.sign_mode"
SIGN_WIDTHS = "prism.hadamard.sign_widths"
SIGN_VALUES = "prism.hadamard.sign_values"
WEIGHT_NAMES = "prism.hadamard.weight_names"
INVERSE_WEIGHT_NAMES = "prism.hadamard.inverse_weight_names"
GDN_V_GROUPED = "prism.hadamard.gdn_v_grouped"
#
# recommended mapping of model tensor names for storage in gguf
@@ -1051,6 +1066,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 +1848,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 +2096,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 +5945,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 +6157,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"
+40
View File
@@ -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.
@@ -1627,6 +1634,39 @@ class GGUFWriter:
def add_xielu_eps(self, values: Sequence[float]):
self.add_array(Keys.xIELU.EPS, values)
def add_prism_hadamard_version(self, value: int) -> None:
self.add_uint32(Keys.PrismHadamard.VERSION, value)
def add_prism_hadamard_tied_output(self, value: bool) -> None:
self.add_bool(Keys.PrismHadamard.TIED_OUTPUT, value)
def add_prism_hadamard_block_size(self, value: int) -> None:
self.add_uint32(Keys.PrismHadamard.BLOCK_SIZE, value)
def add_prism_hadamard_transform(self, value: str) -> None:
self.add_string(Keys.PrismHadamard.TRANSFORM, value)
def add_prism_hadamard_axis(self, value: str) -> None:
self.add_string(Keys.PrismHadamard.AXIS, value)
def add_prism_hadamard_sign_mode(self, value: str) -> None:
self.add_string(Keys.PrismHadamard.SIGN_MODE, value)
def add_prism_hadamard_sign_widths(self, values: Sequence[int]) -> None:
self.add_array(Keys.PrismHadamard.SIGN_WIDTHS, values)
def add_prism_hadamard_sign_values(self, values: Sequence[int]) -> None:
self.add_array(Keys.PrismHadamard.SIGN_VALUES, values)
def add_prism_hadamard_weight_names(self, names: Sequence[str]) -> None:
self.add_array(Keys.PrismHadamard.WEIGHT_NAMES, names)
def add_prism_hadamard_inverse_weight_names(self, names: Sequence[str]) -> None:
self.add_array(Keys.PrismHadamard.INVERSE_WEIGHT_NAMES, names)
def add_prism_hadamard_gdn_v_grouped(self, value: bool) -> None:
self.add_bool(Keys.PrismHadamard.GDN_V_GROUPED, value)
def add_attention_value_expert_count(self, count: int):
self.add_uint32(Keys.Attention.VALUE_EXPERT_COUNT.format(arch=self.arch), count)
+2 -1
View File
@@ -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,
+1
View File
@@ -132,6 +132,7 @@ Public API changes carry a higher bar than internal ones (`CONTRIBUTING.md`). Re
- Only add tests that bring meaningful results. Too-trivial tests just bloat the suite and CI.
- No time-sensitive tests (timing thresholds, sleeps, races against wall-clock); they are flaky on CI.
- Think twice about tests that significantly increase CI run time (expensive computation, large inputs, or long sleep/wait delays) or download large amounts of data from the internet (big models, datasets). Flag them and ask whether a smaller model/input or an existing fixture would do.
- Server: small tests of internal components (helpers, `server_tokens`, queues, format converters, ...) belong in `tests/test-server-component.cpp`. The end-to-end pytest suite in `tools/server/tests` is costly in CI time; only add to it when the test requires end-to-end behavior with a real model.
## General (always)
+4
View File
@@ -334,6 +334,10 @@ static void llama_adapter_lora_init_impl(llama_model & model, FILE * file, llama
if (!model_tensor) {
throw std::runtime_error("LoRA tensor '" + name + "' does not exist in base model (hint: maybe wrong base model?)");
}
// the LoRA delta of a Hadamard-folded weight reads the transformed input, so the result is wrong
if (model.hdmd.weight_blocks.count(name) || model.hdmd.inverse_blocks.count(name)) {
throw std::runtime_error("LoRA tensor '" + name + "' targets a prism.hadamard folded weight, which is not supported");
}
auto * buft = ggml_backend_buffer_get_type(model_tensor->buffer);
+13
View File
@@ -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" },
@@ -440,6 +441,18 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {
{ LLM_KV_XIELU_BETA, "xielu.beta" },
{ LLM_KV_XIELU_EPS, "xielu.eps" },
{ LLM_KV_PRISM_HADAMARD_VERSION, "prism.hadamard.version" },
{ LLM_KV_PRISM_HADAMARD_TIED_OUTPUT, "prism.hadamard.tied_output" },
{ LLM_KV_PRISM_HADAMARD_BLOCK_SIZE, "prism.hadamard.block_size" },
{ LLM_KV_PRISM_HADAMARD_TRANSFORM, "prism.hadamard.transform" },
{ LLM_KV_PRISM_HADAMARD_AXIS, "prism.hadamard.axis" },
{ LLM_KV_PRISM_HADAMARD_SIGN_MODE, "prism.hadamard.sign_mode" },
{ LLM_KV_PRISM_HADAMARD_SIGN_WIDTHS, "prism.hadamard.sign_widths" },
{ LLM_KV_PRISM_HADAMARD_SIGN_VALUES, "prism.hadamard.sign_values" },
{ LLM_KV_PRISM_HADAMARD_WEIGHT_NAMES, "prism.hadamard.weight_names" },
{ LLM_KV_PRISM_HADAMARD_INVERSE_WEIGHT_NAMES, "prism.hadamard.inverse_weight_names" },
{ LLM_KV_PRISM_HADAMARD_GDN_V_GROUPED, "prism.hadamard.gdn_v_grouped" },
// deprecated
{ LLM_KV_TOKENIZER_PREFIX_ID, "tokenizer.ggml.prefix_token_id" },
{ LLM_KV_TOKENIZER_SUFFIX_ID, "tokenizer.ggml.suffix_token_id" },
+13
View File
@@ -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,
@@ -439,6 +440,18 @@ enum llm_kv {
LLM_KV_XIELU_BETA,
LLM_KV_XIELU_EPS,
LLM_KV_PRISM_HADAMARD_VERSION,
LLM_KV_PRISM_HADAMARD_TIED_OUTPUT,
LLM_KV_PRISM_HADAMARD_BLOCK_SIZE,
LLM_KV_PRISM_HADAMARD_TRANSFORM,
LLM_KV_PRISM_HADAMARD_AXIS,
LLM_KV_PRISM_HADAMARD_SIGN_MODE,
LLM_KV_PRISM_HADAMARD_SIGN_WIDTHS,
LLM_KV_PRISM_HADAMARD_SIGN_VALUES,
LLM_KV_PRISM_HADAMARD_WEIGHT_NAMES,
LLM_KV_PRISM_HADAMARD_INVERSE_WEIGHT_NAMES,
LLM_KV_PRISM_HADAMARD_GDN_V_GROUPED,
// deprecated:
LLM_KV_TOKENIZER_PREFIX_ID,
LLM_KV_TOKENIZER_SUFFIX_ID,
+83 -15
View File
@@ -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
View File
@@ -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;
+90
View File
@@ -26,6 +26,89 @@
// llama_context
//
// check that each folded weight in the graph gets its Hadamard transform, and each latent lookup gets the inverse
// without this check, an arch that skips the transform helpers loads and computes wrong results
static void llama_verify_hadamard_graph(
ggml_cgraph * gf,
const llama_hadamard_rotations & rotations,
const llama_hadamard_rotations & inverses,
const llama_moe_cache * moe_cache) {
// the MoE cache gives the matmul a copy of the expert weights, so the copy also needs the transform of its source
llama_hadamard_rotations forward = rotations;
for (const auto & [w, t] : rotations) {
if (const ggml_tensor * cached = moe_cache ? moe_cache->get_experts(w) : nullptr) {
forward.emplace(cached, t);
}
}
auto unwrap = [](const ggml_tensor * t) {
while (t && (t->op == GGML_OP_RESHAPE || t->op == GGML_OP_VIEW)) {
t = t->src[0];
}
return t;
};
std::map<const ggml_tensor *, bool> lookups; // get_rows results of latent tables
for (int i = 0; i < ggml_graph_n_nodes(gf); ++i) {
const ggml_tensor * node = ggml_graph_node(gf, i);
if (node->op == GGML_OP_GET_ROWS && inverses.count(node->src[0])) {
lookups.emplace(node, false);
continue;
}
if (node->op != GGML_OP_MUL_MAT && node->op != GGML_OP_MUL_MAT_ID) {
continue;
}
if (node->op == GGML_OP_MUL_MAT && ((const int32_t *) node->op_params)[1] == GGML_HINT_SRC0_IS_HADAMARD) {
const auto lk = lookups.find(unwrap(node->src[1]));
if (lk != lookups.end()) {
lk->second = true;
}
continue;
}
const auto it = forward.find(node->src[0]);
if (it == forward.end()) {
if (inverses.count(node->src[0])) {
throw std::runtime_error(format(
"Hadamard-latent table '%s' is used as a head without a forward transform", node->src[0]->name));
}
continue;
}
const ggml_tensor * src = unwrap(node->src[1]);
const bool transformed = src && src->op == GGML_OP_MUL_MAT &&
((const int32_t *) src->op_params)[1] == GGML_HINT_SRC0_IS_HADAMARD &&
src->src[0] == it->second.rot;
if (!transformed) {
throw std::runtime_error(format(
"Hadamard-folded weight '%s' is consumed without its activation transform; "
"this graph's matmul path does not support prism.hadamard folding",
node->src[0]->name));
}
}
for (const auto & [node, ok] : lookups) {
if (!ok) {
for (int i = 0; i < ggml_graph_n_nodes(gf); ++i) {
const ggml_tensor * n2 = ggml_graph_node(gf, i);
for (int s = 0; s < GGML_MAX_SRC && n2->src[s]; ++s) {
if (unwrap(n2->src[s]) == node) {
LLAMA_LOG_WARN("%s: latent lookup '%s' consumed by op=%s name='%s' src%d hint=%d\n",
__func__, node->name, ggml_op_name(n2->op), n2->name, s,
((const int32_t *) n2->op_params)[1]);
}
}
}
throw std::runtime_error(format(
"Hadamard-latent table '%s' is read without the inverse transform",
node->src[0]->name));
}
}
}
static llm_graph_type ctx_type_to_graph_type(llama_context_type ctx_type) {
switch (ctx_type) {
case LLAMA_CONTEXT_TYPE_DEFAULT: return LLM_GRAPH_TYPE_DEFAULT;
@@ -2562,6 +2645,12 @@ ggml_cgraph * llama_context::graph_reserve(
auto * gf = model.build_graph(gparams);
// check the graph before scheduling: cross-backend copies break the producer chain that the check follows
if (!hadamard_verified && gf && (!model.hdmd.rot.empty() || !model.hdmd.inv.empty())) {
llama_verify_hadamard_graph(gf, model.hdmd.rot, model.hdmd.inv, moe_cache.get());
hadamard_verified = true;
}
this->n_input_tensors = llama_graph_n_input_tensors(gf);
this->n_outputs = save_n_outputs;
@@ -2600,6 +2689,7 @@ llm_graph_params llama_context::graph_params(
/*.cross =*/ &cross,
/*.moe_cache =*/ moe_cache.get(),
/*.prec_policy =*/ &model.prec_policy,
/*.hdmd =*/ model.hdmd.rot.empty() ? nullptr : &model.hdmd,
/*.samplers =*/ sampling.samplers,
/*.n_outputs =*/ n_outputs,
/*.cb =*/ graph_get_cb(),
+3
View File
@@ -413,6 +413,9 @@ private:
// env: LLAMA_GRAPH_REUSE_DISABLE
bool graph_reuse_disable = false;
// true after the prism.hadamard coverage check passes on a reserved graph
bool hadamard_verified = false;
// perf
mutable int64_t t_start_us = 0;
mutable int64_t t_load_us = 0;
+92 -13
View File
@@ -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));
}
}
@@ -1374,6 +1396,7 @@ void llm_graph_result::reset() {
inputs.clear();
fused_nodes.clear();
hdmd_inputs.clear();
buf_compute_meta.resize(ggml_tensor_overhead()*max_nodes + ggml_graph_overhead_custom(max_nodes, false));
@@ -1479,6 +1502,15 @@ void llm_graph_result::add_fused_node(llm_graph_fused_node result) {
fused_nodes.push_back(result);
}
ggml_tensor * llm_graph_result::get_hdmd_input(const ggml_tensor * cur, const ggml_tensor * rot) const {
const auto it = hdmd_inputs.find({ cur, rot });
return it == hdmd_inputs.end() ? nullptr : it->second;
}
void llm_graph_result::set_hdmd_input(const ggml_tensor * cur, const ggml_tensor * rot, ggml_tensor * res) {
hdmd_inputs[{ cur, rot }] = res;
}
void llm_graph_result::set_params(const llm_graph_params & params) {
this->params = params;
}
@@ -1526,6 +1558,7 @@ llm_graph_context::llm_graph_context(const llm_graph_params & params) :
cross (params.cross),
moe_cache (params.moe_cache),
prec_policy (params.prec_policy),
hdmd (params.hdmd),
samplers (params.samplers),
cb_func (params.cb),
res (params.res),
@@ -1548,10 +1581,42 @@ ggml_tensor * llm_graph_context::build_cvec(
return cvec->apply_to(ctx0, cur, il);
}
ggml_tensor * llm_graph_context::build_hadamard_input(
ggml_tensor * w,
ggml_tensor * cur) const {
if (!hdmd) {
return cur;
}
const auto it = hdmd->rot.find(w);
if (it == hdmd->rot.end()) {
return cur;
}
const auto & t = it->second;
if (ggml_tensor * x = res->get_hdmd_input(cur, t.rot)) {
return x;
}
ggml_tensor * x = cur;
if (t.perm_rep > 1) {
// tiled [hd, nk, rep] -> grouped [hd, rep, nk] feature order
x = ggml_is_contiguous(x) ? x : ggml_cont(ctx0, x);
const int64_t ne1 = x->ne[1], ne2 = x->ne[2], ne3 = x->ne[3];
x = ggml_reshape_4d(ctx0, x, t.perm_hd, t.perm_nk, t.perm_rep, ne1*ne2*ne3);
x = ggml_cont(ctx0, ggml_permute(ctx0, x, 0, 2, 1, 3));
x = ggml_reshape_4d(ctx0, x, t.perm_hd*t.perm_nk*t.perm_rep, ne1, ne2, ne3);
}
if (t.signs) {
x = ggml_mul(ctx0, x, t.signs);
}
x = llama_mul_mat_hadamard(ctx0, x, t.rot);
res->set_hdmd_input(cur, t.rot, x);
return x;
}
ggml_tensor * llm_graph_context::build_lora_mm(
ggml_tensor * w,
ggml_tensor * cur,
ggml_tensor * w_s) const {
cur = build_hadamard_input(w, cur);
ggml_tensor * res = ggml_mul_mat(ctx0, w, cur);
if (prec_policy) {
@@ -1593,6 +1658,7 @@ ggml_tensor * llm_graph_context::build_lora_mm_id(
ggml_tensor * ids,
ggml_tensor * w_s,
ggml_tensor * slots) const {
cur = build_hadamard_input(w, cur);
// the experts in the MoE cache are selected by their slots
ggml_tensor * res = slots == nullptr ?
ggml_mul_mat_id(ctx0, w, cur, ids) :
@@ -2459,6 +2525,9 @@ ggml_tensor * llm_graph_context::build_moe_cache_slots(
ggml_backend_sched_set_tensor_backend(sched, slots, moe_cache->backend(il));
cb(slots, "ffn_moe_slots", il);
// add the lookup now: the transform of a Hadamard-folded expert reads host weights and would start its split first
ggml_build_forward_expand(gf, slots);
return ggml_reshape_2d(ctx0, slots, selected_experts->ne[0], selected_experts->ne[1]); // [n_expert_used, n_tokens]
}
@@ -2522,6 +2591,16 @@ ggml_tensor * llm_graph_context::build_inp_embd(ggml_tensor * tok_embd, float to
// TODO: when lora is active, this is likely going to cause issues similar to https://github.com/ggml-org/llama.cpp/pull/30160
// need to add lora tests and refactor the logic to make the lora GET_ROWS go at the front of the graph
auto build_tok = [&](ggml_tensor * cur, ggml_tensor * ids) {
// a Hadamard-latent table stores rotated rows: restore the primal basis, h = s * (H z)
if (hdmd) {
if (const auto it = hdmd->inv.find(tok_embd); it != hdmd->inv.end()) {
cur = llama_mul_mat_hadamard(ctx0, cur, it->second.rot);
if (it->second.signs) {
cur = ggml_mul(ctx0, cur, it->second.signs);
}
}
}
// apply lora for embedding tokens if needed
for (const auto & lora : *loras) {
llama_adapter_lora_weight * lw = lora.first->get_weight(tok_embd);
@@ -2624,7 +2703,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;
+26 -6
View File
@@ -20,6 +20,7 @@ struct ggml_tensor;
struct llama_cparams;
struct llama_layer;
struct llama_prec_policy;
struct llama_hadamard;
class llama_moe_cache;
@@ -149,10 +150,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 +162,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 +181,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
@@ -800,6 +804,8 @@ struct llm_graph_params {
const llama_prec_policy * prec_policy = nullptr;
const llama_hadamard * hdmd = nullptr;
std::map<llama_seq_id, llama_sampler *> samplers;
static bool samplers_equal(
@@ -838,7 +844,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
@@ -939,6 +946,10 @@ public:
void add_fused_node(llm_graph_fused_node result);
// Hadamard-transformed activations, keyed by (input, rotation): folded weights that read the same activation share one transform
ggml_tensor * get_hdmd_input(const ggml_tensor * cur, const ggml_tensor * rot) const;
void set_hdmd_input(const ggml_tensor * cur, const ggml_tensor * rot, ggml_tensor * res);
const std::vector<llm_graph_fused_node> & get_fused_nodes() const { return fused_nodes; }
void set_params(const llm_graph_params & params);
@@ -961,6 +972,8 @@ public:
std::vector<llm_graph_input_ptr> inputs;
std::vector<llm_graph_fused_node> fused_nodes;
std::map<std::pair<const ggml_tensor *, const ggml_tensor *>, ggml_tensor *> hdmd_inputs;
ggml_context_ptr ctx_compute;
// memory buffers used to evaluate the model
@@ -1044,6 +1057,8 @@ struct llm_graph_context {
const llama_prec_policy * prec_policy;
const llama_hadamard * hdmd;
std::map<llama_seq_id, llama_sampler *> samplers;
const llm_graph_cb & cb_func;
@@ -1076,6 +1091,11 @@ struct llm_graph_context {
ggml_tensor * cur,
int il) const;
// apply the activation-side transform of a Hadamard-folded weight, if any
ggml_tensor * build_hadamard_input(
ggml_tensor * w,
ggml_tensor * cur) const;
// do mat_mul, while optionally apply lora and per-tensor scale
ggml_tensor * build_lora_mm(
ggml_tensor * w,
+9
View File
@@ -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;
+1
View File
@@ -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(),
+1
View File
@@ -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
+368
View File
@@ -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 },
@@ -1331,6 +1350,8 @@ void llama_model_base::load_hparams(llama_model_loader & ml) {
gguf_kv.emplace(name, value);
}
load_hparams_hadamard(ml);
// get general kv
ml.get_key(LLM_KV_GENERAL_NAME, name, false);
@@ -1447,6 +1468,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 +1545,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) {
@@ -1985,9 +2017,342 @@ bool llama_model_base::load_tensors(llama_model_loader & ml) {
}
}
load_tensors_hadamard();
return true;
}
// read and check the prism.hadamard metadata, and record the folded weights (load_tensors_hadamard makes the tensors)
void llama_model_base::load_hparams_hadamard(llama_model_loader & ml) {
uint32_t hadamard_version = 0;
ml.get_key(LLM_KV_PRISM_HADAMARD_TIED_OUTPUT, hdmd.tied_output, false);
if (ml.get_key(LLM_KV_PRISM_HADAMARD_VERSION, hadamard_version, false)) {
if (hadamard_version != 1 && hadamard_version != 2) {
throw std::runtime_error(format("unsupported prism.hadamard.version: %u", hadamard_version));
}
if ((hadamard_version == 2) != hdmd.tied_output) {
throw std::runtime_error("prism.hadamard version 2 requires tied_output=true; version 1 forbids it");
}
if (hdmd.tied_output && ml.get_weight("output.weight")) {
throw std::runtime_error("prism.hadamard.tied_output requires output.weight to be absent");
}
uint32_t block_size = 0;
std::string transform;
std::string axis;
std::string sign_mode;
std::vector<std::string> weight_names;
ml.get_key(LLM_KV_PRISM_HADAMARD_BLOCK_SIZE, block_size);
ml.get_key(LLM_KV_PRISM_HADAMARD_TRANSFORM, transform);
ml.get_key(LLM_KV_PRISM_HADAMARD_AXIS, axis);
ml.get_key(LLM_KV_PRISM_HADAMARD_SIGN_MODE, sign_mode);
ml.get_arr(LLM_KV_PRISM_HADAMARD_WEIGHT_NAMES, weight_names);
if (block_size == 0 || (block_size & (block_size - 1)) != 0) {
throw std::runtime_error(format("invalid prism.hadamard.block_size: %u", block_size));
}
if (transform != "normalized-sylvester-walsh-hadamard") {
throw std::runtime_error(format("unsupported prism.hadamard.transform: %s", transform.c_str()));
}
if (axis != "input-last-dimension") {
throw std::runtime_error(format("unsupported prism.hadamard.axis: %s", axis.c_str()));
}
if (sign_mode != "identity" && sign_mode != "explicit") {
throw std::runtime_error(format("unsupported prism.hadamard.sign_mode: %s", sign_mode.c_str()));
}
if (weight_names.empty()) {
throw std::runtime_error("prism.hadamard.weight_names is empty");
}
if (sign_mode == "explicit") {
std::vector<int32_t> sign_widths;
std::vector<int32_t> sign_values;
ml.get_arr(LLM_KV_PRISM_HADAMARD_SIGN_WIDTHS, sign_widths);
ml.get_arr(LLM_KV_PRISM_HADAMARD_SIGN_VALUES, sign_values);
// explicit mode with no widths gives an empty sign table, which acts as identity and changes the model
if (sign_widths.empty()) {
throw std::runtime_error("prism.hadamard.sign_mode is explicit but sign_widths is empty");
}
size_t off = 0;
for (const int32_t width : sign_widths) {
if (width <= 0 || (uint32_t) width % block_size != 0 || off + width > sign_values.size()) {
throw std::runtime_error(format("invalid prism.hadamard sign width: %d", width));
}
if (hdmd.sign_data.count(width)) {
throw std::runtime_error(format("duplicate prism.hadamard sign width: %d", width));
}
auto & vec = hdmd.sign_data[width];
vec.assign(sign_values.begin() + off, sign_values.begin() + off + width);
for (const int32_t v : vec) {
if (v != 1 && v != -1) {
throw std::runtime_error("prism.hadamard sign values must be +/-1");
}
}
off += width;
}
if (off != sign_values.size()) {
throw std::runtime_error("prism.hadamard.sign_values length mismatch");
}
}
ml.get_key(LLM_KV_PRISM_HADAMARD_GDN_V_GROUPED, hdmd.gdn_v_grouped, false);
// only build_lora_mm/build_lora_mm_id apply the transform: refuse archs and tensor kinds that can skip them
switch (arch) {
case LLM_ARCH_LLAMA:
case LLM_ARCH_QWEN3:
case LLM_ARCH_QWEN3MOE:
case LLM_ARCH_QWEN35:
case LLM_ARCH_QWEN35MOE:
case LLM_ARCH_QWEN3NEXT:
break;
default:
throw std::runtime_error(format(
"prism.hadamard: arch '%s' is not verified to apply the activation transform to all folded weights",
llm_arch_name(arch)));
}
// a folded weight W_f = W*D*H is the weight W with the +1/-1 signs D and the normalized block Hadamard H folded in
// H*H = I and D*D = I, so W*x = W_f*(H*(D*x)): the graph applies D, then H, to the matmul input
const auto is_foldable_weight = [](const std::string & name) {
static const char * kinds[] = {
"attn_q", "attn_k", "attn_v", "attn_qkv", "attn_gate", "attn_output",
"ffn_gate", "ffn_up", "ffn_down",
"ffn_gate_exps", "ffn_up_exps", "ffn_down_exps", "ffn_gate_up_exps",
"ffn_gate_shexp", "ffn_up_shexp", "ffn_down_shexp",
"ssm_out",
};
if (name == "output.weight") {
return true; // the output head is built through build_lora_mm in every arch
}
if (name.compare(0, 4, "blk.") != 0) {
return false;
}
size_t pos = 4;
while (pos < name.size() && isdigit((unsigned char) name[pos])) {
pos++;
}
if (pos == 4 || pos >= name.size() || name[pos] != '.') {
return false;
}
pos++;
for (const char * kind : kinds) {
const std::string suffix = std::string(kind) + ".weight";
if (name.compare(pos, std::string::npos, suffix) == 0) {
return true;
}
}
return false;
};
for (const auto & weight_name : weight_names) {
if (!is_foldable_weight(weight_name)) {
throw std::runtime_error(format(
"prism.hadamard: weight '%s' is not on a verified Hadamard-aware matmul path", weight_name.c_str()));
}
if (!hdmd.weight_blocks.emplace(weight_name, block_size).second) {
throw std::runtime_error(format("duplicate prism.hadamard weight: %s", weight_name.c_str()));
}
}
// tables read by row lookup store latent rows: the inverse transform goes on the lookup result
std::vector<std::string> inverse_names;
ml.get_arr(LLM_KV_PRISM_HADAMARD_INVERSE_WEIGHT_NAMES, inverse_names, false);
for (const auto & name : inverse_names) {
// the graph applies the inverse only after the token-embedding lookup, other latent tables stay rotated
if (name != "token_embd.weight") {
throw std::runtime_error(format(
"prism.hadamard: weight '%s' is not a verified inverse-after-lookup table", name.c_str()));
}
if (hdmd.weight_blocks.count(name) || !hdmd.inverse_blocks.emplace(name, block_size).second) {
throw std::runtime_error(format("duplicate prism.hadamard inverse weight: %s", name.c_str()));
}
}
}
if (hdmd.tied_output) {
if (hadamard_version != 2) {
throw std::runtime_error("prism.hadamard.tied_output requires version 2");
}
const auto it = hdmd.inverse_blocks.find("token_embd.weight");
if (it == hdmd.inverse_blocks.end()) {
throw std::runtime_error("prism.hadamard.tied_output requires a latent token embedding");
}
hdmd.weight_blocks.emplace("token_embd.weight", it->second);
} else if (hdmd.inverse_blocks.count("token_embd.weight") && !ml.get_weight("output.weight")) {
throw std::runtime_error("a tied Hadamard output requires version 2 and tied_output=true");
}
}
// make one Hadamard matrix per block size and one sign vector per width (the GGUF does not contain them)
// each tensor goes on the buffer type of its weights (never a CPU extra type), and each transform goes in hdmd.rot or hdmd.inv
void llama_model_base::load_tensors_hadamard() {
if (hdmd.weight_blocks.empty() && hdmd.inverse_blocks.empty()) {
return;
}
struct hadamard_rotation {
uint32_t block_size;
ggml_backend_buffer_type_t buft;
ggml_tensor * tensor;
};
std::vector<hadamard_rotation> rotations;
std::map<std::pair<uint32_t, ggml_backend_buffer_type_t>, ggml_tensor *> sign_tensors;
const std::pair<const std::unordered_map<std::string, uint32_t> *, llama_hadamard_rotations *> groups[] = {
{ &hdmd.weight_blocks, &hdmd.rot },
{ &hdmd.inverse_blocks, &hdmd.inv },
};
// inverse transforms use the buffer type of the forward rotations, not the host type of a CPU-mapped table (no PCIe round trip per token)
ggml_backend_buffer_type_t preferred_buft = nullptr;
for (const auto & [blocks, target] : groups) {
for (const auto & entry : *blocks) {
const std::string & weight_name = entry.first;
const uint32_t block_size = entry.second;
const ggml_tensor * weight = get_tensor(weight_name.c_str());
if (hdmd.tied_output && weight_name == "token_embd.weight") {
weight = target == &hdmd.rot ? output : tok_embd;
if (!weight || strcmp(weight->name, "token_embd.weight") != 0) {
throw std::runtime_error("prism.hadamard.tied_output is not bound to the token embedding");
}
}
if (weight == nullptr) {
throw std::runtime_error(format("prism.hadamard weight not found: %s", weight_name.c_str()));
}
if (weight->ne[0] % block_size != 0) {
throw std::runtime_error(format(
"prism.hadamard block size %u does not divide input dimension %lld for %s",
block_size, (long long) weight->ne[0], weight_name.c_str()));
}
if (weight->buffer == nullptr) {
throw std::runtime_error(format("prism.hadamard weight has no buffer: %s", weight_name.c_str()));
}
ggml_backend_buffer_type_t buft = ggml_backend_buffer_get_type(weight->buffer);
// CPU extra buffer types (e.g. CPU_REPACK) only accept tensors they can repack
if (ggml_backend_dev_t dev = ggml_backend_buft_get_device(buft)) {
if (ggml_backend_dev_type(dev) == GGML_BACKEND_DEVICE_TYPE_CPU) {
buft = ggml_backend_dev_buffer_type(dev);
}
}
if (target == &hdmd.rot) {
preferred_buft = buft;
} else if (preferred_buft) {
buft = preferred_buft;
}
auto it = std::find_if(rotations.begin(), rotations.end(),
[block_size, buft](const hadamard_rotation & rotation) {
return rotation.block_size == block_size && rotation.buft == buft;
});
if (it == rotations.end()) {
ggml_init_params params = {
/*.mem_size =*/ ggml_tensor_overhead(),
/*.mem_buffer =*/ NULL,
/*.no_alloc =*/ true,
};
ggml_context_ptr ctx { ggml_init(params) };
if (!ctx) {
throw std::runtime_error("failed to create Hadamard rotation context");
}
ggml_tensor * rotation = ggml_new_tensor_2d(ctx.get(), GGML_TYPE_F32, block_size, block_size);
char rotation_name[GGML_MAX_NAME];
snprintf(rotation_name, sizeof(rotation_name), "prism.hadamard.%u", block_size);
ggml_set_name(rotation, rotation_name);
ggml_backend_buffer_ptr buffer { ggml_backend_alloc_ctx_tensors_from_buft(ctx.get(), buft) };
if (!buffer) {
throw std::runtime_error(format("unable to allocate %s Hadamard rotation buffer", ggml_backend_buft_name(buft)));
}
ggml_backend_buffer_set_usage(buffer.get(), GGML_BACKEND_BUFFER_USAGE_WEIGHTS);
std::vector<float> data((size_t) block_size * block_size);
const float scale = 1.0f / sqrtf((float) block_size);
for (uint32_t row = 0; row < block_size; ++row) {
for (uint32_t col = 0; col < block_size; ++col) {
uint32_t parity = row & col;
parity ^= parity >> 16;
parity ^= parity >> 8;
parity ^= parity >> 4;
parity ^= parity >> 2;
parity ^= parity >> 1;
data[(size_t) row * block_size + col] = (parity & 1) ? -scale : scale;
}
}
ggml_backend_tensor_set(rotation, data.data(), 0, data.size() * sizeof(float));
std::vector<ggml_backend_buffer_ptr> buffers;
buffers.emplace_back(std::move(buffer));
pimpl->ctxs_bufs.emplace_back(std::move(ctx), std::move(buffers));
rotations.push_back({ block_size, buft, rotation });
it = std::prev(rotations.end());
}
ggml_tensor * sign_tensor = nullptr;
if (!hdmd.sign_data.empty()) {
const uint32_t width = (uint32_t) weight->ne[0];
const auto sd = hdmd.sign_data.find(width);
if (sd == hdmd.sign_data.end()) {
throw std::runtime_error(format(
"prism.hadamard has no sign vector for width %u (%s)", width, weight_name.c_str()));
}
const auto key = std::make_pair(width, buft);
auto st = sign_tensors.find(key);
if (st == sign_tensors.end()) {
ggml_init_params params = {
/*.mem_size =*/ ggml_tensor_overhead(),
/*.mem_buffer =*/ NULL,
/*.no_alloc =*/ true,
};
ggml_context_ptr ctx { ggml_init(params) };
if (!ctx) {
throw std::runtime_error("failed to create Hadamard sign context");
}
ggml_tensor * signs = ggml_new_tensor_1d(ctx.get(), GGML_TYPE_F32, width);
char sign_name[GGML_MAX_NAME];
snprintf(sign_name, sizeof(sign_name), "prism.hadamard.signs.%u", width);
ggml_set_name(signs, sign_name);
ggml_backend_buffer_ptr buffer { ggml_backend_alloc_ctx_tensors_from_buft(ctx.get(), buft) };
if (!buffer) {
throw std::runtime_error(format("unable to allocate %s Hadamard sign buffer", ggml_backend_buft_name(buft)));
}
ggml_backend_buffer_set_usage(buffer.get(), GGML_BACKEND_BUFFER_USAGE_WEIGHTS);
std::vector<float> data(width);
for (uint32_t i = 0; i < width; ++i) {
data[i] = (float) sd->second[i];
}
ggml_backend_tensor_set(signs, data.data(), 0, data.size() * sizeof(float));
std::vector<ggml_backend_buffer_ptr> buffers;
buffers.emplace_back(std::move(buffer));
pimpl->ctxs_bufs.emplace_back(std::move(ctx), std::move(buffers));
st = sign_tensors.emplace(key, signs).first;
}
sign_tensor = st->second;
}
llama_hadamard_transform transform { it->tensor, sign_tensor };
// the GDN output projection reads its value heads in tiled order, see set_gdn_v_perm
if (hdmd.gdn_v_grouped && weight_name.find(".ssm_out.") != std::string::npos &&
!transform.set_gdn_v_perm(weight->ne[0], hparams.ssm_dt_rank, hparams.ssm_n_group)) {
throw std::runtime_error(format("prism.hadamard: bad GDN head geometry for %s", weight_name.c_str()));
}
target->emplace(weight, transform);
}
}
LLAMA_LOG_INFO("%s: loaded %zu Hadamard-folded weight(s) (%zu inverse-lookup) using %zu rotation(s) and %zu sign vector(s)\n",
__func__, hdmd.rot.size() + hdmd.inv.size(), hdmd.inv.size(), rotations.size(), sign_tensors.size());
}
ggml_tensor * llama_model_base::create_tensor(llama_model_loader & ml, const LLM_TN_IMPL & tn, const std::initializer_list<int64_t> & ne, int flags) {
const buft_list_t * buft_list_layer = nullptr;
if (tn.bid != -1) {
@@ -2158,6 +2523,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);
+47
View File
@@ -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.
@@ -637,6 +638,44 @@ struct llama_prec_policy {
void load(llama_model_loader & ml, const llama_model & model);
};
// transform of a folded weight, applied to the matmul input: optional sign flip, then the normalized block Hadamard rotation
struct llama_hadamard_transform {
ggml_tensor * rot;
ggml_tensor * signs; // nullptr for identity sign mode
// if perm_rep > 1, permute the input from tiled head order [hd, nk, rep] to grouped order [hd, rep, nk] before signs and rotation
int64_t perm_hd = 0;
int64_t perm_nk = 0;
int64_t perm_rep = 0;
// ssm_out of a gated delta net gets its value heads in tiled order, but the fold used grouped order
// record the head geometry for that permutation, return false if it does not match the input width
bool set_gdn_v_perm(int64_t n_in, int64_t n_v, int64_t n_k) {
if (n_k <= 0 || n_v <= 0 || n_v % n_k != 0 || n_in % n_v != 0) {
return false;
}
perm_hd = n_in / n_v;
perm_nk = n_k;
perm_rep = n_v / n_k;
return true;
}
};
using llama_hadamard_rotations = std::unordered_map<const ggml_tensor *, llama_hadamard_transform>;
struct llama_hadamard {
// names and sign data come from the GGUF metadata in load_hparams, the transforms are made in load_tensors
std::unordered_map<std::string, uint32_t> weight_blocks;
std::unordered_map<std::string, uint32_t> inverse_blocks;
std::map<uint32_t, std::vector<int32_t>> sign_data;
bool gdn_v_grouped = false;
bool tied_output = false;
llama_hadamard_rotations rot; // folded weight -> activation transform
llama_hadamard_rotations inv; // latent lookup table -> inverse transform
};
struct llama_model {
llm_type type = LLM_TYPE_UNKNOWN;
llm_arch arch = LLM_ARCH_UNKNOWN;
@@ -649,6 +688,8 @@ struct llama_model {
// per-tensor activation precision policy
llama_prec_policy prec_policy;
llama_hadamard hdmd;
// for classifier models
std::vector<std::string> classifier_labels;
@@ -856,6 +897,12 @@ struct llama_model_base : public llama_model {
};
nextn_flags_t nextn_flags(llama_model_loader & ml, llm_tensor trunk_probe = LLM_TENSOR_ATTN_NORM) const;
// helper: read the prism.hadamard metadata and record which weights are folded
void load_hparams_hadamard(llama_model_loader & ml);
// helper: make the prism.hadamard rotation and sign tensors for the folded weights
void load_tensors_hadamard();
// helper: read the SWA pattern as one flag per layer, or as a period expanded by set_swa_pattern
void load_swa_pattern(llama_model_loader & ml, uint32_t n_pattern, bool dense_first = false);
+7 -5
View File
@@ -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);
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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);
+10 -4
View File
@@ -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);
+10 -4
View File
@@ -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);
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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);
+9 -4
View File
@@ -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;
+8 -5
View File
@@ -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));
+8 -5
View File
@@ -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));
+1 -1
View File
@@ -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);
+11 -1
View File
@@ -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);
@@ -534,6 +534,16 @@ llama_model_qwen35::graph_mtp::graph_mtp(const llama_model & model, const llm_gr
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);
// a Hadamard-latent table stores rotated rows; restore the primal basis
if (hdmd) {
if (const auto it = hdmd->inv.find(tok_embd_w); it != hdmd->inv.end()) {
tok_embd = llama_mul_mat_hadamard(ctx0, tok_embd, it->second.rot);
if (it->second.signs) {
tok_embd = ggml_mul(ctx0, tok_embd, it->second.signs);
}
}
}
} else {
tok_embd = inp->embd;
}
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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);
+8 -3
View File
@@ -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;
+8 -5
View File
@@ -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));
+3
View File
@@ -163,6 +163,9 @@ if (NOT WIN32 OR NOT BUILD_SHARED_LIBS)
llama_build_and_test(test-chat.cpp WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
target_include_directories(test-chat PRIVATE ${PROJECT_SOURCE_DIR}/tools/server)
target_link_libraries(test-chat PRIVATE server-context)
llama_build_and_test(test-server-component.cpp)
target_include_directories(test-server-component PRIVATE ${PROJECT_SOURCE_DIR}/tools/server)
target_link_libraries(test-server-component PRIVATE server-context)
llama_build_and_test(test-json-schema-to-grammar.cpp)
if (NOT GGML_BACKEND_DL)
+45 -6
View File
@@ -5440,9 +5440,10 @@ struct test_mul_mat_id : public test_case {
const int64_t k;
const float amax; // magnitude of src1
const int64_t m_v; // rows of as in memory, the experts of as are strided for m_v > m, no view for m_v == 0
const bool ids_offset;
std::string vars() override {
return VARS_TO_STR10(type_a, type_b, n_mats, n_used, b, m, n, k, amax, m_v);
return VARS_TO_STR11(type_a, type_b, n_mats, n_used, b, m, n, k, amax, m_v, ids_offset);
}
double max_nmse_err() override {
@@ -5467,9 +5468,9 @@ struct test_mul_mat_id : public test_case {
test_mul_mat_id(ggml_type type_a = GGML_TYPE_F32, ggml_type type_b = GGML_TYPE_F32,
int n_mats = 8, int n_used = 2, bool b = false,
int64_t m = 32, int64_t n = 32, int64_t k = 32,
float amax = 1.0f, int64_t m_v = 0)
float amax = 1.0f, int64_t m_v = 0, bool ids_offset = false)
: type_a(type_a), type_b(type_b), n_mats(n_mats), n_used(n_used), b(b),
m(m), n(n), k(k), amax(amax), m_v(m_v) {
m(m), n(n), k(k), amax(amax), m_v(m_v), ids_offset(ids_offset) {
GGML_ASSERT(n_used <= n_mats);
GGML_ASSERT(m_v == 0 || m_v > m);
}
@@ -5482,10 +5483,10 @@ struct test_mul_mat_id : public test_case {
}
ggml_set_name(as, "as");
ggml_tensor * ids = ggml_new_tensor_2d(ctx, GGML_TYPE_I32, n_mats, n);
ggml_tensor * ids = ggml_new_tensor_2d(ctx, GGML_TYPE_I32, n_mats + int(ids_offset), n);
ggml_set_name(ids, "ids");
if (n_used != n_mats) {
ids = ggml_view_2d(ctx, ids, n_used, n, ids->nb[1], 0);
if (n_used != n_mats || ids_offset) {
ids = ggml_view_2d(ctx, ids, n_used, n, ids->nb[1], ids_offset ? sizeof(int32_t) : 0);
ggml_set_name(ids, "view_of_ids");
}
@@ -5512,6 +5513,32 @@ struct test_mul_mat_id : public test_case {
}
};
// MUL_MAT_ID with expert ids repeated within a token's row
struct test_mul_mat_id_dup : public test_mul_mat_id {
using test_mul_mat_id::test_mul_mat_id;
std::string vars() override { return test_mul_mat_id::vars() + ",dup=1"; }
void initialize_tensors(ggml_context * ctx) override {
std::default_random_engine rng(1234);
for (ggml_tensor * t = ggml_get_first_tensor(ctx); t != NULL; t = ggml_get_next_tensor(ctx, t)) {
if (t->type == GGML_TYPE_I32) {
if (ggml_is_view_op(t->op)) { continue; }
for (int64_t r = 0; r < ggml_nrows(t); r++) {
std::vector<int32_t> data(t->ne[0]);
for (int i = 0; i < t->ne[0]; i++) {
data[i] = (rng() % 4 == 0) ? (int32_t) (rng() % n_mats) : 0; // repeated ids, mostly expert 0
}
if (r == ggml_nrows(t) - 1) {
data[ids_offset ? 1 : 0] = n_mats - 1;
}
ggml_backend_tensor_set(t, data.data(), r * t->nb[1], t->ne[0] * sizeof(int32_t));
}
} else {
init_tensor_uniform(t);
}
}
}
};
// FP4 W4A8 path on the MoE path (GGML_PREC_Q8 on src1 disallows 4-bit activations)
struct test_mul_mat_id_w4a8 : public test_mul_mat_id {
test_mul_mat_id_w4a8(ggml_type type_a = GGML_TYPE_NVFP4, ggml_type type_b = GGML_TYPE_F32,
@@ -10972,6 +10999,18 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
test_cases.emplace_back(new test_mul_mat_id(GGML_TYPE_TQ1_0, GGML_TYPE_F32, 28, 10, false, 1024, 1, 4096));
test_cases.emplace_back(new test_mul_mat_id(GGML_TYPE_TQ1_0, GGML_TYPE_F32, 128, 8, false, 1024, 1, 2048));
// repeated expert ids within a row
for (ggml_type ta : {GGML_TYPE_F16, GGML_TYPE_Q8_0, GGML_TYPE_Q4_0}) {
for (int n : {9, 16, 33, 64}) {
test_cases.emplace_back(new test_mul_mat_id_dup(ta, GGML_TYPE_F32, 28, 10, false, 1024, n, 256));
}
}
for (ggml_type ta : {GGML_TYPE_F16, GGML_TYPE_Q4_0}) {
test_cases.emplace_back(new test_mul_mat_id_dup(ta, GGML_TYPE_F32, 1025, 10, false, 64, 33, 256, 1.0f, 0, true));
test_cases.emplace_back(new test_mul_mat_id_dup(ta, GGML_TYPE_F32, 2050, 10, true, 64, 33, 256));
}
for (ggml_type type_a : all_types) {
test_cases.emplace_back(new test_mul_mat_id(type_a, GGML_TYPE_F32, 4, 2, false, 64, 16, 3*ggml_blck_size(type_a)));
}
+13 -7
View File
@@ -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));
});
}
+393
View File
@@ -0,0 +1,393 @@
#include "testing.h"
#include "../tools/server/server-common.h"
#include <cstdlib>
#include <string>
#include <thread>
#include <vector>
// unit tests for server internal components, no model required
// NOTE: end-to-end tests live in tools/server/tests
// see skills/code-review/SKILL.md for more
//
// server_tokens (text only, has_mtmd = false)
//
static void test_server_tokens_basic(testing & t) {
server_tokens st(llama_tokens{1, 2, 3}, false);
t.assert_equal("size", (size_t) 3, st.size());
t.assert_true("not empty", !st.empty());
t.assert_equal("operator[]", 2, st[1]);
st.push_back(4);
t.assert_equal("push_back", (size_t) 4, st.size());
t.assert_true("get_tokens", st.get_tokens() == llama_tokens({1, 2, 3, 4}));
st.insert({5, 6});
t.assert_true("insert appends", st.get_tokens() == llama_tokens({1, 2, 3, 4, 5, 6}));
st.set_token(0, 9);
t.assert_equal("set_token", 9, st[0]);
bool thrown = false;
try {
st.push_back(LLAMA_TOKEN_NULL);
} catch (const std::exception &) {
thrown = true;
}
t.assert_true("push_back(LLAMA_TOKEN_NULL) throws", thrown);
t.assert_equal("size unchanged after throw", (size_t) 6, st.size());
st.clear();
t.assert_true("clear", st.empty());
}
static void test_server_tokens_pos(testing & t) {
server_tokens st(llama_tokens{1, 2, 3, 4, 5}, false);
t.assert_equal("pos_next()", (llama_pos) 5, st.pos_next());
t.assert_equal("pos_next(0)", (llama_pos) 0, st.pos_next(0));
t.assert_equal("pos_next(3)", (llama_pos) 3, st.pos_next(3));
t.assert_equal("size_up_to_pos(0)", (size_t) 0, st.size_up_to_pos(0));
t.assert_equal("size_up_to_pos(3)", (size_t) 3, st.size_up_to_pos(3));
t.assert_equal("size_up_to_pos clamps", (size_t) 5, st.size_up_to_pos(100));
}
static void test_server_tokens_keep_first(testing & t) {
server_tokens st(llama_tokens{1, 2, 3, 4, 5}, false);
st.keep_first(5);
t.assert_equal("keep all", (size_t) 5, st.size());
st.keep_first(2);
t.assert_true("keep 2", st.get_tokens() == llama_tokens({1, 2}));
st.keep_first(0);
t.assert_true("keep 0", st.empty());
}
static void test_server_tokens_common_prefix(testing & t) {
server_tokens a(llama_tokens{1, 2, 3, 4}, false);
server_tokens empty(llama_tokens{}, false);
t.assert_equal("both empty", (size_t) 0, empty.get_common_prefix(empty));
t.assert_equal("one empty", (size_t) 0, a.get_common_prefix(empty));
t.assert_equal("identical", (size_t) 4, a.get_common_prefix(a));
t.assert_equal("diverge at 2", (size_t) 2, a.get_common_prefix(server_tokens(llama_tokens{1, 2, 9, 4}, false)));
t.assert_equal("diverge at 0", (size_t) 0, a.get_common_prefix(server_tokens(llama_tokens{9, 2, 3, 4}, false)));
t.assert_equal("b is prefix", (size_t) 2, a.get_common_prefix(server_tokens(llama_tokens{1, 2}, false)));
t.assert_equal("a is prefix", (size_t) 4, a.get_common_prefix(server_tokens(llama_tokens{1, 2, 3, 4, 5, 6}, false)));
}
static void test_server_tokens_serialize(testing & t) {
// same packing as slot save/restore in server-context.cpp
auto pack = [](const std::vector<char> & data) {
llama_tokens packed(data.size() / sizeof(llama_token));
memcpy(packed.data(), data.data(), data.size());
return packed;
};
t.test("roundtrip", [&](testing & t) {
server_tokens st(llama_tokens{10, 20, 30}, false);
std::vector<char> data = st.serialize();
t.assert_equal("aligned to llama_token", (size_t) 0, data.size() % sizeof(llama_token));
server_tokens restored = server_tokens::deserialize(pack(data), false);
t.assert_true("tokens", restored.get_tokens() == st.get_tokens());
});
t.test("roundtrip empty", [&](testing & t) {
server_tokens st(llama_tokens{}, false);
server_tokens restored = server_tokens::deserialize(pack(st.serialize()), false);
t.assert_true("empty", restored.empty());
});
t.test("legacy plain token list", [&](testing & t) {
server_tokens restored = server_tokens::deserialize(llama_tokens{7, 8, 9}, false);
t.assert_true("tokens", restored.get_tokens() == llama_tokens({7, 8, 9}));
});
auto expect_throw = [](testing & t, const std::string & msg, const llama_tokens & packed) {
bool thrown = false;
try {
server_tokens::deserialize(packed, false);
} catch (const std::exception &) {
thrown = true;
}
t.assert_true(msg, thrown);
};
t.test("corrupted", [&](testing & t) {
llama_tokens good = pack(server_tokens(llama_tokens{10, 20, 30}, false).serialize());
llama_tokens bad_version = good;
bad_version[1] = 999;
expect_throw(t, "bad version", bad_version);
llama_tokens truncated = good;
truncated.pop_back();
expect_throw(t, "truncated", truncated);
llama_tokens trailing = good;
trailing.push_back(42);
expect_throw(t, "trailing data", trailing);
llama_tokens huge_count = good;
huge_count[2] = 0x7fffffff;
expect_throw(t, "huge token count", huge_count);
expect_throw(t, "marker only", llama_tokens{LLAMA_TOKEN_NULL});
});
}
static void test_server_tokens_clone(testing & t) {
server_tokens st(llama_tokens{1, 2, 3}, false);
server_tokens cl = st.clone();
cl.push_back(4);
t.assert_equal("original untouched", (size_t) 3, st.size());
t.assert_equal("clone grew", (size_t) 4, cl.size());
t.assert_true("get_text_tokens", cl.get_text_tokens() == llama_tokens({1, 2, 3, 4}));
}
static void test_server_tokens(testing & t) {
t.test("basic", test_server_tokens_basic);
t.test("pos", test_server_tokens_pos);
t.test("keep_first", test_server_tokens_keep_first);
t.test("common_prefix", test_server_tokens_common_prefix);
t.test("serialize", test_server_tokens_serialize);
t.test("clone", test_server_tokens_clone);
}
//
// string / json helpers
//
static void test_utf8(testing & t) {
t.test("validate_utf8", [](testing & t) {
t.assert_equal("empty", (size_t) 0, validate_utf8(""));
t.assert_equal("ascii", (size_t) 3, validate_utf8("abc"));
t.assert_equal("full 2-byte", (size_t) 3, validate_utf8("a\xC3\xA9"));
t.assert_equal("full 3-byte", (size_t) 4, validate_utf8("a\xE2\x82\xAC"));
t.assert_equal("full 4-byte", (size_t) 5, validate_utf8("a\xF0\x9F\x98\x80"));
t.assert_equal("cut 2-byte (1/2)", (size_t) 1, validate_utf8("a\xC3"));
t.assert_equal("cut 3-byte (1/3)", (size_t) 1, validate_utf8("a\xE2"));
t.assert_equal("cut 3-byte (2/3)", (size_t) 1, validate_utf8("a\xE2\x82"));
t.assert_equal("cut 4-byte (1/4)", (size_t) 1, validate_utf8("a\xF0"));
t.assert_equal("cut 4-byte (2/4)", (size_t) 1, validate_utf8("a\xF0\x9F"));
t.assert_equal("cut 4-byte (3/4)", (size_t) 1, validate_utf8("a\xF0\x9F\x98"));
t.assert_equal("cut only", (size_t) 0, validate_utf8("\xF0\x9F\x98"));
t.assert_equal("full then cut", (size_t) 4, validate_utf8("\xF0\x9F\x98\x80\xC3"));
});
t.test("is_valid_utf8", [](testing & t) {
t.assert_true("empty", is_valid_utf8(""));
t.assert_true("ascii", is_valid_utf8("hello"));
t.assert_true("multi-byte", is_valid_utf8("\xC3\xA9\xE2\x82\xAC\xF0\x9F\x98\x80"));
t.assert_true("truncated", !is_valid_utf8("\xE2\x82"));
t.assert_true("bad cont. byte", !is_valid_utf8("\xC3\x41"));
t.assert_true("lone cont. byte", !is_valid_utf8("\x80"));
t.assert_true("invalid lead", !is_valid_utf8("\xFF"));
});
}
static void test_sse(testing & t) {
t.test("oai", [](testing & t) {
t.assert_equal("single", std::string("data: {\"a\":1}\n\n"), format_oai_sse(json{{"a", 1}}));
t.assert_equal("array", std::string("data: {\"a\":1}\n\ndata: {\"b\":2}\n\n"),
format_oai_sse(json::array({json{{"a", 1}}, json{{"b", 2}}})));
t.assert_equal("invalid utf8 replaced", std::string("data: \"x\xEF\xBF\xBD\"\n\n"), format_oai_sse(json("x\xC3")));
});
t.test("oai_resp", [](testing & t) {
json ev = {{"event", "response.created"}, {"data", {{"id", "r1"}}}};
t.assert_equal("single", std::string("event: response.created\ndata: {\"id\":\"r1\"}\n\n"), format_oai_resp_sse(ev));
t.assert_equal("array", std::string("event: response.created\ndata: {\"id\":\"r1\"}\n\nevent: response.created\ndata: {\"id\":\"r1\"}\n\n"),
format_oai_resp_sse(json::array({ev, ev})));
});
t.test("anthropic", [](testing & t) {
json ev = {{"event", "message_start"}, {"data", {{"type", "message_start"}}}};
t.assert_equal("with event", std::string("event: message_start\ndata: {\"type\":\"message_start\"}\n\n"), format_anthropic_sse(ev));
t.assert_equal("data only", std::string("data: {\"type\":\"ping\"}\n\n"), format_anthropic_sse(json{{"type", "ping"}}));
});
}
static void test_json_helpers(testing & t) {
t.test("json_is_array_of_numbers", [](testing & t) {
t.assert_true("ints", json_is_array_of_numbers(json::array({1, 2, 3})));
t.assert_true("empty array", json_is_array_of_numbers(json::array()));
t.assert_true("float", !json_is_array_of_numbers(json::array({1, 2.5})));
t.assert_true("string", !json_is_array_of_numbers(json::array({1, "a"})));
t.assert_true("not an array", !json_is_array_of_numbers(json(1)));
});
t.test("json_is_array_of_mixed_numbers_strings", [](testing & t) {
t.assert_true("mixed", json_is_array_of_mixed_numbers_strings(json::array({"a", 1})));
t.assert_true("numbers only", !json_is_array_of_mixed_numbers_strings(json::array({1, 2})));
t.assert_true("strings only", !json_is_array_of_mixed_numbers_strings(json::array({"a", "b"})));
t.assert_true("empty", !json_is_array_of_mixed_numbers_strings(json::array()));
});
t.test("json_is_array_and_contains_numbers", [](testing & t) {
t.assert_true("contains", json_is_array_and_contains_numbers(json::array({"a", 1})));
t.assert_true("strings only", !json_is_array_and_contains_numbers(json::array({"a"})));
t.assert_true("not an array", !json_is_array_and_contains_numbers(json(1)));
});
t.test("json_get_nested_values", [](testing & t) {
json js = {{"a", {{"b", {{"c", 1}}}}}, {"x", 2}};
json res = json_get_nested_values({"a/b/c", "x", "a/b", "a/missing", "x/y"}, js);
t.assert_equal("nested", 1, res.at("a/b/c").get<int>());
t.assert_equal("top", 2, res.at("x").get<int>());
t.assert_true("object", res.at("a/b") == json{{"c", 1}});
t.assert_true("missing", !res.contains("a/missing"));
t.assert_true("not obj", !res.contains("x/y"));
t.assert_equal("count", (size_t) 3, res.size());
});
}
static void test_lora_helpers(testing & t) {
// only pointer identity is used, never dereferenced
auto * p0 = reinterpret_cast<llama_adapter_lora *>(0x10);
auto * p1 = reinterpret_cast<llama_adapter_lora *>(0x20);
auto make = [](llama_adapter_lora * ptr, float scale) {
common_adapter_lora_info info;
info.scale = scale;
info.ptr = ptr;
return info;
};
t.test("parse_lora_request", [](testing & t) {
auto res = parse_lora_request(json::array({
json{{"id", 0}, {"scale", 0.5}},
json{{"id", 2}, {"scale", 1.0}},
json{{"id", 0}, {"scale", 0.25}}, // last one wins
}));
t.assert_equal("count", (size_t) 2, res.size());
t.assert_equal("id 0", 0.25f, res.at(0));
t.assert_equal("id 2", 1.0f, res.at(2));
auto defaults = parse_lora_request(json::array({json::object()}));
t.assert_equal("default id/scale", 0.0f, defaults.at(-1));
});
t.test("are_lora_equal", [&](testing & t) {
t.assert_true("both empty", are_lora_equal({}, {}));
t.assert_true("same", are_lora_equal({make(p0, 1.0f)}, {make(p0, 1.0f)}));
t.assert_true("diff scale", !are_lora_equal({make(p0, 1.0f)}, {make(p0, 0.5f)}));
t.assert_true("diff ptr", !are_lora_equal({make(p0, 1.0f)}, {make(p1, 1.0f)}));
t.assert_true("diff size", !are_lora_equal({make(p0, 1.0f)}, {}));
});
t.test("lora_get_enabled_ids", [&](testing & t) {
auto ids = lora_get_enabled_ids({make(p0, 0.0f), make(p1, 0.5f), make(p0, -1.0f), make(p1, 2.0f)});
t.assert_true("ids", ids == std::vector<size_t>({1, 3}));
});
}
//
// server_pipe
//
static void test_server_pipe(testing & t) {
auto stop_now = []() { return true; };
t.test("drain then eof", [](testing & t) {
server_pipe<int> p;
t.assert_true("write 1", p.write(1));
t.assert_true("write 2", p.write(2));
p.close_write();
int v = 0;
t.assert_true("read 1", p.read(v, nullptr) && v == 1);
t.assert_true("read 2", p.read(v, nullptr) && v == 2);
t.assert_true("eof", !p.read(v, nullptr));
});
t.test("broken pipe", [](testing & t) {
server_pipe<int> p;
p.close_read();
t.assert_true("write fails", !p.write(1));
});
t.test("max_size drops oldest", [](testing & t) {
server_pipe<int> p;
p.max_size = 2;
p.write(1);
p.write(2);
p.write(3);
p.close_write();
int v = 0;
t.assert_true("read 2", p.read(v, nullptr) && v == 2);
t.assert_true("read 3", p.read(v, nullptr) && v == 3);
t.assert_true("eof", !p.read(v, nullptr));
});
t.test("queued data wins over should_stop", [&](testing & t) {
server_pipe<int> p;
p.write(1);
int v = 0;
t.assert_true("read", p.read(v, stop_now) && v == 1);
});
t.test("should_stop closes the pipe", [&](testing & t) {
server_pipe<int> p;
int v = 0;
t.assert_true("read stopped", !p.read(v, stop_now));
t.assert_true("write fails", !p.write(1));
});
t.test("should_stop as deadline keeps the pipe open", [&](testing & t) {
server_pipe<int> p;
int v = 0;
t.assert_true("read stopped", !p.read(v, stop_now, /* close_on_stop */ false));
t.assert_true("write ok", p.write(1));
t.assert_true("read again", p.read(v, nullptr) && v == 1);
});
t.test("cross thread", [](testing & t) {
constexpr int n = 1000;
server_pipe<int> p;
std::thread producer([&]() {
for (int i = 0; i < n; i++) {
p.write(int(i));
}
p.close_write();
});
int v = 0;
int count = 0;
bool in_order = true;
while (p.read(v, nullptr)) {
in_order &= v == count;
count++;
}
producer.join();
t.assert_equal("count", n, count);
t.assert_true("in order", in_order);
});
}
int main(int argc, char ** argv) {
testing t;
const char * verbose = getenv("LLAMA_TEST_VERBOSE");
if (verbose) {
t.verbose = std::string(verbose) == "1";
}
if (argc > 1) {
t.set_filter(argv[1]);
}
t.test("server_tokens", test_server_tokens);
t.test("utf8", test_utf8);
t.test("sse", test_sse);
t.test("json_helpers", test_json_helpers);
t.test("lora_helpers", test_lora_helpers);
t.test("server_pipe", test_server_pipe);
return t.summary();
}
+7
View File
@@ -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
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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];
+1 -3
View File
@@ -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);
+1 -7
View File
@@ -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;
+3 -10
View File
@@ -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) {
+2 -8
View File
@@ -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);
+3 -6
View File
@@ -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;
}
+2 -4
View File
@@ -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]
+3
View File
@@ -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];
+1 -3
View File
@@ -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);
+1 -8
View File
@@ -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);
+8 -1
View File
@@ -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);
+1 -8
View File
@@ -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
View File
@@ -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);
}
+31
View File
@@ -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
View File
@@ -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");
}
+18 -1
View File
@@ -113,8 +113,25 @@ $ GGML_RPC_NO_RDMA=1 bin/ggml-rpc-server
### Troubleshooting
Use the `GGML_RPC_DEBUG` environment variable to enable debug messages from `ggml-rpc-server`:
The `GGML_RPC_DEBUG` environment variable controls the verbosity of the logs emitted by the RPC backend.
It can be set on the server, on the client (e.g. `llama-cli`), or both. Larger values produce more detailed output:
- unset / `0` - disabled (only warnings and errors are printed)
- `1` - high-level events: connections, handshake, buffer allocations, graph computes, cache evictions
- `2` - per-command trace: every RPC message sent/received, tensor transfers, cache hits/misses
- `3` - fine detail: per-command timings, graph node dumps, transport byte counts
Non-numeric values are treated as `1`.
```bash
$ GGML_RPC_DEBUG=1 bin/ggml-rpc-server
```
> [!NOTE]
> The debug levels are emitted as `ggml` debug records, which `llama.cpp` applications drop unless the
> global verbosity threshold is raised. On the client, pass `-lv 5` together with `GGML_RPC_DEBUG`:
```bash
$ GGML_RPC_DEBUG=2 bin/llama-cli -hf ggml-org/gemma-3-1b-it-GGUF -ngl 99 --rpc 192.168.88.10:50052 -lv 5
```
+1 -1
View File
@@ -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) |
+5
View File
@@ -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()