Compare commits

...
3 Commits
Author SHA1 Message Date
Hongqiang WangandGitHub 43d87ff2dd opencl: fix out‐of‐bound reads in the Adreno image kernels (#27632)
* opencl: clamp the q4_K decode GEMV's fetch row on a padded x-grid

* opencl: enforce the tiling contract of the image KQ/KQV GEMMs

* opencl: decide the image KQ/KQV split at the dispatch, not from strides
2026-09-01 22:28:45 -07:00
Trivikram ReddyandGitHub 69320fef12 hexagon: add missing FARF logs for cpy/get_rows/set_rows/gdn ops (#28217)
* hexagon: fix bug ne[2] printed in proc_op_req prep-src log

* hexagon: add shape/VTCM farf logs to cpy, get/set rows, gdn
2026-09-01 22:20:29 -07:00
Jhen-Jie HongandGitHub b96806d960 metal : add metallib build support for xcframework (#28163) 2026-09-02 07:45:56 +08:00
10 changed files with 191 additions and 50 deletions
+15 -1
View File
@@ -18,7 +18,7 @@ LLAMA_BUILD_TESTS=OFF
LLAMA_BUILD_SERVER=OFF
LLAMA_BUILD_MTMD=ON
GGML_METAL=ON
GGML_METAL_EMBED_LIBRARY=ON
GGML_METAL_EMBED_LIBRARY=${GGML_METAL_EMBED_LIBRARY:-ON}
GGML_BLAS_DEFAULT=ON
GGML_OPENMP=OFF
@@ -169,6 +169,14 @@ setup_framework_structure() {
cp tools/mtmd/mtmd.h ${header_path}
cp tools/mtmd/mtmd-helper.h ${header_path}
if [[ "$GGML_METAL_EMBED_LIBRARY" == "OFF" ]]; then
if [[ "$platform" == "macos" ]]; then
cp ${build_dir}/bin/*.metallib ${build_dir}/framework/${framework_name}.framework/Versions/A/Resources/
else
cp ${build_dir}/bin/*.metallib ${build_dir}/framework/${framework_name}.framework/
fi
fi
# Create module map (common for all platforms)
cat > ${module_path}module.modulemap << EOF
framework module llama {
@@ -450,6 +458,7 @@ build_ios_sim() {
-DIOS=ON \
-DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_SYSROOT=iphonesimulator \
-DGGML_METAL_TARGET_OS=ios \
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=iphonesimulator \
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
@@ -467,6 +476,7 @@ build_ios_device() {
-DCMAKE_OSX_DEPLOYMENT_TARGET=${IOS_MIN_OS_VERSION} \
-DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_SYSROOT=iphoneos \
-DGGML_METAL_TARGET_OS=ios \
-DCMAKE_OSX_ARCHITECTURES="arm64" \
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=iphoneos \
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
@@ -498,6 +508,7 @@ build_visionos() {
-DCMAKE_OSX_ARCHITECTURES="arm64" \
-DCMAKE_SYSTEM_NAME=visionOS \
-DCMAKE_OSX_SYSROOT=xros \
-DGGML_METAL_TARGET_OS=xros \
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=xros \
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
-DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
@@ -516,6 +527,7 @@ build_visionos_sim() {
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
-DCMAKE_SYSTEM_NAME=visionOS \
-DCMAKE_OSX_SYSROOT=xrsimulator \
-DGGML_METAL_TARGET_OS=xros \
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=xrsimulator \
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
-DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
@@ -534,6 +546,7 @@ build_tvos_sim() {
-DCMAKE_OSX_DEPLOYMENT_TARGET=${TVOS_MIN_OS_VERSION} \
-DCMAKE_SYSTEM_NAME=tvOS \
-DCMAKE_OSX_SYSROOT=appletvsimulator \
-DGGML_METAL_TARGET_OS=tvos \
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
-DGGML_METAL=ON \
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=appletvsimulator \
@@ -552,6 +565,7 @@ build_tvos_device() {
-DCMAKE_OSX_DEPLOYMENT_TARGET=${TVOS_MIN_OS_VERSION} \
-DCMAKE_SYSTEM_NAME=tvOS \
-DCMAKE_OSX_SYSROOT=appletvos \
-DGGML_METAL_TARGET_OS=tvos \
-DCMAKE_OSX_ARCHITECTURES="arm64" \
-DGGML_METAL=ON \
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=appletvos \
+2
View File
@@ -242,6 +242,8 @@ option(GGML_METAL_EMBED_LIBRARY "ggml: embed Metal library"
set (GGML_METAL_MACOSX_VERSION_MIN "" CACHE STRING
"ggml: metal minimum macOS version")
set (GGML_METAL_STD "" CACHE STRING "ggml: metal standard version (-std flag)")
set (GGML_METAL_TARGET_OS "macos" CACHE STRING
"ggml: metal -mtargetos OS name (macos, ios, xros, tvos)")
option(GGML_OPENMP "ggml: use OpenMP" ON)
option(GGML_OPENMP_FETCH "ggml: fetch LLVM OpenMP" OFF)
option(GGML_RPC "ggml: use RPC" OFF)
+4
View File
@@ -323,6 +323,10 @@ int op_cpy(struct htp_ops_context * octx) {
return HTP_STATUS_NO_SUPPORT;
}
FARF(HIGH, "cpy-%s-%s: (%ux%ux%ux%u) -> (%ux%ux%ux%u) : use_dma=%d n_threads %u\n",
src0->type == HTP_TYPE_F32 ? "f32" : "f16", dst->type == HTP_TYPE_F32 ? "f32" : "f16",
ne00, ne01, ne02, ne03, ne0, ne1, ne2, ne3, use_dma, n_threads);
if (use_dma) {
cpy_dma_sametype_sameshape(octx, dst, src0, ct.src0_type_size, ne00, ne01, ne02, ne03, nb01, nb02, nb03, nb1, nb2, nb3);
} else {
@@ -1138,6 +1138,15 @@ int op_gated_delta_net(struct htp_ops_context * octx) {
gctx.vtcm_base = octx->ctx->vtcm_base;
gctx.vtcm_per_thread = 2 * state_aligned;
FARF(HIGH, "gated-delta-net-f32: q(%ux%ux%ux%u) k(%ux%ux%ux%u) v(%ux%ux%ux%u) state(%ux%ux%ux%u) -> (%ux%ux%ux%u) : "
"vtcm-size %zu n_threads %u\n",
q->ne[0], q->ne[1], q->ne[2], q->ne[3],
k->ne[0], k->ne[1], k->ne[2], k->ne[3],
v->ne[0], v->ne[1], v->ne[2], v->ne[3],
state->ne[0], state->ne[1], state->ne[2], state->ne[3],
dst->ne[0], dst->ne[1], dst->ne[2], dst->ne[3],
gctx.vtcm_per_thread * octx->n_threads, octx->n_threads);
if (n_tokens == 1) {
worker_pool_run_func(octx->ctx->worker_pool, gated_delta_net_f32_tg_thread, &gctx, octx->n_threads);
} else {
+8
View File
@@ -247,6 +247,14 @@ int op_get_rows(struct htp_ops_context * octx) {
}
}
FARF(HIGH, "get-rows: (%ux%ux%ux%u) x (%ux%ux%ux%u) -> (%ux%ux%ux%u) : src0-vtcm-size %zu dst-vtcm-size %zu use_dma=%d n_threads %d\n",
octx->src[0]->ne[0], octx->src[0]->ne[1], octx->src[0]->ne[2], octx->src[0]->ne[3],
octx->src[1]->ne[0], octx->src[1]->ne[1], octx->src[1]->ne[2], octx->src[1]->ne[3],
octx->dst->ne[0], octx->dst->ne[1], octx->dst->ne[2], octx->dst->ne[3],
grctx.vtcm_layout.src0_bytes_per_thread * kparams->n_threads,
grctx.vtcm_layout.dst_bytes_per_thread * kparams->n_threads,
kparams->use_dma, kparams->n_threads);
work_queue_run(octx->ctx->work_queue, q_func, &grctx, kparams->n_threads);
return HTP_STATUS_OK;
}
+1 -1
View File
@@ -981,7 +981,7 @@ static int proc_op_req(struct htp_ops_context * octx, struct htp_tensor *tens, u
octx->src_dma[i] = octx->ctx->dma; // FIXME: ? octx->ctx->dma_cached : octx->ctx->dma;
FARF(HIGH, "prep-src #%u: data %p size %u : %u:%u:%u:%u", op->src[i], (void*) src->data, src->size,
src->ne[0], src->ne[1], src->ne[3], src->ne[3]);
src->ne[0], src->ne[1], src->ne[2], src->ne[3]);
}
htp_tensor_flush_all(octx->ctx, octx->src, HTP_OP_MAX_INPUTS);
+8
View File
@@ -216,6 +216,14 @@ int op_set_rows(struct htp_ops_context * octx) {
default: return HTP_STATUS_NO_SUPPORT;
}
FARF(HIGH, "set-rows: (%ux%ux%ux%u) x (%ux%ux%ux%u) -> (%ux%ux%ux%u) : src0-vtcm-size %zu dst-vtcm-size %zu n_threads %d\n",
octx->src[0]->ne[0], octx->src[0]->ne[1], octx->src[0]->ne[2], octx->src[0]->ne[3],
octx->src[1]->ne[0], octx->src[1]->ne[1], octx->src[1]->ne[2], octx->src[1]->ne[3],
octx->dst->ne[0], octx->dst->ne[1], octx->dst->ne[2], octx->dst->ne[3],
srctx.vtcm_layout.src0_bytes_per_thread * kparams->n_threads,
srctx.vtcm_layout.dst_bytes_per_thread * kparams->n_threads,
kparams->n_threads);
work_queue_run(octx->ctx->work_queue, q_func, &srctx, kparams->n_threads);
return HTP_STATUS_OK;
+49 -21
View File
@@ -127,6 +127,18 @@ else()
configure_file(${src} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${src} COPYONLY)
endforeach()
# CMAKE_OSX_SYSROOT is an SDK name or path - xcrun accepts both
set(METAL_SDK ${CMAKE_OSX_SYSROOT})
if (NOT METAL_SDK)
set(METAL_SDK macosx)
endif()
if (CMAKE_OSX_SYSROOT MATCHES "[Ss]imulator")
set(METAL_TARGET_SIM "-simulator")
else()
set(METAL_TARGET_SIM "")
endif()
if (GGML_METAL_SHADER_DEBUG)
# note: disabling fast math is needed in order to pass tests/test-backend-ops
# note: adding -fno-inline fixes the tests when using MTL_SHADER_VALIDATION=1
@@ -138,9 +150,19 @@ else()
set(XC_FLAGS -O3)
endif()
execute_process(COMMAND xcrun -sdk ${METAL_SDK} --show-sdk-version OUTPUT_VARIABLE METAL_SDK_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
if (METAL_SDK_VERSION VERSION_GREATER_EQUAL 26.0)
set(GGML_METAL_HAS_TENSOR_LIB ON)
else()
message(STATUS "Metal SDK ${METAL_SDK_VERSION} does not support the tensor API, skipping ggml-tensor.metallib")
endif()
if (GGML_METAL_MACOSX_VERSION_MIN)
message(STATUS "Adding -mmacosx-version-min=${GGML_METAL_MACOSX_VERSION_MIN} flag to metal compilation")
list (APPEND XC_FLAGS -mmacosx-version-min=${GGML_METAL_MACOSX_VERSION_MIN})
elseif (NOT GGML_METAL_TARGET_OS STREQUAL "macos" AND CMAKE_OSX_DEPLOYMENT_TARGET)
message(STATUS "Adding -mtargetos=${GGML_METAL_TARGET_OS}${CMAKE_OSX_DEPLOYMENT_TARGET}${METAL_TARGET_SIM} flag to metal compilation")
list (APPEND XC_FLAGS -mtargetos=${GGML_METAL_TARGET_OS}${CMAKE_OSX_DEPLOYMENT_TARGET}${METAL_TARGET_SIM})
endif()
if (GGML_METAL_STD)
@@ -156,33 +178,41 @@ else()
list(APPEND AIR_FILES ${AIR})
add_custom_command(
OUTPUT ${AIR}
COMMAND xcrun -sdk macosx metal ${XC_FLAGS} -I ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} -c ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${src} -o ${AIR}
COMMAND xcrun -sdk ${METAL_SDK} metal ${XC_FLAGS} -I ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} -c ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${src} -o ${AIR}
DEPENDS ${src} kernels/common.h kernels/dequantize.h kernels/quantize.h ${METALLIB_COMMON} ggml-metal-impl.h
COMMENT "Compiling ${src}"
VERBATIM
)
endforeach()
# the tensor API kernels go in a separate metallib, loaded only where supported
set(AIR_MM_TENSOR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/mul_mm_tensor.air")
add_custom_command(
OUTPUT ${AIR_MM_TENSOR}
COMMAND xcrun -sdk macosx metal ${XC_FLAGS} -DGGML_METAL_HAS_TENSOR -I ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} -c ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/kernels/mul_mm.metal -o ${AIR_MM_TENSOR}
DEPENDS kernels/mul_mm.metal kernels/common.h kernels/dequantize.h ${METALLIB_COMMON} ggml-metal-impl.h
COMMENT "Compiling kernels/mul_mm.metal (tensor API)"
VERBATIM
)
set(METALLIB_FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/default.metallib)
add_custom_command(
OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ggml-tensor.metallib
COMMAND xcrun -sdk macosx metallib ${AIR_MM_TENSOR} -o ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ggml-tensor.metallib
DEPENDS ${AIR_MM_TENSOR}
COMMENT "Linking tensor API Metal kernels into ggml-tensor.metallib"
)
# the tensor API kernels go in a separate metallib, loaded only where supported
if (GGML_METAL_HAS_TENSOR_LIB)
set(AIR_MM_TENSOR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/mul_mm_tensor.air")
# the tensor API needs OS 26+
set(XC_FLAGS_TENSOR ${XC_FLAGS} -mtargetos=${GGML_METAL_TARGET_OS}26.0${METAL_TARGET_SIM})
add_custom_command(
OUTPUT ${AIR_MM_TENSOR}
COMMAND xcrun -sdk ${METAL_SDK} metal ${XC_FLAGS_TENSOR} -DGGML_METAL_HAS_TENSOR -I ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} -c ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/kernels/mul_mm.metal -o ${AIR_MM_TENSOR}
DEPENDS kernels/mul_mm.metal kernels/common.h kernels/dequantize.h ${METALLIB_COMMON} ggml-metal-impl.h
COMMENT "Compiling kernels/mul_mm.metal (tensor API)"
VERBATIM
)
add_custom_command(
OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ggml-tensor.metallib
COMMAND xcrun -sdk ${METAL_SDK} metallib ${AIR_MM_TENSOR} -o ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ggml-tensor.metallib
DEPENDS ${AIR_MM_TENSOR}
COMMENT "Linking tensor API Metal kernels into ggml-tensor.metallib"
)
list(APPEND METALLIB_FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ggml-tensor.metallib)
endif()
add_custom_command(
OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/default.metallib
COMMAND xcrun -sdk macosx metallib ${AIR_FILES} -o ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/default.metallib
COMMAND xcrun -sdk ${METAL_SDK} metallib ${AIR_FILES} -o ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/default.metallib
COMMAND rm -f ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ggml-common.h
COMMAND rm -f ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ggml-metal-impl.h
COMMAND rm -rf ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/kernels
@@ -192,8 +222,7 @@ else()
add_custom_target(
ggml-metal-lib ALL
DEPENDS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/default.metallib
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ggml-tensor.metallib
DEPENDS ${METALLIB_FILES}
)
endif() # GGML_METAL_EMBED_LIBRARY
@@ -205,8 +234,7 @@ if (NOT GGML_METAL_EMBED_LIBRARY)
)
install(
FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/default.metallib
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ggml-tensor.metallib
FILES ${METALLIB_FILES}
DESTINATION ${CMAKE_INSTALL_BINDIR}
)
endif()
+67 -16
View File
@@ -16254,7 +16254,13 @@ static void ggml_cl_conv_2d(ggml_backend_t backend, const ggml_tensor * src0, co
backend_ctx->enqueue_ndrange_kernel(kernel, 2, global_work_size, local_work_size, dst);
}
static void ggml_cl_mul_mat_kq_kqv_adreno(ggml_backend_t backend, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) {
// is_kq selects which of the two products this call is, and it is decided by the
// CALLER -- the two admission arms in ggml_cl_mul_mat, each of which knows which
// one it matched. It used to be re-derived here from nb01 > nb02, i.e. "K is
// head-major, V^T is not". That discriminator COLLAPSES at n_head_kv == 1, where
// the two strides are equal because there is only one head to order, so nothing
// here could tell a KQ from a KQV. Pass it in rather than infer it.
static void ggml_cl_mul_mat_kq_kqv_adreno(ggml_backend_t backend, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst, bool is_kq) {
ggml_backend_opencl_context *backend_ctx = (ggml_backend_opencl_context *)backend->context;
ggml_tensor_extra_cl * extra0 = (ggml_tensor_extra_cl *)src0->extra;
@@ -16296,19 +16302,14 @@ static void ggml_cl_mul_mat_kq_kqv_adreno(ggml_backend_t backend, const ggml_ten
int N = ne1;
int K = ne00;
if (nb01 > nb02) {
// KQ
kernel = backend_ctx->kernel_mul_mm_f16_f32_kq;
} else {
// KQV
kernel = backend_ctx->kernel_mul_mm_f16_f32_kqv;
}
kernel = is_kq ? backend_ctx->kernel_mul_mm_f16_f32_kq
: backend_ctx->kernel_mul_mm_f16_f32_kqv;
// create sub-buffer for A
// <--------------------------------------------> //
extra0 = src0->view_src ? (ggml_tensor_extra_cl *)src0->view_src->extra : (ggml_tensor_extra_cl *)src0->extra;
region.origin = (extra0->offset + src0->view_offs);
if (nb01 > nb02) {
if (is_kq) {
// KQ
region.size = nb01 * ne01;
} else {
@@ -16332,7 +16333,7 @@ static void ggml_cl_mul_mat_kq_kqv_adreno(ggml_backend_t backend, const ggml_ten
img_fmt_1d = {CL_RGBA, CL_FLOAT};
memset(&img_desc_1d, 0, sizeof(img_desc_1d));
img_desc_1d.image_type = CL_MEM_OBJECT_IMAGE1D_BUFFER;
if (nb01 > nb02) {
if (is_kq) {
img_desc_1d.image_width = (nb01 * ne01 / 4)/4;
}
else {
@@ -19222,13 +19223,61 @@ static void ggml_cl_mul_mat(ggml_backend_t backend, const ggml_tensor * src0, co
#ifdef GGML_OPENCL_USE_ADRENO_KERNELS
if(src0t == GGML_TYPE_F16 && src1t == GGML_TYPE_F32){
if (ne01 >= 64 && ne1 >= 32 && ne00 >= 16 && (ne12 % ne02) == 0 &&
// Two tiling assumptions these kernels make but nothing enforced:
//
// ne00 % TILESIZE_K(16): the K loop has no tail, so a K that does not
// divide folds 1-15 rows of whatever follows the operands into every
// output.
//
// ne01 % TILESIZE_M(64): mm_store_c_N guards the n direction with its
// `mask` argument but nothing guards m -- the store walks all 64 rows
// of the tile at a stride of M. When M does not divide, the last tile
// does not run off the end of the buffer, it writes 64 - (M % 64)
// values ON TOP OF the next column, so the result is silently wrong.
// Reachable on the KQV side for any head size >= 64 that is not a
// multiple of it (80, 96, 112).
//
// Attention shapes in the graph satisfy both -- head sizes are multiples
// of 64 and n_kv is padded -- which is why this has stayed latent.
// Declining leaves the odd shapes on the generic GEMM, which handles them.
if (ne01 >= 64 && ne1 >= 32 && ne00 >= 16 &&
(ne00 % 16) == 0 && (ne01 % 64) == 0 && (ne12 % ne02) == 0 &&
// the KQ/KQV image kernels do not handle dim 3 (multi-stream batches)
ne03 == 1 && ne13 == 1 &&
// dst is wrapped with image1d_buffer, the size limit applies, also src0
(ne0 * ne1 * dst->ne[2] * dst->nb[0] / 4 <= backend_ctx->image_max_buffer_size)) {
// For KQ
if (ggml_is_permuted(src0) && ggml_is_permuted(src1) &&
// For KQ.
//
// Layout admission, mirroring the KQV arm below. The KQ kernel takes
// no stride arguments for A or B: it derives them as K*D_A*2 and
// K*D_B*4, i.e. it assumes both operands pack exactly D heads of K
// elements per row. Every real KV-cache view and permuted-Q view
// does, but a view spanning part of a wider allocation does not, and
// the kernel then walks the wrong rows with nothing to range-check
// it. Gate on the packed layout itself rather than on the stride
// ORDERING, which a wider parent satisfies just as well.
const bool kq_packed_a = (nb01 == (cl_ulong)ne00 * ne02 * ggml_type_size(src0t)) &&
(nb02 == (cl_ulong)ne00 * ggml_type_size(src0t));
const bool kq_packed_b = (nb11 == (cl_ulong)ne10 * ne12 * ggml_type_size(src1t)) &&
(nb12 == (cl_ulong)ne10 * ggml_type_size(src1t));
//
// ggml_is_permuted(src0) stands in for "K is head-major", but it is
// only a proxy and it COLLAPSES at n_head_kv == 1: with a single
// head there is no head stride to be out of order, so nb01 == nb02
// and the view reports itself unpermuted. Such a KQ was declined
// here and fell through to the generic GEMM (gemma-4 E2B, and any
// other multi-query model). The packed check above is the contract
// the kernel actually needs -- it pins both strides exactly -- so
// require permutedness only where there is more than one head for
// it to mean anything.
//
// Default on; GGML_OPENCL_KQ_NHEAD_KV1=0 restores the old proxy so
// the two routings can be compared in one binary.
static const char * kq_nhkv1_env = getenv("GGML_OPENCL_KQ_NHEAD_KV1");
static const bool kq_nhkv1_on =
(kq_nhkv1_env == nullptr || kq_nhkv1_env[0] != '0');
if ((ggml_is_permuted(src0) || (ne02 == 1 && kq_nhkv1_on)) && ggml_is_permuted(src1) &&
kq_packed_a && kq_packed_b &&
((nb01 * ne01 / 4)/4 <= backend_ctx->image_max_buffer_size) &&
nb00 <= nb02 &&
nb02 <= nb01 &&
@@ -19236,13 +19285,15 @@ static void ggml_cl_mul_mat(ggml_backend_t backend, const ggml_tensor * src0, co
nb10 <= nb12 &&
nb12 <= nb11 &&
nb11 <= nb13) {
ggml_cl_mul_mat_kq_kqv_adreno(backend, src0, src1, dst);
ggml_cl_mul_mat_kq_kqv_adreno(backend, src0, src1, dst, /*is_kq =*/ true);
return;
}
// For KQV
// For KQV. Reaching this arm is what makes the op a KQV; the callee
// is told so explicitly rather than re-deriving it from the strides
// the arm above has already ruled on.
if (!ggml_is_contiguous(src0) && ggml_is_contiguous(src1) &&
((nb02 * ne02 / 4)/4 <= backend_ctx->image_max_buffer_size)) {
ggml_cl_mul_mat_kq_kqv_adreno(backend, src0, src1, dst);
ggml_cl_mul_mat_kq_kqv_adreno(backend, src0, src1, dst, /*is_kq =*/ false);
return;
}
}
@@ -235,6 +235,23 @@ kernel void kernel_gemv_noshuffle_q4_k_f32(
uint LINE_STRIDE_A = M / 2;
uint BLOCK_STRIDE_A = NSUBGROUPS * M;
// The x-grid is padded to CEIL_DIV(ne01/2,64)*64, so when ne01 % 128 != 0 the
// tail lanes hold gid >= ne01/2. The output stores below are guarded, but the
// input fetches are not: src0_d and src0_m are raw global half2 pointers,
// src0_s is a raw global uchar pointer, and read_imageui on an
// image1d_buffer_t is UNDEFINED out of range -- an image clamps only for
// SAMPLER reads, which these are not. Those lanes therefore read past the end
// of all three allocations. For a [2816, 2112] weight (2112 % 128 == 64) the
// top tail lane is gid = 1087 while only gid < 1056 is backed, and it runs
// 32 half2 past src0_d/src0_m, 31 uints past the quant image, and 63 bytes
// past src0_s.
//
// Clamp the row used for every fetch. The lanes stay ACTIVE, which the
// sub_group_broadcast in the dequant macros requires, and their results are
// still discarded by the existing output guard. No-op and byte-identical
// whenever ne01 % 128 == 0.
uint gid_s = min(gid, LINE_STRIDE_A - 1);
private uint4 regA;
private half2 regS;
private half2 regM;
@@ -246,10 +263,10 @@ kernel void kernel_gemv_noshuffle_q4_k_f32(
uint sb = k / 8;
uint j = k % 8;
half2 d = src0_d[gid + sb * LINE_STRIDE_A];
half2 dm = src0_m[gid + sb * LINE_STRIDE_A];
half2 d = src0_d[gid_s + sb * LINE_STRIDE_A];
half2 dm = src0_m[gid_s + sb * LINE_STRIDE_A];
global const uchar * sc0 = src0_s + sb * 12 * M + 2 * gid;
global const uchar * sc0 = src0_s + sb * 12 * M + 2 * gid_s;
global const uchar * sc1 = sc0 + 1;
uchar sv0, mn0, sv1, mn1;
@@ -265,20 +282,20 @@ kernel void kernel_gemv_noshuffle_q4_k_f32(
}
// load half weights for two blocks in consecutive rows
regA.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 0)).x;
regA.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 1)).x;
regA.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 2)).x;
regA.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 3)).x;
regA.s0 = read_imageui(src0_q, (gid_s + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 0)).x;
regA.s1 = read_imageui(src0_q, (gid_s + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 1)).x;
regA.s2 = read_imageui(src0_q, (gid_s + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 2)).x;
regA.s3 = read_imageui(src0_q, (gid_s + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 3)).x;
#ifdef VECTOR_SUB_GROUP_BROADCAST
dequantizeBlockAccum_ns_sgbroadcast_8_hi(totalSum, as_ushort8(regA), regS, regM, regB);
#else
dequantizeBlockAccum_ns_sgbroadcast_1_hi(totalSum, as_ushort8(regA), regS, regM, regB);
#endif // VECTOR_SUB_GROUP_BROADCAST
regA.s0 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 4)).x;
regA.s1 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 5)).x;
regA.s2 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 6)).x;
regA.s3 = read_imageui(src0_q, (gid + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 7)).x;
regA.s0 = read_imageui(src0_q, (gid_s + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 4)).x;
regA.s1 = read_imageui(src0_q, (gid_s + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 5)).x;
regA.s2 = read_imageui(src0_q, (gid_s + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 6)).x;
regA.s3 = read_imageui(src0_q, (gid_s + k * BLOCK_STRIDE_A + LINE_STRIDE_A * 7)).x;
#ifdef VECTOR_SUB_GROUP_BROADCAST
dequantizeBlockAccum_ns_sgbroadcast_8_lo(totalSum, as_ushort8(regA), regS, regM, regB);
#else