Save inference parity implementation and evaluation harness
This commit is contained in:
+570
-40
@@ -340,8 +340,8 @@ kernel void kernel_qwen_affine_qmv_fast(
|
||||
|
||||
// MLX affine_qmv_wide for the verifier's S=2..4 matrices. M5-class GPUs
|
||||
// route these shapes here instead of running one independent QMV per row.
|
||||
template <ushort bits, ushort group_size>
|
||||
static inline void qwen_affine_qmv_wide_impl(
|
||||
template <ushort bits, ushort group_size, ushort rows>
|
||||
static inline void qwen_affine_qmv_wide_rows_impl(
|
||||
constant qwen_kernel_args &args,
|
||||
device float *out,
|
||||
device const float *x,
|
||||
@@ -361,12 +361,11 @@ static inline void qwen_affine_qmv_wide_impl(
|
||||
simd_group * outputs_per_simdgroup + simd_row;
|
||||
const uint in_dim = args.u[0];
|
||||
const uint out_dim = args.u[1];
|
||||
const uint rows = args.u[4];
|
||||
const uint row = min(output_row, out_dim - 1u);
|
||||
const uint groups_per_row = in_dim / group_size;
|
||||
const ulong packed_row = (ulong)row * in_dim * bits / 8u;
|
||||
const ulong parameter_row = (ulong)row * groups_per_row;
|
||||
float result[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
float result[rows] = {0.0f};
|
||||
|
||||
for (uint quant_group = k_lane; quant_group < groups_per_row;
|
||||
quant_group += k_lanes) {
|
||||
@@ -374,11 +373,13 @@ static inline void qwen_affine_qmv_wide_impl(
|
||||
scales, args.u[14], parameter_row + quant_group));
|
||||
const float bias = qwen_bf16(qwen_weight_u16(
|
||||
biases, args.u[15], parameter_row + quant_group));
|
||||
#pragma unroll
|
||||
for (uint chunk = 0u; chunk < group_size / sub; chunk++) {
|
||||
const uint column = quant_group * group_size + chunk * sub;
|
||||
const ulong weight_byte = (ulong)args.u[13] + packed_row +
|
||||
(ulong)column * bits / 8u;
|
||||
float weights[sub];
|
||||
#pragma unroll
|
||||
for (uint index = 0u; index < sub; index++) {
|
||||
uint quantized;
|
||||
if constexpr (bits == 4) {
|
||||
@@ -389,8 +390,10 @@ static inline void qwen_affine_qmv_wide_impl(
|
||||
}
|
||||
weights[index] = scale * (float)quantized + bias;
|
||||
}
|
||||
#pragma unroll
|
||||
for (uint vector = 0u; vector < rows; vector++) {
|
||||
float sum = 0.0f;
|
||||
#pragma unroll
|
||||
for (uint index = 0u; index < sub; index++) {
|
||||
sum += x[(ulong)vector * in_dim + column + index] * weights[index];
|
||||
}
|
||||
@@ -411,6 +414,35 @@ static inline void qwen_affine_qmv_wide_impl(
|
||||
}
|
||||
}
|
||||
|
||||
// MTPLX specializes vecs_per_tg: keeping the accumulator indices constant
|
||||
// avoids a runtime-indexed register array in the verifier's inner loop.
|
||||
template <ushort bits, ushort group_size>
|
||||
static inline void qwen_affine_qmv_wide_impl(
|
||||
constant qwen_kernel_args &args,
|
||||
device float *out,
|
||||
device const float *x,
|
||||
device const uchar *packed,
|
||||
device const uchar *scales,
|
||||
device const uchar *biases,
|
||||
uint group,
|
||||
uint simd_group,
|
||||
uint lane) {
|
||||
switch (args.u[4]) {
|
||||
case 2:
|
||||
qwen_affine_qmv_wide_rows_impl<bits, group_size, 2>(
|
||||
args, out, x, packed, scales, biases, group, simd_group, lane);
|
||||
break;
|
||||
case 3:
|
||||
qwen_affine_qmv_wide_rows_impl<bits, group_size, 3>(
|
||||
args, out, x, packed, scales, biases, group, simd_group, lane);
|
||||
break;
|
||||
case 4:
|
||||
qwen_affine_qmv_wide_rows_impl<bits, group_size, 4>(
|
||||
args, out, x, packed, scales, biases, group, simd_group, lane);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <ushort bits, ushort group_size>
|
||||
kernel void kernel_qwen_affine_qmv_batch_fast(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
@@ -583,23 +615,111 @@ kernel qwen_affine_pair_qmv_wide_b8g64 kernel_qwen_affine_pair_qmv_wide<8, 64>;
|
||||
#ifdef DS4_METAL_HAS_TENSOR
|
||||
using namespace mpp::tensor_ops;
|
||||
|
||||
template <ushort bits, ushort group_size, ushort tile_m>
|
||||
kernel void kernel_qwen_affine_qmm_mpp(
|
||||
kernel void kernel_qwen_route_map(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device uint *counts [[buffer(1)]],
|
||||
device const uint *selected [[buffer(2)]],
|
||||
device uint *route_map [[buffer(3)]],
|
||||
device uchar *work [[buffer(4)]],
|
||||
threadgroup ushort *staged [[threadgroup(0)]],
|
||||
uint expert [[thread_index_in_threadgroup]],
|
||||
uint threads [[threads_per_threadgroup]]) {
|
||||
uint count = 0u;
|
||||
device uint *expert_map = route_map + (ulong)expert * args.u[4];
|
||||
for (uint first_row = 0u; first_row < args.u[4]; first_row += threads) {
|
||||
const uint row = first_row + expert;
|
||||
if (row < args.u[4]) {
|
||||
for (uint slot = 0u; slot < args.u[8]; slot++) {
|
||||
staged[expert * args.u[8] + slot] =
|
||||
(ushort)selected[(ulong)row * args.u[8] + slot];
|
||||
}
|
||||
}
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
const uint valid = min(threads, args.u[4] - first_row);
|
||||
for (uint local_row = 0u; local_row < valid; local_row++) {
|
||||
for (uint slot = 0u; slot < args.u[8]; slot++) {
|
||||
if (staged[local_row * args.u[8] + slot] == expert) {
|
||||
expert_map[count++] = (first_row + local_row) * args.u[8] + slot;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
}
|
||||
counts[expert] = count;
|
||||
threadgroup_barrier(mem_flags::mem_device);
|
||||
uint route_base = 0u;
|
||||
for (uint other = 0u; other < expert; other++) route_base += counts[other];
|
||||
device uint *route_offsets = (device uint *)(work + 8);
|
||||
device uint *inverse = route_offsets + 512;
|
||||
route_offsets[expert] = route_base;
|
||||
for (uint index = 0u; index < count; index++) {
|
||||
inverse[expert_map[index]] = route_base + index;
|
||||
}
|
||||
staged[expert] = (ushort)((count + 63u) / 64u);
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
|
||||
uint work_base = 0u;
|
||||
for (uint other = 0u; other < expert; other++) {
|
||||
work_base += staged[other];
|
||||
}
|
||||
device uint *work_count = (device uint *)work;
|
||||
const ulong items_offset =
|
||||
(8ul + 512ul * 4ul + (ulong)args.u[4] * args.u[8] * 4ul + 7ul) & ~7ul;
|
||||
device uint2 *work_items = (device uint2 *)(work + items_offset);
|
||||
for (uint tile = 0u; tile < staged[expert]; tile++) {
|
||||
work_items[work_base + tile] = uint2(expert, tile * 64u);
|
||||
}
|
||||
if (expert + 1u == threads) {
|
||||
work_count[0] = work_base + staged[expert];
|
||||
}
|
||||
}
|
||||
|
||||
kernel void kernel_qwen_route_gather_rows(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device bfloat *out [[buffer(1)]],
|
||||
device const float *x [[buffer(2)]],
|
||||
device const uchar *work [[buffer(3)]],
|
||||
uint2 gid [[thread_position_in_grid]]) {
|
||||
const uint column = gid.x;
|
||||
const uint route = gid.y;
|
||||
if (column >= args.u[0] || route >= args.u[4] * args.u[8]) return;
|
||||
device const uint *inverse = (device const uint *)(work + 8) + 512;
|
||||
const uint sorted = inverse[route];
|
||||
out[(ulong)sorted * args.u[0] + column] =
|
||||
(bfloat)x[(ulong)(route / args.u[8]) * args.u[0] + column];
|
||||
}
|
||||
|
||||
template <ushort bits, ushort group_size>
|
||||
kernel void kernel_qwen_affine_gather_qmm_mpp(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device float *out [[buffer(1)]],
|
||||
device const float *x [[buffer(2)]],
|
||||
device const uint *route_map [[buffer(3)]],
|
||||
device const uchar *work [[buffer(4)]],
|
||||
device const uchar *packed [[buffer(5)]],
|
||||
device const uchar *scales [[buffer(6)]],
|
||||
device const uchar *biases [[buffer(7)]],
|
||||
device const uint *counts [[buffer(8)]],
|
||||
uint2 group [[threadgroup_position_in_grid]],
|
||||
uint tid [[thread_index_in_threadgroup]]) {
|
||||
constexpr uint tile_m = 64u;
|
||||
constexpr uint tile_n = 64u;
|
||||
constexpr uint tile_k = 64u;
|
||||
constexpr uint threads = 128u;
|
||||
device const uint *work_count = (device const uint *)work;
|
||||
if (group.y >= work_count[0]) return;
|
||||
const ulong items_offset =
|
||||
(8ul + 512ul * 4ul + (ulong)args.u[4] * args.u[8] * 4ul + 7ul) & ~7ul;
|
||||
device const uint2 *work_items = (device const uint2 *)(work + items_offset);
|
||||
const uint2 item = work_items[group.y];
|
||||
const uint expert = item.x;
|
||||
const uint first_m = item.y;
|
||||
if (first_m >= counts[expert]) return;
|
||||
const uint first_n = group.x * tile_n;
|
||||
device const uint *expert_map = route_map + (ulong)expert * args.u[4];
|
||||
threadgroup bfloat xs[tile_m * tile_k];
|
||||
threadgroup bfloat ws[tile_n * tile_k];
|
||||
const uint first_m = group.y * tile_m;
|
||||
const uint first_n = group.x * tile_n;
|
||||
constexpr auto descriptor = matmul2d_descriptor(
|
||||
tile_m, tile_n, tile_k, false, true, false,
|
||||
matmul2d_descriptor::mode::multiply_accumulate);
|
||||
@@ -609,28 +729,29 @@ kernel void kernel_qwen_affine_qmm_mpp(
|
||||
tensor<threadgroup bfloat, dextents<int32_t, 2>, tensor_inline>,
|
||||
float>();
|
||||
#pragma clang loop unroll(full)
|
||||
for (uint i = 0u; i < accum.get_capacity(); i++) {
|
||||
accum[i] = 0.0f;
|
||||
}
|
||||
for (uint i = 0u; i < accum.get_capacity(); i++) accum[i] = 0.0f;
|
||||
|
||||
for (uint first_k = 0u; first_k < args.u[0]; first_k += tile_k) {
|
||||
for (uint index = tid; index < tile_m * tile_k; index += threads) {
|
||||
const uint row = index / tile_k;
|
||||
const uint column = index % tile_k;
|
||||
const uint input_m = first_m + row;
|
||||
xs[index] = input_m < args.u[4]
|
||||
? (bfloat)x[(ulong)input_m * args.u[0] + first_k + column]
|
||||
const bool valid = first_m + row < counts[expert];
|
||||
const uint route = valid ? expert_map[first_m + row] : 0u;
|
||||
const uint input_row = args.u[5] != 0u ? route : route / args.u[8];
|
||||
xs[index] = valid
|
||||
? (bfloat)x[(ulong)input_row * args.u[0] + first_k + column]
|
||||
: bfloat(0.0f);
|
||||
}
|
||||
for (uint index = tid; index < tile_n * tile_k; index += threads) {
|
||||
const uint row = index / tile_k;
|
||||
const uint column = index % tile_k;
|
||||
const uint weight_n = first_n + row;
|
||||
const ulong table_row = (ulong)expert * args.u[1] + weight_n;
|
||||
ws[index] = weight_n < args.u[1]
|
||||
? (bfloat)qwen_quant_weight(
|
||||
packed, scales, biases,
|
||||
args.u[13], args.u[14], args.u[15],
|
||||
weight_n, first_k + column,
|
||||
table_row, first_k + column,
|
||||
args.u[0], bits, group_size)
|
||||
: bfloat(0.0f);
|
||||
}
|
||||
@@ -648,15 +769,267 @@ kernel void kernel_qwen_affine_qmm_mpp(
|
||||
const auto index = accum.get_multidimensional_index(i);
|
||||
const uint output_n = first_n + index[0];
|
||||
const uint output_m = first_m + index[1];
|
||||
if (output_n < args.u[1] && output_m < args.u[4]) {
|
||||
const float value = accum[i];
|
||||
out[(ulong)output_m * args.u[1] + output_n] = args.u[11] != 0u
|
||||
? qwen_round_bf16(value)
|
||||
: value;
|
||||
if (output_n < args.u[1] && output_m < counts[expert]) {
|
||||
const uint route = expert_map[output_m];
|
||||
out[(ulong)route * args.u[1] + output_n] =
|
||||
qwen_round_bf16(accum[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <ushort bits, ushort group_size>
|
||||
kernel void kernel_qwen_affine_sorted_qmm_mpp(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device float *out [[buffer(1)]],
|
||||
device bfloat *x [[buffer(2)]],
|
||||
device const uint *route_map [[buffer(3)]],
|
||||
device const uchar *work [[buffer(4)]],
|
||||
device const uchar *packed [[buffer(5)]],
|
||||
device const uchar *scales [[buffer(6)]],
|
||||
device const uchar *biases [[buffer(7)]],
|
||||
device const uint *counts [[buffer(8)]],
|
||||
uint2 group [[threadgroup_position_in_grid]],
|
||||
uint tid [[thread_index_in_threadgroup]]) {
|
||||
constexpr uint tile_m = 64u;
|
||||
constexpr uint tile_n = 64u;
|
||||
constexpr uint tile_k = 32u;
|
||||
constexpr uint threads = 128u;
|
||||
device const uint *work_count = (device const uint *)work;
|
||||
if (group.y >= work_count[0]) return;
|
||||
const ulong items_offset =
|
||||
(8ul + 512ul * 4ul + (ulong)args.u[4] * args.u[8] * 4ul + 7ul) & ~7ul;
|
||||
device const uint2 *work_items = (device const uint2 *)(work + items_offset);
|
||||
const uint2 item = work_items[group.y];
|
||||
const uint expert = item.x;
|
||||
const uint local_m = item.y;
|
||||
if (local_m >= counts[expert]) return;
|
||||
device const uint *route_offsets = (device const uint *)(work + 8);
|
||||
const uint first_m = route_offsets[expert] + local_m;
|
||||
const uint first_n = group.x * tile_n;
|
||||
threadgroup bfloat ws[2u * tile_n * tile_k];
|
||||
auto weights0 = tensor<threadgroup bfloat, dextents<int32_t, 2>, tensor_inline>(
|
||||
ws, dextents<int32_t, 2>(tile_k, tile_n));
|
||||
auto weights1 = tensor<threadgroup bfloat, dextents<int32_t, 2>, tensor_inline>(
|
||||
ws + tile_n * tile_k, dextents<int32_t, 2>(tile_k, tile_n));
|
||||
auto activations = tensor<device bfloat, dextents<int32_t, 2>, tensor_inline>(
|
||||
x, dextents<int32_t, 2>(args.u[0], args.u[6]),
|
||||
array<int, 2>({1, (int)args.u[0]}));
|
||||
constexpr auto descriptor = matmul2d_descriptor(
|
||||
tile_m, tile_n, tile_k, false, true, true,
|
||||
matmul2d_descriptor::mode::multiply_accumulate);
|
||||
matmul2d<descriptor, execution_simdgroups<4>> multiply;
|
||||
auto accum = multiply.template get_destination_cooperative_tensor<
|
||||
decltype(activations), decltype(weights0), float>();
|
||||
#pragma clang loop unroll(full)
|
||||
for (uint i = 0u; i < accum.get_capacity(); i++) accum[i] = 0.0f;
|
||||
|
||||
auto stage_weights = [&](uint first_k, threadgroup bfloat *target) {
|
||||
constexpr uint per_word = 32u / bits;
|
||||
constexpr uint words_per_row = tile_k / per_word;
|
||||
for (uint index = tid; index < tile_n * words_per_row; index += threads) {
|
||||
const uint row = index / words_per_row;
|
||||
const uint column = (index % words_per_row) * per_word;
|
||||
const uint weight_n = first_n + row;
|
||||
const ulong table_row = (ulong)expert * args.u[1] + weight_n;
|
||||
uint word = 0u;
|
||||
float scale = 0.0f, bias = 0.0f;
|
||||
if (weight_n < args.u[1]) {
|
||||
word = qwen_weight_u32(packed, args.u[13],
|
||||
table_row * (args.u[0] / per_word) + (first_k + column) / per_word);
|
||||
const ulong quant_group = table_row * (args.u[0] / group_size)
|
||||
+ (first_k + column) / group_size;
|
||||
scale = qwen_bf16(qwen_weight_u16(scales, args.u[14], quant_group));
|
||||
bias = qwen_bf16(qwen_weight_u16(biases, args.u[15], quant_group));
|
||||
}
|
||||
#pragma clang loop unroll(full)
|
||||
for (uint element = 0u; element < per_word; element++) {
|
||||
const uint quant = (word >> (element * bits)) & ((1u << bits) - 1u);
|
||||
target[row * tile_k + column + element] =
|
||||
(bfloat)fma((float)quant, scale, bias);
|
||||
}
|
||||
}
|
||||
};
|
||||
stage_weights(0u, ws);
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
uint selected_weights = 0u;
|
||||
for (uint first_k = 0u; first_k < args.u[0]; first_k += tile_k) {
|
||||
auto weight_tile = selected_weights ? weights1 : weights0;
|
||||
auto activation_tile = activations.slice(first_k, first_m);
|
||||
multiply.run(activation_tile, weight_tile, accum);
|
||||
const uint next_k = first_k + tile_k;
|
||||
if (next_k < args.u[0]) {
|
||||
selected_weights ^= 1u;
|
||||
stage_weights(next_k, selected_weights ? ws + tile_n * tile_k : ws);
|
||||
}
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
}
|
||||
|
||||
#pragma clang loop unroll(full)
|
||||
for (uint i = 0u; i < accum.get_capacity(); i++) {
|
||||
const auto index = accum.get_multidimensional_index(i);
|
||||
const uint output_n = first_n + index[0];
|
||||
const uint local_output_m = local_m + index[1];
|
||||
if (output_n < args.u[1] && local_output_m < counts[expert]) {
|
||||
out[(ulong)(first_m + index[1]) * args.u[1] + output_n] =
|
||||
qwen_round_bf16(accum[i]);
|
||||
}
|
||||
}
|
||||
(void)route_map;
|
||||
}
|
||||
|
||||
typedef decltype(kernel_qwen_affine_sorted_qmm_mpp<4, 32>) qwen_affine_sorted_qmm_mpp_b4g32;
|
||||
typedef decltype(kernel_qwen_affine_sorted_qmm_mpp<4, 64>) qwen_affine_sorted_qmm_mpp_b4g64;
|
||||
typedef decltype(kernel_qwen_affine_sorted_qmm_mpp<8, 64>) qwen_affine_sorted_qmm_mpp_b8g64;
|
||||
template [[host_name("kernel_qwen_affine_sorted_qmm_mpp_b4g32")]]
|
||||
kernel qwen_affine_sorted_qmm_mpp_b4g32 kernel_qwen_affine_sorted_qmm_mpp<4, 32>;
|
||||
template [[host_name("kernel_qwen_affine_sorted_qmm_mpp_b4g64")]]
|
||||
kernel qwen_affine_sorted_qmm_mpp_b4g64 kernel_qwen_affine_sorted_qmm_mpp<4, 64>;
|
||||
template [[host_name("kernel_qwen_affine_sorted_qmm_mpp_b8g64")]]
|
||||
kernel qwen_affine_sorted_qmm_mpp_b8g64 kernel_qwen_affine_sorted_qmm_mpp<8, 64>;
|
||||
|
||||
typedef decltype(kernel_qwen_affine_gather_qmm_mpp<4, 32>) qwen_affine_gather_qmm_mpp_b4g32;
|
||||
typedef decltype(kernel_qwen_affine_gather_qmm_mpp<4, 64>) qwen_affine_gather_qmm_mpp_b4g64;
|
||||
typedef decltype(kernel_qwen_affine_gather_qmm_mpp<8, 64>) qwen_affine_gather_qmm_mpp_b8g64;
|
||||
|
||||
template [[host_name("kernel_qwen_affine_gather_qmm_mpp_b4g32")]]
|
||||
kernel qwen_affine_gather_qmm_mpp_b4g32 kernel_qwen_affine_gather_qmm_mpp<4, 32>;
|
||||
template [[host_name("kernel_qwen_affine_gather_qmm_mpp_b4g64")]]
|
||||
kernel qwen_affine_gather_qmm_mpp_b4g64 kernel_qwen_affine_gather_qmm_mpp<4, 64>;
|
||||
template [[host_name("kernel_qwen_affine_gather_qmm_mpp_b8g64")]]
|
||||
kernel qwen_affine_gather_qmm_mpp_b8g64 kernel_qwen_affine_gather_qmm_mpp<8, 64>;
|
||||
|
||||
template <ushort bits, ushort group_size, ushort tile_m>
|
||||
kernel void kernel_qwen_affine_qmm_mpp(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device float *out [[buffer(1)]],
|
||||
device float *x [[buffer(2)]],
|
||||
device const uchar *packed [[buffer(5)]],
|
||||
device const uchar *scales [[buffer(6)]],
|
||||
device const uchar *biases [[buffer(7)]],
|
||||
uint2 group [[threadgroup_position_in_grid]],
|
||||
uint tid [[thread_index_in_threadgroup]]) {
|
||||
constexpr uint tile_n = 64u;
|
||||
constexpr uint tile_k = 32u;
|
||||
constexpr uint threads = 128u;
|
||||
threadgroup bfloat ws[2u * tile_n * tile_k];
|
||||
const uint m_tiles = (args.u[4] + tile_m - 1u) / tile_m;
|
||||
const uint partition = group.y / m_tiles;
|
||||
const uint first_m = (group.y % m_tiles) * tile_m;
|
||||
const uint first_n = group.x * tile_n;
|
||||
const uint partition_k = args.u[0] / max(args.u[8], 1u);
|
||||
const uint start_k = partition * partition_k;
|
||||
const uint end_k = start_k + partition_k;
|
||||
out += partition * args.u[4] * args.u[1];
|
||||
auto weights0 = tensor<threadgroup bfloat, dextents<int32_t, 2>, tensor_inline>(
|
||||
ws, dextents<int32_t, 2>(tile_k, tile_n));
|
||||
auto weights1 = tensor<threadgroup bfloat, dextents<int32_t, 2>, tensor_inline>(
|
||||
ws + tile_n * tile_k, dextents<int32_t, 2>(tile_k, tile_n));
|
||||
auto activations = tensor<device float, dextents<int32_t, 2>, tensor_inline>(
|
||||
x, dextents<int32_t, 2>(args.u[0], args.u[4]),
|
||||
array<int, 2>({1, (int)args.u[0]}));
|
||||
constexpr auto descriptor = matmul2d_descriptor(
|
||||
tile_m, tile_n, tile_k, false, true, true,
|
||||
matmul2d_descriptor::mode::multiply_accumulate);
|
||||
matmul2d<descriptor, execution_simdgroups<4>> multiply;
|
||||
auto accum = multiply.template get_destination_cooperative_tensor<
|
||||
decltype(activations), decltype(weights0),
|
||||
float>();
|
||||
#pragma clang loop unroll(full)
|
||||
for (uint i = 0u; i < accum.get_capacity(); i++) {
|
||||
accum[i] = 0.0f;
|
||||
}
|
||||
|
||||
auto stage_weights = [&](uint first_k, threadgroup bfloat *target) {
|
||||
// Decode a packed word once, reusing its scale/bias across its values,
|
||||
// as in the sorted QMM loader and MTPLX's quantized matrix loader.
|
||||
constexpr uint per_word = 32u / bits;
|
||||
constexpr uint words_per_row = tile_k / per_word;
|
||||
for (uint index = tid; index < tile_n * words_per_row; index += threads) {
|
||||
const uint row = index / words_per_row;
|
||||
const uint column = (index % words_per_row) * per_word;
|
||||
const uint weight_n = first_n + row;
|
||||
uint word = 0u;
|
||||
float scale = 0.0f, bias = 0.0f;
|
||||
if (weight_n < args.u[1]) {
|
||||
word = qwen_weight_u32(packed, args.u[13],
|
||||
(ulong)weight_n * (args.u[0] / per_word) + (first_k + column) / per_word);
|
||||
const ulong quant_group = (ulong)weight_n * (args.u[0] / group_size)
|
||||
+ (first_k + column) / group_size;
|
||||
scale = qwen_bf16(qwen_weight_u16(scales, args.u[14], quant_group));
|
||||
bias = qwen_bf16(qwen_weight_u16(biases, args.u[15], quant_group));
|
||||
}
|
||||
#pragma clang loop unroll(full)
|
||||
for (uint element = 0u; element < per_word; element++) {
|
||||
const uint quant = (word >> (element * bits)) & ((1u << bits) - 1u);
|
||||
target[row * tile_k + column + element] =
|
||||
(bfloat)fma((float)quant, scale, bias);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
stage_weights(start_k, ws);
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
uint selected_weights = 0u;
|
||||
for (uint first_k = start_k; first_k < end_k; first_k += tile_k) {
|
||||
auto weight_tile = selected_weights ? weights1 : weights0;
|
||||
auto activation_tile = activations.slice(first_k, first_m);
|
||||
multiply.run(activation_tile, weight_tile, accum);
|
||||
const uint next_k = first_k + tile_k;
|
||||
if (next_k < end_k) {
|
||||
selected_weights ^= 1u;
|
||||
stage_weights(
|
||||
next_k,
|
||||
selected_weights ? ws + tile_n * tile_k : ws);
|
||||
}
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
}
|
||||
|
||||
auto output = tensor<device float, dextents<int32_t, 2>, tensor_inline>(
|
||||
out, dextents<int32_t, 2>(args.u[1], args.u[4]),
|
||||
array<int, 2>({1, (int)args.u[1]}));
|
||||
if (args.u[11] != 0u) {
|
||||
#pragma clang loop unroll(full)
|
||||
for (uint i = 0u; i < accum.get_capacity(); i++) {
|
||||
accum[i] = qwen_round_bf16(accum[i]);
|
||||
}
|
||||
}
|
||||
auto output_tile = output.slice(first_n, first_m);
|
||||
accum.store(output_tile);
|
||||
}
|
||||
|
||||
// Match MTPLX's BF16 column-reduction order, including intermediate rounding.
|
||||
// Each simdgroup handles one output; small reductions use eight strided lanes,
|
||||
// larger reductions use the reference's 32-lane BF16 simd reduction.
|
||||
kernel void kernel_qwen_affine_splitk_reduce(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device float *out [[buffer(1)]],
|
||||
device const float *parts [[buffer(2)]],
|
||||
uint group [[threadgroup_position_in_grid]],
|
||||
uint simd_group [[simdgroup_index_in_threadgroup]],
|
||||
uint lane [[thread_index_in_simdgroup]]) {
|
||||
const uint index = group * 2u + simd_group;
|
||||
const uint stride = args.u[4] * args.u[1];
|
||||
if (index >= stride) return;
|
||||
const uint count = args.u[8];
|
||||
const uint lanes = count < 32u ? min(count, 8u) : 32u;
|
||||
bfloat total = bfloat(0.0f);
|
||||
for (uint p = lane; p < count; p += lanes) {
|
||||
if (lane < lanes) total = bfloat(float(total) + parts[p * stride + index]);
|
||||
}
|
||||
if (count < 32u) {
|
||||
bfloat sum = total;
|
||||
for (uint p = 1u; p < lanes; p++) {
|
||||
const float next = simd_shuffle(float(total), p);
|
||||
sum = bfloat(float(sum) + float(next));
|
||||
}
|
||||
if (lane == 0u) out[index] = float(sum);
|
||||
} else {
|
||||
// MTPLX's bfloat16_t overload reduces in float, then rounds once.
|
||||
const bfloat sum = bfloat(simd_sum(float(total)));
|
||||
if (lane == 0u) out[index] = float(sum);
|
||||
}
|
||||
}
|
||||
|
||||
typedef decltype(kernel_qwen_affine_qmm_mpp<4, 32, 32>) qwen_affine_qmm_mpp_b4g32_bm32;
|
||||
typedef decltype(kernel_qwen_affine_qmm_mpp<4, 64, 32>) qwen_affine_qmm_mpp_b4g64_bm32;
|
||||
typedef decltype(kernel_qwen_affine_qmm_mpp<8, 64, 32>) qwen_affine_qmm_mpp_b8g64_bm32;
|
||||
@@ -1209,6 +1582,33 @@ kernel void kernel_qwen_weighted_sum10(
|
||||
out[index] = value;
|
||||
}
|
||||
|
||||
kernel void kernel_qwen_weighted_sum10_sorted(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device float *out [[buffer(1)]],
|
||||
device const float *experts [[buffer(2)]],
|
||||
device const float *weights [[buffer(3)]],
|
||||
device const uchar *work [[buffer(4)]],
|
||||
uint index [[thread_position_in_grid]]) {
|
||||
if (index >= args.u[0]) return;
|
||||
const uint hidden = args.u[1];
|
||||
const uint row = index / hidden;
|
||||
const uint column = index % hidden;
|
||||
device const uint *inverse = (device const uint *)(work + 8) + 512;
|
||||
const ulong routes_base = (ulong)row * args.u[8];
|
||||
float weighted[10];
|
||||
for (uint slot = 0; slot < 10u; slot++) {
|
||||
const ulong route = routes_base + slot;
|
||||
weighted[slot] = qwen_round_bf16(
|
||||
experts[(ulong)inverse[route] * hidden + column] * weights[route]);
|
||||
}
|
||||
float value = qwen_round_bf16(weighted[0] + weighted[8]);
|
||||
value = qwen_round_bf16(value + qwen_round_bf16(weighted[1] + weighted[9]));
|
||||
for (uint slot = 2; slot < 8u; slot++) {
|
||||
value = qwen_round_bf16(value + weighted[slot]);
|
||||
}
|
||||
out[index] = value;
|
||||
}
|
||||
|
||||
kernel void kernel_qwen_affine_embedding(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device float *out [[buffer(1)]],
|
||||
@@ -1959,7 +2359,9 @@ kernel void kernel_qwen_gdn_conv_norm(
|
||||
: qwen_round_bf16(normalized);
|
||||
}
|
||||
|
||||
// MTPLX fused_gdn_conv_norm_rows, including its in-window convolution tail.
|
||||
// MTPLX fused_gdn_conv_norm_rows at S<=6. Larger prefills preserve the
|
||||
// Conv1d -> SiLU -> L2 BF16 boundaries and parallelize independent rows.
|
||||
// In the parallel path state_out must not alias the input state.
|
||||
kernel void kernel_qwen_gdn_conv_norm_rows(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device float *q_out [[buffer(1)]],
|
||||
@@ -1972,12 +2374,12 @@ kernel void kernel_qwen_gdn_conv_norm_rows(
|
||||
uint tid [[thread_index_in_threadgroup]],
|
||||
uint lane [[thread_index_in_simdgroup]],
|
||||
uint simd_group [[simdgroup_index_in_threadgroup]],
|
||||
uint group [[threadgroup_position_in_grid]]) {
|
||||
uint2 group [[threadgroup_position_in_grid]]) {
|
||||
constexpr uint width = 10240u;
|
||||
constexpr uint key_width = 2048u;
|
||||
constexpr uint dim = 128u;
|
||||
constexpr float inverse_scale = 0.08838834764831845f;
|
||||
const uint channel = group * 1024u + tid;
|
||||
const uint channel = group.x * 1024u + tid;
|
||||
if (channel >= width) return;
|
||||
|
||||
threadgroup float values[1024];
|
||||
@@ -1988,7 +2390,10 @@ kernel void kernel_qwen_gdn_conv_norm_rows(
|
||||
const float w3 = qwen_bf16(qwen_weight_u16(weight, args.u[14], (ulong)channel * 4u + 3u));
|
||||
const bool value_channel = channel >= 2u * key_width;
|
||||
|
||||
for (uint row = 0u; row < args.u[4]; row++) {
|
||||
const bool prefill = args.u[4] > 6u;
|
||||
const uint first_row = prefill ? group.y : 0u;
|
||||
const uint end_row = prefill ? first_row + 1u : args.u[4];
|
||||
for (uint row = first_row; row < end_row; row++) {
|
||||
const float x0 = row < 3u
|
||||
? qwen_bf16(state[(ulong)row * width + channel])
|
||||
: qkv[(ulong)(row - 3u) * width + channel];
|
||||
@@ -2000,7 +2405,10 @@ kernel void kernel_qwen_gdn_conv_norm_rows(
|
||||
: qkv[(ulong)(row - 1u) * width + channel];
|
||||
const float x3 = qkv[(ulong)row * width + channel];
|
||||
const float convolved = w0 * x0 + w1 * x1 + w2 * x2 + w3 * x3;
|
||||
const float activated = convolved / (1.0f + exp(-convolved));
|
||||
const float rounded = qwen_round_bf16(convolved);
|
||||
const float activated = prefill
|
||||
? qwen_round_bf16(rounded * qwen_silu_sigmoid_bf16(rounded))
|
||||
: convolved / (1.0f + exp(-convolved));
|
||||
if (value_channel) {
|
||||
v_out[(ulong)row * (width - 2u * key_width) + channel - 2u * key_width] =
|
||||
qwen_round_bf16(activated);
|
||||
@@ -2013,10 +2421,12 @@ kernel void kernel_qwen_gdn_conv_norm_rows(
|
||||
const uint first = (simd_group / 4u) * 4u;
|
||||
sum = partial[first] + partial[first + 1u] +
|
||||
partial[first + 2u] + partial[first + 3u];
|
||||
const float normalized = values[tid] * rsqrt(sum + 1.0e-6f);
|
||||
const float normalized = values[tid] * (prefill
|
||||
? precise::rsqrt(sum + 1.0e-6f) : rsqrt(sum + 1.0e-6f));
|
||||
if (channel < key_width) {
|
||||
q_out[(ulong)row * key_width + channel] =
|
||||
qwen_round_bf16(normalized * inverse_scale);
|
||||
prefill ? qwen_round_bf16(qwen_round_bf16(normalized) * 0.08837890625f)
|
||||
: qwen_round_bf16(normalized * inverse_scale);
|
||||
} else {
|
||||
k_out[(ulong)row * key_width + channel - key_width] =
|
||||
qwen_round_bf16(normalized);
|
||||
@@ -2024,6 +2434,7 @@ kernel void kernel_qwen_gdn_conv_norm_rows(
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
}
|
||||
|
||||
if (prefill && group.y != 0u) return;
|
||||
for (uint tail = 0u; tail < 3u; tail++) {
|
||||
const uint sequence = args.u[4] + tail;
|
||||
const float value = sequence < 3u
|
||||
@@ -2609,18 +3020,28 @@ kernel void kernel_qwen_gdn_norm_gate(
|
||||
}
|
||||
}
|
||||
|
||||
kernel void kernel_qwen_swiglu(
|
||||
template <typename T>
|
||||
kernel void kernel_qwen_swiglu_typed(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device float *out [[buffer(1)]],
|
||||
device T *out [[buffer(1)]],
|
||||
device const float *gate [[buffer(2)]],
|
||||
device const float *up [[buffer(3)]],
|
||||
uint index [[thread_position_in_grid]]) {
|
||||
if (index >= args.u[0]) return;
|
||||
const float activated = qwen_round_bf16(
|
||||
gate[index] * qwen_silu_sigmoid_bf16(gate[index]));
|
||||
out[index] = qwen_round_bf16(activated * up[index]);
|
||||
out[index] = (T)qwen_round_bf16(activated * up[index]);
|
||||
}
|
||||
|
||||
template [[host_name("kernel_qwen_swiglu")]]
|
||||
kernel void kernel_qwen_swiglu_typed<float>(
|
||||
constant qwen_kernel_args &, device float *, device const float *,
|
||||
device const float *, uint);
|
||||
template [[host_name("kernel_qwen_swiglu_bf16")]]
|
||||
kernel void kernel_qwen_swiglu_typed<bfloat>(
|
||||
constant qwen_kernel_args &, device bfloat *, device const float *,
|
||||
device const float *, uint);
|
||||
|
||||
kernel void kernel_qwen_unpack_gdn_inputs(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device float *qkv [[buffer(1)]],
|
||||
@@ -3017,10 +3438,18 @@ kernel void kernel_qwen_qsa_scores(
|
||||
device float *scores [[buffer(1)]],
|
||||
device const float *query [[buffer(2)]],
|
||||
device const ushort *pooled [[buffer(3)]],
|
||||
uint block [[thread_position_in_grid]]) {
|
||||
uint2 group [[thread_position_in_grid]]) {
|
||||
const uint block = group.x;
|
||||
const uint row = group.y;
|
||||
const uint dim = args.u[0];
|
||||
if (block >= args.u[2]) return;
|
||||
const ulong score_index = (ulong)row * args.u[2] + block;
|
||||
if (args.u[5] != 0u && block >= (args.u[3] + row) / args.u[5]) {
|
||||
scores[score_index] = -INFINITY;
|
||||
return;
|
||||
}
|
||||
float score = 0.0f;
|
||||
query += (ulong)row * args.u[1] * dim;
|
||||
for (uint head = 0; head < args.u[1]; head++) {
|
||||
float head_score = 0.0f;
|
||||
for (uint i = 0; i < dim; i++) {
|
||||
@@ -3030,14 +3459,14 @@ kernel void kernel_qwen_qsa_scores(
|
||||
}
|
||||
score += max(head_score, 0.0f);
|
||||
}
|
||||
scores[block] = score * args.f[0];
|
||||
scores[score_index] = score * args.f[0];
|
||||
}
|
||||
|
||||
kernel void kernel_qwen_qsa_sort_blocks(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device int *selected [[buffer(1)]],
|
||||
uint gid [[thread_position_in_grid]]) {
|
||||
if (gid != 0u) return;
|
||||
uint row [[thread_position_in_grid]]) {
|
||||
selected += (ulong)row * args.u[0];
|
||||
for (uint i = 1; i < args.u[0]; i++) {
|
||||
const int value = selected[i];
|
||||
uint j = i;
|
||||
@@ -3049,15 +3478,45 @@ kernel void kernel_qwen_qsa_sort_blocks(
|
||||
}
|
||||
}
|
||||
|
||||
kernel void kernel_qwen_qsa_mask(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device uchar *mask [[buffer(1)]],
|
||||
device const int *selected [[buffer(2)]],
|
||||
uint row [[threadgroup_position_in_grid]],
|
||||
uint lane [[thread_index_in_threadgroup]]) {
|
||||
if (row >= args.u[4]) return;
|
||||
const uint tokens = args.u[0];
|
||||
const uint visible = args.u[8] + row;
|
||||
const uint tail_start = (visible / args.u[5]) * args.u[5];
|
||||
const ulong mask_base = (ulong)row * tokens;
|
||||
for (uint token = lane; token < tokens; token += args.u[12]) {
|
||||
mask[mask_base + token] = token >= tail_start && token < visible;
|
||||
}
|
||||
threadgroup_barrier(mem_flags::mem_device);
|
||||
if (lane < args.u[6]) {
|
||||
const int block = selected[(ulong)row * args.u[6] + lane];
|
||||
if (block >= 0) {
|
||||
const uint token0 = (uint)block * args.u[5];
|
||||
if (token0 < tail_start) {
|
||||
for (uint within = 0u; within < args.u[5]; within++) {
|
||||
mask[mask_base + token0 + within] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kernel void kernel_qwen_sparse_attention(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device float *out [[buffer(1)]],
|
||||
device const float *query [[buffer(2)]],
|
||||
device const ushort *cache [[buffer(3)]],
|
||||
device const int *selected [[buffer(4)]],
|
||||
uint head [[threadgroup_position_in_grid]],
|
||||
uint tid [[thread_position_in_threadgroup]],
|
||||
uint2 group [[threadgroup_position_in_grid]],
|
||||
uint tid [[thread_index_in_threadgroup]],
|
||||
uint lane [[thread_index_in_simdgroup]]) {
|
||||
const uint head = group.x;
|
||||
const uint row = group.y;
|
||||
const uint heads = args.u[0];
|
||||
const uint kv_heads = args.u[1];
|
||||
const uint dim = args.u[2];
|
||||
@@ -3071,7 +3530,14 @@ kernel void kernel_qwen_sparse_attention(
|
||||
threadgroup float sum_exp_scores[simdgroups];
|
||||
threadgroup float outputs[simdgroups * 256u];
|
||||
const float attention_scale = precise::rsqrt((float)dim);
|
||||
q[tid] = query[(ulong)head * dim + tid] * attention_scale;
|
||||
const ulong query_base = ((ulong)row * heads + head) * dim;
|
||||
const ulong output_base = query_base;
|
||||
selected += (ulong)row * args.u[4];
|
||||
const uint tokens = args.u[8] != 0u ? args.u[8] + row : args.u[3];
|
||||
const uint tail_start = args.u[8] != 0u
|
||||
? (tokens / args.u[5]) * args.u[5]
|
||||
: args.u[6];
|
||||
q[tid] = query[query_base + tid] * attention_scale;
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
|
||||
float result[values_per_lane] = {0.0f};
|
||||
@@ -3101,8 +3567,8 @@ kernel void kernel_qwen_sparse_attention(
|
||||
max_score = new_max;
|
||||
}
|
||||
}
|
||||
for (uint token = args.u[6] + simd_group;
|
||||
token < args.u[3];
|
||||
for (uint token = tail_start + simd_group;
|
||||
token < tokens;
|
||||
token += simdgroups) {
|
||||
const ulong base = (ulong)token * kv_heads * dim * 2u +
|
||||
(ulong)kv_head * dim + column;
|
||||
@@ -3142,7 +3608,7 @@ kernel void kernel_qwen_sparse_attention(
|
||||
merged_sum += sum_exp_scores[group] * weight;
|
||||
merged_value += outputs[group * dim + tid] * weight;
|
||||
}
|
||||
out[(ulong)head * dim + tid] = qwen_round_bf16(merged_value / merged_sum);
|
||||
out[output_base + tid] = qwen_round_bf16(merged_value / merged_sum);
|
||||
}
|
||||
|
||||
kernel void kernel_qwen_dense_attention_masked(
|
||||
@@ -3393,6 +3859,52 @@ kernel void kernel_qwen_attention_fallback_softmax(
|
||||
}
|
||||
}
|
||||
|
||||
kernel void kernel_qwen_attention_fallback_softmax_wide(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device ushort *out [[buffer(1)]],
|
||||
device const ushort *input [[buffer(2)]],
|
||||
uint group [[threadgroup_position_in_grid]],
|
||||
uint lid [[thread_position_in_threadgroup]],
|
||||
uint lane [[thread_index_in_simdgroup]],
|
||||
uint simd_group [[simdgroup_index_in_threadgroup]]) {
|
||||
threadgroup float local_max[32];
|
||||
threadgroup float local_normalizer[32];
|
||||
const uint tokens = args.u[3];
|
||||
const uint threads = args.u[12];
|
||||
const ulong base = (ulong)group * tokens;
|
||||
float maximum = -FLT_MAX;
|
||||
for (uint token = lid; token < tokens; token += threads) {
|
||||
maximum = max(maximum, qwen_bf16(input[base + token]));
|
||||
}
|
||||
maximum = simd_max(maximum);
|
||||
if (lane == 0u) local_max[simd_group] = maximum;
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
if (simd_group == 0u) {
|
||||
maximum = simd_max(local_max[lane]);
|
||||
if (lane == 0u) local_max[0] = maximum;
|
||||
}
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
maximum = local_max[0];
|
||||
|
||||
float normalizer = 0.0f;
|
||||
for (uint token = lid; token < tokens; token += threads) {
|
||||
normalizer += fast::exp(qwen_bf16(input[base + token]) - maximum);
|
||||
}
|
||||
normalizer = simd_sum(normalizer);
|
||||
if (lane == 0u) local_normalizer[simd_group] = normalizer;
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
if (simd_group == 0u) {
|
||||
normalizer = simd_sum(local_normalizer[lane]);
|
||||
if (lane == 0u) local_normalizer[0] = normalizer;
|
||||
}
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
normalizer = 1.0f / local_normalizer[0];
|
||||
for (uint token = lid; token < tokens; token += threads) {
|
||||
out[base + token] = qwen_to_bf16(
|
||||
fast::exp(qwen_bf16(input[base + token]) - maximum) * normalizer);
|
||||
}
|
||||
}
|
||||
|
||||
kernel void kernel_qwen_attention_fallback_output(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device float *out [[buffer(1)]],
|
||||
@@ -3449,6 +3961,24 @@ kernel void kernel_qwen_prepare_dense_kv(
|
||||
values[target] = cache[source + width];
|
||||
}
|
||||
|
||||
kernel void kernel_qwen_prepare_dense_kv_f16(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device half *keys [[buffer(1)]],
|
||||
device const ushort *cache [[buffer(2)]],
|
||||
device half *values [[buffer(3)]],
|
||||
uint2 gid [[thread_position_in_grid]]) {
|
||||
const uint width = args.u[0];
|
||||
const uint dim = args.u[1];
|
||||
const uint tokens = args.u[3];
|
||||
if (gid.x >= width || gid.y >= tokens) return;
|
||||
const uint head = gid.x / dim;
|
||||
const uint column = gid.x % dim;
|
||||
const ulong source = (ulong)gid.y * width * 2u + gid.x;
|
||||
const ulong target = ((ulong)head * tokens + gid.y) * dim + column;
|
||||
keys[target] = (half)qwen_bf16(cache[source]);
|
||||
values[target] = (half)qwen_bf16(cache[source + width]);
|
||||
}
|
||||
|
||||
kernel void kernel_qwen_float_to_bf16(
|
||||
constant qwen_kernel_args &args [[buffer(0)]],
|
||||
device ushort *out [[buffer(1)]],
|
||||
|
||||
Reference in New Issue
Block a user