Skip to content

Commit 0f6fe20

Browse files
roberteg16claude
andcommitted
ggml-cuda: make the weight row padding per-architecture and tunable
The alias stride and the pad were compile-time constants gated to RDNA3.5. Both follow from the cache geometry -- line size times number of sets -- so another architecture needs different values, and neither CUDA nor HIP report enough to derive them: hipDeviceProp_t::l2CacheSize is documented as always returning 0, and set associativity is not exposed at all. Move the pair into cuda_device_info, resolved once per device from a table keyed on the compute capability, then overridden by GGML_CUDA_ROW_ALIAS_STRIDE and GGML_CUDA_ROW_PAD. Porting to another architecture becomes a table entry, and the values can be swept without rebuilding. Architectures with no entry keep packed rows, so each one is opted in only after being measured. Resolving once per device is also where the settings are validated: an alias stride that is not a power of two, or a pad that is a multiple of it and so leaves the rows aliasing, warns and falls back to no padding. GGML_CUDA_NO_PAD_WEIGHTS now resolves to a zero pad, leaving a single way for the padding to be off. Add the per-type check that was missing: ggml_cuda_should_use_mmf and ggml_cuda_should_use_mmvf reject a src0 whose strides are not a multiple of 2*type_size, so a pad that misses this would not fail or corrupt anything, it would quietly stop those kernels from being selected. Also correct the reason quantized weights are excluded. The matmul kernels do read the row stride, as nb[1]/type_size in mmq.cu and mmvq.cu; what a 128 byte pad breaks is that division being exact. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent e79ffe3 commit 0f6fe20

2 files changed

Lines changed: 90 additions & 14 deletions

File tree

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: 80 additions & 14 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,31 +828,30 @@ static void * ggml_backend_cuda_buffer_get_base(ggml_backend_buffer_t buffer) {
761828
return ctx->dev_ptr;
762829
}
763830

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
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.
769833
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;
834+
const ggml_cuda_row_pad_params & row_pad = ggml_cuda_info().devices[device].row_pad;
771835

772-
if (padding_disabled || !(tensor->flags & GGML_TENSOR_FLAG_PAD_ROWS)) {
836+
if (row_pad.pad == 0 || !(tensor->flags & GGML_TENSOR_FLAG_PAD_ROWS)) {
773837
return false;
774838
}
775839

776-
if (!GGML_CUDA_CC_IS_RDNA3_5(ggml_cuda_info().devices[device].cc)) {
840+
if (tensor->view_src != nullptr || ggml_is_quantized(tensor->type) || tensor->ne[1] <= 1) {
777841
return false;
778842
}
779843

780-
if (tensor->view_src != nullptr || ggml_is_quantized(tensor->type) || tensor->ne[1] <= 1) {
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) {
781847
return false;
782848
}
783849

784-
return ggml_row_size(tensor->type, tensor->ne[0]) % GGML_CUDA_ROW_ALIAS_STRIDE == 0;
850+
return ggml_row_size(tensor->type, tensor->ne[0]) % row_pad.alias_stride == 0;
785851
}
786852

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;
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;
789855
}
790856

791857
static enum ggml_status ggml_backend_cuda_buffer_init_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor) {
@@ -797,7 +863,7 @@ static enum ggml_status ggml_backend_cuda_buffer_init_tensor(ggml_backend_buffer
797863
}
798864

799865
if (ggml_cuda_should_pad_weight(tensor, ctx->device)) {
800-
tensor->nb[1] = ggml_cuda_padded_row_size(tensor);
866+
tensor->nb[1] = ggml_cuda_padded_row_size(tensor, ctx->device);
801867
tensor->nb[2] = tensor->nb[1]*tensor->ne[1];
802868
tensor->nb[3] = tensor->nb[2]*tensor->ne[2];
803869

@@ -969,7 +1035,7 @@ static size_t ggml_backend_cuda_buffer_type_get_alloc_size(ggml_backend_buffer_t
9691035

9701036
// reserve room for the row stride that ggml_backend_cuda_buffer_init_tensor will assign
9711037
if (ggml_cuda_should_pad_weight(tensor, buft_ctx->device)) {
972-
size = ggml_cuda_padded_row_size(tensor)*ggml_nrows(tensor);
1038+
size = ggml_cuda_padded_row_size(tensor, buft_ctx->device)*ggml_nrows(tensor);
9731039
}
9741040

9751041
return size;

0 commit comments

Comments
 (0)