// SPDX-License-Identifier: MIT // Copyright (c) 2023-2026 The ggml authors // // Substantial portions derived from ggml / llama.cpp // (https://github.com/ggml-org/llama.cpp), MIT licensed. // The original copyright notice and permission notice are retained. // See libs/ai/NOTICE and, where present, LICENSE in this directory. // #pragma once // Copied from llama.cpp ggml/src/ggml-cuda/mmvq.cu (kernel + launch helpers). // Host ggml_backend cases trimmed; inner mul_mat_vec_q is compile-identical. #include "common.cuh" #include "vecdotq.cuh" #ifndef MMVQ_MAX_BATCH_SIZE #define MMVQ_MAX_BATCH_SIZE 8 #endif typedef float (*vec_dot_q_cuda_t)(const void * __restrict__ vbq, const block_q8_1 * __restrict__ bq8_1, const int & kbx, const int & iqs); static constexpr __device__ vec_dot_q_cuda_t get_vec_dot_q_cuda(ggml_type type) { switch (type) { case GGML_TYPE_Q4_0: return vec_dot_q4_0_q8_1; case GGML_TYPE_Q4_1: return vec_dot_q4_1_q8_1; case GGML_TYPE_Q5_0: return vec_dot_q5_0_q8_1; case GGML_TYPE_Q5_1: return vec_dot_q5_1_q8_1; case GGML_TYPE_Q8_0: return vec_dot_q8_0_q8_1; case GGML_TYPE_MXFP4: return vec_dot_mxfp4_q8_1; case GGML_TYPE_NVFP4: return vec_dot_nvfp4_q8_1; case GGML_TYPE_Q2_K: return vec_dot_q2_K_q8_1; case GGML_TYPE_Q3_K: return vec_dot_q3_K_q8_1; case GGML_TYPE_Q4_K: return vec_dot_q4_K_q8_1; case GGML_TYPE_Q5_K: return vec_dot_q5_K_q8_1; case GGML_TYPE_Q6_K: return vec_dot_q6_K_q8_1; case GGML_TYPE_IQ2_XXS: return vec_dot_iq2_xxs_q8_1; case GGML_TYPE_IQ2_XS: return vec_dot_iq2_xs_q8_1; case GGML_TYPE_IQ2_S: return vec_dot_iq2_s_q8_1; case GGML_TYPE_IQ3_XXS: return vec_dot_iq3_xxs_q8_1; case GGML_TYPE_IQ1_S: return vec_dot_iq1_s_q8_1; case GGML_TYPE_IQ1_M: return vec_dot_iq1_m_q8_1; case GGML_TYPE_IQ4_NL: return vec_dot_iq4_nl_q8_1; case GGML_TYPE_IQ4_XS: return vec_dot_iq4_xs_q8_1; case GGML_TYPE_IQ3_S: return vec_dot_iq3_s_q8_1; default: return nullptr; } } static constexpr __host__ __device__ int get_vdr_mmvq(ggml_type type) { switch (type) { case GGML_TYPE_Q4_0: return VDR_Q4_0_Q8_1_MMVQ; case GGML_TYPE_Q4_1: return VDR_Q4_1_Q8_1_MMVQ; case GGML_TYPE_Q5_0: return VDR_Q5_0_Q8_1_MMVQ; case GGML_TYPE_Q5_1: return VDR_Q5_1_Q8_1_MMVQ; case GGML_TYPE_Q8_0: return VDR_Q8_0_Q8_1_MMVQ; case GGML_TYPE_MXFP4: return VDR_MXFP4_Q8_1_MMVQ; case GGML_TYPE_NVFP4: return VDR_NVFP4_Q8_1_MMVQ; case GGML_TYPE_Q2_K: return VDR_Q2_K_Q8_1_MMVQ; case GGML_TYPE_Q3_K: return VDR_Q3_K_Q8_1_MMVQ; case GGML_TYPE_Q4_K: return VDR_Q4_K_Q8_1_MMVQ; case GGML_TYPE_Q5_K: return VDR_Q5_K_Q8_1_MMVQ; case GGML_TYPE_Q6_K: return VDR_Q6_K_Q8_1_MMVQ; case GGML_TYPE_IQ2_XXS: return VDR_IQ2_XXS_Q8_1_MMVQ; case GGML_TYPE_IQ2_XS: return VDR_IQ2_XS_Q8_1_MMVQ; case GGML_TYPE_IQ2_S: return VDR_IQ2_S_Q8_1_MMVQ; case GGML_TYPE_IQ3_XXS: return VDR_IQ3_XXS_Q8_1_MMVQ; case GGML_TYPE_IQ3_S: return VDR_IQ3_S_Q8_1_MMVQ; case GGML_TYPE_IQ4_NL: return VDR_IQ4_NL_Q8_1_MMVQ; case GGML_TYPE_IQ4_XS: return VDR_IQ4_XS_Q8_1_MMVQ; default: return 1; } } enum mmvq_parameter_table_id { MMVQ_PARAMETERS_GENERIC = 0, MMVQ_PARAMETERS_GCN, MMVQ_PARAMETERS_RDNA2, MMVQ_PARAMETERS_RDNA3_0, MMVQ_PARAMETERS_RDNA4 }; static constexpr __device__ mmvq_parameter_table_id get_device_table_id() { #if defined(RDNA4) return MMVQ_PARAMETERS_RDNA4; #elif defined(RDNA3_0) return MMVQ_PARAMETERS_RDNA3_0; #elif defined(RDNA2) || defined(RDNA3_5) return MMVQ_PARAMETERS_RDNA2; #elif defined(GCN) || defined(CDNA) return MMVQ_PARAMETERS_GCN; #else return MMVQ_PARAMETERS_GENERIC; #endif } static __host__ mmvq_parameter_table_id get_device_table_id(int cc) { if (GGML_CUDA_CC_IS_RDNA4(cc)) { return MMVQ_PARAMETERS_RDNA4; } if (GGML_CUDA_CC_IS_RDNA3_0(cc)) { return MMVQ_PARAMETERS_RDNA3_0; } if (GGML_CUDA_CC_IS_RDNA2(cc) || GGML_CUDA_CC_IS_RDNA3_5(cc)) { return MMVQ_PARAMETERS_RDNA2; } if (GGML_CUDA_CC_IS_GCN(cc) || GGML_CUDA_CC_IS_CDNA(cc)) { return MMVQ_PARAMETERS_GCN; } return MMVQ_PARAMETERS_GENERIC; } // Per-architecture maximum batch size for which MMVQ should be used for MUL_MAT_ID. // Returns a value <= MMVQ_MAX_BATCH_SIZE. Default is MMVQ_MAX_BATCH_SIZE. // Check https://github.com/ggml-org/llama.cpp/pull/20905#issuecomment-4145835627 for details static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_pascal_older(ggml_type type) { switch (type) { case GGML_TYPE_IQ1_S: return 6; case GGML_TYPE_IQ1_M: return 6; case GGML_TYPE_IQ2_S: return 4; case GGML_TYPE_IQ2_XS: return 5; case GGML_TYPE_IQ2_XXS: return 5; case GGML_TYPE_IQ3_S: return 4; case GGML_TYPE_IQ3_XXS: return 4; case GGML_TYPE_IQ4_NL: return 6; case GGML_TYPE_IQ4_XS: return 5; case GGML_TYPE_MXFP4: return 4; case GGML_TYPE_Q2_K: return 4; case GGML_TYPE_Q3_K: return 4; case GGML_TYPE_Q4_0: return 6; case GGML_TYPE_Q4_1: return 6; case GGML_TYPE_Q4_K: return 5; case GGML_TYPE_Q5_0: return 6; case GGML_TYPE_Q5_1: return 6; case GGML_TYPE_Q5_K: return 5; case GGML_TYPE_Q6_K: return 4; case GGML_TYPE_Q8_0: return 4; default: return MMVQ_MAX_BATCH_SIZE; } } static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_turing_plus(ggml_type type) { switch (type) { case GGML_TYPE_IQ2_S: return 7; case GGML_TYPE_IQ3_S: return 6; case GGML_TYPE_IQ3_XXS: return 7; case GGML_TYPE_MXFP4: return 7; case GGML_TYPE_Q2_K: return 7; case GGML_TYPE_Q3_K: return 5; default: return MMVQ_MAX_BATCH_SIZE; } } static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_gcn(ggml_type type) { switch (type) { case GGML_TYPE_IQ1_S: return 5; case GGML_TYPE_IQ1_M: return 5; case GGML_TYPE_IQ2_S: return 4; case GGML_TYPE_IQ2_XS: return 4; case GGML_TYPE_IQ2_XXS: return 4; case GGML_TYPE_IQ3_S: return 4; case GGML_TYPE_IQ3_XXS: return 4; case GGML_TYPE_IQ4_NL: return 6; case GGML_TYPE_IQ4_XS: return 4; case GGML_TYPE_Q2_K: return 4; case GGML_TYPE_Q3_K: return 4; case GGML_TYPE_Q4_0: return 5; case GGML_TYPE_Q4_1: return 5; case GGML_TYPE_Q4_K: return 4; case GGML_TYPE_Q5_K: return 4; case GGML_TYPE_Q6_K: return 4; case GGML_TYPE_Q8_0: return 4; default: return MMVQ_MAX_BATCH_SIZE; } } static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_cdna(ggml_type type) { switch (type) { case GGML_TYPE_IQ2_S: return 5; case GGML_TYPE_IQ2_XS: return 5; case GGML_TYPE_IQ2_XXS: return 5; case GGML_TYPE_IQ3_S: return 4; case GGML_TYPE_IQ3_XXS: return 5; default: return MMVQ_MAX_BATCH_SIZE; } } static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_rdna1_rdna2(ggml_type type) { switch (type) { case GGML_TYPE_IQ2_S: return 4; case GGML_TYPE_IQ2_XS: return 4; case GGML_TYPE_IQ2_XXS: return 4; case GGML_TYPE_IQ3_S: return 4; case GGML_TYPE_IQ3_XXS: return 4; case GGML_TYPE_Q2_K: return 7; case GGML_TYPE_Q3_K: return 4; case GGML_TYPE_Q4_K: return 5; case GGML_TYPE_Q5_K: return 6; case GGML_TYPE_Q6_K: return 5; default: return MMVQ_MAX_BATCH_SIZE; } } static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_rdna3(ggml_type type) { switch (type) { case GGML_TYPE_IQ1_S: return 6; case GGML_TYPE_IQ1_M: return 6; case GGML_TYPE_IQ2_S: return 4; case GGML_TYPE_IQ2_XS: return 4; case GGML_TYPE_IQ2_XXS: return 4; case GGML_TYPE_IQ3_S: return 4; case GGML_TYPE_IQ3_XXS: return 4; case GGML_TYPE_IQ4_NL: return 6; case GGML_TYPE_IQ4_XS: return 6; case GGML_TYPE_Q4_K: return 4; case GGML_TYPE_Q5_K: return 4; case GGML_TYPE_Q6_K: return 4; default: return MMVQ_MAX_BATCH_SIZE; } } static constexpr __host__ __device__ int get_mmvq_mmid_max_batch_rdna4(ggml_type type) { switch (type) { case GGML_TYPE_IQ1_S: return 7; case GGML_TYPE_IQ1_M: return 7; case GGML_TYPE_IQ2_S: return 4; case GGML_TYPE_IQ2_XS: return 4; case GGML_TYPE_IQ2_XXS: return 4; case GGML_TYPE_IQ3_S: return 4; case GGML_TYPE_IQ3_XXS: return 4; case GGML_TYPE_IQ4_NL: return 7; case GGML_TYPE_IQ4_XS: return 5; case GGML_TYPE_MXFP4: return 5; case GGML_TYPE_Q3_K: return 4; case GGML_TYPE_Q4_0: return 7; case GGML_TYPE_Q4_1: return 7; case GGML_TYPE_Q4_K: return 4; case GGML_TYPE_Q5_0: return 7; case GGML_TYPE_Q5_1: return 7; case GGML_TYPE_Q5_K: return 5; case GGML_TYPE_Q6_K: return 5; case GGML_TYPE_Q8_0: return 7; default: return MMVQ_MAX_BATCH_SIZE; } } // Host function: returns the max batch size for the current arch+type at runtime. int get_mmvq_mmid_max_batch(ggml_type type, int cc) { // NVIDIA: Volta, Ada Lovelace, and Blackwell always use MMVQ for MUL_MAT_ID. if (GGML_CUDA_CC_IS_NVIDIA(cc)) { if (cc == GGML_CUDA_CC_VOLTA || cc >= GGML_CUDA_CC_ADA_LOVELACE) { return MMVQ_MAX_BATCH_SIZE; } if (cc >= GGML_CUDA_CC_TURING) { return get_mmvq_mmid_max_batch_turing_plus(type); } return get_mmvq_mmid_max_batch_pascal_older(type); } // AMD if (GGML_CUDA_CC_IS_AMD(cc)) { if (GGML_CUDA_CC_IS_RDNA4(cc)) { return get_mmvq_mmid_max_batch_rdna4(type); } if (GGML_CUDA_CC_IS_RDNA3(cc)) { return get_mmvq_mmid_max_batch_rdna3(type); } if (GGML_CUDA_CC_IS_RDNA1(cc) || GGML_CUDA_CC_IS_RDNA2(cc)) { return get_mmvq_mmid_max_batch_rdna1_rdna2(type); } if (GGML_CUDA_CC_IS_CDNA(cc)) { return get_mmvq_mmid_max_batch_cdna(type); } if (GGML_CUDA_CC_IS_GCN(cc)) { return get_mmvq_mmid_max_batch_gcn(type); } } return MMVQ_MAX_BATCH_SIZE; } // Device constexpr: returns the max batch size for the current arch+type at compile time. template static constexpr __device__ int get_mmvq_mmid_max_batch_for_device() { #if defined(RDNA4) return get_mmvq_mmid_max_batch_rdna4(type); #elif defined(RDNA3) return get_mmvq_mmid_max_batch_rdna3(type); #elif defined(RDNA2) || defined(RDNA1) return get_mmvq_mmid_max_batch_rdna1_rdna2(type); #elif defined(CDNA) return get_mmvq_mmid_max_batch_cdna(type); #elif defined(GCN) return get_mmvq_mmid_max_batch_gcn(type); #elif defined(__CUDA_ARCH__) && (__CUDA_ARCH__ == GGML_CUDA_CC_VOLTA || __CUDA_ARCH__ >= GGML_CUDA_CC_ADA_LOVELACE) return MMVQ_MAX_BATCH_SIZE; #elif defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= GGML_CUDA_CC_TURING return get_mmvq_mmid_max_batch_turing_plus(type); #else return get_mmvq_mmid_max_batch_pascal_older(type); #endif } static constexpr __host__ __device__ int calc_nwarps(ggml_type type, int ncols_dst, mmvq_parameter_table_id table_id) { if (table_id == MMVQ_PARAMETERS_GENERIC) { switch (ncols_dst) { case 1: case 2: case 3: case 4: return 4; case 5: case 6: case 7: case 8: return 2; default: return 1; } } else if (table_id == MMVQ_PARAMETERS_GCN) { switch (ncols_dst) { case 1: case 2: case 3: case 4: return 2; case 5: case 6: case 7: case 8: default: return 1; } } if (table_id == MMVQ_PARAMETERS_RDNA4) { // nwarps=8 benefits types with simple vec_dot on RDNA4 (ncols_dst=1). // Types with complex vec_dot (Q3_K, IQ2_*, IQ3_*) regress due to register // pressure and lookup table contention at higher thread counts. if (ncols_dst == 1) { switch (type) { case GGML_TYPE_Q4_0: case GGML_TYPE_Q4_1: case GGML_TYPE_Q5_0: case GGML_TYPE_Q5_1: case GGML_TYPE_Q8_0: case GGML_TYPE_Q2_K: case GGML_TYPE_Q4_K: case GGML_TYPE_Q5_K: case GGML_TYPE_Q6_K: case GGML_TYPE_IQ4_NL: case GGML_TYPE_IQ4_XS: return 8; default: return 1; } } return 1; } if (table_id == MMVQ_PARAMETERS_RDNA3_0) { // RDNA3 (W7900): stricter whitelist than RDNA4. // Q2_K / Q5_K / IQ4_XS regress in full quant sweeps. if (ncols_dst == 1) { switch (type) { case GGML_TYPE_Q4_0: case GGML_TYPE_Q4_1: case GGML_TYPE_Q5_0: case GGML_TYPE_Q5_1: case GGML_TYPE_Q8_0: case GGML_TYPE_Q4_K: case GGML_TYPE_Q6_K: case GGML_TYPE_IQ4_NL: return 8; default: return 1; } } return 1; } return 1; } static constexpr __host__ __device__ int calc_rows_per_block(int ncols_dst, int table_id, bool small_k = false, int nwarps = 1) { if (table_id == MMVQ_PARAMETERS_GENERIC || table_id == MMVQ_PARAMETERS_GCN) { switch (ncols_dst) { case 1: return small_k ? nwarps : 1; case 2: case 3: case 4: case 5: case 6: case 7: case 8: return 2; default: return 1; } } return 1; } template __launch_bounds__(calc_nwarps(type, ncols_dst, get_device_table_id())*ggml_cuda_get_physical_warp_size(), 1) static __global__ void mul_mat_vec_q( const void * __restrict__ vx, const void * __restrict__ vy, const int32_t * __restrict__ ids, const ggml_cuda_mm_fusion_args_device fusion, float * __restrict__ dst, const uint32_t ncols_x, const uint3 nchannels_y, const uint32_t stride_row_x, const uint32_t stride_col_y, const uint32_t stride_col_dst, const uint3 channel_ratio, const uint32_t stride_channel_x, const uint32_t stride_channel_y, const uint32_t stride_channel_dst, const uint3 sample_ratio, const uint32_t stride_sample_x, const uint32_t stride_sample_y, const uint32_t stride_sample_dst, const uint32_t ids_stride) { constexpr int qk = ggml_cuda_type_traits::qk; constexpr int qi = ggml_cuda_type_traits::qi; constexpr int vdr = get_vdr_mmvq(type); constexpr mmvq_parameter_table_id table_id = get_device_table_id(); constexpr int nwarps = calc_nwarps(type, ncols_dst, table_id); constexpr int rows_per_cuda_block = calc_rows_per_block(ncols_dst, table_id, small_k, nwarps); constexpr int warp_size = ggml_cuda_get_physical_warp_size(); constexpr vec_dot_q_cuda_t vec_dot_q_cuda = get_vec_dot_q_cuda(type); const int tid = warp_size*threadIdx.y + threadIdx.x; const int row0 = rows_per_cuda_block*blockIdx.x; const int blocks_per_row_x = ncols_x / qk; constexpr int blocks_per_iter = vdr * nwarps*warp_size / qi; const uint32_t channel_dst = blockIdx.y; uint32_t channel_x; uint32_t channel_y; uint32_t sample_dst; channel_x = ncols_dst == 1 && ids ? ids[channel_dst] : fastdiv(channel_dst, channel_ratio); channel_y = ncols_dst == 1 && ids ? fastmodulo(channel_dst, nchannels_y) : channel_dst; sample_dst = blockIdx.z; const uint32_t sample_x = fastdiv(sample_dst, sample_ratio); const uint32_t sample_y = sample_dst; bool use_gate = false; bool use_bias = false; bool use_gate_bias = false; const void * vgate = nullptr; const float * x_bias = nullptr; const float * gate_bias = nullptr; ggml_glu_op active_glu; if constexpr (has_fusion) { use_gate = fusion.gate != nullptr; use_bias = fusion.x_bias != nullptr; use_gate_bias = fusion.gate_bias != nullptr && use_gate; vgate = fusion.gate; x_bias = (const float *) fusion.x_bias; gate_bias = (const float *) fusion.gate_bias; active_glu = fusion.glu_op; } float x_biases[ncols_dst] = { 0.0f }; float gate_biases[ncols_dst] = { 0.0f }; if constexpr (has_fusion) { const uint32_t channel_bias = ids ? channel_x : channel_dst; if (use_bias) { x_bias = x_bias + sample_dst*stride_sample_dst + channel_bias*stride_channel_dst + row0; // 1. Hide latency by prefetching bias and gate here // 2. load only on threads that won't die after partial sum calculation if (threadIdx.x < rows_per_cuda_block && threadIdx.y == 0 && (rows_per_cuda_block == 1 || uint32_t(row0 + threadIdx.x) < stride_col_dst)) { #pragma unroll for (int j = 0; j < ncols_dst; ++j) { x_biases[j] = x_bias[j * stride_col_dst + threadIdx.x]; } } } if (use_gate_bias) { gate_bias = gate_bias + sample_dst*stride_sample_dst + channel_bias*stride_channel_dst + row0; if (threadIdx.x < rows_per_cuda_block && threadIdx.y == 0 && (rows_per_cuda_block == 1 || uint32_t(row0 + threadIdx.x) < stride_col_dst)) { #pragma unroll for (int j = 0; j < ncols_dst; ++j) { gate_biases[j] = gate_bias[j * stride_col_dst + threadIdx.x]; } } } } // partial sum for each thread float tmp[ncols_dst][rows_per_cuda_block] = {{0.0f}}; float tmp_gate[ncols_dst][rows_per_cuda_block] = {{0.0f}}; const block_q8_1 * y = ((const block_q8_1 *) vy) + sample_y*stride_sample_y + channel_y*stride_channel_y; const int kbx_offset = sample_x*stride_sample_x + channel_x*stride_channel_x + row0*stride_row_x; for (int kbx = tid / (qi/vdr); kbx < blocks_per_row_x; kbx += blocks_per_iter) { const int kby = kbx * (qk/QK8_1); // y block index that aligns with kbx // x block quant index when casting the quants to int const int kqs = vdr * (tid % (qi/vdr)); #pragma unroll for (int j = 0; j < ncols_dst; ++j) { #pragma unroll for (int i = 0; i < rows_per_cuda_block; ++i) { tmp[j][i] += vec_dot_q_cuda( vx, &y[j*stride_col_y + kby], kbx_offset + i*stride_row_x + kbx, kqs); if constexpr (has_fusion) { if (use_gate) { tmp_gate[j][i] += vec_dot_q_cuda( vgate, &y[j*stride_col_y + kby], kbx_offset + i*stride_row_x + kbx, kqs); } } } } } __shared__ float tmp_shared[nwarps-1 > 0 ? nwarps-1 : 1][ncols_dst][rows_per_cuda_block][warp_size]; __shared__ float tmp_shared_gate[(has_fusion && (nwarps-1 > 0)) ? nwarps-1 : 1][ncols_dst][rows_per_cuda_block][warp_size]; if constexpr (!has_fusion) { (void) tmp_shared_gate; } else if (!use_gate) { (void) tmp_shared_gate; } if (threadIdx.y > 0) { #pragma unroll for (int j = 0; j < ncols_dst; ++j) { #pragma unroll for (int i = 0; i < rows_per_cuda_block; ++i) { tmp_shared[threadIdx.y-1][j][i][threadIdx.x] = tmp[j][i]; if constexpr (has_fusion) { if (use_gate) { tmp_shared_gate[threadIdx.y-1][j][i][threadIdx.x] = tmp_gate[j][i]; } } } } } __syncthreads(); if (threadIdx.y > 0) { return; } dst += sample_dst*stride_sample_dst + channel_dst*stride_channel_dst + row0; // sum up partial sums and write back result #pragma unroll for (int j = 0; j < ncols_dst; ++j) { #pragma unroll for (int i = 0; i < rows_per_cuda_block; ++i) { #pragma unroll for (int l = 0; l < nwarps-1; ++l) { tmp[j][i] += tmp_shared[l][j][i][threadIdx.x]; if constexpr (has_fusion) { if (use_gate) { tmp_gate[j][i] += tmp_shared_gate[l][j][i][threadIdx.x]; } } } tmp[j][i] = warp_reduce_sum(tmp[j][i]); if constexpr (has_fusion) { if (use_gate) { tmp_gate[j][i] = warp_reduce_sum(tmp_gate[j][i]); } } } if (threadIdx.x < rows_per_cuda_block && (rows_per_cuda_block == 1 || uint32_t(row0 + threadIdx.x) < stride_col_dst)) { float result = tmp[j][threadIdx.x]; if constexpr (has_fusion) { if (use_bias) { result += x_biases[j]; } if (use_gate) { float gate_value = tmp_gate[j][threadIdx.x]; if (use_gate_bias) { gate_value += gate_biases[j]; } switch (active_glu) { case GGML_GLU_OP_SWIGLU: result *= ggml_cuda_op_silu_single(gate_value); break; case GGML_GLU_OP_GEGLU: result *= ggml_cuda_op_gelu_single(gate_value); break; case GGML_GLU_OP_SWIGLU_OAI: { result = ggml_cuda_op_swiglu_oai_single(gate_value, result); break; } default: result = result * gate_value; break; } } } dst[j*stride_col_dst + threadIdx.x] = result; } } if constexpr (!has_fusion) { GGML_UNUSED_VARS(use_gate, use_bias, use_gate_bias, active_glu, gate_bias, x_bias, tmp_gate); } }