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