Compare commits

...
24 Commits
Author SHA1 Message Date
Evgen MykhailenkoandGeorgi Gerganov 824eaecf48 llama : optimize vocabulary loading (#30679)
* llama : optimize vocabulary loading

* Update src/unicode.h

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

* CR : added reserve in gguf_kv_to_str

* CR : trailing space fix

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
2026-10-21 12:03:27 +03:00
Aman Gupta eb182d85c0 llama : place d2t by its set_rows use so it can stay on the GPU (#30912) 2026-10-21 16:32:29 +08:00
Niklas Wenzel ea9e3446b5 metal : fix memory leaks and cleanup logic in GPU capture path (#30890) 2026-10-21 09:54:33 +02:00
Max Krasnyanskyandhubezhou 0efe8e4c6b hexagon: neon-vectorized and threaded repack (tiling) (#30901)
* add vectorized q4_0 repack

* apply to more types

* hex-repack: neon-vectorize the rest of the types q1/q2/f16/...

* hex-repack: add simple chunking / threading

---------

Co-authored-by: hubezhou <hubezhou@qti.qualcomm.com>
2026-10-21 10:48:39 +03:00
lhez b531e4c749 opencl: add dp4a GEMM variants to vector load activation (#30753)
* opencl: add dp4a GEMM variants with vector activation loads

* opencl: rename kernels and env vars
2026-10-21 10:48:13 +03:00
Neo Zhang 74b115ca6e sycl : fix fa crash for Query head dimension mismatch (neq0 != HSK / D=2048/4096) (#30286) 2026-10-21 10:46:43 +03:00
Titaniumtown 96caa80059 sycl: collapse duplicate need_check launch bodies in mmq (#30739) 2026-10-21 10:46:00 +03:00
bosh 479a52779f ggml-cpu : compute f32 gelu_quick and geglu_quick without the fp16 table (#30737)
* ggml-cpu : compute f32 gelu_quick and geglu_quick without the fp16 table

* ggml-cpu : keep the GGML_GELU_QUICK_FP16 table path

* ggml-cpu : add the VXE gelu_quick path

* tests : add test-gelu-quick for the f32 CPU kernels

* tests : drop inf/nan inputs from test-gelu-quick
2026-10-21 09:34:37 +02:00
Georgi Gerganov 8baabd53b7 llama : scale moe down projection per expert token (#30861)
* llama : scale moe down projection per expert token

Replace the global amax rescaling for the MoE down projection with a per-expert-token factor computed by a POOL_1D max reduction along the intermediate dimension. Undo the scale on the output with the same exact power-of-two factor. Handle POOL_1D in the Meta split-state logic so a split along axis 0 becomes a replicated per-device partial result, enabling tensor parallelism.

Assisted-by: pi:llama.cpp/ggml-org/DeepSeek-V4-Flash-Vision-Exp-GGUF (Mac Studio, M2 Ultra, 192GB)

* cont : simplify comments

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

* llama : refactor amax src1 scaling result

Return the rescaled activation and the optional scale tensor through a small struct instead of an output pointer.

Assisted-by: pi:llama.cpp/ggml-org/DeepSeek-V4-Flash-Vision-Exp-GGUF (Mac Studio, M2 Ultra, 192GB)

* cont : add TODO + clean-up
2026-10-21 09:01:00 +03:00
Anant Shrivastava 493253fe5a Sycl iq3 crash fix (#30604) 2026-10-21 01:04:13 -04:00
Ben Ashbaugh 339f3242c7 opencl: fix rope program build error with newer Clang (#30893)
Newer versions of Clang diagnose an incompatible pointer type
error when assigning a pointer to float to a pointer to half.
2026-10-20 17:19:08 -07:00
Unbug Lee 9a3b91c680 server : fix usage.completion_tokens undercount with n > 1 (#29486)
Assisted-by: Copilot
2026-10-21 01:19:54 +02:00
Xuan-Son NguyenandPascal 5c0b49a23b server : add POST /tts endpoint for TTS models (#26603)
* mtmd: add audio out stream api

* add /tts endpoint

* add docs

* wire up mtmd_helper_model_can_chat

* upload speaker_ref via form-data

* expose seed to api

* fix header for pcm

* wip

* fix penalty_last_n

* fix server_output_limits

* fix pocket-tts stream

* wip

* add server_schema for tts

* add server routing

* server: coerce seed and stream from multipart form fields

Form fields arrive as strings, so -F seed=42 and -F stream=true were
rejected by the schema. seed joins the integer fields, parsed as a long
long so a full uint32 seed fits, and stream is read as a boolean when
it comes as a string.

---------

Co-authored-by: Pascal <admin@serveurperso.com>
2026-10-21 01:19:42 +02:00
Xuan-Son NguyenandPascal 1242be66f6 server: add realtime transcription API (#30636)
* server: add realtime transcription API

* drop transcription_sessions

* proxy ws

* add docs

* server: let the API key middleware guard the websocket

httplib runs the pre-routing handler before the websocket upgrade, so
the API key middleware answers 401 at handshake like any other route.
This covers the router proxy, whose children run keyless, and accepts
the same key forms as HTTP (Bearer, raw key, X-Api-Key).

* server: keep realtime slots out of the prompt loop

A realtime slot waiting for audio is GENERATING with nothing to decode,
so it fell through the prompt loop and took slot_batched. A request on
a higher slot then failed can_batch_with on the task type and starved
for the whole session. Realtime slots have no prompt: the loop sets
them up once and always leaves them.

* server: start the realtime session once its slot is live

The audio stream was created in the prompt loop, which skips a slot
that cannot batch with the one generating, and session.created went out
before the task was posted: early appends failed and the reader quit,
leaving the socket open and the slot held until the HTTP timeout.

The slot now sets up its stream at launch and signals it with the begin
result, session.created waits for it, and a reader that stops closes
the socket so the session ends with it.

* server: name the committed item in input_audio_buffer.committed

Per the Realtime spec, item_id is the item made from the committed audio
and previous_item_id the one committed before it, null for the first.
committed now comes before the transcript that completes that item, and
both go out with the item switch under one lock, so a delta cannot land
between them.

* server: use the GA transcription session shape

The session object follows the GA Realtime schema: type transcription,
with the input format, transcription model, turn detection and noise
reduction under audio.input. The rate stays the model sample rate, which
is what the client must send for now.

---------

Co-authored-by: Pascal <admin@serveurperso.com>
2026-10-21 00:04:15 +02:00
Tarek DakhranandGeorgi Gerganov 84e66d0321 refactor : move LFM2 shortconv and FFN blocks into a shared LFM base (#30874)
* llama : move LFM2 shortconv and FFN blocks into a shared LFM base

* Update src/models/lfm-base.cpp

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
2026-10-20 23:17:13 +02:00
Takamichi Ohara 52ffa02745 model : support Nemotron-3(.5)-Embed (#30877) 2026-10-20 22:48:45 +02:00
Aman Gupta 839a572a4c qwen: reshape output to 2d instead of 3d (#30823)
* qwen: reshape output to 2d instead of 3d

* remove final reshape, fix comments
2026-10-20 22:09:56 +03:00
ead9f32058 server: Support logprobs with speculative decoding (#27196)
* return logprobs in spec path

* server: add regression test for logprobs with speculative decoding

Asserts accepted draft tokens report the target model's logprobs,
comparing a draft-enabled run against a no-draft baseline (#24271).
Fails on master (top_logprobs empty, logprob 0.0), passes with the
fix from #27196.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KARBWpPcijcBXzJEvttuwk

---------

Co-authored-by: Jeonghyun Lee <sig.jhl@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-10-20 19:36:14 +02:00
Tarek Dakhran b684b498b6 hexagon: fix VTCM size query on images that need layout out-params (#30655) 2026-10-20 10:22:08 -07:00
Yuri KhrustalevandXuan Son Nguyen 4a45de4f2c mtmd : lfm2 tiles and layout match transformers (#30261)
* mtmd : lfm2 tiles stretch the image to the grid

Lfm2VlImageProcessor.crop_image_to_patches resizes the image straight to
(512 * rows, 512 * cols) before it cuts the tiles. llama.cpp kept the aspect
ratio and added black bars for LFM2 (only d1-omni stretched), so the tiles of
d1-3B and every LFM2-VL / LFM2.5-VL model differed from transformers unless
the image had the aspect ratio of the grid. With PAD_NONE the tiles are the
same as in transformers.

* mtmd : lfm2 layout with Python rounding in double precision

Lfm2VlImageProcessor.smart_resize rounds with Python's round (half to even)
and computes beta and the tile grid aspect ratio in double. llama.cpp used
std::round and float, so 31926 of the 16.7M sizes in [1,4096]^2 got another
thumbnail size or grid than transformers (and d1-omni vision.py), e.g.
400x600 -> 416x608 instead of 384x608 and 720x1600 -> 2x5 tiles instead of
2x4. The lfm2 smart resize and grid choice now follow the Python code and
match it on every size of that range.

* fix

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
2026-10-20 18:54:46 +02:00
7e3eeee048 model : add H2O-Lightning-4B decision model (#30361)
* model : add H2O-Lightning-4B decision model

Adds the h2o-lightning decision type for h2oai/h2o-lightning-4b
(Qwen3.5-4B with a merged decision LoRA).

None of the existing types match this model. It answers with " A"
rather than "A", uses up to 255 labels, and was trained on Python
json.dumps text, so floats like 12.0 must render as 12.0.

The converter writes the decision metadata and the systemone template
from the model's serve_config.json and refuses a config that doesn't
match. Tests skip unless the tiny test model is available locally.

Assisted-by: Claude Opus 5.5

* rm redundant code

* put labels to gguf

---------

Co-authored-by: Jonathan C. McKinney <jon.mckinney@h2o.ai>
Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
2026-10-20 18:54:27 +02:00
Xuan-Son Nguyen 5ba3b9fd3d mtmd: fix gemma 4 ffn act (#30882) 2026-10-20 18:33:32 +02:00
TANGBUDUandTANGBUDU 5a9a34d888 server : avoid repeated mmproj memory margin addition on sleep/resume (#28233)
Co-authored-by: TANGBUDU <TANGBUDU@users.noreply.github.com>
2026-10-20 18:32:12 +02:00
Tarek Dakhran 39da130fcd ggml-cuda : support ssm_conv kernel size 7 (#30873) 2026-10-20 17:47:44 +02:00
85 changed files with 4272 additions and 1103 deletions
+4
View File
@@ -1175,6 +1175,7 @@ static const std::map<common_decision_type, std::string> COMMON_DECISION_TYPE_NA
{ COMMON_DECISION_TYPE_LFM2_D1, "lfm2-d1" },
{ COMMON_DECISION_TYPE_LFM2_D1_OMNI, "lfm2-d1-omni" },
{ COMMON_DECISION_TYPE_GRANITE_SWITCH, "granite-switch"},
{ COMMON_DECISION_TYPE_H2O_LIGHTNING, "h2o-lightning" },
// for future models, ALWAYS add org name prefix to avoid name collisions, e.g. "orgname-modelname"
};
@@ -1234,6 +1235,9 @@ common_gguf_info common_get_gguf_info(const std::string & fname) {
info.n_ctx_train = gguf_get_val_u32(gguf_ctx.get(), ctx_id);
}
// same key as llama_model_chat_template(model, nullptr)
info.has_chat_tmpl = gguf_find_key(gguf_ctx.get(), "tokenizer.chat_template") >= 0;
return info;
}
+2
View File
@@ -975,6 +975,7 @@ enum common_decision_type {
COMMON_DECISION_TYPE_LFM2_D1, // same as openjev, the labels depend on the question type
COMMON_DECISION_TYPE_LFM2_D1_OMNI, // same as laya, other prompt layout
COMMON_DECISION_TYPE_GRANITE_SWITCH, // same as openjev, run by the decision aLoRA of granite switch
COMMON_DECISION_TYPE_H2O_LIGHTNING, // same as openjev, the labels are read after an answer prefill
COMMON_DECISION_TYPE_UNKNOWN, // a decision model of a type that is not supported
};
@@ -984,6 +985,7 @@ common_decision_type common_get_decision_type(const struct llama_model * model);
struct common_gguf_info {
common_decision_type decision_type = COMMON_DECISION_TYPE_UNKNOWN; // UNKNOWN if the file is missing, unreadable, or invalid
uint32_t n_ctx_train = 0; // 0 if unknown
bool has_chat_tmpl = false; // has the default tokenizer.chat_template
};
common_gguf_info common_get_gguf_info(const std::string & fname);
+5
View File
@@ -200,7 +200,9 @@ TEXT_MODEL_MAP: dict[str, str] = {
"MiniMaxM3SparseForCausalLM": "minimax",
"MiniMaxM3SparseForConditionalGeneration": "minimax",
"Ministral3ForCausalLM": "mistral3",
"Ministral3Model": "mistral3",
"Mistral3ForConditionalGeneration": "mistral3",
"Mistral3Model": "mistral3",
"VoxtralRealtimeForConditionalGeneration": "mistral3",
"MistralForCausalLM": "llama",
"MixtralForCausalLM": "llama",
@@ -226,6 +228,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
"MuseSparkForConditionalGeneration": "muse_spark",
"OpenELMForCausalLM": "openelm",
"OpenJevModel": "qwen",
"H2OLightningModel": "h2o_lightning",
"OrionForCausalLM": "orion",
"PLMForCausalLM": "plm",
"PLaMo2ForCausalLM": "plamo",
@@ -357,6 +360,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
"MiniCPMV4_6ForConditionalGeneration": "minicpm",
"MiniCPMV4_7ForConditionalGeneration": "minicpm",
"Mistral3ForConditionalGeneration": "llava",
"Mistral3Model": "llava",
"NemotronH_Omni_Reasoning_V3": "nemotron",
"NemotronH_Nano_VL_V2": "nemotron",
"MuseGlimmerForConditionalGeneration": "muse_glimmer",
@@ -375,6 +379,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
"Qwen3VLForConditionalGeneration": "qwen3vl",
"Qwen3VLMoeForConditionalGeneration": "qwen3vl",
"OpenJevModel": "qwen3vl",
"H2OLightningModel": "h2o_lightning",
"Qwen3_5ForConditionalGeneration": "qwen3vl",
"Qwen3_5MoeForConditionalGeneration": "qwen3vl",
"Qwen4ExpForConditionalGeneration": "qwen4exp",
+2
View File
@@ -999,6 +999,8 @@ class Gemma4VisionAudioModel(MmprojModel):
assert self.hparams_vision is not None
self.gguf_writer.add_clip_vision_projector_type(gguf.VisionProjectorType.GEMMA4V)
self.gguf_writer.add_vision_attention_layernorm_eps(self.hparams_vision.get("layer_norm_eps", 1e-6))
assert self.hparams_vision.get("hidden_activation", "gelu_pytorch_tanh") == "gelu_pytorch_tanh"
self.gguf_writer.add_vision_use_gelu(True)
# audio params
if self.has_audio_encoder:
+96
View File
@@ -0,0 +1,96 @@
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from .base import ModelBase, gguf, logger
from .qwen import Qwen3_5TextModel
from .qwen3vl import Qwen3VLVisionModel
def _is_h2o_lightning_checkpoint(dir_model: Path) -> bool:
return (dir_model / "h2o_lightning_shim.py").is_file() and (dir_model / "serve_config.json").is_file()
@ModelBase.register_hparams_loader(_is_h2o_lightning_checkpoint)
def _load_h2o_lightning_hparams(dir_model: Path) -> dict[str, Any]:
logger.info("gguf: detected H2O-Lightning checkpoint")
hparams = ModelBase.load_hparams(dir_model, False, guess=False)
hparams["architectures"] = ["H2OLightningModel"]
return hparams
@ModelBase.register("H2OLightningModel")
@ModelBase.example("h2oai/h2o-lightning-4b")
class H2OLightningModel(Qwen3_5TextModel):
model_arch = gguf.MODEL_ARCH.QWEN35
# prompt and calibration follow serve_config.json and h2o_lightning_shim.py of the model repo
# the state budget is hard-coded in server-decision.cpp
_MAX_STATE_TOKENS = 32000
_DEFAULT_INSTRUCTIONS = "Answer the question below."
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
with open(self.dir_model / "serve_config.json", "r", encoding="utf-8") as f:
self._serve_config = json.load(f)
prompt = self._serve_config["prompt"]
expected = {
"state_compact": (prompt.get("state_compact", True), True),
"max_state_tokens": (prompt.get("max_state_tokens"), self._MAX_STATE_TOKENS),
"layout": (prompt.get("layout", "plain"), "plain"),
"chat_template": (prompt.get("chat_template", True), True),
"enable_thinking": (prompt.get("enable_thinking", False), False),
}
for name, (got, want) in expected.items():
if got != want:
raise ValueError(f"serve_config.json: {name} is {got!r}, the h2o-lightning decision type expects {want!r}")
def get_vocab_base_pre(self, tokenizer) -> str:
# transformers loads this checkpoint as Qwen2Tokenizer: the model was trained and is served with the Qwen2 split
pre = super().get_vocab_base_pre(tokenizer)
if pre != "qwen35":
raise ValueError(f"expected the Qwen3.5 tokenizer, got pre-tokenizer {pre!r}")
return "qwen2"
def set_vocab(self):
super().set_vocab()
self.gguf_writer.add_chat_template([{"name": "systemone", "template": self._systemone_template()}])
def _systemone_template(self) -> str:
# the server gives the template texts rendered as the shim renders them, it only lays them out
prompt = self._serve_config["prompt"]
nl = "{{ '\\n' }}"
description = (
"{% if type == 'noul' %}"
"{% if o.description %}{{ o.description }}{% elif o.key == 'true' %}the statement holds{% else %}it does not{% endif %}"
"{% elif o.description is none %}{{ o.key }}{% else %}{{ o.description }}{% endif %}"
)
user = (
"{% set user %}record: {{ state }}" + nl
+ "question: {{ instructions if instructions else '" + self._DEFAULT_INSTRUCTIONS + "' }}" + nl
+ "options:{% for o in options %}" + nl + "{{ o.label | trim }}) {{ o.key }}: " + description + "{% endfor %}{% endset %}"
)
return (
"<|im_start|>system" + nl + prompt["system"].strip() + "<|im_end|>" + nl
+ "<|im_start|>user" + nl + "{% for image in images %}{{ image }}{% endfor %}" + user + "{{ user | trim }}<|im_end|>" + nl
+ "<|im_start|>assistant" + nl + "<think>{{ '\\n\\n' }}</think>{{ '\\n\\n' }}" + prompt["prefill"]
)
def set_gguf_parameters(self):
super().set_gguf_parameters()
self.gguf_writer.add_decision_type(gguf.DecisionType.H2O_LIGHTNING)
# the answer follows "Answer:", so the token of a label has a leading space
cfg = self._serve_config
self.gguf_writer.add_decision_labels([" " + label for label in cfg["labels"][:cfg.get("max_options", len(cfg["labels"]))]])
for name in ("choice", "noul", "score"):
self.gguf_writer.add_decision_temperature(name, self._serve_config["temperature"][name])
# a request with images has this temperature for all question types
self.gguf_writer.add_decision_temperature("image", self._serve_config["image"]["temperature"])
@ModelBase.register("H2OLightningModel")
class H2OLightningVisionModel(Qwen3VLVisionModel):
pass
+22 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
import json
from typing import Iterable, TYPE_CHECKING
from typing import Callable, Iterable, TYPE_CHECKING
if TYPE_CHECKING:
from torch import Tensor
@@ -128,3 +128,24 @@ class LlavaVisionModel(MmprojModel):
yield from super().modify_tensors(img_break_embd, name, bid)
return # skip other tensors
@ModelBase.register("Mistral3Model")
@ModelBase.example("nvidia/Nemotron-3.5-Embed-1B-BF16")
class Mistral3EmbeddingVisionModel(LlavaVisionModel):
def __init__(self, dir_model, *args, **kwargs):
hparams = ModelBase.load_hparams(dir_model, is_mistral_format=False)
text_config = hparams.get("text_config", hparams)
if text_config.get("is_causal") is not False:
raise NotImplementedError("Expected a Nemotron Embed model for Mistral3Model, but is_causal is not False")
super().__init__(dir_model, *args, **kwargs)
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
if item[0].endswith("embed_tokens.weight"):
return ModelBase.filter_tensors(item)
return super().filter_tensors(item)
def set_gguf_parameters(self):
super().set_gguf_parameters()
self.gguf_writer.add_vision_preproc_image_size(self.preprocessor_config["size"]["longest_edge"])
+28 -6
View File
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING:
from torch import Tensor
from .base import ModelBase, TextModel, gguf
from .base import ModelBase, TextModel, get_model_architecture, gguf
from .deepseek import DeepseekV2Model
from .llama import LlamaModel
@@ -13,9 +13,11 @@ from .llama import LlamaModel
@ModelBase.register(
"Mistral3ForConditionalGeneration",
"Mistral3Model",
"Ministral3ForCausalLM",
"Ministral3Model",
)
@ModelBase.example("mistralai/Mistral-Small-3.1-24B-Instruct-2503", "hf-tiny-v2/tiny-random-Ministral3ForCausalLM")
@ModelBase.example("mistralai/Mistral-Small-3.1-24B-Instruct-2503", "hf-tiny-v2/tiny-random-Ministral3ForCausalLM", "nvidia/Nemotron-3-Embed-1B-BF16", "nvidia/Nemotron-3.5-Embed-1B-BF16")
class Mistral3Model(TextModel):
class Ministral3Model(LlamaModel):
model_arch = gguf.MODEL_ARCH.MISTRAL3
@@ -29,6 +31,18 @@ class Mistral3Model(TextModel):
self.gguf_writer.add_rope_scaling_yarn_log_mul(rope_params["mscale_all_dim"])
self.gguf_writer.add_attn_temperature_scale(rope_params["llama_4_scaling_beta"])
class Mistral3EmbedNemotronModel(Ministral3Model):
model_arch = gguf.MODEL_ARCH.MISTRAL3
def set_vocab(self):
super().set_vocab()
self.gguf_writer.add_add_bos_token(False)
self.gguf_writer.add_add_eos_token(False)
def set_gguf_parameters(self):
super().set_gguf_parameters()
self._try_set_pooling_type()
class Mistral4Model(DeepseekV2Model):
model_arch = gguf.MODEL_ARCH.MISTRAL4
skip_mtp = False # model contains no MTP layers, so no need to skip
@@ -42,12 +56,20 @@ class Mistral3Model(TextModel):
model_arch = gguf.MODEL_ARCH.MISTRAL3 # unused
impl: TextModel
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, dir_model, *args, **kwargs):
hparams = ModelBase.load_hparams(dir_model, is_mistral_format=False)
hf_arch = get_model_architecture(hparams, self.model_type)
if hf_arch in ("Ministral3Model", "Mistral3Model"):
text_config = hparams.get("text_config", hparams)
if text_config.get("is_causal") is not False:
raise NotImplementedError(f"Expected a Nemotron Embed model for {hf_arch}, but is_causal is not False")
super().__init__(dir_model, *args, **kwargs)
if self.hparams.get("model_type") == "mistral4":
self.impl = Mistral3Model.Mistral4Model(*args, **kwargs)
self.impl = Mistral3Model.Mistral4Model(dir_model, *args, **kwargs)
elif self.hf_arch in ("Ministral3Model", "Mistral3Model"):
self.impl = Mistral3Model.Mistral3EmbedNemotronModel(dir_model, *args, **kwargs)
else:
self.impl = Mistral3Model.Ministral3Model(*args, **kwargs)
self.impl = Mistral3Model.Ministral3Model(dir_model, *args, **kwargs)
def set_vocab(self):
self.impl.set_vocab()
+10 -1
View File
@@ -774,6 +774,13 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
return src_ss[0];
};
auto handle_pool_1d = [&](const std::vector<ggml_backend_meta_split_state> & src_ss) -> ggml_backend_meta_split_state {
if (src_ss[0].axis == GGML_BACKEND_SPLIT_AXIS_0) {
return {GGML_BACKEND_SPLIT_AXIS_MIRRORED, {0}, {1}, 1};
}
return src_ss[0];
};
auto handle_flash_attn_ext = [&](const std::vector<ggml_backend_meta_split_state> & src_ss) -> ggml_backend_meta_split_state {
GGML_ASSERT(tensor->src[3] == nullptr || src_ss[3].axis == GGML_BACKEND_SPLIT_AXIS_MIRRORED);
@@ -990,12 +997,14 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
case GGML_OP_CONV_3D:
case GGML_OP_CONV_2D_DW:
case GGML_OP_CONV_TRANSPOSE_2D:
case GGML_OP_POOL_1D:
case GGML_OP_POOL_2D:
case GGML_OP_POOL_2D_BACK:
case GGML_OP_UPSCALE: {
split_state = handle_generic(src_ss, /*scalar_only =*/ true);
} break;
case GGML_OP_POOL_1D: {
split_state = handle_pool_1d(src_ss);
} break;
case GGML_OP_PAD: {
split_state = handle_pad(src_ss);
} break;
+86
View File
@@ -460,6 +460,92 @@ void ggml_vec_swiglu_f32(const int n, float * y, const float * x, const float *
}
}
#ifndef GGML_GELU_QUICK_FP16
// computed in f32 without the fp16 table, which loses precision on f32 input
void ggml_vec_gelu_quick_f32(const int n, float * y, const float * x) {
int i = 0;
#if defined(__AVX512F__) && defined(__AVX512DQ__)
for (; i + 15 < n; i += 16) {
_mm512_storeu_ps(y + i, ggml_v_gelu_quick(_mm512_loadu_ps(x + i)));
}
#elif defined(__AVX2__) && defined(__FMA__)
for (; i + 7 < n; i += 8) {
_mm256_storeu_ps(y + i, ggml_v_gelu_quick(_mm256_loadu_ps(x + i)));
}
#elif defined(__SSE2__)
for (; i + 3 < n; i += 4) {
_mm_storeu_ps(y + i, ggml_v_gelu_quick(_mm_loadu_ps(x + i)));
}
#elif defined(__ARM_FEATURE_SVE) && defined(__aarch64__)
const int vlen = svcntw();
for (; i < n; i += vlen) {
const svbool_t pg = svwhilelt_b32_s32(i, n);
svst1_f32(pg, y + i, ggml_v_gelu_quick(pg, svld1_f32(pg, x + i)));
}
#elif defined(__ARM_NEON) && defined(__aarch64__)
for (; i + 3 < n; i += 4) {
vst1q_f32(y + i, ggml_v_gelu_quick(vld1q_f32(x + i)));
}
#elif defined(__VXE__) || defined(__VXE2__)
for (; i + 3 < n; i += 4) {
vec_xst(ggml_v_gelu_quick(vec_xl(0, x + i)), 0, y + i);
}
#elif defined(__riscv_v_intrinsic)
for (int vl; i < n; i += vl) {
vl = __riscv_vsetvl_e32m2(n - i);
vfloat32m2_t vx = __riscv_vle32_v_f32m2(&x[i], vl);
vfloat32m2_t vy = ggml_v_gelu_quick_m2(vx, vl);
__riscv_vse32_v_f32m2(&y[i], vy, vl);
}
#endif
for (; i < n; ++i) {
y[i] = ggml_gelu_quick_f32(x[i]);
}
}
void ggml_vec_geglu_quick_f32(const int n, float * y, const float * x, const float * g) {
int i = 0;
#if defined(__AVX512F__) && defined(__AVX512DQ__)
for (; i + 15 < n; i += 16) {
_mm512_storeu_ps(y + i, _mm512_mul_ps(ggml_v_gelu_quick(_mm512_loadu_ps(x + i)), _mm512_loadu_ps(g + i)));
}
#elif defined(__AVX2__) && defined(__FMA__)
for (; i + 7 < n; i += 8) {
_mm256_storeu_ps(y + i, _mm256_mul_ps(ggml_v_gelu_quick(_mm256_loadu_ps(x + i)), _mm256_loadu_ps(g + i)));
}
#elif defined(__SSE2__)
for (; i + 3 < n; i += 4) {
_mm_storeu_ps(y + i, _mm_mul_ps(ggml_v_gelu_quick(_mm_loadu_ps(x + i)), _mm_loadu_ps(g + i)));
}
#elif defined(__ARM_FEATURE_SVE) && defined(__aarch64__)
const int vlen = svcntw();
for (; i < n; i += vlen) {
const svbool_t pg = svwhilelt_b32_s32(i, n);
svst1_f32(pg, y + i, svmul_f32_x(pg, ggml_v_gelu_quick(pg, svld1_f32(pg, x + i)), svld1_f32(pg, g + i)));
}
#elif defined(__ARM_NEON) && defined(__aarch64__)
for (; i + 3 < n; i += 4) {
vst1q_f32(y + i, vmulq_f32(ggml_v_gelu_quick(vld1q_f32(x + i)), vld1q_f32(g + i)));
}
#elif defined(__VXE__) || defined(__VXE2__)
for (; i + 3 < n; i += 4) {
vec_xst(vec_mul(ggml_v_gelu_quick(vec_xl(0, x + i)), vec_xl(0, g + i)), 0, y + i);
}
#elif defined(__riscv_v_intrinsic)
for (int vl; i < n; i += vl) {
vl = __riscv_vsetvl_e32m2(n - i);
vfloat32m2_t vx = __riscv_vle32_v_f32m2(&x[i], vl);
vfloat32m2_t vg = __riscv_vle32_v_f32m2(&g[i], vl);
vfloat32m2_t vy = __riscv_vfmul_vv_f32m2(ggml_v_gelu_quick_m2(vx, vl), vg, vl);
__riscv_vse32_v_f32m2(&y[i], vy, vl);
}
#endif
for (; i < n; ++i) {
y[i] = ggml_gelu_quick_f32(x[i]) * g[i];
}
}
#endif
ggml_float ggml_vec_cvar_f32(const int n, float * y, const float * x, const float mean) {
int i = 0;
ggml_float sum = 0;
+50 -11
View File
@@ -44,7 +44,6 @@ inline static ggml_float ggml_sve_sum_f32x2(svfloat32_t sum_lo, svfloat32_t sum_
#endif
#define GGML_GELU_FP16
#define GGML_GELU_QUICK_FP16
#define GGML_SOFT_MAX_UNROLL 4
#define GGML_VEC_DOT_UNROLL 2
@@ -1054,11 +1053,7 @@ inline static void ggml_vec_gelu_quick_f32(const int n, float * y, const float *
}
}
#else
inline static void ggml_vec_gelu_quick_f32(const int n, float * y, const float * x) {
for (int i = 0; i < n; ++i) {
y[i] = ggml_gelu_quick_f32(x[i]);
}
}
void ggml_vec_gelu_quick_f32(const int n, float * y, const float * x);
#endif
// Sigmoid Linear Unit (SiLU) function
@@ -1143,6 +1138,13 @@ inline static svfloat32_t ggml_v_silu(svbool_t pg, svfloat32_t x) {
return svdiv_f32_x(pg, x, one_plus_exp_neg_x);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static svfloat32_t ggml_v_gelu_quick(svbool_t pg, svfloat32_t x) {
const svfloat32_t one = svdup_n_f32_x(pg, 1.0f);
const svfloat32_t exp_cx = ggml_v_expf(pg, svmul_n_f32_x(pg, x, GELU_QUICK_COEF));
return svdiv_f32_x(pg, x, svadd_f32_x(pg, one, exp_cx));
}
#elif defined(__ARM_NEON) && defined(__aarch64__)
// adapted from arm limited optimized routine
@@ -1182,6 +1184,13 @@ inline static float32x4_t ggml_v_silu(float32x4_t x) {
return vdivq_f32(x, one_plus_exp_neg_x);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static float32x4_t ggml_v_gelu_quick(float32x4_t x) {
const float32x4_t one = vdupq_n_f32(1.0f);
const float32x4_t exp_cx = ggml_v_expf(vmulq_n_f32(x, GELU_QUICK_COEF));
return vdivq_f32(x, vaddq_f32(one, exp_cx));
}
#elif defined(__AVX512F__) && defined(__AVX512DQ__)
// adapted from arm limited optimized routine
@@ -1225,6 +1234,13 @@ inline static __m512 ggml_v_silu(__m512 x) {
return _mm512_div_ps(x, one_plus_exp_neg_x);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static __m512 ggml_v_gelu_quick(__m512 x) {
const __m512 one = _mm512_set1_ps(1);
const __m512 exp_cx = ggml_v_expf(_mm512_mul_ps(x, _mm512_set1_ps(GELU_QUICK_COEF)));
return _mm512_div_ps(x, _mm512_add_ps(one, exp_cx));
}
#elif defined(__AVX2__) && defined(__FMA__)
// adapted from arm limited optimized routine
@@ -1280,6 +1296,13 @@ inline static __m256 ggml_v_silu(__m256 x) {
return _mm256_div_ps(x, one_plus_exp_neg_x);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static __m256 ggml_v_gelu_quick(__m256 x) {
const __m256 one = _mm256_set1_ps(1);
const __m256 exp_cx = ggml_v_expf(_mm256_mul_ps(x, _mm256_set1_ps(GELU_QUICK_COEF)));
return _mm256_div_ps(x, _mm256_add_ps(one, exp_cx));
}
#elif defined(__SSE2__) // __AVX2__ / __ARM_NEON
#if defined(__FMA__)
@@ -1334,6 +1357,13 @@ inline static __m128 ggml_v_silu(__m128 x) {
return _mm_div_ps(x, one_plus_exp_neg_x);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static __m128 ggml_v_gelu_quick(__m128 x) {
const __m128 one = _mm_set1_ps(1);
const __m128 exp_cx = ggml_v_expf(_mm_mul_ps(x, _mm_set1_ps(GELU_QUICK_COEF)));
return _mm_div_ps(x, _mm_add_ps(one, exp_cx));
}
#elif defined(__VXE__) || defined(__VXE2__)
// adapted from arm limited optimized routine
@@ -1374,6 +1404,13 @@ inline static float32x4_t ggml_v_silu(float32x4_t v_x) {
return vec_div(v_x, v_one_plus_exp_neg_x);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static float32x4_t ggml_v_gelu_quick(float32x4_t x) {
const float32x4_t one = vec_splats(1.0f);
const float32x4_t exp_cx = ggml_v_expf(vec_mul(x, vec_splats(GELU_QUICK_COEF)));
return vec_div(x, vec_add(one, exp_cx));
}
#elif defined(__riscv_v_intrinsic)
// adapted from arm limited optimized routine
@@ -1426,6 +1463,12 @@ inline static vfloat32m2_t ggml_v_silu_m2(vfloat32m2_t x, int vl) {
return __riscv_vfdiv_vv_f32m2(x, one_plus_exp_neg_x, vl);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static vfloat32m2_t ggml_v_gelu_quick_m2(vfloat32m2_t x, int vl) {
const vfloat32m2_t exp_cx = ggml_v_expf_m2(__riscv_vfmul_vf_f32m2(x, GELU_QUICK_COEF, vl), vl);
return __riscv_vfdiv_vv_f32m2(x, __riscv_vfadd_vf_f32m2(exp_cx, 1.0f, vl), vl);
}
#endif // __ARM_NEON / __AVX2__ / __SSE2__ / __VXE__ / __riscv_v_intrinsic
inline static void ggml_vec_silu_f16(const int n, ggml_fp16_t * y, const ggml_fp16_t * x) {
@@ -1571,11 +1614,7 @@ inline static void ggml_vec_geglu_quick_f32(const int n, float * y, const float
}
}
#else
inline static void ggml_vec_geglu_quick_f32(const int n, float * y, const float * x, const float * g) {
for (int i = 0; i < n; ++i) {
y[i] = ggml_gelu_quick_f32(x[i]) * g[i];
}
}
void ggml_vec_geglu_quick_f32(const int n, float * y, const float * x, const float * g);
#endif
inline static void ggml_vec_geglu_quick_f16(const int n, ggml_fp16_t * y, const ggml_fp16_t * x, const ggml_fp16_t * g) {
+2 -1
View File
@@ -151,9 +151,10 @@ static void ssm_conv_f32_cuda(const float * src0, const float * src1, const floa
case 3: launch_kernel(std::integral_constant<int, 3 >{}); break;
case 4: launch_kernel(std::integral_constant<int, 4 >{}); break;
case 5: launch_kernel(std::integral_constant<int, 5 >{}); break;
case 7: launch_kernel(std::integral_constant<int, 7 >{}); break;
case 9: launch_kernel(std::integral_constant<int, 9 >{}); break;
case 15: launch_kernel(std::integral_constant<int, 15>{}); break;
default: GGML_ABORT("Only support kernel sizes 3, 4, 5, 9, 15 right now.");
default: GGML_ABORT("Only support kernel sizes 3, 4, 5, 7, 9, 15 right now.");
}
}
+513 -72
View File
@@ -9,6 +9,11 @@
#include <cmath>
#include <cstring>
#include <initializer_list>
#include <thread>
#if defined(__ARM_NEON) || defined(__aarch64__)
# include <arm_neon.h>
#endif
// --- 4-bit block-32 nibble helpers (shared by Q4_0, IQ4_NL, Q4_1, MXFP4) ---
@@ -43,8 +48,309 @@ static inline void transpose_tile_to_nibbles(uint8_t tile_quants[32][32], const
}
}
#if defined(__ARM_NEON) || defined(__aarch64__)
static void repack_neon_transpose8x8(const uint8x8_t rows[8], uint8x8_t columns[8]) {
const uint8x8x2_t t0 = vtrn_u8(rows[0], rows[1]);
const uint8x8x2_t t1 = vtrn_u8(rows[2], rows[3]);
const uint8x8x2_t t2 = vtrn_u8(rows[4], rows[5]);
const uint8x8x2_t t3 = vtrn_u8(rows[6], rows[7]);
const uint16x4x2_t t4 = vtrn_u16(vreinterpret_u16_u8(t0.val[0]), vreinterpret_u16_u8(t1.val[0]));
const uint16x4x2_t t5 = vtrn_u16(vreinterpret_u16_u8(t0.val[1]), vreinterpret_u16_u8(t1.val[1]));
const uint16x4x2_t t6 = vtrn_u16(vreinterpret_u16_u8(t2.val[0]), vreinterpret_u16_u8(t3.val[0]));
const uint16x4x2_t t7 = vtrn_u16(vreinterpret_u16_u8(t2.val[1]), vreinterpret_u16_u8(t3.val[1]));
const uint32x2x2_t t8 = vtrn_u32(vreinterpret_u32_u16(t4.val[0]), vreinterpret_u32_u16(t6.val[0]));
const uint32x2x2_t t9 = vtrn_u32(vreinterpret_u32_u16(t4.val[1]), vreinterpret_u32_u16(t6.val[1]));
const uint32x2x2_t ta = vtrn_u32(vreinterpret_u32_u16(t5.val[0]), vreinterpret_u32_u16(t7.val[0]));
const uint32x2x2_t tb = vtrn_u32(vreinterpret_u32_u16(t5.val[1]), vreinterpret_u32_u16(t7.val[1]));
columns[0] = vreinterpret_u8_u32(t8.val[0]);
columns[1] = vreinterpret_u8_u32(ta.val[0]);
columns[2] = vreinterpret_u8_u32(t9.val[0]);
columns[3] = vreinterpret_u8_u32(tb.val[0]);
columns[4] = vreinterpret_u8_u32(t8.val[1]);
columns[5] = vreinterpret_u8_u32(ta.val[1]);
columns[6] = vreinterpret_u8_u32(t9.val[1]);
columns[7] = vreinterpret_u8_u32(tb.val[1]);
}
static uint8x8_t repack_neon_pack_4bit_pairs(uint8x8_t first, uint8x8_t second, bool high) {
const uint8x8x2_t separated = vuzp_u8(first, second);
const uint8x8_t mask = vdup_n_u8(0x0F);
uint8x8_t even = separated.val[0];
uint8x8_t odd = separated.val[1];
if (high) {
even = vshr_n_u8(even, 4);
odd = vshr_n_u8(odd, 4);
}
even = vand_u8(even, mask);
odd = vand_u8(odd, mask);
return vorr_u8(vshl_n_u8(odd, 4), even);
}
static void repack_neon_4bit_rows(const uint8_t * qs[8], uint8_t * tile_dst, int row0) {
uint8x8_t low_rows[8];
uint8x8_t high_rows[8];
uint8x8_t columns[8];
for (int rr = 0; rr < 8; ++rr) {
const uint8x16_t packed = vld1q_u8(qs[rr]);
const uint8x8_t first = vget_low_u8(packed);
const uint8x8_t second = vget_high_u8(packed);
low_rows[rr] = repack_neon_pack_4bit_pairs(first, second, false);
high_rows[rr] = repack_neon_pack_4bit_pairs(first, second, true);
}
repack_neon_transpose8x8(low_rows, columns);
for (int cp = 0; cp < 8; ++cp) {
vst1_u8(tile_dst + cp * 32 + row0, columns[cp]);
}
repack_neon_transpose8x8(high_rows, columns);
for (int cp = 0; cp < 8; ++cp) {
vst1_u8(tile_dst + (cp + 8) * 32 + row0, columns[cp]);
}
}
static void repack_neon_k4_rows(const uint8_t * qs[8], uint8_t * tile_dst, int row0, bool high) {
uint8x8_t first_rows[8];
uint8x8_t second_rows[8];
uint8x8_t columns[8];
for (int rr = 0; rr < 8; ++rr) {
first_rows[rr] = repack_neon_pack_4bit_pairs(vld1_u8(qs[rr]), vld1_u8(qs[rr] + 8), high);
second_rows[rr] = repack_neon_pack_4bit_pairs(vld1_u8(qs[rr] + 16), vld1_u8(qs[rr] + 24), high);
}
repack_neon_transpose8x8(first_rows, columns);
for (int cp = 0; cp < 8; ++cp) {
vst1_u8(tile_dst + cp * 32 + row0, columns[cp]);
}
repack_neon_transpose8x8(second_rows, columns);
for (int cp = 0; cp < 8; ++cp) {
vst1_u8(tile_dst + (cp + 8) * 32 + row0, columns[cp]);
}
}
static void repack_neon_q8_rows(const uint8_t * qs[8], uint8_t * tile_dst, int row0) {
uint8x8_t rows[8];
uint8x8_t columns[8];
for (int cp = 0; cp < 16; cp += 4) {
for (int rr = 0; rr < 8; ++rr) {
rows[rr] = vld1_u8(qs[rr] + 2 * cp);
}
repack_neon_transpose8x8(rows, columns);
for (int pair = 0; pair < 4; ++pair) {
const uint8x8x2_t interleaved = vzip_u8(columns[2 * pair], columns[2 * pair + 1]);
uint8_t * dst = tile_dst + (cp + pair) * 64 + 2 * row0;
vst1_u8(dst, interleaved.val[0]);
vst1_u8(dst + 8, interleaved.val[1]);
}
}
}
static inline void repack_store_u32(uint8_t * dst, uint32_t v) {
memcpy(dst, &v, sizeof(v));
}
static inline uint32x4_t repack_neon_transpose_2bit_4x4(uint32x4_t w) {
uint32x4_t t = vandq_u32(veorq_u32(w, vshrq_n_u32(w, 6)), vdupq_n_u32(0x00CC00CCu));
w = veorq_u32(w, veorq_u32(t, vshlq_n_u32(t, 6)));
t = vandq_u32(veorq_u32(w, vshrq_n_u32(w, 12)), vdupq_n_u32(0x0000F0F0u));
return veorq_u32(w, veorq_u32(t, vshlq_n_u32(t, 12)));
}
static inline void repack_neon_q2_0_block(const uint8_t * qs, uint8_t * tile, int row) {
const uint32x4_t w = repack_neon_transpose_2bit_4x4(vreinterpretq_u32_u8(vld1q_u8(qs)));
repack_store_u32(tile + 0 * 128 + 4 * row, vgetq_lane_u32(w, 0));
repack_store_u32(tile + 1 * 128 + 4 * row, vgetq_lane_u32(w, 1));
repack_store_u32(tile + 2 * 128 + 4 * row, vgetq_lane_u32(w, 2));
repack_store_u32(tile + 3 * 128 + 4 * row, vgetq_lane_u32(w, 3));
}
static inline void repack_neon_q1_0_block(const uint8_t * qs, uint8_t * tile, int row) {
uint32x4_t w = vreinterpretq_u32_u8(vld1q_u8(qs));
uint32x4_t t = vandq_u32(veorq_u32(w, vshrq_n_u32(w, 1)), vdupq_n_u32(0x22222222u));
w = veorq_u32(w, veorq_u32(t, vshlq_n_u32(t, 1)));
t = vandq_u32(veorq_u32(w, vshrq_n_u32(w, 3)), vdupq_n_u32(0x0A0A0A0Au));
w = veorq_u32(w, veorq_u32(t, vshlq_n_u32(t, 3)));
w = repack_neon_transpose_2bit_4x4(w);
repack_store_u32(tile + 0 * 128 + 4 * row, vgetq_lane_u32(w, 0));
repack_store_u32(tile + 1 * 128 + 4 * row, vgetq_lane_u32(w, 1));
repack_store_u32(tile + 2 * 128 + 4 * row, vgetq_lane_u32(w, 2));
repack_store_u32(tile + 3 * 128 + 4 * row, vgetq_lane_u32(w, 3));
}
static inline void repack_neon_pack_2bit_plane(uint8x16_t v0, uint8x16_t v1, uint8_t * dst, int row) {
const uint32x4x2_t z = vuzpq_u32(vreinterpretq_u32_u8(v0), vreinterpretq_u32_u8(v1));
const uint32x4_t p01 = vorrq_u32(z.val[0], vshlq_n_u32(z.val[1], 2));
const uint32x2x2_t u = vuzp_u32(vget_low_u32(p01), vget_high_u32(p01));
const uint32x2_t out = vorr_u32(u.val[0], vshl_n_u32(u.val[1], 4));
repack_store_u32(dst + 0 * 128 + row * 4, vget_lane_u32(out, 0));
repack_store_u32(dst + 1 * 128 + row * 4, vget_lane_u32(out, 1));
}
static inline void repack_neon_q2_3_k_low2(const uint8_t * qs, int kt_local, uint8_t * lo_pl, int row) {
const uint8_t * qs_sub = qs + (kt_local / 4) * 32;
const int8x16_t shift = vdupq_n_s8(-2 * (kt_local % 4));
const uint8x16_t mask = vdupq_n_u8(0x03);
const uint8x16_t v0 = vandq_u8(vshlq_u8(vld1q_u8(qs_sub + 0), shift), mask);
const uint8x16_t v1 = vandq_u8(vshlq_u8(vld1q_u8(qs_sub + 16), shift), mask);
repack_neon_pack_2bit_plane(v0, v1, lo_pl, row);
}
static inline void repack_neon_q3_k_hmask(const uint8_t * hmask, int kt_local, uint8_t * neg_pl, int row) {
static const uint32_t mask_words[4] = { 0x01010101u, 0x02020202u, 0x04040404u, 0x08080808u };
const uint8x16_t m0 = vreinterpretq_u8_u32(vld1q_u32(mask_words));
const uint8x16_t m1 = vshlq_n_u8(m0, 4);
const uint8x16_t hbit = vdupq_n_u8((uint8_t) (1u << kt_local));
const uint8x16_t b0 = vbicq_u8(m0, vtstq_u8(vld1q_u8(hmask + 0), hbit));
const uint8x16_t b1 = vbicq_u8(m1, vtstq_u8(vld1q_u8(hmask + 16), hbit));
const uint32x4_t b01 = vreinterpretq_u32_u8(vorrq_u8(b0, b1));
const uint32x2_t f0 = vorr_u32(vget_low_u32(b01), vget_high_u32(b01));
const uint32x2_t f1 = vpadd_u32(f0, f0);
repack_store_u32(neg_pl + row * 4, vget_lane_u32(f1, 0));
}
static inline void repack_neon_q6_k(const block_q6_K * b, int kt_local, uint8_t * lo_pl, uint8_t * hi_pl, int row) {
const int c = kt_local / 4;
const int g_sb = kt_local % 4;
const uint8_t * ql = b->ql + c * 64 + (g_sb & 1) * 32;
const uint8_t * qh = b->qh + c * 32;
const int8x16_t lshift = vdupq_n_s8((g_sb >= 2) ? -4 : 0);
const int8x16_t hshift = vdupq_n_s8(-2 * g_sb);
const uint8x16_t mask4 = vdupq_n_u8(0x0F);
const uint8x16_t mask2 = vdupq_n_u8(0x03);
const uint8x16_t lo0 = vandq_u8(vshlq_u8(vld1q_u8(ql + 0), lshift), mask4);
const uint8x16_t lo1 = vandq_u8(vshlq_u8(vld1q_u8(ql + 16), lshift), mask4);
const uint32x4x2_t lz = vuzpq_u32(vreinterpretq_u32_u8(lo0), vreinterpretq_u32_u8(lo1));
const uint32x4_t lout = vorrq_u32(lz.val[0], vshlq_n_u32(lz.val[1], 4));
repack_store_u32(lo_pl + 0 * 128 + row * 4, vgetq_lane_u32(lout, 0));
repack_store_u32(lo_pl + 1 * 128 + row * 4, vgetq_lane_u32(lout, 1));
repack_store_u32(lo_pl + 2 * 128 + row * 4, vgetq_lane_u32(lout, 2));
repack_store_u32(lo_pl + 3 * 128 + row * 4, vgetq_lane_u32(lout, 3));
const uint8x16_t hi0 = vandq_u8(vshlq_u8(vld1q_u8(qh + 0), hshift), mask2);
const uint8x16_t hi1 = vandq_u8(vshlq_u8(vld1q_u8(qh + 16), hshift), mask2);
repack_neon_pack_2bit_plane(hi0, hi1, hi_pl, row);
}
static inline void repack_neon_q5_k_qh(const uint8_t * qh, uint8_t hbit, uint8_t * plane, int row) {
static const uint8_t mask_bytes[16] = { 1, 2, 1, 2, 1, 2, 1, 2, 4, 8, 4, 8, 4, 8, 4, 8 };
const uint8x16_t m0 = vld1q_u8(mask_bytes);
const uint8x16_t m1 = vshlq_n_u8(m0, 4);
const uint8x16_t vbit = vdupq_n_u8(hbit);
const uint8x16_t b0 = vandq_u8(vtstq_u8(vld1q_u8(qh + 0), vbit), m0);
const uint8x16_t b1 = vandq_u8(vtstq_u8(vld1q_u8(qh + 16), vbit), m1);
const uint8x16_t b01 = vorrq_u8(b0, b1);
const uint8x8_t fold = vorr_u8(vget_low_u8(b01), vget_high_u8(b01));
const uint8x8_t out = vpadd_u8(fold, fold);
plane[0 * 32 + row] = vget_lane_u8(out, 0);
plane[1 * 32 + row] = vget_lane_u8(out, 1);
plane[2 * 32 + row] = vget_lane_u8(out, 2);
plane[3 * 32 + row] = vget_lane_u8(out, 3);
}
static inline uint16x8_t repack_neon_load_f16x8(const uint16_t * src) {
return vld1q_u16(src);
}
#if defined(__aarch64__) || defined(__ARM_FP16_FORMAT_IEEE)
static inline uint16x8_t repack_neon_load_f16x8(const float * src) {
const float16x4_t lo = vcvt_f16_f32(vld1q_f32(src));
const float16x4_t hi = vcvt_f16_f32(vld1q_f32(src + 4));
return vreinterpretq_u16_f16(vcombine_f16(lo, hi));
}
static inline uint16x8_t repack_neon_load_f16x8(const ggml_bf16_t * src) {
const uint16x8_t bf = vld1q_u16((const uint16_t *) src);
const float32x4_t f_lo = vreinterpretq_f32_u32(vshll_n_u16(vget_low_u16(bf), 16));
const float32x4_t f_hi = vreinterpretq_f32_u32(vshll_n_u16(vget_high_u16(bf), 16));
const float16x4_t lo = vcvt_f16_f32(f_lo);
const float16x4_t hi = vcvt_f16_f32(f_hi);
return vreinterpretq_u16_f16(vcombine_f16(lo, hi));
}
#else
static inline uint16x8_t repack_neon_load_f16x8(const float * src) {
uint16_t tmp[8];
for (int i = 0; i < 8; ++i) {
tmp[i] = GGML_FP32_TO_FP16(src[i]);
}
return vld1q_u16(tmp);
}
static inline uint16x8_t repack_neon_load_f16x8(const ggml_bf16_t * src) {
uint16_t tmp[8];
for (int i = 0; i < 8; ++i) {
tmp[i] = GGML_FP32_TO_FP16(GGML_BF16_TO_FP32(src[i]));
}
return vld1q_u16(tmp);
}
#endif
template <typename SrcT>
static void repack_neon_f16_tile(uint16_t * tile_dst, const SrcT * src_slice, int64_t row_stride, int ct, int kt) {
for (int row0 = 0; row0 < 32; row0 += 4) {
const SrcT * r0 = src_slice + (int64_t) (ct * 32 + row0 + 0) * row_stride + kt * 32;
const SrcT * r1 = src_slice + (int64_t) (ct * 32 + row0 + 1) * row_stride + kt * 32;
const SrcT * r2 = src_slice + (int64_t) (ct * 32 + row0 + 2) * row_stride + kt * 32;
const SrcT * r3 = src_slice + (int64_t) (ct * 32 + row0 + 3) * row_stride + kt * 32;
for (int cp = 0; cp < 16; cp += 4) {
const uint32x4_t v0 = vreinterpretq_u32_u16(repack_neon_load_f16x8(r0 + 2 * cp));
const uint32x4_t v1 = vreinterpretq_u32_u16(repack_neon_load_f16x8(r1 + 2 * cp));
const uint32x4_t v2 = vreinterpretq_u32_u16(repack_neon_load_f16x8(r2 + 2 * cp));
const uint32x4_t v3 = vreinterpretq_u32_u16(repack_neon_load_f16x8(r3 + 2 * cp));
const uint32x2x2_t t0 = vtrn_u32(vget_low_u32(v0), vget_low_u32(v1));
const uint32x2x2_t t1 = vtrn_u32(vget_high_u32(v0), vget_high_u32(v1));
const uint32x2x2_t t2 = vtrn_u32(vget_low_u32(v2), vget_low_u32(v3));
const uint32x2x2_t t3 = vtrn_u32(vget_high_u32(v2), vget_high_u32(v3));
vst1q_u16(tile_dst + (cp + 0) * 64 + row0 * 2, vreinterpretq_u16_u32(vcombine_u32(t0.val[0], t2.val[0])));
vst1q_u16(tile_dst + (cp + 1) * 64 + row0 * 2, vreinterpretq_u16_u32(vcombine_u32(t0.val[1], t2.val[1])));
vst1q_u16(tile_dst + (cp + 2) * 64 + row0 * 2, vreinterpretq_u16_u32(vcombine_u32(t1.val[0], t3.val[0])));
vst1q_u16(tile_dst + (cp + 3) * 64 + row0 * 2, vreinterpretq_u16_u32(vcombine_u32(t1.val[1], t3.val[1])));
}
}
}
#endif
// --- Generic 32x32 tile grid walkers for block-32 and float types ---
template <typename Fn>
static void repack_parallel_for_ct(size_t slice_bytes, int n_col_tiles, Fn && fn) {
int n_threads = 1;
if (slice_bytes > 128 * 1024) {
for (int nt : { 4, 3, 2 }) {
if (n_col_tiles % nt == 0) {
n_threads = nt;
break;
}
}
}
if (n_threads <= 1) {
fn(0, n_col_tiles);
return;
}
const int chunk = n_col_tiles / n_threads;
std::thread workers[3];
for (int i = 0; i < n_threads - 1; ++i) {
workers[i] = std::thread([&fn, i, chunk]() {
fn(i * chunk, (i + 1) * chunk);
});
}
fn((n_threads - 1) * chunk, n_col_tiles);
for (int i = 0; i < n_threads - 1; ++i) {
workers[i].join();
}
}
template <typename SrcElemT, int ELEMS_PER_KTILE, typename PackTileFn>
static void repack_to_tiled_grid_32(ggml_tensor * t, const void * data, size_t offset, size_t size,
size_t tile_size, PackTileFn && pack_tile) {
@@ -70,12 +376,14 @@ static void repack_to_tiled_grid_32(ggml_tensor * t, const void * data, size_t o
const SrcElemT * src_slice = src_matrix + (slice_idx - start_slice) * (ne1 * row_stride);
uint8_t * matrix_dst = (uint8_t *) t->data + slice_idx * matrix_size;
for (int ct = 0; ct < n_col_tiles; ct++) {
for (int kt = 0; kt < n_k_tiles; kt++) {
uint8_t * tile_dst = matrix_dst + ((size_t) ct * n_k_tiles + kt) * tile_size;
pack_tile(tile_dst, src_slice, ne0, ne1, row_stride, ct, kt);
repack_parallel_for_ct(matrix_size, n_col_tiles, [&](int ct0, int ct1) {
for (int ct = ct0; ct < ct1; ct++) {
for (int kt = 0; kt < n_k_tiles; kt++) {
uint8_t * tile_dst = matrix_dst + ((size_t) ct * n_k_tiles + kt) * tile_size;
pack_tile(tile_dst, src_slice, ne0, ne1, row_stride, ct, kt);
}
}
}
});
}
}
@@ -130,8 +438,24 @@ static void unpack_from_tiled_grid_32(void * data, const ggml_tensor * t, size_t
static void repack_q4_0_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
repack_to_tiled_grid_32<block_q4_0, 1>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_Q4_0,
[](uint8_t * tile_dst, const block_q4_0 * src_slice, int64_t ne0, int64_t ne1, int64_t row_stride, int ct, int kt) {
uint8_t tile_quants[32][32];
ggml_half * scale_dst = (ggml_half *) (tile_dst + 512);
#if defined(__ARM_NEON) || defined(__aarch64__)
if (ct * 32 + 32 <= ne1 && kt < ne0 / 32) {
for (int row0 = 0; row0 < 32; row0 += 8) {
const uint8_t * qs[8];
for (int rr = 0; rr < 8; ++rr) {
qs[rr] = src_slice[(ct * 32 + row0 + rr) * row_stride + kt].qs;
}
repack_neon_4bit_rows(qs, tile_dst, row0);
}
for (int row = 0; row < 32; ++row) {
scale_dst[row] = src_slice[(ct * 32 + row) * row_stride + kt].d;
}
return;
}
#endif
uint8_t tile_quants[32][32];
for (int row = 0; row < 32; row++) {
const int64_t r = ct * 32 + row;
if (r < ne1 && kt < ne0 / 32) {
@@ -167,8 +491,26 @@ static void repack_tiled_q4_0(void * data, const ggml_tensor * t, size_t offset,
static void repack_q4_1_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
repack_to_tiled_grid_32<block_q4_1, 1>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_Q4_1,
[](uint8_t * tile_dst, const block_q4_1 * src_slice, int64_t ne0, int64_t ne1, int64_t row_stride, int ct, int kt) {
uint8_t tile_quants[32][32];
ggml_half * scale_dst = (ggml_half *) (tile_dst + 512);
#if defined(__ARM_NEON) || defined(__aarch64__)
if (ct * 32 + 32 <= ne1 && kt < ne0 / 32) {
for (int row0 = 0; row0 < 32; row0 += 8) {
const uint8_t * qs[8];
for (int rr = 0; rr < 8; ++rr) {
qs[rr] = src_slice[(ct * 32 + row0 + rr) * row_stride + kt].qs;
}
repack_neon_4bit_rows(qs, tile_dst, row0);
}
for (int row = 0; row < 32; ++row) {
const block_q4_1 & src_block = src_slice[(ct * 32 + row) * row_stride + kt];
scale_dst[2 * row + 0] = src_block.d;
scale_dst[2 * row + 1] = src_block.m;
}
return;
}
#endif
uint8_t tile_quants[32][32];
for (int row = 0; row < 32; row++) {
const int64_t r = ct * 32 + row;
if (r < ne1 && kt < ne0 / 32) {
@@ -207,8 +549,24 @@ static void repack_tiled_q4_1(void * data, const ggml_tensor * t, size_t offset,
static void repack_mxfp4_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
repack_to_tiled_grid_32<block_mxfp4, 1>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_MXFP4,
[](uint8_t * tile_dst, const block_mxfp4 * src_slice, int64_t ne0, int64_t ne1, int64_t row_stride, int ct, int kt) {
uint8_t tile_quants[32][32];
uint8_t * scale_dst = tile_dst + 512;
#if defined(__ARM_NEON) || defined(__aarch64__)
if (ct * 32 + 32 <= ne1 && kt < ne0 / 32) {
for (int row0 = 0; row0 < 32; row0 += 8) {
const uint8_t * qs[8];
for (int rr = 0; rr < 8; ++rr) {
qs[rr] = src_slice[(ct * 32 + row0 + rr) * row_stride + kt].qs;
}
repack_neon_4bit_rows(qs, tile_dst, row0);
}
for (int row = 0; row < 32; ++row) {
scale_dst[row] = src_slice[(ct * 32 + row) * row_stride + kt].e;
}
return;
}
#endif
uint8_t tile_quants[32][32];
for (int row = 0; row < 32; row++) {
const int64_t r = ct * 32 + row;
if (r < ne1 && kt < ne0 / 32) {
@@ -244,6 +602,23 @@ static void repack_tiled_mxfp4(void * data, const ggml_tensor * t, size_t offset
static void repack_q8_0_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
repack_to_tiled_grid_32<block_q8_0, 1>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_Q8_0,
[](uint8_t * tile_dst, const block_q8_0 * src_slice, int64_t ne0, int64_t ne1, int64_t row_stride, int ct, int kt) {
ggml_half * scale_dst = (ggml_half *) (tile_dst + 1024);
#if defined(__ARM_NEON) || defined(__aarch64__)
if (ct * 32 + 32 <= ne1 && kt < ne0 / 32) {
for (int row0 = 0; row0 < 32; row0 += 8) {
const uint8_t * qs[8];
for (int rr = 0; rr < 8; ++rr) {
qs[rr] = (const uint8_t *) src_slice[(ct * 32 + row0 + rr) * row_stride + kt].qs;
}
repack_neon_q8_rows(qs, tile_dst, row0);
}
for (int row = 0; row < 32; ++row) {
scale_dst[row] = src_slice[(ct * 32 + row) * row_stride + kt].d;
}
return;
}
#endif
for (int cp = 0; cp < 16; cp++) {
const int col0 = cp * 2;
const int col1 = col0 + 1;
@@ -254,7 +629,6 @@ static void repack_q8_0_tiled(ggml_tensor * t, const void * data, size_t offset,
tile_dst[cp * 64 + 2 * row + 1] = b ? b->qs[col1] : 0;
}
}
ggml_half * scale_dst = (ggml_half *) (tile_dst + 1024);
for (int row = 0; row < 32; row++) {
const int64_t r = ct * 32 + row;
scale_dst[row] = (r < ne1 && kt < ne0 / 32) ? src_slice[r * row_stride + kt].d : 0;
@@ -303,24 +677,33 @@ static void repack_q2_0_tiled(ggml_tensor * t, const void * data, size_t offset,
for (int64_t slice = 0; slice < t->ne[2] * t->ne[3]; ++slice) {
const block_q2_0 * src_slice = src + slice * t->ne[1] * blocks_per_row;
uint8_t * dst_slice = (uint8_t *) t->data + slice * matrix_size;
memset(dst_slice, 0, matrix_size);
const size_t ct_bytes = (size_t) n_weight_tiles * HTP_TENSOR_TILE_SIZE_Q2_0;
for (int64_t row_index = 0; row_index < t->ne[1]; ++row_index) {
const int64_t ct = row_index / 32;
const int row = (int) (row_index % 32);
for (int64_t kt = 0; kt < n_weight_tiles; ++kt) {
const block_q2_0 * block = src_slice + row_index * blocks_per_row + kt;
uint8_t * tile = dst_slice + (ct * n_weight_tiles + kt) * HTP_TENSOR_TILE_SIZE_Q2_0;
for (int group = 0; group < 16; ++group) {
for (int byte = 0; byte < 4; ++byte) {
const int k = 4 * group + byte;
const uint8_t code = (block->qs[k / 4] >> (2 * (k % 4))) & 3;
tile[(group >> 2) * 128 + 4 * row + byte] |= code << (2 * (group & 3));
repack_parallel_for_ct(matrix_size, (int) n_col_tiles, [&](int ct0, int ct1) {
memset(dst_slice + (size_t) ct0 * ct_bytes, 0, (size_t) (ct1 - ct0) * ct_bytes);
const int64_t r0 = (int64_t) ct0 * 32;
const int64_t r1 = (std::min)((int64_t) ct1 * 32, t->ne[1]);
for (int64_t row_index = r0; row_index < r1; ++row_index) {
const int64_t ct = row_index / 32;
const int row = (int) (row_index % 32);
for (int64_t kt = 0; kt < n_weight_tiles; ++kt) {
const block_q2_0 * block = src_slice + row_index * blocks_per_row + kt;
uint8_t * tile = dst_slice + (ct * n_weight_tiles + kt) * HTP_TENSOR_TILE_SIZE_Q2_0;
#if defined(__ARM_NEON) || defined(__aarch64__)
repack_neon_q2_0_block(block->qs, tile, row);
#else
for (int group = 0; group < 16; ++group) {
for (int byte = 0; byte < 4; ++byte) {
const int k = 4 * group + byte;
const uint8_t code = (block->qs[k / 4] >> (2 * (k % 4))) & 3;
tile[(group >> 2) * 128 + 4 * row + byte] |= code << (2 * (group & 3));
}
}
#endif
memcpy(tile + 512 + 2 * row, &block->d, sizeof(block->d));
}
memcpy(tile + 512 + 2 * row, &block->d, sizeof(block->d));
}
}
});
}
}
@@ -368,6 +751,12 @@ static void repack_float_to_f16_tiled(ggml_tensor * t, const void * data, size_t
repack_to_tiled_grid_32<SrcT, 32>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_F16,
[&](uint8_t * tile_bytes, const SrcT * src_slice, int64_t ne0, int64_t ne1, int64_t row_stride, int ct, int kt) {
uint16_t * tile_dst = (uint16_t *) tile_bytes;
#if defined(__ARM_NEON) || defined(__aarch64__)
if (ct * 32 + 32 <= ne1 && kt * 32 + 32 <= ne0) {
repack_neon_f16_tile(tile_dst, src_slice, row_stride, ct, kt);
return;
}
#endif
for (int cp = 0; cp < 16; cp++) {
const int64_t c0 = kt * 32 + cp * 2;
const int64_t c1 = c0 + 1;
@@ -424,20 +813,25 @@ static void repack_kquant_to_tiled_grid(ggml_tensor * t, const void * data, size
for (int64_t slice_idx = 0; slice_idx < ne2 * ne3; slice_idx++) {
const BlockT * src_slice = src_matrix + slice_idx * (ne1 * sb_per_row);
uint8_t * matrix_dst = (uint8_t *) t->data + slice_idx * matrix_size;
const size_t ct_bytes = (size_t) n_k_tiles * tile_size;
// Padding rows and OR-ed bit planes require zeroed tiles
memset(matrix_dst, 0, matrix_size);
repack_parallel_for_ct(matrix_size, n_col_tiles, [&](int ct0, int ct1) {
// Padding rows and OR-ed bit planes require zeroed tiles
memset(matrix_dst + (size_t) ct0 * ct_bytes, 0, (size_t) (ct1 - ct0) * ct_bytes);
for (int64_t r = 0; r < ne1; r++) {
const int ct = (int) (r / 32);
const int row = (int) (r % 32);
const BlockT * src_row = src_slice + r * sb_per_row;
const int64_t r0 = (int64_t) ct0 * 32;
const int64_t r1 = (std::min)((int64_t) ct1 * 32, ne1);
for (int64_t r = r0; r < r1; r++) {
const int ct = (int) (r / 32);
const int row = (int) (r % 32);
const BlockT * src_row = src_slice + r * sb_per_row;
for (int kt = 0; kt < n_k_tiles; kt++) {
uint8_t * tile = matrix_dst + ((size_t) ct * n_k_tiles + kt) * tile_size;
pack_kt(tile, &src_row[kt / 8], row, kt % 8);
for (int kt = 0; kt < n_k_tiles; kt++) {
uint8_t * tile = matrix_dst + ((size_t) ct * n_k_tiles + kt) * tile_size;
pack_kt(tile, &src_row[kt / 8], row, kt % 8);
}
}
}
});
}
}
@@ -493,6 +887,7 @@ static void unpack_kquant_from_tiled_grid(void * data, const ggml_tensor * t, si
// --- K-Quant Helpers & Per-Type Implementations (Q2_K, Q3_K, Q4_K, Q5_K, Q6_K) ---
#if !defined(__ARM_NEON) && !defined(__aarch64__)
// unsigned 6-bit value (0..63) of element e of a Q6_K block, same bit layout as dequantize_row_q6_K
static inline uint8_t q6_K_get_quant(const block_q6_K * b, int e) {
const int c = e / 128;
@@ -510,6 +905,7 @@ static inline uint8_t q6_K_get_quant(const block_q6_K * b, int e) {
}
return (uint8_t) (lo | (hi << 4));
}
#endif
static void repack_q6_K_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
repack_kquant_to_tiled_grid<block_q6_K>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_Q6_K,
@@ -519,6 +915,9 @@ static void repack_q6_K_tiled(ggml_tensor * t, const void * data, size_t offset,
uint8_t * hi_pl = tile + 512;
ggml_half * sc_pl = (ggml_half *) (tile + 768);
#if defined(__ARM_NEON) || defined(__aarch64__)
repack_neon_q6_k(b, kt_local, lo_pl, hi_pl, row);
#else
for (int lk = 0; lk < 32; lk++) {
const uint8_t q6 = q6_K_get_quant(b, kt_local * 32 + lk);
const int g = lk >> 2;
@@ -526,6 +925,7 @@ static void repack_q6_K_tiled(ggml_tensor * t, const void * data, size_t offset,
lo_pl[(g >> 1) * 128 + pos] |= (uint8_t) ((q6 & 0xF) << ((g & 1) * 4));
hi_pl[(g >> 2) * 128 + pos] |= (uint8_t) ((q6 >> 4) << ((g & 3) * 2));
}
#endif
for (int sub = 0; sub < 2; sub++) {
sc_pl[sub * 32 + row] = GGML_FP32_TO_FP16(d * (float) b->scales[kt_local * 2 + sub]);
}
@@ -585,6 +985,7 @@ static void repack_tiled_q6_K(void * data, const ggml_tensor * t, size_t offset,
});
}
#if !defined(__ARM_NEON) && !defined(__aarch64__)
// low 2 bits (0..3) of element e of a Q2_K or Q3_K block, same bit layout as dequantize_row_q2_K / q3_K
static inline uint8_t q2_3_K_get_low2(const uint8_t * qs, int e) {
const int c = e / 128;
@@ -600,6 +1001,7 @@ static inline bool q3_K_get_hbit(const block_q3_K * b, int e) {
const int l = e % 32;
return (b->hmask[l] >> (c * 4 + j)) & 1;
}
#endif
// signed 6-bit scale j (-32..31) of a Q3_K block, same packing as quantize_row_q3_K_ref
static inline int q3_K_get_scale(const uint8_t * scales, int j) {
@@ -670,6 +1072,10 @@ static void repack_q3_K_tiled(ggml_tensor * t, const void * data, size_t offset,
uint8_t * neg_pl = tile + 256;
ggml_half * sc_pl = (ggml_half *) (tile + 384);
#if defined(__ARM_NEON) || defined(__aarch64__)
repack_neon_q2_3_k_low2(b->qs, kt_local, lo_pl, row);
repack_neon_q3_k_hmask(b->hmask, kt_local, neg_pl, row);
#else
for (int lk = 0; lk < 32; lk++) {
const int e = kt_local * 32 + lk;
const int g = lk >> 2;
@@ -679,6 +1085,7 @@ static void repack_q3_K_tiled(ggml_tensor * t, const void * data, size_t offset,
neg_pl[pos] |= (uint8_t) (1 << g);
}
}
#endif
for (int sub = 0; sub < 2; sub++) {
sc_pl[sub * 32 + row] = GGML_FP32_TO_FP16(d * (float) q3_K_get_scale(b->scales, kt_local * 2 + sub));
}
@@ -750,11 +1157,15 @@ static void repack_q2_K_tiled(ggml_tensor * t, const void * data, size_t offset,
uint8_t * sc_pl = tile + 256;
ggml_half * dd_pl = (ggml_half *) (tile + 320);
#if defined(__ARM_NEON) || defined(__aarch64__)
repack_neon_q2_3_k_low2(b->qs, kt_local, lo_pl, row);
#else
for (int lk = 0; lk < 32; lk++) {
const int g = lk >> 2;
const int pos = row * 4 + (lk & 3);
lo_pl[(g >> 2) * 128 + pos] |= (uint8_t) (q2_3_K_get_low2(b->qs, kt_local * 32 + lk) << ((g & 3) * 2));
}
#endif
for (int sub = 0; sub < 2; sub++) {
sc_pl[sub * 32 + row] = b->scales[kt_local * 2 + sub];
}
@@ -846,23 +1257,46 @@ static inline void pack_k4_scales_mins(uint8_t * scales, ggml_half * d, ggml_hal
}
}
template <typename BlockT>
static inline void repack_k4_quants(uint8_t * tile_dst, const BlockT * b, int row, int kt_local,
int64_t sb_per_row, bool use_neon) {
#if defined(__ARM_NEON) || defined(__aarch64__)
if (use_neon) {
if ((row & 7) == 0) {
const uint8_t * qs[8];
for (int rr = 0; rr < 8; ++rr) {
qs[rr] = b[rr * sb_per_row].qs + (kt_local / 2) * 32;
}
repack_neon_k4_rows(qs, tile_dst, row, (kt_local & 1) != 0);
}
return;
}
#else
GGML_UNUSED(sb_per_row);
GGML_UNUSED(use_neon);
#endif
const uint8_t * qs_sub = b->qs + (kt_local / 2) * 32;
const int shift = (kt_local & 1) ? 4 : 0;
for (int cp = 0; cp < 16; cp++) {
const uint8_t q0 = (qs_sub[2 * cp + 0] >> shift) & 0x0F;
const uint8_t q1 = (qs_sub[2 * cp + 1] >> shift) & 0x0F;
tile_dst[cp * 32 + row] = (uint8_t) ((q1 << 4) | q0);
}
}
static void repack_q4_K_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
const int64_t sb_per_row = t->ne[0] / QK_K;
const bool use_neon = (t->ne[1] % 8 == 0);
repack_kquant_to_tiled_grid<block_q4_K>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_Q4_K,
[](uint8_t * tile_dst, const block_q4_K * b, int row, int kt_local) {
[sb_per_row, use_neon](uint8_t * tile_dst, const block_q4_K * b, int row, int kt_local) {
const float d = GGML_FP16_TO_FP32(b->d);
const float dmin = GGML_FP16_TO_FP32(b->dmin);
uint8_t sc, m;
get_scale_min_k4(kt_local, b->scales, &sc, &m);
const uint8_t * qs_sub = b->qs + (kt_local / 2) * 32;
const int shift = (kt_local & 1) ? 4 : 0;
for (int cp = 0; cp < 16; cp++) {
const uint8_t q0 = (qs_sub[2 * cp + 0] >> shift) & 0x0F;
const uint8_t q1 = (qs_sub[2 * cp + 1] >> shift) & 0x0F;
tile_dst[cp * 32 + row] = (uint8_t) ((q1 << 4) | q0);
}
repack_k4_quants(tile_dst, b, row, kt_local, sb_per_row, use_neon);
ggml_half * scale_dst = (ggml_half *) (tile_dst + 512);
scale_dst[2 * row + 0] = GGML_FP32_TO_FP16(d * (float) sc);
@@ -900,8 +1334,10 @@ static void repack_tiled_q4_K(void * data, const ggml_tensor * t, size_t offset,
}
static void repack_q5_K_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
const int64_t sb_per_row = t->ne[0] / QK_K;
const bool use_neon = (t->ne[1] % 8 == 0);
repack_kquant_to_tiled_grid<block_q5_K>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_Q5_K,
[](uint8_t * tile_dst, const block_q5_K * b, int row, int kt_local) {
[sb_per_row, use_neon](uint8_t * tile_dst, const block_q5_K * b, int row, int kt_local) {
const float d = GGML_FP16_TO_FP32(b->d);
const float dmin = GGML_FP16_TO_FP32(b->dmin);
uint8_t * plane = tile_dst + 640;
@@ -909,15 +1345,13 @@ static void repack_q5_K_tiled(ggml_tensor * t, const void * data, size_t offset,
uint8_t sc, m;
get_scale_min_k4(kt_local, b->scales, &sc, &m);
const uint8_t * qs_sub = b->qs + (kt_local / 2) * 32;
const int shift = (kt_local & 1) ? 4 : 0;
repack_k4_quants(tile_dst, b, row, kt_local, sb_per_row, use_neon);
const uint8_t hbit = (uint8_t) (1 << kt_local);
#if defined(__ARM_NEON) || defined(__aarch64__)
repack_neon_q5_k_qh(b->qh, hbit, plane, row);
#else
for (int cp = 0; cp < 16; cp++) {
const uint8_t q0 = (qs_sub[2 * cp + 0] >> shift) & 0x0F;
const uint8_t q1 = (qs_sub[2 * cp + 1] >> shift) & 0x0F;
tile_dst[cp * 32 + row] = (uint8_t) ((q1 << 4) | q0);
const int i = cp / 4;
const int lane = (cp % 4) * 32 + row;
if (b->qh[2 * cp + 0] & hbit) {
@@ -927,6 +1361,7 @@ static void repack_q5_K_tiled(ggml_tensor * t, const void * data, size_t offset,
plane[lane] |= (uint8_t) (1 << (2 * i + 1));
}
}
#endif
ggml_half * scale_dst = (ggml_half *) (tile_dst + 512);
scale_dst[2 * row + 0] = GGML_FP32_TO_FP16(d * (float) sc);
@@ -1002,31 +1437,37 @@ static void repack_q1_0_tiled(ggml_tensor * t, const void * data, size_t offset,
const block_q1_0 * src_slice = src_matrix + (slice_idx - start_slice) * ne1 * n_k_tiles;
uint8_t * matrix_dst = (uint8_t *) t->data + slice_idx * matrix_size;
for (int ct = 0; ct < n_col_tiles; ct++) {
for (int kt = 0; kt < n_k_tiles; kt++) {
uint8_t * tile_dst = matrix_dst + ((size_t) ct * n_k_tiles + kt) * tile_size;
ggml_half * scale_dst = (ggml_half *) (tile_dst + 512);
memset(tile_dst, 0, tile_size);
repack_parallel_for_ct(matrix_size, n_col_tiles, [&](int ct0, int ct1) {
for (int ct = ct0; ct < ct1; ct++) {
for (int kt = 0; kt < n_k_tiles; kt++) {
uint8_t * tile_dst = matrix_dst + ((size_t) ct * n_k_tiles + kt) * tile_size;
ggml_half * scale_dst = (ggml_half *) (tile_dst + 512);
memset(tile_dst, 0, tile_size);
for (int row = 0; row < 32; row++) {
const int64_t r = ct * 32 + row;
if (r >= ne1) {
continue;
}
const block_q1_0 & block = src_slice[r * n_k_tiles + kt];
scale_dst[row] = block.d;
for (int k = 0; k < QK1_0; k++) {
int byte_offset;
int bit;
q1_0_tiled_bit_location(k, row, byte_offset, bit);
if (block.qs[k / 8] & (1u << (k % 8))) {
tile_dst[byte_offset] |= (uint8_t) (1u << bit);
for (int row = 0; row < 32; row++) {
const int64_t r = ct * 32 + row;
if (r >= ne1) {
continue;
}
const block_q1_0 & block = src_slice[r * n_k_tiles + kt];
scale_dst[row] = block.d;
#if defined(__ARM_NEON) || defined(__aarch64__)
repack_neon_q1_0_block(block.qs, tile_dst, row);
#else
for (int k = 0; k < QK1_0; k++) {
int byte_offset;
int bit;
q1_0_tiled_bit_location(k, row, byte_offset, bit);
if (block.qs[k / 8] & (1u << (k % 8))) {
tile_dst[byte_offset] |= (uint8_t) (1u << bit);
}
}
#endif
}
}
}
}
});
}
}
@@ -1127,11 +1568,11 @@ void htp_repack_tensor_tiled(ggml_tensor * tensor, const void * data, size_t siz
break;
case GGML_TYPE_F32:
repack_float_to_f16_tiled<float>(tensor, data, 0, size,
[](float v) { return (uint16_t) ggml_fp32_to_fp16(v); });
[](float v) { return (uint16_t) GGML_FP32_TO_FP16(v); });
break;
case GGML_TYPE_BF16:
repack_float_to_f16_tiled<ggml_bf16_t>(tensor, data, 0, size,
[](ggml_bf16_t v) { return (uint16_t) ggml_fp32_to_fp16(ggml_bf16_to_fp32(v)); });
[](ggml_bf16_t v) { return (uint16_t) GGML_FP32_TO_FP16(GGML_BF16_TO_FP32(v)); });
break;
default:
break;
+11 -5
View File
@@ -316,9 +316,17 @@ static int vtcm_release_callback(unsigned int rctx, void * state) {
return 0;
}
static int vtcm_alloc(struct htp_context * ctx) {
static unsigned int vtcm_query_size(void) {
unsigned int vtcm_size = 8 * 1024 * 1024; // 8MB default
HAP_compute_res_query_VTCM(0, &vtcm_size, NULL, NULL, NULL);
unsigned int vtcm_avail;
compute_res_vtcm_page_t total_layout, avail_layout;
// some images (e.g. FuSa cDSP) fail the query when the layout out-params are NULL
HAP_compute_res_query_VTCM(0, &vtcm_size, &total_layout, &vtcm_avail, &avail_layout);
return vtcm_size;
}
static int vtcm_alloc(struct htp_context * ctx) {
unsigned int vtcm_size = vtcm_query_size();
compute_res_attr_t attr;
HAP_compute_res_attr_init(&attr);
@@ -770,9 +778,7 @@ AEEResult htp_iface_hwinfo(remote_handle64 handle, uint32_t * n_threads, uint32_
*n_hvx = n_hvx_val;
*n_hmx = 1;
uint32_t vtcm_sz = 8 * 1024 * 1024; // 8MB default fallback
HAP_compute_res_query_VTCM(0, (unsigned int *)&vtcm_sz, NULL, NULL, NULL);
*vtcm_size = vtcm_sz;
*vtcm_size = vtcm_query_size();
return AEE_SUCCESS;
}
+22 -6
View File
@@ -539,8 +539,14 @@ enum ggml_status ggml_metal_graph_compute(ggml_metal_t ctx, struct ggml_cgraph *
descriptor.outputURL = [NSURL fileURLWithPath:path];
NSError * error = nil;
if (![[MTLCaptureManager sharedCaptureManager] startCaptureWithDescriptor:descriptor error:&error]) {
const bool ok = [[MTLCaptureManager sharedCaptureManager] startCaptureWithDescriptor:descriptor error:&error];
[descriptor release];
if (!ok) {
GGML_LOG_ERROR("%s: error: unable to start capture '%s' (did you set METAL_CAPTURE_ENABLED=1 ?)\n", __func__, [[error localizedDescription] UTF8String]);
[ctx->capture_scope release];
ctx->capture_scope = nil;
} else {
[ctx->capture_scope beginScope];
ctx->capture_started = true;
@@ -600,6 +606,8 @@ enum ggml_status ggml_metal_graph_compute(ggml_metal_t ctx, struct ggml_cgraph *
// enter here only when capturing in order to wait for all computation to finish
// otherwise, we leave the graph to compute asynchronously
if (use_capture && ctx->capture_started) {
enum ggml_status res = GGML_STATUS_SUCCESS;
// wait for completion and check status of each command buffer
// needed to detect if the device ran out-of-memory for example (#1881)
{
@@ -613,11 +621,11 @@ enum ggml_status ggml_metal_graph_compute(ggml_metal_t ctx, struct ggml_cgraph *
GGML_LOG_INFO("error: %s\n", [[cmd_buf error].localizedDescription UTF8String]);
}
return GGML_STATUS_FAILED;
res = GGML_STATUS_FAILED;
}
}
for (int i = 0; i < n_cb; ++i) {
for (int i = 0; i < n_cb && res == GGML_STATUS_SUCCESS; ++i) {
id<MTLCommandBuffer> cmd_buf = ctx->cmd_bufs[i].obj;
[cmd_buf waitUntilCompleted];
@@ -627,8 +635,8 @@ enum ggml_status ggml_metal_graph_compute(ggml_metal_t ctx, struct ggml_cgraph *
if (status == MTLCommandBufferStatusError) {
GGML_LOG_INFO("error: %s\n", [[cmd_buf error].localizedDescription UTF8String]);
}
return GGML_STATUS_FAILED;
res = GGML_STATUS_FAILED;
break;
}
id<MTLCommandBuffer> next_buffer = (i + 1 < n_cb ? ctx->cmd_bufs[i + 1].obj : nil);
@@ -643,7 +651,8 @@ enum ggml_status ggml_metal_graph_compute(ggml_metal_t ctx, struct ggml_cgraph *
if (ctx->abort_callback && ctx->abort_callback(ctx->abort_callback_data)) {
GGML_LOG_INFO("%s: command buffer %d aborted", __func__, i);
return GGML_STATUS_ABORTED;
res = GGML_STATUS_ABORTED;
break;
}
[next_buffer commit];
@@ -652,7 +661,14 @@ enum ggml_status ggml_metal_graph_compute(ggml_metal_t ctx, struct ggml_cgraph *
[ctx->capture_scope endScope];
[[MTLCaptureManager sharedCaptureManager] stopCapture];
[ctx->capture_scope release];
ctx->capture_scope = nil;
ctx->capture_started = false;
if (res != GGML_STATUS_SUCCESS) {
return res;
}
}
}
+41 -9
View File
@@ -1243,6 +1243,7 @@ struct ggml_backend_opencl_context {
cl_kernel kernel_gemm_noshuffle_q4_1_f32;
cl_kernel kernel_gemm_noshuffle_q8_0_f32, kernel_gemm_noshuffle_q8_0_f32_bin;
cl_kernel kernel_gemm_noshuffle_q8_0_q8_1_dp4a = nullptr; // dp4a (int8) dense q8_0 prefill GEMM (opt-in)
cl_kernel kernel_gemm_noshuffle_q8_0_q8_1_dp4a_act_vload4 = nullptr;
cl_kernel kernel_gemm_noshuffle_q8_0_q8_1_dp4a_wimg = nullptr; // q8_0 dense dp4a, weights via texture (opt-in)
cl_kernel kernel_gemm_noshuffle_q8_0_q8_1_dp4a_ila_a8_bin = nullptr;
cl_kernel kernel_gemv_noshuffle_q8_0_f32;
@@ -1264,6 +1265,7 @@ struct ggml_backend_opencl_context {
cl_kernel kernel_gemm_noshuffle_q4_k_q8_1_dp4a_ila_a8_bin;
cl_kernel kernel_gemv_noshuffle_q4_k_f32_32b_trans;
cl_kernel kernel_gemm_noshuffle_q4_k_q8_1_dp4a = nullptr; // dp4a (int8) dense prefill GEMM
cl_kernel kernel_gemm_noshuffle_q4_k_q8_1_dp4a_act_vload4 = nullptr;
cl_kernel kernel_gemm_noshuffle_q4_k_q8_1_dp4a_wimg = nullptr; // dp4a dense prefill GEMM, weights via texture (X1 opt-in)
cl_kernel kernel_gemm_noshuffle_q4_k_q8_1_dp4a_narrow = nullptr;
cl_kernel kernel_gemm_noshuffle_q4_k_q8_1_dp4a_narrow_wimg = nullptr;
@@ -1271,7 +1273,9 @@ struct ggml_backend_opencl_context {
int tile_n_dp4a_narrow_q4_k = 32;
int max_n_dp4a_narrow_q4_k = 16;
cl_kernel kernel_gemm_noshuffle_q5_k_q8_1_dp4a = nullptr; // dp4a (int8) dense q5_K prefill GEMM
cl_kernel kernel_gemm_noshuffle_q5_k_q8_1_dp4a_act_vload4 = nullptr;
cl_kernel kernel_gemm_noshuffle_q6_k_q8_1_dp4a = nullptr; // dp4a (int8) dense q6_K prefill GEMM
cl_kernel kernel_gemm_noshuffle_q6_k_q8_1_dp4a_act_vload4 = nullptr;
cl_kernel kernel_quant_a_q8_1; // plain activation q8_1 pre-pass
cl_kernel kernel_gemm_noshuffle_q4_k_f32_r1;
cl_kernel kernel_gemm_noshuffle_q4_k_f32_kimg;
@@ -1305,6 +1309,7 @@ struct ggml_backend_opencl_context {
cl_kernel kernel_gemm_noshuffle_iq4_nl_f32;
cl_kernel kernel_gemm_noshuffle_iq4_nl_q8_1_dp4a = nullptr; // dp4a (int8) dense IQ4_NL prefill GEMM
cl_kernel kernel_gemm_noshuffle_q4_0_q8_1_dp4a = nullptr; // dp4a (int8) dense q4_0 prefill GEMM
cl_kernel kernel_gemm_noshuffle_q4_0_q8_1_dp4a_act_vload4 = nullptr;
cl_kernel kernel_gemm_coopk_q4_0_q8_1_dp4a_c4 = nullptr;
cl_kernel kernel_gemm_coopk_q4_0_q8_1_dp4a_c2 = nullptr;
int nsg_coopk_dp4a_q4_0 = 8;
@@ -4347,6 +4352,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
#endif
cl_program prog = build_program_from_source(backend_ctx, kernel_src.c_str(), compile_opts);
CL_CHECK((backend_ctx->kernel_gemm_noshuffle_q4_0_q8_1_dp4a = clCreateKernel(prog, "kernel_gemm_noshuffle_q4_0_q8_1_dp4a", &err), err));
CL_CHECK((backend_ctx->kernel_gemm_noshuffle_q4_0_q8_1_dp4a_act_vload4 = clCreateKernel(prog, "kernel_gemm_noshuffle_q4_0_q8_1_dp4a_act_vload4", &err), err));
CL_CHECK(clReleaseProgram(prog));
GGML_LOG_CONT(".");
}
@@ -4486,6 +4492,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
cl_program prog = build_program_from_source(backend_ctx, kernel_src.c_str(), dp4a_opts);
CL_CHECK((backend_ctx->kernel_gemm_noshuffle_q4_k_q8_1_dp4a = clCreateKernel(prog, "kernel_gemm_noshuffle_q4_k_q8_1_dp4a", &err), err));
CL_CHECK((backend_ctx->kernel_gemm_noshuffle_q4_k_q8_1_dp4a_wimg = clCreateKernel(prog, "kernel_gemm_noshuffle_q4_k_q8_1_dp4a_wimg", &err), err));
CL_CHECK((backend_ctx->kernel_gemm_noshuffle_q4_k_q8_1_dp4a_act_vload4 = clCreateKernel(prog, "kernel_gemm_noshuffle_q4_k_q8_1_dp4a_act_vload4", &err), err));
CL_CHECK(clReleaseProgram(prog));
// Compile a smaller N tile to avoid unused columns in small batches.
@@ -4518,6 +4525,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
cl_program prog = build_program_from_source(backend_ctx, kernel_src.c_str(), compile_opts);
CL_CHECK((backend_ctx->kernel_gemm_noshuffle_q8_0_q8_1_dp4a = clCreateKernel(prog, "kernel_gemm_noshuffle_q8_0_q8_1_dp4a", &err), err));
CL_CHECK((backend_ctx->kernel_gemm_noshuffle_q8_0_q8_1_dp4a_wimg = clCreateKernel(prog, "kernel_gemm_noshuffle_q8_0_q8_1_dp4a_wimg", &err), err));
CL_CHECK((backend_ctx->kernel_gemm_noshuffle_q8_0_q8_1_dp4a_act_vload4 = clCreateKernel(prog, "kernel_gemm_noshuffle_q8_0_q8_1_dp4a_act_vload4", &err), err));
CL_CHECK(clReleaseProgram(prog));
GGML_LOG_CONT(".");
}
@@ -4533,6 +4541,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
#endif
cl_program prog = build_program_from_source(backend_ctx, kernel_src.c_str(), compile_opts);
CL_CHECK((backend_ctx->kernel_gemm_noshuffle_q5_k_q8_1_dp4a = clCreateKernel(prog, "kernel_gemm_noshuffle_q5_k_q8_1_dp4a", &err), err));
CL_CHECK((backend_ctx->kernel_gemm_noshuffle_q5_k_q8_1_dp4a_act_vload4 = clCreateKernel(prog, "kernel_gemm_noshuffle_q5_k_q8_1_dp4a_act_vload4", &err), err));
CL_CHECK(clReleaseProgram(prog));
GGML_LOG_CONT(".");
}
@@ -4548,6 +4557,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
#endif
cl_program prog = build_program_from_source(backend_ctx, kernel_src.c_str(), compile_opts);
CL_CHECK((backend_ctx->kernel_gemm_noshuffle_q6_k_q8_1_dp4a = clCreateKernel(prog, "kernel_gemm_noshuffle_q6_k_q8_1_dp4a", &err), err));
CL_CHECK((backend_ctx->kernel_gemm_noshuffle_q6_k_q8_1_dp4a_act_vload4 = clCreateKernel(prog, "kernel_gemm_noshuffle_q6_k_q8_1_dp4a_act_vload4", &err), err));
CL_CHECK(clReleaseProgram(prog));
GGML_LOG_CONT(".");
}
@@ -20430,11 +20440,13 @@ static void ggml_cl_mul_mat_q4_0_f32_adreno(ggml_backend_t backend, const ggml_t
return;
}
// dp4a (int8) dense prefill GEMM, default off
static const char * q4_0_act_vload4_env = getenv("GGML_OPENCL_Q4_0_DP4A_ACT_VLOAD4");
const bool q4_0_act_vload4_on = q4_0_act_vload4_env == nullptr || atoi(q4_0_act_vload4_env) != 0;
// Enable dense prefill by default on X2E when the uint4 activation tile is enabled.
static const char * q4_0_dense_dp4a_env = getenv("GGML_OPENCL_Q4_0_DENSE_DP4A");
bool q4_0_dense_dp4a_on = q4_0_dense_dp4a_env
? (atoi(q4_0_dense_dp4a_env) != 0)
: false;
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E && q4_0_act_vload4_on);
// dot prod has to be available
q4_0_dense_dp4a_on = backend_ctx->has_integer_dot && q4_0_dense_dp4a_on;
@@ -20461,7 +20473,9 @@ static void ggml_cl_mul_mat_q4_0_f32_adreno(ggml_backend_t backend, const ggml_t
size_t q_global[1] = { (size_t)(((n_blocks + 63) / 64) * 64) };
backend_ctx->enqueue_ndrange_kernel(qk, 1, q_global, q_local, dst);
cl_kernel dk = backend_ctx->kernel_gemm_noshuffle_q4_0_q8_1_dp4a;
cl_kernel dk = q4_0_act_vload4_on
? backend_ctx->kernel_gemm_noshuffle_q4_0_q8_1_dp4a_act_vload4
: backend_ctx->kernel_gemm_noshuffle_q4_0_q8_1_dp4a;
int ai = 0;
CL_CHECK(clSetKernelArg(dk, ai++, sizeof(cl_mem), &extra0_q4_0->q));
CL_CHECK(clSetKernelArg(dk, ai++, sizeof(cl_mem), &extra0_q4_0->d));
@@ -21754,8 +21768,13 @@ static void ggml_cl_mul_mat_q8_0_f32_adreno(ggml_backend_t backend, const ggml_t
}
}
cl_kernel dk = use_wimg ? backend_ctx->kernel_gemm_noshuffle_q8_0_q8_1_dp4a_wimg
: backend_ctx->kernel_gemm_noshuffle_q8_0_q8_1_dp4a;
static const char * q8_0_act_vload4_env = getenv("GGML_OPENCL_Q8_0_DP4A_ACT_VLOAD4");
const bool q8_0_act_vload4_on = q8_0_act_vload4_env == nullptr || atoi(q8_0_act_vload4_env) != 0;
cl_kernel dk = use_wimg
? backend_ctx->kernel_gemm_noshuffle_q8_0_q8_1_dp4a_wimg
: q8_0_act_vload4_on
? backend_ctx->kernel_gemm_noshuffle_q8_0_q8_1_dp4a_act_vload4
: backend_ctx->kernel_gemm_noshuffle_q8_0_q8_1_dp4a;
int ai = 0;
if (use_wimg) {
CL_CHECK(clSetKernelArg(dk, ai++, sizeof(cl_mem), &q8_q_img));
@@ -22703,11 +22722,16 @@ static void ggml_cl_mul_mat_q4_k_f32_adreno(ggml_backend_t backend, const ggml_t
const bool use_narrow = backend_ctx->kernel_gemm_noshuffle_q4_k_q8_1_dp4a_narrow != nullptr
&& N <= backend_ctx->max_n_dp4a_narrow_q4_k;
static const char * q4_k_act_vload4_env = getenv("GGML_OPENCL_Q4_K_DP4A_ACT_VLOAD4");
const bool q4_k_act_vload4_on = q4_k_act_vload4_env == nullptr || atoi(q4_k_act_vload4_env) != 0;
cl_kernel dk = use_narrow
? (use_wimg ? backend_ctx->kernel_gemm_noshuffle_q4_k_q8_1_dp4a_narrow_wimg
: backend_ctx->kernel_gemm_noshuffle_q4_k_q8_1_dp4a_narrow)
: (use_wimg ? backend_ctx->kernel_gemm_noshuffle_q4_k_q8_1_dp4a_wimg
: backend_ctx->kernel_gemm_noshuffle_q4_k_q8_1_dp4a);
: use_wimg
? backend_ctx->kernel_gemm_noshuffle_q4_k_q8_1_dp4a_wimg
: q4_k_act_vload4_on
? backend_ctx->kernel_gemm_noshuffle_q4_k_q8_1_dp4a_act_vload4
: backend_ctx->kernel_gemm_noshuffle_q4_k_q8_1_dp4a;
int ai = 0;
if (use_wimg) {
CL_CHECK(clSetKernelArg(dk, ai++, sizeof(cl_mem), &q4k_q_img));
@@ -23369,7 +23393,11 @@ static void ggml_cl_mul_mat_q6_K_f32_adreno(ggml_backend_t backend, const ggml_t
size_t q_global[1] = { (size_t)(((n_blocks + 63) / 64) * 64) };
backend_ctx->enqueue_ndrange_kernel(qk, 1, q_global, q_local, dst);
cl_kernel dk = backend_ctx->kernel_gemm_noshuffle_q6_k_q8_1_dp4a;
static const char * q6_k_act_vload4_env = getenv("GGML_OPENCL_Q6_K_DP4A_ACT_VLOAD4");
const bool q6_k_act_vload4_on = q6_k_act_vload4_env == nullptr || atoi(q6_k_act_vload4_env) != 0;
cl_kernel dk = q6_k_act_vload4_on
? backend_ctx->kernel_gemm_noshuffle_q6_k_q8_1_dp4a_act_vload4
: backend_ctx->kernel_gemm_noshuffle_q6_k_q8_1_dp4a;
int ai = 0;
CL_CHECK(clSetKernelArg(dk, ai++, sizeof(cl_mem), &extra0_q6_K->ql));
CL_CHECK(clSetKernelArg(dk, ai++, sizeof(cl_mem), &extra0_q6_K->qh));
@@ -23925,7 +23953,11 @@ static void ggml_cl_mul_mat_q5_K_f32_adreno(ggml_backend_t backend, const ggml_t
size_t q_global[1] = { (size_t)(((n_blocks + 63) / 64) * 64) };
backend_ctx->enqueue_ndrange_kernel(qk, 1, q_global, q_local, dst);
cl_kernel dk = backend_ctx->kernel_gemm_noshuffle_q5_k_q8_1_dp4a;
static const char * q5_k_act_vload4_env = getenv("GGML_OPENCL_Q5_K_DP4A_ACT_VLOAD4");
const bool q5_k_act_vload4_on = q5_k_act_vload4_env == nullptr || atoi(q5_k_act_vload4_env) != 0;
cl_kernel dk = q5_k_act_vload4_on
? backend_ctx->kernel_gemm_noshuffle_q5_k_q8_1_dp4a_act_vload4
: backend_ctx->kernel_gemm_noshuffle_q5_k_q8_1_dp4a;
int ai = 0;
CL_CHECK(clSetKernelArg(dk, ai++, sizeof(cl_mem), &extra0_q5_k->q));
CL_CHECK(clSetKernelArg(dk, ai++, sizeof(cl_mem), &extra0_q5_k->qh));
@@ -125,3 +125,129 @@ kernel void kernel_gemm_noshuffle_q4_0_q8_1_dp4a(
}
#undef NGROUPS
}
// Stage each 32-K activation block as two uint4 values.
inline int dot8_q8a_v(uint8 qw, uint4 a0, uint4 a1) {
int r = 0;
r = dot_acc_sat_4x8packed_ss_int(qw.s0, a0.x, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s1, a0.y, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s2, a0.z, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s3, a0.w, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s4, a1.x, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s5, a1.y, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s6, a1.z, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s7, a1.w, r);
return r;
}
__attribute__((qcom_wave_pair_mode(1)))
kernel void kernel_gemm_noshuffle_q4_0_q8_1_dp4a_act_vload4(
__global const ushort * src0_q,
__global const half * src0_d,
__global const uint * src1_qa,
__global const half * src1_da,
__global const half * src1_sa,
__global float * dst,
ulong offsetd,
int m,
int n_no_padding,
int k
) {
dst = (global float *)((global char *)dst + offsetd);
const uint lid = get_local_id(0);
const uint block_id_m = get_global_id(1);
const uint block_id_n = get_global_id(2);
const uint row = block_id_m * 64 + lid;
const uint col_base = block_id_n * TILESIZE_N;
const bool row_valid = row < (uint)m;
const uint rrow = row_valid ? row : 0;
const uint k_u = (uint)k >> 2;
const uint k_b = (uint)k >> 5;
__local uint4 sh_qa4[TILESIZE_N][2];
__local half sh_d[TILESIZE_N];
__local half sh_s[TILESIZE_N];
#define NGROUPS (TILESIZE_N / 4)
float4 acc[NGROUPS];
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
acc[g] = (float4)(0.0f);
}
for (uint step = 0; step < (uint)k; step += 32) {
const uint sub = step >> 5;
const float d_w = (float)src0_d[rrow + sub * (uint)m];
const uint qsbase = rrow + (step >> 2) * (uint)m;
uint8 qw;
qw.s0 = EXP4(src0_q[qsbase + 0 * m]);
qw.s1 = EXP4(src0_q[qsbase + 1 * m]);
qw.s2 = EXP4(src0_q[qsbase + 2 * m]);
qw.s3 = EXP4(src0_q[qsbase + 3 * m]);
qw.s4 = EXP4(src0_q[qsbase + 4 * m]);
qw.s5 = EXP4(src0_q[qsbase + 5 * m]);
qw.s6 = EXP4(src0_q[qsbase + 6 * m]);
qw.s7 = EXP4(src0_q[qsbase + 7 * m]);
for (uint idx = lid; idx < TILESIZE_N * 2; idx += 64) {
const uint t = idx >> 1;
const uint v = idx & 1;
const uint c = col_base + t;
sh_qa4[t][v] = (c < (uint)n_no_padding)
? vload4(0, src1_qa + c * k_u + (step >> 2) + (v << 2))
: (uint4)(0u);
}
if (lid < TILESIZE_N) {
const uint c = col_base + lid;
sh_d[lid] = (c < (uint)n_no_padding) ? src1_da[c * k_b + sub] : (half)0;
sh_s[lid] = (c < (uint)n_no_padding) ? src1_sa[c * k_b + sub] : (half)0;
}
barrier(CLK_LOCAL_MEM_FENCE);
#define LD4(arr, b) ((float4)((float)arr[(b)+0], (float)arr[(b)+1], (float)arr[(b)+2], (float)arr[(b)+3]))
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
const int b = g * 4;
float4 rf;
#define DOTV(T) dot8_q8a_v(qw, sh_qa4[T][0], sh_qa4[T][1])
rf.s0 = (float)DOTV(b+0);
rf.s1 = (float)DOTV(b+1);
rf.s2 = (float)DOTV(b+2);
rf.s3 = (float)DOTV(b+3);
#undef DOTV
acc[g] += d_w * (LD4(sh_d, b) * rf - 8.0f * LD4(sh_s, b));
}
#undef LD4
barrier(CLK_LOCAL_MEM_FENCE);
}
if (!row_valid) {
return;
}
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
const uint b = (uint)(g * 4);
const float4 a = acc[g];
const uint c0 = col_base + b;
if (c0 + 0 < (uint)n_no_padding) {
dst[(c0 + 0) * (uint)m + row] = a.s0;
}
if (c0 + 1 < (uint)n_no_padding) {
dst[(c0 + 1) * (uint)m + row] = a.s1;
}
if (c0 + 2 < (uint)n_no_padding) {
dst[(c0 + 2) * (uint)m + row] = a.s2;
}
if (c0 + 3 < (uint)n_no_padding) {
dst[(c0 + 3) * (uint)m + row] = a.s3;
}
}
#undef NGROUPS
}
@@ -279,3 +279,141 @@ kernel void kernel_gemm_noshuffle_q4_k_q8_1_dp4a_wimg(
}
#undef NGROUPS
}
// Stage each 32-K activation block as two uint4 values.
inline int dot8_q8a_v(uint8 qw, uint4 a0, uint4 a1) {
int r = 0;
r = dot_acc_sat_4x8packed_ss_int(qw.s0, a0.x, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s1, a0.y, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s2, a0.z, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s3, a0.w, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s4, a1.x, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s5, a1.y, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s6, a1.z, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s7, a1.w, r);
return r;
}
__attribute__((qcom_wave_pair_mode(1)))
kernel void kernel_gemm_noshuffle_q4_k_q8_1_dp4a_act_vload4(
__global const ushort * src0_q,
__global const uchar * src0_s,
__global const half * src0_d,
__global const half * src0_dm,
__global const uint * src1_qa,
__global const half * src1_da,
__global const half * src1_sa,
__global float * dst,
ulong offsetd,
int m,
int n_no_padding,
int k,
uchar mask_d6,
uchar mask_d4,
uchar mask_hi2
) {
dst = (global float *)((global char *)dst + offsetd);
const uint lid = get_local_id(0);
const uint block_id_m = get_global_id(1);
const uint block_id_n = get_global_id(2);
const uint row = block_id_m * 64 + lid;
const uint col_base = block_id_n * TILESIZE_N;
const bool row_valid = row < (uint)m;
const uint rrow = row_valid ? row : 0;
const uint k_u = (uint)k >> 2;
const uint k_b = (uint)k >> 5;
__local uint4 sh_qa4[TILESIZE_N][2];
__local half sh_d[TILESIZE_N];
__local half sh_s[TILESIZE_N];
#define NGROUPS (TILESIZE_N / 4)
float4 acc[NGROUPS];
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
acc[g] = (float4)(0.0f);
}
for (uint step = 0; step < (uint)k; step += 32) {
const uint sub = step >> 5;
const uint sb_idx = step / QK_K;
const uint sub_idx = sub & 7;
const float dd = (float)src0_d [rrow + sb_idx * m];
const float dmm = (float)src0_dm[rrow + sb_idx * m];
global const uchar * sc = src0_s + sb_idx * K_SCALE_SIZE * (uint)m + rrow;
uchar sv, mn;
get_scale_min_k4(sub_idx, sc, (uint)m, &sv, &mn, mask_d6, mask_d4, mask_hi2);
const float scale = dd * (float)sv;
const float minv = dmm * (float)mn;
const uint wbase = rrow + (step >> 2) * (uint)m;
uint8 qw;
qw.s0 = EXP4(src0_q[wbase + 0 * m]);
qw.s1 = EXP4(src0_q[wbase + 1 * m]);
qw.s2 = EXP4(src0_q[wbase + 2 * m]);
qw.s3 = EXP4(src0_q[wbase + 3 * m]);
qw.s4 = EXP4(src0_q[wbase + 4 * m]);
qw.s5 = EXP4(src0_q[wbase + 5 * m]);
qw.s6 = EXP4(src0_q[wbase + 6 * m]);
qw.s7 = EXP4(src0_q[wbase + 7 * m]);
for (uint idx = lid; idx < TILESIZE_N * 2; idx += 64) {
const uint t = idx >> 1;
const uint v = idx & 1;
const uint c = col_base + t;
sh_qa4[t][v] = (c < (uint)n_no_padding)
? vload4(0, src1_qa + c * k_u + (step >> 2) + (v << 2))
: (uint4)(0u);
}
if (lid < TILESIZE_N) {
const uint c = col_base + lid;
sh_d[lid] = (c < (uint)n_no_padding) ? src1_da[c * k_b + sub] : (half)0;
sh_s[lid] = (c < (uint)n_no_padding) ? src1_sa[c * k_b + sub] : (half)0;
}
barrier(CLK_LOCAL_MEM_FENCE);
#define LD4(arr, b) ((float4)((float)arr[(b)+0], (float)arr[(b)+1], (float)arr[(b)+2], (float)arr[(b)+3]))
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
const int b = g * 4;
float4 rf;
#define DOTV(T) dot8_q8a_v(qw, sh_qa4[T][0], sh_qa4[T][1])
rf.s0 = (float)DOTV(b+0);
rf.s1 = (float)DOTV(b+1);
rf.s2 = (float)DOTV(b+2);
rf.s3 = (float)DOTV(b+3);
#undef DOTV
acc[g] += scale * LD4(sh_d, b) * rf - minv * LD4(sh_s, b);
}
#undef LD4
barrier(CLK_LOCAL_MEM_FENCE);
}
if (!row_valid) {
return;
}
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
const uint b = (uint)(g * 4);
const float4 a = acc[g];
const uint c0 = col_base + b;
if (c0 + 0 < (uint)n_no_padding) {
dst[(c0 + 0) * (uint)m + row] = a.s0;
}
if (c0 + 1 < (uint)n_no_padding) {
dst[(c0 + 1) * (uint)m + row] = a.s1;
}
if (c0 + 2 < (uint)n_no_padding) {
dst[(c0 + 2) * (uint)m + row] = a.s2;
}
if (c0 + 3 < (uint)n_no_padding) {
dst[(c0 + 3) * (uint)m + row] = a.s3;
}
}
#undef NGROUPS
}
@@ -162,3 +162,141 @@ kernel void kernel_gemm_noshuffle_q5_k_q8_1_dp4a(
}
#undef NGROUPS
}
// Stage each 32-K activation block as two uint4 values.
inline int dot8_q8a_v(uint8 qw, uint4 a0, uint4 a1) {
int r = 0;
r = dot_acc_sat_4x8packed_ss_int(qw.s0, a0.x, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s1, a0.y, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s2, a0.z, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s3, a0.w, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s4, a1.x, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s5, a1.y, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s6, a1.z, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s7, a1.w, r);
return r;
}
__attribute__((qcom_wave_pair_mode(1)))
kernel void kernel_gemm_noshuffle_q5_k_q8_1_dp4a_act_vload4(
__global const ushort * src0_q,
__global const uchar * src0_qh,
__global const uchar * src0_s,
__global const half * src0_d,
__global const half * src0_dm,
__global const uint * src1_qa,
__global const half * src1_da,
__global const half * src1_sa,
__global float * dst,
ulong offsetd,
int m,
int n_no_padding,
int k,
uchar mask_d6,
uchar mask_d4,
uchar mask_hi2
) {
dst = (global float *)((global char *)dst + offsetd);
const uint lid = get_local_id(0);
const uint block_id_m = get_global_id(1);
const uint block_id_n = get_global_id(2);
const uint row = block_id_m * 64 + lid;
const uint col_base = block_id_n * TILESIZE_N;
const bool row_valid = row < (uint)m;
const uint rrow = row_valid ? row : 0;
const uint num_superblocks = (uint)k / QK_K;
const uint k_u = (uint)k >> 2;
const uint k_b = (uint)k >> 5;
__local uint4 sh_qa4[TILESIZE_N][2];
__local half sh_d[TILESIZE_N];
__local half sh_s[TILESIZE_N];
#define NGROUPS (TILESIZE_N / 4)
float4 acc[NGROUPS];
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
acc[g] = (float4)(0.0f);
}
for (uint step = 0; step < (uint)k; step += 32) {
const uint sub = step >> 5;
const uint sb_idx = step / QK_K;
const uint sub_idx = sub & 7;
const float dd = (float)src0_d [rrow + sb_idx * m];
const float dmm = (float)src0_dm[rrow + sb_idx * m];
global const uchar * sc = src0_s + rrow * num_superblocks * K_SCALE_SIZE + sb_idx * K_SCALE_SIZE;
uchar sv, mn;
get_scale_min_k4(sub_idx, sc, &sv, &mn, mask_d6, mask_d4, mask_hi2);
const float scale = dd * (float)sv;
const float minv = dmm * (float)mn;
const uint wbase = rrow + (step >> 2) * (uint)m;
const uint qhbase = rrow + (step >> 3) * (uint)m;
uint8 qw;
#define QWU(u) ( EXP4((uint)src0_q[wbase + (uint)(u) * m]) \
| EXP1( (uint)((src0_qh[qhbase + (uint)((u) >> 1) * m] >> (((u) & 1) * 4)) & 0x0Fu) ) )
qw.s0 = QWU(0); qw.s1 = QWU(1); qw.s2 = QWU(2); qw.s3 = QWU(3);
qw.s4 = QWU(4); qw.s5 = QWU(5); qw.s6 = QWU(6); qw.s7 = QWU(7);
#undef QWU
for (uint idx = lid; idx < TILESIZE_N * 2; idx += 64) {
const uint t = idx >> 1;
const uint v = idx & 1;
const uint c = col_base + t;
sh_qa4[t][v] = (c < (uint)n_no_padding)
? vload4(0, src1_qa + c * k_u + (step >> 2) + (v << 2))
: (uint4)(0u);
}
if (lid < TILESIZE_N) {
const uint c = col_base + lid;
sh_d[lid] = (c < (uint)n_no_padding) ? src1_da[c * k_b + sub] : (half)0;
sh_s[lid] = (c < (uint)n_no_padding) ? src1_sa[c * k_b + sub] : (half)0;
}
barrier(CLK_LOCAL_MEM_FENCE);
#define LD4(arr, b) ((float4)((float)arr[(b)+0], (float)arr[(b)+1], (float)arr[(b)+2], (float)arr[(b)+3]))
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
const int b = g * 4;
float4 rf;
#define DOTV(T) dot8_q8a_v(qw, sh_qa4[T][0], sh_qa4[T][1])
rf.s0 = (float)DOTV(b+0);
rf.s1 = (float)DOTV(b+1);
rf.s2 = (float)DOTV(b+2);
rf.s3 = (float)DOTV(b+3);
#undef DOTV
acc[g] += scale * LD4(sh_d, b) * rf - minv * LD4(sh_s, b);
}
#undef LD4
barrier(CLK_LOCAL_MEM_FENCE);
}
if (!row_valid) {
return;
}
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
const uint b = (uint)(g * 4);
const float4 a = acc[g];
const uint c0 = col_base + b;
if (c0 + 0 < (uint)n_no_padding) {
dst[(c0 + 0) * (uint)m + row] = a.s0;
}
if (c0 + 1 < (uint)n_no_padding) {
dst[(c0 + 1) * (uint)m + row] = a.s1;
}
if (c0 + 2 < (uint)n_no_padding) {
dst[(c0 + 2) * (uint)m + row] = a.s2;
}
if (c0 + 3 < (uint)n_no_padding) {
dst[(c0 + 3) * (uint)m + row] = a.s3;
}
}
#undef NGROUPS
}
@@ -142,3 +142,117 @@ kernel void kernel_gemm_noshuffle_q6_k_q8_1_dp4a(
}
#undef NGROUPS
}
// Stage each 32-K activation block as two uint4 values.
__attribute__((qcom_wave_pair_mode(1)))
kernel void kernel_gemm_noshuffle_q6_k_q8_1_dp4a_act_vload4(
__global const ushort * src0_ql,
__global const uchar * src0_qh,
__global const ushort * src0_s,
__global const half * src0_d,
__global const uint * src1_qa,
__global const half * src1_da,
__global float * dst,
ulong offsetd,
int m,
int n_no_padding,
int k
) {
dst = (global float *)((global char *)dst + offsetd);
const uint lid = get_local_id(0);
const uint block_id_m = get_global_id(1);
const uint block_id_n = get_global_id(2);
const uint row = block_id_m * 64 + lid;
const uint col_base = block_id_n * TILESIZE_N;
const bool row_valid = row < (uint)m;
const uint rrow = row_valid ? row : 0;
const uint k_u = (uint)k >> 2;
const uint k_b = (uint)k >> 5;
__local uint4 sh_qa4[TILESIZE_N][2];
__local half sh_d[TILESIZE_N];
#define NGROUPS (TILESIZE_N / 4)
float4 acc[NGROUPS];
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
acc[g] = (float4)(0.0f);
}
for (uint step = 0; step < (uint)k; step += 32) {
const uint sub = step >> 5;
const uint sb_idx = step / QK_K;
const float dd = (float)src0_d[rrow + sb_idx * m];
const char2 sc = as_char2(src0_s[rrow + sub * m]);
const float scale0 = dd * (float)sc.s0;
const float scale1 = dd * (float)sc.s1;
const uint wbase = rrow + (step >> 2) * (uint)m;
uint qw[8];
#pragma unroll
for (int u = 0; u < 8; ++u) {
const uint o = wbase + (uint)u * (uint)m;
qw[u] = SIGN6(EXP4((uint)src0_ql[o]) | EXP2((uint)src0_qh[o]));
}
for (uint idx = lid; idx < TILESIZE_N * 2; idx += 64) {
const uint t = idx >> 1;
const uint v = idx & 1;
const uint c = col_base + t;
sh_qa4[t][v] = (c < (uint)n_no_padding)
? vload4(0, src1_qa + c * k_u + (step >> 2) + (v << 2))
: (uint4)(0u);
}
if (lid < TILESIZE_N) {
const uint c = col_base + lid;
sh_d[lid] = (c < (uint)n_no_padding) ? src1_da[c * k_b + sub] : (half)0;
}
barrier(CLK_LOCAL_MEM_FENCE);
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
const int b = g * 4;
float4 rf;
#define DOT_TOK(j) { \
const uint4 a0 = sh_qa4[b + (j)][0]; \
const uint4 a1 = sh_qa4[b + (j)][1]; \
const int raw1 = dot4_q8a(qw[0], qw[1], qw[2], qw[3], a0.x, a0.y, a0.z, a0.w); \
const int raw2 = dot4_q8a(qw[4], qw[5], qw[6], qw[7], a1.x, a1.y, a1.z, a1.w); \
rf.s##j = scale0 * (float)raw1 + scale1 * (float)raw2; \
}
DOT_TOK(0); DOT_TOK(1); DOT_TOK(2); DOT_TOK(3);
#undef DOT_TOK
const float4 ad = (float4)((float)sh_d[b+0], (float)sh_d[b+1], (float)sh_d[b+2], (float)sh_d[b+3]);
acc[g] += ad * rf;
}
barrier(CLK_LOCAL_MEM_FENCE);
}
if (!row_valid) {
return;
}
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
const uint b = (uint)(g * 4);
const float4 a = acc[g];
const uint c0 = col_base + b;
if (c0 + 0 < (uint)n_no_padding) {
dst[(c0 + 0) * (uint)m + row] = a.s0;
}
if (c0 + 1 < (uint)n_no_padding) {
dst[(c0 + 1) * (uint)m + row] = a.s1;
}
if (c0 + 2 < (uint)n_no_padding) {
dst[(c0 + 2) * (uint)m + row] = a.s2;
}
if (c0 + 3 < (uint)n_no_padding) {
dst[(c0 + 3) * (uint)m + row] = a.s3;
}
}
#undef NGROUPS
}
@@ -210,3 +210,125 @@ kernel void kernel_gemm_noshuffle_q8_0_q8_1_dp4a_wimg(
}
#undef NGROUPS
}
// Stage each 32-K activation block as two uint4 values.
inline int dot8_q8a_v(uint8 qw, uint4 a0, uint4 a1) {
int r = 0;
r = dot_acc_sat_4x8packed_ss_int(qw.s0, a0.x, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s1, a0.y, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s2, a0.z, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s3, a0.w, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s4, a1.x, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s5, a1.y, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s6, a1.z, r);
r = dot_acc_sat_4x8packed_ss_int(qw.s7, a1.w, r);
return r;
}
__attribute__((qcom_wave_pair_mode(1)))
kernel void kernel_gemm_noshuffle_q8_0_q8_1_dp4a_act_vload4(
__global const uint * src0_q,
__global const half * src0_d,
__global const uint * src1_qa,
__global const half * src1_da,
__global float * dst,
ulong offsetd,
int m,
int n_no_padding,
int k
) {
dst = (global float *)((global char *)dst + offsetd);
const uint lid = get_local_id(0);
const uint block_id_m = get_global_id(1);
const uint block_id_n = get_global_id(2);
const uint row = block_id_m * 64 + lid;
const uint col_base = block_id_n * TILESIZE_N;
const bool row_valid = row < (uint)m;
const uint rrow = row_valid ? row : 0;
const uint k_u = (uint)k >> 2;
const uint k_b = (uint)k >> 5;
__local uint4 sh_qa4[TILESIZE_N][2];
__local half sh_d[TILESIZE_N];
#define NGROUPS (TILESIZE_N / 4)
float4 acc[NGROUPS];
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
acc[g] = (float4)(0.0f);
}
for (uint step = 0; step < (uint)k; step += 32) {
const uint sub = step >> 5;
const float d_w = (float)src0_d[rrow + sub * (uint)m];
const uint wbase = rrow + (step >> 2) * (uint)m;
uint8 qw;
qw.s0 = src0_q[wbase + 0 * m];
qw.s1 = src0_q[wbase + 1 * m];
qw.s2 = src0_q[wbase + 2 * m];
qw.s3 = src0_q[wbase + 3 * m];
qw.s4 = src0_q[wbase + 4 * m];
qw.s5 = src0_q[wbase + 5 * m];
qw.s6 = src0_q[wbase + 6 * m];
qw.s7 = src0_q[wbase + 7 * m];
for (uint idx = lid; idx < TILESIZE_N * 2; idx += 64) {
const uint t = idx >> 1;
const uint v = idx & 1;
const uint c = col_base + t;
sh_qa4[t][v] = (c < (uint)n_no_padding)
? vload4(0, src1_qa + c * k_u + (step >> 2) + (v << 2))
: (uint4)(0u);
}
if (lid < TILESIZE_N) {
const uint c = col_base + lid;
sh_d[lid] = (c < (uint)n_no_padding) ? src1_da[c * k_b + sub] : (half)0;
}
barrier(CLK_LOCAL_MEM_FENCE);
#define LD4(arr, b) ((float4)((float)arr[(b)+0], (float)arr[(b)+1], (float)arr[(b)+2], (float)arr[(b)+3]))
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
const int b = g * 4;
float4 rf;
#define DOTV(T) dot8_q8a_v(qw, sh_qa4[T][0], sh_qa4[T][1])
rf.s0 = (float)DOTV(b+0);
rf.s1 = (float)DOTV(b+1);
rf.s2 = (float)DOTV(b+2);
rf.s3 = (float)DOTV(b+3);
#undef DOTV
acc[g] += d_w * LD4(sh_d, b) * rf;
}
#undef LD4
barrier(CLK_LOCAL_MEM_FENCE);
}
if (!row_valid) {
return;
}
#pragma unroll
for (int g = 0; g < NGROUPS; ++g) {
const uint b = (uint)(g * 4);
const float4 a = acc[g];
const uint c0 = col_base + b;
if (c0 + 0 < (uint)n_no_padding) {
dst[(c0 + 0) * (uint)m + row] = a.s0;
}
if (c0 + 1 < (uint)n_no_padding) {
dst[(c0 + 1) * (uint)m + row] = a.s1;
}
if (c0 + 2 < (uint)n_no_padding) {
dst[(c0 + 2) * (uint)m + row] = a.s2;
}
if (c0 + 3 < (uint)n_no_padding) {
dst[(c0 + 3) * (uint)m + row] = a.s3;
}
}
#undef NGROUPS
}
+2 -2
View File
@@ -520,7 +520,7 @@ kernel void kernel_rope_multi_f16(
src0 = (global void*)((global char*)src0 + offset0);
src1 = (global int*)((global char*)src1 + offset1);
src2 = (global float*)((global char*)src2 + offset2);
dst = (global float*)((global char*)dst + offsetd);
dst = (global half*)((global char*)dst + offsetd);
int i3 = get_group_id(2);
int i2 = get_group_id(1);
@@ -714,7 +714,7 @@ kernel void kernel_rope_vision_f16(
src0 = (global void*)((global char*)src0 + offset0);
src1 = (global int*)((global char*)src1 + offset1);
src2 = (global float*)((global char*)src2 + offset2);
dst = (global float*)((global char*)dst + offsetd);
dst = (global half*)((global char*)dst + offsetd);
int i3 = get_group_id(2);
int i2 = get_group_id(1);
+4 -2
View File
@@ -440,7 +440,8 @@ static void dequantize_row_iq3_xxs_sycl_reorder(const void * vx, dst_t * y, cons
sycl::nd_range<3>(sycl::range<3>(1, 1, nb) * sycl::range<3>(1, 1, 32), sycl::range<3>(1, 1, 32)),
[=](sycl::nd_item<3> item_ct1) {
dequantize_block_iq3_xxs_reorder(
vx, y, item_ct1, nb, iq3xxs_grid, ksigns_iq2xs, kmask_iq2xs);
vx, y + item_ct1.get_group(2) * QK_K, item_ct1.get_group(2), item_ct1, nb,
iq3xxs_grid, ksigns_iq2xs, kmask_iq2xs);
});
}
@@ -472,7 +473,8 @@ static void dequantize_row_iq3_s_sycl_reorder(const void * vx, dst_t * y, const
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, nb) * sycl::range<3>(1, 1, 32), sycl::range<3>(1, 1, 32)),
[=](sycl::nd_item<3> item_ct1) {
dequantize_block_iq3_s_reorder(vx, y, item_ct1, nb, kmask_iq2xs, iq3s_grid);
dequantize_block_iq3_s_reorder(vx, y + item_ct1.get_group(2) * QK_K, item_ct1.get_group(2),
item_ct1, nb, kmask_iq2xs, iq3s_grid);
});
}
+6 -6
View File
@@ -1481,12 +1481,11 @@ static void dequantize_block_iq3_xxs(const void * __restrict__ vx, dst_t * __res
}
template<typename dst_t>
static void dequantize_block_iq3_xxs_reorder(const void * __restrict__ vx, dst_t * __restrict__ yy,
static void dequantize_block_iq3_xxs_reorder(const void * __restrict__ vx, dst_t * __restrict__ yy, const int64_t i,
const sycl::nd_item<3> & item_ct1, const int64_t nb,
const uint32_t * iq3xxs_grid,
const uint8_t * ksigns_iq2xs,
const uint8_t * kmask_iq2xs) {
const int64_t i = item_ct1.get_group(2);
const int64_t tid = item_ct1.get_local_id(2);
#if QK_K == 256
const int64_t il = tid / 8;
@@ -1501,7 +1500,7 @@ static void dequantize_block_iq3_xxs_reorder(const void * __restrict__ vx, dst_t
const uint32_t aux32 = gas[0] | (gas[1] << 16);
const float dl = (float) d[i] * (0.5f + (aux32 >> 28)) * 0.5f;
const uint8_t signs = ksigns_iq2xs[(aux32 >> (7 * il)) & 127];
dst_t * y = yy + i * QK_K + 32 * ib + 8 * il;
dst_t * y = yy + 32 * ib + 8 * il;
#pragma unroll
for (int j = 0; j < 4; ++j) {
y[j+0] = dl * grid1[j] * (signs & kmask_iq2xs[j+0] ? -1.f : 1.f);
@@ -1510,6 +1509,7 @@ static void dequantize_block_iq3_xxs_reorder(const void * __restrict__ vx, dst_t
#else
GGML_UNUSED(vx);
GGML_UNUSED(yy);
GGML_UNUSED(i);
GGML_UNUSED(item_ct1);
GGML_UNUSED(nb);
GGML_UNUSED(iq3xxs_grid);
@@ -1551,10 +1551,9 @@ dequantize_block_iq3_s(const void *__restrict__ vx, dst_t *__restrict__ yy,
template <typename dst_t>
__dpct_inline__ static void
dequantize_block_iq3_s_reorder(const void * __restrict__ vx, dst_t * __restrict__ yy,
dequantize_block_iq3_s_reorder(const void * __restrict__ vx, dst_t * __restrict__ yy, const int64_t i,
const sycl::nd_item<3> & item_ct1, const int64_t nb,
const uint8_t * kmask_iq2xs, const uint32_t * iq3s_grid) {
const int64_t i = item_ct1.get_group(2);
const int64_t tid = item_ct1.get_local_id(2);
#if QK_K == 256
const int64_t il = tid / 8;
@@ -1574,7 +1573,7 @@ dequantize_block_iq3_s_reorder(const void * __restrict__ vx, dst_t * __restrict_
iq3s_grid + (qs_ib[2*il+1] | ((qh[ib] << (7 - 2*il)) & 256)));
const float dl = (float) d * (1 + 2 * ((scales[ib/2] >> (4 * (ib%2))) & 0xf));
const uint8_t signs = signs_base[4 * ib + il];
dst_t * y = yy + i * QK_K + 32 * ib + 8 * il;
dst_t * y = yy + 32 * ib + 8 * il;
#pragma unroll
for (int j = 0; j < 4; ++j) {
y[j+0] = dl * grid1[j] * (signs & kmask_iq2xs[j+0] ? -1.f : 1.f);
@@ -1583,6 +1582,7 @@ dequantize_block_iq3_s_reorder(const void * __restrict__ vx, dst_t * __restrict_
#else
GGML_UNUSED(vx);
GGML_UNUSED(yy);
GGML_UNUSED(i);
GGML_UNUSED(item_ct1);
GGML_UNUSED(nb);
GGML_UNUSED(kmask_iq2xs);
+16
View File
@@ -281,6 +281,22 @@ static best_fattn_kernel ggml_sycl_get_best_fattn_kernel(const int device, const
void ggml_sycl_flash_attn_ext(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
ggml_sycl_set_device(ctx.device);
ggml_tensor * Q = dst->src[0];
ggml_tensor * K = dst->src[1];
ggml_tensor * V = dst->src[2];
if (K && Q && K->ne[0] != Q->ne[0] && Q->ne[0] > 0 && K->ne[0] % Q->ne[0] == 0) {
const int64_t d = Q->ne[0];
const int64_t n_heads = (K->ne[0] * K->ne[2]) / d;
K->ne[0] = d;
K->ne[2] = n_heads;
}
if (V && Q && V->ne[0] != Q->ne[0] && Q->ne[0] > 0 && V->ne[0] % Q->ne[0] == 0) {
const int64_t d = Q->ne[0];
const int64_t n_heads = (V->ne[0] * V->ne[2]) / d;
V->ne[0] = d;
V->ne[2] = n_heads;
}
// sparse nodes are gathered down to n_kv_max rows and re-dispatched here
if (ggml_sycl_flash_attn_ext_sparse(ctx, dst)) {
return;
+17 -11
View File
@@ -860,7 +860,7 @@ static void fused_dequant_gemm_tile(
}
}
template <typename S, typename block_q_t>
template <typename S, typename block_q_t, bool reordered = false>
static void fused_dequant_gemm_launch(const void * src0, const typename S::tsb * packed, float * dst, const int M,
const int N, const int Npad, const int K, const int ldd,
const int64_t groups_n, const int64_t groups_m, dpct::queue_ptr stream) {
@@ -871,8 +871,8 @@ static void fused_dequant_gemm_launch(const void * src0, const typename S::tsb *
sycl::nd_range<2>(sycl::range<2>(groups_n, groups_m * S::WG_SIZE), sycl::range<2>(1, S::WG_SIZE)),
[=](sycl::nd_item<2> item) [[sycl::reqd_sub_group_size(S::SG)]] {
const int n0 = item.get_group(0) * S::BN;
fused_dequant_gemm_tile<S, block_q_t, false>((const block_q_t *) src0, packed, dst, M, Npad, K, ldd,
n0, n0, N, tile_a, tile_c, item);
fused_dequant_gemm_tile<S, block_q_t, reordered>((const block_q_t *) src0, packed, dst, M, Npad, K,
ldd, n0, n0, N, tile_a, tile_c, item);
});
});
}
@@ -974,7 +974,7 @@ template <typename F> static bool fg_visit_type(ggml_type type, bool reordered,
}
template <typename S>
static bool fg_fused_run(ggml_type src0_type, const void * src0, const void * src1, ggml_type src1_type,
static bool fg_fused_run(ggml_type src0_type, bool src0_reordered, const void * src0, const void * src1, ggml_type src1_type,
float * dst, int64_t M, int64_t N, int64_t K, int64_t ldd, ggml_sycl_pool & pool,
dpct::queue_ptr stream) {
const int64_t groups_n = (N + S::BN - 1) / S::BN;
@@ -992,21 +992,27 @@ static bool fg_fused_run(ggml_type src0_type, const void * src0, const void * sr
}
const typename S::tsb * packed = packed_b.get();
return fg_visit_type(src0_type, false, [&](auto tag) {
return fg_visit_type(src0_type, src0_reordered, [&](auto tag) {
using block_q_t = typename decltype(tag)::type;
if constexpr (fg_plain_ok<block_q_t>()) {
fused_dequant_gemm_launch<S, block_q_t>(src0, packed, dst, (int) M, (int) N, Npad, (int) K, (int) ldd,
groups_n, groups_m, stream);
fused_dequant_gemm_launch<S, block_q_t, decltype(tag)::reordered>(src0, packed, dst, (int) M, (int) N,
Npad, (int) K, (int) ldd, groups_n,
groups_m, stream);
}
});
}
bool ggml_sycl_fused_dequant_gemm(ggml_type src0_type, const void * src0, const void * src1, ggml_type src1_type,
bool ggml_sycl_fused_dequant_gemm(ggml_type src0_type, bool src0_reordered, const void * src0, const void * src1, ggml_type src1_type,
int32_t src1_prec, float * dst, int64_t M, int64_t N, int64_t K, int64_t ldd,
ggml_sycl_pool & pool, dpct::queue_ptr stream) {
// every BN columns dequantize A again, so wide N is left to the library GEMM
// every BN columns dequantize A again, so wide N is left to the library GEMM.
// src0_reordered: the persistent weight reorder may have been installed on this
// tensor by an earlier small-batch mul_mat; the A stage then has to decode the
// SoA layout. Types without an SoA stage (fg_soa<T>::supported == false)
// decline via fg_visit_layout and the caller falls through to the
// reorder-aware conversion route.
bool plain_ok = false;
fg_visit_type(src0_type, false, [&](auto tag) { plain_ok = fg_plain_ok<typename decltype(tag)::type>(); });
fg_visit_type(src0_type, src0_reordered, [&](auto tag) { plain_ok = fg_plain_ok<typename decltype(tag)::type>(); });
if (g_ggml_sycl_dynamic_precision == GGML_SYCL_DYNAMIC_PRECISION_F32 ||
!ggml_sycl_xmx_gather_type_enabled(src0_type) || !plain_ok) {
return false;
@@ -1023,7 +1029,7 @@ bool ggml_sycl_fused_dequant_gemm(ggml_type src0_type, const void * src0, const
}
bool launched = false;
fg_visit_combo(combo, [&](auto s) {
launched = fg_fused_run<decltype(s)>(src0_type, src0, src1, src1_type, dst, M, N, K, ldd, pool, stream);
launched = fg_fused_run<decltype(s)>(src0_type, src0_reordered, src0, src1, src1_type, dst, M, N, K, ldd, pool, stream);
});
return launched;
}
+1 -1
View File
@@ -69,7 +69,7 @@ inline bool ggml_sycl_xmx_gather_type_enabled(ggml_type src0_type) {
// (op_params[3], [TAG_GGML_PREC]); the accumulator is f32, which meets any request.
// q8_0 and the k-quants are not handled here, only in the grouped path below.
// Returns false when the case is not handled (type, device, precision, or shape).
bool ggml_sycl_fused_dequant_gemm(ggml_type src0_type, const void * src0, const void * src1, ggml_type src1_type,
bool ggml_sycl_fused_dequant_gemm(ggml_type src0_type, bool src0_reordered, const void * src0, const void * src1, ggml_type src1_type,
int32_t src1_prec, float * dst, int64_t M, int64_t N, int64_t K, int64_t ldd,
ggml_sycl_pool & pool, dpct::queue_ptr stream);
+67 -1
View File
@@ -301,6 +301,52 @@ static void get_rows_sycl_reorder_k(const ggml_tensor *src0, const ggml_tensor *
GGML_UNUSED(src0);
}
// IQ3_S / IQ3_XXS in the persistent reorder layout: one work-group of 32 threads
// dequantizes one QK_K block of one requested row, reusing the reorder-aware
// block dequantizers that the dequantize-based mul_mat path already uses.
template <typename F>
static void get_rows_sycl_reorder_iq3(ggml_backend_sycl_context & ctx, const ggml_tensor * src0,
const ggml_tensor * src1, ggml_tensor * dst,
const int32_t * src1_dd, float * dst_dd,
F dequantize_block) {
GGML_TENSOR_BINARY_OP_LOCALS
// the persistent reorder is only installed for 2D weights
GGML_ASSERT(ne02 == 1 && ne03 == 1);
GGML_ASSERT(ne00 % QK_K == 0);
const int64_t nblk = ne00 / QK_K;
const int64_t nb = nblk * ne01; // blocks in the whole tensor
const int64_t ngroups = ne10 * ne11 * ne12 * nblk;
const size_t s1 = nb1 / ggml_element_size(dst);
const size_t s2 = nb2 / ggml_element_size(dst);
const size_t s3 = nb3 / ggml_element_size(dst);
const size_t s10 = nb10 / ggml_element_size(src1);
const size_t s11 = nb11 / ggml_element_size(src1);
const size_t s12 = nb12 / ggml_element_size(src1);
queue_ptr stream = ctx.stream();
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, ngroups * 32), sycl::range<3>(1, 1, 32)),
[=](sycl::nd_item<3> item_ct1) {
const int64_t g = item_ct1.get_group(2);
const int64_t b = g % nblk;
const int64_t r = g / nblk;
const int64_t i10 = r % ne10;
const int64_t i11 = (r / ne10) % ne11;
const int64_t i12 = r / (ne10 * ne11);
const int64_t i01 = src1_dd[i10*s10 + i11*s11 + i12*s12];
float * y = dst_dd + i10*s1 + i11*s2 + i12*s3 + b*QK_K;
dequantize_block(y, i01*nblk + b, item_ct1, nb);
});
GGML_UNUSED(dst);
}
template <int qk, int qr, dequantize_kernel_f32_t dq>
static void get_rows_sycl_f32(ggml_backend_sycl_context & ctx, const ggml_tensor *src0, const ggml_tensor *src1,
ggml_tensor *dst, const void *src0_dd,
@@ -460,7 +506,9 @@ void ggml_sycl_op_get_rows(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
GGML_ASSERT(dst->nb[0] == ggml_type_size(dst->type));
const int32_t * src1_i32 = (const int32_t *) dst->src[1]->data;
// a weight shared with MUL_MAT (e.g. tied embeddings) can be in reorder layout
// a weight shared with MUL_MAT (e.g. tied embeddings) can be in the
// persistent reorder layout (incl. IQ3_S / IQ3_XXS since the SYCL IQ3
// reorder work)
const bool src0_reordered = dst->src[0]->extra &&
((const ggml_tensor_extra_gpu *) dst->src[0]->extra)->optimized_feature.reorder;
/* TODO: Refactor and remove duplicates */
@@ -506,6 +554,15 @@ void ggml_sycl_op_get_rows(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
src1_i32, (float *)dst->data, ctx.stream());
break;
case GGML_TYPE_IQ3_XXS:
if (src0_reordered) {
const void * vx = dst->src[0]->data;
get_rows_sycl_reorder_iq3(ctx, dst->src[0], dst->src[1], dst, src1_i32, (float *) dst->data,
[=](float * y, int64_t iblk, const sycl::nd_item<3> & item_ct1, int64_t nb) {
dequantize_block_iq3_xxs_reorder(vx, y, iblk, item_ct1, nb,
iq3xxs_grid, ksigns_iq2xs, kmask_iq2xs);
});
break;
}
get_rows_sycl<QK_K, 1, dequantize_iq3_xxs>(ctx, dst->src[0], dst->src[1], dst, (const float *)dst->src[0]->data,
src1_i32, (float *)dst->data, ctx.stream());
break;
@@ -518,6 +575,15 @@ void ggml_sycl_op_get_rows(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
src1_i32, (float *)dst->data, ctx.stream());
break;
case GGML_TYPE_IQ3_S:
if (src0_reordered) {
const void * vx = dst->src[0]->data;
get_rows_sycl_reorder_iq3(ctx, dst->src[0], dst->src[1], dst, src1_i32, (float *) dst->data,
[=](float * y, int64_t iblk, const sycl::nd_item<3> & item_ct1, int64_t nb) {
dequantize_block_iq3_s_reorder(vx, y, iblk, item_ct1, nb,
kmask_iq2xs, iq3s_grid);
});
break;
}
get_rows_sycl<QK_K, 1, dequantize_iq3_s>(ctx, dst->src[0], dst->src[1], dst, (const float *)dst->src[0]->data,
src1_i32, (float *)dst->data, ctx.stream());
break;
+9 -2
View File
@@ -3154,9 +3154,16 @@ inline void ggml_sycl_op_mul_mat_sycl(
#endif
// dequantize inside the GEMM instead of writing the f16 weights out and reading them back; src1
// goes in its own type, so there is no separate conversion pass
// goes in its own type, so there is no separate conversion pass. The A stage
// decodes the weight bytes in place, so an installed persistent reorder must be
// visible to it: pass the flag through; types without an SoA A-stage decline
// inside and this falls through to the reorder-aware conversion route below.
bool src0_reordered = false;
if (const auto * src0_extra = static_cast<const ggml_tensor_extra_gpu *>(src0->extra)) {
src0_reordered = src0_extra->optimized_feature.reorder;
}
if (ggml_is_quantized(src0->type) && ggml_is_contiguous(src0) && row_diff == src0->ne[1] &&
ggml_sycl_fused_dequant_gemm(src0->type, src0_dd_i, src1_ddf_i, src1->type, ggml_sycl_src1_prec(dst), dst_dd_i,
ggml_sycl_fused_dequant_gemm(src0->type, src0_reordered, src0_dd_i, src1_ddf_i, src1->type, ggml_sycl_src1_prec(dst), dst_dd_i,
row_diff, src1_ncols, ne10, ldc, ctx.pool(), stream)) {
return;
}
+318 -656
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -344,6 +344,7 @@ class Keys:
ROUTING_BLOCK_COUNT = "{arch}.decision.routing_block_count"
HEAD_COUNT = "{arch}.decision.head_count"
MAX_HEAD_TOKENS = "{arch}.decision.max_head_tokens"
LABELS = "{arch}.decision.labels" # the text of the token read for each option, in order
TEMPERATURE = "{arch}.decision.temperature.{name}" # name: "<type>" or "<type>.<n_opt bucket>"
SCORE_BIAS = "{arch}.decision.score_bias.{n}.{i}" # added to the logit of level i of a score question with n levels
@@ -6339,6 +6340,7 @@ class DecisionType:
LFM2_D1 = "lfm2-d1" # same as openjev, the labels depend on the question type
LFM2_D1_OMNI = "lfm2-d1-omni" # same head as laya on a bidirectional LFM2 trunk, other prompt layout
GRANITE_SWITCH = "granite-switch" # same as openjev, run by the decision aLoRA of granite switch
H2O_LIGHTNING = "h2o-lightning" # same as openjev, labels read after an answer prefill
class VisionProjectorType:
+3
View File
@@ -1401,6 +1401,9 @@ class GGUFWriter:
def add_decision_max_head_tokens(self, value: int) -> None:
self.add_uint32(Keys.Decision.MAX_HEAD_TOKENS.format(arch=self.arch), value)
def add_decision_labels(self, labels: Sequence[str]) -> None:
self.add_array(Keys.Decision.LABELS.format(arch=self.arch), labels)
def add_decision_temperature(self, name: str, value: float) -> None:
self.add_float32(Keys.Decision.TEMPERATURE.format(arch=self.arch, name=name), value)
+2 -2
View File
@@ -179,13 +179,13 @@ static void llama_adapter_lora_init_impl(llama_model & model, FILE * file, llama
? format("%s[%s,%zu]", gguf_type_name(type), gguf_type_name(gguf_get_arr_type(gguf_ctx, i)), gguf_get_arr_n(gguf_ctx, i))
: gguf_type_name(type);
const char * name = gguf_get_key(gguf_ctx, i);
const std::string value = gguf_kv_to_str(gguf_ctx, i);
const size_t MAX_VALUE_LEN = 40;
const std::string value = gguf_kv_to_str(gguf_ctx, i, MAX_VALUE_LEN);
if (type != GGUF_TYPE_ARRAY) {
adapter.gguf_kv.emplace(name, value);
}
const size_t MAX_VALUE_LEN = 40;
std::string print_value = value.size() > MAX_VALUE_LEN ? format("%s...", value.substr(0, MAX_VALUE_LEN - 3).c_str()) : value;
replace_all(print_value, "\n", "\\n");
+2 -1
View File
@@ -383,6 +383,7 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {
{ LLM_KV_DECISION_BLOCK_COUNT, "%s.decision.block_count" },
{ LLM_KV_DECISION_ROUTING_BLOCK_COUNT, "%s.decision.routing_block_count" },
{ LLM_KV_DECISION_HEAD_COUNT, "%s.decision.head_count" },
{ LLM_KV_DECISION_LABELS, "%s.decision.labels" },
{ LLM_KV_TARGET_LAYERS, "%s.target_layers" },
{ LLM_KV_TARGET_HIDDEN_SIZE, "%s.target_hidden_size" },
@@ -1109,7 +1110,7 @@ static const std::map<llm_tensor, llm_tensor_info> LLM_TENSOR_INFOS = {
{LLM_TENSOR_MASKED_EMBD_ORDERING, {LLM_TENSOR_LAYER_INPUT, GGML_OP_NONE}},
// eagle3
{LLM_TENSOR_FC, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}},
{LLM_TENSOR_D2T, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_GET_ROWS}},
{LLM_TENSOR_D2T, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_SET_ROWS}},
// dspark
{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}},
+1
View File
@@ -431,6 +431,7 @@ enum llm_kv {
LLM_KV_DECISION_BLOCK_COUNT,
LLM_KV_DECISION_ROUTING_BLOCK_COUNT,
LLM_KV_DECISION_HEAD_COUNT,
LLM_KV_DECISION_LABELS,
LLM_KV_TARGET_LAYERS,
LLM_KV_TARGET_HIDDEN_SIZE,
+3
View File
@@ -122,6 +122,9 @@ enum llama_decision_order {
// The embeddings output has one value per entry: row i is the score of option i
LLAMA_API bool llama_batch_ext_set_decision_order(struct llama_batch_ext * batch, int32_t idx, enum llama_decision_order order);
// Label of option i of a decision model ("<arch>.decision.labels"), nullptr past the last one
LLAMA_API const char * llama_model_decision_label(const struct llama_model * model, uint32_t i);
// mirrors:
// LLAMA_API float * llama_get_embeddings(struct llama_context * ctx);
LLAMA_API float * llama_get_embeddings_nextn(struct llama_context * ctx);
+52 -5
View File
@@ -1666,6 +1666,40 @@ ggml_tensor * llm_graph_context::build_lora_mm(
return res;
}
llm_graph_amax_src1 llm_graph_context::build_amax_src1_f16(
ggml_tensor * cur) const {
llm_graph_amax_src1 result = {
/*.res =*/ cur,
/*.scale_s =*/ nullptr,
};
const int64_t n_embd = cur->ne[0];
// amax = max|cur| along the rows for each [expert, token] -> [1, n_expert, n_tokens]
ggml_tensor * ab = ggml_abs(ctx0, cur);
ggml_tensor * amax = ggml_pool_1d(ctx0, ab, GGML_OP_POOL_MAX, n_embd, n_embd, 0);
// ref: https://github.com/ggml-org/llama.cpp/pull/26223
constexpr float k_log2_e = 1.4426950408889634f; // 1 / ln(2)
constexpr float k_ln_2 = 0.6931471805599453f;
ggml_tensor * e;
e = ggml_scale(ctx0, amax, 1.0f/32768.0f);
e = ggml_log (ctx0, e);
e = ggml_scale(ctx0, e, k_log2_e);
e = ggml_ceil (ctx0, e);
e = ggml_clamp(ctx0, e, 0.0f, 127.0f);
e = ggml_scale(ctx0, e, k_ln_2);
e = ggml_exp (ctx0, e);
result = {
/*.res =*/ ggml_div(ctx0, cur, e),
/*.scale_s =*/ e,
};
return result;
}
ggml_tensor * llm_graph_context::build_lora_mm_id(
ggml_tensor * w, // ggml_tensor * as
ggml_tensor * cur, // ggml_tensor * b
@@ -2465,12 +2499,25 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
GGML_ABORT("fatal error");
}
experts = build_lora_mm_id(down_exps, cur, selected_experts, down_exps_s, slots); // [n_embd, n_expert_used, n_tokens]
if (arch == LLM_ARCH_MISTRAL4 || arch == LLM_ARCH_MELLUM) {
// src1 can exceed F16 range
ggml_prec_set_src(experts, GGML_PREC_F32, 1);
{
// TODO: this graph-level rescaling should only be applied when the corresponding backend does not support
// ggml_prec_set_src(experts, GGML_PREC_BF16, 1);
// ref: https://github.com/ggml-org/llama.cpp/pull/30656#issuecomment-6269597922
llm_graph_amax_src1 amax_src1 = { nullptr, nullptr };
if (arch == LLM_ARCH_MISTRAL4 || arch == LLM_ARCH_MELLUM) {
amax_src1 = build_amax_src1_f16(cur);
cur = amax_src1.res;
}
experts = build_lora_mm_id(down_exps, cur, selected_experts, down_exps_s, slots); // [n_embd, n_expert_used, n_tokens]
//ggml_prec_set_src(experts, GGML_PREC_BF16, 1); // (see TODO above)
cb(experts, "ffn_moe_down", il);
if (amax_src1.scale_s) {
experts = ggml_mul(ctx0, experts, amax_src1.scale_s);
}
}
cb(experts, "ffn_moe_down", il);
if (down_exps_s) {
cb(experts, "ffn_moe_down_scaled", il);
+10
View File
@@ -1012,6 +1012,11 @@ struct llm_graph_qkv {
ggml_tensor * v; // [n_embd_head, n_head_kv, n_tokens]
};
struct llm_graph_amax_src1 {
ggml_tensor * res; // cur rescaled
ggml_tensor * scale_s; // scale to undo on the matmul result, or nullptr
};
struct llm_graph_context {
const llm_arch arch;
@@ -1115,6 +1120,11 @@ struct llm_graph_context {
ggml_tensor * w_s = nullptr,
ggml_tensor * slots = nullptr) const;
// rescale `cur` (the src1 of a matmul) by a power of two, computed from its maximum absolute
// when the values already fit in F16, the factors are exactly 1.0f
llm_graph_amax_src1 build_amax_src1_f16(
ggml_tensor * cur) const;
ggml_tensor * build_norm(
ggml_tensor * cur,
ggml_tensor * mw,
+30 -21
View File
@@ -11,7 +11,6 @@
#include <cstdarg>
#include <cstring>
#include <vector>
#include <sstream>
struct llama_logger_state {
ggml_log_callback log_callback = llama_log_callback_default;
@@ -149,7 +148,7 @@ std::string llama_format_tensor_shape(const struct ggml_tensor * t) {
return buf;
}
static std::string gguf_data_to_str(enum gguf_type type, const void * data, int i) {
static std::string gguf_data_to_str(enum gguf_type type, const void * data, size_t i) {
switch (type) {
case GGUF_TYPE_UINT8: return std::to_string(((const uint8_t *)data)[i]);
case GGUF_TYPE_INT8: return std::to_string(((const int8_t *)data)[i]);
@@ -166,7 +165,7 @@ static std::string gguf_data_to_str(enum gguf_type type, const void * data, int
}
}
std::string gguf_kv_to_str(const struct gguf_context * ctx_gguf, int i) {
std::string gguf_kv_to_str(const struct gguf_context * ctx_gguf, int i, size_t max_len) {
const enum gguf_type type = gguf_get_kv_type(ctx_gguf, i);
switch (type) {
@@ -175,28 +174,38 @@ std::string gguf_kv_to_str(const struct gguf_context * ctx_gguf, int i) {
case GGUF_TYPE_ARRAY:
{
const enum gguf_type arr_type = gguf_get_arr_type(ctx_gguf, i);
int arr_n = gguf_get_arr_n(ctx_gguf, i);
const size_t arr_n = gguf_get_arr_n(ctx_gguf, i);
const void * data = arr_type == GGUF_TYPE_STRING ? nullptr : gguf_get_arr_data(ctx_gguf, i);
std::stringstream ss;
ss << "[";
for (int j = 0; j < arr_n; j++) {
if (arr_type == GGUF_TYPE_STRING) {
std::string val = gguf_get_arr_str(ctx_gguf, i, j);
// escape quotes
replace_all(val, "\\", "\\\\");
replace_all(val, "\"", "\\\"");
ss << '"' << val << '"';
} else if (arr_type == GGUF_TYPE_ARRAY) {
ss << "???";
} else {
ss << gguf_data_to_str(arr_type, data, j);
std::string res = "[";
size_t j = 0;
for (; j < arr_n && res.size() <= max_len; j++) {
if (j > 0) {
res += ", ";
}
if (j < arr_n - 1) {
ss << ", ";
if (arr_type == GGUF_TYPE_STRING) {
const char * s = gguf_get_arr_str(ctx_gguf, i, j);
const size_t need = res.size() + 2 * strlen(s) + 2;
if (res.capacity() < need) {
res.reserve(std::max(need, 2 * res.capacity()));
}
res += '"';
for (; *s; s++) {
if (*s == '\\' || *s == '"') {
res += '\\';
}
res += *s;
}
res += '"';
} else if (arr_type == GGUF_TYPE_ARRAY) {
res += "???";
} else {
res += gguf_data_to_str(arr_type, data, j);
}
}
ss << "]";
return ss.str();
if (j == arr_n) {
res += "]";
}
return res;
}
default:
return gguf_data_to_str(type, gguf_get_val_data(ctx_gguf, i), 0);
+2 -1
View File
@@ -107,4 +107,5 @@ std::string format(const char * fmt, ...);
std::string llama_format_tensor_shape(const std::vector<int64_t> & ne);
std::string llama_format_tensor_shape(const struct ggml_tensor * t);
std::string gguf_kv_to_str(const struct gguf_context * ctx_gguf, int i);
// arrays are rendered only until the result is longer than max_len
std::string gguf_kv_to_str(const struct gguf_context * ctx_gguf, int i, size_t max_len = SIZE_MAX);
+8 -1
View File
@@ -807,8 +807,8 @@ llama_model_loader::llama_model_loader(
? format("%s[%s,%zu]", gguf_type_name(type), gguf_type_name(gguf_get_arr_type(metadata, i)), gguf_get_arr_n(metadata, i))
: gguf_type_name(type);
std::string value = gguf_kv_to_str(metadata, i);
const size_t MAX_VALUE_LEN = 40;
std::string value = gguf_kv_to_str(metadata, i, MAX_VALUE_LEN);
if (value.size() > MAX_VALUE_LEN) {
value = format("%s...", value.substr(0, MAX_VALUE_LEN - 3).c_str());
}
@@ -951,6 +951,13 @@ static bool weight_buft_supported(const llama_hparams & hparams, ggml_tensor * w
ggml_tensor * b = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, 512);
op_tensor = ggml_get_rows(ctx, w, b);
} break;
case GGML_OP_SET_ROWS:
{
// w holds the row indices (e.g. d2t)
ggml_tensor * a = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, w->ne[0]);
ggml_tensor * b = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, w->ne[0]);
op_tensor = ggml_set_rows(ctx, a, b, w);
} break;
case GGML_OP_MUL_MAT:
{
ggml_tensor * b = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, w->ne[0], 512, w->ne[2], w->ne[3]);
+11
View File
@@ -1549,6 +1549,9 @@ void llama_model_base::load_hparams(llama_model_loader & ml) {
hparams.n_cls_out = classifier_labels.size();
}
// for decision models
ml.get_arr(LLM_KV_DECISION_LABELS, decision_labels, false);
// per-arch hparams
load_arch_hparams(ml);
@@ -3438,6 +3441,14 @@ const char * llama_model_cls_label(const struct llama_model * model, uint32_t i)
return nullptr;
}
const char * llama_model_decision_label(const struct llama_model * model, uint32_t i) {
if (i < model->decision_labels.size()) {
return model->decision_labels[i].c_str();
}
return nullptr;
}
// deprecated
int32_t llama_n_ctx_train(const llama_model * model) {
return llama_model_n_ctx_train(model);
+1
View File
@@ -724,6 +724,7 @@ struct llama_model {
// for classifier models
std::vector<std::string> classifier_labels;
std::vector<std::string> decision_labels;
struct ggml_tensor * tok_embd = nullptr;
struct ggml_tensor * type_embd = nullptr;
+18 -14
View File
@@ -2260,21 +2260,22 @@ void llama_vocab::impl::load(llama_model_loader & ml, const LLM_KV & kv) {
throw std::runtime_error(format("invalid gguf type for %s", kv(LLM_KV_TOKENIZER_MERGES).c_str()));
}
const int n_merges = gguf_get_arr_n(ctx, merges_keyidx);
bpe_ranks.reserve(n_merges);
for (int i = 0; i < n_merges; i++) {
const std::string word = gguf_get_arr_str(ctx, merges_keyidx, i);
const char * word = gguf_get_arr_str(ctx, merges_keyidx, i);
//GGML_ASSERT(unicode_cpts_from_utf8(word).size() > 0);
std::string first;
std::string second;
const size_t pos = word.find(' ', 1);
const char * sep = *word ? strchr(word + 1, ' ') : nullptr;
if (pos != std::string::npos) {
first = word.substr(0, pos);
second = word.substr(pos + 1);
if (sep) {
first.assign(word, sep - word);
second.assign(sep + 1);
}
bpe_ranks.emplace(std::make_pair(first, second), i);
bpe_ranks.emplace(std::make_pair(std::move(first), std::move(second)), i);
}
}
@@ -2371,20 +2372,21 @@ void llama_vocab::impl::load(llama_model_loader & ml, const LLM_KV & kv) {
throw std::runtime_error(format("invalid gguf type for %s", kv(LLM_KV_TOKENIZER_MERGES).c_str()));
}
const int n_merges = gguf_get_arr_n(ctx, merges_keyidx);
bpe_ranks.reserve(n_merges);
for (int i = 0; i < n_merges; i++) {
const std::string word = gguf_get_arr_str(ctx, merges_keyidx, i);
const char * word = gguf_get_arr_str(ctx, merges_keyidx, i);
std::string first;
std::string second;
const size_t pos = word.find(' ', 1);
const char * sep = *word ? strchr(word + 1, ' ') : nullptr;
if (pos != std::string::npos) {
first = word.substr(0, pos);
second = word.substr(pos + 1);
if (sep) {
first.assign(word, sep - word);
second.assign(sep + 1);
}
bpe_ranks.emplace(std::make_pair(first, second), i);
bpe_ranks.emplace(std::make_pair(std::move(first), std::move(second)), i);
}
}
@@ -2764,6 +2766,7 @@ void llama_vocab::impl::load(llama_model_loader & ml, const LLM_KV & kv) {
}
id_to_token.resize(n_tokens);
token_to_id.reserve(n_tokens);
for (uint32_t i = 0; i < n_tokens; i++) {
std::string word = gguf_get_arr_str(ctx, token_idx, i);
@@ -3676,13 +3679,14 @@ static void llama_unescape_whitespace(std::string & word) {
static std::string llama_decode_text(const std::string & text) {
std::string decoded_text;
decoded_text.reserve(text.size());
const auto cpts = unicode_cpts_from_utf8(text);
for (const auto cpt : cpts) {
const auto utf8 = unicode_cpt_to_utf8(cpt);
try {
decoded_text += unicode_utf8_to_byte(utf8);
decoded_text += unicode_cpt_to_byte(cpt);
} catch (const std::out_of_range & /*e*/) {
const auto utf8 = unicode_cpt_to_utf8(cpt);
decoded_text += "[UNK_BYTE_0x";
for (const auto c : utf8) {
decoded_text += format("%02x", (uint8_t) c);
+114
View File
@@ -0,0 +1,114 @@
#include "models.h"
#include "llama-memory-recurrent.h"
#include <algorithm>
llm_build_lfm_base::llm_build_lfm_base(const llm_graph_params & params) : llm_graph_context(params) {}
ggml_tensor * llm_build_lfm_base::build_lfm_shortconv(const llama_model & model,
const llama_memory_recurrent_context * mctx_recr,
llm_graph_input_rs * inp_recr,
ggml_tensor * cur,
int il) {
const uint32_t kv_head = mctx_recr->get_head();
const int64_t n_seq_tokens = ubatch.n_seq_tokens;
const int64_t n_seqs = ubatch.n_seqs;
GGML_ASSERT(n_seqs != 0);
GGML_ASSERT(ubatch.equal_seqs());
GGML_ASSERT(ubatch.n_tokens == n_seq_tokens * n_seqs);
GGML_ASSERT(hparams.n_shortconv_l_cache > 1);
const uint32_t d_conv = hparams.n_shortconv_l_cache - 1;
const int64_t n_embd = hparams.n_embd;
// {n_embd, n_tokens} => {n_embd, n_seq_tokens, n_seqs}
cur = ggml_reshape_3d(ctx0, cur, cur->ne[0], n_seq_tokens, n_seqs);
auto * bcx = build_lora_mm(model.layers[il].shortconv.in_proj, cur);
cb(bcx, "model.layers.{}.conv.in_proj", il);
constexpr auto n_chunks = 3;
GGML_ASSERT(bcx->ne[0] % n_chunks == 0);
const auto chunk_size = bcx->ne[0] / n_chunks;
auto * b = ggml_view_3d(ctx0, bcx, chunk_size, bcx->ne[1], bcx->ne[2], bcx->nb[1], bcx->nb[2],
0 * chunk_size * ggml_element_size(bcx));
auto * c = ggml_view_3d(ctx0, bcx, chunk_size, bcx->ne[1], bcx->ne[2], bcx->nb[1], bcx->nb[2],
1 * chunk_size * ggml_element_size(bcx));
auto * x = ggml_view_3d(ctx0, bcx, chunk_size, bcx->ne[1], bcx->ne[2], bcx->nb[1], bcx->nb[2],
2 * chunk_size * ggml_element_size(bcx));
auto * bx = ggml_transpose(ctx0, ggml_mul(ctx0, b, x));
// read conv state
auto * conv_state = mctx_recr->get_r_l(il);
auto * conv_rs = build_rs(inp_recr, conv_state, hparams.n_embd_r(), n_seqs);
auto * conv = ggml_reshape_3d(ctx0, conv_rs, d_conv, n_embd, n_seqs);
// causal prepends the state, non-causal pads symmetrically for a centered window
if (hparams.causal_attn) {
bx = ggml_concat(ctx0, conv, bx, 0);
} else {
const int64_t pad = (hparams.n_shortconv_l_cache - 1) / 2;
auto * left = ggml_cont(ctx0,
ggml_view_3d(ctx0, conv, pad, n_embd, n_seqs, conv->nb[1], conv->nb[2], (d_conv - pad) * conv->nb[0]));
bx = ggml_pad_ext(ctx0, ggml_concat(ctx0, left, bx, 0), 0, pad, 0, 0, 0, 0, 0, 0);
}
GGML_ASSERT(bx->ne[0] > conv->ne[0]);
// write conv states: slot 0 = the final state, slot s = the state s tokens back (partial rollback)
const int64_t K = hparams.causal_attn && cparams.n_rs_seq > 0 ? (int64_t) cparams.n_rs_seq + 1 : 1;
const int64_t n_written = std::min<int64_t>(n_seq_tokens, K);
const auto mem_size = mctx_recr->get_size();
const size_t row_size = ggml_row_size(conv_state->type, (int64_t) d_conv * n_embd);
for (int64_t slot = 0; slot < n_written; ++slot) {
auto * conv_snap = ggml_view_3d(ctx0, bx, d_conv, bx->ne[1], bx->ne[2], bx->nb[1], bx->nb[2],
(bx->ne[0] - d_conv - slot) * ggml_element_size(bx));
ggml_build_forward_expand(gf, ggml_cpy(ctx0, conv_snap,
ggml_view_2d(ctx0, conv_state, (int64_t) d_conv * n_embd, n_seqs,
conv_state->nb[1],
((size_t) slot * mem_size + kv_head) * row_size)));
}
auto * conv_kernel = model.layers[il].shortconv.conv;
auto * conv_out = ggml_ssm_conv(ctx0, bx, conv_kernel);
cb(conv_out, "model.layers.{}.conv.conv", il);
auto * y = ggml_mul(ctx0, c, conv_out);
y = build_lora_mm(model.layers[il].shortconv.out_proj, y);
cb(y, "model.layers.{}.conv.out_proj", il);
// {n_embd, n_seq_tokens, n_seqs} => {n_embd, n_tokens}
y = ggml_reshape_2d(ctx0, y, y->ne[0], n_seq_tokens * n_seqs);
return y;
}
ggml_tensor * llm_build_lfm_base::build_lfm_dense_ffn(const llama_model & model,
ggml_tensor * cur,
int il) {
GGML_ASSERT(!model.layers[il].ffn_up_b);
GGML_ASSERT(!model.layers[il].ffn_gate_b);
GGML_ASSERT(!model.layers[il].ffn_down_b);
return build_ffn(cur,
model.layers[il].ffn_up, NULL, NULL,
model.layers[il].ffn_gate, NULL, NULL,
model.layers[il].ffn_down, NULL, NULL,
NULL, LLM_FFN_SILU, LLM_FFN_PAR, il);
}
ggml_tensor * llm_build_lfm_base::build_lfm_moe_ffn(const llama_model & model,
ggml_tensor * cur,
int il) {
return build_moe_ffn(cur,
model.layers[il].ffn_gate_inp,
model.layers[il].ffn_up_exps,
model.layers[il].ffn_gate_exps,
model.layers[il].ffn_down_exps,
model.layers[il].ffn_exp_probs_b,
n_expert, n_expert_used,
LLM_FFN_SILU, true,
hparams.expert_weights_scale,
static_cast<llama_expert_gating_func_type>(hparams.expert_gating_func),
il);
}
+8 -104
View File
@@ -147,35 +147,11 @@ std::unique_ptr<llm_graph_context> llama_model_lfm2::build_arch_graph(const llm_
template <bool iswa>
llama_model_lfm2::graph<iswa>::graph(const llama_model & model, const llm_graph_params & params) :
llm_graph_context(params) {
llm_build_lfm_base(params) {
using inp_hybrid_type = std::conditional_t<iswa, llm_graph_input_mem_hybrid_iswa, llm_graph_input_mem_hybrid>;
using inp_attn_type = std::conditional_t<iswa, llm_graph_input_attn_kv_iswa, llm_graph_input_attn_kv>;
using mem_hybrid_ctx = std::conditional_t<iswa, llama_memory_hybrid_iswa_context, llama_memory_hybrid_context>;
// lambda helpers for readability
auto build_dense_feed_forward = [&model, this](ggml_tensor * cur, int il) -> ggml_tensor * {
GGML_ASSERT(!model.layers[il].ffn_up_b);
GGML_ASSERT(!model.layers[il].ffn_gate_b);
GGML_ASSERT(!model.layers[il].ffn_down_b);
return build_ffn(cur,
model.layers[il].ffn_up, NULL, NULL,
model.layers[il].ffn_gate, NULL, NULL,
model.layers[il].ffn_down, NULL, NULL,
NULL, LLM_FFN_SILU, LLM_FFN_PAR, il);
};
auto build_moe_feed_forward = [&model, this](ggml_tensor * cur, int il) -> ggml_tensor * {
return build_moe_ffn(cur,
model.layers[il].ffn_gate_inp,
model.layers[il].ffn_up_exps,
model.layers[il].ffn_gate_exps,
model.layers[il].ffn_down_exps,
model.layers[il].ffn_exp_probs_b,
n_expert, n_expert_used,
LLM_FFN_SILU, true,
hparams.expert_weights_scale,
static_cast<llama_expert_gating_func_type>(hparams.expert_gating_func),
il);
};
auto build_attn_block = [&model, this](ggml_tensor * cur,
ggml_tensor * inp_pos,
inp_attn_type * inp_attn,
@@ -207,82 +183,6 @@ llama_model_lfm2::graph<iswa>::graph(const llama_model & model, const llm_graph_
return cur;
};
auto build_shortconv_block = [&model, this](ggml_tensor * cur,
llm_graph_input_rs * inp_recr,
int il) -> ggml_tensor * {
const auto * mctx_cur = static_cast<const mem_hybrid_ctx *>(mctx)->get_recr();
const uint32_t kv_head = mctx_cur->get_head();
const int64_t n_seq_tokens = ubatch.n_seq_tokens;
const int64_t n_seqs = ubatch.n_seqs;
GGML_ASSERT(n_seqs != 0);
GGML_ASSERT(ubatch.equal_seqs());
GGML_ASSERT(ubatch.n_tokens == n_seq_tokens * n_seqs);
GGML_ASSERT(hparams.n_shortconv_l_cache > 1);
const uint32_t d_conv = hparams.n_shortconv_l_cache - 1;
// {n_embd, n_tokens} => {n_embd, n_seq_tokens, n_seqs}
cur = ggml_reshape_3d(ctx0, cur, cur->ne[0], n_seq_tokens, n_seqs);
auto * bcx = build_lora_mm(model.layers[il].shortconv.in_proj, cur);
cb(bcx, "model.layers.{}.conv.in_proj", il);
constexpr auto n_chunks = 3;
GGML_ASSERT(bcx->ne[0] % n_chunks == 0);
const auto chunk_size = bcx->ne[0] / n_chunks;
auto * b = ggml_view_3d(ctx0, bcx, chunk_size, bcx->ne[1], bcx->ne[2], bcx->nb[1], bcx->nb[2],
0 * chunk_size * ggml_element_size(bcx));
auto * c = ggml_view_3d(ctx0, bcx, chunk_size, bcx->ne[1], bcx->ne[2], bcx->nb[1], bcx->nb[2],
1 * chunk_size * ggml_element_size(bcx));
auto * x = ggml_view_3d(ctx0, bcx, chunk_size, bcx->ne[1], bcx->ne[2], bcx->nb[1], bcx->nb[2],
2 * chunk_size * ggml_element_size(bcx));
auto * bx = ggml_transpose(ctx0, ggml_mul(ctx0, b, x));
// read conv state
auto * conv_state = mctx_cur->get_r_l(il);
auto * conv_rs = build_rs(inp_recr, conv_state, hparams.n_embd_r(), n_seqs);
auto * conv = ggml_reshape_3d(ctx0, conv_rs, d_conv, hparams.n_embd, n_seqs);
// causal prepends the state, non-causal pads symmetrically for a centered window
if (hparams.causal_attn) {
bx = ggml_concat(ctx0, conv, bx, 0);
} else {
const int64_t pad = (hparams.n_shortconv_l_cache - 1) / 2;
auto * left = ggml_cont(ctx0,
ggml_view_3d(ctx0, conv, pad, hparams.n_embd, n_seqs, conv->nb[1], conv->nb[2], (d_conv - pad) * conv->nb[0]));
bx = ggml_pad_ext(ctx0, ggml_concat(ctx0, left, bx, 0), 0, pad, 0, 0, 0, 0, 0, 0);
}
GGML_ASSERT(bx->ne[0] > conv->ne[0]);
// write conv states: slot 0 = the final state, slot s = the state s tokens back (partial rollback)
const int64_t K = hparams.causal_attn && cparams.n_rs_seq > 0 ? (int64_t) cparams.n_rs_seq + 1 : 1;
const int64_t n_written = std::min<int64_t>(n_seq_tokens, K);
const auto mem_size = mctx_cur->get_size();
const size_t row_size = ggml_row_size(conv_state->type, (int64_t) d_conv * n_embd);
for (int64_t slot = 0; slot < n_written; ++slot) {
auto * conv_snap = ggml_view_3d(ctx0, bx, d_conv, bx->ne[1], bx->ne[2], bx->nb[1], bx->nb[2],
(bx->ne[0] - d_conv - slot) * ggml_element_size(bx));
ggml_build_forward_expand(gf, ggml_cpy(ctx0, conv_snap,
ggml_view_2d(ctx0, conv_state, (int64_t) d_conv * n_embd, n_seqs,
conv_state->nb[1],
((size_t) slot * mem_size + kv_head) * row_size)));
}
auto * conv_kernel = model.layers[il].shortconv.conv;
auto * conv_out = ggml_ssm_conv(ctx0, bx, conv_kernel);
cb(conv_out, "model.layers.{}.conv.conv", il);
auto * y = ggml_mul(ctx0, c, conv_out);
y = build_lora_mm(model.layers[il].shortconv.out_proj, y);
cb(y, "model.layers.{}.conv.out_proj", il);
// {n_embd, n_seq_tokens, n_seqs} => {n_embd, n_tokens}
y = ggml_reshape_2d(ctx0, y, y->ne[0], n_seq_tokens * n_seqs);
return y;
};
// actual graph construction starts here
ggml_tensor * cur = build_inp_embd(model.tok_embd);
cb(cur, "model.embed_tokens", -1);
@@ -308,8 +208,12 @@ llama_model_lfm2::graph<iswa>::graph(const llama_model & model, const llm_graph_
cur = build_norm(cur, model.layers[il].attn_norm, NULL, LLM_NORM_RMS, il);
cb(cur, "model.layers.{}.operator_norm", il);
cur = hparams.is_recr(il) ? build_shortconv_block(cur, inp_hybrid->get_recr(), il) :
build_attn_block(cur, inp_pos, inp_hybrid->get_attn(), il);
if (hparams.is_recr(il)) {
const auto * mctx_recr = static_cast<const mem_hybrid_ctx *>(mctx)->get_recr();
cur = build_lfm_shortconv(model, mctx_recr, inp_hybrid->get_recr(), cur, il);
} else {
cur = build_attn_block(cur, inp_pos, inp_hybrid->get_attn(), il);
}
if (il == n_layer - 1 && inp_out_ids) {
cur = ggml_get_rows(ctx0, cur, inp_out_ids);
@@ -322,7 +226,7 @@ llama_model_lfm2::graph<iswa>::graph(const llama_model & model, const llm_graph_
cb(ffn_norm_out, "model.layers.{}.ffn_norm", il);
ggml_tensor * ffn_out =
is_moe_layer ? build_moe_feed_forward(ffn_norm_out, il) : build_dense_feed_forward(ffn_norm_out, il);
is_moe_layer ? build_lfm_moe_ffn(model, ffn_norm_out, il) : build_lfm_dense_ffn(model, ffn_norm_out, il);
cb(ffn_norm_out, "model.layers.{}.ffn_out", il);
cur = ggml_add(ctx0, cur, ffn_out);
+21 -1
View File
@@ -100,6 +100,26 @@ struct llm_build_delta_net_base : public llm_graph_context {
int il);
};
struct llm_build_lfm_base : public llm_graph_context {
llm_build_lfm_base(const llm_graph_params & params);
virtual ~llm_build_lfm_base() = default;
ggml_tensor * build_lfm_shortconv(const llama_model & model,
const llama_memory_recurrent_context * mctx_recr,
llm_graph_input_rs * inp_recr,
ggml_tensor * cur,
int il);
ggml_tensor * build_lfm_dense_ffn(const llama_model & model,
ggml_tensor * cur,
int il);
ggml_tensor * build_lfm_moe_ffn(const llama_model & model,
ggml_tensor * cur,
int il);
};
struct llm_build_rwkv6_base : public llm_graph_context {
const llama_model & model;
@@ -2264,7 +2284,7 @@ struct llama_model_lfm2 : public llama_model_base {
void load_arch_tensors(llama_model_loader & ml) override;
template <bool iswa>
struct graph : public llm_graph_context {
struct graph : public llm_build_lfm_base {
graph(const llama_model & model, const llm_graph_params & params);
};
+2 -5
View File
@@ -465,17 +465,14 @@ ggml_tensor * llama_model_qwen35::graph::build_layer_attn_linear(
// Apply gated normalization: self.norm(core_attn_out, z)
ggml_tensor * attn_out_norm = build_norm_gated(output, model.layers[il].ssm_norm, z_2d, il);
// Final reshape: [head_dim, n_heads, n_tokens, n_seqs] -> [n_tokens, n_seqs, n_heads * head_dim]
ggml_tensor * final_output = ggml_reshape_3d(ctx0, attn_out_norm, head_v_dim * num_v_heads, n_seq_tokens, n_seqs);
// Final reshape: [head_dim, n_heads, n_tokens, n_seqs] -> [n_heads * head_dim , n_tokens * n_seqs ]
ggml_tensor * final_output = ggml_reshape_2d(ctx0, attn_out_norm, head_v_dim * num_v_heads, n_seq_tokens * n_seqs);
cb(final_output, "final_output", il);
// Output projection
cur = build_lora_mm(model.layers[il].ssm_out, final_output, model.layers[il].ssm_out_s);
cb(cur, "linear_attn_out", il);
// Reshape back to original dimensions
cur = ggml_reshape_2d(ctx0, cur, n_embd, n_seq_tokens * n_seqs);
return cur;
}
+2 -6
View File
@@ -475,17 +475,13 @@ ggml_tensor * llama_model_qwen35moe::graph::build_layer_attn_linear(
// Apply gated normalization: self.norm(core_attn_out, z)
ggml_tensor * attn_out_norm = build_norm_gated(output, model.layers[il].ssm_norm, z_2d, il);
// Final reshape: [head_dim, n_heads, n_tokens, n_seqs] -> [n_tokens, n_seqs, n_heads * head_dim]
ggml_tensor * final_output = ggml_reshape_3d(ctx0, attn_out_norm, head_v_dim * num_v_heads, n_seq_tokens, n_seqs);
cb(final_output, "final_output", il);
// Final reshape: [head_dim, n_heads, n_tokens, n_seqs] -> [n_heads * head_dim , n_tokens * n_seqs ]
ggml_tensor * final_output = ggml_reshape_2d(ctx0, attn_out_norm, head_v_dim * num_v_heads, n_seq_tokens * n_seqs);
// Output projection
cur = build_lora_mm(model.layers[il].ssm_out, final_output, model.layers[il].ssm_out_s);
cb(cur, "linear_attn_out", il);
// Reshape back to original dimensions
cur = ggml_reshape_2d(ctx0, cur, n_embd, n_seq_tokens * n_seqs);
return cur;
}
+2 -5
View File
@@ -542,17 +542,14 @@ ggml_tensor * llama_model_qwen3next::graph::build_layer_attn_linear(
// Apply gated normalization: self.norm(core_attn_out, z)
ggml_tensor * attn_out_norm = build_norm_gated(output, model.layers[il].ssm_norm, z_2d, il);
// Final reshape: [head_dim, n_heads, n_tokens, n_seqs] -> [n_tokens, n_seqs, n_heads * head_dim]
ggml_tensor * final_output = ggml_reshape_3d(ctx0, attn_out_norm, head_v_dim * num_v_heads, n_seq_tokens, n_seqs);
// Final reshape: [head_dim, n_heads, n_tokens, n_seqs] -> [n_heads * head_dim , n_tokens * n_seqs ]
ggml_tensor * final_output = ggml_reshape_2d(ctx0, attn_out_norm, head_v_dim * num_v_heads, n_seq_tokens * n_seqs);
cb(final_output, "final_output", il);
// Output projection
cur = build_lora_mm(model.layers[il].ssm_out, final_output);
cb(cur, "linear_attn_out", il);
// Reshape back to original dimensions
cur = ggml_reshape_2d(ctx0, cur, n_embd, n_seq_tokens * n_seqs);
return cur;
}
+1 -3
View File
@@ -1131,14 +1131,12 @@ ggml_tensor * llama_model_qwen4exp::graph::build_layer_attn_linear(
// gated normalization, as self.norm(core_attn_out, z) in the reference
ggml_tensor * attn_out_norm = build_norm_gated(output, model.layers[il].ssm_norm, z_2d, il);
ggml_tensor * final_output = ggml_reshape_3d(ctx0, attn_out_norm, head_v_dim * num_v_heads, n_seq_tokens, n_seqs);
ggml_tensor * final_output = ggml_reshape_2d(ctx0, attn_out_norm, head_v_dim * num_v_heads, n_seq_tokens * n_seqs);
cb(final_output, "final_output", il);
cur = build_lora_mm(model.layers[il].ssm_out, final_output, model.layers[il].ssm_out_s);
cb(cur, "linear_attn_out", il);
cur = ggml_reshape_2d(ctx0, cur, n_embd, n_seq_tokens * n_seqs);
return cur;
}
+21
View File
@@ -202,6 +202,19 @@ static std::unordered_map<std::string, uint8_t> unicode_utf8_to_byte_map() {
return map;
}
static std::vector<int> unicode_cpt_to_byte_map() {
std::vector<int> map;
for (int ch = 0; ch < 256; ++ch) {
size_t offset = 0;
const uint32_t cpt = unicode_cpt_from_utf8(unicode_byte_to_utf8(ch), offset);
if (cpt >= map.size()) {
map.resize(cpt + 1, -1);
}
map[cpt] = ch;
}
return map;
}
static std::vector<std::string> unicode_byte_encoding_process(const std::vector<std::string> & bpe_words) {
std::vector<std::string> bpe_encoded_words;
for (const auto & word : bpe_words) {
@@ -1416,6 +1429,14 @@ uint8_t unicode_utf8_to_byte(const std::string & utf8) {
return map.at(utf8);
}
uint8_t unicode_cpt_to_byte(uint32_t cpt) {
static const std::vector<int> map = unicode_cpt_to_byte_map();
if (cpt >= map.size() || map[cpt] < 0) {
throw std::out_of_range("unicode_cpt_to_byte: not a byte token code point");
}
return map[cpt];
}
uint32_t unicode_tolower(uint32_t cpt) {
// binary search
auto it = std::lower_bound(unicode_map_lowercase.begin(), unicode_map_lowercase.end(), cpt,
+1
View File
@@ -104,6 +104,7 @@ unicode_cpt_flags unicode_cpt_flags_from_utf8(const std::string & utf8);
std::string unicode_byte_to_utf8(uint8_t byte);
uint8_t unicode_utf8_to_byte(const std::string & utf8);
uint8_t unicode_cpt_to_byte (uint32_t cpt);
uint32_t unicode_tolower(uint32_t cpt);
+1
View File
@@ -332,6 +332,7 @@ if (NOT GGML_BACKEND_DL AND GGML_CPU)
llama_build_and_test(test-rope.cpp LINK ggml)
llama_build_and_test(test-col2im-1d.cpp LINK ggml)
llama_build_and_test(test-conv-1d.cpp LINK ggml)
llama_build_and_test(test-gelu-quick.cpp LINK ggml)
endif()
# libmtmd
+79 -4
View File
@@ -2069,6 +2069,15 @@ struct test_unary : public test_case {
return VARS_TO_STR3(type, ne_a, v);
}
double max_nmse_err(ggml_backend_t backend) override {
// Metal computes f32 gelu_quick in f32, so an fp16 table in the CPU reference (NMSE about 5e-8) fails this
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(ggml_backend_get_device(backend));
if (op == GGML_UNARY_OP_GELU_QUICK && type == GGML_TYPE_F32 && strcmp(ggml_backend_reg_name(reg), "MTL") == 0) {
return 1e-10;
}
return test_case::max_nmse_err(backend);
}
test_unary(ggml_unary_op op,
ggml_type type = GGML_TYPE_F32,
std::array<int64_t, 4> ne_a = {128, 2, 2, 2},
@@ -2157,6 +2166,15 @@ struct test_glu : public test_case {
return VARS_TO_STR4(type, ne_a, v, swapped);
}
double max_nmse_err(ggml_backend_t backend) override {
// Metal computes f32 geglu_quick in f32, so an fp16 table in the CPU reference (NMSE about 5e-8) fails this
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(ggml_backend_get_device(backend));
if (op == GGML_GLU_OP_GEGLU_QUICK && type == GGML_TYPE_F32 && strcmp(ggml_backend_reg_name(reg), "MTL") == 0) {
return 1e-10;
}
return test_case::max_nmse_err(backend);
}
test_glu(ggml_glu_op op,
ggml_type type = GGML_TYPE_F32,
std::array<int64_t, 4> ne_a = {128, 2, 2, 2},
@@ -2202,6 +2220,15 @@ struct test_glu_split : public test_case {
return VARS_TO_STR3(type, ne_a, v) + ",split";
}
double max_nmse_err(ggml_backend_t backend) override {
// Metal computes f32 geglu_quick in f32, so an fp16 table in the CPU reference (NMSE about 5e-8) fails this
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(ggml_backend_get_device(backend));
if (op == GGML_GLU_OP_GEGLU_QUICK && type == GGML_TYPE_F32 && strcmp(ggml_backend_reg_name(reg), "MTL") == 0) {
return 1e-10;
}
return test_case::max_nmse_err(backend);
}
test_glu_split(ggml_glu_op op,
ggml_type type = GGML_TYPE_F32,
std::array<int64_t, 4> ne_a = {128, 2, 2, 2},
@@ -2454,7 +2481,9 @@ struct test_get_rows : public test_case {
};
// GGML_OP_GET_ROWS on a weight also used by GGML_OP_MUL_MAT (tied embeddings)
// the mul_mat runs first, so a backend that changes the weight layout must still return the right rows
// that the preceding mul_mat may have put in the SYCL reorder layout. The
// mul_mat runs first, so a backend that changes the weight layout in place
// must still return the right rows from get_rows.
struct test_get_rows_after_mul_mat : public test_case {
const ggml_type type;
const int n; // cols
@@ -2510,6 +2539,48 @@ struct test_get_rows_after_mul_mat : public test_case {
}
};
// The persistent SYCL weight reorder installs on the first small-batch mul_mat; a
// later >8-column mul_mat on the same weight goes through the fused-dequant GEMM /
// conversion routes, which must decode the reordered layout (regression test for the
// #29107 crash: the plain fused-dequant GEMM decoded standard layout unconditionally).
struct test_mul_mat_reorder_then_big_batch : public test_case {
const ggml_type type;
const int n; // cols of the big batch
const int m; // rows
std::string vars() override {
return VARS_TO_STR3(type, n, m);
}
test_mul_mat_reorder_then_big_batch(ggml_type type = GGML_TYPE_IQ3_S, int n = 64, int m = 64)
: type(type), n(n), m(m) {}
double max_nmse_err() override {
return 5e-4; // same as test_mul_mat
}
ggml_tensor * build_graph(ggml_context * ctx) override {
ggml_tensor * w = ggml_new_tensor_2d(ctx, type, 1*256, m);
ggml_set_name(w, "w");
ggml_tensor * x1 = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1*256, 1);
ggml_set_name(x1, "x1");
ggml_tensor * x2 = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1*256, n);
ggml_set_name(x2, "x2");
ggml_tensor * mm1 = ggml_mul_mat(ctx, w, x1);
ggml_set_name(mm1, "mm1");
ggml_tensor * mm2 = ggml_mul_mat(ctx, w, x2);
ggml_set_name(mm2, "mm2");
// mm1 is the first source of the final ADD, so the reorder-installing
// small-batch mul_mat is computed before the big-batch one
return ggml_add(ctx, ggml_repeat(ctx, mm1, mm2), mm2);
}
};
// GGML_OP_GET_ROWS_BACK
struct test_get_rows_back : public test_case {
const ggml_type type;
@@ -9885,9 +9956,13 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
}
test_cases.emplace_back(new test_get_rows(GGML_TYPE_F32, 1, 8, 2, 1, 1, false));
for (ggml_type type : {GGML_TYPE_Q4_0, GGML_TYPE_Q8_0, GGML_TYPE_Q2_K, GGML_TYPE_Q3_K, GGML_TYPE_Q4_K, GGML_TYPE_Q5_K, GGML_TYPE_Q6_K}) {
for (ggml_type type : {GGML_TYPE_Q4_0, GGML_TYPE_Q8_0, GGML_TYPE_Q2_K, GGML_TYPE_Q3_K, GGML_TYPE_Q4_K, GGML_TYPE_Q5_K, GGML_TYPE_Q6_K,
GGML_TYPE_IQ3_XXS, GGML_TYPE_IQ3_S}) {
test_cases.emplace_back(new test_get_rows_after_mul_mat(type));
}
for (ggml_type type : {GGML_TYPE_IQ3_XXS, GGML_TYPE_IQ3_S}) {
test_cases.emplace_back(new test_mul_mat_reorder_then_big_batch(type));
}
for (ggml_type type : all_types) {
for (int b : {1, 7}) {
for (bool v : {false, true}) {
@@ -10659,7 +10734,7 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
// ne[2] > 65535 exceeds the CUDA gridDim.y limit (#27901)
test_cases.emplace_back(new test_rms_norm_mul_rope({4, 1, 65536, 1}, 1e-6f, false, false, false, GGML_ROPE_TYPE_NORMAL));
test_cases.emplace_back(new test_rms_norm_mul_rope({4, 1, 65536, 1}, 1e-6f, false, true, false, GGML_ROPE_TYPE_NEOX));
for (int64_t d_conv : {3, 4, 9}) {
for (int64_t d_conv : {3, 4, 7, 9}) {
for (int64_t d_inner: {1024, 1536, 2048}) {
test_cases.emplace_back(new test_ssm_conv(GGML_TYPE_F32, {d_conv, d_inner, 1, 1}, {d_conv, d_inner, 1, 1}));
test_cases.emplace_back(new test_ssm_conv(GGML_TYPE_F32, {2 * d_conv, d_inner, 1, 1}, {d_conv, d_inner, 1, 1}));
@@ -10672,7 +10747,7 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
// fused ssm_conv + (optional) bias_add + silu. The bias-only graph (no silu) is intentionally
// not tested since there's no fusion for that pattern in ggml_cuda_can_fuse.
for (int64_t d_conv : {3, 4, 9}) {
for (int64_t d_conv : {3, 4, 7, 9}) {
for (int64_t d_inner : {1024, 1536, 2048}) {
for (bool fuse_bias : {false, true}) {
// short token path (n_t <= 32)
+131
View File
@@ -0,0 +1,131 @@
// test-gelu-quick.cpp: validate the F32 gelu_quick and geglu_quick CPU kernels against the exact formula.
//
// test-backend-ops uses the CPU backend as its reference, so it cannot see an error in the CPU kernels
// themselves. This test computes gelu_quick(x) = x / (1 + exp(-1.702 x)) in double precision and compares
// every F32 output of the CPU backend with it: dense ranges, values outside the F16 range, values near
// the float limits, and lengths that leave a tail after the vector loop.
#include "ggml.h"
#include "ggml-cpu.h"
#include <cfloat>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>
// Same coefficient as the CPU kernels
static const double GELU_QUICK_COEF_REF = (double) 1.702f;
static double gelu_quick_ref(double x) {
return x / (1.0 + std::exp(-GELU_QUICK_COEF_REF * x));
}
// Deterministic LCG mapped to [-1, 1]
static uint64_t g_rng = 0x12345678ULL;
static float frand(void) {
g_rng = g_rng * 6364136223846793005ULL + 1442695040888963407ULL;
return (float)((g_rng >> 33) & 0xffffff) / (float)0x800000 - 1.0f;
}
static std::vector<float> make_inputs(void) {
std::vector<float> x;
for (int i = 0; i <= 200000; i++) {
x.push_back(-10.0f + 20.0f * (float) i / 200000.0f);
}
for (int i = 0; i <= 2000; i++) {
x.push_back(-150.0f + 300.0f * (float) i / 2000.0f);
}
// outside the F16 range, near the float limits, and subnormal; both signs
const float edge[] = { 65504.0f, 65520.0f, 1e5f, 1e10f, 1e30f, FLT_MAX, 1e-30f, FLT_MIN, FLT_MIN / 16, 0.0f };
for (float v : edge) {
x.push_back(v);
x.push_back(-v);
}
return x;
}
static bool matches(float y, double ref) {
if (std::isnan(ref)) {
return std::isnan(y);
}
if (std::isinf(ref)) {
return (double) y == ref;
}
return std::fabs((double) y - ref) <= 1e-6 + 1e-6 * std::fabs(ref);
}
// Runs gelu_quick and geglu_quick on x[0, n) and returns the number of outputs that differ from the reference
static int check(const float * x, const float * g, int64_t n) {
struct ggml_init_params params = {
/* .mem_size = */ (size_t) 64 << 20,
/* .mem_base = */ NULL,
/* .no_alloc = */ false,
};
struct ggml_context * ctx = ggml_init(params);
struct ggml_tensor * a = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, n);
struct ggml_tensor * b = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, n);
memcpy(a->data, x, n * sizeof(float));
memcpy(b->data, g, n * sizeof(float));
struct ggml_tensor * y_gelu = ggml_gelu_quick(ctx, a);
struct ggml_tensor * y_geglu = ggml_geglu_quick_split(ctx, a, b);
struct ggml_cgraph * gf = ggml_new_graph(ctx);
ggml_build_forward_expand(gf, y_gelu);
ggml_build_forward_expand(gf, y_geglu);
ggml_graph_compute_with_ctx(ctx, gf, 1);
int bad = 0;
double max_err = 0.0;
for (int64_t i = 0; i < n; i++) {
const double r0 = gelu_quick_ref(x[i]);
const double r1 = r0 * g[i];
const float y0 = ((const float *) y_gelu->data)[i];
const float y1 = ((const float *) y_geglu->data)[i];
if (std::isfinite(r0) && std::isfinite(y0)) {
max_err = std::fmax(max_err, std::fabs((double) y0 - r0) / (1.0 + std::fabs(r0)));
}
if (!matches(y0, r0)) {
if (bad < 3) {
printf(" gelu_quick(%g) = %g, expected %g\n", x[i], y0, r0);
}
bad++;
}
if (!matches(y1, r1)) {
if (bad < 3) {
printf(" geglu_quick(%g, %g) = %g, expected %g\n", x[i], g[i], y1, r1);
}
bad++;
}
}
printf("gelu_quick n=%6d: max err %.2e, %d mismatches %s\n", (int) n, max_err, bad, bad == 0 ? "OK" : "FAIL");
ggml_free(ctx);
return bad;
}
int main(void) {
const std::vector<float> x = make_inputs();
std::vector<float> g(x.size());
for (float & v : g) {
v = frand();
}
int fails = 0;
// every input in one call
fails += check(x.data(), g.data(), (int64_t) x.size()) != 0;
// short lengths end on the edge values and leave a tail after the vector loop
const int64_t lengths[] = { 1, 2, 3, 5, 7, 8, 9, 15, 16, 17, 23, 31, 33 };
for (int64_t n : lengths) {
const int64_t off = (int64_t) x.size() - n;
fails += check(x.data() + off, g.data() + off, n) != 0;
}
printf(fails == 0 ? "all gelu_quick checks passed\n" : "%d gelu_quick checks FAILED\n", fails);
return fails == 0 ? 0 : 1;
}
+30
View File
@@ -91,6 +91,10 @@ MAKE_TEST(test_image_preprocessor_dyn_size) {
}
}
static std::string image_size_to_str(const clip_image_size & size) {
return std::to_string(size.width) + "x" + std::to_string(size.height);
}
MAKE_TEST(test_image_preprocessor_lfm2) {
clip_hparams hparams;
hparams.patch_size = 16;
@@ -121,6 +125,32 @@ MAKE_TEST(test_image_preprocessor_lfm2) {
std::string(actual ? "tiled" : "single"));
}
// { image size, expected size from HF smart_resize }
const std::vector<std::pair<clip_image_size, clip_image_size>> resize_cases = {
{ { 400, 600 }, { 384, 608 } }, // 400 / 32 = 12.5 rounds half to even
{ { 480, 336 }, { 480, 320 } },
{ { 64, 169 }, { 160, 416 } }, // 169 * beta / 32 is exactly 13 in double
{ { 1024, 768 }, { 576, 416 } },
};
for (const auto & [size, expected] : resize_cases) {
t.assert_equal("smart_resize for " + image_size_to_str(size), image_size_to_str(expected),
image_size_to_str(mtmd_image_preprocessor_lfm2::smart_resize(hparams, size)));
}
// { image size, expected grid from HF }
const std::vector<std::pair<clip_image_size, clip_image_size>> grid_cases = {
{ { 720, 1600 }, { 2, 4 } }, // 0.45 is an exact tie between 2/4 and 2/5 in double, not in float
{ { 420, 1440 }, { 1, 3 } },
{ { 255, 2160 }, { 1, 8 } },
{ { 1024, 768 }, { 3, 2 } },
};
for (const auto & [size, expected] : grid_cases) {
t.assert_equal("grid for " + image_size_to_str(size), image_size_to_str(expected),
image_size_to_str(mtmd_image_preprocessor_lfm2::get_grid_layout(hparams, size.height, size.width)));
}
// do_image_splitting=false
hparams.preproc_min_tiles = 1;
hparams.preproc_max_tiles = 1;
+1
View File
@@ -66,6 +66,7 @@ struct clip_hparams {
resize_algo image_resize_algo = RESIZE_ALGO_BICUBIC;
pad_style image_resize_pad = PAD_CEIL; // padding style when resizing
bool image_round_half_even = false; // smart_resize rounds .5 to even, like Python round() in the HF processor
bool image_floor_before_align = false;
std::array<uint8_t, 3> image_pad_color = {0, 0, 0};
// (preprocessor) for llava-uhd style models
+24 -7
View File
@@ -1593,10 +1593,8 @@ struct clip_model_loader {
}
hparams.image_resize_algo_rf = hparams.image_resize_algo;
hparams.image_resize_algo_ov = hparams.image_resize_algo;
// the tiles stretch the image to the grid (d1-omni vision.py)
if (model.proj_type == PROJECTOR_TYPE_D1OMNI_V) {
hparams.image_pad_rf = PAD_NONE;
}
// the tiles stretch the image to the grid (Lfm2VlImageProcessor, d1-omni vision.py)
hparams.image_pad_rf = PAD_NONE;
get_u32(KEY_PROJ_SCALE_FACTOR, hparams.n_merge, false);
// defaults for GGUFs converted before tiling params were written
// ref: https://huggingface.co/LiquidAI/LFM2.5-VL-1.6B/blob/main/processor_config.json
@@ -1627,7 +1625,23 @@ struct clip_model_loader {
hparams.image_resize_algo = RESIZE_ALGO_BICUBIC;
hparams.rope_theta = 10000.0f;
get_u32(KEY_SPATIAL_MERGE_SIZE, hparams.n_merge, false);
hparams.set_limit_image_tokens(8, 1024);
get_u32(KEY_PREPROC_IMAGE_SIZE, hparams.image_longest_edge, false);
if (hparams.image_longest_edge > 0) {
const int64_t patch_size = int64_t(hparams.patch_size) * hparams.n_merge;
if (patch_size <= 0) {
throw std::runtime_error("Pixtral merged patch size must be positive");
}
const int64_t n_tokens_per_side =
(hparams.image_longest_edge + patch_size - 1) / patch_size;
if (n_tokens_per_side * patch_size > std::sqrt(std::numeric_limits<int>::max())) {
throw std::runtime_error("Pixtral preprocessing image size is too large");
}
hparams.image_resize_pad = PAD_NONE;
hparams.image_floor_before_align = true;
hparams.set_limit_image_tokens(0, n_tokens_per_side * n_tokens_per_side);
} else {
hparams.set_limit_image_tokens(8, 1024);
}
hparams.set_warmup_n_tokens(256); // avoid OOM on warmup
} break;
case PROJECTOR_TYPE_LIGHTONOCR:
@@ -1735,6 +1749,8 @@ struct clip_model_loader {
case PROJECTOR_TYPE_GEMMA4UV:
{
hparams.rope_theta = 100.0f;
hparams.ffn_op = FFN_GELU;
log_ffn_op = "gelu";
hparams.n_merge = 3; // pooling_kernel_size
hparams.image_resize_algo = RESIZE_ALGO_BICUBIC;
get_u32(KEY_PROJ_SCALE_FACTOR, hparams.n_merge, false);
@@ -4262,8 +4278,9 @@ struct clip_init_result clip_init(const char * fname, struct clip_context_params
struct clip_cap clip_get_cap(const char * fname) {
clip_cap res;
clip_model_loader loader(fname, /* skip_tensors= */ true);
res.has_vision = loader.has_vision;
res.has_audio = loader.has_audio;
res.has_vision = loader.has_vision;
res.has_audio = loader.has_audio;
res.has_gen_audio = loader.has_gen_audio;
return res;
}
+1
View File
@@ -148,5 +148,6 @@ std::map<ggml_backend_dev_t, size_t> clip_get_mem_usage(const struct clip_ctx *
struct clip_cap {
bool has_vision;
bool has_audio;
bool has_gen_audio;
};
struct clip_cap clip_get_cap(const char * fname);
+138 -39
View File
@@ -50,29 +50,38 @@ static llama_token find_special_token(const llama_vocab * vocab, const std::stri
return LLAMA_TOKEN_NULL;
}
static void put_bytes(std::vector<char> & buf, const void * p, size_t n) {
const char * c = (const char *) p;
buf.insert(buf.end(), c, c + n);
}
// data_sz == UINT32_MAX writes the "unknown length" sentinel (streaming), same as ffmpeg does on a pipe
static void write_wav16_header(std::vector<char> & buf, uint32_t data_sz, int32_t rate) {
const uint32_t riff_sz = data_sz == UINT32_MAX ? UINT32_MAX : 36 + data_sz;
const uint32_t fmt_sz = 16, byte_rate = (uint32_t) rate * 2;
const uint16_t fmt = 1, ch = 1, align = 2, bits = 16;
const uint32_t rate32 = (uint32_t) rate;
put_bytes(buf, "RIFF", 4); put_bytes(buf, &riff_sz, 4); put_bytes(buf, "WAVE", 4);
put_bytes(buf, "fmt ", 4); put_bytes(buf, &fmt_sz, 4);
put_bytes(buf, &fmt, 2); put_bytes(buf, &ch, 2); put_bytes(buf, &rate32, 4);
put_bytes(buf, &byte_rate, 4); put_bytes(buf, &align, 2); put_bytes(buf, &bits, 2);
put_bytes(buf, "data", 4); put_bytes(buf, &data_sz, 4);
}
static void append_wav16_pcm(std::vector<char> & buf, const float * pcm, size_t n) {
for (size_t i = 0; i < n; i++) {
int16_t s = (int16_t) (std::max(-1.0f, std::min(1.0f, pcm[i])) * 32767.0f);
put_bytes(buf, &s, 2);
}
}
static bool write_wav16(std::vector<char> & buf, const std::vector<float> & pcm, int32_t rate) {
// RIFF chunk sizes are 32-bit; refuse to emit a file with a truncated header
if (pcm.size() > ((size_t) UINT32_MAX - 36) / 2) {
return false;
}
const uint32_t data_sz = (uint32_t) (pcm.size() * 2);
const uint32_t riff_sz = 36 + data_sz;
const uint32_t fmt_sz = 16, byte_rate = (uint32_t) rate * 2;
const uint16_t fmt = 1, ch = 1, align = 2, bits = 16;
const uint32_t rate32 = (uint32_t) rate;
auto put = [&](const void * p, size_t n) {
const char * c = (const char *) p;
buf.insert(buf.end(), c, c + n);
};
put("RIFF", 4); put(&riff_sz, 4); put("WAVE", 4);
put("fmt ", 4); put(&fmt_sz, 4);
put(&fmt, 2); put(&ch, 2); put(&rate32, 4);
put(&byte_rate, 4); put(&align, 2); put(&bits, 2);
put("data", 4); put(&data_sz, 4);
for (float v : pcm) {
int16_t s = (int16_t) (std::max(-1.0f, std::min(1.0f, v)) * 32767.0f);
put(&s, 2);
}
write_wav16_header(buf, (uint32_t) (pcm.size() * 2), rate);
append_wav16_pcm(buf, pcm.data(), pcm.size());
return true;
}
@@ -92,6 +101,8 @@ public:
// set out_stop on end-of-speech, h_state_out must be null if no frame is generated
virtual int32_t step_gen(llama_token sampled, const float * h_state_in, const float ** h_state_out, bool * out_stop) = 0;
virtual int32_t get_output(int32_t * out_sample_rate, const char ** out_data, size_t * out_data_len, int64_t * out_n_samples) = 0;
// forces any buffered codes through code2wav now, regardless of window_frames
virtual int32_t flush() = 0;
protected:
llama_context * lctx;
@@ -121,6 +132,9 @@ public:
prompt_batch.reset();
n_prompt = 0;
prompt_pos = 0;
stream = false;
pcm_sent = 0;
wav_header_sent = false;
}
int32_t set_input(const mtmd_helper_gen_audio_inp * inp) override {
@@ -208,6 +222,7 @@ public:
top_p = inp->top_p > 0 ? inp->top_p : def.top_p;
seed = inp->seed;
out_type = inp->out_type;
stream = inp->stream;
// the prompt above holds the whole text stream up to tts_eos, so every generated
// frame adds tts_pad on top of the codes embedding
@@ -301,31 +316,60 @@ public:
}
int32_t get_output(int32_t * out_sample_rate, const char ** out_data, size_t * out_data_len, int64_t * out_n_samples) override {
if (!flush_gen_wav()) {
return 1;
*out_sample_rate = info.sample_rate;
if (!stream) {
// one-shot call: force out whatever's left, regardless of window_frames
if (!flush_gen_wav()) {
return 1;
}
if (out_n_samples) {
*out_n_samples = (int64_t) audio_pcm.size();
}
if (out_type == MTMD_HELPER_GEN_AUDIO_OUTTYPE_PCM) {
*out_data = (const char *) audio_pcm.data();
*out_data_len = audio_pcm.size() * sizeof(float);
return 0;
}
out_buf.clear();
if (!write_wav16(out_buf, audio_pcm, info.sample_rate)) {
LOG_ERR("mtmd_helper_gen_audio: output too large for WAV\n");
return 1;
}
*out_data = out_buf.data();
*out_data_len = out_buf.size();
return 0;
}
*out_sample_rate = info.sample_rate;
// streaming: only return audio produced since the previous call
const size_t n_new = audio_pcm.size() - pcm_sent;
if (out_n_samples) {
*out_n_samples = (int64_t) audio_pcm.size();
*out_n_samples = (int64_t) n_new;
}
if (out_type == MTMD_HELPER_GEN_AUDIO_OUTTYPE_PCM) {
*out_data = (const char *) audio_pcm.data();
*out_data_len = audio_pcm.size() * sizeof(float);
*out_data = (const char *) (audio_pcm.data() + pcm_sent);
*out_data_len = n_new * sizeof(float);
pcm_sent = audio_pcm.size();
return 0;
}
out_buf.clear();
if (!write_wav16(out_buf, audio_pcm, info.sample_rate)) {
LOG_ERR("mtmd_helper_gen_audio: output too large for WAV\n");
return 1;
if (!wav_header_sent) {
write_wav16_header(out_buf, UINT32_MAX, info.sample_rate);
wav_header_sent = true;
}
append_wav16_pcm(out_buf, audio_pcm.data() + pcm_sent, n_new);
pcm_sent = audio_pcm.size();
*out_data = out_buf.data();
*out_data_len = out_buf.size();
return 0;
}
int32_t flush() override {
return flush_gen_wav() ? 0 : 1;
}
private:
bool ensure_cache() {
if (specials_ok) {
@@ -369,7 +413,7 @@ private:
LOG_ERR("mtmd_helper_gen_audio: mmproj has no speaker/audio encoder\n");
return false;
}
const std::string marker = mtmd_default_marker();
const std::string marker = mtmd_get_marker(mctx);
mtmd_input_text text{ marker.c_str(), marker.size(), false, true };
mtmd_input_chunks * chunks = mtmd_input_chunks_init();
const mtmd_bitmap * bptr = bitmap;
@@ -455,6 +499,9 @@ private:
std::vector<float> h_state_buf;
mtmd_helper_gen_audio_outtype out_type = MTMD_HELPER_GEN_AUDIO_OUTTYPE_WAV;
std::vector<char> out_buf;
bool stream = false;
size_t pcm_sent = 0; // samples already returned by get_output()
bool wav_header_sent = false;
};
// settings that only live in the reference's per-pack yaml, not in the checkpoint
@@ -506,6 +553,9 @@ public:
chunk_idx = 0;
n_voice_pos = 0;
chunk_budget = 0;
stream = false;
pcm_sent = 0;
wav_header_sent = false;
}
int32_t set_input(const mtmd_helper_gen_audio_inp * inp) override {
@@ -575,6 +625,7 @@ public:
seed = inp->seed;
out_type = inp->out_type;
stream = inp->stream;
return 0;
}
@@ -662,31 +713,60 @@ public:
}
int32_t get_output(int32_t * out_sample_rate, const char ** out_data, size_t * out_data_len, int64_t * out_n_samples) override {
if (!flush_gen_wav()) {
return 1;
*out_sample_rate = info.sample_rate;
if (!stream) {
// one-shot call: force out whatever's left, regardless of window_frames
if (!flush_gen_wav()) {
return 1;
}
if (out_n_samples) {
*out_n_samples = (int64_t) audio_pcm.size();
}
if (out_type == MTMD_HELPER_GEN_AUDIO_OUTTYPE_PCM) {
*out_data = (const char *) audio_pcm.data();
*out_data_len = audio_pcm.size() * sizeof(float);
return 0;
}
out_buf.clear();
if (!write_wav16(out_buf, audio_pcm, info.sample_rate)) {
LOG_ERR("mtmd_helper_gen_audio: output too large for WAV\n");
return 1;
}
*out_data = out_buf.data();
*out_data_len = out_buf.size();
return 0;
}
*out_sample_rate = info.sample_rate;
// streaming: only return audio produced since the previous call
const size_t n_new = audio_pcm.size() - pcm_sent;
if (out_n_samples) {
*out_n_samples = (int64_t) audio_pcm.size();
*out_n_samples = (int64_t) n_new;
}
if (out_type == MTMD_HELPER_GEN_AUDIO_OUTTYPE_PCM) {
*out_data = (const char *) audio_pcm.data();
*out_data_len = audio_pcm.size() * sizeof(float);
*out_data = (const char *) (audio_pcm.data() + pcm_sent);
*out_data_len = n_new * sizeof(float);
pcm_sent = audio_pcm.size();
return 0;
}
out_buf.clear();
if (!write_wav16(out_buf, audio_pcm, info.sample_rate)) {
LOG_ERR("mtmd_helper_gen_audio: output too large for WAV\n");
return 1;
if (!wav_header_sent) {
write_wav16_header(out_buf, UINT32_MAX, info.sample_rate);
wav_header_sent = true;
}
append_wav16_pcm(out_buf, audio_pcm.data() + pcm_sent, n_new);
pcm_sent = audio_pcm.size();
*out_data = out_buf.data();
*out_data_len = out_buf.size();
return 0;
}
int32_t flush() override {
return flush_gen_wav() ? 0 : 1;
}
private:
bool ensure_cache() {
if (specials_ok) {
@@ -907,7 +987,7 @@ private:
LOG_ERR("mtmd_helper_gen_audio: mmproj has no voice encoder\n");
return false;
}
const std::string marker = mtmd_default_marker();
const std::string marker = mtmd_get_marker(mctx);
mtmd_input_text text{ marker.c_str(), marker.size(), false, true };
mtmd_input_chunks * chunks = mtmd_input_chunks_init();
const mtmd_bitmap * bptr = bitmap;
@@ -989,6 +1069,9 @@ private:
std::vector<float> h_state_buf;
mtmd_helper_gen_audio_outtype out_type = MTMD_HELPER_GEN_AUDIO_OUTTYPE_WAV;
std::vector<char> out_buf;
bool stream = false;
size_t pcm_sent = 0; // samples already returned by get_output()
bool wav_header_sent = false;
};
static std::unique_ptr<mtmd_gen_audio_pipeline> make_pipeline(llama_context * lctx, mtmd_context * mctx) {
@@ -1017,11 +1100,20 @@ void mtmd_helper_gen_audio_free(mtmd_helper_gen_audio * ctx) {
}
void mtmd_helper_gen_audio_reset(mtmd_helper_gen_audio * ctx) {
if (ctx->pipeline) {
if (ctx && ctx->pipeline) {
ctx->pipeline->reset();
}
}
struct mtmd_helper_gen_audio_inp mtmd_helper_gen_audio_inp_default(void) {
mtmd_helper_gen_audio_inp inp{};
inp.top_k = 50;
inp.top_p = 1.0f;
inp.seed = UINT32_MAX; // random
inp.out_type = MTMD_HELPER_GEN_AUDIO_OUTTYPE_WAV;
return inp;
}
int32_t mtmd_helper_gen_audio_set_input(mtmd_helper_gen_audio * ctx, const mtmd_helper_gen_audio_inp * inp) {
if (!ctx->pipeline) {
LOG_ERR("mtmd_helper_gen_audio: unsupported or missing gen-audio pipeline\n");
@@ -1058,3 +1150,10 @@ int32_t mtmd_helper_gen_audio_get_output(mtmd_helper_gen_audio * ctx, int32_t *
}
return ctx->pipeline->get_output(out_sample_rate, out_data, out_data_len, out_n_samples);
}
int32_t mtmd_helper_gen_audio_flush(mtmd_helper_gen_audio * ctx) {
if (!ctx->pipeline) {
return 1;
}
return ctx->pipeline->flush();
}
+46 -1
View File
@@ -204,6 +204,7 @@ enum mtmd_helper_gen_audio_outtype {
MTMD_HELPER_GEN_AUDIO_OUTTYPE_WAV, // WAV PCM 16-bit LE, mono
};
struct mtmd_helper_gen_audio_inp {
bool stream; // if true, output() must be called after each step_gen()
llama_seq_id seq_id;
const char * prompt;
@@ -219,6 +220,8 @@ struct mtmd_helper_gen_audio_inp {
enum mtmd_helper_gen_audio_outtype out_type;
};
MTMD_API struct mtmd_helper_gen_audio_inp mtmd_helper_gen_audio_inp_default(void);
MTMD_API mtmd_helper_gen_audio * mtmd_helper_gen_audio_init(
struct llama_context * lctx,
struct mtmd_context * mctx);
@@ -250,6 +253,8 @@ MTMD_API int32_t mtmd_helper_gen_audio_step_gen(
// out_data valid until next get_output() or reset() call
// out_n_samples (optional, can be NULL) receives the number of generated PCM samples
// if inp->stream is true: returns only audio produced since the previous call, and
// *out_data_len == 0 whenever a full window_frames batch hasn't accumulated yet
MTMD_API int32_t mtmd_helper_gen_audio_get_output(
mtmd_helper_gen_audio * ctx,
int32_t * out_sample_rate,
@@ -257,6 +262,10 @@ MTMD_API int32_t mtmd_helper_gen_audio_get_output(
size_t * out_data_len,
int64_t * out_n_samples);
// forces any buffered codes through code2wav now, regardless of window_frames;
// call once when generation has ended, before the last get_output() in stream mode
MTMD_API int32_t mtmd_helper_gen_audio_flush(mtmd_helper_gen_audio * ctx);
#ifdef __cplusplus
} // extern "C"
#endif
@@ -283,8 +292,41 @@ struct mtmd_helper_gen_audio_deleter {
};
using gen_audio_ptr = std::unique_ptr<mtmd_helper_gen_audio, mtmd_helper_gen_audio_deleter>;
struct gen_audio {
// sub-struct, RAII wrapper for mtmd_helper_gen_audio_inp
struct inp {
mtmd_helper_gen_audio_inp data = mtmd_helper_gen_audio_inp_default();
std::string prompt_str;
std::string lang_str;
mtmd::bitmap_ptr speaker_ref_ptr;
inp() = default;
inp(inp &&) = default;
inp & operator=(inp &&) = default;
inp(const inp &) = delete;
inp & operator=(const inp &) = delete;
void set_prompt (std::string p) { prompt_str = std::move(p); }
void set_lang (std::string l) { lang_str = std::move(l); }
void set_speaker_ref(mtmd::bitmap_ptr bmp) { speaker_ref_ptr = std::move(bmp); }
// pointers are only valid as long as *this is alive
const mtmd_helper_gen_audio_inp * get() {
data.prompt = prompt_str.c_str();
data.prompt_len = prompt_str.size();
data.lang = lang_str.empty() ? nullptr : lang_str.c_str();
data.speaker_ref = speaker_ref_ptr.get();
return &data;
}
};
gen_audio_ptr ctx;
gen_audio(struct llama_context * lctx, struct mtmd_context * mctx) : ctx(mtmd_helper_gen_audio_init(lctx, mctx)) {}
void init(struct llama_context * lctx, struct mtmd_context * mctx) {
ctx.reset(mtmd_helper_gen_audio_init(lctx, mctx));
}
bool valid() const {
return ctx.get() != nullptr;
}
void reset() {
mtmd_helper_gen_audio_reset(ctx.get());
}
@@ -300,6 +342,9 @@ struct gen_audio {
int32_t get_output(int32_t * out_sample_rate, const char ** out_data, size_t * out_data_len, int64_t * out_n_samples = nullptr) {
return mtmd_helper_gen_audio_get_output(ctx.get(), out_sample_rate, out_data, out_data_len, out_n_samples);
}
int32_t flush() {
return mtmd_helper_gen_audio_flush(ctx.get());
}
};
} // namespace mtmd_helper
+56 -21
View File
@@ -873,16 +873,28 @@ mtmd_image_preproc_out mtmd_image_preprocessor_glm5v::preprocess(const clip_imag
clip_image_size mtmd_image_preprocessor_longest_edge::get_target_size(const clip_hparams & hparams, const clip_image_size & original_size) {
GGML_ASSERT(hparams.image_longest_edge > 0);
clip_image_size size = original_size;
if (hparams.image_floor_before_align) {
const double ratio = double(std::max(size.width, size.height)) / hparams.image_longest_edge;
if (ratio > 1.0) {
size.width = static_cast<int>(std::floor(size.width / ratio));
size.height = static_cast<int>(std::floor(size.height / ratio));
if (size.width <= 0 || size.height <= 0) {
throw std::runtime_error("image dimensions must be positive after resizing");
}
}
}
// the original pixtral model doesn't have n_merge
const int cur_merge = hparams.n_merge == 0 ? 1 : hparams.n_merge;
return img_tool::calc_size_preserved_ratio(
original_size,
size,
{
/* align_size */ hparams.patch_size * cur_merge,
/* min_pixels */ std::max(0, hparams.image_min_pixels),
/* max_pixels */ std::max(0, hparams.image_max_pixels),
/* longest_edge */ std::min(hparams.image_longest_edge,
std::max(original_size.width, original_size.height)),
std::max(size.width, size.height)),
});
}
@@ -952,12 +964,33 @@ bool mtmd_image_preprocessor_lfm2::should_tile(
static_cast<double>(hparams.image_max_pixels) * hparams.preproc_max_pixels_tolerance;
}
// same as Lfm2VlImageProcessor.smart_resize: Python's round half to even, double precision
clip_image_size mtmd_image_preprocessor_lfm2::smart_resize(
const clip_hparams & hparams,
const clip_image_size & original_size) {
const int align_size = hparams.patch_size * hparams.n_merge;
const double height = original_size.height;
const double width = original_size.width;
int h_bar = std::max(align_size, static_cast<int>(std::nearbyint(height / align_size)) * align_size);
int w_bar = std::max(align_size, static_cast<int>(std::nearbyint(width / align_size)) * align_size);
if (static_cast<double>(h_bar) * w_bar > hparams.image_max_pixels) {
const double beta = std::sqrt(height * width / hparams.image_max_pixels);
h_bar = std::max(align_size, static_cast<int>(std::floor(height / beta / align_size)) * align_size);
w_bar = std::max(align_size, static_cast<int>(std::floor(width / beta / align_size)) * align_size);
} else if (static_cast<double>(h_bar) * w_bar < hparams.image_min_pixels) {
const double beta = std::sqrt(hparams.image_min_pixels / (height * width));
h_bar = static_cast<int>(std::ceil(height * beta / align_size)) * align_size;
w_bar = static_cast<int>(std::ceil(width * beta / align_size)) * align_size;
}
return {w_bar, h_bar};
}
mtmd_image_preprocessor_llava_uhd::slice_instructions mtmd_image_preprocessor_lfm2::get_slice_instructions(const clip_image_size & original_size) const {
mtmd_image_preprocessor_llava_uhd::slice_instructions inst;
const int align_size = hparams.patch_size * hparams.n_merge;
inst.overview_size = img_tool::calc_size_preserved_ratio(
original_size,
{ align_size, hparams.image_min_pixels, hparams.image_max_pixels, 0 });
inst.overview_size = smart_resize(hparams, original_size);
const bool needs_tiling = should_tile(hparams, original_size);
const int tile_size = hparams.preproc_tile_size;
@@ -968,7 +1001,7 @@ mtmd_image_preprocessor_llava_uhd::slice_instructions mtmd_image_preprocessor_lf
return inst;
}
const clip_image_size grid = get_grid_layout(original_size.height, original_size.width);
const clip_image_size grid = get_grid_layout(hparams, original_size.height, original_size.width);
inst.grid_size = grid;
inst.refined_size = clip_image_size{tile_size * grid.width, tile_size * grid.height};
@@ -996,24 +1029,26 @@ mtmd_image_preprocessor_llava_uhd::slice_instructions mtmd_image_preprocessor_lf
return inst;
}
// double precision as in Python, float breaks exact ties such as 720/1600 between 2/4 and 2/5
clip_image_size mtmd_image_preprocessor_lfm2::find_closest_aspect_ratio(
float aspect_ratio,
const clip_hparams & hparams,
double aspect_ratio,
const std::vector<clip_image_size> & target_ratios,
int width, int height) const {
float best_ratio_diff = std::numeric_limits<float>::max();
int width, int height) {
double best_ratio_diff = std::numeric_limits<double>::max();
clip_image_size best_ratio = {1, 1};
const float area = static_cast<float>(width * height);
const double area = static_cast<double>(width) * height;
for (const auto & ratio : target_ratios) {
const float target_aspect_ratio = static_cast<float>(ratio.width) / ratio.height;
const float ratio_diff = std::abs(aspect_ratio - target_aspect_ratio);
const double target_aspect_ratio = static_cast<double>(ratio.width) / ratio.height;
const double ratio_diff = std::abs(aspect_ratio - target_aspect_ratio);
if (ratio_diff < best_ratio_diff) {
best_ratio_diff = ratio_diff;
best_ratio = ratio;
} else if (ratio_diff == best_ratio_diff) {
const int tile_size = hparams.preproc_tile_size;
const float target_area = static_cast<float>(tile_size * tile_size * ratio.width * ratio.height);
if (area > 0.5f * target_area) {
const int tile_size = hparams.preproc_tile_size;
const double target_area = static_cast<double>(tile_size * tile_size * ratio.width * ratio.height);
if (area > 0.5 * target_area) {
best_ratio = ratio;
}
}
@@ -1021,7 +1056,7 @@ clip_image_size mtmd_image_preprocessor_lfm2::find_closest_aspect_ratio(
return best_ratio;
}
std::vector<clip_image_size> mtmd_image_preprocessor_lfm2::get_target_ratios() const {
std::vector<clip_image_size> mtmd_image_preprocessor_lfm2::get_target_ratios(const clip_hparams & hparams) {
const int min_tiles = hparams.preproc_min_tiles;
const int max_tiles = hparams.preproc_max_tiles;
std::vector<clip_image_size> ratios;
@@ -1049,10 +1084,10 @@ std::vector<clip_image_size> mtmd_image_preprocessor_lfm2::get_target_ratios() c
return ratios;
}
clip_image_size mtmd_image_preprocessor_lfm2::get_grid_layout(int height, int width) const {
const float aspect_ratio = static_cast<float>(width) / height;
const auto ratios = get_target_ratios();
return find_closest_aspect_ratio(aspect_ratio, ratios, width, height);
clip_image_size mtmd_image_preprocessor_lfm2::get_grid_layout(const clip_hparams & hparams, int height, int width) {
const double aspect_ratio = static_cast<double>(width) / height;
const auto ratios = get_target_ratios(hparams);
return find_closest_aspect_ratio(hparams, aspect_ratio, ratios, width, height);
}
//
+7 -5
View File
@@ -199,14 +199,16 @@ struct mtmd_image_preprocessor_lfm2 : mtmd_image_preprocessor_llava_uhd {
slice_instructions get_slice_instructions(const clip_image_size & original_size) const override;
static bool should_tile(const clip_hparams & hparams, const clip_image_size & original_size);
static clip_image_size smart_resize(const clip_hparams & hparams, const clip_image_size & original_size);
static clip_image_size get_grid_layout(const clip_hparams & hparams, int height, int width);
private:
clip_image_size find_closest_aspect_ratio(
float aspect_ratio,
static clip_image_size find_closest_aspect_ratio(
const clip_hparams & hparams,
double aspect_ratio,
const std::vector<clip_image_size> & target_ratios,
int width, int height) const;
std::vector<clip_image_size> get_target_ratios() const;
clip_image_size get_grid_layout(int height, int width) const;
int width, int height);
static std::vector<clip_image_size> get_target_ratios(const clip_hparams & hparams);
};
struct mtmd_image_preprocessor_idefics3 : mtmd_image_preprocessor_llava_uhd {
+7 -2
View File
@@ -804,7 +804,11 @@ struct mtmd_context {
{
// https://github.com/huggingface/transformers/blob/1cd110c6cb6a6237614130c470e9a902dbc1a4bd/docs/source/en/model_doc/pixtral.md
img_end = "[IMG_END]";
image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);
if (clip_get_hparams(ctx_v)->image_longest_edge > 0) {
image_preproc = std::make_unique<mtmd_image_preprocessor_longest_edge>(ctx_v);
} else {
image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);
}
} break;
case PROJECTOR_TYPE_PHI4:
{
@@ -2842,10 +2846,11 @@ struct mtmd_caps mtmd_get_cap_from_file(const char * fname) {
mtmd_caps cap;
cap.inp_audio = tmp.has_audio;
cap.inp_vision = tmp.has_vision;
cap.gen_audio = tmp.has_gen_audio;
return cap;
} catch (const std::exception & e) {
LOG_ERR("%s: failed to get capabilities from file '%s': %s\n", __func__, fname, e.what());
return mtmd_caps{ false, false };
return mtmd_caps{ false, false, false };
}
}
+1
View File
@@ -360,6 +360,7 @@ MTMD_API void mtmd_log_set(ggml_log_callback log_callback, void * user_data);
struct mtmd_caps {
bool inp_vision;
bool inp_audio;
bool gen_audio;
};
MTMD_API struct mtmd_caps mtmd_get_cap_from_file(const char * mmproj_fname);
+79 -5
View File
@@ -802,6 +802,50 @@ curl http://127.0.0.1:8012/v1/rerank \
}' | jq
```
### POST `/tts`: Generate speech audio from text
Returns raw audio bytes (`audio/wav` by default) rather than JSON. For more info, see [tts/README.md](../tts/README.md)
*Options:*
`input`: The text to speak (alias: `prompt`).
`lang`: Language code for the utterance (model-dependent, e.g. `en`, `zh`). Optional.
`speaker_ref_b64`: Base64-encoded reference audio to clone the speaker's voice. Optional. Alternatively, upload the reference audio as a `speaker_ref` file field via `multipart/form-data` (see example below) - if both are provided, the uploaded file takes precedence.
`top_k`, `top_p`: Sampling params for the acoustic code predictor. Optional, model-dependent defaults apply.
`repeat_penalty`: Repetition penalty applied to the backbone sampler over the whole generation (default `1.05`). Without this, the backbone can loop and re-generate the same utterance.
`seed`: RNG seed for both the backbone sampler and the codec/vocoder (`-1` = random, the default). Set to a fixed value for reproducible output.
`n_predict`: Max number of audio frames to generate. Defaults to `512`; generation normally stops earlier once the model emits an end-of-speech token.
`response_format`: `wav` (default) or `pcm` (raw `float32` little-endian mono samples, no header; the response `Content-Type` carries the sample rate, e.g. `audio/pcm;rate=24000;encoding=float;bits=32`).
`stream`: If `true`, the response is streamed as audio becomes available instead of waiting for the full generation to finish. WAV streaming writes an RFC-noncompliant header with an unknown (`0xFFFFFFFF`) size field, since the final length isn't known up front; most players and decoders (ffmpeg, VLC, ...) handle this by reading until EOF.
Note: it's highly recommended to always provide a speaker reference voice; otherwise, the model's performance may be degraded.
*Examples:*
```shell
curl -X POST http://127.0.0.1:9931/tts \
-H "Content-Type: application/json" \
-d '{"input": "Hello, this is a test."}' \
-o output.wav
```
With a speaker reference uploaded as a file (`multipart/form-data`), instead of base64-encoding it into the JSON body:
```shell
curl -X POST http://127.0.0.1:9931/tts \
-F "input=Hello, this is a test." \
-F "speaker_ref=@/path/to/speaker-reference.wav;type=audio/wav" \
-o output.wav
```
### POST `/infill`: For code infilling.
Takes a prefix and a suffix and returns the predicted completion as stream.
@@ -1262,11 +1306,12 @@ Each object in `data` has an `architecture` object. It has two string arrays:
- `input_modalities` lists what the model can read. It always has `text`, plus each media type that the model supports.
- `output_modalities` lists what the model can produce.
One output value is special:
Some output values are special:
| Value | Meaning |
|---|---|
| `decisions` | The model is a native decision model. Serve it with [`/v1/systemone`](#post-v1systemone-typesafe-compatible-system-one-api). |
| `audio` | The model can generate speech. Serve it with [`/tts`](#post-tts-generate-speech-audio-from-text). A TTS-only model has `["audio"]` without `text`. |
A language model that classifies with prompts does not get `decisions`. Only native decision models do.
@@ -1491,6 +1536,31 @@ Acts on an in-flight completion identified by its `id` (the `id` field streamed
Returns a JSON object with a boolean `success` field, and an optional `message` field describing the reason when `success` is `false`.
### WebSocket `/v1/realtime`: Realtime transcription
Transcribes streamed audio, for models with a streaming audio encoder. Compatible with OAI `/v1/realtime` schema.
Audio must be PCM16, mono, at the model sample rate. The sample rate is returned in `session.created` as `audio.input.format.rate`. Each `input_audio_buffer.append` carries a base64-encoded chunk of PCM16 samples.
*Client events:*
- `session.update`: accepted for compatibility, the server replies with `session.updated`
- `input_audio_buffer.append`: `audio` holds base64 PCM16 samples
- `input_audio_buffer.commit`: flushes the audio received so far, so the words still held back by the model's delay are produced. The server replies with `input_audio_buffer.committed` and starts a new item. With `"final": true`, the input ends instead and the last item is completed once the stream drains.
- `input_audio_buffer.clear`: accepted for compatibility, audio already sent cannot be discarded
Closing the websocket also ends the input.
*Server events:*
- `session.created` and `session.updated`: carry the `session` object
- `conversation.item.input_audio_transcription.delta`: `delta` holds the new text for `item_id`
- `conversation.item.input_audio_transcription.completed`: `transcript` holds the full text of `item_id`
- `input_audio_buffer.committed`: `item_id` of the committed item, `previous_item_id` of the item committed before it (`null` for the first one)
- `error`: `error.message` describes the problem
Note that words held back by the model's delay at the time of a commit are attributed to the next item.
### POST `/v1/responses`: OpenAI-compatible Responses API
*Options:*
@@ -1725,13 +1795,13 @@ Follows the [TypeSafe API](https://docs.typesafe.ai/api), streaming is not suppo
The questions of a request are answered independently, an answer does not depend on the other questions. The exception is clef: it reads all the questions in one prompt and decides them jointly.
The number of options of a `choice` question is limited by the model, for example: 52 for openjev, 255 for granite-switch, laya, clef, pplx-decider, vllm-decision2, vllm-d3, lfm2-d1 and lfm2-d1-omni. For laya, long questions and options are truncated to the token budget the model was trained with.
The number of options of a `choice` question is limited by the model, for example: 52 for openjev, 255 for granite-switch, laya, clef, pplx-decider, vllm-decision2, vllm-d3, lfm2-d1, lfm2-d1-omni and h2o-lightning. For laya, long questions and options are truncated to the token budget the model was trained with.
For laya, clef, vllm-d3 and lfm2-d1-omni, the whole prompt is evaluated in one batch: it must fit in `--ubatch-size`. An lfm2-d1-omni prompt is cut to 16384 tokens. For vllm-decision2, the options of a question and the end of the prompt must fit in one batch. A server that runs clef only serves this endpoint, text generation is not available. A decision model that is also a text model, such as granite-switch, serves both and reports `["text", "decisions"]`.
For laya, clef, vllm-d3 and lfm2-d1-omni, the whole prompt is evaluated in one batch: it must fit in `--ubatch-size`. An lfm2-d1-omni prompt is cut to 16384 tokens. For vllm-decision2, the options of a question and the end of the prompt must fit in one batch. An h2o-lightning state longer than 32000 tokens keeps its first and last 16000 tokens. A server that runs clef only serves this endpoint, text generation is not available. A decision model that is also a text model, such as granite-switch, serves both and reports `["text", "decisions"]`.
*Image input:*
Image input needs a model that supports it (for example: openjev, clef, pplx-decider, vllm-d3, lfm2-d1, lfm2-d1-omni) and its multimodal projector, see `--mmproj`.
Image input needs a model that supports it (for example: openjev, clef, pplx-decider, vllm-d3, lfm2-d1, lfm2-d1-omni, h2o-lightning) and its multimodal projector, see `--mmproj`.
Images can be given in two ways, and both can be used in the same request:
@@ -1760,6 +1830,8 @@ All the images are placed before the state in the prompt, the ones from `files`
The probabilities are scaled with the temperatures stored in the model file. They are not guaranteed to be calibrated for your data.
For h2o-lightning, `score` questions can have 1 to 255 levels, and the `instructions` and `type` of a question can be left out (`choice` by default; `"instructions": null` is written as `null`).
*Examples:*
```shell
@@ -1990,6 +2062,8 @@ For **GET** endpoints (`/props`, `/metrics`, etc.) The router uses the `model` q
GET /props?model=ggml-org%2Fgemma-3-4b-it-GGUF%3AQ4_K_M
```
For the **WebSocket** endpoint (`/v1/realtime`) The router also uses the `model` query parameter.
By default, the model will be loaded automatically if it's not loaded. To disable this, add `--no-models-autoload` when starting the server. Additionally, you can include `?autoload=true|false` in the query param to control this behavior per-request.
### GET `/models`: List available models
@@ -2025,7 +2099,7 @@ Note:
- If a model is not running, it will be added or updated according to the source
2. When the model is loaded, the info from `/v1/models` is forwarded to router's `/v1/models`. This includes metadata about the model and the runtime instance.
Each object in `data` has the same `architecture` object as [`GET /v1/models`](#get-v1models-openai-compatible-model-info-api) of a direct server. The server computes both arrays offline. It does not load the model, download files, or run inference. `output_modalities` comes from the GGUF metadata. `input_modalities` comes from the projector file. A native decision model shows `decisions` before its first load, after unload, and while it sleeps:
Each object in `data` has the same `architecture` object as [`GET /v1/models`](#get-v1models-openai-compatible-model-info-api) of a direct server. The server computes both arrays offline. It does not load the model, download files, or run inference. `output_modalities` comes from the GGUF metadata, plus `audio` when the projector file can generate speech. `input_modalities` comes from the projector file. A native decision model shows `decisions` before its first load, after unload, and while it sleeps:
```json
{
+21 -5
View File
@@ -202,7 +202,8 @@ const char * get_media_marker() {
// model output modalities
//
std::vector<std::string> server_model_output_modalities(common_decision_type decision_type) {
std::vector<std::string> server_model_output_modalities(common_decision_type decision_type, bool has_cap_chat, bool has_cap_tts) {
std::vector<std::string> out;
switch (decision_type) {
case COMMON_DECISION_TYPE_OPENJEV:
case COMMON_DECISION_TYPE_LEV:
@@ -215,13 +216,28 @@ std::vector<std::string> server_model_output_modalities(common_decision_type dec
case COMMON_DECISION_TYPE_VLLM_D3:
case COMMON_DECISION_TYPE_LFM2_D1:
case COMMON_DECISION_TYPE_LFM2_D1_OMNI:
return {"decisions"};
case COMMON_DECISION_TYPE_H2O_LIGHTNING:
out = {"decisions"};
break;
case COMMON_DECISION_TYPE_GRANITE_SWITCH:
return {"text", "decisions"};
out = {"text", "decisions"};
break;
default:
// fallback when there is no decision type or the metadata is bad
return {"text"};
// a tts-only model cannot generate text
if (has_cap_chat || !has_cap_tts) {
// fallback when there is no decision type or the metadata is bad
out = {"text"};
}
break;
}
if (has_cap_tts) {
out.push_back("audio");
}
return out;
}
std::vector<std::string> server_model_output_modalities(const llama_model * model, bool has_cap_chat, bool has_cap_tts) {
return server_model_output_modalities(common_get_decision_type(model), has_cap_chat, has_cap_tts);
}
json server_model_architecture_json(
+4 -1
View File
@@ -114,7 +114,10 @@ const char * get_media_marker();
//
// output modalities for architecture.output_modalities in GET /models
std::vector<std::string> server_model_output_modalities(common_decision_type decision_type);
// has_cap_chat: false for a tts-only model, has_cap_tts: the model can generate audio
std::vector<std::string> server_model_output_modalities(common_decision_type decision_type, bool has_cap_chat, bool has_cap_tts);
// same, with the decision type read from a loaded model
std::vector<std::string> server_model_output_modalities(const llama_model * model, bool has_cap_chat, bool has_cap_tts);
// architecture object of GET /models; shared by the direct server and the router
json server_model_architecture_json(
+678 -27
View File
@@ -1,4 +1,7 @@
#include "server-context.h"
#include <atomic>
#include <thread>
#include "base64.hpp"
#include "server-chat.h"
#include "server-common.h"
#include "server-decision.h"
@@ -17,6 +20,7 @@
#include "speculative.h"
#include "mtmd.h"
#include "mtmd-helper.h"
#include "base64.hpp"
#include <algorithm>
#include <cstddef>
@@ -40,6 +44,14 @@
constexpr int HTTP_POLLING_SECONDS = 1;
static common_speculative_output_limits server_output_limits(const common_params & params) {
if (!params.mmproj.path.empty()) {
const auto mcaps = mtmd_get_cap_from_file(params.mmproj.path.c_str());
if (mcaps.gen_audio) {
// some TTS models decode with embeddings output for the whole batch
return { params.n_batch, params.n_batch };
}
}
if (params.embedding ||
(params.pooling_type != LLAMA_POOLING_TYPE_UNSPECIFIED && params.pooling_type != LLAMA_POOLING_TYPE_NONE)) {
return { params.n_batch, 1 };
@@ -254,6 +266,27 @@ struct server_slot {
mtmd_context * mctx = nullptr;
mtmd::batch_ptr mbatch = nullptr;
struct tts_ctx {
mtmd_helper::gen_audio ctx;
std::vector<float> h_state_prompt; // the first h_state after step_prompt done
const float * h_state;
llama_token sampled;
int32_t n_decoded;
bool stop;
bool is_supported() const {
return ctx.valid();
}
void reset() {
ctx.reset();
h_state_prompt.clear();
h_state = nullptr;
sampled = LLAMA_TOKEN_NULL;
n_decoded = 0;
stop = false;
}
};
tts_ctx tts;
// speculative decoding
common_speculative * spec;
@@ -360,8 +393,13 @@ struct server_slot {
// for TTS models, this is the embd generated from prev step, decode this to generate next hidden state
// corresponding to one token position (size = n_embd)
// for realtime transcription, these are the rows from the last audio stream step (size = n_rows * n_embd)
std::vector<float> inp_embd;
// realtime transcription, see SERVER_TASK_TYPE_TRANSCRIBE_RT
mtmd::audio_stream_ptr audio_stream;
bool audio_wait = false; // the stream needs more audio before it can produce rows
server_slot_stats stats;
// accepted tokens per draft position
@@ -413,6 +451,12 @@ struct server_slot {
// clear multimodal state
mbatch.reset();
tts.reset();
audio_stream.reset();
audio_wait = false;
inp_embd.clear();
}
void init_sampler() const {
@@ -542,16 +586,28 @@ struct server_slot {
// add sampled token of this slot to the batch, optionally add the speculative draft tokens if any
void handle_last_sampled_token(server_batch & batch) {
bool add_ok = true;
// the audio stream has no token before the first step, track the position with a dummy token
const llama_token tok_track = sampled == LLAMA_TOKEN_NULL ? 0 : sampled;
if (spec_draft.empty()) {
// no speculative decoding
i_batch = batch.size();
if (!inp_embd.empty()) {
// text embeddings broadcast the same position across the M-RoPE sections
const llama_pos p = prompt.tokens.pos_next();
const llama_pos pos[GGML_MROPE_SECTIONS] = { p, p, p, 0 };
GGML_ASSERT((int32_t) inp_embd.size() == batch.n_embd);
add_ok &= batch.add_embd(id, inp_embd.data(), pos, true, false);
const int32_t n_rows = (int32_t) inp_embd.size() / batch.n_embd;
GGML_ASSERT((int32_t) inp_embd.size() == n_rows * batch.n_embd);
i_batch = batch.size() + n_rows - 1;
for (int32_t i = 0; i < n_rows; i++) {
// text embeddings broadcast the same position across the M-RoPE sections
const llama_pos p = prompt.tokens.pos_next();
const llama_pos pos[GGML_MROPE_SECTIONS] = { p, p, p, 0 };
add_ok &= batch.add_embd(id, inp_embd.data() + (size_t) i * batch.n_embd, pos, i == n_rows - 1, false);
if (i < n_rows - 1) {
prompt.tokens.push_back(tok_track);
}
}
if (audio_stream) {
inp_embd.clear(); // rows are consumed, the next step produces new ones
}
} else {
add_ok &= batch.add(id, sampled, prompt.tokens.pos_next(), true, false);
}
@@ -579,7 +635,7 @@ struct server_slot {
GGML_ASSERT(add_ok && "batch must be large enough to hold the sampled and draft tokens");
prompt.tokens.push_back(sampled);
prompt.tokens.push_back(tok_track);
prompt.tokens.insert(spec_draft);
}
@@ -595,7 +651,12 @@ struct server_slot {
// do not keep context of the child slots - the parent's context is enough
// nor of the stateless slots - no task ever reuses it
if (task->is_child() || task->is_stateless()) {
if (task->is_child() || task->is_stateless() || audio_stream) {
prompt_clear();
}
// mtmd_helper always re-eval the whole prompt
if (task->type == SERVER_TASK_TYPE_TTS) {
prompt_clear();
}
@@ -952,6 +1013,14 @@ public:
mtmd_helper_init_opt init_opt = mtmd_helper_init_opt_default();
const llama_vocab * vocab = nullptr;
bool has_cap_tts() const {
return mctx != nullptr && mtmd_gen_audio_get_info(mctx).type != MTMD_GEN_AUDIO_TYPE_NONE;
}
bool has_cap_chat() const {
return mctx == nullptr || mtmd_helper_model_can_chat(ctx_tgt, mctx);
}
server_decision_context decision;
server_queue queue_tasks;
@@ -1180,7 +1249,8 @@ private:
}
// optionally fit mmproj memory usage
if (has_mmproj && params_base.fit_params) {
// on resume params_base already holds the margin from the first load
if (has_mmproj && params_base.fit_params && !is_resume) {
const auto & mmproj_mem = mmproj_usage.backend_mem_usage;
const int64_t t_elapsed = mmproj_usage_t_us;
if (!mmproj_mem.empty()) {
@@ -1439,6 +1509,10 @@ private:
slot.mctx = mctx;
slot.prompt.tokens.has_mtmd = mctx != nullptr;
if (has_cap_tts()) {
slot.tts.ctx.init(ctx_tgt, mctx);
}
SLT_TRC(slot, "new slot, n_ctx = %d\n", slot.n_ctx);
slot.callback_on_release = [this](int id_slot) {
@@ -1920,6 +1994,20 @@ private:
SLT_DBG(slot, "launching slot : %s\n", safe_json_to_str(slot.to_json()).c_str());
if (task.type == SERVER_TASK_TYPE_TTS) {
GGML_ASSERT(has_cap_tts()); // should already checked in route handler
if (!slot.tts.is_supported()) {
slot.tts.ctx.init(ctx_tgt, slot.mctx);
}
// mtmd_helper always re-eval the whole prompt
slot.prompt_clear();
task.tts_inp.data.seq_id = slot.id;
if (slot.tts.ctx.set_input(task.tts_inp.get()) != 0) {
send_error(task, "failed to process TTS prompt", ERROR_TYPE_SERVER);
return false;
}
}
// initialize samplers
if (task.need_sampling()) {
try {
@@ -1937,6 +2025,9 @@ private:
// TODO: getting pre sampling logits is not yet supported with backend sampling
use_backend_sampling &= !need_pre_sample_logits;
// TODO: check verify if this actually works with TTS
use_backend_sampling &= task.type != SERVER_TASK_TYPE_TTS;
// TODO: tmp until backend sampling is fully implemented
if (use_backend_sampling) {
llama_set_sampler(ctx_tgt, slot.id, common_sampler_get(slot.smpl.get()));
@@ -1962,9 +2053,30 @@ private:
slot.task = std::make_unique<const server_task>(std::move(task));
slot.state = slot.task->is_child()
? SLOT_STATE_WAIT_OTHER // wait for the parent to process prompt
: SLOT_STATE_STARTED;
if (slot.task->type == SERVER_TASK_TYPE_TTS) {
slot.state = SLOT_STATE_PROCESSING_PROMPT;
} else {
slot.state = slot.task->is_child()
? SLOT_STATE_WAIT_OTHER // wait for the parent to process prompt
: SLOT_STATE_STARTED;
}
// no prompt, the rows come from the audio stream, which audio controls reach from now on
if (slot.task->type == SERVER_TASK_TYPE_TRANSCRIBE_RT) {
slot.audio_stream.reset(mtmd_audio_stream_init(mctx, mtmd_audio_stream_params_default()));
if (!slot.audio_stream) {
send_error(slot, "failed to initialize the audio stream", ERROR_TYPE_SERVER);
slot.release();
return false;
}
slot.prompt_clear();
slot.init_sampler();
slot.sampled = LLAMA_TOKEN_NULL;
slot.i_batch = -1;
slot.stats.update_prompt_start();
slot.state = SLOT_STATE_GENERATING;
send_partial_response(slot, {}, false, true);
}
// reset server kill-switch counter
n_empty_consecutive = 0;
@@ -2227,6 +2339,18 @@ private:
queue_results.send(std::move(res));
}
void send_tts_result(server_slot & slot, int32_t sample_rate, const char * data, size_t data_len, bool final) {
auto res = std::make_unique<server_task_result_tts>();
res->id = slot.task->id;
res->index = slot.task->index;
res->sample_rate = sample_rate;
res->audio.assign(data, data_len);
res->final = final;
queue_results.send(std::move(res));
}
void send_final_response(server_slot & slot) {
auto res = std::make_unique<server_task_result_cmpl_final>();
@@ -2828,8 +2952,10 @@ private:
switch (task.type) {
case SERVER_TASK_TYPE_COMPLETION:
case SERVER_TASK_TYPE_INFILL:
case SERVER_TASK_TYPE_TRANSCRIBE_RT:
case SERVER_TASK_TYPE_EMBEDDING:
case SERVER_TASK_TYPE_RERANK:
case SERVER_TASK_TYPE_TTS:
case SERVER_TASK_TYPE_DECISION:
{
// special case: if input is provided via CLI, tokenize it first
@@ -2915,15 +3041,15 @@ private:
server_slot * slot = get_slot_by_cmpl_id(task.params.control_cmpl_id);
if (slot == nullptr) {
SRV_WRN("control %s on unknown completion id=%s, no live slot\n",
task.params.control_action.c_str(), task.params.control_cmpl_id.c_str());
SRV_WRN("control %d on unknown completion id=%s, no live slot\n",
(int) task.params.control_action, task.params.control_cmpl_id.c_str());
res->success = false;
res->message = "no active completion for this id";
queue_results.send(std::move(res));
break;
}
if (task.params.control_action == "reasoning_end") {
if (task.params.control_action == SERVER_CONTROL_ACTION_REASONING_END) {
// the budget sampler only exists when reasoning control was armed
if (!slot->task->params.sampling.reasoning_control) {
res->success = false;
@@ -2934,6 +3060,22 @@ private:
// act on the live slot mid generation, never defer
common_sampler_reasoning_budget_force(slot->smpl.get());
res->success = true;
} else if (task.params.control_action == SERVER_CONTROL_ACTION_AUDIO_APPEND
|| task.params.control_action == SERVER_CONTROL_ACTION_AUDIO_COMMIT
|| task.params.control_action == SERVER_CONTROL_ACTION_AUDIO_END) {
if (!slot->audio_stream) {
res->success = false;
res->message = "not a realtime transcription slot";
} else {
auto * stream = slot->audio_stream.get();
if (task.params.control_action == SERVER_CONTROL_ACTION_AUDIO_APPEND) {
mtmd_audio_stream_push(stream, task.params.control_audio.data(), task.params.control_audio.size());
} else {
mtmd_audio_stream_flush(stream, task.params.control_action == SERVER_CONTROL_ACTION_AUDIO_END);
}
slot->audio_wait = false;
res->success = true;
}
} else {
res->success = false;
res->message = "unknown control action";
@@ -3253,6 +3395,39 @@ private:
};
#endif
// fill slot.inp_embd with the next rows of the audio stream
// returns false if the slot has nothing to decode now
bool slot_audio_step(server_slot & slot) {
if (!slot.inp_embd.empty()) {
return true; // rows from a previous step, not decoded yet
}
const int32_t n = mtmd_audio_stream_step(slot.audio_stream.get(), slot.sampled);
if (n > 0) {
const float * rows = mtmd_audio_stream_get_output_embd(slot.audio_stream.get());
slot.inp_embd.assign(rows, rows + (size_t) n * batch.n_embd);
slot.audio_wait = false;
return true;
}
if (n == 0) {
slot.audio_wait = true;
return false;
}
if (n == -2) {
send_error(slot, "audio stream error", ERROR_TYPE_SERVER);
slot.release();
return false;
}
// end of stream
slot.stop = STOP_TYPE_EOS;
slot.has_next_token = false;
slot.print_timings();
send_final_response(slot);
slot.release();
return false;
}
void update_slots() {
#ifdef DEBUG_TIMINGS
static int64_t t_prev = 0;
@@ -3268,11 +3443,12 @@ private:
#endif
// check if all slots are idle
// a realtime slot waiting for audio has no work, the next audio control task wakes the loop
{
bool all_idle = true;
for (auto & slot : slots) {
if (slot.is_processing()) {
if (slot.is_processing() && !slot.audio_wait) {
all_idle = false;
break;
}
@@ -3305,6 +3481,14 @@ private:
return;
}
// note: TTS slots bypass the shared batch entirely
try {
process_tts_slots();
} catch (const std::exception & e) {
SRV_ERR("process_tts_slots() failed: %s\n", e.what());
abort_all_slots("process_tts_slots() failed: " + std::string(e.what()));
}
GGML_ASSERT(batch.slot_batched || batch.size() == 0);
if (batch.slot_batched) {
@@ -3367,10 +3551,89 @@ private:
}
}
void process_tts_slots() {
iterate(slots, [&](server_slot & slot) {
if (!slot.is_processing() || slot.task->type != SERVER_TASK_TYPE_TTS) {
return;
}
llama_set_embeddings(ctx_tgt, true);
if (slot.state == SLOT_STATE_PROCESSING_PROMPT) {
const int32_t ret = slot.tts.ctx.step_prompt(llama_n_batch(ctx_tgt));
if (ret < 0) {
send_error(slot, "TTS prompt processing failed", ERROR_TYPE_SERVER);
slot.release();
} else if (ret == 0) {
// done prompt, do sample and save h_state_prompt
slot.tts.sampled = common_sampler_sample(slot.smpl.get(), ctx_tgt, -1);
common_sampler_accept(slot.smpl.get(), slot.tts.sampled, true);
const float * h_embd = llama_get_embeddings_ith(ctx_tgt, -1);
slot.tts.h_state_prompt.assign(h_embd, h_embd + llama_model_n_embd(model_tgt));
slot.tts.h_state = slot.tts.h_state_prompt.data();
slot.state = SLOT_STATE_GENERATING;
}
return;
}
const int32_t n_predict = slot.n_predict_max > 0 ? slot.n_predict_max : 512;
if (slot.tts.stop || slot.tts.n_decoded >= n_predict) {
int32_t sample_rate = 0;
const char * data = nullptr;
size_t data_len = 0;
// generation truly ends here: force out any sub-window remainder still buffered
if (slot.tts.ctx.flush() != 0 || slot.tts.ctx.get_output(&sample_rate, &data, &data_len) != 0) {
send_error(slot, "failed to finalize TTS output", ERROR_TYPE_SERVER);
} else {
send_tts_result(slot, sample_rate, data, data_len, true);
}
slot.release();
return;
}
const float * h_state_next = nullptr;
bool stop = false;
if (slot.tts.ctx.step_gen(slot.tts.sampled, slot.tts.h_state, &h_state_next, &stop) != 0) {
send_error(slot, "TTS generation failed", ERROR_TYPE_SERVER);
slot.release();
return;
}
if (h_state_next == nullptr) {
// end-of-speech without a new frame (e.g. pocket-tts eos head)
slot.tts.stop = true;
return;
}
slot.tts.h_state = h_state_next;
slot.tts.n_decoded++;
slot.tts.stop = stop;
if (!stop) {
slot.tts.sampled = common_sampler_sample(slot.smpl.get(), ctx_tgt, -1);
common_sampler_accept(slot.smpl.get(), slot.tts.sampled, true);
}
if (slot.task->params.stream) {
int32_t sample_rate = 0;
const char * data = nullptr;
size_t data_len = 0;
if (slot.tts.ctx.get_output(&sample_rate, &data, &data_len) != 0) {
send_error(slot, "TTS streaming output failed", ERROR_TYPE_SERVER);
slot.release();
} else if (data_len > 0) {
send_tts_result(slot, sample_rate, data, data_len, false);
}
}
});
}
void pre_decode() {
// apply context-shift if needed
// TODO: simplify and improve
iterate(slots, [&](server_slot & slot) {
if (slot.task && slot.task->type == SERVER_TASK_TYPE_TTS) {
// TTS slots drive their own decode loop in process_tts_slots(), never enter the shared batch
return;
}
if (slot.state == SLOT_STATE_GENERATING && slot.prompt.n_tokens() + 1 >= slot.n_ctx) {
if (!params_base.ctx_shift) {
// this check is redundant (for good)
@@ -3443,10 +3706,14 @@ private:
// determine which slots are generating and drafting
iterate(slots, [&](server_slot & slot) {
if (slot.state != SLOT_STATE_GENERATING) {
if (slot.state != SLOT_STATE_GENERATING || slot.task->type == SERVER_TASK_TYPE_TTS) {
return;
}
if (slot.audio_stream && !slot_audio_step(slot)) {
return; // waiting for audio, or the stream ended
}
// check if we can batch this slot with the previous one
if (!slot_batched) {
slot_batched = &slot;
@@ -3585,7 +3852,7 @@ private:
return; // batch is full, skip remaining slots
}
if (!slot.is_processing()) {
if (!slot.is_processing() || slot.task->type == SERVER_TASK_TYPE_TTS) {
return;
}
@@ -3600,6 +3867,11 @@ private:
return;
}
// no prompt, the rows come from the audio stream
if (slot.task->type == SERVER_TASK_TYPE_TRANSCRIBE_RT) {
return;
}
// this slot still has a prompt to be processed
if (slot.state == SLOT_STATE_PROCESSING_PROMPT || slot.state == SLOT_STATE_STARTED) {
const auto & input_tokens = slot.task->tokens;
@@ -4521,11 +4793,14 @@ private:
GGML_ASSERT(n_draft > 0);
// batch indices of the draft tokens, used to get the token probabilities below
auto spec_i_batch = std::move(slot.spec_i_batch);
// verify and try to accept the draft
{
common_sampler_ptr smpl_save(common_sampler_clone(slot.smpl.get()));
GGML_ASSERT(slot.spec_i_batch.size() == n_draft + 1);
GGML_ASSERT(spec_i_batch.size() == n_draft + 1);
GGML_ASSERT(slot.spec_draft_q.empty() || (slot.spec_draft_q.size() == slot.spec_draft.size()));
const auto & synth_probs = common_speculative_get_synth_probs(spec.get());
@@ -4536,16 +4811,15 @@ private:
if (!synth_probs.empty()) {
// synthetic acceptance replaces verification entirely, so it comes first
accepted = server_sample_and_accept_synth(
slot.smpl.get(), slot.ctx_tgt, slot.spec_i_batch, slot.spec_draft,
slot.smpl.get(), slot.ctx_tgt, spec_i_batch, slot.spec_draft,
synth_probs, slot.spec_synth_rng, slot.spec_is_replay);
} else if (slot.spec_is_replay && slot.use_spec_rejection()) {
accepted = server_accept_replay(slot.smpl.get(), slot.ctx_tgt, slot.spec_i_batch, slot.spec_draft);
accepted = server_accept_replay(slot.smpl.get(), slot.ctx_tgt, spec_i_batch, slot.spec_draft);
} else if (use_rejection) {
accepted = common_sampler_sample_and_accept_n_rejection(slot.smpl.get(), slot.ctx_tgt, slot.spec_i_batch, slot.spec_draft, slot.spec_draft_q);
accepted = common_sampler_sample_and_accept_n_rejection(slot.smpl.get(), slot.ctx_tgt, spec_i_batch, slot.spec_draft, slot.spec_draft_q);
} else {
accepted = common_sampler_sample_and_accept_n(slot.smpl.get(), slot.ctx_tgt, slot.spec_i_batch, slot.spec_draft);
accepted = common_sampler_sample_and_accept_n(slot.smpl.get(), slot.ctx_tgt, spec_i_batch, slot.spec_draft);
}
slot.spec_i_batch.clear();
GGML_ASSERT(accepted.size() >= 1);
@@ -4632,7 +4906,10 @@ private:
result.text_to_send = common_token_to_piece(slot.ctx_tgt, result.tok, accept_special_token(slot, result.tok));
result.prob = 1.0f; // set later
// TODO: set result.probs
// post_sampling_probs is not supported with speculative decoding
if (slot.task->params.sampling.n_probs > 0 && !slot.task->params.post_sampling_probs) {
populate_token_probs(slot, result, false, params_base.special, spec_i_batch[i]);
}
slot.stats.n_gen += 1;
@@ -4820,11 +5097,14 @@ server_context_meta server_context::get_meta() const {
/* model_aliases */ impl->model_aliases,
/* model_tags */ impl->model_tags,
/* model_path */ impl->params_base.model.path,
/* model_output_modalities */ server_model_output_modalities(common_get_decision_type(impl->model_tgt)),
/* model_output_modalities */ server_model_output_modalities(impl->model_tgt, impl->has_cap_chat(), impl->has_cap_tts()),
/* has_mtmd */ impl->mctx != nullptr,
/* has_inp_image */ impl->chat_params.allow_image,
/* has_inp_audio */ impl->chat_params.allow_audio,
/* has_inp_video */ impl->chat_params.allow_video,
/* has_cap_chat */ impl->has_cap_chat(),
/* has_cap_tts */ impl->has_cap_tts(),
/* has_audio_stream */ impl->mctx != nullptr && mtmd_support_audio_stream(impl->mctx),
/* json_ui_settings */ impl->json_ui_settings,
/* slot_n_ctx */ impl->n_ctx_slot(),
/* pooling_type */ llama_pooling_type(impl->ctx_tgt),
@@ -4899,6 +5179,11 @@ std::unique_ptr<server_res_generator> server_routes::handle_completions_impl(
res->set_req(&req); // will also set spipe if needed
if (!ctx_server.has_cap_chat()) {
res->error(format_error_response("this server does not support chat/completions", ERROR_TYPE_NOT_SUPPORTED));
return res;
}
int32_t sse_ping_interval = params.sse_ping_interval;
try {
@@ -4992,10 +5277,20 @@ std::unique_ptr<server_res_generator> server_routes::handle_completions_impl(
res->ok(arr[0]);
} else if (res_type == TASK_RESPONSE_TYPE_OAI_CHAT || res_type == TASK_RESPONSE_TYPE_OAI_CMPL) {
// if multiple results in OAI format, we need to re-format them
json & usage = arr[0]["usage"];
json & choices = arr[0]["choices"];
for (size_t i = 1; i < arr.size(); i++) {
choices.push_back(std::move(arr[i]["choices"][0]));
}
// OAI counts generated tokens across all choices
int64_t n_completion_tokens = 0;
for (auto & item : arr) {
n_completion_tokens += item["usage"]["completion_tokens"].get<int64_t>();
}
usage["completion_tokens"] = n_completion_tokens;
usage["total_tokens"] = usage["prompt_tokens"].get<int64_t>() + n_completion_tokens;
res->ok(arr[0]);
} else {
// multi-results, non-OAI compat
@@ -5605,7 +5900,7 @@ void server_routes::init_routes() {
server_task task(SERVER_TASK_TYPE_CONTROL);
task.id = rd.get_new_id();
task.params.control_cmpl_id = cmpl_id;
task.params.control_action = action;
task.params.control_action = SERVER_CONTROL_ACTION_REASONING_END;
rd.post_task(std::move(task));
}
@@ -5680,6 +5975,197 @@ void server_routes::init_routes() {
session);
};
// realtime transcription over websocket, modeled after the OpenAI realtime API
// audio is PCM16 mono at the model sample rate, sent as base64 in input_audio_buffer.append events
// input_audio_buffer.commit with "final": true ends the input, closing the socket also ends it
this->ws_realtime = [this](const server_http_req &, server_http_context::ws_conn & conn) {
auto send_event = [&conn](json ev) {
ev["event_id"] = "event_" + random_string();
conn.send(safe_json_to_str(ev));
};
auto send_error_event = [&send_event](const std::string & message, const std::string & type) {
send_event(json {
{"type", "error"},
{"error", {
{"type", type},
{"code", nullptr},
{"message", message},
{"param", nullptr},
}},
});
};
if (!meta->has_audio_stream) {
send_error_event("The current model does not support realtime transcription.", "invalid_request_error");
conn.close();
return;
}
const std::string session_id = "sess_" + random_string();
const json session_config = json {
{"id", session_id},
{"object", "realtime.transcription_session"},
{"type", "transcription"},
{"audio", {{"input", {
{"format", {{"type", "audio/pcm"}, {"rate", mtmd_get_audio_sample_rate(ctx_server.mctx)}}},
{"transcription", {{"model", meta->model_name}}},
{"turn_detection", nullptr},
{"noise_reduction", nullptr},
}}}},
};
auto res = create_response();
auto & rd = res->rd;
{
server_task task(SERVER_TASK_TYPE_TRANSCRIBE_RT);
task.id = rd.get_new_id();
task.params.sampling = params.sampling;
task.params.stream = true;
task.params.oaicompat_cmpl_id = session_id;
task.params.oaicompat_model = meta->model_name;
rd.post_task(std::move(task));
}
// the slot answers once its audio stream is live, the session starts there
{
auto result = rd.next([&conn]() { return !conn.is_open(); });
if (!result || result->is_error()) {
if (result) {
send_error_event(json_value(result->to_json(), "message", std::string("server error")), "server_error");
}
conn.close();
return;
}
}
send_event(json {{"type", "session.created"}, {"session", session_config}});
// one item per committed audio buffer, deltas are attributed to the current item
std::mutex mutex_item;
std::string item_id = "item_" + random_string();
std::string item_prev; // the committed item before item_id, empty for the first one
std::string item_text;
// called with mutex_item held
auto send_completed = [&]() {
send_event(json {
{"type", "conversation.item.input_audio_transcription.completed"},
{"item_id", item_id},
{"content_index", 0},
{"transcript", item_text},
});
item_text.clear();
};
auto post_control = [&](server_control_action action, std::vector<float> && audio) {
server_response_reader ctl(queue_tasks, queue_results, HTTP_POLLING_SECONDS);
server_task task(SERVER_TASK_TYPE_CONTROL);
task.id = ctl.get_new_id();
task.params.control_action = action;
task.params.control_cmpl_id = session_id;
task.params.control_audio = std::move(audio);
ctl.post_task(std::move(task));
auto result = ctl.next([&conn]() { return !conn.is_open(); });
auto * control = result ? dynamic_cast<server_task_result_control *>(result.get()) : nullptr;
return control && control->success;
};
// client events are read on this thread, the transcript is sent from the thread below
std::atomic<bool> input_ended{false};
std::thread reader([&]() {
std::string msg;
while (conn.read(msg) != 0) {
json ev;
try {
ev = json::parse(msg);
} catch (const std::exception &) {
send_error_event("invalid JSON", "invalid_request_error");
continue;
}
const std::string type = json_value(ev, "type", std::string());
if (type == "session.update" || type == "transcription_session.update") {
send_event(json {{"type", "session.updated"}, {"session", session_config}});
} else if (type == "input_audio_buffer.append") {
std::string pcm;
try {
pcm = base64::decode(json_value(ev, "audio", std::string()));
} catch (const std::exception &) {
send_error_event("invalid base64 audio", "invalid_request_error");
continue;
}
std::vector<float> samples(pcm.size() / 2);
for (size_t i = 0; i < samples.size(); i++) {
int16_t v;
memcpy(&v, pcm.data() + 2 * i, sizeof(v));
samples[i] = v / 32768.0f;
}
if (!post_control(SERVER_CONTROL_ACTION_AUDIO_APPEND, std::move(samples))) {
break;
}
} else if (type == "input_audio_buffer.commit") {
// "final": true ends the input, the last item is completed when the stream drains
if (json_value(ev, "final", false)) {
input_ended = true;
post_control(SERVER_CONTROL_ACTION_AUDIO_END, {});
continue;
}
if (!post_control(SERVER_CONTROL_ACTION_AUDIO_COMMIT, {})) {
break;
}
// the current item is the committed one, its transcript completes it and the next item opens
std::lock_guard<std::mutex> lock(mutex_item);
send_event(json {
{"type", "input_audio_buffer.committed"},
{"previous_item_id", item_prev.empty() ? json(nullptr) : json(item_prev)},
{"item_id", item_id},
});
send_completed();
item_prev = item_id;
item_id = "item_" + random_string();
} else if (type == "input_audio_buffer.clear") {
send_event(json {{"type", "input_audio_buffer.cleared"}});
} else {
send_error_event("unknown event type: " + type, "invalid_request_error");
}
}
if (!input_ended.exchange(true)) {
post_control(SERVER_CONTROL_ACTION_AUDIO_END, {});
}
// the result loop waits on the socket, nobody reads it from here on
conn.close();
});
while (true) {
auto result = rd.next([&conn]() { return !conn.is_open(); });
if (!result) {
break; // connection closed
}
if (result->is_error()) {
send_error_event(json_value(result->to_json(), "message", std::string("server error")), "server_error");
break;
}
if (auto * partial = dynamic_cast<server_task_result_cmpl_partial *>(result.get())) {
if (!partial->content.text.empty()) {
std::lock_guard<std::mutex> lock(mutex_item);
item_text += partial->content.text;
send_event(json {
{"type", "conversation.item.input_audio_transcription.delta"},
{"item_id", item_id},
{"content_index", 0},
{"delta", partial->content.text},
});
}
} else if (result->is_stop()) {
std::lock_guard<std::mutex> lock(mutex_item);
send_completed();
break;
}
}
input_ended = true;
conn.close();
reader.join();
};
this->post_anthropic_messages = [this](const server_http_req & req) {
auto res = create_response();
std::vector<raw_buffer> files;
@@ -5877,6 +6363,171 @@ void server_routes::init_routes() {
return res;
};
this->post_tts = [this](const server_http_req & req) {
auto res = create_response();
res->set_req(&req); // will also set spipe if needed
if (!ctx_server.has_cap_tts()) {
res->error(format_error_response("this server does not support audio generation", ERROR_TYPE_NOT_SUPPORTED));
return res;
}
const auto info = mtmd_gen_audio_get_info(ctx_server.mctx);
json body = json::parse(req.body);
// multipart form fields arrive as strings, correct the types of the numeric and boolean params
for (const char * k : { "top_k", "n_predict", "max_tokens", "seed" }) {
if (body.contains(k) && body[k].is_string()) {
body[k] = std::stoll(body[k].get<std::string>());
}
}
for (const char * k : { "top_p", "repeat_penalty" }) {
if (body.contains(k) && body[k].is_string()) {
body[k] = std::stof(body[k].get<std::string>());
}
}
if (body.contains("stream") && body["stream"].is_string()) {
body["stream"] = body["stream"] == "true";
}
std::string prompt = json_value(body, "input", json_value(body, "prompt", std::string()));
if (prompt.empty()) {
res->error(format_error_response("\"input\" must be a non-empty string", ERROR_TYPE_INVALID_REQUEST));
return res;
}
const std::string response_format = json_value(body, "response_format", std::string("wav"));
server_task task(SERVER_TASK_TYPE_TTS);
server_schema::eval_tts_schema(params, task, body);
const bool stream = task.params.stream;
task.tts_inp.set_prompt(prompt);
task.tts_inp.set_lang(json_value(body, "lang", std::string()));
task.tts_inp.data.stream = stream;
task.tts_inp.data.out_type = response_format == "pcm"
? MTMD_HELPER_GEN_AUDIO_OUTTYPE_PCM
: MTMD_HELPER_GEN_AUDIO_OUTTYPE_WAV;
// note: -1 is clamped to 0 by the sampler, it will disable the penalty
task.params.sampling.penalty_last_n = task.params.n_predict > 0 ? task.params.n_predict : 512;
task.params.sampling.seed = task.tts_inp.data.seed; // same seed for the codec/vocoder RNG
if (task.tts_inp.data.top_k > 0) {
task.params.sampling.top_k = task.tts_inp.data.top_k;
}
if (task.tts_inp.data.top_p > 0) {
task.params.sampling.top_p = task.tts_inp.data.top_p;
}
// speaker reference: either an uploaded form file ("speaker_ref") or a base64 JSON field ("speaker_ref_b64")
const unsigned char * speaker_ref_data = nullptr;
size_t speaker_ref_len = 0;
std::string speaker_ref_b64_decoded;
auto speaker_ref_file = req.files.find("speaker_ref");
if (speaker_ref_file != req.files.end()) {
speaker_ref_data = speaker_ref_file->second.data.data();
speaker_ref_len = speaker_ref_file->second.data.size();
} else {
std::string speaker_ref_b64 = json_value(body, "speaker_ref_b64", std::string());
if (!speaker_ref_b64.empty()) {
try {
speaker_ref_b64_decoded = base64::decode(speaker_ref_b64);
} catch (const std::exception &) {
res->error(format_error_response("\"speaker_ref_b64\" is not valid base64", ERROR_TYPE_INVALID_REQUEST));
return res;
}
speaker_ref_data = (const unsigned char *) speaker_ref_b64_decoded.data();
speaker_ref_len = speaker_ref_b64_decoded.size();
}
}
if (speaker_ref_len > 0) {
auto wrapper = mtmd_helper_bitmap_init_from_buf(ctx_server.mctx, speaker_ref_data, speaker_ref_len, false, ctx_server.init_opt);
if (!wrapper.bitmap) {
res->error(format_error_response("failed to decode \"speaker_ref\"", ERROR_TYPE_INVALID_REQUEST));
return res;
}
task.tts_inp.set_speaker_ref(mtmd::bitmap_ptr(wrapper.bitmap));
} else {
SRV_WRN("%s", "no speaker reference provided, the model may behave randomly\n");
}
auto & rd = res->rd;
task.id = rd.get_new_id();
rd.post_task(std::move(task));
// raw float32 LE mono samples; audio/L16 would imply 16-bit big-endian (RFC 2586)
const std::string content_type = response_format == "pcm"
? "audio/pcm;rate=" + std::to_string(info.sample_rate) + ";encoding=float;bits=32"
: "audio/wav";
if (!stream) {
auto result = rd.next(req.should_stop);
if (!result) {
GGML_ASSERT(req.should_stop());
return res; // connection is closed
}
if (result->is_error()) {
res->error(result->to_json());
return res;
}
auto * tts_res = dynamic_cast<server_task_result_tts *>(result.get());
GGML_ASSERT(tts_res != nullptr);
res->status = 200;
res->content_type = content_type;
res->data = std::move(tts_res->audio);
return res;
} else {
auto first_result = rd.next(req.should_stop);
if (!first_result) {
GGML_ASSERT(req.should_stop());
return res; // connection is closed
}
if (first_result->is_error()) {
res->error(first_result->to_json());
return res;
}
auto * first_tts_res = dynamic_cast<server_task_result_tts *>(first_result.get());
GGML_ASSERT(first_tts_res != nullptr);
res->status = 200;
res->content_type = content_type;
res->data = std::move(first_tts_res->audio);
bool is_done = first_tts_res->final;
res->set_next([res_this = res.get(), is_done](std::string & output) mutable -> bool {
// flush buffered audio before is_done - the first result can already be final
if (!res_this->data.empty()) {
output = std::move(res_this->data);
res_this->data.clear();
return true;
}
if (is_done) {
return false;
}
if (res_this->should_stop()) {
return false;
}
server_response_reader & rd = res_this->rd;
if (!rd.has_next()) {
return false;
}
auto result = rd.next([&res_this]() { return res_this->should_stop(); });
if (!result || result->is_error()) {
return false;
}
auto * tts_res = dynamic_cast<server_task_result_tts *>(result.get());
GGML_ASSERT(tts_res != nullptr);
output = std::move(tts_res->audio);
is_done = tts_res->final;
return true;
});
}
return res;
};
this->post_systemone = [this](const server_http_req & req) {
auto res = create_response();
const auto & decision = ctx_server.decision;
@@ -5889,7 +6540,7 @@ void server_routes::init_routes() {
const auto questions = decision.parse_questions(body);
std::vector<raw_buffer> files;
const json state = decision.parse_state(body, files);
const json state = decision.prepare_state(decision.parse_state(body, files));
if (!files.empty() && (!decision.can_use_images() || !meta->has_inp_image)) {
res->error(format_error_response("This server does not support image input for decisions. For a model that supports it, start it with `--mmproj`", ERROR_TYPE_NOT_SUPPORTED));
return res;
+5
View File
@@ -24,6 +24,9 @@ struct server_context_meta {
bool has_inp_image;
bool has_inp_audio;
bool has_inp_video;
bool has_cap_chat;
bool has_cap_tts;
bool has_audio_stream; // realtime transcription, see mtmd_support_audio_stream()
json json_ui_settings;
int slot_n_ctx;
enum llama_pooling_type pooling_type;
@@ -144,6 +147,7 @@ struct server_routes {
server_http_context::handler_t post_responses_oai;
server_http_context::handler_t post_responses_tok_oai;
server_http_context::handler_t post_transcriptions_oai;
server_http_context::ws_handler_t ws_realtime;
server_http_context::handler_t post_anthropic_messages;
server_http_context::handler_t post_anthropic_count_tokens;
server_http_context::handler_t post_apply_template;
@@ -153,6 +157,7 @@ struct server_routes {
server_http_context::handler_t post_embeddings;
server_http_context::handler_t post_embeddings_oai;
server_http_context::handler_t post_rerank;
server_http_context::handler_t post_tts;
server_http_context::handler_t post_systemone;
server_http_context::handler_t get_lora_adapters;
server_http_context::handler_t post_lora_adapters;
+335 -10
View File
@@ -19,6 +19,10 @@ static const char * decision_question_type_name(server_decision_question_type ty
// lev reads noul from a rating scale: 0 = certainly no, 8 = certainly yes
static const size_t DECISION_LEV_N_RATINGS = 9;
// h2o-lightning follows serve_config.json and h2o_lightning_shim.py of the model repo
// a longer state keeps its head and its tail
static const size_t DECISION_H2O_MAX_STATE_TOKENS = 32000;
static std::string decision_meta_str(const llama_model * model, const std::string & key) {
char buf[256];
const int32_t n = llama_model_meta_val_str(model, key.c_str(), buf, sizeof(buf));
@@ -164,6 +168,39 @@ void server_decision_context::init(const llama_model * model) {
throw std::runtime_error("decision model has no mask token");
}
n_options_max = 255;
} else if (model_type == COMMON_DECISION_TYPE_H2O_LIGHTNING) {
// the answer is read at the token of a label, its text is given to the template
for (uint32_t i = 0; llama_model_decision_label(model, i) != nullptr; i++) {
const std::string text = llama_model_decision_label(model, i);
const auto toks = common_tokenize(vocab, text, false, false);
if (toks.size() != 1 || std::find(labels.begin(), labels.end(), toks[0]) != labels.end()) {
throw std::runtime_error(string_format("decision label \"%s\" is not a single distinct token", text.c_str()));
}
labels.push_back(toks[0]);
label_texts.push_back(text);
}
if (labels.empty()) {
throw std::runtime_error("decision model has no labels");
}
// the texts of the control and user-defined tokens are quoted in the input, so that they stay text
for (llama_token id = 0; id < llama_vocab_n_tokens(vocab); id++) {
if (llama_vocab_get_attr(vocab, id) & (LLAMA_TOKEN_ATTR_CONTROL | LLAMA_TOKEN_ATTR_USER_DEFINED)) {
const std::string text = llama_vocab_get_text(vocab, id);
if (!text.empty()) {
quoted_spellings.push_back(text);
}
}
}
std::stable_sort(quoted_spellings.begin(), quoted_spellings.end(), [](const std::string & a, const std::string & b) {
return a.size() > b.size();
});
for (const auto & spelling : quoted_spellings) {
if (quoted_first.find(spelling[0]) == std::string::npos) {
quoted_first += spelling[0];
}
}
n_options_max = labels.size();
noul_true_first = true;
} else {
throw std::runtime_error("unsupported decision model type: " + type_name);
}
@@ -201,8 +238,10 @@ std::vector<server_decision_question> server_decision_context::parse_questions(c
throw err("must be an object");
}
// the vllm-d3 prompt has a default question
const json instructions = q.contains("instructions") ? q.at("instructions") : json();
if (instructions.is_null() && type != COMMON_DECISION_TYPE_VLLM_D3) {
// h2o-lightning: the instructions can be left out, the template has a default, and the type defaults to choice
const bool is_h2o = type == COMMON_DECISION_TYPE_H2O_LIGHTNING;
const json instructions = q.contains("instructions") ? q.at("instructions") : (is_h2o ? json(std::string()) : json());
if (instructions.is_null() && type != COMMON_DECISION_TYPE_VLLM_D3 && !is_h2o) {
throw err("\"instructions\" must be provided");
}
if (strict_payload && (!decision_is_payload(instructions) || instructions == "")) {
@@ -213,7 +252,7 @@ std::vector<server_decision_question> server_decision_context::parse_questions(c
question.id = id;
question.instructions = instructions;
const std::string type_name = json_value(q, "type", std::string());
const std::string type_name = json_value(q, "type", std::string(is_h2o ? "choice" : ""));
const json criteria = q.contains("criteria") ? q.at("criteria") : json();
if (type_name == "choice") {
@@ -237,7 +276,11 @@ std::vector<server_decision_question> server_decision_context::parse_questions(c
}
} else if (type_name == "score") {
question.type = SERVER_DECISION_QUESTION_SCORE;
if (!criteria.is_array() || criteria.size() < 2 || criteria.size() > 10) {
// h2o-lightning: from 1 level, the maximum is n_options_max
if (is_h2o && (!criteria.is_array() || criteria.empty())) {
throw err("\"criteria\" must be a non-empty array");
}
if (!is_h2o && (!criteria.is_array() || criteria.size() < 2 || criteria.size() > 10)) {
throw err("\"criteria\" must be an array of 2 to 10 levels");
}
for (size_t i = 0; i < criteria.size(); i++) {
@@ -262,8 +305,8 @@ std::vector<server_decision_question> server_decision_context::parse_questions(c
json description;
if (criteria.is_object() && criteria.contains(key)) {
description = criteria.at(key);
} else if (criteria.is_object() && type == COMMON_DECISION_TYPE_LFM2_D1_OMNI) {
// lfm2-d1-omni also reads the descriptions under "no" and "yes"
} else if (criteria.is_object() && (type == COMMON_DECISION_TYPE_LFM2_D1_OMNI || is_h2o)) {
// lfm2-d1-omni and h2o-lightning also read the descriptions under "no" and "yes"
const char * alias = std::string(key) == "true" ? "yes" : "no";
description = criteria.contains(alias) ? criteria.at(alias) : json();
}
@@ -506,6 +549,280 @@ static json decision_d1omni_escape(const json & val) {
return val;
}
//
// h2o-lightning: the texts are rendered the way the reference shim renders them with Python (json.dumps, str.lstrip)
//
// one code point of UTF-8 text at s[i], i moves past it; an invalid byte is read as is
static uint32_t decision_utf8_next(const std::string & s, size_t & i) {
const unsigned char c = s[i];
size_t n = c < 0x80 ? 1 : (c >> 5) == 0x6 ? 2 : (c >> 4) == 0xe ? 3 : (c >> 3) == 0x1e ? 4 : 1;
if (i + n > s.size()) {
n = 1;
}
uint32_t cp = n == 1 ? c : n == 2 ? (c & 0x1f) : n == 3 ? (c & 0x0f) : (c & 0x07);
for (size_t k = 1; k < n; k++) {
cp = (cp << 6) | (s[i + k] & 0x3f);
}
i += n;
return cp;
}
// str.isspace() of Python
static bool decision_py_isspace(uint32_t cp) {
return (cp >= 0x09 && cp <= 0x0d) || (cp >= 0x1c && cp <= 0x20) || cp == 0x85 || cp == 0xa0 || cp == 0x1680 ||
(cp >= 0x2000 && cp <= 0x200a) || cp == 0x2028 || cp == 0x2029 || cp == 0x202f || cp == 0x205f || cp == 0x3000;
}
static std::string decision_py_rstrip(const std::string & s) {
size_t end = 0;
for (size_t i = 0; i < s.size();) {
if (!decision_py_isspace(decision_utf8_next(s, i))) {
end = i;
}
}
return s.substr(0, end);
}
static std::string decision_py_lstrip(const std::string & s) {
size_t i = 0;
while (i < s.size()) {
size_t next = i;
if (!decision_py_isspace(decision_utf8_next(s, next))) {
break;
}
i = next;
}
return s.substr(i);
}
// a code point as JSON escapes, a surrogate pair above the BMP
static std::string decision_py_escape_cp(uint32_t cp) {
if (cp < 0x10000) {
return string_format("\\u%04x", cp);
}
cp -= 0x10000;
return string_format("\\u%04x\\u%04x", 0xd800 + (cp >> 10), 0xdc00 + (cp & 0x3ff));
}
// repr() of a Python float: the shortest digits that read back the same, in exponent form below 1e-4 and from 1e16
static std::string decision_py_float(double v) {
char buf[64];
// the fewest digits that read back the same
for (int prec = 0; prec < 17; prec++) {
snprintf(buf, sizeof(buf), "%.*e", prec, v);
if (std::strtod(buf, nullptr) == v) {
break;
}
}
const std::string sci(buf); // for example "-1.25e+03"
const size_t pos_e = sci.find('e');
const int exp = std::atoi(sci.c_str() + pos_e + 1);
std::string sign;
std::string digits;
for (size_t i = 0; i < pos_e; i++) {
if (sci[i] == '-') {
sign = "-";
} else if (sci[i] != '.') {
digits += sci[i];
}
}
const int n = (int) digits.size();
if (exp < -4 || exp >= 16) {
return sign + digits.substr(0, 1) + (n > 1 ? "." + digits.substr(1) : "") + string_format("e%c%02d", exp < 0 ? '-' : '+', std::abs(exp));
}
if (exp >= n - 1) {
return sign + digits + std::string(exp - (n - 1), '0') + ".0";
}
if (exp >= 0) {
return sign + digits.substr(0, exp + 1) + "." + digits.substr(exp + 1);
}
return sign + "0." + std::string(-exp - 1, '0') + digits;
}
static void decision_py_str(std::string & out, const std::string & s, bool ensure_ascii) {
out += '"';
for (size_t i = 0; i < s.size();) {
const unsigned char c = s[i];
switch (c) {
case '"': out += "\\\""; i++; continue;
case '\\': out += "\\\\"; i++; continue;
case '\b': out += "\\b"; i++; continue;
case '\f': out += "\\f"; i++; continue;
case '\n': out += "\\n"; i++; continue;
case '\r': out += "\\r"; i++; continue;
case '\t': out += "\\t"; i++; continue;
default: break;
}
if (c < 0x20 || (ensure_ascii && c == 0x7f)) {
out += string_format("\\u%04x", c);
i++;
} else if (c < 0x80 || !ensure_ascii) {
out += (char) c;
i++;
} else {
out += decision_py_escape_cp(decision_utf8_next(s, i));
}
}
out += '"';
}
// json.dumps() of Python: indent < 0 is no indent, keys stay in their order
static void decision_py_dump(std::string & out, const json & val, int indent, int level, const std::string & item_sep, const std::string & key_sep, bool ensure_ascii) {
const auto newline = [&](int lvl) {
return indent < 0 ? std::string() : "\n" + std::string(indent * lvl, ' ');
};
if (val.is_null()) {
out += "null";
} else if (val.is_boolean()) {
out += val.get<bool>() ? "true" : "false";
} else if (val.is_number_float()) {
out += decision_py_float(val.get<double>());
} else if (val.is_number()) {
out += val.dump();
} else if (val.is_string()) {
decision_py_str(out, val.get<std::string>(), ensure_ascii);
} else if (val.is_array()) {
if (val.empty()) {
out += "[]";
return;
}
out += "[" + newline(level + 1);
for (size_t i = 0; i < val.size(); i++) {
if (i > 0) {
out += item_sep + newline(level + 1);
}
decision_py_dump(out, val.at(i), indent, level + 1, item_sep, key_sep, ensure_ascii);
}
out += newline(level) + "]";
} else if (val.is_object()) {
if (val.empty()) {
out += "{}";
return;
}
out += "{" + newline(level + 1);
bool first = true;
for (const auto & [key, item] : val.items()) {
if (!first) {
out += item_sep + newline(level + 1);
}
first = false;
decision_py_str(out, key, ensure_ascii);
out += key_sep;
decision_py_dump(out, item, indent, level + 1, item_sep, key_sep, ensure_ascii);
}
out += newline(level) + "}";
}
}
static std::string decision_py_dumps(const json & val, int indent, const std::string & item_sep, const std::string & key_sep, bool ensure_ascii) {
std::string out;
decision_py_dump(out, val, indent, 0, item_sep, key_sep, ensure_ascii);
return out;
}
// a state longer than max_state_tokens keeps its head and its tail around a "..." line
std::string server_decision_context::h2o_cut_state(const std::string & text) const {
// a byte-level BPE never has more tokens than bytes
const size_t max_state_tokens = DECISION_H2O_MAX_STATE_TOKENS;
if (text.size() <= max_state_tokens) {
return text;
}
const llama_tokens tokens = common_tokenize(vocab, text, false, true);
if (tokens.size() <= max_state_tokens) {
return text;
}
// each half is the text of its token pieces, a cut inside a character gives U+FFFD as in a JSON response of the server
const size_t half = max_state_tokens / 2;
auto text_of = [&](llama_tokens::const_iterator begin, llama_tokens::const_iterator end) {
std::string out;
for (auto it = begin; it != end; ++it) {
out += common_token_to_piece(vocab, *it, true);
}
return json::parse(json::make(out).dump_safe()).get<std::string>();
};
return text_of(tokens.begin(), tokens.begin() + half) + "\n...\n" + text_of(tokens.end() - half, tokens.end());
}
// the special token texts written in the input are quoted with JSON escapes, so they stay text
std::string server_decision_context::h2o_quote(const std::string & text) const {
std::string out;
for (size_t i = 0; i < text.size();) {
bool is_quoted = false;
if (quoted_first.find(text[i]) == std::string::npos) {
out += text[i++];
continue;
}
for (const auto & spelling : quoted_spellings) {
if (text.compare(i, spelling.size(), spelling) == 0) {
std::string quoted = spelling;
string_replace_all(quoted, "<", "\\u003c");
string_replace_all(quoted, ">", "\\u003e");
out += quoted;
i += spelling.size();
is_quoted = true;
break;
}
}
if (!is_quoted) {
out += text[i++];
}
}
return out;
}
json server_decision_context::prepare_state(const json & state) const {
if (type != COMMON_DECISION_TYPE_H2O_LIGHTNING) {
return state;
}
// done once per request: compact JSON, the cut, the quoting
return h2o_quote(h2o_cut_state(state.is_string() ? state.get<std::string>() : decision_py_dumps(state, -1, ",", ":", false)));
}
json server_decision_context::render_h2o(const json & state, const server_decision_question & question) const {
// the instructions are JSON with an indent of 1, the descriptions are default JSON, the state is from prepare_state()
const std::string instructions = decision_py_lstrip(question.instructions.is_string() ?
question.instructions.get<std::string>() : decision_py_dumps(question.instructions, 1, ",", ": ", true));
json options = json::array();
for (size_t i = 0; i < question.options.size(); i++) {
const auto & opt = question.options[i];
json description; // null if not provided, the template has a default, except a score level that is shown as JSON null
if (question.type == SERVER_DECISION_QUESTION_SCORE && opt.description.is_null()) {
description = "null";
} else if (!opt.description.is_null()) {
description = h2o_quote(opt.description.is_string() ? opt.description.get<std::string>() : decision_py_dumps(opt.description, -1, ", ", ": ", false));
}
const std::string key = h2o_quote(opt.key);
// the user turn is stripped like Python's str.strip(), the template only trims ASCII whitespace
// so the text that ends the turn is stripped here, a space keeps a description that is only whitespace
if (i + 1 == question.options.size()) {
const bool is_key = question.type == SERVER_DECISION_QUESTION_CHOICE && description.is_null();
if (is_key || description.is_string()) {
const std::string text = is_key ? key : description.get<std::string>();
const std::string stripped = decision_py_rstrip(text);
if (stripped != text) {
description = stripped.empty() ? " " : stripped;
}
}
}
json option = json::object();
option["label"] = label_texts[i];
option["key"] = key;
option["description"] = description;
options.push_back(option);
}
json inp = json::object();
inp["type"] = decision_question_type_name(question.type);
inp["state"] = state;
inp["instructions"] = h2o_quote(instructions);
inp["options"] = options;
return inp;
}
// given to the lfm2-d1-omni template: text between the pieces of the prompt, and at the start of the pieces that are cut to a token budget
static const std::string D1OMNI_MARKER = "<<d1omni:";
static const std::string D1OMNI_SEP = "<<d1omni:sep>>";
@@ -672,7 +989,8 @@ std::string server_decision_context::render(
size_t n_images,
bool is_audio) const {
// the template is given raw JSON values, it serializes the ones that are not strings
json inp = json{
// h2o-lightning: the template is given texts
json inp = type == COMMON_DECISION_TYPE_H2O_LIGHTNING ? render_h2o(state, question) : json{
{"id", question.id},
{"type", decision_question_type_name(question.type)},
{"instructions", question.instructions},
@@ -756,7 +1074,8 @@ void server_decision_context::fill_task(
const std::string prompt = render(state, questions, question, variant, files.size());
if (type == COMMON_DECISION_TYPE_OPENJEV || type == COMMON_DECISION_TYPE_GRANITE_SWITCH || type == COMMON_DECISION_TYPE_LEV || type == COMMON_DECISION_TYPE_NIMBLE || type == COMMON_DECISION_TYPE_PPLX_DECIDER || type == COMMON_DECISION_TYPE_VLLM_D3) {
if (type == COMMON_DECISION_TYPE_OPENJEV || type == COMMON_DECISION_TYPE_GRANITE_SWITCH || type == COMMON_DECISION_TYPE_LEV || type == COMMON_DECISION_TYPE_NIMBLE || type == COMMON_DECISION_TYPE_PPLX_DECIDER || type == COMMON_DECISION_TYPE_VLLM_D3 ||
type == COMMON_DECISION_TYPE_H2O_LIGHTNING) {
// lev reads the ratings of a noul question at its first labels, not at the digits
task.decision.labels.assign(labels.begin(), labels.begin() + n_outputs(question));
}
@@ -769,7 +1088,8 @@ void server_decision_context::fill_task(
task.decision.label_groups.push_back(group.size());
}
}
if (type == COMMON_DECISION_TYPE_OPENJEV || type == COMMON_DECISION_TYPE_LEV || type == COMMON_DECISION_TYPE_NIMBLE || type == COMMON_DECISION_TYPE_PPLX_DECIDER || type == COMMON_DECISION_TYPE_VLLM_D3 || type == COMMON_DECISION_TYPE_LFM2_D1) {
if (type == COMMON_DECISION_TYPE_OPENJEV || type == COMMON_DECISION_TYPE_LEV || type == COMMON_DECISION_TYPE_NIMBLE || type == COMMON_DECISION_TYPE_PPLX_DECIDER || type == COMMON_DECISION_TYPE_VLLM_D3 || type == COMMON_DECISION_TYPE_LFM2_D1 ||
type == COMMON_DECISION_TYPE_H2O_LIGHTNING) {
if (!files.empty()) {
task.tokens = process_mtmd_prompt(mctx, prompt, files, init_opt);
return;
@@ -1181,7 +1501,12 @@ json server_decision_context::format_answer(const server_decision_question & que
// softmax over the outputs of each variant, then the average of the variants
// lfm2-d1-omni: image and audio answers are not calibrated
const float temperature = has_media && type == COMMON_DECISION_TYPE_LFM2_D1_OMNI ? 1.0f : get_temperature(question);
float temperature = has_media && type == COMMON_DECISION_TYPE_LFM2_D1_OMNI ? 1.0f : get_temperature(question);
// a model can have its own temperature for requests with media
const auto it_image = temperatures.find("image");
if (has_media && it_image != temperatures.end()) {
temperature = it_image->second;
}
std::vector<double> probs(n, 0.0);
for (size_t v = 0; v < scores.size(); v++) {
const auto & s = scores[v];
+14 -1
View File
@@ -46,6 +46,7 @@ struct server_decision_context {
case COMMON_DECISION_TYPE_PPLX_DECIDER:
case COMMON_DECISION_TYPE_VLLM_DECISION2:
case COMMON_DECISION_TYPE_LFM2_D1:
case COMMON_DECISION_TYPE_H2O_LIGHTNING:
return true;
default:
return false;
@@ -66,6 +67,7 @@ struct server_decision_context {
case COMMON_DECISION_TYPE_VLLM_D3:
case COMMON_DECISION_TYPE_LFM2_D1:
case COMMON_DECISION_TYPE_LFM2_D1_OMNI:
case COMMON_DECISION_TYPE_H2O_LIGHTNING:
return true;
default:
return false;
@@ -75,6 +77,9 @@ struct server_decision_context {
// throw std::invalid_argument on bad input
std::vector<server_decision_question> parse_questions(const json & body) const;
// the state as the model reads it, done once per request; returns the state for most models
json prepare_state(const json & state) const;
// returns the state without its images, they are appended to files in order
// images come from "files" (alias "images") and from the image_url and input_audio parts of a state made of chat messages
// an image can be an audio clip if the model supports it
@@ -121,7 +126,7 @@ private:
bool strict_payload = false; // state, instructions and descriptions must be text, an object or an array
bool canonical_legend = false; // non-string score descriptions are returned as canonical JSON text
// OPENJEV, LEV, NIMBLE, PPLX_DECIDER, VLLM_D3
// OPENJEV, LEV, NIMBLE, PPLX_DECIDER, VLLM_D3, H2O_LIGHTNING
std::vector<llama_token> labels;
std::vector<std::string> label_texts; // only if the label of an option is given to the template
@@ -132,6 +137,10 @@ private:
size_t max_head_tokens = 0; // question + options
size_t max_option_tokens = 48;
// H2O_LIGHTNING
std::vector<std::string> quoted_spellings; // texts of the special tokens, quoted in the input, longest first
std::string quoted_first; // the first bytes of these texts
std::string render(
const json & state,
const std::vector<server_decision_question> & questions,
@@ -143,6 +152,10 @@ private:
size_t n_outputs(const server_decision_question & question) const;
// LFM2_D1: label text and tokens of each option
void d1_labels(const server_decision_question & question, std::vector<std::string> & texts, std::vector<llama_tokens> & groups) const;
// H2O_LIGHTNING: the input of the template, its texts are rendered as the reference shim does
json render_h2o(const json & state, const server_decision_question & question) const;
std::string h2o_cut_state(const std::string & text) const;
std::string h2o_quote(const std::string & text) const;
void fill_task_laya(llama_tokens & tokens, const server_decision_question & question, server_task & task) const;
void fill_task_d1omni(
const json & state,
+51
View File
@@ -11,6 +11,7 @@
#include <memory>
#include <string>
#include <thread>
#include <mutex>
//
// HTTP implementation using cpp-httplib
@@ -744,6 +745,56 @@ void server_http_context::post(const std::string & path, const server_http_conte
}
}
void server_http_context::ws(const std::string & path, const server_http_context::ws_handler_t & handler) const {
const std::string full_path = path_prefix + path;
auto callback = [handler](const httplib::Request & req, httplib::ws::WebSocket & sock) {
struct conn_impl : server_http_context::ws_conn {
httplib::ws::WebSocket & sock;
std::mutex mutex_send;
conn_impl(httplib::ws::WebSocket & sock) : sock(sock) {}
int read(std::string & msg) override {
switch (sock.read(msg)) {
case httplib::ws::Text: return 1;
case httplib::ws::Binary: return 2;
default: return 0;
}
}
bool send(const std::string & msg) override {
std::lock_guard<std::mutex> lock(mutex_send);
return sock.send(msg);
}
bool send_binary(const std::string & msg) override {
std::lock_guard<std::mutex> lock(mutex_send);
return sock.send(msg.data(), msg.size());
}
bool is_open() const override {
return sock.is_open();
}
void close() override {
sock.close();
}
};
conn_impl conn(sock);
const std::function<bool()> should_stop = [&sock]() {
return !sock.is_open();
};
server_http_req request{
get_params(req),
get_headers(req),
req.path,
build_query_string(req),
req.body,
{},
should_stop
};
handler(request, conn);
};
for (const auto & srv : pimpl->servers) {
srv->WebSocket(full_path, callback);
}
}
void server_http_context::del(const std::string & path, const server_http_context::handler_t & handler) const {
handlers.emplace(path, handler);
auto callback = [handler](const httplib::Request & req, httplib::Response & res) {
+15
View File
@@ -74,6 +74,20 @@ struct server_http_context {
using handler_t = std::function<server_http_res_ptr(const server_http_req & req)>;
mutable std::unordered_map<std::string, handler_t> handlers;
// websocket connection, see ws()
struct ws_conn {
// blocking read, returns 1 for a text message, 2 for a binary message, 0 when the connection is closed
virtual int read(std::string & msg) = 0;
// thread-safe, can be called while another thread is in read()
virtual bool send(const std::string & msg) = 0;
virtual bool send_binary(const std::string & msg) = 0;
virtual bool is_open() const = 0;
virtual void close() = 0;
virtual ~ws_conn() = default;
};
// the handler runs on its own HTTP thread for the whole lifetime of the connection
using ws_handler_t = std::function<void(const server_http_req & req, ws_conn & conn)>;
std::string path_prefix;
int port = 9931;
bool is_ssl = false;
@@ -89,6 +103,7 @@ struct server_http_context {
void get(const std::string & path, const handler_t & handler) const;
void post(const std::string & path, const handler_t & handler) const;
void del(const std::string & path, const handler_t & handler) const;
void ws(const std::string & path, const ws_handler_t & handler) const;
// Register the Google Cloud Platform (Vertex AI) compat (AIP_PREDICT_ROUTE env var, or /predict)
// Must be called AFTER all other API routes are registered
+105 -8
View File
@@ -521,27 +521,31 @@ void server_model_meta::update_caps(const common_params & base) {
return;
}
// read the output modalities and the trained context from the GGUF metadata
std::vector<std::string> output_modalities = {"text"};
if (!params.model.path.empty()) {
const common_gguf_info info = common_get_gguf_info(params.model.path);
output_modalities = server_model_output_modalities(info.decision_type);
n_ctx_train = info.n_ctx_train;
}
bool inp_image = false;
bool inp_audio = false;
bool gen_audio = false;
try {
if (!params.no_mmproj && !params.mmproj.path.empty()) {
mtmd_caps caps = mtmd_get_cap_from_file(params.mmproj.path.c_str());
inp_image = caps.inp_vision;
inp_audio = caps.inp_audio;
gen_audio = caps.gen_audio;
}
} catch (const std::exception & e) {
LOG_WRN("failed to read the multimodal capabilities of '%s': %s\n", name.c_str(), e.what());
// keep the output modalities from the GGUF metadata
}
// read the output modalities and the trained context from the GGUF metadata
std::vector<std::string> output_modalities = {"text"};
if (!params.model.path.empty()) {
const common_gguf_info info = common_get_gguf_info(params.model.path);
// same rule as mtmd_helper_model_can_chat()
const bool can_chat = !gen_audio || info.has_chat_tmpl;
output_modalities = server_model_output_modalities(info.decision_type, can_chat, gen_audio);
n_ctx_train = info.n_ctx_train;
}
// offline discovery cannot see video; a loaded model reports it
architecture = server_model_architecture_json(inp_image, inp_audio, false, output_modalities);
}
@@ -1899,6 +1903,81 @@ server_http_res_ptr server_models::proxy_request(const server_http_req & req, co
return proxy;
}
void server_models::proxy_ws(const server_http_req & req, server_http_context::ws_conn & conn, const std::string & name) {
auto send_error = [&conn](const std::string & message) {
conn.send(safe_json_to_str(json {
{"type", "error"},
{"error", {{"type", "server_error"}, {"code", nullptr}, {"message", message}, {"param", nullptr}}},
}));
conn.close();
};
auto meta = get_meta(name);
if (!meta.has_value() || !meta->is_running()) {
send_error("model name=" + name + " is not running");
return;
}
{
std::unique_lock<std::mutex> lk(mutex);
mapping[name].meta.last_used = ggml_time_ms();
mapping[name].req_count++;
}
SRV_INF("proxying websocket to model %s on port %d\n", name.c_str(), meta->port);
std::string url = std::string("ws://") + CHILD_ADDR + ":" + std::to_string(meta->port) + req.path;
if (!req.query_string.empty()) {
url += '?' + req.query_string;
}
// the client library adds its own upgrade headers
httplib::Headers headers;
for (const auto & [key, value] : req.headers) {
std::string lower = key;
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
if (lower == "host" || lower == "connection" || lower == "upgrade" || lower.compare(0, 14, "sec-websocket-") == 0) {
continue;
}
headers.emplace(key, value);
}
{
httplib::ws::WebSocketClient upstream(url, headers);
if (!upstream.connect()) {
send_error("failed to connect to model name=" + name);
} else {
// client to child
std::thread pump([&]() {
std::string msg;
while (true) {
const int res = conn.read(msg);
if (res == 0 || !(res == 1 ? upstream.send(msg) : upstream.send(msg.data(), msg.size()))) {
break;
}
}
upstream.close();
});
// child to client
std::string msg;
while (true) {
const auto res = upstream.read(msg);
if (res == httplib::ws::Fail || !(res == httplib::ws::Text ? conn.send(msg) : conn.send_binary(msg))) {
break;
}
}
conn.close();
pump.join();
}
}
std::unique_lock<std::mutex> lk(mutex);
auto it = mapping.find(name);
if (it != mapping.end() && it->second.req_count > 0) {
it->second.req_count--;
if (it->second.req_count == 0) {
sched->tick(lk);
}
}
}
void server_models::handle_child_state(const std::string & name, const std::string & raw_input) {
server_state state;
json payload;
@@ -2277,6 +2356,24 @@ void server_models_routes::init_routes() {
return models.proxy_request(req, method, name, false);
};
this->proxy_ws = [this](const server_http_req & req, server_http_context::ws_conn & conn) {
std::string name = req.get_param("model");
bool autoload = is_autoload(params, req);
auto error_res = std::make_unique<server_http_res>();
if (!router_validate_model(name, models, autoload, error_res)) {
// error_res holds {"error": {...}}, send it as a websocket error event
json err = json::parse(error_res->data);
err["type"] = "error";
conn.send(safe_json_to_str(err));
conn.close();
return;
}
if (autoload) {
models.ensure_model_ready(name, req.should_stop);
}
models.proxy_ws(req, conn, name);
};
this->proxy_post = [this](const server_http_req & req) {
std::string method = "POST";
json body = json::parse(req.body);
+4
View File
@@ -366,6 +366,8 @@ public:
// proxy an HTTP request to the model instance
server_http_res_ptr proxy_request(const server_http_req & req, const std::string & method, const std::string & name, bool update_last_used, bool detached = false);
// forward a websocket connection to the child, returns when either side closes
void proxy_ws(const server_http_req & req, server_http_context::ws_conn & conn, const std::string & name);
// handle message sent from server_child::notify_to_router()
// raw input must starts with CMD_CHILD_TO_ROUTER_STATE, followed by a JSON string
@@ -433,6 +435,8 @@ struct server_models_routes {
server_http_context::handler_t get_router_props;
server_http_context::handler_t proxy_get;
server_http_context::handler_t proxy_post;
server_http_context::ws_handler_t proxy_ws;
server_http_context::handler_t get_router_models;
server_http_context::handler_t post_router_models_load;
server_http_context::handler_t post_router_models_unload;
+52
View File
@@ -511,6 +511,58 @@ task_params eval_llama_cmpl_schema(
return params;
}
//
// TTS schema
//
std::vector<std::unique_ptr<field>> make_tts_schema(server_task & task) {
std::vector<std::unique_ptr<field>> fields;
auto add = [&](field * f) {
fields.emplace_back(f);
};
add((new field_num("top_k", task.tts_inp.data.top_k))
->set_limits(0, INT32_MAX)
->set_desc("Top-k for the acoustic code predictor, 0 to use the model default"));
add((new field_num("top_p", task.tts_inp.data.top_p))
->set_limits(0.0f, 1.0f)
->set_desc("Top-p for the acoustic code predictor, 0.0 to use the model default"));
add((new field_num("seed", task.tts_inp.data.seed))
->set_desc("RNG seed for the backbone sampler and the codec/vocoder (-1 = random)"));
add((new field_num("n_predict", task.params.n_predict))
->set_hard_limits(-1, INT32_MAX)
->add_alias("max_tokens")
->set_desc("Max number of audio frames to generate"));
add((new field_num("repeat_penalty", task.params.sampling.penalty_repeat))
->set_desc("Repetition penalty applied to the backbone sampler over the whole generation"));
add((new field_bool("stream", task.params.stream))
->set_desc("Stream the audio as it becomes available instead of waiting for the full generation"));
return fields;
}
void eval_tts_schema(const common_params & params_base, server_task & task, const json & data) {
// baseline defaults, individual requests can override them
// TODO @ngxson : change the default values based on model
task.params.sampling = params_base.sampling;
task.params.sampling.penalty_repeat = 1.05f;
task.params.n_predict = -1;
task.tts_inp.data.top_k = 0;
task.tts_inp.data.top_p = 0.0f;
task.tts_inp.data.seed = params_base.sampling.seed;
field_eval_context ctx(task.params);
for (const auto & f : make_tts_schema(task)) {
f->eval(ctx, data);
}
}
//
// eval() implementations
//
+8
View File
@@ -101,4 +101,12 @@ task_params eval_llama_cmpl_schema(
const std::vector<llama_logit_bias> & logit_bias_eog,
const json & data);
std::vector<std::unique_ptr<field>> make_tts_schema(server_task & task);
// evaluates the /tts request params into task.params and task.tts_inp.data
void eval_tts_schema(
const common_params & params_base,
server_task & task,
const json & data);
} // namespace server_schema
+11
View File
@@ -1508,6 +1508,17 @@ json server_task_result_rerank::to_json() {
};
}
//
// server_task_result_tts
//
json server_task_result_tts::to_json() {
return json {
{"sample_rate", sample_rate},
{"n_bytes", audio.size()},
{"final", final},
};
}
//
// server_task_result_decision
//
+30 -2
View File
@@ -10,6 +10,7 @@
// TODO: prevent including the whole server-common.h as we only use server_tokens
#include "server-common.h"
#include "mtmd-helper.h"
enum server_task_type {
@@ -18,6 +19,7 @@ enum server_task_type {
SERVER_TASK_TYPE_RERANK,
SERVER_TASK_TYPE_DECISION,
SERVER_TASK_TYPE_INFILL,
SERVER_TASK_TYPE_TRANSCRIBE_RT,
SERVER_TASK_TYPE_CANCEL,
SERVER_TASK_TYPE_CONTROL,
SERVER_TASK_TYPE_NEXT_RESPONSE,
@@ -28,6 +30,7 @@ enum server_task_type {
SERVER_TASK_TYPE_SLOT_ERASE,
SERVER_TASK_TYPE_GET_LORA,
SERVER_TASK_TYPE_SET_LORA,
SERVER_TASK_TYPE_TTS,
};
// TODO: change this to more generic "response_format" to replace the "format_response_*" in server-common
@@ -48,6 +51,14 @@ enum stop_type {
STOP_TYPE_LIMIT,
};
enum server_control_action {
SERVER_CONTROL_ACTION_NONE,
SERVER_CONTROL_ACTION_REASONING_END,
SERVER_CONTROL_ACTION_AUDIO_APPEND, // push control_audio into the slot's audio stream
SERVER_CONTROL_ACTION_AUDIO_COMMIT, // flush the audio stream, more audio can follow
SERVER_CONTROL_ACTION_AUDIO_END, // flush the audio stream and end it
};
struct task_params {
bool stream = false;
bool include_usage = false;
@@ -86,8 +97,9 @@ struct task_params {
std::string oaicompat_cmpl_id;
// realtime control (SERVER_TASK_TYPE_CONTROL)
std::string control_action;
std::string control_cmpl_id;
server_control_action control_action = SERVER_CONTROL_ACTION_NONE;
std::string control_cmpl_id;
std::vector<float> control_audio; // PCM F32 samples for SERVER_CONTROL_ACTION_AUDIO_APPEND
// reported in generation_settings, parsing itself is owned by the chat session
common_chat_format chat_format = COMMON_CHAT_FORMAT_CONTENT_ONLY;
@@ -209,6 +221,9 @@ struct server_task {
// used by SERVER_TASK_TYPE_SET_LORA
std::map<int, float> set_lora; // mapping adapter ID -> scale
// used by SERVER_TASK_TYPE_TTS
mtmd_helper::gen_audio::inp tts_inp;
server_task() = default;
server_task(server_task_type type) : type(type) {}
@@ -233,6 +248,7 @@ struct server_task {
switch (type) {
case SERVER_TASK_TYPE_COMPLETION:
case SERVER_TASK_TYPE_INFILL:
case SERVER_TASK_TYPE_TRANSCRIBE_RT:
return true;
case SERVER_TASK_TYPE_DECISION:
return !decision.labels.empty();
@@ -245,6 +261,8 @@ struct server_task {
switch (type) {
case SERVER_TASK_TYPE_COMPLETION:
case SERVER_TASK_TYPE_INFILL:
case SERVER_TASK_TYPE_TTS:
case SERVER_TASK_TYPE_TRANSCRIBE_RT:
return true;
default:
return false;
@@ -514,6 +532,16 @@ struct server_task_result_embd : server_task_result {
json to_json_oaicompat();
};
struct server_task_result_tts : server_task_result {
std::string audio; // raw bytes for this chunk (WAV or PCM, per request's out_type)
int32_t sample_rate = 0;
bool final = false; // true for the last chunk of a request
virtual bool is_stop() override { return final; }
virtual json to_json() override;
};
struct server_task_result_rerank : server_task_result {
float score = -1e6;
+4
View File
@@ -247,6 +247,8 @@ int llama_server(common_params & params, int argc, char ** argv, server_child &
routes.post_control = models_routes->proxy_post;
routes.post_responses_oai = models_routes->proxy_post;
routes.post_transcriptions_oai = models_routes->proxy_post;
routes.post_tts = models_routes->proxy_post;
routes.ws_realtime = models_routes->proxy_ws;
routes.post_anthropic_messages = models_routes->proxy_post;
routes.post_anthropic_count_tokens = models_routes->proxy_post;
routes.post_infill = models_routes->proxy_post;
@@ -292,6 +294,8 @@ int llama_server(common_params & params, int argc, char ** argv, server_child &
ctx_http.post("/responses", ex_wrapper(routes.post_responses_oai));
ctx_http.post("/v1/audio/transcriptions", ex_wrapper(routes.post_transcriptions_oai));
ctx_http.post("/audio/transcriptions", ex_wrapper(routes.post_transcriptions_oai));
ctx_http.post("/tts", ex_wrapper(routes.post_tts));
ctx_http.ws ("/v1/realtime", routes.ws_realtime);
ctx_http.post("/v1/messages", ex_wrapper(routes.post_anthropic_messages)); // anthropic messages API
ctx_http.post("/infill", ex_wrapper(routes.post_infill));
ctx_http.post("/embedding", ex_wrapper(routes.post_embeddings)); // legacy
@@ -587,6 +587,10 @@ def test_chat_completions_multiple_choices():
for choice in res.body["choices"]:
assert "assistant" == choice["message"]["role"]
assert choice["finish_reason"] == "length"
# each choice generates max_tokens tokens, usage counts them all
# ref: https://github.com/ggml-org/llama.cpp/issues/29451
assert res.body["usage"]["completion_tokens"] == 16
assert res.body["usage"]["total_tokens"] == res.body["usage"]["prompt_tokens"] + 16
def test_chat_completions_token_count():
@@ -229,3 +229,39 @@ def test_multi_requests_parallel(n_slots: int, n_requests: int):
for res in results:
assert res.status_code == 200
assert match_regex("(wise|kind|owl|answer)+", res.body["content"])
def test_draft_token_probs():
# tokens accepted from the draft must carry the target model's probabilities,
# same as without speculative decoding (#24271)
global server
request = {
"prompt": "I believe the meaning of life is",
"temperature": 0.0,
"top_k": 1,
"n_predict": 16,
"n_probs": 4,
}
server.model_draft = None # disable draft model
server.spec_type = None
server.start()
res = server.make_request("POST", "/completion", data=request)
assert res.status_code == 200
probs_no_draft = res.body["completion_probabilities"]
server.stop()
create_server()
server.start()
res = server.make_request("POST", "/completion", data=request)
assert res.status_code == 200
assert res.body["timings"]["draft_n"] > 0
probs_draft = res.body["completion_probabilities"]
assert len(probs_draft) == len(probs_no_draft)
for tok_draft, tok_no_draft in zip(probs_draft, probs_no_draft):
assert tok_draft["id"] == tok_no_draft["id"]
assert len(tok_draft["top_logprobs"]) == len(tok_no_draft["top_logprobs"])
assert abs(tok_draft["logprob"] - tok_no_draft["logprob"]) < 0.25
# real logprobs are not all exactly 0.0 (prob 1.0)
assert any(tok["logprob"] < -1e-4 for tok in probs_draft)
+2
View File
@@ -12,6 +12,8 @@ Simple usage:
llama-tts -hf ggml-org/Qwen3-TTS-12Hz-1.7B-Base-GGUF -p "Hello world" --output out.wav
```
Note: it's highly recommended to always provide a speaker reference voice (via `--tts-speaker-file`); otherwise, the model's performance may be degraded.
Common params:
- Sampling params such as `--top-k`, `--top-p`, `--temp`, etc.
- `-n <number_of_frames>` limits the output length, e.g. `-n 500`. Note that how many milliseconds each frame represents varies by model
+3 -3
View File
@@ -111,8 +111,9 @@ int main(int argc, char ** argv) {
speaker_bitmap.reset(wrapper.bitmap);
}
mtmd_helper::gen_audio gen(lctx, mctx.get());
mtmd_helper_gen_audio_inp inp{};
mtmd_helper::gen_audio gen;
gen.init(lctx, mctx.get());
mtmd_helper_gen_audio_inp inp = mtmd_helper_gen_audio_inp_default();
inp.seq_id = 0;
inp.prompt = params.prompt.c_str();
inp.prompt_len = params.prompt.size();
@@ -121,7 +122,6 @@ int main(int argc, char ** argv) {
inp.top_k = params.sampling.top_k;
inp.top_p = params.sampling.top_p;
inp.seed = params.sampling.seed;
inp.out_type = MTMD_HELPER_GEN_AUDIO_OUTTYPE_WAV;
//
// stage 1: process prompt via backbone model, generate semantic representation