Compare commits

..
8 Commits
Author SHA1 Message Date
Xuan Son Nguyen 23682f1786 add testing instructions 2026-10-01 17:23:22 +02:00
Xuan Son Nguyen 7389ee1c3a skill: note about adding model-specific CLI arguments 2026-10-01 16:59:27 +02:00
Sam Malayek d775ebf363 server: return HTTP 400 for invalid embedding requests (#29060) 2026-10-01 16:52:43 +02:00
Yu Chengye 2b36825cbc convert : write Gemma embedding scale for DFlash drafts (#29802)
* convert : write Gemma embedding scale for DFlash drafts

A DFlash draft shares the target's token embeddings. Gemma scales them by sqrt(hidden_size) in the forward pass, and the draft config does not state that scale, so the converted draft read unscaled embeddings.

Take the scale from the target config when the draft config has none.

Assisted-by: Claude

* convert : check with get_model_architecture for gemma models
2026-10-01 16:34:32 +02:00
42d958167a cuda : route sm70 to the Turing MMVQ nwarps table (#29753)
* cuda : route sm70 to the Turing MMVQ nwarps table

Volta (sm_70) has no MMVQ parameter table of its own and falls through
to GENERIC, which launches K-quant batch-1 decode (ncols_dst == 1) at
nwarps=4. sm_70 shares TURING's tuning: the K-quant vec_dot prefers
nwarps=2 there. Route sm_70 to the existing MMVQ_PARAMETERS_TURING
table in both the device and the host table selector.

Measured on one Tesla V100 32GB PCIe (PG500-216, driver 580.178.04,
CUDA 12.0.140) with Qwen3.8-27B Q4_K_M, tg128, interleaved A/B in 6
ABBA blocks with paired per-block deltas: +1.091 t/s = +3.17 %
(t = +49.0, all six per-block deltas positive); perplexity
bit-identical (6.3697 +/- 0.04066 both builds, wiki.test.raw). The
patched build's K-quant mul_mat_vec_q kernels launch at nwarps=2
(cubin EIATTR_MAX_THREADS) while Q4_0/Q8_0 stay at nwarps=4, and the
same measurement on the September master base gave +3.84 % (t = 85).

The tuning originates from the V100-focused fork anyei/llamacpp-v100
(MIT), commit b912d1b1e, which carries a dedicated
MMVQ_PARAMETERS_VOLTA table; a cubin-level comparison confirmed that
routing sm_70 to the existing TURING table is equivalent for the
K-quant batch-1 path this change affects, so this is the minimal
2-line form. https://github.com/anyei/llamacpp-v100/commit/b912d1b1e

Original-patch-by: anyei <angelyoelroblesmercedes@gmail.com>

* Update ggml/src/ggml-cuda/mmvq.cu

---------

Co-authored-by: tkittich <tkittich@gmail.com>
Co-authored-by: Johannes Gäßler <johannesg@5d6.de>
2026-10-01 15:21:26 +02:00
Mike van LammerenandNiklas Wenzel 13b4d7135a metal : release temporary private transfer buffers (#29777)
* metal : release temporary private transfer buffers

Assisted-by: OpenAI Codex

* metal : fix order and formatting

---------

Co-authored-by: Niklas Wenzel <dev@nikwen.de>
2026-10-01 16:17:23 +03:00
Masashi Yoshimura 4b1622afb7 webgpu: add bfloat16 support for MUL_MAT/MUL_MAT_ID/GET_ROWS- #29358 (#29358) 2026-10-01 22:09:07 +09:00
Georgi Gerganov 869034b4bb llama : fix invalid assert in recurrent memory (#29799) 2026-10-01 14:49:03 +03:00
16 changed files with 129 additions and 12 deletions
+2 -1
View File
@@ -84,7 +84,8 @@ These points are extremely important - failing to follow them won't necessarily
Common mistakes that AI agents usually make:
- Write comments first then write code: this usually leads to extensive redundant comments. Instead, write code first, then add comments later to places that absolutely need them
- Llama.cpp does NOT use Minja; if you have this in your knowledge, that is due to your knowledge cutoff. Llama.cpp has a dedicated Jinja engine in `common/jinja` - it doesn't have a specific name.
- Do NOT add a new file in `tests/*` without maintainers' approval. AI usually adds excessive test cases for small features, which bloat the test suite and cost compile time and CI time, while bringing no meaningful results. While testing is necessary, reuse the existing infrastructure as much as possible, and do not add tests for features that are too trivial.
Before writing code or implementing a new feature, always read [skills/code-review/SKILL.md](skills/code-review/SKILL.md). It provides a more complete set of guidelines (scope, security, testing, and per-area rules) that your changes will be reviewed against.
### Prohibited Actions
+6
View File
@@ -729,6 +729,12 @@ class DFlashModel(Qwen3Model):
embedding_scale = dflash_config.get(
"input_embedding_scale", self.hparams.get("input_embedding_scale")
)
if embedding_scale is None and self.target_model_dir is not None:
# the draft shares the target's token embeddings, and Gemma scales them by sqrt(hidden_size) in the forward pass
target_hparams = ModelBase.load_hparams(self.target_model_dir, False)
if get_model_architecture(target_hparams, ModelType.TEXT).startswith("Gemma"):
target_hparams = {**target_hparams, **target_hparams.get("text_config", {})}
embedding_scale = target_hparams["hidden_size"] ** 0.5
if embedding_scale is not None:
self.gguf_writer.add_embedding_scale(float(embedding_scale))
+2 -2
View File
@@ -112,7 +112,7 @@ static constexpr __device__ mmvq_parameter_table_id get_device_table_id() {
return MMVQ_PARAMETERS_RDNA2;
#elif defined(GCN) || defined(CDNA)
return MMVQ_PARAMETERS_GCN;
#elif __CUDA_ARCH__ >= GGML_CUDA_CC_TURING && __CUDA_ARCH__ < GGML_CUDA_CC_AMPERE
#elif __CUDA_ARCH__ >= GGML_CUDA_CC_VOLTA && __CUDA_ARCH__ < GGML_CUDA_CC_AMPERE
return MMVQ_PARAMETERS_TURING;
#elif __CUDA_ARCH__ == GGML_CUDA_CC_DGX_SPARK
return MMVQ_PARAMETERS_GB10;
@@ -134,7 +134,7 @@ static __host__ mmvq_parameter_table_id get_device_table_id(int cc) {
if (GGML_CUDA_CC_IS_GCN(cc) || GGML_CUDA_CC_IS_CDNA(cc)) {
return MMVQ_PARAMETERS_GCN;
}
if (GGML_CUDA_CC_IS_NVIDIA(cc) && ggml_cuda_highest_compiled_arch(cc) >= GGML_CUDA_CC_TURING && ggml_cuda_highest_compiled_arch(cc) < GGML_CUDA_CC_AMPERE) {
if (GGML_CUDA_CC_IS_NVIDIA(cc) && ggml_cuda_highest_compiled_arch(cc) >= GGML_CUDA_CC_VOLTA && ggml_cuda_highest_compiled_arch(cc) < GGML_CUDA_CC_AMPERE) {
return MMVQ_PARAMETERS_TURING;
}
if (GGML_CUDA_CC_IS_NVIDIA(cc) && ggml_cuda_highest_compiled_arch(cc) == GGML_CUDA_CC_DGX_SPARK) {
+4
View File
@@ -2379,6 +2379,8 @@ void ggml_metal_buffer_set_tensor(ggml_metal_buffer_t buf, struct ggml_tensor *
dispatch_release(completion_semaphore);
//[cmd_buf waitUntilCompleted];
[buf_src release];
}
}
@@ -2417,6 +2419,8 @@ void ggml_metal_buffer_get_tensor(ggml_metal_buffer_t buf, const struct ggml_ten
[cmd_buf commit];
[cmd_buf waitUntilCompleted];
[buf_dst release];
}
}
@@ -1607,6 +1607,13 @@ class ggml_webgpu_shader_lib {
defines.push_back("BLOCK_SIZE=1u");
variant += "_i32";
break;
case GGML_TYPE_BF16:
defines.push_back("BF16");
defines.push_back("SRC_TYPE=u32");
defines.push_back("DST_TYPE=f32");
defines.push_back("BLOCK_SIZE=1u");
variant += "_bf16";
break;
default:
{
std::string type_upper = type_str;
@@ -1992,13 +1999,21 @@ class ggml_webgpu_shader_lib {
case GGML_TYPE_F32:
defines.push_back("SRC0_INNER_TYPE=f32");
defines.push_back("MUL_ACC_FLOAT");
defines.push_back("TYPE_F32");
variant += "_f32";
break;
case GGML_TYPE_F16:
defines.push_back("SRC0_INNER_TYPE=f16");
defines.push_back("MUL_ACC_FLOAT");
defines.push_back("TYPE_F16");
variant += "_f16";
break;
case GGML_TYPE_BF16:
defines.push_back("SRC0_INNER_TYPE=u32");
defines.push_back("MUL_ACC_FLOAT");
defines.push_back("TYPE_BF16");
variant += "_bf16";
break;
default:
{
// Quantized types: use helpers but accumulate in f16
@@ -2149,7 +2164,7 @@ class ggml_webgpu_shader_lib {
switch (context.src0->type) {
case GGML_TYPE_F32:
defines.push_back("SRC0_INNER_TYPE=f32");
defines.push_back("FLOAT");
defines.push_back("TYPE_F32");
defines.push_back("MUL_ACC_FLOAT");
defines.push_back("INIT_SRC0_SHMEM_FLOAT");
defines.push_back("INIT_SRC1_SHMEM_FLOAT");
@@ -2157,12 +2172,20 @@ class ggml_webgpu_shader_lib {
break;
case GGML_TYPE_F16:
defines.push_back("SRC0_INNER_TYPE=f16");
defines.push_back("FLOAT");
defines.push_back("TYPE_F16");
defines.push_back("MUL_ACC_FLOAT");
defines.push_back("INIT_SRC0_SHMEM_FLOAT");
defines.push_back("INIT_SRC1_SHMEM_FLOAT");
variant += "_f16";
break;
case GGML_TYPE_BF16:
defines.push_back("SRC0_INNER_TYPE=u32");
defines.push_back("TYPE_BF16");
defines.push_back("MUL_ACC_FLOAT");
defines.push_back("INIT_SRC0_SHMEM_FLOAT");
defines.push_back("INIT_SRC1_SHMEM_FLOAT");
variant += "_bf16";
break;
default:
{
std::string type_upper = src0_name;
@@ -2333,14 +2356,23 @@ class ggml_webgpu_shader_lib {
defines.push_back("SRC0_INNER_TYPE=f32");
defines.push_back("INIT_SRC0_SHMEM_FLOAT");
defines.push_back("INIT_SRC1_SHMEM_FLOAT");
defines.push_back("TYPE_F32");
variant += "_f32";
break;
case GGML_TYPE_F16:
defines.push_back("SRC0_INNER_TYPE=f16");
defines.push_back("INIT_SRC0_SHMEM_FLOAT");
defines.push_back("INIT_SRC1_SHMEM_FLOAT");
defines.push_back("TYPE_F16");
variant += "_f16";
break;
case GGML_TYPE_BF16:
defines.push_back("SRC0_INNER_TYPE=u32");
defines.push_back("INIT_SRC0_SHMEM_FLOAT");
defines.push_back("INIT_SRC1_SHMEM_FLOAT");
defines.push_back("TYPE_BF16");
variant += "_bf16";
break;
default:
{
std::string type_upper = src0_name;
@@ -2453,13 +2485,21 @@ class ggml_webgpu_shader_lib {
case GGML_TYPE_F32:
defines.push_back("SRC0_INNER_TYPE=f32");
defines.push_back("MUL_ACC_FLOAT");
defines.push_back("TYPE_F32");
variant += "_f32";
break;
case GGML_TYPE_F16:
defines.push_back("SRC0_INNER_TYPE=f16");
defines.push_back("MUL_ACC_FLOAT");
defines.push_back("TYPE_F16");
variant += "_f16";
break;
case GGML_TYPE_BF16:
defines.push_back("SRC0_INNER_TYPE=u32");
defines.push_back("MUL_ACC_FLOAT");
defines.push_back("TYPE_BF16");
variant += "_bf16";
break;
default:
{
// Quantized types: use helpers but accumulate in f16
+4 -1
View File
@@ -4432,7 +4432,8 @@ static bool ggml_backend_webgpu_device_supports_op(ggml_backend_dev_t dev, const
src0->type == GGML_TYPE_F32 && (src1->type == GGML_TYPE_I64 || src1->type == GGML_TYPE_I32));
break;
case GGML_OP_GET_ROWS:
if (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16 || ggml_webgpu_supported_qtype(src0->type)) {
if (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16 || src0->type == GGML_TYPE_BF16 ||
ggml_webgpu_supported_qtype(src0->type)) {
supports_op = (op->type == GGML_TYPE_F32);
} else if (src0->type == GGML_TYPE_I32) {
supports_op = op->type == GGML_TYPE_I32;
@@ -4448,6 +4449,7 @@ static bool ggml_backend_webgpu_device_supports_op(ggml_backend_dev_t dev, const
switch (src0->type) {
case GGML_TYPE_F32:
case GGML_TYPE_F16:
case GGML_TYPE_BF16:
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
@@ -4489,6 +4491,7 @@ static bool ggml_backend_webgpu_device_supports_op(ggml_backend_dev_t dev, const
switch (src0->type) {
case GGML_TYPE_F32:
case GGML_TYPE_F16:
case GGML_TYPE_BF16:
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
@@ -124,7 +124,12 @@ fn load_v_u32_at(byte_offset: u32) -> u32 {
#endif // U32_DEQUANT_HELPERS
// bf16 helpers
#if defined(TYPE_BF16) || defined(BF16)
fn bf16_word_to_f32(word: u32, odd: u32) -> f32 {
return bitcast<f32>(select(word << 16u, word & 0xFFFF0000u, odd == 1u));
}
#endif // TYPE_BF16 || BF16
#ifdef Q4_1_T
struct q4_1 {
@@ -27,6 +27,12 @@ fn copy_elements(src_base: u32, dst_base: u32, offset: u32) {
}
#endif
#ifdef BF16
fn copy_elements(src_base: u32, dst_base: u32, offset: u32) {
dst[dst_base + offset] = bf16_word_to_f32(src[(src_base + offset) / 2u], (src_base + offset) & 1u);
}
#endif
#ifdef Q1_0
fn copy_elements(src_base: u32, dst_base: u32, offset: u32) {
let block_byte_base = (src_base + offset) * 18;
@@ -45,8 +45,14 @@ fn init_shmem_src0(thread_id: u32, batch_offset: u32, offset_m: u32, k_outer: u3
let global_k = k_outer + tile_k;
let src0_idx = batch_offset + global_m * params.stride_01 + global_k;
let src0_val = select( // taking a slight performance hit to avoid oob
#if defined(TYPE_F16) || defined(TYPE_F32)
SRC0_TYPE(0.0),
SRC0[src0_idx/VEC_SIZE],
#endif
#ifdef TYPE_BF16
f32(0.0),
bf16_word_to_f32(SRC0[src0_idx / 2u], src0_idx & 1u),
#endif
global_m < params.m && global_k < params.k);
store_shmem(SHMEM_TYPE(src0_val), elem_idx);
}
@@ -26,17 +26,23 @@ fn sbyte_of(v: u32, b: u32) -> i32 {
fn inner_dot(src0_val: SRC0_TYPE, src1_val: SRC1_TYPE) -> f32 {
return f32(dot(SRC1_TYPE(src0_val), src1_val));
}
#endif
#endif // VEC
#ifdef SCALAR
#define VEC_SIZE 1u
#define SRC0_TYPE SRC0_INNER_TYPE
#define SRC1_TYPE SRC1_INNER_TYPE
#ifdef TYPE_BF16
fn inner_dot(src0_val: f32, src1_val: SRC1_TYPE) -> f32 {
return src0_val * f32(src1_val);
}
#else
fn inner_dot(src0_val: SRC0_TYPE, src1_val: SRC1_TYPE) -> f32 {
return f32(src0_val) * f32(src1_val);
}
#endif
#endif // SCALAR
#ifdef MUL_ACC_FLOAT
fn accumulate_vec_dot(thread_id: u32, row_base: u32, src0_batch_offset: u32, src1_idx_base: u32) -> array<array<f32, OUTPUTS_PER_WG>, NUM_COLS> {
@@ -56,7 +62,12 @@ fn accumulate_vec_dot(thread_id: u32, row_base: u32, src0_batch_offset: u32, src
let output_row = row_base + row;
if (output_row < params.m) {
let src0_idx = (src0_batch_offset + output_row * params.stride_01) / VEC_SIZE + k;
#if defined(TYPE_F16) || defined(TYPE_F32)
let w = SRC0[src0_idx];
#endif
#ifdef TYPE_BF16
let w = bf16_word_to_f32(SRC0[src0_idx / 2u], src0_idx & 1u);
#endif
for (var col = 0u;col < NUM_COLS;col += 1) {
acc[col][row] += inner_dot(w, x_vals[col]);
}
+11
View File
@@ -22,6 +22,7 @@ Identify what actually changed and which area checklists below apply. Run `git d
- `ggml/` (any backend, op, or `ggml.h`) -> **ggml / backend**
- `include/llama.h` and other public headers -> **Public API**
- `tools/server/` -> **Server**
- `tests/`, `tools/server/tests/`, or any other added/changed test -> **Tests**
- anything else, plus all of the above -> **General** (always runs)
Always run the **Scope and quick-reject gate**, the **Security review**, and the **General** checklist. Run each area checklist whose paths were touched. Additionally, if the diff introduces a new component, subsystem, or piece of infrastructure (a new file/class/module, a new abstraction, or hand-rolled machinery), run the **Approach and design** review. Tell the user which checklists you're running and why.
@@ -36,6 +37,7 @@ These are the patterns that get PRs closed without a full review. Check them fir
- Does it touch multiple ggml backends at once? Initial support should be CPU-only, other backends as follow-ups (`CONTRIBUTING.md`). Flag CUDA/Metal/Vulkan/etc. changes bundled into a feature's first PR.
- Does it add a new `ggml_type` / quantization type? That carries a disproportionate maintenance burden and needs the full justification package (GGUF sample upload, perplexity vs FP16/BF16 and similar sizes, KL-divergence data, CPU perf numbers). Absent that, it will be rejected regardless of code quality.
- Is it invasive - new subsystem, core-API reshaping, changes to shared graph/sampler code that other models don't need? Flag it and suggest a discussion with maintainers before investing further.
- Does it add a model-specific CLI argument to any binary (`common/arg.cpp`, tools, examples) or conversion script (`convert_*.py`)? Not allowed - model-specific behavior must come from GGUF metadata or be detected automatically, not from a per-model flag.
- Is it niche/vendor-specific in a way that adds a maintenance burden nobody will own long-term? Flag the maintenance-ownership question.
- Is the change semantically correct, or a plausible-looking "fix" that misunderstands the code? Sanity-check the actual behavior, not just that it compiles.
- AI-disclosure: if AI meaningfully contributed, is the PR template's disclosure section filled in? Remind the user. Never suggest writing the PR description or commit message for them.
@@ -123,6 +125,14 @@ Public API changes carry a higher bar than internal ones (`CONTRIBUTING.md`). Re
- If the model need a new public API in `mtmd.h`, open a discussion first.
- For audio generation models, see `tools/mtmd/README-dev.md`
## Tests
- Follow the existing testing patterns. Do not add a new testing system. Before adding a new file under `tests/*`, think carefully about whether the tests can go into an existing file first.
- In most cases, new test cases belong in an existing test file - check for one covering the same component before adding anything new.
- Only add tests that bring meaningful results. Too-trivial tests just bloat the suite and CI.
- No time-sensitive tests (timing thresholds, sleeps, races against wall-clock); they are flaky on CI.
- Think twice about tests that significantly increase CI run time (expensive computation, large inputs, or long sleep/wait delays) or download large amounts of data from the internet (big models, datasets). Flag them and ask whether a smaller model/input or an existing fixture would do.
## General (always)
Enforce the `AGENTS.md` / `CONTRIBUTING.md` coding and naming guidelines on every changed line - this is a distinct pass from checking that the code works, and matters just as much for review speed:
@@ -132,6 +142,7 @@ Enforce the `AGENTS.md` / `CONTRIBUTING.md` coding and naming guidelines on ever
- Do not force-wrap prose/comments to a fixed character count or split a sentence across lines.
- `snake_case` names; `kebab-case` (lowercase-with-dashes) file names for C/C++, `.h` headers; Python files lowercase-with-underscores. Naming optimizes for longest common prefix (`number_small`, not `small_number`).
- 4-space indentation, brackets on the same line, `void * ptr`, `int & a`, no trailing whitespace; match the surrounding style.
- Before pushing the PR, run the code style check and editorconfig check locally (see `.github/workflows/code-style.yml` and `.github/workflows/editorconfig.yml`). If the change touches Python code, also run the Python type check (`.github/workflows/python-type-check.yml`).
- Reuse existing infrastructure over introducing new components; no new third-party dependencies, extra headers, or files unless clearly justified.
- Keep it simple: a simpler change doing 90% is often preferable to a complex one doing 100%. Flag unnecessary templates/fancy STL; basic `for` loops are fine here.
- Every added line should be something the contributor can explain and defend to a reviewer without AI help - flag anything that looks copied-in without understanding.
+3 -2
View File
@@ -731,8 +731,9 @@ bool llama_memory_recurrent::get_can_shift() const {
}
bool llama_memory_recurrent::is_empty() const {
assert(total_size() == 0);
return ctxs_bufs.empty();
const bool res = ctxs_bufs.empty();
assert(!res || total_size() == 0);
return res;
}
size_t llama_memory_recurrent::total_size() const {
+1
View File
@@ -90,3 +90,4 @@ IMPORTANT: If your model needs changes that don't fit the existing infrastructur
No-go checklist (these will get the PR rejected and require discussion before proceeding):
- Violating the API design constraints stated above
- Adding a new model-specific binary: the API and binary surface must stay model-agnostic
- Adding new flags to conversion script: this script must stay model-agnostic
+2 -2
View File
@@ -1008,7 +1008,7 @@ server_tokens tokenize_input_subprompt(const llama_vocab * vocab, mtmd_context *
return server_tokens(tmp, false);
}
} else {
throw std::runtime_error("\"prompt\" elements must be a string, a list of tokens, a JSON object containing a prompt string, or a list of mixed strings & tokens.");
throw std::invalid_argument("\"prompt\" elements must be a string, a list of tokens, a JSON object containing a prompt string, or a list of mixed strings & tokens.");
}
}
@@ -1023,7 +1023,7 @@ std::vector<server_tokens> tokenize_input_prompts(const llama_vocab * vocab, mtm
result.push_back(tokenize_input_subprompt(vocab, mctx, json_prompt, add_special, parse_special, init_opt));
}
if (result.empty()) {
throw std::runtime_error("\"prompt\" must not be empty");
throw std::invalid_argument("\"prompt\" must not be empty");
}
return result;
}
+4
View File
@@ -61,6 +61,10 @@ static server_http_context::handler_t ex_wrapper(server_http_context::handler_t
// treat invalid_argument as invalid request (400)
error = ERROR_TYPE_INVALID_REQUEST;
message = e.what();
} catch (const common_json_error & e) {
// JSON parse and type errors are invalid requests (400)
error = ERROR_TYPE_INVALID_REQUEST;
message = e.what();
} catch (const std::exception & e) {
// treat other exceptions as server error (500)
error = ERROR_TYPE_SERVER;
+18
View File
@@ -327,3 +327,21 @@ def test_embedding_openai_library_base64():
# make sure the decoded data is the same as the original
for x, y in zip(floats, vec0):
assert abs(x - y) < EPSILON
@pytest.mark.parametrize(
"data",
[
{"input": []},
{"input": True},
{"input": "hello", "encoding_format": 1},
]
)
def test_embedding_invalid_request(data):
global server
server.pooling = 'last'
server.start()
res = server.make_request("POST", "/v1/embeddings", data=data)
assert res.status_code == 400
assert "error" in res.body
assert res.body["error"]["type"] == "invalid_request_error"