Skip to content

Commit e844a68

Browse files
committed
fix(cuda): set explicit cuBLAS/hipBLAS workspace to stop VRAM creep
Addresses upstream llama.cpp issue ggml-org#19979: on ROCm/HIP, VRAM creeps up during long prompt processing when using non-f16 KV cache types (q4_0, q5_1, q8_0, turbo2/3/4), eventually causing OOM. Does not occur with f16/f16 KV cache. Root cause: cublasCreate() is called but cublasSetWorkspace() is not, so rocBLAS lazily allocates its own internal workspace on first GEMM and grows it monotonically as it encounters new Tensile kernel shapes across the varying batch sizes / KV cache lengths of prompt processing. The growth is invisible to llama.cpp's memory accounting (hipMemGetInfo snapshots it but the fit algorithm runs before the workspace exists). Fix: after cublasCreate(), allocate a fixed 32 MiB buffer via cudaMalloc and install it as the explicit workspace via cublasSetWorkspace (aliased to hipblasSetWorkspace on HIP). rocBLAS then uses our workspace instead of growing its own, and the workspace is freed deterministically in the backend context destructor. Added: - GGML_CUDA_CUBLAS_WORKSPACE_SIZE constant (32 MiB) in common.cuh - cublas_workspaces[] array in ggml_backend_cuda_context - cublasSetWorkspace -> hipblasSetWorkspace alias in vendors/hip.h - Workspace alloc + install in cublas_handle() - Workspace free in ~ggml_backend_cuda_context() (after cublasDestroy) Upstream reference: ggml-org#19979 The 32 MiB size matches NVIDIA cuBLAS recommendations for typical workloads. Bump if you see rocBLAS falling back to smaller kernels. Affects all ROCm GPU archs, especially GFX11 (RDNA3/3.5) where Tensile solution selection triggers more workspace growth.
1 parent 57daafb commit e844a68

3 files changed

Lines changed: 37 additions & 0 deletions

File tree

ggml/src/ggml-cuda/common.cuh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,13 +1354,26 @@ struct ggml_cuda_stream_context {
13541354
}
13551355
};
13561356

1357+
// Fixed cuBLAS / hipBLAS workspace size. Setting this explicitly via
1358+
// cublasSetWorkspace() on handle creation prevents the underlying BLAS
1359+
// library (particularly rocBLAS on GFX11/RDNA3/3.5) from lazily growing
1360+
// its own untracked workspace as it encounters new GEMM shapes during
1361+
// long prompt processing. Without this, VRAM creeps up until OOM.
1362+
//
1363+
// 32 MiB is large enough for the Tensile kernels rocBLAS ships and matches
1364+
// the NVIDIA cuBLAS recommended default for most workloads. Bump if you
1365+
// see rocBLAS falling back to smaller kernels in traces.
1366+
// Upstream bug: https://github.com/ggml-org/llama.cpp/issues/19979
1367+
#define GGML_CUDA_CUBLAS_WORKSPACE_SIZE (size_t(32) * 1024 * 1024)
1368+
13571369
struct ggml_backend_cuda_context {
13581370
int device;
13591371
std::string name;
13601372
cudaEvent_t copy_event = nullptr;
13611373

13621374
cudaStream_t streams[GGML_CUDA_MAX_DEVICES][GGML_CUDA_MAX_STREAMS] = { { nullptr } };
13631375
cublasHandle_t cublas_handles[GGML_CUDA_MAX_DEVICES] = {nullptr};
1376+
void * cublas_workspaces[GGML_CUDA_MAX_DEVICES] = {nullptr};
13641377

13651378
int curr_stream_no = 0;
13661379

@@ -1426,6 +1439,16 @@ struct ggml_backend_cuda_context {
14261439
ggml_cuda_set_device(device);
14271440
CUBLAS_CHECK(cublasCreate(&cublas_handles[device]));
14281441
CUBLAS_CHECK(cublasSetMathMode(cublas_handles[device], CUBLAS_TF32_TENSOR_OP_MATH));
1442+
1443+
// Allocate and install a fixed cuBLAS/hipBLAS workspace.
1444+
// Without this call, rocBLAS lazily allocates its own workspace
1445+
// and grows it as it encounters new GEMM shapes, producing
1446+
// untracked VRAM creep on GFX11/RDNA3/3.5. See the workspace
1447+
// size constant definition above and upstream issue #19979.
1448+
CUDA_CHECK(cudaMalloc(&cublas_workspaces[device], GGML_CUDA_CUBLAS_WORKSPACE_SIZE));
1449+
CUBLAS_CHECK(cublasSetWorkspace(cublas_handles[device],
1450+
cublas_workspaces[device],
1451+
GGML_CUDA_CUBLAS_WORKSPACE_SIZE));
14291452
}
14301453
return cublas_handles[device];
14311454
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,14 @@ ggml_backend_cuda_context::~ggml_backend_cuda_context() {
577577
}
578578
if (cublas_handles[i] != nullptr) {
579579
CUBLAS_CHECK(cublasDestroy(cublas_handles[i]));
580+
cublas_handles[i] = nullptr;
581+
}
582+
// Free the fixed cuBLAS/hipBLAS workspace installed in cublas_handle()
583+
// above. Must happen after cublasDestroy so the BLAS library doesn't
584+
// touch the buffer during its own cleanup.
585+
if (cublas_workspaces[i] != nullptr) {
586+
CUDA_CHECK(cudaFree(cublas_workspaces[i]));
587+
cublas_workspaces[i] = nullptr;
580588
}
581589
}
582590
}

ggml/src/ggml-cuda/vendors/hip.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@
6262
#define cublasGemmStridedBatchedEx hipblasGemmStridedBatchedEx
6363
#define cublasHandle_t hipblasHandle_t
6464
#define cublasSetMathMode(handle, mode) CUBLAS_STATUS_SUCCESS
65+
// hipblasSetWorkspace is available in hipBLAS 2.0+ (ROCm 6.0+). We call this
66+
// on handle creation with a fixed-size buffer to prevent rocBLAS from lazily
67+
// growing its own untracked workspace across varying GEMM shapes during long
68+
// prompt processing. Upstream issue:
69+
// https://github.com/ggml-org/llama.cpp/issues/19979
70+
#define cublasSetWorkspace hipblasSetWorkspace
6571
#define cublasSetStream hipblasSetStream
6672
#define cublasSgemm hipblasSgemm
6773
#define cublasStatus_t hipblasStatus_t

0 commit comments

Comments
 (0)