Skip to content

Commit e79ffe3

Browse files
roberteg16claude
andcommitted
ggml-cuda: pad matmul weight rows to avoid cache-set aliasing
Weight matrices whose packed row size is a multiple of 2048 bytes map every row onto the same L2 cache sets, so a matmul walking down a column thrashes a single set. On RDNA3.5 this shows up as lost bandwidth in the FFN and attention GEMMs. Add one cache line (128 B) to the row stride of such weights at buffer-init time so consecutive rows land in different sets. get_alloc_size reserves the extra bytes, and the loader uploads the packed file data with a strided 2D copy since the destination rows are no longer adjacent. The gaps are zeroed so they cannot hold NaNs. Quantized weights are excluded: 128 is not a multiple of their block size and would misalign the block-indexed kernels. The matmul paths need no change -- cuBLAS, mmf and mmvf all take the leading dimension from nb[1] already. The loader flags the eligible tensors with GGML_TENSOR_FLAG_PAD_ROWS, reusing llm_tensor_info_for() to tell a matmul weight from a bias or a norm; the token-embedding/output duplication rule it shares with the buffer-type selection is factored into resolve_tn_tensor() so the two cannot drift. Gated to RDNA3.5; disable with GGML_CUDA_NO_PAD_WEIGHTS. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 3c53e42 commit e79ffe3

3 files changed

Lines changed: 86 additions & 17 deletions

File tree

