Compare commits

...
17 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
70 changed files with 3644 additions and 1044 deletions
+3
View File
@@ -1235,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;
}
+1
View File
@@ -985,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);
+3
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",
@@ -358,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",
+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) {
+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;
+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 -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");
+1 -1
View File
@@ -1110,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}},
+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]);
+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
+77 -2
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}) {
+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;
}
+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
+20 -3
View File
@@ -1625,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:
@@ -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
+14 -2
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)),
});
}
+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);
+74 -2
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:*
@@ -1992,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
@@ -2027,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
{
+20 -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:
@@ -216,13 +217,27 @@ std::vector<std::string> server_model_output_modalities(common_decision_type dec
case COMMON_DECISION_TYPE_LFM2_D1:
case COMMON_DECISION_TYPE_LFM2_D1_OMNI:
case COMMON_DECISION_TYPE_H2O_LIGHTNING:
return {"decisions"};
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(
+663 -18
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;
@@ -1440,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) {
@@ -1921,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 {
@@ -1938,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()));
@@ -1963,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;
@@ -2228,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>();
@@ -2829,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
@@ -2916,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;
@@ -2935,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";
@@ -3254,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;
@@ -3269,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;
}
@@ -3306,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) {
@@ -3368,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)
@@ -3444,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;
@@ -3586,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;
}
@@ -3601,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;
@@ -4826,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),
@@ -4905,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 {
@@ -4998,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
@@ -5611,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));
}
@@ -5686,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;
@@ -5883,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;
+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;
+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():
+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