mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-11-11 22:32:07 +01:00
vulkan: support larger argsort (#17313)
* vulkan: support larger argsort This is an extension of the original bitonic sorting shader that puts the temporary values in global memory and when more than 1024 threads are needed it runs multiple workgroups and synchronizes through a pipelinebarrier. To improve the memory access pattern, a copy of the float value is kept with the index value. I've applied this same change to the original shared memory version of the shader, which is still used when ncols <= 1024. * Reduce the number of shader variants. Use smaller workgroups when doing a single pass, for a modest perf boost * reduce loop overhead * run multiple cols per invocation, to reduce barrier overhead
This commit is contained in:
@@ -4,28 +4,27 @@
|
||||
#include "types.glsl"
|
||||
|
||||
layout(constant_id = 0) const int BLOCK_SIZE = 1024;
|
||||
layout(constant_id = 1) const int BLOCK_SIZE_LOG2 = 10;
|
||||
layout(constant_id = 1) const int NCOLS_PADDED_LOG2 = 10;
|
||||
#define ASC 0
|
||||
|
||||
layout(local_size_x_id = 0, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
layout (binding = 0) readonly buffer A {A_TYPE data_a[];};
|
||||
layout (binding = 1) buffer D {int data_d[];};
|
||||
layout (binding = 2) writeonly buffer D {int data_d[];};
|
||||
|
||||
layout (push_constant) uniform parameter {
|
||||
uint ncols;
|
||||
uint ncols_padded;
|
||||
uint ncols_padded_log2;
|
||||
uint nrows;
|
||||
uint order;
|
||||
uint outer_start;
|
||||
uint outer_end;
|
||||
uint inner_start;
|
||||
uint inner_end;
|
||||
} p;
|
||||
|
||||
shared int dst_row[BLOCK_SIZE];
|
||||
shared A_TYPE a_sh[BLOCK_SIZE];
|
||||
|
||||
void swap(uint idx0, uint idx1) {
|
||||
int tmp = dst_row[idx0];
|
||||
dst_row[idx0] = dst_row[idx1];
|
||||
dst_row[idx1] = tmp;
|
||||
}
|
||||
shared ivec2 dst_row[BLOCK_SIZE];
|
||||
|
||||
void argsort(bool needs_bounds_check, const uint row) {
|
||||
// bitonic sort
|
||||
@@ -34,11 +33,10 @@ void argsort(bool needs_bounds_check, const uint row) {
|
||||
const uint row_offset = row * p.ncols;
|
||||
|
||||
// initialize indices
|
||||
dst_row[col] = col;
|
||||
a_sh[col] = data_a[row_offset + col];
|
||||
dst_row[col] = ivec2(col, floatBitsToInt(data_a[row_offset + col]));
|
||||
barrier();
|
||||
|
||||
uint num_outer_loop_iters = BLOCK_SIZE_LOG2;
|
||||
uint num_outer_loop_iters = NCOLS_PADDED_LOG2;
|
||||
[[unroll]] for (uint k = 2, outer_idx = 0; outer_idx < num_outer_loop_iters; k *= 2, outer_idx++) {
|
||||
uint num_inner_loop_iters = outer_idx + 1;
|
||||
[[unroll]] for (uint j = k / 2, inner_idx = 0; inner_idx < num_inner_loop_iters; j /= 2, inner_idx++) {
|
||||
@@ -47,14 +45,15 @@ void argsort(bool needs_bounds_check, const uint row) {
|
||||
int idx_0 = (col & k) == 0 ? col : ixj;
|
||||
int idx_1 = (col & k) == 0 ? ixj : col;
|
||||
|
||||
int sh_idx_0 = dst_row[idx_0];
|
||||
int sh_idx_1 = dst_row[idx_1];
|
||||
bool idx_0_oob = needs_bounds_check ? sh_idx_0 >= p.ncols : false;
|
||||
bool idx_1_oob = needs_bounds_check ? sh_idx_1 >= p.ncols : false;
|
||||
ivec2 sh_idx_0 = dst_row[idx_0];
|
||||
ivec2 sh_idx_1 = dst_row[idx_1];
|
||||
bool idx_0_oob = needs_bounds_check ? sh_idx_0.x >= p.ncols : false;
|
||||
bool idx_1_oob = needs_bounds_check ? sh_idx_1.x >= p.ncols : false;
|
||||
|
||||
if ((idx_0_oob ||
|
||||
(!idx_1_oob && a_sh[sh_idx_0] > a_sh[sh_idx_1])) && (ixj > col)) {
|
||||
swap(idx_0, idx_1);
|
||||
(!idx_1_oob && intBitsToFloat(sh_idx_0.y) > intBitsToFloat(sh_idx_1.y))) && (ixj > col)) {
|
||||
dst_row[idx_0] = sh_idx_1;
|
||||
dst_row[idx_1] = sh_idx_0;
|
||||
}
|
||||
|
||||
barrier();
|
||||
@@ -63,9 +62,9 @@ void argsort(bool needs_bounds_check, const uint row) {
|
||||
|
||||
if (col < p.ncols) {
|
||||
if (p.order == ASC) {
|
||||
data_d[row_offset + col] = dst_row[col];
|
||||
data_d[row_offset + col] = dst_row[col].x;
|
||||
} else {
|
||||
data_d[row_offset + p.ncols - col - 1] = dst_row[col];
|
||||
data_d[row_offset + p.ncols - col - 1] = dst_row[col].x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
#version 450
|
||||
#extension GL_EXT_control_flow_attributes : enable
|
||||
#extension GL_KHR_memory_scope_semantics : enable
|
||||
#pragma use_vulkan_memory_model
|
||||
|
||||
#include "types.glsl"
|
||||
|
||||
layout(constant_id = 0) const int BLOCK_SIZE = 1024;
|
||||
layout(constant_id = 1) const int WG_UNROLL_FACTOR = 2;
|
||||
#define ASC 0
|
||||
|
||||
layout(local_size_x_id = 0, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
layout (binding = 0) readonly buffer A {A_TYPE data_a[];};
|
||||
layout (binding = 1) workgroupcoherent buffer B {ivec2 tmp_idx[];};
|
||||
layout (binding = 2) workgroupcoherent buffer D {int data_d[];};
|
||||
|
||||
layout (push_constant) uniform parameter {
|
||||
uint ncols;
|
||||
uint ncols_padded;
|
||||
uint ncols_padded_log2;
|
||||
uint nrows;
|
||||
uint order;
|
||||
uint outer_start;
|
||||
uint outer_end;
|
||||
uint inner_start;
|
||||
uint inner_end;
|
||||
} p;
|
||||
|
||||
void argsort(bool needs_bounds_check, const uint row) {
|
||||
// bitonic sort
|
||||
int col = int(gl_GlobalInvocationID.x);
|
||||
col = (col % BLOCK_SIZE) + (col / BLOCK_SIZE) * BLOCK_SIZE * WG_UNROLL_FACTOR;
|
||||
|
||||
const uint row_offset = row * p.ncols;
|
||||
uint idx_offset = row * p.ncols_padded;
|
||||
|
||||
bool need_barrier = false;
|
||||
|
||||
// initialize indices
|
||||
if (p.outer_start == 0 && p.inner_start == 0) {
|
||||
[[unroll]] for (int u = 0; u < WG_UNROLL_FACTOR; ++u) {
|
||||
uint c = u*BLOCK_SIZE + col;
|
||||
if (c < p.ncols_padded) {
|
||||
ivec2 v = ivec2(c, floatBitsToInt(data_a[row_offset + c]));
|
||||
tmp_idx[idx_offset + c] = v;
|
||||
}
|
||||
}
|
||||
need_barrier = true;
|
||||
}
|
||||
|
||||
[[unroll]] for (uint outer_idx = p.outer_start, k = (2 << outer_idx); outer_idx < p.outer_end; k *= 2, outer_idx++) {
|
||||
uint inner_end = min(p.inner_end, outer_idx + 1);
|
||||
for (uint j = k >> (p.inner_start + 1), inner_idx = p.inner_start; inner_idx < inner_end; j /= 2, inner_idx++) {
|
||||
if (need_barrier) {
|
||||
controlBarrier(gl_ScopeWorkgroup, gl_ScopeWorkgroup, gl_StorageSemanticsBuffer, gl_SemanticsAcquireRelease);
|
||||
}
|
||||
need_barrier = true;
|
||||
[[unroll]] for (int u = 0; u < WG_UNROLL_FACTOR; ++u) {
|
||||
int c = u*BLOCK_SIZE + col;
|
||||
const int ixj = int(c ^ j);
|
||||
|
||||
if (ixj < c) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int idx_0 = (c & k) == 0 ? c : ixj;
|
||||
int idx_1 = (c & k) == 0 ? ixj : c;
|
||||
|
||||
ivec2 sh_idx_0 = tmp_idx[idx_offset + idx_0];
|
||||
ivec2 sh_idx_1 = tmp_idx[idx_offset + idx_1];
|
||||
bool idx_0_oob = needs_bounds_check ? sh_idx_0.x >= p.ncols : false;
|
||||
bool idx_1_oob = needs_bounds_check ? sh_idx_1.x >= p.ncols : false;
|
||||
|
||||
if ((idx_0_oob ||
|
||||
(!idx_1_oob && intBitsToFloat(sh_idx_0.y) > intBitsToFloat(sh_idx_1.y)))) {
|
||||
tmp_idx[idx_offset + idx_0] = sh_idx_1;
|
||||
tmp_idx[idx_offset + idx_1] = sh_idx_0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p.outer_end == p.ncols_padded_log2 &&
|
||||
p.inner_end >= p.ncols_padded_log2 + 1) {
|
||||
controlBarrier(gl_ScopeWorkgroup, gl_ScopeWorkgroup, gl_StorageSemanticsBuffer, gl_SemanticsAcquireRelease);
|
||||
[[unroll]] for (int u = 0; u < WG_UNROLL_FACTOR; ++u) {
|
||||
uint c = u*BLOCK_SIZE + col;
|
||||
if (c < p.ncols) {
|
||||
if (p.order == ASC) {
|
||||
data_d[row_offset + c] = tmp_idx[idx_offset + c].x;
|
||||
} else {
|
||||
data_d[row_offset + p.ncols - c - 1] = tmp_idx[idx_offset + c].x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
if (p.ncols == p.ncols_padded) {
|
||||
uint row = gl_WorkGroupID.y;
|
||||
while (row < p.nrows) {
|
||||
argsort(false, row);
|
||||
row += gl_WorkGroupSize.y * gl_NumWorkGroups.y;
|
||||
}
|
||||
} else {
|
||||
uint row = gl_WorkGroupID.y;
|
||||
while (row < p.nrows) {
|
||||
argsort(true, row);
|
||||
row += gl_WorkGroupSize.y * gl_NumWorkGroups.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -892,6 +892,7 @@ void process_shaders() {
|
||||
string_to_spv("rope_vision_f16_rte", "rope_vision.comp", {{"A_TYPE", "float16_t"}, {"ROPE_D_TYPE", "float16_t"}, {"RTE16", "1"}});
|
||||
|
||||
string_to_spv("argsort_f32", "argsort.comp", {{"A_TYPE", "float"}});
|
||||
string_to_spv("argsort_large_f32", "argsort_large.comp", {{"A_TYPE", "float"}});
|
||||
|
||||
string_to_spv("argmax_f32", "argmax.comp", merge_maps(base_dict, {{"A_TYPE", "float"}, {"D_TYPE", "int"}}));
|
||||
string_to_spv("sum_rows_f32", "sum_rows.comp", merge_maps(base_dict, {{"A_TYPE", "float"}, {"D_TYPE", "float"}}));
|
||||
|
||||
Reference in New Issue
Block a user