Compare commits

..

17 Commits

Author SHA1 Message Date
Xuan Son Nguyen 99b9a6c08a also show model aliases 2026-07-08 00:35:05 +02:00
Xuan Son Nguyen 1729662ea3 nits fixes 2026-07-07 22:25:21 +02:00
Xuan Son Nguyen 50ed8076fb no more json in header 2026-07-07 22:10:31 +02:00
Xuan Son Nguyen a87b2d77cf pimpl 2026-07-07 21:56:44 +02:00
Xuan Son Nguyen b9617e860a cli-view --> cli-ui 2026-07-07 21:39:11 +02:00
Xuan Son Nguyen 28b71c022a add ftype 2026-07-07 21:36:55 +02:00
Xuan Son Nguyen 7cd7832297 Merge branch 'master' into xsn/cli_http_based 2026-07-07 21:11:05 +02:00
Xuan Son Nguyen a432e6f863 use destructor instead 2026-06-23 22:57:20 +02:00
Xuan Son Nguyen 5d67f69f59 remove outdated comment 2026-06-23 22:49:40 +02:00
Xuan-Son Nguyen beef5cf077 Apply suggestions from code review
Co-authored-by: Piotr Wilkin (ilintar) <piotr.wilkin@syndatis.com>
2026-06-23 22:48:04 +02:00
Xuan Son Nguyen b093e46873 case: router with only one model 2026-06-23 16:47:30 +02:00
Xuan Son Nguyen 1401fc3ca7 cli support router mode
Co-authored-by: Piotr Wilkin <ilintar@gmail.com>
2026-06-23 16:43:58 +02:00
Xuan Son Nguyen 85c58bbcd0 remote server ok 2026-06-23 16:19:28 +02:00
Xuan Son Nguyen 19296c1735 working 2026-06-23 16:09:09 +02:00
Xuan Son Nguyen 90c111bf98 Merge branch 'master' into xsn/cli_http_based 2026-06-23 13:29:22 +02:00
Xuan Son Nguyen f7421eabe8 wip 2026-06-23 13:28:14 +02:00
Xuan Son Nguyen 59797670dc cli: move to HTTP-based implementation 2026-06-23 13:14:28 +02:00
117 changed files with 2427 additions and 4585 deletions
-4
View File
@@ -9,8 +9,6 @@ on:
'.github/workflows/hip-quality-check.yml',
'**/*.cu',
'**/*.cuh',
'ggml/src/ggml-hip/CMakeLists.txt',
'ggml/src/ggml-cuda/vendors/hip.h',
'scripts/hip/gcn-cdna-vgpr-check.py'
]
@@ -20,8 +18,6 @@ on:
'.github/workflows/hip-quality-check.yml',
'**/*.cu',
'**/*.cuh',
'ggml/src/ggml-hip/CMakeLists.txt',
'ggml/src/ggml-cuda/vendors/hip.h',
'scripts/hip/gcn-cdna-vgpr-check.py'
]
+1 -7
View File
@@ -27,7 +27,6 @@
#include <cinttypes>
#include <climits>
#include <cstdarg>
#include <filesystem>
#include <fstream>
#include <list>
#include <regex>
@@ -3458,14 +3457,9 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
).set_env("LLAMA_ARG_LOG_FILE"));
add_opt(common_arg(
{"--log-prompts-dir"}, "PATH",
"Log prompts to directory (auto-created if not present; only used for debugging, default: disabled)",
"Log prompts to directory (only used for debugging, default: disabled)",
[](common_params & params, const std::string & value) {
params.path_prompts_log_dir = value;
std::error_code ec;
std::filesystem::create_directories(value, ec);
if (ec) {
fprintf(stderr, "warning: failed to create prompts-log-dir '%s': %s\n", value.c_str(), ec.message().c_str());
}
}
).set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}));
add_opt(common_arg(
+2 -2
View File
@@ -362,7 +362,7 @@ class EvalState:
case = cases.get(task_id, {})
status = case.get("status", "pending")
expected = case.get("expected", "")
answer = case.get("answer") or "" if status == "ok" else ""
answer = case.get("answer", "") if status == "ok" else ""
is_correct = case.get("correct", False) if status == "ok" else False
response = case.get("response", "") or ""
prompt = case.get("prompt", "") or ""
@@ -647,7 +647,7 @@ class EvalState:
question, prompt, expected = self.get_case(i)
case = cases.get(task_id, {})
status = case.get("status", "pending")
answer = case.get("answer") or "N/A" if status == "ok" else "N/A"
answer = case.get("answer", "N/A") if status == "ok" else "N/A"
tokens = case.get("tokens")
tokens_str = str(tokens) if tokens is not None else "N/A"
tps_gen = case.get("tps_gen")
+8 -33
View File
@@ -5025,8 +5025,8 @@ void ggml_compute_forward_get_rows(
//}
}
template<typename src_t, typename idx_t>
static void ggml_compute_forward_set_rows_impl(
template<typename idx_t>
static void ggml_compute_forward_set_rows_f32(
const ggml_compute_params * params,
ggml_tensor * dst) {
@@ -5041,7 +5041,7 @@ static void ggml_compute_forward_set_rows_impl(
assert(ne0 == nc);
assert(ne2 == ne02);
assert(ne3 == ne03);
GGML_ASSERT(src0->type == GGML_TYPE_F32 || (src0->type == GGML_TYPE_F16 && dst->type == GGML_TYPE_F16));
assert(src0->type == GGML_TYPE_F32);
assert(ne02 % ne11 == 0);
assert(ne03 % ne12 == 0);
@@ -5055,8 +5055,6 @@ static void ggml_compute_forward_set_rows_impl(
const int64_t ir0 = dr*ith;
const int64_t ir1 = std::min(ir0 + dr, nr);
const size_t rs = ggml_row_size(src0->type, nc);
ggml_from_float_t const from_float = ggml_get_type_traits_cpu(dst->type)->from_float;
for (int64_t i03 = 0; i03 < ne03; ++i03) {
@@ -5070,18 +5068,9 @@ static void ggml_compute_forward_set_rows_impl(
GGML_ASSERT(i1 >= 0 && i1 < ne1);
if constexpr (std::is_same_v<src_t, float>) {
from_float(
(const float *) ((char *) src0->data + i*nb01 + i02*nb02 + i03*nb03),
((char *) dst->data + i1*nb1 + i02*nb2 + i03*nb3), nc);
} else if constexpr (std::is_same_v<src_t, ggml_fp16_t>) {
memcpy(
((char *) dst->data + i1*nb1 + i02*nb2 + i03*nb3),
((char *) src0->data + i*nb01 + i02*nb02 + i03*nb03),
rs);
} else {
GGML_ABORT("src0->type = %d (%s) not supported", src0->type, ggml_type_name(src0->type));
}
from_float(
(const float *) ((char *) src0->data + i*nb01 + i02*nb02 + i03*nb03),
((char *) dst->data + i1*nb1 + i02*nb2 + i03*nb3), nc);
}
}
}
@@ -5098,27 +5087,13 @@ void ggml_compute_forward_set_rows(
case GGML_TYPE_F32:
{
if (src1->type == GGML_TYPE_I64) {
ggml_compute_forward_set_rows_impl<float, int64_t>(params, dst);
ggml_compute_forward_set_rows_f32<int64_t>(params, dst);
} else if (src1->type == GGML_TYPE_I32) {
ggml_compute_forward_set_rows_impl<float, int32_t>(params, dst);
ggml_compute_forward_set_rows_f32<int32_t>(params, dst);
} else {
GGML_ABORT("src1->type = %d (%s) not supported", src1->type, ggml_type_name(src1->type));
}
} break;
case GGML_TYPE_F16:
{
if (dst->type == GGML_TYPE_F16) {
if (src1->type == GGML_TYPE_I64) {
ggml_compute_forward_set_rows_impl<ggml_fp16_t, int64_t>(params, dst);
} else if (src1->type == GGML_TYPE_I32) {
ggml_compute_forward_set_rows_impl<ggml_fp16_t, int32_t>(params, dst);
} else {
GGML_ABORT("src1->type = %d (%s) not supported", src1->type, ggml_type_name(src1->type));
}
} else {
GGML_ABORT("dst->type = %d (%s) not supported with src0->type = %d (%s)", dst->type, ggml_type_name(dst->type), src0->type, ggml_type_name(src0->type));
}
} break;
default:
{
GGML_ABORT("src0->type = %d (%s) not supported", src0->type, ggml_type_name(src0->type));
+1 -1
View File
@@ -78,7 +78,7 @@ static void simd_gemm(
for (int64_t i = 0; i < GEMM_RM; i++) {
float a = C[i * N + jj];
for (int64_t kk = 0; kk < K; kk++) {
a += A[i * K + kk] * B[kk * N + jj];
a += A[i + kk] * B[kk * N + jj];
}
C[i * N + jj] = a;
}
+4 -10
View File
@@ -4709,16 +4709,10 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
} break;
case GGML_OP_SET_ROWS:
{
return (
(
(op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16 || op->type == GGML_TYPE_BF16 ||
op->type == GGML_TYPE_Q4_0 || op->type == GGML_TYPE_Q4_1 || op->type == GGML_TYPE_Q5_0 ||
op->type == GGML_TYPE_Q5_1 || op->type == GGML_TYPE_Q8_0 || op->type == GGML_TYPE_IQ4_NL) &&
op->src[0]->type == GGML_TYPE_F32
) || (
op->type == GGML_TYPE_F16 && op->src[0]->type == GGML_TYPE_F16
)
) &&
return (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16 || op->type == GGML_TYPE_BF16 ||
op->type == GGML_TYPE_Q4_0 || op->type == GGML_TYPE_Q4_1 || op->type == GGML_TYPE_Q5_0 ||
op->type == GGML_TYPE_Q5_1 || op->type == GGML_TYPE_Q8_0 || op->type == GGML_TYPE_IQ4_NL) &&
op->src[0]->type == GGML_TYPE_F32 &&
(op->src[1]->type == GGML_TYPE_I64 || op->src[1]->type == GGML_TYPE_I32);
} break;
case GGML_OP_SET:
+4 -64
View File
@@ -322,77 +322,17 @@ static void set_rows_cuda(ggml_backend_cuda_context & ctx, const ggml_tensor * s
}
}
template<>
void set_rows_cuda<half, int32_t>(ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) {
const half * src0_d = (const half *)src0->data;
const int32_t * src1_d = (const int32_t *)src1->data;
GGML_TENSOR_BINARY_OP_LOCALS
cudaStream_t stream = ctx.stream();
if (dst->type == GGML_TYPE_F16) {
set_rows_cuda(
src0_d, src1_d, (half*)dst->data,
ne00, ne01, ne02, ne03,
ne10, ne11, ne12, ne13,
nb01, nb02, nb03,
nb10, nb11, nb12,
nb1, nb2, nb3,
stream
);
} else {
GGML_ABORT("unsupported type %s", ggml_type_name(dst->type));
}
}
template<>
void set_rows_cuda<half, int64_t>(ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) {
const half * src0_d = (const half *)src0->data;
const int64_t * src1_d = (const int64_t *)src1->data;
GGML_TENSOR_BINARY_OP_LOCALS
cudaStream_t stream = ctx.stream();
if (dst->type == GGML_TYPE_F16) {
set_rows_cuda(
src0_d, src1_d, (half*)dst->data,
ne00, ne01, ne02, ne03,
ne10, ne11, ne12, ne13,
nb01, nb02, nb03,
nb10, nb11, nb12,
nb1, nb2, nb3,
stream
);
} else {
GGML_ABORT("unsupported type %s", ggml_type_name(dst->type));
}
}
void ggml_cuda_op_set_rows(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
const ggml_tensor * src0 = dst->src[0];
const ggml_tensor * src1 = dst->src[1];
GGML_ASSERT(src0->type == GGML_TYPE_F32 || (src0->type == GGML_TYPE_F16 && dst->type == GGML_TYPE_F16));
GGML_ASSERT(src0->type == GGML_TYPE_F32);
GGML_ASSERT(src1->type == GGML_TYPE_I64 || src1->type == GGML_TYPE_I32);
if (src0->type == GGML_TYPE_F32) {
if (src1->type == GGML_TYPE_I64) {
set_rows_cuda<float, int64_t>(ctx, src0, src1, dst);
} else {
set_rows_cuda<float, int32_t>(ctx, src0, src1, dst);
}
} else if (src0->type == GGML_TYPE_F16) {
if (src1->type == GGML_TYPE_I64) {
set_rows_cuda<half, int64_t>(ctx, src0, src1, dst);
} else {
set_rows_cuda<half, int32_t>(ctx, src0, src1, dst);
}
if (src1->type == GGML_TYPE_I64) {
set_rows_cuda<float, int64_t>(ctx, src0, src1, dst);
} else {
GGML_ABORT("unsupported type %s", ggml_type_name(src0->type));
set_rows_cuda<float, int32_t>(ctx, src0, src1, dst);
}
}
+176 -134
View File
@@ -2028,10 +2028,10 @@ static bool ggml_hexagon_precompute_flash_attn_params(
kparams->u.hvx.size_v_row_padded = size_v_row_padded;
kparams->u.hvx.src0_div21 = init_fastdiv_values(q->ne[2] * q->ne[1]);
kparams->u.hvx.src0_div1 = init_fastdiv_values(q->ne[1]);
kparams->broadcast_rk2 = init_fastdiv_values(q->ne[2]/k->ne[2]);
kparams->broadcast_rk3 = init_fastdiv_values(q->ne[3]/k->ne[3]);
kparams->broadcast_rv2 = init_fastdiv_values(q->ne[2]/v->ne[2]);
kparams->broadcast_rv3 = init_fastdiv_values(q->ne[3]/v->ne[3]);
kparams->u.hvx.broadcast_rk2 = init_fastdiv_values(q->ne[2]/k->ne[2]);
kparams->u.hvx.broadcast_rk3 = init_fastdiv_values(q->ne[3]/k->ne[3]);
kparams->u.hvx.broadcast_rv2 = init_fastdiv_values(q->ne[2]/v->ne[2]);
kparams->u.hvx.broadcast_rv3 = init_fastdiv_values(q->ne[3]/v->ne[3]);
if (mask) {
kparams->src3_div2 = init_fastdiv_values(mask->ne[2]);
kparams->src3_div3 = init_fastdiv_values(mask->ne[3]);
@@ -2385,30 +2385,31 @@ static void ggml_hexagon_precompute_hvx_mm_params(
kparams->kernel_type = (src1_nrows < (int) sess->n_threads) ? HTP_MM_KERNEL_HVX_QUANT_BLOCK : HTP_MM_KERNEL_HVX_QUANT_ROW;
kparams->src1_row_size = (wtype == GGML_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
struct htp_mm_hvx_vtcm_layout L;
size_t vtcm_src0_size = 0, vtcm_src1_size = 0, vtcm_dst_size = 0;
uint32_t max_prefetch = (src1_nrows > HTP_MM_HMX_MIN_NROWS) ? 2 : 16;
uint32_t best_n_prefetch = 2;
size_t total_size = 0;
for (uint32_t d = max_prefetch; d >= 2; d /= 2) {
htp_mm_hvx_vtcm_layout_build(
&L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
0, src0->nb[1], 0, d, true, false, false
total_size = htp_mm_hvx_id_get_vtcm_sizes(
wtype, ne10, src1_nrows, sess->n_threads, src0->nb[1], d,
&vtcm_src0_size, &vtcm_src1_size, &vtcm_dst_size
);
if (L.total_bytes <= vtcm_budget) {
if (total_size <= vtcm_budget) {
best_n_prefetch = d;
break;
}
}
if (best_n_prefetch == 2 && L.total_bytes > vtcm_budget) {
htp_mm_hvx_vtcm_layout_build(
&L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
0, src0->nb[1], 0, 2, true, false, false
if (best_n_prefetch == 2 && total_size > vtcm_budget) {
total_size = htp_mm_hvx_id_get_vtcm_sizes(
wtype, ne10, src1_nrows, sess->n_threads, src0->nb[1], 2,
&vtcm_src0_size, &vtcm_src1_size, &vtcm_dst_size
);
}
kparams->n_prefetch = best_n_prefetch;
kparams->vtcm_size = L.total_bytes;
kparams->vtcm_src0_size = L.src0_bytes;
kparams->vtcm_src1_size = L.src1_bytes;
kparams->vtcm_dst_size = L.dst_bytes;
kparams->vtcm_size = total_size;
kparams->vtcm_src0_size = vtcm_src0_size;
kparams->vtcm_src1_size = vtcm_src1_size;
kparams->vtcm_dst_size = vtcm_dst_size;
} else {
bool try_tiled = (k_align && opt_mm_select >= 2);
if (try_tiled) {
@@ -2419,36 +2420,37 @@ static void ggml_hexagon_precompute_hvx_mm_params(
kparams->kernel_type = HTP_MM_KERNEL_HVX_QUANT_ROW;
}
struct htp_mm_hvx_vtcm_layout L;
uint32_t max_prefetch = (src1_nrows > HTP_MM_HMX_MIN_NROWS) ? 2 : 16;
uint32_t best_n_prefetch = 2;
size_t vtcm_src0_size = 0, vtcm_src1_size = 0, vtcm_dst_size = 0;
size_t total_size = 0;
for (uint32_t d = max_prefetch; d >= 2; d /= 2) {
htp_mm_hvx_vtcm_layout_build(
&L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], d, false, false, false
total_size = htp_mm_hvx_get_vtcm_sizes(
kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], d, &vtcm_src0_size, &vtcm_src1_size, &vtcm_dst_size
);
if (L.total_bytes <= vtcm_budget) {
if (total_size <= vtcm_budget) {
best_n_prefetch = d;
break;
}
}
if (best_n_prefetch == 2 && L.total_bytes > vtcm_budget) {
htp_mm_hvx_vtcm_layout_build(
&L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], 2, false, false, false
if (best_n_prefetch == 2 && total_size > vtcm_budget) {
total_size = htp_mm_hvx_get_vtcm_sizes(
kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], 2, &vtcm_src0_size, &vtcm_src1_size, &vtcm_dst_size
);
}
kparams->n_prefetch = best_n_prefetch;
if (L.total_bytes <= vtcm_budget) {
kparams->vtcm_size = L.total_bytes;
kparams->vtcm_src0_size = L.src0_bytes;
kparams->vtcm_src1_size = L.src1_bytes;
kparams->vtcm_dst_size = L.dst_bytes;
if (total_size <= vtcm_budget) {
kparams->vtcm_size = total_size;
kparams->vtcm_src0_size = vtcm_src0_size;
kparams->vtcm_src1_size = vtcm_src1_size;
kparams->vtcm_dst_size = vtcm_dst_size;
goto done_quant;
}
HEX_VERBOSE("ggml-hex: %s HVX tiled path VTCM size needed (%zu) > budget (%zu), falling back to HVX flat\n", sess->name.c_str(), L.total_bytes, vtcm_budget);
HEX_VERBOSE("ggml-hex: %s HVX tiled path VTCM size needed (%zu) > budget (%zu), falling back to HVX flat\n", sess->name.c_str(), total_size, vtcm_budget);
}
// Flat HVX fallback
@@ -2456,17 +2458,17 @@ static void ggml_hexagon_precompute_hvx_mm_params(
kparams->src1_row_size = (wtype == GGML_TYPE_Q4_1) ? htp_mm_q8_1_flat_row_size(ne10) : htp_mm_q8_0_flat_row_size(ne10);
kparams->kernel_type = HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT;
struct htp_mm_hvx_vtcm_layout L;
htp_mm_hvx_vtcm_layout_build(
&L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], 16, false, false, false
size_t vtcm_src0_size = 0, vtcm_src1_size = 0, vtcm_dst_size = 0;
size_t total_size = htp_mm_hvx_get_vtcm_sizes(
kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], 16, &vtcm_src0_size, &vtcm_src1_size, &vtcm_dst_size
);
kparams->n_prefetch = 16;
kparams->vtcm_size = L.total_bytes;
kparams->vtcm_src0_size = L.src0_bytes;
kparams->vtcm_src1_size = L.src1_bytes;
kparams->vtcm_dst_size = L.dst_bytes;
kparams->vtcm_size = total_size;
kparams->vtcm_src0_size = vtcm_src0_size;
kparams->vtcm_src1_size = vtcm_src1_size;
kparams->vtcm_dst_size = vtcm_dst_size;
}
}
@@ -2476,19 +2478,19 @@ static void ggml_hexagon_precompute_hvx_mm_params(
const bool is_batched = (ne02 > 1) || (ne03 > 1);
const bool is_permuted = ggml_is_permuted(src0) || ggml_is_permuted(src1);
struct htp_mm_hvx_vtcm_layout L;
htp_mm_hvx_vtcm_layout_build(
&L, HTP_MM_KERNEL_HVX_F16_F16_VTCM, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], 16, false, false, false
size_t vtcm_src0_size = 0, vtcm_src1_size = 0, vtcm_dst_size = 0;
size_t vtcm_size = htp_mm_hvx_get_vtcm_sizes(
HTP_MM_KERNEL_HVX_F16_F16_VTCM, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], 16, &vtcm_src0_size, &vtcm_src1_size, &vtcm_dst_size
);
if (!is_batched && !is_permuted && L.total_bytes <= vtcm_budget) {
if (!is_batched && !is_permuted && vtcm_size <= vtcm_budget) {
kparams->kernel_type = HTP_MM_KERNEL_HVX_F16_F16_VTCM;
kparams->src1_row_size = hex_round_up(ne10 * 2, 128);
kparams->vtcm_size = L.total_bytes;
kparams->vtcm_src0_size = L.src0_bytes;
kparams->vtcm_src1_size = L.src1_bytes;
kparams->vtcm_dst_size = L.dst_bytes;
kparams->vtcm_size = vtcm_size;
kparams->vtcm_src0_size = vtcm_src0_size;
kparams->vtcm_src1_size = vtcm_src1_size;
kparams->vtcm_dst_size = vtcm_dst_size;
kparams->n_prefetch = 16;
} else {
if (src1->type == GGML_TYPE_F32) {
@@ -2497,14 +2499,14 @@ static void ggml_hexagon_precompute_hvx_mm_params(
kparams->kernel_type = HTP_MM_KERNEL_HVX_F16_F16_DDR;
}
kparams->src1_row_size = src1->nb[1];
htp_mm_hvx_vtcm_layout_build(
&L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], 16, false, false, false
size_t ddr_size = htp_mm_hvx_get_vtcm_sizes(
kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], 16, &vtcm_src0_size, &vtcm_src1_size, &vtcm_dst_size
);
kparams->vtcm_size = L.total_bytes;
kparams->vtcm_src0_size = L.src0_bytes;
kparams->vtcm_src1_size = L.src1_bytes;
kparams->vtcm_dst_size = L.dst_bytes;
kparams->vtcm_size = ddr_size;
kparams->vtcm_src0_size = vtcm_src0_size;
kparams->vtcm_src1_size = vtcm_src1_size;
kparams->vtcm_dst_size = vtcm_dst_size;
kparams->n_prefetch = 16;
}
} else {
@@ -2512,31 +2514,31 @@ static void ggml_hexagon_precompute_hvx_mm_params(
const bool is_batched = (ne02 > 1) || (ne03 > 1);
const bool is_permuted = ggml_is_permuted(src0) || ggml_is_permuted(src1);
struct htp_mm_hvx_vtcm_layout L;
htp_mm_hvx_vtcm_layout_build(
&L, HTP_MM_KERNEL_HVX_F32_F32_VTCM, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], 16, false, false, false
size_t vtcm_src0_size = 0, vtcm_src1_size = 0, vtcm_dst_size = 0;
size_t vtcm_size = htp_mm_hvx_get_vtcm_sizes(
HTP_MM_KERNEL_HVX_F32_F32_VTCM, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], 16, &vtcm_src0_size, &vtcm_src1_size, &vtcm_dst_size
);
if (!is_batched && !is_permuted && L.total_bytes <= vtcm_budget) {
if (!is_batched && !is_permuted && vtcm_size <= vtcm_budget) {
kparams->kernel_type = HTP_MM_KERNEL_HVX_F32_F32_VTCM;
kparams->src1_row_size = hex_round_up(ne10 * 4, 128);
kparams->vtcm_size = L.total_bytes;
kparams->vtcm_src0_size = L.src0_bytes;
kparams->vtcm_src1_size = L.src1_bytes;
kparams->vtcm_dst_size = L.dst_bytes;
kparams->vtcm_size = vtcm_size;
kparams->vtcm_src0_size = vtcm_src0_size;
kparams->vtcm_src1_size = vtcm_src1_size;
kparams->vtcm_dst_size = vtcm_dst_size;
kparams->n_prefetch = 16;
} else {
kparams->kernel_type = HTP_MM_KERNEL_HVX_F32_F32_DDR;
kparams->src1_row_size = src1->nb[1];
htp_mm_hvx_vtcm_layout_build(
&L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], 16, false, false, false
size_t ddr_size = htp_mm_hvx_get_vtcm_sizes(
kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
dst->nb[1], src0->nb[1], src1->nb[1], 16, &vtcm_src0_size, &vtcm_src1_size, &vtcm_dst_size
);
kparams->vtcm_size = L.total_bytes;
kparams->vtcm_src0_size = L.src0_bytes;
kparams->vtcm_src1_size = L.src1_bytes;
kparams->vtcm_dst_size = L.dst_bytes;
kparams->vtcm_size = ddr_size;
kparams->vtcm_src0_size = vtcm_src0_size;
kparams->vtcm_src1_size = vtcm_src1_size;
kparams->vtcm_dst_size = vtcm_dst_size;
kparams->n_prefetch = 16;
}
}
@@ -2606,57 +2608,80 @@ static void ggml_hexagon_precompute_fused_qkv_params(
const int src1_nrows = src1->ne[1] * src1->ne[2] * src1->ne[3];
const size_t src1_row_size = (wtype == GGML_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
const size_t src0_row_size = src0->nb[1];
const size_t src0_row_size_padded = hex_round_up(src0_row_size, 128);
size_t src0_sz_per_thread = 0;
size_t src2_sz_per_thread = 0;
size_t src3_sz_per_thread = 0;
uint32_t best_n_prefetch = 16;
size_t quant_scratch_size = hex_round_up(ne10 * sizeof(float), QK_Q8_0_TILED * sizeof(float)) * sess->n_threads;
if (is_repack) {
uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(wtype);
uint32_t n_k_tiles = hex_round_up(ne10, 32) / 32;
uint32_t tile_row_size = n_k_tiles * aligned_tile_size;
size_t src1_sz_per_thread = hex_round_up(src1_row_size * src1_nrows, 128);
size_t src1_sz = src1_sz_per_thread;
const uint32_t max_prefetch = (src1_nrows > HTP_MM_HMX_MIN_NROWS) ? 2 : 16;
best_n_prefetch = 2;
for (uint32_t d = max_prefetch; d >= 2; d /= 2) {
struct htp_mm_hvx_vtcm_layout L;
htp_mm_hvx_vtcm_layout_build(
&L, HTP_MM_KERNEL_HVX_QUANT_ROW, wtype, ne10, src1_nrows, sess->n_threads,
0, src0_row_size, src1_row_size, d, false, true, false
);
if (L.total_bytes <= sess->vtcm_size) {
size_t repacked_vtcm_size = hex_round_up(d * tile_row_size, 128);
size_t src0_sz = repacked_vtcm_size * sess->n_threads;
size_t src2_sz = hex_round_up(d * tile_row_size, 128) * sess->n_threads;
size_t src3_sz = hex_round_up(d * tile_row_size, 128) * sess->n_threads;
size_t tiled_vtcm_size = src0_sz + src1_sz + src2_sz + src3_sz + quant_scratch_size;
if (tiled_vtcm_size <= sess->vtcm_size) {
best_n_prefetch = d;
src0_sz_per_thread = repacked_vtcm_size;
src2_sz_per_thread = hex_round_up(d * tile_row_size, 128);
src3_sz_per_thread = hex_round_up(d * tile_row_size, 128);
break;
}
}
if (best_n_prefetch == 2 && src0_sz_per_thread == 0) {
size_t repacked_vtcm_size = hex_round_up(2 * tile_row_size, 128);
src0_sz_per_thread = repacked_vtcm_size;
src2_sz_per_thread = hex_round_up(2 * tile_row_size, 128);
src3_sz_per_thread = hex_round_up(2 * tile_row_size, 128);
}
} else {
best_n_prefetch = 16;
src0_sz_per_thread = hex_round_up(best_n_prefetch * src0_row_size_padded, 128);
src2_sz_per_thread = hex_round_up(best_n_prefetch * src0_row_size_padded, 128);
src3_sz_per_thread = hex_round_up(best_n_prefetch * src0_row_size_padded, 128);
}
struct htp_mm_hvx_vtcm_layout L;
size_t src1_sz_per_thread = hex_round_up(src1_row_size * src1_nrows, 128);
size_t src0_sz = src0_sz_per_thread * sess->n_threads;
size_t src1_sz = src1_sz_per_thread;
size_t src2_sz = src2_sz_per_thread * sess->n_threads;
size_t src3_sz = src3_sz_per_thread * sess->n_threads;
size_t tiled_vtcm_size = src0_sz + src1_sz + src2_sz + src3_sz + quant_scratch_size;
bool try_tiled = (opt_mm_select >= 2);
// Test tiled first
htp_mm_hvx_vtcm_layout_build(
&L, HTP_MM_KERNEL_HVX_QUANT_ROW, wtype, ne10, src1_nrows, sess->n_threads,
0, src0_row_size, src1_row_size, best_n_prefetch, false, true, false
);
if (try_tiled && L.total_bytes <= sess->vtcm_size) {
if (try_tiled && tiled_vtcm_size <= sess->vtcm_size) {
kparams->kernel_type = HTP_MM_KERNEL_HVX_QUANT_ROW;
kparams->vtcm_src0_size = L.src0_bytes;
kparams->vtcm_src1_size = L.src1_bytes;
kparams->vtcm_src2_size = L.src2_bytes;
kparams->vtcm_src3_size = L.src3_bytes;
kparams->vtcm_dst_size = L.dst_bytes;
kparams->vtcm_size = L.total_bytes;
kparams->vtcm_src0_size = src0_sz;
kparams->vtcm_src1_size = src1_sz;
kparams->vtcm_src2_size = src2_sz;
kparams->vtcm_src3_size = src3_sz;
kparams->vtcm_dst_size = quant_scratch_size;
kparams->vtcm_size = tiled_vtcm_size;
kparams->n_prefetch = best_n_prefetch;
} else {
kparams->kernel_type = HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT;
size_t flat_src1_row_size = (wtype == GGML_TYPE_Q4_1) ? htp_mm_q8_1_flat_row_size(ne10) : htp_mm_q8_0_flat_row_size(ne10);
htp_mm_hvx_vtcm_layout_build(
&L, HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT, wtype, ne10, src1_nrows, sess->n_threads,
0, src0_row_size, flat_src1_row_size, best_n_prefetch, false, true, false
);
kparams->vtcm_src0_size = L.src0_bytes;
kparams->vtcm_src1_size = L.src1_bytes;
kparams->vtcm_src2_size = L.src2_bytes;
kparams->vtcm_src3_size = L.src3_bytes;
kparams->vtcm_dst_size = L.dst_bytes;
kparams->vtcm_size = L.total_bytes;
size_t flat_src1_sz = hex_round_up(flat_src1_row_size * src1_nrows, 128);
kparams->vtcm_src0_size = src0_sz;
kparams->vtcm_src1_size = flat_src1_sz;
kparams->vtcm_src2_size = src2_sz;
kparams->vtcm_src3_size = src3_sz;
kparams->vtcm_dst_size = quant_scratch_size;
kparams->vtcm_size = src0_sz + flat_src1_sz + src2_sz + src3_sz + quant_scratch_size;
kparams->n_prefetch = best_n_prefetch;
}
}
@@ -2676,55 +2701,72 @@ static void ggml_hexagon_precompute_fused_ffn_params(
const int src1_nrows = src1->ne[1] * src1->ne[2] * src1->ne[3];
const size_t src1_row_size = (wtype == GGML_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
const size_t src0_row_size = src0->nb[1];
const size_t src0_row_size_padded = hex_round_up(src0_row_size, 128);
size_t src0_sz_per_thread = 0;
size_t src2_sz_per_thread = 0;
uint32_t best_n_prefetch = 16;
size_t quant_scratch_size = hex_round_up(ne10 * sizeof(float), QK_Q8_0_TILED * sizeof(float)) * sess->n_threads;
if (is_repack) {
uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(wtype);
uint32_t n_k_tiles = hex_round_up(ne10, 32) / 32;
uint32_t tile_row_size = n_k_tiles * aligned_tile_size;
size_t src1_sz_per_thread = hex_round_up(src1_row_size * src1_nrows, 128);
size_t src1_sz = src1_sz_per_thread;
const uint32_t max_prefetch = (src1_nrows > HTP_MM_HMX_MIN_NROWS) ? 2 : 16;
best_n_prefetch = 2;
for (uint32_t d = max_prefetch; d >= 2; d /= 2) {
struct htp_mm_hvx_vtcm_layout L;
htp_mm_hvx_vtcm_layout_build(
&L, HTP_MM_KERNEL_HVX_QUANT_ROW, wtype, ne10, src1_nrows, sess->n_threads,
0, src0_row_size, src1_row_size, d, false, false, true
);
if (L.total_bytes <= sess->vtcm_size) {
size_t repacked_vtcm_size = hex_round_up(d * tile_row_size, 128);
size_t src0_sz = repacked_vtcm_size * sess->n_threads;
size_t src2_sz = hex_round_up(d * tile_row_size, 128) * sess->n_threads;
size_t tiled_vtcm_size = src0_sz + src1_sz + src2_sz + quant_scratch_size;
if (tiled_vtcm_size <= sess->vtcm_size) {
best_n_prefetch = d;
src0_sz_per_thread = repacked_vtcm_size;
src2_sz_per_thread = hex_round_up(d * tile_row_size, 128);
break;
}
}
if (best_n_prefetch == 2 && src0_sz_per_thread == 0) {
size_t repacked_vtcm_size = hex_round_up(2 * tile_row_size, 128);
src0_sz_per_thread = repacked_vtcm_size;
src2_sz_per_thread = hex_round_up(2 * tile_row_size, 128);
}
} else {
best_n_prefetch = 16;
src0_sz_per_thread = hex_round_up(best_n_prefetch * src0_row_size_padded, 128);
src2_sz_per_thread = hex_round_up(best_n_prefetch * src0_row_size_padded, 128);
}
struct htp_mm_hvx_vtcm_layout L;
size_t src1_sz_per_thread = hex_round_up(src1_row_size * src1_nrows, 128);
size_t src0_sz = src0_sz_per_thread * sess->n_threads;
size_t src1_sz = src1_sz_per_thread;
size_t src2_sz = src2_sz_per_thread * sess->n_threads;
size_t tiled_vtcm_size = src0_sz + src1_sz + src2_sz + quant_scratch_size;
bool try_tiled = (opt_mm_select >= 2);
// Test tiled first
htp_mm_hvx_vtcm_layout_build(
&L, HTP_MM_KERNEL_HVX_QUANT_ROW, wtype, ne10, src1_nrows, sess->n_threads,
0, src0_row_size, src1_row_size, best_n_prefetch, false, false, true
);
if (try_tiled && L.total_bytes <= sess->vtcm_size) {
if (try_tiled && tiled_vtcm_size <= sess->vtcm_size) {
kparams->kernel_type = HTP_MM_KERNEL_HVX_QUANT_ROW;
kparams->vtcm_src0_size = L.src0_bytes;
kparams->vtcm_src1_size = L.src1_bytes;
kparams->vtcm_src2_size = L.src2_bytes;
kparams->vtcm_dst_size = L.dst_bytes;
kparams->vtcm_size = L.total_bytes;
kparams->vtcm_src0_size = src0_sz;
kparams->vtcm_src1_size = src1_sz;
kparams->vtcm_src2_size = src2_sz;
kparams->vtcm_dst_size = quant_scratch_size;
kparams->vtcm_size = tiled_vtcm_size;
kparams->n_prefetch = best_n_prefetch;
} else {
kparams->kernel_type = HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT;
size_t flat_src1_row_size = (wtype == GGML_TYPE_Q4_1) ? htp_mm_q8_1_flat_row_size(ne10) : htp_mm_q8_0_flat_row_size(ne10);
htp_mm_hvx_vtcm_layout_build(
&L, HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT, wtype, ne10, src1_nrows, sess->n_threads,
0, src0_row_size, flat_src1_row_size, best_n_prefetch, false, false, true
);
kparams->vtcm_src0_size = L.src0_bytes;
kparams->vtcm_src1_size = L.src1_bytes;
kparams->vtcm_src2_size = L.src2_bytes;
kparams->vtcm_dst_size = L.dst_bytes;
kparams->vtcm_size = L.total_bytes;
size_t flat_src1_sz = hex_round_up(flat_src1_row_size * src1_nrows, 128);
kparams->vtcm_src0_size = src0_sz;
kparams->vtcm_src1_size = flat_src1_sz;
kparams->vtcm_src2_size = src2_sz;
kparams->vtcm_dst_size = quant_scratch_size;
kparams->vtcm_size = src0_sz + flat_src1_sz + src2_sz + quant_scratch_size;
kparams->n_prefetch = best_n_prefetch;
}
}
+2 -2
View File
@@ -20,7 +20,6 @@ add_library(${HTP_LIB} SHARED
worker-pool.c
hex-dma.c
hmx-queue.c
gated-delta-net-ops.c
binary-ops.c
unary-ops.c
sum-rows-ops.c
@@ -38,9 +37,10 @@ add_library(${HTP_LIB} SHARED
concat-ops.c
diag-ops.c
solve-tri-ops.c
gated-delta-net-ops.c
pad-ops.c
flash-attn-ops.c
matmul-ops.c
flash-attn-ops.c
)
target_compile_definitions(${HTP_LIB} PRIVATE
+1 -1
View File
@@ -4,7 +4,7 @@
#include "hexagon_protos.h"
#include "hvx_hexagon_protos.h"
#include "hex-dma.h"
#include "htp-vtcm.h"
#include "vtcm-utils.h"
#include "hvx-utils.h"
#include "hex-fastdiv.h"
#include <string.h>
+293 -277
View File
@@ -8,7 +8,6 @@
#include <HAP_perf.h>
#include <math.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
@@ -23,7 +22,7 @@
#include "hvx-copy.h"
#include "hvx-reduce.h"
#include "hvx-flash-attn.h"
#include "htp-vtcm.h"
#include "vtcm-utils.h"
#include "worker-pool.h"
#define GGML_COMMON_DECL_C
@@ -143,10 +142,6 @@ struct hmx_fa_context {
__fp16 * vtcm_slopes; // ALiBi slopes [g_br]
size_t row_buf_stride; // HVX vectors per row buffer (Bc/64)
size_t mask_buf_row_stride; // elements (__fp16) per row in mask buffer
size_t q_tile_bytes;
size_t o_tile_bytes;
size_t col_vec_bytes;
size_t d_tile_bytes;
bool mask_broadcast; // true when mask->ne[2] == 1 (head-independent, single 2D DMA)
dma_cache m_cache;
};
@@ -468,7 +463,7 @@ typedef struct {
struct hmx_fa_context * factx;
uint32_t kv_rows;
size_t src_stride;
void * curr_k;
size_t buf_idx;
uint32_t kv_start;
uint32_t rows_per_t;
} fa_k_int_args_t;
@@ -488,19 +483,19 @@ static void fa_k_interleave_thread(unsigned int n, unsigned int i, void * data)
struct htp_thread_trace * tr = factx->octx->ctx ? &factx->octx->ctx->trace[i] : NULL;
htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_FA_K_PREP, (uint16_t) (args->kv_start + start));
hmx_interleave_rows_to_tiles(factx->vtcm_k_tiles, (const __fp16 *) args->curr_k, total_rows, factx->DK,
hmx_interleave_rows_to_tiles(factx->vtcm_k_tiles, factx->vtcm_k_fp16[args->buf_idx], total_rows, factx->DK,
args->src_stride, start, end);
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_K_PREP, (uint16_t) (args->kv_start + start));
}
static void fa_phase_k_interleave(struct hmx_fa_context * factx, uint32_t kv_rows, size_t src_stride, void * curr_k, uint32_t kv_start) {
static void fa_phase_k_interleave(struct hmx_fa_context * factx, uint32_t kv_rows, size_t src_stride, size_t buf_idx, uint32_t kv_start) {
worker_pool_context_t wp = factx->octx->ctx->worker_pool;
uint32_t n = 1;
if (factx->n_threads > 1 && kv_rows >= factx->n_threads * 2) {
n = factx->n_threads;
}
uint32_t rows_per_t = hex_align_up(hmx_ceil_div(kv_rows, n), 2);
fa_k_int_args_t args = { factx, kv_rows, src_stride, curr_k, kv_start, rows_per_t };
fa_k_int_args_t args = { factx, kv_rows, src_stride, buf_idx, kv_start, rows_per_t };
if (n > 1) {
worker_pool_run_func(wp, fa_k_interleave_thread, &args, n);
} else {
@@ -512,8 +507,7 @@ typedef struct {
struct hmx_fa_context * factx;
uint32_t kv_rows;
size_t src_stride;
void * v_src;
void * v_tiles_dst;
size_t buf_idx;
size_t n_col_tiles;
uint32_t kv_start;
uint32_t rows_per_t;
@@ -532,11 +526,11 @@ static void fa_v_interleave_thread(unsigned int n, unsigned int i, void * data)
return;
}
__fp16 * v_tiles_dst = (__fp16 *) args->v_tiles_dst;
__fp16 * v_tiles_dest = factx->pipeline ? factx->vtcm_v_tiles[args->buf_idx] : factx->vtcm_v_tiles[0];
struct htp_thread_trace * tr = factx->octx->ctx ? &factx->octx->ctx->trace[i] : NULL;
htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_FA_V_PREP, (uint16_t) (args->kv_start + start));
hmx_interleave_cols_to_tiles(v_tiles_dst, (const __fp16 *) args->v_src, total_rows, factx->DV,
hmx_interleave_cols_to_tiles(v_tiles_dest, factx->vtcm_v_fp16[args->buf_idx], total_rows, factx->DV,
args->src_stride, (uint32_t) args->n_col_tiles, start, end);
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_V_PREP, (uint16_t) (args->kv_start + start));
}
@@ -544,8 +538,7 @@ static void fa_v_interleave_thread(unsigned int n, unsigned int i, void * data)
static void fa_phase_v_interleave(struct hmx_fa_context * factx,
uint32_t kv_rows,
size_t src_stride,
void * v_src,
void * v_tiles_dst,
size_t buf_idx,
size_t n_col_tiles,
uint32_t kv_start) {
worker_pool_context_t wp = factx->octx->ctx->worker_pool;
@@ -554,7 +547,7 @@ static void fa_phase_v_interleave(struct hmx_fa_context * factx,
n = factx->n_threads;
}
uint32_t rows_per_t = hex_align_up(hmx_ceil_div(kv_rows, n), 2);
fa_v_int_args_t args = { factx, kv_rows, src_stride, v_src, v_tiles_dst, n_col_tiles, kv_start, rows_per_t };
fa_v_int_args_t args = { factx, kv_rows, src_stride, buf_idx, n_col_tiles, kv_start, rows_per_t };
if (n > 1) {
worker_pool_run_func(wp, fa_v_interleave_thread, &args, n);
} else {
@@ -570,9 +563,6 @@ typedef struct {
uint32_t ib3;
size_t n_rows_g;
size_t rows_per_t;
size_t n_rows_q;
bool q_transposed;
atomic_uint barrier;
} fa_q_load_args_t;
static void fa_q_load_thread(unsigned int n, unsigned int i, void * data) {
@@ -597,8 +587,9 @@ static void fa_q_load_thread(unsigned int n, unsigned int i, void * data) {
const uint32_t g_br = factx->g_br;
const uint32_t DV = factx->DV;
const size_t col_vec_bytes = factx->col_vec_bytes;
const size_t d_tile_bytes = factx->d_tile_bytes;
const size_t col_vec_bytes = hex_align_up(g_br * sizeof(float), 256);
const size_t d_tile_bytes = hex_align_up(g_br * g_br * sizeof(__fp16), 4096);
const size_t o_tile_bytes = hex_align_up(g_br * DV * sizeof(__fp16), 4096);
// Initialize vtcm_l_vec & vtcm_m_vec
const size_t l_bytes_per_t = hex_align_up(col_vec_bytes / n, 128);
@@ -652,63 +643,72 @@ static void fa_q_load_thread(unsigned int n, unsigned int i, void * data) {
if (d_start < d_tile_bytes) {
hvx_splat_u8_a((char *) factx->vtcm_d_tiles + d_start, 0, d_end - d_start);
}
}
if (start < factx->g_br) {
const struct htp_tensor * q = args->q;
const uint32_t q_start = args->q_start;
const uint32_t kv_head = args->kv_head;
const uint32_t ib3 = args->ib3;
assert(factx->DK == factx->DV);
const size_t o_tile_bytes = factx->o_tile_bytes;
const bool use_q_dma = (2 * o_tile_bytes >= factx->g_br * DK * (factx->is_q_fp32 ? 4 : 2));
__fp16 * q_tiles = factx->vtcm_q_tiles;
if (use_q_dma) {
const size_t g_rows_end = hex_smin(end, n_rows_g);
const uint32_t d_limit = factx->is_q_fp32 ? DK / 32 : DK / 64;
uint8_t * q_flat = (uint8_t *) factx->vtcm_o_tiles[0];
if (factx->is_q_fp32) {
switch (d_limit) {
case 2: hmx_fa_q_prep_fp32_d2(q_tiles, q_flat, start, end, g_rows_end, DK, G, args->n_rows_q, &factx->div_G, args->q_transposed); break;
case 4: hmx_fa_q_prep_fp32_d4(q_tiles, q_flat, start, end, g_rows_end, DK, G, args->n_rows_q, &factx->div_G, args->q_transposed); break;
default: hmx_fa_q_prep_fp32( q_tiles, q_flat, start, end, g_rows_end, DK, G, args->n_rows_q, &factx->div_G, d_limit, args->q_transposed); break;
}
} else {
switch (d_limit) {
case 1: hmx_fa_q_prep_fp16_d1(q_tiles, q_flat, start, end, g_rows_end, DK, G, args->n_rows_q, &factx->div_G, args->q_transposed); break;
case 2: hmx_fa_q_prep_fp16_d2(q_tiles, q_flat, start, end, g_rows_end, DK, G, args->n_rows_q, &factx->div_G, args->q_transposed); break;
default: hmx_fa_q_prep_fp16( q_tiles, q_flat, start, end, g_rows_end, DK, G, args->n_rows_q, &factx->div_G, d_limit, args->q_transposed); break;
}
}
} else {
// Fallback: direct-from-DDR/L2 path
hmx_fa_q_prep_fallback(q_tiles, q->data, q->nb[1], q->nb[2], q->nb[3],
q_start, kv_head, ib3, start, end, n_rows_g, G, DK, factx->is_q_fp32, &factx->div_G);
}
}
// Synchronize threads before zeroing out vtcm_o_tiles[0] to prevent race condition
if (n > 1) {
atomic_fetch_sub(&args->barrier, 1);
while (atomic_load(&args->barrier) > 0) {
// spin wait
}
}
// Zero out vtcm_o_tiles[0] as it was used as temp_q_vtcm
{
const uint32_t g_br = factx->g_br;
const uint32_t DV = factx->DV;
const size_t o_tile_bytes = factx->o_tile_bytes;
// Initialize vtcm_o_tiles[0] to 0
__fp16 * o_tile_prev = factx->vtcm_o_tiles[0];
const size_t o_bytes_per_t = hex_align_up(o_tile_bytes / n, 128);
const size_t o_start = i * o_bytes_per_t;
const size_t o_end = hex_smin(o_start + o_bytes_per_t, o_tile_bytes);
if (o_start < o_tile_bytes) {
hvx_splat_u8_a((char *) factx->vtcm_o_tiles[0] + o_start, 0, o_end - o_start);
hvx_splat_u8_a((char *) o_tile_prev + o_start, 0, o_end - o_start);
}
}
if (start >= factx->g_br) {
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_Q_PREP, (uint16_t) (args->q_start * G + start));
return;
}
const struct htp_tensor * q = args->q;
const uint32_t q_start = args->q_start;
const uint32_t kv_head = args->kv_head;
const uint32_t ib3 = args->ib3;
for (size_t r = start; r < end; r += 2) {
const size_t q_idx0 = fastdiv(r + 0, &factx->div_G);
const size_t h_idx0 = fastmodulo(r + 0, G, &factx->div_G);
const size_t q_idx1 = fastdiv(r + 1, &factx->div_G);
const size_t h_idx1 = fastmodulo(r + 1, G, &factx->div_G);
const uint8_t * q_ptr0 = (r + 0 < n_rows_g) ? ((const uint8_t *) q->data + (q_start + q_idx0) * q->nb[1] +
(kv_head * G + h_idx0) * q->nb[2] + ib3 * q->nb[3]) :
NULL;
const uint8_t * q_ptr1 = (r + 1 < n_rows_g) ? ((const uint8_t *) q->data + (q_start + q_idx1) * q->nb[1] +
(kv_head * G + h_idx1) * q->nb[2] + ib3 * q->nb[3]) :
NULL;
size_t r0 = r / HMX_FP16_TILE_N_ROWS;
size_t r1 = r % HMX_FP16_TILE_N_ROWS;
__fp16 * out_base = factx->vtcm_q_tiles + r0 * HMX_FP16_TILE_N_ROWS * DK;
if (factx->is_q_fp32) {
const HVX_Vector * pv_in0 = q_ptr0 ? (const HVX_Vector *) q_ptr0 : NULL;
const HVX_Vector * pv_in1 = q_ptr1 ? (const HVX_Vector *) q_ptr1 : NULL;
for (uint32_t d = 0; d < DK / 32; ++d) {
HVX_Vector v0 = pv_in0 ? pv_in0[d] : Q6_V_vzero();
HVX_Vector v1 = pv_in1 ? pv_in1[d] : Q6_V_vzero();
HVX_Vector v_hf = hvx_vec_f32_to_f16_shuff(v0, v1);
HVX_Vector * out_tile = (HVX_Vector *) (out_base + d * HMX_FP16_TILE_N_ELMS);
out_tile[r1 / 2] = v_hf;
}
} else {
const HVX_Vector * pv_in0 = q_ptr0 ? (const HVX_Vector *) q_ptr0 : NULL;
const HVX_Vector * pv_in1 = q_ptr1 ? (const HVX_Vector *) q_ptr1 : NULL;
for (uint32_t d = 0; d < DK / 64; ++d) {
HVX_Vector v0 = pv_in0 ? pv_in0[d] : Q6_V_vzero();
HVX_Vector v1 = pv_in1 ? pv_in1[d] : Q6_V_vzero();
HVX_VectorPair vp = Q6_W_vshuff_VVR(v1, v0, -2);
__fp16 * out_dual_tile = out_base + d * HMX_FP16_TILE_N_ELMS * 2;
HVX_Vector * pv_out0 = ((HVX_Vector *) out_dual_tile) + r1 / 2;
HVX_Vector * pv_out1 = pv_out0 + 16;
*pv_out0 = Q6_V_lo_W(vp);
*pv_out1 = Q6_V_hi_W(vp);
}
}
}
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_Q_PREP, (uint16_t) (args->q_start * G + start));
@@ -726,18 +726,7 @@ static void fa_phase_q_load(struct hmx_fa_context * factx,
n = factx->n_threads;
}
size_t rows_per_t = hex_align_up(hmx_ceil_div(factx->g_br, n), 2);
const uint32_t n_rows_q = hex_smin(factx->Br, factx->neq1 - q_start);
fa_q_load_args_t args;
args.factx = factx;
args.q = q;
args.q_start = q_start;
args.kv_head = kv_head;
args.ib3 = ib3;
args.n_rows_g = n_rows_g;
args.rows_per_t = rows_per_t;
args.n_rows_q = n_rows_q;
args.q_transposed = q->nb[1] < q->nb[2];
atomic_init(&args.barrier, n);
fa_q_load_args_t args = { factx, q, q_start, kv_head, ib3, n_rows_g, rows_per_t };
if (n > 1) {
worker_pool_run_func(wp, fa_q_load_thread, &args, n);
} else {
@@ -809,10 +798,11 @@ static void fa_o_store_thread_f16(unsigned int n, unsigned int i, void * data) {
fa_o_store_args_t * args = (fa_o_store_args_t *) data;
struct hmx_fa_context * factx = args->factx;
const size_t n_rows_g = args->n_rows_g;
const size_t n_rows_g = args->n_rows_g;
const size_t G = factx->G;
const size_t DV = factx->DV;
const size_t rows_per_t = args->rows_per_t;
const size_t G = factx->G;
const size_t DV = factx->DV;
const size_t start = (size_t) i * rows_per_t;
const size_t end = hex_smin(start + rows_per_t, n_rows_g);
@@ -841,10 +831,10 @@ static void fa_o_store_thread_f16(unsigned int n, unsigned int i, void * data) {
const __fp16 * tile_row_base = o_tile_src + r0 * HMX_FP16_TILE_N_ROWS * DV;
for (uint32_t d = 0; d < DV / 64; ++d) {
const __fp16 * in_dtile = tile_row_base + d * HMX_FP16_TILE_N_ELMS * 2;
const HVX_Vector * pv_in0 = ((const HVX_Vector *) in_dtile) + r1 / 2;
const HVX_Vector * pv_in1 = pv_in0 + 16;
HVX_VectorPair vp = Q6_W_vdeal_VVR(*pv_in1, *pv_in0, -2);
const __fp16 * in_dual_tile = tile_row_base + d * HMX_FP16_TILE_N_ELMS * 2;
const HVX_Vector * pv_in0 = ((const HVX_Vector *) in_dual_tile) + r1 / 2;
const HVX_Vector * pv_in1 = pv_in0 + 16;
HVX_VectorPair vp = Q6_W_vdeal_VVR(*pv_in1, *pv_in0, -2);
if (r1 % 2 == 0) {
*(HVX_UVector *) (out + d * 64) = Q6_V_lo_W(vp);
} else {
@@ -967,14 +957,14 @@ static inline void fa_softmax_impl(
if (has_softcap) {
const HVX_Vector v_cap = hvx_vec_splat_f16(factx->logit_softcap);
for (size_t c = 0; c < kv_rows; c += 64) {
size_t ci = c / 64;
const __fp16 * in_dtile = s_ld_base + ci * HMX_FP16_TILE_N_ELMS * 2;
const HVX_Vector * pv_s_in0 = ((const HVX_Vector *) in_dtile) + r1 / 2;
const HVX_Vector * pv_s_in1 = pv_s_in0 + 16;
size_t ci = c / 64;
const __fp16 * in_dual_tile = s_ld_base + ci * HMX_FP16_TILE_N_ELMS * 2;
const HVX_Vector * pv_s_in0 = ((const HVX_Vector *) in_dual_tile) + r1 / 2;
const HVX_Vector * pv_s_in1 = pv_s_in0 + 16;
HVX_VectorPair vp_s_drow = Q6_W_vdeal_VVR(*pv_s_in1, *pv_s_in0, -2);
HVX_Vector v_s_row0 = Q6_V_lo_W(vp_s_drow);
HVX_Vector v_s_row1 = Q6_V_hi_W(vp_s_drow);
HVX_VectorPair vp_s_dual_row = Q6_W_vdeal_VVR(*pv_s_in1, *pv_s_in0, -2);
HVX_Vector v_s_row0 = Q6_V_lo_W(vp_s_dual_row);
HVX_Vector v_s_row1 = Q6_V_hi_W(vp_s_dual_row);
HVX_Vector t0 = hvx_vec_tanh_f16(v_s_row0);
my_row_buf0[ci] = hvx_vec_mul_f16_f16(t0, v_cap);
@@ -984,14 +974,14 @@ static inline void fa_softmax_impl(
}
} else {
for (size_t c = 0; c < kv_rows; c += 64) {
size_t ci = c / 64;
const __fp16 * in_dtile = s_ld_base + ci * HMX_FP16_TILE_N_ELMS * 2;
const HVX_Vector * pv_s_in0 = ((const HVX_Vector *) in_dtile) + r1 / 2;
const HVX_Vector * pv_s_in1 = pv_s_in0 + 16;
size_t ci = c / 64;
const __fp16 * in_dual_tile = s_ld_base + ci * HMX_FP16_TILE_N_ELMS * 2;
const HVX_Vector * pv_s_in0 = ((const HVX_Vector *) in_dual_tile) + r1 / 2;
const HVX_Vector * pv_s_in1 = pv_s_in0 + 16;
HVX_VectorPair vp_s_drow = Q6_W_vdeal_VVR(*pv_s_in1, *pv_s_in0, -2);
my_row_buf0[ci] = Q6_V_lo_W(vp_s_drow);
my_row_buf1[ci] = Q6_V_hi_W(vp_s_drow);
HVX_VectorPair vp_s_dual_row = Q6_W_vdeal_VVR(*pv_s_in1, *pv_s_in0, -2);
my_row_buf0[ci] = Q6_V_lo_W(vp_s_dual_row);
my_row_buf1[ci] = Q6_V_hi_W(vp_s_dual_row);
}
}
@@ -1128,9 +1118,9 @@ static inline void fa_softmax_impl(
HVX_Vector v_p_row0_hf = hvx_vec_exp2_f16(Q6_Vhf_equals_Vqf16(v_s_minus_m0));
HVX_Vector v_p_row1_hf = hvx_vec_exp2_f16(Q6_Vhf_equals_Vqf16(v_s_minus_m1));
__fp16 * out_dtile = p_st_base + ci * HMX_FP16_TILE_N_ELMS * 2;
HVX_Vector * pv_p_out0 = ((HVX_Vector *) out_dtile) + r1 / 2;
HVX_Vector * pv_p_out1 = pv_p_out0 + 16;
__fp16 * out_dual_tile = p_st_base + (c / 64) * HMX_FP16_TILE_N_ELMS * 2;
HVX_Vector * pv_p_out0 = ((HVX_Vector *) out_dual_tile) + r1 / 2;
HVX_Vector * pv_p_out1 = pv_p_out0 + 16;
HVX_VectorPair vp_p_dual = Q6_W_vshuff_VVR(v_p_row1_hf, v_p_row0_hf, -2);
*pv_p_out0 = Q6_V_lo_W(vp_p_dual);
@@ -1160,7 +1150,7 @@ static inline void fa_softmax_impl(
}
// Inline fa_ml_update_and_build_d for this vector (lock-free and in parallel)
HVX_VectorPair rowmax_acc_pair = hvx_vec_f16_to_f32(rowmax_acc_v);
HVX_VectorPair rowmax_acc_pair = hvx_vec_f16_to_f32(rowmax_acc_v);
HVX_Vector v_rowmax_acc_f32_0 = Q6_V_lo_W(rowmax_acc_pair);
HVX_Vector v_rowmax_acc_f32_1 = Q6_V_hi_W(rowmax_acc_pair);
@@ -1170,7 +1160,7 @@ static inline void fa_softmax_impl(
HVX_Vector v_m_diff0 = HVX_OP_SUB_F32(m_prev_v0, v_m_curr0);
HVX_Vector v_m_diff1 = HVX_OP_SUB_F32(m_prev_v1, v_m_curr1);
HVX_Vector v_m_diff_f16 = hvx_vec_f32_to_f16(v_m_diff0, v_m_diff1);
HVX_Vector v_m_diff_f16 = hvx_vec_f32_to_f16(v_m_diff0, v_m_diff1);
HVX_Vector exp_m_diff_f16 = hvx_vec_exp2_f16(v_m_diff_f16);
HVX_VectorPair exp_m_diff_pair = hvx_vec_f16_to_f32(exp_m_diff_f16);
@@ -1341,17 +1331,14 @@ static void hmx_fa_qk_dot_worker(void * data) {
__builtin_assume(n_col_tiles > 0);
__builtin_assume(n_dot_tiles > 0);
asm volatile(HMX_SET_BIAS("%0") :: "r"((unsigned int)job->hmx_scales));
const size_t dot_stride = n_dot_tiles * HMX_FP16_TILE_N_ELMS;
Q6_bias_mxmem2_A((void *) job->hmx_scales);
for (size_t r = 0; r < n_row_tiles; ++r) {
const __fp16 * row_tiles = q_tiles + r * dot_stride;
const __fp16 * col_tiles = k_tiles;
__fp16 * out_tile = s_tiles + r * n_tiles_per_bc * HMX_FP16_TILE_N_ELMS;
for (size_t c = 0; c < n_col_tiles; ++c) {
const __fp16 * row_tiles = q_tiles + r * HMX_FP16_TILE_N_ROWS * n_dot_tiles * HMX_FP16_TILE_N_COLS;
const __fp16 * col_tiles = k_tiles + c * HMX_FP16_TILE_N_COLS * n_dot_tiles * HMX_FP16_TILE_N_COLS;
__fp16 * out_tile = s_tiles + (r * n_tiles_per_bc + c) * HMX_FP16_TILE_N_ELMS;
hmx_fa_qk_dot_tile(row_tiles, col_tiles, out_tile, n_dot_tiles);
col_tiles += dot_stride;
out_tile += HMX_FP16_TILE_N_ELMS;
}
}
}
@@ -1386,21 +1373,17 @@ static void hmx_fa_o_update_worker(void * data) {
__builtin_assume(n_col_tiles > 0);
__builtin_assume(DV_tiles > 0);
asm volatile(HMX_SET_BIAS("%0") :: "r"((unsigned int)job->hmx_scales));
const size_t o_stride = n_row_tiles_g_br * HMX_FP16_TILE_N_ELMS;
const size_t v_stride = n_tiles_per_bc * HMX_FP16_TILE_N_ELMS;
Q6_bias_mxmem2_A((void *) job->hmx_scales);
for (size_t r = 0; r < n_row_tiles; ++r) {
const __fp16 * d_diag = d_tiles + r * (n_row_tiles_g_br + 1) * HMX_FP16_TILE_N_ELMS;
const __fp16 * p_tile_in = p_tiles + (r * n_tiles_per_bc) * HMX_FP16_TILE_N_ELMS;
const __fp16 * o_rc = o_prev + r * HMX_FP16_TILE_N_ELMS;
const __fp16 * v_tile_in = v_tiles;
__fp16 * o_tile_out = o_curr + r * HMX_FP16_TILE_N_ELMS;
for (size_t c = 0; c < DV_tiles; ++c) {
// D[r,r] @ O_prev[r,c] — only the diagonal tile
const __fp16 * d_diag = d_tiles + r * (n_row_tiles_g_br + 1) * HMX_FP16_TILE_N_ELMS;
const __fp16 * o_rc = o_prev + (c * n_row_tiles_g_br + r) * HMX_FP16_TILE_N_ELMS;
const __fp16 * p_tile_in = p_tiles + (r * n_tiles_per_bc) * HMX_FP16_TILE_N_ELMS;
const __fp16 * v_tile_in = v_tiles + (c * n_tiles_per_bc) * HMX_FP16_TILE_N_ELMS;
__fp16 * o_tile_out = o_curr + (c * n_row_tiles_g_br + r) * HMX_FP16_TILE_N_ELMS;
hmx_fa_o_update_tile(d_diag, o_rc, p_tile_in, v_tile_in, o_tile_out, n_col_tiles);
o_rc += o_stride;
v_tile_in += v_stride;
o_tile_out += o_stride;
}
}
}
@@ -1426,17 +1409,14 @@ static void hmx_fa_o_norm_worker(void * data) {
__builtin_assume(n_row_tiles > 0);
__builtin_assume(DV_tiles > 0);
asm volatile(HMX_SET_BIAS("%0") :: "r"((unsigned int)job->hmx_scales));
const size_t o_stride = n_row_tiles_g_br * HMX_FP16_TILE_N_ELMS;
Q6_bias_mxmem2_A((void *) job->hmx_scales);
for (size_t r = 0; r < n_row_tiles; ++r) {
const __fp16 * d_diag = d_tiles + r * (n_row_tiles_g_br + 1) * HMX_FP16_TILE_N_ELMS;
const __fp16 * o_rc = o_prev + r * HMX_FP16_TILE_N_ELMS;
__fp16 * o_out = o_curr + r * DV_tiles * HMX_FP16_TILE_N_ELMS;
for (size_t c = 0; c < DV_tiles; ++c) {
const __fp16 * d_diag = d_tiles + r * (n_row_tiles_g_br + 1) * HMX_FP16_TILE_N_ELMS;
const __fp16 * o_rc = o_prev + (c * n_row_tiles_g_br + r) * HMX_FP16_TILE_N_ELMS;
__fp16 * o_out = o_curr + (r * DV_tiles + c) * HMX_FP16_TILE_N_ELMS;
hmx_fa_o_norm_tile(d_diag, o_rc, o_out);
o_rc += o_stride;
o_out += HMX_FP16_TILE_N_ELMS;
}
}
}
@@ -1495,7 +1475,7 @@ static void fa_push_mask_dma_gqa(
uint32_t G,
uint32_t m_line_bytes,
uint32_t kv_rows,
uint32_t n_rows_q,
uint32_t n_q_rows,
struct hmx_fa_context * factx
) {
for (uint32_t g = 0; g < G; ++g) {
@@ -1504,7 +1484,7 @@ static void fa_push_mask_dma_gqa(
const uint8_t * ms_src = (const uint8_t *) mask->data + q_start * mask->nb[1] +
im2 * mask->nb[2] + im3 * mask->nb[3] + kv_start * sizeof(__fp16);
uint8_t * ms_dst = (uint8_t *) factx->vtcm_mask_buf + g * m_line_bytes;
dma_queue_push(dma, dma_make_ptr(ms_dst, ms_src), G * m_line_bytes, mask->nb[1], kv_rows * sizeof(__fp16), n_rows_q);
dma_queue_push(dma, dma_make_ptr(ms_dst, ms_src), G * m_line_bytes, mask->nb[1], kv_rows * sizeof(__fp16), n_q_rows);
}
}
@@ -1602,57 +1582,62 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
const uint32_t G = factx.G;
// ======== VTCM allocation (GQA-aware) ========
// K/V row sizes drive the DMA descriptors (not the VTCM layout) and are used
// throughout the KV loop below.
const size_t size_k_row = DK * sizeof(__fp16);
const size_t size_v_row = DV * sizeof(__fp16);
const size_t size_k_row_padded = hex_round_up(size_k_row, 128);
const size_t size_v_row_padded = hex_round_up(size_v_row, 128);
// Build the VTCM layout once (shared with the host estimator) and place every
// scratch buffer at its computed offset.
struct hmx_fa_vtcm_layout L;
hmx_fa_vtcm_layout_build(&L, G, DK, DV, Br, Bc, n_threads, pipeline);
const size_t q_tile_bytes = hex_align_up(g_br * DK * sizeof(__fp16), 4096);
const size_t o_tile_bytes = hex_align_up(g_br * DV * sizeof(__fp16), 4096);
const size_t k_dma_bytes = hex_align_up(Bc * size_k_row_padded, 4096);
const size_t v_dma_bytes = hex_align_up(Bc * size_v_row_padded, 4096);
const size_t k_tile_bytes = hex_align_up(Bc * DK * sizeof(__fp16), 4096);
const size_t v_tile_bytes = hex_align_up(Bc * DV * sizeof(__fp16), 4096);
const size_t s_tile_bytes = hex_align_up(g_br * Bc * sizeof(__fp16), 4096);
const size_t d_tile_bytes = hex_align_up(g_br * g_br * sizeof(__fp16), 4096);
const size_t col_vec_bytes = hex_align_up(g_br * sizeof(float), 256);
const size_t row_vec_bytes = hex_align_up(Bc * sizeof(__fp16), 256);
const size_t m_line_bytes = hex_align_up(Bc * sizeof(__fp16), 128);
const size_t m_buf_bytes = hex_align_up(Br * m_line_bytes, 4096) * HMX_FA_DMA_CACHE_SIZE;
const size_t slopes_bytes = hex_align_up(g_br * sizeof(__fp16), 128);
if (L.total_bytes > ctx->vtcm_size) {
uint8_t * vtcm_cur = ctx->vtcm_base;
factx.vtcm_q_tiles = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, q_tile_bytes);
factx.vtcm_o_tiles[0] = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, o_tile_bytes);
factx.vtcm_o_tiles[1] = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, o_tile_bytes);
factx.vtcm_k_fp16[0] = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, k_dma_bytes);
factx.vtcm_k_fp16[1] = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, k_dma_bytes);
factx.vtcm_v_fp16[0] = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, v_dma_bytes);
factx.vtcm_v_fp16[1] = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, v_dma_bytes);
factx.vtcm_k_tiles = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, k_tile_bytes);
factx.vtcm_v_tiles[0] = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, v_tile_bytes);
if (pipeline) {
factx.vtcm_v_tiles[1] = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, v_tile_bytes);
} else {
factx.vtcm_v_tiles[1] = NULL;
}
factx.vtcm_s_tiles = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, s_tile_bytes);
factx.vtcm_p_tiles = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, s_tile_bytes);
factx.vtcm_d_tiles = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, d_tile_bytes);
factx.vtcm_m_vec = (HVX_Vector *) vtcm_seq_alloc(&vtcm_cur, col_vec_bytes);
factx.vtcm_l_vec = (HVX_Vector *) vtcm_seq_alloc(&vtcm_cur, col_vec_bytes);
factx.vtcm_s_rowmax = (HVX_Vector *) vtcm_seq_alloc(&vtcm_cur, col_vec_bytes);
factx.vtcm_p_rowsum = (HVX_Vector *) vtcm_seq_alloc(&vtcm_cur, col_vec_bytes);
factx.vtcm_row_bufs = (HVX_Vector *) vtcm_seq_alloc(&vtcm_cur, row_vec_bytes * 2 * n_threads);
factx.row_buf_stride = row_vec_bytes / sizeof(HVX_Vector);
factx.vtcm_hmx_scales_id = vtcm_seq_alloc(&vtcm_cur, 256);
factx.vtcm_hmx_scales_qk = vtcm_seq_alloc(&vtcm_cur, 256);
factx.vtcm_mask_buf = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, m_buf_bytes);
factx.mask_buf_row_stride = m_line_bytes / sizeof(__fp16);
factx.vtcm_slopes = (__fp16 *) vtcm_seq_alloc(&vtcm_cur, slopes_bytes);
dma_cache_init(&factx.m_cache, (uint8_t *) factx.vtcm_mask_buf, hex_align_up(Br * m_line_bytes, 4096), HMX_FA_DMA_CACHE_SIZE);
if ((size_t) (vtcm_cur - ctx->vtcm_base) > ctx->vtcm_size) {
return HTP_STATUS_VTCM_TOO_SMALL;
}
uint8_t * const base = ctx->vtcm_base;
factx.vtcm_q_tiles = VTCM_LAYOUT_PTR(__fp16, base, L.off_q_tiles);
factx.vtcm_o_tiles[0] = VTCM_LAYOUT_PTR(__fp16, base, L.off_o_tiles[0]);
factx.vtcm_o_tiles[1] = VTCM_LAYOUT_PTR(__fp16, base, L.off_o_tiles[1]);
factx.vtcm_k_fp16[0] = VTCM_LAYOUT_PTR(__fp16, base, L.off_k_fp16[0]);
factx.vtcm_k_fp16[1] = VTCM_LAYOUT_PTR(__fp16, base, L.off_k_fp16[1]);
factx.vtcm_v_fp16[0] = VTCM_LAYOUT_PTR(__fp16, base, L.off_v_fp16[0]);
factx.vtcm_v_fp16[1] = VTCM_LAYOUT_PTR(__fp16, base, L.off_v_fp16[1]);
factx.vtcm_k_tiles = VTCM_LAYOUT_PTR(__fp16, base, L.off_k_tiles);
factx.vtcm_v_tiles[0] = VTCM_LAYOUT_PTR(__fp16, base, L.off_v_tiles[0]);
factx.vtcm_v_tiles[1] = VTCM_LAYOUT_PTR_OPTIONAL(__fp16, base, L.off_v_tiles[1], pipeline);
factx.vtcm_s_tiles = VTCM_LAYOUT_PTR(__fp16, base, L.off_s_tiles);
factx.vtcm_p_tiles = VTCM_LAYOUT_PTR(__fp16, base, L.off_p_tiles);
factx.vtcm_d_tiles = VTCM_LAYOUT_PTR(__fp16, base, L.off_d_tiles);
factx.vtcm_m_vec = VTCM_LAYOUT_PTR(HVX_Vector, base, L.off_m_vec);
factx.vtcm_l_vec = VTCM_LAYOUT_PTR(HVX_Vector, base, L.off_l_vec);
factx.vtcm_s_rowmax = VTCM_LAYOUT_PTR(HVX_Vector, base, L.off_s_rowmax);
factx.vtcm_p_rowsum = VTCM_LAYOUT_PTR(HVX_Vector, base, L.off_p_rowsum);
factx.vtcm_row_bufs = VTCM_LAYOUT_PTR(HVX_Vector, base, L.off_row_bufs);
factx.row_buf_stride = L.row_buf_stride;
factx.vtcm_hmx_scales_id = VTCM_LAYOUT_PTR(uint8_t, base, L.off_hmx_scales_id);
factx.vtcm_hmx_scales_qk = VTCM_LAYOUT_PTR(uint8_t, base, L.off_hmx_scales_qk);
factx.vtcm_mask_buf = VTCM_LAYOUT_PTR(__fp16, base, L.off_mask_buf);
factx.mask_buf_row_stride = L.mask_buf_row_stride;
factx.q_tile_bytes = L.q_tile_bytes;
factx.o_tile_bytes = L.o_tile_bytes;
factx.col_vec_bytes = L.col_vec_bytes;
factx.d_tile_bytes = L.d_tile_bytes;
factx.vtcm_slopes = VTCM_LAYOUT_PTR(__fp16, base, L.off_slopes);
const size_t m_line_bytes = L.m_line_bytes; // used by the mask DMAs in the KV loop
dma_cache_init(&factx.m_cache, (uint8_t *) factx.vtcm_mask_buf, L.m_buf_slot_bytes, HMX_FA_DMA_CACHE_SIZE);
// ======== Initialize HMX output scales ========
hmx_init_column_scales(factx.vtcm_hmx_scales_id, Q6_V_vsplat_R(0x3c00)); // 1.0
hmx_init_column_scales(factx.vtcm_hmx_scales_qk, hvx_vec_splat_f16(factx.scale));
@@ -1670,6 +1655,11 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
const size_t qo_element_size = factx.is_q_fp32 ? sizeof(float) : sizeof(__fp16);
// ======== HMX lock strategy ========
if (!factx.pipeline) {
HAP_compute_res_hmx_lock(ctx->vtcm_rctx);
}
// ======== Reusable job descriptors for pipeline ========
hmx_fa_qk_job_t qk_job;
hmx_fa_o_update_job_t ou_job;
@@ -1679,44 +1669,28 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
for (uint32_t ib3 = 0; ib3 < neq3; ++ib3) {
const uint32_t im3 = mask ? fastmodulo(ib3, mask->ne[3], &factx.src3_div3) : 0;
for (uint32_t q_start = 0; q_start < neq1; q_start += Br) {
const uint32_t n_rows_q = hex_smin(Br, neq1 - q_start);
const size_t n_rows_g = n_rows_q * G;
const uint32_t n_q_rows = hex_smin(Br, neq1 - q_start);
const size_t n_rows_g = n_q_rows * G;
const size_t g_br_actual = hex_align_up(n_rows_g, HMX_FP16_TILE_N_ROWS);
const size_t n_row_tiles = g_br_actual / HMX_FP16_TILE_N_ROWS;
for (uint32_t kv_head = 0; kv_head < n_kv_heads; ++kv_head) {
const uint32_t ik2 = kv_head;
const uint32_t ik3 = fastdiv(ib3, &kparams->broadcast_rk3);
const uint32_t ik3 = ib3 / (neq3 / k->ne[3]);
const uint32_t iv2 = kv_head;
const uint32_t iv3 = fastdiv(ib3, &kparams->broadcast_rv3);
const uint32_t iv3 = ib3 / (neq3 / v->ne[3]);
// 1. Push Q DMA (if Q DMA is used)
const size_t o_tile_bytes = factx.o_tile_bytes;
const bool use_q_dma = (2 * o_tile_bytes >= factx.g_br * factx.DK * (factx.is_q_fp32 ? 4 : 2));
if (use_q_dma) {
const bool q_transposed = q->nb[1] < q->nb[2];
const uint8_t * q_ptr = (const uint8_t *) q->data + q_start * q->nb[1] + (kv_head * factx.G) * q->nb[2] + ib3 * q->nb[3];
const size_t el_size = factx.is_q_fp32 ? sizeof(float) : sizeof(__fp16);
const size_t q_row_bytes = q_transposed ? n_rows_q * factx.DK * el_size : factx.G * factx.DK * el_size;
const size_t src_stride = q_transposed ? q->nb[2] : q->nb[1];
const size_t n_rows = q_transposed ? factx.G : n_rows_q;
dma_queue_push(dma, dma_make_ptr(factx.vtcm_o_tiles[0], q_ptr), q_row_bytes, hex_smax(src_stride, q_row_bytes), q_row_bytes, n_rows);
}
// 2. Prefetch first KV block
// Prefetch first KV block
if (factx.n_kv_blocks > 0) {
const uint32_t kv_rows0 = hex_smin(Bc, nek1);
const uint8_t * k_src = (const uint8_t *) k->data + ik2 * k->nb[2] + ik3 * k->nb[3];
dma_queue_push(dma, dma_make_ptr(factx.vtcm_k_fp16[0], k_src), size_k_row_padded, k->nb[1], size_k_row, kv_rows0);
dma_queue_push(dma, dma_make_ptr(factx.vtcm_k_fp16[0], k_src), size_k_row_padded, k->nb[1],
size_k_row, kv_rows0);
const uint8_t * v_src = (const uint8_t *) v->data + iv2 * v->nb[2] + iv3 * v->nb[3];
dma_queue_push(dma, dma_make_ptr(factx.vtcm_v_fp16[0], v_src), size_v_row_padded, v->nb[1], size_v_row, kv_rows0);
}
// 3. Pop Q DMA (blocks until Q is loaded)
if (use_q_dma) {
dma_queue_pop(dma);
dma_queue_push(dma, dma_make_ptr(factx.vtcm_v_fp16[0], v_src), size_v_row_padded, v->nb[1],
size_v_row, kv_rows0);
}
// ---- Load Q block & Initialize per-block state ----
@@ -1735,10 +1709,12 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
const size_t k_src_stride = size_k_row_padded / sizeof(__fp16);
const size_t v_src_stride = size_v_row_padded / sizeof(__fp16);
struct hmx_queue * hmx_q = ctx->hmx_queue;
if (factx.pipeline) {
// ==================================================================
// Pipeline path
// ==================================================================
struct hmx_queue * hmx_q = ctx->hmx_queue;
for (uint32_t kv_blk = 0; kv_blk < factx.n_kv_blocks; ++kv_blk) {
const uint32_t kv_start = kv_blk * Bc;
const uint32_t kv_rows = hex_smin(Bc, nek1 - kv_start);
@@ -1748,22 +1724,15 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
if (mask) {
if (__builtin_expect(factx.mask_broadcast, true)) {
const uint8_t * ms_src = (const uint8_t *) mask->data + q_start * mask->nb[1] + im3 * mask->nb[3] + kv_start * sizeof(__fp16);
dma_cache_push(dma, &factx.m_cache, ms_src, m_line_bytes, mask->nb[1], kv_rows * sizeof(__fp16), n_rows_q);
dma_cache_push(dma, &factx.m_cache, ms_src, m_line_bytes, mask->nb[1], kv_rows * sizeof(__fp16), n_q_rows);
} else {
fa_push_mask_dma_gqa(dma, mask, q_start, im3, kv_start, kv_head, G, m_line_bytes, kv_rows, n_rows_q, &factx);
fa_push_mask_dma_gqa(dma, mask, q_start, im3, kv_start, kv_head, G, m_line_bytes, kv_rows, n_q_rows, &factx);
}
}
// Prefetch next KV block early
if (kv_blk + 1 < factx.n_kv_blocks) {
const uint32_t prefetch_start = (kv_blk + 1) * Bc;
const uint32_t prefetch_rows = hex_smin(Bc, nek1 - prefetch_start);
const size_t prefetch_buf = 1 - buf_idx;
const uint8_t * k_prefetch_src = (const uint8_t *) k->data + prefetch_start * k->nb[1] + ik2 * k->nb[2] + ik3 * k->nb[3];
dma_queue_push(dma, dma_make_ptr(factx.vtcm_k_fp16[prefetch_buf], k_prefetch_src), size_k_row_padded, k->nb[1], size_k_row, prefetch_rows);
const uint8_t * v_prefetch_src = (const uint8_t *) v->data + prefetch_start * v->nb[1] + iv2 * v->nb[2] + iv3 * v->nb[3];
dma_queue_push(dma, dma_make_ptr(factx.vtcm_v_fp16[prefetch_buf], v_prefetch_src), size_v_row_padded, v->nb[1], size_v_row, prefetch_rows);
}
// Wait for current KV DMA
dma_queue_pop(dma); // K
dma_queue_pop(dma); // V
// ---- Phase 1: K_int ----
if (kv_blk > 0) {
@@ -1780,10 +1749,7 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
ou_job.DV = DV;
hmx_queue_push(hmx_q, hmx_queue_make_desc(hmx_fa_o_update_worker, &ou_job));
}
// Wait for current K DMA and interleave
void * curr_k = dma_queue_pop(dma).dst;
fa_phase_k_interleave(&factx, kv_rows, k_src_stride, curr_k, kv_start);
fa_phase_k_interleave(&factx, kv_rows, k_src_stride, buf_idx, kv_start);
// ---- Phase 2: qk_dot ----
qk_job.q_tiles = factx.vtcm_q_tiles;
@@ -1796,9 +1762,16 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
qk_job.hmx_scales = factx.vtcm_hmx_scales_qk;
hmx_queue_push(hmx_q, hmx_queue_make_desc(hmx_fa_qk_dot_worker, &qk_job));
// Wait for current V DMA and interleave
void * curr_v = dma_queue_pop(dma).dst;
fa_phase_v_interleave(&factx, kv_rows, v_src_stride, curr_v, factx.vtcm_v_tiles[buf_idx], n_tiles_per_bc, kv_start);
if (kv_blk + 1 < factx.n_kv_blocks) {
const uint32_t prefetch_start = (kv_blk + 1) * Bc;
const uint32_t prefetch_rows = hex_smin(Bc, nek1 - prefetch_start);
const size_t prefetch_buf = 1 - buf_idx;
const uint8_t * k_prefetch_src = (const uint8_t *) k->data + prefetch_start * k->nb[1] + ik2 * k->nb[2] + ik3 * k->nb[3];
dma_queue_push(dma, dma_make_ptr(factx.vtcm_k_fp16[prefetch_buf], k_prefetch_src), size_k_row_padded, k->nb[1], size_k_row, prefetch_rows);
const uint8_t * v_prefetch_src = (const uint8_t *) v->data + prefetch_start * v->nb[1] + iv2 * v->nb[2] + iv3 * v->nb[3];
dma_queue_push(dma, dma_make_ptr(factx.vtcm_v_fp16[prefetch_buf], v_prefetch_src), size_v_row_padded, v->nb[1], size_v_row, prefetch_rows);
}
fa_phase_v_interleave(&factx, kv_rows, v_src_stride, buf_idx, n_tiles_per_bc, kv_start);
if (kv_blk > 0) {
hmx_queue_pop(hmx_q);
@@ -1865,21 +1838,24 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
}
} else {
// ==================================================================
// Fallback path
// ==================================================================
for (uint32_t kv_blk = 0; kv_blk < factx.n_kv_blocks; ++kv_blk) {
const uint32_t kv_start = kv_blk * Bc;
const uint32_t kv_rows = hex_smin(Bc, nek1 - kv_start);
const size_t n_col_tiles = hmx_ceil_div(kv_rows, HMX_FP16_TILE_N_COLS);
dma_queue_pop(dma); // K
dma_queue_pop(dma); // V
if (mask) {
if (__builtin_expect(factx.mask_broadcast, true)) {
const uint8_t * ms_src = (const uint8_t *) mask->data + q_start * mask->nb[1] + im3 * mask->nb[3] + kv_start * sizeof(__fp16);
dma_cache_push(dma, &factx.m_cache, ms_src, m_line_bytes, mask->nb[1], kv_rows * sizeof(__fp16), n_rows_q);
dma_cache_push(dma, &factx.m_cache, ms_src, m_line_bytes, mask->nb[1], kv_rows * sizeof(__fp16), n_q_rows);
} else {
fa_push_mask_dma_gqa(dma, mask, q_start, im3, kv_start, kv_head, G, m_line_bytes, kv_rows, n_rows_q, &factx);
fa_push_mask_dma_gqa(dma, mask, q_start, im3, kv_start, kv_head, G, m_line_bytes, kv_rows, n_q_rows, &factx);
}
}
if (kv_blk + 1 < factx.n_kv_blocks) {
const uint32_t prefetch_start = (kv_blk + 1) * Bc;
const uint32_t prefetch_rows = hex_smin(Bc, nek1 - prefetch_start);
@@ -1889,29 +1865,31 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
const uint8_t * v_prefetch_src = (const uint8_t *) v->data + prefetch_start * v->nb[1] + iv2 * v->nb[2] + iv3 * v->nb[3];
dma_queue_push(dma, dma_make_ptr(factx.vtcm_v_fp16[prefetch_buf], v_prefetch_src), size_v_row_padded, v->nb[1], size_v_row, prefetch_rows);
}
// Wait for current K DMA and interleave
void * curr_k = dma_queue_pop(dma).dst;
fa_phase_k_interleave(&factx, kv_rows, k_src_stride, curr_k, kv_start);
fa_phase_k_interleave(&factx, kv_rows, k_src_stride, buf_idx, kv_start);
{
qk_job.q_tiles = factx.vtcm_q_tiles;
qk_job.k_tiles = factx.vtcm_k_tiles;
qk_job.s_tiles = factx.vtcm_s_tiles;
qk_job.n_row_tiles = n_row_tiles;
qk_job.n_col_tiles = n_col_tiles;
qk_job.n_dot_tiles = (size_t) (DK / 32);
qk_job.n_tiles_per_bc = n_tiles_per_bc;
qk_job.hmx_scales = factx.vtcm_hmx_scales_qk;
const size_t n_dot_tiles = (size_t) (DK / 32);
const __fp16 * restrict q_base = factx.vtcm_q_tiles;
const __fp16 * restrict k_base = factx.vtcm_k_tiles;
__fp16 * restrict s_base = factx.vtcm_s_tiles;
__builtin_assume(n_row_tiles > 0);
__builtin_assume(n_col_tiles > 0);
__builtin_assume(n_dot_tiles > 0);
hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_fa_qk_dot_worker, &qk_job));
hmx_queue_pop(ctx->hmx_queue);
htp_trace_event_start(tr_hmx, HTP_TRACE_EVT_HMX_COMP, (uint16_t) q_start);
Q6_bias_mxmem2_A((void *) factx.vtcm_hmx_scales_qk);
for (size_t r = 0; r < n_row_tiles; ++r) {
for (size_t c = 0; c < n_col_tiles; ++c) {
const __fp16 * row_tiles = q_base + r * HMX_FP16_TILE_N_ROWS * DK;
const __fp16 * col_tiles = k_base + c * HMX_FP16_TILE_N_COLS * DK;
__fp16 * out_tile = s_base + (r * n_tiles_per_bc + c) * HMX_FP16_TILE_N_ELMS;
hmx_fa_qk_dot_tile(row_tiles, col_tiles, out_tile, n_dot_tiles);
}
}
htp_trace_event_stop(tr_hmx, HTP_TRACE_EVT_HMX_COMP, (uint16_t) q_start);
}
// Wait for current V DMA and interleave
void * curr_v = dma_queue_pop(dma).dst;
fa_phase_v_interleave(&factx, kv_rows, v_src_stride, curr_v, factx.vtcm_v_tiles[0], n_tiles_per_bc, kv_start);
// ---- Phase 3: softmax + build_D ----
__fp16 * current_mask_vtcm = NULL;
if (mask) {
@@ -1944,23 +1922,33 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
sargs.mask_vtcm_row_stride = factx.mask_buf_row_stride;
sargs.slopes = factx.vtcm_slopes;
fa_phase_softmax_and_build_d(&factx, &sargs, n_row_tiles, n_row_tiles_g_br);
fa_phase_v_interleave(&factx, kv_rows, v_src_stride, buf_idx, n_tiles_per_bc, kv_start);
{
ou_job.o_curr = o_tile_curr;
ou_job.o_prev = o_tile_prev;
ou_job.p_tiles = factx.vtcm_p_tiles;
ou_job.v_tiles = factx.vtcm_v_tiles[0];
ou_job.d_tiles = factx.vtcm_d_tiles;
ou_job.hmx_scales = factx.vtcm_hmx_scales_id;
ou_job.n_row_tiles = n_row_tiles;
ou_job.n_col_tiles = n_col_tiles;
ou_job.n_row_tiles_g_br = n_row_tiles_g_br;
ou_job.n_tiles_per_bc = n_tiles_per_bc;
ou_job.DV = DV;
const size_t DV_tiles = (size_t) (DV / 32);
const __fp16 * restrict d_base = factx.vtcm_d_tiles;
const __fp16 * restrict p_base = factx.vtcm_p_tiles;
const __fp16 * restrict v_base = factx.vtcm_v_tiles[0];
const __fp16 * restrict op_base = o_tile_prev;
__fp16 * restrict oc_base = o_tile_curr;
__builtin_assume(n_row_tiles > 0);
__builtin_assume(n_col_tiles > 0);
__builtin_assume(DV_tiles > 0);
hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_fa_o_update_worker, &ou_job));
hmx_queue_pop(ctx->hmx_queue);
htp_trace_event_start(tr_hmx, HTP_TRACE_EVT_HMX_COMP, (uint16_t) q_start);
Q6_bias_mxmem2_A((void *) factx.vtcm_hmx_scales_id);
for (size_t r = 0; r < n_row_tiles; ++r) {
for (size_t c = 0; c < DV_tiles; ++c) {
const __fp16 * d_diag = d_base + r * (n_row_tiles_g_br + 1) * HMX_FP16_TILE_N_ELMS;
const __fp16 * o_rc = op_base + (c * n_row_tiles_g_br + r) * HMX_FP16_TILE_N_ELMS;
const __fp16 * p_tile_in = p_base + (r * n_tiles_per_bc) * HMX_FP16_TILE_N_ELMS;
const __fp16 * v_tile_in = v_base + (c * n_tiles_per_bc) * HMX_FP16_TILE_N_ELMS;
__fp16 * o_tile_out = oc_base + (c * n_row_tiles_g_br + r) * HMX_FP16_TILE_N_ELMS;
hmx_fa_o_update_tile(d_diag, o_rc, p_tile_in, v_tile_in, o_tile_out, n_col_tiles);
}
}
htp_trace_event_stop(tr_hmx, HTP_TRACE_EVT_HMX_COMP, (uint16_t) q_start);
hex_swap_ptr((void **) &o_tile_curr, (void **) &o_tile_prev);
}
@@ -1974,15 +1962,37 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
fa_build_d_diag_inv_l(&factx, n_row_tiles, n_row_tiles_g_br);
htp_trace_event_stop(tr_hvx, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) q_start);
on_job.o_curr = o_tile_curr;
on_job.o_prev = o_tile_prev;
on_job.d_tiles = factx.vtcm_d_tiles;
on_job.hmx_scales = factx.vtcm_hmx_scales_id;
on_job.n_row_tiles = n_row_tiles;
on_job.n_row_tiles_g_br = n_row_tiles_g_br;
on_job.DV = DV;
hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_fa_o_norm_worker, &on_job));
hmx_queue_pop(ctx->hmx_queue);
if (factx.pipeline) {
on_job.o_curr = o_tile_curr;
on_job.o_prev = o_tile_prev;
on_job.d_tiles = factx.vtcm_d_tiles;
on_job.hmx_scales = factx.vtcm_hmx_scales_id;
on_job.n_row_tiles = n_row_tiles;
on_job.n_row_tiles_g_br = n_row_tiles_g_br;
on_job.DV = DV;
hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_fa_o_norm_worker, &on_job));
hmx_queue_pop(ctx->hmx_queue);
} else {
const size_t DV_tiles = (size_t) (DV / 32);
const __fp16 * restrict d_base = factx.vtcm_d_tiles;
const __fp16 * restrict op_base = o_tile_prev;
__fp16 * restrict oc_base = o_tile_curr;
__builtin_assume(n_row_tiles > 0);
__builtin_assume(DV_tiles > 0);
htp_trace_event_start(tr_hmx, HTP_TRACE_EVT_HMX_COMP, (uint16_t) q_start);
Q6_bias_mxmem2_A((void *) factx.vtcm_hmx_scales_id);
for (size_t r = 0; r < n_row_tiles; ++r) {
for (size_t c = 0; c < DV_tiles; ++c) {
const __fp16 * d_diag = d_base + r * (n_row_tiles_g_br + 1) * HMX_FP16_TILE_N_ELMS;
const __fp16 * o_rc = op_base + (c * n_row_tiles_g_br + r) * HMX_FP16_TILE_N_ELMS;
__fp16 * o_out = oc_base + (r * DV_tiles + c) * HMX_FP16_TILE_N_ELMS;
hmx_fa_o_norm_tile(d_diag, o_rc, o_out);
}
}
htp_trace_event_stop(tr_hmx, HTP_TRACE_EVT_HMX_COMP, (uint16_t) q_start);
}
}
// ---- Store O block ----
@@ -1991,6 +2001,12 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
}
}
if (factx.pipeline) {
hmx_queue_suspend(ctx->hmx_queue);
} else {
HAP_compute_res_hmx_unlock(ctx->vtcm_rctx);
}
return HTP_STATUS_OK;
}
@@ -2024,10 +2040,10 @@ int op_flash_attn_ext(struct htp_ops_context * octx) {
factx.src0_div21 = kparams->u.hvx.src0_div21;
factx.src0_div1 = kparams->u.hvx.src0_div1;
factx.broadcast_rk2 = kparams->broadcast_rk2;
factx.broadcast_rk3 = kparams->broadcast_rk3;
factx.broadcast_rv2 = kparams->broadcast_rv2;
factx.broadcast_rv3 = kparams->broadcast_rv3;
factx.broadcast_rk2 = kparams->u.hvx.broadcast_rk2;
factx.broadcast_rk3 = kparams->u.hvx.broadcast_rk3;
factx.broadcast_rv2 = kparams->u.hvx.broadcast_rv2;
factx.broadcast_rv3 = kparams->u.hvx.broadcast_rv3;
if (mask) {
factx.src3_div2 = kparams->src3_div2;
+93 -143
View File
@@ -7,23 +7,19 @@
#include "hex-fastdiv.h"
#include "hex-common.h"
#include "htp-vtcm.h"
#ifdef __cplusplus
extern "C" {
#endif
// Tile constants (mirrored from hmx-utils.h for use on host side if needed)
#define HTP_FA_HMX_TILE_SIZE 2048
#define HMX_FP16_TILE_SIZE 2048
#define HMX_FP16_TILE_N_ROWS 32
#define HMX_FP16_TILE_N_COLS 32
#define HMX_FP16_TILE_N_ELMS 1024
#define HMX_FP16_TILE_SIZE 2048
#define HVX_FA_DMA_CACHE_SIZE 128
#define HMX_FA_DMA_CACHE_SIZE 4
#define HTP_FA_M_INITIAL_VAL -10000.0f
enum htp_fa_kernel_type {
@@ -58,11 +54,6 @@ struct htp_fa_kernel_params {
struct fastdiv_values src3_div2;
struct fastdiv_values src3_div3;
struct fastdiv_values broadcast_rk2;
struct fastdiv_values broadcast_rk3;
struct fastdiv_values broadcast_rv2;
struct fastdiv_values broadcast_rv3;
union {
struct {
uint32_t g_br;
@@ -78,6 +69,10 @@ struct htp_fa_kernel_params {
uint32_t size_v_row_padded;
struct fastdiv_values src0_div21;
struct fastdiv_values src0_div1;
struct fastdiv_values broadcast_rk2;
struct fastdiv_values broadcast_rk3;
struct fastdiv_values broadcast_rv2;
struct fastdiv_values broadcast_rv3;
} hvx;
} u;
};
@@ -86,124 +81,39 @@ struct htp_fa_kernel_params {
static_assert(sizeof(struct htp_fa_kernel_params) <= 128, "htp_fa_kernel_params is too large for kernel_params blob");
#endif
// VTCM region layout for the HMX flash-attention kernel.
//
// Single source of truth for both the host (which needs the total size to pick a
// (Br, Bc) tiling that fits the VTCM budget) and the device (which needs the actual
// byte offsets to place each scratch buffer). Building the layout once and reading
// offsets/total from it makes host estimate and device allocation impossible to
// desync -- previously they were duplicated formulas in two files and drifted.
//
// All fields are byte offsets / byte sizes -- no HVX_Vector type is named here so the
// header stays host-includable. The device casts (base + off_*) to the proper type.
// An offset of 0 marks a region that is not allocated for this configuration (only
// off_v_tiles[1], which exists only when pipelining); the device sets such pointers NULL.
struct hmx_fa_vtcm_layout {
// Byte offsets from vtcm_base for each region.
size_t off_q_tiles;
size_t off_o_tiles[2];
size_t off_k_fp16[2];
size_t off_v_fp16[2];
size_t off_k_tiles;
size_t off_v_tiles[2]; // [1] allocated only when pipeline, else 0
size_t off_s_tiles;
size_t off_p_tiles;
size_t off_d_tiles;
size_t off_m_vec;
size_t off_l_vec;
size_t off_s_rowmax;
size_t off_p_rowsum;
size_t off_row_bufs;
size_t off_hmx_scales_id;
size_t off_hmx_scales_qk;
size_t off_mask_buf;
size_t off_slopes;
// Region byte sizes reused by the device at runtime (not just for allocation).
size_t q_tile_bytes;
size_t o_tile_bytes;
size_t s_tile_bytes; // S and P tiles (same size)
size_t d_tile_bytes;
size_t m_line_bytes; // one mask row
size_t m_buf_slot_bytes; // one dma_cache slot = align_up(Br * m_line_bytes, 4096)
size_t col_vec_bytes;
// Derived strides.
size_t row_buf_stride; // HVX vectors (128B) per row buffer
size_t mask_buf_row_stride; // __fp16 elements per row in the mask buffer
bool pipeline;
size_t total_bytes;
};
// Build the VTCM layout.
static inline void hmx_fa_vtcm_layout_build(struct hmx_fa_vtcm_layout * L,
size_t gqa_factor, size_t DK, size_t DV,
size_t Br, size_t Bc, size_t n_threads, bool pipeline) {
// Exact VTCM usage for a given (gqa_factor, DK, DV, Br, Bc) configuration.
// g_br = hex_align_up(gqa_factor * Br, 32) replaces Br for all Q/O/S/P/D dimensions.
// Layout: Q + O_ping + O_pong + K_dma*2 + V_dma*2 + K_tile + V_tile + S + P + D + vectors + scales
// Mask is DMA'd into a VTCM buffer (Br rows per KV block) to avoid DDR reads in softmax.
static inline size_t hmx_fa_compute_vtcm_usage(size_t gqa_factor, size_t DK, size_t DV, size_t Br, size_t Bc, size_t n_threads, bool pipeline) {
const size_t g_br = hex_align_up(gqa_factor * Br, HMX_FP16_TILE_N_ROWS);
const size_t q_tile_size = hex_align_up(g_br * DK * sizeof(__fp16), HTP_FA_HMX_TILE_SIZE);
const size_t o_tile_size = hex_align_up(g_br * DV * sizeof(__fp16), HTP_FA_HMX_TILE_SIZE);
const size_t k_tile_size = hex_align_up(Bc * DK * sizeof(__fp16), HTP_FA_HMX_TILE_SIZE);
const size_t v_tile_size = hex_align_up(Bc * DV * sizeof(__fp16), HTP_FA_HMX_TILE_SIZE);
const size_t s_tile_size = hex_align_up(g_br * Bc * sizeof(__fp16), HTP_FA_HMX_TILE_SIZE);
const size_t d_tile_size = hex_align_up(g_br * g_br * sizeof(__fp16), HTP_FA_HMX_TILE_SIZE);
const size_t k_dma_size = hex_align_up(Bc * hex_round_up(DK * sizeof(__fp16), 128), 128);
const size_t v_dma_size = hex_align_up(Bc * hex_round_up(DV * sizeof(__fp16), 128), 128);
const size_t col_vec_size = hex_align_up(g_br * sizeof(float), 256);
const size_t row_vec_size = hex_align_up(Bc * sizeof(__fp16), 256);
const size_t m_line_size = hex_align_up(Bc * sizeof(__fp16), 128);
const size_t m_buf_slot = hex_align_up(Br * m_line_size, 256);
const size_t m_buf_size = m_buf_slot * HMX_FA_DMA_CACHE_SIZE;
const size_t q_tile_size = hex_align_up(g_br * DK * sizeof(__fp16), 4096); // Q: [g_br, DK]
const size_t o_tile_size = hex_align_up(g_br * DV * sizeof(__fp16), 4096); // O: [g_br, DV] x2 ping-pong
const size_t k_dma_size = hex_align_up(Bc * hex_round_up(DK * sizeof(__fp16), 128), 4096); // K DMA: [Bc, DK] x2 double-buf
const size_t v_dma_size = hex_align_up(Bc * hex_round_up(DV * sizeof(__fp16), 128), 4096); // V DMA: [Bc, DV] x2 double-buf
const size_t k_tile_size = hex_align_up(Bc * DK * sizeof(__fp16), 4096); // K tiles: [Bc, DK] interleaved
const size_t v_tile_size = hex_align_up(Bc * DV * sizeof(__fp16), 4096); // V tiles: [Bc, DV] interleaved
const size_t s_tile_size = hex_align_up(g_br * Bc * sizeof(__fp16), 4096); // S/P:[g_br, Bc]
const size_t d_tile_size = hex_align_up(g_br * g_br * sizeof(__fp16), 4096); // D: [g_br, g_br]
const size_t col_vec_size = hex_align_up(g_br * sizeof(float), 256); // m, l, etc.
const size_t row_vec_size = hex_align_up(Bc * sizeof(__fp16), 256);
const size_t m_line_size = hex_align_up(Bc * sizeof(__fp16), 128);
const size_t m_buf_size = hex_align_up(Br * m_line_size, 4096) * HMX_FA_DMA_CACHE_SIZE;
const size_t slopes_size = hex_align_up(g_br * sizeof(__fp16), 128);
size_t off = 0;
// Section 1: HMX Tiled Buffers (FA_HMX_TILE_SIZE = 2KB Aligned)
VTCM_LAYOUT_ALLOC(off, off_q_tiles, q_tile_size);
VTCM_LAYOUT_ALLOC(off, off_o_tiles[0], o_tile_size);
VTCM_LAYOUT_ALLOC(off, off_o_tiles[1], o_tile_size);
VTCM_LAYOUT_ALLOC(off, off_k_tiles, k_tile_size);
VTCM_LAYOUT_ALLOC(off, off_v_tiles[0], v_tile_size);
VTCM_LAYOUT_ALLOC_OPTIONAL(off, off_v_tiles[1], v_tile_size, pipeline);
VTCM_LAYOUT_ALLOC(off, off_s_tiles, s_tile_size);
VTCM_LAYOUT_ALLOC(off, off_p_tiles, s_tile_size);
VTCM_LAYOUT_ALLOC(off, off_d_tiles, d_tile_size);
// Section 2: HVX/DMA flat and vector buffers (128B / 256B Aligned)
VTCM_LAYOUT_ALLOC(off, off_k_fp16[0], k_dma_size);
VTCM_LAYOUT_ALLOC(off, off_k_fp16[1], k_dma_size);
VTCM_LAYOUT_ALLOC(off, off_v_fp16[0], v_dma_size);
VTCM_LAYOUT_ALLOC(off, off_v_fp16[1], v_dma_size);
VTCM_LAYOUT_ALLOC(off, off_m_vec, col_vec_size);
VTCM_LAYOUT_ALLOC(off, off_l_vec, col_vec_size);
VTCM_LAYOUT_ALLOC(off, off_s_rowmax, col_vec_size);
VTCM_LAYOUT_ALLOC(off, off_p_rowsum, col_vec_size);
VTCM_LAYOUT_ALLOC(off, off_row_bufs, row_vec_size * 2 * n_threads);
VTCM_LAYOUT_ALLOC(off, off_hmx_scales_id, 256);
VTCM_LAYOUT_ALLOC(off, off_hmx_scales_qk, 256);
VTCM_LAYOUT_ALLOC(off, off_mask_buf, m_buf_size);
VTCM_LAYOUT_ALLOC(off, off_slopes, slopes_size);
L->q_tile_bytes = q_tile_size;
L->o_tile_bytes = o_tile_size;
L->col_vec_bytes = col_vec_size;
L->s_tile_bytes = s_tile_size;
L->d_tile_bytes = d_tile_size;
L->m_line_bytes = m_line_size;
L->m_buf_slot_bytes = m_buf_slot;
L->row_buf_stride = row_vec_size / 128;
L->mask_buf_row_stride = m_line_size / sizeof(__fp16);
L->pipeline = pipeline;
L->total_bytes = off;
}
// Exact VTCM usage for a given (gqa_factor, DK, DV, Br, Bc) configuration.
static inline size_t hmx_fa_compute_vtcm_usage(size_t gqa_factor, size_t DK, size_t DV, size_t Br, size_t Bc, size_t n_threads, bool pipeline) {
struct hmx_fa_vtcm_layout L;
hmx_fa_vtcm_layout_build(&L, gqa_factor, DK, DV, Br, Bc, n_threads, pipeline);
return L.total_bytes;
return q_tile_size * 1 // Q tiles
+ o_tile_size * 2 // O ping-pong
+ k_dma_size * 2 // K DMA x2
+ v_dma_size * 2 // V DMA x2
+ k_tile_size * 1 // K tiles
+ v_tile_size * (pipeline ? 2 : 1) // V tiles (double-buffered if pipelining)
+ s_tile_size * 2 // S + P
+ d_tile_size * 1 // D (diagonal matrix)
+ col_vec_size * 4 // m_vec, l_vec, s_rowmax, p_rowsum
+ row_vec_size * 2 * n_threads // per-thread softmax row scratch
+ m_buf_size * 1 // mask VTCM buffer [Br rows]
+ slopes_size // Slopes
+ 256 * 2; // HMX scales (id + qk)
}
#define FA_HVX_BLOCK_SIZE 64
@@ -243,8 +153,23 @@ static inline int hmx_fa_find_chunk_size(size_t * Br_out,
const size_t T = HMX_FP16_TILE_N_ROWS; // 32
const size_t br_unit = hmx_ceil_div(T, gqa_factor);
const size_t bc_unit = HMX_FP16_TILE_N_COLS * 2; // 64
const size_t fp16 = sizeof(__fp16);
const bool can_pipeline = (kv_len >= FA_MIN_KV_BLOCKS * bc_unit && n_threads >= 2);
// Approximate per-unit VTCM costs (without per-buffer alignment padding).
const size_t per_gbr = (DK + 2 * DV) * fp16 + 4 * sizeof(float); // Q + O*2 + 4 col vectors
const size_t per_gbr2 = fp16; // D diagonal matrix
const size_t per_bc =
3 * DK * fp16 + (can_pipeline ? 4 : 3) * DV * fp16 + 2 * n_threads * fp16; // K/V DMA x2 + tiles + row bufs
const size_t per_gbr_bc = 2 * fp16; // S + P
const size_t overhead = 256 * 2 + 13 * 4096;
if (vtcm_budget <= overhead) {
return -1;
}
const size_t usable = vtcm_budget - overhead;
// Br_max: largest Br aligned to br_unit that does not exceed qo_len.
const size_t Br_max = qo_len >= br_unit ? hex_align_down(qo_len, br_unit) : br_unit;
@@ -260,26 +185,51 @@ static inline int hmx_fa_find_chunk_size(size_t * Br_out,
size_t best_Br = 0, best_Bc = 0;
for (size_t Br = Br_max; Br >= br_unit; Br -= br_unit) {
// Try all Bc candidates from Bc_limit down to bc_unit
for (size_t Bc = Bc_limit; Bc >= bc_unit; Bc -= bc_unit) {
size_t vtcm_needed = hmx_fa_compute_vtcm_usage(gqa_factor, DK, DV, Br, Bc, n_threads, can_pipeline);
if (vtcm_needed <= vtcm_budget) {
// This Bc fits for this Br!
const size_t q_blocks = (qo_len + Br - 1) / Br;
const size_t kv_blocks = (kv_len + Bc - 1) / Bc;
const size_t cost = q_blocks * (c_q_fixed + kv_blocks * c_iter_fixed);
const size_t mn = Br * Bc;
const size_t g_br = hex_align_up(gqa_factor * Br, T);
if (cost < best_cost || (cost == best_cost && mn > best_mn)) {
best_cost = cost;
best_mn = mn;
best_Br = Br;
best_Bc = Bc;
}
// Since we iterate Bc from largest to smallest, this is the largest Bc that fits
// for this Br. We can break to the next Br.
// g_br-dependent VTCM cost: g_br * per_gbr + g_br*g_br * per_gbr2
const size_t gbr_cost = g_br * per_gbr + g_br * g_br * per_gbr2;
if (gbr_cost >= usable) {
if (Br == br_unit) {
break;
}
continue;
}
// Analytically solve for max Bc:
// remain >= Bc * (per_bc + g_br * per_gbr_bc + Br * fp16 * HMX_FA_DMA_CACHE_SIZE)
// The Br * fp16 term accounts for the VTCM mask buffer [Br * Bc].
const size_t remain = usable - gbr_cost;
const size_t bc_denom = per_bc + g_br * per_gbr_bc + Br * fp16 * HMX_FA_DMA_CACHE_SIZE;
size_t Bc = hex_smin(hex_align_down(remain / bc_denom, bc_unit), Bc_limit);
if (Bc < bc_unit) {
if (Br == br_unit) {
break;
}
continue;
}
// Exact VTCM verification (alignment padding may push over budget)
while (Bc >= bc_unit && hmx_fa_compute_vtcm_usage(gqa_factor, DK, DV, Br, Bc, n_threads, can_pipeline) > vtcm_budget) {
Bc -= bc_unit;
}
if (Bc < bc_unit) {
if (Br == br_unit) {
break;
}
continue;
}
const size_t q_blocks = (qo_len + Br - 1) / Br;
const size_t kv_blocks = (kv_len + Bc - 1) / Bc;
const size_t cost = q_blocks * (c_q_fixed + kv_blocks * c_iter_fixed);
const size_t mn = Br * Bc;
if (cost < best_cost || (cost == best_cost && mn > best_mn)) {
best_cost = cost;
best_mn = mn;
best_Br = Br;
best_Bc = Bc;
}
if (Br == br_unit) {
@@ -287,7 +237,7 @@ static inline int hmx_fa_find_chunk_size(size_t * Br_out,
}
}
if (best_Br == 0 || best_Bc == 0) {
if (best_Br == 0) {
return -1;
}
+21 -480
View File
@@ -6,7 +6,6 @@
#include <stdbool.h>
#include "hvx-utils.h"
#include "hmx-utils.h"
#include "hex-fastdiv.h"
// HMX-specific parameters, offsets and inner kernels for Flash Attention
@@ -48,75 +47,22 @@ static const int16_t d_tile_scatter_offsets[64] __attribute__((aligned(128))) =
};
// Inner HMX tile computation kernels
static void hmx_fa_qk_dot_tile(
static inline void hmx_fa_qk_dot_tile(
const __fp16 * row_tiles,
const __fp16 * col_tiles,
__fp16 * out_tile,
size_t n_dot_tiles
) {
if (n_dot_tiles == 2) {
asm volatile(
HMX_LOAD_MPY_F16("%1", "%2", "%0")
HMX_LOAD_MPY_F16("%3", "%4", "%0")
:
: "r"(2047),
"r"(row_tiles + 0 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 0 * HMX_FP16_TILE_N_ELMS),
"r"(row_tiles + 1 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 1 * HMX_FP16_TILE_N_ELMS)
);
} else if (n_dot_tiles == 4) {
asm volatile(
HMX_LOAD_MPY_F16("%1", "%2", "%0")
HMX_LOAD_MPY_F16("%3", "%4", "%0")
HMX_LOAD_MPY_F16("%5", "%6", "%0")
HMX_LOAD_MPY_F16("%7", "%8", "%0")
:
: "r"(2047),
"r"(row_tiles + 0 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 0 * HMX_FP16_TILE_N_ELMS),
"r"(row_tiles + 1 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 1 * HMX_FP16_TILE_N_ELMS),
"r"(row_tiles + 2 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 2 * HMX_FP16_TILE_N_ELMS),
"r"(row_tiles + 3 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 3 * HMX_FP16_TILE_N_ELMS)
);
} else if (n_dot_tiles == 8) {
asm volatile(
HMX_LOAD_MPY_F16("%1", "%2", "%0")
HMX_LOAD_MPY_F16("%3", "%4", "%0")
HMX_LOAD_MPY_F16("%5", "%6", "%0")
HMX_LOAD_MPY_F16("%7", "%8", "%0")
HMX_LOAD_MPY_F16("%9", "%10", "%0")
HMX_LOAD_MPY_F16("%11", "%12", "%0")
HMX_LOAD_MPY_F16("%13", "%14", "%0")
HMX_LOAD_MPY_F16("%15", "%16", "%0")
:
: "r"(2047),
"r"(row_tiles + 0 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 0 * HMX_FP16_TILE_N_ELMS),
"r"(row_tiles + 1 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 1 * HMX_FP16_TILE_N_ELMS),
"r"(row_tiles + 2 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 2 * HMX_FP16_TILE_N_ELMS),
"r"(row_tiles + 3 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 3 * HMX_FP16_TILE_N_ELMS),
"r"(row_tiles + 4 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 4 * HMX_FP16_TILE_N_ELMS),
"r"(row_tiles + 5 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 5 * HMX_FP16_TILE_N_ELMS),
"r"(row_tiles + 6 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 6 * HMX_FP16_TILE_N_ELMS),
"r"(row_tiles + 7 * HMX_FP16_TILE_N_ELMS), "r"(col_tiles + 7 * HMX_FP16_TILE_N_ELMS)
);
} else {
for (size_t k = 0; k < n_dot_tiles; ++k) {
asm volatile(
HMX_LOAD_MPY_F16("%1", "%2", "%0")
:
: "r"(2047), "r"(row_tiles), "r"(col_tiles)
);
row_tiles += HMX_FP16_TILE_N_ELMS;
col_tiles += HMX_FP16_TILE_N_ELMS;
}
for (size_t k = 0; k < n_dot_tiles; ++k) {
Q6_activation_hf_mxmem_RR((unsigned int) row_tiles, 2047);
Q6_weight_hf_mxmem_RR((unsigned int) col_tiles, 2047);
row_tiles += HMX_FP16_TILE_N_ELMS;
col_tiles += HMX_FP16_TILE_N_ELMS;
}
asm volatile(
HMX_STORE_AFTER_F16("%0", "%1")
:
: "r"(out_tile), "r"(0)
: "memory"
);
Q6_mxmem_AR_after_hf(out_tile, 0);
}
static void hmx_fa_o_update_tile(
static inline void hmx_fa_o_update_tile(
const __fp16 * d_diag,
const __fp16 * o_rc,
const __fp16 * p_tile_in,
@@ -124,71 +70,17 @@ static void hmx_fa_o_update_tile(
__fp16 * o_tile_out,
size_t n_col_tiles
) {
asm volatile(
HMX_LOAD_MPY_F16("%1", "%2", "%0")
:
: "r"(2047), "r"(d_diag), "r"(o_rc)
);
if (n_col_tiles == 2) {
asm volatile(
HMX_LOAD_MPY_F16("%1", "%2", "%0")
HMX_LOAD_MPY_F16("%3", "%4", "%0")
:
: "r"(2047),
"r"(p_tile_in + 0 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 0 * HMX_FP16_TILE_N_ELMS),
"r"(p_tile_in + 1 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 1 * HMX_FP16_TILE_N_ELMS)
);
} else if (n_col_tiles == 4) {
asm volatile(
HMX_LOAD_MPY_F16("%1", "%2", "%0")
HMX_LOAD_MPY_F16("%3", "%4", "%0")
HMX_LOAD_MPY_F16("%5", "%6", "%0")
HMX_LOAD_MPY_F16("%7", "%8", "%0")
:
: "r"(2047),
"r"(p_tile_in + 0 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 0 * HMX_FP16_TILE_N_ELMS),
"r"(p_tile_in + 1 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 1 * HMX_FP16_TILE_N_ELMS),
"r"(p_tile_in + 2 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 2 * HMX_FP16_TILE_N_ELMS),
"r"(p_tile_in + 3 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 3 * HMX_FP16_TILE_N_ELMS)
);
} else if (n_col_tiles == 8) {
asm volatile(
HMX_LOAD_MPY_F16("%1", "%2", "%0")
HMX_LOAD_MPY_F16("%3", "%4", "%0")
HMX_LOAD_MPY_F16("%5", "%6", "%0")
HMX_LOAD_MPY_F16("%7", "%8", "%0")
HMX_LOAD_MPY_F16("%9", "%10", "%0")
HMX_LOAD_MPY_F16("%11", "%12", "%0")
HMX_LOAD_MPY_F16("%13", "%14", "%0")
HMX_LOAD_MPY_F16("%15", "%16", "%0")
:
: "r"(2047),
"r"(p_tile_in + 0 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 0 * HMX_FP16_TILE_N_ELMS),
"r"(p_tile_in + 1 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 1 * HMX_FP16_TILE_N_ELMS),
"r"(p_tile_in + 2 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 2 * HMX_FP16_TILE_N_ELMS),
"r"(p_tile_in + 3 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 3 * HMX_FP16_TILE_N_ELMS),
"r"(p_tile_in + 4 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 4 * HMX_FP16_TILE_N_ELMS),
"r"(p_tile_in + 5 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 5 * HMX_FP16_TILE_N_ELMS),
"r"(p_tile_in + 6 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 6 * HMX_FP16_TILE_N_ELMS),
"r"(p_tile_in + 7 * HMX_FP16_TILE_N_ELMS), "r"(v_tile_in + 7 * HMX_FP16_TILE_N_ELMS)
);
} else {
for (size_t k = 0; k < n_col_tiles; ++k) {
asm volatile(
HMX_LOAD_MPY_F16("%1", "%2", "%0")
:
: "r"(2047), "r"(p_tile_in), "r"(v_tile_in)
);
p_tile_in += HMX_FP16_TILE_N_ELMS;
v_tile_in += HMX_FP16_TILE_N_ELMS;
}
Q6_activation_hf_mxmem_RR((unsigned int) d_diag, 2047);
Q6_weight_hf_mxmem_RR((unsigned int) o_rc, 2047);
for (size_t k = 0; k < n_col_tiles; ++k) {
Q6_activation_hf_mxmem_RR((unsigned int) p_tile_in, 2047);
Q6_weight_hf_mxmem_RR((unsigned int) v_tile_in, 2047);
p_tile_in += HMX_FP16_TILE_N_ELMS;
v_tile_in += HMX_FP16_TILE_N_ELMS;
}
asm volatile(
HMX_STORE_AFTER_F16("%0", "%1")
:
: "r"(o_tile_out), "r"(0)
: "memory"
);
Q6_mxmem_AR_after_hf(o_tile_out, 0);
}
static inline void hmx_fa_o_norm_tile(
@@ -196,360 +88,9 @@ static inline void hmx_fa_o_norm_tile(
const __fp16 * o_rc,
__fp16 * o_out
) {
asm volatile(
HMX_LOAD_MPY_F16("%1", "%2", "%0")
:
: "r"(2047), "r"(d_diag), "r"(o_rc)
);
asm volatile(
HMX_STORE_AFTER_F16("%0", "%1")
:
: "r"(o_out), "r"(0)
: "memory"
);
}
static inline void hmx_fa_q_prep_fp32_d2(
__fp16 * vtcm_q_tiles, const uint8_t * temp_q_vtcm,
size_t start, size_t end, size_t g_rows_end,
size_t DK, size_t G, size_t n_rows_q,
const struct fastdiv_values * div_G, bool q_transposed
) {
for (size_t r = start; r < end; r += 2) {
size_t r0 = r / HMX_FP16_TILE_N_ROWS;
size_t r1 = r % HMX_FP16_TILE_N_ROWS;
__fp16 * out_base = vtcm_q_tiles + r0 * HMX_FP16_TILE_N_ROWS * DK;
if (r >= g_rows_end) {
((HVX_Vector *) (out_base + 0 * HMX_FP16_TILE_N_ELMS))[r1 / 2] = Q6_V_vzero();
((HVX_Vector *) (out_base + 1 * HMX_FP16_TILE_N_ELMS))[r1 / 2] = Q6_V_vzero();
continue;
}
const size_t q_idx0 = fastdiv(r + 0, div_G);
const size_t h_idx0 = fastmodulo(r + 0, G, div_G);
const size_t q_idx1 = fastdiv(r + 1, div_G);
const size_t h_idx1 = fastmodulo(r + 1, G, div_G);
const size_t offset0 = q_transposed ? (h_idx0 * n_rows_q + q_idx0) : (q_idx0 * G + h_idx0);
const size_t offset1 = q_transposed ? (h_idx1 * n_rows_q + q_idx1) : (q_idx1 * G + h_idx1);
const HVX_Vector * pv_in0 = (const HVX_Vector *) (temp_q_vtcm + offset0 * DK * sizeof(float));
const HVX_Vector * pv_in1 = (r + 1 < g_rows_end)
? (const HVX_Vector *) (temp_q_vtcm + offset1 * DK * sizeof(float))
: NULL;
{
HVX_Vector v0 = pv_in0[0];
HVX_Vector v1 = pv_in1 ? pv_in1[0] : Q6_V_vzero();
HVX_Vector v_hf = hvx_vec_f32_to_f16_shuff(v0, v1);
((HVX_Vector *) (out_base + 0 * HMX_FP16_TILE_N_ELMS))[r1 / 2] = v_hf;
}
{
HVX_Vector v0 = pv_in0[1];
HVX_Vector v1 = pv_in1 ? pv_in1[1] : Q6_V_vzero();
HVX_Vector v_hf = hvx_vec_f32_to_f16_shuff(v0, v1);
((HVX_Vector *) (out_base + 1 * HMX_FP16_TILE_N_ELMS))[r1 / 2] = v_hf;
}
}
}
static inline void hmx_fa_q_prep_fp32_d4(
__fp16 * vtcm_q_tiles, const uint8_t * temp_q_vtcm,
size_t start, size_t end, size_t g_rows_end,
size_t DK, size_t G, size_t n_rows_q,
const struct fastdiv_values * div_G, bool q_transposed
) {
for (size_t r = start; r < end; r += 2) {
size_t r0 = r / HMX_FP16_TILE_N_ROWS;
size_t r1 = r % HMX_FP16_TILE_N_ROWS;
__fp16 * out_base = vtcm_q_tiles + r0 * HMX_FP16_TILE_N_ROWS * DK;
if (r >= g_rows_end) {
for (uint32_t d = 0; d < 4; ++d) {
((HVX_Vector *) (out_base + d * HMX_FP16_TILE_N_ELMS))[r1 / 2] = Q6_V_vzero();
}
continue;
}
const size_t q_idx0 = fastdiv(r + 0, div_G);
const size_t h_idx0 = fastmodulo(r + 0, G, div_G);
const size_t q_idx1 = fastdiv(r + 1, div_G);
const size_t h_idx1 = fastmodulo(r + 1, G, div_G);
const size_t offset0 = q_transposed ? (h_idx0 * n_rows_q + q_idx0) : (q_idx0 * G + h_idx0);
const size_t offset1 = q_transposed ? (h_idx1 * n_rows_q + q_idx1) : (q_idx1 * G + h_idx1);
const HVX_Vector * pv_in0 = (const HVX_Vector *) (temp_q_vtcm + offset0 * DK * sizeof(float));
const HVX_Vector * pv_in1 = (r + 1 < g_rows_end)
? (const HVX_Vector *) (temp_q_vtcm + offset1 * DK * sizeof(float))
: NULL;
for (uint32_t d = 0; d < 4; ++d) {
HVX_Vector v0 = pv_in0[d];
HVX_Vector v1 = pv_in1 ? pv_in1[d] : Q6_V_vzero();
HVX_Vector v_hf = hvx_vec_f32_to_f16_shuff(v0, v1);
((HVX_Vector *) (out_base + d * HMX_FP16_TILE_N_ELMS))[r1 / 2] = v_hf;
}
}
}
static inline void hmx_fa_q_prep_fp32(
__fp16 * vtcm_q_tiles, const uint8_t * temp_q_vtcm,
size_t start, size_t end, size_t g_rows_end,
size_t DK, size_t G, size_t n_rows_q,
const struct fastdiv_values * div_G, uint32_t d_limit, bool q_transposed
) {
for (size_t r = start; r < end; r += 2) {
size_t r0 = r / HMX_FP16_TILE_N_ROWS;
size_t r1 = r % HMX_FP16_TILE_N_ROWS;
__fp16 * out_base = vtcm_q_tiles + r0 * HMX_FP16_TILE_N_ROWS * DK;
if (r >= g_rows_end) {
for (uint32_t d = 0; d < d_limit; ++d) {
((HVX_Vector *) (out_base + d * HMX_FP16_TILE_N_ELMS))[r1 / 2] = Q6_V_vzero();
}
continue;
}
const size_t q_idx0 = fastdiv(r + 0, div_G);
const size_t h_idx0 = fastmodulo(r + 0, G, div_G);
const size_t q_idx1 = fastdiv(r + 1, div_G);
const size_t h_idx1 = fastmodulo(r + 1, G, div_G);
const size_t offset0 = q_transposed ? (h_idx0 * n_rows_q + q_idx0) : (q_idx0 * G + h_idx0);
const size_t offset1 = q_transposed ? (h_idx1 * n_rows_q + q_idx1) : (q_idx1 * G + h_idx1);
const HVX_Vector * pv_in0 = (const HVX_Vector *) (temp_q_vtcm + offset0 * DK * sizeof(float));
const HVX_Vector * pv_in1 = (r + 1 < g_rows_end)
? (const HVX_Vector *) (temp_q_vtcm + offset1 * DK * sizeof(float))
: NULL;
for (uint32_t d = 0; d < d_limit; ++d) {
HVX_Vector v0 = pv_in0[d];
HVX_Vector v1 = pv_in1 ? pv_in1[d] : Q6_V_vzero();
HVX_Vector v_hf = hvx_vec_f32_to_f16_shuff(v0, v1);
HVX_Vector * out_tile = (HVX_Vector *) (out_base + d * HMX_FP16_TILE_N_ELMS);
out_tile[r1 / 2] = v_hf;
}
}
}
static inline void hmx_fa_q_prep_fp16_d1(
__fp16 * vtcm_q_tiles, const uint8_t * temp_q_vtcm,
size_t start, size_t end, size_t g_rows_end,
size_t DK, size_t G, size_t n_rows_q,
const struct fastdiv_values * div_G, bool q_transposed
) {
for (size_t r = start; r < end; r += 2) {
size_t r0 = r / HMX_FP16_TILE_N_ROWS;
size_t r1 = r % HMX_FP16_TILE_N_ROWS;
__fp16 * out_base = vtcm_q_tiles + r0 * HMX_FP16_TILE_N_ROWS * DK;
if (r >= g_rows_end) {
__fp16 * out_dtile = out_base + 0 * HMX_FP16_TILE_N_ELMS * 2;
HVX_Vector * pv_out0 = ((HVX_Vector *) out_dtile) + r1 / 2;
HVX_Vector * pv_out1 = pv_out0 + 16;
*pv_out0 = Q6_V_vzero();
*pv_out1 = Q6_V_vzero();
continue;
}
const size_t q_idx0 = fastdiv(r + 0, div_G);
const size_t h_idx0 = fastmodulo(r + 0, G, div_G);
const size_t q_idx1 = fastdiv(r + 1, div_G);
const size_t h_idx1 = fastmodulo(r + 1, G, div_G);
const size_t offset0 = q_transposed ? (h_idx0 * n_rows_q + q_idx0) : (q_idx0 * G + h_idx0);
const size_t offset1 = q_transposed ? (h_idx1 * n_rows_q + q_idx1) : (q_idx1 * G + h_idx1);
const HVX_Vector * pv_in0 = (const HVX_Vector *) (temp_q_vtcm + offset0 * DK * sizeof(__fp16));
const HVX_Vector * pv_in1 = (r + 1 < g_rows_end)
? (const HVX_Vector *) (temp_q_vtcm + offset1 * DK * sizeof(__fp16))
: NULL;
HVX_Vector v0 = pv_in0[0];
HVX_Vector v1 = pv_in1 ? pv_in1[0] : Q6_V_vzero();
HVX_VectorPair vp = Q6_W_vshuff_VVR(v1, v0, -2);
__fp16 * out_dtile = out_base + 0 * HMX_FP16_TILE_N_ELMS * 2;
HVX_Vector * pv_out0 = ((HVX_Vector *) out_dtile) + r1 / 2;
HVX_Vector * pv_out1 = pv_out0 + 16;
*pv_out0 = Q6_V_lo_W(vp);
*pv_out1 = Q6_V_hi_W(vp);
}
}
static inline void hmx_fa_q_prep_fp16_d2(
__fp16 * vtcm_q_tiles, const uint8_t * temp_q_vtcm,
size_t start, size_t end, size_t g_rows_end,
size_t DK, size_t G, size_t n_rows_q,
const struct fastdiv_values * div_G, bool q_transposed
) {
for (size_t r = start; r < end; r += 2) {
size_t r0 = r / HMX_FP16_TILE_N_ROWS;
size_t r1 = r % HMX_FP16_TILE_N_ROWS;
__fp16 * out_base = vtcm_q_tiles + r0 * HMX_FP16_TILE_N_ROWS * DK;
if (r >= g_rows_end) {
for (uint32_t d = 0; d < 2; ++d) {
__fp16 * out_dtile = out_base + d * HMX_FP16_TILE_N_ELMS * 2;
HVX_Vector * pv_out0 = ((HVX_Vector *) out_dtile) + r1 / 2;
HVX_Vector * pv_out1 = pv_out0 + 16;
*pv_out0 = Q6_V_vzero();
*pv_out1 = Q6_V_vzero();
}
continue;
}
const size_t q_idx0 = fastdiv(r + 0, div_G);
const size_t h_idx0 = fastmodulo(r + 0, G, div_G);
const size_t q_idx1 = fastdiv(r + 1, div_G);
const size_t h_idx1 = fastmodulo(r + 1, G, div_G);
const size_t offset0 = q_transposed ? (h_idx0 * n_rows_q + q_idx0) : (q_idx0 * G + h_idx0);
const size_t offset1 = q_transposed ? (h_idx1 * n_rows_q + q_idx1) : (q_idx1 * G + h_idx1);
const HVX_Vector * pv_in0 = (const HVX_Vector *) (temp_q_vtcm + offset0 * DK * sizeof(__fp16));
const HVX_Vector * pv_in1 = (r + 1 < g_rows_end)
? (const HVX_Vector *) (temp_q_vtcm + offset1 * DK * sizeof(__fp16))
: NULL;
{
HVX_Vector v0 = pv_in0[0];
HVX_Vector v1 = pv_in1 ? pv_in1[0] : Q6_V_vzero();
HVX_VectorPair vp = Q6_W_vshuff_VVR(v1, v0, -2);
__fp16 * out_dtile = out_base + 0 * HMX_FP16_TILE_N_ELMS * 2;
HVX_Vector * pv_out0 = ((HVX_Vector *) out_dtile) + r1 / 2;
HVX_Vector * pv_out1 = pv_out0 + 16;
*pv_out0 = Q6_V_lo_W(vp);
*pv_out1 = Q6_V_hi_W(vp);
}
{
HVX_Vector v0 = pv_in0[1];
HVX_Vector v1 = pv_in1 ? pv_in1[1] : Q6_V_vzero();
HVX_VectorPair vp = Q6_W_vshuff_VVR(v1, v0, -2);
__fp16 * out_dtile = out_base + 1 * HMX_FP16_TILE_N_ELMS * 2;
HVX_Vector * pv_out0 = ((HVX_Vector *) out_dtile) + r1 / 2;
HVX_Vector * pv_out1 = pv_out0 + 16;
*pv_out0 = Q6_V_lo_W(vp);
*pv_out1 = Q6_V_hi_W(vp);
}
}
}
static inline void hmx_fa_q_prep_fp16(
__fp16 * vtcm_q_tiles, const uint8_t * temp_q_vtcm,
size_t start, size_t end, size_t g_rows_end,
size_t DK, size_t G, size_t n_rows_q,
const struct fastdiv_values * div_G, uint32_t d_limit, bool q_transposed
) {
for (size_t r = start; r < end; r += 2) {
size_t r0 = r / HMX_FP16_TILE_N_ROWS;
size_t r1 = r % HMX_FP16_TILE_N_ROWS;
__fp16 * out_base = vtcm_q_tiles + r0 * HMX_FP16_TILE_N_ROWS * DK;
if (r >= g_rows_end) {
for (uint32_t d = 0; d < d_limit; ++d) {
__fp16 * out_dtile = out_base + d * HMX_FP16_TILE_N_ELMS * 2;
HVX_Vector * pv_out0 = ((HVX_Vector *) out_dtile) + r1 / 2;
HVX_Vector * pv_out1 = pv_out0 + 16;
*pv_out0 = Q6_V_vzero();
*pv_out1 = Q6_V_vzero();
}
continue;
}
const size_t q_idx0 = fastdiv(r + 0, div_G);
const size_t h_idx0 = fastmodulo(r + 0, G, div_G);
const size_t q_idx1 = fastdiv(r + 1, div_G);
const size_t h_idx1 = fastmodulo(r + 1, G, div_G);
const size_t offset0 = q_transposed ? (h_idx0 * n_rows_q + q_idx0) : (q_idx0 * G + h_idx0);
const size_t offset1 = q_transposed ? (h_idx1 * n_rows_q + q_idx1) : (q_idx1 * G + h_idx1);
const HVX_Vector * pv_in0 = (const HVX_Vector *) (temp_q_vtcm + offset0 * DK * sizeof(__fp16));
const HVX_Vector * pv_in1 = (r + 1 < g_rows_end)
? (const HVX_Vector *) (temp_q_vtcm + offset1 * DK * sizeof(__fp16))
: NULL;
for (uint32_t d = 0; d < d_limit; ++d) {
HVX_Vector v0 = pv_in0[d];
HVX_Vector v1 = pv_in1 ? pv_in1[d] : Q6_V_vzero();
HVX_VectorPair vp = Q6_W_vshuff_VVR(v1, v0, -2);
__fp16 * out_dtile = out_base + d * HMX_FP16_TILE_N_ELMS * 2;
HVX_Vector * pv_out0 = ((HVX_Vector *) out_dtile) + r1 / 2;
HVX_Vector * pv_out1 = pv_out0 + 16;
*pv_out0 = Q6_V_lo_W(vp);
*pv_out1 = Q6_V_hi_W(vp);
}
}
}
static inline void hmx_fa_q_prep_fallback(
__fp16 * vtcm_q_tiles, uintptr_t q_data,
size_t q_nb1, size_t q_nb2, size_t q_nb3,
uint32_t q_start, uint32_t kv_head, uint32_t ib3,
size_t start, size_t end, size_t n_rows_g,
size_t G, size_t DK, bool is_q_fp32,
const struct fastdiv_values * div_G
) {
for (size_t r = start; r < end; r += 2) {
const size_t q_idx0 = fastdiv(r + 0, div_G);
const size_t h_idx0 = fastmodulo(r + 0, G, div_G);
const size_t q_idx1 = fastdiv(r + 1, div_G);
const size_t h_idx1 = fastmodulo(r + 1, G, div_G);
const uint8_t * q_ptr0 = (r + 0 < n_rows_g) ? ((const uint8_t *) q_data + (q_start + q_idx0) * q_nb1 +
(kv_head * G + h_idx0) * q_nb2 + ib3 * q_nb3) :
NULL;
const uint8_t * q_ptr1 = (r + 1 < n_rows_g) ? ((const uint8_t *) q_data + (q_start + q_idx1) * q_nb1 +
(kv_head * G + h_idx1) * q_nb2 + ib3 * q_nb3) :
NULL;
size_t r0 = r / HMX_FP16_TILE_N_ROWS;
size_t r1 = r % HMX_FP16_TILE_N_ROWS;
__fp16 * out_base = vtcm_q_tiles + r0 * HMX_FP16_TILE_N_ROWS * DK;
if (is_q_fp32) {
const HVX_UVector * pv_in0 = q_ptr0 ? (const HVX_UVector *) q_ptr0 : NULL;
const HVX_UVector * pv_in1 = q_ptr1 ? (const HVX_UVector *) q_ptr1 : NULL;
for (uint32_t d = 0; d < DK / 32; ++d) {
HVX_Vector v0 = pv_in0 ? pv_in0[d] : Q6_V_vzero();
HVX_Vector v1 = pv_in1 ? pv_in1[d] : Q6_V_vzero();
HVX_Vector v_hf = hvx_vec_f32_to_f16_shuff(v0, v1);
HVX_Vector * out_tile = (HVX_Vector *) (out_base + d * HMX_FP16_TILE_N_ELMS);
out_tile[r1 / 2] = v_hf;
}
} else {
const HVX_UVector * pv_in0 = q_ptr0 ? (const HVX_UVector *) q_ptr0 : NULL;
const HVX_UVector * pv_in1 = q_ptr1 ? (const HVX_UVector *) q_ptr1 : NULL;
for (uint32_t d = 0; d < DK / 64; ++d) {
HVX_Vector v0 = pv_in0 ? pv_in0[d] : Q6_V_vzero();
HVX_Vector v1 = pv_in1 ? pv_in1[d] : Q6_V_vzero();
HVX_VectorPair vp = Q6_W_vshuff_VVR(v1, v0, -2);
__fp16 * out_dtile = out_base + d * HMX_FP16_TILE_N_ELMS * 2;
HVX_Vector * pv_out0 = ((HVX_Vector *) out_dtile) + r1 / 2;
HVX_Vector * pv_out1 = pv_out0 + 16;
*pv_out0 = Q6_V_lo_W(vp);
*pv_out1 = Q6_V_hi_W(vp);
}
}
}
Q6_activation_hf_mxmem_RR((unsigned int) d_diag, 2047);
Q6_weight_hf_mxmem_RR((unsigned int) o_rc, 2047);
Q6_mxmem_AR_after_hf(o_out, 0);
}
#endif /* HMX_FA_KERNELS_H */
+207 -169
View File
@@ -506,8 +506,7 @@ static void dequantize_tiled_weight_to_fp16_task_q8_0(
}
}
static __attribute__((noinline))
void convert_f16_weight_to_fp16_tiles_task(
static void convert_f16_weight_to_fp16_tiles_task(
const tiled_dequantize_state_t *state,
uint32_t start_tile, uint32_t end_tile) {
@@ -544,13 +543,17 @@ void convert_f16_weight_to_fp16_tiles_task(
Q6_vscatter_QRMVwV(q_mask64, (size_t)tile_base, HTP_MM_HMX_TILE_SIZE - 1, v_off, v1);
v_off = Q6_Vw_vadd_VwVw(v_off, v_scat_step);
}
(void) *(volatile HVX_Vector *)(tile_base);
}
++t; ++kt;
}
if (start_tile < end_tile) {
(void) *(volatile HVX_Vector *)(state->dst + (end_tile - 1) * HTP_MM_HMX_TILE_N_ELMS);
}
}
static __attribute__((noinline))
void quantize_f32_weight_to_fp16_tiles_task(
static void quantize_f32_weight_to_fp16_tiles_task(
const tiled_dequantize_state_t *state,
uint32_t start_tile, uint32_t end_tile) {
@@ -591,178 +594,120 @@ void quantize_f32_weight_to_fp16_tiles_task(
Q6_vscatter_QRMVwV(q_mask64, (size_t)tile_base, HTP_MM_HMX_TILE_SIZE - 1, v_off, v_out_hi);
v_off = Q6_Vw_vadd_VwVw(v_off, v_scat_step);
}
(void) *(volatile HVX_Vector *)(tile_base);
}
++t; ++kt;
}
if (start_tile < end_tile) {
(void) *(volatile HVX_Vector *)(state->dst + (end_tile - 1) * HTP_MM_HMX_TILE_N_ELMS);
}
}
// --- End tiled dequantizers ---
// dot-chunk functions require external HMX lock
static void core_dot_chunk_fp16_short(__fp16 *restrict output, const __fp16 *restrict activation,
const __fp16 *restrict weight, const __fp16 *restrict scales,
// requires external HMX lock
static void core_dot_chunk_fp16(__fp16 *restrict output, const __fp16 *restrict activation, const __fp16 *restrict weight, const __fp16 *restrict scales,
uint32_t n_row_tiles, uint32_t n_col_tiles, uint32_t n_dot_tiles) {
__builtin_assume(n_row_tiles > 0);
__builtin_assume(n_col_tiles > 0);
__builtin_assume(n_dot_tiles > 0);
__builtin_assume(n_dot_tiles <= 32);
asm volatile(HMX_SET_BIAS("%0") :: "r"((unsigned int)scales));
const size_t dot_stride = n_dot_tiles * HTP_MM_HMX_TILE_N_ELMS;
const uint32_t range = 2048u * n_dot_tiles - 1;
Q6_bias_mxmem2_A((void *)scales);
for (uint32_t r = 0; r < n_row_tiles; ++r) {
const __fp16 *row_base = activation + r * dot_stride;
const __fp16 *col_base = weight;
__fp16 *out_tile = output + r * n_col_tiles * HTP_MM_HMX_TILE_N_ELMS;
for (size_t c = 0; c < n_col_tiles; ++c) {
asm volatile(HMX_CLRACC_F16());
asm volatile(HMX_LOAD_MPY_DEEP_F16("%1", "%2", "%0") : : "r"(range), "r"(row_base), "r"(col_base));
asm volatile(HMX_STORE_AFTER_F16("%0", "%1") : : "r"(out_tile), "r"(0) : "memory");
col_base += dot_stride;
out_tile += HTP_MM_HMX_TILE_N_ELMS;
Q6_mxclracc_hf();
const __fp16 *row_tiles = activation + r * n_dot_tiles * HTP_MM_HMX_TILE_N_ELMS;
const __fp16 *col_tiles = weight + c * n_dot_tiles * HTP_MM_HMX_TILE_N_ELMS;
for (uint32_t k = 0, k_block; k < n_dot_tiles; k += k_block) {
k_block = hex_smin(n_dot_tiles - k, 32);
const uint32_t range = 2048u * (uint32_t)k_block - 1;
Q6_activation_hf_mxmem_RR_deep((unsigned int)row_tiles, range);
Q6_weight_hf_mxmem_RR((unsigned int)col_tiles, range);
row_tiles += k_block * HTP_MM_HMX_TILE_N_ELMS;
col_tiles += k_block * HTP_MM_HMX_TILE_N_ELMS;
}
__fp16 *out_tile = output + (r * n_col_tiles + c) * HTP_MM_HMX_TILE_N_ELMS;
Q6_mxmem_AR_after_hf(out_tile, 0);
}
}
}
static void core_dot_chunk_fp16(__fp16 *restrict output, const __fp16 *restrict activation,
const __fp16 *restrict weight, const __fp16 *restrict scales,
uint32_t n_row_tiles, uint32_t n_col_tiles, uint32_t n_dot_tiles) {
if (n_dot_tiles <= 32) {
core_dot_chunk_fp16_short(output, activation, weight, scales, n_row_tiles, n_col_tiles, n_dot_tiles);
return;
}
__builtin_assume(n_row_tiles > 0);
__builtin_assume(n_col_tiles > 0);
__builtin_assume(n_dot_tiles > 32);
asm volatile(HMX_SET_BIAS("%0") :: "r"((unsigned int)scales));
const size_t dot_stride = n_dot_tiles * HTP_MM_HMX_TILE_N_ELMS;
for (uint32_t r = 0; r < n_row_tiles; ++r) {
const __fp16 *row_base = activation + r * dot_stride;
const __fp16 *col_base = weight;
__fp16 *out_tile = output + r * n_col_tiles * HTP_MM_HMX_TILE_N_ELMS;
for (size_t c = 0; c < n_col_tiles; ++c) {
const __fp16 *row_tiles = row_base;
const __fp16 *col_tiles = col_base;
asm volatile(HMX_CLRACC_F16());
const uint32_t n_loops = n_dot_tiles / 32;
const uint32_t rem = n_dot_tiles % 32;
for (uint32_t l = 0; l < n_loops; ++l) {
asm volatile(HMX_LOAD_MPY_DEEP_F16("%1", "%2", "%0") : : "r"(65535), "r"(row_tiles), "r"(col_tiles));
row_tiles += 32 * HTP_MM_HMX_TILE_N_ELMS;
col_tiles += 32 * HTP_MM_HMX_TILE_N_ELMS;
}
if (rem > 0) {
const uint32_t range = 2048u * rem - 1;
asm volatile(HMX_LOAD_MPY_DEEP_F16("%1", "%2", "%0") : : "r"(range), "r"(row_tiles), "r"(col_tiles));
}
asm volatile(HMX_STORE_AFTER_F16("%0", "%1") : : "r"(out_tile), "r"(0) : "memory");
col_base += dot_stride;
out_tile += HTP_MM_HMX_TILE_N_ELMS;
}
}
}
static void core_mma_chunk_fp16_short(__fp16 *restrict c, const __fp16 *restrict a, const __fp16 *restrict b,
// C += AB
static void core_mma_chunk_fp16(__fp16 *restrict c, const __fp16 *restrict a, const __fp16 *restrict b,
const __fp16 *restrict col_scales, const __fp16 *restrict eye_tile,
uint32_t n_row_tiles, uint32_t n_col_tiles, uint32_t n_dot_tiles, bool zero_init) {
__builtin_assume(n_row_tiles > 0);
__builtin_assume(n_col_tiles > 0);
__builtin_assume(n_dot_tiles > 0);
__builtin_assume(n_dot_tiles <= 32);
asm volatile(HMX_SET_BIAS("%0") :: "r"((unsigned int)col_scales));
Q6_bias_mxmem2_A((void *)col_scales);
const size_t dot_tile_stride = n_dot_tiles * HTP_MM_HMX_TILE_N_ELMS;
const uint32_t range = 2048u * n_dot_tiles - 1;
for (size_t i = 0; i < n_row_tiles; ++i) {
const __fp16 *row_base = a + i * dot_tile_stride;
__fp16 *res_base = c + i * n_col_tiles * HTP_MM_HMX_TILE_N_ELMS;
const __fp16 *col_base = b;
__fp16 *accum_tile = res_base;
for (size_t j = 0; j < n_col_tiles; ++j) {
asm volatile(HMX_CLRACC_F16());
Q6_mxclracc_hf();
const __fp16 *col_tiles = b + j * dot_tile_stride;
const __fp16 *row_tiles = row_base;
__fp16 *accum_tile = res_base + j * HTP_MM_HMX_TILE_N_ELMS;
if (!zero_init) {
asm volatile(HMX_LOAD_MPY_F16("%1", "%2", "%0") : : "r"(2047), "r"(accum_tile), "r"(eye_tile));
Q6_activation_hf_mxmem_RR((unsigned int)accum_tile, 2047);
Q6_weight_hf_mxmem_RR((unsigned int)eye_tile, 2047);
}
asm volatile(HMX_LOAD_MPY_DEEP_F16("%1", "%2", "%0") : : "r"(range), "r"(row_base), "r"(col_base));
for (uint32_t k = 0, k_block; k < n_dot_tiles; k += k_block) {
k_block = hex_smin(n_dot_tiles - k, 32);
const uint32_t range = 2048u * k_block - 1;
Q6_activation_hf_mxmem_RR_deep((unsigned int)row_tiles, range);
Q6_weight_hf_mxmem_RR((unsigned int)col_tiles, range);
row_tiles += k_block * HTP_MM_HMX_TILE_N_ELMS;
col_tiles += k_block * HTP_MM_HMX_TILE_N_ELMS;
}
asm volatile(HMX_STORE_AFTER_F16("%0", "%1") : : "r"(accum_tile), "r"(0) : "memory");
col_base += dot_tile_stride;
accum_tile += HTP_MM_HMX_TILE_N_ELMS;
Q6_mxmem_AR_after_hf(accum_tile, 0);
}
}
}
static void core_mma_chunk_fp16(__fp16 *restrict c, const __fp16 *restrict a, const __fp16 *restrict b,
const __fp16 *restrict col_scales, const __fp16 *restrict eye_tile,
uint32_t n_row_tiles, uint32_t n_col_tiles, uint32_t n_dot_tiles, bool zero_init) {
if (n_dot_tiles <= 32) {
core_mma_chunk_fp16_short(c, a, b, col_scales, eye_tile, n_row_tiles, n_col_tiles, n_dot_tiles, zero_init);
return;
}
__builtin_assume(n_row_tiles > 0);
__builtin_assume(n_col_tiles > 0);
__builtin_assume(n_dot_tiles > 32);
// --- Async HMX matmul job (for pipeline overlap) ---
asm volatile(HMX_SET_BIAS("%0") :: "r"((unsigned int)col_scales));
typedef struct {
__fp16 * output;
const __fp16 * activation;
const __fp16 * weight;
const __fp16 * scales;
uint32_t n_row_tiles;
uint32_t n_col_tiles;
uint32_t n_dot_tiles;
} hmx_matmul_job_t;
const size_t dot_tile_stride = n_dot_tiles * HTP_MM_HMX_TILE_N_ELMS;
static void hmx_matmul_worker_fn(void * data) {
hmx_matmul_job_t * job = (hmx_matmul_job_t *) data;
FARF(HIGH, "hmx-mm-job: n_row_tiles %u n_col_tiles %u n_dot_tiles %u", job->n_row_tiles, job->n_col_tiles, job->n_dot_tiles);
core_dot_chunk_fp16(job->output, job->activation, job->weight, job->scales, job->n_row_tiles, job->n_col_tiles, job->n_dot_tiles);
}
for (size_t i = 0; i < n_row_tiles; ++i) {
const __fp16 *row_base = a + i * dot_tile_stride;
__fp16 *res_base = c + i * n_col_tiles * HTP_MM_HMX_TILE_N_ELMS;
const __fp16 *col_base = b;
__fp16 *accum_tile = res_base;
for (size_t j = 0; j < n_col_tiles; ++j) {
const __fp16 *col_tiles = col_base;
const __fp16 *row_tiles = row_base;
asm volatile(HMX_CLRACC_F16());
if (!zero_init) {
asm volatile(HMX_LOAD_MPY_F16("%1", "%2", "%0") : : "r"(2047), "r"(accum_tile), "r"(eye_tile));
}
const uint32_t n_loops = n_dot_tiles / 32;
const uint32_t rem = n_dot_tiles % 32;
for (uint32_t l = 0; l < n_loops; ++l) {
asm volatile(HMX_LOAD_MPY_DEEP_F16("%1", "%2", "%0") : : "r"(65535), "r"(row_tiles), "r"(col_tiles));
row_tiles += 32 * HTP_MM_HMX_TILE_N_ELMS;
col_tiles += 32 * HTP_MM_HMX_TILE_N_ELMS;
}
if (rem > 0) {
const uint32_t range = 2048u * rem - 1;
asm volatile(HMX_LOAD_MPY_DEEP_F16("%1", "%2", "%0") : : "r"(range), "r"(row_tiles), "r"(col_tiles));
}
asm volatile(HMX_STORE_AFTER_F16("%0", "%1") : : "r"(accum_tile), "r"(0) : "memory");
col_base += dot_tile_stride;
accum_tile += HTP_MM_HMX_TILE_N_ELMS;
}
}
static inline void hmx_matmul_job_init(hmx_matmul_job_t * job,
__fp16 * output,
const __fp16 * activation,
const __fp16 * weight,
const __fp16 * scales,
uint32_t n_row_tiles,
uint32_t n_col_tiles,
uint32_t n_dot_tiles) {
job->output = output;
job->activation = activation;
job->weight = weight;
job->scales = scales;
job->n_row_tiles = n_row_tiles;
job->n_col_tiles = n_col_tiles;
job->n_dot_tiles = n_dot_tiles;
}
// output : fp16 -> f32p
@@ -956,55 +901,148 @@ static void transfer_activation_chunk_fp32_to_fp16(__fp16 *restrict vtcm_dst, co
}
}
static void transfer_activation_row_pair_fp32_to_fp16(
typedef struct {
__fp16 *dst;
const float *src;
uint32_t n_tasks;
uint32_t n_tot_chunks;
uint32_t n_chunks_per_task;
uint32_t k_block;
uint32_t k_stride;
uint32_t k_valid;
struct htp_thread_trace * traces;
struct htp_context * ctx;
float * vtcm_f32_act;
} activation_transfer_task_state_t;
static void transfer_activation_chunk_fp32_to_fp16_dma_pipelined(
dma_queue *dma_q,
__fp16 *restrict vtcm_dst,
const float *restrict row0,
const float *restrict row1,
uint32_t r,
const float *restrict src,
uint32_t n_rows,
uint32_t k_block,
uint32_t k_stride,
uint32_t k_valid,
bool row0_valid,
bool row1_valid) {
float *thread_f32_act) {
uint32_t r0 = r / HTP_MM_HMX_TILE_N_ROWS; // tile row index
uint32_t r1 = r % HTP_MM_HMX_TILE_N_ROWS; // intra-tile row idx
const uint32_t R = HTP_MM_DMA_ACT_ROWS_PER_STEP;
const uint32_t n_rows_padded = hex_align_up(n_rows, HTP_MM_HMX_TILE_N_ROWS);
uint32_t c = 0;
for (; c + 32 <= k_valid; c += 32) {
HVX_Vector v0 = Q6_V_vzero();
HVX_Vector v1 = Q6_V_vzero();
if (row0_valid) v0 = *(const HVX_Vector *)(row0 + c);
if (row1_valid) v1 = *(const HVX_Vector *)(row1 + c);
const uint32_t n_steps = n_rows_padded / R;
HVX_Vector v_out = hvx_vec_f32_to_f16_shuff(v0, v1);
uint32_t c0 = c / HTP_MM_HMX_TILE_N_COLS; // tile column index
uint32_t tile_idx = r0 * (k_block / HTP_MM_HMX_TILE_N_COLS) + c0;
HVX_Vector *tile = (HVX_Vector *) (vtcm_dst + tile_idx * HTP_MM_HMX_TILE_N_ELMS);
tile[r1 / 2] = v_out;
// pre-fetch step 0
if (n_steps > 0 && n_rows > 0) {
uint32_t nrows_to_fetch = hex_smin(n_rows, R);
dma_queue_push(dma_q, dma_make_ptr(thread_f32_act, src),
k_block * sizeof(float), k_stride * sizeof(float), k_valid * sizeof(float), nrows_to_fetch);
}
if (c < k_block) {
HVX_Vector v0 = Q6_V_vzero();
HVX_Vector v1 = Q6_V_vzero();
if (row0_valid) v0 = *(const HVX_Vector *)(row0 + c);
if (row1_valid) v1 = *(const HVX_Vector *)(row1 + c);
uint32_t rem = k_valid - c;
HVX_VectorPred mask = Q6_Q_vsetq2_R(rem > 0 ? rem * sizeof(float) : 0);
v0 = Q6_V_vmux_QVV(mask, v0, Q6_V_vzero());
v1 = Q6_V_vmux_QVV(mask, v1, Q6_V_vzero());
for (uint32_t s = 0; s < n_steps; ++s) {
uint32_t r = R * s;
float *curr_buf = thread_f32_act + (s % 2) * R * k_block;
HVX_Vector v_out = hvx_vec_f32_to_f16_shuff(v0, v1);
if (r < n_rows) {
dma_queue_pop(dma_q);
}
uint32_t c0 = c / HTP_MM_HMX_TILE_N_COLS; // tile column index
uint32_t tile_idx = r0 * (k_block / HTP_MM_HMX_TILE_N_COLS) + c0;
uint32_t next_s = s + 1;
uint32_t next_r = R * next_s;
if (next_r < n_rows) {
uint32_t nrows_to_fetch = hex_smin(n_rows - next_r, R);
const float *next_src = src + next_r * k_stride;
float *next_buf = thread_f32_act + (next_s % 2) * R * k_block;
dma_queue_push(dma_q, dma_make_ptr(next_buf, next_src),
k_block * sizeof(float), k_stride * sizeof(float), k_valid * sizeof(float), nrows_to_fetch);
}
HVX_Vector *tile = (HVX_Vector *) (vtcm_dst + tile_idx * HTP_MM_HMX_TILE_N_ELMS);
tile[r1 / 2] = v_out;
#pragma unroll
for (uint32_t i = 0; i < HTP_MM_DMA_ACT_ROWS_PER_STEP; i += 2) {
uint32_t curr_r = r + i;
const bool row0_valid = (curr_r < n_rows);
const bool row1_valid = (curr_r + 1) < n_rows;
const float *ptr_in0 = curr_buf + i * k_block;
const float *ptr_in1 = curr_buf + (i + 1) * k_block;
uint32_t c = 0;
for (; c + 32 <= k_valid; c += 32) {
HVX_Vector v0 = Q6_V_vzero();
HVX_Vector v1 = Q6_V_vzero();
if (row0_valid) v0 = *(const HVX_Vector *)(ptr_in0 + c);
if (row1_valid) v1 = *(const HVX_Vector *)(ptr_in1 + c);
HVX_Vector v_out = hvx_vec_f32_to_f16_shuff(v0, v1);
uint32_t r0 = curr_r / HTP_MM_HMX_TILE_N_ROWS; // tile row index
uint32_t r1 = curr_r % HTP_MM_HMX_TILE_N_ROWS; // intra-tile row idx
uint32_t c0 = c / HTP_MM_HMX_TILE_N_COLS; // tile column index
uint32_t tile_idx = r0 * (k_block / HTP_MM_HMX_TILE_N_COLS) + c0;
HVX_Vector *tile = (HVX_Vector *) (vtcm_dst + tile_idx * HTP_MM_HMX_TILE_N_ELMS);
tile[r1 / 2] = v_out;
}
if (c < k_block) {
HVX_Vector v0 = Q6_V_vzero();
HVX_Vector v1 = Q6_V_vzero();
if (row0_valid) v0 = *(const HVX_Vector *)(ptr_in0 + c);
if (row1_valid) v1 = *(const HVX_Vector *)(ptr_in1 + c);
uint32_t rem = k_valid - c;
HVX_VectorPred mask = Q6_Q_vsetq2_R(rem > 0 ? rem * sizeof(float) : 0);
v0 = Q6_V_vmux_QVV(mask, v0, Q6_V_vzero());
v1 = Q6_V_vmux_QVV(mask, v1, Q6_V_vzero());
HVX_Vector v_out = hvx_vec_f32_to_f16_shuff(v0, v1);
uint32_t r0 = curr_r / HTP_MM_HMX_TILE_N_ROWS; // tile row index
uint32_t r1 = curr_r % HTP_MM_HMX_TILE_N_ROWS; // intra-tile row idx
uint32_t c0 = c / HTP_MM_HMX_TILE_N_COLS; // tile column index
uint32_t tile_idx = r0 * (k_block / HTP_MM_HMX_TILE_N_COLS) + c0;
HVX_Vector *tile = (HVX_Vector *) (vtcm_dst + tile_idx * HTP_MM_HMX_TILE_N_ELMS);
tile[r1 / 2] = v_out;
}
}
}
}
typedef struct {
const struct mmid_row_mapping *matrix_rows;
__fp16 *dst;
const float *src;
uint32_t n_tasks;
uint32_t n_tot_chunks;
uint32_t n_chunks_per_task;
uint32_t k_block;
uint32_t cur_a;
uint32_t mapping_stride;
uint32_t ne11;
struct fastdiv_values ne11_div;
size_t nb11;
size_t nb12;
uint32_t start_row;
uint32_t cne1;
uint32_t k_valid;
struct htp_thread_trace *traces;
} activation_transfer_gathered_task_state_t;
typedef struct {
const struct mmid_row_mapping *matrix_rows;
const __fp16 *vtcm_src;
float *dst;
uint32_t n_tasks;
uint32_t n_tot_chunks;
uint32_t n_chunks_per_task;
uint32_t n_cols;
uint32_t cur_a;
uint32_t mapping_stride;
size_t dst_nb1;
size_t dst_nb2;
uint32_t start_row;
uint32_t cne1;
struct htp_thread_trace *traces;
} output_transfer_scattered_task_state_t;
static void transfer_activation_chunk_fp32_to_fp16_gathered(
__fp16 *restrict vtcm_dst,
const float *restrict src,
-7
View File
@@ -6,7 +6,6 @@
#include <qurt_thread.h>
#include <qurt_futex.h>
#include <qurt_hvx.h>
#include <HAP_compute_res.h>
@@ -43,7 +42,6 @@ static inline void hmx_queue_process(struct hmx_queue *q, bool* killed) {
case HMX_QUEUE_NOOP: /* noop */; break;
case HMX_QUEUE_KILL: *killed = true; break;
case HMX_QUEUE_SUSPEND: hmx_unlock(q); break;
case HMX_QUEUE_WAKEUP: hmx_lock(q); break;
default:
hmx_lock(q);
htp_trace_event_start(q->trace, HTP_TRACE_EVT_HMX_COMP, ir);
@@ -72,14 +70,9 @@ static void hmx_queue_thread(void * arg) {
while (!killed) {
unsigned int seqn = atomic_load(&q->seqn);
if (seqn == prev_seqn) {
// drop HVX context while spinning
if (poll_cnt > 1 && poll_cnt == HMX_QUEUE_POLL_COUNT) {
qurt_hvx_unlock();
}
if (--poll_cnt) { hex_pause(); continue; }
FARF(HIGH, "hmx-queue-thread: sleeping");
qurt_futex_wait(&q->seqn, prev_seqn);
poll_cnt = HMX_QUEUE_POLL_COUNT;
continue;
}
prev_seqn = seqn;
+4 -25
View File
@@ -18,19 +18,13 @@ extern "C" {
#endif
#define HMX_QUEUE_THREAD_STACK_SIZE (16 * 1024)
#if __HVX_ARCH__ > 79
#define HMX_QUEUE_POLL_COUNT 2000
#else
#define HMX_QUEUE_POLL_COUNT 1
#endif
#define HMX_QUEUE_POLL_COUNT 2000
typedef void (*hmx_queue_func)(void *);
// Dummy funcs used as signals
enum hmx_queue_signal {
HMX_QUEUE_NOOP = 0, // aka NULL
HMX_QUEUE_WAKEUP,
HMX_QUEUE_SUSPEND,
HMX_QUEUE_KILL
};
@@ -103,7 +97,7 @@ static inline uint32_t hmx_queue_capacity(struct hmx_queue * q) {
return q->capacity;
}
static inline struct hmx_queue_desc hmx_queue_pop_one(struct hmx_queue * q) {
static inline struct hmx_queue_desc hmx_queue_pop(struct hmx_queue * q) {
unsigned int ip = q->idx_pop;
unsigned int iw = q->idx_write;
@@ -126,28 +120,13 @@ static inline struct hmx_queue_desc hmx_queue_pop_one(struct hmx_queue * q) {
return rd;
}
static inline struct hmx_queue_desc hmx_queue_pop(struct hmx_queue * q) {
while (1) {
struct hmx_queue_desc d = hmx_queue_pop_one(q);
uint32_t sig = (uint32_t) d.func;
if (sig && sig <= HMX_QUEUE_KILL)
continue;
return d;
}
}
static inline void hmx_queue_flush(struct hmx_queue * q) {
while (hmx_queue_pop_one(q).func != NULL) ;
}
static inline void hmx_queue_wakeup(struct hmx_queue * q) {
hmx_queue_signal(q, HMX_QUEUE_WAKEUP);
while (hmx_queue_pop(q).func != NULL) ;
}
static inline void hmx_queue_suspend(struct hmx_queue *q) {
hmx_queue_signal(q, HMX_QUEUE_SUSPEND);
hmx_queue_flush(q);
}
#ifdef __cplusplus
-22
View File
@@ -197,26 +197,4 @@ static inline void hmx_interleave_cols_to_tiles(__fp16 * restrict tiles_out,
}
}
// --- HMX inline asm macros for load-store packetization ---
#define HMX_LOAD_MPY_F16(act, wt, range) \
"{\n" \
" activation.hf = mxmem(" act ", " range ")\n" \
" weight.hf = mxmem(" wt ", " range ")\n" \
"}\n"
#define HMX_LOAD_MPY_DEEP_F16(act, wt, range) \
"{\n" \
" activation.hf = mxmem(" act ", " range "):deep\n" \
" weight.hf = mxmem(" wt ", " range ")\n" \
"}\n"
#define HMX_STORE_AFTER_F16(out, scale_reg) \
"mxmem(" out ", " scale_reg "):after.hf = acc\n"
#define HMX_SET_BIAS(scales) \
"bias = mxmem2(" scales ")\n"
#define HMX_CLRACC_F16() \
"mxclracc.hf\n"
#endif // HMX_UTILS_H
-19
View File
@@ -1,19 +0,0 @@
#ifndef HTP_VTCM_H
#define HTP_VTCM_H
#include <stddef.h>
#include <stdint.h>
static inline uint8_t *vtcm_seq_alloc(uint8_t **vtcm_ptr, size_t size) {
uint8_t *p = *vtcm_ptr;
*vtcm_ptr += size;
return p;
}
#define VTCM_LAYOUT_ALLOC(off, field, sz) do { (L)->field = (off); (off) += (sz); } while (0)
#define VTCM_LAYOUT_ALLOC_OPTIONAL(off, field, sz, cond) do { if (cond) { VTCM_LAYOUT_ALLOC(off, field, sz); } else { (L)->field = 0; } } while (0)
#define VTCM_LAYOUT_PTR(type, base, offset) ((type *)((uint8_t *)(base) + (offset)))
#define VTCM_LAYOUT_PTR_OPTIONAL(type, base, offset, cond) ((cond) ? VTCM_LAYOUT_PTR(type, base, offset) : NULL)
#endif // HTP_VTCM_H
-4
View File
@@ -948,8 +948,6 @@ static void htp_packet_callback(dspqueue_t queue, int error, void * context) {
int op_status = HTP_STATUS_OK;
uint32_t op_wakeup = n_ops / 2; // half-way throgh the batch
hmx_queue_wakeup(ctx->hmx_queue);
for (uint32_t i=0; i < n_ops; i++) {
struct profile_data prof;
@@ -978,8 +976,6 @@ static void htp_packet_callback(dspqueue_t queue, int error, void * context) {
}
}
hmx_queue_suspend(ctx->hmx_queue);
struct htp_opbatch_rsp rsp;
rsp.id = req.id;
rsp.status = op_status;
+252 -400
View File
@@ -20,7 +20,7 @@
#include "htp-ctx.h"
#include "htp-ops.h"
#include "matmul-ops.h"
#include "htp-vtcm.h"
#include "vtcm-utils.h"
static void hvx_tensor_add_f32_grid(
const struct htp_tensor * restrict dst,
@@ -1514,26 +1514,37 @@ static int hvx_mm_matmul(struct htp_ops_context * octx) {
break;
}
struct htp_mm_hvx_vtcm_layout L;
htp_mm_hvx_vtcm_layout_build(&L, kparams->kernel_type, src0->type, ne10, src1_nrows, octx->n_threads,
dst_row_size, src0_row_size, src1_row_size, kparams->n_prefetch, false, false, false);
size_t src0_sz = 0, src1_sz = 0, dst_sz = 0;
if (kparams->vtcm_src0_size > 0 || kparams->vtcm_src1_size > 0 || kparams->vtcm_dst_size > 0) {
src0_sz = kparams->vtcm_src0_size;
src1_sz = kparams->vtcm_src1_size;
dst_sz = kparams->vtcm_dst_size;
} else {
const uint32_t n_prefetch = kparams->n_prefetch;
assert(n_prefetch >= 2 && n_prefetch <= HTP_MM_MAX_PREFETCH && (n_prefetch & (n_prefetch - 1)) == 0);
htp_mm_hvx_get_vtcm_sizes(
kparams->kernel_type, src0->type, ne10, src1_nrows, octx->n_threads,
dst_row_size, src0_row_size, src1_row_size, n_prefetch,
&src0_sz, &src1_sz, &dst_sz
);
}
if (kparams->kernel_type == HTP_MM_KERNEL_HVX_F16_F16_VTCM ||
kparams->kernel_type == HTP_MM_KERNEL_HVX_F32_F32_VTCM ||
kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW ||
kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_BLOCK) {
mmctx->vtcm_src1_size_per_thread = L.src1_bytes;
mmctx->vtcm_src1_size_per_thread = src1_sz;
} else {
mmctx->vtcm_src1_size_per_thread = L.src1_bytes / octx->n_threads;
mmctx->vtcm_src1_size_per_thread = src1_sz / octx->n_threads;
}
mmctx->vtcm_src0_size_per_thread = L.src0_bytes / octx->n_threads;
mmctx->vtcm_dst_size_per_thread = L.dst_bytes / octx->n_threads;
mmctx->vtcm_src0_size_per_thread = src0_sz / octx->n_threads;
mmctx->vtcm_dst_size_per_thread = dst_sz / octx->n_threads;
size_t vtcm_size = kparams->vtcm_size > 0 ? (size_t)kparams->vtcm_size : L.total_bytes;
size_t vtcm_size = kparams->vtcm_size > 0 ? (size_t)kparams->vtcm_size : (src1_sz + src0_sz + dst_sz);
FARF(HIGH, "matmul-%s : src0-vtcm-size %zu src1-vtcm-size %zu dst-vtcm-size %zu (%zu)\n", mmctx->type,
L.src0_bytes, L.src1_bytes, L.dst_bytes, vtcm_size);
src0_sz, src1_sz, dst_sz, vtcm_size);
FARF(HIGH, "matmul-%s : %ux%ux%ux%u * %ux%ux%ux%u-> %ux%ux%ux%u (0x%p, 0x%p, 0x%p)\n", mmctx->type, src0->ne[0],
src0->ne[1], src0->ne[2], src0->ne[3], src1->ne[0], src1->ne[1], src1->ne[2], src1->ne[3], dst->ne[0],
@@ -1545,10 +1556,10 @@ static int hvx_mm_matmul(struct htp_ops_context * octx) {
return HTP_STATUS_VTCM_TOO_SMALL;
}
uint8_t * const base = (uint8_t *) octx->ctx->vtcm_base;
mmctx->vtcm_src1 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src1);
mmctx->vtcm_src0 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src0);
mmctx->vtcm_dst = VTCM_LAYOUT_PTR(uint8_t, base, L.off_dst);
uint8_t * vtcm_ptr = (uint8_t *) octx->ctx->vtcm_base;
mmctx->vtcm_src1 = vtcm_seq_alloc(&vtcm_ptr, src1_sz);
mmctx->vtcm_src0 = vtcm_seq_alloc(&vtcm_ptr, src0_sz);
mmctx->vtcm_dst = vtcm_seq_alloc(&vtcm_ptr, dst_sz);
octx->src1_spad.src = NULL;
octx->src0_spad.src = NULL;
@@ -1937,95 +1948,14 @@ static void transfer_output_chunk_worker_fn(unsigned int n, unsigned int i, void
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_O_PROC, start_chunk_idx);
}
typedef struct {
const struct mmid_row_mapping *matrix_rows;
__fp16 *dst;
const float *src;
uint32_t n_tasks;
uint32_t n_tot_chunks;
uint32_t n_chunks_per_task;
uint32_t k_block;
uint32_t k_stride;
uint32_t k_valid;
struct htp_thread_trace * traces;
struct htp_context * ctx;
float * vtcm_f32_act;
size_t vtcm_f32_act_bytes_per_thread;
uint32_t dma_step_rows;
uint32_t dma_step_rows_shift;
} activation_transfer_task_state_t;
static void transfer_activation_chunk_fp32_to_fp16_dma_pipelined(
dma_queue *dma_q,
__fp16 *restrict vtcm_dst,
const float *restrict src,
uint32_t n_rows,
uint32_t k_block,
uint32_t k_stride,
uint32_t k_valid,
float *thread_f32_act,
struct htp_thread_trace *tr,
uint32_t dma_step_rows,
uint32_t dma_step_rows_shift) {
const uint32_t R = dma_step_rows;
const uint32_t n_rows_padded = hex_align_up(n_rows, HTP_MM_HMX_TILE_N_ROWS);
const uint32_t n_steps = n_rows_padded >> dma_step_rows_shift;
// Push step 0
if (n_steps > 0 && n_rows > 0) {
uint32_t nrows_to_fetch = hex_smin(n_rows, R);
dma_queue_push(dma_q, dma_make_ptr(thread_f32_act, src),
k_block * sizeof(float), k_stride * sizeof(float), k_valid * sizeof(float), nrows_to_fetch);
}
// Push step 1 (if valid)
if (n_steps > 1) {
uint32_t next_r = R * 1;
if (next_r < n_rows) {
uint32_t nrows_to_fetch = hex_smin(n_rows - next_r, R);
const float *next_src = src + next_r * k_stride;
float *next_buf = thread_f32_act + 1 * R * k_block;
dma_queue_push(dma_q, dma_make_ptr(next_buf, next_src),
k_block * sizeof(float), k_stride * sizeof(float), k_valid * sizeof(float), nrows_to_fetch);
}
}
for (uint32_t s = 0; s < n_steps; ++s) {
uint32_t r = s << dma_step_rows_shift;
float *curr_buf = thread_f32_act;
if (r < n_rows) {
curr_buf = (float *) dma_queue_pop(dma_q).dst;
}
htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_PREP, r);
for (uint32_t p = 0; p < (R >> 1); ++p) {
uint32_t row_idx = r + (p << 1);
float *pair_buf = curr_buf + (p << 1) * k_block;
bool r0_valid = ((row_idx + 0) < n_rows);
bool r1_valid = ((row_idx + 1) < n_rows);
transfer_activation_row_pair_fp32_to_fp16(vtcm_dst, pair_buf, pair_buf + k_block, row_idx, k_block, k_valid, r0_valid, r1_valid);
}
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_PREP, r);
// Push step s + 2
uint32_t next_s = s + 2;
uint32_t next_r = next_s << dma_step_rows_shift;
if (next_r < n_rows) {
uint32_t nrows_to_fetch = hex_smin(n_rows - next_r, R);
const float *next_src = src + next_r * k_stride;
dma_queue_push(dma_q, dma_make_ptr(curr_buf, next_src),
k_block * sizeof(float), k_stride * sizeof(float), k_valid * sizeof(float), nrows_to_fetch);
}
}
}
static void transfer_activation_chunk_worker_fn(unsigned int n, unsigned int i, void *data) {
activation_transfer_task_state_t *st = (activation_transfer_task_state_t *) data;
struct htp_thread_trace * tr = st->traces ? &st->traces[i] : NULL;
int start_chunk_idx = i * st->n_chunks_per_task;
htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_PREP, start_chunk_idx);
for (unsigned int task_id = i; task_id < (unsigned int)st->n_tasks; task_id += n) {
int chunk_idx = task_id * st->n_chunks_per_task;
size_t chunk_size = hex_smin(st->n_tot_chunks - chunk_idx, st->n_chunks_per_task);
@@ -2034,55 +1964,18 @@ static void transfer_activation_chunk_worker_fn(unsigned int n, unsigned int i,
const float *src = st->src + chunk_idx * st->k_stride;
if (st->vtcm_f32_act) {
float *thread_f32_act = (float *)((char *)st->vtcm_f32_act + i * st->vtcm_f32_act_bytes_per_thread);
float *thread_f32_act = st->vtcm_f32_act + i * HTP_MM_DMA_ACT_MULTIPLIER * st->k_block;
transfer_activation_chunk_fp32_to_fp16_dma_pipelined(
st->ctx->dma[i], dst, src, chunk_size, st->k_block, st->k_stride, st->k_valid, thread_f32_act, tr, st->dma_step_rows, st->dma_step_rows_shift
st->ctx->dma[i], dst, src, chunk_size, st->k_block, st->k_stride, st->k_valid, thread_f32_act
);
} else {
htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_A_PREP, chunk_idx);
transfer_activation_chunk_fp32_to_fp16(dst, src, chunk_size, st->k_block, st->k_stride, st->k_valid);
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_PREP, chunk_idx);
}
}
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_A_PREP, start_chunk_idx);
}
typedef struct {
const struct mmid_row_mapping *matrix_rows;
__fp16 *dst;
const float *src;
uint32_t n_tasks;
uint32_t n_tot_chunks;
uint32_t n_chunks_per_task;
uint32_t k_block;
uint32_t cur_a;
uint32_t mapping_stride;
uint32_t ne11;
struct fastdiv_values ne11_div;
size_t nb11;
size_t nb12;
uint32_t start_row;
uint32_t cne1;
uint32_t k_valid;
struct htp_thread_trace *traces;
} activation_transfer_gathered_task_state_t;
typedef struct {
const struct mmid_row_mapping *matrix_rows;
const __fp16 *vtcm_src;
float *dst;
uint32_t n_tasks;
uint32_t n_tot_chunks;
uint32_t n_chunks_per_task;
uint32_t n_cols;
uint32_t cur_a;
uint32_t mapping_stride;
size_t dst_nb1;
size_t dst_nb2;
uint32_t start_row;
uint32_t cne1;
struct htp_thread_trace *traces;
} output_transfer_scattered_task_state_t;
static void transfer_activation_chunk_gathered_worker_fn(unsigned int n, unsigned int i, void *data) {
activation_transfer_gathered_task_state_t *st = data;
struct htp_thread_trace * tr = st->traces ? &st->traces[i] : NULL;
@@ -2219,89 +2112,32 @@ static void transfer_activation_chunk_threaded(
int k_stride,
int n_threads,
int k_valid,
float *vtcm_f32_act,
size_t vtcm_f32_act_bytes) {
if (n_rows <= 0) {
return;
}
float *vtcm_f32_act) {
assert(k_block % HTP_MM_HMX_TILE_N_COLS == 0 && k_stride % HTP_MM_HMX_TILE_N_COLS == 0);
size_t n_tot_chunks = n_rows;
size_t n_chunks_per_task = (n_threads == 1) ? n_tot_chunks : 32; // must be multiple of 32 to ensure correct destination address
uint32_t dma_step_rows = 2;
uint32_t dma_step_rows_shift = 1;
if (vtcm_f32_act && vtcm_f32_act_bytes > 0 && k_block > 0) {
size_t thread_scratch_elements = vtcm_f32_act_bytes / (n_threads * sizeof(float));
size_t dma_step_rows_max = (thread_scratch_elements / 2) / k_block;
if (dma_step_rows_max >= 4) {
dma_step_rows = 4;
dma_step_rows_shift = 2;
} else {
dma_step_rows = 2;
dma_step_rows_shift = 1;
}
}
activation_transfer_task_state_t state;
state.n_tasks = (n_tot_chunks + n_chunks_per_task - 1) / n_chunks_per_task;
state.n_tot_chunks = n_tot_chunks;
state.n_chunks_per_task = n_chunks_per_task;
state.dst = dst;
state.src = src;
state.k_block = k_block;
state.k_stride = k_stride;
state.k_valid = k_valid;
state.traces = ctx->trace;
state.ctx = ctx;
state.vtcm_f32_act = vtcm_f32_act;
int active_threads = hex_smin(n_threads, (int)state.n_tasks);
state.vtcm_f32_act_bytes_per_thread = (vtcm_f32_act_bytes / active_threads) & ~127u;
state.dma_step_rows = dma_step_rows;
state.dma_step_rows_shift = dma_step_rows_shift;
state.n_tasks = (n_tot_chunks + n_chunks_per_task - 1) / n_chunks_per_task;
state.n_tot_chunks = n_tot_chunks;
state.n_chunks_per_task = n_chunks_per_task;
state.dst = dst;
state.src = src;
state.k_block = k_block;
state.k_stride = k_stride;
state.k_valid = k_valid;
state.traces = ctx->trace;
state.ctx = ctx;
state.vtcm_f32_act = vtcm_f32_act;
if (state.n_tasks == 1 || n_threads == 1) {
transfer_activation_chunk_worker_fn(1, 0, &state);
} else {
worker_pool_run_func(ctx->worker_pool, transfer_activation_chunk_worker_fn, &state, active_threads);
int n_tasks = hex_smin((int) state.n_tasks, n_threads);
worker_pool_run_func(ctx->worker_pool, transfer_activation_chunk_worker_fn, &state, n_tasks);
}
}
// --- Async HMX matmul job (for pipeline overlap) ---
typedef struct {
__fp16 * output;
const __fp16 * activation;
const __fp16 * weight;
const __fp16 * scales;
uint32_t n_row_tiles;
uint32_t n_col_tiles;
uint32_t n_dot_tiles;
} hmx_matmul_job_t;
static void hmx_matmul_worker_fn(void * data) {
hmx_matmul_job_t * job = (hmx_matmul_job_t *) data;
FARF(HIGH, "hmx-mm-job: n_row_tiles %u n_col_tiles %u n_dot_tiles %u", job->n_row_tiles, job->n_col_tiles, job->n_dot_tiles);
core_dot_chunk_fp16(job->output, job->activation, job->weight, job->scales, job->n_row_tiles, job->n_col_tiles, job->n_dot_tiles);
}
static inline void hmx_matmul_job_init(hmx_matmul_job_t * job,
__fp16 * output,
const __fp16 * activation,
const __fp16 * weight,
const __fp16 * scales,
uint32_t n_row_tiles,
uint32_t n_col_tiles,
uint32_t n_dot_tiles) {
job->output = output;
job->activation = activation;
job->weight = weight;
job->scales = scales;
job->n_row_tiles = n_row_tiles;
job->n_col_tiles = n_col_tiles;
job->n_dot_tiles = n_dot_tiles;
}
static int hmx_mm_2d_f32(struct htp_context *ctx,
float *restrict dst,
@@ -2362,33 +2198,48 @@ static int hmx_mm_2d_f32(struct htp_context *ctx,
const size_t qweight_row_stride = is_quant ? (size_t)(n_k_tiles * aligned_tile_size) / 32 : 0;
struct htp_mm_hmx_vtcm_layout L;
htp_mm_hmx_vtcm_layout_build(&L, HTP_MM_KERNEL_HMX_2D, weight_type, k, m_chunk_n_rows, n_chunk_n_cols, 1, false, pipeline, act_threads, aligned_tile_size);
const size_t act_f32_size = hex_align_up((size_t)act_threads * HTP_MM_DMA_ACT_MULTIPLIER * k * sizeof(float), HTP_MM_HMX_TILE_SIZE);
vtcm_used = L.total_bytes;
const size_t weight_area_size = is_quant
? hex_align_up((n_chunk_n_cols / 32) * n_k_tiles * aligned_tile_size, HTP_MM_HMX_TILE_SIZE)
: hex_align_up(n_chunk_n_cols * row_stride, HTP_MM_HMX_TILE_SIZE);
const size_t act_area_size = hex_align_up(m_chunk_n_rows * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
const size_t output_area_size = hex_align_up(m_chunk_n_rows * n_chunk_n_cols * sizeof(__fp16), HTP_MM_HMX_TILE_SIZE);
size_t scratch0_size, scratch1_size, scratch2_size;
scratch0_size = hex_align_up(n_chunk_n_cols * vec_dot_size, HTP_MM_HMX_TILE_SIZE); // dequant buf 0
scratch1_size = pipeline ? scratch0_size : 0; // dequant buf 1
scratch2_size = pipeline ? output_area_size : 0; // output buf 1
uint8_t *vtcm_ptr = (uint8_t *) ctx->vtcm_base;
__fp16 *vtcm_weight_raw[2] = { NULL, NULL };
if (weight_area_size) {
if (pipeline) {
vtcm_weight_raw[0] = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, weight_area_size);
vtcm_weight_raw[1] = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, weight_area_size);
} else {
vtcm_weight_raw[0] = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, weight_area_size);
}
}
__fp16 *vtcm_f16_act = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, act_area_size);
float *vtcm_f32_act = (float *) vtcm_seq_alloc(&vtcm_ptr, act_f32_size);
__fp16 *vtcm_output = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, output_area_size);
void *vtcm_scratch0 = vtcm_seq_alloc(&vtcm_ptr, scratch0_size);
void *vtcm_scratch1 = scratch1_size ? vtcm_seq_alloc(&vtcm_ptr, scratch1_size) : NULL;
void *vtcm_scratch2 = scratch2_size ? vtcm_seq_alloc(&vtcm_ptr, scratch2_size) : NULL;
__fp16 *vtcm_scales = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, 256);
vtcm_used = vtcm_ptr - (uint8_t *) ctx->vtcm_base;
if (vtcm_used > vtcm_budget) {
FARF(ERROR, "hmx-mm-2d-precomputed: VTCM overflow: used %zu budget %zu, m %d k %d n %d mc %zu nc %zu",
vtcm_used, vtcm_budget, m, k, n, m_chunk_n_rows, n_chunk_n_cols);
return -1;
}
uint8_t * const base = (uint8_t *) ctx->vtcm_base;
__fp16 *vtcm_weight_raw[2] = {
VTCM_LAYOUT_PTR(__fp16, base, L.off_weight[0]),
VTCM_LAYOUT_PTR_OPTIONAL(__fp16, base, L.off_weight[1], pipeline)
};
__fp16 *vtcm_f16_act = VTCM_LAYOUT_PTR(__fp16, base, L.off_act);
float *vtcm_f32_act = VTCM_LAYOUT_PTR(float, base, L.off_act_f32);
__fp16 *vtcm_output = VTCM_LAYOUT_PTR(__fp16, base, L.off_dst[0]);
void *vtcm_scratch0 = VTCM_LAYOUT_PTR(void, base, L.off_scratch[0]);
void *vtcm_scratch1 = VTCM_LAYOUT_PTR_OPTIONAL(void, base, L.off_scratch[1], pipeline);
void *vtcm_scratch2 = VTCM_LAYOUT_PTR_OPTIONAL(void, base, L.off_dst[1], pipeline);
__fp16 *vtcm_scales = VTCM_LAYOUT_PTR(__fp16, base, L.off_scales);
hmx_init_column_scales(vtcm_scales, Q6_V_vsplat_R(0x3c00)); // scale: 1.0, bias: 0.0 in FP16
FARF(HIGH, "hmx-mm-2d: m %d k %d n %d wtype %d mc %zu nc %zu vtcm %zu/%zu",
FARF(HIGH, "hmx-mm-2d-precomputed: standard : m %d k %d n %d wtype %d mc %zu nc %zu vtcm %zu/%zu",
m, k, n, weight_type, m_chunk_n_rows, n_chunk_n_cols, vtcm_used, vtcm_budget);
int n_chunk_cnt = hmx_ceil_div(n, n_chunk_n_cols);
@@ -2403,118 +2254,107 @@ static int hmx_mm_2d_f32(struct htp_context *ctx,
void *vtcm_weight_bufs[2] = { vtcm_scratch0, vtcm_scratch1 };
void *vtcm_output_bufs[2] = { vtcm_output, vtcm_scratch2 };
transfer_activation_chunk_threaded(ctx, vtcm_f16_act, activation + mr * act_stride, n_rows, k, act_stride, act_threads, k_valid, vtcm_f32_act, L.act_f32_bytes);
transfer_activation_chunk_threaded(ctx, vtcm_f16_act, activation + mr * act_stride, n_rows, k, act_stride, act_threads, k_valid, vtcm_f32_act);
// Prologue: push A0 and optionally A1 (if n_chunk_cnt > 1)
const size_t n_cols_A0 = hex_smin(n - 0 * n_chunk_n_cols, n_chunk_n_cols);
const size_t n_cols_A0 = hex_smin(n - 0 * n_chunk_n_cols, n_chunk_n_cols);
const uint32_t height_A0 = is_quant ? (n_cols_A0 / 32) * n_k_tiles : n_cols_A0;
dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight_raw[0], weight),
dma_dst_stride, dma_src_stride, dma_width_bytes, height_A0);
if (1 < n_chunk_cnt) {
const size_t n_cols_A1 = hex_smin(n - 1 * n_chunk_n_cols, n_chunk_n_cols);
const size_t n_cols_A1 = hex_smin(n - 1 * n_chunk_n_cols, n_chunk_n_cols);
const uint32_t height_A1 = is_quant ? (n_cols_A1 / 32) * n_k_tiles : n_cols_A1;
dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight_raw[1], weight + n_chunk_n_cols * weight_stride),
dma_dst_stride, dma_src_stride, dma_width_bytes, height_A1);
}
// Main loop: pop A_i -> dequantize A_i -> push A_{i+2} -> submit C_i -> wait C_{i-1} and store D_{i-1}
// pop A0 -> dequantize A0 -> submit C0
dma_queue_pop(ctx->dma[0]);
dequantize_tiled_weight_chunk_to_fp16_tiles(
ctx, vtcm_weight_bufs[0], vtcm_weight_raw[0],
n_cols_A0, k, row_stride, weight_type,
n_k_tiles, n_k_tiles_div, dequant_worker_fn, n_threads);
hmx_matmul_job_init(&job_slots[0], (__fp16 *) vtcm_output_bufs[0], (__fp16 *) vtcm_f16_act,
(__fp16 *) vtcm_weight_bufs[0], vtcm_scales,
hmx_ceil_div(n_rows, HTP_MM_HMX_TILE_N_ROWS),
hmx_ceil_div(n_cols_A0, HTP_MM_HMX_TILE_N_COLS), k / HTP_MM_HMX_TILE_N_ROWS);
hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_matmul_worker_fn, &job_slots[0]));
// Main loop: pop/dequantize A_{i+1} -> push A_{i+2} -> submit C_{i+1} -> wait C_i and store D_i
for (int i = 0; i < n_chunk_cnt; ++i) {
const size_t nc = i * n_chunk_n_cols;
const size_t nc_p1 = nc + 1 * n_chunk_n_cols;
const size_t nc_p2 = nc + 2 * n_chunk_n_cols;
const size_t n_cols = hex_smin(n - nc, n_chunk_n_cols);
const size_t n_cols_p1 = hex_smin(n - nc_p1, n_chunk_n_cols);
const size_t n_cols_p2 = hex_smin(n - nc_p2, n_chunk_n_cols);
// 1. pop A_i
void * curr_raw = dma_queue_pop(ctx->dma[0]).dst;
// 1. pop A_{i+1} and dequantize it (if i+1 < n_chunk_cnt)
if (i + 1 < n_chunk_cnt) {
dma_queue_pop(ctx->dma[0]);
dequantize_tiled_weight_chunk_to_fp16_tiles(
ctx, vtcm_weight_bufs[(i + 1) % 2], vtcm_weight_raw[(i + 1) % 2],
n_cols_p1, k, row_stride, weight_type,
n_k_tiles, n_k_tiles_div, dequant_worker_fn, n_threads);
}
// 2. dequantize A_i
dequantize_tiled_weight_chunk_to_fp16_tiles(
ctx, vtcm_weight_bufs[i % 2], curr_raw,
n_cols, k, row_stride, weight_type,
n_k_tiles, n_k_tiles_div, dequant_worker_fn, n_threads);
// 3. push A_{i+2} (if i+2 < n_chunk_cnt)
// 2. push A_{i+2} (if i+2 < n_chunk_cnt)
if (i + 2 < n_chunk_cnt) {
const uint32_t height_p2 = is_quant ? (n_cols_p2 / 32) * n_k_tiles : n_cols_p2;
dma_queue_push(ctx->dma[0], dma_make_ptr(curr_raw, weight + nc_p2 * weight_stride),
dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight_raw[(i + 2) % 2], weight + nc_p2 * weight_stride),
dma_dst_stride, dma_src_stride, dma_width_bytes, height_p2);
}
// 4. submit C_i
hmx_matmul_job_init(&job_slots[i % 2], (__fp16 *) vtcm_output_bufs[i % 2],
(__fp16 *) vtcm_f16_act, (__fp16 *) vtcm_weight_bufs[i % 2],
vtcm_scales, hmx_ceil_div(n_rows, HTP_MM_HMX_TILE_N_ROWS),
hmx_ceil_div(n_cols, HTP_MM_HMX_TILE_N_COLS), k / HTP_MM_HMX_TILE_N_ROWS);
hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_matmul_worker_fn, &job_slots[i % 2]));
// 3. submit C_{i+1} (if i+1 < n_chunk_cnt)
if (i + 1 < n_chunk_cnt) {
hmx_matmul_job_init(&job_slots[(i + 1) % 2], (__fp16 *) vtcm_output_bufs[(i + 1) % 2],
(__fp16 *) vtcm_f16_act, (__fp16 *) vtcm_weight_bufs[(i + 1) % 2],
vtcm_scales, hmx_ceil_div(n_rows, HTP_MM_HMX_TILE_N_ROWS),
hmx_ceil_div(n_cols_p1, HTP_MM_HMX_TILE_N_COLS), k / HTP_MM_HMX_TILE_N_ROWS);
hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_matmul_worker_fn, &job_slots[(i + 1) % 2]));
}
// 5. wait C_{i-1} and store D_{i-1} (multi-thread HVX, parallel with C_i)
if (i > 0) {
hmx_queue_pop(ctx->hmx_queue);
const size_t nc_prev = (i - 1) * n_chunk_n_cols;
const size_t n_cols_prev = hex_smin(n - nc_prev, n_chunk_n_cols);
float *output_chunk = dst + (mr * dst_stride + nc_prev);
const float *src2_chunk = src2 ? (src2 + mr * src2_stride + nc_prev) : NULL;
int chunk_dst_cols = dst_cols - (int)nc_prev;
if (chunk_dst_cols > 0) {
transfer_output_chunk_threaded(ctx, output_chunk, src2_chunk, vtcm_output_bufs[(i - 1) % 2], n_rows, n_cols_prev, dst_stride, src2_stride, chunk_dst_cols, n_threads);
}
// 4. wait C_i and store D_i (multi-thread HVX, parallel with C_{i+1})
hmx_queue_pop(ctx->hmx_queue);
float *output_chunk = dst + (mr * dst_stride + nc);
const float *src2_chunk = src2 ? (src2 + mr * src2_stride + nc) : NULL;
int chunk_dst_cols = dst_cols - (int)nc;
if (chunk_dst_cols > 0) {
transfer_output_chunk_threaded(ctx, output_chunk, src2_chunk, vtcm_output_bufs[i % 2], n_rows, n_cols, dst_stride, src2_stride, chunk_dst_cols, n_threads);
}
}
// Epilogue: wait C_{last} and store D_{last}
hmx_queue_pop(ctx->hmx_queue);
const size_t nc_last = (n_chunk_cnt - 1) * n_chunk_n_cols;
const size_t n_cols_last = hex_smin(n - nc_last, n_chunk_n_cols);
float *output_chunk = dst + (mr * dst_stride + nc_last);
const float *src2_chunk = src2 ? (src2 + mr * src2_stride + nc_last) : NULL;
int chunk_dst_cols = dst_cols - (int)nc_last;
if (chunk_dst_cols > 0) {
transfer_output_chunk_threaded(ctx, output_chunk, src2_chunk, vtcm_output_bufs[(n_chunk_cnt - 1) % 2], n_rows, n_cols_last, dst_stride, src2_stride, chunk_dst_cols, n_threads);
}
}
hmx_queue_suspend(ctx->hmx_queue);
} else {
// --- Synchronous loop (m <= 32 or fallback) ---
hmx_matmul_job_t job;
// --- Synchronous Un-pipelined loop (m <= 32 or fallback) ---
HAP_compute_res_hmx_lock(ctx->vtcm_rctx);
for (size_t mr = 0; mr < m; mr += m_chunk_n_rows) {
const size_t n_rows = hex_smin(m - mr, m_chunk_n_rows);
transfer_activation_chunk_threaded(ctx, vtcm_f16_act, activation + mr * act_stride, n_rows, k, act_stride, act_threads, k_valid, vtcm_f32_act, L.act_f32_bytes);
// A0: Pre-fetch the first weight chunk (nc = 0)
if (n > 0) {
const size_t n_cols = hex_smin(n, n_chunk_n_cols);
const uint32_t height = is_quant ? (n_cols / 32) * n_k_tiles : n_cols;
dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight_raw[0], weight), dma_dst_stride, dma_src_stride, dma_width_bytes, height);
}
transfer_activation_chunk_threaded(ctx, vtcm_f16_act, activation + mr * act_stride, n_rows, k, act_stride, act_threads, k_valid, vtcm_f32_act);
for (size_t nc = 0; nc < n; nc += n_chunk_n_cols) {
const size_t n_cols = hex_smin(n - nc, n_chunk_n_cols);
const size_t n_row_tiles = hmx_ceil_div(n_rows, HTP_MM_HMX_TILE_N_ROWS);
const size_t n_col_tiles = hmx_ceil_div(n_cols, HTP_MM_HMX_TILE_N_COLS);
// A: Wait for weight DMA
void * curr_raw = dma_queue_pop(ctx->dma[0]).dst;
// A: Weight DMA (Synchronous)
const uint32_t height = is_quant ? (n_cols / 32) * n_k_tiles : n_cols;
dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight_raw[0], weight + nc * weight_stride),
dma_dst_stride, dma_src_stride, dma_width_bytes, height);
dma_queue_pop(ctx->dma[0]);
// B: Weight Dequantize (Threaded)
dequantize_tiled_weight_chunk_to_fp16_tiles(
ctx, vtcm_scratch0, curr_raw,
ctx, vtcm_scratch0, vtcm_weight_raw[0],
n_cols, k, row_stride, weight_type,
n_k_tiles, n_k_tiles_div, dequant_worker_fn, n_threads);
// Start weight DMA for the next chunk early
const size_t nc_next = nc + n_chunk_n_cols;
if (nc_next < n) {
const size_t n_cols_next = hex_smin(n - nc_next, n_chunk_n_cols);
const uint32_t height_next = is_quant ? (n_cols_next / 32) * n_k_tiles : n_cols_next;
dma_queue_push(ctx->dma[0], dma_make_ptr(curr_raw, weight + nc_next * weight_stride), dma_dst_stride, dma_src_stride, dma_width_bytes, height_next);
}
// C: HMX Compute (Queue-based)
hmx_matmul_job_init(&job, vtcm_output, vtcm_f16_act, vtcm_scratch0, vtcm_scales, n_row_tiles, n_col_tiles, k / HTP_MM_HMX_TILE_N_ROWS);
hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_matmul_worker_fn, &job));
hmx_queue_pop(ctx->hmx_queue);
// C: HMX Compute (Synchronous)
core_dot_chunk_fp16(vtcm_output, vtcm_f16_act, vtcm_scratch0, vtcm_scales, n_row_tiles, n_col_tiles, k / HTP_MM_HMX_TILE_N_ROWS);
// D: Output Store
float *output_chunk = dst + (mr * dst_stride + nc);
@@ -2525,6 +2365,7 @@ static int hmx_mm_2d_f32(struct htp_context *ctx,
}
}
}
HAP_compute_res_hmx_unlock(ctx->vtcm_rctx);
}
return 0;
@@ -2617,34 +2458,37 @@ static int hmx_mm_f16_f32_batched(struct htp_context *ctx, const hmx_mm_f16_f32_
size_t n_chunk_n_cols = n_chunk;
size_t vtcm_used = vtcm_size;
struct htp_mm_hmx_vtcm_layout L;
htp_mm_hmx_vtcm_layout_build(&L, HTP_MM_KERNEL_HMX_F16_BATCHED, HTP_TYPE_F16, params->k, m_chunk_n_rows, n_chunk_n_cols, group_size, use_dma_activation, false, act_threads, 0);
const size_t act_head_stride = m_chunk_n_rows * (size_t) params->k; // fp16 elements between heads
const size_t weight_area_size = hex_align_up(n_chunk_n_cols * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
const size_t activation_area_size = hex_align_up(group_size * m_chunk_n_rows * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
const size_t output_area_size = hex_align_up(m_chunk_n_rows * n_chunk_n_cols * sizeof(__fp16), HTP_MM_HMX_TILE_SIZE);
const size_t scratch_area_size = hex_align_up(n_chunk_n_cols * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
if (L.total_bytes > vtcm_budget) {
uint8_t *vtcm_ptr = (uint8_t *) ctx->vtcm_base;
__fp16 *vtcm_weight = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, weight_area_size);
__fp16 *vtcm_f16_act = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, activation_area_size);
__fp16 *vtcm_output = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, output_area_size);
void *vtcm_scratch0 = vtcm_seq_alloc(&vtcm_ptr, scratch_area_size);
void *vtcm_scratch1 = vtcm_seq_alloc(&vtcm_ptr, scratch_area_size);
__fp16 *vtcm_scales = (__fp16 *) vtcm_seq_alloc(&vtcm_ptr, 256);
float *vtcm_f32_act = use_dma_activation ? (float *) vtcm_seq_alloc(&vtcm_ptr, f32_scratch_size) : NULL;
if ((size_t) (vtcm_ptr - (uint8_t *) ctx->vtcm_base) > vtcm_budget) {
FARF(HIGH, "%s: grouped layout overflowed VTCM, falling back to simple batched loop", __func__);
return hmx_mm_f16_f32_batched_simple(ctx, params, m_chunk, n_chunk, pipeline, n_threads, act_threads, vtcm_size);
}
uint8_t * const base = (uint8_t *) ctx->vtcm_base;
__fp16 *vtcm_weight = VTCM_LAYOUT_PTR(__fp16, base, L.off_weight[0]);
__fp16 *vtcm_f16_act = VTCM_LAYOUT_PTR(__fp16, base, L.off_act);
__fp16 *vtcm_output = VTCM_LAYOUT_PTR(__fp16, base, L.off_dst[0]);
void *vtcm_scratch0 = VTCM_LAYOUT_PTR(void, base, L.off_scratch[0]);
void *vtcm_scratch1 = VTCM_LAYOUT_PTR(void, base, L.off_scratch[1]);
__fp16 *vtcm_scales = VTCM_LAYOUT_PTR(__fp16, base, L.off_scales);
float *vtcm_f32_act = VTCM_LAYOUT_PTR_OPTIONAL(float, base, L.off_act_f32, use_dma_activation);
hmx_init_column_scales(vtcm_scales, Q6_V_vsplat_R(0x3c00)); // scale: 1.0, bias: 0.0 in FP16
FARF(HIGH, "%s: grouped path m=%d k=%d n=%d group=%d streams=%d mc=%zu nc=%zu vtcm=%zu/%zu",
__func__, params->m, params->k, params->n, group_size, params->ne13,
m_chunk_n_rows, n_chunk_n_cols,
L.total_bytes, vtcm_budget);
(size_t) (vtcm_ptr - (uint8_t *) ctx->vtcm_base), vtcm_budget);
const size_t fp16_row_bytes = (size_t) params->k * sizeof(__fp16);
const size_t weight_row_bytes = (size_t) params->weight_stride * sizeof(__fp16);
hmx_matmul_job_t job;
HAP_compute_res_hmx_lock(ctx->vtcm_rctx);
for (int b3 = 0; b3 < params->ne13; ++b3) {
for (int b2_base = 0; b2_base < params->ne12; b2_base += group_size) {
@@ -2661,59 +2505,58 @@ static int hmx_mm_f16_f32_batched(struct htp_context *ctx, const hmx_mm_f16_f32_
// thrashing from HVX loads at large strides.
for (int g = 0; g < group_size; ++g) {
const float *activation_chunk = hmx_mm_activation_batch_ptr(params, b2_base + g, b3) + mr * params->act_stride;
__fp16 *vtcm_act_g = vtcm_f16_act + (size_t) g * L.act_head_stride;
__fp16 *vtcm_act_g = vtcm_f16_act + (size_t) g * act_head_stride;
transfer_activation_chunk_threaded(ctx, vtcm_act_g,
activation_chunk, (int) n_rows,
params->k, params->act_stride, act_threads, params->k, vtcm_f32_act, L.act_f32_bytes);
params->k, params->act_stride, act_threads, params->k, vtcm_f32_act);
}
// Prologue: Push A0 and A1 (if exists)
void *buf_curr = vtcm_scratch0;
void *buf_next = vtcm_scratch1;
{
const size_t n_cols_first = hex_smin((size_t) params->n, n_chunk_n_cols);
dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_scratch0, weight_group),
dma_queue_push(ctx->dma[0], dma_make_ptr(buf_curr, weight_group),
fp16_row_bytes, weight_row_bytes, fp16_row_bytes, n_cols_first);
}
if (n_chunk_n_cols < (size_t) params->n) {
const size_t n_cols_second = hex_smin((size_t) params->n - n_chunk_n_cols, n_chunk_n_cols);
dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_scratch1, weight_group + params->weight_stride),
fp16_row_bytes, weight_row_bytes, fp16_row_bytes, n_cols_second);
}
for (size_t nc = 0; nc < (size_t) params->n; nc += n_chunk_n_cols) {
const size_t n_cols = hex_smin((size_t) params->n - nc, n_chunk_n_cols);
const size_t n_cols = hex_smin((size_t) params->n - nc, n_chunk_n_cols);
const size_t n_col_tiles = hmx_ceil_div((int) n_cols, HTP_MM_HMX_TILE_N_COLS);
{
void * curr_raw = dma_queue_pop(ctx->dma[0]).dst;
dma_queue_pop(ctx->dma[0]);
hmx_interleave_rows_to_tiles(vtcm_weight, (const __fp16 *) curr_raw, n_cols, params->k, params->k, 0, n_cols);
const size_t nc_next = nc + n_chunk_n_cols * 2;
const size_t nc_next = nc + n_chunk_n_cols;
if (nc_next < (size_t) params->n) {
const size_t n_cols_next = hex_smin((size_t) params->n - nc_next, n_chunk_n_cols);
const __fp16 *next_weight_chunk = weight_group + nc_next * params->weight_stride;
dma_queue_push(ctx->dma[0], dma_make_ptr(curr_raw, next_weight_chunk),
dma_queue_push(ctx->dma[0], dma_make_ptr(buf_next, next_weight_chunk),
fp16_row_bytes, weight_row_bytes, fp16_row_bytes, n_cols_next);
}
hmx_interleave_rows_to_tiles(vtcm_weight, (const __fp16 *) buf_curr, n_cols, params->k, params->k, 0, n_cols);
hex_swap_ptr(&buf_curr, &buf_next);
}
// Reuse the interleaved weight for every q_head in this GQA group
for (int g = 0; g < group_size; ++g) {
struct htp_thread_trace * tr = &ctx->trace[HTP_MAX_NTHREADS];
htp_trace_event_start(tr, HTP_TRACE_EVT_HMX_COMP, g);
{
const __fp16 * vtcm_act_g = vtcm_f16_act + (size_t) g * L.act_head_stride;
hmx_matmul_job_init(&job, vtcm_output, vtcm_act_g, vtcm_weight, vtcm_scales, n_row_tiles, n_col_tiles, params->k / 32);
hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_matmul_worker_fn, &job));
hmx_queue_pop(ctx->hmx_queue);
const __fp16 * vtcm_act_g = vtcm_f16_act + (size_t) g * act_head_stride;
core_dot_chunk_fp16(vtcm_output, vtcm_act_g, vtcm_weight, vtcm_scales, n_row_tiles, n_col_tiles,
params->k / 32);
}
htp_trace_event_stop(tr, HTP_TRACE_EVT_HMX_COMP, g);
{
float *output = hmx_mm_dst_batch_ptr(params, b2_base + g, b3) + mr * params->dst_stride + nc;
const float *src2_chunk = params->src2 ? (hmx_mm_src2_batch_ptr(params, b2_base + g, b3) + mr * params->src2_stride + nc) : NULL;
int chunk_dst_cols = params->n - (int)nc;
if (chunk_dst_cols > 0) {
transfer_output_chunk_threaded(ctx, output, src2_chunk, vtcm_output, (int) n_rows, (int) n_cols,
params->dst_stride, params->src2_stride, chunk_dst_cols, ctx->n_threads);
transfer_output_chunk_threaded(ctx, output, src2_chunk, vtcm_output, (int) n_rows, (int) n_cols, params->dst_stride, params->src2_stride, chunk_dst_cols, ctx->n_threads);
}
}
}
@@ -2722,6 +2565,8 @@ static int hmx_mm_f16_f32_batched(struct htp_context *ctx, const hmx_mm_f16_f32_
}
}
HAP_compute_res_hmx_unlock(ctx->vtcm_rctx);
return 0;
}
@@ -2913,7 +2758,7 @@ static int hmx_mm_id_2d_f32(struct htp_context *ctx,
hmx_init_column_scales(vtcm_scales, Q6_V_vsplat_R(0x3c00));
hmx_matmul_job_t job;
HAP_compute_res_hmx_lock(ctx->vtcm_rctx);
for (size_t mr = 0; mr < (size_t) m_padded; mr += m_chunk_n_rows) {
const size_t n_rows = hex_smin(m_padded - mr, m_chunk_n_rows);
@@ -2923,52 +2768,37 @@ static int hmx_mm_id_2d_f32(struct htp_context *ctx,
ctx, vtcm_f16_act, activation, (int) mr, (int) n_rows, k,
matrix_rows, cur_a, mapping_stride, ne11, act_nb1, act_nb2, cne1, n_threads, k_valid);
// A0: Pre-fetch the first weight chunk (nc = 0)
if (n > 0) {
const size_t n_cols = hex_smin((size_t) n, n_chunk_n_cols);
const uint32_t height = is_quant ? (n_cols / 32) * n_k_tiles : n_cols;
dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight, weight),
dma_dst_stride, dma_src_stride, dma_width_bytes, height);
}
for (size_t nc = 0; nc < (size_t) n; nc += n_chunk_n_cols) {
const size_t n_cols = hex_smin((size_t) n - nc, n_chunk_n_cols);
const size_t n_col_tiles = hmx_ceil_div(n_cols, HTP_MM_HMX_TILE_N_COLS);
// A: Wait for weight DMA
void * curr_raw = dma_queue_pop(ctx->dma[0]).dst;
const uint32_t height = is_quant ? (n_cols / 32) * n_k_tiles : n_cols;
dma_queue_push(ctx->dma[0], dma_make_ptr(vtcm_weight, weight + nc * weight_stride),
dma_dst_stride, dma_src_stride, dma_width_bytes, height);
dma_queue_pop(ctx->dma[0]);
// B: Weight Dequantize (Threaded)
dequantize_tiled_weight_chunk_to_fp16_tiles(
ctx, vtcm_scratch0, curr_raw,
ctx, vtcm_scratch0, vtcm_weight,
n_cols, k, row_stride, weight_type,
n_k_tiles, n_k_tiles_div, dequant_worker_fn, n_threads
);
// Start weight DMA for the next chunk early
const size_t nc_next = nc + n_chunk_n_cols;
if (nc_next < (size_t) n) {
const size_t n_cols_next = hex_smin((size_t) n - nc_next, n_chunk_n_cols);
const uint32_t height_next = is_quant ? (n_cols_next / 32) * n_k_tiles : n_cols_next;
dma_queue_push(ctx->dma[0], dma_make_ptr(curr_raw, weight + nc_next * weight_stride),
dma_dst_stride, dma_src_stride, dma_width_bytes, height_next);
}
struct htp_thread_trace * tr = &ctx->trace[HTP_MAX_NTHREADS];
htp_trace_event_start(tr, HTP_TRACE_EVT_HMX_COMP, nc);
core_dot_chunk_fp16(vtcm_output, vtcm_f16_act, vtcm_scratch0, vtcm_scales, n_row_tiles, n_col_tiles, k / HTP_MM_HMX_TILE_N_ROWS);
htp_trace_event_stop(tr, HTP_TRACE_EVT_HMX_COMP, nc);
// C: HMX Compute (Queue-based)
hmx_matmul_job_init(&job, vtcm_output, vtcm_f16_act, vtcm_scratch0, vtcm_scales, n_row_tiles, n_col_tiles, k / HTP_MM_HMX_TILE_N_ROWS);
hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_matmul_worker_fn, &job));
hmx_queue_pop(ctx->hmx_queue);
// D: Output Store
transfer_output_chunk_scattered_threaded(
ctx, dst + nc, vtcm_output, (int) mr, (int) n_rows, (int) n_cols,
matrix_rows, cur_a, mapping_stride, dst_nb1, dst_nb2, cne1, n_threads);
}
}
HAP_compute_res_hmx_unlock(ctx->vtcm_rctx);
return 0;
}
// --- Dispatchers and Public Entry Points ---
static int hmx_mm_op_matmul(struct htp_ops_context * octx, const struct htp_mm_kernel_params * kparams) {
@@ -3130,14 +2960,22 @@ static int hvx_mm_matmul_id(
}
size_t src1_row_size = (src0->type == HTP_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
struct htp_mm_hvx_vtcm_layout L;
htp_mm_hvx_vtcm_layout_build(&L, kparams->kernel_type, src0->type, ne10, src1_nrows, octx->n_threads,
0, src0_row_size, src1_row_size, kparams->n_prefetch, true, false, false);
// Scratchpad sizes are computed on the host (htp_mm_hvx_id_get_vtcm_sizes) and passed in.
// The ID layout is routing-independent, so the host has exact visibility -- consume it here
// rather than recomputing, to keep host budgeting and device allocation in lockstep.
size_t src0_sz = kparams->vtcm_src0_size;
size_t src1_sz = kparams->vtcm_src1_size;
size_t src2_sz = 0; // mapping lives in DDR
size_t dst_sz = kparams->vtcm_dst_size;
size_t vtcm_size = kparams->vtcm_size;
size_t vtcm_size = kparams->vtcm_size > 0 ? (size_t)kparams->vtcm_size : L.total_bytes;
size_t src0_sz_per_thread = src0_sz / octx->n_threads;
size_t src1_sz_per_thread = src1_sz;
size_t src2_sz_per_thread = 0;
size_t dst_sz_per_thread = dst_sz / octx->n_threads;
FARF(HIGH, "matmul-id-%s : src0-spad-size %zu src1-spad-size %zu src2-spad-size 0 dst-spad-size %zu (%zu)\n", mmctx->type,
L.src0_bytes, L.src1_bytes, L.dst_bytes, vtcm_size);
FARF(HIGH, "matmul-id-%s : src0-spad-size %zu src1-spad-size %zu src2-spad-size %zu dst-spad-size %zu (%zu)\n", mmctx->type,
src0_sz, src1_sz, src2_sz, dst_sz, vtcm_size);
FARF(HIGH, "matmul-id-%s : %ux%ux%ux%u * %ux%ux%ux%u (%ux%ux%ux%u) -> %ux%ux%ux%u (0x%p, 0x%p, 0x%p)\n", mmctx->type,
src0->ne[0], src0->ne[1], src0->ne[2], src0->ne[3], src1->ne[0], src1->ne[1], src1->ne[2], src1->ne[3],
@@ -3151,11 +2989,11 @@ static int hvx_mm_matmul_id(
return HTP_STATUS_VTCM_TOO_SMALL;
}
uint8_t * const base = (uint8_t *) octx->ctx->vtcm_base;
mmctx->vtcm_src1 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src1);
mmctx->vtcm_src0 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src0);
mmctx->vtcm_src2 = NULL;
mmctx->vtcm_dst = VTCM_LAYOUT_PTR(uint8_t, base, L.off_dst);
uint8_t * vtcm_ptr = (uint8_t *) octx->ctx->vtcm_base;
mmctx->vtcm_src1 = vtcm_seq_alloc(&vtcm_ptr, src1_sz);
mmctx->vtcm_src0 = vtcm_seq_alloc(&vtcm_ptr, src0_sz);
mmctx->vtcm_src2 = vtcm_seq_alloc(&vtcm_ptr, src2_sz);
mmctx->vtcm_dst = vtcm_seq_alloc(&vtcm_ptr, dst_sz);
octx->src1_spad.src = NULL;
octx->src0_spad.src = NULL;
@@ -3165,10 +3003,10 @@ static int hvx_mm_matmul_id(
mmctx->vtcm_src0_stride = src0_row_size_padded;
mmctx->vtcm_src1_stride = src1_row_size;
mmctx->vtcm_src0_size_per_thread = L.src0_bytes / octx->n_threads;
mmctx->vtcm_src1_size_per_thread = L.src1_bytes;
mmctx->vtcm_src2_size_per_thread = 0;
mmctx->vtcm_dst_size_per_thread = L.dst_bytes / octx->n_threads;
mmctx->vtcm_src0_size_per_thread = src0_sz_per_thread;
mmctx->vtcm_src1_size_per_thread = src1_sz_per_thread;
mmctx->vtcm_src2_size_per_thread = src2_sz_per_thread;
mmctx->vtcm_dst_size_per_thread = dst_sz_per_thread;
mmctx->n_quant_rows_per_thread = (src1_nrows + n_quant_tasks - 1) / n_quant_tasks;
mmctx->quant_task_func = quant_task_func;
@@ -3343,11 +3181,19 @@ int op_matmul_qkv(struct htp_ops_context * octx) {
src1_row_size = (src0->type == HTP_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(src1->ne[0]) : htp_mm_q8_0_tiled_row_size(src1->ne[0]);
}
struct htp_mm_hvx_vtcm_layout L;
htp_mm_hvx_vtcm_layout_build(&L, kparams->kernel_type, src0->type, src1->ne[0], src1_nrows, octx->n_threads,
0, src0_row_size, src1_row_size, kparams->n_prefetch, false, true, false);
// Set up scratchpads using precomputed sizes from the host
size_t src0_sz = kparams->vtcm_src0_size;
size_t src1_sz = kparams->vtcm_src1_size;
size_t src2_sz = kparams->vtcm_src2_size;
size_t src3_sz = kparams->vtcm_src3_size;
size_t dst_sz = kparams->vtcm_dst_size;
size_t vtcm_size = kparams->vtcm_size;
size_t vtcm_size = kparams->vtcm_size > 0 ? (size_t)kparams->vtcm_size : L.total_bytes;
size_t src0_sz_per_thread = src0_sz / octx->n_threads;
size_t src1_sz_per_thread = src1_sz;
size_t src2_sz_per_thread = src2_sz / octx->n_threads;
size_t src3_sz_per_thread = src3_sz / octx->n_threads;
size_t dst_sz_per_thread = dst_sz / octx->n_threads;
if (octx->ctx->vtcm_size < vtcm_size) {
FARF(ERROR, "matmul-qkv: current VTCM reservation %zu is too small, needed %zu\n",
@@ -3355,12 +3201,12 @@ int op_matmul_qkv(struct htp_ops_context * octx) {
return HTP_STATUS_VTCM_TOO_SMALL;
}
uint8_t * const base = (uint8_t *) octx->ctx->vtcm_base;
mmctx->vtcm_src1 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src1);
mmctx->vtcm_src0 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src0);
mmctx->vtcm_src2 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src2);
mmctx->vtcm_src3 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src3);
mmctx->vtcm_dst = VTCM_LAYOUT_PTR(uint8_t, base, L.off_dst);
uint8_t * vtcm_ptr = (uint8_t *) octx->ctx->vtcm_base;
mmctx->vtcm_src1 = vtcm_seq_alloc(&vtcm_ptr, src1_sz);
mmctx->vtcm_src0 = vtcm_seq_alloc(&vtcm_ptr, src0_sz);
mmctx->vtcm_src2 = vtcm_seq_alloc(&vtcm_ptr, src2_sz);
mmctx->vtcm_src3 = vtcm_seq_alloc(&vtcm_ptr, src3_sz);
mmctx->vtcm_dst = vtcm_seq_alloc(&vtcm_ptr, dst_sz);
octx->src1_spad.src = NULL;
octx->src0_spad.src = NULL;
@@ -3373,11 +3219,11 @@ int op_matmul_qkv(struct htp_ops_context * octx) {
mmctx->vtcm_src3_stride = is_repacked ? 0 : src0_row_size_padded;
mmctx->vtcm_src1_stride = src1_row_size;
mmctx->vtcm_src0_size_per_thread = L.src0_bytes / octx->n_threads;
mmctx->vtcm_src1_size_per_thread = L.src1_bytes;
mmctx->vtcm_src2_size_per_thread = L.src2_bytes / octx->n_threads;
mmctx->vtcm_src3_size_per_thread = L.src3_bytes / octx->n_threads;
mmctx->vtcm_dst_size_per_thread = L.dst_bytes / octx->n_threads;
mmctx->vtcm_src0_size_per_thread = src0_sz_per_thread;
mmctx->vtcm_src1_size_per_thread = src1_sz_per_thread;
mmctx->vtcm_src2_size_per_thread = src2_sz_per_thread;
mmctx->vtcm_src3_size_per_thread = src3_sz_per_thread;
mmctx->vtcm_dst_size_per_thread = dst_sz_per_thread;
if (octx->flags & HTP_OPFLAGS_SKIP_COMPUTE)
return HTP_STATUS_OK;
@@ -3485,22 +3331,28 @@ int op_matmul_ffn(struct htp_ops_context * octx) {
src1_row_size = (src0->type == HTP_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(src1->ne[0]) : htp_mm_q8_0_tiled_row_size(src1->ne[0]);
}
struct htp_mm_hvx_vtcm_layout L;
htp_mm_hvx_vtcm_layout_build(&L, kparams->kernel_type, src0->type, src1->ne[0], src1_nrows, octx->n_threads,
0, src0_row_size, src1_row_size, kparams->n_prefetch, false, false, true);
// Set up scratchpads using precomputed sizes from the host
size_t src0_sz = kparams->vtcm_src0_size;
size_t src1_sz = kparams->vtcm_src1_size;
size_t src2_sz = kparams->vtcm_src2_size;
size_t dst_sz = kparams->vtcm_dst_size;
size_t vtcm_size = kparams->vtcm_size;
size_t vtcm_size = kparams->vtcm_size > 0 ? (size_t)kparams->vtcm_size : L.total_bytes;
size_t src0_sz_per_thread = src0_sz / octx->n_threads;
size_t src1_sz_per_thread = src1_sz;
size_t src2_sz_per_thread = src2_sz / octx->n_threads;
size_t dst_sz_per_thread = dst_sz / octx->n_threads;
if (octx->ctx->vtcm_size < vtcm_size) {
FARF(ERROR, "matmul-ffn: current VTCM reservation %zu is too small, needed %zu\n", octx->ctx->vtcm_size, vtcm_size);
return HTP_STATUS_VTCM_TOO_SMALL;
}
uint8_t * const base = (uint8_t *) octx->ctx->vtcm_base;
mmctx->vtcm_src1 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src1);
mmctx->vtcm_src0 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src0);
mmctx->vtcm_src2 = VTCM_LAYOUT_PTR(uint8_t, base, L.off_src2);
mmctx->vtcm_dst = VTCM_LAYOUT_PTR(uint8_t, base, L.off_dst);
uint8_t * vtcm_ptr = (uint8_t *) octx->ctx->vtcm_base;
mmctx->vtcm_src1 = vtcm_seq_alloc(&vtcm_ptr, src1_sz);
mmctx->vtcm_src0 = vtcm_seq_alloc(&vtcm_ptr, src0_sz);
mmctx->vtcm_src2 = vtcm_seq_alloc(&vtcm_ptr, src2_sz);
mmctx->vtcm_dst = vtcm_seq_alloc(&vtcm_ptr, dst_sz);
octx->src1_spad.src = NULL;
octx->src0_spad.src = NULL;
@@ -3511,10 +3363,10 @@ int op_matmul_ffn(struct htp_ops_context * octx) {
mmctx->vtcm_src2_stride = is_repacked ? 0 : src0_row_size_padded;
mmctx->vtcm_src1_stride = src1_row_size;
mmctx->vtcm_src0_size_per_thread = L.src0_bytes / octx->n_threads;
mmctx->vtcm_src1_size_per_thread = L.src1_bytes;
mmctx->vtcm_src2_size_per_thread = L.src2_bytes / octx->n_threads;
mmctx->vtcm_dst_size_per_thread = L.dst_bytes / octx->n_threads;
mmctx->vtcm_src0_size_per_thread = src0_sz_per_thread;
mmctx->vtcm_src1_size_per_thread = src1_sz_per_thread;
mmctx->vtcm_src2_size_per_thread = src2_sz_per_thread;
mmctx->vtcm_dst_size_per_thread = dst_sz_per_thread;
if (octx->flags & HTP_OPFLAGS_SKIP_COMPUTE)
return HTP_STATUS_OK;
+156 -311
View File
@@ -6,7 +6,6 @@
#include "htp-ops.h"
#include "hex-fastdiv.h"
#include "hex-common.h"
#include "htp-vtcm.h"
#ifdef __cplusplus
extern "C" {
@@ -45,7 +44,7 @@ extern "C" {
// --- DMA Activation Transfer Configuration ---
#define HTP_MM_DMA_ACT_ROWS_PER_STEP 2
#define HTP_MM_DMA_ACT_MULTIPLIER (2 * HTP_MM_DMA_ACT_ROWS_PER_STEP)
#define HTP_MM_DMA_ACT_MULTIPLIER 4
enum htp_mm_kernel_type {
HTP_MM_KERNEL_UNSUPPORTED = 0,
@@ -296,351 +295,197 @@ static inline void htp_mm_hmx_get_batched_chunk_costs(
*size_per_mn_out = sizeof(uint16_t);
}
struct htp_mm_hmx_vtcm_layout {
// Byte offsets from vtcm_base for each region
size_t off_weight[2]; // [1] is only used when pipelined
size_t off_act;
size_t off_act_f32; // fp32 activation conversion scratch
size_t off_dst[2]; // [1] is only used when pipelined
size_t off_scratch[2]; // dequantization scratch pads
size_t off_scales; // HMX scales (256 bytes)
// Cached sizes of regions for HMX kernel use
size_t weight_area_bytes;
size_t act_area_bytes;
size_t act_f32_bytes;
size_t output_area_bytes;
size_t scratch_bytes[2];
size_t act_head_stride;
size_t total_bytes;
};
struct htp_mm_hvx_vtcm_layout {
// Byte offsets from vtcm_base for each region
size_t off_src1; // vtcm_src1 (activation)
size_t off_src0; // vtcm_src0 (weight/Wk)
size_t off_src2; // vtcm_src2 (Wq / fused only)
size_t off_src3; // vtcm_src3 (Wv / fused only)
size_t off_dst; // vtcm_dst (output scratch)
// Cached sizes
size_t src0_bytes;
size_t src1_bytes;
size_t src2_bytes;
size_t src3_bytes;
size_t dst_bytes;
size_t total_bytes;
};
static inline void htp_mm_hmx_vtcm_layout_build(
struct htp_mm_hmx_vtcm_layout * L,
int kernel_type,
int wtype,
uint32_t k,
size_t mc,
size_t nc,
uint32_t group_size,
bool use_dma_activation,
bool pipeline,
uint32_t act_threads,
uint32_t aligned_tile_size
static inline size_t htp_mm_hmx_get_2d_vtcm_size(
int wtype, uint32_t k, size_t mc, size_t nc, bool pipeline, uint32_t act_threads, uint32_t aligned_tile_size
) {
size_t off = 0;
const uint32_t n_k_tiles = k / HTP_MM_HMX_TILE_N_COLS;
const bool is_quant = (wtype != HTP_TYPE_F16 && wtype != HTP_TYPE_F32);
const size_t row_stride = htp_mm_get_tiled_row_stride(wtype, k);
const size_t vec_dot_size = k * sizeof(uint16_t);
if (kernel_type == HTP_MM_KERNEL_HMX_F16_BATCHED) {
const size_t vec_dot_size = k * sizeof(uint16_t);
const size_t act_head_stride = mc * k;
const size_t weight_area_size = hex_align_up(nc * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
const size_t activation_area_size = hex_align_up(group_size * act_head_stride * sizeof(uint16_t), HTP_MM_HMX_TILE_SIZE);
const size_t output_area_size = hex_align_up(group_size * mc * nc * sizeof(uint16_t), HTP_MM_HMX_TILE_SIZE);
const size_t scratch_area_size = hex_align_up(nc * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
const size_t min_f32_size = use_dma_activation
? hex_align_up(act_threads * HTP_MM_DMA_ACT_MULTIPLIER * k * sizeof(float), 128) : 0;
// Group A: Permanent activation tiles and scales
size_t off_group_a = 0;
VTCM_LAYOUT_ALLOC(off_group_a, off_act, activation_area_size);
VTCM_LAYOUT_ALLOC(off_group_a, off_scales, HTP_MM_HMX_TILE_SIZE); // Padded to 2K for alignment and future persistent data
// Group B: Compute-only buffers (starts at off_group_a)
size_t off_group_b = off_group_a;
VTCM_LAYOUT_ALLOC(off_group_b, off_weight[0], weight_area_size);
VTCM_LAYOUT_ALLOC_OPTIONAL(off_group_b, off_weight[1], weight_area_size, false);
VTCM_LAYOUT_ALLOC(off_group_b, off_dst[0], output_area_size);
VTCM_LAYOUT_ALLOC_OPTIONAL(off_group_b, off_dst[1], output_area_size, false);
VTCM_LAYOUT_ALLOC(off_group_b, off_scratch[0], scratch_area_size);
VTCM_LAYOUT_ALLOC(off_group_b, off_scratch[1], scratch_area_size);
const size_t group_b_size = off_group_b - off_group_a;
// Group C: Activation prep temporary buffer (overlaps Group B, starting at off_group_a)
const size_t max_f32_size = act_threads * 64 * k * sizeof(float);
const size_t act_f32_size = use_dma_activation
? hex_align_up(hex_smin(max_f32_size, hex_smax(min_f32_size, group_b_size)), 128) : 0;
size_t off_group_c = off_group_a;
VTCM_LAYOUT_ALLOC_OPTIONAL(off_group_c, off_act_f32, act_f32_size, use_dma_activation);
const size_t group_c_size = off_group_c - off_group_a;
L->weight_area_bytes = weight_area_size;
L->act_area_bytes = activation_area_size;
L->act_f32_bytes = act_f32_size;
L->output_area_bytes = output_area_size;
L->scratch_bytes[0] = scratch_area_size;
L->scratch_bytes[1] = scratch_area_size;
L->act_head_stride = act_head_stride;
off = off_group_a + hex_smax(group_b_size, group_c_size);
} else {
// HTP_MM_KERNEL_HMX_2D
const bool is_quant = (wtype != HTP_TYPE_F16 && wtype != HTP_TYPE_F32);
const size_t row_stride = htp_mm_get_tiled_row_stride(wtype, k);
const size_t vec_dot_size = k * sizeof(uint16_t);
const uint32_t n_k_tiles = k / HTP_MM_HMX_TILE_N_COLS;
const size_t min_f32_size = hex_align_up(act_threads * HTP_MM_DMA_ACT_MULTIPLIER * k * sizeof(float), 128);
const size_t weight_area_size = is_quant
? hex_align_up((nc / 32) * n_k_tiles * aligned_tile_size, HTP_MM_HMX_TILE_SIZE)
: hex_align_up(nc * row_stride, HTP_MM_HMX_TILE_SIZE);
const size_t act_area_size = hex_align_up(mc * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
const size_t output_area_size = hex_align_up(mc * nc * sizeof(__fp16), HTP_MM_HMX_TILE_SIZE);
const size_t scratch0_size = hex_align_up(nc * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
const size_t scratch1_size = pipeline ? scratch0_size : 0;
// Group A: Scales and activation tiles (must not overlap with Group B or C)
size_t off_group_a = 0;
VTCM_LAYOUT_ALLOC(off_group_a, off_scales, HTP_MM_HMX_TILE_SIZE); // Padded to 2K for alignment and future persistent data
VTCM_LAYOUT_ALLOC(off_group_a, off_act, act_area_size);
// Group B: Compute-only buffers (starts at off_group_a)
size_t off_group_b = off_group_a;
VTCM_LAYOUT_ALLOC(off_group_b, off_weight[0], weight_area_size);
VTCM_LAYOUT_ALLOC_OPTIONAL(off_group_b, off_weight[1], weight_area_size, pipeline);
VTCM_LAYOUT_ALLOC(off_group_b, off_dst[0], output_area_size);
VTCM_LAYOUT_ALLOC(off_group_b, off_scratch[0], scratch0_size);
VTCM_LAYOUT_ALLOC_OPTIONAL(off_group_b, off_scratch[1], scratch0_size, pipeline);
VTCM_LAYOUT_ALLOC_OPTIONAL(off_group_b, off_dst[1], output_area_size, pipeline);
const size_t group_b_size = off_group_b - off_group_a;
// Group C: Activation prep temporary buffer (overlaps Group B, starting at off_group_a)
const size_t max_f32_size = act_threads * 64 * k * sizeof(float);
const size_t act_f32_size = hex_align_up(hex_smin(max_f32_size, hex_smax(min_f32_size, group_b_size)), 128);
size_t off_group_c = off_group_a;
VTCM_LAYOUT_ALLOC(off_group_c, off_act_f32, act_f32_size);
const size_t group_c_size = off_group_c - off_group_a;
L->weight_area_bytes = weight_area_size;
L->act_area_bytes = act_area_size;
L->act_f32_bytes = act_f32_size;
L->output_area_bytes = output_area_size;
L->scratch_bytes[0] = scratch0_size;
L->scratch_bytes[1] = scratch1_size;
L->act_head_stride = 0;
off = off_group_a + hex_smax(group_b_size, group_c_size);
const size_t act_f32_size = htp_mm_round_up(act_threads * 4 * k * sizeof(float), HTP_MM_HMX_TILE_SIZE);
size_t weight_area_size = is_quant
? htp_mm_round_up((nc / 32) * n_k_tiles * aligned_tile_size, HTP_MM_HMX_TILE_SIZE)
: htp_mm_round_up(nc * row_stride, HTP_MM_HMX_TILE_SIZE);
if (pipeline) {
weight_area_size *= 2;
}
const size_t act_area_size = htp_mm_round_up(mc * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
const size_t output_area_size = htp_mm_round_up(mc * nc * sizeof(uint16_t), HTP_MM_HMX_TILE_SIZE);
L->total_bytes = off;
size_t scratch0_size = htp_mm_round_up(nc * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
size_t scratch1_size = pipeline ? scratch0_size : 0;
size_t scratch2_size = pipeline ? output_area_size : 0;
return weight_area_size + act_area_size + act_f32_size + output_area_size +
scratch0_size + scratch1_size + scratch2_size + 256;
}
static inline void htp_mm_hvx_vtcm_layout_build(
struct htp_mm_hvx_vtcm_layout * L,
static inline size_t htp_mm_hmx_get_batched_vtcm_size(
int wtype, uint32_t k, size_t mc, size_t nc, uint32_t group_size, bool use_dma_activation, bool pipeline, uint32_t act_threads) {
(void)wtype;
(void)pipeline;
const size_t vec_dot_size = k * sizeof(uint16_t);
const size_t f32_scratch_size = use_dma_activation
? htp_mm_round_up(act_threads * 4 * k * sizeof(float), HTP_MM_HMX_TILE_SIZE) : 0;
const size_t act_head_stride = mc * k;
const size_t weight_area_size = htp_mm_round_up(nc * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
const size_t act_area_size = htp_mm_round_up(group_size * act_head_stride * sizeof(uint16_t), HTP_MM_HMX_TILE_SIZE);
const size_t output_area_size = htp_mm_round_up(group_size * mc * nc * sizeof(uint16_t), HTP_MM_HMX_TILE_SIZE);
const size_t scratch_area_size = htp_mm_round_up(nc * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
return weight_area_size + act_area_size + output_area_size +
2 * scratch_area_size + 256 + f32_scratch_size;
}
static inline size_t htp_mm_hvx_get_vtcm_sizes(
int kernel_type,
int wtype,
uint32_t ne10, // k
uint32_t src1_nrows, // m_total
uint32_t src1_nrows, // m_total (or act_nrows)
uint32_t n_threads,
size_t dst_row_size,
size_t src0_row_size,
size_t src1_row_size,
uint32_t n_prefetch,
bool is_matmul_id,
bool is_fused_qkv,
bool is_fused_ffn
size_t * vtcm_src0_size_out,
size_t * vtcm_src1_size_out,
size_t * vtcm_dst_size_out
) {
size_t src0_sz = 0;
size_t src1_sz = 0;
size_t src2_sz = 0;
size_t src3_sz = 0;
size_t dst_sz = 0;
size_t vtcm_src0_size = 0;
size_t vtcm_src1_size = 0;
size_t vtcm_dst_size = 0;
const bool is_repack = (wtype == HTP_TYPE_Q4_0 || wtype == HTP_TYPE_Q4_1 ||
wtype == HTP_TYPE_Q8_0 || wtype == HTP_TYPE_IQ4_NL ||
wtype == HTP_TYPE_MXFP4);
if (is_fused_qkv || is_fused_ffn) {
const size_t src0_row_size_padded = hex_round_up(src0_row_size, 128);
const size_t quant_scratch_size = hex_round_up(ne10 * sizeof(float), QK_Q8_0_TILED * sizeof(float)) * n_threads;
const size_t src0_row_size_padded = htp_mm_round_up(src0_row_size, 128);
const size_t dst_nrows = (src1_nrows > 1) ? 0 : 1;
size_t src0_sz_per_thread = 0;
size_t src2_sz_per_thread = 0;
size_t src3_sz_per_thread = 0;
if (is_repack) {
uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(wtype);
uint32_t n_k_tiles = hex_round_up(ne10, 32) / 32;
uint32_t tile_row_size = n_k_tiles * aligned_tile_size;
src0_sz_per_thread = hex_round_up(n_prefetch * tile_row_size, 128);
src2_sz_per_thread = hex_round_up(n_prefetch * tile_row_size, 128);
if (is_fused_qkv) {
src3_sz_per_thread = hex_round_up(n_prefetch * tile_row_size, 128);
}
} else {
src0_sz_per_thread = hex_round_up(n_prefetch * src0_row_size_padded, 128);
src2_sz_per_thread = hex_round_up(n_prefetch * src0_row_size_padded, 128);
if (is_fused_qkv) {
src3_sz_per_thread = hex_round_up(n_prefetch * src0_row_size_padded, 128);
}
switch (kernel_type) {
case HTP_MM_KERNEL_HVX_F16_F16_VTCM: {
size_t f16_src1_row_size = htp_mm_round_up(ne10 * 2, 128);
vtcm_src1_size = htp_mm_round_up(f16_src1_row_size * src1_nrows, 256);
vtcm_src0_size = htp_mm_round_up(n_prefetch * src0_row_size_padded, 256) * n_threads;
vtcm_dst_size = dst_nrows > 0 ? htp_mm_round_up(dst_row_size, 128) * n_threads : 0;
break;
}
size_t flat_src1_row_size = (wtype == HTP_TYPE_Q4_1) ? htp_mm_q8_1_flat_row_size(ne10) : htp_mm_q8_0_flat_row_size(ne10);
size_t tiled_src1_row_size = (wtype == HTP_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
if (kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT) {
src1_sz = hex_round_up(flat_src1_row_size * src1_nrows, 128);
} else {
src1_sz = hex_round_up(tiled_src1_row_size * src1_nrows, 128);
case HTP_MM_KERNEL_HVX_F16_F32_DDR:
case HTP_MM_KERNEL_HVX_F16_F16_DDR:
case HTP_MM_KERNEL_HVX_F32_F32_DDR:
case HTP_MM_KERNEL_HVX_F32_F16_DDR: {
vtcm_src0_size = htp_mm_round_up(n_prefetch * src0_row_size, 256) * n_threads;
vtcm_src1_size = htp_mm_round_up(n_prefetch * src1_row_size, 256) * n_threads;
vtcm_dst_size = dst_nrows > 0 ? htp_mm_round_up(dst_row_size, 128) * n_threads : 0;
break;
}
src0_sz = src0_sz_per_thread * n_threads;
src2_sz = src2_sz_per_thread * n_threads;
src3_sz = src3_sz_per_thread * n_threads;
dst_sz = quant_scratch_size;
} else if (is_matmul_id) {
const size_t src0_row_size_padded = htp_mm_round_up(src0_row_size, 128);
const size_t src1_row_size_tiled = (wtype == HTP_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10)
: htp_mm_q8_0_tiled_row_size(ne10);
size_t src0_sz_per_thread = htp_mm_round_up(n_prefetch * src0_row_size_padded, 256);
src1_sz = htp_mm_round_up(src1_row_size_tiled * src1_nrows, 256);
if (is_repack) {
const uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(wtype);
const uint32_t n_k_tiles = ne10 / 32;
const uint32_t tile_row_size = n_k_tiles * aligned_tile_size;
size_t repacked_vtcm_size = htp_mm_round_up(n_prefetch * tile_row_size, 256);
src0_sz_per_thread = repacked_vtcm_size;
case HTP_MM_KERNEL_HVX_F32_F32_VTCM: {
size_t f32_src1_row_size = htp_mm_round_up(ne10 * 4, 128);
vtcm_src1_size = htp_mm_round_up(f32_src1_row_size * src1_nrows, 256);
vtcm_src0_size = htp_mm_round_up(n_prefetch * src0_row_size_padded, 256) * n_threads;
vtcm_dst_size = dst_nrows > 0 ? htp_mm_round_up(dst_row_size, 128) * n_threads : 0;
break;
}
case HTP_MM_KERNEL_HVX_QUANT_BLOCK:
case HTP_MM_KERNEL_HVX_QUANT_ROW: {
size_t q_src1_row_size = (wtype == HTP_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
src0_sz = src0_sz_per_thread * n_threads;
dst_sz = htp_mm_round_up(ne10 * sizeof(float), QK_Q8_0_TILED * sizeof(float)) * n_threads;
} else {
const size_t src0_row_size_padded = htp_mm_round_up(src0_row_size, 128);
const size_t dst_nrows = (src1_nrows > 1) ? 0 : 1;
vtcm_src0_size = htp_mm_round_up(n_prefetch * src0_row_size_padded, 256);
vtcm_src1_size = htp_mm_round_up(q_src1_row_size * src1_nrows, 256);
switch (kernel_type) {
case HTP_MM_KERNEL_HVX_F16_F16_VTCM: {
size_t f16_src1_row_size = htp_mm_round_up(ne10 * 2, 128);
src1_sz = htp_mm_round_up(f16_src1_row_size * src1_nrows, 256);
src0_sz = htp_mm_round_up(n_prefetch * src0_row_size_padded, 256) * n_threads;
dst_sz = dst_nrows > 0 ? htp_mm_round_up(dst_row_size, 128) * n_threads : 0;
break;
vtcm_src0_size = vtcm_src0_size * n_threads;
if (is_repack) {
uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(wtype);
uint32_t n_k_tiles = ne10 / 32;
uint32_t tile_row_size = n_k_tiles * aligned_tile_size;
size_t repacked_vtcm_size = htp_mm_round_up(n_prefetch * tile_row_size, 256);
vtcm_src0_size = repacked_vtcm_size * n_threads;
}
case HTP_MM_KERNEL_HVX_F16_F32_DDR:
case HTP_MM_KERNEL_HVX_F16_F16_DDR:
case HTP_MM_KERNEL_HVX_F32_F32_DDR:
case HTP_MM_KERNEL_HVX_F32_F16_DDR: {
src0_sz = htp_mm_round_up(n_prefetch * src0_row_size, 256) * n_threads;
src1_sz = htp_mm_round_up(n_prefetch * src1_row_size, 256) * n_threads;
dst_sz = dst_nrows > 0 ? htp_mm_round_up(dst_row_size, 128) * n_threads : 0;
break;
size_t quant_scratch_size_per_thread = htp_mm_round_up(ne10 * sizeof(float), QK_Q8_0_TILED * sizeof(float));
size_t dst_size_per_thread = dst_nrows > 0 ? htp_mm_round_up(dst_row_size, 128) : 0;
if (dst_size_per_thread < quant_scratch_size_per_thread) {
dst_size_per_thread = quant_scratch_size_per_thread;
}
case HTP_MM_KERNEL_HVX_F32_F32_VTCM: {
size_t f32_src1_row_size = htp_mm_round_up(ne10 * 4, 128);
src1_sz = htp_mm_round_up(f32_src1_row_size * src1_nrows, 256);
src0_sz = htp_mm_round_up(n_prefetch * src0_row_size_padded, 256) * n_threads;
dst_sz = dst_nrows > 0 ? htp_mm_round_up(dst_row_size, 128) * n_threads : 0;
break;
}
case HTP_MM_KERNEL_HVX_QUANT_BLOCK:
case HTP_MM_KERNEL_HVX_QUANT_ROW: {
size_t q_src1_row_size = (wtype == HTP_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
src0_sz = htp_mm_round_up(n_prefetch * src0_row_size_padded, 256);
src1_sz = htp_mm_round_up(q_src1_row_size * src1_nrows, 256);
src0_sz = src0_sz * n_threads;
if (is_repack) {
uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(wtype);
uint32_t n_k_tiles = ne10 / 32;
uint32_t tile_row_size = n_k_tiles * aligned_tile_size;
size_t repacked_vtcm_size = htp_mm_round_up(n_prefetch * tile_row_size, 256);
src0_sz = repacked_vtcm_size * n_threads;
}
size_t quant_scratch_size_per_thread = htp_mm_round_up(ne10 * sizeof(float), QK_Q8_0_TILED * sizeof(float));
size_t dst_size_per_thread = dst_nrows > 0 ? htp_mm_round_up(dst_row_size, 128) : 0;
if (dst_size_per_thread < quant_scratch_size_per_thread) {
dst_size_per_thread = quant_scratch_size_per_thread;
}
dst_sz = dst_size_per_thread * n_threads;
break;
}
case HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT: {
size_t q_src1_row_size = (wtype == HTP_TYPE_Q4_1) ? htp_mm_q8_1_flat_row_size(ne10) : htp_mm_q8_0_flat_row_size(ne10);
src0_sz = htp_mm_round_up(n_prefetch * src0_row_size_padded, 256);
src1_sz = htp_mm_round_up(q_src1_row_size * src1_nrows, 256);
src0_sz = src0_sz * n_threads;
if (is_repack) {
uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(wtype);
uint32_t n_k_tiles = ne10 / 32;
uint32_t tile_row_size = n_k_tiles * aligned_tile_size;
size_t repacked_vtcm_size = htp_mm_round_up(n_prefetch * tile_row_size, 256);
src0_sz = repacked_vtcm_size * n_threads;
}
size_t quant_scratch_size_per_thread = htp_mm_round_up(ne10 * sizeof(float), QK_Q8_0_TILED * sizeof(float));
size_t dst_size_per_thread = dst_nrows > 0 ? htp_mm_round_up(dst_row_size, 128) : 0;
if (dst_size_per_thread < quant_scratch_size_per_thread) {
dst_size_per_thread = quant_scratch_size_per_thread;
}
dst_sz = dst_size_per_thread * n_threads;
break;
}
default:
break;
vtcm_dst_size = dst_size_per_thread * n_threads;
break;
}
case HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT: {
size_t q_src1_row_size = (wtype == HTP_TYPE_Q4_1) ? htp_mm_q8_1_flat_row_size(ne10) : htp_mm_q8_0_flat_row_size(ne10);
vtcm_src0_size = htp_mm_round_up(n_prefetch * src0_row_size_padded, 256);
vtcm_src1_size = htp_mm_round_up(q_src1_row_size * src1_nrows, 256);
vtcm_src0_size = vtcm_src0_size * n_threads;
if (is_repack) {
uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(wtype);
uint32_t n_k_tiles = ne10 / 32;
uint32_t tile_row_size = n_k_tiles * aligned_tile_size;
size_t repacked_vtcm_size = htp_mm_round_up(n_prefetch * tile_row_size, 256);
vtcm_src0_size = repacked_vtcm_size * n_threads;
}
size_t quant_scratch_size_per_thread = htp_mm_round_up(ne10 * sizeof(float), QK_Q8_0_TILED * sizeof(float));
size_t dst_size_per_thread = dst_nrows > 0 ? htp_mm_round_up(dst_row_size, 128) : 0;
if (dst_size_per_thread < quant_scratch_size_per_thread) {
dst_size_per_thread = quant_scratch_size_per_thread;
}
vtcm_dst_size = dst_size_per_thread * n_threads;
break;
}
default:
break;
}
size_t off = 0;
VTCM_LAYOUT_ALLOC(off, off_src1, src1_sz);
VTCM_LAYOUT_ALLOC(off, off_src0, src0_sz);
VTCM_LAYOUT_ALLOC(off, off_src2, src2_sz);
VTCM_LAYOUT_ALLOC(off, off_src3, src3_sz);
VTCM_LAYOUT_ALLOC(off, off_dst, dst_sz);
*vtcm_src0_size_out = vtcm_src0_size;
*vtcm_src1_size_out = vtcm_src1_size;
*vtcm_dst_size_out = vtcm_dst_size;
L->src0_bytes = src0_sz;
L->src1_bytes = src1_sz;
L->src2_bytes = src2_sz;
L->src3_bytes = src3_sz;
L->dst_bytes = dst_sz;
L->total_bytes = off;
return vtcm_src0_size + vtcm_src1_size + vtcm_dst_size;
}
static inline size_t htp_mm_hmx_get_2d_vtcm_size(
int wtype, uint32_t k, size_t mc, size_t nc, bool pipeline, uint32_t act_threads, uint32_t aligned_tile_size
static inline size_t htp_mm_hvx_id_get_vtcm_sizes(
int wtype,
uint32_t ne10, // k
uint32_t src1_nrows,
uint32_t n_threads,
size_t src0_row_size, // nb01
uint32_t n_prefetch,
size_t * vtcm_src0_size_out,
size_t * vtcm_src1_size_out,
size_t * vtcm_dst_size_out
) {
struct htp_mm_hmx_vtcm_layout L;
htp_mm_hmx_vtcm_layout_build(&L, HTP_MM_KERNEL_HMX_2D, wtype, k, mc, nc, 1, false, pipeline, act_threads, aligned_tile_size);
return L.total_bytes;
}
const bool is_repack = (wtype == HTP_TYPE_Q4_0 || wtype == HTP_TYPE_Q4_1 ||
wtype == HTP_TYPE_Q8_0 || wtype == HTP_TYPE_IQ4_NL ||
wtype == HTP_TYPE_MXFP4);
static inline size_t htp_mm_hmx_get_batched_vtcm_size(
int wtype, uint32_t k, size_t mc, size_t nc, uint32_t group_size, bool use_dma_activation, bool pipeline, uint32_t act_threads) {
(void)pipeline;
struct htp_mm_hmx_vtcm_layout L;
htp_mm_hmx_vtcm_layout_build(&L, HTP_MM_KERNEL_HMX_F16_BATCHED, wtype, k, mc, nc, group_size, use_dma_activation, false, act_threads, 0);
return L.total_bytes;
const size_t src0_row_size_padded = htp_mm_round_up(src0_row_size, 128);
const size_t src1_row_size = (wtype == HTP_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10)
: htp_mm_q8_0_tiled_row_size(ne10);
size_t src0_sz_per_thread = htp_mm_round_up(n_prefetch * src0_row_size_padded, 256);
size_t src1_sz = htp_mm_round_up(src1_row_size * src1_nrows, 256);
if (is_repack) {
const uint32_t aligned_tile_size = htp_mm_get_weight_aligned_tile_size(wtype);
const uint32_t n_k_tiles = ne10 / 32;
const uint32_t tile_row_size = n_k_tiles * aligned_tile_size;
size_t repacked_vtcm_size = htp_mm_round_up(n_prefetch * tile_row_size, 256);
src0_sz_per_thread = repacked_vtcm_size;
}
const size_t vtcm_src0_size = src0_sz_per_thread * n_threads;
const size_t vtcm_dst_size = htp_mm_round_up(ne10 * sizeof(float), QK_Q8_0_TILED * sizeof(float)) * n_threads;
*vtcm_src0_size_out = vtcm_src0_size;
*vtcm_src1_size_out = src1_sz;
*vtcm_dst_size_out = vtcm_dst_size;
return vtcm_src0_size + src1_sz + vtcm_dst_size;
}
#ifdef __cplusplus
+16
View File
@@ -0,0 +1,16 @@
#ifndef VTCM_UTILS_H
#define VTCM_UTILS_H
#include "hex-utils.h"
#include <assert.h>
#include <stdint.h>
#include <hexagon_types.h>
static inline uint8_t *vtcm_seq_alloc(uint8_t **vtcm_ptr, size_t size) {
uint8_t *p = *vtcm_ptr;
*vtcm_ptr += size;
return p;
}
#endif // VTCM_UTILS_H
+4 -16
View File
@@ -1,9 +1,6 @@
#include "worker-pool.h"
#include "hex-utils.h"
#include <qurt.h>
#include <qurt_hvx.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
@@ -12,6 +9,7 @@
#include "HAP_farf.h"
#define WORKER_THREAD_STACK_SZ (2 * 16384)
#define LOWEST_USABLE_QURT_PRIO (254)
struct worker_pool_s;
@@ -44,27 +42,17 @@ static void worker_pool_main(void * context) {
FARF(HIGH, "worker-pool: thread %u started", me->id);
unsigned int prev_seqn = 0;
unsigned int poll_cnt = WORKER_POOL_POLL_COUNT;
while (!atomic_load(&pool->killed)) {
unsigned int seqn = atomic_load(&pool->seqn);
if (seqn == prev_seqn) {
// drop HVX context while spinning
if (poll_cnt > 1 && poll_cnt == WORKER_POOL_POLL_COUNT) {
qurt_hvx_unlock();
}
if (--poll_cnt) {
hex_pause();
continue;
}
// Nothing to do
qurt_futex_wait(&pool->seqn, prev_seqn);
poll_cnt = WORKER_POOL_POLL_COUNT;
continue;
}
prev_seqn = seqn;
poll_cnt = WORKER_POOL_POLL_COUNT;
// New job
prev_seqn = seqn;
unsigned int n = atomic_load(&pool->n_jobs);
unsigned int i = atomic_fetch_add(&pool->next_job, 1);
if (i >= n) {
-8
View File
@@ -24,17 +24,9 @@ typedef struct {
void * data;
} worker_pool_job_t;
#define WORKER_THREAD_STACK_SZ (2 * 16384)
/// Maximum supported number of worker threads.
#define MAX_NUM_WORKERS 10
#if __HVX_ARCH__ > 79
#define WORKER_POOL_POLL_COUNT 2000
#else
#define WORKER_POOL_POLL_COUNT 1
#endif
// Initialize worker pool.
WORKERPOOL_API AEEResult worker_pool_init(worker_pool_context_t * context, uint32_t n_threads);
+2 -6
View File
@@ -160,15 +160,11 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_get_rows(ggml_me
return res;
}
ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_set_rows(ggml_metal_library_t lib, const ggml_tensor * op) {
ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_set_rows(ggml_metal_library_t lib, ggml_type tidx, ggml_type tdst) {
char base[256];
char name[256];
const auto tsrc = op->src[0]->type;
const auto tidx = op->src[1]->type;
const auto tdst = op->type;
snprintf(base, 256, "kernel_set_rows_%s_%s_%s", ggml_type_name(tsrc), ggml_type_name(tidx), ggml_type_name(tdst));
snprintf(base, 256, "kernel_set_rows_%s_%s", ggml_type_name(tdst), ggml_type_name(tidx));
snprintf(name, 256, "%s", base);
ggml_metal_pipeline_with_params res = ggml_metal_library_get_pipeline(lib, name);
+1 -1
View File
@@ -112,7 +112,7 @@ struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_cpy
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_pool_1d (ggml_metal_library_t lib, const struct ggml_tensor * op, enum ggml_op_pool op_pool);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_pool_2d (ggml_metal_library_t lib, const struct ggml_tensor * op, enum ggml_op_pool op_pool);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_get_rows (ggml_metal_library_t lib, enum ggml_type tsrc);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_set_rows (ggml_metal_library_t lib, const struct ggml_tensor * op);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_set_rows (ggml_metal_library_t lib, enum ggml_type tidx, enum ggml_type tdst);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_diag (ggml_metal_library_t lib, const struct ggml_tensor * op);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_repeat (ggml_metal_library_t lib, enum ggml_type tsrc);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_concat (ggml_metal_library_t lib, enum ggml_type tsrc);
+1 -1
View File
@@ -1334,7 +1334,7 @@ bool ggml_metal_device_supports_op(ggml_metal_device_t dev, const struct ggml_te
return op->src[0]->type != GGML_TYPE_NVFP4;
case GGML_OP_SET_ROWS:
{
if (op->src[0]->type != GGML_TYPE_F32 && op->src[0]->type != GGML_TYPE_F16) {
if (op->src[0]->type != GGML_TYPE_F32) {
return false;
}
+1 -1
View File
@@ -1202,7 +1202,7 @@ int ggml_metal_op_set_rows(ggml_metal_op_t ctx, int idx) {
GGML_TENSOR_LOCALS( int32_t, ne, op, ne);
GGML_TENSOR_LOCALS(uint64_t, nb, op, nb);
auto pipeline = ggml_metal_library_get_pipeline_set_rows(lib, op);
auto pipeline = ggml_metal_library_get_pipeline_set_rows(lib, op->src[1]->type, op->type);
const int32_t nk0 = ne0/ggml_blck_size(op->type);
+75 -74
View File
@@ -42,8 +42,6 @@ typedef matrix<bfloat, 4, 4> bfloat4x4;
typedef matrix<bfloat, 2, 4> bfloat2x4;
#endif
#define QK_NL 16
constexpr constant static float kvalues_iq4nl_f[16] = {
-127.f, -104.f, -83.f, -65.f, -49.f, -35.f, -22.f, -10.f, 1.f, 13.f, 25.f, 38.f, 53.f, 69.f, 89.f, 113.f
};
@@ -9388,40 +9386,7 @@ kernel void kernel_get_rows_f(
}
}
typedef decltype(kernel_get_rows_f<float, float>) get_rows_f_t;
template [[host_name("kernel_get_rows_f32")]] kernel get_rows_f_t kernel_get_rows_f<float, float>;
template [[host_name("kernel_get_rows_f16")]] kernel get_rows_f_t kernel_get_rows_f<half, float>;
template [[host_name("kernel_get_rows_i32")]] kernel get_rows_f_t kernel_get_rows_f<int32_t, int32_t>;
#if defined(GGML_METAL_HAS_BF16)
template [[host_name("kernel_get_rows_bf16")]] kernel get_rows_f_t kernel_get_rows_f<bfloat, float>;
#endif
typedef decltype(kernel_get_rows_q<block_q4_0, 2, dequantize_q4_0>) get_rows_q_t;
template [[host_name("kernel_get_rows_q1_0")]] kernel get_rows_q_t kernel_get_rows_q<block_q1_0, 8, dequantize_q1_0>;
template [[host_name("kernel_get_rows_q4_0")]] kernel get_rows_q_t kernel_get_rows_q<block_q4_0, 2, dequantize_q4_0>;
template [[host_name("kernel_get_rows_q4_1")]] kernel get_rows_q_t kernel_get_rows_q<block_q4_1, 2, dequantize_q4_1>;
template [[host_name("kernel_get_rows_q5_0")]] kernel get_rows_q_t kernel_get_rows_q<block_q5_0, 2, dequantize_q5_0>;
template [[host_name("kernel_get_rows_q5_1")]] kernel get_rows_q_t kernel_get_rows_q<block_q5_1, 2, dequantize_q5_1>;
template [[host_name("kernel_get_rows_q8_0")]] kernel get_rows_q_t kernel_get_rows_q<block_q8_0, 2, dequantize_q8_0>;
template [[host_name("kernel_get_rows_mxfp4")]] kernel get_rows_q_t kernel_get_rows_q<block_mxfp4, 2, dequantize_mxfp4>;
template [[host_name("kernel_get_rows_q2_K")]] kernel get_rows_q_t kernel_get_rows_q<block_q2_K, QK_NL, dequantize_q2_K>;
template [[host_name("kernel_get_rows_q3_K")]] kernel get_rows_q_t kernel_get_rows_q<block_q3_K, QK_NL, dequantize_q3_K>;
template [[host_name("kernel_get_rows_q4_K")]] kernel get_rows_q_t kernel_get_rows_q<block_q4_K, QK_NL, dequantize_q4_K>;
template [[host_name("kernel_get_rows_q5_K")]] kernel get_rows_q_t kernel_get_rows_q<block_q5_K, QK_NL, dequantize_q5_K>;
template [[host_name("kernel_get_rows_q6_K")]] kernel get_rows_q_t kernel_get_rows_q<block_q6_K, QK_NL, dequantize_q6_K>;
template [[host_name("kernel_get_rows_iq2_xxs")]] kernel get_rows_q_t kernel_get_rows_q<block_iq2_xxs, QK_NL, dequantize_iq2_xxs>;
template [[host_name("kernel_get_rows_iq2_xs")]] kernel get_rows_q_t kernel_get_rows_q<block_iq2_xs, QK_NL, dequantize_iq2_xs>;
template [[host_name("kernel_get_rows_iq3_xxs")]] kernel get_rows_q_t kernel_get_rows_q<block_iq3_xxs, QK_NL, dequantize_iq3_xxs>;
template [[host_name("kernel_get_rows_iq3_s")]] kernel get_rows_q_t kernel_get_rows_q<block_iq3_s, QK_NL, dequantize_iq3_s>;
template [[host_name("kernel_get_rows_iq2_s")]] kernel get_rows_q_t kernel_get_rows_q<block_iq2_s, QK_NL, dequantize_iq2_s>;
template [[host_name("kernel_get_rows_iq1_s")]] kernel get_rows_q_t kernel_get_rows_q<block_iq1_s, QK_NL, dequantize_iq1_s>;
template [[host_name("kernel_get_rows_iq1_m")]] kernel get_rows_q_t kernel_get_rows_q<block_iq1_m, QK_NL, dequantize_iq1_m>;
template [[host_name("kernel_get_rows_iq4_nl")]] kernel get_rows_q_t kernel_get_rows_q<block_iq4_nl, 2, dequantize_iq4_nl>;
template [[host_name("kernel_get_rows_iq4_xs")]] kernel get_rows_q_t kernel_get_rows_q<block_iq4_xs, QK_NL, dequantize_iq4_xs>;
template<typename TS, typename TI, typename block_q, void (*quantize_func)(device const float *, device block_q &)>
template<typename TI, typename block_q, void (*quantize_func)(device const float *, device block_q &)>
kernel void kernel_set_rows_q32(
constant ggml_metal_kargs_set_rows & args,
device const void * src0,
@@ -9445,14 +9410,14 @@ kernel void kernel_set_rows_q32(
const TI i1 = ((const device TI *) ((const device char *) src1 + i10*args.nb10 + i11*args.nb11 + i12*args.nb12))[0];
device block_q * dst_row = ( device block_q *) (( device char *) dst + i1*args.nb1 + i02*args.nb2 + i03*args.nb3);
const device TS * src_row = (const device TS *) ((const device char *) src0 + i01*args.nb01 + i02*args.nb02 + i03*args.nb03);
const device float * src_row = (const device float *) ((const device char *) src0 + i01*args.nb01 + i02*args.nb02 + i03*args.nb03);
for (int ind = tiitg%tptg.x; ind < args.nk0; ind += tptg.x) {
quantize_func(src_row + 32*ind, dst_row[ind]);
}
}
template<typename TS, typename TI, typename TD>
template<typename T, typename TI>
kernel void kernel_set_rows_f(
constant ggml_metal_kargs_set_rows & args,
device const void * src0,
@@ -9475,47 +9440,14 @@ kernel void kernel_set_rows_f(
const int32_t i10 = i01;
const TI i1 = ((const device TI *) ((const device char *) src1 + i10*args.nb10 + i11*args.nb11 + i12*args.nb12))[0];
device TD * dst_row = ( device TD *) (( device char *) dst + i1*args.nb1 + i02*args.nb2 + i03*args.nb3);
const device TS * src_row = (const device TS *) ((const device char *) src0 + i01*args.nb01 + i02*args.nb02 + i03*args.nb03);
device T * dst_row = ( device T *) (( device char *) dst + i1*args.nb1 + i02*args.nb2 + i03*args.nb3);
const device float * src_row = (const device float *) ((const device char *) src0 + i01*args.nb01 + i02*args.nb02 + i03*args.nb03);
for (int ind = tiitg%tptg.x; ind < args.nk0; ind += tptg.x) {
dst_row[ind] = (TD) src_row[ind];
dst_row[ind] = (T) src_row[ind];
}
}
typedef decltype(kernel_set_rows_f<float, int64_t, float>) set_rows_f_t;
template [[host_name("kernel_set_rows_f32_i64_f32")]] kernel set_rows_f_t kernel_set_rows_f<float, int64_t, float>;
template [[host_name("kernel_set_rows_f32_i32_f32")]] kernel set_rows_f_t kernel_set_rows_f<float, int32_t, float>;
template [[host_name("kernel_set_rows_f32_i64_f16")]] kernel set_rows_f_t kernel_set_rows_f<float, int64_t, half>;
template [[host_name("kernel_set_rows_f32_i32_f16")]] kernel set_rows_f_t kernel_set_rows_f<float, int32_t, half>;
#if defined(GGML_METAL_HAS_BF16)
template [[host_name("kernel_set_rows_f32_i64_bf16")]] kernel set_rows_f_t kernel_set_rows_f<float, int64_t, bfloat>;
template [[host_name("kernel_set_rows_f32_i32_bf16")]] kernel set_rows_f_t kernel_set_rows_f<float, int32_t, bfloat>;
#endif
template [[host_name("kernel_set_rows_f16_i64_f16")]] kernel set_rows_f_t kernel_set_rows_f<half, int64_t, half>;
template [[host_name("kernel_set_rows_f16_i32_f16")]] kernel set_rows_f_t kernel_set_rows_f<half, int32_t, half>;
#if defined(GGML_METAL_HAS_BF16)
template [[host_name("kernel_set_rows_bf16_i64_bf16")]] kernel set_rows_f_t kernel_set_rows_f<bfloat, int64_t, bfloat>;
template [[host_name("kernel_set_rows_bf16_i32_bf16")]] kernel set_rows_f_t kernel_set_rows_f<bfloat, int32_t, bfloat>;
#endif
typedef decltype(kernel_set_rows_q32<float, int64_t, block_q8_0, quantize_q8_0>) set_rows_q32_t;
template [[host_name("kernel_set_rows_f32_i64_q8_0")]] kernel set_rows_q32_t kernel_set_rows_q32<float, int64_t, block_q8_0, quantize_q8_0>;
template [[host_name("kernel_set_rows_f32_i32_q8_0")]] kernel set_rows_q32_t kernel_set_rows_q32<float, int32_t, block_q8_0, quantize_q8_0>;
template [[host_name("kernel_set_rows_f32_i64_q4_0")]] kernel set_rows_q32_t kernel_set_rows_q32<float, int64_t, block_q4_0, quantize_q4_0>;
template [[host_name("kernel_set_rows_f32_i32_q4_0")]] kernel set_rows_q32_t kernel_set_rows_q32<float, int32_t, block_q4_0, quantize_q4_0>;
template [[host_name("kernel_set_rows_f32_i64_q4_1")]] kernel set_rows_q32_t kernel_set_rows_q32<float, int64_t, block_q4_1, quantize_q4_1>;
template [[host_name("kernel_set_rows_f32_i32_q4_1")]] kernel set_rows_q32_t kernel_set_rows_q32<float, int32_t, block_q4_1, quantize_q4_1>;
template [[host_name("kernel_set_rows_f32_i64_q5_0")]] kernel set_rows_q32_t kernel_set_rows_q32<float, int64_t, block_q5_0, quantize_q5_0>;
template [[host_name("kernel_set_rows_f32_i32_q5_0")]] kernel set_rows_q32_t kernel_set_rows_q32<float, int32_t, block_q5_0, quantize_q5_0>;
template [[host_name("kernel_set_rows_f32_i64_q5_1")]] kernel set_rows_q32_t kernel_set_rows_q32<float, int64_t, block_q5_1, quantize_q5_1>;
template [[host_name("kernel_set_rows_f32_i32_q5_1")]] kernel set_rows_q32_t kernel_set_rows_q32<float, int32_t, block_q5_1, quantize_q5_1>;
template [[host_name("kernel_set_rows_f32_i64_iq4_nl")]] kernel set_rows_q32_t kernel_set_rows_q32<float, int64_t, block_iq4_nl, quantize_iq4_nl>;
template [[host_name("kernel_set_rows_f32_i32_iq4_nl")]] kernel set_rows_q32_t kernel_set_rows_q32<float, int32_t, block_iq4_nl, quantize_iq4_nl>;
kernel void kernel_diag_f32(
constant ggml_metal_kargs_diag & args,
device const char * src0,
@@ -10258,6 +10190,75 @@ kernel void kernel_mul_mm_id(
}
}
#define QK_NL 16
//
// get rows
//
typedef decltype(kernel_get_rows_f<float, float>) get_rows_f_t;
template [[host_name("kernel_get_rows_f32")]] kernel get_rows_f_t kernel_get_rows_f<float, float>;
template [[host_name("kernel_get_rows_f16")]] kernel get_rows_f_t kernel_get_rows_f<half, float>;
template [[host_name("kernel_get_rows_i32")]] kernel get_rows_f_t kernel_get_rows_f<int32_t, int32_t>;
#if defined(GGML_METAL_HAS_BF16)
template [[host_name("kernel_get_rows_bf16")]] kernel get_rows_f_t kernel_get_rows_f<bfloat, float>;
#endif
typedef decltype(kernel_get_rows_q<block_q4_0, 2, dequantize_q4_0>) get_rows_q_t;
template [[host_name("kernel_get_rows_q1_0")]] kernel get_rows_q_t kernel_get_rows_q<block_q1_0, 8, dequantize_q1_0>;
template [[host_name("kernel_get_rows_q4_0")]] kernel get_rows_q_t kernel_get_rows_q<block_q4_0, 2, dequantize_q4_0>;
template [[host_name("kernel_get_rows_q4_1")]] kernel get_rows_q_t kernel_get_rows_q<block_q4_1, 2, dequantize_q4_1>;
template [[host_name("kernel_get_rows_q5_0")]] kernel get_rows_q_t kernel_get_rows_q<block_q5_0, 2, dequantize_q5_0>;
template [[host_name("kernel_get_rows_q5_1")]] kernel get_rows_q_t kernel_get_rows_q<block_q5_1, 2, dequantize_q5_1>;
template [[host_name("kernel_get_rows_q8_0")]] kernel get_rows_q_t kernel_get_rows_q<block_q8_0, 2, dequantize_q8_0>;
template [[host_name("kernel_get_rows_mxfp4")]] kernel get_rows_q_t kernel_get_rows_q<block_mxfp4, 2, dequantize_mxfp4>;
template [[host_name("kernel_get_rows_q2_K")]] kernel get_rows_q_t kernel_get_rows_q<block_q2_K, QK_NL, dequantize_q2_K>;
template [[host_name("kernel_get_rows_q3_K")]] kernel get_rows_q_t kernel_get_rows_q<block_q3_K, QK_NL, dequantize_q3_K>;
template [[host_name("kernel_get_rows_q4_K")]] kernel get_rows_q_t kernel_get_rows_q<block_q4_K, QK_NL, dequantize_q4_K>;
template [[host_name("kernel_get_rows_q5_K")]] kernel get_rows_q_t kernel_get_rows_q<block_q5_K, QK_NL, dequantize_q5_K>;
template [[host_name("kernel_get_rows_q6_K")]] kernel get_rows_q_t kernel_get_rows_q<block_q6_K, QK_NL, dequantize_q6_K>;
template [[host_name("kernel_get_rows_iq2_xxs")]] kernel get_rows_q_t kernel_get_rows_q<block_iq2_xxs, QK_NL, dequantize_iq2_xxs>;
template [[host_name("kernel_get_rows_iq2_xs")]] kernel get_rows_q_t kernel_get_rows_q<block_iq2_xs, QK_NL, dequantize_iq2_xs>;
template [[host_name("kernel_get_rows_iq3_xxs")]] kernel get_rows_q_t kernel_get_rows_q<block_iq3_xxs, QK_NL, dequantize_iq3_xxs>;
template [[host_name("kernel_get_rows_iq3_s")]] kernel get_rows_q_t kernel_get_rows_q<block_iq3_s, QK_NL, dequantize_iq3_s>;
template [[host_name("kernel_get_rows_iq2_s")]] kernel get_rows_q_t kernel_get_rows_q<block_iq2_s, QK_NL, dequantize_iq2_s>;
template [[host_name("kernel_get_rows_iq1_s")]] kernel get_rows_q_t kernel_get_rows_q<block_iq1_s, QK_NL, dequantize_iq1_s>;
template [[host_name("kernel_get_rows_iq1_m")]] kernel get_rows_q_t kernel_get_rows_q<block_iq1_m, QK_NL, dequantize_iq1_m>;
template [[host_name("kernel_get_rows_iq4_nl")]] kernel get_rows_q_t kernel_get_rows_q<block_iq4_nl, 2, dequantize_iq4_nl>;
template [[host_name("kernel_get_rows_iq4_xs")]] kernel get_rows_q_t kernel_get_rows_q<block_iq4_xs, QK_NL, dequantize_iq4_xs>;
//
// set rows
//
typedef decltype(kernel_set_rows_f<float, int64_t>) set_rows_f_t;
template [[host_name("kernel_set_rows_f32_i64")]] kernel set_rows_f_t kernel_set_rows_f<float, int64_t>;
template [[host_name("kernel_set_rows_f32_i32")]] kernel set_rows_f_t kernel_set_rows_f<float, int32_t>;
template [[host_name("kernel_set_rows_f16_i64")]] kernel set_rows_f_t kernel_set_rows_f<half, int64_t>;
template [[host_name("kernel_set_rows_f16_i32")]] kernel set_rows_f_t kernel_set_rows_f<half, int32_t>;
#if defined(GGML_METAL_HAS_BF16)
template [[host_name("kernel_set_rows_bf16_i64")]] kernel set_rows_f_t kernel_set_rows_f<bfloat, int64_t>;
template [[host_name("kernel_set_rows_bf16_i32")]] kernel set_rows_f_t kernel_set_rows_f<bfloat, int32_t>;
#endif
typedef decltype(kernel_set_rows_q32<int64_t, block_q8_0, quantize_q8_0>) set_rows_q32_t;
template [[host_name("kernel_set_rows_q8_0_i64")]] kernel set_rows_q32_t kernel_set_rows_q32<int64_t, block_q8_0, quantize_q8_0>;
template [[host_name("kernel_set_rows_q8_0_i32")]] kernel set_rows_q32_t kernel_set_rows_q32<int32_t, block_q8_0, quantize_q8_0>;
template [[host_name("kernel_set_rows_q4_0_i64")]] kernel set_rows_q32_t kernel_set_rows_q32<int64_t, block_q4_0, quantize_q4_0>;
template [[host_name("kernel_set_rows_q4_0_i32")]] kernel set_rows_q32_t kernel_set_rows_q32<int32_t, block_q4_0, quantize_q4_0>;
template [[host_name("kernel_set_rows_q4_1_i64")]] kernel set_rows_q32_t kernel_set_rows_q32<int64_t, block_q4_1, quantize_q4_1>;
template [[host_name("kernel_set_rows_q4_1_i32")]] kernel set_rows_q32_t kernel_set_rows_q32<int32_t, block_q4_1, quantize_q4_1>;
template [[host_name("kernel_set_rows_q5_0_i64")]] kernel set_rows_q32_t kernel_set_rows_q32<int64_t, block_q5_0, quantize_q5_0>;
template [[host_name("kernel_set_rows_q5_0_i32")]] kernel set_rows_q32_t kernel_set_rows_q32<int32_t, block_q5_0, quantize_q5_0>;
template [[host_name("kernel_set_rows_q5_1_i64")]] kernel set_rows_q32_t kernel_set_rows_q32<int64_t, block_q5_1, quantize_q5_1>;
template [[host_name("kernel_set_rows_q5_1_i32")]] kernel set_rows_q32_t kernel_set_rows_q32<int32_t, block_q5_1, quantize_q5_1>;
template [[host_name("kernel_set_rows_iq4_nl_i64")]] kernel set_rows_q32_t kernel_set_rows_q32<int64_t, block_iq4_nl, quantize_iq4_nl>;
template [[host_name("kernel_set_rows_iq4_nl_i32")]] kernel set_rows_q32_t kernel_set_rows_q32<int32_t, block_iq4_nl, quantize_iq4_nl>;
//
// matrix-matrix multiplication
//
+6 -77
View File
@@ -517,10 +517,6 @@ struct ggml_backend_opencl_context {
bool has_qcom_subgroup_shuffle = false; // specifically cl_qcom_subgroup_shuffle
bool disable_fusion;
// ragged moe, use int to directly pass to kernel
cl_uint adreno_use_moe_ragged;
cl_uint adreno_moe_ragged_skip_gran;
bool adreno_has_large_buffer;
bool adreno_use_large_buffer;
bool adreno_use_bin_kernels;
@@ -5346,15 +5342,6 @@ static ggml_backend_opencl_context * ggml_cl_init(ggml_backend_dev_t dev) {
backend_ctx->adreno_use_large_buffer = getenv("GGML_OPENCL_ADRENO_USE_LARGE_BUFFER") != nullptr &&
backend_ctx->gpu_family == GPU_FAMILY::ADRENO;
// ragged moe, unspecified or non-zero means enabled, set to 0 to disable
static const char * ragged_fp16_env = getenv("GGML_OPENCL_MOE_RAGGED_FP16");
backend_ctx->adreno_use_moe_ragged = (ragged_fp16_env == NULL) ? 1 : (atoi(ragged_fp16_env) != 0);
// ragged moe, tile-skip granularity (columns per skip-group): 8 = quarter (default),
// 16 = half (legacy), 32 = disabled. Override with GGML_OPENCL_MOE_RAGGED_GRAN={8,16,32}
static const char * ragged_gran_env = getenv("GGML_OPENCL_MOE_RAGGED_GRAN");
backend_ctx->adreno_moe_ragged_skip_gran = (ragged_gran_env != NULL) ? atoi(ragged_gran_env) : 8;
#ifdef GGML_OPENCL_USE_ADRENO_BIN_KERNELS
// try loading adreno binary kernels if enabled
// if fails to load, builtin kernels will be used
@@ -6242,14 +6229,8 @@ inline bool use_adreno_kernels(const ggml_backend_opencl_context *backend_ctx, c
threshold_ne0 = 128;
threshold_ne1 = 128;
}
bool threashold_ok = tensor->ne[0] >= threshold_ne0 && tensor->ne[1] >= threshold_ne1 &&
return tensor->ne[0] >= threshold_ne0 && tensor->ne[1] >= threshold_ne1 &&
tensor->ne[2] == 1 && tensor->ne[3] == 1;
// q6_K adreno kernels requires ne1 is multiple of 128
if (tensor->type == GGML_TYPE_Q6_K) {
return threashold_ok && tensor->ne[1] % 128 == 0;
}
return threashold_ok;
}
inline bool use_adreno_moe_kernels(const ggml_backend_opencl_context *backend_ctx, const ggml_tensor *tensor) {
@@ -6279,19 +6260,6 @@ static inline bool use_flat_gemv_for_large_m_q6_K(const ggml_tensor *tensor) {
// threshold is well above typical hidden/FFN dims, but below typical vocab sizes.
// q6_K flat gemv is worse for smaller K; 2048 seems to be a reasonable threshold.
// note that this forces large M weights to use LM GEMM.
// The noshuffle (transposed-weight) layout packs 2 rows per 32-bit texel and the
// gemv reads it with a ne01/2 texel stride and an exact-cover dispatch of
// ceil(ne01/2 / 64)*64 work-items with no store guard; the gemm uses 4-row tiles.
// It is therefore only correct for ne01 % 128 == 0: an odd ne01 (e.g. granitemoe
// lm_head [1536, 49155] -- odd vocab) truncates the texel stride, misaligning every
// odd column of the transposed layout (gross garbage) and dropping the last row;
// other non-multiples over-dispatch and write past the end of dst. Route such
// tensors to the flat GEMV + regular convert; the matching GEMM (ne1>1) falls back
// to CPU (see supports_op). All standard even-vocab/hidden dims are multiples of
// 128 and keep the noshuffle path.
if ((tensor->ne[1] % 128 != 0) && tensor->ne[2] == 1 && tensor->ne[3] == 1) {
return true;
}
return tensor->ne[1] >= 32768 && tensor->ne[0] >= 2048 && tensor->ne[2] == 1 && tensor->ne[3] == 1;
}
@@ -9795,30 +9763,12 @@ static bool ggml_backend_opencl_buffer_type_supports_backend(ggml_backend_buffer
UNUSED(buft);
}
static size_t ggml_backend_opencl_buffer_type_get_alloc_size(ggml_backend_buffer_type_t buft, const ggml_tensor * tensor) {
size_t size = ggml_nbytes(tensor);
#ifdef GGML_OPENCL_SOA_Q
// set_tensor carves quantized weights into per-component subbuffers (d/q,
// ql/qh/s/d, ...) whose origins are each rounded up to the device base
// alignment. When a component's size is not a multiple of the alignment
// (e.g. q6_K [1536,49155]: size_s = 49155*96 leaves a 96-byte gap at 128-byte
// alignment), the aligned carve extends past ggml_nbytes and the last
// subbuffer would overlap the next tensor in the pool. Reserve the worst-case
// carve slack: at most 5 components (q5_K), i.e. 4 aligned gaps.
if (ggml_is_quantized(tensor->type)) {
ggml_backend_opencl_device_context * dev_ctx = (ggml_backend_opencl_device_context *) buft->device->context;
size += 4 * dev_ctx->backend_ctx->alignment;
}
#endif // GGML_OPENCL_SOA_Q
return size;
}
static ggml_backend_buffer_type_i ggml_backend_opencl_buffer_type_interface = {
/* .get_name = */ ggml_backend_opencl_buffer_type_get_name,
/* .alloc_buffer = */ ggml_backend_opencl_buffer_type_alloc_buffer,
/* .get_alignment = */ ggml_backend_opencl_buffer_type_get_alignment,
/* .get_max_size = */ ggml_backend_opencl_buffer_type_get_max_size,
/* .get_alloc_size = */ ggml_backend_opencl_buffer_type_get_alloc_size,
/* .get_alloc_size = */ NULL,
/* .is_host = */ NULL,
};
@@ -16703,7 +16653,6 @@ static cl_mem ggml_cl_mul_mat_dequant_quant_to_f16(
? ggml_cl_is_q4_0_soa(tensor)
: ggml_cl_is_q8_0_soa(tensor);
cl_mem aos = nullptr;
if (is_soa) {
// Reconstruct full parent AoS; view's own nb[] then index it correctly.
const ggml_tensor * parent = tensor->view_src ? tensor->view_src : tensor;
@@ -16715,7 +16664,7 @@ static cl_mem ggml_cl_mul_mat_dequant_quant_to_f16(
const size_t parent_nbytes = (size_t) ggml_nelements(parent) / blck_size * block_bytes;
cl_int err;
aos = clCreateBuffer(backend_ctx->context, CL_MEM_READ_WRITE, parent_nbytes, NULL, &err);
cl_mem aos = clCreateBuffer(backend_ctx->context, CL_MEM_READ_WRITE, parent_nbytes, NULL, &err);
CL_CHECK(err);
// large q4_0/q8_0 WEIGHTS are stored transposed and small weights
@@ -16802,6 +16751,9 @@ static cl_mem ggml_cl_mul_mat_dequant_quant_to_f16(
if (extra_reconstruct) {
*extra_reconstruct = aos;
} else {
// OpenCL retains the memobj while queued kernels reference it.
CL_CHECK(clReleaseMemObject(aos));
}
} else {
auto * extra = (ggml_tensor_extra_cl *) tensor->extra;
@@ -16865,13 +16817,6 @@ static cl_mem ggml_cl_mul_mat_dequant_quant_to_f16(
size_t lws[3] = { 1, 1, 1 };
CL_CHECK(clEnqueueNDRangeKernel(backend_ctx->queue, dq_kernel, 3, NULL, gws, lws, 0, NULL, NULL));
// release the reconstructed aos if
// 1. it was actually reconstructed
// 2. the caller didn't request it to be returned
// src_buf may refer to aos, so we should release after this enqueue
if (aos && !extra_reconstruct) {
CL_CHECK(clReleaseMemObject(aos));
}
return out;
}
@@ -19388,8 +19333,6 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_mem), &(backend_ctx->prealloc_total_tiles.buffer)));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne00));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne01));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_use_moe_ragged));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_moe_ragged_skip_gran));
// set thread grid
global_size[1] = static_cast<size_t>((ne01 + 63) / 64);
@@ -19616,8 +19559,6 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_mem), &(backend_ctx->prealloc_total_tiles.buffer)));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne00));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne01));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_use_moe_ragged));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_moe_ragged_skip_gran));
// set thread grid
global_size[1] = static_cast<size_t>((ne01 + 63) / 64);
@@ -19794,8 +19735,6 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_mem), &(backend_ctx->prealloc_total_tiles.buffer)));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne00));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne01));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_use_moe_ragged));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_moe_ragged_skip_gran));
// set thread grid
global_size[1] = static_cast<size_t>((ne01 + 63) / 64);
@@ -19973,8 +19912,6 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_mem), &(backend_ctx->prealloc_total_tiles.buffer)));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne00));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne01));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_use_moe_ragged));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_moe_ragged_skip_gran));
// set thread grid
global_size[1] = static_cast<size_t>((ne01 + 63) / 64);
@@ -20232,8 +20169,6 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_mem), &(backend_ctx->prealloc_total_tiles.buffer)));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne00));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne01));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_use_moe_ragged));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_moe_ragged_skip_gran));
// set thread grid
global_size[1] = static_cast<size_t>((ne01 + 63) / 64);
@@ -20412,8 +20347,6 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_mem), &(backend_ctx->prealloc_total_tiles.buffer)));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne00));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne01));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_use_moe_ragged));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_moe_ragged_skip_gran));
// set thread grid
global_size[1] = static_cast<size_t>((ne01 + 63) / 64);
@@ -20589,8 +20522,6 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_mem), &(backend_ctx->prealloc_total_tiles.buffer)));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne00));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne01));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_use_moe_ragged));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_moe_ragged_skip_gran));
// set thread grid
global_size[1] = static_cast<size_t>((ne01 + 63) / 64);
@@ -20774,8 +20705,6 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_mem), &(backend_ctx->prealloc_total_tiles.buffer)));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne00));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(int), &ne01));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_use_moe_ragged));
CL_CHECK(clSetKernelArg(kernel, arg_idx++, sizeof(cl_uint), &backend_ctx->adreno_moe_ragged_skip_gran));
// set thread grid
global_size[1] = static_cast<size_t>((ne01 + 63) / 64);
@@ -132,46 +132,6 @@ static inline half8 mxfp4_to_fp16_packed8(ushort2 fp4x8) {
c_reg.lo += convert_float8(acc.lo); \
c_reg.hi += convert_float8(acc.hi); \
// Quarter-tile variant: computes 8 output columns (one skip-group) into a float8
// accumulator. Same reduction order / flush cadence as dotx16_reduce8, so the
// non-skipped path is byte-identical; it just lets the caller skip empty
// 8-column groups at finer granularity. Uses a private half8 `acc8`.
#define dotx8_reduce4(a_reg, b_lm, c_reg, lm_offset) \
acc8.s0 = dot(a_reg.s0123, b_lm[lm_offset + 0]); \
acc8.s1 = dot(a_reg.s0123, b_lm[lm_offset + 1]); \
acc8.s2 = dot(a_reg.s0123, b_lm[lm_offset + 2]); \
acc8.s3 = dot(a_reg.s0123, b_lm[lm_offset + 3]); \
acc8.s4 = dot(a_reg.s0123, b_lm[lm_offset + 4]); \
acc8.s5 = dot(a_reg.s0123, b_lm[lm_offset + 5]); \
acc8.s6 = dot(a_reg.s0123, b_lm[lm_offset + 6]); \
acc8.s7 = dot(a_reg.s0123, b_lm[lm_offset + 7]); \
acc8.s0 += dot(a_reg.s4567, b_lm[lm_offset + 32]); \
acc8.s1 += dot(a_reg.s4567, b_lm[lm_offset + 33]); \
acc8.s2 += dot(a_reg.s4567, b_lm[lm_offset + 34]); \
acc8.s3 += dot(a_reg.s4567, b_lm[lm_offset + 35]); \
acc8.s4 += dot(a_reg.s4567, b_lm[lm_offset + 36]); \
acc8.s5 += dot(a_reg.s4567, b_lm[lm_offset + 37]); \
acc8.s6 += dot(a_reg.s4567, b_lm[lm_offset + 38]); \
acc8.s7 += dot(a_reg.s4567, b_lm[lm_offset + 39]); \
c_reg += convert_float8(acc8); \
acc8.s0 = dot(a_reg.s89ab, b_lm[lm_offset + 64]); \
acc8.s1 = dot(a_reg.s89ab, b_lm[lm_offset + 65]); \
acc8.s2 = dot(a_reg.s89ab, b_lm[lm_offset + 66]); \
acc8.s3 = dot(a_reg.s89ab, b_lm[lm_offset + 67]); \
acc8.s4 = dot(a_reg.s89ab, b_lm[lm_offset + 68]); \
acc8.s5 = dot(a_reg.s89ab, b_lm[lm_offset + 69]); \
acc8.s6 = dot(a_reg.s89ab, b_lm[lm_offset + 70]); \
acc8.s7 = dot(a_reg.s89ab, b_lm[lm_offset + 71]); \
acc8.s0 += dot(a_reg.scdef, b_lm[lm_offset + 96]); \
acc8.s1 += dot(a_reg.scdef, b_lm[lm_offset + 97]); \
acc8.s2 += dot(a_reg.scdef, b_lm[lm_offset + 98]); \
acc8.s3 += dot(a_reg.scdef, b_lm[lm_offset + 99]); \
acc8.s4 += dot(a_reg.scdef, b_lm[lm_offset + 100]); \
acc8.s5 += dot(a_reg.scdef, b_lm[lm_offset + 101]); \
acc8.s6 += dot(a_reg.scdef, b_lm[lm_offset + 102]); \
acc8.s7 += dot(a_reg.scdef, b_lm[lm_offset + 103]); \
c_reg += convert_float8(acc8); \
static inline half e8m0_to_fp16(uchar x) {
ushort bits;
@@ -197,9 +157,7 @@ kernel void kernel_gemm_moe_mxfp4_f32_ns(
__write_only image1d_buffer_t dst,
__global int * total_tiles,
uint ne00,
uint ne01,
uint is_ragged,
uint skip_gran
uint ne01
) {
uint block_id_m = get_global_id(1); // m_tile
uint block_id_n = get_global_id(2); // n_tile
@@ -209,28 +167,6 @@ kernel void kernel_gemm_moe_mxfp4_f32_ns(
return;
}
// Ragged tile-skip: when is_ragged and the upper 16 token-slots of this tile are all
// padding (router 0xFFFFFFFF), skip the second (reg_c.hi) dotx16_reduce8 half -> ~half
// the GEMM dot for sparse tiles. Numerically identical (the skipped lanes are padding).
// Ragged tile-skip: tokens are packed contiguously per expert (moe_scatter fills
// lanes 0..V-1, moe_fill pre-pads the rest), so router padding (0xFFFFFFFF) is always
// trailing. Find the valid-token count V and round it UP to the skip granularity
// skip_gran (columns per skip-group: 8 = quarter, 16 = half/legacy, 32 = disabled).
// A 8-column group g is all-padding iff its first column (8*g) >= n_active, so its
// dotx8_reduce4 is skipped. Numerically identical (skipped lanes are padding).
uint n_active = TILESIZE_N;
if (is_ragged && skip_gran < TILESIZE_N) {
uint n_valid = TILESIZE_N;
for (uint _t = 0; _t < TILESIZE_N; ++_t) {
if (src2[block_id_n * TILESIZE_N + _t] == 0xFFFFFFFFu) { n_valid = _t; break; }
}
n_active = min((uint)TILESIZE_N, ((n_valid + skip_gran - 1) / skip_gran) * skip_gran);
}
// Group 0 (cols 0-7) always runs; groups 1-3 skip when fully padding.
bool skip_g1 = (8u >= n_active);
bool skip_g2 = (16u >= n_active);
bool skip_g3 = (24u >= n_active);
__private half16 reg_a;
__private float32 reg_c = (float32)(0);
__local half4 shared_b[128];
@@ -280,11 +216,9 @@ kernel void kernel_gemm_moe_mxfp4_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
// 32 16x16 fp16 dot product with 8 elements reduction for better precision
half8 acc8;
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
half16 acc;
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
// Repeat for second sub-block
uint half_step = step + TILESIZE_K;
@@ -310,10 +244,8 @@ kernel void kernel_gemm_moe_mxfp4_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
// 32 16x16 fp16 dot product with 3-levels reduction for better precision
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
}
if ((get_global_id(0) + block_id_m * TILESIZE_M) >= ne01) {
@@ -98,46 +98,6 @@
c_reg.lo += convert_float8(acc.lo); \
c_reg.hi += convert_float8(acc.hi); \
// Quarter-tile variant: computes 8 output columns (one skip-group) into a float8
// accumulator. Same reduction order / flush cadence as dotx16_reduce8, so the
// non-skipped path is byte-identical; it just lets the caller skip empty
// 8-column groups at finer granularity. Uses a private half8 `acc8`.
#define dotx8_reduce4(a_reg, b_lm, c_reg, lm_offset) \
acc8.s0 = dot(a_reg.s0123, b_lm[lm_offset + 0]); \
acc8.s1 = dot(a_reg.s0123, b_lm[lm_offset + 1]); \
acc8.s2 = dot(a_reg.s0123, b_lm[lm_offset + 2]); \
acc8.s3 = dot(a_reg.s0123, b_lm[lm_offset + 3]); \
acc8.s4 = dot(a_reg.s0123, b_lm[lm_offset + 4]); \
acc8.s5 = dot(a_reg.s0123, b_lm[lm_offset + 5]); \
acc8.s6 = dot(a_reg.s0123, b_lm[lm_offset + 6]); \
acc8.s7 = dot(a_reg.s0123, b_lm[lm_offset + 7]); \
acc8.s0 += dot(a_reg.s4567, b_lm[lm_offset + 32]); \
acc8.s1 += dot(a_reg.s4567, b_lm[lm_offset + 33]); \
acc8.s2 += dot(a_reg.s4567, b_lm[lm_offset + 34]); \
acc8.s3 += dot(a_reg.s4567, b_lm[lm_offset + 35]); \
acc8.s4 += dot(a_reg.s4567, b_lm[lm_offset + 36]); \
acc8.s5 += dot(a_reg.s4567, b_lm[lm_offset + 37]); \
acc8.s6 += dot(a_reg.s4567, b_lm[lm_offset + 38]); \
acc8.s7 += dot(a_reg.s4567, b_lm[lm_offset + 39]); \
c_reg += convert_float8(acc8); \
acc8.s0 = dot(a_reg.s89ab, b_lm[lm_offset + 64]); \
acc8.s1 = dot(a_reg.s89ab, b_lm[lm_offset + 65]); \
acc8.s2 = dot(a_reg.s89ab, b_lm[lm_offset + 66]); \
acc8.s3 = dot(a_reg.s89ab, b_lm[lm_offset + 67]); \
acc8.s4 = dot(a_reg.s89ab, b_lm[lm_offset + 68]); \
acc8.s5 = dot(a_reg.s89ab, b_lm[lm_offset + 69]); \
acc8.s6 = dot(a_reg.s89ab, b_lm[lm_offset + 70]); \
acc8.s7 = dot(a_reg.s89ab, b_lm[lm_offset + 71]); \
acc8.s0 += dot(a_reg.scdef, b_lm[lm_offset + 96]); \
acc8.s1 += dot(a_reg.scdef, b_lm[lm_offset + 97]); \
acc8.s2 += dot(a_reg.scdef, b_lm[lm_offset + 98]); \
acc8.s3 += dot(a_reg.scdef, b_lm[lm_offset + 99]); \
acc8.s4 += dot(a_reg.scdef, b_lm[lm_offset + 100]); \
acc8.s5 += dot(a_reg.scdef, b_lm[lm_offset + 101]); \
acc8.s6 += dot(a_reg.scdef, b_lm[lm_offset + 102]); \
acc8.s7 += dot(a_reg.scdef, b_lm[lm_offset + 103]); \
c_reg += convert_float8(acc8); \
__attribute__((qcom_wave_pair_mode(1))) // 1=force single 2=force pair
kernel void kernel_gemm_moe_q4_0_f32_ns(
@@ -149,9 +109,7 @@ kernel void kernel_gemm_moe_q4_0_f32_ns(
__write_only image1d_buffer_t dst,
__global int * total_tiles,
uint ne00,
uint ne01,
uint is_ragged,
uint skip_gran
uint ne01
) {
uint block_id_m = get_global_id(1); // m_tile
uint block_id_n = get_global_id(2); // n_tile
@@ -161,28 +119,6 @@ kernel void kernel_gemm_moe_q4_0_f32_ns(
return;
}
// Ragged tile-skip: when is_ragged and the upper 16 token-slots of this tile are all
// padding (router 0xFFFFFFFF), skip the second (reg_c.hi) dotx16_reduce8 half -> ~half
// the GEMM dot for sparse tiles. Numerically identical (the skipped lanes are padding).
// Ragged tile-skip: tokens are packed contiguously per expert (moe_scatter fills
// lanes 0..V-1, moe_fill pre-pads the rest), so router padding (0xFFFFFFFF) is always
// trailing. Find the valid-token count V and round it UP to the skip granularity
// skip_gran (columns per skip-group: 8 = quarter, 16 = half/legacy, 32 = disabled).
// A 8-column group g is all-padding iff its first column (8*g) >= n_active, so its
// dotx8_reduce4 is skipped. Numerically identical (skipped lanes are padding).
uint n_active = TILESIZE_N;
if (is_ragged && skip_gran < TILESIZE_N) {
uint n_valid = TILESIZE_N;
for (uint _t = 0; _t < TILESIZE_N; ++_t) {
if (src2[block_id_n * TILESIZE_N + _t] == 0xFFFFFFFFu) { n_valid = _t; break; }
}
n_active = min((uint)TILESIZE_N, ((n_valid + skip_gran - 1) / skip_gran) * skip_gran);
}
// Group 0 (cols 0-7) always runs; groups 1-3 skip when fully padding.
bool skip_g1 = (8u >= n_active);
bool skip_g2 = (16u >= n_active);
bool skip_g3 = (24u >= n_active);
__private half16 reg_a;
__private float32 reg_c = (float32)(0);
__local half4 shared_b[128];
@@ -231,11 +167,9 @@ kernel void kernel_gemm_moe_q4_0_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
// 32 16x16 fp16 dot product with 8 elements reduction for better precision
half8 acc8;
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
half16 acc;
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
// Repeat for second sub-block
uint half_step = step + TILESIZE_K;
@@ -260,10 +194,8 @@ kernel void kernel_gemm_moe_q4_0_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
// 32 16x16 fp16 dot product with 3-levels reduction for better precision
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
}
if ((get_global_id(0) + block_id_m * TILESIZE_M) >= ne01) {
@@ -98,46 +98,6 @@
c_reg.lo += convert_float8(acc.lo); \
c_reg.hi += convert_float8(acc.hi); \
// Quarter-tile variant: computes 8 output columns (one skip-group) into a float8
// accumulator. Same reduction order / flush cadence as dotx16_reduce8, so the
// non-skipped path is byte-identical; it just lets the caller skip empty
// 8-column groups at finer granularity. Uses a private half8 `acc8`.
#define dotx8_reduce4(a_reg, b_lm, c_reg, lm_offset) \
acc8.s0 = dot(a_reg.s0123, b_lm[lm_offset + 0]); \
acc8.s1 = dot(a_reg.s0123, b_lm[lm_offset + 1]); \
acc8.s2 = dot(a_reg.s0123, b_lm[lm_offset + 2]); \
acc8.s3 = dot(a_reg.s0123, b_lm[lm_offset + 3]); \
acc8.s4 = dot(a_reg.s0123, b_lm[lm_offset + 4]); \
acc8.s5 = dot(a_reg.s0123, b_lm[lm_offset + 5]); \
acc8.s6 = dot(a_reg.s0123, b_lm[lm_offset + 6]); \
acc8.s7 = dot(a_reg.s0123, b_lm[lm_offset + 7]); \
acc8.s0 += dot(a_reg.s4567, b_lm[lm_offset + 32]); \
acc8.s1 += dot(a_reg.s4567, b_lm[lm_offset + 33]); \
acc8.s2 += dot(a_reg.s4567, b_lm[lm_offset + 34]); \
acc8.s3 += dot(a_reg.s4567, b_lm[lm_offset + 35]); \
acc8.s4 += dot(a_reg.s4567, b_lm[lm_offset + 36]); \
acc8.s5 += dot(a_reg.s4567, b_lm[lm_offset + 37]); \
acc8.s6 += dot(a_reg.s4567, b_lm[lm_offset + 38]); \
acc8.s7 += dot(a_reg.s4567, b_lm[lm_offset + 39]); \
c_reg += convert_float8(acc8); \
acc8.s0 = dot(a_reg.s89ab, b_lm[lm_offset + 64]); \
acc8.s1 = dot(a_reg.s89ab, b_lm[lm_offset + 65]); \
acc8.s2 = dot(a_reg.s89ab, b_lm[lm_offset + 66]); \
acc8.s3 = dot(a_reg.s89ab, b_lm[lm_offset + 67]); \
acc8.s4 = dot(a_reg.s89ab, b_lm[lm_offset + 68]); \
acc8.s5 = dot(a_reg.s89ab, b_lm[lm_offset + 69]); \
acc8.s6 = dot(a_reg.s89ab, b_lm[lm_offset + 70]); \
acc8.s7 = dot(a_reg.s89ab, b_lm[lm_offset + 71]); \
acc8.s0 += dot(a_reg.scdef, b_lm[lm_offset + 96]); \
acc8.s1 += dot(a_reg.scdef, b_lm[lm_offset + 97]); \
acc8.s2 += dot(a_reg.scdef, b_lm[lm_offset + 98]); \
acc8.s3 += dot(a_reg.scdef, b_lm[lm_offset + 99]); \
acc8.s4 += dot(a_reg.scdef, b_lm[lm_offset + 100]); \
acc8.s5 += dot(a_reg.scdef, b_lm[lm_offset + 101]); \
acc8.s6 += dot(a_reg.scdef, b_lm[lm_offset + 102]); \
acc8.s7 += dot(a_reg.scdef, b_lm[lm_offset + 103]); \
c_reg += convert_float8(acc8); \
__attribute__((qcom_wave_pair_mode(1))) // 1=force single 2=force pair
kernel void kernel_gemm_moe_q4_1_f32_ns(
@@ -150,9 +110,7 @@ kernel void kernel_gemm_moe_q4_1_f32_ns(
__write_only image1d_buffer_t dst,
__global int * total_tiles,
uint ne00,
uint ne01,
uint is_ragged,
uint skip_gran
uint ne01
) {
uint block_id_m = get_global_id(1); // m_tile
uint block_id_n = get_global_id(2); // n_tile
@@ -162,28 +120,6 @@ kernel void kernel_gemm_moe_q4_1_f32_ns(
return;
}
// Ragged tile-skip: when is_ragged and the upper 16 token-slots of this tile are all
// padding (router 0xFFFFFFFF), skip the second (reg_c.hi) dotx16_reduce8 half -> ~half
// the GEMM dot for sparse tiles. Numerically identical (the skipped lanes are padding).
// Ragged tile-skip: tokens are packed contiguously per expert (moe_scatter fills
// lanes 0..V-1, moe_fill pre-pads the rest), so router padding (0xFFFFFFFF) is always
// trailing. Find the valid-token count V and round it UP to the skip granularity
// skip_gran (columns per skip-group: 8 = quarter, 16 = half/legacy, 32 = disabled).
// A 8-column group g is all-padding iff its first column (8*g) >= n_active, so its
// dotx8_reduce4 is skipped. Numerically identical (skipped lanes are padding).
uint n_active = TILESIZE_N;
if (is_ragged && skip_gran < TILESIZE_N) {
uint n_valid = TILESIZE_N;
for (uint _t = 0; _t < TILESIZE_N; ++_t) {
if (src2[block_id_n * TILESIZE_N + _t] == 0xFFFFFFFFu) { n_valid = _t; break; }
}
n_active = min((uint)TILESIZE_N, ((n_valid + skip_gran - 1) / skip_gran) * skip_gran);
}
// Group 0 (cols 0-7) always runs; groups 1-3 skip when fully padding.
bool skip_g1 = (8u >= n_active);
bool skip_g2 = (16u >= n_active);
bool skip_g3 = (24u >= n_active);
__private half16 reg_a;
__private float32 reg_c = (float32)(0);
__local half4 shared_b[128];
@@ -233,11 +169,9 @@ kernel void kernel_gemm_moe_q4_1_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
// 32 16x16 fp16 dot product with 8 elements reduction for better precision
half8 acc8;
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
half16 acc;
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
// Repeat for second sub-block
uint half_step = step + TILESIZE_K;
@@ -262,10 +196,8 @@ kernel void kernel_gemm_moe_q4_1_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
// 32 16x16 fp16 dot product with 3-levels reduction for better precision
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
}
if ((get_global_id(0) + block_id_m * TILESIZE_M) >= ne01) {
@@ -114,46 +114,6 @@ inline void get_scale_min_k4(
c_reg.lo += convert_float8(acc.lo); \
c_reg.hi += convert_float8(acc.hi); \
// Quarter-tile variant: computes 8 output columns (one skip-group) into a float8
// accumulator. Same reduction order / flush cadence as dotx16_reduce8, so the
// non-skipped path is byte-identical; it just lets the caller skip empty
// 8-column groups at finer granularity. Uses a private half8 `acc8`.
#define dotx8_reduce4(a_reg, b_lm, c_reg, lm_offset) \
acc8.s0 = dot(a_reg.s0123, b_lm[lm_offset + 0]); \
acc8.s1 = dot(a_reg.s0123, b_lm[lm_offset + 1]); \
acc8.s2 = dot(a_reg.s0123, b_lm[lm_offset + 2]); \
acc8.s3 = dot(a_reg.s0123, b_lm[lm_offset + 3]); \
acc8.s4 = dot(a_reg.s0123, b_lm[lm_offset + 4]); \
acc8.s5 = dot(a_reg.s0123, b_lm[lm_offset + 5]); \
acc8.s6 = dot(a_reg.s0123, b_lm[lm_offset + 6]); \
acc8.s7 = dot(a_reg.s0123, b_lm[lm_offset + 7]); \
acc8.s0 += dot(a_reg.s4567, b_lm[lm_offset + 32]); \
acc8.s1 += dot(a_reg.s4567, b_lm[lm_offset + 33]); \
acc8.s2 += dot(a_reg.s4567, b_lm[lm_offset + 34]); \
acc8.s3 += dot(a_reg.s4567, b_lm[lm_offset + 35]); \
acc8.s4 += dot(a_reg.s4567, b_lm[lm_offset + 36]); \
acc8.s5 += dot(a_reg.s4567, b_lm[lm_offset + 37]); \
acc8.s6 += dot(a_reg.s4567, b_lm[lm_offset + 38]); \
acc8.s7 += dot(a_reg.s4567, b_lm[lm_offset + 39]); \
c_reg += convert_float8(acc8); \
acc8.s0 = dot(a_reg.s89ab, b_lm[lm_offset + 64]); \
acc8.s1 = dot(a_reg.s89ab, b_lm[lm_offset + 65]); \
acc8.s2 = dot(a_reg.s89ab, b_lm[lm_offset + 66]); \
acc8.s3 = dot(a_reg.s89ab, b_lm[lm_offset + 67]); \
acc8.s4 = dot(a_reg.s89ab, b_lm[lm_offset + 68]); \
acc8.s5 = dot(a_reg.s89ab, b_lm[lm_offset + 69]); \
acc8.s6 = dot(a_reg.s89ab, b_lm[lm_offset + 70]); \
acc8.s7 = dot(a_reg.s89ab, b_lm[lm_offset + 71]); \
acc8.s0 += dot(a_reg.scdef, b_lm[lm_offset + 96]); \
acc8.s1 += dot(a_reg.scdef, b_lm[lm_offset + 97]); \
acc8.s2 += dot(a_reg.scdef, b_lm[lm_offset + 98]); \
acc8.s3 += dot(a_reg.scdef, b_lm[lm_offset + 99]); \
acc8.s4 += dot(a_reg.scdef, b_lm[lm_offset + 100]); \
acc8.s5 += dot(a_reg.scdef, b_lm[lm_offset + 101]); \
acc8.s6 += dot(a_reg.scdef, b_lm[lm_offset + 102]); \
acc8.s7 += dot(a_reg.scdef, b_lm[lm_offset + 103]); \
c_reg += convert_float8(acc8); \
__attribute__((qcom_wave_pair_mode(1)))
kernel void kernel_gemm_moe_q4_k_f32_ns(
@@ -167,9 +127,7 @@ kernel void kernel_gemm_moe_q4_k_f32_ns(
__write_only image1d_buffer_t dst,
__global int * total_tiles,
uint ne00,
uint ne01,
uint is_ragged,
uint skip_gran
uint ne01
) {
uint block_id_m = get_global_id(1); // m_tile
uint block_id_n = get_global_id(2); // n_tile
@@ -179,25 +137,6 @@ kernel void kernel_gemm_moe_q4_k_f32_ns(
return;
}
// Ragged tile-skip: tokens are packed contiguously per expert (moe_scatter fills
// lanes 0..V-1, moe_fill pre-pads the rest), so router padding (0xFFFFFFFF) is always
// trailing. Find the valid-token count V and round it UP to the skip granularity
// skip_gran (columns per skip-group: 8 = quarter, 16 = half/legacy, 32 = disabled).
// A 8-column group g is all-padding iff its first column (8*g) >= n_active, so its
// dotx8_reduce4 is skipped. Numerically identical (skipped lanes are padding).
uint n_active = TILESIZE_N;
if (is_ragged && skip_gran < TILESIZE_N) {
uint n_valid = TILESIZE_N;
for (uint _t = 0; _t < TILESIZE_N; ++_t) {
if (src2[block_id_n * TILESIZE_N + _t] == 0xFFFFFFFFu) { n_valid = _t; break; }
}
n_active = min((uint)TILESIZE_N, ((n_valid + skip_gran - 1) / skip_gran) * skip_gran);
}
// Group 0 (cols 0-7) always runs; groups 1-3 skip when fully padding.
bool skip_g1 = (8u >= n_active);
bool skip_g2 = (16u >= n_active);
bool skip_g3 = (24u >= n_active);
__private half16 reg_a;
__private float32 reg_c = (float32)(0);
__local half4 shared_b[128];
@@ -260,11 +199,9 @@ kernel void kernel_gemm_moe_q4_k_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
half8 acc8;
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
half16 acc;
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
// Second half (next 16 elements, same sub-block scale)
uint half_step = step + TILESIZE_K;
@@ -284,10 +221,8 @@ kernel void kernel_gemm_moe_q4_k_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
}
if ((get_global_id(0) + block_id_m * TILESIZE_M) >= ne01) {
@@ -98,46 +98,6 @@
c_reg.lo += convert_float8(acc.lo); \
c_reg.hi += convert_float8(acc.hi); \
// Quarter-tile variant: computes 8 output columns (one skip-group) into a float8
// accumulator. Same reduction order / flush cadence as dotx16_reduce8, so the
// non-skipped path is byte-identical; it just lets the caller skip empty
// 8-column groups at finer granularity. Uses a private half8 `acc8`.
#define dotx8_reduce4(a_reg, b_lm, c_reg, lm_offset) \
acc8.s0 = dot(a_reg.s0123, b_lm[lm_offset + 0]); \
acc8.s1 = dot(a_reg.s0123, b_lm[lm_offset + 1]); \
acc8.s2 = dot(a_reg.s0123, b_lm[lm_offset + 2]); \
acc8.s3 = dot(a_reg.s0123, b_lm[lm_offset + 3]); \
acc8.s4 = dot(a_reg.s0123, b_lm[lm_offset + 4]); \
acc8.s5 = dot(a_reg.s0123, b_lm[lm_offset + 5]); \
acc8.s6 = dot(a_reg.s0123, b_lm[lm_offset + 6]); \
acc8.s7 = dot(a_reg.s0123, b_lm[lm_offset + 7]); \
acc8.s0 += dot(a_reg.s4567, b_lm[lm_offset + 32]); \
acc8.s1 += dot(a_reg.s4567, b_lm[lm_offset + 33]); \
acc8.s2 += dot(a_reg.s4567, b_lm[lm_offset + 34]); \
acc8.s3 += dot(a_reg.s4567, b_lm[lm_offset + 35]); \
acc8.s4 += dot(a_reg.s4567, b_lm[lm_offset + 36]); \
acc8.s5 += dot(a_reg.s4567, b_lm[lm_offset + 37]); \
acc8.s6 += dot(a_reg.s4567, b_lm[lm_offset + 38]); \
acc8.s7 += dot(a_reg.s4567, b_lm[lm_offset + 39]); \
c_reg += convert_float8(acc8); \
acc8.s0 = dot(a_reg.s89ab, b_lm[lm_offset + 64]); \
acc8.s1 = dot(a_reg.s89ab, b_lm[lm_offset + 65]); \
acc8.s2 = dot(a_reg.s89ab, b_lm[lm_offset + 66]); \
acc8.s3 = dot(a_reg.s89ab, b_lm[lm_offset + 67]); \
acc8.s4 = dot(a_reg.s89ab, b_lm[lm_offset + 68]); \
acc8.s5 = dot(a_reg.s89ab, b_lm[lm_offset + 69]); \
acc8.s6 = dot(a_reg.s89ab, b_lm[lm_offset + 70]); \
acc8.s7 = dot(a_reg.s89ab, b_lm[lm_offset + 71]); \
acc8.s0 += dot(a_reg.scdef, b_lm[lm_offset + 96]); \
acc8.s1 += dot(a_reg.scdef, b_lm[lm_offset + 97]); \
acc8.s2 += dot(a_reg.scdef, b_lm[lm_offset + 98]); \
acc8.s3 += dot(a_reg.scdef, b_lm[lm_offset + 99]); \
acc8.s4 += dot(a_reg.scdef, b_lm[lm_offset + 100]); \
acc8.s5 += dot(a_reg.scdef, b_lm[lm_offset + 101]); \
acc8.s6 += dot(a_reg.scdef, b_lm[lm_offset + 102]); \
acc8.s7 += dot(a_reg.scdef, b_lm[lm_offset + 103]); \
c_reg += convert_float8(acc8); \
__attribute__((qcom_wave_pair_mode(1))) // 1=force single 2=force pair
kernel void kernel_gemm_moe_q5_0_f32_ns(
@@ -150,9 +110,7 @@ kernel void kernel_gemm_moe_q5_0_f32_ns(
__write_only image1d_buffer_t dst,
__global int * total_tiles,
uint ne00,
uint ne01,
uint is_ragged,
uint skip_gran
uint ne01
) {
uint block_id_m = get_global_id(1); // m_tile
uint block_id_n = get_global_id(2); // n_tile
@@ -162,28 +120,6 @@ kernel void kernel_gemm_moe_q5_0_f32_ns(
return;
}
// Ragged tile-skip: when is_ragged and the upper 16 token-slots of this tile are all
// padding (router 0xFFFFFFFF), skip the second (reg_c.hi) dotx16_reduce8 half -> ~half
// the GEMM dot for sparse tiles. Numerically identical (the skipped lanes are padding).
// Ragged tile-skip: tokens are packed contiguously per expert (moe_scatter fills
// lanes 0..V-1, moe_fill pre-pads the rest), so router padding (0xFFFFFFFF) is always
// trailing. Find the valid-token count V and round it UP to the skip granularity
// skip_gran (columns per skip-group: 8 = quarter, 16 = half/legacy, 32 = disabled).
// A 8-column group g is all-padding iff its first column (8*g) >= n_active, so its
// dotx8_reduce4 is skipped. Numerically identical (skipped lanes are padding).
uint n_active = TILESIZE_N;
if (is_ragged && skip_gran < TILESIZE_N) {
uint n_valid = TILESIZE_N;
for (uint _t = 0; _t < TILESIZE_N; ++_t) {
if (src2[block_id_n * TILESIZE_N + _t] == 0xFFFFFFFFu) { n_valid = _t; break; }
}
n_active = min((uint)TILESIZE_N, ((n_valid + skip_gran - 1) / skip_gran) * skip_gran);
}
// Group 0 (cols 0-7) always runs; groups 1-3 skip when fully padding.
bool skip_g1 = (8u >= n_active);
bool skip_g2 = (16u >= n_active);
bool skip_g3 = (24u >= n_active);
__private half16 reg_a;
__private float32 reg_c = (float32)(0);
__local half4 shared_b[128];
@@ -235,11 +171,9 @@ kernel void kernel_gemm_moe_q5_0_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
// 32 16x16 fp16 dot product with 8 elements reduction for better precision
half8 acc8;
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
half16 acc;
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
// Repeat for second sub-block
uint half_step = step + TILESIZE_K;
@@ -264,10 +198,8 @@ kernel void kernel_gemm_moe_q5_0_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
// 32 16x16 fp16 dot product with 3-levels reduction for better precision
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
}
if ((get_global_id(0) + block_id_m * TILESIZE_M) >= ne01) {
@@ -98,46 +98,6 @@
c_reg.lo += convert_float8(acc.lo); \
c_reg.hi += convert_float8(acc.hi); \
// Quarter-tile variant: computes 8 output columns (one skip-group) into a float8
// accumulator. Same reduction order / flush cadence as dotx16_reduce8, so the
// non-skipped path is byte-identical; it just lets the caller skip empty
// 8-column groups at finer granularity. Uses a private half8 `acc8`.
#define dotx8_reduce4(a_reg, b_lm, c_reg, lm_offset) \
acc8.s0 = dot(a_reg.s0123, b_lm[lm_offset + 0]); \
acc8.s1 = dot(a_reg.s0123, b_lm[lm_offset + 1]); \
acc8.s2 = dot(a_reg.s0123, b_lm[lm_offset + 2]); \
acc8.s3 = dot(a_reg.s0123, b_lm[lm_offset + 3]); \
acc8.s4 = dot(a_reg.s0123, b_lm[lm_offset + 4]); \
acc8.s5 = dot(a_reg.s0123, b_lm[lm_offset + 5]); \
acc8.s6 = dot(a_reg.s0123, b_lm[lm_offset + 6]); \
acc8.s7 = dot(a_reg.s0123, b_lm[lm_offset + 7]); \
acc8.s0 += dot(a_reg.s4567, b_lm[lm_offset + 32]); \
acc8.s1 += dot(a_reg.s4567, b_lm[lm_offset + 33]); \
acc8.s2 += dot(a_reg.s4567, b_lm[lm_offset + 34]); \
acc8.s3 += dot(a_reg.s4567, b_lm[lm_offset + 35]); \
acc8.s4 += dot(a_reg.s4567, b_lm[lm_offset + 36]); \
acc8.s5 += dot(a_reg.s4567, b_lm[lm_offset + 37]); \
acc8.s6 += dot(a_reg.s4567, b_lm[lm_offset + 38]); \
acc8.s7 += dot(a_reg.s4567, b_lm[lm_offset + 39]); \
c_reg += convert_float8(acc8); \
acc8.s0 = dot(a_reg.s89ab, b_lm[lm_offset + 64]); \
acc8.s1 = dot(a_reg.s89ab, b_lm[lm_offset + 65]); \
acc8.s2 = dot(a_reg.s89ab, b_lm[lm_offset + 66]); \
acc8.s3 = dot(a_reg.s89ab, b_lm[lm_offset + 67]); \
acc8.s4 = dot(a_reg.s89ab, b_lm[lm_offset + 68]); \
acc8.s5 = dot(a_reg.s89ab, b_lm[lm_offset + 69]); \
acc8.s6 = dot(a_reg.s89ab, b_lm[lm_offset + 70]); \
acc8.s7 = dot(a_reg.s89ab, b_lm[lm_offset + 71]); \
acc8.s0 += dot(a_reg.scdef, b_lm[lm_offset + 96]); \
acc8.s1 += dot(a_reg.scdef, b_lm[lm_offset + 97]); \
acc8.s2 += dot(a_reg.scdef, b_lm[lm_offset + 98]); \
acc8.s3 += dot(a_reg.scdef, b_lm[lm_offset + 99]); \
acc8.s4 += dot(a_reg.scdef, b_lm[lm_offset + 100]); \
acc8.s5 += dot(a_reg.scdef, b_lm[lm_offset + 101]); \
acc8.s6 += dot(a_reg.scdef, b_lm[lm_offset + 102]); \
acc8.s7 += dot(a_reg.scdef, b_lm[lm_offset + 103]); \
c_reg += convert_float8(acc8); \
__attribute__((qcom_wave_pair_mode(1))) // 1=force single 2=force pair
kernel void kernel_gemm_moe_q5_1_f32_ns(
@@ -151,9 +111,7 @@ kernel void kernel_gemm_moe_q5_1_f32_ns(
__write_only image1d_buffer_t dst,
__global int * total_tiles,
uint ne00,
uint ne01,
uint is_ragged,
uint skip_gran
uint ne01
) {
uint block_id_m = get_global_id(1); // m_tile
uint block_id_n = get_global_id(2); // n_tile
@@ -163,28 +121,6 @@ kernel void kernel_gemm_moe_q5_1_f32_ns(
return;
}
// Ragged tile-skip: when is_ragged and the upper 16 token-slots of this tile are all
// padding (router 0xFFFFFFFF), skip the second (reg_c.hi) dotx16_reduce8 half -> ~half
// the GEMM dot for sparse tiles. Numerically identical (the skipped lanes are padding).
// Ragged tile-skip: tokens are packed contiguously per expert (moe_scatter fills
// lanes 0..V-1, moe_fill pre-pads the rest), so router padding (0xFFFFFFFF) is always
// trailing. Find the valid-token count V and round it UP to the skip granularity
// skip_gran (columns per skip-group: 8 = quarter, 16 = half/legacy, 32 = disabled).
// A 8-column group g is all-padding iff its first column (8*g) >= n_active, so its
// dotx8_reduce4 is skipped. Numerically identical (skipped lanes are padding).
uint n_active = TILESIZE_N;
if (is_ragged && skip_gran < TILESIZE_N) {
uint n_valid = TILESIZE_N;
for (uint _t = 0; _t < TILESIZE_N; ++_t) {
if (src2[block_id_n * TILESIZE_N + _t] == 0xFFFFFFFFu) { n_valid = _t; break; }
}
n_active = min((uint)TILESIZE_N, ((n_valid + skip_gran - 1) / skip_gran) * skip_gran);
}
// Group 0 (cols 0-7) always runs; groups 1-3 skip when fully padding.
bool skip_g1 = (8u >= n_active);
bool skip_g2 = (16u >= n_active);
bool skip_g3 = (24u >= n_active);
__private half16 reg_a;
__private float32 reg_c = (float32)(0);
__local half4 shared_b[128];
@@ -237,11 +173,9 @@ kernel void kernel_gemm_moe_q5_1_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
// 32 16x16 fp16 dot product with 8 elements reduction for better precision
half8 acc8;
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
half16 acc;
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
// Repeat for second sub-block
uint half_step = step + TILESIZE_K;
@@ -266,10 +200,8 @@ kernel void kernel_gemm_moe_q5_1_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
// 32 16x16 fp16 dot product with 3-levels reduction for better precision
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
}
if ((get_global_id(0) + block_id_m * TILESIZE_M) >= ne01) {
@@ -114,46 +114,6 @@ inline void get_scale_min_k4(
c_reg.lo += convert_float8(acc.lo); \
c_reg.hi += convert_float8(acc.hi); \
// Quarter-tile variant: computes 8 output columns (one skip-group) into a float8
// accumulator. Same reduction order / flush cadence as dotx16_reduce8, so the
// non-skipped path is byte-identical; it just lets the caller skip empty
// 8-column groups at finer granularity. Uses a private half8 `acc8`.
#define dotx8_reduce4(a_reg, b_lm, c_reg, lm_offset) \
acc8.s0 = dot(a_reg.s0123, b_lm[lm_offset + 0]); \
acc8.s1 = dot(a_reg.s0123, b_lm[lm_offset + 1]); \
acc8.s2 = dot(a_reg.s0123, b_lm[lm_offset + 2]); \
acc8.s3 = dot(a_reg.s0123, b_lm[lm_offset + 3]); \
acc8.s4 = dot(a_reg.s0123, b_lm[lm_offset + 4]); \
acc8.s5 = dot(a_reg.s0123, b_lm[lm_offset + 5]); \
acc8.s6 = dot(a_reg.s0123, b_lm[lm_offset + 6]); \
acc8.s7 = dot(a_reg.s0123, b_lm[lm_offset + 7]); \
acc8.s0 += dot(a_reg.s4567, b_lm[lm_offset + 32]); \
acc8.s1 += dot(a_reg.s4567, b_lm[lm_offset + 33]); \
acc8.s2 += dot(a_reg.s4567, b_lm[lm_offset + 34]); \
acc8.s3 += dot(a_reg.s4567, b_lm[lm_offset + 35]); \
acc8.s4 += dot(a_reg.s4567, b_lm[lm_offset + 36]); \
acc8.s5 += dot(a_reg.s4567, b_lm[lm_offset + 37]); \
acc8.s6 += dot(a_reg.s4567, b_lm[lm_offset + 38]); \
acc8.s7 += dot(a_reg.s4567, b_lm[lm_offset + 39]); \
c_reg += convert_float8(acc8); \
acc8.s0 = dot(a_reg.s89ab, b_lm[lm_offset + 64]); \
acc8.s1 = dot(a_reg.s89ab, b_lm[lm_offset + 65]); \
acc8.s2 = dot(a_reg.s89ab, b_lm[lm_offset + 66]); \
acc8.s3 = dot(a_reg.s89ab, b_lm[lm_offset + 67]); \
acc8.s4 = dot(a_reg.s89ab, b_lm[lm_offset + 68]); \
acc8.s5 = dot(a_reg.s89ab, b_lm[lm_offset + 69]); \
acc8.s6 = dot(a_reg.s89ab, b_lm[lm_offset + 70]); \
acc8.s7 = dot(a_reg.s89ab, b_lm[lm_offset + 71]); \
acc8.s0 += dot(a_reg.scdef, b_lm[lm_offset + 96]); \
acc8.s1 += dot(a_reg.scdef, b_lm[lm_offset + 97]); \
acc8.s2 += dot(a_reg.scdef, b_lm[lm_offset + 98]); \
acc8.s3 += dot(a_reg.scdef, b_lm[lm_offset + 99]); \
acc8.s4 += dot(a_reg.scdef, b_lm[lm_offset + 100]); \
acc8.s5 += dot(a_reg.scdef, b_lm[lm_offset + 101]); \
acc8.s6 += dot(a_reg.scdef, b_lm[lm_offset + 102]); \
acc8.s7 += dot(a_reg.scdef, b_lm[lm_offset + 103]); \
c_reg += convert_float8(acc8); \
__attribute__((qcom_wave_pair_mode(1)))
kernel void kernel_gemm_moe_q5_k_f32_ns(
@@ -168,9 +128,7 @@ kernel void kernel_gemm_moe_q5_k_f32_ns(
__write_only image1d_buffer_t dst,
__global int * total_tiles,
uint ne00,
uint ne01,
uint is_ragged,
uint skip_gran
uint ne01
) {
uint block_id_m = get_global_id(1); // m_tile
uint block_id_n = get_global_id(2); // n_tile
@@ -180,28 +138,6 @@ kernel void kernel_gemm_moe_q5_k_f32_ns(
return;
}
// Ragged tile-skip: when is_ragged and the upper 16 token-slots of this tile are all
// padding (router 0xFFFFFFFF), skip the second (reg_c.hi) dotx16_reduce8 half -> ~half
// the GEMM dot for sparse tiles. Numerically identical (the skipped lanes are padding).
// Ragged tile-skip: tokens are packed contiguously per expert (moe_scatter fills
// lanes 0..V-1, moe_fill pre-pads the rest), so router padding (0xFFFFFFFF) is always
// trailing. Find the valid-token count V and round it UP to the skip granularity
// skip_gran (columns per skip-group: 8 = quarter, 16 = half/legacy, 32 = disabled).
// A 8-column group g is all-padding iff its first column (8*g) >= n_active, so its
// dotx8_reduce4 is skipped. Numerically identical (skipped lanes are padding).
uint n_active = TILESIZE_N;
if (is_ragged && skip_gran < TILESIZE_N) {
uint n_valid = TILESIZE_N;
for (uint _t = 0; _t < TILESIZE_N; ++_t) {
if (src2[block_id_n * TILESIZE_N + _t] == 0xFFFFFFFFu) { n_valid = _t; break; }
}
n_active = min((uint)TILESIZE_N, ((n_valid + skip_gran - 1) / skip_gran) * skip_gran);
}
// Group 0 (cols 0-7) always runs; groups 1-3 skip when fully padding.
bool skip_g1 = (8u >= n_active);
bool skip_g2 = (16u >= n_active);
bool skip_g3 = (24u >= n_active);
__private half16 reg_a;
__private float32 reg_c = (float32)(0);
__local half4 shared_b[128];
@@ -268,11 +204,9 @@ kernel void kernel_gemm_moe_q5_k_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
half8 acc8;
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
half16 acc;
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
// Second half
uint half_step = step + TILESIZE_K;
@@ -292,10 +226,8 @@ kernel void kernel_gemm_moe_q5_k_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
}
if ((get_global_id(0) + block_id_m * TILESIZE_M) >= ne01) {
@@ -98,46 +98,6 @@
c_reg.lo += convert_float8(acc.lo); \
c_reg.hi += convert_float8(acc.hi); \
// Quarter-tile variant: computes 8 output columns (one skip-group) into a float8
// accumulator. Same reduction order / flush cadence as dotx16_reduce8, so the
// non-skipped path is byte-identical; it just lets the caller skip empty
// 8-column groups at finer granularity. Uses a private half8 `acc8`.
#define dotx8_reduce4(a_reg, b_lm, c_reg, lm_offset) \
acc8.s0 = dot(a_reg.s0123, b_lm[lm_offset + 0]); \
acc8.s1 = dot(a_reg.s0123, b_lm[lm_offset + 1]); \
acc8.s2 = dot(a_reg.s0123, b_lm[lm_offset + 2]); \
acc8.s3 = dot(a_reg.s0123, b_lm[lm_offset + 3]); \
acc8.s4 = dot(a_reg.s0123, b_lm[lm_offset + 4]); \
acc8.s5 = dot(a_reg.s0123, b_lm[lm_offset + 5]); \
acc8.s6 = dot(a_reg.s0123, b_lm[lm_offset + 6]); \
acc8.s7 = dot(a_reg.s0123, b_lm[lm_offset + 7]); \
acc8.s0 += dot(a_reg.s4567, b_lm[lm_offset + 32]); \
acc8.s1 += dot(a_reg.s4567, b_lm[lm_offset + 33]); \
acc8.s2 += dot(a_reg.s4567, b_lm[lm_offset + 34]); \
acc8.s3 += dot(a_reg.s4567, b_lm[lm_offset + 35]); \
acc8.s4 += dot(a_reg.s4567, b_lm[lm_offset + 36]); \
acc8.s5 += dot(a_reg.s4567, b_lm[lm_offset + 37]); \
acc8.s6 += dot(a_reg.s4567, b_lm[lm_offset + 38]); \
acc8.s7 += dot(a_reg.s4567, b_lm[lm_offset + 39]); \
c_reg += convert_float8(acc8); \
acc8.s0 = dot(a_reg.s89ab, b_lm[lm_offset + 64]); \
acc8.s1 = dot(a_reg.s89ab, b_lm[lm_offset + 65]); \
acc8.s2 = dot(a_reg.s89ab, b_lm[lm_offset + 66]); \
acc8.s3 = dot(a_reg.s89ab, b_lm[lm_offset + 67]); \
acc8.s4 = dot(a_reg.s89ab, b_lm[lm_offset + 68]); \
acc8.s5 = dot(a_reg.s89ab, b_lm[lm_offset + 69]); \
acc8.s6 = dot(a_reg.s89ab, b_lm[lm_offset + 70]); \
acc8.s7 = dot(a_reg.s89ab, b_lm[lm_offset + 71]); \
acc8.s0 += dot(a_reg.scdef, b_lm[lm_offset + 96]); \
acc8.s1 += dot(a_reg.scdef, b_lm[lm_offset + 97]); \
acc8.s2 += dot(a_reg.scdef, b_lm[lm_offset + 98]); \
acc8.s3 += dot(a_reg.scdef, b_lm[lm_offset + 99]); \
acc8.s4 += dot(a_reg.scdef, b_lm[lm_offset + 100]); \
acc8.s5 += dot(a_reg.scdef, b_lm[lm_offset + 101]); \
acc8.s6 += dot(a_reg.scdef, b_lm[lm_offset + 102]); \
acc8.s7 += dot(a_reg.scdef, b_lm[lm_offset + 103]); \
c_reg += convert_float8(acc8); \
__attribute__((qcom_wave_pair_mode(1)))
kernel void kernel_gemm_moe_q6_k_f32_ns(
@@ -151,9 +111,7 @@ kernel void kernel_gemm_moe_q6_k_f32_ns(
__write_only image1d_buffer_t dst,
__global int * total_tiles,
uint ne00,
uint ne01,
uint is_ragged,
uint skip_gran
uint ne01
) {
uint block_id_m = get_global_id(1); // m_tile
uint block_id_n = get_global_id(2); // n_tile
@@ -163,28 +121,6 @@ kernel void kernel_gemm_moe_q6_k_f32_ns(
return;
}
// Ragged tile-skip: when is_ragged and the upper 16 token-slots of this tile are all
// padding (router 0xFFFFFFFF), skip the second (reg_c.hi) dotx16_reduce8 half -> ~half
// the GEMM dot for sparse tiles. Numerically identical (the skipped lanes are padding).
// Ragged tile-skip: tokens are packed contiguously per expert (moe_scatter fills
// lanes 0..V-1, moe_fill pre-pads the rest), so router padding (0xFFFFFFFF) is always
// trailing. Find the valid-token count V and round it UP to the skip granularity
// skip_gran (columns per skip-group: 8 = quarter, 16 = half/legacy, 32 = disabled).
// A 8-column group g is all-padding iff its first column (8*g) >= n_active, so its
// dotx8_reduce4 is skipped. Numerically identical (skipped lanes are padding).
uint n_active = TILESIZE_N;
if (is_ragged && skip_gran < TILESIZE_N) {
uint n_valid = TILESIZE_N;
for (uint _t = 0; _t < TILESIZE_N; ++_t) {
if (src2[block_id_n * TILESIZE_N + _t] == 0xFFFFFFFFu) { n_valid = _t; break; }
}
n_active = min((uint)TILESIZE_N, ((n_valid + skip_gran - 1) / skip_gran) * skip_gran);
}
// Group 0 (cols 0-7) always runs; groups 1-3 skip when fully padding.
bool skip_g1 = (8u >= n_active);
bool skip_g2 = (16u >= n_active);
bool skip_g3 = (24u >= n_active);
__private half16 reg_a;
__private float32 reg_c = (float32)(0);
__local half4 shared_b[128];
@@ -247,11 +183,9 @@ kernel void kernel_gemm_moe_q6_k_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
half8 acc8;
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
half16 acc;
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
// Second half
uint half_step = step + TILESIZE_K;
@@ -271,10 +205,8 @@ kernel void kernel_gemm_moe_q6_k_f32_ns(
sub_group_barrier(CLK_LOCAL_MEM_FENCE);
dotx8_reduce4(reg_a, shared_b, reg_c.lo.lo, 0);
if (!skip_g1) { dotx8_reduce4(reg_a, shared_b, reg_c.lo.hi, 8); }
if (!skip_g2) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.lo, 16); }
if (!skip_g3) { dotx8_reduce4(reg_a, shared_b, reg_c.hi.hi, 24); }
dotx16_reduce8(reg_a, shared_b, reg_c.lo, 0);
dotx16_reduce8(reg_a, shared_b, reg_c.hi, 16);
}
if ((get_global_id(0) + block_id_m * TILESIZE_M) >= ne01) {
+2 -14
View File
@@ -10310,8 +10310,7 @@ static void ggml_vk_flash_attn(ggml_backend_vk_context * ctx, vk_context& subctx
}
// Only use mask opt when the mask is fairly large. This hasn't been tuned extensively.
bool use_mask_opt = mask && nem1 >= 32 && nem0 * nem1 > 32768 && nem0 >= tuning_params.block_cols * 16
&& (ctx->device->architecture != vk_device_architecture::AMD_GCN || HSK > 256 || HSV > 256);
bool use_mask_opt = mask && nem1 >= 32 && nem0 * nem1 > 32768 && nem0 >= tuning_params.block_cols * 16;
vk_fa_pipeline_state fa_pipeline_state = get_fa_pipeline_state(ctx->device, tuning_params, HSK, HSV, aligned, f32acc,
mask != nullptr, use_mask_opt, logit_softcap != 0, k->type, v->type);
@@ -16309,18 +16308,7 @@ static ggml_status ggml_backend_vk_graph_compute(ggml_backend_t backend, ggml_cg
uint32_t submit_count = 0;
uint64_t batch_flops = 0;
uint64_t total_flops = 0;
uint64_t flops_cap = 200'000'000'000ULL;
// On weaker AMD GPUs larger submissions can hit a driver timeout, submit more often to avoid this
if (ctx->device->vendor_id == VK_VENDOR_ID_AMD && ctx->device->shader_core_count > 0) {
if (ctx->device->architecture == AMD_GCN && ctx->device->shader_core_count < 32) {
flops_cap = 500'000'000ULL * ctx->device->shader_core_count;
} else if (ctx->device->architecture != AMD_GCN && ctx->device->shader_core_count < 24) {
flops_cap = 2'000'000'000ULL * ctx->device->shader_core_count;
}
}
uint64_t flops_per_submit = std::min(flops_cap, ctx->last_total_flops / 40u);
uint64_t flops_per_submit = std::min(uint64_t(200'000'000'000), ctx->last_total_flops / 40u);
for (int i = 0; i < cgraph->n_nodes; i++) {
if (first_node_in_batch) {
submit_node_idx = i;
+1 -1
View File
@@ -3926,7 +3926,7 @@ struct ggml_tensor * ggml_set_rows(
GGML_ASSERT(b->ne[2] % c->ne[1] == 0);
GGML_ASSERT(b->ne[3] % c->ne[2] == 0);
GGML_ASSERT(c->ne[3] == 1);
GGML_ASSERT(b->type == GGML_TYPE_F32 || b->type == GGML_TYPE_F16);
GGML_ASSERT(b->type == GGML_TYPE_F32);
GGML_ASSERT(c->type == GGML_TYPE_I64 || c->type == GGML_TYPE_I32);
GGML_ASSERT(ggml_is_contiguous_rows(a));
+4 -72
View File
@@ -379,8 +379,6 @@ bool llama_batch_allocr::init(
LLAMA_LOG_ERROR("%s: sequence %d positions are decreasing (not allowed)\n", __func__, seq_id);
return false;
}
cur_seq_pos[seq_id] = pos;
}
}
}
@@ -507,7 +505,7 @@ llama_ubatch llama_batch_allocr::split_simple(uint32_t n_ubatch) {
return ubatch_add(idxs, idxs.size(), false);
}
llama_ubatch llama_batch_allocr::split_equal(uint32_t n_ubatch, bool sequential, uint32_t n_keep_tail) {
llama_ubatch llama_batch_allocr::split_equal(uint32_t n_ubatch, bool sequential) {
if (sequential && has_cpl) {
LLAMA_LOG_ERROR("%s: sequential split is not supported when there are coupled sequences in the input batch (you may need to use the -kvu flag)\n", __func__);
@@ -550,7 +548,7 @@ llama_ubatch llama_batch_allocr::split_equal(uint32_t n_ubatch, bool sequential,
}
}
uint32_t n_seqs = cur_seq_set.size();
const uint32_t n_seqs = cur_seq_set.size();
// we are done
if (n_seqs == 0) {
@@ -571,7 +569,7 @@ llama_ubatch llama_batch_allocr::split_equal(uint32_t n_ubatch, bool sequential,
std::vector<idx_vec_t> idxs_per_seq(n_seqs);
while (true) {
// we can only add new n_seq_tokens tokens if all the sequence sets have at least 1 more unused tokens and
// we can only add new n_seq_tokens tokens if all the sequence sets have at least one more unused token and
// if we haven't reached n_ubatch
bool can_expand = true;
@@ -602,72 +600,6 @@ llama_ubatch llama_batch_allocr::split_equal(uint32_t n_ubatch, bool sequential,
}
}
// if n_keep_tail > 0, keep only the seqs that either finish in this ubatch or have at least
// n_keep_tail tokens remaining for a future ubatch, so that the trailing n_keep_tail tokens
// of each seq are never split across ubatches
if (n_keep_tail > 0) {
GGML_ASSERT(n_ubatch > n_keep_tail);
auto n_remaining = [&](uint32_t s) {
return (uint32_t) (seq_set_map[cur_seq_set[s]].size() - cur_idx[s]);
};
// keep the longest prefix of seqs that satisfy the constraint, to preserve sequential seq ids
uint32_t n_keep = 0;
while (n_keep < n_seqs) {
const uint32_t remaining = n_remaining(n_keep);
if (remaining != 0 && remaining < n_keep_tail) {
break;
}
n_keep++;
}
// all seqs violate the constraint - resolve the first one directly and emit it alone
if (n_keep == 0) {
auto & idxs = idxs_per_seq[0];
const auto & seq_idxs = seq_set_map[cur_seq_set[0]];
if (idxs.size() + n_remaining(0) <= n_ubatch) {
// extend the seq to completion
while (n_remaining(0) > 0) {
const int32_t idx = seq_idxs[cur_idx[0]];
idxs.push_back(idx);
used[idx] = true;
++n_used;
++cur_idx[0];
}
} else {
// truncate the seq so that at least n_keep_tail tokens remain
while (n_remaining(0) < n_keep_tail) {
used[idxs.back()] = false;
--n_used;
idxs.pop_back();
--cur_idx[0];
}
}
n_keep = 1;
}
// return the tokens of the deferred seqs back to the pool
for (uint32_t s = n_keep; s < n_seqs; ++s) {
for (const int32_t idx : idxs_per_seq[s]) {
used[idx] = false;
--n_used;
}
}
n_seqs = n_keep;
}
// concat the per-sequence-set lists
std::vector<int32_t> idxs;
@@ -882,7 +814,7 @@ void llama_batch_allocr::ubatch_print(const llama_ubatch & ubatch, int debug) {
LLAMA_LOG_DEBUG("%s: output = %p\n", __func__, (void *) ubatch.output);
LLAMA_LOG_DEBUG("%s: n_outputs = %d\n", __func__, n_outputs);
if (debug > 0) {
if (debug > 1) {
int seq_id_max = 0;
for (uint32_t i = 0; i < ubatch.n_tokens; ++i) {
for (int s = 0; s < ubatch.n_seq_id[i]; ++s) {
+1 -2
View File
@@ -104,8 +104,7 @@ public:
// make ubatches of equal-length sequences sets
// if sequential == true, the tokens in the ubatch will have increasing sequential sequence ids
// n_keep_tail = minimum trailing tokens of a seq that must land in the same ubatch
llama_ubatch split_equal(uint32_t n_ubatch, bool sequential, uint32_t n_keep_tail);
llama_ubatch split_equal(uint32_t n_ubatch, bool sequential);
// sequence-set-wise split - each ubatch contains a single sequence-set
llama_ubatch split_seq(uint32_t n_ubatch);
+122 -89
View File
@@ -17,7 +17,6 @@
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
//
// llama_context
@@ -31,30 +30,6 @@ static llm_graph_type ctx_type_to_graph_type(llama_context_type ctx_type) {
throw std::runtime_error("Unsupported ctx type");
}
struct llm_fused_op_probe {
llm_fused_op op;
const char * name;
uint32_t n_tokens_per_seq;
};
static const llm_fused_op_probe llm_fused_op_flash_attn_probe = {
/*.op =*/ LLM_FUSED_OP_FLASH_ATTN,
/*.name =*/ "Flash Attention",
/*.n_tokens_per_seq =*/ 1,
};
static const llm_fused_op_probe llm_fused_op_gdn_ar_probe = {
/*.op =*/ LLM_FUSED_OP_GDN_AR,
/*.name =*/ "fused Gated Delta Net (autoregressive)",
/*.n_tokens_per_seq =*/ 1,
};
static const llm_fused_op_probe llm_fused_op_gdn_ch_probe = {
/*.op =*/ LLM_FUSED_OP_GDN_CH,
/*.name =*/ "fused Gated Delta Net (chunked)",
/*.n_tokens_per_seq =*/ 16,
};
llama_context::llama_context(
const llama_model & model,
llama_context_params params) :
@@ -461,69 +436,6 @@ llama_context::~llama_context() {
ggml_opt_free(opt_ctx);
}
void llama_context::resolve_fused_ops(const llama_memory_context_i * mctx, uint32_t n_seqs) {
const char * func = __func__;
auto resolve = [&](const llm_fused_op_probe & probe, bool & enabled) {
if (!enabled) {
return;
}
const uint32_t n_tokens_probe = probe.n_tokens_per_seq*n_seqs;
auto * gf = graph_reserve(n_tokens_probe, n_seqs, n_tokens_probe, mctx, true);
if (!gf) {
throw std::runtime_error(std::string("failed to reserve graph for ") + probe.name + " check");
}
bool device_mismatch = false;
for (const auto & node : get_gf_res_reserve()->get_fused_nodes()) {
if (node.op != probe.op) {
continue;
}
GGML_ASSERT(node.il >= 0);
ggml_backend_t backend_fused = ggml_backend_sched_get_tensor_backend(sched.get(), node.tensor);
ggml_backend_dev_t device_fused = backend_fused ? ggml_backend_get_device(backend_fused) : nullptr;
// TODO: make this descriptor-specific; model.dev_layer() preserves the current behavior,
// but is still wrong for cases like --no-kv-offload.
ggml_backend_dev_t device_layer = model.dev_layer(node.il);
if (device_fused != device_layer) {
LLAMA_LOG_WARN("%s: layer %d is assigned to device %s but %s "
"is assigned to device %s (usually due to missing support)\n",
func, node.il,
device_layer ? ggml_backend_dev_name(device_layer) : "none",
probe.name,
device_fused ? ggml_backend_dev_name(device_fused) : "none");
device_mismatch = true;
break;
}
}
if (device_mismatch) {
enabled = false;
LLAMA_LOG_WARN("%s: %s not supported, set to disabled\n", func, probe.name);
} else {
enabled = true;
LLAMA_LOG_INFO("%s: %s enabled\n", func, probe.name);
}
};
if (cparams.auto_fa) {
resolve(llm_fused_op_flash_attn_probe, cparams.flash_attn);
cparams.auto_fa = false;
}
if (cparams.auto_fgdn) {
LLAMA_LOG_INFO("%s: resolving fused Gated Delta Net support:\n", func);
resolve(llm_fused_op_gdn_ar_probe, cparams.fused_gdn_ar);
resolve(llm_fused_op_gdn_ch_probe, cparams.fused_gdn_ch);
cparams.auto_fgdn = false;
}
}
void llama_context::sched_reserve() {
if (!sched_need_reserve) {
return;
@@ -563,7 +475,128 @@ void llama_context::sched_reserve() {
LLAMA_LOG_DEBUG("%s: worst-case: n_tokens = %d, n_seqs = %d, n_outputs = %d\n", __func__, n_tokens, n_seqs, n_outputs);
resolve_fused_ops(mctx.get(), n_seqs);
// resolve automatic Flash Attention use
if (cparams.auto_fa) {
auto * gf = graph_reserve(1, n_seqs, n_outputs, mctx.get(), true);
if (!gf) {
throw std::runtime_error("failed to reserve graph for Flash Attention check");
}
const size_t prefix_len = strlen(LLAMA_TENSOR_NAME_FATTN) + 1;
bool fa_device_mismatch = false;
for (int i = 0; i < ggml_graph_n_nodes(gf); i++) {
ggml_tensor * n = ggml_graph_node(gf, i);
if (n->op != GGML_OP_FLASH_ATTN_EXT) {
continue;
}
ggml_backend_dev_t device_fa = ggml_backend_get_device(ggml_backend_sched_get_tensor_backend(sched.get(), n));
// TODO: instead of the tensor names, use a map to keep track of which (FA) tensors belong to which layer
GGML_ASSERT(strncmp(n->name, LLAMA_TENSOR_NAME_FATTN "-", prefix_len) == 0);
const int il = std::stoi(n->name + prefix_len);
ggml_backend_dev_t device_kv = model.dev_layer(il);
if (device_fa != device_kv) {
LLAMA_LOG_WARN("%s: layer %d is assigned to device %s but the Flash Attention tensor "
"is assigned to device %s (usually due to missing support)\n",
__func__, il, ggml_backend_dev_name(device_kv), ggml_backend_dev_name(device_fa));
// FIXME: fa_device_mismatch logic is wrong for --no-kv-offload, but this is broken anyways
fa_device_mismatch = true;
break;
}
}
if (fa_device_mismatch) {
cparams.flash_attn = false;
LLAMA_LOG_WARN("%s: Flash Attention was auto, set to disabled\n", __func__);
} else {
cparams.flash_attn = true;
LLAMA_LOG_INFO("%s: Flash Attention was auto, set to enabled\n", __func__);
}
cparams.auto_fa = false;
}
if (cparams.auto_fgdn) {
LLAMA_LOG_INFO("%s: resolving fused Gated Delta Net support:\n", __func__);
if (cparams.fused_gdn_ar) {
auto * gf = graph_reserve(1, n_seqs, n_outputs, mctx.get(), true);
if (!gf) {
throw std::runtime_error("failed to reserve graph for fused Gated Delta Net check (autoregressive)");
}
const size_t prefix_len = strlen(LLAMA_TENSOR_NAME_FGDN_AR) + 1;
bool gdn_device_mismatch = false;
for (int i = 0; i < ggml_graph_n_nodes(gf); i++) {
ggml_tensor * n = ggml_graph_node(gf, i);
if (n->op != GGML_OP_GATED_DELTA_NET) {
continue;
}
ggml_backend_dev_t device_gdn = ggml_backend_get_device(ggml_backend_sched_get_tensor_backend(sched.get(), n));
GGML_ASSERT(strncmp(n->name, LLAMA_TENSOR_NAME_FGDN_AR "-", prefix_len) == 0);
const int il = std::stoi(n->name + prefix_len);
ggml_backend_dev_t device_kv = model.dev_layer(il);
if (device_gdn != device_kv) {
LLAMA_LOG_WARN("%s: layer %d is assigned to device %s but the fused Gated Delta Net tensor "
"is assigned to device %s (usually due to missing support)\n",
__func__, il, ggml_backend_dev_name(device_kv), ggml_backend_dev_name(device_gdn));
gdn_device_mismatch = true;
break;
}
}
if (gdn_device_mismatch) {
cparams.fused_gdn_ar = false;
LLAMA_LOG_WARN("%s: fused Gated Delta Net (autoregressive) not supported, set to disabled\n", __func__);
} else {
LLAMA_LOG_INFO("%s: fused Gated Delta Net (autoregressive) enabled\n", __func__);
}
}
if (cparams.fused_gdn_ch) {
// more than one token in the batch per sequence in order to take the chunked path
// note: n_outputs must match n_tokens for embedding models with mean/rank pooling,
// because build_pooling creates inp_mean with shape [n_tokens, n_seqs] and multiplies
// it with t_embd which is reduced to [n_outputs, ...] via out_ids. if n_outputs != n_tokens,
// the ggml_mul_mat assertion fails.
const uint32_t n_tokens_ch = 16*n_seqs;
auto * gf = graph_reserve(n_tokens_ch, n_seqs, n_tokens_ch, mctx.get(), true);
if (!gf) {
throw std::runtime_error("failed to reserve graph for fused Gated Delta Net check (chunked)");
}
const size_t prefix_len = strlen(LLAMA_TENSOR_NAME_FGDN_CH) + 1;
bool gdn_device_mismatch = false;
for (int i = 0; i < ggml_graph_n_nodes(gf); i++) {
ggml_tensor * n = ggml_graph_node(gf, i);
if (n->op != GGML_OP_GATED_DELTA_NET) {
continue;
}
ggml_backend_dev_t device_gdn = ggml_backend_get_device(ggml_backend_sched_get_tensor_backend(sched.get(), n));
GGML_ASSERT(strncmp(n->name, LLAMA_TENSOR_NAME_FGDN_CH "-", prefix_len) == 0);
const int il = std::stoi(n->name + prefix_len);
ggml_backend_dev_t device_kv = model.dev_layer(il);
if (device_gdn != device_kv) {
LLAMA_LOG_WARN("%s: layer %d is assigned to device %s but the fused Gated Delta Net tensor "
"is assigned to device %s (usually due to missing support)\n",
__func__, il, ggml_backend_dev_name(device_kv), ggml_backend_dev_name(device_gdn));
gdn_device_mismatch = true;
break;
}
}
if (gdn_device_mismatch) {
cparams.fused_gdn_ch = false;
LLAMA_LOG_WARN("%s: fused Gated Delta Net (chunked) not supported, set to disabled\n", __func__);
} else {
LLAMA_LOG_INFO("%s: fused Gated Delta Net (chunked) enabled\n", __func__);
}
}
cparams.auto_fgdn = false;
}
// reserve worst-case graph
int n_splits_pp = -1;
-4
View File
@@ -262,10 +262,6 @@ private:
llm_graph_cb graph_get_cb() const;
// disable auto fused ops (Flash Attention, Gated Delta Net) whose op lands on a device
// that differs from the layer it belongs to (usually due to missing backend support)
void resolve_fused_ops(const llama_memory_context_i * mctx, uint32_t n_seqs);
// TODO: read/write lora adapters and cvec
size_t state_write_data(llama_io_write_i & io);
size_t state_read_data (llama_io_read_i & io);
+1 -8
View File
@@ -1192,7 +1192,6 @@ void llm_graph_result::reset() {
params = {};
inputs.clear();
fused_nodes.clear();
buf_compute_meta.resize(ggml_tensor_overhead()*max_nodes + ggml_graph_overhead_custom(max_nodes, false));
@@ -1294,10 +1293,6 @@ llm_graph_input_i * llm_graph_result::add_input(llm_graph_input_ptr input) {
return inputs.back().get();
}
void llm_graph_result::add_fused_node(llm_graph_fused_node result) {
fused_nodes.push_back(result);
}
void llm_graph_result::set_params(const llm_graph_params & params) {
this->params = params;
}
@@ -1357,8 +1352,6 @@ void llm_graph_context::cb(ggml_tensor * cur, const char * name, int il) const {
}
}
ggml_tensor * llm_graph_context::build_cvec(
ggml_tensor * cur,
int il) const {
@@ -2409,7 +2402,7 @@ ggml_tensor * llm_graph_context::build_attn_mha(
cur = ggml_flash_attn_ext(ctx0, q, k, v, kq_mask, kq_scale, hparams.f_max_alibi_bias,
hparams.attn_soft_cap ? hparams.f_attn_logit_softcapping : 0.0f);
res->add_fused_node({LLM_FUSED_OP_FLASH_ATTN, cur, il});
cb(cur, LLAMA_TENSOR_NAME_FATTN, il);
ggml_flash_attn_ext_add_sinks(cur, sinks);
ggml_flash_attn_ext_set_prec (cur, GGML_PREC_F32);
-17
View File
@@ -38,12 +38,6 @@ enum llm_graph_type {
LLM_GRAPH_TYPE_DECODER_MTP,
};
enum llm_fused_op {
LLM_FUSED_OP_FLASH_ATTN,
LLM_FUSED_OP_GDN_AR,
LLM_FUSED_OP_GDN_CH,
};
enum llm_ffn_op_type : int {
LLM_FFN_NONE = 0, // sentinel: unset; archs must assign before use
LLM_FFN_SILU,
@@ -781,12 +775,6 @@ struct llm_graph_params {
}
};
struct llm_graph_fused_node {
llm_fused_op op;
ggml_tensor * tensor;
int il;
};
class llm_graph_result {
public:
llm_graph_result(int64_t max_nodes);
@@ -820,10 +808,6 @@ public:
llm_graph_input_i * add_input(llm_graph_input_ptr input);
void add_fused_node(llm_graph_fused_node result);
const std::vector<llm_graph_fused_node> & get_fused_nodes() const { return fused_nodes; }
void set_params(const llm_graph_params & params);
// important graph nodes
@@ -842,7 +826,6 @@ public:
std::map<llama_seq_id, ggml_tensor *> t_sampled_probs;
std::vector<llm_graph_input_ptr> inputs;
std::vector<llm_graph_fused_node> fused_nodes;
ggml_context_ptr ctx_compute;
+4
View File
@@ -103,3 +103,7 @@ 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);
#define LLAMA_TENSOR_NAME_FATTN "__fattn__"
#define LLAMA_TENSOR_NAME_FGDN_AR "__fgdn_ar__"
#define LLAMA_TENSOR_NAME_FGDN_CH "__fgdn_ch__"
+1 -1
View File
@@ -113,7 +113,7 @@ llama_memory_context_ptr llama_kv_cache_dsa::init_batch(
std::vector<llama_ubatch> ubatches;
while (true) {
auto ubatch = n_stream == 1 ? balloc.split_simple(n_ubatch) : balloc.split_equal(n_ubatch, true, 0);
auto ubatch = n_stream == 1 ? balloc.split_simple(n_ubatch) : balloc.split_equal(n_ubatch, true);
if (ubatch.n_tokens == 0) {
break;
+1 -1
View File
@@ -1110,7 +1110,7 @@ llama_memory_context_ptr llama_kv_cache_dsv4::init_batch(
if (has_coupled) {
ubatch = balloc.split_seq(n_ubatch);
} else {
ubatch = balloc.split_equal(n_ubatch, raw_per_seq || comp_per_seq, 0);
ubatch = balloc.split_equal(n_ubatch, raw_per_seq || comp_per_seq);
}
if (ubatch.n_tokens == 0) {
+1 -1
View File
@@ -206,7 +206,7 @@ llama_memory_context_ptr llama_kv_cache_iswa::init_batch(llama_batch_allocr & ba
std::vector<llama_ubatch> ubatches;
while (true) {
auto ubatch = balloc.split_equal(n_ubatch, !unified, 0);
auto ubatch = balloc.split_equal(n_ubatch, !unified);
if (ubatch.n_tokens == 0) {
break;
+1 -1
View File
@@ -706,7 +706,7 @@ llama_memory_context_ptr llama_kv_cache::init_batch(
std::vector<llama_ubatch> ubatches;
while (true) {
auto ubatch = n_stream == 1 ? balloc.split_simple(n_ubatch) : balloc.split_equal(n_ubatch, true, 0);
auto ubatch = n_stream == 1 ? balloc.split_simple(n_ubatch) : balloc.split_equal(n_ubatch, true);
if (ubatch.n_tokens == 0) {
break;
+9 -9
View File
@@ -77,15 +77,15 @@ llama_memory_context_ptr llama_memory_hybrid_iswa::init_batch(llama_batch_allocr
// if all tokens are output, split by sequence
ubatch = balloc.split_seq(n_ubatch);
} else {
// Use non-sequential split when KV cache is unified (needed for hellaswag/winogrande/multiple-choice)
const bool unified = (mem_attn->get_base()->get_n_stream() == 1);
// [TAG_RECURRENT_ROLLBACK_SPLITS]
// the trailing (1 + n_rs_seq) tokens of each seq must stay in the same ubatch
// so that the rollback snapshots remain valid
const uint32_t n_rs_seq = mem_recr->n_rs_seq;
ubatch = balloc.split_equal(n_ubatch, !unified, n_rs_seq > 0 ? n_rs_seq + 1 : 0);
if (mem_recr->n_rs_seq > 0) {
// [TAG_RECURRENT_ROLLBACK_SPLITS]
// TODO: recurrent state rollback does not support equal splits
ubatch = balloc.split_seq(n_ubatch);
} else {
// Use non-sequential split when KV cache is unified (needed for hellaswag/winogrande/multiple-choice)
const bool unified = (mem_attn->get_base()->get_n_stream() == 1);
ubatch = balloc.split_equal(n_ubatch, !unified);
}
}
if (ubatch.n_tokens == 0) {
+9 -9
View File
@@ -78,15 +78,15 @@ llama_memory_context_ptr llama_memory_hybrid::init_batch(llama_batch_allocr & ba
// if all tokens are output, split by sequence
ubatch = balloc.split_seq(n_ubatch);
} else {
// Use non-sequential split when KV cache is unified (needed for hellaswag/winogrande/multiple-choice)
const bool unified = (mem_attn->get_n_stream() == 1);
// [TAG_RECURRENT_ROLLBACK_SPLITS]
// the trailing (1 + n_rs_seq) tokens of each seq must stay in the same ubatch
// so that the rollback snapshots remain valid
const uint32_t n_rs_seq = mem_recr->n_rs_seq;
ubatch = balloc.split_equal(n_ubatch, !unified, n_rs_seq > 0 ? n_rs_seq + 1 : 0);
if (mem_recr->n_rs_seq > 0) {
// [TAG_RECURRENT_ROLLBACK_SPLITS]
// TODO: recurrent state rollback does not support equal splits
ubatch = balloc.split_seq(n_ubatch);
} else {
// Use non-sequential split when KV cache is unified (needed for hellaswag/winogrande/multiple-choice)
const bool unified = (mem_attn->get_n_stream() == 1);
ubatch = balloc.split_equal(n_ubatch, !unified);
}
}
if (ubatch.n_tokens == 0) {
+9 -6
View File
@@ -416,12 +416,15 @@ llama_memory_context_ptr llama_memory_recurrent::init_batch(llama_batch_allocr &
// if all tokens are output, split by sequence
ubatch = balloc.split_seq(n_ubatch);
} else {
// TODO: non-sequential equal split can be done if using unified KV cache
// for simplicity, we always use sequential equal split for now
// [TAG_RECURRENT_ROLLBACK_SPLITS]
// the trailing (1 + n_rs_seq) tokens of each seq must stay in the same ubatch
// so that the rollback snapshots remain valid
ubatch = balloc.split_equal(n_ubatch, true, n_rs_seq > 0 ? n_rs_seq + 1 : 0);
if (n_rs_seq > 0) {
// [TAG_RECURRENT_ROLLBACK_SPLITS]
// TODO: recurrent state rollback does not support equal splits
ubatch = balloc.split_seq(n_ubatch);
} else {
// TODO: non-sequential equal split can be done if using unified KV cache
// for simplicity, we always use sequential equal split for now
ubatch = balloc.split_equal(n_ubatch, true);
}
}
if (ubatch.n_tokens == 0) {
+7 -19
View File
@@ -887,6 +887,9 @@ struct llm_tokenizer_ugm : llm_tokenizer {
// blob containing XOR-compressed compact double array (XCDA) entries
uint32_t xcda_blob_size = *(const uint32_t *) &precompiled_charsmap[0];
charsmap_offset += sizeof(xcda_blob_size);
if (xcda_blob_size + charsmap_offset >= precompiled_charsmap.size()) {
throw std::runtime_error("Index out of array bounds in precompiled charsmap!");
}
// Next xcda_blob_size bytes contain entries of XOR-compressed compact
// double array (XCDA). Each entry is bit-packed into a 32-bit integer.
@@ -1202,15 +1205,7 @@ private:
throw std::runtime_error("Index out of array bounds in precompiled charsmap!");
}
const char * prefix_replacement = &(tokenizer.prefix_replacements)[longest_prefix_offset];
size_t max_len = tokenizer.prefix_replacements_size - longest_prefix_offset;
size_t repl_len = 0;
while (repl_len < max_len && prefix_replacement[repl_len] != '\0') {
repl_len++;
}
if (repl_len == max_len) {
throw std::runtime_error("Unterminated string in precompiled charsmap!");
}
return { prefix_replacement, repl_len, longest_prefix_length };
return { prefix_replacement, strlen(prefix_replacement), longest_prefix_length };
}
// check if the input prefix contains a valid sequence of UTF-8 code units
@@ -2023,18 +2018,11 @@ void llama_vocab::impl::load(llama_model_loader & ml, const LLM_KV & kv) {
const size_t n_precompiled_charsmap = gguf_get_arr_n(ctx, precompiled_charsmap_keyidx);
const char * pc = (const char *) gguf_get_arr_data(ctx, precompiled_charsmap_keyidx);
precompiled_charsmap.assign(pc, pc + n_precompiled_charsmap);
if (precompiled_charsmap.size() < sizeof(uint32_t)) {
throw std::runtime_error("precompiled_charsmap too small for xcda_blob_size header!");
}
uint32_t * xcda_blob_size = (uint32_t *) &precompiled_charsmap[0];
#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
*xcda_blob_size = __builtin_bswap32(*xcda_blob_size);
#endif
if (*xcda_blob_size + sizeof(uint32_t) >= precompiled_charsmap.size()) {
throw std::runtime_error("Index out of array bounds in precompiled charsmap!");
}
#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
// correct endianness of data in precompiled_charsmap binary blob
uint32_t * xcda_blob_size = (uint32_t *) &precompiled_charsmap[0];
*xcda_blob_size = __builtin_bswap32(*xcda_blob_size);
assert(*xcda_blob_size + sizeof(uint32_t) < n_precompiled_charsmap);
size_t xcda_array_size = *xcda_blob_size / sizeof(uint32_t);
uint32_t * xcda_array = (uint32_t *) &precompiled_charsmap[sizeof(uint32_t)];
for (size_t i = 0; i < xcda_array_size; ++i) {
+6 -6
View File
@@ -401,9 +401,9 @@ std::pair<ggml_tensor *, ggml_tensor *> llm_build_delta_net_base::build_delta_ne
// K=1: output carries the final state only. state s is 4D [S_v, S_v, H_v, n_seqs].
ggml_tensor * result = ggml_gated_delta_net(ctx0, q, k, v, g, b, s, /*K=*/1);
if (n_tokens == 1) {
res->add_fused_node({LLM_FUSED_OP_GDN_AR, result, il});
cb(result, LLAMA_TENSOR_NAME_FGDN_AR, il);
} else {
res->add_fused_node({LLM_FUSED_OP_GDN_CH, result, il});
cb(result, LLAMA_TENSOR_NAME_FGDN_CH, il);
}
ggml_tensor * output = ggml_view_4d(ctx0, result,
@@ -496,8 +496,8 @@ ggml_tensor * llm_build_delta_net_base::build_conv_state(
ggml_build_forward_expand(gf, ggml_cpy(ctx0, conv_state_last, conv_state_update));
} else {
// [TAG_RECURRENT_ROLLBACK_SPLITS]
// this logic assumes that the last (n_rs_seq + 1) tokens of a sequence in a batch are inside
// the same ubatch, which `split_equal()` guarantees via its n_keep_tail argument
// TODO: this logic incorrectly assumes that the last (n_rs_seq + 1) tokens of a sequence in a batch are
// inside the same ubatch. currently with `split_equal()` this is not correct
const int64_t K = (int64_t) cparams.n_rs_seq + 1;
@@ -566,9 +566,9 @@ ggml_tensor * llm_build_delta_net_base::build_recurrent_attn(
// state s is 4D [S_v, S_v, H_v, n_seqs]; K snapshot slots are written into the output.
ggml_tensor * gdn_out = ggml_gated_delta_net(ctx0, q, k, v, g, b, s, K);
if (n_seq_tokens > 1) {
res->add_fused_node({LLM_FUSED_OP_GDN_CH, gdn_out, il});
cb(gdn_out, LLAMA_TENSOR_NAME_FGDN_CH, il);
} else {
res->add_fused_node({LLM_FUSED_OP_GDN_AR, gdn_out, il});
cb(gdn_out, LLAMA_TENSOR_NAME_FGDN_AR, il);
}
const int64_t attn_score_elems = S_v * H_v * n_seq_tokens * n_seqs;
+19 -25
View File
@@ -2393,8 +2393,7 @@ static void init_set_rows_row_ids(ggml_tensor * t, int num_rows) {
// GGML_OP_SET_ROWS
struct test_set_rows : public test_case {
const ggml_type type_src;
const ggml_type type_dst;
const ggml_type type;
const ggml_type type_idx;
const std::array<int64_t, 4> ne;
const std::array<int, 2> nr23; // broadcast only dims 2 and 3
@@ -2402,22 +2401,21 @@ struct test_set_rows : public test_case {
const bool v; // view (non-contiguous src1)
std::string vars() override {
return VARS_TO_STR7(type_src, type_dst, type_idx, ne, nr23, r, v);
return VARS_TO_STR6(type, type_idx, ne, nr23, r, v);
}
test_set_rows(ggml_type type_src,
ggml_type type_dst,
test_set_rows(ggml_type type,
ggml_type type_idx,
std::array<int64_t, 4> ne,
std::array<int, 2> nr23,
int r, bool v = false)
: type_src(type_src), type_dst(type_dst), type_idx(type_idx), ne(ne), nr23(nr23), r(r), v(v) {}
: type(type), type_idx(type_idx), ne(ne), nr23(nr23), r(r), v(v) {}
ggml_tensor * build_graph(ggml_context * ctx) override {
ggml_tensor * dst = ggml_new_tensor_4d(ctx, type_dst, ne[0], ne[1], ne[2]*nr23[0], ne[3]*nr23[1]);
ggml_tensor * dst = ggml_new_tensor_4d(ctx, type, ne[0], ne[1], ne[2]*nr23[0], ne[3]*nr23[1]);
ggml_set_name(dst, "dst");
ggml_tensor * src = ggml_new_tensor_4d(ctx, type_src, ne[0], r, ne[2]*nr23[0], ne[3]*nr23[1]);
ggml_tensor * src = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, ne[0], r, ne[2]*nr23[0], ne[3]*nr23[1]);
ggml_set_name(src, "src");
ggml_tensor * row_idxs = ggml_new_tensor_3d(ctx, type_idx, r, ne[2], ne[3]);
@@ -2450,17 +2448,17 @@ struct test_set_rows : public test_case {
}
double max_nmse_err() override {
if (type_dst == GGML_TYPE_Q4_0 || type_dst == GGML_TYPE_Q4_1 || type_dst == GGML_TYPE_IQ4_NL ||
type_dst == GGML_TYPE_Q5_0 || type_dst == GGML_TYPE_Q5_1 || type_dst == GGML_TYPE_Q8_0) {
if (type == GGML_TYPE_Q4_0 || type == GGML_TYPE_Q4_1 || type == GGML_TYPE_IQ4_NL ||
type == GGML_TYPE_Q5_0 || type == GGML_TYPE_Q5_1 || type == GGML_TYPE_Q8_0) {
// estimate what the max nmse error would be if one quantized value is
// off by one. The test values are distributed in [-1,1], so it'll be
// roughly (2.0 / 2^bits)^2, divided by the mean square value of the reference,
// which is roughly 0.25 times the number of elements.
double err_estimate = 1.0f/8.0f;
if (type_dst == GGML_TYPE_Q5_0 || type_dst == GGML_TYPE_Q5_1) {
if (type == GGML_TYPE_Q5_0 || type == GGML_TYPE_Q5_1) {
err_estimate /= 2.0f;
}
if (type_dst == GGML_TYPE_Q8_0) {
if (type == GGML_TYPE_Q8_0) {
err_estimate /= 8.0f;
}
err_estimate *= err_estimate;
@@ -2473,7 +2471,7 @@ struct test_set_rows : public test_case {
// See dicussion here: https://github.com/ggml-org/llama.cpp/pull/23760#issuecomment-4566312209
double max_nmse_err(ggml_backend_t backend) override {
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(ggml_backend_get_device(backend));
if (type_dst == GGML_TYPE_Q8_0 && strcmp(ggml_backend_reg_name(reg), "WebGPU") == 0) {
if (type == GGML_TYPE_Q8_0 && strcmp(ggml_backend_reg_name(reg), "WebGPU") == 0) {
return std::max(test_case::max_nmse_err(backend), 2e-7);
}
return test_case::max_nmse_err(backend);
@@ -7875,28 +7873,24 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
test_cases.emplace_back(new test_get_rows_back(GGML_TYPE_I32, 256, 5, 4, 1, v));
}
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, GGML_TYPE_F32, GGML_TYPE_I64, { 1, 8, 1, 3 }, { 1, 1 }, 2, false));
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, GGML_TYPE_F32, GGML_TYPE_I32, { 1, 8, 1, 3 }, { 1, 1 }, 2, false));
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, GGML_TYPE_Q8_0, GGML_TYPE_I32, { 256, 5, 1, 3 }, { 1, 1, }, 1, false));
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, GGML_TYPE_I64, { 1, 8, 1, 3 }, { 1, 1 }, 2, false));
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, GGML_TYPE_I32, { 1, 8, 1, 3 }, { 1, 1 }, 2, false));
test_cases.emplace_back(new test_set_rows(GGML_TYPE_Q8_0, GGML_TYPE_I32, { 256, 5, 1, 3 }, { 1, 1, }, 1, false));
for (ggml_type type : all_types) {
for (int b : {1, 7}) {
for (bool v : {false, true}) {
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, type, GGML_TYPE_I64, { 256, 5, b, 3 }, { 1, 1, }, 1, v));
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, type, GGML_TYPE_I64, { 256, 11, 1, b }, { 2, 3, }, 7, v));
test_cases.emplace_back(new test_set_rows(type, GGML_TYPE_I64, { 256, 5, b, 3 }, { 1, 1, }, 1, v));
test_cases.emplace_back(new test_set_rows(type, GGML_TYPE_I64, { 256, 11, 1, b }, { 2, 3, }, 7, v));
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, type, GGML_TYPE_I64, { 3*ggml_blck_size(type), 3, b, 1 }, { 2, 3, }, 2, v));
test_cases.emplace_back(new test_set_rows(type, GGML_TYPE_I64, { 3*ggml_blck_size(type), 3, b, 1 }, { 2, 3, }, 2, v));
if (ggml_blck_size(type) == 1) {
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, type, GGML_TYPE_I64, { 31, 3, b, 1 }, { 2, 3, }, 2, v));
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F32, type, GGML_TYPE_I64, { 33, 5, 1, b }, { 2, 3, }, 1, v));
test_cases.emplace_back(new test_set_rows(type, GGML_TYPE_I64, { 31, 3, b, 1 }, { 2, 3, }, 2, v));
test_cases.emplace_back(new test_set_rows(type, GGML_TYPE_I64, { 33, 5, 1, b }, { 2, 3, }, 1, v));
}
}
}
}
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F16, GGML_TYPE_F16, GGML_TYPE_I64, { 1, 8, 1, 3 }, { 1, 1 }, 2, false));
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F16, GGML_TYPE_F16, GGML_TYPE_I32, { 1, 8, 1, 3 }, { 1, 1 }, 2, false));
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F16, GGML_TYPE_F16, GGML_TYPE_I64, { 1, 8, 1, 3 }, { 1, 1 }, 2, true));
test_cases.emplace_back(new test_set_rows(GGML_TYPE_F16, GGML_TYPE_F16, GGML_TYPE_I32, { 1, 8, 1, 3 }, { 1, 1 }, 2, true));
for (int mode : { GGML_ROPE_TYPE_NORMAL, GGML_ROPE_TYPE_NEOX, GGML_ROPE_TYPE_MROPE, GGML_ROPE_TYPE_VISION }) {
for (ggml_type type : {GGML_TYPE_F16, GGML_TYPE_F32}) {
+4 -6
View File
@@ -57,7 +57,7 @@ The core architecture consists of the following components:
- `server_tokens`: Unified representation of token sequences (supports both text and multimodal tokens); used by `server_task` and `server_slot`.
- `server_prompt_checkpoint`: For recurrent (e.g., RWKV) and SWA models, stores snapshots of KV cache state. Enables reuse when subsequent requests share the same prompt prefix, saving redundant computation.
- `server_models`: Standalone component for managing multiple backend instances (used in router mode). It is completely independent of `server_context`.
- `stream_session_manager`: process wide owner of resumable SSE stream sessions, keyed by conversation id. A file-static singleton inside `server-stream.cpp`, driven through `server_stream_session_manager_start/stop`. Backs the replay buffer that lets a client reattach to a generation after an HTTP disconnect. See the "Resumable streaming" section below.
- `stream_session_manager`: Process wide owner of resumable SSE stream sessions (`g_stream_sessions`), keyed by conversation id. Backs the replay buffer that lets a client reattach to a generation after an HTTP disconnect. See the "Resumable streaming" section below.
```mermaid
graph TD
@@ -127,12 +127,10 @@ It is opt in via the `X-Conversation-Id` header on `POST /v1/chat/completions`.
The feature lives entirely in `server-stream.{h,cpp}` and rests on three types:
- `stream_session`: a bounded ring buffer (4 MiB cap, oldest bytes drop first) plus a condvar. `append` pushes raw SSE bytes, `read_from` drains from any offset and blocks for live bytes or finalize, `finalize` wakes readers, `cancel` stops the producer. One conv maps to at most one live session.
- `stream_session_manager`: a file-static singleton (`g_stream_sessions`) inside `server-stream.cpp`, owns all sessions keyed by conv id, enforces the one conv one session invariant via `create_or_replace`, and runs a GC thread that drops completed sessions past their TTL. Exposed to main only through `server_stream_session_manager_start/stop`.
- `stream_session_manager` (`g_stream_sessions`): owns all sessions keyed by conv id, enforces the one conv one session invariant via `create_or_replace`, and runs a GC thread that drops completed sessions past their TTL.
- `stream_pipe_producer` / `stream_pipe_consumer`: the write and read ends. The producer owns the session lifetime and finalizes it on destruction; the consumer is read only and never finalizes, so a reader detaching cannot kill a running generation.
The implementation is hidden in `server-stream.cpp` (pimpl). The header exposes only the route handler factories, `server_stream_session_attach_pipe`, `server_stream_aware_should_stop`, `server_stream_conv_id_from_headers` and the GC lifecycle; the session, manager and consumer types stay in the `.cpp`.
Producer side: `server_res_generator` attaches a producer pipe when the header is present. The HTTP content provider mirrors every chunk into the ring before writing it to the socket. While a pipe is attached, `server_stream_aware_should_stop` ignores peer disconnect, so a dropped socket does not stop generation: only an explicit `DELETE` does. When the peer leaves early, `on_complete` calls `close()`, which drains the rest of the generation into the ring on the http worker.
Producer side: `server_res_generator` attaches a producer pipe when the header is present. The HTTP content provider mirrors every chunk into the ring before writing it to the socket. While a pipe is attached, `stream_aware_should_stop` ignores peer disconnect, so a dropped socket does not stop generation: only an explicit `DELETE` does. When the peer leaves early, `on_complete` calls `close()`, which drains the rest of the generation into the ring on the http worker.
Lifetime safety: the producer pipe holds a shared `alive` flag also captured by the session cancel hook. `~server_res_generator` calls `cleanup()` to clear that hook while the reader is still alive, so a `cancel` arriving during teardown can never call `stop()` on a freed response. This ordering is the most fragile part of the feature: finalizing or destroying the producer before `cleanup()` runs reintroduces a use after free.
@@ -146,7 +144,7 @@ Routes:
Router mode binds the same paths to proxy handlers. A `conv_id -> child` map (`conv_models`), populated when a POST is routed, resolves the owning child in one lookup with no polling. The lookup groups ids per child; GET and DELETE proxy straight to the owner. This loopback REST hop is expected to move to a websocket IPC later, swapping only the transport.
Lifecycle: `server_stream_session_manager_start()` runs in main after common init, `server_stream_session_manager_stop()` runs first in `clean_up()` and finalizes every live session so no reader hangs. Reader blocking and the post drop drain both run on httplib worker threads, which block on a condvar rather than spin.
Lifecycle: `g_stream_sessions.start_gc()` runs in main after common init, `stop_gc()` runs first in `clean_up()` and finalizes every live session so no reader hangs. Reader blocking and the post drop drain both run on httplib worker threads, which block on a condvar rather than spin.
| Constant | Value | Role |
| --- | --- | --- |
+1 -1
View File
@@ -228,7 +228,7 @@ For the full list of features, please refer to [server's changelog](https://gith
| `-sps, --slot-prompt-similarity SIMILARITY` | how much the prompt of a request must match the prompt of a slot in order to use that slot (default: 0.10, 0.0 = disabled) |
| `--lora-init-without-apply` | load LoRA adapters without applying them (apply later via POST /lora-adapters) (default: disabled) |
| `--sleep-idle-seconds SECONDS` | number of seconds of idleness after which the server will sleep (default: -1; -1 = disabled) |
| `--log-prompts-dir PATH` | Log prompts to directory (auto-created if not present; only used for debugging, default: disabled) |
| `--log-prompts-dir PATH` | Log prompts to directory (only used for debugging, default: disabled) |
| `--spec-draft-hf, -hfd, -hfrd, --hf-repo-draft <user>/<model>[:quant]` | Same as --hf-repo, but for the draft model (default: unused)<br/>(env: LLAMA_ARG_SPEC_DRAFT_HF_REPO) |
| `--spec-draft-threads, -td, --threads-draft N` | number of threads to use during generation (default: same as --threads) |
| `--spec-draft-threads-batch, -tbd, --threads-batch-draft N` | number of threads to use during batch and prompt processing (default: same as --threads-draft) |
+2 -2
View File
@@ -4188,7 +4188,7 @@ std::unique_ptr<server_res_generator> server_routes::handle_completions_impl(
}
};
auto effective_should_stop = server_stream_aware_should_stop(res_this, req.should_stop);
auto effective_should_stop = stream_aware_should_stop(res_this, req.should_stop);
try {
if (effective_should_stop()) {
@@ -4284,7 +4284,7 @@ std::unique_ptr<server_res_generator> server_routes::handle_completions_impl(
// attach a producer pipe to the response when X-Conversation-Id is present.
// the pipe mirrors SSE chunks into the ring buffer and wires up the cancel hook.
server_stream_session_attach_pipe(*res, req.headers);
stream_session_attach_pipe(*res, req.headers);
return res;
}
+2 -2
View File
@@ -1615,7 +1615,7 @@ void server_models_routes::init_routes() {
}
// remember which child serves this conversation so the stream routes can route straight
// to it without polling, keyed on the exact conv id from the header
std::string conv_id = server_stream_conv_id_from_headers(req.headers);
std::string conv_id = stream_conv_id_from_headers(req.headers);
if (!conv_id.empty()) {
models.conv_models.remember(conv_id, name);
}
@@ -1830,7 +1830,7 @@ void server_models_routes::init_routes() {
if (!from.empty()) {
child_path += "?from=" + from;
}
SRV_TRC("proxying stream resume to model %s on port %d, path=%s\n",
SRV_INF("proxying stream resume to model %s on port %d, path=%s\n",
owner->name.c_str(), owner->port, child_path.c_str());
auto proxy = std::make_unique<server_http_proxy>(
"GET",
+41 -147
View File
@@ -6,12 +6,6 @@
#include <chrono>
#include <memory>
#include <utility>
#include <shared_mutex>
enum class stream_read_status {
OK,
OFFSET_LOST,
};
namespace {
constexpr int64_t STREAM_SESSION_TTL_SECONDS = 300;
@@ -19,6 +13,7 @@ constexpr size_t STREAM_SESSION_MAX_BYTES = 4 * 1024 * 1024;
constexpr int64_t STREAM_SESSION_GC_INTERVAL_SECONDS = 60;
constexpr int64_t STREAM_READ_WAKE_INTERVAL_MS = 200;
// returns unix time in seconds
int64_t now_seconds() {
return std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
@@ -26,91 +21,6 @@ int64_t now_seconds() {
}
}
// owns all live sessions keyed by conversation_id, one conv = at most one live session.
// a periodic GC evicts expired ones
class stream_session_manager {
public:
stream_session_manager();
~stream_session_manager();
stream_session_manager(const stream_session_manager &) = delete;
stream_session_manager & operator=(const stream_session_manager &) = delete;
// install a new session, evicting and cancelling any previous one. conversation_id must be non empty
stream_session_ptr create_or_replace(const std::string & conversation_id);
stream_session_ptr get(const std::string & conversation_id);
std::vector<stream_session_ptr> list_all() const;
void evict(const std::string & conversation_id);
void evict_and_cancel(const std::string & conversation_id);
void start_gc();
void stop_gc();
private:
void gc_loop();
mutable std::shared_mutex map_mu;
std::unordered_map<std::string, stream_session_ptr> sessions; // key: conversation_id
std::thread gc_thread;
bool running;
std::mutex gc_wake_mu;
std::condition_variable gc_wake_cv;
};
// process wide manager, lifecycle controlled by llama-server main() via start_gc/stop_gc
static stream_session_manager g_stream_sessions;
void server_stream_session_manager_start() {
g_stream_sessions.start_gc();
}
void server_stream_session_manager_stop() {
g_stream_sessions.stop_gc();
}
struct stream_session {
std::string conversation_id;
int64_t started_ts; // unix seconds at construction
stream_session(std::string conversation_id_, size_t max_bytes_);
stream_session(const stream_session &) = delete;
stream_session & operator=(const stream_session &) = delete;
bool append(const char * data, size_t len);
void finalize();
// drain from offset into sink, blocking for more bytes or finalize. OFFSET_LOST if offset
// fell below the dropped prefix
stream_read_status read_from(size_t offset,
const std::function<bool(const char *, size_t)> & sink,
const std::function<bool()> & should_stop);
bool is_done() const;
bool is_cancelled() const;
size_t total_size() const; // bytes that ever entered the session
size_t dropped_prefix() const; // bytes evicted from the front due to cap
int64_t completed_at() const; // 0 while alive, unix seconds after finalize
void set_stop_producer(std::function<void()> fn);
void cancel();
private:
mutable std::mutex mu;
std::condition_variable cv;
std::vector<char> buffer;
size_t prefix_dropped;
size_t cap_bytes;
bool done;
std::atomic<bool> cancelled; // polled lock-free by the should_stop closure, no mu
int64_t completed_ts;
std::function<void()> stop_producer;
};
stream_session::stream_session(std::string conversation_id_, size_t max_bytes_)
: conversation_id(std::move(conversation_id_))
, started_ts(now_seconds())
@@ -128,7 +38,7 @@ bool stream_session::append(const char * data, size_t len) {
}
{
std::lock_guard<std::mutex> lock(mu);
if (done) {
if (done.load(std::memory_order_relaxed)) {
return false;
}
if (len >= cap_bytes) {
@@ -152,14 +62,11 @@ bool stream_session::append(const char * data, size_t len) {
}
void stream_session::finalize() {
{
std::lock_guard<std::mutex> lock(mu);
if (done) {
return;
}
done = true;
completed_ts = now_seconds();
bool was_done = done.exchange(true, std::memory_order_acq_rel);
if (was_done) {
return;
}
completed_ts.store(now_seconds(), std::memory_order_release);
cv.notify_all();
}
@@ -189,7 +96,7 @@ stream_read_status stream_session::read_from(size_t offset,
lock.lock();
continue;
}
if (done) {
if (done.load(std::memory_order_acquire)) {
return stream_read_status::OK;
}
// wait for new bytes, finalize, or a periodic wake to re check should_stop
@@ -198,8 +105,7 @@ stream_read_status stream_session::read_from(size_t offset,
}
bool stream_session::is_done() const {
std::lock_guard<std::mutex> lock(mu);
return done;
return done.load(std::memory_order_acquire);
}
size_t stream_session::total_size() const {
@@ -213,8 +119,7 @@ size_t stream_session::dropped_prefix() const {
}
int64_t stream_session::completed_at() const {
std::lock_guard<std::mutex> lock(mu);
return completed_ts;
return completed_ts.load(std::memory_order_acquire);
}
void stream_session::set_stop_producer(std::function<void()> fn) {
@@ -223,7 +128,7 @@ void stream_session::set_stop_producer(std::function<void()> fn) {
}
void stream_session::cancel() {
// flip cancelled first so the producer-side server_stream_aware_should_stop can break out of the
// flip cancelled first so the producer-side stream_aware_should_stop can break out of the
// recv() wait even if remove_waiting_task_ids does not notify the condvar (the cancel task
// posted by rd.stop() will eventually notify, but we do not want to depend on that timing)
cancelled.store(true, std::memory_order_release);
@@ -332,24 +237,18 @@ void stream_session_manager::evict_and_cancel(const std::string & conversation_i
}
void stream_session_manager::start_gc() {
{
std::lock_guard<std::mutex> lock(gc_wake_mu);
if (running) {
return;
}
running = true;
if (running.exchange(true)) {
return;
}
gc_thread = std::thread([this] { gc_loop(); });
}
void stream_session_manager::stop_gc() {
bool was_running;
{
std::lock_guard<std::mutex> lock(gc_wake_mu);
was_running = running;
running = false;
}
bool was_running = running.exchange(false);
if (was_running) {
{
std::lock_guard<std::mutex> lock(gc_wake_mu);
}
gc_wake_cv.notify_all();
if (gc_thread.joinable()) {
gc_thread.join();
@@ -371,15 +270,15 @@ void stream_session_manager::stop_gc() {
}
void stream_session_manager::gc_loop() {
while (true) {
while (running.load(std::memory_order_acquire)) {
{
std::unique_lock<std::mutex> lock(gc_wake_mu);
gc_wake_cv.wait_for(lock,
std::chrono::seconds(STREAM_SESSION_GC_INTERVAL_SECONDS),
[this] { return !running; });
if (!running) {
return;
}
[this] { return !running.load(std::memory_order_acquire); });
}
if (!running.load(std::memory_order_acquire)) {
return;
}
int64_t cutoff = now_seconds() - STREAM_SESSION_TTL_SECONDS;
std::vector<stream_session_ptr> to_drop;
@@ -402,19 +301,10 @@ void stream_session_manager::gc_loop() {
}
}
// stream_pipe
// process wide manager, lifecycle controlled by llama-server main() via start_gc/stop_gc
stream_session_manager g_stream_sessions;
// consumer end: read-only replay of the ring buffer, the destructor does not finalize the session
struct stream_pipe_consumer : stream_pipe {
stream_read_status read(size_t & offset,
const std::function<bool(const char *, size_t)> & sink,
const std::function<bool()> & should_stop);
static std::shared_ptr<stream_pipe_consumer> create(stream_session_ptr session);
private:
explicit stream_pipe_consumer(stream_session_ptr session);
};
// stream_pipe ---------------------------------------------------------------------------------
stream_pipe::stream_pipe(stream_session_ptr session)
: session_(std::move(session)) {
@@ -518,10 +408,12 @@ static server_http_res_ptr make_error_response(int status, const std::string & m
return res;
}
server_http_context::handler_t server_stream_make_get_handler() {
server_http_context::handler_t make_stream_get_handler() {
return [](const server_http_req & req) -> server_http_res_ptr {
// GET /v1/stream/<conv_id>?from=N replays buffered SSE bytes then blocks for live
// bytes until the session finalizes, streamed as text/event-stream for EventSource
// GET /v1/stream/<conv_id>?from=N replays the SSE bytes already buffered for the
// session, blocks for more bytes when the session is still running, returns when
// the session is finalized. the body is streamed back as text/event-stream so the
// browser EventSource can attach to it like a fresh request
std::string conv_id = req.get_param("conv_id");
if (conv_id.empty()) {
return make_error_response(400, "Missing conversation id in path", ERROR_TYPE_INVALID_REQUEST);
@@ -567,10 +459,11 @@ server_http_context::handler_t server_stream_make_get_handler() {
};
}
server_http_context::handler_t server_stream_make_lookup_handler() {
server_http_context::handler_t make_streams_lookup_handler() {
return [](const server_http_req & req) -> server_http_res_ptr {
// POST /v1/streams/lookup returns the matching sessions, only for ids the caller already
// knows. each id matches the exact key and any "<id>::<model>" per model variant
// POST /v1/streams/lookup with body {"conversation_ids": ["X", "Y", ...]} returns the
// matching sessions, only for ids the caller already knows. each id matches the exact key
// and any "<id>::<model>" variant, so one lookup covers every per model session for a conv
std::vector<std::string> requested;
try {
json body = json::parse(req.body);
@@ -625,10 +518,11 @@ server_http_context::handler_t server_stream_make_lookup_handler() {
};
}
server_http_context::handler_t server_stream_make_delete_handler() {
server_http_context::handler_t make_stream_delete_handler() {
return [](const server_http_req & req) -> server_http_res_ptr {
// DELETE /v1/stream/<conv_id> is the explicit user Stop, cancels the producer and evicts
// the buffer. idempotent, returns 204 even if the session was already gone
// DELETE /v1/stream/<conv_id> is the explicit user Stop, cancels the producer hook
// wired by handle_completions_impl and evicts the buffer. idempotent, a session that
// already finalized or was never created returns 204 either way
std::string conv_id = req.get_param("conv_id");
if (conv_id.empty()) {
return make_error_response(400, "Missing conversation id in path", ERROR_TYPE_INVALID_REQUEST);
@@ -642,7 +536,7 @@ server_http_context::handler_t server_stream_make_delete_handler() {
};
}
std::string server_stream_conv_id_from_headers(const std::map<std::string, std::string> & headers) {
std::string stream_conv_id_from_headers(const std::map<std::string, std::string> & headers) {
// case-insensitive scan for x-conversation-id
static constexpr char target[] = "x-conversation-id";
static constexpr size_t target_len = sizeof(target) - 1;
@@ -661,8 +555,8 @@ std::string server_stream_conv_id_from_headers(const std::map<std::string, std::
return std::string();
}
void server_stream_session_attach_pipe(server_http_res & res, const std::map<std::string, std::string> & headers) {
std::string conversation_id = server_stream_conv_id_from_headers(headers);
void stream_session_attach_pipe(server_http_res & res, const std::map<std::string, std::string> & headers) {
std::string conversation_id = stream_conv_id_from_headers(headers);
SRV_TRC("conv_id=%s (empty=%d)\n", conversation_id.c_str(), conversation_id.empty() ? 1 : 0);
if (conversation_id.empty()) {
return;
@@ -671,7 +565,7 @@ void server_stream_session_attach_pipe(server_http_res & res, const std::map<std
res.spipe = stream_pipe_producer::create(session, res);
}
std::function<bool()> server_stream_aware_should_stop(server_http_res * res, std::function<bool()> fallback) {
std::function<bool()> stream_aware_should_stop(server_http_res * res, std::function<bool()> fallback) {
return [res, fallback = std::move(fallback)]() -> bool {
if (res->spipe) {
return res->spipe->is_cancelled();
+136 -15
View File
@@ -3,23 +3,81 @@
#include "server-http.h"
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
// streaming buffer for one generation, survives HTTP disconnect. the producer appends SSE bytes,
// readers drain from any offset via read_from. keyed by conversation_id, one conv = one live session
enum class stream_read_status {
OK,
OFFSET_LOST,
};
struct stream_session;
// streaming buffer for one generation, survives HTTP disconnect. the producer appends raw SSE
// bytes, readers drain from any offset via read_from and block until more bytes or finalize.
// keyed by conversation_id: one conv = at most one live session
struct stream_session {
std::string conversation_id;
int64_t started_ts; // unix seconds at construction, used by /v1/streams listing
stream_session(std::string conversation_id_, size_t max_bytes_);
stream_session(const stream_session &) = delete;
stream_session & operator=(const stream_session &) = delete;
// append raw bytes, drops from the front if the cap is reached.
// returns false if the session is already finalized
bool append(const char * data, size_t len);
// mark the session as complete, wakes all pending readers
void finalize();
// drain bytes from offset, calling sink for each chunk. blocks until more
// bytes arrive or finalize is called. returns OK on clean exit, OFFSET_LOST
// if offset falls below the dropped prefix
stream_read_status read_from(size_t offset,
const std::function<bool(const char *, size_t)> & sink,
const std::function<bool()> & should_stop);
bool is_done() const;
bool is_cancelled() const;
size_t total_size() const; // bytes that ever entered the session
size_t dropped_prefix() const; // bytes evicted from the front due to cap
int64_t completed_at() const; // 0 while alive, unix seconds after finalize
// attach the producer stop hook used to cancel its reader, pass an empty function to detach
void set_stop_producer(std::function<void()> fn);
// signal the producer to abort its inference asap via the stop hook, idempotent
void cancel();
private:
mutable std::mutex mu;
std::condition_variable cv;
std::vector<char> buffer;
size_t prefix_dropped;
size_t cap_bytes;
std::atomic<bool> done;
std::atomic<bool> cancelled;
std::atomic<int64_t> completed_ts;
std::function<void()> stop_producer; // protected by mu
};
using stream_session_ptr = std::shared_ptr<stream_session>;
// base of the producer/consumer pipe ends. virtual dtor so each runs its own teardown:
// one end of a stream_session pipe. the base holds the session and the shared query, the
// producer and consumer ends derive from it. virtual dtor so each end runs its own teardown:
// the producer finalizes the session, the consumer leaves it untouched
struct stream_pipe {
virtual ~stream_pipe() = default;
// true if the session was cancelled (e.g. via DELETE /v1/stream/<conv_id>)
bool is_cancelled() const;
protected:
@@ -37,6 +95,7 @@ protected:
struct stream_pipe_producer : stream_pipe {
~stream_pipe_producer() override;
// append raw bytes to the session's ring buffer, returns false if already finalized
bool write(const char * data, size_t len);
// mark the natural end on the wire so a later close() is a no-op
@@ -62,21 +121,83 @@ private:
server_http_res * res_ = nullptr;
};
void server_stream_session_manager_start();
void server_stream_session_manager_stop();
// consumer end: read-only replay of the ring buffer, the destructor does not finalize the session
struct stream_pipe_consumer : stream_pipe {
// drain bytes from offset, calling sink for each available chunk. blocks until more data
// arrives or the session finalizes. should_stop is polled, returns OFFSET_LOST if offset
// fell below the dropped prefix
stream_read_status read(size_t & offset,
const std::function<bool(const char *, size_t)> & sink,
const std::function<bool()> & should_stop);
// route handler factories wired under /v1/stream/* by server.cpp
server_http_context::handler_t server_stream_make_get_handler();
server_http_context::handler_t server_stream_make_lookup_handler();
server_http_context::handler_t server_stream_make_delete_handler();
static std::shared_ptr<stream_pipe_consumer> create(stream_session_ptr session);
// extract the X-Conversation-Id header value (case-insensitive), empty when absent
std::string server_stream_conv_id_from_headers(const std::map<std::string, std::string> & headers);
private:
explicit stream_pipe_consumer(stream_session_ptr session);
};
// on an X-Conversation-Id header, create or replace the session and attach a producer pipe to res
void server_stream_session_attach_pipe(server_http_res & res, const std::map<std::string, std::string> & headers);
// owns all live sessions, runs a periodic GC to evict expired ones.
// the map is keyed by conversation_id, so the invariant "one conv = at most one
// live session" is enforced at the type level
class stream_session_manager {
public:
stream_session_manager();
~stream_session_manager();
stream_session_manager(const stream_session_manager &) = delete;
stream_session_manager & operator=(const stream_session_manager &) = delete;
// install a new session for this conversation, evicting and cancelling any previous one.
// the conversation_id must be non empty, the caller is responsible for that check.
// returns the new session
stream_session_ptr create_or_replace(const std::string & conversation_id);
// lookup, returns null if unknown or already evicted
stream_session_ptr get(const std::string & conversation_id);
// list every live or recently completed session, used by GET /v1/streams without filter
std::vector<stream_session_ptr> list_all() const;
// remove from the map and finalize, wakes any pending readers
void evict(const std::string & conversation_id);
// signal the producer to cancel asap then evict, used by the explicit user Stop path
void evict_and_cancel(const std::string & conversation_id);
void start_gc();
void stop_gc();
private:
void gc_loop();
mutable std::shared_mutex map_mu;
std::unordered_map<std::string, stream_session_ptr> sessions; // key: conversation_id
std::thread gc_thread;
std::atomic<bool> running;
std::mutex gc_wake_mu;
std::condition_variable gc_wake_cv;
};
// process wide manager, linked by both llama-server and llama-cli. llama-server main() drives
// start_gc/stop_gc, llama-cli leaves it idle. the dtor calls stop_gc() unconditionally so exit
// is safe whether or not the GC thread ran
extern stream_session_manager g_stream_sessions;
// route handler factories operating on g_stream_sessions, wired under /v1/stream/* by server.cpp.
// keeps the resumable stream surface confined to server-stream
server_http_context::handler_t make_stream_get_handler();
server_http_context::handler_t make_streams_lookup_handler();
server_http_context::handler_t make_stream_delete_handler();
// extract the X-Conversation-Id header value (case-insensitive), empty when absent. exposed so
// the router can track which child serves a forwarded POST
std::string stream_conv_id_from_headers(const std::map<std::string, std::string> & headers);
// on an X-Conversation-Id header, create or replace the session and attach a producer pipe to
// res. no-op when absent, called from the server_res_generator constructor
void stream_session_attach_pipe(server_http_res & res, const std::map<std::string, std::string> & headers);
// should_stop closure that ignores peer disconnect when a pipe is attached, so only an explicit
// DELETE stops the producer and generation keeps flowing into the ring buffer. without a pipe it
// delegates to fallback, the legacy non-resumable flow
std::function<bool()> server_stream_aware_should_stop(server_http_res * res, std::function<bool()> fallback);
std::function<bool()> stream_aware_should_stop(server_http_res * res, std::function<bool()> fallback);
+8 -8
View File
@@ -95,7 +95,7 @@ int llama_server(int argc, char ** argv) {
// start the stream session manager GC right after common init, before any HTTP route can
// touch it. lifecycle is symmetric, stop_gc() runs in clean_up() before backend free
server_stream_session_manager_start();
g_stream_sessions.start_gc();
if (!common_params_parse(argc, argv, params, LLAMA_EXAMPLE_SERVER)) {
return 1;
@@ -265,8 +265,8 @@ int llama_server(common_params & params, int argc, char ** argv) {
ctx_http.post("/slots/:id_slot", ex_wrapper(routes.post_slots));
// resumable streaming, the conversation_id is the session identity end to end. router and
// child wire different handlers under the same paths: a child binds the local session
// factories, the router binds proxies that resolve the owning child through the
// child wire different handlers under the same paths: a child binds the local g_stream_sessions
// backed factories, the router binds proxies that resolve the owning child through the
// conv_id -> model map
server_http_context::handler_t stream_get_h;
server_http_context::handler_t streams_lookup_h;
@@ -276,9 +276,9 @@ int llama_server(common_params & params, int argc, char ** argv) {
streams_lookup_h = models_routes->router_streams_lookup;
stream_delete_h = models_routes->router_stream_delete;
} else {
stream_get_h = server_stream_make_get_handler();
streams_lookup_h = server_stream_make_lookup_handler();
stream_delete_h = server_stream_make_delete_handler();
stream_get_h = make_stream_get_handler();
streams_lookup_h = make_streams_lookup_handler();
stream_delete_h = make_stream_delete_handler();
}
ctx_http.get ("/v1/stream/:conv_id", ex_wrapper(stream_get_h));
// POST /v1/streams/lookup with body {"conversation_ids": [...]}. you can only ask for ids
@@ -364,7 +364,7 @@ int llama_server(common_params & params, int argc, char ** argv) {
clean_up = [&models_routes]() {
SRV_INF("%s: cleaning up before exit...\n", __func__);
// stop the session GC first, it finalizes live sessions and wakes pending readers
server_stream_session_manager_stop();
g_stream_sessions.stop_gc();
if (models_routes.has_value()) {
models_routes->stopping.store(true); // maybe redundant, but just to be safe
models_routes->models.unload_all();
@@ -392,7 +392,7 @@ int llama_server(common_params & params, int argc, char ** argv) {
clean_up = [&ctx_http, &ctx_server]() {
SRV_INF("%s: cleaning up before exit...\n", __func__);
// stop the session GC first, it finalizes live sessions and wakes pending readers
server_stream_session_manager_stop();
g_stream_sessions.stop_gc();
ctx_http.stop();
ctx_server.terminate();
llama_backend_free();
+4 -4
View File
@@ -11,7 +11,7 @@
"@chromatic-com/storybook": "5.0.0",
"@eslint/compat": "1.4.1",
"@eslint/js": "9.39.2",
"@internationalized/date": "3.12.2",
"@internationalized/date": "3.10.1",
"@lucide/svelte": "0.515.0",
"@modelcontextprotocol/sdk": "1.26.0",
"@playwright/test": "1.56.1",
@@ -2981,9 +2981,9 @@
}
},
"node_modules/@internationalized/date": {
"version": "3.12.2",
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.2.tgz",
"integrity": "sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==",
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.1.tgz",
"integrity": "sha512-oJrXtQiAXLvT9clCf1K4kxp3eKsQhIaZqxEyowkBcsvZDdZkbWrVmnGknxs5flTD0VGsxrxKgBCZty1EzoiMzA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
+1 -1
View File
@@ -30,7 +30,7 @@
"@chromatic-com/storybook": "5.0.0",
"@eslint/compat": "1.4.1",
"@eslint/js": "9.39.2",
"@internationalized/date": "3.12.2",
"@internationalized/date": "3.10.1",
"@lucide/svelte": "0.515.0",
"@modelcontextprotocol/sdk": "1.26.0",
"@playwright/test": "1.56.1",
@@ -11,8 +11,7 @@
} from '$lib/constants';
import {
ChatFormActionAddToolsSubmenu,
ChatFormActionAddMcpServersSubmenu,
ChatFormActionAddReasoningSubmenu
ChatFormActionAddMcpServersSubmenu
} from '$lib/components/app';
import { useAttachmentMenu } from '$lib/hooks/use-attachment-menu.svelte';
@@ -93,11 +92,7 @@
</Tooltip.Content>
</Tooltip.Root>
<DropdownMenu.Content align="start" class="w-52">
<ChatFormActionAddReasoningSubmenu />
<DropdownMenu.Separator />
<DropdownMenu.Content align="start" class="w-48">
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
<File class="h-4 w-4" />
@@ -7,20 +7,14 @@
ChatFormActionModels,
ChatFormActionRecord,
ChatFormActionSubmit,
ChatFormContextGauge
ChatFormReasoningToggle
} from '$lib/components/app';
import { FileTypeCategory, MessageRole } from '$lib/enums';
import { FileTypeCategory } from '$lib/enums';
import { mcpStore } from '$lib/stores/mcp.svelte';
import { config } from '$lib/stores/settings.svelte';
import { activeMessages, conversationsStore } from '$lib/stores/conversations.svelte';
import {
activeProcessingState,
isChatStreaming,
isLoading as chatIsLoading
} from '$lib/stores/chat.svelte';
import { conversationsStore } from '$lib/stores/conversations.svelte';
import { getFileTypeCategory } from '$lib/utils';
import { goto } from '$app/navigation';
import { page } from '$app/state';
import { ROUTES } from '$lib/constants/routes';
interface Props {
@@ -99,36 +93,6 @@
let activeMessage = $derived(
conversationsStore.activeMessages[conversationsStore.activeMessages.length - 1]
);
let hasProcessedTokens = $derived.by(() => {
if (!page.params.id) return false;
const messages = activeMessages() as DatabaseMessage[];
let totalHistoricalTokens = 0;
for (const m of messages) {
if (m.role !== MessageRole.ASSISTANT) continue;
const timings = m.timings;
if (!timings) continue;
const agenticLlm = timings.agentic?.llm;
if (agenticLlm?.prompt_n != null || agenticLlm?.predicted_n != null) {
totalHistoricalTokens += (agenticLlm?.prompt_n ?? 0) + (agenticLlm?.predicted_n ?? 0);
} else {
totalHistoricalTokens += (timings.prompt_n ?? 0) + (timings.predicted_n ?? 0);
}
}
if (totalHistoricalTokens > 0) return true;
if (!chatIsLoading() && !isChatStreaming()) return false;
const processingState = activeProcessingState();
if (!processingState) return false;
const livePromptTokens = Math.max(
processingState.promptTokens ?? 0,
processingState.promptProgress?.processed ?? 0
);
const liveOutputTokens = processingState.outputTokensUsed ?? 0;
return livePromptTokens > 0 || liveOutputTokens > 0;
});
</script>
<div
@@ -136,7 +100,7 @@
style="container-type: inline-size"
>
{#if showAddButton}
<div class="mr-auto flex items-center gap-2">
<div class="mr-auto flex items-center gap-3">
<ChatFormActionsAdd
{disabled}
{hasAudioModality}
@@ -153,10 +117,8 @@
</div>
{/if}
<div class="flex items-center gap-1.5">
{#if hasProcessedTokens}
<ChatFormContextGauge />
{/if}
<div class="flex items-center gap-2">
<ChatFormReasoningToggle />
{#if showModelSelector}
<ChatFormActionModels
@@ -6,7 +6,6 @@
import { REASONING_EFFORT_TOKENS } from '$lib/constants/reasoning-effort-tokens';
import { REASONING_EFFORT_LEVELS } from '$lib/constants/reasoning-effort';
import type { ReasoningEffortLevel } from '$lib/types';
import { DIALOG_SUBMENU_CONTENT } from '$lib/constants/css-classes';
import {
modelsStore,
checkModelSupportsThinking,
@@ -72,7 +71,9 @@
{#if modelSupportsThinking}
<DropdownMenu.Sub bind:open={subOpen}>
<DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
<DropdownMenu.SubTrigger
class="flex cursor-pointer items-center gap-2 rounded-md px-2.5 py-1.5 text-sm transition-colors outline-none hover:bg-accent focus:bg-accent"
>
{#if thinkingEnabled}
<Lightbulb class="h-4 w-4 shrink-0 text-amber-400" />
{:else}
@@ -88,15 +89,23 @@
{/if}
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent class={DIALOG_SUBMENU_CONTENT}>
<DropdownMenu.SubContent
class="w-60 rounded-xl bg-popover p-3 text-popover-foreground shadow-md outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95"
>
{#each REASONING_EFFORT_LEVELS as level (level.value)}
<button
type="button"
class="flex w-full cursor-pointer items-center gap-2"
class="flex w-full cursor-pointer items-center gap-2 rounded-lg px-2.5 py-2 text-left text-sm transition-colors hover:bg-accent"
class:bg-accent={isSelected(level)}
onclick={() => handleSelection(level)}
>
<span class="flex-1 text-left">{level.label}</span>
{#if isSelected(level)}
<Check class="h-4 w-4 shrink-0 text-foreground" />
{:else}
<div class="h-4 w-4 shrink-0"></div>
{/if}
<span class="flex-1">{level.label}</span>
{#if !level.isOff}
<span class="text-[11px] text-muted-foreground opacity-60">
@@ -116,10 +125,6 @@
</Tooltip.Content>
</Tooltip.Root>
{/if}
{#if isSelected(level)}
<Check class="h-4 w-4 shrink-0 text-foreground" />
{/if}
</button>
{/each}
</DropdownMenu.SubContent>
@@ -2,7 +2,7 @@
import { Lightbulb, LightbulbOff, Check, Info } from '@lucide/svelte';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import * as Tooltip from '$lib/components/ui/tooltip';
import { ReasoningEffort } from '$lib/enums';
import { ReasoningEffort, MessageRole } from '$lib/enums';
import { REASONING_EFFORT_TOKENS } from '$lib/constants/reasoning-effort-tokens';
import { REASONING_EFFORT_LEVELS } from '$lib/constants/reasoning-effort';
import type { ReasoningEffortLevel } from '$lib/types';
@@ -18,23 +18,31 @@
import { isRouterMode } from '$lib/stores/server.svelte';
import type { DatabaseMessage } from '$lib/types/database';
let thinkingEnabled = $derived(conversationsStore.getThinkingEnabled());
let currentEffort = $derived(conversationsStore.getReasoningEffort());
let isOff = $derived(!thinkingEnabled);
let tooltipText = $derived(thinkingEnabled ? `${currentEffort} Reasoning` : 'Disabled Reasoning');
let subOpen = $state(false);
// Get conversation model from message history
let conversationModel = $derived(
chatStore.getConversationModel(activeMessages() as DatabaseMessage[])
);
// Fallback: if model props aren't available, check if any assistant messages
// for this model in the active conversation have reasoning content.
let modelSupportsThinkingFromMessages = $derived.by(() => {
const modelId = isRouterMode() ? modelsStore.selectedModelName || conversationModel : null;
if (!modelId) return false;
const messages = conversationsStore.activeMessages;
return messages.some(
(m) => m.role === 'assistant' && m.model === modelId && !!m.reasoningContent
(m: DatabaseMessage) =>
m.role === MessageRole.ASSISTANT && m.model === modelId && !!m.reasoningContent
);
});
// Check if model supports thinking. Primary: chat template from /props.
// Fallback: message history (reasoning content in assistant messages).
let modelSupportsThinking = $derived.by(() => {
loadedModelIds();
propsCacheVersion();
@@ -44,15 +52,15 @@
return checkModelSupportsThinking(modelId ?? '') || modelSupportsThinkingFromMessages;
}
// In non-router mode, use the built-in supportsThinking
return supportsThinking() || modelSupportsThinkingFromMessages;
});
let thinkingEnabled = $derived(conversationsStore.getThinkingEnabled());
let currentEffort = $derived(conversationsStore.getReasoningEffort());
let isOff = $derived(!thinkingEnabled);
// Check if current item is selected
function isSelected(item: ReasoningEffortLevel): boolean {
if (item.isOff) return isOff;
if (item.isOff) {
return isOff;
}
return thinkingEnabled && currentEffort === item.value;
}
@@ -68,30 +76,39 @@
</script>
{#if modelSupportsThinking}
<DropdownMenu.Sub bind:open={subOpen}>
<DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
{#if thinkingEnabled}
<Lightbulb class="h-4 w-4 shrink-0 text-amber-400" />
{:else}
<LightbulbOff class="h-4 w-4 shrink-0 text-muted-foreground" />
{/if}
<DropdownMenu.Root bind:open={subOpen}>
<Tooltip.Root>
<Tooltip.Trigger>
<DropdownMenu.Trigger
class={[
'flex h-6 w-6 cursor-pointer items-center justify-center rounded-full p-0 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
thinkingEnabled ? 'bg-amber-400/10 hover:bg-amber-400/20' : 'bg-muted'
]}
aria-label={`${tooltipText}. Click to configure.`}
>
{#if thinkingEnabled}
<Lightbulb class="h-3 w-3 text-amber-400" />
{:else}
<LightbulbOff class="h-3 w-3 text-muted-foreground" />
{/if}
</DropdownMenu.Trigger>
</Tooltip.Trigger>
<span class="text-sm inline-flex gap-2 {!thinkingEnabled ? 'text-muted-foreground' : ''}">
Reasoning
<Tooltip.Content>
<p class="capitalize">{tooltipText}</p>
</Tooltip.Content>
</Tooltip.Root>
<span class="capitalize text-muted-foreground">
{thinkingEnabled ? currentEffort : 'off'}
</span>
</span>
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent
class="w-60 bg-popover p-1.5 text-popover-foreground shadow-md outline-none"
<DropdownMenu.Content
align="start"
class="w-60 rounded-xl bg-popover p-3 text-popover-foreground shadow-md outline-none"
>
<div class="mb-2 px-2.5 text-sm font-medium">Reasoning effort</div>
{#each REASONING_EFFORT_LEVELS as level (level.value)}
<button
type="button"
class="flex w-full cursor-pointer items-center gap-3 rounded-md px-2 py-1.75 text-left text-sm transition-colors hover:bg-accent"
class="flex w-full cursor-pointer items-center gap-2 rounded-lg px-2.5 py-2 text-left text-sm transition-colors hover:bg-accent"
class:bg-accent={isSelected(level)}
onclick={() => handleSelection(level)}
>
@@ -123,6 +140,6 @@
{/if}
</button>
{/each}
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
</DropdownMenu.Content>
</DropdownMenu.Root>
{/if}
@@ -1,108 +0,0 @@
<script lang="ts">
import { untrack } from 'svelte';
import * as HoverCard from '$lib/components/ui/hover-card';
import { activeConversation, activeMessages } from '$lib/stores/conversations.svelte';
import { chatStore, isChatStreaming, isLoading } from '$lib/stores/chat.svelte';
import { formatParameters } from '$lib/utils/formatters';
import { useContextGauge } from '$lib/hooks/use-context-gauge.svelte';
import ContextGaugeDial from './ContextGaugeDial.svelte';
import ContextGaugeDetails from './ContextGaugeDetails.svelte';
import ContextGaugeLoadModel from './ContextGaugeLoadModel.svelte';
import { colorLevelBgClass, colorLevelTextClass } from './context-gauge';
const gauge = useContextGauge();
$effect(() => {
const conv = activeConversation();
untrack(() => chatStore.setActiveProcessingConversation(conv?.id ?? null));
});
$effect(() => {
const conv = activeConversation();
const messages = activeMessages() as DatabaseMessage[];
if (!conv) return;
if (isLoading() || isChatStreaming()) return;
if (messages.length === 0) {
untrack(() => chatStore.clearProcessingState(conv.id));
return;
}
untrack(() => chatStore.restoreProcessingStateFromMessages(messages, conv.id));
});
$effect(() => {
gauge.startMonitoring();
});
const showProgressBar = $derived(
gauge.contextTotal !== null &&
gauge.contextTotal > 0 &&
(gauge.activeModelId !== null || gauge.isActiveModelLoaded)
);
</script>
<HoverCard.Root>
<HoverCard.Trigger class="flex h-5 w-5 cursor-default items-center justify-center">
<ContextGaugeDial percent={gauge.contextPercent} level={gauge.colorLevel} />
</HoverCard.Trigger>
<HoverCard.Content
side="bottom"
class="z-50 w-64 rounded-lg border border-border/50 bg-popover p-3 text-popover-foreground shadow-lg"
>
<div class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<span class="font-medium">Context</span>
<span class="text-muted-foreground">·</span>
<span class="font-mono text-muted-foreground">
{formatParameters(gauge.contextUsed)}
/ {gauge.contextTotal !== null ? formatParameters(gauge.contextTotal) : '-'}
</span>
</div>
{#if gauge.activeModelId !== null && !gauge.isActiveModelLoaded}
<ContextGaugeLoadModel
modelId={gauge.activeModelId}
isLoading={gauge.isActiveModelLoading}
onLoad={gauge.loadModel}
/>
{:else if showProgressBar}
<div class="h-1.5 w-full overflow-hidden rounded-full bg-muted">
<div
class="h-full rounded-full transition-all duration-300 {colorLevelBgClass(
gauge.colorLevel
)}"
style="width: {gauge.contextPercent}%"
></div>
</div>
<div class="flex justify-between text-xs text-muted-foreground">
<span>
<span class={colorLevelTextClass(gauge.colorLevel)}>{gauge.contextPercent}%</span> used
</span>
<span>
{formatParameters((gauge.contextTotal ?? 0) - gauge.contextUsed)} remaining
</span>
</div>
{:else}
<div class="text-xs text-muted-foreground">No context info available</div>
{/if}
{#if gauge.hasAnyUsage}
<ContextGaugeDetails
currentRead={gauge.currentRead}
currentFresh={gauge.currentFresh}
currentCache={gauge.currentCache}
currentOutput={gauge.currentOutput}
kvTotal={gauge.kvTotal}
cumulativeRead={gauge.cumulativeRead}
cumulativeOutput={gauge.cumulativeOutput}
cumulativeCacheTotal={gauge.cumulativeCacheTotal}
averageTokensPerSecond={gauge.averageTokensPerSecond}
transientDetails={gauge.transientDetails}
/>
{/if}
</div>
</HoverCard.Content>
</HoverCard.Root>
@@ -1,20 +0,0 @@
<script lang="ts">
interface Props {
label: string;
value: string;
subtitle?: string;
}
let { label, value, subtitle }: Props = $props();
</script>
<div class="grid gap-1.5">
<div class="flex items-baseline justify-between">
<span class="text-muted-foreground">{label}</span>
<span class="font-mono text-muted-foreground">{value}</span>
</div>
{#if subtitle}
<div class="text-[10px] leading-tight text-muted-foreground/70">{subtitle}</div>
{/if}
</div>
@@ -1,122 +0,0 @@
<script lang="ts">
import { ChevronDown } from '@lucide/svelte';
import * as Collapsible from '$lib/components/ui/collapsible';
import { STATS_UNITS } from '$lib/constants';
import ContextGaugeDetailRow from './ContextGaugeDetailRow.svelte';
interface Props {
currentRead: number;
currentFresh: number;
currentCache: number;
currentOutput: number;
kvTotal: number;
cumulativeRead: number;
cumulativeOutput: number;
cumulativeCacheTotal: number;
averageTokensPerSecond: number | null;
transientDetails: string[];
}
let {
currentRead,
currentFresh,
currentCache,
currentOutput,
kvTotal,
cumulativeRead,
cumulativeOutput,
cumulativeCacheTotal,
averageTokensPerSecond,
transientDetails
}: Props = $props();
let open = $state(false);
const hasCumulative = $derived(cumulativeRead > 0 || cumulativeOutput > 0);
const hasCurrent = $derived(currentRead > 0 || currentOutput > 0);
</script>
<Collapsible.Root bind:open class="mt-3 border-t border-border/50 pt-4">
<Collapsible.Trigger
class="flex w-full cursor-pointer items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
>
<span>Token usage details</span>
<ChevronDown class={'ml-auto h-3 w-3 transition-transform' + (open ? ' rotate-180' : '')} />
</Collapsible.Trigger>
<Collapsible.Content class="flex flex-col gap-4 text-xs pt-4">
{#if hasCumulative}
<div>
<h3 class="text-[11px] font-medium uppercase tracking-wide text-muted-foreground/70 mb-2">
Across all turns
</h3>
<div class="flex flex-col gap-2">
{#if cumulativeRead > 0}
<ContextGaugeDetailRow
label="Prompt tokens evaluated"
value={`${cumulativeRead.toLocaleString()} tok`}
subtitle={cumulativeCacheTotal > 0
? `${cumulativeCacheTotal.toLocaleString()} reused from KV cache`
: undefined}
/>
{/if}
{#if cumulativeOutput > 0}
<ContextGaugeDetailRow
label="Tokens generated"
value={`${cumulativeOutput.toLocaleString()} tok`}
/>
{/if}
</div>
</div>
{/if}
{#if hasCurrent}
<div>
<h3 class="text-[11px] font-medium uppercase tracking-wide text-muted-foreground/70 mb-2">
This turn · KV cache
</h3>
<div class="flex flex-col gap-2">
{#if currentRead > 0}
<ContextGaugeDetailRow
label="Prompt"
value={`${currentRead.toLocaleString()} tok`}
subtitle={currentCache > 0
? `${currentFresh.toLocaleString()} fresh + ${currentCache.toLocaleString()} cached`
: undefined}
/>
{/if}
{#if currentOutput > 0}
<ContextGaugeDetailRow
label="Generated"
value={`${currentOutput.toLocaleString()} tok`}
/>
{/if}
<div class="pt-1 mt-0.5 border-t border-border/30">
<div class="flex justify-between">
<span class="text-muted-foreground">KV cache total</span>
<span class="font-mono font-medium">{kvTotal.toLocaleString()} tok</span>
</div>
</div>
</div>
</div>
{/if}
{#if averageTokensPerSecond !== null}
<div class="pt-1.5 mt-1 border-t border-border/30">
<ContextGaugeDetailRow
label="Avg speed"
value={`${averageTokensPerSecond.toFixed(1)}${STATS_UNITS.TOKENS_PER_SECOND}`}
/>
</div>
{/if}
{#each transientDetails as detail (detail)}
<div class="font-mono text-muted-foreground">{detail}</div>
{/each}
</Collapsible.Content>
</Collapsible.Root>
@@ -1,43 +0,0 @@
<script lang="ts">
import type { ColorLevel } from './context-gauge';
import { colorLevelTextClass } from './context-gauge';
interface Props {
percent: number | null;
level: ColorLevel;
size?: 'sm' | 'md';
}
let { percent, level, size = 'sm' }: Props = $props();
const RADIUS = 11;
const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
const strokeLevelClass = $derived(colorLevelTextClass(level));
const dimensions = $derived(size === 'md' ? 'h-6 w-6' : 'h-5 w-5');
const strokeWidth = $derived(size === 'md' ? 4 : 3);
</script>
<svg viewBox="0 0 32 32" fill="none" class={dimensions}>
<circle
cx="16"
cy="16"
r={RADIUS}
stroke="currentColor"
stroke-opacity="0.1"
stroke-width={strokeWidth}
/>
<circle
cx="16"
cy="16"
r={RADIUS}
class="transition-colors duration-300 {strokeLevelClass}"
stroke="currentColor"
stroke-width={strokeWidth}
stroke-linecap="round"
stroke-dasharray={CIRCUMFERENCE}
stroke-dashoffset={percent !== null ? CIRCUMFERENCE * (1 - percent / 100) : CIRCUMFERENCE}
transform="rotate(-90 16 16)"
/>
</svg>
@@ -1,24 +0,0 @@
<script lang="ts">
import { Loader2 } from '@lucide/svelte';
import { Button } from '$lib/components/ui/button';
interface Props {
modelId: string | null;
isLoading: boolean;
onLoad: () => void;
}
let { modelId, isLoading, onLoad }: Props = $props();
</script>
{#if modelId !== null && !isLoading}
<div class="flex flex-col gap-2 border-t border-border/50 pt-2 text-xs text-muted-foreground">
<span>Available context size is only visible once the model is loaded.</span>
<Button size="sm" variant="secondary" class="self-start" onclick={onLoad}>Load model</Button>
</div>
{:else if isLoading}
<div class="flex items-center gap-2 border-t border-border/50 pt-2 text-xs text-muted-foreground">
<Loader2 class="h-3.5 w-3.5 animate-spin" />
<span>Loading model...</span>
</div>
{/if}
@@ -1,37 +0,0 @@
export type ColorLevel = 'ok' | 'warning' | 'critical' | 'neutral';
const WARNING_THRESHOLD = 80;
const CRITICAL_THRESHOLD = 95;
export function colorLevelFromPercent(percent: number | null): ColorLevel {
if (percent === null) return 'neutral';
if (percent >= CRITICAL_THRESHOLD) return 'critical';
if (percent >= WARNING_THRESHOLD) return 'warning';
return 'ok';
}
export function colorLevelTextClass(level: ColorLevel): string {
switch (level) {
case 'critical':
return 'text-red-400';
case 'warning':
return 'text-amber-400';
case 'ok':
return 'text-muted-foreground';
default:
return 'text-muted-foreground';
}
}
export function colorLevelBgClass(level: ColorLevel): string {
switch (level) {
case 'critical':
return 'bg-red-500';
case 'warning':
return 'bg-amber-500';
case 'ok':
return 'bg-green-500';
default:
return 'bg-muted';
}
}
@@ -24,8 +24,6 @@
message: DatabaseMessage;
toolMessages?: DatabaseMessage[];
isLastAssistantMessage?: boolean;
isLastUserMessage?: boolean;
nextAssistantMessage?: DatabaseMessage | null;
siblingInfo?: ChatMessageSiblingInfo | null;
}
@@ -34,8 +32,6 @@
message,
toolMessages = [],
isLastAssistantMessage = false,
isLastUserMessage = false,
nextAssistantMessage = null,
siblingInfo = null
}: Props = $props();
@@ -363,9 +359,7 @@
<ChatMessageUser
class={className}
{deletionInfo}
{isLastUserMessage}
{message}
{nextAssistantMessage}
onConfirmDelete={handleConfirmDelete}
onCopy={handleCopy}
onDelete={handleDelete}
@@ -11,10 +11,11 @@
import { useProcessingState } from '$lib/hooks/use-processing-state.svelte';
import { isLoading, isChatStreaming } from '$lib/stores/chat.svelte';
import { copyToClipboard, deriveAgenticSections, modelLoadProgressText } from '$lib/utils';
import { AgenticSectionType, ChatMessageStatisticsMode } from '$lib/enums';
import { AgenticSectionType } from '$lib/enums';
import { REASONING_TAGS } from '$lib/constants/agentic';
import { tick } from 'svelte';
import { fade } from 'svelte/transition';
import { MessageRole } from '$lib/enums';
import { MessageRole, ChatMessageStatsView } from '$lib/enums';
import { config } from '$lib/stores/settings.svelte';
import { isRouterMode } from '$lib/stores/server.svelte';
import { modelsStore } from '$lib/stores/models.svelte';
@@ -121,6 +122,62 @@
return parts.join('\n\n\n');
});
let activeStatsView = $state<ChatMessageStatsView>(ChatMessageStatsView.GENERATION);
let statsContainerEl: HTMLDivElement | undefined = $state();
function getScrollParent(el: HTMLElement): HTMLElement | null {
let parent = el.parentElement;
while (parent) {
const style = getComputedStyle(parent);
if (/(auto|scroll)/.test(style.overflowY)) {
return parent;
}
parent = parent.parentElement;
}
return null;
}
async function handleStatsViewChange(view: ChatMessageStatsView) {
const el = statsContainerEl;
if (!el) {
activeStatsView = view;
return;
}
const scrollParent = getScrollParent(el);
if (!scrollParent) {
activeStatsView = view;
return;
}
const yBefore = el.getBoundingClientRect().top;
activeStatsView = view;
await tick();
const delta = el.getBoundingClientRect().top - yBefore;
if (delta !== 0) {
scrollParent.scrollTop += delta;
}
// Correct any drift after browser paint
requestAnimationFrame(() => {
const drift = el.getBoundingClientRect().top - yBefore;
if (Math.abs(drift) > 1) {
scrollParent.scrollTop += drift;
}
});
}
let highlightAgenticTurns = $derived(
isAgentic &&
(currentConfig.alwaysShowAgenticTurns || activeStatsView === ChatMessageStatsView.SUMMARY)
);
let displayedModel = $derived(message.model ?? null);
// model being switched to while it loads, so the selector bar tracks it
@@ -234,6 +291,7 @@
{toolMessages}
isStreaming={isChatStreaming()}
{isLastAssistantMessage}
highlightTurns={highlightAgenticTurns}
/>
{/if}
{:else}
@@ -257,7 +315,10 @@
<div class="info my-6 grid gap-4 tabular-nums">
{#if displayedModel}
<div class="inline-flex flex-wrap items-start gap-2 text-xs text-muted-foreground">
<div
bind:this={statsContainerEl}
class="inline-flex flex-wrap items-start gap-2 text-xs text-muted-foreground"
>
{#if isRouter}
<ModelsSelectorDropdown
currentModel={pendingModel ?? displayedModel}
@@ -286,25 +347,28 @@
{#if currentConfig.showMessageStats && message.timings && message.timings.predicted_n && message.timings.predicted_ms}
{@const agentic = message.timings.agentic}
<ChatMessageStatistics
mode={ChatMessageStatisticsMode.GENERATION}
promptTokens={agentic ? agentic.llm.prompt_n : message.timings.prompt_n}
promptMs={agentic ? agentic.llm.prompt_ms : message.timings.prompt_ms}
predictedTokens={agentic ? agentic.llm.predicted_n : message.timings.predicted_n}
predictedMs={agentic ? agentic.llm.predicted_ms : message.timings.predicted_ms}
agenticTimings={agentic}
onActiveViewChange={handleStatsViewChange}
/>
{:else if isLoading() && currentConfig.showMessageStats}
{@const liveStats = processingState.getLiveProcessingStats()}
{@const genStats = processingState.getLiveGenerationStats()}
{@const promptProgress = processingState.processingState?.promptProgress}
{@const isStillProcessingPrompt =
promptProgress && promptProgress.processed < promptProgress.total}
{#if genStats}
{#if liveStats || genStats}
<ChatMessageStatistics
mode={ChatMessageStatisticsMode.GENERATION}
isLive
isProcessingPrompt={!!isStillProcessingPrompt}
promptTokens={liveStats?.tokensProcessed}
promptMs={liveStats?.timeMs}
predictedTokens={genStats.tokensGenerated}
predictedMs={genStats.timeMs}
predictedTokens={genStats?.tokensGenerated}
predictedMs={genStats?.timeMs}
/>
{/if}
{/if}
@@ -2,14 +2,10 @@
import {
ChatMessageActionIcons,
ChatMessageEditForm,
ChatMessageStatistics,
ChatMessageUserBubble
} from '$lib/components/app/chat';
import { getMessageEditContext } from '$lib/contexts';
import { useProcessingState } from '$lib/hooks/use-processing-state.svelte';
import { isLoading } from '$lib/stores/chat.svelte';
import { MessageRole, ChatMessageStatisticsMode } from '$lib/enums';
import { config } from '$lib/stores/settings.svelte';
import { MessageRole } from '$lib/enums';
interface Props {
class?: string;
@@ -21,8 +17,6 @@
assistantMessages: number;
messageTypes: string[];
} | null;
isLastUserMessage?: boolean;
nextAssistantMessage?: DatabaseMessage | null;
showDeleteDialog: boolean;
onEdit: () => void;
onDelete: () => void;
@@ -38,8 +32,6 @@
message,
siblingInfo = null,
deletionInfo,
isLastUserMessage = false,
nextAssistantMessage = null,
showDeleteDialog,
onEdit,
onDelete,
@@ -52,37 +44,6 @@
// Get contexts
const editCtx = getMessageEditContext();
const processingState = useProcessingState();
const currentConfig = $derived(config());
const isActivelyProcessing = $derived(isLastUserMessage && isLoading());
// For agentic turns, prefer the cumulative agentic.llm totals over per-call timings.
let storedReadingStats = $derived.by(() => {
const timings = nextAssistantMessage?.timings;
if (!timings?.prompt_n || !timings?.prompt_ms) return null;
const agentic = timings.agentic;
return {
promptTokens: agentic ? agentic.llm.prompt_n : timings.prompt_n,
promptMs: agentic ? agentic.llm.prompt_ms : timings.prompt_ms
};
});
let showStoredReadingStats = $derived(
Boolean(currentConfig.showMessageStats) && storedReadingStats !== null
);
let showLiveReadingStats = $derived(
Boolean(currentConfig.showMessageStats) && isActivelyProcessing && storedReadingStats === null
);
$effect(() => {
if (showLiveReadingStats) {
processingState.startMonitoring();
}
});
</script>
<div
@@ -99,37 +60,6 @@
renderMarkdown={true}
/>
{#if showStoredReadingStats}
<!-- Reading stats sourced from the assistant message that followed this turn -->
<div class="info my-2 grid w-full justify-items-end gap-4 tabular-nums">
<div
class="inline-flex flex-wrap items-start justify-end gap-2 text-xs text-muted-foreground"
>
<ChatMessageStatistics
mode={ChatMessageStatisticsMode.READING}
promptTokens={storedReadingStats!.promptTokens}
promptMs={storedReadingStats!.promptMs}
/>
</div>
</div>
{:else if showLiveReadingStats}
{@const liveStats = processingState.getLiveProcessingStats()}
{#if liveStats}
<div class="info my-2 grid w-full justify-items-end gap-4 tabular-nums">
<div
class="inline-flex flex-wrap items-start justify-end gap-2 text-xs text-muted-foreground"
>
<ChatMessageStatistics
mode={ChatMessageStatisticsMode.READING}
isLive
promptTokens={liveStats.tokensProcessed}
promptMs={liveStats.timeMs}
/>
</div>
</div>
{/if}
{/if}
{#if message.timestamp}
<div class="max-w-[80%]">
<ChatMessageActionIcons
@@ -2,6 +2,7 @@
import { ActionIcon, ChatMessageEditForm, ChatMessageUserBubble } from '$lib/components/app';
import { fadeInView } from '$lib/actions/fade-in-view.svelte';
import { ArrowUp, Edit, Trash2 } from '@lucide/svelte';
import { getProcessingInfoContext } from '$lib/contexts';
import { useMessageEditContext } from '$lib/hooks/use-message-edit-context.svelte';
interface Props {
@@ -22,6 +23,9 @@
onDelete
}: Props = $props();
const processingInfoCtx = getProcessingInfoContext();
let showProcessingInfo = $derived(processingInfoCtx.showProcessingInfo);
const editCtx = useMessageEditContext({
getContent: () => content,
getExtras: () => extras,
@@ -32,7 +36,9 @@
<div
use:fadeInView
aria-label="Pending user message"
class="group flex flex-col items-end gap-3 transition-opacity hover:opacity-80 md:gap-2 {className} sticky bottom-32"
class="group flex flex-col items-end gap-3 transition-opacity hover:opacity-80 md:gap-2 {className} sticky {showProcessingInfo
? 'bottom-44'
: 'bottom-32'}"
role="group"
>
{#if editCtx.isEditing}
@@ -41,13 +41,15 @@
toolMessages?: DatabaseMessage[];
isStreaming?: boolean;
isLastAssistantMessage?: boolean;
highlightTurns?: boolean;
}
let {
message,
toolMessages = [],
isStreaming = false,
isLastAssistantMessage = false
isLastAssistantMessage = false,
highlightTurns = false
}: Props = $props();
let expandedStates: Record<number, boolean> = $state({});
@@ -55,7 +57,6 @@
const showToolCallInProgress = $derived(config().showToolCallInProgress as boolean);
const showThoughtInProgress = $derived(config().showThoughtInProgress as boolean);
const renderThinkingAsMarkdown = $derived(config().renderThinkingAsMarkdown as boolean);
const showMessageStats = $derived(config().showMessageStats as boolean);
const hasReasoningError = $derived(
isLastAssistantMessage ? !!agenticLastError(message.convId) : false
@@ -353,17 +354,16 @@
{/snippet}
<div class="agentic-content">
{#if turnGroups.length > 1}
{#if highlightTurns && turnGroups.length > 1}
{#each turnGroups as turn, turnIndex (turnIndex)}
{@const turnStats = message?.timings?.agentic?.perTurn?.[turnIndex]}
<div class="agentic-turn group/turn grid gap-3 mb-4">
<div class="agentic-turn my-2 hover:bg-muted/80 dark:hover:bg-muted/30">
<span class="agentic-turn-label">Turn {turnIndex + 1}</span>
{#each turn.sections as section, sIdx (turn.flatIndices[sIdx])}
{@render renderSection(section, turn.flatIndices[sIdx])}
{/each}
{#if turnStats && showMessageStats}
<div class="turn-stats transition-opacity duration-150">
{#if turnStats}
<div class="turn-stats">
<ChatMessageStatistics
promptTokens={turnStats.llm.prompt_n}
promptMs={turnStats.llm.prompt_ms}
@@ -402,21 +402,39 @@
.agentic-content {
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 100%;
max-width: 48rem;
gap: 1rem;
}
.agentic-content > :global(*),
.agentic-turn > :global(*) {
min-width: 0;
}
.agentic-text {
width: 100%;
}
.agentic-turn {
position: relative;
border: 1.5px dashed var(--muted-foreground);
border-radius: 0.75rem;
padding: 1rem;
transition: background 0.1s;
}
.agentic-turn-label {
position: absolute;
top: -1rem;
left: 0.75rem;
padding: 0 0.375rem;
background: var(--background);
font-size: 0.7rem;
font-weight: 500;
color: var(--muted-foreground);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.turn-stats {
margin-top: 0.75rem;
padding-top: 0.5rem;
border-top: 1px solid hsl(var(--muted) / 0.5);
}
</style>
@@ -2,7 +2,7 @@
import { Clock, Gauge, WholeWord, BookOpenText, Sparkles, Wrench, Layers } from '@lucide/svelte';
import { ChatMessageStatisticsBadge } from '$lib/components/app';
import * as Tooltip from '$lib/components/ui/tooltip';
import { ChatMessageStatsView, ChatMessageStatisticsMode } from '$lib/enums';
import { ChatMessageStatsView } from '$lib/enums';
import type { ChatMessageAgenticTimings } from '$lib/types/chat';
import { formatPerformanceTime } from '$lib/utils';
import { MS_PER_SECOND, DEFAULT_PERFORMANCE_TIME } from '$lib/constants';
@@ -19,7 +19,6 @@
agenticTimings?: ChatMessageAgenticTimings;
onActiveViewChange?: (view: ChatMessageStatsView) => void;
hideSummary?: boolean;
mode?: ChatMessageStatisticsMode;
}
let {
@@ -32,30 +31,19 @@
initialView = ChatMessageStatsView.GENERATION,
agenticTimings,
onActiveViewChange,
hideSummary = false,
mode = ChatMessageStatisticsMode.SWITCHABLE
hideSummary = false
}: Props = $props();
let isSwitchable = $derived(mode === ChatMessageStatisticsMode.SWITCHABLE);
let activeView: ChatMessageStatsView = $derived(
mode === ChatMessageStatisticsMode.READING
? ChatMessageStatsView.READING
: mode === ChatMessageStatisticsMode.GENERATION
? ChatMessageStatsView.GENERATION
: initialView
);
let activeView: ChatMessageStatsView = $derived(initialView);
let hasAutoSwitchedToGeneration = $state(false);
$effect(() => {
if (isSwitchable) {
onActiveViewChange?.(activeView);
}
onActiveViewChange?.(activeView);
});
// In live mode: auto-switch to GENERATION tab when prompt processing completes
$effect(() => {
if (isLive && isSwitchable) {
if (isLive) {
// Auto-switch to generation tab only when prompt processing is done (once)
if (
!hasAutoSwitchedToGeneration &&
@@ -103,7 +91,8 @@
formattedPromptTime !== undefined
);
let isGenerationDisabled = $derived(isLive && isSwitchable && !hasGenerationStats);
// In live mode, generation tab is disabled until we have generation stats
let isGenerationDisabled = $derived(isLive && !hasGenerationStats);
let hasAgenticStats = $derived(agenticTimings !== undefined && agenticTimings.toolCallsCount > 0);
@@ -164,44 +153,44 @@
{/snippet}
<div class="inline-flex items-center text-xs text-muted-foreground">
{#if isSwitchable}
<div class="inline-flex items-center rounded-sm bg-muted-foreground/15 p-0.5">
{#if hasPromptStats || isLive}
{@render viewButton({
view: ChatMessageStatsView.READING,
icon: BookOpenText,
label: 'Reading',
tooltipText: 'Processing'
})}
{/if}
<div class="inline-flex items-center rounded-sm bg-muted-foreground/15 p-0.5">
{#if hasPromptStats || isLive}
{@render viewButton({
view: ChatMessageStatsView.GENERATION,
icon: Sparkles,
label: 'Generation',
tooltipText: isGenerationDisabled ? 'Waiting for tokens...' : 'Generation',
disabled: isGenerationDisabled
view: ChatMessageStatsView.READING,
icon: BookOpenText,
label: 'Reading',
tooltipText: 'Reading (prompt processing)'
})}
{/if}
{@render viewButton({
view: ChatMessageStatsView.GENERATION,
icon: Sparkles,
label: 'Generation',
tooltipText: isGenerationDisabled
? 'Generation (waiting for tokens...)'
: 'Generation (token output)',
disabled: isGenerationDisabled
})}
{#if hasAgenticStats}
{@render viewButton({
view: ChatMessageStatsView.TOOLS,
icon: Wrench,
label: 'Tools',
tooltipText: 'Tool calls'
})}
{#if hasAgenticStats}
{#if !hideSummary}
{@render viewButton({
view: ChatMessageStatsView.TOOLS,
icon: Wrench,
label: 'Tools',
tooltipText: 'Tool calls'
view: ChatMessageStatsView.SUMMARY,
icon: Layers,
label: 'Summary',
tooltipText: 'Agentic summary'
})}
{#if !hideSummary}
{@render viewButton({
view: ChatMessageStatsView.SUMMARY,
icon: Layers,
label: 'Summary',
tooltipText: 'Agentic summary'
})}
{/if}
{/if}
</div>
{/if}
{/if}
</div>
<div class="flex items-center gap-1 px-2">
{#if activeView === ChatMessageStatsView.GENERATION && hasGenerationStats}
@@ -267,7 +256,7 @@
value={formattedAgenticTotalTime}
tooltipLabel="Total time (LLM + tools)"
/>
{:else if hasPromptStats && (mode === ChatMessageStatisticsMode.READING || isSwitchable)}
{:else if hasPromptStats}
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={WholeWord}
@@ -186,8 +186,6 @@
message: DatabaseMessage;
toolMessages: DatabaseMessage[];
isLastAssistantMessage: boolean;
isLastUserMessage: boolean;
nextAssistantMessage: DatabaseMessage | null;
siblingInfo: ChatMessageSiblingInfo;
}> = [];
@@ -238,36 +236,18 @@
message: msg,
toolMessages,
isLastAssistantMessage: false,
isLastUserMessage: false,
nextAssistantMessage: null,
siblingInfo
});
}
let lastAssistantIdx = -1;
// Mark the last assistant message
for (let i = result.length - 1; i >= 0; i--) {
if (result[i].message.role === MessageRole.ASSISTANT) {
result[i].isLastAssistantMessage = true;
lastAssistantIdx = i;
break;
}
}
if (lastAssistantIdx > 0 && result[lastAssistantIdx - 1].message.role === MessageRole.USER) {
result[lastAssistantIdx - 1].isLastUserMessage = true;
}
for (let i = 0; i < result.length; i++) {
if (result[i].message.role !== MessageRole.USER) continue;
for (let j = i + 1; j < result.length; j++) {
if (result[j].message.role === MessageRole.ASSISTANT) {
result[i].nextAssistantMessage = result[j].message;
break;
}
}
}
return result;
});
</script>
@@ -277,14 +257,12 @@
{isVisible ? 'opacity-100' : 'opacity-0'}
{previousRouteId === '/(chat)/chat/[id]' ? '' : 'delay-300'}"
>
{#each displayMessages as { message, toolMessages, isLastAssistantMessage, isLastUserMessage, nextAssistantMessage, siblingInfo } (message.id)}
{#each displayMessages as { message, toolMessages, isLastAssistantMessage, siblingInfo } (message.id)}
<ChatMessage
class="mx-auto mt-12 w-full max-w-3xl"
{message}
{toolMessages}
{isLastAssistantMessage}
{isLastUserMessage}
{nextAssistantMessage}
{siblingInfo}
/>
{/each}
@@ -4,10 +4,12 @@
ChatScreenForm,
ChatMessages,
ChatScreenDragOverlay,
ChatScreenProcessingInfo,
ChatScreenStreamResumeStatus,
ServerLoadingSplash,
ChatScreenServerError
} from '$lib/components/app';
import { setProcessingInfoContext } from '$lib/contexts';
import { createAutoScrollController } from '$lib/hooks/use-auto-scroll.svelte';
import { useChatScreenActiveModel } from '$lib/hooks/use-chat-screen-active-model.svelte';
import { useChatScreenDragAndDrop } from '$lib/hooks/use-chat-screen-drag-and-drop.svelte';
@@ -21,7 +23,8 @@
errorDialog,
isLoading,
isChatStreaming,
isEditing
isEditing,
activeProcessingState
} from '$lib/stores/chat.svelte';
import {
conversationsStore,
@@ -39,6 +42,12 @@
let { showCenteredEmpty = false } = $props();
setProcessingInfoContext({
get showProcessingInfo() {
return showProcessingInfo;
}
});
let disableAutoScroll = $derived(Boolean(config().disableAutoScroll) || isMobile.current);
let isMobileUserScrolledUp = $state(false);
let mobileScrollDownHint = $state(false);
@@ -54,6 +63,11 @@
let isServerLoading = $derived(serverLoading());
let hasPropsError = $derived(!!serverError());
let isCurrentConversationLoading = $derived(isLoading() || isChatStreaming());
let showProcessingInfo = $derived(
isCurrentConversationLoading ||
(config().keepStatsVisible && !!page.params.id) ||
activeProcessingState() !== null
);
let chatFormBottomPosition = $derived.by(() => {
if (!isMobile.current) return '1rem';
if (device.isStandalone) return '1.5rem';
@@ -284,6 +298,10 @@
}}
/>
{/if}
{#if showProcessingInfo}
<ChatScreenProcessingInfo />
{/if}
</div>
<ChatScreenForm
@@ -0,0 +1,127 @@
<script lang="ts">
import { untrack } from 'svelte';
import { PROCESSING_INFO_TIMEOUT } from '$lib/constants';
import { useProcessingState } from '$lib/hooks/use-processing-state.svelte';
import { chatStore, isLoading, isChatStreaming } from '$lib/stores/chat.svelte';
import { activeMessages, activeConversation } from '$lib/stores/conversations.svelte';
import { config } from '$lib/stores/settings.svelte';
const processingState = useProcessingState();
let isCurrentConversationLoading = $derived(isLoading());
let isStreaming = $derived(isChatStreaming());
let processingDetails = $derived(processingState.getTechnicalDetails());
let processingVisible = $derived(processingDetails.length > 0);
let { onVisibilityChange }: { onVisibilityChange?: (visible: boolean) => void } = $props();
$effect(() => {
onVisibilityChange?.(processingVisible);
});
$effect(() => {
const conversation = activeConversation();
untrack(() => chatStore.setActiveProcessingConversation(conversation?.id ?? null));
});
$effect(() => {
const keepStatsVisible = config().keepStatsVisible;
const shouldMonitor = keepStatsVisible || isCurrentConversationLoading || isStreaming;
if (shouldMonitor) {
processingState.startMonitoring();
}
if (!isCurrentConversationLoading && !isStreaming && !keepStatsVisible) {
const timeout = setTimeout(() => {
if (!config().keepStatsVisible && !isChatStreaming()) {
processingState.stopMonitoring();
}
}, PROCESSING_INFO_TIMEOUT);
return () => clearTimeout(timeout);
}
});
$effect(() => {
const conversation = activeConversation();
const messages = activeMessages() as DatabaseMessage[];
const keepStatsVisible = config().keepStatsVisible;
if (keepStatsVisible && conversation) {
if (messages.length === 0) {
untrack(() => chatStore.clearProcessingState(conversation.id));
return;
}
if (!isCurrentConversationLoading && !isStreaming) {
untrack(() => chatStore.restoreProcessingStateFromMessages(messages, conversation.id));
}
}
});
</script>
<div
class={[
'chat-processing-info-container pointer-events-none relative w-full hidden md:block',
processingVisible && 'visible'
]}
>
<div class="chat-processing-info-content absolute bottom-4 left-1/2 -translate-x-1/2">
{#each processingDetails as detail (detail)}
<span class="chat-processing-info-detail pointer-events-auto backdrop-blur-sm">{detail}</span>
{/each}
</div>
</div>
<style>
.chat-processing-info-container {
position: sticky;
top: 0;
z-index: 10;
padding: 0 1rem 0.75rem;
opacity: 0;
transform: translateY(50%);
transition:
opacity 300ms ease-out,
transform 300ms ease-out;
}
.chat-processing-info-container.visible {
opacity: 1;
transform: translateY(0);
}
.chat-processing-info-content {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
justify-content: center;
max-width: 48rem;
margin: 0 auto;
}
.chat-processing-info-detail {
color: var(--muted-foreground);
font-size: 0.75rem;
padding: 0.25rem 0.75rem;
border-radius: 0.375rem;
font-family:
ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
white-space: nowrap;
}
@media (max-width: 768px) {
.chat-processing-info-content {
gap: 0.5rem;
}
.chat-processing-info-detail {
font-size: 0.7rem;
padding: 0.2rem 0.5rem;
}
}
</style>
+12 -9
View File
@@ -241,18 +241,13 @@ export { default as ChatFormActionAddToolsSubmenu } from './ChatForm/ChatFormAct
export { default as ChatFormActionAddMcpServersSubmenu } from './ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddMcpServersSubmenu.svelte';
/**
* Dropdown submenu for selecting reasoning effort level.
* **ChatFormReasoningToggle** - Thinking toggle button with effort dropdown
*
* Shows a "Reasoning" sub-menu item with a lightbulb icon indicating
* thinking status, and a nested list of effort levels.
* A toggle button with lightbulb icon that indicates thinking status.
* Shows the reasoning effort dropdown when clicked.
* Only visible when the current model supports thinking.
*/
export { default as ChatFormActionAddReasoningSubmenu } from './ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddReasoningSubmenu.svelte';
/**
* Compact context-usage gauge with per-turn and cumulative breakdown in the tooltip.
*/
export { default as ChatFormContextGauge } from './ChatForm/ChatFormContextGauge/ChatFormContextGauge.svelte';
export { default as ChatFormReasoningToggle } from './ChatForm/ChatFormActions/ChatFormReasoningToggle.svelte';
/**
* Hidden file input element for programmatic file selection.
@@ -674,6 +669,14 @@ export { default as ChatScreenDragOverlay } from './ChatScreen/ChatScreenDragOve
*/
export { default as ChatScreenForm } from './ChatScreen/ChatScreenForm.svelte';
/**
* Processing info display during generation. Shows real-time statistics:
* tokens per second, prompt/completion token counts, and elapsed time.
* Data sourced from slotsService polling during active generation.
* Only visible when `isCurrentConversationLoading` is true.
*/
export { default as ChatScreenProcessingInfo } from './ChatScreen/ChatScreenProcessingInfo.svelte';
/**
* Server error alert displayed when the server is unreachable.
* Shows the error message with a retry button.
@@ -76,7 +76,7 @@
open = value;
onToggle?.();
}}
class="{className} my-0!"
class={className}
>
<Card class="gap-0 border-muted bg-muted/30 py-0">
<Collapsible.Trigger class="flex w-full cursor-pointer items-start justify-between gap-2 p-3">
@@ -72,8 +72,8 @@
</script>
<div
class="code-preview-wrapper min-w-0 max-w-full overflow-x-auto rounded-lg border border-border bg-muted {className}"
style="max-height: {maxHeight}; {maxWidth ? `max-width: ${maxWidth};` : ''}"
class="code-preview-wrapper rounded-lg border border-border bg-muted {className}"
style="max-height: {maxHeight}; max-width: {maxWidth};"
>
<!-- Needs to be formatted as single line for proper rendering -->
<pre class="m-0"><code class="hljs text-sm leading-relaxed">{@html highlightedHtml}</code></pre>
@@ -4,10 +4,9 @@
import * as Dialog from '$lib/components/ui/dialog';
import { fly } from 'svelte/transition';
import { McpServerCardCompact, McpServerForm } from '$lib/components/app/mcp';
import { RECOMMENDED_MCP_SERVERS, SETTINGS_KEYS } from '$lib/constants';
import { RECOMMENDED_MCP_SERVERS } from '$lib/constants';
import { conversationsStore } from '$lib/stores/conversations.svelte';
import { mcpStore } from '$lib/stores/mcp.svelte';
import { settingsStore } from '$lib/stores/settings.svelte';
import { uuid } from '$lib/utils';
import { MCP_SERVERS_ADDED_TO_CHAT_LOCALSTORAGE_KEY, MCP_SERVER_ID_PREFIX } from '$lib/constants';
import type { MCPServerSettingsEntry } from '$lib/types';
@@ -25,22 +24,6 @@
);
let addedServers = $state<MCPServerSettingsEntry[]>([]);
let didAddAny = $state(false);
let selectedRecommendedCount = $derived.by(
() => RECOMMENDED_MCP_SERVERS.filter((server) => selected[server.id]).length
);
let footerLabel = $derived.by(() => {
const recommended = selectedRecommendedCount;
const custom = addedServers.length;
const total = recommended + custom;
if (total === 0) return 'Continue';
if (recommended === 0) return custom === 1 ? 'Add server' : `Add ${custom} servers`;
if (custom === 0) return recommended === 1 ? 'Add server' : `Add ${recommended} servers`;
return `Add ${recommended} servers and ${custom} custom`;
});
let showAddForm = $state(false);
let newServerUrl = $state('');
@@ -61,14 +44,9 @@
showAddForm = false;
newServerUrl = '';
newServerHeaders = '';
if (!didAddAny) {
settingsStore.updateConfig(SETTINGS_KEYS.MCP_SERVERS, []);
}
addedServers = [];
localStorage.setItem(MCP_SERVERS_ADDED_TO_CHAT_LOCALSTORAGE_KEY, 'true');
addedServers = [];
didAddAny = false;
}
open = value;
onOpenChange?.(value);
@@ -81,7 +59,6 @@
}
function enableSelected() {
didAddAny = true;
localStorage.setItem(MCP_SERVERS_ADDED_TO_CHAT_LOCALSTORAGE_KEY, 'true');
for (const server of RECOMMENDED_MCP_SERVERS) {
@@ -106,8 +83,6 @@
function saveNewServer() {
if (newServerUrlError) return;
didAddAny = true;
const newServerId = uuid() ?? `${MCP_SERVER_ID_PREFIX}-${Date.now()}`;
localStorage.setItem(MCP_SERVERS_ADDED_TO_CHAT_LOCALSTORAGE_KEY, 'true');
@@ -199,12 +174,7 @@
<Dialog.Footer>
<Button variant="secondary" size="sm" onclick={() => handleOpenChange(false)}>Not now</Button>
<Button
variant="default"
size="sm"
onclick={enableSelected}
disabled={footerLabel === 'Continue'}>{footerLabel}</Button
>
<Button variant="default" size="sm" onclick={enableSelected}>Add selected</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
@@ -39,17 +39,13 @@
{@const faviconUrl = group.serverId ? mcpStore.getServerFavicon(group.serverId) : null}
<span class="inline-flex min-w-0 items-center gap-1.5 font-medium">
{#if group.source === 'mcp'}
<McpServerIdentity
iconClass="h-4 w-4"
iconRounded="rounded-sm"
showVersion={false}
displayName={group.label}
{faviconUrl}
/>
{:else}
<TruncatedText text={group.label} class="font-medium" />
{/if}
<McpServerIdentity
iconClass="h-4 w-4"
iconRounded="rounded-sm"
showVersion={false}
displayName={group.label}
{faviconUrl}
/>
</span>
<span class="ml-auto shrink-0 text-xs text-muted-foreground">
@@ -1,31 +0,0 @@
<script lang="ts">
import { LinkPreview as HoverCardPrimitive } from 'bits-ui';
import { cn, type WithoutChildrenOrChild } from '$lib/components/ui/utils.js';
import HoverCardPortal from './hover-card-portal.svelte';
import type { ComponentProps } from 'svelte';
let {
ref = $bindable(null),
class: className,
align = 'center',
sideOffset = 4,
portalProps,
...restProps
}: HoverCardPrimitive.ContentProps & {
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof HoverCardPortal>>;
} = $props();
</script>
<HoverCardPortal {...portalProps}>
<HoverCardPrimitive.Content
bind:ref
data-slot="hover-card-content"
{align}
{sideOffset}
class={cn(
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground w-64 rounded-lg p-2.5 text-sm shadow-md ring-1 duration-100 z-50 origin-(--transform-origin) outline-hidden',
className
)}
{...restProps}
/>
</HoverCardPortal>
@@ -1,7 +0,0 @@
<script lang="ts">
import { LinkPreview as HoverCardPrimitive } from 'bits-ui';
let { ...restProps }: HoverCardPrimitive.PortalProps = $props();
</script>
<HoverCardPrimitive.Portal {...restProps} />
@@ -1,7 +0,0 @@
<script lang="ts">
import { LinkPreview as HoverCardPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: HoverCardPrimitive.TriggerProps = $props();
</script>
<HoverCardPrimitive.Trigger bind:ref data-slot="hover-card-trigger" {...restProps} />
@@ -1,7 +0,0 @@
<script lang="ts">
import { LinkPreview as HoverCardPrimitive } from 'bits-ui';
let { open = $bindable(false), ...restProps }: HoverCardPrimitive.RootProps = $props();
</script>
<HoverCardPrimitive.Root bind:open {...restProps} />
@@ -1,15 +0,0 @@
import Root from './hover-card.svelte';
import Content from './hover-card-content.svelte';
import Trigger from './hover-card-trigger.svelte';
import Portal from './hover-card-portal.svelte';
export {
Root,
Content,
Trigger,
Portal,
Root as HoverCard,
Content as HoverCardContent,
Trigger as HoverCardTrigger,
Portal as HoverCardPortal
};
@@ -1,3 +1,4 @@
export const CONTEXT_KEY_MESSAGE_EDIT = 'chat-message-edit';
export const CONTEXT_KEY_CHAT_ACTIONS = 'chat-actions';
export const CONTEXT_KEY_CHAT_SETTINGS_CONFIG = 'chat-settings-config';
export const CONTEXT_KEY_PROCESSING_INFO = 'processing-info';
@@ -17,4 +17,3 @@ export const PANEL_CLASSES = `
`;
export const CHAT_FORM_POPOVER_MAX_HEIGHT = 'max-h-80';
export const DIALOG_SUBMENU_CONTENT = 'w-60';
@@ -6,7 +6,6 @@ import type { ReasoningEffortLevel } from '$lib/types';
* Keys match the ReasoningEffort enum values for type-safe lookups.
*/
export const REASONING_EFFORT_LABELS: Record<string, string> = {
[ReasoningEffort.OFF]: 'Off',
[ReasoningEffort.LOW]: 'Low',
[ReasoningEffort.MEDIUM]: 'Medium',
[ReasoningEffort.HIGH]: 'High',
@@ -14,7 +13,7 @@ export const REASONING_EFFORT_LABELS: Record<string, string> = {
};
export const REASONING_EFFORT_LEVELS: ReasoningEffortLevel[] = [
{ value: ReasoningEffort.OFF, label: 'Off', isOff: true },
{ value: 'off', label: 'Off', isOff: true },
{ value: ReasoningEffort.LOW, label: 'Low' },
{ value: ReasoningEffort.MEDIUM, label: 'Medium' },
{ value: ReasoningEffort.HIGH, label: 'High' },
@@ -22,6 +22,7 @@ export const SETTINGS_KEYS = {
// Display
SHOW_MESSAGE_STATS: 'showMessageStats',
SHOW_THOUGHT_IN_PROGRESS: 'showThoughtInProgress',
KEEP_STATS_VISIBLE: 'keepStatsVisible',
AUTO_MIC_ON_EMPTY: 'autoMicOnEmpty',
RENDER_USER_CONTENT_AS_MARKDOWN: 'renderUserContentAsMarkdown',
DISABLE_AUTO_SCROLL: 'disableAutoScroll',
@@ -60,6 +61,7 @@ export const SETTINGS_KEYS = {
MCP_REQUEST_TIMEOUT_SECONDS: 'mcpRequestTimeoutSeconds',
MCP_DEFAULT_SERVER_OVERRIDES: 'mcpDefaultServerOverrides',
AGENTIC_MAX_TURNS: 'agenticMaxTurns',
ALWAYS_SHOW_AGENTIC_TURNS: 'alwaysShowAgenticTurns',
AGENTIC_MAX_TOOL_PREVIEW_LINES: 'agenticMaxToolPreviewLines',
SHOW_TOOL_CALL_IN_PROGRESS: 'showToolCallInProgress',
// Performance
@@ -258,6 +258,18 @@ const SETTINGS_REGISTRY: Record<string, SettingsSectionEntry> = {
paramType: SyncableParameterType.BOOLEAN
}
},
{
key: SETTINGS_KEYS.KEEP_STATS_VISIBLE,
label: 'Keep stats visible after generation',
help: 'Keep processing statistics visible after generation finishes.',
defaultValue: false,
type: SettingsFieldType.CHECKBOX,
section: SETTINGS_SECTION_SLUGS.DISPLAY,
sync: {
serverKey: SETTINGS_KEYS.KEEP_STATS_VISIBLE,
paramType: SyncableParameterType.BOOLEAN
}
},
{
key: SETTINGS_KEYS.AUTO_MIC_ON_EMPTY,
label: 'Show microphone on empty input',
@@ -367,6 +379,18 @@ const SETTINGS_REGISTRY: Record<string, SettingsSectionEntry> = {
paramType: SyncableParameterType.BOOLEAN
}
},
{
key: SETTINGS_KEYS.ALWAYS_SHOW_AGENTIC_TURNS,
label: 'Always show agentic turns in conversation',
help: 'Always expand and display agentic loop turns in conversation messages.',
defaultValue: false,
type: SettingsFieldType.CHECKBOX,
section: SETTINGS_SECTION_SLUGS.DISPLAY,
sync: {
serverKey: SETTINGS_KEYS.ALWAYS_SHOW_AGENTIC_TURNS,
paramType: SyncableParameterType.BOOLEAN
}
},
{
key: SETTINGS_KEYS.SHOW_BUILD_VERSION,
label: 'Show build version information',

Some files were not shown because too many files have changed in this diff Show More