mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-10-08 22:07:37 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d65c90d04 | ||
|
|
de7fa0a3c6 | ||
|
|
71ad0590f4 | ||
|
|
a11f57ba93 | ||
|
|
fc9ce6b9d5 | ||
|
|
c35b66744f | ||
|
|
1167d3f42c | ||
|
|
4f92965a7b | ||
|
|
c811cb8f0a | ||
|
|
033df86b69 | ||
|
|
ff5888f999 | ||
|
|
dac3087394 |
+1
-1
@@ -2778,7 +2778,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
).set_env("LLAMA_ARG_N_CPU_MOE"));
|
||||
add_opt(common_arg(
|
||||
{"--moe-cache-mib"}, "N",
|
||||
"GPU cache size in MiB for the MoE experts kept in the CPU (default: 0, disabled)",
|
||||
"GPU cache size in MiB for the MoE experts kept in the CPU. with multiple GPUs, it is split among them like the layers (--tensor-split) (default: 0, disabled)",
|
||||
[](common_params & params, int value) {
|
||||
if (value < 0) {
|
||||
throw std::invalid_argument("invalid value");
|
||||
|
||||
+8
-12
@@ -2388,40 +2388,36 @@ void common_prompt_checkpoint::update_dft(
|
||||
}
|
||||
}
|
||||
|
||||
void common_prompt_checkpoint::load_tgt(
|
||||
bool common_prompt_checkpoint::load_tgt(
|
||||
llama_context * ctx,
|
||||
llama_seq_id seq_id,
|
||||
llama_state_seq_flags flags) const {
|
||||
if (ctx == nullptr) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data_tgt.empty()) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
const size_t n = llama_state_seq_set_data_ext(ctx, data_tgt.data(), data_tgt.size(), seq_id, flags);
|
||||
if (n != data_tgt.size()) {
|
||||
GGML_ABORT("checkpoint size mismatch: expected %zu, got %zu\n", data_tgt.size(), n);
|
||||
}
|
||||
return n == data_tgt.size();
|
||||
}
|
||||
|
||||
void common_prompt_checkpoint::load_dft(
|
||||
bool common_prompt_checkpoint::load_dft(
|
||||
llama_context * ctx,
|
||||
llama_seq_id seq_id,
|
||||
llama_state_seq_flags flags) const {
|
||||
if (ctx == nullptr) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data_dft.empty()) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
const size_t n = llama_state_seq_set_data_ext(ctx, data_dft.data(), data_dft.size(), seq_id, flags);
|
||||
if (n != data_dft.size()) {
|
||||
GGML_ABORT("checkpoint size mismatch: expected %zu, got %zu\n", data_dft.size(), n);
|
||||
}
|
||||
return n == data_dft.size();
|
||||
}
|
||||
|
||||
void common_prompt_checkpoint::clear_tgt() {
|
||||
|
||||
+4
-3
@@ -593,7 +593,7 @@ struct common_params {
|
||||
ggml_type cache_type_k = GGML_TYPE_F16; // KV cache data type for the K
|
||||
ggml_type cache_type_v = GGML_TYPE_F16; // KV cache data type for the V
|
||||
|
||||
size_t moe_cache_size = 0; // GPU cache size in bytes for the MoE experts kept in the CPU
|
||||
size_t moe_cache_size = 0; // GPU cache size in bytes for the MoE experts kept in the CPU, split among the GPUs like the layers
|
||||
|
||||
common_conversation_mode conversation_mode = COMMON_CONVERSATION_MODE_AUTO;
|
||||
|
||||
@@ -1296,12 +1296,13 @@ struct common_prompt_checkpoint {
|
||||
llama_seq_id seq_id,
|
||||
llama_state_seq_flags flags);
|
||||
|
||||
void load_tgt(
|
||||
// return false if the state could not be restored
|
||||
bool load_tgt(
|
||||
llama_context * ctx,
|
||||
llama_seq_id seq_id,
|
||||
llama_state_seq_flags flags) const;
|
||||
|
||||
void load_dft(
|
||||
bool load_dft(
|
||||
llama_context * ctx,
|
||||
llama_seq_id seq_id,
|
||||
llama_state_seq_flags flags) const;
|
||||
|
||||
+2
-2
@@ -849,8 +849,8 @@ class Gemma4DSparkModel(DFlashModel):
|
||||
raise ValueError("Gemma4 DSpark attention bias and MoE are not supported")
|
||||
if (self.hparams.get("draft_vocab_size") or self.hparams["vocab_size"]) != self.hparams["vocab_size"]:
|
||||
raise ValueError("Gemma4 DSpark currently requires a full draft vocabulary")
|
||||
if "model.lm_head.weight" not in self.model_tensors and self.hparams.get("tie_word_embeddings") is not True:
|
||||
raise ValueError("Gemma4 DSpark requires lm_head.weight unless tie_word_embeddings is true")
|
||||
if "model.lm_head.weight" not in self.model_tensors:
|
||||
raise ValueError("Gemma4 DSpark requires lm_head.weight")
|
||||
|
||||
self.dflash_config = self.hparams.get("dflash_config", {})
|
||||
markov_type = self.dflash_config.get("markov_head_type", self.hparams.get("markov_head_type", "vanilla"))
|
||||
|
||||
@@ -206,7 +206,7 @@ int main(int argc, char ** argv) {
|
||||
// reset the draft context to the checkpoint before verification
|
||||
if (ctx_dft) {
|
||||
if (use_ckpt_dft) {
|
||||
ckpt.load_dft(ctx_dft, seq_id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
GGML_ASSERT(ckpt.load_dft(ctx_dft, seq_id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY));
|
||||
}
|
||||
|
||||
llama_memory_seq_rm(llama_get_memory(ctx_dft), seq_id, ckpt.pos_max + 1, -1);
|
||||
@@ -269,13 +269,13 @@ int main(int argc, char ** argv) {
|
||||
draft = std::move(ids);
|
||||
|
||||
{
|
||||
ckpt.load_tgt(ctx_tgt, seq_id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
GGML_ASSERT(ckpt.load_tgt(ctx_tgt, seq_id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY));
|
||||
|
||||
llama_memory_seq_rm(llama_get_memory(ctx_tgt), seq_id, ckpt.pos_max + 1, -1);
|
||||
}
|
||||
|
||||
if (ctx_dft) {
|
||||
ckpt.load_dft(ctx_dft, seq_id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
GGML_ASSERT(ckpt.load_dft(ctx_dft, seq_id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY));
|
||||
|
||||
llama_memory_seq_rm(llama_get_memory(ctx_dft), seq_id, ckpt.pos_max + 1, -1);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
#ifdef GGML_CUDA_USE_CUB
|
||||
# include <cub/cub.cuh>
|
||||
# if (CCCL_MAJOR_VERSION >= 3 && CCCL_MINOR_VERSION >= 1)
|
||||
// strided_iterator was added in CCCL 3.1
|
||||
# if (CCCL_MAJOR_VERSION > 3 || (CCCL_MAJOR_VERSION == 3 && CCCL_MINOR_VERSION >= 1))
|
||||
# define STRIDED_ITERATOR_AVAILABLE
|
||||
# include <cuda/iterator>
|
||||
# endif
|
||||
@@ -27,21 +28,21 @@ static __global__ void init_offsets(int * offsets, const int ncols, const int nr
|
||||
}
|
||||
#endif // STRIDED_ITERATOR_AVAILABLE
|
||||
|
||||
#ifdef GGML_CUDA_USE_CUB
|
||||
|
||||
// returns the suggested maximum number of rows to process during one argsort_f32_i32_cuda_cub() call
|
||||
int argsort_f32_i32_cuda_cub_chunk_nrows(const size_t nb01, const int64_t nrows) {
|
||||
// perform argsort in chunks up to approximately this size (currently 64MB)
|
||||
// returns the suggested maximum number of rows to process at once, given the temporary buffer bytes per row
|
||||
int ggml_cuda_chunk_nrows(const size_t row_bytes, const int64_t nrows) {
|
||||
// process rows in chunks up to approximately this size (currently 64MB)
|
||||
// to avoid excessive temporary buffers memory usage
|
||||
const int chunk_bytes = 1 << 26;
|
||||
|
||||
// calculate how many rows will fit in one chunk (must be at least one)
|
||||
const int chunk_nrows = std::max((int) (chunk_bytes / nb01), 1);
|
||||
const int chunk_nrows = std::max((int) (chunk_bytes / row_bytes), 1);
|
||||
|
||||
// limit the resulting amount to total nrows
|
||||
return std::min((int64_t) chunk_nrows, nrows);
|
||||
}
|
||||
|
||||
#ifdef GGML_CUDA_USE_CUB
|
||||
|
||||
void argsort_f32_i32_cuda_cub(ggml_cuda_pool & pool,
|
||||
const float * x,
|
||||
int * dst,
|
||||
@@ -289,7 +290,7 @@ void ggml_cuda_op_argsort(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int chunk_nrows = argsort_f32_i32_cuda_cub_chunk_nrows(src0->nb[1], nrows);
|
||||
const int chunk_nrows = ggml_cuda_chunk_nrows(src0->nb[1], nrows);
|
||||
|
||||
ggml_cuda_pool & pool = ctx.pool();
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
void ggml_cuda_op_argsort(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
int ggml_cuda_chunk_nrows(const size_t row_bytes, const int64_t nrows);
|
||||
|
||||
#ifdef GGML_CUDA_USE_CUB
|
||||
int argsort_f32_i32_cuda_cub_chunk_nrows(const size_t nb01, const int64_t nrows);
|
||||
void argsort_f32_i32_cuda_cub(ggml_cuda_pool & pool,
|
||||
const float * x,
|
||||
int * dst,
|
||||
|
||||
@@ -200,9 +200,12 @@ static bool ggml_cuda_op_fwht_impl(ggml_backend_cuda_context & ctx, const ggml_t
|
||||
case 4096:
|
||||
ggml_cuda_kernel_launch(fwht_cuda_block<4096, nt, T>, launch_params_w, src_d, dst_d, rows, scale);
|
||||
return true;
|
||||
#if !defined(GGML_USE_MUSA)
|
||||
// 32 KB of shared memory, above the MUSA limit; falls back there
|
||||
case 8192:
|
||||
ggml_cuda_kernel_launch(fwht_cuda_block<8192, nt, T>, launch_params_w, src_d, dst_d, rows, scale);
|
||||
return true;
|
||||
#endif // !defined(GGML_USE_MUSA)
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5689,11 +5689,7 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
|
||||
case GGML_OP_SUM:
|
||||
return ggml_is_contiguous_rows(op->src[0]);
|
||||
case GGML_OP_TOP_K:
|
||||
#if defined(GGML_USE_HIP) || defined(GGML_CUDA_USE_CUB)
|
||||
return true;
|
||||
#else
|
||||
return op->src[0]->ne[0] <= 1024;
|
||||
#endif // defined(GGML_USE_HIP) || defined(GGML_CUDA_USE_CUB)
|
||||
return op->src[0]->ne[0] <= INT_MAX;
|
||||
case GGML_OP_ARGSORT:
|
||||
#ifndef GGML_CUDA_USE_CUB
|
||||
{
|
||||
@@ -5705,7 +5701,7 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
|
||||
return ncols_pad * sizeof(int) <= ggml_cuda_info().devices[dev_ctx->device].smpb;
|
||||
}
|
||||
#else
|
||||
return true;
|
||||
return op->src[0]->ne[0] <= INT_MAX;
|
||||
#endif
|
||||
case GGML_OP_SUM_ROWS:
|
||||
return op->src[0]->type == GGML_TYPE_F32 && op->type == GGML_TYPE_F32 && ggml_is_contiguous_rows(op->src[0]);
|
||||
|
||||
+54
-16
@@ -141,7 +141,10 @@ void ggml_cuda_mul_mat_q(
|
||||
GGML_TENSOR_BINARY_OP_LOCALS;
|
||||
|
||||
cudaStream_t stream = ctx.stream();
|
||||
const int cc = ggml_cuda_info().devices[ggml_cuda_get_device()].cc;
|
||||
|
||||
const int id = ggml_cuda_get_device();
|
||||
const int cc = ggml_cuda_info().devices[id].cc;
|
||||
const size_t smpbo = ggml_cuda_info().devices[id].smpbo;
|
||||
|
||||
const size_t ts_src0 = ggml_type_size(src0->type);
|
||||
const size_t ts_src1 = ggml_type_size(src1->type);
|
||||
@@ -176,7 +179,7 @@ void ggml_cuda_mul_mat_q(
|
||||
const int64_t s03 = src0->nb[3] / ts_src0;
|
||||
const int64_t s3 = dst->nb[3] / ts_dst;
|
||||
|
||||
const bool fallback = ne01 % 128 != 0;
|
||||
const bool fallback = ggml_cuda_mmq_needs_fallback(ne01);
|
||||
|
||||
const ggml_prec prec_src1 = ggml_cuda_mmq_get_prec_src1(src0, dst, cc);
|
||||
|
||||
@@ -184,9 +187,52 @@ void ggml_cuda_mul_mat_q(
|
||||
const size_t y_block_size = use_native_fp4 ? sizeof(block_fp4_mmq) : sizeof(block_q8_1_mmq);
|
||||
const size_t y_values_per_block = use_native_fp4 ? QK_FP4_MMQ : QK8_1_MMQ;
|
||||
|
||||
int J_best = 0;
|
||||
int nthreads_best = 0;
|
||||
{
|
||||
int64_t ncols_opt = ne11;
|
||||
if (ids) {
|
||||
const int64_t n_expert_used = ids->ne[0];
|
||||
ncols_opt = ne12;
|
||||
|
||||
// Each expert only sees ne12*n_expert_used/ne02 tokens on average.
|
||||
// On RDNA3 and RDNA4 it is faster to pick the tile size against this value instead of ne12.
|
||||
if (GGML_CUDA_CC_IS_RDNA3(cc) || GGML_CUDA_CC_IS_RDNA4(cc)) {
|
||||
ncols_opt = (ne12*n_expert_used + ne02 - 1) / ne02;
|
||||
}
|
||||
}
|
||||
|
||||
int ntiles_J_best = INT_MAX;
|
||||
|
||||
for (int J = 8; J <= 128 && ntiles_J_best > 1; J += 8) {
|
||||
const ggml_cuda_mmq_config config = ggml_cuda_mmq_get_config(src0->type, J, fallback, cc, prec_src1);
|
||||
if (config.type == GGML_TYPE_COUNT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mmq_get_nbytes_shared(config, cc) > smpbo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int ntiles_x = (ncols_opt + config.J - 1) / config.J;
|
||||
|
||||
if (ntiles_x < ntiles_J_best) {
|
||||
J_best = J;
|
||||
nthreads_best = config.nthreads;
|
||||
ntiles_J_best = ntiles_x;
|
||||
}
|
||||
}
|
||||
}
|
||||
GGML_ASSERT(J_best > 0);
|
||||
|
||||
// A tile of size J can read in at most J - 1 extra columns.
|
||||
// For simplicity, round up the padding of a full tile to a multiple of the number of bytes that nthreads can load in parallel.
|
||||
const size_t src1_load_chunk_size = nthreads_best * sizeof(int);
|
||||
const size_t src1_q8_1_padding = ((J_best * sizeof(block_q8_1_mmq) + src1_load_chunk_size - 1) / src1_load_chunk_size)
|
||||
* src1_load_chunk_size;
|
||||
|
||||
if (!ids) {
|
||||
const size_t nbytes_src1_q8_1 = ne13*ne12 * ne11*ne10_padded * y_block_size/y_values_per_block +
|
||||
ggml_cuda_mmq_get_J_max(src0->type, fallback, cc, ne11) * sizeof(block_q8_1_mmq);
|
||||
const size_t nbytes_src1_q8_1 = ne13*ne12 * ne11*ne10_padded * y_block_size/y_values_per_block + src1_q8_1_padding;
|
||||
ggml_cuda_pool_alloc<char> src1_q8_1(ctx.pool(), nbytes_src1_q8_1);
|
||||
ggml_cuda_pool_alloc<float> src1_scale(ctx.pool());
|
||||
if (src0->type == GGML_TYPE_NVFP4 && use_native_fp4) {
|
||||
@@ -223,7 +269,7 @@ void ggml_cuda_mul_mat_q(
|
||||
ne00, ne01, ne1, s01, ne11, s1,
|
||||
ne02, ne12, s02, s12, s2,
|
||||
ne03, ne13, s03, s13, s3,
|
||||
ne1, ne1};
|
||||
ne1, J_best};
|
||||
ggml_cuda_mul_mat_q_switch_type(ctx, args, stream, prec_src1);
|
||||
return;
|
||||
}
|
||||
@@ -237,7 +283,7 @@ void ggml_cuda_mul_mat_q(
|
||||
GGML_ASSERT(ne1 == n_expert_used);
|
||||
|
||||
ggml_cuda_pool_alloc<int32_t> ids_src1(ctx.pool(), ne_get_rows);
|
||||
ggml_cuda_pool_alloc<int32_t> ids_dst(ctx.pool(), ne_get_rows);
|
||||
ggml_cuda_pool_alloc<int32_t> ids_dst(ctx.pool(), ne_get_rows + J_best-1); // Needs to be padded for unconditional memory access.
|
||||
ggml_cuda_pool_alloc<int32_t> expert_bounds(ctx.pool(), ne02 + 1);
|
||||
|
||||
// gate/up activations are broadcast across experts (ne11 == 1): quantize each token once and
|
||||
@@ -254,8 +300,7 @@ void ggml_cuda_mul_mat_q(
|
||||
CUDA_CHECK(cudaGetLastError());
|
||||
}
|
||||
|
||||
const size_t nbytes_src1_q8_1 = ne12*n_expert_used*ne10_padded * y_block_size/y_values_per_block +
|
||||
ggml_cuda_mmq_get_J_max(src0->type, fallback, cc, ne12) * sizeof(block_q8_1_mmq);
|
||||
const size_t nbytes_src1_q8_1 = ne12*n_expert_used*ne10_padded * y_block_size/y_values_per_block + src1_q8_1_padding;
|
||||
ggml_cuda_pool_alloc<char> src1_q8_1(ctx.pool(), nbytes_src1_q8_1);
|
||||
ggml_cuda_pool_alloc<float> src1_scale(ctx.pool());
|
||||
if (src0->type == GGML_TYPE_NVFP4 && use_native_fp4) {
|
||||
@@ -296,13 +341,6 @@ void ggml_cuda_mul_mat_q(
|
||||
ne11 * ne10_padded * sizeof(block_q8_1) / (QK8_1 * sizeof(int));
|
||||
const int64_t s13 = ne12*s12;
|
||||
|
||||
// Each expert only sees ne12*n_expert_used/ne02 tokens on average.
|
||||
// On RDNA3 and RDNA4 it is faster to pick the tile size against this value instead of ne12.
|
||||
int64_t ncols_opt = ne12;
|
||||
if (GGML_CUDA_CC_IS_RDNA3(cc) || GGML_CUDA_CC_IS_RDNA4(cc)) {
|
||||
ncols_opt = (ne12*n_expert_used + ne02 - 1) / ne02;
|
||||
}
|
||||
|
||||
// Note that ne02 is used instead of ne12 because the number of y channels determines the z dimension of the CUDA grid.
|
||||
const mmq_args args = {
|
||||
src0_d, src0->type, (const int *) src1_q8_1.get(), ids_dst.get(), expert_bounds.get(), dst_d,
|
||||
@@ -310,7 +348,7 @@ void ggml_cuda_mul_mat_q(
|
||||
ne00, ne01, ne_get_rows, s01, ne_get_rows, s1,
|
||||
ne02, ne02, s02, s12, s2,
|
||||
ne03, ne13, s03, s13, s3,
|
||||
ne12, ncols_opt};
|
||||
ne12, J_best};
|
||||
|
||||
ggml_cuda_mul_mat_q_switch_type(ctx, args, stream, prec_src1);
|
||||
}
|
||||
|
||||
+11
-41
@@ -208,7 +208,7 @@ struct ggml_cuda_mmq_config {
|
||||
static_assert((nthreads_) % 32 == 0 && (nthreads_) <= 512, "bad nthreads"); \
|
||||
static_assert( (occupancy_) <= 8, "bad occupancy"); \
|
||||
static_assert((I_) % 32 == 0, "bad I"); \
|
||||
static_assert((J_) % 8 == 0, "bad J"); \
|
||||
static_assert((J_) % 8 == 0 && (J_) <= 128, "bad J"); \
|
||||
static_assert((K_vram_) % 256 == 0, "bad K_vram"); \
|
||||
return ggml_cuda_mmq_config((type_), (nthreads_), (occupancy_), (I_), (J_), (sram_layout_), (K_vram_), (stream_k_), (fallback_)); \
|
||||
} \
|
||||
@@ -295,6 +295,8 @@ static constexpr __device__ ggml_cuda_mmq_config ggml_cuda_mmq_get_config(ggml_t
|
||||
GGML_UNUSED_VARS(type, J, fallback, prec_src1);
|
||||
}
|
||||
|
||||
// FIXME all of the host functions are missing prec_src1, this can lead to inconsitent behavior.
|
||||
|
||||
static __host__ int ggml_cuda_mmq_get_type(const ggml_type type, const int J, const bool fallback, const int cc) {
|
||||
return ggml_cuda_mmq_get_config(type, J, fallback, cc).type;
|
||||
}
|
||||
@@ -369,15 +371,8 @@ static constexpr __device__ int ggml_cuda_mmq_get_sram_stride(ggml_type type, in
|
||||
return ggml_cuda_mmq_get_sram_stride(ggml_cuda_mmq_get_sram_layout(type, J, fallback, prec_src1));
|
||||
}
|
||||
|
||||
static __host__ int ggml_cuda_mmq_get_J_max(const ggml_type type, const bool fallback, const int cc, const int64_t ne11) {
|
||||
int ret = std::min(ne11, int64_t(512));
|
||||
ret -= ret % 8;
|
||||
for (;ret > 0; ret -= 8) {
|
||||
if (ggml_cuda_mmq_get_config(type, ret, fallback, cc).type != GGML_TYPE_COUNT) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
static __host__ bool ggml_cuda_mmq_needs_fallback(const int64_t nrows_x) {
|
||||
return nrows_x % 128 != 0;
|
||||
}
|
||||
|
||||
static constexpr __device__ int ggml_cuda_mmq_get_rows_per_warp(ggml_type type, int J, bool fallback) {
|
||||
@@ -1390,7 +1385,7 @@ struct mmq_args {
|
||||
int64_t nchannels_x; int64_t nchannels_y; int64_t stride_channel_x; int64_t stride_channel_y; int64_t stride_channel_dst;
|
||||
int64_t nsamples_x; int64_t nsamples_y; int64_t stride_sample_x; int64_t stride_sample_y; int64_t stride_sample_dst;
|
||||
int64_t ncols_max;
|
||||
int64_t ncols_opt; // value to optimize the tile size against, launch grid still uses ncols_max
|
||||
int J_best; // Tile width in ne11(dense)/ne12(MoE) direction to use for optimal performance.
|
||||
};
|
||||
|
||||
static size_t mmq_get_nbytes_shared(const ggml_cuda_mmq_config & config, const int cc) {
|
||||
@@ -1484,32 +1479,7 @@ static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & a
|
||||
|
||||
template <ggml_type type, bool fallback, ggml_prec prec_src1 = GGML_PREC_Q8>
|
||||
void mul_mat_q_switch_J(ggml_backend_cuda_context & ctx, const mmq_args & args, cudaStream_t stream) {
|
||||
const int id = ggml_cuda_get_device();
|
||||
const int cc = ggml_cuda_info().devices[id].cc;
|
||||
const size_t smpbo = ggml_cuda_info().devices[id].smpbo;
|
||||
|
||||
int J_best = 0;
|
||||
int ntiles_J_best = INT_MAX;
|
||||
|
||||
for (int J = 8; J <= 128 && ntiles_J_best > 1; J += 8) {
|
||||
const ggml_cuda_mmq_config config = ggml_cuda_mmq_get_config(type, J, fallback, cc, prec_src1);
|
||||
if (config.type == GGML_TYPE_COUNT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mmq_get_nbytes_shared(config, cc) > smpbo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int ntiles_x = (args.ncols_opt + config.J - 1) / config.J;
|
||||
|
||||
if (ntiles_x < ntiles_J_best) {
|
||||
J_best = J;
|
||||
ntiles_J_best = ntiles_x;
|
||||
}
|
||||
}
|
||||
|
||||
switch (J_best) {
|
||||
switch (args.J_best) {
|
||||
case 8:
|
||||
launch_mul_mat_q<type, 8, fallback, prec_src1>(ctx, args, stream);
|
||||
break;
|
||||
@@ -1559,7 +1529,7 @@ void mul_mat_q_switch_J(ggml_backend_cuda_context & ctx, const mmq_args & args,
|
||||
launch_mul_mat_q<type, 128, fallback, prec_src1>(ctx, args, stream);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "J_best=%d\n", J_best);
|
||||
fprintf(stderr, "J_best=%d\n", args.J_best);
|
||||
GGML_ABORT("fatal error");
|
||||
break;
|
||||
}
|
||||
@@ -1567,11 +1537,11 @@ void mul_mat_q_switch_J(ggml_backend_cuda_context & ctx, const mmq_args & args,
|
||||
|
||||
template <ggml_type type, ggml_prec prec_src1 = GGML_PREC_Q8>
|
||||
void mul_mat_q_case(ggml_backend_cuda_context & ctx, const mmq_args & args, cudaStream_t stream) {
|
||||
if (args.nrows_x % 128 == 0) {
|
||||
constexpr bool fallback = false;
|
||||
if (ggml_cuda_mmq_needs_fallback(args.nrows_x)) {
|
||||
constexpr bool fallback = true;
|
||||
mul_mat_q_switch_J<type, fallback, prec_src1>(ctx, args, stream);
|
||||
} else {
|
||||
constexpr bool fallback = true;
|
||||
constexpr bool fallback = false;
|
||||
mul_mat_q_switch_J<type, fallback, prec_src1>(ctx, args, stream);
|
||||
}
|
||||
}
|
||||
|
||||
+38
-35
@@ -15,49 +15,52 @@ static __global__ void pad_f32(const float * src, size_t s00, size_t s01, size_t
|
||||
// blockIdx.z: i3*ne2+i2
|
||||
// blockIdx.y: i1
|
||||
// blockIDx.x: i0 / CUDA_PAD_BLOCK_SIZE
|
||||
// gridDim.y: ne1
|
||||
// gridDim.y and gridDim.z are capped at 65535, blocks stride over larger ne1 and ne2*ne3
|
||||
int i0 = threadIdx.x + blockIdx.x * blockDim.x;
|
||||
int i1 = blockIdx.y;
|
||||
int i2 = blockIdx.z % ne2;
|
||||
int i3 = blockIdx.z / ne2;
|
||||
|
||||
if (i0 >= ne0 || i1 >= ne1 || i2 >= ne2 || i3 >= ne3) {
|
||||
if (i0 >= ne0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t dst_idx = i3 * (ne0 * ne1 * ne2) + i2 * (ne0 * ne1) + i1 * ne0 + i0;
|
||||
for (int i1 = blockIdx.y; i1 < ne1; i1 += gridDim.y) {
|
||||
for (int i23 = blockIdx.z; i23 < ne2 * ne3; i23 += gridDim.z) {
|
||||
int i2 = i23 % ne2;
|
||||
int i3 = i23 / ne2;
|
||||
|
||||
if (!circular) {
|
||||
if ((i0 >= lp0 && i0 < ne0 - rp0) && (i1 >= lp1 && i1 < ne1 - rp1) && (i2 >= lp2 && i2 < ne2 - rp2) &&
|
||||
(i3 >= lp3 && i3 < ne3 - rp3)) {
|
||||
const int64_t i00 = i0 - lp0;
|
||||
const int64_t i01 = i1 - lp1;
|
||||
const int64_t i02 = i2 - lp2;
|
||||
const int64_t i03 = i3 - lp3;
|
||||
const int64_t dst_idx = i3 * (ne0 * ne1 * ne2) + i2 * (ne0 * ne1) + i1 * ne0 + i0;
|
||||
|
||||
const int64_t src_idx = i03 * s03 + i02 * s02 + i01 * s01 + i00 * s00;
|
||||
if (!circular) {
|
||||
if ((i0 >= lp0 && i0 < ne0 - rp0) && (i1 >= lp1 && i1 < ne1 - rp1) && (i2 >= lp2 && i2 < ne2 - rp2) &&
|
||||
(i3 >= lp3 && i3 < ne3 - rp3)) {
|
||||
const int64_t i00 = i0 - lp0;
|
||||
const int64_t i01 = i1 - lp1;
|
||||
const int64_t i02 = i2 - lp2;
|
||||
const int64_t i03 = i3 - lp3;
|
||||
|
||||
dst[dst_idx] = src[src_idx];
|
||||
} else {
|
||||
dst[dst_idx] = 0.0f;
|
||||
const int64_t src_idx = i03 * s03 + i02 * s02 + i01 * s01 + i00 * s00;
|
||||
|
||||
dst[dst_idx] = src[src_idx];
|
||||
} else {
|
||||
dst[dst_idx] = 0.0f;
|
||||
}
|
||||
}
|
||||
// circular means on a torus, so x and y wrap around
|
||||
else {
|
||||
const int64_t ne00 = ne0 - lp0 - rp0;
|
||||
const int64_t ne01 = ne1 - lp1 - rp1;
|
||||
const int64_t ne02 = ne2 - lp2 - rp2;
|
||||
const int64_t ne03 = ne3 - lp3 - rp3;
|
||||
|
||||
const int64_t i00 = wrap_around(i0 - lp0, ne00);
|
||||
const int64_t i01 = wrap_around(i1 - lp1, ne01);
|
||||
const int64_t i02 = wrap_around(i2 - lp2, ne02);
|
||||
const int64_t i03 = wrap_around(i3 - lp3, ne03);
|
||||
|
||||
const int64_t src_idx = i03 * s03 + i02 * s02 + i01 * s01 + i00 * s00;
|
||||
|
||||
dst[dst_idx] = src[src_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
// circular means on a torus, so x and y wrap around
|
||||
else {
|
||||
const int64_t ne00 = ne0 - lp0 - rp0;
|
||||
const int64_t ne01 = ne1 - lp1 - rp1;
|
||||
const int64_t ne02 = ne2 - lp2 - rp2;
|
||||
const int64_t ne03 = ne3 - lp3 - rp3;
|
||||
|
||||
const int64_t i00 = wrap_around(i0 - lp0, ne00);
|
||||
const int64_t i01 = wrap_around(i1 - lp1, ne01);
|
||||
const int64_t i02 = wrap_around(i2 - lp2, ne02);
|
||||
const int64_t i03 = wrap_around(i3 - lp3, ne03);
|
||||
|
||||
const int64_t src_idx = i03 * s03 + i02 * s02 + i01 * s01 + i00 * s00;
|
||||
|
||||
dst[dst_idx] = src[src_idx];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +70,7 @@ static void pad_f32_cuda(const float * src, size_t s00, size_t s01, size_t s02,
|
||||
const int ne0, const int ne1, const int ne2, const int ne3,
|
||||
const bool circular, cudaStream_t stream) {
|
||||
int num_blocks = (ne0 + CUDA_PAD_BLOCK_SIZE - 1) / CUDA_PAD_BLOCK_SIZE;
|
||||
dim3 gridDim(num_blocks, ne1, ne2 * ne3);
|
||||
dim3 gridDim(num_blocks, std::min(ne1, 65535), std::min(ne2 * ne3, 65535));
|
||||
pad_f32<<<gridDim, CUDA_PAD_BLOCK_SIZE, 0, stream>>>(src, s00, s01, s02, s03, dst,
|
||||
lp0, rp0, lp1, rp1, lp2, rp2, lp3, rp3,
|
||||
ne0, ne1, ne2, ne3, circular);
|
||||
|
||||
+124
-66
@@ -1,6 +1,29 @@
|
||||
#include "argsort.cuh"
|
||||
#include "top-k.cuh"
|
||||
|
||||
// Adjusted implementation thresholds from #28547, can be overridden at build time
|
||||
#ifndef GGML_CUDA_TOP_K_NCOLS_THRESHOLD_BITONIC
|
||||
# if defined(GGML_USE_HIP) || defined(GGML_USE_MUSA)
|
||||
// not measured on HIP/MUSA, keep the old split
|
||||
# define GGML_CUDA_TOP_K_NCOLS_THRESHOLD_BITONIC 1024
|
||||
# else
|
||||
# define GGML_CUDA_TOP_K_NCOLS_THRESHOLD_BITONIC 512
|
||||
# endif
|
||||
#endif // GGML_CUDA_TOP_K_NCOLS_THRESHOLD_BITONIC
|
||||
|
||||
#ifndef GGML_CUDA_TOP_K_NCOLS_THRESHOLD_ARGSORT
|
||||
# define GGML_CUDA_TOP_K_NCOLS_THRESHOLD_ARGSORT 4096
|
||||
#endif // GGML_CUDA_TOP_K_NCOLS_THRESHOLD_ARGSORT
|
||||
|
||||
// bitonic up to this width while nrows fits in one wave of SMs, 0 disables
|
||||
#ifndef GGML_CUDA_TOP_K_NCOLS_THRESHOLD_BITONIC_FEW_ROWS
|
||||
# if defined(GGML_USE_HIP) || defined(GGML_USE_MUSA)
|
||||
# define GGML_CUDA_TOP_K_NCOLS_THRESHOLD_BITONIC_FEW_ROWS 0
|
||||
# else
|
||||
# define GGML_CUDA_TOP_K_NCOLS_THRESHOLD_BITONIC_FEW_ROWS 1024
|
||||
# endif
|
||||
#endif // GGML_CUDA_TOP_K_NCOLS_THRESHOLD_BITONIC_FEW_ROWS
|
||||
|
||||
#ifdef GGML_CUDA_USE_CUB
|
||||
# include <cub/cub.cuh>
|
||||
// DeviceTopK has a race condition before CCCL 3.4.3.
|
||||
@@ -14,6 +37,15 @@ using namespace cub;
|
||||
# endif // CCCL >= 3.4.3
|
||||
#endif // GGML_CUDA_USE_CUB
|
||||
|
||||
// max rows for the per-row DeviceTopK / CUB argsort path before switching to radix / bitonic
|
||||
#ifndef GGML_CUDA_TOP_K_NROWS_THRESHOLD
|
||||
# ifdef CUB_TOP_K_AVAILABLE
|
||||
# define GGML_CUDA_TOP_K_NROWS_THRESHOLD 2
|
||||
# else
|
||||
# define GGML_CUDA_TOP_K_NROWS_THRESHOLD 1
|
||||
# endif
|
||||
#endif // GGML_CUDA_TOP_K_NROWS_THRESHOLD
|
||||
|
||||
#ifdef CUB_TOP_K_AVAILABLE
|
||||
|
||||
static void top_k_cub(ggml_cuda_pool & pool,
|
||||
@@ -40,7 +72,7 @@ static void top_k_cub(ggml_cuda_pool & pool,
|
||||
ncols, k, env));
|
||||
}
|
||||
|
||||
#elif defined(GGML_CUDA_USE_CUB) // CUB_TOP_K_AVAILABLE
|
||||
#endif // CUB_TOP_K_AVAILABLE
|
||||
|
||||
static int next_power_of_2(int x) {
|
||||
int n = 1;
|
||||
@@ -50,10 +82,6 @@ static int next_power_of_2(int x) {
|
||||
return n;
|
||||
}
|
||||
|
||||
#endif // CUB_TOP_K_AVAILABLE
|
||||
|
||||
#if !defined(GGML_CUDA_USE_CUB) && defined(GGML_USE_HIP)
|
||||
|
||||
static __device__ __forceinline__ uint32_t top_k_float_to_ordered(float value) {
|
||||
const uint32_t bits = __float_as_uint(value);
|
||||
const uint32_t mask = (uint32_t) (-(int32_t) (bits >> 31)) | 0x80000000U;
|
||||
@@ -95,7 +123,7 @@ static __global__ void top_k_radix_histogram(
|
||||
__syncthreads();
|
||||
|
||||
const top_k_radix_state state = states[row];
|
||||
for (int col = row_block * BLOCK_SIZE + tid;
|
||||
for (int64_t col = row_block * BLOCK_SIZE + tid;
|
||||
col < ncols;
|
||||
col += blocks_per_row * BLOCK_SIZE) {
|
||||
const uint32_t key = top_k_float_to_ordered(row_src[col]);
|
||||
@@ -165,7 +193,7 @@ static __global__ void top_k_radix_gather(
|
||||
int * row_dst = dst + (size_t) row * k;
|
||||
top_k_radix_state * state = &states[row];
|
||||
|
||||
for (int col = row_block * BLOCK_SIZE + tid;
|
||||
for (int64_t col = row_block * BLOCK_SIZE + tid;
|
||||
col < ncols;
|
||||
col += blocks_per_row * BLOCK_SIZE) {
|
||||
const uint32_t key = top_k_float_to_ordered(row_src[col]);
|
||||
@@ -183,36 +211,72 @@ static __global__ void top_k_radix_gather(
|
||||
|
||||
static void top_k_radix_cuda(
|
||||
ggml_cuda_pool & pool,
|
||||
const float * src, int * dst, int ncols, int nrows, int k, cudaStream_t stream) {
|
||||
const float * src, int * dst, int ncols, int64_t nrows, int k, cudaStream_t stream) {
|
||||
constexpr int BLOCK_SIZE = 256;
|
||||
constexpr int RADIX_BITS = 8;
|
||||
constexpr int NBINS = 1 << RADIX_BITS;
|
||||
const int blocks_per_row = std::min((ncols + 1023) / 1024, 64);
|
||||
const int blocks_per_row = (int) std::min<int64_t>(((int64_t) ncols + 1023) / 1024, 64);
|
||||
|
||||
ggml_cuda_pool_alloc<top_k_radix_state> states_alloc(pool, nrows);
|
||||
ggml_cuda_pool_alloc<int> histograms_alloc(pool, (size_t) nrows * blocks_per_row * NBINS);
|
||||
// chunk the rows to bound the histogram memory to 64 MB
|
||||
const int64_t chunk_nrows = ggml_cuda_chunk_nrows((size_t) blocks_per_row * NBINS * sizeof(int), nrows);
|
||||
|
||||
ggml_cuda_pool_alloc<top_k_radix_state> states_alloc(pool, chunk_nrows);
|
||||
ggml_cuda_pool_alloc<int> histograms_alloc(pool, (size_t) chunk_nrows * blocks_per_row * NBINS);
|
||||
top_k_radix_state * states = states_alloc.get();
|
||||
int * histograms = histograms_alloc.get();
|
||||
|
||||
top_k_radix_init<<<(nrows + BLOCK_SIZE - 1) / BLOCK_SIZE, BLOCK_SIZE, 0, stream>>>(states, nrows, k);
|
||||
for (int64_t i = 0; i < nrows; i += chunk_nrows) {
|
||||
const int iter_nrows = std::min(chunk_nrows, nrows - i);
|
||||
|
||||
const dim3 row_grid(blocks_per_row * nrows);
|
||||
for (int shift = 32 - RADIX_BITS; shift >= 0; shift -= RADIX_BITS) {
|
||||
top_k_radix_histogram<BLOCK_SIZE, RADIX_BITS>
|
||||
top_k_radix_init<<<(iter_nrows + BLOCK_SIZE - 1) / BLOCK_SIZE, BLOCK_SIZE, 0, stream>>>(states, iter_nrows, k);
|
||||
|
||||
const dim3 row_grid(blocks_per_row * iter_nrows);
|
||||
for (int shift = 32 - RADIX_BITS; shift >= 0; shift -= RADIX_BITS) {
|
||||
top_k_radix_histogram<BLOCK_SIZE, RADIX_BITS>
|
||||
<<<row_grid, BLOCK_SIZE, 0, stream>>>(
|
||||
src, states, histograms, ncols, blocks_per_row, shift);
|
||||
top_k_radix_select<BLOCK_SIZE, RADIX_BITS>
|
||||
<<<iter_nrows, BLOCK_SIZE, 0, stream>>>(histograms, states, blocks_per_row, shift);
|
||||
}
|
||||
|
||||
top_k_radix_reset_counters
|
||||
<<<(iter_nrows + BLOCK_SIZE - 1) / BLOCK_SIZE, BLOCK_SIZE, 0, stream>>>(states, iter_nrows);
|
||||
top_k_radix_gather<BLOCK_SIZE>
|
||||
<<<row_grid, BLOCK_SIZE, 0, stream>>>(
|
||||
src, states, histograms, ncols, blocks_per_row, shift);
|
||||
top_k_radix_select<BLOCK_SIZE, RADIX_BITS>
|
||||
<<<nrows, BLOCK_SIZE, 0, stream>>>(histograms, states, blocks_per_row, shift);
|
||||
}
|
||||
src, dst, states, ncols, k, blocks_per_row);
|
||||
|
||||
top_k_radix_reset_counters
|
||||
<<<(nrows + BLOCK_SIZE - 1) / BLOCK_SIZE, BLOCK_SIZE, 0, stream>>>(states, nrows);
|
||||
top_k_radix_gather<BLOCK_SIZE>
|
||||
<<<row_grid, BLOCK_SIZE, 0, stream>>>(
|
||||
src, dst, states, ncols, k, blocks_per_row);
|
||||
src += (size_t) ncols * iter_nrows;
|
||||
dst += (size_t) k * iter_nrows;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !defined(GGML_CUDA_USE_CUB) && defined(GGML_USE_HIP)
|
||||
static void top_k_argsort_cuda(
|
||||
ggml_cuda_pool & pool,
|
||||
const float * src, int * dst, int ncols, int64_t nrows, int k, bool use_cub, cudaStream_t stream) {
|
||||
const int64_t chunk_nrows = ggml_cuda_chunk_nrows((size_t) ncols * sizeof(int), nrows);
|
||||
|
||||
ggml_cuda_pool_alloc<int> tmp_alloc(pool, (size_t) ncols * chunk_nrows);
|
||||
int * tmp = tmp_alloc.get();
|
||||
|
||||
for (int64_t i = 0; i < nrows; i += chunk_nrows) {
|
||||
const int iter_nrows = std::min(chunk_nrows, nrows - i);
|
||||
|
||||
if (use_cub) {
|
||||
#ifdef GGML_CUDA_USE_CUB
|
||||
argsort_f32_i32_cuda_cub(pool, src, tmp, ncols, iter_nrows, GGML_SORT_ORDER_DESC, stream);
|
||||
#else
|
||||
GGML_ABORT("CUB is not available");
|
||||
#endif // GGML_CUDA_USE_CUB
|
||||
} else {
|
||||
argsort_f32_i32_cuda_bitonic(src, tmp, ncols, iter_nrows, GGML_SORT_ORDER_DESC, stream);
|
||||
}
|
||||
CUDA_CHECK(cudaMemcpy2DAsync(dst, k * sizeof(int), tmp, ncols * sizeof(int), k * sizeof(int), iter_nrows,
|
||||
cudaMemcpyDeviceToDevice, stream));
|
||||
|
||||
src += (size_t) ncols * iter_nrows;
|
||||
dst += (size_t) k * iter_nrows;
|
||||
}
|
||||
}
|
||||
|
||||
void ggml_cuda_op_top_k(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
@@ -229,51 +293,45 @@ void ggml_cuda_op_top_k(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
const int64_t nrows = ggml_nrows(src0);
|
||||
const int64_t k = dst->ne[0];
|
||||
ggml_cuda_pool & pool = ctx.pool();
|
||||
|
||||
const int device = ggml_cuda_get_device();
|
||||
|
||||
#ifdef CUB_TOP_K_AVAILABLE
|
||||
// TODO: Switch to `DeviceSegmentedTopK` for multi-row TopK once implemented
|
||||
// https://github.com/NVIDIA/cccl/issues/6391
|
||||
// TODO: investigate if there exists a point where parallelized argsort is faster than sequential top-k
|
||||
for (int i = 0; i < nrows; i++) {
|
||||
// a single row always uses DeviceTopK if available
|
||||
const bool bitonic_short = nrows > 1 && ncols <= GGML_CUDA_TOP_K_NCOLS_THRESHOLD_BITONIC;
|
||||
#else
|
||||
const bool bitonic_short = ncols <= GGML_CUDA_TOP_K_NCOLS_THRESHOLD_BITONIC;
|
||||
#endif // CUB_TOP_K_AVAILABLE
|
||||
const bool bitonic_few_rows = nrows > GGML_CUDA_TOP_K_NROWS_THRESHOLD &&
|
||||
ncols <= GGML_CUDA_TOP_K_NCOLS_THRESHOLD_BITONIC_FEW_ROWS &&
|
||||
nrows <= ggml_cuda_info().devices[device].nsm;
|
||||
|
||||
if (bitonic_short || bitonic_few_rows) {
|
||||
// the padded row must fit in shared memory
|
||||
const int ncols_pad = next_power_of_2(ncols);
|
||||
if (ncols_pad * sizeof(int) <= ggml_cuda_info().devices[device].smpb) {
|
||||
top_k_argsort_cuda(pool, src0_d, dst_d, ncols, nrows, k, false, stream);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (nrows > GGML_CUDA_TOP_K_NROWS_THRESHOLD) {
|
||||
top_k_radix_cuda(pool, src0_d, dst_d, ncols, nrows, k, stream);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef CUB_TOP_K_AVAILABLE
|
||||
// TODO: Assess perf of `DeviceBatchedTopK` for multi-row TopK & CCCL >= 3.5.0, re-running perf sweep of https://github.com/ggml-org/llama.cpp/pull/28713
|
||||
for (int64_t i = 0; i < nrows; i++) {
|
||||
top_k_cub(pool, src0_d + i * ncols, dst_d + i * k, ncols, k, stream);
|
||||
}
|
||||
#elif defined(GGML_CUDA_USE_CUB) // CUB_TOP_K_AVAILABLE
|
||||
// Fall back to argsort + copy
|
||||
const int ncols_pad = next_power_of_2(ncols);
|
||||
const size_t shared_mem = ncols_pad * sizeof(int);
|
||||
const size_t max_shared_mem = ggml_cuda_info().devices[ggml_cuda_get_device()].smpb;
|
||||
const bool use_bitonic = shared_mem <= max_shared_mem && ncols <= 1024;
|
||||
const int chunk_nrows = argsort_f32_i32_cuda_cub_chunk_nrows(src0->nb[1], nrows);
|
||||
|
||||
ggml_cuda_pool_alloc<int> temp_dst_alloc(pool, ncols * chunk_nrows);
|
||||
int * tmp_dst = temp_dst_alloc.get();
|
||||
|
||||
for (int64_t i = 0; i < nrows; i += chunk_nrows) {
|
||||
int iter_nrows = std::min((int64_t) chunk_nrows, nrows - i);
|
||||
|
||||
if (use_bitonic) {
|
||||
argsort_f32_i32_cuda_bitonic(src0_d, tmp_dst, ncols, iter_nrows, GGML_SORT_ORDER_DESC, stream);
|
||||
} else {
|
||||
argsort_f32_i32_cuda_cub(pool, src0_d, tmp_dst, ncols, iter_nrows, GGML_SORT_ORDER_DESC, stream);
|
||||
}
|
||||
CUDA_CHECK(cudaMemcpy2DAsync(dst_d, k * sizeof(int), tmp_dst, ncols * sizeof(int), k * sizeof(int), iter_nrows,
|
||||
cudaMemcpyDeviceToDevice, stream));
|
||||
|
||||
src0_d += ncols * iter_nrows;
|
||||
dst_d += k * iter_nrows;
|
||||
if (ncols <= GGML_CUDA_TOP_K_NCOLS_THRESHOLD_ARGSORT) {
|
||||
top_k_argsort_cuda(pool, src0_d, dst_d, ncols, nrows, k, true, stream);
|
||||
} else {
|
||||
top_k_radix_cuda(pool, src0_d, dst_d, ncols, nrows, k, stream);
|
||||
}
|
||||
#else // GGML_CUDA_USE_CUB
|
||||
#if defined(GGML_USE_HIP)
|
||||
if (ncols > 1024) {
|
||||
top_k_radix_cuda(pool, src0_d, dst_d, ncols, nrows, k, stream);
|
||||
} else {
|
||||
#endif // defined(GGML_USE_HIP)
|
||||
ggml_cuda_pool_alloc<int> temp_dst_alloc(pool, ncols * nrows);
|
||||
int * tmp_dst = temp_dst_alloc.get();
|
||||
argsort_f32_i32_cuda_bitonic(src0_d, tmp_dst, ncols, nrows, GGML_SORT_ORDER_DESC, stream);
|
||||
CUDA_CHECK(cudaMemcpy2DAsync(dst_d, k * sizeof(int), tmp_dst, ncols * sizeof(int), k * sizeof(int), nrows,
|
||||
cudaMemcpyDeviceToDevice, stream));
|
||||
#if defined(GGML_USE_HIP)
|
||||
}
|
||||
#endif // defined(GGML_USE_HIP)
|
||||
#endif
|
||||
top_k_radix_cuda(pool, src0_d, dst_d, ncols, nrows, k, stream);
|
||||
#endif // CUB_TOP_K_AVAILABLE
|
||||
}
|
||||
|
||||
@@ -5102,12 +5102,19 @@ static bool ggml_sycl_mul_mat_glu_mmvq_fused(ggml_backend_sycl_context & ctx, gg
|
||||
return false;
|
||||
}
|
||||
|
||||
// quant pairs the reorder kernel cannot serve (mixed gate/up types) take the
|
||||
// standard-layout fused path instead; q4_K keeps the reorder path below
|
||||
if (wg->type != GGML_TYPE_Q4_K || wu->type != GGML_TYPE_Q4_K) {
|
||||
// quant pairs the reorder kernel does not serve (mixed gate/up types, q5_K off BMG) take the
|
||||
// standard-layout fused path instead; same-type q4_K / q5_K keep the reorder path below
|
||||
const bool reorder_pair = wg->type == wu->type &&
|
||||
(wu->type == GGML_TYPE_Q4_K || (wu->type == GGML_TYPE_Q5_K && ggml_sycl_q5_k_mmvq_reuse(ctx.device)));
|
||||
if (!reorder_pair) {
|
||||
return ggml_sycl_mul_mat_glu_mmvq_plain(ctx, glu, gate, up, wu, wg, act);
|
||||
}
|
||||
|
||||
// past 5 columns the two unfused q5_K GEMVs are faster than the fused kernel
|
||||
if (wu->type == GGML_TYPE_Q5_K && act->ne[1] > 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// install the reorder (SoA) layout the fused kernel needs, as the unfused mmvq path would;
|
||||
// a no-op once done. after the bail checks so a declined op does not pay for it.
|
||||
opt_for_reorder(&ctx, wu, act, up, mul_mat_algo::MMVQ);
|
||||
|
||||
+60
-48
@@ -110,7 +110,8 @@ static void mul_mat_vec_q_reorder(const void * __restrict__ vx, const void * __r
|
||||
|
||||
// With has_fusion, `vgate` is a second weight matrix sharing vx's shape, stride and reorder
|
||||
// layout: one pass computes both row dot products and the epilogue writes glu(gate, up).
|
||||
template <typename reorder_vec_dot_q_sycl, int ncols_dst, bool has_fusion = false, int rows_per_sg = 1>
|
||||
template <typename reorder_vec_dot_q_sycl, int ncols_dst, bool has_fusion = false, int rows_per_sg = 1,
|
||||
bool shared_weights = reorder_vec_dot_shared_weights<reorder_vec_dot_q_sycl::gtype>::value>
|
||||
static void mul_mat_vec_q_reorder_ncols(const void * __restrict__ vx, const void * __restrict__ vgate,
|
||||
const void * __restrict__ vy, float * __restrict__ dst, const int ncols,
|
||||
const int nrows, const int stride_col_y_bytes, const int stride_col_dst,
|
||||
@@ -181,7 +182,7 @@ static void mul_mat_vec_q_reorder_ncols(const void * __restrict__ vx, const void
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if constexpr (reorder_vec_dot_shared_weights<reorder_vec_dot_q_sycl::gtype>::value) {
|
||||
} else if constexpr (shared_weights) {
|
||||
const int ibx = row0 * blocks_per_row + i;
|
||||
const auto bx_offset = block_type::get_block_offset(ibx, nblocks);
|
||||
const auto d_offset = block_type::get_d_offset(nrows, ncols, ibx);
|
||||
@@ -1945,8 +1946,8 @@ static void reorder_mul_mat_vec_q5_k_q8_1_sycl(const void * vx, const void * vy,
|
||||
});
|
||||
}
|
||||
|
||||
template <int ncols_dst>
|
||||
static void reorder_mul_mat_vec_q5_k_q8_1_sycl_ncols(
|
||||
template <int ncols_dst, int rows_per_sg, bool shared_weights>
|
||||
static void reorder_mul_mat_vec_q5_k_q8_1_sycl_ncols_impl(
|
||||
const void * vx, const void * vy, float * dst,
|
||||
const int ncols, const int nrows,
|
||||
const int stride_col_y_bytes, const int stride_col_dst,
|
||||
@@ -1954,20 +1955,35 @@ static void reorder_mul_mat_vec_q5_k_q8_1_sycl_ncols(
|
||||
GGML_ASSERT(ncols % QK_K == 0);
|
||||
|
||||
constexpr size_t num_subgroups = WARP_SIZE;
|
||||
const int block_num_y = ceil_div(nrows, GGML_SYCL_MMV_Y * (int) num_subgroups);
|
||||
const int block_num_y = ceil_div(nrows, GGML_SYCL_MMV_Y * (int) num_subgroups * rows_per_sg);
|
||||
const sycl::range<3> block_nums(1, 1, block_num_y);
|
||||
const sycl::range<3> block_dims(1, GGML_SYCL_MMV_Y, num_subgroups * WARP_SIZE);
|
||||
|
||||
stream->submit([&](sycl::handler & cgh) {
|
||||
cgh.parallel_for(sycl::nd_range<3>(block_nums * block_dims, block_dims),
|
||||
[=](sycl::nd_item<3> nd_item) [[sycl::reqd_sub_group_size(WARP_SIZE)]] {
|
||||
mul_mat_vec_q_reorder_ncols<reorder_vec_dot_q_sycl<GGML_TYPE_Q5_K>, ncols_dst>(
|
||||
mul_mat_vec_q_reorder_ncols<reorder_vec_dot_q_sycl<GGML_TYPE_Q5_K>, ncols_dst,
|
||||
/*has_fusion=*/ false, rows_per_sg, shared_weights>(
|
||||
vx, /*vgate=*/ nullptr, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst,
|
||||
/*glu_op=*/ GGML_GLU_OP_SWIGLU, nd_item);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <int ncols_dst>
|
||||
static void reorder_mul_mat_vec_q5_k_q8_1_sycl_ncols(
|
||||
const void * vx, const void * vy, float * dst,
|
||||
const int ncols, const int nrows,
|
||||
const int stride_col_y_bytes, const int stride_col_dst,
|
||||
dpct::queue_ptr stream) {
|
||||
if (ggml_sycl_q5_k_mmvq_reuse(ggml_sycl_get_device())) {
|
||||
constexpr int rows_per_sg = ncols_dst >= 3 ? 2 : 1;
|
||||
reorder_mul_mat_vec_q5_k_q8_1_sycl_ncols_impl<ncols_dst, rows_per_sg, true>(vx, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, stream);
|
||||
} else {
|
||||
reorder_mul_mat_vec_q5_k_q8_1_sycl_ncols_impl<ncols_dst, 1, false>(vx, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, stream);
|
||||
}
|
||||
}
|
||||
|
||||
static void reorder_mul_mat_vec_q5_k_q8_1_sycl_switch_ncols(
|
||||
const void * vx, const void * vy, float * dst,
|
||||
const int ncols, const int nrows, const int ncols_dst,
|
||||
@@ -3129,8 +3145,11 @@ static void launch_mul_mat_vec_q_reorder_glu(const void * vx, const void * vgate
|
||||
const int ncols, const int nrows, const int stride_col_y_bytes,
|
||||
const int stride_col_dst, const ggml_glu_op glu_op,
|
||||
dpct::queue_ptr stream) {
|
||||
// q4_K pairs rows for 3..4 columns, q5_K for 3..5
|
||||
constexpr int row_pair_max = reorder_vec_dot_q_sycl::gtype == GGML_TYPE_Q5_K ? 5 : 4;
|
||||
constexpr int rows_per_sg =
|
||||
reorder_vec_dot_shared_activations<reorder_vec_dot_q_sycl::gtype>::value && ncols_dst >= 3 && ncols_dst <= 4
|
||||
reorder_vec_dot_shared_activations<reorder_vec_dot_q_sycl::gtype>::value && ncols_dst >= 3 &&
|
||||
ncols_dst <= row_pair_max
|
||||
? 2
|
||||
: 1;
|
||||
launch_mul_mat_vec_q_reorder_glu_impl<reorder_vec_dot_q_sycl, ncols_dst, rows_per_sg>(vx, vgate, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, glu_op, stream);
|
||||
@@ -3321,55 +3340,48 @@ bool ggml_sycl_mul_mat_vec_q_glu_plain(enum ggml_type gate_type, enum ggml_type
|
||||
return false;
|
||||
}
|
||||
|
||||
template <ggml_type type, int... Ns>
|
||||
static bool mul_mat_vec_q_glu_reorder_ncols(enum ggml_glu_op glu_op, const void * vx, const void * vgate,
|
||||
const void * vy, float * dst, int ncols, int nrows, int ncols_dst,
|
||||
int stride_col_y_bytes, int stride_col_dst, dpct::queue_ptr stream) {
|
||||
using vec_dot = reorder_vec_dot_q_sycl<type>;
|
||||
|
||||
auto launch = [&](auto I) -> bool {
|
||||
constexpr int n = decltype(I)::value;
|
||||
if (ncols_dst != n) {
|
||||
return false;
|
||||
}
|
||||
if constexpr (type == GGML_TYPE_Q4_K && n == 2) {
|
||||
if (nrows >= Q4_K_MMVQ_ROW_PAIR_MIN_NROWS) {
|
||||
launch_mul_mat_vec_q_reorder_glu_impl<vec_dot, 2, 2>(vx, vgate, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, glu_op, stream);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
launch_mul_mat_vec_q_reorder_glu<vec_dot, n>(vx, vgate, vy, dst, ncols, nrows, stride_col_y_bytes,
|
||||
stride_col_dst, glu_op, stream);
|
||||
return true;
|
||||
};
|
||||
|
||||
// unary fold over launch
|
||||
return (launch(std::integral_constant<int, Ns>{}) || ...);
|
||||
}
|
||||
|
||||
bool ggml_sycl_mul_mat_vec_q_glu_reorder(enum ggml_type src0_type, enum ggml_glu_op glu_op, const void * vx,
|
||||
const void * vgate, const void * vy, float * dst, int ncols, int nrows,
|
||||
int ncols_dst, int stride_col_y_bytes, int stride_col_dst,
|
||||
dpct::queue_ptr stream) {
|
||||
if (src0_type != GGML_TYPE_Q4_K) {
|
||||
return false;
|
||||
}
|
||||
if (glu_op != GGML_GLU_OP_SWIGLU && glu_op != GGML_GLU_OP_GEGLU) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using vec_dot = reorder_vec_dot_q_sycl<GGML_TYPE_Q4_K>;
|
||||
|
||||
switch (ncols_dst) {
|
||||
case 1:
|
||||
launch_mul_mat_vec_q_reorder_glu<vec_dot, 1>(vx, vgate, vy, dst, ncols, nrows, stride_col_y_bytes,
|
||||
stride_col_dst, glu_op, stream);
|
||||
return true;
|
||||
case 2:
|
||||
if (nrows >= Q4_K_MMVQ_ROW_PAIR_MIN_NROWS) {
|
||||
launch_mul_mat_vec_q_reorder_glu_impl<vec_dot, 2, 2>(vx, vgate, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, glu_op, stream);
|
||||
} else {
|
||||
launch_mul_mat_vec_q_reorder_glu_impl<vec_dot, 2, 1>(vx, vgate, vy, dst, ncols, nrows, stride_col_y_bytes, stride_col_dst, glu_op, stream);
|
||||
}
|
||||
return true;
|
||||
case 3:
|
||||
launch_mul_mat_vec_q_reorder_glu<vec_dot, 3>(vx, vgate, vy, dst, ncols, nrows, stride_col_y_bytes,
|
||||
stride_col_dst, glu_op, stream);
|
||||
return true;
|
||||
case 4:
|
||||
launch_mul_mat_vec_q_reorder_glu<vec_dot, 4>(vx, vgate, vy, dst, ncols, nrows, stride_col_y_bytes,
|
||||
stride_col_dst, glu_op, stream);
|
||||
return true;
|
||||
case 5:
|
||||
launch_mul_mat_vec_q_reorder_glu<vec_dot, 5>(vx, vgate, vy, dst, ncols, nrows, stride_col_y_bytes,
|
||||
stride_col_dst, glu_op, stream);
|
||||
return true;
|
||||
case 6:
|
||||
launch_mul_mat_vec_q_reorder_glu<vec_dot, 6>(vx, vgate, vy, dst, ncols, nrows, stride_col_y_bytes,
|
||||
stride_col_dst, glu_op, stream);
|
||||
return true;
|
||||
case 7:
|
||||
launch_mul_mat_vec_q_reorder_glu<vec_dot, 7>(vx, vgate, vy, dst, ncols, nrows, stride_col_y_bytes,
|
||||
stride_col_dst, glu_op, stream);
|
||||
return true;
|
||||
case 8:
|
||||
launch_mul_mat_vec_q_reorder_glu<vec_dot, 8>(vx, vgate, vy, dst, ncols, nrows, stride_col_y_bytes,
|
||||
stride_col_dst, glu_op, stream);
|
||||
return true;
|
||||
switch (src0_type) {
|
||||
case GGML_TYPE_Q4_K:
|
||||
return mul_mat_vec_q_glu_reorder_ncols<GGML_TYPE_Q4_K, 1, 2, 3, 4, 5, 6, 7, 8>(
|
||||
glu_op, vx, vgate, vy, dst, ncols, nrows, ncols_dst, stride_col_y_bytes, stride_col_dst, stream);
|
||||
case GGML_TYPE_Q5_K:
|
||||
// fusion declines q5_K past 5 columns
|
||||
return mul_mat_vec_q_glu_reorder_ncols<GGML_TYPE_Q5_K, 1, 2, 3, 4, 5>(
|
||||
glu_op, vx, vgate, vy, dst, ncols, nrows, ncols_dst, stride_col_y_bytes, stride_col_dst, stream);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
// q5_K multi-column MMVQ shares weights across columns, pairs rows and fuses gate/up in the reorder
|
||||
// layout: faster on Xe2 (BMG), so untested archs keep the per-column kernel
|
||||
inline bool ggml_sycl_q5_k_mmvq_reuse(int device) {
|
||||
const gpu_arch arch = ggml_sycl_info().devices[device].hw_info.arch;
|
||||
return arch == gpu_arch::intel_gpu_bmg_g21 || arch == gpu_arch::intel_gpu_bmg_g31;
|
||||
}
|
||||
|
||||
void ggml_sycl_op_mul_mat_vec_q(
|
||||
ggml_backend_sycl_context & ctx,
|
||||
|
||||
@@ -362,6 +362,10 @@ template <> struct reorder_vec_dot_shared_weights<GGML_TYPE_Q4_K> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
|
||||
template <> struct reorder_vec_dot_shared_weights<GGML_TYPE_Q5_K> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
|
||||
template <ggml_type T> struct reorder_vec_dot_shared_activations {
|
||||
static constexpr bool value = false;
|
||||
};
|
||||
@@ -370,6 +374,10 @@ template <> struct reorder_vec_dot_shared_activations<GGML_TYPE_Q4_K> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
|
||||
template <> struct reorder_vec_dot_shared_activations<GGML_TYPE_Q5_K> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
|
||||
template <> struct reorder_vec_dot_q_sycl<GGML_TYPE_Q4_0> {
|
||||
static constexpr ggml_type gtype = GGML_TYPE_Q4_0;
|
||||
|
||||
@@ -676,56 +684,72 @@ template <> struct reorder_vec_dot_q_sycl<GGML_TYPE_Q5_K> {
|
||||
using q5_k_block = ggml_sycl_reordered::block_q_t<GGML_TYPE_Q5_K>;
|
||||
using q5_k_traits = typename q5_k_block::traits;
|
||||
|
||||
__dpct_inline__ float operator()(const void * __restrict__ vbq, const std::pair<int, int> ibx_offset,
|
||||
const std::pair<int, int> d_offset, const int8_t * q8_1_quant_ptr,
|
||||
const sycl::half2 * q8_1_ds, const int & iqs) {
|
||||
const uint8_t * base = static_cast<const uint8_t *>(vbq);
|
||||
const uint8_t * qs = base + ibx_offset.first; // low 4 bits
|
||||
const uint8_t * qh_base = base + ibx_offset.second; // high bit
|
||||
const uint8_t * scs = base + d_offset.first;
|
||||
const ggml_half2 * dms = reinterpret_cast<const ggml_half2 *>(base + d_offset.second);
|
||||
struct weights {
|
||||
int vl[2];
|
||||
int vh[2];
|
||||
uint16_t aux[2];
|
||||
ggml_half2 dm;
|
||||
};
|
||||
|
||||
// same activation layout as Q4_K
|
||||
static_assert(QR5_K == QR4_K);
|
||||
using activations = reorder_vec_dot_q_sycl<GGML_TYPE_Q4_K>::activations;
|
||||
|
||||
__dpct_inline__ static weights load(const void * __restrict__ vbq, const std::pair<int, int> ibx_offset,
|
||||
const std::pair<int, int> d_offset, const int & iqs) {
|
||||
const uint8_t * base = static_cast<const uint8_t *>(vbq);
|
||||
const uint8_t * qs = base + ibx_offset.first; // low 4 bits
|
||||
const uint8_t * qh_base = base + ibx_offset.second; // high bit
|
||||
const uint8_t * scs = base + d_offset.first;
|
||||
const ggml_half2 * dms = reinterpret_cast<const ggml_half2 *>(base + d_offset.second);
|
||||
|
||||
const int bq8_offset = QR5_K * ((iqs / 2) / (QI8_1 / 2));
|
||||
const int * ql_ptr = (const int *) (qs + 16 * bq8_offset + 4 * ((iqs / 2) % 4));
|
||||
const int * qh_ptr = (const int *) (qh_base + 4 * ((iqs / 2) % 4));
|
||||
const uint16_t * scales = (const uint16_t *) scs;
|
||||
|
||||
int vl[2];
|
||||
int vh[2];
|
||||
int u[2 * QR5_K];
|
||||
float d8[QR5_K];
|
||||
weights w;
|
||||
w.vl[0] = ql_ptr[0];
|
||||
w.vl[1] = ql_ptr[4];
|
||||
|
||||
vl[0] = ql_ptr[0];
|
||||
vl[1] = ql_ptr[4];
|
||||
w.vh[0] = qh_ptr[0] >> bq8_offset;
|
||||
w.vh[1] = qh_ptr[4] >> bq8_offset;
|
||||
|
||||
vh[0] = qh_ptr[0] >> bq8_offset;
|
||||
vh[1] = qh_ptr[4] >> bq8_offset;
|
||||
|
||||
uint16_t aux[2];
|
||||
const int j = (QR5_K * ((iqs / 2) / (QI8_1 / 2))) / 2;
|
||||
if (j < 2) {
|
||||
aux[0] = scales[j + 0] & 0x3f3f;
|
||||
aux[1] = scales[j + 2] & 0x3f3f;
|
||||
w.aux[0] = scales[j + 0] & 0x3f3f;
|
||||
w.aux[1] = scales[j + 2] & 0x3f3f;
|
||||
} else {
|
||||
aux[0] = ((scales[j + 2] >> 0) & 0x0f0f) | ((scales[j - 2] & 0xc0c0) >> 2);
|
||||
aux[1] = ((scales[j + 2] >> 4) & 0x0f0f) | ((scales[j - 0] & 0xc0c0) >> 2);
|
||||
w.aux[0] = ((scales[j + 2] >> 0) & 0x0f0f) | ((scales[j - 2] & 0xc0c0) >> 2);
|
||||
w.aux[1] = ((scales[j + 2] >> 4) & 0x0f0f) | ((scales[j - 0] & 0xc0c0) >> 2);
|
||||
}
|
||||
|
||||
const uint8_t * sc = (const uint8_t *) aux;
|
||||
w.dm = *dms;
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
__dpct_inline__ static activations load_activations(const int8_t * q8_1_quant_ptr,
|
||||
const sycl::half2 * q8_1_ds, const int & iqs) {
|
||||
return reorder_vec_dot_q_sycl<GGML_TYPE_Q4_K>::load_activations(q8_1_quant_ptr, q8_1_ds, iqs);
|
||||
}
|
||||
|
||||
__dpct_inline__ static float apply(const weights & w, const activations & a) {
|
||||
const uint8_t * sc = (const uint8_t *) w.aux;
|
||||
const uint8_t * m = sc + 2;
|
||||
|
||||
for (int i = 0; i < QR5_K; ++i) {
|
||||
const int8_t* quant_base_ptr = q8_1_quant_ptr + (bq8_offset + i) * QK8_1;
|
||||
sycl::half2 ds_values = *(q8_1_ds + bq8_offset + i);
|
||||
return vec_dot_q5_K_q8_1_impl_vmmq(w.vl, w.vh, a.u, sc, m, w.dm, a.d8);
|
||||
}
|
||||
|
||||
d8[i] = ds_values[0];
|
||||
__dpct_inline__ static float dot(const weights & w, const int8_t * q8_1_quant_ptr,
|
||||
const sycl::half2 * q8_1_ds, const int & iqs) {
|
||||
return apply(w, load_activations(q8_1_quant_ptr, q8_1_ds, iqs));
|
||||
}
|
||||
|
||||
const int * q8 = (const int *) quant_base_ptr + ((iqs / 2) % 4);
|
||||
u[2 * i + 0] = q8[0];
|
||||
u[2 * i + 1] = q8[4];
|
||||
}
|
||||
|
||||
return vec_dot_q5_K_q8_1_impl_vmmq(vl, vh, u, sc, m, *dms, d8);
|
||||
__dpct_inline__ float operator()(const void * __restrict__ vbq, const std::pair<int, int> ibx_offset,
|
||||
const std::pair<int, int> d_offset, const int8_t * q8_1_quant_ptr,
|
||||
const sycl::half2 * q8_1_ds, const int & iqs) {
|
||||
return dot(load(vbq, ibx_offset, d_offset, iqs), q8_1_quant_ptr, q8_1_ds, iqs);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8154,10 +8154,14 @@ void ggml_vk_flash_attn(ggml_backend_vk_context * ctx, vk_context& subctx, const
|
||||
// cm2 dense is fast, so it needs a larger reduction to win.
|
||||
// With quantized K/V, sparse only breaks even around 16x (measured on RDNA3/RDNA4).
|
||||
const int64_t min_ratio = tuning_params.path == FA_COOPMAT2 ? 4 : (kv_f16 ? 2 : 16);
|
||||
// coopmat2 vector decode requires 8B strides.
|
||||
auto sparse_gather_aligned = [](const ggml_tensor * t) {
|
||||
return (t->type != GGML_TYPE_F16 && t->type != GGML_TYPE_BF16) ||
|
||||
(t->nb[1] | t->nb[2] | t->nb[3]) % (4 * sizeof(ggml_fp16_t)) == 0;
|
||||
};
|
||||
const bool use_sparse = !disable_sparse && n_kv_max > 0 && mask &&
|
||||
max_bias == 0.0f && logit_softcap == 0.0f &&
|
||||
// the cm2 sparse gather only reads f16
|
||||
(kv_f16 || tuning_params.path != FA_COOPMAT2) &&
|
||||
(tuning_params.path != FA_COOPMAT2 || (sparse_gather_aligned(k) && sparse_gather_aligned(v))) &&
|
||||
nem0 == KV &&
|
||||
(int64_t)KV >= std::max<int64_t>(4096, min_ratio * (int64_t)n_kv_max) &&
|
||||
(gqa_ratio > 1 || (tuning_params.path == FA_SCALAR && N == 1));
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
#ifdef GL_NV_cooperative_matrix_decode_vector
|
||||
#extension GL_NV_cooperative_matrix_decode_vector : enable
|
||||
#endif
|
||||
#extension GL_EXT_buffer_reference : enable
|
||||
#extension GL_EXT_buffer_reference2 : enable
|
||||
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : enable
|
||||
#extension GL_KHR_shader_subgroup_ballot : enable
|
||||
#extension GL_KHR_shader_subgroup_vote : enable
|
||||
#extension GL_EXT_null_initializer : enable
|
||||
@@ -35,6 +36,10 @@
|
||||
#define FA_GATHER_BS 1u
|
||||
#endif
|
||||
|
||||
layout(buffer_reference, std430, buffer_reference_align = 1) buffer decodeBufFA_Byte {
|
||||
uint8_t raw;
|
||||
};
|
||||
|
||||
// buffer_reference stride = sizeof(struct) = FaBlockBytesK/V.
|
||||
layout(buffer_reference, std430, buffer_reference_align = 1) buffer decodeBufFA_K {
|
||||
uint8_t raw[FaBlockBytesK];
|
||||
@@ -113,48 +118,71 @@ layout (binding = 1) readonly buffer K {uint8_t data_k[];};
|
||||
layout (binding = 2) readonly buffer V {uint8_t data_v[];};
|
||||
layout (binding = 3) readonly buffer M {uint8_t data_m[];};
|
||||
|
||||
// f16 aliases for the sparse gather callbacks.
|
||||
layout (binding = 1) readonly buffer KF16 {float16_t data_kf16[];};
|
||||
layout (binding = 2) readonly buffer VF16 {float16_t data_vf16[];};
|
||||
// Native 16-bit aliases for the sparse gather callbacks.
|
||||
layout (binding = 1) readonly buffer K16 {FLOAT_TYPE data_k16[];};
|
||||
layout (binding = 2) readonly buffer V16 {FLOAT_TYPE data_v16[];};
|
||||
layout (binding = 3) readonly buffer MF16 {float16_t data_mf16[];};
|
||||
#ifdef GL_NV_cooperative_matrix_decode_vector
|
||||
layout (binding = 1) readonly buffer KF16V4 {f16vec4 data_kf16v4[];};
|
||||
layout (binding = 2) readonly buffer VF16V4 {f16vec4 data_vf16v4[];};
|
||||
layout (binding = 1) readonly buffer K16V4 {FLOAT_TYPEV4 data_k16v4[];};
|
||||
layout (binding = 2) readonly buffer V16V4 {FLOAT_TYPEV4 data_v16v4[];};
|
||||
#endif
|
||||
|
||||
// K/V/mask f16-element offsets for the current head/batch, set in main().
|
||||
// K/V/mask offsets in 16-bit elements for the current head/batch, set in main().
|
||||
uint32_t g_k_off_elem, g_v_off_elem, g_m_off_elem;
|
||||
|
||||
#if !defined(BFLOAT16)
|
||||
// blockCoords are in block units: KV slot = blockCoords[0],
|
||||
// head dim = blockCoords[1]*FA_GATHER_BS + coordInBlock[1].
|
||||
float16_t faGatherK(const decodeBufFA_K unused, const uint32_t blockCoords[2], const uint32_t coordInBlock[2]) {
|
||||
if (blockCoords[0] >= p.split_kv) { return float16_t(0); }
|
||||
FLOAT_TYPE faGatherK(const decodeBufFA_K bl_in, const uint32_t blockCoords[2], const uint32_t coordInBlock[2]) {
|
||||
if (blockCoords[0] >= p.split_kv) { return FLOAT_TYPE(0.0); }
|
||||
const int r = data_sparse[sparse_base + blockCoords[0]];
|
||||
return r < 0 ? float16_t(0) : data_kf16[g_k_off_elem + uint(r) * k_stride + blockCoords[1] * FA_GATHER_BS + coordInBlock[1]];
|
||||
if (r < 0) { return FLOAT_TYPE(0.0); }
|
||||
#if !defined(BFLOAT16)
|
||||
if (USE_DECODE_K) {
|
||||
decodeBufFA_K block = decodeBufFA_K(decodeBufFA_Byte(bl_in) + uint64_t(uint(r) - blockCoords[0]) * k_stride * FaBlockBytesK);
|
||||
return faDecodeK(block, blockCoords, coordInBlock);
|
||||
}
|
||||
#endif
|
||||
return data_k16[g_k_off_elem + uint(r) * k_stride + blockCoords[1] * FA_GATHER_BS + coordInBlock[1]];
|
||||
}
|
||||
|
||||
float16_t faGatherV(const decodeBufFA_V unused, const uint32_t blockCoords[2], const uint32_t coordInBlock[2]) {
|
||||
if (blockCoords[0] >= p.split_kv) { return float16_t(0); }
|
||||
FLOAT_TYPE faGatherV(const decodeBufFA_V bl_in, const uint32_t blockCoords[2], const uint32_t coordInBlock[2]) {
|
||||
if (blockCoords[0] >= p.split_kv) { return FLOAT_TYPE(0.0); }
|
||||
const int r = data_sparse[sparse_base + blockCoords[0]];
|
||||
return r < 0 ? float16_t(0) : data_vf16[g_v_off_elem + uint(r) * v_stride + blockCoords[1] * FA_GATHER_BS + coordInBlock[1]];
|
||||
if (r < 0) { return FLOAT_TYPE(0.0); }
|
||||
#if !defined(BFLOAT16)
|
||||
if (USE_DECODE_V) {
|
||||
decodeBufFA_V block = decodeBufFA_V(decodeBufFA_Byte(bl_in) + uint64_t(uint(r) - blockCoords[0]) * v_stride * FaBlockBytesV);
|
||||
return faDecodeV(block, blockCoords, coordInBlock);
|
||||
}
|
||||
#endif
|
||||
return data_v16[g_v_off_elem + uint(r) * v_stride + blockCoords[1] * FA_GATHER_BS + coordInBlock[1]];
|
||||
}
|
||||
|
||||
#ifdef GL_NV_cooperative_matrix_decode_vector
|
||||
f16vec4 faGatherKVector(const decodeBufFA_K unused, const uint32_t blockCoords[2], const uint32_t coordInBlock[2]) {
|
||||
if (blockCoords[0] >= p.split_kv) { return f16vec4(0); }
|
||||
FLOAT_TYPEV4 faGatherKVector(const decodeBufFA_K bl_in, const uint32_t blockCoords[2], const uint32_t coordInBlock[2]) {
|
||||
if (blockCoords[0] >= p.split_kv) { return FLOAT_TYPEV4(0.0); }
|
||||
const int r = data_sparse[sparse_base + blockCoords[0]];
|
||||
if (r < 0) { return f16vec4(0); }
|
||||
if (r < 0) { return FLOAT_TYPEV4(0.0); }
|
||||
#if !defined(BFLOAT16)
|
||||
if (USE_DECODE_K) {
|
||||
decodeBufFA_K block = decodeBufFA_K(decodeBufFA_Byte(bl_in) + uint64_t(uint(r) - blockCoords[0]) * k_stride * FaBlockBytesK);
|
||||
return faDecodeKVector(block, blockCoords, coordInBlock);
|
||||
}
|
||||
#endif
|
||||
const uint32_t o = g_k_off_elem + uint(r) * k_stride + blockCoords[1] * FA_GATHER_BS + coordInBlock[1];
|
||||
return data_kf16v4[o / 4];
|
||||
return data_k16v4[o / 4];
|
||||
}
|
||||
|
||||
f16vec4 faGatherVVector(const decodeBufFA_V unused, const uint32_t blockCoords[2], const uint32_t coordInBlock[2]) {
|
||||
if (blockCoords[0] >= p.split_kv) { return f16vec4(0); }
|
||||
FLOAT_TYPEV4 faGatherVVector(const decodeBufFA_V bl_in, const uint32_t blockCoords[2], const uint32_t coordInBlock[2]) {
|
||||
if (blockCoords[0] >= p.split_kv) { return FLOAT_TYPEV4(0.0); }
|
||||
const int r = data_sparse[sparse_base + blockCoords[0]];
|
||||
if (r < 0) { return f16vec4(0); }
|
||||
if (r < 0) { return FLOAT_TYPEV4(0.0); }
|
||||
#if !defined(BFLOAT16)
|
||||
if (USE_DECODE_V) {
|
||||
decodeBufFA_V block = decodeBufFA_V(decodeBufFA_Byte(bl_in) + uint64_t(uint(r) - blockCoords[0]) * v_stride * FaBlockBytesV);
|
||||
return faDecodeVVector(block, blockCoords, coordInBlock);
|
||||
}
|
||||
#endif
|
||||
const uint32_t o = g_v_off_elem + uint(r) * v_stride + blockCoords[1] * FA_GATHER_BS + coordInBlock[1];
|
||||
return data_vf16v4[o / 4];
|
||||
return data_v16v4[o / 4];
|
||||
}
|
||||
|
||||
#define FAGATHERK , faGatherK, faGatherKVector
|
||||
@@ -163,7 +191,6 @@ f16vec4 faGatherVVector(const decodeBufFA_V unused, const uint32_t blockCoords[2
|
||||
#define FAGATHERK , faGatherK
|
||||
#define FAGATHERV , faGatherV
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Add gathered mask to S (slope==1 since sparse requires max_bias==0). col = slot in block jblk.
|
||||
ACC_TYPE faAddSparseMask(const uint32_t row, const uint32_t col, const ACC_TYPE elem, const uint32_t jblk) {
|
||||
@@ -252,8 +279,8 @@ void main() {
|
||||
|
||||
tensorViewNV<2, false, 1, 0> tensorViewTranspose = createTensorViewNV(2, false, 1, 0);
|
||||
|
||||
const uint bs_k = USE_SPARSE ? FA_GATHER_BS : fa_block_elems(FaTypeK);
|
||||
const uint bs_v = USE_SPARSE ? FA_GATHER_BS : fa_block_elems(FaTypeV);
|
||||
const uint bs_k = USE_SPARSE ? max(FA_GATHER_BS, BLOCK_SIZE_K) : BLOCK_SIZE_K;
|
||||
const uint bs_v = USE_SPARSE ? max(FA_GATHER_BS, BLOCK_SIZE_V) : BLOCK_SIZE_V;
|
||||
tensorLayoutK = setTensorLayoutBlockSizeNV(tensorLayoutK, 1, bs_k);
|
||||
tensorLayoutV = setTensorLayoutBlockSizeNV(tensorLayoutV, 1, bs_v);
|
||||
|
||||
@@ -384,18 +411,15 @@ void main() {
|
||||
|
||||
uint32_t k_offset = ik2*p.nb12 + ik3*p.nb13;
|
||||
// F16: bs_k==1 (direct load). F32: bs_k==4 (vec4 / dequantFuncF32). Quantized types: bs_k==32.
|
||||
#if defined(BFLOAT16)
|
||||
coopMatLoadTensorNV(K_T, data_k, k_offset, sliceTensorLayoutNV(tensorLayoutK, j * Bc, Bc, 0, HSK_pad), tensorViewTranspose);
|
||||
#else
|
||||
const bool k_use_decode = (bs_k > 1u);
|
||||
if (USE_SPARSE) {
|
||||
coopMatLoadTensorNV(K_T, data_k, k_offset, sliceTensorLayoutNV(tensorLayoutK, j * Bc, Bc, 0, HSK_pad), tensorViewTranspose FAGATHERK);
|
||||
} else if (k_use_decode) {
|
||||
#if !defined(BFLOAT16)
|
||||
} else if (USE_DECODE_K) {
|
||||
coopMatLoadTensorNV(K_T, data_k, k_offset, sliceTensorLayoutNV(tensorLayoutK, j * Bc, Bc, 0, HSK_pad), tensorViewTranspose FADECODEK);
|
||||
#endif
|
||||
} else {
|
||||
coopMatLoadTensorNV(K_T, data_k, k_offset, sliceTensorLayoutNV(tensorLayoutK, j * Bc, Bc, 0, HSK_pad), tensorViewTranspose);
|
||||
}
|
||||
#endif
|
||||
S = coopMatMulAdd(Qf16, K_T, S);
|
||||
|
||||
if (LOGIT_SOFTCAP) {
|
||||
@@ -458,18 +482,15 @@ void main() {
|
||||
|
||||
coopmat<FLOAT_TYPE, gl_ScopeWorkgroup, Bc, HSV_pad, gl_MatrixUseB> V;
|
||||
uint32_t v_offset = iv2*p.nb22 + iv3*p.nb23;
|
||||
#if defined(BFLOAT16)
|
||||
coopMatLoadTensorNV(V, data_v, v_offset, sliceTensorLayoutNV(tensorLayoutV, j * Bc, Bc, 0, HSV_pad));
|
||||
#else
|
||||
const bool v_use_decode = (bs_v > 1u);
|
||||
if (USE_SPARSE) {
|
||||
coopMatLoadTensorNV(V, data_v, v_offset, sliceTensorLayoutNV(tensorLayoutV, j * Bc, Bc, 0, HSV_pad) FAGATHERV);
|
||||
} else if (v_use_decode) {
|
||||
#if !defined(BFLOAT16)
|
||||
} else if (USE_DECODE_V) {
|
||||
coopMatLoadTensorNV(V, data_v, v_offset, sliceTensorLayoutNV(tensorLayoutV, j * Bc, Bc, 0, HSV_pad) FADECODEV);
|
||||
#endif
|
||||
} else {
|
||||
coopMatLoadTensorNV(V, data_v, v_offset, sliceTensorLayoutNV(tensorLayoutV, j * Bc, Bc, 0, HSV_pad));
|
||||
}
|
||||
#endif
|
||||
|
||||
L = eM*L + rowsum;
|
||||
|
||||
|
||||
@@ -60,7 +60,13 @@ void topk(const uint row) {
|
||||
if (gl_GlobalInvocationID.x < p.ncols_input) {
|
||||
if (p.first_pass != 0) {
|
||||
const uint row_offset = row * p.ncols_input;
|
||||
dst_row[tid] = ivec2(gl_GlobalInvocationID.x, floatBitsToInt(data_a[row_offset + gl_GlobalInvocationID.x]));
|
||||
// NaN ranks lowest, like -inf, so that every value has a place in
|
||||
// the ordering the search below counts
|
||||
float a = float(data_a[row_offset + gl_GlobalInvocationID.x]);
|
||||
if (isnan(a)) {
|
||||
a = uintBitsToFloat(0xFF800000);
|
||||
}
|
||||
dst_row[tid] = ivec2(gl_GlobalInvocationID.x, floatBitsToInt(a));
|
||||
} else {
|
||||
const uint row_offset = row * p.ncols_input;
|
||||
dst_row[tid] = data_s[row_offset + gl_GlobalInvocationID.x];
|
||||
@@ -76,8 +82,10 @@ void topk(const uint row) {
|
||||
if (tid < s) {
|
||||
ivec2 a = dst_row[tid];
|
||||
ivec2 b = dst_row[tid + s];
|
||||
// compare as floats: the bit patterns of negative values
|
||||
// order the other way as integers
|
||||
if (a.x >= p.orig_ncols ||
|
||||
b.x < p.orig_ncols && b.y > a.y) {
|
||||
b.x < p.orig_ncols && intBitsToFloat(b.y) > intBitsToFloat(a.y)) {
|
||||
dst_row[tid] = b;
|
||||
}
|
||||
}
|
||||
@@ -95,9 +103,11 @@ void topk(const uint row) {
|
||||
int shift = 32 - SUBGROUP_SIZE_LOG2;
|
||||
uint mask = ((1 << SUBGROUP_SIZE_LOG2) - 1) << shift;
|
||||
|
||||
// The current range.
|
||||
// The current range, [range_min, range_max). It starts as every value
|
||||
// (+inf maps to 0xFF800000 and NaN was replaced by -inf), so the
|
||||
// buckets always hold at least limit values.
|
||||
uint range_min = 0;
|
||||
uint range_max = 0xFF800000;
|
||||
uint range_max = 0xFFFFFFFF;
|
||||
// How many are above the current range, and how many we need to find.
|
||||
uint total = 0;
|
||||
uint limit = min(p.k, p.ncols_input - gl_WorkGroupID.x * BLOCK_SIZE);
|
||||
@@ -138,8 +148,12 @@ void topk(const uint row) {
|
||||
total = sh_total;
|
||||
|
||||
// Update the range, and break if we've found the K-th largest.
|
||||
range_max = range_min + ((min_idx + 1) << shift);
|
||||
range_min = range_min + (min_idx << shift);
|
||||
// The end of the top bucket wraps past 2^32, clamp it instead.
|
||||
range_min = range_min + (uint(min_idx) << shift);
|
||||
range_max = range_min + (1u << shift);
|
||||
if (range_max < range_min) {
|
||||
range_max = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
if (total == p.k) {
|
||||
break;
|
||||
|
||||
+1
-1
@@ -396,7 +396,7 @@ extern "C" {
|
||||
enum ggml_type type_k; // data type for K cache [EXPERIMENTAL]
|
||||
enum ggml_type type_v; // data type for V cache [EXPERIMENTAL]
|
||||
|
||||
size_t moe_cache_size; // device cache in bytes for the experts kept in host memory, 0 = disabled [EXPERIMENTAL]
|
||||
size_t moe_cache_size; // device cache in bytes for the experts kept in host memory, split among the devices like the layers, 0 = disabled [EXPERIMENTAL]
|
||||
|
||||
// Abort callback
|
||||
// if it returns true, execution of llama_decode() will be aborted
|
||||
|
||||
+3
-14
@@ -436,7 +436,8 @@ llama_context::llama_context(
|
||||
model.n_gpu_layers() > model.hparams.n_layer_all &&
|
||||
model.split_mode() == LLAMA_SPLIT_MODE_LAYER &&
|
||||
cparams.offload_kqv &&
|
||||
!model.has_tensor_overrides();
|
||||
!model.has_tensor_overrides() &&
|
||||
cparams.moe_cache_size == 0; // not supported by the MoE cache
|
||||
|
||||
// pipeline parallelism requires support for async compute and events in all devices
|
||||
if (pipeline_parallel) {
|
||||
@@ -465,19 +466,7 @@ llama_context::llama_context(
|
||||
}
|
||||
|
||||
if (cparams.moe_cache_size > 0) {
|
||||
if (cparams.pipeline_parallel || model.n_devices() > 1) {
|
||||
throw std::runtime_error("MoE cache does not support multiple devices");
|
||||
}
|
||||
for (size_t i = 0; i < backend_ptrs.size(); ++i) {
|
||||
const auto type = ggml_backend_dev_type(ggml_backend_get_device(backend_ptrs[i]));
|
||||
if (type == GGML_BACKEND_DEVICE_TYPE_GPU || type == GGML_BACKEND_DEVICE_TYPE_IGPU) {
|
||||
moe_cache = std::make_unique<llama_moe_cache>(model, backend_ptrs[i], backend_buft[i], cparams.moe_cache_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!moe_cache) {
|
||||
throw std::runtime_error("MoE cache requires a GPU backend");
|
||||
}
|
||||
moe_cache = std::make_unique<llama_moe_cache>(model, backend_ptrs, backend_buft, cparams.moe_cache_size);
|
||||
}
|
||||
|
||||
sched_reserve();
|
||||
|
||||
+2
-2
@@ -2448,10 +2448,10 @@ ggml_tensor * llm_graph_context::build_moe_cache_slots(
|
||||
// the slot map is a host weight, so the scheduler starts a new split here and copies it with the copy callback
|
||||
// the callback reads the selected experts, uploads the missing ones and updates the slot map
|
||||
ggml_tensor * slots = ggml_get_rows(ctx0, slot_map, ids); // [1, n_expert_used*n_tokens]
|
||||
if (!ggml_backend_supports_op(moe_cache->backend(), slots)) {
|
||||
if (!ggml_backend_supports_op(moe_cache->backend(il), slots)) {
|
||||
return nullptr;
|
||||
}
|
||||
ggml_backend_sched_set_tensor_backend(sched, slots, moe_cache->backend());
|
||||
ggml_backend_sched_set_tensor_backend(sched, slots, moe_cache->backend(il));
|
||||
cb(slots, "ffn_moe_slots", il);
|
||||
|
||||
return ggml_reshape_2d(ctx0, slots, selected_experts->ne[0], selected_experts->ne[1]); // [n_expert_used, n_tokens]
|
||||
|
||||
+141
-52
@@ -151,8 +151,22 @@ static bool llama_moe_cache_is_host_weight(const ggml_tensor * t) {
|
||||
}
|
||||
|
||||
struct llama_moe_cache::impl {
|
||||
// layers with the same expert tensor layout share the banks and the LRU of a group
|
||||
// a GPU with its own budget and banks, it caches the layers assigned to it
|
||||
struct device {
|
||||
ggml_backend_t backend;
|
||||
ggml_backend_buffer_type_t buft;
|
||||
size_t host_bytes = 0; // host experts of the layers it caches
|
||||
double split = 0.0; // share of the budget
|
||||
|
||||
// banks and their views
|
||||
ggml_context_ptr ctx;
|
||||
ggml_backend_buffer_ptr buf;
|
||||
size_t buf_size = 0;
|
||||
};
|
||||
|
||||
// layers of the same device with the same expert tensor layout share the banks and the LRU of a group
|
||||
struct group {
|
||||
int32_t id; // device
|
||||
std::vector<ggml_tensor *> ref; // expert tensors of the first layer
|
||||
std::vector<int32_t> layers;
|
||||
std::vector<ggml_tensor *> banks; // device storage of all slots, one per expert tensor
|
||||
@@ -181,13 +195,13 @@ struct llama_moe_cache::impl {
|
||||
|
||||
static constexpr int64_t max_batch = 32;
|
||||
|
||||
ggml_backend_t backend;
|
||||
int32_t n_expert_used;
|
||||
|
||||
stats stats_small; // up to 8 tokens per ubatch
|
||||
stats stats_large;
|
||||
stats stats_copy; // experts copied from the cache for large batches
|
||||
|
||||
std::vector<device> devices;
|
||||
std::vector<group> groups;
|
||||
std::vector<layer> layers;
|
||||
std::unordered_map<const ggml_tensor *, binding> bindings; // host experts -> cached experts
|
||||
@@ -196,11 +210,6 @@ struct llama_moe_cache::impl {
|
||||
std::vector<int32_t> ids;
|
||||
std::vector<moe_cache_lru::fill> fills;
|
||||
|
||||
// banks and their views on the device
|
||||
ggml_context_ptr ctx;
|
||||
ggml_backend_buffer_ptr buf;
|
||||
size_t buf_size = 0;
|
||||
|
||||
// slot maps in host memory
|
||||
ggml_context_ptr ctx_host;
|
||||
ggml_backend_buffer_ptr buf_host;
|
||||
@@ -209,11 +218,17 @@ struct llama_moe_cache::impl {
|
||||
// views used by copy_experts
|
||||
ggml_context_ptr ctx_views;
|
||||
|
||||
impl(const llama_model & model, ggml_backend_t backend, ggml_backend_buffer_type_t buft, size_t size) :
|
||||
backend(backend), n_expert_used(model.hparams.n_expert_used_max()), layers(model.layers.size()) {
|
||||
ggml_backend_dev_t dev = ggml_backend_get_device(backend);
|
||||
const auto dev_type = ggml_backend_dev_type(dev);
|
||||
if (dev_type != GGML_BACKEND_DEVICE_TYPE_GPU && dev_type != GGML_BACKEND_DEVICE_TYPE_IGPU) {
|
||||
impl(const llama_model & model, const std::vector<ggml_backend_t> & backends, const std::vector<ggml_backend_buffer_type_t> & bufts, size_t size) :
|
||||
n_expert_used(model.hparams.n_expert_used_max()), layers(model.layers.size()) {
|
||||
for (size_t i = 0; i < backends.size(); ++i) {
|
||||
const auto dev_type = ggml_backend_dev_type(ggml_backend_get_device(backends[i]));
|
||||
if (dev_type == GGML_BACKEND_DEVICE_TYPE_GPU || dev_type == GGML_BACKEND_DEVICE_TYPE_IGPU) {
|
||||
auto & d = devices.emplace_back();
|
||||
d.backend = backends[i];
|
||||
d.buft = bufts[i];
|
||||
}
|
||||
}
|
||||
if (devices.empty()) {
|
||||
throw std::runtime_error("MoE cache requires a GPU backend");
|
||||
}
|
||||
if (model.split_mode() == LLAMA_SPLIT_MODE_TENSOR) {
|
||||
@@ -223,24 +238,28 @@ struct llama_moe_cache::impl {
|
||||
throw std::runtime_error("MoE cache requires a MoE model");
|
||||
}
|
||||
|
||||
// only cache layers that keep all of their experts in host memory
|
||||
size_t host_bytes = 0;
|
||||
// only cache layers that keep all of their experts in host memory, on the device the layer is assigned to
|
||||
for (size_t il = 0; il < model.layers.size(); ++il) {
|
||||
auto experts = llama_moe_cache_layer_experts(model.layers[il]);
|
||||
if (experts.empty() || model.dev_layer(il) != dev ||
|
||||
!std::all_of(experts.begin(), experts.end(), llama_moe_cache_is_host_weight)) {
|
||||
if (experts.empty() || !std::all_of(experts.begin(), experts.end(), llama_moe_cache_is_host_weight)) {
|
||||
continue;
|
||||
}
|
||||
auto it = std::find_if(groups.begin(), groups.end(), [&](const group & g) { return llama_moe_cache_same_layout(g.ref, experts); });
|
||||
const auto it_dev = std::find_if(devices.begin(), devices.end(), [&](const device & d) { return ggml_backend_get_device(d.backend) == model.dev_layer(il); });
|
||||
if (it_dev == devices.end()) {
|
||||
continue;
|
||||
}
|
||||
const int32_t id = (int32_t) (it_dev - devices.begin());
|
||||
auto it = std::find_if(groups.begin(), groups.end(), [&](const group & g) { return g.id == id && llama_moe_cache_same_layout(g.ref, experts); });
|
||||
if (it == groups.end()) {
|
||||
groups.emplace_back();
|
||||
it = groups.end() - 1;
|
||||
it->id = id;
|
||||
it->ref = experts;
|
||||
}
|
||||
it->layers.push_back(il);
|
||||
for (const ggml_tensor * t : experts) {
|
||||
it->host_bytes += ggml_nbytes(t);
|
||||
host_bytes += ggml_nbytes(t);
|
||||
it->host_bytes += ggml_nbytes(t);
|
||||
devices[id].host_bytes += ggml_nbytes(t);
|
||||
}
|
||||
}
|
||||
if (groups.empty()) {
|
||||
@@ -249,8 +268,8 @@ struct llama_moe_cache::impl {
|
||||
}
|
||||
|
||||
// one extra slot at the end, CUDA MMQ can read past the last expert
|
||||
const size_t alignment = ggml_backend_buft_get_alignment(buft);
|
||||
auto alloc_size = [&](const group & g, int32_t n_slots) {
|
||||
const size_t alignment = ggml_backend_buft_get_alignment(devices[g.id].buft);
|
||||
size_t res = 0;
|
||||
for (const ggml_tensor * t : g.ref) {
|
||||
res += GGML_PAD(t->nb[2]*(n_slots + 1), alignment);
|
||||
@@ -258,12 +277,43 @@ struct llama_moe_cache::impl {
|
||||
return res;
|
||||
};
|
||||
|
||||
// split the budget by the size of the experts, so each group caches the same fraction of its experts
|
||||
size_t n_tensors = 0;
|
||||
// the budget is split among the devices with host experts like the layers, by the tensor split or by default by free memory
|
||||
const float * tensor_split = model.tensor_split();
|
||||
const bool split_by_free = tensor_split == nullptr ||
|
||||
std::all_of(tensor_split, tensor_split + model.n_devices(), [](float x) { return x == 0.0f; });
|
||||
double split_sum = 0.0;
|
||||
for (device & d : devices) {
|
||||
if (d.host_bytes == 0) {
|
||||
continue;
|
||||
}
|
||||
ggml_backend_dev_t dev = ggml_backend_get_device(d.backend);
|
||||
if (split_by_free) {
|
||||
size_t free;
|
||||
size_t total;
|
||||
ggml_backend_dev_memory(dev, &free, &total);
|
||||
d.split = (double) free;
|
||||
} else {
|
||||
const auto it = std::find_if(model.devices.begin(), model.devices.end(), [&](const llama_device & ld) { return ld.dev == dev; });
|
||||
GGML_ASSERT(it != model.devices.end());
|
||||
d.split = (double) tensor_split[it - model.devices.begin()];
|
||||
}
|
||||
split_sum += d.split;
|
||||
}
|
||||
if (split_sum == 0.0) {
|
||||
// the devices do not report their free memory
|
||||
for (device & d : devices) {
|
||||
d.split = d.host_bytes > 0 ? 1.0 : 0.0;
|
||||
split_sum += d.split;
|
||||
}
|
||||
}
|
||||
|
||||
// within a device the budget is split by the size of the experts, so each group caches the same fraction of its experts
|
||||
std::vector<size_t> n_tensors(devices.size(), 0);
|
||||
size_t n_tensors_host = 0;
|
||||
for (group & g : groups) {
|
||||
const device & d = devices[g.id];
|
||||
const int32_t n_expert = g.ref[0]->ne[2];
|
||||
const size_t budget = (size_t) ((double) size*g.host_bytes/host_bytes);
|
||||
const size_t budget = (size_t) ((double) size*d.split/split_sum*g.host_bytes/d.host_bytes);
|
||||
const int32_t max_slots = g.layers.size()*n_expert;
|
||||
while (g.n_slots < max_slots && alloc_size(g, g.n_slots + 1) <= budget) {
|
||||
g.n_slots++;
|
||||
@@ -274,10 +324,10 @@ struct llama_moe_cache::impl {
|
||||
continue;
|
||||
}
|
||||
g.lru.init(model.layers.size(), n_expert, g.n_slots);
|
||||
n_tensors += g.ref.size()*(1 + g.layers.size());
|
||||
n_tensors_host += g.layers.size();
|
||||
n_tensors[g.id] += g.ref.size()*(1 + g.layers.size());
|
||||
n_tensors_host += g.layers.size();
|
||||
}
|
||||
if (n_tensors == 0) {
|
||||
if (n_tensors_host == 0) {
|
||||
throw std::runtime_error("MoE cache is too small to hold the experts of one token");
|
||||
}
|
||||
|
||||
@@ -293,7 +343,11 @@ struct llama_moe_cache::impl {
|
||||
}
|
||||
return res;
|
||||
};
|
||||
ctx = init_ctx(n_tensors);
|
||||
for (size_t id = 0; id < devices.size(); ++id) {
|
||||
if (n_tensors[id] > 0) {
|
||||
devices[id].ctx = init_ctx(n_tensors[id]);
|
||||
}
|
||||
}
|
||||
ctx_host = init_ctx(n_tensors_host);
|
||||
ctx_views = init_ctx(2);
|
||||
|
||||
@@ -305,8 +359,9 @@ struct llama_moe_cache::impl {
|
||||
if (g.n_slots == 0) {
|
||||
continue;
|
||||
}
|
||||
ggml_context * ctx = devices[g.id].ctx.get();
|
||||
for (const ggml_tensor * t : g.ref) {
|
||||
ggml_tensor * bank = ggml_new_tensor_3d(ctx.get(), t->type, t->ne[0], t->ne[1], g.n_slots + 1);
|
||||
ggml_tensor * bank = ggml_new_tensor_3d(ctx, t->type, t->ne[0], t->ne[1], g.n_slots + 1);
|
||||
GGML_ASSERT(bank->nb[2] == t->nb[2]);
|
||||
ggml_format_name(bank, "moe_cache.%zu.%s", ig, t->name);
|
||||
g.banks.push_back(bank);
|
||||
@@ -317,7 +372,7 @@ struct llama_moe_cache::impl {
|
||||
l.experts = llama_moe_cache_layer_experts(model.layers[il]);
|
||||
for (size_t ip = 0; ip < l.experts.size(); ++ip) {
|
||||
ggml_tensor * bank = g.banks[ip];
|
||||
ggml_tensor * cached = ggml_view_3d(ctx.get(), bank, bank->ne[0], bank->ne[1], g.n_slots, bank->nb[1], bank->nb[2], 0);
|
||||
ggml_tensor * cached = ggml_view_3d(ctx, bank, bank->ne[0], bank->ne[1], g.n_slots, bank->nb[1], bank->nb[2], 0);
|
||||
ggml_format_name(cached, "moe_cache.%s", l.experts[ip]->name);
|
||||
bindings[l.experts[ip]] = { il, (int32_t) ip, cached };
|
||||
}
|
||||
@@ -326,28 +381,41 @@ struct llama_moe_cache::impl {
|
||||
layer_of[l.slot_map] = il;
|
||||
buf_host_size += GGML_PAD(ggml_nbytes(l.slot_map), alignment_host);
|
||||
}
|
||||
buf_size += alloc_size(g, g.n_slots);
|
||||
devices[g.id].buf_size += alloc_size(g, g.n_slots);
|
||||
}
|
||||
|
||||
if (model.hparams.no_alloc) {
|
||||
// only used to measure the memory use, see llama_context::memory_breakdown
|
||||
buf.reset(ggml_backend_buft_alloc_buffer(buft, 0));
|
||||
buf_host.reset(ggml_backend_buft_alloc_buffer(buft_host, 0));
|
||||
for (ggml_tensor * t = ggml_get_first_tensor(ctx.get()); t != nullptr; t = ggml_get_next_tensor(ctx.get(), t)) {
|
||||
t->buffer = buf.get();
|
||||
for (device & d : devices) {
|
||||
if (!d.ctx) {
|
||||
continue;
|
||||
}
|
||||
d.buf.reset(ggml_backend_buft_alloc_buffer(d.buft, 0));
|
||||
for (ggml_tensor * t = ggml_get_first_tensor(d.ctx.get()); t != nullptr; t = ggml_get_next_tensor(d.ctx.get(), t)) {
|
||||
t->buffer = d.buf.get();
|
||||
}
|
||||
}
|
||||
buf_host.reset(ggml_backend_buft_alloc_buffer(buft_host, 0));
|
||||
for (ggml_tensor * t = ggml_get_first_tensor(ctx_host.get()); t != nullptr; t = ggml_get_next_tensor(ctx_host.get(), t)) {
|
||||
t->buffer = buf_host.get();
|
||||
}
|
||||
} else {
|
||||
buf.reset(ggml_backend_alloc_ctx_tensors_from_buft(ctx.get(), buft));
|
||||
for (device & d : devices) {
|
||||
if (!d.ctx) {
|
||||
continue;
|
||||
}
|
||||
d.buf.reset(ggml_backend_alloc_ctx_tensors_from_buft(d.ctx.get(), d.buft));
|
||||
if (!d.buf) {
|
||||
throw std::runtime_error("failed to allocate the MoE cache buffers");
|
||||
}
|
||||
ggml_backend_buffer_clear(d.buf.get(), 0);
|
||||
d.buf_size = ggml_backend_buffer_get_size(d.buf.get());
|
||||
}
|
||||
buf_host.reset(ggml_backend_alloc_ctx_tensors_from_buft(ctx_host.get(), buft_host));
|
||||
if (!buf || !buf_host) {
|
||||
if (!buf_host) {
|
||||
throw std::runtime_error("failed to allocate the MoE cache buffers");
|
||||
}
|
||||
ggml_backend_buffer_clear(buf.get(), 0);
|
||||
ggml_backend_buffer_clear(buf_host.get(), 0xff); // all slots are -1
|
||||
buf_size = ggml_backend_buffer_get_size(buf.get());
|
||||
buf_host_size = ggml_backend_buffer_get_size(buf_host.get());
|
||||
|
||||
for (group & g : groups) {
|
||||
@@ -360,17 +428,33 @@ struct llama_moe_cache::impl {
|
||||
}
|
||||
|
||||
// as weights, the ops that read the banks run on the device and the slot maps are copied with the copy callback
|
||||
ggml_backend_buffer_set_usage(buf.get(), GGML_BACKEND_BUFFER_USAGE_WEIGHTS);
|
||||
for (device & d : devices) {
|
||||
if (d.buf) {
|
||||
ggml_backend_buffer_set_usage(d.buf.get(), GGML_BACKEND_BUFFER_USAGE_WEIGHTS);
|
||||
}
|
||||
}
|
||||
ggml_backend_buffer_set_usage(buf_host.get(), GGML_BACKEND_BUFFER_USAGE_WEIGHTS);
|
||||
|
||||
LLAMA_LOG_INFO("%s: %10s MoE cache size = %8.2f MiB for %.2f MiB of host experts\n", __func__,
|
||||
ggml_backend_buft_name(buft), buf_size/1024.0/1024.0, host_bytes/1024.0/1024.0);
|
||||
for (const group & g : groups) {
|
||||
LLAMA_LOG_INFO("%s: %2zu layers, %s: %5d slots (%.1f%%)\n", __func__,
|
||||
g.layers.size(), ggml_type_name(g.ref.back()->type), g.n_slots, 100.0*g.n_slots/(g.layers.size()*g.ref[0]->ne[2]));
|
||||
for (size_t id = 0; id < devices.size(); ++id) {
|
||||
const device & d = devices[id];
|
||||
if (d.host_bytes == 0) {
|
||||
continue;
|
||||
}
|
||||
LLAMA_LOG_INFO("%s: %10s MoE cache size = %8.2f MiB for %.2f MiB of host experts\n", __func__,
|
||||
ggml_backend_buft_name(d.buft), d.buf_size/1024.0/1024.0, d.host_bytes/1024.0/1024.0);
|
||||
for (const group & g : groups) {
|
||||
if (g.id == (int32_t) id) {
|
||||
LLAMA_LOG_INFO("%s: %2zu layers, %s: %5d slots (%.1f%%)\n", __func__,
|
||||
g.layers.size(), ggml_type_name(g.ref.back()->type), g.n_slots, 100.0*g.n_slots/(g.layers.size()*g.ref[0]->ne[2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ggml_backend_t backend(int32_t il) const {
|
||||
return devices[groups[layers[il].ig].id].backend;
|
||||
}
|
||||
|
||||
~impl() {
|
||||
log_stats();
|
||||
}
|
||||
@@ -395,11 +479,14 @@ struct llama_moe_cache::impl {
|
||||
|
||||
int64_t copy_experts(ggml_backend_t backend, const ggml_tensor * w, ggml_tensor * dst, int64_t e, int64_t last) {
|
||||
const auto it = bindings.find(w);
|
||||
if (it == bindings.end() || backend != this->backend) {
|
||||
if (it == bindings.end()) {
|
||||
return 0;
|
||||
}
|
||||
const binding & b = it->second;
|
||||
const group & g = groups[layers[b.il].ig];
|
||||
if (backend != devices[g.id].backend) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// large batches only read the cache, so the experts used in generation stay in it
|
||||
const int32_t * slots = g.lru.slot_map[b.il];
|
||||
@@ -434,7 +521,7 @@ struct llama_moe_cache::impl {
|
||||
const layer & l = layers[il];
|
||||
group & g = groups[l.ig];
|
||||
|
||||
GGML_ASSERT(backend == this->backend);
|
||||
GGML_ASSERT(backend == devices[g.id].backend);
|
||||
|
||||
// the get_rows that looks up the slots of the selected experts
|
||||
const int n_nodes = ggml_graph_n_nodes(graph);
|
||||
@@ -512,14 +599,14 @@ struct llama_moe_cache::impl {
|
||||
}
|
||||
};
|
||||
|
||||
llama_moe_cache::llama_moe_cache(const llama_model & model, ggml_backend_t backend, ggml_backend_buffer_type_t buft, size_t size) :
|
||||
pimpl(new impl(model, backend, buft, size)) {
|
||||
llama_moe_cache::llama_moe_cache(const llama_model & model, const std::vector<ggml_backend_t> & backends, const std::vector<ggml_backend_buffer_type_t> & bufts, size_t size) :
|
||||
pimpl(new impl(model, backends, bufts, size)) {
|
||||
}
|
||||
|
||||
llama_moe_cache::~llama_moe_cache() = default;
|
||||
|
||||
ggml_backend_t llama_moe_cache::backend() const {
|
||||
return pimpl->backend;
|
||||
ggml_backend_t llama_moe_cache::backend(int32_t il) const {
|
||||
return pimpl->backend(il);
|
||||
}
|
||||
|
||||
ggml_tensor * llama_moe_cache::get_slot_map(int32_t il, int64_t n_tokens, int64_t n_expert_used) const {
|
||||
@@ -540,8 +627,10 @@ int64_t llama_moe_cache::copy_experts(ggml_backend_t backend, const ggml_tensor
|
||||
|
||||
std::map<ggml_backend_buffer_type_t, size_t> llama_moe_cache::memory_breakdown() const {
|
||||
std::map<ggml_backend_buffer_type_t, size_t> res;
|
||||
if (pimpl->buf) {
|
||||
res[ggml_backend_buffer_get_type(pimpl->buf.get())] += pimpl->buf_size;
|
||||
for (const auto & d : pimpl->devices) {
|
||||
if (d.buf) {
|
||||
res[ggml_backend_buffer_get_type(d.buf.get())] += d.buf_size;
|
||||
}
|
||||
}
|
||||
if (pimpl->buf_host) {
|
||||
res[ggml_backend_buffer_get_type(pimpl->buf_host.get())] += pimpl->buf_host_size;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
struct llama_model;
|
||||
|
||||
@@ -11,10 +12,12 @@ struct llama_model;
|
||||
// each layer has a slot map in host memory: when the scheduler copies it to the device, the copy callback uploads the missing experts
|
||||
class llama_moe_cache {
|
||||
public:
|
||||
llama_moe_cache(const llama_model & model, ggml_backend_t backend, ggml_backend_buffer_type_t buft, size_t size);
|
||||
// backends are all the backends of the context, each GPU gets its own cache of the given size for the layers assigned to it
|
||||
llama_moe_cache(const llama_model & model, const std::vector<ggml_backend_t> & backends, const std::vector<ggml_backend_buffer_type_t> & bufts, size_t size);
|
||||
~llama_moe_cache();
|
||||
|
||||
ggml_backend_t backend() const;
|
||||
// the device that caches layer il
|
||||
ggml_backend_t backend(int32_t il) const;
|
||||
|
||||
// the slot map of layer il, if its experts can be read from the cache for n_tokens tokens, nullptr otherwise
|
||||
ggml_tensor * get_slot_map(int32_t il, int64_t n_tokens, int64_t n_expert_used) const;
|
||||
|
||||
@@ -167,9 +167,6 @@ void llama_model_dflash::load_arch_tensors(llama_model_loader &) {
|
||||
// optional: reduced-vocab drafts ship their own lm head, full-vocab drafts can share the target's via ctx_other
|
||||
// a draft with its own embeddings + head references no target tensors and can run on devices the target does not use (e.g. -devd with a tensor-split target)
|
||||
output = create_tensor(tn(LLM_TENSOR_OUTPUT, "weight"), { n_embd, n_vocab_draft }, TENSOR_NOT_REQUIRED);
|
||||
if (output == nullptr && tok_embd != nullptr) {
|
||||
output = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), { n_embd, n_vocab_draft }, TENSOR_DUPLICATED);
|
||||
}
|
||||
|
||||
if (hparams.dsv4_hc_mult > 0) {
|
||||
const int64_t q_lora_rank = hparams.n_lora_q;
|
||||
|
||||
@@ -7042,6 +7042,46 @@ struct test_top_k : public test_case {
|
||||
}
|
||||
};
|
||||
|
||||
// top_k over rows like log-probabilities: distinct negative values, fewer
|
||||
// than k +inf (none for k = 1, so the expected indices are unique) and many
|
||||
// -inf (masked tokens)
|
||||
struct test_top_k_inf : public test_top_k {
|
||||
test_top_k_inf(std::array<int64_t, 4> ne, int k)
|
||||
: test_top_k(GGML_TYPE_F32, ne, k, false) {}
|
||||
|
||||
std::string vars() override {
|
||||
return test_top_k::vars() + ",inf=1";
|
||||
}
|
||||
|
||||
// compare only the output: the input holds infinities, which err() would
|
||||
// read as indices
|
||||
bool run_whole_graph() override { return true; }
|
||||
|
||||
void initialize_tensors(ggml_context * ctx) override {
|
||||
std::random_device rd;
|
||||
std::default_random_engine rng(rd());
|
||||
for (ggml_tensor * t = ggml_get_first_tensor(ctx); t != NULL; t = ggml_get_next_tensor(ctx, t)) {
|
||||
for (int64_t r = 0; r < ggml_nrows(t); r++) {
|
||||
std::vector<float> data(t->ne[0]);
|
||||
for (int i = 0; i < t->ne[0]; i++) {
|
||||
data[i] = -1.0f - i;
|
||||
}
|
||||
std::shuffle(data.begin(), data.end(), rng);
|
||||
const int n_pinf = k / 2;
|
||||
for (int i = 0; i < t->ne[0]; i++) {
|
||||
if (i < n_pinf) {
|
||||
data[i] = INFINITY;
|
||||
} else if (i % 3 == 0) {
|
||||
data[i] = -INFINITY;
|
||||
}
|
||||
}
|
||||
std::shuffle(data.begin(), data.end(), rng);
|
||||
ggml_backend_tensor_set(t, data.data(), r * t->nb[1], t->ne[0] * sizeof(float));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// qwen4exp QSA indexer top-k fusion: expand per-block scores to cells, add the f16 mask, top-k.
|
||||
struct test_topk_qsa : public test_case {
|
||||
const int64_t n_blocks;
|
||||
@@ -11179,6 +11219,11 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {n, 2, 1, 3}, k, true));
|
||||
}
|
||||
}
|
||||
for (int k : {1, 10, 40}) {
|
||||
test_cases.emplace_back(new test_top_k_inf({4096, 2, 1, 1}, k));
|
||||
test_cases.emplace_back(new test_top_k_inf({248320, 1, 1, 1}, k));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
for (int k : {1, 2, 3, 7, 15, 100, 500, 1023, 9999}) {
|
||||
if (k <= 1<<i) {
|
||||
@@ -11221,6 +11266,10 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, { 8192, 2, 1, 1 }, 2051, true));
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, { 33024, 4, 1, 1 }, 2051, true));
|
||||
|
||||
// rows that CUDA processes in several chunks (bitonic, radix)
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, { 500, 40000, 1, 1 }, 16));
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, { 1100, 33000, 1, 1 }, 16));
|
||||
|
||||
// qwen4exp QSA indexer top-k fusion (get_rows + f16 mask + top_k)
|
||||
test_cases.emplace_back(new test_topk_qsa(512, 2048, 1, 1, 1500));
|
||||
test_cases.emplace_back(new test_topk_qsa(512, 2048, 2, 1, 1500));
|
||||
@@ -11299,6 +11348,10 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
test_cases.emplace_back(new test_pad(GGML_TYPE_F32, {100, 1, 1, 1}, 100, 0, false));
|
||||
test_cases.emplace_back(new test_pad(GGML_TYPE_F32, {100, 1, 1, 1}, 0, 100, false));
|
||||
test_cases.emplace_back(new test_pad(GGML_TYPE_F32, {100, 100, 1, 1}, 50, 50, false));
|
||||
// more than 65535 rows or slices, beyond the CUDA grid.y/grid.z limit
|
||||
test_cases.emplace_back(new test_pad(GGML_TYPE_F32, {4, 70000, 1, 1}, 1, 1, false));
|
||||
test_cases.emplace_back(new test_pad(GGML_TYPE_F32, {4, 70000, 1, 1}, 1, 1, true));
|
||||
test_cases.emplace_back(new test_pad_ext(GGML_TYPE_F32, {4, 2, 300, 300}, 1, 1, 0, 0, 0, 0, 0, 0, 0, false));
|
||||
|
||||
test_cases.emplace_back(new test_pad_reflect_1d());
|
||||
test_cases.emplace_back(new test_pad_reflect_1d(GGML_TYPE_F32, {3000, 384, 4, 1}));
|
||||
@@ -11526,6 +11579,15 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
// KV not a multiple of the compaction workgroup size.
|
||||
test_cases.emplace_back(new test_flash_attn_ext(128, 128, 1, { 8, 1}, 5003, 4, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_F16, {0, 1, 2, 3}, true, false, 512));
|
||||
|
||||
// Sparse gather: native block sizes, padded slots, and head/batch strides.
|
||||
for (ggml_type type : {GGML_TYPE_F32, GGML_TYPE_F16, GGML_TYPE_BF16, GGML_TYPE_Q4_0, GGML_TYPE_Q4_1, GGML_TYPE_Q5_0, GGML_TYPE_Q5_1, GGML_TYPE_Q8_0, GGML_TYPE_IQ4_NL}) {
|
||||
test_cases.emplace_back(new test_flash_attn_ext(128, 96, 2, {8, 2}, 5003, 3, true, false, 0, 0, GGML_PREC_F32, type, type, {0, 1, 2, 3}, true, false, 257));
|
||||
}
|
||||
test_cases.emplace_back(new test_flash_attn_ext(128, 96, 2, {8, 2}, 5003, 3, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_F16, {0, 2, 1, 3}, true, false, 257));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(128, 96, 2, {8, 2}, 5003, 3, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_Q4_0, {0, 2, 1, 3}, true, false, 257));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(128, 96, 2, {8, 2}, 5003, 3, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_F32, {0, 1, 2, 3}, true, false, 257));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(128, 96, 2, {8, 2}, 5003, 3, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F32, GGML_TYPE_Q8_0, {0, 1, 2, 3}, true, false, 257));
|
||||
|
||||
// more V-is-sub-view-of-K cases: other head shapes, and full views with equal head sizes
|
||||
test_cases.emplace_back(new test_flash_attn_ext(320, 256, 1, {32, 1}, 512, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_F16, {0, 1, 2, 3}, true, true));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(192, 128, 4, {8, 1}, 512, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_F16, {0, 1, 2, 3}, true, true));
|
||||
@@ -12017,6 +12079,8 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_perf() {
|
||||
test_cases.emplace_back(new test_flash_attn_ext(576, 512, 1, {16, 1}, kv, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_F16, {0, 1, 2, 3}, true, true, 512));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {12, 1}, kv, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_F16, {0, 1, 2, 3}, true, false, 2048));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {12, 1}, kv, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 1, 2, 3}, true, false, 2048));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {12, 1}, kv, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q4_0, GGML_TYPE_Q4_0, {0, 1, 2, 3}, true, false, 2048));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {12, 1}, kv, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_BF16, GGML_TYPE_BF16, {0, 1, 2, 3}, true, false, 2048));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {12, 1}, kv, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 1, 2, 3}, true, false, 0));
|
||||
}
|
||||
|
||||
@@ -12168,6 +12232,12 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_perf() {
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {cols, nrows, 1, 1}, 2048));
|
||||
}
|
||||
}
|
||||
// bitonic vs radix crossover
|
||||
for (auto cols : {520, 1000, 2048, 3000, 4096}) {
|
||||
for (auto nrows : {2, 16, 32, 48, 64, 128, 256, 512}) {
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {cols, nrows, 1, 1}, 16));
|
||||
}
|
||||
}
|
||||
// backend sampler: one row of the vocab (llama-sampler.cpp top_k)
|
||||
for (auto k : {20, 40}) {
|
||||
test_cases.emplace_back(new test_top_k(GGML_TYPE_F32, {151936, 1, 1, 1}, k));
|
||||
|
||||
@@ -919,6 +919,11 @@ static int test_backends(const std::string & arch_filter, const size_t seed, con
|
||||
if (type == GGML_BACKEND_DEVICE_TYPE_GPU || type == GGML_BACKEND_DEVICE_TYPE_IGPU) {
|
||||
dev_configs.emplace_back(std::vector<ggml_backend_dev_t>{devices_meta[0]}, "MoE cache", LLAMA_SPLIT_MODE_LAYER, true, 1536*1024);
|
||||
max_device_label_length = std::max(max_device_label_length, dev_configs.back().label.length());
|
||||
// each GPU caches the layers assigned to it
|
||||
if (devices_meta.size() > 1) {
|
||||
dev_configs.emplace_back(devices_meta, "MoE cache, layer split", LLAMA_SPLIT_MODE_LAYER, true, 1536*1024);
|
||||
max_device_label_length = std::max(max_device_label_length, dev_configs.back().label.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
| `-ot, --override-tensor <tensor name pattern>=<buffer type>,...` | override tensor buffer type<br/>(env: LLAMA_ARG_OVERRIDE_TENSOR) |
|
||||
| `-cmoe, --cpu-moe` | keep all Mixture of Experts (MoE) weights in the CPU<br/>(env: LLAMA_ARG_CPU_MOE) |
|
||||
| `-ncmoe, --n-cpu-moe N` | keep the Mixture of Experts (MoE) weights of the first N layers in the CPU<br/>(env: LLAMA_ARG_N_CPU_MOE) |
|
||||
| `--moe-cache-mib N` | GPU cache size in MiB for the MoE experts kept in the CPU. with multiple GPUs, it is split among them like the layers (--tensor-split) (default: 0, disabled)<br/>(env: LLAMA_ARG_MOE_CACHE_MIB) |
|
||||
| `-ncffn, --n-cpu-ffn N` | keep the dense FFN weights of the first N layers in the CPU<br/>(dense models; for MoE expert weights use --n-cpu-moe)<br/>(env: LLAMA_ARG_N_CPU_FFN) |
|
||||
| `-ngl, --gpu-layers, --n-gpu-layers N` | max. number of layers to store in VRAM, either an exact number, 'auto', or 'all' (default: auto)<br/>(env: LLAMA_ARG_N_GPU_LAYERS) |
|
||||
| `-sm, --split-mode {none,layer,row,tensor}` | how to split the model across multiple GPUs, one of:<br/>- none: use one GPU only<br/>- layer (default): split layers and KV across GPUs (pipelined)<br/>- row: split weight across GPUs by rows (parallelized)<br/>- tensor: split weights and KV across GPUs (parallelized, EXPERIMENTAL)<br/>(env: LLAMA_ARG_SPLIT_MODE) |
|
||||
@@ -204,6 +205,7 @@
|
||||
| `--spec-draft-p-split, --draft-p-split P` | speculative decoding split probability (default: 0.10)<br/>(env: LLAMA_ARG_SPEC_DRAFT_P_SPLIT) |
|
||||
| `--spec-draft-p-min, --draft-p-min P` | minimum speculative decoding probability (greedy) (default: 0.00)<br/>(env: LLAMA_ARG_SPEC_DRAFT_P_MIN) |
|
||||
| `--spec-draft-backend-sampling, --no-spec-draft-backend-sampling` | offload draft sampling to the backend (default: enabled)<br/>(env: LLAMA_ARG_SPEC_DRAFT_BACKEND_SAMPLING) |
|
||||
| `--spec-draft-sampling {greedy,probabilistic}` | how the draft is sampled: greedy takes its argmax, probabilistic samples it and has the target verify by rejection sampling (default: greedy)<br/>(env: LLAMA_ARG_SPEC_DRAFT_SAMPLING) |
|
||||
| `--spec-draft-device, -devd, --device-draft <dev1,dev2,..>` | comma-separated list of devices to use for offloading the draft model (none = don't offload, default: follows --device)<br/>use --list-devices to see a list of available devices |
|
||||
| `--spec-draft-ngl, -ngld, --gpu-layers-draft, --n-gpu-layers-draft N` | max. number of draft model layers to store in VRAM, either an exact number, 'auto', or 'all' (default: auto)<br/>(env: LLAMA_ARG_N_GPU_LAYERS_DRAFT) |
|
||||
| `--spec-draft-model, -md, --model-draft FNAME` | draft model for speculative decoding (default: unused)<br/>(env: LLAMA_ARG_SPEC_DRAFT_MODEL) |
|
||||
|
||||
@@ -146,6 +146,7 @@ llama-completion.exe -m models\gemma-1.1-7b-it.Q4_K_M.gguf --ignore-eos -n -1
|
||||
| `-ot, --override-tensor <tensor name pattern>=<buffer type>,...` | override tensor buffer type<br/>(env: LLAMA_ARG_OVERRIDE_TENSOR) |
|
||||
| `-cmoe, --cpu-moe` | keep all Mixture of Experts (MoE) weights in the CPU<br/>(env: LLAMA_ARG_CPU_MOE) |
|
||||
| `-ncmoe, --n-cpu-moe N` | keep the Mixture of Experts (MoE) weights of the first N layers in the CPU<br/>(env: LLAMA_ARG_N_CPU_MOE) |
|
||||
| `--moe-cache-mib N` | GPU cache size in MiB for the MoE experts kept in the CPU. with multiple GPUs, it is split among them like the layers (--tensor-split) (default: 0, disabled)<br/>(env: LLAMA_ARG_MOE_CACHE_MIB) |
|
||||
| `-ncffn, --n-cpu-ffn N` | keep the dense FFN weights of the first N layers in the CPU<br/>(dense models; for MoE expert weights use --n-cpu-moe)<br/>(env: LLAMA_ARG_N_CPU_FFN) |
|
||||
| `-ngl, --gpu-layers, --n-gpu-layers N` | max. number of layers to store in VRAM, either an exact number, 'auto', or 'all' (default: auto)<br/>(env: LLAMA_ARG_N_GPU_LAYERS) |
|
||||
| `-sm, --split-mode {none,layer,row,tensor}` | how to split the model across multiple GPUs, one of:<br/>- none: use one GPU only<br/>- layer (default): split layers and KV across GPUs (pipelined)<br/>- row: split weight across GPUs by rows (parallelized)<br/>- tensor: split weights and KV across GPUs (parallelized, EXPERIMENTAL)<br/>(env: LLAMA_ARG_SPLIT_MODE) |
|
||||
|
||||
@@ -80,6 +80,7 @@ For the full list of features, please refer to [server's changelog](https://gith
|
||||
| `-ot, --override-tensor <tensor name pattern>=<buffer type>,...` | override tensor buffer type<br/>(env: LLAMA_ARG_OVERRIDE_TENSOR) |
|
||||
| `-cmoe, --cpu-moe` | keep all Mixture of Experts (MoE) weights in the CPU<br/>(env: LLAMA_ARG_CPU_MOE) |
|
||||
| `-ncmoe, --n-cpu-moe N` | keep the Mixture of Experts (MoE) weights of the first N layers in the CPU<br/>(env: LLAMA_ARG_N_CPU_MOE) |
|
||||
| `--moe-cache-mib N` | GPU cache size in MiB for the MoE experts kept in the CPU. with multiple GPUs, it is split among them like the layers (--tensor-split) (default: 0, disabled)<br/>(env: LLAMA_ARG_MOE_CACHE_MIB) |
|
||||
| `-ncffn, --n-cpu-ffn N` | keep the dense FFN weights of the first N layers in the CPU<br/>(dense models; for MoE expert weights use --n-cpu-moe)<br/>(env: LLAMA_ARG_N_CPU_FFN) |
|
||||
| `-ngl, --gpu-layers, --n-gpu-layers N` | max. number of layers to store in VRAM, either an exact number, 'auto', or 'all' (default: auto)<br/>(env: LLAMA_ARG_N_GPU_LAYERS) |
|
||||
| `-sm, --split-mode {none,layer,row,tensor}` | how to split the model across multiple GPUs, one of:<br/>- none: use one GPU only<br/>- layer (default): split layers and KV across GPUs (pipelined)<br/>- row: split weight across GPUs by rows (parallelized)<br/>- tensor: split weights and KV across GPUs (parallelized, EXPERIMENTAL)<br/>(env: LLAMA_ARG_SPLIT_MODE) |
|
||||
@@ -265,6 +266,7 @@ For the full list of features, please refer to [server's changelog](https://gith
|
||||
| `--spec-draft-p-split, --draft-p-split P` | speculative decoding split probability (default: 0.10)<br/>(env: LLAMA_ARG_SPEC_DRAFT_P_SPLIT) |
|
||||
| `--spec-draft-p-min, --draft-p-min P` | minimum speculative decoding probability (greedy) (default: 0.00)<br/>(env: LLAMA_ARG_SPEC_DRAFT_P_MIN) |
|
||||
| `--spec-draft-backend-sampling, --no-spec-draft-backend-sampling` | offload draft sampling to the backend (default: enabled)<br/>(env: LLAMA_ARG_SPEC_DRAFT_BACKEND_SAMPLING) |
|
||||
| `--spec-draft-sampling {greedy,probabilistic}` | how the draft is sampled: greedy takes its argmax, probabilistic samples it and has the target verify by rejection sampling (default: greedy)<br/>(env: LLAMA_ARG_SPEC_DRAFT_SAMPLING) |
|
||||
| `--spec-draft-device, -devd, --device-draft <dev1,dev2,..>` | comma-separated list of devices to use for offloading the draft model (none = don't offload, default: follows --device)<br/>use --list-devices to see a list of available devices |
|
||||
| `--spec-draft-ngl, -ngld, --gpu-layers-draft, --n-gpu-layers-draft N` | max. number of draft model layers to store in VRAM, either an exact number, 'auto', or 'all' (default: auto)<br/>(env: LLAMA_ARG_N_GPU_LAYERS_DRAFT) |
|
||||
| `--spec-draft-model, -md, --model-draft FNAME` | draft model for speculative decoding (default: unused)<br/>(env: LLAMA_ARG_SPEC_DRAFT_MODEL) |
|
||||
|
||||
@@ -2583,6 +2583,136 @@ private:
|
||||
cur.pos_max, cur.n_tokens, (float) cur.size() / 1024 / 1024);
|
||||
}
|
||||
|
||||
// checkpoints are appended to the slot save file, after the llama state payload
|
||||
// they cannot be recreated from the final state alone (a recurrent state cannot be rewound)
|
||||
static constexpr uint32_t SLOT_CKPT_MAGIC = 0x504b4353; // "SCKP"
|
||||
static constexpr uint32_t SLOT_CKPT_VERSION = 1;
|
||||
|
||||
static bool ckpt_read(std::ifstream & ifs, void * dst, size_t size, size_t & n_read) {
|
||||
if (!ifs.read((char *) dst, size)) {
|
||||
return false;
|
||||
}
|
||||
n_read += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ckpt_read_buf(std::ifstream & ifs, std::vector<uint8_t> & buf, size_t n_avail, size_t & n_read) {
|
||||
uint64_t n = 0;
|
||||
// check the size against the bytes left in the file before allocating, the size field may be corrupted
|
||||
if (!ckpt_read(ifs, &n, sizeof(n), n_read) || n > n_avail - n_read) {
|
||||
return false;
|
||||
}
|
||||
buf.resize(n);
|
||||
return n == 0 || ckpt_read(ifs, buf.data(), n, n_read);
|
||||
}
|
||||
|
||||
static void ckpt_write(std::ofstream & ofs, const void * src, size_t size, size_t & n_written) {
|
||||
ofs.write((const char *) src, size);
|
||||
n_written += size;
|
||||
}
|
||||
|
||||
static void ckpt_write_buf(std::ofstream & ofs, const std::vector<uint8_t> & buf, size_t & n_written) {
|
||||
const uint64_t n = buf.size();
|
||||
ckpt_write(ofs, &n, sizeof(n), n_written);
|
||||
if (n > 0) {
|
||||
ckpt_write(ofs, buf.data(), n, n_written);
|
||||
}
|
||||
}
|
||||
|
||||
// returns false if the appendix could not be written completely
|
||||
bool save_slot_checkpoints(const std::string & filepath, const server_slot & slot, size_t & n_written) const {
|
||||
n_written = 0;
|
||||
if (slot.prompt.checkpoints.empty()) {
|
||||
return true;
|
||||
}
|
||||
std::ofstream ofs(std::filesystem::u8path(filepath), std::ios::binary | std::ios::app);
|
||||
if (!ofs) {
|
||||
SLT_WRN(slot, "failed to append context checkpoints to '%s'\n", filepath.c_str());
|
||||
return false;
|
||||
}
|
||||
const uint32_t magic = SLOT_CKPT_MAGIC;
|
||||
const uint32_t version = SLOT_CKPT_VERSION;
|
||||
const uint32_t count = (uint32_t) slot.prompt.checkpoints.size();
|
||||
ckpt_write(ofs, &magic, sizeof(magic), n_written);
|
||||
ckpt_write(ofs, &version, sizeof(version), n_written);
|
||||
ckpt_write(ofs, &count, sizeof(count), n_written);
|
||||
for (const auto & cur : slot.prompt.checkpoints) {
|
||||
ckpt_write(ofs, &cur.n_tokens, sizeof(cur.n_tokens), n_written);
|
||||
ckpt_write(ofs, &cur.pos_min, sizeof(cur.pos_min), n_written);
|
||||
ckpt_write(ofs, &cur.pos_max, sizeof(cur.pos_max), n_written);
|
||||
ckpt_write_buf(ofs, cur.data_tgt, n_written);
|
||||
ckpt_write_buf(ofs, cur.data_dft, n_written);
|
||||
ckpt_write_buf(ofs, cur.data_spec, n_written);
|
||||
}
|
||||
ofs.flush();
|
||||
if (!ofs) {
|
||||
SLT_WRN(slot, "failed to append context checkpoints to '%s' - the appendix is incomplete\n", filepath.c_str());
|
||||
return false;
|
||||
}
|
||||
SLT_INF(slot, "appended %u context checkpoint(s) (%.3f MiB) to '%s'\n",
|
||||
count, (float) n_written / 1024 / 1024, filepath.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns the number of bytes consumed, 0 if there is no usable appendix
|
||||
size_t load_slot_checkpoints(const std::string & filepath, size_t offset, server_slot & slot) const {
|
||||
std::ifstream ifs(std::filesystem::u8path(filepath), std::ios::binary | std::ios::ate);
|
||||
const size_t file_size = ifs ? (size_t) ifs.tellg() : 0;
|
||||
if (!ifs || file_size < offset || !ifs.seekg(offset)) {
|
||||
return 0;
|
||||
}
|
||||
const size_t n_avail = file_size - offset; // bytes after the llama state payload
|
||||
size_t n_read = 0;
|
||||
uint32_t magic = 0;
|
||||
uint32_t version = 0;
|
||||
uint32_t count = 0;
|
||||
if (!ckpt_read(ifs, &magic, sizeof(magic), n_read) || magic != SLOT_CKPT_MAGIC) {
|
||||
return 0;
|
||||
}
|
||||
if (!ckpt_read(ifs, &version, sizeof(version), n_read) || version != SLOT_CKPT_VERSION ||
|
||||
!ckpt_read(ifs, &count, sizeof(count), n_read)) {
|
||||
SLT_WRN(slot, "invalid context checkpoint appendix in '%s' - ignored\n", filepath.c_str());
|
||||
return 0;
|
||||
}
|
||||
std::list<common_prompt_checkpoint> checkpoints;
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
common_prompt_checkpoint cur;
|
||||
cur.id_task = -1; // not created by a task - marks a checkpoint restored from a slot file
|
||||
if (!ckpt_read(ifs, &cur.n_tokens, sizeof(cur.n_tokens), n_read) ||
|
||||
!ckpt_read(ifs, &cur.pos_min, sizeof(cur.pos_min), n_read) ||
|
||||
!ckpt_read(ifs, &cur.pos_max, sizeof(cur.pos_max), n_read) ||
|
||||
!ckpt_read_buf(ifs, cur.data_tgt, n_avail, n_read) ||
|
||||
!ckpt_read_buf(ifs, cur.data_dft, n_avail, n_read) ||
|
||||
!ckpt_read_buf(ifs, cur.data_spec, n_avail, n_read)) {
|
||||
SLT_WRN(slot, "truncated context checkpoint appendix in '%s' - ignored\n", filepath.c_str());
|
||||
return 0;
|
||||
}
|
||||
// a saved checkpoint always holds a target state - an empty blob would roll back without restoring anything
|
||||
if (cur.data_tgt.empty()) {
|
||||
SLT_WRN(slot, "invalid context checkpoint appendix in '%s' - ignored\n", filepath.c_str());
|
||||
return 0;
|
||||
}
|
||||
checkpoints.push_back(std::move(cur));
|
||||
if (checkpoints.size() > (size_t) params_base.n_ctx_checkpoints) {
|
||||
checkpoints.pop_front();
|
||||
}
|
||||
}
|
||||
// the slot file does not check the draft context - test-load one draft checkpoint, drop the draft data if it does not fit
|
||||
if (ctx_dft != nullptr && !checkpoints.empty() && !checkpoints.back().data_dft.empty()) {
|
||||
const bool ok = checkpoints.back().load_dft(ctx_dft, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
llama_memory_seq_rm(llama_get_memory(ctx_dft), slot.id, -1, -1);
|
||||
if (!ok) {
|
||||
SLT_WRN(slot, "draft context checkpoint data in '%s' does not match the draft context - dropped\n", filepath.c_str());
|
||||
for (auto & cur : checkpoints) {
|
||||
cur.clear_dft();
|
||||
}
|
||||
}
|
||||
}
|
||||
slot.prompt.checkpoints = std::move(checkpoints);
|
||||
SLT_INF(slot, "restored %zu context checkpoint(s) from '%s'\n", slot.prompt.checkpoints.size(), filepath.c_str());
|
||||
return n_read;
|
||||
}
|
||||
|
||||
// returns false to decline the task, it is offered again after the decode is done
|
||||
bool process_single_task(server_task && task, bool is_yielding) {
|
||||
// while yielding, an encode / decode is running and only reading the server state is safe
|
||||
@@ -2792,6 +2922,12 @@ private:
|
||||
break;
|
||||
}
|
||||
|
||||
size_t nwrite_ckpt = 0;
|
||||
if (!save_slot_checkpoints(filepath, *slot, nwrite_ckpt)) {
|
||||
send_error(task, "Unable to save slot: incomplete context checkpoints", ERROR_TYPE_SERVER);
|
||||
break;
|
||||
}
|
||||
|
||||
const int64_t t_end = ggml_time_us();
|
||||
const double t_save_ms = (t_end - t_start) / 1000.0;
|
||||
|
||||
@@ -2801,7 +2937,7 @@ private:
|
||||
res->filename = filename;
|
||||
res->is_save = true;
|
||||
res->n_tokens = slot->prompt.tokens.size();
|
||||
res->n_bytes = nwrite;
|
||||
res->n_bytes = nwrite + nwrite_ckpt;
|
||||
res->t_ms = t_save_ms;
|
||||
queue_results.send(std::move(res));
|
||||
} break;
|
||||
@@ -2857,6 +2993,9 @@ private:
|
||||
break;
|
||||
}
|
||||
|
||||
// nread is the end offset of the llama state payload within the file
|
||||
const size_t nread_ckpt = load_slot_checkpoints(filepath, nread, *slot);
|
||||
|
||||
const int64_t t_end = ggml_time_us();
|
||||
const double t_restore_ms = (t_end - t_start) / 1000.0;
|
||||
|
||||
@@ -2866,7 +3005,7 @@ private:
|
||||
res->filename = filename;
|
||||
res->is_save = false;
|
||||
res->n_tokens = slot->prompt.tokens.size();
|
||||
res->n_bytes = nread;
|
||||
res->n_bytes = nread + nread_ckpt;
|
||||
res->t_ms = t_restore_ms;
|
||||
queue_results.send(std::move(res));
|
||||
} break;
|
||||
@@ -3278,7 +3417,7 @@ private:
|
||||
|
||||
if (ctx_dft) {
|
||||
if (use_ckpt_dft) {
|
||||
ckpt.load_dft(ctx_dft, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
GGML_ASSERT(ckpt.load_dft(ctx_dft, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY));
|
||||
}
|
||||
|
||||
if (!llama_memory_seq_rm(llama_get_memory(ctx_dft), slot.id, ckpt.pos_max + 1, -1)) {
|
||||
@@ -3604,8 +3743,18 @@ private:
|
||||
|
||||
if (!do_reset) {
|
||||
// restore the context checkpoint
|
||||
it->load_tgt(ctx_tgt, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
it->load_dft(ctx_dft, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
if (!it->load_tgt(ctx_tgt, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY) ||
|
||||
!it->load_dft(ctx_dft, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY)) {
|
||||
if (it->id_task != -1) {
|
||||
GGML_ABORT("failed to restore context checkpoint\n");
|
||||
}
|
||||
// restored from a slot file, not guaranteed to load - fall back to full prompt re-processing
|
||||
SLT_WRN(slot, "%s", "failed to load context checkpoint restored from a slot file\n");
|
||||
do_reset = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!do_reset) {
|
||||
// restore the draft's speculative state
|
||||
common_speculative_set_state(spec.get(), slot.id, it->data_spec);
|
||||
|
||||
@@ -4300,10 +4449,10 @@ private:
|
||||
|
||||
SLT_DBG(slot, "restoring speculative checkpoint (pos_min = %d, pos_max = %d, size = %zu)\n", ckpt.pos_min, ckpt.pos_max, ckpt.size());
|
||||
|
||||
ckpt.load_tgt(slot.ctx_tgt, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
GGML_ASSERT(ckpt.load_tgt(slot.ctx_tgt, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY));
|
||||
|
||||
if (slot.ctx_dft) {
|
||||
ckpt.load_dft(slot.ctx_dft, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
GGML_ASSERT(ckpt.load_dft(slot.ctx_dft, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY));
|
||||
}
|
||||
|
||||
slot.mem.seq_rm(slot.id, ckpt.pos_max + 1, -1);
|
||||
|
||||
@@ -37,6 +37,8 @@ def test_slot_save_restore():
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert res.body["n_saved"] == 84
|
||||
slot_file = os.path.join(server.slot_save_path, "slot1.bin")
|
||||
assert res.body["n_written"] == os.path.getsize(slot_file)
|
||||
|
||||
# Since we have cache, this should only process the last tokens
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
@@ -54,6 +56,7 @@ def test_slot_save_restore():
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert res.body["n_restored"] == 84
|
||||
assert res.body["n_read"] == os.path.getsize(slot_file)
|
||||
|
||||
# Since we have cache, slot 0 should only process the last tokens
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
@@ -546,3 +549,243 @@ def test_slot_restore_media_file_without_mmproj(mmproj_server):
|
||||
assert res.status_code == 200
|
||||
assert res.body["timings"]["cache_n"] == 0
|
||||
assert res.body["content"] == content
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def swa_server():
|
||||
swa = ServerPreset.tinygemma3()
|
||||
swa.slot_save_path = "./tmp"
|
||||
swa.temperature = 0.0
|
||||
swa.cache_ram = 0
|
||||
# Keep the first prompt checkpoint before the divergence point.
|
||||
swa.n_ubatch = 32
|
||||
return swa
|
||||
|
||||
|
||||
# the non-ASCII name checks that the appendix lands in the same file as the llama state on Windows
|
||||
@pytest.mark.parametrize("filename", ["ckpt_slot1.bin", "ckpt_slot1_é.bin"])
|
||||
def test_slot_restore_preserves_context_checkpoints(swa_server, filename):
|
||||
server = swa_server
|
||||
server.start()
|
||||
|
||||
base = "The quick brown fox jumps over the lazy dog. " * 20
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": base + "The first ending of this story is a happy one.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
n_full = res.body["timings"]["prompt_n"]
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": base + "But the second ending was different and sad.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
n_live = res.body["timings"]["prompt_n"]
|
||||
assert n_live < n_full
|
||||
|
||||
res = server.make_request("POST", "/slots/1?action=erase")
|
||||
assert res.status_code == 200
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": base + "The first ending of this story is a happy one.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
|
||||
res = server.make_request("POST", "/slots/1?action=save", data={
|
||||
"filename": filename,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert res.body["n_saved"] > 0
|
||||
ckpt_file = os.path.join(server.slot_save_path, filename)
|
||||
assert res.body["n_written"] == os.path.getsize(ckpt_file)
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": "Unrelated text with no common prefix occupies the slot now.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
|
||||
res = server.make_request("POST", "/slots/1?action=restore", data={
|
||||
"filename": filename,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert res.body["n_read"] == os.path.getsize(ckpt_file)
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": base + "But the second ending was different and sad.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert res.body["timings"]["prompt_n"] == n_live
|
||||
|
||||
|
||||
# checkpoint appendix: magic(4) version(4) count(4), then per checkpoint
|
||||
# n_tokens(8) pos_min(4) pos_max(4) and three blobs (target, draft, speculative), each size(8) + data
|
||||
def parse_ckpt_appendix(data):
|
||||
off = data.find(struct.pack("<II", 0x504b4353, 1))
|
||||
assert off > 0
|
||||
count = struct.unpack_from("<I", data, off + 8)[0]
|
||||
ckpts = []
|
||||
pos = off + 12
|
||||
for _ in range(count):
|
||||
start = pos
|
||||
pos += 16
|
||||
blobs = []
|
||||
for _ in range(3):
|
||||
n = struct.unpack_from("<Q", data, pos)[0]
|
||||
blobs.append(pos + 8)
|
||||
pos += 8 + n
|
||||
ckpts.append((start, pos, blobs[0]))
|
||||
assert pos == len(data)
|
||||
return off, ckpts
|
||||
|
||||
|
||||
# a damaged appendix must be ignored, or its checkpoints dropped when they fail to load, without aborting the server
|
||||
@pytest.mark.parametrize("damage", ["oversized_blob", "empty_target", "corrupt_state", "many_checkpoints"])
|
||||
def test_slot_restore_damaged_checkpoint_appendix(swa_server, damage):
|
||||
server = swa_server
|
||||
server.start()
|
||||
|
||||
base = "The quick brown fox jumps over the lazy dog. " * 20
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": base + "The first ending of this story is a happy one.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": base + "But the second ending was different and sad.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
n_live = res.body["timings"]["prompt_n"]
|
||||
|
||||
res = server.make_request("POST", "/slots/1?action=erase")
|
||||
assert res.status_code == 200
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": base + "The first ending of this story is a happy one.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
|
||||
res = server.make_request("POST", "/slots/1?action=save", data={
|
||||
"filename": "ckpt_damaged.bin",
|
||||
})
|
||||
assert res.status_code == 200
|
||||
|
||||
path = os.path.join(server.slot_save_path, "ckpt_damaged.bin")
|
||||
with open(path, "rb") as f:
|
||||
data = bytearray(f.read())
|
||||
off, ckpts = parse_ckpt_appendix(data)
|
||||
|
||||
if damage == "oversized_blob":
|
||||
# the first target blob declares a size that cannot be allocated, it must be rejected before allocating
|
||||
data = data[:ckpts[0][0] + 16] + struct.pack("<Q", 1 << 62)
|
||||
elif damage == "empty_target":
|
||||
# the target blobs are removed and their size set to 0, a valid save never writes an empty target state
|
||||
for start, end, tgt in reversed(ckpts):
|
||||
size = struct.unpack_from("<Q", data, tgt - 8)[0]
|
||||
data = data[:tgt - 8] + struct.pack("<Q", 0) + data[tgt + size:]
|
||||
elif damage == "corrupt_state":
|
||||
# the sizes are intact, but the target states do not load
|
||||
for _, _, tgt in ckpts:
|
||||
struct.pack_into("<I", data, tgt, 0xdeadbeef)
|
||||
else:
|
||||
# more than 1024 entries: one-byte fillers that never match go first, the real checkpoints stay last
|
||||
filler = struct.pack("<qiiQBQQ", 0, 0, 1 << 30, 1, 0, 0, 0)
|
||||
data = data[:off + 12] + filler * (1025 - len(ckpts)) + data[off + 12:]
|
||||
struct.pack_into("<I", data, off + 8, 1025)
|
||||
|
||||
with open(path, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
res = server.make_request("POST", "/slots/1?action=restore", data={
|
||||
"filename": "ckpt_damaged.bin",
|
||||
})
|
||||
assert res.status_code == 200
|
||||
if damage in ("oversized_blob", "empty_target"):
|
||||
assert res.body["n_read"] == off
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": base + "But the second ending was different and sad.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
if damage == "many_checkpoints":
|
||||
assert res.body["timings"]["prompt_n"] == n_live
|
||||
else:
|
||||
assert res.body["timings"]["prompt_n"] > n_live
|
||||
|
||||
|
||||
# the draft blobs of the checkpoint appendix are not covered by the main payload checks,
|
||||
# so restoring into a server with another draft KV cache type must not abort
|
||||
@pytest.mark.parametrize("ctkd_restore", ["f16", "q8_0"])
|
||||
def test_slot_restore_checkpoints_draft_kv_type_change(swa_server, ctkd_restore):
|
||||
server = swa_server
|
||||
server.model_draft_hf_repo = "ggml-org/tinygemma3-GGUF:Q8_0" # same file as the target, already in the HF cache
|
||||
server.spec_type = "draft-simple"
|
||||
server.ctkd = "f16"
|
||||
server.start()
|
||||
|
||||
base = "The quick brown fox jumps over the lazy dog. " * 20
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": base + "The first ending of this story is a happy one.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": base + "But the second ending was different and sad.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
n_live = res.body["timings"]["prompt_n"]
|
||||
|
||||
res = server.make_request("POST", "/slots/1?action=erase")
|
||||
assert res.status_code == 200
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": base + "The first ending of this story is a happy one.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
|
||||
res = server.make_request("POST", "/slots/1?action=save", data={
|
||||
"filename": "ckpt_draft_slot1.bin",
|
||||
})
|
||||
assert res.status_code == 200
|
||||
|
||||
server.stop()
|
||||
server.ctkd = ctkd_restore
|
||||
server.start()
|
||||
|
||||
res = server.make_request("POST", "/slots/1?action=restore", data={
|
||||
"filename": "ckpt_draft_slot1.bin",
|
||||
})
|
||||
assert res.status_code == 200
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": base + "But the second ending was different and sad.",
|
||||
"id_slot": 1,
|
||||
"cache_prompt": True,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert res.body["timings"]["prompt_n"] == n_live
|
||||
|
||||
@@ -65,6 +65,7 @@ class ServerProcess:
|
||||
model_url: str | None = None
|
||||
model_file: str | None = None
|
||||
model_draft: str | None = None
|
||||
model_draft_hf_repo: str | None = None
|
||||
n_threads: int | None = None
|
||||
n_gpu_layer: int | None = None
|
||||
n_batch: int | None = None
|
||||
@@ -80,6 +81,7 @@ class ServerProcess:
|
||||
n_slots: int | None = None
|
||||
ctk: str | None = None
|
||||
ctv: str | None = None
|
||||
ctkd: str | None = None
|
||||
fa: str | None = None
|
||||
server_continuous_batching: bool | None = False
|
||||
server_embeddings: bool | None = False
|
||||
@@ -171,6 +173,8 @@ class ServerProcess:
|
||||
server_args.extend(["--model-url", self.model_url])
|
||||
if self.model_draft:
|
||||
server_args.extend(["--model-draft", self.model_draft])
|
||||
if self.model_draft_hf_repo:
|
||||
server_args.extend(["--hf-repo-draft", self.model_draft_hf_repo])
|
||||
if self.model_hf_repo:
|
||||
server_args.extend(["--hf-repo", self.model_hf_repo])
|
||||
if self.model_hf_file:
|
||||
@@ -221,6 +225,8 @@ class ServerProcess:
|
||||
server_args.extend(["-ctk", self.ctk])
|
||||
if self.ctv:
|
||||
server_args.extend(["-ctv", self.ctv])
|
||||
if self.ctkd:
|
||||
server_args.extend(["-ctkd", self.ctkd])
|
||||
if self.fa is not None:
|
||||
server_args.extend(["-fa", self.fa])
|
||||
if self.n_predict:
|
||||
|
||||
@@ -283,10 +283,12 @@ class SettingsStore {
|
||||
*/
|
||||
syncWithServerDefaults(): void {
|
||||
const propsDefaults = this.getServerDefaults();
|
||||
|
||||
if (Object.keys(propsDefaults).length === 0) return;
|
||||
|
||||
const uiSettings = serverStore.uiSettings;
|
||||
|
||||
// a router main instance reports no sampling defaults, but its
|
||||
// ui_settings still need the first visit pass below
|
||||
if (Object.keys(propsDefaults).length === 0 && !(uiSettings && this.isFirstVisit)) return;
|
||||
|
||||
const uiSettingsKeys = new Set(uiSettings ? Object.keys(uiSettings) : []);
|
||||
|
||||
for (const [key, propsValue] of Object.entries(propsDefaults)) {
|
||||
|
||||
@@ -3,12 +3,15 @@ import { serverStore } from '$lib/stores/server.svelte';
|
||||
import { settingsStore } from '$lib/stores/settings/index.svelte';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
function mockProps(uiSettings: Record<string, string | number | boolean>) {
|
||||
function mockProps(
|
||||
uiSettings: Record<string, string | number | boolean>,
|
||||
params: Record<string, number> = { temperature: 0.8 }
|
||||
) {
|
||||
Object.defineProperty(serverStore, 'props', {
|
||||
configurable: true,
|
||||
get: () =>
|
||||
({
|
||||
default_generation_settings: { params: { temperature: 0.8 } },
|
||||
default_generation_settings: { params },
|
||||
ui_settings: uiSettings
|
||||
}) as unknown as typeof serverStore.props
|
||||
});
|
||||
@@ -28,6 +31,16 @@ describe('server ui_settings application semantics', () => {
|
||||
expect(settingsStore.config.theme).toBe('dark');
|
||||
});
|
||||
|
||||
it('applies the admin defaults when /props carries no sampling defaults', () => {
|
||||
settingsStore.initialize();
|
||||
// router mode: the main instance answers /props with empty params
|
||||
mockProps({ theme: 'dark' }, {});
|
||||
|
||||
settingsStore.syncWithServerDefaults();
|
||||
|
||||
expect(settingsStore.config.theme).toBe('dark');
|
||||
});
|
||||
|
||||
it('never reapplies on later loads: the user config diverges freely', () => {
|
||||
settingsStore.initialize();
|
||||
settingsStore.updateConfig('theme', 'light');
|
||||
|
||||
Reference in New Issue
Block a user