mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-08-28 02:57:42 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca3d5a3e10 | ||
|
|
e70802a01f | ||
|
|
83d855c5a6 | ||
|
|
18443257a3 | ||
|
|
32176338a6 | ||
|
|
6c84c7d5d8 | ||
|
|
6fdd0ac890 | ||
|
|
b10f9ca58c | ||
|
|
58546250cf | ||
|
|
732707dff2 | ||
|
|
cb300598d5 | ||
|
|
1a946ec745 | ||
|
|
fac889fb38 | ||
|
|
cae63579b6 | ||
|
|
bcb6084a4e | ||
|
|
fe235f4343 |
@@ -714,10 +714,10 @@ jobs:
|
||||
with:
|
||||
key: release-windows-2025-vs2026-${{ matrix.arch }}-cpu
|
||||
|
||||
# TODO: build only the ggml-hip backend like the other windows backend jobs
|
||||
# (windows-cuda, windows-sycl), then drop the ui-build dependency
|
||||
# note: builds only the ggml-hip backend - llama-server is injected from the
|
||||
# windows-cpu zip during the release "Merge artifacts" step
|
||||
windows-rocm:
|
||||
needs: [check-release, ui-build]
|
||||
needs: [check-release]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: windows-2022
|
||||
@@ -736,11 +736,9 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download UI build
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
- name: Install Ninja
|
||||
run: |
|
||||
choco install ninja
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.21
|
||||
@@ -795,33 +793,28 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. `
|
||||
-G "Unix Makefiles" `
|
||||
cmake -S . -B build `
|
||||
-G "Ninja Multi-Config" `
|
||||
-DCMAKE_PREFIX_PATH="${env:HIP_PATH}" `
|
||||
-DCMAKE_BUILD_TYPE=Release `
|
||||
-DGGML_BACKEND_DL=ON `
|
||||
-DGGML_NATIVE=OFF `
|
||||
-DGGML_CPU=ON `
|
||||
-DGGML_CPU_ALL_VARIANTS=ON `
|
||||
-DGGML_CPU=OFF `
|
||||
-DGGML_HIP=ON `
|
||||
-DCMAKE_C_COMPILER="${env:HIP_PATH}\lib\llvm\bin\clang.exe" `
|
||||
-DCMAKE_CXX_COMPILER="${env:HIP_PATH}\lib\llvm\bin\clang++.exe" `
|
||||
-DCMAKE_C_FLAGS="-Wno-error=incompatible-pointer-types" `
|
||||
-DCMAKE_HIP_COMPILER="${env:HIP_PATH}\lib\llvm\bin\clang.exe" `
|
||||
-DHIP_PATH="${env:HIP_PATH}" `
|
||||
-DGGML_HIP_ROCWMMA_FATTN=ON `
|
||||
-DAMDGPU_TARGETS="${{ matrix.gpu_targets }}"
|
||||
cmake --build . --config Release --parallel ${env:NUMBER_OF_PROCESSORS}
|
||||
cmake --build build --config Release --parallel ${env:NUMBER_OF_PROCESSORS} --target ggml-hip
|
||||
|
||||
- name: Verify HIP backend was built
|
||||
run: |
|
||||
$hipDll = Get-ChildItem -Path build\bin -Filter "ggml-hip*.dll" -ErrorAction SilentlyContinue
|
||||
$hipDll = Get-ChildItem -Path build\bin\Release -Filter "ggml-hip*.dll" -ErrorAction SilentlyContinue
|
||||
if (-not $hipDll) {
|
||||
Write-Host "##[error]ggml-hip*.dll was NOT produced. The HIP backend silently failed to build."
|
||||
Write-Host "Contents of build\bin:"
|
||||
Get-ChildItem build\bin | Format-Table -AutoSize
|
||||
Write-Host "Contents of build\bin\Release:"
|
||||
Get-ChildItem build\bin\Release | Format-Table -AutoSize
|
||||
exit 1
|
||||
}
|
||||
Write-Host "HIP backend artifact found:"
|
||||
@@ -836,10 +829,40 @@ jobs:
|
||||
$rocmVersionShort = ('${{ matrix.ROCM_VERSION }}'.Split('.')[0..1] -join '.')
|
||||
echo "ROCM_VERSION_SHORT=$rocmVersionShort" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Bundle HIP runtime DLLs (amdhip64_7.dll, rocm_kpack.dll, amd_comgr.dll)
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
# See issue https://github.com/ggml-org/llama.cpp/issues/26929.
|
||||
# ggml-hip.dll loads amdhip64_7.dll at run time. The Adrenalin driver
|
||||
# ships an amdhip64_7.dll in System32, which the loader searches before PATH,
|
||||
# so a matching DLL from PATH cannot win. Copy amdhip64 next to the
|
||||
# binaries (exe directory is searched before System32) so the correct
|
||||
# runtime is used. rocm_kpack.dll is amdhip64_7's direct dependency, so
|
||||
# copy the matching version too. amd_comgr is copied as well to keep it
|
||||
# in sync with the bundled amdhip64, avoiding a version mismatch with a
|
||||
# amd_comgr from System32.
|
||||
# rocblas/hipblaslt kernels resolve fine via PATH and are not copied.
|
||||
$binPath = (rocm-sdk path --bin).Trim()
|
||||
if (-not $binPath) { throw "rocm-sdk path --bin returned empty" }
|
||||
write-host "ROCm bin path: $binPath"
|
||||
|
||||
$patterns = @("amdhip64_7.dll", "rocm_kpack.dll", "amd_comgr.dll")
|
||||
foreach ($pattern in $patterns) {
|
||||
$files = Get-ChildItem -Path $binPath -Filter $pattern -ErrorAction SilentlyContinue
|
||||
if (-not $files) { throw "no match for $pattern in $binPath" }
|
||||
foreach ($f in $files) {
|
||||
Copy-Item $f.FullName -Destination build\bin\Release -Force
|
||||
write-host " copied $($f.Name)"
|
||||
}
|
||||
}
|
||||
|
||||
- name: Pack artifacts
|
||||
run: |
|
||||
cp "LICENSE" "build\bin\"
|
||||
7z a -snl llama-bin-win-rocm-${{ env.ROCM_VERSION_SHORT }}-${{ matrix.build }}.zip .\build\bin\*
|
||||
7z a -snl llama-bin-win-rocm-${{ env.ROCM_VERSION_SHORT }}-${{ matrix.build }}.zip `
|
||||
.\build\bin\Release\ggml-hip.dll `
|
||||
.\build\bin\Release\amdhip64_7.dll `
|
||||
.\build\bin\Release\rocm_kpack.dll `
|
||||
.\build\bin\Release\amd_comgr.dll
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
|
||||
@@ -1643,6 +1643,14 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
}
|
||||
}
|
||||
).set_env("LLAMA_ARG_CTX_SIZE"));
|
||||
add_opt(common_arg(
|
||||
{ "--kv-unified-per-slot" }, "N",
|
||||
"context limit per parallel slot (default: unset, behavior unchanged).\n"
|
||||
"when set without -c/--ctx-size, the shared KV pool is sized to n_parallel*N",
|
||||
[](common_params & params, int value) {
|
||||
params.kv_unified_per_slot = value;
|
||||
}
|
||||
).set_env("LLAMA_ARG_KV_UNIFIED_PER_SLOT").set_examples({ LLAMA_EXAMPLE_SERVER }));
|
||||
add_opt(common_arg(
|
||||
{"-n", "--predict", "--n-predict"}, "N",
|
||||
string_format(
|
||||
@@ -2720,6 +2728,19 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
else { throw std::invalid_argument("invalid value"); }
|
||||
}
|
||||
).set_env("LLAMA_ARG_LOAD_MODE"));
|
||||
add_opt(common_arg(
|
||||
{"--tensor-read-lazy"}, "MODE",
|
||||
"on-demand reading of certain tensors, for example per-layer embeddings (default: auto)\n"
|
||||
"- on: read the rows of such tensors from disk on demand instead of keeping them resident (requires mmap)\n"
|
||||
"- auto: on, but only for tensors larger than 4 GiB\n"
|
||||
"- off: always keep them resident",
|
||||
[](common_params & params, const std::string & value) {
|
||||
/**/ if (value == "on") { params.tensor_read_lazy = LLAMA_TENSOR_READ_LAZY_ON; }
|
||||
else if (value == "auto") { params.tensor_read_lazy = LLAMA_TENSOR_READ_LAZY_AUTO; }
|
||||
else if (value == "off") { params.tensor_read_lazy = LLAMA_TENSOR_READ_LAZY_OFF; }
|
||||
else { throw std::invalid_argument("invalid value"); }
|
||||
}
|
||||
).set_env("LLAMA_ARG_TENSOR_READ_LAZY"));
|
||||
add_opt(common_arg(
|
||||
{"--numa"}, "TYPE",
|
||||
"attempt optimizations that help on some NUMA systems\n"
|
||||
|
||||
@@ -1688,6 +1688,7 @@ struct llama_model_params common_model_params_to_llama(common_params & params) {
|
||||
mparams.main_gpu = params.main_gpu;
|
||||
mparams.split_mode = params.split_mode;
|
||||
mparams.load_mode = params.load_mode;
|
||||
mparams.tensor_read_lazy = params.tensor_read_lazy;
|
||||
mparams.tensor_split = params.tensor_split;
|
||||
mparams.check_tensors = params.check_tensors;
|
||||
mparams.use_extra_bufts = !params.no_extra_bufts;
|
||||
|
||||
@@ -483,6 +483,8 @@ struct common_params {
|
||||
enum llama_split_mode split_mode = LLAMA_SPLIT_MODE_LAYER; // how to split the model across GPUs
|
||||
enum llama_load_mode load_mode = LLAMA_LOAD_MODE_AUTO; // how to load the model
|
||||
|
||||
enum llama_tensor_read_lazy tensor_read_lazy = LLAMA_TENSOR_READ_LAZY_AUTO; // on-demand reading of tensors marked by the arch
|
||||
|
||||
common_cpu_params cpuparams;
|
||||
common_cpu_params cpuparams_batch;
|
||||
|
||||
@@ -625,6 +627,7 @@ struct common_params {
|
||||
bool cache_prompt = true; // whether to enable prompt caching
|
||||
bool cache_idle_slots = true; // save and clear idle slots upon starting a new task
|
||||
int32_t n_ctx_checkpoints = 32; // max number of context checkpoints per slot
|
||||
int32_t kv_unified_per_slot = 0; // max context per parallel slot; 0 = unset
|
||||
int32_t checkpoint_min_step = 8192; // minimum spacing between context checkpoints
|
||||
int32_t cache_ram_mib = 8192; // -1 = no limit, 0 - disable, 1 = 1 MiB, etc.
|
||||
|
||||
|
||||
+86
-6
@@ -925,12 +925,19 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
int32_t block_size = 0;
|
||||
llama_token mask_token_id = 0;
|
||||
|
||||
bool is_dflash2 = false;
|
||||
bool is_mrope = false;
|
||||
int32_t selector_top_k = 0;
|
||||
|
||||
// draft-dspark: the draft carries a Markov head and uses an anchor-first block layout
|
||||
const bool is_dspark;
|
||||
|
||||
// dspark speculators
|
||||
bool sample_from_anchor = true;
|
||||
|
||||
// block-internal attention
|
||||
bool causal_attn = false;
|
||||
|
||||
const int32_t * target_layer_ids = nullptr; // model_dft's extract layer indices
|
||||
uint32_t target_layer_ids_n = 0;
|
||||
|
||||
@@ -968,9 +975,25 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
if (llama_model_meta_val_str(model_dft, "dflash.sample_from_anchor", buf, sizeof(buf)) >= 0) {
|
||||
sample_from_anchor = std::strcmp(buf, "true") == 0;
|
||||
}
|
||||
if (llama_model_meta_val_str(model_dft, "dflash.attention.causal", buf, sizeof(buf)) >= 0) {
|
||||
causal_attn = std::strcmp(buf, "true") == 0;
|
||||
}
|
||||
}
|
||||
|
||||
selector_top_k = llama_model_dflash_selector_top_k(model_dft);
|
||||
is_dflash2 = selector_top_k > 0;
|
||||
mask_token_id = llama_vocab_mask(llama_model_get_vocab(model_dft));
|
||||
|
||||
if (is_dspark && this->params.p_min > 0.0f) {
|
||||
char buf[16] = {};
|
||||
const bool has_conf =
|
||||
llama_model_meta_val_str(model_dft, "dflash.has_confidence_head", buf, sizeof(buf)) < 0 ||
|
||||
std::strcmp(buf, "true") == 0;
|
||||
if (!has_conf) {
|
||||
throw std::runtime_error("DSpark draft has no confidence head: please set --spec-draft-p-min 0");
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INF("%s: adding speculative implementation '%s'\n", __func__, common_speculative_type_to_str(type).c_str());
|
||||
LOG_INF("%s: - n_max=%d, n_min=%d, p_min=%.2f\n", __func__, this->params.n_max, this->params.n_min, this->params.p_min);
|
||||
LOG_INF("%s: - block_size=%d, mask_token_id=%d, n_extract=%u, sample_from_anchor=%s\n", __func__,
|
||||
@@ -990,6 +1013,13 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
batch = llama_batch_init(llama_n_batch(ctx_dft), 0, n_seq);
|
||||
batch_inject = llama_batch_init(llama_n_batch(ctx_dft), n_embd_dec, n_seq);
|
||||
|
||||
// embd batches on an M-RoPE draft need 4 position rows per token
|
||||
is_mrope = llama_model_rope_type(model_dft) == LLAMA_ROPE_TYPE_MROPE;
|
||||
if (is_mrope) {
|
||||
free(batch_inject.pos);
|
||||
batch_inject.pos = (llama_pos *) malloc(sizeof(llama_pos) * 4 * llama_n_batch(ctx_dft));
|
||||
}
|
||||
|
||||
smpls.resize(n_seq);
|
||||
for (auto & s : smpls) {
|
||||
common_params_sampling sparams;
|
||||
@@ -1001,7 +1031,7 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
|
||||
// offload draft sampling to the backend
|
||||
backend_chains.assign(n_seq, nullptr);
|
||||
if (this->params.backend_sampling) {
|
||||
if (this->params.backend_sampling && !is_dflash2) {
|
||||
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
|
||||
llama_sampler * chain = llama_sampler_chain_init(llama_sampler_chain_default_params());
|
||||
llama_sampler_chain_add(chain, llama_sampler_init_top_k(10));
|
||||
@@ -1020,8 +1050,9 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
llama_set_embeddings_layer_inp(ctx_tgt, (uint32_t) target_layer_ids[k], true);
|
||||
}
|
||||
|
||||
llama_set_embeddings_nextn(ctx_dft, true, /*masked*/ true);
|
||||
llama_set_causal_attn(ctx_dft, false); // DFlash needs non-causal attention
|
||||
// DFlash2 reads its selector lattice from h_nextn and never consumes raw logits.
|
||||
llama_set_embeddings_nextn(ctx_dft, true, /*masked*/ !is_dflash2);
|
||||
llama_set_causal_attn(ctx_dft, causal_attn); // DFlash needs non-causal attention unless the model says otherwise
|
||||
}
|
||||
|
||||
~common_speculative_impl_draft_dflash() override {
|
||||
@@ -1121,11 +1152,24 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
}
|
||||
|
||||
// fuse extracted features through DFlash encoder
|
||||
// M-RoPE drafts read 4 position rows per token from embd batches, so pass them explicitly
|
||||
std::vector<llama_pos> enc_pos;
|
||||
if (is_mrope) {
|
||||
enc_pos.resize((size_t) 4 * n_chunk);
|
||||
for (int32_t i = 0; i < n_chunk; ++i) {
|
||||
const llama_pos p = batch_in.pos[i_batch_beg[seq_id] + offset + i];
|
||||
enc_pos[0 * n_chunk + i] = p;
|
||||
enc_pos[1 * n_chunk + i] = p;
|
||||
enc_pos[2 * n_chunk + i] = p;
|
||||
enc_pos[3 * n_chunk + i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
llama_batch enc_batch = {
|
||||
/*.n_tokens =*/ n_chunk,
|
||||
/*.token =*/ nullptr,
|
||||
/*.embd =*/ features_buf.data(),
|
||||
/*.pos =*/ nullptr,
|
||||
/*.pos =*/ is_mrope ? enc_pos.data() : nullptr,
|
||||
/*.n_seq_id =*/ nullptr,
|
||||
/*.seq_id =*/ nullptr,
|
||||
/*.logits =*/ nullptr,
|
||||
@@ -1146,7 +1190,13 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
std::memcpy(batch_inject.embd, inp_g, (size_t) n_chunk * n_embd_dec * sizeof(float));
|
||||
|
||||
for (int32_t i = 0; i < n_chunk; ++i) {
|
||||
batch_inject.pos[i] = batch_in.pos[i_batch_beg[seq_id] + offset + i];
|
||||
const llama_pos p = batch_in.pos[i_batch_beg[seq_id] + offset + i];
|
||||
batch_inject.pos[i] = p;
|
||||
if (is_mrope) {
|
||||
batch_inject.pos[1 * n_chunk + i] = p;
|
||||
batch_inject.pos[2 * n_chunk + i] = p;
|
||||
batch_inject.pos[3 * n_chunk + i] = 0;
|
||||
}
|
||||
batch_inject.n_seq_id[i] = 1;
|
||||
batch_inject.seq_id[i][0] = seq_id;
|
||||
batch_inject.logits[i] = false;
|
||||
@@ -1189,7 +1239,7 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
i_block_beg[seq_id] = batch.n_tokens;
|
||||
n_block [seq_id] = n_block_tokens;
|
||||
for (int32_t i = 0; i < n_block_tokens; ++i) {
|
||||
common_batch_add(batch, i == 0 ? dp.id_last : mask_token_id, n + i, { seq_id }, true);
|
||||
common_batch_add(batch, i == 0 ? dp.id_last : mask_token_id, n + i, { seq_id }, !is_dflash2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1217,6 +1267,36 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
|
||||
auto & result = *dp.result;
|
||||
|
||||
if (is_dflash2) {
|
||||
const float * lattice = llama_get_embeddings_nextn(ctx_dft);
|
||||
GGML_ASSERT(lattice && "DFlash2 selector produced no lattice");
|
||||
|
||||
int32_t predecessor = 0;
|
||||
for (int32_t i = 1; i < n_block_tokens; ++i) {
|
||||
const float * row = lattice + (size_t) (beg + i) * n_embd_dec;
|
||||
const float * scores = row + selector_top_k + (size_t) predecessor * selector_top_k;
|
||||
|
||||
predecessor = (int32_t) std::distance(scores,
|
||||
std::max_element(scores, scores + selector_top_k));
|
||||
if (params.p_min > 0.0f) {
|
||||
// softmax(scores) at the argmax, i.e. 1 / sum(exp(s_k - s_max))
|
||||
float sum = 0.0f;
|
||||
for (int32_t k = 0; k < selector_top_k; ++k) {
|
||||
sum += std::exp(scores[k] - scores[predecessor]);
|
||||
}
|
||||
if (1.0f / sum < params.p_min) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.push_back((llama_token) row[predecessor]);
|
||||
}
|
||||
|
||||
if (result.size() < (size_t) params.n_min) {
|
||||
result.clear();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_dspark) {
|
||||
// DSpark: read from the first draft slot, truncate below the confidence threshold
|
||||
const float * conf = params.p_min > 0.0f ? llama_get_embeddings_nextn(ctx_dft) : nullptr;
|
||||
|
||||
@@ -54,6 +54,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"DeepseekV3ForCausalLM": "deepseek",
|
||||
"DeepseekV32ForCausalLM": "deepseek",
|
||||
"DFlashDraftModel": "qwen",
|
||||
"DFlash2DraftModel": "qwen",
|
||||
"Qwen3DSparkModel": "qwen",
|
||||
"DSparkDraftModel": "qwen",
|
||||
"DSparkSpeculator": "qwen",
|
||||
@@ -235,6 +236,8 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"Qwen3_5ForConditionalGeneration": "qwen",
|
||||
"Qwen3_5MoeForCausalLM": "qwen",
|
||||
"Qwen3_5MoeForConditionalGeneration": "qwen",
|
||||
"Qwen4ExpForCausalLM": "qwen4exp",
|
||||
"Qwen4ExpForConditionalGeneration": "qwen4exp",
|
||||
"RND1": "qwen",
|
||||
"RWForCausalLM": "falcon",
|
||||
"RWKV6Qwen2ForCausalLM": "rwkv",
|
||||
@@ -332,6 +335,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"Qwen3VLMoeForConditionalGeneration": "qwen3vl",
|
||||
"Qwen3_5ForConditionalGeneration": "qwen3vl",
|
||||
"Qwen3_5MoeForConditionalGeneration": "qwen3vl",
|
||||
"Qwen4ExpForConditionalGeneration": "qwen4exp",
|
||||
"RADIOModel": "nemotron",
|
||||
"Sarashina2VisionForCausalLM": "sarashina2",
|
||||
"SmolVLMForConditionalGeneration": "smolvlm",
|
||||
|
||||
+6
-2
@@ -1006,12 +1006,16 @@ class ModelBase:
|
||||
else:
|
||||
raise ValueError(f"Unknown file type: {self.ftype.name}")
|
||||
|
||||
# a chunked tensor quantizes as one chunk at a time, while it is written
|
||||
quantize = data.quantize if isinstance(data, gguf.LazyChunkedTensor) else (
|
||||
lambda qtype, d=data: gguf.quants.quantize(d, qtype))
|
||||
|
||||
try:
|
||||
data = gguf.quants.quantize(data, data_qtype)
|
||||
data = quantize(data_qtype)
|
||||
except gguf.QuantError as e:
|
||||
logger.warning("%s, %s", e, "falling back to F16")
|
||||
data_qtype = gguf.GGMLQuantizationType.F16
|
||||
data = gguf.quants.quantize(data, data_qtype)
|
||||
data = quantize(data_qtype)
|
||||
|
||||
shape = gguf.quant_shape_from_byte_shape(data.shape, data_qtype) if data.dtype == np.uint8 else data.shape
|
||||
|
||||
|
||||
@@ -302,6 +302,10 @@ class NemotronHModel(GraniteHybridModel):
|
||||
)
|
||||
if not keep:
|
||||
return None
|
||||
# PEFT names adapter tensors using model.layers.*, while Nemotron-H checkpoints
|
||||
# and the GGUF tensor map use backbone.layers.*
|
||||
if name.startswith("model.layers.") and ".mixer." in name:
|
||||
name = name.replace("model.layers.", "backbone.layers.", 1)
|
||||
return super().filter_tensors((name, gen))
|
||||
|
||||
def prepare_metadata(self, vocab_only: bool):
|
||||
|
||||
+74
-6
@@ -639,7 +639,7 @@ class Qwen3_5MoeTextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
|
||||
model_arch = gguf.MODEL_ARCH.QWEN35MOE
|
||||
|
||||
|
||||
@ModelBase.register("DFlashDraftModel")
|
||||
@ModelBase.register("DFlashDraftModel", "DFlash2DraftModel")
|
||||
@ModelBase.example("z-lab/Qwen3.5-9B-DFlash")
|
||||
class DFlashModel(Qwen3Model):
|
||||
model_arch = gguf.MODEL_ARCH.DFLASH
|
||||
@@ -678,34 +678,98 @@ class DFlashModel(Qwen3Model):
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
|
||||
block_size = self.hparams.get("block_size", 16)
|
||||
self.gguf_writer.add_block_size(block_size)
|
||||
dflash_config = self.hparams.get("dflash_config", {})
|
||||
block_size = dflash_config.get("block_size", self.hparams.get("block_size", 16))
|
||||
self.gguf_writer.add_block_size(block_size)
|
||||
|
||||
if "conv_kernel_size" in dflash_config:
|
||||
self.gguf_writer.add_conv_kernel_size(int(dflash_config["conv_kernel_size"]))
|
||||
self.gguf_writer.add_conv_group_size(int(dflash_config["conv_group_size"]))
|
||||
self.gguf_writer.add_selector_rank(int(dflash_config["selector_rank"]))
|
||||
self.gguf_writer.add_selector_top_k(int(dflash_config["selector_top_k"]))
|
||||
|
||||
output_multiplier = dflash_config.get(
|
||||
"output_multiplier", self.hparams.get("output_multiplier")
|
||||
)
|
||||
if output_multiplier is not None:
|
||||
self.gguf_writer.add_logit_scale(float(output_multiplier))
|
||||
softcap = dflash_config.get(
|
||||
"final_logit_softcapping", self.hparams.get("final_logit_softcapping")
|
||||
)
|
||||
if softcap is not None and float(softcap) > 0:
|
||||
self.gguf_writer.add_final_logit_softcapping(float(softcap))
|
||||
embedding_scale = dflash_config.get(
|
||||
"input_embedding_scale", self.hparams.get("input_embedding_scale")
|
||||
)
|
||||
if embedding_scale is not None:
|
||||
self.gguf_writer.add_embedding_scale(float(embedding_scale))
|
||||
|
||||
target_layer_ids = dflash_config.get("target_layer_ids", [])
|
||||
if target_layer_ids:
|
||||
extract_layer_ids = [i + 1 for i in target_layer_ids]
|
||||
self.gguf_writer.add_target_layers(extract_layer_ids)
|
||||
|
||||
use_sliding_window = self.hparams.get("use_sliding_window", False)
|
||||
sliding_window = self.hparams.get("sliding_window")
|
||||
use_sliding_window = self.hparams.get("use_sliding_window", False) or dflash_config.get("use_swa", False)
|
||||
sliding_window = dflash_config.get("swa_window_size") or self.hparams.get("sliding_window")
|
||||
layer_types = self.hparams.get("layer_types")
|
||||
if use_sliding_window and sliding_window and layer_types:
|
||||
is_swa = [lt == "sliding_attention" for lt in layer_types]
|
||||
self.gguf_writer.add_sliding_window(sliding_window)
|
||||
self.gguf_writer.add_sliding_window_pattern(is_swa)
|
||||
|
||||
causal = self.hparams.get("is_causal")
|
||||
if causal is None:
|
||||
causal = dflash_config.get("causal")
|
||||
if causal is not None:
|
||||
self.gguf_writer.add_causal_attention(bool(causal))
|
||||
|
||||
# M-RoPE target: the draft ropes on the temporal dim only, so write
|
||||
# degenerate sections [n_rot/2, 0, 0, 0]
|
||||
if self._target_uses_mrope():
|
||||
head_dim = self.hparams.get("head_dim") or self.hparams["hidden_size"] // self.hparams["num_attention_heads"]
|
||||
self.gguf_writer.add_rope_dimension_sections([head_dim // 2, 0, 0, 0])
|
||||
|
||||
def _target_uses_mrope(self) -> bool:
|
||||
if self.target_model_dir is None:
|
||||
return False
|
||||
with open(self.target_model_dir / "config.json", "r", encoding="utf-8") as f:
|
||||
cfg = json.load(f)
|
||||
cfg = cfg.get("text_config", cfg)
|
||||
rope = cfg.get("rope_parameters") or cfg.get("rope_scaling") or {}
|
||||
return "mrope_section" in rope
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
if not name.startswith("model."):
|
||||
name = "model." + name
|
||||
if "sink" in name and not name.endswith(".weight"):
|
||||
name += ".weight"
|
||||
return super().filter_tensors((name, gen))
|
||||
|
||||
_ROPE_PERMUTE_SUFFIXES = (
|
||||
"self_attn.q_proj.weight",
|
||||
"self_attn.k_proj.weight",
|
||||
"self_attn.q_norm.weight",
|
||||
"self_attn.k_norm.weight",
|
||||
)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name == "model.embed_tokens.weight" and not self.hparams.get("has_embed_tokens", True):
|
||||
return
|
||||
|
||||
# interleaved-rope checkpoints (rope_is_neox_style = false) -> NeoX layout: per head, even dims first then odd
|
||||
if not self.hparams.get("rope_is_neox_style", True) and name.endswith(self._ROPE_PERMUTE_SUFFIXES):
|
||||
head_dim = self.hparams["head_dim"]
|
||||
shape = data_torch.shape
|
||||
data_torch = data_torch.reshape(-1, head_dim // 2, 2, *shape[1:]).transpose(1, 2).reshape(shape)
|
||||
|
||||
if name in (
|
||||
"model.candidate_selector.predecessor_codebook",
|
||||
"model.candidate_selector.successor_codebook",
|
||||
):
|
||||
name += ".weight"
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
@@ -759,6 +823,10 @@ class DSparkModel(DFlashModel):
|
||||
super().set_gguf_parameters()
|
||||
self.gguf_writer.add_sample_from_anchor(self._sample_from_anchor)
|
||||
|
||||
# confidence head is optional: vanilla-markov exports ship without it
|
||||
has_conf = any("confidence_head.proj" in name for name in self.model_tensors)
|
||||
self.gguf_writer.add_has_confidence_head(has_conf)
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
if item[0] == "t2d": # not used at runtime
|
||||
@@ -777,7 +845,7 @@ class DSparkModel(DFlashModel):
|
||||
self._d2t = data_torch
|
||||
return
|
||||
|
||||
if self._n_vocab_draft == self.hparams["vocab_size"] and name.endswith(("embed_tokens.weight", "lm_head.weight")):
|
||||
if self._n_vocab_draft == self.hparams["vocab_size"] and name.endswith("lm_head.weight"):
|
||||
return
|
||||
|
||||
# interleaved-rope checkpoints (rope_is_neox_style = false) -> NeoX layout: per head, even dims first then odd
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Iterable, cast
|
||||
|
||||
import torch
|
||||
from torch import Tensor
|
||||
|
||||
import gguf
|
||||
import numpy as np
|
||||
|
||||
from .base import ModelBase
|
||||
from .qwen import _LinearAttentionVReorderBase, _Qwen35MRopeMixin
|
||||
from .qwen3vl import Qwen3VLVisionModel
|
||||
|
||||
|
||||
@ModelBase.register("Qwen4ExpForConditionalGeneration", "Qwen4ExpForCausalLM")
|
||||
@ModelBase.example("Qwen/Qwen3.8-Flash-Next")
|
||||
class Qwen4ExpTextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
|
||||
"""Qwen3.8-Flash-Next.
|
||||
|
||||
Shares the Qwen3.5 gated delta net and interleaved mrope, and adds three things:
|
||||
hyper-connections in place of every layer norm, QSA sparse attention on the full
|
||||
attention layers, and PLE n-gram hash embeddings on a single layer.
|
||||
"""
|
||||
|
||||
model_arch = gguf.MODEL_ARCH.QWEN4EXP
|
||||
|
||||
# the MTP block is a separate draft head; vLLM drops it too
|
||||
supports_mtp_export = False
|
||||
no_mtp = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# only the shard names, so the table itself is never held
|
||||
self._ple_shards: dict[int, str] = {}
|
||||
self._ple_row_dim: int | None = None
|
||||
|
||||
def _read_hash_constants(self, suffix: str) -> list[int]:
|
||||
"""Read an int64 PLE constant straight from the checkpoint.
|
||||
|
||||
prepare_tensors() casts every non-float dtype to float32 before
|
||||
modify_tensors() sees it (base.py), which would silently round these
|
||||
45-bit multipliers. Reading the lazy tensor here bypasses that.
|
||||
"""
|
||||
for name, gen in self.model_tensors.items():
|
||||
if name.endswith(suffix):
|
||||
t = gen()
|
||||
if t.dtype != torch.int64:
|
||||
t = t.to(torch.int64)
|
||||
return [int(x) for x in t.tolist()]
|
||||
raise ValueError(f"PLE constant {suffix!r} missing from the checkpoint")
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
hp = self.hparams
|
||||
|
||||
self.gguf_writer.add_hyper_connection_count(hp["hc_count"])
|
||||
self.gguf_writer.add_hyper_connection_low_rank(hp["hc_lowrank"])
|
||||
|
||||
n_layer = hp["num_hidden_layers"]
|
||||
self.gguf_writer.add_indexer_head_count(hp["indexer_n_heads"])
|
||||
self.gguf_writer.add_indexer_key_length(hp["indexer_head_dim"])
|
||||
self.gguf_writer.add_indexer_top_k(hp["indexer_budget"])
|
||||
ratio = hp["indexer_compress_ratio"]
|
||||
layer_types = hp["layer_types"]
|
||||
self.gguf_writer.add_attention_compress_ratios(
|
||||
[ratio if layer_types[i] == "full_attention" else 0 for i in range(n_layer)]
|
||||
)
|
||||
|
||||
# ple_layer_ids is 1-based in the HF config; empty means no n-gram table,
|
||||
# so emit no PLE keys rather than optional ones
|
||||
ple_layers = [i - 1 for i in hp["ple_layer_ids"]]
|
||||
if not ple_layers:
|
||||
return
|
||||
self.gguf_writer.add_ple_layers(ple_layers)
|
||||
self.gguf_writer.add_ple_ngram_size(hp["ngram_size"])
|
||||
self.gguf_writer.add_ple_heads_per_ngram(hp["heads_per_ngram"])
|
||||
self.gguf_writer.add_ple_conv_kernel(hp["ple_conv_kernel_size"])
|
||||
self.gguf_writer.add_ple_eos_token_id(self._eos_token_id())
|
||||
# an image is decoded as an embeddings-only batch, so the graph has no placeholder
|
||||
# ids to hash; carry the id and let it stand in for those positions
|
||||
_img = self._image_token_id()
|
||||
if _img is not None:
|
||||
self.gguf_writer.add_ple_image_token_id(int(_img))
|
||||
if self._ple_row_dim is not None:
|
||||
self.gguf_writer.add_embedding_length_per_layer_input(self._ple_row_dim)
|
||||
|
||||
self.gguf_writer.add_ple_layer_multipliers(
|
||||
self._read_hash_constants("ple_embedding.layer_multipliers"))
|
||||
self.gguf_writer.add_ple_head_offsets(
|
||||
self._read_hash_constants("ple_embedding.ngram_heads_offsets"))
|
||||
self.gguf_writer.add_ple_head_vocab_sizes(
|
||||
self._read_hash_constants("ple_embedding.ngram_heads_vocab_sizes"))
|
||||
|
||||
def _image_token_id(self) -> int | None:
|
||||
img = self.hparams.get("image_token_id")
|
||||
return None if img is None else int(img)
|
||||
|
||||
def _eos_token_id(self) -> int:
|
||||
eos = self.hparams.get("eos_token_id")
|
||||
if isinstance(eos, list):
|
||||
# the PLE hash resets n-grams on the primary EOS
|
||||
return int(eos[-1])
|
||||
if eos is None:
|
||||
raise ValueError("eos_token_id is required: the PLE hash resets its n-grams on it")
|
||||
return int(eos)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
# int64 hash constants must stay exact; 1-D tensors force F32, so use KV
|
||||
if name.endswith("ple_embedding.layer_multipliers"):
|
||||
self._ple_multipliers = [int(x) for x in data_torch.tolist()]
|
||||
return []
|
||||
if name.endswith("ple_embedding.ngram_heads_offsets"):
|
||||
self._ple_head_offsets = [int(x) for x in data_torch.tolist()]
|
||||
return []
|
||||
if name.endswith("ple_embedding.ngram_heads_vocab_sizes"):
|
||||
self._ple_head_vocab_sizes = [int(x) for x in data_torch.tolist()]
|
||||
return []
|
||||
|
||||
if ".ngram_embedding.shard_" in name:
|
||||
return self._place_ple_shard(data_torch, name)
|
||||
|
||||
# one projection feeds indexer q and k; split it, as minimax-m3 does
|
||||
if ".indexer.index_qk_proj.weight" in name:
|
||||
n_q = self.hparams["indexer_n_heads"] * self.hparams["indexer_head_dim"]
|
||||
q = data_torch[:n_q]
|
||||
k = data_torch[n_q:]
|
||||
return [
|
||||
(self.format_tensor_name(gguf.MODEL_TENSOR.INDEXER_Q_PROJ, bid, ".weight"), q),
|
||||
(self.format_tensor_name(gguf.MODEL_TENSOR.INDEXER_K_PROJ, bid, ".weight"), k),
|
||||
]
|
||||
|
||||
# Gemma zero-centred gammas the inherited norm.weight rule misses
|
||||
if name.endswith((".ple.norm_key.weight", ".ple.norm_query.weight", ".ple.norm_conv.weight",
|
||||
".indexer.q_layernorm.weight", ".indexer.k_layernorm.weight")):
|
||||
return [(self.map_tensor_name(name), data_torch + 1)]
|
||||
|
||||
if name.endswith(".ple.conv1d.weight"):
|
||||
return [(self.map_tensor_name(name), data_torch.squeeze())]
|
||||
|
||||
return super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
# the shards concatenate into a tensor of well over 100 GB
|
||||
# use LazyChunkedTensor here, a single shard resident at a time
|
||||
def _place_ple_shard(self, data_torch: Tensor, name: str) -> Iterable[tuple[str, Tensor]]:
|
||||
|
||||
idx = int(name.rpartition(".shard_")[2].partition(".")[0])
|
||||
n_parts = self.hparams["split_ngram_parts"]
|
||||
|
||||
self._ple_shards[idx] = name
|
||||
self._ple_row_dim = int(data_torch.shape[-1])
|
||||
|
||||
if len(self._ple_shards) < n_parts:
|
||||
return []
|
||||
|
||||
# the checkpoint may yield the shards in any order, the row order is by index
|
||||
shards = [self._ple_shards[i] for i in sorted(self._ple_shards)]
|
||||
rows = 0
|
||||
for shard in shards:
|
||||
shape = self.model_tensors[shard]().shape
|
||||
if int(shape[-1]) != self._ple_row_dim:
|
||||
raise ValueError(
|
||||
f"PLE shard {shard} has row dim {int(shape[-1])}, expected {self._ple_row_dim}")
|
||||
rows += int(shape[0])
|
||||
|
||||
table = gguf.LazyChunkedTensor(
|
||||
[self._load_ple_shard(shard) for shard in shards],
|
||||
shape=(rows, self._ple_row_dim),
|
||||
dtype=np.float32,
|
||||
)
|
||||
gguf_name = gguf.TENSOR_NAMES[gguf.MODEL_TENSOR.PER_LAYER_TOKEN_EMBD]
|
||||
return [(gguf_name + ".weight", cast(Tensor, table))]
|
||||
|
||||
def _load_ple_shard(self, name: str):
|
||||
def load() -> np.ndarray:
|
||||
from .base import LazyTorchTensor
|
||||
|
||||
# a fresh lazy tensor every call, or to_eager() memoizes every shard
|
||||
eager = LazyTorchTensor.to_eager(self.model_tensors[name]())
|
||||
return eager.to(torch.float32).contiguous().numpy()
|
||||
return load
|
||||
|
||||
def prepare_tensors(self):
|
||||
super().prepare_tensors()
|
||||
n_parts = self.hparams.get("split_ngram_parts", 0)
|
||||
if self._ple_shards and len(self._ple_shards) != n_parts:
|
||||
raise ValueError(
|
||||
f"got {len(self._ple_shards)} PLE embedding shards, expected {n_parts}"
|
||||
)
|
||||
|
||||
|
||||
@ModelBase.register("Qwen4ExpForConditionalGeneration")
|
||||
@ModelBase.example("Qwen/Qwen3.8-Flash-Next")
|
||||
class Qwen4ExpVisionModel(Qwen3VLVisionModel):
|
||||
"""The vision tower is an unmodified Qwen3-VL ViT."""
|
||||
@@ -4643,6 +4643,7 @@ static htp_op_code op_remap_to_htp(const ggml_tensor * t) {
|
||||
case GGML_OP_CLAMP: return HTP_OP_CLAMP;
|
||||
case GGML_OP_SQR: return HTP_OP_SQR;
|
||||
case GGML_OP_SQRT: return HTP_OP_SQRT;
|
||||
case GGML_OP_LOG: return HTP_OP_UNARY_LOG;
|
||||
case GGML_OP_SOFT_MAX: return HTP_OP_SOFTMAX;
|
||||
case GGML_OP_SSM_CONV: return HTP_OP_SSM_CONV;
|
||||
case GGML_OP_GATED_DELTA_NET: return HTP_OP_GATED_DELTA_NET;
|
||||
@@ -4666,6 +4667,7 @@ static htp_op_code op_remap_to_htp(const ggml_tensor * t) {
|
||||
case GGML_UNARY_OP_EXP: return HTP_OP_UNARY_EXP;
|
||||
case GGML_UNARY_OP_SOFTPLUS: return HTP_OP_UNARY_SOFTPLUS;
|
||||
case GGML_UNARY_OP_TANH: return HTP_OP_UNARY_TANH;
|
||||
case GGML_UNARY_OP_ABS: return HTP_OP_UNARY_ABS;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -5463,6 +5465,7 @@ static bool ggml_backend_hexagon_device_supports_op(ggml_backend_dev_t dev, cons
|
||||
|
||||
case GGML_OP_SQR:
|
||||
case GGML_OP_SQRT:
|
||||
case GGML_OP_LOG:
|
||||
supp = ggml_hexagon_supported_unary(sess, op);
|
||||
break;
|
||||
|
||||
@@ -5481,6 +5484,7 @@ static bool ggml_backend_hexagon_device_supports_op(ggml_backend_dev_t dev, cons
|
||||
case GGML_UNARY_OP_SIGMOID:
|
||||
case GGML_UNARY_OP_SOFTPLUS:
|
||||
case GGML_UNARY_OP_TANH:
|
||||
case GGML_UNARY_OP_ABS:
|
||||
case GGML_UNARY_OP_SILU:
|
||||
case GGML_UNARY_OP_GELU:
|
||||
case GGML_UNARY_OP_GELU_QUICK:
|
||||
|
||||
@@ -62,6 +62,8 @@ enum htp_op_code {
|
||||
HTP_OP_UNARY_NEG,
|
||||
HTP_OP_UNARY_SOFTPLUS,
|
||||
HTP_OP_UNARY_TANH,
|
||||
HTP_OP_UNARY_ABS,
|
||||
HTP_OP_UNARY_LOG,
|
||||
HTP_OP_GLU_SWIGLU,
|
||||
HTP_OP_GLU_SWIGLU_OAI,
|
||||
HTP_OP_GLU_GEGLU,
|
||||
|
||||
@@ -358,6 +358,34 @@ static inline void hvx_clamp_scalar_f32(uint8_t * restrict dst, const uint8_t *
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Abs
|
||||
//
|
||||
|
||||
static inline void hvx_abs_f32_aa(uint8_t * restrict dst, const uint8_t * restrict src, uint32_t n) {
|
||||
assert((unsigned long) dst % 128 == 0);
|
||||
assert((unsigned long) src % 128 == 0);
|
||||
|
||||
HVX_Vector * restrict vdst = (HVX_Vector *) dst;
|
||||
HVX_Vector * restrict vsrc = (HVX_Vector *) src;
|
||||
|
||||
const uint32_t elem_size = sizeof(float);
|
||||
const uint32_t epv = 128 / elem_size;
|
||||
const uint32_t nvec = n / epv;
|
||||
const uint32_t nloe = n % epv;
|
||||
|
||||
uint32_t i = 0;
|
||||
|
||||
_Pragma("unroll(4)")
|
||||
for (; i < nvec; i++) {
|
||||
vdst[i] = hvx_vec_abs_f32(vsrc[i]);
|
||||
}
|
||||
if (nloe) {
|
||||
HVX_Vector v = hvx_vec_abs_f32(vsrc[i]);
|
||||
hvx_vec_store_a((void *) &vdst[i], nloe * elem_size, v);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Square
|
||||
//
|
||||
|
||||
@@ -62,4 +62,28 @@ static inline HVX_Vector hvx_vec_log_f32(HVX_Vector x) {
|
||||
return hvx_vec_add_f32_f32(term_e, res);
|
||||
}
|
||||
|
||||
static inline void hvx_log_f32_aa(uint8_t * restrict dst, const uint8_t * restrict src, uint32_t n) {
|
||||
assert((unsigned long) dst % 128 == 0);
|
||||
assert((unsigned long) src % 128 == 0);
|
||||
|
||||
HVX_Vector * restrict vdst = (HVX_Vector *) dst;
|
||||
HVX_Vector * restrict vsrc = (HVX_Vector *) src;
|
||||
|
||||
const uint32_t elem_size = sizeof(float);
|
||||
const uint32_t epv = 128 / elem_size;
|
||||
const uint32_t nvec = n / epv;
|
||||
const uint32_t nloe = n % epv;
|
||||
|
||||
uint32_t i = 0;
|
||||
|
||||
_Pragma("unroll(4)")
|
||||
for (; i < nvec; i++) {
|
||||
vdst[i] = hvx_vec_log_f32(vsrc[i]);
|
||||
}
|
||||
if (nloe) {
|
||||
HVX_Vector v = hvx_vec_log_f32(vsrc[i]);
|
||||
hvx_vec_store_a((void *) &vdst[i], nloe * elem_size, v);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* HVX_LOG_H */
|
||||
|
||||
@@ -777,6 +777,8 @@ static int execute_op(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_NEG:
|
||||
case HTP_OP_UNARY_EXP:
|
||||
case HTP_OP_UNARY_TANH:
|
||||
case HTP_OP_UNARY_ABS:
|
||||
case HTP_OP_UNARY_LOG:
|
||||
case HTP_OP_L2_NORM:
|
||||
return op_unary(octx);
|
||||
|
||||
|
||||
@@ -443,6 +443,34 @@ static void tanh_f32(const float * restrict src,
|
||||
}
|
||||
}
|
||||
|
||||
static void abs_f32(const float * restrict src,
|
||||
float * restrict dst,
|
||||
const uint32_t num_rows,
|
||||
const struct htp_unary_context * uctx) {
|
||||
htp_unary_op_preamble;
|
||||
|
||||
for (uint32_t ir = 0; ir < num_rows; ir++) {
|
||||
const uint8_t * restrict src_local = (const uint8_t *)src + (ir * src0_row_size_aligned);
|
||||
uint8_t * restrict dst_local = (uint8_t *)dst + (ir * dst_row_size_aligned);
|
||||
|
||||
hvx_abs_f32_aa(dst_local, src_local, ne0);
|
||||
}
|
||||
}
|
||||
|
||||
static void log_f32(const float * restrict src,
|
||||
float * restrict dst,
|
||||
const uint32_t num_rows,
|
||||
const struct htp_unary_context * uctx) {
|
||||
htp_unary_op_preamble;
|
||||
|
||||
for (uint32_t ir = 0; ir < num_rows; ir++) {
|
||||
const uint8_t * restrict src_local = (const uint8_t *)src + (ir * src0_row_size_aligned);
|
||||
uint8_t * restrict dst_local = (uint8_t *)dst + (ir * dst_row_size_aligned);
|
||||
|
||||
hvx_log_f32_aa(dst_local, src_local, ne0);
|
||||
}
|
||||
}
|
||||
|
||||
#define DEFINE_UNARY_TASK(NAME, IS_RMS_NORM_MUL, IS_TRI, CORE_EXPR) \
|
||||
static void unary_task_f32_##NAME(unsigned int nth, unsigned int ith, void * data) { \
|
||||
const struct htp_unary_context * uctx = (const struct htp_unary_context *) data; \
|
||||
@@ -478,6 +506,9 @@ static void unary_task_f32_##NAME(unsigned int nth, unsigned int ith, void * dat
|
||||
const uint32_t nb11 = src1 ? src1->nb[1] : 0; \
|
||||
const uint32_t nb12 = src1 ? src1->nb[2] : 0; \
|
||||
const uint32_t nb13 = src1 ? src1->nb[3] : 0; \
|
||||
const uint32_t nb11_bc = (src1 && src1->ne[1] > 1) ? nb11 : 0; \
|
||||
const uint32_t nb12_bc = (src1 && src1->ne[2] > 1) ? nb12 : 0; \
|
||||
const uint32_t nb13_bc = (src1 && src1->ne[3] > 1) ? nb13 : 0; \
|
||||
const bool src1_contig = src1 ? ((nb12 == (size_t)ne01 * nb11) && (nb13 == (size_t)ne02 * nb12)) : false; \
|
||||
\
|
||||
uint8_t * src0_vtcm_data = uctx->vtcm_src0 + (ith * uctx->vtcm_src0_size_per_thread); \
|
||||
@@ -497,8 +528,12 @@ static void unary_task_f32_##NAME(unsigned int nth, unsigned int ith, void * dat
|
||||
const struct fastdiv_values * div_ne02 = &uctx->kparams->div_ne02; \
|
||||
const struct fastdiv_values * div_ne012 = &uctx->kparams->div_ne012; \
|
||||
\
|
||||
const uint32_t src0_max_block = src0_contig ? uctx->block : MIN((uint32_t)uctx->block, ne01); \
|
||||
const uint32_t dst_max_block = dst_contig ? uctx->block : MIN((uint32_t)uctx->block, ne1); \
|
||||
const bool src1_needs_row_clip = (IS_RMS_NORM_MUL) && !uctx->broadcast_weight && !src1_contig; \
|
||||
const bool block_src0_contig = src0_contig && !src1_needs_row_clip; \
|
||||
const bool block_dst_contig = dst_contig && !src1_needs_row_clip; \
|
||||
\
|
||||
const uint32_t src0_max_block = block_src0_contig ? uctx->block : MIN((uint32_t)uctx->block, ne01); \
|
||||
const uint32_t dst_max_block = block_dst_contig ? uctx->block : MIN((uint32_t)uctx->block, ne1); \
|
||||
const uint32_t BLOCK = MIN(src0_max_block, dst_max_block); \
|
||||
if (BLOCK == 0) { \
|
||||
FARF(ERROR, "unary-f32 : current VTCM reservation %zu is too small, needed at least %zu\n", \
|
||||
@@ -515,8 +550,8 @@ static void unary_task_f32_##NAME(unsigned int nth, unsigned int ith, void * dat
|
||||
} \
|
||||
\
|
||||
for (uint32_t ir = src0_start_row, vtcm_idx = 0; ir < src0_end_row && vtcm_idx < 2; vtcm_idx++) { \
|
||||
const uint32_t block_size = unary_block_size(ir, src0_end_row, BLOCK, src0_contig, dst_contig, ne01, \
|
||||
div_ne01); \
|
||||
const uint32_t block_size = unary_block_size(ir, src0_end_row, BLOCK, block_src0_contig, block_dst_contig, \
|
||||
ne01, div_ne01); \
|
||||
\
|
||||
dma_queue_push(dma_queue, \
|
||||
dma_make_ptr(data_dst, dst_vtcm_data + (vtcm_idx * dst_vtcm_half_size)), \
|
||||
@@ -530,7 +565,7 @@ static void unary_task_f32_##NAME(unsigned int nth, unsigned int ith, void * dat
|
||||
\
|
||||
if ((IS_RMS_NORM_MUL) && !uctx->broadcast_weight) { \
|
||||
const size_t src1_off = src1_contig ? (ir * nb11) : \
|
||||
unary_row_offset(ir, ne01, ne02, div_ne01, div_ne02, div_ne012, nb11, nb12, nb13); \
|
||||
unary_row_offset(ir, ne01, ne02, div_ne01, div_ne02, div_ne012, nb11_bc, nb12_bc, nb13_bc); \
|
||||
dma_queue_push(dma_queue, \
|
||||
dma_make_ptr(src1_vtcm_data + (vtcm_idx * src1_vtcm_half_size), data_src1 + src1_off), \
|
||||
uctx->src1_row_size_aligned, nb11, uctx->src1_data_row_size, block_size); \
|
||||
@@ -540,8 +575,8 @@ static void unary_task_f32_##NAME(unsigned int nth, unsigned int ith, void * dat
|
||||
} \
|
||||
\
|
||||
for (uint32_t ir = src0_start_row; ir < src0_end_row; ) { \
|
||||
const uint32_t block_size = unary_block_size(ir, src0_end_row, BLOCK, src0_contig, dst_contig, ne01, \
|
||||
div_ne01); \
|
||||
const uint32_t block_size = unary_block_size(ir, src0_end_row, BLOCK, block_src0_contig, block_dst_contig, \
|
||||
ne01, div_ne01); \
|
||||
\
|
||||
float * dst_vtcm = (float *) dma_queue_pop(dma_queue).src; \
|
||||
float * src0_vtcm = (float *) dma_queue_pop(dma_queue).dst; \
|
||||
@@ -562,12 +597,12 @@ static void unary_task_f32_##NAME(unsigned int nth, unsigned int ith, void * dat
|
||||
\
|
||||
const uint32_t next_ir = ir + block_size; \
|
||||
if (next_ir < src0_end_row) { \
|
||||
const uint32_t next_block_size = unary_block_size(next_ir, src0_end_row, BLOCK, src0_contig, dst_contig,\
|
||||
ne01, div_ne01); \
|
||||
const uint32_t next_block_size = unary_block_size(next_ir, src0_end_row, BLOCK, block_src0_contig, \
|
||||
block_dst_contig, ne01, div_ne01); \
|
||||
const uint32_t pref_ir = next_ir + next_block_size; \
|
||||
if (pref_ir < src0_end_row) { \
|
||||
const uint32_t pref_block_size = unary_block_size(pref_ir, src0_end_row, BLOCK, src0_contig, \
|
||||
dst_contig, ne01, div_ne01); \
|
||||
const uint32_t pref_block_size = unary_block_size(pref_ir, src0_end_row, BLOCK, block_src0_contig, \
|
||||
block_dst_contig, ne01, div_ne01); \
|
||||
const size_t src0_pref_off = src0_contig ? (pref_ir * nb01) : \
|
||||
unary_row_offset(pref_ir, ne01, ne02, div_ne01, div_ne02, div_ne012, nb01, nb02, nb03); \
|
||||
dma_queue_push(dma_queue, \
|
||||
@@ -576,7 +611,8 @@ static void unary_task_f32_##NAME(unsigned int nth, unsigned int ith, void * dat
|
||||
\
|
||||
if ((IS_RMS_NORM_MUL) && !uctx->broadcast_weight) { \
|
||||
const size_t src1_pref_off = src1_contig ? (pref_ir * nb11) : \
|
||||
unary_row_offset(pref_ir, ne01, ne02, div_ne01, div_ne02, div_ne012, nb11, nb12, nb13); \
|
||||
unary_row_offset(pref_ir, ne01, ne02, div_ne01, div_ne02, div_ne012, nb11_bc, nb12_bc, \
|
||||
nb13_bc); \
|
||||
dma_queue_push(dma_queue, \
|
||||
dma_make_ptr(src1_vtcm, data_src1 + src1_pref_off), \
|
||||
uctx->src1_row_size_aligned, nb11, uctx->src1_data_row_size, pref_block_size); \
|
||||
@@ -603,6 +639,8 @@ DEFINE_UNARY_TASK(unary_silu, false, false, silu_f32(src0_vtcm, dst_vtcm, bl
|
||||
DEFINE_UNARY_TASK(unary_gelu, false, false, gelu_f32(src0_vtcm, dst_vtcm, block_size, uctx))
|
||||
DEFINE_UNARY_TASK(unary_softplus, false, false, softplus_f32(src0_vtcm, dst_vtcm, block_size, uctx))
|
||||
DEFINE_UNARY_TASK(unary_tanh, false, false, tanh_f32(src0_vtcm, dst_vtcm, block_size, uctx))
|
||||
DEFINE_UNARY_TASK(unary_abs, false, false, abs_f32(src0_vtcm, dst_vtcm, block_size, uctx))
|
||||
DEFINE_UNARY_TASK(unary_log, false, false, log_f32(src0_vtcm, dst_vtcm, block_size, uctx))
|
||||
DEFINE_UNARY_TASK(l2_norm, false, false, l2_norm_f32(src0_vtcm, dst_vtcm, block_size, uctx))
|
||||
DEFINE_UNARY_TASK(tri, false, true, tri_f32(src0_vtcm, dst_vtcm, block_size, ir, uctx))
|
||||
|
||||
@@ -850,6 +888,8 @@ DEFINE_UNARY_TILED_TASK(unary_silu, false, tile_silu_f32(dst_vtcm, src_vtcm,
|
||||
DEFINE_UNARY_TILED_TASK(unary_gelu, false, tile_gelu_f32(dst_vtcm, src_vtcm, tw))
|
||||
DEFINE_UNARY_TILED_TASK(unary_softplus, false, tile_unary_softplus_f32(dst_vtcm, src_vtcm, tw))
|
||||
DEFINE_UNARY_TILED_TASK(unary_tanh, false, hvx_tanh_f32_aa(dst_vtcm, src_vtcm, tw))
|
||||
DEFINE_UNARY_TILED_TASK(unary_abs, false, hvx_abs_f32_aa(dst_vtcm, src_vtcm, tw))
|
||||
DEFINE_UNARY_TILED_TASK(unary_log, false, hvx_log_f32_aa(dst_vtcm, src_vtcm, tw))
|
||||
DEFINE_UNARY_TILED_TASK(tri, true, tri_apply_tile_f32(src_vtcm, dst_vtcm, tw, col, i01, ne0, tri_ttype))
|
||||
|
||||
static int execute_op_unary_f32(struct htp_ops_context * octx) {
|
||||
@@ -875,6 +915,8 @@ static int execute_op_unary_f32(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_GELU: op_type = "gelu-f32"; break;
|
||||
case HTP_OP_UNARY_SOFTPLUS: op_type = "softplus-f32"; break;
|
||||
case HTP_OP_UNARY_TANH: op_type = "tanh-f32"; break;
|
||||
case HTP_OP_UNARY_ABS: op_type = "abs-f32"; break;
|
||||
case HTP_OP_UNARY_LOG: op_type = "log-f32"; break;
|
||||
case HTP_OP_L2_NORM: op_type = "l2norm-f32"; break;
|
||||
case HTP_OP_TRI: op_type = "tri-f32"; break;
|
||||
|
||||
@@ -973,6 +1015,8 @@ static int execute_op_unary_f32(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_GELU: task_func = unary_task_f32_tiled_unary_gelu; break;
|
||||
case HTP_OP_UNARY_SOFTPLUS: task_func = unary_task_f32_tiled_unary_softplus; break;
|
||||
case HTP_OP_UNARY_TANH: task_func = unary_task_f32_tiled_unary_tanh; break;
|
||||
case HTP_OP_UNARY_ABS: task_func = unary_task_f32_tiled_unary_abs; break;
|
||||
case HTP_OP_UNARY_LOG: task_func = unary_task_f32_tiled_unary_log; break;
|
||||
case HTP_OP_TRI: task_func = unary_task_f32_tiled_tri; break;
|
||||
default: break;
|
||||
}
|
||||
@@ -992,6 +1036,8 @@ static int execute_op_unary_f32(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_GELU: task_func = unary_task_f32_unary_gelu; break;
|
||||
case HTP_OP_UNARY_SOFTPLUS: task_func = unary_task_f32_unary_softplus; break;
|
||||
case HTP_OP_UNARY_TANH: task_func = unary_task_f32_unary_tanh; break;
|
||||
case HTP_OP_UNARY_ABS: task_func = unary_task_f32_unary_abs; break;
|
||||
case HTP_OP_UNARY_LOG: task_func = unary_task_f32_unary_log; break;
|
||||
case HTP_OP_L2_NORM: task_func = unary_task_f32_l2_norm; break;
|
||||
case HTP_OP_TRI: task_func = unary_task_f32_tri; break;
|
||||
default: break;
|
||||
|
||||
@@ -55,6 +55,8 @@ static inline bool htp_op_is_unary(uint32_t opcode) {
|
||||
case HTP_OP_UNARY_GELU:
|
||||
case HTP_OP_UNARY_SOFTPLUS:
|
||||
case HTP_OP_UNARY_TANH:
|
||||
case HTP_OP_UNARY_ABS:
|
||||
case HTP_OP_UNARY_LOG:
|
||||
case HTP_OP_L2_NORM:
|
||||
case HTP_OP_TRI:
|
||||
return true;
|
||||
|
||||
@@ -903,6 +903,8 @@ struct ggml_backend_opencl_context {
|
||||
cl_kernel kernel_gemv_moe_mxfp4_f32_ns_wimg = nullptr; // weight-as-texture MoE decode GEMV
|
||||
cl_kernel kernel_gemm_moe_mxfp4_q8_1_dp4a = nullptr; // dp4a (int8) mxfp4 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_q4_0_q8_1_dp4a = nullptr; // dp4a (int8) q4_0 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_mxfp4_q8_1_dp4a_bin = nullptr; // binary dp4a (int8) mxfp4 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_q4_0_q8_1_dp4a_bin = nullptr; // binary dp4a (int8) q4_0 MoE prefill GEMM
|
||||
cl_kernel kernel_moe_reorder_b;
|
||||
cl_kernel kernel_moe_histogram, kernel_moe_scan, kernel_moe_fill, kernel_moe_scatter;
|
||||
cl_kernel kernel_moe_scatter_stable = nullptr; // deterministic slot assignment
|
||||
@@ -4248,6 +4250,24 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
|
||||
// gemm_moe_mxfp4_q8_1_dp4a_bin (dp4a prefill GEMM)
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
size_t bin_size = 0;
|
||||
backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_bin = nullptr;
|
||||
|
||||
if (use_adreno_bin_kernels(backend_ctx)) {
|
||||
const char * kernel_bin = (const char *)backend_ctx->get_adreno_bin_kernel("gemm_moe_mxfp4_q8_1_dp4a_ila", &bin_size);
|
||||
if (kernel_bin && bin_size > 0) {
|
||||
cl_program prog =
|
||||
build_program_from_binary(backend_ctx->context, backend_ctx->device, kernel_bin, CL_moe_compile_opts, bin_size);
|
||||
|
||||
CL_CHECK((backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_bin = clCreateKernel(prog, "kernel_gemm_moe_mxfp4_q8_1_dp4a_ila", &err), err));
|
||||
CL_CHECK(clReleaseProgram(prog));
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gemm_moe_q4_0_q8_1_dp4a (dp4a prefill GEMM)
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
@@ -4265,6 +4285,24 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
|
||||
// gemm_moe_q4_0_q8_1_dp4a_bin (dp4a prefill GEMM)
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
size_t bin_size = 0;
|
||||
backend_ctx->kernel_gemm_moe_q4_0_q8_1_dp4a_bin = nullptr;
|
||||
|
||||
if (use_adreno_bin_kernels(backend_ctx)) {
|
||||
const char * kernel_bin = (const char *)backend_ctx->get_adreno_bin_kernel("gemm_moe_q4_0_q8_1_dp4a_ila", &bin_size);
|
||||
if (kernel_bin && bin_size > 0) {
|
||||
cl_program prog =
|
||||
build_program_from_binary(backend_ctx->context, backend_ctx->device, kernel_bin, CL_moe_compile_opts, bin_size);
|
||||
|
||||
CL_CHECK((backend_ctx->kernel_gemm_moe_q4_0_q8_1_dp4a_bin = clCreateKernel(prog, "kernel_gemm_moe_q4_0_q8_1_dp4a_ila", &err), err));
|
||||
CL_CHECK(clReleaseProgram(prog));
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gemm_moe_q8_1_dp4a (generic dp4a MoE GEMM; MOE_QT=80 -> q8_0 expert variant)
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
@@ -21519,7 +21557,9 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
// dot prod has to be available
|
||||
use_moe_dp4a = backend_ctx->has_integer_dot && use_moe_dp4a;
|
||||
// bin kernel takes precedence
|
||||
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_q4_0_f32_ns_bin == nullptr;
|
||||
if (backend_ctx->kernel_gemm_moe_q4_0_q8_1_dp4a_bin == nullptr) {
|
||||
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_q4_0_f32_ns_bin == nullptr;
|
||||
}
|
||||
|
||||
cl_buffer_region region;
|
||||
region.origin = 0;
|
||||
@@ -21625,6 +21665,10 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
|
||||
// dp4a GEMM
|
||||
cl_kernel dk = backend_ctx->kernel_gemm_moe_q4_0_q8_1_dp4a;
|
||||
if (backend_ctx->kernel_gemm_moe_q4_0_q8_1_dp4a_bin) {
|
||||
dk = backend_ctx->kernel_gemm_moe_q4_0_q8_1_dp4a_bin;
|
||||
}
|
||||
|
||||
int aidx = 0;
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &extra0_q4_0->q_img));
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &extra0_q4_0->d));
|
||||
@@ -23463,8 +23507,10 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E);
|
||||
// dot prod has to be available
|
||||
use_moe_dp4a = backend_ctx->has_integer_dot && use_moe_dp4a;
|
||||
// bin kernel takes precedence
|
||||
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_mxfp4_f32_ns_bin == nullptr;
|
||||
// bin kernel takes precedence, dp4a bin kernel has higher priority than normal bin kernel
|
||||
if (backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_bin == nullptr) {
|
||||
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_mxfp4_f32_ns_bin == nullptr;
|
||||
}
|
||||
|
||||
cl_buffer_region region;
|
||||
region.origin = 0;
|
||||
@@ -23573,6 +23619,10 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
|
||||
// dp4a GEMM
|
||||
cl_kernel dk = backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a;
|
||||
if (backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_bin) {
|
||||
dk = backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_bin;
|
||||
}
|
||||
|
||||
int aidx = 0;
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &extra0_mxfp4->q_img));
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &extra0_mxfp4->e));
|
||||
|
||||
@@ -767,6 +767,21 @@ static constexpr std::initializer_list<std::array<int, 3>> rms_norm_mul_rope_vie
|
||||
{ 4, 0, 3 }, // set_rows->src[0] == view
|
||||
};
|
||||
|
||||
static constexpr std::array<ggml_type, 9> lightning_indexer_k_types = {
|
||||
GGML_TYPE_F32,
|
||||
GGML_TYPE_F16,
|
||||
GGML_TYPE_BF16,
|
||||
GGML_TYPE_Q8_0,
|
||||
GGML_TYPE_Q5_1,
|
||||
GGML_TYPE_Q5_0,
|
||||
GGML_TYPE_Q4_1,
|
||||
GGML_TYPE_Q4_0,
|
||||
GGML_TYPE_IQ4_NL,
|
||||
};
|
||||
|
||||
static bool ggml_vk_lightning_indexer_k_type_supported(ggml_type type) {
|
||||
return std::find(lightning_indexer_k_types.begin(), lightning_indexer_k_types.end(), type) != lightning_indexer_k_types.end();
|
||||
}
|
||||
|
||||
struct vk_device_struct {
|
||||
std::recursive_mutex mutex;
|
||||
@@ -1068,6 +1083,7 @@ struct vk_device_struct {
|
||||
vk_pipeline pipeline_rwkv_wkv6_f32;
|
||||
vk_pipeline pipeline_rwkv_wkv7_f32;
|
||||
vk_pipeline pipeline_gated_linear_attn_f32;
|
||||
vk_pipeline pipeline_lightning_indexer_f32[GGML_TYPE_COUNT];
|
||||
// [size_idx][kda] where size_idx: 0=d16, 1=d32, 2=d64, 3=d128
|
||||
vk_pipeline pipeline_gated_delta_net[4][2];
|
||||
vk_pipeline pipeline_ssm_scan_f32_d128;
|
||||
@@ -1848,6 +1864,26 @@ struct vk_op_gated_linear_attn_push_constants {
|
||||
uint32_t H;
|
||||
float scale;
|
||||
};
|
||||
struct vk_op_lightning_indexer_push_constants {
|
||||
uint32_t n_kv;
|
||||
uint32_t n_heads;
|
||||
uint32_t n_tokens;
|
||||
uint32_t n_streams;
|
||||
uint32_t n_masks;
|
||||
uint32_t dispatch_x;
|
||||
uint32_t q_nb1;
|
||||
uint32_t q_nb2;
|
||||
uint32_t q_nb3;
|
||||
uint32_t k_nb2;
|
||||
uint32_t k_nb3;
|
||||
uint32_t w_nb1;
|
||||
uint32_t w_nb3;
|
||||
uint32_t m_nb1;
|
||||
uint32_t m_nb3;
|
||||
uint32_t d_nb1;
|
||||
uint32_t d_nb3;
|
||||
};
|
||||
static_assert(sizeof(vk_op_lightning_indexer_push_constants) <= 128);
|
||||
struct vk_op_gated_delta_net_push_constants {
|
||||
uint32_t H;
|
||||
uint32_t n_tokens;
|
||||
@@ -3904,11 +3940,16 @@ static vk_fa_pipeline_state get_fa_pipeline_state(const vk_device& device, const
|
||||
return vk_fa_pipeline_state{hsk, hsv, params.block_rows, params.block_cols, params.d_split, params.row_split, params.shmem_staging, params.path, params.workgroup_size, subgroup_size, aligned, f32acc, flags, params.limit_occupancy_shmem, k_type, v_type};
|
||||
}
|
||||
|
||||
// Bytes per buffer block for the FaBlockBytesK/V spec constants. F32 is fed as
|
||||
// a vec4 "block" of 4 floats, everything else uses its ggml block size.
|
||||
static uint32_t fa_block_bytes(ggml_type t) {
|
||||
if (t == GGML_TYPE_F32) {
|
||||
return 16u;
|
||||
}
|
||||
return (uint32_t) ggml_type_size(t);
|
||||
}
|
||||
|
||||
static std::vector<uint32_t> get_fa_spec_constants(const vk_fa_pipeline_state& state) {
|
||||
const auto fa_block_bytes = [](ggml_type t) -> uint32_t {
|
||||
if (t == GGML_TYPE_F32) return 16u;
|
||||
return (uint32_t) ggml_type_size(t);
|
||||
};
|
||||
return {
|
||||
/* 0 WorkGroupSize */ state.workgroup_size,
|
||||
/* 1 Br */ state.Br,
|
||||
@@ -5847,6 +5888,17 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
|
||||
ggml_vk_create_pipeline(device, device->pipeline_gated_linear_attn_f32, "gated_linear_attn_f32", gated_linear_attn_f32_len, gated_linear_attn_f32_data, "main", 6, sizeof(vk_op_gated_linear_attn_push_constants), {1, 1, 1}, {}, 1);
|
||||
|
||||
{
|
||||
const bool li_subgroup = device->subgroup_arithmetic && device->subgroup_require_full_support;
|
||||
const size_t li_len = li_subgroup ? lightning_indexer_subgroup_f32_len : lightning_indexer_f32_len;
|
||||
const void * li_data = li_subgroup ? (const void *)lightning_indexer_subgroup_f32_data : (const void *)lightning_indexer_f32_data;
|
||||
|
||||
for (ggml_type k_type : lightning_indexer_k_types) {
|
||||
const std::string name = "lightning_indexer_" + std::string(ggml_type_name(k_type)) + "_k_f32";
|
||||
ggml_vk_create_pipeline(device, device->pipeline_lightning_indexer_f32[k_type], name.c_str(), li_len, li_data, "main", 5, sizeof(vk_op_lightning_indexer_push_constants), {1, 1, 1}, {(uint32_t)k_type, fa_block_bytes(k_type), device->subgroup_size}, 1, true, li_subgroup);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const uint32_t gdn_sizes[] = {16, 32, 64, 128};
|
||||
const char * gdn_names[][2] = {
|
||||
@@ -11697,6 +11749,12 @@ static vk_pipeline ggml_vk_op_get_pipeline(ggml_backend_vk_context * ctx, const
|
||||
return ctx->device->pipeline_gated_linear_attn_f32;
|
||||
}
|
||||
return nullptr;
|
||||
case GGML_OP_LIGHTNING_INDEXER:
|
||||
// only the k type selects a pipeline, the other types are fixed by ggml_lightning_indexer()
|
||||
if (ggml_vk_lightning_indexer_k_type_supported(src1->type)) {
|
||||
return ctx->device->pipeline_lightning_indexer_f32[src1->type];
|
||||
}
|
||||
return nullptr;
|
||||
case GGML_OP_GATED_DELTA_NET:
|
||||
if (src0->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32) {
|
||||
const uint32_t S_v = dst->src[2]->ne[0];
|
||||
@@ -12772,6 +12830,55 @@ static void ggml_vk_gated_linear_attn(ggml_backend_vk_context * ctx, vk_context&
|
||||
pc, { (uint32_t)(n_seqs * n_heads), 1, 1 });
|
||||
}
|
||||
|
||||
static void ggml_vk_lightning_indexer(ggml_backend_vk_context * ctx, vk_context& subctx, ggml_tensor * dst) {
|
||||
const ggml_tensor * q = dst->src[0];
|
||||
const ggml_tensor * k = dst->src[1];
|
||||
const ggml_tensor * w = dst->src[2];
|
||||
const ggml_tensor * m = dst->src[3];
|
||||
|
||||
vk_pipeline pipeline = ggml_vk_op_get_pipeline(ctx, q, k, w, dst, dst->op);
|
||||
GGML_ASSERT(pipeline != nullptr);
|
||||
|
||||
ggml_pipeline_request_descriptor_sets(ctx, pipeline, 1);
|
||||
|
||||
const uint32_t n_kv = k->ne[2];
|
||||
const uint32_t n_heads = q->ne[1];
|
||||
const uint32_t n_tokens = q->ne[2];
|
||||
const uint32_t n_streams = q->ne[3];
|
||||
const uint32_t n_masks = m->ne[3];
|
||||
|
||||
const uint32_t n_outputs = (uint32_t)(dst->ne[0] * dst->ne[1] * dst->ne[3]);
|
||||
const uint32_t dispatch_x = std::min(n_outputs, ctx->device->properties.limits.maxComputeWorkGroupCount[0]);
|
||||
const uint32_t dispatch_y = CEIL_DIV(n_outputs, dispatch_x);
|
||||
|
||||
// q, w and dst are f32 and m is f16, so their strides are passed in elements;
|
||||
// k may be quantized, so its strides stay in bytes
|
||||
const uint32_t q_nb1 = q->nb[1] / sizeof(float);
|
||||
const uint32_t q_nb2 = q->nb[2] / sizeof(float);
|
||||
const uint32_t q_nb3 = q->nb[3] / sizeof(float);
|
||||
const uint32_t k_nb2 = k->nb[2];
|
||||
const uint32_t k_nb3 = k->nb[3];
|
||||
const uint32_t w_nb1 = w->nb[1] / sizeof(float);
|
||||
const uint32_t w_nb3 = w->nb[3] / sizeof(float);
|
||||
const uint32_t m_nb1 = m->nb[1] / sizeof(ggml_fp16_t);
|
||||
const uint32_t m_nb3 = m->nb[3] / sizeof(ggml_fp16_t);
|
||||
const uint32_t d_nb1 = dst->nb[1] / sizeof(float);
|
||||
const uint32_t d_nb3 = dst->nb[3] / sizeof(float);
|
||||
|
||||
const vk_op_lightning_indexer_push_constants pc = {
|
||||
n_kv, n_heads, n_tokens, n_streams, n_masks, dispatch_x,
|
||||
q_nb1, q_nb2, q_nb3,
|
||||
k_nb2, k_nb3,
|
||||
w_nb1, w_nb3,
|
||||
m_nb1, m_nb3,
|
||||
d_nb1, d_nb3,
|
||||
};
|
||||
|
||||
ggml_vk_dispatch_pipeline(ctx, subctx, pipeline,
|
||||
{ggml_vk_tensor_subbuffer(ctx, q), ggml_vk_tensor_subbuffer(ctx, k), ggml_vk_tensor_subbuffer(ctx, w), ggml_vk_tensor_subbuffer(ctx, m), ggml_vk_tensor_subbuffer(ctx, dst)},
|
||||
pc, {dispatch_x, dispatch_y, 1});
|
||||
}
|
||||
|
||||
static void ggml_vk_gated_delta_net(ggml_backend_vk_context * ctx, vk_context& subctx, ggml_tensor * dst) {
|
||||
const ggml_tensor * src_q = dst->src[0];
|
||||
const ggml_tensor * src_v = dst->src[2];
|
||||
@@ -15898,6 +16005,11 @@ static bool ggml_vk_build_graph(ggml_backend_vk_context * ctx, ggml_cgraph * cgr
|
||||
|
||||
break;
|
||||
|
||||
case GGML_OP_LIGHTNING_INDEXER:
|
||||
ggml_vk_lightning_indexer(ctx, compute_ctx, node);
|
||||
|
||||
break;
|
||||
|
||||
case GGML_OP_GATED_DELTA_NET:
|
||||
ggml_vk_gated_delta_net(ctx, compute_ctx, node);
|
||||
|
||||
@@ -18676,6 +18788,40 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
|
||||
case GGML_OP_GATED_LINEAR_ATTN:
|
||||
// the shader block size is hardcoded to head_size 64
|
||||
return op->src[0]->type == GGML_TYPE_F32 && op->type == GGML_TYPE_F32 && op->src[0]->ne[0] == 64;
|
||||
case GGML_OP_LIGHTNING_INDEXER:
|
||||
{
|
||||
const ggml_tensor * q = op->src[0];
|
||||
const ggml_tensor * k = op->src[1];
|
||||
const ggml_tensor * w = op->src[2];
|
||||
const ggml_tensor * m = op->src[3];
|
||||
|
||||
// the q/w/m types and the shape relationships between q, k, w, m and dst
|
||||
// are already asserted in ggml_lightning_indexer()
|
||||
if (!ggml_vk_lightning_indexer_k_type_supported(k->type) || !device->fp16) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// the shader block size is hardcoded to head size 128
|
||||
if (q->ne[0] != 128) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// the shader indexes the buffers by element stride, and is dispatched
|
||||
// without allow_misalign
|
||||
for (const ggml_tensor * t : {q, k, w, m, op}) {
|
||||
if (t->nb[0] != ggml_type_size(t->type) ||
|
||||
(vk_tensor_offset(t) + t->view_offs) % device->properties.limits.minStorageBufferOffsetAlignment != 0) {
|
||||
return false;
|
||||
}
|
||||
// the strides get scaled down from bytes, so the division must be exact
|
||||
for (int i = 1; i < GGML_MAX_DIMS; ++i) {
|
||||
if (t->nb[i] % ggml_type_size(t->type) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case GGML_OP_GATED_DELTA_NET:
|
||||
{
|
||||
const uint32_t S_v = op->src[2]->ne[0];
|
||||
@@ -19685,6 +19831,8 @@ static void ggml_vk_check_results_0(ggml_backend_vk_context * ctx, ggml_cgraph *
|
||||
const float * op_params = (const float *)tensor->op_params;
|
||||
tensor_clone = ggml_gated_linear_attn(ggml_ctx, src_clone[0], src_clone[1],
|
||||
src_clone[2], src_clone[3], src_clone[4], op_params[0]);
|
||||
} else if (tensor->op == GGML_OP_LIGHTNING_INDEXER) {
|
||||
tensor_clone = ggml_lightning_indexer(ggml_ctx, src_clone[0], src_clone[1], src_clone[2], src_clone[3]);
|
||||
} else if (tensor->op == GGML_OP_GATED_DELTA_NET) {
|
||||
tensor_clone = ggml_gated_delta_net(ggml_ctx, src_clone[0], src_clone[1],
|
||||
src_clone[2], src_clone[3], src_clone[4], src_clone[5],
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#if !defined(GGML_FA_TYPES_COMP)
|
||||
#define GGML_FA_TYPES_COMP
|
||||
|
||||
// FaTypeK / FaTypeV spec constant values. These mirror enum ggml_type so the
|
||||
// host can pass the type directly. Keep in sync with ggml.h.
|
||||
#define FA_TYPE_F32 0u
|
||||
#define FA_TYPE_F16 1u
|
||||
#define FA_TYPE_Q4_0 2u
|
||||
#define FA_TYPE_Q4_1 3u
|
||||
#define FA_TYPE_Q5_0 6u
|
||||
#define FA_TYPE_Q5_1 7u
|
||||
#define FA_TYPE_Q8_0 8u
|
||||
#define FA_TYPE_IQ4_NL 20u
|
||||
#define FA_TYPE_BF16 30u
|
||||
|
||||
// Number of matrix elements per buffer block, derived from the K/V type spec
|
||||
// constant. F32 is treated as a vec4 "block" of 4 floats. F16 uses block size 1
|
||||
// and bypasses the dequant path entirely. Quants follow their ggml block sizes.
|
||||
uint fa_block_elems(uint ty) {
|
||||
switch (ty) {
|
||||
case FA_TYPE_F32: return 4u;
|
||||
case FA_TYPE_F16: return 1u;
|
||||
case FA_TYPE_Q4_0: return uint(QUANT_K_Q4_0);
|
||||
case FA_TYPE_Q4_1: return uint(QUANT_K_Q4_1);
|
||||
case FA_TYPE_Q5_0: return uint(QUANT_K_Q5_0);
|
||||
case FA_TYPE_Q5_1: return uint(QUANT_K_Q5_1);
|
||||
case FA_TYPE_Q8_0: return uint(QUANT_K_Q8_0);
|
||||
case FA_TYPE_IQ4_NL: return uint(QUANT_K_IQ4_NL);
|
||||
case FA_TYPE_BF16: return 1u;
|
||||
default: return 1u;
|
||||
}
|
||||
}
|
||||
|
||||
// QUANT_R_MMQ for FA-eligible K types. Q4_*/Q5_* store two nibbles per byte
|
||||
// (R==2); Q8_0 stores one byte per element (R==1). Used to derive the number
|
||||
// of int32s per 32-element block on the MMQ K path: ints_per_block == 8 / R.
|
||||
uint fa_quant_r_mmq(uint ty) {
|
||||
switch (ty) {
|
||||
case FA_TYPE_Q4_0: return uint(QUANT_R_Q4_0);
|
||||
case FA_TYPE_Q4_1: return uint(QUANT_R_Q4_1);
|
||||
case FA_TYPE_Q5_0: return uint(QUANT_R_Q5_0);
|
||||
case FA_TYPE_Q5_1: return uint(QUANT_R_Q5_1);
|
||||
case FA_TYPE_Q8_0: return uint(QUANT_R_Q8_0);
|
||||
default: return 1u;
|
||||
}
|
||||
}
|
||||
|
||||
bool fa_type_needs_shmem(uint ty) {
|
||||
switch (ty) {
|
||||
case FA_TYPE_IQ4_NL: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !defined(GGML_FA_TYPES_COMP)
|
||||
@@ -88,17 +88,7 @@ layout (binding = 6) readonly buffer MO {uint32_t data_mask_opt[];};
|
||||
#define BINDING_IDX_K 0
|
||||
#define BINDING_IDX_V 1
|
||||
|
||||
// FaTypeK / FaTypeV spec constant values. These mirror enum ggml_type so the
|
||||
// host can pass the type directly. Keep in sync with ggml.h.
|
||||
#define FA_TYPE_F32 0u
|
||||
#define FA_TYPE_F16 1u
|
||||
#define FA_TYPE_Q4_0 2u
|
||||
#define FA_TYPE_Q4_1 3u
|
||||
#define FA_TYPE_Q5_0 6u
|
||||
#define FA_TYPE_Q5_1 7u
|
||||
#define FA_TYPE_Q8_0 8u
|
||||
#define FA_TYPE_IQ4_NL 20u
|
||||
#define FA_TYPE_BF16 30u
|
||||
#include "fa_types.glsl"
|
||||
|
||||
#if defined(BFLOAT16)
|
||||
#define O_TYPE float
|
||||
@@ -108,45 +98,6 @@ layout (binding = 6) readonly buffer MO {uint32_t data_mask_opt[];};
|
||||
#define O_TYPEV4 FLOAT_TYPEV4
|
||||
#endif
|
||||
|
||||
// Number of matrix elements per buffer block, derived from the K/V type spec
|
||||
// constant. F32 is treated as a vec4 "block" of 4 floats. F16 uses block size 1
|
||||
// and bypasses the dequant path entirely. Quants follow their ggml block sizes.
|
||||
uint fa_block_elems(uint ty) {
|
||||
switch (ty) {
|
||||
case FA_TYPE_F32: return 4u;
|
||||
case FA_TYPE_F16: return 1u;
|
||||
case FA_TYPE_Q4_0: return uint(QUANT_K_Q4_0);
|
||||
case FA_TYPE_Q4_1: return uint(QUANT_K_Q4_1);
|
||||
case FA_TYPE_Q5_0: return uint(QUANT_K_Q5_0);
|
||||
case FA_TYPE_Q5_1: return uint(QUANT_K_Q5_1);
|
||||
case FA_TYPE_Q8_0: return uint(QUANT_K_Q8_0);
|
||||
case FA_TYPE_IQ4_NL: return uint(QUANT_K_IQ4_NL);
|
||||
case FA_TYPE_BF16: return 1u;
|
||||
default: return 1u;
|
||||
}
|
||||
}
|
||||
|
||||
// QUANT_R_MMQ for FA-eligible K types. Q4_*/Q5_* store two nibbles per byte
|
||||
// (R==2); Q8_0 stores one byte per element (R==1). Used to derive the number
|
||||
// of int32s per 32-element block on the MMQ K path: ints_per_block == 8 / R.
|
||||
uint fa_quant_r_mmq(uint ty) {
|
||||
switch (ty) {
|
||||
case FA_TYPE_Q4_0: return uint(QUANT_R_Q4_0);
|
||||
case FA_TYPE_Q4_1: return uint(QUANT_R_Q4_1);
|
||||
case FA_TYPE_Q5_0: return uint(QUANT_R_Q5_0);
|
||||
case FA_TYPE_Q5_1: return uint(QUANT_R_Q5_1);
|
||||
case FA_TYPE_Q8_0: return uint(QUANT_R_Q8_0);
|
||||
default: return 1u;
|
||||
}
|
||||
}
|
||||
|
||||
bool fa_type_needs_shmem(uint ty) {
|
||||
switch (ty) {
|
||||
case FA_TYPE_IQ4_NL: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
// These can't be `const` globals because GLSL forbids function calls in global
|
||||
// const initializers, even when the spec constants would let the driver fold
|
||||
// them. Macros expand at the use site and fold after specialization.
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
#version 450
|
||||
|
||||
#extension GL_EXT_control_flow_attributes : require
|
||||
#extension GL_EXT_shader_16bit_storage : require
|
||||
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
|
||||
#extension GL_KHR_shader_subgroup_basic : enable
|
||||
#if USE_SUBGROUP_ADD
|
||||
#extension GL_KHR_shader_subgroup_arithmetic : enable
|
||||
#endif
|
||||
|
||||
#define BINDING_IDX_K 0u
|
||||
|
||||
#include "types.glsl"
|
||||
#include "fa_types.glsl"
|
||||
#define FaTypeV FA_TYPE_F32
|
||||
|
||||
layout(constant_id = 0) const uint FaTypeK = FA_TYPE_F32;
|
||||
layout(constant_id = 1) const uint FaBlockBytesK = 4;
|
||||
layout(constant_id = 2) const uint SUBGROUP_SIZE = 32;
|
||||
|
||||
#include "flash_attn_dequant.glsl"
|
||||
|
||||
// one workgroup computes one output element, one invocation per head element
|
||||
#define HEAD_SIZE 128
|
||||
|
||||
layout(local_size_x = HEAD_SIZE, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
layout(binding = 0) readonly buffer QBuf { float q[]; };
|
||||
layout(binding = 1) readonly buffer KBufF16 { float16_t k_f16[]; };
|
||||
layout(binding = 1) readonly buffer KBufF32 { float k_f32[]; };
|
||||
layout(binding = 1) readonly buffer KBufBF16 { uint16_t k_bf16[]; };
|
||||
layout(binding = 2) readonly buffer WBuf { float weights[]; };
|
||||
layout(binding = 3) readonly buffer MBuf { float16_t mask[]; };
|
||||
layout(binding = 4) writeonly buffer DstBuf { float dst[]; };
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
uint n_kv;
|
||||
uint n_heads;
|
||||
uint n_tokens;
|
||||
uint n_streams;
|
||||
uint n_masks;
|
||||
uint dispatch_x;
|
||||
uint q_nb1;
|
||||
uint q_nb2;
|
||||
uint q_nb3;
|
||||
uint k_nb2;
|
||||
uint k_nb3;
|
||||
uint w_nb1;
|
||||
uint w_nb3;
|
||||
uint m_nb1;
|
||||
uint m_nb3;
|
||||
uint d_nb1;
|
||||
uint d_nb3;
|
||||
};
|
||||
|
||||
shared float k_row[HEAD_SIZE];
|
||||
|
||||
#if USE_SUBGROUP_ADD
|
||||
shared float sg_partials[HEAD_SIZE / SUBGROUP_SIZE];
|
||||
#else
|
||||
shared float partials[HEAD_SIZE];
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
const uint tid = gl_LocalInvocationID.x;
|
||||
const uint output_idx = gl_WorkGroupID.y * dispatch_x + gl_WorkGroupID.x;
|
||||
const uint n_outputs = n_kv * n_tokens * n_streams;
|
||||
|
||||
if (fa_type_needs_shmem(FaTypeK)) {
|
||||
init_iq_shmem(gl_WorkGroupSize);
|
||||
}
|
||||
|
||||
if (output_idx >= n_outputs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint ik = output_idx % n_kv;
|
||||
const uint ts = output_idx / n_kv;
|
||||
const uint t = ts % n_tokens;
|
||||
const uint s = ts / n_tokens;
|
||||
const uint k_offset = ik * k_nb2 + s * k_nb3;
|
||||
|
||||
// k strides come in as bytes, so scale them down to the view being indexed
|
||||
const uint k_block_elems = fa_block_elems(FaTypeK);
|
||||
const uint k_elem_bytes = FaBlockBytesK / k_block_elems;
|
||||
|
||||
if (FaTypeK == FA_TYPE_F16) {
|
||||
k_row[tid] = float(k_f16[k_offset / k_elem_bytes + tid]);
|
||||
} else if (FaTypeK == FA_TYPE_F32) {
|
||||
k_row[tid] = k_f32[k_offset / k_elem_bytes + tid];
|
||||
} else if (FaTypeK == FA_TYPE_BF16) {
|
||||
k_row[tid] = bf16_to_fp32(uint(k_bf16[k_offset / k_elem_bytes + tid]));
|
||||
} else if (4 * tid < HEAD_SIZE) {
|
||||
const uint coord = 4 * tid;
|
||||
const uint ib = coord / k_block_elems;
|
||||
const uint iqs = coord % k_block_elems;
|
||||
const vec4 values = dequantize4(ib, iqs, k_offset / FaBlockBytesK, BINDING_IDX_K);
|
||||
k_row[coord + 0] = values.x;
|
||||
k_row[coord + 1] = values.y;
|
||||
k_row[coord + 2] = values.z;
|
||||
k_row[coord + 3] = values.w;
|
||||
}
|
||||
barrier();
|
||||
|
||||
const float k_val = k_row[tid];
|
||||
|
||||
float score = 0.0;
|
||||
for (uint h = 0; h < n_heads; ++h) {
|
||||
const float prod = q[h * q_nb1 + t * q_nb2 + s * q_nb3 + tid] * k_val;
|
||||
|
||||
#if USE_SUBGROUP_ADD
|
||||
const float sg_sum = subgroupAdd(prod);
|
||||
if (gl_SubgroupInvocationID == 0) {
|
||||
sg_partials[gl_SubgroupID] = sg_sum;
|
||||
}
|
||||
barrier();
|
||||
|
||||
if (tid == 0) {
|
||||
float sum = 0.0;
|
||||
[[unroll]] for (uint i = 0; i < HEAD_SIZE / SUBGROUP_SIZE; ++i) {
|
||||
sum += sg_partials[i];
|
||||
}
|
||||
score += max(sum, 0.0) * weights[h + t * w_nb1 + s * w_nb3];
|
||||
}
|
||||
// the reads above must complete before the next iteration overwrites sg_partials
|
||||
barrier();
|
||||
#else
|
||||
partials[tid] = prod;
|
||||
barrier();
|
||||
|
||||
[[unroll]] for (uint stride = HEAD_SIZE / 2; stride > 0; stride >>= 1) {
|
||||
if (tid < stride) {
|
||||
partials[tid] += partials[tid + stride];
|
||||
}
|
||||
barrier();
|
||||
}
|
||||
|
||||
if (tid == 0) {
|
||||
score += max(partials[0], 0.0) * weights[h + t * w_nb1 + s * w_nb3];
|
||||
}
|
||||
// the read of partials[0] above must complete before the next iteration
|
||||
// overwrites partials[tid]
|
||||
barrier();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (tid == 0) {
|
||||
const uint mask_offset = ik + t * m_nb1 + (s % n_masks) * m_nb3;
|
||||
dst[ik + t * d_nb1 + s * d_nb3] = score + float(mask[mask_offset]);
|
||||
}
|
||||
}
|
||||
@@ -1069,6 +1069,12 @@ void process_shaders() {
|
||||
|
||||
string_to_spv("gated_linear_attn_f32", "gla.comp", merge_maps(base_dict, {{"A_TYPE", "float"}}));
|
||||
|
||||
// Compile IQ4_NL support in so its shared LUT is available when K uses it.
|
||||
// K quant type is selected at runtime via the FaTypeK spec constant.
|
||||
std::map<std::string, std::string> li_dict = {{"FLOAT_TYPE", "float"}, {"FLOAT_TYPEV4", "vec4"}, {"DATA_A_IQ4_NL", "1"}};
|
||||
string_to_spv("lightning_indexer_f32", "lightning_indexer.comp", li_dict);
|
||||
string_to_spv("lightning_indexer_subgroup_f32", "lightning_indexer.comp", merge_maps(li_dict, {{"USE_SUBGROUP_ADD", "1"}}));
|
||||
|
||||
string_to_spv("rwkv_wkv7_f32", "wkv7.comp", merge_maps(base_dict, {{"A_TYPE", "float"}}));
|
||||
|
||||
string_to_spv("gated_delta_net_f32", "gated_delta_net.comp", merge_maps(base_dict, {{"FLOAT_TYPE", "float"}, {"USE_SUBGROUP_ADD", "1"}, {"USE_SUBGROUP_CLUSTERED", "1"}}));
|
||||
|
||||
@@ -162,7 +162,12 @@ class Keys:
|
||||
TARGET_LAYERS = "{arch}.target_layers"
|
||||
TARGET_HIDDEN_SIZE = "{arch}.target_hidden_size"
|
||||
BLOCK_SIZE = "{arch}.block_size"
|
||||
CONV_KERNEL_SIZE = "{arch}.conv_kernel_size"
|
||||
CONV_GROUP_SIZE = "{arch}.conv_group_size"
|
||||
SELECTOR_RANK = "{arch}.selector_rank"
|
||||
SELECTOR_TOP_K = "{arch}.selector_top_k"
|
||||
SAMPLE_FROM_ANCHOR = "{arch}.sample_from_anchor"
|
||||
HAS_CONFIDENCE_HEAD = "{arch}.has_confidence_head"
|
||||
NORM_BEFORE_RESIDUAL = "{arch}.norm_before_residual"
|
||||
NORM_BEFORE_FC = "{arch}.norm_before_fc"
|
||||
|
||||
@@ -225,6 +230,19 @@ class Keys:
|
||||
COUNT = "{arch}.hyper_connection.count"
|
||||
SINKHORN_ITERATIONS = "{arch}.hyper_connection.sinkhorn_iterations"
|
||||
EPSILON = "{arch}.hyper_connection.epsilon"
|
||||
# absent means the mix projection is full rank (DeepSeek-V4 behaviour)
|
||||
LOW_RANK = "{arch}.hyper_connection.low_rank"
|
||||
|
||||
class PerLayerEmbedding:
|
||||
LAYERS = "{arch}.ple.layers"
|
||||
NGRAM_SIZE = "{arch}.ple.ngram_size"
|
||||
HEADS_PER_NGRAM = "{arch}.ple.heads_per_ngram"
|
||||
CONV_KERNEL = "{arch}.ple.conv_kernel"
|
||||
LAYER_MULTIPLIERS = "{arch}.ple.layer_multipliers"
|
||||
HEAD_OFFSETS = "{arch}.ple.head_offsets"
|
||||
HEAD_VOCAB_SIZES = "{arch}.ple.head_vocab_sizes"
|
||||
EOS_TOKEN_ID = "{arch}.ple.eos_token_id"
|
||||
IMAGE_TOKEN_ID = "{arch}.ple.image_token_id"
|
||||
|
||||
class Rope:
|
||||
DIMENSION_COUNT = "{arch}.rope.dimension_count"
|
||||
@@ -494,6 +512,7 @@ class MODEL_ARCH(IntEnum):
|
||||
QWEN3VLMOE = auto()
|
||||
QWEN35 = auto()
|
||||
QWEN35MOE = auto()
|
||||
QWEN4EXP = auto()
|
||||
PHI2 = auto()
|
||||
PHI3 = auto()
|
||||
PHIMOE = auto()
|
||||
@@ -636,6 +655,9 @@ class MODEL_TENSOR(IntEnum):
|
||||
HC_HEAD_FN = auto()
|
||||
HC_HEAD_BASE = auto()
|
||||
HC_HEAD_SCALE = auto()
|
||||
HC_HEAD_NORM = auto() # qwen4exp
|
||||
HC_HEAD_DOWN = auto() # qwen4exp
|
||||
HC_HEAD_UP = auto() # qwen4exp
|
||||
ROPE_FREQS = auto()
|
||||
ROPE_FACTORS_LONG = auto()
|
||||
ROPE_FACTORS_SHORT = auto()
|
||||
@@ -780,6 +802,20 @@ class MODEL_TENSOR(IntEnum):
|
||||
HC_FFN_FN = auto()
|
||||
HC_FFN_BASE = auto()
|
||||
HC_FFN_SCALE = auto()
|
||||
HC_ATTN_NORM = auto() # qwen4exp
|
||||
HC_ATTN_DOWN = auto() # qwen4exp
|
||||
HC_ATTN_UP = auto() # qwen4exp
|
||||
HC_ATTN_INJECT = auto() # qwen4exp
|
||||
HC_FFN_NORM = auto() # qwen4exp
|
||||
HC_FFN_DOWN = auto() # qwen4exp
|
||||
HC_FFN_UP = auto() # qwen4exp
|
||||
HC_FFN_INJECT = auto() # qwen4exp
|
||||
PLE_KEY = auto() # qwen4exp
|
||||
PLE_VALUE = auto() # qwen4exp
|
||||
PLE_NORM_KEY = auto() # qwen4exp
|
||||
PLE_NORM_QUERY = auto() # qwen4exp
|
||||
PLE_NORM_CONV = auto() # qwen4exp
|
||||
PLE_CONV1D = auto() # qwen4exp
|
||||
ATTN_COMPRESSOR_WKV = auto()
|
||||
ATTN_COMPRESSOR_WGATE = auto()
|
||||
ATTN_COMPRESSOR_APE = auto()
|
||||
@@ -1146,6 +1182,13 @@ class MODEL_TENSOR(IntEnum):
|
||||
DSPARK_MARKOV_W1 = auto() # markov head: prev-token embed
|
||||
DSPARK_MARKOV_W2 = auto() # markov head: bias projection
|
||||
DSPARK_CONF_PROJ = auto() # confidence head
|
||||
DFLASH_ATTN_CONV_BASE = auto()
|
||||
DFLASH_ATTN_CONV_PROJ = auto()
|
||||
DFLASH_FFN_CONV_BASE = auto()
|
||||
DFLASH_FFN_CONV_PROJ = auto()
|
||||
DFLASH_SELECTOR_PREV = auto()
|
||||
DFLASH_SELECTOR_NEXT = auto()
|
||||
DFLASH_SELECTOR_HIDDEN = auto()
|
||||
# lfm2 audio
|
||||
A_ENC_NORM_CONV = auto()
|
||||
A_ENC_LINEAR_POS = auto()
|
||||
@@ -1217,6 +1260,7 @@ MODEL_ARCH_NAMES: dict[MODEL_ARCH, str] = {
|
||||
MODEL_ARCH.QWEN3VLMOE: "qwen3vlmoe",
|
||||
MODEL_ARCH.QWEN35: "qwen35",
|
||||
MODEL_ARCH.QWEN35MOE: "qwen35moe",
|
||||
MODEL_ARCH.QWEN4EXP: "qwen4exp",
|
||||
MODEL_ARCH.PHI2: "phi2",
|
||||
MODEL_ARCH.PHI3: "phi3",
|
||||
MODEL_ARCH.PHIMOE: "phimoe",
|
||||
@@ -1358,6 +1402,9 @@ TENSOR_NAMES: dict[MODEL_TENSOR, str] = {
|
||||
MODEL_TENSOR.HC_HEAD_FN: "output_hc_fn",
|
||||
MODEL_TENSOR.HC_HEAD_BASE: "output_hc_base",
|
||||
MODEL_TENSOR.HC_HEAD_SCALE: "output_hc_scale",
|
||||
MODEL_TENSOR.HC_HEAD_NORM: "output_hc_norm", # qwen4exp
|
||||
MODEL_TENSOR.HC_HEAD_DOWN: "output_hc_down", # qwen4exp
|
||||
MODEL_TENSOR.HC_HEAD_UP: "output_hc_up", # qwen4exp
|
||||
MODEL_TENSOR.ROPE_FREQS: "rope_freqs",
|
||||
MODEL_TENSOR.ROPE_FACTORS_LONG: "rope_factors_long",
|
||||
MODEL_TENSOR.ROPE_FACTORS_SHORT: "rope_factors_short",
|
||||
@@ -1502,6 +1549,20 @@ TENSOR_NAMES: dict[MODEL_TENSOR, str] = {
|
||||
MODEL_TENSOR.HC_FFN_FN: "blk.{bid}.hc_ffn_fn",
|
||||
MODEL_TENSOR.HC_FFN_BASE: "blk.{bid}.hc_ffn_base",
|
||||
MODEL_TENSOR.HC_FFN_SCALE: "blk.{bid}.hc_ffn_scale",
|
||||
MODEL_TENSOR.HC_ATTN_NORM: "blk.{bid}.hc_attn_norm", # qwen4exp
|
||||
MODEL_TENSOR.HC_ATTN_DOWN: "blk.{bid}.hc_attn_down", # qwen4exp
|
||||
MODEL_TENSOR.HC_ATTN_UP: "blk.{bid}.hc_attn_up", # qwen4exp
|
||||
MODEL_TENSOR.HC_ATTN_INJECT: "blk.{bid}.hc_attn_inject", # qwen4exp
|
||||
MODEL_TENSOR.HC_FFN_NORM: "blk.{bid}.hc_ffn_norm", # qwen4exp
|
||||
MODEL_TENSOR.HC_FFN_DOWN: "blk.{bid}.hc_ffn_down", # qwen4exp
|
||||
MODEL_TENSOR.HC_FFN_UP: "blk.{bid}.hc_ffn_up", # qwen4exp
|
||||
MODEL_TENSOR.HC_FFN_INJECT: "blk.{bid}.hc_ffn_inject", # qwen4exp
|
||||
MODEL_TENSOR.PLE_KEY: "blk.{bid}.ple_key", # qwen4exp
|
||||
MODEL_TENSOR.PLE_VALUE: "blk.{bid}.ple_value", # qwen4exp
|
||||
MODEL_TENSOR.PLE_NORM_KEY: "blk.{bid}.ple_norm_key", # qwen4exp
|
||||
MODEL_TENSOR.PLE_NORM_QUERY: "blk.{bid}.ple_norm_query", # qwen4exp
|
||||
MODEL_TENSOR.PLE_NORM_CONV: "blk.{bid}.ple_norm_conv", # qwen4exp
|
||||
MODEL_TENSOR.PLE_CONV1D: "blk.{bid}.ple_conv1d", # qwen4exp
|
||||
MODEL_TENSOR.ATTN_COMPRESSOR_WKV: "blk.{bid}.attn_compressor_kv",
|
||||
MODEL_TENSOR.ATTN_COMPRESSOR_WGATE: "blk.{bid}.attn_compressor_gate",
|
||||
MODEL_TENSOR.ATTN_COMPRESSOR_APE: "blk.{bid}.attn_compressor_ape",
|
||||
@@ -1895,6 +1956,13 @@ TENSOR_NAMES: dict[MODEL_TENSOR, str] = {
|
||||
MODEL_TENSOR.DSPARK_MARKOV_W1: "markov_w1",
|
||||
MODEL_TENSOR.DSPARK_MARKOV_W2: "markov_w2",
|
||||
MODEL_TENSOR.DSPARK_CONF_PROJ: "conf_proj",
|
||||
MODEL_TENSOR.DFLASH_ATTN_CONV_BASE: "blk.{bid}.attn_conv_base",
|
||||
MODEL_TENSOR.DFLASH_ATTN_CONV_PROJ: "blk.{bid}.attn_conv_proj",
|
||||
MODEL_TENSOR.DFLASH_FFN_CONV_BASE: "blk.{bid}.ffn_conv_base",
|
||||
MODEL_TENSOR.DFLASH_FFN_CONV_PROJ: "blk.{bid}.ffn_conv_proj",
|
||||
MODEL_TENSOR.DFLASH_SELECTOR_PREV: "selector_predecessor",
|
||||
MODEL_TENSOR.DFLASH_SELECTOR_NEXT: "selector_successor",
|
||||
MODEL_TENSOR.DFLASH_SELECTOR_HIDDEN: "selector_hidden",
|
||||
MODEL_TENSOR.D2T: "d2t",
|
||||
}
|
||||
|
||||
@@ -2795,6 +2863,58 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
|
||||
MODEL_TENSOR.NEXTN_SHARED_HEAD_HEAD,
|
||||
MODEL_TENSOR.NEXTN_SHARED_HEAD_NORM,
|
||||
],
|
||||
MODEL_ARCH.QWEN4EXP: [
|
||||
MODEL_TENSOR.TOKEN_EMBD,
|
||||
MODEL_TENSOR.OUTPUT,
|
||||
# no OUTPUT_NORM / ATTN_NORM / ATTN_POST_NORM: hyper-connections replace every layer norm
|
||||
MODEL_TENSOR.HC_HEAD_NORM,
|
||||
MODEL_TENSOR.HC_HEAD_DOWN,
|
||||
MODEL_TENSOR.HC_HEAD_UP,
|
||||
MODEL_TENSOR.HC_ATTN_NORM,
|
||||
MODEL_TENSOR.HC_ATTN_DOWN,
|
||||
MODEL_TENSOR.HC_ATTN_UP,
|
||||
MODEL_TENSOR.HC_ATTN_INJECT,
|
||||
MODEL_TENSOR.HC_FFN_NORM,
|
||||
MODEL_TENSOR.HC_FFN_DOWN,
|
||||
MODEL_TENSOR.HC_FFN_UP,
|
||||
MODEL_TENSOR.HC_FFN_INJECT,
|
||||
# full attention layers: ATTN_Q holds [q|gate] interleaved per head
|
||||
MODEL_TENSOR.ATTN_Q,
|
||||
MODEL_TENSOR.ATTN_Q_NORM,
|
||||
MODEL_TENSOR.ATTN_K,
|
||||
MODEL_TENSOR.ATTN_K_NORM,
|
||||
MODEL_TENSOR.ATTN_V,
|
||||
MODEL_TENSOR.ATTN_OUT,
|
||||
MODEL_TENSOR.INDEXER_Q_PROJ,
|
||||
MODEL_TENSOR.INDEXER_K_PROJ,
|
||||
MODEL_TENSOR.INDEXER_Q_NORM,
|
||||
MODEL_TENSOR.INDEXER_K_NORM,
|
||||
MODEL_TENSOR.ATTN_QKV,
|
||||
MODEL_TENSOR.ATTN_GATE,
|
||||
MODEL_TENSOR.SSM_A,
|
||||
MODEL_TENSOR.SSM_CONV1D,
|
||||
MODEL_TENSOR.SSM_DT,
|
||||
MODEL_TENSOR.SSM_NORM,
|
||||
MODEL_TENSOR.SSM_BETA,
|
||||
MODEL_TENSOR.SSM_ALPHA,
|
||||
MODEL_TENSOR.SSM_OUT,
|
||||
MODEL_TENSOR.FFN_GATE_INP,
|
||||
MODEL_TENSOR.FFN_GATE_INP_SHEXP,
|
||||
MODEL_TENSOR.FFN_UP_SHEXP,
|
||||
MODEL_TENSOR.FFN_DOWN_SHEXP,
|
||||
MODEL_TENSOR.FFN_GATE_SHEXP,
|
||||
MODEL_TENSOR.FFN_DOWN_EXP,
|
||||
MODEL_TENSOR.FFN_UP_EXP,
|
||||
MODEL_TENSOR.FFN_GATE_EXP,
|
||||
MODEL_TENSOR.FFN_GATE_UP_EXP,
|
||||
MODEL_TENSOR.PER_LAYER_TOKEN_EMBD,
|
||||
MODEL_TENSOR.PLE_KEY,
|
||||
MODEL_TENSOR.PLE_VALUE,
|
||||
MODEL_TENSOR.PLE_NORM_KEY,
|
||||
MODEL_TENSOR.PLE_NORM_QUERY,
|
||||
MODEL_TENSOR.PLE_NORM_CONV,
|
||||
MODEL_TENSOR.PLE_CONV1D,
|
||||
],
|
||||
MODEL_ARCH.PLAMO: [
|
||||
MODEL_TENSOR.TOKEN_EMBD,
|
||||
MODEL_TENSOR.OUTPUT_NORM,
|
||||
@@ -4953,6 +5073,13 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
|
||||
MODEL_TENSOR.DSPARK_MARKOV_W1,
|
||||
MODEL_TENSOR.DSPARK_MARKOV_W2,
|
||||
MODEL_TENSOR.DSPARK_CONF_PROJ,
|
||||
MODEL_TENSOR.DFLASH_ATTN_CONV_BASE,
|
||||
MODEL_TENSOR.DFLASH_ATTN_CONV_PROJ,
|
||||
MODEL_TENSOR.DFLASH_FFN_CONV_BASE,
|
||||
MODEL_TENSOR.DFLASH_FFN_CONV_PROJ,
|
||||
MODEL_TENSOR.DFLASH_SELECTOR_PREV,
|
||||
MODEL_TENSOR.DFLASH_SELECTOR_NEXT,
|
||||
MODEL_TENSOR.DFLASH_SELECTOR_HIDDEN,
|
||||
],
|
||||
MODEL_ARCH.MISTRAL4: [
|
||||
MODEL_TENSOR.TOKEN_EMBD,
|
||||
|
||||
@@ -993,9 +993,24 @@ class GGUFWriter:
|
||||
def add_block_size(self, value: int) -> None:
|
||||
self.add_uint32(Keys.LLM.BLOCK_SIZE.format(arch=self.arch), value)
|
||||
|
||||
def add_conv_kernel_size(self, value: int) -> None:
|
||||
self.add_uint32(Keys.LLM.CONV_KERNEL_SIZE.format(arch=self.arch), value)
|
||||
|
||||
def add_conv_group_size(self, value: int) -> None:
|
||||
self.add_uint32(Keys.LLM.CONV_GROUP_SIZE.format(arch=self.arch), value)
|
||||
|
||||
def add_selector_rank(self, value: int) -> None:
|
||||
self.add_uint32(Keys.LLM.SELECTOR_RANK.format(arch=self.arch), value)
|
||||
|
||||
def add_selector_top_k(self, value: int) -> None:
|
||||
self.add_uint32(Keys.LLM.SELECTOR_TOP_K.format(arch=self.arch), value)
|
||||
|
||||
def add_sample_from_anchor(self, value: bool) -> None:
|
||||
self.add_bool(Keys.LLM.SAMPLE_FROM_ANCHOR.format(arch=self.arch), value)
|
||||
|
||||
def add_has_confidence_head(self, value: bool) -> None:
|
||||
self.add_bool(Keys.LLM.HAS_CONFIDENCE_HEAD.format(arch=self.arch), value)
|
||||
|
||||
def add_target_layers(self, value: Sequence[int]) -> None:
|
||||
self.add_array(Keys.LLM.TARGET_LAYERS.format(arch=self.arch), value)
|
||||
|
||||
@@ -1029,6 +1044,40 @@ class GGUFWriter:
|
||||
def add_hyper_connection_epsilon(self, value: float) -> None:
|
||||
self.add_float32(Keys.HyperConnection.EPSILON.format(arch=self.arch), value)
|
||||
|
||||
def add_hyper_connection_low_rank(self, value: int) -> None:
|
||||
self.add_uint32(Keys.HyperConnection.LOW_RANK.format(arch=self.arch), value)
|
||||
|
||||
def add_ple_layers(self, values: Sequence[int]) -> None:
|
||||
self.add_array(Keys.PerLayerEmbedding.LAYERS.format(arch=self.arch), values)
|
||||
|
||||
def add_ple_ngram_size(self, value: int) -> None:
|
||||
self.add_uint32(Keys.PerLayerEmbedding.NGRAM_SIZE.format(arch=self.arch), value)
|
||||
|
||||
def add_ple_heads_per_ngram(self, value: int) -> None:
|
||||
self.add_uint32(Keys.PerLayerEmbedding.HEADS_PER_NGRAM.format(arch=self.arch), value)
|
||||
|
||||
def add_ple_conv_kernel(self, value: int) -> None:
|
||||
self.add_uint32(Keys.PerLayerEmbedding.CONV_KERNEL.format(arch=self.arch), value)
|
||||
|
||||
# multipliers reach ~2.4e13; default INT32 inference would truncate them
|
||||
def _add_u64_array(self, key: str, values: Sequence[int]) -> None:
|
||||
self.add_key_value(key, list(values), GGUFValueType.ARRAY, GGUFValueType.UINT64)
|
||||
|
||||
def add_ple_layer_multipliers(self, values: Sequence[int]) -> None:
|
||||
self._add_u64_array(Keys.PerLayerEmbedding.LAYER_MULTIPLIERS.format(arch=self.arch), values)
|
||||
|
||||
def add_ple_head_offsets(self, values: Sequence[int]) -> None:
|
||||
self._add_u64_array(Keys.PerLayerEmbedding.HEAD_OFFSETS.format(arch=self.arch), values)
|
||||
|
||||
def add_ple_head_vocab_sizes(self, values: Sequence[int]) -> None:
|
||||
self._add_u64_array(Keys.PerLayerEmbedding.HEAD_VOCAB_SIZES.format(arch=self.arch), values)
|
||||
|
||||
def add_ple_eos_token_id(self, value: int) -> None:
|
||||
self.add_uint32(Keys.PerLayerEmbedding.EOS_TOKEN_ID.format(arch=self.arch), value)
|
||||
|
||||
def add_ple_image_token_id(self, value: int) -> None:
|
||||
self.add_uint32(Keys.PerLayerEmbedding.IMAGE_TOKEN_ID.format(arch=self.arch), value)
|
||||
|
||||
def add_attention_scale(self, value: float) -> None:
|
||||
self.add_float32(Keys.Attention.SCALE.format(arch=self.arch), value)
|
||||
|
||||
|
||||
@@ -226,3 +226,64 @@ class LazyNumpyTensor(LazyBase):
|
||||
return eager.tofile(*args, **kwargs)
|
||||
|
||||
# TODO: __array_function__
|
||||
|
||||
|
||||
# Tensor written to file one row-chunk at a time
|
||||
class LazyChunkedTensor:
|
||||
|
||||
def __init__(
|
||||
self, chunks: list[Callable[[], np.ndarray]], shape: tuple[int, ...], dtype: DTypeLike,
|
||||
qtype: Any = None, byteswap: bool = False,
|
||||
):
|
||||
self._chunks = chunks
|
||||
self._qtype = qtype
|
||||
self._byteswap = byteswap
|
||||
self.shape = tuple(shape)
|
||||
self.dtype = np.dtype(dtype)
|
||||
|
||||
@property
|
||||
def nbytes(self) -> int:
|
||||
n = self.dtype.itemsize
|
||||
for d in self.shape:
|
||||
n *= d
|
||||
return n
|
||||
|
||||
def numpy(self) -> LazyChunkedTensor:
|
||||
return self
|
||||
|
||||
def quantize(self, qtype: Any) -> LazyChunkedTensor:
|
||||
from .constants import GGMLQuantizationType
|
||||
from .quants import QuantError, quant_shape_to_byte_shape
|
||||
|
||||
if qtype == GGMLQuantizationType.F32:
|
||||
shape, dtype = self.shape, np.dtype(np.float32)
|
||||
elif qtype == GGMLQuantizationType.F16:
|
||||
shape, dtype = self.shape, np.dtype(np.float16)
|
||||
else:
|
||||
try:
|
||||
shape, dtype = quant_shape_to_byte_shape(self.shape, qtype), np.dtype(np.uint8)
|
||||
except ValueError as e:
|
||||
# raised here and not per chunk, so callers can still fall back to F16
|
||||
raise QuantError(str(e)) from e
|
||||
return LazyChunkedTensor(self._chunks, shape, dtype, qtype, self._byteswap)
|
||||
|
||||
def byteswap(self, inplace: bool = False) -> LazyChunkedTensor:
|
||||
if inplace:
|
||||
raise NotImplementedError("a chunked tensor cannot be byteswapped in place")
|
||||
return LazyChunkedTensor(self._chunks, self.shape, self.dtype, self._qtype, not self._byteswap)
|
||||
|
||||
def tofile(self, *args, **kwargs) -> None:
|
||||
from .quants import quantize
|
||||
|
||||
written = 0
|
||||
for load_chunk in self._chunks:
|
||||
chunk = load_chunk()
|
||||
if self._qtype is not None:
|
||||
# exact only because chunks split on rows, and blocks never cross one
|
||||
chunk = quantize(chunk, self._qtype)
|
||||
if self._byteswap:
|
||||
chunk = chunk.byteswap(inplace=False)
|
||||
chunk.tofile(*args, **kwargs)
|
||||
written += chunk.nbytes
|
||||
del chunk
|
||||
assert written == self.nbytes, f"chunked tensor wrote {written} bytes, expected {self.nbytes}"
|
||||
|
||||
@@ -1355,6 +1355,34 @@ class TensorNameMap:
|
||||
"model.confidence_head.proj", # dspark
|
||||
),
|
||||
|
||||
MODEL_TENSOR.DFLASH_ATTN_CONV_BASE: (
|
||||
"model.layers.{bid}.attention_conv.base_kernel",
|
||||
),
|
||||
|
||||
MODEL_TENSOR.DFLASH_ATTN_CONV_PROJ: (
|
||||
"model.layers.{bid}.attention_conv.kernel_projection",
|
||||
),
|
||||
|
||||
MODEL_TENSOR.DFLASH_FFN_CONV_BASE: (
|
||||
"model.layers.{bid}.mlp_conv.base_kernel",
|
||||
),
|
||||
|
||||
MODEL_TENSOR.DFLASH_FFN_CONV_PROJ: (
|
||||
"model.layers.{bid}.mlp_conv.kernel_projection",
|
||||
),
|
||||
|
||||
MODEL_TENSOR.DFLASH_SELECTOR_PREV: (
|
||||
"model.candidate_selector.predecessor_codebook",
|
||||
),
|
||||
|
||||
MODEL_TENSOR.DFLASH_SELECTOR_NEXT: (
|
||||
"model.candidate_selector.successor_codebook",
|
||||
),
|
||||
|
||||
MODEL_TENSOR.DFLASH_SELECTOR_HIDDEN: (
|
||||
"model.candidate_selector.hidden_projection",
|
||||
),
|
||||
|
||||
MODEL_TENSOR.CLS: (
|
||||
"classifier", # jina
|
||||
"classifier.dense", # roberta
|
||||
@@ -2680,6 +2708,65 @@ class TensorNameMap:
|
||||
"model.layers.{bid}.post_attention_layernorm",
|
||||
),
|
||||
},
|
||||
MODEL_ARCH.QWEN4EXP: {
|
||||
MODEL_TENSOR.HC_ATTN_NORM: (
|
||||
"model.layers.{bid}.attn_hyper_connection.hc_norm",
|
||||
),
|
||||
MODEL_TENSOR.HC_ATTN_DOWN: (
|
||||
"model.layers.{bid}.attn_hyper_connection.input_mix_weight_down",
|
||||
),
|
||||
MODEL_TENSOR.HC_ATTN_UP: (
|
||||
"model.layers.{bid}.attn_hyper_connection.input_mix_weight_up",
|
||||
),
|
||||
MODEL_TENSOR.HC_ATTN_INJECT: (
|
||||
"model.layers.{bid}.attn_hyper_connection.block_inject_weight",
|
||||
),
|
||||
MODEL_TENSOR.HC_FFN_NORM: (
|
||||
"model.layers.{bid}.mlp_hyper_connection.hc_norm",
|
||||
),
|
||||
MODEL_TENSOR.HC_FFN_DOWN: (
|
||||
"model.layers.{bid}.mlp_hyper_connection.input_mix_weight_down",
|
||||
),
|
||||
MODEL_TENSOR.HC_FFN_UP: (
|
||||
"model.layers.{bid}.mlp_hyper_connection.input_mix_weight_up",
|
||||
),
|
||||
MODEL_TENSOR.HC_FFN_INJECT: (
|
||||
"model.layers.{bid}.mlp_hyper_connection.block_inject_weight",
|
||||
),
|
||||
MODEL_TENSOR.HC_HEAD_NORM: (
|
||||
"model.hyper_connection_mixer.hc_norm",
|
||||
),
|
||||
MODEL_TENSOR.HC_HEAD_DOWN: (
|
||||
"model.hyper_connection_mixer.input_mix_weight_down",
|
||||
),
|
||||
MODEL_TENSOR.HC_HEAD_UP: (
|
||||
"model.hyper_connection_mixer.input_mix_weight_up",
|
||||
),
|
||||
MODEL_TENSOR.INDEXER_Q_NORM: (
|
||||
"model.layers.{bid}.self_attn.indexer.q_layernorm",
|
||||
),
|
||||
MODEL_TENSOR.INDEXER_K_NORM: (
|
||||
"model.layers.{bid}.self_attn.indexer.k_layernorm",
|
||||
),
|
||||
MODEL_TENSOR.PLE_KEY: (
|
||||
"model.layers.{bid}.ple.key_proj",
|
||||
),
|
||||
MODEL_TENSOR.PLE_VALUE: (
|
||||
"model.layers.{bid}.ple.value_proj",
|
||||
),
|
||||
MODEL_TENSOR.PLE_NORM_KEY: (
|
||||
"model.layers.{bid}.ple.norm_key",
|
||||
),
|
||||
MODEL_TENSOR.PLE_NORM_QUERY: (
|
||||
"model.layers.{bid}.ple.norm_query",
|
||||
),
|
||||
MODEL_TENSOR.PLE_NORM_CONV: (
|
||||
"model.layers.{bid}.ple.norm_conv",
|
||||
),
|
||||
MODEL_TENSOR.PLE_CONV1D: (
|
||||
"model.layers.{bid}.ple.conv1d",
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
mapping: dict[str, tuple[MODEL_TENSOR, str]]
|
||||
|
||||
@@ -214,6 +214,12 @@ extern "C" {
|
||||
LLAMA_API const char * llama_load_mode_name(enum llama_load_mode load_mode);
|
||||
LLAMA_API enum llama_load_mode llama_load_mode_from_str(const char * str);
|
||||
|
||||
enum llama_tensor_read_lazy {
|
||||
LLAMA_TENSOR_READ_LAZY_OFF = 0, // always read the whole tensor up front
|
||||
LLAMA_TENSOR_READ_LAZY_AUTO = 1, // lazy only for marked tensors larger than 4 GiB (requires mmap)
|
||||
LLAMA_TENSOR_READ_LAZY_ON = 2, // read the rows of tensors marked by the arch on demand (requires mmap)
|
||||
};
|
||||
|
||||
enum llama_context_type {
|
||||
LLAMA_CONTEXT_TYPE_DEFAULT = 0,
|
||||
LLAMA_CONTEXT_TYPE_MTP = 1,
|
||||
@@ -315,6 +321,8 @@ extern "C" {
|
||||
enum llama_split_mode split_mode; // how to split the model across multiple GPUs
|
||||
enum llama_load_mode load_mode; // how to load the model
|
||||
|
||||
enum llama_tensor_read_lazy tensor_read_lazy; // on-demand reading of tensors marked by the arch
|
||||
|
||||
// the GPU that is used for the entire model when split_mode is LLAMA_SPLIT_MODE_NONE
|
||||
int32_t main_gpu;
|
||||
|
||||
@@ -437,6 +445,7 @@ extern "C" {
|
||||
const struct llama_model_kv_override * kv_overrides; // pointer to kv overrides
|
||||
const struct llama_model_tensor_override * tt_overrides; // pointer to tensor overrides
|
||||
const int32_t * prune_layers; // pointer to layer indices to prune
|
||||
size_t max_buf_size; // max bytes of tensor rows kept in memory at once, 0 = default (8 GiB)
|
||||
} llama_model_quantize_params;
|
||||
|
||||
typedef struct llama_logit_bias {
|
||||
|
||||
+5
-1
@@ -48,7 +48,11 @@ echo "org/repo: $org_repo"
|
||||
|
||||
meta=$(curl -sSLf -H "Accept: application/vnd.github+json" "https://api.github.com/repos/$org_repo/pulls/$PR")
|
||||
|
||||
url_remote=$(echo "$meta" | jq -r '.head.repo.clone_url')
|
||||
if [[ $url_origin =~ ^git@ ]]; then
|
||||
url_remote=$(echo "$meta" | jq -r '.head.repo.ssh_url')
|
||||
else
|
||||
url_remote=$(echo "$meta" | jq -r '.head.repo.clone_url')
|
||||
fi
|
||||
head_ref=$(echo "$meta" | jq -r '.head.ref')
|
||||
|
||||
echo "url: $url_remote"
|
||||
|
||||
@@ -31,6 +31,7 @@ add_library(llama
|
||||
llama-memory.cpp
|
||||
llama-memory-hybrid.cpp
|
||||
llama-memory-hybrid-iswa.cpp
|
||||
llama-memory-hybrid-idx.cpp
|
||||
llama-memory-recurrent.cpp
|
||||
llama-mmap.cpp
|
||||
llama-model-loader.cpp
|
||||
|
||||
@@ -40,6 +40,7 @@ static const std::map<llm_arch, const char *> LLM_ARCH_NAMES = {
|
||||
{ LLM_ARCH_QWEN3VLMOE, "qwen3vlmoe" },
|
||||
{ LLM_ARCH_QWEN35, "qwen35" },
|
||||
{ LLM_ARCH_QWEN35MOE, "qwen35moe" },
|
||||
{ LLM_ARCH_QWEN4EXP, "qwen4exp" },
|
||||
{ LLM_ARCH_PHI2, "phi2" },
|
||||
{ LLM_ARCH_PHI3, "phi3" },
|
||||
{ LLM_ARCH_PHIMOE, "phimoe" },
|
||||
@@ -293,6 +294,17 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {
|
||||
{ LLM_KV_HYPER_CONNECTION_COUNT, "%s.hyper_connection.count" },
|
||||
{ LLM_KV_HYPER_CONNECTION_SINKHORN_ITERATIONS, "%s.hyper_connection.sinkhorn_iterations" },
|
||||
{ LLM_KV_HYPER_CONNECTION_EPSILON, "%s.hyper_connection.epsilon" },
|
||||
{ LLM_KV_HYPER_CONNECTION_LOW_RANK, "%s.hyper_connection.low_rank" },
|
||||
|
||||
{ LLM_KV_PLE_LAYERS, "%s.ple.layers" },
|
||||
{ LLM_KV_PLE_NGRAM_SIZE, "%s.ple.ngram_size" },
|
||||
{ LLM_KV_PLE_HEADS_PER_NGRAM, "%s.ple.heads_per_ngram" },
|
||||
{ LLM_KV_PLE_CONV_KERNEL, "%s.ple.conv_kernel" },
|
||||
{ LLM_KV_PLE_LAYER_MULTIPLIERS, "%s.ple.layer_multipliers" },
|
||||
{ LLM_KV_PLE_HEAD_OFFSETS, "%s.ple.head_offsets" },
|
||||
{ LLM_KV_PLE_HEAD_VOCAB_SIZES, "%s.ple.head_vocab_sizes" },
|
||||
{ LLM_KV_PLE_EOS_TOKEN_ID, "%s.ple.eos_token_id" },
|
||||
{ LLM_KV_PLE_IMAGE_TOKEN_ID, "%s.ple.image_token_id" },
|
||||
|
||||
{ LLM_KV_HASH_LAYER_COUNT, "%s.hash_layer_count" },
|
||||
|
||||
@@ -344,6 +356,12 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {
|
||||
{ LLM_KV_NORM_BEFORE_RESIDUAL, "%s.norm_before_residual" },
|
||||
{ LLM_KV_NORM_BEFORE_FC, "%s.norm_before_fc" },
|
||||
|
||||
{ LLM_KV_DFLASH_BLOCK_SIZE, "%s.block_size" },
|
||||
{ LLM_KV_DFLASH_CONV_KERNEL_SIZE, "%s.conv_kernel_size" },
|
||||
{ LLM_KV_DFLASH_CONV_GROUP_SIZE, "%s.conv_group_size" },
|
||||
{ LLM_KV_DFLASH_SELECTOR_RANK, "%s.selector_rank" },
|
||||
{ LLM_KV_DFLASH_SELECTOR_TOP_K, "%s.selector_top_k" },
|
||||
|
||||
{ LLM_KV_SHORTCONV_L_CACHE, "%s.shortconv.l_cache" },
|
||||
// sentence-transformers dense modules feature dims
|
||||
{ LLM_KV_DENSE_2_FEAT_IN, "%s.dense_2_feat_in" },
|
||||
@@ -500,12 +518,29 @@ static const std::map<llm_tensor, const char *> LLM_TENSOR_NAMES = {
|
||||
{ LLM_TENSOR_HC_HEAD_FN, "output_hc_fn" },
|
||||
{ LLM_TENSOR_HC_HEAD_BASE, "output_hc_base" },
|
||||
{ LLM_TENSOR_HC_HEAD_SCALE, "output_hc_scale" },
|
||||
{ LLM_TENSOR_HC_HEAD_NORM, "output_hc_norm" },
|
||||
{ LLM_TENSOR_HC_HEAD_DOWN, "output_hc_down" },
|
||||
{ LLM_TENSOR_HC_HEAD_UP, "output_hc_up" },
|
||||
{ LLM_TENSOR_HC_ATTN_FN, "blk.%d.hc_attn_fn" },
|
||||
{ LLM_TENSOR_HC_ATTN_BASE, "blk.%d.hc_attn_base" },
|
||||
{ LLM_TENSOR_HC_ATTN_SCALE, "blk.%d.hc_attn_scale" },
|
||||
{ LLM_TENSOR_HC_FFN_FN, "blk.%d.hc_ffn_fn" },
|
||||
{ LLM_TENSOR_HC_FFN_BASE, "blk.%d.hc_ffn_base" },
|
||||
{ LLM_TENSOR_HC_FFN_SCALE, "blk.%d.hc_ffn_scale" },
|
||||
{ LLM_TENSOR_HC_ATTN_NORM, "blk.%d.hc_attn_norm" },
|
||||
{ LLM_TENSOR_HC_ATTN_DOWN, "blk.%d.hc_attn_down" },
|
||||
{ LLM_TENSOR_HC_ATTN_UP, "blk.%d.hc_attn_up" },
|
||||
{ LLM_TENSOR_HC_ATTN_INJECT, "blk.%d.hc_attn_inject" },
|
||||
{ LLM_TENSOR_HC_FFN_NORM, "blk.%d.hc_ffn_norm" },
|
||||
{ LLM_TENSOR_HC_FFN_DOWN, "blk.%d.hc_ffn_down" },
|
||||
{ LLM_TENSOR_HC_FFN_UP, "blk.%d.hc_ffn_up" },
|
||||
{ LLM_TENSOR_HC_FFN_INJECT, "blk.%d.hc_ffn_inject" },
|
||||
{ LLM_TENSOR_PLE_KEY, "blk.%d.ple_key" },
|
||||
{ LLM_TENSOR_PLE_VALUE, "blk.%d.ple_value" },
|
||||
{ LLM_TENSOR_PLE_NORM_KEY, "blk.%d.ple_norm_key" },
|
||||
{ LLM_TENSOR_PLE_NORM_QUERY, "blk.%d.ple_norm_query" },
|
||||
{ LLM_TENSOR_PLE_NORM_CONV, "blk.%d.ple_norm_conv" },
|
||||
{ LLM_TENSOR_PLE_CONV1D, "blk.%d.ple_conv1d" },
|
||||
{ LLM_TENSOR_ATTN_COMPRESSOR_WKV, "blk.%d.attn_compressor_kv" },
|
||||
{ LLM_TENSOR_ATTN_COMPRESSOR_WGATE, "blk.%d.attn_compressor_gate" },
|
||||
{ LLM_TENSOR_ATTN_COMPRESSOR_APE, "blk.%d.attn_compressor_ape" },
|
||||
@@ -651,6 +686,13 @@ static const std::map<llm_tensor, const char *> LLM_TENSOR_NAMES = {
|
||||
{ LLM_TENSOR_DSPARK_MARKOV_W1, "markov_w1" },
|
||||
{ LLM_TENSOR_DSPARK_MARKOV_W2, "markov_w2" },
|
||||
{ LLM_TENSOR_DSPARK_CONF_PROJ, "conf_proj" },
|
||||
{ LLM_TENSOR_DFLASH_ATTN_CONV_BASE, "blk.%d.attn_conv_base" },
|
||||
{ LLM_TENSOR_DFLASH_ATTN_CONV_PROJ, "blk.%d.attn_conv_proj" },
|
||||
{ LLM_TENSOR_DFLASH_FFN_CONV_BASE, "blk.%d.ffn_conv_base" },
|
||||
{ LLM_TENSOR_DFLASH_FFN_CONV_PROJ, "blk.%d.ffn_conv_proj" },
|
||||
{ LLM_TENSOR_DFLASH_SELECTOR_PREV, "selector_predecessor" },
|
||||
{ LLM_TENSOR_DFLASH_SELECTOR_NEXT, "selector_successor" },
|
||||
{ LLM_TENSOR_DFLASH_SELECTOR_HIDDEN, "selector_hidden" },
|
||||
};
|
||||
|
||||
// declare information about the model weight tensors:
|
||||
@@ -704,12 +746,29 @@ static const std::map<llm_tensor, llm_tensor_info> LLM_TENSOR_INFOS = {
|
||||
{LLM_TENSOR_HC_HEAD_FN, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_HC_HEAD_BASE, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_ADD}},
|
||||
{LLM_TENSOR_HC_HEAD_SCALE, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_HC_HEAD_NORM, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_HC_HEAD_DOWN, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_HC_HEAD_UP, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_HC_ATTN_FN, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_HC_ATTN_BASE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_ADD}},
|
||||
{LLM_TENSOR_HC_ATTN_SCALE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_HC_FFN_FN, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_HC_FFN_BASE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_ADD}},
|
||||
{LLM_TENSOR_HC_FFN_SCALE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_HC_ATTN_NORM, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_HC_ATTN_DOWN, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_HC_ATTN_UP, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_HC_ATTN_INJECT, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_HC_FFN_NORM, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_HC_FFN_DOWN, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_HC_FFN_UP, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_HC_FFN_INJECT, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_PLE_KEY, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_PLE_VALUE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_PLE_NORM_KEY, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_PLE_NORM_QUERY, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_PLE_NORM_CONV, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_PLE_CONV1D, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_SSM_CONV}},
|
||||
{LLM_TENSOR_ATTN_COMPRESSOR_WKV, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_ATTN_COMPRESSOR_WGATE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_ATTN_COMPRESSOR_APE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_GET_ROWS}},
|
||||
@@ -916,6 +975,13 @@ static const std::map<llm_tensor, llm_tensor_info> LLM_TENSOR_INFOS = {
|
||||
{LLM_TENSOR_DSPARK_MARKOV_W1, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_GET_ROWS}},
|
||||
{LLM_TENSOR_DSPARK_MARKOV_W2, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_DSPARK_CONF_PROJ, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_DFLASH_ATTN_CONV_BASE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_DFLASH_ATTN_CONV_PROJ, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_DFLASH_FFN_CONV_BASE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_DFLASH_FFN_CONV_PROJ, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_DFLASH_SELECTOR_PREV, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_GET_ROWS}},
|
||||
{LLM_TENSOR_DFLASH_SELECTOR_NEXT, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_GET_ROWS}},
|
||||
{LLM_TENSOR_DFLASH_SELECTOR_HIDDEN, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}},
|
||||
};
|
||||
|
||||
LLM_KV::LLM_KV(llm_arch arch, const char * suffix) : arch(arch), suffix(suffix) {}
|
||||
@@ -1009,6 +1075,7 @@ bool llm_arch_is_hybrid(const llm_arch & arch) {
|
||||
case LLM_ARCH_KIMI_K3:
|
||||
case LLM_ARCH_QWEN35:
|
||||
case LLM_ARCH_QWEN35MOE:
|
||||
case LLM_ARCH_QWEN4EXP:
|
||||
case LLM_ARCH_DEEPSEEK4:
|
||||
case LLM_ARCH_MINIMAX_01:
|
||||
return true;
|
||||
|
||||
@@ -45,6 +45,7 @@ enum llm_arch {
|
||||
LLM_ARCH_QWEN3VLMOE,
|
||||
LLM_ARCH_QWEN35,
|
||||
LLM_ARCH_QWEN35MOE,
|
||||
LLM_ARCH_QWEN4EXP,
|
||||
LLM_ARCH_PHI2,
|
||||
LLM_ARCH_PHI3,
|
||||
LLM_ARCH_PHIMOE,
|
||||
@@ -298,6 +299,17 @@ enum llm_kv {
|
||||
LLM_KV_HYPER_CONNECTION_COUNT,
|
||||
LLM_KV_HYPER_CONNECTION_SINKHORN_ITERATIONS,
|
||||
LLM_KV_HYPER_CONNECTION_EPSILON,
|
||||
LLM_KV_HYPER_CONNECTION_LOW_RANK,
|
||||
|
||||
LLM_KV_PLE_LAYERS,
|
||||
LLM_KV_PLE_NGRAM_SIZE,
|
||||
LLM_KV_PLE_HEADS_PER_NGRAM,
|
||||
LLM_KV_PLE_CONV_KERNEL,
|
||||
LLM_KV_PLE_LAYER_MULTIPLIERS,
|
||||
LLM_KV_PLE_HEAD_OFFSETS,
|
||||
LLM_KV_PLE_HEAD_VOCAB_SIZES,
|
||||
LLM_KV_PLE_EOS_TOKEN_ID,
|
||||
LLM_KV_PLE_IMAGE_TOKEN_ID,
|
||||
|
||||
LLM_KV_HASH_LAYER_COUNT,
|
||||
|
||||
@@ -387,6 +399,11 @@ enum llm_kv {
|
||||
|
||||
LLM_KV_TARGET_LAYERS,
|
||||
LLM_KV_TARGET_HIDDEN_SIZE,
|
||||
LLM_KV_DFLASH_BLOCK_SIZE,
|
||||
LLM_KV_DFLASH_CONV_KERNEL_SIZE,
|
||||
LLM_KV_DFLASH_CONV_GROUP_SIZE,
|
||||
LLM_KV_DFLASH_SELECTOR_RANK,
|
||||
LLM_KV_DFLASH_SELECTOR_TOP_K,
|
||||
LLM_KV_NORM_BEFORE_RESIDUAL,
|
||||
LLM_KV_NORM_BEFORE_FC,
|
||||
|
||||
@@ -565,12 +582,29 @@ enum llm_tensor {
|
||||
LLM_TENSOR_HC_HEAD_FN,
|
||||
LLM_TENSOR_HC_HEAD_BASE,
|
||||
LLM_TENSOR_HC_HEAD_SCALE,
|
||||
LLM_TENSOR_HC_HEAD_NORM, // qwen4exp
|
||||
LLM_TENSOR_HC_HEAD_DOWN, // qwen4exp
|
||||
LLM_TENSOR_HC_HEAD_UP, // qwen4exp
|
||||
LLM_TENSOR_HC_ATTN_FN,
|
||||
LLM_TENSOR_HC_ATTN_BASE,
|
||||
LLM_TENSOR_HC_ATTN_SCALE,
|
||||
LLM_TENSOR_HC_FFN_FN,
|
||||
LLM_TENSOR_HC_FFN_BASE,
|
||||
LLM_TENSOR_HC_FFN_SCALE,
|
||||
LLM_TENSOR_HC_ATTN_NORM, // qwen4exp
|
||||
LLM_TENSOR_HC_ATTN_DOWN, // qwen4exp
|
||||
LLM_TENSOR_HC_ATTN_UP, // qwen4exp
|
||||
LLM_TENSOR_HC_ATTN_INJECT, // qwen4exp
|
||||
LLM_TENSOR_HC_FFN_NORM, // qwen4exp
|
||||
LLM_TENSOR_HC_FFN_DOWN, // qwen4exp
|
||||
LLM_TENSOR_HC_FFN_UP, // qwen4exp
|
||||
LLM_TENSOR_HC_FFN_INJECT, // qwen4exp
|
||||
LLM_TENSOR_PLE_KEY, // qwen4exp
|
||||
LLM_TENSOR_PLE_VALUE, // qwen4exp
|
||||
LLM_TENSOR_PLE_NORM_KEY, // qwen4exp
|
||||
LLM_TENSOR_PLE_NORM_QUERY, // qwen4exp
|
||||
LLM_TENSOR_PLE_NORM_CONV, // qwen4exp
|
||||
LLM_TENSOR_PLE_CONV1D, // qwen4exp
|
||||
LLM_TENSOR_ATTN_COMPRESSOR_WKV,
|
||||
LLM_TENSOR_ATTN_COMPRESSOR_WGATE,
|
||||
LLM_TENSOR_ATTN_COMPRESSOR_APE,
|
||||
@@ -659,6 +693,13 @@ enum llm_tensor {
|
||||
LLM_TENSOR_DSPARK_MARKOV_W1,
|
||||
LLM_TENSOR_DSPARK_MARKOV_W2,
|
||||
LLM_TENSOR_DSPARK_CONF_PROJ,
|
||||
LLM_TENSOR_DFLASH_ATTN_CONV_BASE,
|
||||
LLM_TENSOR_DFLASH_ATTN_CONV_PROJ,
|
||||
LLM_TENSOR_DFLASH_FFN_CONV_BASE,
|
||||
LLM_TENSOR_DFLASH_FFN_CONV_PROJ,
|
||||
LLM_TENSOR_DFLASH_SELECTOR_PREV,
|
||||
LLM_TENSOR_DFLASH_SELECTOR_NEXT,
|
||||
LLM_TENSOR_DFLASH_SELECTOR_HIDDEN,
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -2301,12 +2301,17 @@ uint32_t llama_context::graph_max_nodes(uint32_t n_tokens) const {
|
||||
model.arch == LLM_ARCH_BAILINGMOE3 ||
|
||||
model.arch == LLM_ARCH_QWEN35 ||
|
||||
model.arch == LLM_ARCH_QWEN35MOE ||
|
||||
model.arch == LLM_ARCH_QWEN4EXP ||
|
||||
model.arch == LLM_ARCH_DEEPSEEK4 ||
|
||||
(model.arch == LLM_ARCH_DFLASH && model.hparams.dsv4_hc_mult > 0) ||
|
||||
model.arch == LLM_ARCH_NANBEIGE ||
|
||||
model.arch == LLM_ARCH_MINIMAX_01 ||
|
||||
model.arch == LLM_ARCH_MINIMAX_M3) {
|
||||
res = std::max<uint32_t>(n_tokens * 40, 32u * model.n_tensors());
|
||||
} else if (model.arch == LLM_ARCH_DFLASH && model.hparams.dflash_selector_rank > 0) {
|
||||
// DFlash2's convolutions and selector are shape work rather than matmuls,
|
||||
// so they cost ~8.6 nodes per tensor against ~5.9 for a plain DFlash draft
|
||||
res = std::max<uint32_t>(1024u, 12u*model.n_tensors());
|
||||
} else {
|
||||
res = std::max<uint32_t>(1024u, 8u*model.n_tensors());
|
||||
for (const auto & lora : model.loras) {
|
||||
|
||||
@@ -120,6 +120,8 @@ LLAMA_API llama_context * llama_get_ctx_other(struct llama_context * ctx);
|
||||
// model/context data extraction
|
||||
//
|
||||
|
||||
LLAMA_API int32_t llama_model_dflash_selector_top_k(const struct llama_model * model);
|
||||
|
||||
// returns pointer to the target-model layer indices
|
||||
LLAMA_API const int32_t * llama_model_target_layer_ids (const struct llama_model * model);
|
||||
// returns the number of extracted layers from target model
|
||||
|
||||
+22
-1
@@ -201,7 +201,11 @@ uint32_t llama_hparams::n_embd_r() const {
|
||||
// TODO: maybe support other convolution strides than 1
|
||||
// NOTE: since the first column of the conv_state is shifted out each time, it's not actually needed
|
||||
// Corresponds to Mamba's conv_states size
|
||||
return (ssm_d_conv > 0 ? ssm_d_conv - 1 : 0) * (ssm_d_inner + 2*ssm_n_group*ssm_d_state);
|
||||
const uint32_t n_conv = (ssm_d_conv > 0 ? ssm_d_conv - 1 : 0) * (ssm_d_inner + 2*ssm_n_group*ssm_d_state);
|
||||
|
||||
// PLE conv history needs its own row: Meta splits cache_r_l by head, so a history packed behind the first is unaddressable
|
||||
// it lives in cache_ple_r_l instead, mirrored like the rest of the PLE module
|
||||
return n_conv;
|
||||
}
|
||||
|
||||
uint32_t llama_hparams::n_embd_s() const {
|
||||
@@ -236,6 +240,23 @@ bool llama_hparams::is_recr(uint32_t il) const {
|
||||
GGML_ABORT("%s: il (%u) out of bounds (n_layer_all: %u)\n", __func__, il, n_layer_all);
|
||||
}
|
||||
|
||||
uint32_t llama_hparams::ple_conv_state() const {
|
||||
if (ple_n_heads == 0 || ple_conv_kernel == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// dilation equals the n-gram size, matching the reference module
|
||||
return (ple_conv_kernel - 1) * ple_ngram_size * dsv4_hc_mult * n_embd;
|
||||
}
|
||||
|
||||
bool llama_hparams::is_ple(uint32_t il) const {
|
||||
if (il < n_layer_all) {
|
||||
return is_ple_impl[il];
|
||||
}
|
||||
|
||||
GGML_ABORT("%s: il (%u) out of bounds (n_layer_all: %u)\n", __func__, il, n_layer_all);
|
||||
}
|
||||
|
||||
uint32_t llama_hparams::n_pos_per_embd() const {
|
||||
return rope_type == LLAMA_ROPE_TYPE_MROPE || rope_type == LLAMA_ROPE_TYPE_IMROPE ? 4 : 1;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
#include "llama.h"
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
// bump if necessary
|
||||
#define LLAMA_MAX_LAYERS 512
|
||||
#define LLAMA_MAX_EXPERTS 1024 // Kimi K3
|
||||
#define LLAMA_MAX_PLE_NGRAM 8 // qwen4exp
|
||||
#define LLAMA_MAX_PLE_HEADS 64 // qwen4exp
|
||||
|
||||
enum llama_expert_gating_func_type {
|
||||
LLAMA_EXPERT_GATING_FUNC_TYPE_NONE = 0,
|
||||
@@ -223,6 +226,12 @@ struct llama_hparams {
|
||||
// output embedding dimension (0 = use n_embd)
|
||||
uint32_t n_embd_out_impl = 0;
|
||||
|
||||
uint32_t dflash_block_size = 0;
|
||||
uint32_t dflash_conv_kernel_size = 0;
|
||||
uint32_t dflash_conv_group_size = 0;
|
||||
uint32_t dflash_selector_rank = 0;
|
||||
uint32_t dflash_selector_top_k = 0;
|
||||
|
||||
// llama4 smallthinker
|
||||
uint32_t n_moe_layer_step = 0;
|
||||
uint32_t n_no_rope_layer_step = 4;
|
||||
@@ -270,6 +279,30 @@ struct llama_hparams {
|
||||
float dsv4_hc_eps = 0.0f;
|
||||
std::array<uint32_t, LLAMA_MAX_LAYERS> dsv4_compress_ratios;
|
||||
|
||||
// 0 = full rank (DeepSeek-V4)
|
||||
uint32_t hc_low_rank = 0;
|
||||
|
||||
uint32_t ple_ngram_size = 0;
|
||||
uint32_t ple_heads_per_ngram = 0;
|
||||
uint32_t ple_conv_kernel = 0;
|
||||
uint32_t ple_n_heads = 0; // (ngram_size - 1) * heads_per_ngram
|
||||
uint32_t ple_head_dim = 0;
|
||||
uint32_t ple_eos_token_id = 0;
|
||||
// the id the PLE hash stands in at image positions; 0 makes the loader fall back to EOS
|
||||
uint32_t ple_image_token_id = 0;
|
||||
// the file lists PLE layer indices, so this is never a per-layer gguf array and can hold one bit per layer
|
||||
std::bitset<LLAMA_MAX_LAYERS> is_ple_impl;
|
||||
// the hash multipliers reach ~2e13 and have to stay 64-bit
|
||||
std::array<uint64_t, LLAMA_MAX_PLE_NGRAM> ple_layer_multipliers;
|
||||
// head offsets and vocab sizes are token-space indices; the gather truncates them to int32 anyway
|
||||
std::array<uint32_t, LLAMA_MAX_PLE_HEADS> ple_head_offsets;
|
||||
std::array<uint32_t, LLAMA_MAX_PLE_HEADS> ple_head_vocab_sizes;
|
||||
|
||||
bool is_ple(uint32_t il) const;
|
||||
|
||||
// PLE conv history rows: (kernel - 1) * ngram_size; 0 without a PLE module
|
||||
uint32_t ple_conv_state() const;
|
||||
|
||||
// qwen3vl deepstack
|
||||
// When parsed from GGUF, this implies the first N layers consume the first
|
||||
// N deepstack embeddings. Use deepstack_mapping_arr if you need a more
|
||||
|
||||
+136
-19
@@ -6,6 +6,7 @@
|
||||
#include "llama-context.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
@@ -78,7 +79,8 @@ llama_kv_cache::llama_kv_cache(
|
||||
llama_memory_t mem_other,
|
||||
const layer_filter_cb & filter,
|
||||
const layer_reuse_cb & reuse,
|
||||
const layer_share_cb & share) :
|
||||
const layer_share_cb & share,
|
||||
const char * name_tag) :
|
||||
model(model), hparams(hparams), v_trans(v_trans),
|
||||
n_seq_max(n_seq_max), n_stream(unified ? 1 : n_seq_max), n_pad(n_pad), n_swa(n_swa), swa_type(swa_type),
|
||||
other(static_cast<llama_kv_cache *>(mem_other)),
|
||||
@@ -232,8 +234,8 @@ llama_kv_cache::llama_kv_cache(
|
||||
ggml_tensor * k = has_k ? ggml_new_tensor_3d(ctx, type_k, n_embd_k_gqa, kv_size, n_stream) : nullptr;
|
||||
ggml_tensor * v = has_v ? ggml_new_tensor_3d(ctx, type_v, n_embd_v_gqa, kv_size, n_stream) : nullptr;
|
||||
|
||||
has_k && ggml_format_name(k, "cache_k_l%d", il);
|
||||
has_v && ggml_format_name(v, "cache_v_l%d", il);
|
||||
has_k && ggml_format_name(k, "cache_%sk_l%d", name_tag, il);
|
||||
has_v && ggml_format_name(v, "cache_%sv_l%d", name_tag, il);
|
||||
|
||||
std::vector<ggml_tensor *> k_stream;
|
||||
std::vector<ggml_tensor *> v_stream;
|
||||
@@ -1129,7 +1131,7 @@ void llama_kv_cache::apply_ubatch(const slot_info & sinfo, const llama_ubatch &
|
||||
|
||||
cells.pos_set(idx, ubatch.pos[i]);
|
||||
|
||||
if (ubatch.is_pos_2d() || ubatch.token) {
|
||||
if (ubatch.is_pos_2d() || ubatch.token || hparams.ple_n_heads > 0) {
|
||||
llama_kv_cell_ext ext;
|
||||
|
||||
if (ubatch.is_pos_2d()) {
|
||||
@@ -1139,6 +1141,12 @@ void llama_kv_cache::apply_ubatch(const slot_info & sinfo, const llama_ubatch &
|
||||
|
||||
if (ubatch.token) {
|
||||
ext.tok = ubatch.token[i];
|
||||
} else if (hparams.ple_n_heads > 0) {
|
||||
// embd batch (multimodal input) has no token ids, need to pad it with the correct ID for PLE layers
|
||||
// TODO @ngxson : check if we can do the same as gemma 3n / gemma 4
|
||||
ext.tok = hparams.ple_image_token_id != 0
|
||||
? (llama_token) hparams.ple_image_token_id
|
||||
: (llama_token) hparams.ple_eos_token_id;
|
||||
}
|
||||
|
||||
cells.ext_set(idx, ext);
|
||||
@@ -1814,7 +1822,8 @@ void llama_kv_cache::set_input_v_rot(ggml_tensor * dst) const {
|
||||
}
|
||||
|
||||
bool llama_kv_cache::has_cell_ext() const {
|
||||
return hparams.n_pos_per_embd() > 1;
|
||||
// M-RoPE needs the 2D position, the PLE n-gram hash needs the token id
|
||||
return hparams.n_pos_per_embd() > 1 || hparams.ple_n_heads > 0;
|
||||
}
|
||||
|
||||
void llama_kv_cache::get_prev_tokens(const llama_ubatch & ubatch, uint32_t n, std::vector<llama_token> & res) const {
|
||||
@@ -1843,6 +1852,8 @@ void llama_kv_cache::get_prev_tokens(const llama_ubatch & ubatch, uint32_t n, st
|
||||
seqs.set(ubatch.seq_id_unq[s]);
|
||||
}
|
||||
|
||||
const llama_pos w0 = p_min - (llama_pos) n;
|
||||
|
||||
// (seq_id, pos) -> token, for every cell that could be a predecessor of a ubatch token
|
||||
std::unordered_map<uint64_t, llama_token> hist;
|
||||
|
||||
@@ -1850,28 +1861,71 @@ void llama_kv_cache::get_prev_tokens(const llama_ubatch & ubatch, uint32_t n, st
|
||||
return ((uint64_t) seq_id << 32) | (uint32_t) pos;
|
||||
};
|
||||
|
||||
// handle M-RoPE gaps: multiple tokens share the same temporal pos
|
||||
// TODO @ngxson : improve this in the future
|
||||
std::array<std::pair<llama_pos, llama_token>, LLAMA_MAX_SEQ> below;
|
||||
below.fill({ -1, LLAMA_TOKEN_NULL });
|
||||
|
||||
for (uint32_t s = 0; s < n_stream; ++s) {
|
||||
v_cells[s].for_each_token_in(seqs, p_min - (llama_pos) n, p_max,
|
||||
// p_max inclusive: an embd token looks up cells at its own (shared) position
|
||||
v_cells[s].for_each_token_in(seqs, 0, p_max + 1,
|
||||
[&](llama_seq_id seq_id, llama_pos pos, llama_token tok) {
|
||||
hist[key(seq_id, pos)] = tok;
|
||||
if (pos >= w0) {
|
||||
hist[key(seq_id, pos)] = tok;
|
||||
} else if (pos > below[seq_id].first) {
|
||||
below[seq_id] = { pos, tok };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// the token at pos p, or the nearest earlier one when p falls in an M-RoPE gap
|
||||
const auto lookup = [&](llama_seq_id seq_id, llama_pos p) -> llama_token {
|
||||
for (llama_pos q = p; q >= w0; --q) {
|
||||
const auto it = hist.find(key(seq_id, q));
|
||||
if (it != hist.end()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
return below[seq_id].second;
|
||||
};
|
||||
|
||||
// an embd (multimodal) ubatch can repeat one position for a whole image, so positions
|
||||
// do not encode the token order; resolve its predecessors by ubatch order instead
|
||||
std::vector<uint32_t> ord; // index among the ubatch tokens of the same seq
|
||||
std::unordered_map<llama_seq_id, std::vector<uint32_t>> seq_idx;
|
||||
|
||||
if (!ubatch.token) {
|
||||
ord.resize(n_tokens);
|
||||
for (uint32_t i = 0; i < n_tokens; ++i) {
|
||||
auto & v = seq_idx[ubatch.seq_id[i][0]];
|
||||
ord[i] = v.size();
|
||||
v.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < n_tokens; ++i) {
|
||||
// TODO: a token that belongs to more than one sequence has an ambiguous history.
|
||||
// the n-gram architectures have to reject such batches
|
||||
const llama_seq_id seq_id = ubatch.seq_id[i][0];
|
||||
|
||||
for (uint32_t j = 0; j < n; ++j) {
|
||||
const llama_pos p = ubatch.pos[i] - (llama_pos) (n - j);
|
||||
const llama_pos d = (llama_pos) (n - j);
|
||||
|
||||
llama_pos p;
|
||||
if (!ubatch.token) {
|
||||
const auto & v = seq_idx[seq_id];
|
||||
const int64_t k = (int64_t) ord[i] - d;
|
||||
// k >= 0: an earlier token of this very ubatch; k < 0: before the chunk
|
||||
p = k >= 0 ? ubatch.pos[v[k]] : ubatch.pos[v[0]] + (llama_pos) k;
|
||||
} else {
|
||||
p = ubatch.pos[i] - d;
|
||||
}
|
||||
|
||||
if (p < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto it = hist.find(key(seq_id, p));
|
||||
if (it != hist.end()) {
|
||||
res[i*n + j] = it->second;
|
||||
}
|
||||
res[i*n + j] = lookup(seq_id, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2108,6 +2162,15 @@ void llama_kv_cache::state_write(llama_io_write_i & io, llama_seq_id seq_id, lla
|
||||
}
|
||||
|
||||
void llama_kv_cache::state_read(llama_io_read_i & io, llama_seq_id seq_id, llama_state_seq_flags flags) {
|
||||
state_read_sinfo(io, seq_id, flags, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void llama_kv_cache::state_read_sinfo(
|
||||
llama_io_read_i & io,
|
||||
llama_seq_id seq_id,
|
||||
llama_state_seq_flags flags,
|
||||
slot_info_vec_t * sinfos_out,
|
||||
const slot_info_vec_t * sinfos_in) {
|
||||
// TODO: refactor [TAG_KV_CACHE_SHARE_CELLS]
|
||||
if (other) {
|
||||
return;
|
||||
@@ -2118,17 +2181,35 @@ void llama_kv_cache::state_read(llama_io_read_i & io, llama_seq_id seq_id, llama
|
||||
// TODO: fix incosistent handling of `seq_id < 0` and `seq_id == -1` in the codebase [TAG_LLAMA_SEQ_ID_NEG]
|
||||
GGML_ASSERT(seq_id == -1 || (seq_id >= 0 && (size_t) seq_id < seq_to_stream.size()));
|
||||
|
||||
if (sinfos_out) {
|
||||
sinfos_out->assign(n_stream, slot_info{});
|
||||
}
|
||||
|
||||
if (sinfos_in && sinfos_in->size() != n_stream) {
|
||||
throw std::runtime_error("failed to restore kv cache: mirrored slot layout has the wrong stream count");
|
||||
}
|
||||
|
||||
uint32_t n_stream_cur;
|
||||
io.read(&n_stream_cur, sizeof(n_stream_cur));
|
||||
if (n_stream_cur != n_stream) {
|
||||
throw std::runtime_error("n_stream mismatch");
|
||||
}
|
||||
|
||||
// a whole-context restore replaces every stream, so the cache is emptied once here
|
||||
// clear() resets all streams at once, so doing it per stream below would keep only the last one
|
||||
if (seq_id == -1) {
|
||||
clear(true);
|
||||
}
|
||||
|
||||
for (uint32_t s = 0; s < n_stream; ++s) {
|
||||
uint32_t cell_count;
|
||||
io.read(&cell_count, sizeof(cell_count));
|
||||
|
||||
if (cell_count == 0) {
|
||||
// a mirrored cache must be empty here as well, or the two no longer agree cell for cell
|
||||
if (sinfos_in && !(*sinfos_in)[s].empty()) {
|
||||
throw std::runtime_error("failed to restore kv cache: mirrored cache holds cells this one does not");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2137,7 +2218,7 @@ void llama_kv_cache::state_read(llama_io_read_i & io, llama_seq_id seq_id, llama
|
||||
slot_info sinfo;
|
||||
|
||||
bool res = true;
|
||||
res = res && state_read_meta(io, strm, cell_count, sinfo, seq_id);
|
||||
res = res && state_read_meta(io, strm, cell_count, sinfo, seq_id, sinfos_in ? &(*sinfos_in)[s] : nullptr);
|
||||
|
||||
try {
|
||||
res = res && state_read_data(io, strm, cell_count, sinfo);
|
||||
@@ -2153,6 +2234,10 @@ void llama_kv_cache::state_read(llama_io_read_i & io, llama_seq_id seq_id, llama
|
||||
}
|
||||
throw std::runtime_error("failed to restore kv cache");
|
||||
}
|
||||
|
||||
if (sinfos_out) {
|
||||
(*sinfos_out)[s] = sinfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2288,7 +2373,7 @@ void llama_kv_cache::state_write_data(llama_io_write_i & io, const cell_ranges_t
|
||||
}
|
||||
}
|
||||
|
||||
bool llama_kv_cache::state_read_meta(llama_io_read_i & io, uint32_t strm, uint32_t cell_count, slot_info & sinfo, llama_seq_id dest_seq_id) {
|
||||
bool llama_kv_cache::state_read_meta(llama_io_read_i & io, uint32_t strm, uint32_t cell_count, slot_info & sinfo, llama_seq_id dest_seq_id, const slot_info * sinfo_in) {
|
||||
auto & cells = v_cells[strm];
|
||||
auto & head = v_heads[strm];
|
||||
|
||||
@@ -2338,10 +2423,37 @@ bool llama_kv_cache::state_read_meta(llama_io_read_i & io, uint32_t strm, uint32
|
||||
ubatch.seq_id[i] = &dest_seq_id;
|
||||
}
|
||||
|
||||
sinfo = find_slot(ubatch, false);
|
||||
if (sinfo.empty()) {
|
||||
LLAMA_LOG_ERROR("%s: failed to find %d available cells in kv cache\n", __func__, cell_count);
|
||||
return false;
|
||||
if (sinfo_in) {
|
||||
// this cache mirrors another one, so it takes that cache's layout instead of searching for its own cells
|
||||
if (sinfo_in->empty() || sinfo_in->n_stream() != 1 || sinfo_in->idxs[0].size() != cell_count) {
|
||||
LLAMA_LOG_ERROR("%s: mirrored slot layout holds %d cells, this cache restores %d\n", __func__,
|
||||
sinfo_in->empty() ? 0 : (int) sinfo_in->idxs[0].size(), cell_count);
|
||||
return false;
|
||||
}
|
||||
|
||||
sinfo = *sinfo_in;
|
||||
|
||||
// the layout is cell indices, so it means the same in both caches only while their streams line up
|
||||
sinfo.s0 = strm;
|
||||
sinfo.s1 = strm;
|
||||
sinfo.strm[0] = strm;
|
||||
|
||||
// seq_rm above freed exactly the cells this sequence held
|
||||
// anything else in the way is a cache that had already drifted, which this restore must not hide
|
||||
for (uint32_t i = 0; i < cell_count; ++i) {
|
||||
const uint32_t idx = sinfo.idxs[0][i];
|
||||
|
||||
if (idx >= cells.size() || !cells.is_empty(idx)) {
|
||||
LLAMA_LOG_ERROR("%s: cell %u of the mirrored slot layout is not free\n", __func__, idx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sinfo = find_slot(ubatch, false);
|
||||
if (sinfo.empty()) {
|
||||
LLAMA_LOG_ERROR("%s: failed to find %d available cells in kv cache\n", __func__, cell_count);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// note: apply_ubatch() rebuilds llama_kv_cell_ext from the ubatch
|
||||
@@ -2367,7 +2479,12 @@ bool llama_kv_cache::state_read_meta(llama_io_read_i & io, uint32_t strm, uint32
|
||||
return false;
|
||||
}
|
||||
|
||||
clear(true);
|
||||
// the cells go in from 0, so a mirrored cache lands on the same ones as long as it restores the same count. the layout itself carries no more information here
|
||||
if (sinfo_in && (sinfo_in->empty() || sinfo_in->n_stream() != 1 || sinfo_in->idxs[0].size() != cell_count)) {
|
||||
LLAMA_LOG_ERROR("%s: mirrored slot layout holds %d cells, this cache restores %d\n", __func__,
|
||||
sinfo_in->empty() ? 0 : (int) sinfo_in->idxs[0].size(), cell_count);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < cell_count; ++i) {
|
||||
llama_pos pos;
|
||||
|
||||
+20
-3
@@ -112,7 +112,9 @@ public:
|
||||
llama_memory_t mem_other,
|
||||
const layer_filter_cb & filter,
|
||||
const layer_reuse_cb & reuse,
|
||||
const layer_share_cb & share);
|
||||
const layer_share_cb & share,
|
||||
// a model can hold more than one cache, so the tensor names have to stay unique
|
||||
const char * name_tag = "");
|
||||
|
||||
~llama_kv_cache() = default;
|
||||
|
||||
@@ -166,6 +168,17 @@ public:
|
||||
|
||||
const llama_kv_cells & get_cells(llama_seq_id seq_id) const;
|
||||
|
||||
// state_read, plus the cells the restored tokens were placed in
|
||||
// a cache that mirrors another one (the qwen4exp indexer) must not search for its own cells: two searches agree only by luck
|
||||
// sinfos_out: if set, filled with the layout used; a stream with no cells leaves an empty entry
|
||||
// sinfos_in : if set, the layout to use instead of searching. one entry per stream, cell count must match the blob
|
||||
void state_read_sinfo(
|
||||
llama_io_read_i & io,
|
||||
llama_seq_id seq_id,
|
||||
llama_state_seq_flags flags,
|
||||
slot_info_vec_t * sinfos_out,
|
||||
const slot_info_vec_t * sinfos_in);
|
||||
|
||||
//
|
||||
// graph_build API
|
||||
//
|
||||
@@ -223,7 +236,10 @@ public:
|
||||
bool has_cell_ext() const;
|
||||
|
||||
// for every token of the ubatch, the ids of the n tokens that precede it in its sequence
|
||||
// entries with no matching cell are set to LLAMA_TOKEN_NULL
|
||||
// example for M-RoPE image case: tokens A B X X X C, where X is a 3-token image at pos 2 spanning positions 2..4:
|
||||
// tok: A B X X X C
|
||||
// pos: 0 1 2 2 2 5
|
||||
// prev, n=2: A -> [NULL, NULL], B -> [NULL, A], 3rd X -> [X, X], C -> [X, X]
|
||||
// note: used by n-gram input embeddings
|
||||
void get_prev_tokens(const llama_ubatch & ubatch, uint32_t n, std::vector<llama_token> & res) const;
|
||||
|
||||
@@ -326,7 +342,8 @@ private:
|
||||
void state_write_meta(llama_io_write_i & io, const cell_ranges_t & cr, llama_seq_id seq_id = -1) const;
|
||||
void state_write_data(llama_io_write_i & io, const cell_ranges_t & cr) const;
|
||||
|
||||
bool state_read_meta(llama_io_read_i & io, uint32_t strm, uint32_t cell_count, slot_info & sinfo, llama_seq_id dest_seq_id = -1);
|
||||
// sinfo_in, when set, replaces the find_slot call: the cells are given by the caller
|
||||
bool state_read_meta(llama_io_read_i & io, uint32_t strm, uint32_t cell_count, slot_info & sinfo, llama_seq_id dest_seq_id = -1, const slot_info * sinfo_in = nullptr);
|
||||
bool state_read_data(llama_io_read_i & io, uint32_t strm, uint32_t cell_count, const slot_info & sinfo);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
#include "llama-memory-hybrid-idx.h"
|
||||
|
||||
#include "llama-impl.h"
|
||||
#include "llama-batch.h"
|
||||
#include "llama-io.h"
|
||||
#include "llama-model.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
|
||||
//
|
||||
// llama_memory_hybrid_idx
|
||||
//
|
||||
|
||||
llama_memory_hybrid_idx::llama_memory_hybrid_idx(
|
||||
const llama_model & model,
|
||||
/* attn */
|
||||
ggml_type type_k,
|
||||
ggml_type type_v,
|
||||
bool v_trans,
|
||||
uint32_t kv_size,
|
||||
uint32_t n_pad,
|
||||
uint32_t n_swa,
|
||||
llama_swa_type swa_type,
|
||||
/* recurrent */
|
||||
ggml_type type_r,
|
||||
ggml_type type_s,
|
||||
uint32_t rs_size,
|
||||
/* common */
|
||||
uint32_t n_seq_max,
|
||||
uint32_t n_rs_seq,
|
||||
bool offload,
|
||||
bool unified,
|
||||
/* layer filters */
|
||||
const layer_filter_cb & filter_attn,
|
||||
const layer_filter_cb & filter_recr,
|
||||
const layer_filter_cb & filter_idx) :
|
||||
llama_memory_hybrid(
|
||||
model,
|
||||
type_k, type_v, v_trans, kv_size, n_pad, n_swa, swa_type,
|
||||
type_r, type_s, rs_size,
|
||||
n_seq_max, n_rs_seq, offload, unified,
|
||||
filter_attn, filter_recr),
|
||||
hparams_idx(model.hparams),
|
||||
mem_idx(filter_idx == nullptr ? nullptr : [&] {
|
||||
// MQA with a single key head of indexer_head_size, as llama_kv_cache_dsa shapes its own
|
||||
std::fill(hparams_idx.n_head_kv_arr.begin(), hparams_idx.n_head_kv_arr.end(), 1);
|
||||
hparams_idx.n_embd_head_k_full = model.hparams.indexer_head_size;
|
||||
|
||||
LLAMA_LOG_INFO("%s: creating indexer KV cache, size = %u cells\n", __func__, kv_size);
|
||||
|
||||
return new llama_kv_cache(
|
||||
model, hparams_idx, type_k, type_v, v_trans, offload, unified,
|
||||
kv_size, n_seq_max, n_pad, n_swa, swa_type,
|
||||
nullptr, filter_idx, nullptr, nullptr, "idx_");
|
||||
}()) {}
|
||||
|
||||
llama_memory_context_ptr llama_memory_hybrid_idx::init_batch(llama_batch_allocr & balloc, uint32_t n_ubatch, bool embd_all) {
|
||||
// note: repeats llama_memory_hybrid::init_batch, as the indexer needs the attention slot infos that the base context hides
|
||||
do {
|
||||
balloc.split_reset();
|
||||
|
||||
// follow the recurrent pattern for creating the ubatch splits
|
||||
std::vector<llama_ubatch> ubatches;
|
||||
|
||||
while (true) {
|
||||
llama_ubatch ubatch;
|
||||
|
||||
if (embd_all) {
|
||||
// if all tokens are output, split by sequence
|
||||
ubatch = balloc.split_seq(n_ubatch);
|
||||
} else {
|
||||
// Use non-sequential split when KV cache is unified (needed for hellaswag/winogrande/multiple-choice)
|
||||
const bool unified = (get_mem_attn()->get_n_stream() == 1);
|
||||
|
||||
// [TAG_RECURRENT_ROLLBACK_SPLITS]
|
||||
// the trailing (1 + n_rs_seq) tokens of each seq must stay in the same ubatch
|
||||
// so that the rollback snapshots remain valid
|
||||
const uint32_t n_rs_seq = get_mem_recr()->n_rs_seq;
|
||||
|
||||
ubatch = balloc.split_equal(n_ubatch, !unified, n_rs_seq > 0 ? n_rs_seq + 1 : 0);
|
||||
}
|
||||
|
||||
if (ubatch.n_tokens == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
ubatches.push_back(std::move(ubatch)); // NOLINT
|
||||
}
|
||||
|
||||
if (balloc.get_n_used() < balloc.get_n_tokens()) {
|
||||
// failed to find a suitable split
|
||||
break;
|
||||
}
|
||||
|
||||
// prepare the recurrent batches first
|
||||
if (!get_mem_recr()->prepare(ubatches)) {
|
||||
// TODO: will the recurrent cache be in an undefined context at this point?
|
||||
LLAMA_LOG_ERROR("%s: failed to prepare recurrent ubatches\n", __func__);
|
||||
return std::make_unique<llama_memory_hybrid_idx_context>(LLAMA_MEMORY_STATUS_FAILED_PREPARE);
|
||||
}
|
||||
|
||||
// prepare the attention cache
|
||||
auto heads_attn = get_mem_attn()->prepare(ubatches);
|
||||
if (heads_attn.empty()) {
|
||||
LLAMA_LOG_ERROR("%s: failed to prepare attention ubatches\n", __func__);
|
||||
return std::make_unique<llama_memory_hybrid_idx_context>(LLAMA_MEMORY_STATUS_FAILED_PREPARE);
|
||||
}
|
||||
|
||||
// the indexer uses the attention cache's slot layout; a separate one can drift from it
|
||||
llama_kv_cache::slot_info_vec_t heads_idx;
|
||||
if (mem_idx) {
|
||||
heads_idx = heads_attn;
|
||||
}
|
||||
|
||||
return std::make_unique<llama_memory_hybrid_idx_context>(
|
||||
this, std::move(heads_attn), std::move(heads_idx), std::move(ubatches));
|
||||
} while(false);
|
||||
|
||||
return std::make_unique<llama_memory_hybrid_idx_context>(LLAMA_MEMORY_STATUS_FAILED_PREPARE);
|
||||
}
|
||||
|
||||
llama_memory_context_ptr llama_memory_hybrid_idx::init_full() {
|
||||
return std::make_unique<llama_memory_hybrid_idx_context>(this);
|
||||
}
|
||||
|
||||
llama_memory_context_ptr llama_memory_hybrid_idx::init_update(llama_context * lctx, bool optimize) {
|
||||
return std::make_unique<llama_memory_hybrid_idx_context>(this, lctx, optimize);
|
||||
}
|
||||
|
||||
void llama_memory_hybrid_idx::clear(bool data) {
|
||||
llama_memory_hybrid::clear(data);
|
||||
|
||||
if (mem_idx) {
|
||||
mem_idx->clear(data);
|
||||
}
|
||||
}
|
||||
|
||||
bool llama_memory_hybrid_idx::seq_rm(llama_seq_id seq_id, llama_pos p0, llama_pos p1) {
|
||||
// same order as llama_memory_hybrid::seq_rm: the recurrent cache can refuse, so try it first
|
||||
if (!get_mem_recr()->seq_rm(seq_id, p0, p1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mem_idx) {
|
||||
mem_idx->seq_rm(seq_id, p0, p1);
|
||||
}
|
||||
|
||||
return get_mem_attn()->seq_rm(seq_id, p0, p1);
|
||||
}
|
||||
|
||||
void llama_memory_hybrid_idx::seq_cp(llama_seq_id seq_id_src, llama_seq_id seq_id_dst, llama_pos p0, llama_pos p1) {
|
||||
llama_memory_hybrid::seq_cp(seq_id_src, seq_id_dst, p0, p1);
|
||||
|
||||
if (mem_idx) {
|
||||
mem_idx->seq_cp(seq_id_src, seq_id_dst, p0, p1);
|
||||
}
|
||||
}
|
||||
|
||||
void llama_memory_hybrid_idx::seq_keep(llama_seq_id seq_id) {
|
||||
llama_memory_hybrid::seq_keep(seq_id);
|
||||
|
||||
if (mem_idx) {
|
||||
mem_idx->seq_keep(seq_id);
|
||||
}
|
||||
}
|
||||
|
||||
void llama_memory_hybrid_idx::seq_add(llama_seq_id seq_id, llama_pos p0, llama_pos p1, llama_pos shift) {
|
||||
llama_memory_hybrid::seq_add(seq_id, p0, p1, shift);
|
||||
|
||||
if (mem_idx) {
|
||||
mem_idx->seq_add(seq_id, p0, p1, shift);
|
||||
}
|
||||
}
|
||||
|
||||
void llama_memory_hybrid_idx::seq_div(llama_seq_id seq_id, llama_pos p0, llama_pos p1, int d) {
|
||||
llama_memory_hybrid::seq_div(seq_id, p0, p1, d);
|
||||
|
||||
if (mem_idx) {
|
||||
mem_idx->seq_div(seq_id, p0, p1, d);
|
||||
}
|
||||
}
|
||||
|
||||
std::map<ggml_backend_buffer_type_t, size_t> llama_memory_hybrid_idx::memory_breakdown() const {
|
||||
std::map<ggml_backend_buffer_type_t, size_t> mb = llama_memory_hybrid::memory_breakdown();
|
||||
|
||||
if (mem_idx) {
|
||||
for (const auto & buft_size : mem_idx->memory_breakdown()) {
|
||||
mb[buft_size.first] += buft_size.second;
|
||||
}
|
||||
}
|
||||
|
||||
return mb;
|
||||
}
|
||||
|
||||
void llama_memory_hybrid_idx::state_write(llama_io_write_i & io, llama_seq_id seq_id, llama_state_seq_flags flags) const {
|
||||
llama_memory_hybrid::state_write(io, seq_id, flags);
|
||||
|
||||
// [TAG_HYBRID_IDX_STATE] the indexer section goes last, so it is a pure suffix: an old reader stops early instead of misparsing it
|
||||
// The indexer mirrors the attention cache, so it uses the same PARTIAL_ONLY gate.
|
||||
if ((flags & LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY) == 0) {
|
||||
if (mem_idx) {
|
||||
mem_idx->state_write(io, seq_id, flags);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void llama_memory_hybrid_idx::state_read(llama_io_read_i & io, llama_seq_id seq_id, llama_state_seq_flags flags) {
|
||||
// note: repeats llama_memory_hybrid::state_read
|
||||
// the indexer needs the attention cache's cells, and a half-failed restore must leave all three caches alike
|
||||
|
||||
// [TAG_HYBRID_IDX_SINFO]
|
||||
// the indexer restore adopts the attention cache's layout instead of searching for cells of its own
|
||||
// two find_slot calls agree only while both caches see the same occupancy, which a restore cannot promise
|
||||
llama_kv_cache::slot_info_vec_t sinfos_attn;
|
||||
|
||||
try {
|
||||
if ((flags & LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY) == 0) {
|
||||
get_mem_attn()->state_read_sinfo(io, seq_id, flags, mem_idx ? &sinfos_attn : nullptr, nullptr);
|
||||
}
|
||||
|
||||
get_mem_recr()->state_read(io, seq_id, flags);
|
||||
|
||||
// [TAG_HYBRID_IDX_STATE] must mirror the write order in state_write
|
||||
if ((flags & LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY) == 0) {
|
||||
if (mem_idx) {
|
||||
mem_idx->state_read_sinfo(io, seq_id, flags, nullptr, &sinfos_attn);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
// a half-restored context is the one state the indexer cannot fix by itself: attention holds new cells, the indexer old ones
|
||||
// drop what was being restored from all of them, which is a state they do agree on.
|
||||
state_drop(seq_id);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void llama_memory_hybrid_idx::state_drop(llama_seq_id seq_id) {
|
||||
// dropped directly, not via seq_rm: the recurrent cache may refuse it and then only the other two get cleared
|
||||
if (seq_id < 0) {
|
||||
clear(true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
get_mem_attn()->seq_rm(seq_id, -1, -1);
|
||||
get_mem_recr()->seq_rm(seq_id, -1, -1);
|
||||
|
||||
if (mem_idx) {
|
||||
mem_idx->seq_rm(seq_id, -1, -1);
|
||||
}
|
||||
}
|
||||
|
||||
llama_kv_cache * llama_memory_hybrid_idx::get_mem_idx() const {
|
||||
return mem_idx.get();
|
||||
}
|
||||
|
||||
//
|
||||
// llama_memory_hybrid_idx_context
|
||||
//
|
||||
|
||||
// streams in each ubatch's slot info, matching get_k/get_v's `ns`
|
||||
static std::vector<uint32_t> llama_memory_hybrid_idx_ns(const llama_kv_cache::slot_info_vec_t & sinfos) {
|
||||
std::vector<uint32_t> res;
|
||||
res.reserve(sinfos.size());
|
||||
|
||||
for (const auto & sinfo : sinfos) {
|
||||
res.push_back(sinfo.s1 - sinfo.s0 + 1);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
llama_memory_hybrid_idx_context::llama_memory_hybrid_idx_context(llama_memory_status status) :
|
||||
llama_memory_hybrid_context(status) {}
|
||||
|
||||
llama_memory_hybrid_idx_context::llama_memory_hybrid_idx_context(llama_memory_hybrid_idx * mem) :
|
||||
llama_memory_hybrid_context(mem),
|
||||
mem(mem),
|
||||
// graph reservation walks a full context, and qwen4exp builds the sparse attention only when this is set
|
||||
// without it the reserved worst case is the dense graph, so ggml-alloc must grow the buffer on the first decode
|
||||
ns_ubatch(mem->get_mem_idx() == nullptr ?
|
||||
std::vector<uint32_t>() : std::vector<uint32_t>{ mem->get_mem_idx()->get_n_stream() }),
|
||||
ctx_idx(mem->get_mem_idx() == nullptr ? nullptr :
|
||||
new llama_kv_cache_context(mem->get_mem_idx())) {}
|
||||
|
||||
llama_memory_hybrid_idx_context::llama_memory_hybrid_idx_context(
|
||||
llama_memory_hybrid_idx * mem,
|
||||
llama_context * lctx,
|
||||
bool optimize) :
|
||||
llama_memory_hybrid_context(mem, lctx, optimize),
|
||||
mem(mem) {}
|
||||
|
||||
llama_memory_hybrid_idx_context::llama_memory_hybrid_idx_context(
|
||||
llama_memory_hybrid_idx * mem,
|
||||
slot_info_vec_t sinfos_attn,
|
||||
slot_info_vec_t sinfos_idx,
|
||||
std::vector<llama_ubatch> ubatches) :
|
||||
// note: the base copies the ubatches; ctx_idx gets a copy of its own
|
||||
llama_memory_hybrid_context(mem, std::move(sinfos_attn), ubatches),
|
||||
mem(mem),
|
||||
ns_ubatch(llama_memory_hybrid_idx_ns(sinfos_idx)),
|
||||
ctx_idx(mem->get_mem_idx() == nullptr ? nullptr :
|
||||
new llama_kv_cache_context(mem->get_mem_idx(), std::move(sinfos_idx), ubatches)) {}
|
||||
|
||||
bool llama_memory_hybrid_idx_context::next() {
|
||||
if (ctx_idx) {
|
||||
ctx_idx->next();
|
||||
}
|
||||
|
||||
++i_cur;
|
||||
|
||||
return llama_memory_hybrid_context::next();
|
||||
}
|
||||
|
||||
bool llama_memory_hybrid_idx_context::apply() {
|
||||
bool res = llama_memory_hybrid_context::apply();
|
||||
|
||||
if (ctx_idx) {
|
||||
res = res & ctx_idx->apply();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
const llama_kv_cache_context * llama_memory_hybrid_idx_context::get_idx() const {
|
||||
return static_cast<const llama_kv_cache_context *>(ctx_idx.get());
|
||||
}
|
||||
|
||||
uint32_t llama_memory_hybrid_idx_context::get_n_stream() const {
|
||||
GGML_ASSERT(i_cur < ns_ubatch.size());
|
||||
|
||||
return ns_ubatch[i_cur];
|
||||
}
|
||||
|
||||
void llama_memory_hybrid_idx_context::set_input_qsa(
|
||||
ggml_tensor * cell_blk,
|
||||
ggml_tensor * blk_cells,
|
||||
ggml_tensor * blk_pos,
|
||||
ggml_tensor * bias,
|
||||
const llama_ubatch * ubatch,
|
||||
uint32_t ratio,
|
||||
bool blk_bias) const {
|
||||
GGML_ASSERT(ratio > 0);
|
||||
GGML_ASSERT(mem != nullptr && mem->get_mem_idx() != nullptr);
|
||||
|
||||
GGML_ASSERT(ggml_backend_buffer_is_host(cell_blk->buffer));
|
||||
|
||||
const int64_t n_kv = cell_blk->ne[0];
|
||||
const int64_t n_ns = cell_blk->ne[1]; // streams in this ubatch
|
||||
const int64_t n_blocks = blk_pos->ne[0]/(4*n_ns);
|
||||
const int64_t n_tokens = ubatch->n_tokens;
|
||||
const int64_t r = ratio;
|
||||
|
||||
GGML_ASSERT(n_tokens % n_ns == 0);
|
||||
const int64_t n_tps = n_tokens/n_ns; // tokens per stream
|
||||
|
||||
int32_t * dst_cell_blk = (int32_t *) cell_blk->data;
|
||||
int32_t * dst_blk_cells = (int32_t *) blk_cells->data;
|
||||
int32_t * dst_blk_pos = (int32_t *) blk_pos->data;
|
||||
float * dst_bias = (float *) bias->data;
|
||||
|
||||
// block b covers [b*ratio, (b+1)*ratio), so its first token is at b*ratio
|
||||
// all mrope sections carry it: exact for text, approximate for images
|
||||
for (int64_t sec = 0; sec < 4; ++sec) {
|
||||
for (int64_t s = 0; s < n_ns; ++s) {
|
||||
for (int64_t b = 0; b < n_blocks; ++b) {
|
||||
dst_blk_pos[sec*(n_blocks*n_ns) + s*n_blocks + b] = (int32_t) (b*r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// one pass per stream: cell j is a different token in each, so no mapping is shared
|
||||
std::vector<int32_t> blk_of(n_kv);
|
||||
std::vector<int32_t> filled(n_blocks);
|
||||
|
||||
for (int64_t s = 0; s < n_ns; ++s) {
|
||||
// ubatch index s*n_tps belongs to this stream; ask which cells array it uses
|
||||
const llama_seq_id seq_of_stream = ubatch->seq_id[s*n_tps][0];
|
||||
const auto & cells = mem->get_mem_idx()->get_cells(seq_of_stream);
|
||||
|
||||
int32_t * cur_cell_blk = dst_cell_blk + s*n_kv;
|
||||
int32_t * cur_blk_cells = dst_blk_cells + s*(r*n_blocks);
|
||||
|
||||
// an incomplete block cannot be pooled; the bias below forces those tail cells in
|
||||
// -1 means no usable block, and block 0 only keeps the gather in range
|
||||
std::fill(blk_of.begin(), blk_of.end(), -1);
|
||||
std::fill(filled.begin(), filled.end(), 0);
|
||||
std::fill(cur_blk_cells, cur_blk_cells + r*n_blocks, 0);
|
||||
|
||||
// a cell no block covers needs its own -inf, which a per-block bias cannot carry
|
||||
// every cache path keeps the position below the cell window, so this stays false
|
||||
bool oor = false;
|
||||
|
||||
for (int64_t j = 0; j < n_kv; ++j) {
|
||||
if (cells.is_empty(j)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const llama_pos p = cells.pos_get(j);
|
||||
const int64_t b = p/r;
|
||||
|
||||
if (b >= n_blocks) {
|
||||
oor = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
blk_of[j] = (int32_t) b;
|
||||
cur_blk_cells[b*r + (p%r)] = (int32_t) j;
|
||||
filled[b]++;
|
||||
}
|
||||
|
||||
GGML_ASSERT((!blk_bias || !oor) && "qsa: cell position runs past the cell window");
|
||||
|
||||
// per-block mode keeps an unpooled cell's real block, so the block's own -inf reaches it
|
||||
// per-cell mode carries that -inf itself and only needs the gather in range
|
||||
for (int64_t j = 0; j < n_kv; ++j) {
|
||||
if (blk_of[j] >= 0 && filled[blk_of[j]] < r && !blk_bias) {
|
||||
blk_of[j] = -1;
|
||||
}
|
||||
cur_cell_blk[j] = blk_of[j] < 0 ? 0 : blk_of[j];
|
||||
}
|
||||
|
||||
for (int64_t ii = 0; ii < n_tps; ++ii) {
|
||||
const int64_t i = s*n_tps + ii;
|
||||
const llama_seq_id seq_id = ubatch->seq_id[i][0];
|
||||
const llama_pos q = ubatch->pos[i];
|
||||
|
||||
// the tail is an incomplete block and is always visible, as in the reference
|
||||
const llama_pos tail_start = (q + 1)/r*r;
|
||||
|
||||
if (blk_bias) {
|
||||
// a block sits wholly inside or outside the tail, so one value covers it
|
||||
// the caller adds the attention mask, which drops empty, foreign and future cells
|
||||
float * cur_blk_bias = dst_bias + i*n_blocks;
|
||||
|
||||
for (int64_t b = 0; b < n_blocks; ++b) {
|
||||
// finite, so it can never meet a -inf and produce a nan
|
||||
cur_blk_bias[b] = b*r >= tail_start ? 1e9f : (filled[b] < r ? -INFINITY : 0.0f);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
float * cur_bias = dst_bias + i*n_kv;
|
||||
|
||||
for (int64_t j = 0; j < n_kv; ++j) {
|
||||
float v = -INFINITY;
|
||||
|
||||
if (!cells.is_empty(j) && cells.seq_has(j, seq_id) && cells.pos_get(j) <= q) {
|
||||
// finite, so it can never meet a -inf and produce a nan
|
||||
v = cells.pos_get(j) >= tail_start ? 1e9f : (blk_of[j] < 0 ? -INFINITY : 0.0f);
|
||||
}
|
||||
|
||||
cur_bias[j] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
|
||||
#include "llama-memory-hybrid.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
//
|
||||
// llama_memory_hybrid_idx
|
||||
//
|
||||
|
||||
// llama_memory_hybrid plus a third cache with one indexer key per token, for block-sparse attention (qwen4exp QSA)
|
||||
// the indexer is a side buffer over the attention cells: same size, padding, streams and slots, so cell j is one token in both
|
||||
|
||||
class llama_memory_hybrid_idx : public llama_memory_hybrid {
|
||||
public:
|
||||
llama_memory_hybrid_idx(
|
||||
const llama_model & model,
|
||||
/* attn */
|
||||
ggml_type type_k,
|
||||
ggml_type type_v,
|
||||
bool v_trans,
|
||||
uint32_t kv_size,
|
||||
uint32_t n_pad,
|
||||
uint32_t n_swa,
|
||||
llama_swa_type swa_type,
|
||||
/* recurrent */
|
||||
ggml_type type_r,
|
||||
ggml_type type_s,
|
||||
uint32_t rs_size,
|
||||
/* common */
|
||||
uint32_t n_seq_max,
|
||||
uint32_t n_rs_seq,
|
||||
bool offload,
|
||||
bool unified,
|
||||
/* layer filters */
|
||||
const layer_filter_cb & filter_attn,
|
||||
const layer_filter_cb & filter_recr,
|
||||
/* the indexer cache exists only if this is given */
|
||||
const layer_filter_cb & filter_idx);
|
||||
|
||||
~llama_memory_hybrid_idx() = default;
|
||||
|
||||
//
|
||||
// llama_memory_i
|
||||
//
|
||||
|
||||
llama_memory_context_ptr init_batch(
|
||||
llama_batch_allocr & balloc,
|
||||
uint32_t n_ubatch,
|
||||
bool embd_all) override;
|
||||
|
||||
llama_memory_context_ptr init_full() override;
|
||||
|
||||
llama_memory_context_ptr init_update(llama_context * lctx, bool optimize) override;
|
||||
|
||||
void clear(bool data) override;
|
||||
|
||||
bool seq_rm (llama_seq_id seq_id, llama_pos p0, llama_pos p1) override;
|
||||
void seq_cp (llama_seq_id seq_id_src, llama_seq_id seq_id_dst, llama_pos p0, llama_pos p1) override;
|
||||
void seq_keep(llama_seq_id seq_id) override;
|
||||
void seq_add (llama_seq_id seq_id, llama_pos p0, llama_pos p1, llama_pos shift) override;
|
||||
void seq_div (llama_seq_id seq_id, llama_pos p0, llama_pos p1, int d) override;
|
||||
|
||||
std::map<ggml_backend_buffer_type_t, size_t> memory_breakdown() const override;
|
||||
|
||||
// state write/load
|
||||
|
||||
void state_write(llama_io_write_i & io, llama_seq_id seq_id = -1, llama_state_seq_flags flags = 0) const override;
|
||||
void state_read (llama_io_read_i & io, llama_seq_id seq_id = -1, llama_state_seq_flags flags = 0) override;
|
||||
|
||||
//
|
||||
// llama_memory_hybrid_idx specific API
|
||||
//
|
||||
|
||||
llama_kv_cache * get_mem_idx() const; // nullptr when the model carries no indexer
|
||||
|
||||
private:
|
||||
// forget seq_id (all of it if seq_id < 0) in every cache at once, so a failed restore cannot leave the caches out of step
|
||||
// seq_id < 0 drops the whole context, as the caches themselves do on a failed restore
|
||||
void state_drop(llama_seq_id seq_id);
|
||||
|
||||
// the indexer cache holds one key head per layer, so it needs its own hparams:
|
||||
// llama_kv_cache keeps a reference to what it is given
|
||||
llama_hparams hparams_idx;
|
||||
|
||||
const std::unique_ptr<llama_kv_cache> mem_idx;
|
||||
};
|
||||
|
||||
class llama_memory_hybrid_idx_context : public llama_memory_hybrid_context {
|
||||
public:
|
||||
using slot_info_vec_t = llama_kv_cache::slot_info_vec_t;
|
||||
|
||||
// used for errors
|
||||
explicit llama_memory_hybrid_idx_context(llama_memory_status status);
|
||||
|
||||
// used to create a full-cache context
|
||||
explicit llama_memory_hybrid_idx_context(llama_memory_hybrid_idx * mem);
|
||||
|
||||
// used to create an update context
|
||||
llama_memory_hybrid_idx_context(
|
||||
llama_memory_hybrid_idx * mem,
|
||||
llama_context * lctx,
|
||||
bool optimize);
|
||||
|
||||
// used to create a batch processing context from a batch
|
||||
llama_memory_hybrid_idx_context(
|
||||
llama_memory_hybrid_idx * mem,
|
||||
slot_info_vec_t sinfos_attn,
|
||||
slot_info_vec_t sinfos_idx,
|
||||
std::vector<llama_ubatch> ubatches);
|
||||
|
||||
~llama_memory_hybrid_idx_context() = default;
|
||||
|
||||
//
|
||||
// llama_memory_context_i
|
||||
//
|
||||
|
||||
bool next() override;
|
||||
bool apply() override;
|
||||
|
||||
//
|
||||
// llama_memory_hybrid_idx_context specific API
|
||||
//
|
||||
|
||||
// nullptr with no indexer, and for the update context, which builds no sparse graph
|
||||
const llama_kv_cache_context * get_idx() const;
|
||||
|
||||
// streams in the current slot info, the `ns` of get_k/get_v; 1 if unified
|
||||
uint32_t get_n_stream() const;
|
||||
|
||||
// block-compressed sparse attention (qwen4exp QSA) over the cells of the indexer cache.
|
||||
// Blocks cut the position line, not the cell array, so no caller assumes a contiguous layout:
|
||||
// cell_blk I32 [n_kv, ns] block each cell belongs to
|
||||
// blk_cells I32 [ratio*n_blocks, ns] cells making up each block
|
||||
// blk_pos I32 [4*n_blocks*ns] mrope position rows of each block's first token
|
||||
// bias F32 [n_kv, n_tokens/ns, ns] -inf where invisible, large where always visible
|
||||
// blk_bias asks for the bias per block instead: [n_blocks, n_tokens/ns, ns]
|
||||
// the caller then adds the attention mask, the only part of the bias that varies within a block
|
||||
void set_input_qsa(ggml_tensor * cell_blk, ggml_tensor * blk_cells, ggml_tensor * blk_pos,
|
||||
ggml_tensor * bias, const llama_ubatch * ubatch, uint32_t ratio,
|
||||
bool blk_bias) const;
|
||||
|
||||
private:
|
||||
const llama_memory_hybrid_idx * mem = nullptr;
|
||||
|
||||
// streams per ubatch, read from the slot infos before ctx_idx takes them
|
||||
// declared first, so it is initialised while sinfos_idx is still intact
|
||||
const std::vector<uint32_t> ns_ubatch;
|
||||
|
||||
// null unless the model has an indexer and this is a batch or full context
|
||||
const llama_memory_context_ptr ctx_idx;
|
||||
|
||||
// mirrors the base class's ubatch cursor, which is private there
|
||||
size_t i_cur = 0;
|
||||
};
|
||||
@@ -51,7 +51,8 @@ llama_memory_recurrent::llama_memory_recurrent(
|
||||
auto it = ctx_map.find(buft);
|
||||
if (it == ctx_map.end()) {
|
||||
ggml_init_params params = {
|
||||
/*.mem_size =*/ size_t(2u*n_layer*ggml_tensor_overhead()),
|
||||
// r and s per layer, plus the separate PLE conv row where the model has one
|
||||
/*.mem_size =*/ size_t((hparams.ple_conv_state() > 0 ? 3u : 2u)*n_layer*ggml_tensor_overhead()),
|
||||
/*.mem_buffer =*/ NULL,
|
||||
/*.no_alloc =*/ true,
|
||||
};
|
||||
@@ -71,6 +72,7 @@ llama_memory_recurrent::llama_memory_recurrent(
|
||||
|
||||
r_l.resize(n_layer);
|
||||
s_l.resize(n_layer);
|
||||
p_l.resize(n_layer);
|
||||
|
||||
for (int i = 0; i < n_layer; i++) {
|
||||
if (filter && !filter(i)) {
|
||||
@@ -103,6 +105,13 @@ llama_memory_recurrent::llama_memory_recurrent(
|
||||
ggml_format_name(s, "cache_s_l%d", i);
|
||||
r_l[i] = r;
|
||||
s_l[i] = s;
|
||||
|
||||
// the PLE history needs its own row: Meta must mirror it while the delta-net conv state next door stays split
|
||||
if (hparams.ple_conv_state() > 0 && hparams.is_ple(i)) {
|
||||
ggml_tensor * p = ggml_new_tensor_2d(ctx, type_r, hparams.ple_conv_state(), n_rows);
|
||||
ggml_format_name(p, "cache_ple_r_l%d", i);
|
||||
p_l[i] = p;
|
||||
}
|
||||
}
|
||||
|
||||
// allocate tensors and initialize the buffers to avoid NaNs in the padding
|
||||
@@ -119,11 +128,13 @@ llama_memory_recurrent::llama_memory_recurrent(
|
||||
{
|
||||
const size_t memory_size_r = size_r_bytes();
|
||||
const size_t memory_size_s = size_s_bytes();
|
||||
const size_t memory_size_p = size_p_bytes();
|
||||
|
||||
LLAMA_LOG_INFO("%s: size = %7.2f MiB (%6u cells, %3d layers, %2u seqs %2u rs_seq), R (%s): %7.2f MiB, S (%s): %7.2f MiB\n", __func__,
|
||||
(float)(memory_size_r + memory_size_s) / (1024.0f * 1024.0f), mem_size, n_layer, n_seq_max, n_rs_seq,
|
||||
LLAMA_LOG_INFO("%s: size = %7.2f MiB (%6u cells, %3d layers, %2u seqs %2u rs_seq), R (%s): %7.2f MiB, S (%s): %7.2f MiB, P (%s): %7.2f MiB\n", __func__,
|
||||
(float)(memory_size_r + memory_size_s + memory_size_p) / (1024.0f * 1024.0f), mem_size, n_layer, n_seq_max, n_rs_seq,
|
||||
ggml_type_name(type_r), (float)memory_size_r / (1024.0f * 1024.0f),
|
||||
ggml_type_name(type_s), (float)memory_size_s / (1024.0f * 1024.0f));
|
||||
ggml_type_name(type_s), (float)memory_size_s / (1024.0f * 1024.0f),
|
||||
ggml_type_name(type_r), (float)memory_size_p / (1024.0f * 1024.0f));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,6 +751,18 @@ size_t llama_memory_recurrent::size_s_bytes() const {
|
||||
return size_s_bytes;
|
||||
}
|
||||
|
||||
size_t llama_memory_recurrent::size_p_bytes() const {
|
||||
size_t size_p_bytes = 0;
|
||||
|
||||
for (const auto & p : p_l) {
|
||||
if (p != nullptr) {
|
||||
size_p_bytes += ggml_nbytes(p);
|
||||
}
|
||||
}
|
||||
|
||||
return size_p_bytes;
|
||||
}
|
||||
|
||||
void llama_memory_recurrent::state_write(llama_io_write_i & io, llama_seq_id seq_id, llama_state_seq_flags flags) const {
|
||||
GGML_UNUSED(flags);
|
||||
|
||||
@@ -899,6 +922,17 @@ void llama_memory_recurrent::state_write_data(llama_io_write_i & io, const std::
|
||||
const size_t buf_size = range_size * r_size_row;
|
||||
io.write_tensor(r_l[il], range.first * r_size_row, buf_size);
|
||||
}
|
||||
|
||||
// the PLE conv history is a second recurrent row, so it has to travel with the first
|
||||
if (p_l[il] != nullptr) {
|
||||
const uint64_t p_size_row = ggml_row_size(p_l[il]->type, hparams.ple_conv_state());
|
||||
io.write(&p_size_row, sizeof(p_size_row));
|
||||
|
||||
for (const auto & range : cell_ranges) {
|
||||
const size_t range_size = range.second - range.first;
|
||||
io.write_tensor(p_l[il], range.first * p_size_row, range_size * p_size_row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!s_trans) {
|
||||
@@ -1097,6 +1131,20 @@ bool llama_memory_recurrent::state_read_data(llama_io_read_i & io, uint32_t cell
|
||||
// Read and set the keys for the whole cell range
|
||||
io.read_tensor(r_l[il], head * r_size_row, cell_count * r_size_row);
|
||||
}
|
||||
|
||||
if (p_l[il] != nullptr) {
|
||||
uint64_t p_size_row_ref;
|
||||
io.read(&p_size_row_ref, sizeof(p_size_row_ref));
|
||||
const size_t p_size_row = ggml_row_size(p_l[il]->type, hparams.ple_conv_state());
|
||||
if (p_size_row != p_size_row_ref) {
|
||||
LLAMA_LOG_ERROR("%s: mismatched ple row size (%zu != %zu, layer %d)\n", __func__, p_size_row, (size_t) p_size_row_ref, il);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cell_count) {
|
||||
io.read_tensor(p_l[il], head * p_size_row, cell_count * p_size_row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!s_trans) {
|
||||
@@ -1251,6 +1299,10 @@ ggml_tensor * llama_memory_recurrent_context::get_s_l(int32_t il) const {
|
||||
return mem->s_l[il];
|
||||
}
|
||||
|
||||
ggml_tensor * llama_memory_recurrent_context::get_p_l(int32_t il) const {
|
||||
return mem->p_l[il];
|
||||
}
|
||||
|
||||
int32_t llama_memory_recurrent_context::s_copy(int i) const {
|
||||
const uint32_t cell_idx = i + mem->head;
|
||||
const int32_t src0 = mem->cells[cell_idx].src0;
|
||||
|
||||
@@ -111,6 +111,8 @@ public:
|
||||
// per layer
|
||||
std::vector<ggml_tensor *> r_l;
|
||||
std::vector<ggml_tensor *> s_l;
|
||||
// a second conv history that must stay replicated across devices, so it cannot share the r row
|
||||
std::vector<ggml_tensor *> p_l;
|
||||
|
||||
private:
|
||||
//const llama_model & model;
|
||||
@@ -125,6 +127,7 @@ private:
|
||||
|
||||
size_t size_r_bytes() const;
|
||||
size_t size_s_bytes() const;
|
||||
size_t size_p_bytes() const;
|
||||
|
||||
void state_write_meta(llama_io_write_i & io, const std::vector<std::pair<uint32_t, uint32_t>> & cell_ranges, llama_seq_id seq_id = -1) const;
|
||||
void state_write_data(llama_io_write_i & io, const std::vector<std::pair<uint32_t, uint32_t>> & cell_ranges) const;
|
||||
@@ -170,6 +173,7 @@ public:
|
||||
|
||||
ggml_tensor * get_r_l(int32_t il) const;
|
||||
ggml_tensor * get_s_l(int32_t il) const;
|
||||
ggml_tensor * get_p_l(int32_t il) const;
|
||||
|
||||
int32_t s_copy(int i) const;
|
||||
|
||||
|
||||
+59
-13
@@ -438,11 +438,34 @@ void llama_file::write_u32(uint32_t val) const { pimpl->write_u32(val); }
|
||||
|
||||
// llama_mmap
|
||||
|
||||
#if defined(_POSIX_MAPPED_FILES) || defined(_WIN32)
|
||||
// merge `ranges` and return their complement within [0, limit)
|
||||
static llama_mmap::ranges ranges_complement(llama_mmap::ranges ranges, size_t limit) {
|
||||
llama_mmap::ranges res;
|
||||
std::sort(ranges.begin(), ranges.end());
|
||||
|
||||
size_t pos = 0;
|
||||
for (const auto & range : ranges) {
|
||||
const size_t beg = std::min(range.first, limit);
|
||||
const size_t end = std::min(range.second, limit);
|
||||
if (beg > pos) {
|
||||
res.emplace_back(pos, beg);
|
||||
}
|
||||
pos = std::max(pos, end);
|
||||
}
|
||||
if (pos < limit) {
|
||||
res.emplace_back(pos, limit);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct llama_mmap::impl {
|
||||
#ifdef _POSIX_MAPPED_FILES
|
||||
std::vector<std::pair<size_t, size_t>> mapped_fragments;
|
||||
|
||||
impl(struct llama_file * file, size_t prefetch, bool numa) {
|
||||
impl(struct llama_file * file, size_t prefetch, bool numa, const llama_mmap::ranges & lazy_ranges) {
|
||||
size = file->size();
|
||||
int fd = file->file_id();
|
||||
int flags = MAP_SHARED;
|
||||
@@ -452,18 +475,34 @@ struct llama_mmap::impl {
|
||||
LLAMA_LOG_WARN("warning: posix_fadvise(.., POSIX_FADV_SEQUENTIAL) failed: %s\n",
|
||||
strerror(errno));
|
||||
}
|
||||
if (prefetch) { flags |= MAP_POPULATE; }
|
||||
// MAP_POPULATE would fault in the lazy ranges too
|
||||
if (prefetch && lazy_ranges.empty()) { flags |= MAP_POPULATE; }
|
||||
#endif
|
||||
addr = mmap(NULL, file->size(), PROT_READ, flags, fd, 0);
|
||||
if (addr == MAP_FAILED) {
|
||||
throw std::runtime_error(format("mmap failed: %s", strerror(errno)));
|
||||
}
|
||||
|
||||
if (prefetch > 0) {
|
||||
if (posix_madvise(addr, std::min(file->size(), prefetch), POSIX_MADV_WILLNEED)) {
|
||||
LLAMA_LOG_WARN("warning: posix_madvise(.., POSIX_MADV_WILLNEED) failed: %s\n",
|
||||
strerror(errno));
|
||||
// page-aligned madvise over [beg, end), clamped to the file
|
||||
auto advise = [&](size_t beg, size_t end, int advice, const char * name) {
|
||||
const size_t page_size = sysconf(_SC_PAGESIZE);
|
||||
beg = beg & ~(page_size - 1);
|
||||
end = std::min((end + page_size - 1) & ~(page_size - 1), file->size());
|
||||
if (beg >= end) {
|
||||
return;
|
||||
}
|
||||
if (posix_madvise((char *) addr + beg, end - beg, advice)) {
|
||||
LLAMA_LOG_WARN("warning: posix_madvise(.., %s) failed: %s\n", name, strerror(errno));
|
||||
}
|
||||
};
|
||||
|
||||
if (prefetch > 0) {
|
||||
for (const auto & range : ranges_complement(lazy_ranges, std::min(file->size(), prefetch))) {
|
||||
advise(range.first, range.second, POSIX_MADV_WILLNEED, "POSIX_MADV_WILLNEED");
|
||||
}
|
||||
}
|
||||
for (const auto & range : lazy_ranges) {
|
||||
advise(range.first, range.second, POSIX_MADV_RANDOM, "POSIX_MADV_RANDOM");
|
||||
}
|
||||
if (numa) {
|
||||
if (posix_madvise(addr, file->size(), POSIX_MADV_RANDOM)) {
|
||||
@@ -533,7 +572,7 @@ struct llama_mmap::impl {
|
||||
#elif defined(_WIN32)
|
||||
HANDLE hMapping = nullptr;
|
||||
|
||||
impl(struct llama_file * file, size_t prefetch, bool numa) {
|
||||
impl(struct llama_file * file, size_t prefetch, bool numa, const llama_mmap::ranges & lazy_ranges) {
|
||||
GGML_UNUSED(numa);
|
||||
|
||||
size = file->size();
|
||||
@@ -563,10 +602,15 @@ struct llama_mmap::impl {
|
||||
pPrefetchVirtualMemory = (decltype(pPrefetchVirtualMemory))(void *) GetProcAddress(hKernel32, "PrefetchVirtualMemory");
|
||||
|
||||
if (pPrefetchVirtualMemory) {
|
||||
WIN32_MEMORY_RANGE_ENTRY range;
|
||||
range.VirtualAddress = addr;
|
||||
range.NumberOfBytes = (SIZE_T) std::min(size, prefetch);
|
||||
if (!pPrefetchVirtualMemory(GetCurrentProcess(), 1, &range, 0)) {
|
||||
std::vector<WIN32_MEMORY_RANGE_ENTRY> entries;
|
||||
for (const auto & range : ranges_complement(lazy_ranges, std::min(size, prefetch))) {
|
||||
WIN32_MEMORY_RANGE_ENTRY entry;
|
||||
entry.VirtualAddress = (char *) addr + range.first;
|
||||
entry.NumberOfBytes = (SIZE_T) (range.second - range.first);
|
||||
entries.push_back(entry);
|
||||
}
|
||||
if (!entries.empty() &&
|
||||
!pPrefetchVirtualMemory(GetCurrentProcess(), (ULONG_PTR) entries.size(), entries.data(), 0)) {
|
||||
LLAMA_LOG_WARN("warning: PrefetchVirtualMemory failed: %s\n",
|
||||
llama_format_win_err(GetLastError()).c_str());
|
||||
}
|
||||
@@ -597,10 +641,11 @@ struct llama_mmap::impl {
|
||||
}
|
||||
}
|
||||
#else
|
||||
impl(struct llama_file * file, size_t prefetch, bool numa) {
|
||||
impl(struct llama_file * file, size_t prefetch, bool numa, const llama_mmap::ranges & lazy_ranges) {
|
||||
GGML_UNUSED(file);
|
||||
GGML_UNUSED(prefetch);
|
||||
GGML_UNUSED(numa);
|
||||
GGML_UNUSED(lazy_ranges);
|
||||
|
||||
throw std::runtime_error("mmap not supported");
|
||||
}
|
||||
@@ -617,7 +662,8 @@ struct llama_mmap::impl {
|
||||
size_t size;
|
||||
};
|
||||
|
||||
llama_mmap::llama_mmap(struct llama_file * file, size_t prefetch, bool numa) : pimpl(std::make_unique<impl>(file, prefetch, numa)) {}
|
||||
llama_mmap::llama_mmap(struct llama_file * file, size_t prefetch, bool numa,
|
||||
const ranges & lazy_ranges) : pimpl(std::make_unique<impl>(file, prefetch, numa, lazy_ranges)) {}
|
||||
llama_mmap::~llama_mmap() = default;
|
||||
|
||||
size_t llama_mmap::size() const { return pimpl->size; }
|
||||
|
||||
+6
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
|
||||
@@ -41,8 +42,12 @@ private:
|
||||
};
|
||||
|
||||
struct llama_mmap {
|
||||
// list of [first, last) byte ranges within a file
|
||||
using ranges = std::vector<std::pair<size_t, size_t>>;
|
||||
|
||||
llama_mmap(const llama_mmap &) = delete;
|
||||
llama_mmap(struct llama_file * file, size_t prefetch = (size_t) -1, bool numa = false);
|
||||
llama_mmap(struct llama_file * file, size_t prefetch = (size_t) -1, bool numa = false,
|
||||
const ranges & lazy_ranges = {});
|
||||
~llama_mmap();
|
||||
|
||||
size_t size() const;
|
||||
|
||||
+39
-17
@@ -321,10 +321,11 @@ namespace GGUFMeta {
|
||||
case GGUF_TYPE_UINT32:
|
||||
case GGUF_TYPE_INT32: type_ok = (std::is_same<T, int32_t>::value) ||
|
||||
(std::is_same<T, uint32_t>::value); break;
|
||||
case GGUF_TYPE_UINT64: type_ok = (std::is_same<T, uint64_t>::value); break;
|
||||
case GGUF_TYPE_FLOAT32: type_ok = (std::is_same<T, float>::value); break;
|
||||
case GGUF_TYPE_STRING: type_ok = (std::is_same<T, std::string>::value); break;
|
||||
default:
|
||||
throw std::runtime_error(format("%s is not a string/float32/uint32/int32 array", key.c_str()));
|
||||
throw std::runtime_error(format("%s is not a string/float32/uint32/int32/uint64 array", key.c_str()));
|
||||
}
|
||||
if (!type_ok) {
|
||||
throw std::runtime_error(format("%s has wrong array element type %s", key.c_str(), gguf_type_name(arr_info.gt)));
|
||||
@@ -367,10 +368,11 @@ namespace GGUFMeta {
|
||||
case GGUF_TYPE_UINT32:
|
||||
case GGUF_TYPE_INT32: type_ok = (std::is_same<T, int32_t>::value) ||
|
||||
(std::is_same<T, uint32_t>::value); break;
|
||||
case GGUF_TYPE_UINT64: type_ok = (std::is_same<T, uint64_t>::value); break;
|
||||
case GGUF_TYPE_FLOAT32: type_ok = (std::is_same<T, float>::value); break;
|
||||
case GGUF_TYPE_STRING: type_ok = (std::is_same<T, std::string>::value); break;
|
||||
default:
|
||||
throw std::runtime_error(format("%s is not a string/float32/uint32/int32 array", key.c_str()));
|
||||
throw std::runtime_error(format("%s is not a string/float32/uint32/int32/uint64 array", key.c_str()));
|
||||
}
|
||||
if (!type_ok) {
|
||||
throw std::runtime_error(format("%s has wrong array element type %s", key.c_str(), gguf_type_name(arr_info.gt)));
|
||||
@@ -410,6 +412,9 @@ namespace GGUFMeta {
|
||||
template bool llama_model_loader::get_arr<std::array<int32_t, 512>>(enum llm_kv kid, std::array<int32_t, 512> & result, bool required);
|
||||
template bool llama_model_loader::get_arr<std::vector<int32_t>>(enum llm_kv kid, std::vector<int32_t> & result, bool required);
|
||||
template bool llama_model_loader::get_arr<std::array<uint32_t, LLAMA_MAX_LAYERS>>(enum llm_kv kid, std::array<uint32_t, LLAMA_MAX_LAYERS> & result, bool required);
|
||||
template bool llama_model_loader::get_arr<std::vector<uint32_t>>(enum llm_kv kid, std::vector<uint32_t> & result, bool required);
|
||||
template bool llama_model_loader::get_arr<std::array<uint64_t, LLAMA_MAX_PLE_NGRAM>>(enum llm_kv kid, std::array<uint64_t, LLAMA_MAX_PLE_NGRAM> & result, bool required);
|
||||
template bool llama_model_loader::get_arr<std::array<uint64_t, LLAMA_MAX_PLE_HEADS>>(enum llm_kv kid, std::array<uint64_t, LLAMA_MAX_PLE_HEADS> & result, bool required);
|
||||
|
||||
template<typename T>
|
||||
bool llama_model_loader::get_key(const std::string & key, T & result, bool required) {
|
||||
@@ -1282,6 +1287,18 @@ struct ggml_tensor * llama_model_loader::create_tensor(
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((flags & TENSOR_READ_LAZY) && use_mmap && tensor_read_lazy != LLAMA_TENSOR_READ_LAZY_OFF) {
|
||||
// in auto mode, small tensors are cheap enough to keep resident
|
||||
constexpr size_t auto_lazy_min_size = 4ull * 1024 * 1024 * 1024;
|
||||
if (tensor_read_lazy == LLAMA_TENSOR_READ_LAZY_ON || ggml_nbytes(cur) > auto_lazy_min_size) {
|
||||
const auto & w = require_weight(tn.str().c_str());
|
||||
lazy_tensor_ranges[w.idx].emplace_back(w.offs, w.offs + ggml_nbytes(cur));
|
||||
|
||||
LLAMA_LOG_INFO("%s: tensor %s (size = %zu MiB) lazy read enabled\n",
|
||||
__func__, tn.str().c_str(), ggml_nbytes(cur)/1024/1024);
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor t_meta = *cur;
|
||||
if (flags & TENSOR_ALLOW_RESHAPE) {
|
||||
for (size_t dim = 0; dim < GGML_MAX_DIMS; dim++) {
|
||||
@@ -1349,7 +1366,9 @@ void llama_model_loader::init_mappings(bool prefetch, llama_mlocks * mlock_mmaps
|
||||
if (use_mmap) {
|
||||
mappings.reserve(files.size());
|
||||
mmaps_used.reserve(files.size());
|
||||
for (const auto & file : files) {
|
||||
for (uint32_t idx = 0; idx < files.size(); idx++) {
|
||||
const auto & file = files[idx];
|
||||
|
||||
bool is_numa = false;
|
||||
|
||||
auto * dev = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU);
|
||||
@@ -1361,7 +1380,11 @@ void llama_model_loader::init_mappings(bool prefetch, llama_mlocks * mlock_mmaps
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<llama_mmap> mapping = std::make_unique<llama_mmap>(file.get(), prefetch ? -1 : 0, is_numa);
|
||||
const auto it_lazy = lazy_tensor_ranges.find(idx);
|
||||
static const llama_mmap::ranges no_lazy_ranges;
|
||||
|
||||
std::unique_ptr<llama_mmap> mapping = std::make_unique<llama_mmap>(file.get(), prefetch ? -1 : 0, is_numa,
|
||||
it_lazy != lazy_tensor_ranges.end() ? it_lazy->second : no_lazy_ranges);
|
||||
mmaps_used.emplace_back(mapping->size(), 0);
|
||||
if (mlock_mmaps) {
|
||||
std::unique_ptr<llama_mlock> mlock_mmap(new llama_mlock());
|
||||
@@ -1400,27 +1423,26 @@ void llama_model_loader::unmap_weight(const llama_tensor_weight & w) const {
|
||||
mappings.at(w.idx)->unmap_fragment(w.offs, w.offs + ggml_nbytes(w.tensor));
|
||||
}
|
||||
|
||||
void llama_model_loader::load_data_for(struct ggml_tensor * cur) const {
|
||||
const auto & w = require_weight(ggml_get_name(cur));
|
||||
const void * llama_model_loader::load_data_range(const llama_tensor_weight & w, size_t offs, size_t size, void * buf) const {
|
||||
GGML_ASSERT(offs + size <= ggml_nbytes(w.tensor));
|
||||
|
||||
const void * data = buf;
|
||||
|
||||
if (use_mmap) {
|
||||
const auto & mapping = mappings.at(w.idx);
|
||||
if (cur->data == nullptr) {
|
||||
cur->data = (uint8_t *)mapping->addr() + w.offs;
|
||||
} else {
|
||||
memcpy(cur->data, (uint8_t *)mapping->addr() + w.offs, ggml_nbytes(cur));
|
||||
}
|
||||
data = (const uint8_t *) mappings.at(w.idx)->addr() + w.offs + offs;
|
||||
} else {
|
||||
GGML_ASSERT(cur->data != nullptr);
|
||||
GGML_ASSERT(buf != nullptr);
|
||||
GGML_ASSERT(w.idx < files.size());
|
||||
const auto & file = files.at(w.idx);
|
||||
file->seek(w.offs, SEEK_SET);
|
||||
file->read_raw(cur->data, ggml_nbytes(cur));
|
||||
file->seek(w.offs + offs, SEEK_SET);
|
||||
file->read_raw(buf, size);
|
||||
}
|
||||
|
||||
if (check_tensors && !ggml_validate_row_data(cur->type, cur->data, ggml_nbytes(cur))) {
|
||||
throw std::runtime_error(format("tensor '%s' has invalid data", ggml_get_name(cur)));
|
||||
if (check_tensors && !ggml_validate_row_data(w.tensor->type, data, size)) {
|
||||
throw std::runtime_error(format("tensor '%s' has invalid data", ggml_get_name(w.tensor)));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
bool llama_model_loader::load_all_data(
|
||||
|
||||
@@ -68,6 +68,7 @@ struct llama_model_loader {
|
||||
static const int TENSOR_SKIP = 1 << 2;
|
||||
static const int TENSOR_SKIP_IF_VIRTUAL = 1 << 3;
|
||||
static const int TENSOR_ALLOW_RESHAPE = 1 << 4;
|
||||
static const int TENSOR_READ_LAZY = 1 << 5; // read rows on demand instead of loading whole tensor; requires mmap for now
|
||||
|
||||
int n_kv = 0;
|
||||
int n_tensors = 0;
|
||||
@@ -82,12 +83,18 @@ struct llama_model_loader {
|
||||
bool no_alloc;
|
||||
bool load_mtp;
|
||||
|
||||
// set by the caller before the create_tensor() calls
|
||||
enum llama_tensor_read_lazy tensor_read_lazy = LLAMA_TENSOR_READ_LAZY_OFF;
|
||||
|
||||
llama_files files;
|
||||
llama_ftype ftype;
|
||||
llama_fver fver;
|
||||
|
||||
llama_mmaps mappings;
|
||||
|
||||
// byte ranges of TENSOR_READ_LAZY tensors, per file index
|
||||
std::map<uint32_t, llama_mmap::ranges> lazy_tensor_ranges;
|
||||
|
||||
std::map<std::string, llama_tensor_weight, weight_name_comparer> weights_map;
|
||||
std::unordered_map<std::string, llama_model_kv_override> kv_overrides;
|
||||
const llama_model_tensor_buft_override * tensor_buft_overrides;
|
||||
@@ -197,8 +204,9 @@ struct llama_model_loader {
|
||||
// release a weight's mmap pages
|
||||
void unmap_weight(const llama_tensor_weight & w) const;
|
||||
|
||||
// for backwards compatibility, does not support ggml-backend
|
||||
void load_data_for(struct ggml_tensor * cur) const;
|
||||
// read a byte range of a weight's data
|
||||
// with mmap, returns a pointer into the mapping, otherwise reads into buf and returns buf
|
||||
const void * load_data_range(const llama_tensor_weight & w, size_t offs, size_t size, void * buf) const;
|
||||
|
||||
// Returns false if cancelled by progress_callback
|
||||
bool load_all_data(
|
||||
|
||||
@@ -60,6 +60,10 @@ void llama_model_saver::add_kv(const enum llm_kv key, const int32_t value) {
|
||||
gguf_set_val_i32(gguf_ctx, llm_kv(key).c_str(), value);
|
||||
}
|
||||
|
||||
void llama_model_saver::add_kv(const enum llm_kv key, const uint64_t value) {
|
||||
gguf_set_val_u64(gguf_ctx, llm_kv(key).c_str(), value);
|
||||
}
|
||||
|
||||
void llama_model_saver::add_kv(const enum llm_kv key, const float value) {
|
||||
gguf_set_val_f32(gguf_ctx, llm_kv(key).c_str(), value);
|
||||
}
|
||||
@@ -113,6 +117,8 @@ void llama_model_saver::add_kv(const enum llm_kv key, const Container & value, c
|
||||
gguf_set_arr_data(gguf_ctx, llm_kv(key).c_str(), GGUF_TYPE_BOOL, value.data(), n_values);
|
||||
} else if (std::is_same<typename Container::value_type, int32_t>::value) {
|
||||
gguf_set_arr_data(gguf_ctx, llm_kv(key).c_str(), GGUF_TYPE_INT32, value.data(), n_values);
|
||||
} else if (std::is_same<typename Container::value_type, uint64_t>::value) {
|
||||
gguf_set_arr_data(gguf_ctx, llm_kv(key).c_str(), GGUF_TYPE_UINT64, value.data(), n_values);
|
||||
} else if (std::is_same<typename Container::value_type, float>::value) {
|
||||
gguf_set_arr_data(gguf_ctx, llm_kv(key).c_str(), GGUF_TYPE_FLOAT32, value.data(), n_values);
|
||||
} else if (std::is_same<Container, std::string>::value) {
|
||||
@@ -124,6 +130,7 @@ void llama_model_saver::add_kv(const enum llm_kv key, const Container & value, c
|
||||
// instantiate for external usage:
|
||||
template void llama_model_saver::add_kv<std::vector<uint32_t>>(const enum llm_kv, const std::vector<uint32_t> &, const bool);
|
||||
template void llama_model_saver::add_kv<std::vector<float>>(const enum llm_kv, const std::vector<float> &, const bool);
|
||||
template void llama_model_saver::add_kv<std::vector<uint64_t>>(const enum llm_kv, const std::vector<uint64_t> &, const bool);
|
||||
|
||||
void llama_model_saver::add_kv(const enum llm_kv key, const std::vector<std::string> & value) {
|
||||
std::vector<const char *> tmp(value.size());
|
||||
@@ -308,6 +315,32 @@ void llama_model_saver::add_kv_from_model() {
|
||||
add_kv(LLM_KV_HYPER_CONNECTION_SINKHORN_ITERATIONS, hparams.dsv4_hc_sinkhorn_iters);
|
||||
add_kv(LLM_KV_HYPER_CONNECTION_EPSILON, hparams.dsv4_hc_eps);
|
||||
add_kv(LLM_KV_HASH_LAYER_COUNT, hparams.dsv4_hash_layer_count);
|
||||
add_kv(LLM_KV_HYPER_CONNECTION_LOW_RANK, hparams.hc_low_rank);
|
||||
|
||||
// the PLE group only means anything whole: write all of it or none
|
||||
if (hparams.ple_n_heads > 0) {
|
||||
std::vector<uint32_t> ple_layers;
|
||||
for (uint32_t il = 0; il < hparams.n_layer_all; ++il) {
|
||||
if (hparams.is_ple_impl[il]) {
|
||||
ple_layers.push_back(il);
|
||||
}
|
||||
}
|
||||
add_kv(LLM_KV_PLE_LAYERS, ple_layers);
|
||||
add_kv(LLM_KV_PLE_NGRAM_SIZE, hparams.ple_ngram_size);
|
||||
add_kv(LLM_KV_PLE_HEADS_PER_NGRAM, hparams.ple_heads_per_ngram);
|
||||
add_kv(LLM_KV_PLE_CONV_KERNEL, hparams.ple_conv_kernel);
|
||||
add_kv(LLM_KV_PLE_EOS_TOKEN_ID, hparams.ple_eos_token_id);
|
||||
add_kv(LLM_KV_EMBEDDING_LENGTH_PER_LAYER, hparams.ple_head_dim);
|
||||
add_kv(LLM_KV_PLE_LAYER_MULTIPLIERS, std::vector<uint64_t>(
|
||||
hparams.ple_layer_multipliers.begin(),
|
||||
hparams.ple_layer_multipliers.begin() + hparams.ple_ngram_size));
|
||||
add_kv(LLM_KV_PLE_HEAD_OFFSETS, std::vector<uint64_t>(
|
||||
hparams.ple_head_offsets.begin(),
|
||||
hparams.ple_head_offsets.begin() + hparams.ple_n_heads));
|
||||
add_kv(LLM_KV_PLE_HEAD_VOCAB_SIZES, std::vector<uint64_t>(
|
||||
hparams.ple_head_vocab_sizes.begin(),
|
||||
hparams.ple_head_vocab_sizes.begin() + hparams.ple_n_heads));
|
||||
}
|
||||
|
||||
const float rope_scaling_factor = hparams.rope_freq_scale_train == 1.0f ? 0.0f : 1.0f/hparams.rope_freq_scale_train;
|
||||
|
||||
@@ -442,6 +475,10 @@ void llama_model_saver::add_tensors_from_model() {
|
||||
add_tensor(model->hc_head_fn);
|
||||
add_tensor(model->hc_head_base);
|
||||
add_tensor(model->hc_head_scale);
|
||||
add_tensor(model->per_layer_tok_embd);
|
||||
add_tensor(model->hc_head_norm);
|
||||
add_tensor(model->hc_head_down);
|
||||
add_tensor(model->hc_head_up);
|
||||
|
||||
for (const struct llama_layer & layer : model->layers) {
|
||||
for (size_t i = 0; i < sizeof(layer)/sizeof(struct ggml_tensor *); ++i) {
|
||||
|
||||
@@ -21,6 +21,7 @@ struct llama_model_saver {
|
||||
|
||||
void add_kv(enum llm_kv key, uint32_t value);
|
||||
void add_kv(enum llm_kv key, int32_t value);
|
||||
void add_kv(enum llm_kv key, uint64_t value);
|
||||
void add_kv(enum llm_kv key, float value);
|
||||
void add_kv(enum llm_kv key, bool value);
|
||||
void add_kv(enum llm_kv key, const char * value);
|
||||
|
||||
+65
-4
@@ -16,6 +16,7 @@
|
||||
#include "llama-kv-cache-dsv4.h"
|
||||
#include "llama-memory-hybrid.h"
|
||||
#include "llama-memory-hybrid-iswa.h"
|
||||
#include "llama-memory-hybrid-idx.h"
|
||||
#include "llama-memory-recurrent.h"
|
||||
|
||||
#include "llama.h"
|
||||
@@ -319,6 +320,8 @@ static llama_model * llama_model_mapping(llm_arch arch, const llama_model_params
|
||||
return new llama_model_qwen35(params);
|
||||
case LLM_ARCH_QWEN35MOE:
|
||||
return new llama_model_qwen35moe(params);
|
||||
case LLM_ARCH_QWEN4EXP:
|
||||
return new llama_model_qwen4exp(params);
|
||||
case LLM_ARCH_MISTRAL3:
|
||||
return new llama_model_mistral3(params);
|
||||
case LLM_ARCH_EAGLE3:
|
||||
@@ -376,6 +379,7 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
|
||||
static const std::regex pattern_qkv_bias ("blk\\.\\d*\\.attn_qkv.bias");
|
||||
static const std::regex pattern_qk_norm ("blk\\.\\d*\\.attn_(q|k)_norm\\.weight");
|
||||
static const std::regex pattern_kv_cache ("cache_(k|v)_l\\d*");
|
||||
static const std::regex pattern_idx_cache ("cache_idx_(k|v)_l\\d*");
|
||||
static const std::regex pattern_dsv4_state ("dsv4_(csa|hca|lid)_state_(kv|score)_l\\d*");
|
||||
static const std::regex pattern_attn_sinks ("blk\\.\\d*\\.attn_sinks.weight");
|
||||
static const std::regex pattern_attn_out_weight ("blk\\.\\d*\\.attn_output.weight");
|
||||
@@ -391,6 +395,7 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
|
||||
static const std::regex pattern_ssm_beta ("blk\\.\\d*\\.ssm_beta.weight");
|
||||
static const std::regex pattern_ssm_beta_alpha ("blk\\.\\d*\\.ssm_ba.weight");
|
||||
static const std::regex pattern_r_cache ("cache_r_l\\d*");
|
||||
static const std::regex pattern_ple_r_cache ("cache_ple_r_l\\d*");
|
||||
static const std::regex pattern_s_cache ("cache_s_l\\d*");
|
||||
static const std::regex pattern_ssm_conv1d ("blk\\.\\d*\\.ssm_conv1d.weight");
|
||||
static const std::regex pattern_ssm_out_weight ("blk\\.\\d*\\.ssm_out.weight");
|
||||
@@ -488,6 +493,16 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
|
||||
}
|
||||
}
|
||||
|
||||
// the qsa indexer has one key head and its projections are mirrored, so its cache cannot be split
|
||||
if (std::regex_match(tensor_name, pattern_idx_cache)) {
|
||||
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED);
|
||||
}
|
||||
|
||||
// the PLE table is model-level and its conv is mirrored, so every device runs the whole conv and needs the whole history
|
||||
if (std::regex_match(tensor_name, pattern_ple_r_cache)) {
|
||||
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED);
|
||||
}
|
||||
|
||||
// standard attention
|
||||
if (std::regex_match(tensor_name, pattern_q_weight) || std::regex_match(tensor_name, pattern_kv_weight)) {
|
||||
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "attn_output.weight", "ssm_out.weight");
|
||||
@@ -576,7 +591,8 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
|
||||
};
|
||||
|
||||
auto get_split_segments = [&](int axis, uint32_t il) -> std::vector<std::pair<int64_t, uint32_t>> {
|
||||
if (ud->model->arch == LLM_ARCH_QWEN3NEXT || ud->model->arch == LLM_ARCH_QWEN35 || ud->model->arch == LLM_ARCH_QWEN35MOE) {
|
||||
if (ud->model->arch == LLM_ARCH_QWEN3NEXT || ud->model->arch == LLM_ARCH_QWEN35 || ud->model->arch == LLM_ARCH_QWEN35MOE ||
|
||||
ud->model->arch == LLM_ARCH_QWEN4EXP) {
|
||||
const int64_t head_k_dim = hparams.ssm_d_state;
|
||||
const int64_t head_v_dim = hparams.ssm_d_state;
|
||||
const int64_t n_k_heads = hparams.ssm_n_group;
|
||||
@@ -714,7 +730,8 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
|
||||
if (std::regex_match(tensor_name, pattern_q_weight) || std::regex_match(tensor_name, pattern_q_bias)) {
|
||||
GGML_ASSERT(segments.size() == 1);
|
||||
// some models have Q gate tensors, for those cases the granularity needs to be doubled:
|
||||
if (ud->model->arch == LLM_ARCH_QWEN3NEXT || ud->model->arch == LLM_ARCH_QWEN35 || ud->model->arch == LLM_ARCH_QWEN35MOE) {
|
||||
if (ud->model->arch == LLM_ARCH_QWEN3NEXT || ud->model->arch == LLM_ARCH_QWEN35 || ud->model->arch == LLM_ARCH_QWEN35MOE ||
|
||||
ud->model->arch == LLM_ARCH_QWEN4EXP) {
|
||||
return {std::lcm(2*n_embd_q, blck_size_perf)};
|
||||
}
|
||||
return {granularity_q};
|
||||
@@ -927,6 +944,7 @@ const char * llm_type_name(llm_type type) {
|
||||
case LLM_TYPE_35B_A3B: return "35B.A3B";
|
||||
case LLM_TYPE_48B_A3B: return "48B.A3B";
|
||||
case LLM_TYPE_80B_A3B: return "80B.A3B";
|
||||
case LLM_TYPE_A3B: return "A3B";
|
||||
case LLM_TYPE_100B_A6B: return "100B.A6B";
|
||||
case LLM_TYPE_102B_A12B: return "102B.A12B";
|
||||
case LLM_TYPE_106B_A12B: return "106B.A12B";
|
||||
@@ -2431,6 +2449,10 @@ llama_memory_i * llama_model::create_memory(const llama_memory_params & params,
|
||||
// layer filters, so pick the right one here
|
||||
llama_memory_hybrid::layer_filter_cb filter_attn = nullptr;
|
||||
llama_memory_hybrid::layer_filter_cb filter_recr = nullptr;
|
||||
// only the sparse-attention architectures use llama_memory_hybrid_idx
|
||||
// a null filter_idx means the GGUF has no indexer tensors
|
||||
llama_memory_hybrid::layer_filter_cb filter_idx = nullptr;
|
||||
const bool needs_mem_idx = (arch == LLM_ARCH_QWEN4EXP);
|
||||
if (arch == LLM_ARCH_FALCON_H1) {
|
||||
filter_attn = [&](uint32_t) { return true; };
|
||||
filter_recr = [&](uint32_t) { return true; };
|
||||
@@ -2441,13 +2463,20 @@ llama_memory_i * llama_model::create_memory(const llama_memory_params & params,
|
||||
filter_recr = [&](uint32_t il) {
|
||||
return hparams.is_recr(il) && hparams.n_ff(il) == 0;
|
||||
};
|
||||
} else if (arch == LLM_ARCH_QWEN3NEXT || arch == LLM_ARCH_QWEN35 || arch == LLM_ARCH_QWEN35MOE || arch == LLM_ARCH_MINIMAX_01) {
|
||||
} else if (arch == LLM_ARCH_QWEN3NEXT || arch == LLM_ARCH_QWEN35 || arch == LLM_ARCH_QWEN35MOE || arch == LLM_ARCH_QWEN4EXP || arch == LLM_ARCH_MINIMAX_01) {
|
||||
filter_attn = [&](uint32_t il) {
|
||||
return il < hparams.n_layer() && !hparams.is_recr(il);
|
||||
};
|
||||
filter_recr = [&](uint32_t il) {
|
||||
return il < hparams.n_layer() && hparams.is_recr(il);
|
||||
};
|
||||
|
||||
if (arch == LLM_ARCH_QWEN4EXP && hparams.indexer_head_size > 0) {
|
||||
// QSA runs on the dense-attention layers only
|
||||
filter_idx = [&](uint32_t il) {
|
||||
return il < hparams.n_layer() && !hparams.is_recr(il);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (hparams.swa_type != LLAMA_SWA_TYPE_NONE) {
|
||||
@@ -2470,6 +2499,27 @@ llama_memory_i * llama_model::create_memory(const llama_memory_params & params,
|
||||
/* unified */ cparams.kv_unified,
|
||||
/* filter_attn */ std::move(filter_attn),
|
||||
/* filter_recr */ std::move(filter_recr));
|
||||
} else if (needs_mem_idx) {
|
||||
// sparse attention over a per-token indexer cache, in its own memory type
|
||||
res = new llama_memory_hybrid_idx(
|
||||
/* model */ *this,
|
||||
/* attn_type_k */ params.type_k,
|
||||
/* attn_type_v */ params.type_v,
|
||||
/* attn_v_trans */ !cparams.flash_attn,
|
||||
/* attn_kv_size */ cparams.n_ctx_seq,
|
||||
/* attn_n_pad */ 1,
|
||||
/* attn_n_swa */ hparams.n_swa,
|
||||
/* attn_swa_type */ hparams.swa_type,
|
||||
/* recurrent_type_k */ GGML_TYPE_F32,
|
||||
/* recurrent_type_v */ GGML_TYPE_F32,
|
||||
/* recurrent_kv_size */ std::max((uint32_t) 1, cparams.n_seq_max),
|
||||
/* n_seq_max */ cparams.n_seq_max,
|
||||
/* n_rs_seq */ cparams.n_rs_seq,
|
||||
/* offload */ cparams.offload_kqv,
|
||||
/* unified */ cparams.kv_unified,
|
||||
/* filter_attn */ std::move(filter_attn),
|
||||
/* filter_recr */ std::move(filter_recr),
|
||||
/* filter_idx */ std::move(filter_idx));
|
||||
} else {
|
||||
res = new llama_memory_hybrid(
|
||||
/* model */ *this,
|
||||
@@ -2631,6 +2681,7 @@ llama_model_params llama_model_default_params() {
|
||||
/*.n_gpu_layers =*/ -1,
|
||||
/*.split_mode =*/ LLAMA_SPLIT_MODE_LAYER,
|
||||
/*.load_mode =*/ LLAMA_LOAD_MODE_AUTO,
|
||||
/*.tensor_read_lazy =*/ LLAMA_TENSOR_READ_LAZY_AUTO,
|
||||
/*.main_gpu =*/ 0,
|
||||
/*.tensor_split =*/ nullptr,
|
||||
/*.progress_callback =*/ nullptr,
|
||||
@@ -2683,6 +2734,10 @@ int32_t llama_model_n_layer_nextn(const llama_model * model) {
|
||||
return model->hparams.n_layer_nextn;
|
||||
}
|
||||
|
||||
int32_t llama_model_dflash_selector_top_k(const llama_model * model) {
|
||||
return model->hparams.dflash_selector_top_k;
|
||||
}
|
||||
|
||||
int32_t llama_model_n_head(const llama_model * model) {
|
||||
return model->hparams.n_head();
|
||||
}
|
||||
@@ -2881,6 +2936,10 @@ llama_rope_type llama_model_rope_type(const llama_model * model) {
|
||||
return LLAMA_ROPE_TYPE_NEOX;
|
||||
|
||||
case LLM_ARCH_DFLASH:
|
||||
// drafts for M-RoPE targets carry rope sections and follow the target's temporal dim
|
||||
if (const auto & s = model->hparams.rope_sections; s[0] || s[1] || s[2] || s[3]) {
|
||||
return LLAMA_ROPE_TYPE_MROPE;
|
||||
}
|
||||
// DSV4 DSpark drafters use DeepSeek-V4's normal RoPE; legacy DFlash backbones are NeoX
|
||||
return model->hparams.dsv4_hc_mult > 0 ? LLAMA_ROPE_TYPE_NORM : LLAMA_ROPE_TYPE_NEOX;
|
||||
|
||||
@@ -2891,6 +2950,7 @@ llama_rope_type llama_model_rope_type(const llama_model * model) {
|
||||
case LLM_ARCH_QWEN3VLMOE:
|
||||
case LLM_ARCH_QWEN35:
|
||||
case LLM_ARCH_QWEN35MOE:
|
||||
case LLM_ARCH_QWEN4EXP:
|
||||
case LLM_ARCH_QWEN3TTS:
|
||||
return LLAMA_ROPE_TYPE_IMROPE;
|
||||
|
||||
@@ -3067,7 +3127,8 @@ llama_model_base::llama_model_base(const struct llama_model_params & params) : l
|
||||
TENSOR_NOT_REQUIRED (llama_model_loader::TENSOR_NOT_REQUIRED),
|
||||
TENSOR_SKIP (llama_model_loader::TENSOR_SKIP),
|
||||
TENSOR_SKIP_IF_VIRTUAL(llama_model_loader::TENSOR_SKIP_IF_VIRTUAL),
|
||||
TENSOR_ALLOW_RESHAPE (llama_model_loader::TENSOR_ALLOW_RESHAPE) {}
|
||||
TENSOR_ALLOW_RESHAPE (llama_model_loader::TENSOR_ALLOW_RESHAPE),
|
||||
TENSOR_READ_LAZY (llama_model_loader::TENSOR_READ_LAZY) {}
|
||||
|
||||
ggml_tensor * llama_model_base::create_tensor(const LLM_TN_IMPL & tn, const std::initializer_list<int64_t> & ne, int flags) {
|
||||
GGML_ASSERT(ml != nullptr);
|
||||
|
||||
@@ -129,6 +129,7 @@ enum llm_type {
|
||||
LLM_TYPE_35B_A3B, // Qwen3.5
|
||||
LLM_TYPE_48B_A3B, // Kimi Linear
|
||||
LLM_TYPE_80B_A3B, // Qwen3 Next
|
||||
LLM_TYPE_A3B, // Qwen3.8 Flash Next
|
||||
LLM_TYPE_100B_A6B,
|
||||
LLM_TYPE_102B_A12B, // Solar-Open
|
||||
LLM_TYPE_106B_A12B, // GLM-4.5-Air
|
||||
@@ -363,6 +364,11 @@ struct llama_layer {
|
||||
struct ggml_tensor * ffn_exp_probs_b = nullptr;
|
||||
struct ggml_tensor * ffn_gate_tid2eid = nullptr;
|
||||
|
||||
struct ggml_tensor * dflash_attn_conv_base = nullptr;
|
||||
struct ggml_tensor * dflash_attn_conv_proj = nullptr;
|
||||
struct ggml_tensor * dflash_ffn_conv_base = nullptr;
|
||||
struct ggml_tensor * dflash_ffn_conv_proj = nullptr;
|
||||
|
||||
// mamba proj
|
||||
struct ggml_tensor * ssm_in = nullptr;
|
||||
struct ggml_tensor * ssm_x = nullptr;
|
||||
@@ -555,6 +561,22 @@ struct llama_layer {
|
||||
struct ggml_tensor * index_q_norm = nullptr;
|
||||
struct ggml_tensor * index_k_norm = nullptr;
|
||||
|
||||
struct ggml_tensor * hc_attn_norm = nullptr;
|
||||
struct ggml_tensor * hc_attn_down = nullptr;
|
||||
struct ggml_tensor * hc_attn_up = nullptr;
|
||||
struct ggml_tensor * hc_attn_inject = nullptr;
|
||||
struct ggml_tensor * hc_ffn_norm = nullptr;
|
||||
struct ggml_tensor * hc_ffn_down = nullptr;
|
||||
struct ggml_tensor * hc_ffn_up = nullptr;
|
||||
struct ggml_tensor * hc_ffn_inject = nullptr;
|
||||
|
||||
struct ggml_tensor * ple_key = nullptr;
|
||||
struct ggml_tensor * ple_value = nullptr;
|
||||
struct ggml_tensor * ple_norm_key = nullptr;
|
||||
struct ggml_tensor * ple_norm_query = nullptr;
|
||||
struct ggml_tensor * ple_norm_conv = nullptr;
|
||||
struct ggml_tensor * ple_conv1d = nullptr;
|
||||
|
||||
// gemma4 layer output scale, reused for talkie embedding skip scale
|
||||
struct ggml_tensor * out_scale = nullptr;
|
||||
|
||||
@@ -635,6 +657,10 @@ struct llama_model {
|
||||
struct ggml_tensor * altup_proj = nullptr;
|
||||
struct ggml_tensor * altup_unembd_proj = nullptr;
|
||||
struct ggml_tensor * per_layer_tok_embd = nullptr;
|
||||
|
||||
struct ggml_tensor * hc_head_norm = nullptr;
|
||||
struct ggml_tensor * hc_head_down = nullptr;
|
||||
struct ggml_tensor * hc_head_up = nullptr;
|
||||
struct ggml_tensor * per_layer_model_proj = nullptr;
|
||||
struct ggml_tensor * per_layer_proj_norm = nullptr;
|
||||
|
||||
@@ -646,9 +672,14 @@ struct llama_model {
|
||||
// dspark
|
||||
struct ggml_tensor * dspark_markov_w1 = nullptr;
|
||||
struct ggml_tensor * dspark_markov_w2 = nullptr;
|
||||
struct ggml_tensor * dspark_markov_w2_s = nullptr;
|
||||
struct ggml_tensor * dspark_conf_proj = nullptr;
|
||||
struct ggml_tensor * dspark_conf_proj_b = nullptr;
|
||||
|
||||
struct ggml_tensor * dflash_selector_prev = nullptr;
|
||||
struct ggml_tensor * dflash_selector_next = nullptr;
|
||||
struct ggml_tensor * dflash_selector_hidden = nullptr;
|
||||
|
||||
// unified vector to store target-model extracted layer ids in eagle3, dflash, etc.
|
||||
std::vector<int32_t> target_layer_ids;
|
||||
|
||||
@@ -756,6 +787,7 @@ struct llama_model_base : public llama_model {
|
||||
const int TENSOR_SKIP;
|
||||
const int TENSOR_SKIP_IF_VIRTUAL;
|
||||
const int TENSOR_ALLOW_RESHAPE;
|
||||
const int TENSOR_READ_LAZY;
|
||||
|
||||
explicit llama_model_base(const llama_model_params & params);
|
||||
virtual ~llama_model_base() = default;
|
||||
|
||||
+102
-63
@@ -38,6 +38,9 @@ enum class tensor_category {
|
||||
OTHER
|
||||
};
|
||||
|
||||
// max amount of tensor data kept in memory while quantizing a single tensor
|
||||
static const size_t LLAMA_QUANT_MAX_BUF_SIZE = 8ull*1024*1024*1024;
|
||||
|
||||
static void zeros(std::ofstream & file, size_t n) {
|
||||
char zero = 0;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
@@ -211,31 +214,26 @@ struct tensor_metadata {
|
||||
//
|
||||
|
||||
static void llama_tensor_dequantize_impl(
|
||||
ggml_tensor * tensor, std::vector<no_init<float>> & output, std::vector<std::thread> & workers,
|
||||
ggml_type type, const void * data, float * f32_output, std::vector<std::thread> & workers,
|
||||
const size_t nelements, const int nthread
|
||||
) {
|
||||
if (output.size() < nelements) {
|
||||
output.resize(nelements);
|
||||
}
|
||||
float * f32_output = (float *) output.data();
|
||||
|
||||
const ggml_type_traits * qtype = ggml_get_type_traits(tensor->type);
|
||||
if (ggml_is_quantized(tensor->type)) {
|
||||
const ggml_type_traits * qtype = ggml_get_type_traits(type);
|
||||
if (ggml_is_quantized(type)) {
|
||||
if (qtype->to_float == NULL) {
|
||||
throw std::runtime_error(format("type %s unsupported for integer quantization: no dequantization available", ggml_type_name(tensor->type)));
|
||||
throw std::runtime_error(format("type %s unsupported for integer quantization: no dequantization available", ggml_type_name(type)));
|
||||
}
|
||||
} else if (tensor->type != GGML_TYPE_F16 &&
|
||||
tensor->type != GGML_TYPE_BF16) {
|
||||
throw std::runtime_error(format("cannot dequantize/convert tensor type %s", ggml_type_name(tensor->type)));
|
||||
} else if (type != GGML_TYPE_F16 &&
|
||||
type != GGML_TYPE_BF16) {
|
||||
throw std::runtime_error(format("cannot dequantize/convert tensor type %s", ggml_type_name(type)));
|
||||
}
|
||||
|
||||
if (nthread < 2) {
|
||||
if (tensor->type == GGML_TYPE_F16) {
|
||||
ggml_fp16_to_fp32_row((ggml_fp16_t *)tensor->data, f32_output, nelements);
|
||||
} else if (tensor->type == GGML_TYPE_BF16) {
|
||||
ggml_bf16_to_fp32_row((ggml_bf16_t *)tensor->data, f32_output, nelements);
|
||||
} else if (ggml_is_quantized(tensor->type)) {
|
||||
qtype->to_float(tensor->data, f32_output, nelements);
|
||||
if (type == GGML_TYPE_F16) {
|
||||
ggml_fp16_to_fp32_row((const ggml_fp16_t *)data, f32_output, nelements);
|
||||
} else if (type == GGML_TYPE_BF16) {
|
||||
ggml_bf16_to_fp32_row((const ggml_bf16_t *)data, f32_output, nelements);
|
||||
} else if (ggml_is_quantized(type)) {
|
||||
qtype->to_float(data, f32_output, nelements);
|
||||
} else {
|
||||
GGML_ABORT("fatal error"); // unreachable
|
||||
}
|
||||
@@ -243,14 +241,14 @@ static void llama_tensor_dequantize_impl(
|
||||
}
|
||||
|
||||
size_t block_size;
|
||||
if (tensor->type == GGML_TYPE_F16 ||
|
||||
tensor->type == GGML_TYPE_BF16) {
|
||||
if (type == GGML_TYPE_F16 ||
|
||||
type == GGML_TYPE_BF16) {
|
||||
block_size = 1;
|
||||
} else {
|
||||
block_size = (size_t)ggml_blck_size(tensor->type);
|
||||
block_size = (size_t)ggml_blck_size(type);
|
||||
}
|
||||
|
||||
size_t block_size_bytes = ggml_type_size(tensor->type);
|
||||
size_t block_size_bytes = ggml_type_size(type);
|
||||
|
||||
GGML_ASSERT(nelements % block_size == 0);
|
||||
size_t nblocks = nelements / block_size;
|
||||
@@ -265,16 +263,16 @@ static void llama_tensor_dequantize_impl(
|
||||
size_t thr_elems = thr_blocks * block_size; // number of elements for this thread
|
||||
size_t thr_block_bytes = thr_blocks * block_size_bytes; // number of input bytes for this thread
|
||||
|
||||
auto compute = [qtype] (ggml_type typ, uint8_t * inbuf, float * outbuf, int nels) {
|
||||
auto compute = [qtype] (ggml_type typ, const uint8_t * inbuf, float * outbuf, int nels) {
|
||||
if (typ == GGML_TYPE_F16) {
|
||||
ggml_fp16_to_fp32_row((ggml_fp16_t *)inbuf, outbuf, nels);
|
||||
ggml_fp16_to_fp32_row((const ggml_fp16_t *)inbuf, outbuf, nels);
|
||||
} else if (typ == GGML_TYPE_BF16) {
|
||||
ggml_bf16_to_fp32_row((ggml_bf16_t *)inbuf, outbuf, nels);
|
||||
ggml_bf16_to_fp32_row((const ggml_bf16_t *)inbuf, outbuf, nels);
|
||||
} else {
|
||||
qtype->to_float(inbuf, outbuf, nels);
|
||||
}
|
||||
};
|
||||
workers.emplace_back(compute, tensor->type, (uint8_t *) tensor->data + in_buff_offs, f32_output + out_buff_offs, thr_elems);
|
||||
workers.emplace_back(compute, type, (const uint8_t *) data + in_buff_offs, f32_output + out_buff_offs, thr_elems);
|
||||
in_buff_offs += thr_block_bytes;
|
||||
out_buff_offs += thr_elems;
|
||||
}
|
||||
@@ -401,6 +399,12 @@ static ggml_type tensor_type_fallback(quantize_state_impl & qs, const ggml_tenso
|
||||
case GGML_TYPE_Q5_K: return_type = GGML_TYPE_Q5_1; break;
|
||||
case GGML_TYPE_Q6_K: return_type = GGML_TYPE_Q8_0; break;
|
||||
default:
|
||||
if (qk_k <= 32) {
|
||||
// the target is already a 32-block type, so there is no smaller block to demote to
|
||||
// the check below turns it into F16, as a 256-block type does when its fallback does not fit
|
||||
return_type = target_type;
|
||||
break;
|
||||
}
|
||||
throw std::runtime_error(format("no tensor type fallback is defined for type %s",
|
||||
ggml_type_name(target_type)));
|
||||
}
|
||||
@@ -681,7 +685,21 @@ static ggml_type llama_tensor_get_type(quantize_state_impl & qs, const llama_mod
|
||||
return tensor->type;
|
||||
}
|
||||
if (params->token_embedding_type < GGML_TYPE_COUNT && tm.category == tensor_category::TOKEN_EMBD) {
|
||||
return params->token_embedding_type;
|
||||
// per_layer_token_embd follows --token-embedding-type by default, but it is a large
|
||||
// separate table, so let an explicit --tensor-type name it
|
||||
bool named = false;
|
||||
if (std::strcmp(tensor->name, "per_layer_token_embd.weight") == 0) {
|
||||
const std::string tensor_name(tensor->name);
|
||||
for (const auto & [pattern, qtype] : qs.tensor_type_patterns) {
|
||||
if (std::regex_search(tensor_name, pattern)) {
|
||||
named = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!named) {
|
||||
return params->token_embedding_type;
|
||||
}
|
||||
}
|
||||
if (params->output_tensor_type < GGML_TYPE_COUNT && tm.category == tensor_category::OUTPUT) {
|
||||
return params->output_tensor_type;
|
||||
@@ -1093,6 +1111,8 @@ static void llama_model_quantize_impl(const std::string & fname_inp, const std::
|
||||
std::vector<no_init<uint8_t>> work;
|
||||
std::vector<no_init<float>> f32_conv_buf;
|
||||
|
||||
const size_t max_buf_size = params->max_buf_size ? params->max_buf_size : LLAMA_QUANT_MAX_BUF_SIZE;
|
||||
|
||||
int cur_split = -1;
|
||||
std::ofstream fout;
|
||||
auto close_ofstream = [&]() {
|
||||
@@ -1143,15 +1163,13 @@ static void llama_model_quantize_impl(const std::string & fname_inp, const std::
|
||||
|
||||
const size_t tensor_size = ggml_nbytes(tensor);
|
||||
|
||||
if (!params->dry_run) {
|
||||
if (!ml.use_mmap) {
|
||||
if (read_data.size() < tensor_size) {
|
||||
read_data.resize(tensor_size);
|
||||
}
|
||||
tensor->data = read_data.data();
|
||||
// read a byte range of the current tensor
|
||||
auto load_range = [&](size_t offs, size_t size) -> const void * {
|
||||
if (!ml.use_mmap && read_data.size() < size) {
|
||||
read_data.resize(size);
|
||||
}
|
||||
ml.load_data_for(tensor);
|
||||
}
|
||||
return ml.load_data_range(weight, offs, size, read_data.data());
|
||||
};
|
||||
|
||||
LLAMA_LOG_INFO("[%4d/%4d] %-36s - [%s], type = %6s, ",
|
||||
++idx, ml.n_tensors,
|
||||
@@ -1166,7 +1184,6 @@ static void llama_model_quantize_impl(const std::string & fname_inp, const std::
|
||||
// in then there's nothing to do.
|
||||
bool quantize = cur_type != new_type;
|
||||
|
||||
void * new_data;
|
||||
size_t new_size;
|
||||
|
||||
if (params->dry_run) {
|
||||
@@ -1190,12 +1207,18 @@ static void llama_model_quantize_impl(const std::string & fname_inp, const std::
|
||||
} else {
|
||||
// no --dry-run, perform quantization
|
||||
if (!quantize) {
|
||||
new_data = tensor->data;
|
||||
new_size = tensor_size;
|
||||
LLAMA_LOG_INFO("size = %8.3f MiB\n", tensor_size/1024.0/1024.0);
|
||||
} else {
|
||||
const int64_t nelements = ggml_nelements(tensor);
|
||||
|
||||
// copy in slabs of whole rows, so that each slab can be validated
|
||||
const size_t row_size = ggml_row_size(tensor->type, tensor->ne[0]);
|
||||
const size_t slab_size = std::max<size_t>(row_size, (max_buf_size/row_size)*row_size);
|
||||
|
||||
for (size_t offs = 0; offs < tensor_size; offs += slab_size) {
|
||||
const size_t size = std::min(slab_size, tensor_size - offs);
|
||||
fout.write((const char *) load_range(offs, size), size);
|
||||
}
|
||||
} else {
|
||||
const float * imatrix = nullptr;
|
||||
if (imatrix_data) {
|
||||
auto it = imatrix_data->find(tm.remapped_imatrix_name);
|
||||
@@ -1227,43 +1250,60 @@ static void llama_model_quantize_impl(const std::string & fname_inp, const std::
|
||||
throw std::runtime_error(format("Missing importance matrix for tensor %s in a very low-bit quantization", tensor->name));
|
||||
}
|
||||
|
||||
float * f32_data;
|
||||
|
||||
if (tensor->type == GGML_TYPE_F32) {
|
||||
f32_data = (float *) tensor->data;
|
||||
} else if (ggml_is_quantized(tensor->type) && !params->allow_requantize) {
|
||||
if (ggml_is_quantized(tensor->type) && !params->allow_requantize) {
|
||||
throw std::runtime_error(format("requantizing from type %s is disabled", ggml_type_name(tensor->type)));
|
||||
} else {
|
||||
llama_tensor_dequantize_impl(tensor, f32_conv_buf, workers, nelements, nthread);
|
||||
f32_data = (float *) f32_conv_buf.data();
|
||||
}
|
||||
|
||||
LLAMA_LOG_INFO("converting to %s .. ", ggml_type_name(new_type));
|
||||
fflush(stdout);
|
||||
|
||||
if (work.size() < (size_t)nelements * 4) {
|
||||
work.resize(nelements * 4); // upper bound on size
|
||||
}
|
||||
new_data = work.data();
|
||||
|
||||
const int64_t n_per_row = tensor->ne[0];
|
||||
const int64_t nrows = tensor->ne[1];
|
||||
|
||||
const size_t row_size_src = ggml_row_size(tensor->type, n_per_row);
|
||||
const size_t row_size_dst = ggml_row_size(new_type, n_per_row);
|
||||
|
||||
// process the rows in slabs, so that the buffers stay below max_buf_size
|
||||
const size_t bytes_per_row = row_size_src + row_size_dst + (tensor->type == GGML_TYPE_F32 ? 0 : n_per_row*sizeof(float));
|
||||
const int64_t nrows_slab = std::max<int64_t>(1, std::min<int64_t>(nrows, max_buf_size/bytes_per_row));
|
||||
|
||||
static const int64_t min_chunk_size = 32 * 512;
|
||||
const int64_t chunk_size = (n_per_row >= min_chunk_size ? n_per_row : n_per_row * ((min_chunk_size + n_per_row - 1)/n_per_row));
|
||||
|
||||
const int64_t nelements_matrix = tensor->ne[0] * tensor->ne[1];
|
||||
const int64_t nchunk = (nelements_matrix + chunk_size - 1)/chunk_size;
|
||||
const int64_t nthread_use = nthread > 1 ? std::max((int64_t)1, std::min((int64_t)nthread, nchunk)) : 1;
|
||||
|
||||
// quantize each expert separately since they have different importance matrices
|
||||
new_size = 0;
|
||||
for (int64_t i03 = 0; i03 < tensor->ne[2]; ++i03) {
|
||||
const float * f32_data_03 = f32_data + i03 * nelements_matrix;
|
||||
void * new_data_03 = (char *)new_data + ggml_row_size(new_type, n_per_row) * i03 * nrows;
|
||||
const float * imatrix_03 = imatrix ? imatrix + i03 * n_per_row : nullptr;
|
||||
|
||||
new_size += llama_tensor_quantize_impl(new_type, f32_data_03, new_data_03, chunk_size, nrows, n_per_row, imatrix_03, workers, nthread_use);
|
||||
for (int64_t ir = 0; ir < nrows; ir += nrows_slab) {
|
||||
const int64_t nrows_cur = std::min(nrows_slab, nrows - ir);
|
||||
const int64_t nelements_cur = nrows_cur * n_per_row;
|
||||
|
||||
const void * src = load_range((i03*nrows + ir)*row_size_src, nrows_cur*row_size_src);
|
||||
|
||||
const float * f32_data;
|
||||
if (tensor->type == GGML_TYPE_F32) {
|
||||
f32_data = (const float *) src;
|
||||
} else {
|
||||
if (f32_conv_buf.size() < (size_t) nelements_cur) {
|
||||
f32_conv_buf.resize(nelements_cur);
|
||||
}
|
||||
llama_tensor_dequantize_impl(tensor->type, src, (float *) f32_conv_buf.data(), workers, nelements_cur, nthread);
|
||||
f32_data = (const float *) f32_conv_buf.data();
|
||||
}
|
||||
|
||||
if (work.size() < nrows_cur*row_size_dst) {
|
||||
work.resize(nrows_cur*row_size_dst);
|
||||
}
|
||||
|
||||
const int64_t nchunk = (nelements_cur + chunk_size - 1)/chunk_size;
|
||||
const int64_t nthread_use = nthread > 1 ? std::max((int64_t)1, std::min((int64_t)nthread, nchunk)) : 1;
|
||||
|
||||
const size_t size_cur = llama_tensor_quantize_impl(new_type, f32_data, work.data(), chunk_size, nrows_cur, n_per_row, imatrix_03, workers, nthread_use);
|
||||
|
||||
fout.write((const char *) work.data(), size_cur);
|
||||
new_size += size_cur;
|
||||
}
|
||||
}
|
||||
LLAMA_LOG_INFO("size = %8.2f MiB -> %8.2f MiB\n", tensor_size/1024.0/1024.0, new_size/1024.0/1024.0);
|
||||
}
|
||||
@@ -1273,10 +1313,8 @@ static void llama_model_quantize_impl(const std::string & fname_inp, const std::
|
||||
// update the gguf metadata as we go
|
||||
gguf_set_tensor_type(ctx_outs[cur_split].get(), metadata[i].name.c_str(), new_type);
|
||||
GGML_ASSERT(gguf_get_tensor_size(ctx_outs[cur_split].get(), gguf_find_tensor(ctx_outs[cur_split].get(), metadata[i].name.c_str())) == new_size);
|
||||
gguf_set_tensor_data(ctx_outs[cur_split].get(), metadata[i].name.c_str(), new_data);
|
||||
|
||||
// write tensor data + padding
|
||||
fout.write((const char *) new_data, new_size);
|
||||
// tensor data is already written, add the padding
|
||||
zeros(fout, GGML_PAD(new_size, align) - new_size);
|
||||
|
||||
// unmap the tensor to free memory
|
||||
@@ -1323,7 +1361,8 @@ llama_model_quantize_params llama_model_quantize_default_params() {
|
||||
/*.imatrix =*/ nullptr,
|
||||
/*.kv_overrides =*/ nullptr,
|
||||
/*.tensor_type =*/ nullptr,
|
||||
/*.prune_layers =*/ nullptr
|
||||
/*.prune_layers =*/ nullptr,
|
||||
/*.max_buf_size =*/ LLAMA_QUANT_MAX_BUF_SIZE
|
||||
};
|
||||
|
||||
return result;
|
||||
|
||||
@@ -318,6 +318,8 @@ static std::pair<int, llama_model *> llama_model_load(struct gguf_context * meta
|
||||
llama_model_loader ml(metadata, set_tensor_data, set_tensor_data_ud, fname, splits, file, params.load_mode,
|
||||
params.check_tensors, params.no_alloc, params.load_mtp, params.kv_overrides, params.tensor_buft_overrides);
|
||||
|
||||
ml.tensor_read_lazy = params.tensor_read_lazy;
|
||||
|
||||
ml.print_info();
|
||||
std::unique_ptr<llama_model> model_ptr(llama_model_create(ml, params));
|
||||
|
||||
|
||||
+299
-39
@@ -7,6 +7,18 @@
|
||||
void llama_model_dflash::load_arch_hparams(llama_model_loader & ml) {
|
||||
|
||||
ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps);
|
||||
ml.get_key(LLM_KV_LOGIT_SCALE, hparams.f_logit_scale, false);
|
||||
hparams.f_final_logit_softcapping = 0.0f;
|
||||
ml.get_key(LLM_KV_FINAL_LOGIT_SOFTCAPPING, hparams.f_final_logit_softcapping, false);
|
||||
|
||||
// drafts for M-RoPE targets carry degenerate sections [n_rot/2, 0, 0, 0]
|
||||
ml.get_key_or_arr(LLM_KV_ROPE_DIMENSION_SECTIONS, hparams.rope_sections, 4, false);
|
||||
|
||||
ml.get_key(LLM_KV_DFLASH_BLOCK_SIZE, hparams.dflash_block_size, false);
|
||||
ml.get_key(LLM_KV_DFLASH_CONV_KERNEL_SIZE, hparams.dflash_conv_kernel_size, false);
|
||||
ml.get_key(LLM_KV_DFLASH_CONV_GROUP_SIZE, hparams.dflash_conv_group_size, false);
|
||||
ml.get_key(LLM_KV_DFLASH_SELECTOR_RANK, hparams.dflash_selector_rank, false);
|
||||
ml.get_key(LLM_KV_DFLASH_SELECTOR_TOP_K, hparams.dflash_selector_top_k, false);
|
||||
|
||||
if (!ml.get_arr(LLM_KV_TARGET_LAYERS, target_layer_ids, false)) {
|
||||
throw std::runtime_error("DFlash model requires 'target_layers' in GGUF metadata");
|
||||
@@ -103,15 +115,39 @@ void llama_model_dflash::load_arch_tensors(llama_model_loader &) {
|
||||
if (markov_meta) {
|
||||
const int64_t dspark_markov_rank = markov_meta->ne[0];
|
||||
|
||||
dspark_markov_w1 = create_tensor(tn(LLM_TENSOR_DSPARK_MARKOV_W1, "weight"), { dspark_markov_rank, n_vocab }, 0);
|
||||
dspark_markov_w2 = create_tensor(tn(LLM_TENSOR_DSPARK_MARKOV_W2, "weight"), { dspark_markov_rank, n_vocab_draft }, 0);
|
||||
dspark_markov_w1 = create_tensor(tn(LLM_TENSOR_DSPARK_MARKOV_W1, "weight"), { dspark_markov_rank, n_vocab }, 0);
|
||||
dspark_markov_w2 = create_tensor(tn(LLM_TENSOR_DSPARK_MARKOV_W2, "weight"), { dspark_markov_rank, n_vocab_draft }, 0);
|
||||
dspark_markov_w2_s = create_tensor(tn(LLM_TENSOR_DSPARK_MARKOV_W2, "scale"), { 1 }, TENSOR_NOT_REQUIRED);
|
||||
|
||||
dspark_conf_proj = create_tensor(tn(LLM_TENSOR_DSPARK_CONF_PROJ, "weight"), { n_embd + dspark_markov_rank, 1 }, 0);
|
||||
dspark_conf_proj = create_tensor(tn(LLM_TENSOR_DSPARK_CONF_PROJ, "weight"), { n_embd + dspark_markov_rank, 1 }, TENSOR_NOT_REQUIRED);
|
||||
dspark_conf_proj_b = create_tensor(tn(LLM_TENSOR_DSPARK_CONF_PROJ, "bias"), { 1 }, TENSOR_NOT_REQUIRED);
|
||||
|
||||
LLAMA_LOG_INFO("%s: DFlash with DSpark markov head (rank = %lld)\n", __func__, (long long) dspark_markov_rank);
|
||||
}
|
||||
|
||||
const struct ggml_tensor * selector_meta = ml->get_tensor_meta("selector_hidden.weight");
|
||||
if (selector_meta) {
|
||||
const int64_t rank = hparams.dflash_selector_rank;
|
||||
if (rank <= 0 || hparams.dflash_block_size <= 0 || hparams.dflash_selector_top_k <= 0 ||
|
||||
hparams.dflash_conv_kernel_size <= 0 || hparams.dflash_conv_group_size <= 0) {
|
||||
throw std::runtime_error("DFlash2 model is missing conv/selector metadata");
|
||||
}
|
||||
if (n_embd % hparams.dflash_conv_group_size != 0) {
|
||||
throw std::runtime_error("DFlash2 hidden size must be divisible by conv_group_size");
|
||||
}
|
||||
if (n_embd < hparams.dflash_selector_top_k * (hparams.dflash_selector_top_k + 1)) {
|
||||
throw std::runtime_error("DFlash2 hidden size is too small for the selector lattice");
|
||||
}
|
||||
|
||||
dflash_selector_prev = create_tensor(tn(LLM_TENSOR_DFLASH_SELECTOR_PREV, "weight"), { rank, n_vocab }, 0);
|
||||
dflash_selector_next = create_tensor(tn(LLM_TENSOR_DFLASH_SELECTOR_NEXT, "weight"), { rank, n_vocab }, 0);
|
||||
dflash_selector_hidden = create_tensor(tn(LLM_TENSOR_DFLASH_SELECTOR_HIDDEN, "weight"), { n_embd, rank }, 0);
|
||||
|
||||
LLAMA_LOG_INFO("%s: DFlash2 conv kernel = %u, group = %u, selector rank = %u, top-k = %u\n", __func__,
|
||||
hparams.dflash_conv_kernel_size, hparams.dflash_conv_group_size,
|
||||
hparams.dflash_selector_rank, hparams.dflash_selector_top_k);
|
||||
}
|
||||
|
||||
fc = create_tensor(tn(LLM_TENSOR_FC, "weight"), { n_embd_inp, n_embd }, 0);
|
||||
fc_s = create_tensor(tn(LLM_TENSOR_FC, "scale"), { 1 }, TENSOR_NOT_REQUIRED);
|
||||
output_norm_enc = create_tensor(tn(LLM_TENSOR_ENC_OUTPUT_NORM, "weight"), { n_embd }, 0); // encoder hidden_norm (after fc)
|
||||
@@ -184,10 +220,23 @@ void llama_model_dflash::load_arch_tensors(llama_model_loader &) {
|
||||
layer.attn_q_norm = create_tensor(tn(LLM_TENSOR_ATTN_Q_NORM, "weight", i), { n_embd_head_k }, 0);
|
||||
layer.attn_k_norm = create_tensor(tn(LLM_TENSOR_ATTN_K_NORM, "weight", i), { n_embd_head_k }, 0);
|
||||
|
||||
// optional per-head attention sinks (e.g. Nemotron DSpark)
|
||||
layer.attn_sinks = create_tensor(tn(LLM_TENSOR_ATTN_SINKS, "weight", i), { n_head }, TENSOR_NOT_REQUIRED);
|
||||
|
||||
layer.ffn_norm = create_tensor(tn(LLM_TENSOR_FFN_NORM, "weight", i), { n_embd }, 0);
|
||||
layer.ffn_gate = create_tensor(tn(LLM_TENSOR_FFN_GATE, "weight", i), { n_embd, n_ff }, 0);
|
||||
layer.ffn_down = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "weight", i), { n_ff, n_embd }, 0);
|
||||
layer.ffn_up = create_tensor(tn(LLM_TENSOR_FFN_UP, "weight", i), { n_embd, n_ff }, 0);
|
||||
|
||||
if (selector_meta) {
|
||||
const int64_t kernel = hparams.dflash_conv_kernel_size;
|
||||
const int64_t groups = n_embd / hparams.dflash_conv_group_size;
|
||||
const int64_t projected = 2 * kernel * groups;
|
||||
layer.dflash_attn_conv_base = create_tensor(tn(LLM_TENSOR_DFLASH_ATTN_CONV_BASE, i), { n_embd, kernel, 2 }, 0);
|
||||
layer.dflash_attn_conv_proj = create_tensor(tn(LLM_TENSOR_DFLASH_ATTN_CONV_PROJ, "weight", i), { n_embd, projected }, 0);
|
||||
layer.dflash_ffn_conv_base = create_tensor(tn(LLM_TENSOR_DFLASH_FFN_CONV_BASE, i), { n_embd, kernel, 2 }, 0);
|
||||
layer.dflash_ffn_conv_proj = create_tensor(tn(LLM_TENSOR_DFLASH_FFN_CONV_PROJ, "weight", i), { n_embd, projected }, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +294,10 @@ static void build_dspark_markov_head(llm_graph_context & g, const llama_model &
|
||||
|
||||
ggml_tensor * w1 = model.dspark_markov_w1;
|
||||
ggml_tensor * w2 = model.dspark_markov_w2;
|
||||
GGML_ASSERT(w1 && w2 && model.dspark_conf_proj && "DSpark markov/confidence weights not loaded");
|
||||
GGML_ASSERT(w1 && w2 && "DSpark markov weights not loaded");
|
||||
|
||||
// confidence head is optional
|
||||
const bool has_conf = model.dspark_conf_proj != nullptr;
|
||||
|
||||
ggml_tensor * base = res->t_logits; // [n_vocab, n_tokens]
|
||||
const int64_t n_vocab = base->ne[0];
|
||||
@@ -276,23 +328,22 @@ static void build_dspark_markov_head(llm_graph_context & g, const llama_model &
|
||||
ggml_tensor * prev = ggml_view_2d(ctx0, tokens, 1, n_blocks, token_stride, 0);
|
||||
prev = ggml_cont_1d(ctx0, prev, n_blocks);
|
||||
|
||||
// confidence head input: predicts per-position acceptance
|
||||
ggml_tensor * conf_inp = res->t_embd; // [n_embd, n_tok]
|
||||
|
||||
ggml_tensor * cat = nullptr;
|
||||
ggml_tensor * cat_conf = nullptr;
|
||||
|
||||
if (!sample_from_anchor) {
|
||||
// bonus anchor slot: pass the logits through unbiased, pad the (unread) confidence column
|
||||
cat = ggml_cont(ctx0, ggml_view_2d(ctx0, base, n_vocab, n_blocks, base_stride, 0));
|
||||
cat_conf = ggml_sigmoid(ctx0, ggml_cont(ctx0, ggml_view_2d(ctx0, base, 1, n_blocks, base_stride, 0)));
|
||||
cat = ggml_cont(ctx0, ggml_view_2d(ctx0, base, n_vocab, n_blocks, base_stride, 0));
|
||||
if (has_conf) {
|
||||
cat_conf = ggml_sigmoid(ctx0, ggml_cont(ctx0, ggml_view_2d(ctx0, base, 1, n_blocks, base_stride, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: the in-graph chain is greedy (argmax); sampling params affect only the final
|
||||
// token pick, not the Markov conditioning path
|
||||
for (int64_t i = i_draft_beg; i < block_drafts; ++i) {
|
||||
ggml_tensor * w1_prev = ggml_get_rows(ctx0, w1, prev); // [R, n_blocks]
|
||||
ggml_tensor * bias = ggml_mul_mat(ctx0, w2, w1_prev); // [n_vocab_draft, n_blocks]
|
||||
ggml_tensor * w1_prev = ggml_get_rows(ctx0, w1, prev); // [R, n_blocks]
|
||||
ggml_tensor * bias = g.build_lora_mm(w2, w1_prev, model.dspark_markov_w2_s); // [n_vocab_draft, n_blocks]
|
||||
if (model.d2t) {
|
||||
// reduced draft vocab: scatter the bias to the target rows (base is -inf on the others)
|
||||
const int64_t n_draft_vocab = bias->ne[0];
|
||||
@@ -309,17 +360,21 @@ static void build_dspark_markov_head(llm_graph_context & g, const llama_model &
|
||||
|
||||
cat = cat ? ggml_concat(ctx0, cat, col, 1) : col;
|
||||
|
||||
// conf(i) = sigmoid(conf_proj . [conf_inp(i); markov_w1[prev(i)]] + b) -- [1, n_blocks]
|
||||
ggml_tensor * conf_inp_i = ggml_view_2d(ctx0, conf_inp, conf_inp->ne[0], n_blocks,
|
||||
(size_t) block_drafts * conf_inp->nb[1], i*conf_inp->nb[1]);
|
||||
ggml_tensor * feat = ggml_concat(ctx0, ggml_cont(ctx0, conf_inp_i), w1_prev, 0);
|
||||
ggml_tensor * conf = ggml_mul_mat(ctx0, model.dspark_conf_proj, feat);
|
||||
if (model.dspark_conf_proj_b) {
|
||||
conf = ggml_add(ctx0, conf, model.dspark_conf_proj_b);
|
||||
}
|
||||
conf = ggml_sigmoid(ctx0, conf);
|
||||
if (has_conf) {
|
||||
// confidence head input: predicts per-position acceptance
|
||||
ggml_tensor * conf_inp = res->t_embd; // [n_embd, n_tok]
|
||||
// conf(i) = sigmoid(conf_proj . [conf_inp(i); markov_w1[prev(i)]] + b) -- [1, n_blocks]
|
||||
ggml_tensor * conf_inp_i = ggml_view_2d(ctx0, conf_inp, conf_inp->ne[0], n_blocks,
|
||||
(size_t) block_drafts * conf_inp->nb[1], i*conf_inp->nb[1]);
|
||||
ggml_tensor * feat = ggml_concat(ctx0, ggml_cont(ctx0, conf_inp_i), w1_prev, 0);
|
||||
ggml_tensor * conf = ggml_mul_mat(ctx0, model.dspark_conf_proj, feat);
|
||||
if (model.dspark_conf_proj_b) {
|
||||
conf = ggml_add(ctx0, conf, model.dspark_conf_proj_b);
|
||||
}
|
||||
conf = ggml_sigmoid(ctx0, conf);
|
||||
|
||||
cat_conf = cat_conf ? ggml_concat(ctx0, cat_conf, conf, 1) : conf;
|
||||
cat_conf = cat_conf ? ggml_concat(ctx0, cat_conf, conf, 1) : conf;
|
||||
}
|
||||
|
||||
if (i + 1 < block_drafts) {
|
||||
prev = ggml_argmax(ctx0, col);
|
||||
@@ -331,7 +386,7 @@ static void build_dspark_markov_head(llm_graph_context & g, const llama_model &
|
||||
out = ggml_cont(ctx0, ggml_permute(ctx0, out, 0, 2, 1, 3)); // [n_vocab, block_drafts, n_blocks]
|
||||
out = ggml_reshape_2d(ctx0, out, n_vocab, n_tok);
|
||||
|
||||
{
|
||||
if (has_conf) {
|
||||
ggml_tensor * conf = ggml_reshape_3d(ctx0, cat_conf, 1, n_blocks, block_drafts);
|
||||
conf = ggml_cont(ctx0, ggml_permute(ctx0, conf, 0, 2, 1, 3));
|
||||
conf = ggml_reshape_2d(ctx0, conf, 1, n_tok);
|
||||
@@ -346,6 +401,167 @@ static void build_dspark_markov_head(llm_graph_context & g, const llama_model &
|
||||
ggml_build_forward_expand(g.gf, out);
|
||||
}
|
||||
|
||||
static ggml_tensor * build_dflash2_conv(
|
||||
llm_graph_context & g,
|
||||
ggml_tensor * hidden,
|
||||
ggml_tensor * dynamic,
|
||||
ggml_tensor * base,
|
||||
int side) {
|
||||
const auto & hparams = g.hparams;
|
||||
const int64_t hidden_size = hidden->ne[0];
|
||||
const int64_t n_tokens = hidden->ne[1];
|
||||
const int64_t n_blocks = g.ubatch.n_seqs_unq;
|
||||
const int64_t kernel_size = hparams.dflash_conv_kernel_size;
|
||||
const int64_t group_size = hparams.dflash_conv_group_size;
|
||||
const int64_t n_groups = hidden_size / group_size;
|
||||
|
||||
GGML_ASSERT(n_blocks > 0 && n_tokens % n_blocks == 0);
|
||||
GGML_ASSERT(dynamic && base && side >= 0 && side < 2);
|
||||
|
||||
const int64_t block_size = n_tokens / n_blocks;
|
||||
ggml_context * ctx0 = g.ctx0;
|
||||
// ggml_cont copies even when the tensor is already contiguous
|
||||
if (!ggml_is_contiguous(hidden) || hidden->ne[1] != n_tokens) {
|
||||
hidden = ggml_cont_2d(ctx0, hidden, hidden_size, n_tokens);
|
||||
}
|
||||
if (!ggml_is_contiguous(dynamic) || dynamic->ne[1] != n_tokens) {
|
||||
dynamic = ggml_cont_2d(ctx0, dynamic, dynamic->ne[0], n_tokens);
|
||||
}
|
||||
ggml_tensor * blocks = ggml_reshape_3d(ctx0, hidden, hidden_size, block_size, n_blocks);
|
||||
ggml_tensor * coeffs = ggml_reshape_4d(ctx0, dynamic, n_groups, kernel_size, 2, n_tokens);
|
||||
ggml_tensor * coeffs_side = ggml_view_3d(ctx0, coeffs, n_groups, kernel_size, n_tokens,
|
||||
coeffs->nb[1], coeffs->nb[3], side * coeffs->nb[2]);
|
||||
|
||||
ggml_tensor * coeff_all = ggml_cont(ctx0, coeffs_side);
|
||||
coeff_all = ggml_reshape_4d(ctx0, coeff_all, 1, n_groups, kernel_size, n_tokens);
|
||||
coeff_all = ggml_repeat_4d(ctx0, coeff_all, group_size, n_groups, kernel_size, n_tokens);
|
||||
|
||||
ggml_tensor * base_side = ggml_reshape_4d(ctx0,
|
||||
ggml_view_1d(ctx0, base, hidden_size * kernel_size, side * base->nb[2]),
|
||||
group_size, n_groups, kernel_size, 1);
|
||||
|
||||
ggml_tensor * weight_all = ggml_add(ctx0, coeff_all, base_side);
|
||||
|
||||
ggml_tensor * result = nullptr;
|
||||
for (int64_t tap = 0; tap < kernel_size; ++tap) {
|
||||
ggml_tensor * values = blocks;
|
||||
if (tap > 0) {
|
||||
ggml_tensor * zeros = ggml_fill(ctx0,
|
||||
ggml_new_tensor_3d(ctx0, hidden->type, hidden_size, std::min(tap, block_size), n_blocks), 0.0f);
|
||||
if (tap < block_size) {
|
||||
ggml_tensor * previous = ggml_view_3d(ctx0, blocks, hidden_size, block_size - tap, n_blocks,
|
||||
blocks->nb[1], blocks->nb[2], 0);
|
||||
values = ggml_concat(ctx0, zeros, previous, 1);
|
||||
} else {
|
||||
values = zeros;
|
||||
}
|
||||
}
|
||||
values = ggml_reshape_2d(ctx0, values, hidden_size, n_tokens);
|
||||
|
||||
ggml_tensor * weight = ggml_reshape_2d(ctx0,
|
||||
ggml_cont(ctx0, ggml_view_4d(ctx0, weight_all, group_size, n_groups, 1, n_tokens,
|
||||
weight_all->nb[1], weight_all->nb[2], weight_all->nb[3], tap * weight_all->nb[2])),
|
||||
hidden_size, n_tokens);
|
||||
|
||||
ggml_tensor * term = ggml_mul(ctx0, weight, values);
|
||||
result = result ? ggml_add(ctx0, result, term) : term;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// DFlash2 selector: top-k candidates per block position plus the pairwise
|
||||
// transition scores, packed into the nextn output slot for the CPU-side walk.
|
||||
static void build_dflash2_selector(llm_graph_context & g, const llama_model & model, ggml_tensor * tokens) {
|
||||
ggml_context * ctx0 = g.ctx0;
|
||||
auto & res = g.res;
|
||||
|
||||
const auto & hparams = g.hparams;
|
||||
const int64_t n_tokens = g.n_tokens;
|
||||
const int64_t n_embd = g.n_embd;
|
||||
|
||||
const int64_t top_k = hparams.dflash_selector_top_k;
|
||||
const int64_t rank = hparams.dflash_selector_rank;
|
||||
const int64_t n_blocks = g.ubatch.n_seqs_unq;
|
||||
GGML_ASSERT(n_blocks > 0 && n_tokens % n_blocks == 0);
|
||||
GGML_ASSERT(res->t_logits->ne[1] == n_tokens);
|
||||
if (!tokens) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t tokens_per_block = n_tokens / n_blocks;
|
||||
const int64_t block_size = std::min<int64_t>(tokens_per_block, hparams.dflash_block_size);
|
||||
const int64_t row_used = top_k + top_k * top_k;
|
||||
|
||||
ggml_tensor * candidates = ggml_top_k(ctx0, res->t_logits, top_k);
|
||||
ggml_tensor * logits_rows = ggml_reshape_3d(ctx0, res->t_logits, 1, res->t_logits->ne[0], n_tokens);
|
||||
ggml_tensor * unary = ggml_reshape_2d(ctx0,
|
||||
ggml_get_rows(ctx0, logits_rows, candidates), top_k, n_tokens);
|
||||
ggml_tensor * gate = g.build_lora_mm(model.dflash_selector_hidden, res->t_embd);
|
||||
|
||||
// Everything below indexes [.., tokens_per_block, n_blocks]: the block
|
||||
// position varies fastest, sequences are the outer dimension.
|
||||
ggml_tensor * cand_blk = ggml_reshape_3d(ctx0, candidates, top_k, tokens_per_block, n_blocks);
|
||||
ggml_tensor * unary_blk = ggml_reshape_3d(ctx0, unary, top_k, tokens_per_block, n_blocks);
|
||||
ggml_tensor * gate_blk = ggml_reshape_3d(ctx0, gate, rank, tokens_per_block, n_blocks);
|
||||
|
||||
// a position's score reads only the candidate sets at pos-1 and pos, so a run
|
||||
// of positions has no internal dependency and scores in one batched matmul
|
||||
auto score_run = [&](int64_t beg_pos, int64_t n_pos, ggml_tensor * pred_ids) {
|
||||
ggml_tensor * cand_run = ggml_cont(ctx0, ggml_view_3d(ctx0, cand_blk, top_k, n_pos, n_blocks,
|
||||
cand_blk->nb[1], cand_blk->nb[2], beg_pos * cand_blk->nb[1]));
|
||||
ggml_tensor * unary_run = ggml_cont(ctx0, ggml_view_3d(ctx0, unary_blk, top_k, n_pos, n_blocks,
|
||||
unary_blk->nb[1], unary_blk->nb[2], beg_pos * unary_blk->nb[1]));
|
||||
ggml_tensor * gate_run = ggml_cont(ctx0, ggml_view_3d(ctx0, gate_blk, rank, n_pos, n_blocks,
|
||||
gate_blk->nb[1], gate_blk->nb[2], beg_pos * gate_blk->nb[1]));
|
||||
|
||||
const int64_t n_pred = pred_ids->ne[0] / (n_pos * n_blocks);
|
||||
|
||||
ggml_tensor * successor = ggml_reshape_4d(ctx0,
|
||||
ggml_get_rows(ctx0, model.dflash_selector_next, ggml_reshape_1d(ctx0, cand_run, top_k * n_pos * n_blocks)),
|
||||
rank, top_k, n_pos, n_blocks);
|
||||
ggml_tensor * predecessor = ggml_reshape_4d(ctx0,
|
||||
ggml_get_rows(ctx0, model.dflash_selector_prev, pred_ids),
|
||||
rank, n_pred, n_pos, n_blocks);
|
||||
|
||||
ggml_tensor * gate_bcast = ggml_reshape_4d(ctx0, gate_run, rank, 1, n_pos, n_blocks);
|
||||
ggml_tensor * cond = ggml_mul(ctx0, predecessor, ggml_repeat(ctx0, gate_bcast, predecessor));
|
||||
ggml_tensor * score = ggml_mul_mat(ctx0, successor, cond);
|
||||
if (n_pred == 1) {
|
||||
score = ggml_repeat_4d(ctx0, score, top_k, top_k, n_pos, n_blocks);
|
||||
}
|
||||
ggml_tensor * unary_bcast = ggml_reshape_4d(ctx0, unary_run, top_k, 1, n_pos, n_blocks);
|
||||
score = ggml_add(ctx0, score, ggml_repeat(ctx0, unary_bcast, score));
|
||||
|
||||
ggml_tensor * row = ggml_concat(ctx0,
|
||||
ggml_cast(ctx0, cand_run, GGML_TYPE_F32),
|
||||
ggml_reshape_3d(ctx0, score, top_k * top_k, n_pos, n_blocks), 0);
|
||||
return ggml_pad(ctx0, row, n_embd - row_used, 0, 0, 0);
|
||||
};
|
||||
|
||||
ggml_tensor * packed = ggml_fill(ctx0,
|
||||
ggml_new_tensor_3d(ctx0, GGML_TYPE_F32, n_embd, 1, n_blocks), 0.0f);
|
||||
|
||||
if (block_size > 1) {
|
||||
// Position 1 alone: its predecessor is the anchor token, one id per
|
||||
// sequence rather than a candidate set.
|
||||
ggml_tensor * anchor_ids = ggml_cont_1d(ctx0,
|
||||
ggml_view_2d(ctx0, tokens, 1, n_blocks, tokens_per_block * tokens->nb[0], 0), n_blocks);
|
||||
packed = ggml_concat(ctx0, packed, score_run(1, 1, anchor_ids), 1);
|
||||
}
|
||||
if (block_size > 2) {
|
||||
ggml_tensor * prev_ids = ggml_reshape_1d(ctx0,
|
||||
ggml_cont(ctx0, ggml_view_3d(ctx0, cand_blk, top_k, block_size - 2, n_blocks,
|
||||
cand_blk->nb[1], cand_blk->nb[2], cand_blk->nb[1])),
|
||||
top_k * (block_size - 2) * n_blocks);
|
||||
packed = ggml_concat(ctx0, packed, score_run(2, block_size - 2, prev_ids), 1);
|
||||
}
|
||||
|
||||
packed = ggml_reshape_2d(ctx0, packed, n_embd, block_size * n_blocks);
|
||||
g.cb(packed, "dflash2_lattice", -1);
|
||||
res->t_h_nextn = packed;
|
||||
ggml_build_forward_expand(g.gf, packed);
|
||||
}
|
||||
|
||||
// DFlash decoder, dual-mode by batch type:
|
||||
// * embd batch -> fused target features: project + inject K/V into the cache.
|
||||
// * token batch -> noise-block diffusion: attend over [committed, MASK...] to generate draft tokens
|
||||
@@ -370,6 +586,20 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
|
||||
|
||||
const float kq_scale = 1.0f/sqrtf(float(n_embd_head));
|
||||
|
||||
// drafts for M-RoPE targets use degenerate sections (temporal dim only)
|
||||
int sections[4];
|
||||
std::copy(std::begin(hparams.rope_sections), std::begin(hparams.rope_sections) + 4, sections);
|
||||
|
||||
auto build_rope = [&](ggml_tensor * cur, ggml_tensor * pos) {
|
||||
return rope_type == GGML_ROPE_TYPE_MROPE
|
||||
? ggml_rope_multi(ctx0, cur, pos, nullptr,
|
||||
n_rot, sections, rope_type, n_ctx_orig, freq_base, freq_scale,
|
||||
ext_factor, attn_factor, beta_fast, beta_slow)
|
||||
: ggml_rope_ext(ctx0, cur, pos, nullptr,
|
||||
n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
|
||||
ext_factor, attn_factor, beta_fast, beta_slow);
|
||||
};
|
||||
|
||||
// KV cache injection
|
||||
if (ubatch.embd) {
|
||||
auto inp = std::make_unique<llm_graph_input_embd>(n_embd);
|
||||
@@ -392,11 +622,7 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
|
||||
Vcur = ggml_reshape_3d(ctx0, Vcur, n_embd_head, n_head_kv, n_tokens);
|
||||
|
||||
Kcur = build_norm(Kcur, layer.attn_k_norm, NULL, LLM_NORM_RMS, il);
|
||||
Kcur = ggml_rope_ext(
|
||||
ctx0, Kcur, inp_pos, nullptr,
|
||||
n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
|
||||
ext_factor, attn_factor, beta_fast, beta_slow
|
||||
);
|
||||
Kcur = build_rope(Kcur, inp_pos);
|
||||
cb(Kcur, "Kcur_injected", il);
|
||||
cb(Vcur, "Vcur_injected", il);
|
||||
|
||||
@@ -450,6 +676,7 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
|
||||
|
||||
inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
|
||||
ggml_set_input(inp->tokens);
|
||||
res->t_inp_tokens = inp->tokens;
|
||||
|
||||
ggml_tensor * inp_tokens = inp->tokens;
|
||||
|
||||
@@ -464,6 +691,13 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
|
||||
ggml_tensor * noise_norm = build_norm(inpL, layer.attn_norm, NULL, LLM_NORM_RMS, il);
|
||||
cb(noise_norm, "noise_norm", il);
|
||||
|
||||
ggml_tensor * attn_dynamic = nullptr;
|
||||
if (layer.dflash_attn_conv_proj) {
|
||||
attn_dynamic = build_lora_mm(layer.dflash_attn_conv_proj, noise_norm);
|
||||
noise_norm = build_dflash2_conv(*this, noise_norm, attn_dynamic, layer.dflash_attn_conv_base, 0);
|
||||
cb(noise_norm, "attn_conv_in", il);
|
||||
}
|
||||
|
||||
ggml_tensor * Qcur = build_lora_mm(layer.wq, noise_norm);
|
||||
ggml_tensor * Kcur = build_lora_mm(layer.wk, noise_norm);
|
||||
ggml_tensor * Vcur = build_lora_mm(layer.wv, noise_norm);
|
||||
@@ -475,24 +709,21 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
|
||||
Qcur = build_norm(Qcur, layer.attn_q_norm, NULL, LLM_NORM_RMS, il);
|
||||
Kcur = build_norm(Kcur, layer.attn_k_norm, NULL, LLM_NORM_RMS, il);
|
||||
|
||||
Qcur = ggml_rope_ext(
|
||||
ctx0, Qcur, inp_pos, nullptr,
|
||||
n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
|
||||
ext_factor, attn_factor, beta_fast, beta_slow
|
||||
);
|
||||
Kcur = ggml_rope_ext(
|
||||
ctx0, Kcur, inp_pos, nullptr,
|
||||
n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
|
||||
ext_factor, attn_factor, beta_fast, beta_slow
|
||||
);
|
||||
Qcur = build_rope(Qcur, inp_pos);
|
||||
Kcur = build_rope(Kcur, inp_pos);
|
||||
cb(Qcur, "Qcur", il);
|
||||
cb(Kcur, "Kcur", il);
|
||||
cb(Vcur, "Vcur", il);
|
||||
|
||||
// cache-aware, non-causal attention
|
||||
ggml_tensor * cur = use_iswa
|
||||
? build_attn(inp_attn_iswa, layer.wo, NULL, NULL, Qcur, Kcur, Vcur, nullptr, nullptr, nullptr, kq_scale, il)
|
||||
: build_attn(inp_attn, layer.wo, NULL, NULL, Qcur, Kcur, Vcur, nullptr, nullptr, nullptr, kq_scale, il);
|
||||
? build_attn(inp_attn_iswa, layer.wo, NULL, NULL, Qcur, Kcur, Vcur, nullptr, layer.attn_sinks, nullptr, kq_scale, il)
|
||||
: build_attn(inp_attn, layer.wo, NULL, NULL, Qcur, Kcur, Vcur, nullptr, layer.attn_sinks, nullptr, kq_scale, il);
|
||||
|
||||
if (attn_dynamic) {
|
||||
cur = build_dflash2_conv(*this, cur, attn_dynamic, layer.dflash_attn_conv_base, 1);
|
||||
cb(cur, "attn_conv_out", il);
|
||||
}
|
||||
|
||||
ggml_tensor * ffn_inp = ggml_add(ctx0, cur, inpL);
|
||||
cb(ffn_inp, "ffn_inp", il);
|
||||
@@ -500,6 +731,13 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
|
||||
cur = build_norm(ffn_inp, layer.ffn_norm, NULL, LLM_NORM_RMS, il);
|
||||
cb(cur, "ffn_norm", il);
|
||||
|
||||
ggml_tensor * ffn_dynamic = nullptr;
|
||||
if (layer.dflash_ffn_conv_proj) {
|
||||
ffn_dynamic = build_lora_mm(layer.dflash_ffn_conv_proj, cur);
|
||||
cur = build_dflash2_conv(*this, cur, ffn_dynamic, layer.dflash_ffn_conv_base, 0);
|
||||
cb(cur, "ffn_conv_in", il);
|
||||
}
|
||||
|
||||
cur = build_ffn(cur,
|
||||
layer.ffn_up, NULL, layer.ffn_up_s,
|
||||
layer.ffn_gate, NULL, layer.ffn_gate_s,
|
||||
@@ -508,6 +746,11 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
|
||||
LLM_FFN_SILU, LLM_FFN_PAR, il);
|
||||
cb(cur, "ffn_out", il);
|
||||
|
||||
if (ffn_dynamic) {
|
||||
cur = build_dflash2_conv(*this, cur, ffn_dynamic, layer.dflash_ffn_conv_base, 1);
|
||||
cb(cur, "ffn_conv_out", il);
|
||||
}
|
||||
|
||||
cur = ggml_add(ctx0, cur, ffn_inp);
|
||||
cb(cur, "l_out", il);
|
||||
|
||||
@@ -532,6 +775,19 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
|
||||
|
||||
cur = build_lora_mm(output, cur, output_s);
|
||||
|
||||
// DFlash2 feeds these logits to the selector, so they need the target's output
|
||||
// transforms; DFlash1 and DSpark read them through the sampler instead
|
||||
if (model.dflash_selector_hidden) {
|
||||
if (hparams.f_logit_scale != 0.0f) {
|
||||
cur = ggml_scale(ctx0, cur, hparams.f_logit_scale);
|
||||
}
|
||||
if (hparams.f_final_logit_softcapping > 0.0f) {
|
||||
cur = ggml_scale(ctx0, cur, 1.0f / hparams.f_final_logit_softcapping);
|
||||
cur = ggml_tanh(ctx0, cur);
|
||||
cur = ggml_scale(ctx0, cur, hparams.f_final_logit_softcapping);
|
||||
}
|
||||
}
|
||||
|
||||
// reduced-draft-vocab exports: scatter the draft logits to the target vocabulary via d2t
|
||||
if (model.d2t) {
|
||||
const int64_t n_draft_vocab = cur->ne[0];
|
||||
@@ -556,6 +812,10 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
|
||||
if (model.dspark_markov_w1) {
|
||||
build_dspark_markov_head(*this, model, inp_tokens);
|
||||
}
|
||||
|
||||
if (model.dflash_selector_hidden) {
|
||||
build_dflash2_selector(*this, model, inp_tokens);
|
||||
}
|
||||
}
|
||||
|
||||
// DSV4 DSpark decoder, dual-mode by batch type (see the DFlash decoder above):
|
||||
|
||||
@@ -50,7 +50,7 @@ void llama_model_gemma4::load_arch_tensors(llama_model_loader &) {
|
||||
tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, 0);
|
||||
|
||||
if (n_embd_per_layer > 0) {
|
||||
per_layer_tok_embd = create_tensor(tn(LLM_TENSOR_PER_LAYER_TOKEN_EMBD, "weight"), {n_embd_per_layer * n_layer, n_vocab}, 0);
|
||||
per_layer_tok_embd = create_tensor(tn(LLM_TENSOR_PER_LAYER_TOKEN_EMBD, "weight"), {n_embd_per_layer * n_layer, n_vocab}, TENSOR_READ_LAZY);
|
||||
per_layer_model_proj = create_tensor(tn(LLM_TENSOR_PER_LAYER_MODEL_PROJ, "weight", 0), {n_embd, n_embd_per_layer * n_layer}, 0);
|
||||
per_layer_proj_norm = create_tensor(tn(LLM_TENSOR_PER_LAYER_PROJ_NORM, "weight", 0), {n_embd_per_layer}, 0);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
// note: almost all graphs require at least sqrtf, so include cmath globally
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
|
||||
class llama_memory_hybrid_idx_context;
|
||||
|
||||
//
|
||||
// base classes
|
||||
@@ -2272,6 +2275,108 @@ struct llama_model_qwen35 : public llama_model_base {
|
||||
};
|
||||
|
||||
|
||||
struct llama_model_qwen4exp : public llama_model_base {
|
||||
llama_model_qwen4exp(const struct llama_model_params & params) : llama_model_base(params) {}
|
||||
|
||||
class llm_graph_input_qsa;
|
||||
|
||||
void load_arch_hparams(llama_model_loader & ml) override;
|
||||
void load_arch_tensors(llama_model_loader & ml) override;
|
||||
|
||||
struct graph : public llm_build_delta_net_base {
|
||||
graph(const llama_model & model, const llm_graph_params & params);
|
||||
private:
|
||||
// HC replaces every layer norm: residual is [n_embd, hc, n_tokens]
|
||||
ggml_tensor * build_hc_mix(
|
||||
ggml_tensor * x,
|
||||
ggml_tensor * w_norm,
|
||||
ggml_tensor * w_down,
|
||||
ggml_tensor * w_up,
|
||||
ggml_tensor * w_inject,
|
||||
ggml_tensor ** inject,
|
||||
int il);
|
||||
|
||||
ggml_tensor * build_hc_combine(
|
||||
ggml_tensor * residual,
|
||||
ggml_tensor * block_out,
|
||||
ggml_tensor * inject,
|
||||
int il);
|
||||
|
||||
ggml_tensor * build_layer_attn(
|
||||
llm_graph_input_attn_kv * inp_attn,
|
||||
const llama_memory_hybrid_idx_context * mctx_hyb,
|
||||
ggml_tensor * cur,
|
||||
ggml_tensor * inp_pos,
|
||||
int * sections,
|
||||
int il);
|
||||
|
||||
// dense self-attention restricted to the cells that top_k names
|
||||
ggml_tensor * build_attn_qsa(
|
||||
llm_graph_input_attn_kv * inp,
|
||||
ggml_tensor * q_cur,
|
||||
ggml_tensor * k_cur,
|
||||
ggml_tensor * v_cur,
|
||||
ggml_tensor * top_k,
|
||||
float kq_scale,
|
||||
int il);
|
||||
|
||||
// the QSA cache layout inputs do not depend on the layer, only on its compress ratio,
|
||||
// so the layers sharing a ratio share one input set
|
||||
std::map<uint32_t, llm_graph_input_qsa *> qsa_inps;
|
||||
|
||||
// QSA: token indices this layer's queries may attend to, or nullptr for dense
|
||||
ggml_tensor * build_qsa_top_k(
|
||||
const llama_memory_hybrid_idx_context * mctx_hyb,
|
||||
ggml_tensor * cur,
|
||||
ggml_tensor * inp_pos,
|
||||
ggml_tensor * kq_mask,
|
||||
int * sections,
|
||||
int il);
|
||||
|
||||
ggml_tensor * build_layer_attn_linear(
|
||||
llm_graph_input_rs * inp,
|
||||
ggml_tensor * cur,
|
||||
int il);
|
||||
|
||||
ggml_tensor * build_layer_ffn(
|
||||
ggml_tensor * cur,
|
||||
int il);
|
||||
|
||||
ggml_tensor * build_norm_gated(
|
||||
ggml_tensor * input,
|
||||
ggml_tensor * weights,
|
||||
ggml_tensor * gate,
|
||||
int layer);
|
||||
|
||||
// build_rs writes the state tensor in place, so one gather per cache tensor is reused
|
||||
std::map<ggml_tensor *, ggml_tensor *> rs_rows;
|
||||
|
||||
// one conv history per cache tensor: delta-net and PLE each have their own
|
||||
ggml_tensor * build_conv_state_at(
|
||||
llm_graph_input_rs * inp,
|
||||
ggml_tensor * conv_states_all,
|
||||
ggml_tensor * x,
|
||||
int64_t state_cols,
|
||||
int64_t channels,
|
||||
int il);
|
||||
|
||||
ggml_tensor * build_ple(
|
||||
llm_graph_input_rs * inp,
|
||||
const llama_memory_hybrid_idx_context * mctx_hyb,
|
||||
ggml_tensor * hidden,
|
||||
int il);
|
||||
|
||||
// returns pair of qkv, z
|
||||
std::pair<ggml_tensor *, ggml_tensor *> build_qkvz(
|
||||
ggml_tensor * input,
|
||||
int il);
|
||||
|
||||
const llama_model & model;
|
||||
};
|
||||
|
||||
std::unique_ptr<llm_graph_context> build_arch_graph(const llm_graph_params & params) const override;
|
||||
};
|
||||
|
||||
struct llama_model_qwen35moe : public llama_model_base {
|
||||
llama_model_qwen35moe(const struct llama_model_params & params) : llama_model_base(params) {}
|
||||
void load_arch_hparams(llama_model_loader & ml) override;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9717,6 +9717,17 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int k : {4, 8, 16, 32}) {
|
||||
for (int nrows : {1, 8, 16}) {
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {202048, nrows, 1, 1}, k));
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {151936, nrows, 1, 1}, k));
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {8192, nrows, 1, 1}, k));
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {8193, nrows, 1, 1}, k));
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {8192, nrows, 1, 1}, k, true));
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {202048, nrows, 1, 1}, k, true));
|
||||
}
|
||||
}
|
||||
|
||||
for (int k : {1, 2, 3, 7, 15}) {
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {16, 10, 10, 10}, k));
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {60, 10, 10, 10}, k));
|
||||
@@ -10454,7 +10465,13 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_perf() {
|
||||
test_cases.emplace_back(new test_argsort(GGML_TYPE_F32, {200000, 16, 1, 1}));
|
||||
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {2, 1, 1, 1}, 1));
|
||||
for (auto k : {1, 10, 40, 400}) {
|
||||
// widths around the tiling threshold
|
||||
for (auto cols : {4096, 8192, 12288, 16384, 24576, 32768, 65536, 131072}) {
|
||||
for (auto nrows : {1, 16}) {
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {cols, nrows, 1, 1}, 16));
|
||||
}
|
||||
}
|
||||
for (auto k : {1, 4, 8, 10, 16, 32, 40, 400}) {
|
||||
for (auto nrows : {1, 16}) {
|
||||
for (auto cols : {k, 1000, 65000, 200000}) {
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {cols, nrows, 1, 1}, k));
|
||||
|
||||
@@ -249,8 +249,17 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {
|
||||
|
||||
// MSA requires one indexer head per GQA (KV) head, unlike the DSA archs where the
|
||||
// indexer head count is independent of the main attention head count.
|
||||
if (arch == LLM_ARCH_QWEN4EXP) {
|
||||
ms.add_kv(LLM_KV_HYPER_CONNECTION_COUNT, uint32_t(4));
|
||||
ms.add_kv(LLM_KV_HYPER_CONNECTION_LOW_RANK, uint32_t(8));
|
||||
// without this the QSA layers fall back to dense and go uncovered
|
||||
ms.add_kv(LLM_KV_ATTENTION_COMPRESS_RATIOS, std::vector<uint32_t>(n_layer, 4));
|
||||
}
|
||||
|
||||
ms.add_kv(LLM_KV_ATTENTION_INDEXER_HEAD_COUNT, arch == LLM_ARCH_MINIMAX_M3 || arch == LLM_ARCH_DEEPSEEK4 ? n_head : uint32_t(1));
|
||||
ms.add_kv(LLM_KV_ATTENTION_INDEXER_KEY_LENGTH, uint32_t(64));
|
||||
// qwen4exp ropes indexer keys with the main rotary width, so its head can't be < n_rot
|
||||
ms.add_kv(LLM_KV_ATTENTION_INDEXER_KEY_LENGTH,
|
||||
arch == LLM_ARCH_QWEN4EXP ? n_embd_head : uint32_t(64));
|
||||
ms.add_kv(LLM_KV_ATTENTION_INDEXER_TOP_K, uint32_t(8));
|
||||
ms.add_kv(LLM_KV_ATTENTION_INDEXER_BLOCK_SIZE, uint32_t(4));
|
||||
ms.add_kv(LLM_KV_ATTENTION_INDEXER_LOCAL_BLOCKS, uint32_t(1));
|
||||
@@ -294,7 +303,7 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {
|
||||
ms.add_kv(LLM_KV_XIELU_ALPHA_P, 1.0f);
|
||||
ms.add_kv(LLM_KV_XIELU_BETA, 1.0f);
|
||||
ms.add_kv(LLM_KV_XIELU_EPS, 1.0e-7f);
|
||||
ms.add_kv(LLM_KV_SSM_INNER_SIZE, arch == LLM_ARCH_QWEN3NEXT || arch == LLM_ARCH_QWEN35 || arch == LLM_ARCH_QWEN35MOE ? 256 : 2*n_embd);
|
||||
ms.add_kv(LLM_KV_SSM_INNER_SIZE, arch == LLM_ARCH_QWEN3NEXT || arch == LLM_ARCH_QWEN35 || arch == LLM_ARCH_QWEN35MOE || arch == LLM_ARCH_QWEN4EXP ? 256 : 2*n_embd);
|
||||
ms.add_kv(LLM_KV_SSM_CONV_KERNEL, uint32_t(4));
|
||||
ms.add_kv(LLM_KV_SSM_STATE_SIZE, uint32_t(128));
|
||||
ms.add_kv(LLM_KV_SSM_TIME_STEP_RANK, n_head);
|
||||
@@ -411,6 +420,7 @@ static bool moe_mandatory(const llm_arch arch) {
|
||||
case LLM_ARCH_QWEN3NEXT:
|
||||
case LLM_ARCH_QWEN3VLMOE:
|
||||
case LLM_ARCH_QWEN35MOE:
|
||||
case LLM_ARCH_QWEN4EXP:
|
||||
case LLM_ARCH_PHIMOE:
|
||||
case LLM_ARCH_DBRX:
|
||||
case LLM_ARCH_OLMOE:
|
||||
@@ -507,7 +517,7 @@ static bool arch_supported(const llm_arch arch) {
|
||||
}
|
||||
// FIXME: these hit scheduler/view-backed-output issues with WebGPU on CI.
|
||||
#ifdef GGML_USE_WEBGPU
|
||||
if (arch == LLM_ARCH_DEEPSEEK32 || arch == LLM_ARCH_GLM_DSA || arch == LLM_ARCH_DOTS3NOTE) {
|
||||
if (arch == LLM_ARCH_DEEPSEEK32 || arch == LLM_ARCH_GLM_DSA || arch == LLM_ARCH_DOTS3NOTE || arch == LLM_ARCH_QWEN4EXP) {
|
||||
return false;
|
||||
}
|
||||
#endif // GGML_USE_WEBGPU
|
||||
|
||||
+2
-1
@@ -59,12 +59,14 @@
|
||||
| `--mmap, --no-mmap` | DEPRECATED in favor of `--load-mode`: whether to memory-map model. (if mmap disabled, slower load but may reduce pageouts if not using mlock)<br/>(env: LLAMA_ARG_MMAP) |
|
||||
| `-dio, --direct-io, -ndio, --no-direct-io` | DEPRECATED in favor of `--load-mode`: use DirectIO if available<br/>(env: LLAMA_ARG_DIO) |
|
||||
| `-lm, --load-mode MODE` | model loading mode (default: auto)<br/>- auto: mmap, unless a device does not support it<br/>- none: no special loading mode<br/>- mmap: memory-map model (if mmap disabled, slower load but may reduce pageouts if not using mlock)<br/>- mlock: force system to keep model in RAM rather than swapping or compressing<br/>- mmap+mlock: mmap + force system to keep model in RAM rather than swapping or compressing<br/>- dio: use DirectIO if available<br/><br/>(env: LLAMA_ARG_LOAD_MODE) |
|
||||
| `--tensor-read-lazy MODE` | on-demand reading of certain tensors, for example per-layer embeddings (default: auto)<br/>- on: read the rows of such tensors from disk on demand instead of keeping them resident (requires mmap)<br/>- auto: on, but only for tensors larger than 4 GiB<br/>- off: always keep them resident<br/>(env: LLAMA_ARG_TENSOR_READ_LAZY) |
|
||||
| `--numa TYPE` | attempt optimizations that help on some NUMA systems<br/>- distribute: spread execution evenly over all nodes<br/>- isolate: only spawn threads on CPUs on the node that execution started on<br/>- numactl: use the CPU map provided by numactl<br/>if run without this previously, it is recommended to drop the system page cache before using this<br/>see https://github.com/ggml-org/llama.cpp/issues/1437<br/>(env: LLAMA_ARG_NUMA) |
|
||||
| `-dev, --device <dev1,dev2,..>` | comma-separated list of devices to use for offloading (none = don't offload)<br/>use --list-devices to see a list of available devices<br/>(env: LLAMA_ARG_DEVICE) |
|
||||
| `--list-devices` | print list of available devices and exit |
|
||||
| `-ot, --override-tensor <tensor name pattern>=<buffer type>,...` | override tensor buffer type<br/>(env: LLAMA_ARG_OVERRIDE_TENSOR) |
|
||||
| `-cmoe, --cpu-moe` | keep all Mixture of Experts (MoE) weights in the CPU<br/>(env: LLAMA_ARG_CPU_MOE) |
|
||||
| `-ncmoe, --n-cpu-moe N` | keep the Mixture of Experts (MoE) weights of the first N layers in the CPU<br/>(env: LLAMA_ARG_N_CPU_MOE) |
|
||||
| `-ncffn, --n-cpu-ffn N` | keep the dense FFN weights of the first N layers in the CPU<br/>(dense models; for MoE expert weights use --n-cpu-moe)<br/>(env: LLAMA_ARG_N_CPU_FFN) |
|
||||
| `-ngl, --gpu-layers, --n-gpu-layers N` | max. number of layers to store in VRAM, either an exact number, 'auto', or 'all' (default: auto)<br/>(env: LLAMA_ARG_N_GPU_LAYERS) |
|
||||
| `-sm, --split-mode {none,layer,row,tensor}` | how to split the model across multiple GPUs, one of:<br/>- none: use one GPU only<br/>- layer (default): split layers and KV across GPUs (pipelined)<br/>- row: split weight across GPUs by rows (parallelized)<br/>- tensor: split weights and KV across GPUs (parallelized, EXPERIMENTAL)<br/>(env: LLAMA_ARG_SPLIT_MODE) |
|
||||
| `-ts, --tensor-split N0,N1,N2,...` | fraction of the model to offload to each GPU, comma-separated list of proportions, e.g. 3,1<br/>(env: LLAMA_ARG_TENSOR_SPLIT) |
|
||||
@@ -154,7 +156,6 @@
|
||||
| `-sysf, --system-prompt-file FNAME` | a file containing the system prompt (default: none) |
|
||||
| `-r, --reverse-prompt PROMPT` | halt generation at PROMPT, return control in interactive mode |
|
||||
| `-sp, --special` | special tokens output enabled (default: false) |
|
||||
| `-cnv, --conversation, -no-cnv, --no-conversation` | whether to run in conversation mode:<br/>- does not print special tokens and suffix/prefix<br/>- interactive mode is also enabled<br/>(default: auto enabled if chat template is available) |
|
||||
| `-st, --single-turn` | run conversation for a single turn only, then exit when done<br/>will not be interactive if first turn is predefined with --prompt<br/>(default: false) |
|
||||
| `-mli, --multiline-input` | allows you to write or paste multiple lines without ending each in '\' |
|
||||
| `--warmup, --no-warmup` | whether to perform warmup with an empty run (default: enabled) |
|
||||
|
||||
@@ -142,12 +142,14 @@ llama-completion.exe -m models\gemma-1.1-7b-it.Q4_K_M.gguf --ignore-eos -n -1
|
||||
| `--mmap, --no-mmap` | DEPRECATED in favor of `--load-mode`: whether to memory-map model. (if mmap disabled, slower load but may reduce pageouts if not using mlock)<br/>(env: LLAMA_ARG_MMAP) |
|
||||
| `-dio, --direct-io, -ndio, --no-direct-io` | DEPRECATED in favor of `--load-mode`: use DirectIO if available<br/>(env: LLAMA_ARG_DIO) |
|
||||
| `-lm, --load-mode MODE` | model loading mode (default: auto)<br/>- auto: mmap, unless a device does not support it<br/>- none: no special loading mode<br/>- mmap: memory-map model (if mmap disabled, slower load but may reduce pageouts if not using mlock)<br/>- mlock: force system to keep model in RAM rather than swapping or compressing<br/>- mmap+mlock: mmap + force system to keep model in RAM rather than swapping or compressing<br/>- dio: use DirectIO if available<br/><br/>(env: LLAMA_ARG_LOAD_MODE) |
|
||||
| `--tensor-read-lazy MODE` | on-demand reading of certain tensors, for example per-layer embeddings (default: auto)<br/>- on: read the rows of such tensors from disk on demand instead of keeping them resident (requires mmap)<br/>- auto: on, but only for tensors larger than 4 GiB<br/>- off: always keep them resident<br/>(env: LLAMA_ARG_TENSOR_READ_LAZY) |
|
||||
| `--numa TYPE` | attempt optimizations that help on some NUMA systems<br/>- distribute: spread execution evenly over all nodes<br/>- isolate: only spawn threads on CPUs on the node that execution started on<br/>- numactl: use the CPU map provided by numactl<br/>if run without this previously, it is recommended to drop the system page cache before using this<br/>see https://github.com/ggml-org/llama.cpp/issues/1437<br/>(env: LLAMA_ARG_NUMA) |
|
||||
| `-dev, --device <dev1,dev2,..>` | comma-separated list of devices to use for offloading (none = don't offload)<br/>use --list-devices to see a list of available devices<br/>(env: LLAMA_ARG_DEVICE) |
|
||||
| `--list-devices` | print list of available devices and exit |
|
||||
| `-ot, --override-tensor <tensor name pattern>=<buffer type>,...` | override tensor buffer type<br/>(env: LLAMA_ARG_OVERRIDE_TENSOR) |
|
||||
| `-cmoe, --cpu-moe` | keep all Mixture of Experts (MoE) weights in the CPU<br/>(env: LLAMA_ARG_CPU_MOE) |
|
||||
| `-ncmoe, --n-cpu-moe N` | keep the Mixture of Experts (MoE) weights of the first N layers in the CPU<br/>(env: LLAMA_ARG_N_CPU_MOE) |
|
||||
| `-ncffn, --n-cpu-ffn N` | keep the dense FFN weights of the first N layers in the CPU<br/>(dense models; for MoE expert weights use --n-cpu-moe)<br/>(env: LLAMA_ARG_N_CPU_FFN) |
|
||||
| `-ngl, --gpu-layers, --n-gpu-layers N` | max. number of layers to store in VRAM, either an exact number, 'auto', or 'all' (default: auto)<br/>(env: LLAMA_ARG_N_GPU_LAYERS) |
|
||||
| `-sm, --split-mode {none,layer,row,tensor}` | how to split the model across multiple GPUs, one of:<br/>- none: use one GPU only<br/>- layer (default): split layers and KV across GPUs (pipelined)<br/>- row: split weight across GPUs by rows (parallelized)<br/>- tensor: split weights and KV across GPUs (parallelized, EXPERIMENTAL)<br/>(env: LLAMA_ARG_SPLIT_MODE) |
|
||||
| `-ts, --tensor-split N0,N1,N2,...` | fraction of the model to offload to each GPU, comma-separated list of proportions, e.g. 3,1<br/>(env: LLAMA_ARG_TENSOR_SPLIT) |
|
||||
|
||||
@@ -122,7 +122,7 @@ static bool try_parse_ftype(const std::string & ftype_str_in, llama_ftype & ftyp
|
||||
static void usage(const char * executable) {
|
||||
printf("usage: %s [--help] [--allow-requantize] [--leave-output-tensor] [--pure] [--imatrix] [--include-weights]\n", executable);
|
||||
printf(" [--exclude-weights] [--output-tensor-type] [--token-embedding-type] [--tensor-type] [--tensor-type-file]\n");
|
||||
printf(" [--prune-layers] [--keep-split] [--override-kv] [--dry-run]\n");
|
||||
printf(" [--prune-layers] [--keep-split] [--override-kv] [--dry-run] [--max-buffer-size]\n");
|
||||
printf(" model-f32.gguf [model-quant.gguf] type [nthreads]\n\n");
|
||||
printf(" --allow-requantize\n");
|
||||
printf(" allow requantizing tensors that have already been quantized\n");
|
||||
@@ -161,7 +161,10 @@ static void usage(const char * executable) {
|
||||
printf(" WARNING: this is an advanced option, use with care.\n");
|
||||
printf(" --dry-run\n");
|
||||
printf(" calculate and show the final quantization size without performing quantization\n");
|
||||
printf(" example: llama-quantize --dry-run model-f32.gguf Q4_K\n\n");
|
||||
printf(" example: llama-quantize --dry-run model-f32.gguf Q4_K\n");
|
||||
printf(" --max-buffer-size MiB\n");
|
||||
printf(" max amount of tensor rows kept in memory while quantizing one tensor (default: 8192)\n");
|
||||
printf(" lower it to quantize models with very large tensors on a machine with little RAM\n\n");
|
||||
printf("note: --include-weights and --exclude-weights cannot be used together\n\n");
|
||||
printf("-----------------------------------------------------------------------------\n");
|
||||
printf(" allowed quantization types\n");
|
||||
@@ -467,6 +470,16 @@ int llama_quantize(int argc, char ** argv) {
|
||||
}
|
||||
} else if (strcmp(argv[arg_idx], "--keep-split") == 0) {
|
||||
params.keep_split = true;
|
||||
} else if (strcmp(argv[arg_idx], "--max-buffer-size") == 0) {
|
||||
if (arg_idx == argc-1) {
|
||||
usage(argv[0]);
|
||||
}
|
||||
const int mib = atoi(argv[++arg_idx]);
|
||||
if (mib <= 0) {
|
||||
fprintf(stderr, "%s: invalid --max-buffer-size '%s'\n", __func__, argv[arg_idx]);
|
||||
return 1;
|
||||
}
|
||||
params.max_buf_size = (size_t) mib * 1024 * 1024;
|
||||
} else {
|
||||
usage(argv[0]);
|
||||
}
|
||||
|
||||
@@ -76,12 +76,14 @@ For the full list of features, please refer to [server's changelog](https://gith
|
||||
| `--mmap, --no-mmap` | DEPRECATED in favor of `--load-mode`: whether to memory-map model. (if mmap disabled, slower load but may reduce pageouts if not using mlock)<br/>(env: LLAMA_ARG_MMAP) |
|
||||
| `-dio, --direct-io, -ndio, --no-direct-io` | DEPRECATED in favor of `--load-mode`: use DirectIO if available<br/>(env: LLAMA_ARG_DIO) |
|
||||
| `-lm, --load-mode MODE` | model loading mode (default: auto)<br/>- auto: mmap, unless a device does not support it<br/>- none: no special loading mode<br/>- mmap: memory-map model (if mmap disabled, slower load but may reduce pageouts if not using mlock)<br/>- mlock: force system to keep model in RAM rather than swapping or compressing<br/>- mmap+mlock: mmap + force system to keep model in RAM rather than swapping or compressing<br/>- dio: use DirectIO if available<br/><br/>(env: LLAMA_ARG_LOAD_MODE) |
|
||||
| `--tensor-read-lazy MODE` | on-demand reading of certain tensors, for example per-layer embeddings (default: auto)<br/>- on: read the rows of such tensors from disk on demand instead of keeping them resident (requires mmap)<br/>- auto: on, but only for tensors larger than 4 GiB<br/>- off: always keep them resident<br/>(env: LLAMA_ARG_TENSOR_READ_LAZY) |
|
||||
| `--numa TYPE` | attempt optimizations that help on some NUMA systems<br/>- distribute: spread execution evenly over all nodes<br/>- isolate: only spawn threads on CPUs on the node that execution started on<br/>- numactl: use the CPU map provided by numactl<br/>if run without this previously, it is recommended to drop the system page cache before using this<br/>see https://github.com/ggml-org/llama.cpp/issues/1437<br/>(env: LLAMA_ARG_NUMA) |
|
||||
| `-dev, --device <dev1,dev2,..>` | comma-separated list of devices to use for offloading (none = don't offload)<br/>use --list-devices to see a list of available devices<br/>(env: LLAMA_ARG_DEVICE) |
|
||||
| `--list-devices` | print list of available devices and exit |
|
||||
| `-ot, --override-tensor <tensor name pattern>=<buffer type>,...` | override tensor buffer type<br/>(env: LLAMA_ARG_OVERRIDE_TENSOR) |
|
||||
| `-cmoe, --cpu-moe` | keep all Mixture of Experts (MoE) weights in the CPU<br/>(env: LLAMA_ARG_CPU_MOE) |
|
||||
| `-ncmoe, --n-cpu-moe N` | keep the Mixture of Experts (MoE) weights of the first N layers in the CPU<br/>(env: LLAMA_ARG_N_CPU_MOE) |
|
||||
| `-ncffn, --n-cpu-ffn N` | keep the dense FFN weights of the first N layers in the CPU<br/>(dense models; for MoE expert weights use --n-cpu-moe)<br/>(env: LLAMA_ARG_N_CPU_FFN) |
|
||||
| `-ngl, --gpu-layers, --n-gpu-layers N` | max. number of layers to store in VRAM, either an exact number, 'auto', or 'all' (default: auto)<br/>(env: LLAMA_ARG_N_GPU_LAYERS) |
|
||||
| `-sm, --split-mode {none,layer,row,tensor}` | how to split the model across multiple GPUs, one of:<br/>- none: use one GPU only<br/>- layer (default): split layers and KV across GPUs (pipelined)<br/>- row: split weight across GPUs by rows (parallelized)<br/>- tensor: split weights and KV across GPUs (parallelized, EXPERIMENTAL)<br/>(env: LLAMA_ARG_SPLIT_MODE) |
|
||||
| `-ts, --tensor-split N0,N1,N2,...` | fraction of the model to offload to each GPU, comma-separated list of proportions, e.g. 3,1<br/>(env: LLAMA_ARG_TENSOR_SPLIT) |
|
||||
@@ -161,6 +163,7 @@ For the full list of features, please refer to [server's changelog](https://gith
|
||||
| -------- | ----------- |
|
||||
| `-lcs, --lookup-cache-static FNAME` | path to static lookup cache to use for lookup decoding (not updated by generation) |
|
||||
| `-lcd, --lookup-cache-dynamic FNAME` | path to dynamic lookup cache to use for lookup decoding (updated by generation) |
|
||||
| `--kv-unified-per-slot N` | context limit per parallel slot (default: unset, behavior unchanged).<br/>when set without -c/--ctx-size, the shared KV pool is sized to n_parallel*N<br/>(env: LLAMA_ARG_KV_UNIFIED_PER_SLOT) |
|
||||
| `-ctxcp, --ctx-checkpoints, --swa-checkpoints N` | max number of context checkpoints to create per slot (default: 32)[(more info)](https://github.com/ggml-org/llama.cpp/pull/15293)<br/>(env: LLAMA_ARG_CTX_CHECKPOINTS) |
|
||||
| `-cms, --checkpoint-min-step N` | minimum spacing between context checkpoints in tokens (default: 8192, 0 = no minimum)<br/>(env: LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT) |
|
||||
| `-cram, --cache-ram N` | set the maximum cache size in MiB (default: 8192, -1 - no limit, 0 - disable)[(more info)](https://github.com/ggml-org/llama.cpp/pull/16391)<br/>(env: LLAMA_ARG_CACHE_RAM) |
|
||||
|
||||
@@ -1208,10 +1208,31 @@ private:
|
||||
|
||||
const int n_ctx_train = llama_model_n_ctx_train(model_tgt);
|
||||
|
||||
int n_ctx_slot = llama_n_ctx_seq(ctx_tgt);
|
||||
if (n_ctx_slot > n_ctx_train) {
|
||||
SRV_WRN("the slot context (%d) exceeds the training context of the model (%d) - capping\n", n_ctx_slot, n_ctx_train);
|
||||
n_ctx_slot = n_ctx_train;
|
||||
{
|
||||
// note: the capping itself is done in n_ctx_slot(), here we only report it
|
||||
const int n_ctx_seq = llama_n_ctx_seq(ctx_tgt);
|
||||
|
||||
if (params_base.kv_unified_per_slot > 0) {
|
||||
if (n_ctx_seq > params_base.kv_unified_per_slot) {
|
||||
SRV_INF("capping per-slot context (%d) to --kv-unified-per-slot (%d)\n",
|
||||
n_ctx_seq, params_base.kv_unified_per_slot);
|
||||
} else if (params_base.kv_unified_per_slot > n_ctx_seq) {
|
||||
// cap is above the per-slot pool capacity, so it can never bind
|
||||
SRV_WRN(
|
||||
"--kv-unified-per-slot (%d) exceeds the per-slot pool capacity (%d) - cap has no effect, "
|
||||
"slots are limited to %d (raise the KV pool with -c, or unset -c to size it to "
|
||||
"n_parallel * kv_unified_per_slot)\n",
|
||||
params_base.kv_unified_per_slot, n_ctx_seq, n_ctx_seq);
|
||||
}
|
||||
}
|
||||
|
||||
const int n_ctx_capped = params_base.kv_unified_per_slot > 0 ?
|
||||
std::min(n_ctx_seq, params_base.kv_unified_per_slot) : n_ctx_seq;
|
||||
|
||||
if (n_ctx_capped > n_ctx_train) {
|
||||
SRV_WRN("the slot context (%d) exceeds the training context of the model (%d) - capping\n",
|
||||
n_ctx_capped, n_ctx_train);
|
||||
}
|
||||
}
|
||||
|
||||
slots.clear();
|
||||
@@ -1227,7 +1248,7 @@ private:
|
||||
|
||||
// setup slots
|
||||
SRV_INF("initializing, n_slots = %d, n_ctx_slot = %d, kv_unified = '%s'\n",
|
||||
params_base.n_parallel, n_ctx_slot, params_base.kv_unified ? "true" : "false");
|
||||
params_base.n_parallel, n_ctx_slot(), params_base.kv_unified ? "true" : "false");
|
||||
|
||||
// initialize slots
|
||||
for (int i = 0; i < params_base.n_parallel; i++) {
|
||||
@@ -1271,7 +1292,7 @@ private:
|
||||
slot.ctx_dft = ctx_dft;
|
||||
slot.mem.init(ctx_tgt, ctx_dft);
|
||||
slot.spec = spec.get();
|
||||
slot.n_ctx = n_ctx_slot;
|
||||
slot.n_ctx = n_ctx_slot();
|
||||
|
||||
slot.mctx = mctx;
|
||||
slot.prompt.tokens.has_mtmd = mctx != nullptr;
|
||||
@@ -3975,8 +3996,15 @@ private:
|
||||
});
|
||||
}
|
||||
|
||||
int get_slot_n_ctx() {
|
||||
return slots.back().n_ctx;
|
||||
// context size of a single slot, capped by --kv-unified-per-slot and by the training context of the model
|
||||
int n_ctx_slot() const {
|
||||
int res = llama_n_ctx_seq(ctx_tgt);
|
||||
|
||||
if (params_base.kv_unified_per_slot > 0) {
|
||||
res = std::min(res, params_base.kv_unified_per_slot);
|
||||
}
|
||||
|
||||
return std::min(res, llama_model_n_ctx_train(model_tgt));
|
||||
}
|
||||
|
||||
server_response_reader get_response_reader() {
|
||||
@@ -4142,7 +4170,7 @@ server_context_meta server_context::get_meta() const {
|
||||
/* has_inp_audio */ impl->chat_params.allow_audio,
|
||||
/* has_inp_video */ impl->chat_params.allow_video,
|
||||
/* json_ui_settings */ impl->json_ui_settings,
|
||||
/* slot_n_ctx */ impl->get_slot_n_ctx(),
|
||||
/* slot_n_ctx */ impl->n_ctx_slot(),
|
||||
/* pooling_type */ llama_pooling_type(impl->ctx_tgt),
|
||||
|
||||
/* chat_params */ impl->chat_params,
|
||||
|
||||
@@ -157,6 +157,18 @@ int llama_server(common_params & params, int argc, char ** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
// size the KV pool from --kv-unified-per-slot, unless the user pinned it with -c
|
||||
// or with -c 0 for max context
|
||||
const bool ctx_pool_auto_sized = params.kv_unified_per_slot > 0 &&
|
||||
params.n_ctx == 0 &&
|
||||
(uint32_t) params.fit_params_min_ctx != UINT32_MAX;
|
||||
|
||||
if (ctx_pool_auto_sized) {
|
||||
params.n_ctx = params.n_parallel * params.kv_unified_per_slot;
|
||||
SRV_INF("--kv-unified-per-slot: sizing KV pool to n_parallel * kv_unified_per_slot = %d * %d = %d\n", params.n_parallel,
|
||||
params.kv_unified_per_slot, params.n_ctx);
|
||||
}
|
||||
|
||||
// for consistency between server router mode and single-model mode, we set the same model name as alias
|
||||
auto model_name = params.model.get_name();
|
||||
if (params.model_alias.empty() && !model_name.empty()) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Eye, Mic, Video } from '@lucide/svelte';
|
||||
import { MODALITY_ICONS, MODALITY_LABELS } from '$lib/constants';
|
||||
import { ModelModality } from '$lib/enums';
|
||||
|
||||
interface Props {
|
||||
@@ -8,29 +8,22 @@
|
||||
}
|
||||
|
||||
let { class: className = '', modalities }: Props = $props();
|
||||
|
||||
const shownModalities = [ModelModality.VISION, ModelModality.AUDIO, ModelModality.VIDEO] as const;
|
||||
|
||||
let visible = $derived(shownModalities.filter((modality) => modalities.includes(modality)));
|
||||
</script>
|
||||
|
||||
{#each modalities as modality (modality)}
|
||||
{#if modality === ModelModality.VISION || modality === ModelModality.AUDIO || modality === ModelModality.VIDEO}
|
||||
<span
|
||||
class={[
|
||||
'inline-flex items-center gap-1 rounded-md bg-muted px-2 py-1 text-xs font-medium',
|
||||
className
|
||||
]}
|
||||
>
|
||||
{#if modality === ModelModality.VISION}
|
||||
<Eye class="h-3 w-3" />
|
||||
{#each visible as modality (modality)}
|
||||
{@const ModalityIcon = MODALITY_ICONS[modality]}
|
||||
<span
|
||||
class={[
|
||||
'inline-flex items-center gap-1 rounded-md bg-muted px-2 py-1 text-xs font-medium',
|
||||
className
|
||||
]}
|
||||
>
|
||||
<ModalityIcon class="h-3 w-3" />
|
||||
|
||||
Vision (Image)
|
||||
{:else if modality === ModelModality.VIDEO}
|
||||
<Video class="h-3 w-3" />
|
||||
|
||||
Vision (Video)
|
||||
{:else}
|
||||
<Mic class="h-3 w-3" />
|
||||
|
||||
Audio
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
{MODALITY_LABELS[modality]}
|
||||
</span>
|
||||
{/each}
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
FileExtensionText,
|
||||
KeyboardKey,
|
||||
MimeTypeText,
|
||||
SpecialFileType
|
||||
SpecialFileType,
|
||||
ToolSource
|
||||
} from '$lib/enums';
|
||||
import { useChatFormPickers } from '$lib/hooks/use-chat-form-pickers.svelte';
|
||||
import {
|
||||
@@ -73,7 +74,6 @@
|
||||
disabled?: boolean;
|
||||
isLoading?: boolean;
|
||||
placeholder?: string;
|
||||
showMcpPromptButton?: boolean;
|
||||
showAddButton?: boolean;
|
||||
showModelSelector?: boolean;
|
||||
|
||||
@@ -103,7 +103,6 @@
|
||||
onValueChange,
|
||||
placeholder = 'Type a message...',
|
||||
showAddButton = true,
|
||||
showMcpPromptButton = false,
|
||||
showModelSelector = true,
|
||||
uploadedFiles = $bindable([]),
|
||||
value = $bindable('')
|
||||
@@ -152,9 +151,18 @@
|
||||
getServerHome: () => toolsStore.serverHome ?? null,
|
||||
getShowModelSelector: () => showModelSelector,
|
||||
getValue: () => value,
|
||||
hasCwdTools: () => toolsStore.hasEnabledCwdTools,
|
||||
hasPrompts: () =>
|
||||
mcpStore.hasPromptsCapability(conversationsStore.preferences.getAllMcpServerOverrides()),
|
||||
hasCwdTools: () => conversationsStore.preferences.hasEnabledCwdTools(),
|
||||
// policy-aware, same rule as the agentic flow: MCP category on and at
|
||||
// least one globally-enabled server whose group key is not disabled
|
||||
hasPrompts: () => {
|
||||
const prefs = conversationsStore.preferences;
|
||||
|
||||
if (!prefs.isCategoryEnabled(ToolSource.MCP)) return false;
|
||||
|
||||
return mcpStore
|
||||
.getServers()
|
||||
.some((s) => s.enabled && prefs.isServerToolsEnabled(s.id) && s.url.trim());
|
||||
},
|
||||
openModelSelector: () => chatFormActionsRef?.openModelSelector(),
|
||||
setCaretOffset: (offset) => inputRef?.setCaretOffset(offset),
|
||||
setValue: (v) => {
|
||||
@@ -620,8 +628,6 @@
|
||||
isReasoning={chatStore.isReasoning}
|
||||
{isRecording}
|
||||
onFileUpload={handleFileUpload}
|
||||
onMcpPromptClick={showMcpPromptButton ? () => pickers.openPromptPicker() : undefined}
|
||||
onMcpResourcesClick={() => (isResourceDialogOpen = true)}
|
||||
onMcpSettingsClick={() => (isMcpServersDialogOpen = true)}
|
||||
onMicClick={handleMicClick}
|
||||
{onStop}
|
||||
@@ -635,7 +641,7 @@
|
||||
|
||||
<ContextGaugePopup />
|
||||
|
||||
{#if toolsStore.hasEnabledCwdTools}
|
||||
{#if conversationsStore.preferences.hasEnabledCwdTools()}
|
||||
<ChatFormCurrentWorkingDirectory
|
||||
bind:query={pickers.workingDirectoryQuery}
|
||||
customAnchor={mentionAnchor}
|
||||
|
||||
+37
-47
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { File, MessageSquare, Plus } from '@lucide/svelte';
|
||||
import { File, Image, MessageSquare, Mic, Plus, Video } from '@lucide/svelte';
|
||||
import { ChatFormActionAddToolsSubmenu, McpLogo } from '$lib/components/app';
|
||||
import { buttonVariants } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
@@ -8,10 +8,10 @@
|
||||
import {
|
||||
ATTACHMENT_FILE_ITEMS,
|
||||
ATTACHMENT_TOOLTIP_TEXT,
|
||||
ICON_CLASS_DEFAULT,
|
||||
TOOLTIP_DELAY_DURATION
|
||||
ICON_CLASS_DEFAULT
|
||||
} from '$lib/constants';
|
||||
import { getChatFormActionsContext } from '$lib/contexts';
|
||||
import { AttachmentAction, AttachmentItemEnabledWhen } from '$lib/enums';
|
||||
import { useAttachmentMenu } from '$lib/hooks/use-attachment-menu.svelte';
|
||||
|
||||
interface Props {
|
||||
@@ -30,21 +30,29 @@
|
||||
const attachmentMenu = useAttachmentMenu(
|
||||
() => ({
|
||||
hasAudioModality: chatFormActions.hasAudioModality,
|
||||
hasMcpPromptsSupport: chatFormActions.hasMcpPromptsSupport,
|
||||
hasMcpResourcesSupport: chatFormActions.hasMcpResourcesSupport,
|
||||
hasVideoModality: chatFormActions.hasVideoModality,
|
||||
hasVisionModality: chatFormActions.hasVisionModality
|
||||
}),
|
||||
() => ({
|
||||
onFileUpload: chatFormActions.onFileUpload,
|
||||
onMcpPromptClick: chatFormActions.onMcpPromptClick,
|
||||
onMcpResourcesClick: chatFormActions.onMcpResourcesClick,
|
||||
onSystemPromptClick: chatFormActions.onSystemPromptClick
|
||||
}),
|
||||
() => {
|
||||
dropdownOpen = false;
|
||||
}
|
||||
);
|
||||
|
||||
const FILE_MODALITY_ICONS: Record<string, { icon: typeof Image; label: string }> = {
|
||||
[AttachmentItemEnabledWhen.HAS_AUDIO_MODALITY]: { icon: Mic, label: 'Audio' },
|
||||
[AttachmentItemEnabledWhen.HAS_VIDEO_MODALITY]: { icon: Video, label: 'Video' },
|
||||
[AttachmentItemEnabledWhen.HAS_VISION_MODALITY]: { icon: Image, label: 'Vision' }
|
||||
};
|
||||
|
||||
const supportedModalities = $derived.by(() =>
|
||||
ATTACHMENT_FILE_ITEMS.filter((item) => attachmentMenu.isItemEnabled(item.enabledWhen))
|
||||
.map((item) => FILE_MODALITY_ICONS[item.enabledWhen ?? ''])
|
||||
.filter((modality) => modality !== undefined)
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="flex items-center gap-1 {className}">
|
||||
@@ -84,50 +92,32 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
|
||||
<File class={ICON_CLASS_DEFAULT} />
|
||||
<DropdownMenu.Item
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={() => attachmentMenu.callbacks[AttachmentAction.FILE_UPLOAD]()}
|
||||
>
|
||||
<File class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span class="flex min-w-0 items-center gap-2">
|
||||
<span>Add files</span>
|
||||
</DropdownMenu.SubTrigger>
|
||||
|
||||
<DropdownMenu.SubContent class="w-48">
|
||||
{#each ATTACHMENT_FILE_ITEMS as item (item.id)}
|
||||
{@const enabled = attachmentMenu.isItemEnabled(item.enabledWhen)}
|
||||
{#if enabled}
|
||||
<DropdownMenu.Item
|
||||
class="{item.class ?? ''} flex cursor-pointer items-center gap-2"
|
||||
onclick={() => attachmentMenu.callbacks[item.action]()}
|
||||
>
|
||||
<item.icon class={ICON_CLASS_DEFAULT} />
|
||||
{#if supportedModalities.length > 0}
|
||||
<span class="flex items-center gap-0.75 text-muted-foreground">
|
||||
{#each supportedModalities as modality (modality.label)}
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<modality.icon class="size-2.75" />
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<span>{item.label}</span>
|
||||
</DropdownMenu.Item>
|
||||
{:else if item.disabledTooltip}
|
||||
<Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
|
||||
<Tooltip.Trigger tabindex={-1}>
|
||||
{#snippet child({ props })}
|
||||
<div {...props} class="cursor-default">
|
||||
<DropdownMenu.Item
|
||||
class="{item.class ?? ''} flex items-center gap-2"
|
||||
disabled
|
||||
>
|
||||
<item.icon class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>{item.label}</span>
|
||||
</DropdownMenu.Item>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content side="right">
|
||||
<p>{item.disabledTooltip}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/if}
|
||||
{/each}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
<Tooltip.Content>
|
||||
<p>{modality.label}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { FolderOpen, Server, Zap } from '@lucide/svelte';
|
||||
import { McpLogo } from '$lib/components/app';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { getChatFormActionsContext } from '$lib/contexts';
|
||||
|
||||
const chatFormActions = getChatFormActionsContext();
|
||||
|
||||
function handleServersClick() {
|
||||
chatFormActions.onMcpSettingsClick?.();
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
|
||||
<McpLogo class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>MCP</span>
|
||||
</DropdownMenu.SubTrigger>
|
||||
|
||||
<DropdownMenu.SubContent class="w-48">
|
||||
<DropdownMenu.Item class="flex cursor-pointer items-center gap-2" onclick={handleServersClick}>
|
||||
<Server class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>Servers</span>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
{#if chatFormActions.hasMcpPromptsSupport}
|
||||
<DropdownMenu.Item
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={chatFormActions.onMcpPromptClick}
|
||||
>
|
||||
<Zap class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>Prompts</span>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
|
||||
{#if chatFormActions.hasMcpResourcesSupport}
|
||||
<DropdownMenu.Item
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={chatFormActions.onMcpResourcesClick}
|
||||
>
|
||||
<FolderOpen class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>Resources</span>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
+59
-61
@@ -8,70 +8,68 @@
|
||||
const reasoning = useReasoningMenu();
|
||||
</script>
|
||||
|
||||
{#if reasoning.modelSupportsThinking}
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
|
||||
{#if reasoning.thinkingEnabled}
|
||||
<Lightbulb class="{ICON_CLASS_DEFAULT} shrink-0 text-amber-400" />
|
||||
{:else if reasoning.isOff}
|
||||
<LightbulbOff class="{ICON_CLASS_DEFAULT} shrink-0 text-muted-foreground" />
|
||||
{:else}
|
||||
<Lightbulb class="{ICON_CLASS_DEFAULT} shrink-0 text-muted-foreground" />
|
||||
{/if}
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
|
||||
{#if reasoning.isReasoningActive}
|
||||
<Lightbulb class="{ICON_CLASS_DEFAULT} shrink-0 text-amber-400" />
|
||||
{:else if reasoning.isOff}
|
||||
<LightbulbOff class="{ICON_CLASS_DEFAULT} shrink-0 text-muted-foreground" />
|
||||
{:else}
|
||||
<Lightbulb class="{ICON_CLASS_DEFAULT} shrink-0 text-muted-foreground" />
|
||||
{/if}
|
||||
|
||||
<span
|
||||
class="text-sm inline-flex gap-2 {!reasoning.thinkingEnabled
|
||||
? 'text-muted-foreground'
|
||||
: ''}"
|
||||
>
|
||||
Reasoning
|
||||
|
||||
<span class="capitalize text-muted-foreground">
|
||||
{reasoning.currentEffort}
|
||||
</span>
|
||||
</span>
|
||||
</DropdownMenu.SubTrigger>
|
||||
|
||||
<DropdownMenu.SubContent
|
||||
class="w-60 bg-popover p-1.5 text-popover-foreground shadow-md outline-none"
|
||||
<span
|
||||
class="text-sm inline-flex gap-2 {!reasoning.isReasoningActive
|
||||
? 'text-muted-foreground'
|
||||
: ''}"
|
||||
>
|
||||
{#each reasoning.levels as level (level.value)}
|
||||
{@const tokenLabel = reasoning.tokenLabel(level)}
|
||||
<DropdownMenu.Item
|
||||
class="flex w-full cursor-pointer items-center gap-3 rounded-md px-2 py-1.75 text-left text-sm transition-colors hover:bg-accent {reasoning.isSelected(
|
||||
level
|
||||
)
|
||||
? 'bg-accent'
|
||||
: ''}"
|
||||
onclick={() => reasoning.select(level)}
|
||||
>
|
||||
{#if reasoning.isSelected(level)}
|
||||
<Check class="{ICON_CLASS_DEFAULT} shrink-0 text-foreground" />
|
||||
{:else}
|
||||
<div class="{ICON_CLASS_DEFAULT} shrink-0"></div>
|
||||
{/if}
|
||||
Reasoning
|
||||
|
||||
<span class="flex-1">{level.label}</span>
|
||||
<span class="capitalize text-muted-foreground">
|
||||
{reasoning.currentEffort}
|
||||
</span>
|
||||
</span>
|
||||
</DropdownMenu.SubTrigger>
|
||||
|
||||
{#if tokenLabel}
|
||||
<span class="text-[11px] text-muted-foreground opacity-60">
|
||||
{tokenLabel}
|
||||
</span>
|
||||
{/if}
|
||||
<DropdownMenu.SubContent
|
||||
class="w-60 bg-popover p-1.5 text-popover-foreground shadow-md outline-none"
|
||||
>
|
||||
{#each reasoning.levels as level (level.value)}
|
||||
{@const tokenLabel = reasoning.tokenLabel(level)}
|
||||
<DropdownMenu.Item
|
||||
class="flex w-full cursor-pointer items-center gap-3 rounded-md px-2 py-1.75 text-left text-sm transition-colors hover:bg-accent {reasoning.isSelected(
|
||||
level
|
||||
)
|
||||
? 'bg-accent'
|
||||
: ''}"
|
||||
onclick={() => reasoning.select(level)}
|
||||
>
|
||||
{#if reasoning.isSelected(level)}
|
||||
<Check class="{ICON_CLASS_DEFAULT} shrink-0 text-foreground" />
|
||||
{:else}
|
||||
<div class="{ICON_CLASS_DEFAULT} shrink-0"></div>
|
||||
{/if}
|
||||
|
||||
{#if level.hasInfo}
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<Info class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
</Tooltip.Trigger>
|
||||
<span class="flex-1">{level.label}</span>
|
||||
|
||||
<Tooltip.Content side="left">
|
||||
<p>Maximum reasoning effort with extended context usage</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/if}
|
||||
</DropdownMenu.Item>
|
||||
{/each}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
{/if}
|
||||
{#if tokenLabel}
|
||||
<span class="text-[11px] text-muted-foreground opacity-60">
|
||||
{tokenLabel}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if level.hasInfo}
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<Info class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content side="left">
|
||||
<p>Maximum reasoning effort with extended context usage</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/if}
|
||||
</DropdownMenu.Item>
|
||||
{/each}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
|
||||
+61
-145
@@ -1,18 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { File, FolderOpen, MessageSquare, Zap } from '@lucide/svelte';
|
||||
import {
|
||||
Check,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
File,
|
||||
Lightbulb,
|
||||
LightbulbOff,
|
||||
MessageSquare,
|
||||
PencilRuler
|
||||
} from '@lucide/svelte';
|
||||
import { McpLogo } from '$lib/components/app';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import * as Collapsible from '$lib/components/ui/collapsible';
|
||||
import * as Sheet from '$lib/components/ui/sheet';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import {
|
||||
ATTACHMENT_FILE_ITEMS,
|
||||
@@ -20,12 +20,11 @@
|
||||
TOOLTIP_DELAY_DURATION
|
||||
} from '$lib/constants';
|
||||
import { getChatFormActionsContext } from '$lib/contexts';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
import { AttachmentAction } from '$lib/enums/attachment.enums';
|
||||
import { useAttachmentMenu } from '$lib/hooks/use-attachment-menu.svelte';
|
||||
import { useReasoningMenu } from '$lib/hooks/use-reasoning-menu.svelte';
|
||||
import { useToolsPanel } from '$lib/hooks/use-tools-panel.svelte';
|
||||
import { conversationsStore, mcpStore } from '$lib/stores';
|
||||
import type { ToolGroup } from '$lib/types';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
@@ -38,23 +37,18 @@
|
||||
const chatFormActions = getChatFormActionsContext();
|
||||
|
||||
let sheetOpen = $state(false);
|
||||
let reasoningExpanded = $state(false);
|
||||
let filesExpanded = $state(true);
|
||||
let reasoningExpanded = $state(false);
|
||||
let toolsExpanded = $state(false);
|
||||
let mcpExpanded = $state(false);
|
||||
|
||||
const attachmentMenu = useAttachmentMenu(
|
||||
() => ({
|
||||
hasAudioModality: chatFormActions.hasAudioModality,
|
||||
hasMcpPromptsSupport: chatFormActions.hasMcpPromptsSupport,
|
||||
hasMcpResourcesSupport: chatFormActions.hasMcpResourcesSupport,
|
||||
hasVideoModality: chatFormActions.hasVideoModality,
|
||||
hasVisionModality: chatFormActions.hasVisionModality
|
||||
}),
|
||||
() => ({
|
||||
onFileUpload: chatFormActions.onFileUpload,
|
||||
onMcpPromptClick: chatFormActions.onMcpPromptClick,
|
||||
onMcpResourcesClick: chatFormActions.onMcpResourcesClick,
|
||||
onSystemPromptClick: chatFormActions.onSystemPromptClick
|
||||
}),
|
||||
() => {
|
||||
@@ -70,8 +64,6 @@
|
||||
|
||||
const sheetItemRowClass =
|
||||
'flex w-full items-center justify-between gap-2 rounded-md px-3 py-2 text-left text-sm transition-colors hover:bg-accent';
|
||||
|
||||
let mcpServers = $derived(mcpStore.getServers());
|
||||
</script>
|
||||
|
||||
<div class="flex items-center gap-1 {className}">
|
||||
@@ -194,80 +186,15 @@
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
|
||||
<Collapsible.Root onOpenChange={(open) => (mcpExpanded = open)} open={mcpExpanded}>
|
||||
<Collapsible.Trigger class={sheetItemClass}>
|
||||
{#if mcpExpanded}
|
||||
<ChevronDown class="{ICON_CLASS_DEFAULT} shrink-0" />
|
||||
{:else}
|
||||
<ChevronRight class="{ICON_CLASS_DEFAULT} shrink-0" />
|
||||
{/if}
|
||||
<button
|
||||
class={sheetItemClass}
|
||||
onclick={() => attachmentMenu.callbacks[AttachmentAction.SYSTEM_PROMPT_CLICK]()}
|
||||
type="button"
|
||||
>
|
||||
<MessageSquare class="{ICON_CLASS_DEFAULT} shrink-0" />
|
||||
|
||||
<McpLogo class="inline {ICON_CLASS_DEFAULT} shrink-0" />
|
||||
|
||||
<span class="flex-1">MCP Servers</span>
|
||||
|
||||
<span class="text-xs text-muted-foreground">
|
||||
{mcpServers.length} server{mcpServers.length !== 1 ? 's' : ''}
|
||||
</span>
|
||||
</Collapsible.Trigger>
|
||||
|
||||
<Collapsible.Content>
|
||||
<div class="flex flex-col gap-0.5 pl-4">
|
||||
{#each mcpServers as server (server.id)}
|
||||
{@const healthState = mcpStore.getHealthCheckState(server.id)}
|
||||
{@const hasError = healthState.status === HealthCheckStatus.ERROR}
|
||||
{@const displayName = mcpStore.getServerLabel(server)}
|
||||
{@const faviconUrl = mcpStore.getServerFavicon(server.id)}
|
||||
{@const isEnabled = conversationsStore.preferences.isMcpServerEnabledForChat(
|
||||
server.id
|
||||
)}
|
||||
|
||||
<button
|
||||
class={sheetItemRowClass}
|
||||
disabled={hasError}
|
||||
onclick={() =>
|
||||
!hasError && conversationsStore.preferences.toggleMcpServerForChat(server.id)}
|
||||
type="button"
|
||||
>
|
||||
<div class="flex min-w-0 flex-1 items-center gap-2">
|
||||
{#if faviconUrl}
|
||||
<img
|
||||
alt=""
|
||||
class="{ICON_CLASS_DEFAULT} shrink-0 rounded-sm"
|
||||
onerror={(e) => {
|
||||
(e.currentTarget as HTMLImageElement).style.display = 'none';
|
||||
}}
|
||||
src={faviconUrl}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<span class="min-w-0 truncate text-sm">{displayName}</span>
|
||||
</div>
|
||||
|
||||
{#if hasError}
|
||||
<span
|
||||
class="shrink-0 rounded bg-destructive/15 px-1.5 py-0.5 text-xs text-destructive"
|
||||
>
|
||||
Error
|
||||
</span>
|
||||
{:else}
|
||||
<Switch
|
||||
checked={isEnabled}
|
||||
onCheckedChange={() =>
|
||||
conversationsStore.preferences.toggleMcpServerForChat(server.id)}
|
||||
/>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
{#if mcpServers.length === 0}
|
||||
<div class="px-3 py-2 text-center text-sm text-muted-foreground">
|
||||
No MCP servers configured
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
<span>System Message</span>
|
||||
</button>
|
||||
|
||||
{#if toolsPanel.totalToolCount > 0}
|
||||
<Collapsible.Root onOpenChange={(open) => (toolsExpanded = open)} open={toolsExpanded}>
|
||||
@@ -289,40 +216,12 @@
|
||||
|
||||
<Collapsible.Content>
|
||||
<div class="flex flex-col gap-0.5 pl-4">
|
||||
{#each toolsPanel.activeGroups as group (group.key)}
|
||||
{@const checked = toolsPanel.isGroupChecked(group)}
|
||||
{@const enabledCount = toolsPanel.getEnabledToolCount(group)}
|
||||
{@const favicon = toolsPanel.getFavicon(group)}
|
||||
{#each toolsPanel.categoryGroups as group (group.key)}
|
||||
{@render sheetGroupRow(group)}
|
||||
{/each}
|
||||
|
||||
<button
|
||||
class={sheetItemRowClass}
|
||||
onclick={() => toolsPanel.toggleGroupByKey(group.key)}
|
||||
type="button"
|
||||
>
|
||||
{#if favicon}
|
||||
<img
|
||||
alt=""
|
||||
class="{ICON_CLASS_DEFAULT} shrink-0 rounded-sm"
|
||||
onerror={(e) => {
|
||||
(e.currentTarget as HTMLImageElement).style.display = 'none';
|
||||
}}
|
||||
src={favicon}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<span class="min-w-0 flex-1 truncate text-sm font-medium">{group.label}</span>
|
||||
|
||||
<span class="shrink-0 text-xs text-muted-foreground">
|
||||
{enabledCount}/{group.tools.length}
|
||||
</span>
|
||||
|
||||
<Checkbox
|
||||
{checked}
|
||||
class="{ICON_CLASS_DEFAULT} shrink-0"
|
||||
onCheckedChange={() => toolsPanel.toggleGroupByKey(group.key)}
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</button>
|
||||
{#each toolsPanel.mcpGroups as group (group.key)}
|
||||
{@render sheetGroupRow(group)}
|
||||
{/each}
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
@@ -331,38 +230,55 @@
|
||||
|
||||
<button
|
||||
class={sheetItemClass}
|
||||
onclick={() => attachmentMenu.callbacks[AttachmentAction.SYSTEM_PROMPT_CLICK]()}
|
||||
onclick={() => {
|
||||
sheetOpen = false;
|
||||
chatFormActions.onMcpSettingsClick?.();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<MessageSquare class="{ICON_CLASS_DEFAULT} shrink-0" />
|
||||
<McpLogo class="inline {ICON_CLASS_DEFAULT} shrink-0" />
|
||||
|
||||
<span>System Message</span>
|
||||
<span>MCP Servers</span>
|
||||
</button>
|
||||
|
||||
{#if chatFormActions.hasMcpPromptsSupport}
|
||||
<button
|
||||
class={sheetItemClass}
|
||||
onclick={() => attachmentMenu.callbacks[AttachmentAction.MCP_PROMPT_CLICK]()}
|
||||
type="button"
|
||||
>
|
||||
<Zap class="{ICON_CLASS_DEFAULT} shrink-0" />
|
||||
|
||||
<span>MCP Prompt</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if chatFormActions.hasMcpResourcesSupport}
|
||||
<button
|
||||
class={sheetItemClass}
|
||||
onclick={() => attachmentMenu.callbacks[AttachmentAction.MCP_RESOURCES_CLICK]()}
|
||||
type="button"
|
||||
>
|
||||
<FolderOpen class="{ICON_CLASS_DEFAULT} shrink-0" />
|
||||
|
||||
<span>MCP Resources</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</Sheet.Content>
|
||||
</Sheet.Root>
|
||||
</div>
|
||||
|
||||
{#snippet sheetGroupRow(group: ToolGroup)}
|
||||
{@const checkState = toolsPanel.getGroupCheckState(group)}
|
||||
{@const enabledCount = toolsPanel.getEnabledToolCount(group)}
|
||||
{@const favicon = toolsPanel.getFavicon(group)}
|
||||
{@const groupDisabled = toolsPanel.isGroupDisabled(group)}
|
||||
|
||||
<button
|
||||
class="{sheetItemRowClass} {groupDisabled ? 'pointer-events-none opacity-50' : ''}"
|
||||
onclick={() => toolsPanel.toggleGroupByKey(group.key)}
|
||||
type="button"
|
||||
>
|
||||
{#if favicon}
|
||||
<img
|
||||
alt=""
|
||||
class="{ICON_CLASS_DEFAULT} shrink-0 rounded-sm"
|
||||
onerror={(e) => {
|
||||
(e.currentTarget as HTMLImageElement).style.display = 'none';
|
||||
}}
|
||||
src={favicon}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<span class="min-w-0 flex-1 truncate text-sm font-medium">{group.label}</span>
|
||||
|
||||
<span class="shrink-0 text-xs text-muted-foreground">
|
||||
{enabledCount}/{group.tools.length}
|
||||
</span>
|
||||
|
||||
<Checkbox
|
||||
checked={checkState.checked}
|
||||
class="{ICON_CLASS_DEFAULT} shrink-0"
|
||||
indeterminate={checkState.indeterminate}
|
||||
onCheckedChange={() => toolsPanel.toggleGroupByKey(group.key)}
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</button>
|
||||
{/snippet}
|
||||
|
||||
+100
-86
@@ -7,6 +7,7 @@
|
||||
import { CLI_FLAGS, ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { useToolsPanel } from '$lib/hooks/use-tools-panel.svelte';
|
||||
import { mcpStore, toolsStore } from '$lib/stores';
|
||||
import type { ToolGroup } from '$lib/types';
|
||||
|
||||
const toolsPanel = useToolsPanel();
|
||||
const hasMcpServersAvailable = $derived(mcpStore.getServers().length > 0);
|
||||
@@ -62,95 +63,108 @@
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="max-h-80 overflow-y-auto p-2 pr-1">
|
||||
{#each toolsPanel.activeGroups as group (group.key)}
|
||||
{@const isExpanded = toolsPanel.expandedGroups.has(group.key)}
|
||||
{@const checked = toolsPanel.isGroupChecked(group)}
|
||||
{@const favicon = toolsPanel.getFavicon(group)}
|
||||
{#each toolsPanel.categoryGroups as group (group.key)}
|
||||
{@render groupRow(group)}
|
||||
{/each}
|
||||
|
||||
<Collapsible.Root
|
||||
onOpenChange={() => toolsPanel.toggleGroupExpanded(group.key)}
|
||||
open={isExpanded}
|
||||
>
|
||||
<div class="flex items-center gap-1">
|
||||
<Collapsible.Trigger
|
||||
class="flex min-w-0 flex-1 items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-muted/50"
|
||||
>
|
||||
{#if isExpanded}
|
||||
<ChevronDown class="h-3.5 w-3.5 shrink-0" />
|
||||
{:else}
|
||||
<ChevronRight class="h-3.5 w-3.5 shrink-0" />
|
||||
{/if}
|
||||
|
||||
<span class="inline-flex min-w-0 items-center gap-1.5 font-medium">
|
||||
{#if favicon}
|
||||
<img
|
||||
alt=""
|
||||
class="{ICON_CLASS_DEFAULT} shrink-0 rounded-sm"
|
||||
onerror={(e) => {
|
||||
(e.currentTarget as HTMLImageElement).style.display = 'none';
|
||||
}}
|
||||
src={favicon}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<span class="truncate">{group.label}</span>
|
||||
</span>
|
||||
|
||||
<span class="ml-auto shrink-0 text-xs text-muted-foreground">
|
||||
{toolsPanel.getEnabledToolCount(group)}/{group.tools.length}
|
||||
</span>
|
||||
</Collapsible.Trigger>
|
||||
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Checkbox
|
||||
{...props}
|
||||
{checked}
|
||||
class="mr-2 {ICON_CLASS_DEFAULT} shrink-0"
|
||||
onCheckedChange={() => toolsPanel.toggleGroupByKey(group.key)}
|
||||
/>
|
||||
{/snippet}
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content side="right">
|
||||
<p>
|
||||
{checked ? 'Disable' : 'Enable'}
|
||||
{group.tools.length} tool{group.tools.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
<Collapsible.Content>
|
||||
<div class="ml-4 flex flex-col gap-0.5 border-l border-border/50 pl-2">
|
||||
{#each group.tools as entry (entry.key)}
|
||||
{@const enabled = toolsStore.isToolEnabled(entry.key)}
|
||||
<button
|
||||
class="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted/50"
|
||||
onclick={() => toolsStore.toggleTool(entry.key)}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground"
|
||||
data-slot="checkbox"
|
||||
data-state={enabled ? 'checked' : 'unchecked'}
|
||||
>
|
||||
{#if enabled}
|
||||
<Check class="size-3.5" />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span class="min-w-0 flex-1 truncate font-mono text-[12px]">
|
||||
{entry.definition.function.name}
|
||||
</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
{#each toolsPanel.mcpGroups as group (group.key)}
|
||||
{@render groupRow(group)}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
|
||||
{#snippet groupRow(group: ToolGroup)}
|
||||
{@const isExpanded = toolsPanel.expandedGroups.has(group.key)}
|
||||
{@const checkState = toolsPanel.getGroupCheckState(group)}
|
||||
{@const favicon = toolsPanel.getFavicon(group)}
|
||||
{@const groupDisabled = toolsPanel.isGroupDisabled(group)}
|
||||
|
||||
<Collapsible.Root
|
||||
onOpenChange={() => toolsPanel.toggleGroupExpanded(group.key)}
|
||||
open={isExpanded}
|
||||
>
|
||||
<div class="flex items-center gap-1 {groupDisabled ? 'pointer-events-none opacity-50' : ''}">
|
||||
<Collapsible.Trigger
|
||||
class="flex min-w-0 flex-1 items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-muted/50"
|
||||
>
|
||||
{#if isExpanded}
|
||||
<ChevronDown class="h-3.5 w-3.5 shrink-0" />
|
||||
{:else}
|
||||
<ChevronRight class="h-3.5 w-3.5 shrink-0" />
|
||||
{/if}
|
||||
|
||||
<span class="inline-flex min-w-0 items-center gap-1.5 font-medium">
|
||||
{#if favicon}
|
||||
<img
|
||||
alt=""
|
||||
class="{ICON_CLASS_DEFAULT} shrink-0 rounded-sm"
|
||||
onerror={(e) => {
|
||||
(e.currentTarget as HTMLImageElement).style.display = 'none';
|
||||
}}
|
||||
src={favicon}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<span class="truncate">{group.label}</span>
|
||||
</span>
|
||||
|
||||
<span class="ml-auto shrink-0 text-xs text-muted-foreground">
|
||||
{toolsPanel.getEnabledToolCount(group)}/{group.tools.length}
|
||||
</span>
|
||||
</Collapsible.Trigger>
|
||||
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Checkbox
|
||||
{...props}
|
||||
checked={checkState.checked}
|
||||
class="mr-2 {ICON_CLASS_DEFAULT} shrink-0"
|
||||
indeterminate={checkState.indeterminate}
|
||||
onCheckedChange={() => toolsPanel.toggleGroupByKey(group.key)}
|
||||
/>
|
||||
{/snippet}
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content side="right">
|
||||
<p>
|
||||
{checkState.checked ? 'Disable' : 'Enable'}
|
||||
{group.tools.length} tool{group.tools.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
<Collapsible.Content>
|
||||
<div class="ml-4 flex flex-col gap-0.5 border-l border-border/50 pl-2">
|
||||
{#each group.tools as entry (entry.key)}
|
||||
{@const enabled = toolsPanel.isToolEnabled(entry)}
|
||||
{@const parentDisabled = toolsPanel.isToolParentDisabled(entry)}
|
||||
<button
|
||||
class="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted/50 {parentDisabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
onclick={() => toolsPanel.toggleTool(entry)}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground"
|
||||
data-slot="checkbox"
|
||||
data-state={enabled ? 'checked' : 'unchecked'}
|
||||
>
|
||||
{#if enabled}
|
||||
<Check class="size-3.5" />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span class="min-w-0 flex-1 truncate font-mono text-[12px]">
|
||||
{entry.definition.function.name}
|
||||
</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
{/snippet}
|
||||
|
||||
+1
-29
@@ -13,7 +13,7 @@
|
||||
import { setChatFormActionsContext } from '$lib/contexts';
|
||||
import { FileTypeCategory, MessageRole } from '$lib/enums';
|
||||
import { ChatService } from '$lib/services';
|
||||
import { chatStore, conversationsStore, mcpStore, settingsStore } from '$lib/stores';
|
||||
import { chatStore, conversationsStore, settingsStore } from '$lib/stores';
|
||||
import { getFileTypeCategory } from '$lib/utils';
|
||||
|
||||
interface Props {
|
||||
@@ -31,8 +31,6 @@
|
||||
onMicClick?: () => void;
|
||||
onStop?: () => void;
|
||||
onSystemPromptClick?: () => void;
|
||||
onMcpPromptClick?: () => void;
|
||||
onMcpResourcesClick?: () => void;
|
||||
onMcpSettingsClick?: () => void;
|
||||
}
|
||||
|
||||
@@ -45,8 +43,6 @@
|
||||
isReasoning = false,
|
||||
isRecording = false,
|
||||
onFileUpload,
|
||||
onMcpPromptClick,
|
||||
onMcpResourcesClick,
|
||||
onMcpSettingsClick,
|
||||
onMicClick,
|
||||
onStop,
|
||||
@@ -58,18 +54,6 @@
|
||||
|
||||
let currentConfig = $derived(settingsStore.config);
|
||||
|
||||
let hasMcpPromptsSupport = $derived.by(() => {
|
||||
const perChatOverrides = conversationsStore.preferences.getAllMcpServerOverrides();
|
||||
|
||||
return mcpStore.hasPromptsCapability(perChatOverrides);
|
||||
});
|
||||
|
||||
let hasMcpResourcesSupport = $derived.by(() => {
|
||||
const perChatOverrides = conversationsStore.preferences.getAllMcpServerOverrides();
|
||||
|
||||
return mcpStore.hasResourcesCapability(perChatOverrides);
|
||||
});
|
||||
|
||||
let hasAudioModality = $state(false);
|
||||
let hasVideoModality = $state(false);
|
||||
let hasVisionModality = $state(false);
|
||||
@@ -142,12 +126,6 @@
|
||||
get hasAudioModality() {
|
||||
return hasAudioModality;
|
||||
},
|
||||
get hasMcpPromptsSupport() {
|
||||
return hasMcpPromptsSupport;
|
||||
},
|
||||
get hasMcpResourcesSupport() {
|
||||
return hasMcpResourcesSupport;
|
||||
},
|
||||
get hasVideoModality() {
|
||||
return hasVideoModality;
|
||||
},
|
||||
@@ -157,12 +135,6 @@
|
||||
get onFileUpload() {
|
||||
return onFileUpload;
|
||||
},
|
||||
get onMcpPromptClick() {
|
||||
return onMcpPromptClick;
|
||||
},
|
||||
get onMcpResourcesClick() {
|
||||
return onMcpResourcesClick;
|
||||
},
|
||||
get onMcpSettingsClick() {
|
||||
return onMcpSettingsClick;
|
||||
},
|
||||
|
||||
+6
-3
@@ -5,12 +5,12 @@
|
||||
import SearchInput from '$lib/components/app/forms/SearchInput.svelte';
|
||||
import * as Popover from '$lib/components/ui/popover';
|
||||
import { DEFAULT_MOBILE_BREAKPOINT, HOME_TILDE, SEARCH, UI_DATA_ATTRS } from '$lib/constants';
|
||||
import { BuiltInTool, GlobSearchType, KeyboardKey } from '$lib/enums';
|
||||
import { BuiltInTool, GlobSearchType, KeyboardKey, ToolSource } from '$lib/enums';
|
||||
import { useDebouncedSearch } from '$lib/hooks/use-debounced-search.svelte';
|
||||
import { usePickerNavigation } from '$lib/hooks/use-picker-navigation.svelte';
|
||||
import { useScrollActiveRow } from '$lib/hooks/use-scroll-active-row.svelte';
|
||||
import { ToolsService } from '$lib/services/tools.service';
|
||||
import { toolsStore } from '$lib/stores';
|
||||
import { conversationsStore, toolsStore } from '$lib/stores';
|
||||
import type { GlobEntry } from '$lib/types';
|
||||
import {
|
||||
abbreviateHome,
|
||||
@@ -63,8 +63,11 @@
|
||||
// unavailable instead of firing searches that would only fail. Browse is
|
||||
// hidden too: it resolves the picked folder name through the same tool.
|
||||
const fileSearchKey = $derived(toolsStore.getPermissionKey(BuiltInTool.SERVER_FILE_GLOB_SEARCH));
|
||||
// effective policy: the active conversation's tool policy, or global defaults
|
||||
const fileSearchEnabled = $derived(
|
||||
fileSearchKey !== null && toolsStore.isToolEnabled(fileSearchKey)
|
||||
fileSearchKey !== null &&
|
||||
conversationsStore.preferences.isToolEnabled(fileSearchKey) &&
|
||||
conversationsStore.preferences.isCategoryEnabled(ToolSource.SERVER)
|
||||
);
|
||||
const searchUnavailableMessage = $derived(
|
||||
fileSearchKey === null
|
||||
|
||||
+2
-3
@@ -9,7 +9,7 @@
|
||||
} from '$lib/components/app/chat';
|
||||
import Badge from '$lib/components/ui/badge/badge.svelte';
|
||||
import { KeyboardKey } from '$lib/enums';
|
||||
import { conversationsStore, mcpStore } from '$lib/stores';
|
||||
import { mcpStore } from '$lib/stores';
|
||||
import type { GetPromptResult, MCPPromptInfo, MCPServerSettingsEntry } from '$lib/types';
|
||||
import { debounce, uuid } from '$lib/utils';
|
||||
import { SvelteMap } from 'svelte/reactivity';
|
||||
@@ -87,8 +87,7 @@
|
||||
isLoading = true;
|
||||
|
||||
try {
|
||||
const perChatOverrides = conversationsStore.preferences.getAllMcpServerOverrides();
|
||||
const initialized = await mcpStore.ensureInitialized(perChatOverrides);
|
||||
const initialized = await mcpStore.ensureInitialized();
|
||||
|
||||
if (!initialized) {
|
||||
prompts = [];
|
||||
|
||||
+12
-3
@@ -5,10 +5,16 @@
|
||||
import * as Popover from '$lib/components/ui/popover';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { FILE_GLOB_SEARCH_PICKERS, HOME_TILDE, SEARCH } from '$lib/constants';
|
||||
import { BuiltInTool, FileMentionEntryType, GlobSearchType, KeyboardKey } from '$lib/enums';
|
||||
import {
|
||||
BuiltInTool,
|
||||
FileMentionEntryType,
|
||||
GlobSearchType,
|
||||
KeyboardKey,
|
||||
ToolSource
|
||||
} from '$lib/enums';
|
||||
import { useDebouncedSearch } from '$lib/hooks/use-debounced-search.svelte';
|
||||
import { usePickerNavigation } from '$lib/hooks/use-picker-navigation.svelte';
|
||||
import { deviceStore, settingsStore, toolsStore } from '$lib/stores';
|
||||
import { conversationsStore, deviceStore, settingsStore, toolsStore } from '$lib/stores';
|
||||
import type { FileMentionEntry, GlobEntryResult } from '$lib/types';
|
||||
import { abbreviateHome, runGlobSearchWithChildren } from '$lib/utils';
|
||||
|
||||
@@ -52,8 +58,11 @@
|
||||
// --tools) or the user disabled it, the picker still opens but explains
|
||||
// why instead of firing searches that would only fail.
|
||||
const fileSearchKey = $derived(toolsStore.getPermissionKey(BuiltInTool.SERVER_FILE_GLOB_SEARCH));
|
||||
// effective policy: the active conversation's tool policy, or global defaults
|
||||
const fileSearchEnabled = $derived(
|
||||
fileSearchKey !== null && toolsStore.isToolEnabled(fileSearchKey)
|
||||
fileSearchKey !== null &&
|
||||
conversationsStore.preferences.isToolEnabled(fileSearchKey) &&
|
||||
conversationsStore.preferences.isCategoryEnabled(ToolSource.SERVER)
|
||||
);
|
||||
|
||||
let searchResults = $state<FileMentionEntry[]>([]);
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
onValueChange={editCtx.setContent}
|
||||
placeholder="Edit your message..."
|
||||
showAddButton={editCtx.messageRole === MessageRole.USER}
|
||||
showMcpPromptButton
|
||||
showModelSelector={editCtx.messageRole === MessageRole.USER}
|
||||
value={editCtx.editedContent}
|
||||
/>
|
||||
|
||||
@@ -160,6 +160,5 @@
|
||||
onSubmit={handleSubmit}
|
||||
onSystemPromptClick={handleSystemPromptClick}
|
||||
onUploadedFileRemove={handleUploadedFileRemove}
|
||||
showMcpPromptButton
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -220,19 +220,6 @@ export { default as ChatFormActionModels } from './ChatForm/ChatFormActions/Chat
|
||||
*/
|
||||
export { default as ChatFormActionAddToolsSubmenu } from './ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddToolsSubmenu.svelte';
|
||||
|
||||
/**
|
||||
* Dropdown submenu for MCP prompts and resources in the chat form.
|
||||
*
|
||||
* Shows an "MCP" sub-menu item with entries for MCP Prompts and MCP
|
||||
* Resources. Only visible when the server supports them.
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <ChatFormActionAddMcpSubmenu />
|
||||
* ```
|
||||
*/
|
||||
export { default as ChatFormActionAddMcpSubmenu } from './ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddMcpSubmenu.svelte';
|
||||
|
||||
/**
|
||||
* Dropdown submenu for selecting reasoning effort level.
|
||||
*
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { conversationsStore, mcpStore } from '$lib/stores';
|
||||
import { mcpStore } from '$lib/stores';
|
||||
import type { MCPResourceContent, MCPResourceInfo, MCPResourceTemplateInfo } from '$lib/types';
|
||||
import { getResourceDisplayName } from '$lib/utils';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
@@ -48,8 +48,7 @@
|
||||
});
|
||||
|
||||
async function loadResources() {
|
||||
const perChatOverrides = conversationsStore.preferences.getAllMcpServerOverrides();
|
||||
const initialized = await mcpStore.ensureInitialized(perChatOverrides);
|
||||
const initialized = await mcpStore.ensureInitialized();
|
||||
|
||||
if (initialized) {
|
||||
await mcpStore.fetchAllResources();
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
RECOMMENDED_MCP_SERVERS
|
||||
} from '$lib/constants';
|
||||
import { BooleanString, HealthCheckStatus } from '$lib/enums';
|
||||
import { conversationsStore, mcpStore } from '$lib/stores';
|
||||
import { mcpStore } from '$lib/stores';
|
||||
import { canonicalizeServerUrl, parseHeadersToArray, uuid } from '$lib/utils';
|
||||
|
||||
interface Props {
|
||||
@@ -234,8 +234,6 @@
|
||||
useProxy: newServerUseProxy
|
||||
});
|
||||
|
||||
conversationsStore.preferences.setMcpServerOverride(newServerId, true);
|
||||
|
||||
handleOpenChange(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,22 +76,19 @@
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open {onOpenChange}>
|
||||
<Dialog.Content class="@container z-9999 !max-h-[80dvh] !max-w-[60rem] max-w-full">
|
||||
<style>
|
||||
@container (max-width: 56rem) {
|
||||
.resizable-text-container {
|
||||
max-width: calc(100vw - var(--threshold));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<Dialog.Content
|
||||
class="z-9999 max-md:h-[100dvh]! max-md:w-screen! max-md:max-w-none! md:w-[calc(100vw-4rem)]! md:max-w-[60rem]! md:max-h-[80dvh]!"
|
||||
>
|
||||
<!-- sticky header holds only the close button; the title scrolls with the body -->
|
||||
<Dialog.Header />
|
||||
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Model Information</Dialog.Title>
|
||||
<div class="min-w-0 space-y-6 md:py-4 -mt-4! md:mt-0 pb-4">
|
||||
<div class="min-w-0 space-y-2">
|
||||
<Dialog.Title>Model Information</Dialog.Title>
|
||||
|
||||
<Dialog.Description>Current model details and capabilities</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Dialog.Description>Current model details and capabilities</Dialog.Description>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6 py-4">
|
||||
{#if isLoadingModels || isLoadingRouterProps}
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div class="text-sm text-muted-foreground">Loading model information...</div>
|
||||
@@ -100,17 +97,15 @@
|
||||
{@const modelMeta = firstModel.meta}
|
||||
|
||||
{#if serverProps}
|
||||
<Table.Root>
|
||||
<!-- Desktop: fixed-layout table, long values scroll inside their cell -->
|
||||
<Table.Root class="hidden table-fixed md:table">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head class="w-[10rem]">Model</Table.Head>
|
||||
|
||||
<Table.Head>
|
||||
<div class="inline-flex items-center gap-2">
|
||||
<span
|
||||
style:--threshold="12rem"
|
||||
class="resizable-text-container min-w-0 flex-1 truncate"
|
||||
>
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<span class="min-w-0 flex-1 overflow-x-auto whitespace-nowrap">
|
||||
{modelName}
|
||||
</span>
|
||||
|
||||
@@ -129,20 +124,17 @@
|
||||
<Table.Row>
|
||||
<Table.Cell class="h-10 align-middle font-medium">File Path</Table.Cell>
|
||||
|
||||
<Table.Cell
|
||||
class="inline-flex h-10 items-center gap-2 align-middle font-mono text-xs"
|
||||
>
|
||||
<span
|
||||
style:--threshold="14rem"
|
||||
class="resizable-text-container min-w-0 flex-1 truncate"
|
||||
>
|
||||
{serverProps.model_path}
|
||||
</span>
|
||||
<Table.Cell class="h-10 align-middle font-mono text-xs">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<span class="min-w-0 flex-1 overflow-x-auto whitespace-nowrap">
|
||||
{serverProps.model_path}
|
||||
</span>
|
||||
|
||||
<ActionIconCopyToClipboard
|
||||
ariaLabel="Copy model path to clipboard"
|
||||
text={serverProps.model_path}
|
||||
/>
|
||||
<ActionIconCopyToClipboard
|
||||
ariaLabel="Copy model path to clipboard"
|
||||
text={serverProps.model_path}
|
||||
/>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
|
||||
@@ -251,18 +243,113 @@
|
||||
<!-- Chat Template -->
|
||||
{#if serverProps.chat_template}
|
||||
<Table.Row>
|
||||
<Table.Cell class="align-middle font-medium">Chat Template</Table.Cell>
|
||||
<Table.Cell class="py-4" colspan={2}>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-medium">Chat Template</span>
|
||||
|
||||
<Table.Cell class="py-10">
|
||||
<div class="rounded-md bg-muted p-4">
|
||||
<pre
|
||||
class="font-mono text-xs whitespace-pre-wrap">{serverProps.chat_template}</pre>
|
||||
<div class="overflow-x-auto rounded-md bg-muted p-4">
|
||||
<pre
|
||||
class="font-mono text-xs whitespace-pre">{serverProps.chat_template}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
<!-- Mobile: stacked layout; long values wrap instead of scrolling the page -->
|
||||
<div class="flex min-w-0 flex-col gap-4 md:hidden">
|
||||
<div class="min-w-0 space-y-1">
|
||||
<div class="text-xs font-medium text-muted-foreground">Model</div>
|
||||
|
||||
<div class="flex min-w-0 items-start gap-2">
|
||||
<span class="min-w-0 flex-1 break-all font-mono text-xs">{modelName}</span>
|
||||
|
||||
<ActionIconCopyToClipboard
|
||||
ariaLabel="Copy model name to clipboard"
|
||||
canCopy={!!modelName}
|
||||
text={modelName || ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 space-y-1">
|
||||
<div class="text-xs font-medium text-muted-foreground">File Path</div>
|
||||
|
||||
<div class="flex min-w-0 items-start gap-2">
|
||||
<span class="min-w-0 flex-1 break-all font-mono text-xs"
|
||||
>{serverProps.model_path}</span
|
||||
>
|
||||
|
||||
<ActionIconCopyToClipboard
|
||||
ariaLabel="Copy model path to clipboard"
|
||||
text={serverProps.model_path}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if serverProps?.default_generation_settings?.n_ctx}
|
||||
{@render infoRow(
|
||||
'Context Size',
|
||||
`${formatNumber(serverProps.default_generation_settings.n_ctx)} tokens`
|
||||
)}
|
||||
{:else}
|
||||
{@render infoRow('Context Size', 'Not available', 'text-red-500')}
|
||||
{/if}
|
||||
|
||||
{#if modelMeta?.n_ctx_train}
|
||||
{@render infoRow('Training Context', `${formatNumber(modelMeta.n_ctx_train)} tokens`)}
|
||||
{/if}
|
||||
|
||||
{#if modelMeta?.size}
|
||||
{@render infoRow('Model Size', formatFileSize(modelMeta.size))}
|
||||
{/if}
|
||||
|
||||
{#if modelMeta?.n_params}
|
||||
{@render infoRow('Parameters', formatParameters(modelMeta.n_params))}
|
||||
{/if}
|
||||
|
||||
{#if modelMeta?.n_embd}
|
||||
{@render infoRow('Embedding Size', formatNumber(modelMeta.n_embd))}
|
||||
{/if}
|
||||
|
||||
{#if modelMeta?.n_vocab}
|
||||
{@render infoRow('Vocabulary Size', `${formatNumber(modelMeta.n_vocab)} tokens`)}
|
||||
{/if}
|
||||
|
||||
{#if modelMeta?.vocab_type}
|
||||
{@render infoRow('Vocabulary Type', modelMeta.vocab_type, 'capitalize')}
|
||||
{/if}
|
||||
|
||||
{@render infoRow('Parallel Slots', `${serverProps.total_slots}`)}
|
||||
|
||||
{#if modalities.length > 0}
|
||||
<div class="min-w-0 space-y-1">
|
||||
<div class="text-xs font-medium text-muted-foreground">Modalities</div>
|
||||
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<BadgesModality {modalities} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="min-w-0 space-y-1">
|
||||
<div class="text-xs font-medium text-muted-foreground">Build Info</div>
|
||||
|
||||
<span class="block break-all font-mono text-xs">{serverProps.build_info}</span>
|
||||
</div>
|
||||
|
||||
{#if serverProps.chat_template}
|
||||
<div class="min-w-0 space-y-2">
|
||||
<div class="text-xs font-medium text-muted-foreground">Chat Template</div>
|
||||
|
||||
<div class="overflow-x-auto rounded-md bg-muted p-4">
|
||||
<pre class="font-mono text-xs whitespace-pre">{serverProps.chat_template}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{:else if !isLoadingModels}
|
||||
<div class="flex items-center justify-center py-8">
|
||||
@@ -272,3 +359,11 @@
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
{#snippet infoRow(label: string, value: string, valueClass: string = '')}
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<span class="shrink-0 text-xs font-medium text-muted-foreground {valueClass}">{label}</span>
|
||||
|
||||
<span class="text-sm {valueClass}">{value}</span>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import McpLogo from './McpLogo.svelte';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { ICON_CLASS_DEFAULT, MAX_DISPLAYED_MCP_AVATARS } from '$lib/constants';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
import { HealthCheckStatus, ToolSource } from '$lib/enums';
|
||||
import { conversationsStore, mcpStore } from '$lib/stores';
|
||||
|
||||
interface Props {
|
||||
@@ -13,9 +13,13 @@
|
||||
let { class: className = '', onclick }: Props = $props();
|
||||
|
||||
let mcpServers = $derived(mcpStore.getServers().filter((s) => s.enabled));
|
||||
// respect the active conversation's tool policy, not just global enablement
|
||||
let enabledMcpServersForChat = $derived(
|
||||
mcpServers.filter(
|
||||
(s) => conversationsStore.preferences.isMcpServerEnabledForChat(s.id) && s.url.trim()
|
||||
(s) =>
|
||||
s.url.trim() &&
|
||||
conversationsStore.preferences.isCategoryEnabled(ToolSource.MCP) &&
|
||||
conversationsStore.preferences.isServerToolsEnabled(s.id)
|
||||
)
|
||||
);
|
||||
let healthyEnabledMcpServers = $derived(
|
||||
|
||||
@@ -1,27 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { TruncatedText } from '$lib/components/app';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import {
|
||||
CAPABILITY_FLAG_KEYS,
|
||||
CAPABILITY_ICONS,
|
||||
CAPABILITY_LABELS,
|
||||
MODALITY_FLAG_KEYS,
|
||||
MODALITY_ICONS,
|
||||
MODALITY_LABELS
|
||||
} from '$lib/constants';
|
||||
import { ModelCapability, ModelModality } from '$lib/enums';
|
||||
import { ModelsService } from '$lib/services/models.service';
|
||||
import { settingsStore } from '$lib/stores';
|
||||
import type { ModelCapabilities, ModelModalities } from '$lib/types/models';
|
||||
|
||||
interface Props {
|
||||
modelId: string;
|
||||
hideOrgName?: boolean;
|
||||
showRaw?: boolean;
|
||||
showRawTooltip?: boolean;
|
||||
hideQuantization?: boolean;
|
||||
hideTags?: boolean;
|
||||
aliases?: string[];
|
||||
tags?: string[];
|
||||
modalities?: ModelModalities;
|
||||
capabilities?: ModelCapabilities;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
aliases,
|
||||
capabilities,
|
||||
class: className = '',
|
||||
hideOrgName = false,
|
||||
hideQuantization,
|
||||
hideTags,
|
||||
modalities,
|
||||
modelId,
|
||||
showRaw = undefined,
|
||||
showRawTooltip = false,
|
||||
tags,
|
||||
...rest
|
||||
}: Props = $props();
|
||||
@@ -43,6 +60,16 @@
|
||||
let uniqueAliases = $derived([...new Set(aliases ?? [])]);
|
||||
let uniqueTags = $derived([...new Set([...(parsed.tags ?? []), ...(tags ?? [])])]);
|
||||
|
||||
const allModalities = [ModelModality.VISION, ModelModality.VIDEO, ModelModality.AUDIO] as const;
|
||||
const allCapabilities: ModelCapability[] = [ModelCapability.REASONING];
|
||||
|
||||
let activeModalities = $derived(
|
||||
allModalities.filter((modality) => modalities?.[MODALITY_FLAG_KEYS[modality]])
|
||||
);
|
||||
let activeCapabilities = $derived(
|
||||
allCapabilities.filter((capability) => capabilities?.[CAPABILITY_FLAG_KEYS[capability]])
|
||||
);
|
||||
|
||||
let primaryAlias = $derived(uniqueAliases.length === 1 ? uniqueAliases[0] : null);
|
||||
let displayName = $derived(primaryAlias ?? parsed.modelName ?? modelId);
|
||||
</script>
|
||||
@@ -50,37 +77,87 @@
|
||||
{#if resolvedShowRaw}
|
||||
<TruncatedText class="font-medium {className}" showTooltip={false} text={modelId} {...rest} />
|
||||
{:else}
|
||||
<span class="flex min-w-0 flex-wrap items-center gap-1 {className}" {...rest}>
|
||||
{#snippet nameAndBadges()}
|
||||
<span class="min-w-0 truncate font-medium">
|
||||
{#if !hideOrgName && parsed.orgName}{parsed.orgName}/{/if}{displayName}
|
||||
</span>
|
||||
|
||||
{#if parsed.params}
|
||||
<span class={badgeClass}>
|
||||
{parsed.params}{parsed.activatedParams ? `-${parsed.activatedParams}` : ''}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if parsed.quantization && !resolvedHideQuantization}
|
||||
<span class={badgeClass}>
|
||||
{parsed.quantization}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if primaryAlias}
|
||||
{#if primaryAlias !== parsed.modelName}
|
||||
<span class={badgeClass}>{parsed.modelName ?? modelId}</span>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
{#if parsed.params}
|
||||
<span class={badgeClass}>
|
||||
{parsed.params}{parsed.activatedParams ? `-${parsed.activatedParams}` : ''}
|
||||
</span>
|
||||
{/if}
|
||||
{:else if uniqueAliases.length > 1}
|
||||
{#each uniqueAliases as alias (alias)}
|
||||
<span class={badgeClass}>{alias}</span>
|
||||
{/each}
|
||||
|
||||
{#if parsed.quantization && !resolvedHideQuantization}
|
||||
<span class={badgeClass}>
|
||||
{parsed.quantization}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if primaryAlias}
|
||||
{#if primaryAlias !== parsed.modelName}
|
||||
<span class={badgeClass}>{parsed.modelName ?? modelId}</span>
|
||||
{/if}
|
||||
{:else if uniqueAliases.length > 1}
|
||||
{#each uniqueAliases as alias (alias)}
|
||||
<span class={badgeClass}>{alias}</span>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if uniqueTags.length > 0 && !resolvedHideTags}
|
||||
{#each uniqueTags as tag (tag)}
|
||||
<span class={tagBadgeClass}>{tag}</span>
|
||||
{/each}
|
||||
{/if}
|
||||
</span>
|
||||
{/snippet}
|
||||
|
||||
<span class="flex min-w-0 items-center gap-1.5 {className}" {...rest}>
|
||||
{#if showRawTooltip}
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger class="flex min-w-0 items-center gap-1.5">
|
||||
{@render nameAndBadges()}
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content>
|
||||
<p>{modelId}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{:else}
|
||||
{@render nameAndBadges()}
|
||||
{/if}
|
||||
|
||||
{#if uniqueTags.length > 0 && !resolvedHideTags}
|
||||
{#each uniqueTags as tag (tag)}
|
||||
<span class={tagBadgeClass}>{tag}</span>
|
||||
{/each}
|
||||
{#if activeCapabilities.length > 0 || activeModalities.length > 0}
|
||||
<span class="inline-flex items-center gap-1.25 text-muted-foreground">
|
||||
{#each activeCapabilities as capability (capability)}
|
||||
{@const CapabilityIcon = CAPABILITY_ICONS[capability]}
|
||||
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<CapabilityIcon class="h-3 w-3 text-muted-foreground" />
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content>
|
||||
<p>{CAPABILITY_LABELS[capability]}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/each}
|
||||
|
||||
{#each activeModalities as modality (modality)}
|
||||
{@const ModalityIcon = MODALITY_ICONS[modality]}
|
||||
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<ModalityIcon class="h-3 w-3 text-muted-foreground" />
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content>
|
||||
<p>{MODALITY_LABELS[modality]}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import ModelLoadHighlight from './ModelLoadHighlight.svelte';
|
||||
import type { ModelItem } from './utils';
|
||||
import { ChevronDown, Loader2 } from '@lucide/svelte';
|
||||
import { ChevronDown, Lightbulb, Loader2 } from '@lucide/svelte';
|
||||
import {
|
||||
ChatFormActionAddReasoningSubmenu,
|
||||
DialogModelInformation,
|
||||
DropdownMenuSearchable,
|
||||
ModelId,
|
||||
@@ -11,10 +12,11 @@
|
||||
} from '$lib/components/app';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { MODEL_SELECTOR_ICON } from '$lib/constants';
|
||||
import { MODEL_SELECTOR_ICON, SETTINGS_KEYS } from '$lib/constants';
|
||||
import { KeyboardKey, ServerModelStatus } from '$lib/enums';
|
||||
import { useModelsSelector } from '$lib/hooks/use-models-selector.svelte';
|
||||
import { modelsStore } from '$lib/stores';
|
||||
import { useReasoningMenu } from '$lib/hooks/use-reasoning-menu.svelte';
|
||||
import { modelsStore, settingsStore } from '$lib/stores';
|
||||
import { modelLoadFraction } from '$lib/utils';
|
||||
|
||||
interface Props {
|
||||
@@ -37,6 +39,9 @@
|
||||
|
||||
let isOpen = $state(false);
|
||||
let highlightedId = $state<string | null>(null);
|
||||
// The model submenu opens together with the menu so the list and its search
|
||||
// box are immediately available, as before the submenu was introduced
|
||||
let modelSubOpen = $state(false);
|
||||
|
||||
const ms = useModelsSelector({
|
||||
currentModel: () => currentModel,
|
||||
@@ -44,24 +49,41 @@
|
||||
onOpenChange: (open) => {
|
||||
isOpen = open;
|
||||
highlightedId = null;
|
||||
|
||||
if (open) {
|
||||
// Defer submenu open so the Sub component is mounted first;
|
||||
// setting bind:open synchronously can be lost if the Sub hasn't
|
||||
// rendered yet.
|
||||
queueMicrotask(() => {
|
||||
if (isOpen) modelSubOpen = true;
|
||||
});
|
||||
} else {
|
||||
modelSubOpen = false;
|
||||
}
|
||||
},
|
||||
useGlobalSelection: () => useGlobalSelection
|
||||
});
|
||||
|
||||
const reasoning = useReasoningMenu();
|
||||
|
||||
const showOrgNameInTrigger = $derived(
|
||||
settingsStore.config[SETTINGS_KEYS.SHOW_MODEL_ORG_NAME_IN_TRIGGER] ?? false
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
void ms.searchTerm;
|
||||
highlightedId = null;
|
||||
});
|
||||
|
||||
// Focus the dropdown's search box without scrolling the page. bits-ui
|
||||
// Focus the model submenu's search box without scrolling the page. bits-ui
|
||||
// auto-focuses the opened content by default, which can yank the page
|
||||
// scroll; we prevent that on the Content and refocus the search here.
|
||||
$effect(() => {
|
||||
if (!isOpen) return;
|
||||
if (!isOpen || !modelSubOpen) return;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const search = document.querySelector<HTMLElement>(
|
||||
'[data-slot="dropdown-menu-content"] input'
|
||||
'[data-slot="dropdown-menu-sub-content"] input'
|
||||
);
|
||||
|
||||
search?.focus({ preventScroll: true });
|
||||
@@ -188,7 +210,7 @@
|
||||
<DropdownMenu.Trigger
|
||||
{...props}
|
||||
class={[
|
||||
`relative inline-grid cursor-pointer grid-cols-[1fr_auto_1fr] items-center gap-1.5 rounded-sm bg-background px-1.5 py-1 text-xs shadow-sm transition hover:bg-muted-foreground/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-muted-foreground/15 dark:text-secondary-foreground`,
|
||||
`relative inline-grid cursor-pointer grid-cols-[1fr_auto_1fr] items-center gap-1 rounded-sm bg-background px-1.5 py-1 text-xs shadow-sm transition hover:bg-muted-foreground/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-muted-foreground/15 dark:text-secondary-foreground`,
|
||||
!ms.isCurrentModelInCache
|
||||
? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
|
||||
: forceForegroundText
|
||||
@@ -203,16 +225,22 @@
|
||||
>
|
||||
<MODEL_SELECTOR_ICON class="h-3.5 w-3.5 shrink-0" />
|
||||
|
||||
{#if selectedOption}
|
||||
<ModelId
|
||||
class="min-w-0 overflow-hidden"
|
||||
hideOrgName={false}
|
||||
hideQuantization
|
||||
modelId={selectedOption.model}
|
||||
/>
|
||||
{:else}
|
||||
<span class="min-w-0 font-medium">Select model</span>
|
||||
{/if}
|
||||
<span class="flex min-w-0 items-center gap-1">
|
||||
{#if selectedOption}
|
||||
<ModelId
|
||||
class="min-w-0 overflow-hidden"
|
||||
hideOrgName={!showOrgNameInTrigger}
|
||||
hideQuantization
|
||||
modelId={selectedOption.model}
|
||||
/>
|
||||
{:else}
|
||||
<span class="min-w-0 font-medium">Select model</span>
|
||||
{/if}
|
||||
|
||||
{#if reasoning.isReasoningActive}
|
||||
<Lightbulb class="h-3.5 w-3.5 shrink-0 text-amber-400" />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
{#if ms.updating || ms.isLoadingModel}
|
||||
<Loader2 class="h-3 w-3.5 shrink-0 animate-spin" />
|
||||
@@ -236,73 +264,94 @@
|
||||
|
||||
<DropdownMenu.Content
|
||||
align="end"
|
||||
class="w-full max-w-[100vw] pt-0 sm:w-max sm:max-w-[calc(100vw-2rem)]"
|
||||
class="w-full md:min-w-64 md:max-w-80 max-w-[calc(100vw-2rem)]"
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
<DropdownMenuSearchable
|
||||
emptyMessage="No models found."
|
||||
isEmpty={ms.filteredOptions.length === 0 && ms.isCurrentModelInCache}
|
||||
onSearchChange={(v) => ms.setSearchTerm(v)}
|
||||
onSearchKeyDown={handleSearchKeyDown}
|
||||
placeholder="Search models..."
|
||||
searchValue={ms.searchTerm}
|
||||
>
|
||||
<div class="models-list">
|
||||
{#if !ms.isCurrentModelInCache && currentModel}
|
||||
<!-- Show unavailable model as first option (disabled) -->
|
||||
<button
|
||||
aria-disabled="true"
|
||||
aria-selected="true"
|
||||
class="flex w-full cursor-not-allowed items-center bg-red-400/10 p-2 text-left text-sm text-red-400"
|
||||
disabled
|
||||
role="option"
|
||||
type="button"
|
||||
>
|
||||
<ModelId class="flex-1" hideQuantization modelId={currentModel} />
|
||||
<DropdownMenu.Sub bind:open={modelSubOpen}>
|
||||
<DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
|
||||
<MODEL_SELECTOR_ICON class="h-4 w-4" />
|
||||
|
||||
<span class="ml-2 text-xs whitespace-nowrap opacity-70">(not available)</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if ms.filteredOptions.length === 0}
|
||||
<p class="px-4 py-3 text-sm text-muted-foreground">No models found.</p>
|
||||
{/if}
|
||||
|
||||
{#snippet modelOption(item: ModelItem, hideOrgName: boolean)}
|
||||
{@const { option } = item}
|
||||
{@const isSelected = currentModel === option.model || ms.activeId === option.id}
|
||||
{@const isHighlighted = option.id === highlightedId}
|
||||
{@const isFav = ms.isFavorite(option.model)}
|
||||
|
||||
<ModelsSelectorOption
|
||||
{hideOrgName}
|
||||
{isFav}
|
||||
{isHighlighted}
|
||||
{isSelected}
|
||||
onInfoClick={ms.handleInfoClick}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === KeyboardKey.ENTER || event.key === KeyboardKey.SPACE) {
|
||||
event.preventDefault();
|
||||
void handleModelKeyAction(option.id, event.altKey);
|
||||
}
|
||||
}}
|
||||
onMouseEnter={() => (highlightedId = option.id)}
|
||||
onSelect={ms.handleSelect}
|
||||
{option}
|
||||
{#if selectedOption}
|
||||
<ModelId
|
||||
class="min-w-0 flex-1 overflow-hidden"
|
||||
hideOrgName={!showOrgNameInTrigger}
|
||||
hideQuantization
|
||||
modelId={selectedOption.model}
|
||||
/>
|
||||
{/snippet}
|
||||
{:else}
|
||||
<span class="min-w-0 flex-1 truncate text-muted-foreground">No model</span>
|
||||
{/if}
|
||||
</DropdownMenu.SubTrigger>
|
||||
|
||||
<ModelsSelectorList
|
||||
activeId={ms.activeId}
|
||||
{currentModel}
|
||||
groups={ms.groupedFilteredOptions}
|
||||
onInfoClick={ms.handleInfoClick}
|
||||
onSelect={ms.handleSelect}
|
||||
renderOption={modelOption}
|
||||
sectionHeaderClass="my-1.5 px-2 py-2 text-[13px] font-semibold text-muted-foreground/70 select-none"
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenuSearchable>
|
||||
<DropdownMenu.SubContent class="w-100 max-w-[calc(100vw-2rem)] pt-0">
|
||||
<DropdownMenuSearchable
|
||||
emptyMessage="No models found."
|
||||
isEmpty={ms.filteredOptions.length === 0 && ms.isCurrentModelInCache}
|
||||
onSearchChange={(v) => ms.setSearchTerm(v)}
|
||||
onSearchKeyDown={handleSearchKeyDown}
|
||||
placeholder="Search models..."
|
||||
searchValue={ms.searchTerm}
|
||||
>
|
||||
<div class="models-list">
|
||||
{#if !ms.isCurrentModelInCache && currentModel}
|
||||
<!-- Show unavailable model as first option (disabled) -->
|
||||
<button
|
||||
aria-disabled="true"
|
||||
aria-selected="true"
|
||||
class="flex w-full cursor-not-allowed items-center bg-red-400/10 p-2 text-left text-sm text-red-400"
|
||||
disabled
|
||||
role="option"
|
||||
type="button"
|
||||
>
|
||||
<ModelId class="flex-1" hideQuantization modelId={currentModel} />
|
||||
|
||||
<span class="ml-2 text-xs whitespace-nowrap opacity-70">(not available)</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if ms.filteredOptions.length === 0}
|
||||
<p class="px-4 py-3 text-sm text-muted-foreground">No models found.</p>
|
||||
{/if}
|
||||
|
||||
{#snippet modelOption(item: ModelItem, hideOrgName: boolean)}
|
||||
{@const { option } = item}
|
||||
{@const isSelected = currentModel === option.model || ms.activeId === option.id}
|
||||
{@const isHighlighted = option.id === highlightedId}
|
||||
{@const isFav = ms.isFavorite(option.model)}
|
||||
|
||||
<ModelsSelectorOption
|
||||
{hideOrgName}
|
||||
{isFav}
|
||||
{isHighlighted}
|
||||
{isSelected}
|
||||
onInfoClick={ms.handleInfoClick}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === KeyboardKey.ENTER || event.key === KeyboardKey.SPACE) {
|
||||
event.preventDefault();
|
||||
void handleModelKeyAction(option.id, event.altKey);
|
||||
}
|
||||
}}
|
||||
onMouseEnter={() => (highlightedId = option.id)}
|
||||
onSelect={ms.handleSelect}
|
||||
{option}
|
||||
/>
|
||||
{/snippet}
|
||||
|
||||
<ModelsSelectorList
|
||||
activeId={ms.activeId}
|
||||
{currentModel}
|
||||
groups={ms.groupedFilteredOptions}
|
||||
onInfoClick={ms.handleInfoClick}
|
||||
onSelect={ms.handleSelect}
|
||||
renderOption={modelOption}
|
||||
sectionHeaderClass="my-1.5 px-2 py-2 text-[13px] font-semibold text-muted-foreground/70 select-none"
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenuSearchable>
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
|
||||
<ChatFormActionAddReasoningSubmenu />
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
{:else}
|
||||
@@ -332,12 +381,16 @@
|
||||
{#if selectedOption}
|
||||
<ModelId
|
||||
class="min-w-0 overflow-hidden"
|
||||
hideOrgName={false}
|
||||
hideOrgName={!showOrgNameInTrigger}
|
||||
hideQuantization
|
||||
modelId={selectedOption.model}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if reasoning.isReasoningActive}
|
||||
<Lightbulb class="h-3.5 w-3.5 shrink-0 text-amber-400" />
|
||||
{/if}
|
||||
|
||||
{#if ms.updating}
|
||||
<Loader2 class="h-3 w-3.5 shrink-0 animate-spin" />
|
||||
{/if}
|
||||
|
||||
@@ -58,6 +58,10 @@
|
||||
let loadProgress = $derived(isLoading ? modelsStore.status.getLoadProgress(option.model) : null);
|
||||
let loadPercent = $derived(Math.round(modelLoadFraction(loadProgress) * 100));
|
||||
let loadTitle = $derived(modelLoadProgressText(loadProgress));
|
||||
let modalities = $derived(option.modalities);
|
||||
let capabilities = $derived.by(() => ({
|
||||
reasoning: modelsStore.props.checkModelSupportsThinking(option.model)
|
||||
}));
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -65,9 +69,11 @@
|
||||
class={[
|
||||
'group relative flex w-full items-center gap-2 rounded-sm p-2 text-left text-sm transition focus:outline-none',
|
||||
'cursor-pointer',
|
||||
isSelected && 'bg-accent/50 text-accent-foreground',
|
||||
isSelected && !isHighlighted && 'bg-accent/50',
|
||||
isHighlighted && 'bg-accent',
|
||||
!isSelected && !isHighlighted && 'hover:bg-muted',
|
||||
(isSelected || isHighlighted) && 'text-accent-foreground',
|
||||
'hover:bg-accent',
|
||||
'focus:bg-accent',
|
||||
isLoaded ? 'text-popover-foreground' : 'text-muted-foreground'
|
||||
]}
|
||||
onclick={() => onSelect(option.id)}
|
||||
@@ -79,9 +85,12 @@
|
||||
>
|
||||
<ModelId
|
||||
aliases={option.aliases}
|
||||
{capabilities}
|
||||
class="flex-1"
|
||||
{hideOrgName}
|
||||
{modalities}
|
||||
modelId={option.model}
|
||||
showRawTooltip
|
||||
tags={option.tags}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ModelModality } from '$lib/enums';
|
||||
import type { ModelOption } from '$lib/types/models';
|
||||
import { SvelteMap } from 'svelte/reactivity';
|
||||
|
||||
@@ -17,6 +18,23 @@ export interface GroupedModelOptions {
|
||||
available: OrgGroup[];
|
||||
}
|
||||
|
||||
function matchesModality(option: ModelOption, term: string): boolean {
|
||||
const modalities = option.modalities;
|
||||
|
||||
if (!modalities) return false;
|
||||
|
||||
switch (term) {
|
||||
case ModelModality.VISION.toLowerCase():
|
||||
return modalities.vision;
|
||||
case ModelModality.AUDIO.toLowerCase():
|
||||
return modalities.audio;
|
||||
case ModelModality.VIDEO.toLowerCase():
|
||||
return modalities.video;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function filterModelOptions(options: ModelOption[], searchTerm: string): ModelOption[] {
|
||||
const term = searchTerm.trim().toLowerCase();
|
||||
|
||||
@@ -27,7 +45,8 @@ export function filterModelOptions(options: ModelOption[], searchTerm: string):
|
||||
option.model.toLowerCase().includes(term) ||
|
||||
option.name?.toLowerCase().includes(term) ||
|
||||
option.aliases?.some((alias: string) => alias.toLowerCase().includes(term)) ||
|
||||
option.tags?.some((tag: string) => tag.toLowerCase().includes(term))
|
||||
option.tags?.some((tag: string) => tag.toLowerCase().includes(term)) ||
|
||||
matchesModality(option, term)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
<div class="py-8 text-center text-sm text-muted-foreground">No tools available</div>
|
||||
{:else}
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Applies to new conversations. Tool picks inside a chat only affect that chat.
|
||||
</p>
|
||||
|
||||
{#each groups as group (group.key)}
|
||||
{@const isExpanded = expandedGroups.has(group.key)}
|
||||
<Collapsible.Root onOpenChange={() => toggleExpanded(group.key)} open={isExpanded}>
|
||||
@@ -37,6 +41,17 @@
|
||||
<ChevronRight class="h-3.5 w-3.5 shrink-0" />
|
||||
{/if}
|
||||
|
||||
{@const isCategoryEnabled =
|
||||
group.source !== ToolSource.MCP && toolsStore.isCategoryEnabled(group.source)}
|
||||
|
||||
{#if group.source !== ToolSource.MCP}
|
||||
<Checkbox
|
||||
checked={isCategoryEnabled}
|
||||
onCheckedChange={() => toolsStore.toggleCategory(group.source)}
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{@const faviconUrl = group.serverId ? mcpStore.getServerFavicon(group.serverId) : null}
|
||||
|
||||
<span class="inline-flex min-w-0 items-center gap-1.5 font-medium">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Empty from '$lib/components/ui/empty';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
import { conversationsStore, mcpStore, toolsStore } from '$lib/stores';
|
||||
import { mcpStore, toolsStore } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
@@ -86,15 +86,13 @@
|
||||
<McpServerCardSkeleton />
|
||||
{:else}
|
||||
<McpServerCard
|
||||
enabled={conversationsStore.preferences.isMcpServerEnabledForChat(server.id)}
|
||||
enabled={server.enabled}
|
||||
onBrowseResources={() => (isResourcesDialogOpen = true)}
|
||||
onDelete={() => mcpStore.removeServer(server.id)}
|
||||
onToggle={async () => {
|
||||
const wasEnabled = conversationsStore.preferences.isMcpServerEnabledForChat(
|
||||
server.id
|
||||
);
|
||||
const wasEnabled = server.enabled;
|
||||
|
||||
await conversationsStore.preferences.toggleMcpServerForChat(server.id);
|
||||
mcpStore.updateServer(server.id, { enabled: !wasEnabled });
|
||||
|
||||
if (!wasEnabled) {
|
||||
// Promote the connection so tools/prompts/resources become
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
>
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<div class="text-current transition-none" data-slot="checkbox-indicator">
|
||||
{#if checked}
|
||||
<CheckIcon class="size-3.5" />
|
||||
{:else if indeterminate}
|
||||
{#if indeterminate}
|
||||
<MinusIcon class="size-3.5" />
|
||||
{:else if checked}
|
||||
<CheckIcon class="size-3.5" />
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { FolderOpen, MessageSquare, Zap } from '@lucide/svelte';
|
||||
import { FILE_TYPE_ICONS } from '$lib/constants';
|
||||
import {
|
||||
AttachmentAction,
|
||||
AttachmentItemEnabledWhen,
|
||||
AttachmentItemVisibleWhen,
|
||||
AttachmentMenuItemId
|
||||
} from '$lib/enums';
|
||||
import { AttachmentAction, AttachmentItemEnabledWhen, AttachmentMenuItemId } from '$lib/enums';
|
||||
import type { AttachmentMenuItem } from '$lib/types';
|
||||
|
||||
/**
|
||||
@@ -58,36 +52,4 @@ export const ATTACHMENT_FILE_ITEMS: AttachmentMenuItem[] = [
|
||||
}
|
||||
];
|
||||
|
||||
export const ATTACHMENT_EXTRA_ITEMS: AttachmentMenuItem[] = [];
|
||||
|
||||
export const ATTACHMENT_PROMPT_ITEMS: AttachmentMenuItem[] = [
|
||||
{
|
||||
action: AttachmentAction.SYSTEM_PROMPT_CLICK,
|
||||
enabledWhen: AttachmentItemEnabledWhen.ALWAYS,
|
||||
hasEnabledTooltip: true,
|
||||
icon: MessageSquare,
|
||||
id: AttachmentMenuItemId.SYSTEM_MESSAGE,
|
||||
label: 'System Message'
|
||||
},
|
||||
{
|
||||
action: AttachmentAction.MCP_PROMPT_CLICK,
|
||||
enabledWhen: AttachmentItemEnabledWhen.ALWAYS,
|
||||
icon: Zap,
|
||||
id: AttachmentMenuItemId.MCP_PROMPT,
|
||||
label: 'MCP Prompts',
|
||||
visibleWhen: AttachmentItemVisibleWhen.HAS_MCP_PROMPTS_SUPPORT
|
||||
}
|
||||
];
|
||||
|
||||
export const ATTACHMENT_MCP_ITEMS: AttachmentMenuItem[] = [
|
||||
{
|
||||
action: AttachmentAction.MCP_RESOURCES_CLICK,
|
||||
enabledWhen: AttachmentItemEnabledWhen.ALWAYS,
|
||||
icon: FolderOpen,
|
||||
id: AttachmentMenuItemId.MCP_RESOURCES,
|
||||
label: 'MCP Resources',
|
||||
visibleWhen: AttachmentItemVisibleWhen.HAS_MCP_RESOURCES_SUPPORT
|
||||
}
|
||||
];
|
||||
|
||||
export const ATTACHMENT_TOOLTIP_TEXT = 'Add files, prompts, tools or MCP Servers';
|
||||
|
||||
@@ -8,10 +8,13 @@ import {
|
||||
File as FileIcon,
|
||||
FileText as FileTextIcon,
|
||||
Image as ImageIcon,
|
||||
Lightbulb as ReasoningIcon,
|
||||
Mic as AudioIcon,
|
||||
Video as VideoIcon
|
||||
} from '@lucide/svelte';
|
||||
import { FileTypeCategory, ModelModality } from '$lib/enums';
|
||||
import { FileTypeCategory, ModelCapability, ModelModality } from '$lib/enums';
|
||||
import type { ModelCapabilities, ModelModalities } from '$lib/types/models';
|
||||
import type { Component } from 'svelte';
|
||||
|
||||
export const FILE_TYPE_ICONS = {
|
||||
[FileTypeCategory.AUDIO]: AudioIcon,
|
||||
@@ -35,6 +38,29 @@ export const MODALITY_LABELS = {
|
||||
[ModelModality.VISION]: 'Vision'
|
||||
} as const;
|
||||
|
||||
/** Maps an input ModelModality to the boolean flag it drives on the ModelModalities type */
|
||||
export const MODALITY_FLAG_KEYS: Record<
|
||||
Exclude<ModelModality, ModelModality.TEXT>,
|
||||
keyof ModelModalities
|
||||
> = {
|
||||
[ModelModality.AUDIO]: 'audio',
|
||||
[ModelModality.VIDEO]: 'video',
|
||||
[ModelModality.VISION]: 'vision'
|
||||
};
|
||||
|
||||
export const CAPABILITY_ICONS: Record<ModelCapability, Component> = {
|
||||
[ModelCapability.REASONING]: ReasoningIcon
|
||||
} as const;
|
||||
|
||||
export const CAPABILITY_LABELS: Record<ModelCapability, string> = {
|
||||
[ModelCapability.REASONING]: 'Reasoning'
|
||||
} as const;
|
||||
|
||||
/** Maps a ModelCapability to the boolean flag it drives on the ModelCapabilities type */
|
||||
export const CAPABILITY_FLAG_KEYS: Record<ModelCapability, keyof ModelCapabilities> = {
|
||||
[ModelCapability.REASONING]: 'reasoning'
|
||||
};
|
||||
|
||||
// Shared SVG icon strings for copy and preview buttons
|
||||
export const COPY_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy-icon lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>`;
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ export const SETTINGS_KEYS = {
|
||||
SHOW_FULL_PATH_IN_MENTIONS: 'showFullPathInMentions',
|
||||
// Display
|
||||
SHOW_MESSAGE_STATS: 'showMessageStats',
|
||||
SHOW_MODEL_ORG_NAME_IN_TRIGGER: 'showModelOrgNameInTrigger',
|
||||
SHOW_MODEL_QUANTIZATION: 'showModelQuantization',
|
||||
SHOW_MODEL_TAGS: 'showModelTags',
|
||||
SHOW_RAW_MODEL_NAMES: 'showRawModelNames',
|
||||
|
||||
@@ -111,9 +111,8 @@ export const SETTINGS_REGISTRY: SettingsSectionEntry[] = [
|
||||
type: SettingsFieldType.CHECKBOX
|
||||
},
|
||||
{
|
||||
defaultValue: false,
|
||||
defaultValue: true,
|
||||
help: 'Automatically show microphone button instead of send button when textarea is empty for models with audio modality support.',
|
||||
isExperimental: true,
|
||||
key: SETTINGS_KEYS.AUTO_MIC_ON_EMPTY,
|
||||
label: 'Show microphone on empty input',
|
||||
type: SettingsFieldType.CHECKBOX
|
||||
@@ -283,6 +282,13 @@ export const SETTINGS_REGISTRY: SettingsSectionEntry[] = [
|
||||
label: 'Show model tags',
|
||||
type: SettingsFieldType.CHECKBOX
|
||||
},
|
||||
{
|
||||
defaultValue: false,
|
||||
help: 'Display the organization name in the model selector trigger button.',
|
||||
key: SETTINGS_KEYS.SHOW_MODEL_ORG_NAME_IN_TRIGGER,
|
||||
label: 'Show organization name in model selector trigger',
|
||||
type: SettingsFieldType.CHECKBOX
|
||||
},
|
||||
{
|
||||
defaultValue: false,
|
||||
help: 'Display the current build version in the bottom-right corner of the interface.',
|
||||
|
||||
@@ -20,6 +20,9 @@ export const DISABLED_TOOLS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.disabledTool
|
||||
|
||||
/** Disabled tools keyed by stable selection identity, no migration from the name based key */
|
||||
export const DISABLED_TOOL_KEYS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.disabledToolKeys`;
|
||||
|
||||
/** Default disabled tool categories, seeded into newly created conversations */
|
||||
export const DISABLED_TOOL_CATEGORIES_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.disabledToolCategories`;
|
||||
export const FAVORITE_MODELS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.favoriteModels`;
|
||||
export const REASONING_EFFORT_DEFAULT_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.reasoningEffortDefault`;
|
||||
export const CONVERSATION_TABS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.conversationTabs`;
|
||||
|
||||
@@ -19,8 +19,6 @@ export enum AttachmentType {
|
||||
export enum AttachmentMenuItemId {
|
||||
AUDIO = 'audio',
|
||||
IMAGES = 'images',
|
||||
MCP_PROMPT = 'mcp-prompt',
|
||||
MCP_RESOURCES = 'mcp-resources',
|
||||
PDF = 'pdf',
|
||||
SYSTEM_MESSAGE = 'system-message',
|
||||
TEXT = 'text',
|
||||
@@ -42,8 +40,6 @@ export enum AttachmentItemEnabledWhen {
|
||||
*/
|
||||
export enum AttachmentAction {
|
||||
FILE_UPLOAD = 'onFileUpload',
|
||||
MCP_PROMPT_CLICK = 'onMcpPromptClick',
|
||||
MCP_RESOURCES_CLICK = 'onMcpResourcesClick',
|
||||
SYSTEM_PROMPT_CLICK = 'onSystemPromptClick'
|
||||
}
|
||||
|
||||
@@ -56,11 +52,3 @@ export enum AttachmentLabel {
|
||||
MCP_RESOURCE = 'MCP Resource',
|
||||
PDF_FILE = 'PDF File'
|
||||
}
|
||||
|
||||
/**
|
||||
* Visibility conditions for attachment menu items.
|
||||
*/
|
||||
export enum AttachmentItemVisibleWhen {
|
||||
HAS_MCP_PROMPTS_SUPPORT = 'hasMcpPromptsSupport',
|
||||
HAS_MCP_RESOURCES_SUPPORT = 'hasMcpResourcesSupport'
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ export {
|
||||
AttachmentType,
|
||||
AttachmentMenuItemId,
|
||||
AttachmentItemEnabledWhen,
|
||||
AttachmentAction,
|
||||
AttachmentItemVisibleWhen
|
||||
AttachmentAction
|
||||
} from './attachment.enums';
|
||||
|
||||
export {
|
||||
@@ -68,7 +67,7 @@ export {
|
||||
JsonSchemaType
|
||||
} from './mcp.enums';
|
||||
|
||||
export { ModelModality } from './model.enums';
|
||||
export { ModelCapability, ModelModality } from './model.enums';
|
||||
|
||||
export { ServerRole, ServerModelStatus, ServerModelsSseEventType } from './server.enums';
|
||||
|
||||
|
||||
@@ -4,3 +4,7 @@ export enum ModelModality {
|
||||
VIDEO = 'VIDEO',
|
||||
VISION = 'VISION'
|
||||
}
|
||||
|
||||
export enum ModelCapability {
|
||||
REASONING = 'REASONING'
|
||||
}
|
||||
|
||||
@@ -5,21 +5,16 @@ export interface AttachmentModalityFlags {
|
||||
hasVisionModality: boolean;
|
||||
hasAudioModality: boolean;
|
||||
hasVideoModality: boolean;
|
||||
hasMcpPromptsSupport: boolean;
|
||||
hasMcpResourcesSupport: boolean;
|
||||
}
|
||||
|
||||
export interface AttachmentActionCallbacks {
|
||||
onFileUpload?: () => void;
|
||||
onSystemPromptClick?: () => void;
|
||||
onMcpPromptClick?: () => void;
|
||||
onMcpResourcesClick?: () => void;
|
||||
}
|
||||
|
||||
export interface UseAttachmentMenuReturn {
|
||||
readonly callbacks: Record<string, () => void>;
|
||||
isItemEnabled(enabledWhen: string | undefined): boolean;
|
||||
isItemVisible(visibleWhen: string | undefined): boolean;
|
||||
getSystemMessageTooltip(): string;
|
||||
}
|
||||
|
||||
@@ -49,8 +44,6 @@ export function useAttachmentMenu(
|
||||
|
||||
return {
|
||||
[AttachmentAction.FILE_UPLOAD]: wrap(cbs.onFileUpload),
|
||||
[AttachmentAction.MCP_PROMPT_CLICK]: wrap(cbs.onMcpPromptClick),
|
||||
[AttachmentAction.MCP_RESOURCES_CLICK]: wrap(cbs.onMcpResourcesClick),
|
||||
[AttachmentAction.SYSTEM_PROMPT_CLICK]: wrap(cbs.onSystemPromptClick)
|
||||
};
|
||||
});
|
||||
@@ -61,12 +54,6 @@ export function useAttachmentMenu(
|
||||
return !!modalityFlags[enabledWhen as keyof AttachmentModalityFlags];
|
||||
}
|
||||
|
||||
function isItemVisible(visibleWhen: string | undefined): boolean {
|
||||
if (!visibleWhen) return true;
|
||||
|
||||
return !!modalityFlags[visibleWhen as keyof AttachmentModalityFlags];
|
||||
}
|
||||
|
||||
function getSystemMessageTooltip(): string {
|
||||
return !page.params.id
|
||||
? 'Add custom system message for a new conversation'
|
||||
@@ -78,7 +65,6 @@ export function useAttachmentMenu(
|
||||
return callbacks;
|
||||
},
|
||||
getSystemMessageTooltip,
|
||||
isItemEnabled,
|
||||
isItemVisible
|
||||
isItemEnabled
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getConversationModel } from '$lib/utils';
|
||||
export interface UseReasoningMenuReturn {
|
||||
readonly modelSupportsThinking: boolean;
|
||||
readonly thinkingEnabled: boolean;
|
||||
readonly isReasoningActive: boolean;
|
||||
readonly isOff: boolean;
|
||||
readonly currentEffort: ReasoningEffort;
|
||||
readonly levels: ReasoningEffortLevel[];
|
||||
@@ -59,6 +60,12 @@ export function useReasoningMenu(): UseReasoningMenuReturn {
|
||||
const thinkingEnabled = $derived(
|
||||
currentEffort !== ReasoningEffort.OFF && currentEffort !== ReasoningEffort.DEFAULT
|
||||
);
|
||||
// Thinking is effectively on (lightbulb lit) either when an explicit effort
|
||||
// is selected, or when the effort is left at "Default" and the model
|
||||
// supports thinking.
|
||||
const isReasoningActive = $derived(
|
||||
thinkingEnabled || (currentEffort === ReasoningEffort.DEFAULT && modelSupportsThinking)
|
||||
);
|
||||
|
||||
return {
|
||||
get currentEffort() {
|
||||
@@ -67,6 +74,9 @@ export function useReasoningMenu(): UseReasoningMenuReturn {
|
||||
get isOff() {
|
||||
return currentEffort === ReasoningEffort.OFF;
|
||||
},
|
||||
get isReasoningActive() {
|
||||
return isReasoningActive;
|
||||
},
|
||||
isSelected(level: ReasoningEffortLevel): boolean {
|
||||
return currentEffort === level.value;
|
||||
},
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { CLI_FLAGS } from '$lib/constants';
|
||||
import { ToolSource } from '$lib/enums';
|
||||
import { conversationsStore, mcpStore, toolsStore } from '$lib/stores';
|
||||
import type { ToolGroup } from '$lib/types';
|
||||
import type { ToolEntry, ToolGroup } from '$lib/types';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
|
||||
export interface UseToolsPanelReturn {
|
||||
readonly expandedGroups: SvelteSet<string>;
|
||||
readonly groups: ToolGroup[];
|
||||
readonly activeGroups: ToolGroup[];
|
||||
readonly categoryGroups: ToolGroup[];
|
||||
readonly mcpGroups: ToolGroup[];
|
||||
readonly totalToolCount: number;
|
||||
readonly noToolsInfoMessage: string | null;
|
||||
isGroupChecked(group: ToolGroup): boolean;
|
||||
getEnabledToolCount(group: ToolGroup): number;
|
||||
getGroupCheckState(group: ToolGroup): { checked: boolean; indeterminate: boolean };
|
||||
getFavicon(group: ToolGroup): string | null;
|
||||
isGroupDisabled(group: ToolGroup): boolean;
|
||||
isToolEnabled(entry: ToolEntry): boolean;
|
||||
isToolParentDisabled(entry: ToolEntry): boolean;
|
||||
toggleTool(entry: ToolEntry): void;
|
||||
toggleGroupExpanded(key: string): void;
|
||||
/** Toggle all tools in a group by its stable key (avoids stale group object references). */
|
||||
toggleGroupByKey(key: string): void;
|
||||
@@ -26,19 +30,18 @@ export interface UseToolsPanelReturn {
|
||||
* Used by both the desktop dropdown (`ChatFormActionAddToolsSubmenu`)
|
||||
* and the mobile sheet (`ChatFormActionAddSheet`) to avoid
|
||||
* duplicating group filtering, checked-state derivation, and favicon logic.
|
||||
*
|
||||
* All toggle state routes through `conversationsStore.preferences`: with an
|
||||
* active conversation it edits that conversation's tool policy, on the
|
||||
* new-chat screen it edits the global defaults seeded into new conversations.
|
||||
*/
|
||||
export function useToolsPanel(): UseToolsPanelReturn {
|
||||
const expandedGroups = new SvelteSet<string>();
|
||||
const groups = $derived(toolsStore.toolGroups);
|
||||
const activeGroups = $derived(
|
||||
groups.filter(
|
||||
(g) =>
|
||||
g.source !== ToolSource.MCP ||
|
||||
!g.serverId ||
|
||||
conversationsStore.preferences.isMcpServerEnabledForChat(g.serverId)
|
||||
)
|
||||
);
|
||||
const totalToolCount = $derived(activeGroups.reduce((n, g) => n + g.tools.length, 0));
|
||||
// non-MCP groups are 1:1 with tool categories; MCP tools group per server
|
||||
const categoryGroups = $derived(groups.filter((g) => g.source !== ToolSource.MCP));
|
||||
const mcpGroups = $derived(groups.filter((g) => g.source === ToolSource.MCP));
|
||||
const totalToolCount = $derived(groups.reduce((n, g) => n + g.tools.length, 0));
|
||||
const noToolsInfoMessage = $derived.by(() => {
|
||||
if (toolsStore.loading) return null;
|
||||
|
||||
@@ -56,11 +59,27 @@ export function useToolsPanel(): UseToolsPanelReturn {
|
||||
});
|
||||
|
||||
function isGroupChecked(group: ToolGroup): boolean {
|
||||
return toolsStore.isGroupFullyEnabled(group);
|
||||
return conversationsStore.preferences.isGroupChecked(group);
|
||||
}
|
||||
|
||||
function getEnabledToolCount(group: ToolGroup): number {
|
||||
return group.tools.filter((tool) => toolsStore.isToolEnabled(tool.key)).length;
|
||||
return group.tools.filter((tool) => conversationsStore.preferences.isToolActive(tool)).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group checkbox state: checked is the parent flag (category on, or the
|
||||
* server key on for MCP groups); indeterminate marks the mixed case where
|
||||
* the parent is on but nothing or only part of the group is enabled.
|
||||
* isToolActive folds the parent gates into the count, so a disabled parent
|
||||
* always yields plain unchecked.
|
||||
*/
|
||||
function getGroupCheckState(group: ToolGroup): { checked: boolean; indeterminate: boolean } {
|
||||
const checked = isGroupChecked(group);
|
||||
const enabledCount = getEnabledToolCount(group);
|
||||
const indeterminate =
|
||||
group.tools.length > 0 && (enabledCount === 0 ? checked : enabledCount < group.tools.length);
|
||||
|
||||
return { checked, indeterminate };
|
||||
}
|
||||
|
||||
function getFavicon(group: ToolGroup): string | null {
|
||||
@@ -70,13 +89,25 @@ export function useToolsPanel(): UseToolsPanelReturn {
|
||||
}
|
||||
|
||||
function isGroupDisabled(group: ToolGroup): boolean {
|
||||
// MCP server groups gray out while the whole MCP category is off
|
||||
return (
|
||||
group.source === ToolSource.MCP &&
|
||||
!!group.serverId &&
|
||||
!conversationsStore.preferences.isMcpServerEnabledForChat(group.serverId)
|
||||
!conversationsStore.preferences.isCategoryEnabled(ToolSource.MCP)
|
||||
);
|
||||
}
|
||||
|
||||
function isToolEnabled(entry: ToolEntry): boolean {
|
||||
return conversationsStore.preferences.isToolEnabled(entry.key);
|
||||
}
|
||||
|
||||
function isToolParentDisabled(entry: ToolEntry): boolean {
|
||||
return conversationsStore.preferences.isToolParentDisabled(entry);
|
||||
}
|
||||
|
||||
function toggleTool(entry: ToolEntry): void {
|
||||
void conversationsStore.preferences.toggleTool(entry.key);
|
||||
}
|
||||
|
||||
function toggleGroupExpanded(key: string): void {
|
||||
if (expandedGroups.has(key)) {
|
||||
expandedGroups.delete(key);
|
||||
@@ -87,11 +118,11 @@ export function useToolsPanel(): UseToolsPanelReturn {
|
||||
|
||||
function toggleGroupByKey(key: string): void {
|
||||
// Find current group by key to get up-to-date tool references
|
||||
const group = activeGroups.find((g) => g.key === key);
|
||||
const group = groups.find((g) => g.key === key);
|
||||
|
||||
if (!group) return;
|
||||
|
||||
toolsStore.toggleGroup(group);
|
||||
void conversationsStore.preferences.toggleGroup(group);
|
||||
}
|
||||
|
||||
function handleOpen(): void {
|
||||
@@ -103,23 +134,27 @@ export function useToolsPanel(): UseToolsPanelReturn {
|
||||
}
|
||||
|
||||
return {
|
||||
get activeGroups() {
|
||||
return activeGroups;
|
||||
get categoryGroups() {
|
||||
return categoryGroups;
|
||||
},
|
||||
expandedGroups,
|
||||
getEnabledToolCount,
|
||||
getFavicon,
|
||||
get groups() {
|
||||
return groups;
|
||||
},
|
||||
getGroupCheckState,
|
||||
handleOpen,
|
||||
isGroupChecked,
|
||||
isGroupDisabled,
|
||||
isToolEnabled,
|
||||
isToolParentDisabled,
|
||||
get mcpGroups() {
|
||||
return mcpGroups;
|
||||
},
|
||||
get noToolsInfoMessage() {
|
||||
return noToolsInfoMessage;
|
||||
},
|
||||
toggleGroupByKey,
|
||||
toggleGroupExpanded,
|
||||
toggleTool,
|
||||
get totalToolCount() {
|
||||
return totalToolCount;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user