mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-07 16:37:57 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e71b80510c | ||
|
|
ccc3646c63 | ||
|
|
c0b1871bc7 | ||
|
|
160bd031b2 | ||
|
|
dbeb37548e | ||
|
|
7a333e7240 | ||
|
|
0c963452ea | ||
|
|
4735997382 | ||
|
|
d23c47f2a9 | ||
|
|
73ab7599b5 | ||
|
|
0cae43063c | ||
|
|
1173700b9c | ||
|
|
5202104b59 | ||
|
|
9a7570587c | ||
|
|
b74f590eaf |
@@ -31,7 +31,7 @@
|
||||
]
|
||||
&& blas.meta.available,
|
||||
useCuda ? config.cudaSupport,
|
||||
useMetalKit ? stdenv.isAarch64 && stdenv.isDarwin,
|
||||
useMetalKit ? stdenv.hostPlatform.isAarch64 && stdenv.hostPlatform.isDarwin,
|
||||
# Increases the runtime closure size by ~700M
|
||||
useMpi ? false,
|
||||
useRocm ? config.rocmSupport,
|
||||
@@ -92,7 +92,7 @@ let
|
||||
|
||||
cudaBuildInputs = with cudaPackages; [
|
||||
cuda_cudart
|
||||
cuda_cccl # <nv/target>
|
||||
cccl # <nv/target>
|
||||
libcublas
|
||||
];
|
||||
|
||||
@@ -166,7 +166,7 @@ effectiveStdenv.mkDerivation (finalAttrs: {
|
||||
# `xcrun` is used find the path of the Metal compiler, which is varible
|
||||
# and not on $PATH
|
||||
# see https://github.com/ggml-org/llama.cpp/pull/6118 for discussion
|
||||
__noChroot = effectiveStdenv.isDarwin && useMetalKit && precompileMetalShaders;
|
||||
__noChroot = effectiveStdenv.hostPlatform.isDarwin && useMetalKit && precompileMetalShaders;
|
||||
|
||||
nativeBuildInputs =
|
||||
[
|
||||
@@ -181,10 +181,10 @@ effectiveStdenv.mkDerivation (finalAttrs: {
|
||||
autoAddDriverRunpath
|
||||
]
|
||||
++ optionals (effectiveStdenv.hostPlatform.isGnu && enableStatic) [ glibc.static ]
|
||||
++ optionals (effectiveStdenv.isDarwin && useMetalKit && precompileMetalShaders) [ xcrunHost ];
|
||||
++ optionals (effectiveStdenv.hostPlatform.isDarwin && useMetalKit && precompileMetalShaders) [ xcrunHost ];
|
||||
|
||||
buildInputs =
|
||||
optionals effectiveStdenv.isDarwin darwinBuildInputs
|
||||
optionals effectiveStdenv.hostPlatform.isDarwin darwinBuildInputs
|
||||
++ optionals useCuda cudaBuildInputs
|
||||
++ optionals useMpi [ mpi ]
|
||||
++ optionals useRocm rocmBuildInputs
|
||||
@@ -245,7 +245,7 @@ effectiveStdenv.mkDerivation (finalAttrs: {
|
||||
|
||||
# Configurations that are known to result in build failures. Can be
|
||||
# overridden by importing Nixpkgs with `allowBroken = true`.
|
||||
broken = (useMetalKit && !effectiveStdenv.isDarwin);
|
||||
broken = (useMetalKit && !effectiveStdenv.hostPlatform.isDarwin);
|
||||
|
||||
description = "Inference of LLaMA model in pure C/C++${descriptionSuffix}";
|
||||
homepage = "https://github.com/ggml-org/llama.cpp/";
|
||||
|
||||
@@ -117,6 +117,7 @@ caps caps_get(jinja::program & prog) {
|
||||
|
||||
JJ_DEBUG("%s\n", ">>> Running capability check: typed content");
|
||||
|
||||
bool checks_for_string = false;
|
||||
static const std::string content_marker = "STRING_MARKER";
|
||||
|
||||
// case: typed content support
|
||||
@@ -136,6 +137,10 @@ caps caps_get(jinja::program & prog) {
|
||||
[&](context &, bool success, value & messages, value &, const std::string & rendered) {
|
||||
auto & content = messages->at(0)->at("content");
|
||||
caps_print_stats(content, "messages[0].content");
|
||||
if (has_op(content, "test_is_string")) {
|
||||
// checked if content is string
|
||||
checks_for_string = true;
|
||||
}
|
||||
bool used_as_array = has_op(content, "selectattr") || has_op(content, "array_access");
|
||||
if (used_as_array) {
|
||||
// accessed as an array
|
||||
@@ -151,6 +156,33 @@ caps caps_get(jinja::program & prog) {
|
||||
}
|
||||
);
|
||||
|
||||
if (checks_for_string) {
|
||||
caps_try_execute(
|
||||
prog,
|
||||
[&]() {
|
||||
// messages
|
||||
return json::array({
|
||||
{
|
||||
{"role", "user"},
|
||||
{"content", json::array({
|
||||
})}
|
||||
}
|
||||
});
|
||||
},
|
||||
nullptr, // ctx_fn
|
||||
nullptr, // tools_fn
|
||||
[&](context &, bool success, value & messages, value &, const std::string &) {
|
||||
auto & content = messages->at(0)->at("content");
|
||||
caps_print_stats(content, "messages[0].content");
|
||||
bool used_as_array = has_op(content, "selectattr") || has_op(content, "array_access");
|
||||
if (used_as_array && success) {
|
||||
// accessed as an array
|
||||
result.supports_typed_content = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
JJ_DEBUG("%s\n", ">>> Running capability check: system prompt");
|
||||
|
||||
// case: system prompt support
|
||||
|
||||
@@ -412,12 +412,18 @@ value test_expression::execute_impl(context & ctx) {
|
||||
throw std::runtime_error("Invalid test expression");
|
||||
}
|
||||
|
||||
auto it = builtins.find("test_is_" + test_id);
|
||||
JJ_DEBUG("Test expression %s '%s' %s (using function 'test_is_%s')", operand->type().c_str(), test_id.c_str(), negate ? "(negate)" : "", test_id.c_str());
|
||||
const std::string test_name = "test_is_" + test_id;
|
||||
auto it = builtins.find(test_name);
|
||||
JJ_DEBUG("Test expression %s '%s' %s (using function '%s')", operand->type().c_str(), test_id.c_str(), negate ? "(negate)" : "", test_name.c_str());
|
||||
if (it == builtins.end()) {
|
||||
throw std::runtime_error("Unknown test '" + test_id + "'");
|
||||
}
|
||||
|
||||
if (ctx.is_get_stats) {
|
||||
value_t::stats_t::mark_used(input);
|
||||
input->stats.ops.insert(test_name);
|
||||
}
|
||||
|
||||
auto res = it->second(args);
|
||||
|
||||
if (negate) {
|
||||
|
||||
+22
-89
@@ -9,20 +9,6 @@ from .base import ModelBase, gguf, logger
|
||||
from .deepseek import DeepseekV2Model
|
||||
|
||||
|
||||
def split_kv_b_proj(weight: torch.Tensor, n_head: int, qk_nope: int, v_head_dim: int):
|
||||
"""Split kv_b_proj into k_b (transposed) and v_b, matching DeepSeek MLA absorption.
|
||||
|
||||
weight: [n_head*(qk_nope+v_head_dim), kv_lora_rank].
|
||||
Returns (k_b, v_b): k_b [n_head, kv_lora_rank, qk_nope], v_b [n_head, v_head_dim, kv_lora_rank].
|
||||
"""
|
||||
kv_lora = weight.shape[-1]
|
||||
assert weight.shape[0] == n_head * (qk_nope + v_head_dim)
|
||||
kv_b = weight.view(n_head, qk_nope + v_head_dim, kv_lora)
|
||||
k_b, v_b = torch.split(kv_b, [qk_nope, v_head_dim], dim=1)
|
||||
k_b = k_b.transpose(1, 2).contiguous() # [n_head, kv_lora, qk_nope]
|
||||
return k_b, v_b.contiguous()
|
||||
|
||||
|
||||
def split_gate_up(weight: torch.Tensor, moe_intermediate_size: int):
|
||||
"""Split a fused stacked gate_up expert tensor into (gate, up).
|
||||
|
||||
@@ -36,6 +22,7 @@ def split_gate_up(weight: torch.Tensor, moe_intermediate_size: int):
|
||||
|
||||
|
||||
@ModelBase.register("HYV4ForCausalLM")
|
||||
@ModelBase.example("tencent/Hy4-preview")
|
||||
class HYV4Model(DeepseekV2Model):
|
||||
"""HY_V4: DeepSeek-V3 style MLA + MoE with iHC, a gated MLA output and a learnable sink.
|
||||
|
||||
@@ -54,6 +41,8 @@ class HYV4Model(DeepseekV2Model):
|
||||
|
||||
model_arch = gguf.MODEL_ARCH.HY_V4
|
||||
|
||||
merge_expert = False
|
||||
|
||||
# tensors a "full" indexer layer must carry
|
||||
INDEXER_SUFFIXES = frozenset({
|
||||
"self_attn.indexer.wq_b.weight",
|
||||
@@ -186,6 +175,10 @@ class HYV4Model(DeepseekV2Model):
|
||||
)
|
||||
|
||||
def prepare_tensors(self):
|
||||
# Hy4-preview for some reason has num_key_value_heads equal to 8, so override it here
|
||||
# without this conversion/deepseek.py fails on assert
|
||||
self.hparams["num_key_value_heads"] = self.hparams["num_attention_heads"]
|
||||
|
||||
# validate before the base materializes tensors, so a mismatch fails early
|
||||
is_full = self.indexer_is_full()
|
||||
if is_full is not None:
|
||||
@@ -227,85 +220,25 @@ class HYV4Model(DeepseekV2Model):
|
||||
|
||||
def modify_tensors(self, data_torch: torch.Tensor, name: str, bid: int | None) -> Iterable[tuple[str, torch.Tensor]]:
|
||||
hparams = self.hparams
|
||||
n_head = hparams["num_attention_heads"]
|
||||
qk_nope = hparams["qk_nope_head_dim"]
|
||||
v_head_dim = hparams["v_head_dim"]
|
||||
moe_inter = hparams["moe_intermediate_size"]
|
||||
|
||||
tn = self.format_tensor_name
|
||||
|
||||
# ---- global (non per-layer) ----
|
||||
if name == "model.embed_tokens.weight":
|
||||
return [(tn(gguf.MODEL_TENSOR.TOKEN_EMBD), data_torch)]
|
||||
if name == "model.norm.weight":
|
||||
return [(tn(gguf.MODEL_TENSOR.OUTPUT_NORM), data_torch)]
|
||||
if name == "lm_head.weight":
|
||||
return [(tn(gguf.MODEL_TENSOR.OUTPUT), data_torch)]
|
||||
if name == "model.hc_head.hc_head_fn":
|
||||
return [(tn(gguf.MODEL_TENSOR.HC_HEAD_FN), data_torch)]
|
||||
if name == "model.hc_head.hc_head_base":
|
||||
return [(tn(gguf.MODEL_TENSOR.HC_HEAD_BASE), data_torch)]
|
||||
if name == "model.hc_head.hc_head_scale":
|
||||
return [(tn(gguf.MODEL_TENSOR.HC_HEAD_SCALE), data_torch)]
|
||||
|
||||
assert bid is not None, f"expected a per-layer tensor, got {name!r}"
|
||||
|
||||
# ---- per-layer, keyed by suffix after 'model.layers.{bid}.' ----
|
||||
suffix = name.split(f"model.layers.{bid}.", 1)[-1]
|
||||
|
||||
# note: q_b_proj and kv_a_proj_with_mqa are mapped straight through (no RoPE permute),
|
||||
# the graph rotates consecutive pairs so the rows need no reordering
|
||||
simple = {
|
||||
"input_layernorm.weight": (gguf.MODEL_TENSOR.ATTN_NORM, ".weight"),
|
||||
"post_attention_layernorm.weight": (gguf.MODEL_TENSOR.FFN_NORM, ".weight"),
|
||||
"self_attn.q_a_proj.weight": (gguf.MODEL_TENSOR.ATTN_Q_A, ".weight"),
|
||||
"self_attn.q_a_layernorm.weight": (gguf.MODEL_TENSOR.ATTN_Q_A_NORM, ".weight"),
|
||||
"self_attn.q_b_proj.weight": (gguf.MODEL_TENSOR.ATTN_Q_B, ".weight"),
|
||||
"self_attn.kv_a_proj_with_mqa.weight": (gguf.MODEL_TENSOR.ATTN_KV_A_MQA, ".weight"),
|
||||
"self_attn.kv_a_layernorm.weight": (gguf.MODEL_TENSOR.ATTN_KV_A_NORM, ".weight"),
|
||||
"self_attn.o_proj.weight": (gguf.MODEL_TENSOR.ATTN_OUT, ".weight"),
|
||||
"self_attn.linear_gate.weight": (gguf.MODEL_TENSOR.ATTN_GATE, ".weight"),
|
||||
"self_attn.learnable_sink_param": (gguf.MODEL_TENSOR.ATTN_SINKS, ".weight"),
|
||||
"self_attn.indexer.wq_b.weight": (gguf.MODEL_TENSOR.INDEXER_ATTN_Q_B, ".weight"),
|
||||
"self_attn.indexer.wk.weight": (gguf.MODEL_TENSOR.INDEXER_ATTN_K, ".weight"),
|
||||
"self_attn.indexer.k_norm.weight": (gguf.MODEL_TENSOR.INDEXER_K_NORM, ".weight"),
|
||||
"self_attn.indexer.k_norm.bias": (gguf.MODEL_TENSOR.INDEXER_K_NORM, ".bias"),
|
||||
"self_attn.indexer.weights_proj.weight": (gguf.MODEL_TENSOR.INDEXER_PROJ, ".weight"),
|
||||
"hc_attn_layer.hc_pre.hc_fn": (gguf.MODEL_TENSOR.HC_ATTN_FN, ".weight"),
|
||||
"hc_attn_layer.hc_pre.hc_base": (gguf.MODEL_TENSOR.HC_ATTN_BASE, ".weight"),
|
||||
"hc_attn_layer.hc_pre.hc_scale": (gguf.MODEL_TENSOR.HC_ATTN_SCALE, ".weight"),
|
||||
"hc_mlp_layer.hc_pre.hc_fn": (gguf.MODEL_TENSOR.HC_FFN_FN, ".weight"),
|
||||
"hc_mlp_layer.hc_pre.hc_base": (gguf.MODEL_TENSOR.HC_FFN_BASE, ".weight"),
|
||||
"hc_mlp_layer.hc_pre.hc_scale": (gguf.MODEL_TENSOR.HC_FFN_SCALE, ".weight"),
|
||||
"mlp.gate.weight": (gguf.MODEL_TENSOR.FFN_GATE_INP, ".weight"),
|
||||
"mlp.gate.e_score_correction.bias":(gguf.MODEL_TENSOR.FFN_EXP_PROBS_B, ".bias"),
|
||||
"mlp.gate_proj.weight": (gguf.MODEL_TENSOR.FFN_GATE, ".weight"),
|
||||
"mlp.up_proj.weight": (gguf.MODEL_TENSOR.FFN_UP, ".weight"),
|
||||
"mlp.down_proj.weight": (gguf.MODEL_TENSOR.FFN_DOWN, ".weight"),
|
||||
"mlp.shared_experts.gate_proj.weight": (gguf.MODEL_TENSOR.FFN_GATE_SHEXP, ".weight"),
|
||||
"mlp.shared_experts.up_proj.weight": (gguf.MODEL_TENSOR.FFN_UP_SHEXP, ".weight"),
|
||||
"mlp.shared_experts.down_proj.weight": (gguf.MODEL_TENSOR.FFN_DOWN_SHEXP, ".weight"),
|
||||
}
|
||||
if suffix in simple:
|
||||
key, sfx = simple[suffix]
|
||||
return [(tn(key, bid, sfx), data_torch)]
|
||||
|
||||
# kv_b_proj: split into k_b (transposed) and v_b
|
||||
if suffix == "self_attn.kv_b_proj.weight":
|
||||
k_b, v_b = split_kv_b_proj(data_torch, n_head, qk_nope, v_head_dim)
|
||||
return [
|
||||
(tn(gguf.MODEL_TENSOR.ATTN_K_B, bid), k_b),
|
||||
(tn(gguf.MODEL_TENSOR.ATTN_V_B, bid), v_b),
|
||||
]
|
||||
|
||||
# fused stacked experts: split gate_up into gate/up
|
||||
if suffix == "mlp.experts.gate_up_proj":
|
||||
if name.endswith("mlp.experts.gate_up_proj"):
|
||||
gate, up = split_gate_up(data_torch, moe_inter)
|
||||
return [
|
||||
(tn(gguf.MODEL_TENSOR.FFN_GATE_EXP, bid), gate),
|
||||
(tn(gguf.MODEL_TENSOR.FFN_UP_EXP, bid), up),
|
||||
]
|
||||
if suffix == "mlp.experts.down_proj":
|
||||
return [(tn(gguf.MODEL_TENSOR.FFN_DOWN_EXP, bid), data_torch)]
|
||||
yield from super().modify_tensors(gate, tn(gguf.MODEL_TENSOR.FFN_GATE_EXP, bid), bid)
|
||||
yield from super().modify_tensors(up, tn(gguf.MODEL_TENSOR.FFN_UP_EXP, bid), bid)
|
||||
return
|
||||
|
||||
raise ValueError(f"Unsupported HY_V4 tensor {name!r} (suffix {suffix!r})")
|
||||
# add .weight suffixes
|
||||
if name.endswith("mlp.experts.down_proj") or name.endswith(".self_attn.learnable_sink_param"):
|
||||
name += ".weight"
|
||||
|
||||
if re.search(r"\.hc_head\.hc_head_(?:fn|base|scale)$", name):
|
||||
name += ".weight"
|
||||
|
||||
if re.search(r"\.hc_(?:attn|mlp)_layer\.hc_pre\.hc_(?:fn|base|scale)$", name):
|
||||
name += ".weight"
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
@@ -379,6 +379,13 @@ class Qwen3NextModel(_QwenMtpMixin, Qwen2MoeModel):
|
||||
self.gguf_writer.add_ssm_group_count(self.hparams["linear_num_key_heads"])
|
||||
self.gguf_writer.add_ssm_time_step_rank(self.hparams["linear_num_value_heads"])
|
||||
self.gguf_writer.add_ssm_inner_size(self.hparams["linear_value_head_dim"] * self.hparams["linear_num_value_heads"])
|
||||
if (layer_types := self.hparams.get("layer_types")) is not None:
|
||||
n_layer = self.hparams["num_hidden_layers"]
|
||||
if len(layer_types) != n_layer:
|
||||
raise ValueError(f"layer_types has {len(layer_types)} entries, expected num_hidden_layers ({n_layer})")
|
||||
recurrent = [t == "linear_attention" for t in layer_types]
|
||||
recurrent += [False] * (self.block_count - n_layer)
|
||||
self.gguf_writer.add_recurrent_layers(recurrent)
|
||||
self.gguf_writer.add_full_attention_interval(self.hparams.get("full_attention_interval", 4))
|
||||
if (rope_dim := self.hparams.get("head_dim")) is None:
|
||||
rope_dim = self.hparams["hidden_size"] // self.hparams["num_attention_heads"]
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
#include <cstdio>
|
||||
|
||||
int main(void) {
|
||||
printf("[test-cmake] version: %s, build: %d (%s)\n",
|
||||
printf("[test-cmake] llama.cpp version: %s, build: %d (%s)\n",
|
||||
llama_version(), LLAMA_BUILD_NUMBER, LLAMA_BUILD_COMMIT);
|
||||
printf("[test-cmake] ggml version: %s, commit: %s\n", ggml_version(), ggml_commit());
|
||||
printf("[test-cmake] Initializing backend...\n");
|
||||
llama_backend_init();
|
||||
printf("[test-cmake] Backend initialized.\n");
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
}:
|
||||
{
|
||||
# For standardised reproducible formatting with `nix fmt`
|
||||
formatter = pkgs.nixfmt-rfc-style;
|
||||
formatter = pkgs.nixfmt;
|
||||
|
||||
# Unlike `.#packages`, legacyPackages may contain values of
|
||||
# arbitrary types (including nested attrsets) and may even throw
|
||||
@@ -156,7 +156,7 @@
|
||||
windows = config.legacyPackages.llamaPackagesWindows.llama-cpp;
|
||||
python-scripts = config.legacyPackages.llamaPackages.python-scripts;
|
||||
}
|
||||
// lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||
// lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
|
||||
cuda = config.legacyPackages.llamaPackagesCuda.llama-cpp;
|
||||
|
||||
mpi-cpu = config.packages.default.override { useMpi = true; };
|
||||
|
||||
@@ -69,6 +69,8 @@
|
||||
#define GGML_CUDA_CC_GCN4 (GGML_CUDA_CC_OFFSET_AMD + 0x803) // Tonga, Fiji, Polaris, minimum for fast fp16
|
||||
#define GGML_CUDA_CC_VEGA (GGML_CUDA_CC_OFFSET_AMD + 0x900) // Vega56/64, minimum for fp16 dual issue
|
||||
#define GGML_CUDA_CC_VEGA20 (GGML_CUDA_CC_OFFSET_AMD + 0x906) // MI50/Radeon VII, minimum for dp4a
|
||||
#define GGML_CUDA_CC_GFX909 (GGML_CUDA_CC_OFFSET_AMD + 0x909) // GCN APU
|
||||
#define GGML_CUDA_CC_GFX90C (GGML_CUDA_CC_OFFSET_AMD + 0x90c) // GCN APU
|
||||
#define GGML_CUDA_CC_CDNA1 (GGML_CUDA_CC_OFFSET_AMD + 0x908) // MI100, minimum for MFMA, acc registers
|
||||
#define GGML_CUDA_CC_CDNA2 (GGML_CUDA_CC_OFFSET_AMD + 0x90a) // MI210 (gfx90a), minimum acc register renaming
|
||||
#define GGML_CUDA_CC_CDNA3 (GGML_CUDA_CC_OFFSET_AMD + 0x942) // MI300
|
||||
@@ -89,12 +91,13 @@
|
||||
#define GGML_CUDA_CC_IS_RDNA3_5(cc) (cc >= GGML_CUDA_CC_RDNA3_5 && cc < GGML_CUDA_CC_RDNA4)
|
||||
#define GGML_CUDA_CC_IS_RDNA3(cc) (GGML_CUDA_CC_IS_RDNA3_0(cc) || GGML_CUDA_CC_IS_RDNA3_5(cc))
|
||||
#define GGML_CUDA_CC_IS_RDNA4(cc) (cc >= GGML_CUDA_CC_RDNA4)
|
||||
#define GGML_CUDA_CC_IS_GCN(cc) (cc > GGML_CUDA_CC_OFFSET_AMD && cc < GGML_CUDA_CC_CDNA1)
|
||||
#define GGML_CUDA_CC_IS_CDNA(cc) (cc >= GGML_CUDA_CC_CDNA1 && cc < GGML_CUDA_CC_RDNA1)
|
||||
#define GGML_CUDA_CC_IS_CDNA1(cc) (cc >= GGML_CUDA_CC_CDNA1 && cc < GGML_CUDA_CC_CDNA2)
|
||||
#define GGML_CUDA_CC_IS_CDNA2(cc) (cc >= GGML_CUDA_CC_CDNA2 && cc < GGML_CUDA_CC_CDNA3)
|
||||
#define GGML_CUDA_CC_IS_CDNA3(cc) (cc >= GGML_CUDA_CC_CDNA3 && cc < GGML_CUDA_CC_CDNA4)
|
||||
#define GGML_CUDA_CC_IS_CDNA4(cc) (cc >= GGML_CUDA_CC_CDNA4 && cc < GGML_CUDA_CC_RDNA1)
|
||||
#define GGML_CUDA_CC_IS_GCN_APU(cc) ((cc) == GGML_CUDA_CC_GFX909 || (cc) == GGML_CUDA_CC_GFX90C)
|
||||
#define GGML_CUDA_CC_IS_GCN(cc) ((cc > GGML_CUDA_CC_OFFSET_AMD && cc < GGML_CUDA_CC_CDNA1) || GGML_CUDA_CC_IS_GCN_APU(cc))
|
||||
#define GGML_CUDA_CC_IS_CDNA(cc) (!GGML_CUDA_CC_IS_GCN_APU(cc) && cc >= GGML_CUDA_CC_CDNA1 && cc < GGML_CUDA_CC_RDNA1)
|
||||
#define GGML_CUDA_CC_IS_CDNA1(cc) (GGML_CUDA_CC_IS_CDNA(cc) && cc >= GGML_CUDA_CC_CDNA1 && cc < GGML_CUDA_CC_CDNA2)
|
||||
#define GGML_CUDA_CC_IS_CDNA2(cc) (GGML_CUDA_CC_IS_CDNA(cc) && cc >= GGML_CUDA_CC_CDNA2 && cc < GGML_CUDA_CC_CDNA3)
|
||||
#define GGML_CUDA_CC_IS_CDNA3(cc) (GGML_CUDA_CC_IS_CDNA(cc) && cc >= GGML_CUDA_CC_CDNA3 && cc < GGML_CUDA_CC_CDNA4)
|
||||
#define GGML_CUDA_CC_IS_CDNA4(cc) (GGML_CUDA_CC_IS_CDNA(cc) && cc >= GGML_CUDA_CC_CDNA4 && cc < GGML_CUDA_CC_RDNA1)
|
||||
|
||||
// Moore Threads
|
||||
#define MUSART_HMASK 40300 // MUSA rc4.3, min. ver. for half2 -> uint mask comparisons
|
||||
@@ -976,6 +979,7 @@ template<>
|
||||
struct ggml_cuda_type_traits<GGML_TYPE_F16> {
|
||||
static constexpr int qk = 1;
|
||||
static constexpr int qr = 1;
|
||||
static constexpr int bs = sizeof(ggml_half);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -983,6 +987,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_Q1_0> {
|
||||
static constexpr int qk = QK1_0;
|
||||
static constexpr int qr = QR1_0;
|
||||
static constexpr int qi = QI1_0;
|
||||
static constexpr int bs = sizeof(block_q1_0);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -990,6 +995,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_Q2_0> {
|
||||
static constexpr int qk = QK2_0;
|
||||
static constexpr int qr = QR2_0;
|
||||
static constexpr int qi = QI2_0;
|
||||
static constexpr int bs = sizeof(block_q2_0);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -997,6 +1003,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_Q4_0> {
|
||||
static constexpr int qk = QK4_0;
|
||||
static constexpr int qr = QR4_0;
|
||||
static constexpr int qi = QI4_0;
|
||||
static constexpr int bs = sizeof(block_q4_0);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1004,6 +1011,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_Q4_1> {
|
||||
static constexpr int qk = QK4_1;
|
||||
static constexpr int qr = QR4_1;
|
||||
static constexpr int qi = QI4_1;
|
||||
static constexpr int bs = sizeof(block_q4_1);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1011,6 +1019,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_Q5_0> {
|
||||
static constexpr int qk = QK5_0;
|
||||
static constexpr int qr = QR5_0;
|
||||
static constexpr int qi = QI5_0;
|
||||
static constexpr int bs = sizeof(block_q5_0);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1018,6 +1027,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_Q5_1> {
|
||||
static constexpr int qk = QK5_1;
|
||||
static constexpr int qr = QR5_1;
|
||||
static constexpr int qi = QI5_1;
|
||||
static constexpr int bs = sizeof(block_q5_1);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1025,6 +1035,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_Q8_0> {
|
||||
static constexpr int qk = QK8_0;
|
||||
static constexpr int qr = QR8_0;
|
||||
static constexpr int qi = QI8_0;
|
||||
static constexpr int bs = sizeof(block_q8_0);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1032,6 +1043,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_MXFP4> {
|
||||
static constexpr int qk = QK_MXFP4;
|
||||
static constexpr int qr = QR_MXFP4;
|
||||
static constexpr int qi = QI_MXFP4;
|
||||
static constexpr int bs = sizeof(block_mxfp4);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1039,6 +1051,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_NVFP4> {
|
||||
static constexpr int qk = QK_NVFP4;
|
||||
static constexpr int qr = QR_NVFP4;
|
||||
static constexpr int qi = QI_NVFP4;
|
||||
static constexpr int bs = sizeof(block_nvfp4);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1046,6 +1059,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_Q2_K> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR2_K;
|
||||
static constexpr int qi = QI2_K;
|
||||
static constexpr int bs = sizeof(block_q2_K);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1053,6 +1067,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_Q3_K> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR3_K;
|
||||
static constexpr int qi = QI3_K;
|
||||
static constexpr int bs = sizeof(block_q3_K);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1060,6 +1075,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_Q4_K> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR4_K;
|
||||
static constexpr int qi = QI4_K;
|
||||
static constexpr int bs = sizeof(block_q4_K);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1067,6 +1083,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_Q5_K> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR5_K;
|
||||
static constexpr int qi = QI5_K;
|
||||
static constexpr int bs = sizeof(block_q5_K);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1074,6 +1091,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_Q6_K> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR6_K;
|
||||
static constexpr int qi = QI6_K;
|
||||
static constexpr int bs = sizeof(block_q6_K);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1081,6 +1099,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_IQ2_XXS> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR2_XXS;
|
||||
static constexpr int qi = QI2_XXS;
|
||||
static constexpr int bs = sizeof(block_iq2_xxs);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1088,6 +1107,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_IQ2_XS> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR2_XS;
|
||||
static constexpr int qi = QI2_XS;
|
||||
static constexpr int bs = sizeof(block_iq2_xs);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1095,6 +1115,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_IQ2_S> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR2_S;
|
||||
static constexpr int qi = QI2_S;
|
||||
static constexpr int bs = sizeof(block_iq2_s);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1102,6 +1123,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_IQ3_XXS> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR3_XXS;
|
||||
static constexpr int qi = QI3_XXS;
|
||||
static constexpr int bs = sizeof(block_iq3_xxs);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1109,6 +1131,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_IQ1_S> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR1_S;
|
||||
static constexpr int qi = QI1_S;
|
||||
static constexpr int bs = sizeof(block_iq1_s);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1116,6 +1139,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_IQ1_M> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR1_M;
|
||||
static constexpr int qi = QI1_M;
|
||||
static constexpr int bs = sizeof(block_iq1_m);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1123,6 +1147,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_IQ4_NL> {
|
||||
static constexpr int qk = QK4_NL;
|
||||
static constexpr int qr = QR4_NL;
|
||||
static constexpr int qi = QI4_NL;
|
||||
static constexpr int bs = sizeof(block_iq4_nl);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1130,6 +1155,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_IQ4_XS> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR4_XS;
|
||||
static constexpr int qi = QI4_XS;
|
||||
static constexpr int bs = sizeof(block_iq4_xs);
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -1137,6 +1163,7 @@ struct ggml_cuda_type_traits<GGML_TYPE_IQ3_S> {
|
||||
static constexpr int qk = QK_K;
|
||||
static constexpr int qr = QR3_S;
|
||||
static constexpr int qi = QI3_S;
|
||||
static constexpr int bs = sizeof(block_iq3_s);
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
|
||||
@@ -1545,77 +1545,77 @@ static __device__ __forceinline__ void flash_attn_ext_f16_process_tile(
|
||||
}
|
||||
}
|
||||
|
||||
if (np > 1 && threadIdx.y % np == 0) {
|
||||
// Combine the meta data for parallel warps via shared memory.
|
||||
// Warps with threadIdx.y % np != 0 must NOT return early.
|
||||
// All threads must return simultaneously to avoid race conditions with work on the next tile.
|
||||
|
||||
if (np > 1) {
|
||||
constexpr int nmeta = np*cols_per_warp >= warp_size ? np*cols_per_warp/warp_size : 1;
|
||||
|
||||
float KQ_cmn;
|
||||
float KQ_cms[nmeta];
|
||||
float KQ_crs;
|
||||
|
||||
const int jc_meta = threadIdx.y*cols_per_warp + (np*cols_per_warp < warp_size ? threadIdx.x % (np*cols_per_warp) : threadIdx.x);
|
||||
float2 * const meta_ptr = ((float2 *) tile_Q) + jc_meta*(tile_stride/2) + nbatch_combine/2;
|
||||
float2 meta[nmeta];
|
||||
#pragma unroll
|
||||
for (int imeta = 0; imeta < nmeta; ++imeta) {
|
||||
meta[imeta] = meta_ptr[imeta * warp_size * tile_stride/2];
|
||||
}
|
||||
|
||||
float KQ_cmn = meta[0].x; // KQ combine max new, max between all parallel warps.
|
||||
if (threadIdx.y % np == 0) {
|
||||
// Combine the meta data for parallel warps via shared memory.
|
||||
float2 meta[nmeta];
|
||||
#pragma unroll
|
||||
for (int imeta = 1; imeta < nmeta; ++imeta) {
|
||||
KQ_cmn = fmaxf(KQ_cmn, meta[imeta].x);
|
||||
}
|
||||
#pragma unroll
|
||||
for (int offset = np*cols_per_warp/2; offset >= cols_per_warp; offset >>= 1) {
|
||||
if (offset < warp_size) {
|
||||
KQ_cmn = fmaxf(KQ_cmn, __shfl_xor_sync(0xFFFFFFFF, KQ_cmn, offset, warp_size));
|
||||
for (int imeta = 0; imeta < nmeta; ++imeta) {
|
||||
meta[imeta] = meta_ptr[imeta * warp_size * tile_stride/2];
|
||||
}
|
||||
}
|
||||
|
||||
float KQ_cms[nmeta]; // KQ combine max scale per warp.
|
||||
KQ_cmn = meta[0].x; // KQ combine max new, max between all parallel warps.
|
||||
#pragma unroll
|
||||
for (int imeta = 0; imeta < nmeta; ++imeta) {
|
||||
KQ_cms[imeta] = expf(meta[imeta].x - KQ_cmn);
|
||||
}
|
||||
for (int imeta = 1; imeta < nmeta; ++imeta) {
|
||||
KQ_cmn = fmaxf(KQ_cmn, meta[imeta].x);
|
||||
}
|
||||
#pragma unroll
|
||||
for (int offset = np*cols_per_warp/2; offset >= cols_per_warp; offset >>= 1) {
|
||||
if (offset < warp_size) {
|
||||
KQ_cmn = fmaxf(KQ_cmn, __shfl_xor_sync(0xFFFFFFFF, KQ_cmn, offset, warp_size));
|
||||
}
|
||||
}
|
||||
|
||||
float KQ_crs = KQ_cms[0]*meta[0].y; // KQ combine rowsum, scaled sum of all parallel warps.
|
||||
#pragma unroll
|
||||
for (int imeta = 1; imeta < nmeta; ++imeta) {
|
||||
KQ_crs += KQ_cms[imeta]*meta[imeta].y;
|
||||
}
|
||||
for (int imeta = 0; imeta < nmeta; ++imeta) {
|
||||
KQ_cms[imeta] = expf(meta[imeta].x - KQ_cmn);
|
||||
}
|
||||
|
||||
KQ_crs = KQ_cms[0]*meta[0].y; // KQ combine rowsum, scaled sum of all parallel warps.
|
||||
#pragma unroll
|
||||
for (int offset = np*cols_per_warp/2; offset >= cols_per_warp; offset >>= 1) {
|
||||
if (offset < warp_size) {
|
||||
KQ_crs += __shfl_xor_sync(0xFFFFFFFF, KQ_crs, offset, warp_size);
|
||||
for (int imeta = 1; imeta < nmeta; ++imeta) {
|
||||
KQ_crs += KQ_cms[imeta]*meta[imeta].y;
|
||||
}
|
||||
#pragma unroll
|
||||
for (int offset = np*cols_per_warp/2; offset >= cols_per_warp; offset >>= 1) {
|
||||
if (offset < warp_size) {
|
||||
KQ_crs += __shfl_xor_sync(0xFFFFFFFF, KQ_crs, offset, warp_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// Write back combined meta data:
|
||||
if (threadIdx.y % np == 0) {
|
||||
// Write back combined meta data:
|
||||
#pragma unroll
|
||||
for (int imeta = 0; imeta < nmeta; ++imeta) {
|
||||
if (np*cols_per_warp >= warp_size || threadIdx.x < np*cols_per_warp) {
|
||||
// Combined KQ max scale + rowsum.
|
||||
meta_ptr[imeta * warp_size * tile_stride/2] = make_float2(KQ_cms[imeta], KQ_crs);
|
||||
for (int imeta = 0; imeta < nmeta; ++imeta) {
|
||||
if (np*cols_per_warp >= warp_size || threadIdx.x < np*cols_per_warp) {
|
||||
// Combined KQ max scale + rowsum.
|
||||
meta_ptr[imeta * warp_size * tile_stride/2] = make_float2(KQ_cms[imeta], KQ_crs);
|
||||
}
|
||||
}
|
||||
|
||||
// Combined KQ max + rowsum.
|
||||
static_assert(cols_per_warp <= warp_size);
|
||||
if (needs_fixup && (cols_per_warp == warp_size || threadIdx.x < cols_per_warp)) {
|
||||
float2 * dstk_fixup_meta = dstk_fixup + blockIdx.x*ncols;
|
||||
dstk_fixup_meta[(threadIdx.y/np)*cols_per_warp + threadIdx.x] = make_float2(KQ_cmn, KQ_crs);
|
||||
}
|
||||
if (is_fixup && (cols_per_warp == warp_size || threadIdx.x < cols_per_warp)) {
|
||||
float2 * dstk_fixup_meta = dstk_fixup + (gridDim.x + blockIdx.x)*ncols;
|
||||
dstk_fixup_meta[(threadIdx.y/np)*cols_per_warp + threadIdx.x] = make_float2(KQ_cmn, KQ_crs);
|
||||
}
|
||||
}
|
||||
|
||||
// Combined KQ max + rowsum.
|
||||
static_assert(cols_per_warp <= warp_size);
|
||||
if (needs_fixup && (cols_per_warp == warp_size || threadIdx.x < cols_per_warp)) {
|
||||
float2 * dstk_fixup_meta = dstk_fixup + blockIdx.x*ncols;
|
||||
dstk_fixup_meta[(threadIdx.y/np)*cols_per_warp + threadIdx.x] = make_float2(KQ_cmn, KQ_crs);
|
||||
}
|
||||
if (is_fixup && (cols_per_warp == warp_size || threadIdx.x < cols_per_warp)) {
|
||||
float2 * dstk_fixup_meta = dstk_fixup + (gridDim.x + blockIdx.x)*ncols;
|
||||
dstk_fixup_meta[(threadIdx.y/np)*cols_per_warp + threadIdx.x] = make_float2(KQ_cmn, KQ_crs);
|
||||
}
|
||||
} else if (np > 1) {
|
||||
// Warps with threadIdx.y % np == 0 execute a __syncthreads() in the if branch.
|
||||
// Therefore, all other warps also need to execute a __syncthreads().
|
||||
// Otherwise the points at which warps synchronize with each other would become misaligned.
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
#pragma unroll
|
||||
|
||||
@@ -212,6 +212,7 @@ static int ggml_cuda_parse_id(char devName[]) {
|
||||
}
|
||||
archNum += archMajor * 0x100;
|
||||
archNum += archMinor;
|
||||
|
||||
return archNum;
|
||||
}
|
||||
#endif // defined(GGML_USE_HIP)
|
||||
|
||||
@@ -375,10 +375,10 @@ bool ggml_cuda_should_use_mmq(enum ggml_type type, int cc, int64_t ne11, int64_t
|
||||
return true;
|
||||
}
|
||||
|
||||
// gfx900 (Vega 10) lacks native dp4a, loses to dequant + hipBLAS
|
||||
// gfx900 (Vega 10), gfx909, and gfx90c lack native dp4a, losing to dequant + hipBLAS
|
||||
// for dense matrices; keep MMQ only for MoE, where the
|
||||
// hipBLAS path is much slower.
|
||||
if (cc == GGML_CUDA_CC_VEGA) {
|
||||
if (cc == GGML_CUDA_CC_VEGA || GGML_CUDA_CC_IS_GCN_APU(cc)) {
|
||||
return n_experts > 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,35 @@
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
// only enabled on DGX Spark, where it is a gain on every type below. On the higher-bandwidth parts the kernel
|
||||
// has little exposed latency left to hide and the extra requests cost more than they save.
|
||||
// For perf data, see https://github.com/ggml-org/llama.cpp/pull/26705#issuecomment-5569335031
|
||||
#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ == GGML_CUDA_CC_DGX_SPARK
|
||||
// returns true only for those quants that benefit from prefetch and false otherwise
|
||||
static constexpr __host__ __device__ bool mmvq_should_prefetch(ggml_type type) {
|
||||
switch (type) {
|
||||
case GGML_TYPE_Q4_0:
|
||||
case GGML_TYPE_Q5_0:
|
||||
case GGML_TYPE_Q8_0:
|
||||
case GGML_TYPE_MXFP4:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q4_K:
|
||||
case GGML_TYPE_Q5_K:
|
||||
case GGML_TYPE_Q6_K:
|
||||
case GGML_TYPE_IQ1_M:
|
||||
case GGML_TYPE_IQ4_NL:
|
||||
case GGML_TYPE_IQ4_XS:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static __device__ __forceinline__ void mmvq_prefetch_l2(const void * p) {
|
||||
asm volatile("prefetch.global.L2 [%0];" :: "l"(p));
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef float (*vec_dot_q_cuda_t)(const void * __restrict__ vbq, const block_q8_1 * __restrict__ bq8_1, const int & kbx, const int & iqs);
|
||||
|
||||
static constexpr __device__ vec_dot_q_cuda_t get_vec_dot_q_cuda(ggml_type type) {
|
||||
@@ -298,9 +327,6 @@ bool ggml_cuda_should_use_mmvq(enum ggml_type type, int cc, int64_t ne11) {
|
||||
return ne11 <= 4;
|
||||
case GGML_TYPE_Q3_K:
|
||||
return ne11 <= 6;
|
||||
case GGML_TYPE_Q4_K:
|
||||
case GGML_TYPE_Q5_K:
|
||||
return ne11 <= 7;
|
||||
default:
|
||||
return ne11 <= MMVQ_MAX_BATCH_SIZE;
|
||||
}
|
||||
@@ -310,8 +336,9 @@ bool ggml_cuda_should_use_mmvq(enum ggml_type type, int cc, int64_t ne11) {
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q4_K:
|
||||
case GGML_TYPE_Q5_K:
|
||||
return ne11 <= 5;
|
||||
case GGML_TYPE_Q5_K:
|
||||
return ne11 <= 6;
|
||||
case GGML_TYPE_Q6_K:
|
||||
return ne11 <= 7;
|
||||
default:
|
||||
@@ -675,6 +702,26 @@ static __global__ void mul_mat_vec_q(
|
||||
// x block quant index when casting the quants to int
|
||||
const int kqs = vdr * (tid % (qi/vdr));
|
||||
|
||||
#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ == GGML_CUDA_CC_DGX_SPARK
|
||||
// start the next iterations' weight loads early
|
||||
if constexpr (mmvq_should_prefetch(type)) {
|
||||
constexpr int pf_dist = 2; // loop iterations, not blocks
|
||||
const int kbx_pf = kbx + pf_dist*blocks_per_iter;
|
||||
if (kbx_pf < blocks_per_row_x) {
|
||||
#pragma unroll
|
||||
for (int i = 0; i < rows_per_cuda_block; ++i) {
|
||||
const size_t off = (size_t)(kbx_offset + i*stride_row_x + kbx_pf) * ggml_cuda_type_traits<type>::bs;
|
||||
mmvq_prefetch_l2((const char *) vx + off);
|
||||
if constexpr (has_fusion) {
|
||||
if (use_gate) {
|
||||
mmvq_prefetch_l2((const char *) vgate + off);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma unroll
|
||||
for (int j = 0; j < ncols_dst; ++j) {
|
||||
#pragma unroll
|
||||
|
||||
@@ -936,16 +936,20 @@ static __device__ __forceinline__ float vec_dot_q4_K_q8_1(
|
||||
v[0] = q4[0];
|
||||
v[1] = q4[4];
|
||||
|
||||
// branchless so nvcc can hoist this out of the ncols_dst loop
|
||||
const uint16_t * scales = (const uint16_t *)bq4_K->scales;
|
||||
const int j = bq8_offset/2;
|
||||
const int jm = j & 1;
|
||||
|
||||
const uint32_t s0 = scales[jm + 0];
|
||||
const uint32_t s2 = scales[jm + 2];
|
||||
const uint32_t s4 = scales[jm + 4];
|
||||
|
||||
const uint32_t hi = (uint32_t) -(int32_t) (j >= 2);
|
||||
|
||||
uint16_t aux[2];
|
||||
const int j = bq8_offset/2;
|
||||
if (j < 2) {
|
||||
aux[0] = scales[j+0] & 0x3f3f;
|
||||
aux[1] = scales[j+2] & 0x3f3f;
|
||||
} else {
|
||||
aux[0] = ((scales[j+2] >> 0) & 0x0f0f) | ((scales[j-2] & 0xc0c0) >> 2);
|
||||
aux[1] = ((scales[j+2] >> 4) & 0x0f0f) | ((scales[j-0] & 0xc0c0) >> 2);
|
||||
}
|
||||
aux[0] = (uint16_t) (((s0 & 0x3f3f) & ~hi) | ((((s4 >> 0) & 0x0f0f) | ((s0 & 0xc0c0) >> 2)) & hi));
|
||||
aux[1] = (uint16_t) (((s2 & 0x3f3f) & ~hi) | ((((s4 >> 4) & 0x0f0f) | ((s2 & 0xc0c0) >> 2)) & hi));
|
||||
const uint8_t * sc = (const uint8_t *)aux;
|
||||
const uint8_t * m = sc + 2;
|
||||
|
||||
@@ -981,16 +985,21 @@ static __device__ __forceinline__ float vec_dot_q5_K_q8_1(
|
||||
vh[0] = qh[0] >> bq8_offset;
|
||||
vh[1] = qh[4] >> bq8_offset;
|
||||
|
||||
// same as q4_K
|
||||
const uint16_t * scales = (const uint16_t *)bq5_K->scales;
|
||||
const int j = bq8_offset/2;
|
||||
const int jm = j & 1;
|
||||
|
||||
const uint32_t s0 = scales[jm + 0];
|
||||
const uint32_t s2 = scales[jm + 2];
|
||||
const uint32_t s4 = scales[jm + 4];
|
||||
|
||||
const uint32_t hi = (uint32_t) -(int32_t) (j >= 2);
|
||||
|
||||
uint16_t aux[2];
|
||||
const int j = bq8_offset/2;
|
||||
if (j < 2) {
|
||||
aux[0] = scales[j+0] & 0x3f3f;
|
||||
aux[1] = scales[j+2] & 0x3f3f;
|
||||
} else {
|
||||
aux[0] = ((scales[j+2] >> 0) & 0x0f0f) | ((scales[j-2] & 0xc0c0) >> 2);
|
||||
aux[1] = ((scales[j+2] >> 4) & 0x0f0f) | ((scales[j-0] & 0xc0c0) >> 2);
|
||||
}
|
||||
aux[0] = (uint16_t) (((s0 & 0x3f3f) & ~hi) | ((((s4 >> 0) & 0x0f0f) | ((s0 & 0xc0c0) >> 2)) & hi));
|
||||
aux[1] = (uint16_t) (((s2 & 0x3f3f) & ~hi) | ((((s4 >> 4) & 0x0f0f) | ((s2 & 0xc0c0) >> 2)) & hi));
|
||||
|
||||
const uint8_t * sc = (const uint8_t *)aux;
|
||||
const uint8_t * m = sc + 2;
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -176,9 +176,9 @@
|
||||
|
||||
#define __CUDA_ARCH__ 1300
|
||||
|
||||
#if defined(__gfx900__) || defined(__gfx906__)
|
||||
#if defined(__gfx900__) || defined(__gfx906__) || defined(__gfx909__) || defined(__gfx90c__)
|
||||
#define GCN5
|
||||
#endif // defined(__gfx900__) || defined(__gfx906__)
|
||||
#endif // defined(__gfx900__) || defined(__gfx906__) || defined(__gfx909__) || defined(__gfx90c__)
|
||||
|
||||
#if defined(__gfx803__)
|
||||
#define GCN4
|
||||
|
||||
@@ -1091,6 +1091,10 @@ static ggml_openvino_op_support is_op_supported_case(const ggml_tensor * op) {
|
||||
if (op->ne[3] != 1) {
|
||||
return {false, "GET_ROWS/SET_ROWS with ne[3] != 1 (ne[3]=" + std::to_string(op->ne[3]) + ") is not supported"};
|
||||
}
|
||||
if (op->op == GGML_OP_GET_ROWS && ggml_is_quantized(op->src[0]->type) &&
|
||||
op->src[0]->view_src != nullptr && op->src[0]->view_offs != 0) {
|
||||
return {false, "GET_ROWS with a nonzero quantized src0 view offset is not supported"};
|
||||
}
|
||||
if (op->op == GGML_OP_GET_ROWS && ggml_openvino_get_device_name() == "GPU" &&
|
||||
op->src[0]->type == GGML_TYPE_BF16) {
|
||||
return {false, "GET_ROWS with BF16 src0 is not supported on GPU"};
|
||||
|
||||
@@ -4858,6 +4858,78 @@ static bool ggml_sycl_mul_mat_glu_mmvq_fused(ggml_backend_sycl_context & ctx, gg
|
||||
/*stride_col_dst=*/(int) glu->ne[0], stream);
|
||||
}
|
||||
|
||||
// Batch the run of consecutive L2_NORM siblings starting at node_idx into one launch.
|
||||
// Returns the number of extra graph nodes consumed, or 0 if the run is shorter than two
|
||||
// (the caller then runs the norm through the per-tensor kernel).
|
||||
static int ggml_sycl_l2_norm_batch_fused(ggml_backend_sycl_context & ctx, ggml_cgraph * cgraph, int node_idx) {
|
||||
const ggml_tensor * node = cgraph->nodes[node_idx];
|
||||
if (ggml_sycl_info().device_count != 1 || node->type != GGML_TYPE_F32 ||
|
||||
node->src[0]->type != GGML_TYPE_F32 || node->src[0]->ne[0] >= 1024) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ggml_tensor * batch[GGML_SYCL_L2_BATCH_MAX];
|
||||
int count = 0;
|
||||
int last = node_idx;
|
||||
float eps0;
|
||||
memcpy(&eps0, node->op_params, sizeof(float));
|
||||
|
||||
// Conservative aliasing test: the batched norms run concurrently in one kernel,
|
||||
// so none may read what another writes, and none may write where another writes.
|
||||
auto overlaps = [](const ggml_tensor * a, const ggml_tensor * b) {
|
||||
const char * ab = (const char *) a->data;
|
||||
const char * bb = (const char *) b->data;
|
||||
return ab < bb + ggml_nbytes(b) && bb < ab + ggml_nbytes(a);
|
||||
};
|
||||
|
||||
for (int j = node_idx; j < cgraph->n_nodes && count < GGML_SYCL_L2_BATCH_MAX; ++j) {
|
||||
ggml_tensor * nj = cgraph->nodes[j];
|
||||
if (ggml_is_empty(nj) || nj->op == GGML_OP_RESHAPE || nj->op == GGML_OP_TRANSPOSE ||
|
||||
nj->op == GGML_OP_VIEW || nj->op == GGML_OP_PERMUTE || nj->op == GGML_OP_NONE ||
|
||||
(nj->flags & GGML_TENSOR_FLAG_COMPUTE) == 0) {
|
||||
continue; // not a launch; cannot break a run of adjacent norms
|
||||
}
|
||||
if (nj->op != GGML_OP_L2_NORM || nj->type != GGML_TYPE_F32 ||
|
||||
nj->src[0]->type != GGML_TYPE_F32 || !ggml_are_same_shape(nj, node) ||
|
||||
!ggml_are_same_shape(nj->src[0], node->src[0])) {
|
||||
break; // any other launch ends the run
|
||||
}
|
||||
bool same_nb = true;
|
||||
for (int d = 0; d < GGML_MAX_DIMS; ++d) {
|
||||
if (nj->nb[d] != node->nb[d] || nj->src[0]->nb[d] != node->src[0]->nb[d]) {
|
||||
same_nb = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!same_nb) {
|
||||
break; // one nb[] stride set is shared by the whole batch
|
||||
}
|
||||
float epsj;
|
||||
memcpy(&epsj, nj->op_params, sizeof(float));
|
||||
if (epsj != eps0) {
|
||||
break; // eps mismatch ends the run
|
||||
}
|
||||
bool indep = true;
|
||||
for (int k = 0; k < count; ++k) {
|
||||
if (overlaps(nj->src[0], batch[k]) || overlaps(nj, batch[k])) {
|
||||
indep = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!indep) {
|
||||
break; // an overlapping tensor would race inside one launch
|
||||
}
|
||||
batch[count++] = nj;
|
||||
last = j;
|
||||
}
|
||||
if (count < 2) {
|
||||
return 0; // a lone norm falls through to the per-tensor kernel
|
||||
}
|
||||
ggml_sycl_l2_norm_batch(ctx, batch, count);
|
||||
return last - node_idx;
|
||||
}
|
||||
|
||||
|
||||
__dpct_inline__ static void k_copy_src1_to_contiguous(
|
||||
const char *__restrict__ src1_original, char *__restrict__ src1_contiguous,
|
||||
const mmid_row_mapping *__restrict__ row_mapping,
|
||||
@@ -5908,6 +5980,17 @@ static void ggml_backend_sycl_graph_compute_impl(ggml_backend_sycl_context * syc
|
||||
continue;
|
||||
}
|
||||
|
||||
// Batch consecutive independent same-shape F32 L2_NORM siblings (the GDN q/k
|
||||
// norms) into one launch; sources are strided views of the fused qkv buffer, so
|
||||
// the scan skips the interleaved view nodes instead of breaking on them.
|
||||
if (node->op == GGML_OP_L2_NORM) {
|
||||
const int l2_batch_skip = ggml_sycl_l2_norm_batch_fused(*sycl_ctx, cgraph, i);
|
||||
if (l2_batch_skip > 0) {
|
||||
i += l2_batch_skip;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (node->op == GGML_OP_MUL_MAT && ggml_sycl_mul_mat_glu_mmvq_fused(*sycl_ctx, cgraph, i)) {
|
||||
i += 2;
|
||||
continue;
|
||||
|
||||
@@ -543,6 +543,62 @@ static void l2_norm_f32_sycl(const float * x,
|
||||
}
|
||||
}
|
||||
|
||||
// Batched L2 norm: N independent same-shape F32 tensors in one launch; the tensor
|
||||
// index is folded into grid dim0 and each row's reduction is identical to the
|
||||
// single-tensor kernel, so the result is bit-exact.
|
||||
struct l2_batch_ptrs {
|
||||
const float * src[GGML_SYCL_L2_BATCH_MAX];
|
||||
float * dst[GGML_SYCL_L2_BATCH_MAX];
|
||||
};
|
||||
|
||||
// One stride set shared by the whole batch: the caller only groups tensors whose nb[]
|
||||
// all match, so per-tensor state stays two pointers.
|
||||
struct l2_batch_strides {
|
||||
int ne1, ne2;
|
||||
int64_t ss0, ss1, ss2, ss3;
|
||||
int64_t ds0, ds1, ds2, ds3;
|
||||
};
|
||||
|
||||
template <int warp_size>
|
||||
static void l2_norm_f32_batch(l2_batch_ptrs p, l2_batch_strides st, const int ncols, const float eps,
|
||||
const sycl::nd_item<3> & item_ct1) {
|
||||
const int t = item_ct1.get_group(0); // tensor index
|
||||
const int r = item_ct1.get_group(2); // flattened row over ne1*ne2*ne3
|
||||
const int tid = item_ct1.get_local_id(2);
|
||||
|
||||
const int i1 = r % st.ne1;
|
||||
const int i2 = (r / st.ne1) % st.ne2;
|
||||
const int i3 = r / (st.ne1 * st.ne2);
|
||||
|
||||
const float * x = p.src[t] + i3 * st.ss3 + i2 * st.ss2 + i1 * st.ss1;
|
||||
float * dst = p.dst[t] + i3 * st.ds3 + i2 * st.ds2 + i1 * st.ds1;
|
||||
|
||||
float tmp = 0.0f;
|
||||
for (int col = tid; col < ncols; col += warp_size) {
|
||||
const float xi = x[col * st.ss0];
|
||||
tmp += xi * xi;
|
||||
}
|
||||
tmp = block_reduce<block_reduce_method::SUM, warp_size>(tmp, (float *) nullptr, warp_size);
|
||||
const float scale = sycl::rsqrt(sycl::fmax(tmp, eps * eps));
|
||||
for (int col = tid; col < ncols; col += warp_size) {
|
||||
dst[col * st.ds0] = scale * x[col * st.ss0];
|
||||
}
|
||||
}
|
||||
|
||||
template <int warp_size>
|
||||
static void l2_norm_f32_batch_sycl(l2_batch_ptrs p, l2_batch_strides st, const int n_tensors,
|
||||
const int ncols, const int nrows_total, const float eps,
|
||||
queue_ptr stream) {
|
||||
const dpct::dim3 blocks_num(nrows_total, 1, n_tensors);
|
||||
const dpct::dim3 block_dims(warp_size, 1, 1);
|
||||
stream->submit([&](sycl::handler & cgh) {
|
||||
cgh.parallel_for(sycl::nd_range<3>(blocks_num * block_dims, block_dims),
|
||||
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(warp_size)]] {
|
||||
l2_norm_f32_batch<warp_size>(p, st, ncols, eps, item_ct1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void ggml_sycl_op_norm(ggml_backend_sycl_context& ctx, ggml_tensor* dst) {
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
|
||||
@@ -961,3 +1017,30 @@ void ggml_sycl_op_l2_norm(ggml_backend_sycl_context& ctx, ggml_tensor* dst) {
|
||||
l2_norm_f32_sycl<WARP_SIZE>(src0_d, dst_d, ne00, ne01, ne02, ne03,
|
||||
ss0, ss1, ss2, ss3, ds0, ds1, ds2, ds3, eps, stream, ctx.device);
|
||||
}
|
||||
|
||||
// nodes[0..count) are independent, same-shape, same-eps, same-nb L2_NORM ops validated
|
||||
// by the caller; requires ncols < 1024 (the warp reduction path).
|
||||
void ggml_sycl_l2_norm_batch(ggml_backend_sycl_context & ctx, ggml_tensor ** nodes, int count) {
|
||||
const ggml_tensor * s0 = nodes[0]->src[0];
|
||||
const int ncols = (int) s0->ne[0];
|
||||
const int nrows_total = (int) ggml_nrows(s0);
|
||||
float eps;
|
||||
memcpy(&eps, nodes[0]->op_params, sizeof(float));
|
||||
GGML_ASSERT(eps >= 0.0f);
|
||||
|
||||
l2_batch_ptrs p{};
|
||||
for (int t = 0; t < count; ++t) {
|
||||
p.src[t] = (const float *) nodes[t]->src[0]->data;
|
||||
p.dst[t] = (float *) nodes[t]->data;
|
||||
}
|
||||
|
||||
const ggml_tensor * d0 = nodes[0];
|
||||
const size_t ts = ggml_type_size(GGML_TYPE_F32);
|
||||
l2_batch_strides st{};
|
||||
st.ne1 = (int) s0->ne[1];
|
||||
st.ne2 = (int) s0->ne[2];
|
||||
st.ss0 = s0->nb[0] / ts; st.ss1 = s0->nb[1] / ts; st.ss2 = s0->nb[2] / ts; st.ss3 = s0->nb[3] / ts;
|
||||
st.ds0 = d0->nb[0] / ts; st.ds1 = d0->nb[1] / ts; st.ds2 = d0->nb[2] / ts; st.ds3 = d0->nb[3] / ts;
|
||||
|
||||
l2_norm_f32_batch_sycl<WARP_SIZE>(p, st, count, ncols, nrows_total, eps, ctx.stream());
|
||||
}
|
||||
|
||||
@@ -29,4 +29,7 @@ void ggml_sycl_op_group_norm(ggml_backend_sycl_context& ctx, ggml_tensor* dst);
|
||||
|
||||
void ggml_sycl_op_l2_norm(ggml_backend_sycl_context& ctx, ggml_tensor* dst);
|
||||
|
||||
#define GGML_SYCL_L2_BATCH_MAX 8
|
||||
void ggml_sycl_l2_norm_batch(ggml_backend_sycl_context & ctx, ggml_tensor ** nodes, int count);
|
||||
|
||||
#endif // GGML_SYCL_NORM_HPP
|
||||
|
||||
@@ -1110,6 +1110,9 @@ struct vk_device_struct {
|
||||
vk_pipeline pipeline_cumsum_multipass2_f32;
|
||||
vk_pipeline pipeline_argmax_f32;
|
||||
vk_pipeline pipeline_count_equal_i32;
|
||||
vk_pipeline pipeline_dsv4_hc_comb_f32;
|
||||
vk_pipeline pipeline_dsv4_hc_pre_f32;
|
||||
vk_pipeline pipeline_dsv4_hc_post_f32;
|
||||
std::map<vk_solve_tri_pipeline_state, vk_pipeline> pipeline_solve_tri_f32;
|
||||
vk_pipeline pipeline_im2col_f32, pipeline_im2col_f32_f16;
|
||||
vk_pipeline pipeline_im2col_3d_f32, pipeline_im2col_3d_f32_f16;
|
||||
@@ -1467,6 +1470,53 @@ struct vk_op_fwht_push_constants {
|
||||
float scale;
|
||||
};
|
||||
|
||||
struct vk_op_dsv4_hc_comb_push_constants {
|
||||
uint32_t n_tokens;
|
||||
|
||||
uint32_t nbm0; uint32_t nbm1;
|
||||
uint32_t nbs0;
|
||||
uint32_t nbb0;
|
||||
uint32_t nbd0; uint32_t nbd1; uint32_t nbd2;
|
||||
|
||||
uint32_t m_offset;
|
||||
uint32_t s_offset;
|
||||
uint32_t b_offset;
|
||||
uint32_t d_offset;
|
||||
|
||||
float eps;
|
||||
uint32_t n_iter;
|
||||
};
|
||||
|
||||
struct vk_op_dsv4_hc_pre_push_constants {
|
||||
uint32_t n_embd;
|
||||
uint32_t n_tokens;
|
||||
|
||||
uint32_t nbx0; uint32_t nbx1; uint32_t nbx2;
|
||||
uint32_t nbw0; uint32_t nbw1;
|
||||
uint32_t nbd0; uint32_t nbd1;
|
||||
|
||||
uint32_t x_offset;
|
||||
uint32_t w_offset;
|
||||
uint32_t d_offset;
|
||||
};
|
||||
|
||||
struct vk_op_dsv4_hc_post_push_constants {
|
||||
uint32_t n_embd;
|
||||
uint32_t n_tokens;
|
||||
|
||||
uint32_t nbx0; uint32_t nbx1;
|
||||
uint32_t nbr0; uint32_t nbr1; uint32_t nbr2;
|
||||
uint32_t nbp0; uint32_t nbp1;
|
||||
uint32_t nbc0; uint32_t nbc1; uint32_t nbc2;
|
||||
uint32_t nbd0; uint32_t nbd1; uint32_t nbd2;
|
||||
|
||||
uint32_t x_offset;
|
||||
uint32_t r_offset;
|
||||
uint32_t p_offset;
|
||||
uint32_t c_offset;
|
||||
uint32_t d_offset;
|
||||
};
|
||||
|
||||
struct vk_op_count_experts_push_constants {
|
||||
uint32_t ne00;
|
||||
uint32_t ne01;
|
||||
@@ -2515,9 +2565,38 @@ static uint64_t vk_tensor_offset(const ggml_tensor * tensor) {
|
||||
return (uint8_t *) tensor->data - (uint8_t *) vk_ptr_base;
|
||||
}
|
||||
|
||||
static uint32_t get_misalign_bytes(const ggml_backend_vk_context * ctx, const ggml_tensor * t)
|
||||
{
|
||||
return ((vk_tensor_offset(t) + t->view_offs) & (ctx->device->properties.limits.minStorageBufferOffsetAlignment - 1));;
|
||||
static void ggml_vk_host_get(const vk_device& device, const void * ptr, vk_buffer& buf, size_t& buf_offset);
|
||||
|
||||
static size_t ggml_vk_tensor_buffer_offset(const ggml_backend_vk_context * ctx, const ggml_tensor * t) {
|
||||
// vk_tensor_offset() is relative to vk_ptr_base, but mapped host tensors need an offset relative to their Vulkan buffer.
|
||||
if (ctx->device->uma) {
|
||||
vk_buffer buf = nullptr;
|
||||
size_t off = 0;
|
||||
ggml_vk_host_get(ctx->device, t->data, buf, off);
|
||||
if (buf) {
|
||||
return off;
|
||||
}
|
||||
}
|
||||
return (size_t)(vk_tensor_offset(t) + t->view_offs);
|
||||
}
|
||||
|
||||
static size_t ggml_vk_descriptor_offset(size_t tensor_offset, size_t alignment, size_t type_size) {
|
||||
// Move the descriptor back until its distance to the tensor is divisible by the tensor type size.
|
||||
size_t descriptor_offset = tensor_offset & ~(alignment - 1);
|
||||
while ((tensor_offset - descriptor_offset) % type_size != 0) {
|
||||
GGML_ASSERT(descriptor_offset >= alignment);
|
||||
descriptor_offset -= alignment;
|
||||
}
|
||||
|
||||
return descriptor_offset;
|
||||
}
|
||||
|
||||
static uint32_t get_misalign_bytes(const ggml_backend_vk_context * ctx, const ggml_tensor * t) {
|
||||
const size_t tensor_offset = ggml_vk_tensor_buffer_offset(ctx, t);
|
||||
const size_t descriptor_offset = ggml_vk_descriptor_offset(
|
||||
tensor_offset, ctx->device->properties.limits.minStorageBufferOffsetAlignment, ggml_type_size(t->type));
|
||||
GGML_ASSERT(tensor_offset - descriptor_offset <= UINT32_MAX);
|
||||
return tensor_offset - descriptor_offset;
|
||||
}
|
||||
|
||||
static uint32_t ggml_vk_concat_unit_size(ggml_type type) {
|
||||
@@ -2602,6 +2681,32 @@ template <> void init_pushconst_tensor_offsets(ggml_backend_vk_context * ctx, vk
|
||||
GGML_UNUSED(src3);
|
||||
}
|
||||
|
||||
template <> void init_pushconst_tensor_offsets(ggml_backend_vk_context * ctx, vk_op_dsv4_hc_comb_push_constants &p, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * src2, const ggml_tensor * src3, ggml_tensor * dst) {
|
||||
p.m_offset = get_misalign_bytes(ctx, src0) / ggml_type_size(src0->type);
|
||||
p.s_offset = get_misalign_bytes(ctx, src1) / ggml_type_size(src1->type);
|
||||
p.b_offset = get_misalign_bytes(ctx, src2) / ggml_type_size(src2->type);
|
||||
p.d_offset = get_misalign_bytes(ctx, dst) / ggml_type_size(dst->type);
|
||||
|
||||
GGML_UNUSED(src3);
|
||||
}
|
||||
|
||||
template <> void init_pushconst_tensor_offsets(ggml_backend_vk_context * ctx, vk_op_dsv4_hc_pre_push_constants &p, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * src2, const ggml_tensor * src3, ggml_tensor * dst) {
|
||||
p.x_offset = get_misalign_bytes(ctx, src0) / ggml_type_size(src0->type);
|
||||
p.w_offset = get_misalign_bytes(ctx, src1) / ggml_type_size(src1->type);
|
||||
p.d_offset = get_misalign_bytes(ctx, dst) / ggml_type_size(dst->type);
|
||||
|
||||
GGML_UNUSED(src2);
|
||||
GGML_UNUSED(src3);
|
||||
}
|
||||
|
||||
template <> void init_pushconst_tensor_offsets(ggml_backend_vk_context * ctx, vk_op_dsv4_hc_post_push_constants &p, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * src2, const ggml_tensor * src3, ggml_tensor * dst) {
|
||||
p.x_offset = get_misalign_bytes(ctx, src0) / ggml_type_size(src0->type);
|
||||
p.r_offset = get_misalign_bytes(ctx, src1) / ggml_type_size(src1->type);
|
||||
p.p_offset = get_misalign_bytes(ctx, src2) / ggml_type_size(src2->type);
|
||||
p.c_offset = get_misalign_bytes(ctx, src3) / ggml_type_size(src3->type);
|
||||
p.d_offset = get_misalign_bytes(ctx, dst) / ggml_type_size(dst->type);
|
||||
}
|
||||
|
||||
struct ggml_backend_vk_buffer_context {
|
||||
vk_device_ref device;
|
||||
vk_buffer dev_buffer;
|
||||
@@ -5948,6 +6053,16 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
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);
|
||||
}
|
||||
|
||||
// comb holds a token's 4x4 matrix in one 16-lane slice of a subgroup, so it
|
||||
// needs at least 16 lanes, pinned to a known size.
|
||||
if (device->subgroup_basic && device->subgroup_shuffle && device->subgroup_require_full_support && device->subgroup_size >= 16) {
|
||||
const uint32_t tokens_per_workgroup = 4 * (device->subgroup_size / 16);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_dsv4_hc_comb_f32, "dsv4_hc_comb_f32", dsv4_hc_comb_f32_len, dsv4_hc_comb_f32_data, "main", 4, sizeof(vk_op_dsv4_hc_comb_push_constants), {tokens_per_workgroup, 1, 1}, { device->subgroup_size }, 1, true, true, device->subgroup_size);
|
||||
}
|
||||
|
||||
ggml_vk_create_pipeline(device, device->pipeline_dsv4_hc_pre_f32, "dsv4_hc_pre_f32", dsv4_hc_pre_f32_len, dsv4_hc_pre_f32_data, "main", 3, sizeof(vk_op_dsv4_hc_pre_push_constants), {256, 1, 1}, { 256 }, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_dsv4_hc_post_f32, "dsv4_hc_post_f32", dsv4_hc_post_f32_len, dsv4_hc_post_f32_data, "main", 5, sizeof(vk_op_dsv4_hc_post_push_constants), {256, 1, 1}, { 256 }, 1);
|
||||
|
||||
for (auto &s : device->pipeline_solve_tri_f32) {
|
||||
const vk_solve_tri_pipeline_state &state = s.first;
|
||||
|
||||
@@ -8265,10 +8380,12 @@ static vk_subbuffer ggml_vk_tensor_subbuffer(
|
||||
|
||||
size_t size = ggml_nbytes(tensor);
|
||||
|
||||
size_t misalign_bytes = offset & (ctx->device->properties.limits.minStorageBufferOffsetAlignment - 1);
|
||||
const size_t descriptor_offset = ggml_vk_descriptor_offset(
|
||||
offset, ctx->device->properties.limits.minStorageBufferOffsetAlignment, ggml_type_size(tensor->type));
|
||||
const size_t misalign_bytes = offset - descriptor_offset;
|
||||
// The shader must support misaligned offsets when indexing into the buffer
|
||||
GGML_ASSERT(allow_misalign || misalign_bytes == 0);
|
||||
offset &= ~misalign_bytes;
|
||||
offset = descriptor_offset;
|
||||
size += misalign_bytes;
|
||||
|
||||
return vk_subbuffer{buffer, offset, size};
|
||||
@@ -10173,6 +10290,98 @@ static void ggml_vk_fwht(ggml_backend_vk_context * ctx, vk_context& subctx, cons
|
||||
ggml_vk_dispatch_pipeline(ctx, subctx, pipeline, { src_buf, dst_buf }, pc, { workgroups_x, 1, 1 });
|
||||
}
|
||||
|
||||
static uint32_t ggml_vk_nb_elem(const ggml_tensor * t, int i) {
|
||||
return (uint32_t)(t->nb[i] / ggml_type_size(t->type));
|
||||
}
|
||||
|
||||
static void ggml_vk_dsv4_hc_comb(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * mixes, const ggml_tensor * scale, const ggml_tensor * base, ggml_tensor * dst) {
|
||||
VK_LOG_DEBUG("ggml_vk_dsv4_hc_comb(" << mixes << ", " << scale << ", " << base << ", " << dst << ")");
|
||||
|
||||
vk_pipeline pipeline = ctx->device->pipeline_dsv4_hc_comb_f32;
|
||||
GGML_ASSERT(pipeline != nullptr);
|
||||
|
||||
const uint32_t n_tokens = (uint32_t)mixes->ne[1];
|
||||
|
||||
ggml_pipeline_request_descriptor_sets(ctx, pipeline, 1);
|
||||
|
||||
const vk_subbuffer mixes_buf = ggml_vk_tensor_subbuffer(ctx, mixes, true);
|
||||
const vk_subbuffer scale_buf = ggml_vk_tensor_subbuffer(ctx, scale, true);
|
||||
const vk_subbuffer base_buf = ggml_vk_tensor_subbuffer(ctx, base, true);
|
||||
const vk_subbuffer dst_buf = ggml_vk_tensor_subbuffer(ctx, dst, true);
|
||||
|
||||
vk_op_dsv4_hc_comb_push_constants pc = {
|
||||
n_tokens,
|
||||
ggml_vk_nb_elem(mixes, 0), ggml_vk_nb_elem(mixes, 1),
|
||||
ggml_vk_nb_elem(scale, 0),
|
||||
ggml_vk_nb_elem(base, 0),
|
||||
ggml_vk_nb_elem(dst, 0), ggml_vk_nb_elem(dst, 1), ggml_vk_nb_elem(dst, 2),
|
||||
0, 0, 0, 0,
|
||||
ggml_get_op_params_f32(dst, 0),
|
||||
(uint32_t)ggml_get_op_params_i32(dst, 1),
|
||||
};
|
||||
init_pushconst_tensor_offsets(ctx, pc, mixes, scale, base, nullptr, dst);
|
||||
|
||||
ggml_vk_dispatch_pipeline(ctx, subctx, pipeline, { mixes_buf, scale_buf, base_buf, dst_buf }, pc, { n_tokens, 1, 1 });
|
||||
}
|
||||
|
||||
static void ggml_vk_dsv4_hc_pre(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * x, const ggml_tensor * weights, ggml_tensor * dst) {
|
||||
VK_LOG_DEBUG("ggml_vk_dsv4_hc_pre(" << x << ", " << weights << ", " << dst << ")");
|
||||
|
||||
vk_pipeline pipeline = ctx->device->pipeline_dsv4_hc_pre_f32;
|
||||
GGML_ASSERT(pipeline != nullptr);
|
||||
|
||||
const uint32_t n_embd = (uint32_t)x->ne[0];
|
||||
const uint32_t n_tokens = (uint32_t)x->ne[2];
|
||||
|
||||
ggml_pipeline_request_descriptor_sets(ctx, pipeline, 1);
|
||||
|
||||
const vk_subbuffer x_buf = ggml_vk_tensor_subbuffer(ctx, x, true);
|
||||
const vk_subbuffer w_buf = ggml_vk_tensor_subbuffer(ctx, weights, true);
|
||||
const vk_subbuffer d_buf = ggml_vk_tensor_subbuffer(ctx, dst, true);
|
||||
|
||||
vk_op_dsv4_hc_pre_push_constants pc = {
|
||||
n_embd, n_tokens,
|
||||
ggml_vk_nb_elem(x, 0), ggml_vk_nb_elem(x, 1), ggml_vk_nb_elem(x, 2),
|
||||
ggml_vk_nb_elem(weights, 0), ggml_vk_nb_elem(weights, 1),
|
||||
ggml_vk_nb_elem(dst, 0), ggml_vk_nb_elem(dst, 1),
|
||||
0, 0, 0,
|
||||
};
|
||||
init_pushconst_tensor_offsets(ctx, pc, x, weights, nullptr, nullptr, dst);
|
||||
|
||||
ggml_vk_dispatch_pipeline(ctx, subctx, pipeline, { x_buf, w_buf, d_buf }, pc, { n_embd, n_tokens, 1 });
|
||||
}
|
||||
|
||||
static void ggml_vk_dsv4_hc_post(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * x, const ggml_tensor * residual, const ggml_tensor * post, const ggml_tensor * comb, ggml_tensor * dst) {
|
||||
VK_LOG_DEBUG("ggml_vk_dsv4_hc_post(" << x << ", " << residual << ", " << post << ", " << comb << ", " << dst << ")");
|
||||
|
||||
vk_pipeline pipeline = ctx->device->pipeline_dsv4_hc_post_f32;
|
||||
GGML_ASSERT(pipeline != nullptr);
|
||||
|
||||
const uint32_t n_embd = (uint32_t)x->ne[0];
|
||||
const uint32_t n_tokens = (uint32_t)x->ne[1];
|
||||
|
||||
ggml_pipeline_request_descriptor_sets(ctx, pipeline, 1);
|
||||
|
||||
const vk_subbuffer x_buf = ggml_vk_tensor_subbuffer(ctx, x, true);
|
||||
const vk_subbuffer r_buf = ggml_vk_tensor_subbuffer(ctx, residual, true);
|
||||
const vk_subbuffer p_buf = ggml_vk_tensor_subbuffer(ctx, post, true);
|
||||
const vk_subbuffer c_buf = ggml_vk_tensor_subbuffer(ctx, comb, true);
|
||||
const vk_subbuffer d_buf = ggml_vk_tensor_subbuffer(ctx, dst, true);
|
||||
|
||||
vk_op_dsv4_hc_post_push_constants pc = {
|
||||
n_embd, n_tokens,
|
||||
ggml_vk_nb_elem(x, 0), ggml_vk_nb_elem(x, 1),
|
||||
ggml_vk_nb_elem(residual, 0), ggml_vk_nb_elem(residual, 1), ggml_vk_nb_elem(residual, 2),
|
||||
ggml_vk_nb_elem(post, 0), ggml_vk_nb_elem(post, 1),
|
||||
ggml_vk_nb_elem(comb, 0), ggml_vk_nb_elem(comb, 1), ggml_vk_nb_elem(comb, 2),
|
||||
ggml_vk_nb_elem(dst, 0), ggml_vk_nb_elem(dst, 1), ggml_vk_nb_elem(dst, 2),
|
||||
0, 0, 0, 0, 0,
|
||||
};
|
||||
init_pushconst_tensor_offsets(ctx, pc, x, residual, post, comb, dst);
|
||||
|
||||
ggml_vk_dispatch_pipeline(ctx, subctx, pipeline, { x_buf, r_buf, p_buf, c_buf, d_buf }, pc, { n_embd, n_tokens, 1 });
|
||||
}
|
||||
|
||||
static void ggml_vk_mul_mat(ggml_backend_vk_context * ctx, vk_context& subctx, const struct ggml_cgraph * cgraph, int node_idx) {
|
||||
ggml_tensor * dst = cgraph->nodes[node_idx];
|
||||
ggml_tensor * src0 = dst->src[0];
|
||||
@@ -12155,7 +12364,9 @@ template <> void init_pushconst_tensor_offsets(ggml_backend_vk_context * ctx, vk
|
||||
const uint32_t b_offset = get_misalign_bytes(ctx, src1) / ggml_type_size(src1->type);
|
||||
const uint32_t d_offset = get_misalign_bytes(ctx, dst) / ggml_type_size(dst->type);
|
||||
|
||||
GGML_ASSERT(dst->op != GGML_OP_GET_ROWS || (a_offset == 0 && b_offset == 0 && d_offset == 0));
|
||||
GGML_ASSERT(a_offset <= 0xFFFF);
|
||||
GGML_ASSERT(b_offset <= 0xFF);
|
||||
GGML_ASSERT(d_offset <= 0xFF);
|
||||
|
||||
p.misalign_offsets = (a_offset << 16) | (b_offset << 8) | d_offset;
|
||||
|
||||
@@ -16189,6 +16400,18 @@ static bool ggml_vk_build_graph(ggml_backend_vk_context * ctx, ggml_cgraph * cgr
|
||||
case GGML_OP_CUMSUM:
|
||||
ggml_vk_cumsum(ctx, compute_ctx, src0, node);
|
||||
|
||||
break;
|
||||
case GGML_OP_DSV4_HC_COMB:
|
||||
ggml_vk_dsv4_hc_comb(ctx, compute_ctx, src0, src1, src2, node);
|
||||
|
||||
break;
|
||||
case GGML_OP_DSV4_HC_PRE:
|
||||
ggml_vk_dsv4_hc_pre(ctx, compute_ctx, src0, src1, node);
|
||||
|
||||
break;
|
||||
case GGML_OP_DSV4_HC_POST:
|
||||
ggml_vk_dsv4_hc_post(ctx, compute_ctx, src0, src1, src2, src3, node);
|
||||
|
||||
break;
|
||||
case GGML_OP_MEAN:
|
||||
ggml_vk_mean(ctx, compute_ctx, src0, node);
|
||||
@@ -19256,6 +19479,31 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case GGML_OP_DSV4_HC_COMB:
|
||||
case GGML_OP_DSV4_HC_PRE:
|
||||
case GGML_OP_DSV4_HC_POST:
|
||||
{
|
||||
if (op->type != GGML_TYPE_F32) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < GGML_MAX_SRC; ++i) {
|
||||
if (op->src[i] && op->src[i]->type != GGML_TYPE_F32) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// hc is hardcoded to 4 in the shaders. ggml only constrains it
|
||||
// to 4 for COMB, so PRE/POST have to be checked here.
|
||||
if (op->op == GGML_OP_DSV4_HC_PRE && op->src[0]->ne[1] != 4) {
|
||||
return false;
|
||||
}
|
||||
if (op->op == GGML_OP_DSV4_HC_POST && op->src[1]->ne[1] != 4) {
|
||||
return false;
|
||||
}
|
||||
if (op->op == GGML_OP_DSV4_HC_COMB) {
|
||||
return device->pipeline_dsv4_hc_comb_f32 != nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case GGML_OP_SOLVE_TRI:
|
||||
{
|
||||
if (op->type != GGML_TYPE_F32 || op->src[0]->type != GGML_TYPE_F32) {
|
||||
@@ -20244,6 +20492,13 @@ static void ggml_vk_check_results_0(ggml_backend_vk_context * ctx, ggml_cgraph *
|
||||
tensor_clone = ggml_sum_rows(ggml_ctx, src_clone[0]);
|
||||
} else if (tensor->op == GGML_OP_CUMSUM) {
|
||||
tensor_clone = ggml_cumsum(ggml_ctx, src_clone[0]);
|
||||
} else if (tensor->op == GGML_OP_DSV4_HC_COMB) {
|
||||
tensor_clone = ggml_dsv4_hc_comb(ggml_ctx, src_clone[0], src_clone[1], src_clone[2],
|
||||
ggml_get_op_params_f32(tensor, 0), ggml_get_op_params_i32(tensor, 1));
|
||||
} else if (tensor->op == GGML_OP_DSV4_HC_PRE) {
|
||||
tensor_clone = ggml_dsv4_hc_pre(ggml_ctx, src_clone[0], src_clone[1]);
|
||||
} else if (tensor->op == GGML_OP_DSV4_HC_POST) {
|
||||
tensor_clone = ggml_dsv4_hc_post(ggml_ctx, src_clone[0], src_clone[1], src_clone[2], src_clone[3]);
|
||||
} else if (tensor->op == GGML_OP_MEAN) {
|
||||
tensor_clone = ggml_mean(ggml_ctx, src_clone[0]);
|
||||
} else if (tensor->op == GGML_OP_ARGMAX) {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#version 450
|
||||
|
||||
#extension GL_EXT_control_flow_attributes : require
|
||||
#extension GL_KHR_shader_subgroup_basic : require
|
||||
#extension GL_KHR_shader_subgroup_shuffle : require
|
||||
|
||||
// 16 lanes per token, indexed idst + hc*isrc: idst in bits 0..1, isrc in bits 2..3,
|
||||
// so subgroupShuffleXor by 1|2 reduces a row and by 4|8 a column.
|
||||
|
||||
layout(constant_id = 0) const uint SUBGROUP_SIZE = 32;
|
||||
|
||||
layout(local_size_x_id = 0, local_size_y = 4, local_size_z = 1) in;
|
||||
|
||||
layout(push_constant) uniform parameter
|
||||
{
|
||||
uint n_tokens;
|
||||
|
||||
uint nbm0; uint nbm1; // mixes
|
||||
uint nbs0; // scale
|
||||
uint nbb0; // base
|
||||
uint nbd0; uint nbd1; uint nbd2; // dst
|
||||
|
||||
uint m_offset;
|
||||
uint s_offset;
|
||||
uint b_offset;
|
||||
uint d_offset;
|
||||
|
||||
float eps;
|
||||
uint n_iter;
|
||||
};
|
||||
|
||||
layout(binding = 0, std430) readonly buffer M { float data_m[]; };
|
||||
layout(binding = 1, std430) readonly buffer S { float data_s[]; };
|
||||
layout(binding = 2, std430) readonly buffer B { float data_b[]; };
|
||||
layout(binding = 3, std430) writeonly buffer D { float data_d[]; };
|
||||
|
||||
const uint hc = 4;
|
||||
const uint comb_offset = 2 * hc;
|
||||
|
||||
const uint TOKENS_PER_SUBGROUP = SUBGROUP_SIZE / 16;
|
||||
|
||||
void main() {
|
||||
const uint lane = gl_SubgroupInvocationID;
|
||||
const uint blk = lane >> 4; // which 16-lane block, i.e. which token
|
||||
const uint idx = lane & 15; // idst + hc*isrc
|
||||
|
||||
const uint sg = gl_WorkGroupID.x * gl_WorkGroupSize.y + gl_SubgroupID;
|
||||
const uint it = sg * TOKENS_PER_SUBGROUP + blk;
|
||||
|
||||
// no early return, the shuffles need every lane; out-of-range blocks compute a discarded value
|
||||
const bool in_range = it < n_tokens;
|
||||
|
||||
const float scale_comb = data_s[s_offset + 2 * nbs0];
|
||||
|
||||
float v = 0.0f;
|
||||
if (in_range) {
|
||||
v = data_m[m_offset + (comb_offset + idx) * nbm0 + it * nbm1] * scale_comb
|
||||
+ data_b[b_offset + (comb_offset + idx) * nbb0];
|
||||
}
|
||||
|
||||
// Softmax across destinations: the four lanes sharing an isrc.
|
||||
float vmax = max(v, subgroupShuffleXor(v, 1));
|
||||
vmax = max(vmax, subgroupShuffleXor(vmax, 2));
|
||||
v = exp(v - vmax);
|
||||
|
||||
float sum = v + subgroupShuffleXor(v, 1);
|
||||
sum += subgroupShuffleXor(sum, 2);
|
||||
v = v / sum + eps;
|
||||
|
||||
// Normalize columns: equal destination indices are four lanes apart.
|
||||
sum = v + subgroupShuffleXor(v, 4);
|
||||
sum += subgroupShuffleXor(sum, 8);
|
||||
v /= sum + eps;
|
||||
|
||||
for (uint i = 1; i < n_iter; ++i) {
|
||||
sum = v + subgroupShuffleXor(v, 1);
|
||||
sum += subgroupShuffleXor(sum, 2);
|
||||
v /= sum + eps;
|
||||
|
||||
sum = v + subgroupShuffleXor(v, 4);
|
||||
sum += subgroupShuffleXor(sum, 8);
|
||||
v /= sum + eps;
|
||||
}
|
||||
|
||||
if (in_range) {
|
||||
const uint idst = idx & 3;
|
||||
const uint isrc = idx >> 2;
|
||||
data_d[d_offset + idst * nbd0 + isrc * nbd1 + it * nbd2] = v;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#version 450
|
||||
|
||||
#extension GL_EXT_control_flow_attributes : require
|
||||
|
||||
// Fan one stream back out to hc streams and add the combination-weighted
|
||||
// residuals:
|
||||
//
|
||||
// dst[i0, idst, it] = x[i0, it]*post[idst, it]
|
||||
// + sum_isrc residual[i0, isrc, it]*comb[idst, isrc, it]
|
||||
|
||||
layout(constant_id = 0) const uint BLOCK_SIZE = 256;
|
||||
|
||||
layout(local_size_x_id = 0, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
layout(push_constant) uniform parameter
|
||||
{
|
||||
uint n_embd;
|
||||
uint n_tokens;
|
||||
|
||||
uint nbx0; uint nbx1; // x
|
||||
uint nbr0; uint nbr1; uint nbr2; // residual
|
||||
uint nbp0; uint nbp1; // post
|
||||
uint nbc0; uint nbc1; uint nbc2; // comb
|
||||
uint nbd0; uint nbd1; uint nbd2; // dst
|
||||
|
||||
uint x_offset;
|
||||
uint r_offset;
|
||||
uint p_offset;
|
||||
uint c_offset;
|
||||
uint d_offset;
|
||||
};
|
||||
|
||||
layout(binding = 0, std430) readonly buffer X { float data_x[]; };
|
||||
layout(binding = 1, std430) readonly buffer R { float data_r[]; };
|
||||
layout(binding = 2, std430) readonly buffer P { float data_p[]; };
|
||||
layout(binding = 3, std430) readonly buffer C { float data_c[]; };
|
||||
layout(binding = 4, std430) writeonly buffer D { float data_d[]; };
|
||||
|
||||
const uint hc = 4;
|
||||
|
||||
shared float post_s[hc];
|
||||
shared float comb_s[hc * hc];
|
||||
|
||||
void main() {
|
||||
const uint tid = gl_LocalInvocationID.x;
|
||||
const uint it = gl_WorkGroupID.y;
|
||||
|
||||
if (tid < hc) {
|
||||
post_s[tid] = data_p[p_offset + tid * nbp0 + it * nbp1];
|
||||
}
|
||||
if (tid < hc * hc) {
|
||||
const uint idst = tid & 3;
|
||||
const uint isrc = tid >> 2;
|
||||
comb_s[tid] = data_c[c_offset + idst * nbc0 + isrc * nbc1 + it * nbc2];
|
||||
}
|
||||
barrier();
|
||||
|
||||
// After the barrier, so every invocation reaches it.
|
||||
const uint i0 = gl_WorkGroupID.x * BLOCK_SIZE + tid;
|
||||
if (i0 >= n_embd) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float xv = data_x[x_offset + i0 * nbx0 + it * nbx1];
|
||||
|
||||
const uint rb = r_offset + i0 * nbr0 + it * nbr2;
|
||||
|
||||
float r[hc];
|
||||
[[unroll]]
|
||||
for (uint isrc = 0; isrc < hc; ++isrc) {
|
||||
r[isrc] = data_r[rb + isrc * nbr1];
|
||||
}
|
||||
|
||||
[[unroll]]
|
||||
for (uint idst = 0; idst < hc; ++idst) {
|
||||
float result = xv * post_s[idst];
|
||||
[[unroll]]
|
||||
for (uint isrc = 0; isrc < hc; ++isrc) {
|
||||
result = fma(r[isrc], comb_s[idst + hc * isrc], result);
|
||||
}
|
||||
data_d[d_offset + i0 * nbd0 + idst * nbd1 + it * nbd2] = result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#version 450
|
||||
|
||||
#extension GL_EXT_control_flow_attributes : require
|
||||
|
||||
// Collapse the hc residual streams of a token into one, weighted per stream:
|
||||
//
|
||||
// dst[i0, it] = sum_ih x[i0, ih, it] * weights[ih, it]
|
||||
|
||||
layout(constant_id = 0) const uint BLOCK_SIZE = 256;
|
||||
|
||||
layout(local_size_x_id = 0, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
layout(push_constant) uniform parameter
|
||||
{
|
||||
uint n_embd;
|
||||
uint n_tokens;
|
||||
|
||||
uint nbx0; uint nbx1; uint nbx2; // x
|
||||
uint nbw0; uint nbw1; // weights
|
||||
uint nbd0; uint nbd1; // dst
|
||||
|
||||
uint x_offset;
|
||||
uint w_offset;
|
||||
uint d_offset;
|
||||
};
|
||||
|
||||
layout(binding = 0, std430) readonly buffer X { float data_x[]; };
|
||||
layout(binding = 1, std430) readonly buffer W { float data_w[]; };
|
||||
layout(binding = 2, std430) writeonly buffer D { float data_d[]; };
|
||||
|
||||
const uint hc = 4;
|
||||
|
||||
shared float w[hc];
|
||||
|
||||
void main() {
|
||||
const uint tid = gl_LocalInvocationID.x;
|
||||
const uint it = gl_WorkGroupID.y;
|
||||
|
||||
if (tid < hc) {
|
||||
w[tid] = data_w[w_offset + tid * nbw0 + it * nbw1];
|
||||
}
|
||||
barrier();
|
||||
|
||||
// After the barrier, so every invocation reaches it.
|
||||
const uint i0 = gl_WorkGroupID.x * BLOCK_SIZE + tid;
|
||||
if (i0 >= n_embd) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint xb = x_offset + i0 * nbx0 + it * nbx2;
|
||||
|
||||
float result = 0.0f;
|
||||
[[unroll]]
|
||||
for (uint ih = 0; ih < hc; ++ih) {
|
||||
result = fma(data_x[xb + ih * nbx1], w[ih], result);
|
||||
}
|
||||
|
||||
data_d[d_offset + i0 * nbd0 + it * nbd1] = result;
|
||||
}
|
||||
@@ -27,10 +27,10 @@ void main() {
|
||||
const uint i11 = gid_z / p.ne12;
|
||||
const uint i12 = gid_z % p.ne12;
|
||||
|
||||
const uint i01 = data_b[i10*p.nb10 + i11*p.nb11 + i12*p.nb12];
|
||||
const uint i01 = data_b[get_boffset() + i10*p.nb10 + i11*p.nb11 + i12*p.nb12];
|
||||
|
||||
const uint a_offset = i01*p.nb01 + i11*p.nb02 + i12*p.nb03;
|
||||
const uint d_offset = i10*p.nb21 + i11*p.nb22 + i12*p.nb23;
|
||||
const uint a_offset = get_aoffset() + i01*p.nb01 + i11*p.nb02 + i12*p.nb03;
|
||||
const uint d_offset = get_doffset() + i10*p.nb21 + i11*p.nb22 + i12*p.nb23;
|
||||
|
||||
const uint ib = a_offset + i00/QUANT_K; // block index
|
||||
const uint iqs = (i00%QUANT_K)/QUANT_R; // quant index
|
||||
|
||||
@@ -1042,6 +1042,9 @@ void process_shaders() {
|
||||
string_to_spv("fwht_f32", "fwht.comp", {});
|
||||
string_to_spv("fwht_shmem_f32", "fwht.comp", {{"FWHT_SHMEM", "1"}});
|
||||
string_to_spv("count_equal_i32", "count_equal.comp", merge_maps(base_dict, {{"A_TYPE", "int"}, {"B_TYPE", "int"}, {"D_TYPE", "int"}}));
|
||||
string_to_spv("dsv4_hc_comb_f32", "dsv4_hc_comb.comp", {});
|
||||
string_to_spv("dsv4_hc_pre_f32", "dsv4_hc_pre.comp", {});
|
||||
string_to_spv("dsv4_hc_post_f32", "dsv4_hc_post.comp", {});
|
||||
string_to_spv("cumsum_f32", "cumsum.comp", merge_maps(base_dict, {{"A_TYPE", "float"}, {"D_TYPE", "float"}}));
|
||||
string_to_spv("cumsum_multipass1_f32", "cumsum_multipass1.comp", merge_maps(base_dict, {{"A_TYPE", "float"}, {"D_TYPE", "float"}}));
|
||||
string_to_spv("cumsum_multipass2_f32", "cumsum_multipass2.comp", merge_maps(base_dict, {{"A_TYPE", "float"}, {"D_TYPE", "float"}}));
|
||||
|
||||
@@ -4324,12 +4324,22 @@ static bool ggml_backend_webgpu_device_supports_op(ggml_backend_dev_t dev, const
|
||||
src0->type == GGML_TYPE_F32 && (src1->type == GGML_TYPE_I64 || src1->type == GGML_TYPE_I32));
|
||||
break;
|
||||
case GGML_OP_GET_ROWS:
|
||||
if (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16 || ggml_webgpu_supported_qtype(src0->type)) {
|
||||
supports_op = (op->type == GGML_TYPE_F32);
|
||||
} else if (src0->type == GGML_TYPE_I32) {
|
||||
supports_op = op->type == GGML_TYPE_I32;
|
||||
{
|
||||
const size_t storage_alignment =
|
||||
ctx->webgpu_global_ctx->capabilities.limits.minStorageBufferOffsetAlignment;
|
||||
const size_t src_address_unit =
|
||||
src0->type == GGML_TYPE_F32 && op->ne[0] % 4 == 0 ? 4 * sizeof(float) : ggml_type_size(src0->type);
|
||||
if (ggml_webgpu_tensor_misalignment(src0, storage_alignment) % src_address_unit != 0) {
|
||||
break;
|
||||
}
|
||||
if (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16 ||
|
||||
ggml_webgpu_supported_qtype(src0->type)) {
|
||||
supports_op = (op->type == GGML_TYPE_F32);
|
||||
} else if (src0->type == GGML_TYPE_I32) {
|
||||
supports_op = op->type == GGML_TYPE_I32;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GGML_OP_MUL_MAT:
|
||||
{
|
||||
switch (src1->type) {
|
||||
|
||||
@@ -215,6 +215,7 @@ class Keys:
|
||||
KV_LORA_RANK_SWA = "{arch}.attention.kv_lora_rank_swa"
|
||||
SHARED_KV_LAYERS = "{arch}.attention.shared_kv_layers"
|
||||
SLIDING_WINDOW_PATTERN = "{arch}.attention.sliding_window_pattern"
|
||||
RECURRENT_LAYERS = "{arch}.attention.recurrent_layers"
|
||||
TEMPERATURE_SCALE = "{arch}.attention.temperature_scale"
|
||||
ROPE_PATTERN = "{arch}.attention.rope_pattern"
|
||||
|
||||
|
||||
@@ -841,6 +841,9 @@ class GGUFWriter:
|
||||
else:
|
||||
self.add_array(key, value)
|
||||
|
||||
def add_recurrent_layers(self, value: Sequence[bool]) -> None:
|
||||
self.add_array(Keys.Attention.RECURRENT_LAYERS.format(arch=self.arch), value)
|
||||
|
||||
def add_rope_pattern(self, value: Sequence[bool]) -> None:
|
||||
self.add_array(Keys.Attention.ROPE_PATTERN.format(arch=self.arch), value)
|
||||
|
||||
|
||||
@@ -385,6 +385,7 @@ class TensorNameMap:
|
||||
MODEL_TENSOR.ATTN_SINKS: (
|
||||
"model.layers.{bid}.self_attn.sinks", # openai-moe
|
||||
"model.layers.{bid}.self_attn.attention_sink_bias", # mimov2
|
||||
"model.layers.{bid}.self_attn.learnable_sink_param", # hy-v4
|
||||
),
|
||||
|
||||
MODEL_TENSOR.ATTN_GATE: (
|
||||
@@ -392,6 +393,7 @@ class TensorNameMap:
|
||||
"model.layers.{bid}.linear_attn.in_proj_z", # qwen3.5
|
||||
"model.layers.{bid}.self_attn.g_proj", # step3.5 head-wise attention gate
|
||||
"model.layers.{bid}.self_attn.output_gate", # minimax-01
|
||||
"model.layers.{bid}.self_attn.linear_gate", # hy-v4
|
||||
),
|
||||
|
||||
# Feed-forward norm
|
||||
@@ -1329,6 +1331,42 @@ class TensorNameMap:
|
||||
"model.layers.{bid}.self_attn.index_q_norm", # MSA
|
||||
),
|
||||
|
||||
MODEL_TENSOR.HC_ATTN_FN: (
|
||||
"model.layers.{bid}.hc_attn_layer.hc_pre.hc_fn", # hy-v4
|
||||
),
|
||||
|
||||
MODEL_TENSOR.HC_ATTN_BASE: (
|
||||
"model.layers.{bid}.hc_attn_layer.hc_pre.hc_base", # hy-v4
|
||||
),
|
||||
|
||||
MODEL_TENSOR.HC_ATTN_SCALE: (
|
||||
"model.layers.{bid}.hc_attn_layer.hc_pre.hc_scale", # hy-v4
|
||||
),
|
||||
|
||||
MODEL_TENSOR.HC_FFN_FN: (
|
||||
"model.layers.{bid}.hc_mlp_layer.hc_pre.hc_fn", # hy-v4
|
||||
),
|
||||
|
||||
MODEL_TENSOR.HC_FFN_BASE: (
|
||||
"model.layers.{bid}.hc_mlp_layer.hc_pre.hc_base", # hy-v4
|
||||
),
|
||||
|
||||
MODEL_TENSOR.HC_FFN_SCALE: (
|
||||
"model.layers.{bid}.hc_mlp_layer.hc_pre.hc_scale", # hy-v4
|
||||
),
|
||||
|
||||
MODEL_TENSOR.HC_HEAD_FN: (
|
||||
"model.hc_head.hc_head_fn", # hy-v4
|
||||
),
|
||||
|
||||
MODEL_TENSOR.HC_HEAD_BASE: (
|
||||
"model.hc_head.hc_head_base", # hy-v4
|
||||
),
|
||||
|
||||
MODEL_TENSOR.HC_HEAD_SCALE: (
|
||||
"model.hc_head.hc_head_scale", # hy-v4
|
||||
),
|
||||
|
||||
############################################################################
|
||||
# TODO: these do not belong to block_mappings_cfg - move them to mappings_cfg
|
||||
MODEL_TENSOR.ENC_OUTPUT_NORM: (
|
||||
|
||||
+96
-15
@@ -2336,27 +2336,40 @@ struct test_get_rows : public test_case {
|
||||
const int r; // rows to get
|
||||
const int be1; // batch size
|
||||
const int be2; // batch size
|
||||
const bool v; // view (non-contiguous src1)
|
||||
const bool v; // view src1
|
||||
const bool vs0; // view src0
|
||||
|
||||
std::string vars() override {
|
||||
return VARS_TO_STR7(type, n, m, r, be1, be2, v);
|
||||
return VARS_TO_STR8(type, n, m, r, be1, be2, v, vs0);
|
||||
}
|
||||
|
||||
test_get_rows(ggml_type type = GGML_TYPE_F32, int n = 10, int m = 5, int r = 3, int be1 = 1, int be2 = 1, bool v = false)
|
||||
: type(type), n(n), m(m), r(r), be1(be1), be2(be2), v(v) {}
|
||||
test_get_rows(ggml_type type = GGML_TYPE_F32, int n = 10, int m = 5, int r = 3, int be1 = 1, int be2 = 1, bool v = false, bool vs0 = false)
|
||||
: type(type), n(n), m(m), r(r), be1(be1), be2(be2), v(v), vs0(vs0) {}
|
||||
|
||||
ggml_tensor * build_graph(ggml_context * ctx) override {
|
||||
ggml_tensor * in = ggml_new_tensor_4d(ctx, type, n, m, be1, be2);
|
||||
ggml_set_name(in, "in");
|
||||
ggml_tensor * in;
|
||||
if (vs0) {
|
||||
const int offset_rows = 3;
|
||||
const int padded_m = m + offset_rows;
|
||||
ggml_tensor * in_padded = ggml_new_tensor_4d(ctx, type, n, padded_m, be1, be2);
|
||||
ggml_set_name(in_padded, "in_padded");
|
||||
in = ggml_view_4d(ctx, in_padded, n, m, be1, be2,
|
||||
in_padded->nb[1], in_padded->nb[2], in_padded->nb[3],
|
||||
offset_rows * in_padded->nb[1]);
|
||||
ggml_set_name(in, "in_view");
|
||||
} else {
|
||||
in = ggml_new_tensor_4d(ctx, type, n, m, be1, be2);
|
||||
ggml_set_name(in, "in");
|
||||
}
|
||||
|
||||
ggml_tensor * rows = ggml_new_tensor_3d(ctx, GGML_TYPE_I32, r, be1, be2);
|
||||
ggml_tensor * rows = ggml_new_tensor_3d(ctx, GGML_TYPE_I32, v ? r + 1 : r, be1, be2);
|
||||
ggml_set_name(rows, "rows");
|
||||
if (v) {
|
||||
rows = ggml_view_3d(ctx, rows, r/2, be1, be2, rows->nb[1], rows->nb[2], 0);
|
||||
rows = ggml_view_3d(ctx, rows, r/2, be1, be2, rows->nb[1], rows->nb[2], rows->nb[0]);
|
||||
ggml_set_name(rows, "view_of_rows");
|
||||
}
|
||||
|
||||
const bool grad_supported = ggml_is_matrix(in) && ggml_is_vector(rows);
|
||||
const bool grad_supported = !vs0 && ggml_is_matrix(in) && ggml_is_vector(rows);
|
||||
if (grad_supported) {
|
||||
ggml_set_param(in);
|
||||
// rows is a constant input -> no gradients
|
||||
@@ -2370,14 +2383,16 @@ struct test_get_rows : public test_case {
|
||||
|
||||
void initialize_tensors(ggml_context * ctx) override {
|
||||
for (ggml_tensor * t = ggml_get_first_tensor(ctx); t != NULL; t = ggml_get_next_tensor(ctx, t)) {
|
||||
if (ggml_is_view_op(t->op)) {
|
||||
continue;
|
||||
}
|
||||
if (t->type == GGML_TYPE_I32) {
|
||||
if (ggml_is_view_op(t->op)) { continue; }
|
||||
// rows
|
||||
std::vector<int> data(r*be1*be2);
|
||||
for (int i = 0; i < r*be1*be2; i++) {
|
||||
std::vector<int> data(ggml_nelements(t));
|
||||
for (size_t i = 0; i < data.size(); i++) {
|
||||
data[i] = rand() % m;
|
||||
}
|
||||
ggml_backend_tensor_set(t, data.data(), 0, r * be1 * be2 * sizeof(int));
|
||||
ggml_backend_tensor_set(t, data.data(), 0, data.size() * sizeof(int));
|
||||
} else {
|
||||
init_tensor_uniform(t);
|
||||
}
|
||||
@@ -7191,6 +7206,49 @@ struct test_group_norm_mul_add : public test_case {
|
||||
}
|
||||
};
|
||||
|
||||
// GGML_OP_L2_NORM x N: independent same-shape norms in one graph (strided qkv views or
|
||||
// contiguous), consuming adds nested so the norms stay adjacent in the graph.
|
||||
struct test_l2_norm_batch : public test_case {
|
||||
const ggml_type type;
|
||||
const std::array<int64_t, 4> ne;
|
||||
const int n_norms;
|
||||
const float eps;
|
||||
const bool strided;
|
||||
|
||||
std::string vars() override { return VARS_TO_STR5(type, ne, n_norms, eps, strided); }
|
||||
std::string op_desc(ggml_tensor * t) override { GGML_UNUSED(t); return "L2_NORM_BATCH"; }
|
||||
bool run_whole_graph() override { return true; }
|
||||
|
||||
test_l2_norm_batch(ggml_type type = GGML_TYPE_F32, std::array<int64_t, 4> ne = { 128, 16, 16, 1 },
|
||||
int n_norms = 4, float eps = 1e-12f, bool strided = true)
|
||||
: type(type), ne(ne), n_norms(n_norms), eps(eps), strided(strided) {}
|
||||
|
||||
ggml_tensor * build_graph(ggml_context * ctx) override {
|
||||
GGML_ASSERT(n_norms >= 2 && n_norms <= 8);
|
||||
ggml_tensor * parent = nullptr;
|
||||
if (strided) {
|
||||
parent = ggml_new_tensor_4d(ctx, type, ne[0], ne[1] * n_norms, ne[2], ne[3]); // qkv buffer
|
||||
}
|
||||
ggml_tensor * norms[8];
|
||||
for (int t = 0; t < n_norms; ++t) {
|
||||
ggml_tensor * src;
|
||||
if (strided) {
|
||||
src = ggml_view_4d(ctx, parent, ne[0], ne[1], ne[2], ne[3], parent->nb[1], parent->nb[2],
|
||||
parent->nb[3], t * ne[1] * parent->nb[1]);
|
||||
} else {
|
||||
src = ggml_new_tensor(ctx, type, 4, ne.data());
|
||||
}
|
||||
norms[t] = ggml_l2_norm(ctx, src, eps);
|
||||
}
|
||||
ggml_tensor * out = norms[n_norms - 1];
|
||||
for (int t = n_norms - 2; t >= 0; --t) {
|
||||
out = ggml_add(ctx, norms[t], out);
|
||||
}
|
||||
ggml_set_name(out, "out");
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
// GGML_OP_L2_NORM
|
||||
struct test_l2_norm : public test_case {
|
||||
const ggml_type type;
|
||||
@@ -8792,6 +8850,11 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
test_cases.emplace_back(new test_dsv4_hc_comb(17, 4));
|
||||
test_cases.emplace_back(new test_dsv4_hc_comb(257, 8));
|
||||
test_cases.emplace_back(new test_dsv4_hc_comb(17, 20));
|
||||
// production n_iter (DeepSeek-V4 uses 20) across batch sizes that cross
|
||||
// subgroup and workgroup boundaries; 1 = single-token decode
|
||||
for (int64_t n_tokens : {1, 256, 336, 512, 513, 1024, 2048}) {
|
||||
test_cases.emplace_back(new test_dsv4_hc_comb(n_tokens, 20));
|
||||
}
|
||||
|
||||
test_cases.emplace_back(new test_dsv4_hc_pre(1, 1));
|
||||
test_cases.emplace_back(new test_dsv4_hc_pre(31, 17));
|
||||
@@ -8848,13 +8911,17 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
for (ggml_type type : all_types) {
|
||||
for (int b : {1, 7}) {
|
||||
for (bool v : {false, true}) {
|
||||
test_cases.emplace_back(new test_get_rows(type, 256, 5, 4, b, 1, v));
|
||||
for (bool vs0 : {false, true}) {
|
||||
test_cases.emplace_back(new test_get_rows(type, 256, 5, 4, b, 1, v, vs0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int b : {1, 7}) {
|
||||
for (bool v : {false, true}) {
|
||||
test_cases.emplace_back(new test_get_rows(GGML_TYPE_I32, 256, 5, 4, b, 1, v));
|
||||
for (bool vs0 : {false, true}) {
|
||||
test_cases.emplace_back(new test_get_rows(GGML_TYPE_I32, 256, 5, 4, b, 1, v, vs0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9471,6 +9538,10 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
test_cases.emplace_back(new test_l2_norm(GGML_TYPE_F32, { n, 5, 4, 3 }, eps, false));
|
||||
test_cases.emplace_back(new test_l2_norm(GGML_TYPE_F32, { n, 5, 4, 3 }, eps, true));
|
||||
test_cases.emplace_back(new test_l2_norm(GGML_TYPE_F32, { n, 5, 4, 3 }, eps, false, true));
|
||||
// sibling batching: strided (production shape) and contiguous, 2 and 4 wide
|
||||
test_cases.emplace_back(new test_l2_norm_batch(GGML_TYPE_F32, { n, 5, 4, 3 }, 2, eps, true));
|
||||
test_cases.emplace_back(new test_l2_norm_batch(GGML_TYPE_F32, { n, 5, 4, 3 }, 4, eps, true));
|
||||
test_cases.emplace_back(new test_l2_norm_batch(GGML_TYPE_F32, { n, 5, 4, 3 }, 4, eps, false));
|
||||
}
|
||||
// row lengths that are not a multiple of 32, for the scalar (33) and float4 (132, 260) paths
|
||||
for (uint32_t n : { 33, 132, 260 }) {
|
||||
@@ -11157,6 +11228,16 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_perf() {
|
||||
}
|
||||
}
|
||||
|
||||
// launch-overhead isolation: single L2_NORM launch vs batched siblings at the GDN
|
||||
// production shape (strided qkv views) -- perf-mode only, the eval list has its own
|
||||
// 2/4-wide coverage
|
||||
for (int n : { 128, 256 }) {
|
||||
test_cases.emplace_back(new test_l2_norm(GGML_TYPE_F32, { n, 16, 16, 1 }, 1e-12f, false, false));
|
||||
test_cases.emplace_back(new test_l2_norm_batch(GGML_TYPE_F32, { n, 16, 16, 1 }, 2, 1e-12f, true));
|
||||
test_cases.emplace_back(new test_l2_norm_batch(GGML_TYPE_F32, { n, 16, 16, 1 }, 4, 1e-12f, true));
|
||||
}
|
||||
|
||||
|
||||
return test_cases;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,18 +80,19 @@ struct server_lru_sched {
|
||||
}
|
||||
|
||||
// returns "" if no model can be given up
|
||||
std::string pick_victim(std::unique_lock<std::mutex> & lk, const std::string & exclude) {
|
||||
std::string pick_victim(std::unique_lock<std::mutex> & lk) {
|
||||
check_lock(lk);
|
||||
std::string victim;
|
||||
int64_t victim_last_used = 0;
|
||||
for (const auto & m : models.mapping) {
|
||||
if (m.first == exclude) {
|
||||
continue;
|
||||
}
|
||||
// a busy model is mid-request, one still coming up has no request to finish
|
||||
if (m.second.req_count != 0 || !m.second.meta.is_ready_or_sleep()) {
|
||||
continue;
|
||||
}
|
||||
// already on its way out, or a queued request wants it
|
||||
if (models.stopping_models.count(m.first) || find(m.first)) {
|
||||
continue;
|
||||
}
|
||||
if (victim.empty() || m.second.meta.last_used < victim_last_used) {
|
||||
victim = m.first;
|
||||
victim_last_used = m.second.meta.last_used;
|
||||
@@ -109,7 +110,7 @@ struct server_lru_sched {
|
||||
SRV_INF("request for name=%s joined the queue, %d waiting\n", model_id.c_str(), e->n_waiters);
|
||||
return;
|
||||
}
|
||||
queue.push_back({ model_id, 1, false, false });
|
||||
queue.push_back({ model_id, 1, false });
|
||||
SRV_INF("models_max reached, request for name=%s queued at position %zu\n",
|
||||
model_id.c_str(), queue.size());
|
||||
}
|
||||
@@ -144,85 +145,67 @@ struct server_lru_sched {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ok means the model is up: drop the entry, the other waiters just watch its status now
|
||||
// on failure the entry is back in line; on success it stays until its waiters leave,
|
||||
// so the model coming up is never picked as a victim before they use it
|
||||
void claim_done(std::unique_lock<std::mutex> & lk, const std::string & model_id, bool ok) {
|
||||
check_lock(lk);
|
||||
if (ok) {
|
||||
return;
|
||||
}
|
||||
for (auto it = queue.begin(); it != queue.end(); ++it) {
|
||||
if (it->model_id == model_id) {
|
||||
if (ok) {
|
||||
queue.erase(it);
|
||||
} else {
|
||||
it->loading = false;
|
||||
}
|
||||
it->loading = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a model is on its way out for this entry, so other requests do not also give up one
|
||||
void mark_slot_pending(std::unique_lock<std::mutex> & lk, const std::string & model_id) {
|
||||
// evict idle models while queued requests outnumber the slots that are free or being freed
|
||||
// caller must hold models.mutex; never blocks, so it is safe from any thread
|
||||
void tick(std::unique_lock<std::mutex> & lk) {
|
||||
check_lock(lk);
|
||||
if (entry_t * e = find(model_id)) {
|
||||
e->slot_pending = true;
|
||||
if (models.base_params.models_max <= 0 || queue.empty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// model_id went idle: give up its slot if a queued request needs one
|
||||
// thread-safe, caller must NOT hold models.mutex
|
||||
void on_model_idle(const std::string & model_id) {
|
||||
if (models.base_params.models_max <= 0) {
|
||||
return; // no limit, nothing is ever queued
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(models.mutex);
|
||||
if (queue.empty()) {
|
||||
return;
|
||||
}
|
||||
size_t promised = 0;
|
||||
bool has_unserved = false;
|
||||
for (const auto & e : queue) {
|
||||
if (e.needs_slot()) {
|
||||
has_unserved = true;
|
||||
} else {
|
||||
promised++;
|
||||
}
|
||||
}
|
||||
if (!has_unserved) {
|
||||
return;
|
||||
}
|
||||
if ((int) count_running() - (int) promised < models.base_params.models_max) {
|
||||
return; // a slot is already on its way
|
||||
}
|
||||
// never give up a model that a queued request wants
|
||||
for (const auto & e : queue) {
|
||||
if (e.model_id == model_id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto it = models.mapping.find(model_id);
|
||||
if (it == models.mapping.end() || it->second.req_count != 0 || !it->second.meta.is_ready_or_sleep()) {
|
||||
return;
|
||||
}
|
||||
for (auto & e : queue) {
|
||||
if (!e.slot_pending) {
|
||||
e.slot_pending = true;
|
||||
break;
|
||||
int n_running = 0;
|
||||
int n_stopping = 0;
|
||||
for (const auto & m : models.mapping) {
|
||||
if (m.second.meta.is_running()) {
|
||||
n_running++;
|
||||
if (models.stopping_models.count(m.first)) {
|
||||
n_stopping++;
|
||||
}
|
||||
}
|
||||
}
|
||||
SRV_INF("model name=%s went idle, giving up its slot to a queued request\n", model_id.c_str());
|
||||
models.unload(model_id);
|
||||
int n_needed = 0;
|
||||
int n_claimed = 0; // claimed the slot, but load() has not spawned yet
|
||||
for (const auto & e : queue) {
|
||||
if (!e.loading) {
|
||||
n_needed++;
|
||||
continue;
|
||||
}
|
||||
auto it = models.mapping.find(e.model_id);
|
||||
if (it != models.mapping.end() && !it->second.meta.is_running()) {
|
||||
n_claimed++;
|
||||
}
|
||||
}
|
||||
int n_free = models.base_params.models_max - n_running + n_stopping - n_claimed;
|
||||
while (n_free < n_needed) {
|
||||
std::string victim = pick_victim(lk);
|
||||
if (victim.empty()) {
|
||||
return; // all remaining models are busy, wait for a request to end
|
||||
}
|
||||
SRV_INF("evicting idle LRU name=%s for a queued request\n", victim.c_str());
|
||||
models.request_stop(victim);
|
||||
n_free++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct entry_t {
|
||||
std::string model_id;
|
||||
int n_waiters; // requests waiting for this model
|
||||
bool slot_pending; // a model is already being evicted for this entry
|
||||
bool loading; // one of the waiters is doing the load right now
|
||||
|
||||
// a slot is already coming, or already taken by the load in flight
|
||||
bool needs_slot() const { return !slot_pending && !loading; }
|
||||
int n_waiters; // requests waiting for this model
|
||||
bool loading; // one of the waiters is doing the load right now
|
||||
};
|
||||
|
||||
entry_t * find(const std::string & model_id) {
|
||||
@@ -946,7 +929,7 @@ void server_models::unload_lru() {
|
||||
if (sched->has_capacity(lk)) {
|
||||
return;
|
||||
}
|
||||
lru_model_name = sched->pick_victim(lk, "");
|
||||
lru_model_name = sched->pick_victim(lk);
|
||||
}
|
||||
if (!lru_model_name.empty()) {
|
||||
SRV_INF("models_max limit reached, removing LRU name=%s\n", lru_model_name.c_str());
|
||||
@@ -1169,6 +1152,11 @@ void server_models::load(const std::string & name, const load_options & opts) {
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
void server_models::request_stop(const std::string & name) {
|
||||
stopping_models.insert(name);
|
||||
cv_stop.notify_all();
|
||||
}
|
||||
|
||||
void server_models::unload(const std::string & name) {
|
||||
std::unique_lock<std::mutex> lk(mutex);
|
||||
auto it = mapping.find(name);
|
||||
@@ -1182,13 +1170,12 @@ void server_models::unload(const std::string & name) {
|
||||
});
|
||||
} else if (it->second.meta.is_running()) {
|
||||
SRV_INF("stopping model instance name=%s\n", name.c_str());
|
||||
stopping_models.insert(name);
|
||||
if (it->second.meta.status == SERVER_MODEL_STATUS_LOADING) {
|
||||
// special case: if model is in loading state, unloading means force-killing it
|
||||
SRV_WRN("model name=%s is still loading, force-killing\n", name.c_str());
|
||||
it->second.subproc->terminate();
|
||||
}
|
||||
cv_stop.notify_all();
|
||||
request_stop(name);
|
||||
// status change will be handled by the managing thread
|
||||
} else {
|
||||
SRV_WRN("model instance name=%s is not running\n", name.c_str());
|
||||
@@ -1206,8 +1193,7 @@ void server_models::unload_all() {
|
||||
inst.subproc->stopped.store(true, std::memory_order_relaxed);
|
||||
} else if (inst.meta.is_running()) {
|
||||
SRV_INF("stopping model instance name=%s\n", name.c_str());
|
||||
stopping_models.insert(name);
|
||||
cv_stop.notify_all();
|
||||
request_stop(name);
|
||||
// status change will be handled by the managing thread
|
||||
}
|
||||
// moving the thread to join list to avoid deadlock
|
||||
@@ -1234,6 +1220,8 @@ void server_models::update_status(const std::string & name, const update_status_
|
||||
if (!args.progress.is_null()) {
|
||||
meta.progress = args.progress;
|
||||
}
|
||||
// a model that comes up idle or goes down changes the slot count for queued requests
|
||||
sched->tick(lk);
|
||||
}
|
||||
// broadcast status change to SSE
|
||||
{
|
||||
@@ -1380,13 +1368,11 @@ bool server_models::ensure_model_ready(const std::string & name, const std::func
|
||||
|
||||
bool queued = false;
|
||||
bool did_load = false;
|
||||
std::string victim;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mutex);
|
||||
auto it = mapping.find(name);
|
||||
if (it != mapping.end() && it->second.meta.status == SERVER_MODEL_STATUS_UNLOADED) {
|
||||
bool has_capacity = sched->has_capacity(lk);
|
||||
if (has_capacity && sched->queue_empty(lk)) {
|
||||
if (sched->has_capacity(lk) && sched->queue_empty(lk)) {
|
||||
lk.unlock();
|
||||
SRV_INF("model name=%s is not loaded, loading...\n", name.c_str());
|
||||
load(name);
|
||||
@@ -1394,21 +1380,11 @@ bool server_models::ensure_model_ready(const std::string & name, const std::func
|
||||
} else {
|
||||
// also queue when a slot looks free but others wait already, else they starve
|
||||
sched->join(lk, name);
|
||||
sched->tick(lk);
|
||||
queued = true;
|
||||
if (!has_capacity) {
|
||||
// an idle model may sit here right now, do not wait for a request to end
|
||||
victim = sched->pick_victim(lk, name);
|
||||
if (!victim.empty()) {
|
||||
sched->mark_slot_pending(lk, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!victim.empty()) {
|
||||
SRV_INF("evicting idle LRU name=%s to make room for name=%s\n", victim.c_str(), name.c_str());
|
||||
unload(victim);
|
||||
}
|
||||
|
||||
// while queued, this is also where the load happens: the head of the queue does it
|
||||
SRV_INF("waiting until model name=%s is fully loaded...\n", name.c_str());
|
||||
@@ -1470,9 +1446,7 @@ bool server_models::ensure_model_ready(const std::string & name, const std::func
|
||||
}
|
||||
lk.lock();
|
||||
sched->claim_done(lk, name, ok);
|
||||
if (ok) {
|
||||
queued = false; // entry is gone, the other waiters watch the status now
|
||||
}
|
||||
sched->tick(lk);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1480,6 +1454,7 @@ bool server_models::ensure_model_ready(const std::string & name, const std::func
|
||||
}
|
||||
} catch (...) {
|
||||
leave_queue();
|
||||
sched->tick(lk); // a slot freed for this waiter goes to the next one
|
||||
throw;
|
||||
}
|
||||
leave_queue();
|
||||
@@ -1529,18 +1504,14 @@ server_http_res_ptr server_models::proxy_request(const server_http_req & req, co
|
||||
);
|
||||
|
||||
proxy->cleanup = [this, name]() {
|
||||
bool went_idle = false;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mutex);
|
||||
auto it = mapping.find(name);
|
||||
if (it != mapping.end() && it->second.req_count > 0) {
|
||||
it->second.req_count--;
|
||||
went_idle = it->second.req_count == 0;
|
||||
std::unique_lock<std::mutex> lk(mutex);
|
||||
auto it = mapping.find(name);
|
||||
if (it != mapping.end() && it->second.req_count > 0) {
|
||||
it->second.req_count--;
|
||||
if (it->second.req_count == 0) {
|
||||
sched->tick(lk);
|
||||
}
|
||||
}
|
||||
if (went_idle) {
|
||||
sched->on_model_idle(name);
|
||||
}
|
||||
};
|
||||
|
||||
return proxy;
|
||||
|
||||
@@ -216,6 +216,10 @@ private:
|
||||
// not thread-safe, caller must hold mutex
|
||||
void add_model(server_model_meta && meta);
|
||||
|
||||
// ask the monitoring thread to stop a running instance
|
||||
// not thread-safe, caller must hold mutex
|
||||
void request_stop(const std::string & name);
|
||||
|
||||
// notify SSE clients
|
||||
void notify_sse(const std::string & event, const std::string & model_id, const json & data = nullptr);
|
||||
|
||||
|
||||
@@ -297,6 +297,26 @@ def test_router_queue_is_fifo():
|
||||
assert first.done_at < second.done_at, "queue was not served in arrival order"
|
||||
|
||||
|
||||
def test_router_queue_two_waiters_share_one_eviction():
|
||||
"""two requests that both find the same idle model must both be served in the end"""
|
||||
global server
|
||||
server.models_max = 1
|
||||
server.start()
|
||||
|
||||
_load_model_and_wait(MODEL_A, timeout=120)
|
||||
|
||||
# both arrive while MODEL_A is idle, so both want its slot; only one eviction can happen
|
||||
first = _Bg(lambda: _tokenize(MODEL_B)).start()
|
||||
second = _Bg(lambda: _tokenize(MODEL_C)).start()
|
||||
|
||||
first.join(90)
|
||||
second.join(90)
|
||||
|
||||
first.assert_ok("first queued request")
|
||||
second.assert_ok("second queued request")
|
||||
assert _get_model_status(MODEL_A) == "unloaded"
|
||||
|
||||
|
||||
def test_router_no_models_autoload():
|
||||
global server
|
||||
server.no_models_autoload = True
|
||||
|
||||
Reference in New Issue
Block a user