mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-10-21 12:17:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d454a204ef | ||
|
|
0756e83fc1 | ||
|
|
9febe63d5a | ||
|
|
9b56ccf00d | ||
|
|
0a1ef309cd | ||
|
|
261700b896 | ||
|
|
f849095365 | ||
|
|
824eaecf48 | ||
|
|
eb182d85c0 | ||
|
|
ea9e3446b5 | ||
|
|
0efe8e4c6b | ||
|
|
b531e4c749 | ||
|
|
74b115ca6e | ||
|
|
96caa80059 | ||
|
|
479a52779f | ||
|
|
8baabd53b7 | ||
|
|
493253fe5a | ||
|
|
339f3242c7 | ||
|
|
9a3b91c680 | ||
|
|
5c0b49a23b | ||
|
|
1242be66f6 | ||
|
|
84e66d0321 | ||
|
|
52ffa02745 | ||
|
|
839a572a4c |
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "lexer.h"
|
||||
#include "runtime.h"
|
||||
#include "unicode.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -75,6 +77,24 @@ lexer_result lexer::tokenize(const std::string & source) {
|
||||
}
|
||||
// add escaped char
|
||||
char escaped_char = src[pos++];
|
||||
if (escaped_char == 'x') {
|
||||
if (pos + 1 >= src.size()) {
|
||||
throw lexer_exception("incomplete hexadecimal escape", source, pos);
|
||||
}
|
||||
|
||||
unsigned int value = 0;
|
||||
const char * first = &src[pos];
|
||||
const char * last = first + 2;
|
||||
|
||||
auto [ptr, ec] = std::from_chars(first, last, value, 16);
|
||||
if (ec != std::errc{} || ptr != last) {
|
||||
throw lexer_exception("invalid hexadecimal escape", source, pos);
|
||||
}
|
||||
|
||||
str += common_unicode_cpt_to_utf8(value);
|
||||
pos += 2;
|
||||
continue;
|
||||
}
|
||||
if (escape_chars.find(escaped_char) == escape_chars.end()) {
|
||||
throw lexer_exception(std::string("unknown escape character \\") + escaped_char, source, pos);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -8,10 +8,10 @@ extern "C" {
|
||||
|
||||
#define RPC_PROTO_MAJOR_VERSION 8
|
||||
#define RPC_PROTO_MINOR_VERSION 0
|
||||
#define RPC_PROTO_PATCH_VERSION 0
|
||||
#define RPC_PROTO_PATCH_VERSION 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
static_assert(GGML_OP_COUNT == 101, "GGML_OP_COUNT has changed - update RPC_PROTO_PATCH_VERSION");
|
||||
static_assert(GGML_OP_COUNT == 102, "GGML_OP_COUNT has changed - update RPC_PROTO_PATCH_VERSION");
|
||||
#endif
|
||||
|
||||
#define GGML_RPC_MAX_SERVERS 16
|
||||
|
||||
@@ -585,6 +585,7 @@ extern "C" {
|
||||
GGML_OP_DSV4_HC_COMB,
|
||||
GGML_OP_DSV4_HC_PRE,
|
||||
GGML_OP_DSV4_HC_POST,
|
||||
GGML_OP_TOP_K_MASK,
|
||||
|
||||
GGML_OP_UNARY,
|
||||
|
||||
@@ -2732,6 +2733,26 @@ extern "C" {
|
||||
struct ggml_tensor * post,
|
||||
struct ggml_tensor * comb);
|
||||
|
||||
// top-k sparse attention mask: keep the mask cells of the live picks and the extra cells, set the rest to -inf
|
||||
//
|
||||
// mask: [n_kv, m1, m2, m3] f16 or f32, broadcast to the rows of top_k
|
||||
// top_k: [n_top, ne1, ne2, ne3] i32, picks of each row
|
||||
// score: [n_pick, ne1, ne2, ne3] f32, can be NULL: a pick is live when its score is above -inf, else every pick is live
|
||||
// blocks: [n_blk, n_pick] i32, can be NULL: the member cells of each pick, else a pick is a cell
|
||||
// extra: [n_extra, ne1, ne2, ne3] i32, can be NULL: cells of each row kept without a pick
|
||||
// res: [n_kv, ne1, ne2, ne3] same type as mask
|
||||
//
|
||||
// res[c, i] = mask[c, i % m] when c is a cell of a live pick of row i or an extra cell of row i, else -inf
|
||||
// picks and cells out of range are skipped
|
||||
//
|
||||
GGML_API struct ggml_tensor * ggml_top_k_mask(
|
||||
struct ggml_context * ctx,
|
||||
struct ggml_tensor * mask,
|
||||
struct ggml_tensor * top_k,
|
||||
struct ggml_tensor * score,
|
||||
struct ggml_tensor * blocks,
|
||||
struct ggml_tensor * extra);
|
||||
|
||||
// custom operators
|
||||
|
||||
typedef void (*ggml_custom1_op_t)(struct ggml_tensor * dst , const struct ggml_tensor * a, int ith, int nth, void * userdata);
|
||||
|
||||
@@ -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;
|
||||
@@ -1046,7 +1055,8 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
|
||||
} break;
|
||||
case GGML_OP_DSV4_HC_COMB:
|
||||
case GGML_OP_DSV4_HC_PRE:
|
||||
case GGML_OP_DSV4_HC_POST: {
|
||||
case GGML_OP_DSV4_HC_POST:
|
||||
case GGML_OP_TOP_K_MASK: {
|
||||
split_state = handle_generic(src_ss, /*scalar_only =*/ true);
|
||||
} break;
|
||||
case GGML_OP_UNARY: {
|
||||
|
||||
@@ -2105,6 +2105,10 @@ static void ggml_compute_forward(struct ggml_compute_params * params, struct ggm
|
||||
{
|
||||
ggml_compute_forward_dsv4_hc_post(params, tensor);
|
||||
} break;
|
||||
case GGML_OP_TOP_K_MASK:
|
||||
{
|
||||
ggml_compute_forward_top_k_mask(params, tensor);
|
||||
} break;
|
||||
case GGML_OP_MAP_CUSTOM1:
|
||||
{
|
||||
ggml_compute_forward_map_custom1(params, tensor);
|
||||
@@ -2288,6 +2292,7 @@ static int ggml_get_n_tasks(struct ggml_tensor * node, int n_threads) {
|
||||
case GGML_OP_DSV4_HC_COMB:
|
||||
case GGML_OP_DSV4_HC_PRE:
|
||||
case GGML_OP_DSV4_HC_POST:
|
||||
case GGML_OP_TOP_K_MASK:
|
||||
{
|
||||
n_tasks = n_threads;
|
||||
} break;
|
||||
|
||||
@@ -11736,6 +11736,92 @@ void ggml_compute_forward_dsv4_hc_post(
|
||||
}
|
||||
}
|
||||
|
||||
// ggml_compute_forward_top_k_mask
|
||||
|
||||
void ggml_compute_forward_top_k_mask(
|
||||
const ggml_compute_params * params,
|
||||
ggml_tensor * dst) {
|
||||
const ggml_tensor * mask = dst->src[0];
|
||||
const ggml_tensor * top_k = dst->src[1];
|
||||
const ggml_tensor * score = dst->src[2];
|
||||
const ggml_tensor * blocks = dst->src[3];
|
||||
const ggml_tensor * extra = dst->src[4];
|
||||
|
||||
GGML_ASSERT(dst->type == mask->type);
|
||||
GGML_ASSERT(dst->nb[0] == ggml_type_size(dst->type));
|
||||
|
||||
const int64_t n_kv = mask->ne[0];
|
||||
const int64_t ne1 = dst->ne[1];
|
||||
const int64_t ne2 = dst->ne[2];
|
||||
const int64_t n_rows = ggml_nrows(dst);
|
||||
const int64_t n_top = top_k->ne[0];
|
||||
const int64_t n_blk = blocks ? blocks->ne[0] : 1;
|
||||
const int64_t n_pick = blocks ? blocks->ne[1] : score ? score->ne[0] : n_kv;
|
||||
const int64_t n_extra = extra ? extra->ne[0] : 0;
|
||||
|
||||
const size_t ts = ggml_type_size(dst->type);
|
||||
|
||||
const int ith = params->ith;
|
||||
const int nth = params->nth;
|
||||
|
||||
for (int64_t ir = ith; ir < n_rows; ir += nth) {
|
||||
const int64_t i1 = ir % ne1;
|
||||
const int64_t i2 = (ir / ne1) % ne2;
|
||||
const int64_t i3 = ir / (ne1*ne2);
|
||||
|
||||
const char * mask_row = (const char *) mask->data
|
||||
+ (i1 % mask->ne[1])*mask->nb[1] + (i2 % mask->ne[2])*mask->nb[2] + (i3 % mask->ne[3])*mask->nb[3];
|
||||
char * dst_row = (char *) dst->data + i1*dst->nb[1] + i2*dst->nb[2] + i3*dst->nb[3];
|
||||
|
||||
if (dst->type == GGML_TYPE_F16) {
|
||||
const ggml_fp16_t ninf = GGML_CPU_FP32_TO_FP16(-INFINITY);
|
||||
for (int64_t c = 0; c < n_kv; ++c) {
|
||||
((ggml_fp16_t *) dst_row)[c] = ninf;
|
||||
}
|
||||
} else {
|
||||
for (int64_t c = 0; c < n_kv; ++c) {
|
||||
((float *) dst_row)[c] = -INFINITY;
|
||||
}
|
||||
}
|
||||
|
||||
auto keep = [&](int32_t c) {
|
||||
if (c >= 0 && c < n_kv) {
|
||||
memcpy(dst_row + c*ts, mask_row + c*ts, ts);
|
||||
}
|
||||
};
|
||||
|
||||
const char * top_k_row = (const char *) top_k->data + i1*top_k->nb[1] + i2*top_k->nb[2] + i3*top_k->nb[3];
|
||||
|
||||
for (int64_t j = 0; j < n_top; ++j) {
|
||||
const int32_t p = *(const int32_t *) (top_k_row + j*top_k->nb[0]);
|
||||
if (p < 0 || p >= n_pick) {
|
||||
continue;
|
||||
}
|
||||
if (score) {
|
||||
const float s = *(const float *) ((const char *) score->data
|
||||
+ p*score->nb[0] + i1*score->nb[1] + i2*score->nb[2] + i3*score->nb[3]);
|
||||
if (!(s > -INFINITY)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!blocks) {
|
||||
keep(p);
|
||||
continue;
|
||||
}
|
||||
for (int64_t k = 0; k < n_blk; ++k) {
|
||||
keep(*(const int32_t *) ((const char *) blocks->data + k*blocks->nb[0] + p*blocks->nb[1]));
|
||||
}
|
||||
}
|
||||
|
||||
if (extra) {
|
||||
const char * extra_row = (const char *) extra->data + i1*extra->nb[1] + i2*extra->nb[2] + i3*extra->nb[3];
|
||||
for (int64_t j = 0; j < n_extra; ++j) {
|
||||
keep(*(const int32_t *) (extra_row + j*extra->nb[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ggml_compute_forward_rwkv_wkv7
|
||||
|
||||
static void ggml_compute_forward_rwkv_wkv7_f32(
|
||||
|
||||
@@ -108,6 +108,7 @@ void ggml_compute_forward_lightning_indexer(const struct ggml_compute_params * p
|
||||
void ggml_compute_forward_dsv4_hc_comb(const struct ggml_compute_params * params, struct ggml_tensor * dst);
|
||||
void ggml_compute_forward_dsv4_hc_pre(const struct ggml_compute_params * params, struct ggml_tensor * dst);
|
||||
void ggml_compute_forward_dsv4_hc_post(const struct ggml_compute_params * params, struct ggml_tensor * dst);
|
||||
void ggml_compute_forward_top_k_mask(const struct ggml_compute_params * params, struct ggml_tensor * dst);
|
||||
void ggml_compute_forward_map_custom1(const struct ggml_compute_params * params, struct ggml_tensor * dst);
|
||||
void ggml_compute_forward_map_custom2(const struct ggml_compute_params * params, struct ggml_tensor * dst);
|
||||
void ggml_compute_forward_map_custom3(const struct ggml_compute_params * params, struct ggml_tensor * dst);
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
#include "ggml-cuda/cumsum.cuh"
|
||||
#include "ggml-cuda/fill.cuh"
|
||||
#include "ggml-cuda/lightning-indexer.cuh"
|
||||
#include "ggml-cuda/top-k-mask.cuh"
|
||||
#include "ggml.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -2570,6 +2571,9 @@ static bool ggml_cuda_compute_forward(ggml_backend_cuda_context & ctx, struct gg
|
||||
case GGML_OP_LIGHTNING_INDEXER:
|
||||
ggml_cuda_lightning_indexer(ctx, dst);
|
||||
break;
|
||||
case GGML_OP_TOP_K_MASK:
|
||||
ggml_cuda_op_top_k_mask(ctx, dst);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -6003,6 +6007,8 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
|
||||
return true;
|
||||
case GGML_OP_LIGHTNING_INDEXER:
|
||||
return ggml_cuda_lightning_indexer_supported(dev_ctx->device, op);
|
||||
case GGML_OP_TOP_K_MASK:
|
||||
return op->type == GGML_TYPE_F16 || op->type == GGML_TYPE_F32;
|
||||
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
#include "top-k-mask.cuh"
|
||||
#include "convert.cuh"
|
||||
|
||||
#define CUDA_TOP_K_MASK_BLOCK_SIZE 512
|
||||
|
||||
// one block per row: fill the row with -inf, then copy the mask cells of the live picks and the extra cells
|
||||
template <typename T>
|
||||
static __global__ void top_k_mask_kernel(
|
||||
const T * mask, const int32_t * top_k, const float * score, const int32_t * blocks, const int32_t * extra, T * dst,
|
||||
const int64_t n_kv, const int64_t ne1, const int64_t ne2,
|
||||
const int64_t m1, const int64_t m2, const int64_t m3,
|
||||
const int64_t n_top, const int64_t n_pick, const int64_t n_blk, const int64_t n_extra,
|
||||
const int64_t sm1, const int64_t sm2, const int64_t sm3,
|
||||
const int64_t sd1, const int64_t sd2, const int64_t sd3,
|
||||
const int64_t sk0, const int64_t sk1, const int64_t sk2, const int64_t sk3,
|
||||
const int64_t ss0, const int64_t ss1, const int64_t ss2, const int64_t ss3,
|
||||
const int64_t sb0, const int64_t sb1,
|
||||
const int64_t se0, const int64_t se1, const int64_t se2, const int64_t se3,
|
||||
const bool vec) {
|
||||
ggml_cuda_pdl_lc();
|
||||
|
||||
const int64_t ir = blockIdx.x;
|
||||
const int64_t i1 = ir % ne1;
|
||||
const int64_t i2 = (ir / ne1) % ne2;
|
||||
const int64_t i3 = ir / (ne1*ne2);
|
||||
|
||||
const T * mask_row = mask + (i1 % m1)*sm1 + (i2 % m2)*sm2 + (i3 % m3)*sm3;
|
||||
T * dst_row = dst + i1*sd1 + i2*sd2 + i3*sd3;
|
||||
const int32_t * top_k_row = top_k + i1*sk1 + i2*sk2 + i3*sk3;
|
||||
|
||||
ggml_cuda_pdl_sync();
|
||||
|
||||
const T ninf = ggml_cuda_cast<T>(-INFINITY);
|
||||
if (vec) {
|
||||
constexpr int nv = sizeof(int4)/sizeof(T);
|
||||
T fill[nv];
|
||||
#pragma unroll
|
||||
for (int j = 0; j < nv; ++j) {
|
||||
fill[j] = ninf;
|
||||
}
|
||||
const int4 fill4 = *(const int4 *) fill;
|
||||
for (int64_t i = threadIdx.x; i < n_kv/nv; i += blockDim.x) {
|
||||
((int4 *) dst_row)[i] = fill4;
|
||||
}
|
||||
} else {
|
||||
for (int64_t i = threadIdx.x; i < n_kv; i += blockDim.x) {
|
||||
dst_row[i] = ninf;
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// one thread per pick or extra cell; cells kept twice get the same value
|
||||
for (int64_t i = threadIdx.x; i < n_top + n_extra; i += blockDim.x) {
|
||||
if (i >= n_top) {
|
||||
const int32_t c = extra[(i - n_top)*se0 + i1*se1 + i2*se2 + i3*se3];
|
||||
if (c >= 0 && c < n_kv) {
|
||||
dst_row[c] = mask_row[c];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32_t p = top_k_row[i*sk0];
|
||||
if (p < 0 || p >= n_pick) {
|
||||
continue;
|
||||
}
|
||||
if (score && !(score[p*ss0 + i1*ss1 + i2*ss2 + i3*ss3] > -INFINITY)) {
|
||||
continue;
|
||||
}
|
||||
if (!blocks) {
|
||||
if (p < n_kv) {
|
||||
dst_row[p] = mask_row[p];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (int64_t k = 0; k < n_blk; ++k) {
|
||||
const int32_t c = blocks[k*sb0 + p*sb1];
|
||||
if (c >= 0 && c < n_kv) {
|
||||
dst_row[c] = mask_row[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void top_k_mask_cuda(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
const ggml_tensor * mask = dst->src[0];
|
||||
const ggml_tensor * top_k = dst->src[1];
|
||||
const ggml_tensor * score = dst->src[2];
|
||||
const ggml_tensor * blocks = dst->src[3];
|
||||
const ggml_tensor * extra = dst->src[4];
|
||||
|
||||
const int64_t n_kv = mask->ne[0];
|
||||
|
||||
GGML_ASSERT(dst->nb[0] == sizeof(T) && mask->nb[0] == sizeof(T));
|
||||
GGML_ASSERT(dst->nb[1] % sizeof(T) == 0 && mask->nb[1] % sizeof(T) == 0);
|
||||
|
||||
// 16-byte stores need every dst row aligned
|
||||
const bool vec = (n_kv*sizeof(T)) % sizeof(int4) == 0 &&
|
||||
dst->nb[1] % sizeof(int4) == 0 && dst->nb[2] % sizeof(int4) == 0 && dst->nb[3] % sizeof(int4) == 0 &&
|
||||
(uintptr_t) dst->data % sizeof(int4) == 0;
|
||||
|
||||
const int64_t n_pick = blocks ? blocks->ne[1] : score ? score->ne[0] : n_kv;
|
||||
|
||||
const size_t si = sizeof(int32_t);
|
||||
const size_t sf = sizeof(float);
|
||||
|
||||
const dim3 block_dims(CUDA_TOP_K_MASK_BLOCK_SIZE, 1, 1);
|
||||
const dim3 grid_dims(ggml_nrows(dst), 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(grid_dims, block_dims, 0, ctx.stream());
|
||||
|
||||
ggml_cuda_kernel_launch(top_k_mask_kernel<T>, launch_params,
|
||||
(const T *) mask->data, (const int32_t *) top_k->data,
|
||||
score ? (const float *) score->data : nullptr,
|
||||
blocks ? (const int32_t *) blocks->data : nullptr,
|
||||
extra ? (const int32_t *) extra->data : nullptr,
|
||||
(T *) dst->data,
|
||||
n_kv, dst->ne[1], dst->ne[2],
|
||||
mask->ne[1], mask->ne[2], mask->ne[3],
|
||||
top_k->ne[0], n_pick, blocks ? blocks->ne[0] : 1, extra ? extra->ne[0] : 0,
|
||||
mask->nb[1]/sizeof(T), mask->nb[2]/sizeof(T), mask->nb[3]/sizeof(T),
|
||||
dst->nb[1]/sizeof(T), dst->nb[2]/sizeof(T), dst->nb[3]/sizeof(T),
|
||||
top_k->nb[0]/si, top_k->nb[1]/si, top_k->nb[2]/si, top_k->nb[3]/si,
|
||||
score ? score->nb[0]/sf : 0, score ? score->nb[1]/sf : 0, score ? score->nb[2]/sf : 0, score ? score->nb[3]/sf : 0,
|
||||
blocks ? blocks->nb[0]/si : 0, blocks ? blocks->nb[1]/si : 0,
|
||||
extra ? extra->nb[0]/si : 0, extra ? extra->nb[1]/si : 0, extra ? extra->nb[2]/si : 0, extra ? extra->nb[3]/si : 0,
|
||||
vec);
|
||||
}
|
||||
|
||||
void ggml_cuda_op_top_k_mask(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
switch (dst->type) {
|
||||
case GGML_TYPE_F16:
|
||||
top_k_mask_cuda<half>(ctx, dst);
|
||||
break;
|
||||
case GGML_TYPE_F32:
|
||||
top_k_mask_cuda<float>(ctx, dst);
|
||||
break;
|
||||
default:
|
||||
GGML_ABORT("unsupported type");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "common.cuh"
|
||||
|
||||
void ggml_cuda_op_top_k_mask(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,8 @@ set(GGML_OPENCL_KERNELS
|
||||
solve_tri
|
||||
im2col_f32
|
||||
im2col_f16
|
||||
im2col_3d_f32
|
||||
im2col_3d_f16
|
||||
mean
|
||||
mul_mv_f16_f16
|
||||
mul_mv_f16_f32_1row
|
||||
|
||||
@@ -971,6 +971,7 @@ struct ggml_backend_opencl_context {
|
||||
cl_kernel kernel_mul_mv_iq4_nl_f32_flat;
|
||||
cl_kernel kernel_solve_tri_f32;
|
||||
cl_kernel kernel_im2col_f32, kernel_im2col_f16;
|
||||
cl_kernel kernel_im2col_3d_f32, kernel_im2col_3d_f16;
|
||||
cl_kernel kernel_argsort_f32_i32;
|
||||
cl_kernel kernel_sum_rows_f32, kernel_sum_rows_f32_4;
|
||||
cl_kernel kernel_cumsum_blk, kernel_cumsum_add;
|
||||
@@ -1243,6 +1244,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 +1266,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 +1274,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 +1310,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;
|
||||
@@ -2096,6 +2102,40 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
|
||||
// im2col_3d_f32
|
||||
{
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "im2col_3d_f32.cl.h"
|
||||
};
|
||||
#else
|
||||
const std::string kernel_src = read_file("im2col_3d_f32.cl");
|
||||
#endif
|
||||
cl_program prog =
|
||||
build_program_from_source(backend_ctx, kernel_src.c_str(), compile_opts);
|
||||
|
||||
CL_CHECK((backend_ctx->kernel_im2col_3d_f32 = clCreateKernel(prog, "kernel_im2col_3d_f32", &err), err));
|
||||
CL_CHECK(clReleaseProgram(prog));
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
|
||||
// im2col_3d_f16
|
||||
{
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "im2col_3d_f16.cl.h"
|
||||
};
|
||||
#else
|
||||
const std::string kernel_src = read_file("im2col_3d_f16.cl");
|
||||
#endif
|
||||
cl_program prog =
|
||||
build_program_from_source(backend_ctx, kernel_src.c_str(), compile_opts);
|
||||
|
||||
CL_CHECK((backend_ctx->kernel_im2col_3d_f16 = clCreateKernel(prog, "kernel_im2col_3d_f16", &err), err));
|
||||
CL_CHECK(clReleaseProgram(prog));
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
|
||||
// mul_mv_q4_0_f32
|
||||
{
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
@@ -4347,6 +4387,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 +4527,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 +4560,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 +4576,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 +4592,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(".");
|
||||
}
|
||||
@@ -9646,6 +9691,8 @@ static bool ggml_opencl_supports_op(ggml_backend_dev_t dev, const struct ggml_te
|
||||
return op->src[0]->type == GGML_TYPE_F32 && ggml_is_contiguous(op->src[0]);
|
||||
case GGML_OP_IM2COL:
|
||||
return true;
|
||||
case GGML_OP_IM2COL_3D:
|
||||
return true;
|
||||
case GGML_OP_ARGSORT: {
|
||||
load_cl_kernels_argsort(backend_ctx);
|
||||
|
||||
@@ -20430,11 +20477,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 +20510,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 +21805,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 +22759,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 +23430,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 +23990,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));
|
||||
@@ -30000,6 +30069,101 @@ static void ggml_cl_im2col(ggml_backend_t backend, const ggml_tensor * src0, con
|
||||
backend_ctx->enqueue_ndrange_kernel(kernel, 3, global_work_size, local_work_size, dst);
|
||||
}
|
||||
|
||||
static void ggml_cl_im2col_3d(ggml_backend_t backend, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) {
|
||||
GGML_ASSERT(src0);
|
||||
GGML_ASSERT(src1);
|
||||
GGML_ASSERT(src1->extra);
|
||||
GGML_ASSERT(dst);
|
||||
GGML_ASSERT(dst->extra);
|
||||
|
||||
GGML_ASSERT(src1->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(dst->type == GGML_TYPE_F16 || dst->type == GGML_TYPE_F32);
|
||||
|
||||
ggml_backend_opencl_context *backend_ctx = (ggml_backend_opencl_context *)backend->context;
|
||||
|
||||
ggml_tensor_extra_cl * extra1 = (ggml_tensor_extra_cl *)src1->extra;
|
||||
ggml_tensor_extra_cl * extrad = (ggml_tensor_extra_cl *)dst->extra;
|
||||
|
||||
cl_ulong offset1 = extra1->offset + src1->view_offs;
|
||||
cl_ulong offsetd = extrad->offset + dst->view_offs;
|
||||
|
||||
const int32_t s0 = ((const int32_t*)(dst->op_params))[0];
|
||||
const int32_t s1 = ((const int32_t*)(dst->op_params))[1];
|
||||
const int32_t s2 = ((const int32_t*)(dst->op_params))[2];
|
||||
const int32_t p0 = ((const int32_t*)(dst->op_params))[3];
|
||||
const int32_t p1 = ((const int32_t*)(dst->op_params))[4];
|
||||
const int32_t p2 = ((const int32_t*)(dst->op_params))[5];
|
||||
const int32_t d0 = ((const int32_t*)(dst->op_params))[6];
|
||||
const int32_t d1 = ((const int32_t*)(dst->op_params))[7];
|
||||
const int32_t d2 = ((const int32_t*)(dst->op_params))[8];
|
||||
const cl_long IC = ((const int32_t*)(dst->op_params))[9];
|
||||
|
||||
const cl_long N = src1->ne[3] / IC;
|
||||
const cl_long ID = src1->ne[2];
|
||||
const cl_long IH = src1->ne[1];
|
||||
const cl_long IW = src1->ne[0];
|
||||
|
||||
const cl_long KD = src0->ne[2];
|
||||
const cl_long KH = src0->ne[1];
|
||||
const cl_long KW = src0->ne[0];
|
||||
|
||||
const cl_long OD = dst->ne[3] / N;
|
||||
const cl_long OH = dst->ne[2];
|
||||
const cl_long OW = dst->ne[1];
|
||||
|
||||
const size_t es = ggml_element_size(src1);
|
||||
const cl_long stride_x = src1->nb[0] / es;
|
||||
const cl_long stride_y = src1->nb[1] / es;
|
||||
const cl_long stride_z = src1->nb[2] / es;
|
||||
const cl_long stride_q = src1->nb[3] / es;
|
||||
|
||||
const cl_long IC_KD_KH_KW = IC * KD * KH * KW;
|
||||
|
||||
cl_kernel kernel;
|
||||
|
||||
if (dst->type == GGML_TYPE_F16) {
|
||||
kernel = backend_ctx->kernel_im2col_3d_f16;
|
||||
} else {
|
||||
kernel = backend_ctx->kernel_im2col_3d_f32;
|
||||
}
|
||||
|
||||
CL_CHECK(clSetKernelArg(kernel, 0, sizeof(cl_mem), &extra1->data_device));
|
||||
CL_CHECK(clSetKernelArg(kernel, 1, sizeof(cl_ulong), &offset1));
|
||||
CL_CHECK(clSetKernelArg(kernel, 2, sizeof(cl_mem), &extrad->data_device));
|
||||
CL_CHECK(clSetKernelArg(kernel, 3, sizeof(cl_ulong), &offsetd));
|
||||
CL_CHECK(clSetKernelArg(kernel, 4, sizeof(cl_long), &IC));
|
||||
CL_CHECK(clSetKernelArg(kernel, 5, sizeof(cl_long), &ID));
|
||||
CL_CHECK(clSetKernelArg(kernel, 6, sizeof(cl_long), &IH));
|
||||
CL_CHECK(clSetKernelArg(kernel, 7, sizeof(cl_long), &IW));
|
||||
CL_CHECK(clSetKernelArg(kernel, 8, sizeof(cl_long), &KD));
|
||||
CL_CHECK(clSetKernelArg(kernel, 9, sizeof(cl_long), &KH));
|
||||
CL_CHECK(clSetKernelArg(kernel, 10, sizeof(cl_long), &KW));
|
||||
CL_CHECK(clSetKernelArg(kernel, 11, sizeof(cl_long), &OD));
|
||||
CL_CHECK(clSetKernelArg(kernel, 12, sizeof(cl_long), &OH));
|
||||
CL_CHECK(clSetKernelArg(kernel, 13, sizeof(cl_long), &OW));
|
||||
CL_CHECK(clSetKernelArg(kernel, 14, sizeof(cl_long), &N));
|
||||
CL_CHECK(clSetKernelArg(kernel, 15, sizeof(cl_long), &stride_x));
|
||||
CL_CHECK(clSetKernelArg(kernel, 16, sizeof(cl_long), &stride_y));
|
||||
CL_CHECK(clSetKernelArg(kernel, 17, sizeof(cl_long), &stride_z));
|
||||
CL_CHECK(clSetKernelArg(kernel, 18, sizeof(cl_long), &stride_q));
|
||||
CL_CHECK(clSetKernelArg(kernel, 19, sizeof(int), &s0));
|
||||
CL_CHECK(clSetKernelArg(kernel, 20, sizeof(int), &s1));
|
||||
CL_CHECK(clSetKernelArg(kernel, 21, sizeof(int), &s2));
|
||||
CL_CHECK(clSetKernelArg(kernel, 22, sizeof(int), &p0));
|
||||
CL_CHECK(clSetKernelArg(kernel, 23, sizeof(int), &p1));
|
||||
CL_CHECK(clSetKernelArg(kernel, 24, sizeof(int), &p2));
|
||||
CL_CHECK(clSetKernelArg(kernel, 25, sizeof(int), &d0));
|
||||
CL_CHECK(clSetKernelArg(kernel, 26, sizeof(int), &d1));
|
||||
CL_CHECK(clSetKernelArg(kernel, 27, sizeof(int), &d2));
|
||||
|
||||
const size_t local_col = 64;
|
||||
const size_t num_blocks = (IC_KD_KH_KW + local_col - 1) / local_col;
|
||||
size_t global_work_size[] = { num_blocks * local_col, (size_t)OW, (size_t)(N * OD * OH) };
|
||||
size_t local_work_size[] = { local_col, 1, 1 };
|
||||
|
||||
backend_ctx->enqueue_ndrange_kernel(kernel, 3, global_work_size, local_work_size, dst);
|
||||
}
|
||||
|
||||
static void ggml_cl_argsort(ggml_backend_t backend, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) {
|
||||
GGML_ASSERT(src0);
|
||||
GGML_ASSERT(src0->extra);
|
||||
@@ -30901,6 +31065,12 @@ bool ggml_cl_compute_forward(ggml_backend_t backend, struct ggml_tensor * tensor
|
||||
}
|
||||
func = ggml_cl_im2col;
|
||||
break;
|
||||
case GGML_OP_IM2COL_3D:
|
||||
if (!any_on_device) {
|
||||
return false;
|
||||
}
|
||||
func = ggml_cl_im2col_3d;
|
||||
break;
|
||||
case GGML_OP_ARGSORT:
|
||||
if (!any_on_device) {
|
||||
return false;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#pragma OPENCL EXTENSION cl_khr_fp16 : enable
|
||||
|
||||
kernel void kernel_im2col_3d_f16(
|
||||
global float * src1,
|
||||
ulong offset1,
|
||||
global half * dst,
|
||||
ulong offsetd,
|
||||
long IC,
|
||||
long ID,
|
||||
long IH,
|
||||
long IW,
|
||||
long KD,
|
||||
long KH,
|
||||
long KW,
|
||||
long OD,
|
||||
long OH,
|
||||
long OW,
|
||||
long N,
|
||||
long stride_x,
|
||||
long stride_y,
|
||||
long stride_z,
|
||||
long stride_q,
|
||||
int s0,
|
||||
int s1,
|
||||
int s2,
|
||||
int p0,
|
||||
int p1,
|
||||
int p2,
|
||||
int d0,
|
||||
int d1,
|
||||
int d2
|
||||
) {
|
||||
const int ic = (int)IC;
|
||||
const int kd = (int)KD;
|
||||
const int kh = (int)KH;
|
||||
const int kw = (int)KW;
|
||||
const int od = (int)OD;
|
||||
const int oh = (int)OH;
|
||||
const int ow = (int)OW;
|
||||
const int n = (int)N;
|
||||
|
||||
const int KD_KH_KW = kd * kh * kw;
|
||||
const int KH_KW = kh * kw;
|
||||
const int IC_KD_KH_KW = ic * KD_KH_KW;
|
||||
|
||||
const int i = (int)get_global_id(0);
|
||||
if (i >= IC_KD_KH_KW) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int iow = (int)get_global_id(1);
|
||||
if (iow >= ow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int OD_OH = od * oh;
|
||||
const int iz = (int)get_global_id(2);
|
||||
if (iz >= n * OD_OH) {
|
||||
return;
|
||||
}
|
||||
|
||||
src1 = (global float*)((global char*)src1 + offset1);
|
||||
dst = (global half*)((global char*)dst + offsetd);
|
||||
|
||||
const int iic = i / KD_KH_KW;
|
||||
const int ikd = (i - iic * KD_KH_KW) / KH_KW;
|
||||
const int ikh = (i - iic * KD_KH_KW - ikd * KH_KW) / kw;
|
||||
const int ikw = i % kw;
|
||||
|
||||
const int in = iz / OD_OH;
|
||||
const int iod = (iz - in * OD_OH) / oh;
|
||||
const int ioh = iz % oh;
|
||||
|
||||
const int iiw = iow * s0 + ikw * d0 - p0;
|
||||
const int iih = ioh * s1 + ikh * d1 - p1;
|
||||
const int iid = iod * s2 + ikd * d2 - p2;
|
||||
|
||||
const long offset_dst =
|
||||
(long)(iz * ow + iow) * (long)IC_KD_KH_KW +
|
||||
(long)(iic * KD_KH_KW + ikd * KH_KW + ikh * kw + ikw);
|
||||
|
||||
if (iih < 0 || iih >= IH || iiw < 0 || iiw >= IW || iid < 0 || iid >= ID) {
|
||||
dst[offset_dst] = 0.0f;
|
||||
} else {
|
||||
const long offset_src =
|
||||
(long)(in * ic + iic) * stride_q + (long)iid * stride_z +
|
||||
(long)iih * stride_y + (long)iiw * stride_x;
|
||||
dst[offset_dst] = convert_half_rte(src1[offset_src]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#pragma OPENCL EXTENSION cl_khr_fp16 : enable
|
||||
|
||||
kernel void kernel_im2col_3d_f32(
|
||||
global float * src1,
|
||||
ulong offset1,
|
||||
global float * dst,
|
||||
ulong offsetd,
|
||||
long IC,
|
||||
long ID,
|
||||
long IH,
|
||||
long IW,
|
||||
long KD,
|
||||
long KH,
|
||||
long KW,
|
||||
long OD,
|
||||
long OH,
|
||||
long OW,
|
||||
long N,
|
||||
long stride_x,
|
||||
long stride_y,
|
||||
long stride_z,
|
||||
long stride_q,
|
||||
int s0,
|
||||
int s1,
|
||||
int s2,
|
||||
int p0,
|
||||
int p1,
|
||||
int p2,
|
||||
int d0,
|
||||
int d1,
|
||||
int d2
|
||||
) {
|
||||
const int ic = (int)IC;
|
||||
const int kd = (int)KD;
|
||||
const int kh = (int)KH;
|
||||
const int kw = (int)KW;
|
||||
const int od = (int)OD;
|
||||
const int oh = (int)OH;
|
||||
const int ow = (int)OW;
|
||||
const int n = (int)N;
|
||||
|
||||
const int KD_KH_KW = kd * kh * kw;
|
||||
const int KH_KW = kh * kw;
|
||||
const int IC_KD_KH_KW = ic * KD_KH_KW;
|
||||
|
||||
const int i = (int)get_global_id(0);
|
||||
if (i >= IC_KD_KH_KW) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int iow = (int)get_global_id(1);
|
||||
if (iow >= ow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int OD_OH = od * oh;
|
||||
const int iz = (int)get_global_id(2);
|
||||
if (iz >= n * OD_OH) {
|
||||
return;
|
||||
}
|
||||
|
||||
src1 = (global float*)((global char*)src1 + offset1);
|
||||
dst = (global float*)((global char*)dst + offsetd);
|
||||
|
||||
const int iic = i / KD_KH_KW;
|
||||
const int ikd = (i - iic * KD_KH_KW) / KH_KW;
|
||||
const int ikh = (i - iic * KD_KH_KW - ikd * KH_KW) / kw;
|
||||
const int ikw = i % kw;
|
||||
|
||||
const int in = iz / OD_OH;
|
||||
const int iod = (iz - in * OD_OH) / oh;
|
||||
const int ioh = iz % oh;
|
||||
|
||||
const int iiw = iow * s0 + ikw * d0 - p0;
|
||||
const int iih = ioh * s1 + ikh * d1 - p1;
|
||||
const int iid = iod * s2 + ikd * d2 - p2;
|
||||
|
||||
const long offset_dst =
|
||||
(long)(iz * ow + iow) * (long)IC_KD_KH_KW +
|
||||
(long)(iic * KD_KH_KW + ikd * KH_KW + ikh * kw + ikw);
|
||||
|
||||
if (iih < 0 || iih >= IH || iiw < 0 || iiw >= IW || iid < 0 || iid >= ID) {
|
||||
dst[offset_dst] = 0.0f;
|
||||
} else {
|
||||
const long offset_src =
|
||||
(long)(in * ic + iic) * stride_q + (long)iid * stride_z +
|
||||
(long)iih * stride_y + (long)iiw * stride_x;
|
||||
dst[offset_dst] = src1[offset_src];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -177,15 +177,12 @@ static void dequantize_row_q8_0_sycl_reorder(const void *vx, dst_t *y, const int
|
||||
dpct::has_capability_or_fail(stream->get_device(),
|
||||
{sycl::aspect::fp16});
|
||||
|
||||
// flat range<1>, two pairs (4 elements) per item: no nd_range/group math, one vectorized store.
|
||||
// k % QK8_0 == 0 implies k % 4 == 0, so the wide launch covers the row exactly (no tail).
|
||||
GGML_ASSERT(k % QK8_0 == 0);
|
||||
const int64_t n_groups = ceil_div(k / 2, SYCL_DEQUANTIZE_BLOCK_SIZE);
|
||||
// decrease global range when it exceeds the max int; the kernel loops to cover the rest
|
||||
const int64_t wg_size = downsample_sycl_global_range(n_groups, SYCL_DEQUANTIZE_BLOCK_SIZE);
|
||||
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, n_groups) *
|
||||
sycl::range<3>(1, 1, wg_size),
|
||||
sycl::range<3>(1, 1, wg_size)),
|
||||
[=](sycl::nd_item<3> item_ct1) {
|
||||
dequantize_block_q8_0_reorder(vx, y, k, item_ct1);
|
||||
stream->parallel_for(sycl::range<1>((size_t) (k / 4)),
|
||||
[=](sycl::id<1> idx) {
|
||||
dequantize_block_q8_0_reorder_wide(vx, y, k, idx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -440,7 +437,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 +470,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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -857,6 +857,26 @@ static void dequantize_block_q8_0_reorder(const void * __restrict__ vx, dst_t *
|
||||
}
|
||||
}
|
||||
|
||||
template<typename dst_t>
|
||||
static void dequantize_block_q8_0_reorder_wide(const void * __restrict__ vx, dst_t * __restrict__ yy, int64_t k,
|
||||
const sycl::id<1> idx) {
|
||||
const int64_t i = 4 * (int64_t) idx[0];
|
||||
|
||||
const int8_t * qs = (const int8_t *) vx;
|
||||
const sycl::half * d_ptr = (const sycl::half *) ((const uint8_t *) vx + k);
|
||||
|
||||
const float d0 = float(d_ptr[i / QK8_0]);
|
||||
const float d1 = float(d_ptr[(i + 2) / QK8_0]);
|
||||
|
||||
sycl::vec<float, 4> p;
|
||||
p[0] = d0 * (float) qs[i + 0];
|
||||
p[1] = d0 * (float) qs[i + 1];
|
||||
p[2] = d1 * (float) qs[i + 2];
|
||||
p[3] = d1 * (float) qs[i + 3];
|
||||
|
||||
*reinterpret_cast<sycl::vec<dst_t, 4> *>(yy + i) = p.convert<dst_t, sycl::rounding_mode::automatic>();
|
||||
}
|
||||
|
||||
template<typename dst_t>
|
||||
static void dequantize_block_q4_1(const void * __restrict__ vx, dst_t * __restrict__ yy, int64_t nb32,
|
||||
const sycl::nd_item<3> &item_ct1) {
|
||||
@@ -1481,12 +1501,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 +1520,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 +1529,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 +1571,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 +1593,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 +1602,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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+46
-2
@@ -1095,6 +1095,7 @@ static const char * GGML_OP_NAME[GGML_OP_COUNT] = {
|
||||
"DSV4_HC_COMB",
|
||||
"DSV4_HC_PRE",
|
||||
"DSV4_HC_POST",
|
||||
"TOP_K_MASK",
|
||||
|
||||
"UNARY",
|
||||
|
||||
@@ -1112,7 +1113,7 @@ static const char * GGML_OP_NAME[GGML_OP_COUNT] = {
|
||||
"GLU",
|
||||
};
|
||||
|
||||
static_assert(GGML_OP_COUNT == 101, "GGML_OP_COUNT != 101");
|
||||
static_assert(GGML_OP_COUNT == 102, "GGML_OP_COUNT != 102");
|
||||
|
||||
static const char * GGML_OP_SYMBOL[GGML_OP_COUNT] = {
|
||||
"none",
|
||||
@@ -1210,6 +1211,7 @@ static const char * GGML_OP_SYMBOL[GGML_OP_COUNT] = {
|
||||
"dsv4_hc_comb(mixes, scale, base)",
|
||||
"dsv4_hc_pre(x, weights)",
|
||||
"dsv4_hc_post(x, residual, post, comb)",
|
||||
"top_k_mask(mask, top_k, score, blocks, extra)",
|
||||
|
||||
"unary(x)",
|
||||
|
||||
@@ -1227,7 +1229,7 @@ static const char * GGML_OP_SYMBOL[GGML_OP_COUNT] = {
|
||||
"glu(x)",
|
||||
};
|
||||
|
||||
static_assert(GGML_OP_COUNT == 101, "GGML_OP_COUNT != 101");
|
||||
static_assert(GGML_OP_COUNT == 102, "GGML_OP_COUNT != 102");
|
||||
|
||||
static_assert(GGML_OP_POOL_COUNT == 2, "GGML_OP_POOL_COUNT != 2");
|
||||
|
||||
@@ -6630,6 +6632,48 @@ struct ggml_tensor * ggml_dsv4_hc_post(
|
||||
return result;
|
||||
}
|
||||
|
||||
// ggml_top_k_mask
|
||||
|
||||
struct ggml_tensor * ggml_top_k_mask(
|
||||
struct ggml_context * ctx,
|
||||
struct ggml_tensor * mask,
|
||||
struct ggml_tensor * top_k,
|
||||
struct ggml_tensor * score,
|
||||
struct ggml_tensor * blocks,
|
||||
struct ggml_tensor * extra) {
|
||||
GGML_ASSERT(mask->type == GGML_TYPE_F16 || mask->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(mask->nb[0] == ggml_type_size(mask->type));
|
||||
GGML_ASSERT(top_k->type == GGML_TYPE_I32);
|
||||
GGML_ASSERT(top_k->ne[1] % mask->ne[1] == 0 && top_k->ne[2] % mask->ne[2] == 0 && top_k->ne[3] % mask->ne[3] == 0);
|
||||
|
||||
if (score) {
|
||||
GGML_ASSERT(score->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(score->ne[1] == top_k->ne[1] && score->ne[2] == top_k->ne[2] && score->ne[3] == top_k->ne[3]);
|
||||
}
|
||||
|
||||
if (blocks) {
|
||||
GGML_ASSERT(blocks->type == GGML_TYPE_I32);
|
||||
GGML_ASSERT(blocks->ne[2] == 1 && blocks->ne[3] == 1);
|
||||
GGML_ASSERT(!score || blocks->ne[1] == score->ne[0]);
|
||||
}
|
||||
|
||||
if (extra) {
|
||||
GGML_ASSERT(extra->type == GGML_TYPE_I32);
|
||||
GGML_ASSERT(extra->ne[1] == top_k->ne[1] && extra->ne[2] == top_k->ne[2] && extra->ne[3] == top_k->ne[3]);
|
||||
}
|
||||
|
||||
struct ggml_tensor * result = ggml_new_tensor_4d(ctx, mask->type, mask->ne[0], top_k->ne[1], top_k->ne[2], top_k->ne[3]);
|
||||
|
||||
result->op = GGML_OP_TOP_K_MASK;
|
||||
result->src[0] = mask;
|
||||
result->src[1] = top_k;
|
||||
result->src[2] = score;
|
||||
result->src[3] = blocks;
|
||||
result->src[4] = extra;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct ggml_hash_set ggml_hash_set_new(size_t size) {
|
||||
|
||||
@@ -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
@@ -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}},
|
||||
|
||||
@@ -165,6 +165,12 @@ static const llm_fused_op_probe llm_fused_op_dsv4_hc_post_probe = {
|
||||
/*.n_tokens_per_seq =*/ 1,
|
||||
};
|
||||
|
||||
static const llm_fused_op_probe llm_fused_op_top_k_mask_probe = {
|
||||
/*.op =*/ LLM_FUSED_OP_TOP_K_MASK,
|
||||
/*.name =*/ "fused top-k mask",
|
||||
/*.n_tokens_per_seq =*/ 1,
|
||||
};
|
||||
|
||||
llama_context::llama_context(
|
||||
const llama_model & model,
|
||||
llama_context_params params) :
|
||||
@@ -328,6 +334,10 @@ llama_context::llama_context(
|
||||
cparams.fused_dsv4_hc_post = true;
|
||||
cparams.auto_fhc = true;
|
||||
|
||||
// only the indexer models build this mask, skip the probe for the others
|
||||
cparams.fused_top_k_mask = true;
|
||||
cparams.auto_ftkm = hparams.indexer_top_k > 0;
|
||||
|
||||
// with causal attention, the batch size is limited by the context size
|
||||
cparams.n_batch = cparams.causal_attn ? std::min(cparams.n_ctx, params.n_batch) : params.n_batch;
|
||||
|
||||
@@ -671,6 +681,12 @@ void llama_context::resolve_fused_ops(const llama_memory_context_i * mctx, uint3
|
||||
resolve(llm_fused_op_dsv4_hc_post_probe, cparams.fused_dsv4_hc_post);
|
||||
cparams.auto_fhc = false;
|
||||
}
|
||||
|
||||
if (cparams.auto_ftkm) {
|
||||
LLAMA_LOG_INFO("%s: resolving fused top-k mask support:\n", func);
|
||||
resolve(llm_fused_op_top_k_mask_probe, cparams.fused_top_k_mask);
|
||||
cparams.auto_ftkm = false;
|
||||
}
|
||||
}
|
||||
|
||||
static int llama_graph_n_input_tensors(ggml_cgraph * gf) {
|
||||
|
||||
@@ -48,6 +48,8 @@ struct llama_cparams {
|
||||
bool fused_dsv4_hc_comb;
|
||||
bool fused_dsv4_hc_post;
|
||||
bool auto_fhc;
|
||||
bool fused_top_k_mask; // use fused top-k sparse attention mask
|
||||
bool auto_ftkm;
|
||||
bool no_perf;
|
||||
bool warmup; // TODO: remove [TAG_LLAMA_GRAPH_NO_WARMUP]
|
||||
bool op_offload;
|
||||
|
||||
+97
-24
@@ -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);
|
||||
@@ -3328,6 +3375,27 @@ ggml_tensor * llm_graph_context::build_attn(
|
||||
return cur;
|
||||
}
|
||||
|
||||
ggml_tensor * llm_graph_context::build_fused_top_k_mask(
|
||||
ggml_tensor * kq_mask,
|
||||
ggml_tensor * top_k,
|
||||
ggml_tensor * score,
|
||||
ggml_tensor * blocks,
|
||||
ggml_tensor * extra,
|
||||
int il) const {
|
||||
const int64_t m1 = kq_mask->ne[1];
|
||||
const int64_t m2 = kq_mask->ne[2];
|
||||
const int64_t m3 = kq_mask->ne[3];
|
||||
|
||||
auto rows = [&](ggml_tensor * t) {
|
||||
return t ? ggml_reshape_4d(ctx0, t, t->ne[0], m1, m2, m3) : nullptr;
|
||||
};
|
||||
|
||||
ggml_tensor * cur = ggml_top_k_mask(ctx0, kq_mask, rows(top_k), rows(score), blocks, rows(extra));
|
||||
res->add_fused_node({LLM_FUSED_OP_TOP_K_MASK, cur, il});
|
||||
|
||||
return cur;
|
||||
}
|
||||
|
||||
ggml_tensor * llm_graph_context::build_attn(
|
||||
llm_graph_input_attn_k_dsa * inp,
|
||||
ggml_tensor * wo,
|
||||
@@ -3360,31 +3428,36 @@ ggml_tensor * llm_graph_context::build_attn(
|
||||
|
||||
const auto & kq_mask = inp->get_kq_mask_mla();
|
||||
|
||||
// prepare new kq mask - starts filled with -INFINITY
|
||||
ggml_tensor * kq_mask_all = ggml_fill(ctx0, kq_mask, -INFINITY);
|
||||
ggml_tensor * kq_mask_top_k = nullptr;
|
||||
if (cparams.fused_top_k_mask) {
|
||||
kq_mask_top_k = build_fused_top_k_mask(kq_mask, top_k, nullptr, nullptr, nullptr, il);
|
||||
} else {
|
||||
// prepare new kq mask - starts filled with -INFINITY
|
||||
ggml_tensor * kq_mask_all = ggml_fill(ctx0, kq_mask, -INFINITY);
|
||||
|
||||
// reshape KQ mask into tensor with rows of size 1:
|
||||
// [n_kv, n_batch, 1, n_stream] -> [1, n_kv, n_batch, n_stream]
|
||||
kq_mask_all = ggml_view_4d(ctx0, kq_mask_all, 1, kq_mask_all->ne[0], kq_mask_all->ne[1], kq_mask_all->ne[3], kq_mask_all->nb[0], kq_mask_all->nb[1], kq_mask_all->nb[2], 0);
|
||||
// reshape KQ mask into tensor with rows of size 1:
|
||||
// [n_kv, n_batch, 1, n_stream] -> [1, n_kv, n_batch, n_stream]
|
||||
kq_mask_all = ggml_view_4d(ctx0, kq_mask_all, 1, kq_mask_all->ne[0], kq_mask_all->ne[1], kq_mask_all->ne[3], kq_mask_all->nb[0], kq_mask_all->nb[1], kq_mask_all->nb[2], 0);
|
||||
|
||||
// reshape top_k indices: [n_top_k, n_batch, 1, n_stream] -> [n_top_k, n_batch, n_stream, 1]
|
||||
ggml_tensor * top_k_3d = ggml_view_4d(ctx0, top_k, top_k->ne[0], top_k->ne[1], top_k->ne[3], 1, top_k->nb[1], top_k->nb[2], top_k->ne[3]*top_k->nb[3], 0);
|
||||
// reshape top_k indices: [n_top_k, n_batch, 1, n_stream] -> [n_top_k, n_batch, n_stream, 1]
|
||||
ggml_tensor * top_k_3d = ggml_view_4d(ctx0, top_k, top_k->ne[0], top_k->ne[1], top_k->ne[3], 1, top_k->nb[1], top_k->nb[2], top_k->ne[3]*top_k->nb[3], 0);
|
||||
|
||||
// prepare zero-filled tensor with rows of size 1: [1, n_top_k, n_batch, n_stream]
|
||||
// this will be our source of zero values for unmasking top k mask elements
|
||||
ggml_tensor * zeros = ggml_new_tensor_4d(ctx0, GGML_TYPE_F32, 1, top_k_3d->ne[0], top_k_3d->ne[1], top_k_3d->ne[2]);
|
||||
zeros = ggml_fill(ctx0, zeros, 0.0f);
|
||||
// prepare zero-filled tensor with rows of size 1: [1, n_top_k, n_batch, n_stream]
|
||||
// this will be our source of zero values for unmasking top k mask elements
|
||||
ggml_tensor * zeros = ggml_new_tensor_4d(ctx0, GGML_TYPE_F32, 1, top_k_3d->ne[0], top_k_3d->ne[1], top_k_3d->ne[2]);
|
||||
zeros = ggml_fill(ctx0, zeros, 0.0f);
|
||||
|
||||
// modify KQ mask by unmasking elements that are in top_k indices
|
||||
// ggml_set_rows([1, n_kv, n_batch, n_stream], [1, n_top_k, n_batch, n_stream], [n_top_k, n_batch, n_stream, 1])
|
||||
ggml_tensor * kq_mask_top_k = ggml_set_rows(ctx0, kq_mask_all, zeros, top_k_3d);
|
||||
// modify KQ mask by unmasking elements that are in top_k indices
|
||||
// ggml_set_rows([1, n_kv, n_batch, n_stream], [1, n_top_k, n_batch, n_stream], [n_top_k, n_batch, n_stream, 1])
|
||||
kq_mask_top_k = ggml_set_rows(ctx0, kq_mask_all, zeros, top_k_3d);
|
||||
|
||||
// reshape to restore the original shape of KQ mask:
|
||||
// [1, n_kv, n_batch, n_stream] -> [n_kv, n_batch, 1, n_stream]
|
||||
kq_mask_top_k = ggml_view_4d(ctx0, kq_mask_top_k, kq_mask_top_k->ne[1], kq_mask_top_k->ne[2], 1, kq_mask_top_k->ne[3], kq_mask_top_k->nb[2], kq_mask_top_k->nb[3], kq_mask_top_k->nb[3], 0);
|
||||
// reshape to restore the original shape of KQ mask:
|
||||
// [1, n_kv, n_batch, n_stream] -> [n_kv, n_batch, 1, n_stream]
|
||||
kq_mask_top_k = ggml_view_4d(ctx0, kq_mask_top_k, kq_mask_top_k->ne[1], kq_mask_top_k->ne[2], 1, kq_mask_top_k->ne[3], kq_mask_top_k->nb[2], kq_mask_top_k->nb[3], kq_mask_top_k->nb[3], 0);
|
||||
|
||||
// combine with the original kq mask
|
||||
kq_mask_top_k = ggml_add(ctx0, kq_mask_top_k, kq_mask);
|
||||
// combine with the original kq mask
|
||||
kq_mask_top_k = ggml_add(ctx0, kq_mask_top_k, kq_mask);
|
||||
}
|
||||
|
||||
ggml_tensor * q = q_cur;
|
||||
ggml_tensor * k = mctx_cur->get_k(ctx0, il);
|
||||
|
||||
@@ -53,6 +53,7 @@ enum llm_fused_op {
|
||||
LLM_FUSED_OP_DSV4_HC_PRE,
|
||||
LLM_FUSED_OP_DSV4_HC_COMB,
|
||||
LLM_FUSED_OP_DSV4_HC_POST,
|
||||
LLM_FUSED_OP_TOP_K_MASK,
|
||||
};
|
||||
|
||||
enum llm_ffn_op_type : int {
|
||||
@@ -1012,6 +1013,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 +1121,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,
|
||||
@@ -1325,6 +1336,15 @@ struct llm_graph_context {
|
||||
float kq_scale,
|
||||
int il) const;
|
||||
|
||||
// ggml_top_k_mask with top_k, score and extra viewed in the row layout of kq_mask, see ggml.h
|
||||
ggml_tensor * build_fused_top_k_mask(
|
||||
ggml_tensor * kq_mask, // [n_kv, n_tokens/n_stream, 1, n_stream]
|
||||
ggml_tensor * top_k, // [n_top, n_tokens]
|
||||
ggml_tensor * score, // [n_pick, n_tokens], can be null
|
||||
ggml_tensor * blocks, // [n_blk, n_pick], can be null
|
||||
ggml_tensor * extra, // [n_extra, n_tokens], can be null
|
||||
int il) const;
|
||||
|
||||
llm_graph_input_attn_kv_iswa * build_attn_inp_kv_iswa() const;
|
||||
|
||||
llm_graph_input_dsv4 * build_inp_dsv4() const;
|
||||
|
||||
+30
-21
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -683,6 +683,13 @@ ggml_tensor * llama_model_deepseek4::graph::build_top_k_mask(
|
||||
GGML_ASSERT(kq_mask);
|
||||
GGML_ASSERT(top_k);
|
||||
|
||||
if (cparams.fused_top_k_mask) {
|
||||
ggml_tensor * kq_mask_top_k = build_fused_top_k_mask(kq_mask, top_k, nullptr, nullptr, nullptr, il);
|
||||
cb(kq_mask_top_k, name, il);
|
||||
|
||||
return kq_mask_top_k;
|
||||
}
|
||||
|
||||
ggml_tensor * kq_mask_all = ggml_fill(ctx0, kq_mask, -INFINITY);
|
||||
kq_mask_all = ggml_view_4d(ctx0, kq_mask_all, 1, kq_mask_all->ne[0], kq_mask_all->ne[1], kq_mask_all->ne[3],
|
||||
kq_mask_all->nb[0], kq_mask_all->nb[1], kq_mask_all->nb[2], 0);
|
||||
|
||||
@@ -323,13 +323,15 @@ llama_model_glm5_next::llm_graph_input_kpool * llama_model_glm5_next::graph::bui
|
||||
const int64_t n_sel = kpool*n_top_pool + (hparams.indexer_kpool_select_tail ? kpool - 1 : 0);
|
||||
inp->n_sel = (uint32_t) n_sel;
|
||||
|
||||
// the scatter maps the dead slots of this mask to dump rows
|
||||
inp->sel_mask = ggml_new_tensor_4d(ctx0, GGML_TYPE_F32, n_sel, 1, 1, n_tokens);
|
||||
ggml_set_input(inp->sel_mask);
|
||||
// set_input_kpool always fills the mask, so it stays allocated in every graph
|
||||
ggml_build_forward_expand(gf, inp->sel_mask);
|
||||
// one view for all layers: each view of a host input is a separate upload
|
||||
inp->sel_mask_2d = ggml_reshape_2d(ctx0, inp->sel_mask, n_sel, n_tokens);
|
||||
// the scatter maps the dead slots of this mask to dump rows, the fused mask op reads liveness from the scores instead
|
||||
if (!cparams.fused_top_k_mask) {
|
||||
inp->sel_mask = ggml_new_tensor_4d(ctx0, GGML_TYPE_F32, n_sel, 1, 1, n_tokens);
|
||||
ggml_set_input(inp->sel_mask);
|
||||
// set_input_kpool always fills the mask, so it stays allocated in every graph
|
||||
ggml_build_forward_expand(gf, inp->sel_mask);
|
||||
// one view for all layers: each view of a host input is a separate upload
|
||||
inp->sel_mask_2d = ggml_reshape_2d(ctx0, inp->sel_mask, n_sel, n_tokens);
|
||||
}
|
||||
}
|
||||
|
||||
inp->n_new = n_new;
|
||||
@@ -973,6 +975,17 @@ ggml_tensor * llama_model_glm5_next::graph::build_kpool_select(
|
||||
const int64_t n_top_pool = std::min<int64_t>(n_pool, hparams.indexer_top_k / kpool);
|
||||
ggml_tensor * top_k = ggml_top_k(ctx0, score, n_top_pool); // [n_top_pool, n_tokens], UNORDERED
|
||||
|
||||
// keep the kq_mask cells of the live picked pools and the tail, -inf elsewhere
|
||||
if (cparams.fused_top_k_mask) {
|
||||
ggml_tensor * tail = hparams.indexer_kpool_select_tail ? inp_kpool->tail_idxs : nullptr;
|
||||
GGML_ASSERT(kpool*n_top_pool + (tail ? tail->ne[0] : 0) == (int64_t) inp_kpool->n_sel);
|
||||
|
||||
ggml_tensor * sel = build_fused_top_k_mask(kq_mask, top_k, score, inp_kpool->pool_idxs, tail, il);
|
||||
cb(sel, "indexer_sel", il);
|
||||
|
||||
return sel;
|
||||
}
|
||||
|
||||
// The selection mask marks the first min(nv, n_top_pool) slots as the visible pools, so order the set by descending score.
|
||||
ggml_tensor * sel_score = ggml_get_rows(ctx0,
|
||||
ggml_reshape_3d(ctx0, score, 1, n_pool, n_tokens), top_k); // [1, n_top_pool, n_tokens]
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+11
-3
@@ -813,6 +813,16 @@ ggml_tensor * llama_model_qwen4exp::graph::build_qsa_sel(
|
||||
ggml_tensor * top_k = ggml_top_k(ctx0, score, n_top_pool); // [n_top_pool, n_tokens], unordered
|
||||
cb(top_k, "indexer_top_k", il);
|
||||
|
||||
// keep the kq_mask cells of the live picked pools and the tail, -inf elsewhere
|
||||
if (cparams.fused_top_k_mask) {
|
||||
GGML_ASSERT(kpool*n_top_pool + inp_kpool->tail_idxs->ne[0] == inp_kpool->n_sel);
|
||||
|
||||
ggml_tensor * sel = build_fused_top_k_mask(kq_mask, top_k, score, inp_kpool->pool_idxs, inp_kpool->tail_idxs, il);
|
||||
cb(sel, "indexer_sel", il);
|
||||
|
||||
return sel;
|
||||
}
|
||||
|
||||
// the top blocks, then the incomplete tail with n_kv for missing cells
|
||||
ggml_tensor * sel_idx = ggml_get_rows(ctx0, inp_kpool->pool_idxs,
|
||||
ggml_reshape_1d(ctx0, top_k, n_top_pool*n_tokens)); // [kpool, n_top_pool*n_tokens]
|
||||
@@ -1131,14 +1141,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+246
-2
@@ -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;
|
||||
@@ -9057,6 +9128,148 @@ struct test_lightning_indexer : public test_case {
|
||||
}
|
||||
};
|
||||
|
||||
// GGML_OP_TOP_K_MASK
|
||||
struct test_top_k_mask : public test_case {
|
||||
const ggml_type type;
|
||||
const int64_t n_kv;
|
||||
const int64_t n_blk; // 0: no blocks, a pick is a cell
|
||||
const int64_t n_pick; // number of blocks, ignored without blocks
|
||||
const int64_t n_top;
|
||||
const int64_t n_extra; // 0: no extra cells
|
||||
const bool score; // false: every pick is live
|
||||
const int64_t nb; // rows per stream
|
||||
const int64_t nh; // rows of top_k per mask row, broadcast over ne2
|
||||
const int64_t ns; // streams
|
||||
const bool real; // blocks of consecutive cells, live picks, as in a real cache
|
||||
|
||||
std::string vars() override {
|
||||
return VARS_TO_STR11(type, n_kv, n_blk, n_pick, n_top, n_extra, score, nb, nh, ns, real);
|
||||
}
|
||||
|
||||
// the result is a copy of the mask or -inf, the harness checks the -inf pattern, the finite values must match exactly
|
||||
double max_err() override {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double err(const float * a, const float * b, size_t n) override {
|
||||
double result = 0.0;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (std::isfinite(a[i]) && std::isfinite(b[i])) {
|
||||
result = std::max(result, (double) fabsf(a[i] - b[i]));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t op_size(ggml_tensor * t) override {
|
||||
const size_t ts = ggml_type_size(type);
|
||||
const size_t nc = std::max<int64_t>(n_blk, 1);
|
||||
return ggml_nbytes(t) + nb*nh*ns*(n_top*(sizeof(int32_t) + (score ? sizeof(float) : 0) + nc*(sizeof(int32_t) + 2*ts)) + n_extra*(sizeof(int32_t) + 2*ts));
|
||||
}
|
||||
|
||||
test_top_k_mask(ggml_type type = GGML_TYPE_F16, int64_t n_kv = 256, int64_t n_blk = 4, int64_t n_pick = 64,
|
||||
int64_t n_top = 16, int64_t n_extra = 3, bool score = true, int64_t nb = 32, int64_t nh = 1, int64_t ns = 1, bool real = false)
|
||||
: type(type), n_kv(n_kv), n_blk(n_blk), n_pick(n_blk ? n_pick : n_kv), n_top(n_top), n_extra(n_extra), score(score),
|
||||
nb(nb), nh(nh), ns(ns), real(real) {}
|
||||
|
||||
ggml_tensor * build_graph(ggml_context * ctx) override {
|
||||
ggml_tensor * m = ggml_new_tensor_4d(ctx, type, n_kv, nb, 1, ns);
|
||||
ggml_set_name(m, "m");
|
||||
|
||||
ggml_tensor * top_k = ggml_new_tensor_4d(ctx, GGML_TYPE_I32, n_top, nb, nh, ns);
|
||||
ggml_set_name(top_k, "top_k");
|
||||
|
||||
ggml_tensor * s = nullptr;
|
||||
if (score) {
|
||||
s = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, n_pick, nb, nh, ns);
|
||||
ggml_set_name(s, "score");
|
||||
}
|
||||
|
||||
ggml_tensor * blocks = nullptr;
|
||||
if (n_blk > 0) {
|
||||
blocks = ggml_new_tensor_2d(ctx, GGML_TYPE_I32, n_blk, n_pick);
|
||||
ggml_set_name(blocks, "blocks");
|
||||
}
|
||||
|
||||
ggml_tensor * extra = nullptr;
|
||||
if (n_extra > 0) {
|
||||
extra = ggml_new_tensor_4d(ctx, GGML_TYPE_I32, n_extra, nb, nh, ns);
|
||||
ggml_set_name(extra, "extra");
|
||||
}
|
||||
|
||||
ggml_tensor * out = ggml_top_k_mask(ctx, m, top_k, s, blocks, extra);
|
||||
ggml_set_name(out, "out");
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void initialize_tensors(ggml_context * ctx) override {
|
||||
std::mt19937 gen(0x4B50);
|
||||
|
||||
// cells and picks sometimes fall out of range, the op skips them
|
||||
auto init_cells = [&](ggml_tensor * t) {
|
||||
std::vector<int32_t> data(ggml_nelements(t));
|
||||
if (real) {
|
||||
// block p holds cells p*n_blk..p*n_blk + n_blk - 1, the extra cells are the last cells
|
||||
const bool is_blocks = strcmp(t->name, "blocks") == 0;
|
||||
for (int64_t i = 0; i < (int64_t) data.size(); ++i) {
|
||||
const int64_t c = is_blocks ? i : n_kv - 1 - i % t->ne[0];
|
||||
data[i] = c < n_kv ? (int32_t) c : (int32_t) n_kv;
|
||||
}
|
||||
} else {
|
||||
std::uniform_int_distribution<int32_t> cell(0, n_kv - 1);
|
||||
for (auto & c : data) {
|
||||
const uint32_t r = gen() % 16;
|
||||
c = r == 0 ? (int32_t) n_kv : r == 1 ? -1 : cell(gen);
|
||||
}
|
||||
}
|
||||
ggml_backend_tensor_set(t, data.data(), 0, data.size()*sizeof(int32_t));
|
||||
};
|
||||
|
||||
for (ggml_tensor * t = ggml_get_first_tensor(ctx); t != NULL; t = ggml_get_next_tensor(ctx, t)) {
|
||||
if (strcmp(t->name, "m") == 0) {
|
||||
if (type == GGML_TYPE_F16) {
|
||||
init_tensor_kq_mask(t);
|
||||
} else {
|
||||
std::vector<float> data(ggml_nelements(t));
|
||||
std::uniform_real_distribution<float> dis(-1.0f, 1.0f);
|
||||
for (auto & v : data) {
|
||||
v = gen() % 4 == 0 ? -INFINITY : dis(gen);
|
||||
}
|
||||
ggml_backend_tensor_set(t, data.data(), 0, data.size()*sizeof(float));
|
||||
}
|
||||
} else if (strcmp(t->name, "score") == 0) {
|
||||
// -inf marks the picks a row cannot see
|
||||
std::vector<float> data(ggml_nelements(t));
|
||||
std::uniform_real_distribution<float> dis(-1.0f, 1.0f);
|
||||
for (auto & v : data) {
|
||||
v = !real && gen() % 4 == 0 ? -INFINITY : dis(gen);
|
||||
}
|
||||
ggml_backend_tensor_set(t, data.data(), 0, data.size()*sizeof(float));
|
||||
} else if (strcmp(t->name, "top_k") == 0) {
|
||||
std::vector<int32_t> data(ggml_nelements(t));
|
||||
std::vector<int32_t> order(n_pick);
|
||||
for (int64_t r = 0; r < ggml_nrows(t); ++r) {
|
||||
for (int64_t i = 0; i < n_pick; ++i) {
|
||||
order[i] = i;
|
||||
}
|
||||
std::shuffle(order.begin(), order.end(), gen);
|
||||
for (int64_t j = 0; j < n_top; ++j) {
|
||||
const bool out = !real && j == 0 && r % 5 == 0;
|
||||
data[r*n_top + j] = out ? (r % 2 ? -1 : (int32_t) n_pick) : order[j % n_pick];
|
||||
}
|
||||
}
|
||||
ggml_backend_tensor_set(t, data.data(), 0, data.size()*sizeof(int32_t));
|
||||
} else if (strcmp(t->name, "blocks") == 0 || strcmp(t->name, "extra") == 0) {
|
||||
init_cells(t);
|
||||
} else {
|
||||
// the harness sentinels and the output, the callback checks the sentinels for NaN
|
||||
init_tensor_uniform(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Deserializable generic test case
|
||||
struct input_tensor {
|
||||
ggml_type type;
|
||||
@@ -9841,6 +10054,25 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
test_cases.emplace_back(new test_dsv4_hc_post(2560, 21, true, true));
|
||||
test_cases.emplace_back(new test_dsv4_hc_post(31, 17, false, true));
|
||||
|
||||
for (ggml_type type : {GGML_TYPE_F16, GGML_TYPE_F32}) {
|
||||
// blocks with extra cells and scores (k-pool)
|
||||
test_cases.emplace_back(new test_top_k_mask(type));
|
||||
test_cases.emplace_back(new test_top_k_mask(type, 1001, 4, 70, 16, 3, true, 7));
|
||||
test_cases.emplace_back(new test_top_k_mask(type, 256, 4, 64, 64, 0, true, 33));
|
||||
test_cases.emplace_back(new test_top_k_mask(type, 512, 8, 16, 32, 7, true, 5, 1, 4));
|
||||
test_cases.emplace_back(new test_top_k_mask(type, 4096, 4, 1024, 512, 3, true, 1));
|
||||
test_cases.emplace_back(new test_top_k_mask(type, 4096, 4, 1024, 512, 3, true, 64, 1, 2));
|
||||
// cells without scores (DSA)
|
||||
test_cases.emplace_back(new test_top_k_mask(type, 256, 0, 0, 16, 0, false, 32));
|
||||
test_cases.emplace_back(new test_top_k_mask(type, 1001, 0, 0, 64, 0, false, 7, 1, 3));
|
||||
test_cases.emplace_back(new test_top_k_mask(type, 4096, 0, 0, 2048, 0, false, 5));
|
||||
// cells with scores and extra cells
|
||||
test_cases.emplace_back(new test_top_k_mask(type, 512, 0, 0, 32, 5, true, 9));
|
||||
// blocks picked per head, the mask broadcast over the heads
|
||||
test_cases.emplace_back(new test_top_k_mask(type, 512, 16, 32, 4, 0, true, 6, 4, 1));
|
||||
test_cases.emplace_back(new test_top_k_mask(type, 1024, 8, 128, 16, 8, false, 3, 2, 2));
|
||||
}
|
||||
|
||||
// glu ops
|
||||
for (ggml_type type : {GGML_TYPE_F16, GGML_TYPE_F32, GGML_TYPE_BF16}) {
|
||||
for (int v : {0, 1}) {
|
||||
@@ -9885,9 +10117,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}) {
|
||||
@@ -12670,6 +12906,14 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_perf() {
|
||||
}
|
||||
}
|
||||
|
||||
// top_k_mask, Flash-Next QSA shapes: 512 pools of 4 + a 3-cell tail, and DSA shapes: 2048 cells
|
||||
for (int64_t n_kv : { 18432, 131072 }) {
|
||||
for (int64_t nb : { 1, 2048 }) {
|
||||
test_cases.emplace_back(new test_top_k_mask(GGML_TYPE_F16, n_kv, 4, n_kv/4, 512, 3, true, nb, 1, 1, true));
|
||||
test_cases.emplace_back(new test_top_k_mask(GGML_TYPE_F16, n_kv, 0, 0, 2048, 0, false, nb, 1, 1, true));
|
||||
}
|
||||
}
|
||||
|
||||
// launch-overhead isolation: single L2_NORM launch vs batched siblings at the GDN
|
||||
// production shape (strided qkv views) -- perf-mode only, the eval list has its own
|
||||
// 2/4-wide coverage
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -25,6 +25,7 @@ static void test_expressions(testing & t);
|
||||
static void test_set_statement(testing & t);
|
||||
static void test_filters(testing & t);
|
||||
static void test_literals(testing & t);
|
||||
static void test_hexadecimal_escapes(testing & t);
|
||||
static void test_comments(testing & t);
|
||||
static void test_macros(testing & t);
|
||||
static void test_namespace(testing & t);
|
||||
@@ -65,6 +66,7 @@ int main(int argc, char *argv[]) {
|
||||
t.test("set statement", test_set_statement);
|
||||
t.test("filters", test_filters);
|
||||
t.test("literals", test_literals);
|
||||
t.test("hexadecimal escapes", test_hexadecimal_escapes);
|
||||
t.test("comments", test_comments);
|
||||
t.test("macros", test_macros);
|
||||
t.test("namespace", test_namespace);
|
||||
@@ -1171,6 +1173,54 @@ static void test_literals(testing & t) {
|
||||
);
|
||||
}
|
||||
|
||||
static void test_hexadecimal_escapes(testing & t) {
|
||||
test_template(t, "remove NUL from message content",
|
||||
R"({{ message.content | replace("\x00", "") }})",
|
||||
{{"message", {{"content", std::string("\0a\0b\0", 5)}}}},
|
||||
"ab"
|
||||
);
|
||||
|
||||
test_template(t, "non-ASCII hex escape produces UTF-8",
|
||||
R"({{ '\xE9' }})",
|
||||
json::object(),
|
||||
"\xc3\xa9"
|
||||
);
|
||||
|
||||
test_template(t, "consume exactly two hex digits",
|
||||
R"({{ '\x414' }})",
|
||||
json::object(),
|
||||
"A4"
|
||||
);
|
||||
|
||||
test_template(t, "escaped backslashes remain literal",
|
||||
R"({{ '\\xE9' }})",
|
||||
json::object(),
|
||||
R"(\xE9)"
|
||||
);
|
||||
|
||||
if (!g_python_mode) {
|
||||
const char * invalid_templates[] = {
|
||||
R"({{ '\x' }})",
|
||||
R"({{ '\x0' }})",
|
||||
R"({{ '\xG1' }})",
|
||||
R"({{ '\x1G' }})",
|
||||
R"({{ '\x)",
|
||||
R"({{ '\x0)",
|
||||
};
|
||||
for (const auto * tmpl : invalid_templates) {
|
||||
t.test("reject " + json(tmpl).dump(), [&](testing & t) {
|
||||
bool rejected = false;
|
||||
try {
|
||||
jinja::lexer().tokenize(tmpl);
|
||||
} catch (const jinja::lexer_exception &) {
|
||||
rejected = true;
|
||||
}
|
||||
t.assert_true("malformed hex escape must raise a lexer error", rejected);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void test_comments(testing & t) {
|
||||
test_template(t, "inline comment",
|
||||
"before{# comment #}after",
|
||||
|
||||
@@ -55,7 +55,7 @@ For example, Qwen3-TTS:
|
||||
Due to wide variety of audio generation pipelines, the `mtmd_gen_audio` system is designed to be flexible and reusable by new models.
|
||||
|
||||
`mtmd_gen_audio` is split into 2 main API:
|
||||
- Core API `mtmd.h`: handles main inference. Important: the API surface must be stateless; caller must handle state management and audio frame accumulation.
|
||||
- Core API `mtmd.h`: handles main inference. Important: `mtmd_gen_state` holds only the per-sequence data that the mmproj itself writes and reads back (RNG, decoder KV / conv state, output buffers). Data that goes to or comes from the backbone (hidden states, codes, PCM) must stay explicit in `mtmd_gen_inp` / `mtmd_gen_out`; the caller drives the backbone and handles audio frame accumulation.
|
||||
- Helper API `mtmd-helper.h`: provides a model-agnostic stateful API. Usage example can be found in the `tools/tts` directory.
|
||||
|
||||
### Checklist for porting new audio generation models to mtmd
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
@@ -463,6 +464,15 @@ struct clip_enc_state {
|
||||
std::vector<ggml_tensor *> v;
|
||||
};
|
||||
|
||||
// per-sequence state of audio generation, see clip_gen_state_init()
|
||||
struct clip_gen_state {
|
||||
std::mt19937 rng;
|
||||
|
||||
ggml_context_ptr ctx_data;
|
||||
ggml_backend_buffer_ptr buf;
|
||||
std::vector<ggml_tensor *> slots; // same order as list_gen_state_slots(), F32 [ne0, ne1]
|
||||
};
|
||||
|
||||
// forward declaration
|
||||
// TODO: improve this later
|
||||
struct clip_ctx;
|
||||
|
||||
@@ -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
|
||||
|
||||
+89
-35
@@ -174,10 +174,6 @@ struct clip_ctx {
|
||||
|
||||
bool support_batch = false;
|
||||
|
||||
// for audio gen, reseeded only when the caller asks for another seed
|
||||
std::mt19937 rng{std::random_device{}()};
|
||||
uint32_t rng_seed = UINT32_MAX;
|
||||
|
||||
clip_ctx(clip_context_params & ctx_params) {
|
||||
flash_attn_type = ctx_params.flash_attn_type;
|
||||
no_alloc = ctx_params.no_alloc;
|
||||
@@ -1625,7 +1621,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 +4274,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;
|
||||
}
|
||||
|
||||
@@ -4700,6 +4713,59 @@ static std::vector<c2w_state_slot> list_gen_state_slots(const clip_hparams & hpa
|
||||
}
|
||||
}
|
||||
|
||||
// state_out can have another shape than its slot, only the byte size must match
|
||||
static void copy_gen_state_out(ggml_tensor * src, ggml_tensor * dst) {
|
||||
GGML_ASSERT(ggml_nbytes(src) == ggml_nbytes(dst));
|
||||
if (src->type == dst->type && ggml_are_same_shape(src, dst) && ggml_is_contiguous(src)) {
|
||||
ggml_backend_tensor_copy(src, dst);
|
||||
return;
|
||||
}
|
||||
std::vector<uint8_t> tmp(ggml_nbytes(src));
|
||||
ggml_backend_tensor_get(src, tmp.data(), 0, tmp.size());
|
||||
ggml_backend_tensor_set(dst, tmp.data(), 0, tmp.size());
|
||||
}
|
||||
|
||||
clip_gen_state * clip_gen_state_init(clip_ctx * ctx, uint32_t seed) {
|
||||
auto st = std::make_unique<clip_gen_state>();
|
||||
st->rng.seed(seed == UINT32_MAX ? std::random_device{}() : seed);
|
||||
|
||||
const auto slots = list_gen_state_slots(ctx->model.hparams, ctx->model);
|
||||
if (slots.empty()) {
|
||||
return st.release();
|
||||
}
|
||||
|
||||
ggml_init_params params = {
|
||||
/*.mem_size =*/ ggml_tensor_overhead() * slots.size(),
|
||||
/*.mem_buffer =*/ nullptr,
|
||||
/*.no_alloc =*/ true,
|
||||
};
|
||||
st->ctx_data.reset(ggml_init(params));
|
||||
for (const auto & slot : slots) {
|
||||
ggml_tensor * t = ggml_new_tensor_2d(st->ctx_data.get(), GGML_TYPE_F32, slot.ne0, slot.ne1);
|
||||
ggml_format_name(t, "gen_state_%s", slot.name.c_str());
|
||||
st->slots.push_back(t);
|
||||
}
|
||||
st->buf.reset(ggml_backend_alloc_ctx_tensors(st->ctx_data.get(), ctx->backend));
|
||||
if (!st->buf) {
|
||||
LOG_ERR("%s: failed to allocate the gen state\n", __func__);
|
||||
return nullptr;
|
||||
}
|
||||
// a zero state is the cold start
|
||||
ggml_backend_buffer_clear(st->buf.get(), 0);
|
||||
|
||||
return st.release();
|
||||
}
|
||||
|
||||
void clip_gen_state_free(clip_gen_state * state) {
|
||||
delete state;
|
||||
}
|
||||
|
||||
void clip_gen_state_reset(clip_gen_state * state) {
|
||||
if (state->buf) {
|
||||
ggml_backend_buffer_clear(state->buf.get(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
clip_enc_state * clip_enc_state_init(clip_ctx * ctx, int32_t n_frames_max) {
|
||||
const auto & hparams = ctx->model.hparams;
|
||||
if (ctx->proj_type() != PROJECTOR_TYPE_VOXTRAL_RT || hparams.attn_window_size <= 0) {
|
||||
@@ -4765,9 +4831,9 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
clip_model_loader::warmup(*ctx, *params->imgs);
|
||||
}
|
||||
|
||||
if (params->seed != ctx->rng_seed) {
|
||||
ctx->rng_seed = params->seed;
|
||||
ctx->rng.seed(params->seed == UINT32_MAX ? std::random_device{}() : params->seed);
|
||||
if (params->gen_process != CLIP_GEN_PROCESS_GEN_UNKNOWN && params->gen_state == nullptr) {
|
||||
LOG_ERR("%s: audio gen requires a gen state\n", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
// build the inference graph
|
||||
@@ -4831,19 +4897,13 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
ggml_backend_tensor_set(cur, values.data(), 0, ggml_nbytes(cur));
|
||||
};
|
||||
|
||||
// upload the decoder state from the previous call, or zero-fill on a cold start
|
||||
// state of the previous call, zero on a cold start
|
||||
auto set_gen_state_in = [&]() {
|
||||
size_t offset = 0;
|
||||
const auto & slots = params->gen_state->slots;
|
||||
size_t i = 0;
|
||||
for (const auto & slot : list_gen_state_slots(hparams, model)) {
|
||||
ggml_tensor * t = get_inp_tensor(("state_in_" + slot.name).c_str());
|
||||
const size_t nb = ggml_nbytes(t);
|
||||
if (params->state_in && params->state_in->size() >= offset + nb) {
|
||||
ggml_backend_tensor_set(t, params->state_in->data() + offset, 0, nb);
|
||||
} else {
|
||||
std::vector<uint8_t> zeros(nb, 0);
|
||||
ggml_backend_tensor_set(t, zeros.data(), 0, nb);
|
||||
}
|
||||
offset += nb;
|
||||
GGML_ASSERT(i < slots.size());
|
||||
ggml_backend_tensor_copy(slots[i++], get_inp_tensor(("state_in_" + slot.name).c_str()));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5567,7 +5627,7 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
std::normal_distribution<float> dist(0.0f, std::sqrt(temp));
|
||||
std::vector<float> noise(ggml_nelements(t));
|
||||
for (auto & v : noise) {
|
||||
v = dist(ctx->rng);
|
||||
v = dist(params->gen_state->rng);
|
||||
}
|
||||
set_input_f32("inp_noise", noise);
|
||||
}
|
||||
@@ -5715,7 +5775,7 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
|
||||
const int64_t n_acoustic = model.gen_code_head_w->ne[2];
|
||||
for (int64_t g = 0; g < n_acoustic; g++) {
|
||||
std::vector<float> r = { dist(ctx->rng) };
|
||||
std::vector<float> r = { dist(params->gen_state->rng) };
|
||||
set_input_f32(("inp_rand_" + std::to_string(g)).c_str(), r);
|
||||
}
|
||||
}
|
||||
@@ -6245,22 +6305,16 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
st->n_past += n_new;
|
||||
}
|
||||
|
||||
if (params->state_out != nullptr) {
|
||||
auto & state_out = *params->state_out;
|
||||
size_t total = 0;
|
||||
for (const auto & slot : list_gen_state_slots(hparams, model)) {
|
||||
total += (size_t) (slot.ne0 * slot.ne1) * sizeof(float);
|
||||
}
|
||||
state_out.resize(total);
|
||||
size_t offset = 0;
|
||||
if (params->gen_process == CLIP_GEN_PROCESS_GEN_WAV) {
|
||||
const auto & slots = params->gen_state->slots;
|
||||
size_t i = 0;
|
||||
for (const auto & slot : list_gen_state_slots(hparams, model)) {
|
||||
ggml_tensor * t = ggml_graph_get_tensor(gf, ("state_out_" + slot.name).c_str());
|
||||
if (t == nullptr) {
|
||||
GGML_ABORT("state_out requested but graph has no \"state_out_%s\" tensor", slot.name.c_str());
|
||||
GGML_ABORT("graph has no \"state_out_%s\" tensor", slot.name.c_str());
|
||||
}
|
||||
const size_t nb = ggml_nbytes(t);
|
||||
ggml_backend_tensor_get(t, state_out.data() + offset, 0, nb);
|
||||
offset += nb;
|
||||
GGML_ASSERT(i < slots.size());
|
||||
copy_gen_state_out(t, slots[i++]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-3
@@ -14,6 +14,7 @@
|
||||
|
||||
struct clip_ctx;
|
||||
struct clip_enc_state;
|
||||
struct clip_gen_state;
|
||||
|
||||
struct clip_image_size {
|
||||
int width;
|
||||
@@ -110,7 +111,6 @@ struct clip_encode_params {
|
||||
float top_p = 1.0f;
|
||||
std::vector<int32_t> * out_codes = nullptr; // this frame's 16 sampled codes
|
||||
std::vector<float> * out_feats = nullptr; // continuous counterpart of out_codes
|
||||
uint32_t seed = UINT32_MAX; // UINT32_MAX for random
|
||||
float temp = 0.0f; // sampling temperature, noise scale for flow-matching decoders
|
||||
bool * out_is_eos = nullptr;
|
||||
|
||||
@@ -118,8 +118,9 @@ struct clip_encode_params {
|
||||
const std::vector<int32_t> * codes = nullptr; // this frame's 16 RVQ codes
|
||||
const std::vector<float> * feats = nullptr; // continuous counterpart of codes
|
||||
std::vector<float> * out_audio = nullptr; // decoded PCM samples, F32
|
||||
const std::vector<uint8_t> * state_in = nullptr; // state from previous call, null or wrong size means cold start
|
||||
std::vector<uint8_t> * state_out = nullptr; // state for the next call
|
||||
|
||||
// audio gen: state of one sequence, clip_encode() updates it in place
|
||||
struct clip_gen_state * gen_state = nullptr;
|
||||
|
||||
// streaming audio input, updated in place by clip_encode()
|
||||
struct clip_enc_state * enc_state = nullptr;
|
||||
@@ -132,6 +133,12 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params);
|
||||
struct clip_enc_state * clip_enc_state_init(struct clip_ctx * ctx, int32_t n_frames_max);
|
||||
void clip_enc_state_free(struct clip_enc_state * state);
|
||||
|
||||
// per-sequence state of audio generation: RNG and the slots kept between GEN_WAV calls
|
||||
// seed UINT32_MAX means random; reset() clears the slots and keeps the RNG stream
|
||||
struct clip_gen_state * clip_gen_state_init(struct clip_ctx * ctx, uint32_t seed);
|
||||
void clip_gen_state_free(struct clip_gen_state * state);
|
||||
void clip_gen_state_reset(struct clip_gen_state * state);
|
||||
|
||||
bool clip_is_llava(const struct clip_ctx * ctx);
|
||||
// note for contributor: this clip_is_(model) pattern is deprecated
|
||||
// do NOT add new functions like this
|
||||
@@ -148,5 +155,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);
|
||||
|
||||
+164
-63
@@ -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,14 +101,29 @@ 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:
|
||||
// one state per generation, so a fixed seed always gives the same output
|
||||
bool init_gen_state(uint32_t seed) {
|
||||
mtmd_gen_state_params params = mtmd_gen_state_params_default();
|
||||
params.seed = seed;
|
||||
gen_state.reset(mtmd_gen_state_init(mctx, params));
|
||||
if (!gen_state) {
|
||||
LOG_ERR("mtmd_helper_gen_audio: failed to create the gen state\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
llama_context * lctx;
|
||||
mtmd_context * mctx;
|
||||
const llama_model * model;
|
||||
const llama_vocab * vocab;
|
||||
int n_embd;
|
||||
mtmd_gen_audio_info info;
|
||||
mtmd::gen_state_ptr gen_state;
|
||||
};
|
||||
|
||||
// Qwen3-TTS: backbone samples codec_0, code_predictor gives the other 15 codebooks,
|
||||
@@ -112,7 +136,7 @@ public:
|
||||
seq_id = 0;
|
||||
pos = 0;
|
||||
codes_buf.clear();
|
||||
c2w_state.clear();
|
||||
gen_state.reset();
|
||||
audio_pcm.clear();
|
||||
overlay.clear();
|
||||
h_state_buf.clear();
|
||||
@@ -121,6 +145,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 {
|
||||
@@ -206,8 +233,11 @@ public:
|
||||
const mtmd_gen_inp def = mtmd_gen_inp_default(mctx);
|
||||
top_k = inp->top_k > 0 ? inp->top_k : def.top_k;
|
||||
top_p = inp->top_p > 0 ? inp->top_p : def.top_p;
|
||||
seed = inp->seed;
|
||||
out_type = inp->out_type;
|
||||
stream = inp->stream;
|
||||
if (!init_gen_state(inp->seed)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -264,9 +294,8 @@ public:
|
||||
inp.embd = const_cast<float *>(h_state_in);
|
||||
inp.top_k = top_k;
|
||||
inp.top_p = top_p;
|
||||
inp.seed = seed;
|
||||
mtmd_gen_out out{};
|
||||
if (mtmd_gen_audio_process(mctx, &inp, &out) != 0) {
|
||||
if (mtmd_gen_audio_process(mctx, gen_state.get(), &inp, &out) != 0) {
|
||||
LOG_ERR("mtmd_helper_gen_audio: gen_code process failed\n");
|
||||
return 1;
|
||||
}
|
||||
@@ -301,31 +330,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 +427,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;
|
||||
@@ -405,16 +463,12 @@ private:
|
||||
inp.type = MTMD_GEN_PROCESS_TYPE_GEN_WAV;
|
||||
inp.codes = codes_buf.data();
|
||||
inp.n_codes = codes_buf.size();
|
||||
inp.seed = seed; // same seed as gen_code, else clip reseeds mid-generation
|
||||
inp.state_data = c2w_state.empty() ? nullptr : (const char *) c2w_state.data();
|
||||
inp.state_size = c2w_state.size();
|
||||
mtmd_gen_out out{};
|
||||
if (mtmd_gen_audio_process(mctx, &inp, &out) != 0) {
|
||||
if (mtmd_gen_audio_process(mctx, gen_state.get(), &inp, &out) != 0) {
|
||||
LOG_ERR("mtmd_helper_gen_audio: gen_wav process failed\n");
|
||||
return false;
|
||||
}
|
||||
audio_pcm.insert(audio_pcm.end(), out.audio, out.audio + out.n_samples);
|
||||
c2w_state.assign(out.state_data, out.state_data + out.state_size);
|
||||
codes_buf.clear();
|
||||
return true;
|
||||
}
|
||||
@@ -447,14 +501,15 @@ private:
|
||||
int prompt_pos = 0;
|
||||
int32_t top_k = 50;
|
||||
float top_p = 1.0f;
|
||||
uint32_t seed = UINT32_MAX;
|
||||
std::vector<int32_t> codes_buf;
|
||||
std::vector<uint8_t> c2w_state;
|
||||
std::vector<float> audio_pcm;
|
||||
std::vector<float> overlay;
|
||||
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
|
||||
@@ -492,7 +547,7 @@ public:
|
||||
seq_id = 0;
|
||||
pos = 0;
|
||||
feats_buf.clear();
|
||||
dec_state.clear();
|
||||
gen_state.reset();
|
||||
audio_pcm.clear();
|
||||
h_state_buf.clear();
|
||||
out_buf.clear();
|
||||
@@ -506,6 +561,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 {
|
||||
@@ -573,8 +631,11 @@ public:
|
||||
prompt_batch->set_position_normal(0, seq_id);
|
||||
prompt_pos = 0;
|
||||
|
||||
seed = inp->seed;
|
||||
out_type = inp->out_type;
|
||||
stream = inp->stream;
|
||||
if (!init_gen_state(inp->seed)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -612,13 +673,11 @@ public:
|
||||
mtmd_gen_inp inp = mtmd_gen_inp_default(mctx);
|
||||
inp.type = MTMD_GEN_PROCESS_TYPE_GEN_CODE;
|
||||
inp.embd = const_cast<float *>(h_state_in);
|
||||
// clip only reseeds when the seed changes, so pass the same one on every step
|
||||
inp.seed = seed;
|
||||
if (pack.temp > 0.0f) {
|
||||
inp.temp = pack.temp;
|
||||
}
|
||||
mtmd_gen_out out{};
|
||||
if (mtmd_gen_audio_process(mctx, &inp, &out) != 0) {
|
||||
if (mtmd_gen_audio_process(mctx, gen_state.get(), &inp, &out) != 0) {
|
||||
LOG_ERR("mtmd_helper_gen_audio: flow decode failed\n");
|
||||
return 1;
|
||||
}
|
||||
@@ -662,31 +721,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) {
|
||||
@@ -815,7 +903,7 @@ private:
|
||||
return 1;
|
||||
}
|
||||
// the decoder restarts too, the next chunk's audio is not continuous with this one
|
||||
dec_state.clear();
|
||||
mtmd_gen_state_reset(gen_state.get());
|
||||
|
||||
if (chunk_idx + 1 >= chunks.size()) {
|
||||
*out_stop = true;
|
||||
@@ -907,7 +995,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;
|
||||
@@ -943,16 +1031,12 @@ private:
|
||||
inp.type = MTMD_GEN_PROCESS_TYPE_GEN_WAV;
|
||||
inp.feats = feats_buf.data();
|
||||
inp.n_feats = feats_buf.size();
|
||||
inp.seed = seed;
|
||||
inp.state_data = dec_state.empty() ? nullptr : (const char *) dec_state.data();
|
||||
inp.state_size = dec_state.size();
|
||||
mtmd_gen_out out{};
|
||||
if (mtmd_gen_audio_process(mctx, &inp, &out) != 0) {
|
||||
if (mtmd_gen_audio_process(mctx, gen_state.get(), &inp, &out) != 0) {
|
||||
LOG_ERR("mtmd_helper_gen_audio: mimi decode failed\n");
|
||||
return false;
|
||||
}
|
||||
audio_pcm.insert(audio_pcm.end(), out.audio, out.audio + out.n_samples);
|
||||
dec_state.assign(out.state_data, out.state_data + out.state_size);
|
||||
feats_buf.clear();
|
||||
return true;
|
||||
}
|
||||
@@ -969,7 +1053,6 @@ private:
|
||||
std::unique_ptr<decode_embd_batch> prompt_batch;
|
||||
int n_prompt = 0;
|
||||
int prompt_pos = 0;
|
||||
uint32_t seed = UINT32_MAX;
|
||||
// end-of-speech is latched, then a few more frames are generated as tail padding
|
||||
int step_idx = 0;
|
||||
int eos_step = -1;
|
||||
@@ -984,11 +1067,13 @@ private:
|
||||
// latents are decoded a window at a time, the decoder state bridges the windows
|
||||
size_t window_frames = 8;
|
||||
std::vector<float> feats_buf;
|
||||
std::vector<uint8_t> dec_state;
|
||||
std::vector<float> audio_pcm;
|
||||
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 +1102,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 +1152,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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+69
-33
@@ -523,11 +523,6 @@ struct mtmd_context {
|
||||
|
||||
// generation context
|
||||
struct clip_ctx * ctx_gen_a; // audio
|
||||
std::vector<int32_t> gen_out_codes; // this frame's 16 sampled codes (GEN_CODE)
|
||||
std::vector<float> gen_out_feats; // this frame's continuous features, if any (GEN_CODE)
|
||||
std::vector<float> gen_out_embd; // next-step hidden state fed back to backbone (GEN_CODE)
|
||||
std::vector<float> gen_out_audio; // decoded PCM samples for the current frame (GEN_WAV)
|
||||
std::vector<uint8_t> gen_out_state; // state to feed into the next GEN_WAV call
|
||||
|
||||
bool print_timings;
|
||||
int n_threads;
|
||||
@@ -804,7 +799,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:
|
||||
{
|
||||
@@ -2076,7 +2075,6 @@ mtmd_gen_audio_info mtmd_gen_audio_get_info(const mtmd_context * ctx) {
|
||||
mtmd_gen_inp mtmd_gen_inp_default(const mtmd_context * ctx) {
|
||||
mtmd_gen_inp inp{};
|
||||
inp.type = MTMD_GEN_PROCESS_TYPE_GEN_CODE;
|
||||
inp.seed = UINT32_MAX;
|
||||
if (!ctx->ctx_gen_a) {
|
||||
return inp;
|
||||
}
|
||||
@@ -2100,12 +2098,58 @@ mtmd_gen_inp mtmd_gen_inp_default(const mtmd_context * ctx) {
|
||||
return inp;
|
||||
}
|
||||
|
||||
static int32_t mtmd_gen_audio_process_impl(mtmd_context * ctx, const mtmd_gen_inp * inp, mtmd_gen_out * out) {
|
||||
struct mtmd_gen_state {
|
||||
clip_ctx * ctx_clip = nullptr;
|
||||
clip_gen_state * clip_state = nullptr;
|
||||
|
||||
std::vector<int32_t> out_codes; // this frame's 16 sampled codes (GEN_CODE)
|
||||
std::vector<float> out_feats; // this frame's continuous features, if any (GEN_CODE)
|
||||
std::vector<float> out_embd; // next-step hidden state fed back to backbone (GEN_CODE)
|
||||
std::vector<float> out_audio; // decoded PCM samples for the current frame (GEN_WAV)
|
||||
|
||||
~mtmd_gen_state() {
|
||||
clip_gen_state_free(clip_state);
|
||||
}
|
||||
};
|
||||
|
||||
mtmd_gen_state_params mtmd_gen_state_params_default(void) {
|
||||
mtmd_gen_state_params params{};
|
||||
params.seed = UINT32_MAX;
|
||||
return params;
|
||||
}
|
||||
|
||||
mtmd_gen_state * mtmd_gen_state_init(mtmd_context * ctx, mtmd_gen_state_params params) {
|
||||
if (!ctx->ctx_gen_a) {
|
||||
LOG_ERR("%s: model does not support audio generation\n", __func__);
|
||||
return nullptr;
|
||||
}
|
||||
auto st = std::make_unique<mtmd_gen_state>();
|
||||
st->ctx_clip = ctx->ctx_gen_a;
|
||||
st->clip_state = clip_gen_state_init(ctx->ctx_gen_a, params.seed);
|
||||
if (!st->clip_state) {
|
||||
return nullptr;
|
||||
}
|
||||
return st.release();
|
||||
}
|
||||
|
||||
void mtmd_gen_state_free(mtmd_gen_state * state) {
|
||||
delete state;
|
||||
}
|
||||
|
||||
void mtmd_gen_state_reset(mtmd_gen_state * state) {
|
||||
clip_gen_state_reset(state->clip_state);
|
||||
}
|
||||
|
||||
static int32_t mtmd_gen_audio_process_impl(mtmd_context * ctx, mtmd_gen_state * state, const mtmd_gen_inp * inp, mtmd_gen_out * out) {
|
||||
clip_ctx * ctx_clip = ctx->ctx_gen_a;
|
||||
if (!ctx_clip) {
|
||||
LOG_ERR("%s: model does not support audio generation\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
if (!state || state->ctx_clip != ctx_clip) {
|
||||
LOG_ERR("%s: invalid gen state\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
*out = {};
|
||||
|
||||
@@ -2135,24 +2179,24 @@ static int32_t mtmd_gen_audio_process_impl(mtmd_context * ctx, const mtmd_gen_in
|
||||
params.code0 = inp->code0;
|
||||
params.top_k = inp->top_k;
|
||||
params.top_p = inp->top_p;
|
||||
params.seed = inp->seed;
|
||||
params.temp = inp->temp;
|
||||
params.out_is_eos = &is_eos;
|
||||
params.gen_state = state->clip_state;
|
||||
|
||||
if (!clip_encode(ctx_clip, ¶ms)) {
|
||||
LOG_ERR("%s: clip_encode failed (gen_code)\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ctx->gen_out_embd = std::move(out_embd);
|
||||
ctx->gen_out_codes = std::move(out_codes);
|
||||
ctx->gen_out_feats = std::move(out_feats);
|
||||
state->out_embd = std::move(out_embd);
|
||||
state->out_codes = std::move(out_codes);
|
||||
state->out_feats = std::move(out_feats);
|
||||
|
||||
out->embd = ctx->gen_out_embd.data();
|
||||
out->codes = ctx->gen_out_codes.data();
|
||||
out->n_codes = ctx->gen_out_codes.size();
|
||||
out->feats = ctx->gen_out_feats.data();
|
||||
out->n_feats = ctx->gen_out_feats.size();
|
||||
out->embd = state->out_embd.data();
|
||||
out->codes = state->out_codes.data();
|
||||
out->n_codes = state->out_codes.size();
|
||||
out->feats = state->out_feats.data();
|
||||
out->n_feats = state->out_feats.size();
|
||||
out->is_eos = is_eos;
|
||||
return 0;
|
||||
}
|
||||
@@ -2171,10 +2215,6 @@ static int32_t mtmd_gen_audio_process_impl(mtmd_context * ctx, const mtmd_gen_in
|
||||
} else {
|
||||
in_feats.assign(inp->feats, inp->feats + inp->n_feats);
|
||||
}
|
||||
std::vector<uint8_t> in_state;
|
||||
if (inp->state_data) {
|
||||
in_state.assign(inp->state_data, inp->state_data + inp->state_size);
|
||||
}
|
||||
|
||||
// gen_wav has no hidden-state input, the batch entry is an unused placeholder
|
||||
// TODO @ngxson : some models in the future may require hidden-state input, need to update this code later
|
||||
@@ -2190,30 +2230,25 @@ static int32_t mtmd_gen_audio_process_impl(mtmd_context * ctx, const mtmd_gen_in
|
||||
params.imgs = &batch;
|
||||
params.n_threads = ctx->n_threads;
|
||||
params.gen_process = CLIP_GEN_PROCESS_GEN_WAV;
|
||||
// gen_wav draws no randomness, but keep the seed so it does not reseed mid-generation
|
||||
params.seed = inp->seed;
|
||||
params.codes = has_codes ? &in_codes : nullptr;
|
||||
params.feats = has_feats ? &in_feats : nullptr;
|
||||
params.out_audio = &ctx->gen_out_audio;
|
||||
params.state_in = inp->state_data ? &in_state : nullptr;
|
||||
params.state_out = &ctx->gen_out_state;
|
||||
params.out_audio = &state->out_audio;
|
||||
params.gen_state = state->clip_state;
|
||||
|
||||
if (!clip_encode(ctx_clip, ¶ms)) {
|
||||
LOG_ERR("%s: clip_encode failed (code2wav)\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
out->audio = ctx->gen_out_audio.data();
|
||||
out->n_samples = ctx->gen_out_audio.size();
|
||||
out->state_data = (const char *) ctx->gen_out_state.data();
|
||||
out->state_size = ctx->gen_out_state.size();
|
||||
out->audio = state->out_audio.data();
|
||||
out->n_samples = state->out_audio.size();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t mtmd_gen_audio_process(mtmd_context * ctx, const struct mtmd_gen_inp * inp, struct mtmd_gen_out * out) {
|
||||
int32_t mtmd_gen_audio_process(mtmd_context * ctx, mtmd_gen_state * state, const struct mtmd_gen_inp * inp, struct mtmd_gen_out * out) {
|
||||
try {
|
||||
return mtmd_gen_audio_process_impl(ctx, inp, out);
|
||||
return mtmd_gen_audio_process_impl(ctx, state, inp, out);
|
||||
} catch (const std::exception & e) {
|
||||
LOG_ERR("%s: error: %s\n", __func__, e.what());
|
||||
return 1;
|
||||
@@ -2842,10 +2877,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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-8
@@ -65,6 +65,7 @@ struct mtmd_image_tokens;
|
||||
struct mtmd_input_chunk;
|
||||
struct mtmd_input_chunks;
|
||||
struct mtmd_batch;
|
||||
struct mtmd_gen_state;
|
||||
|
||||
struct mtmd_input_text {
|
||||
const char * text;
|
||||
@@ -79,6 +80,7 @@ struct mtmd_input_part {
|
||||
const struct mtmd_bitmap * bitmap;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// C API
|
||||
//
|
||||
@@ -91,6 +93,7 @@ typedef struct mtmd_input_chunks mtmd_input_chunks;
|
||||
typedef struct mtmd_input_text mtmd_input_text;
|
||||
typedef struct mtmd_input_part mtmd_input_part;
|
||||
typedef struct mtmd_batch mtmd_batch;
|
||||
typedef struct mtmd_gen_state mtmd_gen_state;
|
||||
|
||||
typedef bool (*mtmd_progress_callback)(float progress, void * user_data);
|
||||
|
||||
@@ -360,6 +363,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);
|
||||
|
||||
@@ -442,8 +446,7 @@ struct mtmd_gen_inp {
|
||||
float * embd; // the hidden state from backbone, must have n_text_embd elements
|
||||
int32_t top_k;
|
||||
float top_p;
|
||||
uint32_t seed; // UINT32_MAX for random
|
||||
float temp; // sampling temperature, or noise scale for flow-matching decoders
|
||||
float temp; // sampling temperature, or noise scale for flow-matching decoders
|
||||
|
||||
// for MTMD_GEN_PROCESS_TYPE_GEN_WAV
|
||||
// pass either codes (discrete) or feats (continuous), depending on the pipeline
|
||||
@@ -451,12 +454,10 @@ struct mtmd_gen_inp {
|
||||
size_t n_codes;
|
||||
const float * feats;
|
||||
size_t n_feats;
|
||||
const char * state_data;
|
||||
size_t state_size;
|
||||
};
|
||||
|
||||
struct mtmd_gen_out {
|
||||
// note: output memory is allocated by the context, valid until next process() call
|
||||
// note: output memory is owned by the state, valid until the next process() call with this state
|
||||
|
||||
// for MTMD_GEN_PROCESS_TYPE_GEN_CODE
|
||||
const int32_t * codes;
|
||||
@@ -470,15 +471,28 @@ struct mtmd_gen_out {
|
||||
// for MTMD_GEN_PROCESS_TYPE_GEN_WAV
|
||||
const float * audio;
|
||||
size_t n_samples;
|
||||
const char * state_data;
|
||||
size_t state_size;
|
||||
};
|
||||
|
||||
struct mtmd_gen_state_params {
|
||||
uint32_t seed; // UINT32_MAX for random
|
||||
};
|
||||
|
||||
MTMD_API struct mtmd_gen_state_params mtmd_gen_state_params_default(void);
|
||||
|
||||
// returns nullptr if the model does not support audio generation
|
||||
MTMD_API mtmd_gen_state * mtmd_gen_state_init(mtmd_context * ctx, struct mtmd_gen_state_params params);
|
||||
MTMD_API void mtmd_gen_state_free(mtmd_gen_state * state);
|
||||
|
||||
// clear the decoder state (cold start), keep the RNG stream
|
||||
MTMD_API void mtmd_gen_state_reset(mtmd_gen_state * state);
|
||||
|
||||
// defaults tuned for the loaded pipeline, callers override only what they care about
|
||||
MTMD_API struct mtmd_gen_inp mtmd_gen_inp_default(const mtmd_context * ctx);
|
||||
|
||||
// note: this API is stateless, caller must handle state management and audio frame accumulation
|
||||
// run one step and update the state in place
|
||||
// the caller drives the backbone and collects the audio frames
|
||||
MTMD_API int32_t mtmd_gen_audio_process(mtmd_context * ctx,
|
||||
mtmd_gen_state * state,
|
||||
const struct mtmd_gen_inp * inp,
|
||||
struct mtmd_gen_out * out);
|
||||
|
||||
@@ -543,6 +557,11 @@ struct mtmd_audio_stream_deleter {
|
||||
};
|
||||
using audio_stream_ptr = std::unique_ptr<mtmd_audio_stream, mtmd_audio_stream_deleter>;
|
||||
|
||||
struct mtmd_gen_state_deleter {
|
||||
void operator()(mtmd_gen_state * val) { mtmd_gen_state_free(val); }
|
||||
};
|
||||
using gen_state_ptr = std::unique_ptr<mtmd_gen_state, mtmd_gen_state_deleter>;
|
||||
|
||||
struct bitmap {
|
||||
bitmap_ptr ptr;
|
||||
bitmap() : ptr(nullptr) {}
|
||||
|
||||
+78
-3
@@ -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:*
|
||||
@@ -1649,6 +1719,8 @@ See [Anthropic Messages API documentation](https://docs.anthropic.com/en/api/mes
|
||||
|
||||
`top_k`: Top-k sampling
|
||||
|
||||
`extra_params`: llama.cpp parameters that the Anthropic API has no field for, such as `seed`, `min_p` or `repeat_penalty`, as an object (e.g. `{"seed": 42, "min_p": 0.05}`). Its keys are merged into the converted request and behave as on `/v1/chat/completions`.
|
||||
|
||||
`stop_sequences`: Array of stop sequences
|
||||
|
||||
`stream`: Enable streaming (default: false)
|
||||
@@ -1669,7 +1741,8 @@ curl http://localhost:9931/v1/messages \
|
||||
"system": "You are a helpful assistant.",
|
||||
"messages": [
|
||||
{"role": "user", "content": "Hello!"}
|
||||
]
|
||||
],
|
||||
"extra_params": {"seed": 42, "min_p": 0.05}
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -1992,6 +2065,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 +2102,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
|
||||
{
|
||||
|
||||
@@ -615,6 +615,13 @@ json server_chat_convert_anthropic_to_oai(const json & body) {
|
||||
}
|
||||
}
|
||||
|
||||
// llama.cpp params (e.g. seed, min_p) go under extra_params and are merged into the converted body
|
||||
if (body.contains("extra_params") && body.at("extra_params").is_object()) {
|
||||
for (const auto & [key, value] : body.at("extra_params").items()) {
|
||||
oai_body[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return oai_body;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Vendored
+1
-1
@@ -43,7 +43,7 @@ if (LLAMA_BUILD_BORINGSSL)
|
||||
set(FIPS OFF CACHE BOOL "Enable FIPS (BoringSSL)")
|
||||
|
||||
set(BORINGSSL_GIT "https://boringssl.googlesource.com/boringssl" CACHE STRING "BoringSSL git repository")
|
||||
set(BORINGSSL_VERSION "0.20260929.0" CACHE STRING "BoringSSL version")
|
||||
set(BORINGSSL_VERSION "0.20261020.0" CACHE STRING "BoringSSL version")
|
||||
|
||||
message(STATUS "Fetching BoringSSL version ${BORINGSSL_VERSION}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user