ggml/include/ggml.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -648,11 +648,12 @@ extern "C" {
648648

649649
// this tensor...
650650
enum ggml_tensor_flag {
651-
GGML_TENSOR_FLAG_INPUT = 1, // ...is an input for the GGML compute graph
652-
GGML_TENSOR_FLAG_OUTPUT = 2, // ...is an output for the GGML compute graph
653-
GGML_TENSOR_FLAG_PARAM = 4, // ...contains trainable parameters
654-
GGML_TENSOR_FLAG_LOSS = 8, // ...defines loss for numerical optimization (multiple loss tensors add up)
655-
GGML_TENSOR_FLAG_COMPUTE = 16, // ...must be computed
651+
GGML_TENSOR_FLAG_INPUT = 1, // ...is an input for the GGML compute graph
652+
GGML_TENSOR_FLAG_OUTPUT = 2, // ...is an output for the GGML compute graph
653+
GGML_TENSOR_FLAG_PARAM = 4, // ...contains trainable parameters
654+
GGML_TENSOR_FLAG_LOSS = 8, // ...defines loss for numerical optimization (multiple loss tensors add up)
655+
GGML_TENSOR_FLAG_COMPUTE = 16, // ...must be computed
656+
GGML_TENSOR_FLAG_PAD_ROWS = 32, // ...is a matmul weight whose row stride a backend may pad to avoid cache-set aliasing
656657
};
657658

658659
enum ggml_tri_type {

ggml/src/ggml-cuda/ggml-cuda.cu

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,33 @@ static void * ggml_backend_cuda_buffer_get_base(ggml_backend_buffer_t buffer) {
761761
return ctx->dev_ptr;
762762
}
763763

764+
#define GGML_CUDA_ROW_ALIAS_STRIDE 2048 // rows of this size, or a multiple of it, map to the same cache sets
765+
#define GGML_CUDA_ROW_PAD 128 // one cache line, added to the row stride to break the aliasing
766+
767+
// quantized weights are excluded: GGML_CUDA_ROW_PAD is not a multiple of their block size and would
768+
// misalign the block-indexed matmul kernels
769+
static bool ggml_cuda_should_pad_weight(const ggml_tensor * tensor, int device) {
770+
static const bool padding_disabled = getenv("GGML_CUDA_NO_PAD_WEIGHTS") != nullptr;
771+
772+
if (padding_disabled || !(tensor->flags & GGML_TENSOR_FLAG_PAD_ROWS)) {
773+
return false;
774+
}
775+
776+
if (!GGML_CUDA_CC_IS_RDNA3_5(ggml_cuda_info().devices[device].cc)) {
777+
return false;
778+
}
779+
780+
if (tensor->view_src != nullptr || ggml_is_quantized(tensor->type) || tensor->ne[1] <= 1) {
781+
return false;
782+
}
783+
784+
return ggml_row_size(tensor->type, tensor->ne[0]) % GGML_CUDA_ROW_ALIAS_STRIDE == 0;
785+
}
786+
787+
static size_t ggml_cuda_padded_row_size(const ggml_tensor * tensor) {
788+
return ggml_row_size(tensor->type, tensor->ne[0]) + GGML_CUDA_ROW_PAD;
789+
}
790+
764791
static enum ggml_status ggml_backend_cuda_buffer_init_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor) {
765792
ggml_backend_cuda_buffer_context * ctx = (ggml_backend_cuda_buffer_context *)buffer->context;
766793

@@ -769,6 +796,17 @@ static enum ggml_status ggml_backend_cuda_buffer_init_tensor(ggml_backend_buffer
769796
return GGML_STATUS_SUCCESS;
770797
}
771798

799+
if (ggml_cuda_should_pad_weight(tensor, ctx->device)) {
800+
tensor->nb[1] = ggml_cuda_padded_row_size(tensor);
801+
tensor->nb[2] = tensor->nb[1]*tensor->ne[1];
802+
tensor->nb[3] = tensor->nb[2]*tensor->ne[2];
803+
804+
// the gaps between rows are never written, initialize them to 0 to avoid possible NaN values
805+
ggml_cuda_set_device(ctx->device);
806+
CUDA_CHECK(cudaMemset(tensor->data, 0, ggml_backend_buft_get_alloc_size(buffer->buft, tensor)));
807+
return GGML_STATUS_SUCCESS;
808+
}
809+
772810
if (ggml_is_quantized(tensor->type) && tensor->view_src == nullptr && ggml_backend_buffer_get_usage(buffer) != GGML_BACKEND_BUFFER_USAGE_COMPUTE) {
773811
// initialize padding to 0 to avoid possible NaN values
774812
const size_t original_size = ggml_nbytes(tensor);
@@ -929,6 +967,11 @@ static size_t ggml_backend_cuda_buffer_type_get_alloc_size(ggml_backend_buffer_t
929967
}
930968
}
931969

970+
// reserve room for the row stride that ggml_backend_cuda_buffer_init_tensor will assign
971+
if (ggml_cuda_should_pad_weight(tensor, buft_ctx->device)) {
972+
size = ggml_cuda_padded_row_size(tensor)*ggml_nrows(tensor);
973+
}
974+
932975
return size;
933976
}
934977

src/llama-model-loader.cpp

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,17 @@ static ggml_backend_buffer_type_t select_weight_buft(const llama_hparams & hpara
10381038
return nullptr;
10391039
}
10401040

1041+
// some models use the token embedding tensor as the output, but since these are used in different layers and with different ops
1042+
// the tensor is duplicated
1043+
// to handle this, we check if the tensor is duplicated, and if so, we assume that it is being loaded as the output tensor
1044+
static llm_tensor resolve_tn_tensor(const LLM_TN_IMPL & tn, int flags) {
1045+
if (tn.tensor == LLM_TENSOR_TOKEN_EMBD && (flags & llama_model_loader::TENSOR_DUPLICATED)) {
1046+
return LLM_TENSOR_OUTPUT;
1047+
}
1048+
1049+
return tn.tensor;
1050+
}
1051+
10411052
struct ggml_tensor * llama_model_loader::create_tensor(
10421053
const llama_hparams & hparams, const buft_list_t * buft_list_cpu, const buft_list_t * buft_list_input, const buft_list_t * buft_list_output,
10431054
const buft_list_t * buft_list_layer, const LLM_TN_IMPL & tn, const std::initializer_list<int64_t> & ne, int flags) {
@@ -1079,17 +1090,9 @@ struct ggml_tensor * llama_model_loader::create_tensor(
10791090
throw std::runtime_error(format("missing tensor '%s'", tn.str().c_str()));
10801091
}
10811092

1082-
// some models use the token embedding tensor as the output, but since these are used in different layers and with different ops
1083-
// the tensor is duplicated
1084-
// to handle this, we check if the tensor is duplicated, and if so, we assume that it is being loaded as the output tensor
1085-
llm_tensor tn_tensor = tn.tensor;
1086-
if (tn.tensor == LLM_TENSOR_TOKEN_EMBD && (flags & TENSOR_DUPLICATED)) {
1087-
tn_tensor = LLM_TENSOR_OUTPUT;
1088-
}
1089-
10901093
llm_tensor_info info;
10911094
try {
1092-
info = llm_tensor_info_for(tn_tensor);
1095+
info = llm_tensor_info_for(resolve_tn_tensor(tn, flags));
10931096
} catch (const std::out_of_range & e) {
10941097
throw std::runtime_error(format("missing tensor info mapping for %s", tn.str().c_str()));
10951098
}
@@ -1271,6 +1274,16 @@ struct ggml_tensor * llama_model_loader::create_tensor(
12711274
struct ggml_tensor * tensor = ggml_dup_tensor(ctx, cur);
12721275
ggml_set_name(tensor, ggml_get_name(cur));
12731276

1277+
// tensors with a "weight" suffix are used as the src0 of the op that they map to; flag the ones
1278+
// that feed a matrix multiplication so that backends may pad their row stride
1279+
const bool is_weight = tn.suffix == nullptr || strcmp(tn.suffix, "weight") == 0;
1280+
if (is_weight) {
1281+
const ggml_op op = llm_tensor_info_for(resolve_tn_tensor(tn, flags)).op;
1282+
if (op == GGML_OP_MUL_MAT || op == GGML_OP_MUL_MAT_ID) {
1283+
tensor->flags |= GGML_TENSOR_FLAG_PAD_ROWS;
1284+
}
1285+
}
1286+
12741287
if (duplicated) {
12751288
size_data += ggml_nbytes(cur);
12761289
} else {
@@ -1527,7 +1540,13 @@ bool llama_model_loader::load_all_data(
15271540
}
15281541
}
15291542

1530-
size_t n_size = ggml_nbytes(cur);
1543+
// the data in the file is packed, while the destination tensor may have a padded row
1544+
// stride, in which case the rows are uploaded with a strided 2D copy
1545+
const size_t packed_row_size = ggml_row_size(cur->type, cur->ne[0]);
1546+
const int64_t n_rows = ggml_nelements(cur) / cur->ne[0];
1547+
const bool row_padded = cur->nb[1] != packed_row_size;
1548+
1549+
const size_t n_size = row_padded ? packed_row_size*n_rows : ggml_nbytes(cur);
15311550

15321551
if (use_mmap) {
15331552
const auto & mapping = mappings.at(weight->idx);
@@ -1554,6 +1573,8 @@ bool llama_model_loader::load_all_data(
15541573
auto & mmap_used = mmaps_used[weight->idx];
15551574
mmap_used.first = std::min(mmap_used.first, weight->offs);
15561575
mmap_used.second = std::max(mmap_used.second, weight->offs + n_size);
1576+
} else if (row_padded) {
1577+
ggml_backend_tensor_set_2d(cur, data, 0, packed_row_size, n_rows, cur->nb[1], packed_row_size);
15571578
} else {
15581579
ggml_backend_tensor_set(cur, data, 0, n_size);
15591580
}
@@ -1570,7 +1591,7 @@ bool llama_model_loader::load_all_data(
15701591
}
15711592
} else {
15721593
// If upload_backend is valid load the tensor in chunks to pinned memory and upload the buffers asynchronously to the GPU.
1573-
if (upload_backend) {
1594+
if (upload_backend && !row_padded) {
15741595
size_t offset = weight->offs;
15751596
alignment = file->read_alignment();
15761597
size_t aligned_offset = offset & ~(alignment - 1);
@@ -1626,7 +1647,11 @@ bool llama_model_loader::load_all_data(
16261647
read_buf.resize(n_size);
16271648
file->seek(weight->offs, SEEK_SET);
16281649
file->read_raw(read_buf.data(), n_size);
1629-
ggml_backend_tensor_set(cur, read_buf.data(), 0, n_size);
1650+
if (row_padded) {
1651+
ggml_backend_tensor_set_2d(cur, read_buf.data(), 0, packed_row_size, n_rows, cur->nb[1], packed_row_size);
1652+
} else {
1653+
ggml_backend_tensor_set(cur, read_buf.data(), 0, n_size);
1654+
}
16301655
if (check_tensors && !ggml_validate_row_data(cur->type, read_buf.data(), n_size)) {
16311656
throw std::runtime_error(format("tensor '%s' has invalid data", ggml_get_name(cur)));
16321657
}

0 commit comments

Comments
 (0)