Skip to content

Commit 7e03b88

Browse files
authored
Merge pull request #78 from AMD-Ecosystem/rogarcia.padd-gemm-weight-rows-to-avoid-cache-set
ggml-cuda: pad matmul weight rows to avoid cache-set aliasing
2 parents 3c53e42 + 0f6fe20 commit 7e03b88

4 files changed

Lines changed: 162 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/common.cuh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,15 @@ struct ggml_cuda_type_traits<GGML_TYPE_IQ3_S> {
11261126

11271127
//////////////////////
11281128

1129+
// Row-stride padding for matrix multiplication weights. A weight whose packed row size is a
1130+
// multiple of alias_stride puts every row in the same cache sets, so pad bytes are added to the
1131+
// row stride to break the aliasing. Both values follow from the cache geometry (line size times
1132+
// number of sets), which neither CUDA nor HIP report, so they are tabulated per architecture.
1133+
struct ggml_cuda_row_pad_params {
1134+
size_t alias_stride; // packed row sizes that are a multiple of this alias in the cache
1135+
size_t pad; // bytes added to the row stride; 0 disables the padding
1136+
};
1137+
11291138
struct ggml_cuda_device_info {
11301139
int device_count; // number of (possibly virtual) devices exposed to the rest of ggml
11311140
int physical_device_count; // number of physical CUDA devices actually present
@@ -1144,6 +1153,7 @@ struct ggml_cuda_device_info {
11441153
int physical_device; // backing physical CUDA device for this (virtual) device
11451154
int physical_share_count; // number of (virtual) devices sharing this device's physical GPU
11461155
int virtual_index; // index of this (virtual) device among those sharing its physical GPU
1156+
ggml_cuda_row_pad_params row_pad; // weight row-stride padding
11471157
};
11481158

11491159
cuda_device_info devices[GGML_CUDA_MAX_DEVICES] = {};

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

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,71 @@ static int ggml_cuda_parse_id(char devName[]) {
220220
}
221221
#endif // defined(GGML_USE_HIP)
222222

223+
// Weight row-stride padding per architecture. An architecture is opted in only once the padding
224+
// has been measured on it; every other one gets {0, 0} and keeps packed rows.
225+
static ggml_cuda_row_pad_params ggml_cuda_row_pad_params_for(int cc) {
226+
if (GGML_CUDA_CC_IS_RDNA3_5(cc)) {
227+
return { 2048, 128 }; // one cache line added to rows that alias every 2 KiB
228+
}
229+
230+
return { 0, 0 };
231+
}
232+
233+
// Returns the environment override for name, or -1 when it is unset or not a number.
234+
static int64_t ggml_cuda_row_pad_env(const char * name) {
235+
const char * val = getenv(name);
236+
if (!val || !val[0]) {
237+
return -1;
238+
}
239+
240+
char * end = nullptr;
241+
const int64_t parsed = strtoll(val, &end, 0);
242+
if (*end != '\0' || parsed < 0) {
243+
GGML_LOG_WARN("%s: ignoring %s=%s, expected a non-negative integer\n", __func__, name, val);
244+
return -1;
245+
}
246+
247+
return parsed;
248+
}
249+
250+
// Resolves the padding for one device: the architecture default, then the environment overrides,
251+
// then the sanity checks. Runs once per device so an invalid setting warns once, not per tensor.
252+
static ggml_cuda_row_pad_params ggml_cuda_resolve_row_pad(int cc) {
253+
if (getenv("GGML_CUDA_NO_PAD_WEIGHTS")) {
254+
return { 0, 0 };
255+
}
256+
257+
ggml_cuda_row_pad_params params = ggml_cuda_row_pad_params_for(cc);
258+
259+
const int64_t alias_stride_override = ggml_cuda_row_pad_env("GGML_CUDA_ROW_ALIAS_STRIDE");
260+
const int64_t pad_override = ggml_cuda_row_pad_env("GGML_CUDA_ROW_PAD");
261+
if (alias_stride_override >= 0) {
262+
params.alias_stride = alias_stride_override;
263+
}
264+
if (pad_override >= 0) {
265+
params.pad = pad_override;
266+
}
267+
268+
if (params.pad == 0) {
269+
return { 0, 0 };
270+
}
271+
272+
if (params.alias_stride == 0 || (params.alias_stride & (params.alias_stride - 1)) != 0) {
273+
GGML_LOG_WARN("%s: disabling weight row padding, alias stride %zu is not a power of two\n",
274+
__func__, params.alias_stride);
275+
return { 0, 0 };
276+
}
277+
278+
// a pad that is itself a multiple of the alias stride leaves the rows aliasing
279+
if (params.pad % params.alias_stride == 0) {
280+
GGML_LOG_WARN("%s: disabling weight row padding, pad %zu is a multiple of alias stride %zu\n",
281+
__func__, params.pad, params.alias_stride);
282+
return { 0, 0 };
283+
}
284+
285+
return params;
286+
}
287+
223288
static ggml_cuda_device_info ggml_cuda_init() {
224289
ggml_cuda_device_info info = {};
225290

@@ -376,6 +441,8 @@ static ggml_cuda_device_info ggml_cuda_init() {
376441
}
377442

378443
#endif // defined(GGML_USE_HIP)
444+
445+
info.devices[id].row_pad = ggml_cuda_resolve_row_pad(info.devices[id].cc);
379446
}
380447

381448
if (ggml_cuda_highest_compiled_arch(GGML_CUDA_CC_TURING) >= GGML_CUDA_CC_TURING && !turing_devices_without_mma.empty()) {
@@ -761,6 +828,32 @@ static void * ggml_backend_cuda_buffer_get_base(ggml_backend_buffer_t buffer) {
761828
return ctx->dev_ptr;
762829
}
763830

831+
// Quantized weights are excluded: the matmul kernels read the row stride as nb[1]/type_size, an
832+
// exact division that a pad which is not a multiple of the block size would break.
833+
static bool ggml_cuda_should_pad_weight(const ggml_tensor * tensor, int device) {
834+
const ggml_cuda_row_pad_params & row_pad = ggml_cuda_info().devices[device].row_pad;
835+
836+
if (row_pad.pad == 0 || !(tensor->flags & GGML_TENSOR_FLAG_PAD_ROWS)) {
837+
return false;
838+
}
839+
840+
if (tensor->view_src != nullptr || ggml_is_quantized(tensor->type) || tensor->ne[1] <= 1) {
841+
return false;
842+
}
843+
844+
// ggml_cuda_should_use_mmf and ggml_cuda_should_use_mmvf reject a src0 whose strides are not a
845+
// multiple of 2*type_size, so a pad that misses this would silently cost those kernels
846+
if (row_pad.pad % (2*ggml_type_size(tensor->type)) != 0) {
847+
return false;
848+
}
849+
850+
return ggml_row_size(tensor->type, tensor->ne[0]) % row_pad.alias_stride == 0;
851+
}
852+
853+
static size_t ggml_cuda_padded_row_size(const ggml_tensor * tensor, int device) {
854+
return ggml_row_size(tensor->type, tensor->ne[0]) + ggml_cuda_info().devices[device].row_pad.pad;
855+
}
856+
764857
static enum ggml_status ggml_backend_cuda_buffer_init_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor) {
765858
ggml_backend_cuda_buffer_context * ctx = (ggml_backend_cuda_buffer_context *)buffer->context;
766859

@@ -769,6 +862,17 @@ static enum ggml_status ggml_backend_cuda_buffer_init_tensor(ggml_backend_buffer
769862
return GGML_STATUS_SUCCESS;
770863
}
771864

865+
if (ggml_cuda_should_pad_weight(tensor, ctx->device)) {
866+
tensor->nb[1] = ggml_cuda_padded_row_size(tensor, ctx->device);
867+
tensor->nb[2] = tensor->nb[1]*tensor->ne[1];
868+
tensor->nb[3] = tensor->nb[2]*tensor->ne[2];
869+
870+
// the gaps between rows are never written, initialize them to 0 to avoid possible NaN values
871+
ggml_cuda_set_device(ctx->device);
872+
CUDA_CHECK(cudaMemset(tensor->data, 0, ggml_backend_buft_get_alloc_size(buffer->buft, tensor)));
873+
return GGML_STATUS_SUCCESS;
874+
}
875+
772876
if (ggml_is_quantized(tensor->type) && tensor->view_src == nullptr && ggml_backend_buffer_get_usage(buffer) != GGML_BACKEND_BUFFER_USAGE_COMPUTE) {
773877
// initialize padding to 0 to avoid possible NaN values
774878
const size_t original_size = ggml_nbytes(tensor);
@@ -929,6 +1033,11 @@ static size_t ggml_backend_cuda_buffer_type_get_alloc_size(ggml_backend_buffer_t
9291033
}
9301034
}
9311035

1036+
// reserve room for the row stride that ggml_backend_cuda_buffer_init_tensor will assign
1037+
if (ggml_cuda_should_pad_weight(tensor, buft_ctx->device)) {
1038+
size = ggml_cuda_padded_row_size(tensor, buft_ctx->device)*ggml_nrows(tensor);
1039+
}
1040+
9321041
return size;
9331042
}
9341043

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)